在Screeps中,是否以允许CPU限制强健代码的方式强制执行CPU限制?

The*_*Man 8 javascript screeps

在Screeps中,每个玩家对CPU的使用都是有限的,但是此功能文档并没有使得强制执行的方式足以清楚地编写CPU限制强健代码.我考虑过以下四种可能性:


1.玩家的周期永远不会中断.

在一个极端,玩家的内存反序列化,主脚本执行和内存重新序列化永远不会中断,超过CPU限制只是意味着玩家的循环将在后续的滴答中被跳过,直到CPU债务被偿还为止.在这种情况下,CPU限制强健代码并不是绝对必要的,但是检测播放器的循环何时被跳过并且可能更有效地开始执行操作仍然是有用的.这可以使用以下代码轻松实现:

module.exports.loop = function()
{
  var skippedTicks = 0;

  if ( 'time' in Memory )
  {
    skippedTicks = Game.time - Memory.time - 1;
  }

  // Main body of loop goes here, and possibly uses skippedTicks to try to do
  // things more efficiently.

  Memory.time = Game.time;
};
Run Code Online (Sandbox Code Playgroud)

这种管理玩家CPU使用率的方式容易受到无限循环的滥用,我几乎可以肯定这不是Screeps的行为.

2.玩家的周期是原子的.

下一个可能性是玩家的周期是原子的.如果超出CPU限制,则播放器的周期中断,但预定的游戏状态不会更改,也不会提交对内存的更改.在检测到中断循环时提高效率变得更加重要,因为忽略它意味着玩家的脚本将无法改变游戏状态或记忆.但是,检测中断的周期仍然很简单:

module.exports.loop = function()
{
  var failedTicks = 0;

  if ( 'time' in Memory )
  {
    failedTicks = Game.time - Memory.time - 1;

    // N failed ticks means the result of this calculation failed to commit N times.
    Memory.workQuota /= Math.pow( 2, failedTicks );
  }

  // Main body of loop goes here, and uses Memory.workQuota to limit the number
  // of active game objects to process.

  Memory.time = Game.time;
}
Run Code Online (Sandbox Code Playgroud)

2.5.对内存的更改是原子的,但对Game对象的更改则不是.

编辑:在阅读RawMemory对象的文档后,我发生了这种可能性.如果脚本被中断,则已提交已安排的任何游戏状态更改,但不会提交对内存的任何更改.考虑到RawMemory提供的功能,这是有道理的,因为如果在运行自定义内存序列化之前脚本被中断,则运行默认的JSON序列化,这会使自定义内存序列化更复杂:自定义反序列化需要能够处理除了自定义序列化编写的任何格式之外,还有默认的JSON.

3. JavaScript语句是原子的.

另一种可能性是玩家的周期不是原子的,而是JavaScript语句.当玩家的周期因超过CPU限制而中断时,游戏状态改变不完整并且内存更改已提交,但仔细编码 - 使得Screeps API调用的语句必须将调用结果分配给Memory键 - 游戏状态更改和内存更改不会相互矛盾.为这种情况编写完全CPU限制的健壮代码似乎很复杂 - 这不是我已经解决的问题,我想确保这是Screeps在尝试之前的真实行为.

没有什么是原子的.

在另一个极端,甚至单个语句都不是原子的:将一个Screeps API调用的结果分配给Memory中的一个键的语句可能会在调用完成和结果分配之间中断,并且两个游戏状态都不完整并且提交了不完整的内存更改(现在彼此不一致).在这种情况下,编写CPU限制鲁棒代码的可能性非常有限.例如,虽然通过以下语句写入Memory的值的存在将毫无疑问地表明Screeps API调用已完成,但它的缺席并不表示调用未完成:

Memory.callResults[ Game.time ][ creep.name ] = creep.move( TOP );
Run Code Online (Sandbox Code Playgroud)


有谁知道这些是Screeps的行为?或者是我没有考虑过的其他事情?文档中的以下引用:

CPU限制100表示​​在100毫秒后,即使尚未完成某些工作,脚本的执行也将终止.

暗示可能是案例3或案例4,但不是很有说服力.

另一方面,在具有单个蠕变的仿真模式中进行实验的结果,以下主循环,并在对话框中为无响应脚本选择"终止":

module.exports.loop = function()
{
  var failedTicks = 0;

  if ( 'time' in Memory )
  {
    var failedTicks = Game.time - Memory.time - 1;

    console.log( '' + failedTicks + ' failed ticks.' );
  }

  for ( var creepName in Game.creeps )
  {
    var creep = Game.creeps[ creepName ];

    creep.move( TOP );
  }

  if ( failedTicks < 3 )
  {
    // This intentional infinite loop was initially commented out, and
    // uncommented after Memory.time had been successfully initialized.

    while ( true )
    {
    }
  }

  Memory.time = Game.time;
};
Run Code Online (Sandbox Code Playgroud)

是因为failedTicks达到了它的阈值,所以creep只会在跳过无限循环的ticks上移动.这指向案例2,但不是决定性的,因为模拟模式下的CPU限制与在线不同 - 除非使用对话框的"终止"按钮终止,否则它似乎是无限的.

The*_*Man 4

默认情况下为情况 4,但可修改为情况 2.5

正如 nehegeb 和 dwurf 所怀疑的,并且私人服务器的实验已经证实,默认行为是情况 4。提交中断之前发生的游戏状态和内存的更改。

然而,服务器主循环默认 JSON 序列化的运行是由 RawMemory 中未记录的键“_parsed”的存在来控制的;键的值是对 Memory 的引用。在脚本主循环开始时删除键并在结束时恢复它,可以使脚本主循环所做的整组内存更改原子化,即情况 2.5:

module.exports.loop = function()
{
  // Run the default JSON deserialize. This also creates a key '_parsed' in
  // RawMemory - that '_parsed' key and Memory refer to the same object, and the
  // existence of the '_parsed' key tells the server main loop to run the
  // default JSON serialize.
  Memory;

  // Disable the default JSON serialize by deleting the key that tells the
  // server main loop to run it.
  delete RawMemory._parsed;

  ...

  // An example of code that would be wrong without a way to make it CPU limit
  // robust:

  mySpawn.memory.queue.push('harvester');
  // If the script is interrupted here, myRoom.memory.harvesterCreepsQueued is
  // no longer an accurate count of the number of 'harvester's in
  // mySpawn.memory.queue.
  myRoom.memory.harvesterCreepsQueued++;

  ...

  // Re-enable the default JSON serialize by restoring the key that tells the
  // server main loop to run it.
  RawMemory._parsed = Memory;
};
Run Code Online (Sandbox Code Playgroud)