Skip to main content

elevator_core/
entity.rs

1//! Entity identity and allocation via generational keys.
2
3use serde::{Deserialize, Serialize};
4
5slotmap::new_key_type! {
6    /// Universal entity identifier used across all component storages.
7    /// Serialize/Deserialize provided by slotmap's `serde` feature.
8    pub struct EntityId;
9}
10
11/// Generates a typed newtype wrapper around [`EntityId`].
12///
13/// Each wrapper is `#[repr(transparent)]` with a public inner field for
14/// convenient internal access via `.0`, and delegates `Display` to
15/// `EntityId`'s `Debug` (since slotmap keys do not implement `Display`).
16macro_rules! typed_entity_id {
17    ($(#[$meta:meta])* $name:ident) => {
18        $(#[$meta])*
19        #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
20        #[serde(transparent)]
21        #[repr(transparent)]
22        pub struct $name(pub EntityId);
23
24        impl $name {
25            /// Returns the inner [`EntityId`].
26            #[inline]
27            pub const fn entity(self) -> EntityId {
28                self.0
29            }
30        }
31
32        impl std::fmt::Display for $name {
33            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34                write!(f, "{}({:?})", stringify!($name), self.0)
35            }
36        }
37
38        impl $name {
39            /// Wrap an `EntityId` in this typed newtype **without** verifying
40            /// the entity is actually of that kind. Wrong-kind IDs surface
41            /// later as `EntityNotFound` / `NotAnElevator` from accessor
42            /// calls.
43            ///
44            /// The explicit name signals the unsafety that the silent
45            /// [`From<EntityId>`] impl on this type also exposes — both
46            /// constructors are intended for callers that already hold a
47            /// confirmed-kind id (typed-ID accessors like
48            /// [`World::elevator_ids`](crate::world::World::elevator_ids),
49            /// snapshot deserialization, defense-in-depth tests). At host
50            /// boundaries, prefer [`Simulation::elevator_id`](crate::sim::Simulation::elevator_id)
51            /// / [`Simulation::rider_id`](crate::sim::Simulation::rider_id),
52            /// which return `Option` after a runtime kind check.
53            #[inline]
54            #[must_use]
55            pub const fn wrap_unchecked(id: EntityId) -> Self {
56                Self(id)
57            }
58        }
59
60        impl From<$name> for EntityId {
61            #[inline]
62            fn from(id: $name) -> Self {
63                id.0
64            }
65        }
66
67        impl From<EntityId> for $name {
68            /// Wrap an `EntityId` in this typed newtype **without** verifying
69            /// the entity is actually of that kind. Wrong-kind IDs surface
70            /// later as `EntityNotFound` / `NotAnElevator` from accessor
71            /// calls. Equivalent to [`wrap_unchecked`](Self::wrap_unchecked);
72            /// at host boundaries, prefer the verified
73            /// [`Simulation::elevator_id`](crate::sim::Simulation::elevator_id)
74            /// / [`Simulation::rider_id`](crate::sim::Simulation::rider_id)
75            /// accessors, which return `Option` after a runtime kind check.
76            #[inline]
77            fn from(id: EntityId) -> Self {
78                Self(id)
79            }
80        }
81
82        impl Default for $name {
83            fn default() -> Self {
84                Self(EntityId::default())
85            }
86        }
87    };
88}
89
90typed_entity_id! {
91    /// Typed wrapper around [`EntityId`] for elevator entities.
92    ElevatorId
93}
94
95typed_entity_id! {
96    /// Typed wrapper around [`EntityId`] for rider entities.
97    RiderId
98}