-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Split WorldQuery into WorldQueryData and WorldQueryFilter #9918
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
Split WorldQuery into WorldQueryData and WorldQueryFilter #9918
Conversation
Alright, I'm going to review this and for my own sanity, I'll split it up in parts. Here's a short checklist for what I'm going to do.
I'll let you know if I have any questions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm done looking at the non-macro ecs code. I think the separation of filter and data queries cleans it up a lot.
/// `fetch` accesses a single component in a readonly way. | ||
/// This is sound because `update_component_access` and `update_archetype_component_access` add read access for that component and panic when appropriate. | ||
/// `update_component_access` adds a `With` filter for a component. | ||
/// This is sound because `matches_component_set` returns whether the set contains that component. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What exactly do you mean here?
I've looked at the macro code and I'll be honest: I am not qualified to give a good opinion on it. I know this is not the most rigorous but given that it has passed all the CI, I'm assuming that it is probably fine. |
By restricting Q to |
Maybe I'm missing something, but I don't really see the utility behind splitting the traits in this way. What kind of bugs would this actually prevent? Why do we want to prevent people from writing The |
|
That's a good point - I have added that example to the migration guide. |
IIRC |
Just for illustration, I made a quick and dirty implementation of my suggestion on this branch: main...JoJoJet:bevy:query-filter I prefer this approach since it a simpler change, is far less breaking for users, retains flexibility, and still fixes the major footguns. |
I double-checked what EDIT: Never mind, I thought of one. It can be used as a workaround for inverse filters (see also #7265) fn system(query: Query<Changed<Foo>>, query_two: Query<Foo>) {
let has_changed = query.to_vec();
let foos = query_two.to_vec();
for i in 0..has_changed.size() {
if !has_changed[i] {
// do something to the unchanged foos[i]
}
}
} |
It's not particularly useful since you can just use |
Sorry for being slow replying - having a very busy weekend. I wasn't aware that Just to explain, my bug looked something like this. I think it's fairly unlikely that I would notice it to be incorrect if I read it in someone else's code: fn my_function(query: Query<Changed<ComponentA>>){
if !query.is_empty()
{
// do expensive computation that's only necessary when at least one `ComponentA` has changed
}
} I find it particularly unintuitive that I will certainly admit that bugs caused by this are likely to be rare (to assume otherwise based on my having been affected by one would be to succumb to selection bias) and the changes in this PR will likely break a lot of users who happen to be using filters "incorrectly" but whose program works perfectly well. I am not well placed to make a judgement about which concern is the more pressing but I did want to at least make clear the advantages of my approach.
I've just read it casually but this all looks very sensible. I particularly like how you've done the macros. |
I've thought about it some more and I think that disallowing filters in the data slot is the right choice. The possible use you could get out of a vector of booleans without components to give it context is minimal. As the documentation states, you could also check for I think that there is potential for filters in the data slot, e.g. Query<(Added<&ComponentA>, &ComponentB)>> == Query<(&ComponentA, &ComponentB), Added<ComponentA>> This would add a nice bit of syntactic sugar to queries. This should probably not be implemented for But for now, I think it's fine to remove the possibility for it outright as the current state is more confusing than helpful. This feature is also - as far as I've checked - completely undocumented (admittedly, that's an easy fix). |
FYI, |
@wainwrightmark I'm doing a review pass to try and get this into 0.12 (ETA Saturday): can you resolve merge conflicts for us? |
Waiting on another review from an SME-ECS. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall, I'm in favor of moving in this direction. It makes the Query DSL much more well-formed, without allowing for odd edge case interactions. The code seems to work, and there aren't any glaring unsoundness issues. Most of my comments here are focused on code quality and style. LGTM otherwise.
user_ty_generics: &TypeGenerics, | ||
user_ty_generics_with_world: &TypeGenerics, | ||
user_where_clauses_with_world: Option<&WhereClause>, | ||
) -> proc_macro2::TokenStream { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any way to avoid the sheer number of arguments here and below?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Quite possibly. I will have a go at factoring all the arguments into a small number of structs instead.
I have done this. It's a bit scary because it looks like there are 400 files changed but the only files I had to touch were: examples\ecs\custom_query_param.rs slight conflict with imports crates\bevy_render\src\batching\mod.rs The crates\bevy_pbr\src\render\mesh.rs and crates\bevy_animation\src\lib.rs someone else had done the migration to crates\bevy_render\src\extract_instances.rs crates\bevy_ecs\src\query\filter.rs There were a couple of conflicts around |
Will try to do the larger changes suggested by @james7132 this afternoon (GMT) |
I have moved |
I have had a go at tidying the macros (2098790) - it working and expanding to the same code as before but I'm not 100% happy with it yet and realistically I think it would be better to do it in a separate PR. |
@wainwrightmark this is in a good state; can you please resolve conflicts and we can merge? |
Added breaking change label since it requires adapting |
Sorry for taking a few days to get round to this. I merged main back and resolved the conflicts. They were mostly trivial, just tweaking the use statements in And there were some comments added to crates/bevy_ecs/src/query/filter.rs in 48af029 which I copied across. |
For future people clicking on the Split WorldQuery into WorldQueryData and WorldQueryFilter link in the blog post and not realizing these were renamed: #10776 |
Objective
Solution
The traits
WorldQueryData : WorldQuery
andWorldQueryFilter : WorldQuery
have been added and some of the types and functions fromWorldQuery
has been moved into them.ReadOnlyWorldQuery
has been replaced withReadOnlyWorldQueryData
.WorldQueryFilter
is safe (as long asWorldQuery
is implemented safely).WorldQueryData
is unsafe - safely implementing it requires thatSelf::ReadOnly
is a readonly version ofSelf
(this used to be a safety requirement ofWorldQuery
)The type parameters
Q
andF
ofQuery
must now implementWorldQueryData
andWorldQueryFilter
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. UseHas<T>
instead in these cases.The
WorldQuery
derive macro has been split into separate derive macros forWorldQueryData
andWorldQueryFilter
.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 replacingOption<With<T>>
withHas<T>
.All
WorldQueryData
types always returnedtrue
forIS_ARCHETYPAL
so I moved it toWorldQueryFilter
andreplaced all calls to it with
true
. That should be the only logic change outside of the macro generation code.Changed<T>
andAdded<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
WorldQuery
intoWorldQueryData
andWorldQueryFilter
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 implementsWorldQueryData
and the second implementsWorldQueryFilter
Migration Guide
Option<With<T>>
withHas<T>