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:
parent
e369a8ad51
commit
ba6b74ba20
@ -318,6 +318,10 @@ path = "examples/ecs/ecs_guide.rs"
|
|||||||
name = "component_change_detection"
|
name = "component_change_detection"
|
||||||
path = "examples/ecs/component_change_detection.rs"
|
path = "examples/ecs/component_change_detection.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "custom_query_param"
|
||||||
|
path = "examples/ecs/custom_query_param.rs"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "event"
|
name = "event"
|
||||||
path = "examples/ecs/event.rs"
|
path = "examples/ecs/event.rs"
|
||||||
|
583
crates/bevy_ecs/macros/src/fetch.rs
Normal file
583
crates/bevy_ecs/macros/src/fetch.rs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,9 @@
|
|||||||
extern crate proc_macro;
|
extern crate proc_macro;
|
||||||
|
|
||||||
mod component;
|
mod component;
|
||||||
|
mod fetch;
|
||||||
|
|
||||||
|
use crate::fetch::derive_world_query_impl;
|
||||||
use bevy_macro_utils::{derive_label, get_named_struct_fields, BevyManifest};
|
use bevy_macro_utils::{derive_label, get_named_struct_fields, BevyManifest};
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use proc_macro2::Span;
|
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)]
|
#[proc_macro_derive(SystemLabel)]
|
||||||
pub fn derive_system_label(input: TokenStream) -> TokenStream {
|
pub fn derive_system_label(input: TokenStream) -> TokenStream {
|
||||||
let input = parse_macro_input!(input as DeriveInput);
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
|
@ -8,6 +8,7 @@ use crate::{
|
|||||||
world::{Mut, World},
|
world::{Mut, World},
|
||||||
};
|
};
|
||||||
use bevy_ecs_macros::all_tuples;
|
use bevy_ecs_macros::all_tuples;
|
||||||
|
pub use bevy_ecs_macros::WorldQuery;
|
||||||
use std::{
|
use std::{
|
||||||
cell::UnsafeCell,
|
cell::UnsafeCell,
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
@ -40,6 +41,267 @@ use std::{
|
|||||||
/// For more information on these consult the item's corresponding documentation.
|
/// For more information on these consult the item's corresponding documentation.
|
||||||
///
|
///
|
||||||
/// [`Or`]: crate::query::Or
|
/// [`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 {
|
pub trait WorldQuery {
|
||||||
type Fetch: for<'world, 'state> Fetch<'world, 'state, State = Self::State>;
|
type Fetch: for<'world, 'state> Fetch<'world, 'state, State = Self::State>;
|
||||||
type State: FetchState;
|
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;
|
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 {
|
pub trait Fetch<'world, 'state>: Sized {
|
||||||
type Item;
|
type Item;
|
||||||
type State: FetchState;
|
type State: FetchState;
|
||||||
|
@ -11,6 +11,9 @@ use std::{cell::UnsafeCell, marker::PhantomData, ptr};
|
|||||||
|
|
||||||
/// Extension trait for [`Fetch`] containing methods used by query filters.
|
/// 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 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> {
|
pub trait FilterFetch: for<'w, 's> Fetch<'w, 's> {
|
||||||
/// # Safety
|
/// # Safety
|
||||||
///
|
///
|
||||||
|
@ -204,6 +204,7 @@ pub struct QuerySetState<T>(T);
|
|||||||
impl_query_set!();
|
impl_query_set!();
|
||||||
|
|
||||||
pub trait Resource: Send + Sync + 'static {}
|
pub trait Resource: Send + Sync + 'static {}
|
||||||
|
|
||||||
impl<T> Resource for T where T: Send + Sync + 'static {}
|
impl<T> Resource for T where T: Send + Sync + 'static {}
|
||||||
|
|
||||||
/// Shared borrow of a resource.
|
/// Shared borrow of a resource.
|
||||||
|
@ -166,6 +166,7 @@ Example | File | Description
|
|||||||
--- | --- | ---
|
--- | --- | ---
|
||||||
`ecs_guide` | [`ecs/ecs_guide.rs`](./ecs/ecs_guide.rs) | Full guide to Bevy's ECS
|
`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
|
`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
|
`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
|
`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
|
`generic_system` | [`ecs/generic_system.rs`](./ecs/generic_system.rs) | Shows how to create systems that can be reused with different types
|
||||||
|
200
examples/ecs/custom_query_param.rs
Normal file
200
examples/ecs/custom_query_param.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user