System Order of Execution

Bevy's scheduling algorithm is designed to deliver maximum performance by running as many systems as possible in parallel across the available CPU threads.

This is possible when the systems do not conflict over the data they need to access. However, when a system needs to have mutable (exclusive) access to a piece of data, other systems that need to access the same data cannot be run at the same time. Bevy determines all of this information from the system's function signature (the types of the parameters it takes).

In such situations, the order is nondeterministic by default. Bevy takes no regard for when each system will run, and the order could even change every frame!

Does it even matter?

In many cases, you don't need to worry about this.

However, sometimes you need to rely on specific systems to run in a particular order. For example:

  • Maybe the logic you wrote in one of your systems needs any modifications done to that data by another system to always happen first?
  • One system needs to receive events sent by another system.
  • You are using change detection.

In such situations, systems running in the wrong order typically causes their behavior to be delayed until the next frame. In rare cases, depending on your game logic, it may even result in more serious logic bugs!

It is up to you to decide if this is important.

With many things in typical games, such as juicy visual effects, it probably doesn't matter if they get delayed by a frame. It might not be worthwhile to bother with it. If you don't care, leaving the order ambiguous may also result in better performance.

On the other hand, for things like handling the player input controls, this would result in annoying lag, so you should probably fix it.

Explicit System Ordering

The solution is to use system labels to explicitly specify the order you want:

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

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

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

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

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

Each label is a reference point that other systems can be ordered around.

.label/.before/.after may be used as many times as you need on one system.

You can place multiple labels on one system.

You can also use the same label on multiple systems.

When you have multiple systems with common labels or ordering, it may be convenient to use system sets.

Circular Dependencies

If you have multiple systems mutually depending on each other, then it is clearly impossible to resolve the situation completely like that.

You should try to redesign your game to avoid such situations, or just accept the consequences. You can at least make it behave predictably, using explicit ordering to specify the order you prefer.