Run Criteria

Run Criteria are a mechanism for controlling if Bevy should run specific systems, at runtime. This is how you can make functionality that only runs under certain conditions.

Run Criteria are a lower-level primitive. Bevy provides higher-level abstractions on top, such as States. You can use Run Criteria without such abstractions, if you really need more direct control.

Run Criteria can be applied to individual systems, system sets, and stages.

Run Criteria are Bevy systems that return a value of type enum ShouldRun. They can accept any system parameters, like a normal system.

This example shows how run criteria might be used to implement different multiplayer modes:

use bevy::ecs::schedule::ShouldRun;

#[derive(Debug, PartialEq, Eq)]
enum MultiplayerKind {
    Client,
    Host,
    Local,
}

fn run_if_connected(
    mode: Res<MultiplayerKind>,
    session: Res<MyNetworkSession>,
) -> ShouldRun
{
    if *mode == MultiplayerKind::Client && session.is_connected() {
        ShouldRun::Yes
    } else {
        ShouldRun::No
    }
}

fn run_if_host(
    mode: Res<MultiplayerKind>,
) -> ShouldRun
{
    if *mode == MultiplayerKind::Host || *mode == MultiplayerKind::Local {
        ShouldRun::Yes
    } else {
        ShouldRun::No
    }
}

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

        // if we are currently connected to a server,
        // activate our client systems
        .add_system_set(
            SystemSet::new()
                .with_run_criteria(run_if_connected.system())
                .before("input")
                .with_system(server_session.system())
                .with_system(fetch_server_updates.system())
        )

        // if we are hosting the game,
        // activate our game hosting systems
        .add_system_set(
            SystemSet::new()
                .with_run_criteria(run_if_host.system())
                .before("input")
                .with_system(host_session.system())
                .with_system(host_player_movement.system())
                .with_system(host_enemy_ai.system())
        )

        // other systems in our game
        .add_system(smoke_particles.system())
        .add_system(water_animation.system())
        .add_system_set(
            SystemSet::new()
                .label("input")
                .with_system(keyboard_input.system())
                .with_system(gamepad_input.system())
        )
        .run();
}

Run Criteria Labels

If you have multiple systems or system sets that you want to share the same run criteria, you can give it a label.

When you use a label, Bevy will only execute the run criteria once, remember its output, and apply it to everything with the label.

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(RunCriteriaLabel)]
enum MyRunCriteria {
    Client,
    Host,
}

fn main() {
    App::build()
        // ...
        .add_system_set(
            SystemSet::new()
                .with_run_criteria(
                    // assign it a label
                    run_if_host.system()
                        .label(MyRunCriteria::Host)
                )
                .with_system(host_session.system())
                .with_system(host_player_movement.system())
                .with_system(host_enemy_ai.system())
        )

        // extra system for debugging the host
        // it can share our previously-registered run criteria
        .add_system(host_debug.system()
            .with_run_criteria(MyRunCriteria::Host)
        )
        .run();
}

The run-once property is especially important if you have a complex run criteria system that performs mutations or is otherwise non-idempotent.

Known Pitfalls

When receiving events in systems that don't run every frame, you will miss any events that are sent during the frames when the receiving systems are not running!

To mitigate this, you could implement a custom cleanup strategy, to manually manage the lifetime of the relevant event types.


Bevy's fixed timestep is also implemented using run criteria under the hood.