系统执行顺序

Bevy 的调度算法被设计为通过在可用的 CPU 线程上并行执行尽可能多的系统来提供最大的性能。

当各系统在访问需要的数据没有冲突时,这是有可能的。然而,当一个系统需要对某数据进行写(独占)访问时,其他需要访问同一数据的系统则不能同时运行。Bevy 从系统的函数签名(它的参数类型)中来确定所有这些信息。

默认情况下系统的执行顺序是不确定的。Bevy 不考虑每个系统何时执行,每帧的执行顺序甚至都可能在发生变化!

这有什么关系吗?

在许多情况下,你不需要担心这个问题。

然而,有时你需要依靠特定的系统以特定的顺序运行。比如说:

  • 也许你在一个系统中写的逻辑依赖于另一个系统对该数据做的任何修改总是先发生?
  • 一个系统需要接收另一个系统发送的事件。
  • 你正在进行变更检测。

在这种情况下,系统以错误的顺序执行通常会导致其行为被延迟到下一帧。在极少数情况下,根据你的游戏逻辑,这甚至可能导致更严重的逻辑错误。

是否重要取决于你的决定。

对于典型游戏中的许多情况,例如果汁的视觉效果,如果它们被延迟了一帧,可能并不重要,也不值得为它费心。如果在这里忽视执行顺序也可能带来更好的性能。

另一方面,对于像处理玩家输入控制这样的情况,这将导致恼人的滞后体验,所以你需要修复它。

明确的系统排序

解决方案是使用系统标签来明确指定你想要的顺序:

    fn main() {
        App::new()
            .add_plugins(DefaultPlugins)

            // order doesn't matter for these systems:
            .add_system(particle_effects)
            .add_system(npc_behaviors)
            .add_system(enemy_movement)

            // create labels, because we need to order other systems around these:
            .add_system(map_player_input.label("input"))
            .add_system(update_map.label("map"))

            // this will always run before anything labeled "input"
            .add_system(input_parameters.before("input"))

            // this will always run after anything labeled "input" and "map"
            // also label it just in case
            .add_system(
                player_movement
                    .label("player_movement")
                    .after("input")
                    .after("map")
            )
            .run();
    }

每个标签都是一个参考点,用于对系统进行排序。

.label/.before/.after 方法可以根据需要在一个系统上使用多次。

你可以在一个系统上放置多个标签。

你也可以在多个系统上使用同一个标签。

当你有多个系统具有相同标签或执行顺序时,使用系统集可能更方便。

循环依赖关系

如果你有多个系统相互依赖,那么显然即使用标签也不可能完全解决问题。

你应该试着重新设计你的游戏以避免这种情况,或者只能接受现实。即使这样,你仍然可以按想要的方式对系统显式排序,至少确保最终结果是可预测的。