Skip to content

Commit b5f79b1

Browse files
committed
Add iter_join_map(_mut).
1 parent 4d7f114 commit b5f79b1

File tree

3 files changed

+312
-4
lines changed

3 files changed

+312
-4
lines changed

crates/bevy_ecs/src/query/iter.rs

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,155 @@ where
214214
{
215215
}
216216

217+
/// An [`Iterator`] over [`Query`](crate::system::Query) results a list mapped to [`Entity`]'s and the list items.
218+
///
219+
/// This struct is created by the [`Query::iter_join_map`](crate::system::Query::iter_join_map) and [`Query::iter_join_map_mut`](crate::system::Query::iter_join_map_mut) methods.
220+
pub struct QueryJoinMapIter<'w, 's, Q: WorldQuery, F: WorldQuery, I: Iterator, MapFn>
221+
where
222+
MapFn: FnMut(&I::Item) -> Entity,
223+
{
224+
list: I,
225+
map_f: MapFn,
226+
entities: &'w Entities,
227+
tables: &'w Tables,
228+
archetypes: &'w Archetypes,
229+
fetch: QueryFetch<'w, Q>,
230+
filter: QueryFetch<'w, F>,
231+
query_state: &'s QueryState<Q, F>,
232+
}
233+
234+
impl<'w, 's, Q: WorldQuery, F: WorldQuery, I: Iterator, MapFn>
235+
QueryJoinMapIter<'w, 's, Q, F, I, MapFn>
236+
where
237+
MapFn: FnMut(&I::Item) -> Entity,
238+
{
239+
/// # Safety
240+
/// This does not check for mutable query correctness. To be safe, make sure mutable queries
241+
/// have unique access to the components they query.
242+
/// This does not validate that `world.id()` matches `query_state.world_id`. Calling this on a `world`
243+
/// with a mismatched [`WorldId`](crate::world::WorldId) is unsound.
244+
pub(crate) unsafe fn new<II: IntoIterator<IntoIter = I>>(
245+
world: &'w World,
246+
query_state: &'s QueryState<Q, F>,
247+
last_change_tick: u32,
248+
change_tick: u32,
249+
entity_map: II,
250+
map_f: MapFn,
251+
) -> QueryJoinMapIter<'w, 's, Q, F, I, MapFn> {
252+
let fetch = Q::init_fetch(
253+
world,
254+
&query_state.fetch_state,
255+
last_change_tick,
256+
change_tick,
257+
);
258+
let filter = F::init_fetch(
259+
world,
260+
&query_state.filter_state,
261+
last_change_tick,
262+
change_tick,
263+
);
264+
QueryJoinMapIter {
265+
query_state,
266+
entities: &world.entities,
267+
archetypes: &world.archetypes,
268+
tables: &world.storages.tables,
269+
fetch,
270+
filter,
271+
list: entity_map.into_iter(),
272+
map_f,
273+
}
274+
}
275+
276+
/// SAFETY:
277+
/// The lifetime here is not restrictive enough for Fetch with &mut access,
278+
/// as calling `fetch_next_aliased_unchecked` multiple times can produce multiple
279+
/// references to the same component, leading to unique reference aliasing.
280+
///
281+
/// It is always safe for immutable borrows.
282+
#[inline(always)]
283+
unsafe fn fetch_next_aliased_unchecked(&mut self) -> Option<(QueryItem<'w, Q>, I::Item)> {
284+
for item in self.list.by_ref() {
285+
let location = match self.entities.get((self.map_f)(&item)) {
286+
Some(location) => location,
287+
None => continue,
288+
};
289+
290+
if !self
291+
.query_state
292+
.matched_archetypes
293+
.contains(location.archetype_id.index())
294+
{
295+
continue;
296+
}
297+
298+
let archetype = &self.archetypes[location.archetype_id];
299+
300+
// SAFETY: `archetype` is from the world that `fetch/filter` were created for,
301+
// `fetch_state`/`filter_state` are the states that `fetch/filter` were initialized with
302+
Q::set_archetype(
303+
&mut self.fetch,
304+
&self.query_state.fetch_state,
305+
archetype,
306+
self.tables,
307+
);
308+
// SAFETY: `table` is from the world that `fetch/filter` were created for,
309+
// `fetch_state`/`filter_state` are the states that `fetch/filter` were initialized with
310+
F::set_archetype(
311+
&mut self.filter,
312+
&self.query_state.filter_state,
313+
archetype,
314+
self.tables,
315+
);
316+
// SAFETY: set_archetype was called prior.
317+
// `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
318+
if F::archetype_filter_fetch(&mut self.filter, location.index) {
319+
// SAFETY: set_archetype was called prior, `location.index` is an archetype index in range of the current archetype
320+
return Some((Q::archetype_fetch(&mut self.fetch, location.index), item));
321+
}
322+
}
323+
None
324+
}
325+
326+
/// Get the next item from the iterator.
327+
#[inline(always)]
328+
pub fn fetch_next(&mut self) -> Option<(QueryItem<'_, Q>, I::Item)> {
329+
// safety: we are limiting the returned reference to self,
330+
// making sure this method cannot be called multiple times without getting rid
331+
// of any previously returned unique references first, thus preventing aliasing.
332+
unsafe {
333+
self.fetch_next_aliased_unchecked()
334+
.map(|(q_item, item)| (Q::shrink(q_item), item))
335+
}
336+
}
337+
}
338+
339+
impl<'w, 's, Q: ReadOnlyWorldQuery, F: ReadOnlyWorldQuery, I: Iterator, MapFn> Iterator
340+
for QueryJoinMapIter<'w, 's, Q, F, I, MapFn>
341+
where
342+
MapFn: FnMut(&I::Item) -> Entity,
343+
{
344+
type Item = (QueryItem<'w, Q>, I::Item);
345+
346+
#[inline(always)]
347+
fn next(&mut self) -> Option<Self::Item> {
348+
// SAFETY: it is safe to alias for ReadOnlyWorldQuery
349+
unsafe { self.fetch_next_aliased_unchecked() }
350+
}
351+
352+
fn size_hint(&self) -> (usize, Option<usize>) {
353+
let (_, max_size) = self.list.size_hint();
354+
(0, max_size)
355+
}
356+
}
357+
358+
// This is correct as QueryJoinMapIter always returns `None` once exhausted.
359+
impl<'w, 's, Q: ReadOnlyWorldQuery, F: ReadOnlyWorldQuery, I: Iterator, MapFn> FusedIterator
360+
for QueryJoinMapIter<'w, 's, Q, F, I, MapFn>
361+
where
362+
MapFn: FnMut(&I::Item) -> Entity,
363+
{
364+
}
365+
217366
/// An iterator over `K`-sized combinations of query items without repetition.
218367
///
219368
/// In this context, a combination is an unordered subset of `K` elements.

crates/bevy_ecs/src/query/state.rs

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ use crate::{
33
component::ComponentId,
44
entity::Entity,
55
prelude::FromWorld,
6-
query::{Access, FilteredAccess, QueryCombinationIter, QueryIter, WorldQuery},
6+
query::{
7+
Access, FilteredAccess, QueryCombinationIter, QueryIter, QueryJoinMapIter, WorldQuery,
8+
},
79
storage::TableId,
810
world::{World, WorldId},
911
};
@@ -642,6 +644,50 @@ impl<Q: WorldQuery, F: WorldQuery> QueryState<Q, F> {
642644
}
643645
}
644646

