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 proc_macro::TokenStream;
|
||||
use proc_macro2::{Ident, Span};
|
||||
use quote::{quote, ToTokens};
|
||||
use quote::{format_ident, quote, ToTokens};
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
parse_macro_input, parse_quote,
|
||||
punctuated::Punctuated,
|
||||
Attribute, Data, DataStruct, DeriveInput, Field, Fields,
|
||||
Attribute, Data, DataStruct, DeriveInput, Field, Index,
|
||||
};
|
||||
|
||||
use crate::bevy_ecs_path;
|
||||
@ -116,34 +116,49 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream {
|
||||
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 fields = match &ast.data {
|
||||
Data::Struct(DataStruct {
|
||||
fields: Fields::Named(fields),
|
||||
..
|
||||
}) => &fields.named,
|
||||
_ => panic!("Expected a struct with named fields"),
|
||||
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 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 field in fields {
|
||||
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());
|
||||
field_idents.push(field.ident.as_ref().unwrap().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));
|
||||
@ -176,15 +191,34 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream {
|
||||
&field_types
|
||||
};
|
||||
|
||||
let item_struct = 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>,)*
|
||||
}
|
||||
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;
|
||||
},
|
||||
};
|
||||
|
||||
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."]
|
||||
#[automatically_derived]
|
||||
#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
|
||||
@ -223,14 +258,15 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream {
|
||||
_this_run: #path::component::Tick,
|
||||
) -> <Self as #path::query::WorldQuery>::Fetch<'__w> {
|
||||
#fetch_struct_name {
|
||||
#(#field_idents:
|
||||
#(#named_field_idents:
|
||||
<#field_types>::init_fetch(
|
||||
_world,
|
||||
&state.#field_idents,
|
||||
&state.#named_field_idents,
|
||||
_last_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> {
|
||||
#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,
|
||||
_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`
|
||||
@ -266,7 +303,7 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream {
|
||||
_state: &Self::State,
|
||||
_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`.
|
||||
@ -277,7 +314,7 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream {
|
||||
_table_row: #path::storage::TableRow,
|
||||
) -> <Self as #path::query::WorldQuery>::Item<'__w> {
|
||||
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,
|
||||
_table_row: #path::storage::TableRow,
|
||||
) -> 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>) {
|
||||
#( <#field_types>::update_component_access(&state.#field_idents, _access); )*
|
||||
#( <#field_types>::update_component_access(&state.#named_field_idents, _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>
|
||||
) {
|
||||
#(
|
||||
<#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 {
|
||||
#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 {
|
||||
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 = "`]."]
|
||||
#[automatically_derived]
|
||||
#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
|
||||
@ -374,7 +411,7 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream {
|
||||
#[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,)*
|
||||
#(#named_field_idents: <#field_types as #path::query::WorldQuery>::State,)*
|
||||
}
|
||||
|
||||
#mutable_impl
|
||||
|
||||
@ -55,8 +55,7 @@ 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 if each field also implements `WorldQuery`.
|
||||
/// The derive macro only supports regular structs (structs with named fields).
|
||||
/// This trait can only be derived for structs, if each field also implements `WorldQuery`.
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
@ -1468,11 +1467,37 @@ unsafe impl<T: ?Sized> ReadOnlyWorldQuery for PhantomData<T> {}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{self as bevy_ecs, system::Query};
|
||||
use crate::{
|
||||
self as bevy_ecs,
|
||||
system::{assert_is_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() {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user