Skip to content

Commit 698194e

Browse files
committed
RFC: Before/After markers
Explicit ordering of systems is hard: during refactoring you forgot one edge, and your program befomes non-deterministic. This draft PR proposes implementing ordering based on function signature params (`SystemParam`). Basically, the idea is this. We introduce two new system params: ``` Before<T> After<T> ``` where `T` must be a `SystemSet + Default`. And it can be used like this: ``` #[derive(SystemSet)] struct MyBarrier; fn first(_before: Before<MyBarrier>) {} fn second(_after: After<MyBarrier>) {} ``` Now if `first` and `second` are added to the same schedule, they will be ordered like if `first.before(after)` was done explicitly. `SystemParam` are composable, so it is possible to build higher level constructs on top of it. For example, we may want to schedule all event writers before all event writers: ``` #[derive(SystemParam)] struct MyOrderedEventWriter<T> { writer: EventWriter<T>, after: After<MyOrderedEventWriterBarrier<T>>, } #[derive(SystemParam)] struct MyOrderedEventWriter<T> { reader: EventReader<T>, after: Before<MyOrderedEventWriterBarrier<T>>, } ``` Want to get early feedback before spending several more hours on it. WDYT?
1 parent 0c9f265 commit 698194e

13 files changed

+320
-0
lines changed

