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


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

使用 Chromium 的 setTimeout 实现,我们可以将 setTimeout(fn, 1) 的误差控制在 4ms 左右,而 setTimeout(fn, 48) 的误差可以控制在 1ms 左右。于是,我们的心中有了一幅新的蓝图,它让我们的代码看起来像是这样:

复制代码 代码如下:


/* Get the max interval deviation */ 
var deviation = getMaxIntervalDeviation(bucketSize); // bucketSsize = 48, deviation = 2; 
function gameLoop() { 
  var now = Date.now(); 
  if (previousBucket + bucketSize <= now) { 
    previousBucket = now; 
    doLogic(); 
  } 
  if (previousBucket + bucketSize - Date.now() > deviation) { 
    // Wait 46ms. The actual delay is less than 48ms. 
    setTimeout(gameLoop, bucketSize - deviation); 
  } else { 
    // Busy waiting. Use setImmediate instead of process.nextTick because the former does not block IO events. 
    setImmediate(gameLoop); 
  } 

上面的代码让我们等待一个误差小于 bucket_size( bucket_size – deviation) 的时间而不是直接等于一个 bucket_size,46ms 的 delay 即便发生了最大的误差,根据上文的理论,实际间隔也是小于48ms的。剩下的时间我们使用忙等待的方法,确保我们的 gameLoop 在足够精确的 interval 下执行。

虽然我们利用 Chromium 在一定程度上解决了问题,但这显然不够优雅。

还记得我们最初的要求吗?我们的服务器端代码是应该可以脱离 Node-Webkit 客户端的,直接在一台有 Node.js 环境的电脑中运行。如果直接跑上面的代码,deviation 的值至少是16ms,也就是说在每一个48ms中,我们要忙等待16ms的时间。CPU使用率蹭蹭蹭就上去了。

意想不到的惊喜

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

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