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

2020-06-17 06:55:03易采站长站整理

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

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


void Time::EnableHighResolutionTimer(bool enable) { 
  base::AutoLock lock(g_high_res_lock.Get()); 
  if (g_high_res_timer_enabled == enable) 
    return; 
  g_high_res_timer_enabled = enable; 
  if (!g_high_res_timer_count) 
    return; 
  // Since g_high_res_timer_count != 0, an ActivateHighResolutionTimer(true) 
  // was called which called timeBeginPeriod with g_high_res_timer_enabled 
  // with a value which is the opposite of |enable|. With that information we 
  // call timeEndPeriod with the same value used in timeBeginPeriod and 
  // therefore undo the period effect. 
  if (enable) { 
    timeEndPeriod(kMinTimerIntervalLowResMs); 
    timeBeginPeriod(kMinTimerIntervalHighResMs); 
  } else { 
    timeEndPeriod(kMinTimerIntervalHighResMs); 
    timeBeginPeriod(kMinTimerIntervalLowResMs); 
  } 

其中,kMinTimerIntervalLowResMs = 4,kMinTimerIntervalHighResMs = 1。timeBeginPeriod 以及timeEndPeriod 是 Windows 提供的用来修改系统 timer interval 的函数。也就是说在接电源时,我们能拿到的最小的 timer interval 是1ms,而使用电池时,是4ms。由于我们的循环不断地调用了 setTimeout,根据 W3C 规范,最小的间隔也是 4ms,所以松口气,这个对我们的影响不大。

又一个精度问题

回到开头,我们发现测试结果显示,setTimeout 的间隔并不是稳定在 4ms 的,而是在不断地波动。而http://marks.lrednight.com/test.html#48 测试结果也显示,间隔在 48ms 和 49ms 之间跳动。原因是,在 Chromium 和 Node.js 的 event loop 中,等待 IO 事件的那个 Windows 函数调用的精度,受当前系统的计时器影响。游戏逻辑的实现需要用到 requestAnimationFrame 函数(不停更新画布),这个函数可以帮我们将计时器间隔至少设置为 kMinTimerIntervalLowResMs(因为他需要一个16ms的计时器,触发了高精度计时器的要求)。测试机使用电源的时候,系统的 timer interval 是 1ms,所以测试结果有 ±1ms 的误差。如果你的电脑没有被更改系统计时器间隔,运行上面那个#48的测试,max可能会到达48+16=64ms。