Skip to content

Commit 026e1f3

Browse files
committed
World query filter proposal prototype implementation.
This PR is a prototype implementaton of bevyengine/rfcs#58 RFC that proposes disallowing using component types as query filters. This PR also introduces `derive(WorldQueryFilter)` proc macro to make custom query filters, analogous to custom queries.
1 parent f969c62 commit 026e1f3

File tree

16 files changed

+211
-70
lines changed

16 files changed

+211
-70
lines changed

crates/bevy_ecs/macros/src/fetch.rs

+40-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ mod field_attr_keywords {
2525

2626
pub static WORLD_QUERY_ATTRIBUTE_NAME: &str = "world_query";
2727

28-
pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream {
28+
pub fn derive_world_query_impl(ast: DeriveInput, impl_filter: bool) -> TokenStream {
2929
let visibility = ast.vis;
3030

3131
let mut fetch_struct_attributes = FetchStructAttributes::default();
@@ -316,13 +316,49 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream {
316316
}
317317
};
318318

319+
let impl_world_query_filter = if impl_filter {
320+
quote! {
321+
impl #user_impl_generics #path::query::WorldQueryFilter for #struct_name #user_ty_generics #user_where_clauses {}
322+
}
323+
} else {
324+
quote! {}
325+
};
326+
327+
let assert_world_query_filter = if impl_filter {
328+
quote! {
329+
fn assert_world_query_filter<T>()
330+
where
331+
T: #path::query::WorldQueryFilter,
332+
{
333+
}
334+
}
335+
} else {
336+
quote! {}
337+
};
338+
339+
let world_query_filter_asserts = if impl_filter {
340+
quote! {
341+
// Statically checks that the field implements `WorldQueryFilter`.
342+
// We need this to make sure that we can't implement `WorldQueryFilter` for structs with non-`WorldQueryFilter` fields. I.e.:
343+
// ```
344+
// #[derive(WorldQueryFilter)]
345+
// pub struct Foo { a: &'static mut MyComponent }
346+
// ```
347+
#( assert_world_query_filter::<#field_types>(); )*
348+
}
349+
} else {
350+
quote! {}
351+
};
352+
319353
let tokens = TokenStream::from(quote! {
320354
#fetch_impl
321355

322356
#state_impl
323357

324358
#read_only_fetch_impl
325359

360+
#impl_world_query_filter
361+
326362
impl #user_impl_generics #path::query::WorldQuery for #struct_name #user_ty_generics #user_where_clauses {
327363
type State = #state_struct_name #user_ty_generics;
328364
fn shrink<'__wlong: '__wshort, '__wshort>(item: #path::query::#item_type_alias<'__wlong, Self>)
@@ -356,9 +392,12 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream {
356392
{
357393
}
358394

395+
#assert_world_query_filter
396+
359397
// We generate a readonly assertion for every struct member.
360398
fn assert_all #user_impl_generics_with_world () #user_where_clauses_with_world {
361399
#read_only_asserts
400+
#world_query_filter_asserts
362401
}
363402
};
364403

crates/bevy_ecs/macros/src/lib.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,14 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
431431
#[proc_macro_derive(WorldQuery, attributes(world_query))]
432432
pub fn derive_world_query(input: TokenStream) -> TokenStream {
433433
let ast = parse_macro_input!(input as DeriveInput);
434-
derive_world_query_impl(ast)
434+
derive_world_query_impl(ast, false)
435+
}
436+
437+
/// Implement `WorldQueryFilter` to use a struct as a parameter in a query
438+
#[proc_macro_derive(WorldQueryFilter, attributes(world_query))]
439+
pub fn derive_world_query_filter(input: TokenStream) -> TokenStream {
440+
let ast = parse_macro_input!(input as DeriveInput);
441+
derive_world_query_impl(ast, true)
435442
}
436443

437444
#[proc_macro_derive(SystemLabel)]

crates/bevy_ecs/src/lib.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ mod tests {
5656
bundle::Bundle,
5757
component::{Component, ComponentId},
5858
entity::Entity,
59-
query::{Added, ChangeTrackers, Changed, FilteredAccess, With, Without, WorldQuery},
59+
query::{Added, ChangeTrackers, Changed, FilteredAccess, With, Without, WorldQueryFilter},
6060
world::{Mut, World},
6161
};
6262
use bevy_tasks::{ComputeTaskPool, TaskPool};
@@ -900,7 +900,7 @@ mod tests {
900900
}
901901
}
902902

903-
fn get_filtered<F: WorldQuery>(world: &mut World) -> Vec<Entity> {
903+
fn get_filtered<F: WorldQueryFilter>(world: &mut World) -> Vec<Entity> {
904904
world
905905
.query_filtered::<Entity, F>()
906906
.iter(world)

crates/bevy_ecs/src/query/fetch.rs

+41-20
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::{
88
world::{Mut, World},
99
};
1010
use bevy_ecs_macros::all_tuples;
11-
pub use bevy_ecs_macros::WorldQuery;
11+
pub use bevy_ecs_macros::{WorldQuery, WorldQueryFilter};
1212
use bevy_ptr::{ThinSlicePtr, UnsafeCellDeref};
1313
use std::{cell::UnsafeCell, marker::PhantomData};
1414

@@ -28,19 +28,11 @@ use std::{cell::UnsafeCell, marker::PhantomData};
2828
/// - `Option<WQ>`: Queries the inner [`WorldQuery`] `WQ` but instead of discarding the entity if the world
2929
/// query fails it returns [`None`]. See [`Query`](crate::system::Query).
3030
/// - `(WQ1, WQ2, ...)`: Queries all contained world queries allowing to query for more than one thing.
31-
/// This is the `And` operator for filters. See [`Or`].
31+
/// This is the `And` operator for filters. See [`Or`](crate::query::Or).
3232
/// - `ChangeTrackers<C>`: See the docs of [`ChangeTrackers`].
3333
/// - [`Entity`]: Using the entity type as a world query will grant access to the entity that is
3434
/// being queried for. See [`Entity`].
3535
///
36-
/// Bevy also offers a few filters like [`Added`](crate::query::Added), [`Changed`](crate::query::Changed),
37-
/// [`With`](crate::query::With), [`Without`](crate::query::Without) and [`Or`].
38-
/// For more information on these consult the item's corresponding documentation.
39-
///
40-
/// [`Or`]: crate::query::Or
41-
///
42-
/// # Derive
43-
///
4436
/// This trait can be derived with the [`derive@super::WorldQuery`] macro.
4537
///
4638
/// You may want to implement a custom query with the derive macro for the following reasons:
@@ -260,14 +252,28 @@ use std::{cell::UnsafeCell, marker::PhantomData};
260252
///
261253
/// # bevy_ecs::system::assert_is_system(my_system);
262254
/// ```
255+
pub trait WorldQuery: for<'w> WorldQueryGats<'w, _State = Self::State> {
256+
type State: FetchState;
257+
258+
/// This function manually implements variance for the query items.
259+
fn shrink<'wlong: 'wshort, 'wshort>(item: QueryItem<'wlong, Self>) -> QueryItem<'wshort, Self>;
260+
}
261+
262+
/// Types that can be used as a query filter for queries from a [`World`].
263+
///
264+
/// Bevy offers a few filters like [`Added`](crate::query::Added), [`Changed`](crate::query::Changed),
265+
/// [`With`](crate::query::With), [`Without`](crate::query::Without) and [`Or`].
266+
/// For more information on these consult the item's corresponding documentation.
263267
///
264-
/// ## Filters
268+
/// [`Or`]: crate::query::Or
265269
///
266-
/// Using [`derive@super::WorldQuery`] macro we can create our own query filters.
270+
/// # Derive
271+
///
272+
/// Using [`derive@super::WorldQueryFilter`] macro we can create our own query filters.
267273
///
268274
/// ```
269275
/// # use bevy_ecs::prelude::*;
270-
/// use bevy_ecs::{query::WorldQuery, component::Component};
276+
/// use bevy_ecs::{query::WorldQueryFilter, component::Component};
271277
///
272278
/// #[derive(Component)]
273279
/// struct Foo;
@@ -278,7 +284,7 @@ use std::{cell::UnsafeCell, marker::PhantomData};
278284
/// #[derive(Component)]
279285
/// struct Qux;
280286
///
281-
/// #[derive(WorldQuery)]
287+
/// #[derive(WorldQueryFilter)]
282288
/// struct MyFilter<T: Component, P: Component> {
283289
/// _foo: With<Foo>,
284290
/// _bar: With<Bar>,
@@ -292,12 +298,22 @@ use std::{cell::UnsafeCell, marker::PhantomData};
292298
///
293299
/// # bevy_ecs::system::assert_is_system(my_system);
294300
/// ```
295-
pub trait WorldQuery: for<'w> WorldQueryGats<'w, _State = Self::State> {
296-
type State: FetchState;
297-
298-
/// This function manually implements variance for the query items.
299-
fn shrink<'wlong: 'wshort, 'wshort>(item: QueryItem<'wlong, Self>) -> QueryItem<'wshort, Self>;
300-
}
301+
///
302+
/// **Note:** component types themselves can't be used as filters:
303+
///
304+
/// ```compile_fail
305+
/// # use bevy_ecs::prelude::*;
306+
/// use bevy_ecs::query::WorldQueryFilter;
307+
///
308+
/// #[derive(Component)]
309+
/// struct Foo;
310+
///
311+
/// #[derive(WorldQueryFilter)]
312+
/// struct FooFilter {
313+
/// foo: &'static Foo,
314+
/// }
315+
/// ```
316+
pub trait WorldQueryFilter: WorldQuery {}
301317

302318
/// The [`Fetch`] of a [`WorldQuery`], which declares which data it needs access to
303319
pub type QueryFetch<'w, Q> = <Q as WorldQueryGats<'w>>::Fetch;
@@ -1555,6 +1571,11 @@ macro_rules! impl_tuple_fetch {
15551571
}
15561572
}
15571573

