-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Use distinct types for query "data" generics and query "filter" generics #7680
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
Comments
Hi, I've had a quick go at this and gotten all the tests and examples working - there's no logic changes, just changing some types and type parameters. I haven't done any of the "fun" bits like fixing documentation and writing tests yet. I've added The thing I'm unsure of is how the derive macros ought to work. #[derive(WorldQuery)]
pub struct DataQuery {
a: &'static A,
}
#[derive(WorldQuery)]
#[world_query(mutable)]
pub struct MutDataQuery {
pub a: &'static mut A,
}
#[derive(WorldQuery)]
struct FilterQuery{
a: With<A>,
} and it generates implementations of Should we use two different macros like this: #[derive(WorldQueryData)]
pub struct DataQuery {
a: &'static A,
}
#[derive(WorldQueryData)]
#[world_query(mutable)]
pub struct MutDataQuery {
pub a: &'static mut A,
}
#[derive(WorldQueryFilter)]
struct FilterQuery{
a: With<A>,
} That seems good but I worry that that makes it impossible to derive both for the same struct. (Both macros would generate a The other options would be: #[derive(WorldQuery)]
#[world_query(data)]
pub struct DataQuery {
a: &'static A,
}
#[derive(WorldQuery)]
#[world_query(mutable)] //data is implied by mutable
pub struct MutDataQuery {
pub a: &'static mut A,
}
#[derive(WorldQuery)]
#[world_query(filter)]
struct FilterQuery{
a: With<A>,
} or #[derive(WorldQuery, WorldQueryData)]
pub struct DataQuery {
a: &'static A,
}
#[derive(WorldQuery, WorldQueryData)]
#[world_query(mutable)]
pub struct MutDataQuery {
pub a: &'static mut A,
}
#[derive(WorldQuery, WorldQueryFilter)]
struct FilterQuery{
a: With<A>,
} or possibly one I haven't thought of. |
I would want to keep having this use case possible. I want to implement However, I want to point out I am still in a design phase and I might end up not needing this. Others might though. |
I like the two distinct
Yeah, this is an important pattern to consider how to support. We could implement My personal experience has been that these types are only really used as the only type in |
Agreed. After trying the different options, I think this is definitely the best one.
The changes I've tried so far do prevent that pattern from working so it would be good to find a way to keep it.
We could have something like #[derive(WorldQuery)]
#[world_query(mutable)]
pub struct FooWithoutBar{
foo: &'static mut Foo,
#[filter]
filter: Without<Bar>
} The macro could then exclude the filter items from the returned The problem with that approach is that it might be difficult to get to work for Another option might be to have some sort of impl FilteredItem<&'static mut Foo, Without<Bar>>
{
fn baz(self){
todo!()
}
}
fn baz_all_foos_without_bar(mut query: Query<&'static mut Foo, Without<Bar>>)
{
for item in query.as_filtered()
{
item.baz();
}
} This way keeps the separation of data and filter and it's quite nice in that you don't need to use the derive macro to take advantage of it. That said, it is a bit less ergonomic and I think it would require major refactoring of |
# Objective - Fixes #7680 - This is an updated for #8899 which had the same objective but fell a long way behind the latest changes ## Solution The traits `WorldQueryData : WorldQuery` and `WorldQueryFilter : WorldQuery` have been added and some of the types and functions from `WorldQuery` has been moved into them. `ReadOnlyWorldQuery` has been replaced with `ReadOnlyWorldQueryData`. `WorldQueryFilter` is safe (as long as `WorldQuery` is implemented safely). `WorldQueryData` is unsafe - safely implementing it requires that `Self::ReadOnly` is a readonly version of `Self` (this used to be a safety requirement of `WorldQuery`) The type parameters `Q` and `F` of `Query` must now implement `WorldQueryData` and `WorldQueryFilter` respectively. This makes it impossible to accidentally use a filter in the data position or vice versa which was something that could lead to bugs. ~~Compile failure tests have been added to check this.~~ It was previously sometimes useful to use `Option<With<T>>` in the data position. Use `Has<T>` instead in these cases. The `WorldQuery` derive macro has been split into separate derive macros for `WorldQueryData` and `WorldQueryFilter`. Previously it was possible to derive both `WorldQuery` for a struct that had a mixture of data and filter items. This would not work correctly in some cases but could be a useful pattern in others. *This is no longer possible.* --- ## Notes - The changes outside of `bevy_ecs` are all changing type parameters to the new types, updating the macro use, or replacing `Option<With<T>>` with `Has<T>`. - All `WorldQueryData` types always returned `true` for `IS_ARCHETYPAL` so I moved it to `WorldQueryFilter` and replaced all calls to it with `true`. That should be the only logic change outside of the macro generation code. - `Changed<T>` and `Added<T>` were being generated by a macro that I have expanded. Happy to revert that if desired. - The two derive macros share some functions for implementing `WorldQuery` but the tidiest way I could find to implement them was to give them a ton of arguments and ask clippy to ignore that. ## Changelog ### Changed - Split `WorldQuery` into `WorldQueryData` and `WorldQueryFilter` which now have separate derive macros. It is not possible to derive both for the same type. - `Query` now requires that the first type argument implements `WorldQueryData` and the second implements `WorldQueryFilter` ## Migration Guide - Update derives ```rust // old #[derive(WorldQuery)] #[world_query(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA } #[derive(WorldQuery)] struct QueryFilter { _c: With<ComponentC> } // new #[derive(WorldQueryData)] #[world_query_data(mutable, derive(Debug))] struct CustomQuery { entity: Entity, a: &'static mut ComponentA, } #[derive(WorldQueryFilter)] struct QueryFilter { _c: With<ComponentC> } ``` - Replace `Option<With<T>>` with `Has<T>` ```rust /// old fn my_system(query: Query<(Entity, Option<With<ComponentA>>)>) { for (entity, has_a_option) in query.iter(){ let has_a:bool = has_a_option.is_some(); //todo!() } } /// new fn my_system(query: Query<(Entity, Has<ComponentA>)>) { for (entity, has_a) in query.iter(){ //todo!() } } ``` - Fix queries which had filters in the data position or vice versa. ```rust // old fn my_system(query: Query<(Entity, With<ComponentA>)>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Entity, With<ComponentA>>) { for entity in query.iter(){ //todo!() } } // old fn my_system(query: Query<AnyOf<(&ComponentA, With<ComponentB>)>>) { for (entity, _) in query.iter(){ //todo!() } } // new fn my_system(query: Query<Option<&ComponentA>, Or<(With<ComponentA>, With<ComponentB>)>>) { for entity in query.iter(){ //todo!() } } ``` --------- Co-authored-by: Alice Cecile <[email protected]>
What problem does this solve or what need does it fill?
It's easy for users to accidentally mix up their "data" (
Q
) and "filter" (F
) generics onQuery
.For example:
Query<(&A, With<B>)
: forces ugly unpacking.Query<(&A, Without<B>)
: likely seriously wrong.Query<Changed<T>)
: probably not what the user wants.Query<(), Entity>
: nonsensicalQuery<&A, &B>
: very weird, probably brokenSee #7679 for an example of this footgun in the wild.
What solution would you like?
WorldQueryData: WorldQuery
andWorldQueryFilter: WorldQuery
.derive(WorldQuery)
macro into two parts.WorldQueryData
/WorldQueryFilter
for the correctWorldQuery
types.What alternative(s) have you considered?
We could use a custom linter #1602, but a) this catches errors later / less elegantly and b) we don't have one yet.
The text was updated successfully, but these errors were encountered: