-
-
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
Let query items borrow from query state to avoid needing to clone #15396
base: main
Are you sure you want to change the base?
Conversation
That takes a mutable borrow of the QueryState, which will conflict once query items may borrow the state.
This allows them to borrow from the query state, which can avoid expensive clones in some cases.
IIUC, the lack of |
It might be. The issue is that the For what it's worth, I don't think |
@@ -229,9 +229,9 @@ mod tests { | |||
let e2 = world.spawn(name.clone()).id(); | |||
let mut query = world.query::<NameOrEntity>(); | |||
let d1 = query.get(&world, e1).unwrap(); | |||
let d2 = query.get(&world, e2).unwrap(); |
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 needing to be moved is suspicious. Can you not call get multiple times on an immutable borrow of the world anymore?
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.
Not in this case, but I promise it's not as bad as it looks!
QueryState::get()
takes &mut self
so that it can call update_archetypes()
. And NameOrEntity
uses #[derive(QueryData)]
, which now generates an item struct that includes the 's
lifetime. So the item is treated as having a mutable borrow of the QueryState
.
This doesn't affect Query::get()
, which always takes &self
.
And this doesn't affect concrete types where QueryData::Item
doesn't include 's
. That is, it only affects generic parameters, #[derive(QueryData)]
types, and (in the future) FilteredEntityRef|Mut
. If we use let mut query = world.query::<(Option<&Name>, Entity)>();
here, it will compile.
And there is a workaround in this case, which is to call query.update_archetypes()
manually and then use query.get_manual()
, which takes &self
and so will allow overlapping borrows.
But, yeah, it is a breaking change here.
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.
After digesting this for a bit, maybe it isn't such a bad change. get
takes an &mut self
so hopefully it won't be that surprising for new users if you can't get multiple items out of it. And the workaround is more performant, so leading users to that is probably better.
But we should document the workaround on QueryState::get
and in the migration guide.
I haven't done a full review yet, but hopefully will get to that soon.
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.
Does this not also affect query iteration? As in, for the affected types, any item would have to be dropped before another can be retrieved?
(Like what happens in QueryManyIter::fetch_next()
, where item lifetimes are shrunk like this intentionally)
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.
Does this not also affect query iteration?
No, it doesn't. Query
has a &'s QueryState
, so we can copy that reference and hand it out with the full 's
lifetime. This change only affects methods that take &mut QueryState
.
(The issue with things like QueryManyIter::fetch_next()
and Query::iter_mut()
are that the UnsafeWorldCell<'w>
is acting like a &'w mut
, so we can't copy it and instead have to reborrow it with a shorter 'w
lifetime.)
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.
Okay, so this does not present an issue within individual iterators.
However, this will be a breaking change to more methods of QueryState
than what you've currently listed:
get
get_single
(no checked manual version)get_many
(no manual version)single
(no manual version)iter
iter_many
iter_combinations
(no manual version)par_iter
(no manual version)- and naturally, any method that takes at least
&'a QueryState
while the query items returned by one of the above is live.
The lack of manual versions can be considered API holes for some of the above, however not all of them (f.e. single
)
Do I understand correctly?
If so, I think the ability to mix these methods of QueryState
contains important use cases, and I would see those as more valuable than this change in its current form :/
Yeah, an additional collect would regress sort performance. That would make this change a perf trade-off, not a pure gain. By how much, would have to be benchmarked.
The tuple behavior is a bug: #14349. |
… they are necessary to work around borrowing QueryState mutably.
Yeah, there are definitely tradeoffs here! Given that (It feels like there must be some way to get the best of both options by dropping the
Sorry, I was wrong; it works fine. I confused the local |
To note, I think |
I'm not sure how this would help avoiding to clone the |
Objective
Improve the performance of
FilteredEntity(Ref|Mut)
andEntity(Ref|Mut)Except
.FilteredEntityRef
needs anAccess<ComponentId>
to determine what components it can access. There is one stored in the query state, but query items cannot borrow from the state, so it has toclone()
the access for each row. Cloning the access involves memory allocations and can be expensive.Solution
Let query items borrow from their query state.
Add an
's
lifetime toWorldQuery::Item
andWorldQuery::Fetch
, similar to the one inSystemParam
, and provide&'s Self::State
to the fetch so that it can borrow from the state.Unfortunately, there are a few cases where we currently return query items from temporary query states: the sorted iteration methods create a temporary state to query the sort keys, and the
EntityRef::components<Q>()
methods create a temporary state for their query.To allow these to continue to work with most
QueryData
implementations, introduce a new subtraitReleaseStateQueryData
that converts aQueryItem<'w, 's>
toQueryItem<'w, 'static>
, and is implemented for everything exceptFilteredEntity(Ref|Mut)
andEntity(Ref|Mut)Except
.#[derive(QueryData)]
should generateReleaseStateQueryData
implementations, but that is not currently implemented in this PR.This PR does not actually change the implementation of
FilteredEntity(Ref|Mut)
orEntity(Ref|Mut)Except
! That will be done as a follow-up PR so that the changes are easier to review. I have pushed the changes as chescock#5.Testing
I ran performance traces of many_foxes, both against main and against chescock#5, both including #15282. These changes do appear to make generalized animation a bit faster:
(Red is main, yellow is chescock#5)
Migration Guide
The
WorldQuery::Item
andWorldQuery::Fetch
associated types and theQueryItem
andROQueryItem
type aliases now have an additional lifetime parameter corresponding to the's
lifetime inQuery
. Manual implementations ofWorldQuery
will need to update the method signatures to include the new lifetimes. Other uses of the types will need to be updated to include a lifetime parameter, although it can usually be passed as'_
. In particular,ROQueryItem
is used when implementingRenderCommand
.Before:
After:
Methods on
QueryState
that take&mut self
may now result in conflicting borrows if the query items capture the lifetime of the mutable reference. This affectsget()
,get_many()
,iter()
, anditer_many()
. To fix the errors, first callQueryState::update_archetypes()
, and then replace the calls with the corresponding_manual
methods, which take&self
.Before:
After: