From cbca72b120c7894b09388040ca23b9b58161e5a9 Mon Sep 17 00:00:00 2001 From: Charles Bournhonesque Date: Sun, 19 Oct 2025 19:16:52 -0400 Subject: [PATCH 01/10] wip: add uncached queries --- crates/bevy_ecs/src/query/builder.rs | 1 + crates/bevy_ecs/src/query/cache.rs | 342 +++++++++++++++++++++++ crates/bevy_ecs/src/query/iter.rs | 260 +++++++++-------- crates/bevy_ecs/src/query/mod.rs | 7 +- crates/bevy_ecs/src/query/par_iter.rs | 31 +- crates/bevy_ecs/src/query/state.rs | 329 +++++----------------- crates/bevy_ecs/src/query/world_query.rs | 71 +++++ crates/bevy_ecs/src/system/query.rs | 188 ++++++------- 8 files changed, 740 insertions(+), 489 deletions(-) create mode 100644 crates/bevy_ecs/src/query/cache.rs diff --git a/crates/bevy_ecs/src/query/builder.rs b/crates/bevy_ecs/src/query/builder.rs index 8ba34a34f9ee7..2dba029b1c227 100644 --- a/crates/bevy_ecs/src/query/builder.rs +++ b/crates/bevy_ecs/src/query/builder.rs @@ -71,6 +71,7 @@ impl<'w, D: QueryData, F: QueryFilter> QueryBuilder<'w, D, F> { } } + // TODO: warning about this not being used, maybe i messed something up pub(super) fn is_dense(&self) -> bool { // Note: `component_id` comes from the user in safe code, so we cannot trust it to // exist. If it doesn't exist we pessimistically assume it's sparse. diff --git a/crates/bevy_ecs/src/query/cache.rs b/crates/bevy_ecs/src/query/cache.rs new file mode 100644 index 0000000000000..93cd9bdeb7eb9 --- /dev/null +++ b/crates/bevy_ecs/src/query/cache.rs @@ -0,0 +1,342 @@ +use core::fmt::{Debug, Formatter}; +use alloc::vec::Vec; +use fixedbitset::FixedBitSet; +use log::warn; +use bevy_ecs::archetype::{Archetype, ArchetypeGeneration, ArchetypeId, Archetypes}; +use bevy_ecs::component::ComponentId; +use bevy_ecs::entity_disabling::DefaultQueryFilters; +use bevy_ecs::prelude::World; +use bevy_ecs::query::{QueryData, QueryFilter}; +use bevy_ecs::query::state::StorageId; +use bevy_ecs::storage::{SparseSetIndex, TableId}; +use bevy_ecs::world::unsafe_world_cell::UnsafeWorldCell; +use crate::query::QueryState; + +impl QueryState { + + /// Splits self into an immutable view of the "prefix" + /// (all fields *except* cache) and a mutable ref to the `cache`. + pub fn split_cache(&mut self) -> (&QueryState, &mut C) { + // This is safe because `QueryState<..., Uncached>` is a + // valid "prefix" of `QueryState<..., C>`, and QueryState uses `repr(c)` + let rest: &QueryState = unsafe { + &*(self as *mut Self as *const QueryState) + }; + + // This is safe because `cache` is disjoint from the prefix. + let cache_mut: &mut C = &mut self.cache; + + (rest, cache_mut) + } + + /// Updates the state's internal view of the [`World`]'s archetypes. If this is not called before querying data, + /// the results may not accurately reflect what is in the `world`. + /// + /// This is only required if a `manual` method (such as [`Self::get_manual`]) is being called, and it only needs to + /// be called if the `world` has been structurally mutated (i.e. added/removed a component or resource). Users using + /// non-`manual` methods such as [`QueryState::get`] do not need to call this as it will be automatically called for them. + /// + /// If you have an [`UnsafeWorldCell`] instead of `&World`, consider using [`QueryState::update_archetypes_unsafe_world_cell`]. + /// + /// # Panics + /// + /// If `world` does not match the one used to call `QueryState::new` for this instance. + pub fn update_archetypes(&mut self, world: &World) { + self.update_archetypes_unsafe_world_cell(world.as_unsafe_world_cell_readonly()); + } + + pub fn update_archetypes_unsafe_world_cell(&mut self, world: UnsafeWorldCell) { + let (uncached_state, cache) = self.split_cache(); + cache.update_archetypes(uncached_state, world); + } + + /// Safety: todo. + pub(crate) fn matches(&self, archetype: &Archetype) -> bool { + // SAFETY: from parent function's safety constraint + unsafe { self.cache.contains(self, archetype) } + } +} + + +impl QueryState { + /// Returns `true` if this query matches a set of components. Otherwise, returns `false`. + /// + /// # Safety + /// `archetype` must be from the `World` this state was initialized from. + unsafe fn matches_archetype(&self, archetype: &Archetype) -> bool { + D::matches_component_set(&self.fetch_state, &|id| archetype.contains(id)) + && F::matches_component_set(&self.filter_state, &|id| archetype.contains(id)) + && self.matches_component_set(&|id| archetype.contains(id)) + } + + /// Returns `true` if this query matches a set of components. Otherwise, returns `false`. + pub fn matches_component_set(&self, set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { + self.component_access.filter_sets.iter().any(|set| { + set.with + .ones() + .all(|index| set_contains_id(ComponentId::get_sparse_set_index(index))) + && set + .without + .ones() + .all(|index| !set_contains_id(ComponentId::get_sparse_set_index(index))) + }) + } + + /// Iterate through all archetypes that match the [`QueryState`] with an [`ArchetypeGeneration`] higher than the provided one, + /// and call `f` on each of them. + fn iter_archetypes(&self, archetype_generation: ArchetypeGeneration, archetypes: &Archetypes, mut f: impl FnMut(&Archetype)) { + if self.component_access.required.is_empty() { + archetypes[archetype_generation..].iter().for_each(|archetype| { + // SAFETY: The validate_world call ensures that the world is the same the QueryState + // was initialized from. + if unsafe { self.matches_archetype(archetype) } { + f(archetype); + } + }) + } else { + // if there are required components, we can optimize by only iterating through archetypes + // that contain at least one of the required components + let potential_archetype_ids = self + .component_access + .required + .ones() + .filter_map(|idx| { + let component_id = ComponentId::get_sparse_set_index(idx); + archetypes + .component_index() + .get(&component_id) + .map(|index| index.keys()) + }) + // select the component with the fewest archetypes + .min_by_key(ExactSizeIterator::len); + if let Some(archetype_ids) = potential_archetype_ids { + for archetype_id in archetype_ids { + // exclude archetypes that have already been processed + if archetype_id < &archetype_generation.0 { + continue; + } + // SAFETY: get_potential_archetypes only returns archetype ids that are valid for the world + let archetype = &archetypes[*archetype_id]; + // SAFETY: The validate_world call ensures that the world is the same the QueryState + // was initialized from. + if unsafe { self.matches_archetype(archetype) } { + f(archetype) + } + } + } + } + } + +} + +pub trait QueryCache: Debug + Clone { + + fn iteration_data<'s, D: QueryData, F: QueryFilter>(&self, query: &QueryState, world: UnsafeWorldCell, storage_ids: &'s mut Vec) -> IterationData<'s>; + + /// # Safety + /// `archetype` must be from the `World` this state was initialized from. + unsafe fn contains(&self, query: &QueryState, archetype: &Archetype) -> bool; + + /// Creates a new [`QueryCache`] but does not populate it with the matched results from the World yet + /// + /// `new_archetype` and its variants must be called on all of the World's archetypes before the + /// state can return valid query results. + fn uninitialized(world: &World) -> Self; + + fn update_archetypes(&mut self, uncached: &QueryState, world: UnsafeWorldCell); + + /// Return a new cache that contains the archetypes matched by the query joining self and other + fn join(&self, other: &Self) -> Self; + + +} + +#[derive(Clone)] +pub(super) struct IterationData<'s> { + pub(super) is_dense: bool, + pub(super) storage_ids: core::slice::Iter<'s, StorageId>, +} + +#[derive(Clone)] +pub struct CacheState { + pub(crate) archetype_generation: ArchetypeGeneration, + /// Metadata about the [`Table`](crate::storage::Table)s matched by this query. + pub(crate) matched_tables: FixedBitSet, + /// Metadata about the [`Archetype`]s matched by this query. + pub(crate) matched_archetypes: FixedBitSet, + // NOTE: we maintain both a bitset and a vec because iterating the vec is faster + pub(super) matched_storage_ids: Vec, + // Represents whether this query iteration is dense or not. When this is true + // `matched_storage_ids` stores `TableId`s, otherwise it stores `ArchetypeId`s. + pub(super) is_dense: bool, +} + +impl QueryCache for CacheState { + + fn iteration_data<'s, D: QueryData, F: QueryFilter>(self: &Self, _: &QueryState, _: UnsafeWorldCell, _: &'s mut Vec) -> IterationData<'s> { + IterationData { + storage_ids: self.matched_storage_ids.iter(), + is_dense: self.is_dense, + } + } + + unsafe fn contains(&self, _: &QueryState, archetype: &Archetype) -> bool { + self.matched_archetypes.contains(archetype.id().index()) + } + + fn uninitialized(world: &World) -> Self { + // For queries without dynamic filters the dense-ness of the query is equal to the dense-ness + // of its static type parameters. + let mut is_dense = D::IS_DENSE && F::IS_DENSE; + + if let Some(default_filters) = world.get_resource::() { + is_dense &= default_filters.is_dense(world.components()); + } + + Self { + archetype_generation: ArchetypeGeneration::initial(), + matched_tables: Default::default(), + matched_archetypes: Default::default(), + matched_storage_ids: Vec::new(), + is_dense, + } + } + fn update_archetypes(&mut self, uncached: &QueryState, world: UnsafeWorldCell) { + uncached.validate_world(world.id()); + if self.archetype_generation == world.archetypes().generation() { + // skip if we are already up to date + return + } + let old_generation = + core::mem::replace(&mut self.archetype_generation, world.archetypes().generation()); + uncached.iter_archetypes(old_generation, world.archetypes(), |archetype| self.cache_archetype(archetype)); + } + + fn join(&self, other: &Self) -> Self { + if self.archetype_generation != other.archetype_generation { + warn!("You have tried to join queries with different archetype_generations. This could lead to unpredictable results."); + } + // the join is dense of both the queries were dense. + let is_dense = self.is_dense && other.is_dense; + // take the intersection of the matched ids + let mut matched_tables = self.matched_tables.clone(); + let mut matched_archetypes = self.matched_archetypes.clone(); + matched_tables.intersect_with(&other.matched_tables); + matched_archetypes.intersect_with(&other.matched_archetypes); + let matched_storage_ids = if is_dense { + matched_tables + .ones() + .map(|id| StorageId { + table_id: TableId::from_usize(id), + }) + .collect() + } else { + matched_archetypes + .ones() + .map(|id| StorageId { + archetype_id: ArchetypeId::new(id), + }) + .collect() + }; + CacheState { + archetype_generation: self.archetype_generation, + matched_tables, + matched_archetypes, + matched_storage_ids, + is_dense, + } + + } +} + + +impl Debug for CacheState { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + f.debug_struct("CacheState") + .field("matched_table_count", &self.matched_tables.count_ones(..)) + .field( + "matched_archetype_count", + &self.matched_archetypes.count_ones(..), + ) + .finish() + } +} + +impl CacheState { + /// Returns the tables matched by this query. + pub fn matched_tables(&self) -> impl Iterator + '_ { + self.matched_tables.ones().map(TableId::from_usize) + } + + /// Returns the archetypes matched by this query. + pub fn matched_archetypes(&self) -> impl Iterator + '_ { + self.matched_archetypes.ones().map(ArchetypeId::new) + } + + /// Add a new archetype in the cache + fn cache_archetype(&mut self, archetype: &Archetype) { + let archetype_index = archetype.id().index(); + if !self.matched_archetypes.contains(archetype_index) { + self.matched_archetypes.grow_and_insert(archetype_index); + if !self.is_dense { + self.matched_storage_ids.push(StorageId { + archetype_id: archetype.id(), + }); + } + } + let table_index = archetype.table_id().as_usize(); + if !self.matched_tables.contains(table_index) { + self.matched_tables.grow_and_insert(table_index); + if self.is_dense { + self.matched_storage_ids.push(StorageId { + table_id: archetype.table_id(), + }); + } + } + } + +} + + +#[derive(Debug, Clone)] +pub struct Uncached; + + + +impl QueryCache for Uncached { + + fn iteration_data<'s, D: QueryData, F: QueryFilter>(&self, query: &QueryState, world: UnsafeWorldCell, storage_ids: &'s mut Vec) -> IterationData<'s> { + let generation = world.archetypes().generation(); + // TODO: what about computing is_dense from DefaultQueryFilters? + // Realistically all DefaultQueryFilters would be dense. We should enforce it. + let is_dense = D::IS_DENSE && F::IS_DENSE; + query.iter_archetypes(generation, world.archetypes(), |archetype| { + storage_ids.push(if !is_dense { + StorageId { archetype_id: archetype.id() } + } else { + StorageId { table_id: archetype.table_id() } + }) + }); + IterationData { + is_dense, + storage_ids: storage_ids.iter() + } + } + + unsafe fn contains(&self, query: &QueryState, archetype: &Archetype) -> bool { + // SAFETY: satisfied from QueryCache::contains's safety constraints + unsafe { query.matches_archetype(archetype) } + } + + fn uninitialized(_: &World) -> Self { + Uncached + } + + /// We do not update anything. This is here for feature parity. + fn update_archetypes(&mut self, _: &QueryState, _: UnsafeWorldCell) { + } + + fn join(&self, _: &Self) -> Self { + self.clone() + } +} \ No newline at end of file diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index 13e69ce3cb969..17d920440f22d 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -20,26 +20,28 @@ use core::{ ops::Range, }; use nonmax::NonMaxU32; +use bevy_ecs::query::cache::QueryCache; +use bevy_ecs::query::IterationData; /// An [`Iterator`] over query results of a [`Query`](crate::system::Query). /// /// This struct is created by the [`Query::iter`](crate::system::Query::iter) and /// [`Query::iter_mut`](crate::system::Query::iter_mut) methods. -pub struct QueryIter<'w, 's, D: QueryData, F: QueryFilter> { +pub struct QueryIter<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> { world: UnsafeWorldCell<'w>, tables: &'w Tables, archetypes: &'w Archetypes, - query_state: &'s QueryState, - cursor: QueryIterationCursor<'w, 's, D, F>, + query_state: &'s QueryState, + cursor: QueryIterationCursor<'w, 's, D, F, C>, } -impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> QueryIter<'w, 's, D, F, C> { /// # Safety /// - `world` must have permission to access any of the components registered in `query_state`. /// - `world` must be the same one used to initialize `query_state`. pub(crate) unsafe fn new( world: UnsafeWorldCell<'w>, - query_state: &'s QueryState, + query_state: &'s QueryState, last_run: Tick, this_run: Tick, ) -> Self { @@ -79,7 +81,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { /// } /// } /// ``` - pub fn remaining(&self) -> QueryIter<'w, 's, D, F> + pub fn remaining(&self) -> QueryIter<'w, 's, D, F, C> where D: ReadOnlyQueryData, { @@ -116,7 +118,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { /// } /// } /// ``` - pub fn remaining_mut(&mut self) -> QueryIter<'_, 's, D, F> { + pub fn remaining_mut(&mut self) -> QueryIter<'_, 's, D, F, C> { QueryIter { world: self.world, tables: self.tables, @@ -515,6 +517,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { 's, D, F, + C, impl ExactSizeIterator + DoubleEndedIterator + FusedIterator + 'w, > where @@ -572,6 +575,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { 's, D, F, + C, impl ExactSizeIterator + DoubleEndedIterator + FusedIterator + 'w, > where @@ -637,6 +641,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { 's, D, F, + C, impl ExactSizeIterator + DoubleEndedIterator + FusedIterator + 'w, > { self.sort_impl::(move |keyed_query| { @@ -669,6 +674,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { 's, D, F, + C, impl ExactSizeIterator + DoubleEndedIterator + FusedIterator + 'w, > { self.sort_impl::(move |keyed_query| { @@ -761,6 +767,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { 's, D, F, + C, impl ExactSizeIterator + DoubleEndedIterator + FusedIterator + 'w, > where @@ -794,6 +801,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { 's, D, F, + C, impl ExactSizeIterator + DoubleEndedIterator + FusedIterator + 'w, > where @@ -829,6 +837,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { 's, D, F, + C, impl ExactSizeIterator + DoubleEndedIterator + FusedIterator + 'w, > where @@ -859,6 +868,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { 's, D, F, + C, impl ExactSizeIterator + DoubleEndedIterator + FusedIterator + 'w, > { // On the first successful iteration of `QueryIterationCursor`, `archetype_entities` or `table_entities` @@ -902,7 +912,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { } } -impl<'w, 's, D: QueryData, F: QueryFilter> Iterator for QueryIter<'w, 's, D, F> { +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> Iterator for QueryIter<'w, 's, D, F, C> { type Item = D::Item<'w, 's>; #[inline(always)] @@ -945,48 +955,48 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Iterator for QueryIter<'w, 's, D, F> } // This is correct as [`QueryIter`] always returns `None` once exhausted. -impl<'w, 's, D: QueryData, F: QueryFilter> FusedIterator for QueryIter<'w, 's, D, F> {} +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> FusedIterator for QueryIter<'w, 's, D, F, C> {} // SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once. -unsafe impl<'w, 's, F: QueryFilter> EntitySetIterator for QueryIter<'w, 's, Entity, F> {} +unsafe impl<'w, 's, F: QueryFilter, C: QueryCache> EntitySetIterator for QueryIter<'w, 's, Entity, F, C> {} // SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once. -unsafe impl<'w, 's, F: QueryFilter> EntitySetIterator for QueryIter<'w, 's, EntityRef<'_>, F> {} +unsafe impl<'w, 's, F: QueryFilter, C: QueryCache> EntitySetIterator for QueryIter<'w, 's, EntityRef<'_>, F, C> {} // SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once. -unsafe impl<'w, 's, F: QueryFilter> EntitySetIterator for QueryIter<'w, 's, EntityMut<'_>, F> {} +unsafe impl<'w, 's, F: QueryFilter, C: QueryCache> EntitySetIterator for QueryIter<'w, 's, EntityMut<'_>, F, C> {} // SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once. -unsafe impl<'w, 's, F: QueryFilter> EntitySetIterator - for QueryIter<'w, 's, FilteredEntityRef<'_, '_>, F> +unsafe impl<'w, 's, F: QueryFilter, C: QueryCache> EntitySetIterator + for QueryIter<'w, 's, FilteredEntityRef<'_, '_>, F, C> { } // SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once. -unsafe impl<'w, 's, F: QueryFilter> EntitySetIterator - for QueryIter<'w, 's, FilteredEntityMut<'_, '_>, F> +unsafe impl<'w, 's, F: QueryFilter, C: QueryCache> EntitySetIterator + for QueryIter<'w, 's, FilteredEntityMut<'_, '_>, F, C> { } // SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once. -unsafe impl<'w, 's, F: QueryFilter, B: Bundle> EntitySetIterator - for QueryIter<'w, 's, EntityRefExcept<'_, '_, B>, F> +unsafe impl<'w, 's, F: QueryFilter, B: Bundle, C: QueryCache> EntitySetIterator + for QueryIter<'w, 's, EntityRefExcept<'_, '_, B>, F, C> { } // SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once. -unsafe impl<'w, 's, F: QueryFilter, B: Bundle> EntitySetIterator - for QueryIter<'w, 's, EntityMutExcept<'_, '_, B>, F> +unsafe impl<'w, 's, F: QueryFilter, B: Bundle, C: QueryCache> EntitySetIterator + for QueryIter<'w, 's, EntityMutExcept<'_, '_, B>, F, C> { } -impl<'w, 's, D: QueryData, F: QueryFilter> Debug for QueryIter<'w, 's, D, F> { +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> Debug for QueryIter<'w, 's, D, F, C> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("QueryIter").finish() } } -impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter> Clone for QueryIter<'w, 's, D, F> { +impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, C: QueryCache> Clone for QueryIter<'w, 's, D, F, C> { fn clone(&self) -> Self { self.remaining() } @@ -997,7 +1007,7 @@ impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter> Clone for QueryIter<'w, 's, D /// This struct is created by the [`QueryIter::sort`], [`QueryIter::sort_unstable`], /// [`QueryIter::sort_by`], [`QueryIter::sort_unstable_by`], [`QueryIter::sort_by_key`], /// [`QueryIter::sort_unstable_by_key`], and [`QueryIter::sort_by_cached_key`] methods. -pub struct QuerySortedIter<'w, 's, D: QueryData, F: QueryFilter, I> +pub struct QuerySortedIter<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, I> where I: Iterator, { @@ -1006,10 +1016,10 @@ where tables: &'w Tables, archetypes: &'w Archetypes, fetch: D::Fetch<'w>, - query_state: &'s QueryState, + query_state: &'s QueryState, } -impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> QuerySortedIter<'w, 's, D, F, I> +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, I: Iterator> QuerySortedIter<'w, 's, D, F, C, I> where I: Iterator, { @@ -1019,11 +1029,11 @@ where /// - `entity_list` must only contain unique entities or be empty. pub(crate) unsafe fn new>( world: UnsafeWorldCell<'w>, - query_state: &'s QueryState, + query_state: &'s QueryState, entity_list: EntityList, last_run: Tick, this_run: Tick, - ) -> QuerySortedIter<'w, 's, D, F, I> { + ) -> QuerySortedIter<'w, 's, D, F, C, I> { let fetch = D::init_fetch(world, &query_state.fetch_state, last_run, this_run); QuerySortedIter { query_state, @@ -1080,8 +1090,8 @@ where } } -impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> Iterator - for QuerySortedIter<'w, 's, D, F, I> +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, I: Iterator> Iterator + for QuerySortedIter<'w, 's, D, F, C, I> where I: Iterator, { @@ -1099,8 +1109,8 @@ where } } -impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> DoubleEndedIterator - for QuerySortedIter<'w, 's, D, F, I> +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, I: Iterator> DoubleEndedIterator + for QuerySortedIter<'w, 's, D, F, C, I> where I: DoubleEndedIterator, { @@ -1112,16 +1122,16 @@ where } } -impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> ExactSizeIterator - for QuerySortedIter<'w, 's, D, F, I> +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, I: Iterator> ExactSizeIterator + for QuerySortedIter<'w, 's, D, F, C, I> where I: ExactSizeIterator, { } // This is correct as [`QuerySortedIter`] returns `None` once exhausted if `entity_iter` does. -impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> FusedIterator - for QuerySortedIter<'w, 's, D, F, I> +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, I: Iterator> FusedIterator + for QuerySortedIter<'w, 's, D, F, C, I> where I: FusedIterator, { @@ -1130,13 +1140,13 @@ where // SAFETY: // `I` stems from a collected and sorted `EntitySetIterator` ([`QueryIter`]). // Fetching unique entities maintains uniqueness. -unsafe impl<'w, 's, F: QueryFilter, I: Iterator> EntitySetIterator - for QuerySortedIter<'w, 's, Entity, F, I> +unsafe impl<'w, 's, F: QueryFilter, C: QueryCache, I: Iterator> EntitySetIterator + for QuerySortedIter<'w, 's, Entity, F, C, I> { } -impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> Debug - for QuerySortedIter<'w, 's, D, F, I> +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, I: Iterator> Debug + for QuerySortedIter<'w, 's, D, F, C, I> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("QuerySortedIter").finish() @@ -1149,7 +1159,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> Debug /// Entities that don't match the query are skipped. /// /// This struct is created by the [`Query::iter_many`](crate::system::Query::iter_many) and [`Query::iter_many_mut`](crate::system::Query::iter_many_mut) methods. -pub struct QueryManyIter<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> +pub struct QueryManyIter<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, I: Iterator> { world: UnsafeWorldCell<'w>, entity_iter: I, @@ -1158,22 +1168,22 @@ pub struct QueryManyIter<'w, 's, D: QueryData, F: QueryFilter, I: Iterator, filter: F::Fetch<'w>, - query_state: &'s QueryState, + query_state: &'s QueryState, } -impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> - QueryManyIter<'w, 's, D, F, I> +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, I: Iterator> + QueryManyIter<'w, 's, D, F, C, I> { /// # Safety /// - `world` must have permission to access any of the components registered in `query_state`. /// - `world` must be the same one used to initialize `query_state`. pub(crate) unsafe fn new>( world: UnsafeWorldCell<'w>, - query_state: &'s QueryState, + query_state: &'s QueryState, entity_list: EntityList, last_run: Tick, this_run: Tick, - ) -> QueryManyIter<'w, 's, D, F, I> { + ) -> QueryManyIter<'w, 's, D, F, C, I> { let fetch = D::init_fetch(world, &query_state.fetch_state, last_run, this_run); let filter = F::init_fetch(world, &query_state.filter_state, last_run, this_run); QueryManyIter { @@ -1206,7 +1216,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> archetypes: &'w Archetypes, fetch: &mut D::Fetch<'w>, filter: &mut F::Fetch<'w>, - query_state: &'s QueryState, + query_state: &'s QueryState, ) -> Option> { for entity_borrow in entity_iter { let entity = entity_borrow.entity(); @@ -1214,14 +1224,10 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> continue; }; - if !query_state - .matched_archetypes - .contains(location.archetype_id.index()) - { + let archetype = archetypes.get(location.archetype_id).debug_checked_unwrap(); + if !query_state.matches(archetype) { continue; } - - let archetype = archetypes.get(location.archetype_id).debug_checked_unwrap(); let table = tables.get(location.table_id).debug_checked_unwrap(); // SAFETY: `archetype` is from the world that `fetch/filter` were created for, @@ -1379,6 +1385,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> 's, D, F, + C, impl ExactSizeIterator + DoubleEndedIterator + FusedIterator + 'w, > where @@ -1437,6 +1444,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> 's, D, F, + C, impl ExactSizeIterator + DoubleEndedIterator + FusedIterator + 'w, > where @@ -1503,6 +1511,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> 's, D, F, + C, impl ExactSizeIterator + DoubleEndedIterator + FusedIterator + 'w, > { self.sort_impl::(move |keyed_query| { @@ -1534,6 +1543,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> 's, D, F, + C, impl ExactSizeIterator + DoubleEndedIterator + FusedIterator + 'w, > { self.sort_impl::(move |keyed_query| { @@ -1628,6 +1638,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> 's, D, F, + C, impl ExactSizeIterator + DoubleEndedIterator + FusedIterator + 'w, > where @@ -1660,6 +1671,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> 's, D, F, + C, impl ExactSizeIterator + DoubleEndedIterator + FusedIterator + 'w, > where @@ -1694,6 +1706,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> 's, D, F, + C, impl ExactSizeIterator + DoubleEndedIterator + FusedIterator + 'w, > where @@ -1723,6 +1736,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> 's, D, F, + C, impl ExactSizeIterator + DoubleEndedIterator + FusedIterator + 'w, > { let world = self.world; @@ -1762,8 +1776,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> } } -impl<'w, 's, D: QueryData, F: QueryFilter, I: DoubleEndedIterator> - QueryManyIter<'w, 's, D, F, I> +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, I: DoubleEndedIterator> + QueryManyIter<'w, 's, D, F, C, I> { /// Get next result from the back of the query #[inline(always)] @@ -1788,8 +1802,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: DoubleEndedIterator> Iterator - for QueryManyIter<'w, 's, D, F, I> +impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, C: QueryCache, I: Iterator> Iterator + for QueryManyIter<'w, 's, D, F, C, I> { type Item = D::Item<'w, 's>; @@ -1822,8 +1836,9 @@ impl< 's, D: ReadOnlyQueryData, F: QueryFilter, + C: QueryCache, I: DoubleEndedIterator, - > DoubleEndedIterator for QueryManyIter<'w, 's, D, F, I> + > DoubleEndedIterator for QueryManyIter<'w, 's, D, F, C, I> { #[inline(always)] fn next_back(&mut self) -> Option { @@ -1845,19 +1860,19 @@ impl< } // This is correct as [`QueryManyIter`] always returns `None` once exhausted. -impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, I: Iterator> - FusedIterator for QueryManyIter<'w, 's, D, F, I> +impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, C: QueryCache, I: Iterator> + FusedIterator for QueryManyIter<'w, 's, D, F, C, I> { } // SAFETY: Fetching unique entities maintains uniqueness. -unsafe impl<'w, 's, F: QueryFilter, I: EntitySetIterator> EntitySetIterator - for QueryManyIter<'w, 's, Entity, F, I> +unsafe impl<'w, 's, F: QueryFilter, C: QueryCache, I: EntitySetIterator> EntitySetIterator + for QueryManyIter<'w, 's, Entity, F, C, I> { } -impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> Debug - for QueryManyIter<'w, 's, D, F, I> +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, I: Iterator> Debug + for QueryManyIter<'w, 's, D, F, C, I> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("QueryManyIter").finish() @@ -1877,23 +1892,23 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> /// [`iter_many_unique`]: crate::system::Query::iter_many /// [`iter_many_unique_mut`]: crate::system::Query::iter_many_mut /// [`Query`]: crate::system::Query -pub struct QueryManyUniqueIter<'w, 's, D: QueryData, F: QueryFilter, I: EntitySetIterator>( - QueryManyIter<'w, 's, D, F, I>, +pub struct QueryManyUniqueIter<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, I: EntitySetIterator>( + QueryManyIter<'w, 's, D, F, C, I>, ); -impl<'w, 's, D: QueryData, F: QueryFilter, I: EntitySetIterator> - QueryManyUniqueIter<'w, 's, D, F, I> +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, I: EntitySetIterator> + QueryManyUniqueIter<'w, 's, D, F, C, I> { /// # Safety /// - `world` must have permission to access any of the components registered in `query_state`. /// - `world` must be the same one used to initialize `query_state`. pub(crate) unsafe fn new>( world: UnsafeWorldCell<'w>, - query_state: &'s QueryState, + query_state: &'s QueryState, entity_list: EntityList, last_run: Tick, this_run: Tick, - ) -> QueryManyUniqueIter<'w, 's, D, F, I> { + ) -> QueryManyUniqueIter<'w, 's, D, F, C, I> { QueryManyUniqueIter(QueryManyIter::new( world, query_state, @@ -1904,8 +1919,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: EntitySetIterator> } } -impl<'w, 's, D: QueryData, F: QueryFilter, I: EntitySetIterator> Iterator - for QueryManyUniqueIter<'w, 's, D, F, I> +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, I: EntitySetIterator> Iterator + for QueryManyUniqueIter<'w, 's, D, F, C, I> { type Item = D::Item<'w, 's>; @@ -1913,7 +1928,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: EntitySetIterator> Iterator fn next(&mut self) -> Option { // SAFETY: Entities are guaranteed to be unique, thus do not alias. unsafe { - QueryManyIter::<'w, 's, D, F, I>::fetch_next_aliased_unchecked( + QueryManyIter::<'w, 's, D, F, C, I>::fetch_next_aliased_unchecked( &mut self.0.entity_iter, self.0.entities, self.0.tables, @@ -1932,19 +1947,19 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: EntitySetIterator> Iterator } // This is correct as [`QueryManyIter`] always returns `None` once exhausted. -impl<'w, 's, D: QueryData, F: QueryFilter, I: EntitySetIterator> FusedIterator - for QueryManyUniqueIter<'w, 's, D, F, I> +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, I: EntitySetIterator> FusedIterator + for QueryManyUniqueIter<'w, 's, D, F, C, I> { } // SAFETY: Fetching unique entities maintains uniqueness. -unsafe impl<'w, 's, F: QueryFilter, I: EntitySetIterator> EntitySetIterator - for QueryManyUniqueIter<'w, 's, Entity, F, I> +unsafe impl<'w, 's, F: QueryFilter, C: QueryCache, I: EntitySetIterator> EntitySetIterator + for QueryManyUniqueIter<'w, 's, Entity, F, C, I> { } -impl<'w, 's, D: QueryData, F: QueryFilter, I: EntitySetIterator> Debug - for QueryManyUniqueIter<'w, 's, D, F, I> +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, I: EntitySetIterator> Debug + for QueryManyUniqueIter<'w, 's, D, F, C, I> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("QueryManyUniqueIter").finish() @@ -1956,17 +1971,17 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: EntitySetIterator> Debug /// This struct is created by the [`sort`](QueryManyIter), [`sort_unstable`](QueryManyIter), /// [`sort_by`](QueryManyIter), [`sort_unstable_by`](QueryManyIter), [`sort_by_key`](QueryManyIter), /// [`sort_unstable_by_key`](QueryManyIter), and [`sort_by_cached_key`](QueryManyIter) methods of [`QueryManyIter`]. -pub struct QuerySortedManyIter<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> { +pub struct QuerySortedManyIter<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, I: Iterator> { entity_iter: I, entities: &'w Entities, tables: &'w Tables, archetypes: &'w Archetypes, fetch: D::Fetch<'w>, - query_state: &'s QueryState, + query_state: &'s QueryState, } -impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> - QuerySortedManyIter<'w, 's, D, F, I> +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, I: Iterator> + QuerySortedManyIter<'w, 's, D, F, C, I> { /// # Safety /// - `world` must have permission to access any of the components registered in `query_state`. @@ -1974,11 +1989,11 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> /// - `entity_list` must only contain unique entities or be empty. pub(crate) unsafe fn new>( world: UnsafeWorldCell<'w>, - query_state: &'s QueryState, + query_state: &'s QueryState, entity_list: EntityList, last_run: Tick, this_run: Tick, - ) -> QuerySortedManyIter<'w, 's, D, F, I> { + ) -> QuerySortedManyIter<'w, 's, D, F, C, I> { let fetch = D::init_fetch(world, &query_state.fetch_state, last_run, this_run); QuerySortedManyIter { query_state, @@ -2055,8 +2070,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> } } -impl<'w, 's, D: QueryData, F: QueryFilter, I: DoubleEndedIterator> - QuerySortedManyIter<'w, 's, D, F, I> +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, I: DoubleEndedIterator> + QuerySortedManyIter<'w, 's, D, F, C, I> { /// Get next result from the query #[inline(always)] @@ -2074,8 +2089,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: DoubleEndedIterator } } -impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, I: Iterator> Iterator - for QuerySortedManyIter<'w, 's, D, F, I> +impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, C: QueryCache, I: Iterator> Iterator + for QuerySortedManyIter<'w, 's, D, F, C, I> { type Item = D::Item<'w, 's>; @@ -2093,8 +2108,8 @@ impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, I: Iterator> I } } -impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, I: DoubleEndedIterator> - DoubleEndedIterator for QuerySortedManyIter<'w, 's, D, F, I> +impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, C: QueryCache, I: DoubleEndedIterator> + DoubleEndedIterator for QuerySortedManyIter<'w, 's, D, F, C, I> { #[inline(always)] fn next_back(&mut self) -> Option { @@ -2106,13 +2121,13 @@ impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, I: DoubleEndedIterator> - ExactSizeIterator for QuerySortedManyIter<'w, 's, D, F, I> +impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, C: QueryCache, I: ExactSizeIterator> + ExactSizeIterator for QuerySortedManyIter<'w, 's, D, F, C, I> { } -impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> Debug - for QuerySortedManyIter<'w, 's, D, F, I> +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, I: Iterator> Debug + for QuerySortedManyIter<'w, 's, D, F, C, I> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("QuerySortedManyIter").finish() @@ -2182,20 +2197,20 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> Debug /// [`Query`]: crate::system::Query /// [`Query::iter_combinations`]: crate::system::Query::iter_combinations /// [`Query::iter_combinations_mut`]: crate::system::Query::iter_combinations_mut -pub struct QueryCombinationIter<'w, 's, D: QueryData, F: QueryFilter, const K: usize> { +pub struct QueryCombinationIter<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, const K: usize> { tables: &'w Tables, archetypes: &'w Archetypes, - query_state: &'s QueryState, - cursors: [QueryIterationCursor<'w, 's, D, F>; K], + query_state: &'s QueryState, + cursors: [QueryIterationCursor<'w, 's, D, F, C>; K], } -impl<'w, 's, D: QueryData, F: QueryFilter, const K: usize> QueryCombinationIter<'w, 's, D, F, K> { +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, const K: usize> QueryCombinationIter<'w, 's, D, F, C, K> { /// # Safety /// - `world` must have permission to access any of the components registered in `query_state`. /// - `world` must be the same one used to initialize `query_state`. pub(crate) unsafe fn new( world: UnsafeWorldCell<'w>, - query_state: &'s QueryState, + query_state: &'s QueryState, last_run: Tick, this_run: Tick, ) -> Self { @@ -2203,10 +2218,10 @@ impl<'w, 's, D: QueryData, F: QueryFilter, const K: usize> QueryCombinationIter< // Initialize array with cursors. // There is no FromIterator on arrays, so instead initialize it manually with MaybeUninit - let mut array: MaybeUninit<[QueryIterationCursor<'w, 's, D, F>; K]> = MaybeUninit::uninit(); + let mut array: MaybeUninit<[QueryIterationCursor<'w, 's, D, F, C>; K]> = MaybeUninit::uninit(); let ptr = array .as_mut_ptr() - .cast::>(); + .cast::>(); ptr.write(QueryIterationCursor::init( world, query_state, @@ -2291,8 +2306,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter, const K: usize> QueryCombinationIter< // Iterator type is intentionally implemented only for read-only access. // Doing so for mutable references would be unsound, because calling `next` // multiple times would allow multiple owned references to the same data to exist. -impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, const K: usize> Iterator - for QueryCombinationIter<'w, 's, D, F, K> +impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, C: QueryCache, const K: usize> Iterator + for QueryCombinationIter<'w, 's, D, F, C, K> { type Item = [D::Item<'w, 's>; K]; @@ -2334,7 +2349,7 @@ impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, const K: usize> Iterator } } -impl<'w, 's, D: QueryData, F: QueryFilter> ExactSizeIterator for QueryIter<'w, 's, D, F> +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> ExactSizeIterator for QueryIter<'w, 's, D, F, C> where F: ArchetypeFilter, { @@ -2344,21 +2359,21 @@ where } // This is correct as [`QueryCombinationIter`] always returns `None` once exhausted. -impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, const K: usize> FusedIterator - for QueryCombinationIter<'w, 's, D, F, K> +impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, C: QueryCache, const K: usize> FusedIterator + for QueryCombinationIter<'w, 's, D, F, C, K> { } -impl<'w, 's, D: QueryData, F: QueryFilter, const K: usize> Debug - for QueryCombinationIter<'w, 's, D, F, K> +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, const K: usize> Debug + for QueryCombinationIter<'w, 's, D, F, C, K> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("QueryCombinationIter").finish() } } -struct QueryIterationCursor<'w, 's, D: QueryData, F: QueryFilter> { - // whether the query iteration is dense or not. Mirrors QueryState's `is_dense` field. +struct QueryIterationCursor<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> { + // whether the query iteration is dense or not. is_dense: bool, storage_id_iter: core::slice::Iter<'s, StorageId>, table_entities: &'w [Entity], @@ -2369,9 +2384,10 @@ struct QueryIterationCursor<'w, 's, D: QueryData, F: QueryFilter> { current_len: u32, // either table row or archetype index, depending on whether both `D`'s and `F`'s fetches are dense current_row: u32, + _cache: core::marker::PhantomData, } -impl Clone for QueryIterationCursor<'_, '_, D, F> { +impl Clone for QueryIterationCursor<'_, '_, D, F, C> { fn clone(&self) -> Self { Self { is_dense: self.is_dense, @@ -2382,17 +2398,18 @@ impl Clone for QueryIterationCursor<'_, '_, D, F> filter: self.filter.clone(), current_len: self.current_len, current_row: self.current_row, + _cache: core::marker::PhantomData } } } -impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> { +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache + 's> QueryIterationCursor<'w, 's, D, F, C> { /// # Safety /// - `world` must have permission to access any of the components registered in `query_state`. /// - `world` must be the same one used to initialize `query_state`. unsafe fn init_empty( world: UnsafeWorldCell<'w>, - query_state: &'s QueryState, + query_state: &'s QueryState, last_run: Tick, this_run: Tick, ) -> Self { @@ -2407,25 +2424,29 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> { /// - `world` must be the same one used to initialize `query_state`. unsafe fn init( world: UnsafeWorldCell<'w>, - query_state: &'s QueryState, + query_state: &'s QueryState, last_run: Tick, this_run: Tick, ) -> Self { let fetch = D::init_fetch(world, &query_state.fetch_state, last_run, this_run); let filter = F::init_fetch(world, &query_state.filter_state, last_run, this_run); + let mut storage_ids = Vec::new(); + let iteration_data = query_state.cache.iteration_data(query_state, world, &mut storage_ids); QueryIterationCursor { fetch, filter, table_entities: &[], archetype_entities: &[], - storage_id_iter: query_state.matched_storage_ids.iter(), - is_dense: query_state.is_dense, + storage_id_iter: iteration_data.storage_ids, + is_dense: iteration_data.is_dense, current_len: 0, current_row: 0, + _cache: core::marker::PhantomData, } } - fn reborrow(&mut self) -> QueryIterationCursor<'_, 's, D, F> { + + fn reborrow(&mut self) -> QueryIterationCursor<'_, 's, D, F, C> { QueryIterationCursor { is_dense: self.is_dense, fetch: D::shrink_fetch(self.fetch.clone()), @@ -2435,6 +2456,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> { storage_id_iter: self.storage_id_iter.clone(), current_len: self.current_len, current_row: self.current_row, + _cache: core::marker::PhantomData } } @@ -2444,7 +2466,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> { /// The result of `next` and any previous calls to `peek_last` with this row must have been /// dropped to prevent aliasing mutable references. #[inline] - unsafe fn peek_last(&mut self, query_state: &'s QueryState) -> Option> { + unsafe fn peek_last(&mut self, query_state: &'s QueryState) -> Option> { if self.current_row > 0 { let index = self.current_row - 1; if self.is_dense { @@ -2512,7 +2534,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> { &mut self, tables: &'w Tables, archetypes: &'w Archetypes, - query_state: &'s QueryState, + query_state: &'s QueryState, ) -> Option> { if self.is_dense { loop { diff --git a/crates/bevy_ecs/src/query/mod.rs b/crates/bevy_ecs/src/query/mod.rs index fb8899fd5de87..d2c844caa2659 100644 --- a/crates/bevy_ecs/src/query/mod.rs +++ b/crates/bevy_ecs/src/query/mod.rs @@ -9,10 +9,12 @@ mod iter; mod par_iter; mod state; mod world_query; +mod cache; pub use access::*; pub use bevy_ecs_macros::{QueryData, QueryFilter}; pub use builder::*; +pub use cache::*; pub use error::*; pub use fetch::*; pub use filter::*; @@ -123,6 +125,7 @@ mod tests { use bevy_ecs_macros::QueryFilter; use core::{any::type_name, fmt::Debug, hash::Hash}; use std::{collections::HashSet, println}; + use bevy_ecs::query::CacheState; #[derive(Component, Debug, Hash, Eq, PartialEq, Clone, Copy, PartialOrd, Ord)] struct A(usize); @@ -169,7 +172,7 @@ mod tests { F: ArchetypeFilter, { let mut query = world.query_filtered::(); - let query_type = type_name::>(); + let query_type = type_name::>(); let iter = query.iter_combinations::(world); assert_all_sizes_iterator_equal(iter, expected_size, 0, query_type); let iter = query.iter_combinations::(world); @@ -183,7 +186,7 @@ mod tests { F: ArchetypeFilter, { let mut query = world.query_filtered::(); - let query_type = type_name::>(); + let query_type = type_name::>(); assert_all_exact_sizes_iterator_equal(query.iter(world), expected_size, 0, query_type); assert_all_exact_sizes_iterator_equal(query.iter(world), expected_size, 1, query_type); assert_all_exact_sizes_iterator_equal(query.iter(world), expected_size, 5, query_type); diff --git a/crates/bevy_ecs/src/query/par_iter.rs b/crates/bevy_ecs/src/query/par_iter.rs index 6242f8e39b8be..336d5e84cfd77 100644 --- a/crates/bevy_ecs/src/query/par_iter.rs +++ b/crates/bevy_ecs/src/query/par_iter.rs @@ -8,20 +8,25 @@ use crate::{ use super::{QueryData, QueryFilter, QueryItem, QueryState, ReadOnlyQueryData}; use alloc::vec::Vec; +use bevy_ecs::query::QueryCache; +#[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))] +use bevy_ecs::query::QueryIterationData; /// A parallel iterator over query results of a [`Query`](crate::system::Query). /// /// This struct is created by the [`Query::par_iter`](crate::system::Query::par_iter) and /// [`Query::par_iter_mut`](crate::system::Query::par_iter_mut) methods. -pub struct QueryParIter<'w, 's, D: QueryData, F: QueryFilter> { +pub struct QueryParIter<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> { pub(crate) world: UnsafeWorldCell<'w>, - pub(crate) state: &'s QueryState, + pub(crate) state: &'s QueryState, + #[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))] + pub(crate) iteration_data: C::IterationData<'s>, pub(crate) last_run: Tick, pub(crate) this_run: Tick, pub(crate) batching_strategy: BatchingStrategy, } -impl<'w, 's, D: QueryData, F: QueryFilter> QueryParIter<'w, 's, D, F> { +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> QueryParIter<'w, 's, D, F, C> { /// Changes the batching strategy used when iterating. /// /// For more information on how this affects the resultant iteration, see @@ -132,8 +137,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryParIter<'w, 's, D, F> { #[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))] fn get_batch_size(&self, thread_count: usize) -> u32 { let max_items = || { - let id_iter = self.state.matched_storage_ids.iter(); - if self.state.is_dense { + let id_iter = self.iteration_data.storage_ids(); + if self.iteration_data.is_dense() { // SAFETY: We only access table metadata. let tables = unsafe { &self.world.world_metadata().storages().tables }; id_iter @@ -161,17 +166,17 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryParIter<'w, 's, D, F> { /// /// [`Entity`]: crate::entity::Entity /// [`Query::par_iter_many`]: crate::system::Query::par_iter_many -pub struct QueryParManyIter<'w, 's, D: QueryData, F: QueryFilter, E: EntityEquivalent> { +pub struct QueryParManyIter<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, E: EntityEquivalent> { pub(crate) world: UnsafeWorldCell<'w>, - pub(crate) state: &'s QueryState, + pub(crate) state: &'s QueryState, pub(crate) entity_list: Vec, pub(crate) last_run: Tick, pub(crate) this_run: Tick, pub(crate) batching_strategy: BatchingStrategy, } -impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, E: EntityEquivalent + Sync> - QueryParManyIter<'w, 's, D, F, E> +impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, C: QueryCache, E: EntityEquivalent + Sync> + QueryParManyIter<'w, 's, D, F, C, E> { /// Changes the batching strategy used when iterating. /// @@ -315,18 +320,18 @@ impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, E: EntityEquivalent + Sync> /// [`EntitySet`]: crate::entity::EntitySet /// [`Query::par_iter_many_unique`]: crate::system::Query::par_iter_many_unique /// [`Query::par_iter_many_unique_mut`]: crate::system::Query::par_iter_many_unique_mut -pub struct QueryParManyUniqueIter<'w, 's, D: QueryData, F: QueryFilter, E: EntityEquivalent + Sync> +pub struct QueryParManyUniqueIter<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, E: EntityEquivalent + Sync> { pub(crate) world: UnsafeWorldCell<'w>, - pub(crate) state: &'s QueryState, + pub(crate) state: &'s QueryState, pub(crate) entity_list: UniqueEntityEquivalentVec, pub(crate) last_run: Tick, pub(crate) this_run: Tick, pub(crate) batching_strategy: BatchingStrategy, } -impl<'w, 's, D: QueryData, F: QueryFilter, E: EntityEquivalent + Sync> - QueryParManyUniqueIter<'w, 's, D, F, E> +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, E: EntityEquivalent + Sync> + QueryParManyUniqueIter<'w, 's, D, F, C, E> { /// Changes the batching strategy used when iterating. /// diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 91a979fa2e5e7..eeb492fb25c19 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -1,12 +1,11 @@ use crate::{ - archetype::{Archetype, ArchetypeGeneration, ArchetypeId}, + archetype::{Archetype, ArchetypeId}, change_detection::Tick, - component::ComponentId, entity::{Entity, EntityEquivalent, EntitySet, UniqueEntityArray}, entity_disabling::DefaultQueryFilters, prelude::FromWorld, query::{FilteredAccess, QueryCombinationIter, QueryIter, QueryParIter, WorldQuery}, - storage::{SparseSetIndex, TableId}, + storage::{TableId}, system::Query, world::{unsafe_world_cell::UnsafeWorldCell, World, WorldId}, }; @@ -14,14 +13,13 @@ use crate::{ #[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))] use crate::entity::UniqueEntityEquivalentSlice; -use alloc::vec::Vec; use bevy_utils::prelude::DebugName; use core::{fmt, ptr}; -use fixedbitset::FixedBitSet; -use log::warn; #[cfg(feature = "trace")] use tracing::Span; - +use bevy_ecs::query::cache::{CacheState, QueryCache}; +#[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))] +use bevy_ecs::query::IterationData; use super::{ NopWorldQuery, QueryBuilder, QueryData, QueryEntityError, QueryFilter, QueryManyIter, QueryManyUniqueIter, QuerySingleError, ROQueryItem, ReadOnlyQueryData, @@ -65,39 +63,33 @@ pub(super) union StorageId { // SAFETY NOTE: // Do not add any new fields that use the `D` or `F` generic parameters as this may // make `QueryState::as_transmuted_state` unsound if not done with care. -pub struct QueryState { +pub struct QueryState { world_id: WorldId, - pub(crate) archetype_generation: ArchetypeGeneration, - /// Metadata about the [`Table`](crate::storage::Table)s matched by this query. - pub(crate) matched_tables: FixedBitSet, - /// Metadata about the [`Archetype`]s matched by this query. - pub(crate) matched_archetypes: FixedBitSet, /// [`FilteredAccess`] computed by combining the `D` and `F` access. Used to check which other queries /// this query can run in parallel with. /// Note that because we do a zero-cost reference conversion in `Query::as_readonly`, /// the access for a read-only query may include accesses for the original mutable version, /// but the `Query` does not have exclusive access to those components. pub(crate) component_access: FilteredAccess, - // NOTE: we maintain both a bitset and a vec because iterating the vec is faster - pub(super) matched_storage_ids: Vec, - // Represents whether this query iteration is dense or not. When this is true - // `matched_storage_ids` stores `TableId`s, otherwise it stores `ArchetypeId`s. - pub(super) is_dense: bool, pub(crate) fetch_state: D::State, pub(crate) filter_state: F::State, #[cfg(feature = "trace")] par_iter_span: Span, + /// Cache that can store archetypes and tables that match the query so that we can iterate through them faster. + /// This field MUST BE LAST. + pub(crate) cache: C, } -impl fmt::Debug for QueryState { + +impl fmt::Debug for QueryState { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("QueryState") .field("world_id", &self.world_id) - .field("matched_table_count", &self.matched_tables.count_ones(..)) - .field( - "matched_archetype_count", - &self.matched_archetypes.count_ones(..), - ) + // .field("matched_table_count", &self.matched_tables.count_ones(..)) + // .field( + // "matched_archetype_count", + // &self.matched_archetypes.count_ones(..), + // ) .finish_non_exhaustive() } } @@ -108,9 +100,10 @@ impl FromWorld for QueryState { } } -impl QueryState { +impl QueryState { + /// Converts this `QueryState` reference to a `QueryState` that does not access anything mutably. - pub fn as_readonly(&self) -> &QueryState { + pub fn as_readonly(&self) -> &QueryState { // SAFETY: invariant on `WorldQuery` trait upholds that `D::ReadOnly` and `F::ReadOnly` // have a subset of the access, and match the exact same archetypes/tables as `D`/`F` respectively. unsafe { self.as_transmuted_state::() } @@ -121,7 +114,7 @@ impl QueryState { /// /// This doesn't use `NopWorldQuery` as it loses filter functionality, for example /// `NopWorldQuery>` is functionally equivalent to `With`. - pub(crate) fn as_nop(&self) -> &QueryState, F> { + pub(crate) fn as_nop(&self) -> &QueryState, F, C> { // SAFETY: `NopWorldQuery` doesn't have any accesses and defers to // `D` for table/archetype matching unsafe { self.as_transmuted_state::, F>() } @@ -141,8 +134,8 @@ impl QueryState { NewF: QueryFilter, >( &self, - ) -> &QueryState { - &*ptr::from_ref(self).cast::>() + ) -> &QueryState { + &*ptr::from_ref(self).cast::>() } /// Returns the components accessed by this query. @@ -150,16 +143,6 @@ impl QueryState { &self.component_access } - /// Returns the tables matched by this query. - pub fn matched_tables(&self) -> impl Iterator + '_ { - self.matched_tables.ones().map(TableId::from_usize) - } - - /// Returns the archetypes matched by this query. - pub fn matched_archetypes(&self) -> impl Iterator + '_ { - self.matched_archetypes.ones().map(ArchetypeId::new) - } - /// Creates a new [`QueryState`] from a given [`World`] and inherits the result of `world.id()`. pub fn new(world: &mut World) -> Self { let mut state = Self::new_uninitialized(world); @@ -223,25 +206,18 @@ impl QueryState { // properly considered in a global "cross-query" context (both within systems and across systems). component_access.extend(&filter_component_access); - // For queries without dynamic filters the dense-ness of the query is equal to the dense-ness - // of its static type parameters. - let mut is_dense = D::IS_DENSE && F::IS_DENSE; - if let Some(default_filters) = world.get_resource::() { default_filters.modify_access(&mut component_access); - is_dense &= default_filters.is_dense(world.components()); } + let cache = C::uninitialized::(world); + Self { world_id: world.id(), - archetype_generation: ArchetypeGeneration::initial(), - matched_storage_ids: Vec::new(), - is_dense, fetch_state, filter_state, component_access, - matched_tables: Default::default(), - matched_archetypes: Default::default(), + cache, #[cfg(feature = "trace")] par_iter_span: tracing::info_span!( "par_for_each", @@ -266,24 +242,19 @@ impl QueryState { let mut component_access = builder.access().clone(); - // For dynamic queries the dense-ness is given by the query builder. - let mut is_dense = builder.is_dense(); if let Some(default_filters) = builder.world().get_resource::() { default_filters.modify_access(&mut component_access); - is_dense &= default_filters.is_dense(builder.world().components()); } + let cache = C::uninitialized::(builder.world()); + let mut state = Self { world_id: builder.world().id(), - archetype_generation: ArchetypeGeneration::initial(), - matched_storage_ids: Vec::new(), - is_dense, fetch_state, filter_state, component_access, - matched_tables: Default::default(), - matched_archetypes: Default::default(), + cache, #[cfg(feature = "trace")] par_iter_span: tracing::info_span!( "par_for_each", @@ -298,7 +269,7 @@ impl QueryState { /// Creates a [`Query`] from the given [`QueryState`] and [`World`]. /// /// This will create read-only queries, see [`Self::query_mut`] for mutable queries. - pub fn query<'w, 's>(&'s mut self, world: &'w World) -> Query<'w, 's, D::ReadOnly, F> { + pub fn query<'w, 's>(&'s mut self, world: &'w World) -> Query<'w, 's, D::ReadOnly, F, C> { self.update_archetypes(world); self.query_manual(world) } @@ -314,7 +285,7 @@ impl QueryState { /// access to `self`. /// /// This will create read-only queries, see [`Self::query_mut`] for mutable queries. - pub fn query_manual<'w, 's>(&'s self, world: &'w World) -> Query<'w, 's, D::ReadOnly, F> { + pub fn query_manual<'w, 's>(&'s self, world: &'w World) -> Query<'w, 's, D::ReadOnly, F, C> { self.validate_world(world.id()); // SAFETY: // - We have read access to the entire world, and we call `as_readonly()` so the query only performs read access. @@ -326,7 +297,7 @@ impl QueryState { } /// Creates a [`Query`] from the given [`QueryState`] and [`World`]. - pub fn query_mut<'w, 's>(&'s mut self, world: &'w mut World) -> Query<'w, 's, D, F> { + pub fn query_mut<'w, 's>(&'s mut self, world: &'w mut World) -> Query<'w, 's, D, F, C> { let last_run = world.last_change_tick(); let this_run = world.change_tick(); // SAFETY: We have exclusive access to the entire world. @@ -342,7 +313,7 @@ impl QueryState { pub unsafe fn query_unchecked<'w, 's>( &'s mut self, world: UnsafeWorldCell<'w>, - ) -> Query<'w, 's, D, F> { + ) -> Query<'w, 's, D, F, C> { self.update_archetypes_unsafe_world_cell(world); // SAFETY: Caller ensures we have the required access unsafe { self.query_unchecked_manual(world) } @@ -367,7 +338,7 @@ impl QueryState { pub unsafe fn query_unchecked_manual<'w, 's>( &'s self, world: UnsafeWorldCell<'w>, - ) -> Query<'w, 's, D, F> { + ) -> Query<'w, 's, D, F, C> { let last_run = world.last_change_tick(); let this_run = world.change_tick(); // SAFETY: @@ -387,7 +358,7 @@ impl QueryState { world: UnsafeWorldCell<'w>, last_run: Tick, this_run: Tick, - ) -> Query<'w, 's, D, F> { + ) -> Query<'w, 's, D, F, C> { self.update_archetypes_unsafe_world_cell(world); // SAFETY: // - The caller ensured we have the correct access to the world. @@ -416,7 +387,7 @@ impl QueryState { world: UnsafeWorldCell<'w>, last_run: Tick, this_run: Tick, - ) -> Query<'w, 's, D, F> { + ) -> Query<'w, 's, D, F, C> { // SAFETY: // - The caller ensured we have the correct access to the world. // - The caller ensured that the world matches. @@ -472,91 +443,6 @@ impl QueryState { .contains(entity) } - /// Updates the state's internal view of the [`World`]'s archetypes. If this is not called before querying data, - /// the results may not accurately reflect what is in the `world`. - /// - /// This is only required if a `manual` method (such as [`Self::get_manual`]) is being called, and it only needs to - /// be called if the `world` has been structurally mutated (i.e. added/removed a component or resource). Users using - /// non-`manual` methods such as [`QueryState::get`] do not need to call this as it will be automatically called for them. - /// - /// If you have an [`UnsafeWorldCell`] instead of `&World`, consider using [`QueryState::update_archetypes_unsafe_world_cell`]. - /// - /// # Panics - /// - /// If `world` does not match the one used to call `QueryState::new` for this instance. - #[inline] - pub fn update_archetypes(&mut self, world: &World) { - self.update_archetypes_unsafe_world_cell(world.as_unsafe_world_cell_readonly()); - } - - /// Updates the state's internal view of the `world`'s archetypes. If this is not called before querying data, - /// the results may not accurately reflect what is in the `world`. - /// - /// This is only required if a `manual` method (such as [`Self::get_manual`]) is being called, and it only needs to - /// be called if the `world` has been structurally mutated (i.e. added/removed a component or resource). Users using - /// non-`manual` methods such as [`QueryState::get`] do not need to call this as it will be automatically called for them. - /// - /// # Note - /// - /// This method only accesses world metadata. - /// - /// # Panics - /// - /// If `world` does not match the one used to call `QueryState::new` for this instance. - pub fn update_archetypes_unsafe_world_cell(&mut self, world: UnsafeWorldCell) { - self.validate_world(world.id()); - if self.component_access.required.is_empty() { - let archetypes = world.archetypes(); - let old_generation = - core::mem::replace(&mut self.archetype_generation, archetypes.generation()); - - for archetype in &archetypes[old_generation..] { - // SAFETY: The validate_world call ensures that the world is the same the QueryState - // was initialized from. - unsafe { - self.new_archetype(archetype); - } - } - } else { - // skip if we are already up to date - if self.archetype_generation == world.archetypes().generation() { - return; - } - // if there are required components, we can optimize by only iterating through archetypes - // that contain at least one of the required components - let potential_archetypes = self - .component_access - .required - .ones() - .filter_map(|idx| { - let component_id = ComponentId::get_sparse_set_index(idx); - world - .archetypes() - .component_index() - .get(&component_id) - .map(|index| index.keys()) - }) - // select the component with the fewest archetypes - .min_by_key(ExactSizeIterator::len); - if let Some(archetypes) = potential_archetypes { - for archetype_id in archetypes { - // exclude archetypes that have already been processed - if archetype_id < &self.archetype_generation.0 { - continue; - } - // SAFETY: get_potential_archetypes only returns archetype ids that are valid for the world - let archetype = &world.archetypes()[*archetype_id]; - // SAFETY: The validate_world call ensures that the world is the same the QueryState - // was initialized from. - unsafe { - self.new_archetype(archetype); - } - } - } - self.archetype_generation = world.archetypes().generation(); - } - } - /// # Panics /// /// If `world_id` does not match the [`World`] used to call `QueryState::new` for this instance. @@ -578,50 +464,6 @@ impl QueryState { } } - /// Update the current [`QueryState`] with information from the provided [`Archetype`] - /// (if applicable, i.e. if the archetype has any intersecting [`ComponentId`] with the current [`QueryState`]). - /// - /// # Safety - /// `archetype` must be from the `World` this state was initialized from. - pub unsafe fn new_archetype(&mut self, archetype: &Archetype) { - if D::matches_component_set(&self.fetch_state, &|id| archetype.contains(id)) - && F::matches_component_set(&self.filter_state, &|id| archetype.contains(id)) - && self.matches_component_set(&|id| archetype.contains(id)) - { - let archetype_index = archetype.id().index(); - if !self.matched_archetypes.contains(archetype_index) { - self.matched_archetypes.grow_and_insert(archetype_index); - if !self.is_dense { - self.matched_storage_ids.push(StorageId { - archetype_id: archetype.id(), - }); - } - } - let table_index = archetype.table_id().as_usize(); - if !self.matched_tables.contains(table_index) { - self.matched_tables.grow_and_insert(table_index); - if self.is_dense { - self.matched_storage_ids.push(StorageId { - table_id: archetype.table_id(), - }); - } - } - } - } - - /// Returns `true` if this query matches a set of components. Otherwise, returns `false`. - pub fn matches_component_set(&self, set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { - self.component_access.filter_sets.iter().any(|set| { - set.with - .ones() - .all(|index| set_contains_id(ComponentId::get_sparse_set_index(index))) - && set - .without - .ones() - .all(|index| !set_contains_id(ComponentId::get_sparse_set_index(index))) - }) - } - /// Use this to transform a [`QueryState`] into a more generic [`QueryState`]. /// This can be useful for passing to another function that might take the more general form. /// See [`Query::transmute_lens`](crate::system::Query::transmute_lens) for more details. @@ -633,7 +475,7 @@ impl QueryState { pub fn transmute<'a, NewD: QueryData>( &self, world: impl Into>, - ) -> QueryState { + ) -> QueryState { self.transmute_filtered::(world.into()) } @@ -644,7 +486,7 @@ impl QueryState { pub fn transmute_filtered<'a, NewD: QueryData, NewF: QueryFilter>( &self, world: impl Into>, - ) -> QueryState { + ) -> QueryState { let world = world.into(); self.validate_world(world.id()); @@ -679,14 +521,10 @@ impl QueryState { QueryState { world_id: self.world_id, - archetype_generation: self.archetype_generation, - matched_storage_ids: self.matched_storage_ids.clone(), - is_dense: self.is_dense, + cache: self.cache.clone(), fetch_state, filter_state, component_access: self_access, - matched_tables: self.matched_tables.clone(), - matched_archetypes: self.matched_archetypes.clone(), #[cfg(feature = "trace")] par_iter_span: tracing::info_span!( "par_for_each", @@ -718,8 +556,8 @@ impl QueryState { pub fn join<'a, OtherD: QueryData, NewD: QueryData>( &self, world: impl Into>, - other: &QueryState, - ) -> QueryState { + other: &QueryState, + ) -> QueryState { self.join_filtered::<_, (), NewD, ()>(world, other) } @@ -738,8 +576,8 @@ impl QueryState { >( &self, world: impl Into>, - other: &QueryState, - ) -> QueryState { + other: &QueryState, + ) -> QueryState { if self.world_id != other.world_id { panic!("Joining queries initialized on different worlds is not allowed."); } @@ -796,44 +634,12 @@ impl QueryState { DebugName::type_name::<(NewD, NewF)>(), DebugName::type_name::<(D, F)>(), DebugName::type_name::<(OtherD, OtherF)>() ); - if self.archetype_generation != other.archetype_generation { - warn!("You have tried to join queries with different archetype_generations. This could lead to unpredictable results."); - } - - // the join is dense of both the queries were dense. - let is_dense = self.is_dense && other.is_dense; - - // take the intersection of the matched ids - let mut matched_tables = self.matched_tables.clone(); - let mut matched_archetypes = self.matched_archetypes.clone(); - matched_tables.intersect_with(&other.matched_tables); - matched_archetypes.intersect_with(&other.matched_archetypes); - let matched_storage_ids = if is_dense { - matched_tables - .ones() - .map(|id| StorageId { - table_id: TableId::from_usize(id), - }) - .collect() - } else { - matched_archetypes - .ones() - .map(|id| StorageId { - archetype_id: ArchetypeId::new(id), - }) - .collect() - }; - QueryState { world_id: self.world_id, - archetype_generation: self.archetype_generation, - matched_storage_ids, - is_dense, fetch_state: new_fetch_state, filter_state: new_filter_state, component_access: joined_component_access, - matched_tables, - matched_archetypes, + cache: self.cache.join(&other.cache), #[cfg(feature = "trace")] par_iter_span: tracing::info_span!( "par_for_each", @@ -1090,7 +896,7 @@ impl QueryState { /// If you need to iterate multiple times at once but get borrowing errors, /// consider using [`Self::update_archetypes`] followed by multiple [`Self::iter_manual`] calls. #[inline] - pub fn iter<'w, 's>(&'s mut self, world: &'w World) -> QueryIter<'w, 's, D::ReadOnly, F> { + pub fn iter<'w, 's>(&'s mut self, world: &'w World) -> QueryIter<'w, 's, D::ReadOnly, F, C> { self.query(world).into_iter() } @@ -1099,7 +905,7 @@ impl QueryState { /// This iterator is always guaranteed to return results from each matching entity once and only once. /// Iteration order is not guaranteed. #[inline] - pub fn iter_mut<'w, 's>(&'s mut self, world: &'w mut World) -> QueryIter<'w, 's, D, F> { + pub fn iter_mut<'w, 's>(&'s mut self, world: &'w mut World) -> QueryIter<'w, 's, D, F, C> { self.query_mut(world).into_iter() } @@ -1111,7 +917,7 @@ impl QueryState { /// /// This can only be called for read-only queries. #[inline] - pub fn iter_manual<'w, 's>(&'s self, world: &'w World) -> QueryIter<'w, 's, D::ReadOnly, F> { + pub fn iter_manual<'w, 's>(&'s self, world: &'w World) -> QueryIter<'w, 's, D::ReadOnly, F, C> { self.query_manual(world).into_iter() } @@ -1143,7 +949,7 @@ impl QueryState { pub fn iter_combinations<'w, 's, const K: usize>( &'s mut self, world: &'w World, - ) -> QueryCombinationIter<'w, 's, D::ReadOnly, F, K> { + ) -> QueryCombinationIter<'w, 's, D::ReadOnly, F, C, K> { self.query(world).iter_combinations_inner() } @@ -1168,7 +974,7 @@ impl QueryState { pub fn iter_combinations_mut<'w, 's, const K: usize>( &'s mut self, world: &'w mut World, - ) -> QueryCombinationIter<'w, 's, D, F, K> { + ) -> QueryCombinationIter<'w, 's, D, F, C, K> { self.query_mut(world).iter_combinations_inner() } @@ -1188,7 +994,7 @@ impl QueryState { &'s mut self, world: &'w World, entities: EntityList, - ) -> QueryManyIter<'w, 's, D::ReadOnly, F, EntityList::IntoIter> { + ) -> QueryManyIter<'w, 's, D::ReadOnly, F, C, EntityList::IntoIter> { self.query(world).iter_many_inner(entities) } @@ -1211,7 +1017,7 @@ impl QueryState { &'s self, world: &'w World, entities: EntityList, - ) -> QueryManyIter<'w, 's, D::ReadOnly, F, EntityList::IntoIter> { + ) -> QueryManyIter<'w, 's, D::ReadOnly, F, C, EntityList::IntoIter> { self.query_manual(world).iter_many_inner(entities) } @@ -1224,7 +1030,7 @@ impl QueryState { &'s mut self, world: &'w mut World, entities: EntityList, - ) -> QueryManyIter<'w, 's, D, F, EntityList::IntoIter> { + ) -> QueryManyIter<'w, 's, D, F, C, EntityList::IntoIter> { self.query_mut(world).iter_many_inner(entities) } @@ -1241,7 +1047,7 @@ impl QueryState { &'s mut self, world: &'w World, entities: EntityList, - ) -> QueryManyUniqueIter<'w, 's, D::ReadOnly, F, EntityList::IntoIter> { + ) -> QueryManyUniqueIter<'w, 's, D::ReadOnly, F, C, EntityList::IntoIter> { self.query(world).iter_many_unique_inner(entities) } @@ -1265,7 +1071,7 @@ impl QueryState { &'s self, world: &'w World, entities: EntityList, - ) -> QueryManyUniqueIter<'w, 's, D::ReadOnly, F, EntityList::IntoIter> { + ) -> QueryManyUniqueIter<'w, 's, D::ReadOnly, F, C, EntityList::IntoIter> { self.query_manual(world).iter_many_unique_inner(entities) } @@ -1278,7 +1084,7 @@ impl QueryState { &'s mut self, world: &'w mut World, entities: EntityList, - ) -> QueryManyUniqueIter<'w, 's, D, F, EntityList::IntoIter> { + ) -> QueryManyUniqueIter<'w, 's, D, F, C, EntityList::IntoIter> { self.query_mut(world).iter_many_unique_inner(entities) } /// Returns an [`Iterator`] over the query results for the given [`World`]. @@ -1294,7 +1100,7 @@ impl QueryState { pub unsafe fn iter_unchecked<'w, 's>( &'s mut self, world: UnsafeWorldCell<'w>, - ) -> QueryIter<'w, 's, D, F> { + ) -> QueryIter<'w, 's, D, F, C> { self.query_unchecked(world).into_iter() } @@ -1313,7 +1119,7 @@ impl QueryState { pub unsafe fn iter_combinations_unchecked<'w, 's, const K: usize>( &'s mut self, world: UnsafeWorldCell<'w>, - ) -> QueryCombinationIter<'w, 's, D, F, K> { + ) -> QueryCombinationIter<'w, 's, D, F, C, K> { self.query_unchecked(world).iter_combinations_inner() } @@ -1329,7 +1135,7 @@ impl QueryState { pub fn par_iter<'w, 's>( &'s mut self, world: &'w World, - ) -> QueryParIter<'w, 's, D::ReadOnly, F> { + ) -> QueryParIter<'w, 's, D::ReadOnly, F, C> { self.query(world).par_iter_inner() } @@ -1378,7 +1184,7 @@ impl QueryState { /// [`par_iter`]: Self::par_iter /// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool #[inline] - pub fn par_iter_mut<'w, 's>(&'s mut self, world: &'w mut World) -> QueryParIter<'w, 's, D, F> { + pub fn par_iter_mut<'w, 's>(&'s mut self, world: &'w mut World) -> QueryParIter<'w, 's, D, F, C> { self.query_mut(world).par_iter_inner() } @@ -1415,6 +1221,7 @@ impl QueryState { // QueryIter, QueryIterationCursor, QueryManyIter, QueryCombinationIter,QueryState::par_fold_init_unchecked_manual, // QueryState::par_many_fold_init_unchecked_manual, QueryState::par_many_unique_fold_init_unchecked_manual use arrayvec::ArrayVec; + let iteration_data = self.iteration_data(world); bevy_tasks::ComputeTaskPool::get().scope(|scope| { // SAFETY: We only access table data that has been registered in `self.component_access`. @@ -1463,14 +1270,14 @@ impl QueryState { }; let storage_entity_count = |storage_id: StorageId| -> u32 { - if self.is_dense { + if iteration_data.is_dense() { tables[storage_id.table_id].entity_count() } else { archetypes[storage_id.archetype_id].len() } }; - for storage_id in &self.matched_storage_ids { + for storage_id in &iteration_data.storage_ids() { let count = storage_entity_count(*storage_id); // skip empty storage @@ -1558,7 +1365,7 @@ impl QueryState { } } -impl QueryState { +impl QueryState { /// Runs `func` on each read-only query result in parallel for the given [`Entity`] list, /// where the last change and the current change tick are given. This is faster than the equivalent /// `iter_many()` method, but cannot be chained like a normal [`Iterator`]. @@ -1621,7 +1428,7 @@ impl QueryState { } } -impl QueryState { +impl QueryState { /// Returns a single immutable query result when there is exactly one entity matching /// the query. /// @@ -1766,7 +1573,7 @@ impl QueryState { } } -impl From> for QueryState { +impl From> for QueryState { fn from(mut value: QueryBuilder) -> Self { QueryState::from_builder(&mut value) } @@ -2212,7 +2019,7 @@ mod tests { let mut query = QueryState::<()>::new(&mut world); // There are no sparse components involved thus the query is dense - assert!(query.is_dense); + assert!(query.cache.is_dense); assert_eq!(3, query.query(&world).count()); world.register_disabling_component::(); @@ -2220,7 +2027,7 @@ mod tests { let mut query = QueryState::<()>::new(&mut world); // The query doesn't ask for sparse components, but the default filters adds // a sparse components thus it is NOT dense - assert!(!query.is_dense); + assert!(!query.cache.is_dense); assert_eq!(1, query.query(&world).count()); let mut df = DefaultQueryFilters::from_world(&mut world); @@ -2229,12 +2036,12 @@ mod tests { let mut query = QueryState::<()>::new(&mut world); // If the filter is instead a table components, the query can still be dense - assert!(query.is_dense); + assert!(query.cache.is_dense); assert_eq!(1, query.query(&world).count()); let mut query = QueryState::<&Sparse>::new(&mut world); // But only if the original query was dense - assert!(!query.is_dense); + assert!(!query.cache.is_dense); assert_eq!(1, query.query(&world).count()); } } diff --git a/crates/bevy_ecs/src/query/world_query.rs b/crates/bevy_ecs/src/query/world_query.rs index fef5b0257f167..0bcfb8cf36f6d 100644 --- a/crates/bevy_ecs/src/query/world_query.rs +++ b/crates/bevy_ecs/src/query/world_query.rs @@ -8,6 +8,77 @@ use crate::{ }; use variadics_please::all_tuples; +/* + +Queries in bevy are driven a lot by the Type. +We compute a QueryState that is an index of the tables/entities to return, but the QueryState is mostly just a Tuple of the inner QueryStates. +In Flecs, the QueryState contains a graph, that can then be optimized. +Our equivalent would be to have a WorldQuery::add_to_query_graph method that lets each type build the inner QueryState graph. + +We want to support Query<(Mass, Parent)>: match each entity with Mass where the parent matches D, F +Query<(Entity, Any>): match each entity where any children match D, F + +// all Components and Relationships would have extra generics available in the query? +QueryWithVariables<(SpaceShip, DockedTo, Planet) + +So we could compute a graph, and then: +- if the graph is simple (doesn't consider other entities than $this, i.e. is only SingleEntityData), then we just evaluate each `matches_id` in order. +- if the graph is more complex, we would do a traversal with backtracking: `Query<(Mass, Parent)>` + - does this table have Mass and ChildOf? in that case that table is matched + - for each entity in that table, fetch the Parent entity. Does the Parent entity match D, F ? In that case return $this. + +Uncached queries: +- observers usually trigger on a given component +- knowing this, all other queries in an observer could be uncached queries since they can use the ComponentIndex to quickly find the subset of entities to target. +- how would they know the component from the observer? we can if we do query observers +-> have a step to check if the archetype/table matches the query +-> have a step to check if the specific entity matches the query. + +How to express the Cache? +Maybe a QueryCache trait, implemented for StateCache or for (). +If cache is present, then use it. Else don't. + + + +Queries that are less tied to the type system. +- they update a QueryPlan that can get compiled and optimized. + +QueryState::from_typed +QueryState::from_builder + +Query Observers: +- Enter>: emit as soon as we match the query +- Exit>: emit as soon as we stop matching the query +-> the query would be uncached +-> have to evaluate query multiple times: evaluate the query once before adding a component, and then once after. +- how do we evaluate a query in an uncached manner? + - if archetypal and no variables: can use our current logic + - if not: can still filter for the archetypal parts of the query + - call matches_set on the entity to check if it matches the archetypes + +UncachedQueries: +- contains QueryPlan (which currently is only the component_access, but we could have nested access) +- + +CachedQueries = QueryPlan + MatchedArchetypes +-> later we could optimize this to cache a subset of a QueryPlan + +Query<(&A, &mut B), With> -> creates a QueryPlan that has (&A, &mut B), With +Query<(&A, &mut B) + + +- We go from QueryBuilder::with(component_id) to Query by transmuting (which gives you a convenient API) + But maybe we should have the Query contain an untyped QueryPlan instead? + +1. We want uncached queries that don't store matched archetypes/tables. + - when we call iter, the underlying untyped plan checks every archetypes by using the component index. +2. The core query logic should be untyped (because it can contain untyped terms like variables). Maybe if all the terms are typed we can keep doing what we do now, since it would be faster? + +Multi-event observers: +- have a single observer that matches both OnAdd and OnRemove + + */ + /// Types that can be used as parameters in a [`Query`]. /// Types that implement this should also implement either [`QueryData`] or [`QueryFilter`] /// diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index 36a1ca3dc7d69..489f7cbadae25 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -16,6 +16,7 @@ use core::{ mem::MaybeUninit, ops::{Deref, DerefMut}, }; +use bevy_ecs::query::{CacheState, QueryCache}; /// A [system parameter] that provides selective access to the [`Component`] data stored in a [`World`]. /// @@ -482,23 +483,23 @@ use core::{ /// ``` /// /// [autovectorization]: https://en.wikipedia.org/wiki/Automatic_vectorization -pub struct Query<'world, 'state, D: QueryData, F: QueryFilter = ()> { +pub struct Query<'world, 'state, D: QueryData, F: QueryFilter = (), C: QueryCache = CacheState> { // SAFETY: Must have access to the components registered in `state`. world: UnsafeWorldCell<'world>, - state: &'state QueryState, + state: &'state QueryState, last_run: Tick, this_run: Tick, } -impl Clone for Query<'_, '_, D, F> { +impl Clone for Query<'_, '_, D, F, C> { fn clone(&self) -> Self { *self } } -impl Copy for Query<'_, '_, D, F> {} +impl Copy for Query<'_, '_, D, F, C> {} -impl core::fmt::Debug for Query<'_, '_, D, F> { +impl core::fmt::Debug for Query<'_, '_, D, F, C> { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { f.debug_struct("Query") .field("matched_entities", &self.iter().count()) @@ -510,7 +511,7 @@ impl core::fmt::Debug for Query<'_, '_, D, F> { } } -impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> Query<'w, 's, D, F, C> { /// Creates a new query. /// /// # Safety @@ -521,7 +522,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { #[inline] pub(crate) unsafe fn new( world: UnsafeWorldCell<'w>, - state: &'s QueryState, + state: &'s QueryState, last_run: Tick, this_run: Tick, ) -> Self { @@ -542,14 +543,14 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// # See also /// /// [`into_readonly`](Self::into_readonly) for a version that consumes the `Query` to return one with the full `'world` lifetime. - pub fn as_readonly(&self) -> Query<'_, 's, D::ReadOnly, F> { + pub fn as_readonly(&self) -> Query<'_, 's, D::ReadOnly, F, C> { // SAFETY: The reborrowed query is converted to read-only, so it cannot perform mutable access, // and the original query is held with a shared borrow, so it cannot perform mutable access either. unsafe { self.reborrow_unsafe() }.into_readonly() } /// Returns another `Query` from this does not return any data, which can be faster. - fn as_nop(&self) -> Query<'_, 's, NopWorldQuery, F> { + fn as_nop(&self) -> Query<'_, 's, NopWorldQuery, F, C> { let new_state = self.state.as_nop(); // SAFETY: // - The reborrowed query is converted to read-only, so it cannot perform mutable access, @@ -569,7 +570,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// # See also /// /// [`as_readonly`](Self::as_readonly) for a version that borrows the `Query` instead of consuming it. - pub fn into_readonly(self) -> Query<'w, 's, D::ReadOnly, F> { + pub fn into_readonly(self) -> Query<'w, 's, D::ReadOnly, F, C> { let new_state = self.state.as_readonly(); // SAFETY: // - This is memory safe because it turns the query immutable. @@ -601,7 +602,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// } /// } /// ``` - pub fn reborrow(&mut self) -> Query<'_, 's, D, F> { + pub fn reborrow(&mut self) -> Query<'_, 's, D, F, C> { // SAFETY: this query is exclusively borrowed while the new one exists, so // no overlapping access can occur. unsafe { self.reborrow_unsafe() } @@ -618,7 +619,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// # See also /// /// - [`reborrow`](Self::reborrow) for the safe versions. - pub unsafe fn reborrow_unsafe(&self) -> Query<'_, 's, D, F> { + pub unsafe fn reborrow_unsafe(&self) -> Query<'_, 's, D, F, C> { // SAFETY: // - This is memory safe because the caller ensures that there are no conflicting references. // - The world matches because it was the same one used to construct self. @@ -636,7 +637,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// # See also /// /// - [`reborrow_unsafe`](Self::reborrow_unsafe) for a safer version that constrains the returned `'w` lifetime to the length of the borrow. - unsafe fn copy_unsafe(&self) -> Query<'w, 's, D, F> { + unsafe fn copy_unsafe(&self) -> Query<'w, 's, D, F, C> { // SAFETY: // - This is memory safe because the caller ensures that there are no conflicting references. // - The world matches because it was the same one used to construct self. @@ -670,7 +671,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// /// [`iter_mut`](Self::iter_mut) for mutable query items. #[inline] - pub fn iter(&self) -> QueryIter<'_, 's, D::ReadOnly, F> { + pub fn iter(&self) -> QueryIter<'_, 's, D::ReadOnly, F, C> { self.as_readonly().into_iter() } @@ -701,7 +702,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// /// [`iter`](Self::iter) for read-only query items. #[inline] - pub fn iter_mut(&mut self) -> QueryIter<'_, 's, D, F> { + pub fn iter_mut(&mut self) -> QueryIter<'_, 's, D, F, C> { self.reborrow().into_iter() } @@ -731,7 +732,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { #[inline] pub fn iter_combinations( &self, - ) -> QueryCombinationIter<'_, 's, D::ReadOnly, F, K> { + ) -> QueryCombinationIter<'_, 's, D::ReadOnly, F, C, K> { self.as_readonly().iter_combinations_inner() } @@ -761,7 +762,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { #[inline] pub fn iter_combinations_mut( &mut self, - ) -> QueryCombinationIter<'_, 's, D, F, K> { + ) -> QueryCombinationIter<'_, 's, D, F, C, K> { self.reborrow().iter_combinations_inner() } @@ -790,7 +791,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// - [`iter_combinations`](Self::iter_combinations) for read-only query item combinations. /// - [`iter_combinations_mut`](Self::iter_combinations_mut) for mutable query item combinations. #[inline] - pub fn iter_combinations_inner(self) -> QueryCombinationIter<'w, 's, D, F, K> { + pub fn iter_combinations_inner(self) -> QueryCombinationIter<'w, 's, D, F, C, K> { // SAFETY: `self.world` has permission to access the required components. unsafe { QueryCombinationIter::new(self.world, self.state, self.last_run, self.this_run) } } @@ -836,7 +837,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { pub fn iter_many>( &self, entities: EntityList, - ) -> QueryManyIter<'_, 's, D::ReadOnly, F, EntityList::IntoIter> { + ) -> QueryManyIter<'_, 's, D::ReadOnly, F, C, EntityList::IntoIter> { self.as_readonly().iter_many_inner(entities) } @@ -881,7 +882,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { pub fn iter_many_mut>( &mut self, entities: EntityList, - ) -> QueryManyIter<'_, 's, D, F, EntityList::IntoIter> { + ) -> QueryManyIter<'_, 's, D, F, C, EntityList::IntoIter> { self.reborrow().iter_many_inner(entities) } @@ -899,7 +900,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { pub fn iter_many_inner>( self, entities: EntityList, - ) -> QueryManyIter<'w, 's, D, F, EntityList::IntoIter> { + ) -> QueryManyIter<'w, 's, D, F, C, EntityList::IntoIter> { // SAFETY: `self.world` has permission to access the required components. unsafe { QueryManyIter::new( @@ -964,7 +965,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { pub fn iter_many_unique( &self, entities: EntityList, - ) -> QueryManyUniqueIter<'_, 's, D::ReadOnly, F, EntityList::IntoIter> { + ) -> QueryManyUniqueIter<'_, 's, D::ReadOnly, F, C, EntityList::IntoIter> { self.as_readonly().iter_many_unique_inner(entities) } @@ -1019,7 +1020,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { pub fn iter_many_unique_mut( &mut self, entities: EntityList, - ) -> QueryManyUniqueIter<'_, 's, D, F, EntityList::IntoIter> { + ) -> QueryManyUniqueIter<'_, 's, D, F, C, EntityList::IntoIter> { self.reborrow().iter_many_unique_inner(entities) } @@ -1074,7 +1075,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { pub fn iter_many_unique_inner( self, entities: EntityList, - ) -> QueryManyUniqueIter<'w, 's, D, F, EntityList::IntoIter> { + ) -> QueryManyUniqueIter<'w, 's, D, F, C, EntityList::IntoIter> { // SAFETY: `self.world` has permission to access the required components. unsafe { QueryManyUniqueIter::new( @@ -1101,7 +1102,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// /// - [`iter`](Self::iter) and [`iter_mut`](Self::iter_mut) for the safe versions. #[inline] - pub unsafe fn iter_unsafe(&self) -> QueryIter<'_, 's, D, F> { + pub unsafe fn iter_unsafe(&self) -> QueryIter<'_, 's, D, F, C> { // SAFETY: The caller promises that this will not result in multiple mutable references. unsafe { self.reborrow_unsafe() }.into_iter() } @@ -1122,7 +1123,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { #[inline] pub unsafe fn iter_combinations_unsafe( &self, - ) -> QueryCombinationIter<'_, 's, D, F, K> { + ) -> QueryCombinationIter<'_, 's, D, F, C, K> { // SAFETY: The caller promises that this will not result in multiple mutable references. unsafe { self.reborrow_unsafe() }.iter_combinations_inner() } @@ -1144,7 +1145,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { pub unsafe fn iter_many_unsafe>( &self, entities: EntityList, - ) -> QueryManyIter<'_, 's, D, F, EntityList::IntoIter> { + ) -> QueryManyIter<'_, 's, D, F, C, EntityList::IntoIter> { // SAFETY: The caller promises that this will not result in multiple mutable references. unsafe { self.reborrow_unsafe() }.iter_many_inner(entities) } @@ -1166,7 +1167,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { pub unsafe fn iter_many_unique_unsafe( &self, entities: EntityList, - ) -> QueryManyUniqueIter<'_, 's, D, F, EntityList::IntoIter> { + ) -> QueryManyUniqueIter<'_, 's, D, F, C, EntityList::IntoIter> { // SAFETY: The caller promises that this will not result in multiple mutable references. unsafe { self.reborrow_unsafe() }.iter_many_unique_inner(entities) } @@ -1187,7 +1188,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// [`par_iter_mut`]: Self::par_iter_mut /// [`World`]: crate::world::World #[inline] - pub fn par_iter(&self) -> QueryParIter<'_, 's, D::ReadOnly, F> { + pub fn par_iter(&self) -> QueryParIter<'_, 's, D::ReadOnly, F, C> { self.as_readonly().par_iter_inner() } @@ -1222,7 +1223,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// [`par_iter`]: Self::par_iter /// [`World`]: crate::world::World #[inline] - pub fn par_iter_mut(&mut self) -> QueryParIter<'_, 's, D, F> { + pub fn par_iter_mut(&mut self) -> QueryParIter<'_, 's, D, F, C> { self.reborrow().par_iter_inner() } @@ -1253,10 +1254,12 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// # bevy_ecs::system::assert_is_system(gravity_system); /// ``` #[inline] - pub fn par_iter_inner(self) -> QueryParIter<'w, 's, D, F> { + pub fn par_iter_inner(self) -> QueryParIter<'w, 's, D, F, C> { QueryParIter { world: self.world, state: self.state, + #[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))] + iteration_data: self.state.iteration_data(self.world), last_run: self.last_run, this_run: self.this_run, batching_strategy: BatchingStrategy::new(), @@ -1282,7 +1285,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { pub fn par_iter_many>( &self, entities: EntityList, - ) -> QueryParManyIter<'_, 's, D::ReadOnly, F, EntityList::Item> { + ) -> QueryParManyIter<'_, 's, D::ReadOnly, F, C, EntityList::Item> { QueryParManyIter { world: self.world, state: self.state.as_readonly(), @@ -1311,7 +1314,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { pub fn par_iter_many_unique>( &self, entities: EntityList, - ) -> QueryParManyUniqueIter<'_, 's, D::ReadOnly, F, EntityList::Item> { + ) -> QueryParManyUniqueIter<'_, 's, D::ReadOnly, F, C, EntityList::Item> { QueryParManyUniqueIter { world: self.world, state: self.state.as_readonly(), @@ -1340,7 +1343,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { pub fn par_iter_many_unique_mut>( &mut self, entities: EntityList, - ) -> QueryParManyUniqueIter<'_, 's, D, F, EntityList::Item> { + ) -> QueryParManyUniqueIter<'_, 's, D, F, C, EntityList::Item> { QueryParManyUniqueIter { world: self.world, state: self.state, @@ -1545,21 +1548,18 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { .entities() .get(entity) .ok_or(EntityDoesNotExistError::new(entity, self.world.entities()))?; - if !self - .state - .matched_archetypes - .contains(location.archetype_id.index()) - { - return Err(QueryEntityError::QueryDoesNotMatch( - entity, - location.archetype_id, - )); - } + let archetype = self .world .archetypes() .get(location.archetype_id) .debug_checked_unwrap(); + if !self.state.matches(archetype) { + return Err(QueryEntityError::QueryDoesNotMatch( + entity, + location.archetype_id, + )); + } let mut fetch = D::init_fetch( self.world, &self.state.fetch_state, @@ -2240,7 +2240,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// assert_valid_transmute_filtered::, (), Entity, Or<(Changed, With)>>(); /// ``` #[track_caller] - pub fn transmute_lens(&mut self) -> QueryLens<'_, NewD> { + pub fn transmute_lens(&mut self) -> QueryLens<'_, NewD, (), C> { self.transmute_lens_filtered::() } @@ -2296,7 +2296,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// /// - [`transmute_lens`](Self::transmute_lens) to convert to a lens using a mutable borrow of the [`Query`]. #[track_caller] - pub fn transmute_lens_inner(self) -> QueryLens<'w, NewD> { + pub fn transmute_lens_inner(self) -> QueryLens<'w, NewD, (), C> { self.transmute_lens_filtered_inner::() } @@ -2312,7 +2312,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { #[track_caller] pub fn transmute_lens_filtered( &mut self, - ) -> QueryLens<'_, NewD, NewF> { + ) -> QueryLens<'_, NewD, NewF, C> { self.reborrow().transmute_lens_filtered_inner() } @@ -2333,7 +2333,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { #[track_caller] pub fn transmute_lens_filtered_inner( self, - ) -> QueryLens<'w, NewD, NewF> { + ) -> QueryLens<'w, NewD, NewF, C> { let state = self.state.transmute_filtered::(self.world); QueryLens { world: self.world, @@ -2344,7 +2344,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { } /// Gets a [`QueryLens`] with the same accesses as the existing query - pub fn as_query_lens(&mut self) -> QueryLens<'_, D> { + pub fn as_query_lens(&mut self) -> QueryLens<'_, D, (), C> { self.transmute_lens() } @@ -2353,7 +2353,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// # See also /// /// - [`as_query_lens`](Self::as_query_lens) to convert to a lens using a mutable borrow of the [`Query`]. - pub fn into_query_lens(self) -> QueryLens<'w, D> { + pub fn into_query_lens(self) -> QueryLens<'w, D, (), C> { self.transmute_lens_inner() } @@ -2413,8 +2413,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// See [`Self::transmute_lens`] for more details. pub fn join<'a, OtherD: QueryData, NewD: QueryData>( &'a mut self, - other: &'a mut Query, - ) -> QueryLens<'a, NewD> { + other: &'a mut Query, + ) -> QueryLens<'a, NewD, (), C> { self.join_filtered(other) } @@ -2440,8 +2440,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// - [`join`](Self::join) to join using a mutable borrow of the [`Query`]. pub fn join_inner( self, - other: Query<'w, '_, OtherD>, - ) -> QueryLens<'w, NewD> { + other: Query<'w, '_, OtherD, (), C>, + ) -> QueryLens<'w, NewD, (), C> { self.join_filtered_inner(other) } @@ -2460,8 +2460,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { NewF: QueryFilter, >( &'a mut self, - other: &'a mut Query, - ) -> QueryLens<'a, NewD, NewF> { + other: &'a mut Query, + ) -> QueryLens<'a, NewD, NewF, C> { self.reborrow().join_filtered_inner(other.reborrow()) } @@ -2484,8 +2484,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { NewF: QueryFilter, >( self, - other: Query<'w, '_, OtherD, OtherF>, - ) -> QueryLens<'w, NewD, NewF> { + other: Query<'w, '_, OtherD, OtherF, C>, + ) -> QueryLens<'w, NewD, NewF, C> { let state = self .state .join_filtered::(self.world, other.state); @@ -2498,9 +2498,9 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { } } -impl<'w, 's, D: QueryData, F: QueryFilter> IntoIterator for Query<'w, 's, D, F> { +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> IntoIterator for Query<'w, 's, D, F, C> { type Item = D::Item<'w, 's>; - type IntoIter = QueryIter<'w, 's, D, F>; + type IntoIter = QueryIter<'w, 's, D, F, C>; fn into_iter(self) -> Self::IntoIter { // SAFETY: @@ -2511,25 +2511,25 @@ impl<'w, 's, D: QueryData, F: QueryFilter> IntoIterator for Query<'w, 's, D, F> } } -impl<'w, 's, D: QueryData, F: QueryFilter> IntoIterator for &'w Query<'_, 's, D, F> { +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> IntoIterator for &'w Query<'_, 's, D, F, C> { type Item = ROQueryItem<'w, 's, D>; - type IntoIter = QueryIter<'w, 's, D::ReadOnly, F>; + type IntoIter = QueryIter<'w, 's, D::ReadOnly, F, C>; fn into_iter(self) -> Self::IntoIter { self.iter() } } -impl<'w, 's, D: QueryData, F: QueryFilter> IntoIterator for &'w mut Query<'_, 's, D, F> { +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> IntoIterator for &'w mut Query<'_, 's, D, F, C> { type Item = D::Item<'w, 's>; - type IntoIter = QueryIter<'w, 's, D, F>; + type IntoIter = QueryIter<'w, 's, D, F, C>; fn into_iter(self) -> Self::IntoIter { self.iter_mut() } } -impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter> Query<'w, 's, D, F> { +impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, C: QueryCache> Query<'w, 's, D, F, C> { /// Returns an [`Iterator`] over the query items, with the actual "inner" world lifetime. /// /// This can only return immutable data (mutable data will be cast to an immutable form). @@ -2554,7 +2554,7 @@ impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter> Query<'w, 's, D, F> { /// # bevy_ecs::system::assert_is_system(report_names_system); /// ``` #[inline] - pub fn iter_inner(&self) -> QueryIter<'w, 's, D::ReadOnly, F> { + pub fn iter_inner(&self) -> QueryIter<'w, 's, D::ReadOnly, F, C> { (*self).into_iter() } } @@ -2562,16 +2562,16 @@ impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter> Query<'w, 's, D, F> { /// Type returned from [`Query::transmute_lens`] containing the new [`QueryState`]. /// /// Call [`query`](QueryLens::query) or [`into`](Into::into) to construct the resulting [`Query`] -pub struct QueryLens<'w, Q: QueryData, F: QueryFilter = ()> { +pub struct QueryLens<'w, Q: QueryData, F: QueryFilter = (), C: QueryCache = CacheState> { world: UnsafeWorldCell<'w>, - state: QueryState, + state: QueryState, last_run: Tick, this_run: Tick, } -impl<'w, Q: QueryData, F: QueryFilter> QueryLens<'w, Q, F> { +impl<'w, Q: QueryData, F: QueryFilter, C: QueryCache> QueryLens<'w, Q, F, C> { /// Create a [`Query`] from the underlying [`QueryState`]. - pub fn query(&mut self) -> Query<'_, '_, Q, F> { + pub fn query(&mut self) -> Query<'_, '_, Q, F, C> { Query { world: self.world, state: &self.state, @@ -2581,11 +2581,11 @@ impl<'w, Q: QueryData, F: QueryFilter> QueryLens<'w, Q, F> { } } -impl<'w, Q: ReadOnlyQueryData, F: QueryFilter> QueryLens<'w, Q, F> { +impl<'w, Q: ReadOnlyQueryData, F: QueryFilter, C: QueryCache> QueryLens<'w, Q, F, C> { /// Create a [`Query`] from the underlying [`QueryState`]. /// This returns results with the actual "inner" world lifetime, /// so it may only be used with read-only queries to prevent mutable aliasing. - pub fn query_inner(&self) -> Query<'w, '_, Q, F> { + pub fn query_inner(&self) -> Query<'w, '_, Q, F, C> { Query { world: self.world, state: &self.state, @@ -2595,18 +2595,18 @@ impl<'w, Q: ReadOnlyQueryData, F: QueryFilter> QueryLens<'w, Q, F> { } } -impl<'w, 's, Q: QueryData, F: QueryFilter> From<&'s mut QueryLens<'w, Q, F>> - for Query<'s, 's, Q, F> +impl<'w, 's, Q: QueryData, F: QueryFilter, C: QueryCache> From<&'s mut QueryLens<'w, Q, F, C>> + for Query<'s, 's, Q, F, C> { - fn from(value: &'s mut QueryLens<'w, Q, F>) -> Query<'s, 's, Q, F> { + fn from(value: &'s mut QueryLens<'w, Q, F, C>) -> Query<'s, 's, Q, F, C> { value.query() } } -impl<'w, 'q, Q: QueryData, F: QueryFilter> From<&'q mut Query<'w, '_, Q, F>> - for QueryLens<'q, Q, F> +impl<'w, 'q, Q: QueryData, F: QueryFilter, C: QueryCache> From<&'q mut Query<'w, '_, Q, F, C>> + for QueryLens<'q, Q, F, C> { - fn from(value: &'q mut Query<'w, '_, Q, F>) -> QueryLens<'q, Q, F> { + fn from(value: &'q mut Query<'w, '_, Q, F, C>) -> QueryLens<'q, Q, F, C> { value.transmute_lens_filtered() } } @@ -2616,7 +2616,7 @@ impl<'w, 'q, Q: QueryData, F: QueryFilter> From<&'q mut Query<'w, '_, Q, F>> /// This [`SystemParam`](crate::system::SystemParam) fails validation if zero or more than one matching entity exists. /// This will cause the system to be skipped, according to the rules laid out in [`SystemParamValidationError`](crate::system::SystemParamValidationError). /// -/// Use [`Option>`] instead if zero or one matching entities can exist. +/// Use [`Option>`] instead if zero or one matching entities can exist. /// /// See [`Query`] for more details. /// @@ -2675,53 +2675,53 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Single<'w, 's, D, F> { /// See [`Query`] for more details. /// /// [System parameter]: crate::system::SystemParam -pub struct Populated<'w, 's, D: QueryData, F: QueryFilter = ()>(pub(crate) Query<'w, 's, D, F>); +pub struct Populated<'w, 's, D: QueryData, F: QueryFilter = (), C: QueryCache = CacheState>(pub(crate) Query<'w, 's, D, F, C>); -impl<'w, 's, D: QueryData, F: QueryFilter> Deref for Populated<'w, 's, D, F> { - type Target = Query<'w, 's, D, F>; +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> Deref for Populated<'w, 's, D, F, C> { + type Target = Query<'w, 's, D, F, C>; fn deref(&self) -> &Self::Target { &self.0 } } -impl DerefMut for Populated<'_, '_, D, F> { +impl DerefMut for Populated<'_, '_, D, F, C> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } -impl<'w, 's, D: QueryData, F: QueryFilter> Populated<'w, 's, D, F> { +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> Populated<'w, 's, D, F, C> { /// Returns the inner item with ownership. - pub fn into_inner(self) -> Query<'w, 's, D, F> { + pub fn into_inner(self) -> Query<'w, 's, D, F, C> { self.0 } } -impl<'w, 's, D: QueryData, F: QueryFilter> IntoIterator for Populated<'w, 's, D, F> { - type Item = as IntoIterator>::Item; +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> IntoIterator for Populated<'w, 's, D, F, C> { + type Item = as IntoIterator>::Item; - type IntoIter = as IntoIterator>::IntoIter; + type IntoIter = as IntoIterator>::IntoIter; fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } } -impl<'a, 'w, 's, D: QueryData, F: QueryFilter> IntoIterator for &'a Populated<'w, 's, D, F> { - type Item = <&'a Query<'w, 's, D, F> as IntoIterator>::Item; +impl<'a, 'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> IntoIterator for &'a Populated<'w, 's, D, F, C> { + type Item = <&'a Query<'w, 's, D, F, C> as IntoIterator>::Item; - type IntoIter = <&'a Query<'w, 's, D, F> as IntoIterator>::IntoIter; + type IntoIter = <&'a Query<'w, 's, D, F, C> as IntoIterator>::IntoIter; fn into_iter(self) -> Self::IntoIter { self.deref().into_iter() } } -impl<'a, 'w, 's, D: QueryData, F: QueryFilter> IntoIterator for &'a mut Populated<'w, 's, D, F> { - type Item = <&'a mut Query<'w, 's, D, F> as IntoIterator>::Item; +impl<'a, 'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> IntoIterator for &'a mut Populated<'w, 's, D, F, C> { + type Item = <&'a mut Query<'w, 's, D, F, C> as IntoIterator>::Item; - type IntoIter = <&'a mut Query<'w, 's, D, F> as IntoIterator>::IntoIter; + type IntoIter = <&'a mut Query<'w, 's, D, F, C> as IntoIterator>::IntoIter; fn into_iter(self) -> Self::IntoIter { self.deref_mut().into_iter() From 576777f39ff158edc457e7ddfa6aa2a1248ebf2b Mon Sep 17 00:00:00 2001 From: Charles Bournhonesque Date: Mon, 20 Oct 2025 01:09:22 -0400 Subject: [PATCH 02/10] fix all tests --- crates/bevy_ecs/src/query/builder.rs | 1 - crates/bevy_ecs/src/query/cache.rs | 73 ++++++++++++++++++++-------- crates/bevy_ecs/src/query/iter.rs | 29 +++++++++-- crates/bevy_ecs/src/query/state.rs | 11 ++--- 4 files changed, 79 insertions(+), 35 deletions(-) diff --git a/crates/bevy_ecs/src/query/builder.rs b/crates/bevy_ecs/src/query/builder.rs index 2dba029b1c227..8ba34a34f9ee7 100644 --- a/crates/bevy_ecs/src/query/builder.rs +++ b/crates/bevy_ecs/src/query/builder.rs @@ -71,7 +71,6 @@ impl<'w, D: QueryData, F: QueryFilter> QueryBuilder<'w, D, F> { } } - // TODO: warning about this not being used, maybe i messed something up pub(super) fn is_dense(&self) -> bool { // Note: `component_id` comes from the user in safe code, so we cannot trust it to // exist. If it doesn't exist we pessimistically assume it's sparse. diff --git a/crates/bevy_ecs/src/query/cache.rs b/crates/bevy_ecs/src/query/cache.rs index 93cd9bdeb7eb9..61931dcb2418b 100644 --- a/crates/bevy_ecs/src/query/cache.rs +++ b/crates/bevy_ecs/src/query/cache.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use core::fmt::{Debug, Formatter}; use alloc::vec::Vec; use fixedbitset::FixedBitSet; @@ -6,7 +7,7 @@ use bevy_ecs::archetype::{Archetype, ArchetypeGeneration, ArchetypeId, Archetype use bevy_ecs::component::ComponentId; use bevy_ecs::entity_disabling::DefaultQueryFilters; use bevy_ecs::prelude::World; -use bevy_ecs::query::{QueryData, QueryFilter}; +use bevy_ecs::query::{QueryBuilder, QueryData, QueryFilter}; use bevy_ecs::query::state::StorageId; use bevy_ecs::storage::{SparseSetIndex, TableId}; use bevy_ecs::world::unsafe_world_cell::UnsafeWorldCell; @@ -131,17 +132,17 @@ impl QueryState { pub trait QueryCache: Debug + Clone { - fn iteration_data<'s, D: QueryData, F: QueryFilter>(&self, query: &QueryState, world: UnsafeWorldCell, storage_ids: &'s mut Vec) -> IterationData<'s>; + fn iteration_data<'s, 'a: 's, D: QueryData, F: QueryFilter>(&'a self, query: &QueryState, world: UnsafeWorldCell) -> IterationData<'s>; /// # Safety /// `archetype` must be from the `World` this state was initialized from. unsafe fn contains(&self, query: &QueryState, archetype: &Archetype) -> bool; /// Creates a new [`QueryCache`] but does not populate it with the matched results from the World yet - /// - /// `new_archetype` and its variants must be called on all of the World's archetypes before the - /// state can return valid query results. - fn uninitialized(world: &World) -> Self; + fn from_world_uninitialized(world: &World) -> Self; + + /// Creates a new [`QueryCache`] but does not populate it with the matched results from the World yet + fn from_builder_uninitialized(builder: &QueryBuilder) -> Self; fn update_archetypes(&mut self, uncached: &QueryState, world: UnsafeWorldCell); @@ -152,9 +153,9 @@ pub trait QueryCache: Debug + Clone { } #[derive(Clone)] -pub(super) struct IterationData<'s> { +pub struct IterationData<'s> { pub(super) is_dense: bool, - pub(super) storage_ids: core::slice::Iter<'s, StorageId>, + pub(super) storage_ids: Cow<'s, [StorageId]>, } #[derive(Clone)] @@ -173,9 +174,9 @@ pub struct CacheState { impl QueryCache for CacheState { - fn iteration_data<'s, D: QueryData, F: QueryFilter>(self: &Self, _: &QueryState, _: UnsafeWorldCell, _: &'s mut Vec) -> IterationData<'s> { + fn iteration_data<'s, 'a: 's, D: QueryData, F: QueryFilter>(&'a self, _: &QueryState, _: UnsafeWorldCell) -> IterationData<'s> { IterationData { - storage_ids: self.matched_storage_ids.iter(), + storage_ids: Cow::Borrowed(&self.matched_storage_ids), is_dense: self.is_dense, } } @@ -184,11 +185,12 @@ impl QueryCache for CacheState { self.matched_archetypes.contains(archetype.id().index()) } - fn uninitialized(world: &World) -> Self { + fn from_world_uninitialized(world: &World) -> Self { // For queries without dynamic filters the dense-ness of the query is equal to the dense-ness // of its static type parameters. let mut is_dense = D::IS_DENSE && F::IS_DENSE; + // TODO: disallow non-dense DefaultQueryFilters if let Some(default_filters) = world.get_resource::() { is_dense &= default_filters.is_dense(world.components()); } @@ -201,6 +203,21 @@ impl QueryCache for CacheState { is_dense, } } + + fn from_builder_uninitialized(builder: &QueryBuilder) -> Self { + // For dynamic queries the dense-ness is given by the query builder. + let is_dense = builder.is_dense(); + + // TODO: disallow non-dense DefaultQueryFilters + + Self { + archetype_generation: ArchetypeGeneration::initial(), + matched_tables: Default::default(), + matched_archetypes: Default::default(), + matched_storage_ids: Vec::new(), + is_dense, + } + } fn update_archetypes(&mut self, uncached: &QueryState, world: UnsafeWorldCell) { uncached.validate_world(world.id()); if self.archetype_generation == world.archetypes().generation() { @@ -299,27 +316,27 @@ impl CacheState { #[derive(Debug, Clone)] -pub struct Uncached; +pub struct Uncached { + is_dense: bool, +} impl QueryCache for Uncached { - fn iteration_data<'s, D: QueryData, F: QueryFilter>(&self, query: &QueryState, world: UnsafeWorldCell, storage_ids: &'s mut Vec) -> IterationData<'s> { + fn iteration_data<'s, 'a: 's, D: QueryData, F: QueryFilter>(&'a self, query: &QueryState, world: UnsafeWorldCell) -> IterationData<'s> { let generation = world.archetypes().generation(); - // TODO: what about computing is_dense from DefaultQueryFilters? - // Realistically all DefaultQueryFilters would be dense. We should enforce it. - let is_dense = D::IS_DENSE && F::IS_DENSE; + let mut storage_ids = Vec::new(); query.iter_archetypes(generation, world.archetypes(), |archetype| { - storage_ids.push(if !is_dense { + storage_ids.push(if !self.is_dense { StorageId { archetype_id: archetype.id() } } else { StorageId { table_id: archetype.table_id() } }) }); IterationData { - is_dense, - storage_ids: storage_ids.iter() + is_dense: self.is_dense, + storage_ids: Cow::Owned(storage_ids), } } @@ -328,8 +345,22 @@ impl QueryCache for Uncached { unsafe { query.matches_archetype(archetype) } } - fn uninitialized(_: &World) -> Self { - Uncached + fn from_world_uninitialized(_: &World) -> Self { + // For queries without dynamic filters the dense-ness of the query is equal to the dense-ness + // of its static type parameters. + // TODO: what about computing is_dense from DefaultQueryFilters? + // Realistically all DefaultQueryFilters would be dense. We should enforce it. + Uncached { + is_dense: D::IS_DENSE && F::IS_DENSE + } + } + + fn from_builder_uninitialized(builder: &QueryBuilder) -> Self { + // TODO: what about computing is_dense from DefaultQueryFilters? + // Realistically all DefaultQueryFilters would be dense. We should enforce it. + Uncached { + is_dense: builder.is_dense(), + } } /// We do not update anything. This is here for feature parity. diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index 17d920440f22d..b3d27ffeb9925 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use super::{QueryData, QueryFilter, ReadOnlyQueryData}; use crate::{ archetype::{Archetype, ArchetypeEntity, Archetypes}, @@ -21,7 +22,6 @@ use core::{ }; use nonmax::NonMaxU32; use bevy_ecs::query::cache::QueryCache; -use bevy_ecs::query::IterationData; /// An [`Iterator`] over query results of a [`Query`](crate::system::Query). /// @@ -1620,7 +1620,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, I: Iterator(|entity_ref| { /// ( /// entity_ref.contains::(), - // entity_ref.get::().copied() + /// entity_ref.get::().copied() /// ) /// }) /// .rev() @@ -2376,6 +2376,7 @@ struct QueryIterationCursor<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> // whether the query iteration is dense or not. is_dense: bool, storage_id_iter: core::slice::Iter<'s, StorageId>, + owned_storage_ids: Option>, table_entities: &'w [Entity], archetype_entities: &'w [ArchetypeEntity], fetch: D::Fetch<'w>, @@ -2392,6 +2393,7 @@ impl Clone for QueryIterationCursor Self { is_dense: self.is_dense, storage_id_iter: self.storage_id_iter.clone(), + owned_storage_ids: self.owned_storage_ids.clone(), table_entities: self.table_entities, archetype_entities: self.archetype_entities, fetch: self.fetch.clone(), @@ -2430,14 +2432,30 @@ impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache + 's> QueryIterationCur ) -> Self { let fetch = D::init_fetch(world, &query_state.fetch_state, last_run, this_run); let filter = F::init_fetch(world, &query_state.filter_state, last_run, this_run); - let mut storage_ids = Vec::new(); - let iteration_data = query_state.cache.iteration_data(query_state, world, &mut storage_ids); + let iteration_data = query_state.cache.iteration_data(query_state, world); + let (owned_storage_ids, storage_id_iter) =match iteration_data.storage_ids { + Cow::Borrowed(slice) => { + (None, slice.iter()) + } + Cow::Owned(owned) => { + let owned_ids = Some(owned); + let slice_ptr: *const [StorageId] = owned_ids.as_ref().unwrap().as_slice(); + // SAFETY: + // - The slice pointer refers to memory owned by `owned_ids` + // - `owned_ids` is moved *as a whole* into the struct below and won’t move afterward + // - The struct owns `owned_ids` for the same lifetime as `ids_iter` + // => Safe as long as `Cursor` isn’t moved after creation (so don’t mem::swap etc.) + let ids_iter = unsafe { (&*slice_ptr).iter() }; + (owned_ids, ids_iter) + } + }; QueryIterationCursor { fetch, filter, table_entities: &[], archetype_entities: &[], - storage_id_iter: iteration_data.storage_ids, + storage_id_iter, + owned_storage_ids, is_dense: iteration_data.is_dense, current_len: 0, current_row: 0, @@ -2454,6 +2472,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache + 's> QueryIterationCur table_entities: self.table_entities, archetype_entities: self.archetype_entities, storage_id_iter: self.storage_id_iter.clone(), + owned_storage_ids: self.owned_storage_ids.clone(), current_len: self.current_len, current_row: self.current_row, _cache: core::marker::PhantomData diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index eeb492fb25c19..e3822f78bb389 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -85,11 +85,7 @@ impl fmt::Debug for QueryState) -> fmt::Result { f.debug_struct("QueryState") .field("world_id", &self.world_id) - // .field("matched_table_count", &self.matched_tables.count_ones(..)) - // .field( - // "matched_archetype_count", - // &self.matched_archetypes.count_ones(..), - // ) + .field("cache", &self.cache) .finish_non_exhaustive() } } @@ -210,7 +206,7 @@ impl QueryState { default_filters.modify_access(&mut component_access); } - let cache = C::uninitialized::(world); + let cache = C::from_world_uninitialized::(world); Self { world_id: world.id(), @@ -242,12 +238,11 @@ impl QueryState { let mut component_access = builder.access().clone(); - if let Some(default_filters) = builder.world().get_resource::() { default_filters.modify_access(&mut component_access); } - let cache = C::uninitialized::(builder.world()); + let cache = C::from_builder_uninitialized(builder); let mut state = Self { world_id: builder.world().id(), From 8d942b9e824bbf8d7ca5e9570fca5f3859dfdddb Mon Sep 17 00:00:00 2001 From: Charles Bournhonesque Date: Mon, 20 Oct 2025 01:32:59 -0400 Subject: [PATCH 03/10] add simple doc strings --- crates/bevy_ecs/src/query/cache.rs | 167 ++++++++++++++++++-------- crates/bevy_ecs/src/query/iter.rs | 158 +++++++++++++++++------- crates/bevy_ecs/src/query/mod.rs | 4 +- crates/bevy_ecs/src/query/par_iter.rs | 29 +++-- crates/bevy_ecs/src/query/state.rs | 33 ++--- crates/bevy_ecs/src/system/query.rs | 32 +++-- 6 files changed, 296 insertions(+), 127 deletions(-) diff --git a/crates/bevy_ecs/src/query/cache.rs b/crates/bevy_ecs/src/query/cache.rs index 61931dcb2418b..4b4eaabfb6ac2 100644 --- a/crates/bevy_ecs/src/query/cache.rs +++ b/crates/bevy_ecs/src/query/cache.rs @@ -1,28 +1,26 @@ +use crate::query::QueryState; use alloc::borrow::Cow; -use core::fmt::{Debug, Formatter}; use alloc::vec::Vec; -use fixedbitset::FixedBitSet; -use log::warn; use bevy_ecs::archetype::{Archetype, ArchetypeGeneration, ArchetypeId, Archetypes}; use bevy_ecs::component::ComponentId; use bevy_ecs::entity_disabling::DefaultQueryFilters; use bevy_ecs::prelude::World; -use bevy_ecs::query::{QueryBuilder, QueryData, QueryFilter}; use bevy_ecs::query::state::StorageId; +use bevy_ecs::query::{QueryBuilder, QueryData, QueryFilter}; use bevy_ecs::storage::{SparseSetIndex, TableId}; use bevy_ecs::world::unsafe_world_cell::UnsafeWorldCell; -use crate::query::QueryState; +use core::fmt::{Debug, Formatter}; +use fixedbitset::FixedBitSet; +use log::warn; impl QueryState { - /// Splits self into an immutable view of the "prefix" /// (all fields *except* cache) and a mutable ref to the `cache`. pub fn split_cache(&mut self) -> (&QueryState, &mut C) { // This is safe because `QueryState<..., Uncached>` is a // valid "prefix" of `QueryState<..., C>`, and QueryState uses `repr(c)` - let rest: &QueryState = unsafe { - &*(self as *mut Self as *const QueryState) - }; + let rest: &QueryState = + unsafe { &*(self as *mut Self as *const QueryState) }; // This is safe because `cache` is disjoint from the prefix. let cache_mut: &mut C = &mut self.cache; @@ -46,6 +44,20 @@ impl QueryState { self.update_archetypes_unsafe_world_cell(world.as_unsafe_world_cell_readonly()); } + /// Updates the state's internal view of the `world`'s archetypes. If this is not called before querying data, + /// the results may not accurately reflect what is in the `world`. + /// + /// This is only required if a `manual` method (such as [`Self::get_manual`]) is being called, and it only needs to + /// be called if the `world` has been structurally mutated (i.e. added/removed a component or resource). Users using + /// non-`manual` methods such as [`QueryState::get`] do not need to call this as it will be automatically called for them. + /// + /// # Note + /// + /// This method only accesses world metadata. + /// + /// # Panics + /// + /// If `world` does not match the one used to call `QueryState::new` for this instance. pub fn update_archetypes_unsafe_world_cell(&mut self, world: UnsafeWorldCell) { let (uncached_state, cache) = self.split_cache(); cache.update_archetypes(uncached_state, world); @@ -58,7 +70,6 @@ impl QueryState { } } - impl QueryState { /// Returns `true` if this query matches a set of components. Otherwise, returns `false`. /// @@ -85,15 +96,22 @@ impl QueryState { /// Iterate through all archetypes that match the [`QueryState`] with an [`ArchetypeGeneration`] higher than the provided one, /// and call `f` on each of them. - fn iter_archetypes(&self, archetype_generation: ArchetypeGeneration, archetypes: &Archetypes, mut f: impl FnMut(&Archetype)) { + fn iter_archetypes( + &self, + archetype_generation: ArchetypeGeneration, + archetypes: &Archetypes, + mut f: impl FnMut(&Archetype), + ) { if self.component_access.required.is_empty() { - archetypes[archetype_generation..].iter().for_each(|archetype| { - // SAFETY: The validate_world call ensures that the world is the same the QueryState - // was initialized from. - if unsafe { self.matches_archetype(archetype) } { - f(archetype); - } - }) + archetypes[archetype_generation..] + .iter() + .for_each(|archetype| { + // SAFETY: The validate_world call ensures that the world is the same the QueryState + // was initialized from. + if unsafe { self.matches_archetype(archetype) } { + f(archetype); + } + }) } else { // if there are required components, we can optimize by only iterating through archetypes // that contain at least one of the required components @@ -127,37 +145,52 @@ impl QueryState { } } } - } -pub trait QueryCache: Debug + Clone { - - fn iteration_data<'s, 'a: 's, D: QueryData, F: QueryFilter>(&'a self, query: &QueryState, world: UnsafeWorldCell) -> IterationData<'s>; +/// Types that can cache archetypes matched by a `Query`. +pub trait QueryCache: Debug + Clone + Sync { + /// Returns the data needed to iterate through the archetypes that match the query. + fn iteration_data<'s, 'a: 's, D: QueryData, F: QueryFilter>( + &'a self, + query: &QueryState, + world: UnsafeWorldCell, + ) -> IterationData<'s>; /// # Safety /// `archetype` must be from the `World` this state was initialized from. - unsafe fn contains(&self, query: &QueryState, archetype: &Archetype) -> bool; + unsafe fn contains( + &self, + query: &QueryState, + archetype: &Archetype, + ) -> bool; /// Creates a new [`QueryCache`] but does not populate it with the matched results from the World yet fn from_world_uninitialized(world: &World) -> Self; /// Creates a new [`QueryCache`] but does not populate it with the matched results from the World yet - fn from_builder_uninitialized(builder: &QueryBuilder) -> Self; + fn from_builder_uninitialized( + builder: &QueryBuilder, + ) -> Self; - fn update_archetypes(&mut self, uncached: &QueryState, world: UnsafeWorldCell); + /// Update the [`QueryCache`] by storing in the cache every new archetypes that match the query. + fn update_archetypes( + &mut self, + uncached: &QueryState, + world: UnsafeWorldCell, + ); /// Return a new cache that contains the archetypes matched by the query joining self and other fn join(&self, other: &Self) -> Self; - - } +/// Data needed to iterate through query results #[derive(Clone)] pub struct IterationData<'s> { pub(super) is_dense: bool, pub(super) storage_ids: Cow<'s, [StorageId]>, } +/// Default [`QueryCache`] to use if caching is enabled for a query. #[derive(Clone)] pub struct CacheState { pub(crate) archetype_generation: ArchetypeGeneration, @@ -173,15 +206,22 @@ pub struct CacheState { } impl QueryCache for CacheState { - - fn iteration_data<'s, 'a: 's, D: QueryData, F: QueryFilter>(&'a self, _: &QueryState, _: UnsafeWorldCell) -> IterationData<'s> { + fn iteration_data<'s, 'a: 's, D: QueryData, F: QueryFilter>( + &'a self, + _: &QueryState, + _: UnsafeWorldCell, + ) -> IterationData<'s> { IterationData { storage_ids: Cow::Borrowed(&self.matched_storage_ids), is_dense: self.is_dense, } } - unsafe fn contains(&self, _: &QueryState, archetype: &Archetype) -> bool { + unsafe fn contains( + &self, + _: &QueryState, + archetype: &Archetype, + ) -> bool { self.matched_archetypes.contains(archetype.id().index()) } @@ -204,7 +244,9 @@ impl QueryCache for CacheState { } } - fn from_builder_uninitialized(builder: &QueryBuilder) -> Self { + fn from_builder_uninitialized( + builder: &QueryBuilder, + ) -> Self { // For dynamic queries the dense-ness is given by the query builder. let is_dense = builder.is_dense(); @@ -218,15 +260,23 @@ impl QueryCache for CacheState { is_dense, } } - fn update_archetypes(&mut self, uncached: &QueryState, world: UnsafeWorldCell) { + fn update_archetypes( + &mut self, + uncached: &QueryState, + world: UnsafeWorldCell, + ) { uncached.validate_world(world.id()); if self.archetype_generation == world.archetypes().generation() { // skip if we are already up to date - return + return; } - let old_generation = - core::mem::replace(&mut self.archetype_generation, world.archetypes().generation()); - uncached.iter_archetypes(old_generation, world.archetypes(), |archetype| self.cache_archetype(archetype)); + let old_generation = core::mem::replace( + &mut self.archetype_generation, + world.archetypes().generation(), + ); + uncached.iter_archetypes(old_generation, world.archetypes(), |archetype| { + self.cache_archetype(archetype) + }); } fn join(&self, other: &Self) -> Self { @@ -262,11 +312,9 @@ impl QueryCache for CacheState { matched_storage_ids, is_dense, } - } } - impl Debug for CacheState { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { f.debug_struct("CacheState") @@ -311,27 +359,34 @@ impl CacheState { } } } - } - +/// [`QueryCache`] used if caching is disabled for a query. +/// +/// We will not cache any matching archetypes for a query, so they will have to be recomputed +/// from scratch every time. #[derive(Debug, Clone)] pub struct Uncached { is_dense: bool, } - - impl QueryCache for Uncached { - - fn iteration_data<'s, 'a: 's, D: QueryData, F: QueryFilter>(&'a self, query: &QueryState, world: UnsafeWorldCell) -> IterationData<'s> { + fn iteration_data<'s, 'a: 's, D: QueryData, F: QueryFilter>( + &'a self, + query: &QueryState, + world: UnsafeWorldCell, + ) -> IterationData<'s> { let generation = world.archetypes().generation(); let mut storage_ids = Vec::new(); query.iter_archetypes(generation, world.archetypes(), |archetype| { storage_ids.push(if !self.is_dense { - StorageId { archetype_id: archetype.id() } + StorageId { + archetype_id: archetype.id(), + } } else { - StorageId { table_id: archetype.table_id() } + StorageId { + table_id: archetype.table_id(), + } }) }); IterationData { @@ -340,7 +395,11 @@ impl QueryCache for Uncached { } } - unsafe fn contains(&self, query: &QueryState, archetype: &Archetype) -> bool { + unsafe fn contains( + &self, + query: &QueryState, + archetype: &Archetype, + ) -> bool { // SAFETY: satisfied from QueryCache::contains's safety constraints unsafe { query.matches_archetype(archetype) } } @@ -351,11 +410,13 @@ impl QueryCache for Uncached { // TODO: what about computing is_dense from DefaultQueryFilters? // Realistically all DefaultQueryFilters would be dense. We should enforce it. Uncached { - is_dense: D::IS_DENSE && F::IS_DENSE + is_dense: D::IS_DENSE && F::IS_DENSE, } } - fn from_builder_uninitialized(builder: &QueryBuilder) -> Self { + fn from_builder_uninitialized( + builder: &QueryBuilder, + ) -> Self { // TODO: what about computing is_dense from DefaultQueryFilters? // Realistically all DefaultQueryFilters would be dense. We should enforce it. Uncached { @@ -364,10 +425,14 @@ impl QueryCache for Uncached { } /// We do not update anything. This is here for feature parity. - fn update_archetypes(&mut self, _: &QueryState, _: UnsafeWorldCell) { + fn update_archetypes( + &mut self, + _: &QueryState, + _: UnsafeWorldCell, + ) { } fn join(&self, _: &Self) -> Self { self.clone() } -} \ No newline at end of file +} diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index b3d27ffeb9925..e1033bb838ebd 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -1,4 +1,3 @@ -use alloc::borrow::Cow; use super::{QueryData, QueryFilter, ReadOnlyQueryData}; use crate::{ archetype::{Archetype, ArchetypeEntity, Archetypes}, @@ -12,7 +11,9 @@ use crate::{ FilteredEntityMut, FilteredEntityRef, }, }; +use alloc::borrow::Cow; use alloc::vec::Vec; +use bevy_ecs::query::cache::QueryCache; use core::{ cmp::Ordering, fmt::{self, Debug, Formatter}, @@ -21,7 +22,6 @@ use core::{ ops::Range, }; use nonmax::NonMaxU32; -use bevy_ecs::query::cache::QueryCache; /// An [`Iterator`] over query results of a [`Query`](crate::system::Query). /// @@ -955,16 +955,28 @@ impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> Iterator for QueryIter } // This is correct as [`QueryIter`] always returns `None` once exhausted. -impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> FusedIterator for QueryIter<'w, 's, D, F, C> {} +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> FusedIterator + for QueryIter<'w, 's, D, F, C> +{ +} // SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once. -unsafe impl<'w, 's, F: QueryFilter, C: QueryCache> EntitySetIterator for QueryIter<'w, 's, Entity, F, C> {} +unsafe impl<'w, 's, F: QueryFilter, C: QueryCache> EntitySetIterator + for QueryIter<'w, 's, Entity, F, C> +{ +} // SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once. -unsafe impl<'w, 's, F: QueryFilter, C: QueryCache> EntitySetIterator for QueryIter<'w, 's, EntityRef<'_>, F, C> {} +unsafe impl<'w, 's, F: QueryFilter, C: QueryCache> EntitySetIterator + for QueryIter<'w, 's, EntityRef<'_>, F, C> +{ +} // SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once. -unsafe impl<'w, 's, F: QueryFilter, C: QueryCache> EntitySetIterator for QueryIter<'w, 's, EntityMut<'_>, F, C> {} +unsafe impl<'w, 's, F: QueryFilter, C: QueryCache> EntitySetIterator + for QueryIter<'w, 's, EntityMut<'_>, F, C> +{ +} // SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once. unsafe impl<'w, 's, F: QueryFilter, C: QueryCache> EntitySetIterator @@ -996,7 +1008,9 @@ impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> Debug for QueryIter<'w } } -impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, C: QueryCache> Clone for QueryIter<'w, 's, D, F, C> { +impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, C: QueryCache> Clone + for QueryIter<'w, 's, D, F, C> +{ fn clone(&self) -> Self { self.remaining() } @@ -1019,7 +1033,8 @@ where query_state: &'s QueryState, } -impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, I: Iterator> QuerySortedIter<'w, 's, D, F, C, I> +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, I: Iterator> + QuerySortedIter<'w, 's, D, F, C, I> where I: Iterator, { @@ -1159,8 +1174,14 @@ impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, I: Iterator> -{ +pub struct QueryManyIter< + 'w, + 's, + D: QueryData, + F: QueryFilter, + C: QueryCache, + I: Iterator, +> { world: UnsafeWorldCell<'w>, entity_iter: I, entities: &'w Entities, @@ -1776,8 +1797,14 @@ impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, I: Iterator> - QueryManyIter<'w, 's, D, F, C, I> +impl< + 'w, + 's, + D: QueryData, + F: QueryFilter, + C: QueryCache, + I: DoubleEndedIterator, + > QueryManyIter<'w, 's, D, F, C, I> { /// Get next result from the back of the query #[inline(always)] @@ -1802,8 +1829,14 @@ impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, I: DoubleEndedIterator } } -impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, C: QueryCache, I: Iterator> Iterator - for QueryManyIter<'w, 's, D, F, C, I> +impl< + 'w, + 's, + D: ReadOnlyQueryData, + F: QueryFilter, + C: QueryCache, + I: Iterator, + > Iterator for QueryManyIter<'w, 's, D, F, C, I> { type Item = D::Item<'w, 's>; @@ -1860,8 +1893,14 @@ impl< } // This is correct as [`QueryManyIter`] always returns `None` once exhausted. -impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, C: QueryCache, I: Iterator> - FusedIterator for QueryManyIter<'w, 's, D, F, C, I> +impl< + 'w, + 's, + D: ReadOnlyQueryData, + F: QueryFilter, + C: QueryCache, + I: Iterator, + > FusedIterator for QueryManyIter<'w, 's, D, F, C, I> { } @@ -1892,9 +1931,14 @@ impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, I: Iterator( - QueryManyIter<'w, 's, D, F, C, I>, -); +pub struct QueryManyUniqueIter< + 'w, + 's, + D: QueryData, + F: QueryFilter, + C: QueryCache, + I: EntitySetIterator, +>(QueryManyIter<'w, 's, D, F, C, I>); impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, I: EntitySetIterator> QueryManyUniqueIter<'w, 's, D, F, C, I> @@ -1971,7 +2015,14 @@ impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, I: EntitySetIterator> /// This struct is created by the [`sort`](QueryManyIter), [`sort_unstable`](QueryManyIter), /// [`sort_by`](QueryManyIter), [`sort_unstable_by`](QueryManyIter), [`sort_by_key`](QueryManyIter), /// [`sort_unstable_by_key`](QueryManyIter), and [`sort_by_cached_key`](QueryManyIter) methods of [`QueryManyIter`]. -pub struct QuerySortedManyIter<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, I: Iterator> { +pub struct QuerySortedManyIter< + 'w, + 's, + D: QueryData, + F: QueryFilter, + C: QueryCache, + I: Iterator, +> { entity_iter: I, entities: &'w Entities, tables: &'w Tables, @@ -2070,8 +2121,14 @@ impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, I: Iterator> - QuerySortedManyIter<'w, 's, D, F, C, I> +impl< + 'w, + 's, + D: QueryData, + F: QueryFilter, + C: QueryCache, + I: DoubleEndedIterator, + > QuerySortedManyIter<'w, 's, D, F, C, I> { /// Get next result from the query #[inline(always)] @@ -2089,8 +2146,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, I: DoubleEndedIterator } } -impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, C: QueryCache, I: Iterator> Iterator - for QuerySortedManyIter<'w, 's, D, F, C, I> +impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, C: QueryCache, I: Iterator> + Iterator for QuerySortedManyIter<'w, 's, D, F, C, I> { type Item = D::Item<'w, 's>; @@ -2108,8 +2165,14 @@ impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, C: QueryCache, I: Iterator> - DoubleEndedIterator for QuerySortedManyIter<'w, 's, D, F, C, I> +impl< + 'w, + 's, + D: ReadOnlyQueryData, + F: QueryFilter, + C: QueryCache, + I: DoubleEndedIterator, + > DoubleEndedIterator for QuerySortedManyIter<'w, 's, D, F, C, I> { #[inline(always)] fn next_back(&mut self) -> Option { @@ -2121,8 +2184,14 @@ impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, C: QueryCache, I: DoubleEnded } } -impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, C: QueryCache, I: ExactSizeIterator> - ExactSizeIterator for QuerySortedManyIter<'w, 's, D, F, C, I> +impl< + 'w, + 's, + D: ReadOnlyQueryData, + F: QueryFilter, + C: QueryCache, + I: ExactSizeIterator, + > ExactSizeIterator for QuerySortedManyIter<'w, 's, D, F, C, I> { } @@ -2197,14 +2266,17 @@ impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, I: Iterator { +pub struct QueryCombinationIter<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, const K: usize> +{ tables: &'w Tables, archetypes: &'w Archetypes, query_state: &'s QueryState, cursors: [QueryIterationCursor<'w, 's, D, F, C>; K], } -impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, const K: usize> QueryCombinationIter<'w, 's, D, F, C, K> { +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, const K: usize> + QueryCombinationIter<'w, 's, D, F, C, K> +{ /// # Safety /// - `world` must have permission to access any of the components registered in `query_state`. /// - `world` must be the same one used to initialize `query_state`. @@ -2218,7 +2290,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, const K: usize> QueryC // Initialize array with cursors. // There is no FromIterator on arrays, so instead initialize it manually with MaybeUninit - let mut array: MaybeUninit<[QueryIterationCursor<'w, 's, D, F, C>; K]> = MaybeUninit::uninit(); + let mut array: MaybeUninit<[QueryIterationCursor<'w, 's, D, F, C>; K]> = + MaybeUninit::uninit(); let ptr = array .as_mut_ptr() .cast::>(); @@ -2349,7 +2422,8 @@ impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, C: QueryCache, const K: usize } } -impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> ExactSizeIterator for QueryIter<'w, 's, D, F, C> +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> ExactSizeIterator + for QueryIter<'w, 's, D, F, C> where F: ArchetypeFilter, { @@ -2400,12 +2474,14 @@ impl Clone for QueryIterationCursor filter: self.filter.clone(), current_len: self.current_len, current_row: self.current_row, - _cache: core::marker::PhantomData + _cache: core::marker::PhantomData, } } } -impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache + 's> QueryIterationCursor<'w, 's, D, F, C> { +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache + 's> + QueryIterationCursor<'w, 's, D, F, C> +{ /// # Safety /// - `world` must have permission to access any of the components registered in `query_state`. /// - `world` must be the same one used to initialize `query_state`. @@ -2433,10 +2509,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache + 's> QueryIterationCur let fetch = D::init_fetch(world, &query_state.fetch_state, last_run, this_run); let filter = F::init_fetch(world, &query_state.filter_state, last_run, this_run); let iteration_data = query_state.cache.iteration_data(query_state, world); - let (owned_storage_ids, storage_id_iter) =match iteration_data.storage_ids { - Cow::Borrowed(slice) => { - (None, slice.iter()) - } + let (owned_storage_ids, storage_id_iter) = match iteration_data.storage_ids { + Cow::Borrowed(slice) => (None, slice.iter()), Cow::Owned(owned) => { let owned_ids = Some(owned); let slice_ptr: *const [StorageId] = owned_ids.as_ref().unwrap().as_slice(); @@ -2463,7 +2537,6 @@ impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache + 's> QueryIterationCur } } - fn reborrow(&mut self) -> QueryIterationCursor<'_, 's, D, F, C> { QueryIterationCursor { is_dense: self.is_dense, @@ -2475,7 +2548,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache + 's> QueryIterationCur owned_storage_ids: self.owned_storage_ids.clone(), current_len: self.current_len, current_row: self.current_row, - _cache: core::marker::PhantomData + _cache: core::marker::PhantomData, } } @@ -2485,7 +2558,10 @@ impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache + 's> QueryIterationCur /// The result of `next` and any previous calls to `peek_last` with this row must have been /// dropped to prevent aliasing mutable references. #[inline] - unsafe fn peek_last(&mut self, query_state: &'s QueryState) -> Option> { + unsafe fn peek_last( + &mut self, + query_state: &'s QueryState, + ) -> Option> { if self.current_row > 0 { let index = self.current_row - 1; if self.is_dense { diff --git a/crates/bevy_ecs/src/query/mod.rs b/crates/bevy_ecs/src/query/mod.rs index d2c844caa2659..73b9345ac733a 100644 --- a/crates/bevy_ecs/src/query/mod.rs +++ b/crates/bevy_ecs/src/query/mod.rs @@ -2,6 +2,7 @@ mod access; mod builder; +mod cache; mod error; mod fetch; mod filter; @@ -9,7 +10,6 @@ mod iter; mod par_iter; mod state; mod world_query; -mod cache; pub use access::*; pub use bevy_ecs_macros::{QueryData, QueryFilter}; @@ -122,10 +122,10 @@ mod tests { world::{unsafe_world_cell::UnsafeWorldCell, World}, }; use alloc::{vec, vec::Vec}; + use bevy_ecs::query::CacheState; use bevy_ecs_macros::QueryFilter; use core::{any::type_name, fmt::Debug, hash::Hash}; use std::{collections::HashSet, println}; - use bevy_ecs::query::CacheState; #[derive(Component, Debug, Hash, Eq, PartialEq, Clone, Copy, PartialOrd, Ord)] struct A(usize); diff --git a/crates/bevy_ecs/src/query/par_iter.rs b/crates/bevy_ecs/src/query/par_iter.rs index 336d5e84cfd77..c6fa3b1a482ea 100644 --- a/crates/bevy_ecs/src/query/par_iter.rs +++ b/crates/bevy_ecs/src/query/par_iter.rs @@ -8,9 +8,9 @@ use crate::{ use super::{QueryData, QueryFilter, QueryItem, QueryState, ReadOnlyQueryData}; use alloc::vec::Vec; -use bevy_ecs::query::QueryCache; #[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))] -use bevy_ecs::query::QueryIterationData; +use bevy_ecs::query::IterationData; +use bevy_ecs::query::QueryCache; /// A parallel iterator over query results of a [`Query`](crate::system::Query). /// @@ -20,7 +20,7 @@ pub struct QueryParIter<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> { pub(crate) world: UnsafeWorldCell<'w>, pub(crate) state: &'s QueryState, #[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))] - pub(crate) iteration_data: C::IterationData<'s>, + pub(crate) iteration_data: IterationData<'s>, pub(crate) last_run: Tick, pub(crate) this_run: Tick, pub(crate) batching_strategy: BatchingStrategy, @@ -137,8 +137,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> QueryParIter<'w, 's, D #[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))] fn get_batch_size(&self, thread_count: usize) -> u32 { let max_items = || { - let id_iter = self.iteration_data.storage_ids(); - if self.iteration_data.is_dense() { + let id_iter = self.iteration_data.storage_ids.iter(); + if self.iteration_data.is_dense { // SAFETY: We only access table metadata. let tables = unsafe { &self.world.world_metadata().storages().tables }; id_iter @@ -166,7 +166,14 @@ impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> QueryParIter<'w, 's, D /// /// [`Entity`]: crate::entity::Entity /// [`Query::par_iter_many`]: crate::system::Query::par_iter_many -pub struct QueryParManyIter<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, E: EntityEquivalent> { +pub struct QueryParManyIter< + 'w, + 's, + D: QueryData, + F: QueryFilter, + C: QueryCache, + E: EntityEquivalent, +> { pub(crate) world: UnsafeWorldCell<'w>, pub(crate) state: &'s QueryState, pub(crate) entity_list: Vec, @@ -320,8 +327,14 @@ impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, C: QueryCache, E: EntityEquiv /// [`EntitySet`]: crate::entity::EntitySet /// [`Query::par_iter_many_unique`]: crate::system::Query::par_iter_many_unique /// [`Query::par_iter_many_unique_mut`]: crate::system::Query::par_iter_many_unique_mut -pub struct QueryParManyUniqueIter<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, E: EntityEquivalent + Sync> -{ +pub struct QueryParManyUniqueIter< + 'w, + 's, + D: QueryData, + F: QueryFilter, + C: QueryCache, + E: EntityEquivalent + Sync, +> { pub(crate) world: UnsafeWorldCell<'w>, pub(crate) state: &'s QueryState, pub(crate) entity_list: UniqueEntityEquivalentVec, diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index e3822f78bb389..1a5881fe268d2 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -1,11 +1,11 @@ use crate::{ - archetype::{Archetype, ArchetypeId}, + archetype::ArchetypeId, change_detection::Tick, entity::{Entity, EntityEquivalent, EntitySet, UniqueEntityArray}, entity_disabling::DefaultQueryFilters, prelude::FromWorld, query::{FilteredAccess, QueryCombinationIter, QueryIter, QueryParIter, WorldQuery}, - storage::{TableId}, + storage::TableId, system::Query, world::{unsafe_world_cell::UnsafeWorldCell, World, WorldId}, }; @@ -13,17 +13,15 @@ use crate::{ #[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))] use crate::entity::UniqueEntityEquivalentSlice; -use bevy_utils::prelude::DebugName; -use core::{fmt, ptr}; -#[cfg(feature = "trace")] -use tracing::Span; -use bevy_ecs::query::cache::{CacheState, QueryCache}; -#[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))] -use bevy_ecs::query::IterationData; use super::{ NopWorldQuery, QueryBuilder, QueryData, QueryEntityError, QueryFilter, QueryManyIter, QueryManyUniqueIter, QuerySingleError, ROQueryItem, ReadOnlyQueryData, }; +use bevy_ecs::query::cache::{CacheState, QueryCache}; +use bevy_utils::prelude::DebugName; +use core::{fmt, ptr}; +#[cfg(feature = "trace")] +use tracing::Span; /// An ID for either a table or an archetype. Used for Query iteration. /// @@ -80,7 +78,6 @@ pub struct QueryState fmt::Debug for QueryState { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("QueryState") @@ -97,7 +94,6 @@ impl FromWorld for QueryState { } impl QueryState { - /// Converts this `QueryState` reference to a `QueryState` that does not access anything mutably. pub fn as_readonly(&self) -> &QueryState { // SAFETY: invariant on `WorldQuery` trait upholds that `D::ReadOnly` and `F::ReadOnly` @@ -1179,7 +1175,10 @@ impl QueryState { /// [`par_iter`]: Self::par_iter /// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool #[inline] - pub fn par_iter_mut<'w, 's>(&'s mut self, world: &'w mut World) -> QueryParIter<'w, 's, D, F, C> { + pub fn par_iter_mut<'w, 's>( + &'s mut self, + world: &'w mut World, + ) -> QueryParIter<'w, 's, D, F, C> { self.query_mut(world).par_iter_inner() } @@ -1216,7 +1215,7 @@ impl QueryState { // QueryIter, QueryIterationCursor, QueryManyIter, QueryCombinationIter,QueryState::par_fold_init_unchecked_manual, // QueryState::par_many_fold_init_unchecked_manual, QueryState::par_many_unique_fold_init_unchecked_manual use arrayvec::ArrayVec; - let iteration_data = self.iteration_data(world); + let iteration_data = self.cache.iteration_data(self, world); bevy_tasks::ComputeTaskPool::get().scope(|scope| { // SAFETY: We only access table data that has been registered in `self.component_access`. @@ -1265,14 +1264,14 @@ impl QueryState { }; let storage_entity_count = |storage_id: StorageId| -> u32 { - if iteration_data.is_dense() { + if iteration_data.is_dense { tables[storage_id.table_id].entity_count() } else { archetypes[storage_id.archetype_id].len() } }; - for storage_id in &iteration_data.storage_ids() { + for storage_id in iteration_data.storage_ids.iter() { let count = storage_entity_count(*storage_id); // skip empty storage @@ -1568,7 +1567,9 @@ impl QueryState { } } -impl From> for QueryState { +impl From> + for QueryState +{ fn from(mut value: QueryBuilder) -> Self { QueryState::from_builder(&mut value) } diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index 489f7cbadae25..1ffcc34637cfc 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -11,12 +11,12 @@ use crate::{ }, world::unsafe_world_cell::UnsafeWorldCell, }; +use bevy_ecs::query::{CacheState, QueryCache}; use core::{ marker::PhantomData, mem::MaybeUninit, ops::{Deref, DerefMut}, }; -use bevy_ecs::query::{CacheState, QueryCache}; /// A [system parameter] that provides selective access to the [`Component`] data stored in a [`World`]. /// @@ -791,7 +791,9 @@ impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> Query<'w, 's, D, F, C> /// - [`iter_combinations`](Self::iter_combinations) for read-only query item combinations. /// - [`iter_combinations_mut`](Self::iter_combinations_mut) for mutable query item combinations. #[inline] - pub fn iter_combinations_inner(self) -> QueryCombinationIter<'w, 's, D, F, C, K> { + pub fn iter_combinations_inner( + self, + ) -> QueryCombinationIter<'w, 's, D, F, C, K> { // SAFETY: `self.world` has permission to access the required components. unsafe { QueryCombinationIter::new(self.world, self.state, self.last_run, self.this_run) } } @@ -1259,7 +1261,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> Query<'w, 's, D, F, C> world: self.world, state: self.state, #[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))] - iteration_data: self.state.iteration_data(self.world), + iteration_data: self.state.cache.iteration_data(self.state, self.world), last_run: self.last_run, this_run: self.this_run, batching_strategy: BatchingStrategy::new(), @@ -2511,7 +2513,9 @@ impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> IntoIterator for Query } } -impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> IntoIterator for &'w Query<'_, 's, D, F, C> { +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> IntoIterator + for &'w Query<'_, 's, D, F, C> +{ type Item = ROQueryItem<'w, 's, D>; type IntoIter = QueryIter<'w, 's, D::ReadOnly, F, C>; @@ -2520,7 +2524,9 @@ impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> IntoIterator for &'w Q } } -impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> IntoIterator for &'w mut Query<'_, 's, D, F, C> { +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> IntoIterator + for &'w mut Query<'_, 's, D, F, C> +{ type Item = D::Item<'w, 's>; type IntoIter = QueryIter<'w, 's, D, F, C>; @@ -2675,7 +2681,9 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Single<'w, 's, D, F> { /// See [`Query`] for more details. /// /// [System parameter]: crate::system::SystemParam -pub struct Populated<'w, 's, D: QueryData, F: QueryFilter = (), C: QueryCache = CacheState>(pub(crate) Query<'w, 's, D, F, C>); +pub struct Populated<'w, 's, D: QueryData, F: QueryFilter = (), C: QueryCache = CacheState>( + pub(crate) Query<'w, 's, D, F, C>, +); impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> Deref for Populated<'w, 's, D, F, C> { type Target = Query<'w, 's, D, F, C>; @@ -2698,7 +2706,9 @@ impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> Populated<'w, 's, D, F } } -impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> IntoIterator for Populated<'w, 's, D, F, C> { +impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> IntoIterator + for Populated<'w, 's, D, F, C> +{ type Item = as IntoIterator>::Item; type IntoIter = as IntoIterator>::IntoIter; @@ -2708,7 +2718,9 @@ impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> IntoIterator for Popul } } -impl<'a, 'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> IntoIterator for &'a Populated<'w, 's, D, F, C> { +impl<'a, 'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> IntoIterator + for &'a Populated<'w, 's, D, F, C> +{ type Item = <&'a Query<'w, 's, D, F, C> as IntoIterator>::Item; type IntoIter = <&'a Query<'w, 's, D, F, C> as IntoIterator>::IntoIter; @@ -2718,7 +2730,9 @@ impl<'a, 'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> IntoIterator for & } } -impl<'a, 'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> IntoIterator for &'a mut Populated<'w, 's, D, F, C> { +impl<'a, 'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> IntoIterator + for &'a mut Populated<'w, 's, D, F, C> +{ type Item = <&'a mut Query<'w, 's, D, F, C> as IntoIterator>::Item; type IntoIter = <&'a mut Query<'w, 's, D, F, C> as IntoIterator>::IntoIter; From 4aff343fb2f425140c4dd6a665bba19489c8b86d Mon Sep 17 00:00:00 2001 From: Charles Bournhonesque Date: Mon, 20 Oct 2025 01:47:57 -0400 Subject: [PATCH 04/10] clippy --- crates/bevy_ecs/src/query/cache.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/crates/bevy_ecs/src/query/cache.rs b/crates/bevy_ecs/src/query/cache.rs index 4b4eaabfb6ac2..76e4999a30af3 100644 --- a/crates/bevy_ecs/src/query/cache.rs +++ b/crates/bevy_ecs/src/query/cache.rs @@ -20,7 +20,9 @@ impl QueryState { // This is safe because `QueryState<..., Uncached>` is a // valid "prefix" of `QueryState<..., C>`, and QueryState uses `repr(c)` let rest: &QueryState = - unsafe { &*(self as *mut Self as *const QueryState) }; + // SAFETY: This is safe because `QueryState<..., Uncached>` is a + // valid "prefix" of `QueryState<..., C>`, and QueryState uses `repr(c)` + unsafe { &*(core::ptr::from_mut::(self) as *const QueryState) }; // This is safe because `cache` is disjoint from the prefix. let cache_mut: &mut C = &mut self.cache; @@ -111,7 +113,7 @@ impl QueryState { if unsafe { self.matches_archetype(archetype) } { f(archetype); } - }) + }); } else { // if there are required components, we can optimize by only iterating through archetypes // that contain at least one of the required components @@ -139,7 +141,7 @@ impl QueryState { // SAFETY: The validate_world call ensures that the world is the same the QueryState // was initialized from. if unsafe { self.matches_archetype(archetype) } { - f(archetype) + f(archetype); } } } @@ -275,7 +277,7 @@ impl QueryCache for CacheState { world.archetypes().generation(), ); uncached.iter_archetypes(old_generation, world.archetypes(), |archetype| { - self.cache_archetype(archetype) + self.cache_archetype(archetype); }); } @@ -387,7 +389,7 @@ impl QueryCache for Uncached { StorageId { table_id: archetype.table_id(), } - }) + }); }); IterationData { is_dense: self.is_dense, From 7fceef08c1987185508e030a46f8d418ffb5172a Mon Sep 17 00:00:00 2001 From: Charles Bournhonesque Date: Mon, 20 Oct 2025 20:00:41 -0400 Subject: [PATCH 05/10] use query prefix Change-Id: I688258fc8dd59674ad6ba252b9d8dde7e8d8daaf --- crates/bevy_ecs/src/query/cache.rs | 168 +++++++++++++++++------------ crates/bevy_ecs/src/query/state.rs | 37 ++++++- 2 files changed, 136 insertions(+), 69 deletions(-) diff --git a/crates/bevy_ecs/src/query/cache.rs b/crates/bevy_ecs/src/query/cache.rs index 76e4999a30af3..ee089f065f016 100644 --- a/crates/bevy_ecs/src/query/cache.rs +++ b/crates/bevy_ecs/src/query/cache.rs @@ -6,80 +6,40 @@ use bevy_ecs::component::ComponentId; use bevy_ecs::entity_disabling::DefaultQueryFilters; use bevy_ecs::prelude::World; use bevy_ecs::query::state::StorageId; -use bevy_ecs::query::{QueryBuilder, QueryData, QueryFilter}; +use bevy_ecs::query::{FilteredAccess, QueryBuilder, QueryData, QueryFilter}; use bevy_ecs::storage::{SparseSetIndex, TableId}; use bevy_ecs::world::unsafe_world_cell::UnsafeWorldCell; +use bevy_ecs::world::WorldId; use core::fmt::{Debug, Formatter}; use fixedbitset::FixedBitSet; use log::warn; -impl QueryState { - /// Splits self into an immutable view of the "prefix" - /// (all fields *except* cache) and a mutable ref to the `cache`. - pub fn split_cache(&mut self) -> (&QueryState, &mut C) { - // This is safe because `QueryState<..., Uncached>` is a - // valid "prefix" of `QueryState<..., C>`, and QueryState uses `repr(c)` - let rest: &QueryState = - // SAFETY: This is safe because `QueryState<..., Uncached>` is a - // valid "prefix" of `QueryState<..., C>`, and QueryState uses `repr(c)` - unsafe { &*(core::ptr::from_mut::(self) as *const QueryState) }; - - // This is safe because `cache` is disjoint from the prefix. - let cache_mut: &mut C = &mut self.cache; - - (rest, cache_mut) - } - - /// Updates the state's internal view of the [`World`]'s archetypes. If this is not called before querying data, - /// the results may not accurately reflect what is in the `world`. - /// - /// This is only required if a `manual` method (such as [`Self::get_manual`]) is being called, and it only needs to - /// be called if the `world` has been structurally mutated (i.e. added/removed a component or resource). Users using - /// non-`manual` methods such as [`QueryState::get`] do not need to call this as it will be automatically called for them. - /// - /// If you have an [`UnsafeWorldCell`] instead of `&World`, consider using [`QueryState::update_archetypes_unsafe_world_cell`]. - /// - /// # Panics - /// - /// If `world` does not match the one used to call `QueryState::new` for this instance. - pub fn update_archetypes(&mut self, world: &World) { - self.update_archetypes_unsafe_world_cell(world.as_unsafe_world_cell_readonly()); - } - - /// Updates the state's internal view of the `world`'s archetypes. If this is not called before querying data, - /// the results may not accurately reflect what is in the `world`. - /// - /// This is only required if a `manual` method (such as [`Self::get_manual`]) is being called, and it only needs to - /// be called if the `world` has been structurally mutated (i.e. added/removed a component or resource). Users using - /// non-`manual` methods such as [`QueryState::get`] do not need to call this as it will be automatically called for them. - /// - /// # Note - /// - /// This method only accesses world metadata. - /// - /// # Panics - /// - /// If `world` does not match the one used to call `QueryState::new` for this instance. - pub fn update_archetypes_unsafe_world_cell(&mut self, world: UnsafeWorldCell) { - let (uncached_state, cache) = self.split_cache(); - cache.update_archetypes(uncached_state, world); - } +/// Borrow-only view over the non-cache fields of a QueryState. +pub struct QueryPrefix<'a, D: QueryData, F: QueryFilter> { + pub(crate) world_id: WorldId, + pub(crate) component_access: &'a FilteredAccess, + pub(crate) fetch_state: &'a D::State, + pub(crate) filter_state: &'a F::State, +} - /// Safety: todo. - pub(crate) fn matches(&self, archetype: &Archetype) -> bool { - // SAFETY: from parent function's safety constraint - unsafe { self.cache.contains(self, archetype) } +impl<'a, D: QueryData, F: QueryFilter> QueryPrefix<'a, D, F> { + #[inline] + pub fn validate_world(&self, world_id: WorldId) { + if self.world_id != world_id { + panic!( + "Encountered a mismatched World. This QueryState was created from {:?}, but a method was called using {:?}.", + self.world_id, world_id + ); + } } -} -impl QueryState { /// Returns `true` if this query matches a set of components. Otherwise, returns `false`. /// /// # Safety /// `archetype` must be from the `World` this state was initialized from. - unsafe fn matches_archetype(&self, archetype: &Archetype) -> bool { - D::matches_component_set(&self.fetch_state, &|id| archetype.contains(id)) - && F::matches_component_set(&self.filter_state, &|id| archetype.contains(id)) + pub unsafe fn matches_archetype(&self, archetype: &Archetype) -> bool { + D::matches_component_set(self.fetch_state, &|id| archetype.contains(id)) + && F::matches_component_set(self.filter_state, &|id| archetype.contains(id)) && self.matches_component_set(&|id| archetype.contains(id)) } @@ -96,9 +56,9 @@ impl QueryState { }) } - /// Iterate through all archetypes that match the [`QueryState`] with an [`ArchetypeGeneration`] higher than the provided one, + /// Iterate through all archetypes that match the query with an ArchetypeGeneration higher than the provided one, /// and call `f` on each of them. - fn iter_archetypes( + pub fn iter_archetypes( &self, archetype_generation: ArchetypeGeneration, archetypes: &Archetypes, @@ -149,6 +109,80 @@ impl QueryState { } } +impl QueryState { + + + /// Updates the state's internal view of the [`World`]'s archetypes. If this is not called before querying data, + /// the results may not accurately reflect what is in the `world`. + /// + /// This is only required if a `manual` method (such as [`Self::get_manual`]) is being called, and it only needs to + /// be called if the `world` has been structurally mutated (i.e. added/removed a component or resource). Users using + /// non-`manual` methods such as [`QueryState::get`] do not need to call this as it will be automatically called for them. + /// + /// If you have an [`UnsafeWorldCell`] instead of `&World`, consider using [`QueryState::update_archetypes_unsafe_world_cell`]. + /// + /// # Panics + /// + /// If `world` does not match the one used to call `QueryState::new` for this instance. + pub fn update_archetypes(&mut self, world: &World) { + self.update_archetypes_unsafe_world_cell(world.as_unsafe_world_cell_readonly()); + } + + /// Updates the state's internal view of the `world`'s archetypes. If this is not called before querying data, + /// the results may not accurately reflect what is in the `world`. + /// + /// This is only required if a `manual` method (such as [`Self::get_manual`]) is being called, and it only needs to + /// be called if the `world` has been structurally mutated (i.e. added/removed a component or resource). Users using + /// non-`manual` methods such as [`QueryState::get`] do not need to call this as it will be automatically called for them. + /// + /// # Note + /// + /// This method only accesses world metadata. + /// + /// # Panics + /// + /// If `world` does not match the one used to call `QueryState::new` for this instance. + pub fn update_archetypes_unsafe_world_cell(&mut self, world: UnsafeWorldCell) { + self.with_prefix(|prefix, cache| { + cache.update_archetypes(&prefix, world); + }); + } + + /// Safety: todo. + pub(crate) fn matches(&self, archetype: &Archetype) -> bool { + // SAFETY: from parent function's safety constraint + unsafe { self.cache.contains(self, archetype) } + } +} + +impl QueryState { + /// Returns `true` if this query matches a set of components. Otherwise, returns `false`. + /// + /// # Safety + /// `archetype` must be from the `World` this state was initialized from. + unsafe fn matches_archetype(&self, archetype: &Archetype) -> bool { + // Delegate to read-only prefix to avoid duplication + unsafe { self.as_prefix().matches_archetype(archetype) } + } + + /// Returns `true` if this query matches a set of components. Otherwise, returns `false`. + pub fn matches_component_set(&self, set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { + self.as_prefix().matches_component_set(set_contains_id) + } + + /// Iterate through all archetypes that match the [`QueryState`] with an [`ArchetypeGeneration`] higher than the provided one, + /// and call `f` on each of them. + fn iter_archetypes( + &self, + archetype_generation: ArchetypeGeneration, + archetypes: &Archetypes, + mut f: impl FnMut(&Archetype), + ) { + self.as_prefix() + .iter_archetypes(archetype_generation, archetypes, f) + } +} + /// Types that can cache archetypes matched by a `Query`. pub trait QueryCache: Debug + Clone + Sync { /// Returns the data needed to iterate through the archetypes that match the query. @@ -177,7 +211,7 @@ pub trait QueryCache: Debug + Clone + Sync { /// Update the [`QueryCache`] by storing in the cache every new archetypes that match the query. fn update_archetypes( &mut self, - uncached: &QueryState, + prefix: &QueryPrefix<'_, D, F>, world: UnsafeWorldCell, ); @@ -264,10 +298,10 @@ impl QueryCache for CacheState { } fn update_archetypes( &mut self, - uncached: &QueryState, + prefix: &QueryPrefix<'_, D, F>, world: UnsafeWorldCell, ) { - uncached.validate_world(world.id()); + prefix.validate_world(world.id()); if self.archetype_generation == world.archetypes().generation() { // skip if we are already up to date return; @@ -276,7 +310,7 @@ impl QueryCache for CacheState { &mut self.archetype_generation, world.archetypes().generation(), ); - uncached.iter_archetypes(old_generation, world.archetypes(), |archetype| { + prefix.iter_archetypes(old_generation, world.archetypes(), |archetype| { self.cache_archetype(archetype); }); } diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 1a5881fe268d2..8efbbd5c2d256 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -17,7 +17,7 @@ use super::{ NopWorldQuery, QueryBuilder, QueryData, QueryEntityError, QueryFilter, QueryManyIter, QueryManyUniqueIter, QuerySingleError, ROQueryItem, ReadOnlyQueryData, }; -use bevy_ecs::query::cache::{CacheState, QueryCache}; +use bevy_ecs::query::cache::{CacheState, QueryCache, QueryPrefix, Uncached}; use bevy_utils::prelude::DebugName; use core::{fmt, ptr}; #[cfg(feature = "trace")] @@ -78,6 +78,9 @@ pub struct QueryState = QueryState; + impl fmt::Debug for QueryState { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("QueryState") @@ -94,6 +97,36 @@ impl FromWorld for QueryState { } impl QueryState { + /// Returns a borrow-only prefix view for read-only operations. + #[inline] + pub(crate) fn as_prefix(&self) -> QueryPrefix<'_, D, F> { + QueryPrefix { + world_id: self.world_id, + component_access: &self.component_access, + fetch_state: &self.fetch_state, + filter_state: &self.filter_state, + } + } + /// Safely provides access to a borrow-only prefix view and a mutable reference to the cache. + #[inline] + pub fn with_prefix(&mut self, f: impl for<'a> FnOnce(QueryPrefix<'a, D, F>, &mut C) -> R) -> R { + // Create raw pointers to immutably borrowed fields to avoid overlapping borrows with &mut cache. + let world_id = self.world_id; + let component_access = ptr::from_ref(&self.component_access); + let fetch_state = ptr::from_ref(&self.fetch_state); + let filter_state = ptr::from_ref(&self.filter_state); + // SAFETY: We only create shared references to fields distinct from `cache` and do not move `self`. + let prefix = unsafe { + QueryPrefix { + world_id, + component_access: &*component_access, + fetch_state: &*fetch_state, + filter_state: &*filter_state, + } + }; + let cache = &mut self.cache; + f(prefix, cache) + } /// Converts this `QueryState` reference to a `QueryState` that does not access anything mutably. pub fn as_readonly(&self) -> &QueryState { // SAFETY: invariant on `WorldQuery` trait upholds that `D::ReadOnly` and `F::ReadOnly` @@ -931,7 +964,7 @@ impl QueryState { /// /// The `iter_combinations` method does not guarantee order of iteration. /// - /// This iterator is always guaranteed to return results from each unique pair of matching entities. + /// This iterator is always guaranteed to return results from each unique pai/* */r of matching entities. /// Iteration order is not guaranteed. /// /// This can only be called for read-only queries, see [`Self::iter_combinations_mut`] for From ef9ff96a84863c460de5f1f186711e589a289706 Mon Sep 17 00:00:00 2001 From: Charles Bournhonesque Date: Mon, 20 Oct 2025 20:14:40 -0400 Subject: [PATCH 06/10] store StorageId as Cow<[StorageId]> Change-Id: I0953e9836f8ddfd8923c4b00132d9e653c575572 --- crates/bevy_ecs/src/query/cache.rs | 2 +- crates/bevy_ecs/src/query/iter.rs | 63 +++++++++++++++++------------- crates/bevy_ecs/src/query/state.rs | 7 +++- 3 files changed, 43 insertions(+), 29 deletions(-) diff --git a/crates/bevy_ecs/src/query/cache.rs b/crates/bevy_ecs/src/query/cache.rs index ee089f065f016..f03cb6c1bb8d0 100644 --- a/crates/bevy_ecs/src/query/cache.rs +++ b/crates/bevy_ecs/src/query/cache.rs @@ -463,7 +463,7 @@ impl QueryCache for Uncached { /// We do not update anything. This is here for feature parity. fn update_archetypes( &mut self, - _: &QueryState, + _: &QueryPrefix, _: UnsafeWorldCell, ) { } diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index e1033bb838ebd..02b990c57b76d 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -945,7 +945,12 @@ impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> Iterator for QueryIter accum = func(accum, item); } - for id in self.cursor.storage_id_iter.clone().copied() { + let mut i = self.cursor.storage_index; + let len = self.cursor.storage_ids.as_ref().len(); + while i < len { + // Take a short-lived copy of the id to avoid holding a borrow of `self` across the call + let id = self.cursor.storage_ids.as_ref()[i]; + i += 1; // SAFETY: // - The range(None) is equivalent to [0, storage.entity_count) accum = unsafe { self.fold_over_storage_range(accum, &mut func, id, None) }; @@ -2449,8 +2454,10 @@ impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, const K: usize> Debug struct QueryIterationCursor<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> { // whether the query iteration is dense or not. is_dense: bool, - storage_id_iter: core::slice::Iter<'s, StorageId>, - owned_storage_ids: Option>, + // Storage ids to iterate over; owned for uncached, borrowed for cached + storage_ids: Cow<'s, [StorageId]>, + // Current index into storage_ids + storage_index: usize, table_entities: &'w [Entity], archetype_entities: &'w [ArchetypeEntity], fetch: D::Fetch<'w>, @@ -2466,8 +2473,8 @@ impl Clone for QueryIterationCursor fn clone(&self) -> Self { Self { is_dense: self.is_dense, - storage_id_iter: self.storage_id_iter.clone(), - owned_storage_ids: self.owned_storage_ids.clone(), + storage_ids: self.storage_ids.clone(), + storage_index: self.storage_index, table_entities: self.table_entities, archetype_entities: self.archetype_entities, fetch: self.fetch.clone(), @@ -2492,7 +2499,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache + 's> this_run: Tick, ) -> Self { QueryIterationCursor { - storage_id_iter: [].iter(), + storage_ids: Cow::Borrowed(&[]), + storage_index: 0, ..Self::init(world, query_state, last_run, this_run) } } @@ -2509,27 +2517,14 @@ impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache + 's> let fetch = D::init_fetch(world, &query_state.fetch_state, last_run, this_run); let filter = F::init_fetch(world, &query_state.filter_state, last_run, this_run); let iteration_data = query_state.cache.iteration_data(query_state, world); - let (owned_storage_ids, storage_id_iter) = match iteration_data.storage_ids { - Cow::Borrowed(slice) => (None, slice.iter()), - Cow::Owned(owned) => { - let owned_ids = Some(owned); - let slice_ptr: *const [StorageId] = owned_ids.as_ref().unwrap().as_slice(); - // SAFETY: - // - The slice pointer refers to memory owned by `owned_ids` - // - `owned_ids` is moved *as a whole* into the struct below and won’t move afterward - // - The struct owns `owned_ids` for the same lifetime as `ids_iter` - // => Safe as long as `Cursor` isn’t moved after creation (so don’t mem::swap etc.) - let ids_iter = unsafe { (&*slice_ptr).iter() }; - (owned_ids, ids_iter) - } - }; + let storage_ids = iteration_data.storage_ids; QueryIterationCursor { fetch, filter, table_entities: &[], archetype_entities: &[], - storage_id_iter, - owned_storage_ids, + storage_ids, + storage_index: 0, is_dense: iteration_data.is_dense, current_len: 0, current_row: 0, @@ -2544,8 +2539,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache + 's> filter: F::shrink_fetch(self.filter.clone()), table_entities: self.table_entities, archetype_entities: self.archetype_entities, - storage_id_iter: self.storage_id_iter.clone(), - owned_storage_ids: self.owned_storage_ids.clone(), + storage_ids: self.storage_ids.clone(), + storage_index: self.storage_index, current_len: self.current_len, current_row: self.current_row, _cache: core::marker::PhantomData, @@ -2605,7 +2600,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache + 's> /// Note that if `F::IS_ARCHETYPAL`, the return value /// will be **the exact count of remaining values**. fn max_remaining(&self, tables: &'w Tables, archetypes: &'w Archetypes) -> u32 { - let ids = self.storage_id_iter.clone(); + let ids = self.storage_ids.as_ref()[self.storage_index..].iter().copied(); let remaining_matched: u32 = if self.is_dense { // SAFETY: The if check ensures that storage_id_iter stores TableIds unsafe { ids.map(|id| tables[id.table_id].entity_count()).sum() } @@ -2635,7 +2630,14 @@ impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache + 's> loop { // we are on the beginning of the query, or finished processing a table, so skip to the next if self.current_row == self.current_len { - let table_id = self.storage_id_iter.next()?.table_id; + let table_id = { + let id = *self + .storage_ids + .as_ref() + .get(self.storage_index)?; + self.storage_index += 1; + id.table_id + }; let table = tables.get(table_id).debug_checked_unwrap(); if table.is_empty() { continue; @@ -2676,7 +2678,14 @@ impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache + 's> } else { loop { if self.current_row == self.current_len { - let archetype_id = self.storage_id_iter.next()?.archetype_id; + let archetype_id = { + let id = *self + .storage_ids + .as_ref() + .get(self.storage_index)?; + self.storage_index += 1; + id.archetype_id + }; let archetype = archetypes.get(archetype_id).debug_checked_unwrap(); if archetype.is_empty() { continue; diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 8efbbd5c2d256..7e3cc60149538 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -57,11 +57,16 @@ pub(super) union StorageId { /// [`State`]: crate::query::world_query::WorldQuery::State /// [`Fetch`]: crate::query::world_query::WorldQuery::Fetch /// [`Table`]: crate::storage::Table +#[cfg(feature = "query_uncached_default")] +pub type DefaultCache = Uncached; +#[cfg(not(feature = "query_uncached_default"))] +pub type DefaultCache = CacheState; + #[repr(C)] // SAFETY NOTE: // Do not add any new fields that use the `D` or `F` generic parameters as this may // make `QueryState::as_transmuted_state` unsound if not done with care. -pub struct QueryState { +pub struct QueryState { world_id: WorldId, /// [`FilteredAccess`] computed by combining the `D` and `F` access. Used to check which other queries /// this query can run in parallel with. From 16bbc33b686406c27c6b7079afa6625f85c9e629 Mon Sep 17 00:00:00 2001 From: Charles Bournhonesque Date: Tue, 21 Oct 2025 00:09:32 -0400 Subject: [PATCH 07/10] fix unsafe, add release guide, run all tests for uncached queries --- crates/bevy_ecs/Cargo.toml | 4 + crates/bevy_ecs/src/entity_disabling.rs | 10 +- crates/bevy_ecs/src/lib.rs | 15 +- crates/bevy_ecs/src/query/cache.rs | 165 +++++++++--------- crates/bevy_ecs/src/query/iter.rs | 16 +- crates/bevy_ecs/src/query/mod.rs | 2 +- crates/bevy_ecs/src/query/state.rs | 58 +++--- crates/bevy_ecs/src/system/mod.rs | 6 +- crates/bevy_ecs/src/system/query.rs | 30 +++- crates/bevy_ecs/src/world/mod.rs | 123 +++++++++++++ .../release-notes/uncached_queries.md | 22 +++ tools/ci/src/commands/test.rs | 7 + 12 files changed, 311 insertions(+), 147 deletions(-) create mode 100644 release-content/release-notes/uncached_queries.md diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index 98b9c33bb07c8..43b8d8b7ea66f 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -52,6 +52,10 @@ bevy_debug_stepping = [] ## This will often provide more detailed error messages. track_location = [] +## Makes queries become uncached by default. +## Mostly useful for unit tests. +query_uncached_default = [] + # Executor Backend ## Uses `async-executor` as a task execution backend. diff --git a/crates/bevy_ecs/src/entity_disabling.rs b/crates/bevy_ecs/src/entity_disabling.rs index c2fc5ee6c53b9..c251b479364d2 100644 --- a/crates/bevy_ecs/src/entity_disabling.rs +++ b/crates/bevy_ecs/src/entity_disabling.rs @@ -97,7 +97,7 @@ //! [`Query` performance]: crate::prelude::Query#performance use crate::{ - component::{ComponentId, Components, StorageType}, + component::ComponentId, query::FilteredAccess, world::{FromWorld, World}, }; @@ -248,14 +248,6 @@ impl DefaultQueryFilters { } } } - - pub(super) fn is_dense(&self, components: &Components) -> bool { - self.disabling_ids().all(|component_id| { - components - .get_info(component_id) - .is_some_and(|info| info.storage_type() == StorageType::Table) - }) - } } #[cfg(test)] diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index d2f59ac29f83d..d639f4ad2bd45 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -88,7 +88,10 @@ pub mod prelude { message::{Message, MessageMutator, MessageReader, MessageWriter, Messages}, name::{Name, NameOrEntity}, observer::{Observer, On, Trigger}, - query::{Added, Allow, AnyOf, Changed, Has, Or, QueryBuilder, QueryState, With, Without}, + query::{ + Added, Allow, AnyOf, Changed, Has, Or, QueryBuilder, QueryState, UncachedQueryState, + With, Without, + }, related, relationship::RelationshipTarget, resource::Resource, @@ -101,7 +104,7 @@ pub mod prelude { Command, Commands, Deferred, EntityCommand, EntityCommands, If, In, InMut, InRef, IntoSystem, Local, NonSend, NonSendMut, ParamSet, Populated, Query, ReadOnlySystem, Res, ResMut, Single, System, SystemIn, SystemInput, SystemParamBuilder, - SystemParamFunction, + SystemParamFunction, UncachedQuery, }, world::{ EntityMut, EntityRef, EntityWorldMut, FilteredResources, FilteredResourcesMut, @@ -475,8 +478,12 @@ mod tests { assert_eq!(ents, &[(e, A(123))]); let f = world.spawn((TableStored("def"), A(456), B(1))).id(); - let ents = query.iter(&world).map(|(e, &i)| (e, i)).collect::>(); - assert_eq!(ents, &[(e, A(123)), (f, A(456))]); + let ents = query + .iter(&world) + .map(|(e, &i)| (e, i)) + .collect::>(); + assert!(ents.contains(&(e, A(123)))); + assert!(ents.contains(&(f, A(456)))); } #[test] diff --git a/crates/bevy_ecs/src/query/cache.rs b/crates/bevy_ecs/src/query/cache.rs index f03cb6c1bb8d0..2e318bf9101f0 100644 --- a/crates/bevy_ecs/src/query/cache.rs +++ b/crates/bevy_ecs/src/query/cache.rs @@ -3,7 +3,6 @@ use alloc::borrow::Cow; use alloc::vec::Vec; use bevy_ecs::archetype::{Archetype, ArchetypeGeneration, ArchetypeId, Archetypes}; use bevy_ecs::component::ComponentId; -use bevy_ecs::entity_disabling::DefaultQueryFilters; use bevy_ecs::prelude::World; use bevy_ecs::query::state::StorageId; use bevy_ecs::query::{FilteredAccess, QueryBuilder, QueryData, QueryFilter}; @@ -15,48 +14,55 @@ use fixedbitset::FixedBitSet; use log::warn; /// Borrow-only view over the non-cache fields of a QueryState. -pub struct QueryPrefix<'a, D: QueryData, F: QueryFilter> { +#[doc(hidden)] +pub struct UncachedQueryBorrow<'a, D: QueryData, F: QueryFilter> { pub(crate) world_id: WorldId, pub(crate) component_access: &'a FilteredAccess, pub(crate) fetch_state: &'a D::State, pub(crate) filter_state: &'a F::State, } -impl<'a, D: QueryData, F: QueryFilter> QueryPrefix<'a, D, F> { +impl<'a, D: QueryData, F: QueryFilter> UncachedQueryBorrow<'a, D, F> { + /// # Panics + /// + /// If `world_id` does not match the [`World`] used to call `QueryState::new` for this instance. + /// + /// Many unsafe query methods require the world to match for soundness. This function is the easiest + /// way of ensuring that it matches. #[inline] + #[track_caller] pub fn validate_world(&self, world_id: WorldId) { + #[inline(never)] + #[track_caller] + #[cold] + fn panic_mismatched(this: WorldId, other: WorldId) -> ! { + panic!("Encountered a mismatched World. This QueryState was created from {this:?}, but a method was called using {other:?}."); + } + if self.world_id != world_id { - panic!( - "Encountered a mismatched World. This QueryState was created from {:?}, but a method was called using {:?}.", - self.world_id, world_id - ); + panic_mismatched(self.world_id, world_id); } } /// Returns `true` if this query matches a set of components. Otherwise, returns `false`. /// /// # Safety - /// `archetype` must be from the `World` this state was initialized from. + /// `archetype` must be from the `World` this [`UncachedQueryBorrow`] was initialized from. pub unsafe fn matches_archetype(&self, archetype: &Archetype) -> bool { D::matches_component_set(self.fetch_state, &|id| archetype.contains(id)) && F::matches_component_set(self.filter_state, &|id| archetype.contains(id)) - && self.matches_component_set(&|id| archetype.contains(id)) - } - - /// Returns `true` if this query matches a set of components. Otherwise, returns `false`. - pub fn matches_component_set(&self, set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { - self.component_access.filter_sets.iter().any(|set| { - set.with - .ones() - .all(|index| set_contains_id(ComponentId::get_sparse_set_index(index))) - && set - .without + && self.component_access.filter_sets.iter().any(|set| { + set.with .ones() - .all(|index| !set_contains_id(ComponentId::get_sparse_set_index(index))) - }) + .all(|index| archetype.contains(ComponentId::get_sparse_set_index(index))) + && set + .without + .ones() + .all(|index| !archetype.contains(ComponentId::get_sparse_set_index(index))) + }) } - /// Iterate through all archetypes that match the query with an ArchetypeGeneration higher than the provided one, + /// Iterate through all new archetypes more recent than the provided [`ArchetypeGeneration`], /// and call `f` on each of them. pub fn iter_archetypes( &self, @@ -110,8 +116,6 @@ impl<'a, D: QueryData, F: QueryFilter> QueryPrefix<'a, D, F> { } impl QueryState { - - /// Updates the state's internal view of the [`World`]'s archetypes. If this is not called before querying data, /// the results may not accurately reflect what is in the `world`. /// @@ -143,57 +147,49 @@ impl QueryState { /// /// If `world` does not match the one used to call `QueryState::new` for this instance. pub fn update_archetypes_unsafe_world_cell(&mut self, world: UnsafeWorldCell) { - self.with_prefix(|prefix, cache| { + self.split_cache(|prefix, cache| { cache.update_archetypes(&prefix, world); }); } - /// Safety: todo. - pub(crate) fn matches(&self, archetype: &Archetype) -> bool { + /// Returns `true` if this query matches this [`Archetype`]. Otherwise, returns `false`. + /// + /// # Safety + /// `archetype` must be from the `World` this state was initialized from. + pub(crate) unsafe fn matches_archetype(&self, archetype: &Archetype) -> bool { // SAFETY: from parent function's safety constraint unsafe { self.cache.contains(self, archetype) } } } impl QueryState { - /// Returns `true` if this query matches a set of components. Otherwise, returns `false`. - /// - /// # Safety - /// `archetype` must be from the `World` this state was initialized from. - unsafe fn matches_archetype(&self, archetype: &Archetype) -> bool { - // Delegate to read-only prefix to avoid duplication - unsafe { self.as_prefix().matches_archetype(archetype) } - } - - /// Returns `true` if this query matches a set of components. Otherwise, returns `false`. - pub fn matches_component_set(&self, set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { - self.as_prefix().matches_component_set(set_contains_id) - } - /// Iterate through all archetypes that match the [`QueryState`] with an [`ArchetypeGeneration`] higher than the provided one, /// and call `f` on each of them. fn iter_archetypes( &self, archetype_generation: ArchetypeGeneration, archetypes: &Archetypes, - mut f: impl FnMut(&Archetype), + f: impl FnMut(&Archetype), ) { - self.as_prefix() - .iter_archetypes(archetype_generation, archetypes, f) + self.as_uncached() + .iter_archetypes(archetype_generation, archetypes, f); } } /// Types that can cache archetypes matched by a `Query`. pub trait QueryCache: Debug + Clone + Sync { /// Returns the data needed to iterate through the archetypes that match the query. + /// Usually used to populate a [`QueryIterationCursor`](super::iter::QueryIterationCursor) fn iteration_data<'s, 'a: 's, D: QueryData, F: QueryFilter>( &'a self, query: &QueryState, world: UnsafeWorldCell, ) -> IterationData<'s>; + /// Returns true if the cache contains information about the archetype being matches by the query + /// /// # Safety - /// `archetype` must be from the `World` this state was initialized from. + /// `archetype` must be from the `World` the state was initialized from. unsafe fn contains( &self, query: &QueryState, @@ -211,22 +207,26 @@ pub trait QueryCache: Debug + Clone + Sync { /// Update the [`QueryCache`] by storing in the cache every new archetypes that match the query. fn update_archetypes( &mut self, - prefix: &QueryPrefix<'_, D, F>, + uncached: &UncachedQueryBorrow<'_, D, F>, world: UnsafeWorldCell, ); - /// Return a new cache that contains the archetypes matched by the query joining self and other + /// Return a new cache that contains the archetypes matched by the intersection of itself and the + /// other cache. fn join(&self, other: &Self) -> Self; } -/// Data needed to iterate through query results +/// Contains a list of matches tables or archetypes, that can be used to iterate through archetypes +/// that match a query #[derive(Clone)] +#[doc(hidden)] pub struct IterationData<'s> { pub(super) is_dense: bool, pub(super) storage_ids: Cow<'s, [StorageId]>, } /// Default [`QueryCache`] to use if caching is enabled for a query. +/// Will store a pre-computed list of archetypes or tables that match a query. #[derive(Clone)] pub struct CacheState { pub(crate) archetype_generation: ArchetypeGeneration, @@ -261,16 +261,10 @@ impl QueryCache for CacheState { self.matched_archetypes.contains(archetype.id().index()) } - fn from_world_uninitialized(world: &World) -> Self { + fn from_world_uninitialized(_: &World) -> Self { // For queries without dynamic filters the dense-ness of the query is equal to the dense-ness // of its static type parameters. - let mut is_dense = D::IS_DENSE && F::IS_DENSE; - - // TODO: disallow non-dense DefaultQueryFilters - if let Some(default_filters) = world.get_resource::() { - is_dense &= default_filters.is_dense(world.components()); - } - + let is_dense = D::IS_DENSE && F::IS_DENSE; Self { archetype_generation: ArchetypeGeneration::initial(), matched_tables: Default::default(), @@ -285,9 +279,6 @@ impl QueryCache for CacheState { ) -> Self { // For dynamic queries the dense-ness is given by the query builder. let is_dense = builder.is_dense(); - - // TODO: disallow non-dense DefaultQueryFilters - Self { archetype_generation: ArchetypeGeneration::initial(), matched_tables: Default::default(), @@ -298,10 +289,10 @@ impl QueryCache for CacheState { } fn update_archetypes( &mut self, - prefix: &QueryPrefix<'_, D, F>, + uncached: &UncachedQueryBorrow<'_, D, F>, world: UnsafeWorldCell, ) { - prefix.validate_world(world.id()); + uncached.validate_world(world.id()); if self.archetype_generation == world.archetypes().generation() { // skip if we are already up to date return; @@ -310,7 +301,7 @@ impl QueryCache for CacheState { &mut self.archetype_generation, world.archetypes().generation(), ); - prefix.iter_archetypes(old_generation, world.archetypes(), |archetype| { + uncached.iter_archetypes(old_generation, world.archetypes(), |archetype| { self.cache_archetype(archetype); }); } @@ -403,7 +394,7 @@ impl CacheState { /// from scratch every time. #[derive(Debug, Clone)] pub struct Uncached { - is_dense: bool, + pub(super) is_dense: bool, } impl QueryCache for Uncached { @@ -412,19 +403,33 @@ impl QueryCache for Uncached { query: &QueryState, world: UnsafeWorldCell, ) -> IterationData<'s> { - let generation = world.archetypes().generation(); let mut storage_ids = Vec::new(); - query.iter_archetypes(generation, world.archetypes(), |archetype| { - storage_ids.push(if !self.is_dense { - StorageId { - archetype_id: archetype.id(), - } - } else { - StorageId { - table_id: archetype.table_id(), + let mut matched_storages = FixedBitSet::new(); + query.iter_archetypes( + ArchetypeGeneration::initial(), + world.archetypes(), + |archetype| { + if let Some(storage_id) = if self.is_dense { + let table_index = archetype.table_id().as_usize(); + (!matched_storages.contains(table_index)).then(|| { + matched_storages.grow_and_insert(table_index); + StorageId { + table_id: archetype.table_id(), + } + }) + } else { + let archetype_index = archetype.id().index(); + (!matched_storages.contains(archetype_index)).then(|| { + matched_storages.grow_and_insert(archetype_index); + StorageId { + archetype_id: archetype.id(), + } + }) + } { + storage_ids.push(storage_id); } - }); - }); + }, + ); IterationData { is_dense: self.is_dense, storage_ids: Cow::Owned(storage_ids), @@ -437,14 +442,12 @@ impl QueryCache for Uncached { archetype: &Archetype, ) -> bool { // SAFETY: satisfied from QueryCache::contains's safety constraints - unsafe { query.matches_archetype(archetype) } + unsafe { query.as_uncached().matches_archetype(archetype) } } fn from_world_uninitialized(_: &World) -> Self { // For queries without dynamic filters the dense-ness of the query is equal to the dense-ness // of its static type parameters. - // TODO: what about computing is_dense from DefaultQueryFilters? - // Realistically all DefaultQueryFilters would be dense. We should enforce it. Uncached { is_dense: D::IS_DENSE && F::IS_DENSE, } @@ -453,19 +456,17 @@ impl QueryCache for Uncached { fn from_builder_uninitialized( builder: &QueryBuilder, ) -> Self { - // TODO: what about computing is_dense from DefaultQueryFilters? - // Realistically all DefaultQueryFilters would be dense. We should enforce it. Uncached { is_dense: builder.is_dense(), } } - /// We do not update anything. This is here for feature parity. fn update_archetypes( &mut self, - _: &QueryPrefix, - _: UnsafeWorldCell, + uncached: &UncachedQueryBorrow, + world: UnsafeWorldCell, ) { + uncached.validate_world(world.id()); } fn join(&self, _: &Self) -> Self { diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index 02b990c57b76d..727cc68ae851b 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -1251,7 +1251,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache, I: Iterator /// Note that if `F::IS_ARCHETYPAL`, the return value /// will be **the exact count of remaining values**. fn max_remaining(&self, tables: &'w Tables, archetypes: &'w Archetypes) -> u32 { - let ids = self.storage_ids.as_ref()[self.storage_index..].iter().copied(); + let ids = self.storage_ids.as_ref()[self.storage_index..] + .iter() + .copied(); let remaining_matched: u32 = if self.is_dense { // SAFETY: The if check ensures that storage_id_iter stores TableIds unsafe { ids.map(|id| tables[id.table_id].entity_count()).sum() } @@ -2631,10 +2633,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache + 's> // we are on the beginning of the query, or finished processing a table, so skip to the next if self.current_row == self.current_len { let table_id = { - let id = *self - .storage_ids - .as_ref() - .get(self.storage_index)?; + let id = *self.storage_ids.as_ref().get(self.storage_index)?; self.storage_index += 1; id.table_id }; @@ -2679,10 +2678,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache + 's> loop { if self.current_row == self.current_len { let archetype_id = { - let id = *self - .storage_ids - .as_ref() - .get(self.storage_index)?; + let id = *self.storage_ids.as_ref().get(self.storage_index)?; self.storage_index += 1; id.archetype_id }; diff --git a/crates/bevy_ecs/src/query/mod.rs b/crates/bevy_ecs/src/query/mod.rs index 73b9345ac733a..22426bf388836 100644 --- a/crates/bevy_ecs/src/query/mod.rs +++ b/crates/bevy_ecs/src/query/mod.rs @@ -186,7 +186,7 @@ mod tests { F: ArchetypeFilter, { let mut query = world.query_filtered::(); - let query_type = type_name::>(); + let query_type = type_name::>(); assert_all_exact_sizes_iterator_equal(query.iter(world), expected_size, 0, query_type); assert_all_exact_sizes_iterator_equal(query.iter(world), expected_size, 1, query_type); assert_all_exact_sizes_iterator_equal(query.iter(world), expected_size, 5, query_type); diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 7e3cc60149538..34c8192b9209d 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -17,7 +17,7 @@ use super::{ NopWorldQuery, QueryBuilder, QueryData, QueryEntityError, QueryFilter, QueryManyIter, QueryManyUniqueIter, QuerySingleError, ROQueryItem, ReadOnlyQueryData, }; -use bevy_ecs::query::cache::{CacheState, QueryCache, QueryPrefix, Uncached}; +use bevy_ecs::query::cache::{QueryCache, Uncached, UncachedQueryBorrow}; use bevy_utils::prelude::DebugName; use core::{fmt, ptr}; #[cfg(feature = "trace")] @@ -45,6 +45,16 @@ pub(super) union StorageId { pub(super) archetype_id: ArchetypeId, } +/// Makes queries uncached by default if they don't explicitly specify the caching behaviour. +/// +/// This is mostly useful for unit tests. +#[cfg(feature = "query_uncached_default")] +pub type DefaultCache = Uncached; + +/// Makes queries cached by default if they don't explicitly specify the caching behaviour. +#[cfg(not(feature = "query_uncached_default"))] +pub type DefaultCache = bevy_ecs::query::cache::CacheState; + /// Provides scoped access to a [`World`] state according to a given [`QueryData`] and [`QueryFilter`]. /// /// This data is cached between system runs, and is used to: @@ -57,17 +67,14 @@ pub(super) union StorageId { /// [`State`]: crate::query::world_query::WorldQuery::State /// [`Fetch`]: crate::query::world_query::WorldQuery::Fetch /// [`Table`]: crate::storage::Table -#[cfg(feature = "query_uncached_default")] -pub type DefaultCache = Uncached; -#[cfg(not(feature = "query_uncached_default"))] -pub type DefaultCache = CacheState; - #[repr(C)] // SAFETY NOTE: // Do not add any new fields that use the `D` or `F` generic parameters as this may // make `QueryState::as_transmuted_state` unsound if not done with care. pub struct QueryState { world_id: WorldId, + /// Cache that can store archetypes and tables that match the query so that we can iterate through them faster. + pub(crate) cache: C, /// [`FilteredAccess`] computed by combining the `D` and `F` access. Used to check which other queries /// this query can run in parallel with. /// Note that because we do a zero-cost reference conversion in `Query::as_readonly`, @@ -78,9 +85,6 @@ pub struct QueryState FromWorld for QueryState { } impl QueryState { - /// Returns a borrow-only prefix view for read-only operations. + /// Returns a borrow-only view of the [`QueryState`] that doesn't use a cache #[inline] - pub(crate) fn as_prefix(&self) -> QueryPrefix<'_, D, F> { - QueryPrefix { + pub(crate) fn as_uncached(&self) -> UncachedQueryBorrow<'_, D, F> { + UncachedQueryBorrow { world_id: self.world_id, component_access: &self.component_access, fetch_state: &self.fetch_state, filter_state: &self.filter_state, } } - /// Safely provides access to a borrow-only prefix view and a mutable reference to the cache. + + /// Safely provides access to a borrow-only uncached view and a mutable reference to the cache. #[inline] - pub fn with_prefix(&mut self, f: impl for<'a> FnOnce(QueryPrefix<'a, D, F>, &mut C) -> R) -> R { + pub fn split_cache( + &mut self, + f: impl for<'a> FnOnce(UncachedQueryBorrow<'a, D, F>, &mut C) -> R, + ) -> R { // Create raw pointers to immutably borrowed fields to avoid overlapping borrows with &mut cache. let world_id = self.world_id; let component_access = ptr::from_ref(&self.component_access); @@ -122,7 +130,7 @@ impl QueryState { let filter_state = ptr::from_ref(&self.filter_state); // SAFETY: We only create shared references to fields distinct from `cache` and do not move `self`. let prefix = unsafe { - QueryPrefix { + UncachedQueryBorrow { world_id, component_access: &*component_access, fetch_state: &*fetch_state, @@ -132,6 +140,7 @@ impl QueryState { let cache = &mut self.cache; f(prefix, cache) } + /// Converts this `QueryState` reference to a `QueryState` that does not access anything mutably. pub fn as_readonly(&self) -> &QueryState { // SAFETY: invariant on `WorldQuery` trait upholds that `D::ReadOnly` and `F::ReadOnly` @@ -481,16 +490,7 @@ impl QueryState { #[inline] #[track_caller] pub fn validate_world(&self, world_id: WorldId) { - #[inline(never)] - #[track_caller] - #[cold] - fn panic_mismatched(this: WorldId, other: WorldId) -> ! { - panic!("Encountered a mismatched World. This QueryState was created from {this:?}, but a method was called using {other:?}."); - } - - if self.world_id != world_id { - panic_mismatched(self.world_id, world_id); - } + self.as_uncached().validate_world(world_id); } /// Use this to transform a [`QueryState`] into a more generic [`QueryState`]. @@ -2056,14 +2056,6 @@ mod tests { assert!(query.cache.is_dense); assert_eq!(3, query.query(&world).count()); - world.register_disabling_component::(); - - let mut query = QueryState::<()>::new(&mut world); - // The query doesn't ask for sparse components, but the default filters adds - // a sparse components thus it is NOT dense - assert!(!query.cache.is_dense); - assert_eq!(1, query.query(&world).count()); - let mut df = DefaultQueryFilters::from_world(&mut world); df.register_disabling_component(world.register_component::()); world.insert_resource(df); diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 2b9fe4319b973..e6abfd63e5b6e 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -1391,9 +1391,9 @@ mod tests { world.spawn((A(2), B(2))); { let query = system_state.get(&world); - assert_eq!( - query.iter().collect::>(), - vec![&A(1), &A(2)], + let result = query.iter().collect::>(); + assert!( + result.contains(&&A(1)) && result.contains(&&A(2)), "components from both archetypes returned" ); } diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index 1ffcc34637cfc..81b850038ae49 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -11,7 +11,7 @@ use crate::{ }, world::unsafe_world_cell::UnsafeWorldCell, }; -use bevy_ecs::query::{CacheState, QueryCache}; +use bevy_ecs::query::{DefaultCache, QueryCache, Uncached}; use core::{ marker::PhantomData, mem::MaybeUninit, @@ -483,7 +483,7 @@ use core::{ /// ``` /// /// [autovectorization]: https://en.wikipedia.org/wiki/Automatic_vectorization -pub struct Query<'world, 'state, D: QueryData, F: QueryFilter = (), C: QueryCache = CacheState> { +pub struct Query<'world, 'state, D: QueryData, F: QueryFilter = (), C: QueryCache = DefaultCache> { // SAFETY: Must have access to the components registered in `state`. world: UnsafeWorldCell<'world>, state: &'state QueryState, @@ -491,6 +491,26 @@ pub struct Query<'world, 'state, D: QueryData, F: QueryFilter = (), C: QueryCach this_run: Tick, } +/// Similar to [`Query`] but does not perform any caching of the [`Archetype`]s that match the query. +/// +/// This can be useful for one-off queries that don't need to pay the extra memory cost of storing the list +/// of matched archetypes. However the query iteration time will be slower on repeated usages since it won't +/// make use of the cache. +/// +/// ```rust +/// # use bevy_ecs::prelude::*; +/// # +/// # #[derive(Component)] +/// # struct ComponentA; +/// +/// let mut world = World::new(); +/// world.spawn(ComponentA); +/// +/// world.query() +/// +/// ``` +pub type UncachedQuery<'world, 'state, D, F = ()> = Query<'world, 'state, D, F, Uncached>; + impl Clone for Query<'_, '_, D, F, C> { fn clone(&self) -> Self { *self @@ -1556,7 +1576,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, C: QueryCache> Query<'w, 's, D, F, C> .archetypes() .get(location.archetype_id) .debug_checked_unwrap(); - if !self.state.matches(archetype) { + if !self.state.matches_archetype(archetype) { return Err(QueryEntityError::QueryDoesNotMatch( entity, location.archetype_id, @@ -2568,7 +2588,7 @@ impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, C: QueryCache> Query<'w, 's, /// Type returned from [`Query::transmute_lens`] containing the new [`QueryState`]. /// /// Call [`query`](QueryLens::query) or [`into`](Into::into) to construct the resulting [`Query`] -pub struct QueryLens<'w, Q: QueryData, F: QueryFilter = (), C: QueryCache = CacheState> { +pub struct QueryLens<'w, Q: QueryData, F: QueryFilter = (), C: QueryCache = DefaultCache> { world: UnsafeWorldCell<'w>, state: QueryState, last_run: Tick, @@ -2681,7 +2701,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Single<'w, 's, D, F> { /// See [`Query`] for more details. /// /// [System parameter]: crate::system::SystemParam -pub struct Populated<'w, 's, D: QueryData, F: QueryFilter = (), C: QueryCache = CacheState>( +pub struct Populated<'w, 's, D: QueryData, F: QueryFilter = (), C: QueryCache = DefaultCache>( pub(crate) Query<'w, 's, D, F, C>, ); diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 8f013b52ec7db..0d7f13ca7ebac 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -64,6 +64,7 @@ use crate::{ }, }; use alloc::{boxed::Box, vec::Vec}; +use bevy_ecs::query::UncachedQueryState; use bevy_platform::sync::atomic::{AtomicU32, Ordering}; use bevy_ptr::{move_as_ptr, MovingPtr, OwningPtr, Ptr}; use bevy_utils::prelude::DebugName; @@ -1552,6 +1553,44 @@ impl World { self.query_filtered::() } + /// Returns [`UncachedQueryState`] for the given [`QueryData`], which is used to + /// run queries on the [`World`]. In contrast to [`QueryState`], this won't cache any of the + /// archetypes that match the query. + /// ``` + /// use bevy_ecs::{component::Component, entity::Entity, world::World}; + /// + /// #[derive(Component, Debug, PartialEq)] + /// struct Position { + /// x: f32, + /// y: f32, + /// } + /// + /// #[derive(Component)] + /// struct Velocity { + /// x: f32, + /// y: f32, + /// } + /// + /// let mut world = World::new(); + /// let entities = world.spawn_batch(vec![ + /// (Position { x: 0.0, y: 0.0}, Velocity { x: 1.0, y: 0.0 }), + /// (Position { x: 0.0, y: 0.0}, Velocity { x: 0.0, y: 1.0 }), + /// ]).collect::>(); + /// + /// let mut query = world.query_uncached::<(&mut Position, &Velocity)>(); + /// for (mut position, velocity) in query.iter_mut(&mut world) { + /// position.x += velocity.x; + /// position.y += velocity.y; + /// } + /// + /// assert_eq!(world.get::(entities[0]).unwrap(), &Position { x: 1.0, y: 0.0 }); + /// assert_eq!(world.get::(entities[1]).unwrap(), &Position { x: 0.0, y: 1.0 }); + /// ``` + #[inline] + pub fn query_uncached(&mut self) -> UncachedQueryState { + self.query_filtered_uncached::() + } + /// Returns [`QueryState`] for the given filtered [`QueryData`], which is used to efficiently /// run queries on the [`World`] by storing and reusing the [`QueryState`]. /// ``` @@ -1576,6 +1615,33 @@ impl World { QueryState::new(self) } + /// Returns [`UncachedQueryState`] for the given [`QueryData`], which is used to + /// run queries on the [`World`]. In contrast to [`QueryState`], this won't cache any of the + /// archetypes that match the query. + /// ``` + /// use bevy_ecs::{component::Component, entity::Entity, world::World, query::With}; + /// + /// #[derive(Component)] + /// struct A; + /// #[derive(Component)] + /// struct B; + /// + /// let mut world = World::new(); + /// let e1 = world.spawn(A).id(); + /// let e2 = world.spawn((A, B)).id(); + /// + /// let mut query = world.query_filtered_uncached::>(); + /// let matching_entities = query.iter(&world).collect::>(); + /// + /// assert_eq!(matching_entities, vec![e2]); + /// ``` + #[inline] + pub fn query_filtered_uncached( + &mut self, + ) -> UncachedQueryState { + QueryState::new(self) + } + /// Returns [`QueryState`] for the given [`QueryData`], which is used to efficiently /// run queries on the [`World`] by storing and reusing the [`QueryState`]. /// ``` @@ -1627,6 +1693,33 @@ impl World { self.try_query_filtered::() } + /// Returns [`UncachedQueryState`] for the given [`QueryData`], which is used to + /// run queries on the [`World`]. In contrast to [`QueryState`], this won't cache any of the + /// archetypes that match the query. + /// + /// Requires only an immutable world reference, but may fail if, for example, + /// the components that make up this query have not been registered into the world. + /// ``` + /// use bevy_ecs::{component::Component, entity::Entity, world::World}; + /// + /// #[derive(Component)] + /// struct A; + /// + /// let mut world = World::new(); + /// + /// let none_query = world.try_query_uncached::<&A>(); + /// assert!(none_query.is_none()); + /// + /// world.register_component::(); + /// + /// let some_query = world.try_query_uncached::<&A>(); + /// assert!(some_query.is_some()); + /// ``` + #[inline] + pub fn try_query_uncached(&mut self) -> Option> { + self.try_query_filtered_uncached::() + } + /// Returns [`QueryState`] for the given filtered [`QueryData`], which is used to efficiently /// run queries on the [`World`] by storing and reusing the [`QueryState`]. /// ``` @@ -1654,6 +1747,36 @@ impl World { QueryState::try_new(self) } + /// Returns [`UncachedQueryState`] for the given [`QueryData`], which is used to + /// run queries on the [`World`]. In contrast to [`QueryState`], this won't cache any of the + /// archetypes that match the query. + /// ``` + /// use bevy_ecs::{component::Component, entity::Entity, world::World, query::With}; + /// + /// #[derive(Component)] + /// struct A; + /// #[derive(Component)] + /// struct B; + /// + /// let mut world = World::new(); + /// let e1 = world.spawn(A).id(); + /// let e2 = world.spawn((A, B)).id(); + /// + /// let mut query = world.try_query_filtered_uncached::>().unwrap(); + /// let matching_entities = query.iter(&world).collect::>(); + /// + /// assert_eq!(matching_entities, vec![e2]); + /// ``` + /// + /// Requires only an immutable world reference, but may fail if, for example, + /// the components that make up this query have not been registered into the world. + #[inline] + pub fn try_query_filtered_uncached( + &self, + ) -> Option> { + QueryState::try_new(self) + } + /// Returns an iterator of entities that had components of type `T` removed /// since the last call to [`World::clear_trackers`]. pub fn removed(&self) -> impl Iterator + '_ { diff --git a/release-content/release-notes/uncached_queries.md b/release-content/release-notes/uncached_queries.md new file mode 100644 index 0000000000000..2f93798596e0d --- /dev/null +++ b/release-content/release-notes/uncached_queries.md @@ -0,0 +1,22 @@ +--- +title: "UncachedQueries" +authors: ["@cbournhonesque"] +pull_requests: [21607] +--- + +`Query` and `QueryState` now have a third generic parameter `C: QueryCache` that specify how the query caches +the archetypes/tables that match it. + +There are two types that implement the `QueryCache` trait: +- `CacheState`: this matches the current behaviour that `Queries` have where each matched archetyped is cached +- `Uncached`: this won't perform any caching, so any query will just query the world from scratch. + + +This can be useful for one-off queries where there is no need to catch the list of matched archetypes since it won't be re-used. +A type alias `UncachedQuery = Query` is provided for convenience. + +The following new methods on `World` are introduced: +- `query_uncached` +- `query_filtered_uncached` +- `try_query_uncached` +- `try_query_filtered_uncached` \ No newline at end of file diff --git a/tools/ci/src/commands/test.rs b/tools/ci/src/commands/test.rs index ae449f6a8fec2..dec3127a3d58e 100644 --- a/tools/ci/src/commands/test.rs +++ b/tools/ci/src/commands/test.rs @@ -24,6 +24,13 @@ impl Prepare for TestCommand { ), "Please fix failing tests in output above.", ), + PreparedCommand::new::( + cmd!( + sh, + "cargo test bevy_ecs --lib --bins --tests --features bevy_ecs/track_location,bevy_ecs/query_uncached_default {no_fail_fast...} {jobs_ref...} -- {test_threads_ref...}" + ), + "Please fix failing tests in output above.", + ), PreparedCommand::new::( cmd!( sh, From 69c9bcc10460b65b2a4b31789c17271e5bc252ac Mon Sep 17 00:00:00 2001 From: Charles Bournhonesque Date: Tue, 21 Oct 2025 00:34:48 -0400 Subject: [PATCH 08/10] nits --- crates/bevy_ecs/src/query/state.rs | 4 ++-- crates/bevy_ecs/src/system/query.rs | 13 ------------- .../release-notes/uncached_queries.md | 18 +++++++++++------- 3 files changed, 13 insertions(+), 22 deletions(-) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 34c8192b9209d..5556fdaa5c59e 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -45,13 +45,13 @@ pub(super) union StorageId { pub(super) archetype_id: ArchetypeId, } -/// Makes queries uncached by default if they don't explicitly specify the caching behaviour. +/// Makes queries uncached by default if they don't explicitly specify the caching behavior. /// /// This is mostly useful for unit tests. #[cfg(feature = "query_uncached_default")] pub type DefaultCache = Uncached; -/// Makes queries cached by default if they don't explicitly specify the caching behaviour. +/// Makes queries cached by default if they don't explicitly specify the caching behavior. #[cfg(not(feature = "query_uncached_default"))] pub type DefaultCache = bevy_ecs::query::cache::CacheState; diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index 81b850038ae49..4902684e65ff4 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -496,19 +496,6 @@ pub struct Query<'world, 'state, D: QueryData, F: QueryFilter = (), C: QueryCach /// This can be useful for one-off queries that don't need to pay the extra memory cost of storing the list /// of matched archetypes. However the query iteration time will be slower on repeated usages since it won't /// make use of the cache. -/// -/// ```rust -/// # use bevy_ecs::prelude::*; -/// # -/// # #[derive(Component)] -/// # struct ComponentA; -/// -/// let mut world = World::new(); -/// world.spawn(ComponentA); -/// -/// world.query() -/// -/// ``` pub type UncachedQuery<'world, 'state, D, F = ()> = Query<'world, 'state, D, F, Uncached>; impl Clone for Query<'_, '_, D, F, C> { diff --git a/release-content/release-notes/uncached_queries.md b/release-content/release-notes/uncached_queries.md index 2f93798596e0d..2da4f8fd9bcff 100644 --- a/release-content/release-notes/uncached_queries.md +++ b/release-content/release-notes/uncached_queries.md @@ -4,19 +4,23 @@ authors: ["@cbournhonesque"] pull_requests: [21607] --- -`Query` and `QueryState` now have a third generic parameter `C: QueryCache` that specify how the query caches -the archetypes/tables that match it. +`Query` and `QueryState` now have a third generic parameter `C: QueryCache` +that specify how the query caches the archetypes/tables that match it. There are two types that implement the `QueryCache` trait: -- `CacheState`: this matches the current behaviour that `Queries` have where each matched archetyped is cached -- `Uncached`: this won't perform any caching, so any query will just query the world from scratch. +- `CacheState`: this matches the current behavior that `Queries` have where +each matched archetype is cached +- `Uncached`: this won't perform any caching, so any query will just query +the world from scratch. -This can be useful for one-off queries where there is no need to catch the list of matched archetypes since it won't be re-used. -A type alias `UncachedQuery = Query` is provided for convenience. +This can be useful for one-off queries where there is no need to catch the +list of matched archetypes since it won't be re-used. A type alias +`UncachedQuery = Query` is provided for convenience. The following new methods on `World` are introduced: + - `query_uncached` - `query_filtered_uncached` - `try_query_uncached` -- `try_query_filtered_uncached` \ No newline at end of file +- `try_query_filtered_uncached` From 65518cf3de54bb49675877cd1c306fc9b9f2a6c6 Mon Sep 17 00:00:00 2001 From: Charles Bournhonesque Date: Tue, 21 Oct 2025 00:36:48 -0400 Subject: [PATCH 09/10] remove comment --- crates/bevy_ecs/src/query/world_query.rs | 71 ------------------------ 1 file changed, 71 deletions(-) diff --git a/crates/bevy_ecs/src/query/world_query.rs b/crates/bevy_ecs/src/query/world_query.rs index 0bcfb8cf36f6d..fef5b0257f167 100644 --- a/crates/bevy_ecs/src/query/world_query.rs +++ b/crates/bevy_ecs/src/query/world_query.rs @@ -8,77 +8,6 @@ use crate::{ }; use variadics_please::all_tuples; -/* - -Queries in bevy are driven a lot by the Type. -We compute a QueryState that is an index of the tables/entities to return, but the QueryState is mostly just a Tuple of the inner QueryStates. -In Flecs, the QueryState contains a graph, that can then be optimized. -Our equivalent would be to have a WorldQuery::add_to_query_graph method that lets each type build the inner QueryState graph. - -We want to support Query<(Mass, Parent)>: match each entity with Mass where the parent matches D, F -Query<(Entity, Any>): match each entity where any children match D, F - -// all Components and Relationships would have extra generics available in the query? -QueryWithVariables<(SpaceShip, DockedTo, Planet) - -So we could compute a graph, and then: -- if the graph is simple (doesn't consider other entities than $this, i.e. is only SingleEntityData), then we just evaluate each `matches_id` in order. -- if the graph is more complex, we would do a traversal with backtracking: `Query<(Mass, Parent)>` - - does this table have Mass and ChildOf? in that case that table is matched - - for each entity in that table, fetch the Parent entity. Does the Parent entity match D, F ? In that case return $this. - -Uncached queries: -- observers usually trigger on a given component -- knowing this, all other queries in an observer could be uncached queries since they can use the ComponentIndex to quickly find the subset of entities to target. -- how would they know the component from the observer? we can if we do query observers --> have a step to check if the archetype/table matches the query --> have a step to check if the specific entity matches the query. - -How to express the Cache? -Maybe a QueryCache trait, implemented for StateCache or for (). -If cache is present, then use it. Else don't. - - - -Queries that are less tied to the type system. -- they update a QueryPlan that can get compiled and optimized. - -QueryState::from_typed -QueryState::from_builder - -Query Observers: -- Enter>: emit as soon as we match the query -- Exit>: emit as soon as we stop matching the query --> the query would be uncached --> have to evaluate query multiple times: evaluate the query once before adding a component, and then once after. -- how do we evaluate a query in an uncached manner? - - if archetypal and no variables: can use our current logic - - if not: can still filter for the archetypal parts of the query - - call matches_set on the entity to check if it matches the archetypes - -UncachedQueries: -- contains QueryPlan (which currently is only the component_access, but we could have nested access) -- - -CachedQueries = QueryPlan + MatchedArchetypes --> later we could optimize this to cache a subset of a QueryPlan - -Query<(&A, &mut B), With> -> creates a QueryPlan that has (&A, &mut B), With -Query<(&A, &mut B) - - -- We go from QueryBuilder::with(component_id) to Query by transmuting (which gives you a convenient API) - But maybe we should have the Query contain an untyped QueryPlan instead? - -1. We want uncached queries that don't store matched archetypes/tables. - - when we call iter, the underlying untyped plan checks every archetypes by using the component index. -2. The core query logic should be untyped (because it can contain untyped terms like variables). Maybe if all the terms are typed we can keep doing what we do now, since it would be faster? - -Multi-event observers: -- have a single observer that matches both OnAdd and OnRemove - - */ - /// Types that can be used as parameters in a [`Query`]. /// Types that implement this should also implement either [`QueryData`] or [`QueryFilter`] /// From c57c92bab53bedb332569817b816bf37b99336b4 Mon Sep 17 00:00:00 2001 From: Charles Bournhonesque Date: Tue, 21 Oct 2025 01:01:40 -0400 Subject: [PATCH 10/10] fix docs --- crates/bevy_ecs/src/query/cache.rs | 2 +- crates/bevy_ecs/src/query/state.rs | 5 +++-- crates/bevy_ecs/src/system/query.rs | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ecs/src/query/cache.rs b/crates/bevy_ecs/src/query/cache.rs index 2e318bf9101f0..d3d21ec56beab 100644 --- a/crates/bevy_ecs/src/query/cache.rs +++ b/crates/bevy_ecs/src/query/cache.rs @@ -179,7 +179,7 @@ impl QueryState { /// Types that can cache archetypes matched by a `Query`. pub trait QueryCache: Debug + Clone + Sync { /// Returns the data needed to iterate through the archetypes that match the query. - /// Usually used to populate a [`QueryIterationCursor`](super::iter::QueryIterationCursor) + /// Usually used to populate a `QueryIterationCursor` fn iteration_data<'s, 'a: 's, D: QueryData, F: QueryFilter>( &'a self, query: &QueryState, diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 5556fdaa5c59e..c34cec987706d 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -26,9 +26,9 @@ use tracing::Span; /// An ID for either a table or an archetype. Used for Query iteration. /// /// Query iteration is exclusively dense (over tables) or archetypal (over archetypes) based on whether -/// the query filters are dense or not. This is represented by the [`QueryState::is_dense`] field. +/// the query filters are dense or not. /// -/// Note that `D::IS_DENSE` and `F::IS_DENSE` have no relationship with `QueryState::is_dense` and +/// Note that `D::IS_DENSE` and `F::IS_DENSE` have no relationship with the dense-ness of the query and /// any combination of their values can happen. /// /// This is a union instead of an enum as the usage is determined at compile time, as all [`StorageId`]s for @@ -67,6 +67,7 @@ pub type DefaultCache = bevy_ecs::query::cache::CacheState; /// [`State`]: crate::query::world_query::WorldQuery::State /// [`Fetch`]: crate::query::world_query::WorldQuery::Fetch /// [`Table`]: crate::storage::Table +/// [`Archetype`]: crate::archetype::Archetype #[repr(C)] // SAFETY NOTE: // Do not add any new fields that use the `D` or `F` generic parameters as this may diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index 4902684e65ff4..5ae71677d6ae1 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -491,7 +491,7 @@ pub struct Query<'world, 'state, D: QueryData, F: QueryFilter = (), C: QueryCach this_run: Tick, } -/// Similar to [`Query`] but does not perform any caching of the [`Archetype`]s that match the query. +/// Similar to [`Query`] but does not perform any caching of the archetypes that match the query. /// /// This can be useful for one-off queries that don't need to pay the extra memory cost of storing the list /// of matched archetypes. However the query iteration time will be slower on repeated usages since it won't