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

[Merged by Bors] - Add system sets and run criteria example #1909

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,10 @@ path = "examples/ecs/system_chaining.rs"
name = "system_param"
path = "examples/ecs/system_param.rs"

[[example]]
name = "system_sets"
path = "examples/ecs/system_sets.rs"

[[example]]
name = "timers"
path = "examples/ecs/timers.rs"
Expand Down
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ Example | File | Description
`state` | [`ecs/state.rs`](./ecs/state.rs) | Illustrates how to use States to control transitioning from a Menu state to an InGame state
`system_chaining` | [`ecs/system_chaining.rs`](./ecs/system_chaining.rs) | Chain two systems together, specifying a return type in a system (such as `Result`)
`system_param` | [`ecs/system_param.rs`](./ecs/system_param.rs) | Illustrates creating custom system parameters with `SystemParam`
`system_sets` | [`ecs/system_sets.rs`](./ecs/system_sets.rs) | Shows `SystemSet` use along with run criterion
`timers` | [`ecs/timers.rs`](./ecs/timers.rs) | Illustrates ticking `Timer` resources inside systems and handling their state

## Games
Expand Down
163 changes: 163 additions & 0 deletions examples/ecs/system_sets.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
use bevy::{app::AppExit, ecs::schedule::ShouldRun, prelude::*};

/// A [SystemLabel] can be applied as a label to systems and system sets,
/// which can then be referred to from other systems.
/// This is useful in case a user wants to e.g. run _before_ or _after_
/// some label.
/// `Clone`, `Hash`, `Debug`, `PartialEq`, `Eq`, are all required to derive
/// [SystemLabel].
#[derive(Clone, Hash, Debug, PartialEq, Eq, SystemLabel)]
struct Physics;

#[derive(Clone, Hash, Debug, PartialEq, Eq, SystemLabel)]
struct PostPhysics;

/// Resource used to stop our example.
#[derive(Default)]
struct Done(bool);

/// This is used to show that within a [SystemSet], individual systems can also
/// be labelled, allowing further fine tuning of run ordering.
#[derive(Clone, Hash, Debug, PartialEq, Eq, SystemLabel)]
pub enum PhysicsSystem {
UpdateVelocity,
Movement,
}

/// This example realizes the following scheme:
///
/// ```none
/// Physics (Criteria: App has run < 1.0 seconds)
/// \--> update_velocity (via label PhysicsSystem::UpdateVelocity)
/// \--> movement (via label PhysicsSystem::Movement)
/// PostPhysics (Criteria: Resource `done` is false)
/// \--> collision || sfx
/// Exit (Criteria: Resource `done` is true)
/// \--> exit
/// ```
///
/// The `Physics` label represents a [SystemSet] containing two systems.
/// This set's criteria is to stop after a second has elapsed.
/// The two systems (update_velocity, movement) runs in a specified order.
///
/// Another label `PostPhysics` uses run criteria to only run after `Physics` has finished.
/// This set's criteria is to run only when _not done_, as specified via a resource.
/// The two systems here (collision, sfx) are not specified to run in any order, and the actual
/// ordering can then change between invocations.
///
/// Lastly a system with run criterion _done_ is used to exit the app.
/// ```
fn main() {
App::build()
.add_plugins(DefaultPlugins)
.init_resource::<Done>()
// Note that the system sets added in this example sets their run criteria explicitly.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: "Note that the system sets added in this example set their run criteria explicitly."

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

// See the `ecs/state.rs` example for a pattern where run criteria are set implicitly for common
// use cases- typically state transitions.
// Also note that a system set has a single run criterion at most, which means using `.with_run_criteria(...)`
// after `SystemSet::on_update(...)` would override the state transition criterion.
.add_system_set(
SystemSet::new()
// This label is added to all systems in this set.
// The label can then be referred to elsewhere (other sets).
.label(Physics)
.with_system(
update_velocity
.system()
// Only applied to the `update_velocity` system
.label(PhysicsSystem::UpdateVelocity),
)
.with_system(
movement
.system()
// Only applied to the `movement` system
.label(PhysicsSystem::Movement)
// Enforce order within this system by specifying this
.after(PhysicsSystem::UpdateVelocity),
)
// This criteria ensures this whole system set only runs when this system's
// output says so (ShouldRun::Yes)
.with_run_criteria(run_for_a_second.system()),
)
.add_system_set(
SystemSet::new()
.label(PostPhysics)
// `collision` and `sfx` are not ordered with respect to
// each other, and may run in any order
.with_system(collision.system())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small nit: I like to define systems last in system sets as a general pattern. This creates a nice SetConfig -> Systems order.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok! I'm all for consistency. Updated.

.with_system(sfx.system())
// This whole set runs after `Physics` (which in this case is a label for
// another set).
// There is also `.before(..)`.
.after(Physics)
// This shows that we can modify existing run criteria results.
// Here we create a _not done_ criteria by piping the output of
// the `is_done` system and inverting the output.
// Notice a string literal also works as a label.
.with_run_criteria(RunCriteria::pipe("is_done_label", inverse.system())),
)
.add_system(
exit.system()
.after(PostPhysics)
// Label the run criteria such that the `PostPhysics` set can reference it
.with_run_criteria(is_done.system().label("is_done_label")),
)
.run();
}

/// Example of a run criteria.
/// Here we only want to run for a second, then stop.
fn run_for_a_second(time: Res<Time>, mut done: ResMut<Done>) -> ShouldRun {
let elapsed = time.seconds_since_startup();
if elapsed < 1.0 {
info!(
"We should run again. Elapsed/remaining: {:.2}s/{:.2}s",
elapsed,
1.0 - elapsed
);
ShouldRun::Yes
} else {
done.0 = true;
ShouldRun::No
}
}

/// Another run criteria, simply using a resource.
fn is_done(done: Res<Done>) -> ShouldRun {
if done.0 {
ShouldRun::Yes
} else {
ShouldRun::No
}
}

/// Used with [RunCritera::pipe], inverts the result of the
/// passed system.
fn inverse(input: In<ShouldRun>) -> ShouldRun {
match input.0 {
ShouldRun::No => ShouldRun::Yes,
ShouldRun::Yes => ShouldRun::No,
_ => unreachable!(),
}
}

fn update_velocity() {
info!("Updating velocity");
}

fn movement() {
info!("Updating movement");
}

fn collision() {
info!("Physics done- checking collisions");
}

fn sfx() {
info!("Physics done- playing some sfx");
}

fn exit(mut app_exit_events: EventWriter<AppExit>) {
info!("Exiting...");
app_exit_events.send(AppExit);
}