网络传输、对象复制、带宽治理与运动平滑
本文研究引擎/Framework 客户端网络机制,不将其当作完整在线后端、权威服务器或实际线上配置说明。静态研究,未联网运行。入口:Summary。
1. 从字节可靠性到对象一致性#
Base net 提供连接、序列、确认、重发及平台网络适配;Framework fwnet 提供对象、同步树、事件、数组、所有权迁移、带宽和 blender。游戏层决定对象类型、玩家关系和同步内容。能把字节送达,不等于对象已创建、依赖已驻留或远端物理状态已经收敛。
2. 可靠传输仍有容量与时间限制#
netConnection 保存 outbound/inbound sequence、乱序 ACK bits、未确认和重发队列,公开 oldest resend 和 queue length。低内存时优先丢弃 outbound unreliable;为 reliable 腾空间时也会尝试释放已排队 unreliable,最终内存仍不足则排队失败。
因此 reliable 的含义是传输策略,不是无限队列或永不失败。业务层必须处理入队失败、连接断开及消息过期。对状态复制,过旧的状态即使可靠到达也可能失去价值;对创建/销毁或交易类操作,重试则需配合唯一身份和去重。后者是新项目建议,不是本次对完整业务协议的实证。
网络慢连接可能把内存压力传回主线程。观察总带宽还不够,至少要同时观察未确认年龄、重发次数、队列字节和每玩家积压;否则“平均吞吐正常”会掩盖一个玩家拖慢对象生命周期的问题。
3. Sync tree 是紧凑 schema,不是场景树#
Parent node 表示子树是否有内容,Data node 表示一个数据块。把常常同时出现的数据组织到子树,可减少更新 guard bits,并支持增加/重排字段。它按序列化模式区分创建、更新、迁移、关键更新、checksum/verify 等需求,而不是每次发送整个 C++ 对象内存。
树的结构和对象状态分开:每种对象类型只有一个静态树,per-object netSyncData 在对象上,树记录当前处理对象。netSyncTree::Write 实际调用 SetTargetObject(pObj),再写 activation flags、清除 updated flags 并遍历根节点。这是明确的可重入约束:不应直接让多个 worker 同时通过同一树实例写不同对象。若要并行化,需把可变上下文从共享 schema 中移出,或保证实例隔离及同步。
树结构改变也可能改变 wire layout。所谓“易于新增节点”是维护便利,不意味着跨版本自动兼容;新项目需要 schema version、能力协商或稳定字段标识。
4. Dirty、Write、ACK 是三个阶段#
Update/DirtyNode 管理待同步状态;Write 将本次可发送内容写进有限 bit buffer;ProcessAck 按玩家、序列、发送时间和 node flags 确认具体数据单元。ProcessAck 仅对被确认的节点处理 ReceivedAck,并在序列匹配时清除相应未确认序列,随后更新对象同步状态。
Write 会为 header bits 预留空间,并有 m_FailedToWriteAllData 状态。因此“对象已调用 Write”不能等于“对象所有脏数据已发送”;“传输收到 ACK”也不能简单清除对象的所有新变化。更新时钟、节点版本和每玩家状态需要配合,防止旧确认吞掉较新的修改。
这种粒度比整对象重发更省带宽,但引入每玩家每节点的内存及 bookkeeping。对象数、节点数和玩家数相乘后,复制元数据本身也可能成为显著成本。
5. 用批次平摊复制工作#
netSyncTree 维护 update level、允许批次数、各批对象数和下一更新时间;新对象选择较少对象的批,迁移批次有差值阈值。UpdateCurrentObjectBatches 到期后推进当前批和下一批。
这避免所有对象同帧刷新,却也把发送时延与批次安排关联。按对象数量均衡不等于按真实序列化成本均衡:一个状态频繁变化的大对象可能重于多个静态对象。新项目可保留分层频率,但用字节/CPU 估计成本、最大状态年龄和重要性共同治理。
netBandwidthMgr 提供每玩家 CanSendData、发送间隔、目标上行、失败均值与 reliable/unreliable throttle 检查。头文件中的带宽常量及默认间隔只是这个实现的配置起点,不能当作 GTA Online 实际线上带宽、tick rate 或玩家规模的证明。
6. 所有权和运动平滑解决不同问题#
对象管理与 reassignment 负责谁发送/管理状态,blender 负责接收端如何接近目标。netBlenderLinInterp 的历史/当前快照分别存位置、速度以及各自时间戳,支持最大预测时间、平滑时间、速度误差模式、停止时间和屏幕内外误差阈值。Owner change 有专门重置入口。
高精度模式注释明确以运动平滑为代价换取更紧的同步。这揭示不可消除的取舍:短缓冲和紧跟状态减少位置误差,却更容易显露抖动;较长平滑窗改善观感,却增加响应迟滞。预测在碰撞、急转和权威切换时更容易失效,最大预测时间防止无限外推。
这些接口证明插值/预测与修正能力,不能据此宣称完整 rollback、跨机器确定性或统一 server-authoritative 模型。尤其物理并未证明确定性,同步架构必须容纳状态差异。
7. 与大世界生命周期的交点#
远端对象存在、模型已驻留、物理碰撞已接入、动画可求值、复制所有权有效,是不同条件。网络接收不能假定资源即时可用,卸载也不能忽视仍未完成的对象创建、迁移或引用。游戏层需要决定等待、降级或取消策略。
新项目应使用稳定对象 ID/世代号区分“旧包里的对象”与“同槽位新对象”,把序列化 schema、权威规则、资源可用状态和显示平滑分层。此为设计建议;本报告未逐条审计所有游戏网络对象的陈旧包防护实现。
8. 建议验证与证据#
建议在授权测试环境分别改变 RTT、抖动、丢包、乱序、上行限制和对象 churn,记录节点字节、同步年龄、queue age、迁移耗时、平滑位置误差与资源等待。优先检验“最终新状态到达”和“对象销毁后不复活”等语义,再优化码率。本次未执行这些实验。
| 文件 | 入口 |
|---|---|
| connection.h | 145–179 序列/ACK/低内存;重发与队列接口 |
| netsyncnode.h | 67–83 树目的、共享实例和对象数据分离 |
| netsynctree.h、cpp | cpp 138/240 批次;512 Write;868 ProcessAck |
| netbandwidthmgr.h | 间隔、每玩家预算和限流 |
| netblenderlininterp.h | 62–115 快照/平滑参数;139 所有权变化 |