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}