Skip to content

Commit

Permalink
Turn apply_deferred into a ZST System (#16642)
Browse files Browse the repository at this point in the history
# Objective

- Required by #16622 due to differing implementations of `System` by
`FunctionSystem` and `ExclusiveFunctionSystem`.
- Optimize the memory usage of instances of `apply_deferred` in system
schedules.

## Solution

By changing `apply_deferred` from being an ordinary system that ends up
as an `ExclusiveFunctionSystem`, and instead into a ZST struct that
implements `System` manually, we save ~320 bytes per instance of
`apply_deferred` in any schedule.

## Testing

- All current tests pass.

---

## Migration Guide

- If you were previously calling the special `apply_deferred` system via
`apply_deferred(world)`, don't.
  • Loading branch information
ItsDoot authored Dec 5, 2024
1 parent 67bd2b0 commit f87b9fe
Show file tree
Hide file tree
Showing 14 changed files with 166 additions and 57 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1839,7 +1839,7 @@ wasm = false

[package.metadata.example.apply_deferred]
name = "Apply System Buffers"
description = "Show how to use `apply_deferred` system"
description = "Show how to use `ApplyDeferred` system"
category = "ECS (Entity Component System)"
wasm = false

Expand Down
5 changes: 3 additions & 2 deletions crates/bevy_ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ pub use bevy_ptr as ptr;
///
/// This includes the most common types in this crate, re-exported for your convenience.
pub mod prelude {
#[expect(deprecated)]
#[doc(hidden)]
pub use crate::{
bundle::Bundle,
Expand All @@ -53,8 +54,8 @@ pub mod prelude {
query::{Added, AnyOf, Changed, Has, Or, QueryBuilder, QueryState, With, Without},
removal_detection::RemovedComponents,
schedule::{
apply_deferred, common_conditions::*, Condition, IntoSystemConfigs, IntoSystemSet,
IntoSystemSetConfigs, Schedule, Schedules, SystemSet,
apply_deferred, common_conditions::*, ApplyDeferred, Condition, IntoSystemConfigs,
IntoSystemSet, IntoSystemSetConfigs, Schedule, Schedules, SystemSet,
},
system::{
Commands, Deferred, EntityCommand, EntityCommands, In, InMut, InRef, IntoSystem, Local,
Expand Down
14 changes: 7 additions & 7 deletions crates/bevy_ecs/src/schedule/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ where
/// Runs before all systems in `set`. If `self` has any systems that produce [`Commands`](crate::system::Commands)
/// or other [`Deferred`](crate::system::Deferred) operations, all systems in `set` will see their effect.
///
/// If automatically inserting [`apply_deferred`](crate::schedule::apply_deferred) like
/// If automatically inserting [`ApplyDeferred`](crate::schedule::ApplyDeferred) like
/// this isn't desired, use [`before_ignore_deferred`](Self::before_ignore_deferred) instead.
///
/// Calling [`.chain`](Self::chain) is often more convenient and ensures that all systems are added to the schedule.
Expand All @@ -305,7 +305,7 @@ where
/// Run after all systems in `set`. If `set` has any systems that produce [`Commands`](crate::system::Commands)
/// or other [`Deferred`](crate::system::Deferred) operations, all systems in `self` will see their effect.
///
/// If automatically inserting [`apply_deferred`](crate::schedule::apply_deferred) like
/// If automatically inserting [`ApplyDeferred`](crate::schedule::ApplyDeferred) like
/// this isn't desired, use [`after_ignore_deferred`](Self::after_ignore_deferred) instead.
///
/// Calling [`.chain`](Self::chain) is often more convenient and ensures that all systems are added to the schedule.
Expand Down Expand Up @@ -429,7 +429,7 @@ where
///
/// Ordering constraints will be applied between the successive elements.
///
/// If the preceding node on a edge has deferred parameters, a [`apply_deferred`](crate::schedule::apply_deferred)
/// If the preceding node on a edge has deferred parameters, a [`ApplyDeferred`](crate::schedule::ApplyDeferred)
/// will be inserted on the edge. If this behavior is not desired consider using
/// [`chain_ignore_deferred`](Self::chain_ignore_deferred) instead.
fn chain(self) -> SystemConfigs {
Expand All @@ -440,7 +440,7 @@ where
///
/// Ordering constraints will be applied between the successive elements.
///
/// Unlike [`chain`](Self::chain) this will **not** add [`apply_deferred`](crate::schedule::apply_deferred) on the edges.
/// Unlike [`chain`](Self::chain) this will **not** add [`ApplyDeferred`](crate::schedule::ApplyDeferred) on the edges.
fn chain_ignore_deferred(self) -> SystemConfigs {
self.into_configs().chain_ignore_deferred()
}
Expand Down Expand Up @@ -610,7 +610,7 @@ where
/// Runs before all systems in `set`. If `self` has any systems that produce [`Commands`](crate::system::Commands)
/// or other [`Deferred`](crate::system::Deferred) operations, all systems in `set` will see their effect.
///
/// If automatically inserting [`apply_deferred`](crate::schedule::apply_deferred) like
/// If automatically inserting [`ApplyDeferred`](crate::schedule::ApplyDeferred) like
/// this isn't desired, use [`before_ignore_deferred`](Self::before_ignore_deferred) instead.
fn before<M>(self, set: impl IntoSystemSet<M>) -> SystemSetConfigs {
self.into_configs().before(set)
Expand All @@ -619,7 +619,7 @@ where
/// Runs before all systems in `set`. If `set` has any systems that produce [`Commands`](crate::system::Commands)
/// or other [`Deferred`](crate::system::Deferred) operations, all systems in `self` will see their effect.
///
/// If automatically inserting [`apply_deferred`](crate::schedule::apply_deferred) like
/// If automatically inserting [`ApplyDeferred`](crate::schedule::ApplyDeferred) like
/// this isn't desired, use [`after_ignore_deferred`](Self::after_ignore_deferred) instead.
fn after<M>(self, set: impl IntoSystemSet<M>) -> SystemSetConfigs {
self.into_configs().after(set)
Expand Down Expand Up @@ -672,7 +672,7 @@ where
///
/// Ordering constraints will be applied between the successive elements.
///
/// Unlike [`chain`](Self::chain) this will **not** add [`apply_deferred`](crate::schedule::apply_deferred) on the edges.
/// Unlike [`chain`](Self::chain) this will **not** add [`ApplyDeferred`](crate::schedule::ApplyDeferred) on the edges.
fn chain_ignore_deferred(self) -> SystemSetConfigs {
self.into_configs().chain_ignore_deferred()
}
Expand Down
143 changes: 125 additions & 18 deletions crates/bevy_ecs/src/schedule/executor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ mod multi_threaded;
mod simple;
mod single_threaded;

use alloc::borrow::Cow;
use core::any::TypeId;

pub use self::{
multi_threaded::{MainThreadExecutor, MultiThreadedExecutor},
simple::SimpleExecutor,
Expand All @@ -11,9 +14,13 @@ pub use self::{
use fixedbitset::FixedBitSet;

use crate::{
schedule::{BoxedCondition, NodeId},
system::BoxedSystem,
world::World,
archetype::ArchetypeComponentId,
component::{ComponentId, Tick},
prelude::{IntoSystemSet, SystemSet},
query::Access,
schedule::{BoxedCondition, InternedSystemSet, NodeId, SystemTypeSet},
system::{BoxedSystem, System, SystemIn},
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World},
};

/// Types that can run a [`SystemSchedule`] on a [`World`].
Expand Down Expand Up @@ -100,32 +107,132 @@ impl SystemSchedule {
}
}

/// Instructs the executor to call [`System::apply_deferred`](crate::system::System::apply_deferred)
/// on the systems that have run but not applied their [`Deferred`](crate::system::Deferred) system parameters
/// (like [`Commands`](crate::prelude::Commands)) or other system buffers.
/// See [`ApplyDeferred`].
#[deprecated = "Use `ApplyDeferred` instead. This was previously a function but is now a marker struct System."]
#[expect(non_upper_case_globals)]
pub const apply_deferred: ApplyDeferred = ApplyDeferred;

/// A special [`System`] that instructs the executor to call
/// [`System::apply_deferred`] on the systems that have run but not applied
/// their [`Deferred`] system parameters (like [`Commands`]) or other system buffers.
///
/// ## Scheduling
///
/// `apply_deferred` systems are scheduled *by default*
/// `ApplyDeferred` systems are scheduled *by default*
/// - later in the same schedule run (for example, if a system with `Commands` param
/// is scheduled in `Update`, all the changes will be visible in `PostUpdate`)
/// - between systems with dependencies if the dependency
/// [has deferred buffers](crate::system::System::has_deferred)
/// (if system `bar` directly or indirectly depends on `foo`, and `foo` uses `Commands` param,
/// changes to the world in `foo` will be visible in `bar`)
/// - between systems with dependencies if the dependency [has deferred buffers]
/// (if system `bar` directly or indirectly depends on `foo`, and `foo` uses
/// `Commands` param, changes to the world in `foo` will be visible in `bar`)
///
/// ## Notes
/// - This function (currently) does nothing if it's called manually or wrapped inside a [`PipeSystem`](crate::system::PipeSystem).
/// - Modifying a [`Schedule`](super::Schedule) may change the order buffers are applied.
/// - This system (currently) does nothing if it's called manually or wrapped
/// inside a [`PipeSystem`].
/// - Modifying a [`Schedule`] may change the order buffers are applied.
///
/// [`System::apply_deferred`]: crate::system::System::apply_deferred
/// [`Deferred`]: crate::system::Deferred
/// [`Commands`]: crate::prelude::Commands
/// [has deferred buffers]: crate::system::System::has_deferred
/// [`PipeSystem`]: crate::system::PipeSystem
/// [`Schedule`]: super::Schedule
#[doc(alias = "apply_system_buffers")]
#[allow(unused_variables)]
pub fn apply_deferred(world: &mut World) {}
pub struct ApplyDeferred;

/// Returns `true` if the [`System`](crate::system::System) is an instance of [`apply_deferred`].
/// Returns `true` if the [`System`] is an instance of [`ApplyDeferred`].
pub(super) fn is_apply_deferred(system: &BoxedSystem) -> bool {
use crate::system::IntoSystem;
// deref to use `System::type_id` instead of `Any::type_id`
system.as_ref().type_id() == apply_deferred.system_type_id()
system.as_ref().type_id() == TypeId::of::<ApplyDeferred>()
}

impl System for ApplyDeferred {
type In = ();
type Out = ();

fn name(&self) -> Cow<'static, str> {
Cow::Borrowed("bevy_ecs::apply_deferred")
}

fn component_access(&self) -> &Access<ComponentId> {
// This system accesses no components.
const { &Access::new() }
}

fn archetype_component_access(&self) -> &Access<ArchetypeComponentId> {
// This system accesses no archetype components.
const { &Access::new() }
}

fn is_send(&self) -> bool {
// Although this system itself does nothing on its own, the system
// executor uses it to apply deferred commands. Commands must be allowed
// to access non-send resources, so this system must be non-send for
// scheduling purposes.
false
}

fn is_exclusive(&self) -> bool {
// This system is labeled exclusive because it is used by the system
// executor to find places where deferred commands should be applied,
// and commands can only be applied with exclusive access to the world.
true
}

fn has_deferred(&self) -> bool {
// This system itself doesn't have any commands to apply, but when it
// is pulled from the schedule to be ran, the executor will apply
// deferred commands from other systems.
false
}

unsafe fn run_unsafe(
&mut self,
_input: SystemIn<'_, Self>,
_world: UnsafeWorldCell,
) -> Self::Out {
// This system does nothing on its own. The executor will apply deferred
// commands from other systems instead of running this system.
}

fn run(&mut self, _input: SystemIn<'_, Self>, _world: &mut World) -> Self::Out {
// This system does nothing on its own. The executor will apply deferred
// commands from other systems instead of running this system.
}

fn apply_deferred(&mut self, _world: &mut World) {}

fn queue_deferred(&mut self, _world: DeferredWorld) {}

unsafe fn validate_param_unsafe(&mut self, _world: UnsafeWorldCell) -> bool {
// This system is always valid to run because it doesn't do anything,
// and only used as a marker for the executor.
true
}

fn initialize(&mut self, _world: &mut World) {}

fn update_archetype_component_access(&mut self, _world: UnsafeWorldCell) {}

fn check_change_tick(&mut self, _change_tick: Tick) {}

fn default_system_sets(&self) -> Vec<InternedSystemSet> {
vec![SystemTypeSet::<Self>::new().intern()]
}

fn get_last_run(&self) -> Tick {
// This system is never run, so it has no last run tick.
Tick::MAX
}

fn set_last_run(&mut self, _last_run: Tick) {}
}

impl IntoSystemSet<()> for ApplyDeferred {
type Set = SystemTypeSet<Self>;

fn into_system_set(self) -> Self::Set {
SystemTypeSet::<Self>::new()
}
}

/// These functions hide the bottom of the callstack from `RUST_BACKTRACE=1` (assuming the default panic handler is used).
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_ecs/src/schedule/executor/simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world: &mut W
#[cfg(test)]
#[test]
fn skip_automatic_sync_points() {
// Schedules automatically insert apply_deferred systems, but these should
// Schedules automatically insert ApplyDeferred systems, but these should
// not be executed as they only serve as markers and are not initialized
use crate::prelude::*;
let mut sched = Schedule::default();
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_ecs/src/schedule/graph/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ pub(crate) enum DependencyKind {
Before,
/// A node that should be succeeded.
After,
/// A node that should be preceded and will **not** automatically insert an instance of `apply_deferred` on the edge.
/// A node that should be preceded and will **not** automatically insert an instance of `ApplyDeferred` on the edge.
BeforeNoSync,
/// A node that should be succeeded and will **not** automatically insert an instance of `apply_deferred` on the edge.
/// A node that should be succeeded and will **not** automatically insert an instance of `ApplyDeferred` on the edge.
AfterNoSync,
}

Expand Down
12 changes: 6 additions & 6 deletions crates/bevy_ecs/src/schedule/schedule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,9 +213,9 @@ fn make_executor(kind: ExecutorKind) -> Box<dyn SystemExecutor> {
#[derive(PartialEq)]
pub enum Chain {
/// Run nodes in order. If there are deferred parameters in preceding systems a
/// [`apply_deferred`] will be added on the edge.
/// [`ApplyDeferred`] will be added on the edge.
Yes,
/// Run nodes in order. This will not add [`apply_deferred`] between nodes.
/// Run nodes in order. This will not add [`ApplyDeferred`] between nodes.
YesIgnoreDeferred,
/// Nodes are allowed to run in any order.
No,
Expand Down Expand Up @@ -367,7 +367,7 @@ impl Schedule {

/// Set whether the schedule applies deferred system buffers on final time or not. This is a catch-all
/// in case a system uses commands but was not explicitly ordered before an instance of
/// [`apply_deferred`]. By default this
/// [`ApplyDeferred`]. By default this
/// setting is true, but may be disabled if needed.
pub fn set_apply_final_deferred(&mut self, apply_final_deferred: bool) -> &mut Self {
self.executor.set_apply_final_deferred(apply_final_deferred);
Expand Down Expand Up @@ -1191,13 +1191,13 @@ impl ScheduleGraph {
Ok(sync_point_graph)
}

/// add an [`apply_deferred`] system with no config
/// add an [`ApplyDeferred`] system with no config
fn add_auto_sync(&mut self) -> NodeId {
let id = NodeId::System(self.systems.len());

self.systems
.push(SystemNode::new(Box::new(IntoSystem::into_system(
apply_deferred,
ApplyDeferred,
))));
self.system_conditions.push(Vec::new());

Expand Down Expand Up @@ -1998,7 +1998,7 @@ pub struct ScheduleBuildSettings {
///
/// Defaults to [`LogLevel::Warn`].
pub hierarchy_detection: LogLevel,
/// Auto insert [`apply_deferred`] systems into the schedule,
/// Auto insert [`ApplyDeferred`] systems into the schedule,
/// when there are [`Deferred`](crate::prelude::Deferred)
/// in one system and there are ordering dependencies on that system. [`Commands`](crate::system::Commands) is one
/// such deferred buffer.
Expand Down
7 changes: 4 additions & 3 deletions crates/bevy_ecs/src/system/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ pub use parallel_scope::*;
///
/// Since each command requires exclusive access to the `World`,
/// all queued commands are automatically applied in sequence
/// when the `apply_deferred` system runs (see [`apply_deferred`] documentation for more details).
/// when the `ApplyDeferred` system runs (see [`ApplyDeferred`] documentation for more details).
///
/// Each command can be used to modify the [`World`] in arbitrary ways:
/// * spawning or despawning entities
Expand All @@ -43,7 +43,8 @@ pub use parallel_scope::*;
///
/// # Usage
///
/// Add `mut commands: Commands` as a function argument to your system to get a copy of this struct that will be applied the next time a copy of [`apply_deferred`] runs.
/// Add `mut commands: Commands` as a function argument to your system to get a
/// copy of this struct that will be applied the next time a copy of [`ApplyDeferred`] runs.
/// Commands are almost always used as a [`SystemParam`](crate::system::SystemParam).
///
/// ```
Expand Down Expand Up @@ -75,7 +76,7 @@ pub use parallel_scope::*;
/// # }
/// ```
///
/// [`apply_deferred`]: crate::schedule::apply_deferred
/// [`ApplyDeferred`]: crate::schedule::ApplyDeferred
pub struct Commands<'w, 's> {
queue: InternalQueue<'s>,
entities: &'w Entities,
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_ecs/src/system/function_system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ impl SystemMeta {
}

/// Marks the system as having deferred buffers like [`Commands`](`super::Commands`)
/// This lets the scheduler insert [`apply_deferred`](`crate::prelude::apply_deferred`) systems automatically.
/// This lets the scheduler insert [`ApplyDeferred`](`crate::prelude::ApplyDeferred`) systems automatically.
#[inline]
pub fn set_has_deferred(&mut self) {
self.has_deferred = true;
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_ecs/src/system/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ mod tests {
query::{Added, Changed, Or, With, Without},
removal_detection::RemovedComponents,
schedule::{
apply_deferred, common_conditions::resource_exists, Condition, IntoSystemConfigs,
common_conditions::resource_exists, ApplyDeferred, Condition, IntoSystemConfigs,
Schedule,
},
system::{
Expand Down Expand Up @@ -493,7 +493,7 @@ mod tests {

let mut schedule = Schedule::default();

schedule.add_systems((incr_e_on_flip, apply_deferred, World::clear_trackers).chain());
schedule.add_systems((incr_e_on_flip, ApplyDeferred, World::clear_trackers).chain());

schedule.run(&mut world);
assert_eq!(world.resource::<Added>().0, 1);
Expand Down
Loading

0 comments on commit f87b9fe

Please sign in to comment.