Skip to content

Commit 83a9e16

Browse files
committed
Replace many_for_each_mut with iter_many_mut. (#5402)
# Objective Replace `many_for_each_mut` with `iter_many_mut` using the same tricks to avoid aliased mutability that `iter_combinations_mut` uses. <sub>I tried rebasing the draft PR I made for this before and it died. F</sub> ## Why `many_for_each_mut` is worse for a few reasons: 1. The closure prevents the use of `continue`, `break`, and `return` behaves like a limited `continue`. 2. rustfmt will crumple it and double the indentation when the line gets too long. ```rust query.many_for_each_mut( &entity_list, |(mut transform, velocity, mut component_c)| { // Double trouble. }, ); ``` 3. It is more surprising to have `many_for_each_mut` as a mutable counterpart to `iter_many` than `iter_many_mut`. 4. It required a separate unsafe fn; more unsafe code to maintain. 5. The `iter_many_mut` API matches the existing `iter_combinations_mut` API. Co-authored-by: devil-ira <justthecooldude@gmail.com>
1 parent 418beff commit 83a9e16

11 files changed

+217
-169
lines changed

crates/bevy_ecs/src/query/iter.rs

Lines changed: 49 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,9 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> Iterator for QueryIter<'w, 's, Q, F>
7272
// This is correct as [`QueryIter`] always returns `None` once exhausted.
7373
impl<'w, 's, Q: WorldQuery, F: WorldQuery> FusedIterator for QueryIter<'w, 's, Q, F> {}
7474

75-
/// An [`Iterator`] over query results of a [`Query`](crate::system::Query).
75+
/// An [`Iterator`] over [`Query`](crate::system::Query) results of a list of [`Entity`]s.
7676
///
77-
/// This struct is created by the [`Query::iter_many`](crate::system::Query::iter_many) method.
77+
/// This struct is created by the [`Query::iter_many`](crate::system::Query::iter_many) and [`Query::iter_many_mut`](crate::system::Query::iter_many_mut) methods.
7878
pub struct QueryManyIter<'w, 's, Q: WorldQuery, F: WorldQuery, I: Iterator>
7979
where
8080
I::Item: Borrow<Entity>,
@@ -126,16 +126,15 @@ where
126126
entity_iter: entity_list.into_iter(),
127127
}
128128
}
129-
}
130-
131-
impl<'w, 's, Q: WorldQuery, F: WorldQuery, I: Iterator> Iterator for QueryManyIter<'w, 's, Q, F, I>
132-
where
133-
I::Item: Borrow<Entity>,
134-
{
135-
type Item = QueryItem<'w, Q>;
136129

130+
/// Safety:
131+
/// The lifetime here is not restrictive enough for Fetch with &mut access,
132+
/// as calling `fetch_next_aliased_unchecked` multiple times can produce multiple
133+
/// references to the same component, leading to unique reference aliasing.
134+
///
135+
/// It is always safe for shared access.
137136
#[inline(always)]
138-
fn next(&mut self) -> Option<Self::Item> {
137+
unsafe fn fetch_next_aliased_unchecked(&mut self) -> Option<QueryItem<'w, Q>> {
139138
for entity in self.entity_iter.by_ref() {
140139
let location = match self.entities.get(*entity.borrow()) {
141140
Some(location) => location,
@@ -154,32 +153,61 @@ where
154153

155154
// SAFETY: `archetype` is from the world that `fetch/filter` were created for,
156155
// `fetch_state`/`filter_state` are the states that `fetch/filter` were initialized with
157-
unsafe {
158-
self.fetch
159-
.set_archetype(&self.query_state.fetch_state, archetype, self.tables);
160-
}
156+
self.fetch
157+
.set_archetype(&self.query_state.fetch_state, archetype, self.tables);
158+
161159
// SAFETY: `table` is from the world that `fetch/filter` were created for,
162160
// `fetch_state`/`filter_state` are the states that `fetch/filter` were initialized with
163-
unsafe {
164-
self.filter
165-
.set_archetype(&self.query_state.filter_state, archetype, self.tables);
166-
}
161+
self.filter
162+
.set_archetype(&self.query_state.filter_state, archetype, self.tables);
163+
167164
// SAFETY: set_archetype was called prior.
168165
// `location.index` is an archetype index row in range of the current archetype, because if it was not, the match above would have `continue`d
169-
if unsafe { self.filter.archetype_filter_fetch(location.index) } {
166+
if self.filter.archetype_filter_fetch(location.index) {
170167
// SAFETY: set_archetype was called prior, `location.index` is an archetype index in range of the current archetype
171-
return Some(unsafe { self.fetch.archetype_fetch(location.index) });
168+
return Some(self.fetch.archetype_fetch(location.index));
172169
}
173170
}
174171
None
175172
}
176173

174+
/// Get next result from the query
175+
#[inline(always)]
176+
pub fn fetch_next(&mut self) -> Option<QueryItem<'_, Q>> {
177+
// SAFETY: we are limiting the returned reference to self,
178+
// making sure this method cannot be called multiple times without getting rid
179+
// of any previously returned unique references first, thus preventing aliasing.
180+
unsafe { self.fetch_next_aliased_unchecked().map(Q::shrink) }
181+
}
182+
}
183+
184+
impl<'w, 's, Q: ReadOnlyWorldQuery, F: ReadOnlyWorldQuery, I: Iterator> Iterator
185+
for QueryManyIter<'w, 's, Q, F, I>
186+
where
187+
I::Item: Borrow<Entity>,
188+
{
189+
type Item = QueryItem<'w, Q>;
190+
191+
#[inline(always)]
192+
fn next(&mut self) -> Option<Self::Item> {
193+
// SAFETY: It is safe to alias for ReadOnlyWorldQuery.
194+
unsafe { self.fetch_next_aliased_unchecked() }
195+
}
196+
177197
fn size_hint(&self) -> (usize, Option<usize>) {
178198
let (_, max_size) = self.entity_iter.size_hint();
179199
(0, max_size)
180200
}
181201
}
182202

203+
// This is correct as [`QueryManyIter`] always returns `None` once exhausted.
204+
impl<'w, 's, Q: ReadOnlyWorldQuery, F: ReadOnlyWorldQuery, I: Iterator> FusedIterator
205+
for QueryManyIter<'w, 's, Q, F, I>
206+
where
207+
I::Item: Borrow<Entity>,
208+
{
209+
}
210+
183211
pub struct QueryCombinationIter<'w, 's, Q: WorldQuery, F: WorldQuery, const K: usize> {
184212
tables: &'w Tables,
185213
archetypes: &'w Archetypes,
@@ -476,7 +504,7 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> QueryIterationCursor<'w, 's, Q, F> {
476504
}
477505

478506
// NOTE: If you are changing query iteration code, remember to update the following places, where relevant:
479-
// QueryIterationCursor, QueryState::for_each_unchecked_manual, QueryState::par_for_each_unchecked_manual
507+
// QueryIter, QueryIterationCursor, QueryManyIter, QueryCombinationIter, QueryState::for_each_unchecked_manual, QueryState::par_for_each_unchecked_manual
480508
/// # Safety
481509
/// `tables` and `archetypes` must belong to the same world that the [`QueryIterationCursor`]
482510
/// was initialized for.

crates/bevy_ecs/src/query/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -633,9 +633,10 @@ count(): {count}"#
633633
}
634634
{
635635
fn system(has_a: Query<Entity, With<A>>, mut b_query: Query<&mut B>) {
636-
b_query.many_for_each_mut(&has_a, |mut b| {
636+
let mut iter = b_query.iter_many_mut(&has_a);
637+
while let Some(mut b) = iter.fetch_next() {
637638
b.0 = 1;
638-
});
639+
}
639640
}
640641
let mut system = IntoSystem::into_system(system);
641642
system.initialize(&mut world);

crates/bevy_ecs/src/query/state.rs

Lines changed: 26 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -602,7 +602,7 @@ impl<Q: WorldQuery, F: WorldQuery> QueryState<Q, F> {
602602
/// Returns an [`Iterator`] over the query results of a list of [`Entity`]'s.
603603
///
604604
/// This can only return immutable data (mutable data will be cast to an immutable form).
605-
/// See [`Self::many_for_each_mut`] for queries that contain at least one mutable component.
605+
/// See [`Self::iter_many_mut`] for queries that contain at least one mutable component.
606606
///
607607
#[inline]
608608
pub fn iter_many<'w, 's, EntityList: IntoIterator>(
@@ -613,9 +613,9 @@ impl<Q: WorldQuery, F: WorldQuery> QueryState<Q, F> {
613613
where
614614
EntityList::Item: Borrow<Entity>,
615615
{
616+
self.update_archetypes(world);
616617
// SAFETY: query is read only
617618
unsafe {
618-
self.update_archetypes(world);
619619
self.as_readonly().iter_many_unchecked_manual(
620620
entities,
621621
world,
@@ -625,6 +625,28 @@ impl<Q: WorldQuery, F: WorldQuery> QueryState<Q, F> {
625625
}
626626
}
627627

628+
/// Returns an iterator over the query results of a list of [`Entity`]'s.
629+
#[inline]
630+
pub fn iter_many_mut<'w, 's, EntityList: IntoIterator>(
631+
&'s mut self,
632+
world: &'w mut World,
633+
entities: EntityList,
634+
) -> QueryManyIter<'w, 's, Q, F, EntityList::IntoIter>
635+
where
636+
EntityList::Item: Borrow<Entity>,
637+
{
638+
self.update_archetypes(world);
639+
// SAFETY: Query has unique world access.
640+
unsafe {
641+
self.iter_many_unchecked_manual(
642+
entities,
643+
world,
644+
world.last_change_tick(),
645+
world.read_change_tick(),
646+
)
647+
}
648+
}
649+
628650
/// Returns an [`Iterator`] over the query results for the given [`World`].
629651
///
630652
/// # Safety
@@ -868,29 +890,6 @@ impl<Q: WorldQuery, F: WorldQuery> QueryState<Q, F> {
868890
);
869891
}
870892

871-
/// Runs `func` on each query result where the entities match.
872-
#[inline]
873-
pub fn many_for_each_mut<EntityList: IntoIterator>(
874-
&mut self,
875-
world: &mut World,
876-
entities: EntityList,
877-
func: impl FnMut(QueryItem<'_, Q>),
878-
) where
879-
EntityList::Item: Borrow<Entity>,
880-
{
881-
// SAFETY: query has unique world access
882-
unsafe {
883-
self.update_archetypes(world);
884-
self.many_for_each_unchecked_manual(
885-
world,
886-
entities,
887-
func,
888-
world.last_change_tick(),
889-
world.read_change_tick(),
890-
);
891-
};
892-
}
893-
894893
/// Runs `func` on each query result for the given [`World`], where the last change and
895894
/// the current change tick are given. This is faster than the equivalent
896895
/// iter() method, but cannot be chained like a normal [`Iterator`].
@@ -909,7 +908,7 @@ impl<Q: WorldQuery, F: WorldQuery> QueryState<Q, F> {
909908
change_tick: u32,
910909
) {
911910
// NOTE: If you are changing query iteration code, remember to update the following places, where relevant:
912-
// QueryIter, QueryIterationCursor, QueryState::for_each_unchecked_manual, QueryState::many_for_each_unchecked_manual, QueryState::par_for_each_unchecked_manual
911+
// QueryIter, QueryIterationCursor, QueryManyIter, QueryCombinationIter, QueryState::for_each_unchecked_manual, QueryState::par_for_each_unchecked_manual
913912
let mut fetch =
914913
<QueryFetch<Q> as Fetch>::init(world, &self.fetch_state, last_change_tick, change_tick);
915914
let mut filter = <QueryFetch<F> as Fetch>::init(
@@ -978,7 +977,7 @@ impl<Q: WorldQuery, F: WorldQuery> QueryState<Q, F> {
978977
change_tick: u32,
979978
) {
980979
// NOTE: If you are changing query iteration code, remember to update the following places, where relevant:
981-
// QueryIter, QueryIterationCursor, QueryState::for_each_unchecked_manual, QueryState::many_for_each_unchecked_manual, QueryState::par_for_each_unchecked_manual
980+
// QueryIter, QueryIterationCursor, QueryManyIter, QueryCombinationIter, QueryState::for_each_unchecked_manual, QueryState::par_for_each_unchecked_manual
982981
ComputeTaskPool::get().scope(|scope| {
983982
if <QueryFetch<'static, Q>>::IS_DENSE && <QueryFetch<'static, F>>::IS_DENSE {
984983
let tables = &world.storages().tables;
@@ -1078,62 +1077,6 @@ impl<Q: WorldQuery, F: WorldQuery> QueryState<Q, F> {
10781077
});
10791078
}
10801079

1081-
/// Runs `func` on each query result for the given [`World`] and list of [`Entity`]'s, where the last change and
1082-
/// the current change tick are given. This is faster than the equivalent
1083-
/// iter() method, but cannot be chained like a normal [`Iterator`].
1084-
///
1085-
/// # Safety
1086-
///
1087-
/// This does not check for mutable query correctness. To be safe, make sure mutable queries
1088-
/// have unique access to the components they query.
1089-
/// This does not validate that `world.id()` matches `self.world_id`. Calling this on a `world`
1090-
/// with a mismatched [`WorldId`] is unsound.
1091-
pub(crate) unsafe fn many_for_each_unchecked_manual<EntityList: IntoIterator>(
1092-
&self,
1093-
world: &World,
1094-
entity_list: EntityList,
1095-
mut func: impl FnMut(QueryItem<'_, Q>),
1096-
last_change_tick: u32,
1097-
change_tick: u32,
1098-
) where
1099-
EntityList::Item: Borrow<Entity>,
1100-
{
1101-
// NOTE: If you are changing query iteration code, remember to update the following places, where relevant:
1102-
// QueryIter, QueryIterationCursor, QueryState::for_each_unchecked_manual, QueryState::many_for_each_unchecked_manual, QueryState::par_for_each_unchecked_manual
1103-
let mut fetch =
1104-
<QueryFetch<Q> as Fetch>::init(world, &self.fetch_state, last_change_tick, change_tick);
1105-
let mut filter = <QueryFetch<F> as Fetch>::init(
1106-
world,
1107-
&self.filter_state,
1108-
last_change_tick,
1109-
change_tick,
1110-
);
1111-
1112-
let tables = &world.storages.tables;
1113-
1114-
for entity in entity_list {
1115-
let location = match world.entities.get(*entity.borrow()) {
1116-
Some(location) => location,
1117-
None => continue,
1118-
};
1119-
1120-
if !self
1121-
.matched_archetypes
1122-
.contains(location.archetype_id.index())
1123-
{
1124-
continue;
1125-
}
1126-
1127-
let archetype = &world.archetypes[location.archetype_id];
1128-
1129-
fetch.set_archetype(&self.fetch_state, archetype, tables);
1130-
filter.set_archetype(&self.filter_state, archetype, tables);
1131-
if filter.archetype_filter_fetch(location.index) {
1132-
func(fetch.archetype_fetch(location.index));
1133-
}
1134-
}
1135-
}
1136-
11371080
/// Returns a single immutable query result when there is exactly one entity matching
11381081
/// the query.
11391082
///

0 commit comments

Comments
 (0)