多人同步与实体所有权
返回总览。本篇沿脚本广播、原生数组同步、网络实体和事件链路分析。所有性能讨论都是机制分析,未采样带宽、测延迟或运行多人环境。
1. 三种同步平面#
| 平面 | 典型内容 | 源码入口 | 不应承担的职责 |
|---|---|---|---|
| 脚本广播变量 | 任务阶段、选中变体、队伍、网络实体 ID、玩家参与状态 | NETWORK_REGISTER_HOST/PLAYER_BROADCAST_VARIABLES | 复制每一个本地临时变量 |
| 网络实体复制 | 角色、车辆、物体及同步树中的状态 | CNetworkObjectMgr、SyncTree、clone | 决定所有业务奖励和解锁 |
| 网络/脚本事件 | 操作请求、通知、参与者变化、一次性交互 | SEND_TU_SCRIPT_EVENT、事件队列 | 作为迟加入恢复所需的唯一事实来源 |
另有 NetworkScriptWorldStateManager 及道路、scenario 阻塞区、人口倍率区等 world state 类型,表明“场景级规则变化”也不应都包装成一个可见实体。[S9]
2. 广播数据是显式登记的契约#
freemode.sc 登记多个 GlobalplayerBD_* 和 GlobalServerBD_* 块,包括任务请求、任务列表、排除区域、活动选择器、房产实例、自由模式事件、随机事件等。[S1,L15093 起]
主机块表达脚本级共享事实,玩家块表达按参与者维护的状态。未登记的本地变量不会因为脚本在多人模式运行就自动同步。登记地址、大小、顺序和结构布局均属于双方解释数据的契约。
Native 实现把登记交给 script Handler 的 network component,再由 netArrayManager_Script 建立相应 array handler。原生层区分 host data 和 player data,记录 handler 序号及高频更新属性,并有重复登记检查。[S2][S3]
这不是“每帧把整份 freemode 全局内存广播一遍”。数组系统具有脏元素、消息大小估算、WriteUpdate/ReadUpdate、ACK 和 split-update ACK 等机制。[S4] 也不能据 SIZE_OF 直接报线上字节数;例如事件 Native 会把脚本 word 数乘以 sizeof(scrValue),其他通道还涉及序列化和包头。
3. 主机迁移不是重新开始任务#
MigrateScriptHost 发送迁移消息、设置 pending host 和迁移计时器,并暂时清空当前 host。主动迁移条件包含本地为 host、存在其他参与者、没有 pending host、没有迁移阻止等;这些是该入口的条件,不等于所有失联恢复都使用完全相同规则。[S3,L612]
最有价值的带宽设计出现在 netArrayHandlerBase::HandleChangeOfArbitration:当仲裁者变化时,为避免把全部数组状态重新发给所有玩家造成峰值,客户端向新仲裁者发 checksum,仅对不一致者补状态;代码同时保留让新加入者获得此前修改元素的机制。[S4,L1065]
由此可得内容层的要求:全局推进所需事实必须存在于接手者可获取的状态中;纯本地临时计时器要能重建或重新初始化;“我刚成为 host”不能无条件把已进行一半的任务重置到起点。checksum 是一致性修复工具,不等于对所有恶意或业务错误的验证。
4. 实体 owner 与脚本 host 不同#
CNetworkObjectMgr 负责注册本地网络对象、创建远端 clone、同步/移除 clone、处理 ACK,并跟踪等待 owner 变化的对象。[S5]
NETWORK_REQUEST_CONTROL_OF_ENTITY 是请求;NETWORK_HAS_CONTROL_OF_ENTITY 才查询当前控制条件。脚本在请求后仍应等待和验证,不能据一次请求调用直接认为已取得写权限。[S2]
图省略了 H 与 O 同机的常见情况。具体 Native 也可能替脚本转交远端操作:GivePedScriptedTask 对 clone/待迁移 Ped 调用网络处理路径。因此不能把“非 owner 调用必然完全无效”当成所有接口的统一规则。[S6]
创建时又有额外区分:CREATE_VEHICLE 的网络对象标志与 script-host object 标志不同。要求 host object 的创建会检查当前网络脚本的 host 身份。实体可见、存在、由本机拥有和由本脚本管理,也应分别判断。
5. 事件通道与 API 版本细节#
net_process_events.sch 处理加入/离开脚本、广播变量更新以及大量业务事件,通过 GET_EVENT_DATA 按结构读取。[S7] 事件适合通知和请求,但不能把“已经发送”视为全体客户端已完成对应动作,也不能假定它天然保存所有历史。
当前源码中应特别关注 API 演进:旧 TRIGGER_SCRIPT_EVENT 原生实现带有在线时“不再使用此命令”的非 FINAL 断言;实际脚本可见 SEND_TU_SCRIPT_EVENT,其注册在 commands_script.cpp 中绑定到 CommandWinterIsComing。不能根据函数名字寻找不到实现就断定接口缺失。[S8]
原生实现把脚本 word 大小换成字节,处理本地与远端接收者位掩码,并检查活动玩家范围。业务处理器仍需验证事件对应的玩家、活动实例、目标状态;传输成功不等于业务请求合法。本文未对全部事件处理器进行安全审计。
6. 预算、频率与内容规模#
网络对象管理器有 clone create/remove cap;带宽管理器提供目标带宽、节流与更新逻辑;在线事件启动器还会预留角色、车辆、物体额度。[S5][S10] 三者分别限制对象生命周期突发、传输量和活动资源需求,不能互相替代。
对内容设计的直接影响是:
- 广播“阶段、目标 ID、必要参数”,把纯视觉插值和界面细节留本地。
- 避免在可重复执行的初始化中反复清空/重写巨大广播结构。
- 大量生成和销毁应评估对所有附近玩家造成的复制突发。
- 结束活动时既要停止广播和参与关系,也要移交/删除实体并撤销世界覆盖。
- 新增字段要核对所有登记方和读写方,不把结构兼容性当成本地编译器的问题。
本地代码证明存在分包、ACK、校验和迁移恢复机制;它并不直接提供可用于本项目的实测“每玩家多少 KB/s”结论。
源码证据#
| 编号 | 位置 |
|---|---|
| [S1] | freemode.sc L15093–15174 广播登记 |
| [S2] | commands_network.cpp L4123 网络脚本;L4332 广播;L5809/5879 控制权 |
| [S3] | scripthandlernetcomponent.cpp L612 迁移;L1672/1739 广播登记 |
| [S4] | netarrayhandler.cpp L297/415 更新;L516/579 ACK;L968 dirty;L1065 仲裁变化 |
| [S5] | NetworkObjectMgr.h L67 注册;L236–282 clone;L314 待转移 owner |
| [S6] | script_cars_and_peds.cpp L1261 网络任务转交 |
| [S7] | net_process_events.sch L522、L914、L1067;L6278 TU 事件发送 |
| [S8] | commands_script.cpp L478 数据大小;L493 旧接口;L731 起 TU 路径;L862 注册 |
| [S9] | network/General/ScriptWorldState 世界状态管理与具体类型 |
| [S10] | NetworkBandwidthManager.cpp L83 初始化;L97 更新 |
[S1]: <../../../GTAV Source/script/dev_ng/multiplayer/scripts/modes/freemode/freemode.sc> [S2]: <../../../GTAV Source/src/dev_ng/game/script/commands_network.cpp> [S3]: <../../../GTAV Source/src/dev_ng/rage/framework/src/fwscript/scripthandlernetcomponent.cpp> [S4]: <../../../GTAV Source/src/dev_ng/rage/framework/src/fwnet/netarrayhandler.cpp> [S5]: <../../../GTAV Source/src/dev_ng/game/network/Objects/NetworkObjectMgr.h> [S6]: <../../../GTAV Source/src/dev_ng/game/script/script_cars_and_peds.cpp> [S7]: <../../../GTAV Source/script/dev_ng/multiplayer/include/public/net_process_events.sch> [S8]: <../../../GTAV Source/src/dev_ng/game/script/commands_script.cpp> [S9]: <../../../GTAV Source/src/dev_ng/game/network/General/ScriptWorldState> [S10]: <../../../GTAV Source/src/dev_ng/game/network/Bandwidth/NetworkBandwidthManager.cpp>