From 93df8126453bfb774302ee46ce8507c53e18601d Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Wed, 24 Jun 2026 23:18:30 +0800 Subject: [PATCH 01/13] Expose system accesses and filters in BRP schedule.graph --- .../bevy_dev_tools/src/schedule_data/serde.rs | 293 +++++++++++++++--- crates/bevy_ecs/src/query/access.rs | 24 ++ crates/bevy_ecs/src/schedule/node.rs | 5 + crates/bevy_ecs/src/schedule/schedule.rs | 19 ++ 4 files changed, 295 insertions(+), 46 deletions(-) diff --git a/crates/bevy_dev_tools/src/schedule_data/serde.rs b/crates/bevy_dev_tools/src/schedule_data/serde.rs index 05f1ef6161d74..2b2b4ec42693d 100644 --- a/crates/bevy_dev_tools/src/schedule_data/serde.rs +++ b/crates/bevy_dev_tools/src/schedule_data/serde.rs @@ -71,6 +71,10 @@ pub struct ScheduleData { pub conflicts: Vec, } +/// A newtype for the index of a component. +#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Copy, PartialOrd, Ord, Hash)] +pub struct ComponentIndex(pub u32); + /// Data about a component type. #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] pub struct ComponentData { @@ -89,7 +93,65 @@ pub struct SystemData { pub exclusive: bool, /// Whether this system has deferred buffers to apply. pub deferred: bool, - // TODO: Store the conditions specific to this system. + + /// Combined access for the system + pub combined_access: AccessData, + /// Filtered accesses for the system + pub filtered_accesses: Vec, +} + +/// Access wrapper +#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Default)] +pub struct AccessData { + read_and_writes: Vec, + writes: Vec, + read_and_writes_inverted: bool, + writes_inverted: bool, + archetypal: Vec, +} + +impl AccessData { + fn new(value: &bevy_ecs::query::Access, trace: &mut ComponentTrace) -> Self { + Self { + read_and_writes: trace.get_indexes(value.read_and_writes().iter()), + writes: trace.get_indexes(value.writes().iter()), + read_and_writes_inverted: value.read_and_writes_inverted(), + writes_inverted: value.writes_inverted(), + archetypal: trace.get_indexes(value.archetypal().iter()), + } + } +} + +/// `AccessFilters` wrapper +#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Default)] +pub struct AccessFiltersData { + with: Vec, + without: Vec, +} + +/// `FilteredAccess` wrapper +#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Default)] +pub struct FilteredAccessData { + access: AccessData, + required: Vec, + filter_sets: Vec, +} + +impl FilteredAccessData { + fn new(value: &bevy_ecs::query::FilteredAccess, trace: &mut ComponentTrace) -> Self { + Self { + access: AccessData::new(value.access(), trace), + required: trace.get_indexes(value.required().iter()), + filter_sets: value + .filter_sets() + .iter() + .map(|f| AccessFiltersData { + with: trace.get_indexes(f.with().iter()), + without: trace.get_indexes(f.without().iter()), + }) + .collect(), + } + } } /// Data about a particular system set. @@ -145,6 +207,49 @@ pub enum AccessConflict { #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Copy, PartialOrd, Ord)] pub struct SystemSetIndex(pub u32); +// Helper to build the components used by systems in this schedule +struct ComponentTrace<'a> { + world_components: &'a Components, + component_id_to_index: HashMap, + components: Vec, +} + +impl<'a> ComponentTrace<'a> { + fn with_world_components(world_components: &'a Components) -> Self { + Self { + world_components, + component_id_to_index: HashMap::new(), + components: vec![], + } + } + + fn get_index(&mut self, id: ComponentId) -> u32 { + match self.component_id_to_index.entry(id) { + Entry::Occupied(entry) => *entry.get() as _, + Entry::Vacant(entry) => { + let component = self + .world_components + .get_info(id) + .expect("the component has already been registered by the system"); + + self.components.push(ComponentData { + name: format!("{}", component.name()), + }); + + *entry.insert(self.components.len() - 1) as _ + } + } + } + + fn get_indexes(&mut self, ids: impl Iterator) -> Vec { + ids.map(|id| self.get_index(id)).collect() + } + + fn get_indexes3<'b>(&mut self, ids: impl Iterator) -> Vec { + ids.map(|id| self.get_index(*id)).collect() + } +} + impl ScheduleData { /// Creates the data from the underlying [`Schedule`]. /// @@ -154,6 +259,8 @@ impl ScheduleData { world_components: &Components, build_metadata: Option<&ScheduleBuildMetadata>, ) -> Result { + let mut component_trace = ComponentTrace::with_world_components(world_components); + let graph = schedule.graph(); let mut system_key_to_index = HashMap::new(); @@ -169,14 +276,20 @@ impl ScheduleData { } let systems = schedule - .systems() + .systems_with_access() .map_err(|_| { ExtractAppDataError::ScheduleNotInitialized(format!("{:?}", schedule.label())) })? .enumerate() - .map(|(index, (key, system))| { + .map(|(index, (key, system_with_access))| { system_key_to_index.insert(key, index); + let system = system_with_access.system(); + let access = system_with_access.access(); + + let combined_access = access.combined_access(); + let filtered_accesses = access.filtered_accesses(); + let flags = system.flags(); SystemData { @@ -185,6 +298,11 @@ impl ScheduleData { == core::any::TypeId::of::(), exclusive: flags.contains(SystemStateFlags::EXCLUSIVE), deferred: flags.contains(SystemStateFlags::DEFERRED), + combined_access: AccessData::new(combined_access, &mut component_trace), + filtered_accesses: filtered_accesses + .iter() + .map(|fa| FilteredAccessData::new(fa, &mut component_trace)) + .collect(), } }) .collect(); @@ -258,9 +376,6 @@ impl ScheduleData { ); } - let mut component_id_to_index = HashMap::::new(); - let mut components = vec![]; - let conflicts = graph .conflicting_systems() .iter() @@ -279,29 +394,14 @@ impl ScheduleData { // The systems conflict on the world if there's no particular component IDs. AccessConflict::World } else { - AccessConflict::Components( - conflicts - .iter() - .map(|id| match component_id_to_index.entry(*id) { - Entry::Occupied(entry) => *entry.get() as _, - Entry::Vacant(entry) => { - let component = world_components.get_info(*id).expect( - "the component has already been registered by the system", - ); - - components.push(ComponentData { - name: format!("{}", component.name()), - }); - *entry.insert(components.len() - 1) as _ - } - }) - .collect(), - ) + AccessConflict::Components(component_trace.get_indexes3(conflicts.iter())) }, } }) .collect(); + let components = component_trace.components; + Ok(Self { name: format!("{:?}", schedule.label()), components, @@ -337,8 +437,9 @@ pub mod tests { use bevy_platform::collections::HashMap; use crate::schedule_data::serde::{ - AccessConflict, AppData, ComponentData, ExtractAppDataError, ScheduleData, ScheduleIndex, - SystemConflict, SystemData, SystemSetData, SystemSetIndex, + AccessConflict, AccessData, AccessFiltersData, AppData, ComponentData, ExtractAppDataError, + FilteredAccessData, ScheduleData, ScheduleIndex, SystemConflict, SystemData, SystemSetData, + SystemSetIndex, }; fn app_data_from_app(app: &mut App) -> Result { @@ -465,6 +566,19 @@ pub mod tests { .unwrap() as u32; }; + let reindex_component_vec = |components: &mut Vec| { + for component in components.iter_mut() { + reindex_component(component) + } + components.sort(); + }; + + let reindex_access = |access: &mut AccessData| { + reindex_component_vec(&mut access.archetypal); + reindex_component_vec(&mut access.read_and_writes); + reindex_component_vec(&mut access.writes); + }; + // Sort the conditions in a system set. for set in schedule.system_sets.iter_mut() { set.conditions @@ -485,6 +599,21 @@ pub mod tests { } schedule.dependency.sort(); + // Reindex access in systems + for system in schedule.systems.iter_mut() { + reindex_access(&mut system.combined_access); + + for filtered_access in system.filtered_accesses.iter_mut() { + reindex_access(&mut filtered_access.access); + + reindex_component_vec(&mut filtered_access.required); + for filter_set in filtered_access.filter_sets.iter_mut() { + reindex_component_vec(&mut filter_set.with); + reindex_component_vec(&mut filter_set.without); + } + } + } + // Reindex the conflicts. for conflict in schedule.conflicts.iter_mut() { reindex_system(&mut conflict.system_1); @@ -517,6 +646,58 @@ pub mod tests { apply_deferred: false, exclusive: false, deferred: false, + combined_access: AccessData::default(), + filtered_accesses: vec![], + } + } + + /// Convenience to create a [`SystemData`] for more detailed case. + pub fn full_system( + name: &str, + needs: Vec, + writes: Vec, + filter: Option>, + reject: Option>, + ) -> SystemData { + // +1 on inputted components as 0 = Disabled + let offset_needs: Vec = needs.iter().map(|n| n + 1).collect(); + let offset_writes: Vec = writes.iter().map(|n| n + 1).collect(); + + let offset_filter: Vec = filter + .and_then(|f| Some(f.iter().map(|n| n + 1).collect())) + .unwrap_or(offset_needs.clone()); + + let mut reject_filter: Vec = reject + .and_then(|f| Some(f.iter().map(|n| n + 1).collect())) + .unwrap_or(vec![]); + reject_filter.insert(0, 0); + + SystemData { + name: name.into(), + apply_deferred: false, + exclusive: false, + deferred: false, + combined_access: AccessData { + read_and_writes: offset_needs.clone(), + writes: offset_writes.clone(), + read_and_writes_inverted: false, + writes_inverted: false, + archetypal: vec![], + }, + filtered_accesses: vec![FilteredAccessData { + access: AccessData { + read_and_writes: offset_needs.clone(), + writes: offset_writes.clone(), + read_and_writes_inverted: false, + writes_inverted: false, + archetypal: vec![], + }, + required: offset_needs.clone(), + filter_sets: vec![AccessFiltersData { + with: offset_filter.clone(), + without: reject_filter.clone(), + }], + }], } } @@ -704,18 +885,24 @@ pub mod tests { apply_deferred: false, exclusive: false, deferred: true, + combined_access: AccessData::default(), + filtered_accesses: vec![], }, SystemData { name: "a1".into(), apply_deferred: false, exclusive: false, deferred: true, + combined_access: AccessData::default(), + filtered_accesses: vec![], }, SystemData { name: "apply_deferred".into(), apply_deferred: true, exclusive: true, deferred: false, + combined_access: AccessData::default(), + filtered_accesses: vec![], }, simple_system("b0"), simple_system("b1"), @@ -812,21 +999,39 @@ pub mod tests { assert_eq!(data.schedules.len(), 1); let schedule = &data.schedules[0]; assert_eq!(schedule.name, "Update"); + assert_eq!( + schedule.components, + [ + simple_component("Disabled"), + simple_component("MyComponent<0>"), + simple_component("MyComponent<1>"), + simple_component("MyComponent<2>"), + simple_component("MyComponent<3>"), + simple_component("MyComponent<4>"), + simple_component("MyComponent<5>"), + simple_component("MyComponent<6>"), + simple_component("MyComponent<7>"), + simple_component("MyComponent<8>"), + simple_component("MyComponent<9>"), + ] + ); + assert_eq!( schedule.systems, [ - simple_system("a0"), - simple_system("a1"), - simple_system("b0"), - simple_system("b1"), - simple_system("c0"), - simple_system("c1"), - simple_system("d0"), - simple_system("d1"), - simple_system("e0"), - simple_system("e1"), + full_system("a0", vec![0], vec![], None, None), + full_system("a1", vec![0], vec![], None, None), + full_system("b0", vec![1], vec![], None, None), + full_system("b1", vec![1], vec![1], None, None), + full_system("c0", vec![2, 3, 4, 5], vec![3], None, None), + full_system("c1", vec![2, 3, 4, 6], vec![2], None, None), + full_system("d0", vec![7], vec![7], Some(vec![7, 8]), None), + full_system("d1", vec![7], vec![7], None, Some(vec![8])), + full_system("e0", vec![9], vec![9], None, None), + full_system("e1", vec![9], vec![9], None, None), ] ); + assert_eq!( schedule.system_sets, [ @@ -864,19 +1069,15 @@ pub mod tests { (ScheduleIndex::System(8), ScheduleIndex::System(9)), ] ); - assert_eq!( - schedule.components, - [ - simple_component("MyComponent<1>"), - simple_component("MyComponent<2>"), - simple_component("MyComponent<3>"), - ] - ); assert_eq!( schedule.conflicts, [ - conflict(2, 3, AccessConflict::Components(vec![0])), - conflict(4, 5, AccessConflict::Components(vec![1, 2])) + // +1 on components for 0 = Disabled + + // b0, b1 conflict on 1 + conflict(2, 3, AccessConflict::Components(vec![2])), + // c0, c1 conflict on 2, 3 + conflict(4, 5, AccessConflict::Components(vec![3, 4])) ] ); } diff --git a/crates/bevy_ecs/src/query/access.rs b/crates/bevy_ecs/src/query/access.rs index 3e00230bd5e56..947723145cf3d 100644 --- a/crates/bevy_ecs/src/query/access.rs +++ b/crates/bevy_ecs/src/query/access.rs @@ -395,6 +395,30 @@ impl Access { AccessConflicts::Individual(conflicts) } + /// All accessed components, or forbidden components if + /// `Self::component_read_and_writes_inverted` is set. + pub fn read_and_writes(&self) -> &ComponentIdSet { + &self.read_and_writes + } + + /// All exclusively-accessed components, or components that may not be + /// exclusively accessed if `Self::component_writes_inverted` is set. + pub fn writes(&self) -> &ComponentIdSet { + &self.writes + } + + /// Is `true` if this component can read all components *except* those + /// present in `Self::read_and_writes`. + pub fn read_and_writes_inverted(&self) -> bool { + self.read_and_writes_inverted + } + + /// Is `true` if this component can write to all components *except* those + /// present in `Self::writes`. + pub fn writes_inverted(&self) -> bool { + self.writes_inverted + } + /// Returns the indices of the components that this has an archetypal access to. /// /// These are components whose values are not accessed (and thus will never cause conflicts), diff --git a/crates/bevy_ecs/src/schedule/node.rs b/crates/bevy_ecs/src/schedule/node.rs index b98815dc0b86d..d7c2cd58e4756 100644 --- a/crates/bevy_ecs/src/schedule/node.rs +++ b/crates/bevy_ecs/src/schedule/node.rs @@ -55,6 +55,11 @@ impl SystemWithAccess { pub fn system(&self) -> &ScheduleSystem { &self.system } + + /// Access + pub fn access(&self) -> &FilteredAccessSet { + &self.access + } } impl System for SystemWithAccess { diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 9c851a1210461..71fd64a05b4f8 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -708,6 +708,25 @@ impl Schedule { Ok(iter) } + /// Sys + pub fn systems_with_access( + &self, + ) -> Result + Sized, ScheduleNotInitialized> + { + if !self.executor_initialized { + return Err(ScheduleNotInitialized); + } + + let iter = self + .executable + .system_ids + .iter() + .zip(&self.executable.systems) + .map(|(&node_id, system)| (node_id, system)); + + Ok(iter) + } + /// Returns the number of systems in this schedule. pub fn systems_len(&self) -> usize { if !self.executor_initialized { From b187d5cec76743caecaa03189f32c4fb4e7d8d5a Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Thu, 25 Jun 2026 01:26:55 +0800 Subject: [PATCH 02/13] clippy --- crates/bevy_dev_tools/src/schedule_data/serde.rs | 6 +++--- crates/bevy_remote/src/builtin_methods.rs | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/bevy_dev_tools/src/schedule_data/serde.rs b/crates/bevy_dev_tools/src/schedule_data/serde.rs index 2b2b4ec42693d..024ec129ffd6b 100644 --- a/crates/bevy_dev_tools/src/schedule_data/serde.rs +++ b/crates/bevy_dev_tools/src/schedule_data/serde.rs @@ -568,7 +568,7 @@ pub mod tests { let reindex_component_vec = |components: &mut Vec| { for component in components.iter_mut() { - reindex_component(component) + reindex_component(component); } components.sort(); }; @@ -664,11 +664,11 @@ pub mod tests { let offset_writes: Vec = writes.iter().map(|n| n + 1).collect(); let offset_filter: Vec = filter - .and_then(|f| Some(f.iter().map(|n| n + 1).collect())) + .map(|f| f.iter().map(|n| n + 1).collect()) .unwrap_or(offset_needs.clone()); let mut reject_filter: Vec = reject - .and_then(|f| Some(f.iter().map(|n| n + 1).collect())) + .map(|f| f.iter().map(|n| n + 1).collect()) .unwrap_or(vec![]); reject_filter.insert(0, 0); diff --git a/crates/bevy_remote/src/builtin_methods.rs b/crates/bevy_remote/src/builtin_methods.rs index 2f10c3397d6a0..a7d22d5ef4810 100644 --- a/crates/bevy_remote/src/builtin_methods.rs +++ b/crates/bevy_remote/src/builtin_methods.rs @@ -2413,8 +2413,7 @@ mod tests { assert_eq!(res3.schedule_data.system_sets.len(), 6); assert_eq!(res3.schedule_data.hierarchy.len(), 6); assert_eq!(res3.schedule_data.dependency.len(), 4); - // Components are only currently recorded for conflicts - this may change in the future. - assert_eq!(res3.schedule_data.components.len(), 0); + assert_eq!(res3.schedule_data.components.len(), 2); assert_eq!(res3.schedule_data.conflicts.len(), 0); } From 9c380e222975d37eb498f6d93555265109750529 Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Thu, 25 Jun 2026 23:30:44 +0800 Subject: [PATCH 03/13] Use try_read_and_writes() --- .../bevy_dev_tools/src/schedule_data/serde.rs | 42 ++++++++++++++----- crates/bevy_ecs/src/query/access.rs | 12 ------ 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/crates/bevy_dev_tools/src/schedule_data/serde.rs b/crates/bevy_dev_tools/src/schedule_data/serde.rs index 024ec129ffd6b..96dc90293a219 100644 --- a/crates/bevy_dev_tools/src/schedule_data/serde.rs +++ b/crates/bevy_dev_tools/src/schedule_data/serde.rs @@ -111,14 +111,21 @@ pub struct AccessData { } impl AccessData { - fn new(value: &bevy_ecs::query::Access, trace: &mut ComponentTrace) -> Self { - Self { - read_and_writes: trace.get_indexes(value.read_and_writes().iter()), - writes: trace.get_indexes(value.writes().iter()), + fn try_new(value: &bevy_ecs::query::Access, trace: &mut ComponentTrace) -> Option { + let x = value.try_reads_and_writes(); + let y = value.try_writes(); + + if x.is_err() || y.is_err() { + return None; + } + + Some(Self { + read_and_writes: trace.get_indexes(x.unwrap().iter()), + writes: trace.get_indexes(y.unwrap().iter()), read_and_writes_inverted: value.read_and_writes_inverted(), writes_inverted: value.writes_inverted(), archetypal: trace.get_indexes(value.archetypal().iter()), - } + }) } } @@ -138,9 +145,18 @@ pub struct FilteredAccessData { } impl FilteredAccessData { - fn new(value: &bevy_ecs::query::FilteredAccess, trace: &mut ComponentTrace) -> Self { - Self { - access: AccessData::new(value.access(), trace), + fn try_new( + value: &bevy_ecs::query::FilteredAccess, + trace: &mut ComponentTrace, + ) -> Option { + let a = AccessData::try_new(value.access(), trace); + + if a.is_none() { + return None; + } + + Some(Self { + access: a.unwrap(), required: trace.get_indexes(value.required().iter()), filter_sets: value .filter_sets() @@ -150,7 +166,7 @@ impl FilteredAccessData { without: trace.get_indexes(f.without().iter()), }) .collect(), - } + }) } } @@ -298,10 +314,14 @@ impl ScheduleData { == core::any::TypeId::of::(), exclusive: flags.contains(SystemStateFlags::EXCLUSIVE), deferred: flags.contains(SystemStateFlags::DEFERRED), - combined_access: AccessData::new(combined_access, &mut component_trace), + combined_access: AccessData::try_new(combined_access, &mut component_trace) + .unwrap_or_default(), filtered_accesses: filtered_accesses .iter() - .map(|fa| FilteredAccessData::new(fa, &mut component_trace)) + .map(|fa| { + FilteredAccessData::try_new(fa, &mut component_trace) + .unwrap_or_default() + }) .collect(), } }) diff --git a/crates/bevy_ecs/src/query/access.rs b/crates/bevy_ecs/src/query/access.rs index 947723145cf3d..66a78623a6688 100644 --- a/crates/bevy_ecs/src/query/access.rs +++ b/crates/bevy_ecs/src/query/access.rs @@ -395,18 +395,6 @@ impl Access { AccessConflicts::Individual(conflicts) } - /// All accessed components, or forbidden components if - /// `Self::component_read_and_writes_inverted` is set. - pub fn read_and_writes(&self) -> &ComponentIdSet { - &self.read_and_writes - } - - /// All exclusively-accessed components, or components that may not be - /// exclusively accessed if `Self::component_writes_inverted` is set. - pub fn writes(&self) -> &ComponentIdSet { - &self.writes - } - /// Is `true` if this component can read all components *except* those /// present in `Self::read_and_writes`. pub fn read_and_writes_inverted(&self) -> bool { From 7cc897e3c061ae49b65c1717a863582f6a396866 Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Fri, 26 Jun 2026 00:14:07 +0800 Subject: [PATCH 04/13] wat --- crates/bevy_dev_tools/src/schedule_data/serde.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bevy_dev_tools/src/schedule_data/serde.rs b/crates/bevy_dev_tools/src/schedule_data/serde.rs index 96dc90293a219..8d55e034bef7e 100644 --- a/crates/bevy_dev_tools/src/schedule_data/serde.rs +++ b/crates/bevy_dev_tools/src/schedule_data/serde.rs @@ -151,6 +151,7 @@ impl FilteredAccessData { ) -> Option { let a = AccessData::try_new(value.access(), trace); + #[expect(clippy::question_mark, reason = "wat")] if a.is_none() { return None; } From 0abcd6612f8f96f77739ba97c45b40a52ff17a69 Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Fri, 26 Jun 2026 07:33:19 +0800 Subject: [PATCH 05/13] Early exit --- crates/bevy_dev_tools/src/schedule_data/serde.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/crates/bevy_dev_tools/src/schedule_data/serde.rs b/crates/bevy_dev_tools/src/schedule_data/serde.rs index 8d55e034bef7e..318627cb1f5c8 100644 --- a/crates/bevy_dev_tools/src/schedule_data/serde.rs +++ b/crates/bevy_dev_tools/src/schedule_data/serde.rs @@ -149,15 +149,10 @@ impl FilteredAccessData { value: &bevy_ecs::query::FilteredAccess, trace: &mut ComponentTrace, ) -> Option { - let a = AccessData::try_new(value.access(), trace); - - #[expect(clippy::question_mark, reason = "wat")] - if a.is_none() { - return None; - } + let a = AccessData::try_new(value.access(), trace)?; Some(Self { - access: a.unwrap(), + access: a, required: trace.get_indexes(value.required().iter()), filter_sets: value .filter_sets() From c125bc1d6920c38964fcc95b6e22ff71af60868a Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Fri, 26 Jun 2026 21:44:30 +0800 Subject: [PATCH 06/13] Review notes --- .../bevy_dev_tools/src/schedule_data/serde.rs | 100 +++++++++++++----- crates/bevy_ecs/src/schedule/node.rs | 2 +- crates/bevy_ecs/src/schedule/schedule.rs | 5 +- 3 files changed, 76 insertions(+), 31 deletions(-) diff --git a/crates/bevy_dev_tools/src/schedule_data/serde.rs b/crates/bevy_dev_tools/src/schedule_data/serde.rs index 318627cb1f5c8..d83a52efde04f 100644 --- a/crates/bevy_dev_tools/src/schedule_data/serde.rs +++ b/crates/bevy_dev_tools/src/schedule_data/serde.rs @@ -93,35 +93,44 @@ pub struct SystemData { pub exclusive: bool, /// Whether this system has deferred buffers to apply. pub deferred: bool, - /// Combined access for the system pub combined_access: AccessData, /// Filtered accesses for the system pub filtered_accesses: Vec, + // TODO: Store run conditions specific to this system. } -/// Access wrapper +/// A serializable version of [`bevy_ecs::query::Access`] (docs copied from there). +/// Tracks read and write access to specific elements in a collection. +/// +/// All indexes are into [`ScheduleData::components`] #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Default)] pub struct AccessData { - read_and_writes: Vec, - writes: Vec, - read_and_writes_inverted: bool, - writes_inverted: bool, - archetypal: Vec, + /// All accessed components, or forbidden components if + /// `Self::component_read_and_writes_inverted` is set. + pub read_and_writes: Vec, + /// All exclusively-accessed components, or components that may not be + /// exclusively accessed if `Self::component_writes_inverted` is set. + pub writes: Vec, + /// Is `true` if this component can read all components *except* those + /// present in `Self::read_and_writes`. + pub read_and_writes_inverted: bool, + /// Is `true` if this component can write to all components *except* those + /// present in `Self::writes`. + pub writes_inverted: bool, + /// Components that are not accessed, but whose presence in an archetype affect query results. + pub archetypal: Vec, } impl AccessData { fn try_new(value: &bevy_ecs::query::Access, trace: &mut ComponentTrace) -> Option { - let x = value.try_reads_and_writes(); - let y = value.try_writes(); - - if x.is_err() || y.is_err() { - return None; - } + // try_reads_and_writes returns error if reads_and_writes_inverted=true, thus its always false + let read_and_writes = value.try_reads_and_writes().ok()?; + let writes = value.try_writes().ok()?; Some(Self { - read_and_writes: trace.get_indexes(x.unwrap().iter()), - writes: trace.get_indexes(y.unwrap().iter()), + read_and_writes: trace.get_indexes(read_and_writes.iter()), + writes: trace.get_indexes(writes.iter()), read_and_writes_inverted: value.read_and_writes_inverted(), writes_inverted: value.writes_inverted(), archetypal: trace.get_indexes(value.archetypal().iter()), @@ -129,19 +138,50 @@ impl AccessData { } } -/// `AccessFilters` wrapper +/// A serializable version of [`bevy_ecs::query::AccessFilters`] (docs copied from there). +/// A clause in disjunctive normal form that filters entities by their components. +/// An [`AccessFilters`] matches entities that have *all* the components in the +/// `with` filters and *none* of the components in the `without` filters. +/// +/// All indexes are into [`ScheduleData::components`] #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Default)] pub struct AccessFiltersData { - with: Vec, - without: Vec, + /// The set of components that must all be present for this [`AccessFilters`] to match. + pub with: Vec, + /// The set of components that must all be absent for this [`AccessFilters`] to match. + pub without: Vec, } -/// `FilteredAccess` wrapper +/// A serializable version of [`bevy_ecs::query::FilteredAccess`] (docs copied from there). +/// An [`Access`] that has been filtered to include and exclude certain combinations of elements. +/// +/// Used internally to statically check if queries are disjoint. +/// +/// Subtle: a `read` or `write` in `access` should not be considered to imply a +/// `with` access. +/// +/// For example consider `Query>` this only has a `read` of `T` as doing +/// otherwise would allow for queries to be considered disjoint when they shouldn't: +/// - `Query<(&mut T, Option<&U>)>` read/write `T`, read `U`, with `U` +/// - `Query<&mut T, Without>` read/write `T`, without `U` +/// from this we could reasonably conclude that the queries are disjoint but they aren't. +/// +/// In order to solve this the actual access that `Query<(&mut T, Option<&U>)>` has +/// is read/write `T`, read `U`. It must still have a read `U` access otherwise the following +/// queries would be incorrectly considered disjoint: +/// - `Query<&mut T>` read/write `T` +/// - `Query>` accesses nothing +/// +/// See comments the [`WorldQuery`](super::WorldQuery) impls of [`AnyOf`](super::AnyOf)/`Option`/[`Or`](super::Or) for more information. #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Default)] pub struct FilteredAccessData { - access: AccessData, - required: Vec, - filter_sets: Vec, + /// + pub access: AccessData, + /// + pub required: Vec, + /// An array of filter sets to express `With` or `Without` clauses in disjunctive normal form, for example: `Or<(With, With)>`. + /// Filters like `(With, Or<(With, Without)>` are expanded into `Or<((With, With), (With, Without))>`. + pub filter_sets: Vec, } impl FilteredAccessData { @@ -149,10 +189,10 @@ impl FilteredAccessData { value: &bevy_ecs::query::FilteredAccess, trace: &mut ComponentTrace, ) -> Option { - let a = AccessData::try_new(value.access(), trace)?; + let access = AccessData::try_new(value.access(), trace)?; Some(Self { - access: a, + access, required: trace.get_indexes(value.required().iter()), filter_sets: value .filter_sets() @@ -219,7 +259,7 @@ pub enum AccessConflict { #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Copy, PartialOrd, Ord)] pub struct SystemSetIndex(pub u32); -// Helper to build the components used by systems in this schedule +/// Helper to build the components used by systems in this schedule struct ComponentTrace<'a> { world_components: &'a Components, component_id_to_index: HashMap, @@ -257,9 +297,9 @@ impl<'a> ComponentTrace<'a> { ids.map(|id| self.get_index(id)).collect() } - fn get_indexes3<'b>(&mut self, ids: impl Iterator) -> Vec { - ids.map(|id| self.get_index(*id)).collect() - } + // fn get_indexes3<'b>(&mut self, ids: impl Iterator) -> Vec { + // ids.map(|id| self.get_index(*id)).collect() + // } } impl ScheduleData { @@ -410,7 +450,9 @@ impl ScheduleData { // The systems conflict on the world if there's no particular component IDs. AccessConflict::World } else { - AccessConflict::Components(component_trace.get_indexes3(conflicts.iter())) + AccessConflict::Components( + component_trace.get_indexes(conflicts.iter().copied()), + ) }, } }) diff --git a/crates/bevy_ecs/src/schedule/node.rs b/crates/bevy_ecs/src/schedule/node.rs index d7c2cd58e4756..e7db3e90a0dc5 100644 --- a/crates/bevy_ecs/src/schedule/node.rs +++ b/crates/bevy_ecs/src/schedule/node.rs @@ -56,7 +56,7 @@ impl SystemWithAccess { &self.system } - /// Access + /// Returns the underlying [`FilteredAccessSet`] pub fn access(&self) -> &FilteredAccessSet { &self.access } diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 71fd64a05b4f8..1f9f4fc1d0bff 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -708,7 +708,10 @@ impl Schedule { Ok(iter) } - /// Sys + /// Returns an iterator over all systems with access in this schedule. + /// + /// Note: this method will return [`ScheduleNotInitialized`] if the + /// schedule has never been initialized or run. pub fn systems_with_access( &self, ) -> Result + Sized, ScheduleNotInitialized> From b09c32311687724c285a8c1bc731025b0c61f7ba Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Sat, 27 Jun 2026 00:21:02 +0800 Subject: [PATCH 07/13] Doc cleanup --- .../bevy_dev_tools/src/schedule_data/serde.rs | 60 +++++++------------ 1 file changed, 22 insertions(+), 38 deletions(-) diff --git a/crates/bevy_dev_tools/src/schedule_data/serde.rs b/crates/bevy_dev_tools/src/schedule_data/serde.rs index d83a52efde04f..972eae5947e1f 100644 --- a/crates/bevy_dev_tools/src/schedule_data/serde.rs +++ b/crates/bevy_dev_tools/src/schedule_data/serde.rs @@ -93,24 +93,24 @@ pub struct SystemData { pub exclusive: bool, /// Whether this system has deferred buffers to apply. pub deferred: bool, - /// Combined access for the system + /// Combined access for the system, summarises `Self::filtered_accesses` pub combined_access: AccessData, - /// Filtered accesses for the system + /// Filtered accesses for the system, generally 1x per system query pub filtered_accesses: Vec, // TODO: Store run conditions specific to this system. } -/// A serializable version of [`bevy_ecs::query::Access`] (docs copied from there). -/// Tracks read and write access to specific elements in a collection. +/// A serializable version of [`bevy_ecs::query::Access`]. +/// Tracks read and write access to specific components. /// /// All indexes are into [`ScheduleData::components`] #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Default)] pub struct AccessData { /// All accessed components, or forbidden components if - /// `Self::component_read_and_writes_inverted` is set. + /// `Self::read_and_writes_inverted` is set. pub read_and_writes: Vec, /// All exclusively-accessed components, or components that may not be - /// exclusively accessed if `Self::component_writes_inverted` is set. + /// exclusively accessed if `Self::writes_inverted` is set. pub writes: Vec, /// Is `true` if this component can read all components *except* those /// present in `Self::read_and_writes`. @@ -124,7 +124,12 @@ pub struct AccessData { impl AccessData { fn try_new(value: &bevy_ecs::query::Access, trace: &mut ComponentTrace) -> Option { - // try_reads_and_writes returns error if reads_and_writes_inverted=true, thus its always false + // TODO: `try_reads_and_writes` returns error if `read_and_writes_inverted=true`, + // thus `AccessData` always has `read_and_writes_inverted=false` + // Consider making `try_reads_and_writes()` return an enum of Normal or Except + // Then can remove `read_and_writes_inverted()`. + // Similarly for `try_writes()` and `writes_inverted` and `writes_inverted()`. + let read_and_writes = value.try_reads_and_writes().ok()?; let writes = value.try_writes().ok()?; @@ -138,49 +143,32 @@ impl AccessData { } } -/// A serializable version of [`bevy_ecs::query::AccessFilters`] (docs copied from there). +/// A serializable version of [`bevy_ecs::query::AccessFilters`]. /// A clause in disjunctive normal form that filters entities by their components. -/// An [`AccessFilters`] matches entities that have *all* the components in the +/// An [`AccessFiltersData`] matches entities that have *all* the components in the /// `with` filters and *none* of the components in the `without` filters. /// /// All indexes are into [`ScheduleData::components`] #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Default)] pub struct AccessFiltersData { - /// The set of components that must all be present for this [`AccessFilters`] to match. + /// The set of components that must all be present for this [`AccessFiltersData`] to match. pub with: Vec, - /// The set of components that must all be absent for this [`AccessFilters`] to match. + /// The set of components that must all be absent for this [`AccessFiltersData`] to match. pub without: Vec, } /// A serializable version of [`bevy_ecs::query::FilteredAccess`] (docs copied from there). -/// An [`Access`] that has been filtered to include and exclude certain combinations of elements. -/// -/// Used internally to statically check if queries are disjoint. -/// -/// Subtle: a `read` or `write` in `access` should not be considered to imply a -/// `with` access. -/// -/// For example consider `Query>` this only has a `read` of `T` as doing -/// otherwise would allow for queries to be considered disjoint when they shouldn't: -/// - `Query<(&mut T, Option<&U>)>` read/write `T`, read `U`, with `U` -/// - `Query<&mut T, Without>` read/write `T`, without `U` -/// from this we could reasonably conclude that the queries are disjoint but they aren't. -/// -/// In order to solve this the actual access that `Query<(&mut T, Option<&U>)>` has -/// is read/write `T`, read `U`. It must still have a read `U` access otherwise the following -/// queries would be incorrectly considered disjoint: -/// - `Query<&mut T>` read/write `T` -/// - `Query>` accesses nothing +/// Corresponds to a query in the system signature. /// -/// See comments the [`WorldQuery`](super::WorldQuery) impls of [`AnyOf`](super::AnyOf)/`Option`/[`Or`](super::Or) for more information. +/// All indexes are into [`ScheduleData::components`] #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Default)] pub struct FilteredAccessData { - /// + /// The access of the Query (components that have read/writes) pub access: AccessData, - /// + /// Required components (summary of read/writes from `Self::access` ). pub required: Vec, - /// An array of filter sets to express `With` or `Without` clauses in disjunctive normal form, for example: `Or<(With, With)>`. - /// Filters like `(With, Or<(With, Without)>` are expanded into `Or<((With, With), (With, Without))>`. + /// An array of filter sets to express `With` or `Without` clauses. + /// Each filter set is `Or`-d together pub filter_sets: Vec, } @@ -296,10 +284,6 @@ impl<'a> ComponentTrace<'a> { fn get_indexes(&mut self, ids: impl Iterator) -> Vec { ids.map(|id| self.get_index(id)).collect() } - - // fn get_indexes3<'b>(&mut self, ids: impl Iterator) -> Vec { - // ids.map(|id| self.get_index(*id)).collect() - // } } impl ScheduleData { From ae194a8d4e8e2b1cf2c9d78872f53f680d3913be Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Sat, 27 Jun 2026 00:26:53 +0800 Subject: [PATCH 08/13] Sp --- crates/bevy_dev_tools/src/schedule_data/serde.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_dev_tools/src/schedule_data/serde.rs b/crates/bevy_dev_tools/src/schedule_data/serde.rs index 972eae5947e1f..06e8859b0d8d5 100644 --- a/crates/bevy_dev_tools/src/schedule_data/serde.rs +++ b/crates/bevy_dev_tools/src/schedule_data/serde.rs @@ -93,7 +93,7 @@ pub struct SystemData { pub exclusive: bool, /// Whether this system has deferred buffers to apply. pub deferred: bool, - /// Combined access for the system, summarises `Self::filtered_accesses` + /// Combined access for the system, summarizes `Self::filtered_accesses` pub combined_access: AccessData, /// Filtered accesses for the system, generally 1x per system query pub filtered_accesses: Vec, From ee0458c97bdc11edbe83b685e16f1fa1c368b837 Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Sat, 27 Jun 2026 10:28:30 +0800 Subject: [PATCH 09/13] Return InvertibleComponentIdSet from Access for reads_and_writes --- .../migration-guides/access_is_invertible.md | 16 +++++++ crates/bevy_ecs/src/query/access.rs | 44 ++++++++++++------- 2 files changed, 43 insertions(+), 17 deletions(-) create mode 100644 _release-content/migration-guides/access_is_invertible.md diff --git a/_release-content/migration-guides/access_is_invertible.md b/_release-content/migration-guides/access_is_invertible.md new file mode 100644 index 0000000000000..13157a7782a86 --- /dev/null +++ b/_release-content/migration-guides/access_is_invertible.md @@ -0,0 +1,16 @@ +--- +title: "Reads and writes from `Access` are exposed" +pull_requests: [] +--- + +Removed from [`bevy_ecs::query::Access`] methods that gave `Result>`: + +- `try_reads_and_writes()` +- `try_writes()` + +Added new functions that return a new enum `InvertibleComponentIdSet`: + +- `reads_and_writes()` +- `writes()` + +The `try_` methods would fail for exclusion queries. diff --git a/crates/bevy_ecs/src/query/access.rs b/crates/bevy_ecs/src/query/access.rs index 66a78623a6688..b20cc2ffef44b 100644 --- a/crates/bevy_ecs/src/query/access.rs +++ b/crates/bevy_ecs/src/query/access.rs @@ -419,30 +419,22 @@ impl Access { &self.archetypal } - /// Returns the set of components with read or write access, - /// or an error if the access is unbounded. - pub fn try_reads_and_writes(&self) -> Result<&ComponentIdSet, UnboundedAccessError> { + /// Returns the set of components with read or write access + pub fn reads_and_writes(&self) -> InvertibleComponentIdSet<'_> { // writes_inverted is only ever true when read_and_writes_inverted is // also true. Therefore it is sufficient to check just read_and_writes_inverted. if self.read_and_writes_inverted { - return Err(UnboundedAccessError { - writes_inverted: self.writes_inverted, - read_and_writes_inverted: self.read_and_writes_inverted, - }); + return InvertibleComponentIdSet::Excluded(&self.read_and_writes); } - Ok(&self.read_and_writes) + InvertibleComponentIdSet::Included(&self.read_and_writes) } - /// Returns the set of components with write access, - /// or an error if the access is unbounded. - pub fn try_writes(&self) -> Result<&ComponentIdSet, UnboundedAccessError> { + /// Returns the set of components with write access + pub fn writes(&self) -> InvertibleComponentIdSet<'_> { if self.writes_inverted { - return Err(UnboundedAccessError { - writes_inverted: self.writes_inverted, - read_and_writes_inverted: self.read_and_writes_inverted, - }); + return InvertibleComponentIdSet::Excluded(&self.writes); } - Ok(&self.writes) + InvertibleComponentIdSet::Included(&self.writes) } /// Returns an iterator over the component IDs and their [`ComponentAccessKind`]. @@ -478,7 +470,16 @@ impl Access { pub fn try_iter_access( &self, ) -> Result + '_, UnboundedAccessError> { - let reads_and_writes = self.try_reads_and_writes()?.iter().map(|index| { + let a = self.reads_and_writes(); + + let InvertibleComponentIdSet::Included(b) = a else { + return Err(UnboundedAccessError { + writes_inverted: self.writes_inverted, + read_and_writes_inverted: self.read_and_writes_inverted, + }); + }; + + let reads_and_writes = b.iter().map(|index| { if self.writes.contains(index) { ComponentAccessKind::Exclusive(index) } else { @@ -495,6 +496,15 @@ impl Access { } } +/// Either all or nothing in a [`ComponentIdSet`] +#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy)] +pub enum InvertibleComponentIdSet<'a> { + /// All components in the set + Included(&'a ComponentIdSet), + /// All components *not* in the set + Excluded(&'a ComponentIdSet), +} + /// Performs an in-place union of `other` into `self`, where either set may be inverted. /// /// Each set corresponds to a `FixedBitSet` if `inverted` is `false`, From 2c062a885c28e03fddacd538c8e0c6c75cc605fc Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Sat, 27 Jun 2026 12:54:47 +0800 Subject: [PATCH 10/13] More impl --- crates/bevy_ecs/src/query/access.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/crates/bevy_ecs/src/query/access.rs b/crates/bevy_ecs/src/query/access.rs index b20cc2ffef44b..8b63d76b5c9e3 100644 --- a/crates/bevy_ecs/src/query/access.rs +++ b/crates/bevy_ecs/src/query/access.rs @@ -505,6 +505,23 @@ pub enum InvertibleComponentIdSet<'a> { Excluded(&'a ComponentIdSet), } +impl InvertibleComponentIdSet<'_> { + /// Returns true if this is Excluded, otherwise false + pub fn inverted(&self) -> bool { + match self { + InvertibleComponentIdSet::Included(_) => false, + InvertibleComponentIdSet::Excluded(_) => true, + } + } + + /// Iterate the underlying component ids + pub fn iter(&self) -> ComponentIdIter> { + match self { + InvertibleComponentIdSet::Included(b) | InvertibleComponentIdSet::Excluded(b) => b.iter(), + } + } +} + /// Performs an in-place union of `other` into `self`, where either set may be inverted. /// /// Each set corresponds to a `FixedBitSet` if `inverted` is `false`, From 127bd1f9f673897cba9c259eea41dc6bc65d32aa Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Sat, 27 Jun 2026 13:11:11 +0800 Subject: [PATCH 11/13] Fmt --- crates/bevy_ecs/src/query/access.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/query/access.rs b/crates/bevy_ecs/src/query/access.rs index 8b63d76b5c9e3..f6400c6a14a99 100644 --- a/crates/bevy_ecs/src/query/access.rs +++ b/crates/bevy_ecs/src/query/access.rs @@ -517,7 +517,9 @@ impl InvertibleComponentIdSet<'_> { /// Iterate the underlying component ids pub fn iter(&self) -> ComponentIdIter> { match self { - InvertibleComponentIdSet::Included(b) | InvertibleComponentIdSet::Excluded(b) => b.iter(), + InvertibleComponentIdSet::Included(b) | InvertibleComponentIdSet::Excluded(b) => { + b.iter() + } } } } From d8e822f209f9bd0897b3fb03bb523127ff05eeae Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Sat, 27 Jun 2026 14:02:14 +0800 Subject: [PATCH 12/13] Better names --- crates/bevy_ecs/src/query/access.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs/src/query/access.rs b/crates/bevy_ecs/src/query/access.rs index f6400c6a14a99..d130c4cb6bb87 100644 --- a/crates/bevy_ecs/src/query/access.rs +++ b/crates/bevy_ecs/src/query/access.rs @@ -470,16 +470,16 @@ impl Access { pub fn try_iter_access( &self, ) -> Result + '_, UnboundedAccessError> { - let a = self.reads_and_writes(); + let invertible_set = self.reads_and_writes(); - let InvertibleComponentIdSet::Included(b) = a else { + let InvertibleComponentIdSet::Included(component_set) = invertible_set else { return Err(UnboundedAccessError { writes_inverted: self.writes_inverted, read_and_writes_inverted: self.read_and_writes_inverted, }); }; - let reads_and_writes = b.iter().map(|index| { + let reads_and_writes = component_set.iter().map(|index| { if self.writes.contains(index) { ComponentAccessKind::Exclusive(index) } else { From f1a70595e2604e66db26cb7c0d1833c0a2841b79 Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Sat, 27 Jun 2026 10:53:40 +0800 Subject: [PATCH 13/13] Use InvertibleComponentIdSet --- .../bevy_dev_tools/src/schedule_data/serde.rs | 40 +++++++------------ crates/bevy_ecs/src/query/access.rs | 12 ------ 2 files changed, 15 insertions(+), 37 deletions(-) diff --git a/crates/bevy_dev_tools/src/schedule_data/serde.rs b/crates/bevy_dev_tools/src/schedule_data/serde.rs index 06e8859b0d8d5..feea65bad16f0 100644 --- a/crates/bevy_dev_tools/src/schedule_data/serde.rs +++ b/crates/bevy_dev_tools/src/schedule_data/serde.rs @@ -123,23 +123,20 @@ pub struct AccessData { } impl AccessData { - fn try_new(value: &bevy_ecs::query::Access, trace: &mut ComponentTrace) -> Option { - // TODO: `try_reads_and_writes` returns error if `read_and_writes_inverted=true`, - // thus `AccessData` always has `read_and_writes_inverted=false` - // Consider making `try_reads_and_writes()` return an enum of Normal or Except - // Then can remove `read_and_writes_inverted()`. - // Similarly for `try_writes()` and `writes_inverted` and `writes_inverted()`. + fn new(value: &bevy_ecs::query::Access, trace: &mut ComponentTrace) -> Self { + let read_and_writes = value.reads_and_writes(); + let writes = value.writes(); - let read_and_writes = value.try_reads_and_writes().ok()?; - let writes = value.try_writes().ok()?; + let read_and_writes_inverted = read_and_writes.inverted(); + let writes_inverted = writes.inverted(); - Some(Self { + Self { read_and_writes: trace.get_indexes(read_and_writes.iter()), writes: trace.get_indexes(writes.iter()), - read_and_writes_inverted: value.read_and_writes_inverted(), - writes_inverted: value.writes_inverted(), + read_and_writes_inverted, + writes_inverted, archetypal: trace.get_indexes(value.archetypal().iter()), - }) + } } } @@ -173,13 +170,10 @@ pub struct FilteredAccessData { } impl FilteredAccessData { - fn try_new( - value: &bevy_ecs::query::FilteredAccess, - trace: &mut ComponentTrace, - ) -> Option { - let access = AccessData::try_new(value.access(), trace)?; + fn new(value: &bevy_ecs::query::FilteredAccess, trace: &mut ComponentTrace) -> Self { + let access = AccessData::new(value.access(), trace); - Some(Self { + Self { access, required: trace.get_indexes(value.required().iter()), filter_sets: value @@ -190,7 +184,7 @@ impl FilteredAccessData { without: trace.get_indexes(f.without().iter()), }) .collect(), - }) + } } } @@ -334,14 +328,10 @@ impl ScheduleData { == core::any::TypeId::of::(), exclusive: flags.contains(SystemStateFlags::EXCLUSIVE), deferred: flags.contains(SystemStateFlags::DEFERRED), - combined_access: AccessData::try_new(combined_access, &mut component_trace) - .unwrap_or_default(), + combined_access: AccessData::new(combined_access, &mut component_trace), filtered_accesses: filtered_accesses .iter() - .map(|fa| { - FilteredAccessData::try_new(fa, &mut component_trace) - .unwrap_or_default() - }) + .map(|fa| FilteredAccessData::new(fa, &mut component_trace)) .collect(), } }) diff --git a/crates/bevy_ecs/src/query/access.rs b/crates/bevy_ecs/src/query/access.rs index d130c4cb6bb87..a6d1860ec220e 100644 --- a/crates/bevy_ecs/src/query/access.rs +++ b/crates/bevy_ecs/src/query/access.rs @@ -395,18 +395,6 @@ impl Access { AccessConflicts::Individual(conflicts) } - /// Is `true` if this component can read all components *except* those - /// present in `Self::read_and_writes`. - pub fn read_and_writes_inverted(&self) -> bool { - self.read_and_writes_inverted - } - - /// Is `true` if this component can write to all components *except* those - /// present in `Self::writes`. - pub fn writes_inverted(&self) -> bool { - self.writes_inverted - } - /// Returns the indices of the components that this has an archetypal access to. /// /// These are components whose values are not accessed (and thus will never cause conflicts),