diff --git a/crates/bevy_ecs/macros/src/fetch.rs b/crates/bevy_ecs/macros/src/fetch.rs index ddeb301c19974..fb3f5ef84f669 100644 --- a/crates/bevy_ecs/macros/src/fetch.rs +++ b/crates/bevy_ecs/macros/src/fetch.rs @@ -249,6 +249,8 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream { } } + const IS_ARCHETYPAL: bool = true #(&& <#field_types as #path::query::WorldQuery>::ReadOnlyFetch::IS_ARCHETYPAL)*; + const IS_DENSE: bool = true #(&& <#field_types as #path::query::WorldQuery>::ReadOnlyFetch::IS_DENSE)*; #[inline] @@ -298,6 +300,8 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream { } } + const IS_ARCHETYPAL: bool = true #(&& <#field_types as #path::query::WorldQuery>::#fetch_associated_type::IS_ARCHETYPAL)*; + const IS_DENSE: bool = true #(&& <#field_types as #path::query::WorldQuery>::#fetch_associated_type::IS_DENSE)*; /// SAFETY: we call `set_archetype` for each member that implements `Fetch` diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 8d5c59adfe849..5748abb91d0b5 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -50,7 +50,8 @@ mod tests { component::{Component, ComponentId}, entity::Entity, query::{ - Added, ChangeTrackers, Changed, FilterFetch, FilteredAccess, With, Without, WorldQuery, + Added, ChangeTrackers, Changed, FilterFetch, FilteredAccess, Or, With, Without, + WorldQuery, }, world::{Mut, World}, }; @@ -1392,6 +1393,40 @@ mod tests { ); } + #[test] + fn test_is_archetypal_size_hints() { + let mut world = World::default(); + macro_rules! query_min_size { + ($query:ty, $filter:ty) => { + world + .query_filtered::<$query, $filter>() + .iter(&world) + .size_hint() + .0 + }; + } + + world.spawn().insert_bundle((A(1), B(1), C)); + world.spawn().insert_bundle((A(1), C)); + world.spawn().insert_bundle((A(1), B(1))); + world.spawn().insert_bundle((B(1), C)); + world.spawn().insert(A(1)); + world.spawn().insert(C); + assert_eq!(2, query_min_size![(), (With, Without)],); + assert_eq!(3, query_min_size![&B, Or<(With, With)>],); + assert_eq!(1, query_min_size![&B, (With, With)],); + assert_eq!(1, query_min_size![(&A, &B), With],); + assert_eq!(4, query_min_size![&A, ()], "Simple Archetypal"); + assert_eq!(4, query_min_size![ChangeTrackers, ()],); + // All the following should set minimum size to 0, as it's impossible to predict + // how many entites the filters will trim. + assert_eq!(0, query_min_size![(), Added], "Simple Added"); + assert_eq!(0, query_min_size![(), Changed], "Simple Changed"); + assert_eq!(0, query_min_size![(&A, &B), Changed],); + assert_eq!(0, query_min_size![&A, (Changed, With)],); + assert_eq!(0, query_min_size![(&A, &B), Or<(Changed, Changed)>],); + } + #[test] fn reserve_entities_across_worlds() { let mut world_a = World::default(); diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index a99e892bf6f4d..236392f68ff6a 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -340,6 +340,13 @@ pub trait Fetch<'world, 'state>: Sized { /// [`Fetch::archetype_fetch`] will be called for iterators. const IS_DENSE: bool; + /// Returns true if (and only if) this Fetch relies strictly on archetypes to limit which + /// components are acessed by the Query. + /// + /// This enables optimizations for [`crate::query::QueryIter`] that rely on knowing exactly how + /// many elements are being iterated (such as `Iterator::collect()`). + const IS_ARCHETYPAL: bool; + /// Adjusts internal state to account for the next [`Archetype`]. This will always be called on /// archetypes that match this [`Fetch`]. /// @@ -456,6 +463,8 @@ impl<'w, 's> Fetch<'w, 's> for EntityFetch { const IS_DENSE: bool = true; + const IS_ARCHETYPAL: bool = true; + unsafe fn init( _world: &World, _state: &Self::State, @@ -579,6 +588,8 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for ReadFetch { } }; + const IS_ARCHETYPAL: bool = true; + unsafe fn init( world: &World, state: &Self::State, @@ -760,6 +771,8 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for WriteFetch { } }; + const IS_ARCHETYPAL: bool = true; + unsafe fn init( world: &World, state: &Self::State, @@ -866,6 +879,8 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for ReadOnlyWriteFetch { } }; + const IS_ARCHETYPAL: bool = true; + unsafe fn init( world: &World, state: &Self::State, @@ -993,6 +1008,8 @@ impl<'w, 's, T: Fetch<'w, 's>> Fetch<'w, 's> for OptionFetch { const IS_DENSE: bool = T::IS_DENSE; + const IS_ARCHETYPAL: bool = T::IS_ARCHETYPAL; + unsafe fn init( world: &World, state: &Self::State, @@ -1201,6 +1218,8 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for ChangeTrackersFetch { } }; + const IS_ARCHETYPAL: bool = true; + unsafe fn init( world: &World, state: &Self::State, @@ -1303,6 +1322,8 @@ macro_rules! impl_tuple_fetch { const IS_DENSE: bool = true $(&& $name::IS_DENSE)*; + const IS_ARCHETYPAL: bool = true $(&& $name::IS_ARCHETYPAL)*; + #[inline] unsafe fn set_archetype(&mut self, _state: &Self::State, _archetype: &Archetype, _tables: &Tables) { let ($($name,)*) = self; @@ -1396,6 +1417,8 @@ macro_rules! impl_anytuple_fetch { const IS_DENSE: bool = true $(&& $name::IS_DENSE)*; + const IS_ARCHETYPAL: bool = true $(&& $name::IS_ARCHETYPAL)*; + #[inline] unsafe fn set_archetype(&mut self, _state: &Self::State, _archetype: &Archetype, _tables: &Tables) { let ($($name,)*) = &mut self.0; @@ -1501,6 +1524,8 @@ impl<'w, 's, State: FetchState> Fetch<'w, 's> for NopFetch { const IS_DENSE: bool = true; + const IS_ARCHETYPAL: bool = true; + #[inline(always)] unsafe fn init( _world: &World, diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index 6c936f2d0a538..e4682ef8021b6 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -143,6 +143,8 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for WithFetch { } }; + const IS_ARCHETYPAL: bool = true; + #[inline] unsafe fn set_table(&mut self, _state: &Self::State, _table: &Table) {} @@ -266,6 +268,8 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for WithoutFetch { } }; + const IS_ARCHETYPAL: bool = true; + #[inline] unsafe fn set_table(&mut self, _state: &Self::State, _table: &Table) {} @@ -375,6 +379,8 @@ macro_rules! impl_query_filter_tuple { const IS_DENSE: bool = true $(&& $filter::IS_DENSE)*; + const IS_ARCHETYPAL: bool = true $(&& $filter::IS_ARCHETYPAL)*; + #[inline] unsafe fn set_table(&mut self, state: &Self::State, table: &Table) { let ($($filter,)*) = &mut self.0; @@ -549,6 +555,8 @@ macro_rules! impl_tick_filter { } }; + const IS_ARCHETYPAL: bool = false; + unsafe fn set_table(&mut self, state: &Self::State, table: &Table) { self.table_ticks = table .get_column(state.component_id).unwrap() diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index 7e135c78080dd..30dccd228d18a 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -149,7 +149,11 @@ where .map(|index| self.world.archetypes[ArchetypeId::new(index)].len()) .sum(); - (0, Some(max_size)) + // TODO: it's _probably possible to use const generics to have specialized implementation + // of size_hint based on whether this is true or not. + let archetype_query = F::Fetch::IS_ARCHETYPAL && QF::IS_ARCHETYPAL; + let min_size = if archetype_query { max_size } else { 0 }; + (min_size, Some(max_size)) } }