// Copyright Epic Games, Inc. All Rights Reserved. #include "AITestsCommon.h" #include "MassEntityTestTypes.h" #include "MassEntityBuilder.h" #include "MassEntityManager.h" #include "MassExecutionContext.h" #include "MassExecutor.h" #include "MassProcessingContext.h" #define LOCTEXT_NAMESPACE "MassTest" UE_DISABLE_OPTIMIZATION_SHIP /** * Tests to be added: * - test observers triggering as expected, i.e. respecting the construction context * - entity grouping */ namespace UE::Mass::Test::EntityBuilder { struct FSimpleBuild : FEntityTestBase { virtual bool InstantTest() override { #if WITH_MASSENTITY_DEBUG int32 EntitiesCreated = EntityManager->DebugGetArchetypeEntitiesCount(IntsArchetype); #endif // WITH_MASSENTITY_DEBUG int32 SomeCounter = 1; int32 EntitiesCreatedThisStep = 0; { FEntityBuilder EntityBuilder(*EntityManager.Get()); EntityBuilder.Add_GetRef(SomeCounter++); EntityBuilder.Commit(); EntitiesCreatedThisStep = 1; } #if WITH_MASSENTITY_DEBUG AITEST_EQUAL("Number of entities created with basic use", EntityManager->DebugGetArchetypeEntitiesCount(IntsArchetype) - EntitiesCreated, EntitiesCreatedThisStep); EntitiesCreated = EntityManager->DebugGetArchetypeEntitiesCount(IntsArchetype); #endif // WITH_MASSENTITY_DEBUG { FEntityBuilder EntityBuilder(*EntityManager.Get()); ON_SCOPE_EXIT { EntityBuilder.Commit(); }; EntityBuilder.Add_GetRef(SomeCounter++); } EntitiesCreatedThisStep = 1; #if WITH_MASSENTITY_DEBUG AITEST_EQUAL("Number of entities created with ON_SCOPE_EXIT", EntityManager->DebugGetArchetypeEntitiesCount(IntsArchetype) - EntitiesCreated, EntitiesCreatedThisStep); EntitiesCreated = EntityManager->DebugGetArchetypeEntitiesCount(IntsArchetype); #endif // WITH_MASSENTITY_DEBUG { FScopedEntityBuilder EntityBuilder(*EntityManager.Get()); EntityBuilder.Add_GetRef(SomeCounter++); } EntitiesCreatedThisStep = 1; #if WITH_MASSENTITY_DEBUG AITEST_EQUAL("Number of entities created with scoped builder", EntityManager->DebugGetArchetypeEntitiesCount(IntsArchetype) - EntitiesCreated, EntitiesCreatedThisStep); EntitiesCreated = EntityManager->DebugGetArchetypeEntitiesCount(IntsArchetype); #endif // WITH_MASSENTITY_DEBUG { FScopedEntityBuilder EntityBuilder(*EntityManager.Get()); EntityBuilder.Add_GetRef(SomeCounter++); FEntityBuilder EntityBuilder2 = EntityBuilder; EntityBuilder2.Commit(); } EntitiesCreatedThisStep = 2; #if WITH_MASSENTITY_DEBUG AITEST_EQUAL("Number of entities created with with a scoped builder and its regular copy", EntityManager->DebugGetArchetypeEntitiesCount(IntsArchetype) - EntitiesCreated, EntitiesCreatedThisStep); EntitiesCreated = EntityManager->DebugGetArchetypeEntitiesCount(IntsArchetype); #endif // WITH_MASSENTITY_DEBUG { FEntityBuilder EntityBuilder(*EntityManager.Get()); EntityBuilder.Add_GetRef(SomeCounter++); EntityBuilder.Commit(); FEntityBuilder EntityBuilder2 = EntityBuilder; } EntitiesCreatedThisStep = 1; #if WITH_MASSENTITY_DEBUG AITEST_EQUAL("Number of entities created with Commit and an abandoned copy of a builder", EntityManager->DebugGetArchetypeEntitiesCount(IntsArchetype) - EntitiesCreated, EntitiesCreatedThisStep); EntitiesCreated = EntityManager->DebugGetArchetypeEntitiesCount(IntsArchetype); #endif // WITH_MASSENTITY_DEBUG { FEntityBuilder EntityBuilder(*EntityManager.Get()); EntityBuilder.Add_GetRef(SomeCounter++); FEntityBuilder EntityBuilder2 = EntityBuilder; EntityBuilder2.Commit(); EntityBuilder.Reset(); } EntitiesCreatedThisStep = 1; #if WITH_MASSENTITY_DEBUG AITEST_EQUAL("Number of entities created with committed copy and abandoned original builder", EntityManager->DebugGetArchetypeEntitiesCount(IntsArchetype) - EntitiesCreated, EntitiesCreatedThisStep); EntitiesCreated = EntityManager->DebugGetArchetypeEntitiesCount(IntsArchetype); #endif // WITH_MASSENTITY_DEBUG return true; } }; IMPLEMENT_AI_INSTANT_TEST(FSimpleBuild, "System.Mass.EntityBuilder.SimpleBuild"); struct FAbort: FEntityTestBase { virtual bool InstantTest() override { { FEntityBuilder Builder(EntityManager.ToSharedRef()); Builder.Add(); FMassEntityHandle ReservedEntityHandle = Builder.GetEntityHandle(); { const bool bValid = EntityManager->IsEntityValid(ReservedEntityHandle); AITEST_TRUE("Before committing the entity handle is reserved", bValid); const bool bIsBuilt = EntityManager->IsEntityActive(ReservedEntityHandle); AITEST_FALSE("Before committing the entity is already created", bIsBuilt); } Builder.Reset(); { const bool bValid = EntityManager->IsEntityValid(ReservedEntityHandle); AITEST_FALSE("After resetting the entity handle is still valid", bValid); } } FMassEntityHandle AbandonedEntityHandle; { FEntityBuilder Builder(EntityManager.ToSharedRef()); Builder.Add(); AbandonedEntityHandle = Builder.GetEntityHandle(); } { const bool bValid = EntityManager->IsEntityValid(AbandonedEntityHandle); AITEST_FALSE("After builder's destruction without committing the entity handle is still valid", bValid); } return true; } }; IMPLEMENT_AI_INSTANT_TEST(FAbort, "System.Mass.EntityBuilder.Abort"); struct FOneliner: FEntityTestBase { virtual bool InstantTest() override { int32 TotalCountCreated = 0; { FMassEntityHandle CreatedEntity = EntityManager->MakeEntityBuilder().Add().Commit(); ++TotalCountCreated; const bool bIsBuilt = EntityManager->IsEntityActive(CreatedEntity); AITEST_TRUE("The entity has been created", bIsBuilt); #if WITH_MASSENTITY_DEBUG AITEST_TRUE("Only a single entity has been created", EntityManager->DebugGetArchetypeEntitiesCount(IntsArchetype) == TotalCountCreated); #endif // WITH_MASSENTITY_DEBUG } { FEntityBuilder EntityBuilder = EntityManager->MakeEntityBuilder().Add(); EntityBuilder.Commit(); ++TotalCountCreated; } #if WITH_MASSENTITY_DEBUG AITEST_TRUE("The number of entities created matches expectations", EntityManager->DebugGetArchetypeEntitiesCount(IntsArchetype) == TotalCountCreated); #endif // WITH_MASSENTITY_DEBUG { // we're not committing so this builder won't create an entity. EntityManager->MakeEntityBuilder().Add(); // similarly here, even reserving the entity won't result in building that entity without manual `Commit` call. FMassEntityHandle ReservedEntity = EntityManager->MakeEntityBuilder().Add().GetEntityHandle(); } #if WITH_MASSENTITY_DEBUG AITEST_EQUAL("The number of entities created after not committing builders", EntityManager->DebugGetArchetypeEntitiesCount(IntsArchetype), TotalCountCreated); #endif // WITH_MASSENTITY_DEBUG return true; } }; IMPLEMENT_AI_INSTANT_TEST(FOneliner, "System.Mass.EntityBuilder.OneLiner"); struct FCopyBuilder : FEntityTestBase { virtual bool InstantTest() override { const int32 ValueA = 1; FEntityBuilder BuilderA(*EntityManager.Get()); BuilderA.Add(ValueA); const int32 ValueB = ValueA + 1; FEntityBuilder BuilderB = EntityManager->MakeEntityBuilder(); BuilderB.Add(ValueB); // a different way of setting the value FEntityBuilder BuilderC = BuilderA; BuilderC.GetOrCreate().Value = ValueB; BuilderA.Commit(); BuilderB.Commit(); BuilderC.Commit(); FTestFragment_Int* FragmentA = EntityManager->GetFragmentDataPtr(BuilderA.GetEntityHandle()); AITEST_NOT_NULL("The original entity has the expected fragment", FragmentA); AITEST_EQUAL("The value of the original entity's fragment matches expectations", FragmentA->Value, ValueA); FTestFragment_Int* FragmentB = EntityManager->GetFragmentDataPtr(BuilderB.GetEntityHandle()); AITEST_NOT_NULL("The copied entity has the expected fragment", FragmentB); AITEST_EQUAL("The value of the copied entity's fragment matches expectations", FragmentB->Value, ValueB); FTestFragment_Int* FragmentC = EntityManager->GetFragmentDataPtr(BuilderC.GetEntityHandle()); AITEST_NOT_NULL("The other copied entity has the expected fragment", FragmentC); AITEST_EQUAL("The value of the other copied entity's fragment matches expectations", FragmentC->Value, FragmentB->Value); return true; } }; IMPLEMENT_AI_INSTANT_TEST(FCopyBuilder, "System.Mass.EntityBuilder.Copy"); struct FOverride : FEntityTestBase { virtual bool InstantTest() override { FEntityBuilder BuilderA(*EntityManager.Get()); BuilderA.Add(); FEntityBuilder BuilderB(*EntityManager.Get()); BuilderB.Add(); FEntityBuilder BuilderC = BuilderB; FMassEntityHandle EntityA = BuilderA.GetEntityHandle(); FMassEntityHandle EntityB = BuilderB.GetEntityHandle(); FMassEntityHandle EntityC = BuilderC.GetEntityHandle(); AITEST_NOT_EQUAL("Entities reserved by different builders, A|B", EntityA, EntityB); AITEST_NOT_EQUAL("Entities reserved by different builders, A|C", EntityA, EntityC); AITEST_NOT_EQUAL("Entities reserved by different builders, B|C", EntityB, EntityC); // the following operation is expected to stomp the settings of the target builder, but not the entity BuilderB = BuilderA; FMassEntityHandle EntityB2 = BuilderB.GetEntityHandle(); AITEST_TRUE("The uncommitted (i.e. reserved) entity handle does not change with builder's config override", EntityB == EntityB2); // overriding a committed builder results in creation of a new handle. BuilderC.Commit(); BuilderC = BuilderA; FMassEntityHandle EntityC2 = BuilderC.GetEntityHandle(); AITEST_TRUE("The committed entity handle differs from the new one", EntityC != EntityC2); return true; } }; IMPLEMENT_AI_INSTANT_TEST(FOverride, "System.Mass.EntityBuilder.Override"); struct FPassOver : FEntityTestBase { virtual bool InstantTest() override { { FEntityBuilder BuilderA(*EntityManager.Get()); BuilderA.Add(); // forces handle reservation FMassEntityHandle EntityA = BuilderA.GetEntityHandle(); FMassEntityHandle EntityB; { FEntityBuilder BuilderB(*EntityManager.Get()); BuilderB.Add(); EntityB = BuilderB.GetEntityHandle(); BuilderA = MoveTemp(BuilderB); } // at this point EntityB should be valid while the original EntityA not AITEST_TRUE("The original entity is invalid", EntityManager->IsEntityValid(EntityA) == false); AITEST_TRUE("The passed-over entity entity is valid", EntityManager->IsEntityValid(EntityB)); } { FEntityBuilder BuilderA(*EntityManager.Get()); BuilderA.Add(); // forces handle reservation FMassEntityHandle EntityA = BuilderA.Commit(); FMassEntityHandle EntityB; { FEntityBuilder BuilderB(*EntityManager.Get()); BuilderB.Add(); EntityB = BuilderB.GetEntityHandle(); BuilderA = MoveTemp(BuilderB); } // at this point EntityB should be valid while the original EntityA not AITEST_TRUE("The original entity is valid, since it was committed", EntityManager->IsEntityValid(EntityA)); AITEST_TRUE("The original entity is active", EntityManager->IsEntityActive(EntityA)); AITEST_TRUE("The passed-over entity entity is valid", EntityManager->IsEntityValid(EntityB)); } { FEntityBuilder BuilderA(*EntityManager.Get()); BuilderA.Add(); // forces handle reservation FMassEntityHandle EntityA = BuilderA.GetEntityHandle(); FMassEntityHandle EntityB; { FEntityBuilder BuilderB(*EntityManager.Get()); BuilderB.Add(); EntityB = BuilderB.Commit(); BuilderA = MoveTemp(BuilderB); } // at this point EntityB should be valid while the original EntityA not AITEST_TRUE("The original entity is valid", EntityManager->IsEntityValid(EntityA)); AITEST_TRUE("The original entity is NOT active", EntityManager->IsEntityActive(EntityA) == false); AITEST_TRUE("The secondary entity entity is valid", EntityManager->IsEntityValid(EntityB)); AITEST_TRUE("The secondary entity is active", EntityManager->IsEntityActive(EntityB)); } { FEntityBuilder BuilderA(*EntityManager.Get()); BuilderA.Add(); // forces handle reservation FMassEntityHandle EntityA = BuilderA.Commit(); FMassEntityHandle EntityB; { FEntityBuilder BuilderB(*EntityManager.Get()); BuilderB.Add(); EntityB = BuilderB.Commit(); BuilderA = MoveTemp(BuilderB); } // at this point EntityB should be valid while the original EntityA not AITEST_TRUE("The original entity is valid", EntityManager->IsEntityValid(EntityA)); AITEST_TRUE("The original entity is active", EntityManager->IsEntityActive(EntityA)); AITEST_TRUE("The secondary entity entity is valid", EntityManager->IsEntityValid(EntityB)); AITEST_TRUE("The secondary entity is active", EntityManager->IsEntityActive(EntityB)); { AITEST_SCOPED_CHECK("Trying to commit an already committed", 1); BuilderA.Commit(); } FMassEntityHandle EntityA2 = BuilderA.GetEntityHandle(); AITEST_TRUE("The entity handle is the same as the builder's that has been moved", EntityA2 == EntityB); } return true; } }; IMPLEMENT_AI_INSTANT_TEST(FPassOver, "System.Mass.EntityBuilder.PassOver"); struct FBuilderReuse : FEntityTestBase { virtual bool InstantTest() override { FEntityBuilder Builder = EntityManager->MakeEntityBuilder(); Builder.Add(); auto TestBuilder = [this, &Builder](const FMassArchetypeHandle& ExpectedArchetype)-> bool { TArray Entities = { Builder.CommitAndReprepare() , Builder.CommitAndReprepare() }; const FMassArchetypeHandle LocalArchetype = Builder.GetArchetypeHandle(); AITEST_NOT_EQUAL("Two entities created sequentially", Entities[0], Entities[1]); AITEST_EQUAL("Entities' archetype", EntityManager->GetArchetypeForEntity(Entities[0]), EntityManager->GetArchetypeForEntity(Entities[1])); AITEST_EQUAL("Builders archetype and entities' archetype", Builder.GetArchetypeHandle(), EntityManager->GetArchetypeForEntity(Entities[0])) AITEST_TRUE("Archetype matches expectations", EntityManager->GetArchetypeForEntity(Entities[0]) == ExpectedArchetype); return true; }; if (TestBuilder(IntsArchetype)) { Builder.Add(); return TestBuilder(FloatsIntsArchetype); } return false; } }; IMPLEMENT_AI_INSTANT_TEST(FBuilderReuse, "System.Mass.EntityBuilder.Reuse"); struct FDuringProcessing : FEntityTestBase { virtual bool InstantTest() override { constexpr int32 NumIterations = 5; // creating a single entity to enforce the execution function of the processor we're going to use to execute // exactly once EntityManager->CreateEntity(IntsArchetype); TArray EntityHandles; constexpr int32 InitialValueToSet = 100; int32 ValueToSet = InitialValueToSet; UMassTestProcessorBase* Processor = NewTestProcessor(EntityManager); Processor->ForEachEntityChunkExecutionFunction = [&ValueToSet, &EntityHandles](FMassExecutionContext& Context) { FEntityBuilder AsyncBuilder = Context.GetEntityManagerChecked().MakeEntityBuilder(); AsyncBuilder.Add(ValueToSet++); EntityHandles.Add(AsyncBuilder.Commit()); }; Processor->EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); FMassProcessingContext ProcessingContext(EntityManager, /*DeltaSeconds=*/0.f, /*bFlushCommandBuffer=*/false); for (int32 Iteration = 0; Iteration < NumIterations; ++Iteration) { Executor::RunProcessorsView(MakeArrayView(reinterpret_cast(&Processor), 1), ProcessingContext); AITEST_EQUAL(FString::Printf(TEXT("Number of entities after iteration %d"), Iteration), EntityHandles.Num(), Iteration + 1); } for (int32 Iteration = 0; Iteration < NumIterations; ++Iteration) { AITEST_FALSE(FString::Printf(TEXT("(NOT) Entity %d is `created`"), Iteration), EntityManager->IsEntityBuilt(EntityHandles[Iteration])); } EntityManager->FlushCommands(); for (int32 Iteration = 0; Iteration < NumIterations; ++Iteration) { AITEST_TRUE(FString::Printf(TEXT("Entity %d is `created`"), Iteration), EntityManager->IsEntityBuilt(EntityHandles[Iteration])); AITEST_TRUE(FString::Printf(TEXT("Entity %d has the right archetype"), Iteration), EntityManager->GetArchetypeForEntity(EntityHandles[Iteration]) == IntsArchetype); if (Iteration + 1 < NumIterations) { AITEST_FALSE(FString::Printf(TEXT("(NOT) Entity handles are the same %d"), Iteration), EntityHandles[Iteration] == EntityHandles[Iteration + 1]); } } return true; } }; IMPLEMENT_AI_INSTANT_TEST(FDuringProcessing, "System.Mass.EntityBuilder.DuringProcessing"); struct FSyncBuildingAsyncSubmission : FEntityTestBase { virtual bool InstantTest() override { // creating a single entity to enforce the execution function of the processor we're going to use to execute // exactly once EntityManager->CreateEntity(IntsArchetype); FEntityBuilder SyncBuilder = EntityManager->MakeEntityBuilder(); SyncBuilder.Add(); const FMassEntityHandle ReservedHandle = SyncBuilder.GetEntityHandle(); int32 ProcessedEntitiesCount = 0; UMassTestProcessorBase* Processor = NewTestProcessor(EntityManager); Processor->ForEachEntityChunkExecutionFunction = [&SyncBuilder, &ProcessedEntitiesCount](FMassExecutionContext& Context) { SyncBuilder.Commit(); ProcessedEntitiesCount += Context.GetNumEntities(); }; Processor->EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); FMassProcessingContext ProcessingContext(EntityManager, /*DeltaSeconds=*/0.f, /*bFlushCommandBuffer=*/false); Executor::RunProcessorsView(MakeArrayView(reinterpret_cast(&Processor), 1), ProcessingContext); AITEST_EQUAL("Number of fully-formed entities expected", ProcessedEntitiesCount, 1); AITEST_EQUAL("The entity handle before and after async commit", ReservedHandle, SyncBuilder.GetEntityHandle()); AITEST_TRUE("The Builder is in `Committed` state", SyncBuilder.IsCommitted()); // since the commands are not flushed yet, due to ProcessingContext's values, we expect the entity to not be created yet AITEST_FALSE("(NOT) the entity has been created", EntityManager->IsEntityBuilt(ReservedHandle)); { AITEST_SCOPED_CHECK("Trying to commit an already committed", 1); AITEST_INFO("Second execution of the processor shouldn't change a thing."); Executor::RunProcessorsView(MakeArrayView(reinterpret_cast(&Processor), 1), ProcessingContext); } AITEST_EQUAL("Run 2: The entity handle before and after async commit", ReservedHandle, SyncBuilder.GetEntityHandle()); AITEST_TRUE("Run 2: The Builder is in `Committed` state", SyncBuilder.IsCommitted()); // since the commands are not flushed yet, due to ProcessingContext's values, we expect the entity to not be created yet AITEST_FALSE("(NOT) Run 2: the entity has been created", EntityManager->IsEntityBuilt(ReservedHandle)); EntityManager->FlushCommands(); #if WITH_MASSENTITY_DEBUG AITEST_EQUAL("Number of entities in the target archetype, after flushing", EntityManager->DebugGetArchetypeEntitiesCount(IntsArchetype), 2); #endif // WITH_MASSENTITY_DEBUG AITEST_TRUE("the entity has been created", EntityManager->IsEntityBuilt(ReservedHandle)); return true; } }; IMPLEMENT_AI_INSTANT_TEST(FSyncBuildingAsyncSubmission, "System.Mass.EntityBuilder.SyncBuildingAsyncSubmission"); struct FAllElementsUsed : FEntityTestBase { FMassEntityHandle OriginalEntity; const int32 TestIntValue = 17; const float TestFloatValue = 3.1415f; const float TestSharedFloatValue = 2.71828f; const int32 TestSharedIntValue = 1009; virtual bool SetUp() override { if (FEntityTestBase::SetUp()) { // quick builder just to create an entity with known properties FEntityBuilder Builder(EntityManager.ToSharedRef()); Builder.Add(TestIntValue); Builder.Add(TestFloatValue); Builder.Add(); Builder.Add(TestSharedFloatValue); Builder.Add(TestSharedIntValue); OriginalEntity = Builder.Commit(); return true; } return false; } virtual bool InstantTest() override { FMassArchetypeCompositionDescriptor PredictedComposition; PredictedComposition.Add(); PredictedComposition.Add(); PredictedComposition.Add(); PredictedComposition.Add(); PredictedComposition.Add(); // testing composition const FMassArchetypeHandle ArchetypeHandle = EntityManager->GetArchetypeForEntity(OriginalEntity); const FMassArchetypeCompositionDescriptor& ArchetypeComposition = EntityManager->GetArchetypeComposition(ArchetypeHandle); AITEST_TRUE("Resulting archetype composition matches prediction", ArchetypeComposition.IsEquivalent(PredictedComposition)); return TestEntity(OriginalEntity); } bool TestEntity(const FMassEntityHandle TestedEntity) const { { FTestFragment_Int* IntFragmentInstance = EntityManager->GetFragmentDataPtr(TestedEntity); AITEST_NOT_NULL("Created entity has the int fragment", IntFragmentInstance); AITEST_EQUAL("Resulting Int fragment value", IntFragmentInstance->Value, TestIntValue); } { FTestFragment_Float* FloatFragmentInstance = EntityManager->GetFragmentDataPtr(TestedEntity); AITEST_NOT_NULL("Created entity has the int fragment", FloatFragmentInstance); AITEST_EQUAL("Resulting Int fragment value", FloatFragmentInstance->Value, TestFloatValue); } { FTestSharedFragment_Float* SharedFragmentInstance = EntityManager->GetSharedFragmentDataPtr(TestedEntity); AITEST_NOT_NULL("Created entity has the int fragment", SharedFragmentInstance); AITEST_EQUAL("Resulting Int fragment value", SharedFragmentInstance->Value, TestSharedFloatValue); } { FTestConstSharedFragment_Int* ConstSharedFragmentInstance = EntityManager->GetConstSharedFragmentDataPtr(TestedEntity); AITEST_NOT_NULL("Created entity has the int fragment", ConstSharedFragmentInstance); AITEST_EQUAL("Resulting Int fragment value", ConstSharedFragmentInstance->Value, TestSharedIntValue); } return true; } }; IMPLEMENT_AI_INSTANT_TEST(FAllElementsUsed, "System.Mass.EntityBuilder.AllElements"); struct FCopyEntity : FAllElementsUsed { virtual bool InstantTest() override { FEntityBuilder Builder(EntityManager.ToSharedRef()); Builder.CopyDataFromEntity(OriginalEntity); const FMassEntityHandle NewEntityHandle = Builder.Commit(); AITEST_TRUE("Source and target entities are in the same archetype" , EntityManager->GetArchetypeForEntity(OriginalEntity) == EntityManager->GetArchetypeForEntity(NewEntityHandle)); return TestEntity(NewEntityHandle); } }; IMPLEMENT_AI_INSTANT_TEST(FCopyEntity, "System.Mass.EntityBuilder.CopyEntity"); struct FAppendFromEntity : FAllElementsUsed { virtual bool InstantTest() override { FEntityBuilder Builder(EntityManager.ToSharedRef()); // adding something the appending won't add, just to remove it later and test the result Builder.Add(); Builder.AppendDataFromEntity(OriginalEntity); const FMassEntityHandle NewEntityHandle = Builder.Commit(); EntityManager->RemoveTagFromEntity(NewEntityHandle, FTestTag_A::StaticStruct()); AITEST_TRUE("Source and target entities are in the same archetype" , EntityManager->GetArchetypeForEntity(OriginalEntity) == EntityManager->GetArchetypeForEntity(NewEntityHandle)); return TestEntity(NewEntityHandle); } }; IMPLEMENT_AI_INSTANT_TEST(FAppendFromEntity, "System.Mass.EntityBuilder.Append"); struct FUsingInstancedStructs : FAllElementsUsed { virtual bool SetUp() override { // deliberately skipping FAllElementsUsed, we're doing a different setup here if (FEntityTestBase::SetUp()) { // quick builder just to create an entity with known properties FEntityBuilder Builder(EntityManager.ToSharedRef()); FInstancedStruct ElementInstance; ElementInstance.InitializeAs(TestIntValue); Builder.Add(ElementInstance); ElementInstance.InitializeAs(TestFloatValue); Builder.Add(MoveTemp(ElementInstance)); ElementInstance.InitializeAs(TestSharedFloatValue); Builder.Add(MoveTemp(ElementInstance)); ElementInstance.InitializeAs(TestSharedIntValue); Builder.Add(ElementInstance); // tags cannot be added as instanced structs Builder.Add(); OriginalEntity = Builder.Commit(); return true; } return false; } }; IMPLEMENT_AI_INSTANT_TEST(FUsingInstancedStructs, "System.Mass.EntityBuilder.WithInstancedStructs"); } // UE::Mass::Test::EntityBuilder UE_ENABLE_OPTIMIZATION_SHIP #undef LOCTEXT_NAMESPACE