Implement WorldQuery derive macro (#2713)

# Objective

- Closes #786
- Closes #2252
- Closes #2588

This PR implements a derive macro that allows users to define their queries as structs with named fields.

## Example

```rust
#[derive(WorldQuery)]
#[world_query(derive(Debug))]
struct NumQuery<'w, T: Component, P: Component> {
    entity: Entity,
    u: UNumQuery<'w>,
    generic: GenericQuery<'w, T, P>,
}

#[derive(WorldQuery)]
#[world_query(derive(Debug))]
struct UNumQuery<'w> {
    u_16: &'w u16,
    u_32_opt: Option<&'w u32>,
}

#[derive(WorldQuery)]
#[world_query(derive(Debug))]
struct GenericQuery<'w, T: Component, P: Component> {
    generic: (&'w T, &'w P),
}

#[derive(WorldQuery)]
#[world_query(filter)]
struct NumQueryFilter<T: Component, P: Component> {
    _u_16: With<u16>,
    _u_32: With<u32>,
    _or: Or<(With<i16>, Changed<u16>, Added<u32>)>,
    _generic_tuple: (With<T>, With<P>),
    _without: Without<Option<u16>>,
    _tp: PhantomData<(T, P)>,
}

fn print_nums_readonly(query: Query<NumQuery<u64, i64>, NumQueryFilter<u64, i64>>) {
    for num in query.iter() {
        println!("{:#?}", num);
    }
}

#[derive(WorldQuery)]
#[world_query(mutable, derive(Debug))]
struct MutNumQuery<'w, T: Component, P: Component> {
    i_16: &'w mut i16,
    i_32_opt: Option<&'w mut i32>,
}

fn print_nums(mut query: Query<MutNumQuery, NumQueryFilter<u64, i64>>) {
    for num in query.iter_mut() {
        println!("{:#?}", num);
    }
}
```

## TODOs:
- [x] Add support for `&T` and `&mut T`
  - [x] Test
- [x] Add support for optional types
  - [x] Test
- [x] Add support for `Entity`
  - [x] Test
- [x] Add support for nested `WorldQuery`
  - [x] Test
- [x] Add support for tuples
  - [x] Test
- [x] Add support for generics
  - [x] Test
- [x] Add support for query filters
  - [x] Test
- [x] Add support for `PhantomData`
  - [x] Test
- [x] Refactor `read_world_query_field_type_info`
- [x] Properly document `readonly` attribute for nested queries and the static assertions that guarantee safety
  - [x] Test that we never implement `ReadOnlyFetch` for types that need mutable access
  - [x] Test that we insert static assertions for nested `WorldQuery` that a user marked as readonly
This commit is contained in:
Vladyslav Batyrenko 2022-02-24 00:19:49 +00:00
parent e369a8ad51
commit ba6b74ba20
8 changed files with 1069 additions and 1 deletions

View file

@ -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"

View file

@ -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<syn::NestedMeta, syn::token::Comma>,
}
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, syn::token::Comma>(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<T: #path::query::ReadOnlyFetch>() {}
// 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<Attribute>,
}
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::<Option<field_attr_keywords::ignore>>()?
.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();
}
}

View file

@ -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);

View file

@ -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<MyQuery>) {
/// 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<HealthQuery>) {
/// // 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<T: std::fmt::Debug>() {}
///
/// assert_debug::<FooQueryItem>();
/// assert_debug::<FooQueryReadOnlyItem>();
/// ```
///
/// ## 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<EmptyQuery>) {
/// 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<T: Component, P: Component> {
/// _foo: With<Foo>,
/// _bar: With<Bar>,
/// _or: Or<(With<Baz>, Changed<Foo>, Added<Bar>)>,
/// _generic_tuple: (With<T>, Without<P>),
/// #[world_query(ignore)]
/// _tp: std::marker::PhantomData<(T, P)>,
/// }
///
/// fn my_system(query: Query<Entity, MyFilter<Foo, Qux>>) {
/// 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> = <<Q as WorldQuery>::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;

View file

@ -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
///

View file

@ -204,6 +204,7 @@ pub struct QuerySetState<T>(T);
impl_query_set!();
pub trait Resource: Send + Sync + 'static {}
impl<T> Resource for T where T: Send + Sync + 'static {}
/// Shared borrow of a resource.

View file

@ -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

View file

@ -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<NestedQuery<'w>>,
optional_tuple: Option<(&'w ComponentB, &'w ComponentZ)>,
generic: GenericQuery<'w, T, P>,
empty: EmptyQuery<'w>,
}
fn print_components_read_only(
query: Query<ReadOnlyCustomQuery<ComponentC, ComponentD>, QueryFilter<ComponentC, ComponentD>>,
) {
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<NestedQuery<'w>>,
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<T: Component, P: Component> {
_c: With<ComponentC>,
_d: With<ComponentD>,
_or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>,
_generic_tuple: (With<T>, With<P>),
#[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<CustomQuery<ComponentC, ComponentD>, QueryFilter<ComponentC, ComponentD>>,
) {
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<CustomQuery<ComponentC, ComponentD>, QueryFilter<ComponentC, ComponentD>>,
) {
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<ComponentC, ComponentD>,
),
(
With<ComponentC>,
With<ComponentD>,
Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>,
),
>,
) {
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);
}
}