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()`).
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
///
/// 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)),*) => {
#[allow(unused_variables)]
#[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_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.
///
@ -524,7 +530,7 @@ all_tuples!(impl_query_filter_tuple, 0, 15, F, S);
/// # Time complexity
///
/// `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.
///
/// 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
/// from an table.
/// from a table.
///
/// # Safety
/// - 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],
fetch: D::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,
// either table row or archetype index, depending on whether both `D`'s and `F`'s fetches are dense
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?
///
/// 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**.
fn max_remaining(&self, tables: &'w Tables, archetypes: &'w Archetypes) -> usize {
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.
// `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 row = TableRow::from_usize(self.current_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:
// - set_table was called prior.
// - `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`.
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`].
///
/// 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)]
// SAFETY NOTE:
// 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 = ()> {
world_id: WorldId,
pub(crate) archetype_generation: ArchetypeGeneration,
/// Metadata about the [`Table`](crate::storage::Table)s matched by this query.
pub(crate) matched_tables: FixedBitSet,
/// Metadata about the [`Archetype`]s matched by this query.
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>,
// NOTE: we maintain both a TableId bitset and a vec because iterating the vec is faster
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 {
if D::matches_component_set(&self.fetch_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>;
/// 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
// and forgetting to do so would be unsound.
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>;
/// 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(
state: &Self::State,
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.
///
/// 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
///