Skip to content

Commit cc5b79c

Browse files
committed
collective_run_if for SystemConfigs via anonymous system sets
1 parent 3533c3d commit cc5b79c

File tree

4 files changed

+144
-6
lines changed

4 files changed

+144
-6
lines changed

crates/bevy_app/src/app.rs

+2
Original file line numberDiff line numberDiff line change
@@ -380,8 +380,10 @@ impl App {
380380
/// # fn system_a() {}
381381
/// # fn system_b() {}
382382
/// # fn system_c() {}
383+
/// # fn should_run() -> bool { true }
383384
/// #
384385
/// app.add_systems(Update, (system_a, system_b, system_c));
386+
/// app.add_systems(Update, (system_a, system_b).collective_run_if(should_run));
385387
/// ```
386388
pub fn add_systems<M>(
387389
&mut self,

crates/bevy_ecs/src/schedule/config.rs

+55
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ pub enum SystemConfigs {
5959
SystemConfig(SystemConfig),
6060
Configs {
6161
configs: Vec<SystemConfigs>,
62+
collective_conditions: Vec<BoxedCondition>,
6263
/// If `true`, adds `before -> after` ordering constraints between the successive elements.
6364
chained: bool,
6465
},
@@ -123,6 +124,20 @@ impl SystemConfigs {
123124
}
124125
}
125126

127+
fn collective_run_if_inner(&mut self, condition: BoxedCondition) {
128+
match self {
129+
SystemConfigs::SystemConfig(config) => {
130+
config.conditions.push(condition);
131+
}
132+
SystemConfigs::Configs {
133+
collective_conditions,
134+
..
135+
} => {
136+
collective_conditions.push(condition);
137+
}
138+
}
139+
}
140+
126141
fn distributive_run_if_inner<M>(&mut self, condition: impl Condition<M> + Clone) {
127142
match self {
128143
SystemConfigs::SystemConfig(config) => {
@@ -199,6 +214,40 @@ where
199214
self.into_configs().after(set)
200215
}
201216

217+
/// Add a single run condition for all contained systems.
218+
///
219+
/// The [`Condition`] will be evaluated at most once (per schedule run),
220+
/// when one of the contained systems is first run.
221+
///
222+
/// This is equivalent to adding each system to an common set and configuring
223+
/// the run condition on that set, as shown below:
224+
///
225+
/// # Examples
226+
///
227+
/// ```
228+
/// # use bevy_ecs::prelude::*;
229+
/// # let mut app = Schedule::new();
230+
/// # fn a() {}
231+
/// # fn b() {}
232+
/// # fn condition() -> bool { true }
233+
/// # #[derive(SystemSet, Debug, Eq, PartialEq, Hash, Clone, Copy)]
234+
/// # struct C;
235+
/// app.add_systems((a, b).collective_run_if(condition));
236+
/// app.add_systems((a, b).in_set(C)).configure_set(C.run_if(condition));
237+
/// ```
238+
///
239+
/// # Note
240+
///
241+
/// Because the condition will only be evaluated once, there is no guarantee that the condition
242+
/// is upheld after the first system has run. You need to make sure that no other systems that
243+
/// could invalidate the condition are scheduled inbetween the first and last run system.
244+
///
245+
/// Use [`distributive_run_if`](IntoSystemConfigs::distributive_run_if) if you want the
246+
/// condition to be evaluated for each individual system, right before one is run.
247+
fn collective_run_if<P>(self, condition: impl Condition<P>) -> SystemConfigs {
248+
self.into_configs().collective_run_if(condition)
249+
}
250+
202251
/// Add a run condition to each contained system.
203252
///
204253
/// Each system will receive its own clone of the [`Condition`] and will only run
@@ -319,6 +368,11 @@ impl IntoSystemConfigs<()> for SystemConfigs {
319368
self
320369
}
321370

371+
fn collective_run_if<M>(mut self, condition: impl Condition<M>) -> Self {
372+
self.collective_run_if_inner(new_condition(condition));
373+
self
374+
}
375+
322376
fn distributive_run_if<M>(mut self, condition: impl Condition<M> + Clone) -> SystemConfigs {
323377
self.distributive_run_if_inner(condition);
324378
self
@@ -364,6 +418,7 @@ macro_rules! impl_system_collection {
364418
let ($($sys,)*) = self;
365419
SystemConfigs::Configs {
366420
configs: vec![$($sys.into_configs(),)*],
421+
collective_conditions: Vec::new(),
367422
chained: false,
368423
}
369424
}

crates/bevy_ecs/src/schedule/schedule.rs

+59-6
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,10 @@ impl SystemSetNode {
349349
pub fn is_system_type(&self) -> bool {
350350
self.inner.system_type().is_some()
351351
}
352+
353+
pub fn is_anonymous(&self) -> bool {
354+
self.inner.is_anonymous()
355+
}
352356
}
353357

354358
/// A [`BoxedSystem`] with metadata, stored in a [`ScheduleGraph`].
@@ -525,7 +529,11 @@ impl ScheduleGraph {
525529
}
526530
}
527531
}
528-
SystemConfigs::Configs { configs, chained } => {
532+
SystemConfigs::Configs {
533+
configs,
534+
collective_conditions,
535+
chained,
536+
} => {
529537
let mut config_iter = configs.into_iter();
530538
let mut nodes_in_scope = Vec::new();
531539
let mut densely_chained = true;
@@ -536,9 +544,26 @@ impl ScheduleGraph {
536544
densely_chained: true
537545
}
538546
};
547+
let more_than_one_entry = config_iter.len() > 0;
548+
let anonymous_set = if more_than_one_entry && !collective_conditions.is_empty()
549+
{
550+
Some(AnonymousSet::new())
551+
} else {
552+
None
553+
};
554+
let prev = if let Some(anonymous_set) = anonymous_set {
555+
prev.in_set(anonymous_set)
556+
} else {
557+
prev
558+
};
539559
let mut previous_result = self.add_systems_inner(prev, true);
540560
densely_chained = previous_result.densely_chained;
541561
for current in config_iter {
562+
let current = if let Some(anonymous_set) = anonymous_set {
563+
current.in_set(anonymous_set)
564+
} else {
565+
current
566+
};
542567
let current_result = self.add_systems_inner(current, true);
543568
densely_chained = densely_chained && current_result.densely_chained;
544569
match (
@@ -608,7 +633,18 @@ impl ScheduleGraph {
608633
}
609634
} else {
610635
let more_than_one_entry = config_iter.len() > 1;
636+
let anonymous_set = if more_than_one_entry && !collective_conditions.is_empty()
637+
{
638+
Some(AnonymousSet::new())
639+
} else {
640+
None
641+
};
611642
for config in config_iter {
643+
let config = if let Some(anonymous_set) = anonymous_set {
644+
config.in_set(anonymous_set)
645+
} else {
646+
config
647+
};
612648
let result = self.add_systems_inner(config, ancestor_chained);
613649
densely_chained = densely_chained && result.densely_chained;
614650
if ancestor_chained {
@@ -705,8 +741,7 @@ impl ScheduleGraph {
705741
match self.system_set_ids.get(set) {
706742
Some(set_id) => {
707743
if id == set_id {
708-
let string = format!("{:?}", &set);
709-
return Err(ScheduleBuildError::HierarchyLoop(string));
744+
return Err(ScheduleBuildError::HierarchyLoop(self.get_node_name(id)));
710745
}
711746
}
712747
None => {
@@ -742,8 +777,7 @@ impl ScheduleGraph {
742777
match self.system_set_ids.get(set) {
743778
Some(set_id) => {
744779
if id == set_id {
745-
let string = format!("{:?}", &set);
746-
return Err(ScheduleBuildError::DependencyLoop(string));
780+
return Err(ScheduleBuildError::DependencyLoop(self.get_node_name(id)));
747781
}
748782
}
749783
None => {
@@ -1257,14 +1291,33 @@ impl ScheduleGraph {
12571291
name
12581292
}
12591293
}
1260-
NodeId::Set(_) => self.system_sets[id.index()].name(),
1294+
NodeId::Set(_) => {
1295+
let set = &self.system_sets[id.index()];
1296+
if set.is_anonymous() {
1297+
self.anonymous_set_name(id)
1298+
} else {
1299+
set.name()
1300+
}
1301+
}
12611302
};
12621303
if self.settings.use_shortnames {
12631304
name = bevy_utils::get_short_name(&name);
12641305
}
12651306
name
12661307
}
12671308

1309+
fn anonymous_set_name(&self, id: &NodeId) -> String {
1310+
format!(
1311+
"({})",
1312+
self.hierarchy
1313+
.graph
1314+
.edges_directed(*id, Direction::Outgoing)
1315+
.map(|(_, member_id, _)| self.get_node_name(&member_id))
1316+
.reduce(|a, b| format!("{a}, {b}"))
1317+
.unwrap_or_default()
1318+
)
1319+
}
1320+
12681321
fn get_node_kind(&self, id: &NodeId) -> &'static str {
12691322
match id {
12701323
NodeId::System(_) => "system",

crates/bevy_ecs/src/schedule/set.rs

+28
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::any::TypeId;
22
use std::fmt::Debug;
33
use std::hash::{Hash, Hasher};
44
use std::marker::PhantomData;
5+
use std::sync::atomic::{AtomicUsize, Ordering};
56

67
pub use bevy_ecs_macros::{ScheduleLabel, SystemSet};
78
use bevy_utils::define_boxed_label;
@@ -23,6 +24,10 @@ pub trait SystemSet: DynHash + Debug + Send + Sync + 'static {
2324
None
2425
}
2526

27+
/// Returns `true` if this system set is an [`AnonymousSet`].
28+
fn is_anonymous(&self) -> bool {
29+
false
30+
}
2631
/// Creates a boxed clone of the label corresponding to this system set.
2732
fn dyn_clone(&self) -> Box<dyn SystemSet>;
2833
}
@@ -102,6 +107,29 @@ impl<T> SystemSet for SystemTypeSet<T> {
102107
}
103108
}
104109

110+
/// A [`SystemSet`] implicitly created when using
111+
/// [`Schedule::add_systems`](super::Schedule::add_systems).
112+
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
113+
pub struct AnonymousSet(usize);
114+
115+
static NEXT_ANONYMOUS_SET_ID: AtomicUsize = AtomicUsize::new(0);
116+
117+
impl AnonymousSet {
118+
pub(crate) fn new() -> Self {
119+
Self(NEXT_ANONYMOUS_SET_ID.fetch_add(1, Ordering::Relaxed))
120+
}
121+
}
122+
123+
impl SystemSet for AnonymousSet {
124+
fn is_anonymous(&self) -> bool {
125+
true
126+
}
127+
128+
fn dyn_clone(&self) -> Box<dyn SystemSet> {
129+
Box::new(*self)
130+
}
131+
}
132+
105133
/// Types that can be converted into a [`SystemSet`].
106134
pub trait IntoSystemSet<Marker>: Sized {
107135
type Set: SystemSet;

0 commit comments

Comments
 (0)