From 9cdc6952da6f5e741acb46323be96f8a8ef0c62c Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Thu, 24 Jul 2025 11:16:18 -0700 Subject: [PATCH 01/32] add method to get system keys by --- crates/bevy_ecs/src/schedule/node.rs | 5 +++++ crates/bevy_ecs/src/schedule/schedule.rs | 15 +++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/crates/bevy_ecs/src/schedule/node.rs b/crates/bevy_ecs/src/schedule/node.rs index 75c5c71ae8ce8..7f50f4b418f5f 100644 --- a/crates/bevy_ecs/src/schedule/node.rs +++ b/crates/bevy_ecs/src/schedule/node.rs @@ -646,6 +646,11 @@ impl SystemSets { self.sets.get(key).map(|set| &**set) } + /// Returns the kkey for the given system set, returns None if it does not exist. + pub fn get_key(&self, set: InternedSystemSet) -> Option { + self.ids.get(&set).copied() + } + /// Returns the key for the given system set, inserting it into this /// container if it does not already exist. pub fn get_key_or_insert(&mut self, set: InternedSystemSet) -> SystemSetKey { diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index b911b59352b8a..514a97ede5e96 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -683,6 +683,8 @@ pub struct ScheduleGraph { hierarchy: Dag, /// Directed acyclic graph of the dependency (which systems/sets have to run before which other systems/sets) dependency: Dag, + /// Map of systems in each set + set_systems: HashMap>, ambiguous_with: UnGraph, /// Nodes that are allowed to have ambiguous ordering relationship with any other systems. pub ambiguous_with_all: HashSet, @@ -701,6 +703,7 @@ impl ScheduleGraph { system_sets: SystemSets::default(), hierarchy: Dag::new(), dependency: Dag::new(), + set_systems: HashMap::new(), ambiguous_with: UnGraph::default(), ambiguous_with_all: HashSet::default(), conflicting_systems: Vec::new(), @@ -899,6 +902,17 @@ impl ScheduleGraph { AnonymousSet::new(id) } + /// Returns iterator over all [`SystemId`]'s in a [SystemSet] + /// Returns `None` if the label is not found or the schedule is not built + pub fn systems_in_set(&self, system_set: impl IntoSystemSet) -> Option> { + if self.changed { + return None; + } + let set = system_set.into_system_set(); + let system_set_id = self.system_sets.get_key(set.intern())?; + self.set_systems.get(&system_set_id).cloned() + } + /// Update the internal graphs (hierarchy, dependency, ambiguity) by adding a single [`GraphInfo`] fn update_graphs(&mut self, id: NodeId, graph_info: GraphInfo) { self.changed = true; @@ -1036,6 +1050,7 @@ impl ScheduleGraph { // flatten: combine `in_set` with `ambiguous_with` information let ambiguous_with_flattened = self.get_ambiguous_with_flattened(&set_systems); + self.set_systems = set_systems; // check for conflicts let conflicting_systems = self.get_conflicting_systems( From 535aceef606801f3933f7fda6ee64f6f7f8fb609 Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Thu, 24 Jul 2025 13:07:43 -0700 Subject: [PATCH 02/32] remove systems --- crates/bevy_ecs/src/schedule/node.rs | 27 +++++++++ crates/bevy_ecs/src/schedule/schedule.rs | 77 ++++++++++++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/crates/bevy_ecs/src/schedule/node.rs b/crates/bevy_ecs/src/schedule/node.rs index 7f50f4b418f5f..85af6c8c4a69d 100644 --- a/crates/bevy_ecs/src/schedule/node.rs +++ b/crates/bevy_ecs/src/schedule/node.rs @@ -554,6 +554,24 @@ impl Systems { key } + pub fn remove(&mut self, key: SystemKey) -> bool { + let mut found = false; + if self.nodes.remove(key).is_some() { + found = true; + } + + if self.conditions.remove(key).is_some() { + found = true; + } + + if let Some(index) = self.uninit.iter().position(|value| *value == key) { + self.uninit.remove(index); + found = true; + } + + found + } + /// Returns `true` if all systems in this container have been initialized. pub fn is_initialized(&self) -> bool { self.uninit.is_empty() @@ -721,6 +739,15 @@ impl SystemSets { key } + pub fn remove(&mut self, key: SystemSetKey) -> bool { + self.sets.remove(key); + self.conditions.remove(key); + if let Some(index) = self.uninit.iter().position(|uninit| uninit.key == key) { + self.uninit.remove(index); + } + true + } + /// Returns `true` if all system sets' conditions in this container have /// been initialized. pub fn is_initialized(&self) -> bool { diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 514a97ede5e96..63f8d5fea0219 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -913,6 +913,38 @@ impl ScheduleGraph { self.set_systems.get(&system_set_id).cloned() } + /// Remove system from schedule. This will cause the schedule to be rebuilt the next time it is run. + /// This also removes and dependencies on that system. Use [`systems_in_set`] to get the [`SystemKey`] + /// Returns count of the number of systems removed + pub fn remove_systems(&mut self, system_set: impl IntoSystemSet) -> usize { + let set = system_set.into_system_set(); + let interned = set.intern(); + let Some(keys) = self.systems_in_set(set) else { + return 0; + }; + self.changed = true; + for &key in &keys { + self.systems.remove(key); + + self.hierarchy.graph.remove_node(key.into()); + self.dependency.graph.remove_node(key.into()); + self.ambiguous_with.remove_node(key.into()); + self.ambiguous_with_all.remove(&NodeId::from(key)); + } + + let Some(set_key) = self.system_sets.get_key(interned) else { + return 0; + }; + self.system_sets.remove(set_key); + self.set_systems.remove(&set_key); + self.hierarchy.graph.remove_node(set_key.into()); + self.dependency.graph.remove_node(set_key.into()); + self.ambiguous_with.remove_node(set_key.into()); + self.ambiguous_with_all.remove(&NodeId::from(set_key)); + + keys.len() + } + /// Update the internal graphs (hierarchy, dependency, ambiguity) by adding a single [`GraphInfo`] fn update_graphs(&mut self, id: NodeId, graph_info: GraphInfo) { self.changed = true; @@ -1365,6 +1397,7 @@ impl ScheduleGraph { .zip(schedule.systems.drain(..)) .zip(schedule.system_conditions.drain(..)) { + // TODO: change this code to just drop the system and conditions if the key doesn't exist self.systems.node_mut(key).inner = Some(system); *self.systems.get_conditions_mut(key).unwrap() = conditions; } @@ -1374,6 +1407,7 @@ impl ScheduleGraph { .drain(..) .zip(schedule.set_conditions.drain(..)) { + // TODO: change this code to just drop the system and conditions if the key doesn't exist *self.system_sets.get_conditions_mut(key).unwrap() = conditions; } @@ -2598,4 +2632,47 @@ mod tests { ); schedule.run(&mut world); } + + #[test] + fn get_a_system_key() { + fn test_system() {} + + let mut schedule = Schedule::default(); + schedule.add_systems(test_system); + let mut world = World::default(); + let _ = schedule.initialize(&mut world); + + let keys = schedule.graph().systems_in_set(test_system).unwrap(); + assert_eq!(keys.len(), 1); + } + + #[test] + fn get_system_keys_in_set() { + fn system_1() {} + fn system_2() {} + + let mut schedule = Schedule::default(); + schedule.add_systems((system_1, system_2).in_set(TestSet::First)); + let mut world = World::default(); + let _ = schedule.initialize(&mut world); + + let keys = schedule.graph().systems_in_set(TestSet::First).unwrap(); + assert_eq!(keys.len(), 2); + } + + #[test] + fn get_system_keys_with_same_name() { + fn test_system() {} + + let mut schedule = Schedule::default(); + schedule.add_systems((test_system, test_system)); + let mut world = World::default(); + let _ = schedule.initialize(&mut world); + + let keys = schedule.graph().systems_in_set(test_system).unwrap(); + assert_eq!(keys.len(), 2); + } + + #[test] + fn remove_a_system_after_running() {} } From 04ceb710a7dfee9721507699c301e0ef1d42cf4d Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Fri, 25 Jul 2025 14:54:32 -0700 Subject: [PATCH 03/32] add tests --- crates/bevy_ecs/src/schedule/node.rs | 4 +- crates/bevy_ecs/src/schedule/schedule.rs | 76 +++++++++++++++++++++--- 2 files changed, 71 insertions(+), 9 deletions(-) diff --git a/crates/bevy_ecs/src/schedule/node.rs b/crates/bevy_ecs/src/schedule/node.rs index 85af6c8c4a69d..70cee426f0df7 100644 --- a/crates/bevy_ecs/src/schedule/node.rs +++ b/crates/bevy_ecs/src/schedule/node.rs @@ -493,8 +493,8 @@ impl Systems { /// # Panics /// /// If the system with the given key does not exist in this container. - pub(crate) fn node_mut(&mut self, key: SystemKey) -> &mut SystemNode { - &mut self.nodes[key] + pub(crate) fn node_mut(&mut self, key: SystemKey) -> Option<&mut SystemNode> { + self.nodes.get_mut(key) } /// Returns `true` if the system with the given key has conditions. diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 63f8d5fea0219..bf68900eee5c2 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -379,6 +379,10 @@ impl Schedule { self } + pub fn remove_systems_in_set(&mut self, set: impl IntoSystemSet) -> usize { + self.graph.remove_systems(set) + } + /// Suppress warnings and errors that would result from systems in these sets having ambiguities /// (conflicting access but indeterminate order) with systems in `set`. #[track_caller] @@ -1398,8 +1402,13 @@ impl ScheduleGraph { .zip(schedule.system_conditions.drain(..)) { // TODO: change this code to just drop the system and conditions if the key doesn't exist - self.systems.node_mut(key).inner = Some(system); - *self.systems.get_conditions_mut(key).unwrap() = conditions; + if let Some(node) = self.systems.node_mut(key) { + node.inner = Some(system); + } + + if let Some(node_conditions) = self.systems.get_conditions_mut(key) { + *node_conditions = conditions; + } } for (key, conditions) in schedule @@ -1407,8 +1416,9 @@ impl ScheduleGraph { .drain(..) .zip(schedule.set_conditions.drain(..)) { - // TODO: change this code to just drop the system and conditions if the key doesn't exist - *self.system_sets.get_conditions_mut(key).unwrap() = conditions; + if let Some(node_conditions) = self.system_sets.get_conditions_mut(key) { + *node_conditions = conditions; + } } let (new_schedule, warnings) = self.build_schedule(world, ignored_ambiguities)?; @@ -1416,7 +1426,7 @@ impl ScheduleGraph { // move systems into new schedule for &key in &schedule.system_ids { - let system = self.systems.node_mut(key).inner.take().unwrap(); + let system = self.systems.node_mut(key).unwrap().inner.take().unwrap(); let conditions = core::mem::take(self.systems.get_conditions_mut(key).unwrap()); schedule.systems.push(system); schedule.system_conditions.push(conditions); @@ -2670,9 +2680,61 @@ mod tests { let _ = schedule.initialize(&mut world); let keys = schedule.graph().systems_in_set(test_system).unwrap(); - assert_eq!(keys.len(), 2); + assert_ne!(keys[0], keys[1]); + } + + #[test] + fn remove_a_system() { + fn system() {} + + let mut schedule = Schedule::default(); + schedule.add_systems(system); + let mut world = World::default(); + // TODO: needing to call initialize before calling remove_systems_in_set is a big footgun + schedule.initialize(&mut world).unwrap(); + + let remove_count = schedule.remove_systems_in_set(system); + assert_eq!(remove_count, 1); + + // schedule has changed, so we check initializing again + schedule.initialize(&mut world).unwrap(); + assert_eq!(schedule.graph().systems.len(), 0); + } + + #[test] + fn remove_multiple_systems() { + fn system() {} + + let mut schedule = Schedule::default(); + schedule.add_systems((system, system)); + let mut world = World::default(); + // TODO: needing to call initialize before calling remove_systems_in_set is a big footgun + let _ = schedule.initialize(&mut world); + + let remove_count = schedule.remove_systems_in_set(system); + assert_eq!(remove_count, 2); + + // schedule has changed, so we check initializing again + schedule.initialize(&mut world).unwrap(); + assert_eq!(schedule.graph().systems.len(), 0); } #[test] - fn remove_a_system_after_running() {} + fn remove_a_system_with_dependencies() { + fn system_1() {} + fn system_2() {} + + let mut schedule = Schedule::default(); + schedule.add_systems((system_1, system_2).chain()); + let mut world = World::default(); + // TODO: needing to call initialize before calling remove_systems_in_set is a big footgun + let _ = schedule.initialize(&mut world); + + let remove_count = schedule.remove_systems_in_set(system_1); + assert_eq!(remove_count, 1); + + // schedule has changed, so we check initializing again + schedule.initialize(&mut world).unwrap(); + assert_eq!(schedule.graph().systems.len(), 1); + } } From b557ac4dccdecdd7378bd3c5a4c8e74451ac100f Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Fri, 25 Jul 2025 16:14:10 -0700 Subject: [PATCH 04/32] switch to return result --- crates/bevy_ecs/src/schedule/error.rs | 11 ++++++ crates/bevy_ecs/src/schedule/schedule.rs | 48 +++++++++++++++--------- 2 files changed, 41 insertions(+), 18 deletions(-) diff --git a/crates/bevy_ecs/src/schedule/error.rs b/crates/bevy_ecs/src/schedule/error.rs index ec008e9c3fa69..e45fa21d99bda 100644 --- a/crates/bevy_ecs/src/schedule/error.rs +++ b/crates/bevy_ecs/src/schedule/error.rs @@ -259,3 +259,14 @@ impl ScheduleBuildWarning { } } } + +/// Error returned from some `Schedule` methods +#[derive(Error, Debug)] +pub enum ScheduleError { + /// Operation cannot be completed because the schedule has changed and `Schedule::initialize` needs to be called + #[error("Operation cannot be completed because the schedule has changed and `Schedule::initialize` needs to be called")] + Uninitialized, + /// Method could not find key + #[error("Not Found")] + NotFound, +} diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index bf68900eee5c2..3fb8cb019b64a 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -379,7 +379,10 @@ impl Schedule { self } - pub fn remove_systems_in_set(&mut self, set: impl IntoSystemSet) -> usize { + pub fn remove_systems_in_set( + &mut self, + set: impl IntoSystemSet, + ) -> Result { self.graph.remove_systems(set) } @@ -907,25 +910,37 @@ impl ScheduleGraph { } /// Returns iterator over all [`SystemId`]'s in a [SystemSet] - /// Returns `None` if the label is not found or the schedule is not built - pub fn systems_in_set(&self, system_set: impl IntoSystemSet) -> Option> { + /// Returns `ScheduleBuildError::Uninitialized` if schedule has been changed and `Self::initialize` + /// has not been called. + pub fn systems_in_set( + &self, + system_set: impl IntoSystemSet, + ) -> Result, ScheduleError> { if self.changed { - return None; + return Err(ScheduleError::Uninitialized); } let set = system_set.into_system_set(); - let system_set_id = self.system_sets.get_key(set.intern())?; - self.set_systems.get(&system_set_id).cloned() + let system_set_id = self + .system_sets + .get_key(set.intern()) + .ok_or(ScheduleError::NotFound)?; + self.set_systems + .get(&system_set_id) + .cloned() + .ok_or(ScheduleError::NotFound) } /// Remove system from schedule. This will cause the schedule to be rebuilt the next time it is run. /// This also removes and dependencies on that system. Use [`systems_in_set`] to get the [`SystemKey`] /// Returns count of the number of systems removed - pub fn remove_systems(&mut self, system_set: impl IntoSystemSet) -> usize { + pub fn remove_systems( + &mut self, + system_set: impl IntoSystemSet, + ) -> Result { let set = system_set.into_system_set(); let interned = set.intern(); - let Some(keys) = self.systems_in_set(set) else { - return 0; - }; + let keys = self.systems_in_set(set)?; + self.changed = true; for &key in &keys { self.systems.remove(key); @@ -937,7 +952,7 @@ impl ScheduleGraph { } let Some(set_key) = self.system_sets.get_key(interned) else { - return 0; + return Ok(keys.len()); }; self.system_sets.remove(set_key); self.set_systems.remove(&set_key); @@ -946,7 +961,7 @@ impl ScheduleGraph { self.ambiguous_with.remove_node(set_key.into()); self.ambiguous_with_all.remove(&NodeId::from(set_key)); - keys.len() + Ok(keys.len()) } /// Update the internal graphs (hierarchy, dependency, ambiguity) by adding a single [`GraphInfo`] @@ -2690,11 +2705,10 @@ mod tests { let mut schedule = Schedule::default(); schedule.add_systems(system); let mut world = World::default(); - // TODO: needing to call initialize before calling remove_systems_in_set is a big footgun schedule.initialize(&mut world).unwrap(); let remove_count = schedule.remove_systems_in_set(system); - assert_eq!(remove_count, 1); + assert_eq!(remove_count.unwrap(), 1); // schedule has changed, so we check initializing again schedule.initialize(&mut world).unwrap(); @@ -2708,11 +2722,10 @@ mod tests { let mut schedule = Schedule::default(); schedule.add_systems((system, system)); let mut world = World::default(); - // TODO: needing to call initialize before calling remove_systems_in_set is a big footgun let _ = schedule.initialize(&mut world); let remove_count = schedule.remove_systems_in_set(system); - assert_eq!(remove_count, 2); + assert_eq!(remove_count.unwrap(), 2); // schedule has changed, so we check initializing again schedule.initialize(&mut world).unwrap(); @@ -2727,11 +2740,10 @@ mod tests { let mut schedule = Schedule::default(); schedule.add_systems((system_1, system_2).chain()); let mut world = World::default(); - // TODO: needing to call initialize before calling remove_systems_in_set is a big footgun let _ = schedule.initialize(&mut world); let remove_count = schedule.remove_systems_in_set(system_1); - assert_eq!(remove_count, 1); + assert_eq!(remove_count.unwrap(), 1); // schedule has changed, so we check initializing again schedule.initialize(&mut world).unwrap(); From 6836140879f23d1f115ce6fd49b154f23d0d971c Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Fri, 25 Jul 2025 16:46:42 -0700 Subject: [PATCH 05/32] add minimal docs --- crates/bevy_ecs/src/schedule/node.rs | 6 ++++-- crates/bevy_ecs/src/schedule/schedule.rs | 15 ++++++++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/crates/bevy_ecs/src/schedule/node.rs b/crates/bevy_ecs/src/schedule/node.rs index 70cee426f0df7..8f9267838afbc 100644 --- a/crates/bevy_ecs/src/schedule/node.rs +++ b/crates/bevy_ecs/src/schedule/node.rs @@ -554,7 +554,8 @@ impl Systems { key } - pub fn remove(&mut self, key: SystemKey) -> bool { + /// Remove a system with [`SystemKey`] + pub(crate) fn remove(&mut self, key: SystemKey) -> bool { let mut found = false; if self.nodes.remove(key).is_some() { found = true; @@ -739,7 +740,8 @@ impl SystemSets { key } - pub fn remove(&mut self, key: SystemSetKey) -> bool { + /// Remove a set with a [`SystemSetKey`] + pub(crate) fn remove(&mut self, key: SystemSetKey) -> bool { self.sets.remove(key); self.conditions.remove(key); if let Some(index) = self.uninit.iter().position(|uninit| uninit.key == key) { diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 3fb8cb019b64a..5b6037d71c33d 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -379,11 +379,18 @@ impl Schedule { self } + /// Removes all systems in a [`SystemSet`]. This will cause the schedule to be rebuilt when + /// the schedule is run again. A [`ScheduleError`] is returned if the schedule needs to be + /// [`Schedule::initialize`]'d or the `set` is not found. + /// + /// Note that this can remove all systems of a type if you pass + /// the system to this function as systems implicitly create a set based + /// on the system type. pub fn remove_systems_in_set( &mut self, set: impl IntoSystemSet, ) -> Result { - self.graph.remove_systems(set) + self.graph.remove_systems_in_set(set) } /// Suppress warnings and errors that would result from systems in these sets having ambiguities @@ -930,10 +937,8 @@ impl ScheduleGraph { .ok_or(ScheduleError::NotFound) } - /// Remove system from schedule. This will cause the schedule to be rebuilt the next time it is run. - /// This also removes and dependencies on that system. Use [`systems_in_set`] to get the [`SystemKey`] - /// Returns count of the number of systems removed - pub fn remove_systems( + /// Remove all systems in a set and any dependencies on those systems and set. + pub fn remove_systems_in_set( &mut self, system_set: impl IntoSystemSet, ) -> Result { From fef6f97dd31b560fec5f37c2a687a019b1afa66a Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Fri, 25 Jul 2025 22:16:33 -0700 Subject: [PATCH 06/32] remove todo --- crates/bevy_ecs/src/schedule/schedule.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 5b6037d71c33d..42496fe885f0c 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -1421,7 +1421,6 @@ impl ScheduleGraph { .zip(schedule.systems.drain(..)) .zip(schedule.system_conditions.drain(..)) { - // TODO: change this code to just drop the system and conditions if the key doesn't exist if let Some(node) = self.systems.node_mut(key) { node.inner = Some(system); } From ae584057d1b3c598849b3fe7ae315eb82f17a4ee Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Fri, 25 Jul 2025 22:59:13 -0700 Subject: [PATCH 07/32] add an example --- crates/bevy_ecs/src/schedule/schedule.rs | 45 +++++++++++++++++++----- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 42496fe885f0c..88373e5e5d37a 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -386,6 +386,25 @@ impl Schedule { /// Note that this can remove all systems of a type if you pass /// the system to this function as systems implicitly create a set based /// on the system type. + /// + /// ## Example + /// ``` + /// # use bevy_ecs::prelude::*; + /// # + /// # fn my_system() {} + /// # + /// let mut schedule = Schedule::default(); + /// // add the system to the schedule + /// schedule.add_systems(my_system); + /// let mut world = World::default(); + /// + /// // we need to run initialize before removing the system to + /// // generate which systems are in which sets + /// schedule.initialize(&mut world).unwrap(); + /// + /// // remove the system + /// schedule.remove_systems_in_set(my_system); + /// ``` pub fn remove_systems_in_set( &mut self, set: impl IntoSystemSet, @@ -919,17 +938,16 @@ impl ScheduleGraph { /// Returns iterator over all [`SystemId`]'s in a [SystemSet] /// Returns `ScheduleBuildError::Uninitialized` if schedule has been changed and `Self::initialize` /// has not been called. - pub fn systems_in_set( + pub fn systems_in_set( &self, - system_set: impl IntoSystemSet, + system_set: InternedSystemSet, ) -> Result, ScheduleError> { if self.changed { return Err(ScheduleError::Uninitialized); } - let set = system_set.into_system_set(); let system_set_id = self .system_sets - .get_key(set.intern()) + .get_key(system_set) .ok_or(ScheduleError::NotFound)?; self.set_systems .get(&system_set_id) @@ -944,7 +962,7 @@ impl ScheduleGraph { ) -> Result { let set = system_set.into_system_set(); let interned = set.intern(); - let keys = self.systems_in_set(set)?; + let keys = self.systems_in_set(interned)?; self.changed = true; for &key in &keys { @@ -1844,7 +1862,7 @@ mod tests { use crate::{ error::{ignore, panic, DefaultErrorHandler, Result}, - prelude::{ApplyDeferred, Res, Resource}, + prelude::{ApplyDeferred, IntoSystemSet, Res, Resource}, schedule::{ tests::ResMut, IntoScheduleConfigs, Schedule, ScheduleBuildSettings, SystemSet, }, @@ -2671,7 +2689,10 @@ mod tests { let mut world = World::default(); let _ = schedule.initialize(&mut world); - let keys = schedule.graph().systems_in_set(test_system).unwrap(); + let keys = schedule + .graph() + .systems_in_set(test_system.into_system_set().intern()) + .unwrap(); assert_eq!(keys.len(), 1); } @@ -2685,7 +2706,10 @@ mod tests { let mut world = World::default(); let _ = schedule.initialize(&mut world); - let keys = schedule.graph().systems_in_set(TestSet::First).unwrap(); + let keys = schedule + .graph() + .systems_in_set(TestSet::First.into_system_set().intern()) + .unwrap(); assert_eq!(keys.len(), 2); } @@ -2698,7 +2722,10 @@ mod tests { let mut world = World::default(); let _ = schedule.initialize(&mut world); - let keys = schedule.graph().systems_in_set(test_system).unwrap(); + let keys = schedule + .graph() + .systems_in_set(test_system.into_system_set().intern()) + .unwrap(); assert_ne!(keys[0], keys[1]); } From 55c200c087370c4ac0b686ea920f4c6c80fe6c7d Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Fri, 25 Jul 2025 23:16:53 -0700 Subject: [PATCH 08/32] fix doc link --- crates/bevy_ecs/src/schedule/schedule.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 88373e5e5d37a..76d6a3efc18e5 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -935,7 +935,7 @@ impl ScheduleGraph { AnonymousSet::new(id) } - /// Returns iterator over all [`SystemId`]'s in a [SystemSet] + /// Returns iterator over all [`SystemKey`]'s in a [`SystemSet`] /// Returns `ScheduleBuildError::Uninitialized` if schedule has been changed and `Self::initialize` /// has not been called. pub fn systems_in_set( From e2b7dac49da5c02e165015c3415b83650aadb181 Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Sat, 26 Jul 2025 13:10:29 -0700 Subject: [PATCH 09/32] auto initialize if needed --- crates/bevy_ecs/src/schedule/schedule.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 76d6a3efc18e5..47269e7b8d034 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -398,17 +398,17 @@ impl Schedule { /// schedule.add_systems(my_system); /// let mut world = World::default(); /// - /// // we need to run initialize before removing the system to - /// // generate which systems are in which sets - /// schedule.initialize(&mut world).unwrap(); - /// /// // remove the system /// schedule.remove_systems_in_set(my_system); /// ``` pub fn remove_systems_in_set( &mut self, set: impl IntoSystemSet, + world: &mut World, ) -> Result { + if self.graph.changed { + self.graph.initialize(world); + } self.graph.remove_systems_in_set(set) } @@ -2736,9 +2736,8 @@ mod tests { let mut schedule = Schedule::default(); schedule.add_systems(system); let mut world = World::default(); - schedule.initialize(&mut world).unwrap(); - let remove_count = schedule.remove_systems_in_set(system); + let remove_count = schedule.remove_systems_in_set(system, &mut world); assert_eq!(remove_count.unwrap(), 1); // schedule has changed, so we check initializing again @@ -2753,9 +2752,8 @@ mod tests { let mut schedule = Schedule::default(); schedule.add_systems((system, system)); let mut world = World::default(); - let _ = schedule.initialize(&mut world); - let remove_count = schedule.remove_systems_in_set(system); + let remove_count = schedule.remove_systems_in_set(system, &mut world); assert_eq!(remove_count.unwrap(), 2); // schedule has changed, so we check initializing again @@ -2771,9 +2769,8 @@ mod tests { let mut schedule = Schedule::default(); schedule.add_systems((system_1, system_2).chain()); let mut world = World::default(); - let _ = schedule.initialize(&mut world); - let remove_count = schedule.remove_systems_in_set(system_1); + let remove_count = schedule.remove_systems_in_set(system_1, &mut world); assert_eq!(remove_count.unwrap(), 1); // schedule has changed, so we check initializing again From b0143c671cf1c68be3fa197161e5109aa0d3b61e Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Sat, 26 Jul 2025 13:28:33 -0700 Subject: [PATCH 10/32] fix doc example --- crates/bevy_ecs/src/schedule/schedule.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 47269e7b8d034..79e04299bcbb7 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -399,7 +399,7 @@ impl Schedule { /// let mut world = World::default(); /// /// // remove the system - /// schedule.remove_systems_in_set(my_system); + /// schedule.remove_systems_in_set(my_system, &mut world); /// ``` pub fn remove_systems_in_set( &mut self, From ab30bac0dae46337e3e61dc444749b6887b125e7 Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Sat, 26 Jul 2025 13:34:45 -0700 Subject: [PATCH 11/32] call the correct initialize --- crates/bevy_ecs/src/schedule/error.rs | 3 +++ crates/bevy_ecs/src/schedule/schedule.rs | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/schedule/error.rs b/crates/bevy_ecs/src/schedule/error.rs index e45fa21d99bda..db2ea32a222f2 100644 --- a/crates/bevy_ecs/src/schedule/error.rs +++ b/crates/bevy_ecs/src/schedule/error.rs @@ -269,4 +269,7 @@ pub enum ScheduleError { /// Method could not find key #[error("Not Found")] NotFound, + /// Error initializing schedule + #[error("{0}")] + ScheduleBuildError(ScheduleBuildError), } diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 79e04299bcbb7..0743c1b187dcd 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -407,7 +407,9 @@ impl Schedule { world: &mut World, ) -> Result { if self.graph.changed { - self.graph.initialize(world); + if let Err(e) = self.initialize(world) { + return Err(ScheduleError::ScheduleBuildError(e)); + } } self.graph.remove_systems_in_set(set) } From d0123e202227ea04eb13351e8ebaff5e8ddf59e8 Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Mon, 28 Jul 2025 15:19:44 -0700 Subject: [PATCH 12/32] add methods to app and schedules --- crates/bevy_app/src/app.rs | 32 +++++++++++++++++++++++- crates/bevy_app/src/sub_app.rs | 17 ++++++++++++- crates/bevy_ecs/src/schedule/schedule.rs | 12 +++++++++ 3 files changed, 59 insertions(+), 2 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index e2755078c6535..32c3014a68004 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -14,7 +14,7 @@ use bevy_ecs::{ event::{event_update_system, EventCursor}, intern::Interned, prelude::*, - schedule::{InternedSystemSet, ScheduleBuildSettings, ScheduleLabel}, + schedule::{InternedSystemSet, ScheduleBuildSettings, ScheduleError, ScheduleLabel}, system::{IntoObserverSystem, ScheduleSystem, SystemId, SystemInput}, }; use bevy_platform::collections::HashMap; @@ -313,6 +313,36 @@ impl App { self } + /// Removes all systems in a [`SystemSet`]. This will cause the schedule to be rebuilt when + /// the schedule is run again and can be slow. A [`ScheduleError`] is returned if the schedule needs to be + /// [`Schedule::initialize`]'d or the `set` is not found. + /// + /// Note that this can remove all systems of a type if you pass + /// the system to this function as systems implicitly create a set based + /// on the system type. + /// + /// ## Example + /// ``` + /// # use bevy_app::prelude::*; + /// # + /// # let mut app = App::new(); + /// # fn system_a() {} + /// # fn system_b() {} + /// + /// // add the system + /// app.add_systems(Update, system_a); + /// + /// // remove the system + /// app.remove_systems_in_set(Update, system_a); + /// ``` + pub fn remove_systems_in_set( + &mut self, + schedule: impl ScheduleLabel, + set: impl IntoSystemSet, + ) -> Result { + self.main_mut().remove_systems_in_set(schedule, set) + } + /// Registers a system and returns a [`SystemId`] so it can later be called by [`World::run_system`]. /// /// It's possible to register the same systems more than once, they'll be stored separately. diff --git a/crates/bevy_app/src/sub_app.rs b/crates/bevy_app/src/sub_app.rs index 56a496f2b59bd..fb074d6d9684d 100644 --- a/crates/bevy_app/src/sub_app.rs +++ b/crates/bevy_app/src/sub_app.rs @@ -3,7 +3,10 @@ use alloc::{boxed::Box, string::String, vec::Vec}; use bevy_ecs::{ event::EventRegistry, prelude::*, - schedule::{InternedScheduleLabel, InternedSystemSet, ScheduleBuildSettings, ScheduleLabel}, + schedule::{ + InternedScheduleLabel, InternedSystemSet, ScheduleBuildSettings, ScheduleError, + ScheduleLabel, + }, system::{ScheduleSystem, SystemId, SystemInput}, }; use bevy_platform::collections::{HashMap, HashSet}; @@ -219,6 +222,18 @@ impl SubApp { self } + /// See [`App::remove_systems_in_set`] + pub fn remove_systems_in_set( + &mut self, + schedule: impl ScheduleLabel, + set: impl IntoSystemSet, + ) -> Result { + self.world + .resource_scope(|world, mut schedules: Mut| { + schedules.remove_systems_in_set(schedule, set, world) + }) + } + /// See [`App::register_system`]. pub fn register_system( &mut self, diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 0743c1b187dcd..78d0a407c67f0 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -179,6 +179,18 @@ impl Schedules { self } + /// Removes all systems in a [`SystemSet`]. This will cause the schedule to be rebuilt when + /// the schedule is run again. A [`ScheduleError`] is returned if the schedule needs to be + /// [`Schedule::initialize`]'d or the `set` is not found. + pub fn remove_systems_in_set( + &mut self, + schedule: impl ScheduleLabel, + set: impl IntoSystemSet, + world: &mut World, + ) -> Result { + self.entry(schedule).remove_systems_in_set(set, world) + } + /// Configures a collection of system sets in the provided schedule, adding any sets that do not exist. #[track_caller] pub fn configure_sets( From 28190608899a7714f1c2e308dd9045078e8055d0 Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Mon, 28 Jul 2025 15:24:50 -0700 Subject: [PATCH 13/32] add a release note --- .../release-notes/remove_systems.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 release-content/release-notes/remove_systems.md diff --git a/release-content/release-notes/remove_systems.md b/release-content/release-notes/remove_systems.md new file mode 100644 index 0000000000000..bdcf82b3936b6 --- /dev/null +++ b/release-content/release-notes/remove_systems.md @@ -0,0 +1,19 @@ +--- +title: Remove Systems from Schedules +authors: ["@hymm"] +pull_requests: [20298] +--- + +A long requested feature has come to Bevy! You can now remove systems from a schedule. The previous recommened way of preventing a system from running was to use RunConditions. This is still the recommended way for most situations, because actually removing the system causes the schedule to be rebuilt. This can potentially be slow as a bunch of graph logic needs to be rechecked. But for situations where this is not a problem, you can now call `remove_systems_in_set`. + +```rust +app.add_systems((system_a, (system_b, system_c).in_set(MySet))); + +// remove a system +app.remove_systems_in_set(system_a); + +// remove systems in a set +app.remove_systems_in_set(MySet) +``` + + From b1eb124510334c27c47186ec37e3ab2bcae6ba47 Mon Sep 17 00:00:00 2001 From: Mike Date: Mon, 28 Jul 2025 22:35:39 -0700 Subject: [PATCH 14/32] docs cleanup Co-authored-by: Barrett Ray <66580279+onkoe@users.noreply.github.com> --- crates/bevy_ecs/src/schedule/node.rs | 2 +- crates/bevy_ecs/src/schedule/schedule.rs | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ecs/src/schedule/node.rs b/crates/bevy_ecs/src/schedule/node.rs index 8f9267838afbc..c3f9a403df0de 100644 --- a/crates/bevy_ecs/src/schedule/node.rs +++ b/crates/bevy_ecs/src/schedule/node.rs @@ -665,7 +665,7 @@ impl SystemSets { self.sets.get(key).map(|set| &**set) } - /// Returns the kkey for the given system set, returns None if it does not exist. + /// Returns the key for the given system set, returns None if it does not exist. pub fn get_key(&self, set: InternedSystemSet) -> Option { self.ids.get(&set).copied() } diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 78d0a407c67f0..373a086f8af6c 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -949,9 +949,16 @@ impl ScheduleGraph { AnonymousSet::new(id) } - /// Returns iterator over all [`SystemKey`]'s in a [`SystemSet`] - /// Returns `ScheduleBuildError::Uninitialized` if schedule has been changed and `Self::initialize` - /// has not been called. + /// Returns a `Vec` containing all [`SystemKey`]s in a [`SystemSet`]. + /// + /// # Errors + /// + /// This method may return an error. It'll be: + /// + /// - `ScheduleError::Uninitialized` if the schedule has been changed, + /// and `Self::initialize` has not been called. + /// - `ScheduleError::NotFound` if `system_set` isn't present in the + /// schedule. pub fn systems_in_set( &self, system_set: InternedSystemSet, From 2791ec55173858b12f9fecfb59701aba781cbff1 Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Mon, 28 Jul 2025 21:56:48 -0700 Subject: [PATCH 15/32] fix doc test --- crates/bevy_app/src/sub_app.rs | 7 +++---- crates/bevy_ecs/src/schedule/error.rs | 9 ++++++--- crates/bevy_ecs/src/schedule/schedule.rs | 8 +++++--- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/crates/bevy_app/src/sub_app.rs b/crates/bevy_app/src/sub_app.rs index fb074d6d9684d..164e1f4758d2e 100644 --- a/crates/bevy_app/src/sub_app.rs +++ b/crates/bevy_app/src/sub_app.rs @@ -228,10 +228,9 @@ impl SubApp { schedule: impl ScheduleLabel, set: impl IntoSystemSet, ) -> Result { - self.world - .resource_scope(|world, mut schedules: Mut| { - schedules.remove_systems_in_set(schedule, set, world) - }) + self.world.schedule_scope(schedule, |world, schedule| { + schedule.remove_systems_in_set(set, world) + }) } /// See [`App::register_system`]. diff --git a/crates/bevy_ecs/src/schedule/error.rs b/crates/bevy_ecs/src/schedule/error.rs index db2ea32a222f2..9a6607f77dd67 100644 --- a/crates/bevy_ecs/src/schedule/error.rs +++ b/crates/bevy_ecs/src/schedule/error.rs @@ -266,9 +266,12 @@ pub enum ScheduleError { /// Operation cannot be completed because the schedule has changed and `Schedule::initialize` needs to be called #[error("Operation cannot be completed because the schedule has changed and `Schedule::initialize` needs to be called")] Uninitialized, - /// Method could not find key - #[error("Not Found")] - NotFound, + /// Method could not find set + #[error("Set not found")] + SetNotFound, + /// Schedule not found + #[error("Schedule not found.")] + ScheduleNotFound, /// Error initializing schedule #[error("{0}")] ScheduleBuildError(ScheduleBuildError), diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 373a086f8af6c..ee5528a1269c9 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -188,7 +188,9 @@ impl Schedules { set: impl IntoSystemSet, world: &mut World, ) -> Result { - self.entry(schedule).remove_systems_in_set(set, world) + self.get_mut(schedule) + .ok_or(ScheduleError::ScheduleNotFound)? + .remove_systems_in_set(set, world) } /// Configures a collection of system sets in the provided schedule, adding any sets that do not exist. @@ -969,11 +971,11 @@ impl ScheduleGraph { let system_set_id = self .system_sets .get_key(system_set) - .ok_or(ScheduleError::NotFound)?; + .ok_or(ScheduleError::SetNotFound)?; self.set_systems .get(&system_set_id) .cloned() - .ok_or(ScheduleError::NotFound) + .ok_or(ScheduleError::SetNotFound) } /// Remove all systems in a set and any dependencies on those systems and set. From c85d7e46f96877e469e40f03d5ee378152b96b1b Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Mon, 28 Jul 2025 22:19:54 -0700 Subject: [PATCH 16/32] edit release note a bit --- release-content/release-notes/remove_systems.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/release-content/release-notes/remove_systems.md b/release-content/release-notes/remove_systems.md index bdcf82b3936b6..267a3c3cf1158 100644 --- a/release-content/release-notes/remove_systems.md +++ b/release-content/release-notes/remove_systems.md @@ -4,7 +4,12 @@ authors: ["@hymm"] pull_requests: [20298] --- -A long requested feature has come to Bevy! You can now remove systems from a schedule. The previous recommened way of preventing a system from running was to use RunConditions. This is still the recommended way for most situations, because actually removing the system causes the schedule to be rebuilt. This can potentially be slow as a bunch of graph logic needs to be rechecked. But for situations where this is not a problem, you can now call `remove_systems_in_set`. +A long requested feature has come to Bevy! You can now remove systems from a schedule. +The previous recommended way of preventing a scheduled system from running was to use `RunCondition`'s. +You will still use this for most situations as removing a system will cause the schedule to be rebuilt. +This process can be slow since the schedule checking logic is complex. But in situations where this is +not a problem, you can now call `remove_systems_in_set`. The advantage of this is that this will remove the +cost of the run condition being checked. ```rust app.add_systems((system_a, (system_b, system_c).in_set(MySet))); @@ -15,5 +20,3 @@ app.remove_systems_in_set(system_a); // remove systems in a set app.remove_systems_in_set(MySet) ``` - - From f7cc75cc6853a78d8d2fa1672e45ab1a61c8000a Mon Sep 17 00:00:00 2001 From: Mike Date: Mon, 28 Jul 2025 22:44:17 -0700 Subject: [PATCH 17/32] remove all uninit sets Co-authored-by: Chris Russell <8494645+chescock@users.noreply.github.com> --- crates/bevy_ecs/src/schedule/node.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/bevy_ecs/src/schedule/node.rs b/crates/bevy_ecs/src/schedule/node.rs index c3f9a403df0de..e92103a42b4bb 100644 --- a/crates/bevy_ecs/src/schedule/node.rs +++ b/crates/bevy_ecs/src/schedule/node.rs @@ -744,9 +744,7 @@ impl SystemSets { pub(crate) fn remove(&mut self, key: SystemSetKey) -> bool { self.sets.remove(key); self.conditions.remove(key); - if let Some(index) = self.uninit.iter().position(|uninit| uninit.key == key) { - self.uninit.remove(index); - } + self.uninit.retain(|uninit| uninit.key != key); true } From 2e9f5bbe90e36d393df677e9d689b017d168dcf9 Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Mon, 28 Jul 2025 22:37:30 -0700 Subject: [PATCH 18/32] more doc cleanup --- crates/bevy_ecs/src/schedule/node.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/crates/bevy_ecs/src/schedule/node.rs b/crates/bevy_ecs/src/schedule/node.rs index e92103a42b4bb..ce9f16d826a93 100644 --- a/crates/bevy_ecs/src/schedule/node.rs +++ b/crates/bevy_ecs/src/schedule/node.rs @@ -487,12 +487,8 @@ impl Systems { self.nodes.get_mut(key).and_then(|node| node.get_mut()) } - /// Returns a mutable reference to the system with the given key, panicking - /// if it does not exist. - /// - /// # Panics - /// - /// If the system with the given key does not exist in this container. + /// Returns a mutable reference to the system with the given key. Will return + /// `None` if the key does not exist. pub(crate) fn node_mut(&mut self, key: SystemKey) -> Option<&mut SystemNode> { self.nodes.get_mut(key) } From 420e6ff2bf6c9d63dab27c4cd6fbc51abe048b2b Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Mon, 28 Jul 2025 22:49:24 -0700 Subject: [PATCH 19/32] convert errors using `From` --- crates/bevy_ecs/src/schedule/error.rs | 6 ++++++ crates/bevy_ecs/src/schedule/schedule.rs | 6 ++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ecs/src/schedule/error.rs b/crates/bevy_ecs/src/schedule/error.rs index 9a6607f77dd67..caa34a1d40a6a 100644 --- a/crates/bevy_ecs/src/schedule/error.rs +++ b/crates/bevy_ecs/src/schedule/error.rs @@ -276,3 +276,9 @@ pub enum ScheduleError { #[error("{0}")] ScheduleBuildError(ScheduleBuildError), } + +impl From for ScheduleError { + fn from(value: ScheduleBuildError) -> Self { + Self::ScheduleBuildError(value) + } +} diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index ee5528a1269c9..54d5558b517e9 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -421,9 +421,7 @@ impl Schedule { world: &mut World, ) -> Result { if self.graph.changed { - if let Err(e) = self.initialize(world) { - return Err(ScheduleError::ScheduleBuildError(e)); - } + self.initialize(world)?; } self.graph.remove_systems_in_set(set) } @@ -956,7 +954,7 @@ impl ScheduleGraph { /// # Errors /// /// This method may return an error. It'll be: - /// + /// /// - `ScheduleError::Uninitialized` if the schedule has been changed, /// and `Self::initialize` has not been called. /// - `ScheduleError::NotFound` if `system_set` isn't present in the From 3a4e9318ae4497b89bfe4ae4e71e4eee446b2e42 Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Tue, 29 Jul 2025 09:27:46 -0700 Subject: [PATCH 20/32] remove spaces from release note --- release-content/release-notes/remove_systems.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/release-content/release-notes/remove_systems.md b/release-content/release-notes/remove_systems.md index 267a3c3cf1158..13dbe63b97b44 100644 --- a/release-content/release-notes/remove_systems.md +++ b/release-content/release-notes/remove_systems.md @@ -4,10 +4,10 @@ authors: ["@hymm"] pull_requests: [20298] --- -A long requested feature has come to Bevy! You can now remove systems from a schedule. -The previous recommended way of preventing a scheduled system from running was to use `RunCondition`'s. +A long requested feature has come to Bevy! You can now remove systems from a schedule. +The previous recommended way of preventing a scheduled system from running was to use `RunCondition`'s. You will still use this for most situations as removing a system will cause the schedule to be rebuilt. -This process can be slow since the schedule checking logic is complex. But in situations where this is +This process can be slow since the schedule checking logic is complex. But in situations where this is not a problem, you can now call `remove_systems_in_set`. The advantage of this is that this will remove the cost of the run condition being checked. From 80f4a39841571ae1c54433a4b84df13fd3292487 Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Thu, 28 Aug 2025 13:49:27 -0700 Subject: [PATCH 21/32] add policy enum --- crates/bevy_ecs/src/schedule/schedule.rs | 38 ++++++++++++++++++++---- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 54d5558b517e9..02e334985eff4 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -190,7 +190,7 @@ impl Schedules { ) -> Result { self.get_mut(schedule) .ok_or(ScheduleError::ScheduleNotFound)? - .remove_systems_in_set(set, world) + .remove_systems_in_set(set, world, ScheduleCleanupPolicy::RemoveSetAndSystems) } /// Configures a collection of system sets in the provided schedule, adding any sets that do not exist. @@ -419,11 +419,12 @@ impl Schedule { &mut self, set: impl IntoSystemSet, world: &mut World, + policy: ScheduleCleanupPolicy, ) -> Result { if self.graph.changed { self.initialize(world)?; } - self.graph.remove_systems_in_set(set) + self.graph.remove_systems_in_set(set, policy) } /// Suppress warnings and errors that would result from systems in these sets having ambiguities @@ -980,6 +981,7 @@ impl ScheduleGraph { pub fn remove_systems_in_set( &mut self, system_set: impl IntoSystemSet, + policy: ScheduleCleanupPolicy, ) -> Result { let set = system_set.into_system_set(); let interned = set.intern(); @@ -1536,6 +1538,20 @@ pub enum ReportCycles { Dependency, } +/// Policy to use when removing systems. +#[derive(Default)] +pub enum ScheduleCleanupPolicy { + /// Remove the set and any systems in the set. Note that this will break any transient dependencies on + /// that set or systmes. This does not remove sets that might sub sets of the set. + #[default] + RemoveSetAndSystems, + /// Remove only the systems in the set. This is useful if you want to keep the run conditions and dependencies + /// on the set and add new systems to the set. Note that this will break any transient dependencies on + /// the systems. i.e. if you remove `system_b`, but there is a chain between system_a, system_b, and system_c. + /// system_a and system_c will no longer be properly ordered. + RemoveOnlySystems, +} + // methods for reporting errors impl ScheduleGraph { /// Returns the name of the node with the given [`NodeId`]. Resolves @@ -2758,7 +2774,11 @@ mod tests { schedule.add_systems(system); let mut world = World::default(); - let remove_count = schedule.remove_systems_in_set(system, &mut world); + let remove_count = schedule.remove_systems_in_set( + system, + &mut world, + ScheduleCleanupPolicy::RemoveSetAndSystems, + ); assert_eq!(remove_count.unwrap(), 1); // schedule has changed, so we check initializing again @@ -2774,7 +2794,11 @@ mod tests { schedule.add_systems((system, system)); let mut world = World::default(); - let remove_count = schedule.remove_systems_in_set(system, &mut world); + let remove_count = schedule.remove_systems_in_set( + system, + &mut world, + ScheduleCleanupPolicy::RemoveSetAndSystems, + ); assert_eq!(remove_count.unwrap(), 2); // schedule has changed, so we check initializing again @@ -2791,7 +2815,11 @@ mod tests { schedule.add_systems((system_1, system_2).chain()); let mut world = World::default(); - let remove_count = schedule.remove_systems_in_set(system_1, &mut world); + let remove_count = schedule.remove_systems_in_set( + system_1, + &mut world, + ScheduleCleanupPolicy::RemoveSetAndSystems, + ); assert_eq!(remove_count.unwrap(), 1); // schedule has changed, so we check initializing again From ed04770014626491da4c85dfc0531e9c6614906b Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Thu, 28 Aug 2025 14:01:15 -0700 Subject: [PATCH 22/32] implement policies --- crates/bevy_ecs/src/schedule/schedule.rs | 31 +++++++++++++++++++----- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 02e334985eff4..0a02d1e17b9ab 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -187,10 +187,11 @@ impl Schedules { schedule: impl ScheduleLabel, set: impl IntoSystemSet, world: &mut World, + policy: ScheduleCleanupPolicy, ) -> Result { self.get_mut(schedule) .ok_or(ScheduleError::ScheduleNotFound)? - .remove_systems_in_set(set, world, ScheduleCleanupPolicy::RemoveSetAndSystems) + .remove_systems_in_set(set, world, policy) } /// Configures a collection of system sets in the provided schedule, adding any sets that do not exist. @@ -988,6 +989,27 @@ impl ScheduleGraph { let keys = self.systems_in_set(interned)?; self.changed = true; + + match policy { + ScheduleCleanupPolicy::RemoveSetAndSystems => { + self.remove_systems_by_keys(keys); + + let Some(set_key) = self.system_sets.get_key(interned) else { + return Ok(keys.len()); + }; + self.remove_set_by_key(set_key); + + Ok(keys.len()) + } + ScheduleCleanupPolicy::RemoveOnlySystems => { + self.remove_systems_by_keys(keys); + + Ok(keys.len()) + } + } + } + + fn remove_systems_by_keys(&mut self, keys: Vec) { for &key in &keys { self.systems.remove(key); @@ -996,18 +1018,15 @@ impl ScheduleGraph { self.ambiguous_with.remove_node(key.into()); self.ambiguous_with_all.remove(&NodeId::from(key)); } + } - let Some(set_key) = self.system_sets.get_key(interned) else { - return Ok(keys.len()); - }; + fn remove_set_by_key(&mut self, key: SystemSetKey) { self.system_sets.remove(set_key); self.set_systems.remove(&set_key); self.hierarchy.graph.remove_node(set_key.into()); self.dependency.graph.remove_node(set_key.into()); self.ambiguous_with.remove_node(set_key.into()); self.ambiguous_with_all.remove(&NodeId::from(set_key)); - - Ok(keys.len()) } /// Update the internal graphs (hierarchy, dependency, ambiguity) by adding a single [`GraphInfo`] From c6675ca119584d342c1f56a52fb3ba21c3575f98 Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Thu, 28 Aug 2025 15:34:28 -0700 Subject: [PATCH 23/32] thinking about it --- crates/bevy_ecs/src/schedule/schedule.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 0a02d1e17b9ab..33ed8f2e46def 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -1569,6 +1569,10 @@ pub enum ScheduleCleanupPolicy { /// the systems. i.e. if you remove `system_b`, but there is a chain between system_a, system_b, and system_c. /// system_a and system_c will no longer be properly ordered. RemoveOnlySystems, + /// attempt to fix the transitive dependencies and link before and after edges together. Add dependencies between + /// the existing before and after dependendencies. For every after dependency add a new edge to a before dependency + // TODO: Would you ever not want to do this? My feeling is no. Might be better to have 2 functions that just do this. + FixTransitiveDependencies, } // methods for reporting errors From 0b3f48af4fb37664db1ff5bea1a6653f74657a44 Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Thu, 4 Sep 2025 13:31:23 -0700 Subject: [PATCH 24/32] add healing the graph --- crates/bevy_ecs/src/schedule/schedule.rs | 109 ++++++++++++++++++----- 1 file changed, 88 insertions(+), 21 deletions(-) diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 33ed8f2e46def..4b2f019f0e2a6 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -978,6 +978,42 @@ impl ScheduleGraph { .ok_or(ScheduleError::SetNotFound) } + fn add_edges_for_transitive_dependencies(&mut self, node: NodeId) { + let in_nodes: Vec<_> = self + .hierarchy + .graph + .neighbors_directed(node, Incoming) + .collect(); + let out_nodes: Vec<_> = self + .hierarchy + .graph + .neighbors_directed(node, Outgoing) + .collect(); + + for &in_node in &in_nodes { + for &out_node in &out_nodes { + self.hierarchy.graph.add_edge(in_node, out_node); + } + } + + let in_nodes: Vec<_> = self + .dependency + .graph + .neighbors_directed(node, Incoming) + .collect(); + let out_nodes: Vec<_> = self + .dependency + .graph + .neighbors_directed(node, Outgoing) + .collect(); + + for &in_node in &in_nodes { + for &out_node in &out_nodes { + self.dependency.graph.add_edge(in_node, out_node); + } + } + } + /// Remove all systems in a set and any dependencies on those systems and set. pub fn remove_systems_in_set( &mut self, @@ -991,26 +1027,51 @@ impl ScheduleGraph { self.changed = true; match policy { - ScheduleCleanupPolicy::RemoveSetAndSystems => { - self.remove_systems_by_keys(keys); + ScheduleCleanupPolicy::SetAndSystems => { + let Some(set_key) = self.system_sets.get_key(interned) else { + return Err(ScheduleError::SetNotFound); + }; + self.remove_systems_by_keys(&keys); + self.remove_set_by_key(set_key); + + Ok(keys.len()) + } + ScheduleCleanupPolicy::OnlySystems => { + self.remove_systems_by_keys(&keys); + + Ok(keys.len()) + } + ScheduleCleanupPolicy::FixSetAndSystems => { let Some(set_key) = self.system_sets.get_key(interned) else { - return Ok(keys.len()); + return Err(ScheduleError::SetNotFound); }; + + for &key in &keys { + self.add_edges_for_transitive_dependencies(key.into()); + } + + self.add_edges_for_transitive_dependencies(set_key.into()); + + self.remove_systems_by_keys(&keys); self.remove_set_by_key(set_key); Ok(keys.len()) } - ScheduleCleanupPolicy::RemoveOnlySystems => { - self.remove_systems_by_keys(keys); + ScheduleCleanupPolicy::FixOnlySystems => { + for &key in &keys { + self.add_edges_for_transitive_dependencies(key.into()); + } + + self.remove_systems_by_keys(&keys); Ok(keys.len()) } } } - fn remove_systems_by_keys(&mut self, keys: Vec) { - for &key in &keys { + fn remove_systems_by_keys(&mut self, keys: &Vec) { + for &key in keys { self.systems.remove(key); self.hierarchy.graph.remove_node(key.into()); @@ -1021,12 +1082,12 @@ impl ScheduleGraph { } fn remove_set_by_key(&mut self, key: SystemSetKey) { - self.system_sets.remove(set_key); - self.set_systems.remove(&set_key); - self.hierarchy.graph.remove_node(set_key.into()); - self.dependency.graph.remove_node(set_key.into()); - self.ambiguous_with.remove_node(set_key.into()); - self.ambiguous_with_all.remove(&NodeId::from(set_key)); + self.system_sets.remove(key); + self.set_systems.remove(&key); + self.hierarchy.graph.remove_node(key.into()); + self.dependency.graph.remove_node(key.into()); + self.ambiguous_with.remove_node(key.into()); + self.ambiguous_with_all.remove(&NodeId::from(key)); } /// Update the internal graphs (hierarchy, dependency, ambiguity) by adding a single [`GraphInfo`] @@ -1563,16 +1624,18 @@ pub enum ScheduleCleanupPolicy { /// Remove the set and any systems in the set. Note that this will break any transient dependencies on /// that set or systmes. This does not remove sets that might sub sets of the set. #[default] - RemoveSetAndSystems, + SetAndSystems, /// Remove only the systems in the set. This is useful if you want to keep the run conditions and dependencies /// on the set and add new systems to the set. Note that this will break any transient dependencies on /// the systems. i.e. if you remove `system_b`, but there is a chain between system_a, system_b, and system_c. /// system_a and system_c will no longer be properly ordered. - RemoveOnlySystems, + OnlySystems, /// attempt to fix the transitive dependencies and link before and after edges together. Add dependencies between /// the existing before and after dependendencies. For every after dependency add a new edge to a before dependency - // TODO: Would you ever not want to do this? My feeling is no. Might be better to have 2 functions that just do this. - FixTransitiveDependencies, + FixSetAndSystems, + /// attempt to fix the transitive dependencies and link before and after edges together. Add dependencies between + /// the existing before and after dependendencies. For every after dependency add a new edge to a before dependency + FixOnlySystems, } // methods for reporting errors @@ -1924,7 +1987,8 @@ mod tests { error::{ignore, panic, DefaultErrorHandler, Result}, prelude::{ApplyDeferred, IntoSystemSet, Res, Resource}, schedule::{ - tests::ResMut, IntoScheduleConfigs, Schedule, ScheduleBuildSettings, SystemSet, + tests::ResMut, IntoScheduleConfigs, Schedule, ScheduleBuildSettings, + ScheduleCleanupPolicy, SystemSet, }, system::Commands, world::World, @@ -2800,7 +2864,7 @@ mod tests { let remove_count = schedule.remove_systems_in_set( system, &mut world, - ScheduleCleanupPolicy::RemoveSetAndSystems, + ScheduleCleanupPolicy::SetAndSystems, ); assert_eq!(remove_count.unwrap(), 1); @@ -2820,7 +2884,7 @@ mod tests { let remove_count = schedule.remove_systems_in_set( system, &mut world, - ScheduleCleanupPolicy::RemoveSetAndSystems, + ScheduleCleanupPolicy::SetAndSystems, ); assert_eq!(remove_count.unwrap(), 2); @@ -2841,7 +2905,7 @@ mod tests { let remove_count = schedule.remove_systems_in_set( system_1, &mut world, - ScheduleCleanupPolicy::RemoveSetAndSystems, + ScheduleCleanupPolicy::SetAndSystems, ); assert_eq!(remove_count.unwrap(), 1); @@ -2849,4 +2913,7 @@ mod tests { schedule.initialize(&mut world).unwrap(); assert_eq!(schedule.graph().systems.len(), 1); } + + #[test] + fn remove_a_system_and_reuse_set() {} } From 2ec5c01bdb52486fdcc38fe03c5a0a968a532674 Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Thu, 4 Sep 2025 16:03:41 -0700 Subject: [PATCH 25/32] add more tests --- crates/bevy_app/src/app.rs | 8 +++- crates/bevy_app/src/sub_app.rs | 7 ++-- crates/bevy_ecs/src/schedule/schedule.rs | 49 +++++++++++++++++++++++- 3 files changed, 58 insertions(+), 6 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 32c3014a68004..1c6e3d31234b5 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -14,7 +14,10 @@ use bevy_ecs::{ event::{event_update_system, EventCursor}, intern::Interned, prelude::*, - schedule::{InternedSystemSet, ScheduleBuildSettings, ScheduleError, ScheduleLabel}, + schedule::{ + InternedSystemSet, ScheduleBuildSettings, ScheduleCleanupPolicy, ScheduleError, + ScheduleLabel, + }, system::{IntoObserverSystem, ScheduleSystem, SystemId, SystemInput}, }; use bevy_platform::collections::HashMap; @@ -339,8 +342,9 @@ impl App { &mut self, schedule: impl ScheduleLabel, set: impl IntoSystemSet, + policy: ScheduleCleanupPolicy, ) -> Result { - self.main_mut().remove_systems_in_set(schedule, set) + self.main_mut().remove_systems_in_set(schedule, set, policy) } /// Registers a system and returns a [`SystemId`] so it can later be called by [`World::run_system`]. diff --git a/crates/bevy_app/src/sub_app.rs b/crates/bevy_app/src/sub_app.rs index 164e1f4758d2e..6f5e01df56375 100644 --- a/crates/bevy_app/src/sub_app.rs +++ b/crates/bevy_app/src/sub_app.rs @@ -4,8 +4,8 @@ use bevy_ecs::{ event::EventRegistry, prelude::*, schedule::{ - InternedScheduleLabel, InternedSystemSet, ScheduleBuildSettings, ScheduleError, - ScheduleLabel, + InternedScheduleLabel, InternedSystemSet, ScheduleBuildSettings, ScheduleCleanupPolicy, + ScheduleError, ScheduleLabel, }, system::{ScheduleSystem, SystemId, SystemInput}, }; @@ -227,9 +227,10 @@ impl SubApp { &mut self, schedule: impl ScheduleLabel, set: impl IntoSystemSet, + policy: ScheduleCleanupPolicy, ) -> Result { self.world.schedule_scope(schedule, |world, schedule| { - schedule.remove_systems_in_set(set, world) + schedule.remove_systems_in_set(set, world, policy) }) } diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 4b2f019f0e2a6..66c78a922938a 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -2915,5 +2915,52 @@ mod tests { } #[test] - fn remove_a_system_and_reuse_set() {} + fn remove_a_system_and_still_ordered() { + #[derive(Resource)] + struct A; + + fn system_1(_: ResMut) {} + fn system_2() {} + fn system_3(_: ResMut) {} + + let mut schedule = Schedule::default(); + schedule.add_systems((system_1, system_2, system_3).chain()); + let mut world = World::new(); + + let _ = schedule.remove_systems_in_set( + system_2, + &mut world, + ScheduleCleanupPolicy::FixSetAndSystems, + ); + + let result = schedule.initialize(&mut world); + assert!(result.is_ok()); + let conflicts = schedule.graph().conflicting_systems(); + assert!(conflicts.is_empty()); + } + + #[test] + fn remove_a_set_and_still_ordered() { + #[derive(Resource)] + struct A; + + #[derive(SystemSet, Hash, PartialEq, Eq, Clone, Debug)] + struct B; + + fn system_1(_: ResMut) {} + fn system_2() {} + fn system_3(_: ResMut) {} + + let mut schedule = Schedule::default(); + schedule.add_systems((system_1.before(B), system_2, system_3.after(B))); + let mut world = World::new(); + + let _ = + schedule.remove_systems_in_set(B, &mut world, ScheduleCleanupPolicy::FixSetAndSystems); + + let result = schedule.initialize(&mut world); + assert!(result.is_ok()); + let conflicts = schedule.graph().conflicting_systems(); + assert!(conflicts.is_empty()); + } } From b60f393044d0336da8af368a10d20d0398874c9e Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Tue, 23 Sep 2025 11:09:54 -0700 Subject: [PATCH 26/32] rename policies and improve docs --- crates/bevy_ecs/src/schedule/schedule.rs | 55 +++++++++++++----------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index d92b8272639c0..59bf4cdb19ae0 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -1023,7 +1023,7 @@ impl ScheduleGraph { self.changed = true; match policy { - ScheduleCleanupPolicy::SetAndSystems => { + ScheduleCleanupPolicy::RemoveSetAndSystemsAllowBreakages => { let Some(set_key) = self.system_sets.get_key(interned) else { return Err(ScheduleError::SetNotFound); }; @@ -1033,12 +1033,12 @@ impl ScheduleGraph { Ok(keys.len()) } - ScheduleCleanupPolicy::OnlySystems => { + ScheduleCleanupPolicy::RemoveOnlySystemsAllowBreakages => { self.remove_systems_by_keys(&keys); Ok(keys.len()) } - ScheduleCleanupPolicy::FixSetAndSystems => { + ScheduleCleanupPolicy::RemoveSetAndSystems => { let Some(set_key) = self.system_sets.get_key(interned) else { return Err(ScheduleError::SetNotFound); }; @@ -1054,7 +1054,7 @@ impl ScheduleGraph { Ok(keys.len()) } - ScheduleCleanupPolicy::FixOnlySystems => { + ScheduleCleanupPolicy::RemoveOnlySystems => { for &key in &keys { self.add_edges_for_transitive_dependencies(key.into()); } @@ -1626,21 +1626,25 @@ pub enum ReportCycles { /// Policy to use when removing systems. #[derive(Default)] pub enum ScheduleCleanupPolicy { - /// Remove the set and any systems in the set. Note that this will break any transient dependencies on - /// that set or systmes. This does not remove sets that might sub sets of the set. + /// Remove the referenced set and any systems in the set. + /// Attempts to maintain the order between the transitive dependencies by adding new edges + /// between the existing before and after dependendencies on the set and the systems. + /// This does not remove sets that might sub sets of the set. #[default] - SetAndSystems, - /// Remove only the systems in the set. This is useful if you want to keep the run conditions and dependencies - /// on the set and add new systems to the set. Note that this will break any transient dependencies on - /// the systems. i.e. if you remove `system_b`, but there is a chain between system_a, system_b, and system_c. - /// system_a and system_c will no longer be properly ordered. - OnlySystems, - /// attempt to fix the transitive dependencies and link before and after edges together. Add dependencies between - /// the existing before and after dependendencies. For every after dependency add a new edge to a before dependency - FixSetAndSystems, - /// attempt to fix the transitive dependencies and link before and after edges together. Add dependencies between - /// the existing before and after dependendencies. For every after dependency add a new edge to a before dependency - FixOnlySystems, + RemoveSetAndSystems, + /// Remove only the systems in the set. The set + /// Attempts to maintain the order between the transitive dependencies by adding new edges + /// between the existing before and after dependendencies on the systems. + RemoveOnlySystems, + /// Remove the set and any systems in the set. + /// Note that this will not add new edges and + /// so will break any transitive dependencies on that set or systems. + /// This does not remove sets that might sub sets of the set. + RemoveSetAndSystemsAllowBreakages, + /// Remove only the systems in the set. + /// Note that this will not add new edges and + /// so will break any transitive dependencies on that set or systems. + RemoveOnlySystemsAllowBreakages, } // methods for reporting errors @@ -2869,7 +2873,7 @@ mod tests { let remove_count = schedule.remove_systems_in_set( system, &mut world, - ScheduleCleanupPolicy::SetAndSystems, + ScheduleCleanupPolicy::RemoveSetAndSystemsAllowBreakages, ); assert_eq!(remove_count.unwrap(), 1); @@ -2889,7 +2893,7 @@ mod tests { let remove_count = schedule.remove_systems_in_set( system, &mut world, - ScheduleCleanupPolicy::SetAndSystems, + ScheduleCleanupPolicy::RemoveSetAndSystemsAllowBreakages, ); assert_eq!(remove_count.unwrap(), 2); @@ -2910,7 +2914,7 @@ mod tests { let remove_count = schedule.remove_systems_in_set( system_1, &mut world, - ScheduleCleanupPolicy::SetAndSystems, + ScheduleCleanupPolicy::RemoveSetAndSystemsAllowBreakages, ); assert_eq!(remove_count.unwrap(), 1); @@ -2935,7 +2939,7 @@ mod tests { let _ = schedule.remove_systems_in_set( system_2, &mut world, - ScheduleCleanupPolicy::FixSetAndSystems, + ScheduleCleanupPolicy::RemoveSetAndSystems, ); let result = schedule.initialize(&mut world); @@ -2960,8 +2964,11 @@ mod tests { schedule.add_systems((system_1.before(B), system_2, system_3.after(B))); let mut world = World::new(); - let _ = - schedule.remove_systems_in_set(B, &mut world, ScheduleCleanupPolicy::FixSetAndSystems); + let _ = schedule.remove_systems_in_set( + B, + &mut world, + ScheduleCleanupPolicy::RemoveSetAndSystems, + ); let result = schedule.initialize(&mut world); assert!(result.is_ok()); From 07511f47b01457891ed4dc124fa6a222f6668d92 Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Tue, 23 Sep 2025 11:18:48 -0700 Subject: [PATCH 27/32] fix typos --- crates/bevy_app/src/app.rs | 3 ++- crates/bevy_ecs/src/schedule/schedule.rs | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index bb6b229fb984f..31abc217d24af 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -339,6 +339,7 @@ impl App { /// ## Example /// ``` /// # use bevy_app::prelude::*; + /// # use bevy_ecs::schedule::ScheduleCleanupPolicy; /// # /// # let mut app = App::new(); /// # fn system_a() {} @@ -348,7 +349,7 @@ impl App { /// app.add_systems(Update, system_a); /// /// // remove the system - /// app.remove_systems_in_set(Update, system_a); + /// app.remove_systems_in_set(Update, system_a, ScheduleCleanupPolicy::RemoveOnlySystems); /// ``` pub fn remove_systems_in_set( &mut self, diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 7d0947d7ae5f5..d62ae0bae758c 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -1052,7 +1052,7 @@ impl ScheduleGraph { Ok(keys.len()) } - ScheduleCleanupPolicy::RemoveOnlySystems => { + ScheduleCleanupPolicy::RemoveSystemsOnly => { for &key in &keys { self.add_edges_for_transitive_dependencies(key.into()); } @@ -1626,14 +1626,14 @@ pub enum ReportCycles { pub enum ScheduleCleanupPolicy { /// Remove the referenced set and any systems in the set. /// Attempts to maintain the order between the transitive dependencies by adding new edges - /// between the existing before and after dependendencies on the set and the systems. + /// between the existing before and after dependencies on the set and the systems. /// This does not remove sets that might sub sets of the set. #[default] RemoveSetAndSystems, /// Remove only the systems in the set. The set /// Attempts to maintain the order between the transitive dependencies by adding new edges - /// between the existing before and after dependendencies on the systems. - RemoveOnlySystems, + /// between the existing before and after dependencies on the systems. + RemoveSystemsOnly, /// Remove the set and any systems in the set. /// Note that this will not add new edges and /// so will break any transitive dependencies on that set or systems. @@ -1642,7 +1642,7 @@ pub enum ScheduleCleanupPolicy { /// Remove only the systems in the set. /// Note that this will not add new edges and /// so will break any transitive dependencies on that set or systems. - RemoveOnlySystemsAllowBreakages, + RemoveSystemsOnlyAllowBreakages, } // methods for reporting errors From bef58f79a89c2f41ebc699c6316e66f740dde82c Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Tue, 23 Sep 2025 15:12:14 -0700 Subject: [PATCH 28/32] fix doc tests --- crates/bevy_app/src/app.rs | 2 +- crates/bevy_ecs/src/schedule/schedule.rs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 31abc217d24af..eae6c7e119b3e 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -349,7 +349,7 @@ impl App { /// app.add_systems(Update, system_a); /// /// // remove the system - /// app.remove_systems_in_set(Update, system_a, ScheduleCleanupPolicy::RemoveOnlySystems); + /// app.remove_systems_in_set(Update, system_a, ScheduleCleanupPolicy::RemoveSystemsOnly); /// ``` pub fn remove_systems_in_set( &mut self, diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index d62ae0bae758c..e03ad85b6153f 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -403,6 +403,7 @@ impl Schedule { /// ## Example /// ``` /// # use bevy_ecs::prelude::*; + /// # use bevy_ecs::schedule::ScheduleCleanupPolicy; /// # /// # fn my_system() {} /// # @@ -412,7 +413,7 @@ impl Schedule { /// let mut world = World::default(); /// /// // remove the system - /// schedule.remove_systems_in_set(my_system, &mut world); + /// schedule.remove_systems_in_set(my_system, &mut world, ScheduleCleanupPolicy::RemoveSystemsOnly); /// ``` pub fn remove_systems_in_set( &mut self, @@ -1031,7 +1032,7 @@ impl ScheduleGraph { Ok(keys.len()) } - ScheduleCleanupPolicy::RemoveOnlySystemsAllowBreakages => { + ScheduleCleanupPolicy::RemoveSystemsOnlyAllowBreakages => { self.remove_systems_by_keys(&keys); Ok(keys.len()) From b895374159ef6b1893b86f116b295334c357c426 Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Thu, 25 Sep 2025 11:03:09 -0700 Subject: [PATCH 29/32] return slice from systems_is_set --- crates/bevy_ecs/src/schedule/schedule.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index e03ad85b6153f..78fb45c28f64f 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -959,7 +959,7 @@ impl ScheduleGraph { pub fn systems_in_set( &self, system_set: InternedSystemSet, - ) -> Result, ScheduleError> { + ) -> Result<&[SystemKey], ScheduleError> { if self.changed { return Err(ScheduleError::Uninitialized); } @@ -969,7 +969,7 @@ impl ScheduleGraph { .ok_or(ScheduleError::SetNotFound)?; self.set_systems .get(&system_set_id) - .cloned() + .map(|vec| vec.as_slice()) .ok_or(ScheduleError::SetNotFound) } @@ -1017,7 +1017,8 @@ impl ScheduleGraph { ) -> Result { let set = system_set.into_system_set(); let interned = set.intern(); - let keys = self.systems_in_set(interned)?; + // clone the keys out of the schedule as the systems are getting removed from self + let keys = self.systems_in_set(interned)?.to_vec(); self.changed = true; @@ -1065,7 +1066,7 @@ impl ScheduleGraph { } } - fn remove_systems_by_keys(&mut self, keys: &Vec) { + fn remove_systems_by_keys(&mut self, keys: &[SystemKey]) { for &key in keys { self.systems.remove(key); From 2d44ceaabfcb0414654e2348c7ebd70b6df8f00e Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Thu, 25 Sep 2025 11:08:07 -0700 Subject: [PATCH 30/32] clippy --- crates/bevy_ecs/src/schedule/schedule.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 78fb45c28f64f..87a91c86ae568 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -969,7 +969,7 @@ impl ScheduleGraph { .ok_or(ScheduleError::SetNotFound)?; self.set_systems .get(&system_set_id) - .map(|vec| vec.as_slice()) + .map(Vec::as_slice) .ok_or(ScheduleError::SetNotFound) } From 0bb50bfa9ef2682272c3466f4fff8aa4812abe38 Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Mon, 29 Sep 2025 09:47:48 -0700 Subject: [PATCH 31/32] update release note --- release-content/release-notes/remove_systems.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/release-content/release-notes/remove_systems.md b/release-content/release-notes/remove_systems.md index 13dbe63b97b44..ac1c81a58b09a 100644 --- a/release-content/release-notes/remove_systems.md +++ b/release-content/release-notes/remove_systems.md @@ -15,8 +15,8 @@ cost of the run condition being checked. app.add_systems((system_a, (system_b, system_c).in_set(MySet))); // remove a system -app.remove_systems_in_set(system_a); +schedule.remove_systems_in_set(my_system, ScheduleCleanupPolicy::RemoveSystemsOnly); // remove systems in a set -app.remove_systems_in_set(MySet) +app.remove_systems_in_set(MySet, ScheduleCleanupPolicy::RemoveSetAndSystems); ``` From 92efa4ff89b9a811f094398a32722d97a6ad712f Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Mon, 29 Sep 2025 10:19:48 -0700 Subject: [PATCH 32/32] hide some whitespace in example --- crates/bevy_app/src/app.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index eae6c7e119b3e..789debfac3578 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -344,7 +344,7 @@ impl App { /// # let mut app = App::new(); /// # fn system_a() {} /// # fn system_b() {} - /// + /// # /// // add the system /// app.add_systems(Update, system_a); ///