Skip to content

Commit 6972e2b

Browse files
Deduplicate logic
1 parent fce42c7 commit 6972e2b

File tree

2 files changed

+88
-94
lines changed

2 files changed

+88
-94
lines changed

crates/bevy_ecs/src/query/state.rs

Lines changed: 72 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -198,27 +198,15 @@ where
198198
) -> Result<[<Q::ReadOnlyFetch as Fetch<'w, 's>>::Item; N], QueryEntityError> {
199199
self.update_archetypes(world);
200200

201-
let array_of_results = entities.map(|entity| {
202-
// SAFETY: query is read only
203-
unsafe {
204-
self.get_unchecked_manual::<Q::ReadOnlyFetch>(
205-
world,
206-
entity,
207-
world.last_change_tick(),
208-
world.read_change_tick(),
209-
)
210-
}
211-
});
212-
213-
// If any of the entities were not present, return an error
214-
for result in &array_of_results {
215-
if let Err(QueryEntityError::NoSuchEntity(entity)) = result {
216-
return Err(QueryEntityError::NoSuchEntity(*entity));
217-
}
201+
// SAFE: query is read-only
202+
unsafe {
203+
self.get_multiple_unchecked_manual::<Q::ReadOnlyFetch>(
204+
world,
205+
entities,
206+
world.last_change_tick(),
207+
world.change_tick(),
208+
)
218209
}
219-
220-
// Since we have verified that all entities are present, we can safely unwrap
221-
Ok(array_of_results.map(|result| result.unwrap()))
222210
}
223211

224212
/// Gets the query result for the given [`World`] and [`Entity`].
@@ -288,35 +276,17 @@ where
288276
) -> Result<[<Q::Fetch as Fetch<'w, 's>>::Item; N], QueryEntityError> {
289277
self.update_archetypes(world);
290278

291-
for i in 0..N {
292-
for j in 0..i {
293-
if entities[i] == entities[j] {
294-
return Err(QueryEntityError::AliasedMutability(entities[i]));
295-
}
296-
}
297-
}
279+
verify_entities_unique(entities)?;
298280

299-
let array_of_results = entities.map(|entity| {
300-
// SAFETY: entity list is checked for uniqueness, and we require exclusive access to the World
301-
unsafe {
302-
self.get_unchecked_manual::<Q::Fetch>(
303-
world,
304-
entity,
305-
world.last_change_tick(),
306-
world.read_change_tick(),
307-
)
308-
}
309-
});
310-
311-
// If any of the entities were not present, return an error
312-
for result in &array_of_results {
313-
if let Err(QueryEntityError::NoSuchEntity(entity)) = result {
314-
return Err(QueryEntityError::NoSuchEntity(*entity));
315-
}
281+
// SAFE: method requires exclusive world access, and entities are checked for uniqueness
282+
unsafe {
283+
self.get_multiple_unchecked_manual::<Q::Fetch>(
284+
world,
285+
entities,
286+
world.last_change_tick(),
287+
world.change_tick(),
288+
)
316289
}
317-
318-
// Since we have verified that all entities are present, we can safely unwrap
319-
Ok(array_of_results.map(|result| result.unwrap()))
320290
}
321291

322292
#[inline]
@@ -396,6 +366,47 @@ where
396366
}
397367
}
398368

369+
/// Gets the query results for the given [`World`] and array of [`Entity`], where the last change and
370+
/// the current change tick are given.
371+
///
372+
/// # Safety
373+
/// This does not check for mutable query correctness. To be safe, make sure mutable queries
374+
/// have unique access to the components they query.
375+
///
376+
/// If you are calling this method with a mutable query, you must verify that `entities` is free of duplicates:
377+
/// use [`verify_entities_unique`] to do so efficiently for small `N`.
378+
pub(crate) unsafe fn get_multiple_unchecked_manual<
379+
's,
380+
'w,
381+
QF: Fetch<'w, 's, State = Q::State>,
382+
const N: usize,
383+
>(
384+
&'s self,
385+
world: &'w World,
386+
entities: [Entity; N],
387+
last_change_tick: u32,
388+
change_tick: u32,
389+
) -> Result<[<QF as Fetch>::Item; N], QueryEntityError> {
390+
let array_of_results = entities.map(|entity| {
391+
self.get_unchecked_manual::<QF>(
392+
world,
393+
entity,
394+
world.last_change_tick(),
395+
world.read_change_tick(),
396+
)
397+
});
398+
399+
// If any of the entities were not present, return an error
400+
for result in &array_of_results {
401+
if let Err(QueryEntityError::NoSuchEntity(entity)) = result {
402+
return Err(QueryEntityError::NoSuchEntity(*entity));
403+
}
404+
}
405+
406+
// Since we have verified that all entities are present, we can safely unwrap
407+
Ok(array_of_results.map(|result| result.unwrap()))
408+
}
409+
399410
/// Returns an [`Iterator`] over the query results for the given [`World`].
400411
///
401412
/// This can only be called for read-only queries, see [`Self::iter_mut`] for write-queries.
@@ -890,3 +901,17 @@ pub enum QueryEntityError {
890901
#[error("The entity was requested mutably more than once.")]
891902
AliasedMutability(Entity),
892903
}
904+
905+
#[inline]
906+
pub(crate) fn verify_entities_unique<const N: usize>(
907+
entities: [Entity; N],
908+
) -> Result<(), QueryEntityError> {
909+
for i in 0..N {
910+
for j in 0..i {
911+
if entities[i] == entities[j] {
912+
return Err(QueryEntityError::AliasedMutability(entities[i]));
913+
}
914+
}
915+
}
916+
Ok(())
917+
}

