diff --git a/crates/bevy_ecs/macros/src/fetch.rs b/crates/bevy_ecs/macros/src/fetch.rs index 5d21a93f642d4..8231fdf52b6dd 100644 --- a/crates/bevy_ecs/macros/src/fetch.rs +++ b/crates/bevy_ecs/macros/src/fetch.rs @@ -255,8 +255,19 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream { true #(&& self.#field_idents.archetype_filter_fetch(_archetype_index))* } + fn init_state(world: &mut #path::world::World) -> Self::State { + #state_struct_name { + #(#field_idents: #path::query::#fetch_type_alias::<'static, #field_types>::init_state(world),)* + #(#ignored_field_idents: Default::default(),)* + } + } + fn update_component_access(state: &Self::State, _access: &mut #path::query::FilteredAccess<#path::component::ComponentId>) { - #( #path::query::#fetch_type_alias::<'static, #field_types> :: update_component_access(&state.#field_idents, _access); )* + #(#path::query::#fetch_type_alias::<'static, #field_types>::update_component_access(&state.#field_idents, _access);)* + } + + fn matches_component_set(state: &Self::State, _set_contains_id: &impl Fn(#path::component::ComponentId) -> bool) -> bool { + true #(&& #path::query::#fetch_type_alias::<'static, #field_types>::matches_component_set(&state.#field_idents, _set_contains_id))* } fn update_archetype_component_access(state: &Self::State, _archetype: &#path::archetype::Archetype, _access: &mut #path::query::Access<#path::archetype::ArchetypeComponentId>) { @@ -274,24 +285,9 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream { let state_impl = quote! { #[doc(hidden)] #visibility struct #state_struct_name #user_impl_generics #user_where_clauses { - #(#field_idents: <#field_types as #path::query::WorldQuery>::State,)* #(#ignored_field_idents: #ignored_field_types,)* } - - impl #user_impl_generics #path::query::FetchState for #state_struct_name #user_ty_generics #user_where_clauses { - fn init(world: &mut #path::world::World) -> Self { - #state_struct_name { - #(#field_idents: <<#field_types as #path::query::WorldQuery>::State as #path::query::FetchState>::init(world),)* - #(#ignored_field_idents: Default::default(),)* - } - } - - fn matches_component_set(&self, _set_contains_id: &impl Fn(#path::component::ComponentId) -> bool) -> bool { - true #(&& self.#field_idents.matches_component_set(_set_contains_id))* - - } - } }; let read_only_fetch_impl = if fetch_struct_attributes.is_mutable { diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 33b3f47529c2e..c634b30359cdf 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -54,7 +54,7 @@ use std::{cell::UnsafeCell, marker::PhantomData}; /// /// The derive macro implements [`WorldQuery`] for your type and declares an additional struct /// which will be used as an item for query iterators. The implementation also generates two other -/// structs that implement [`Fetch`] and [`FetchState`] and are used as [`WorldQuery::Fetch`](WorldQueryGats::Fetch) and +/// structs that implement [`Fetch`] and are used as [`WorldQuery::Fetch`](WorldQueryGats::Fetch) and /// [`WorldQuery::State`] associated types respectively. /// /// The derive macro requires every struct field to implement the `WorldQuery` trait. @@ -321,7 +321,7 @@ use std::{cell::UnsafeCell, marker::PhantomData}; /// component access of `ROQueryFetch` should be a subset of `QueryFetch` pub unsafe trait WorldQuery: for<'w> WorldQueryGats<'w, _State = Self::State> { type ReadOnly: ReadOnlyWorldQuery; - type State: FetchState; + type State: Send + Sync; /// This function manually implements variance for the query items. fn shrink<'wlong: 'wshort, 'wshort>(item: QueryItem<'wlong, Self>) -> QueryItem<'wshort, Self>; @@ -346,7 +346,7 @@ pub type ROQueryItem<'w, Q> = QueryItem<'w, ::ReadOnly>; /// A helper trait for [`WorldQuery`] that works around Rust's lack of Generic Associated Types pub trait WorldQueryGats<'world> { type Fetch: Fetch<'world, State = Self::_State>; - type _State: FetchState; + type _State: Send + Sync; } /// Types that implement this trait are responsible for fetching query items from tables or @@ -359,17 +359,17 @@ pub trait WorldQueryGats<'world> { /// /// Implementor must ensure that [`Fetch::update_component_access`] and /// [`Fetch::update_archetype_component_access`] exactly reflects the results of -/// [`FetchState::matches_component_set`], [`Fetch::archetype_fetch`], and +/// [`Fetch::matches_component_set`], [`Fetch::archetype_fetch`], and /// [`Fetch::table_fetch`]. pub unsafe trait Fetch<'world>: Sized { type Item; - type State: FetchState; + type State: Send + Sync; /// Creates a new instance of this fetch. /// /// # Safety /// - /// `state` must have been initialized (via [`FetchState::init`]) using the same `world` passed + /// `state` must have been initialized (via [`Fetch::init_state`]) using the same `world` passed /// in to this function. unsafe fn init( world: &'world World, @@ -453,6 +453,8 @@ pub unsafe trait Fetch<'world>: Sized { true } + fn init_state(world: &mut World) -> Self::State; + // This does not have a default body of `{}` because 99% of cases need to add accesses // and forgetting to do so would be unsound. fn update_component_access(state: &Self::State, access: &mut FilteredAccess); @@ -463,20 +465,17 @@ pub unsafe trait Fetch<'world>: Sized { archetype: &Archetype, access: &mut Access, ); -} -/// State used to construct a Fetch. This will be cached inside [`QueryState`](crate::query::QueryState), -/// so it is best to move as much data / computation here as possible to reduce the cost of -/// constructing Fetch. -pub trait FetchState: Send + Sync + Sized { - fn init(world: &mut World) -> Self; - fn matches_component_set(&self, set_contains_id: &impl Fn(ComponentId) -> bool) -> bool; + fn matches_component_set( + state: &Self::State, + set_contains_id: &impl Fn(ComponentId) -> bool, + ) -> bool; } /// SAFETY: no component or archetype access unsafe impl WorldQuery for Entity { type ReadOnly = Self; - type State = EntityState; + type State = (); fn shrink<'wlong: 'wshort, 'wshort>(item: QueryItem<'wlong, Self>) -> QueryItem<'wshort, Self> { item @@ -493,30 +492,15 @@ pub struct EntityFetch<'w> { /// SAFETY: access is read only unsafe impl ReadOnlyWorldQuery for Entity {} -/// The [`FetchState`] of [`Entity`]. -#[doc(hidden)] -pub struct EntityState; - -impl FetchState for EntityState { - fn init(_world: &mut World) -> Self { - Self - } - - #[inline] - fn matches_component_set(&self, _set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { - true - } -} - impl<'w> WorldQueryGats<'w> for Entity { type Fetch = EntityFetch<'w>; - type _State = EntityState; + type _State = (); } /// SAFETY: no component or archetype access unsafe impl<'w> Fetch<'w> for EntityFetch<'w> { type Item = Entity; - type State = EntityState; + type State = (); const IS_DENSE: bool = true; @@ -524,7 +508,7 @@ unsafe impl<'w> Fetch<'w> for EntityFetch<'w> { unsafe fn init( _world: &'w World, - _state: &EntityState, + _state: &Self::State, _last_change_tick: u32, _change_tick: u32, ) -> EntityFetch<'w> { @@ -558,6 +542,8 @@ unsafe impl<'w> Fetch<'w> for EntityFetch<'w> { *entities.get(archetype_index) } + fn init_state(_world: &mut World) -> Self::State {} + fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) {} fn update_archetype_component_access( @@ -566,39 +552,26 @@ unsafe impl<'w> Fetch<'w> for EntityFetch<'w> { _access: &mut Access, ) { } + + #[inline] + fn matches_component_set( + _state: &Self::State, + _set_contains_id: &impl Fn(ComponentId) -> bool, + ) -> bool { + true + } } /// SAFETY: `ROQueryFetch` is the same as `QueryFetch` unsafe impl WorldQuery for &T { type ReadOnly = Self; - type State = ComponentIdState; + type State = ComponentId; fn shrink<'wlong: 'wshort, 'wshort>(item: QueryItem<'wlong, Self>) -> QueryItem<'wshort, Self> { item } } -/// The [`FetchState`] of `&T`. -#[doc(hidden)] -pub struct ComponentIdState { - component_id: ComponentId, - marker: PhantomData, -} - -impl FetchState for ComponentIdState { - fn init(world: &mut World) -> Self { - let component_id = world.init_component::(); - ComponentIdState { - component_id, - marker: PhantomData, - } - } - - fn matches_component_set(&self, set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { - set_contains_id(self.component_id) - } -} - /// The [`Fetch`] of `&T`. #[doc(hidden)] pub struct ReadFetch<'w, T> { @@ -626,14 +599,14 @@ unsafe impl ReadOnlyWorldQuery for &T {} impl<'w, T: Component> WorldQueryGats<'w> for &T { type Fetch = ReadFetch<'w, T>; - type _State = ComponentIdState; + type _State = ComponentId; } // SAFETY: component access and archetype component access are properly updated to reflect that T is // read unsafe impl<'w, T: Component> Fetch<'w> for ReadFetch<'w, T> { type Item = &'w T; - type State = ComponentIdState; + type State = ComponentId; const IS_DENSE: bool = { match T::Storage::STORAGE_TYPE { @@ -646,7 +619,7 @@ unsafe impl<'w, T: Component> Fetch<'w> for ReadFetch<'w, T> { unsafe fn init( world: &'w World, - state: &ComponentIdState, + state: &Self::State, _last_change_tick: u32, _change_tick: u32, ) -> ReadFetch<'w, T> { @@ -654,13 +627,8 @@ unsafe impl<'w, T: Component> Fetch<'w> for ReadFetch<'w, T> { table_components: None, entity_table_rows: None, entities: None, - sparse_set: (T::Storage::STORAGE_TYPE == StorageType::SparseSet).then(|| { - world - .storages() - .sparse_sets - .get(state.component_id) - .unwrap() - }), + sparse_set: (T::Storage::STORAGE_TYPE == StorageType::SparseSet) + .then(|| world.storages().sparse_sets.get(*state).unwrap()), } } @@ -674,9 +642,7 @@ unsafe impl<'w, T: Component> Fetch<'w> for ReadFetch<'w, T> { match T::Storage::STORAGE_TYPE { StorageType::Table => { self.entity_table_rows = Some(archetype.entity_table_rows().into()); - let column = tables[archetype.table_id()] - .get_column(state.component_id) - .unwrap(); + let column = tables[archetype.table_id()].get_column(*state).unwrap(); self.table_components = Some(column.get_data_slice().into()); } StorageType::SparseSet => self.entities = Some(archetype.entities().into()), @@ -685,13 +651,7 @@ unsafe impl<'w, T: Component> Fetch<'w> for ReadFetch<'w, T> { #[inline] unsafe fn set_table(&mut self, state: &Self::State, table: &'w Table) { - self.table_components = Some( - table - .get_column(state.component_id) - .unwrap() - .get_data_slice() - .into(), - ); + self.table_components = Some(table.get_column(*state).unwrap().get_data_slice().into()); } #[inline] @@ -727,13 +687,17 @@ unsafe impl<'w, T: Component> Fetch<'w> for ReadFetch<'w, T> { components.get(table_row).deref() } + fn init_state(world: &mut World) -> Self::State { + world.init_component::() + } + fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { assert!( - !access.access().has_write(state.component_id), + !access.access().has_write(*state), "&{} conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.", std::any::type_name::(), ); - access.add_read(state.component_id); + access.add_read(*state); } fn update_archetype_component_access( @@ -741,18 +705,23 @@ unsafe impl<'w, T: Component> Fetch<'w> for ReadFetch<'w, T> { archetype: &Archetype, access: &mut Access, ) { - if let Some(archetype_component_id) = - archetype.get_archetype_component_id(state.component_id) - { + if let Some(archetype_component_id) = archetype.get_archetype_component_id(*state) { access.add_read(archetype_component_id); } } + + fn matches_component_set( + state: &Self::State, + set_contains_id: &impl Fn(ComponentId) -> bool, + ) -> bool { + set_contains_id(*state) + } } /// SAFETY: access of `&T` is a subset of `&mut T` unsafe impl<'w, T: Component> WorldQuery for &'w mut T { type ReadOnly = &'w T; - type State = ComponentIdState; + type State = ComponentId; fn shrink<'wlong: 'wshort, 'wshort>(item: QueryItem<'wlong, Self>) -> QueryItem<'wshort, Self> { item @@ -790,14 +759,14 @@ impl Clone for WriteFetch<'_, T> { impl<'w, T: Component> WorldQueryGats<'w> for &mut T { type Fetch = WriteFetch<'w, T>; - type _State = ComponentIdState; + type _State = ComponentId; } /// SAFETY: component access and archetype component access are properly updated to reflect that T is /// read and write unsafe impl<'w, T: Component> Fetch<'w> for WriteFetch<'w, T> { type Item = Mut<'w, T>; - type State = ComponentIdState; + type State = ComponentId; const IS_DENSE: bool = { match T::Storage::STORAGE_TYPE { @@ -810,7 +779,7 @@ unsafe impl<'w, T: Component> Fetch<'w> for WriteFetch<'w, T> { unsafe fn init( world: &'w World, - state: &ComponentIdState, + state: &Self::State, last_change_tick: u32, change_tick: u32, ) -> Self { @@ -818,13 +787,8 @@ unsafe impl<'w, T: Component> Fetch<'w> for WriteFetch<'w, T> { table_components: None, entities: None, entity_table_rows: None, - sparse_set: (T::Storage::STORAGE_TYPE == StorageType::SparseSet).then(|| { - world - .storages() - .sparse_sets - .get(state.component_id) - .unwrap() - }), + sparse_set: (T::Storage::STORAGE_TYPE == StorageType::SparseSet) + .then(|| world.storages().sparse_sets.get(*state).unwrap()), table_ticks: None, last_change_tick, change_tick, @@ -841,9 +805,7 @@ unsafe impl<'w, T: Component> Fetch<'w> for WriteFetch<'w, T> { match T::Storage::STORAGE_TYPE { StorageType::Table => { self.entity_table_rows = Some(archetype.entity_table_rows().into()); - let column = tables[archetype.table_id()] - .get_column(state.component_id) - .unwrap(); + let column = tables[archetype.table_id()].get_column(*state).unwrap(); self.table_components = Some(column.get_data_slice().into()); self.table_ticks = Some(column.get_ticks_slice().into()); } @@ -853,7 +815,7 @@ unsafe impl<'w, T: Component> Fetch<'w> for WriteFetch<'w, T> { #[inline] unsafe fn set_table(&mut self, state: &Self::State, table: &'w Table) { - let column = table.get_column(state.component_id).unwrap(); + let column = table.get_column(*state).unwrap(); self.table_components = Some(column.get_data_slice().into()); self.table_ticks = Some(column.get_ticks_slice().into()); } @@ -913,13 +875,17 @@ unsafe impl<'w, T: Component> Fetch<'w> for WriteFetch<'w, T> { } } + fn init_state(world: &mut World) -> Self::State { + world.init_component::() + } + fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { assert!( - !access.access().has_read(state.component_id), + !access.access().has_read(*state), "&mut {} conflicts with a previous access in this query. Mutable component access must be unique.", std::any::type_name::(), ); - access.add_write(state.component_id); + access.add_write(*state); } fn update_archetype_component_access( @@ -927,18 +893,23 @@ unsafe impl<'w, T: Component> Fetch<'w> for WriteFetch<'w, T> { archetype: &Archetype, access: &mut Access, ) { - if let Some(archetype_component_id) = - archetype.get_archetype_component_id(state.component_id) - { + if let Some(archetype_component_id) = archetype.get_archetype_component_id(*state) { access.add_write(archetype_component_id); } } + + fn matches_component_set( + state: &Self::State, + set_contains_id: &impl Fn(ComponentId) -> bool, + ) -> bool { + set_contains_id(*state) + } } // SAFETY: defers to soundness of `T: WorldQuery` impl unsafe impl WorldQuery for Option { type ReadOnly = Option; - type State = OptionState; + type State = T::State; fn shrink<'wlong: 'wshort, 'wshort>(item: QueryItem<'wlong, Self>) -> QueryItem<'wshort, Self> { item.map(T::shrink) @@ -956,34 +927,16 @@ pub struct OptionFetch { /// SAFETY: [`OptionFetch`] is read only because `T` is read only unsafe impl ReadOnlyWorldQuery for Option {} -/// The [`FetchState`] of `Option`. -#[doc(hidden)] -pub struct OptionState { - state: T, -} - -impl FetchState for OptionState { - fn init(world: &mut World) -> Self { - Self { - state: T::init(world), - } - } - - fn matches_component_set(&self, _set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { - true - } -} - impl<'w, T: WorldQueryGats<'w>> WorldQueryGats<'w> for Option { type Fetch = OptionFetch; - type _State = OptionState; + type _State = T::_State; } // SAFETY: component access and archetype component access are properly updated according to the // internal Fetch unsafe impl<'w, T: Fetch<'w>> Fetch<'w> for OptionFetch { type Item = Option; - type State = OptionState; + type State = T::State; const IS_DENSE: bool = T::IS_DENSE; @@ -991,12 +944,12 @@ unsafe impl<'w, T: Fetch<'w>> Fetch<'w> for OptionFetch { unsafe fn init( world: &'w World, - state: &OptionState, + state: &Self::State, last_change_tick: u32, change_tick: u32, ) -> Self { Self { - fetch: T::init(world, &state.state, last_change_tick, change_tick), + fetch: T::init(world, state, last_change_tick, change_tick), matches: false, } } @@ -1008,21 +961,17 @@ unsafe impl<'w, T: Fetch<'w>> Fetch<'w> for OptionFetch { archetype: &'w Archetype, tables: &'w Tables, ) { - self.matches = state - .state - .matches_component_set(&|id| archetype.contains(id)); + self.matches = T::matches_component_set(state, &|id| archetype.contains(id)); if self.matches { - self.fetch.set_archetype(&state.state, archetype, tables); + self.fetch.set_archetype(state, archetype, tables); } } #[inline] unsafe fn set_table(&mut self, state: &Self::State, table: &'w Table) { - self.matches = state - .state - .matches_component_set(&|id| table.has_column(id)); + self.matches = T::matches_component_set(state, &|id| table.has_column(id)); if self.matches { - self.fetch.set_table(&state.state, table); + self.fetch.set_table(state, table); } } @@ -1044,13 +993,17 @@ unsafe impl<'w, T: Fetch<'w>> Fetch<'w> for OptionFetch { } } + fn init_state(world: &mut World) -> Self::State { + T::init_state(world) + } + fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { // We don't want to add the `with`/`without` of `T` as `Option` will match things regardless of // `T`'s filters. for example `Query<(Option<&U>, &mut V)>` will match every entity with a `V` component // regardless of whether it has a `U` component. If we dont do this the query will not conflict with // `Query<&mut V, Without>` which would be unsound. let mut intermediate = access.clone(); - T::update_component_access(&state.state, &mut intermediate); + T::update_component_access(state, &mut intermediate); access.extend_access(&intermediate); } @@ -1059,10 +1012,17 @@ unsafe impl<'w, T: Fetch<'w>> Fetch<'w> for OptionFetch { archetype: &Archetype, access: &mut Access, ) { - if state.matches_component_set(&|id| archetype.contains(id)) { - T::update_archetype_component_access(&state.state, archetype, access); + if T::matches_component_set(state, &|id| archetype.contains(id)) { + T::update_archetype_component_access(state, archetype, access); } } + + fn matches_component_set( + _state: &Self::State, + _set_contains_id: &impl Fn(ComponentId) -> bool, + ) -> bool { + true + } } /// [`WorldQuery`] that tracks changes and additions for component `T`. @@ -1132,34 +1092,13 @@ impl ChangeTrackers { // SAFETY: `ROQueryFetch` is the same as `QueryFetch` unsafe impl WorldQuery for ChangeTrackers { type ReadOnly = Self; - type State = ChangeTrackersState; + type State = ComponentId; fn shrink<'wlong: 'wshort, 'wshort>(item: QueryItem<'wlong, Self>) -> QueryItem<'wshort, Self> { item } } -/// The [`FetchState`] of [`ChangeTrackers`]. -#[doc(hidden)] -pub struct ChangeTrackersState { - component_id: ComponentId, - marker: PhantomData, -} - -impl FetchState for ChangeTrackersState { - fn init(world: &mut World) -> Self { - let component_id = world.init_component::(); - Self { - component_id, - marker: PhantomData, - } - } - - fn matches_component_set(&self, set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { - set_contains_id(self.component_id) - } -} - /// The [`Fetch`] of [`ChangeTrackers`]. #[doc(hidden)] pub struct ChangeTrackersFetch<'w, T> { @@ -1194,14 +1133,14 @@ unsafe impl ReadOnlyWorldQuery for ChangeTrackers {} impl<'w, T: Component> WorldQueryGats<'w> for ChangeTrackers { type Fetch = ChangeTrackersFetch<'w, T>; - type _State = ChangeTrackersState; + type _State = ComponentId; } // SAFETY: component access and archetype component access are properly updated to reflect that T is // read unsafe impl<'w, T: Component> Fetch<'w> for ChangeTrackersFetch<'w, T> { type Item = ChangeTrackers; - type State = ChangeTrackersState; + type State = ComponentId; const IS_DENSE: bool = { match T::Storage::STORAGE_TYPE { @@ -1214,7 +1153,7 @@ unsafe impl<'w, T: Component> Fetch<'w> for ChangeTrackersFetch<'w, T> { unsafe fn init( world: &'w World, - state: &ChangeTrackersState, + state: &Self::State, last_change_tick: u32, change_tick: u32, ) -> ChangeTrackersFetch<'w, T> { @@ -1222,13 +1161,8 @@ unsafe impl<'w, T: Component> Fetch<'w> for ChangeTrackersFetch<'w, T> { table_ticks: None, entities: None, entity_table_rows: None, - sparse_set: (T::Storage::STORAGE_TYPE == StorageType::SparseSet).then(|| { - world - .storages() - .sparse_sets - .get(state.component_id) - .unwrap() - }), + sparse_set: (T::Storage::STORAGE_TYPE == StorageType::SparseSet) + .then(|| world.storages().sparse_sets.get(*state).unwrap()), marker: PhantomData, last_change_tick, change_tick, @@ -1245,9 +1179,7 @@ unsafe impl<'w, T: Component> Fetch<'w> for ChangeTrackersFetch<'w, T> { match T::Storage::STORAGE_TYPE { StorageType::Table => { self.entity_table_rows = Some(archetype.entity_table_rows().into()); - let column = tables[archetype.table_id()] - .get_column(state.component_id) - .unwrap(); + let column = tables[archetype.table_id()].get_column(*state).unwrap(); self.table_ticks = Some(column.get_ticks_slice().into()); } StorageType::SparseSet => self.entities = Some(archetype.entities().into()), @@ -1256,13 +1188,7 @@ unsafe impl<'w, T: Component> Fetch<'w> for ChangeTrackersFetch<'w, T> { #[inline] unsafe fn set_table(&mut self, state: &Self::State, table: &'w Table) { - self.table_ticks = Some( - table - .get_column(state.component_id) - .unwrap() - .get_ticks_slice() - .into(), - ); + self.table_ticks = Some(table.get_column(*state).unwrap().get_ticks_slice().into()); } #[inline] @@ -1319,13 +1245,17 @@ unsafe impl<'w, T: Component> Fetch<'w> for ChangeTrackersFetch<'w, T> { } } + fn init_state(world: &mut World) -> Self::State { + world.init_component::() + } + fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { assert!( - !access.access().has_write(state.component_id), + !access.access().has_write(*state), "ChangeTrackers<{}> conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.", std::any::type_name::() ); - access.add_read(state.component_id); + access.add_read(*state); } fn update_archetype_component_access( @@ -1333,12 +1263,17 @@ unsafe impl<'w, T: Component> Fetch<'w> for ChangeTrackersFetch<'w, T> { archetype: &Archetype, access: &mut Access, ) { - if let Some(archetype_component_id) = - archetype.get_archetype_component_id(state.component_id) - { + if let Some(archetype_component_id) = archetype.get_archetype_component_id(*state) { access.add_read(archetype_component_id); } } + + fn matches_component_set( + state: &Self::State, + set_contains_id: &impl Fn(ComponentId) -> bool, + ) -> bool { + set_contains_id(*state) + } } macro_rules! impl_tuple_fetch { @@ -1350,8 +1285,8 @@ macro_rules! impl_tuple_fetch { type _State = ($($name::_State,)*); } - #[allow(non_snake_case)] // SAFETY: update_component_access and update_archetype_component_access are called for each item in the tuple + #[allow(non_snake_case)] unsafe impl<'w, $($name: Fetch<'w>),*> Fetch<'w> for ($($name,)*) { type Item = ($($name::Item,)*); type State = ($($name::State,)*); @@ -1408,27 +1343,24 @@ macro_rules! impl_tuple_fetch { true $(&& $name.archetype_filter_fetch(archetype_index))* } - fn update_component_access(state: &Self::State, _access: &mut FilteredAccess) { - let ($($name,)*) = state; - $($name::update_component_access($name, _access);)* + #[allow(clippy::unused_unit)] + fn init_state(_world: &mut World) -> Self::State { + ($($name::init_state(_world),)*) } - fn update_archetype_component_access(state: &Self::State, _archetype: &Archetype, _access: &mut Access) { - let ($($name,)*) = state; - $($name::update_archetype_component_access($name, _archetype, _access);)* + fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) { + let ($($name,)*) = _state; + $($name::update_component_access($name, _access);)* } - } - #[allow(non_snake_case)] - #[allow(clippy::unused_unit)] - impl<$($name: FetchState),*> FetchState for ($($name,)*) { - fn init(_world: &mut World) -> Self { - ($($name::init(_world),)*) + fn update_archetype_component_access(_state: &Self::State, _archetype: &Archetype, _access: &mut Access) { + let ($($name,)*) = _state; + $($name::update_archetype_component_access($name, _archetype, _access);)* } - fn matches_component_set(&self, _set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { - let ($($name,)*) = self; - true $(&& $name.matches_component_set(_set_contains_id))* + fn matches_component_set(_state: &Self::State, _set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { + let ($($name,)*) = _state; + true $(&& $name::matches_component_set($name, _set_contains_id))* } } @@ -1469,8 +1401,8 @@ macro_rules! impl_anytuple_fetch { type _State = AnyOf<($($name::_State,)*)>; } - #[allow(non_snake_case)] // SAFETY: update_component_access and update_archetype_component_access are called for each item in the tuple + #[allow(non_snake_case)] unsafe impl<'w, $($name: Fetch<'w>),*> Fetch<'w> for AnyOf<($(($name, bool),)*)> { type Item = ($(Option<$name::Item>,)*); type State = AnyOf<($($name::State,)*)>; @@ -1490,7 +1422,7 @@ macro_rules! impl_anytuple_fetch { let ($($name,)*) = &mut self.0; let ($($state,)*) = &_state.0; $( - $name.1 = $state.matches_component_set(&|id| _archetype.contains(id)); + $name.1 = $name::matches_component_set($state, &|id| _archetype.contains(id)); if $name.1 { $name.0.set_archetype($state, _archetype, _tables); } @@ -1502,7 +1434,7 @@ macro_rules! impl_anytuple_fetch { let ($($name,)*) = &mut self.0; let ($($state,)*) = &_state.0; $( - $name.1 = $state.matches_component_set(&|id| _table.has_column(id)); + $name.1 = $name::matches_component_set($state, &|id| _table.has_column(id)); if $name.1 { $name.0.set_table($state, _table); } @@ -1514,7 +1446,7 @@ macro_rules! impl_anytuple_fetch { unsafe fn table_fetch(&mut self, _table_row: usize) -> Self::Item { let ($($name,)*) = &mut self.0; ($( - $name.1.then(|| $name.0.table_fetch(_table_row)), + $name.1.then(|| $name.0.table_fetch( _table_row)), )*) } @@ -1527,6 +1459,10 @@ macro_rules! impl_anytuple_fetch { )*) } + fn init_state(_world: &mut World) -> Self::State { + AnyOf(($($name::init_state(_world),)*)) + } + fn update_component_access(state: &Self::State, _access: &mut FilteredAccess) { let ($($name,)*) = &state.0; @@ -1551,7 +1487,6 @@ macro_rules! impl_anytuple_fetch { _intersected_access.extend_intersect_filter(&intermediate); _intersected_access.extend_access(&intermediate); } else { - $name::update_component_access($name, &mut _intersected_access); _not_first = true; } @@ -1560,29 +1495,21 @@ macro_rules! impl_anytuple_fetch { *_access = _intersected_access; } - fn update_archetype_component_access(state: &Self::State, _archetype: &Archetype, _access: &mut Access) { - let ($($name,)*) = &state.0; + fn matches_component_set(_state: &Self::State, _set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { + let ($($name,)*) = &_state.0; + false $(|| $name::matches_component_set($name, _set_contains_id))* + } + + fn update_archetype_component_access(_state: &Self::State, _archetype: &Archetype, _access: &mut Access) { + let ($($name,)*) = &_state.0; $( - if $name.matches_component_set(&|id| _archetype.contains(id)) { + if $name::matches_component_set($name, &|id| _archetype.contains(id)) { $name::update_archetype_component_access($name, _archetype, _access); } )* } } - #[allow(non_snake_case)] - #[allow(clippy::unused_unit)] - impl<$($name: FetchState),*> FetchState for AnyOf<($($name,)*)> { - fn init(_world: &mut World) -> Self { - AnyOf(($($name::init(_world),)*)) - } - - fn matches_component_set(&self, _set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { - let ($($name,)*) = &self.0; - false $(|| $name.matches_component_set(_set_contains_id))* - } - } - #[allow(non_snake_case)] #[allow(clippy::unused_unit)] // SAFETY: defers to soundness of `$name: WorldQuery` impl @@ -1615,7 +1542,7 @@ pub struct NopFetch { } // SAFETY: NopFetch doesnt access anything -unsafe impl<'w, State: FetchState> Fetch<'w> for NopFetch { +unsafe impl<'w, State: Send + Sync> Fetch<'w> for NopFetch { type Item = (); type State = State; @@ -1651,6 +1578,11 @@ unsafe impl<'w, State: FetchState> Fetch<'w> for NopFetch { #[inline(always)] unsafe fn table_fetch(&mut self, _table_row: usize) -> Self::Item {} + #[inline(always)] + fn init_state(_world: &mut World) -> Self::State { + unimplemented!(); + } + fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) {} fn update_archetype_component_access( @@ -1659,4 +1591,11 @@ unsafe impl<'w, State: FetchState> Fetch<'w> for NopFetch { _access: &mut Access, ) { } + + fn matches_component_set( + _state: &Self::State, + _set_contains_id: &impl Fn(ComponentId) -> bool, + ) -> bool { + true + } } diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index 1626dd43fc013..5924b4dd43e19 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -3,8 +3,8 @@ use crate::{ component::{Component, ComponentId, ComponentStorage, ComponentTicks, StorageType}, entity::Entity, query::{ - debug_checked_unreachable, Access, Fetch, FetchState, FilteredAccess, QueryFetch, - WorldQuery, WorldQueryGats, + debug_checked_unreachable, Access, Fetch, FilteredAccess, QueryFetch, WorldQuery, + WorldQueryGats, }, storage::{ComponentSparseSet, Table, Tables}, world::World, @@ -47,7 +47,7 @@ pub struct With(PhantomData); // SAFETY: `ROQueryFetch` is the same as `QueryFetch` unsafe impl WorldQuery for With { type ReadOnly = Self; - type State = WithState; + type State = ComponentId; #[allow(clippy::semicolon_if_nothing_returned)] fn shrink<'wlong: 'wshort, 'wshort>( @@ -63,40 +63,19 @@ pub struct WithFetch { marker: PhantomData, } -/// The [`FetchState`] of [`With`]. -#[doc(hidden)] -pub struct WithState { - component_id: ComponentId, - marker: PhantomData, -} - -impl FetchState for WithState { - fn init(world: &mut World) -> Self { - let component_id = world.init_component::(); - Self { - component_id, - marker: PhantomData, - } - } - - fn matches_component_set(&self, set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { - set_contains_id(self.component_id) - } -} - impl WorldQueryGats<'_> for With { type Fetch = WithFetch; - type _State = WithState; + type _State = ComponentId; } // SAFETY: no component access or archetype component access unsafe impl<'w, T: Component> Fetch<'w> for WithFetch { type Item = (); - type State = WithState; + type State = ComponentId; unsafe fn init( _world: &World, - _state: &WithState, + _state: &Self::State, _last_change_tick: u32, _change_tick: u32, ) -> Self { @@ -132,9 +111,13 @@ unsafe impl<'w, T: Component> Fetch<'w> for WithFetch { #[inline] unsafe fn table_fetch(&mut self, _table_row: usize) {} + fn init_state(world: &mut World) -> Self::State { + world.init_component::() + } + #[inline] fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { - access.add_with(state.component_id); + access.add_with(*state); } #[inline] @@ -144,6 +127,13 @@ unsafe impl<'w, T: Component> Fetch<'w> for WithFetch { _access: &mut Access, ) { } + + fn matches_component_set( + state: &Self::State, + set_contains_id: &impl Fn(ComponentId) -> bool, + ) -> bool { + set_contains_id(*state) + } } // SAFETY: no component access or archetype component access @@ -188,7 +178,7 @@ pub struct Without(PhantomData); // SAFETY: `ROQueryFetch` is the same as `QueryFetch` unsafe impl WorldQuery for Without { type ReadOnly = Self; - type State = WithoutState; + type State = ComponentId; #[allow(clippy::semicolon_if_nothing_returned)] fn shrink<'wlong: 'wshort, 'wshort>( @@ -204,40 +194,19 @@ pub struct WithoutFetch { marker: PhantomData, } -/// The [`FetchState`] of [`Without`]. -#[doc(hidden)] -pub struct WithoutState { - component_id: ComponentId, - marker: PhantomData, -} - -impl FetchState for WithoutState { - fn init(world: &mut World) -> Self { - let component_id = world.init_component::(); - Self { - component_id, - marker: PhantomData, - } - } - - fn matches_component_set(&self, set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { - !set_contains_id(self.component_id) - } -} - impl WorldQueryGats<'_> for Without { type Fetch = WithoutFetch; - type _State = WithoutState; + type _State = ComponentId; } // SAFETY: no component access or archetype component access unsafe impl<'w, T: Component> Fetch<'w> for WithoutFetch { type Item = (); - type State = WithoutState; + type State = ComponentId; unsafe fn init( _world: &World, - _state: &WithoutState, + _state: &Self::State, _last_change_tick: u32, _change_tick: u32, ) -> Self { @@ -273,18 +242,28 @@ unsafe impl<'w, T: Component> Fetch<'w> for WithoutFetch { #[inline] unsafe fn table_fetch(&mut self, _table_row: usize) {} + fn init_state(world: &mut World) -> Self::State { + world.init_component::() + } + #[inline] fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { - access.add_without(state.component_id); + access.add_without(*state); } - #[inline] fn update_archetype_component_access( _state: &Self::State, _archetype: &Archetype, _access: &mut Access, ) { } + + fn matches_component_set( + state: &Self::State, + set_contains_id: &impl Fn(ComponentId) -> bool, + ) -> bool { + !set_contains_id(*state) + } } // SAFETY: no component access or archetype component access @@ -363,21 +342,21 @@ macro_rules! impl_query_filter_tuple { type _State = Or<($($filter::_State,)*)>; } + // SAFETY: update_component_access and update_archetype_component_access are called for each item in the tuple #[allow(unused_variables)] #[allow(non_snake_case)] - // SAFETY: update_component_access and update_archetype_component_access are called for each item in the tuple unsafe impl<'w, $($filter: Fetch<'w>),*> Fetch<'w> for Or<($(OrFetch<'w, $filter>,)*)> { - type State = Or<($(<$filter as Fetch<'w>>::State,)*)>; + type State = Or<($($filter::State,)*)>; type Item = bool; const IS_DENSE: bool = true $(&& $filter::IS_DENSE)*; const IS_ARCHETYPAL: bool = true $(&& $filter::IS_ARCHETYPAL)*; - unsafe fn init(world: &'w World, state: & Or<($(<$filter as Fetch<'w>>::State,)*)>, last_change_tick: u32, change_tick: u32) -> Self { + unsafe fn init(world: &'w World, state: & Or<($($filter::State,)*)>, last_change_tick: u32, change_tick: u32) -> Self { let ($($filter,)*) = &state.0; Or(($(OrFetch { - fetch: <$filter as Fetch<'w>>::init(world, $filter, last_change_tick, change_tick), + fetch: $filter::init(world, $filter, last_change_tick, change_tick), matches: false, _marker: PhantomData, },)*)) @@ -388,7 +367,7 @@ macro_rules! impl_query_filter_tuple { let ($($filter,)*) = &mut self.0; let ($($state,)*) = &state.0; $( - $filter.matches = $state.matches_component_set(&|id| table.has_column(id)); + $filter.matches = $filter::matches_component_set($state, &|id| table.has_column(id)); if $filter.matches { $filter.fetch.set_table($state, table); } @@ -400,7 +379,7 @@ macro_rules! impl_query_filter_tuple { let ($($filter,)*) = &mut self.0; let ($($state,)*) = &state.0; $( - $filter.matches = $state.matches_component_set(&|id| archetype.contains(id)); + $filter.matches = $filter::matches_component_set($state, &|id| archetype.contains(id)); if $filter.matches { $filter.fetch.set_archetype($state, archetype, tables); } @@ -429,6 +408,10 @@ macro_rules! impl_query_filter_tuple { self.archetype_fetch(archetype_index) } + fn init_state(world: &mut World) -> Self::State { + Or(($($filter::init_state(world),)*)) + } + fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { let ($($filter,)*) = &state.0; @@ -465,18 +448,10 @@ macro_rules! impl_query_filter_tuple { let ($($filter,)*) = &state.0; $($filter::update_archetype_component_access($filter, archetype, access);)* } - } - - #[allow(unused_variables)] - #[allow(non_snake_case)] - impl<$($filter: FetchState),*> FetchState for Or<($($filter,)*)> { - fn init(world: &mut World) -> Self { - Or(($($filter::init(world),)*)) - } - fn matches_component_set(&self, _set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { - let ($($filter,)*) = &self.0; - false $(|| $filter.matches_component_set(_set_contains_id))* + fn matches_component_set(state: &Self::State, _set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { + let ($($filter,)*) = &state.0; + false $(|| $filter::matches_component_set($filter, _set_contains_id))* } } @@ -491,8 +466,6 @@ macro_rules! impl_tick_filter { ( $(#[$meta:meta])* $name: ident, - $(#[$state_meta:meta])* - $state_name: ident, $(#[$fetch_meta:meta])* $fetch_name: ident, $is_detected: expr @@ -512,53 +485,33 @@ macro_rules! impl_tick_filter { change_tick: u32, } - #[doc(hidden)] - $(#[$state_meta])* - pub struct $state_name { - component_id: ComponentId, - marker: PhantomData, - } - // SAFETY: `ROQueryFetch` is the same as `QueryFetch` unsafe impl WorldQuery for $name { type ReadOnly = Self; - type State = $state_name; + type State = ComponentId; fn shrink<'wlong: 'wshort, 'wshort>(item: super::QueryItem<'wlong, Self>) -> super::QueryItem<'wshort, Self> { item } } - impl FetchState for $state_name { - fn init(world: &mut World) -> Self { - Self { - component_id: world.init_component::(), - marker: PhantomData, - } - } - - fn matches_component_set(&self, set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { - set_contains_id(self.component_id) - } - } - impl<'w, T: Component> WorldQueryGats<'w> for $name { type Fetch = $fetch_name<'w, T>; - type _State = $state_name; + type _State = ComponentId; } // SAFETY: this reads the T component. archetype component access and component access are updated to reflect that unsafe impl<'w, T: Component> Fetch<'w> for $fetch_name<'w, T> { - type State = $state_name; + type State = ComponentId; type Item = bool; - unsafe fn init(world: &'w World, state: & $state_name, last_change_tick: u32, change_tick: u32) -> Self { + unsafe fn init(world: &'w World, state: &Self::State, last_change_tick: u32, change_tick: u32) -> Self { Self { table_ticks: None, entities: None, entity_table_rows: None, sparse_set: (T::Storage::STORAGE_TYPE == StorageType::SparseSet) - .then(|| world.storages().sparse_sets.get(state.component_id).unwrap()), + .then(|| world.storages().sparse_sets.get(*state).unwrap()), marker: PhantomData, last_change_tick, change_tick, @@ -575,7 +528,7 @@ macro_rules! impl_tick_filter { const IS_ARCHETYPAL: bool = false; unsafe fn set_table(&mut self, state: &Self::State, table: &'w Table) { - self.table_ticks = Some(table.get_column(state.component_id).unwrap().get_ticks_slice().into()); + self.table_ticks = Some(table.get_column(*state).unwrap().get_ticks_slice().into()); } unsafe fn set_archetype(&mut self, state: &Self::State, archetype: &'w Archetype, tables: &'w Tables) { @@ -583,7 +536,7 @@ macro_rules! impl_tick_filter { StorageType::Table => { self.entity_table_rows = Some(archetype.entity_table_rows().into()); let table = &tables[archetype.table_id()]; - self.table_ticks = Some(table.get_column(state.component_id).unwrap().get_ticks_slice().into()); + self.table_ticks = Some(table.get_column(*state).unwrap().get_ticks_slice().into()); } StorageType::SparseSet => self.entities = Some(archetype.entities().into()), } @@ -623,13 +576,17 @@ macro_rules! impl_tick_filter { self.archetype_fetch(archetype_index) } + fn init_state(world: &mut World) -> Self::State { + world.init_component::() + } + #[inline] fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { - if access.access().has_write(state.component_id) { - panic!("$state_name<{}> conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.", + if access.access().has_write(*state) { + panic!("$name<{}> conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.", std::any::type_name::()); } - access.add_read(state.component_id); + access.add_read(*state); } #[inline] @@ -638,10 +595,17 @@ macro_rules! impl_tick_filter { archetype: &Archetype, access: &mut Access, ) { - if let Some(archetype_component_id) = archetype.get_archetype_component_id(state.component_id) { + if let Some(archetype_component_id) = archetype.get_archetype_component_id(*state) { access.add_read(archetype_component_id); } } + + fn matches_component_set( + state: &Self::State, + set_contains_id: &impl Fn(ComponentId) -> bool, + ) -> bool { + set_contains_id(*state) + } } /// SAFETY: read-only access @@ -693,8 +657,6 @@ impl_tick_filter!( /// # bevy_ecs::system::assert_is_system(print_add_name_component); /// ``` Added, - /// The [`FetchState`] of [`Added`]. - AddedState, /// The [`Fetch`] of [`Added`]. AddedFetch, ComponentTicks::is_added @@ -733,8 +695,6 @@ impl_tick_filter!( /// # bevy_ecs::system::assert_is_system(print_moving_objects_system); /// ``` Changed, - /// The [`FetchState`] of [`Changed`]. - ChangedState, /// The [`Fetch`] of [`Changed`]. ChangedFetch, ComponentTicks::is_changed diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index cc769886b3c70..0661ea2658331 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -3,10 +3,7 @@ use crate::{ component::ComponentId, entity::Entity, prelude::FromWorld, - query::{ - Access, Fetch, FetchState, FilteredAccess, NopFetch, QueryCombinationIter, QueryIter, - WorldQuery, - }, + query::{Access, Fetch, FilteredAccess, NopFetch, QueryCombinationIter, QueryIter, WorldQuery}, storage::TableId, world::{World, WorldId}, }; @@ -43,20 +40,17 @@ impl FromWorld for QueryState { impl QueryState { /// Creates a new [`QueryState`] from a given [`World`] and inherits the result of `world.id()`. pub fn new(world: &mut World) -> Self { - let fetch_state = ::init(world); - let filter_state = ::init(world); + let fetch_state = Q::Fetch::init_state(world); + let filter_state = F::Fetch::init_state(world); let mut component_access = FilteredAccess::default(); - QueryFetch::<'static, Q>::update_component_access(&fetch_state, &mut component_access); + Q::Fetch::update_component_access(&fetch_state, &mut component_access); // Use a temporary empty FilteredAccess for filters. This prevents them from conflicting with the // main Query's `fetch_state` access. Filters are allowed to conflict with the main query fetch // because they are evaluated *before* a specific reference is constructed. let mut filter_component_access = FilteredAccess::default(); - QueryFetch::<'static, F>::update_component_access( - &filter_state, - &mut filter_component_access, - ); + F::Fetch::update_component_access(&filter_state, &mut filter_component_access); // Merge the temporary filter access with the main access. This ensures that filter access is // properly considered in a global "cross-query" context (both within systems and across systems). @@ -118,19 +112,15 @@ impl QueryState { /// Creates a new [`Archetype`]. pub fn new_archetype(&mut self, archetype: &Archetype) { - if self - .fetch_state - .matches_component_set(&|id| archetype.contains(id)) - && self - .filter_state - .matches_component_set(&|id| archetype.contains(id)) + if Q::Fetch::matches_component_set(&self.fetch_state, &|id| archetype.contains(id)) + && F::Fetch::matches_component_set(&self.filter_state, &|id| archetype.contains(id)) { - QueryFetch::<'static, Q>::update_archetype_component_access( + Q::Fetch::update_archetype_component_access( &self.fetch_state, archetype, &mut self.archetype_component_access, ); - QueryFetch::<'static, F>::update_archetype_component_access( + F::Fetch::update_archetype_component_access( &self.filter_state, archetype, &mut self.archetype_component_access, diff --git a/crates/bevy_ecs_compile_fail_tests/tests/ui/system_param_derive_readonly.stderr b/crates/bevy_ecs_compile_fail_tests/tests/ui/system_param_derive_readonly.stderr index a6da80b259cda..8065f20076291 100644 --- a/crates/bevy_ecs_compile_fail_tests/tests/ui/system_param_derive_readonly.stderr +++ b/crates/bevy_ecs_compile_fail_tests/tests/ui/system_param_derive_readonly.stderr @@ -15,7 +15,7 @@ error[E0277]: the trait bound `&'static mut Foo: ReadOnlyWorldQuery` is not sati = note: `ReadOnlyWorldQuery` is implemented for `&'static Foo`, but not for `&'static mut Foo` = note: required because of the requirements on the impl of `ReadOnlySystemParamFetch` for `QueryState<&'static mut Foo>` = note: 2 redundant requirements hidden - = note: required because of the requirements on the impl of `ReadOnlySystemParamFetch` for `_::FetchState<(QueryState<&'static mut Foo>,)>` + = note: required because of the requirements on the impl of `ReadOnlySystemParamFetch` for `FetchState<(QueryState<&'static mut Foo>,)>` note: required by a bound in `assert_readonly` --> tests/ui/system_param_derive_readonly.rs:23:32 |