Files
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

612 lines
21 KiB
C++

// 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<UScriptStruct*> MakeRelationType(FName TypeName)
{
UScriptStruct* NewStruct = NewObject<UScriptStruct>(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<FMassEntityHandle> 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<UMassTestProcessorBase>(EntityManager);
Processor->EntityQuery.AddRequirement<FTestFragment_Int>(EMassFragmentAccess::ReadOnly);
Processor->EntityQuery.AddRequirement<FMassChildOfFragment>(EMassFragmentAccess::ReadOnly, EMassFragmentPresence::Optional);
Processor->EntityQuery.GroupBy(EntityManager->GetTypeManager().GetRelationTypeChecked(RelationTypeHandle).RegisteredGroupType);
BuildHierarchy();
return true;
}
return false;
}
virtual void ExecuteTestProcessor(TArray<int32>& 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<FMassChildOfFragment> Parents = Context.GetFragmentView<FMassChildOfFragment>();
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<int32> Scratchpad;
ExecuteTestProcessor(Scratchpad);
TArray<int32> 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<FTestFragment_Int>(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<FTestFragment_Int>(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<FTestFragment_Int>(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<FTestFragment_Int>(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<FTestFragment_Int>(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<FTestFragment_Int>(CreatedEntities[Index]);
IndexCounterFragment.Value = Index;
if (Index > 0)
{
// issue relationship-creating commands, done here for convenience
EntityManager->Defer().PushCommand<FMassCommandMakeRelation<FMassChildOfRelation>>(CreatedEntities[Index], CreatedEntities[Index-1]);
}
}
EntityManager->FlushCommands();
}
virtual bool ReparentEntity(const int32 ChildIndex, const int32 ParentIndex) override
{
EntityManager->Defer().PushCommand<FMassCommandMakeRelation<FMassChildOfRelation>>(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<FTestFragment_Int>(CreatedEntities[Index]);
IndexCounterFragment.Value = Index;
}
// [1, 2] - child of [0]
EntityManager->Defer().PushCommand<FMassCommandMakeRelation<FMassChildOfRelation>>(MakeArrayView(&CreatedEntities[1], 2), MakeArrayView(&CreatedEntities[0], 1));
// [3, 4, 5] - child of [1]
EntityManager->Defer().PushCommand<FMassCommandMakeRelation<FMassChildOfRelation>>(MakeArrayView(&CreatedEntities[3], 3), MakeArrayView(&CreatedEntities[1], 1));
// [6, 7] - child of [2]
EntityManager->Defer().PushCommand<FMassCommandMakeRelation<FMassChildOfRelation>>(MakeArrayView(&CreatedEntities[6], 2), MakeArrayView(&CreatedEntities[2], 1));
EntityManager->FlushCommands();
}
virtual bool ReparentEntity(const int32 ChildIndex, const int32 ParentIndex) override
{
EntityManager->Defer().PushCommand<FMassCommandMakeRelation<FMassChildOfRelation>>(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<uint8>(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<FMassEntityHandle> 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<FMassEntityHandle> ParentSubjects = RelationManager.GetRelationSubjects(RelationTypeHandle, CreatedEntities[0]);
TArray<FMassEntityHandle> ParentObjects = RelationManager.GetRelationSubjects(RelationTypeHandle, CreatedEntities[0]);
TArray<FMassEntityHandle> ChildSubjects = RelationManager.GetRelationSubjects(RelationTypeHandle, CreatedEntities[1]);
TArray<FMassEntityHandle> 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