Skip to content

Commit bb4ea9c

Browse files
Stop storing access for all systems (#19477)
# Objective Reduce memory usage by storing fewer copies of `FilteredAccessSet<ComponentId>`. Currently, the `System` trait exposes the `component_access_set` for the system, which is used by the multi-threaded executor to determine which systems can run concurrently. But because it is available on the trait, it needs to be stored for *every* system, even ones that are not run by the executor! In particular, it is never needed for observers, or for the inner systems in a `PipeSystem` or `CombinatorSystem`. ## Solution Instead of exposing the access from a method on `System`, return it from `System::initialize`. Since it is still needed during scheduling, store the access alongside the boxed system in the schedule. That's not quite enough for systems built using `SystemParamBuilder`s, though. Those calculate the access in `SystemParamBuilder::build`, which happens earlier than `System::initialize`. To handle those, we separate `SystemParam::init_state` into `init_state`, which creates the state value, and `init_access`, which calculates the access. This lets `System::initialize` call `init_access` on a state that was provided by the builder. An additional benefit of that separation is that it removes the need to duplicate access checks between `SystemParamBuilder::build` and `SystemParam::init_state`. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
1 parent 4da420c commit bb4ea9c

26 files changed

+838
-572
lines changed

crates/bevy_ecs/macros/src/lib.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -440,10 +440,10 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
440440
> #path::system::SystemParamBuilder<#generic_struct> for #builder_name<#(#builder_type_parameters,)*>
441441
#where_clause
442442
{
443-
fn build(self, world: &mut #path::world::World, meta: &mut #path::system::SystemMeta) -> <#generic_struct as #path::system::SystemParam>::State {
443+
fn build(self, world: &mut #path::world::World) -> <#generic_struct as #path::system::SystemParam>::State {
444444
let #builder_name { #(#fields: #field_locals,)* } = self;
445445
#state_struct_name {
446-
state: #path::system::SystemParamBuilder::build((#(#tuple_patterns,)*), world, meta)
446+
state: #path::system::SystemParamBuilder::build((#(#tuple_patterns,)*), world)
447447
}
448448
}
449449
}
@@ -472,12 +472,16 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
472472
type State = #state_struct_name<#punctuated_generic_idents>;
473473
type Item<'w, 's> = #struct_name #ty_generics;
474474

475-
fn init_state(world: &mut #path::world::World, system_meta: &mut #path::system::SystemMeta) -> Self::State {
475+
fn init_state(world: &mut #path::world::World) -> Self::State {
476476
#state_struct_name {
477-
state: <#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::init_state(world, system_meta),
477+
state: <#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::init_state(world),
478478
}
479479
}
480480

481+
fn init_access(state: &Self::State, system_meta: &mut #path::system::SystemMeta, component_access_set: &mut #path::query::FilteredAccessSet<#path::component::ComponentId>, world: &mut #path::world::World) {
482+
<#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::init_access(&state.state, system_meta, component_access_set, world);
483+
}
484+
481485
fn apply(state: &mut Self::State, system_meta: &#path::system::SystemMeta, world: &mut #path::world::World) {
482486
<#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::apply(&mut state.state, system_meta, world);
483487
}

crates/bevy_ecs/src/lifecycle.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ use crate::{
5454
component::{Component, ComponentId, ComponentIdFor, Tick},
5555
entity::Entity,
5656
event::{Event, EventCursor, EventId, EventIterator, EventIteratorWithId, Events},
57+
query::FilteredAccessSet,
5758
relationship::RelationshipHookMode,
5859
storage::SparseSet,
5960
system::{Local, ReadOnlySystemParam, SystemMeta, SystemParam},
@@ -617,7 +618,15 @@ unsafe impl<'a> SystemParam for &'a RemovedComponentEvents {
617618
type State = ();
618619
type Item<'w, 's> = &'w RemovedComponentEvents;
619620

620-
fn init_state(_world: &mut World, _system_meta: &mut SystemMeta) -> Self::State {}
621+
fn init_state(_world: &mut World) -> Self::State {}
622+
623+
fn init_access(
624+
_state: &Self::State,
625+
_system_meta: &mut SystemMeta,
626+
_component_access_set: &mut FilteredAccessSet<ComponentId>,
627+
_world: &mut World,
628+
) {
629+
}
621630

622631
#[inline]
623632
unsafe fn get_param<'w, 's>(

crates/bevy_ecs/src/schedule/auto_insert_apply_deferred.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ impl ScheduleBuildPass for AutoInsertApplyDeferredPass {
102102
let mut system_has_conditions_cache = HashMap::<usize, bool>::default();
103103
let mut is_valid_explicit_sync_point = |system: NodeId| {
104104
let index = system.index();
105-
is_apply_deferred(graph.systems[index].get().unwrap())
105+
is_apply_deferred(&graph.systems[index].get().unwrap().system)
106106
&& !*system_has_conditions_cache
107107
.entry(index)
108108
.or_insert_with(|| system_has_conditions(graph, system))
@@ -138,7 +138,11 @@ impl ScheduleBuildPass for AutoInsertApplyDeferredPass {
138138
} else if !node_needs_sync {
139139
// No previous node has postponed sync points to add so check if the system itself
140140
// has deferred params that require a sync point to apply them.
141-
node_needs_sync = graph.systems[node.index()].get().unwrap().has_deferred();
141+
node_needs_sync = graph.systems[node.index()]
142+
.get()
143+
.unwrap()
144+
.system
145+
.has_deferred();
142146
}
143147

144148
for target in dependency_flattened.neighbors_directed(*node, Direction::Outgoing) {
@@ -148,7 +152,11 @@ impl ScheduleBuildPass for AutoInsertApplyDeferredPass {
148152

149153
let mut edge_needs_sync = node_needs_sync;
150154
if node_needs_sync
151-
&& !graph.systems[target.index()].get().unwrap().is_exclusive()
155+
&& !graph.systems[target.index()]
156+
.get()
157+
.unwrap()
158+
.system
159+
.is_exclusive()
152160
&& self.no_sync_edges.contains(&(*node, target))
153161
{
154162
// The node has deferred params to apply, but this edge is ignoring sync points.
@@ -190,7 +198,7 @@ impl ScheduleBuildPass for AutoInsertApplyDeferredPass {
190198
continue;
191199
}
192200

193-
if is_apply_deferred(graph.systems[target.index()].get().unwrap()) {
201+
if is_apply_deferred(&graph.systems[target.index()].get().unwrap().system) {
194202
// We don't need to insert a sync point since ApplyDeferred is a sync point
195203
// already!
196204
continue;

crates/bevy_ecs/src/schedule/executor/mod.rs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use crate::{
1919
error::{BevyError, ErrorContext, Result},
2020
prelude::{IntoSystemSet, SystemSet},
2121
query::FilteredAccessSet,
22-
schedule::{BoxedCondition, InternedSystemSet, NodeId, SystemTypeSet},
22+
schedule::{ConditionWithAccess, InternedSystemSet, NodeId, SystemTypeSet, SystemWithAccess},
2323
system::{ScheduleSystem, System, SystemIn, SystemParamValidationError, SystemStateFlags},
2424
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World},
2525
};
@@ -74,9 +74,9 @@ pub struct SystemSchedule {
7474
/// List of system node ids.
7575
pub(super) system_ids: Vec<NodeId>,
7676
/// Indexed by system node id.
77-
pub(super) systems: Vec<ScheduleSystem>,
77+
pub(super) systems: Vec<SystemWithAccess>,
7878
/// Indexed by system node id.
79-
pub(super) system_conditions: Vec<Vec<BoxedCondition>>,
79+
pub(super) system_conditions: Vec<Vec<ConditionWithAccess>>,
8080
/// Indexed by system node id.
8181
/// Number of systems that the system immediately depends on.
8282
#[cfg_attr(
@@ -97,7 +97,7 @@ pub struct SystemSchedule {
9797
/// List of system set node ids.
9898
pub(super) set_ids: Vec<NodeId>,
9999
/// Indexed by system set node id.
100-
pub(super) set_conditions: Vec<Vec<BoxedCondition>>,
100+
pub(super) set_conditions: Vec<Vec<ConditionWithAccess>>,
101101
/// Indexed by system set node id.
102102
/// List of systems that are in sets that have conditions.
103103
///
@@ -162,11 +162,6 @@ impl System for ApplyDeferred {
162162
Cow::Borrowed("bevy_ecs::apply_deferred")
163163
}
164164

165-
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId> {
166-
// This system accesses no components.
167-
const { &FilteredAccessSet::new() }
168-
}
169-
170165
fn flags(&self) -> SystemStateFlags {
171166
// non-send , exclusive , no deferred
172167
SystemStateFlags::NON_SEND | SystemStateFlags::EXCLUSIVE
@@ -205,7 +200,9 @@ impl System for ApplyDeferred {
205200
Ok(())
206201
}
207202

208-
fn initialize(&mut self, _world: &mut World) {}
203+
fn initialize(&mut self, _world: &mut World) -> FilteredAccessSet<ComponentId> {
204+
FilteredAccessSet::new()
205+
}
209206

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

crates/bevy_ecs/src/schedule/executor/multi_threaded.rs

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ use tracing::{info_span, Span};
1515
use crate::{
1616
error::{ErrorContext, ErrorHandler, Result},
1717
prelude::Resource,
18-
schedule::{is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule},
18+
schedule::{
19+
is_apply_deferred, ConditionWithAccess, ExecutorKind, SystemExecutor, SystemSchedule,
20+
SystemWithAccess,
21+
},
1922
system::ScheduleSystem,
2023
world::{unsafe_world_cell::UnsafeWorldCell, World},
2124
};
@@ -27,14 +30,14 @@ use super::__rust_begin_short_backtrace;
2730
/// Borrowed data used by the [`MultiThreadedExecutor`].
2831
struct Environment<'env, 'sys> {
2932
executor: &'env MultiThreadedExecutor,
30-
systems: &'sys [SyncUnsafeCell<ScheduleSystem>],
33+
systems: &'sys [SyncUnsafeCell<SystemWithAccess>],
3134
conditions: SyncUnsafeCell<Conditions<'sys>>,
3235
world_cell: UnsafeWorldCell<'env>,
3336
}
3437

3538
struct Conditions<'a> {
36-
system_conditions: &'a mut [Vec<BoxedCondition>],
37-
set_conditions: &'a mut [Vec<BoxedCondition>],
39+
system_conditions: &'a mut [Vec<ConditionWithAccess>],
40+
set_conditions: &'a mut [Vec<ConditionWithAccess>],
3841
sets_with_conditions_of_systems: &'a [FixedBitSet],
3942
systems_in_sets_with_conditions: &'a [FixedBitSet],
4043
}
@@ -172,8 +175,8 @@ impl SystemExecutor for MultiThreadedExecutor {
172175
conflicting_systems: FixedBitSet::with_capacity(sys_count),
173176
condition_conflicting_systems: FixedBitSet::with_capacity(sys_count),
174177
dependents: schedule.system_dependents[index].clone(),
175-
is_send: schedule.systems[index].is_send(),
176-
is_exclusive: schedule.systems[index].is_exclusive(),
178+
is_send: schedule.systems[index].system.is_send(),
179+
is_exclusive: schedule.systems[index].system.is_exclusive(),
177180
});
178181
if schedule.system_dependencies[index] == 0 {
179182
self.starting_systems.insert(index);
@@ -187,10 +190,7 @@ impl SystemExecutor for MultiThreadedExecutor {
187190
let system1 = &schedule.systems[index1];
188191
for index2 in 0..index1 {
189192
let system2 = &schedule.systems[index2];
190-
if !system2
191-
.component_access_set()
192-
.is_compatible(system1.component_access_set())
193-
{
193+
if !system2.access.is_compatible(&system1.access) {
194194
state.system_task_metadata[index1]
195195
.conflicting_systems
196196
.insert(index2);
@@ -202,11 +202,10 @@ impl SystemExecutor for MultiThreadedExecutor {
202202

203203
for index2 in 0..sys_count {
204204
let system2 = &schedule.systems[index2];
205-
if schedule.system_conditions[index1].iter().any(|condition| {
206-
!system2
207-
.component_access_set()
208-
.is_compatible(condition.component_access_set())
209-
}) {
205+
if schedule.system_conditions[index1]
206+
.iter()
207+
.any(|condition| !system2.access.is_compatible(&condition.access))
208+
{
210209
state.system_task_metadata[index1]
211210
.condition_conflicting_systems
212211
.insert(index2);
@@ -220,11 +219,10 @@ impl SystemExecutor for MultiThreadedExecutor {
220219
let mut conflicting_systems = FixedBitSet::with_capacity(sys_count);
221220
for sys_index in 0..sys_count {
222221
let system = &schedule.systems[sys_index];
223-
if schedule.set_conditions[set_idx].iter().any(|condition| {
224-
!system
225-
.component_access_set()
226-
.is_compatible(condition.component_access_set())
227-
}) {
222+
if schedule.set_conditions[set_idx]
223+
.iter()
224+
.any(|condition| !system.access.is_compatible(&condition.access))
225+
{
228226
conflicting_systems.insert(sys_index);
229227
}
230228
}
@@ -468,7 +466,8 @@ impl ExecutorState {
468466
debug_assert!(!self.running_systems.contains(system_index));
469467
// SAFETY: Caller assured that these systems are not running.
470468
// Therefore, no other reference to this system exists and there is no aliasing.
471-
let system = unsafe { &mut *context.environment.systems[system_index].get() };
469+
let system =
470+
&mut unsafe { &mut *context.environment.systems[system_index].get() }.system;
472471

473472
#[cfg(feature = "hotpatching")]
474473
if should_update_hotpatch {
@@ -661,7 +660,7 @@ impl ExecutorState {
661660
/// used by the specified system.
662661
unsafe fn spawn_system_task(&mut self, context: &Context, system_index: usize) {
663662
// SAFETY: this system is not running, no other reference exists
664-
let system = unsafe { &mut *context.environment.systems[system_index].get() };
663+
let system = &mut unsafe { &mut *context.environment.systems[system_index].get() }.system;
665664
// Move the full context object into the new future.
666665
let context = *context;
667666

@@ -703,7 +702,7 @@ impl ExecutorState {
703702
/// Caller must ensure no systems are currently borrowed.
704703
unsafe fn spawn_exclusive_system_task(&mut self, context: &Context, system_index: usize) {
705704
// SAFETY: this system is not running, no other reference exists
706-
let system = unsafe { &mut *context.environment.systems[system_index].get() };
705+
let system = &mut unsafe { &mut *context.environment.systems[system_index].get() }.system;
707706
// Move the full context object into the new future.
708707
let context = *context;
709708

@@ -785,12 +784,12 @@ impl ExecutorState {
785784

786785
fn apply_deferred(
787786
unapplied_systems: &FixedBitSet,
788-
systems: &[SyncUnsafeCell<ScheduleSystem>],
787+
systems: &[SyncUnsafeCell<SystemWithAccess>],
789788
world: &mut World,
790789
) -> Result<(), Box<dyn Any + Send>> {
791790
for system_index in unapplied_systems.ones() {
792791
// SAFETY: none of these systems are running, no other references exist
793-
let system = unsafe { &mut *systems[system_index].get() };
792+
let system = &mut unsafe { &mut *systems[system_index].get() }.system;
794793
let res = std::panic::catch_unwind(AssertUnwindSafe(|| {
795794
system.apply_deferred(world);
796795
}));
@@ -813,7 +812,7 @@ fn apply_deferred(
813812
/// - `world` must have permission to read any world data
814813
/// required by `conditions`.
815814
unsafe fn evaluate_and_fold_conditions(
816-
conditions: &mut [BoxedCondition],
815+
conditions: &mut [ConditionWithAccess],
817816
world: UnsafeWorldCell,
818817
error_handler: ErrorHandler,
819818
) -> bool {
@@ -823,7 +822,7 @@ unsafe fn evaluate_and_fold_conditions(
823822
)]
824823
conditions
825824
.iter_mut()
826-
.map(|condition| {
825+
.map(|ConditionWithAccess { condition, .. }| {
827826
// SAFETY:
828827
// - The caller ensures that `world` has permission to read any data
829828
// required by the condition.

crates/bevy_ecs/src/schedule/executor/simple.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ use std::eprintln;
1212
use crate::{
1313
error::{ErrorContext, ErrorHandler},
1414
schedule::{
15-
executor::is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule,
15+
executor::is_apply_deferred, ConditionWithAccess, ExecutorKind, SystemExecutor,
16+
SystemSchedule,
1617
},
1718
world::World,
1819
};
@@ -70,7 +71,7 @@ impl SystemExecutor for SimpleExecutor {
7071

7172
for system_index in 0..schedule.systems.len() {
7273
#[cfg(feature = "trace")]
73-
let name = schedule.systems[system_index].name();
74+
let name = schedule.systems[system_index].system.name();
7475
#[cfg(feature = "trace")]
7576
let should_run_span = info_span!("check_conditions", name = &*name).entered();
7677

@@ -105,7 +106,7 @@ impl SystemExecutor for SimpleExecutor {
105106

106107
should_run &= system_conditions_met;
107108

108-
let system = &mut schedule.systems[system_index];
109+
let system = &mut schedule.systems[system_index].system;
109110
if should_run {
110111
let valid_params = match system.validate_param(world) {
111112
Ok(()) => true,
@@ -195,7 +196,7 @@ impl SimpleExecutor {
195196
note = "Use SingleThreadedExecutor instead. See https://github.com/bevyengine/bevy/issues/18453 for motivation."
196197
)]
197198
fn evaluate_and_fold_conditions(
198-
conditions: &mut [BoxedCondition],
199+
conditions: &mut [ConditionWithAccess],
199200
world: &mut World,
200201
error_handler: ErrorHandler,
201202
) -> bool {
@@ -211,7 +212,7 @@ fn evaluate_and_fold_conditions(
211212
)]
212213
conditions
213214
.iter_mut()
214-
.map(|condition| {
215+
.map(|ConditionWithAccess { condition, .. }| {
215216
match condition.validate_param(world) {
216217
Ok(()) => (),
217218
Err(e) => {

0 commit comments

Comments
 (0)