-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Introduce methods on QueryState to obtain a Query #15858
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
Conversation
…ry to consume Self and return the original lifetime.
I agree that there's a lot of duplication between QueryState's and Query's API and it's good that this is lessened, but just out of curiosity (just to offer a potentially dumb idea), would it perhaps be better if QueryState and Query were just one single struct instead of two? |
I think the idea is that you need access to the world to actually do querying, but that we want to cache some state that lives longer than that borrow. So a I don't see a clean way to get rid of either. If you don't have |
I realized I could split this into two independent PRs: One for the methods on |
I say go big! Go ahead and remove the to-be-deprecated methods on QueryState! 😆 You can split it up if you think it would help, but I don't mind bigger PRs. |
@@ -1542,24 +1542,6 @@ mod tests { | |||
}); | |||
} | |||
|
|||
#[test] | |||
#[should_panic = "Encountered a mismatched World."] | |||
fn query_validates_world_id() { |
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.
Why remove this test? Is it no longer applicable?
#[inline] | ||
pub(crate) unsafe fn new( | ||
world: UnsafeWorldCell<'w>, | ||
state: &'s QueryState<D, F>, | ||
last_run: Tick, | ||
this_run: Tick, | ||
) -> Self { | ||
state.validate_world(world.id()); |
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.
Why remove this check?
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.
Oh, right, that was subtle and I should have pointed it out!
The reason is that I changed all of the &self
and &mut self
methods on Query
to call self.as_readonly().actual_method()
and self.reborrow().actual_method()
, and as_readonly()
and reborrow()
are implemented in terms of Query::new()
. I wanted to make sure that change was zero-cost, but checking the assembly with cargo-show-asm
showed that it was adding a call to validate_world
to every existing method on Query
! Since we always had a world that was already validated, I changed it to be a safety requirement, and now the assembly appears to be unchanged.
(Then I removed the test you asked about above because it's no longer true that it panics.)
I think it would be good to add more examples/tests that might help miri find |
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 good to me, mostly just nits although I do have one question about that IntoIterator
for Query
?
@@ -381,6 +381,14 @@ pub struct Query<'world, 'state, D: QueryData, F: QueryFilter = ()> { | |||
this_run: Tick, | |||
} | |||
|
|||
impl<D: ReadOnlyQueryData, F: QueryFilter> Clone for Query<'_, '_, D, F> { | |||
fn clone(&self) -> Self { | |||
*self |
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.
sorry, noob question: how does this work?
The &QueryState
inside the Query is simply copied? i.e. we create a new Query that points to the same QueryState?
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.
Yup! Everything in Query
is Copy
, which means it can be copied by memcpy
. That includes references like &QueryState
, which just makes a new reference to the same underlying QueryState
. So we can make Query
be Copy
, and then implement Clone
in terms of it.
/// Creates a [`Query`] from the given [`QueryState`] and [`World`]. | ||
/// | ||
/// This will create read-only queries, see [`Self::query_mut`] for mutable queries. | ||
pub fn query<'w, 's>(&'s mut self, world: &'w World) -> Query<'w, 's, D::ReadOnly, F> { |
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.
Oh my gosh finally.
self.state | ||
.iter_unchecked_manual(self.world, self.last_run, self.this_run) | ||
} | ||
self.reborrow().into_iter() |
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.
Sorry noob question again could you please explain again why we need to re-borrow?
self.reborrow().into_iter()
creates a new Query with reborrow()
and then borrows from that with into_iter()
self.into_iter()
would simply create a QueryIter that borrows from the initital &'a mut Query
so the lifetime is constrained by `a; is that the issue?
The lifetimes are elided on reborrow
so I don't get exactly what is happening
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.
reborrow()
goes from &'a mut Query<'w>
to Query<'a>
, turning a borrowed Query
into an owned one with a shorter lifetime.
into_iter()
takes self
instead of &mut self
, so it actually consumes the Query
.
We have an &mut Query
, so we first reborrow()
to get an owned Query
with a shorter lifetime, and then use into_iter()
to consume the owned query and create a QueryIter
- still with the shorter lifetime.
It's more complex when looking at one method, but across the whole type it means we can re-use the safety proofs in reborrow()
and as_readonly()
. That lets us implement this method without unsafe
code, and means the compiler would catch it if we tried to return QueryIter<'w, ...>
instead of QueryIter<'_, ...>
.
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.
Sorry to block this (the merge queue got there first), but I don't think Query
being Copy
is sound?
IIUC you can go from a mutable Query
to a read-only QueryLens
, make a Query
out of that lens again, then copy that Query
, transmute_lens
it again, and get a Query
from it for a last time. Then you'd have a read-only query that is no longer bound to the &mut self
of the original mutable Query
.
Did I overlook something?
Discuss in #17693. |
Something else I'd like to note is that this PR implements |
Yup! That's the generalization of I'm hoping it will be a nicer experience for users: You can write the obvious And because read-only queries also |
And while I'd been focusing on For example: let mut lens: QueryLens<_> = ...;
// Fails with "error[E0716]: temporary value dropped while borrowed"
let first = lens.query().iter_mut().next();
assert!(first.is_some());
// Works!
let first = lens.query().into_iter().next();
assert!(first.is_some()); |
# Objective Restore the behavior of `Query::get_many` prior to #15858. When passed duplicate `Entity`s, `get_many` is supposed to return results for all of them, since read-only queries don't alias. However, #15858 merged the implementation with `get_many_mut` and caused it to return `QueryEntityError::AliasedMutability`. ## Solution Introduce a new `Query::get_many_readonly` method that consumes the `Query` like `get_many_inner`, but that is constrained to `D: ReadOnlyQueryData` so that it can skip the aliasing check. Implement `Query::get_many` in terms of that new method. Add a test, and a comment explaining why it doesn't match the pattern of the other `&self` methods.
# Objective Fix unsoundness introduced by #15858. `QueryLens::query()` would hand out a `Query` with the full `'w` lifetime, and the new `_inner` methods would let the results outlive the `Query`. This could be used to create aliasing mutable references, like ```rust fn bad<'w>(mut lens: QueryLens<'w, EntityMut>, entity: Entity) { let one: EntityMut<'w> = lens.query().get_inner(entity).unwrap(); let two: EntityMut<'w> = lens.query().get_inner(entity).unwrap(); assert!(one.entity() == two.entity()); } ``` Fixes #17693 ## Solution Restrict the `'world` lifetime in the `Query` returned by `QueryLens::query()` to `'_`, the lifetime of the borrow of the `QueryLens`. The model here is that `Query<'w, 's, D, F>` and `QueryLens<'w, D, F>` have permission to access their components for the lifetime `'w`. So going from `&'a mut QueryLens<'w>` to `Query<'w, 'a>` would borrow the permission only for the `'a` lifetime, but incorrectly give it out for the full `'w` lifetime. To handle any cases where users were calling `get_inner()` or `iter_inner()` on the `Query` and expecting the full `'w` lifetime, we introduce a new `QueryLens::query_inner()` method. This is only valid for `ReadOnlyQueryData`, so it may safely hand out a copy of the permission for the full `'w` lifetime. Since `get_inner()` and `iter_inner()` were only valid on `ReadOnlyQueryData` prior to #15858, that should cover any uses that relied on the longer lifetime. ## Migration Guide Users of `QueryLens::query()` who were calling `get_inner()` or `iter_inner()` will need to replace the call with `QueryLens::query_inner()`.
The reasoning suggests that this is mainly to work around I have some thoughts on It feels like we are stacking features/implementation logic on this detail, when really the proper solution should be to make a Ideally, consuming methods will not be needed, but more likely will still have their place. All in all, I'm struggling to keep a mental model of all this |
…#17822) # Objective Simplify the API surface by removing duplicated functionality between `Query` and `QueryState`. Reduce the amount of `unsafe` code required in `QueryState`. This is a follow-up to #15858. ## Solution Move implementations of `Query` methods from `QueryState` to `Query`. Instead of the original methods being on `QueryState`, with `Query` methods calling them by passing the individual parameters, the original methods are now on `Query`, with `QueryState` methods calling them by constructing a `Query`. This also adds two `_inner` methods that were missed in #15858: `iter_many_unique_inner` and `single_inner`. One goal here is to be able to deprecate and eventually remove many of the methods on `QueryState`, reducing the overall API surface. (I expected to do that in this PR, but this change was large enough on its own!) Now that the `QueryState` methods each consist of a simple expression like `self.query(world).get_inner(entity)`, a future PR can deprecate some or all of them with simple migration instructions. The other goal is to reduce the amount of `unsafe` code. The current implementation of a read-only method like `QueryState::get` directly calls the `unsafe fn get_unchecked_manual` and needs to repeat the proof that `&World` has enough access. With this change, `QueryState::get` is entirely safe code, with the proof that `&World` has enough access done by the `query()` method and shared across all read-only operations. ## Future Work The next step will be to mark the `QueryState` methods as `#[deprecated]` and migrate callers to the methods on `Query`.
# Objective Simplify and expand the API for `QueryState`. `QueryState` has a lot of methods that mirror those on `Query`. These are then multiplied by variants that take `&World`, `&mut World`, and `UnsafeWorldCell`. In addition, many of them have `_manual` variants that take `&QueryState` and avoid calling `update_archetypes()`. Not all of the combinations exist, however, so some operations are not possible. ## Solution Introduce methods to get a `Query` from a `QueryState`. That will reduce duplication between the types, and ensure that the full `Query` API is always available for `QueryState`. Introduce methods on `Query` that consume the query to return types with the full `'w` lifetime. This avoids issues with borrowing where things like `query_state.query(&world).get(entity)` don't work because they borrow from the temporary `Query`. Finally, implement `Copy` for read-only `Query`s. `get_inner` and `iter_inner` currently take `&self`, so changing them to consume `self` would be a breaking change. By making `Query: Copy`, they can consume a copy of `self` and continue to work. The consuming methods also let us simplify the implementation of methods on `Query`, by doing `fn foo(&self) { self.as_readonly().foo_inner() }` and `fn foo_mut(&mut self) { self.reborrow().foo_inner() }`. That structure makes it more difficult to accidentally extend lifetimes, since the safe `as_readonly()` and `reborrow()` methods shrink them appropriately. The optimizer is able to see that they are both identity functions and inline them, so there should be no performance cost. Note that this change would conflict with bevyengine#15848. If `QueryState` is stored as a `Cow`, then the consuming methods cannot be implemented, and `Copy` cannot be implemented. ## Future Work The next step is to mark the methods on `QueryState` as `#[deprecated]`, and move the implementations into `Query`. ## Migration Guide `Query::to_readonly` has been renamed to `Query::as_readonly`.
Thank you to everyone involved with the authoring or reviewing of this PR! This work is relatively important and needs release notes! Head over to bevyengine/bevy-website#1990 if you'd like to help out. |
Objective
Simplify and expand the API for
QueryState
.QueryState
has a lot of methods that mirror those onQuery
. These are then multiplied by variants that take&World
,&mut World
, andUnsafeWorldCell
. In addition, many of them have_manual
variants that take&QueryState
and avoid callingupdate_archetypes()
. Not all of the combinations exist, however, so some operations are not possible.Solution
Introduce methods to get a
Query
from aQueryState
. That will reduce duplication between the types, and ensure that the fullQuery
API is always available forQueryState
.Introduce methods on
Query
that consume the query to return types with the full'w
lifetime. This avoids issues with borrowing where things likequery_state.query(&world).get(entity)
don't work because they borrow from the temporaryQuery
.Finally, implement
Copy
for read-onlyQuery
s.get_inner
anditer_inner
currently take&self
, so changing them to consumeself
would be a breaking change. By makingQuery: Copy
, they can consume a copy ofself
and continue to work.The consuming methods also let us simplify the implementation of methods on
Query
, by doingfn foo(&self) { self.as_readonly().foo_inner() }
andfn foo_mut(&mut self) { self.reborrow().foo_inner() }
. That structure makes it more difficult to accidentally extend lifetimes, since the safeas_readonly()
andreborrow()
methods shrink them appropriately. The optimizer is able to see that they are both identity functions and inline them, so there should be no performance cost.Note that this change would conflict with #15848. If
QueryState
is stored as aCow
, then the consuming methods cannot be implemented, andCopy
cannot be implemented.Future Work
The next step is to mark the methods on
QueryState
as#[deprecated]
, and move the implementations intoQuery
.Migration Guide
Query::to_readonly
has been renamed toQuery::as_readonly
.