From 248296bb98bb034857ad366c450ef94f235148cd Mon Sep 17 00:00:00 2001 From: mvlabat Date: Mon, 23 Aug 2021 00:37:22 +0300 Subject: [PATCH] Implement WorldQuery derive macro --- Cargo.toml | 4 + crates/bevy_ecs/macros/src/lib.rs | 572 ++++++++++++++++++++++++++++- crates/bevy_ecs/src/query/fetch.rs | 24 ++ examples/README.md | 1 + examples/ecs/world_query.rs | 59 +++ 5 files changed, 658 insertions(+), 2 deletions(-) create mode 100644 examples/ecs/world_query.rs diff --git a/Cargo.toml b/Cargo.toml index 0fafd91be7970..34c42ce7fb67e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -314,6 +314,10 @@ path = "examples/ecs/system_sets.rs" name = "timers" path = "examples/ecs/timers.rs" +[[example]] +name = "world_query" +path = "examples/ecs/world_query.rs" + # Games [[example]] name = "alien_cake_addict" diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 8371de3e4b160..351998ecf6bde 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -9,8 +9,9 @@ use syn::{ parse_macro_input, punctuated::Punctuated, token::Comma, - Data, DataStruct, DeriveInput, Field, Fields, GenericParam, Ident, Index, LitInt, Path, Result, - Token, + Data, DataStruct, DeriveInput, Field, Fields, GenericArgument, GenericParam, Ident, Index, + Lifetime, LifetimeDef, LitInt, Path, PathArguments, Result, Token, Type, TypePath, + TypeReference, }; struct AllTuples { @@ -423,6 +424,332 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { }) } +static READONLY_ATTRIBUTE_NAME: &str = "readonly"; +static QUERY_FILTER_ATTRIBUTE_NAME: &str = "query_filter"; +static FILTER_ATTRIBUTE_NAME: &str = "filter"; + +/// Implement `WorldQuery` to use a struct as a parameter in a query +#[proc_macro_derive(WorldQuery, attributes(readonly, query_filter, filter))] +pub fn derive_world_query(input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as DeriveInput); + let fields = match &ast.data { + Data::Struct(DataStruct { + fields: Fields::Named(fields), + .. + }) => &fields.named, + _ => panic!("Expected a struct with named fields"), + }; + let is_query_filter = ast + .attrs + .iter() + .any(|attr| attr.path.get_ident().unwrap() == QUERY_FILTER_ATTRIBUTE_NAME); + + let world_lt = ast.generics.params.first().and_then(|param| match param { + lt @ GenericParam::Lifetime(_) => Some(lt.clone()), + _ => None, + }); + let struct_has_world_lt = world_lt.is_some(); + let world_lt = world_lt.unwrap_or_else(|| { + GenericParam::Lifetime(LifetimeDef::new(Lifetime::new("'world", Span::call_site()))) + }); + let state_lt = + 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_lt.clone()); + fetch_trait_punctuated_lifetimes.push(state_lt.clone()); + + let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); + + let mut fetch_generics = ast.generics.clone(); + fetch_generics.params.insert(0, state_lt); + if !struct_has_world_lt { + fetch_generics.params.insert(0, world_lt); + } + fetch_generics + .params + .push(GenericParam::Lifetime(LifetimeDef::new(Lifetime::new( + "'fetch", + Span::call_site(), + )))); + let (fetch_impl_generics, _, _) = fetch_generics.split_for_impl(); + let mut fetch_generics = ast.generics.clone(); + if struct_has_world_lt { + *fetch_generics.params.first_mut().unwrap() = + GenericParam::Lifetime(LifetimeDef::new(Lifetime::new("'fetch", Span::call_site()))); + } + let (_, fetch_ty_generics, _) = fetch_generics.split_for_impl(); + + let path = bevy_ecs_path(); + + let struct_name = &ast.ident; + 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 mut is_read_only = true; + + let mut field_idents = Vec::new(); + + let mut phantom_field_idents = Vec::new(); + let mut phantom_field_types = Vec::new(); + let mut field_types = Vec::new(); + let mut query_types = Vec::new(); + let mut init_types = Vec::new(); + let mut readonly_assertions = Vec::new(); + + let generic_names = ast + .generics + .params + .iter() + .filter_map(|param| match param { + GenericParam::Type(ty) => Some(ty.ident.to_string()), + _ => None, + }) + .collect::>(); + + for field in fields.iter() { + let has_readonly_attribute = field.attrs.iter().any(|attr| { + attr.path + .get_ident() + .map_or(false, |ident| ident == READONLY_ATTRIBUTE_NAME) + }); + let filter_type = field + .attrs + .iter() + .find(|attr| { + attr.path + .get_ident() + .map_or(false, |ident| ident == FILTER_ATTRIBUTE_NAME) + }) + .map(|filter| { + filter + .parse_args::() + .expect("Expected a filter type (example: `#[filter(With)]`)") + }); + + let WorldQueryFieldTypeInfo { + query_type, + init_type, + is_read_only: field_is_read_only, + is_phantom, + } = read_world_query_field_type_info( + &field.ty, + is_query_filter, + false, + filter_type, + has_readonly_attribute, + &generic_names, + &mut readonly_assertions, + ); + if is_phantom { + phantom_field_idents.push(field.ident.as_ref().unwrap().clone()); + phantom_field_types.push(field.ty.clone()); + } else { + field_idents.push(field.ident.as_ref().unwrap().clone()); + field_types.push(field.ty.clone()); + query_types.push(query_type); + init_types.push(init_type); + } + is_read_only = is_read_only && field_is_read_only; + } + + let read_only_impl = if is_read_only { + quote! { + /// SAFETY: each item in the struct is read only + unsafe impl #impl_generics #path::query::ReadOnlyFetch for #fetch_struct_name #ty_generics #where_clause {} + + // Statically checks that the safety guarantee holds true indeed. We need this to make + // sure that we don't compile ReadOnlyFetch if our struct contains nested WorldQuery + // that don't implement it. + #[allow(dead_code)] + const _: () = { + fn assert_readonly() {} + + // We generate a readonly assertion for every type that isn't &T, &mut T, Option<&T> or Option<&mut T> + fn assert_all #impl_generics () #where_clause { + #(#readonly_assertions)* + } + }; + } + } else { + quote! {} + }; + + let tokens = if is_query_filter { + quote! { + struct #fetch_struct_name #impl_generics #where_clause { + #(#field_idents: <#query_types as #path::query::WorldQuery>::Fetch,)* + #(#phantom_field_idents: #phantom_field_types,)* + } + + struct #state_struct_name #impl_generics #where_clause { + #(#field_idents: <#query_types as #path::query::WorldQuery>::State,)* + #(#phantom_field_idents: #phantom_field_types,)* + } + + impl #fetch_impl_generics #path::query::Fetch<#fetch_trait_punctuated_lifetimes> for #fetch_struct_name #fetch_ty_generics #where_clause { + type Item = bool; + 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 { + #fetch_struct_name { + #(#field_idents: <#init_types as #path::query::WorldQuery>::Fetch::init(_world, &state.#field_idents, _last_change_tick, _change_tick),)* + #(#phantom_field_idents: Default::default(),)* + } + } + + #[inline] + fn is_dense(&self) -> bool { + true #(&& self.#field_idents.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))* + } + } + + // 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: <#query_types as #path::query::WorldQuery>::State::init(world),)* + #(#phantom_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))* + } + } + + 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; + } + + #read_only_impl + } + } else { + quote! { + struct #fetch_struct_name #impl_generics #where_clause { + #(#field_idents: <#query_types as #path::query::WorldQuery>::Fetch,)* + } + + struct #state_struct_name #impl_generics #where_clause { + #(#field_idents: <#query_types as #path::query::WorldQuery>::State,)* + } + + impl #fetch_impl_generics #path::query::Fetch<#fetch_trait_punctuated_lifetimes> for #fetch_struct_name #fetch_ty_generics #where_clause { + type 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 { + #fetch_struct_name { + #(#field_idents: <#init_types as #path::query::WorldQuery>::Fetch::init(_world, &state.#field_idents, _last_change_tick, _change_tick),)* + } + } + + + #[inline] + fn is_dense(&self) -> bool { + true #(&& self.#field_idents.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 { + #struct_name { + #(#field_idents: self.#field_idents.table_fetch(_table_row),)* + #(#phantom_field_idents: Default::default(),)* + } + } + + #[inline] + unsafe fn archetype_fetch(&mut self, _archetype_index: usize) -> Self::Item { + #struct_name { + #(#field_idents: self.#field_idents.archetype_fetch(_archetype_index),)* + #(#phantom_field_idents: Default::default(),)* + } + } + } + + // 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: <#query_types as #path::query::WorldQuery>::State::init(world),)* + } + } + + 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))* + } + } + + 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; + } + + #read_only_impl + } + }; + + let tokens = TokenStream::from(tokens); + tokens +} + #[proc_macro_derive(SystemLabel)] pub fn derive_system_label(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); @@ -468,6 +795,247 @@ fn derive_label(input: DeriveInput, label_type: Ident) -> TokenStream2 { } } +struct WorldQueryFieldTypeInfo { + query_type: Type, + init_type: Type, + is_read_only: bool, + is_phantom: bool, +} + +fn read_world_query_field_type_info( + ty: &Type, + is_query_filter_element: bool, + is_tuple_element: bool, + filter_type: Option, + has_readonly_attribute: bool, + generic_names: &[String], + readonly_assertions: &mut Vec, +) -> WorldQueryFieldTypeInfo { + let mut query_type = ty.clone(); + let mut init_type = ty.clone(); + let mut is_read_only = true; + let mut is_phantom = false; + + match (ty, &mut init_type) { + (Type::Path(path), Type::Path(path_init)) => { + if path.qself.is_some() { + // There's a risk that it contains a generic parameter that we can't test + // whether it's readonly or not. + panic!("Self type qualifiers aren't supported"); + } + + let segment = path.path.segments.last().unwrap(); + if is_query_filter_element && !(segment.ident == "bool" || segment.ident == "PhantomData") { + panic!("Invalid type: only `bool` and `PhantomData` are allowed for query filters"); + } + + // Checking for Option. + if segment.ident == "Option" { + let ty = match &segment.arguments { + PathArguments::AngleBracketed(args) => { + args.args.last().and_then(|arg| match arg { + GenericArgument::Type(ty) => Some(ty), + _ => None, + }) + } + _ => None, + }; + match ty.expect("Option type is expected to have generic arguments") { + Type::Reference(reference) => { + if reference.mutability.is_some() { + panic!("Invalid reference type: use `Mut` instead of `&mut T`"); + } + match &mut path_init.path.segments.last_mut().unwrap().arguments { + PathArguments::AngleBracketed(args) => { + match args.args.last_mut().unwrap() { + GenericArgument::Type(Type::Reference(ty)) => ty.lifetime = Some(Lifetime::new("'fetch", Span::call_site())), + _ => unreachable!(), + } + } + _ => unreachable!(), + } + } + Type::Path(path) => { + assert_not_generic(&path, generic_names); + + let segment = path.path.segments.last().unwrap(); + let ty_ident = &segment.ident; + if ty_ident == "Mut" { + is_read_only = false; + let (mut_lifetime, mut_ty) = match &segment.arguments { + PathArguments::AngleBracketed(args) => { + (args.args.first().and_then(|arg| { match arg { + GenericArgument::Lifetime(lifetime) => Some(lifetime.clone()), + _ => None, + }}).expect("Mut is expected to have a lifetime"), + args.args.last().and_then(|arg| { match arg { + GenericArgument::Type(ty) => Some(ty.clone()), + _ => None, + }}).expect("Mut is expected to have a lifetime")) + } + _ => panic!("Mut type is expected to have generic arguments") + }; + + match query_type { + Type::Path(ref mut path) => { + let segment = path.path.segments.last_mut().unwrap(); + match segment.arguments { + PathArguments::AngleBracketed(ref mut args) => { + match args.args.last_mut().unwrap() { + GenericArgument::Type(ty) => { + *ty = Type::Reference(TypeReference { + and_token: Token![&](Span::call_site()), + lifetime: Some(mut_lifetime), + mutability: Some(Token![mut](Span::call_site())), + elem: Box::new(mut_ty.clone()), + }); + } + _ => unreachable!() + } + } + _ => unreachable!() + } + } + _ => unreachable!() + } + + let segment = path_init.path.segments.last_mut().unwrap(); + match segment.arguments { + PathArguments::AngleBracketed(ref mut args) => { + match args.args.last_mut().unwrap() { + GenericArgument::Type(ty) => { + *ty = Type::Reference(TypeReference { + and_token: Token![&](Span::call_site()), + lifetime: Some(Lifetime::new("'fetch", Span::call_site())), + mutability: Some(Token![mut](Span::call_site())), + elem: Box::new(mut_ty), + }); + } + _ => unreachable!() + } + } + _ => unreachable!() + } + } else { + panic!("Option type is expected to have a reference value (`Option<&T>` or `Option>`)"); + } + } + _ => panic!("Option type is expected to have a reference value (`Option<&T>` or `Option>`)"), + } + } else if segment.ident == "Mut" { + is_read_only = false; + let (mut_lifetime, mut_ty) = match &segment.arguments { + PathArguments::AngleBracketed(args) => { + let lt = args.args.first().and_then(|arg| { match arg { + GenericArgument::Lifetime(lifetime) => Some(lifetime.clone()), + _ => None, + }}).expect("`Mut` is expected to have a lifetime"); + let ty = args.args.last().and_then(|arg| { match arg { + GenericArgument::Type(ty) => Some(ty.clone()), + _ => None, + }}).expect("`Mut` is expected to have a lifetime"); + (lt, ty) + } + _ => panic!("`Mut` is expected to have generic arguments") + }; + + query_type = Type::Reference(TypeReference { + and_token: Token![&](Span::call_site()), + lifetime: Some(mut_lifetime), + mutability: Some(Token![mut](Span::call_site())), + elem: Box::new(mut_ty.clone()), + }); + init_type = Type::Reference(TypeReference { + and_token: Token![&](Span::call_site()), + lifetime: Some(Lifetime::new("'fetch", Span::call_site())), + mutability: Some(Token![mut](Span::call_site())), + elem: Box::new(mut_ty), + }); + } else if segment.ident == "bool" { + if is_tuple_element { + panic!("Invalid tuple element: bool"); + } + init_type = filter_type.expect("Field type is `bool` but no `filter` attribute is found (example: `#[filter(With)]`)"); + query_type = init_type.clone(); + } else if (segment.ident == "With" || segment.ident == "Without" || segment.ident == "Or" || segment.ident == "Added" || segment.ident == "Changed") && !is_tuple_element { + panic!("Invalid filter type: use `bool` field type and specify the filter with `#[filter({})]` attribute", segment.ident.to_string()); + } else if segment.ident == "PhantomData" { + if is_tuple_element { + panic!("Invalid tuple element: PhantomData"); + } + is_phantom = true; + } else if segment.ident != "Entity" { + assert_not_generic(&path, generic_names); + + match &mut path_init.path.segments.last_mut().unwrap().arguments { + PathArguments::AngleBracketed(args) => { + match args.args.first_mut() { + Some(GenericArgument::Lifetime(lt)) => { + *lt = Lifetime::new("'fetch", Span::call_site()); + } + _ => {}, + } + } + _ => {}, + } + + if has_readonly_attribute { + readonly_assertions.push(quote! { + assert_readonly::<#path>(); + }); + } else { + is_read_only = false; + } + } + } + (Type::Reference(reference), Type::Reference(init_reference)) => { + if reference.mutability.is_some() { + panic!("Invalid reference type: use `Mut` instead of `&mut T`"); + } + init_reference.lifetime = Some(Lifetime::new("'fetch", Span::call_site())); + } + (Type::Tuple(tuple), Type::Tuple(init_tuple)) => { + let mut query_tuple_elems = tuple.elems.clone(); + query_tuple_elems.clear(); + let mut init_tuple_elems = query_tuple_elems.clone(); + for ty in tuple.elems.iter() { + let WorldQueryFieldTypeInfo { query_type, init_type, is_read_only: elem_is_read_only, is_phantom: _ } = read_world_query_field_type_info(ty, is_query_filter_element, true, None, has_readonly_attribute, generic_names, readonly_assertions); + query_tuple_elems.push(query_type); + init_tuple_elems.push(init_type); + is_read_only = is_read_only && elem_is_read_only; + } + match query_type { + Type::Tuple(ref mut tuple) => { + tuple.elems = query_tuple_elems; + } + _ => unreachable!(), + } + init_tuple.elems = init_tuple_elems; + } + _ => panic!("Only the following types (or their tuples) are supported for WorldQuery: &T, &mut T, Option<&T>, Option<&mut T>, Entity, or other structs that implement WorldQuery"), + } + + return WorldQueryFieldTypeInfo { + query_type, + init_type, + is_read_only, + is_phantom, + }; +} + +fn assert_not_generic(type_path: &TypePath, generic_names: &[String]) { + // `get_ident` returns Some if it consists of a single segment, in this case it + // makes sense to ensure that it's not a generic. + if let Some(ident) = type_path.path.get_ident() { + let is_generic = generic_names + .iter() + .any(|generic_name| ident == generic_name.as_str()); + if is_generic { + panic!("Only references to generic types are supported: i.e. instead of `component: T`, use `component: &T` or `component: Mut` (optional references are supported as well)"); + } + } +} + fn bevy_ecs_path() -> syn::Path { BevyManifest::default().get_path("bevy_ecs") } diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 9e20c208761e7..8e53aedefee62 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -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,29 @@ 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. +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// use bevy_ecs::query::WorldQuery; +/// +/// #[derive(WorldQuery)] +/// struct MyQuery<'w> { +/// foo: &'w u32, +/// bar: Mut<'w, i32>, +/// } +/// +/// fn my_system(mut query: Query) { +/// for q in query.iter_mut() { +/// q.foo; +/// } +/// } +/// +/// # my_system.system(); +/// ``` pub trait WorldQuery { type Fetch: for<'world, 'state> Fetch<'world, 'state, State = Self::State>; type State: FetchState; diff --git a/examples/README.md b/examples/README.md index 7796afdb16223..f9f17a1b9125e 100644 --- a/examples/README.md +++ b/examples/README.md @@ -169,6 +169,7 @@ Example | File | Description `system_param` | [`ecs/system_param.rs`](./ecs/system_param.rs) | Illustrates creating custom system parameters with `SystemParam` `system_sets` | [`ecs/system_sets.rs`](./ecs/system_sets.rs) | Shows `SystemSet` use along with run criterion `timers` | [`ecs/timers.rs`](./ecs/timers.rs) | Illustrates ticking `Timer` resources inside systems and handling their state +`world_query` | [`ecs/world_query.rs`](./ecs/world_query.rs) | Illustrates creating custom world queries with `WorldQuery` ## Games diff --git a/examples/ecs/world_query.rs b/examples/ecs/world_query.rs new file mode 100644 index 0000000000000..8fdb7a70ede59 --- /dev/null +++ b/examples/ecs/world_query.rs @@ -0,0 +1,59 @@ +use bevy::ecs::component::Component; +use bevy::{ecs::query::WorldQuery, prelude::*}; +use std::marker::PhantomData; + +fn main() { + App::new() + .add_startup_system(spawn) + .add_system(print_nums) + .run(); +} + +#[derive(WorldQuery, Debug)] +struct NumQuery<'w, T: Component, P: Component> { + u_16: &'w u16, + u_32_opt: Option<&'w u32>, + i: INumQuery<'w, T, P>, + #[filter(NumQueryFilter)] + filter: bool, +} + +#[derive(WorldQuery, Debug)] +struct INumQuery<'w, T: Component, P: Component> { + i_16: Mut<'w, i16>, + i_32_opt: Option>, + test: (Mut<'w, T>, Mut<'w, P>), +} + +#[derive(WorldQuery, Debug)] +#[query_filter] +struct NumQueryFilter { + #[filter(With)] + u_16: bool, + #[filter(With)] + u_32: bool, + #[filter(Or<(With, Changed, Added)>)] + i: bool, + #[filter((With, With

))] + test: bool, + #[filter(Without>)] + without: bool, + _tp: PhantomData<(T, P)>, +} + +fn spawn(mut commands: Commands) { + commands + .spawn() + .insert(1u16) + .insert(2u32) + .insert(3i16) + .insert(4i32) + .insert(5u64) + .insert(6i64); +} + +fn print_nums(mut query: Query, NumQueryFilter>) { + for num in query.iter_mut() { + dbg!(num); + } +}