// Copyright Epic Games, Inc. All Rights Reserved. #include "AITestsCommon.h" #include "MassEntityManager.h" #include "MassProcessingTypes.h" #include "MassEntityTestTypes.h" #include "MassEntityUtils.h" #include "MassExecutor.h" #include "MassExecutionContext.h" #include "MassProcessingContext.h" #include "MassQueryExecutor.h" #include "HAL/PlatformTime.h" #define LOCTEXT_NAMESPACE "MassTest" DEFINE_LOG_CATEGORY_STATIC(LogMassPerfTest, Log, All); UE_DISABLE_OPTIMIZATION_SHIP void UMassTestProcessorAutoExecuteQueryComparison::ConfigureQueries(const TSharedRef& EntityManager) { EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); EntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); } void UMassTestProcessorAutoExecuteQueryComparison::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) { EntityQuery.ForEachEntityChunk(Context, [](FMassExecutionContext& Context) { const TConstArrayView TestFloat = Context.GetFragmentView(); const TArrayView TestInt = Context.GetMutableFragmentView(); const TConstArrayView TestBool = Context.GetFragmentView(); const TConstArrayView TestLarge = Context.GetFragmentView(); const TConstArrayView TestArray = Context.GetFragmentView(); for (uint32 EntityIndex : Context.CreateEntityIterator()) { int32& TestIntVal = TestInt[EntityIndex].Value; if (TestFloat[EntityIndex].Value > 0.0f) { ++TestIntVal; } else { --TestIntVal; } if (TestBool[EntityIndex].bValue) { ++TestIntVal; } else { --TestIntVal; } if (TestLarge[EntityIndex].Value[0] > 0) { ++TestIntVal; } else { --TestIntVal; } if (TestArray[EntityIndex].Value.Num() > 0) { ++TestIntVal; } else { --TestIntVal; } } }); } void UMassTestProcessorAutoExecuteQueryComparison_Parallel::ConfigureQueries(const TSharedRef& EntityManager) { EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); EntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); } void UMassTestProcessorAutoExecuteQueryComparison_Parallel::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) { EntityQuery.ParallelForEachEntityChunk(Context, [](FMassExecutionContext& Context) { const TConstArrayView TestFloat = Context.GetFragmentView(); const TArrayView TestInt = Context.GetMutableFragmentView(); const TConstArrayView TestBool = Context.GetFragmentView(); const TConstArrayView TestLarge = Context.GetFragmentView(); const TConstArrayView TestArray = Context.GetFragmentView(); for (uint32 EntityIndex : Context.CreateEntityIterator()) { int32& TestIntVal = TestInt[EntityIndex].Value; if (TestFloat[EntityIndex].Value > 0.0f) { ++TestIntVal; } else { --TestIntVal; } if (TestBool[EntityIndex].bValue) { ++TestIntVal; } else { --TestIntVal; } if (TestLarge[EntityIndex].Value[0] > 0) { ++TestIntVal; } else { --TestIntVal; } if (TestArray[EntityIndex].Value.Num() > 0) { ++TestIntVal; } else { --TestIntVal; } } }); } namespace FMassQueryExecutorTest { struct FTestQueryExecutor_LoadTestSetup : public UE::Mass::FQueryExecutor { UE::Mass::FQueryDefinition< UE::Mass::FMutableFragmentAccess, UE::Mass::FMutableFragmentAccess, UE::Mass::FMutableFragmentAccess, UE::Mass::FMutableFragmentAccess, UE::Mass::FMutableFragmentAccess, UE::Mass::FMutableSubsystemAccess > Accessors{ *this }; virtual void Execute(FMassExecutionContext& Context) { ParallelForEachEntityChunk(Context, Accessors, [](FMassExecutionContext& Context, auto& Data) { for (uint32 EntityIndex : Context.CreateEntityIterator()) { Data.template Get()[EntityIndex].Value = 0; Data.template Get()[EntityIndex].Value = FMath::FRandRange(-1.0f, 1.0f); Data.template Get()[EntityIndex].bValue = FMath::RandBool(); Data.template Get()[EntityIndex].Value[0] = FMath::RandBool() ? 1 : 0; Data.template Get()[EntityIndex].Value.SetNum(FMath::RandBool() ? 1 : 0); Data.template Get()->Write(0); } }); } }; struct FTestQueryExecutor_LoadTestReset : public UE::Mass::FQueryExecutor { UE::Mass::FQueryDefinition< UE::Mass::FMutableFragmentAccess > Accessors{ *this }; virtual void Execute(FMassExecutionContext& Context) { ParallelForEachEntityChunk(Context, Accessors, [](FMassExecutionContext& Context, auto& Data) { for (uint32 EntityIndex : Context.CreateEntityIterator()) { Data.template Get()[EntityIndex].Value = 0; } }); } }; struct FTestQueryExecutor_LoadTestValidate : public UE::Mass::FQueryExecutor { UE::Mass::FQueryDefinition< UE::Mass::FConstFragmentAccess > Accessors{ *this }; std::atomic Sum = 0; virtual void Execute(FMassExecutionContext& Context) { ParallelForEachEntityChunk(Context, Accessors, [&Sum = Sum](FMassExecutionContext& Context, auto& Data) { int64 ChunkSum = 0; for (uint32 EntityIndex : Context.CreateEntityIterator()) { ChunkSum += Data.template Get()[EntityIndex].Value; } Sum += ChunkSum; }); } }; struct FTestQueryExecutor_LoadTest : public UE::Mass::FQueryExecutor { UE::Mass::FQueryDefinition< UE::Mass::FConstFragmentAccess, UE::Mass::FMutableFragmentAccess, UE::Mass::FConstFragmentAccess, UE::Mass::FConstFragmentAccess, UE::Mass::FConstFragmentAccess > Accessors{ *this }; virtual void Execute(FMassExecutionContext& Context) { ForEachEntityChunk(Context, Accessors, [](FMassExecutionContext& Context, auto& Data) { for (uint32 EntityIndex : Context.CreateEntityIterator()) { int32& TestInt = Data.template Get()[EntityIndex].Value; if (Data.template Get()[EntityIndex].Value > 0.0f) { ++TestInt; } else { --TestInt; } if (Data.template Get()[EntityIndex].bValue) { ++TestInt; } else { --TestInt; } if (Data.template Get()[EntityIndex].Value[0] > 0) { ++TestInt; } else { --TestInt; } if (Data.template Get()[EntityIndex].Value.Num() > 0) { ++TestInt; } else { --TestInt; } } }); } }; struct FTestQueryExecutor_LoadTest_Parallel : public UE::Mass::FQueryExecutor { UE::Mass::FQueryDefinition< UE::Mass::FConstFragmentAccess, UE::Mass::FMutableFragmentAccess, UE::Mass::FConstFragmentAccess, UE::Mass::FConstFragmentAccess, UE::Mass::FConstFragmentAccess > Accessors{ *this }; virtual void Execute(FMassExecutionContext& Context) { ParallelForEachEntityChunk(Context, Accessors, [](FMassExecutionContext& Context, auto& Data) { for (uint32 EntityIndex : Context.CreateEntityIterator()) { int32& TestInt = Data.template Get()[EntityIndex].Value; if (Data.template Get()[EntityIndex].Value > 0.0f) { ++TestInt; } else { --TestInt; } if (Data.template Get()[EntityIndex].bValue) { ++TestInt; } else { --TestInt; } if (Data.template Get()[EntityIndex].Value[0] > 0) { ++TestInt; } else { --TestInt; } if (Data.template Get()[EntityIndex].Value.Num() > 0) { ++TestInt; } else { --TestInt; } } }); } }; struct FTestQueryExecutor_LoadTest_ByEntity : public UE::Mass::FQueryExecutor { UE::Mass::FQueryDefinition< UE::Mass::FConstFragmentAccess, UE::Mass::FMutableFragmentAccess, UE::Mass::FConstFragmentAccess, UE::Mass::FConstFragmentAccess, UE::Mass::FConstFragmentAccess > Accessors{ *this }; virtual void Execute(FMassExecutionContext& Context) { ForEachEntity(Context, Accessors, [](FMassExecutionContext& Context, auto& Data, uint32 EntityIndex) { int32& TestInt = Data.template Get()[EntityIndex].Value; if (Data.template Get()[EntityIndex].Value > 0.0f) { ++TestInt; } else { --TestInt; } if (Data.template Get()[EntityIndex].bValue) { ++TestInt; } else { --TestInt; } if (Data.template Get()[EntityIndex].Value[0] > 0) { ++TestInt; } else { --TestInt; } if (Data.template Get()[EntityIndex].Value.Num() > 0) { ++TestInt; } else { --TestInt; } }); } }; struct FTestQueryExecutor_LoadTest_ByEntity_Parallel : public UE::Mass::FQueryExecutor { UE::Mass::FQueryDefinition< UE::Mass::FConstFragmentAccess, UE::Mass::FMutableFragmentAccess, UE::Mass::FConstFragmentAccess, UE::Mass::FConstFragmentAccess, UE::Mass::FConstFragmentAccess > Accessors{ *this }; virtual void Execute(FMassExecutionContext& Context) { ParallelForEachEntity(Context, Accessors, [](FMassExecutionContext& Context, auto& Data, uint32 EntityIndex) { int32& TestInt = Data.template Get()[EntityIndex].Value; if (Data.template Get()[EntityIndex].Value > 0.0f) { ++TestInt; } else { --TestInt; } if (Data.template Get()[EntityIndex].bValue) { ++TestInt; } else { --TestInt; } if (Data.template Get()[EntityIndex].Value[0] > 0) { ++TestInt; } else { --TestInt; } if (Data.template Get()[EntityIndex].Value.Num() > 0) { ++TestInt; } else { --TestInt; } }); } }; struct FTestQueryExecutor_AnyTag : public UE::Mass::FQueryExecutor { int32 EntityCount = 0; UE::Mass::FQueryDefinition< UE::Mass::FConstFragmentAccess > Accessors{ *this }; virtual void Execute(FMassExecutionContext& Context) { ForEachEntity(Context, Accessors, [this](FMassExecutionContext& Context, auto& Data, uint32 EntityIndex) { ++EntityCount; }); } }; struct FTestQueryExecutor_NeedTagA : public UE::Mass::FQueryExecutor { UE::Mass::FQueryDefinition< UE::Mass::FConstFragmentAccess, UE::Mass::FMassTagRequired > Accessors{ *this }; int32 EntityCount = 0; virtual void Execute(FMassExecutionContext& Context) { ForEachEntity(Context, Accessors, [this](FMassExecutionContext& Context, auto& Data, uint32 EntityIndex) { ++EntityCount; }); } }; struct FTestQueryExecutor_BlockTagB : public UE::Mass::FQueryExecutor { UE::Mass::FQueryDefinition< UE::Mass::FConstFragmentAccess, UE::Mass::FMassTagBlocked > Accessors{ *this }; int32 EntityCount = 0; virtual void Execute(FMassExecutionContext& Context) { ForEachEntity(Context, Accessors, [this](FMassExecutionContext& Context, auto& Data, uint32 EntityIndex) { ++EntityCount; }); } }; struct FQueryExecutor_IteratorConsistency : FEntityTestBase { virtual bool InstantTest() override { CA_ASSUME(EntityManager); const int32 EntityCountNoTag = 7; const int32 EntityCountA = 9; const int32 EntityCountB = 13; const int32 EntityCountAB = 17; TArray Entities; EntityManager->BatchCreateEntities(FloatsArchetype, {}, EntityCountNoTag, Entities); Entities.Reset(); EntityManager->BatchCreateEntities(FloatsArchetype, {}, EntityCountA, Entities); for (FMassEntityHandle& Entity : Entities) { EntityManager->AddTagToEntity(Entity, FTestTag_A::StaticStruct()); } Entities.Reset(); EntityManager->BatchCreateEntities(FloatsArchetype, {}, EntityCountB, Entities); for (FMassEntityHandle& Entity : Entities) { EntityManager->AddTagToEntity(Entity, FTestTag_B::StaticStruct()); } Entities.Reset(); EntityManager->BatchCreateEntities(FloatsArchetype, {}, EntityCountAB, Entities); for (FMassEntityHandle& Entity : Entities) { EntityManager->AddTagToEntity(Entity, FTestTag_A::StaticStruct()); EntityManager->AddTagToEntity(Entity, FTestTag_B::StaticStruct()); } TObjectPtr Processor_AnyTag = NewObject(); CA_ASSUME(Processor_AnyTag); TSharedPtr TestQuery_AnyTag = UE::Mass::FQueryExecutor::CreateQuery(Processor_AnyTag->EntityQuery, Processor_AnyTag); Processor_AnyTag->SetAutoExecuteQuery(TestQuery_AnyTag); Processor_AnyTag->CallInitialize(GetTransientPackage(), EntityManager.ToSharedRef()); TObjectPtr Processor_TagA = NewObject(); CA_ASSUME(Processor_TagA); TSharedPtr TestQuery_TagA = UE::Mass::FQueryExecutor::CreateQuery(Processor_TagA->EntityQuery, Processor_TagA); Processor_TagA->SetAutoExecuteQuery(TestQuery_TagA); Processor_TagA->CallInitialize(GetTransientPackage(), EntityManager.ToSharedRef()); TObjectPtr Processor_TagB = NewObject(); CA_ASSUME(Processor_TagB); TSharedPtr TestQuery_TagB = UE::Mass::FQueryExecutor::CreateQuery(Processor_TagB->EntityQuery, Processor_TagB); Processor_TagB->SetAutoExecuteQuery(TestQuery_TagB); Processor_TagB->CallInitialize(GetTransientPackage(), EntityManager.ToSharedRef()); TArray> Processors; Processors.Add(Processor_AnyTag); Processors.Add(Processor_TagA); Processors.Add(Processor_TagB); FMassProcessingContext ProcessingContext(EntityManager, /*DeltaSeconds=*/0.f); UE::Mass::Executor::RunProcessorsView(Processors, ProcessingContext); const int32 AnyCount = TestQuery_AnyTag->EntityCount; const int32 ExpectedAnyCount = EntityCountNoTag + EntityCountA + EntityCountB + EntityCountAB; const int32 ACount = TestQuery_TagA->EntityCount; const int32 ExpectedACount = EntityCountA + EntityCountAB; const int32 BCount = TestQuery_TagB->EntityCount; const int32 ExpectedBCount = EntityCountNoTag + EntityCountA; AITEST_EQUAL(TEXT("Any Tag Entities Processed"), AnyCount, ExpectedAnyCount); AITEST_EQUAL(TEXT("Require TagA Entities Processed"), ACount, ExpectedACount); AITEST_EQUAL(TEXT("Blocked TagB Entities Processed"), BCount, ExpectedBCount); return true; } }; IMPLEMENT_AI_INSTANT_TEST(FQueryExecutor_IteratorConsistency, "System.Mass.Processor.AutoExecuteQuery.IteratorConsistency"); struct FTestQueryExecutor_LoadTest_Float : public UE::Mass::FQueryExecutor { UE::Mass::FQueryDefinition< UE::Mass::FConstFragmentAccess, UE::Mass::FMutableFragmentAccess > Accessors{ *this }; virtual void Execute(FMassExecutionContext& Context) { ParallelForEachEntityChunk(Context, Accessors, [](FMassExecutionContext& Context, auto& Data) { for (uint32 EntityIndex : Context.CreateEntityIterator()) { if (Data.template Get()[EntityIndex].Value > 0.0f) { ++Data.template Get()[EntityIndex].Value; } else { --Data.template Get()[EntityIndex].Value; } } }); } }; struct FTestQueryExecutor_LoadTest_Bool : public UE::Mass::FQueryExecutor { UE::Mass::FQueryDefinition< UE::Mass::FMutableFragmentAccess, UE::Mass::FConstFragmentAccess > Accessors{ *this }; virtual void Execute(FMassExecutionContext& Context) { ParallelForEachEntityChunk(Context, Accessors, [](FMassExecutionContext& Context, auto& Data) { for (uint32 EntityIndex : Context.CreateEntityIterator()) { if (Data.template Get()[EntityIndex].bValue) { ++Data.template Get()[EntityIndex].Value; } else { --Data.template Get()[EntityIndex].Value; } } }); } }; struct FTestQueryExecutor_LoadTest_Large : public UE::Mass::FQueryExecutor { UE::Mass::FQueryDefinition< UE::Mass::FMutableFragmentAccess, UE::Mass::FConstFragmentAccess > Accessors{ *this }; virtual void Execute(FMassExecutionContext& Context) { ParallelForEachEntityChunk(Context, Accessors, [](FMassExecutionContext& Context, auto& Data) { for (uint32 EntityIndex : Context.CreateEntityIterator()) { int32& TestInt = Data.template Get()[EntityIndex].Value; if (Data.template Get()[EntityIndex].Value[0] > 0) { ++Data.template Get()[EntityIndex].Value; } else { --Data.template Get()[EntityIndex].Value; } } }); } }; struct FTestQueryExecutor_LoadTest_Array : public UE::Mass::FQueryExecutor { UE::Mass::FQueryDefinition< UE::Mass::FMutableFragmentAccess, UE::Mass::FConstFragmentAccess > Accessors{ *this }; virtual void Execute(FMassExecutionContext& Context) { ParallelForEachEntityChunk(Context, Accessors, [](FMassExecutionContext& Context, auto& Data) { for (uint32 EntityIndex : Context.CreateEntityIterator()) { if (Data.template Get()[EntityIndex].Value.Num() > 0) { ++Data.template Get()[EntityIndex].Value; } else { --Data.template Get()[EntityIndex].Value; } } }); } }; struct FQueryExecutor_LoadTest : FEntityTestBase { virtual bool InstantTest() override { CA_ASSUME(EntityManager); constexpr uint32 NumTags = 5; const UScriptStruct* Tags[NumTags] = { FTestFragment_Tag::StaticStruct(), FTestTag_A::StaticStruct(), FTestTag_B::StaticStruct(), FTestTag_C::StaticStruct(), FTestTag_D::StaticStruct(), }; TArray Fragments; Fragments.Add(FTestFragment_Float::StaticStruct()); Fragments.Add(FTestFragment_Int::StaticStruct()); Fragments.Add(FTestFragment_Bool::StaticStruct()); Fragments.Add(FTestFragment_Large::StaticStruct()); Fragments.Add(FTestFragment_Array::StaticStruct()); FMassArchetypeHandle LoadTestArchetype = EntityManager->CreateArchetype(Fragments); TArray Entities; auto CreateEntities = [&](uint32 EntityCount) { EntityManager->BatchDestroyEntities(Entities); Entities.Reset(EntityCount); EntityManager->BatchCreateEntities(LoadTestArchetype, {}, EntityCount, Entities); int32 TagMask = 0; for (FMassEntityHandle& Entity : Entities) { // maximize entity fragmentation to test worst-case scenario for (int32 BitIndex = 0; BitIndex < NumTags; ++BitIndex) { if ((TagMask & (1 << BitIndex)) != 0) { EntityManager->AddTagToEntity(Entity, Tags[BitIndex]); } } if (++TagMask >= (1 << NumTags)) { TagMask = 0; } } }; TObjectPtr Processor_LoadTest_Parallel = NewObject(); CA_ASSUME(Processor_LoadTest_Parallel); TSharedPtr TestQuery_LoadTest_Parallel = UE::Mass::FQueryExecutor::CreateQuery(Processor_LoadTest_Parallel->EntityQuery, Processor_LoadTest_Parallel); Processor_LoadTest_Parallel->SetAutoExecuteQuery(TestQuery_LoadTest_Parallel); Processor_LoadTest_Parallel->CallInitialize(GetTransientPackage(), EntityManager.ToSharedRef()); TObjectPtr Processor_LoadTest_ByEntity_Parallel = NewObject(); CA_ASSUME(Processor_LoadTest_ByEntity_Parallel); TSharedPtr TestQuery_LoadTest_ByEntity_Parallel = UE::Mass::FQueryExecutor::CreateQuery(Processor_LoadTest_ByEntity_Parallel->EntityQuery, Processor_LoadTest_ByEntity_Parallel); Processor_LoadTest_ByEntity_Parallel->SetAutoExecuteQuery(TestQuery_LoadTest_ByEntity_Parallel); Processor_LoadTest_ByEntity_Parallel->CallInitialize(GetTransientPackage(), EntityManager.ToSharedRef()); TObjectPtr Processor_LoadTest = NewObject(); CA_ASSUME(Processor_LoadTest); TSharedPtr TestQuery_LoadTest = UE::Mass::FQueryExecutor::CreateQuery(Processor_LoadTest->EntityQuery, Processor_LoadTest); Processor_LoadTest->SetAutoExecuteQuery(TestQuery_LoadTest); Processor_LoadTest->CallInitialize(GetTransientPackage(), EntityManager.ToSharedRef()); TObjectPtr Processor_LoadTest_ByEntity = NewObject(); CA_ASSUME(Processor_LoadTest_ByEntity); TSharedPtr TestQuery_LoadTest_ByEntity = UE::Mass::FQueryExecutor::CreateQuery(Processor_LoadTest_ByEntity->EntityQuery, Processor_LoadTest_ByEntity); Processor_LoadTest_ByEntity->SetAutoExecuteQuery(TestQuery_LoadTest_ByEntity); Processor_LoadTest_ByEntity->CallInitialize(GetTransientPackage(), EntityManager.ToSharedRef()); TObjectPtr Processor_Setup = NewObject(); CA_ASSUME(Processor_Setup); TSharedPtr TestQuery_Setup = UE::Mass::FQueryExecutor::CreateQuery(Processor_Setup->EntityQuery, Processor_Setup); Processor_Setup->SetAutoExecuteQuery(TestQuery_Setup); Processor_Setup->CallInitialize(GetTransientPackage(), EntityManager.ToSharedRef()); TObjectPtr Processor_Reset = NewObject(); CA_ASSUME(Processor_Reset); TSharedPtr TestQuery_Reset = UE::Mass::FQueryExecutor::CreateQuery(Processor_Reset->EntityQuery, Processor_Reset); Processor_Reset->SetAutoExecuteQuery(TestQuery_Reset); Processor_Reset->CallInitialize(GetTransientPackage(), EntityManager.ToSharedRef()); TObjectPtr Processor_Validate = NewObject(); CA_ASSUME(Processor_Validate); TSharedPtr TestQuery_Validate = UE::Mass::FQueryExecutor::CreateQuery(Processor_Validate->EntityQuery, Processor_Validate); Processor_Validate->SetAutoExecuteQuery(TestQuery_Validate); Processor_Validate->CallInitialize(GetTransientPackage(), EntityManager.ToSharedRef()); TObjectPtr Processor_Float = NewObject(); CA_ASSUME(Processor_Float); TSharedPtr TestQuery_Float = UE::Mass::FQueryExecutor::CreateQuery(Processor_Float->EntityQuery, Processor_Float); Processor_Float->SetAutoExecuteQuery(TestQuery_Float); Processor_Float->CallInitialize(GetTransientPackage(), EntityManager.ToSharedRef()); TObjectPtr Processor_Bool = NewObject(); CA_ASSUME(Processor_Bool); TSharedPtr TestQuery_Bool = UE::Mass::FQueryExecutor::CreateQuery(Processor_Bool->EntityQuery, Processor_Bool); Processor_Bool->SetAutoExecuteQuery(TestQuery_Bool); Processor_Bool->CallInitialize(GetTransientPackage(), EntityManager.ToSharedRef()); TObjectPtr Processor_Large = NewObject(); CA_ASSUME(Processor_Large); TSharedPtr TestQuery_Large = UE::Mass::FQueryExecutor::CreateQuery(Processor_Large->EntityQuery, Processor_Large); Processor_Large->SetAutoExecuteQuery(TestQuery_Large); Processor_Large->CallInitialize(GetTransientPackage(), EntityManager.ToSharedRef()); TObjectPtr Processor_Array = NewObject(); CA_ASSUME(Processor_Array); TSharedPtr TestQuery_Array = UE::Mass::FQueryExecutor::CreateQuery(Processor_Array->EntityQuery, Processor_Array); Processor_Array->SetAutoExecuteQuery(TestQuery_Array); Processor_Array->CallInitialize(GetTransientPackage(), EntityManager.ToSharedRef()); TArray> IndividualProcessors_LoadTest; IndividualProcessors_LoadTest.Add(Processor_Float); IndividualProcessors_LoadTest.Add(Processor_Bool); IndividualProcessors_LoadTest.Add(Processor_Large); IndividualProcessors_LoadTest.Add(Processor_Array); TArray> Processors; FMassProcessingContext ProcessingContext(EntityManager, /*DeltaSeconds=*/0.f); UMassTestProcessorAutoExecuteQueryComparison* DefaultProcessor = NewTestProcessor(EntityManager); UMassTestProcessorAutoExecuteQueryComparison_Parallel* DefaultProcessor_Parallel = NewTestProcessor(EntityManager); Processors.Reset(); Processors.Add(Processor_Setup); UE::Mass::Executor::RunProcessorsView(Processors, ProcessingContext); struct TestReturn { const TCHAR* Name; uint64 Sum; double Duration; double NormalizedTime; }; auto RunTest = [&](TObjectPtr Proc, TArray>* ProcArray, uint32 EntityCount, uint32 RunCount, const TCHAR* Name) -> TestReturn { Processors.Reset(); if (ProcArray != nullptr) { Processors.Append(*ProcArray); } if (Proc) { Processors.Add(Proc); } // warm the cache with a single untimed iteration UE::Mass::Executor::RunProcessorsView(Processors, ProcessingContext); const double StartTime = FPlatformTime::Seconds(); for (uint32 i = 0; i < RunCount; i++) { UE::Mass::Executor::RunProcessorsView(Processors, ProcessingContext); } const double EndTime = FPlatformTime::Seconds(); const double Duration = EndTime - StartTime; const double NormalizedTime = (Duration) / ((((double)EntityCount)/1000.0) * (((double)RunCount)/1000.0)); Processors.Reset(); Processors.Add(Processor_Validate); UE::Mass::Executor::RunProcessorsView(Processors, ProcessingContext); uint64 ProcSum = TestQuery_Validate->Sum; TestQuery_Validate->Sum = 0; Processors.Reset(); Processors.Add(Processor_Reset); UE::Mass::Executor::RunProcessorsView(Processors, ProcessingContext); return { Name, ProcSum, Duration, NormalizedTime }; }; auto LogResult = [&](TestReturn& Test, const TestReturn& Baseline) { const double PercentDiff = ((Test.NormalizedTime - Baseline.NormalizedTime) / Baseline.NormalizedTime) * 100.0; if (PercentDiff > 0.0) { UE_LOG(LogMassPerfTest, Log, TEXT("%s: %.4fns/Entity (-%.4f%%)."), Test.Name, Test.NormalizedTime, PercentDiff); } else if (PercentDiff <= 0.0) { UE_LOG(LogMassPerfTest, Log, TEXT("%s: %.4fns/Entity (+%.4f%%)."), Test.Name, Test.NormalizedTime, FMath::Abs(PercentDiff)); } }; auto LogTime = [&](TestReturn& Test) { UE_LOG(LogMassPerfTest, Log, TEXT("%s,%.8f"), Test.Name, Test.NormalizedTime); }; auto SortPredicate = [](const TestReturn& A, const TestReturn& B) { return A.NormalizedTime < B.NormalizedTime; }; auto RunBattery = [&](uint32 EntityCount, uint32 RunCount) { UE_LOG(LogMassPerfTest, Log, TEXT("\n%d entities:"), EntityCount); CreateEntities(EntityCount); TArray Results; int32 DefaultIndex = Results.Emplace(RunTest(DefaultProcessor, nullptr, EntityCount, RunCount, TEXT("Default"))); TestReturn Default = Results[DefaultIndex]; Results.Emplace(RunTest(nullptr, &IndividualProcessors_LoadTest, EntityCount, RunCount, TEXT("Split_Processing_Parallel"))); Results.Emplace(RunTest(Processor_LoadTest, nullptr, EntityCount, RunCount, TEXT("QueryExecutor"))); Results.Emplace(RunTest(Processor_LoadTest_ByEntity, nullptr, EntityCount, RunCount, TEXT("QueryExecutor_ByEntity"))); Results.Emplace(RunTest(Processor_LoadTest_Parallel, nullptr, EntityCount, RunCount, TEXT("QueryExecutor_Parallel"))); Results.Emplace(RunTest(Processor_LoadTest_ByEntity_Parallel, nullptr, EntityCount, RunCount, TEXT("QueryExecutor_ByEntity_Parallel"))); Results.Emplace(RunTest(DefaultProcessor_Parallel, nullptr, EntityCount, RunCount, TEXT("Default_Parallel"))); for (int32 i = 0; i < Results.Num(); i++) { FString TestMessage = FString::Format(TEXT("{0} Sum should match Default"), { Results[i].Name}); AITEST_EQUAL(TestMessage, Results[i].Sum, Default.Sum); } for (TestReturn& Result : Results) { LogTime(Result); } return true; }; RunBattery(1, 10000); RunBattery(10, 1000); RunBattery(100, 100); RunBattery(1000, 100); return true; } }; // This test can't fail and only exists to compare relative performance of various processor implementations and environments //IMPLEMENT_AI_INSTANT_TEST(FQueryExecutor_LoadTest, "System.Mass.Processor.AutoExecuteQuery.LoadTest"); } // FMassQueryExecutorTest UE_ENABLE_OPTIMIZATION_SHIP #undef LOCTEXT_NAMESPACE