From 16abf5cc67ff548863b07dc86484b87e8431bf4d Mon Sep 17 00:00:00 2001 From: Ellen Date: Fri, 29 Jul 2022 04:26:16 +0100 Subject: [PATCH] completely flatten `WorldQuery` trait hierarchy --- crates/bevy_ecs/macros/src/fetch.rs | 258 ++-- crates/bevy_ecs/src/query/fetch.rs | 1098 ++++++++--------- crates/bevy_ecs/src/query/filter.rs | 431 +++---- crates/bevy_ecs/src/query/iter.rs | 63 +- crates/bevy_ecs/src/query/state.rs | 127 +- .../ui/system_param_derive_readonly.stderr | 2 +- 6 files changed, 911 insertions(+), 1068 deletions(-) diff --git a/crates/bevy_ecs/macros/src/fetch.rs b/crates/bevy_ecs/macros/src/fetch.rs index 624cae137ab2a8..f8dfa2218030f9 100644 --- a/crates/bevy_ecs/macros/src/fetch.rs +++ b/crates/bevy_ecs/macros/src/fetch.rs @@ -79,6 +79,8 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream { .unwrap_or_else(|_| panic!("Invalid `{}` attribute format", WORLD_QUERY_ATTRIBUTE_NAME)); } + let path = bevy_ecs_path(); + let user_generics = ast.generics.clone(); let (user_impl_generics, user_ty_generics, user_where_clauses) = user_generics.split_for_impl(); let user_generics_with_world = { @@ -112,11 +114,6 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream { let state_struct_name = Ident::new(&format!("{}State", struct_name), Span::call_site()); - let fetch_type_alias = Ident::new("QueryFetch", Span::call_site()); - let read_only_fetch_type_alias = Ident::new("ROQueryFetch", Span::call_site()); - let item_type_alias = Ident::new("QueryItem", Span::call_site()); - let read_only_item_type_alias = Ident::new("ROQueryItem", Span::call_site()); - let fields = match &ast.data { Data::Struct(DataStruct { fields: Fields::Named(fields), @@ -133,6 +130,7 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream { let mut field_visibilities = Vec::new(); let mut field_idents = Vec::new(); let mut field_types = Vec::new(); + let mut read_only_field_types = Vec::new(); for field in fields { let WorldQueryFieldInfo { is_ignored, attrs } = read_world_query_field_info(field); @@ -147,7 +145,9 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream { field_attrs.push(attrs); field_visibilities.push(field.vis.clone()); field_idents.push(field_ident.clone()); - field_types.push(field.ty.clone()); + let field_ty = field.ty.clone(); + field_types.push(quote!(#field_ty)); + read_only_field_types.push(quote!(<#field_ty as #path::query::WorldQuery>::ReadOnly)); } } @@ -155,46 +155,76 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream { // `#[derive()]` is valid syntax let derive_macro_call = quote! { #[derive(#derive_args)] }; - let path = bevy_ecs_path(); - - let impl_fetch = |is_readonly: bool, fetch_struct_name: Ident, item_struct_name: Ident| { - let fetch_type_alias = if is_readonly { - &read_only_fetch_type_alias - } else { - &fetch_type_alias + let impl_fetch = |is_readonly: bool| { + let struct_name = match is_readonly { + false => struct_name.clone(), + true => read_only_struct_name.clone(), }; - let item_type_alias = if is_readonly { - &read_only_item_type_alias - } else { - &item_type_alias + let item_struct_name = match is_readonly { + false => item_struct_name.clone(), + true => read_only_item_struct_name.clone(), + }; + let fetch_struct_name = match is_readonly { + false => fetch_struct_name.clone(), + true => read_only_fetch_struct_name.clone(), + }; + + let field_types = match is_readonly { + false => field_types.clone(), + true => read_only_field_types.clone(), }; quote! { #derive_macro_call #[automatically_derived] #visibility struct #item_struct_name #user_impl_generics_with_world #user_where_clauses_with_world { - #(#(#field_attrs)* #field_visibilities #field_idents: #path::query::#item_type_alias<'__w, #field_types>,)* + #(#(#field_attrs)* #field_visibilities #field_idents: <#field_types as #path::query::WorldQueryGats<'__w>>::Item,)* #(#(#ignored_field_attrs)* #ignored_field_visibilities #ignored_field_idents: #ignored_field_types,)* } #[derive(Clone)] #[doc(hidden)] #visibility struct #fetch_struct_name #user_impl_generics_with_world #user_where_clauses_with_world { - #(#field_idents: #path::query::#fetch_type_alias::<'__w, #field_types>,)* + #(#field_idents: <#field_types as #path::query::WorldQueryGats<'__w>>::Fetch,)* #(#ignored_field_idents: #ignored_field_types,)* } // SAFETY: `update_component_access` and `update_archetype_component_access` are called on every field - unsafe impl #user_impl_generics_with_world #path::query::Fetch<'__w> - for #fetch_struct_name #user_ty_generics_with_world #user_where_clauses_with_world { - type Item = #item_struct_name #user_ty_generics_with_world; + impl #user_impl_generics_with_world #path::query::WorldQueryGats<'__w> + for #struct_name #user_ty_generics #user_where_clauses { + type Item = #item_struct_name #user_ty_generics_with_world; + type Fetch = #fetch_struct_name #user_ty_generics_with_world; + } + + unsafe impl #user_impl_generics #path::query::WorldQuery + for #struct_name #user_ty_generics #user_where_clauses { + + type ReadOnly = #read_only_struct_name #user_ty_generics; type State = #state_struct_name #user_ty_generics; - unsafe fn init(_world: &'__w #path::world::World, state: &Self::State, _last_change_tick: u32, _change_tick: u32) -> Self { - Self { + fn shrink<'__wlong: '__wshort, '__wshort>( + item: <#struct_name #user_ty_generics as #path::query::WorldQueryGats<'__wlong>>::Item + ) -> <#struct_name #user_ty_generics as #path::query::WorldQueryGats<'__wshort>>::Item { + #item_struct_name { + #( + #field_idents: <#field_types>::shrink(item.#field_idents), + )* + #( + #ignored_field_idents: item.#ignored_field_idents, + )* + } + } + + unsafe fn init_fetch<'__w>( + _world: &'__w #path::world::World, + state: &Self::State, + _last_change_tick: u32, + _change_tick: u32 + ) -> >::Fetch { + #fetch_struct_name { #(#field_idents: - #path::query::#fetch_type_alias::<'__w, #field_types>::init( + <#field_types>::init_fetch( _world, &state.#field_idents, _last_change_tick, @@ -205,141 +235,110 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream { } } - const IS_DENSE: bool = true #(&& #path::query::#fetch_type_alias::<'__w, #field_types>::IS_DENSE)*; + const IS_DENSE: bool = true #(&& <#field_types>::IS_DENSE)*; - const IS_ARCHETYPAL: bool = true #(&& #path::query::#fetch_type_alias::<'__w, #field_types>::IS_ARCHETYPAL)*; + const IS_ARCHETYPAL: bool = true #(&& <#field_types>::IS_ARCHETYPAL)*; /// SAFETY: we call `set_archetype` for each member that implements `Fetch` #[inline] - unsafe fn set_archetype( - &mut self, + unsafe fn set_archetype<'__w>( + _fetch: &mut >::Fetch, _state: &Self::State, _archetype: &'__w #path::archetype::Archetype, _tables: &'__w #path::storage::Tables ) { - #(self.#field_idents.set_archetype(&_state.#field_idents, _archetype, _tables);)* + #(<#field_types>::set_archetype(&mut _fetch.#field_idents, &_state.#field_idents, _archetype, _tables);)* } /// SAFETY: we call `set_table` for each member that implements `Fetch` #[inline] - unsafe fn set_table(&mut self, _state: &Self::State, _table: &'__w #path::storage::Table) { - #(self.#field_idents.set_table(&_state.#field_idents, _table);)* + unsafe fn set_table<'__w>( + _fetch: &mut >::Fetch, + _state: &Self::State, + _table: &'__w #path::storage::Table + ) { + #(<#field_types>::set_table(&mut _fetch.#field_idents, &_state.#field_idents, _table);)* } /// SAFETY: we call `table_fetch` for each member that implements `Fetch`. #[inline] - unsafe fn table_fetch(&mut self, _table_row: usize) -> Self::Item { + unsafe fn table_fetch<'__w>( + _fetch: &mut >::Fetch, + _table_row: usize + ) -> >::Item { Self::Item { - #(#field_idents: self.#field_idents.table_fetch(_table_row),)* + #(#field_idents: <#field_types>::table_fetch(&mut _fetch.#field_idents, _table_row),)* #(#ignored_field_idents: Default::default(),)* } } /// SAFETY: we call `archetype_fetch` for each member that implements `Fetch`. #[inline] - unsafe fn archetype_fetch(&mut self, _archetype_index: usize) -> Self::Item { + unsafe fn archetype_fetch<'__w>( + _fetch: &mut >::Fetch, + _archetype_index: usize + ) -> >::Item { Self::Item { - #(#field_idents: self.#field_idents.archetype_fetch(_archetype_index),)* + #(#field_idents: <#field_types>::archetype_fetch(&mut _fetch.#field_idents, _archetype_index),)* #(#ignored_field_idents: Default::default(),)* } } #[allow(unused_variables)] #[inline] - unsafe fn table_filter_fetch(&mut self, _table_row: usize) -> bool { - true #(&& self.#field_idents.table_filter_fetch(_table_row))* + unsafe fn table_filter_fetch<'__w>(_fetch: &mut >::Fetch, _table_row: usize) -> bool { + true #(&& <#field_types>::table_filter_fetch(&mut _fetch.#field_idents, _table_row))* } #[allow(unused_variables)] #[inline] - unsafe fn archetype_filter_fetch(&mut self, _archetype_index: usize) -> bool { - true #(&& self.#field_idents.archetype_filter_fetch(_archetype_index))* + unsafe fn archetype_filter_fetch<'__w>(_fetch: &mut >::Fetch, _archetype_index: usize) -> bool { + true #(&& <#field_types>::archetype_filter_fetch(&mut _fetch.#field_idents, _archetype_index))* } 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); )* + #( <#field_types>::update_component_access(&state.#field_idents, _access); )* } - fn update_archetype_component_access(state: &Self::State, _archetype: &#path::archetype::Archetype, _access: &mut #path::query::Access<#path::archetype::ArchetypeComponentId>) { + fn update_archetype_component_access( + state: &Self::State, + _archetype: &#path::archetype::Archetype, + _access: &mut #path::query::Access<#path::archetype::ArchetypeComponentId> + ) { #( - #path::query::#fetch_type_alias::<'static, #field_types> - :: update_archetype_component_access(&state.#field_idents, _archetype, _access); + <#field_types>::update_archetype_component_access(&state.#field_idents, _archetype, _access); )* } - } - } - }; - - let fetch_impl = impl_fetch(false, fetch_struct_name.clone(), item_struct_name.clone()); - 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 init_state(world: &mut #path::world::World) -> #state_struct_name #user_ty_generics { + #state_struct_name { + #(#field_idents: <#field_types>::init_state(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))* + fn matches_component_set(state: &Self::State, _set_contains_id: &impl Fn(#path::component::ComponentId) -> bool) -> bool { + true #(&& <#field_types>::matches_component_set(&state.#field_idents, _set_contains_id))* + } } } }; - let read_only_fetch_impl = if fetch_struct_attributes.is_mutable { - impl_fetch( - true, - read_only_fetch_struct_name.clone(), - read_only_item_struct_name.clone(), - ) - } else { - quote! {} - }; - - let read_only_world_query_impl = if fetch_struct_attributes.is_mutable { - quote! { - #[automatically_derived] - #visibility struct #read_only_struct_name #user_impl_generics #user_where_clauses { - #( #field_idents: < #field_types as #path::query::WorldQuery >::ReadOnly, )* - #(#(#ignored_field_attrs)* #ignored_field_visibilities #ignored_field_idents: #ignored_field_types,)* - } - - // SAFETY: `ROQueryFetch` is the same as `QueryFetch` - unsafe impl #user_impl_generics #path::query::WorldQuery for #read_only_struct_name #user_ty_generics #user_where_clauses { - type ReadOnly = Self; - type State = #state_struct_name #user_ty_generics; - - fn shrink<'__wlong: '__wshort, '__wshort>(item: #path::query::#item_type_alias<'__wlong, Self>) - -> #path::query::#item_type_alias<'__wshort, Self> { - #read_only_item_struct_name { - #( - #field_idents : < - < #field_types as #path::query::WorldQuery >::ReadOnly as #path::query::WorldQuery - > :: shrink( item.#field_idents ), - )* - #( - #ignored_field_idents: item.#ignored_field_idents, - )* - } + let mutable_impl = impl_fetch(false); + let readonly_impl = match fetch_struct_attributes.is_mutable { + true => { + let world_query_impl = impl_fetch(true); + quote! { + #[automatically_derived] + #visibility struct #read_only_struct_name #user_impl_generics #user_where_clauses { + #( #field_idents: #read_only_field_types, )* + #(#(#ignored_field_attrs)* #ignored_field_visibilities #ignored_field_idents: #ignored_field_types,)* } - } - impl #user_impl_generics_with_world #path::query::WorldQueryGats<'__w> for #read_only_struct_name #user_ty_generics #user_where_clauses { - type Fetch = #read_only_fetch_struct_name #user_ty_generics_with_world; - type _State = #state_struct_name #user_ty_generics; + #world_query_impl } } - } else { - quote! {} + false => quote!(), }; let read_only_asserts = if fetch_struct_attributes.is_mutable { @@ -347,7 +346,7 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream { // Double-check that the data fetched by `<_ as WorldQuery>::ReadOnly` is read-only. // This is technically unnecessary as `<_ as WorldQuery>::ReadOnly: ReadOnlyWorldQuery` // but to protect against future mistakes we assert the assoc type implements `ReadOnlyWorldQuery` anyway - #( assert_readonly::< < #field_types as #path::query::WorldQuery > :: ReadOnly >(); )* + #( assert_readonly::<#read_only_field_types>(); )* } } else { quote! { @@ -363,38 +362,17 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream { }; TokenStream::from(quote! { - #fetch_impl - - #state_impl + #mutable_impl - #read_only_fetch_impl + #readonly_impl - #read_only_world_query_impl - - // SAFETY: if the worldquery is mutable this defers to soundness of the `#field_types: WorldQuery` impl, otherwise - // if the world query is immutable then `#read_only_struct_name #user_ty_generics` is the same type as `#struct_name #user_ty_generics` - unsafe impl #user_impl_generics #path::query::WorldQuery for #struct_name #user_ty_generics #user_where_clauses { - type ReadOnly = #read_only_struct_name #user_ty_generics; - type State = #state_struct_name #user_ty_generics; - fn shrink<'__wlong: '__wshort, '__wshort>(item: #path::query::#item_type_alias<'__wlong, Self>) - -> #path::query::#item_type_alias<'__wshort, Self> { - #item_struct_name { - #( - #field_idents : < #field_types as #path::query::WorldQuery> :: shrink( item.#field_idents ), - )* - #( - #ignored_field_idents: item.#ignored_field_idents, - )* - } - } - } - - impl #user_impl_generics_with_world #path::query::WorldQueryGats<'__w> for #struct_name #user_ty_generics #user_where_clauses { - type Fetch = #fetch_struct_name #user_ty_generics_with_world; - type _State = #state_struct_name #user_ty_generics; + #[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,)* } - /// SAFETY: each item in the struct is read only + /// SAFETY: we assert fields are readonly below unsafe impl #user_impl_generics #path::query::ReadOnlyWorldQuery for #read_only_struct_name #user_ty_generics #user_where_clauses {} @@ -417,9 +395,15 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream { // workaround. #[allow(dead_code)] const _: () = { - fn dead_code_workaround #user_impl_generics (q: #struct_name #user_ty_generics) #user_where_clauses { + fn dead_code_workaround #user_impl_generics ( + q: #struct_name #user_ty_generics, + q2: #read_only_struct_name #user_ty_generics + ) #user_where_clauses { #(q.#field_idents;)* #(q.#ignored_field_idents;)* + #(q2.#field_idents;)* + #(q2.#ignored_field_idents;)* + } }; }) diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 42db0b5e9f7e0d..dcf9a1ea511bb4 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -54,8 +54,8 @@ 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 -/// [`WorldQuery::State`] associated types respectively. +/// structs that are used as [`WorldQuery::Fetch`](WorldQueryGats::Fetch) and +/// [`WorldQuery::State`] associated types. /// /// The derive macro requires every struct field to implement the `WorldQuery` trait. /// @@ -320,70 +320,41 @@ use std::{cell::UnsafeCell, marker::PhantomData}; /// /// component access of `ROQueryFetch` must be a subset of `QueryFetch` /// and `ROQueryFetch` must match exactly the same archetypes/tables as `QueryFetch` -pub unsafe trait WorldQuery: for<'w> WorldQueryGats<'w, _State = Self::State> { - type ReadOnly: ReadOnlyWorldQuery; - type State: FetchState; - - /// This function manually implements variance for the query items. - fn shrink<'wlong: 'wshort, 'wshort>(item: QueryItem<'wlong, Self>) -> QueryItem<'wshort, Self>; -} - -/// A world query that is read only. /// -/// # Safety -/// -/// This must only be implemented for read-only [`WorldQuery`]'s. -pub unsafe trait ReadOnlyWorldQuery: WorldQuery {} - -/// The [`Fetch`] of a [`WorldQuery`], which declares which data it needs access to -pub type QueryFetch<'w, Q> = >::Fetch; -/// The item type returned when a [`WorldQuery`] is iterated over -pub type QueryItem<'w, Q> = <>::Fetch as Fetch<'w>>::Item; -/// The read-only [`Fetch`] of a [`WorldQuery`], which declares which data it needs access to when accessed immutably -pub type ROQueryFetch<'w, Q> = QueryFetch<'w, ::ReadOnly>; -/// The read-only variant of the item type returned when a [`WorldQuery`] is iterated over immutably -pub type ROQueryItem<'w, Q> = QueryItem<'w, ::ReadOnly>; +/// Implementor must ensure that [`WorldQuery::update_component_access`] and +/// [`WorldQuery::update_archetype_component_access`] exactly reflects the results of +/// [`WorldQuery::matches_component_set`], [`WorldQuery::archetype_fetch`], and +/// [`WorldQuery::table_fetch`]. +pub unsafe trait WorldQuery: for<'w> WorldQueryGats<'w> { + /// The read-only variant of this [`WorldQuery`], which satsifies the [`ReadOnlyWorldQuery`] trait. + type ReadOnly: ReadOnlyWorldQuery; -/// 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; -} + /// State used to construct a [`Self::Fetch`](crate::query::WorldQueryGats::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 [`Self::Fetch`](crate::query::WorldQueryGats::Fetch). + type State: Send + Sync + Sized; -/// Types that implement this trait are responsible for fetching query items from tables or -/// archetypes. -/// -/// Every type that implements [`WorldQuery`] have their associated [`WorldQuery::Fetch`](WorldQueryGats::Fetch) and -/// [`WorldQuery::State`] types that are essential for fetching component data. -/// -/// # Safety -/// -/// 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::table_fetch`]. -pub unsafe trait Fetch<'world>: Sized { - type Item; - type State: FetchState; + /// This function manually implements subtyping for the query items. + fn shrink<'wlong: 'wshort, 'wshort>(item: QueryItem<'wlong, Self>) -> QueryItem<'wshort, Self>; /// 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 [`WorldQuery::init_state`]) using the same `world` passed /// in to this function. - unsafe fn init( - world: &'world World, + unsafe fn init_fetch<'w>( + world: &'w World, state: &Self::State, last_change_tick: u32, change_tick: u32, - ) -> Self; + ) -> >::Fetch; /// Returns true if (and only if) every table of every archetype matched by this fetch contains /// all of the matched components. This is used to select a more efficient "table iterator" - /// for "dense" queries. If this returns true, [`Fetch::set_table`] and [`Fetch::table_fetch`] - /// will be called for iterators. If this returns false, [`Fetch::set_archetype`] and - /// [`Fetch::archetype_fetch`] will be called for iterators. + /// for "dense" queries. If this returns true, [`WorldQuery::set_table`] and [`WorldQuery::table_fetch`] + /// will be called for iterators. If this returns false, [`WorldQuery::set_archetype`] and + /// [`WorldQuery::archetype_fetch`] will be called for iterators. const IS_DENSE: bool; /// Returns true if (and only if) this Fetch relies strictly on archetypes to limit which @@ -394,63 +365,79 @@ pub unsafe trait Fetch<'world>: Sized { const IS_ARCHETYPAL: bool; /// Adjusts internal state to account for the next [`Archetype`]. This will always be called on - /// archetypes that match this [`Fetch`]. + /// archetypes that match this [`WorldQuery`]. /// /// # Safety /// - /// `archetype` and `tables` must be from the [`World`] [`Fetch::init`] was called on. `state` must + /// `archetype` and `tables` must be from the [`World`] [`WorldQuery::init_state`] was called on. `state` must /// be the [`Self::State`] this was initialized with. - unsafe fn set_archetype( - &mut self, + unsafe fn set_archetype<'w>( + fetch: &mut >::Fetch, state: &Self::State, - archetype: &'world Archetype, - tables: &'world Tables, + archetype: &'w Archetype, + tables: &'w Tables, ); /// Adjusts internal state to account for the next [`Table`]. This will always be called on tables - /// that match this [`Fetch`]. + /// that match this [`WorldQuery`]. /// /// # Safety /// - /// `table` must be from the [`World`] [`Fetch::init`] was called on. `state` must be the + /// `table` must be from the [`World`] [`WorldQuery::init_state`] was called on. `state` must be the /// [`Self::State`] this was initialized with. - unsafe fn set_table(&mut self, state: &Self::State, table: &'world Table); + unsafe fn set_table<'w>( + fetch: &mut >::Fetch, + state: &Self::State, + table: &'w Table, + ); - /// Fetch [`Self::Item`] for the given `archetype_index` in the current [`Archetype`]. This must - /// always be called after [`Fetch::set_archetype`] with an `archetype_index` in the range of + /// Fetch [`Self::Item`](`WorldQueryGats::Item`) for the given `archetype_index` in the current [`Archetype`]. This must + /// always be called after [`WorldQuery::set_archetype`] with an `archetype_index` in the range of /// the current [`Archetype`] /// /// # Safety - /// Must always be called _after_ [`Fetch::set_archetype`]. `archetype_index` must be in the range + /// Must always be called _after_ [`WorldQuery::set_archetype`]. `archetype_index` must be in the range /// of the current archetype - unsafe fn archetype_fetch(&mut self, archetype_index: usize) -> Self::Item; + unsafe fn archetype_fetch<'w>( + fetch: &mut >::Fetch, + archetype_index: usize, + ) -> >::Item; - /// Fetch [`Self::Item`] for the given `table_row` in the current [`Table`]. This must always be - /// called after [`Fetch::set_table`] with a `table_row` in the range of the current [`Table`] + /// Fetch [`Self::Item`](`WorldQueryGats::Item`) for the given `table_row` in the current [`Table`]. This must always be + /// called after [`WorldQuery::set_table`] with a `table_row` in the range of the current [`Table`] /// /// # Safety /// - /// Must always be called _after_ [`Fetch::set_table`]. `table_row` must be in the range of the + /// Must always be called _after_ [`WorldQuery::set_table`]. `table_row` must be in the range of the /// current table - unsafe fn table_fetch(&mut self, table_row: usize) -> Self::Item; + unsafe fn table_fetch<'w>( + fetch: &mut >::Fetch, + table_row: usize, + ) -> >::Item; /// # Safety /// - /// Must always be called _after_ [`Fetch::set_archetype`]. `archetype_index` must be in the range + /// Must always be called _after_ [`WorldQuery::set_archetype`]. `archetype_index` must be in the range /// of the current archetype. #[allow(unused_variables)] #[inline] - unsafe fn archetype_filter_fetch(&mut self, archetype_index: usize) -> bool { + unsafe fn archetype_filter_fetch( + fetch: &mut >::Fetch, + archetype_index: usize, + ) -> bool { true } /// # Safety /// - /// Must always be called _after_ [`Fetch::set_table`]. `table_row` must be in the range of the + /// Must always be called _after_ [`WorldQuery::set_table`]. `table_row` must be in the range of the /// current table. #[allow(unused_variables)] #[inline] - unsafe fn table_filter_fetch(&mut self, table_row: usize) -> bool { + unsafe fn table_filter_fetch( + fetch: &mut >::Fetch, + table_row: usize, + ) -> bool { true } @@ -464,68 +451,58 @@ pub unsafe trait Fetch<'world>: Sized { archetype: &Archetype, access: &mut Access, ); + + fn init_state(world: &mut World) -> Self::State; + fn matches_component_set( + state: &Self::State, + set_contains_id: &impl Fn(ComponentId) -> bool, + ) -> bool; } -/// 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; +/// A helper trait for [`WorldQuery`] that works around Rust's lack of Generic Associated Types +pub trait WorldQueryGats<'world> { + type Item; + type Fetch; } -/// SAFETY: no component or archetype access -unsafe impl WorldQuery for Entity { - type ReadOnly = Self; - type State = EntityState; +/// A world query that is read only. +/// +/// # Safety +/// +/// This must only be implemented for read-only [`WorldQuery`]'s. +pub unsafe trait ReadOnlyWorldQuery: WorldQuery {} - fn shrink<'wlong: 'wshort, 'wshort>(item: QueryItem<'wlong, Self>) -> QueryItem<'wshort, Self> { - item - } -} +/// The `Fetch` of a [`WorldQuery`], which is used to store state for each archetype/table. +pub type QueryFetch<'w, Q> = >::Fetch; +/// The item type returned when a [`WorldQuery`] is iterated over +pub type QueryItem<'w, Q> = >::Item; +/// The read-only `Fetch` of a [`WorldQuery`], which is used to store state for each archetype/table. +pub type ROQueryFetch<'w, Q> = QueryFetch<'w, ::ReadOnly>; +/// The read-only variant of the item type returned when a [`WorldQuery`] is iterated over immutably +pub type ROQueryItem<'w, Q> = QueryItem<'w, ::ReadOnly>; -/// The [`Fetch`] of [`Entity`]. #[doc(hidden)] #[derive(Clone)] pub struct EntityFetch<'w> { entities: Option>, } -/// 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 - } +/// SAFETY: no component or archetype access +unsafe impl WorldQuery for Entity { + type ReadOnly = Self; + type State = (); - #[inline] - fn matches_component_set(&self, _set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { - true + fn shrink<'wlong: 'wshort, 'wshort>(item: QueryItem<'wlong, Self>) -> QueryItem<'wshort, Self> { + item } -} - -impl<'w> WorldQueryGats<'w> for Entity { - type Fetch = EntityFetch<'w>; - type _State = EntityState; -} - -/// SAFETY: no component or archetype access -unsafe impl<'w> Fetch<'w> for EntityFetch<'w> { - type Item = Entity; - type State = EntityState; const IS_DENSE: bool = true; const IS_ARCHETYPAL: bool = true; - unsafe fn init( + unsafe fn init_fetch<'w>( _world: &'w World, - _state: &EntityState, + _state: &(), _last_change_tick: u32, _change_tick: u32, ) -> EntityFetch<'w> { @@ -533,29 +510,39 @@ unsafe impl<'w> Fetch<'w> for EntityFetch<'w> { } #[inline] - unsafe fn set_archetype( - &mut self, - _state: &Self::State, + unsafe fn set_archetype<'w>( + fetch: &mut EntityFetch<'w>, + _state: &(), archetype: &'w Archetype, _tables: &Tables, ) { - self.entities = Some(archetype.entities().into()); + fetch.entities = Some(archetype.entities().into()); } #[inline] - unsafe fn set_table(&mut self, _state: &Self::State, table: &'w Table) { - self.entities = Some(table.entities().into()); + unsafe fn set_table<'w>(fetch: &mut EntityFetch<'w>, _state: &(), table: &'w Table) { + fetch.entities = Some(table.entities().into()); } #[inline] - unsafe fn table_fetch(&mut self, table_row: usize) -> Self::Item { - let entities = self.entities.unwrap_or_else(|| debug_checked_unreachable()); + unsafe fn table_fetch<'w>( + fetch: &mut >::Fetch, + table_row: usize, + ) -> QueryItem<'w, Self> { + let entities = fetch + .entities + .unwrap_or_else(|| debug_checked_unreachable()); *entities.get(table_row) } #[inline] - unsafe fn archetype_fetch(&mut self, archetype_index: usize) -> Self::Item { - let entities = self.entities.unwrap_or_else(|| debug_checked_unreachable()); + unsafe fn archetype_fetch<'w>( + fetch: &mut >::Fetch, + archetype_index: usize, + ) -> >::Item { + let entities = fetch + .entities + .unwrap_or_else(|| debug_checked_unreachable()); *entities.get(archetype_index) } @@ -567,40 +554,25 @@ unsafe impl<'w> Fetch<'w> for EntityFetch<'w> { _access: &mut Access, ) { } -} -/// SAFETY: `ROQueryFetch` is the same as `QueryFetch` -unsafe impl WorldQuery for &T { - type ReadOnly = Self; - type State = ComponentIdState; + fn init_state(_world: &mut World) {} - fn shrink<'wlong: 'wshort, 'wshort>(item: QueryItem<'wlong, Self>) -> QueryItem<'wshort, Self> { - item + fn matches_component_set( + _state: &Self::State, + _set_contains_id: &impl Fn(ComponentId) -> bool, + ) -> bool { + true } } -/// The [`FetchState`] of `&T`. -#[doc(hidden)] -pub struct ComponentIdState { - component_id: ComponentId, - marker: PhantomData, +impl<'w> WorldQueryGats<'w> for Entity { + type Fetch = EntityFetch<'w>; + type Item = Entity; } -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) - } -} +/// SAFETY: access is read only +unsafe impl ReadOnlyWorldQuery for Entity {} -/// The [`Fetch`] of `&T`. #[doc(hidden)] pub struct ReadFetch<'w, T> { // T::Storage = TableStorage @@ -611,30 +583,14 @@ pub struct ReadFetch<'w, T> { sparse_set: Option<&'w ComponentSparseSet>, } -impl Clone for ReadFetch<'_, T> { - fn clone(&self) -> Self { - Self { - table_components: self.table_components, - entity_table_rows: self.entity_table_rows, - entities: self.entities, - sparse_set: self.sparse_set, - } - } -} - -/// SAFETY: access is read only -unsafe impl ReadOnlyWorldQuery for &T {} - -impl<'w, T: Component> WorldQueryGats<'w> for &T { - type Fetch = ReadFetch<'w, T>; - type _State = ComponentIdState; -} +/// SAFETY: `ROQueryFetch` is the same as `QueryFetch` +unsafe impl WorldQuery for &T { + type ReadOnly = Self; + 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; + fn shrink<'wlong: 'wshort, 'wshort>(item: &'wlong T) -> &'wshort T { + item + } const IS_DENSE: bool = { match T::Storage::STORAGE_TYPE { @@ -645,9 +601,9 @@ unsafe impl<'w, T: Component> Fetch<'w> for ReadFetch<'w, T> { const IS_ARCHETYPAL: bool = true; - unsafe fn init( + unsafe fn init_fetch<'w>( world: &'w World, - state: &ComponentIdState, + &component_id: &ComponentId, _last_change_tick: u32, _change_tick: u32, ) -> ReadFetch<'w, T> { @@ -655,61 +611,53 @@ 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(component_id).unwrap()), } } #[inline] - unsafe fn set_archetype( - &mut self, - state: &Self::State, + unsafe fn set_archetype<'w>( + fetch: &mut ReadFetch<'w, T>, + &component_id: &ComponentId, archetype: &'w Archetype, tables: &'w Tables, ) { match T::Storage::STORAGE_TYPE { StorageType::Table => { - self.entity_table_rows = Some(archetype.entity_table_rows().into()); + fetch.entity_table_rows = Some(archetype.entity_table_rows().into()); let column = tables[archetype.table_id()] - .get_column(state.component_id) + .get_column(component_id) .unwrap(); - self.table_components = Some(column.get_data_slice().into()); + fetch.table_components = Some(column.get_data_slice().into()); } - StorageType::SparseSet => self.entities = Some(archetype.entities().into()), + StorageType::SparseSet => fetch.entities = Some(archetype.entities().into()), } } #[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(), - ); + unsafe fn set_table<'w>(fetch: &mut ReadFetch<'w, T>, &id: &ComponentId, table: &'w Table) { + fetch.table_components = Some(table.get_column(id).unwrap().get_data_slice().into()); } #[inline] - unsafe fn archetype_fetch(&mut self, archetype_index: usize) -> Self::Item { + unsafe fn archetype_fetch<'w>( + fetch: &mut >::Fetch, + archetype_index: usize, + ) -> >::Item { match T::Storage::STORAGE_TYPE { StorageType::Table => { - let (entity_table_rows, table_components) = self + let (entity_table_rows, table_components) = fetch .entity_table_rows - .zip(self.table_components) + .zip(fetch.table_components) .unwrap_or_else(|| debug_checked_unreachable()); let table_row = *entity_table_rows.get(archetype_index); table_components.get(table_row).deref() } StorageType::SparseSet => { - let (entities, sparse_set) = self + let (entities, sparse_set) = fetch .entities - .zip(self.sparse_set) + .zip(fetch.sparse_set) .unwrap_or_else(|| debug_checked_unreachable()); let entity = *entities.get(archetype_index); sparse_set @@ -721,46 +669,69 @@ unsafe impl<'w, T: Component> Fetch<'w> for ReadFetch<'w, T> { } #[inline] - unsafe fn table_fetch(&mut self, table_row: usize) -> Self::Item { - let components = self + unsafe fn table_fetch<'w>( + fetch: &mut >::Fetch, + table_row: usize, + ) -> >::Item { + let components = fetch .table_components .unwrap_or_else(|| debug_checked_unreachable()); components.get(table_row).deref() } - fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { + fn update_component_access( + &component_id: &ComponentId, + access: &mut FilteredAccess, + ) { assert!( - !access.access().has_write(state.component_id), + !access.access().has_write(component_id), "&{} 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(component_id); } fn update_archetype_component_access( - state: &Self::State, + &component_id: &ComponentId, 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(component_id) { access.add_read(archetype_component_id); } } -} -/// 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; + fn init_state(world: &mut World) -> ComponentId { + world.init_component::() + } - fn shrink<'wlong: 'wshort, 'wshort>(item: QueryItem<'wlong, Self>) -> QueryItem<'wshort, Self> { - item + fn matches_component_set( + &state: &ComponentId, + set_contains_id: &impl Fn(ComponentId) -> bool, + ) -> bool { + set_contains_id(state) } } -/// The [`Fetch`] of `&mut T`. +impl Clone for ReadFetch<'_, T> { + fn clone(&self) -> Self { + Self { + table_components: self.table_components, + entity_table_rows: self.entity_table_rows, + entities: self.entities, + sparse_set: self.sparse_set, + } + } +} + +/// SAFETY: access is read only +unsafe impl ReadOnlyWorldQuery for &T {} + +impl<'w, T: Component> WorldQueryGats<'w> for &T { + type Fetch = ReadFetch<'w, T>; + type Item = &'w T; +} + #[doc(hidden)] pub struct WriteFetch<'w, T> { // T::Storage = TableStorage @@ -775,30 +746,14 @@ pub struct WriteFetch<'w, T> { change_tick: u32, } -impl Clone for WriteFetch<'_, T> { - fn clone(&self) -> Self { - Self { - table_components: self.table_components, - table_ticks: self.table_ticks, - entities: self.entities, - entity_table_rows: self.entity_table_rows, - sparse_set: self.sparse_set, - last_change_tick: self.last_change_tick, - change_tick: self.change_tick, - } - } -} - -impl<'w, T: Component> WorldQueryGats<'w> for &mut T { - type Fetch = WriteFetch<'w, T>; - type _State = ComponentIdState; -} +/// 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 = 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; + fn shrink<'wlong: 'wshort, 'wshort>(item: Mut<'wlong, T>) -> Mut<'wshort, T> { + item + } const IS_DENSE: bool = { match T::Storage::STORAGE_TYPE { @@ -809,23 +764,18 @@ unsafe impl<'w, T: Component> Fetch<'w> for WriteFetch<'w, T> { const IS_ARCHETYPAL: bool = true; - unsafe fn init( + unsafe fn init_fetch<'w>( world: &'w World, - state: &ComponentIdState, + &component_id: &ComponentId, last_change_tick: u32, change_tick: u32, - ) -> Self { - Self { + ) -> WriteFetch<'w, T> { + WriteFetch { 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(component_id).unwrap()), table_ticks: None, last_change_tick, change_tick, @@ -833,54 +783,61 @@ unsafe impl<'w, T: Component> Fetch<'w> for WriteFetch<'w, T> { } #[inline] - unsafe fn set_archetype( - &mut self, - state: &Self::State, + unsafe fn set_archetype<'w>( + fetch: &mut WriteFetch<'w, T>, + &component_id: &ComponentId, archetype: &'w Archetype, tables: &'w Tables, ) { match T::Storage::STORAGE_TYPE { StorageType::Table => { - self.entity_table_rows = Some(archetype.entity_table_rows().into()); + fetch.entity_table_rows = Some(archetype.entity_table_rows().into()); let column = tables[archetype.table_id()] - .get_column(state.component_id) + .get_column(component_id) .unwrap(); - self.table_components = Some(column.get_data_slice().into()); - self.table_ticks = Some(column.get_ticks_slice().into()); + fetch.table_components = Some(column.get_data_slice().into()); + fetch.table_ticks = Some(column.get_ticks_slice().into()); } - StorageType::SparseSet => self.entities = Some(archetype.entities().into()), + StorageType::SparseSet => fetch.entities = Some(archetype.entities().into()), } } #[inline] - unsafe fn set_table(&mut self, state: &Self::State, table: &'w Table) { - let column = table.get_column(state.component_id).unwrap(); - self.table_components = Some(column.get_data_slice().into()); - self.table_ticks = Some(column.get_ticks_slice().into()); + unsafe fn set_table<'w>( + fetch: &mut WriteFetch<'w, T>, + &component_id: &ComponentId, + table: &'w Table, + ) { + let column = table.get_column(component_id).unwrap(); + fetch.table_components = Some(column.get_data_slice().into()); + fetch.table_ticks = Some(column.get_ticks_slice().into()); } #[inline] - unsafe fn archetype_fetch(&mut self, archetype_index: usize) -> Self::Item { + unsafe fn archetype_fetch<'w>( + fetch: &mut >::Fetch, + archetype_index: usize, + ) -> >::Item { match T::Storage::STORAGE_TYPE { StorageType::Table => { - let (entity_table_rows, (table_components, table_ticks)) = self + let (entity_table_rows, (table_components, table_ticks)) = fetch .entity_table_rows - .zip(self.table_components.zip(self.table_ticks)) + .zip(fetch.table_components.zip(fetch.table_ticks)) .unwrap_or_else(|| debug_checked_unreachable()); let table_row = *entity_table_rows.get(archetype_index); Mut { value: table_components.get(table_row).deref_mut(), ticks: Ticks { component_ticks: table_ticks.get(table_row).deref_mut(), - change_tick: self.change_tick, - last_change_tick: self.last_change_tick, + change_tick: fetch.change_tick, + last_change_tick: fetch.last_change_tick, }, } } StorageType::SparseSet => { - let (entities, sparse_set) = self + let (entities, sparse_set) = fetch .entities - .zip(self.sparse_set) + .zip(fetch.sparse_set) .unwrap_or_else(|| debug_checked_unreachable()); let entity = *entities.get(archetype_index); let (component, component_ticks) = sparse_set @@ -890,8 +847,8 @@ unsafe impl<'w, T: Component> Fetch<'w> for WriteFetch<'w, T> { value: component.assert_unique().deref_mut(), ticks: Ticks { component_ticks: component_ticks.deref_mut(), - change_tick: self.change_tick, - last_change_tick: self.last_change_tick, + change_tick: fetch.change_tick, + last_change_tick: fetch.last_change_tick, }, } } @@ -899,171 +856,202 @@ unsafe impl<'w, T: Component> Fetch<'w> for WriteFetch<'w, T> { } #[inline] - unsafe fn table_fetch(&mut self, table_row: usize) -> Self::Item { - let (table_components, table_ticks) = self + unsafe fn table_fetch<'w>( + fetch: &mut >::Fetch, + table_row: usize, + ) -> >::Item { + let (table_components, table_ticks) = fetch .table_components - .zip(self.table_ticks) + .zip(fetch.table_ticks) .unwrap_or_else(|| debug_checked_unreachable()); Mut { value: table_components.get(table_row).deref_mut(), ticks: Ticks { component_ticks: table_ticks.get(table_row).deref_mut(), - change_tick: self.change_tick, - last_change_tick: self.last_change_tick, + change_tick: fetch.change_tick, + last_change_tick: fetch.last_change_tick, }, } } - fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { + fn update_component_access( + &component_id: &ComponentId, + access: &mut FilteredAccess, + ) { assert!( - !access.access().has_read(state.component_id), + !access.access().has_read(component_id), "&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(component_id); } fn update_archetype_component_access( - state: &Self::State, + &component_id: &ComponentId, 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(component_id) { access.add_write(archetype_component_id); } } -} -// SAFETY: defers to soundness of `T: WorldQuery` impl -unsafe impl WorldQuery for Option { - type ReadOnly = Option; - type State = OptionState; + fn init_state(world: &mut World) -> ComponentId { + world.init_component::() + } - fn shrink<'wlong: 'wshort, 'wshort>(item: QueryItem<'wlong, Self>) -> QueryItem<'wshort, Self> { - item.map(T::shrink) + fn matches_component_set( + &state: &ComponentId, + set_contains_id: &impl Fn(ComponentId) -> bool, + ) -> bool { + set_contains_id(state) } } -/// The [`Fetch`] of `Option`. -#[doc(hidden)] -#[derive(Clone)] -pub struct OptionFetch { - fetch: T, - matches: bool, +impl Clone for WriteFetch<'_, T> { + fn clone(&self) -> Self { + Self { + table_components: self.table_components, + table_ticks: self.table_ticks, + entities: self.entities, + entity_table_rows: self.entity_table_rows, + sparse_set: self.sparse_set, + last_change_tick: self.last_change_tick, + change_tick: self.change_tick, + } + } } -/// SAFETY: [`OptionFetch`] is read only because `T` is read only -unsafe impl ReadOnlyWorldQuery for Option {} +impl<'w, T: Component> WorldQueryGats<'w> for &mut T { + type Fetch = WriteFetch<'w, T>; + type Item = Mut<'w, T>; +} -/// The [`FetchState`] of `Option`. #[doc(hidden)] -pub struct OptionState { - state: T, +pub struct OptionFetch<'w, T: WorldQuery> { + fetch: >::Fetch, + matches: bool, } - -impl FetchState for OptionState { - fn init(world: &mut World) -> Self { +impl<'w, T: WorldQuery> Clone for OptionFetch<'w, T> +where + >::Fetch: Clone, +{ + fn clone(&self) -> Self { Self { - state: T::init(world), + fetch: self.fetch.clone(), + matches: self.matches, } } - - 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; -} +// SAFETY: defers to soundness of `T: WorldQuery` impl +unsafe impl WorldQuery for Option { + type ReadOnly = Option; + 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; + fn shrink<'wlong: 'wshort, 'wshort>(item: QueryItem<'wlong, Self>) -> QueryItem<'wshort, Self> { + item.map(T::shrink) + } const IS_DENSE: bool = T::IS_DENSE; const IS_ARCHETYPAL: bool = T::IS_ARCHETYPAL; - unsafe fn init( + unsafe fn init_fetch<'w>( world: &'w World, - state: &OptionState, + state: &T::State, last_change_tick: u32, change_tick: u32, - ) -> Self { - Self { - fetch: T::init(world, &state.state, last_change_tick, change_tick), + ) -> OptionFetch<'w, T> { + OptionFetch { + fetch: T::init_fetch(world, state, last_change_tick, change_tick), matches: false, } } #[inline] - unsafe fn set_archetype( - &mut self, - state: &Self::State, + unsafe fn set_archetype<'w>( + fetch: &mut OptionFetch<'w, T>, + state: &T::State, archetype: &'w Archetype, tables: &'w Tables, ) { - self.matches = state - .state - .matches_component_set(&|id| archetype.contains(id)); - if self.matches { - self.fetch.set_archetype(&state.state, archetype, tables); + fetch.matches = T::matches_component_set(state, &|id| archetype.contains(id)); + if fetch.matches { + T::set_archetype(&mut fetch.fetch, 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)); - if self.matches { - self.fetch.set_table(&state.state, table); + unsafe fn set_table<'w>(fetch: &mut OptionFetch<'w, T>, state: &T::State, table: &'w Table) { + fetch.matches = T::matches_component_set(state, &|id| table.has_column(id)); + if fetch.matches { + T::set_table(&mut fetch.fetch, state, table); } } #[inline] - unsafe fn archetype_fetch(&mut self, archetype_index: usize) -> Self::Item { - if self.matches { - Some(self.fetch.archetype_fetch(archetype_index)) + unsafe fn archetype_fetch<'w>( + fetch: &mut >::Fetch, + archetype_index: usize, + ) -> >::Item { + if fetch.matches { + Some(T::archetype_fetch(&mut fetch.fetch, archetype_index)) } else { None } } #[inline] - unsafe fn table_fetch(&mut self, table_row: usize) -> Self::Item { - if self.matches { - Some(self.fetch.table_fetch(table_row)) + unsafe fn table_fetch<'w>( + fetch: &mut >::Fetch, + table_row: usize, + ) -> >::Item { + if fetch.matches { + Some(T::table_fetch(&mut fetch.fetch, table_row)) } else { None } } - fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { + fn update_component_access(state: &T::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); } fn update_archetype_component_access( - state: &Self::State, + state: &T::State, 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 init_state(world: &mut World) -> T::State { + T::init_state(world) + } + + fn matches_component_set( + _state: &T::State, + _set_contains_id: &impl Fn(ComponentId) -> bool, + ) -> bool { + true + } +} + +/// SAFETY: [`OptionFetch`] is read only because `T` is read only +unsafe impl ReadOnlyWorldQuery for Option {} + +impl<'w, T: WorldQuery> WorldQueryGats<'w> for Option { + type Fetch = OptionFetch<'w, T>; + type Item = Option>; } /// [`WorldQuery`] that tracks changes and additions for component `T`. @@ -1130,38 +1118,6 @@ impl ChangeTrackers { } } -// SAFETY: `ROQueryFetch` is the same as `QueryFetch` -unsafe impl WorldQuery for ChangeTrackers { - type ReadOnly = Self; - type State = ChangeTrackersState; - - 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> { // T::Storage = TableStorage @@ -1190,19 +1146,14 @@ impl Clone for ChangeTrackersFetch<'_, T> { } } -/// SAFETY: access is read only -unsafe impl ReadOnlyWorldQuery for ChangeTrackers {} - -impl<'w, T: Component> WorldQueryGats<'w> for ChangeTrackers { - type Fetch = ChangeTrackersFetch<'w, T>; - type _State = ChangeTrackersState; -} +// SAFETY: `ROQueryFetch` is the same as `QueryFetch` +unsafe impl WorldQuery for ChangeTrackers { + type ReadOnly = Self; + 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; + fn shrink<'wlong: 'wshort, 'wshort>(item: QueryItem<'wlong, Self>) -> QueryItem<'wshort, Self> { + item + } const IS_DENSE: bool = { match T::Storage::STORAGE_TYPE { @@ -1213,9 +1164,9 @@ unsafe impl<'w, T: Component> Fetch<'w> for ChangeTrackersFetch<'w, T> { const IS_ARCHETYPAL: bool = true; - unsafe fn init( + unsafe fn init_fetch<'w>( world: &'w World, - state: &ChangeTrackersState, + &id: &ComponentId, last_change_tick: u32, change_tick: u32, ) -> ChangeTrackersFetch<'w, T> { @@ -1223,13 +1174,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(id).unwrap()), marker: PhantomData, last_change_tick, change_tick, @@ -1237,60 +1183,61 @@ unsafe impl<'w, T: Component> Fetch<'w> for ChangeTrackersFetch<'w, T> { } #[inline] - unsafe fn set_archetype( - &mut self, - state: &Self::State, + unsafe fn set_archetype<'w>( + fetch: &mut ChangeTrackersFetch<'w, T>, + &id: &ComponentId, archetype: &'w Archetype, tables: &'w Tables, ) { 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(); - self.table_ticks = Some(column.get_ticks_slice().into()); + fetch.entity_table_rows = Some(archetype.entity_table_rows().into()); + let column = tables[archetype.table_id()].get_column(id).unwrap(); + fetch.table_ticks = Some(column.get_ticks_slice().into()); } - StorageType::SparseSet => self.entities = Some(archetype.entities().into()), + StorageType::SparseSet => fetch.entities = Some(archetype.entities().into()), } } #[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(), - ); + unsafe fn set_table<'w>( + fetch: &mut ChangeTrackersFetch<'w, T>, + &id: &ComponentId, + table: &'w Table, + ) { + fetch.table_ticks = Some(table.get_column(id).unwrap().get_ticks_slice().into()); } #[inline] - unsafe fn archetype_fetch(&mut self, archetype_index: usize) -> Self::Item { + unsafe fn archetype_fetch<'w>( + fetch: &mut >::Fetch, + archetype_index: usize, + ) -> >::Item { match T::Storage::STORAGE_TYPE { StorageType::Table => { - let entity_table_rows = self + let entity_table_rows = fetch .entity_table_rows .unwrap_or_else(|| debug_checked_unreachable()); let table_row = *entity_table_rows.get(archetype_index); ChangeTrackers { component_ticks: { - let table_ticks = self + let table_ticks = fetch .table_ticks .unwrap_or_else(|| debug_checked_unreachable()); table_ticks.get(table_row).read() }, marker: PhantomData, - last_change_tick: self.last_change_tick, - change_tick: self.change_tick, + last_change_tick: fetch.last_change_tick, + change_tick: fetch.change_tick, } } StorageType::SparseSet => { - let entities = self.entities.unwrap_or_else(|| debug_checked_unreachable()); + let entities = fetch + .entities + .unwrap_or_else(|| debug_checked_unreachable()); let entity = *entities.get(archetype_index); ChangeTrackers { - component_ticks: self + component_ticks: fetch .sparse_set .unwrap_or_else(|| debug_checked_unreachable()) .get_ticks(entity) @@ -1298,48 +1245,68 @@ unsafe impl<'w, T: Component> Fetch<'w> for ChangeTrackersFetch<'w, T> { .cloned() .unwrap_or_else(|| debug_checked_unreachable()), marker: PhantomData, - last_change_tick: self.last_change_tick, - change_tick: self.change_tick, + last_change_tick: fetch.last_change_tick, + change_tick: fetch.change_tick, } } } } #[inline] - unsafe fn table_fetch(&mut self, table_row: usize) -> Self::Item { + unsafe fn table_fetch<'w>( + fetch: &mut >::Fetch, + table_row: usize, + ) -> >::Item { ChangeTrackers { component_ticks: { - let table_ticks = self + let table_ticks = fetch .table_ticks .unwrap_or_else(|| debug_checked_unreachable()); table_ticks.get(table_row).read() }, marker: PhantomData, - last_change_tick: self.last_change_tick, - change_tick: self.change_tick, + last_change_tick: fetch.last_change_tick, + change_tick: fetch.change_tick, } } - fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { + fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess) { assert!( - !access.access().has_write(state.component_id), + !access.access().has_write(id), "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(id); } fn update_archetype_component_access( - state: &Self::State, + &id: &ComponentId, 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(id) { access.add_read(archetype_component_id); } } + + fn init_state(world: &mut World) -> ComponentId { + world.init_component::() + } + + fn matches_component_set( + &id: &ComponentId, + set_contains_id: &impl Fn(ComponentId) -> bool, + ) -> bool { + set_contains_id(id) + } +} + +/// SAFETY: access is read only +unsafe impl ReadOnlyWorldQuery for ChangeTrackers {} + +impl<'w, T: Component> WorldQueryGats<'w> for ChangeTrackers { + type Fetch = ChangeTrackersFetch<'w, T>; + type Item = ChangeTrackers; } macro_rules! impl_tuple_fetch { @@ -1348,19 +1315,27 @@ macro_rules! impl_tuple_fetch { #[allow(non_snake_case)] impl<'w, $($name: WorldQueryGats<'w>),*> WorldQueryGats<'w> for ($($name,)*) { type Fetch = ($($name::Fetch,)*); - type _State = ($($name::_State,)*); + type Item = ($($name::Item,)*); } #[allow(non_snake_case)] - // SAFETY: update_component_access and update_archetype_component_access are called for each item in the tuple - unsafe impl<'w, $($name: Fetch<'w>),*> Fetch<'w> for ($($name,)*) { - type Item = ($($name::Item,)*); + #[allow(clippy::unused_unit)] + // SAFETY: defers to soundness `$name: WorldQuery` impl + unsafe impl<$($name: WorldQuery),*> WorldQuery for ($($name,)*) { + type ReadOnly = ($($name::ReadOnly,)*); type State = ($($name::State,)*); + fn shrink<'wlong: 'wshort, 'wshort>(item: QueryItem<'wlong, Self>) -> QueryItem<'wshort, Self> { + let ($($name,)*) = item; + ($( + $name::shrink($name), + )*) + } + #[allow(clippy::unused_unit)] - unsafe fn init(_world: &'w World, state: &Self::State, _last_change_tick: u32, _change_tick: u32) -> Self { + unsafe fn init_fetch<'w>(_world: &'w World, state: &Self::State, _last_change_tick: u32, _change_tick: u32) -> >::Fetch { let ($($name,)*) = state; - ($($name::init(_world, $name, _last_change_tick, _change_tick),)*) + ($($name::init_fetch(_world, $name, _last_change_tick, _change_tick),)*) } const IS_DENSE: bool = true $(&& $name::IS_DENSE)*; @@ -1368,45 +1343,45 @@ macro_rules! impl_tuple_fetch { const IS_ARCHETYPAL: bool = true $(&& $name::IS_ARCHETYPAL)*; #[inline] - unsafe fn set_archetype(&mut self, _state: &Self::State, _archetype: &'w Archetype, _tables: &'w Tables) { - let ($($name,)*) = self; + unsafe fn set_archetype<'w>(_fetch: &mut >::Fetch, _state: &Self::State, _archetype: &'w Archetype, _tables: &'w Tables) { + let ($($name,)*) = _fetch; let ($($state,)*) = _state; - $($name.set_archetype($state, _archetype, _tables);)* + $($name::set_archetype($name, $state, _archetype, _tables);)* } #[inline] - unsafe fn set_table(&mut self, _state: &Self::State, _table: &'w Table) { - let ($($name,)*) = self; + unsafe fn set_table<'w>(_fetch: &mut >::Fetch, _state: &Self::State, _table: &'w Table) { + let ($($name,)*) = _fetch; let ($($state,)*) = _state; - $($name.set_table($state, _table);)* + $($name::set_table($name, $state, _table);)* } #[inline] #[allow(clippy::unused_unit)] - unsafe fn table_fetch(&mut self, _table_row: usize) -> Self::Item { - let ($($name,)*) = self; - ($($name.table_fetch(_table_row),)*) + unsafe fn table_fetch<'w>(_fetch: &mut >::Fetch, _table_row: usize) -> QueryItem<'w, Self> { + let ($($name,)*) = _fetch; + ($($name::table_fetch($name, _table_row),)*) } #[inline] #[allow(clippy::unused_unit)] - unsafe fn archetype_fetch(&mut self, _archetype_index: usize) -> Self::Item { - let ($($name,)*) = self; - ($($name.archetype_fetch(_archetype_index),)*) + unsafe fn archetype_fetch<'w>(_fetch: &mut >::Fetch, _archetype_index: usize) -> QueryItem<'w, Self> { + let ($($name,)*) = _fetch; + ($($name::archetype_fetch($name, _archetype_index),)*) } #[allow(unused_variables)] #[inline] - unsafe fn table_filter_fetch(&mut self, table_row: usize) -> bool { - let ($($name,)*) = self; - true $(&& $name.table_filter_fetch(table_row))* + unsafe fn table_filter_fetch(_fetch: &mut QueryFetch<'_, Self>, table_row: usize) -> bool { + let ($($name,)*) = _fetch; + true $(&& $name::table_filter_fetch($name, table_row))* } #[allow(unused_variables)] #[inline] - unsafe fn archetype_filter_fetch(&mut self, archetype_index: usize) -> bool { - let ($($name,)*) = self; - true $(&& $name.archetype_filter_fetch(archetype_index))* + unsafe fn archetype_filter_fetch(_fetch: &mut QueryFetch<'_, Self>, archetype_index: usize) -> bool { + let ($($name,)*) = _fetch; + true $(&& $name::archetype_filter_fetch($name, archetype_index))* } fn update_component_access(state: &Self::State, _access: &mut FilteredAccess) { @@ -1418,33 +1393,15 @@ macro_rules! impl_tuple_fetch { let ($($name,)*) = state; $($name::update_archetype_component_access($name, _archetype, _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 matches_component_set(&self, _set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { - let ($($name,)*) = self; - true $(&& $name.matches_component_set(_set_contains_id))* + fn init_state(_world: &mut World) -> Self::State { + ($($name::init_state(_world),)*) } - } - - #[allow(non_snake_case)] - #[allow(clippy::unused_unit)] - // SAFETY: defers to soundness `$name: WorldQuery` impl - unsafe impl<$($name: WorldQuery),*> WorldQuery for ($($name,)*) { - type ReadOnly = ($($name::ReadOnly,)*); - type State = ($($name::State,)*); - fn shrink<'wlong: 'wshort, 'wshort>(item: QueryItem<'wlong, Self>) -> QueryItem<'wshort, Self> { - let ($($name,)*) = item; - ($( - $name::shrink($name), - )*) + 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))* } } @@ -1460,27 +1417,35 @@ macro_rules! impl_tuple_fetch { /// Each of the components in `T` is returned as an `Option`, as with `Option` queries. /// Entities are guaranteed to have at least one of the components in `T`. #[derive(Clone)] -pub struct AnyOf(T); +pub struct AnyOf(PhantomData); macro_rules! impl_anytuple_fetch { ($(($name: ident, $state: ident)),*) => { #[allow(unused_variables)] #[allow(non_snake_case)] impl<'w, $($name: WorldQueryGats<'w>),*> WorldQueryGats<'w> for AnyOf<($($name,)*)> { - type Fetch = AnyOf<($(($name::Fetch, bool),)*)>; - type _State = AnyOf<($($name::_State,)*)>; + type Fetch = ($(($name::Fetch, bool),)*); + type Item = ($(Option<$name::Item>,)*); } #[allow(non_snake_case)] - // SAFETY: update_component_access and update_archetype_component_access are called for each item in the tuple - unsafe impl<'w, $($name: Fetch<'w>),*> Fetch<'w> for AnyOf<($(($name, bool),)*)> { - type Item = ($(Option<$name::Item>,)*); - type State = AnyOf<($($name::State,)*)>; + #[allow(clippy::unused_unit)] + // SAFETY: defers to soundness of `$name: WorldQuery` impl + unsafe impl<$($name: WorldQuery),*> WorldQuery for AnyOf<($($name,)*)> { + type ReadOnly = AnyOf<($($name::ReadOnly,)*)>; + type State = ($($name::State,)*); + + fn shrink<'wlong: 'wshort, 'wshort>(item: QueryItem<'wlong, Self>) -> QueryItem<'wshort, Self> { + let ($($name,)*) = item; + ($( + $name.map($name::shrink), + )*) + } #[allow(clippy::unused_unit)] - unsafe fn init(_world: &'w World, state: &Self::State, _last_change_tick: u32, _change_tick: u32) -> Self { - let ($($name,)*) = &state.0; - AnyOf(($(($name::init(_world, $name, _last_change_tick, _change_tick), false),)*)) + unsafe fn init_fetch<'w>(_world: &'w World, state: &Self::State, _last_change_tick: u32, _change_tick: u32) -> >::Fetch { + let ($($name,)*) = state; + ($(($name::init_fetch(_world, $name, _last_change_tick, _change_tick), false),)*) } const IS_DENSE: bool = true $(&& $name::IS_DENSE)*; @@ -1488,49 +1453,49 @@ macro_rules! impl_anytuple_fetch { const IS_ARCHETYPAL: bool = true $(&& $name::IS_ARCHETYPAL)*; #[inline] - unsafe fn set_archetype(&mut self, _state: &Self::State, _archetype: &'w Archetype, _tables: &'w Tables) { - let ($($name,)*) = &mut self.0; - let ($($state,)*) = &_state.0; + unsafe fn set_archetype<'w>(_fetch: &mut >::Fetch, _state: &Self::State, _archetype: &'w Archetype, _tables: &'w Tables) { + let ($($name,)*) = _fetch; + let ($($state,)*) = _state; $( - $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); + $name::set_archetype(&mut $name.0, $state, _archetype, _tables); } )* } #[inline] - unsafe fn set_table(&mut self, _state: &Self::State, _table: &'w Table) { - let ($($name,)*) = &mut self.0; - let ($($state,)*) = &_state.0; + unsafe fn set_table<'w>(_fetch: &mut >::Fetch, _state: &Self::State, _table: &'w Table) { + let ($($name,)*) = _fetch; + let ($($state,)*) = _state; $( - $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); + $name::set_table(&mut $name.0, $state, _table); } )* } #[inline] #[allow(clippy::unused_unit)] - unsafe fn table_fetch(&mut self, _table_row: usize) -> Self::Item { - let ($($name,)*) = &mut self.0; + unsafe fn table_fetch<'w>(_fetch: &mut >::Fetch, _table_row: usize) -> QueryItem<'w, Self> { + let ($($name,)*) = _fetch; ($( - $name.1.then(|| $name.0.table_fetch(_table_row)), + $name.1.then(|| $name::table_fetch(&mut $name.0, _table_row)), )*) } #[inline] #[allow(clippy::unused_unit)] - unsafe fn archetype_fetch(&mut self, _archetype_index: usize) -> Self::Item { - let ($($name,)*) = &mut self.0; + unsafe fn archetype_fetch<'w>(_fetch: &mut >::Fetch, _archetype_index: usize) -> QueryItem<'w, Self> { + let ($($name,)*) = _fetch; ($( - $name.1.then(|| $name.0.archetype_fetch(_archetype_index)), + $name.1.then(|| $name::archetype_fetch(&mut $name.0, _archetype_index)), )*) } fn update_component_access(state: &Self::State, _access: &mut FilteredAccess) { - let ($($name,)*) = &state.0; + let ($($name,)*) = state; // We do not unconditionally add `$name`'s `with`/`without` accesses to `_access` // as this would be unsound. For example the following two queries should conflict: @@ -1563,40 +1528,21 @@ macro_rules! impl_anytuple_fetch { } fn update_archetype_component_access(state: &Self::State, _archetype: &Archetype, _access: &mut Access) { - let ($($name,)*) = &state.0; + let ($($name,)*) = state; $( - 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))* + fn init_state(_world: &mut World) -> Self::State { + ($($name::init_state(_world),)*) } - } - - #[allow(non_snake_case)] - #[allow(clippy::unused_unit)] - // SAFETY: defers to soundness of `$name: WorldQuery` impl - unsafe impl<$($name: WorldQuery),*> WorldQuery for AnyOf<($($name,)*)> { - type ReadOnly = AnyOf<($($name::ReadOnly,)*)>; - type State = AnyOf<($($name::State,)*)>; - fn shrink<'wlong: 'wshort, 'wshort>(item: QueryItem<'wlong, Self>) -> QueryItem<'wshort, Self> { - let ($($name,)*) = item; - ($( - $name.map($name::shrink), - )*) + fn matches_component_set(_state: &Self::State, _set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { + let ($($name,)*) = _state; + false $(|| $name::matches_component_set($name, _set_contains_id))* } } @@ -1620,64 +1566,70 @@ unsafe impl WorldQuery for NopWorldQuery { type State = Q::State; fn shrink<'wlong: 'wshort, 'wshort>(_: ()) {} -} -impl<'a, Q: WorldQuery> WorldQueryGats<'a> for NopWorldQuery { - type Fetch = NopFetch>; - type _State = >::_State; -} -/// SAFETY: `NopFetch` never accesses any data -unsafe impl ReadOnlyWorldQuery for NopWorldQuery {} - -/// [`Fetch`] that does not actually fetch anything -/// -/// Mostly useful when something is generic over the Fetch and you don't want to fetch as you will discard the result -pub struct NopFetch { - state: PhantomData, -} - -// SAFETY: NopFetch doesnt access anything -unsafe impl<'w, F: Fetch<'w>> Fetch<'w> for NopFetch { - type Item = (); - type State = F::State; - const IS_DENSE: bool = F::IS_DENSE; + const IS_DENSE: bool = Q::IS_DENSE; const IS_ARCHETYPAL: bool = true; #[inline(always)] - unsafe fn init( - _world: &'w World, - _state: &F::State, + unsafe fn init_fetch( + _world: &World, + _state: &Q::State, _last_change_tick: u32, _change_tick: u32, - ) -> Self { - Self { state: PhantomData } + ) { } #[inline(always)] unsafe fn set_archetype( - &mut self, - _state: &Self::State, + _fetch: &mut (), + _state: &Q::State, _archetype: &Archetype, _tables: &Tables, ) { } #[inline(always)] - unsafe fn set_table(&mut self, _state: &Self::State, _table: &Table) {} + unsafe fn set_table<'w>(_fetch: &mut (), _state: &Q::State, _table: &Table) {} #[inline(always)] - unsafe fn archetype_fetch(&mut self, _archetype_index: usize) -> Self::Item {} + unsafe fn archetype_fetch<'w>( + _fetch: &mut >::Fetch, + _archetype_index: usize, + ) -> >::Item { + } #[inline(always)] - unsafe fn table_fetch(&mut self, _table_row: usize) -> Self::Item {} + unsafe fn table_fetch<'w>( + _fetch: &mut (), + _table_row: usize, + ) -> >::Item { + } - fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) {} + fn update_component_access(_state: &Q::State, _access: &mut FilteredAccess) {} fn update_archetype_component_access( - _state: &Self::State, + _state: &Q::State, _archetype: &Archetype, _access: &mut Access, ) { } + + fn init_state(world: &mut World) -> Self::State { + Q::init_state(world) + } + + fn matches_component_set( + state: &Self::State, + set_contains_id: &impl Fn(ComponentId) -> bool, + ) -> bool { + Q::matches_component_set(state, set_contains_id) + } } + +impl<'a, Q: WorldQuery> WorldQueryGats<'a> for NopWorldQuery { + type Fetch = (); + type Item = (); +} +/// SAFETY: `NopFetch` never accesses any data +unsafe impl ReadOnlyWorldQuery for NopWorldQuery {} diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index 23392fd49db777..960c6d4bcd7f53 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -3,8 +3,7 @@ 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, FilteredAccess, QueryFetch, WorldQuery, WorldQueryGats, }, storage::{ComponentSparseSet, Table, Tables}, world::World, @@ -44,65 +43,27 @@ use super::ReadOnlyWorldQuery; /// ``` pub struct With(PhantomData); +impl WorldQueryGats<'_> for With { + type Fetch = (); + type Item = (); +} + // 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>( - item: super::QueryItem<'wlong, Self>, - ) -> super::QueryItem<'wshort, Self> { - item + _: >::Item, + ) -> >::Item { } -} -/// The [`Fetch`] of [`With`]. -#[doc(hidden)] -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; -} - -// SAFETY: no component access or archetype component access -unsafe impl<'w, T: Component> Fetch<'w> for WithFetch { - type Item = (); - type State = WithState; - - unsafe fn init( + unsafe fn init_fetch( _world: &World, - _state: &WithState, + _state: &ComponentId, _last_change_tick: u32, _change_tick: u32, - ) -> Self { - Self { - marker: PhantomData, - } + ) { } const IS_DENSE: bool = { @@ -115,49 +76,58 @@ unsafe impl<'w, T: Component> Fetch<'w> for WithFetch { const IS_ARCHETYPAL: bool = true; #[inline] - unsafe fn set_table(&mut self, _state: &Self::State, _table: &Table) {} + unsafe fn set_table(_fetch: &mut (), _state: &ComponentId, _table: &Table) {} #[inline] unsafe fn set_archetype( - &mut self, - _state: &Self::State, + _fetch: &mut (), + _state: &ComponentId, _archetype: &Archetype, _tables: &Tables, ) { } #[inline] - unsafe fn archetype_fetch(&mut self, _archetype_index: usize) {} + unsafe fn archetype_fetch<'w>( + _fetch: &mut >::Fetch, + _archetype_index: usize, + ) -> >::Item { + } #[inline] - unsafe fn table_fetch(&mut self, _table_row: usize) {} + unsafe fn table_fetch<'w>( + _fetch: &mut >::Fetch, + _table_row: usize, + ) -> >::Item { + } #[inline] - fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { - access.add_with(state.component_id); + fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess) { + access.add_with(id); } #[inline] fn update_archetype_component_access( - _state: &Self::State, + _state: &ComponentId, _archetype: &Archetype, _access: &mut Access, ) { } -} -// SAFETY: no component access or archetype component access -unsafe impl ReadOnlyWorldQuery for With {} + fn init_state(world: &mut World) -> ComponentId { + world.init_component::() + } -impl Clone for WithFetch { - fn clone(&self) -> Self { - Self { - marker: self.marker, - } + fn matches_component_set( + &id: &ComponentId, + set_contains_id: &impl Fn(ComponentId) -> bool, + ) -> bool { + set_contains_id(id) } } -impl Copy for WithFetch {} +// SAFETY: no component access or archetype component access +unsafe impl ReadOnlyWorldQuery for With {} /// Filter that selects entities without a component `T`. /// @@ -188,62 +158,19 @@ 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>( - item: super::QueryItem<'wlong, Self>, - ) -> super::QueryItem<'wshort, Self> { - item - } -} - -/// The [`Fetch`] of [`Without`]. -#[doc(hidden)] -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, - } + _: >::Item, + ) -> >::Item { } - 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; -} - -// SAFETY: no component access or archetype component access -unsafe impl<'w, T: Component> Fetch<'w> for WithoutFetch { - type Item = (); - type State = WithoutState; - - unsafe fn init( + unsafe fn init_fetch( _world: &World, - _state: &WithoutState, + _state: &ComponentId, _last_change_tick: u32, _change_tick: u32, - ) -> Self { - WithoutFetch { - marker: PhantomData, - } + ) { } const IS_DENSE: bool = { @@ -256,49 +183,63 @@ unsafe impl<'w, T: Component> Fetch<'w> for WithoutFetch { const IS_ARCHETYPAL: bool = true; #[inline] - unsafe fn set_table(&mut self, _state: &Self::State, _table: &Table) {} + unsafe fn set_table(_fetch: &mut (), _state: &Self::State, _table: &Table) {} #[inline] unsafe fn set_archetype( - &mut self, - _state: &Self::State, + _fetch: &mut (), + _state: &ComponentId, _archetype: &Archetype, _tables: &Tables, ) { } #[inline] - unsafe fn archetype_fetch(&mut self, _archetype_index: usize) {} + unsafe fn archetype_fetch<'w>( + _fetch: &mut >::Fetch, + _archetype_index: usize, + ) -> >::Item { + } #[inline] - unsafe fn table_fetch(&mut self, _table_row: usize) {} + unsafe fn table_fetch<'w>( + _fetch: &mut >::Fetch, + _table_row: usize, + ) -> >::Item { + } #[inline] - fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { - access.add_without(state.component_id); + fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess) { + access.add_without(id); } #[inline] fn update_archetype_component_access( - _state: &Self::State, + _state: &ComponentId, _archetype: &Archetype, _access: &mut Access, ) { } -} -// SAFETY: no component access or archetype component access -unsafe impl ReadOnlyWorldQuery for Without {} + fn init_state(world: &mut World) -> ComponentId { + world.init_component::() + } -impl Clone for WithoutFetch { - fn clone(&self) -> Self { - Self { - marker: self.marker, - } + fn matches_component_set( + &id: &ComponentId, + set_contains_id: &impl Fn(ComponentId) -> bool, + ) -> bool { + !set_contains_id(id) } } -impl Copy for WithoutFetch {} +impl WorldQueryGats<'_> for Without { + type Fetch = (); + type Item = (); +} + +// SAFETY: no component access or archetype component access +unsafe impl ReadOnlyWorldQuery for Without {} /// A filter that tests if any of the given filters apply. /// @@ -333,104 +274,106 @@ impl Copy for WithoutFetch {} #[derive(Clone, Copy)] pub struct Or(pub T); -/// The [`Fetch`] of [`Or`]. -#[derive(Clone, Copy)] #[doc(hidden)] -pub struct OrFetch<'w, T: Fetch<'w>> { - fetch: T, +pub struct OrFetch<'w, T: WorldQuery> { + fetch: QueryFetch<'w, T>, matches: bool, - _marker: PhantomData<&'w ()>, +} +impl<'w, T: WorldQuery> Copy for OrFetch<'w, T> where QueryFetch<'w, T>: Copy {} +impl<'w, T: WorldQuery> Clone for OrFetch<'w, T> +where + QueryFetch<'w, T>: Clone, +{ + fn clone(&self) -> Self { + Self { + fetch: self.fetch.clone(), + matches: self.matches, + } + } } macro_rules! impl_query_filter_tuple { ($(($filter: ident, $state: ident)),*) => { #[allow(unused_variables)] #[allow(non_snake_case)] + impl<'w, $($filter: WorldQuery),*> WorldQueryGats<'w> for Or<($($filter,)*)> { + type Fetch = ($(OrFetch<'w, $filter>,)*); + type Item = bool; + } + + + #[allow(unused_variables)] + #[allow(non_snake_case)] + #[allow(clippy::unused_unit)] // SAFETY: defers to soundness of `$filter: WorldQuery` impl unsafe impl<$($filter: WorldQuery),*> WorldQuery for Or<($($filter,)*)> { type ReadOnly = Or<($($filter::ReadOnly,)*)>; - type State = Or<($($filter::State,)*)>; + type State = ($($filter::State,)*); fn shrink<'wlong: 'wshort, 'wshort>(item: super::QueryItem<'wlong, Self>) -> super::QueryItem<'wshort, Self> { item } - } - - #[allow(unused_variables)] - #[allow(non_snake_case)] - impl<'w, $($filter: WorldQueryGats<'w>),*> WorldQueryGats<'w> for Or<($($filter,)*)> { - type Fetch = Or<($(OrFetch<'w, QueryFetch<'w, $filter>>,)*)>; - type _State = Or<($($filter::_State,)*)>; - } - - #[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 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 { - let ($($filter,)*) = &state.0; - Or(($(OrFetch { - fetch: <$filter as Fetch<'w>>::init(world, $filter, last_change_tick, change_tick), + unsafe fn init_fetch<'w>(world: &'w World, state: &Self::State, last_change_tick: u32, change_tick: u32) -> >::Fetch { + let ($($filter,)*) = state; + ($(OrFetch { + fetch: $filter::init_fetch(world, $filter, last_change_tick, change_tick), matches: false, - _marker: PhantomData, - },)*)) + },)*) } #[inline] - unsafe fn set_table(&mut self, state: &Self::State, table: &'w Table) { - let ($($filter,)*) = &mut self.0; - let ($($state,)*) = &state.0; + unsafe fn set_table<'w>(fetch: &mut >::Fetch, state: &Self::State, table: &'w Table) { + let ($($filter,)*) = fetch; + let ($($state,)*) = state; $( - $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); + $filter::set_table(&mut $filter.fetch, $state, table); } )* } #[inline] - unsafe fn set_archetype(&mut self, state: & Self::State, archetype: &'w Archetype, tables: &'w Tables) { - let ($($filter,)*) = &mut self.0; - let ($($state,)*) = &state.0; + unsafe fn set_archetype<'w>(fetch: &mut >::Fetch, state: &Self::State, archetype: &'w Archetype, tables: &'w Tables) { + let ($($filter,)*) = fetch; + let ($($state,)*) = state; $( - $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); + $filter::set_archetype(&mut $filter.fetch, $state, archetype, tables); } )* } #[inline] - unsafe fn table_fetch(&mut self, table_row: usize) -> bool { - let ($($filter,)*) = &mut self.0; - false $(|| ($filter.matches && $filter.fetch.table_filter_fetch(table_row)))* + unsafe fn table_fetch<'w>(fetch: &mut >::Fetch, table_row: usize) -> >::Item { + let ($($filter,)*) = fetch; + false $(|| ($filter.matches && $filter::table_filter_fetch(&mut $filter.fetch, table_row)))* } #[inline] - unsafe fn archetype_fetch(&mut self, archetype_index: usize) -> bool { - let ($($filter,)*) = &mut self.0; - false $(|| ($filter.matches && $filter.fetch.archetype_filter_fetch(archetype_index)))* + unsafe fn archetype_fetch<'w>(fetch: &mut >::Fetch, archetype_index: usize) -> >::Item { + let ($($filter,)*) = fetch; + false $(|| ($filter.matches && $filter::archetype_filter_fetch(&mut $filter.fetch, archetype_index)))* } #[inline] - unsafe fn table_filter_fetch(&mut self, table_row: usize) -> bool { - self.table_fetch(table_row) + unsafe fn table_filter_fetch(fetch: &mut QueryFetch<'_, Self>, table_row: usize) -> bool { + Self::table_fetch(fetch, table_row) } #[inline] - unsafe fn archetype_filter_fetch(&mut self, archetype_index: usize) -> bool { - self.archetype_fetch(archetype_index) + unsafe fn archetype_filter_fetch(fetch: &mut QueryFetch<'_, Self>, archetype_index: usize) -> bool { + Self::archetype_fetch(fetch, archetype_index) } fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { - let ($($filter,)*) = &state.0; + let ($($filter,)*) = state; // We do not unconditionally add `$filter`'s `with`/`without` accesses to `access` // as this would be unsound. For example the following two queries should conflict: @@ -462,21 +405,17 @@ macro_rules! impl_query_filter_tuple { } fn update_archetype_component_access(state: &Self::State, archetype: &Archetype, access: &mut Access) { - let ($($filter,)*) = &state.0; + let ($($filter,)*) = state; $($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 init_state(world: &mut World) -> Self::State { + ($($filter::init_state(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; + false $(|| $filter::matches_component_set($filter, _set_contains_id))* } } @@ -491,8 +430,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 +449,22 @@ 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; - } - - // 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 Item = bool; - unsafe fn init(world: &'w World, state: & $state_name, last_change_tick: u32, change_tick: u32) -> Self { - Self { + unsafe fn init_fetch<'w>(world: &'w World, &id: &ComponentId, last_change_tick: u32, change_tick: u32) -> >::Fetch { + QueryFetch::<'w, 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(id).unwrap()), marker: PhantomData, last_change_tick, change_tick, @@ -574,74 +480,87 @@ 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()); + unsafe fn set_table<'w>(fetch: &mut >::Fetch, &id: &ComponentId, table: &'w Table) { + fetch.table_ticks = Some(table.get_column(id).unwrap().get_ticks_slice().into()); } - unsafe fn set_archetype(&mut self, state: &Self::State, archetype: &'w Archetype, tables: &'w Tables) { + unsafe fn set_archetype<'w>(fetch: &mut >::Fetch, &id: &ComponentId, archetype: &'w Archetype, tables: &'w Tables) { match T::Storage::STORAGE_TYPE { StorageType::Table => { - self.entity_table_rows = Some(archetype.entity_table_rows().into()); + fetch.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()); + fetch.table_ticks = Some(table.get_column(id).unwrap().get_ticks_slice().into()); } - StorageType::SparseSet => self.entities = Some(archetype.entities().into()), + StorageType::SparseSet => fetch.entities = Some(archetype.entities().into()), } } - unsafe fn table_fetch(&mut self, table_row: usize) -> bool { - $is_detected(&*(self.table_ticks.unwrap_or_else(|| debug_checked_unreachable()).get(table_row)).deref(), self.last_change_tick, self.change_tick) + unsafe fn table_fetch<'w>(fetch: &mut >::Fetch, table_row: usize) -> >::Item { + $is_detected(&*(fetch.table_ticks.unwrap_or_else(|| debug_checked_unreachable()).get(table_row)).deref(), fetch.last_change_tick, fetch.change_tick) } - unsafe fn archetype_fetch(&mut self, archetype_index: usize) -> bool { + unsafe fn archetype_fetch<'w>(fetch: &mut >::Fetch, archetype_index: usize) -> >::Item { match T::Storage::STORAGE_TYPE { StorageType::Table => { - let table_row = *self.entity_table_rows.unwrap_or_else(|| debug_checked_unreachable()).get(archetype_index); - $is_detected(&*(self.table_ticks.unwrap_or_else(|| debug_checked_unreachable()).get(table_row)).deref(), self.last_change_tick, self.change_tick) + let table_row = *fetch.entity_table_rows.unwrap_or_else(|| debug_checked_unreachable()).get(archetype_index); + $is_detected(&*(fetch.table_ticks.unwrap_or_else(|| debug_checked_unreachable()).get(table_row)).deref(), fetch.last_change_tick, fetch.change_tick) } StorageType::SparseSet => { - let entity = *self.entities.unwrap_or_else(|| debug_checked_unreachable()).get(archetype_index); - let ticks = self + let entity = *fetch.entities.unwrap_or_else(|| debug_checked_unreachable()).get(archetype_index); + let ticks = fetch .sparse_set .unwrap_or_else(|| debug_checked_unreachable()) .get_ticks(entity) .map(|ticks| &*ticks.get()) .cloned() .unwrap(); - $is_detected(&ticks, self.last_change_tick, self.change_tick) + $is_detected(&ticks, fetch.last_change_tick, fetch.change_tick) } } } #[inline] - unsafe fn table_filter_fetch(&mut self, table_row: usize) -> bool { - self.table_fetch(table_row) + unsafe fn table_filter_fetch(fetch: &mut QueryFetch<'_, Self>, table_row: usize) -> bool { + Self::table_fetch(fetch, table_row) } #[inline] - unsafe fn archetype_filter_fetch(&mut self, archetype_index: usize) -> bool { - self.archetype_fetch(archetype_index) + unsafe fn archetype_filter_fetch(fetch: &mut QueryFetch<'_, Self>, archetype_index: usize) -> bool { + Self::archetype_fetch(fetch, archetype_index) } #[inline] - fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { - if access.access().has_write(state.component_id) { + fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess) { + if access.access().has_write(id) { panic!("$state_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(id); } #[inline] fn update_archetype_component_access( - state: &Self::State, + &id: &ComponentId, 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(id) { access.add_read(archetype_component_id); } } + + fn init_state(world: &mut World) -> ComponentId { + world.init_component::() + } + + fn matches_component_set(&id: &ComponentId, set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { + set_contains_id(id) + } + } + + impl<'w, T: Component> WorldQueryGats<'w> for $name { + type Fetch = $fetch_name<'w, T>; + type Item = bool; } /// SAFETY: read-only access @@ -693,9 +612,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,9 +649,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 ); @@ -745,7 +658,7 @@ impl_tick_filter!( /// This is needed to implement [`ExactSizeIterator`](std::iter::ExactSizeIterator) for /// [`QueryIter`](crate::query::QueryIter) that contains archetype-level filters. /// -/// The trait must only be implement for filters where its corresponding [`Fetch::IS_ARCHETYPAL`](crate::query::Fetch::IS_ARCHETYPAL) +/// The trait must only be implement for filters where its corresponding [`WorldQuery::IS_ARCHETYPAL`](crate::query::WorldQuery::IS_ARCHETYPAL) /// is [`prim@true`]. As such, only the [`With`] and [`Without`] filters can implement the trait. /// [Tuples](prim@tuple) and [`Or`] filters are automatically implemented with the trait only if its containing types /// also implement the same trait. diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index b75c73cbbe83bf..5aef8ba2e12c3a 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -2,7 +2,7 @@ use crate::{ archetype::{ArchetypeId, Archetypes}, entity::{Entities, Entity}, prelude::World, - query::{ArchetypeFilter, Fetch, QueryState, WorldQuery}, + query::{ArchetypeFilter, QueryState, WorldQuery}, storage::{TableId, Tables}, }; use std::{borrow::Borrow, iter::FusedIterator, marker::PhantomData, mem::MaybeUninit}; @@ -63,7 +63,7 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> Iterator for QueryIter<'w, 's, Q, F> .map(|id| self.archetypes[*id].len()) .sum(); - let archetype_query = Q::Fetch::IS_ARCHETYPAL && F::Fetch::IS_ARCHETYPAL; + let archetype_query = Q::IS_ARCHETYPAL && F::IS_ARCHETYPAL; let min_size = if archetype_query { max_size } else { 0 }; (min_size, Some(max_size)) } @@ -104,13 +104,13 @@ where last_change_tick: u32, change_tick: u32, ) -> QueryManyIter<'w, 's, Q, F, I> { - let fetch = Q::Fetch::init( + let fetch = Q::init_fetch( world, &query_state.fetch_state, last_change_tick, change_tick, ); - let filter = F::Fetch::init( + let filter = F::init_fetch( world, &query_state.filter_state, last_change_tick, @@ -155,20 +155,28 @@ where // SAFETY: `archetype` is from the world that `fetch/filter` were created for, // `fetch_state`/`filter_state` are the states that `fetch/filter` were initialized with unsafe { - self.fetch - .set_archetype(&self.query_state.fetch_state, archetype, self.tables); + Q::set_archetype( + &mut self.fetch, + &self.query_state.fetch_state, + archetype, + self.tables, + ); } // SAFETY: `table` is from the world that `fetch/filter` were created for, // `fetch_state`/`filter_state` are the states that `fetch/filter` were initialized with unsafe { - self.filter - .set_archetype(&self.query_state.filter_state, archetype, self.tables); + F::set_archetype( + &mut self.filter, + &self.query_state.filter_state, + archetype, + self.tables, + ); } // SAFETY: set_archetype was called prior. // `location.index` is an archetype index row in range of the current archetype, because if it was not, the match above would have `continue`d - if unsafe { self.filter.archetype_filter_fetch(location.index) } { + if unsafe { F::archetype_filter_fetch(&mut self.filter, location.index) } { // SAFETY: set_archetype was called prior, `location.index` is an archetype index in range of the current archetype - return Some(unsafe { self.fetch.archetype_fetch(location.index) }); + return Some(unsafe { Q::archetype_fetch(&mut self.fetch, location.index) }); } } None @@ -342,7 +350,7 @@ where let smallest = K.min(max_size - K); let max_combinations = choose(max_size, smallest); - let archetype_query = F::Fetch::IS_ARCHETYPAL && Q::Fetch::IS_ARCHETYPAL; + let archetype_query = F::IS_ARCHETYPAL && Q::IS_ARCHETYPAL; let known_max = max_combinations.unwrap_or(usize::MAX); let min_combinations = if archetype_query { known_max } else { 0 }; (min_combinations, max_combinations) @@ -417,7 +425,7 @@ where } impl<'w, 's, Q: WorldQuery, F: WorldQuery> QueryIterationCursor<'w, 's, Q, F> { - const IS_DENSE: bool = Q::Fetch::IS_DENSE && F::Fetch::IS_DENSE; + const IS_DENSE: bool = Q::IS_DENSE && F::IS_DENSE; unsafe fn init_empty( world: &'w World, @@ -438,13 +446,13 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> QueryIterationCursor<'w, 's, Q, F> { last_change_tick: u32, change_tick: u32, ) -> Self { - let fetch = Q::Fetch::init( + let fetch = Q::init_fetch( world, &query_state.fetch_state, last_change_tick, change_tick, ); - let filter = F::Fetch::init( + let filter = F::init_fetch( world, &query_state.filter_state, last_change_tick, @@ -466,9 +474,9 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> QueryIterationCursor<'w, 's, Q, F> { unsafe fn peek_last(&mut self) -> Option> { if self.current_index > 0 { if Self::IS_DENSE { - Some(self.fetch.table_fetch(self.current_index - 1)) + Some(Q::table_fetch(&mut self.fetch, self.current_index - 1)) } else { - Some(self.fetch.archetype_fetch(self.current_index - 1)) + Some(Q::archetype_fetch(&mut self.fetch, self.current_index - 1)) } } else { None @@ -496,8 +504,8 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> QueryIterationCursor<'w, 's, Q, F> { let table = &tables[*table_id]; // SAFETY: `table` is from the world that `fetch/filter` were created for, // `fetch_state`/`filter_state` are the states that `fetch/filter` were initialized with - self.fetch.set_table(&query_state.fetch_state, table); - self.filter.set_table(&query_state.filter_state, table); + Q::set_table(&mut self.fetch, &query_state.fetch_state, table); + F::set_table(&mut self.filter, &query_state.filter_state, table); self.current_len = table.len(); self.current_index = 0; continue; @@ -505,14 +513,14 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> QueryIterationCursor<'w, 's, Q, F> { // SAFETY: set_table was called prior. // `current_index` is a table row in range of the current table, because if it was not, then the if above would have been executed. - if !self.filter.table_filter_fetch(self.current_index) { + if !F::table_filter_fetch(&mut self.filter, self.current_index) { self.current_index += 1; continue; } // SAFETY: set_table was called prior. // `current_index` is a table row in range of the current table, because if it was not, then the if above would have been executed. - let item = self.fetch.table_fetch(self.current_index); + let item = Q::table_fetch(&mut self.fetch, self.current_index); self.current_index += 1; return Some(item); @@ -524,10 +532,13 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> QueryIterationCursor<'w, 's, Q, F> { let archetype = &archetypes[*archetype_id]; // SAFETY: `archetype` and `tables` are from the world that `fetch/filter` were created for, // `fetch_state`/`filter_state` are the states that `fetch/filter` were initialized with - self.fetch - .set_archetype(&query_state.fetch_state, archetype, tables); - self.filter - .set_archetype(&query_state.filter_state, archetype, tables); + Q::set_archetype(&mut self.fetch, &query_state.fetch_state, archetype, tables); + F::set_archetype( + &mut self.filter, + &query_state.filter_state, + archetype, + tables, + ); self.current_len = archetype.len(); self.current_index = 0; continue; @@ -535,14 +546,14 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> QueryIterationCursor<'w, 's, Q, F> { // SAFETY: set_archetype was called prior. // `current_index` is an archetype index row in range of the current archetype, because if it was not, then the if above would have been executed. - if !self.filter.archetype_filter_fetch(self.current_index) { + if !F::archetype_filter_fetch(&mut self.filter, self.current_index) { self.current_index += 1; continue; } // SAFETY: set_archetype was called prior, `current_index` is an archetype index in range of the current archetype // `current_index` is an archetype index row in range of the current archetype, because if it was not, then the if above would have been executed. - let item = self.fetch.archetype_fetch(self.current_index); + let item = Q::archetype_fetch(&mut self.fetch, self.current_index); self.current_index += 1; return Some(item); } diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 41a833c6751a93..a744f64a5e5477 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -3,9 +3,7 @@ use crate::{ component::ComponentId, entity::Entity, prelude::FromWorld, - query::{ - Access, Fetch, FetchState, FilteredAccess, QueryCombinationIter, QueryIter, WorldQuery, - }, + query::{Access, FilteredAccess, QueryCombinationIter, QueryIter, WorldQuery}, storage::TableId, world::{World, WorldId}, }; @@ -15,7 +13,7 @@ use bevy_utils::tracing::Instrument; use fixedbitset::FixedBitSet; use std::{borrow::Borrow, fmt}; -use super::{NopWorldQuery, QueryFetch, QueryItem, QueryManyIter, ROQueryItem}; +use super::{NopWorldQuery, QueryItem, QueryManyIter, ROQueryItem}; /// Provides scoped access to a [`World`] state according to a given [`WorldQuery`] and query filter. #[repr(C)] @@ -84,20 +82,17 @@ impl 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::init_state(world); + let filter_state = F::init_state(world); let mut component_access = FilteredAccess::default(); - QueryFetch::<'static, Q>::update_component_access(&fetch_state, &mut component_access); + Q::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::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). @@ -160,19 +155,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::matches_component_set(&self.fetch_state, &|id| archetype.contains(id)) + && F::matches_component_set(&self.filter_state, &|id| archetype.contains(id)) { - QueryFetch::<'static, Q>::update_archetype_component_access( + Q::update_archetype_component_access( &self.fetch_state, archetype, &mut self.archetype_component_access, ); - QueryFetch::<'static, F>::update_archetype_component_access( + F::update_archetype_component_access( &self.filter_state, archetype, &mut self.archetype_component_access, @@ -408,19 +399,23 @@ impl QueryState { return Err(QueryEntityError::QueryDoesNotMatch(entity)); } let archetype = &world.archetypes[location.archetype_id]; - let mut fetch = - as Fetch>::init(world, &self.fetch_state, last_change_tick, change_tick); - let mut filter = as Fetch>::init( - world, + let mut fetch = Q::init_fetch(world, &self.fetch_state, last_change_tick, change_tick); + let mut filter = F::init_fetch(world, &self.filter_state, last_change_tick, change_tick); + + Q::set_archetype( + &mut fetch, + &self.fetch_state, + archetype, + &world.storages().tables, + ); + F::set_archetype( + &mut filter, &self.filter_state, - last_change_tick, - change_tick, + archetype, + &world.storages().tables, ); - - fetch.set_archetype(&self.fetch_state, archetype, &world.storages().tables); - filter.set_archetype(&self.filter_state, archetype, &world.storages().tables); - if filter.archetype_filter_fetch(location.index) { - Ok(fetch.archetype_fetch(location.index)) + if F::archetype_filter_fetch(&mut filter, location.index) { + Ok(Q::archetype_fetch(&mut fetch, location.index)) } else { Err(QueryEntityError::QueryDoesNotMatch(entity)) } @@ -910,27 +905,21 @@ impl QueryState { ) { // NOTE: If you are changing query iteration code, remember to update the following places, where relevant: // QueryIter, QueryIterationCursor, QueryState::for_each_unchecked_manual, QueryState::many_for_each_unchecked_manual, QueryState::par_for_each_unchecked_manual - let mut fetch = - as Fetch>::init(world, &self.fetch_state, last_change_tick, change_tick); - let mut filter = as Fetch>::init( - world, - &self.filter_state, - last_change_tick, - change_tick, - ); + let mut fetch = Q::init_fetch(world, &self.fetch_state, last_change_tick, change_tick); + let mut filter = F::init_fetch(world, &self.filter_state, last_change_tick, change_tick); - if >::IS_DENSE && >::IS_DENSE { + if Q::IS_DENSE && F::IS_DENSE { let tables = &world.storages().tables; for table_id in &self.matched_table_ids { let table = &tables[*table_id]; - fetch.set_table(&self.fetch_state, table); - filter.set_table(&self.filter_state, table); + Q::set_table(&mut fetch, &self.fetch_state, table); + F::set_table(&mut filter, &self.filter_state, table); for table_index in 0..table.len() { - if !filter.table_filter_fetch(table_index) { + if !F::table_filter_fetch(&mut filter, table_index) { continue; } - let item = fetch.table_fetch(table_index); + let item = Q::table_fetch(&mut fetch, table_index); func(item); } } @@ -939,14 +928,14 @@ impl QueryState { let tables = &world.storages().tables; for archetype_id in &self.matched_archetype_ids { let archetype = &archetypes[*archetype_id]; - fetch.set_archetype(&self.fetch_state, archetype, tables); - filter.set_archetype(&self.filter_state, archetype, tables); + Q::set_archetype(&mut fetch, &self.fetch_state, archetype, tables); + F::set_archetype(&mut filter, &self.filter_state, archetype, tables); for archetype_index in 0..archetype.len() { - if !filter.archetype_filter_fetch(archetype_index) { + if !F::archetype_filter_fetch(&mut filter, archetype_index) { continue; } - func(fetch.archetype_fetch(archetype_index)); + func(Q::archetype_fetch(&mut fetch, archetype_index)); } } } @@ -980,7 +969,7 @@ impl QueryState { // NOTE: If you are changing query iteration code, remember to update the following places, where relevant: // QueryIter, QueryIterationCursor, QueryState::for_each_unchecked_manual, QueryState::many_for_each_unchecked_manual, QueryState::par_for_each_unchecked_manual ComputeTaskPool::get().scope(|scope| { - if >::IS_DENSE && >::IS_DENSE { + if Q::IS_DENSE && F::IS_DENSE { let tables = &world.storages().tables; for table_id in &self.matched_table_ids { let table = &tables[*table_id]; @@ -989,13 +978,13 @@ impl QueryState { let func = func.clone(); let len = batch_size.min(table.len() - offset); let task = async move { - let mut fetch = as Fetch>::init( + let mut fetch = Q::init_fetch( world, &self.fetch_state, last_change_tick, change_tick, ); - let mut filter = as Fetch>::init( + let mut filter = F::init_fetch( world, &self.filter_state, last_change_tick, @@ -1003,13 +992,13 @@ impl QueryState { ); let tables = &world.storages().tables; let table = &tables[*table_id]; - fetch.set_table(&self.fetch_state, table); - filter.set_table(&self.filter_state, table); + Q::set_table(&mut fetch, &self.fetch_state, table); + F::set_table(&mut filter, &self.filter_state, table); for table_index in offset..offset + len { - if !filter.table_filter_fetch(table_index) { + if !F::table_filter_fetch(&mut filter, table_index) { continue; } - let item = fetch.table_fetch(table_index); + let item = Q::table_fetch(&mut fetch, table_index); func(item); } }; @@ -1035,13 +1024,13 @@ impl QueryState { let func = func.clone(); let len = batch_size.min(archetype.len() - offset); let task = async move { - let mut fetch = as Fetch>::init( + let mut fetch = Q::init_fetch( world, &self.fetch_state, last_change_tick, change_tick, ); - let mut filter = as Fetch>::init( + let mut filter = F::init_fetch( world, &self.filter_state, last_change_tick, @@ -1049,14 +1038,14 @@ impl QueryState { ); let tables = &world.storages().tables; let archetype = &world.archetypes[*archetype_id]; - fetch.set_archetype(&self.fetch_state, archetype, tables); - filter.set_archetype(&self.filter_state, archetype, tables); + Q::set_archetype(&mut fetch, &self.fetch_state, archetype, tables); + F::set_archetype(&mut filter, &self.filter_state, archetype, tables); for archetype_index in offset..offset + len { - if !filter.archetype_filter_fetch(archetype_index) { + if !F::archetype_filter_fetch(&mut filter, archetype_index) { continue; } - func(fetch.archetype_fetch(archetype_index)); + func(Q::archetype_fetch(&mut fetch, archetype_index)); } }; @@ -1100,14 +1089,8 @@ impl QueryState { { // NOTE: If you are changing query iteration code, remember to update the following places, where relevant: // QueryIter, QueryIterationCursor, QueryState::for_each_unchecked_manual, QueryState::many_for_each_unchecked_manual, QueryState::par_for_each_unchecked_manual - let mut fetch = - as Fetch>::init(world, &self.fetch_state, last_change_tick, change_tick); - let mut filter = as Fetch>::init( - world, - &self.filter_state, - last_change_tick, - change_tick, - ); + let mut fetch = Q::init_fetch(world, &self.fetch_state, last_change_tick, change_tick); + let mut filter = F::init_fetch(world, &self.filter_state, last_change_tick, change_tick); let tables = &world.storages.tables; @@ -1126,10 +1109,10 @@ impl QueryState { let archetype = &world.archetypes[location.archetype_id]; - fetch.set_archetype(&self.fetch_state, archetype, tables); - filter.set_archetype(&self.filter_state, archetype, tables); - if filter.archetype_filter_fetch(location.index) { - func(fetch.archetype_fetch(location.index)); + Q::set_archetype(&mut fetch, &self.fetch_state, archetype, tables); + F::set_archetype(&mut filter, &self.filter_state, archetype, tables); + if F::archetype_filter_fetch(&mut filter, location.index) { + func(Q::archetype_fetch(&mut fetch, location.index)); } } } 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 6245a562f3dc7c..95333f44b0dd43 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 @@ -25,7 +25,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 |