One-Shot Systems

One-Shot Systems are systems that you intend to call yourself, whenever you want. For example: on a button press, upon triggering a special item or ability in your game, etc…

fn item_handler_health(
    mut q_player: Query<&mut Health, With<Player>>,
) {
    let mut health = q_player.single_mut();
    health.hp += 25.0;
}

fn item_handler_magic_potion(
    mut evw_magic: EventWriter<MyMagicEvent>,
    mut commands: Commands,
) {
    evw_magic.send(MyMagicEvent::Sparkles);
    commands.spawn(MySparklesBundle::default());
}

Registration

You should not add these systems to a schedule.

Instead, you can register them into the World, to get a SystemId. You can then store that SystemId somewhere and use it to run the system later.

The most convenient way is probably to use FromWorld and put your SystemIds in a resource:

/// For this simple example, we will just organize our systems
/// using string keys in a hash map.
#[derive(Resource)]
struct MyItemSystems(HashMap<String, SystemId>);

impl FromWorld for MyItemSystems {
    fn from_world(world: &mut World) -> Self {
        let mut my_item_systems = MyItemSystems(HashMap::new());

        my_item_systems.0.insert(
            "health".into(),
            world.register_system(item_handler_health)
        );
        my_item_systems.0.insert(
            "magic".into(),
            world.register_system(item_handler_magic_potion)
        );

        my_item_systems
    }
}
app.init_resource::<MyItemSystems>();

Alternative: register from an exclusive system:

Code:
fn register_item_handler_systems(world: &mut World) {
    let mut my_item_systems = MyItemSystems(HashMap::new());

    my_item_systems.0.insert(
        "health".into(),
        world.register_system(item_handler_health)
    );
    my_item_systems.0.insert(
        "magic".into(),
        world.register_system(item_handler_magic_potion)
    );

    world.insert_resource(my_item_systems);
}
app.add_systems(Startup, register_item_handler_systems);

Or from the app builder:

Code:
fn my_plugin(app: &mut App) {
    let mut my_item_systems = MyItemSystems(HashMap::new());

    my_item_systems.0.insert(
        "health".into(),
        app.register_system(item_handler_health)
    );
    my_item_systems.0.insert(
        "magic".into(),
        app.register_system(item_handler_magic_potion)
    );

    app.insert_resource(my_item_systems);
}

Running

The easiest way is using Commands (Commands):

fn trigger_health_item(
    mut commands: Commands,
    systems: Res<MyItemSystems>,
) {
    // TODO: do some logic to implement picking up the health item

    let id = systems.0["health"];
    commands.run_system(id);
}

This queues up the system to be run later, whenever Bevy decides to apply the Commands.

If you want to run a one-shot system immediately, like a normal function call, you need direct World access. Do it from an exclusive system:

fn trigger_magic_item(world: &mut World) {
    // TODO: do some logic to implement picking up the magic item

    let id = world.resource::<MyItemSystems>().0["magic"];
    world.run_system(id).expect("Error Running Oneshot System");

    // Since we are in an exclusive system, we can expect
    // the magic potion to now be in effect!
}

Either way, the one-shot system's Commands are automatically applied immediately when it runs.

Without Registration

It is possible to also run one-shot systems without registering them beforehand:

world.run_system_once(my_oneshot_system_fn);

If you do this, Bevy is unable to store any data related to the system:

  • Locals will not retain their value from a previous run.
  • Queries will not be able to cache their lookups, leading to slower performance.
  • etc…

It is therefore recommended to register your one-shot systems, unless you really only intend to run them once.

Performance Considerations

To run a one-shot system, exclusive World access is required. The system can have arbitrary parameters, and Bevy cannot validate its data access against other systems, like it does when the system is part of a schedule. So, no multi-threading allowed.

In practice, this isn't usually a problem, because the use cases for one-shot systems are things that happen rarely.

But maybe don't overuse them! If something happens regularly, consider doing it from a normal system that is part of a schedule, and controlling it with run conditions instead.