From 49f4da6bde9ba7e0bc6c9326c9e1d2f2f54172ac Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Wed, 14 Jun 2023 20:11:11 +0100 Subject: [PATCH 01/11] Added Has WorldQuery type --- crates/bevy_ecs/src/query/fetch.rs | 128 ++++++++++++++++++++++++++++ crates/bevy_ecs/src/query/filter.rs | 5 +- crates/bevy_ecs/src/query/mod.rs | 20 ++++- 3 files changed, 150 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 4540909f61685..0c48b20d52a3f 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -1096,6 +1096,134 @@ unsafe impl WorldQuery for Option { /// SAFETY: [`OptionFetch`] is read only because `T` is read only unsafe impl ReadOnlyWorldQuery for Option {} +/// Returns whether each entity has component `T`. +/// +/// This can be used in a [`Query`](crate::system::Query) if you want to know whether or not entities +/// have the component `T` but don't actually care about the component's value. +/// +/// # Examples +/// +/// ``` +/// # use bevy_ecs::component::Component; +/// # use bevy_ecs::query::Has; +/// # use bevy_ecs::system::IntoSystem; +/// # use bevy_ecs::system::Query; +/// # +/// # #[derive(Component)] +/// # struct IsHungry; +/// # #[derive(Component)] +/// # struct Name { name: &'static str }; +/// # +/// fn compliment_entity_system(query: Query<(&Name, Has) >) { +/// for (name, is_hungry) in &query { +/// if is_hungry{ +/// println!("{} would like some food.", name.name); +/// } else { +/// println!("{} has had sufficient.", name.name); +/// } +/// } +/// } +/// # bevy_ecs::system::assert_is_system(compliment_entity_system); +/// ``` +pub struct Has(PhantomData); + +#[doc(hidden)] +pub struct HasFetch { + phantom: PhantomData, + matches: bool, +} + +// SAFETY: `Self::ReadOnly` is the same as `Self` +unsafe impl WorldQuery for Has { + type Fetch<'w> = HasFetch; + type Item<'w> = bool; + type ReadOnly = Self; + type State = ComponentId; + + fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { + item + } + + const IS_DENSE: bool = { + match T::Storage::STORAGE_TYPE { + StorageType::Table => true, + StorageType::SparseSet => false, + } + }; + + const IS_ARCHETYPAL: bool = true; + + #[inline] + unsafe fn init_fetch( + _world: &World, + _state: &ComponentId, + _last_run: Tick, + _this_run: Tick, + ) -> HasFetch { + HasFetch { + phantom: Default::default(), + matches: false, + } + } + + unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> { + HasFetch { + phantom: Default::default(), + matches: fetch.matches, + } + } + + #[inline] + unsafe fn set_archetype( + fetch: &mut HasFetch, + state: &ComponentId, + archetype: &Archetype, + _table: &Table, + ) { + fetch.matches = archetype.contains(*state); + } + + #[inline] + unsafe fn set_table(_fetch: &mut HasFetch, _state: &ComponentId, _table: &Table) { + _fetch.matches = _table.has_column(*_state); + } + + #[inline(always)] + unsafe fn fetch<'w>( + fetch: &mut Self::Fetch<'w>, + _entity: Entity, + _table_row: TableRow, + ) -> Self::Item<'w> { + fetch.matches + } + + fn update_component_access(_state: &ComponentId, _access: &mut FilteredAccess) { + // Do nothing as presence of `Has` never affects whether two queries are disjoint + } + + fn update_archetype_component_access( + _state: &ComponentId, + _archetype: &Archetype, + _access: &mut Access, + ) { + } + + fn init_state(world: &mut World) -> ComponentId { + world.init_component::() + } + + fn matches_component_set( + _state: &ComponentId, + _set_contains_id: &impl Fn(ComponentId) -> bool, + ) -> bool { + // `Has` always matches + true + } +} + +/// SAFETY: [`Has`] is read only +unsafe impl ReadOnlyWorldQuery for Has {} + macro_rules! impl_tuple_fetch { ($(($name: ident, $state: ident)),*) => { #[allow(non_snake_case)] diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index 996ef81b12ea0..a49e93d5f27f3 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -2,7 +2,7 @@ use crate::{ archetype::{Archetype, ArchetypeComponentId}, component::{Component, ComponentId, ComponentStorage, StorageType, Tick}, entity::Entity, - query::{Access, DebugCheckedUnwrap, FilteredAccess, WorldQuery}, + query::{Access, DebugCheckedUnwrap, FilteredAccess, Has, WorldQuery}, storage::{Column, ComponentSparseSet, Table, TableRow}, world::World, }; @@ -623,7 +623,7 @@ impl_tick_filter!( /// [`QueryIter`](crate::query::QueryIter) that contains archetype-level filters. /// /// The trait must only be implement for filters where its corresponding [`WorldQuery::IS_ARCHETYPAL`](crate::query::WorldQuery::IS_ARCHETYPAL) -/// is [`prim@true`]. As such, only the [`With`] and [`Without`] filters can implement the trait. +/// is [`prim@true`]. As such, only the [`With`], [`Without`], and [`Has`] filters can implement the trait. /// [Tuples](prim@tuple) and [`Or`] filters are automatically implemented with the trait only if its containing types /// also implement the same trait. /// @@ -633,6 +633,7 @@ pub trait ArchetypeFilter {} impl ArchetypeFilter for With {} impl ArchetypeFilter for Without {} +impl ArchetypeFilter for Has {} macro_rules! impl_archetype_filter_tuple { ($($filter: ident),*) => { diff --git a/crates/bevy_ecs/src/query/mod.rs b/crates/bevy_ecs/src/query/mod.rs index 1c6185e4e858e..458addde9cbfe 100644 --- a/crates/bevy_ecs/src/query/mod.rs +++ b/crates/bevy_ecs/src/query/mod.rs @@ -64,7 +64,7 @@ impl DebugCheckedUnwrap for Option { mod tests { use super::{ReadOnlyWorldQuery, WorldQuery}; use crate::prelude::{AnyOf, Changed, Entity, Or, QueryState, With, Without}; - use crate::query::{ArchetypeFilter, QueryCombinationIter}; + use crate::query::{ArchetypeFilter, Has, QueryCombinationIter}; use crate::schedule::{IntoSystemConfigs, Schedule}; use crate::system::{IntoSystem, Query, System, SystemState}; use crate::{self as bevy_ecs, component::Component, world::World}; @@ -476,6 +476,24 @@ mod tests { ); } + #[test] + fn has_query() { + let mut world = World::new(); + + world.spawn((A(1), B(1))); + world.spawn(A(2)); + world.spawn((A(3), B(1))); + world.spawn(A(4)); + + let values: Vec<(&A, bool)> = world.query::<(&A, Has)>().iter(&world).collect(); + + // The query seems to put the components with B first + assert_eq!( + values, + vec![(&A(1), true), (&A(3), true), (&A(2), false), (&A(4), false),] + ); + } + #[test] #[should_panic = "&mut bevy_ecs::query::tests::A conflicts with a previous access in this query."] fn self_conflicting_worldquery() { From 34e9feba1d7429e46bfaac4fa17a0ae7e10a1b98 Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Thu, 15 Jun 2023 06:53:23 +0100 Subject: [PATCH 02/11] Added another doctest for Has --- crates/bevy_ecs/src/query/fetch.rs | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 0c48b20d52a3f..daeb9e8a3bccb 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -1114,7 +1114,7 @@ unsafe impl ReadOnlyWorldQuery for Option {} /// # #[derive(Component)] /// # struct Name { name: &'static str }; /// # -/// fn compliment_entity_system(query: Query<(&Name, Has) >) { +/// fn food_entity_system(query: Query<(&Name, Has) >) { /// for (name, is_hungry) in &query { /// if is_hungry{ /// println!("{} would like some food.", name.name); @@ -1123,7 +1123,29 @@ unsafe impl ReadOnlyWorldQuery for Option {} /// } /// } /// } -/// # bevy_ecs::system::assert_is_system(compliment_entity_system); +/// # bevy_ecs::system::assert_is_system(food_entity_system); +/// ``` +/// +/// ``` +/// # use bevy_ecs::component::Component; +/// # use bevy_ecs::query::Has; +/// # use bevy_ecs::system::IntoSystem; +/// # use bevy_ecs::system::Query; +/// # +/// # #[derive(Component)] +/// # struct Alpha{has_beta: bool}; +/// # #[derive(Component)] +/// # struct Beta { has_alpha: bool }; +/// # +/// fn alphabet_entity_system(mut alphas: Query<(&mut Alpha, Has)>, mut betas: Query<(&mut Beta, Has)>) { +/// for (mut alpha, has_beta) in alphas.iter_mut() { +/// alpha.has_beta = has_beta; +/// } +/// for (mut beta, has_alpha) in betas.iter_mut() { +/// beta.has_alpha = has_alpha; +/// } +/// } +/// # bevy_ecs::system::assert_is_system(alphabet_entity_system); /// ``` pub struct Has(PhantomData); From e06274e035110c5a37db678c0c88730e92b5e3af Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Thu, 15 Jun 2023 06:56:35 +0100 Subject: [PATCH 03/11] Update crates/bevy_ecs/src/query/fetch.rs Co-authored-by: Alice Cecile --- crates/bevy_ecs/src/query/fetch.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 0c48b20d52a3f..1d0755215ab51 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -1096,7 +1096,7 @@ unsafe impl WorldQuery for Option { /// SAFETY: [`OptionFetch`] is read only because `T` is read only unsafe impl ReadOnlyWorldQuery for Option {} -/// Returns whether each entity has component `T`. +/// Returns a bool that describes if the entity has component `T`. /// /// This can be used in a [`Query`](crate::system::Query) if you want to know whether or not entities /// have the component `T` but don't actually care about the component's value. From 7f6aaded9a444c46904c5fc35a2d75d76cf02655 Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Thu, 15 Jun 2023 06:56:35 +0100 Subject: [PATCH 04/11] Update crates/bevy_ecs/src/query/fetch.rs Co-authored-by: Alice Cecile --- crates/bevy_ecs/src/query/fetch.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index daeb9e8a3bccb..59a34df8f1428 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -1096,7 +1096,7 @@ unsafe impl WorldQuery for Option { /// SAFETY: [`OptionFetch`] is read only because `T` is read only unsafe impl ReadOnlyWorldQuery for Option {} -/// Returns whether each entity has component `T`. +/// Returns a bool that describes if the entity has component `T`. /// /// This can be used in a [`Query`](crate::system::Query) if you want to know whether or not entities /// have the component `T` but don't actually care about the component's value. From e361590cf7d038baf6a4ffcbd966aa271f0ac7f9 Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Thu, 15 Jun 2023 09:42:04 +0100 Subject: [PATCH 05/11] fixed compilation error in Has --- crates/bevy_ecs/src/query/fetch.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index cd132da63cf7a..d6fc9c2e5b241 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -1191,7 +1191,7 @@ unsafe impl WorldQuery for Has { #[inline] unsafe fn init_fetch( - _world: &World, + _world: UnsafeWorldCell, _state: &ComponentId, _last_run: Tick, _this_run: Tick, From 94a2998d5af7827f7c5effbc19e88a963a0177d2 Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Thu, 15 Jun 2023 14:53:46 +0100 Subject: [PATCH 06/11] Update crates/bevy_ecs/src/query/fetch.rs Co-authored-by: Alice Cecile --- crates/bevy_ecs/src/query/fetch.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index d6fc9c2e5b241..331128e8ba365 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -1151,6 +1151,8 @@ unsafe impl ReadOnlyWorldQuery for Option {} /// # #[derive(Component)] /// # struct Beta { has_alpha: bool }; /// # +/// // Unlike `Option<&T>`, `Has` is compatible with `&mut T` +/// // as it does not actually access any data. /// fn alphabet_entity_system(mut alphas: Query<(&mut Alpha, Has)>, mut betas: Query<(&mut Beta, Has)>) { /// for (mut alpha, has_beta) in alphas.iter_mut() { /// alpha.has_beta = has_beta; From d55fe1edc8d4f4c1780a8138ef34a8472ba3ff0a Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Thu, 15 Jun 2023 17:29:07 +0100 Subject: [PATCH 07/11] Tidied `Has.set_table()` --- crates/bevy_ecs/src/query/fetch.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 331128e8ba365..556b40f5ec518 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -1151,7 +1151,7 @@ unsafe impl ReadOnlyWorldQuery for Option {} /// # #[derive(Component)] /// # struct Beta { has_alpha: bool }; /// # -/// // Unlike `Option<&T>`, `Has` is compatible with `&mut T` +/// // Unlike `Option<&T>`, `Has` is compatible with `&mut T` /// // as it does not actually access any data. /// fn alphabet_entity_system(mut alphas: Query<(&mut Alpha, Has)>, mut betas: Query<(&mut Beta, Has)>) { /// for (mut alpha, has_beta) in alphas.iter_mut() { @@ -1222,8 +1222,8 @@ unsafe impl WorldQuery for Has { } #[inline] - unsafe fn set_table(_fetch: &mut HasFetch, _state: &ComponentId, _table: &Table) { - _fetch.matches = _table.has_column(*_state); + unsafe fn set_table(fetch: &mut HasFetch, state: &ComponentId, table: &Table) { + fetch.matches = table.has_column(*state); } #[inline(always)] From f564df3ea473ebe5dfddefeeba37155331dafce6 Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Fri, 16 Jun 2023 09:09:25 +0100 Subject: [PATCH 08/11] replaced `HasFetch` with `bool` --- crates/bevy_ecs/src/query/fetch.rs | 47 ++++++++++++------------------ 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 556b40f5ec518..1887e05cb00de 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -1165,15 +1165,10 @@ unsafe impl ReadOnlyWorldQuery for Option {} /// ``` pub struct Has(PhantomData); -#[doc(hidden)] -pub struct HasFetch { - phantom: PhantomData, - matches: bool, -} // SAFETY: `Self::ReadOnly` is the same as `Self` unsafe impl WorldQuery for Has { - type Fetch<'w> = HasFetch; + type Fetch<'w> = bool; type Item<'w> = bool; type ReadOnly = Self; type State = ComponentId; @@ -1192,38 +1187,32 @@ unsafe impl WorldQuery for Has { const IS_ARCHETYPAL: bool = true; #[inline] - unsafe fn init_fetch( - _world: UnsafeWorldCell, - _state: &ComponentId, + unsafe fn init_fetch<'w>( + _world: UnsafeWorldCell<'w>, + _state: &Self::State, _last_run: Tick, _this_run: Tick, - ) -> HasFetch { - HasFetch { - phantom: Default::default(), - matches: false, - } + ) -> Self::Fetch<'w> { + false } unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> { - HasFetch { - phantom: Default::default(), - matches: fetch.matches, - } + *fetch } #[inline] - unsafe fn set_archetype( - fetch: &mut HasFetch, - state: &ComponentId, - archetype: &Archetype, + unsafe fn set_archetype<'w>( + fetch: &mut Self::Fetch<'w>, + state: &Self::State, + archetype: &'w Archetype, _table: &Table, ) { - fetch.matches = archetype.contains(*state); + *fetch = archetype.contains(*state); } #[inline] - unsafe fn set_table(fetch: &mut HasFetch, state: &ComponentId, table: &Table) { - fetch.matches = table.has_column(*state); + unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table) { + *fetch = table.has_column(*state); } #[inline(always)] @@ -1232,15 +1221,15 @@ unsafe impl WorldQuery for Has { _entity: Entity, _table_row: TableRow, ) -> Self::Item<'w> { - fetch.matches + *fetch } - fn update_component_access(_state: &ComponentId, _access: &mut FilteredAccess) { + fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) { // Do nothing as presence of `Has` never affects whether two queries are disjoint } fn update_archetype_component_access( - _state: &ComponentId, + _state: &Self::State, _archetype: &Archetype, _access: &mut Access, ) { @@ -1251,7 +1240,7 @@ unsafe impl WorldQuery for Has { } fn matches_component_set( - _state: &ComponentId, + _state: &Self::State, _set_contains_id: &impl Fn(ComponentId) -> bool, ) -> bool { // `Has` always matches From 62b8b3c6297be14c8480e91d948ef2aecdd93add Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Fri, 16 Jun 2023 09:33:10 +0100 Subject: [PATCH 09/11] Fixed formatting --- crates/bevy_ecs/src/query/fetch.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 1887e05cb00de..0ae362d2ac446 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -1165,7 +1165,6 @@ unsafe impl ReadOnlyWorldQuery for Option {} /// ``` pub struct Has(PhantomData); - // SAFETY: `Self::ReadOnly` is the same as `Self` unsafe impl WorldQuery for Has { type Fetch<'w> = bool; From 7c431395b671db132184446b2c10f7c9b858890b Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Mon, 19 Jun 2023 06:24:29 +0100 Subject: [PATCH 10/11] Update crates/bevy_ecs/src/query/fetch.rs Co-authored-by: JoJoJet <21144246+JoJoJet@users.noreply.github.com> --- crates/bevy_ecs/src/query/fetch.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 0ae362d2ac446..574bb2c4fb518 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -1110,7 +1110,7 @@ unsafe impl WorldQuery for Option { /// SAFETY: [`OptionFetch`] is read only because `T` is read only unsafe impl ReadOnlyWorldQuery for Option {} -/// Returns a bool that describes if the entity has component `T`. +/// Returns a bool that describes if an entity has the component `T`. /// /// This can be used in a [`Query`](crate::system::Query) if you want to know whether or not entities /// have the component `T` but don't actually care about the component's value. From e58d52cf774838aa33f6ae4e62f858a3f53862b4 Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Mon, 19 Jun 2023 06:35:54 +0100 Subject: [PATCH 11/11] Has should not be an ArchetypeFilter --- crates/bevy_ecs/src/query/filter.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index b67ce7ea2299d..b3f039566eb0f 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -2,7 +2,7 @@ use crate::{ archetype::{Archetype, ArchetypeComponentId}, component::{Component, ComponentId, ComponentStorage, StorageType, Tick}, entity::Entity, - query::{Access, DebugCheckedUnwrap, FilteredAccess, Has, WorldQuery}, + query::{Access, DebugCheckedUnwrap, FilteredAccess, WorldQuery}, storage::{Column, ComponentSparseSet, Table, TableRow}, world::{unsafe_world_cell::UnsafeWorldCell, World}, }; @@ -641,7 +641,7 @@ impl_tick_filter!( /// [`QueryIter`](crate::query::QueryIter) that contains archetype-level filters. /// /// The trait must only be implement for filters where its corresponding [`WorldQuery::IS_ARCHETYPAL`](crate::query::WorldQuery::IS_ARCHETYPAL) -/// is [`prim@true`]. As such, only the [`With`], [`Without`], and [`Has`] filters can implement the trait. +/// is [`prim@true`]. As such, only the [`With`] and [`Without`] filters can implement the trait. /// [Tuples](prim@tuple) and [`Or`] filters are automatically implemented with the trait only if its containing types /// also implement the same trait. /// @@ -651,7 +651,6 @@ pub trait ArchetypeFilter {} impl ArchetypeFilter for With {} impl ArchetypeFilter for Without {} -impl ArchetypeFilter for Has {} macro_rules! impl_archetype_filter_tuple { ($($filter: ident),*) => {