在 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;
};
这种管理玩家 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. 对 Memory 的更改是原子的,但对 Game 对象的更改不是。
编辑:阅读RawMemory对象的文档后,我想到了这种可能性。如果脚本被中断,任何已经安排好的游戏状态更改都会提交,但不会提交对内存的更改。考虑到 RawMemory 提供的功能,这是有道理的,因为如果脚本在运行自定义内存序列化之前被中断,则会运行默认的 JSON 序列化,这会使自定义内存序列化更加复杂:自定义反序列化需要能够处理除了自定义序列化编写的任何格式之外的默认 JSON。
3. JavaScript 语句是原子的。
另一种可能性是播放器的循环不是原子的,但 JavaScript 语句是原子的。当玩家的循环因超过 CPU 限制而中断时,会提交不完整的游戏状态更改和内存更改,但需要仔细编码 - 进行 Screeps API 调用的语句必须将调用结果分配给内存键 - 游戏状态更改和内存更改不会相互不一致。为这种情况编写完全受 CPU 限制的健壮代码似乎很复杂——这不是我已经解决的问题,我想在尝试之前确定这是 Screeps 的真实行为。
4. 没有什么是原子的。
在另一个极端,甚至单个语句都不是原子的:将 Screeps API 调用的结果分配给内存中的键的语句可能会在调用完成和分配结果之间中断,并且不完整的游戏状态都会发生变化并且不完整的内存更改(现在彼此不一致)被提交。在这种情况下,编写 CPU 限制健壮代码的可能性非常有限。例如,尽管以下语句写入 Memory 的值的存在无疑表明 Screeps API 调用已完成,但它的缺失并不表明调用未完成毫无疑问:
Memory.callResults[ Game.time ][ creep.name ] = creep.move( TOP );
有谁知道其中哪些是 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;
};
是蠕变只在无限循环被跳过的滴答声上移动,因为 failedTicks 达到了它的阈值。这指向案例 2,但不是决定性的,因为模拟模式下的 CPU 限制与在线不同 - 它似乎是无限的,除非使用对话框的“终止”按钮终止。