Old Light 是一款浏览器策略游戏,一个标签页可以连续打开好几天。客户端保存着它被允许看到的完整星系状态副本,服务器通过发送补丁来保证这份副本的诚实性:每一次变化都以 world.delta 消息的形式到达,客户端把它合并进自己已有的状态里。发送变化而不是重新发送状态,这是教科书式的增量编码。但真正的问题在于,一个游戏状态补丁里到底装了什么,以及为什么对手收到的补丁和你收到的不一样。
增量编码的常见做法在这里行不通
人们说到增量编码时,通常指的是字节差异:比较一个数据块的两个版本,只发送差异部分。这要求发送方知道接收方当前持有的是哪个版本。但一个向数千个套接字广播的游戏服务器承担不起这种成本;为每个客户端跟踪“最后已知状态”并在每次变化时都与之做差异比较,会比更新本身更昂贵。
所以 Old Light 的增量消息直接陈述玩家和星区的事实,而不是做字节差异。消息结构里包含几个可选字段:新增的玩家、被移除的玩家 ID、被更新的玩家和星区、标记为过期的星区坐标、发生变动的交易板,以及只有谈判双方才会收到的交易变动。最后两个字段不携带任何负载数据,它们只是说明某个界面发生了变化,持有该界面的客户端会去读取最新内容。这样一来,繁忙的市场就不会推送到那些根本没在看市场的套接字上。
同一条消息发给所有套接字
服务器可以向每个套接字发送完全相同的消息,而不需要知道它们各自当前持有什么状态,客户端则可以把这条消息应用到它已有的任何状态上。这条消息还精确地告诉渲染器需要重绘什么:玩家更新只涉及名单和 HUD,星区过期条目只涉及指定星区里的六边形格子。屏幕上的其他内容不会被重绘。
增量消息里的每一个值都是新的总量,客户端直接赋值,不需要和它已经持有的值做任何算术运算。当你的帝国分数发生变化时,负载里就包含这个分数,合并过程就是按 ID 做替换。绝对数值让补丁具备了幂等性:一条消息送达两次会落在同一个状态上;一条消息从未到达会让客户端保持陈旧但不会损坏,而同一实体的下一次更新会完整修复它,因为那次更新同样携带了完整的新值。
对手收到的补丁为什么不同
答案藏在消息结构的设计里。交易变动字段只发给谈判的双方,这意味着同一次游戏状态变化,不同客户端收到的 world.delta 消息内容并不相同。服务器不需要为每个客户端单独计算差异,它只需要根据消息类型决定这条补丁该发给谁。那些不涉及特定客户端的字段,比如玩家加入、玩家 ID 消失、玩家数据行变化、星区公开地图数据过期,可以原样广播给所有人。而那些只与特定客户端有关的字段,则只在相关套接字上出现。
这种设计把“谁需要知道什么”的判断从服务器端的差异计算中剥离出来,变成了消息结构本身的一部分。服务器仍然不需要知道每个客户端当前持有什么版本,它只需要知道这条消息属于哪一类,以及这一类消息应该到达哪些套接字。客户端收到消息后,按照字段类型决定合并方式和重绘范围,整个过程不需要任何版本协商。
热门跟贴