diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index 497ad579fa081..82ddf458cb40f 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -69,6 +69,62 @@ where current_index: 0, } } + + /// Consumes `self` and returns true if there were no elements remaining in this iterator. + #[inline(always)] + pub(crate) fn none_remaining(mut self) -> bool { + // NOTE: this mimics the behavior of `QueryIter::next()`, except that it + // never gets a `Self::Item`. + unsafe { + if self.is_dense { + loop { + if self.current_index == self.current_len { + let table_id = match self.table_id_iter.next() { + Some(table_id) => table_id, + None => return true, + }; + let table = &self.tables[*table_id]; + self.filter.set_table(&self.query_state.filter_state, table); + self.current_len = table.len(); + self.current_index = 0; + continue; + } + + if !self.filter.table_filter_fetch(self.current_index) { + self.current_index += 1; + continue; + } + + return false; + } + } else { + loop { + if self.current_index == self.current_len { + let archetype_id = match self.archetype_id_iter.next() { + Some(archetype_id) => archetype_id, + None => return true, + }; + let archetype = &self.archetypes[*archetype_id]; + self.filter.set_archetype( + &self.query_state.filter_state, + archetype, + self.tables, + ); + self.current_len = archetype.len(); + self.current_index = 0; + continue; + } + + if !self.filter.archetype_filter_fetch(self.current_index) { + self.current_index += 1; + continue; + } + + return false; + } + } + } + } } impl<'w, 's, Q: WorldQuery, F: WorldQuery> Iterator for QueryIter<'w, 's, Q, F> diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index d8a12f3a7b2a5..05c009c6e82db 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -68,6 +68,16 @@ where state } + #[inline] + pub fn is_empty(&self, world: &World, last_change_tick: u32, change_tick: u32) -> bool { + // SAFE: the iterator is instantly consumed via `none_remaining` and the implementation of + // `QueryIter::none_remaining` never creates any references to the `>::Item`. + unsafe { + self.iter_unchecked_manual(world, last_change_tick, change_tick) + .none_remaining() + } + } + pub fn validate_world_and_update_archetypes(&mut self, world: &World) { if world.id() != self.world_id { panic!("Attempted to use {} with a mismatched World. QueryStates can only be used with the World they were created from.", diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index b10ec03c6506e..2810c316f7e91 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -438,6 +438,30 @@ mod tests { assert_eq!(conflicts, vec![b_id, d_id]); } + #[test] + fn query_is_empty() { + fn without_filter(not_empty: Query<&A>, empty: Query<&B>) { + assert!(!not_empty.is_empty()); + assert!(empty.is_empty()); + } + + fn with_filter(not_empty: Query<&A, With>, empty: Query<&A, With>) { + assert!(!not_empty.is_empty()); + assert!(empty.is_empty()); + } + + let mut world = World::default(); + world.spawn().insert(A).insert(C); + + let mut without_filter = without_filter.system(); + without_filter.initialize(&mut world); + without_filter.run((), &mut world); + + let mut with_filter = with_filter.system(); + with_filter.initialize(&mut world); + with_filter.run((), &mut world); + } + #[test] #[allow(clippy::too_many_arguments)] fn can_have_16_parameters() { diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index 908651125ac2b..6173889eeef33 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -543,6 +543,15 @@ where >())), } } + + /// Returns true if this query contains no elements. + #[inline] + pub fn is_empty(&self) -> bool { + // TODO: This code can be replaced with `self.iter().next().is_none()` if/when + // we sort out how to convert "write" queries to "read" queries. + self.state + .is_empty(self.world, self.last_change_tick, self.change_tick) + } } /// An error that occurs when retrieving a specific [`Entity`]'s component from a [`Query`]