crates/bevy_ecs/macros/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ pub fn impl_param_set(_input: TokenStream) -> TokenStream {
188188
{
189189
type State = (#(#param::State,)*);
190190
type Item<'w, 's> = ParamSet<'w, 's, (#(#param,)*)>;
191+
type BarrierList = (#(#param::BarrierList,)*);
191192

192193
fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State {
193194
#(
@@ -392,6 +393,7 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
392393
{
393394
type State = #state_struct_name<#punctuated_generic_idents>;
394395
type Item<'w, 's> = #struct_name #ty_generics;
396+
type BarrierList = ();
395397

396398
fn init_state(world: &mut #path::world::World, system_meta: &mut #path::system::SystemMeta) -> Self::State {
397399
#state_struct_name {

crates/bevy_ecs/src/removal_detection.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ unsafe impl<'a> ReadOnlySystemParam for &'a RemovedComponentEvents {}
263263
unsafe impl<'a> SystemParam for &'a RemovedComponentEvents {
264264
type State = ();
265265
type Item<'w, 's> = &'w RemovedComponentEvents;
266+
type BarrierList = ();
266267

267268
fn init_state(_world: &mut World, _system_meta: &mut SystemMeta) -> Self::State {}
268269

crates/bevy_ecs/src/schedule/config.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,18 @@ impl SystemConfigs {
7979
fn new_system(system: BoxedSystem) -> Self {
8080
// include system in its default sets
8181
let sets = system.default_system_sets().into_iter().collect();
82+
let mut dependencies = Vec::new();
83+
for before in system.before_list() {
84+
dependencies.push(Dependency::new(DependencyKind::Before, before));
85+
}
86+
for after in system.after_list() {
87+
dependencies.push(Dependency::new(DependencyKind::After, after));
88+
}
8289
Self::NodeConfig(SystemConfig {
8390
node: system,
8491
graph_info: GraphInfo {
8592
sets,
93+
dependencies,
8694
..Default::default()
8795
},
8896
conditions: Vec::new(),

crates/bevy_ecs/src/schedule/mod.rs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,17 @@ pub use self::graph_utils::NodeId;
2222
#[cfg(test)]
2323
mod tests {
2424
use super::*;
25+
use bevy_ecs_macros::SystemParam;
26+
use std::fmt;
27+
use std::fmt::Debug;
28+
use std::hash::{Hash, Hasher};
29+
use std::marker::PhantomData;
2530
use std::sync::atomic::{AtomicU32, Ordering};
2631

2732
pub use crate as bevy_ecs;
33+
use crate::event::{Event, EventReader, EventWriter, Events};
2834
pub use crate::schedule::{IntoSystemSetConfigs, Schedule, SystemSet};
35+
use crate::system::{After, Before};
2936
pub use crate::system::{Res, ResMut};
3037
pub use crate::{prelude::World, system::Resource};
3138

@@ -1098,4 +1105,114 @@ mod tests {
10981105
assert!(schedule.graph().conflicting_systems().is_empty());
10991106
}
11001107
}
1108+
1109+
#[test]
1110+
fn test_before_after_from_param() {
1111+
#[derive(SystemSet, Debug, Hash, Eq, PartialEq, Default, Clone, Copy)]
1112+
struct MyBarrier;
1113+
1114+
#[derive(Resource)]
1115+
struct MyRes {
1116+
x: u32,
1117+
}
1118+
1119+
fn first(_before: Before<MyBarrier>, mut res: ResMut<MyRes>) {
1120+
assert_eq!(0, res.x);
1121+
res.x = 1;
1122+
}
1123+
1124+
fn second(_after: After<MyBarrier>, mut res: ResMut<MyRes>) {
1125+
assert_eq!(1, res.x);
1126+
res.x = 2;
1127+
}
1128+
1129+
let mut schedule = Schedule::default();
1130+
schedule.add_systems((second, first));
1131+
1132+
let mut world = World::new();
1133+
world.insert_resource(MyRes { x: 0 });
1134+
1135+
schedule.run(&mut world);
1136+
1137+
assert_eq!(2, world.resource::<MyRes>().x);
1138+
}
1139+
1140+
// Test `Before`/`After` work when hidden inside `#[derive(SystemParam)]`.
1141+
#[test]
1142+
fn test_before_after_derive_system_param() {
1143+
#[derive(SystemSet)]
1144+
struct MyEventBarrier<T>(PhantomData<T>);
1145+
1146+
impl<T> Default for MyEventBarrier<T> {
1147+
fn default() -> Self {
1148+
Self(PhantomData)
1149+
}
1150+
}
1151+
1152+
impl<T> Clone for MyEventBarrier<T> {
1153+
fn clone(&self) -> Self {
1154+
Self(PhantomData)
1155+
}
1156+
}
1157+
1158+
impl<T> Copy for MyEventBarrier<T> {}
1159+
1160+
impl<T> PartialEq for MyEventBarrier<T> {
1161+
fn eq(&self, _other: &Self) -> bool {
1162+
true
1163+
}
1164+
}
1165+
1166+
impl<T> Eq for MyEventBarrier<T> {}
1167+
1168+
impl<T> Hash for MyEventBarrier<T> {
1169+
fn hash<H: Hasher>(&self, _state: &mut H) {}
1170+
}
1171+
1172+
impl<T> Debug for MyEventBarrier<T> {
1173+
fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
1174+
Ok(())
1175+
}
1176+
}
1177+
1178+
#[derive(SystemParam)]
1179+
struct MyBarrierEventReader<'w, 's, T: Event> {
1180+
reader: EventReader<'w, 's, T>,
1181+
_before: Before<MyEventBarrier<T>>,
1182+
}
1183+
1184+
#[derive(SystemParam)]
1185+
struct MyBarrierEventWriter<'w, T: Event> {
1186+
writer: EventWriter<'w, T>,
1187+
_after: After<MyEventBarrier<T>>,
1188+
}
1189+
1190+
#[derive(Event)]
1191+
struct MyEvent;
1192+
1193+
#[derive(Resource)]
1194+
struct MyRes(bool);
1195+
1196+
fn read(mut reader: MyBarrierEventReader<MyEvent>) {
1197+
assert_eq!(1, reader.reader.read().count());
1198+
}
1199+
1200+
fn write(mut writer: MyBarrierEventWriter<MyEvent>, mut res: ResMut<MyRes>) {
1201+
writer.writer.send(MyEvent);
1202+
assert_eq!(false, res.0);
1203+
res.0 = true;
1204+
}
1205+
1206+
let mut schedule = Schedule::default();
1207+
1208+
let mut world = World::default();
1209+
world.insert_resource(MyRes(false));
1210+
world.init_resource::<Events<MyEvent>>();
1211+
1212+
schedule.add_systems((read, write));
1213+
1214+
schedule.run(&mut world);
1215+
1216+
assert_eq!(true, world.resource::<MyRes>().0);
1217+
}
11011218
}

crates/bevy_ecs/src/system/adapter_system.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,14 @@ where
146146
fn default_system_sets(&self) -> Vec<InternedSystemSet> {
147147
self.system.default_system_sets()
148148
}
149+
150+
fn before_list(&self) -> Vec<InternedSystemSet> {
151+
Vec::new()
152+
}
153+
154+
fn after_list(&self) -> Vec<InternedSystemSet> {
155+
Vec::new()
156+
}
149157
}
150158

151159
// SAFETY: The inner system is read-only.

crates/bevy_ecs/src/system/barrier.rs

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
use bevy_utils::all_tuples;
2+
3+
use crate::component::Tick;
4+
use crate::prelude::World;
5+
use crate::schedule::{InternedSystemSet, SystemSet};
6+
use crate::system::{SystemMeta, SystemParam};
7+
use crate::world::unsafe_world_cell::UnsafeWorldCell;
8+
use std::marker::PhantomData;
9+
10+
/// List of barrier dependencies for the system.
11+
pub trait BarrierList {
12+
/// System sets before this system.
13+
fn before_list() -> Vec<InternedSystemSet>;
14+
/// System sets after this system.
15+
fn after_list() -> Vec<InternedSystemSet>;
16+
}
17+
18+
/// System param to mark current system to run before a given system set.
19+
pub struct Before<S: SystemSet + Default> {
20+
marker: PhantomData<S>,
21+
}
22+
23+
/// System param to mark current system to run after a given system set.
24+
pub struct After<S: SystemSet + Default> {
25+
marker: PhantomData<S>,
26+
}
27+
28+
unsafe impl<S: SystemSet + Default> SystemParam for Before<S> {
29+
type State = ();
30+
type Item<'world, 'state> = Self;
31+
type BarrierList = BeforeBarrierList<S>;
32+
33+
fn init_state(_world: &mut World, _system_meta: &mut SystemMeta) -> Self::State {
34+
()
35+
}
36+
37+
unsafe fn get_param<'world, 'state>(
38+
_state: &'state mut Self::State,
39+
_system_meta: &SystemMeta,
40+
_world: UnsafeWorldCell<'world>,
41+
_change_tick: Tick,
42+
) -> Self::Item<'world, 'state> {
43+
Before {
44+
marker: PhantomData,
45+
}
46+
}
47+
}
48+
49+
unsafe impl<S: SystemSet + Default> SystemParam for After<S> {
50+
type State = ();
51+
type Item<'world, 'state> = Self;
52+
53+
type BarrierList = AfterBarrierList<S>;
54+
55+
fn init_state(_world: &mut World, _system_meta: &mut SystemMeta) -> Self::State {
56+
()
57+
}
58+
59+
unsafe fn get_param<'world, 'state>(
60+
_state: &'state mut Self::State,
61+
_system_meta: &SystemMeta,
62+
_world: UnsafeWorldCell<'world>,
63+
_change_tick: Tick,
64+
) -> Self::Item<'world, 'state> {
65+
After {
66+
marker: PhantomData,
67+
}
68+
}
69+
}
70+
71+
#[doc(hidden)]
72+
pub struct BeforeBarrierList<S: SystemSet + Default>(PhantomData<S>);
73+
#[doc(hidden)]
74+
pub struct AfterBarrierList<S: SystemSet + Default>(PhantomData<S>);
75+
76+
impl<S: SystemSet + Default> BarrierList for BeforeBarrierList<S> {
77+
fn before_list() -> Vec<InternedSystemSet> {
78+
vec![S::default().intern()]
79+
}
80+
81+
fn after_list() -> Vec<InternedSystemSet> {
82+
Vec::new()
83+
}
84+
}
85+
86+
impl<S: SystemSet + Default> BarrierList for AfterBarrierList<S> {
87+
fn before_list() -> Vec<InternedSystemSet> {
88+
Vec::new()
89+
}
90+
91+
fn after_list() -> Vec<InternedSystemSet> {
92+
vec![S::default().intern()]
93+
}
94+
}
95+
96+
macro_rules! barrier_list_for_tuple {
97+
($($barrier: ident),*) => {
98+
impl<$($barrier),*> BarrierList for ($($barrier,)*)
99+
where
100+
$($barrier: BarrierList),*
101+
{
102+
fn before_list() -> Vec<InternedSystemSet> {
103+
let mut list = Vec::new();
104+
let _ignore_empty = &mut list;
105+
$(list.extend($barrier::before_list());)*
106+
list
107+
}
108+
109+
fn after_list() -> Vec<InternedSystemSet> {
110+
let mut list = Vec::new();
111+
let _ignore_empty = &mut list;
112+
$(list.extend($barrier::after_list());)*
113+
list
114+
}
115+
}
116+
}
117+
}
118+
119+
all_tuples!(barrier_list_for_tuple, 0, 16, P);

crates/bevy_ecs/src/system/combinator.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,22 @@ where
232232
default_sets.append(&mut self.b.default_system_sets());
233233
default_sets
234234
}
235+
236+
fn before_list(&self) -> Vec<InternedSystemSet> {
237+
self.a
238+
.before_list()
239+
.into_iter()
240+
.chain(self.b.before_list())
241+
.collect()
242+
}
243+
244+
fn after_list(&self) -> Vec<InternedSystemSet> {
245+
self.a
246+
.after_list()
247+
.into_iter()
248+
.chain(self.b.after_list())
249+
.collect()
250+
}
235251
}
236252

237253
/// SAFETY: Both systems are read-only, so any system created by combining them will only read from the world.

crates/bevy_ecs/src/system/exclusive_function_system.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,14 @@ where
155155
let set = crate::schedule::SystemTypeSet::<F>::new();
156156
vec![set.intern()]
157157
}
158+
159+
fn before_list(&self) -> Vec<InternedSystemSet> {
160+
Vec::new()
161+
}
162+
163+
fn after_list(&self) -> Vec<InternedSystemSet> {
164+
Vec::new()
165+
}
158166
}
159167

160168
/// A trait implemented for all exclusive system functions that can be used as [`System`]s.

crates/bevy_ecs/src/system/function_system.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use crate::{
1111
use bevy_utils::all_tuples;
1212
use std::{any::TypeId, borrow::Cow, marker::PhantomData};
1313

14+
use crate::system::barrier::BarrierList;
1415
#[cfg(feature = "trace")]
1516
use bevy_utils::tracing::{info_span, Span};
1617

@@ -533,6 +534,14 @@ where
533534
let set = crate::schedule::SystemTypeSet::<F>::new();
534535
vec![set.intern()]
535536
}
537+
538+
fn before_list(&self) -> Vec<InternedSystemSet> {
539+
<F::Param as SystemParam>::BarrierList::before_list()
540+
}
541+
542+
fn after_list(&self) -> Vec<InternedSystemSet> {
543+
<F::Param as SystemParam>::BarrierList::after_list()
544+
}
536545
}
537546

538547
/// SAFETY: `F`'s param is [`ReadOnlySystemParam`], so this system will only read from the world.

crates/bevy_ecs/src/system/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@
103103
//! - [`()` (unit primitive type)](https://doc.rust-lang.org/stable/std/primitive.unit.html)
104104
105105
mod adapter_system;
106+
mod barrier;
106107
mod combinator;
107108
mod commands;
108109
mod exclusive_function_system;
@@ -117,6 +118,7 @@ mod system_registry;
117118
use std::borrow::Cow;
118119

119120
pub use adapter_system::*;
121+
pub use barrier::*;
120122
pub use combinator::*;
121123
pub use commands::*;
122124
pub use exclusive_function_system::*;

crates/bevy_ecs/src/system/system.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ pub trait System: Send + Sync + 'static {
3939
/// Returns true if the system is [`Send`].
4040
fn is_send(&self) -> bool;
4141

42+
fn before_list(&self) -> Vec<InternedSystemSet>;
43+
fn after_list(&self) -> Vec<InternedSystemSet>;
44+
4245
/// Returns true if the system must be run exclusively.
4346
fn is_exclusive(&self) -> bool;
4447

0 commit comments

Comments
 (0)