复制路由: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 数、重复候选数、未路由原因、路由迁移失败数和迁移后首个有效更新延迟。路由层只负责缩小候选集,不能宣称自己已经解决带宽预算。