// Copyright Epic Games, Inc. All Rights Reserved. #include "AITestsCommon.h" #include "MassEntityManager.h" #include "MassEntityTypes.h" #include "MassEntityTestTypes.h" #include "MassArchetypeData.h" #include "MassArchetypeTypes.h" #include "Algo/RandomShuffle.h" #include "MassObserverNotificationTypes.h" #define LOCTEXT_NAMESPACE "MassTest" UE_DISABLE_OPTIMIZATION_SHIP namespace FMassEntityTest { //----------------------------------------------------------------------------- // Helpers //----------------------------------------------------------------------------- template const TMassSharedFragmentType* GetConstSharedFragmentPtr(const FMassArchetypeSharedFragmentValues& Values) { FConstSharedStruct SharedStruct = Values.GetConstSharedFragmentStruct(TMassSharedFragmentType::StaticStruct()); return SharedStruct.IsValid() ? SharedStruct.GetPtr() : nullptr; } template TMassSharedFragmentType* GetMutableSharedFragmentPtr(FMassArchetypeSharedFragmentValues& Values) { FSharedStruct SharedStruct = Values.GetSharedFragmentStruct(TMassSharedFragmentType::StaticStruct()); return SharedStruct.IsValid() ? SharedStruct.GetPtr() : nullptr; } //----------------------------------------------------------------------------- // FMassArchetypeSharedFragmentValues Tests //----------------------------------------------------------------------------- struct FSharedFragmentValues_Create : FExecutionTestBase { virtual bool InstantTest() override { constexpr int32 TestIntValue = 59; FMassArchetypeSharedFragmentValues Values; { FTestSharedFragment_Int FragmentInstance(TestIntValue); FSharedStruct SharedFragmentInstance = FSharedStruct::Make(FragmentInstance); Values.Add(SharedFragmentInstance); } const FTestSharedFragment_Int* ConstInstance = GetConstSharedFragmentPtr(Values); FTestSharedFragment_Int* NonConstInstance = GetMutableSharedFragmentPtr(Values); AITEST_NULL("Fetching fragment as a const shared fragment should fail", ConstInstance); AITEST_NOT_NULL("Fetching fragment as a shared fragment should not fail", NonConstInstance); AITEST_EQUAL("The fetched value should match the expectations", NonConstInstance->Value, TestIntValue); return true; } }; IMPLEMENT_AI_INSTANT_TEST(FSharedFragmentValues_Create, "System.Mass.SharedFragments.CreateValue"); struct FSharedFragmentValues_CreateConst : FExecutionTestBase { virtual bool InstantTest() override { constexpr int32 TestIntValue = 59; FMassArchetypeSharedFragmentValues Values; { FConstSharedStruct SharedFragmentInstance = FConstSharedStruct::Make(TestIntValue); Values.Add(SharedFragmentInstance); } const FTestConstSharedFragment_Int* ConstInstance = GetConstSharedFragmentPtr(Values); FTestConstSharedFragment_Int* NonConstInstance = GetMutableSharedFragmentPtr(Values); AITEST_NULL("Fetching fragment as a shared fragment should fail", NonConstInstance); AITEST_NOT_NULL("Fetching fragment as a const shared fragment should not fail", ConstInstance); AITEST_EQUAL("The fetched value should match the expectations", ConstInstance->Value, TestIntValue); return true; } }; IMPLEMENT_AI_INSTANT_TEST(FSharedFragmentValues_CreateConst, "System.Mass.SharedFragments.CreateConstValue"); struct FSharedFragmentValues_Contains : FExecutionTestBase { virtual bool InstantTest() override { constexpr int32 TestIntValue = 31; constexpr float TestFloatValue = 63.f; FMassArchetypeSharedFragmentValues Values; AITEST_FALSE("Empty FMassArchetypeSharedFragmentValues should fail ContainsType tests" , Values.ContainsType(FTestSharedFragment_Int::StaticStruct())); AITEST_FALSE("Empty FMassArchetypeSharedFragmentValues should fail ContainsType tests" , Values.ContainsType()); { FSharedStruct SharedFragmentInstance = FSharedStruct::Make(TestIntValue); Values.Add(SharedFragmentInstance); } AITEST_TRUE("Empty FMassArchetypeSharedFragmentValues should fail ContainsType tests" , Values.ContainsType(FTestSharedFragment_Int::StaticStruct())); AITEST_TRUE("Empty FMassArchetypeSharedFragmentValues should fail ContainsType tests" , Values.ContainsType()); { FSharedStruct SharedFragmentInstance = FSharedStruct::Make(TestFloatValue); Values.Add(SharedFragmentInstance); } AITEST_TRUE("Empty FMassArchetypeSharedFragmentValues should fail ContainsType tests" , Values.ContainsType(FTestSharedFragment_Float::StaticStruct())); AITEST_TRUE("Empty FMassArchetypeSharedFragmentValues should fail ContainsType tests" , Values.ContainsType()); return true; } }; IMPLEMENT_AI_INSTANT_TEST(FSharedFragmentValues_Contains, "System.Mass.SharedFragments.Contains"); struct FSharedFragmentValues_Append : FExecutionTestBase { virtual bool InstantTest() override { constexpr int32 TestIntValue = 31; constexpr float TestFloatValue = 63.f; FMassArchetypeSharedFragmentValues ValuesNonConstInt; ValuesNonConstInt.Add(FSharedStruct::Make(TestIntValue)); FMassArchetypeSharedFragmentValues ValuesNonConstFloat; ValuesNonConstFloat.Add(FSharedStruct::Make(TestFloatValue)); FMassArchetypeSharedFragmentValues ValuestNonConstIntFloat; ValuestNonConstIntFloat.Add(FSharedStruct::Make(TestIntValue)); ValuestNonConstIntFloat.Add(FSharedStruct::Make(TestFloatValue)); // `Appending` int/float values to an new Values instance should result in the same result as `Adding` them { FMassArchetypeSharedFragmentValues Values; Values.Append(ValuesNonConstInt); AITEST_TRUE("#1 Append results should match expectations", Values.HasSameValues(ValuesNonConstInt)); Values.Append(ValuesNonConstFloat); AITEST_TRUE("#2 Append results should match expectations", Values.HasSameValues(ValuestNonConstIntFloat)); } { FMassArchetypeSharedFragmentValues Values; Values.Append(ValuesNonConstFloat); AITEST_TRUE("#3 Append results should match expectations", Values.HasSameValues(ValuesNonConstFloat)); Values.Append(ValuesNonConstInt); AITEST_TRUE("#4 Append results should match expectations", Values.HasSameValues(ValuestNonConstIntFloat)); } FMassArchetypeSharedFragmentValues ValuesConstInt; ValuesConstInt.Add(FConstSharedStruct::Make(TestIntValue)); FMassArchetypeSharedFragmentValues ValuesConstFloat; ValuesConstFloat.Add(FConstSharedStruct::Make(TestFloatValue)); FMassArchetypeSharedFragmentValues ValuestConstIntFloat; ValuestConstIntFloat.Add(FConstSharedStruct::Make(TestIntValue)); ValuestConstIntFloat.Add(FConstSharedStruct::Make(TestFloatValue)); { FMassArchetypeSharedFragmentValues Values; Values.Append(ValuesConstInt); AITEST_TRUE("#5 Append results should match expectations", Values.HasSameValues(ValuesConstInt)); Values.Append(ValuesConstFloat); AITEST_TRUE("#6 Append results should match expectations", Values.HasSameValues(ValuestConstIntFloat)); } { FMassArchetypeSharedFragmentValues Values; Values.Append(ValuesConstFloat); AITEST_TRUE("#7 Append results should match expectations", Values.HasSameValues(ValuesConstFloat)); Values.Append(ValuesConstInt); AITEST_TRUE("#8 Append results should match expectations", Values.HasSameValues(ValuestConstIntFloat)); } return true; } }; IMPLEMENT_AI_INSTANT_TEST(FSharedFragmentValues_Append, "System.Mass.SharedFragments.Append"); struct FSharedFragmentValues_Remove : FExecutionTestBase { virtual bool InstantTest() override { constexpr int32 TestIntValue = 31; constexpr float TestFloatValue = 63.f; { FMassArchetypeSharedFragmentValues ValuesNonConstInt; ValuesNonConstInt.Add(FSharedStruct::Make(TestIntValue)); FMassArchetypeSharedFragmentValues ValuesNonConstFloat; ValuesNonConstFloat.Add(FSharedStruct::Make(TestFloatValue)); FMassArchetypeSharedFragmentValues ValuestNonConstIntFloat; ValuestNonConstIntFloat.Add(FSharedStruct::Make(TestIntValue)); ValuestNonConstIntFloat.Add(FSharedStruct::Make(TestFloatValue)); { FMassArchetypeSharedFragmentValues Values = ValuestNonConstIntFloat; AITEST_TRUE("Assignment should result in same values", Values.HasSameValues(ValuestNonConstIntFloat)); // removing just the Int shared fragment Values.Remove(ValuesNonConstInt.GetSharedFragmentBitSet()); AITEST_TRUE("#1 Removal results should match expectations", Values.HasSameValues(ValuesNonConstFloat)); } { FMassArchetypeSharedFragmentValues Values = ValuestNonConstIntFloat; // removing just the Float shared fragment Values.Remove(ValuesNonConstFloat.GetSharedFragmentBitSet()); AITEST_TRUE("#2 Removal results should match expectations", Values.HasSameValues(ValuesNonConstInt)); } } { FMassArchetypeSharedFragmentValues ValuesConstInt; ValuesConstInt.Add(FConstSharedStruct::Make(TestIntValue)); FMassArchetypeSharedFragmentValues ValuesConstFloat; ValuesConstFloat.Add(FConstSharedStruct::Make(TestFloatValue)); FMassArchetypeSharedFragmentValues ValuestConstIntFloat; ValuestConstIntFloat.Add(FConstSharedStruct::Make(TestIntValue)); ValuestConstIntFloat.Add(FConstSharedStruct::Make(TestFloatValue)); { FMassArchetypeSharedFragmentValues Values = ValuestConstIntFloat; AITEST_TRUE("Assignment should result in same values", Values.HasSameValues(ValuestConstIntFloat)); // removing just the Int shared fragment Values.Remove(ValuesConstInt.GetConstSharedFragmentBitSet()); AITEST_TRUE("#3 Removal results should match expectations", Values.HasSameValues(ValuesConstFloat)); } { FMassArchetypeSharedFragmentValues Values = ValuestConstIntFloat; // removing just the Float shared fragment Values.Remove(ValuesConstFloat.GetConstSharedFragmentBitSet()); AITEST_TRUE("#4 Removal results should match expectations", Values.HasSameValues(ValuesConstInt)); } } return true; } }; IMPLEMENT_AI_INSTANT_TEST(FSharedFragmentValues_Remove, "System.Mass.SharedFragments.Remove"); struct FSharedFragmentValues_Hash : FExecutionTestBase { virtual bool InstantTest() override { constexpr int32 TestIntValue = 31; constexpr float TestFloatValue = 63.f; FMassArchetypeSharedFragmentValues ValuestNonConstIntFloat; ValuestNonConstIntFloat.Add(FSharedStruct::Make(TestIntValue)); ValuestNonConstIntFloat.Add(FSharedStruct::Make(TestFloatValue)); AITEST_SCOPED_CHECK("Expecting the containers to be sorted", 1); const uint32 EmptyHash = ValuestNonConstIntFloat.CalculateHash(); AITEST_EQUAL("Expecting unsorted collection hashing to result in 0", EmptyHash, 0u); ValuestNonConstIntFloat.Sort(); const uint32 ValidHash = ValuestNonConstIntFloat.CalculateHash(); AITEST_NOT_EQUAL("Expecting sorted collection hashing to result in non 0", ValidHash, 0u); return true; } }; IMPLEMENT_AI_INSTANT_TEST(FSharedFragmentValues_Hash, "System.Mass.SharedFragments.Hash"); struct FSharedFragment_ForEach : FExecutionTestBase { virtual bool InstantTest() override { constexpr int32 NumSharedFragments = 4; TStaticArray TestInitValues = { 9, 1, 12, 13}; for (int32 InitValue : TestInitValues) { EntityManager->GetOrCreateSharedFragment(InitValue); } TArray Results; TArray ModifiedValues; EntityManager->ForEachSharedFragment([&Results, &ModifiedValues](FTestSharedFragment_Int& SharedFragment) { Results.Add(SharedFragment.Value); ModifiedValues.Add(SharedFragment.Value += 100); }); AITEST_EQUAL("Number of processed shared fragments", Results.Num(), NumSharedFragments); for (int32 InitValue : TestInitValues) { AITEST_TRUE("Read values matches init values", Results.Find(InitValue) != INDEX_NONE); } TArray MutatedResults; EntityManager->ForEachSharedFragment([&MutatedResults](const FTestSharedFragment_Int& SharedFragment) { MutatedResults.Add(SharedFragment.Value); }); AITEST_EQUAL("Number of shared fragments processed in second round", Results.Num(), NumSharedFragments); for (int32 ModifiedValue : ModifiedValues) { AITEST_TRUE("Read values matches values set in the first round", MutatedResults.Find(ModifiedValue) != INDEX_NONE); } constexpr int32 ConditionalLimit = 10; TArray ConditionalResults; EntityManager->ForEachSharedFragmentConditional( [](FTestSharedFragment_Int& SharedFragment) { return SharedFragment.Value > ConditionalLimit; } , [&ConditionalResults](FTestSharedFragment_Int& SharedFragment) { ConditionalResults.Add(SharedFragment.Value); } ); for (int32 Value : ConditionalResults) { AITEST_TRUE("Only the values matching the condition get processed", Value > ConditionalLimit); } return true; } }; IMPLEMENT_AI_INSTANT_TEST(FSharedFragment_ForEach, "System.Mass.SharedFragments.ForEach"); struct FConstSharedFragment_ForEach : FExecutionTestBase { virtual bool InstantTest() override { constexpr int32 NumSharedFragments = 4; TStaticArray TestInitValues = { 9, 1, 12, 13 }; for (int32 InitValue : TestInitValues) { EntityManager->GetOrCreateConstSharedFragment(InitValue); } TArray Results; EntityManager->ForEachConstSharedFragment([&Results](const FTestConstSharedFragment_Int& SharedFragment) { Results.Add(SharedFragment.Value); }); AITEST_EQUAL("Number of processed shared fragments", Results.Num(), NumSharedFragments); for (int32 InitValue : TestInitValues) { AITEST_TRUE("Read values matches init values", Results.Find(InitValue) != INDEX_NONE); } constexpr int32 ConditionalLimit = 10; TArray ConditionalResults; EntityManager->ForEachConstSharedFragmentConditional( [](const FTestConstSharedFragment_Int& SharedFragment) { return SharedFragment.Value > ConditionalLimit; } , [&ConditionalResults](const FTestConstSharedFragment_Int& SharedFragment) { ConditionalResults.Add(SharedFragment.Value); } ); for (int32 Value : ConditionalResults) { AITEST_TRUE("Only the values matching the condition get processed", Value > ConditionalLimit); } return true; } }; IMPLEMENT_AI_INSTANT_TEST(FConstSharedFragment_ForEach, "System.Mass.ConstSharedFragments.ForEach"); //----------------------------------------------------------------------------- // Entity-related Tests //----------------------------------------------------------------------------- struct FSharedFragmentBase : public FEntityTestBase { template FConstStructView GetSharedFragmentView(const FMassEntityHandle EntityHandle) { if constexpr (TIsDerivedFrom::IsDerived) { return EntityManager->GetSharedFragmentDataStruct(EntityHandle, TSharedFragment::StaticStruct()); } else { return EntityManager->GetConstSharedFragmentDataStruct(EntityHandle, TSharedFragment::StaticStruct()); } }; template void CreateEntities(TArray& OutEntityHandles, const int32 NumToCreate, const typename TSharedFragment::FValueType TestValue) { TSharedFragment FragmentInstance(TestValue); FMassArchetypeSharedFragmentValues SharedIntValues; TSharedStruct SharedFragmentInstance = TSharedStruct::Make(FragmentInstance); SharedIntValues.Add(SharedFragmentInstance); EntityManager->BatchCreateEntities(FloatsArchetype, SharedIntValues, NumToCreate, OutEntityHandles); } template void CreateEntity(FMassEntityHandle& OutEntityHandle, const typename TSharedFragment::FValueType TestValue) { TSharedFragment FragmentInstance(TestValue); FMassArchetypeSharedFragmentValues SharedIntValues; TSharedStruct SharedFragmentInstance = TSharedStruct::Make(FragmentInstance); SharedIntValues.Add(SharedFragmentInstance); OutEntityHandle = EntityManager->CreateEntity(FloatsArchetype, SharedIntValues); } }; template struct FSharedFragment_CreateEntitesWithSharedFragment : public FSharedFragmentBase { virtual bool InstantTest() override { constexpr int32 TestIntValueA = 1023; constexpr int32 TestIntValueB = 63; FMassEntityHandle EntityA, EntityB; CreateEntity(EntityA, TestIntValueA); CreateEntity(EntityB, TestIntValueB); AITEST_EQUAL("Both entities should end up in the same archetype", EntityManager->GetArchetypeForEntityUnsafe(EntityA) , EntityManager->GetArchetypeForEntityUnsafe(EntityB)); /*FConstStructView SharedFragmentA, SharedFragmentB; if constexpr (TIsDerivedFrom::IsDerived) { SharedFragmentA = EntityManager->GetSharedFragmentDataStruct(EntityA, FTestSharedFragment_Int::StaticStruct()); SharedFragmentB = EntityManager->GetSharedFragmentDataStruct(EntityB, FTestSharedFragment_Int::StaticStruct()); } else { SharedFragmentA = EntityManager->GetConstSharedFragmentDataStruct(EntityA, FTestSharedFragment_Int::StaticStruct()); SharedFragmentB = EntityManager->GetConstSharedFragmentDataStruct(EntityB, FTestSharedFragment_Int::StaticStruct()); }*/ FConstStructView SharedFragmentA = GetSharedFragmentView(EntityA); FConstStructView SharedFragmentB = GetSharedFragmentView(EntityB); AITEST_TRUE("SharedFragmentA should be valid", SharedFragmentA.IsValid()); AITEST_TRUE("SharedFragmentB should be valid", SharedFragmentB.IsValid()); AITEST_EQUAL("SharedFragmentA should be of expected type", SharedFragmentA.GetScriptStruct(), TSharedFragment::StaticStruct()); AITEST_EQUAL("SharedFragmentB should be of expected type", SharedFragmentB.GetScriptStruct(), TSharedFragment::StaticStruct()); AITEST_NOT_EQUAL("SharedFragmentA and SharedFragmentB should be different instanceS", SharedFragmentA, SharedFragmentB); AITEST_NOT_EQUAL("SharedFragmentA and SharedFragmentB should be distinct", SharedFragmentA, SharedFragmentB); AITEST_EQUAL("SharedFragmentA's value should match the expected value", SharedFragmentA.Get().Value, TestIntValueA); AITEST_EQUAL("SharedFragmentB's value should match the expected value", SharedFragmentB.Get().Value, TestIntValueB); return true; } }; using FSharedFragment_CreateEntitesWithNonConstSharedFragment = FSharedFragment_CreateEntitesWithSharedFragment; IMPLEMENT_AI_INSTANT_TEST(FSharedFragment_CreateEntitesWithNonConstSharedFragment, "System.Mass.SharedFragments.CreateEntities"); using FSharedFragment_CreateEntitesWithConstSharedFragment = FSharedFragment_CreateEntitesWithSharedFragment; IMPLEMENT_AI_INSTANT_TEST(FSharedFragment_CreateEntitesWithConstSharedFragment, "System.Mass.SharedFragments.CreateEntitiesConst"); template struct FSharedFragment_BatchCreateEntitesWithSharedFragment : public FSharedFragmentBase { virtual bool InstantTest() override { constexpr int32 TestIntValueA = 1023; constexpr int32 TestIntValueB = 63; const int32 EntitiesPerChunk = FMassArchetypeHelper::ArchetypeDataFromHandleChecked(FloatsArchetype).GetNumEntitiesPerChunk(); // we create more than one chunk can handle properly test moving entities between chunks const int32 EntitiesToCreateNumA = FMath::FloorToInt(float(EntitiesPerChunk) * 1.2f); const int32 EntitiesToCreateNumB = 1; constexpr int32 ExpectedNumberOfInitialChunks = 3; TArray EntitiesA; TArray EntitiesB; CreateEntities(EntitiesA, EntitiesToCreateNumA, TestIntValueA); CreateEntities(EntitiesB, EntitiesToCreateNumB, TestIntValueB); FMassArchetypeHandle CommonArchetype = EntityManager->GetArchetypeForEntityUnsafe(EntitiesA[0]); AITEST_EQUAL("All the entities should end up in the same archetype" , FMassArchetypeHelper::ArchetypeDataFromHandleChecked(CommonArchetype).GetNumEntities(), EntitiesToCreateNumA + EntitiesToCreateNumB); AITEST_EQUAL("The total number of chunks in the resulting archetype should match expectations" , FMassArchetypeHelper::ArchetypeDataFromHandleChecked(CommonArchetype).GetChunkCount(), ExpectedNumberOfInitialChunks); for (FMassEntityHandle EntityHandle : EntitiesA) { FConstStructView SharedFragment = GetSharedFragmentView(EntityHandle); AITEST_TRUE("SharedFragment for entity type A should be valid", SharedFragment.IsValid()); AITEST_EQUAL("SharedFragment for entity type A should be of expected type", SharedFragment.GetScriptStruct(), TSharedFragment::StaticStruct()); AITEST_EQUAL("SharedFragment's value for entity type A should match the expected value", SharedFragment.Get().Value, TestIntValueA); } FConstStructView SharedFragment = GetSharedFragmentView(EntitiesB[0]); AITEST_TRUE("SharedFragment for entity type B should be valid", SharedFragment.IsValid()); AITEST_EQUAL("SharedFragment for entity type B should be of expected type", SharedFragment.GetScriptStruct(), TSharedFragment::StaticStruct()); AITEST_EQUAL("SharedFragment's value for entity type B should match the expected value", SharedFragment.Get().Value, TestIntValueB); return true; } }; using FSharedFragment_BatchCreateEntitesWithNonConstSharedFragment = FSharedFragment_BatchCreateEntitesWithSharedFragment; IMPLEMENT_AI_INSTANT_TEST(FSharedFragment_BatchCreateEntitesWithNonConstSharedFragment, "System.Mass.SharedFragments.BatchCreateEntities"); using FSharedFragment_BatchCreateEntitesWithConstSharedFragment = FSharedFragment_BatchCreateEntitesWithSharedFragment; IMPLEMENT_AI_INSTANT_TEST(FSharedFragment_BatchCreateEntitesWithConstSharedFragment, "System.Mass.SharedFragments.BatchCreateEntitiesConst"); struct FConstSharedFragment_AddToEntity : FEntityTestBase { virtual bool InstantTest() override { constexpr int32 TestIntValue = 1023; const FTestConstSharedFragment_Int FragmentInstance(TestIntValue); const FConstSharedStruct SharedFragmentInstance = FSharedStruct::Make(FragmentInstance); const FMassEntityHandle EntityHandle = EntityManager->CreateEntity(FloatsArchetype); const FTestConstSharedFragment_Int* EntitySharedFragment = EntityManager->GetConstSharedFragmentDataPtr(EntityHandle); AITEST_NULL("Initially the entity is not expected to have the shared fragment", EntitySharedFragment); EntityManager->AddConstSharedFragmentToEntity(EntityHandle, SharedFragmentInstance); EntitySharedFragment = EntityManager->GetConstSharedFragmentDataPtr(EntityHandle); AITEST_NOT_NULL("The entity is expected to have the shared fragment after the operation", EntitySharedFragment); AITEST_EQUAL("The the shared fragment is expected to store the configured value", EntitySharedFragment->Value, TestIntValue); AITEST_NOT_EQUAL("The the entity's new archetype is not the same as the original one", EntityManager->GetArchetypeForEntity(EntityHandle), FloatsArchetype); // at this point the Entity already has a shared fragment of a given type // now we're going to add it again and test the systems behavior, we'll be adding the same FMasSharedFragment type // in both const and non-const way. constexpr int32 DifferentTestIntValue = TestIntValue + 1; const FTestConstSharedFragment_Int DifferentFragmentInstance(DifferentTestIntValue); const FSharedStruct DifferentSharedFragmentInstance = FSharedStruct::Make(DifferentFragmentInstance); const FConstSharedStruct DifferentConstSharedFragmentInstance = FConstSharedStruct::Make(DifferentFragmentInstance); GetTestRunner().AddExpectedError(TEXT("Changing shared fragment value of entities is not supported"), EAutomationExpectedErrorFlags::Contains, 2); const bool bSuccessfullyAddedSharedFragment = EntityManager->AddConstSharedFragmentToEntity(EntityHandle, DifferentSharedFragmentInstance); AITEST_FALSE("Adding existing shared fragment type should fail", bSuccessfullyAddedSharedFragment); const bool bSuccessfullyAddedConstSharedFragment = EntityManager->AddConstSharedFragmentToEntity(EntityHandle, DifferentConstSharedFragmentInstance); AITEST_FALSE("Adding existing const shared fragment type should fail", bSuccessfullyAddedConstSharedFragment); return true; } }; IMPLEMENT_AI_INSTANT_TEST(FConstSharedFragment_AddToEntity, "System.Mass.ConstSharedFragments.AddToEntity"); struct FSharedFragment_AddToEntity : FEntityTestBase { virtual bool InstantTest() override { constexpr int32 TestIntValue = 1023; const FTestSharedFragment_Int FragmentInstance(TestIntValue); const FSharedStruct SharedFragmentInstance = FSharedStruct::Make(FragmentInstance); const FMassEntityHandle EntityHandle = EntityManager->CreateEntity(FloatsArchetype); const FTestSharedFragment_Int* EntitySharedFragment = EntityManager->GetSharedFragmentDataPtr(EntityHandle); AITEST_NULL("Initially the entity is not expected to have the shared fragment", EntitySharedFragment); EntityManager->AddSharedFragmentToEntity(EntityHandle, SharedFragmentInstance); EntitySharedFragment = EntityManager->GetSharedFragmentDataPtr(EntityHandle); AITEST_NOT_NULL("The entity is expected to have the shared fragment after the operation", EntitySharedFragment); AITEST_EQUAL("The the shared fragment is expected to store the configured value", EntitySharedFragment->Value, TestIntValue); // at this point the Entity already has a shared fragment of a given type // now we're going to add it again and test the systems behavior. constexpr int32 DifferentTestIntValue = TestIntValue + 1; const FTestSharedFragment_Int DifferentFragmentInstance(DifferentTestIntValue); const FSharedStruct DifferentSharedFragmentInstance = FSharedStruct::Make(DifferentFragmentInstance); GetTestRunner().AddExpectedError(TEXT("Changing shared fragment value of entities is not supported"), EAutomationExpectedErrorFlags::Contains, 1); const bool bSuccessfullyAddedSharedFragment = EntityManager->AddSharedFragmentToEntity(EntityHandle, DifferentSharedFragmentInstance); AITEST_FALSE("Adding existing shared fragment type should fail", bSuccessfullyAddedSharedFragment); return true; } }; IMPLEMENT_AI_INSTANT_TEST(FSharedFragment_AddToEntity, "System.Mass.SharedFragments.AddToEntity"); struct FConstSharedFragment_RemoveFromEntity : FEntityTestBase { virtual bool InstantTest() override { constexpr int32 TestIntValue = 1023; const FTestConstSharedFragment_Int FragmentInstance(TestIntValue); const FConstSharedStruct SharedFragmentInstance = FSharedStruct::Make(FragmentInstance); const FMassEntityHandle EntityHandle = EntityManager->CreateEntity(FloatsArchetype); const FTestConstSharedFragment_Int* EntitySharedFragment = EntityManager->GetConstSharedFragmentDataPtr(EntityHandle); AITEST_NULL("Initially the entity is not expected to have the shared fragment", EntitySharedFragment); AITEST_FALSE("Attempt to remove shared fragment from entity that doesn't have shared fragment should return false and do nothing", EntityManager->RemoveConstSharedFragmentFromEntity(EntityHandle, *FTestConstSharedFragment_Int::StaticStruct())); AITEST_TRUE("Adding shared fragment to entity should succeed", EntityManager->AddConstSharedFragmentToEntity(EntityHandle, SharedFragmentInstance)); EntitySharedFragment = EntityManager->GetConstSharedFragmentDataPtr(EntityHandle); AITEST_NOT_NULL("The entity is expected to have the shared fragment after the operation", EntitySharedFragment); AITEST_EQUAL("The the shared fragment is expected to store the configured value", EntitySharedFragment->Value, TestIntValue); AITEST_TRUE( "Removing shared fragment from entity that has the shared fragment should succeed", EntityManager->RemoveConstSharedFragmentFromEntity(EntityHandle, *FTestConstSharedFragment_Int::StaticStruct())); EntitySharedFragment = EntityManager->GetConstSharedFragmentDataPtr(EntityHandle); AITEST_NULL("The entity is not expected to have the shared fragment after the operation", EntitySharedFragment); AITEST_EQUAL("The the entity's new archetype is the same as the initial one", EntityManager->GetArchetypeForEntity(EntityHandle), FloatsArchetype); return true; } }; IMPLEMENT_AI_INSTANT_TEST(FConstSharedFragment_RemoveFromEntity, "System.Mass.ConstSharedFragments.RemoveFromEntity"); struct FSharedFragment_RemoveFromEntity : FEntityTestBase { virtual bool InstantTest() override { constexpr int32 TestIntValue = 1023; const FTestSharedFragment_Int FragmentInstance(TestIntValue); const FSharedStruct SharedFragmentInstance = FSharedStruct::Make(FragmentInstance); const FMassEntityHandle EntityHandle = EntityManager->CreateEntity(FloatsArchetype); const FTestSharedFragment_Int* EntitySharedFragment = EntityManager->GetSharedFragmentDataPtr(EntityHandle); AITEST_NULL("Initially the entity is not expected to have the shared fragment", EntitySharedFragment); AITEST_FALSE("Attempt to remove shared fragment from entity that doesn't have shared fragment should return false and do nothing", EntityManager->RemoveSharedFragmentFromEntity(EntityHandle, *FTestSharedFragment_Int::StaticStruct())); AITEST_TRUE("Adding shared fragment to entity should succeed", EntityManager->AddSharedFragmentToEntity(EntityHandle, SharedFragmentInstance)); EntitySharedFragment = EntityManager->GetSharedFragmentDataPtr(EntityHandle); AITEST_NOT_NULL("The entity is expected to have the shared fragment after the operation", EntitySharedFragment); AITEST_EQUAL("The the shared fragment is expected to store the configured value", EntitySharedFragment->Value, TestIntValue); AITEST_TRUE( "Removing shared fragment from entity that has the shared fragment should succeed", EntityManager->RemoveSharedFragmentFromEntity(EntityHandle, *FTestSharedFragment_Int::StaticStruct())); EntitySharedFragment = EntityManager->GetSharedFragmentDataPtr(EntityHandle); AITEST_NULL("The entity is not expected to have the shared fragment after the operation", EntitySharedFragment); AITEST_EQUAL("The the entity's new archetype is the same as the initial one", EntityManager->GetArchetypeForEntity(EntityHandle), FloatsArchetype); return true; } }; IMPLEMENT_AI_INSTANT_TEST(FSharedFragment_RemoveFromEntity, "System.Mass.SharedFragments.RemoveFromEntity"); struct FSharedFragment_BatchAddToEntity : FEntityTestBase { virtual bool InstantTest() override { constexpr int32 TestIntValue = 1023; const FMassArchetypeHandle InitialArchetype = FloatsArchetype; const int32 EntitiesPerChunk = FMassArchetypeHelper::ArchetypeDataFromHandleChecked(InitialArchetype).GetNumEntitiesPerChunk(); const int32 EntitiesToCreateNum = FMath::FloorToInt(float(EntitiesPerChunk) * 2.2f); const int32 EntitiesToMoveNum = FMath::FloorToInt(float(EntitiesPerChunk) * 1.2f); TArray CreatedEntityHandles; EntityManager->BatchCreateEntities(InitialArchetype, EntitiesToCreateNum, CreatedEntityHandles); TArray EntitiesToMove = CreatedEntityHandles; Algo::RandomShuffle(EntitiesToMove); TConstArrayView EntitiesMoved = MakeArrayView(EntitiesToMove.GetData(), EntitiesToMoveNum); FMassArchetypeEntityCollection EntityCollection(InitialArchetype, EntitiesMoved, FMassArchetypeEntityCollection::EDuplicatesHandling::NoDuplicates); FMassArchetypeSharedFragmentValues SharedValues; FConstSharedStruct ConstSharedFragment = FConstSharedStruct::Make(TestIntValue); SharedValues.Add(ConstSharedFragment); EntityManager->BatchAddSharedFragmentsForEntities(MakeArrayView(&EntityCollection, 1), SharedValues); const FMassArchetypeHandle TargetArchetype = EntityManager->GetArchetypeForEntityUnsafe(EntitiesToMove[0]); const int32 EntitiesMovedNum = FMassArchetypeHelper::ArchetypeDataFromHandleChecked(TargetArchetype).GetNumEntities(); AITEST_EQUAL("Number of entities moves needs to match expectations", EntitiesMovedNum, EntitiesToMoveNum); for (const FMassEntityHandle& EntityHandle : EntitiesMoved) { FTestConstSharedFragment_Int* SharedFragmentInstance = EntityManager->GetConstSharedFragmentDataPtr(EntityHandle); AITEST_NOT_NULL("Every entity moved needs to have a valid shared fragment", SharedFragmentInstance); AITEST_EQUAL("The shared fragment's value needs to match expectations", SharedFragmentInstance->Value, TestIntValue); } return true; } }; IMPLEMENT_AI_INSTANT_TEST(FSharedFragment_BatchAddToEntity, "System.Mass.SharedFragments.BatchAddToEntity"); struct FSharedFragment_BatchSetAttempt : FEntityTestBase { virtual bool InstantTest() override { constexpr int32 TestIntValue = 1023; constexpr int32 OtherTestIntValue = TestIntValue + 1; const int32 EntitiesPerChunk = FMassArchetypeHelper::ArchetypeDataFromHandleChecked(FloatsArchetype).GetNumEntitiesPerChunk(); const int32 EntitiesToCreateNum = FMath::FloorToInt(float(EntitiesPerChunk) * 2.2f); const int32 EntitiesToMoveNum = FMath::FloorToInt(float(EntitiesPerChunk) * 1.2f); TArray CreatedEntityHandles; FMassArchetypeSharedFragmentValues SharedIntValues; FConstSharedStruct ConstSharedFragment = FConstSharedStruct::Make(TestIntValue); SharedIntValues.Add(ConstSharedFragment); TSharedRef CreationContext = EntityManager->BatchCreateEntities(FloatsArchetype, SharedIntValues, EntitiesToCreateNum, CreatedEntityHandles); const FMassArchetypeHandle ResultingArchetype = CreationContext->GetEntityCollections(*EntityManager.Get())[0].GetArchetype(); FMassArchetypeEntityCollection EntityCollection(ResultingArchetype, CreatedEntityHandles, FMassArchetypeEntityCollection::EDuplicatesHandling::NoDuplicates); // attempting to add the same values again should fail with checks and ensures { AITEST_SCOPED_CHECK("Setting shared fragment values without archetype change is not supported", 1); AITEST_SCOPED_CHECK("Trying to set shared fragment values, without adding new shared fragments", 1); EntityManager->BatchAddSharedFragmentsForEntities(MakeArrayView(&EntityCollection, 1), SharedIntValues); } { FMassArchetypeSharedFragmentValues DifferentSharedIntValues; FConstSharedStruct OtherConstSharedFragment = FConstSharedStruct::Make(OtherTestIntValue); DifferentSharedIntValues.Add(OtherConstSharedFragment); AITEST_SCOPED_CHECK("Setting shared fragment values without archetype change is not supported", 1); AITEST_SCOPED_CHECK("Trying to set shared fragment values, without adding new shared fragments", 1); EntityManager->BatchAddSharedFragmentsForEntities(MakeArrayView(&EntityCollection, 1), DifferentSharedIntValues); } return true; } }; IMPLEMENT_AI_INSTANT_TEST(FSharedFragment_BatchSetAttempt, "System.Mass.SharedFragments.BatchSetAttempt"); struct FSharedFragment_BatchAddToEmpty : FEntityTestBase { virtual bool InstantTest() override { constexpr int32 NumToReserve = 32; FMassArchetypeSharedFragmentValues SharedIntValues; FSharedStruct SharedFragment = FSharedStruct::Make(); SharedIntValues.Add(SharedFragment); TArray ReservedEntityHandles; EntityManager->BatchReserveEntities(NumToReserve, ReservedEntityHandles); FMassArchetypeEntityCollection EntityCollection(FMassArchetypeHandle(), ReservedEntityHandles, FMassArchetypeEntityCollection::EDuplicatesHandling::NoDuplicates); // attempting to add the values before the entities are created is not a valid operation { AITEST_SCOPED_CHECK("Adding shared fragments to archetype-less entities is not supported", 1); EntityManager->BatchAddSharedFragmentsForEntities(MakeArrayView(&EntityCollection, 1), SharedIntValues); } return true; } }; IMPLEMENT_AI_INSTANT_TEST(FSharedFragment_BatchAddToEmpty, "System.Mass.SharedFragments.BatchAddToEmpty"); struct FSharedFragmentValues_TypeEquivalency : FEntityTestBase { virtual bool InstantTest() override { constexpr int32 TestIntValue = 32; FMassArchetypeSharedFragmentValues Values; const FMassSharedFragmentBitSet EmptySharedFragmentBitSet; const FMassConstSharedFragmentBitSet EmptyConstSharedFragmentBitSet; AITEST_TRUE("Empty shared values match type with empty bitset", Values.HasExactSharedFragmentTypesMatch(EmptySharedFragmentBitSet)); AITEST_TRUE("Empty const shared values match type with empty const bitset", Values.HasExactConstSharedFragmentTypesMatch(EmptyConstSharedFragmentBitSet)); const FMassSharedFragmentBitSet IntSharedFragmentBitSet = FMassSharedFragmentBitSet::GetTypeBitSet(); FMassSharedFragmentBitSet IntFloatSharedFragmentBitSet = IntSharedFragmentBitSet; IntFloatSharedFragmentBitSet.Add(); Values.Add(FSharedStruct::Make(TestIntValue)); AITEST_TRUE("Single shared value type matches expected bitset", Values.HasExactSharedFragmentTypesMatch(IntSharedFragmentBitSet)); AITEST_FALSE("Single shared value type doesn't match two-type bitset", Values.HasExactSharedFragmentTypesMatch(IntFloatSharedFragmentBitSet)); AITEST_FALSE("Single shared value type doesn't match empty", Values.HasExactSharedFragmentTypesMatch(EmptySharedFragmentBitSet)); const FMassConstSharedFragmentBitSet IntConstSharedFragmentBitSet = FMassConstSharedFragmentBitSet::GetTypeBitSet(); FMassConstSharedFragmentBitSet IntFloatConstSharedFragmentBitSet = IntConstSharedFragmentBitSet; IntFloatConstSharedFragmentBitSet.Add(); Values.Add(FConstSharedStruct::Make(TestIntValue)); AITEST_TRUE("Single const shared value type matches expected bitset", Values.HasExactConstSharedFragmentTypesMatch(IntConstSharedFragmentBitSet)); AITEST_FALSE("Single const shared value type doesn't match two-type bitset", Values.HasExactConstSharedFragmentTypesMatch(IntFloatConstSharedFragmentBitSet)); AITEST_FALSE("Single const shared value type doesn't match empty", Values.HasExactConstSharedFragmentTypesMatch(EmptyConstSharedFragmentBitSet)); Values.Remove(IntSharedFragmentBitSet); AITEST_TRUE("Emptied shared values match type with empty bitset", Values.HasExactSharedFragmentTypesMatch(EmptySharedFragmentBitSet)); Values.Remove(IntConstSharedFragmentBitSet); AITEST_TRUE("Emptied const shared values match type with empty const bitset", Values.HasExactConstSharedFragmentTypesMatch(EmptyConstSharedFragmentBitSet)); return true; } }; IMPLEMENT_AI_INSTANT_TEST(FSharedFragmentValues_TypeEquivalency, "System.Mass.SharedFragments.TypeEquivalency"); struct FSharedFragmentValues_GetOrCreateWithArgs : FEntityTestBase { virtual bool InstantTest() override { constexpr int32 ConstIntValueOne = 1; constexpr int32 ConstIntValueTwo = 2; const FSharedStruct SharedFragment1 = EntityManager->GetOrCreateSharedFragment(/*Args*/ConstIntValueOne); const FSharedStruct SharedFragment2 = EntityManager->GetOrCreateSharedFragment(/*Args*/ConstIntValueOne); const FSharedStruct SharedFragment3 = EntityManager->GetOrCreateSharedFragment(/*Args*/ConstIntValueTwo); AITEST_EQUAL("Shared fragments created for same struct type using same constructor value should share memory", SharedFragment1, SharedFragment2); AITEST_EQUAL("Value in shared struct should be the same as the argument provided to GetOrCreateSharedFragment", SharedFragment1.Get().Value, ConstIntValueOne); AITEST_NOT_EQUAL("Shared fragments created for same struct type using different constructor values should not share memory", SharedFragment1, SharedFragment3); AITEST_EQUAL("Value in shared struct should be the same as the argument provided to GetOrCreateSharedFragment", SharedFragment3.Get().Value, ConstIntValueTwo); return true; } }; IMPLEMENT_AI_INSTANT_TEST(FSharedFragmentValues_GetOrCreateWithArgs, "System.Mass.SharedFragments.GetOrCreate.WithArgs"); struct FSharedFragmentValues_GetOrCreateWithStruct : FEntityTestBase { virtual bool InstantTest() override { constexpr int32 ConstIntValueOne = 1; constexpr int32 ConstIntValueTwo = 2; const FTestSharedFragment_Int TestSharedFragment_Int1(ConstIntValueOne); const FTestSharedFragment_Int TestSharedFragment_Int2(ConstIntValueOne); const FTestSharedFragment_Int TestSharedFragment_Int3(ConstIntValueTwo); const FSharedStruct SharedFragment1 = EntityManager->GetOrCreateSharedFragment(TestSharedFragment_Int1); const FSharedStruct SharedFragment2 = EntityManager->GetOrCreateSharedFragment(TestSharedFragment_Int2); const FSharedStruct SharedFragment3 = EntityManager->GetOrCreateSharedFragment(TestSharedFragment_Int3); AITEST_EQUAL("Shared fragments created for same struct type using same constructor value should share memory", SharedFragment1, SharedFragment2); AITEST_EQUAL("Value in shared struct should be the same as the argument provided to GetOrCreateSharedFragment", SharedFragment1.Get().Value, ConstIntValueOne); AITEST_NOT_EQUAL("Shared fragments created for same struct type using different constructor values should not share memory", SharedFragment1, SharedFragment3); AITEST_EQUAL("Value in shared struct should be the same as the argument provided to GetOrCreateSharedFragment", SharedFragment3.Get().Value, ConstIntValueTwo); return true; } }; IMPLEMENT_AI_INSTANT_TEST(FSharedFragmentValues_GetOrCreateWithStruct, "System.Mass.SharedFragments.GetOrCreate.WithStruct"); struct FSharedFragmentValues_GetOrCreateNoArgs : FEntityTestBase { virtual bool InstantTest() override { const FSharedStruct SharedFragment1 = EntityManager->GetOrCreateSharedFragment(); const FSharedStruct SharedFragment2 = EntityManager->GetOrCreateSharedFragment(); const FSharedStruct SharedFragment3 = EntityManager->GetOrCreateSharedFragment(); AITEST_EQUAL("Shared fragments created for same struct type using default constructor should share memory", SharedFragment1, SharedFragment2); AITEST_EQUAL("Shared fragments created for same struct type using default constructor should share memory", SharedFragment1, SharedFragment3); return true; } }; IMPLEMENT_AI_INSTANT_TEST(FSharedFragmentValues_GetOrCreateNoArgs, "System.Mass.SharedFragments.GetOrCreate.NoArgs"); struct FSharedFragmentValues_GetOrCreateConstNoArgs : FEntityTestBase { virtual bool InstantTest() override { const FConstSharedStruct SharedFragment1 = EntityManager->GetOrCreateConstSharedFragment(); const FConstSharedStruct SharedFragment2 = EntityManager->GetOrCreateConstSharedFragment(); const FConstSharedStruct SharedFragment3 = EntityManager->GetOrCreateConstSharedFragment(); AITEST_EQUAL("Shared fragments created for same struct type using default constructor should share memory", SharedFragment1, SharedFragment2); AITEST_EQUAL("Shared fragments created for same struct type using default constructor should share memory", SharedFragment1, SharedFragment3); return true; } }; IMPLEMENT_AI_INSTANT_TEST(FSharedFragmentValues_GetOrCreateConstNoArgs, "System.Mass.SharedFragments.GetOrCreate.ConstNoArgs"); struct FSharedFragmentValues_GetOrCreateConstWithArgs : FEntityTestBase { virtual bool InstantTest() override { constexpr int32 ConstIntValueOne = 1; constexpr int32 ConstIntValueTwo = 2; const FConstSharedStruct SharedFragment1 = EntityManager->GetOrCreateConstSharedFragment(/*Args*/ConstIntValueOne); const FConstSharedStruct SharedFragment2 = EntityManager->GetOrCreateConstSharedFragment(/*Args*/ConstIntValueOne); const FConstSharedStruct SharedFragment3 = EntityManager->GetOrCreateConstSharedFragment(/*Args*/ConstIntValueTwo); AITEST_EQUAL("Shared fragments created for same struct type using same constructor value should share memory", SharedFragment1, SharedFragment2); AITEST_EQUAL("Value in shared struct should be the same as the argument provided to GetOrCreateConstSharedFragment", SharedFragment1.Get().Value, ConstIntValueOne); AITEST_NOT_EQUAL("Shared fragments created for same struct type using different constructor values should not share memory", SharedFragment1, SharedFragment3); AITEST_EQUAL("Value in shared struct should be the same as the argument provided to GetOrCreateConstSharedFragment", SharedFragment3.Get().Value, ConstIntValueTwo); return true; } }; IMPLEMENT_AI_INSTANT_TEST(FSharedFragmentValues_GetOrCreateConstWithArgs, "System.Mass.SharedFragments.GetOrCreate.ConstWithArgs"); struct FSharedFragmentValues_GetOrCreateConstWithStruct : FEntityTestBase { virtual bool InstantTest() override { constexpr int32 ConstIntValueOne = 1; constexpr int32 ConstIntValueTwo = 2; const FTestConstSharedFragment_Int TestSharedFragment_Int1(ConstIntValueOne); const FTestConstSharedFragment_Int TestSharedFragment_Int2(ConstIntValueOne); const FTestConstSharedFragment_Int TestSharedFragment_Int3(ConstIntValueTwo); const FConstSharedStruct SharedFragment1 = EntityManager->GetOrCreateConstSharedFragment(TestSharedFragment_Int1); const FConstSharedStruct SharedFragment2 = EntityManager->GetOrCreateConstSharedFragment(TestSharedFragment_Int2); const FConstSharedStruct SharedFragment3 = EntityManager->GetOrCreateConstSharedFragment(TestSharedFragment_Int3); AITEST_EQUAL("Shared fragments created for same struct type using same constructor value should share memory", SharedFragment1, SharedFragment2); AITEST_EQUAL("Value in shared struct should be the same as the argument provided to GetOrCreateConstSharedFragment", SharedFragment1.Get().Value, ConstIntValueOne); AITEST_NOT_EQUAL("Shared fragments created for same struct type using different constructor values should not share memory", SharedFragment1, SharedFragment3); AITEST_EQUAL("Value in shared struct should be the same as the argument provided to GetOrCreateConstSharedFragment", SharedFragment3.Get().Value, ConstIntValueTwo); return true; } }; IMPLEMENT_AI_INSTANT_TEST(FSharedFragmentValues_GetOrCreateConstWithStruct, "System.Mass.SharedFragments.GetOrCreate.ConstWithStruct"); } // FMassEntityTest UE_ENABLE_OPTIMIZATION_SHIP #undef LOCTEXT_NAMESPACE