crates/bevy_ecs/src/system/query.rs

Lines changed: 16 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ use crate::{
22
component::Component,
33
entity::Entity,
44
query::{
5-
Fetch, FilterFetch, NopFetch, QueryCombinationIter, QueryEntityError, QueryIter,
6-
QueryState, ReadOnlyFetch, WorldQuery,
5+
verify_entities_unique, Fetch, FilterFetch, NopFetch, QueryCombinationIter,
6+
QueryEntityError, QueryIter, QueryState, ReadOnlyFetch, WorldQuery,
77
},
88
world::{Mut, World},
99
};
@@ -631,27 +631,15 @@ where
631631
&self,
632632
entities: [Entity; N],
633633
) -> Result<[<Q::ReadOnlyFetch as Fetch<'_, 's>>::Item; N], QueryEntityError> {
634-
let array_of_results = entities.map(|entity| {
635-
// SAFETY: query is read only
636-
unsafe {
637-
self.state.get_unchecked_manual::<Q::ReadOnlyFetch>(
638-
self.world,
639-
entity,
640-
self.last_change_tick,
641-
self.change_tick,
642-
)
643-
}
644-
});
645-
646-
// If any of the entities were not present, return an error
647-
for result in &array_of_results {
648-
if let Err(QueryEntityError::NoSuchEntity(entity)) = result {
649-
return Err(QueryEntityError::NoSuchEntity(*entity));
650-
}
634+
// SAFE: Query is read-only
635+
unsafe {
636+
self.state.get_multiple_unchecked_manual::<Q::Fetch>(
637+
self.world,
638+
entities,
639+
self.last_change_tick,
640+
self.change_tick,
641+
)
651642
}
652-
653-
// Since we have verified that all entities are present, we can safely unwrap
654-
Ok(array_of_results.map(|result| result.unwrap()))
655643
}
656644

657645
/// Returns the read-only query items for the provided array of [`Entity`]
@@ -749,37 +737,18 @@ where
749737
&mut self,
750738
entities: [Entity; N],
751739
) -> Result<[<Q::Fetch as Fetch<'_, 's>>::Item; N], QueryEntityError> {
752-
for i in 0..N {
753-
for j in 0..i {
754-
if entities[i] == entities[j] {
755-
return Err(QueryEntityError::AliasedMutability(entities[i]));
756-
}
757-
}
758-
}
740+
verify_entities_unique(entities)?;
759741

760-
let array_of_results = entities.map(|entity| {
761-
// SAFETY: Entities are checked for uniqueness above,
762-
// the scheduler ensure that we do not have conflicting world access,
763-
// and we require &mut self to avoid any other simultaneous operations on this Query
764-
unsafe {
765-
self.state.get_unchecked_manual::<Q::Fetch>(
742+
// SAFE: scheduler ensures safe Query world access, and entities are checked for uniqueness
743+
unsafe {
744+
self.state
745+
.get_multiple_unchecked_manual::<<Q::Fetch>::Item>(
766746
self.world,
767-
entity,
747+
entities,
768748
self.last_change_tick,
769749
self.change_tick,
770750
)
771-
}
772-
});
773-
774-
// If any of the entities were not present, return an error
775-
for result in &array_of_results {
776-
if let Err(QueryEntityError::NoSuchEntity(entity)) = result {
777-
return Err(QueryEntityError::NoSuchEntity(*entity));
778-
}
779751
}
780-
781-
// Since we have verified that all entities are present, we can safely unwrap
782-
Ok(array_of_results.map(|result| result.unwrap()))
783752
}
784753

785754
/// Returns the query items for the provided array of [`Entity`]

0 commit comments

Comments
 (0)