Skip to main content

unlit_ecs/
entity.rs

1//! Entity identifiers and their allocator.
2//!
3//! An [`Entity`] is an index plus a generation. Despawning bumps the
4//! generation, so a handle to a dead entity never resolves to a new entity that
5//! happened to reuse the index.
6//!
7//! A handle also carries a name, so that a message names the object it is about
8//! rather than a bare number. [`Debug`] prints it. The name is only a label: it
9//! takes no part in comparison, hashing or [`Entity::to_bits`].
10//!
11//! Where the name comes from is decided where the entity is made. A [`crate::bundle!`]
12//! names the entity after its own source position, [`crate::DynamicBundle::named`]
13//! and [`crate::ScopeMut::spawn_named`] name it after the string the caller
14//! passed, and [`crate::World::reserve_entity`] names it at the reservation.
15//! A builder put together at run time by hand carries no source position, so an
16//! entity spawned from one is called by whatever the caller named it.
17
18use crate::error::EntityAccessError;
19
20/// A handle to an entity.
21///
22/// The handle is a plain value: it stays valid across world mutations as long
23/// as the entity lives, and resolves to nothing once the entity is despawned.
24///
25/// It also carries a name, which [`Debug`] prints. A name is given where the
26/// entity is made: a [`crate::bundle!`] names it after its own source position,
27/// [`crate::DynamicBundle::named`] and [`crate::ScopeMut::spawn_named`] after
28/// the string the caller passed, and [`crate::World::reserve_entity`] at the
29/// reservation. Two handles to one entity can carry different names and still be
30/// equal, because a handle restored from [`Entity::to_bits`] has no name to
31/// restore; [`crate::Scope::entity_name`] reads the one the row stores.
32#[derive(Clone, Copy)]
33pub struct Entity {
34    index: u32,
35    generation: u32,
36    name: &'static str,
37}
38
39/// Prints the name and the bits the handle travels as, and not the index and
40/// generation they are made of: those are the allocator's business, while the
41/// bits are the only part a caller holds when the handle came over a wire.
42impl core::fmt::Debug for Entity {
43    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
44        f.debug_struct("Entity")
45            .field("name", &self.name)
46            .field("bits", &self.to_bits())
47            .finish()
48    }
49}
50impl PartialEq for Entity {
51    fn eq(&self, other: &Self) -> bool {
52        self.index == other.index && self.generation == other.generation
53    }
54}
55
56impl Eq for Entity {}
57
58impl PartialOrd for Entity {
59    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
60        Some(self.cmp(other))
61    }
62}
63
64impl Ord for Entity {
65    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
66        (self.index, self.generation).cmp(&(other.index, other.generation))
67    }
68}
69
70impl Entity {
71    /// Build a handle from raw parts.
72    ///
73    /// The handle only refers to an entity if it was produced by the same
74    /// world, so this is for restoring a handle that came from that world
75    /// (a save file, for example).
76    pub const fn from_raw(index: u32, generation: u32) -> Self {
77        Self {
78            index,
79            generation,
80            name: "",
81        }
82    }
83
84    /// The name part, empty when the handle was restored rather than spawned.
85    #[must_use]
86    pub const fn name(self) -> &'static str {
87        self.name
88    }
89
90    /// The index part.
91    #[must_use]
92    pub const fn index(self) -> u32 {
93        self.index
94    }
95
96    /// The generation part.
97    #[must_use]
98    pub const fn generation(self) -> u32 {
99        self.generation
100    }
101
102    /// The two parts as one value, generation high.
103    ///
104    /// This is not the order [`Ord`] uses: the comparison is by index first,
105    /// then generation, which is what makes entities spawned together sort
106    /// together.
107    #[must_use]
108    pub const fn to_bits(self) -> u64 {
109        ((self.generation as u64) << 32) | self.index as u64
110    }
111
112    /// The handle [`Entity::to_bits`] produced.
113    ///
114    /// The handle only refers to an entity if it was produced by the same
115    /// world, so this is for restoring a handle that came from that world. The
116    /// restored handle carries no name.
117    #[must_use]
118    pub const fn from_bits(bits: u64) -> Self {
119        Self::from_raw(bits as u32, (bits >> 32) as u32)
120    }
121}
122
123/// An [`Entity`] as its [`Entity::to_bits`] integer.
124///
125/// `Entity` is not a newtype, so it cannot be reflected through
126/// [`facet::Facet`]'s `transparent`; this proxy is what a field whose type is
127/// an entity names with `#[facet(opaque, proxy = EntityProxy)]`, and its
128/// transparent `u64` is the shape such a field takes in JSON.
129#[cfg(feature = "reflect")]
130#[derive(facet::Facet)]
131#[facet(transparent)]
132pub struct EntityProxy(pub u64);
133
134#[cfg(feature = "reflect")]
135impl From<EntityProxy> for Entity {
136    fn from(proxy: EntityProxy) -> Self {
137        Entity::from_bits(proxy.0)
138    }
139}
140
141#[cfg(feature = "reflect")]
142impl From<&Entity> for EntityProxy {
143    fn from(value: &Entity) -> Self {
144        Self(value.to_bits())
145    }
146}
147
148/// A list of [`Entity`] handles, each as its [`Entity::to_bits`] integer.
149///
150/// The plural of [`EntityProxy`]: a reflected field whose type is
151/// `Vec<Entity>` names this with `#[facet(opaque, proxy = EntityVecProxy)]`,
152/// and its transparent `Vec<u64>` is the JSON array such a field becomes.
153#[cfg(feature = "reflect")]
154#[derive(facet::Facet)]
155#[facet(transparent)]
156pub struct EntityVecProxy(pub Vec<u64>);
157
158#[cfg(feature = "reflect")]
159impl From<EntityVecProxy> for Vec<Entity> {
160    fn from(proxy: EntityVecProxy) -> Self {
161        proxy.0.into_iter().map(Entity::from_bits).collect()
162    }
163}
164
165#[cfg(feature = "reflect")]
166impl From<&Vec<Entity>> for EntityVecProxy {
167    fn from(value: &Vec<Entity>) -> Self {
168        Self(value.iter().map(|entity| entity.to_bits()).collect())
169    }
170}
171
172/// Where an entity's components live.
173///
174/// A location names an archetype and a row in it. Entities of the same
175/// archetype share the archetype, so a caller holding many locations can group
176/// them by [`Location::archetype`] and resolve that archetype's columns once,
177/// then reach each row through [`Scope::query_one`](crate::Scope::query_one).
178#[derive(Clone, Copy, PartialEq, Eq, Debug)]
179pub struct Location {
180    pub(crate) archetype: u32,
181    pub(crate) row: u32,
182}
183
184impl Location {
185    /// The archetype the entity's components live in.
186    #[inline]
187    pub fn archetype(self) -> u32 {
188        self.archetype
189    }
190
191    /// The entity's row within its archetype.
192    #[inline]
193    pub fn row(self) -> usize {
194        self.row as usize
195    }
196}
197
198#[derive(Clone, Copy)]
199struct Meta {
200    generation: u32,
201    location: Option<Location>,
202    /// Whether the index is handed out by [`Entities::reserve`] and not
203    /// spawned or freed yet.
204    reserved: bool,
205}
206
207/// Allocates entity handles and maps them to their row in an archetype.
208#[derive(Default)]
209pub(crate) struct Entities {
210    meta: Vec<Meta>,
211    /// Indices whose entity is despawned and whose current generation is
212    /// already recorded in `meta`.
213    free: Vec<u32>,
214    /// How many indices are reserved but not spawned. Every reserved index is
215    /// flagged in its [`Meta`], so releasing one is a flag flip rather than a
216    /// search.
217    reserved: usize,
218}
219
220impl Entities {
221    /// Whether the handle refers to a live entity.
222    pub(crate) fn contains(&self, entity: Entity) -> bool {
223        self.location(entity).is_ok()
224    }
225
226    /// Where the entity lives.
227    ///
228    /// A handle whose index was never allocated, whose generation was bumped by
229    /// a despawn, or whose index is reserved but not spawned yet answers
230    /// [`EntityAccessError::NotAlive`].
231    #[inline]
232    pub(crate) fn location(&self, entity: Entity) -> Result<Location, EntityAccessError> {
233        let Some(meta) = self.meta.get(entity.index as usize) else {
234            return Err(EntityAccessError::NotAlive { entity });
235        };
236        if meta.generation != entity.generation {
237            return Err(EntityAccessError::NotAlive { entity });
238        }
239        meta.location.ok_or(EntityAccessError::NotAlive { entity })
240    }
241
242    /// Record where an entity lives, replacing any earlier location.
243    ///
244    /// Panics when the handle is not live: a location is only ever recorded for
245    /// an entity that was just allocated or reserved.
246    pub(crate) fn set_location(&mut self, entity: Entity, location: Location) {
247        let meta = self
248            .meta
249            .get_mut(entity.index as usize)
250            .unwrap_or_else(|| panic!("{entity:?} names no entity index"));
251        assert_eq!(
252            meta.generation, entity.generation,
253            "{entity:?} is a stale handle"
254        );
255        meta.location = Some(location);
256    }
257
258    /// Reuse a freed index, or grow `meta` for a fresh one.
259    fn index(&mut self) -> usize {
260        match self.free.pop() {
261            Some(index) => index as usize,
262            None => {
263                self.meta.push(Meta {
264                    generation: 0,
265                    location: None,
266                    reserved: false,
267                });
268                self.meta.len() - 1
269            }
270        }
271    }
272
273    /// Hand out a fresh handle and mark the index as reserved.
274    pub(crate) fn reserve(&mut self, name: &'static str) -> Entity {
275        let index = self.index();
276        self.meta[index].reserved = true;
277        self.reserved += 1;
278        Entity {
279            index: index as u32,
280            generation: self.meta[index].generation,
281            name,
282        }
283    }
284
285    /// Hand out a handle for a new entity, reusing a free index when possible.
286    pub(crate) fn alloc(&mut self, name: &'static str) -> Entity {
287        let index = self.index();
288        Entity {
289            index: index as u32,
290            generation: self.meta[index].generation,
291            name,
292        }
293    }
294
295    /// Whether the handle came from [`reserve`](Self::reserve) and has not
296    /// been spawned or freed yet.
297    pub(crate) fn is_reserved(&self, entity: Entity) -> bool {
298        self.meta
299            .get(entity.index as usize)
300            .is_some_and(|meta| meta.reserved && meta.generation == entity.generation)
301    }
302
303    /// Mark a reserved handle as spawned.
304    ///
305    /// Returns whether the handle was reserved; a handle that is not is left
306    /// untouched.
307    pub(crate) fn take_reserved(&mut self, entity: Entity) -> bool {
308        if !self.is_reserved(entity) {
309            return false;
310        }
311        self.meta[entity.index as usize].reserved = false;
312        self.reserved -= 1;
313        true
314    }
315
316    /// Release a dead entity's index, so a later entity reuses it with a new
317    /// generation.
318    ///
319    /// Returns whether the handle referred to a reserved or live entity; a
320    /// stale handle, or one already freed, is a no-op.
321    pub(crate) fn free(&mut self, entity: Entity) -> bool {
322        let Some(meta) = self.meta.get_mut(entity.index as usize) else {
323            return false;
324        };
325        if meta.generation != entity.generation {
326            return false;
327        }
328        if !meta.reserved && meta.location.is_none() {
329            return false;
330        }
331        meta.location = None;
332        meta.generation = meta.generation.wrapping_add(1);
333        if meta.reserved {
334            meta.reserved = false;
335            self.reserved -= 1;
336        }
337        self.free.push(entity.index);
338        true
339    }
340}
341#[cfg(test)]
342mod tests {
343    use super::*;
344
345    #[test]
346    fn alloc_reuses_freed_indices_with_a_new_generation() {
347        let mut entities = Entities::default();
348        let first = entities.alloc("test");
349        assert_eq!(first.index(), 0);
350        assert_eq!(first.generation(), 0);
351
352        entities.set_location(
353            first,
354            Location {
355                archetype: 0,
356                row: 0,
357            },
358        );
359        assert!(entities.free(first));
360
361        let second = entities.alloc("test");
362        assert_eq!(second.index(), 0, "the index is reused");
363        assert_ne!(second.generation(), first.generation(), "but not the id");
364        assert!(!entities.contains(first), "the old handle is dead");
365    }
366
367    #[test]
368    fn reserved_entities_are_not_alive_until_spawned() {
369        let mut entities = Entities::default();
370        let entity = entities.reserve("test");
371        assert!(entities.is_reserved(entity));
372        assert!(!entities.contains(entity));
373
374        entities.set_location(
375            entity,
376            Location {
377                archetype: 0,
378                row: 0,
379            },
380        );
381        assert!(
382            entities.take_reserved(entity),
383            "the reservation is consumed"
384        );
385        assert!(entities.contains(entity));
386        assert!(!entities.take_reserved(entity), "and only once");
387    }
388
389    #[test]
390    fn freeing_a_stale_handle_fails() {
391        let mut entities = Entities::default();
392        let entity = entities.alloc("test");
393        entities.set_location(
394            entity,
395            Location {
396                archetype: 0,
397                row: 0,
398            },
399        );
400        assert!(entities.free(entity));
401        assert!(!entities.free(entity), "the handle is already stale");
402    }
403
404    #[test]
405    fn freeing_retires_a_handle_and_keeps_the_other() {
406        let mut entities = Entities::default();
407        let first = entities.alloc("test");
408        let second = entities.alloc("test");
409        entities.set_location(
410            first,
411            Location {
412                archetype: 0,
413                row: 0,
414            },
415        );
416        entities.set_location(
417            second,
418            Location {
419                archetype: 0,
420                row: 1,
421            },
422        );
423        assert!(entities.free(first));
424        assert!(!entities.contains(first));
425        assert!(entities.contains(second));
426    }
427
428    #[test]
429    fn entity_bits_round_trip() {
430        let entity = Entity::from_raw(7, 3);
431        assert_eq!(entity.index(), 7);
432        assert_eq!(entity.generation(), 3);
433        assert_eq!(entity.to_bits(), (3u64 << 32) | 7);
434    }
435
436    #[test]
437    fn the_name_is_a_label_and_not_part_of_the_identity() {
438        let mut entities = Entities::default();
439        let named = entities.reserve("ball");
440        let nameless = Entity::from_raw(named.index(), named.generation());
441        assert_eq!(named, nameless, "a name does not change the handle");
442        assert_eq!(named.to_bits(), nameless.to_bits(), "nor its bits");
443        assert_eq!(named.name(), "ball");
444        assert_eq!(nameless.name(), "", "a restored handle has no name");
445        assert_eq!(
446            Entity::from_bits(named.to_bits()).name(),
447            "",
448            "bits carry no name"
449        );
450    }
451
452    #[test]
453    fn a_debug_message_names_the_entity() {
454        let mut entities = Entities::default();
455        let printed = format!("{:?}", entities.reserve("ball"));
456        assert!(printed.contains("ball"), "{printed} names the entity");
457    }
458}