一、系统概述

1.1 设计目标

该子系统旨在优化Actor及其组件的Transform更新流程。通过跳过引擎默认的子组件Transform更新机制,转而采用批量处理和并行计算的方式,显著降低Transform更新的性能开销。

1.2 适用场景

  • 场景中存在大量需要频繁更新Transform的Actor
  • Actor的组件层级结构较为复杂
  • Transform更新成为性能瓶颈
  • 需要精确控制Transform更新时机

1.3 架构定位

该子系统继承自 UWorldSubsystem,在客户端和独立端运行(不运行于专用服务器),通过自定义Tick函数在 TG_PostUpdateWork 阶段执行Transform更新。

二、核心数据结构

2.1 注册Actor映射表

TMap<TWeakObjectPtr<AActor>, FActorTransformCache> RegisteredActors

存储所有已注册Actor的缓存信息,键为Actor弱引用,值为Transform缓存结构。

2.2 Transform缓存结构

FActorTransformCache 结构包含:

  • CachedComponents:缓存的所有组件Transform信息数组
  • bIsCached:缓存是否有效的标志位

FCachedComponentTransformInfo 结构包含:

  • Component:组件弱引用
  • RelativeToRootTransform:相对于RootComponent的Transform

2.3 待更新Actor集合

TSet<TWeakObjectPtr<AActor>> DirtyActors

存储Transform发生变化、需要在下一帧更新的Actor集合。

2.4 回调句柄映射表

TMap<TWeakObjectPtr<AActor>, FDelegateHandle> TransformUpdateHandles

存储Actor与其Transform更新回调句柄的映射关系,用于解绑回调。

2.5 并行执行单元数组

TArray<FComponentExecutionUnit> CachedComponentExecutionUnits

临时存储已缓存Actor的所有组件执行单元,用于并行处理。包含:

  • ActorPtr:Actor弱引用
  • Component:组件指针
  • RelativeToRootTransform:相对Transform
  • RootToWorld:RootComponent的世界Transform

三、注册与注销流程

3.1 Actor注册流程

前置条件检查:

  1. 检查优化功能是否启用(通过控制台变量 FmTransformOptimization.Enabled)
  2. 验证Actor指针有效性
  3. 确认Actor实现了 IInterface_TransformUpdateOptimization 接口
  4. 检查Actor是否已注册(避免重复注册)
  5. 调用接口方法验证Actor是否适合优化

初始化操作:

  1. 设置Actor所有SceneComponent的 bSkipUpdateChildTransform 为 true,禁用引擎默认的子组件Transform更新
  2. 获取用于监听Transform变化的SceneComponent(通过接口获取,若返回nullptr则使用RootComponent)
  3. 绑定RootComponent变化回调(用于处理RootComponent替换的情况)
  4. 绑定Transform更新回调(用于监听Transform变化)
  5. 将Actor添加到注册映射表,初始时不缓存(等待首次Tick时缓存)
  6. 调用接口方法通知Actor已注册

3.2 Actor注销流程

清理操作:

  1. 获取监听Transform变化的SceneComponent
  2. 解绑RootComponent变化回调
  3. 解绑Transform更新回调
  4. 从DirtyActors集合中移除
  5. 恢复所有SceneComponent的 bSkipUpdateChildTransform 为 false
  6. 从注册映射表中移除
  7. 调用接口方法通知Actor已注销

3.3 接口约束

Actor必须实现 IInterface_TransformUpdateOptimization 接口,该接口定义以下方法:

必须实现:

  • IsActorValidForTransformOptimization():判断Actor是否适合进行Transform优化
  • OnRegisteredToTransformOptimization():注册成功通知
  • OnUnregisteredFromTransformOptimization():注销成功通知

可选实现:

  • GetTransformUpdateListenComponent():返回用于监听Transform变化的组件,若返回nullptr则使用RootComponent

四、Transform变化监听机制

4.1 监听绑定策略

双重监听机制:

  1. RootComponent变化监听:

    • 绑定 IsRootComponentChanged 委托
    • 当RootComponent被替换时触发回调
    • 用于重新绑定Transform更新回调到新的RootComponent
  2. Transform更新监听:

    • 绑定 TransformUpdated 委托
    • 当组件Transform发生变化时触发回调
    • 用于标记Actor为Dirty,加入待更新队列

4.2 回调处理逻辑

