MassProcessor的执行顺序
MassProcessor的执行顺序由UMassCompositeProcessor来控制,在视频29:52有过说明,这里会从源码层面说一下它的流程。
MassProcessingPhaseManager
Processor在哪个TickGroup执行时MassProcessingPhaseManager决定的,来看源码中的注释:
//MassProcessingPhaseManager.h
/** MassProcessingPhaseManager owns separate FMassProcessingPhase instances for every ETickingGroup. When activated
* via Start function it registers and enables the FMassProcessingPhase instances which themselves are tick functions
* that host UMassCompositeProcessor which they trigger as part of their Tick function.
* MassProcessingPhaseManager serves as an interface to said FMassProcessingPhase instances and allows initialization
* with MassSchematics (via InitializePhases function) as well as registering arbitrary functions to be called
* when a particular phase starts of ends (via GetOnPhaseStart and GetOnPhaseEnd functions). */
UCLASS(Transient, HideCategories = (Tick))
class MASSENTITY_API UMassProcessingPhaseManager : public UObject
{
...
}
大致意思是,MassProcessingPhaseManager管理着一堆的FMassProcessingPhase, 这些FMassProcessingPhase是FTickFunction, 可以在不同的TickGroup中执行。每个FMassProcessingPhase又持有一个UMassCompositeProcessor,从而UMassCompositeProcessor也有了在不同TickGroup中执行的能力:

