Node.js 制作实时多人游戏框架(4)

按照上述步骤,假设我们设置了一个 timeout = 1ms 的计时器,poll 函数会最多阻塞 1ms 之后恢复(如果期间没有任何 IO 事件)。在继续进入 event loop 循环的时候, uv_update_time 就会更新时间,然后uv_process_timers 发现我们的计时器到期,执行回调。所以初步的分析是,要么是uv_update_time 出了问题(没有正确地更新当前时间),要么是 poll 函数等待 1ms 之后恢复,这个 1ms 的等待出了问题。

查阅 MSDN,我们惊人地发现对 GetTickCount 函数的描述:

The resolution of the GetTickCount function is limited to the resolution of the system timer, which is typically in the range of 10 milliseconds to 16 milliseconds.
 
GetTickCount 的精度是如此的粗糙!假设 poll 函数正确地阻塞了 1ms 的时间,然而下一次执行uv_update_time 的时候并没有正确地更新当前 loop 的时间!所以我们的定时器没有被判定为过期,于是 poll 又等待了 1ms,又进入了下一次 event loop。直到终于 GetTickCount 正确地更新了(所谓15.625ms更新一次),loop 的当前时间被更新,我们的计时器才在 uv_process_timers 里被判定过期。

向 WebKit 求助
Node.js 的这段源码看得人很无助:他使用了一个精度低下的时间函数,而且没有做任何处理。不过我们立刻想到了既然我们使用 Node-WebKit,那么除了 Node.js 的 setTimeout,我们还有 Chromium 的 setTimeout。写一段测试代码,用我们的浏览器或者 Node-WebKit 跑一下: (#后面跟的数字表示需要测定的间隔),结果如下图:

Node.js 制作实时多人游戏框架

按照 HTML5 的规范,理论结果应该是前5次结果是1ms,以后的结果是4ms。测试用例中显示的结果是从第3次开始的,也就是说表上的数据理论上应该是前3次都是1ms,之后的结果都是4ms。结果有一定的误差,而且根据规定,我们能拿到的最小的理论结果是4ms。虽然我们不满足,但显然这比 node.js 的结果让我们满意多了。强大的好奇心趋势我们看看 Chromium 的源码,看看他是如何实现的。(https://chromium.googlesource.com/chromium/src.git/+/38.0.2125.101/base/time/time_win.cc)

首先,在确定 loop 的当前时间方面,Chromium 使用了 timeGetTime() 函数。查阅 MSDN 可以发现这个函数的精度受系统当前 timer interval 影响。在我们的测试机上,理论上也就是上文中提到过的 1.001ms。然而 Windows 系统默认情况下,timer interval 是其最大值(测试机上也就是 15.625ms),除非应用程序修改了全局 timer interval。

如果你关注 IT界的新闻,你一定看过这样的一条新闻。看起来我们的 Chromium 把计时器间隔设定得很小了嘛!看来我们不用担心系统计时器间隔的问题了?不要开心得太早,这样的一条修复给了我们当头一棒。事实上,这个问题在 Chrome 38 中已经得到了修复。难道我们要使用修复以前的 Node-WebKit?这显然不够优雅,而且阻止了我们使用性能更高的 Chromium 版本。

进一步查看 Chromium 源码我们可以发现,在有计时器,且计时器的 timeout < 32ms 时,Chromium 会更改系统的全局定时器间隔以实现小于 15.625ms 精度的计时器。(查看源码) 启动计时器时,一个叫HighResolutionTimerManager 的东西会被启用,这个类会根据当前设备的电源类型,调用EnableHighResolutionTimer 函数。具体来说,当前设备用电池时,他会调用EnableHighResolutionTimer(false),而使用电源时会传入 true。EnableHighResolutionTimer 函数的实现如下:

复制代码 代码如下:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wgxgzp.html