mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
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
8 changed files with 1069 additions and 1 deletions
|
@ -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"
|
||||
|
|
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;
|
||||
|
||||
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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
///
|
||||
|
|
|
@ -189,7 +189,7 @@ fn assert_component_access_compatibility(
|
|||
.collect::<Vec<&str>>();
|
||||
let accesses = conflicting_components.join(", ");
|
||||
panic!("error[B0001]: Query<{}, {}> in system {} accesses component(s) {} in a way that conflicts with a previous system parameter. Consider using `Without<T>` to create disjoint Queries or merging conflicting Queries into a `QuerySet`.",
|
||||
query_type, filter_type, system_name, accesses);
|
||||
query_type, filter_type, system_name, accesses);
|
||||
}
|
||||
|
||||
pub struct QuerySet<'w, 's, T> {
|
||||
|
@ -204,6 +204,7 @@ pub struct QuerySetState<T>(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.
|
||||
|
|
|
@ -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
|
||||
|
|
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 a new issue