Skip to content

Commit

Permalink
Implement WorldQuery derive macro (#2713)
Browse files Browse the repository at this point in the history
# 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
  • Loading branch information
vladbat00 committed Feb 24, 2022
1 parent e369a8a commit ba6b74b
Show file tree
Hide file tree
Showing 8 changed files with 1,069 additions and 1 deletion.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
583 changes: 583 additions & 0 deletions crates/bevy_ecs/macros/src/fetch.rs

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions crates/bevy_ecs/macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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);
Expand Down
267 changes: 267 additions & 0 deletions crates/bevy_ecs/src/query/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions crates/bevy_ecs/src/query/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
///
Expand Down
3 changes: 2 additions & 1 deletion crates/bevy_ecs/src/system/system_param.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand All @@ -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.
Expand Down
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit ba6b74b

Please sign in to comment.