1133 lines
49 KiB
C++
1133 lines
49 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "AnimNextAnimGraphTraitInterfacesTest.h"
|
|
#include "AnimNextRuntimeTest.h"
|
|
#include "AnimNextTest.h"
|
|
|
|
#include "Misc/AutomationTest.h"
|
|
|
|
#if WITH_DEV_AUTOMATION_TESTS
|
|
|
|
#include "Serialization/MemoryReader.h"
|
|
|
|
#include "TraitCore/Trait.h"
|
|
#include "TraitCore/TraitReader.h"
|
|
#include "TraitCore/TraitRegistry.h"
|
|
#include "TraitCore/TraitInterfaceRegistry.h"
|
|
#include "TraitCore/TraitWriter.h"
|
|
#include "TraitCore/ExecutionContext.h"
|
|
#include "TraitCore/ITraitInterface.h"
|
|
#include "TraitCore/IScopedTraitInterface.h"
|
|
#include "TraitCore/NodeInstance.h"
|
|
#include "TraitCore/NodeTemplateBuilder.h"
|
|
#include "TraitCore/NodeTemplateRegistry.h"
|
|
#include "TraitInterfaces/IEvaluate.h"
|
|
#include "TraitInterfaces/IHierarchy.h"
|
|
#include "TraitInterfaces/IUpdate.h"
|
|
#include "Graph/AnimNextAnimationGraph.h"
|
|
#include "Graph/AnimNextAnimationGraphFactory.h"
|
|
|
|
//****************************************************************************
|
|
// AnimNext Runtime TraitInterfaces Tests
|
|
//****************************************************************************
|
|
|
|
namespace UE::AnimNext
|
|
{
|
|
namespace Private
|
|
{
|
|
static TArray<FTraitUID>* UpdatedTraits = nullptr;
|
|
static TArray<FTraitUID>* EvaluatedTraits = nullptr;
|
|
|
|
static FName TestTag(TEXT("MyTag"));
|
|
static TArray<bool>* IsTagInScope = nullptr;
|
|
static bool AutoPopTag = false;
|
|
}
|
|
|
|
struct FTraitWithNoChildren : FBaseTrait, IUpdate, IEvaluate
|
|
{
|
|
DECLARE_ANIM_TRAIT(FTraitWithNoChildren, FBaseTrait)
|
|
|
|
// IUpdate impl
|
|
virtual void PreUpdate(FUpdateTraversalContext& Context, const TTraitBinding<IUpdate>& Binding, const FTraitUpdateState& TraitState) const override
|
|
{
|
|
if (Private::UpdatedTraits != nullptr)
|
|
{
|
|
Private::UpdatedTraits->Add(FTraitWithNoChildren::TraitUID);
|
|
}
|
|
|
|
IUpdate::PreUpdate(Context, Binding, TraitState);
|
|
}
|
|
|
|
virtual void PostUpdate(FUpdateTraversalContext& Context, const TTraitBinding<IUpdate>& Binding, const FTraitUpdateState& TraitState) const override
|
|
{
|
|
if (Private::UpdatedTraits != nullptr)
|
|
{
|
|
Private::UpdatedTraits->Add(FTraitWithNoChildren::TraitUID);
|
|
}
|
|
|
|
IUpdate::PostUpdate(Context, Binding, TraitState);
|
|
}
|
|
|
|
// IEvaluate impl
|
|
virtual void PreEvaluate(FEvaluateTraversalContext& Context, const TTraitBinding<IEvaluate>& Binding) const override
|
|
{
|
|
if (Private::EvaluatedTraits != nullptr)
|
|
{
|
|
Private::EvaluatedTraits->Add(FTraitWithNoChildren::TraitUID);
|
|
}
|
|
|
|
IEvaluate::PreEvaluate(Context, Binding);
|
|
}
|
|
|
|
virtual void PostEvaluate(FEvaluateTraversalContext& Context, const TTraitBinding<IEvaluate>& Binding) const override
|
|
{
|
|
if (Private::EvaluatedTraits != nullptr)
|
|
{
|
|
Private::EvaluatedTraits->Add(FTraitWithNoChildren::TraitUID);
|
|
}
|
|
|
|
IEvaluate::PostEvaluate(Context, Binding);
|
|
}
|
|
};
|
|
|
|
// Trait implementation boilerplate
|
|
#define TRAIT_INTERFACE_ENUMERATOR(GeneratorMacro) \
|
|
GeneratorMacro(IEvaluate) \
|
|
GeneratorMacro(IUpdate) \
|
|
|
|
GENERATE_ANIM_TRAIT_IMPLEMENTATION(FTraitWithNoChildren, TRAIT_INTERFACE_ENUMERATOR, NULL_ANIM_TRAIT_INTERFACE_ENUMERATOR, NULL_ANIM_TRAIT_EVENT_ENUMERATOR)
|
|
#undef TRAIT_INTERFACE_ENUMERATOR
|
|
|
|
// This trait does not update or evaluate
|
|
struct FTraitWithOneChild : FBaseTrait, IHierarchy
|
|
{
|
|
DECLARE_ANIM_TRAIT(FTraitWithOneChild, FBaseTrait)
|
|
|
|
using FSharedData = FTraitWithOneChildSharedData;
|
|
|
|
struct FInstanceData : FTrait::FInstanceData
|
|
{
|
|
FTraitPtr Child;
|
|
|
|
void Construct(const FExecutionContext& Context, const FTraitBinding& Binding)
|
|
{
|
|
Child = Context.AllocateNodeInstance(Binding.GetTraitPtr(), Binding.GetSharedData<FSharedData>()->Child);
|
|
}
|
|
};
|
|
|
|
// IHierarchy impl
|
|
virtual uint32 GetNumChildren(const FExecutionContext& Context, const TTraitBinding<IHierarchy>& Binding) const override
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
virtual void GetChildren(const FExecutionContext& Context, const TTraitBinding<IHierarchy>& Binding, FChildrenArray& Children) const override
|
|
{
|
|
const FInstanceData* InstanceData = Binding.GetInstanceData<FInstanceData>();
|
|
|
|
Children.Add(InstanceData->Child);
|
|
}
|
|
};
|
|
|
|
// Trait implementation boilerplate
|
|
#define TRAIT_INTERFACE_ENUMERATOR(GeneratorMacro) \
|
|
GeneratorMacro(IHierarchy) \
|
|
|
|
GENERATE_ANIM_TRAIT_IMPLEMENTATION(FTraitWithOneChild, TRAIT_INTERFACE_ENUMERATOR, NULL_ANIM_TRAIT_INTERFACE_ENUMERATOR, NULL_ANIM_TRAIT_EVENT_ENUMERATOR)
|
|
#undef TRAIT_INTERFACE_ENUMERATOR
|
|
|
|
struct FTraitWithChildren : FBaseTrait, IHierarchy, IUpdate, IUpdateTraversal, IEvaluate
|
|
{
|
|
DECLARE_ANIM_TRAIT(FTraitWithChildren, FBaseTrait)
|
|
|
|
using FSharedData = FTraitWithChildrenSharedData;
|
|
|
|
struct FInstanceData : FTrait::FInstanceData
|
|
{
|
|
FTraitPtr Children[2];
|
|
|
|
void Construct(const FExecutionContext& Context, const FTraitBinding& Binding)
|
|
{
|
|
Children[0] = Context.AllocateNodeInstance(Binding.GetTraitPtr(), Binding.GetSharedData<FSharedData>()->Children[0]);
|
|
Children[1] = Context.AllocateNodeInstance(Binding.GetTraitPtr(), Binding.GetSharedData<FSharedData>()->Children[1]);
|
|
}
|
|
};
|
|
|
|
// IHierarchy impl
|
|
virtual uint32 GetNumChildren(const FExecutionContext& Context, const TTraitBinding<IHierarchy>& Binding) const override
|
|
{
|
|
return 2;
|
|
}
|
|
|
|
virtual void GetChildren(const FExecutionContext& Context, const TTraitBinding<IHierarchy>& Binding, FChildrenArray& Children) const override
|
|
{
|
|
const FInstanceData* InstanceData = Binding.GetInstanceData<FInstanceData>();
|
|
|
|
Children.Add(InstanceData->Children[0]);
|
|
Children.Add(InstanceData->Children[1]);
|
|
}
|
|
|
|
// IUpdate impl
|
|
virtual void PreUpdate(FUpdateTraversalContext& Context, const TTraitBinding<IUpdate>& Binding, const FTraitUpdateState& TraitState) const override
|
|
{
|
|
if (Private::UpdatedTraits != nullptr)
|
|
{
|
|
Private::UpdatedTraits->Add(FTraitWithChildren::TraitUID);
|
|
}
|
|
|
|
IUpdate::PreUpdate(Context, Binding, TraitState);
|
|
}
|
|
|
|
virtual void PostUpdate(FUpdateTraversalContext& Context, const TTraitBinding<IUpdate>& Binding, const FTraitUpdateState& TraitState) const override
|
|
{
|
|
if (Private::UpdatedTraits != nullptr)
|
|
{
|
|
Private::UpdatedTraits->Add(FTraitWithChildren::TraitUID);
|
|
}
|
|
|
|
IUpdate::PostUpdate(Context, Binding, TraitState);
|
|
}
|
|
|
|
// IUpdateTraversal impl
|
|
virtual void QueueChildrenForTraversal(FUpdateTraversalContext& Context, const TTraitBinding<IUpdateTraversal>& Binding, const FTraitUpdateState& TraitState, FUpdateTraversalQueue& TraversalQueue) const override
|
|
{
|
|
const FInstanceData* InstanceData = Binding.GetInstanceData<FInstanceData>();
|
|
|
|
TraversalQueue.Push(InstanceData->Children[0], TraitState);
|
|
TraversalQueue.Push(InstanceData->Children[1], TraitState);
|
|
}
|
|
|
|
// IEvaluate impl
|
|
virtual void PreEvaluate(FEvaluateTraversalContext& Context, const TTraitBinding<IEvaluate>& Binding) const override
|
|
{
|
|
if (Private::EvaluatedTraits != nullptr)
|
|
{
|
|
Private::EvaluatedTraits->Add(FTraitWithChildren::TraitUID);
|
|
}
|
|
|
|
IEvaluate::PreEvaluate(Context, Binding);
|
|
}
|
|
|
|
virtual void PostEvaluate(FEvaluateTraversalContext& Context, const TTraitBinding<IEvaluate>& Binding) const override
|
|
{
|
|
if (Private::EvaluatedTraits != nullptr)
|
|
{
|
|
Private::EvaluatedTraits->Add(FTraitWithChildren::TraitUID);
|
|
}
|
|
|
|
IEvaluate::PostEvaluate(Context, Binding);
|
|
}
|
|
};
|
|
|
|
// Trait implementation boilerplate
|
|
#define TRAIT_INTERFACE_ENUMERATOR(GeneratorMacro) \
|
|
GeneratorMacro(IEvaluate) \
|
|
GeneratorMacro(IHierarchy) \
|
|
GeneratorMacro(IUpdate) \
|
|
GeneratorMacro(IUpdateTraversal) \
|
|
|
|
GENERATE_ANIM_TRAIT_IMPLEMENTATION(FTraitWithChildren, TRAIT_INTERFACE_ENUMERATOR, NULL_ANIM_TRAIT_INTERFACE_ENUMERATOR, NULL_ANIM_TRAIT_EVENT_ENUMERATOR)
|
|
#undef TRAIT_INTERFACE_ENUMERATOR
|
|
|
|
struct IScopedTagInterface : IScopedTraitInterface
|
|
{
|
|
DECLARE_ANIM_TRAIT_INTERFACE(IScopedTagInterface)
|
|
|
|
virtual FName GetTag(const FExecutionContext& Context, const TTraitBinding<IScopedTagInterface>& Binding) const;
|
|
|
|
static bool IsTagInScope(const FExecutionContext& Context, FName Tag);
|
|
};
|
|
|
|
template<>
|
|
struct TTraitBinding<IScopedTagInterface> : FTraitBinding
|
|
{
|
|
FName GetTag(const FExecutionContext& Context) const
|
|
{
|
|
return GetInterface()->GetTag(Context, *this);
|
|
}
|
|
|
|
protected:
|
|
const IScopedTagInterface* GetInterface() const { return GetInterfaceTyped<IScopedTagInterface>(); }
|
|
};
|
|
|
|
FName IScopedTagInterface::GetTag(const FExecutionContext& Context, const TTraitBinding<IScopedTagInterface>& Binding) const
|
|
{
|
|
TTraitBinding<IScopedTagInterface> SuperBinding;
|
|
if (Binding.GetStackInterfaceSuper(SuperBinding))
|
|
{
|
|
return SuperBinding.GetTag(Context);
|
|
}
|
|
|
|
return NAME_None;
|
|
}
|
|
|
|
bool IScopedTagInterface::IsTagInScope(const FExecutionContext& Context, FName Tag)
|
|
{
|
|
bool bResult = false;
|
|
|
|
Context.ForEachScopedInterface<IScopedTagInterface>([&Context, Tag, &bResult](TTraitBinding<IScopedTagInterface>& InterfaceBinding)
|
|
{
|
|
if (Tag == InterfaceBinding.GetTag(Context))
|
|
{
|
|
// We found our tag, stop iterating
|
|
bResult = true;
|
|
return false;
|
|
}
|
|
|
|
// Keep searching
|
|
return true;
|
|
});
|
|
|
|
return bResult;
|
|
}
|
|
|
|
// Adds a scoped tag
|
|
struct FScopedTagTrait : FAdditiveTrait, IScopedTagInterface, IUpdate
|
|
{
|
|
DECLARE_ANIM_TRAIT(FScopedTagTrait, FAdditiveTrait)
|
|
|
|
// IScopedTagInterface impl
|
|
virtual FName GetTag(const FExecutionContext& Context, const TTraitBinding<IScopedTagInterface>& Binding) const override
|
|
{
|
|
return Private::TestTag;
|
|
}
|
|
|
|
// IUpdate impl
|
|
virtual void PreUpdate(FUpdateTraversalContext& Context, const TTraitBinding<IUpdate>& Binding, const FTraitUpdateState& TraitState) const override
|
|
{
|
|
Context.PushScopedInterface<IScopedTagInterface>(Binding);
|
|
|
|
IUpdate::PreUpdate(Context, Binding, TraitState);
|
|
}
|
|
|
|
virtual void PostUpdate(FUpdateTraversalContext& Context, const TTraitBinding<IUpdate>& Binding, const FTraitUpdateState& TraitState) const override
|
|
{
|
|
if (!Private::AutoPopTag)
|
|
{
|
|
ensure(Context.PopScopedInterface<IScopedTagInterface>(Binding));
|
|
}
|
|
|
|
IUpdate::PostUpdate(Context, Binding, TraitState);
|
|
}
|
|
};
|
|
|
|
// Trait implementation boilerplate
|
|
#define TRAIT_INTERFACE_ENUMERATOR(GeneratorMacro) \
|
|
GeneratorMacro(IScopedTagInterface) \
|
|
GeneratorMacro(IUpdate) \
|
|
|
|
GENERATE_ANIM_TRAIT_IMPLEMENTATION(FScopedTagTrait, TRAIT_INTERFACE_ENUMERATOR, NULL_ANIM_TRAIT_INTERFACE_ENUMERATOR, NULL_ANIM_TRAIT_EVENT_ENUMERATOR)
|
|
#undef TRAIT_INTERFACE_ENUMERATOR
|
|
|
|
// Tests if we have a scoped tag
|
|
struct FTestScopedTagTrait : FAdditiveTrait, IUpdate
|
|
{
|
|
DECLARE_ANIM_TRAIT(FTestScopedTagTrait, FAdditiveTrait)
|
|
|
|
// IUpdate impl
|
|
virtual void PreUpdate(FUpdateTraversalContext& Context, const TTraitBinding<IUpdate>& Binding, const FTraitUpdateState& TraitState) const override
|
|
{
|
|
if (Private::IsTagInScope != nullptr)
|
|
{
|
|
Private::IsTagInScope->Add(IScopedTagInterface::IsTagInScope(Context, Private::TestTag));
|
|
}
|
|
|
|
IUpdate::PreUpdate(Context, Binding, TraitState);
|
|
}
|
|
|
|
virtual void PostUpdate(FUpdateTraversalContext& Context, const TTraitBinding<IUpdate>& Binding, const FTraitUpdateState& TraitState) const override
|
|
{
|
|
if (Private::IsTagInScope != nullptr)
|
|
{
|
|
Private::IsTagInScope->Add(IScopedTagInterface::IsTagInScope(Context, Private::TestTag));
|
|
}
|
|
|
|
IUpdate::PostUpdate(Context, Binding, TraitState);
|
|
}
|
|
};
|
|
|
|
// Trait implementation boilerplate
|
|
#define TRAIT_INTERFACE_ENUMERATOR(GeneratorMacro) \
|
|
GeneratorMacro(IUpdate) \
|
|
|
|
GENERATE_ANIM_TRAIT_IMPLEMENTATION(FTestScopedTagTrait, TRAIT_INTERFACE_ENUMERATOR, NULL_ANIM_TRAIT_INTERFACE_ENUMERATOR, NULL_ANIM_TRAIT_EVENT_ENUMERATOR)
|
|
#undef TRAIT_INTERFACE_ENUMERATOR
|
|
|
|
|
|
static const FText GInterfaceTestAName = FText::FromString(TEXT("Interface Test A"));
|
|
static const FText GInterfaceTestAShortName = FText::FromString(TEXT("ITA"));
|
|
|
|
struct ITraitInterfaceTestA : ITraitInterface
|
|
{
|
|
DECLARE_ANIM_TRAIT_INTERFACE(ITraitInterfaceTestA)
|
|
|
|
#if WITH_EDITOR
|
|
virtual const FText& GetDisplayName() const override
|
|
{
|
|
return GInterfaceTestAName;
|
|
}
|
|
|
|
virtual const FText& GetDisplayShortName() const override
|
|
{
|
|
return GInterfaceTestAShortName;
|
|
}
|
|
#endif // WITH_EDITOR
|
|
};
|
|
|
|
static const FText GInterfaceTestBName = FText::FromString(TEXT("Interface Test B"));
|
|
static const FText GInterfaceTestBShortName = FText::FromString(TEXT("ITB"));
|
|
|
|
struct ITraitInterfaceTestB : ITraitInterface
|
|
{
|
|
DECLARE_ANIM_TRAIT_INTERFACE(ITraitInterfaceTestB)
|
|
|
|
#if WITH_EDITOR
|
|
virtual const FText& GetDisplayName() const override
|
|
{
|
|
return GInterfaceTestBName;
|
|
}
|
|
|
|
virtual const FText& GetDisplayShortName() const override
|
|
{
|
|
return GInterfaceTestBShortName;
|
|
}
|
|
|
|
virtual bool IsInternal() const override
|
|
{
|
|
return true;
|
|
}
|
|
#endif // WITH_EDITOR
|
|
};
|
|
|
|
} // end namespace UE::AnimNext
|
|
|
|
// --- Runtime Test Trait Interface Registry ---
|
|
|
|
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FAnimationAnimNextRuntimeTest_TraitInterfaceRegistry, "Animation.AnimNext.Runtime.TraitInterfaceRegistry", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
|
|
|
|
bool FAnimationAnimNextRuntimeTest_TraitInterfaceRegistry::RunTest(const FString& InParameters)
|
|
{
|
|
using namespace UE::AnimNext;
|
|
|
|
{
|
|
FTraitInterfaceRegistry& Registry = FTraitInterfaceRegistry::Get();
|
|
|
|
// Some traits already exist in the engine, keep track of them
|
|
const uint32 NumAutoRegisteredTraitInterfaces = Registry.GetNum();
|
|
|
|
AddErrorIfFalse(Registry.Find(ITraitInterfaceTestA::InterfaceUID) == nullptr, "FAnimationAnimNextRuntimeTest_TraitInterfaceRegistry -> Registry should not contain the Test Interface A");
|
|
AddErrorIfFalse(Registry.Find(ITraitInterfaceTestB::InterfaceUID) == nullptr, "FAnimationAnimNextRuntimeTest_TraitInterfaceRegistry -> Registry should not contain the Test Interface B");
|
|
|
|
{
|
|
AUTO_REGISTER_ANIM_TRAIT_INTERFACE(ITraitInterfaceTestA)
|
|
|
|
AddErrorIfFalse(Registry.GetNum() == NumAutoRegisteredTraitInterfaces + 1, "FAnimationAnimNextRuntimeTest_TraitInterfaceRegistry -> Registry should contain 1 new trait interface");
|
|
|
|
const ITraitInterface* TraitInterfaceA = Registry.Find(ITraitInterfaceTestA::InterfaceUID);
|
|
AddErrorIfFalse(TraitInterfaceA->GetInterfaceUID() == ITraitInterfaceTestA::InterfaceUID, "FAnimationAnimNextRuntimeTest_TraitInterfaceRegistry -> Incorrect InterfaceUID for ITraitInterfaceTestA");
|
|
|
|
#if WITH_EDITOR
|
|
AddErrorIfFalse(TraitInterfaceA->GetDisplayName().EqualTo(GInterfaceTestAName), "FAnimationAnimNextRuntimeTest_TraitInterfaceRegistry -> Incorrect Interface Display Name for ITraitInterfaceTestA");
|
|
AddErrorIfFalse(TraitInterfaceA->GetDisplayShortName().EqualTo(GInterfaceTestAShortName), "FAnimationAnimNextRuntimeTest_TraitInterfaceRegistry -> Incorrect Interface Display Short Name for ITraitInterfaceTestA");
|
|
|
|
AddErrorIfFalse(TraitInterfaceA->IsInternal() == false, "FAnimationAnimNextRuntimeTest_TraitInterfaceRegistry -> Incorrect Interface Internal flag for ITraitInterfaceTestA");
|
|
#endif // WITH_EDITOR
|
|
|
|
{
|
|
AUTO_REGISTER_ANIM_TRAIT_INTERFACE(ITraitInterfaceTestB)
|
|
|
|
AddErrorIfFalse(Registry.GetNum() == NumAutoRegisteredTraitInterfaces + 2, "FAnimationAnimNextRuntimeTest_TraitInterfaceRegistry -> Registry should contain 2 new trait interfaces");
|
|
|
|
const ITraitInterface* TraitInterfaceB = Registry.Find(ITraitInterfaceTestB::InterfaceUID);
|
|
AddErrorIfFalse(TraitInterfaceB->GetInterfaceUID() == ITraitInterfaceTestB::InterfaceUID, "FAnimationAnimNextRuntimeTest_TraitInterfaceRegistry -> Incorrect InterfaceUID for ITraitInterfaceTestB");
|
|
|
|
#if WITH_EDITOR
|
|
AddErrorIfFalse(TraitInterfaceB->GetDisplayName().EqualTo(GInterfaceTestBName), "FAnimationAnimNextRuntimeTest_TraitInterfaceRegistry -> Incorrect Interface Display Name for ITraitInterfaceTestB");
|
|
AddErrorIfFalse(TraitInterfaceB->GetDisplayShortName().EqualTo(GInterfaceTestBShortName), "FAnimationAnimNextRuntimeTest_TraitInterfaceRegistry -> Incorrect Interface Display Short Name for ITraitInterfaceTestB");
|
|
|
|
AddErrorIfFalse(TraitInterfaceB->IsInternal() == true, "FAnimationAnimNextRuntimeTest_TraitInterfaceRegistry -> Incorrect Interface Internal flag for ITraitInterfaceTestB");
|
|
#endif // WITH_EDITOR
|
|
}
|
|
|
|
AddErrorIfFalse(Registry.Find(ITraitInterfaceTestB::InterfaceUID) == nullptr, "FAnimationAnimNextRuntimeTest_TraitInterfaceRegistry -> Registry should not contain the Test Interface B");
|
|
|
|
AddErrorIfFalse(Registry.GetNum() == NumAutoRegisteredTraitInterfaces + 1, "FAnimationAnimNextRuntimeTest_TraitInterfaceRegistry -> Registry should contain 1 new trait interface");
|
|
}
|
|
|
|
AddErrorIfFalse(Registry.Find(ITraitInterfaceTestA::InterfaceUID) == nullptr, "FAnimationAnimNextRuntimeTest_TraitInterfaceRegistry -> Registry should not contain the Test Interface A");
|
|
|
|
AddErrorIfFalse(Registry.GetNum() == NumAutoRegisteredTraitInterfaces, "FAnimationAnimNextRuntimeTest_TraitInterfaceRegistry -> Registry should contain 0 new trait interfaces");
|
|
}
|
|
|
|
Tests::FUtils::CleanupAfterTests();
|
|
|
|
return true;
|
|
}
|
|
|
|
// --- Trait Interfaces IHierarchy Test ---
|
|
|
|
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FAnimationAnimNextRuntimeTest_IHierarchy, "Animation.AnimNext.Runtime.IHierarchy", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
|
|
|
|
bool FAnimationAnimNextRuntimeTest_IHierarchy::RunTest(const FString& InParameters)
|
|
{
|
|
using namespace UE::AnimNext;
|
|
|
|
{
|
|
AUTO_REGISTER_ANIM_TRAIT(FTraitWithNoChildren)
|
|
AUTO_REGISTER_ANIM_TRAIT(FTraitWithOneChild)
|
|
AUTO_REGISTER_ANIM_TRAIT(FTraitWithChildren)
|
|
|
|
UFactory* GraphFactory = NewObject<UAnimNextAnimationGraphFactory>();
|
|
UAnimNextAnimationGraph* AnimationGraph = CastChecked<UAnimNextAnimationGraph>(GraphFactory->FactoryCreateNew(UAnimNextAnimationGraph::StaticClass(), GetTransientPackage(), TEXT("TestAnimNextGraph"), RF_Transient, nullptr, nullptr, NAME_None));
|
|
UE_RETURN_ON_ERROR(AnimationGraph != nullptr, "FAnimationAnimNextRuntimeTest_GraphTraitEvent -> Failed to create animation graph");
|
|
|
|
FScopedClearNodeTemplateRegistry ScopedClearNodeTemplateRegistry;
|
|
FNodeTemplateRegistry& Registry = FNodeTemplateRegistry::Get();
|
|
|
|
// We create a few node templates
|
|
// Template A has a single trait with no children
|
|
TArray<FTraitUID> NodeTemplateTraitListA;
|
|
NodeTemplateTraitListA.Add(FTraitWithNoChildren::TraitUID);
|
|
|
|
// Template B has a single trait with one child
|
|
TArray<FTraitUID> NodeTemplateTraitListB;
|
|
NodeTemplateTraitListB.Add(FTraitWithOneChild::TraitUID);
|
|
|
|
// Template C has two traits, each with one child
|
|
TArray<FTraitUID> NodeTemplateTraitListC;
|
|
NodeTemplateTraitListC.Add(FTraitWithOneChild::TraitUID);
|
|
NodeTemplateTraitListC.Add(FTraitWithOneChild::TraitUID);
|
|
|
|
// Template D has a single trait with children
|
|
TArray<FTraitUID> NodeTemplateTraitListD;
|
|
NodeTemplateTraitListD.Add(FTraitWithChildren::TraitUID);
|
|
|
|
// Populate our node template registry
|
|
TArray<uint8> NodeTemplateBufferA, NodeTemplateBufferB, NodeTemplateBufferC, NodeTemplateBufferD;
|
|
const FNodeTemplate* NodeTemplateA = FNodeTemplateBuilder::BuildNodeTemplate(NodeTemplateTraitListA, NodeTemplateBufferA);
|
|
const FNodeTemplate* NodeTemplateB = FNodeTemplateBuilder::BuildNodeTemplate(NodeTemplateTraitListB, NodeTemplateBufferB);
|
|
const FNodeTemplate* NodeTemplateC = FNodeTemplateBuilder::BuildNodeTemplate(NodeTemplateTraitListC, NodeTemplateBufferC);
|
|
const FNodeTemplate* NodeTemplateD = FNodeTemplateBuilder::BuildNodeTemplate(NodeTemplateTraitListD, NodeTemplateBufferD);
|
|
|
|
// Build our graph, it as follow (each node template has a single node instance):
|
|
// NodeA has no children
|
|
// NodeB has one child: NodeA
|
|
// NodeC has two children: NodeA and NodeB (but both traits are base, only NodeB will be referenced)
|
|
// NodeD has two children: NodeA and NodeC
|
|
|
|
TArray<FNodeHandle> NodeHandles;
|
|
|
|
// Write our graph
|
|
TArray<uint8> GraphSharedDataArchiveBuffer;
|
|
TArray<TObjectPtr<UObject>> GraphReferencedObjects;
|
|
TArray<FSoftObjectPath> GraphReferencedSoftObjects;
|
|
{
|
|
FTraitWriter TraitWriter;
|
|
|
|
NodeHandles.Add(TraitWriter.RegisterNode(*NodeTemplateA));
|
|
NodeHandles.Add(TraitWriter.RegisterNode(*NodeTemplateB));
|
|
NodeHandles.Add(TraitWriter.RegisterNode(*NodeTemplateC));
|
|
NodeHandles.Add(TraitWriter.RegisterNode(*NodeTemplateD));
|
|
|
|
// We don't have trait properties
|
|
TArray<TMap<FName, FString>> TraitPropertiesA;
|
|
TraitPropertiesA.AddDefaulted(NodeTemplateTraitListA.Num());
|
|
|
|
TArray<TMap<FName, FString>> TraitPropertiesB;
|
|
TraitPropertiesB.AddDefaulted(NodeTemplateTraitListB.Num());
|
|
TraitPropertiesB[0].Add(TEXT("Child"), ToString<FTraitWithOneChild::FSharedData>(TEXT("Child"), FAnimNextTraitHandle(NodeHandles[0])));
|
|
|
|
TArray<TMap<FName, FString>> TraitPropertiesC;
|
|
TraitPropertiesC.AddDefaulted(NodeTemplateTraitListC.Num());
|
|
TraitPropertiesC[0].Add(TEXT("Child"), ToString<FTraitWithOneChild::FSharedData>(TEXT("Child"), FAnimNextTraitHandle(NodeHandles[0])));
|
|
TraitPropertiesC[1].Add(TEXT("Child"), ToString<FTraitWithOneChild::FSharedData>(TEXT("Child"), FAnimNextTraitHandle(NodeHandles[1])));
|
|
|
|
TArray<TMap<FName, FString>> TraitPropertiesD;
|
|
TraitPropertiesD.AddDefaulted(NodeTemplateTraitListD.Num());
|
|
FAnimNextTraitHandle ChildrenHandlesD[2] = { FAnimNextTraitHandle(NodeHandles[0]), FAnimNextTraitHandle(NodeHandles[2], 1)};
|
|
TraitPropertiesD[0].Add(TEXT("Children"), ToString<FTraitWithChildren::FSharedData>(TEXT("Children"), ChildrenHandlesD));
|
|
|
|
TraitWriter.BeginNodeWriting();
|
|
TraitWriter.WriteNode(NodeHandles[0],
|
|
[&TraitPropertiesA](uint32 TraitIndex, FName PropertyName)
|
|
{
|
|
return TraitPropertiesA[TraitIndex][PropertyName];
|
|
},
|
|
[](uint32 TraitIndex, FName PropertyName)
|
|
{
|
|
return MAX_uint16;
|
|
});
|
|
TraitWriter.WriteNode(NodeHandles[1],
|
|
[&TraitPropertiesB](uint32 TraitIndex, FName PropertyName)
|
|
{
|
|
return TraitPropertiesB[TraitIndex][PropertyName];
|
|
},
|
|
[](uint32 TraitIndex, FName PropertyName)
|
|
{
|
|
return MAX_uint16;
|
|
});
|
|
TraitWriter.WriteNode(NodeHandles[2],
|
|
[&TraitPropertiesC](uint32 TraitIndex, FName PropertyName)
|
|
{
|
|
return TraitPropertiesC[TraitIndex][PropertyName];
|
|
},
|
|
[](uint32 TraitIndex, FName PropertyName)
|
|
{
|
|
return MAX_uint16;
|
|
});
|
|
TraitWriter.WriteNode(NodeHandles[3],
|
|
[&TraitPropertiesD](uint32 TraitIndex, FName PropertyName)
|
|
{
|
|
return TraitPropertiesD[TraitIndex][PropertyName];
|
|
},
|
|
[](uint32 TraitIndex, FName PropertyName)
|
|
{
|
|
return MAX_uint16;
|
|
});
|
|
TraitWriter.EndNodeWriting();
|
|
|
|
AddErrorIfFalse(TraitWriter.GetErrorState() == FTraitWriter::EErrorState::None, "FAnimationAnimNextRuntimeTest_IHierarchy -> Failed to write traits");
|
|
GraphSharedDataArchiveBuffer = TraitWriter.GetGraphSharedData();
|
|
GraphReferencedObjects = TraitWriter.GetGraphReferencedObjects();
|
|
GraphReferencedSoftObjects = TraitWriter.GetGraphReferencedSoftObjects();
|
|
}
|
|
|
|
// Read our graph
|
|
FTestUtils::LoadFromArchiveBuffer(*AnimationGraph, NodeHandles, GraphSharedDataArchiveBuffer);
|
|
|
|
TSharedPtr<FAnimNextGraphInstance> GraphInstance = AnimationGraph->AllocateInstance();
|
|
|
|
FExecutionContext Context(*GraphInstance.Get());
|
|
|
|
{
|
|
FMemMark Mark(FMemStack::Get());
|
|
|
|
FAnimNextTraitHandle RootHandle(NodeHandles[3]); // Point to NodeD, first base trait
|
|
|
|
FTraitPtr NodeDPtr = Context.AllocateNodeInstance(*GraphInstance.Get(), RootHandle);
|
|
AddErrorIfFalse(NodeDPtr.IsValid(), "FAnimationAnimNextRuntimeTest_IHierarchy -> Failed to allocate root node instance");
|
|
|
|
FTraitStackBinding StackNodeD;
|
|
AddErrorIfFalse(Context.GetStack(NodeDPtr, StackNodeD), "FAnimationAnimNextRuntimeTest_IHierarchy -> Failed to bind to trait stack");
|
|
|
|
TTraitBinding<IHierarchy> HierarchyBindingNodeD;
|
|
AddErrorIfFalse(StackNodeD.GetInterface(HierarchyBindingNodeD), "FAnimationAnimNextRuntimeTest_IHierarchy -> IHierarchy not found");
|
|
|
|
FChildrenArray ChildrenNodeD;
|
|
HierarchyBindingNodeD.GetChildren(Context, ChildrenNodeD);
|
|
|
|
AddErrorIfFalse(ChildrenNodeD.Num() == 2, "FAnimationAnimNextRuntimeTest_IHierarchy -> Expected 2 children");
|
|
AddErrorIfFalse(HierarchyBindingNodeD.GetNumChildren(Context) == 2, "FAnimationAnimNextRuntimeTest_IHierarchy -> Expected 2 children");
|
|
AddErrorIfFalse(ChildrenNodeD[0].IsValid() && ChildrenNodeD[0].GetNodeInstance()->GetNodeHandle() == NodeHandles[0], "FAnimationAnimNextRuntimeTest_IHierarchy -> Expected child: NodeA");
|
|
AddErrorIfFalse(ChildrenNodeD[1].IsValid() && ChildrenNodeD[1].GetNodeInstance()->GetNodeHandle() == NodeHandles[2], "FAnimationAnimNextRuntimeTest_IHierarchy -> Expected child: NodeC");
|
|
|
|
ChildrenNodeD.Reset();
|
|
IHierarchy::GetStackChildren(Context, StackNodeD, ChildrenNodeD);
|
|
|
|
AddErrorIfFalse(ChildrenNodeD.Num() == 2, "FAnimationAnimNextRuntimeTest_IHierarchy -> Expected 2 children");
|
|
AddErrorIfFalse(IHierarchy::GetNumStackChildren(Context, StackNodeD) == 2, "FAnimationAnimNextRuntimeTest_IHierarchy -> Expected 2 children");
|
|
AddErrorIfFalse(ChildrenNodeD[0].IsValid() && ChildrenNodeD[0].GetNodeInstance()->GetNodeHandle() == NodeHandles[0], "FAnimationAnimNextRuntimeTest_IHierarchy -> Expected child: NodeA");
|
|
AddErrorIfFalse(ChildrenNodeD[1].IsValid() && ChildrenNodeD[1].GetNodeInstance()->GetNodeHandle() == NodeHandles[2], "FAnimationAnimNextRuntimeTest_IHierarchy -> Expected child: NodeC");
|
|
|
|
{
|
|
FTraitStackBinding StackNodeC;
|
|
AddErrorIfFalse(Context.GetStack(ChildrenNodeD[1], StackNodeC), "FAnimationAnimNextRuntimeTest_IHierarchy -> Failed to bind to trait stack");
|
|
|
|
TTraitBinding<IHierarchy> HierarchyBindingNodeC;
|
|
AddErrorIfFalse(StackNodeC.GetInterface(HierarchyBindingNodeC), "FAnimationAnimNextRuntimeTest_IHierarchy -> IHierarchy not found");
|
|
|
|
FChildrenArray ChildrenNodeC;
|
|
HierarchyBindingNodeC.GetChildren(Context, ChildrenNodeC);
|
|
|
|
AddErrorIfFalse(ChildrenNodeC.Num() == 1, "FAnimationAnimNextRuntimeTest_IHierarchy -> Expected 1 child");
|
|
AddErrorIfFalse(HierarchyBindingNodeC.GetNumChildren(Context) == 1, "FAnimationAnimNextRuntimeTest_IHierarchy -> Expected 1 child");
|
|
AddErrorIfFalse(ChildrenNodeC[0].IsValid() && ChildrenNodeC[0].GetNodeInstance()->GetNodeHandle() == NodeHandles[1], "FAnimationAnimNextRuntimeTest_IHierarchy -> Expected child: NodeB");
|
|
|
|
ChildrenNodeC.Reset();
|
|
IHierarchy::GetStackChildren(Context, StackNodeC, ChildrenNodeC);
|
|
|
|
AddErrorIfFalse(ChildrenNodeC.Num() == 1, "FAnimationAnimNextRuntimeTest_IHierarchy -> Expected 1 child");
|
|
AddErrorIfFalse(IHierarchy::GetNumStackChildren(Context, StackNodeC) == 1, "FAnimationAnimNextRuntimeTest_IHierarchy -> Expected 1 child");
|
|
AddErrorIfFalse(ChildrenNodeC[0].IsValid() && ChildrenNodeC[0].GetNodeInstance()->GetNodeHandle() == NodeHandles[1], "FAnimationAnimNextRuntimeTest_IHierarchy -> Expected child: NodeB");
|
|
|
|
{
|
|
FTraitStackBinding StackNodeB;
|
|
AddErrorIfFalse(Context.GetStack(ChildrenNodeC[0], StackNodeB), "FAnimationAnimNextRuntimeTest_IHierarchy -> Failed to bind to trait stack");
|
|
|
|
TTraitBinding<IHierarchy> HierarchyBindingNodeB;
|
|
AddErrorIfFalse(StackNodeB.GetInterface(HierarchyBindingNodeB), "FAnimationAnimNextRuntimeTest_IHierarchy -> IHierarchy not found");
|
|
|
|
FChildrenArray ChildrenNodeB;
|
|
HierarchyBindingNodeB.GetChildren(Context, ChildrenNodeB);
|
|
|
|
AddErrorIfFalse(ChildrenNodeB.Num() == 1, "FAnimationAnimNextRuntimeTest_IHierarchy -> Expected 1 child");
|
|
AddErrorIfFalse(HierarchyBindingNodeB.GetNumChildren(Context) == 1, "FAnimationAnimNextRuntimeTest_IHierarchy -> Expected 1 child");
|
|
AddErrorIfFalse(ChildrenNodeB[0].IsValid() && ChildrenNodeB[0].GetNodeInstance()->GetNodeHandle() == NodeHandles[0], "FAnimationAnimNextRuntimeTest_IHierarchy -> Expected child: NodeA");
|
|
|
|
ChildrenNodeB.Reset();
|
|
IHierarchy::GetStackChildren(Context, StackNodeB, ChildrenNodeB);
|
|
|
|
AddErrorIfFalse(ChildrenNodeB.Num() == 1, "FAnimationAnimNextRuntimeTest_IHierarchy -> Expected 1 child");
|
|
AddErrorIfFalse(IHierarchy::GetNumStackChildren(Context, StackNodeB) == 1, "FAnimationAnimNextRuntimeTest_IHierarchy -> Expected 1 child");
|
|
AddErrorIfFalse(ChildrenNodeB[0].IsValid() && ChildrenNodeB[0].GetNodeInstance()->GetNodeHandle() == NodeHandles[0], "FAnimationAnimNextRuntimeTest_IHierarchy -> Expected child: NodeA");
|
|
}
|
|
}
|
|
}
|
|
|
|
Registry.Unregister(NodeTemplateA);
|
|
Registry.Unregister(NodeTemplateB);
|
|
Registry.Unregister(NodeTemplateC);
|
|
Registry.Unregister(NodeTemplateD);
|
|
|
|
AddErrorIfFalse(Registry.GetNum() == 0, "FAnimationAnimNextRuntimeTest_IHierarchy -> Registry should contain 0 templates");
|
|
}
|
|
|
|
Tests::FUtils::CleanupAfterTests();
|
|
|
|
return true;
|
|
}
|
|
|
|
// --- Trait Interfaces IUpdate Test ---
|
|
|
|
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FAnimationAnimNextRuntimeTest_IUpdate, "Animation.AnimNext.Runtime.IUpdate", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
|
|
|
|
bool FAnimationAnimNextRuntimeTest_IUpdate::RunTest(const FString& InParameters)
|
|
{
|
|
using namespace UE::AnimNext;
|
|
{
|
|
AUTO_REGISTER_ANIM_TRAIT(FTraitWithNoChildren)
|
|
AUTO_REGISTER_ANIM_TRAIT(FTraitWithOneChild)
|
|
AUTO_REGISTER_ANIM_TRAIT(FTraitWithChildren)
|
|
|
|
UFactory* GraphFactory = NewObject<UAnimNextAnimationGraphFactory>();
|
|
UAnimNextAnimationGraph* AnimationGraph = CastChecked<UAnimNextAnimationGraph>(GraphFactory->FactoryCreateNew(UAnimNextAnimationGraph::StaticClass(), GetTransientPackage(), TEXT("TestAnimNextGraph"), RF_Transient, nullptr, nullptr, NAME_None));
|
|
UE_RETURN_ON_ERROR(AnimationGraph != nullptr, "FAnimationAnimNextRuntimeTest_GraphTraitEvent -> Failed to create animation graph");
|
|
|
|
FScopedClearNodeTemplateRegistry ScopedClearNodeTemplateRegistry;
|
|
FNodeTemplateRegistry& Registry = FNodeTemplateRegistry::Get();
|
|
|
|
// We create a few node templates
|
|
// Template A has a single trait with no children
|
|
TArray<FTraitUID> NodeTemplateTraitListA;
|
|
NodeTemplateTraitListA.Add(FTraitWithNoChildren::TraitUID);
|
|
|
|
// Template B has a single trait with one child, it doesn't update
|
|
TArray<FTraitUID> NodeTemplateTraitListB;
|
|
NodeTemplateTraitListB.Add(FTraitWithOneChild::TraitUID);
|
|
|
|
// Template C has a single trait with children
|
|
TArray<FTraitUID> NodeTemplateTraitListC;
|
|
NodeTemplateTraitListC.Add(FTraitWithChildren::TraitUID);
|
|
|
|
// Populate our node template registry
|
|
TArray<uint8> NodeTemplateBufferA, NodeTemplateBufferB, NodeTemplateBufferC;
|
|
const FNodeTemplate* NodeTemplateA = FNodeTemplateBuilder::BuildNodeTemplate(NodeTemplateTraitListA, NodeTemplateBufferA);
|
|
const FNodeTemplate* NodeTemplateB = FNodeTemplateBuilder::BuildNodeTemplate(NodeTemplateTraitListB, NodeTemplateBufferB);
|
|
const FNodeTemplate* NodeTemplateC = FNodeTemplateBuilder::BuildNodeTemplate(NodeTemplateTraitListC, NodeTemplateBufferC);
|
|
|
|
// Build our graph, it as follow (each node template has a single node instance):
|
|
// NodeA has no children
|
|
// NodeB has one child: NodeA (it doesn't update)
|
|
// NodeC (root) has two children: NodeA and NodeB
|
|
|
|
TArray<FNodeHandle> NodeHandles;
|
|
|
|
// Write our graph
|
|
TArray<uint8> GraphSharedDataArchiveBuffer;
|
|
TArray<TObjectPtr<UObject>> GraphReferencedObjects;
|
|
TArray<FSoftObjectPath> GraphReferencedSoftObjects;
|
|
{
|
|
FTraitWriter TraitWriter;
|
|
|
|
NodeHandles.Add(TraitWriter.RegisterNode(*NodeTemplateC)); // Root node
|
|
NodeHandles.Add(TraitWriter.RegisterNode(*NodeTemplateA));
|
|
NodeHandles.Add(TraitWriter.RegisterNode(*NodeTemplateB));
|
|
|
|
// We don't have trait properties
|
|
TArray<TMap<FName, FString>> TraitPropertiesA;
|
|
TraitPropertiesA.AddDefaulted(NodeTemplateTraitListA.Num());
|
|
|
|
TArray<TMap<FName, FString>> TraitPropertiesB;
|
|
TraitPropertiesB.AddDefaulted(NodeTemplateTraitListB.Num());
|
|
TraitPropertiesB[0].Add(TEXT("Child"), ToString<FTraitWithOneChild::FSharedData>(TEXT("Child"), FAnimNextTraitHandle(NodeHandles[1])));
|
|
|
|
TArray<TMap<FName, FString>> TraitPropertiesC;
|
|
TraitPropertiesC.AddDefaulted(NodeTemplateTraitListC.Num());
|
|
FAnimNextTraitHandle ChildrenHandlesC[2] = { FAnimNextTraitHandle(NodeHandles[1]), FAnimNextTraitHandle(NodeHandles[2])};
|
|
TraitPropertiesC[0].Add(TEXT("Children"), ToString<FTraitWithChildren::FSharedData>(TEXT("Children"), ChildrenHandlesC));
|
|
|
|
TraitWriter.BeginNodeWriting();
|
|
TraitWriter.WriteNode(NodeHandles[0],
|
|
[&TraitPropertiesC](uint32 TraitIndex, FName PropertyName)
|
|
{
|
|
return TraitPropertiesC[TraitIndex][PropertyName];
|
|
},
|
|
[](uint32 TraitIndex, FName PropertyName)
|
|
{
|
|
return MAX_uint16;
|
|
});
|
|
TraitWriter.WriteNode(NodeHandles[1],
|
|
[&TraitPropertiesA](uint32 TraitIndex, FName PropertyName)
|
|
{
|
|
return TraitPropertiesA[TraitIndex][PropertyName];
|
|
},
|
|
[](uint32 TraitIndex, FName PropertyName)
|
|
{
|
|
return MAX_uint16;
|
|
});
|
|
TraitWriter.WriteNode(NodeHandles[2],
|
|
[&TraitPropertiesB](uint32 TraitIndex, FName PropertyName)
|
|
{
|
|
return TraitPropertiesB[TraitIndex][PropertyName];
|
|
},
|
|
[](uint32 TraitIndex, FName PropertyName)
|
|
{
|
|
return MAX_uint16;
|
|
});
|
|
TraitWriter.EndNodeWriting();
|
|
|
|
AddErrorIfFalse(TraitWriter.GetErrorState() == FTraitWriter::EErrorState::None, "FAnimationAnimNextRuntimeTest_IUpdate -> Failed to write traits");
|
|
GraphSharedDataArchiveBuffer = TraitWriter.GetGraphSharedData();
|
|
GraphReferencedObjects = TraitWriter.GetGraphReferencedObjects();
|
|
GraphReferencedSoftObjects = TraitWriter.GetGraphReferencedSoftObjects();
|
|
}
|
|
|
|
// Read our graph
|
|
FTestUtils::LoadFromArchiveBuffer(*AnimationGraph, NodeHandles, GraphSharedDataArchiveBuffer);
|
|
|
|
TSharedPtr<FAnimNextGraphInstance> GraphInstance = AnimationGraph->AllocateInstance();
|
|
|
|
FExecutionContext Context(*GraphInstance.Get());
|
|
|
|
{
|
|
TArray<FTraitUID> UpdatedTraits;
|
|
Private::UpdatedTraits = &UpdatedTraits;
|
|
|
|
// Call pre/post update on our graph
|
|
FUpdateGraphContext UpdateGraphContext(*GraphInstance.Get(), 0.0333f);
|
|
UpdateGraph(UpdateGraphContext);
|
|
|
|
AddErrorIfFalse(UpdatedTraits.Num() == 6, "FAnimationAnimNextRuntimeTest_IUpdate -> Expected 6 nodes to have been visited during the update traversal");
|
|
AddErrorIfFalse(UpdatedTraits[0] == FTraitWithChildren::TraitUID, "FAnimationAnimNextRuntimeTest_IUpdate -> Unexpected update order"); // NodeC
|
|
AddErrorIfFalse(UpdatedTraits[1] == FTraitWithNoChildren::TraitUID, "FAnimationAnimNextRuntimeTest_IUpdate -> Unexpected update order"); // NodeA
|
|
AddErrorIfFalse(UpdatedTraits[2] == FTraitWithNoChildren::TraitUID, "FAnimationAnimNextRuntimeTest_IUpdate -> Unexpected update order"); // NodeA
|
|
AddErrorIfFalse(UpdatedTraits[3] == FTraitWithNoChildren::TraitUID, "FAnimationAnimNextRuntimeTest_IUpdate -> Unexpected update order"); // NodeB -> NodeA
|
|
AddErrorIfFalse(UpdatedTraits[4] == FTraitWithNoChildren::TraitUID, "FAnimationAnimNextRuntimeTest_IUpdate -> Unexpected update order"); // NodeB -> NodeA
|
|
AddErrorIfFalse(UpdatedTraits[5] == FTraitWithChildren::TraitUID, "FAnimationAnimNextRuntimeTest_IUpdate -> Unexpected update order"); // NodeC
|
|
|
|
Private::UpdatedTraits = nullptr;
|
|
}
|
|
|
|
Registry.Unregister(NodeTemplateA);
|
|
Registry.Unregister(NodeTemplateB);
|
|
Registry.Unregister(NodeTemplateC);
|
|
|
|
AddErrorIfFalse(Registry.GetNum() == 0, "FAnimationAnimNextRuntimeTest_IUpdate -> Registry should contain 0 templates");
|
|
}
|
|
Tests::FUtils::CleanupAfterTests();
|
|
|
|
return true;
|
|
}
|
|
|
|
// --- Trait Interfaces IEvaluate Test ---
|
|
|
|
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FAnimationAnimNextRuntimeTest_IEvaluate, "Animation.AnimNext.Runtime.IEvaluate", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
|
|
|
|
bool FAnimationAnimNextRuntimeTest_IEvaluate::RunTest(const FString& InParameters)
|
|
{
|
|
using namespace UE::AnimNext;
|
|
{
|
|
AUTO_REGISTER_ANIM_TRAIT(FTraitWithNoChildren)
|
|
AUTO_REGISTER_ANIM_TRAIT(FTraitWithOneChild)
|
|
AUTO_REGISTER_ANIM_TRAIT(FTraitWithChildren)
|
|
|
|
UFactory* GraphFactory = NewObject<UAnimNextAnimationGraphFactory>();
|
|
UAnimNextAnimationGraph* AnimationGraph = CastChecked<UAnimNextAnimationGraph>(GraphFactory->FactoryCreateNew(UAnimNextAnimationGraph::StaticClass(), GetTransientPackage(), TEXT("TestAnimNextGraph"), RF_Transient, nullptr, nullptr, NAME_None));
|
|
UE_RETURN_ON_ERROR(AnimationGraph != nullptr, "FAnimationAnimNextRuntimeTest_GraphTraitEvent -> Failed to create animation graph");
|
|
|
|
FScopedClearNodeTemplateRegistry ScopedClearNodeTemplateRegistry;
|
|
FNodeTemplateRegistry& Registry = FNodeTemplateRegistry::Get();
|
|
|
|
// We create a few node templates
|
|
// Template A has a single trait with no children
|
|
TArray<FTraitUID> NodeTemplateTraitListA;
|
|
NodeTemplateTraitListA.Add(FTraitWithNoChildren::TraitUID);
|
|
|
|
// Template B has a single trait with one child, it doesn't evaluate
|
|
TArray<FTraitUID> NodeTemplateTraitListB;
|
|
NodeTemplateTraitListB.Add(FTraitWithOneChild::TraitUID);
|
|
|
|
// Template C has a single trait with children
|
|
TArray<FTraitUID> NodeTemplateTraitListC;
|
|
NodeTemplateTraitListC.Add(FTraitWithChildren::TraitUID);
|
|
|
|
// Populate our node template registry
|
|
TArray<uint8> NodeTemplateBufferA, NodeTemplateBufferB, NodeTemplateBufferC;
|
|
const FNodeTemplate* NodeTemplateA = FNodeTemplateBuilder::BuildNodeTemplate(NodeTemplateTraitListA, NodeTemplateBufferA);
|
|
const FNodeTemplate* NodeTemplateB = FNodeTemplateBuilder::BuildNodeTemplate(NodeTemplateTraitListB, NodeTemplateBufferB);
|
|
const FNodeTemplate* NodeTemplateC = FNodeTemplateBuilder::BuildNodeTemplate(NodeTemplateTraitListC, NodeTemplateBufferC);
|
|
|
|
// Build our graph, it as follow (each node template has a single node instance):
|
|
// NodeA has no children
|
|
// NodeB has one child: NodeA (it doesn't evaluate)
|
|
// NodeC (root) has two children: NodeA and NodeB
|
|
|
|
TArray<FNodeHandle> NodeHandles;
|
|
|
|
// Write our graph
|
|
TArray<uint8> GraphSharedDataArchiveBuffer;
|
|
TArray<TObjectPtr<UObject>> GraphReferencedObjects;
|
|
TArray<FSoftObjectPath> GraphReferencedSoftObjects;
|
|
{
|
|
FTraitWriter TraitWriter;
|
|
|
|
NodeHandles.Add(TraitWriter.RegisterNode(*NodeTemplateC)); // Root node
|
|
NodeHandles.Add(TraitWriter.RegisterNode(*NodeTemplateA));
|
|
NodeHandles.Add(TraitWriter.RegisterNode(*NodeTemplateB));
|
|
|
|
// We don't have trait properties
|
|
TArray<TMap<FName, FString>> TraitPropertiesA;
|
|
TraitPropertiesA.AddDefaulted(NodeTemplateTraitListA.Num());
|
|
|
|
TArray<TMap<FName, FString>> TraitPropertiesB;
|
|
TraitPropertiesB.AddDefaulted(NodeTemplateTraitListB.Num());
|
|
TraitPropertiesB[0].Add(TEXT("Child"), ToString<FTraitWithOneChild::FSharedData>(TEXT("Child"), FAnimNextTraitHandle(NodeHandles[1])));
|
|
|
|
TArray<TMap<FName, FString>> TraitPropertiesC;
|
|
TraitPropertiesC.AddDefaulted(NodeTemplateTraitListC.Num());
|
|
|
|
FAnimNextTraitHandle ChildrenHandlesC[2] = { FAnimNextTraitHandle(NodeHandles[1]), FAnimNextTraitHandle(NodeHandles[2])};
|
|
TraitPropertiesC[0].Add(TEXT("Children"), ToString<FTraitWithChildren::FSharedData>(TEXT("Children"), ChildrenHandlesC));
|
|
|
|
TraitWriter.BeginNodeWriting();
|
|
TraitWriter.WriteNode(NodeHandles[0],
|
|
[&TraitPropertiesC](uint32 TraitIndex, FName PropertyName)
|
|
{
|
|
return TraitPropertiesC[TraitIndex][PropertyName];
|
|
},
|
|
[](uint32 TraitIndex, FName PropertyName)
|
|
{
|
|
return MAX_uint16;
|
|
});
|
|
TraitWriter.WriteNode(NodeHandles[1],
|
|
[&TraitPropertiesA](uint32 TraitIndex, FName PropertyName)
|
|
{
|
|
return TraitPropertiesA[TraitIndex][PropertyName];
|
|
},
|
|
[](uint32 TraitIndex, FName PropertyName)
|
|
{
|
|
return MAX_uint16;
|
|
});
|
|
TraitWriter.WriteNode(NodeHandles[2],
|
|
[&TraitPropertiesB](uint32 TraitIndex, FName PropertyName)
|
|
{
|
|
return TraitPropertiesB[TraitIndex][PropertyName];
|
|
},
|
|
[](uint32 TraitIndex, FName PropertyName)
|
|
{
|
|
return MAX_uint16;
|
|
});
|
|
TraitWriter.EndNodeWriting();
|
|
|
|
AddErrorIfFalse(TraitWriter.GetErrorState() == FTraitWriter::EErrorState::None, "FAnimationAnimNextRuntimeTest_IEvaluate -> Failed to write traits");
|
|
GraphSharedDataArchiveBuffer = TraitWriter.GetGraphSharedData();
|
|
GraphReferencedObjects = TraitWriter.GetGraphReferencedObjects();
|
|
GraphReferencedSoftObjects = TraitWriter.GetGraphReferencedSoftObjects();
|
|
}
|
|
|
|
// Read our graph
|
|
FTestUtils::LoadFromArchiveBuffer(*AnimationGraph, NodeHandles, GraphSharedDataArchiveBuffer);
|
|
|
|
TSharedPtr<FAnimNextGraphInstance> GraphInstance = AnimationGraph->AllocateInstance();
|
|
|
|
{
|
|
TArray<FTraitUID> EvaluatedTraits;
|
|
Private::EvaluatedTraits = &EvaluatedTraits;
|
|
|
|
// Call pre/post evaluate on our graph
|
|
UE::AnimNext::FEvaluateGraphContext EvaluateGraphContext(*GraphInstance.Get(), UE::AnimNext::FReferencePose(), 0);
|
|
(void)EvaluateGraph(EvaluateGraphContext);
|
|
|
|
AddErrorIfFalse(EvaluatedTraits.Num() == 6, "FAnimationAnimNextRuntimeTest_IEvaluate -> Expected 6 nodes to have been visited during the evaluate traversal");
|
|
AddErrorIfFalse(EvaluatedTraits[0] == FTraitWithChildren::TraitUID, "FAnimationAnimNextRuntimeTest_IEvaluate -> Unexpected evaluate order"); // NodeC
|
|
AddErrorIfFalse(EvaluatedTraits[1] == FTraitWithNoChildren::TraitUID, "FAnimationAnimNextRuntimeTest_IEvaluate -> Unexpected evaluate order"); // NodeA
|
|
AddErrorIfFalse(EvaluatedTraits[2] == FTraitWithNoChildren::TraitUID, "FAnimationAnimNextRuntimeTest_IEvaluate -> Unexpected evaluate order"); // NodeA
|
|
AddErrorIfFalse(EvaluatedTraits[3] == FTraitWithNoChildren::TraitUID, "FAnimationAnimNextRuntimeTest_IEvaluate -> Unexpected evaluate order"); // NodeB -> NodeA
|
|
AddErrorIfFalse(EvaluatedTraits[4] == FTraitWithNoChildren::TraitUID, "FAnimationAnimNextRuntimeTest_IEvaluate -> Unexpected evaluate order"); // NodeB -> NodeA
|
|
AddErrorIfFalse(EvaluatedTraits[5] == FTraitWithChildren::TraitUID, "FAnimationAnimNextRuntimeTest_IEvaluate -> Unexpected evaluate order"); // NodeC
|
|
|
|
Private::EvaluatedTraits = nullptr;
|
|
}
|
|
|
|
Registry.Unregister(NodeTemplateA);
|
|
Registry.Unregister(NodeTemplateB);
|
|
Registry.Unregister(NodeTemplateC);
|
|
|
|
AddErrorIfFalse(Registry.GetNum() == 0, "FAnimationAnimNextRuntimeTest_IEvaluate -> Registry should contain 0 templates");
|
|
}
|
|
Tests::FUtils::CleanupAfterTests();
|
|
|
|
return true;
|
|
}
|
|
|
|
// --- Trait Interfaces IScopedInterface Test ---
|
|
|
|
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FAnimationAnimNextRuntimeTest_IScopedInterface, "Animation.AnimNext.Runtime.IScopedInterface", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
|
|
|
|
bool FAnimationAnimNextRuntimeTest_IScopedInterface::RunTest(const FString& InParameters)
|
|
{
|
|
using namespace UE::AnimNext;
|
|
{
|
|
AUTO_REGISTER_ANIM_TRAIT(FTraitWithOneChild)
|
|
AUTO_REGISTER_ANIM_TRAIT(FScopedTagTrait)
|
|
AUTO_REGISTER_ANIM_TRAIT(FTestScopedTagTrait)
|
|
|
|
UFactory* GraphFactory = NewObject<UAnimNextAnimationGraphFactory>();
|
|
UAnimNextAnimationGraph* AnimationGraph = CastChecked<UAnimNextAnimationGraph>(GraphFactory->FactoryCreateNew(UAnimNextAnimationGraph::StaticClass(), GetTransientPackage(), TEXT("TestAnimNextGraph"), RF_Transient, nullptr, nullptr, NAME_None));
|
|
UE_RETURN_ON_ERROR(AnimationGraph != nullptr, "FAnimationAnimNextRuntimeTest_GraphTraitEvent -> Failed to create animation graph");
|
|
|
|
FScopedClearNodeTemplateRegistry ScopedClearNodeTemplateRegistry;
|
|
FNodeTemplateRegistry& Registry = FNodeTemplateRegistry::Get();
|
|
|
|
// We create a few node templates
|
|
// Template A has a single child and tests for our tag
|
|
TArray<FTraitUID> NodeTemplateTraitList0;
|
|
NodeTemplateTraitList0.Add(FTraitWithOneChild::TraitUID);
|
|
NodeTemplateTraitList0.Add(FTestScopedTagTrait::TraitUID);
|
|
|
|
// Template B has a single child, it tests and pushes our tag
|
|
TArray<FTraitUID> NodeTemplateTraitList1;
|
|
NodeTemplateTraitList1.Add(FTraitWithOneChild::TraitUID);
|
|
NodeTemplateTraitList1.Add(FTestScopedTagTrait::TraitUID); // Test after push/pop
|
|
NodeTemplateTraitList1.Add(FScopedTagTrait::TraitUID);
|
|
NodeTemplateTraitList1.Add(FTestScopedTagTrait::TraitUID); // Test before push/pop
|
|
|
|
// Populate our node template registry
|
|
TArray<uint8> NodeTemplateBuffer0, NodeTemplateBuffer1;
|
|
const FNodeTemplate* NodeTemplate0 = FNodeTemplateBuilder::BuildNodeTemplate(NodeTemplateTraitList0, NodeTemplateBuffer0);
|
|
const FNodeTemplate* NodeTemplate1 = FNodeTemplateBuilder::BuildNodeTemplate(NodeTemplateTraitList1, NodeTemplateBuffer1);
|
|
|
|
// Build our graph, it as follow:
|
|
// NodeA has no child (tag is scoped)
|
|
// NodeB has one child: NodeA (NodeB adds the scoped tag)
|
|
// NodeC (root) has one child: NodeB (no tag scoped)
|
|
|
|
TArray<FNodeHandle> NodeHandles;
|
|
|
|
// Write our graph
|
|
TArray<uint8> GraphSharedDataArchiveBuffer;
|
|
TArray<TObjectPtr<UObject>> GraphReferencedObjects;
|
|
TArray<FSoftObjectPath> GraphReferencedSoftObjects;
|
|
{
|
|
FTraitWriter TraitWriter;
|
|
|
|
NodeHandles.Add(TraitWriter.RegisterNode(*NodeTemplate0)); // NodeC (root node)
|
|
NodeHandles.Add(TraitWriter.RegisterNode(*NodeTemplate1)); // NodeB
|
|
NodeHandles.Add(TraitWriter.RegisterNode(*NodeTemplate0)); // NodeA
|
|
|
|
// We don't have trait properties
|
|
TArray<TMap<FName, FString>> TraitPropertiesA;
|
|
TraitPropertiesA.AddDefaulted(NodeTemplateTraitList0.Num());
|
|
TraitPropertiesA[0].Add(TEXT("Child"), ToString<FTraitWithOneChild::FSharedData>(TEXT("Child"), FAnimNextTraitHandle()));
|
|
|
|
TArray<TMap<FName, FString>> TraitPropertiesB;
|
|
TraitPropertiesB.AddDefaulted(NodeTemplateTraitList1.Num());
|
|
TraitPropertiesB[0].Add(TEXT("Child"), ToString<FTraitWithOneChild::FSharedData>(TEXT("Child"), FAnimNextTraitHandle(NodeHandles[2])));
|
|
|
|
TArray<TMap<FName, FString>> TraitPropertiesC;
|
|
TraitPropertiesC.AddDefaulted(NodeTemplateTraitList0.Num());
|
|
TraitPropertiesC[0].Add(TEXT("Child"), ToString<FTraitWithOneChild::FSharedData>(TEXT("Child"), FAnimNextTraitHandle(NodeHandles[1])));
|
|
|
|
TraitWriter.BeginNodeWriting();
|
|
TraitWriter.WriteNode(NodeHandles[0],
|
|
[&TraitPropertiesC](uint32 TraitIndex, FName PropertyName)
|
|
{
|
|
return TraitPropertiesC[TraitIndex][PropertyName];
|
|
},
|
|
[](uint32 TraitIndex, FName PropertyName)
|
|
{
|
|
return MAX_uint16;
|
|
});
|
|
TraitWriter.WriteNode(NodeHandles[1],
|
|
[&TraitPropertiesB](uint32 TraitIndex, FName PropertyName)
|
|
{
|
|
return TraitPropertiesB[TraitIndex][PropertyName];
|
|
},
|
|
[](uint32 TraitIndex, FName PropertyName)
|
|
{
|
|
return MAX_uint16;
|
|
});
|
|
TraitWriter.WriteNode(NodeHandles[2],
|
|
[&TraitPropertiesA](uint32 TraitIndex, FName PropertyName)
|
|
{
|
|
return TraitPropertiesA[TraitIndex][PropertyName];
|
|
},
|
|
[](uint32 TraitIndex, FName PropertyName)
|
|
{
|
|
return MAX_uint16;
|
|
});
|
|
TraitWriter.EndNodeWriting();
|
|
|
|
AddErrorIfFalse(TraitWriter.GetErrorState() == FTraitWriter::EErrorState::None, "FAnimationAnimNextRuntimeTest_IScopedInterface -> Failed to write traits");
|
|
GraphSharedDataArchiveBuffer = TraitWriter.GetGraphSharedData();
|
|
GraphReferencedObjects = TraitWriter.GetGraphReferencedObjects();
|
|
GraphReferencedSoftObjects = TraitWriter.GetGraphReferencedSoftObjects();
|
|
}
|
|
|
|
// Read our graph
|
|
FTestUtils::LoadFromArchiveBuffer(*AnimationGraph, NodeHandles, GraphSharedDataArchiveBuffer);
|
|
|
|
TSharedPtr<FAnimNextGraphInstance> GraphInstance = AnimationGraph->AllocateInstance();
|
|
|
|
FExecutionContext Context(*GraphInstance.Get());
|
|
|
|
{
|
|
TArray<bool> IsTagInScope;
|
|
Private::IsTagInScope = &IsTagInScope;
|
|
|
|
// Call pre/post update on our graph
|
|
Private::AutoPopTag = true;
|
|
FUpdateGraphContext UpdateGraphContext(*GraphInstance.Get(), 0.0333f);
|
|
UpdateGraph(UpdateGraphContext);
|
|
|
|
AddErrorIfFalse(IsTagInScope.Num() == 8, "FAnimationAnimNextRuntimeTest_IScopedInterface -> Unexpected number of entries");
|
|
AddErrorIfFalse(IsTagInScope[0] == false, "FAnimationAnimNextRuntimeTest_IScopedInterface -> Unexpected scoped tag state"); // NodeC::PreUpdate (template 0)
|
|
AddErrorIfFalse(IsTagInScope[1] == false, "FAnimationAnimNextRuntimeTest_IScopedInterface -> Unexpected scoped tag state"); // NodeB::Before::PreUpdate (template 1)
|
|
AddErrorIfFalse(IsTagInScope[2] == true, "FAnimationAnimNextRuntimeTest_IScopedInterface -> Unexpected scoped tag state"); // NodeB::After::PreUpdate (template 1)
|
|
AddErrorIfFalse(IsTagInScope[3] == true, "FAnimationAnimNextRuntimeTest_IScopedInterface -> Unexpected scoped tag state"); // NodeA::PreUpdate (template 0)
|
|
AddErrorIfFalse(IsTagInScope[4] == true, "FAnimationAnimNextRuntimeTest_IScopedInterface -> Unexpected scoped tag state"); // NodeA::PostUpdate (template 0)
|
|
AddErrorIfFalse(IsTagInScope[5] == true, "FAnimationAnimNextRuntimeTest_IScopedInterface -> Unexpected scoped tag state"); // NodeB::Before::PostUpdate (template 1)
|
|
AddErrorIfFalse(IsTagInScope[6] == true, "FAnimationAnimNextRuntimeTest_IScopedInterface -> Unexpected scoped tag state"); // NodeB::After::PostUpdate (template 1)
|
|
AddErrorIfFalse(IsTagInScope[7] == false, "FAnimationAnimNextRuntimeTest_IScopedInterface -> Unexpected scoped tag state"); // NodeC::PostUpdate (template 0)
|
|
|
|
IsTagInScope.Reset();
|
|
Private::AutoPopTag = false;
|
|
|
|
// Call pre/post update on our graph
|
|
UpdateGraph(UpdateGraphContext);
|
|
|
|
AddErrorIfFalse(IsTagInScope.Num() == 8, "FAnimationAnimNextRuntimeTest_IScopedInterface -> Unexpected number of entries");
|
|
AddErrorIfFalse(IsTagInScope[0] == false, "FAnimationAnimNextRuntimeTest_IScopedInterface -> Unexpected scoped tag state"); // NodeC::PreUpdate (template 0)
|
|
AddErrorIfFalse(IsTagInScope[1] == false, "FAnimationAnimNextRuntimeTest_IScopedInterface -> Unexpected scoped tag state"); // NodeB::Before::PreUpdate (template 1)
|
|
AddErrorIfFalse(IsTagInScope[2] == true, "FAnimationAnimNextRuntimeTest_IScopedInterface -> Unexpected scoped tag state"); // NodeB::After::PreUpdate (template 1)
|
|
AddErrorIfFalse(IsTagInScope[3] == true, "FAnimationAnimNextRuntimeTest_IScopedInterface -> Unexpected scoped tag state"); // NodeA::PreUpdate (template 0)
|
|
AddErrorIfFalse(IsTagInScope[4] == true, "FAnimationAnimNextRuntimeTest_IScopedInterface -> Unexpected scoped tag state"); // NodeA::PostUpdate (template 0)
|
|
AddErrorIfFalse(IsTagInScope[5] == true, "FAnimationAnimNextRuntimeTest_IScopedInterface -> Unexpected scoped tag state"); // NodeB::Before::PostUpdate (template 1)
|
|
AddErrorIfFalse(IsTagInScope[6] == false, "FAnimationAnimNextRuntimeTest_IScopedInterface -> Unexpected scoped tag state"); // NodeB::After::PostUpdate (template 1)
|
|
AddErrorIfFalse(IsTagInScope[7] == false, "FAnimationAnimNextRuntimeTest_IScopedInterface -> Unexpected scoped tag state"); // NodeC::PostUpdate (template 0)
|
|
|
|
Private::UpdatedTraits = nullptr;
|
|
}
|
|
|
|
Registry.Unregister(NodeTemplate0);
|
|
Registry.Unregister(NodeTemplate1);
|
|
|
|
AddErrorIfFalse(Registry.GetNum() == 0, "FAnimationAnimNextRuntimeTest_IScopedInterface -> Registry should contain 0 templates");
|
|
}
|
|
Tests::FUtils::CleanupAfterTests();
|
|
|
|
return true;
|
|
}
|
|
|
|
#endif
|