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

时间:2016-08-14 15:05:40

标签: javascript screeps

在Screeps中,每个玩家对CPU的使用都是有限的,但是documentation for this feature并没有使得强制执行的方式足以清楚地编写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;
};

这种管理玩家的方式' 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;
}

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

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

3。 JavaScript语句是原子的。

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

4。没有什么是原子的。

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

Memory.callResults[ Game.time ][ creep.name ] = creep.move( TOP );

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

  

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

暗示可能是案例3或案例4,但不是很令人信服。

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

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;
};

是蠕变仅在跳过无限循环的刻度上移动,因为failedTicks达到了它的阈值。这指向案例2,但并不确定,因为模拟模式下的CPU限制与在线不同 - 它似乎是无限的,除非使用对话框终止&#39;终止&#39;终止&#39;终止&#39;终止&#39;终止&#39;按钮。

3 个答案:

答案 0 :(得分:2)

不是#1或#2。我认为它是#4,它最有意义的是监视主循环外部的CPU使用情况并在达到限制时终止它。 #3需要在screeps服务器中执行复杂的代码才能执行“语句级”事务。正如您所发现的,模拟器中没有CPU限制。

大多数玩家只需在主循环中尽早放置关键代码即可解决此问题。塔码首先出现,然后产生代码,然后进行蠕变运动/工作。这也可以防止代码中未被捕获的异常,因为您最关键的函数(希望)已经执行过了。这对于CPU限制来说是一个糟糕的解决方案,但在我的观察中,一旦你使用了桶中的所有CPU并且不断达到常规限制,似乎你的代码每跳过2次都会被跳过。

我现在没有CPU问题(我有订阅),但是我会通过将CPU密集型代码放在最后来解决这个问题,如果可能的话,只有当你的CPU中有足够的CPU时才执行它你甚至不能达到500每CPU限额。它也有助于拥有更大的小兵,这对于寻路或甚至只是移动(每次移动0.2次)来占用相当大的CPU和更大的小兵意味着更少的小兵很常见。

答案 1 :(得分:2)

当天的游戏提示之一&#39;表示:

TIP OF THE DAY: If CPU limit raises, your script will execute only partially.

因此,我会说,它很可能#4
就像dwurf所说,在大多数情况下,以下脚本布局方法应该可以解决问题:

  

大多数玩家只需在主循环中尽早放置关键代码即可解决此问题。塔码首先出现,然后产生代码,然后进行蠕变运动/工作。 [...]

答案 2 :(得分:1)

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

正如nehegeb和dwurf所怀疑的,以及私人服务器的实验已经确认,默认行为是案例4.在中断之前发生的游戏状态和内存的更改都已提交。

但是,服务器主循环运行默认JSON序列化是由一个未记录的密钥&#39; _sparsed&#39;在RawMemory;键的值是对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;
};