复制路由:bReplicates只说明能复制,不说明发给谁
自动关联目录:复制路由:bReplicates只说明能复制,不说明发给谁
核心判断:
bReplicates只声明 Actor 可以进入复制系统,不能回答它应进入全局、Owner、组、空间、依赖还是兼容相关性集合。路由策略必须是可观测、可迁移、可校验的运行时事实。
一、业务冲突:同样是复制 Actor,可见范围完全不同
玩家自身状态、队友标记、世界机关、跟随附件和移动单位都需要网络同步,但接收者集合不同。若仍依赖逐 Actor、逐连接调用相关性函数,成本接近 O(Connection * Actor * Viewer);若把所有对象都放进 Always Relevant,又会把 CPU 和带宽问题推给 ActorChannel。
二、实际实现:按类建立路由表,运行时可以换路
工程把 Actor 分到不路由、全连接、Owner 连接、组连接、依赖 Actor、兼容相关性、空间静态、空间动态和休眠驱动等节点。类默认对象提供初始策略;Actor 已注册后若策略变化,会先从旧节点移除、改全局复制设置,再加入新节点。引擎合并候选列表后会跳过同帧已复制 Actor,因此重复进入多个节点通常不会重复发包。
这套设计值得保留:候选集选择集中在复制图,业务代码只上报语义变化;动态 Actor、静态 Actor 与 Dormancy 也拥有不同更新成本。
三、结构性缺陷:类默认值不是实例当前事实
Owner、组、Dormancy、Cull Distance 和运行状态都可能在实例级变化。只在注册时从类默认值推断,会让 Actor 已改变语义却仍留在旧节点;引擎最后的去重只能避免重复发送,不能消掉重复 Gather、列表内存和优先级歧义。
NotRouted 也不能只是安静地什么都不做。它既可能表示专用节点已接管,也可能表示配置遗漏;两者若没有原因码和审计,表现都是“客户端偶发看不见”。
四、目标设计:路由是一份带版本的实例描述
struct RouteDesc {
RouteKind primary; // Global / Owner / Group / Spatial / Special
SpatialMode spatial; // Static / Dynamic / DormancyDriven
GroupId group;
uint32 revision;
RouteReason reason;
};
bool ChangeRoute(Actor& a, RouteDesc next) {
RouteDesc old = Routes.Get(a);
if (!Validate(next, a)) return false;
Nodes.Detach(a, old); // 只移除本路由拥有的成员关系
Routes.Commit(a, next);
Nodes.Attach(a, next);
a.ForceNetUpdate();
return AuditExactlyOnePrimaryRoute(a);
}主路由应唯一,依赖边和紧急列表是显式附加关系;迁移失败要回滚旧路由。每次变化记录 actor、old、next、reason、revision,并提供“Actor 当前在哪些节点、为何在那里”的调试查询。
五、决策与验证
| 问题 | 正确归属 |
|---|---|
| 谁可能收到 Actor | 路由与每连接相关性 |
| 本帧是否轮到 | 调度与优先级 |
| 最终能否写入包 | NetDriver/Channel 预算 |
| 暂时不变化 | Dormancy |
| 永久停止权威复制 | TearOff 或销毁生命周期 |
验证必须覆盖注册前后修改 Owner、组、Dormancy 和 Cull Distance,统计每类节点 Actor 数、重复候选数、未路由原因、路由迁移失败数和迁移后首个有效更新延迟。路由层只负责缩小候选集,不能宣称自己已经解决带宽预算。