Update ecs query docs (#12595)

# Objective

I'm reading through the ecs query code for the first time, and updating
the docs:
- fixed some typos
- added some docs about things I was confused about (in particular what
the difference between `matches_component_set` and
`update_component_access` was)
This commit is contained in:
Charles Bournhonesque 2024-03-22 09:28:41 -04:00 committed by GitHub
parent 7673afb03e
commit e33b93e312
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 40 additions and 9 deletions

View File

@ -79,6 +79,12 @@ pub trait QueryFilter: WorldQuery {
/// many elements are being iterated (such as `Iterator::collect()`). /// many elements are being iterated (such as `Iterator::collect()`).
const IS_ARCHETYPAL: bool; const IS_ARCHETYPAL: bool;
/// Returns true if the provided [`Entity`] and [`TableRow`] should be included in the query results.
/// If false, the entity will be skipped.
///
/// Note that this is called after already restricting the matched [`Table`]s and [`Archetype`]s to the
/// ones that are compatible with the Filter's access.
///
/// # Safety /// # Safety
/// ///
/// Must always be called _after_ [`WorldQuery::set_table`] or [`WorldQuery::set_archetype`]. `entity` and /// Must always be called _after_ [`WorldQuery::set_table`] or [`WorldQuery::set_archetype`]. `entity` and
@ -357,7 +363,7 @@ impl<T: WorldQuery> Clone for OrFetch<'_, T> {
} }
} }
macro_rules! impl_query_filter_tuple { macro_rules! impl_or_query_filter {
($(($filter: ident, $state: ident)),*) => { ($(($filter: ident, $state: ident)),*) => {
#[allow(unused_variables)] #[allow(unused_variables)]
#[allow(non_snake_case)] #[allow(non_snake_case)]
@ -506,7 +512,7 @@ macro_rules! impl_tuple_query_filter {
} }
all_tuples!(impl_tuple_query_filter, 0, 15, F); all_tuples!(impl_tuple_query_filter, 0, 15, F);
all_tuples!(impl_query_filter_tuple, 0, 15, F, S); all_tuples!(impl_or_query_filter, 0, 15, F, S);
/// A filter on a component that only retains results added after the system last ran. /// A filter on a component that only retains results added after the system last ran.
/// ///
@ -524,7 +530,7 @@ all_tuples!(impl_query_filter_tuple, 0, 15, F, S);
/// # Time complexity /// # Time complexity
/// ///
/// `Added` is not [`ArchetypeFilter`], which practically means that /// `Added` is not [`ArchetypeFilter`], which practically means that
/// if query (with `T` component filter) matches million entities, /// if the query (with `T` component filter) matches a million entities,
/// `Added<T>` filter will iterate over all of them even if none of them were just added. /// `Added<T>` filter will iterate over all of them even if none of them were just added.
/// ///
/// For example, these two systems are roughly equivalent in terms of performance: /// For example, these two systems are roughly equivalent in terms of performance:

View File

@ -42,7 +42,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> {
} }
/// Executes the equivalent of [`Iterator::for_each`] over a contiguous segment /// Executes the equivalent of [`Iterator::for_each`] over a contiguous segment
/// from an table. /// from a table.
/// ///
/// # Safety /// # Safety
/// - all `rows` must be in `[0, table.entity_count)`. /// - all `rows` must be in `[0, table.entity_count)`.
@ -656,7 +656,7 @@ struct QueryIterationCursor<'w, 's, D: QueryData, F: QueryFilter> {
archetype_entities: &'w [ArchetypeEntity], archetype_entities: &'w [ArchetypeEntity],
fetch: D::Fetch<'w>, fetch: D::Fetch<'w>,
filter: F::Fetch<'w>, filter: F::Fetch<'w>,
// length of the table table or length of the archetype, depending on whether both `D`'s and `F`'s fetches are dense // length of the table or length of the archetype, depending on whether both `D`'s and `F`'s fetches are dense
current_len: usize, current_len: usize,
// either table row or archetype index, depending on whether both `D`'s and `F`'s fetches are dense // either table row or archetype index, depending on whether both `D`'s and `F`'s fetches are dense
current_row: usize, current_row: usize,
@ -743,7 +743,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> {
/// How many values will this cursor return at most? /// How many values will this cursor return at most?
/// ///
/// Note that if `D::IS_ARCHETYPAL && F::IS_ARCHETYPAL`, the return value /// Note that if `F::IS_ARCHETYPAL`, the return value
/// will be **the exact count of remaining values**. /// will be **the exact count of remaining values**.
fn max_remaining(&self, tables: &'w Tables, archetypes: &'w Archetypes) -> usize { fn max_remaining(&self, tables: &'w Tables, archetypes: &'w Archetypes) -> usize {
let remaining_matched: usize = if Self::IS_DENSE { let remaining_matched: usize = if Self::IS_DENSE {
@ -788,7 +788,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> {
} }
// SAFETY: set_table was called prior. // SAFETY: set_table was called prior.
// `current_row` is a table row in range of the current table, because if it was not, then the if above would have been executed. // `current_row` is a table row in range of the current table, because if it was not, then the above would have been executed.
let entity = unsafe { self.table_entities.get_unchecked(self.current_row) }; let entity = unsafe { self.table_entities.get_unchecked(self.current_row) };
let row = TableRow::from_usize(self.current_row); let row = TableRow::from_usize(self.current_row);
if !F::filter_fetch(&mut self.filter, *entity, row) { if !F::filter_fetch(&mut self.filter, *entity, row) {
@ -799,7 +799,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> {
// SAFETY: // SAFETY:
// - set_table was called prior. // - set_table was called prior.
// - `current_row` must be a table row in range of the current table, // - `current_row` must be a table row in range of the current table,
// because if it was not, then the if above would have been executed. // because if it was not, then the above would have been executed.
// - fetch is only called once for each `entity`. // - fetch is only called once for each `entity`.
let item = unsafe { D::fetch(&mut self.fetch, *entity, row) }; let item = unsafe { D::fetch(&mut self.fetch, *entity, row) };

View File

@ -22,6 +22,17 @@ use super::{
}; };
/// Provides scoped access to a [`World`] state according to a given [`QueryData`] and [`QueryFilter`]. /// Provides scoped access to a [`World`] state according to a given [`QueryData`] and [`QueryFilter`].
///
/// This data is cached between system runs, and is used to:
/// - store metadata about which [`Table`] or [`Archetype`] are matched by the query. "Matched" means
/// that the query will iterate over the data in the matched table/archetype.
/// - cache the [`State`] needed to compute the [`Fetch`] struct used to retrieve data
/// from a specific [`Table`] or [`Archetype`]
/// - build iterators that can iterate over the query results
///
/// [`State`]: crate::query::world_query::WorldQuery::State
/// [`Fetch`]: crate::query::world_query::WorldQuery::Fetch
/// [`Table`]: crate::storage::Table
#[repr(C)] #[repr(C)]
// SAFETY NOTE: // SAFETY NOTE:
// Do not add any new fields that use the `D` or `F` generic parameters as this may // Do not add any new fields that use the `D` or `F` generic parameters as this may
@ -29,8 +40,12 @@ use super::{
pub struct QueryState<D: QueryData, F: QueryFilter = ()> { pub struct QueryState<D: QueryData, F: QueryFilter = ()> {
world_id: WorldId, world_id: WorldId,
pub(crate) archetype_generation: ArchetypeGeneration, pub(crate) archetype_generation: ArchetypeGeneration,
/// Metadata about the [`Table`](crate::storage::Table)s matched by this query.
pub(crate) matched_tables: FixedBitSet, pub(crate) matched_tables: FixedBitSet,
/// Metadata about the [`Archetype`]s matched by this query.
pub(crate) matched_archetypes: FixedBitSet, pub(crate) matched_archetypes: FixedBitSet,
/// [`FilteredAccess`] computed by combining the `D` and `F` access. Used to check which other queries
/// this query can run in parallel with.
pub(crate) component_access: FilteredAccess<ComponentId>, pub(crate) component_access: FilteredAccess<ComponentId>,
// NOTE: we maintain both a TableId bitset and a vec because iterating the vec is faster // NOTE: we maintain both a TableId bitset and a vec because iterating the vec is faster
pub(crate) matched_table_ids: Vec<TableId>, pub(crate) matched_table_ids: Vec<TableId>,
@ -330,6 +345,11 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
} }
} }
/// Process the given [`Archetype`] to update internal metadata about the [`Table`](crate::storage::Table)s
/// and [`Archetype`]s that are matched by this query.
///
/// Returns `true` if the given `archetype` matches the query. Otherwise, returns `false`.
/// If there is no match, then there is no need to update the query's [`FilteredAccess`].
fn new_archetype_internal(&mut self, archetype: &Archetype) -> bool { fn new_archetype_internal(&mut self, archetype: &Archetype) -> bool {
if D::matches_component_set(&self.fetch_state, &|id| archetype.contains(id)) if D::matches_component_set(&self.fetch_state, &|id| archetype.contains(id))
&& F::matches_component_set(&self.filter_state, &|id| archetype.contains(id)) && F::matches_component_set(&self.filter_state, &|id| archetype.contains(id))

View File

@ -120,6 +120,8 @@ pub unsafe trait WorldQuery {
) -> Self::Item<'w>; ) -> Self::Item<'w>;
/// Adds any component accesses used by this [`WorldQuery`] to `access`. /// Adds any component accesses used by this [`WorldQuery`] to `access`.
///
/// Used to check which queries are disjoint and can run in parallel
// This does not have a default body of `{}` because 99% of cases need to add accesses // This does not have a default body of `{}` because 99% of cases need to add accesses
// and forgetting to do so would be unsound. // and forgetting to do so would be unsound.
fn update_component_access(state: &Self::State, access: &mut FilteredAccess<ComponentId>); fn update_component_access(state: &Self::State, access: &mut FilteredAccess<ComponentId>);
@ -132,6 +134,9 @@ pub unsafe trait WorldQuery {
fn get_state(world: &World) -> Option<Self::State>; fn get_state(world: &World) -> Option<Self::State>;
/// Returns `true` if this query matches a set of components. Otherwise, returns `false`. /// Returns `true` if this query matches a set of components. Otherwise, returns `false`.
///
/// Used to check which [`Archetype`]s can be skipped by the query
/// (if none of the [`Component`](crate::component::Component)s match)
fn matches_component_set( fn matches_component_set(
state: &Self::State, state: &Self::State,
set_contains_id: &impl Fn(ComponentId) -> bool, set_contains_id: &impl Fn(ComponentId) -> bool,

View File

@ -552,7 +552,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
/// Returns an [`Iterator`] over the read-only query items generated from an [`Entity`] list. /// Returns an [`Iterator`] over the read-only query items generated from an [`Entity`] list.
/// ///
/// Items are returned in the order of the list of entities, and may not be unique if the input /// Items are returned in the order of the list of entities, and may not be unique if the input
/// doesnn't guarantee uniqueness. Entities that don't match the query are skipped. /// doesn't guarantee uniqueness. Entities that don't match the query are skipped.
/// ///
/// # Example /// # Example
/// ///