Document and lock down types in bevy_ecs::archetype (#6742)

# Objective
Document `bevy_ecs::archetype` and and declutter the public documentation for the module by making types non-`pub`.

Addresses #3362 for `bevy_ecs::archetype`.

## Solution
 - Add module level documentation.
 - Add type and API level documentation for all public facing types.
 - Make `ArchetypeId`, `ArchetypeGeneration`, and `ArchetypeComponentId` truly opaque IDs that are not publicly constructable. 
 - Make `AddBundle` non-pub, make `Edges::get_add_bundle` return a `Option<ArchetypeId>` and fork the existing function into `Edges::get_add_bundle_internal`.
 - Remove `pub(crate)` on fields that have a corresponding pub accessor function.
 - Removed the `Archetypes: Default` impl, opting for a `pub(crate) fn new` alternative instead.

---

## Changelog
Added: `ArchetypeGeneration` now implements `Ord` and `PartialOrd`.
Removed: `Archetypes`'s `Default` implementation.
Removed: `Archetype::new` and `Archetype::is_empty`.
Removed: `ArchetypeId::new` and `ArchetypeId::value`.
Removed: `ArchetypeGeneration::value`
Removed: `ArchetypeIdentity`.
Removed: `ArchetypeComponentId::new` and `ArchetypeComponentId::value`.
Removed: `AddBundle`. `Edges::get_add_bundle` now returns `Option<ArchetypeId>`
This commit is contained in:
James Liu 2022-11-28 13:54:12 +00:00
parent bbb652a438
commit d79888bdae
5 changed files with 224 additions and 54 deletions

View file

@ -1,5 +1,23 @@
//! Types for defining [`Archetype`]s, collections of entities that have the same set of
//! components.
//!
//! An archetype uniquely describes a group of entities that share the same components:
//! a world only has one archetype for each unique combination of components, and all
//! entities that have those components and only those components belong to that
//! archetype.
//!
//! Archetypes are not to be confused with [`Table`]s. Each archetype stores its table
//! components in one table, and each archetype uniquely points to one table, but multiple
//! archetypes may store their table components in the same table. These archetypes
//! differ only by the [`SparseSet`] components.
//!
//! Like tables, archetypes can be created but are never cleaned up. Empty archetypes are
//! not removed, and persist until the world is dropped.
//!
//! Archetypes can be fetched from [`Archetypes`], which is accessible via [`World::archetypes`].
//!
//! [`Table`]: crate::storage::Table
//! [`World::archetypes`]: crate::world::World::archetypes
use crate::{
bundle::BundleId,
@ -13,11 +31,21 @@ use std::{
ops::{Index, IndexMut},
};
/// An opaque unique ID for a single [`Archetype`] within a [`World`].
///
/// Archetype IDs are only valid for a given World, and are not globally unique.
/// Attempting to use an archetype ID on a world that it wasn't sourced from will
/// not return the archetype with the same components. The only exception to this is
/// [`EMPTY`] which is guarenteed to be identical for all Worlds.
///
/// [`World`]: crate::world::World
/// [`EMPTY`]: crate::archetype::ArchetypeId::EMPTY
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[repr(transparent)]
pub struct ArchetypeId(usize);
impl ArchetypeId {
/// The ID for the [`Archetype`] without any components.
pub const EMPTY: ArchetypeId = ArchetypeId(0);
/// # Safety:
///
@ -25,12 +53,12 @@ impl ArchetypeId {
pub const INVALID: ArchetypeId = ArchetypeId(usize::MAX);
#[inline]
pub const fn new(index: usize) -> Self {
pub(crate) const fn new(index: usize) -> Self {
ArchetypeId(index)
}
#[inline]
pub fn index(self) -> usize {
pub(crate) fn index(self) -> usize {
self.0
}
}
@ -41,9 +69,9 @@ pub(crate) enum ComponentStatus {
Mutated,
}
pub struct AddBundle {
pub(crate) struct AddBundle {
pub archetype_id: ArchetypeId,
pub(crate) bundle_status: Vec<ComponentStatus>,
pub bundle_status: Vec<ComponentStatus>,
}
/// This trait is used to report the status of [`Bundle`](crate::bundle::Bundle) components
@ -98,11 +126,29 @@ pub struct Edges {
}
impl Edges {
/// Checks the cache for the target archetype when adding a bundle to the
/// source archetype. For more information, see [`EntityMut::insert`].
///
/// If this returns `None`, it means there has not been a transition from
/// the source archetype via the provided bundle.
///
/// [`EntityMut::insert`]: crate::world::EntityMut::insert
#[inline]
pub fn get_add_bundle(&self, bundle_id: BundleId) -> Option<&AddBundle> {
pub fn get_add_bundle(&self, bundle_id: BundleId) -> Option<ArchetypeId> {
self.get_add_bundle_internal(bundle_id)
.map(|bundle| bundle.archetype_id)
}
/// Internal version of `get_add_bundle` that fetches the full `AddBundle`.
#[inline]
pub(crate) fn get_add_bundle_internal(&self, bundle_id: BundleId) -> Option<&AddBundle> {
self.add_bundle.get(bundle_id)
}
/// Caches the target archetype when adding a bundle to the source archetype.
/// For more information, see [`EntityMut::insert`].
///
/// [`EntityMut::insert`]: crate::world::EntityMut::insert
#[inline]
pub(crate) fn insert_add_bundle(
&mut self,
@ -119,11 +165,25 @@ impl Edges {
);
}
/// Checks the cache for the target archetype when removing a bundle to the
/// source archetype. For more information, see [`EntityMut::remove`].
///
/// If this returns `None`, it means there has not been a transition from
/// the source archetype via the provided bundle.
///
/// If this returns `Some(None)`, it means that the bundle cannot be removed
/// from the source archetype.
///
/// [`EntityMut::remove`]: crate::world::EntityMut::remove
#[inline]
pub fn get_remove_bundle(&self, bundle_id: BundleId) -> Option<Option<ArchetypeId>> {
self.remove_bundle.get(bundle_id).cloned()
}
/// Caches the target archetype when removing a bundle to the source archetype.
/// For more information, see [`EntityMut::remove`].
///
/// [`EntityMut::remove`]: crate::world::EntityMut::remove
#[inline]
pub(crate) fn insert_remove_bundle(
&mut self,
@ -133,6 +193,13 @@ impl Edges {
self.remove_bundle.insert(bundle_id, archetype_id);
}
/// Checks the cache for the target archetype when removing a bundle to the
/// source archetype. For more information, see [`EntityMut::remove_intersection`].
///
/// If this returns `None`, it means there has not been a transition from
/// the source archetype via the provided bundle.
///
/// [`EntityMut::remove_intersection`]: crate::world::EntityMut::remove_intersection
#[inline]
pub fn get_remove_bundle_intersection(
&self,
@ -141,6 +208,10 @@ impl Edges {
self.remove_bundle_intersection.get(bundle_id).cloned()
}
/// Caches the target archetype when removing a bundle to the source archetype.
/// For more information, see [`EntityMut::remove_intersection`].
///
/// [`EntityMut::remove_intersection`]: crate::world::EntityMut::remove_intersection
#[inline]
pub(crate) fn insert_remove_bundle_intersection(
&mut self,
@ -152,17 +223,24 @@ impl Edges {
}
}
/// Metadata about an [`Entity`] in a [`Archetype`].
pub struct ArchetypeEntity {
pub(crate) entity: Entity,
pub(crate) table_row: usize,
entity: Entity,
table_row: usize,
}
impl ArchetypeEntity {
pub fn entity(&self) -> Entity {
/// The ID of the entity.
#[inline]
pub const fn entity(&self) -> Entity {
self.entity
}
pub fn table_row(&self) -> usize {
/// The row in the [`Table`] where the entity's components are stored.
///
/// [`Table`]: crate::storage::Table
#[inline]
pub const fn table_row(&self) -> usize {
self.table_row
}
}
@ -172,11 +250,20 @@ pub(crate) struct ArchetypeSwapRemoveResult {
pub(crate) table_row: usize,
}
pub(crate) struct ArchetypeComponentInfo {
pub(crate) storage_type: StorageType,
pub(crate) archetype_component_id: ArchetypeComponentId,
/// Internal metadata for a [`Component`] within a given [`Archetype`].
///
/// [`Component`]: crate::component::Component
struct ArchetypeComponentInfo {
storage_type: StorageType,
archetype_component_id: ArchetypeComponentId,
}
/// Metadata for a single archetype within a [`World`].
///
/// For more information, see the *[module level documentation]*.
///
/// [`World`]: crate::world::World
/// [module level documentation]: crate::archetype
pub struct Archetype {
id: ArchetypeId,
table_id: TableId,
@ -186,7 +273,7 @@ pub struct Archetype {
}
impl Archetype {
pub fn new(
pub(crate) fn new(
id: ArchetypeId,
table_id: TableId,
table_components: impl Iterator<Item = (ComponentId, ArchetypeComponentId)>,
@ -223,11 +310,15 @@ impl Archetype {
}
}
/// Fetches the ID for the archetype.
#[inline]
pub fn id(&self) -> ArchetypeId {
self.id
}
/// Fetches the archetype's [`Table`] ID.
///
/// [`Table`]: crate::storage::Table
#[inline]
pub fn table_id(&self) -> TableId {
self.table_id
@ -238,6 +329,11 @@ impl Archetype {
&self.entities
}
/// Gets an iterator of all of the components stored in [`Table`]s.
///
/// All of the IDs are unique.
///
/// [`Table`]: crate::storage::Table
#[inline]
pub fn table_components(&self) -> impl Iterator<Item = ComponentId> + '_ {
self.components
@ -246,6 +342,11 @@ impl Archetype {
.map(|(id, _)| *id)
}
/// Gets an iterator of all of the components stored in [`ComponentSparseSet`]s.
///
/// All of the IDs are unique.
///
/// [`ComponentSparseSet`]: crate::storage::ComponentSparseSet
#[inline]
pub fn sparse_set_components(&self) -> impl Iterator<Item = ComponentId> + '_ {
self.components
@ -254,31 +355,57 @@ impl Archetype {
.map(|(id, _)| *id)
}
/// Gets an iterator of all of the components in the archetype.
///
/// All of the IDs are unique.
#[inline]
pub fn components(&self) -> impl Iterator<Item = ComponentId> + '_ {
self.components.indices()
}
/// Fetches a immutable reference to the archetype's [`Edges`], a cache of
/// archetypal relationships.
#[inline]
pub fn edges(&self) -> &Edges {
&self.edges
}
/// Fetches a mutable reference to the archetype's [`Edges`], a cache of
/// archetypal relationships.
#[inline]
pub(crate) fn edges_mut(&mut self) -> &mut Edges {
&mut self.edges
}
/// Fetches the row in the [`Table`] where the components for the entity at `index`
/// is stored.
///
/// An entity's archetype index can be fetched from [`EntityLocation::index`], which
/// can be retrieved from [`Entities::get`].
///
/// # Panics
/// This function will panic if `index >= self.len()`.
///
/// [`Table`]: crate::storage::Table
/// [`EntityLocation`]: crate::entity::EntityLocation::index
/// [`Entities::get`]: crate::entity::Entities::get
#[inline]
pub fn entity_table_row(&self, index: usize) -> usize {
self.entities[index].table_row
}
/// Updates if the components for the entity at `index` can be found
/// in the corresponding table.
///
/// # Panics
/// This function will panic if `index >= self.len()`.
#[inline]
pub(crate) fn set_entity_table_row(&mut self, index: usize, table_row: usize) {
self.entities[index].table_row = table_row;
}
/// Allocates an entity to the archetype.
///
/// # Safety
/// valid component values must be immediately written to the relevant storages
/// `table_row` must be valid
@ -297,6 +424,9 @@ impl Archetype {
/// Removes the entity at `index` by swapping it out. Returns the table row the entity is stored
/// in.
///
/// # Panics
/// This function will panic if `index >= self.len()`
pub(crate) fn swap_remove(&mut self, index: usize) -> ArchetypeSwapRemoveResult {
let is_last = index == self.entities.len() - 1;
let entity = self.entities.swap_remove(index);
@ -310,21 +440,27 @@ impl Archetype {
}
}
/// Gets the total number of entities that belong to the archetype.
#[inline]
pub fn len(&self) -> usize {
self.entities.len()
}
/// Checks if the archetype has any entities.
#[inline]
pub fn is_empty(&self) -> bool {
self.entities.is_empty()
}
/// Checks if the archetype contains a specific component. This runs in `O(1)` time.
#[inline]
pub fn contains(&self, component_id: ComponentId) -> bool {
self.components.contains(component_id)
}
/// Gets the type of storage where a component in the archetype can be found.
/// Returns `None` if the component is not part of the archetype.
/// This runs in `O(1)` time.
#[inline]
pub fn get_storage_type(&self, component_id: ComponentId) -> Option<StorageType> {
self.components
@ -332,6 +468,9 @@ impl Archetype {
.map(|info| info.storage_type)
}
/// Fetches the corresponding [`ArchetypeComponentId`] for a component in the archetype.
/// Returns `None` if the component is not part of the archetype.
/// This runs in `O(1)` time.
#[inline]
pub fn get_archetype_component_id(
&self,
@ -342,46 +481,68 @@ impl Archetype {
.map(|info| info.archetype_component_id)
}
/// Clears all entities from the archetype.
pub(crate) fn clear_entities(&mut self) {
self.entities.clear();
}
}
/// A generational id that changes every time the set of archetypes changes
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
/// An opaque generational id that changes every time the set of [`Archetypes`] changes.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub struct ArchetypeGeneration(usize);
impl ArchetypeGeneration {
#[inline]
pub const fn initial() -> Self {
pub(crate) const fn initial() -> Self {
ArchetypeGeneration(0)
}
#[inline]
pub fn value(self) -> usize {
pub(crate) fn value(self) -> usize {
self.0
}
}
#[derive(Hash, PartialEq, Eq)]
pub struct ArchetypeIdentity {
struct ArchetypeIdentity {
table_components: Box<[ComponentId]>,
sparse_set_components: Box<[ComponentId]>,
}
/// An opaque unique joint ID for a [`Component`] in an [`Archetype`] within a [`World`].
///
/// A component may be present within multiple archetypes, but each component within
/// each archetype has its own unique `ArchetypeComponentId`. This is leveraged by the system
/// schedulers to opportunistically run multiple systems in parallel that would otherwise
/// conflict. For example, `Query<&mut A, With<B>>` and `Query<&mut A, Without<B>>` can run in
/// parallel as the matched `ArchetypeComponentId` sets for both queries are disjoint, even
/// though `&mut A` on both queries point to the same [`ComponentId`].
///
/// In SQL terms, these IDs are composite keys on a [many-to-many relationship] between archetypes
/// and components. Each component type will have only one [`ComponentId`], but may have many
/// [`ArchetypeComponentId`]s, one for every archetype the component is present in. Likewise, each
/// archetype will have only one [`ArchetypeId`] but may have many [`ArchetypeComponentId`]s, one
/// for each component that belongs to the archetype.
///
/// Every [`Resource`] is also assigned one of these IDs. As resources do not belong to any
/// particular archetype, a resource's ID uniquely identifies it.
///
/// These IDs are only valid within a given World, and are not globally unique.
/// Attempting to use an ID on a world that it wasn't sourced from will
/// not point to the same archetype nor the same component.
///
/// [`Component`]: crate::component::Component
/// [`World`]: crate::world::World
/// [`Resource`]: crate::system::Resource
/// [many-to-many relationship]: https://en.wikipedia.org/wiki/Many-to-many_(data_model)
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct ArchetypeComponentId(usize);
impl ArchetypeComponentId {
#[inline]
pub const fn new(index: usize) -> Self {
pub(crate) const fn new(index: usize) -> Self {
Self(index)
}
#[inline]
pub fn index(self) -> usize {
self.0
}
}
impl SparseSetIndex for ArchetypeComponentId {
@ -395,14 +556,20 @@ impl SparseSetIndex for ArchetypeComponentId {
}
}
/// The backing store of all [`Archetype`]s within a [`World`].
///
/// For more information, see the *[module level documentation]*.
///
/// [`World`]: crate::world::World
/// [*module level documentation]: crate::archetype
pub struct Archetypes {
pub(crate) archetypes: Vec<Archetype>,
pub(crate) archetype_component_count: usize,
archetype_ids: HashMap<ArchetypeIdentity, ArchetypeId>,
}
impl Default for Archetypes {
fn default() -> Self {
impl Archetypes {
pub(crate) fn new() -> Self {
let mut archetypes = Archetypes {
archetypes: Vec::new(),
archetype_ids: Default::default(),
@ -411,25 +578,29 @@ impl Default for Archetypes {
archetypes.get_id_or_insert(TableId::empty(), Vec::new(), Vec::new());
archetypes
}
}
impl Archetypes {
#[inline]
pub fn generation(&self) -> ArchetypeGeneration {
ArchetypeGeneration(self.archetypes.len())
}
/// Fetches the total number of [`Archetype`]s within the world.
#[inline]
#[allow(clippy::len_without_is_empty)] // the internal vec is never empty.
pub fn len(&self) -> usize {
self.archetypes.len()
}
/// Fetches an immutable reference to the archetype without any compoennts.
///
/// Shorthand for `archetypes.get(ArchetypeId::EMPTY).unwrap()`
#[inline]
pub fn empty(&self) -> &Archetype {
// SAFETY: empty archetype always exists
unsafe { self.archetypes.get_unchecked(ArchetypeId::EMPTY.index()) }
}
/// Fetches an mutable reference to the archetype without any compoennts.
#[inline]
pub(crate) fn empty_mut(&mut self) -> &mut Archetype {
// SAFETY: empty archetype always exists
@ -439,11 +610,8 @@ impl Archetypes {
}
}
#[inline]
pub fn is_empty(&self) -> bool {
self.archetypes.is_empty()
}
/// Fetches an immutable reference to an [`Archetype`] using its
/// ID. Returns `None` if no corresponding archetype exists.
#[inline]
pub fn get(&self, id: ArchetypeId) -> Option<&Archetype> {
self.archetypes.get(id.index())
@ -464,6 +632,7 @@ impl Archetypes {
}
}
/// Returns a read-only iterator over all archetypes.
#[inline]
pub fn iter(&self) -> impl Iterator<Item = &Archetype> {
self.archetypes.iter()
@ -517,6 +686,7 @@ impl Archetypes {
self.archetype_component_count
}
/// Clears all entities from all archetypes.
pub(crate) fn clear_entities(&mut self) {
for archetype in &mut self.archetypes {
archetype.clear_entities();

View file

@ -420,8 +420,8 @@ impl BundleInfo {
components: &mut Components,
archetype_id: ArchetypeId,
) -> ArchetypeId {
if let Some(add_bundle) = archetypes[archetype_id].edges().get_add_bundle(self.id) {
return add_bundle.archetype_id;
if let Some(add_bundle_id) = archetypes[archetype_id].edges().get_add_bundle(self.id) {
return add_bundle_id;
}
let mut new_table_components = Vec::new();
let mut new_sparse_set_components = Vec::new();
@ -537,7 +537,7 @@ impl<'a, 'b> BundleInserter<'a, 'b> {
let add_bundle = self
.archetype
.edges()
.get_add_bundle(self.bundle_info.id)
.get_add_bundle_internal(self.bundle_info.id)
.unwrap();
self.bundle_info.write_components(
self.table,
@ -562,7 +562,7 @@ impl<'a, 'b> BundleInserter<'a, 'b> {
let add_bundle = self
.archetype
.edges()
.get_add_bundle(self.bundle_info.id)
.get_add_bundle_internal(self.bundle_info.id)
.unwrap();
self.bundle_info.write_components(
self.table,
@ -614,7 +614,7 @@ impl<'a, 'b> BundleInserter<'a, 'b> {
let add_bundle = self
.archetype
.edges()
.get_add_bundle(self.bundle_info.id)
.get_add_bundle_internal(self.bundle_info.id)
.unwrap();
self.bundle_info.write_components(
new_table,

View file

@ -550,8 +550,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's,
let archetype_entity = self.archetype_entities.get_unchecked(index);
Some(Q::fetch(
&mut self.fetch,
archetype_entity.entity,
archetype_entity.table_row,
archetype_entity.entity(),
archetype_entity.table_row(),
))
}
} else {
@ -644,8 +644,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's,
let archetype_entity = self.archetype_entities.get_unchecked(self.current_index);
if !F::filter_fetch(
&mut self.filter,
archetype_entity.entity,
archetype_entity.table_row,
archetype_entity.entity(),
archetype_entity.table_row(),
) {
self.current_index += 1;
continue;
@ -655,8 +655,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's,
// `current_index` is an archetype index row in range of the current archetype, because if it was not, then the if above would have been executed.
let item = Q::fetch(
&mut self.fetch,
archetype_entity.entity,
archetype_entity.table_row,
archetype_entity.entity(),
archetype_entity.table_row(),
);
self.current_index += 1;
return Some(item);

View file

@ -966,15 +966,15 @@ impl<Q: WorldQuery, F: ReadOnlyWorldQuery> QueryState<Q, F> {
let archetype_entity = entities.get_unchecked(idx);
if !F::filter_fetch(
&mut filter,
archetype_entity.entity,
archetype_entity.table_row,
archetype_entity.entity(),
archetype_entity.table_row(),
) {
continue;
}
func(Q::fetch(
&mut fetch,
archetype_entity.entity,
archetype_entity.table_row,
archetype_entity.entity(),
archetype_entity.table_row(),
));
}
}
@ -1097,15 +1097,15 @@ impl<Q: WorldQuery, F: ReadOnlyWorldQuery> QueryState<Q, F> {
let archetype_entity = entities.get_unchecked(archetype_index);
if !F::filter_fetch(
&mut filter,
archetype_entity.entity,
archetype_entity.table_row,
archetype_entity.entity(),
archetype_entity.table_row(),
) {
continue;
}
func(Q::fetch(
&mut fetch,
archetype_entity.entity,
archetype_entity.table_row,
archetype_entity.entity(),
archetype_entity.table_row(),
));
}
};

View file

@ -70,7 +70,7 @@ impl Default for World {
id: WorldId::new().expect("More `bevy` `World`s have been created than is supported"),
entities: Default::default(),
components: Default::default(),
archetypes: Default::default(),
archetypes: Archetypes::new(),
storages: Default::default(),
bundles: Default::default(),
removed_components: Default::default(),
@ -327,7 +327,7 @@ impl World {
self.archetypes
.iter()
.flat_map(|archetype| archetype.entities().iter())
.map(|archetype_entity| archetype_entity.entity)
.map(|archetype_entity| archetype_entity.entity())
}
/// Retrieves an [`EntityMut`] that exposes read and write operations for the given `entity`.