Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
9cdc695
add method to get system keys by
hymm Jul 24, 2025
535acee
remove systems
hymm Jul 24, 2025
04ceb71
add tests
hymm Jul 25, 2025
b557ac4
switch to return result
hymm Jul 25, 2025
6836140
add minimal docs
hymm Jul 25, 2025
fef6f97
remove todo
hymm Jul 26, 2025
ae58405
add an example
hymm Jul 26, 2025
55c200c
fix doc link
hymm Jul 26, 2025
e2b7dac
auto initialize if needed
hymm Jul 26, 2025
b0143c6
fix doc example
hymm Jul 26, 2025
ab30bac
call the correct initialize
hymm Jul 26, 2025
d0123e2
add methods to app and schedules
hymm Jul 28, 2025
2819060
add a release note
hymm Jul 28, 2025
b1eb124
docs cleanup
hymm Jul 29, 2025
2791ec5
fix doc test
hymm Jul 29, 2025
c85d7e4
edit release note a bit
hymm Jul 29, 2025
f7cc75c
remove all uninit sets
hymm Jul 29, 2025
2e9f5bb
more doc cleanup
hymm Jul 29, 2025
420e6ff
convert errors using `From`
hymm Jul 29, 2025
3a4e931
remove spaces from release note
hymm Jul 29, 2025
6715f31
Merge remote-tracking branch 'upstream/main' into remove-systems
hymm Jul 29, 2025
80f4a39
add policy enum
hymm Aug 28, 2025
ed04770
implement policies
hymm Aug 28, 2025
c6675ca
thinking about it
hymm Aug 28, 2025
0b3f48a
add healing the graph
hymm Sep 4, 2025
2ec5c01
add more tests
hymm Sep 4, 2025
e50973f
Merge remote-tracking branch 'upstream/main' into more-remove-systems…
hymm Sep 5, 2025
b60f393
rename policies and improve docs
hymm Sep 23, 2025
c77abd2
Merge remote-tracking branch 'upstream/main' into remove-systems
hymm Sep 23, 2025
07511f4
fix typos
hymm Sep 23, 2025
bef58f7
fix doc tests
hymm Sep 23, 2025
b895374
return slice from systems_is_set
hymm Sep 25, 2025
2d44cea
clippy
hymm Sep 25, 2025
0bb50bf
update release note
hymm Sep 29, 2025
bab0d07
Merge remote-tracking branch 'upstream/main' into remove-systems
hymm Sep 29, 2025
92efa4f
hide some whitespace in example
hymm Sep 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion crates/bevy_app/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ use bevy_ecs::{
intern::Interned,
message::{message_update_system, MessageCursor},
prelude::*,
schedule::{InternedSystemSet, ScheduleBuildSettings, ScheduleLabel},
schedule::{
InternedSystemSet, ScheduleBuildSettings, ScheduleCleanupPolicy, ScheduleError,
ScheduleLabel,
},
system::{IntoObserverSystem, ScheduleSystem, SystemId, SystemInput},
};
use bevy_platform::collections::HashMap;
Expand Down Expand Up @@ -325,6 +328,38 @@ 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::*;
/// # use bevy_ecs::schedule::ScheduleCleanupPolicy;
/// #
/// # 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, ScheduleCleanupPolicy::RemoveSystemsOnly);
/// ```
pub fn remove_systems_in_set<M>(
&mut self,
schedule: impl ScheduleLabel,
set: impl IntoSystemSet<M>,
policy: ScheduleCleanupPolicy,
) -> Result<usize, ScheduleError> {
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`].
///
/// It's possible to register the same systems more than once, they'll be stored separately.
Expand Down
17 changes: 16 additions & 1 deletion crates/bevy_app/src/sub_app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ use alloc::{boxed::Box, string::String, vec::Vec};
use bevy_ecs::{
message::MessageRegistry,
prelude::*,
schedule::{InternedScheduleLabel, InternedSystemSet, ScheduleBuildSettings, ScheduleLabel},
schedule::{
InternedScheduleLabel, InternedSystemSet, ScheduleBuildSettings, ScheduleCleanupPolicy,
ScheduleError, ScheduleLabel,
},
system::{ScheduleSystem, SystemId, SystemInput},
};
use bevy_platform::collections::{HashMap, HashSet};
Expand Down Expand Up @@ -219,6 +222,18 @@ impl SubApp {
self
}

/// See [`App::remove_systems_in_set`]
pub fn remove_systems_in_set<M>(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious about the intended use cases here, given the overhead tradeoffs. Whats a real world scenario where someone would want to call this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious about the intended use cases here, given the overhead tradeoffs. Whats a real world scenario where someone would want to call this?

I'm not sure what others are planning, but I've been tinkering with building a plugin to define non-exclusive systems with scripting languages. It would want to remove a system if its script was removed, or if the parameters to its script changed and it needed to be replaced with a system with different parameters. Loading the new script wouldn't be very performance-sensitive.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cases I see are replacing a system and moving closer to plugin removal. With this you could remove most of the things that a plugin adds excluding app level stuff (i.e. sub apps and runners). I also see that people would like to remove systems when having multiples games in the same binary a la ufo50 or when changing levels where you already are going through a loading screen. Though in some of those cases using observers are probably a better fit.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also think that we might be able to use this for hotpatching!

&mut self,
schedule: impl ScheduleLabel,
set: impl IntoSystemSet<M>,
policy: ScheduleCleanupPolicy,
) -> Result<usize, ScheduleError> {
self.world.schedule_scope(schedule, |world, schedule| {
schedule.remove_systems_in_set(set, world, policy)
})
}

/// See [`App::register_system`].
pub fn register_system<I, O, M>(
&mut self,
Expand Down
23 changes: 23 additions & 0 deletions crates/bevy_ecs/src/schedule/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,3 +259,26 @@ 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 set
#[error("Set not found")]
SetNotFound,
/// Schedule not found
#[error("Schedule not found.")]
ScheduleNotFound,
/// Error initializing schedule
#[error("{0}")]
ScheduleBuildError(ScheduleBuildError),
}

impl From<ScheduleBuildError> for ScheduleError {
fn from(value: ScheduleBuildError) -> Self {
Self::ScheduleBuildError(value)
}
}
44 changes: 36 additions & 8 deletions crates/bevy_ecs/src/schedule/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -487,14 +487,10 @@ 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.
pub(crate) fn node_mut(&mut self, key: SystemKey) -> &mut SystemNode {
&mut self.nodes[key]
/// 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)
}

/// Returns `true` if the system with the given key has conditions.
Expand Down Expand Up @@ -554,6 +550,25 @@ impl Systems {
key
}

/// 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;
}

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()
Expand Down Expand Up @@ -646,6 +661,11 @@ impl SystemSets {
self.sets.get(key).map(|set| &**set)
}

/// Returns the key for the given system set, returns None if it does not exist.
pub fn get_key(&self, set: InternedSystemSet) -> Option<SystemSetKey> {
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 {
Expand Down Expand Up @@ -716,6 +736,14 @@ impl SystemSets {
key
}

/// Remove a set with a [`SystemSetKey`]
pub(crate) fn remove(&mut self, key: SystemSetKey) -> bool {
self.sets.remove(key);
self.conditions.remove(key);
self.uninit.retain(|uninit| uninit.key != key);
true
}

/// Returns `true` if all system sets' conditions in this container have
/// been initialized.
pub fn is_initialized(&self) -> bool {
Expand Down
Loading
Loading