反射重置:把UPROPERTY拷回模板不等于恢复Actor
自动关联目录:反射重置:把UPROPERTY拷回模板不等于恢复Actor
核心判断:反射可以恢复一部分声明式值,却看不见 TimerManager、外部 Registry、网络通道和大量原生运行时状态。对象池完成的判据不能是“属性像模板”,而应是“上一代已静默,新一代可重新构建”。
一、业务冲突:通用对象池既想零业务接入,又要像重新 Spawn
大型角色和交互物包含大量蓝图属性、容器、默认子对象、碰撞组件与复制字段。要求每个类型手写完整 Reset 容易漏项;直接 Destroy/Spawn 又会重新支付对象创建、组件注册和脚本初始化成本。通用反射恢复因此很有吸引力:把回收对象的属性拷回基线,似乎就能得到一只“新 Actor”。
二、实际实现:这是有工程深度的混合方案
实际实现没有只做 SetHidden。池会在 World 中创建隐藏模板,保存各 PrimitiveComponent 的碰撞模式;回池时先调用业务钩子,删除临时音频组件,通知可池化组件,修正物理 Body 所有者,销毁 Physics State,关闭碰撞与 Tick,解除挂接、清理 Overlap,并从网络 Actor 集合中移除。
之后才进入反射恢复:递归处理 Struct、Array、Set、Map、资产引用和默认子对象;对复制属性调用 Push Model 脏标记;恢复工作还被放进独立队列,按帧限制对象数,完成后才进入可借队列。这个方向有三个明显优点:
- 先静默运行时,再恢复数据,顺序正确;
- Reset 队列与 Ready 队列分离,半恢复对象不会正常借出;
- 复制字段恢复后主动标脏,意识到了“本地值改变”不等于网络系统已经感知。
三、代码本身已经暴露了反射边界
实现对动态多播委托保留了未完成分支,对弱对象引用选择跳过,对运行时 UObject 指针只能按资产、默认子对象和普通运行时对象分类处理,并允许业务提供默认子对象黑名单。角色网格相对变换还需要反射后专门矫正。
这些都不是偶然例外,而是在说明四类结构性限制:
- 基线不纯:World 中的模板 Actor 已经过 Spawn、组件初始化和池化回调,它是一个运行实例,不天然等于创作默认值;
- 范围过宽:遍历完整 PropertyLink 会触及继承来的引擎状态和 Transient 字段,黑名单只能追着例外增长;
- 范围又不够:Timer、原生句柄、异步请求、外部系统注册、导航占用和网络 Channel 不在 UPROPERTY 图里;
- 复制不等于重建:物理实例、动画实例、渲染代理等对象需要按 UE 生命周期重建,写回字段不能替代对应 API。
四、目标方案:Reset 是五阶段协议,反射只是第三阶段的工具
enum class EResetStage { Quiesce, Detach, Restore, Rebuild, Verify, Ready };
bool ResetForPool(FResetContext& Ctx) {
Ctx.Advance(Quiesce);
CancelTimersAsyncAIAndAudio(Ctx.Generation);
Ctx.Advance(Detach);
UnregisterSpatialSignificanceAndNetwork();
ClearDelegatesAttachmentsAndOverlaps();
Ctx.Advance(Restore);
RestoreAuthoredFields(ResetDescriptor); // 白名单,不扫全部 PropertyLink
Ctx.Advance(Rebuild);
RecreatePhysicsAndRuntimeComponentsForDormantState();
Ctx.Advance(Verify);
return VerifyNoLiveTasks() && VerifyNoExternalMembership()
&& VerifyDormantCollision() && VerifyReplicationBaseline();
}声明式基线优先来自 CDO、构造参数和显式 ResetDescriptor,而不是会运行逻辑的模板 Actor。反射仍可用于批量恢复纯数据字段,但采用白名单:业务身份、配置值、复制初值和确定可重建的默认子对象可以自动处理;委托、对象所有权、引擎内部状态和外部资源必须显式处理。
Reset 失败的对象不能进入 Ready 队列。开发版本应输出失败 Stage、残留 Timer 数、外部注册数和首个不一致字段;线上可直接销毁该对象并补充池容量,避免为保一次复用污染后续所有生命周期。
五、取舍与验证
| 方案 | 优点 | 代价与边界 |
|---|---|---|
| 全量反射拷贝 | 接入快、覆盖蓝图字段 | 误拷引擎状态,仍漏运行时协议 |
| 每类手写 Reset | 语义最准确 | 重复代码多,容易随字段演进漏改 |
| 分层协议 + 描述符 | 通用停机与业务恢复各归其主 | 需要建立 Reset 审查和验证工具 |
故障注入应覆盖动态多播委托、局部 Timer、软资源晚到、运行时对象引用、挂接子 Actor、Overlap、物理模拟、Push Model 字段和外部空间索引。借出前比较的不只是属性,还包括“活动任务为零、外部成员关系为零、物理和网络处于约定休眠态”。
六、反思
这套实现最值得肯定的不是反射技巧,而是已经形成“业务停机、物理静默、分帧恢复、Ready 隔离”的雏形。真正需要批判的是把属性相等继续外推成对象等价。Actor 是多套 UE 子系统共同维护的运行实体,不是一块可以 memcpy 回默认值的结构体。