Skip to main content

unlit_ecs/
archetype.rs

1//! Archetype storage.
2//!
3//! An archetype owns all entities that have exactly the same set of component
4//! types, and stores one column per type. Rows of the different columns line up
5//! with [`Archetype::entities`], so an entity's archetype and row are enough
6//! to find every component it has.
7//!
8//! Archetypes are created on demand and never removed; an empty archetype costs
9//! one allocation.
10
11use core::any::{Any, TypeId};
12
13use hashbrown::HashMap;
14
15use crate::column::{AnyColumn, Column};
16use crate::entity::Entity;
17use crate::error::ArchetypeAccessError;
18use crate::world::World;
19
20/// Which scopes an archetype's rows live in, as far as a whole-archetype gate
21/// can tell.
22///
23/// A derived summary, not part of the archetype's identity: it is counted from
24/// the scope each row was pushed with, and the gates below are the only thing
25/// that uses it. Both gates are conservative in the same direction: they never
26/// accept a row that the per-row check would reject.
27#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
28pub(crate) enum ScopeSummary {
29    /// No rows.
30    #[default]
31    Empty,
32    /// Every row lives in this scope.
33    Uniform(Entity),
34    /// Rows live in more than one scope.
35    Mixed,
36}
37
38impl ScopeSummary {
39    /// Whether at least one row of the archetype is visible from `caller`.
40    ///
41    /// This is the gate a query applies before it visits any row. A uniform
42    /// archetype is either entirely visible or has exactly one visible row: the
43    /// caller's own entity, which lives in `caller`'s parent scope and so sits
44    /// in that parent's uniform archetype.
45    pub(crate) fn any_visible(self, world: &World, caller: Entity) -> bool {
46        match self {
47            Self::Empty => false,
48            Self::Uniform(scope) => {
49                world.visible(caller, scope) || world.parent_scope(caller).ok() == Some(scope)
50            }
51            Self::Mixed => true,
52        }
53    }
54
55    /// Whether every row of the archetype is visible from `caller`.
56    ///
57    /// Vacuously true for an empty archetype, which is why the callers that
58    /// hand out rows also require the archetype to be non-empty.
59    pub(crate) fn all_visible(self, world: &World, caller: Entity) -> bool {
60        match self {
61            Self::Empty => true,
62            Self::Uniform(scope) => world.visible(caller, scope),
63            Self::Mixed => false,
64        }
65    }
66
67    /// Whether row `row` of `archetype` is visible from `caller`.
68    ///
69    /// The per-row answer for a summary a whole-archetype gate could not
70    /// decide. The caller's own row is always visible: a scope sees the entity
71    /// it is rooted at, and that entity lives in the scope's parent.
72    pub(crate) fn row_visible(
73        self,
74        archetype: &Archetype,
75        world: &World,
76        caller: Entity,
77        row: usize,
78    ) -> bool {
79        match self {
80            Self::Empty => false,
81            Self::Uniform(scope) => {
82                world.visible(caller, scope) || archetype.entities()[row] == caller
83            }
84            Self::Mixed => {
85                let entity = archetype.entities()[row];
86                // The row's own scope, stored beside it: a visibility test per
87                // row is the hot path, and walking the tree for the parent
88                // would be a second lookup for an answer already recorded.
89                entity == caller || world.visible(caller, archetype.scopes()[row])
90            }
91        }
92    }
93}
94
95/// One archetype: the entities that share a component set.
96pub struct Archetype {
97    types: Box<[TypeId]>,
98    /// The name of each component type, in the same order as `types`.
99    ///
100    /// Cached here rather than looked up in the world so that an archetype can
101    /// describe itself without reaching back into the world it came from.
102    names: Box<[&'static str]>,
103    columns: Box<[Box<dyn AnyColumn>]>,
104    entities: Vec<Entity>,
105    /// The scope each row lives in, in the same order as `entities`.
106    ///
107    /// The per-row answer to what a whole-archetype `ScopeSummary` cannot decide.
108    /// It is stored rather than looked up so that a visibility test costs one
109    /// read instead of a walk of the scope tree, and it is the only place the
110    /// scope of a row is recorded.
111    scopes: Vec<Entity>,
112    /// How many rows live in each scope, one entry per distinct scope.
113    ///
114    /// The scope summary is derived from this rather than stored, so it cannot
115    /// go stale: an archetype whose rows all move out of one scope stops being
116    /// `Mixed` the moment the last of them leaves.
117    scope_counts: Vec<(Entity, u32)>,
118}
119
120impl Archetype {
121    /// Build an archetype from its component types, their names and their
122    /// columns.
123    ///
124    /// `types` must be sorted and unique, and `names` and `columns` must line
125    /// up with it. Sorting is what makes one component set one archetype: the
126    /// types are the key the pool looks an archetype up by, so two spawns of the
127    /// same set have to arrive in the same order.
128    pub(crate) fn new(
129        types: Box<[TypeId]>,
130        names: Box<[&'static str]>,
131        columns: Box<[Box<dyn AnyColumn>]>,
132    ) -> Self {
133        debug_assert_eq!(types.len(), names.len());
134        debug_assert_eq!(types.len(), columns.len());
135        debug_assert!(types.windows(2).all(|pair| pair[0] < pair[1]));
136        Self {
137            types,
138            names,
139            columns,
140            entities: Vec::new(),
141            scopes: Vec::new(),
142            scope_counts: Vec::new(),
143        }
144    }
145
146    /// The component types, sorted.
147    pub fn types(&self) -> &[TypeId] {
148        &self.types
149    }
150
151    /// The names of the component types, in the same order as
152    /// [`Archetype::types`].
153    ///
154    /// Each name is what [`core::any::type_name`] reported for the type when it
155    /// first entered the world, so the two slices answer for the same types in
156    /// the same order and an archetype names its own shape without consulting
157    /// the world it came from.
158    pub fn names(&self) -> &[&'static str] {
159        &self.names
160    }
161
162    /// The entities, one per row.
163    pub fn entities(&self) -> &[Entity] {
164        &self.entities
165    }
166
167    /// Number of entities.
168    pub fn len(&self) -> usize {
169        self.entities.len()
170    }
171
172    /// Whether the archetype holds no entity.
173    pub fn is_empty(&self) -> bool {
174        self.entities.is_empty()
175    }
176
177    /// The position of `type_id` in [`Archetype::types`], if present.
178    ///
179    /// A scan rather than a binary search. An archetype holds the components one
180    /// entity was spawned with, which a scene entity keeps to a handful, and
181    /// over that range a scan wins: comparing is branch-free and vectorizes once
182    /// this is inlined into its caller, while a binary search pays a
183    /// mispredicted branch per probe. A scan only loses on an archetype far
184    /// wider than a tuple spawn reaches, which a hand-written [`Bundle`] can
185    /// still build. The sorted order [`Bundle`] gives the types is what keeps
186    /// archetypes identical across spawn orders; the lookup does not rely on it.
187    ///
188    /// [`Bundle`]: crate::Bundle
189    #[inline]
190    pub(crate) fn column_index(&self, type_id: TypeId) -> Option<usize> {
191        self.types
192            .iter()
193            .position(|&candidate| candidate == type_id)
194    }
195
196    /// Whether this archetype carries component `C`.
197    ///
198    /// Only the component set is consulted, so no cell is borrowed and this
199    /// answers the same during a borrow that would block reading the component
200    /// itself.
201    #[inline]
202    #[must_use]
203    pub fn has<C: 'static>(&self) -> bool {
204        self.column_index(TypeId::of::<C>()).is_some()
205    }
206
207    /// The column of component `C`.
208    ///
209    /// A query resolves this once per archetype instead of doing the lookup per
210    /// row.
211    #[inline]
212    pub(crate) fn column<C: 'static>(&self) -> Option<&Column<C>> {
213        let index = self.column_index(TypeId::of::<C>())?;
214        self.columns[index].as_any().downcast_ref::<Column<C>>()
215    }
216
217    /// The column of component `C`, to move a value in or out.
218    ///
219    /// Only reachable from an exclusive borrow of the world, so handing it out
220    /// cannot race a query that holds one of the rows.
221    #[inline]
222    pub(crate) fn column_mut<C: 'static>(&mut self) -> Option<&mut Column<C>> {
223        let index = self.column_index(TypeId::of::<C>())?;
224        self.columns[index].as_any_mut().downcast_mut::<Column<C>>()
225    }
226
227    /// Append an already erased component value.
228    ///
229    /// Every caller supplies a value whose type is one of the archetype's
230    /// component types.
231    pub(crate) fn push_erased(&mut self, type_id: TypeId, value: Box<dyn Any>) {
232        let index = self
233            .column_index(type_id)
234            .expect("the archetype was built with this component type");
235        self.columns[index].push_boxed(value);
236    }
237
238    /// Append one entity row that lives in `scope`.
239    pub(crate) fn push_entity(&mut self, entity: Entity, scope: Entity) {
240        self.entities.push(entity);
241        self.scopes.push(scope);
242        self.count_scope(scope, 1);
243    }
244
245    /// The scope each row lives in, aligned with [`Archetype::entities`].
246    #[inline]
247    pub(crate) fn scopes(&self) -> &[Entity] {
248        &self.scopes
249    }
250
251    /// Which scopes the rows live in.
252    pub(crate) fn scope_summary(&self) -> ScopeSummary {
253        match self.scope_counts.as_slice() {
254            [] => ScopeSummary::Empty,
255            [(scope, _)] => ScopeSummary::Uniform(*scope),
256            _ => ScopeSummary::Mixed,
257        }
258    }
259
260    /// Move row `row` from `from` to `to`.
261    ///
262    /// Called when a row's entity is linked into another scope: the component
263    /// set is unchanged, so the row stays where it is and only its scope and
264    /// the counts change.
265    pub(crate) fn move_scope(&mut self, row: usize, from: Entity, to: Entity) {
266        self.scopes[row] = to;
267        self.count_scope(from, -1);
268        self.count_scope(to, 1);
269    }
270
271    /// Add `delta` to the row count of `scope`, dropping the entry at zero.
272    ///
273    /// A count that would go below zero means the counts and the rows already
274    /// disagreed, and the per-scope gates would answer from the disagreement:
275    /// panicking here is what keeps it from spreading into the tree.
276    fn count_scope(&mut self, scope: Entity, delta: i32) {
277        if let Some(index) = self
278            .scope_counts
279            .iter()
280            .position(|(candidate, _)| *candidate == scope)
281        {
282            let count = &mut self.scope_counts[index].1;
283            *count = count
284                .checked_add_signed(delta)
285                .expect("a scope's row count cannot go below zero");
286            if *count == 0 {
287                self.scope_counts.swap_remove(index);
288            }
289            return;
290        }
291        assert!(delta > 0, "a scope with no rows cannot lose one");
292        self.scope_counts.push((scope, delta as u32));
293    }
294
295    /// Drop the row at `row`, moving the last row into the hole.
296    ///
297    /// Returns the entity that was moved into the hole, so its location can be
298    /// updated, or `None` when the row was the last one.
299    pub(crate) fn swap_remove(&mut self, row: usize) -> Option<Entity> {
300        let last = self.entities.len() - 1;
301        let scope = self.scopes.swap_remove(row);
302        for column in self.columns.iter_mut() {
303            drop(column.swap_remove(row));
304        }
305        self.entities.swap_remove(row);
306        self.count_scope(scope, -1);
307        (row != last).then(|| self.entities[row])
308    }
309}
310
311impl core::fmt::Debug for Archetype {
312    /// Reports the shape and the row count, not the rows.
313    ///
314    /// An archetype can hold tens of thousands of entities, and printing them
315    /// would bury the one fact a caller is usually after: which components the
316    /// rows carry. The entity list is reachable through
317    /// [`Archetype::entities`].
318    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
319        f.debug_struct("Archetype")
320            .field("components", &self.names)
321            .field("len", &self.entities.len())
322            .field("scopes", &self.scope_counts.len())
323            .finish_non_exhaustive()
324    }
325}
326
327/// Every archetype of a world.
328pub struct Archetypes {
329    archetypes: Vec<Archetype>,
330    by_types: HashMap<Box<[TypeId]>, u32>,
331}
332
333impl Archetypes {
334    /// A pool containing only the empty archetype.
335    pub(crate) fn new() -> Self {
336        let mut pool = Self {
337            archetypes: Vec::new(),
338            by_types: HashMap::new(),
339        };
340        pool.register(Archetype::new(Box::new([]), Box::new([]), Box::new([])));
341        pool
342    }
343
344    /// The id of the archetype whose types are `types` as `is_match` reads
345    /// them, or `None` when no such archetype is registered.
346    ///
347    /// This is the allocation-free lookup: the type sequence is hashed as it is
348    /// iterated and compared in place, so a caller that holds the types apart
349    /// rather than in one slice builds no key for a hit. The hash comes from
350    /// the map's own hasher, so it always agrees with how the stored keys were
351    /// hashed.
352    ///
353    /// Relies on hashbrown's `raw-entry` feature: without it this does not
354    /// compile rather than silently missing every lookup.
355    pub(crate) fn find_with<T>(
356        &self,
357        types: T,
358        mut is_match: impl FnMut(&[TypeId]) -> bool,
359    ) -> Option<u32>
360    where
361        T: ExactSizeIterator<Item = TypeId>,
362    {
363        use core::hash::{BuildHasher, Hash, Hasher};
364        let mut hasher = self.by_types.hasher().build_hasher();
365        types.len().hash(&mut hasher);
366        for type_id in types {
367            type_id.hash(&mut hasher);
368        }
369        let hash = hasher.finish();
370        self.by_types
371            .raw_entry()
372            .from_hash(hash, |stored| is_match(stored))
373            .map(|(_, &id)| id)
374    }
375
376    /// Register a freshly built archetype and return its id.
377    pub(crate) fn register(&mut self, archetype: Archetype) -> u32 {
378        let id = self.archetypes.len() as u32;
379        self.by_types.insert(archetype.types().into(), id);
380        self.archetypes.push(archetype);
381        id
382    }
383
384    /// The archetype with this id.
385    ///
386    /// An id is only minted when an archetype is registered, so an id that names
387    /// nothing came from another world or from arithmetic on a `u32`; either
388    /// way it answers [`ArchetypeAccessError::NoSuchArchetype`] rather than
389    /// quietly naming no archetype.
390    #[inline]
391    pub fn get(&self, id: u32) -> Result<&Archetype, ArchetypeAccessError> {
392        self.archetypes
393            .get(id as usize)
394            .ok_or(ArchetypeAccessError::NoSuchArchetype { id })
395    }
396
397    /// The archetype with this id, mutably. See [`Archetypes::get`].
398    pub(crate) fn get_mut(&mut self, id: u32) -> Result<&mut Archetype, ArchetypeAccessError> {
399        self.archetypes
400            .get_mut(id as usize)
401            .ok_or(ArchetypeAccessError::NoSuchArchetype { id })
402    }
403
404    /// Every archetype, as a slice.
405    ///
406    /// An archetype's id is its position in this slice, which is what
407    /// [`crate::Location::archetype`] and [`Archetype::names`] are read
408    /// against.
409    pub fn as_slice(&self) -> &[Archetype] {
410        &self.archetypes
411    }
412}
413
414impl core::fmt::Debug for Archetypes {
415    /// Reports the number of archetypes, not the archetypes themselves.
416    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
417        f.debug_struct("Archetypes")
418            .field("len", &self.archetypes.len())
419            .finish_non_exhaustive()
420    }
421}
422
423#[cfg(test)]
424mod tests {
425    use super::*;
426    use crate::column::{AnyColumn, Column};
427
428    #[test]
429    fn the_empty_archetype_exists_first() {
430        let archetypes: Archetypes = Archetypes::new();
431        let empty = archetypes.get(0).expect("the empty archetype");
432        assert!(empty.is_empty());
433        assert!(empty.types().is_empty());
434        assert!(empty.names().is_empty());
435    }
436
437    #[test]
438    fn an_archetype_is_looked_up_by_its_component_set() {
439        // Archetypes keep their component types sorted, so the fixture is too,
440        // and the names are built after the sort so that the two stay aligned.
441        let mut types = [TypeId::of::<u32>(), TypeId::of::<bool>()];
442        types.sort_unstable();
443        let columns: Vec<Box<dyn AnyColumn>> = types
444            .iter()
445            .map(|type_id| {
446                if *type_id == TypeId::of::<u32>() {
447                    Column::<u32>::new().erase()
448                } else {
449                    Column::<bool>::new().erase()
450                }
451            })
452            .collect();
453        let names: Vec<&'static str> = types
454            .iter()
455            .map(|type_id| {
456                if *type_id == TypeId::of::<u32>() {
457                    core::any::type_name::<u32>()
458                } else {
459                    core::any::type_name::<bool>()
460                }
461            })
462            .collect();
463        let mut archetypes: Archetypes = Archetypes::new();
464        let id = archetypes.register(Archetype::new(
465            types.into(),
466            names.clone().into_boxed_slice(),
467            columns.into_boxed_slice(),
468        ));
469        let found = archetypes.find_with(types.iter().copied(), |stored| stored == types);
470        assert_eq!(found, Some(id));
471        let other = [TypeId::of::<u32>()];
472        assert_eq!(
473            archetypes.find_with(other.iter().copied(), |stored| stored == other),
474            None,
475            "a shorter type sequence is another archetype",
476        );
477        assert_eq!(archetypes.as_slice().len(), 2);
478        let registered = archetypes.get(id).expect("just registered");
479        assert_eq!(registered.types(), types);
480        assert_eq!(registered.names(), names.as_slice());
481        assert!(
482            matches!(
483                archetypes.get(u32::MAX),
484                Err(ArchetypeAccessError::NoSuchArchetype { id: u32::MAX })
485            ),
486            "no such archetype",
487        );
488    }
489
490    /// An archetype holding one `u32` column, whose rows carry a scope.
491    fn scoped_archetype() -> Archetype {
492        Archetype::new(
493            Box::new([TypeId::of::<u32>()]),
494            Box::new([core::any::type_name::<u32>()]),
495            Box::new([Column::<u32>::new().erase()]),
496        )
497    }
498
499    /// Push a row of `archetype` that carries `value` and lives in `scope`.
500    fn push_row(archetype: &mut Archetype, entity: Entity, scope: Entity, value: u32) {
501        archetype.push_erased(TypeId::of::<u32>(), Box::new(value));
502        archetype.push_entity(entity, scope);
503    }
504
505    #[test]
506    fn swap_remove_reports_the_entity_moved_into_the_hole() {
507        let mut archetype = scoped_archetype();
508        let scope = Entity::from_raw(9, 0);
509        let a = Entity::from_raw(0, 0);
510        let b = Entity::from_raw(1, 0);
511        let c = Entity::from_raw(2, 0);
512        for (value, entity) in [a, b, c].into_iter().enumerate() {
513            push_row(&mut archetype, entity, scope, value as u32);
514        }
515        assert_eq!(archetype.swap_remove(0), Some(c), "the last row moved in");
516        assert_eq!(archetype.entities(), [c, b]);
517        // Removing the last row moves nothing.
518        assert_eq!(archetype.swap_remove(1), None);
519        assert_eq!(archetype.entities(), [c]);
520    }
521
522    #[test]
523    fn the_scope_summary_follows_the_rows() {
524        let mut archetype = scoped_archetype();
525        let first = Entity::from_raw(9, 0);
526        let second = Entity::from_raw(10, 0);
527        assert_eq!(archetype.scope_summary(), ScopeSummary::Empty);
528        push_row(&mut archetype, Entity::from_raw(0, 0), first, 0);
529        push_row(&mut archetype, Entity::from_raw(1, 0), first, 1);
530        assert_eq!(archetype.scope_summary(), ScopeSummary::Uniform(first));
531        push_row(&mut archetype, Entity::from_raw(2, 0), second, 2);
532        assert_eq!(archetype.scope_summary(), ScopeSummary::Mixed);
533        // Removing the only row of a scope drops it from the count, so the
534        // archetype is uniform again.
535        archetype.swap_remove(2);
536        assert_eq!(archetype.scope_summary(), ScopeSummary::Uniform(first));
537    }
538
539    #[test]
540    fn component_values_are_stored_and_read_back() {
541        let types = [TypeId::of::<u32>()];
542        let columns: Vec<Box<dyn AnyColumn>> = vec![Column::<u32>::new().erase()];
543        let mut archetype: Archetype = Archetype::new(
544            types.into(),
545            Box::new([core::any::type_name::<u32>()]),
546            columns.into_boxed_slice(),
547        );
548        archetype.push_erased(TypeId::of::<u32>(), Box::new(7u32));
549        archetype.push_entity(Entity::from_raw(0, 0), Entity::from_raw(9, 0));
550        let value = archetype.column_mut::<u32>().expect("the column is there");
551        // SAFETY: the test is the only reader, and nothing else borrows the row.
552        assert_eq!(unsafe { *value.cells()[0].get() }, 7);
553    }
554}