Transform更新回调(OnListenComponentTransformChanged):

  • 检查是否正在Tick执行中(避免数据竞争)
  • 验证Actor有效性
  • 将Actor添加到DirtyActors集合
  • 记录详细日志(包含帧号、网络模式、Actor名称、组件名称等信息)

RootComponent变化回调(OnRootComponentChanged):

  • 检查是否正在Tick执行中
  • 验证Actor有效性
  • 解绑旧组件的Transform更新回调
  • 获取新的监听组件(通过接口或使用新RootComponent)
  • 绑定新组件的Transform更新回调

4.3 回调保护机制

通过 bIsTicking 标志位防止回调重入:

  • Tick执行前设置 bIsTicking = true
  • 回调中检查该标志位,若为true则直接返回
  • Tick执行后设置 bIsTicking = false

五、核心更新流程

5.1 Tick执行时机

子系统通过自定义的 FFmTransformOptimizationTickFunction 在 TG_PostUpdateWork 阶段执行,确保在所有游戏逻辑更新完成后进行Transform更新。

5.2 Tick主流程

执行步骤:

  1. 早期退出检查:

    • 检查优化功能是否启用
    • 检查注册Actor数量是否为0
    • 即使提前返回也要累加清理计时器
  2. 设置执行标志:

    • 设置 bIsTicking = true 防止回调重入
  3. 清理无效引用:

    • 累加清理计时器
    • 清理DirtyActors中的无效引用(每帧执行)
    • 分帧清理RegisteredActors和TransformUpdateHandles中的无效引用(定时执行)
  4. 处理Dirty Actors:

    • 调用 ProcessDirtyActors 处理所有待更新的Actor
  5. 广播PostTick委托:

    • 广播 PostTickDelegate,允许外部系统在Transform更新完成后执行逻辑
  6. 清除执行标志:

    • 设置 bIsTicking = false

5.3 Dirty Actors处理流程

步骤分解:

  1. 转换并清空集合:

    • 将DirtyActors转换为数组
    • 立即清空DirtyActors集合,接收新的变化
  2. 分离已缓存和未缓存Actor:

    • 遍历DirtyActors数组
    • 根据缓存标志位 bIsCached 分类
    • 分别添加到UncachedActors和CachedActors数组
  3. 处理未缓存Actor:

    • 调用 ProcessUncachedActors
    • 执行层级更新和首次缓存
  4. 处理已缓存Actor:

    • 调用 ProcessCachedActors
    • 利用缓存信息进行高效的并行更新

六、未缓存Actor处理

6.1 处理流程

针对未缓存Actor(首次更新或缓存失效),执行以下操作:

并行处理(ParallelFor):

  1. 验证Actor有效性
  2. 执行层级Transform更新(UpdateActorTransformsHierarchical)
  3. 缓存组件信息(CacheActorComponents)

6.2 层级Transform更新

递归收集组件:

  • 从RootComponent开始,递归遍历所有AttachChildren
  • 按父子关系顺序收集到数组(父组件在前,子组件在后)

手动更新Transform:

  • 遍历所有收集的组件
  • 设置 bSkipUpdateChildTransform = true(确保跳过自动更新)
  • 调用 ManualUpdateComponentToWorld 手动更新Transform

6.3 组件缓存

缓存计算:

  1. 获取或创建Actor的缓存结构
  2. 清空旧缓存
  3. 收集所有SceneComponent
  4. 计算RootComponent的WorldTransform
  5. 为每个组件计算相对于RootComponent的Transform并缓存

相对Transform计算:

FTransform RelativeToRoot = Component->GetComponentTransform().GetRelativeTransform(RootToWorld);

七、已缓存Actor处理

7.1 执行单元构建

预处理阶段:

  1. 遍历所有已缓存Actor
  2. 统计总组件数量,一次性预留内存
  3. 清空并预留 CachedComponentExecutionUnits 数组

构建执行单元:

  • 遍历每个已缓存Actor
  • 获取当前RootComponent的WorldTransform
  • 将每个组件封装为执行单元,包含:
    • Actor弱引用
    • 组件指针
    • 相对Transform(从缓存获取)
    • RootToWorld(当前帧实时值)

7.2 并行Transform更新

并行处理(ParallelFor):

  1. 验证Actor和组件有效性
  2. 计算新的WorldTransform:
    FTransform NewWorldTransform = RelativeToRootTransform * RootToWorld;
  3. 直接设置 ComponentToWorld:
    Component->SetComponentToWorld(NewWorldTransform);
  4. 标记渲染Transform为脏:
    Component->ManualMarkRenderTransformDirty();
  5. 执行延迟渲染更新:
    Component->DoDeferredRenderUpdates_Concurrent();

