diff --git a/crates/bevy_ecs/macros/src/fetch.rs b/crates/bevy_ecs/macros/src/fetch.rs index c2d925a56b94d..03cfba5179c83 100644 --- a/crates/bevy_ecs/macros/src/fetch.rs +++ b/crates/bevy_ecs/macros/src/fetch.rs @@ -1,13 +1,11 @@ -use bevy_macro_utils::ensure_no_collision; use proc_macro::TokenStream; use proc_macro2::{Ident, Span}; -use quote::{format_ident, quote, ToTokens}; +use quote::{quote, ToTokens}; use syn::{ parse::{Parse, ParseStream}, - parse_macro_input, parse_quote, + parse_quote, punctuated::Punctuated, - token::Comma, - Attribute, Data, DataStruct, DeriveInput, Field, Index, Meta, + Attribute, Data, DataStruct, DeriveInput, Field, Fields, }; use crate::bevy_ecs_path; @@ -15,7 +13,7 @@ use crate::bevy_ecs_path; #[derive(Default)] struct FetchStructAttributes { pub is_mutable: bool, - pub derive_args: Punctuated, + pub derive_args: Punctuated, } static MUTABLE_ATTRIBUTE_NAME: &str = "mutable"; @@ -27,16 +25,13 @@ mod field_attr_keywords { pub static WORLD_QUERY_ATTRIBUTE_NAME: &str = "world_query"; -pub fn derive_world_query_impl(input: TokenStream) -> TokenStream { - let tokens = input.clone(); - - let ast = parse_macro_input!(input as DeriveInput); +pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream { let visibility = ast.vis; let mut fetch_struct_attributes = FetchStructAttributes::default(); for attr in &ast.attrs { if !attr - .path() + .path .get_ident() .map_or(false, |ident| ident == WORLD_QUERY_ATTRIBUTE_NAME) { @@ -44,7 +39,7 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream { } attr.parse_args_with(|input: ParseStream| { - let meta = input.parse_terminated(syn::Meta::parse, Comma)?; + let meta = input.parse_terminated::(syn::Meta::parse)?; for meta in meta { let ident = meta.path().get_ident().unwrap_or_else(|| { panic!( @@ -62,10 +57,9 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream { } } else if ident == DERIVE_ATTRIBUTE_NAME { if let syn::Meta::List(meta_list) = meta { - meta_list.parse_nested_meta(|meta| { - fetch_struct_attributes.derive_args.push(Meta::Path(meta.path)); - Ok(()) - })?; + fetch_struct_attributes + .derive_args + .extend(meta_list.nested.iter().cloned()); } else { panic!( "Expected a structured list within the `{DERIVE_ATTRIBUTE_NAME}` attribute", @@ -88,18 +82,17 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream { 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 = { - let mut generics = ast.generics; + let mut generics = ast.generics.clone(); generics.params.insert(0, parse_quote!('__w)); generics }; let (user_impl_generics_with_world, user_ty_generics_with_world, user_where_clauses_with_world) = user_generics_with_world.split_for_impl(); - let struct_name = ast.ident; + let struct_name = ast.ident.clone(); let read_only_struct_name = if fetch_struct_attributes.is_mutable { Ident::new(&format!("{struct_name}ReadOnly"), Span::call_site()) } else { - #[allow(clippy::redundant_clone)] struct_name.clone() }; @@ -107,66 +100,53 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream { let read_only_item_struct_name = if fetch_struct_attributes.is_mutable { Ident::new(&format!("{struct_name}ReadOnlyItem"), Span::call_site()) } else { - #[allow(clippy::redundant_clone)] item_struct_name.clone() }; let fetch_struct_name = Ident::new(&format!("{struct_name}Fetch"), Span::call_site()); - let fetch_struct_name = ensure_no_collision(fetch_struct_name, tokens.clone()); let read_only_fetch_struct_name = if fetch_struct_attributes.is_mutable { - let new_ident = Ident::new(&format!("{struct_name}ReadOnlyFetch"), Span::call_site()); - ensure_no_collision(new_ident, tokens.clone()) + Ident::new(&format!("{struct_name}ReadOnlyFetch"), Span::call_site()) } else { - #[allow(clippy::redundant_clone)] fetch_struct_name.clone() }; - let marker_name = - ensure_no_collision(format_ident!("_world_query_derive_marker"), tokens.clone()); - - // Generate a name for the state struct that doesn't conflict - // with the struct definition. let state_struct_name = Ident::new(&format!("{struct_name}State"), Span::call_site()); - let state_struct_name = ensure_no_collision(state_struct_name, tokens); - - let Data::Struct(DataStruct { fields, .. }) = &ast.data else { - return syn::Error::new( - Span::call_site(), - "#[derive(WorldQuery)]` only supports structs", - ) - .into_compile_error() - .into() + + let fields = match &ast.data { + Data::Struct(DataStruct { + fields: Fields::Named(fields), + .. + }) => &fields.named, + _ => panic!("Expected a struct with named fields"), }; + let mut ignored_field_attrs = Vec::new(); + let mut ignored_field_visibilities = Vec::new(); + let mut ignored_field_idents = Vec::new(); + let mut ignored_field_types = Vec::new(); let mut field_attrs = Vec::new(); let mut field_visibilities = Vec::new(); let mut field_idents = Vec::new(); - let mut named_field_idents = Vec::new(); let mut field_types = Vec::new(); let mut read_only_field_types = Vec::new(); - for (i, field) in fields.iter().enumerate() { - let attrs = match read_world_query_field_info(field) { - Ok(WorldQueryFieldInfo { attrs }) => attrs, - Err(e) => return e.into_compile_error().into(), - }; - let named_field_ident = field - .ident - .as_ref() - .cloned() - .unwrap_or_else(|| format_ident!("f{i}")); - let i = Index::from(i); - let field_ident = field - .ident - .as_ref() - .map_or(quote! { #i }, |i| quote! { #i }); - field_idents.push(field_ident); - named_field_idents.push(named_field_ident); - field_attrs.push(attrs); - field_visibilities.push(field.vis.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)); + for field in fields { + let WorldQueryFieldInfo { is_ignored, attrs } = read_world_query_field_info(field); + + let field_ident = field.ident.as_ref().unwrap().clone(); + if is_ignored { + ignored_field_attrs.push(attrs); + ignored_field_visibilities.push(field.vis.clone()); + ignored_field_idents.push(field_ident.clone()); + ignored_field_types.push(field.ty.clone()); + } else { + field_attrs.push(attrs); + field_visibilities.push(field.vis.clone()); + field_idents.push(field_ident.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)); + } } let derive_args = &fetch_struct_attributes.derive_args; @@ -196,45 +176,25 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream { &field_types }; - let item_struct = match fields { - syn::Fields::Named(_) => quote! { - #derive_macro_call - #[doc = "Automatically generated [`WorldQuery`] item type for [`"] - #[doc = stringify!(#struct_name)] - #[doc = "`], returned when iterating over query results."] - #[automatically_derived] - #visibility struct #item_struct_name #user_impl_generics_with_world #user_where_clauses_with_world { - #(#(#field_attrs)* #field_visibilities #field_idents: <#field_types as #path::query::WorldQuery>::Item<'__w>,)* - } - }, - syn::Fields::Unnamed(_) => quote! { - #derive_macro_call - #[doc = "Automatically generated [`WorldQuery`] item type for [`"] - #[doc = stringify!(#struct_name)] - #[doc = "`], returned when iterating over query results."] - #[automatically_derived] - #visibility struct #item_struct_name #user_impl_generics_with_world #user_where_clauses_with_world( - #( #field_visibilities <#field_types as #path::query::WorldQuery>::Item<'__w>, )* - ); - }, - syn::Fields::Unit => quote! { - #[doc = "Automatically generated [`WorldQuery`] item type for [`"] - #[doc = stringify!(#struct_name)] - #[doc = "`], returned when iterating over query results."] - #[automatically_derived] - #visibility type #item_struct_name #user_ty_generics_with_world = #struct_name #user_ty_generics; - }, - }; + quote! { + #derive_macro_call + #[doc = "Automatically generated [`WorldQuery`] item type for [`"] + #[doc = stringify!(#struct_name)] + #[doc = "`], returned when iterating over query results."] + #[automatically_derived] + #visibility struct #item_struct_name #user_impl_generics_with_world #user_where_clauses_with_world { + #(#(#field_attrs)* #field_visibilities #field_idents: <#field_types as #path::query::WorldQuery>::Item<'__w>,)* + #(#(#ignored_field_attrs)* #ignored_field_visibilities #ignored_field_idents: #ignored_field_types,)* + } - let query_impl = quote! { #[doc(hidden)] #[doc = "Automatically generated internal [`WorldQuery`] fetch type for [`"] #[doc = stringify!(#struct_name)] #[doc = "`], used to define the world data accessed by this query."] #[automatically_derived] #visibility struct #fetch_struct_name #user_impl_generics_with_world #user_where_clauses_with_world { - #(#named_field_idents: <#field_types as #path::query::WorldQuery>::Fetch<'__w>,)* - #marker_name: &'__w (), + #(#field_idents: <#field_types as #path::query::WorldQuery>::Fetch<'__w>,)* + #(#ignored_field_idents: #ignored_field_types,)* } // SAFETY: `update_component_access` and `update_archetype_component_access` are called on every field @@ -253,25 +213,28 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream { #( #field_idents: <#field_types>::shrink(item.#field_idents), )* + #( + #ignored_field_idents: item.#ignored_field_idents, + )* } } unsafe fn init_fetch<'__w>( - _world: #path::world::unsafe_world_cell::UnsafeWorldCell<'__w>, + _world: &'__w #path::world::World, state: &Self::State, - _last_run: #path::component::Tick, - _this_run: #path::component::Tick, + _last_change_tick: u32, + _change_tick: u32 ) -> ::Fetch<'__w> { #fetch_struct_name { - #(#named_field_idents: + #(#field_idents: <#field_types>::init_fetch( _world, - &state.#named_field_idents, - _last_run, - _this_run, + &state.#field_idents, + _last_change_tick, + _change_tick ), )* - #marker_name: &(), + #(#ignored_field_idents: Default::default(),)* } } @@ -280,9 +243,11 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream { ) -> ::Fetch<'__w> { #fetch_struct_name { #( - #named_field_idents: <#field_types>::clone_fetch(& _fetch. #named_field_idents), + #field_idents: <#field_types>::clone_fetch(& _fetch. #field_idents), + )* + #( + #ignored_field_idents: Default::default(), )* - #marker_name: &(), } } @@ -298,7 +263,7 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream { _archetype: &'__w #path::archetype::Archetype, _table: &'__w #path::storage::Table ) { - #(<#field_types>::set_archetype(&mut _fetch.#named_field_idents, &_state.#named_field_idents, _archetype, _table);)* + #(<#field_types>::set_archetype(&mut _fetch.#field_idents, &_state.#field_idents, _archetype, _table);)* } /// SAFETY: we call `set_table` for each member that implements `Fetch` @@ -308,7 +273,7 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream { _state: &Self::State, _table: &'__w #path::storage::Table ) { - #(<#field_types>::set_table(&mut _fetch.#named_field_idents, &_state.#named_field_idents, _table);)* + #(<#field_types>::set_table(&mut _fetch.#field_idents, &_state.#field_idents, _table);)* } /// SAFETY: we call `fetch` for each member that implements `Fetch`. @@ -319,7 +284,8 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream { _table_row: #path::storage::TableRow, ) -> ::Item<'__w> { Self::Item { - #(#field_idents: <#field_types>::fetch(&mut _fetch.#named_field_idents, _entity, _table_row),)* + #(#field_idents: <#field_types>::fetch(&mut _fetch.#field_idents, _entity, _table_row),)* + #(#ignored_field_idents: Default::default(),)* } } @@ -330,11 +296,11 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream { _entity: #path::entity::Entity, _table_row: #path::storage::TableRow, ) -> bool { - true #(&& <#field_types>::filter_fetch(&mut _fetch.#named_field_idents, _entity, _table_row))* + true #(&& <#field_types>::filter_fetch(&mut _fetch.#field_idents, _entity, _table_row))* } fn update_component_access(state: &Self::State, _access: &mut #path::query::FilteredAccess<#path::component::ComponentId>) { - #( <#field_types>::update_component_access(&state.#named_field_idents, _access); )* + #( <#field_types>::update_component_access(&state.#field_idents, _access); )* } fn update_archetype_component_access( @@ -343,46 +309,41 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream { _access: &mut #path::query::Access<#path::archetype::ArchetypeComponentId> ) { #( - <#field_types>::update_archetype_component_access(&state.#named_field_idents, _archetype, _access); + <#field_types>::update_archetype_component_access(&state.#field_idents, _archetype, _access); )* } fn init_state(world: &mut #path::world::World) -> #state_struct_name #user_ty_generics { #state_struct_name { - #(#named_field_idents: <#field_types>::init_state(world),)* + #(#field_idents: <#field_types>::init_state(world),)* + #(#ignored_field_idents: Default::default(),)* } } fn matches_component_set(state: &Self::State, _set_contains_id: &impl Fn(#path::component::ComponentId) -> bool) -> bool { - true #(&& <#field_types>::matches_component_set(&state.#named_field_idents, _set_contains_id))* + true #(&& <#field_types>::matches_component_set(&state.#field_idents, _set_contains_id))* } } - }; - (item_struct, query_impl) + } }; - let (mutable_struct, mutable_impl) = impl_fetch(false); - let (read_only_struct, read_only_impl) = if fetch_struct_attributes.is_mutable { - let (readonly_state, read_only_impl) = impl_fetch(true); - let read_only_structs = quote! { + let mutable_impl = impl_fetch(false); + let readonly_impl = if fetch_struct_attributes.is_mutable { + let world_query_impl = impl_fetch(true); + quote! { #[doc = "Automatically generated [`WorldQuery`] type for a read-only variant of [`"] #[doc = stringify!(#struct_name)] #[doc = "`]."] #[automatically_derived] #visibility struct #read_only_struct_name #user_impl_generics #user_where_clauses { - #( - #[doc = "Automatically generated read-only field for accessing `"] - #[doc = stringify!(#field_types)] - #[doc = "`."] - #field_visibilities #named_field_idents: #read_only_field_types, - )* + #( #field_visibilities #field_idents: #read_only_field_types, )* + #(#(#ignored_field_attrs)* #ignored_field_visibilities #ignored_field_idents: #ignored_field_types,)* } - #readonly_state - }; - (read_only_structs, read_only_impl) + #world_query_impl + } } else { - (quote! {}, quote! {}) + quote! {} }; let read_only_asserts = if fetch_struct_attributes.is_mutable { @@ -406,29 +367,24 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream { }; TokenStream::from(quote! { - #mutable_struct - - #read_only_struct + #mutable_impl + + #readonly_impl + + #[doc(hidden)] + #[doc = "Automatically generated internal [`WorldQuery`] state type for [`"] + #[doc = stringify!(#struct_name)] + #[doc = "`], used for caching."] + #[automatically_derived] + #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: 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 {} - const _: () = { - #[doc(hidden)] - #[doc = "Automatically generated internal [`WorldQuery`] state type for [`"] - #[doc = stringify!(#struct_name)] - #[doc = "`], used for caching."] - #[automatically_derived] - #visibility struct #state_struct_name #user_impl_generics #user_where_clauses { - #(#named_field_idents: <#field_types as #path::query::WorldQuery>::State,)* - } - - #mutable_impl - - #read_only_impl - }; - #[allow(dead_code)] const _: () = { fn assert_readonly() @@ -453,32 +409,57 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream { 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;)* + } }; }) } struct WorldQueryFieldInfo { + /// Has `#[fetch(ignore)]` or `#[filter_fetch(ignore)]` attribute. + is_ignored: bool, /// All field attributes except for `world_query` ones. attrs: Vec, } -fn read_world_query_field_info(field: &Field) -> syn::Result { - let mut attrs = Vec::new(); - for attr in &field.attrs { - if attr - .path() - .get_ident() - .map_or(false, |ident| ident == WORLD_QUERY_ATTRIBUTE_NAME) - { - return Err(syn::Error::new_spanned( - attr, - "#[derive(WorldQuery)] does not support field attributes.", - )); - } - attrs.push(attr.clone()); - } +fn read_world_query_field_info(field: &Field) -> WorldQueryFieldInfo { + let is_ignored = field + .attrs + .iter() + .find(|attr| { + attr.path + .get_ident() + .map_or(false, |ident| ident == WORLD_QUERY_ATTRIBUTE_NAME) + }) + .map_or(false, |attr| { + let mut is_ignored = false; + attr.parse_args_with(|input: ParseStream| { + if input + .parse::>()? + .is_some() + { + is_ignored = true; + } + Ok(()) + }) + .unwrap_or_else(|_| panic!("Invalid `{WORLD_QUERY_ATTRIBUTE_NAME}` attribute format")); + + is_ignored + }); + + let attrs = field + .attrs + .iter() + .filter(|attr| { + attr.path + .get_ident() + .map_or(true, |ident| ident != WORLD_QUERY_ATTRIBUTE_NAME) + }) + .cloned() + .collect(); - Ok(WorldQueryFieldInfo { attrs }) + WorldQueryFieldInfo { is_ignored, attrs } } diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index e207c6f9623c5..02229420fa994 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -1,11 +1,11 @@ use crate::{ archetype::{Archetype, ArchetypeComponentId}, change_detection::{Ticks, TicksMut}, - component::{Component, ComponentId, ComponentStorage, StorageType, Tick}, + component::{Component, ComponentId, ComponentStorage, ComponentTicks, StorageType, Tick}, entity::Entity, query::{Access, DebugCheckedUnwrap, FilteredAccess}, storage::{ComponentSparseSet, Table, TableRow}, - world::{unsafe_world_cell::UnsafeWorldCell, EntityRef, Mut, Ref, World}, + world::{Mut, Ref, World}, }; pub use bevy_ecs_macros::WorldQuery; use bevy_ptr::{ThinSlicePtr, UnsafeCellDeref}; @@ -37,11 +37,11 @@ use std::{cell::UnsafeCell, marker::PhantomData}; /// Wrapping it into an `Option` will increase the query search space, and it will return `None` if an entity doesn't satisfy the `WorldQuery`. /// - **[`AnyOf`].** /// Equivalent to wrapping each world query inside it into an `Option`. -/// - **[`Ref`].** +/// - **[`ChangeTrackers`].** /// Similar to change detection filters but it is used as a query fetch parameter. /// It exposes methods to check for changes to the wrapped component. /// -/// Implementing the trait manually can allow for a fundamentally new type of behavior. +/// Implementing the trait manually can allow for a fundamentally new type of behaviour. /// /// # Trait derivation /// @@ -55,7 +55,14 @@ use std::{cell::UnsafeCell, marker::PhantomData}; /// - Methods can be implemented for the query items. /// - There is no hardcoded limit on the number of elements. /// -/// This trait can only be derived for structs, if each field also implements `WorldQuery`. +/// This trait can only be derived if each field either +/// +/// * also implements `WorldQuery`, or +/// * is marked with `#[world_query(ignore)]`. Fields decorated with this attribute +/// must implement [`Default`] and will be initialized to the default value as defined +/// by the trait. +/// +/// The derive macro only supports regular structs (structs with named fields). /// /// ``` /// # use bevy_ecs::prelude::*; @@ -85,13 +92,16 @@ use std::{cell::UnsafeCell, marker::PhantomData}; /// /// ## Macro expansion /// -/// Expanding the macro will declare one or three additional structs, depending on whether or not the struct is marked as mutable. +/// Expanding the macro will declare three or six additional structs, depending on whether or not the struct is marked as mutable. /// For a struct named `X`, the additional structs will be: /// /// |Struct name|`mutable` only|Description| /// |:---:|:---:|---| +/// |`XState`|---|Used as the [`State`] type for `X` and `XReadOnly`| /// |`XItem`|---|The type of the query item for `X`| +/// |`XFetch`|---|Used as the [`Fetch`] type for `X`| /// |`XReadOnlyItem`|✓|The type of the query item for `XReadOnly`| +/// |`XReadOnlyFetch`|✓|Used as the [`Fetch`] type for `XReadOnly`| /// |`XReadOnly`|✓|[`ReadOnly`] variant of `X`| /// /// ## Adding mutable references @@ -271,24 +281,6 @@ use std::{cell::UnsafeCell, marker::PhantomData}; /// # bevy_ecs::system::assert_is_system(my_system); /// ``` /// -/// # Generic Queries -/// -/// When writing generic code, it is often necessary to use [`PhantomData`] -/// to constrain type parameters. Since `WorldQuery` is implemented for all -/// `PhantomData` types, this pattern can be used with this macro. -/// -/// ``` -/// # use bevy_ecs::{prelude::*, query::WorldQuery}; -/// # use std::marker::PhantomData; -/// #[derive(WorldQuery)] -/// pub struct GenericQuery { -/// id: Entity, -/// marker: PhantomData, -/// } -/// # fn my_system(q: Query>) {} -/// # bevy_ecs::system::assert_is_system(my_system); -/// ``` -/// /// # Safety /// /// Component access of `Self::ReadOnly` must be a subset of `Self` @@ -304,6 +296,7 @@ use std::{cell::UnsafeCell, marker::PhantomData}; /// [`Added`]: crate::query::Added /// [`fetch`]: Self::fetch /// [`Changed`]: crate::query::Changed +/// [`Fetch`]: crate::query::WorldQuery::Fetch /// [`matches_component_set`]: Self::matches_component_set /// [`Or`]: crate::query::Or /// [`Query`]: crate::system::Query @@ -335,14 +328,13 @@ pub unsafe trait WorldQuery { /// /// # Safety /// - /// - `world` must have permission to access any of the components specified in `Self::update_archetype_component_access`. - /// - `state` must have been initialized (via [`WorldQuery::init_state`]) using the same `world` passed - /// in to this function. + /// `state` must have been initialized (via [`WorldQuery::init_state`]) using the same `world` passed + /// in to this function. unsafe fn init_fetch<'w>( - world: UnsafeWorldCell<'w>, + world: &'w World, state: &Self::State, - last_run: Tick, - this_run: Tick, + last_change_tick: u32, + change_tick: u32, ) -> Self::Fetch<'w>; /// While this function can be called for any query, it is always safe to call if `Self: ReadOnlyWorldQuery` holds. @@ -373,10 +365,8 @@ pub unsafe trait WorldQuery { /// /// # Safety /// - /// - `archetype` and `tables` must be from the same [`World`] that [`WorldQuery::init_state`] was called on. - /// - [`Self::update_archetype_component_access`] must have been previously called with `archetype`. - /// - `table` must correspond to `archetype`. - /// - `state` must be the [`State`](Self::State) that `fetch` was initialized with. + /// `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<'w>( fetch: &mut Self::Fetch<'w>, state: &Self::State, @@ -389,10 +379,8 @@ pub unsafe trait WorldQuery { /// /// # Safety /// - /// - `table` must be from the same [`World`] that [`WorldQuery::init_state`] was called on. - /// - `table` must belong to an archetype that was previously registered with - /// [`Self::update_archetype_component_access`]. - /// - `state` must be the [`State`](Self::State) that `fetch` was initialized with. + /// `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<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table); /// Fetch [`Self::Item`](`WorldQuery::Item`) for either the given `entity` in the current [`Table`], @@ -424,12 +412,9 @@ pub unsafe trait WorldQuery { true } - /// Adds any component accesses used by this [`WorldQuery`] to `access`. // This does not have a default body of `{}` because 99% of cases need to add accesses // and forgetting to do so would be unsound. fn update_component_access(state: &Self::State, access: &mut FilteredAccess); - - /// For the given `archetype`, adds any component accessed used by this [`WorldQuery`] to `access`. // This does not have a default body of `{}` because 99% of cases need to add accesses // and forgetting to do so would be unsound. fn update_archetype_component_access( @@ -438,10 +423,7 @@ pub unsafe trait WorldQuery { access: &mut Access, ); - /// Creates and initializes a [`State`](WorldQuery::State) for this [`WorldQuery`] type. fn init_state(world: &mut World) -> Self::State; - - /// Returns `true` if this query matches a set of components. Otherwise, returns `false`. fn matches_component_set( state: &Self::State, set_contains_id: &impl Fn(ComponentId) -> bool, @@ -455,18 +437,15 @@ pub unsafe trait WorldQuery { /// This must only be implemented for read-only [`WorldQuery`]'s. pub unsafe trait ReadOnlyWorldQuery: WorldQuery {} +/// The `Fetch` of a [`WorldQuery`], which is used to store state for each archetype/table. +pub type QueryFetch<'w, Q> = ::Fetch<'w>; /// The item type returned when a [`WorldQuery`] is iterated over pub type QueryItem<'w, Q> = ::Item<'w>; +/// 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 a [`WorldQuery`], which is used to store state for each archetype/table. -#[deprecated = "use ::Fetch<'w> instead"] -pub type QueryFetch<'w, Q> = ::Fetch<'w>; -/// The read-only `Fetch` of a [`WorldQuery`], which is used to store state for each archetype/table. -#[deprecated = "use <::ReadOnly as WorldQuery>::Fetch<'w> instead"] -pub type ROQueryFetch<'w, Q> = <::ReadOnly as WorldQuery>::Fetch<'w>; - /// SAFETY: no component or archetype access unsafe impl WorldQuery for Entity { type Fetch<'w> = (); @@ -483,10 +462,10 @@ unsafe impl WorldQuery for Entity { const IS_ARCHETYPAL: bool = true; unsafe fn init_fetch<'w>( - _world: UnsafeWorldCell<'w>, + _world: &'w World, _state: &Self::State, - _last_run: Tick, - _this_run: Tick, + _last_change_tick: u32, + _change_tick: u32, ) -> Self::Fetch<'w> { } @@ -536,89 +515,6 @@ unsafe impl WorldQuery for Entity { /// SAFETY: access is read only unsafe impl ReadOnlyWorldQuery for Entity {} -/// SAFETY: `Self` is the same as `Self::ReadOnly` -unsafe impl<'a> WorldQuery for EntityRef<'a> { - type Fetch<'w> = &'w World; - type Item<'w> = EntityRef<'w>; - type ReadOnly = Self; - type State = (); - - fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { - item - } - - const IS_DENSE: bool = true; - - const IS_ARCHETYPAL: bool = true; - - unsafe fn init_fetch<'w>( - world: UnsafeWorldCell<'w>, - _state: &Self::State, - _last_run: Tick, - _this_run: Tick, - ) -> Self::Fetch<'w> { - // SAFE: EntityRef has permission to access the whole world immutably thanks to update_component_access and update_archetype_component_access - world.world() - } - - unsafe fn clone_fetch<'w>(world: &Self::Fetch<'w>) -> Self::Fetch<'w> { - world - } - - #[inline] - unsafe fn set_archetype<'w>( - _fetch: &mut Self::Fetch<'w>, - _state: &Self::State, - _archetype: &'w Archetype, - _table: &Table, - ) { - } - - #[inline] - unsafe fn set_table<'w>(_fetch: &mut Self::Fetch<'w>, _state: &Self::State, _table: &'w Table) { - } - - #[inline(always)] - unsafe fn fetch<'w>( - world: &mut Self::Fetch<'w>, - entity: Entity, - _table_row: TableRow, - ) -> Self::Item<'w> { - // SAFETY: `fetch` must be called with an entity that exists in the world - unsafe { world.get_entity(entity).debug_checked_unwrap() } - } - - fn update_component_access(_state: &Self::State, access: &mut FilteredAccess) { - assert!( - !access.access().has_any_write(), - "EntityRef conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.", - ); - access.read_all(); - } - - fn update_archetype_component_access( - _state: &Self::State, - archetype: &Archetype, - access: &mut Access, - ) { - for component_id in archetype.components() { - access.add_read(archetype.get_archetype_component_id(component_id).unwrap()); - } - } - - fn init_state(_world: &mut World) {} - - fn matches_component_set( - _state: &Self::State, - _set_contains_id: &impl Fn(ComponentId) -> bool, - ) -> bool { - true - } -} - -/// SAFETY: access is read only -unsafe impl<'a> ReadOnlyWorldQuery for EntityRef<'a> {} - #[doc(hidden)] pub struct ReadFetch<'w, T> { // T::Storage = TableStorage @@ -647,21 +543,16 @@ unsafe impl WorldQuery for &T { const IS_ARCHETYPAL: bool = true; - #[inline] unsafe fn init_fetch<'w>( - world: UnsafeWorldCell<'w>, + world: &'w World, &component_id: &ComponentId, - _last_run: Tick, - _this_run: Tick, + _last_change_tick: u32, + _change_tick: u32, ) -> ReadFetch<'w, T> { ReadFetch { table_components: None, sparse_set: (T::Storage::STORAGE_TYPE == StorageType::SparseSet).then(|| { world - // SAFETY: The underlying type associated with `component_id` is `T`, - // which we are allowed to access since we registered it in `update_archetype_component_access`. - // Note that we do not actually access any components in this function, we just get a shared - // reference to the sparse set, which is used to access the components in `Self::fetch`. .storages() .sparse_sets .get(component_id) @@ -773,8 +664,8 @@ pub struct RefFetch<'w, T> { // T::Storage = SparseStorage sparse_set: Option<&'w ComponentSparseSet>, - last_run: Tick, - this_run: Tick, + last_change_tick: u32, + change_tick: u32, } /// SAFETY: `Self` is the same as `Self::ReadOnly` @@ -797,25 +688,23 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { const IS_ARCHETYPAL: bool = true; - #[inline] unsafe fn init_fetch<'w>( - world: UnsafeWorldCell<'w>, + world: &'w World, &component_id: &ComponentId, - last_run: Tick, - this_run: Tick, + last_change_tick: u32, + change_tick: u32, ) -> RefFetch<'w, T> { RefFetch { table_data: None, sparse_set: (T::Storage::STORAGE_TYPE == StorageType::SparseSet).then(|| { world - // SAFETY: See &T::init_fetch. .storages() .sparse_sets .get(component_id) .debug_checked_unwrap() }), - last_run, - this_run, + last_change_tick, + change_tick, } } @@ -823,8 +712,8 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { RefFetch { table_data: fetch.table_data, sparse_set: fetch.sparse_set, - last_run: fetch.last_run, - this_run: fetch.this_run, + last_change_tick: fetch.last_change_tick, + change_tick: fetch.change_tick, } } @@ -869,8 +758,8 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { ticks: Ticks { added: added_ticks.get(table_row.index()).deref(), changed: changed_ticks.get(table_row.index()).deref(), - this_run: fetch.this_run, - last_run: fetch.last_run, + change_tick: fetch.change_tick, + last_change_tick: fetch.last_change_tick, }, } } @@ -882,7 +771,7 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { .debug_checked_unwrap(); Ref { value: component.deref(), - ticks: Ticks::from_tick_cells(ticks, fetch.last_run, fetch.this_run), + ticks: Ticks::from_tick_cells(ticks, fetch.last_change_tick, fetch.change_tick), } } } @@ -936,8 +825,8 @@ pub struct WriteFetch<'w, T> { // T::Storage = SparseStorage sparse_set: Option<&'w ComponentSparseSet>, - last_run: Tick, - this_run: Tick, + last_change_tick: u32, + change_tick: u32, } /// SAFETY: access of `&T` is a subset of `&mut T` @@ -960,25 +849,23 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { const IS_ARCHETYPAL: bool = true; - #[inline] unsafe fn init_fetch<'w>( - world: UnsafeWorldCell<'w>, + world: &'w World, &component_id: &ComponentId, - last_run: Tick, - this_run: Tick, + last_change_tick: u32, + change_tick: u32, ) -> WriteFetch<'w, T> { WriteFetch { table_data: None, sparse_set: (T::Storage::STORAGE_TYPE == StorageType::SparseSet).then(|| { world - // SAFETY: See &T::init_fetch. .storages() .sparse_sets .get(component_id) .debug_checked_unwrap() }), - last_run, - this_run, + last_change_tick, + change_tick, } } @@ -986,8 +873,8 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { WriteFetch { table_data: fetch.table_data, sparse_set: fetch.sparse_set, - last_run: fetch.last_run, - this_run: fetch.this_run, + last_change_tick: fetch.last_change_tick, + change_tick: fetch.change_tick, } } @@ -1032,8 +919,8 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { ticks: TicksMut { added: added_ticks.get(table_row.index()).deref_mut(), changed: changed_ticks.get(table_row.index()).deref_mut(), - this_run: fetch.this_run, - last_run: fetch.last_run, + change_tick: fetch.change_tick, + last_change_tick: fetch.last_change_tick, }, } } @@ -1045,7 +932,11 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { .debug_checked_unwrap(); Mut { value: component.assert_unique().deref_mut(), - ticks: TicksMut::from_tick_cells(ticks, fetch.last_run, fetch.this_run), + ticks: TicksMut::from_tick_cells( + ticks, + fetch.last_change_tick, + fetch.change_tick, + ), } } } @@ -1106,15 +997,14 @@ unsafe impl WorldQuery for Option { const IS_ARCHETYPAL: bool = T::IS_ARCHETYPAL; - #[inline] unsafe fn init_fetch<'w>( - world: UnsafeWorldCell<'w>, + world: &'w World, state: &T::State, - last_run: Tick, - this_run: Tick, + last_change_tick: u32, + change_tick: u32, ) -> OptionFetch<'w, T> { OptionFetch { - fetch: T::init_fetch(world, state, last_run, this_run), + fetch: T::init_fetch(world, state, last_change_tick, change_tick), matches: false, } } @@ -1193,65 +1083,100 @@ unsafe impl WorldQuery for Option { /// SAFETY: [`OptionFetch`] is read only because `T` is read only unsafe impl ReadOnlyWorldQuery for Option {} -/// Returns a bool that describes if an entity has the component `T`. +/// [`WorldQuery`] that tracks changes and additions for component `T`. /// -/// This can be used in a [`Query`](crate::system::Query) if you want to know whether or not entities -/// have the component `T` but don't actually care about the component's value. +/// Wraps a [`Component`] to track whether the component changed for the corresponding entities in +/// a query since the last time the system that includes these queries ran. +/// +/// If you only care about entities that changed or that got added use the +/// [`Changed`](crate::query::Changed) and [`Added`](crate::query::Added) filters instead. /// /// # Examples /// /// ``` /// # use bevy_ecs::component::Component; -/// # use bevy_ecs::query::Has; +/// # use bevy_ecs::query::ChangeTrackers; /// # use bevy_ecs::system::IntoSystem; /// # use bevy_ecs::system::Query; /// # +/// # #[derive(Component, Debug)] +/// # struct Name {}; /// # #[derive(Component)] -/// # struct IsHungry; -/// # #[derive(Component)] -/// # struct Name { name: &'static str }; +/// # struct Transform {}; /// # -/// fn food_entity_system(query: Query<(&Name, Has) >) { -/// for (name, is_hungry) in &query { -/// if is_hungry{ -/// println!("{} would like some food.", name.name); +/// fn print_moving_objects_system(query: Query<(&Name, ChangeTrackers)>) { +/// for (name, tracker) in &query { +/// if tracker.is_changed() { +/// println!("Entity moved: {:?}", name); /// } else { -/// println!("{} has had sufficient.", name.name); +/// println!("Entity stood still: {:?}", name); /// } /// } /// } -/// # bevy_ecs::system::assert_is_system(food_entity_system); -/// ``` -/// +/// # bevy_ecs::system::assert_is_system(print_moving_objects_system); /// ``` -/// # use bevy_ecs::component::Component; -/// # use bevy_ecs::query::Has; -/// # use bevy_ecs::system::IntoSystem; -/// # use bevy_ecs::system::Query; -/// # -/// # #[derive(Component)] -/// # struct Alpha{has_beta: bool}; -/// # #[derive(Component)] -/// # struct Beta { has_alpha: bool }; -/// # -/// // Unlike `Option<&T>`, `Has` is compatible with `&mut T` -/// // as it does not actually access any data. -/// fn alphabet_entity_system(mut alphas: Query<(&mut Alpha, Has)>, mut betas: Query<(&mut Beta, Has)>) { -/// for (mut alpha, has_beta) in alphas.iter_mut() { -/// alpha.has_beta = has_beta; -/// } -/// for (mut beta, has_alpha) in betas.iter_mut() { -/// beta.has_alpha = has_alpha; -/// } -/// } -/// # bevy_ecs::system::assert_is_system(alphabet_entity_system); -/// ``` -pub struct Has(PhantomData); +#[deprecated = "`ChangeTrackers` will be removed in bevy 0.11. Use `bevy_ecs::prelude::Ref` instead."] +pub struct ChangeTrackers { + pub(crate) component_ticks: ComponentTicks, + pub(crate) last_change_tick: u32, + pub(crate) change_tick: u32, + marker: PhantomData, +} + +#[allow(deprecated)] +impl Clone for ChangeTrackers { + fn clone(&self) -> Self { + *self + } +} + +#[allow(deprecated)] +impl Copy for ChangeTrackers {} + +#[allow(deprecated)] +impl std::fmt::Debug for ChangeTrackers { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ChangeTrackers") + .field("component_ticks", &self.component_ticks) + .field("last_change_tick", &self.last_change_tick) + .field("change_tick", &self.change_tick) + .finish() + } +} + +#[allow(deprecated)] +impl ChangeTrackers { + /// Returns true if this component has been added since the last execution of this system. + pub fn is_added(&self) -> bool { + self.component_ticks + .is_added(self.last_change_tick, self.change_tick) + } -// SAFETY: `Self::ReadOnly` is the same as `Self` -unsafe impl WorldQuery for Has { - type Fetch<'w> = bool; - type Item<'w> = bool; + /// Returns true if this component has been changed since the last execution of this system. + pub fn is_changed(&self) -> bool { + self.component_ticks + .is_changed(self.last_change_tick, self.change_tick) + } +} + +#[doc(hidden)] +pub struct ChangeTrackersFetch<'w, T> { + // T::Storage = TableStorage + table_added: Option>>, + table_changed: Option>>, + // T::Storage = SparseStorage + sparse_set: Option<&'w ComponentSparseSet>, + + marker: PhantomData, + last_change_tick: u32, + change_tick: u32, +} + +#[allow(deprecated)] +// SAFETY: `ROQueryFetch` is the same as `QueryFetch` +unsafe impl WorldQuery for ChangeTrackers { + type Fetch<'w> = ChangeTrackersFetch<'w, T>; + type Item<'w> = ChangeTrackers; type ReadOnly = Self; type State = ComponentId; @@ -1268,53 +1193,118 @@ unsafe impl WorldQuery for Has { const IS_ARCHETYPAL: bool = true; - #[inline] unsafe fn init_fetch<'w>( - _world: UnsafeWorldCell<'w>, - _state: &Self::State, - _last_run: Tick, - _this_run: Tick, - ) -> Self::Fetch<'w> { - false + world: &'w World, + &component_id: &ComponentId, + last_change_tick: u32, + change_tick: u32, + ) -> ChangeTrackersFetch<'w, T> { + ChangeTrackersFetch { + table_added: None, + table_changed: None, + sparse_set: (T::Storage::STORAGE_TYPE == StorageType::SparseSet).then(|| { + world + .storages() + .sparse_sets + .get(component_id) + .debug_checked_unwrap() + }), + marker: PhantomData, + last_change_tick, + change_tick, + } } unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> { - *fetch + ChangeTrackersFetch { + table_added: fetch.table_added, + table_changed: fetch.table_changed, + sparse_set: fetch.sparse_set, + marker: fetch.marker, + last_change_tick: fetch.last_change_tick, + change_tick: fetch.change_tick, + } } #[inline] unsafe fn set_archetype<'w>( - fetch: &mut Self::Fetch<'w>, - state: &Self::State, - archetype: &'w Archetype, - _table: &Table, + fetch: &mut ChangeTrackersFetch<'w, T>, + component_id: &ComponentId, + _archetype: &'w Archetype, + table: &'w Table, ) { - *fetch = archetype.contains(*state); + if Self::IS_DENSE { + Self::set_table(fetch, component_id, table); + } } #[inline] - unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table) { - *fetch = table.has_column(*state); + unsafe fn set_table<'w>( + fetch: &mut ChangeTrackersFetch<'w, T>, + &id: &ComponentId, + table: &'w Table, + ) { + let column = table.get_column(id).debug_checked_unwrap(); + fetch.table_added = Some(column.get_added_ticks_slice().into()); + fetch.table_changed = Some(column.get_changed_ticks_slice().into()); } #[inline(always)] unsafe fn fetch<'w>( fetch: &mut Self::Fetch<'w>, - _entity: Entity, - _table_row: TableRow, + entity: Entity, + table_row: TableRow, ) -> Self::Item<'w> { - *fetch + match T::Storage::STORAGE_TYPE { + StorageType::Table => ChangeTrackers { + component_ticks: { + ComponentTicks { + added: fetch + .table_added + .debug_checked_unwrap() + .get(table_row.index()) + .read(), + changed: fetch + .table_changed + .debug_checked_unwrap() + .get(table_row.index()) + .read(), + } + }, + marker: PhantomData, + last_change_tick: fetch.last_change_tick, + change_tick: fetch.change_tick, + }, + StorageType::SparseSet => ChangeTrackers { + component_ticks: fetch + .sparse_set + .debug_checked_unwrap() + .get_ticks(entity) + .debug_checked_unwrap(), + marker: PhantomData, + last_change_tick: fetch.last_change_tick, + change_tick: fetch.change_tick, + }, + } } - fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) { - // Do nothing as presence of `Has` never affects whether two queries are disjoint + fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess) { + assert!( + !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(id); } fn update_archetype_component_access( - _state: &Self::State, - _archetype: &Archetype, - _access: &mut Access, + &id: &ComponentId, + archetype: &Archetype, + access: &mut Access, ) { + 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 { @@ -1322,16 +1312,16 @@ unsafe impl WorldQuery for Has { } fn matches_component_set( - _state: &Self::State, - _set_contains_id: &impl Fn(ComponentId) -> bool, + &id: &ComponentId, + set_contains_id: &impl Fn(ComponentId) -> bool, ) -> bool { - // `Has` always matches - true + set_contains_id(id) } } -/// SAFETY: [`Has`] is read only -unsafe impl ReadOnlyWorldQuery for Has {} +#[allow(deprecated)] +/// SAFETY: access is read only +unsafe impl ReadOnlyWorldQuery for ChangeTrackers {} macro_rules! impl_tuple_fetch { ($(($name: ident, $state: ident)),*) => { @@ -1351,11 +1341,10 @@ macro_rules! impl_tuple_fetch { )*) } - #[inline] #[allow(clippy::unused_unit)] - unsafe fn init_fetch<'w>(_world: UnsafeWorldCell<'w>, state: &Self::State, _last_run: Tick, _this_run: Tick) -> Self::Fetch<'w> { + unsafe fn init_fetch<'w>(_world: &'w World, state: &Self::State, _last_change_tick: u32, _change_tick: u32) -> Self::Fetch<'w> { let ($($name,)*) = state; - ($($name::init_fetch(_world, $name, _last_run, _this_run),)*) + ($($name::init_fetch(_world, $name, _last_change_tick, _change_tick),)*) } unsafe fn clone_fetch<'w>( @@ -1400,8 +1389,8 @@ macro_rules! impl_tuple_fetch { } #[inline(always)] - unsafe fn filter_fetch( - _fetch: &mut Self::Fetch<'_>, + unsafe fn filter_fetch<'w>( + _fetch: &mut Self::Fetch<'w>, _entity: Entity, _table_row: TableRow ) -> bool { @@ -1461,11 +1450,10 @@ macro_rules! impl_anytuple_fetch { )*) } - #[inline] #[allow(clippy::unused_unit)] - unsafe fn init_fetch<'w>(_world: UnsafeWorldCell<'w>, state: &Self::State, _last_run: Tick, _this_run: Tick) -> Self::Fetch<'w> { + unsafe fn init_fetch<'w>(_world: &'w World, state: &Self::State, _last_change_tick: u32, _change_tick: u32) -> Self::Fetch<'w> { let ($($name,)*) = state; - ($(($name::init_fetch(_world, $name, _last_run, _this_run), false),)*) + ($(($name::init_fetch(_world, $name, _last_change_tick, _change_tick), false),)*) } unsafe fn clone_fetch<'w>( @@ -1524,21 +1512,34 @@ macro_rules! impl_anytuple_fetch { fn update_component_access(state: &Self::State, _access: &mut FilteredAccess) { let ($($name,)*) = state; - let mut _new_access = _access.clone(); + // 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: + // - Query<(AnyOf<(&A, ())>, &mut B)> + // - Query<&mut B, Without> + // + // If we were to unconditionally add `$name`'s `with`/`without` accesses then `AnyOf<(&A, ())>` + // would have a `With` access which is incorrect as this `WorldQuery` will match entities that + // do not have the `A` component. This is the same logic as the `Or<...>: WorldQuery` impl. + // + // The correct thing to do here is to only add a `with`/`without` access to `_access` if all + // `$name` params have that `with`/`without` access. More jargony put- we add the intersection + // of all `with`/`without` accesses of the `$name` params to `_access`. + let mut _intersected_access = _access.clone(); let mut _not_first = false; $( if _not_first { let mut intermediate = _access.clone(); $name::update_component_access($name, &mut intermediate); - _new_access.append_or(&intermediate); - _new_access.extend_access(&intermediate); + _intersected_access.extend_intersect_filter(&intermediate); + _intersected_access.extend_access(&intermediate); } else { - $name::update_component_access($name, &mut _new_access); + + $name::update_component_access($name, &mut _intersected_access); _not_first = true; } )* - *_access = _new_access; + *_access = _intersected_access; } fn update_archetype_component_access(state: &Self::State, _archetype: &Archetype, _access: &mut Access) { @@ -1562,6 +1563,7 @@ macro_rules! impl_anytuple_fetch { /// SAFETY: each item in the tuple is read only unsafe impl<$($name: ReadOnlyWorldQuery),*> ReadOnlyWorldQuery for AnyOf<($($name,)*)> {} + }; } @@ -1588,10 +1590,10 @@ unsafe impl WorldQuery for NopWorldQuery { #[inline(always)] unsafe fn init_fetch( - _world: UnsafeWorldCell, + _world: &World, _state: &Q::State, - _last_run: Tick, - _this_run: Tick, + _last_change_tick: u32, + _change_tick: u32, ) { } @@ -1641,119 +1643,14 @@ unsafe impl WorldQuery for NopWorldQuery { /// SAFETY: `NopFetch` never accesses any data unsafe impl ReadOnlyWorldQuery for NopWorldQuery {} -/// SAFETY: `PhantomData` never accesses any world data. -unsafe impl WorldQuery for PhantomData { - type Item<'a> = (); - type Fetch<'a> = (); - type ReadOnly = Self; - type State = (); - - fn shrink<'wlong: 'wshort, 'wshort>(_item: Self::Item<'wlong>) -> Self::Item<'wshort> {} - - unsafe fn init_fetch<'w>( - _world: UnsafeWorldCell<'w>, - _state: &Self::State, - _last_run: Tick, - _this_run: Tick, - ) -> Self::Fetch<'w> { - } - - unsafe fn clone_fetch<'w>(_fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> {} - - // `PhantomData` does not match any components, so all components it matches - // are stored in a Table (vacuous truth). - const IS_DENSE: bool = true; - // `PhantomData` matches every entity in each archetype. - const IS_ARCHETYPAL: bool = true; - - unsafe fn set_archetype<'w>( - _fetch: &mut Self::Fetch<'w>, - _state: &Self::State, - _archetype: &'w Archetype, - _table: &'w Table, - ) { - } - - unsafe fn set_table<'w>(_fetch: &mut Self::Fetch<'w>, _state: &Self::State, _table: &'w Table) { - } - - unsafe fn fetch<'w>( - _fetch: &mut Self::Fetch<'w>, - _entity: Entity, - _table_row: TableRow, - ) -> Self::Item<'w> { - } - - fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) {} - - fn update_archetype_component_access( - _state: &Self::State, - _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 { - true - } -} - -/// SAFETY: `PhantomData` never accesses any world data. -unsafe impl ReadOnlyWorldQuery for PhantomData {} - #[cfg(test)] mod tests { use super::*; - use crate::{ - self as bevy_ecs, - system::{assert_is_system, Query}, - }; + use crate::{self as bevy_ecs, system::Query}; #[derive(Component)] pub struct A; - #[derive(Component)] - pub struct B; - - // Tests that each variant of struct can be used as a `WorldQuery`. - #[test] - fn world_query_struct_variants() { - #[derive(WorldQuery)] - pub struct NamedQuery { - id: Entity, - a: &'static A, - } - - #[derive(WorldQuery)] - pub struct TupleQuery(&'static A, &'static B); - - #[derive(WorldQuery)] - pub struct UnitQuery; - - fn my_system(_: Query<(NamedQuery, TupleQuery, UnitQuery)>) {} - - assert_is_system(my_system); - } - - // Compile test for https://github.com/bevyengine/bevy/pull/8030. - #[test] - fn world_query_phantom_data() { - #[derive(WorldQuery)] - pub struct IgnoredQuery { - id: Entity, - _marker: PhantomData, - } - - fn ignored_system(_: Query>) {} - - crate::system::assert_is_system(ignored_system); - } - // Ensures that each field of a `WorldQuery` struct's read-only variant // has the same visibility as its corresponding mutable field. #[test] @@ -1778,32 +1675,4 @@ mod tests { crate::system::assert_is_system(my_system); } - - // Ensures that metadata types generated by the WorldQuery macro - // do not conflict with user-defined types. - // Regression test for https://github.com/bevyengine/bevy/issues/8010. - #[test] - fn world_query_metadata_collision() { - // The metadata types generated would be named `ClientState` and `ClientFetch`, - // but they should rename themselves to avoid conflicts. - #[derive(WorldQuery)] - pub struct Client { - pub state: &'static S, - pub fetch: &'static ClientFetch, - } - - pub trait ClientState: Component {} - - #[derive(Component)] - pub struct ClientFetch; - - #[derive(Component)] - pub struct C; - - impl ClientState for C {} - - fn client_system(_: Query>) {} - - crate::system::assert_is_system(client_system); - } }