Add Allows filter to bypass DefaultQueryFilters (#18192)

# Objective

Fixes #17803 

## Solution

- Add an `Allows<T>` `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`
This commit is contained in:
NiseVoid 2025-05-06 01:21:26 +02:00 committed by GitHub
parent bfc76c589e
commit 02d569d0e4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 65 additions and 2 deletions

View File

@ -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,

View File

@ -257,9 +257,10 @@ impl<T: SparseSetIndex> Access<T> {
/// 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<T>`].
/// Currently, this is only used for [`Has<T>`] and [`Allows<T>`].
///
/// [`Has<T>`]: crate::query::Has
/// [`Allows<T>`]: 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<T: SparseSetIndex> Access<T> {
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,

View File

@ -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<T>(PhantomData<T>);
/// 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<T: Component> WorldQuery for Allows<T> {
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<ComponentId>) {
access.access_mut().add_archetypal(id);
}
fn init_state(world: &mut World) -> ComponentId {
world.register_component::<T>()
}
fn get_state(components: &Components) -> Option<Self::State> {
components.component_id::<T>()
}
fn matches_component_set(_: &ComponentId, _: &impl Fn(ComponentId) -> bool) -> bool {
// Allows<T> always matches
true
}
}
// SAFETY: WorldQuery impl performs no access at all
unsafe impl<T: Component> QueryFilter for Allows<T> {
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.

View File

@ -2315,6 +2315,10 @@ mod tests {
let mut query = QueryState::<Has<C>>::new(&mut world);
assert_eq!(3, query.iter(&world).count());
// Allows should bypass the filter entirely
let mut query = QueryState::<(), Allows<C>>::new(&mut world);
assert_eq!(3, query.iter(&world).count());
// Other filters should still be respected
let mut query = QueryState::<Has<C>, Without<B>>::new(&mut world);
assert_eq!(1, query.iter(&world).count());