Skip to content

Commit f8c56f5

Browse files
authored
0.11 Section: Schedule First APIs and Nested System Tuples (#658)
1 parent 84cab00 commit f8c56f5

File tree

1 file changed

+140
-5
lines changed
  • content/news/2023-07-07-bevy-0.11

1 file changed

+140
-5
lines changed

content/news/2023-07-07-bevy-0.11/index.md

Lines changed: 140 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,146 @@ Since our last release a few months ago we've added a _ton_ of new features, bug
1717

1818
* **Feature**: description
1919

20-
## Feature
21-
22-
<div class="release-feature-authors">authors: @todo</div>
23-
24-
Description
20+
## Schedule-First ECS APIs
21+
22+
<div class="release-feature-authors">authors: @cart</div>
23+
24+
In **Bevy 0.10** we introduced [ECS Schedule V3](/news/bevy-0-10/#ecs-schedule-v3), which _vastly_ improved the capabilities of Bevy ECS system scheduling: scheduler API ergonomics, system chaining, the ability to run exclusive systems and apply deferred system operations at any point in a schedule, a single unified schedule, configurable System Sets, run conditions, and a better State system.
25+
26+
However it pretty quickly became clear that the new system still had some areas to improve:
27+
28+
* **Base Sets were hard to understand and error prone**: What _is_ a Base Set? When do I use them? Why do they exist? Why is my ordering implicitly invalid due to incompatible Base Set ordering? Why do some schedules have a default Base Set while others don't? [Base Sets were confusing!](https://github.com/bevyengine/bevy/pull/8079#base-set-confusion)
29+
* **There were too many ways to schedule a System**: We've accumulated too many scheduling APIs. As of Bevy **0.10**, we had [_SIX_ different ways to add a system to the "startup" schedule](https://github.com/bevyengine/bevy/pull/8079#unify-system-apis). Thats too many ways!
30+
* **Too much implicit configuration**: There were both default Schedules and default Base Sets. In some cases systems had default schedules or default base sets, but in other cases they didn't! [A system's schedule and configuration should be explicit and clear](https://github.com/bevyengine/bevy/pull/8079#schedule-should-be-clear).
31+
* **Adding Systems to Schedules wasn't ergonomic**: Things like `add_system(foo.in_schedule(CoreSchedule::Startup))` were not fun to type or read. We created special-case helpers, such as `add_startup_system(foo)`, but [this required more internal code, user-defined schedules didn't benefit from the special casing, and it completely hid the `CoreSchedule::Startup` symbol!](https://github.com/bevyengine/bevy/pull/8079#ergonomic-system-adding).
32+
33+
### Unraveling the Complexity
34+
35+
If your eyes started to glaze over as you tried to wrap your head around this, or phrases like "implicitly added to the `Update` Base Set" filled you with dread ... don't worry. After [a lot of careful thought](https://github.com/bevyengine/bevy/pull/8079) we've unraveled the complexity and built something clear and simple.
36+
37+
In **Bevy 0.11** the "scheduling mental model" is _much_ simpler thanks to **Schedule-First ECS APIs**:
38+
39+
```rust
40+
app
41+
.add_systems(Startup, (a, b))
42+
.add_systems(Update, (c, d, e))
43+
.add_systems(FixedUpdate, (f, g))
44+
.add_systems(PostUpdate, h)
45+
.add_systems(OnEnter(AppState::Menu), enter_menu)
46+
.add_systems(OnExit(AppState::Menu), exit_menu)
47+
```
48+
49+
* **There is _exactly_ one way to schedule systems**
50+
* Call `add_systems`, state the schedule name, and specify one or more systems
51+
* **Base Sets have been entirely removed in favor of Schedules, which have friendly / short names**
52+
* Ex: The `CoreSet::Update` Base Set has become `Update`
53+
* **There is no implicit or implied configuration**
54+
* Default Schedules and default Base Sets don't exist
55+
* **The syntax is easy on the eyes and ergonomic**
56+
* Schedules are first so they "line up" when formatted
57+
58+
<details>
59+
<summary>To compare, expand this to see what it used to be!</summary>
60+
61+
```rust
62+
app
63+
// Startup system variant 1.
64+
// Has an implied default StartupSet::Startup base set
65+
// Has an implied CoreSchedule::Startup schedule
66+
.add_startup_systems((a, b))
67+
// Startup system variant 2.
68+
// Has an implied default StartupSet::Startup base set
69+
// Has an implied CoreSchedule::Startup schedule
70+
.add_systems((a, b).on_startup())
71+
// Startup system variant 3.
72+
// Has an implied default StartupSet::Startup base set
73+
.add_systems((a, b).in_schedule(CoreSchedule::Startup))
74+
// Update system variant 1.
75+
// `CoreSet::Update` base set and `CoreSchedule::Main` are implied
76+
.add_system(c)
77+
// Update system variant 2 (note the add_system vs add_systems difference)
78+
// `CoreSet::Update` base set and `CoreSchedule::Main` are implied
79+
.add_systems((d, e))
80+
// No implied default base set because CoreSchedule::FixedUpdate doesn't have one
81+
.add_systems((f, g).in_schedule(CoreSchedule::FixedUpdate))
82+
// `CoreSchedule::Main` is implied, in_base_set overrides the default CoreSet::Update set
83+
.add_system(h.in_base_set(CoreSet::PostUpdate))
84+
// This has no implied default base set
85+
.add_systems(enter_menu.in_schedule(OnEnter(AppState::Menu)))
86+
// This has no implied default base set
87+
.add_systems(exit_menu.in_schedule(OnExit(AppState::Menu)))
88+
```
89+
90+
</details>
91+
92+
Note that normal "system sets" still exist! You can still use sets to organize and order your systems:
93+
94+
```rust
95+
app.add_systems(Update, (
96+
(walk, jump).in_set(Movement),
97+
collide.after(Movement),
98+
))
99+
```
100+
101+
The `configure_set` API has also been adjusted for parity:
102+
103+
```rust
104+
// before
105+
app.configure_set(Foo.after(Bar).in_schedule(PostUpdate))
106+
// after
107+
app.configure_set(PostUpdate, Foo.after(Bar))
108+
```
109+
110+
## Nested System Tuples and Chaining
111+
112+
<div class="release-feature-authors">authors: @cart</div>
113+
114+
It is now possible to infinitely nest tuples of systems in a `.add_systems` call!
115+
116+
```rust
117+
app.add_systems(Update, (
118+
(a, (b, c, d, e), f),
119+
(g, h),
120+
i
121+
))
122+
```
123+
124+
At first glance, this might not seem very useful. But in combination with per-tuple configuration, it allows you to easily and cleanly express schedules:
125+
126+
```rust
127+
app.add_systems(Update, (
128+
(attack, defend).in_set(Combat).before(check_health)
129+
check_health,
130+
(handle_death, respawn).after(check_health)
131+
))
132+
```
133+
134+
`.chain()` has also been adapted to support arbitrary nesting! The ordering in the example above could be rephrased like this:
135+
136+
```rust
137+
app.add_systems(Update,
138+
(
139+
(attack, defend).in_set(Combat)
140+
check_health,
141+
(handle_death, respawn)
142+
).chain()
143+
)
144+
```
145+
146+
This will run `attack` and `defend` first (in parallel), then `check_health`, then `handle_death` and `respawn` (in parallel).
147+
148+
This allows for powerful and expressive "graph-like" ordering expressions:
149+
150+
```rust
151+
app.add_systems(Update,
152+
(
153+
(a, (b, c, d).chain()),
154+
(e, f),
155+
).chain()
156+
)
157+
```
158+
159+
This will run `a` in parallel with `b->c->d`, then after those have finished running it will run `e` and `f` in parallel.
25160

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

0 commit comments

Comments
 (0)