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
69 changes: 69 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,75 @@ 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 `SystemId` that is returned 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)`.

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 is useful and allows for a great degree of control. However, if you _just_ want to run a system - sans registration - there's `RunSystemOnce`, a new trait that does just that.
Trashtalk217 marked this conversation as resolved.
Show resolved Hide resolved

```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, which will *always* detect data as added/changed. This state isn't saved between two `run_system_once` calls, this may create some unexpected behavior. Be careful and you should be fine.
alice-i-cecile marked this conversation as resolved.
Show resolved Hide resolved

## <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