diff --git a/Cargo.toml b/Cargo.toml index b04683e80bf438..9f9f58246a0e5a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -318,6 +318,10 @@ path = "examples/ecs/ecs_guide.rs" name = "component_change_detection" path = "examples/ecs/component_change_detection.rs" +[[example]] +name = "custom_query_param" +path = "examples/ecs/custom_query_param.rs" + [[example]] name = "event" path = "examples/ecs/event.rs" diff --git a/crates/bevy_ecs/macros/src/fetch.rs b/crates/bevy_ecs/macros/src/fetch.rs new file mode 100644 index 00000000000000..ddeb301c199743 --- /dev/null +++ b/crates/bevy_ecs/macros/src/fetch.rs @@ -0,0 +1,583 @@ +use proc_macro::TokenStream; +use proc_macro2::{Ident, Span}; +use quote::{quote, ToTokens}; +use syn::{ + parse::{Parse, ParseStream}, + punctuated::Punctuated, + Attribute, Data, DataStruct, DeriveInput, Field, Fields, GenericArgument, GenericParam, + Lifetime, LifetimeDef, Path, PathArguments, ReturnType, Token, Type, TypePath, +}; + +use crate::bevy_ecs_path; + +#[derive(Default)] +struct FetchStructAttributes { + pub is_filter: bool, + pub is_mutable: bool, + pub derive_args: Punctuated, +} + +static FILTER_ATTRIBUTE_NAME: &str = "filter"; +static MUTABLE_ATTRIBUTE_NAME: &str = "mutable"; +static DERIVE_ATTRIBUTE_NAME: &str = "derive"; + +mod field_attr_keywords { + syn::custom_keyword!(ignore); +} + +pub static WORLD_QUERY_ATTRIBUTE_NAME: &str = "world_query"; + +pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream { + let mut fetch_struct_attributes = FetchStructAttributes::default(); + for attr in &ast.attrs { + if !attr + .path + .get_ident() + .map_or(false, |ident| ident == WORLD_QUERY_ATTRIBUTE_NAME) + { + continue; + } + + attr.parse_args_with(|input: ParseStream| { + let meta = input.parse_terminated::(syn::Meta::parse)?; + for meta in meta { + let ident = meta.path().get_ident().unwrap_or_else(|| { + panic!( + "Unrecognized attribute: `{}`", + meta.path().to_token_stream() + ) + }); + if ident == MUTABLE_ATTRIBUTE_NAME { + if let syn::Meta::Path(_) = meta { + fetch_struct_attributes.is_mutable = true; + } else { + panic!( + "The `{}` attribute is expected to have no value or arguments", + MUTABLE_ATTRIBUTE_NAME + ); + } + } else if ident == DERIVE_ATTRIBUTE_NAME { + if let syn::Meta::List(meta_list) = meta { + fetch_struct_attributes + .derive_args + .extend(meta_list.nested.iter().cloned()); + } else { + panic!( + "Expected a structured list within the `{}` attribute", + DERIVE_ATTRIBUTE_NAME + ); + } + } else if ident == FILTER_ATTRIBUTE_NAME { + if let syn::Meta::Path(_) = meta { + fetch_struct_attributes.is_filter = true; + } else { + panic!( + "The `{}` attribute is expected to have no value or arguments", + FILTER_ATTRIBUTE_NAME + ); + } + } else { + panic!( + "Unrecognized attribute: `{}`", + meta.path().to_token_stream() + ); + } + } + Ok(()) + }) + .unwrap_or_else(|_| panic!("Invalid `{}` attribute format", WORLD_QUERY_ATTRIBUTE_NAME)); + } + + if fetch_struct_attributes.is_filter && fetch_struct_attributes.is_mutable { + panic!( + "The `{}` attribute is not expected to be used in conjunction with the `{}` attribute", + FILTER_ATTRIBUTE_NAME, MUTABLE_ATTRIBUTE_NAME + ); + } + + let world_lifetime = ast.generics.params.first().and_then(|param| match param { + lt @ GenericParam::Lifetime(_) => Some(lt.clone()), + _ => None, + }); + // Fetch's HRTBs require substituting world lifetime with an additional one to make the + // implementation compile. I don't fully understand why this works though. If anyone's curious + // enough to try to find a better work around, I'll leave playground links here: + // - https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=da5e260a5c2f3e774142d60a199e854a (this fails) + // - https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=802517bb3d8f83c45ee8c0be360bb250 (this compiles) + let fetch_lifetime_param = + GenericParam::Lifetime(LifetimeDef::new(Lifetime::new("'fetch", Span::call_site()))); + + let has_world_lifetime = world_lifetime.is_some(); + let world_lifetime_param = world_lifetime.unwrap_or_else(|| { + GenericParam::Lifetime(LifetimeDef::new(Lifetime::new("'world", Span::call_site()))) + }); + let state_lifetime_param = + GenericParam::Lifetime(LifetimeDef::new(Lifetime::new("'state", Span::call_site()))); + + let mut fetch_trait_punctuated_lifetimes = Punctuated::<_, Token![,]>::new(); + fetch_trait_punctuated_lifetimes.push(world_lifetime_param.clone()); + fetch_trait_punctuated_lifetimes.push(state_lifetime_param.clone()); + + let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); + + let struct_name = ast.ident.clone(); + let item_struct_name = Ident::new(&format!("{}Item", struct_name), Span::call_site()); + let read_only_item_struct_name = if fetch_struct_attributes.is_mutable { + Ident::new(&format!("{}ReadOnlyItem", struct_name), Span::call_site()) + } else { + item_struct_name.clone() + }; + let fetch_struct_name = Ident::new(&format!("{}Fetch", struct_name), Span::call_site()); + let state_struct_name = Ident::new(&format!("{}State", struct_name), Span::call_site()); + let read_only_fetch_struct_name = if fetch_struct_attributes.is_mutable { + Ident::new(&format!("{}ReadOnlyFetch", struct_name), Span::call_site()) + } else { + fetch_struct_name.clone() + }; + let fetch_associated_type = Ident::new("Fetch", Span::call_site()); + let read_only_fetch_associated_type = Ident::new("ReadOnlyFetch", Span::call_site()); + + 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 field_types = Vec::new(); + let mut fetch_init_types = Vec::new(); + + let (world_lifetime, fetch_lifetime) = match (&world_lifetime_param, &fetch_lifetime_param) { + (GenericParam::Lifetime(world), GenericParam::Lifetime(fetch)) => { + (&world.lifetime, &fetch.lifetime) + } + _ => unreachable!(), + }; + + for field in fields.iter() { + let WorldQueryFieldInfo { + field_type, + fetch_init_type: init_type, + is_ignored, + attrs, + } = read_world_query_field_info(field, world_lifetime, fetch_lifetime); + + 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()); + field_types.push(field_type); + fetch_init_types.push(init_type); + } + } + + // We expect that only regular query declarations have a lifetime. + if fetch_struct_attributes.is_filter { + if has_world_lifetime { + panic!("Expected a struct without a lifetime"); + } + } else if !has_world_lifetime { + panic!("Expected a struct with a lifetime"); + } + + let derive_macro_call = if fetch_struct_attributes.derive_args.is_empty() { + quote! {} + } else { + let derive_args = &fetch_struct_attributes.derive_args; + quote! { #[derive(#derive_args)] } + }; + + // Add `'state` and `'fetch` lifetimes that will be used in `Fetch` implementation. + let mut fetch_generics = ast.generics.clone(); + if !has_world_lifetime { + fetch_generics + .params + .insert(0, world_lifetime_param.clone()); + } + fetch_generics.params.insert(1, state_lifetime_param); + fetch_generics + .params + .insert(2, fetch_lifetime_param.clone()); + let (fetch_impl_generics, _, _) = fetch_generics.split_for_impl(); + + // Replace lifetime `'world` with `'fetch`. See `replace_lifetime_for_type` for more details. + let mut fetch_generics = ast.generics.clone(); + *fetch_generics.params.first_mut().unwrap() = fetch_lifetime_param; + + let fetch_ty_generics = if fetch_struct_attributes.is_filter { + ty_generics.clone() + } else { + let (_, fetch_ty_generics, _) = fetch_generics.split_for_impl(); + fetch_ty_generics + }; + + let path = bevy_ecs_path(); + + let impl_fetch = |is_filter: bool, + fetch_associated_type: Ident, + fetch_struct_name: Ident, + item_struct_name: Ident| { + if is_filter { + quote! { + struct #fetch_struct_name #impl_generics #where_clause { + #(#field_idents: <#field_types as #path::query::WorldQuery>::#fetch_associated_type,)* + #(#ignored_field_idents: #ignored_field_types,)* + } + + impl #fetch_impl_generics #path::query::Fetch<#fetch_trait_punctuated_lifetimes> for #fetch_struct_name #ty_generics #where_clause { + type Item = bool; + type State = #state_struct_name #ty_generics; + + unsafe fn init(_world: &#path::world::World, state: &Self::State, _last_change_tick: u32, _change_tick: u32) -> Self { + #fetch_struct_name { + #(#field_idents: <#field_types as #path::query::WorldQuery>::ReadOnlyFetch::init(_world, &state.#field_idents, _last_change_tick, _change_tick),)* + #(#ignored_field_idents: Default::default(),)* + } + } + + const IS_DENSE: bool = true #(&& <#field_types as #path::query::WorldQuery>::ReadOnlyFetch::IS_DENSE)*; + + #[inline] + unsafe fn set_archetype(&mut self, _state: &Self::State, _archetype: &#path::archetype::Archetype, _tables: &#path::storage::Tables) { + #(self.#field_idents.set_archetype(&_state.#field_idents, _archetype, _tables);)* + } + + #[inline] + unsafe fn set_table(&mut self, _state: &Self::State, _table: &#path::storage::Table) { + #(self.#field_idents.set_table(&_state.#field_idents, _table);)* + } + + #[inline] + unsafe fn table_fetch(&mut self, _table_row: usize) -> Self::Item { + use #path::query::FilterFetch; + true #(&& self.#field_idents.table_filter_fetch(_table_row))* + } + + #[inline] + unsafe fn archetype_fetch(&mut self, _archetype_index: usize) -> Self::Item { + use #path::query::FilterFetch; + true #(&& self.#field_idents.archetype_filter_fetch(_archetype_index))* + } + } + } + } else { + quote! { + #derive_macro_call + struct #item_struct_name #impl_generics #where_clause { + #(#(#field_attrs)* #field_visibilities #field_idents: <<#field_types as #path::query::WorldQuery>::#fetch_associated_type as #path::query::Fetch<#world_lifetime, #world_lifetime>>::Item,)* + #(#(#ignored_field_attrs)* #ignored_field_visibilities #ignored_field_idents: #ignored_field_types,)* + } + + struct #fetch_struct_name #impl_generics #where_clause { + #(#field_idents: <#field_types as #path::query::WorldQuery>::#fetch_associated_type,)* + #(#ignored_field_idents: #ignored_field_types,)* + } + + impl #fetch_impl_generics #path::query::Fetch<#fetch_trait_punctuated_lifetimes> for #fetch_struct_name #fetch_ty_generics #where_clause { + type Item = #item_struct_name #ty_generics; + type State = #state_struct_name #fetch_ty_generics; + + unsafe fn init(_world: &#path::world::World, state: &Self::State, _last_change_tick: u32, _change_tick: u32) -> Self { + Self { + #(#field_idents: <#fetch_init_types as #path::query::WorldQuery>::#fetch_associated_type::init(_world, &state.#field_idents, _last_change_tick, _change_tick),)* + #(#ignored_field_idents: Default::default(),)* + } + } + + const IS_DENSE: bool = true #(&& <#field_types as #path::query::WorldQuery>::#fetch_associated_type::IS_DENSE)*; + + /// SAFETY: we call `set_archetype` for each member that implements `Fetch` + #[inline] + unsafe fn set_archetype(&mut self, _state: &Self::State, _archetype: &#path::archetype::Archetype, _tables: &#path::storage::Tables) { + #(self.#field_idents.set_archetype(&_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: &#path::storage::Table) { + #(self.#field_idents.set_table(&_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 { + Self::Item { + #(#field_idents: self.#field_idents.table_fetch(_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 { + Self::Item { + #(#field_idents: self.#field_idents.archetype_fetch(_archetype_index),)* + #(#ignored_field_idents: Default::default(),)* + } + } + } + } + } + }; + + let fetch_impl = impl_fetch( + fetch_struct_attributes.is_filter, + fetch_associated_type, + fetch_struct_name.clone(), + item_struct_name, + ); + + let state_impl = quote! { + struct #state_struct_name #impl_generics #where_clause { + #(#field_idents: <#field_types as #path::query::WorldQuery>::State,)* + #(#ignored_field_idents: #ignored_field_types,)* + } + + // SAFETY: `update_component_access` and `update_archetype_component_access` are called for each item in the struct + unsafe impl #impl_generics #path::query::FetchState for #state_struct_name #ty_generics #where_clause { + 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 update_component_access(&self, _access: &mut #path::query::FilteredAccess<#path::component::ComponentId>) { + #(self.#field_idents.update_component_access(_access);)* + } + + fn update_archetype_component_access(&self, _archetype: &#path::archetype::Archetype, _access: &mut #path::query::Access<#path::archetype::ArchetypeComponentId>) { + #(self.#field_idents.update_archetype_component_access(_archetype, _access);)* + } + + fn matches_archetype(&self, _archetype: &#path::archetype::Archetype) -> bool { + true #(&& self.#field_idents.matches_archetype(_archetype))* + } + + fn matches_table(&self, _table: &#path::storage::Table) -> bool { + true #(&& self.#field_idents.matches_table(_table))* + } + } + }; + + let read_only_impl = if fetch_struct_attributes.is_filter { + quote! {} + } else if fetch_struct_attributes.is_mutable { + let fetch_impl = impl_fetch( + false, + read_only_fetch_associated_type, + read_only_fetch_struct_name.clone(), + read_only_item_struct_name.clone(), + ); + + quote! { + #fetch_impl + + impl #impl_generics #path::query::WorldQuery for #read_only_item_struct_name #ty_generics #where_clause { + type Fetch = #read_only_fetch_struct_name #ty_generics; + type State = #state_struct_name #ty_generics; + type ReadOnlyFetch = #read_only_fetch_struct_name #ty_generics; + } + } + } else { + quote! { + // Statically checks that the safety guarantee actually holds true. We need this to make + // sure that we don't compile `ReadOnlyFetch` if our struct contains nested `WorldQuery` + // members that don't implement it. + #[allow(dead_code)] + const _: () = { + fn assert_readonly() {} + + // We generate a readonly assertion for every struct member. + fn assert_all #impl_generics () #where_clause { + #(assert_readonly::<<#field_types as #path::query::WorldQuery>::Fetch>();)* + } + }; + } + }; + + let tokens = TokenStream::from(quote! { + #fetch_impl + + #state_impl + + #read_only_impl + + impl #impl_generics #path::query::WorldQuery for #struct_name #ty_generics #where_clause { + type Fetch = #fetch_struct_name #ty_generics; + type State = #state_struct_name #ty_generics; + type ReadOnlyFetch = #read_only_fetch_struct_name #ty_generics; + } + + /// SAFETY: each item in the struct is read only + unsafe impl #impl_generics #path::query::ReadOnlyFetch for #read_only_fetch_struct_name #ty_generics #where_clause {} + + // The original struct will most likely be left unused. As we don't want our users having + // to specify `#[allow(dead_code)]` for their custom queries, we are using this cursed + // workaround. + #[allow(dead_code)] + const _: () = { + fn dead_code_workaround #impl_generics (q: #struct_name #ty_generics) #where_clause { + #(q.#field_idents;)* + #(q.#ignored_field_idents;)* + } + }; + }); + tokens +} + +struct WorldQueryFieldInfo { + /// The original field type. + field_type: Type, + /// The same as `query_type` but with `'fetch` lifetime. + fetch_init_type: Type, + /// 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, + world_lifetime: &Lifetime, + fetch_lifetime: &Lifetime, +) -> 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 `{}` attribute format", WORLD_QUERY_ATTRIBUTE_NAME) + }); + + is_ignored + }); + + let attrs = field + .attrs + .iter() + .filter(|attr| { + attr.path + .get_ident() + .map_or(true, |ident| ident != WORLD_QUERY_ATTRIBUTE_NAME) + }) + .cloned() + .collect(); + + let field_type = field.ty.clone(); + let mut fetch_init_type: Type = field_type.clone(); + + replace_lifetime_for_type(&mut fetch_init_type, world_lifetime, fetch_lifetime); + + WorldQueryFieldInfo { + field_type, + fetch_init_type, + is_ignored, + attrs, + } +} + +// Fetch's HRTBs require substituting world lifetime with an additional one to make the +// implementation compile. I don't fully understand why this works though. If anyone's curious +// enough to try to find a better work around, I'll leave playground links here: +// - https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=da5e260a5c2f3e774142d60a199e854a (this fails) +// - https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=802517bb3d8f83c45ee8c0be360bb250 (this compiles) +fn replace_lifetime_for_type(ty: &mut Type, world_lifetime: &Lifetime, fetch_lifetime: &Lifetime) { + match ty { + Type::Path(ref mut path) => { + replace_world_lifetime_for_type_path(path, world_lifetime, fetch_lifetime) + } + Type::Reference(ref mut reference) => { + if let Some(lifetime) = reference.lifetime.as_mut() { + replace_lifetime(lifetime, world_lifetime, fetch_lifetime); + } + replace_lifetime_for_type(reference.elem.as_mut(), world_lifetime, fetch_lifetime); + } + Type::Tuple(tuple) => { + for ty in tuple.elems.iter_mut() { + replace_lifetime_for_type(ty, world_lifetime, fetch_lifetime); + } + } + ty => panic!("Unsupported type: {}", ty.to_token_stream()), + } +} + +fn replace_world_lifetime_for_type_path( + path: &mut TypePath, + world_lifetime: &Lifetime, + fetch_lifetime: &Lifetime, +) { + if let Some(qself) = path.qself.as_mut() { + replace_lifetime_for_type(qself.ty.as_mut(), world_lifetime, fetch_lifetime); + } + + replace_world_lifetime_for_path(&mut path.path, world_lifetime, fetch_lifetime); +} + +fn replace_world_lifetime_for_path( + path: &mut Path, + world_lifetime: &Lifetime, + fetch_lifetime: &Lifetime, +) { + for segment in path.segments.iter_mut() { + match segment.arguments { + PathArguments::None => {} + PathArguments::AngleBracketed(ref mut args) => { + for arg in args.args.iter_mut() { + match arg { + GenericArgument::Lifetime(lifetime) => { + replace_lifetime(lifetime, world_lifetime, fetch_lifetime); + } + GenericArgument::Type(ty) => { + replace_lifetime_for_type(ty, world_lifetime, fetch_lifetime) + } + _ => {} + } + } + } + PathArguments::Parenthesized(ref mut args) => { + for input in args.inputs.iter_mut() { + replace_lifetime_for_type(input, world_lifetime, fetch_lifetime); + } + if let ReturnType::Type(_, _) = args.output { + panic!("Function types aren't supported"); + } + } + } + } +} + +fn replace_lifetime(lifetime: &mut Lifetime, world_lifetime: &Lifetime, fetch_lifetime: &Lifetime) { + if lifetime.ident == world_lifetime.ident { + lifetime.ident = fetch_lifetime.ident.clone(); + } +} diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 7b1ab6b7973742..b1f1f63d6c92b9 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -1,7 +1,9 @@ extern crate proc_macro; mod component; +mod fetch; +use crate::fetch::derive_world_query_impl; use bevy_macro_utils::{derive_label, get_named_struct_fields, BevyManifest}; use proc_macro::TokenStream; use proc_macro2::Span; @@ -425,6 +427,13 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { }) } +/// Implement `WorldQuery` to use a struct as a parameter in a query +#[proc_macro_derive(WorldQuery, attributes(world_query))] +pub fn derive_world_query(input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as DeriveInput); + derive_world_query_impl(ast) +} + #[proc_macro_derive(SystemLabel)] pub fn derive_system_label(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 8324da9bc9a812..a99e892bf6f4d1 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -8,6 +8,7 @@ use crate::{ world::{Mut, World}, }; use bevy_ecs_macros::all_tuples; +pub use bevy_ecs_macros::WorldQuery; use std::{ cell::UnsafeCell, marker::PhantomData, @@ -40,6 +41,267 @@ use std::{ /// For more information on these consult the item's corresponding documentation. /// /// [`Or`]: crate::query::Or +/// +/// # Derive +/// +/// This trait can be derived with the [`derive@super::WorldQuery`] macro. +/// +/// You may want to implement a custom query with the derive macro for the following reasons: +/// - Named structs can be clearer and easier to use than complex query tuples. Access via struct +/// fields is more convenient than destructuring tuples or accessing them via `q.0, q.1, ...` +/// pattern and saves a lot of maintenance burden when adding or removing components. +/// - Nested queries enable the composition pattern and makes query types easier to re-use. +/// - You can bypass the limit of 15 components that exists for query tuples. +/// +/// Implementing the trait manually can allow for a fundamentally new type of behaviour. +/// +/// 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`] and +/// [`WorldQuery::State`] associated types respectively. +/// +/// The derive macro requires every struct field to implement the `WorldQuery` trait. +/// +/// **Note:** currently, the macro only supports named structs. +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// use bevy_ecs::query::WorldQuery; +/// +/// #[derive(Component)] +/// struct Foo; +/// #[derive(Component)] +/// struct Bar; +/// +/// #[derive(WorldQuery)] +/// struct MyQuery<'w> { +/// entity: Entity, +/// foo: &'w Foo, +/// bar: Option<&'w Bar>, +/// } +/// +/// fn my_system(query: Query) { +/// for q in query.iter() { +/// // Note the type of the returned item. +/// let q: MyQueryItem<'_> = q; +/// q.foo; +/// } +/// } +/// +/// # bevy_ecs::system::assert_is_system(my_system); +/// ``` +/// +/// ## Mutable queries +/// +/// All queries that are derived with the `WorldQuery` macro provide only an immutable access by default. +/// If you need a mutable access to components, you can mark a struct with the `mutable` attribute. +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// use bevy_ecs::query::WorldQuery; +/// +/// #[derive(Component)] +/// struct Health(f32); +/// #[derive(Component)] +/// struct Buff(f32); +/// +/// #[derive(WorldQuery)] +/// #[world_query(mutable)] +/// struct HealthQuery<'w> { +/// health: &'w mut Health, +/// buff: Option<&'w mut Buff>, +/// } +/// +/// // This implementation is only available when iterating with `iter_mut`. +/// impl<'w> HealthQueryItem<'w> { +/// fn damage(&mut self, value: f32) { +/// self.health.0 -= value; +/// } +/// +/// fn total(&self) -> f32 { +/// self.health.0 + self.buff.as_deref().map_or(0.0, |Buff(buff)| *buff) +/// } +/// } +/// +/// // If you want to use it with `iter`, you'll need to write an additional implementation. +/// impl<'w> HealthQueryReadOnlyItem<'w> { +/// fn total(&self) -> f32 { +/// self.health.0 + self.buff.map_or(0.0, |Buff(buff)| *buff) +/// } +/// } +/// +/// fn my_system(mut health_query: Query) { +/// // Iterator's item is `HealthQueryReadOnlyItem`. +/// for health in health_query.iter() { +/// println!("Total: {}", health.total()); +/// } +/// // Iterator's item is `HealthQueryItem`. +/// for mut health in health_query.iter_mut() { +/// health.damage(1.0); +/// println!("Total (mut): {}", health.total()); +/// } +/// } +/// +/// # bevy_ecs::system::assert_is_system(my_system); +/// ``` +/// +/// **Note:** if you omit the `mutable` attribute for a query that doesn't implement +/// `ReadOnlyFetch`, compilation will fail. We insert static checks as in the example above for +/// every query component and a nested query. +/// (The checks neither affect the runtime, nor pollute your local namespace.) +/// +/// ```compile_fail +/// # use bevy_ecs::prelude::*; +/// use bevy_ecs::query::WorldQuery; +/// +/// #[derive(Component)] +/// struct Foo; +/// #[derive(Component)] +/// struct Bar; +/// +/// #[derive(WorldQuery)] +/// struct FooQuery<'w> { +/// foo: &'w Foo, +/// bar_query: BarQuery<'w>, +/// } +/// +/// #[derive(WorldQuery)] +/// #[world_query(mutable)] +/// struct BarQuery<'w> { +/// bar: &'w mut Bar, +/// } +/// ``` +/// +/// ## Derives for items +/// +/// If you want query items to have derivable traits, you can pass them with using +/// the `world_query(derive)` attribute. When the `WorldQuery` macro generates the structs +/// for query items, it doesn't automatically inherit derives of a query itself. Since derive macros +/// can't access information about other derives, they need to be passed manually with the +/// `world_query(derive)` attribute. +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// use bevy_ecs::query::WorldQuery; +/// +/// #[derive(Component, Debug)] +/// struct Foo; +/// +/// #[derive(WorldQuery)] +/// #[world_query(mutable, derive(Debug))] +/// struct FooQuery<'w> { +/// foo: &'w Foo, +/// } +/// +/// fn assert_debug() {} +/// +/// assert_debug::(); +/// assert_debug::(); +/// ``` +/// +/// ## Nested queries +/// +/// Using nested queries enable the composition pattern, which makes it possible to re-use other +/// query types. All types that implement [`WorldQuery`] (including the ones that use this derive +/// macro) are supported. +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// use bevy_ecs::query::WorldQuery; +/// +/// #[derive(Component)] +/// struct Foo; +/// #[derive(Component)] +/// struct Bar; +/// #[derive(Component)] +/// struct OptionalFoo; +/// #[derive(Component)] +/// struct OptionalBar; +/// +/// #[derive(WorldQuery)] +/// struct MyQuery<'w> { +/// foo: FooQuery<'w>, +/// bar: (&'w Bar, Option<&'w OptionalBar>) +/// } +/// +/// #[derive(WorldQuery)] +/// struct FooQuery<'w> { +/// foo: &'w Foo, +/// optional_foo: Option<&'w OptionalFoo>, +/// } +/// +/// // You can also compose derived queries with regular ones in tuples. +/// fn my_system(query: Query<(&Foo, MyQuery, FooQuery)>) { +/// for (foo, my_query, foo_query) in query.iter() { +/// foo; my_query; foo_query; +/// } +/// } +/// +/// # bevy_ecs::system::assert_is_system(my_system); +/// ``` +/// +/// ## Ignored fields +/// +/// The macro also supports `ignore` attribute for struct members. Fields marked with this attribute +/// must implement the `Default` trait. +/// +/// This example demonstrates a query that would iterate over every entity. +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// use bevy_ecs::query::WorldQuery; +/// +/// #[derive(WorldQuery, Debug)] +/// struct EmptyQuery<'w> { +/// #[world_query(ignore)] +/// _w: std::marker::PhantomData<&'w ()>, +/// } +/// +/// fn my_system(query: Query) { +/// for _ in query.iter() {} +/// } +/// +/// # bevy_ecs::system::assert_is_system(my_system); +/// ``` +/// +/// ## Filters +/// +/// Using [`derive@super::WorldQuery`] macro in conjunctions with the `#[world_query(filter)]` +/// attribute allows creating custom query filters. +/// +/// To do so, all fields in the struct must be filters themselves (their [`WorldQuery::Fetch`] +/// associated types should implement [`super::FilterFetch`]). +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// use bevy_ecs::{query::WorldQuery, component::Component}; +/// +/// #[derive(Component)] +/// struct Foo; +/// #[derive(Component)] +/// struct Bar; +/// #[derive(Component)] +/// struct Baz; +/// #[derive(Component)] +/// struct Qux; +/// +/// #[derive(WorldQuery)] +/// #[world_query(filter)] +/// struct MyFilter { +/// _foo: With, +/// _bar: With, +/// _or: Or<(With, Changed, Added)>, +/// _generic_tuple: (With, Without

), +/// #[world_query(ignore)] +/// _tp: std::marker::PhantomData<(T, P)>, +/// } +/// +/// fn my_system(query: Query>) { +/// for _ in query.iter() {} +/// } +/// +/// # bevy_ecs::system::assert_is_system(my_system); +/// ``` pub trait WorldQuery { type Fetch: for<'world, 'state> Fetch<'world, 'state, State = Self::State>; type State: FetchState; @@ -49,6 +311,11 @@ pub trait WorldQuery { pub type QueryItem<'w, 's, Q> = <::Fetch as Fetch<'w, 's>>::Item; +/// 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`] and +/// [`WorldQuery::State`] types that are essential for fetching component data. pub trait Fetch<'world, 'state>: Sized { type Item; type State: FetchState; diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index e84ec3db263943..6c936f2d0a5383 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -11,6 +11,9 @@ use std::{cell::UnsafeCell, marker::PhantomData, ptr}; /// Extension trait for [`Fetch`] containing methods used by query filters. /// This trait exists to allow "short circuit" behaviors for relevant query filter fetches. +/// +/// This trait is automatically implemented for every type that implements [`Fetch`] trait and +/// specifies `bool` as the associated type for [`Fetch::Item`]. pub trait FilterFetch: for<'w, 's> Fetch<'w, 's> { /// # Safety /// diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 9bc4b22efd17b1..37d4c57b938562 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -189,7 +189,7 @@ fn assert_component_access_compatibility( .collect::>(); let accesses = conflicting_components.join(", "); panic!("error[B0001]: Query<{}, {}> in system {} accesses component(s) {} in a way that conflicts with a previous system parameter. Consider using `Without` to create disjoint Queries or merging conflicting Queries into a `QuerySet`.", - query_type, filter_type, system_name, accesses); + query_type, filter_type, system_name, accesses); } pub struct QuerySet<'w, 's, T> { @@ -204,6 +204,7 @@ pub struct QuerySetState(T); impl_query_set!(); pub trait Resource: Send + Sync + 'static {} + impl Resource for T where T: Send + Sync + 'static {} /// Shared borrow of a resource. diff --git a/examples/README.md b/examples/README.md index d147b0a79c773c..745d93ef5d5218 100644 --- a/examples/README.md +++ b/examples/README.md @@ -166,6 +166,7 @@ Example | File | Description --- | --- | --- `ecs_guide` | [`ecs/ecs_guide.rs`](./ecs/ecs_guide.rs) | Full guide to Bevy's ECS `component_change_detection` | [`ecs/component_change_detection.rs`](./ecs/component_change_detection.rs) | Change detection on components +`custom_query_param` | [`ecs/custom_query_param.rs`](./ecs/custom_query_param.rs) | Groups commonly used compound queries and query filters into a single type `event` | [`ecs/event.rs`](./ecs/event.rs) | Illustrates event creation, activation, and reception `fixed_timestep` | [`ecs/fixed_timestep.rs`](./ecs/fixed_timestep.rs) | Shows how to create systems that run every fixed timestep, rather than every tick `generic_system` | [`ecs/generic_system.rs`](./ecs/generic_system.rs) | Shows how to create systems that can be reused with different types diff --git a/examples/ecs/custom_query_param.rs b/examples/ecs/custom_query_param.rs new file mode 100644 index 00000000000000..67a0369ee85af8 --- /dev/null +++ b/examples/ecs/custom_query_param.rs @@ -0,0 +1,200 @@ +use bevy::{ + ecs::{component::Component, query::WorldQuery}, + prelude::*, +}; +use std::{fmt::Debug, marker::PhantomData}; + +/// This examples illustrates the usage of the `WorldQuery` derive macro, which allows +/// defining custom query and filter types. +/// +/// While regular tuple queries work great in most of simple scenarios, using custom queries +/// declared as named structs can bring the following advantages: +/// - They help to avoid destructuring or using `q.0, q.1, ...` access pattern. +/// - Adding, removing components or changing items order with structs greatly reduces maintenance +/// burden, as you don't need to update statements that destructure tuples, care about order +/// of elements, etc. Instead, you can just add or remove places where a certain element is used. +/// - Named structs enable the composition pattern, that makes query types easier to re-use. +/// - You can bypass the limit of 15 components that exists for query tuples. +/// +/// For more details on the `WorldQuery` derive macro, see the trait documentation. +fn main() { + App::new() + .add_startup_system(spawn) + .add_system(print_components_read_only.label("print_components_read_only")) + .add_system( + print_components_iter_mut + .label("print_components_iter_mut") + .after("print_components_read_only"), + ) + .add_system( + print_components_iter + .label("print_components_iter") + .after("print_components_iter_mut"), + ) + .add_system(print_components_tuple.after("print_components_iter")) + .run(); +} + +#[derive(Component, Debug)] +struct ComponentA; +#[derive(Component, Debug)] +struct ComponentB; +#[derive(Component, Debug)] +struct ComponentC; +#[derive(Component, Debug)] +struct ComponentD; +#[derive(Component, Debug)] +struct ComponentZ; + +#[derive(WorldQuery)] +#[world_query(derive(Debug))] +struct ReadOnlyCustomQuery<'w, T: Component + Debug, P: Component + Debug> { + entity: Entity, + a: &'w ComponentA, + b: Option<&'w ComponentB>, + nested: NestedQuery<'w>, + optional_nested: Option>, + optional_tuple: Option<(&'w ComponentB, &'w ComponentZ)>, + generic: GenericQuery<'w, T, P>, + empty: EmptyQuery<'w>, +} + +fn print_components_read_only( + query: Query, QueryFilter>, +) { + println!("Print components (read_only):"); + for e in query.iter() { + println!("Entity: {:?}", e.entity); + println!("A: {:?}", e.a); + println!("B: {:?}", e.b); + println!("Nested: {:?}", e.nested); + println!("Optional nested: {:?}", e.optional_nested); + println!("Optional tuple: {:?}", e.optional_tuple); + println!("Generic: {:?}", e.generic); + } + println!(); +} + +// If you are going to mutate the data in a query, you must mark it with the `mutable` attribute. +// The `WorldQuery` derive macro will still create a read-only version, which will be have `ReadOnly` +// suffix. +// Note: if you want to use derive macros with read-only query variants, you need to pass them with +// using the `derive` attribute. +#[derive(WorldQuery)] +#[world_query(mutable, derive(Debug))] +struct CustomQuery<'w, T: Component + Debug, P: Component + Debug> { + entity: Entity, + a: &'w mut ComponentA, + b: Option<&'w mut ComponentB>, + nested: NestedQuery<'w>, + optional_nested: Option>, + optional_tuple: Option<(NestedQuery<'w>, &'w mut ComponentZ)>, + generic: GenericQuery<'w, T, P>, + empty: EmptyQuery<'w>, +} + +// This is a valid query as well, which would iterate over every entity. +#[derive(WorldQuery)] +#[world_query(derive(Debug))] +struct EmptyQuery<'w> { + // The derive macro expect a lifetime. As Rust doesn't allow unused lifetimes, we need + // to use `PhantomData` as a work around. + #[world_query(ignore)] + _w: std::marker::PhantomData<&'w ()>, +} + +#[derive(WorldQuery)] +#[world_query(derive(Debug))] +struct NestedQuery<'w> { + c: &'w ComponentC, + d: Option<&'w ComponentD>, +} + +#[derive(WorldQuery)] +#[world_query(derive(Debug))] +struct GenericQuery<'w, T: Component, P: Component> { + generic: (&'w T, &'w P), +} + +#[derive(WorldQuery)] +#[world_query(filter)] +struct QueryFilter { + _c: With, + _d: With, + _or: Or<(Added, Changed, Without)>, + _generic_tuple: (With, With

), + #[world_query(ignore)] + _tp: PhantomData<(T, P)>, +} + +fn spawn(mut commands: Commands) { + commands + .spawn() + .insert(ComponentA) + .insert(ComponentB) + .insert(ComponentC) + .insert(ComponentD); +} + +fn print_components_iter_mut( + mut query: Query, QueryFilter>, +) { + println!("Print components (iter_mut):"); + for e in query.iter_mut() { + // Re-declaring the variable to illustrate the type of the actual iterator item. + let e: CustomQueryItem<'_, _, _> = e; + println!("Entity: {:?}", e.entity); + println!("A: {:?}", e.a); + println!("B: {:?}", e.b); + println!("Optional nested: {:?}", e.optional_nested); + println!("Optional tuple: {:?}", e.optional_tuple); + println!("Nested: {:?}", e.nested); + println!("Generic: {:?}", e.generic); + } + println!(); +} + +fn print_components_iter( + query: Query, QueryFilter>, +) { + println!("Print components (iter):"); + for e in query.iter() { + // Re-declaring the variable to illustrate the type of the actual iterator item. + let e: CustomQueryReadOnlyItem<'_, _, _> = e; + println!("Entity: {:?}", e.entity); + println!("A: {:?}", e.a); + println!("B: {:?}", e.b); + println!("Nested: {:?}", e.nested); + println!("Generic: {:?}", e.generic); + } + println!(); +} + +type NestedTupleQuery<'w> = (&'w ComponentC, &'w ComponentD); +type GenericTupleQuery<'w, T, P> = (&'w T, &'w P); + +fn print_components_tuple( + query: Query< + ( + Entity, + &ComponentA, + &ComponentB, + NestedTupleQuery, + GenericTupleQuery, + ), + ( + With, + With, + Or<(Added, Changed, Without)>, + ), + >, +) { + println!("Print components (tuple):"); + for (entity, a, b, nested, (generic_c, generic_d)) in query.iter() { + println!("Entity: {:?}", entity); + println!("A: {:?}", a); + println!("B: {:?}", b); + println!("Nested: {:?} {:?}", nested.0, nested.1); + println!("Generic: {:?} {:?}", generic_c, generic_d); + } +}