From 02d569d0e4ac0685b678a64178d9260e7265a057 Mon Sep 17 00:00:00 2001 From: NiseVoid Date: Tue, 6 May 2025 01:21:26 +0200 Subject: [PATCH] Add Allows filter to bypass DefaultQueryFilters (#18192) # Objective Fixes #17803 ## Solution - Add an `Allows` `QueryFilter` that adds archetypal access for `T` - Fix access merging to include archetypal from both sides ## Testing - Added a case to the unit test for the application of `DefaultQueryFilters` --- crates/bevy_ecs/src/lib.rs | 2 +- crates/bevy_ecs/src/query/access.rs | 4 +- crates/bevy_ecs/src/query/filter.rs | 57 +++++++++++++++++++++++++++++ crates/bevy_ecs/src/query/state.rs | 4 ++ 4 files changed, 65 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index b5b6ea0125..ec0b8cdfd4 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -80,7 +80,7 @@ pub mod prelude { hierarchy::{ChildOf, ChildSpawner, ChildSpawnerCommands, Children}, name::{Name, NameOrEntity}, observer::{Observer, Trigger}, - query::{Added, AnyOf, Changed, Has, Or, QueryBuilder, QueryState, With, Without}, + query::{Added, Allows, AnyOf, Changed, Has, Or, QueryBuilder, QueryState, With, Without}, related, relationship::RelationshipTarget, removal_detection::RemovedComponents, diff --git a/crates/bevy_ecs/src/query/access.rs b/crates/bevy_ecs/src/query/access.rs index 45af59011b..d8d9c8c3fa 100644 --- a/crates/bevy_ecs/src/query/access.rs +++ b/crates/bevy_ecs/src/query/access.rs @@ -257,9 +257,10 @@ impl Access { /// This is for components whose values are not accessed (and thus will never cause conflicts), /// but whose presence in an archetype may affect query results. /// - /// Currently, this is only used for [`Has`]. + /// Currently, this is only used for [`Has`] and [`Allows`]. /// /// [`Has`]: crate::query::Has + /// [`Allows`]: crate::query::filter::Allows pub fn add_archetypal(&mut self, index: T) { self.archetypal.grow_and_insert(index.sparse_set_index()); } @@ -499,6 +500,7 @@ impl Access { self.resource_read_and_writes .union_with(&other.resource_read_and_writes); self.resource_writes.union_with(&other.resource_writes); + self.archetypal.union_with(&other.archetypal); } /// Returns `true` if the access and `other` can be active at the same time, diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index e4e1f0fd66..dc6acabe9b 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -555,6 +555,63 @@ all_tuples!( S ); +/// Allows a query to contain entities with the component `T`, bypassing [`DefaultQueryFilters`]. +/// +/// [`DefaultQueryFilters`]: crate::entity_disabling::DefaultQueryFilters +pub struct Allows(PhantomData); + +/// SAFETY: +/// `update_component_access` does not add any accesses. +/// This is sound because [`QueryFilter::filter_fetch`] does not access any components. +/// `update_component_access` adds an archetypal filter for `T`. +/// This is sound because it doesn't affect the query +unsafe impl WorldQuery for Allows { + type Fetch<'w> = (); + type State = ComponentId; + + fn shrink_fetch<'wlong: 'wshort, 'wshort>(_: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {} + + #[inline] + unsafe fn init_fetch(_: UnsafeWorldCell, _: &ComponentId, _: Tick, _: Tick) {} + + // Even if the component is sparse, this implementation doesn't do anything with it + const IS_DENSE: bool = true; + + #[inline] + unsafe fn set_archetype(_: &mut (), _: &ComponentId, _: &Archetype, _: &Table) {} + + #[inline] + unsafe fn set_table(_: &mut (), _: &ComponentId, _: &Table) {} + + #[inline] + fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess) { + access.access_mut().add_archetypal(id); + } + + fn init_state(world: &mut World) -> ComponentId { + world.register_component::() + } + + fn get_state(components: &Components) -> Option { + components.component_id::() + } + + fn matches_component_set(_: &ComponentId, _: &impl Fn(ComponentId) -> bool) -> bool { + // Allows always matches + true + } +} + +// SAFETY: WorldQuery impl performs no access at all +unsafe impl QueryFilter for Allows { + const IS_ARCHETYPAL: bool = true; + + #[inline(always)] + unsafe fn filter_fetch(_: &mut Self::Fetch<'_>, _: Entity, _: TableRow) -> bool { + true + } +} + /// A filter on a component that only retains results the first time after they have been added. /// /// A common use for this filter is one-time initialization. diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index e9a00f4646..6c2d26b08c 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -2315,6 +2315,10 @@ mod tests { let mut query = QueryState::>::new(&mut world); assert_eq!(3, query.iter(&world).count()); + // Allows should bypass the filter entirely + let mut query = QueryState::<(), Allows>::new(&mut world); + assert_eq!(3, query.iter(&world).count()); + // Other filters should still be respected let mut query = QueryState::, Without>::new(&mut world); assert_eq!(1, query.iter(&world).count());