初始化
初始化堆栈:
UMassProcessingPhaseManager::CreatePhases()
-> UMassProcessingPhaseManager::PostInitProperties()
-> FObjectInitializer::PostConstructInit()
-> UObject::CreateDefaultSubobject(...)
-> UMassSimulationSubsystem::UMassSimulationSubsystem(const FObjectInitializer & ObjectInitializer)
它在一个Subsystem中被创建,初始化的主要逻辑为:
//MassProcessingPhaseManager.cpp
void UMassProcessingPhaseManager::CreatePhases()
{
//预计要改成从Settings里面读取?
// @todo copy from settings instead of blindly creating from scratch
for (int i = 0; i < int(EMassProcessingPhase::MAX); ++i)
{
ProcessingPhases[i].Phase = EMassProcessingPhase(i);
//指定TickGroup
ProcessingPhases[i].TickGroup = UE::Mass::Private::PhaseToTickingGroup[i];
//为每个TickGroup创建了一个MassCompositeProcessor, 保存在ProcessingPhases中
UMassCompositeProcessor* PhaseProcessor = NewObject<UMassCompositeProcessor>(this,
UMassCompositeProcessor::StaticClass(), *FString::Printf(TEXT("ProcessingPhase_%s"),
*UEnum::GetDisplayValueAsText(EMassProcessingPhase(i)).ToString()));
SetPhaseProcessor(EMassProcessingPhase(i), PhaseProcessor);
}
}
注册Tick
注册堆栈:
UMassProcessingPhaseManager::EnableTickFunctions(const UWorld & World)
-> UMassProcessingPhaseManager::Start(UWorld & World)
-> UMassSimulationSubsystem::StartSimulation(UWorld & InWorld)
-> UMassSimulationSubsystem::OnWorldBeginPlay(UWorld & InWorld)
-> UWorld::BeginPlay()
大致逻辑:
//MassProcessingPhaseManager.cpp
void UMassProcessingPhaseManager::EnableTickFunctions(const UWorld& World)
{
for (FMassProcessingPhase& Phase : ProcessingPhases)
{
//就是通用的注册TickFunction的方式
Phase.RegisterTickFunction(World.PersistentLevel);
Phase.SetTickFunctionEnable(true);
}
}
执行Tick
//MassProcessingPhaseManager.cpp
void FMassProcessingPhase::ExecuteTick(float DeltaTime, ELevelTick TickType, ...)
{
//这里只是设置下状态
PhaseManager->OnPhaseStart(*this);
{
//大部分逻辑这里执行, 通常用如下代码进行注册
//PhaseManager->GetOnPhaseStart(Phase)->AddUObject(...)
OnPhaseStart.Broadcast(DeltaTime);
}
//如果是并行模式,默认是true
if (bRunInParallelMode)
{
if (PhaseProcessor->IsEmpty() == false)
{
//并行运行CompositionProcessor
const FGraphEventRef PipelineCompletionEvent = UE::Mass::Executor::TriggerParallelTasks(*PhaseProcessor, Context,
[this, DeltaTime]()
{
//这个函数其实是调用OnPhaseEnd.Broadcast(DeltaTime)和PhaseManager->OnPhaseEnd(*this);
OnParallelExecutionDone(DeltaTime);
});
}
}
else
{
//串行
UE::Mass::Executor::Run(*PhaseProcessor, Context);
{
LLM_SCOPE_BYNAME(TEXT("Mass/PhaseEndDelegate"));
OnPhaseEnd.Broadcast(DeltaTime);
}
PhaseManager->OnPhaseEnd(*this);
bIsDuringMassProcessing = false;
}
}
UMassCompositeProcessor
并行运行UMassCompositeProcessor时,基本逻辑如下:
//MassProcessor.cpp
FGraphEventRef UMassCompositeProcessor::DispatchProcessorTasks(...)
{
//ProcessingFlatGraph已经是个有依赖顺序的数组了,即后面的元素依赖前面的元素,第一个元素一定不会依赖其他项的
for (FDependencyNode& ProcessingNode : ProcessingFlatGraph)
{
FGraphEventArray Prerequisites;
//创建出TaskGraph
for (const int32 DependencyIndex : ProcessingNode.Dependencies)
{
Prerequisites.Add(Events[DependencyIndex]);
}
// we don't expect any group nodes at this point. If we get any there's a bug in dependencies solving
if (ensure(ProcessingNode.Processor))
{
Events.Add(ProcessingNode.Processor->DispatchProcessorTasks(EntityManager, ExecutionContext, Prerequisites));
}
}
//执行
FGraphEventRef CompletionEvent = FFunctionGraphTask::CreateAndDispatchWhenReady([this](){}
, GET_STATID(Mass_GroupCompletedTask), &Events, ENamedThreads::AnyHiPriThreadHiPriTask);
}
初始化
在UMassCompositionProcessor中有这么几个变量:
//MassProcessor.h
UCLASS()
class MASSENTITY_API UMassCompositeProcessor : public UMassProcessor
{
protected:
//就是各TArray<TObjectPtr<UMassProcessor>> Processors
UPROPERTY(VisibleAnywhere, Category=Mass)
FMassRuntimePipeline ChildPipeline;
//子Processor的依赖关系
TArray<FDependencyNode> ProcessingFlatGraph;
}
初始化主要就是针对这两个变量。进入游戏时会重建Processor的执行管线:
UMassSimulationSubsystem::RebuildTickPipeline()
-> UMassSimulationSubsystem::OnWorldBeginPlay(UWorld & InWorld)
-> UWorld::BeginPlay()
主要是调用了这个函数:
void UMassProcessingPhaseManager::InitializePhases(UObject& InProcessorOwner)
{
//获取当前需要运行的Processor, 主要逻辑在UMassEntitySettings::BuildProcessorList中:
//1. 反射得到所有MassProcessor的对象,去掉abstract的和CompositeProcessor,存到ProcessorCDOs变量中
//2. 果这个Processor设置了bAutoRegisterWithProcessingPhases, 意味着要自动运行,再把这些Processor加入到ProcessingPhasesConfig中
// 返回的也是这个ProcessingPhasesConfig
const FMassProcessingPhaseConfig* ProcessingPhasesConfig = GET_MASS_CONFIG_VALUE(GetProcessingPhasesConfig());
for (int i = 0; i < int(EMassProcessingPhase::MAX); ++i)
{
const FMassProcessingPhaseConfig& PhaseConfig = ProcessingPhasesConfig[i];
UMassCompositeProcessor* PhaseProcessor = ProcessingPhases[i].PhaseProcessor;
FString FileName = ...;
//主要是调用了UMassCompositeProcessor::SetProcessors函数
//1. 使用FProcessorDependencySolver分析依赖关系,把PhaseProcessor的所有子Processor写入ProcessingFlatGraph变量
//2. 将所有的子Processor写入ChildPipeline变量
PhaseProcessor->CopyAndSort(PhaseConfig, FileName);
//初始化所有子Processor
PhaseProcessor->Initialize(InProcessorOwner);
}
ProcessorDependencySolver
所有的Processor的依赖关系都是通过ProcessorDependencySolver来解析。每个Processor里都有ExecutionOrder变量:
//MassProcessor.h
USTRUCT()
struct FMassProcessorExecutionOrder
{
GENERATED_BODY()
//Processor所属的组名,有子组概念,可参考FProcessorDependencySolver::CreateSubGroupNames中的注释
FName ExecuteInGroup = FName();
//在哪个Processor之前执行, 可以是组名,也可以是Processor名
TArray<FName> ExecuteBefore;
//在哪个Processor之前执行, 可以是组名,也可以是Processor名
TArray<FName> ExecuteAfter;
};
class MASSENTITY_API UMassProcessor : public UObject
{
UPROPERTY(EditDefaultsOnly, Category = Processor, config)
FMassProcessorExecutionOrder ExecutionOrder;
}
在中,会对Processor依次调用FProcessorDependencySolver::CreateNodes来建立索引关系:
//MassProcessorDependencySolver.h
//CreateNode中的Node, 用以辅助建立索引关系,所有的Node保存在AllNodes变量中
struct FProcessorDependencySolver::FNode
{
FName Name = TEXT("");
UMassProcessor* Processor = nullptr;
TArray<int32> OriginalDependencies;
TArray<int32> TransientDependencies;
TArray<FName> ExecuteBefore;
TArray<FName> ExecuteAfter;
FMassExecutionRequirements Requirements;
int32 NodeIndex = INDEX_NONE;
TArray<int32> SubNodeIndices; //所有的子节点, 用以对Processor分组
}
//MassProcessorDependencySolver.cpp
//这一步建立了ProcessorGroup和Processor的关系
void FProcessorDependencySolver::CreateNodes(UMassProcessor& Processor)
{
//1. 分析出Processor的组名,判断AllNode中是否创建过该组名的节点
// 1.1 如果有, 意味着某个Node需要加一个叶子节点。
// 在AllNode中新加一个该Processor的节点,并在该组名节点的SubNodeIndices中引用这个节点
// 1.2 如果没有,可能遇到了一个全新组
// 如果组名正确,创建组名节点:AllNodes.Add_GetRef({ GroupFName, nullptr, NewGroupNodeIndex })
// 创建Processor节点,并在该组名节点的SubNodeIndices中引用这个节点
}
在之后的BuildDependencies函数中,去除组的概念,将对组的依赖改为对Processor的依赖:
//MassProcessorDependencySolver.cpp
void FProcessorDependencySolver::BuildDependencies()
{
//1. 改为单向依赖,即把A.ExecuteBefore(B)该为 B.ExecuteAfter(A)
//2. 处理组和Process的依赖关系, 例如
// 2.1 组A[P1, P2] 依赖 P3, 那么P1, P2都依赖P3
// 2.2 P4依赖组B[P5, P6], 那么P4依赖P5,P6
}
至此,依赖关系建立完毕,调用Solve输出。