Skip to content

Commit 0ff2cd6

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>>, } ``` But proc macro for barriers is not implemented in this PR. Want to get early feedback before spending several more hours on it. WDYT?
1 parent 0c9f265 commit 0ff2cd6

13 files changed

+243
-0
lines changed

crates/bevy_ecs/macros/src/lib.rs

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

192194
fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State {
193195
#(
@@ -392,6 +394,7 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
392394
{
393395
type State = #state_struct_name<#punctuated_generic_idents>;
394396
type Item<'w, 's> = #struct_name #ty_generics;
397+
type BarrierList = ();
395398

396399
fn init_state(world: &mut #path::world::World, system_meta: &mut #path::system::SystemMeta) -> Self::State {
397400
#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: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ mod tests {
2626

2727
pub use crate as bevy_ecs;
2828
pub use crate::schedule::{IntoSystemSetConfigs, Schedule, SystemSet};
29+
use crate::system::{After, Before};
2930
pub use crate::system::{Res, ResMut};
3031
pub use crate::{prelude::World, system::Resource};
3132

@@ -1098,4 +1099,38 @@ mod tests {
10981099
assert!(schedule.graph().conflicting_systems().is_empty());
10991100
}
11001101
}
1102+
1103+
#[test]
1104+
fn test_before_after_from_param() {
1105+
#[derive(ScheduleLabel, Hash, PartialEq, Eq, Debug, Clone)]
1106+
struct BarrierTestSchedule;
1107+
1108+
#[derive(SystemSet, Debug, Hash, Eq, PartialEq, Default, Clone, Copy)]
1109+
struct MyBarrier;
1110+
1111+
#[derive(Resource)]
1112+
struct MyRes {
1113+
x: u32,
1114+
}
1115+
1116+
fn first(_before: Before<MyBarrier>, mut res: ResMut<MyRes>) {
1117+
assert_eq!(0, res.x);
1118+
res.x = 1;
1119+
}
1120+
1121+
fn second(_after: After<MyBarrier>, mut res: ResMut<MyRes>) {
1122+
assert_eq!(1, res.x);
1123+
res.x = 2;
1124+
}
1125+
1126+
let mut schedule = Schedule::new(BarrierTestSchedule);
1127+
schedule.add_systems((second, first));
1128+
1129+
let mut world = World::new();
1130+
world.insert_resource(MyRes { x: 0 });
1131+
1132+
schedule.run(&mut world);
1133+
1134+
assert_eq!(2, world.resource::<MyRes>().x);
1135+
}
11011136
}

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

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)