一、系统概述
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:相对TransformRootToWorld:RootComponent的世界Transform
三、注册与注销流程
3.1 Actor注册流程
前置条件检查:
- 检查优化功能是否启用(通过控制台变量
FmTransformOptimization.Enabled) - 验证Actor指针有效性
- 确认Actor实现了
IInterface_TransformUpdateOptimization接口 - 检查Actor是否已注册(避免重复注册)
- 调用接口方法验证Actor是否适合优化
初始化操作:
- 设置Actor所有SceneComponent的
bSkipUpdateChildTransform为true,禁用引擎默认的子组件Transform更新 - 获取用于监听Transform变化的SceneComponent(通过接口获取,若返回nullptr则使用RootComponent)
- 绑定RootComponent变化回调(用于处理RootComponent替换的情况)
- 绑定Transform更新回调(用于监听Transform变化)
- 将Actor添加到注册映射表,初始时不缓存(等待首次Tick时缓存)
- 调用接口方法通知Actor已注册
3.2 Actor注销流程
清理操作:
- 获取监听Transform变化的SceneComponent
- 解绑RootComponent变化回调
- 解绑Transform更新回调
- 从DirtyActors集合中移除
- 恢复所有SceneComponent的
bSkipUpdateChildTransform为false - 从注册映射表中移除
- 调用接口方法通知Actor已注销
3.3 接口约束
Actor必须实现 IInterface_TransformUpdateOptimization 接口,该接口定义以下方法:
必须实现:
IsActorValidForTransformOptimization():判断Actor是否适合进行Transform优化OnRegisteredToTransformOptimization():注册成功通知OnUnregisteredFromTransformOptimization():注销成功通知
可选实现:
GetTransformUpdateListenComponent():返回用于监听Transform变化的组件,若返回nullptr则使用RootComponent
四、Transform变化监听机制
4.1 监听绑定策略
双重监听机制:
-
RootComponent变化监听:
- 绑定
IsRootComponentChanged委托 - 当RootComponent被替换时触发回调
- 用于重新绑定Transform更新回调到新的RootComponent
- 绑定
-
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主流程
执行步骤:
-
早期退出检查:
- 检查优化功能是否启用
- 检查注册Actor数量是否为0
- 即使提前返回也要累加清理计时器
-
设置执行标志:
- 设置
bIsTicking = true防止回调重入
- 设置
-
清理无效引用:
- 累加清理计时器
- 清理DirtyActors中的无效引用(每帧执行)
- 分帧清理RegisteredActors和TransformUpdateHandles中的无效引用(定时执行)
-
处理Dirty Actors:
- 调用
ProcessDirtyActors处理所有待更新的Actor
- 调用
-
广播PostTick委托:
- 广播
PostTickDelegate,允许外部系统在Transform更新完成后执行逻辑
- 广播
-
清除执行标志:
- 设置
bIsTicking = false
- 设置
5.3 Dirty Actors处理流程
步骤分解:
-
转换并清空集合:
- 将DirtyActors转换为数组
- 立即清空DirtyActors集合,接收新的变化
-
分离已缓存和未缓存Actor:
- 遍历DirtyActors数组
- 根据缓存标志位
bIsCached分类 - 分别添加到UncachedActors和CachedActors数组
-
处理未缓存Actor:
- 调用
ProcessUncachedActors - 执行层级更新和首次缓存
- 调用
-
处理已缓存Actor:
- 调用
ProcessCachedActors - 利用缓存信息进行高效的并行更新
- 调用
六、未缓存Actor处理
6.1 处理流程
针对未缓存Actor(首次更新或缓存失效),执行以下操作:
并行处理(ParallelFor):
- 验证Actor有效性
- 执行层级Transform更新(
UpdateActorTransformsHierarchical) - 缓存组件信息(
CacheActorComponents)
6.2 层级Transform更新
递归收集组件:
- 从RootComponent开始,递归遍历所有AttachChildren
- 按父子关系顺序收集到数组(父组件在前,子组件在后)
手动更新Transform:
- 遍历所有收集的组件
- 设置
bSkipUpdateChildTransform = true(确保跳过自动更新) - 调用
ManualUpdateComponentToWorld手动更新Transform
6.3 组件缓存
缓存计算:
- 获取或创建Actor的缓存结构
- 清空旧缓存
- 收集所有SceneComponent
- 计算RootComponent的WorldTransform
- 为每个组件计算相对于RootComponent的Transform并缓存
相对Transform计算:
FTransform RelativeToRoot = Component->GetComponentTransform().GetRelativeTransform(RootToWorld);七、已缓存Actor处理
7.1 执行单元构建
预处理阶段:
- 遍历所有已缓存Actor
- 统计总组件数量,一次性预留内存
- 清空并预留
CachedComponentExecutionUnits数组
构建执行单元:
- 遍历每个已缓存Actor
- 获取当前RootComponent的WorldTransform
- 将每个组件封装为执行单元,包含:
- Actor弱引用
- 组件指针
- 相对Transform(从缓存获取)
- RootToWorld(当前帧实时值)
7.2 并行Transform更新
并行处理(ParallelFor):
- 验证Actor和组件有效性
- 计算新的WorldTransform:
FTransform NewWorldTransform = RelativeToRootTransform * RootToWorld; - 直接设置
ComponentToWorld:Component->SetComponentToWorld(NewWorldTransform); - 标记渲染Transform为脏:
Component->ManualMarkRenderTransformDirty(); - 执行延迟渲染更新:
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分析性能瓶颈和优化效果