Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

0.12: One-Shot Systems #769

Merged
merged 13 commits into from
Oct 27, 2023
73 changes: 73 additions & 0 deletions content/news/2023-10-21-bevy-0.12/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,79 @@ Since our last release a few months ago we've added a _ton_ of new features, bug

<div class="release-feature-authors">authors: @author</div>

## One-Shot Systems

<div class="release-feature-authors">authors: @alice-i-cecile @pascualex, @Trashtalk217, @Zeenobit</div>

Ordinarily, systems run once per frame, as part of a schedule.
But what if you don't want to poll for them regularly?
Trashtalk217 marked this conversation as resolved.
Show resolved Hide resolved
Maybe you're responding to a very rare event like in a complex turn-based game, or simply don't want to clutter your schedule with a new system for every single button.
One-shot systems flip that logic on its head, and provide you the ability to run arbitrary logic on demand, using the powerful and familiar system syntax.

```rust
#[derive(Resource, Default, Debug)]
struct Counter(u8);

fn increment(mut counter: ResMut<Counter>) {
counter.0 += 1;
println!("{}", counter.0);
}

let mut world = World::new();
Trashtalk217 marked this conversation as resolved.
Show resolved Hide resolved
world.init_resource::<Counter>();
let id = world.register_system(increment);
let _ = world.run_system(id); // prints 1
let _ = world.run_system(id); // prints 2
```

There is now a new way to run systems. First you need to register a system within a world, by calling `world.register_system(your_system)`. The returned `SystemId` can then be used to run that system, either within an exclusive system via `world.run_system(id)` or via a command `commands.run_system(id)`.
alice-i-cecile marked this conversation as resolved.
Show resolved Hide resolved

One-shot systems are very flexible. For example, you can have multiple instances of one system registered at the same time. One-shot systems can be nested (although not recursive), and you can also wrap `SystemId`s into components, making it possible to treat registered systems like entities.
Trashtalk217 marked this conversation as resolved.
Show resolved Hide resolved
Trashtalk217 marked this conversation as resolved.
Show resolved Hide resolved

```rust
use bevy::ecs::system::SystemId;

#[derive(Component)]
struct Callback(SystemId);

// calling all callbacks!
fn call_all(query: Query<&Callback>, mut commands: Commands) {
for callback in query.iter() {
commands.run_system(callback.0);
}
}
```

One-shot systems are not without their limitations.
Currently, exclusive systems and systems designed for system piping (with either an `In` parameter or a return type) can't be used at all.
Additionally, one-shot systems are always evaluated sequentially, rather than in parallel.
While this reduces both complexity and overhead, this can be meaningfully slower than using a schedule with a parallel executor if you want to run a large number of heavy systems at once.

Registering and running systems offers improved performance and correctness: you can have multiple copies of a single system, each with their own `Local` values and cached system state.
It also plays nice with asset-driven workflows: recording a mapping from a string to an identifier in a serialized callback is much nicer than trying to do so with Rust functions!

However, when you're just prototyping or writing a unit test, it can be a real hassle: who wants all that boilerplate and indirection!
When those caveats don't bother you, just use the `World::run_system_once` method.

```rust
use bevy::ecs::system::RunSystemOnce;

#[derive(Resource, Default, Debug)]
struct Counter(u8);

fn increment(mut counter: ResMut<Counter>) {
counter.0 += 1;
println!("{}", counter.0);
}

let mut world = World::new();
world.init_resource::<Counter>();
world.run_system_once(increment); // prints 1
world.run_system_once(increment); // prints 2
```

This is great for unit testing systems and queries, and it's both lower overhead and simpler to use. However, there is one caveat. Some systems have state, either in the form of `Local` arguments or change detection. This state isn't saved between two `run_system_once` calls, creating odd behavior. The `Local`'s reset every run, while change detection will *always* detect data as added/changed. Be careful and you'll be alright.

## <a name="what-s-next"></a>What's Next?

We have plenty of work that is pretty much finished and is therefore very likely to land in **Bevy 0.13**:
Expand Down
Loading