-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
Accelerate Query::get #12925
base: main
Are you sure you want to change the base?
Accelerate Query::get #12925
Conversation
crates/bevy_ecs/src/system/query.rs
Outdated
} | ||
|
||
/// Return a [`QueryEntityGetter`] to get the query item for the [`Entity`]. | ||
/// this could be more efficient than [`Query::get`]. |
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 should call out why / when you might expect this to be faster.
crates/bevy_ecs/src/query/getter.rs
Outdated
|
||
use super::{DebugCheckedUnwrap, QueryData, QueryEntityError, QueryFilter, QueryState}; | ||
|
||
/// A getter used for fast fetch its Component with Specific [`Entity`] |
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 doc string needs an editing pass.
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.
Thanks for taking a swing at this! Haven't had the time for a full review yet, but a bit of a bikeshed on the name QueryEntityGetter
-> PointQuery
.
This should be done in a followup PR, but PointQuery
implementing SystemParam
would also be useful, though it may not be intuitive to use since immutable fetches require a mutable struct.
I did a benchmark on TBH, the greatest value of this PR lies in providing a more versatile method for retrieving items from many entities than iter_many. However, I'm not certain if this approach is worthy. |
Might want to test this against the visibility propagation system since that runs single-threaded and thus can needing fetch reinitialization as often as the transform propagate system. |
run |
.entities | ||
.get(entity) | ||
.ok_or(QueryEntityError::NoSuchEntity(entity))?; | ||
if !D::IS_DENSE || self.last_archetype_id != location.archetype_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.
This is unsound. Please check how the dense checks are done elsewhere. If D::IS_DENSE
and F::IS_DENSE
, this should use tables and table IDs instead of archetypes.
/// | ||
/// This is always guaranteed to run in `O(1)` time. | ||
#[inline] | ||
pub fn get(&mut self, entity: Entity) -> Result<D::Item<'w>, QueryEntityError> { |
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.
pub fn get(&mut self, entity: Entity) -> Result<D::Item<'w>, QueryEntityError> { | |
pub fn get(&mut self, entity: Entity) -> Result<D::Item<'_>, QueryEntityError> { |
For this to be sound, we cannot allow this borrow to live longer than the borrow on self
.
/// `entity` must on the same `World` that the `Query` was generated. | ||
/// `entity` must match the `Query` that generate this getter. | ||
#[inline] | ||
pub unsafe fn get_unchecked(&mut self, entity: Entity) -> D::Item<'w> { |
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.
pub unsafe fn get_unchecked(&mut self, entity: Entity) -> D::Item<'w> { | |
pub unsafe fn get_unchecked(&mut self, entity: Entity) -> D::Item<'_> { |
Co-authored-by: James Liu <contact@jamessliu.com>
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 couple things.
pub fn get(&mut self, entity: Entity) -> Result<D::Item<'_>, QueryEntityError> { | ||
// SAFETY: system runs without conflicts with other systems. | ||
// same-system queries have runtime borrow checks when they conflict | ||
unsafe { std::mem::transmute(self.get_unsafe(entity)) } |
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.
unsafe { std::mem::transmute(self.get_unsafe(entity)) } | |
let result = unsafe { self.get_unsafe(entity) }; | |
match result { | |
Ok(item) => Ok(D::shrink(item)), | |
Err(err) => Err(err), | |
} |
We can use WorldQuery::shrink
instead of a transmute.
} | ||
|
||
let item = D::fetch(&mut self.fetch, entity, location.table_row); | ||
std::mem::transmute(item) |
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.
std::mem::transmute(item) | |
D::shrink(item) |
We can use shrink here as well, instead of the transmute.
Objective
query::get
is commonly used to retrieve an item from a specific entity. However, its performance may not be optimal when dealing with a group of entities, particularly for wide queries. In fact, it may even be slower thanEntityHashmap
.Solution
Changelog
QueryEntityGetter
Query::getter(_mut)
QueryState::getter(_mut)
Performance
QueryEntityGetter::get
, 4x-10x faster withQueryEntityGetter::get_unchecked
.