1574+
#[allow(non_snake_case)]
1575+
#[allow(clippy::unused_unit)]
1576+
impl<$($name: WorldQueryFilter),*> WorldQueryFilter for ($($name,)*) {
1577+
}
1578+
15581579
/// SAFETY: each item in the tuple is read only
15591580
unsafe impl<'w, $($name: ReadOnlyFetch),*> ReadOnlyFetch for ($($name,)*) {}
15601581

crates/bevy_ecs/src/query/filter.rs

+13-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::{
44
entity::Entity,
55
query::{
66
debug_checked_unreachable, Access, Fetch, FetchState, FilteredAccess, QueryFetch,
7-
ROQueryFetch, WorldQuery, WorldQueryGats,
7+
ROQueryFetch, WorldQuery, WorldQueryFilter, WorldQueryGats,
88
},
99
storage::{ComponentSparseSet, Table, Tables},
1010
world::World,
@@ -55,6 +55,8 @@ impl<T: Component> WorldQuery for With<T> {
5555
}
5656
}
5757

58+
impl<T: Component> WorldQueryFilter for With<T> {}
59+
5860
/// The [`Fetch`] of [`With`].
5961
#[doc(hidden)]
6062
pub struct WithFetch<T> {
@@ -195,6 +197,8 @@ impl<T: Component> WorldQuery for Without<T> {
195197
}
196198
}
197199

200+
impl<T: Component> WorldQueryFilter for Without<T> {}
201+
198202
/// The [`Fetch`] of [`Without`].
199203
#[doc(hidden)]
200204
pub struct WithoutFetch<T> {
@@ -351,6 +355,11 @@ macro_rules! impl_query_filter_tuple {
351355
}
352356
}
353357

358+
#[allow(unused_variables)]
359+
#[allow(non_snake_case)]
360+
impl<$($filter: WorldQuery),*> WorldQueryFilter for Or<($($filter,)*)> {
361+
}
362+
354363
#[allow(unused_variables)]
355364
#[allow(non_snake_case)]
356365
impl<'w, $($filter: WorldQueryGats<'w>),*> WorldQueryGats<'w> for Or<($($filter,)*)> {
@@ -523,6 +532,9 @@ macro_rules! impl_tick_filter {
523532
}
524533
}
525534

535+
impl<T: Component> WorldQueryFilter for $name<T> {
536+
}
537+
526538
// SAFETY: this reads the T component. archetype component access and component access are updated to reflect that
527539
unsafe impl<T: Component> FetchState for $state_name<T> {
528540
fn init(world: &mut World) -> Self {

0 commit comments

Comments
 (0)