Skip to content

Commit ba31c9b

Browse files
committed
Implement Fetch and FilterFetch derive macros
1 parent 803e8cd commit ba31c9b

File tree

8 files changed

+1215
-1
lines changed

8 files changed

+1215
-1
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,10 @@ path = "examples/ecs/ecs_guide.rs"
315315
name = "component_change_detection"
316316
path = "examples/ecs/component_change_detection.rs"
317317

318+
[[example]]
319+
name = "custom_query_param"
320+
path = "examples/ecs/custom_query_param.rs"
321+
318322
[[example]]
319323
name = "event"
320324
path = "examples/ecs/event.rs"

crates/bevy_ecs/macros/src/fetch.rs

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

crates/bevy_ecs/macros/src/lib.rs

Lines changed: 14 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_fetch_impl, derive_filter_fetch_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,18 @@ 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(Fetch, attributes(read_only, read_only_derive))]
432+
pub fn derive_fetch(input: TokenStream) -> TokenStream {
433+
derive_fetch_impl(input)
434+
}
435+
436+
/// Implement `FilterFetch` to use a struct as a filter parameter in a query
437+
#[proc_macro_derive(FilterFetch)]
438+
pub fn derive_filter_fetch(input: TokenStream) -> TokenStream {
439+
derive_filter_fetch_impl(input)
440+
}
441+
428442
#[proc_macro_derive(SystemLabel)]
429443
pub fn derive_system_label(input: TokenStream) -> TokenStream {
430444
let input = parse_macro_input!(input as DeriveInput);

crates/bevy_ecs/src/query/fetch.rs

Lines changed: 212 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::{Fetch, FilterFetch};
1112
use std::{
1213
cell::UnsafeCell,
1314
marker::PhantomData,
@@ -21,6 +22,10 @@ use std::{
2122
///
2223
/// See [`Query`](crate::system::Query) for a primer on queries.
2324
///
25+
/// If you want to implement a custom query, see [`Fetch`] trait documentation.
26+
///
27+
/// If you want to implement a custom query filter, see [`FilterFetch`] trait documentation.
28+
///
2429
/// # Basic [`WorldQuery`]'s
2530
///
2631
/// Here is a small list of the most important world queries to know about where `C` stands for a
@@ -49,6 +54,213 @@ pub trait WorldQuery {
4954

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

57+
/// Types that implement this trait are responsible for fetching query items from tables or
58+
/// archetypes.
59+
///
60+
/// Every type that implements [`WorldQuery`] have their associated [`WorldQuery::Fetch`] and
61+
/// [`WorldQuery::State`] types that are essential for fetching component data. If you want to
62+
/// implement a custom query type, you'll need to implement [`Fetch`] and [`FetchState`] for
63+
/// those associated types.
64+
///
65+
/// You may want to implement a custom query for the following reasons:
66+
/// - Named structs can be clearer and easier to use than complex query tuples. Access via struct
67+
/// fields is more convenient than destructuring tuples or accessing them via `q.0, q.1, ...`
68+
/// pattern and saves a lot of maintenance burden when adding or removing components.
69+
/// - Nested queries enable the composition pattern and makes query types easier to re-use.
70+
/// - You can bypass the limit of 15 components that exists for query tuples.
71+
///
72+
/// Implementing the trait manually can allow for a fundamentally new type of behaviour.
73+
///
74+
/// # Derive
75+
///
76+
/// This trait can be derived with the [`derive@super::Fetch`] macro.
77+
/// To do so, all fields in the struct must themselves impl [`WorldQuery`].
78+
///
79+
/// The derive macro implements [`WorldQuery`] for your type and declares two structs that
80+
/// implement [`Fetch`] and [`FetchState`] and are used as [`WorldQuery::Fetch`] and
81+
/// [`WorldQuery::State`] associated types respectively.
82+
///
83+
/// **Note:** currently, the macro only supports named structs.
84+
///
85+
/// ```
86+
/// # use bevy_ecs::prelude::*;
87+
/// use bevy_ecs::query::Fetch;
88+
///
89+
/// #[derive(Component)]
90+
/// struct Foo;
91+
/// #[derive(Component)]
92+
/// struct Bar;
93+
/// #[derive(Component)]
94+
/// struct OptionalFoo;
95+
/// #[derive(Component)]
96+
/// struct OptionalBar;
97+
///
98+
/// #[derive(Fetch)]
99+
/// struct MyQuery<'w> {
100+
/// entity: Entity,
101+
/// foo: &'w Foo,
102+
/// // `Mut<'w, T>` is a necessary replacement for `&'w mut T`
103+
/// bar: Mut<'w, Bar>,
104+
/// optional_foo: Option<&'w OptionalFoo>,
105+
/// optional_bar: Option<Mut<'w, OptionalBar>>,
106+
/// }
107+
///
108+
/// fn my_system(mut query: Query<MyQuery>) {
109+
/// for q in query.iter_mut() {
110+
/// q.foo;
111+
/// }
112+
/// }
113+
///
114+
/// # my_system.system();
115+
/// ```
116+
///
117+
/// ## Nested queries
118+
///
119+
/// Using nested queries enable the composition pattern, which makes it possible to re-use other
120+
/// query types. All types that implement [`WorldQuery`] (including the ones that use this derive
121+
/// macro) are supported.
122+
///
123+
/// ```
124+
/// # use bevy_ecs::prelude::*;
125+
/// use bevy_ecs::query::Fetch;
126+
///
127+
/// #[derive(Component)]
128+
/// struct Foo;
129+
/// #[derive(Component)]
130+
/// struct Bar;
131+
/// #[derive(Component)]
132+
/// struct OptionalFoo;
133+
/// #[derive(Component)]
134+
/// struct OptionalBar;
135+
///
136+
/// #[derive(Fetch)]
137+
/// struct MyQuery<'w> {
138+
/// foo: FooQuery<'w>,
139+
/// bar: (&'w Bar, Option<&'w OptionalBar>)
140+
/// }
141+
///
142+
/// #[derive(Fetch)]
143+
/// struct FooQuery<'w> {
144+
/// foo: &'w Foo,
145+
/// optional_foo: Option<&'w OptionalFoo>,
146+
/// }
147+
///
148+
/// ```
149+
///
150+
/// ## Read-only queries
151+
///
152+
/// All queries that are derived with `Fetch` macro have their read-only variants with `ReadOnly`
153+
/// suffix. If you are going to use a query only for reading, you can mark it with `read_only`
154+
/// attribute.
155+
///
156+
/// ```
157+
/// # use bevy_ecs::prelude::*;
158+
/// use bevy_ecs::query::{Fetch, ReadOnlyFetch, WorldQuery};
159+
///
160+
/// #[derive(Component)]
161+
/// struct Foo;
162+
/// #[derive(Component)]
163+
/// struct Bar;
164+
///
165+
/// #[derive(Fetch)]
166+
/// #[read_only]
167+
/// struct FooQuery<'w> {
168+
/// foo: &'w Foo,
169+
/// bar_query: BarQueryReadOnly<'w>,
170+
/// }
171+
///
172+
/// #[derive(Fetch)]
173+
/// struct BarQuery<'w> {
174+
/// bar: &'w Bar,
175+
/// }
176+
///
177+
/// fn assert_read_only<T: ReadOnlyFetch>() {}
178+
///
179+
/// assert_read_only::<<FooQuery as WorldQuery>::Fetch>();
180+
/// assert_read_only::<<BarQuery as WorldQuery>::ReadOnlyFetch>();
181+
/// // the following will fail to compile:
182+
/// // assert_read_only::<<BarQuery as WorldQuery>::Fetch>();
183+
/// ```
184+
///
185+
/// If you want to use derive macros with read-only query variants, you need to pass them with
186+
/// using `read_only_derive` attribute.
187+
///
188+
/// # use bevy_ecs::prelude::*;
189+
/// use bevy_ecs::query::{Fetch, ReadOnlyFetch, WorldQuery};
190+
///
191+
/// ```
192+
/// # use bevy_ecs::prelude::*;
193+
/// use bevy_ecs::query::{Fetch, ReadOnlyFetch, WorldQuery};
194+
///
195+
/// #[derive(Component, Debug)]
196+
/// struct Foo;
197+
///
198+
/// #[derive(Fetch, Debug)]
199+
/// #[read_only_derive(Debug)]
200+
/// struct FooQuery<'w> {
201+
/// foo: &'w Foo,
202+
/// }
203+
///
204+
/// fn assert_debug<T: std::fmt::Debug>() {}
205+
///
206+
/// assert_debug::<FooQuery>();
207+
/// assert_debug::<FooQueryReadOnly>();
208+
/// ```
209+
///
210+
/// **Note:** if you mark a query that doesn't implement `ReadOnlyFetch` as `read_only`,
211+
/// compilation will fail. We insert static checks as in the example above for every nested query
212+
/// marked as `read_only`. (They neither affect the runtime, nor pollute your local namespace.)
213+
///
214+
/// ```compile_fail
215+
/// # use bevy_ecs::prelude::*;
216+
/// use bevy_ecs::query::{Fetch, ReadOnlyFetch, WorldQuery};
217+
///
218+
/// #[derive(Component)]
219+
/// struct Foo;
220+
/// #[derive(Component)]
221+
/// struct Bar;
222+
///
223+
/// #[derive(Fetch)]
224+
/// #[read_only]
225+
/// struct FooQuery<'w> {
226+
/// foo: &'w Foo,
227+
/// bar_query: BarQuery<'w>,
228+
/// }
229+
///
230+
/// #[derive(Fetch)]
231+
/// struct BarQuery<'w> {
232+
/// bar: Mut<'w, Bar>,
233+
/// }
234+
/// ```
235+
///
236+
/// ## Limitations
237+
///
238+
/// Currently, we don't support members that have a manual [`WorldQuery`] implementation if their
239+
/// [`Fetch::Item`] is different from the member type. For instance, the following code won't
240+
/// compile:
241+
///
242+
/// ```ignore
243+
/// #[derive(Component)]
244+
/// struct CustomQueryParameter;
245+
/// #[derive(Component)]
246+
/// struct ItemDataType;
247+
///
248+
/// struct CustomQueryParameterFetch {
249+
/// // ...
250+
/// }
251+
///
252+
/// impl<'w, 's> Fetch<'w, 's> for CustomQueryParameterFetch {
253+
/// type Item = ItemDataType;
254+
///
255+
/// // ...
256+
/// }
257+
///
258+
/// #[derive(Fetch)]
259+
/// struct MyQuery {
260+
/// custom_item: ItemDataType,
261+
/// }
262+
///
263+
/// ```
52264
pub trait Fetch<'world, 'state>: Sized {
53265
type Item;
54266
type State: FetchState;

crates/bevy_ecs/src/query/filter.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,53 @@ 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`].
17+
///
18+
/// Using [`derive@super::FilterFetch`] macro allows creating custom query filters.
19+
/// You may want to implement a custom query filter for the following reasons:
20+
/// - Nested query filters enable the composition pattern and makes them easier to re-use.
21+
/// - You can bypass the limit of 15 components that exists for query filters declared as tuples.
22+
///
23+
/// Implementing the trait manually can allow for a fundamentally new type of behaviour.
24+
///
25+
/// ## Derive
26+
///
27+
/// This trait can be derived with the [`derive@super::FilterFetch`] macro.
28+
/// To do so, all fields in the struct must be filters themselves (their [`WorldQuery::Fetch`]
29+
/// associated types should implement [`FilterFetch`]).
30+
///
31+
/// **Note:** currently, the macro only supports named structs.
32+
///
33+
/// ```
34+
/// # use bevy_ecs::prelude::*;
35+
/// use bevy_ecs::{query::FilterFetch, component::Component};
36+
///
37+
/// #[derive(Component)]
38+
/// struct Foo;
39+
/// #[derive(Component)]
40+
/// struct Bar;
41+
/// #[derive(Component)]
42+
/// struct Baz;
43+
/// #[derive(Component)]
44+
/// struct Qux;
45+
///
46+
/// #[derive(FilterFetch)]
47+
/// struct MyFilter<T: Component, P: Component> {
48+
/// _foo: With<Foo>,
49+
/// _bar: With<Bar>,
50+
/// _or: Or<(With<Baz>, Changed<Foo>, Added<Bar>)>,
51+
/// _generic_tuple: (With<T>, Without<P>),
52+
/// _tp: std::marker::PhantomData<(T, P)>,
53+
/// }
54+
///
55+
/// fn my_system(query: Query<Entity, MyFilter<Foo, Qux>>) {
56+
/// for _ in query.iter() {}
57+
/// }
58+
///
59+
/// # my_system.system();
60+
/// ```
1461
pub trait FilterFetch: for<'w, 's> Fetch<'w, 's> {
1562
/// # Safety
1663
///

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)