Skip to content

Commit ba6b74b

Browse files
committed
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
1 parent e369a8a commit ba6b74b

File tree

8 files changed

+1069
-1
lines changed

8 files changed

+1069
-1
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,10 @@ path = "examples/ecs/ecs_guide.rs"
318318
name = "component_change_detection"
319319
path = "examples/ecs/component_change_detection.rs"
320320

321+
[[example]]
322+
name = "custom_query_param"
323+
path = "examples/ecs/custom_query_param.rs"
324+
321325
[[example]]
322326
name = "event"
323327
path = "examples/ecs/event.rs"

crates/bevy_ecs/macros/src/fetch.rs

Lines changed: 583 additions & 0 deletions
Large diffs are not rendered by default.

crates/bevy_ecs/macros/src/lib.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
extern crate proc_macro;
22

33
mod component;
4+
mod fetch;
45

6+
use crate::fetch::derive_world_query_impl;
57
use bevy_macro_utils::{derive_label, get_named_struct_fields, BevyManifest};
68
use proc_macro::TokenStream;
79
use proc_macro2::Span;
@@ -425,6 +427,13 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
425427
})
426428
}
427429

430+
/// Implement `WorldQuery` to use a struct as a parameter in a query
431+
#[proc_macro_derive(WorldQuery, attributes(world_query))]
432+
pub fn derive_world_query(input: TokenStream) -> TokenStream {
433+
let ast = parse_macro_input!(input as DeriveInput);
434+
derive_world_query_impl(ast)
435+
}
436+
428437
#[proc_macro_derive(SystemLabel)]
429438
pub fn derive_system_label(input: TokenStream) -> TokenStream {
430439
let input = parse_macro_input!(input as DeriveInput);

crates/bevy_ecs/src/query/fetch.rs

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use crate::{
88
world::{Mut, World},
99
};
1010
use bevy_ecs_macros::all_tuples;
11+
pub use bevy_ecs_macros::WorldQuery;
1112
use std::{
1213
cell::UnsafeCell,
1314
marker::PhantomData,
@@ -40,6 +41,267 @@ use std::{
4041
/// For more information on these consult the item's corresponding documentation.
4142
///
4243
/// [`Or`]: crate::query::Or
44+
///
45+
/// # Derive
46+
///
47+
/// This trait can be derived with the [`derive@super::WorldQuery`] macro.
48+
///
49+
/// You may want to implement a custom query with the derive macro for the following reasons:
50+
/// - Named structs can be clearer and easier to use than complex query tuples. Access via struct
51+
/// fields is more convenient than destructuring tuples or accessing them via `q.0, q.1, ...`
52+
/// pattern and saves a lot of maintenance burden when adding or removing components.
53+
/// - Nested queries enable the composition pattern and makes query types easier to re-use.
54+
/// - You can bypass the limit of 15 components that exists for query tuples.
55+
///
56+
/// Implementing the trait manually can allow for a fundamentally new type of behaviour.
57+
///
58+
/// The derive macro implements [`WorldQuery`] for your type and declares an additional struct
59+
/// which will be used as an item for query iterators. The implementation also generates two other
60+
/// structs that implement [`Fetch`] and [`FetchState`] and are used as [`WorldQuery::Fetch`] and
61+
/// [`WorldQuery::State`] associated types respectively.
62+
///
63+
/// The derive macro requires every struct field to implement the `WorldQuery` trait.
64+
///
65+
/// **Note:** currently, the macro only supports named structs.
66+
///
67+
/// ```
68+
/// # use bevy_ecs::prelude::*;
69+
/// use bevy_ecs::query::WorldQuery;
70+
///
71+
/// #[derive(Component)]
72+
/// struct Foo;
73+
/// #[derive(Component)]
74+
/// struct Bar;
75+
///
76+
/// #[derive(WorldQuery)]
77+
/// struct MyQuery<'w> {
78+
/// entity: Entity,
79+
/// foo: &'w Foo,
80+
/// bar: Option<&'w Bar>,
81+
/// }
82+
///
83+
/// fn my_system(query: Query<MyQuery>) {
84+
/// for q in query.iter() {
85+
/// // Note the type of the returned item.
86+
/// let q: MyQueryItem<'_> = q;
87+
/// q.foo;
88+
/// }
89+
/// }
90+
///
91+
/// # bevy_ecs::system::assert_is_system(my_system);
92+
/// ```
93+
///
94+
/// ## Mutable queries
95+
///
96+
/// All queries that are derived with the `WorldQuery` macro provide only an immutable access by default.
97+
/// If you need a mutable access to components, you can mark a struct with the `mutable` attribute.
98+
///
99+
/// ```
100+
/// # use bevy_ecs::prelude::*;
101+
/// use bevy_ecs::query::WorldQuery;
102+
///
103+
/// #[derive(Component)]
104+
/// struct Health(f32);
105+
/// #[derive(Component)]
106+
/// struct Buff(f32);
107+
///
108+
/// #[derive(WorldQuery)]
109+
/// #[world_query(mutable)]
110+
/// struct HealthQuery<'w> {
111+
/// health: &'w mut Health,
112+
/// buff: Option<&'w mut Buff>,
113+
/// }
114+
///
115+
/// // This implementation is only available when iterating with `iter_mut`.
116+
/// impl<'w> HealthQueryItem<'w> {
117+
/// fn damage(&mut self, value: f32) {
118+
/// self.health.0 -= value;
119+
/// }
120+
///
121+
/// fn total(&self) -> f32 {
122+
/// self.health.0 + self.buff.as_deref().map_or(0.0, |Buff(buff)| *buff)
123+
/// }
124+
/// }
125+
///
126+
/// // If you want to use it with `iter`, you'll need to write an additional implementation.
127+
/// impl<'w> HealthQueryReadOnlyItem<'w> {
128+
/// fn total(&self) -> f32 {
129+
/// self.health.0 + self.buff.map_or(0.0, |Buff(buff)| *buff)
130+
/// }
131+
/// }
132+
///
133+
/// fn my_system(mut health_query: Query<HealthQuery>) {
134+
/// // Iterator's item is `HealthQueryReadOnlyItem`.
135+
/// for health in health_query.iter() {
136+
/// println!("Total: {}", health.total());
137+
/// }
138+
/// // Iterator's item is `HealthQueryItem`.
139+
/// for mut health in health_query.iter_mut() {
140+
/// health.damage(1.0);
141+
/// println!("Total (mut): {}", health.total());
142+
/// }
143+
/// }
144+
///
145+
/// # bevy_ecs::system::assert_is_system(my_system);
146+
/// ```
147+
///
148+
/// **Note:** if you omit the `mutable` attribute for a query that doesn't implement
149+
/// `ReadOnlyFetch`, compilation will fail. We insert static checks as in the example above for
150+
/// every query component and a nested query.
151+
/// (The checks neither affect the runtime, nor pollute your local namespace.)
152+
///
153+
/// ```compile_fail
154+
/// # use bevy_ecs::prelude::*;
155+
/// use bevy_ecs::query::WorldQuery;
156+
///
157+
/// #[derive(Component)]
158+
/// struct Foo;
159+
/// #[derive(Component)]
160+
/// struct Bar;
161+
///
162+
/// #[derive(WorldQuery)]
163+
/// struct FooQuery<'w> {
164+
/// foo: &'w Foo,
165+
/// bar_query: BarQuery<'w>,
166+
/// }
167+
///
168+
/// #[derive(WorldQuery)]
169+
/// #[world_query(mutable)]
170+
/// struct BarQuery<'w> {
171+
/// bar: &'w mut Bar,
172+
/// }
173+
/// ```
174+
///
175+
/// ## Derives for items
176+
///
177+
/// If you want query items to have derivable traits, you can pass them with using
178+
/// the `world_query(derive)` attribute. When the `WorldQuery` macro generates the structs
179+
/// for query items, it doesn't automatically inherit derives of a query itself. Since derive macros
180+
/// can't access information about other derives, they need to be passed manually with the
181+
/// `world_query(derive)` attribute.
182+
///
183+
/// ```
184+
/// # use bevy_ecs::prelude::*;
185+
/// use bevy_ecs::query::WorldQuery;
186+
///
187+
/// #[derive(Component, Debug)]
188+
/// struct Foo;
189+
///
190+
/// #[derive(WorldQuery)]
191+
/// #[world_query(mutable, derive(Debug))]
192+
/// struct FooQuery<'w> {
193+
/// foo: &'w Foo,
194+
/// }
195+
///
196+
/// fn assert_debug<T: std::fmt::Debug>() {}
197+
///
198+
/// assert_debug::<FooQueryItem>();
199+
/// assert_debug::<FooQueryReadOnlyItem>();
200+
/// ```
201+
///
202+
/// ## Nested queries
203+
///
204+
/// Using nested queries enable the composition pattern, which makes it possible to re-use other
205+
/// query types. All types that implement [`WorldQuery`] (including the ones that use this derive
206+
/// macro) are supported.
207+
///
208+
/// ```
209+
/// # use bevy_ecs::prelude::*;
210+
/// use bevy_ecs::query::WorldQuery;
211+
///
212+
/// #[derive(Component)]
213+
/// struct Foo;
214+
/// #[derive(Component)]
215+
/// struct Bar;
216+
/// #[derive(Component)]
217+
/// struct OptionalFoo;
218+
/// #[derive(Component)]
219+
/// struct OptionalBar;
220+
///
221+
/// #[derive(WorldQuery)]
222+
/// struct MyQuery<'w> {
223+
/// foo: FooQuery<'w>,
224+
/// bar: (&'w Bar, Option<&'w OptionalBar>)
225+
/// }
226+
///
227+
/// #[derive(WorldQuery)]
228+
/// struct FooQuery<'w> {
229+
/// foo: &'w Foo,
230+
/// optional_foo: Option<&'w OptionalFoo>,
231+
/// }
232+
///
233+
/// // You can also compose derived queries with regular ones in tuples.
234+
/// fn my_system(query: Query<(&Foo, MyQuery, FooQuery)>) {
235+
/// for (foo, my_query, foo_query) in query.iter() {
236+
/// foo; my_query; foo_query;
237+
/// }
238+
/// }
239+
///
240+
/// # bevy_ecs::system::assert_is_system(my_system);
241+
/// ```
242+
///
243+
/// ## Ignored fields
244+
///
245+
/// The macro also supports `ignore` attribute for struct members. Fields marked with this attribute
246+
/// must implement the `Default` trait.
247+
///
248+
/// This example demonstrates a query that would iterate over every entity.
249+
///
250+
/// ```
251+
/// # use bevy_ecs::prelude::*;
252+
/// use bevy_ecs::query::WorldQuery;
253+
///
254+
/// #[derive(WorldQuery, Debug)]
255+
/// struct EmptyQuery<'w> {
256+
/// #[world_query(ignore)]
257+
/// _w: std::marker::PhantomData<&'w ()>,
258+
/// }
259+
///
260+
/// fn my_system(query: Query<EmptyQuery>) {
261+
/// for _ in query.iter() {}
262+
/// }
263+
///
264+
/// # bevy_ecs::system::assert_is_system(my_system);
265+
/// ```
266+
///
267+
/// ## Filters
268+
///
269+
/// Using [`derive@super::WorldQuery`] macro in conjunctions with the `#[world_query(filter)]`
270+
/// attribute allows creating custom query filters.
271+
///
272+
/// To do so, all fields in the struct must be filters themselves (their [`WorldQuery::Fetch`]
273+
/// associated types should implement [`super::FilterFetch`]).
274+
///
275+
/// ```
276+
/// # use bevy_ecs::prelude::*;
277+
/// use bevy_ecs::{query::WorldQuery, component::Component};
278+
///
279+
/// #[derive(Component)]
280+
/// struct Foo;
281+
/// #[derive(Component)]
282+
/// struct Bar;
283+
/// #[derive(Component)]
284+
/// struct Baz;
285+
/// #[derive(Component)]
286+
/// struct Qux;
287+
///
288+
/// #[derive(WorldQuery)]
289+
/// #[world_query(filter)]
290+
/// struct MyFilter<T: Component, P: Component> {
291+
/// _foo: With<Foo>,
292+
/// _bar: With<Bar>,
293+
/// _or: Or<(With<Baz>, Changed<Foo>, Added<Bar>)>,
294+
/// _generic_tuple: (With<T>, Without<P>),
295+
/// #[world_query(ignore)]
296+
/// _tp: std::marker::PhantomData<(T, P)>,
297+
/// }
298+
///
299+
/// fn my_system(query: Query<Entity, MyFilter<Foo, Qux>>) {
300+
/// for _ in query.iter() {}
301+
/// }
302+
///
303+
/// # bevy_ecs::system::assert_is_system(my_system);
304+
/// ```
43305
pub trait WorldQuery {
44306
type Fetch: for<'world, 'state> Fetch<'world, 'state, State = Self::State>;
45307
type State: FetchState;
@@ -49,6 +311,11 @@ pub trait WorldQuery {
49311

50312
pub type QueryItem<'w, 's, Q> = <<Q as WorldQuery>::Fetch as Fetch<'w, 's>>::Item;
51313

314+
/// Types that implement this trait are responsible for fetching query items from tables or
315+
/// archetypes.
316+
///
317+
/// Every type that implements [`WorldQuery`] have their associated [`WorldQuery::Fetch`] and
318+
/// [`WorldQuery::State`] types that are essential for fetching component data.
52319
pub trait Fetch<'world, 'state>: Sized {
53320
type Item;
54321
type State: FetchState;

crates/bevy_ecs/src/query/filter.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ use std::{cell::UnsafeCell, marker::PhantomData, ptr};
1111

1212
/// Extension trait for [`Fetch`] containing methods used by query filters.
1313
/// This trait exists to allow "short circuit" behaviors for relevant query filter fetches.
14+
///
15+
/// This trait is automatically implemented for every type that implements [`Fetch`] trait and
16+
/// specifies `bool` as the associated type for [`Fetch::Item`].
1417
pub trait FilterFetch: for<'w, 's> Fetch<'w, 's> {
1518
/// # Safety
1619
///

crates/bevy_ecs/src/system/system_param.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ fn assert_component_access_compatibility(
189189
.collect::<Vec<&str>>();
190190
let accesses = conflicting_components.join(", ");
191191
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`.",
192-
query_type, filter_type, system_name, accesses);
192+
query_type, filter_type, system_name, accesses);
193193
}
194194

195195
pub struct QuerySet<'w, 's, T> {
@@ -204,6 +204,7 @@ pub struct QuerySetState<T>(T);
204204
impl_query_set!();
205205

206206
pub trait Resource: Send + Sync + 'static {}
207+
207208
impl<T> Resource for T where T: Send + Sync + 'static {}
208209

209210
/// Shared borrow of a resource.

examples/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ Example | File | Description
166166
--- | --- | ---
167167
`ecs_guide` | [`ecs/ecs_guide.rs`](./ecs/ecs_guide.rs) | Full guide to Bevy's ECS
168168
`component_change_detection` | [`ecs/component_change_detection.rs`](./ecs/component_change_detection.rs) | Change detection on components
169+
`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
169170
`event` | [`ecs/event.rs`](./ecs/event.rs) | Illustrates event creation, activation, and reception
170171
`fixed_timestep` | [`ecs/fixed_timestep.rs`](./ecs/fixed_timestep.rs) | Shows how to create systems that run every fixed timestep, rather than every tick
171172
`generic_system` | [`ecs/generic_system.rs`](./ecs/generic_system.rs) | Shows how to create systems that can be reused with different types

0 commit comments

Comments
 (0)