Expose query accesses (#11700)

# Objective

It would be useful to be able to inspect a `QueryState`'s accesses so we
can detect when the data it accesses changes without having to iterate
it. However there are two things preventing this:

* These accesses are unnecessarily encapsulated.
* `Has<T>` indirectly accesses `T`, but does not register it.

## Solution

* Expose accesses and matches used by `QueryState`.
* Add the notion of "archetypal" accesses, which are not accessed
directly, but whose presence in an archetype affects a query result.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
This commit is contained in:
Joseph 2024-02-10 07:22:07 -08:00 committed by GitHub
parent 33676112da
commit 2e2f89869b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 79 additions and 2 deletions

View file

@ -59,6 +59,8 @@ pub struct Access<T: SparseSetIndex> {
/// Is `true` if this has mutable access to all elements in the collection.
/// If this is true, then `reads_all` must also be true.
writes_all: bool,
// Elements that are not accessed, but whose presence in an archetype affect query results.
archetypal: FixedBitSet,
marker: PhantomData<T>,
}
@ -90,6 +92,7 @@ impl<T: SparseSetIndex> Access<T> {
writes_all: false,
reads_and_writes: FixedBitSet::new(),
writes: FixedBitSet::new(),
archetypal: FixedBitSet::new(),
marker: PhantomData,
}
}
@ -116,6 +119,19 @@ impl<T: SparseSetIndex> Access<T> {
self.writes.insert(index.sparse_set_index());
}
/// Adds an archetypal (indirect) access to the element given by `index`.
///
/// This is for elements 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>`].
///
/// [`Has<T>`]: crate::query::Has
pub fn add_archetypal(&mut self, index: T) {
self.archetypal.grow(index.sparse_set_index() + 1);
self.archetypal.insert(index.sparse_set_index());
}
/// Returns `true` if this can access the element given by `index`.
pub fn has_read(&self, index: T) -> bool {
self.reads_all || self.reads_and_writes.contains(index.sparse_set_index())
@ -136,6 +152,18 @@ impl<T: SparseSetIndex> Access<T> {
self.writes_all || !self.writes.is_clear()
}
/// Returns true if this has an archetypal (indirect) access to the element given by `index`.
///
/// This is an element whose value is 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>`].
///
/// [`Has<T>`]: crate::query::Has
pub fn has_archetypal(&self, index: T) -> bool {
self.archetypal.contains(index.sparse_set_index())
}
/// Sets this as having access to all indexed elements (i.e. `&World`).
pub fn read_all(&mut self) {
self.reads_all = true;
@ -272,6 +300,18 @@ impl<T: SparseSetIndex> Access<T> {
pub fn writes(&self) -> impl Iterator<Item = T> + '_ {
self.writes.ones().map(T::get_sparse_set_index)
}
/// Returns the indices of the elements that this has an archetypal access to.
///
/// These are elements 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>`].
///
/// [`Has<T>`]: crate::query::Has
pub fn archetypal(&self) -> impl Iterator<Item = T> + '_ {
self.archetypal.ones().map(T::get_sparse_set_index)
}
}
/// An [`Access`] that has been filtered to include and exclude certain combinations of elements.
@ -469,6 +509,20 @@ impl<T: SparseSetIndex> FilteredAccess<T> {
pub fn is_subset(&self, other: &FilteredAccess<T>) -> bool {
self.required.is_subset(&other.required) && self.access().is_subset(other.access())
}
/// Returns the indices of the elements that this access filters for.
pub fn with_filters(&self) -> impl Iterator<Item = T> + '_ {
self.filter_sets
.iter()
.flat_map(|f| f.with.ones().map(T::get_sparse_set_index))
}
/// Returns the indices of the elements that this access filters out.
pub fn without_filters(&self) -> impl Iterator<Item = T> + '_ {
self.filter_sets
.iter()
.flat_map(|f| f.without.ones().map(T::get_sparse_set_index))
}
}
#[derive(Clone, Eq, PartialEq)]

View file

@ -1403,8 +1403,11 @@ unsafe impl<T: Component> WorldQuery for Has<T> {
*fetch
}
fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess<ComponentId>) {
// Do nothing as presence of `Has<T>` never affects whether two queries are disjoint
fn update_component_access(
&component_id: &Self::State,
access: &mut FilteredAccess<ComponentId>,
) {
access.access_mut().add_archetypal(component_id);
}
fn init_state(world: &mut World) -> ComponentId {

View file

@ -95,6 +95,26 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
) -> &QueryState<NewD, NewF> {
&*(self as *const QueryState<D, F> as *const QueryState<NewD, NewF>)
}
/// Returns the archetype components accessed by this query.
pub fn archetype_component_access(&self) -> &Access<ArchetypeComponentId> {
&self.archetype_component_access
}
/// Returns the components accessed by this query.
pub fn component_access(&self) -> &FilteredAccess<ComponentId> {
&self.component_access
}
/// Returns the tables matched by this query.
pub fn matched_tables(&self) -> &[TableId] {
&self.matched_table_ids
}
/// Returns the archetypes matched by this query.
pub fn matched_archetypes(&self) -> &[ArchetypeId] {
&self.matched_archetype_ids
}
}
impl<D: QueryData, F: QueryFilter> QueryState<D, F> {