From a2d6b13ab39a19bb9ee2813ecfbc8a8cd73a0500 Mon Sep 17 00:00:00 2001 From: james7132 Date: Sat, 26 Nov 2022 15:43:47 -0800 Subject: [PATCH 01/24] Move for_each implementation onto Iterator::fold --- crates/bevy_ecs/src/query/iter.rs | 138 ++++++++++++++++++----- crates/bevy_ecs/src/query/state.rs | 166 ++++------------------------ crates/bevy_ecs/src/system/query.rs | 23 ++-- examples/app/empty.rs | 15 ++- 4 files changed, 154 insertions(+), 188 deletions(-) diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index 0be5022085013..e4fac1eb4556c 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -1,11 +1,11 @@ use crate::{ - archetype::{ArchetypeEntity, ArchetypeId, Archetypes}, + archetype::{Archetype, ArchetypeEntity, ArchetypeId, Archetypes}, entity::{Entities, Entity}, prelude::World, query::{ArchetypeFilter, DebugCheckedUnwrap, QueryState, WorldQuery}, - storage::{TableId, Tables}, + storage::{Table, TableId, Tables}, }; -use std::{borrow::Borrow, iter::FusedIterator, marker::PhantomData, mem::MaybeUninit}; +use std::{borrow::Borrow, iter::FusedIterator, marker::PhantomData, mem::MaybeUninit, ops::Range}; use super::ReadOnlyWorldQuery; @@ -39,6 +39,72 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIter<'w, 's, Q, F> { cursor: QueryIterationCursor::init(world, query_state, last_change_tick, change_tick), } } + + #[inline] + pub(super) unsafe fn fold_table( + &mut self, + mut accum: B, + func: &mut Func, + table: &'w Table, + rows: Range, + ) -> B + where + Func: FnMut(B, Q::Item<'w>) -> B, + { + Q::set_table(&mut self.cursor.fetch, &self.query_state.fetch_state, table); + F::set_table( + &mut self.cursor.filter, + &self.query_state.filter_state, + table, + ); + + let entities = table.entities(); + for row in rows { + let entity = entities.get_unchecked(row); + if let Some(item) = self.cursor.fetch(*entity, row) { + accum = func(accum, item); + } + } + accum + } + + #[inline] + pub(super) unsafe fn fold_archetype( + &mut self, + mut accum: B, + func: &mut Func, + archetype: &'w Archetype, + indices: Range, + ) -> B + where + Func: FnMut(B, Q::Item<'w>) -> B, + { + let table = self.tables.get(archetype.table_id()).debug_checked_unwrap(); + Q::set_archetype( + &mut self.cursor.fetch, + &self.query_state.fetch_state, + archetype, + table, + ); + F::set_archetype( + &mut self.cursor.filter, + &self.query_state.filter_state, + archetype, + table, + ); + + let entities = archetype.entities(); + for index in indices { + let archetype_entity = entities.get_unchecked(index); + if let Some(item) = self + .cursor + .fetch(archetype_entity.entity, archetype_entity.table_row) + { + accum = func(accum, item); + } + } + accum + } } impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Iterator for QueryIter<'w, 's, Q, F> { @@ -61,6 +127,33 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Iterator for QueryIter<'w, 's let min_size = if archetype_query { max_size } else { 0 }; (min_size, Some(max_size)) } + + #[inline] + fn fold(mut self, init: B, mut func: Func) -> B + where + Func: FnMut(B, Self::Item) -> B, + { + let mut accum = init; + if Q::IS_DENSE && F::IS_DENSE { + for table_id in self.cursor.table_id_iter.clone() { + // SAFETY: Matched table IDs are guarenteed to still exist. + let table = unsafe { self.tables.get(*table_id).debug_checked_unwrap() }; + accum = + // SAFETY: The fetched table matches the query + unsafe { self.fold_table(accum, &mut func, table, 0..table.entity_count()) }; + } + } else { + for archetype_id in self.cursor.archetype_id_iter.clone() { + let archetype = + // SAFETY: Matched archetype IDs are guarenteed to still exist. + unsafe { self.archetypes.get(*archetype_id).debug_checked_unwrap() }; + accum = + // SAFETY: The fetched archetype and table matches the query + unsafe { self.fold_archetype(accum, &mut func, archetype, 0..archetype.len()) }; + } + } + accum + } } // This is correct as [`QueryIter`] always returns `None` once exhausted. @@ -606,26 +699,20 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's, // SAFETY: set_table was called prior. // `current_index` is a table row in range of the current table, because if it was not, then the if above would have been executed. let entity = self.table_entities.get_unchecked(self.current_index); - if !F::filter_fetch(&mut self.filter, *entity, self.current_index) { + if let Some(item) = self.fetch(*entity, self.current_index) { self.current_index += 1; - continue; + return Some(item); } - - // SAFETY: set_table was called prior. - // `current_index` is a table row in range of the current table, because if it was not, then the if above would have been executed. - let item = Q::fetch(&mut self.fetch, *entity, self.current_index); - self.current_index += 1; - return Some(item); } } else { loop { if self.current_index == self.current_len { let archetype_id = self.archetype_id_iter.next()?; let archetype = archetypes.get(*archetype_id).debug_checked_unwrap(); + let table = tables.get(archetype.table_id()).debug_checked_unwrap(); // SAFETY: `archetype` and `tables` are from the world that `fetch/filter` were created for, // `fetch_state`/`filter_state` are the states that `fetch/filter` were initialized with - let table = tables.get(archetype.table_id()).debug_checked_unwrap(); Q::set_archetype(&mut self.fetch, &query_state.fetch_state, archetype, table); F::set_archetype( &mut self.filter, @@ -642,25 +729,18 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's, // SAFETY: set_archetype was called prior. // `current_index` is an archetype index row in range of the current archetype, because if it was not, then the if above would have been executed. let archetype_entity = self.archetype_entities.get_unchecked(self.current_index); - if !F::filter_fetch( - &mut self.filter, - archetype_entity.entity, - archetype_entity.table_row, - ) { - self.current_index += 1; - continue; - } - - // SAFETY: set_archetype was called prior, `current_index` is an archetype index in range of the current archetype - // `current_index` is an archetype index row in range of the current archetype, because if it was not, then the if above would have been executed. - let item = Q::fetch( - &mut self.fetch, - archetype_entity.entity, - archetype_entity.table_row, - ); self.current_index += 1; - return Some(item); + if let Some(item) = self.fetch(archetype_entity.entity, archetype_entity.table_row) + { + return Some(item); + } } } } + + #[inline(always)] + unsafe fn fetch(&mut self, entity: Entity, table_row: usize) -> Option> { + F::filter_fetch(&mut self.filter, entity, table_row) + .then(|| Q::fetch(&mut self.fetch, entity, table_row)) + } } diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 245ff7dacb7b4..f56d40c5350f8 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -779,34 +779,26 @@ impl QueryState { /// iter() method, but cannot be chained like a normal [`Iterator`]. /// /// This can only be called for read-only queries, see [`Self::for_each_mut`] for write-queries. + /// + /// Shorthand for `query.iter(world).for_each(..)`. #[inline] pub fn for_each<'w, FN: FnMut(ROQueryItem<'w, Q>)>(&mut self, world: &'w World, func: FN) { // SAFETY: query is read only unsafe { self.update_archetypes(world); - self.as_readonly().for_each_unchecked_manual( - world, - func, - world.last_change_tick(), - world.read_change_tick(), - ); + self.as_readonly() + .iter_unchecked_manual(world, world.last_change_tick(), world.read_change_tick()) + .for_each(func); } } /// Runs `func` on each query result for the given [`World`]. This is faster than the equivalent /// `iter_mut()` method, but cannot be chained like a normal [`Iterator`]. + /// + /// Shorthand for `query.iter_mut(world).for_each(..)`. #[inline] pub fn for_each_mut<'w, FN: FnMut(Q::Item<'w>)>(&mut self, world: &'w mut World, func: FN) { - // SAFETY: query has unique world access - unsafe { - self.update_archetypes(world); - self.for_each_unchecked_manual( - world, - func, - world.last_change_tick(), - world.read_change_tick(), - ); - } + self.iter_mut(world).for_each(func); } /// Runs `func` on each query result for the given [`World`]. This is faster than the equivalent @@ -825,12 +817,8 @@ impl QueryState { func: FN, ) { self.update_archetypes(world); - self.for_each_unchecked_manual( - world, - func, - world.last_change_tick(), - world.read_change_tick(), - ); + self.iter_unchecked_manual(world, world.last_change_tick(), world.read_change_tick()) + .for_each(func); } /// Runs `func` on each query result in parallel. @@ -915,72 +903,6 @@ impl QueryState { ); } - /// Runs `func` on each query result for the given [`World`], where the last change and - /// the current change tick are given. This is faster than the equivalent - /// iter() method, but cannot be chained like a normal [`Iterator`]. - /// - /// # Safety - /// - /// This does not check for mutable query correctness. To be safe, make sure mutable queries - /// have unique access to the components they query. - /// This does not validate that `world.id()` matches `self.world_id`. Calling this on a `world` - /// with a mismatched [`WorldId`] is unsound. - pub(crate) unsafe fn for_each_unchecked_manual<'w, FN: FnMut(Q::Item<'w>)>( - &self, - world: &'w World, - mut func: FN, - last_change_tick: u32, - change_tick: u32, - ) { - // NOTE: If you are changing query iteration code, remember to update the following places, where relevant: - // QueryIter, QueryIterationCursor, QueryManyIter, QueryCombinationIter, QueryState::for_each_unchecked_manual, QueryState::par_for_each_unchecked_manual - let mut fetch = Q::init_fetch(world, &self.fetch_state, last_change_tick, change_tick); - let mut filter = F::init_fetch(world, &self.filter_state, last_change_tick, change_tick); - - let tables = &world.storages().tables; - if Q::IS_DENSE && F::IS_DENSE { - for table_id in &self.matched_table_ids { - let table = tables.get(*table_id).debug_checked_unwrap(); - Q::set_table(&mut fetch, &self.fetch_state, table); - F::set_table(&mut filter, &self.filter_state, table); - - let entities = table.entities(); - for row in 0..table.entity_count() { - let entity = entities.get_unchecked(row); - if !F::filter_fetch(&mut filter, *entity, row) { - continue; - } - func(Q::fetch(&mut fetch, *entity, row)); - } - } - } else { - let archetypes = &world.archetypes; - for archetype_id in &self.matched_archetype_ids { - let archetype = archetypes.get(*archetype_id).debug_checked_unwrap(); - let table = tables.get(archetype.table_id()).debug_checked_unwrap(); - Q::set_archetype(&mut fetch, &self.fetch_state, archetype, table); - F::set_archetype(&mut filter, &self.filter_state, archetype, table); - - let entities = archetype.entities(); - for idx in 0..archetype.len() { - let archetype_entity = entities.get_unchecked(idx); - if !F::filter_fetch( - &mut filter, - archetype_entity.entity, - archetype_entity.table_row, - ) { - continue; - } - func(Q::fetch( - &mut fetch, - archetype_entity.entity, - archetype_entity.table_row, - )); - } - } - } - } - /// Runs `func` on each query result in parallel for the given [`World`], where the last change and /// the current change tick are given. This is faster than the equivalent /// iter() method, but cannot be chained like a normal [`Iterator`]. @@ -1022,30 +944,14 @@ impl QueryState { let func = func.clone(); let len = batch_size.min(table.entity_count() - offset); let task = async move { - let mut fetch = Q::init_fetch( - world, - &self.fetch_state, - last_change_tick, - change_tick, - ); - let mut filter = F::init_fetch( - world, - &self.filter_state, - last_change_tick, - change_tick, - ); - let tables = &world.storages().tables; - let table = tables.get(*table_id).debug_checked_unwrap(); - let entities = table.entities(); - Q::set_table(&mut fetch, &self.fetch_state, table); - F::set_table(&mut filter, &self.filter_state, table); - for row in offset..offset + len { - let entity = entities.get_unchecked(row); - if !F::filter_fetch(&mut filter, *entity, row) { - continue; - } - func(Q::fetch(&mut fetch, *entity, row)); - } + let table = &world + .storages() + .tables + .get(*table_id) + .debug_checked_unwrap(); + let batch = offset..offset + len; + self.iter_unchecked_manual(world, last_change_tick, change_tick) + .fold_table((), &mut |_, item| func(item), table, batch); }; #[cfg(feature = "trace")] let span = bevy_utils::tracing::info_span!( @@ -1073,41 +979,11 @@ impl QueryState { let func = func.clone(); let len = batch_size.min(archetype.len() - offset); let task = async move { - let mut fetch = Q::init_fetch( - world, - &self.fetch_state, - last_change_tick, - change_tick, - ); - let mut filter = F::init_fetch( - world, - &self.filter_state, - last_change_tick, - change_tick, - ); - let tables = &world.storages().tables; let archetype = world.archetypes.get(*archetype_id).debug_checked_unwrap(); - let table = tables.get(archetype.table_id()).debug_checked_unwrap(); - Q::set_archetype(&mut fetch, &self.fetch_state, archetype, table); - F::set_archetype(&mut filter, &self.filter_state, archetype, table); - - let entities = archetype.entities(); - for archetype_index in offset..offset + len { - let archetype_entity = entities.get_unchecked(archetype_index); - if !F::filter_fetch( - &mut filter, - archetype_entity.entity, - archetype_entity.table_row, - ) { - continue; - } - func(Q::fetch( - &mut fetch, - archetype_entity.entity, - archetype_entity.table_row, - )); - } + let batch = offset..offset + len; + self.iter_unchecked_manual(world, last_change_tick, change_tick) + .fold_archetype((), &mut |_, item| func(item), archetype, batch); }; #[cfg(feature = "trace")] diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index 8f7e4f70bc303..a7ad4cd3d8070 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -651,6 +651,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { /// Runs `f` on each read-only query item. /// + /// Shorthand for `query.iter().for_each(..)`. + /// /// # Example /// /// Here, the `report_names_system` iterates over the `Player` component of every entity that contains it: @@ -678,17 +680,17 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { // SAFETY: system runs without conflicts with other systems. // same-system queries have runtime borrow checks when they conflict unsafe { - self.state.as_readonly().for_each_unchecked_manual( - self.world, - f, - self.last_change_tick, - self.change_tick, - ); + self.state + .as_readonly() + .iter_unchecked_manual(self.world, self.last_change_tick, self.change_tick) + .for_each(f); }; } /// Runs `f` on each query item. /// + /// Shorthand for `query.iter_mut().for_each(..)`. + /// /// # Example /// /// Here, the `gravity_system` updates the `Velocity` component of every entity that contains it: @@ -716,12 +718,9 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { // SAFETY: system runs without conflicts with other systems. same-system queries have runtime // borrow checks when they conflict unsafe { - self.state.for_each_unchecked_manual( - self.world, - f, - self.last_change_tick, - self.change_tick, - ); + self.state + .iter_unchecked_manual(self.world, self.last_change_tick, self.change_tick) + .for_each(f); }; } diff --git a/examples/app/empty.rs b/examples/app/empty.rs index 758ff94555c16..761d6a15ec756 100644 --- a/examples/app/empty.rs +++ b/examples/app/empty.rs @@ -2,6 +2,17 @@ use bevy::prelude::*; -fn main() { - App::new().run(); +#[derive(Component)] +struct A(f32); + +#[derive(Component)] +struct B(f32); + +#[no_mangle] +fn test(mut query: Query<(&mut A, &B)>) { + query.iter_mut().for_each(|(mut a, b)| { + a.0 += b.0; + }); } + +fn main() {} From 7d17204bbb874928b4e1407b8fe94b073ed80aee Mon Sep 17 00:00:00 2001 From: james7132 Date: Sat, 26 Nov 2022 17:16:56 -0800 Subject: [PATCH 02/24] Fix starting fold from the middle of iteration --- crates/bevy_ecs/src/query/iter.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index e4fac1eb4556c..bd0a76bb0facd 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -134,6 +134,11 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Iterator for QueryIter<'w, 's Func: FnMut(B, Self::Item) -> B, { let mut accum = init; + // Empty any remaining uniterated values from the current table/archetype + while self.cursor.current_index != self.cursor.current_len { + let Some(item) = self.next() else { break }; + accum = func(accum, item); + } if Q::IS_DENSE && F::IS_DENSE { for table_id in self.cursor.table_id_iter.clone() { // SAFETY: Matched table IDs are guarenteed to still exist. From 2c5af86744e6b3a0c1ec81c83da4f26a54f1ca49 Mon Sep 17 00:00:00 2001 From: james7132 Date: Sat, 26 Nov 2022 18:01:39 -0800 Subject: [PATCH 03/24] Simplify for_each cases and add safety docs --- crates/bevy_ecs/src/query/iter.rs | 64 ++++++++++++++++++++++++++++++ crates/bevy_ecs/src/query/state.rs | 8 ++-- 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index bd0a76bb0facd..93f79039c38f0 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -40,6 +40,13 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIter<'w, 's, Q, F> { } } + /// Executes the equivalent of [`Iterator::fold`] over a contiguous segment + /// from an archetype. + /// + /// # Safety + /// - all `rows` must be in `[0, tables.entity_count)`. + /// - `table` must match the WorldQueries Q and F + /// - Both Q::IS_DENSE and F::IS_DENSE must be true. #[inline] pub(super) unsafe fn fold_table( &mut self, @@ -61,6 +68,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIter<'w, 's, Q, F> { let entities = table.entities(); for row in rows { let entity = entities.get_unchecked(row); + // SAFETY: `set_table` has been called above for the target table. if let Some(item) = self.cursor.fetch(*entity, row) { accum = func(accum, item); } @@ -68,6 +76,13 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIter<'w, 's, Q, F> { accum } + /// Executes the equivalent of [`Iterator::fold`] over a contiguous segment + /// from an archetype. + /// + /// # Safety + /// - all `indicies` must be in `[0, archetype.len())`. + /// - `archetype` must match the WorldQueries Q and F + /// - Either Q::IS_DENSE or F::IS_DENSE must be false. #[inline] pub(super) unsafe fn fold_archetype( &mut self, @@ -96,6 +111,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIter<'w, 's, Q, F> { let entities = archetype.entities(); for index in indices { let archetype_entity = entities.get_unchecked(index); + // SAFETY: `set_archetype` has been called above for the target table. if let Some(item) = self .cursor .fetch(archetype_entity.entity, archetype_entity.table_row) @@ -105,6 +121,46 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIter<'w, 's, Q, F> { } accum } + + /// Executes the equivalent of [`Iterator::for_each`] over a contiguous segment + /// from an table. + /// + /// # Safety + /// - all `rows` must be in `[0, tables.entity_count)`. + /// - `table` must match the WorldQueries Q and F + /// - Both Q::IS_DENSE and F::IS_DENSE must be true. + #[inline] + pub(super) unsafe fn for_each_table( + &mut self, + func: &mut Func, + table: &'w Table, + rows: Range, + ) + where + Func: FnMut(Q::Item<'w>), + { + self.fold_table((), &mut |_, item| func(item), table, rows); + } + + /// Executes the equivalent of [`Iterator::for_each`] over a contiguous segment + /// from an archetype. + /// + /// # Safety + /// - all `indicies` must be in `[0, archetype.len())`. + /// - `archetype` must match the WorldQueries Q and F + /// - Either Q::IS_DENSE or F::IS_DENSE must be false. + #[inline] + pub(super) unsafe fn for_each_archetype( + &mut self, + func: &mut Func, + archetype: &'w Archetype, + indices: Range, + ) + where + Func: FnMut(Q::Item<'w>), + { + self.fold_archetype((), &mut |_, item| func(item), archetype, indices); + } } impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Iterator for QueryIter<'w, 's, Q, F> { @@ -743,6 +799,14 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's, } } + /// Fetches a query item from storage. + /// + /// Returns `None` if the target entity does not match the filter. + /// + /// # Safety + /// - `set_archetype` and `set_table` must be called on both Q and F prior for every change in archetype or table. + /// - `entity` must belong to the archetype or table that was set + /// - `table_row` must be in bounds `0 <= table_row < table.entity_count()` for the table that was set. #[inline(always)] unsafe fn fetch(&mut self, entity: Entity, table_row: usize) -> Option> { F::filter_fetch(&mut self.filter, entity, table_row) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index f56d40c5350f8..b05ad70a99308 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -941,7 +941,7 @@ impl QueryState { let mut offset = 0; while offset < table.entity_count() { - let func = func.clone(); + let mut func = func.clone(); let len = batch_size.min(table.entity_count() - offset); let task = async move { let table = &world @@ -951,7 +951,7 @@ impl QueryState { .debug_checked_unwrap(); let batch = offset..offset + len; self.iter_unchecked_manual(world, last_change_tick, change_tick) - .fold_table((), &mut |_, item| func(item), table, batch); + .for_each_table(&mut func, table, batch); }; #[cfg(feature = "trace")] let span = bevy_utils::tracing::info_span!( @@ -976,14 +976,14 @@ impl QueryState { } while offset < archetype.len() { - let func = func.clone(); + let mut func = func.clone(); let len = batch_size.min(archetype.len() - offset); let task = async move { let archetype = world.archetypes.get(*archetype_id).debug_checked_unwrap(); let batch = offset..offset + len; self.iter_unchecked_manual(world, last_change_tick, change_tick) - .fold_archetype((), &mut |_, item| func(item), archetype, batch); + .for_each_archetype(&mut func, archetype, batch); }; #[cfg(feature = "trace")] From 16b94dcff9b40614495967cfd3fec289fb185556 Mon Sep 17 00:00:00 2001 From: james7132 Date: Sun, 27 Nov 2022 01:04:06 -0800 Subject: [PATCH 04/24] Undo test changes to empty.rs --- examples/app/empty.rs | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/examples/app/empty.rs b/examples/app/empty.rs index 761d6a15ec756..3e97a4cb23284 100644 --- a/examples/app/empty.rs +++ b/examples/app/empty.rs @@ -2,17 +2,6 @@ use bevy::prelude::*; -#[derive(Component)] -struct A(f32); - -#[derive(Component)] -struct B(f32); - -#[no_mangle] -fn test(mut query: Query<(&mut A, &B)>) { - query.iter_mut().for_each(|(mut a, b)| { - a.0 += b.0; - }); +fn main() { + App::new().run() } - -fn main() {} From fe681cb60e2592e0071e7225bdface369ef80353 Mon Sep 17 00:00:00 2001 From: james7132 Date: Sun, 27 Nov 2022 01:05:05 -0800 Subject: [PATCH 05/24] Formatting --- crates/bevy_ecs/src/query/iter.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index 93f79039c38f0..913ff416dd22e 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -135,8 +135,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIter<'w, 's, Q, F> { func: &mut Func, table: &'w Table, rows: Range, - ) - where + ) where Func: FnMut(Q::Item<'w>), { self.fold_table((), &mut |_, item| func(item), table, rows); @@ -155,8 +154,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIter<'w, 's, Q, F> { func: &mut Func, archetype: &'w Archetype, indices: Range, - ) - where + ) where Func: FnMut(Q::Item<'w>), { self.fold_archetype((), &mut |_, item| func(item), archetype, indices); @@ -803,7 +801,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's, /// /// Returns `None` if the target entity does not match the filter. /// - /// # Safety + /// # Safety /// - `set_archetype` and `set_table` must be called on both Q and F prior for every change in archetype or table. /// - `entity` must belong to the archetype or table that was set /// - `table_row` must be in bounds `0 <= table_row < table.entity_count()` for the table that was set. From a11827fa4aec3c04812a57a085446db8ed16f89d Mon Sep 17 00:00:00 2001 From: james7132 Date: Fri, 6 Jan 2023 18:56:23 -0800 Subject: [PATCH 06/24] Fix build --- crates/bevy_ecs/src/query/iter.rs | 21 +++++++++++---------- crates/bevy_ecs/src/query/state.rs | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index 7fd2eb16747e6..efe900c0d1a16 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -69,7 +69,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIter<'w, 's, Q, F> { for row in rows { let entity = entities.get_unchecked(row); // SAFETY: `set_table` has been called above for the target table. - if let Some(item) = self.cursor.fetch(*entity, row) { + if let Some(item) = self.cursor.fetch(*entity, TableRow::new(row)) { accum = func(accum, item); } } @@ -189,7 +189,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Iterator for QueryIter<'w, 's { let mut accum = init; // Empty any remaining uniterated values from the current table/archetype - while self.cursor.current_index != self.cursor.current_len { + while self.cursor.current_row != self.cursor.current_len { let Some(item) = self.next() else { break }; accum = func(accum, item); } @@ -756,13 +756,13 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's, // SAFETY: set_table was called prior. // `current_row` is a table row in range of the current table, because if it was not, then the if above would have been executed. - let entity = self.table_entities.get_unchecked(self.current_index); + let entity = self.table_entities.get_unchecked(self.current_row); let row = TableRow::new(self.current_row); if let Some(item) = self.fetch(*entity, row) { - self.current_index += 1; + self.current_row += 1; return Some(item); } - self.current_index += 1; + self.current_row += 1; } } else { loop { @@ -786,10 +786,11 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's, } // SAFETY: set_archetype was called prior. - // `current_index` is an archetype index row in range of the current archetype, because if it was not, then the if above would have been executed. - let archetype_entity = self.archetype_entities.get_unchecked(self.current_index); - self.current_index += 1; - if let Some(item) = self.fetch(archetype_entity.entity(), archetype_entity.table_row()) + // `current_row` is an archetype index row in range of the current archetype, because if it was not, then the if above would have been executed. + let archetype_entity = self.archetype_entities.get_unchecked(self.current_row); + self.current_row += 1; + if let Some(item) = + self.fetch(archetype_entity.entity(), archetype_entity.table_row()) { return Some(item); } @@ -806,7 +807,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's, /// - `entity` must belong to the archetype or table that was set /// - `table_row` must be in bounds `0 <= table_row < table.entity_count()` for the table that was set. #[inline(always)] - unsafe fn fetch(&mut self, entity: Entity, table_row: usize) -> Option> { + unsafe fn fetch(&mut self, entity: Entity, table_row: TableRow) -> Option> { F::filter_fetch(&mut self.filter, entity, table_row) .then(|| Q::fetch(&mut self.fetch, entity, table_row)) } diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 6a7b0061fd00d..f8509ceed5828 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -6,7 +6,7 @@ use crate::{ query::{ Access, DebugCheckedUnwrap, FilteredAccess, QueryCombinationIter, QueryIter, WorldQuery, }, - storage::{TableId, TableRow}, + storage::TableId, world::{World, WorldId}, }; use bevy_tasks::ComputeTaskPool; From 2abc32730e08ad8b146921089ac9f4592ddfd880 Mon Sep 17 00:00:00 2001 From: james7132 Date: Fri, 6 Jan 2023 19:30:45 -0800 Subject: [PATCH 07/24] Fix CI --- crates/bevy_ecs/src/query/iter.rs | 16 ++++++++-------- examples/app/empty.rs | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index efe900c0d1a16..39aa60b492bef 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -45,8 +45,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIter<'w, 's, Q, F> { /// /// # Safety /// - all `rows` must be in `[0, tables.entity_count)`. - /// - `table` must match the WorldQueries Q and F - /// - Both Q::IS_DENSE and F::IS_DENSE must be true. + /// - `table` must match Q and F + /// - Both `Q::IS_DENSE` and `F::IS_DENSE` must be true. #[inline] pub(super) unsafe fn fold_table( &mut self, @@ -81,8 +81,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIter<'w, 's, Q, F> { /// /// # Safety /// - all `indicies` must be in `[0, archetype.len())`. - /// - `archetype` must match the WorldQueries Q and F - /// - Either Q::IS_DENSE or F::IS_DENSE must be false. + /// - `archetype` must match Q and F + /// - Either `Q::IS_DENSE` or `F::IS_DENSE` must be false. #[inline] pub(super) unsafe fn fold_archetype( &mut self, @@ -127,8 +127,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIter<'w, 's, Q, F> { /// /// # Safety /// - all `rows` must be in `[0, tables.entity_count)`. - /// - `table` must match the WorldQueries Q and F - /// - Both Q::IS_DENSE and F::IS_DENSE must be true. + /// - `table` must match Q and F + /// - Both `Q::IS_DENSE` and `F::IS_DENSE` must be true. #[inline] pub(super) unsafe fn for_each_table( &mut self, @@ -146,8 +146,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIter<'w, 's, Q, F> { /// /// # Safety /// - all `indicies` must be in `[0, archetype.len())`. - /// - `archetype` must match the WorldQueries Q and F - /// - Either Q::IS_DENSE or F::IS_DENSE must be false. + /// - `archetype` must match Q and F + /// - Either `Q::IS_DENSE` or `F::IS_DENSE` must be false. #[inline] pub(super) unsafe fn for_each_archetype( &mut self, diff --git a/examples/app/empty.rs b/examples/app/empty.rs index 3e97a4cb23284..758ff94555c16 100644 --- a/examples/app/empty.rs +++ b/examples/app/empty.rs @@ -3,5 +3,5 @@ use bevy::prelude::*; fn main() { - App::new().run() + App::new().run(); } From 1883adc2cee60b29627af5906a96955fe1eb88bc Mon Sep 17 00:00:00 2001 From: james7132 Date: Fri, 17 Feb 2023 11:31:30 -0800 Subject: [PATCH 08/24] Revert QueryIterationCursor::fetch --- crates/bevy_ecs/src/query/iter.rs | 84 +++++++++++++++++---------- crates/bevy_ecs/src/query/par_iter.rs | 13 +++-- 2 files changed, 61 insertions(+), 36 deletions(-) diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index 39aa60b492bef..ad8f1662662b7 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -67,11 +67,19 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIter<'w, 's, Q, F> { let entities = table.entities(); for row in rows { + // SAFETY: set_table was called prior. + // `current_row` is a table row in range of the current table, because if it was not, then the if above would have been executed. let entity = entities.get_unchecked(row); - // SAFETY: `set_table` has been called above for the target table. - if let Some(item) = self.cursor.fetch(*entity, TableRow::new(row)) { - accum = func(accum, item); + let row = TableRow::new(row); + if !F::filter_fetch(&mut self.cursor.filter, *entity, row) { + continue; } + + // SAFETY: set_table was called prior. + // `current_row` is a table row in range of the current table, because if it was not, then the if above would have been executed. + let item = Q::fetch(&mut self.cursor.fetch, *entity, row); + + accum = func(accum, item); } accum } @@ -110,14 +118,26 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIter<'w, 's, Q, F> { let entities = archetype.entities(); for index in indices { + // SAFETY: set_archetype was called prior. + // `current_row` is an archetype index row in range of the current archetype, because if it was not, then the if above would have been executed. let archetype_entity = entities.get_unchecked(index); - // SAFETY: `set_archetype` has been called above for the target table. - if let Some(item) = self - .cursor - .fetch(archetype_entity.entity(), archetype_entity.table_row()) - { - accum = func(accum, item); + if !F::filter_fetch( + &mut self.cursor.filter, + archetype_entity.entity(), + archetype_entity.table_row(), + ) { + continue; } + + // SAFETY: set_archetype was called prior, `current_row` is an archetype index in range of the current archetype + // `current_row` is an archetype index row in range of the current archetype, because if it was not, then the if above would have been executed. + let item = Q::fetch( + &mut self.cursor.fetch, + archetype_entity.entity(), + archetype_entity.table_row(), + ); + + accum = func(accum, item); } accum } @@ -758,11 +778,17 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's, // `current_row` is a table row in range of the current table, because if it was not, then the if above would have been executed. let entity = self.table_entities.get_unchecked(self.current_row); let row = TableRow::new(self.current_row); - if let Some(item) = self.fetch(*entity, row) { + if !F::filter_fetch(&mut self.filter, *entity, row) { self.current_row += 1; - return Some(item); + continue; } + + // SAFETY: set_table was called prior. + // `current_row` is a table row in range of the current table, because if it was not, then the if above would have been executed. + let item = Q::fetch(&mut self.fetch, *entity, row); + self.current_row += 1; + return Some(item); } } else { loop { @@ -788,27 +814,25 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's, // SAFETY: set_archetype was called prior. // `current_row` is an archetype index row in range of the current archetype, because if it was not, then the if above would have been executed. let archetype_entity = self.archetype_entities.get_unchecked(self.current_row); - self.current_row += 1; - if let Some(item) = - self.fetch(archetype_entity.entity(), archetype_entity.table_row()) - { - return Some(item); + if !F::filter_fetch( + &mut self.filter, + archetype_entity.entity(), + archetype_entity.table_row(), + ) { + self.current_row += 1; + continue; } + + // SAFETY: set_archetype was called prior, `current_row` is an archetype index in range of the current archetype + // `current_row` is an archetype index row in range of the current archetype, because if it was not, then the if above would have been executed. + let item = Q::fetch( + &mut self.fetch, + archetype_entity.entity(), + archetype_entity.table_row(), + ); + self.current_row += 1; + return Some(item); } } } - - /// Fetches a query item from storage. - /// - /// Returns `None` if the target entity does not match the filter. - /// - /// # Safety - /// - `set_archetype` and `set_table` must be called on both Q and F prior for every change in archetype or table. - /// - `entity` must belong to the archetype or table that was set - /// - `table_row` must be in bounds `0 <= table_row < table.entity_count()` for the table that was set. - #[inline(always)] - unsafe fn fetch(&mut self, entity: Entity, table_row: TableRow) -> Option> { - F::filter_fetch(&mut self.filter, entity, table_row) - .then(|| Q::fetch(&mut self.fetch, entity, table_row)) - } } diff --git a/crates/bevy_ecs/src/query/par_iter.rs b/crates/bevy_ecs/src/query/par_iter.rs index fd53fb71a2b9d..cf96c8a9ccc96 100644 --- a/crates/bevy_ecs/src/query/par_iter.rs +++ b/crates/bevy_ecs/src/query/par_iter.rs @@ -148,12 +148,13 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryParIter<'w, 's, Q, F> { ) { let thread_count = ComputeTaskPool::get().thread_num(); if thread_count <= 1 { - self.state.for_each_unchecked_manual( - self.world, - func, - self.world.last_change_tick(), - self.world.read_change_tick(), - ); + self.state + .iter_unchecked_manual( + self.world, + self.world.last_change_tick(), + self.world.read_change_tick(), + ) + .for_each(func); } else { // Need a batch size of at least 1. let batch_size = self.get_batch_size(thread_count).max(1); From c57481a2b81561c44c084f9a711ef50c5d13b9c3 Mon Sep 17 00:00:00 2001 From: james7132 Date: Fri, 17 Feb 2023 23:03:06 -0800 Subject: [PATCH 09/24] Deprecate the functions --- crates/bevy_ecs/src/query/state.rs | 2 ++ crates/bevy_ecs/src/system/query.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 4d86a8479d57f..9fdcb2724e7c3 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -767,6 +767,7 @@ impl QueryState { /// /// Shorthand for `query.iter(world).for_each(..)`. #[inline] + #[deprecated(since = "0.10", note = "QueryState::for_each was not idiomatic Rust and has been moved to query.iter().for_each()")] pub fn for_each<'w, FN: FnMut(ROQueryItem<'w, Q>)>(&mut self, world: &'w World, func: FN) { // SAFETY: query is read only unsafe { @@ -782,6 +783,7 @@ impl QueryState { /// /// Shorthand for `query.iter_mut(world).for_each(..)`. #[inline] + #[deprecated(since = "0.10", note = "QueryState::for_each_mut was not idiomatic Rust and has been moved to query.iter().for_each_mut()")] pub fn for_each_mut<'w, FN: FnMut(Q::Item<'w>)>(&mut self, world: &'w mut World, func: FN) { self.iter_mut(world).for_each(func); } diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index cb02ab8dd8ae3..b8240b2a29ccc 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -685,6 +685,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { /// - [`for_each_mut`](Self::for_each_mut) to operate on mutable query items. /// - [`iter`](Self::iter) for the iterator based alternative. #[inline] + #[deprecated(since = "0.10", note = "Query::for_each was not idiomatic Rust and has been moved to query.iter().for_each()")] pub fn for_each<'this>(&'this self, f: impl FnMut(ROQueryItem<'this, Q>)) { // SAFETY: system runs without conflicts with other systems. // same-system queries have runtime borrow checks when they conflict @@ -723,6 +724,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { /// - [`for_each`](Self::for_each) to operate on read-only query items. /// - [`iter_mut`](Self::iter_mut) for the iterator based alternative. #[inline] + #[deprecated(since = "0.10", note = "Query::for_each_mut was not idiomatic Rust and has been moved to query.iter_mut().for_each()")] pub fn for_each_mut<'a>(&'a mut self, f: impl FnMut(Q::Item<'a>)) { // SAFETY: system runs without conflicts with other systems. same-system queries have runtime // borrow checks when they conflict From 65028bfe3ace85d2398838ee9918c104a2c7dedf Mon Sep 17 00:00:00 2001 From: James Liu Date: Sat, 18 Feb 2023 16:19:23 -0800 Subject: [PATCH 10/24] Apply suggestions from code review Co-authored-by: JoJoJet <21144246+JoJoJet@users.noreply.github.com> --- crates/bevy_ecs/src/query/iter.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index ad8f1662662b7..060e59d31e624 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -88,7 +88,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIter<'w, 's, Q, F> { /// from an archetype. /// /// # Safety - /// - all `indicies` must be in `[0, archetype.len())`. + /// - all `indices` must be in `[0, archetype.len())`. /// - `archetype` must match Q and F /// - Either `Q::IS_DENSE` or `F::IS_DENSE` must be false. #[inline] @@ -165,7 +165,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIter<'w, 's, Q, F> { /// from an archetype. /// /// # Safety - /// - all `indicies` must be in `[0, archetype.len())`. + /// - all `indices` must be in `[0, archetype.len())`. /// - `archetype` must match Q and F /// - Either `Q::IS_DENSE` or `F::IS_DENSE` must be false. #[inline] @@ -215,7 +215,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Iterator for QueryIter<'w, 's } if Q::IS_DENSE && F::IS_DENSE { for table_id in self.cursor.table_id_iter.clone() { - // SAFETY: Matched table IDs are guarenteed to still exist. + // SAFETY: Matched table IDs are guaranteed to still exist. let table = unsafe { self.tables.get(*table_id).debug_checked_unwrap() }; accum = // SAFETY: The fetched table matches the query @@ -224,7 +224,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Iterator for QueryIter<'w, 's } else { for archetype_id in self.cursor.archetype_id_iter.clone() { let archetype = - // SAFETY: Matched archetype IDs are guarenteed to still exist. + // SAFETY: Matched archetype IDs are guaranteed to still exist. unsafe { self.archetypes.get(*archetype_id).debug_checked_unwrap() }; accum = // SAFETY: The fetched archetype and table matches the query From 2f997d0bbf79694cfd781d0e0bcd65354c3b5ef1 Mon Sep 17 00:00:00 2001 From: james7132 Date: Sat, 18 Feb 2023 16:23:43 -0800 Subject: [PATCH 11/24] Formatting --- crates/bevy_ecs/src/query/state.rs | 10 ++++++++-- crates/bevy_ecs/src/system/query.rs | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 9fdcb2724e7c3..6ecf6211545b9 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -767,7 +767,10 @@ impl QueryState { /// /// Shorthand for `query.iter(world).for_each(..)`. #[inline] - #[deprecated(since = "0.10", note = "QueryState::for_each was not idiomatic Rust and has been moved to query.iter().for_each()")] + #[deprecated( + since = "0.10", + note = "QueryState::for_each was not idiomatic Rust and has been moved to query.iter().for_each()" + )] pub fn for_each<'w, FN: FnMut(ROQueryItem<'w, Q>)>(&mut self, world: &'w World, func: FN) { // SAFETY: query is read only unsafe { @@ -783,7 +786,10 @@ impl QueryState { /// /// Shorthand for `query.iter_mut(world).for_each(..)`. #[inline] - #[deprecated(since = "0.10", note = "QueryState::for_each_mut was not idiomatic Rust and has been moved to query.iter().for_each_mut()")] + #[deprecated( + since = "0.10", + note = "QueryState::for_each_mut was not idiomatic Rust and has been moved to query.iter().for_each_mut()" + )] pub fn for_each_mut<'w, FN: FnMut(Q::Item<'w>)>(&mut self, world: &'w mut World, func: FN) { self.iter_mut(world).for_each(func); } diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index b8240b2a29ccc..39a8869538aa0 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -685,7 +685,10 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { /// - [`for_each_mut`](Self::for_each_mut) to operate on mutable query items. /// - [`iter`](Self::iter) for the iterator based alternative. #[inline] - #[deprecated(since = "0.10", note = "Query::for_each was not idiomatic Rust and has been moved to query.iter().for_each()")] + #[deprecated( + since = "0.10", + note = "Query::for_each was not idiomatic Rust and has been moved to query.iter().for_each()" + )] pub fn for_each<'this>(&'this self, f: impl FnMut(ROQueryItem<'this, Q>)) { // SAFETY: system runs without conflicts with other systems. // same-system queries have runtime borrow checks when they conflict @@ -724,7 +727,10 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { /// - [`for_each`](Self::for_each) to operate on read-only query items. /// - [`iter_mut`](Self::iter_mut) for the iterator based alternative. #[inline] - #[deprecated(since = "0.10", note = "Query::for_each_mut was not idiomatic Rust and has been moved to query.iter_mut().for_each()")] + #[deprecated( + since = "0.10", + note = "Query::for_each_mut was not idiomatic Rust and has been moved to query.iter_mut().for_each()" + )] pub fn for_each_mut<'a>(&'a mut self, f: impl FnMut(Q::Item<'a>)) { // SAFETY: system runs without conflicts with other systems. same-system queries have runtime // borrow checks when they conflict From 4bde3f45471fe40e99bae1e11eda9b31f1e8a38d Mon Sep 17 00:00:00 2001 From: james7132 Date: Sat, 18 Feb 2023 17:21:41 -0800 Subject: [PATCH 12/24] Address deprecation --- .../bevy_ecs/iteration/iter_frag_foreach.rs | 2 +- .../iteration/iter_frag_foreach_sparse.rs | 2 +- .../bevy_ecs/iteration/iter_frag_foreach_wide.rs | 2 +- .../iteration/iter_frag_foreach_wide_sparse.rs | 2 +- .../bevy_ecs/iteration/iter_simple_foreach.rs | 3 ++- .../iteration/iter_simple_foreach_sparse_set.rs | 3 ++- .../iteration/iter_simple_foreach_wide.rs | 2 +- .../iter_simple_foreach_wide_sparse_set.rs | 2 +- .../bevy_ecs/scheduling/running_systems.rs | 14 +++++++------- benches/benches/bevy_ecs/scheduling/schedule.rs | 6 +++--- benches/benches/bevy_ecs/world/world_get.rs | 4 ++-- crates/bevy_ecs/src/lib.rs | 16 ++++++++++------ crates/bevy_ecs/src/query/mod.rs | 4 ++-- crates/bevy_ecs/src/query/state.rs | 4 ++-- crates/bevy_ecs/src/system/query.rs | 4 ++-- .../tests/ui/query_lifetime_safety.rs | 8 ++++---- .../tests/ui/query_lifetime_safety.stderr | 16 ++++++++-------- 17 files changed, 50 insertions(+), 44 deletions(-) diff --git a/benches/benches/bevy_ecs/iteration/iter_frag_foreach.rs b/benches/benches/bevy_ecs/iteration/iter_frag_foreach.rs index 480d6f942d2e9..01438fa5412b2 100644 --- a/benches/benches/bevy_ecs/iteration/iter_frag_foreach.rs +++ b/benches/benches/bevy_ecs/iteration/iter_frag_foreach.rs @@ -28,7 +28,7 @@ impl<'w> Benchmark<'w> { } pub fn run(&mut self) { - self.1.for_each_mut(&mut self.0, |mut data| { + self.1.iter_mut(&mut self.0).for_each(|mut data| { data.0 *= 2.0; }); } diff --git a/benches/benches/bevy_ecs/iteration/iter_frag_foreach_sparse.rs b/benches/benches/bevy_ecs/iteration/iter_frag_foreach_sparse.rs index add30629afe3b..4381900a9c0b5 100644 --- a/benches/benches/bevy_ecs/iteration/iter_frag_foreach_sparse.rs +++ b/benches/benches/bevy_ecs/iteration/iter_frag_foreach_sparse.rs @@ -39,7 +39,7 @@ impl<'w> Benchmark<'w> { } pub fn run(&mut self) { - self.1.for_each_mut(&mut self.0, |mut data| { + self.1.iter_mut(&mut self.0).for_each(|mut data| { data.0 *= 2.0; }); } diff --git a/benches/benches/bevy_ecs/iteration/iter_frag_foreach_wide.rs b/benches/benches/bevy_ecs/iteration/iter_frag_foreach_wide.rs index f7807486a14f8..14f2b25efc4e5 100644 --- a/benches/benches/bevy_ecs/iteration/iter_frag_foreach_wide.rs +++ b/benches/benches/bevy_ecs/iteration/iter_frag_foreach_wide.rs @@ -56,7 +56,7 @@ impl<'w> Benchmark<'w> { } pub fn run(&mut self) { - self.1.for_each_mut(&mut self.0, |mut data| { + self.1.iter_mut(&mut self.0).for_each(|mut data| { data.0 .0 *= 2.0; data.1 .0 *= 2.0; data.2 .0 *= 2.0; diff --git a/benches/benches/bevy_ecs/iteration/iter_frag_foreach_wide_sparse.rs b/benches/benches/bevy_ecs/iteration/iter_frag_foreach_wide_sparse.rs index 99ada032f3fd9..33b0ef77b088a 100644 --- a/benches/benches/bevy_ecs/iteration/iter_frag_foreach_wide_sparse.rs +++ b/benches/benches/bevy_ecs/iteration/iter_frag_foreach_wide_sparse.rs @@ -66,7 +66,7 @@ impl<'w> Benchmark<'w> { } pub fn run(&mut self) { - self.1.for_each_mut(&mut self.0, |mut data| { + self.1.iter_mut(&mut self.0).for_each(|mut data| { data.0 .0 *= 2.0; data.1 .0 *= 2.0; data.2 .0 *= 2.0; diff --git a/benches/benches/bevy_ecs/iteration/iter_simple_foreach.rs b/benches/benches/bevy_ecs/iteration/iter_simple_foreach.rs index 36972562b6128..0a515156777c2 100644 --- a/benches/benches/bevy_ecs/iteration/iter_simple_foreach.rs +++ b/benches/benches/bevy_ecs/iteration/iter_simple_foreach.rs @@ -35,7 +35,8 @@ impl<'w> Benchmark<'w> { pub fn run(&mut self) { self.1 - .for_each_mut(&mut self.0, |(velocity, mut position)| { + .iter_mut(&mut self.0) + .for_each(|(velocity, mut position)| { position.0 += velocity.0; }); } diff --git a/benches/benches/bevy_ecs/iteration/iter_simple_foreach_sparse_set.rs b/benches/benches/bevy_ecs/iteration/iter_simple_foreach_sparse_set.rs index 24c37debb2562..f9dad991d482b 100644 --- a/benches/benches/bevy_ecs/iteration/iter_simple_foreach_sparse_set.rs +++ b/benches/benches/bevy_ecs/iteration/iter_simple_foreach_sparse_set.rs @@ -37,7 +37,8 @@ impl<'w> Benchmark<'w> { pub fn run(&mut self) { self.1 - .for_each_mut(&mut self.0, |(velocity, mut position)| { + .iter_mut(&mut self.0) + .for_each(|(velocity, mut position)| { position.0 += velocity.0; }); } diff --git a/benches/benches/bevy_ecs/iteration/iter_simple_foreach_wide.rs b/benches/benches/bevy_ecs/iteration/iter_simple_foreach_wide.rs index a6f5902e41b27..c8809de28e558 100644 --- a/benches/benches/bevy_ecs/iteration/iter_simple_foreach_wide.rs +++ b/benches/benches/bevy_ecs/iteration/iter_simple_foreach_wide.rs @@ -56,7 +56,7 @@ impl<'w> Benchmark<'w> { } pub fn run(&mut self) { - self.1.for_each_mut(&mut self.0, |mut item| { + self.1.iter_mut(&mut self.0).for_each(|mut item| { item.1 .0 += item.0 .0; item.3 .0 += item.2 .0; item.5 .0 += item.4 .0; diff --git a/benches/benches/bevy_ecs/iteration/iter_simple_foreach_wide_sparse_set.rs b/benches/benches/bevy_ecs/iteration/iter_simple_foreach_wide_sparse_set.rs index 0b91fe976a02f..64daf5638b8e6 100644 --- a/benches/benches/bevy_ecs/iteration/iter_simple_foreach_wide_sparse_set.rs +++ b/benches/benches/bevy_ecs/iteration/iter_simple_foreach_wide_sparse_set.rs @@ -58,7 +58,7 @@ impl<'w> Benchmark<'w> { } pub fn run(&mut self) { - self.1.for_each_mut(&mut self.0, |mut item| { + self.1.iter_mut(&mut self.0).for_each(|mut item| { item.1 .0 += item.0 .0; item.3 .0 += item.2 .0; item.5 .0 += item.4 .0; diff --git a/benches/benches/bevy_ecs/scheduling/running_systems.rs b/benches/benches/bevy_ecs/scheduling/running_systems.rs index 9206bd285fcf4..3ebfecde1a4c6 100644 --- a/benches/benches/bevy_ecs/scheduling/running_systems.rs +++ b/benches/benches/bevy_ecs/scheduling/running_systems.rs @@ -54,17 +54,17 @@ pub fn empty_systems(criterion: &mut Criterion) { pub fn busy_systems(criterion: &mut Criterion) { fn ab(mut q: Query<(&mut A, &mut B)>) { - q.for_each_mut(|(mut a, mut b)| { + q.iter_mut().for_each(|(mut a, mut b)| { std::mem::swap(&mut a.0, &mut b.0); }); } fn cd(mut q: Query<(&mut C, &mut D)>) { - q.for_each_mut(|(mut c, mut d)| { + q.iter_mut().for_each(|(mut c, mut d)| { std::mem::swap(&mut c.0, &mut d.0); }); } fn ce(mut q: Query<(&mut C, &mut E)>) { - q.for_each_mut(|(mut c, mut e)| { + q.iter_mut().for_each(|(mut c, mut e)| { std::mem::swap(&mut c.0, &mut e.0); }); } @@ -103,20 +103,20 @@ pub fn busy_systems(criterion: &mut Criterion) { pub fn contrived(criterion: &mut Criterion) { fn s_0(mut q_0: Query<(&mut A, &mut B)>) { - q_0.for_each_mut(|(mut c_0, mut c_1)| { + q_0.iter_mut().for_each(|(mut c_0, mut c_1)| { std::mem::swap(&mut c_0.0, &mut c_1.0); }); } fn s_1(mut q_0: Query<(&mut A, &mut C)>, mut q_1: Query<(&mut B, &mut D)>) { - q_0.for_each_mut(|(mut c_0, mut c_1)| { + q_0.iter_mut().for_each(|(mut c_0, mut c_1)| { std::mem::swap(&mut c_0.0, &mut c_1.0); }); - q_1.for_each_mut(|(mut c_0, mut c_1)| { + q_1.iter_mut().for_each(|(mut c_0, mut c_1)| { std::mem::swap(&mut c_0.0, &mut c_1.0); }); } fn s_2(mut q_0: Query<(&mut C, &mut D)>) { - q_0.for_each_mut(|(mut c_0, mut c_1)| { + q_0.iter_mut().for_each(|(mut c_0, mut c_1)| { std::mem::swap(&mut c_0.0, &mut c_1.0); }); } diff --git a/benches/benches/bevy_ecs/scheduling/schedule.rs b/benches/benches/bevy_ecs/scheduling/schedule.rs index 18a2699becad9..c53bc1ae8be4a 100644 --- a/benches/benches/bevy_ecs/scheduling/schedule.rs +++ b/benches/benches/bevy_ecs/scheduling/schedule.rs @@ -15,19 +15,19 @@ pub fn schedule(c: &mut Criterion) { struct E(f32); fn ab(mut query: Query<(&mut A, &mut B)>) { - query.for_each_mut(|(mut a, mut b)| { + query.iter_mut().for_each(|(mut a, mut b)| { std::mem::swap(&mut a.0, &mut b.0); }); } fn cd(mut query: Query<(&mut C, &mut D)>) { - query.for_each_mut(|(mut c, mut d)| { + query.iter_mut().for_each(|(mut c, mut d)| { std::mem::swap(&mut c.0, &mut d.0); }); } fn ce(mut query: Query<(&mut C, &mut E)>) { - query.for_each_mut(|(mut c, mut e)| { + query.iter_mut().for_each(|(mut c, mut e)| { std::mem::swap(&mut c.0, &mut e.0); }); } diff --git a/benches/benches/bevy_ecs/world/world_get.rs b/benches/benches/bevy_ecs/world/world_get.rs index 4fc7ed51046b2..66828e4eb814f 100644 --- a/benches/benches/bevy_ecs/world/world_get.rs +++ b/benches/benches/bevy_ecs/world/world_get.rs @@ -230,7 +230,7 @@ pub fn world_query_for_each(criterion: &mut Criterion) { bencher.iter(|| { let mut count = 0; - query.for_each(&world, |comp| { + query.iter(&world).for_each(|comp| { black_box(comp); count += 1; black_box(count); @@ -244,7 +244,7 @@ pub fn world_query_for_each(criterion: &mut Criterion) { bencher.iter(|| { let mut count = 0; - query.for_each(&world, |comp| { + query.iter(&world).for_each(|comp| { black_box(comp); count += 1; black_box(count); diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 8ed41658ea57a..c09bdafd84ff1 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -346,7 +346,8 @@ mod tests { let mut results = Vec::new(); world .query::<(Entity, &A, &TableStored)>() - .for_each(&world, |(e, &i, &s)| results.push((e, i, s))); + .iter(&world) + .for_each(|(e, &i, &s)| results.push((e, i, s))); assert_eq!( results, &[ @@ -391,7 +392,8 @@ mod tests { let mut results = Vec::new(); world .query::<(Entity, &A)>() - .for_each(&world, |(e, &i)| results.push((e, i))); + .iter(&world) + .for_each(|(e, &i)| results.push((e, i))); assert_eq!(results, &[(e, A(123)), (f, A(456))]); } @@ -482,7 +484,8 @@ mod tests { let mut results = Vec::new(); world .query_filtered::<&A, With>() - .for_each(&world, |i| results.push(*i)); + .iter(&world) + .for_each(|i| results.push(*i)); assert_eq!(results, vec![A(123)]); } @@ -509,7 +512,8 @@ mod tests { let mut results = Vec::new(); world .query_filtered::<&A, With>() - .for_each(&world, |i| results.push(*i)); + .iter(&world) + .for_each(|i| results.push(*i)); assert_eq!(results, vec![A(123)]); } @@ -1408,8 +1412,8 @@ mod tests { let mut world_a = World::new(); let world_b = World::new(); let mut query = world_a.query::<&A>(); - query.for_each(&world_a, |_| {}); - query.for_each(&world_b, |_| {}); + query.iter(&world_a).for_each(|_| {}); + query.iter(&world_b).for_each(|_| {}); } #[test] diff --git a/crates/bevy_ecs/src/query/mod.rs b/crates/bevy_ecs/src/query/mod.rs index 1b3e1f4d08bca..ce255c1de2381 100644 --- a/crates/bevy_ecs/src/query/mod.rs +++ b/crates/bevy_ecs/src/query/mod.rs @@ -726,7 +726,7 @@ mod tests { let _: Option<[&Foo; 2]> = q.iter_combinations::<2>(&world).next(); let _: Option<&Foo> = q.iter_manual(&world).next(); let _: Option<&Foo> = q.iter_many(&world, [e]).next(); - q.for_each(&world, |_: &Foo| ()); + q.iter(&world).for_each(|_: &Foo| ()); let _: Option<&Foo> = q.get(&world, e).ok(); let _: Option<&Foo> = q.get_manual(&world, e).ok(); @@ -740,7 +740,7 @@ mod tests { let _: Option<&Foo> = q.iter().next(); let _: Option<[&Foo; 2]> = q.iter_combinations::<2>().next(); let _: Option<&Foo> = q.iter_many([e]).next(); - q.for_each(|_: &Foo| ()); + q.iter().for_each(|_: &Foo| ()); let _: Option<&Foo> = q.get(e).ok(); let _: Option<&Foo> = q.get_component(e).ok(); diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 6ecf6211545b9..296da79115c45 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -768,7 +768,7 @@ impl QueryState { /// Shorthand for `query.iter(world).for_each(..)`. #[inline] #[deprecated( - since = "0.10", + since = "0.10.0", note = "QueryState::for_each was not idiomatic Rust and has been moved to query.iter().for_each()" )] pub fn for_each<'w, FN: FnMut(ROQueryItem<'w, Q>)>(&mut self, world: &'w World, func: FN) { @@ -787,7 +787,7 @@ impl QueryState { /// Shorthand for `query.iter_mut(world).for_each(..)`. #[inline] #[deprecated( - since = "0.10", + since = "0.10.0", note = "QueryState::for_each_mut was not idiomatic Rust and has been moved to query.iter().for_each_mut()" )] pub fn for_each_mut<'w, FN: FnMut(Q::Item<'w>)>(&mut self, world: &'w mut World, func: FN) { diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index 39a8869538aa0..606318cc1006c 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -686,7 +686,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { /// - [`iter`](Self::iter) for the iterator based alternative. #[inline] #[deprecated( - since = "0.10", + since = "0.10.0", note = "Query::for_each was not idiomatic Rust and has been moved to query.iter().for_each()" )] pub fn for_each<'this>(&'this self, f: impl FnMut(ROQueryItem<'this, Q>)) { @@ -728,7 +728,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { /// - [`iter_mut`](Self::iter_mut) for the iterator based alternative. #[inline] #[deprecated( - since = "0.10", + since = "0.10.0", note = "Query::for_each_mut was not idiomatic Rust and has been moved to query.iter_mut().for_each()" )] pub fn for_each_mut<'a>(&'a mut self, f: impl FnMut(Q::Item<'a>)) { diff --git a/crates/bevy_ecs_compile_fail_tests/tests/ui/query_lifetime_safety.rs b/crates/bevy_ecs_compile_fail_tests/tests/ui/query_lifetime_safety.rs index 1cc111e1374bd..f1d6048e19995 100644 --- a/crates/bevy_ecs_compile_fail_tests/tests/ui/query_lifetime_safety.rs +++ b/crates/bevy_ecs_compile_fail_tests/tests/ui/query_lifetime_safety.rs @@ -75,16 +75,16 @@ fn main() { { let mut opt_data: Option<&Foo> = None; let mut opt_data_2: Option> = None; - query.for_each(|data| opt_data = Some(data)); - query.for_each_mut(|data| opt_data_2 = Some(data)); + query.iter().for_each(|data| opt_data = Some(data)); + query.iter_mut().for_each(|data| opt_data_2 = Some(data)); assert_eq!(opt_data.unwrap(), &mut *opt_data_2.unwrap()); // oops UB } { let mut opt_data_2: Option> = None; let mut opt_data: Option<&Foo> = None; - query.for_each_mut(|data| opt_data_2 = Some(data)); - query.for_each(|data| opt_data = Some(data)); + query.iter_mut().for_each(|data| opt_data_2 = Some(data)); + query.iter().for_each(|data| opt_data = Some(data)); assert_eq!(opt_data.unwrap(), &mut *opt_data_2.unwrap()); // oops UB } dbg!("bye"); diff --git a/crates/bevy_ecs_compile_fail_tests/tests/ui/query_lifetime_safety.stderr b/crates/bevy_ecs_compile_fail_tests/tests/ui/query_lifetime_safety.stderr index 7b1d0fe610e9c..9ac36f350387d 100644 --- a/crates/bevy_ecs_compile_fail_tests/tests/ui/query_lifetime_safety.stderr +++ b/crates/bevy_ecs_compile_fail_tests/tests/ui/query_lifetime_safety.stderr @@ -101,19 +101,19 @@ error[E0502]: cannot borrow `query` as immutable because it is also borrowed as error[E0502]: cannot borrow `query` as mutable because it is also borrowed as immutable --> tests/ui/query_lifetime_safety.rs:79:13 | -78 | query.for_each(|data| opt_data = Some(data)); - | -------------------------------------------- immutable borrow occurs here -79 | query.for_each_mut(|data| opt_data_2 = Some(data)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here +78 | query.iter().for_each(|data| opt_data = Some(data)); + | ------------ immutable borrow occurs here +79 | query.iter_mut().for_each(|data| opt_data_2 = Some(data)); + | ^^^^^^^^^^^^^^^^ mutable borrow occurs here 80 | assert_eq!(opt_data.unwrap(), &mut *opt_data_2.unwrap()); // oops UB | -------- immutable borrow later used here error[E0502]: cannot borrow `query` as immutable because it is also borrowed as mutable --> tests/ui/query_lifetime_safety.rs:87:13 | -86 | query.for_each_mut(|data| opt_data_2 = Some(data)); - | -------------------------------------------------- mutable borrow occurs here -87 | query.for_each(|data| opt_data = Some(data)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ immutable borrow occurs here +86 | query.iter_mut().for_each(|data| opt_data_2 = Some(data)); + | ---------------- mutable borrow occurs here +87 | query.iter().for_each(|data| opt_data = Some(data)); + | ^^^^^^^^^^^^ immutable borrow occurs here 88 | assert_eq!(opt_data.unwrap(), &mut *opt_data_2.unwrap()); // oops UB | ---------- mutable borrow later used here From bca5e612991bfc867ef30fdede620b08ace574bc Mon Sep 17 00:00:00 2001 From: james7132 Date: Sat, 18 Feb 2023 17:24:49 -0800 Subject: [PATCH 13/24] Update the safety comments. --- crates/bevy_ecs/src/query/iter.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index 060e59d31e624..3f42f011c4ea4 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -68,7 +68,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIter<'w, 's, Q, F> { let entities = table.entities(); for row in rows { // SAFETY: set_table was called prior. - // `current_row` is a table row in range of the current table, because if it was not, then the if above would have been executed. + // Caller assures `row` in range of the current archetype. let entity = entities.get_unchecked(row); let row = TableRow::new(row); if !F::filter_fetch(&mut self.cursor.filter, *entity, row) { @@ -76,7 +76,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIter<'w, 's, Q, F> { } // SAFETY: set_table was called prior. - // `current_row` is a table row in range of the current table, because if it was not, then the if above would have been executed. + // Caller assures `row` in range of the current archetype. let item = Q::fetch(&mut self.cursor.fetch, *entity, row); accum = func(accum, item); @@ -119,7 +119,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIter<'w, 's, Q, F> { let entities = archetype.entities(); for index in indices { // SAFETY: set_archetype was called prior. - // `current_row` is an archetype index row in range of the current archetype, because if it was not, then the if above would have been executed. + // Caller assures `index` in range of the current archetype. let archetype_entity = entities.get_unchecked(index); if !F::filter_fetch( &mut self.cursor.filter, @@ -129,8 +129,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIter<'w, 's, Q, F> { continue; } - // SAFETY: set_archetype was called prior, `current_row` is an archetype index in range of the current archetype - // `current_row` is an archetype index row in range of the current archetype, because if it was not, then the if above would have been executed. + // SAFETY: set_archetype was called prior, `index` is an archetype index in range of the current archetype + // Caller assures `index` in range of the current archetype. let item = Q::fetch( &mut self.cursor.fetch, archetype_entity.entity(), From df3d3bbf3ae6f3c717f61476bf719b2b7c5ff830 Mon Sep 17 00:00:00 2001 From: james7132 Date: Sat, 18 Feb 2023 17:40:11 -0800 Subject: [PATCH 14/24] Address JoJoJet's comment --- crates/bevy_ecs/src/query/iter.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index 3f42f011c4ea4..f9119e4c7c046 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -67,10 +67,11 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIter<'w, 's, Q, F> { let entities = table.entities(); for row in rows { - // SAFETY: set_table was called prior. - // Caller assures `row` in range of the current archetype. + // SAFETY: Caller assures `row` in range of the current archetype. let entity = entities.get_unchecked(row); let row = TableRow::new(row); + // SAFETY: set_table was called prior. + // Caller assures `row` in range of the current archetype. if !F::filter_fetch(&mut self.cursor.filter, *entity, row) { continue; } @@ -118,9 +119,10 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIter<'w, 's, Q, F> { let entities = archetype.entities(); for index in indices { + // SAFETY: Caller assures `index` in range of the current archetype. + let archetype_entity = entities.get_unchecked(index); // SAFETY: set_archetype was called prior. // Caller assures `index` in range of the current archetype. - let archetype_entity = entities.get_unchecked(index); if !F::filter_fetch( &mut self.cursor.filter, archetype_entity.entity(), From ca81c1c9d174c446659999cdd5e98f2701afb734 Mon Sep 17 00:00:00 2001 From: james7132 Date: Sat, 18 Feb 2023 19:48:51 -0800 Subject: [PATCH 15/24] make Query::for_each safer --- crates/bevy_ecs/src/query/state.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 296da79115c45..05558b02c79ac 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -772,13 +772,7 @@ impl QueryState { note = "QueryState::for_each was not idiomatic Rust and has been moved to query.iter().for_each()" )] pub fn for_each<'w, FN: FnMut(ROQueryItem<'w, Q>)>(&mut self, world: &'w World, func: FN) { - // SAFETY: query is read only - unsafe { - self.update_archetypes(world); - self.as_readonly() - .iter_unchecked_manual(world, world.last_change_tick(), world.read_change_tick()) - .for_each(func); - } + self.iter(world).for_each(func); } /// Runs `func` on each query result for the given [`World`]. This is faster than the equivalent From dab9734c4e4eec179f39c329ab92cc9488b494cb Mon Sep 17 00:00:00 2001 From: james7132 Date: Sat, 18 Feb 2023 19:52:09 -0800 Subject: [PATCH 16/24] Remove mentions of QueryState::for_each_unchecked_manual --- crates/bevy_ecs/src/query/iter.rs | 2 +- crates/bevy_ecs/src/query/state.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index f9119e4c7c046..168418115d9c2 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -748,7 +748,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's, } // NOTE: If you are changing query iteration code, remember to update the following places, where relevant: - // QueryIter, QueryIterationCursor, QueryManyIter, QueryCombinationIter, QueryState::for_each_unchecked_manual, QueryState::par_for_each_unchecked_manual + // QueryIter, QueryIterationCursor, QueryManyIter, QueryCombinationIter, QueryState::par_for_each_unchecked_manual /// # Safety /// `tables` and `archetypes` must belong to the same world that the [`QueryIterationCursor`] /// was initialized for. diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 05558b02c79ac..9dc916f518765 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -864,7 +864,7 @@ impl QueryState { change_tick: u32, ) { // NOTE: If you are changing query iteration code, remember to update the following places, where relevant: - // QueryIter, QueryIterationCursor, QueryManyIter, QueryCombinationIter, QueryState::for_each_unchecked_manual, QueryState::par_for_each_unchecked_manual + // QueryIter, QueryIterationCursor, QueryManyIter, QueryCombinationIter, QueryState::par_for_each_unchecked_manual ComputeTaskPool::get().scope(|scope| { if Q::IS_DENSE && F::IS_DENSE { let tables = &world.storages().tables; From d13e9a6400a707d98e318ca6bd38a26da4755891 Mon Sep 17 00:00:00 2001 From: james7132 Date: Sat, 25 Nov 2023 22:35:02 -0800 Subject: [PATCH 17/24] Update deprecation versions --- crates/bevy_ecs/src/query/state.rs | 4 ++-- crates/bevy_ecs/src/system/query.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index f6c74d4f5d526..01c276b395316 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -1018,7 +1018,7 @@ impl QueryState { /// Shorthand for `query.iter(world).for_each(..)`. #[inline] #[deprecated( - since = "0.10.0", + since = "0.13.0", note = "QueryState::for_each was not idiomatic Rust and has been moved to query.iter().for_each()" )] pub fn for_each<'w, FN: FnMut(ROQueryItem<'w, Q>)>(&mut self, world: &'w World, func: FN) { @@ -1031,7 +1031,7 @@ impl QueryState { /// Shorthand for `query.iter_mut(world).for_each(..)`. #[inline] #[deprecated( - since = "0.10.0", + since = "0.13.0", note = "QueryState::for_each_mut was not idiomatic Rust and has been moved to query.iter().for_each_mut()" )] pub fn for_each_mut<'w, FN: FnMut(Q::Item<'w>)>(&mut self, world: &'w mut World, func: FN) { diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index ce27a5f65bf4c..faf98cdba905d 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -736,7 +736,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { /// - [`iter`](Self::iter) for the iterator based alternative. #[inline] #[deprecated( - since = "0.10.0", + since = "0.13.0", note = "Query::for_each was not idiomatic Rust and has been moved to query.iter().for_each()" )] pub fn for_each<'this>(&'this self, f: impl FnMut(ROQueryItem<'this, Q>)) { @@ -779,7 +779,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { /// - [`iter_mut`](Self::iter_mut) for the iterator based alternative. #[inline] #[deprecated( - since = "0.10.0", + since = "0.13.0", note = "Query::for_each_mut was not idiomatic Rust and has been moved to query.iter_mut().for_each()" )] pub fn for_each_mut<'a>(&'a mut self, f: impl FnMut(Q::Item<'a>)) { From 144bb149e74471ac8db569edaf286f7b91a7d1e1 Mon Sep 17 00:00:00 2001 From: james7132 Date: Sat, 25 Nov 2023 23:16:31 -0800 Subject: [PATCH 18/24] Fix CI and rename fold_*/for_each_* --- crates/bevy_ecs/src/query/iter.rs | 66 ++++++++++++++++++++++++++---- crates/bevy_ecs/src/query/state.rs | 4 +- 2 files changed, 60 insertions(+), 10 deletions(-) diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index 9d5e62dfca09f..b36c35724ee19 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -40,15 +40,59 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIter<'w, 's, Q, F> { } } - /// Executes the equivalent of [`Iterator::fold`] over a contiguous segment + /// Executes the equivalent of [`Iterator::for_each`] over a contiguous segment + /// from an table. + /// + /// # Safety + /// - all `rows` must be in `[0, table.entity_count)`. + /// - `table` must match Q and F + /// - Both `Q::IS_DENSE` and `F::IS_DENSE` must be true. + pub(super) unsafe fn for_each_in_table_range( + &mut self, + func: &mut Func, + table: &'w Table, + rows: Range, + ) where + Func: FnMut(Q::Item<'w>), + { + // Caller assures that Q::IS_DENSE and F::IS_DENSE are true, that table matches Q and F + // and all indicies in rows are in range. + unsafe { + self.fold_over_table_range((), &mut |_, item| func(item), table, rows); + } + } + + /// Executes the equivalent of [`Iterator::for_each`] over a contiguous segment /// from an archetype. /// /// # Safety - /// - all `rows` must be in `[0, tables.entity_count)`. + /// - all `indices` must be in `[0, archetype.len())`. + /// - `archetype` must match Q and F + /// - Either `Q::IS_DENSE` or `F::IS_DENSE` must be false. + pub(super) unsafe fn for_each_in_archetype_range( + &mut self, + func: &mut Func, + archetype: &'w Archetype, + rows: Range, + ) where + Func: FnMut(Q::Item<'w>), + { + // Caller assures that either Q::IS_DENSE or F::IS_DENSE are falsae, that archetype matches Q and F + // and all indicies in rows are in range. + unsafe { + self.fold_over_archetype_range((), &mut |_, item| func(item), archetype, rows); + } + } + + /// Executes the equivalent of [`Iterator::fold`] over a contiguous segment + /// from an table. + /// + /// # Safety + /// - all `rows` must be in `[0, table.entity_count)`. /// - `table` must match Q and F /// - Both `Q::IS_DENSE` and `F::IS_DENSE` must be true. #[inline] - pub(super) unsafe fn fold_table( + pub(super) unsafe fn fold_over_table_range( &mut self, mut accum: B, func: &mut Func, @@ -93,7 +137,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIter<'w, 's, Q, F> { /// - `archetype` must match Q and F /// - Either `Q::IS_DENSE` or `F::IS_DENSE` must be false. #[inline] - pub(super) unsafe fn fold_archetype( + pub(super) unsafe fn fold_over_archetype_range( &mut self, mut accum: B, func: &mut Func, @@ -182,8 +226,11 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Iterator for QueryIter<'w, 's // SAFETY: Matched table IDs are guaranteed to still exist. let table = unsafe { self.tables.get(*table_id).debug_checked_unwrap() }; accum = - // SAFETY: The fetched table matches the query - unsafe { self.fold_table(accum, &mut func, table, 0..table.entity_count()) }; + // SAFETY: + // - The fetched table matches both Q and F + // - The provided range is equivalent to [0, table.entity_count) + // - The if block ensures that Q::IS_DENSE and F::IS_DENSE are both true + unsafe { self.fold_over_table_range(accum, &mut func, table, 0..table.entity_count()) }; } } else { for archetype_id in self.cursor.archetype_id_iter.clone() { @@ -191,8 +238,11 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Iterator for QueryIter<'w, 's // SAFETY: Matched archetype IDs are guaranteed to still exist. unsafe { self.archetypes.get(*archetype_id).debug_checked_unwrap() }; accum = - // SAFETY: The fetched archetype and table matches the query - unsafe { self.fold_archetype(accum, &mut func, archetype, 0..archetype.len()) }; + // SAFETY: + // - The fetched archetype matches both Q and F + // - The provided range is equivalent to [0, archetype.len) + // - The if block ensures that ether Q::IS_DENSE or F::IS_DENSE are false + unsafe { self.fold_over_archetype_range(accum, &mut func, archetype, 0..archetype.len()) }; } } accum diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 01c276b395316..c78480cac5136 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -1149,7 +1149,7 @@ impl QueryState { .debug_checked_unwrap(); let batch = offset..offset + len; self.iter_unchecked_manual(world, last_run, this_run) - .for_each_table(&mut func, table, batch); + .for_each_in_table_range(&mut func, table, batch); }); offset += batch_size; } @@ -1173,7 +1173,7 @@ impl QueryState { world.archetypes().get(*archetype_id).debug_checked_unwrap(); let batch = offset..offset + len; self.iter_unchecked_manual(world, last_run, this_run) - .for_each_archetype(&mut func, archetype, batch); + .for_each_in_archetype_range(&mut func, archetype, batch); }); offset += batch_size; } From 624c3c54f9934a15a572748a830e5ceec33dfda8 Mon Sep 17 00:00:00 2001 From: james7132 Date: Sat, 25 Nov 2023 23:50:23 -0800 Subject: [PATCH 19/24] Fix CI --- crates/bevy_ecs/src/query/iter.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index b36c35724ee19..11a48601a3ee1 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -55,7 +55,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIter<'w, 's, Q, F> { ) where Func: FnMut(Q::Item<'w>), { - // Caller assures that Q::IS_DENSE and F::IS_DENSE are true, that table matches Q and F + // SAFETY: Caller assures that Q::IS_DENSE and F::IS_DENSE are true, that table matches Q and F // and all indicies in rows are in range. unsafe { self.fold_over_table_range((), &mut |_, item| func(item), table, rows); @@ -77,7 +77,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIter<'w, 's, Q, F> { ) where Func: FnMut(Q::Item<'w>), { - // Caller assures that either Q::IS_DENSE or F::IS_DENSE are falsae, that archetype matches Q and F + // SAFETY: Caller assures that either Q::IS_DENSE or F::IS_DENSE are falsae, that archetype matches Q and F // and all indicies in rows are in range. unsafe { self.fold_over_archetype_range((), &mut |_, item| func(item), archetype, rows); From 94bc428abd2d42b2af76f7e3f6dd606b21bb13fb Mon Sep 17 00:00:00 2001 From: james7132 Date: Sun, 26 Nov 2023 00:58:49 -0800 Subject: [PATCH 20/24] Fix UX tests --- .../tests/ui/query_lifetime_safety.stderr | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs_compile_fail_tests/tests/ui/query_lifetime_safety.stderr b/crates/bevy_ecs_compile_fail_tests/tests/ui/query_lifetime_safety.stderr index d1ffdf456ff03..042cb246f8019 100644 --- a/crates/bevy_ecs_compile_fail_tests/tests/ui/query_lifetime_safety.stderr +++ b/crates/bevy_ecs_compile_fail_tests/tests/ui/query_lifetime_safety.stderr @@ -102,7 +102,7 @@ error[E0502]: cannot borrow `query` as mutable because it is also borrowed as im --> tests/ui/query_lifetime_safety.rs:79:13 | 78 | query.iter().for_each(|data| opt_data = Some(data)); - | ------------ immutable borrow occurs here + | ----- immutable borrow occurs here 79 | query.iter_mut().for_each(|data| opt_data_2 = Some(data)); | ^^^^^^^^^^^^^^^^ mutable borrow occurs here 80 | assert_eq!(opt_data.unwrap(), &mut *opt_data_2.unwrap()); // oops UB @@ -112,8 +112,8 @@ error[E0502]: cannot borrow `query` as immutable because it is also borrowed as --> tests/ui/query_lifetime_safety.rs:87:13 | 86 | query.iter_mut().for_each(|data| opt_data_2 = Some(data)); - | ---------------- mutable borrow occurs here + | ----- mutable borrow occurs here 87 | query.iter().for_each(|data| opt_data = Some(data)); - | ^^^^^^^^^^^^ immutable borrow occurs here + | ^^^^^ immutable borrow occurs here 88 | assert_eq!(opt_data.unwrap(), &mut *opt_data_2.unwrap()); // oops UB | ---------- mutable borrow later used here From 381bfbb93fb129c1ea3adaa935d772a974e74f19 Mon Sep 17 00:00:00 2001 From: James Liu Date: Sun, 26 Nov 2023 01:11:36 -0800 Subject: [PATCH 21/24] Fix deprecation notice for iter_mut().for_each Co-authored-by: Joseph <21144246+JoJoJet@users.noreply.github.com> --- crates/bevy_ecs/src/query/state.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index c78480cac5136..9072a43505e5b 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -1032,7 +1032,7 @@ impl QueryState { #[inline] #[deprecated( since = "0.13.0", - note = "QueryState::for_each_mut was not idiomatic Rust and has been moved to query.iter().for_each_mut()" + note = "QueryState::for_each_mut was not idiomatic Rust and has been moved to query.iter_mut().for_each()" )] pub fn for_each_mut<'w, FN: FnMut(Q::Item<'w>)>(&mut self, world: &'w mut World, func: FN) { self.iter_mut(world).for_each(func); From 541f4c5234daa86f14add5739daa66dd39af29c2 Mon Sep 17 00:00:00 2001 From: james7132 Date: Thu, 30 Nov 2023 22:54:24 -0800 Subject: [PATCH 22/24] Inline for_each_in* --- crates/bevy_ecs/src/query/iter.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index 1d60ba782e1d2..dc89979aa4a20 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -47,6 +47,7 @@ impl<'w, 's, Q: WorldQueryData, F: WorldQueryFilter> QueryIter<'w, 's, Q, F> { /// - all `rows` must be in `[0, table.entity_count)`. /// - `table` must match Q and F /// - Both `Q::IS_DENSE` and `F::IS_DENSE` must be true. + #[inline] pub(super) unsafe fn for_each_in_table_range( &mut self, func: &mut Func, @@ -69,6 +70,7 @@ impl<'w, 's, Q: WorldQueryData, F: WorldQueryFilter> QueryIter<'w, 's, Q, F> { /// - all `indices` must be in `[0, archetype.len())`. /// - `archetype` must match Q and F /// - Either `Q::IS_DENSE` or `F::IS_DENSE` must be false. + #[inline] pub(super) unsafe fn for_each_in_archetype_range( &mut self, func: &mut Func, From 997ab70255ba084307b2ddf5469175b00872f0a7 Mon Sep 17 00:00:00 2001 From: james7132 Date: Thu, 30 Nov 2023 23:05:12 -0800 Subject: [PATCH 23/24] Fix CI --- crates/bevy_ecs/src/query/iter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index dc89979aa4a20..f109f01d65342 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -2,7 +2,7 @@ use crate::{ archetype::{Archetype, ArchetypeEntity, ArchetypeId, Archetypes}, component::Tick, entity::{Entities, Entity}, - query::{ArchetypeFilter, DebugCheckedUnwrap, QueryState, WorldQuery}, + query::{ArchetypeFilter, DebugCheckedUnwrap, QueryState}, storage::{Table, TableId, TableRow, Tables}, world::unsafe_world_cell::UnsafeWorldCell, }; From 3a9c92c4868c005fab5ea200e53f30fd53eb7893 Mon Sep 17 00:00:00 2001 From: james7132 Date: Thu, 30 Nov 2023 23:26:33 -0800 Subject: [PATCH 24/24] Remove the for_each_* functions when multithreading is not required. --- crates/bevy_ecs/src/query/iter.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index f109f01d65342..76b9535f2a870 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -48,6 +48,7 @@ impl<'w, 's, Q: WorldQueryData, F: WorldQueryFilter> QueryIter<'w, 's, Q, F> { /// - `table` must match Q and F /// - Both `Q::IS_DENSE` and `F::IS_DENSE` must be true. #[inline] + #[cfg(all(not(target = "wasm32"), feature = "multi-threaded"))] pub(super) unsafe fn for_each_in_table_range( &mut self, func: &mut Func, @@ -71,6 +72,7 @@ impl<'w, 's, Q: WorldQueryData, F: WorldQueryFilter> QueryIter<'w, 's, Q, F> { /// - `archetype` must match Q and F /// - Either `Q::IS_DENSE` or `F::IS_DENSE` must be false. #[inline] + #[cfg(all(not(target = "wasm32"), feature = "multi-threaded"))] pub(super) unsafe fn for_each_in_archetype_range( &mut self, func: &mut Func,