7.3 优化原理

性能优势:

  • 避免重复计算相对Transform关系(已缓存)
  • 以组件为单位并行,充分利用多核CPU
  • 直接操作 ComponentToWorld,跳过引擎的层级更新逻辑
  • 批量处理减少函数调用开销

八、缓存管理机制

8.1 缓存失效触发

自动失效:

  • RootComponent被替换时(通过RootComponent变化回调)
  • 组件层级结构发生变化时(通过外部调用 MarkActorCacheDirty)

手动失效:

  • 调用 MarkActorCacheDirty 接口标记缓存为脏
  • 下次Tick时会重新执行层级更新和缓存

8.2 缓存清理策略

DirtyActors清理(每帧执行):

  • 遍历DirtyActors集合
  • 移除无效的弱引用

注册Actor清理(定时执行):

  • 通过 CleanupTimer 和 CleanupInterval 控制清理频率
  • 使用 ParallelFor 并行检查所有Actor的有效性
  • 使用 TBitArray 记录无效索引,避免锁竞争
  • 从 RegisteredActors 和 TransformUpdateHandles 中移除无效引用

8.3 清理优化技术

并行有效性检查:

ParallelFor(AllActorKeys.Num(), [&AllActorKeys, &InvalidActorFlags](int32 Index)
{
    const TWeakObjectPtr<AActor>& ActorPtr = AllActorKeys[Index];
    if (!ActorPtr.IsValid())
    {
        InvalidActorFlags[Index] = true;
    }
});

内存优化:

  • 使用 TBitArray 而非 TArray<bool>,每个元素仅占1 bit
  • 避免使用锁,每个线程写入独立位置
  • 使用 RemoveAtSwap 快速移除元素(不保证顺序)

九、Tick依赖管理

9.1 前置依赖

添加前置依赖(AddTickPrerequisite):

  • 确保子系统的Tick在指定对象的TickFunction之后执行
  • 记录已添加的依赖对象,避免重复添加
  • 使用 TickFunction.AddPrerequisite 建立依赖关系

移除前置依赖(RemoveTickPrerequisite):

  • 检查依赖是否存在
  • 使用 TickFunction.RemovePrerequisite 移除依赖
  • 从记录中移除依赖对象

9.2 后继依赖

添加后继依赖(AddTickSuccessor):

  • 确保外部TickFunction在子系统的Tick之后执行
  • 通过 InTickFunction.AddPrerequisite(this, TickFunction) 建立

移除后继依赖(RemoveTickSuccessor):

  • 使用 InTickFunction.RemovePrerequisite(this, TickFunction) 移除

十、调试与监控

10.1 日志系统

日志分类:

  • 使用 DEFINE_LOG_CATEGORY_STATIC 定义静态日志类别 LogTransformOptimization
  • 日志级别:Log、Warning、Verbose

关键日志点:

  • 子系统初始化和反初始化
  • Actor注册和注销
  • Transform变化事件(Verbose级别,包含帧号、网络模式、Actor名称)
  • Tick执行(Verbose级别,包含帧号、Dirty Actor数量)

10.2 调试信息获取

GetDebugSide函数:

  • 根据World的NetMode判断运行端
  • 格式化输出:[帧号]Client/Server/Standalone[_PIEInstanceID]
  • 用于日志中区分不同客户端和服务端

10.3 性能分析

命名事件:

SCOPED_NAMED_EVENT_TEXT("Transform Update Optimization Tick", FColor::Purple);
SCOPED_NAMED_EVENT_TEXT("Post Tick", FColor::Purple);
  • 使用UE的Profiler系统标记性能关键区域
  • 方便在性能分析工具中定位耗时

十一、配置与控制

11.1 控制台变量

优化开关:

FmTransformOptimization.Enabled
  • 类型:bool
  • 默认值:true
  • 作用:全局启用或禁用Transform优化功能
  • 运行时可通过控制台动态调整

11.2 子系统创建条件

ShouldCreateSubsystem:

  • 调用父类实现
  • 检查Outer是否有效
  • 对于DedicatedServer,不创建子系统(返回false)

DoesSupportWorldType:

  • 支持 EWorldType::Game(游戏运行时)
  • 支持 EWorldType::PIE(编辑器预览)

