diff --git a/Cargo.toml b/Cargo.toml index 36f8cd493318a..6b72b89799490 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/examples/README.md b/examples/README.md index 15175765937f4..e5c9c1c069daa 100644 --- a/examples/README.md +++ b/examples/README.md @@ -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 diff --git a/examples/ecs/system_sets.rs b/examples/ecs/system_sets.rs new file mode 100644 index 0000000000000..fb5d1d3b31333 --- /dev/null +++ b/examples/ecs/system_sets.rs @@ -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::() + // Note that the system sets added in this example set their run criteria explicitly. + // 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) + // 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()) + .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), + ), + ) + .add_system_set( + SystemSet::new() + .label(PostPhysics) + // 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())) + // `collision` and `sfx` are not ordered with respect to + // each other, and may run in any order + .with_system(collision.system()) + .with_system(sfx.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