-
-
Notifications
You must be signed in to change notification settings - Fork 4k
[Merged by Bors] - Implement WorldQuery
derive macro
#2713
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
Changes from all commits
ba31c9b
3cb3fb6
8574a8a
a717b7b
f67876a
005fbf6
ba68958
b7845d9
0a8ba72
cfe8db3
a2f3d3b
27b1155
96f6043
10510f2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -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, | ||||||
|
@@ -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. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, Without this, adding tuple structs support won't be free and will likely produce much more convoluted conditions inside the macro code. Btw, our There was a problem hiding this comment. Choose a reason for hiding this commentThe 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>, | ||||||
/// } | ||||||
/// | ||||||
vladbat00 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; | ||||||
|
@@ -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; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should prevent users forgeting to implement
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @alice-i-cecile I'm afraid we can't. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||||||
type State: FetchState; | ||||||
|
Uh oh!
There was an error while loading. Please reload this page.