十二、线程安全与并发

12.1 线程模型

Tick执行线程:

  • 通过 FFmTransformOptimizationTickFunction::ExecuteTick 确保在游戏线程执行
  • 检查 CurrentThread == ENamedThreads::GameThread

并行处理:

  • 使用 ParallelFor 进行并行计算
  • 使用 FOptionalTaskTagScope(ETaskTag::EParallelGameThread) 标记并行游戏线程任务
  • 每个线程写入独立的数据位置,避免数据竞争

12.2 数据竞争防护

回调保护:

  • 使用 bIsTicking 标志位防止Tick执行期间的回调重入
  • 回调中检查标志位,若正在Tick则直接返回

弱引用使用:

  • 所有Actor和组件引用均使用 TWeakObjectPtr
  • 避免强引用导致的内存管理问题
  • 每次使用前验证有效性

并行写入隔离:

  • 使用 TBitArray 记录无效索引,每个线程写入独立位置
  • 并行处理后再串行移除无效元素

十三、使用示例

13.1 Actor实现接口

class AMyOptimizedActor : public AActor, public IInterface_TransformUpdateOptimization
{
    // 实现接口方法
    virtual bool IsActorValidForTransformOptimization() override
    {
        return IsActive() && !IsPendingKillPending();
    }
    
    virtual void OnRegisteredToTransformOptimization() override
    {
        // 注册成功后的初始化
    }
    
    virtual void OnUnregisteredFromTransformOptimization() override
    {
        // 注销后的清理
    }
    
    virtual USceneComponent* GetTransformUpdateListenComponent() override
    {
        // 返回需要监听的组件,nullptr表示使用RootComponent
        return CustomListenComponent;
    }
};

13.2 注册Actor

void RegisterMyActor(AMyOptimizedActor* Actor)
{
    if (UFmTransformUpdateOptimizationSubsystem* Subsystem = 
        UFmTransformUpdateOptimizationSubsystem::Get(Actor->GetWorld()))
    {
        Subsystem->RegisterActor(Actor);
    }
}

13.3 注销Actor

void UnregisterMyActor(AMyOptimizedActor* Actor)
{
    if (UFmTransformUpdateOptimizationSubsystem* Subsystem = 
        UFmTransformUpdateOptimizationSubsystem::Get(Actor->GetWorld()))
    {
        Subsystem->UnregisterActor(Actor);
    }
}

13.4 标记缓存失效

void OnActorStructureChanged(AActor* Actor)
{
    if (UFmTransformUpdateOptimizationSubsystem* Subsystem = 
        UFmTransformUpdateOptimizationSubsystem::Get(Actor->GetWorld()))
    {
        Subsystem->MarkActorCacheDirty(Actor);
    }
}

十四、性能优化总结

14.1 优化策略

策略实现方式效果
跳过自动更新设置bSkipUpdateChildTransform避免引擎默认的层级更新开销
批量处理DirtyActors集合统一处理减少函数调用开销
并行计算ParallelFor多线程处理充分利用多核CPU
缓存机制缓存相对Transform关系避免重复计算
延迟清理分帧定时清理无效引用分散性能开销

14.2 适用场景建议

适合使用:

  • 大量静态或半静态Actor(如建筑、植被)
  • 组件层级复杂但结构稳定的Actor
  • Transform更新频率适中的场景

不适合使用:

  • 组件结构频繁变化的Actor
  • Transform每帧都剧烈变化的Actor
  • 组件数量极少、优化收益小的Actor

十五、注意事项

15.1 接口实现要求

  • 必须实现 IInterface_TransformUpdateOptimization 接口
  • 接口方法中不应执行耗时操作
  • GetTransformUpdateListenComponent 应返回稳定的组件引用

15.2 时序要求

  • 子系统Tick在 TG_PostUpdateWork 阶段执行
  • 若需在Transform更新后执行逻辑,可绑定 PostTickDelegate
  • 避免在Tick期间修改Actor的组件结构

15.3 网络模式

  • 子系统不在DedicatedServer上创建
  • 客户端和独立端独立运行各自的优化逻辑
  • 网络同步的Actor应在服务器完成Transform同步后,客户端自动优化

15.4 调试建议

  • 使用控制台变量 FmTransformOptimization.Enabled 0 可快速禁用优化
  • 通过Verbose日志级别查看详细的Transform变化事件
  • 使用UE Profiler分析性能瓶颈和优化效果