diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index b6bcc154cd5614..8266ac0bf49c9b 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -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, + pub bundle_status: Vec, } /// 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 { + 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 fn get_add_bundle(&self, bundle_id: BundleId) -> Option<&AddBundle> { + 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> { 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, @@ -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 + '_ { 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 + '_ { 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 + '_ { 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 { 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>` and `Query<&mut A, Without>` 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, pub(crate) archetype_component_count: usize, archetype_ids: HashMap, } -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 { 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(); diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 82c8893feb8f0e..21b9bb6b24f5c6 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -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, diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index 0be50220850134..c5f1bab22095de 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -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); diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 245ff7dacb7b4e..63788b9dfca0b2 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -966,15 +966,15 @@ impl QueryState { 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 QueryState { 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(), )); } }; diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 132ac2bb728f59..7b11b42cf57ca5 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -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`.