Extend the WorldQuery macro to tuple structs (#8119)
				
					
				
			# Objective The `#[derive(WorldQuery)]` macro currently only supports structs with named fields. Same motivation as #6957. Remove sharp edges from the derive macro, make it just work more often. ## Solution Support tuple structs. --- ## Changelog + Added support for tuple structs to the `#[derive(WorldQuery)]` macro.
This commit is contained in:
		
							parent
							
								
									2aaaed7f69
								
							
						
					
					
						commit
						b423e6ee15
					
				| @ -1,12 +1,12 @@ | |||||||
| use bevy_macro_utils::ensure_no_collision; | use bevy_macro_utils::ensure_no_collision; | ||||||
| use proc_macro::TokenStream; | use proc_macro::TokenStream; | ||||||
| use proc_macro2::{Ident, Span}; | use proc_macro2::{Ident, Span}; | ||||||
| use quote::{quote, ToTokens}; | use quote::{format_ident, quote, ToTokens}; | ||||||
| use syn::{ | use syn::{ | ||||||
|     parse::{Parse, ParseStream}, |     parse::{Parse, ParseStream}, | ||||||
|     parse_macro_input, parse_quote, |     parse_macro_input, parse_quote, | ||||||
|     punctuated::Punctuated, |     punctuated::Punctuated, | ||||||
|     Attribute, Data, DataStruct, DeriveInput, Field, Fields, |     Attribute, Data, DataStruct, DeriveInput, Field, Index, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| use crate::bevy_ecs_path; | use crate::bevy_ecs_path; | ||||||
| @ -116,34 +116,49 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream { | |||||||
|         fetch_struct_name.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
 |     // Generate a name for the state struct that doesn't conflict
 | ||||||
|     // with the struct definition.
 |     // with the struct definition.
 | ||||||
|     let state_struct_name = Ident::new(&format!("{struct_name}State"), Span::call_site()); |     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 state_struct_name = ensure_no_collision(state_struct_name, tokens); | ||||||
| 
 | 
 | ||||||
|     let fields = match &ast.data { |     let Data::Struct(DataStruct { fields, .. }) = &ast.data else { | ||||||
|         Data::Struct(DataStruct { |         return syn::Error::new( | ||||||
|             fields: Fields::Named(fields), |             Span::call_site(), | ||||||
|             .. |             "#[derive(WorldQuery)]` only supports structs", | ||||||
|         }) => &fields.named, |         ) | ||||||
|         _ => panic!("Expected a struct with named fields"), |         .into_compile_error() | ||||||
|  |         .into() | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     let mut field_attrs = Vec::new(); |     let mut field_attrs = Vec::new(); | ||||||
|     let mut field_visibilities = Vec::new(); |     let mut field_visibilities = Vec::new(); | ||||||
|     let mut field_idents = Vec::new(); |     let mut field_idents = Vec::new(); | ||||||
|  |     let mut named_field_idents = Vec::new(); | ||||||
|     let mut field_types = Vec::new(); |     let mut field_types = Vec::new(); | ||||||
|     let mut read_only_field_types = Vec::new(); |     let mut read_only_field_types = Vec::new(); | ||||||
| 
 |     for (i, field) in fields.iter().enumerate() { | ||||||
|     for field in fields { |  | ||||||
|         let attrs = match read_world_query_field_info(field) { |         let attrs = match read_world_query_field_info(field) { | ||||||
|             Ok(WorldQueryFieldInfo { attrs }) => attrs, |             Ok(WorldQueryFieldInfo { attrs }) => attrs, | ||||||
|             Err(e) => return e.into_compile_error().into(), |             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_attrs.push(attrs); | ||||||
|         field_visibilities.push(field.vis.clone()); |         field_visibilities.push(field.vis.clone()); | ||||||
|         field_idents.push(field.ident.as_ref().unwrap().clone()); |  | ||||||
|         let field_ty = field.ty.clone(); |         let field_ty = field.ty.clone(); | ||||||
|         field_types.push(quote!(#field_ty)); |         field_types.push(quote!(#field_ty)); | ||||||
|         read_only_field_types.push(quote!(<#field_ty as #path::query::WorldQuery>::ReadOnly)); |         read_only_field_types.push(quote!(<#field_ty as #path::query::WorldQuery>::ReadOnly)); | ||||||
| @ -176,7 +191,8 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream { | |||||||
|             &field_types |             &field_types | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         let item_struct = quote! { |         let item_struct = match fields { | ||||||
|  |             syn::Fields::Named(_) => quote! { | ||||||
|                 #derive_macro_call |                 #derive_macro_call | ||||||
|                 #[doc = "Automatically generated [`WorldQuery`] item type for [`"] |                 #[doc = "Automatically generated [`WorldQuery`] item type for [`"] | ||||||
|                 #[doc = stringify!(#struct_name)] |                 #[doc = stringify!(#struct_name)] | ||||||
| @ -185,6 +201,24 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream { | |||||||
|                 #visibility struct #item_struct_name #user_impl_generics_with_world #user_where_clauses_with_world { |                 #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>,)* |                     #(#(#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; | ||||||
|  |             }, | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         let query_impl = quote! { |         let query_impl = quote! { | ||||||
| @ -194,7 +228,8 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream { | |||||||
|             #[doc = "`], used to define the world data accessed by this query."] |             #[doc = "`], used to define the world data accessed by this query."] | ||||||
|             #[automatically_derived] |             #[automatically_derived] | ||||||
|             #visibility struct #fetch_struct_name #user_impl_generics_with_world #user_where_clauses_with_world { |             #visibility struct #fetch_struct_name #user_impl_generics_with_world #user_where_clauses_with_world { | ||||||
|                 #(#field_idents: <#field_types as #path::query::WorldQuery>::Fetch<'__w>,)* |                 #(#named_field_idents: <#field_types as #path::query::WorldQuery>::Fetch<'__w>,)* | ||||||
|  |                 #marker_name: &'__w (), | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             // SAFETY: `update_component_access` and `update_archetype_component_access` are called on every field
 |             // SAFETY: `update_component_access` and `update_archetype_component_access` are called on every field
 | ||||||
| @ -223,14 +258,15 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream { | |||||||
|                     _this_run: #path::component::Tick, |                     _this_run: #path::component::Tick, | ||||||
|                 ) -> <Self as #path::query::WorldQuery>::Fetch<'__w> { |                 ) -> <Self as #path::query::WorldQuery>::Fetch<'__w> { | ||||||
|                     #fetch_struct_name { |                     #fetch_struct_name { | ||||||
|                         #(#field_idents: |                         #(#named_field_idents: | ||||||
|                             <#field_types>::init_fetch( |                             <#field_types>::init_fetch( | ||||||
|                                 _world, |                                 _world, | ||||||
|                                 &state.#field_idents, |                                 &state.#named_field_idents, | ||||||
|                                 _last_run, |                                 _last_run, | ||||||
|                                 _this_run, |                                 _this_run, | ||||||
|                             ), |                             ), | ||||||
|                         )* |                         )* | ||||||
|  |                         #marker_name: &(), | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
| @ -239,8 +275,9 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream { | |||||||
|                 ) -> <Self as #path::query::WorldQuery>::Fetch<'__w> { |                 ) -> <Self as #path::query::WorldQuery>::Fetch<'__w> { | ||||||
|                     #fetch_struct_name { |                     #fetch_struct_name { | ||||||
|                         #( |                         #( | ||||||
|                             #field_idents: <#field_types>::clone_fetch(& _fetch. #field_idents), |                             #named_field_idents: <#field_types>::clone_fetch(& _fetch. #named_field_idents), | ||||||
|                         )* |                         )* | ||||||
|  |                         #marker_name: &(), | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
| @ -256,7 +293,7 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream { | |||||||
|                     _archetype: &'__w #path::archetype::Archetype, |                     _archetype: &'__w #path::archetype::Archetype, | ||||||
|                     _table: &'__w #path::storage::Table |                     _table: &'__w #path::storage::Table | ||||||
|                 ) { |                 ) { | ||||||
|                     #(<#field_types>::set_archetype(&mut _fetch.#field_idents, &_state.#field_idents, _archetype, _table);)* |                     #(<#field_types>::set_archetype(&mut _fetch.#named_field_idents, &_state.#named_field_idents, _archetype, _table);)* | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 /// SAFETY: we call `set_table` for each member that implements `Fetch`
 |                 /// SAFETY: we call `set_table` for each member that implements `Fetch`
 | ||||||
| @ -266,7 +303,7 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream { | |||||||
|                     _state: &Self::State, |                     _state: &Self::State, | ||||||
|                     _table: &'__w #path::storage::Table |                     _table: &'__w #path::storage::Table | ||||||
|                 ) { |                 ) { | ||||||
|                     #(<#field_types>::set_table(&mut _fetch.#field_idents, &_state.#field_idents, _table);)* |                     #(<#field_types>::set_table(&mut _fetch.#named_field_idents, &_state.#named_field_idents, _table);)* | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 /// SAFETY: we call `fetch` for each member that implements `Fetch`.
 |                 /// SAFETY: we call `fetch` for each member that implements `Fetch`.
 | ||||||
| @ -277,7 +314,7 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream { | |||||||
|                     _table_row: #path::storage::TableRow, |                     _table_row: #path::storage::TableRow, | ||||||
|                 ) -> <Self as #path::query::WorldQuery>::Item<'__w> { |                 ) -> <Self as #path::query::WorldQuery>::Item<'__w> { | ||||||
|                     Self::Item { |                     Self::Item { | ||||||
|                         #(#field_idents: <#field_types>::fetch(&mut _fetch.#field_idents, _entity, _table_row),)* |                         #(#field_idents: <#field_types>::fetch(&mut _fetch.#named_field_idents, _entity, _table_row),)* | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
| @ -288,11 +325,11 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream { | |||||||
|                     _entity: #path::entity::Entity, |                     _entity: #path::entity::Entity, | ||||||
|                     _table_row: #path::storage::TableRow, |                     _table_row: #path::storage::TableRow, | ||||||
|                 ) -> bool { |                 ) -> bool { | ||||||
|                     true #(&& <#field_types>::filter_fetch(&mut _fetch.#field_idents, _entity, _table_row))* |                     true #(&& <#field_types>::filter_fetch(&mut _fetch.#named_field_idents, _entity, _table_row))* | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 fn update_component_access(state: &Self::State, _access: &mut #path::query::FilteredAccess<#path::component::ComponentId>) { |                 fn update_component_access(state: &Self::State, _access: &mut #path::query::FilteredAccess<#path::component::ComponentId>) { | ||||||
|                     #( <#field_types>::update_component_access(&state.#field_idents, _access); )* |                     #( <#field_types>::update_component_access(&state.#named_field_idents, _access); )* | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 fn update_archetype_component_access( |                 fn update_archetype_component_access( | ||||||
| @ -301,18 +338,18 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream { | |||||||
|                     _access: &mut #path::query::Access<#path::archetype::ArchetypeComponentId> |                     _access: &mut #path::query::Access<#path::archetype::ArchetypeComponentId> | ||||||
|                 ) { |                 ) { | ||||||
|                     #( |                     #( | ||||||
|                         <#field_types>::update_archetype_component_access(&state.#field_idents, _archetype, _access); |                         <#field_types>::update_archetype_component_access(&state.#named_field_idents, _archetype, _access); | ||||||
|                     )* |                     )* | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 fn init_state(world: &mut #path::world::World) -> #state_struct_name #user_ty_generics { |                 fn init_state(world: &mut #path::world::World) -> #state_struct_name #user_ty_generics { | ||||||
|                     #state_struct_name { |                     #state_struct_name { | ||||||
|                         #(#field_idents: <#field_types>::init_state(world),)* |                         #(#named_field_idents: <#field_types>::init_state(world),)* | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 fn matches_component_set(state: &Self::State, _set_contains_id: &impl Fn(#path::component::ComponentId) -> bool) -> bool { |                 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))* |                     true #(&& <#field_types>::matches_component_set(&state.#named_field_idents, _set_contains_id))* | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         }; |         }; | ||||||
| @ -328,7 +365,7 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream { | |||||||
|             #[doc = "`]."] |             #[doc = "`]."] | ||||||
|             #[automatically_derived] |             #[automatically_derived] | ||||||
|             #visibility struct #read_only_struct_name #user_impl_generics #user_where_clauses { |             #visibility struct #read_only_struct_name #user_impl_generics #user_where_clauses { | ||||||
|                 #( #field_visibilities #field_idents: #read_only_field_types, )* |                 #( #field_visibilities #named_field_idents: #read_only_field_types, )* | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             #readonly_state |             #readonly_state | ||||||
| @ -374,7 +411,7 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream { | |||||||
|             #[doc = "`], used for caching."] |             #[doc = "`], used for caching."] | ||||||
|             #[automatically_derived] |             #[automatically_derived] | ||||||
|             #visibility struct #state_struct_name #user_impl_generics #user_where_clauses { |             #visibility struct #state_struct_name #user_impl_generics #user_where_clauses { | ||||||
|                 #(#field_idents: <#field_types as #path::query::WorldQuery>::State,)* |                 #(#named_field_idents: <#field_types as #path::query::WorldQuery>::State,)* | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             #mutable_impl |             #mutable_impl | ||||||
|  | |||||||
| @ -55,8 +55,7 @@ use std::{cell::UnsafeCell, marker::PhantomData}; | |||||||
| /// - Methods can be implemented for the query items.
 | /// - Methods can be implemented for the query items.
 | ||||||
| /// - There is no hardcoded limit on the number of elements.
 | /// - There is no hardcoded limit on the number of elements.
 | ||||||
| ///
 | ///
 | ||||||
| /// This trait can only be derived if each field also implements `WorldQuery`.
 | /// This trait can only be derived for structs, if each field also implements `WorldQuery`.
 | ||||||
| /// The derive macro only supports regular structs (structs with named fields).
 |  | ||||||
| ///
 | ///
 | ||||||
| /// ```
 | /// ```
 | ||||||
| /// # use bevy_ecs::prelude::*;
 | /// # use bevy_ecs::prelude::*;
 | ||||||
| @ -1468,11 +1467,37 @@ unsafe impl<T: ?Sized> ReadOnlyWorldQuery for PhantomData<T> {} | |||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| mod tests { | mod tests { | ||||||
|     use super::*; |     use super::*; | ||||||
|     use crate::{self as bevy_ecs, system::Query}; |     use crate::{ | ||||||
|  |         self as bevy_ecs, | ||||||
|  |         system::{assert_is_system, Query}, | ||||||
|  |     }; | ||||||
| 
 | 
 | ||||||
|     #[derive(Component)] |     #[derive(Component)] | ||||||
|     pub struct A; |     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.
 |     // Compile test for https://github.com/bevyengine/bevy/pull/8030.
 | ||||||
|     #[test] |     #[test] | ||||||
|     fn world_query_phantom_data() { |     fn world_query_phantom_data() { | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 JoJoJet
						JoJoJet