-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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 iter() for mutable Queries #2305
Conversation
bors try |
Note that I believe #1301 is calling for |
tryBuild failed: |
Thought this was going to be a normal PR since GitHub was being odd with the button... Left as draft due to:
|
I'm not sure this is something we want, as it would encourage using a mutable query when an immutable would work, and mutable have a heavier cost for system parallelisation. Having to use |
crates/bevy_ecs/src/system/query.rs
Outdated
// TODO: This code can be replaced with `self.iter().next().is_none()` if/when | ||
// we sort out how to convert "write" queries to "read" queries. | ||
self.state | ||
.is_empty(self.world, self.last_change_tick, self.change_tick) | ||
self.iter().next().is_none() |
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.
If this PR lands, I'm curious if we actually want to replace the implementation of is_empty
with this.
Now that I think of it, having a specialized is_empty
functionality actually gives us a small performance win (even if it is at the cost of extra code duplication). Since in the implementation we never have to touch self.fetch
so we bypass that extra unnecessary code path.
Where as in here, we'll have to follow the normal code path and do a bit of extra work.
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 refactored this to use a NopFetch
to get the performance benefit without the code duplication. How does that look?
Went and applied the refactor to all of the existing locations. Now the only reference to I tried to transform the non- |
filter: F::Fetch, | ||
current_len: usize, | ||
current_index: usize, | ||
is_dense: bool, | ||
phantom: PhantomData<&'w Q>, |
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 believe that QF: Fetch<'s>
and dropping the Q
and 'w
would work for this, as I believe the lifetime parameter on Fetch
is "lasts at least as long" but I wasn't 100% sure so went with a more obviously "correct" solution of including them with a PhantomData.
bors try |
tryBuild succeeded: |
Let me know if it needs to be repeated on the other issue but: I license past and future contributions under the dual MIT/Apache-2.0 license, allowing licensees to choose either at their option. |
d7845da
to
857cb9b
Compare
@DJMcNab IIRC we wanted this all in one thread? This should suffice, but I'll leave it up to your judgement there :) |
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.
This looks fantastic; that's a nice batch of changes! I've done a thorough review of the code, but I could not spot any particular nits to pick.
It may be nice to have a test to verify that this new functionality continues to work, but I'll leave that to your judgement.
Commented on linked issue as well. I can throw together a unit test that verified |
Looks like it's exposed in |
I am worried about a user constructing with a custom I haven't done the mental work to decide what it means to have a user supply a custom |
1a59210
to
fe78224
Compare
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've given this another look over and it looks ready to merge. Very nice usability / correctness win, and the docs and tests are solid.
One minor nit, but feel free to merge without it being addressed.
@@ -973,8 +962,6 @@ where | |||
/// ``` | |||
#[inline] | |||
pub fn is_empty(&self) -> bool { | |||
// TODO: This code can be replaced with `self.iter().next().is_none()` if/when | |||
// we sort out how to convert "write" queries to "read" queries. | |||
self.state |
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.
We should replace this with the code in the comment now that we've figured out that conversion. I expect it will be both cleaner and marginally faster.
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.
QueryState::is_empty
existed and got updated so I left this as a pass through to it to avoid changing any pub fn
Also the implementation isn't quite what is described in the comment as the actual implementation uses a custom Fetch
to avoid touching any of the data much like the custom is_empty
did (NopFetch
)
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.
Sounds good to me.
fe78224
to
b38bf90
Compare
Any ETA on merging this? |
We could use another review or two; if you're comfortable evaluating it feedback would be helpful @deontologician. Otherwise, it should be one of the top priorities to be merged once Cart peels away from the focused rendering work. |
So, I don't have an informed opinion on whether this PR is the right fix for bevy architecturally etc, but I did manage to compile this PR of fixes for bevy_rapier by @pcwalton: I had to add some things to deal with the new |
The biggest change here is that doing the same query twice, one mutably and one immutably, is no longer needed, once bevyengine/bevy#2305 lands. Before that lands, however, this upgrade is actually impossible to do safely, since the `q0`/`q0_mut` distinction is gone and therefore it's impossible to implement the Rapier component traits on Bevy queries. (This is filed upstream as bevyengine/bevy#2744.) Therefore, this upgrade has to wait until that Bevy PR lands. Other minor changes: * `Light` has been renamed to `PointLight`. * `App::build()` is now `App::new()`, and `AppBuilder` is now `App`. * `glam` has been upgraded upstream.
- Requires that all queries specify an immutable Fetch with identical State - Added a unit test to verify that calling iter vs iter_mut returns the same things - Added a second unit test to verify that calling iter_mut borrows correctly
b38bf90
to
65e1885
Compare
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.
Noticed a conflict which was easy to resolve (if monotonous) however upon building the new ExtractComponent
is super unhappy with how I broke QueryItem<T>
...
I really wish there was a way to guarentee that when Query: ReadOnlyQuery
then the two types are the same but I am almost certain that is an impossible ask.
I can totally ensure that their Item
values are the same (as I did with State
just weirder bounds) but that doesn't work due to &T
vs Mut<T>
for WriteQuery
...
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.
This generally looks good to me. Impressive first PR! The extra QF type complexity in our internal apis is a bit painful, but I can't think of alternatives that don't involve code duplication. And QF does give us additional flexibility, which has already yielded fruit (ex: the NopFetch impl). We might even find more creative uses of this pattern in the future. I've resolved a couple of my comments already, which I'll push after leaving this review.
bors r+ |
A sample implementation of how to have `iter()` work on mutable queries without breaking aliasing rules. # Objective - Fixes #753 ## Solution - Added a ReadOnlyFetch to WorldQuery that is the `&T` version of `&mut T` that is used to specify the return type for read only operations like `iter()`. - ~~As the comment suggests specifying the bound doesn't work due to restrictions on defining recursive implementations (like `Or`). However bounds on the functions are fine~~ Never mind I misread how `Or` was constructed, bounds now exist. - Note that the only mutable one has a new `Fetch` for readonly as the `State` has to be the same for any of this to work Co-authored-by: Carter Anderson <mcanders1@gmail.com>
The biggest change here is that doing the same query twice, one mutably and one immutably, is no longer needed, once bevyengine/bevy#2305 lands. Before that lands, however, this upgrade is actually impossible to do safely, since the `q0`/`q0_mut` distinction is gone and therefore it's impossible to implement the Rapier component traits on Bevy queries. (This is filed upstream as bevyengine/bevy#2744.) Therefore, this upgrade has to wait until that Bevy PR lands. Other minor changes: * `Light` has been renamed to `PointLight`. * `App::build()` is now `App::new()`, and `AppBuilder` is now `App`. * `glam` has been upgraded upstream.
A sample implementation of how to have
iter()
work on mutable queries without breaking aliasing rules.Objective
Solution
&T
version of&mut T
that is used to specify the return type for read only operations likeiter()
.As the comment suggests specifying the bound doesn't work due to restrictions on defining recursive implementations (likeNever mind I misread howOr
). However bounds on the functions are fineOr
was constructed, bounds now exist.Fetch
for readonly as theState
has to be the same for any of this to work