Files
UnrealEngine/Engine/Source/Developer/MassEntityTestSuite/Private/MassEntityCollectionTest.cpp
2025-05-18 13:04:45 +08:00

415 lines
21 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
//#include "MassArchetypeData.h"
#include "MassEntityManager.h"
#include "MassEntityTestTypes.h"
#include "Algo/Compare.h"
#include "MassEntityCollection.h"
#define LOCTEXT_NAMESPACE "MassTest"
UE_DISABLE_OPTIMIZATION_SHIP
//----------------------------------------------------------------------//
// tests
//----------------------------------------------------------------------//
namespace UE::Mass::Test::EntityCollection
{
struct FEntityCollectionTestBase : FEntityTestBase
{
TArray<FMassEntityHandle> FloatEntities;
TArray<FMassEntityHandle> IntEntities;
TArray<FMassEntityHandle> FloatIntEntities;
int32 EntitiesToCreatePerArchetype = 100;
FRandomStream RandomStream = 0;
static constexpr int32 NumArchetypesUsed = 3;
static constexpr int32 NumTestedEntities = 50;
TConstArrayView<FMassEntityHandle> GetEntityArray(const int32 ArrayIndex)
{
return ArrayIndex == 2
? FloatIntEntities
: (ArrayIndex == 1
? IntEntities
: FloatEntities);
}
TArray<FMassEntityHandle> CreateEntitySubset(const int32 NumEntities = NumTestedEntities)
{
TArray<FMassEntityHandle> EntitiesSubSet;
int32 ArrayIndex = 0;
while (EntitiesSubSet.Num() < NumEntities)
{
EntitiesSubSet.AddUnique(GetEntityArray(ArrayIndex)[RandomStream.RandRange(0, EntitiesToCreatePerArchetype - 1)]);
ArrayIndex = (ArrayIndex + 1) % NumArchetypesUsed;
}
return EntitiesSubSet;
}
TArray<FMassEntityHandle> GetArraySubset(TConstArrayView<FMassEntityHandle> InArray, int32 NumEntities) const
{
TArray<FMassEntityHandle> EntitiesSubSet;
if (NumEntities >= InArray.Num())
{
EntitiesSubSet = InArray;
}
else
{
while (EntitiesSubSet.Num() < NumEntities)
{
EntitiesSubSet.AddUnique(InArray[RandomStream.RandRange(0, InArray.Num() - 1)]);
}
}
return EntitiesSubSet;
}
bool CompareCollectionArrays(TConstArrayView<FMassArchetypeEntityCollection> CollectionsA, TConstArrayView<FMassArchetypeEntityCollection> CollectionsB) const
{
// note that the order of FMassArchetypeEntityCollection instances in individual arrays might be different,
// we need to find the matching instances first.
for (const FMassArchetypeEntityCollection& ArchetypeCollectionA : CollectionsA)
{
const FMassArchetypeEntityCollection* ArchetypeCollectionB = CollectionsB.FindByPredicate([&ArchetypeCollectionA](const FMassArchetypeEntityCollection& Element)
{
return Element.IsSameArchetype(ArchetypeCollectionA);
});
AITEST_NOT_NULL("Matching collection found in the other archetype collection set", ArchetypeCollectionB);
AITEST_TRUE("Individual archetype collections match", ArchetypeCollectionA.IsSame(*ArchetypeCollectionB));
}
return true;
}
virtual bool SetUp() override
{
if (FEntityTestBase::SetUp())
{
EntityManager->BatchCreateEntities(FloatsArchetype, EntitiesToCreatePerArchetype, FloatEntities);
EntityManager->BatchCreateEntities(IntsArchetype, EntitiesToCreatePerArchetype, IntEntities);
EntityManager->BatchCreateEntities(FloatsIntsArchetype, EntitiesToCreatePerArchetype, FloatIntEntities);
return true;
}
return false;
}
};
struct FNewlyCreated : FEntityCollectionTestBase
{
virtual bool InstantTest() override
{
FEntityCollection EntityCollection;
AITEST_TRUE("Newly created collection is empty", EntityCollection.IsEmpty());
AITEST_TRUE("Newly created collection is up-to-date", EntityCollection.IsUpToDate());
AITEST_TRUE("Newly created collection contains no entity handles", EntityCollection.GetEntityHandlesView().IsEmpty());
AITEST_TRUE("Newly created collection contains no archetype collections", EntityCollection.GetCachedPerArchetypeCollections().IsEmpty());
AITEST_TRUE("Newly created collection contains no archetype collections after rebuilding them", EntityCollection.GetUpToDatePerArchetypeCollections(*EntityManager.Get()).IsEmpty());
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FNewlyCreated, "System.Mass.EntityCollection.Empty");
struct FCreateWithHandle : FEntityCollectionTestBase
{
virtual bool InstantTest() override
{
FEntityCollection EntityCollection;
TArray<FMassEntityHandle> EntitiesSubSet = CreateEntitySubset();
EntityCollection.AddHandle(EntitiesSubSet[0]);
AITEST_FALSE("(NOT) Single-handle collection is empty", EntityCollection.IsEmpty());
AITEST_FALSE("(NOT) Single-handle collection is up-to-date", EntityCollection.IsUpToDate());
AITEST_EQUAL("Single-handle collection's number of entity handles", EntityCollection.GetEntityHandlesView().Num(), 1);
AITEST_EQUAL("Single-handle collection's number of cached archetype collections", EntityCollection.GetCachedPerArchetypeCollections().Num(), 0);
AITEST_EQUAL("Single-handle collection's number of updated archetype collections", EntityCollection.GetUpToDatePerArchetypeCollections(*EntityManager.Get()).Num(), 1);
AITEST_TRUE("Single-handle collection is up-to-date after updating archetype collections", EntityCollection.IsUpToDate());
EntityCollection.AddHandle(EntitiesSubSet[1]);
AITEST_FALSE("(NOT) Two-handles collection is up-to-date", EntityCollection.IsUpToDate());
AITEST_EQUAL("Two-handles collection's number of entity handles", EntityCollection.GetEntityHandlesView().Num(), 2);
AITEST_EQUAL("Two-handles collection's number of cached archetype collections", EntityCollection.GetCachedPerArchetypeCollections().Num(), 0);
for (int32 HandleIndex = 2; HandleIndex < EntitiesSubSet.Num(); ++HandleIndex)
{
EntityCollection.AddHandle(EntitiesSubSet[HandleIndex]);
AITEST_EQUAL("Collection's number of entity handles", EntityCollection.GetEntityHandlesView().Num(), HandleIndex + 1);
}
AITEST_EQUAL("Collection's number of cached archetype collections", EntityCollection.GetCachedPerArchetypeCollections().Num(), 0);
AITEST_EQUAL("Collection's number of updated archetype collections", EntityCollection.GetUpToDatePerArchetypeCollections(*EntityManager.Get()).Num(), NumArchetypesUsed);
AITEST_TRUE("Collection is up-to-date after updating archetype collections", EntityCollection.IsUpToDate());
// now we'll verify that the order in which handles are added is irrelevant
FEntityCollection SecondEntityCollection;
for (int32 HandleIndex = EntitiesSubSet.Num() - 1; HandleIndex >= 0; --HandleIndex)
{
SecondEntityCollection.AddHandle(EntitiesSubSet[HandleIndex]);
}
TConstArrayView<FMassArchetypeEntityCollection> ArchetypeCollections = EntityCollection.GetUpToDatePerArchetypeCollections(*EntityManager.Get());
TConstArrayView<FMassArchetypeEntityCollection> SecondArchetypeCollections = SecondEntityCollection.GetUpToDatePerArchetypeCollections(*EntityManager.Get());
return CompareCollectionArrays(ArchetypeCollections, SecondArchetypeCollections);
}
};
IMPLEMENT_AI_INSTANT_TEST(FCreateWithHandle, "System.Mass.EntityCollection.PopulateWithIndividualHandles");
struct FCreateWithHandleArrays : FEntityCollectionTestBase
{
virtual bool InstantTest() override
{
FEntityCollection EntityCollection;
TArray<FMassEntityHandle> EntitiesSubSet = CreateEntitySubset();
check(EntitiesSubSet.Num() >= 5);
TArray<TConstArrayView<FMassEntityHandle>> SubViews = {
MakeArrayView(&EntitiesSubSet[0], EntitiesSubSet.Num() / 2)
, MakeArrayView(&EntitiesSubSet[EntitiesSubSet.Num() / 2], 2)
, MakeArrayView(&EntitiesSubSet[EntitiesSubSet.Num() / 2 + 2], EntitiesSubSet.Num() / 2 - 2)
};
EntityCollection.AppendHandles(SubViews[0]);
AITEST_FALSE("(NOT) Collection is empty", EntityCollection.IsEmpty());
AITEST_FALSE("(NOT) Collection is up-to-date", EntityCollection.IsUpToDate());
AITEST_EQUAL("Collection's number of entity handles from first slice", EntityCollection.GetEntityHandlesView().Num(), SubViews[0].Num());
AITEST_EQUAL("Collection's number of cached archetype collections", EntityCollection.GetCachedPerArchetypeCollections().Num(), 0);
const int32 NumArchetypesInFirstSlice = EntityCollection.GetUpToDatePerArchetypeCollections(*EntityManager.Get()).Num();
AITEST_TRUE("Collection's number of updated archetype collections from first slice", NumArchetypesInFirstSlice > 0 && NumArchetypesInFirstSlice <= NumArchetypesUsed);
AITEST_TRUE("Collection is up-to-date after updating archetype collections", EntityCollection.IsUpToDate());
EntityCollection.AppendHandles(SubViews[1]);
AITEST_FALSE("(NOT) Two-slice collection is up-to-date", EntityCollection.IsUpToDate());
AITEST_EQUAL("Two-slice collection's number of entities", EntityCollection.GetEntityHandlesView().Num(), SubViews[0].Num() + SubViews[1].Num());
const int32 NumArchetypesInTwoSlices = EntityCollection.GetUpToDatePerArchetypeCollections(*EntityManager.Get()).Num();
AITEST_TRUE("Collection's number of updated archetype collections from two slices", NumArchetypesInTwoSlices >= NumArchetypesInFirstSlice);
EntityCollection.AppendHandles(SubViews[2]);
AITEST_EQUAL("Two-slice collection's number of entities", EntityCollection.GetEntityHandlesView().Num(), EntitiesSubSet.Num());
const int32 TotalNumArchetypes = EntityCollection.GetUpToDatePerArchetypeCollections(*EntityManager.Get()).Num();
AITEST_EQUAL("Collection's number of updated archetype collections from all entities", TotalNumArchetypes, NumArchetypesUsed);
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FCreateWithHandleArrays, "System.Mass.EntityCollection.PopulateWithHandleArrays");
struct FCreateWithCollections : FEntityCollectionTestBase
{
virtual bool InstantTest() override
{
TArray<FMassEntityHandle> EntitiesSubSet = CreateEntitySubset();
TArray<FMassArchetypeEntityCollection> ArchetypeCollections;
Utils::CreateEntityCollections(*EntityManager.Get(), EntitiesSubSet, FMassArchetypeEntityCollection::NoDuplicates, ArchetypeCollections);
check(ArchetypeCollections.Num() == NumArchetypesUsed);
static_assert(NumArchetypesUsed >= 3);
FEntityCollection EntityCollection;
EntityCollection.AppendCollection(ArchetypeCollections[0]);
AITEST_FALSE("(NOT) Collection is empty", EntityCollection.IsEmpty());
AITEST_TRUE("Collection is up-to-date", EntityCollection.IsUpToDate());
AITEST_EQUAL("Collection's number of cached archetype collections", EntityCollection.GetCachedPerArchetypeCollections().Num(), 1);
EntityCollection.AppendCollection(MoveTemp(ArchetypeCollections[1]));
AITEST_TRUE("Collection is up-to-date", EntityCollection.IsUpToDate());
AITEST_EQUAL("Collection's number of cached archetype collections", EntityCollection.GetCachedPerArchetypeCollections().Num(), 2);
EntityCollection.AppendCollection(ArchetypeCollections[2]);
AITEST_TRUE("Collection is up-to-date", EntityCollection.IsUpToDate());
AITEST_EQUAL("Collection's number of cached archetype collections", EntityCollection.GetCachedPerArchetypeCollections().Num(), 3);
AITEST_EQUAL("Collection's number of entities", EntityCollection.GetEntityHandlesView().Num(), EntitiesSubSet.Num());
TArray<FMassEntityHandle> ExportedHandles;
ExportedHandles.Append(EntityCollection.GetEntityHandlesView());
ExportedHandles.Sort();
EntitiesSubSet.Sort();
AITEST_TRUE("Collection's entity handles match expectation", Algo::Compare(ExportedHandles, EntitiesSubSet));
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FCreateWithCollections, "System.Mass.EntityCollection.PopulateWithCollections");
struct FCreateWithOutdatedCollections : FEntityCollectionTestBase
{
virtual bool InstantTest() override
{
constexpr int32 NumEntities = 10;
TArray<FMassEntityHandle> EntitiesSubSet = GetArraySubset(FloatEntities, NumEntities);
FMassArchetypeEntityCollection InitialCollection = FMassArchetypeEntityCollection(FloatsArchetype, EntitiesSubSet, FMassArchetypeEntityCollection::NoDuplicates);
AITEST_TRUE("Initially the created archetype collection is up to date", InitialCollection.IsUpToDate());
// we now move the entities to another archetype, to force InitialCollection's invalidation
EntityManager->BatchChangeFragmentCompositionForEntities(MakeArrayView(&InitialCollection, 1), FMassFragmentBitSet(*FTestFragment_Int::StaticStruct()), {});
AITEST_FALSE("(NOT) After entities are moved to another archetype the InitialCollection is up to date", InitialCollection.IsUpToDate());
{
AITEST_SCOPED_CHECK("The entity collection is out of date", 1);
FEntityCollection EntityCollection(InitialCollection);
AITEST_TRUE("The EntityCollection is still empty", EntityCollection.IsEmpty());
}
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FCreateWithOutdatedCollections, "System.Mass.EntityCollection.PopulateWithOutdatedCollections");
struct FCreateWithCollectionsHandlesPairs : FEntityCollectionTestBase
{
virtual bool InstantTest() override
{
const int32 SubEntitiesPerArchetype = EntitiesToCreatePerArchetype / 3;
TArray<TArray<FMassEntityHandle>> PerArchetypeEntitiesSubSet = {
GetArraySubset(FloatEntities, SubEntitiesPerArchetype )
, GetArraySubset(IntEntities, SubEntitiesPerArchetype )
, GetArraySubset(FloatIntEntities, SubEntitiesPerArchetype )
};
TArray<FMassArchetypeEntityCollection> ArchetypeCollections = {
FMassArchetypeEntityCollection(FloatsArchetype, PerArchetypeEntitiesSubSet[0], FMassArchetypeEntityCollection::NoDuplicates)
, FMassArchetypeEntityCollection(IntsArchetype, PerArchetypeEntitiesSubSet[1], FMassArchetypeEntityCollection::NoDuplicates)
, FMassArchetypeEntityCollection(FloatsIntsArchetype, PerArchetypeEntitiesSubSet[2], FMassArchetypeEntityCollection::NoDuplicates)
};
FEntityCollection EntityCollection;
EntityCollection.AppendHandles(PerArchetypeEntitiesSubSet[0], MoveTemp(ArchetypeCollections[0]));
AITEST_FALSE("(NOT) Collection is empty", EntityCollection.IsEmpty());
AITEST_TRUE("Collection is up-to-date", EntityCollection.IsUpToDate());
AITEST_EQUAL("Collection's number of cached archetype collections", EntityCollection.GetCachedPerArchetypeCollections().Num(), 1);
AITEST_EQUAL("Collection's number of stored handles", EntityCollection.GetEntityHandlesView().Num(), SubEntitiesPerArchetype);
EntityCollection.AppendHandles(PerArchetypeEntitiesSubSet[1], MoveTemp(ArchetypeCollections[1]));
AITEST_TRUE("Collection is up-to-date", EntityCollection.IsUpToDate());
AITEST_EQUAL("Collection's number of cached archetype collections", EntityCollection.GetCachedPerArchetypeCollections().Num(), 2);
AITEST_EQUAL("Collection's number of stored entities after second operation", EntityCollection.GetEntityHandlesView().Num(), SubEntitiesPerArchetype * 2);
EntityCollection.AppendHandles(PerArchetypeEntitiesSubSet[2], MoveTemp(ArchetypeCollections[2]));
AITEST_TRUE("Collection is up-to-date", EntityCollection.IsUpToDate());
AITEST_EQUAL("Collection's number of cached archetype collections", EntityCollection.GetCachedPerArchetypeCollections().Num(), 3);
AITEST_EQUAL("Collection's number of stored entities after last operation", EntityCollection.GetEntityHandlesView().Num(), SubEntitiesPerArchetype * 3);
TArray<FMassArchetypeEntityCollection> CachedCollections;
CachedCollections.Append(EntityCollection.GetCachedPerArchetypeCollections());
AITEST_TRUE("Cached collections are the same as updated"
, CompareCollectionArrays(
CachedCollections
, EntityCollection.GetUpToDatePerArchetypeCollections(*EntityManager.Get())));
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FCreateWithCollectionsHandlesPairs, "System.Mass.EntityCollection.PopulateWithCollectionsHandlesPairs");
struct FCreateWithDuplicates : FEntityCollectionTestBase
{
virtual bool InstantTest() override
{
constexpr int32 DuplicatesCount = 5;
FEntityCollection EntityCollection;
FMassEntityHandle EntityHandle = FloatEntities[RandomStream.RandRange(0, FloatEntities.Num() - 1)];
for (int32 Counter = 0; Counter < DuplicatesCount; ++Counter)
{
EntityCollection.AddHandle(EntityHandle);
}
AITEST_EQUAL("Collection's number of stored entity handles", EntityCollection.GetEntityHandlesView().Num(), DuplicatesCount);
AITEST_FALSE("(NOT) Collection is up-to-date", EntityCollection.IsUpToDate());
TConstArrayView<FMassArchetypeEntityCollection> Collections = EntityCollection.GetUpToDatePerArchetypeCollections(*EntityManager);
AITEST_EQUAL("Number of up-to-date archetype collections", Collections.Num(), 1);
#if WITH_MASSENTITY_DEBUG
AITEST_EQUAL("Number of entities in archetype collections", Collections[0].DebugCountEntities(), 1);
#endif // WITH_MASSENTITY_DEBUG
AITEST_EQUAL("Collection's number of stored entity handles after getting updated collections", EntityCollection.GetEntityHandlesView().Num(), DuplicatesCount);
AITEST_TRUE("Duplicates identified and removed", EntityCollection.UpdateAndRemoveDuplicates(*EntityManager));
AITEST_EQUAL("Number of entities after removing duplicates", EntityCollection.GetEntityHandlesView().Num(), 1);
AITEST_EQUAL("Number of cached archetype collections", EntityCollection.GetCachedPerArchetypeCollections().Num(), 1);
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FCreateWithDuplicates, "System.Mass.EntityCollection.PopulateWithDuplicates");
struct FCreationMethodEquivalency : FEntityCollectionTestBase
{
virtual bool InstantTest() override
{
const int32 SubEntitiesPerArchetype = EntitiesToCreatePerArchetype / 3;
TArray<TArray<FMassEntityHandle>> PerArchetypeEntitiesSubSet = {
GetArraySubset(FloatEntities, SubEntitiesPerArchetype )
, GetArraySubset(IntEntities, SubEntitiesPerArchetype )
, GetArraySubset(FloatIntEntities, SubEntitiesPerArchetype )
};
TArray<FMassEntityHandle> EntitiesSubSet = PerArchetypeEntitiesSubSet[0];
EntitiesSubSet.Append(PerArchetypeEntitiesSubSet[1]);
EntitiesSubSet.Append(PerArchetypeEntitiesSubSet[2]);
FEntityCollection EntityCollectionFromArray(EntitiesSubSet);
FEntityCollection EntityCollectionFromHandles;
for (const FMassEntityHandle& EntityHandle : EntitiesSubSet)
{
EntityCollectionFromHandles.AddHandle(EntityHandle);
}
AITEST_TRUE("Collections created from an array of handles vs individual handles"
, CompareCollectionArrays(
EntityCollectionFromArray.GetUpToDatePerArchetypeCollections(*EntityManager.Get())
, EntityCollectionFromHandles.GetUpToDatePerArchetypeCollections(*EntityManager.Get())));
// the following collection is constructed with a mix of arrays and individual handles, starting with a handle
FEntityCollection EntityCollectionFromHandlesAndArrays;
{
EntityCollectionFromHandlesAndArrays.AddHandle(EntitiesSubSet[0]);
EntityCollectionFromHandlesAndArrays.AppendHandles(MakeArrayView(&EntitiesSubSet[1], (EntitiesSubSet.Num()) / 2));
EntityCollectionFromHandlesAndArrays.AddHandle(EntitiesSubSet[EntityCollectionFromHandlesAndArrays.GetEntityHandlesView().Num()]);
const int32 NumHandlesStoredAlready = EntityCollectionFromHandlesAndArrays.GetEntityHandlesView().Num();
EntityCollectionFromHandlesAndArrays.AppendHandles(MakeArrayView(&EntitiesSubSet[NumHandlesStoredAlready], EntitiesSubSet.Num() - NumHandlesStoredAlready));
}
FEntityCollection EntityCollectionFromArraysAndHandles;
{
EntityCollectionFromArraysAndHandles.AppendHandles(MakeArrayView(&EntitiesSubSet[0], (EntitiesSubSet.Num()) / 2));
EntityCollectionFromArraysAndHandles.AddHandle(EntitiesSubSet[EntityCollectionFromArraysAndHandles.GetEntityHandlesView().Num()]);
const int32 NumHandlesStoredAlready = EntityCollectionFromArraysAndHandles.GetEntityHandlesView().Num();
EntityCollectionFromArraysAndHandles.AppendHandles(MakeArrayView(&EntitiesSubSet[NumHandlesStoredAlready], EntitiesSubSet.Num() - NumHandlesStoredAlready));
}
AITEST_TRUE("Collections created with a mix of handles and arrays"
, CompareCollectionArrays(
EntityCollectionFromHandlesAndArrays.GetUpToDatePerArchetypeCollections(*EntityManager.Get())
, EntityCollectionFromArraysAndHandles.GetUpToDatePerArchetypeCollections(*EntityManager.Get())));
AITEST_TRUE("Collections created with a mix of handles and arrays vs heterogeneous approaches"
, CompareCollectionArrays(
EntityCollectionFromHandlesAndArrays.GetUpToDatePerArchetypeCollections(*EntityManager.Get())
, EntityCollectionFromHandles.GetUpToDatePerArchetypeCollections(*EntityManager.Get())));
TArray<FMassArchetypeEntityCollection> ArchetypeCollections = {
FMassArchetypeEntityCollection(FloatsArchetype, PerArchetypeEntitiesSubSet[0], FMassArchetypeEntityCollection::NoDuplicates)
, FMassArchetypeEntityCollection(IntsArchetype, PerArchetypeEntitiesSubSet[1], FMassArchetypeEntityCollection::NoDuplicates)
, FMassArchetypeEntityCollection(FloatsIntsArchetype, PerArchetypeEntitiesSubSet[2], FMassArchetypeEntityCollection::NoDuplicates)
};
FEntityCollection EntityCollection(PerArchetypeEntitiesSubSet[0], MoveTemp(ArchetypeCollections[0]));
EntityCollection.AppendHandles(PerArchetypeEntitiesSubSet[1], MoveTemp(ArchetypeCollections[1]));
EntityCollection.AppendHandles(PerArchetypeEntitiesSubSet[2], MoveTemp(ArchetypeCollections[2]));
AITEST_TRUE("Collection created with handles-and-archetype-collections vs handles-only"
, CompareCollectionArrays(
EntityCollection.GetUpToDatePerArchetypeCollections(*EntityManager.Get())
, EntityCollectionFromHandles.GetUpToDatePerArchetypeCollections(*EntityManager.Get())));
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FCreationMethodEquivalency, "System.Mass.EntityCollection.MethodEquivalency");
}
UE_ENABLE_OPTIMIZATION_SHIP
#undef LOCTEXT_NAMESPACE