// Copyright Epic Games, Inc. All Rights Reserved. #include "MassProcessingPhaseManager.h" #include "MassProcessingTypes.h" #include "MassDebugger.h" #include "MassProcessor.h" #include "MassExecutor.h" #include "MassEntityManager.h" #include "MassEntitySubsystem.h" #include "VisualLogger/VisualLogger.h" #include "Engine/World.h" #include "MassCommandBuffer.h" #include "MassEntityTrace.h" #include "MassProcessingContext.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(MassProcessingPhaseManager) #define LOCTEXT_NAMESPACE "Mass" DECLARE_CYCLE_STAT(TEXT("Mass Phase Tick"), STAT_Mass_PhaseTick, STATGROUP_Mass); DECLARE_CYCLE_STAT(TEXT("Mass Phase Configure Pipeline Creation"), STAT_Mass_PhaseConfigurePipelineCreation, STATGROUP_Mass); namespace UE::Mass::Tweakables { bool bFullyParallel = MASS_DO_PARALLEL; bool bMakePrePhysicsTickFunctionHighPriority = true; FAutoConsoleVariableRef CVars[] = { {TEXT("mass.FullyParallel"), bFullyParallel, TEXT("Enables mass processing distribution to all available thread (via the task graph)")}, {TEXT("mass.MakePrePhysicsTickFunctionHighPriority"), bMakePrePhysicsTickFunctionHighPriority, TEXT("Whether to make the PrePhysics tick function high priority - can minimise GameThread waits by starting parallel work as soon as possible")}, }; } namespace UE::Mass::Private { ETickingGroup PhaseToTickingGroup[int(EMassProcessingPhase::MAX)] { ETickingGroup::TG_PrePhysics, // EMassProcessingPhase::PrePhysics ETickingGroup::TG_StartPhysics, // EMassProcessingPhase::StartPhysics ETickingGroup::TG_DuringPhysics, // EMassProcessingPhase::DuringPhysics ETickingGroup::TG_EndPhysics, // EMassProcessingPhase::EndPhysics ETickingGroup::TG_PostPhysics, // EMassProcessingPhase::PostPhysics ETickingGroup::TG_LastDemotable, // EMassProcessingPhase::FrameEnd }; } // UE::Mass::Private //----------------------------------------------------------------------// // FMassProcessingPhase //----------------------------------------------------------------------// FMassProcessingPhase::FMassProcessingPhase() { bCanEverTick = true; bStartWithTickEnabled = false; SupportedTickTypes = (1 << LEVELTICK_All) | (1 << LEVELTICK_TimeOnly); } void FMassProcessingPhase::ExecuteTick(float DeltaTime, ELevelTick TickType, ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent) { if (ShouldTick(TickType) == false) { return; } SCOPE_CYCLE_COUNTER(STAT_Mass_PhaseTick); SCOPE_CYCLE_COUNTER(STAT_Mass_Total); checkf(PhaseManager, TEXT("Manager is null which is not a supported case. Either this FMassProcessingPhase has not been initialized properly or it's been left dangling after the FMassProcessingPhase owner got destroyed.")); TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(*FString::Printf(TEXT("FMassProcessingPhase::ExecuteTick %s"), *UEnum::GetValueAsString(Phase))); PhaseManager->OnPhaseStart(*this); { LLM_SCOPE_BYNAME(TEXT("Mass/PhaseStartDelegate")); OnPhaseStart.Broadcast(DeltaTime); } check(PhaseProcessor); FMassEntityManager& EntityManager = PhaseManager->GetEntityManagerRef(); FMassProcessingContext Context(EntityManager, DeltaTime); bIsDuringMassProcessing = true; if (bRunInParallelMode && PhaseManager->IsPaused() == false) { bool bWorkRequested = false; if (PhaseProcessor->IsEmpty() == false) { const FGraphEventRef PipelineCompletionEvent = UE::Mass::Executor::TriggerParallelTasks(*PhaseProcessor, MoveTemp(Context), [this, DeltaTime]() { OnParallelExecutionDone(DeltaTime); } , CurrentThread); if (PipelineCompletionEvent.IsValid()) { MyCompletionGraphEvent->DontCompleteUntil(PipelineCompletionEvent); bWorkRequested = true; } } if (bWorkRequested == false) { OnParallelExecutionDone(DeltaTime); } } else { if (PhaseManager->IsPaused() == false) { UE::Mass::Executor::Run(*PhaseProcessor, Context); } { LLM_SCOPE_BYNAME(TEXT("Mass/PhaseEndDelegate")); OnPhaseEnd.Broadcast(DeltaTime); } PhaseManager->OnPhaseEnd(*this); bIsDuringMassProcessing = false; } } void FMassProcessingPhase::OnParallelExecutionDone(const float DeltaTime) { bIsDuringMassProcessing = false; { LLM_SCOPE_BYNAME(TEXT("Mass/PhaseEndDelegate")); OnPhaseEnd.Broadcast(DeltaTime); } check(PhaseManager); PhaseManager->OnPhaseEnd(*this); } FString FMassProcessingPhase::DiagnosticMessage() { return (PhaseManager ? PhaseManager->GetName() : TEXT("NULL-MassProcessingPhaseManager")) + TEXT("[ProcessingPhaseTick]"); } FName FMassProcessingPhase::DiagnosticContext(bool bDetailed) { return TEXT("MassProcessingPhase"); } void FMassProcessingPhase::Initialize(FMassProcessingPhaseManager& InPhaseManager, const EMassProcessingPhase InPhase, const ETickingGroup InTickGroup, UMassCompositeProcessor& InPhaseProcessor) { PhaseManager = &InPhaseManager; Phase = InPhase; TickGroup = InTickGroup; PhaseProcessor = &InPhaseProcessor; } //----------------------------------------------------------------------// // FPhaseProcessorConfigurator //----------------------------------------------------------------------// void FMassPhaseProcessorConfigurationHelper::Configure(TArrayView DynamicProcessors, TArray>& InOutRemovedDynamicProcessors , EProcessorExecutionFlags InWorldExecutionFlags, const TSharedRef& EntityManager , FMassProcessorDependencySolver::FResult& InOutOptionalResult) { FMassRuntimePipeline TmpPipeline(PhaseProcessor.GetChildProcessorsView(), InWorldExecutionFlags); { SCOPE_CYCLE_COUNTER(STAT_Mass_PhaseConfigurePipelineCreation); TmpPipeline.AppendProcessors(InOutOptionalResult.PrunedProcessors); if (TmpPipeline.Num()) { // some previously added dynamic processors were either in the active processor group, // or were among the pruned processors. At this point we have both groups in TmpPipeline now // so we need to check if any of these processors have been removed since las processing // graph recreation for (int32 Index = InOutRemovedDynamicProcessors.Num() - 1; Index >= 0; --Index) { if (const UMassProcessor* RemovedProcessor = InOutRemovedDynamicProcessors[Index].Get()) { if (TmpPipeline.RemoveProcessor(*RemovedProcessor) == false) { continue; } } InOutRemovedDynamicProcessors.RemoveAtSwap(Index, 1, EAllowShrinking::No); } } for (UMassProcessor* Processor : DynamicProcessors) { checkf(Processor != nullptr, TEXT("Dynamic processor provided to MASS is null.")); if (Processor->GetProcessingPhase() == Phase) { TmpPipeline.AppendUniqueProcessor(*Processor); } } UObject* Owner = EntityManager->GetOwner(); check(Owner); // @todo consider doing this only during initial config. TmpPipeline.AppendUniqueRuntimeProcessorCopies(PhaseConfig.ProcessorCDOs, *Owner, EntityManager); } TArray SortedProcessors; FMassProcessorDependencySolver Solver(TmpPipeline.GetMutableProcessors(), bIsGameRuntime); Solver.ResolveDependencies(SortedProcessors, EntityManager, &InOutOptionalResult); PhaseProcessor.UpdateProcessorsCollection(SortedProcessors, InWorldExecutionFlags); #if WITH_MASSENTITY_DEBUG for (const FMassProcessorOrderInfo& ProcessorOrderInfo : SortedProcessors) { TmpPipeline.RemoveProcessor(*ProcessorOrderInfo.Processor); } if (TmpPipeline.Num()) { UE_VLOG_UELOG(&PhaseProcessor, LogMass, Verbose, TEXT("Discarding processors due to not having anything to do (no relevant Archetypes):")); for (UMassProcessor* Processor : TmpPipeline.GetProcessors()) { UE_VLOG_UELOG(&PhaseProcessor, LogMass, Verbose, TEXT("\t%s"), *Processor->GetProcessorName()); } } #endif // WITH_MASSENTITY_DEBUG if (Solver.IsSolvingForSingleThread() == false) { PhaseProcessor.BuildFlatProcessingGraph(SortedProcessors); } if (bInitializeCreatedProcessors) { PhaseProcessor.InitializeInternal(ProcessorOuter, EntityManager); } } //----------------------------------------------------------------------// // FMassProcessingPhaseManager::FPhaseGraphBuildState //----------------------------------------------------------------------// void FMassProcessingPhaseManager::FPhaseGraphBuildState::Reset() { LastResult.Reset(); bInitialized = false; } //----------------------------------------------------------------------// // FMassProcessingPhaseManager //----------------------------------------------------------------------// FMassProcessingPhaseManager::FMassProcessingPhaseManager(EProcessorExecutionFlags InProcessorExecutionFlags) : ProcessorExecutionFlags(InProcessorExecutionFlags) { #if WITH_MASSENTITY_DEBUG OnDebugEntityManagerInitializedHandle = FMassDebugger::OnEntityManagerInitialized.AddRaw(this, &FMassProcessingPhaseManager::OnDebugEntityManagerInitialized); OnDebugEntityManagerDeinitializedHandle = FMassDebugger::OnEntityManagerInitialized.AddRaw(this, &FMassProcessingPhaseManager::OnDebugEntityManagerDeinitialized); #endif // WITH_MASSENTITY_DEBUG } void FMassProcessingPhaseManager::Initialize(UObject& InOwner, TConstArrayView InProcessingPhasesConfig, const FString& DependencyGraphFileName) { UWorld* World = InOwner.GetWorld(); Owner = &InOwner; ProcessingPhasesConfig = InProcessingPhasesConfig; ProcessorExecutionFlags = UE::Mass::Utils::DetermineProcessorExecutionFlags(World, ProcessorExecutionFlags); const uint8 SupportedTickTypes = UE::Mass::Utils::DetermineProcessorSupportedTickTypes(World); for (int PhaseAsInt = 0; PhaseAsInt < int(EMassProcessingPhase::MAX); ++PhaseAsInt) { const EMassProcessingPhase Phase = EMassProcessingPhase(PhaseAsInt); FMassProcessingPhase& ProcessingPhase = ProcessingPhases[PhaseAsInt]; UMassCompositeProcessor* PhaseProcessor = NewObject(&InOwner, UMassCompositeProcessor::StaticClass() , *FString::Printf(TEXT("ProcessingPhase_%s"), *UEnum::GetDisplayValueAsText(Phase).ToString())); check(PhaseProcessor); ProcessingPhase.Initialize(*this, Phase, UE::Mass::Private::PhaseToTickingGroup[PhaseAsInt], *PhaseProcessor); ProcessingPhase.SupportedTickTypes = SupportedTickTypes; REDIRECT_OBJECT_TO_VLOG(PhaseProcessor, &InOwner); PhaseProcessor->SetProcessingPhase(Phase); PhaseProcessor->SetGroupName(FName(FString::Printf(TEXT("%s Group"), *UEnum::GetDisplayValueAsText(Phase).ToString()))); #if WITH_MASSENTITY_DEBUG FStringOutputDevice Ar; PhaseProcessor->DebugOutputDescription(Ar); UE_VLOG(&InOwner, LogMass, Log, TEXT("Setting new group processor for phase %s:\n%s"), *UEnum::GetValueAsString(Phase), *Ar); #endif // WITH_MASSENTITY_DEBUG } bIsAllowedToTick = true; } void FMassProcessingPhaseManager::Deinitialize() { for (FMassProcessingPhase& Phase : ProcessingPhases) { Phase.PhaseProcessor = nullptr; } DynamicProcessors.Reset(); for (FPhaseGraphBuildState& GraphBuildState : ProcessingGraphBuildStates) { GraphBuildState.Reset(); } // manually deque all the queues, since there's no guarantee that this // FMassProcessingPhaseManager instance is getting destroyed right after this call FDynamicProcessorOperation DummyElement; for (TMpscQueue& Queue : PendingDynamicProcessors) { while (Queue.Dequeue(DummyElement)) { // empty on purpose } } } const FGraphEventRef& FMassProcessingPhaseManager::TriggerPhase(const EMassProcessingPhase Phase, const float DeltaTime , const FGraphEventRef& MyCompletionGraphEvent, ENamedThreads::Type CurrentThread) { check(Phase != EMassProcessingPhase::MAX); if (bIsAllowedToTick) { ProcessingPhases[(int)Phase].ExecuteTick(DeltaTime, LEVELTICK_All, CurrentThread, MyCompletionGraphEvent); } return MyCompletionGraphEvent; } void FMassProcessingPhaseManager::Start(UWorld& World) { UMassEntitySubsystem* EntitySubsystem = UWorld::GetSubsystem(&World); if (ensure(EntitySubsystem)) { Start(EntitySubsystem->GetMutableEntityManager().AsShared()); } else { UE_VLOG_UELOG(Owner.Get(), LogMass, Error, TEXT("Called %s while missing the EntitySubsystem"), ANSI_TO_TCHAR(__FUNCTION__)); } } void FMassProcessingPhaseManager::Start(const TSharedRef& InEntityManager) { EntityManager = InEntityManager; #if WITH_MASSENTITY_DEBUG FMassDebugger::RegisterProcessorDataProvider(TEXT("Phase-executed processors"), InEntityManager, [WeakThis = AsWeak()](TArray& OutProcessors) { if (TSharedPtr SharedThis = WeakThis.Pin()) { for (const FMassProcessingPhase& Phase : SharedThis->ProcessingPhases) { OutProcessors.Add(Phase.DebugGetPhaseProcessor()); OutProcessors.Append(Phase.DebugGetPhaseProcessor()->GetChildProcessorsView()); } } }); FMassDebugger::RegisterProcessorDataProvider(TEXT("Pruned processors"), InEntityManager, [WeakThis = AsWeak()](TArray& OutProcessors) { if (TSharedPtr SharedThis = WeakThis.Pin()) { TConstArrayView BuildStates = SharedThis->DebugGetProcessingGraphBuildStates(); for (const FPhaseGraphBuildState& State : BuildStates) { OutProcessors.Append(ObjectPtrDecay(State.LastResult.PrunedProcessors)); } } }); #endif // WITH_MASSENTITY_DEBUG OnNewArchetypeHandle = EntityManager->GetOnNewArchetypeEvent().AddRaw(this, &FMassProcessingPhaseManager::OnNewArchetype); if (UWorld* World = EntityManager->GetWorld()) { EnableTickFunctions(*World); } bIsAllowedToTick = true; } void FMassProcessingPhaseManager::AddReferencedObjects(FReferenceCollector& Collector) { for (FMassProcessingPhase& Phase : ProcessingPhases) { if (Phase.PhaseProcessor) { Collector.AddReferencedObject(Phase.PhaseProcessor); } } auto NullProcessorRemover = [](const TObjectPtr& Processor) { return !Processor; }; check(DynamicProcessors.RemoveAllSwap(NullProcessorRemover) == 0); Collector.AddStableReferenceArray(&DynamicProcessors); // we also need to store our pruned processors for (FPhaseGraphBuildState& GraphBuildState : ProcessingGraphBuildStates) { check(GraphBuildState.LastResult.PrunedProcessors.RemoveAllSwap(NullProcessorRemover) == 0); Collector.AddStableReferenceArray(&GraphBuildState.LastResult.PrunedProcessors); } } void FMassProcessingPhaseManager::EnableTickFunctions(const UWorld& World) { check(EntityManager); const bool bIsGameWorld = World.IsGameWorld(); for (FMassProcessingPhase& Phase : ProcessingPhases) { if (UE::Mass::Tweakables::bMakePrePhysicsTickFunctionHighPriority && (Phase.Phase == EMassProcessingPhase::PrePhysics)) { constexpr bool bHighPriority = true; Phase.SetPriorityIncludingPrerequisites(bHighPriority); } Phase.RegisterTickFunction(World.PersistentLevel); Phase.SetTickFunctionEnable(true); #if WITH_MASSENTITY_DEBUG if (Phase.PhaseProcessor && bIsGameWorld) { // not logging this in the editor mode since it messes up the game-recorded vislog display (with its progressively larger timestamp) FStringOutputDevice Ar; Phase.PhaseProcessor->DebugOutputDescription(Ar); UE_VLOG_UELOG(Owner.Get(), LogMass, Log, TEXT("Enabling phase %s tick:\n%s") , *UEnum::GetValueAsString(Phase.Phase), *Ar); } #endif // WITH_MASSENTITY_DEBUG } if (bIsGameWorld) { // not logging this in the editor mode since it messes up the game-recorded vislog display (with its progressively larger timestamp) UE_VLOG_UELOG(Owner.Get(), LogMass, Log, TEXT("MassProcessingPhaseManager %s.%s has been started") , *GetNameSafe(Owner.Get()), *GetName()); } } void FMassProcessingPhaseManager::Stop() { bIsAllowedToTick = false; if (EntityManager) { EntityManager->GetOnNewArchetypeEvent().Remove(OnNewArchetypeHandle); EntityManager.Reset(); } for (FMassProcessingPhase& Phase : ProcessingPhases) { Phase.SetTickFunctionEnable(false); } if (UObject* LocalOwner = Owner.Get()) { UWorld* World = LocalOwner->GetWorld(); if (World && World->IsGameWorld()) { // not logging this in editor mode since it messes up the game-recorded vislog display (with its progressively larger timestamp) UE_VLOG_UELOG(LocalOwner, LogMass, Log, TEXT("MassProcessingPhaseManager %s.%s has been stopped") , *GetNameSafe(LocalOwner), *GetName()); } } } void FMassProcessingPhaseManager::Pause() { check(IsInGameThread()); if (bIsPaused == false) { bIsPauseTogglePending = true; UE_VLOG_UELOG(Owner.Get(), LogMass, Log, TEXT("Scheduling Pause for next FrameEnd phase")); } } void FMassProcessingPhaseManager::Resume() { check(IsInGameThread()); if (bIsPaused == true) { bIsPauseTogglePending = true; UE_VLOG_UELOG(Owner.Get(), LogMass, Log, TEXT("Scheduling Resume for next PrePhysics phase")); } } void FMassProcessingPhaseManager::OnPhaseStart(FMassProcessingPhase& Phase) { ensure(CurrentPhase == EMassProcessingPhase::MAX); CurrentPhase = Phase.Phase; const int32 PhaseAsInt = int32(Phase.Phase); // The VERY FIRST thing we do in the first phase is to change the Pause state if needed. // This way any code that depends on knowing the pause state (if any) gets consistent results. if (bIsPauseTogglePending && bIsPaused == true && PhaseAsInt == 0) { bIsPaused = false; bIsPauseTogglePending = false; UE_VLOG_UELOG(Owner.Get(), LogMass, Log, TEXT("Phase Processing is now Resumed")); } // switch between parallel and single-thread versions only after a given batch of processing has been wrapped up if (Phase.IsConfiguredForParallelMode() != UE::Mass::Tweakables::bFullyParallel) { if (UE::Mass::Tweakables::bFullyParallel) { Phase.ConfigureForParallelMode(); } else { Phase.ConfigureForSingleThreadMode(); } } if (PendingDynamicProcessors[PhaseAsInt].IsEmpty() == false) { HandlePendingDynamicProcessorOperations(PhaseAsInt); } UE_TRACE_MASS_PHASE_BEGIN(PhaseAsInt) if (Owner.IsValid() && ensure(Phase.Phase != EMassProcessingPhase::MAX) && (ProcessingGraphBuildStates[PhaseAsInt].bNewArchetypes || ProcessingGraphBuildStates[PhaseAsInt].bProcessorsNeedRebuild) // if not a valid index then we're not able to recalculate dependencies && ensure(ProcessingPhasesConfig.IsValidIndex(PhaseAsInt))) { TRACE_CPUPROFILER_EVENT_SCOPE_STR("Mass Rebuild Phase Graph"); FPhaseGraphBuildState& GraphBuildState = ProcessingGraphBuildStates[PhaseAsInt]; if (GraphBuildState.bInitialized == false || ProcessingGraphBuildStates[PhaseAsInt].bProcessorsNeedRebuild || FMassProcessorDependencySolver::IsResultUpToDate(GraphBuildState.LastResult, EntityManager) == false) { UMassCompositeProcessor* PhaseProcessor = ProcessingPhases[PhaseAsInt].PhaseProcessor; check(PhaseProcessor); GraphBuildState.LastResult.Reset(); FMassPhaseProcessorConfigurationHelper Configurator(*PhaseProcessor, ProcessingPhasesConfig[PhaseAsInt], *Owner.Get(), Phase.Phase); Configurator.Configure(DynamicProcessors, RemovedDynamicProcessors, ProcessorExecutionFlags, EntityManager.ToSharedRef(), GraphBuildState.LastResult); GraphBuildState.bInitialized = true; #if WITH_MASSENTITY_DEBUG UObject* OwnerPtr = Owner.Get(); // print it all out to vislog UE_VLOG_UELOG(OwnerPtr, LogMass, Verbose, TEXT("Phases initialization done. Current composition:")); FStringOutputDevice OutDescription; PhaseProcessor->DebugOutputDescription(OutDescription); UE_VLOG_UELOG(OwnerPtr, LogMass, Verbose, TEXT("--- %s"), *OutDescription); OutDescription.Reset(); #endif // WITH_MASSENTITY_DEBUG } ProcessingGraphBuildStates[PhaseAsInt].bProcessorsNeedRebuild = false; ProcessingGraphBuildStates[PhaseAsInt].bNewArchetypes = false; } } void FMassProcessingPhaseManager::OnPhaseEnd(FMassProcessingPhase& Phase) { ensure(CurrentPhase == Phase.Phase); UE_TRACE_MASS_PHASE_END(static_cast(CurrentPhase)) CurrentPhase = EMassProcessingPhase::MAX; // The VERY LAST thing we do in FrameEnd is change the Pause state if needed. // This way any code that depends on knowing the pause state (if any) gets consistent results. if (bIsPauseTogglePending && bIsPaused == false && Phase.Phase == EMassProcessingPhase::FrameEnd) { bIsPaused = true; bIsPauseTogglePending = false; #if WITH_MASSENTITY_DEBUG UE_VLOG_UELOG(Owner.Get(), LogMass, Log, TEXT("Phase Processing is now Paused")); #endif // WITH_MASSENTITY_DEBUG } if (GetEntityManagerRef().Defer().HasPendingCommands()) { GetEntityManagerRef().FlushCommands(); } } FString FMassProcessingPhaseManager::GetName() const { return GetNameSafe(Owner.Get()) + TEXT("_MassProcessingPhaseManager"); } void FMassProcessingPhaseManager::RegisterDynamicProcessor(UMassProcessor& Processor) { if (ensureMsgf(Processor.GetProcessingPhase() != EMassProcessingPhase::MAX , TEXT("%hs, Misconfigured processor %s, marked as ProcessingPhase == MAX"), __FUNCTION__, *Processor.GetName())) { PendingDynamicProcessors[int32(Processor.GetProcessingPhase())].Enqueue(&Processor, EDynamicProcessorOperationType::Add); } } void FMassProcessingPhaseManager::RegisterDynamicProcessorInternal(TNotNull Processor) { if (Processor->IsInitialized() == false) { check(EntityManager->GetOwner()); Processor->CallInitialize(EntityManager->GetOwner(), EntityManager.ToSharedRef()); } DynamicProcessors.Add(Processor); Processor->MarkAsDynamic(); } void FMassProcessingPhaseManager::UnregisterDynamicProcessor(UMassProcessor& Processor) { if (ensureMsgf(Processor.GetProcessingPhase() != EMassProcessingPhase::MAX , TEXT("%hs, Misconfigured processor %s, marked as ProcessingPhase == MAX"), __FUNCTION__, *Processor.GetName())) { PendingDynamicProcessors[int32(Processor.GetProcessingPhase())].Enqueue(&Processor, EDynamicProcessorOperationType::Remove); } } void FMassProcessingPhaseManager::UnregisterDynamicProcessorInternal(TNotNull Processor) { int32 Index = INDEX_NONE; if (DynamicProcessors.Find(Processor, Index)) { DynamicProcessors.RemoveAtSwap(Index, 1, EAllowShrinking::No); ProcessingGraphBuildStates[int32(Processor->GetProcessingPhase())].bProcessorsNeedRebuild = true; // it's possible that the given dynamic processor is a part of processing graph at the moment // we need to store the information about its removal and use it when rebuilding the graph next time around. RemovedDynamicProcessors.Add(Processor); } else { checkf(false, TEXT("Unable to remove Processor '%s', as it was never added or already removed."), *Processor->GetName()); } } void FMassProcessingPhaseManager::HandlePendingDynamicProcessorOperations(const int32 PhaseIndex) { bool bWorkDone = false; FDynamicProcessorOperation Operation; while (PendingDynamicProcessors[PhaseIndex].Dequeue(Operation)) { if (Operation.Get<1>() == EDynamicProcessorOperationType::Add) { RegisterDynamicProcessorInternal(Operation.Get<0>().Get()); } else { UnregisterDynamicProcessorInternal(Operation.Get<0>().Get()); } bWorkDone = true; } if (bWorkDone) { ProcessingGraphBuildStates[PhaseIndex].bProcessorsNeedRebuild = bWorkDone; } } void FMassProcessingPhaseManager::OnNewArchetype(const FMassArchetypeHandle& NewArchetype) { for (FPhaseGraphBuildState& GraphBuildState : ProcessingGraphBuildStates) { GraphBuildState.bNewArchetypes = true; } } #if WITH_MASSENTITY_DEBUG void FMassProcessingPhaseManager::OnDebugEntityManagerInitialized(const FMassEntityManager& InEntityManager) { } void FMassProcessingPhaseManager::OnDebugEntityManagerDeinitialized(const FMassEntityManager& InEntityManager) { } #endif // WITH_MASSENTITY_DEBUG //----------------------------------------------------------------------------- // DEPRECATED //----------------------------------------------------------------------------- UE_DEPRECATED(5.6, "This flavor of Configure is deprecated. Please use the one using a TSharedRef parameter instead") void FMassPhaseProcessorConfigurationHelper::Configure(TArrayView DynamicProcessors, EProcessorExecutionFlags InWorldExecutionFlags , const TSharedPtr& EntityManager , FMassProcessorDependencySolver::FResult* OutOptionalResult) { if (ensureMsgf(EntityManager, TEXT("Configuring processors without a valid EntityManager is no longer supported")) && OutOptionalResult) { static TArray> DummyRemovedDynamicProcessors; Configure(DynamicProcessors, DummyRemovedDynamicProcessors, InWorldExecutionFlags, EntityManager.ToSharedRef(), *OutOptionalResult); } } void FMassProcessingPhaseManager::Start(const TSharedPtr& InEntityManager) { if (InEntityManager) { Start(InEntityManager.ToSharedRef()); } } #undef LOCTEXT_NAMESPACE