647+
/// Returns an [`Iterator`] over the inner join of the results of a query and list of items mapped to [`Entity`]'s.
648+
///
649+
/// This can only be called for read-only queries, see [`Self::iter_list_mut`] for write-queries.
650+
#[inline]
651+
pub fn iter_join_map<'w, 's, I: IntoIterator, MapFn: FnMut(&I::Item) -> Entity>(
652+
&'s mut self,
653+
world: &'w World,
654+
list: I,
655+
map_f: MapFn,
656+
) -> QueryJoinMapIter<'w, 's, Q::ReadOnly, F::ReadOnly, I::IntoIter, MapFn> {
657+
self.update_archetypes(world);
658+
// SAFETY: Query has unique world access.
659+
unsafe {
660+
self.as_readonly().iter_join_map_unchecked_manual(
661+
world,
662+
world.last_change_tick(),
663+
world.read_change_tick(),
664+
list,
665+
map_f,
666+
)
667+
}
668+
}
669+
670+
/// Returns an iterator over the inner join of the results of a query and list of items mapped to [`Entity`]'s.
671+
#[inline]
672+
pub fn iter_join_map_mut<'w, 's, I: IntoIterator, MapFn: FnMut(&I::Item) -> Entity>(
673+
&'s mut self,
674+
world: &'w mut World,
675+
list: I,
676+
map_f: MapFn,
677+
) -> QueryJoinMapIter<'w, 's, Q, F, I::IntoIter, MapFn> {
678+
self.update_archetypes(world);
679+
// SAFETY: Query has unique world access.
680+
unsafe {
681+
self.iter_join_map_unchecked_manual(
682+
world,
683+
world.last_change_tick(),
684+
world.read_change_tick(),
685+
list,
686+
map_f,
687+
)
688+
}
689+
}
690+
645691
/// Returns an [`Iterator`] over the query results for the given [`World`].
646692
///
647693
/// # Safety
@@ -704,7 +750,6 @@ impl<Q: WorldQuery, F: WorldQuery> QueryState<Q, F> {
704750
///
705751
/// This does not check for mutable query correctness. To be safe, make sure mutable queries
706752
/// have unique access to the components they query.
707-
/// This does not check for entity uniqueness
708753
/// This does not validate that `world.id()` matches `self.world_id`. Calling this on a `world`
709754
/// with a mismatched [`WorldId`] is unsound.
710755
#[inline]
@@ -721,6 +766,29 @@ impl<Q: WorldQuery, F: WorldQuery> QueryState<Q, F> {
721766
QueryManyIter::new(world, self, entities, last_change_tick, change_tick)
722767
}
723768

769+
/// Returns an [`Iterator`] over each item in an inner join on [`Entity`] between the query and a list of items which are mapped to [`Entity`]'s.
770+
///
771+
/// # Safety
772+
///
773+
/// This does not check for mutable query correctness. To be safe, make sure mutable queries
774+
/// have unique access to the components they query.
775+
/// This does not validate that `world.id()` matches `self.world_id`. Calling this on a `world`
776+
/// with a mismatched [`WorldId`] is unsound.
777+
#[inline]
778+
pub(crate) unsafe fn iter_join_map_unchecked_manual<'w, 's, I: IntoIterator, MapFn>(
779+
&'s self,
780+
world: &'w World,
781+
last_change_tick: u32,
782+
change_tick: u32,
783+
list: I,
784+
map_f: MapFn,
785+
) -> QueryJoinMapIter<'w, 's, Q, F, I::IntoIter, MapFn>
786+
where
787+
MapFn: FnMut(&I::Item) -> Entity,
788+
{
789+
QueryJoinMapIter::new(world, self, last_change_tick, change_tick, list, map_f)
790+
}
791+
724792
/// Returns an [`Iterator`] over all possible combinations of `K` query results for the
725793
/// given [`World`] without repetition.
726794
/// This can only be called for read-only queries.

crates/bevy_ecs/src/system/query.rs

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ use crate::{
22
component::Component,
33
entity::Entity,
44
query::{
5-
QueryCombinationIter, QueryEntityError, QueryItem, QueryIter, QueryManyIter,
6-
QuerySingleError, QueryState, ROQueryItem, ReadOnlyWorldQuery, WorldQuery,
5+
QueryCombinationIter, QueryEntityError, QueryItem, QueryIter, QueryJoinMapIter,
6+
QueryManyIter, QuerySingleError, QueryState, ROQueryItem, ReadOnlyWorldQuery, WorldQuery,
77
},
88
world::{Mut, World},
99
};
@@ -520,6 +520,97 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> Query<'w, 's, Q, F> {
520520
}
521521
}
522522

