反射重置:把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 脏标记;恢复工作还被放进独立队列,按帧限制对象数,完成后才进入可借队列。这个方向有三个明显优点:

  1. 先静默运行时,再恢复数据,顺序正确;
  2. Reset 队列与 Ready 队列分离,半恢复对象不会正常借出;
  3. 复制字段恢复后主动标脏,意识到了“本地值改变”不等于网络系统已经感知。

三、代码本身已经暴露了反射边界

实现对动态多播委托保留了未完成分支,对弱对象引用选择跳过,对运行时 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 回默认值的结构体。