Actor迁移:先抽离真相,再替换运行时表示

自动关联目录:Actor迁移:先抽离真相,再替换运行时表示

核心判断:从全 Actor 迁移到数据化运行时,最危险的不是慢,而是快路径漏掉旧路径隐含的不变量;迁移顺序必须先抽离身份和真相,再替换投影。

一、业务冲突

Actor/Component 天然适合 UE 交互、碰撞和编辑,但海量内容若把每个逻辑对象都永久物化为 Actor,会同时支付 UObject、组件注册、Tick、复制、物理和 GC 成本。直接改成批量 Mesh 或数据表又会破坏旧系统依赖的对象身份、附着关系、角色排除规则和物理所有权。

二、实际实现

早期实现由多个玩法管理器维护逻辑 ID 到 Actor 或弱引用集合,查询和更新天然围绕 Actor 展开。后续实现引入逻辑对象与原生数据注册表,再由 Actor Bridge 把对象 ID 映射到 Actor 或组件别名,开始把“对象存在”与“Actor 存在”分离。

网格对象还有一条优化初始化快路径。它在直接提交前检查注册表身份、当前 Actor 绑定、所属 World、角色物理体排除、结构附着、物理所有者、Transform Lease 和对象 Generation;任何关键事实缺失就回退到原命令路径,而不是猜测成功。

三、值得保留

这是合理的绞杀式迁移:旧路径继续承担兼容基线,新路径只有证明语义等价后才加速。显式回退比一次性替换更适合复杂 UE 运行时,也让性能收益不会凌驾于正确性。

四、结构性缺陷

全局管理器和裸对象 ID 仍不足以区分两个 World 中的同名对象;多个逻辑 ID 可以别名到同一个 Actor,反向查询必须知道期望种类。快路径检查若散落在 Bridge 内,会与旧命令路径分别演进,最终出现“都能成功但结果不同”。而只替换 Spawn、不替换查询和所有权,实际上仍让 Actor 充当真相。

五、目标设计

目标是 World 作用域的实体存储与可丢弃投影:

struct EntityKey { SessionId session; WorldGeneration world; EntityId id; };

class EntityStore { // 权威或本地确认的值状态
    EntityRecord Read(EntityKey);
    Receipt Apply(Command, ExpectedRevision);
};

class ProjectionRegistry { // UE 表示,可销毁重建
    ProjectionHandle Find(EntityKey, ProjectionKind);
    CommitResult Materialize(EntityRecord, ProjectionPlan);
};

迁移顺序是:统一作用域身份,抽离模型事实,替换查询入口,再接入 Actor/Component/ISM 等投影,最后按显著性选择性物化。新旧路径并行期对同一命令做影子执行,比较状态摘要、附着、碰撞、可见性和回执,而不是只比较有没有 Actor。

六、取舍与验证

双路径会增加短期复杂度,影子执行也消耗资源,因此只在抽样内容和测试环境开启。验证应覆盖 Actor 销毁重建、两个 World 同 ID、多逻辑对象别名、组件与 Actor 两种投影、批量快路径回退、角色物理体排除和命令重放。性能验收同时看 Actor/组件数量、注册与 GC 峰值、主线程预算和语义差异率;只有差异长期为零,旧路径才能删除。