523+
/// Returns an [`Iterator`] over the inner join of the results of a [`Query`] and list of items mapped to [`Entity`]'s.
524+
///
525+
/// # Example
526+
/// ```
527+
/// # use bevy_ecs::prelude::*;
528+
/// # #[derive(Component)]
529+
/// # struct Health {
530+
/// # value: f32
531+
/// # }
532+
/// #[derive(Component)]
533+
/// struct DamageEvent {
534+
/// target: Entity,
535+
/// damage: f32,
536+
/// }
537+
///
538+
/// fn system(
539+
/// mut damage_events: EventReader<DamageEvent>,
540+
/// health_query: Query<&Health>,
541+
/// ) {
542+
/// for (health, event) in
543+
/// health_query.iter_join_map(damage_events.iter(), |event| event.target)
544+
/// {
545+
/// println!("Entity has {} health and will take {} damage!", health.value, event.damage);
546+
/// }
547+
/// }
548+
/// # bevy_ecs::system::assert_is_system(system);
549+
/// ```
550+
#[inline]
551+
pub fn iter_join_map<I: IntoIterator, MapFn: FnMut(&I::Item) -> Entity>(
552+
&self,
553+
list: I,
554+
map_f: MapFn,
555+
) -> QueryJoinMapIter<'w, 's, Q::ReadOnly, F::ReadOnly, I::IntoIter, MapFn> {
556+
// SAFETY: system runs without conflicts with other systems.
557+
// same-system queries have runtime borrow checks when they conflict
558+
unsafe {
559+
self.state.as_readonly().iter_join_map_unchecked_manual(
560+
self.world,
561+
self.last_change_tick,
562+
self.change_tick,
563+
list,
564+
map_f,
565+
)
566+
}
567+
}
568+
569+
/// Returns an iterator over the inner join of the results of a [`Query`] and list of items mapped to [`Entity`]'s.
570+
///
571+
/// # Example
572+
/// ```
573+
/// # use bevy_ecs::prelude::*;
574+
/// # #[derive(Component)]
575+
/// # struct Health {
576+
/// # value: f32
577+
/// # }
578+
/// #[derive(Component)]
579+
/// struct DamageEvent {
580+
/// target: Entity,
581+
/// damage: f32,
582+
/// }
583+
///
584+
/// fn system(
585+
/// mut damage_events: EventReader<DamageEvent>,
586+
/// mut health_query: Query<&mut Health>,
587+
/// ) {
588+
/// let mut join = health_query.iter_join_map_mut(damage_events.iter(), |event| event.target);
589+
/// while let Some((mut health, event)) = join.fetch_next() {
590+
/// health.value -= event.damage;
591+
/// }
592+
/// }
593+
/// # bevy_ecs::system::assert_is_system(system);
594+
/// ```
595+
#[inline]
596+
pub fn iter_join_map_mut<I: IntoIterator, MapFn: FnMut(&I::Item) -> Entity>(
597+
&mut self,
598+
list: I,
599+
map_f: MapFn,
600+
) -> QueryJoinMapIter<'_, '_, Q, F, I::IntoIter, MapFn> {
601+
// SAFETY: system runs without conflicts with other systems.
602+
// same-system queries have runtime borrow checks when they conflict
603+
unsafe {
604+
self.state.iter_join_map_unchecked_manual(
605+
self.world,
606+
self.last_change_tick,
607+
self.change_tick,
608+
list,
609+
map_f,
610+
)
611+
}
612+
}
613+
523614
/// Returns an [`Iterator`] over the query results.
524615
///
525616
/// # Safety

0 commit comments

Comments
 (0)