Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Implement WorldQuery derive macro #2713

Closed
wants to merge 14 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,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.
mvlabat marked this conversation as resolved.
Show resolved Hide resolved
///
/// 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm surprised by this limitation: tuple structs are named structs, just with dumb field names of 0, 1 etc.

See rust-lang/rust-clippy#7985

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, cool, I forgot about this. This indeed makes it a lot easier to support tuple structs

Copy link
Contributor Author

@mvlabat mvlabat Jan 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've just tried it - unfortunately, it doesn't make it much easier to implement a macro.

Tuple structs can't be declared like this:

struct Test {
    0: i32,
}

Also, Ident::new("0", Span::call_site()); panics.

Without this, adding tuple structs support won't be free and will likely produce much more convoluted conditions inside the macro code. Btw, our Bundle derive doesn't support tuple structs either, so I'd prefer to leave it as is for now for consistency and simplicity sake.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine by me for now. Although do note that #3634 exists.

///
/// ```
/// # 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>,
/// }
///
mvlabat marked this conversation as resolved.
Show resolved Hide resolved
/// // 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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should prevent users forgeting to implement FetchedItem and thus make WorldQuerys that don't support the derive.

Suggested change
type Item;
type Item: FetchedItem;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I tried to do this, but unfortunately, it won't work for filters, as their Item is bool. And bool can mean absolutely any filter...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we impl FetchedItem for bool?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alice-i-cecile I'm afraid we can't. <<With<T> as WorldQuery>::Fetch as Fetch>::Item corresponds to bool. So does Without<T>. As it's not a one-to-one relation for filters, I think there's no way to implement it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we'll be able to do it one day if we ban using filters as regular queries' items.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I guess we can do it like so: e65d274

It's obviously useless for determining a filter type but allows us to add FetchedItem bound to Fetch::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