Quake2游戏循环时间测量中Sys_Milliseconds掩码设计原因
Quake2
Sys_Milliseconds 掩码设计说明 0xffff0000 掩码的设计意图
这个掩码操作是改动成本极低的兼容性+熵增设计,对应两个核心作用:
- 大幅降低32位有符号整数溢出的触发概率
Win32 APItimeGetTime()返回的是系统启动后经过的毫秒数,为32位无符号值,周期约49.7天回绕。而Quake2全链路的时间存储变量全是int(32位有符号整型),正数上限仅为2^31-1,对应约24.8天。如果直接存储timeGetTime()的返回值,只要系统启动超过24.8天,返回值就会超出有符号整型正数范围,被解析为负数,后续计算newtime - oldtime的时间差会出现完全错误的结果,直接导致物理步进、帧率计算、网络同步逻辑崩溃。
用timeGetTime() & 0xffff0000计算base时,会把第一次调用时的时间值低16位清零,后续返回的curtime = timeGetTime() - base初始值落在0~65535区间(仅保留当前时间的低16位),相当于把时间计数的零点拉到距离第一次调用不足65.5秒的位置,curtime要涨到有符号整型上限需要游戏连续运行约24.8天——在90年代的PC使用场景下,几乎没有用户会连续运行一款游戏24天不重启,直接把溢出bug的触发概率压到了可忽略的程度。 - 保留初始时间值的随机熵
代码注释里提到的「保留16位有效随机数据」就是这层作用:Quake2的随机数种子初始化、客户端服务器初始时间偏移校验、早期反作弊逻辑都会用到启动初期的时间值做熵源。如果直接把base设为第一次调用的timeGetTime()返回值,第一次返回的curtime永远是0,启动后短时间内的时间值都很小,熵极低,容易被预测。而掩码操作后,第一次返回的curtime是调用时刻时间值的低16位,数值在0~65535之间完全随机(用户启动游戏的时间点不可能精准对齐65536毫秒的整数倍),不需要额外写随机逻辑就补了16位的熵,且完全不影响时间差的计算结果(时间差计算时base会被抵消,(newtime-base)-(oldtime-base) = newtime-oldtime)。
为什么不直接调用timeGetTime()实现时间计算
给出的简化实现逻辑上看似等价,实际存在两个硬伤:
- 溢出bug触发门槛太低:如前面所说,直接用
int存储timeGetTime()返回值,只要系统开机超过24.8天就会触发溢出,不需要游戏长时间运行,很多长期不关机的服务器或者工作站用户一启动游戏就会碰到时间计算错误。 - 初始时间值熵不足:直接用原始
timeGetTime()做差时,虽然时间差计算正确,但上层逻辑能拿到的初始时间值是系统启动后的绝对时间,在系统刚启动的场景下数值很小,随机性差,容易被预测,会影响随机数生成和早期反作弊逻辑的可靠性。
另外补充一点:当时如果选择把全链路的时间变量从int改成无符号整型来解决溢出问题,需要改动几百处上层代码逻辑,引入新bug的风险极高,而这个掩码加偏移的方案只需要修改Sys_Milliseconds一个函数,是成本最低的兼容方案。
内容的提问来源于stack exchange,提问作者darro911
相关产品推荐
相关产品推荐

