Bevy Version: | 0.14 | (current) |
---|
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
SystemId
s 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.