Skip to content

Commit 38feddb

Browse files
Add system sets and run criteria example (#1909)
If accepted, fixes #1694 . I wanted to explore system sets and run criteria a bit, so made an example expanding a bit on the snippets shown in the 0.5 release post. Shows a couple of system sets, uses system labels, run criterion, and a use of the interesting `RunCriterion::pipe` functionality.
1 parent d653ad2 commit 38feddb

File tree

3 files changed

+168
-0
lines changed

3 files changed

+168
-0
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,10 @@ path = "examples/ecs/system_chaining.rs"
294294
name = "system_param"
295295
path = "examples/ecs/system_param.rs"
296296

297+
[[example]]
298+
name = "system_sets"
299+
path = "examples/ecs/system_sets.rs"
300+
297301
[[example]]
298302
name = "timers"
299303
path = "examples/ecs/timers.rs"

examples/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ Example | File | Description
150150
`state` | [`ecs/state.rs`](./ecs/state.rs) | Illustrates how to use States to control transitioning from a Menu state to an InGame state
151151
`system_chaining` | [`ecs/system_chaining.rs`](./ecs/system_chaining.rs) | Chain two systems together, specifying a return type in a system (such as `Result`)
152152
`system_param` | [`ecs/system_param.rs`](./ecs/system_param.rs) | Illustrates creating custom system parameters with `SystemParam`
153+
`system_sets` | [`ecs/system_sets.rs`](./ecs/system_sets.rs) | Shows `SystemSet` use along with run criterion
153154
`timers` | [`ecs/timers.rs`](./ecs/timers.rs) | Illustrates ticking `Timer` resources inside systems and handling their state
154155

155156
## Games

examples/ecs/system_sets.rs

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
use bevy::{app::AppExit, ecs::schedule::ShouldRun, prelude::*};
2+
3+
/// A [SystemLabel] can be applied as a label to systems and system sets,
4+
/// which can then be referred to from other systems.
5+
/// This is useful in case a user wants to e.g. run _before_ or _after_
6+
/// some label.
7+
/// `Clone`, `Hash`, `Debug`, `PartialEq`, `Eq`, are all required to derive
8+
/// [SystemLabel].
9+
#[derive(Clone, Hash, Debug, PartialEq, Eq, SystemLabel)]
10+
struct Physics;
11+
12+
#[derive(Clone, Hash, Debug, PartialEq, Eq, SystemLabel)]
13+
struct PostPhysics;
14+
15+
/// Resource used to stop our example.
16+
#[derive(Default)]
17+
struct Done(bool);
18+
19+
/// This is used to show that within a [SystemSet], individual systems can also
20+
/// be labelled, allowing further fine tuning of run ordering.
21+
#[derive(Clone, Hash, Debug, PartialEq, Eq, SystemLabel)]
22+
pub enum PhysicsSystem {
23+
UpdateVelocity,
24+
Movement,
25+
}
26+
27+
/// This example realizes the following scheme:
28+
///
29+
/// ```none
30+
/// Physics (Criteria: App has run < 1.0 seconds)
31+
/// \--> update_velocity (via label PhysicsSystem::UpdateVelocity)
32+
/// \--> movement (via label PhysicsSystem::Movement)
33+
/// PostPhysics (Criteria: Resource `done` is false)
34+
/// \--> collision || sfx
35+
/// Exit (Criteria: Resource `done` is true)
36+
/// \--> exit
37+
/// ```
38+
///
39+
/// The `Physics` label represents a [SystemSet] containing two systems.
40+
/// This set's criteria is to stop after a second has elapsed.
41+
/// The two systems (update_velocity, movement) runs in a specified order.
42+
///
43+
/// Another label `PostPhysics` uses run criteria to only run after `Physics` has finished.
44+
/// This set's criteria is to run only when _not done_, as specified via a resource.
45+
/// The two systems here (collision, sfx) are not specified to run in any order, and the actual
46+
/// ordering can then change between invocations.
47+
///
48+
/// Lastly a system with run criterion _done_ is used to exit the app.
49+
/// ```
50+
fn main() {
51+
App::build()
52+
.add_plugins(DefaultPlugins)
53+
.init_resource::<Done>()
54+
// Note that the system sets added in this example set their run criteria explicitly.
55+
// See the `ecs/state.rs` example for a pattern where run criteria are set implicitly for common
56+
// use cases- typically state transitions.
57+
// Also note that a system set has a single run criterion at most, which means using `.with_run_criteria(...)`
58+
// after `SystemSet::on_update(...)` would override the state transition criterion.
59+
.add_system_set(
60+
SystemSet::new()
61+
// This label is added to all systems in this set.
62+
// The label can then be referred to elsewhere (other sets).
63+
.label(Physics)
64+
// This criteria ensures this whole system set only runs when this system's
65+
// output says so (ShouldRun::Yes)
66+
.with_run_criteria(run_for_a_second.system())
67+
.with_system(
68+
update_velocity
69+
.system()
70+
// Only applied to the `update_velocity` system
71+
.label(PhysicsSystem::UpdateVelocity),
72+
)
73+
.with_system(
74+
movement
75+
.system()
76+
// Only applied to the `movement` system
77+
.label(PhysicsSystem::Movement)
78+
// Enforce order within this system by specifying this
79+
.after(PhysicsSystem::UpdateVelocity),
80+
),
81+
)
82+
.add_system_set(
83+
SystemSet::new()
84+
.label(PostPhysics)
85+
// This whole set runs after `Physics` (which in this case is a label for
86+
// another set).
87+
// There is also `.before(..)`.
88+
.after(Physics)
89+
// This shows that we can modify existing run criteria results.
90+
// Here we create a _not done_ criteria by piping the output of
91+
// the `is_done` system and inverting the output.
92+
// Notice a string literal also works as a label.
93+
.with_run_criteria(RunCriteria::pipe("is_done_label", inverse.system()))
94+
// `collision` and `sfx` are not ordered with respect to
95+
// each other, and may run in any order
96+
.with_system(collision.system())
97+
.with_system(sfx.system()),
98+
)
99+
.add_system(
100+
exit.system()
101+
.after(PostPhysics)
102+
// Label the run criteria such that the `PostPhysics` set can reference it
103+
.with_run_criteria(is_done.system().label("is_done_label")),
104+
)
105+
.run();
106+
}
107+
108+
/// Example of a run criteria.
109+
/// Here we only want to run for a second, then stop.
110+
fn run_for_a_second(time: Res<Time>, mut done: ResMut<Done>) -> ShouldRun {
111+
let elapsed = time.seconds_since_startup();
112+
if elapsed < 1.0 {
113+
info!(
114+
"We should run again. Elapsed/remaining: {:.2}s/{:.2}s",
115+
elapsed,
116+
1.0 - elapsed
117+
);
118+
ShouldRun::Yes
119+
} else {
120+
done.0 = true;
121+
ShouldRun::No
122+
}
123+
}
124+
125+
/// Another run criteria, simply using a resource.
126+
fn is_done(done: Res<Done>) -> ShouldRun {
127+
if done.0 {
128+
ShouldRun::Yes
129+
} else {
130+
ShouldRun::No
131+
}
132+
}
133+
134+
/// Used with [RunCritera::pipe], inverts the result of the
135+
/// passed system.
136+
fn inverse(input: In<ShouldRun>) -> ShouldRun {
137+
match input.0 {
138+
ShouldRun::No => ShouldRun::Yes,
139+
ShouldRun::Yes => ShouldRun::No,
140+
_ => unreachable!(),
141+
}
142+
}
143+
144+
fn update_velocity() {
145+
info!("Updating velocity");
146+
}
147+
148+
fn movement() {
149+
info!("Updating movement");
150+
}
151+
152+
fn collision() {
153+
info!("Physics done- checking collisions");
154+
}
155+
156+
fn sfx() {
157+
info!("Physics done- playing some sfx");
158+
}
159+
160+
fn exit(mut app_exit_events: EventWriter<AppExit>) {
161+
info!("Exiting...");
162+
app_exit_events.send(AppExit);
163+
}

0 commit comments

Comments
 (0)