// Copyright Epic Games, Inc. All Rights Reserved. #include "AITestsCommon.h" #include "MassEntityBuilder.h" #include "MassRelationCommands.h" #include "MassEntityTestTypes.h" #include "MassExecutionContext.h" #include "Algo/Compare.h" #include "Relations/MassChildOf.h" #define LOCTEXT_NAMESPACE "MassTest" UE_DISABLE_OPTIMIZATION_SHIP namespace UE::Mass::Test::ChildOfRelation { using namespace UE::Mass; static TNotNull MakeRelationType(FName TypeName) { UScriptStruct* NewStruct = NewObject(UScriptStruct::StaticClass(), FName(*FString::Printf(TEXT("Relation_%s"), *TypeName.ToString())), RF_Public); NewStruct->SetSuperStruct(FMassRelation::StaticStruct()); return NewStruct; } struct FChildOfBase : FEntityTestBase { enum class EStructure : uint8 { String, Tree, MAX }; EStructure StructureType = EStructure::MAX; using Super = FEntityTestBase; TArray CreatedEntities; int32 NumEntities = -1; UMassTestProcessorBase* Processor = nullptr; bool bAPISupportsReparenting = true; FTypeHandle RelationTypeHandle; UE::Mass::FRelationManager* RelationManager = nullptr; virtual void BuildHierarchy() = 0; virtual bool ReparentEntity(const int32 ChildIndex, const int32 ParentIndex) { return false; } virtual bool DeleteEntity(const int32 EntityIndex) { EntityManager->DestroyEntity(CreatedEntities[EntityIndex]); return true; } virtual void SetUpRelationHandle() { RelationTypeHandle = EntityManager->GetTypeManager().GetRelationTypeHandle(FMassChildOfRelation::StaticStruct()); ensure(RelationTypeHandle == UE::Mass::Relations::ChildOfHandle); } virtual bool SetUp() override { check(StructureType != EStructure::MAX); switch (StructureType) { case EStructure::String: NumEntities = 5; break; case EStructure::Tree: NumEntities = 8; break; } if (Super::SetUp()) { SetUpRelationHandle(); RelationManager = &EntityManager->GetRelationManager(); Processor = NewTestProcessor(EntityManager); Processor->EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); Processor->EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly, EMassFragmentPresence::Optional); Processor->EntityQuery.GroupBy(EntityManager->GetTypeManager().GetRelationTypeChecked(RelationTypeHandle).RegisteredGroupType); BuildHierarchy(); return true; } return false; } virtual void ExecuteTestProcessor(TArray& Scratchpad) { Scratchpad.Reset(); Scratchpad.AddUninitialized(CreatedEntities.Num()); for (int32 EntityIndex = 0; EntityIndex < CreatedEntities.Num(); ++EntityIndex) { Scratchpad[EntityIndex] = -1; } EntityManager->FlushCommands(); Processor->ForEachEntityChunkExecutionFunction = [RelationManager = RelationManager, CreatedEntitiesView = MakeArrayView(CreatedEntities), &Scratchpad](FMassExecutionContext& Context) { // @todo should this be the right way to fetch parents? TConstArrayView Parents = Context.GetFragmentView(); if (Parents.Num()) { for (auto EntityIt = Context.CreateEntityIterator(); EntityIt; ++EntityIt) { // get parent const FMassEntityHandle ParentHandle = Parents[EntityIt].Parent; if (ParentHandle.IsValid()) { const int32 EntityGlobalIndex = CreatedEntitiesView.Find(Context.GetEntity(EntityIt)); const int32 ParentGlobalIndex = CreatedEntitiesView.Find(ParentHandle); check(EntityGlobalIndex != INDEX_NONE && ParentGlobalIndex != INDEX_NONE); Scratchpad[EntityGlobalIndex] = Scratchpad[ParentGlobalIndex] * 10 + EntityGlobalIndex; } } } else { // no-parent for (int32 EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex) { const int32 EntityGlobalIndex = CreatedEntitiesView.Find(Context.GetEntity(EntityIndex)); check(EntityGlobalIndex != INDEX_NONE); Scratchpad[EntityGlobalIndex] = EntityGlobalIndex; } } }; Processor->TestExecute(EntityManager); } virtual bool InstantTest() override { TArray Scratchpad; ExecuteTestProcessor(Scratchpad); TArray ExpectedValues; switch (StructureType) { case EStructure::String: // requires SetUp() override to implement the following structure, indicated with indices to CreatedEntities: // 0 // L 1 // L 2 // L 3 // L 4 ExpectedValues = { 0, 1, 12, 123, 1234 }; break; case EStructure::Tree: // requires SetUp() override to implement the following structure, indicated with indices to CreatedEntities: // 0 // + 1 // | + 3 // | + 4 // | L 5 // L 2 // + 6 // L 7 ExpectedValues = { 0, 1, 2, 13, 14, 15, 26, 27 }; break; } AITEST_TRUE("Processing hierarchy produces expected results", Algo::Compare(Scratchpad, ExpectedValues)); if (bAPISupportsReparenting == false) { // the API is just for entity creation, we end the test here. return true; } // here we reparent a leaf node AITEST_TRUE("Reparenting leaf node.", ReparentEntity(CreatedEntities.Num() - 1, 0)); switch (StructureType) { case EStructure::String: // requires SetUp() override to implement the following structure, indicated with indices to CreatedEntities: // 0 // L 1 // | L 2 // | L 3 // L 4 ExpectedValues = { 0, 1, 12, 123, 4 }; break; case EStructure::Tree: // requires SetUp() override to implement the following structure, indicated with indices to CreatedEntities: // 0 // + 1 // | + 3 // | + 4 // | L 5 // L 2 // | L 6 // L 7 ExpectedValues = { 0, 1, 2, 13, 14, 15, 26, 7 }; break; } ExecuteTestProcessor(Scratchpad); AITEST_TRUE("Reparenting a leaf produces expected results", Algo::Compare(Scratchpad, ExpectedValues)); AITEST_TRUE("Reparenting a mid-node to a leaf node.", ReparentEntity(2, 4)); switch (StructureType) { case EStructure::String: // requires SetUp() override to implement the following structure, indicated with indices to CreatedEntities: // 0 // L 1 // L 4 // L 2 // L 3 ExpectedValues = { 0, 1, 42, 423, 4 }; break; case EStructure::Tree: // requires SetUp() override to implement the following structure, indicated with indices to CreatedEntities: // 0 // + 1 // | + 3 // | + 4 // | | L 2 // | | L 6 // | L 5 // L 7 ExpectedValues = { 0, 1, 142, 13, 14, 15, 1426, 7 }; break; } ExecuteTestProcessor(Scratchpad); AITEST_TRUE("Reparenting a subtree to a leaf produces expected results", Algo::Compare(Scratchpad, ExpectedValues)); AITEST_TRUE("Deleting a leaf node", DeleteEntity(3)); switch (StructureType) { case EStructure::String: // requires SetUp() override to implement the following structure, indicated with indices to CreatedEntities: // 0 // L 1 // L 4 // L 2 ExpectedValues = { 0, 1, 42, -1, 4 }; break; case EStructure::Tree: // requires SetUp() override to implement the following structure, indicated with indices to CreatedEntities: // 0 // + 1 // | + 4 // | | L 2 // | | L 6 // | L 5 // L 7 ExpectedValues = { 0, 1, 142, -1, 14, 15, 1426, 7 }; break; } ExecuteTestProcessor(Scratchpad); AITEST_TRUE("Delete a leaf node produces expected results", Algo::Compare(Scratchpad, ExpectedValues)); AITEST_TRUE("Deleting mid-level node", DeleteEntity(4)); switch (StructureType) { case EStructure::String: // requires SetUp() override to implement the following structure, indicated with indices to CreatedEntities: // 0 // + 1 ExpectedValues = { 0, 1, -1, -1, -1 }; break; case EStructure::Tree: // requires SetUp() override to implement the following structure, indicated with indices to CreatedEntities: // 0 // + 1 // | L 5 // L 7 ExpectedValues = { 0, 1, -1, -1, -1, 15, -1, 7 }; break; } ExecuteTestProcessor(Scratchpad); AITEST_TRUE("Delete a mid-level node produces expected results", Algo::Compare(Scratchpad, ExpectedValues)); AITEST_TRUE("Deleting mid-level node", DeleteEntity(0)); switch (StructureType) { case EStructure::String: ExpectedValues = { -1, -1, -1, -1, -1 }; break; case EStructure::Tree: ExpectedValues = { -1, -1, -1, -1, -1, -1, -1, -1 }; break; } ExecuteTestProcessor(Scratchpad); AITEST_TRUE("Delete a top-level node produces expected results", Algo::Compare(Scratchpad, ExpectedValues)); return true; } }; struct FChildOf_IndividualAPI_StringHierarchy : FChildOfBase { FChildOf_IndividualAPI_StringHierarchy() { StructureType = EStructure::String; } virtual void BuildHierarchy() override { EntityManager->BatchCreateEntities(IntsArchetype, {}, NumEntities, CreatedEntities); // set up indices for (int32 Index = 0; Index < CreatedEntities.Num(); ++Index) { FTestFragment_Int& IndexCounterFragment = EntityManager->GetFragmentDataChecked(CreatedEntities[Index]); IndexCounterFragment.Value = Index; } for (int32 ChildIndex = 1; ChildIndex < CreatedEntities.Num(); ++ChildIndex) { RelationManager->CreateRelationInstance(RelationTypeHandle, CreatedEntities[ChildIndex], CreatedEntities[ChildIndex - 1]); } } virtual bool ReparentEntity(const int32 ChildIndex, const int32 ParentIndex) override { return RelationManager->CreateRelationInstance(RelationTypeHandle, CreatedEntities[ChildIndex], CreatedEntities[ParentIndex]).IsValid(); } }; IMPLEMENT_AI_INSTANT_TEST(FChildOf_IndividualAPI_StringHierarchy, "System.Mass.Relations.ChildOf.IndividualAPI.StringHierarchy"); struct FChildOf_IndividualAPI_TreeHierarchy : FChildOfBase { FChildOf_IndividualAPI_TreeHierarchy() { StructureType = EStructure::Tree; } virtual void BuildHierarchy() override { EntityManager->BatchCreateEntities(IntsArchetype, {}, NumEntities, CreatedEntities); // set up indices for (int32 Index = 0; Index < CreatedEntities.Num(); ++Index) { FTestFragment_Int& IndexCounterFragment = EntityManager->GetFragmentDataChecked(CreatedEntities[Index]); IndexCounterFragment.Value = Index; } // [1] - child of [0] RelationManager->CreateRelationInstance(RelationTypeHandle, CreatedEntities[1], CreatedEntities[0]); // [2] - child of [0] RelationManager->CreateRelationInstance(RelationTypeHandle, CreatedEntities[2], CreatedEntities[0]); // [3] - child of [1] RelationManager->CreateRelationInstance(RelationTypeHandle, CreatedEntities[3], CreatedEntities[1]); // [4] - child of [1] RelationManager->CreateRelationInstance(RelationTypeHandle, CreatedEntities[4], CreatedEntities[1]); // [5] - child of [1] RelationManager->CreateRelationInstance(RelationTypeHandle, CreatedEntities[5], CreatedEntities[1]); // [6] - child of [2] RelationManager->CreateRelationInstance(RelationTypeHandle, CreatedEntities[6], CreatedEntities[2]); // [7] - child of [2] RelationManager->CreateRelationInstance(RelationTypeHandle, CreatedEntities[7], CreatedEntities[2]); }; virtual bool ReparentEntity(const int32 ChildIndex, const int32 ParentIndex) override { return RelationManager->CreateRelationInstance(RelationTypeHandle, CreatedEntities[ChildIndex], CreatedEntities[ParentIndex]).IsValid(); } }; IMPLEMENT_AI_INSTANT_TEST(FChildOf_IndividualAPI_TreeHierarchy, "System.Mass.Relations.ChildOf.IndividualAPI.TreeHierarchy"); struct FChildOfEntityBuilder_StringHierarchy : FChildOfBase { FChildOfEntityBuilder_StringHierarchy() { StructureType = EStructure::String; bAPISupportsReparenting = false; } virtual void BuildHierarchy() override { FEntityBuilder Builder = EntityManager->MakeEntityBuilder(); FTestFragment_Int& IndexCounterFragment = Builder.Add_GetRef(CreatedEntities.Num()); CreatedEntities.Add(Builder.CommitAndReprepare()); for (int32 ChildIndex = 0; ChildIndex < 4; ++ChildIndex) { Builder.AddRelation(RelationTypeHandle, CreatedEntities.Last()); IndexCounterFragment.Value = CreatedEntities.Num(); CreatedEntities.Add(Builder.CommitAndReprepare()); } } }; IMPLEMENT_AI_INSTANT_TEST(FChildOfEntityBuilder_StringHierarchy, "System.Mass.Relations.ChildOf.EntityBuilder.StringHierarchy"); struct FChildOfEntityBuilder_TreeHierarchy : FChildOfBase { FChildOfEntityBuilder_TreeHierarchy() { StructureType = EStructure::Tree; bAPISupportsReparenting = false; } virtual void BuildHierarchy() override { FEntityBuilder Builder = EntityManager->MakeEntityBuilder(); FTestFragment_Int& IndexCounterFragment = Builder.Add_GetRef(CreatedEntities.Num()); // [0] - parent entity CreatedEntities.Add(Builder.CommitAndReprepare()); Builder.AddRelation(RelationTypeHandle, CreatedEntities.Last()); // [1] - child of [0] IndexCounterFragment.Value = CreatedEntities.Num(); CreatedEntities.Add(Builder.CommitAndReprepare()); // [2] - child of [0] IndexCounterFragment.Value = CreatedEntities.Num(); CreatedEntities.Add(Builder.CommitAndReprepare()); Builder.AddRelation(RelationTypeHandle, CreatedEntities[1]); // [3] - child of [1] IndexCounterFragment.Value = CreatedEntities.Num(); CreatedEntities.Add(Builder.CommitAndReprepare()); // [4] - child of [1] IndexCounterFragment.Value = CreatedEntities.Num(); CreatedEntities.Add(Builder.CommitAndReprepare()); // [5] - child of [1] IndexCounterFragment.Value = CreatedEntities.Num(); CreatedEntities.Add(Builder.CommitAndReprepare()); Builder.AddRelation(RelationTypeHandle, CreatedEntities[2]); // [6] - child of [2] IndexCounterFragment.Value = CreatedEntities.Num(); CreatedEntities.Add(Builder.CommitAndReprepare()); // [7] - child of [2] IndexCounterFragment.Value = CreatedEntities.Num(); CreatedEntities.Add(Builder.CommitAndReprepare()); } }; IMPLEMENT_AI_INSTANT_TEST(FChildOfEntityBuilder_TreeHierarchy, "System.Mass.Relations.ChildOf.EntityBuilder.TreeHierarchy"); struct FChildOfBatchAPI_Tree : FChildOfBase { FChildOfBatchAPI_Tree() { StructureType = EStructure::Tree; } virtual void BuildHierarchy() override { EntityManager->BatchCreateEntities(IntsArchetype, {}, NumEntities, CreatedEntities); // set up indices for (int32 Index = 0; Index < CreatedEntities.Num(); ++Index) { FTestFragment_Int& IndexCounterFragment = EntityManager->GetFragmentDataChecked(CreatedEntities[Index]); IndexCounterFragment.Value = Index; } // [1, 2] - child of [0] EntityManager->BatchCreateRelations(RelationTypeHandle, MakeArrayView(&CreatedEntities[1], 1), MakeArrayView(&CreatedEntities[0], 1)); EntityManager->BatchCreateRelations(RelationTypeHandle, MakeArrayView(&CreatedEntities[2], 1), MakeArrayView(&CreatedEntities[0], 1)); // [3, 4, 5] - child of [1] EntityManager->BatchCreateRelations(RelationTypeHandle, MakeArrayView(&CreatedEntities[3], 1), MakeArrayView(&CreatedEntities[1], 1)); EntityManager->BatchCreateRelations(RelationTypeHandle, MakeArrayView(&CreatedEntities[4], 1), MakeArrayView(&CreatedEntities[1], 1)); EntityManager->BatchCreateRelations(RelationTypeHandle, MakeArrayView(&CreatedEntities[5], 1), MakeArrayView(&CreatedEntities[1], 1)); // [6, 7] - child of [2] EntityManager->BatchCreateRelations(RelationTypeHandle, MakeArrayView(&CreatedEntities[6], 1), MakeArrayView(&CreatedEntities[2], 1)); EntityManager->BatchCreateRelations(RelationTypeHandle, MakeArrayView(&CreatedEntities[7], 1), MakeArrayView(&CreatedEntities[2], 1)); } virtual bool ReparentEntity(const int32 ChildIndex, const int32 ParentIndex) override { return EntityManager->BatchCreateRelations(RelationTypeHandle, MakeArrayView(&CreatedEntities[ChildIndex], 1), MakeArrayView(&CreatedEntities[ParentIndex], 1)); } }; IMPLEMENT_AI_INSTANT_TEST(FChildOfBatchAPI_Tree, "System.Mass.Relations.ChildOf.BatchAPI"); struct FChildOfCommands_StringHierarchy : FChildOfBase { FChildOfCommands_StringHierarchy() { StructureType = EStructure::String; } virtual void BuildHierarchy() override { EntityManager->BatchCreateEntities(IntsArchetype, {}, NumEntities, CreatedEntities); // set up indices for (int32 Index = 0; Index < CreatedEntities.Num(); ++Index) { FTestFragment_Int& IndexCounterFragment = EntityManager->GetFragmentDataChecked(CreatedEntities[Index]); IndexCounterFragment.Value = Index; if (Index > 0) { // issue relationship-creating commands, done here for convenience EntityManager->Defer().PushCommand>(CreatedEntities[Index], CreatedEntities[Index-1]); } } EntityManager->FlushCommands(); } virtual bool ReparentEntity(const int32 ChildIndex, const int32 ParentIndex) override { EntityManager->Defer().PushCommand>(CreatedEntities[ChildIndex], CreatedEntities[ParentIndex]); return true; } virtual bool DeleteEntity(const int32 EntityIndex) override { EntityManager->Defer().DestroyEntity(CreatedEntities[EntityIndex]); return true; } }; IMPLEMENT_AI_INSTANT_TEST(FChildOfCommands_StringHierarchy, "System.Mass.Relations.ChildOf.Commands.StringHierarchy"); struct FChildOfCommands_TreeHierarchy : FChildOfBase { FChildOfCommands_TreeHierarchy() { StructureType = EStructure::Tree; } virtual void BuildHierarchy() override { EntityManager->BatchCreateEntities(IntsArchetype, {}, NumEntities, CreatedEntities); for (int32 Index = 0; Index < CreatedEntities.Num(); ++Index) { FTestFragment_Int& IndexCounterFragment = EntityManager->GetFragmentDataChecked(CreatedEntities[Index]); IndexCounterFragment.Value = Index; } // [1, 2] - child of [0] EntityManager->Defer().PushCommand>(MakeArrayView(&CreatedEntities[1], 2), MakeArrayView(&CreatedEntities[0], 1)); // [3, 4, 5] - child of [1] EntityManager->Defer().PushCommand>(MakeArrayView(&CreatedEntities[3], 3), MakeArrayView(&CreatedEntities[1], 1)); // [6, 7] - child of [2] EntityManager->Defer().PushCommand>(MakeArrayView(&CreatedEntities[6], 2), MakeArrayView(&CreatedEntities[2], 1)); EntityManager->FlushCommands(); } virtual bool ReparentEntity(const int32 ChildIndex, const int32 ParentIndex) override { EntityManager->Defer().PushCommand>(CreatedEntities[ChildIndex], CreatedEntities[ParentIndex]); EntityManager->FlushCommands(); return true; } virtual bool DeleteEntity(const int32 EntityIndex) override { EntityManager->Defer().DestroyEntity(CreatedEntities[EntityIndex]); return true; } }; IMPLEMENT_AI_INSTANT_TEST(FChildOfCommands_TreeHierarchy, "System.Mass.Relations.ChildOf.Commands.TreeHierarchy"); struct FChildOfBatchAPI_Tree_NoMapping : FChildOfBatchAPI_Tree { virtual void SetUpRelationHandle() override { UScriptStruct* MaplessRelationType = MakeRelationType("ChildOfNoMapping"); FRelationTypeTraits RelationTraits(EntityManager->GetTypeManager().GetRelationTypeChecked(UE::Mass::Relations::ChildOfHandle), MaplessRelationType); RelationTraits.RoleTraits[static_cast(ERelationRole::Subject)].RequiresExternalMapping = EExternalMappingRequired::No; CA_ASSUME(MaplessRelationType); RelationTypeHandle = EntityManager->GetTypeManager().RegisterType(MoveTemp(RelationTraits)); } }; // commented out on purpose, functionality not implemented yet //IMPLEMENT_AI_INSTANT_TEST(FChildOfBatchAPI_Tree_NoMapping, "System.Mass.Relations.ChildOf.NoMapping.BatchAPI"); // - other tests --- struct FSetChildAsParent : FEntityTestBase { virtual bool InstantTest() override { FTypeHandle RelationTypeHandle = EntityManager->GetTypeManager().GetRelationTypeHandle(FMassChildOfRelation::StaticStruct()); TArray CreatedEntities; EntityManager->BatchCreateEntities(IntsArchetype, {}, 2, CreatedEntities); UE::Mass::FRelationManager& RelationManager = EntityManager->GetRelationManager(); RelationManager.CreateRelationInstance(RelationTypeHandle, CreatedEntities[0], CreatedEntities[1]); EntityManager->FlushCommands(); // original parent becomes the child and vice versa: RelationManager.CreateRelationInstance(RelationTypeHandle, CreatedEntities[1], CreatedEntities[0]); EntityManager->FlushCommands(); TArray ParentSubjects = RelationManager.GetRelationSubjects(RelationTypeHandle, CreatedEntities[0]); TArray ParentObjects = RelationManager.GetRelationSubjects(RelationTypeHandle, CreatedEntities[0]); TArray ChildSubjects = RelationManager.GetRelationSubjects(RelationTypeHandle, CreatedEntities[1]); TArray ChildObjects = RelationManager.GetRelationSubjects(RelationTypeHandle, CreatedEntities[1]); //AITEST_TRUE() return true; } }; IMPLEMENT_AI_INSTANT_TEST(FSetChildAsParent, "System.Mass.Relations.ChildOf.SetChildAsParent"); } // UE::Mass::Test::ChildOfRelation UE_ENABLE_OPTIMIZATION_SHIP #undef LOCTEXT_NAMESPACE #undef WITH_BUILDER