UE5.8 Mass Framework 专题系列

UE5.8 Mass 专题(四):Processor、Phase、Dependency 与调度

拆解 UMassProcessor、EMassProcessingPhase、ExecutionOrder、RegisterQuery、ConfigureQueries、ExportRequirements、Processor Dependency Solver 与全局处理列表。

总览

UMassProcessor 是 Mass 运行逻辑的基本单元,但它不是普通 Tick()。Processor 会声明 Query、执行阶段、执行顺序、线程要求和激活状态。Mass 的 Phase Manager 把多个 Processor 组织到 PrePhysics、DuringPhysics、PostPhysics 等阶段,Dependency Solver 再根据 Query 读写需求和显式顺序构建执行图。

这套系统的目标是让项目能写大量小 Processor,而不是一个巨大 Manager。小 Processor 更容易声明依赖、更容易并行,也更容易调试某个环节为什么没写出预期 Fragment。

UE5.8 Mass 专题(四):Processor、Phase、Dependency 与调度 配图
Mass 用 Processor 的查询需求和执行顺序推导依赖,把每个阶段组织成可调试、可并行的处理图。

源码依据

MassProcessor.hUMassProcessor 拥有 FMassProcessorExecutionOrder,包含 ExecuteInGroupExecuteBeforeExecuteAfter;默认 Phase 是 EMassProcessingPhase::PrePhysics。Processor 通过 ConfigureQueries 配置需求,通过 Execute 执行逻辑,通过 RegisterQueryQuery.RegisterWithProcessor(*this) 暴露需求。MassProcessorDependencySolver.h 负责依赖求解,MassProcessingPhaseManager.h 管阶段调度。

架构分析

调度有两种依赖:数据依赖和业务依赖。数据依赖来自 Query 读写声明,例如移动意图 Processor 写 FMassDesiredMovementFragment,应用移动 Processor 读它;业务依赖来自显式执行顺序,例如你希望“目的地选择”一定在“移动意图”之前,即使它们不直接写同一个 Fragment。好的 Mass 架构应该先靠数据依赖,再用少量业务依赖补充。

Processor 分组也很重要。Representation 相关源码里有 UE::Mass::ProcessorGroupNames::Representation,Movement、LOD、Replication 也都有各自模块边界。项目自定义 Processor 可以按 Project.Crowd.DecisionProject.Crowd.MovementProject.Crowd.Visual 这样的组名组织,避免 ExecuteBefore/After 全都指向具体类,导致后期改名和替换困难。

使用案例

一个人群模拟的阶段可以这样排:

阶段/组 Processor 输入 输出
PrePhysics.Decision 选择目的地 StateTree/Intent TargetLocation
PrePhysics.Movement 生成期望移动 Transform/Target DesiredMovement
PrePhysics.Movement 避障和转向 DesiredMovement/Obstacle Force/DesiredMovement
PrePhysics.Apply 应用移动 DesiredMovement/Velocity Transform/Velocity
PostPhysics.Visual Representation Transform/LOD Actor/ISM 更新

示例构造函数:

UShopperDesiredMovementProcessor::UShopperDesiredMovementProcessor()
{
    ExecutionOrder.ExecuteInGroup = UE::Mass::ProcessorGroupNames::Movement;
    ExecutionOrder.ExecuteAfter.Add(TEXT("Project.Crowd.Decision"));
    ExecutionFlags = static_cast<int32>(EProcessorExecutionFlags::All);
    bAutoRegisterWithProcessingPhases = true;
}

实际项目里更推荐把自定义组名集中定义,避免字符串散落。

项目落地

先画数据流,再写 Processor。每个 Processor 名称尽量体现“写什么”:ChooseDestinationBuildDesiredMovementApplyCheckoutStateCrowdProcessor 更好。每个 Processor 的 Query 控制在 3 到 6 个关键 Requirement 以内;超过这个范围,通常说明它承担了多个职责。

常见坑

不要用 ExecuteBefore/After 修复错误的读写声明。不要让一个 Processor 同时做决策、移动、表现和销毁。不要默认 Processor 能并行,访问 World、Actor、Component、非线程安全 Subsystem 时要明确要求游戏线程。不要忽略 ActivationState,OneShot 或手动激活适合初始化和批处理工具,不适合长期模拟。

源码路径索引

  • MassEntity/Public/MassProcessor.h
  • MassEntity/Public/MassProcessingPhaseManager.h
  • MassEntity/Public/MassProcessorDependencySolver.h
  • MassEntity/Public/MassProcessingTypes.h