Skip to main content

elevator_core/components/
rider.rs

1//! Rider (passenger/cargo) core data and lifecycle.
2
3use serde::{Deserialize, Serialize};
4
5use super::units::Weight;
6use crate::entity::EntityId;
7
8/// Lifecycle phase of a rider entity.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10#[non_exhaustive]
11pub enum RiderPhase {
12    /// Waiting at a stop.
13    Waiting,
14    /// Boarding an elevator (transient, one tick).
15    Boarding(EntityId),
16    /// Riding in an elevator.
17    Riding(EntityId),
18    /// Exiting an elevator (transient, one tick).
19    #[serde(alias = "Alighting")]
20    Exiting(EntityId),
21    /// Walking between transfer stops.
22    Walking,
23    /// Reached final destination.
24    Arrived,
25    /// Gave up waiting.
26    Abandoned,
27    /// Parked at a stop, not seeking an elevator.
28    Resident,
29}
30
31impl RiderPhase {
32    /// True when the rider is currently inside or transitioning through an
33    /// elevator cab — i.e., [`Boarding`](Self::Boarding),
34    /// [`Riding`](Self::Riding), or [`Exiting`](Self::Exiting).
35    ///
36    /// Useful for code that needs to treat all three mid-elevator phases
37    /// uniformly (rendering, per-elevator population counts, skipping
38    /// stop-queue updates) without writing a three-arm `match`.
39    #[must_use]
40    pub const fn is_aboard(&self) -> bool {
41        matches!(self, Self::Boarding(_) | Self::Riding(_) | Self::Exiting(_))
42    }
43}
44
45/// Data-less companion to [`RiderPhase`] for error messages and pattern matching
46/// without requiring the inner `EntityId`.
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
48#[non_exhaustive]
49pub enum RiderPhaseKind {
50    /// Waiting at a stop.
51    Waiting,
52    /// Boarding an elevator.
53    Boarding,
54    /// Riding in an elevator.
55    Riding,
56    /// Exiting an elevator.
57    Exiting,
58    /// Walking between transfer stops.
59    Walking,
60    /// Reached final destination.
61    Arrived,
62    /// Gave up waiting.
63    Abandoned,
64    /// Parked at a stop.
65    Resident,
66}
67
68impl RiderPhase {
69    /// Return the data-less kind of this phase.
70    #[must_use]
71    pub const fn kind(&self) -> RiderPhaseKind {
72        match self {
73            Self::Waiting => RiderPhaseKind::Waiting,
74            Self::Boarding(_) => RiderPhaseKind::Boarding,
75            Self::Riding(_) => RiderPhaseKind::Riding,
76            Self::Exiting(_) => RiderPhaseKind::Exiting,
77            Self::Walking => RiderPhaseKind::Walking,
78            Self::Arrived => RiderPhaseKind::Arrived,
79            Self::Abandoned => RiderPhaseKind::Abandoned,
80            Self::Resident => RiderPhaseKind::Resident,
81        }
82    }
83}
84
85impl std::fmt::Display for RiderPhaseKind {
86    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87        match self {
88            Self::Waiting => write!(f, "Waiting"),
89            Self::Boarding => write!(f, "Boarding"),
90            Self::Riding => write!(f, "Riding"),
91            Self::Exiting => write!(f, "Exiting"),
92            Self::Walking => write!(f, "Walking"),
93            Self::Arrived => write!(f, "Arrived"),
94            Self::Abandoned => write!(f, "Abandoned"),
95            Self::Resident => write!(f, "Resident"),
96        }
97    }
98}
99
100impl std::fmt::Display for RiderPhase {
101    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102        match self {
103            Self::Waiting => write!(f, "Waiting"),
104            Self::Boarding(id) => write!(f, "Boarding({id:?})"),
105            Self::Riding(id) => write!(f, "Riding({id:?})"),
106            Self::Exiting(id) => write!(f, "Exiting({id:?})"),
107            Self::Walking => write!(f, "Walking"),
108            Self::Arrived => write!(f, "Arrived"),
109            Self::Abandoned => write!(f, "Abandoned"),
110            Self::Resident => write!(f, "Resident"),
111        }
112    }
113}
114
115/// Core component for any entity that rides elevators.
116///
117/// This is the minimum data the simulation needs. Games attach
118/// additional components (`VipTag`, `FreightData`, `PersonData`, etc.)
119/// for game-specific behavior. An entity with `Rider` but no
120/// Route component can be boarded/exited manually by game code.
121#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
122pub struct Rider {
123    /// Weight contributed to elevator load.
124    pub(crate) weight: Weight,
125    /// Current rider lifecycle phase.
126    pub(crate) phase: RiderPhase,
127    /// The stop entity this rider is currently at (while Waiting/Arrived/Abandoned/Resident).
128    pub(crate) current_stop: Option<EntityId>,
129    /// Tick when this rider was spawned.
130    pub(crate) spawn_tick: u64,
131    /// Tick when this rider boarded (for ride-time metrics).
132    pub(crate) board_tick: Option<u64>,
133    /// Opaque consumer-attached tag. The engine doesn't interpret
134    /// this value — it survives snapshot round-trip so consumers
135    /// can correlate riders with external identifiers (e.g. a
136    /// game-side sim id, a player id, a freight-shipment id) without
137    /// maintaining a parallel map keyed by `RiderId`. Defaults to 0
138    /// (no tag); `0` is reserved by convention for "untagged."
139    #[serde(default)]
140    pub(crate) tag: u64,
141}
142
143impl Rider {
144    /// Weight contributed to elevator load.
145    #[must_use]
146    pub const fn weight(&self) -> Weight {
147        self.weight
148    }
149
150    /// Current rider lifecycle phase.
151    #[must_use]
152    pub const fn phase(&self) -> RiderPhase {
153        self.phase
154    }
155
156    /// The stop entity this rider is currently at (while Waiting/Arrived/Abandoned/Resident).
157    #[must_use]
158    pub const fn current_stop(&self) -> Option<EntityId> {
159        self.current_stop
160    }
161
162    /// Tick when this rider was spawned.
163    #[must_use]
164    pub const fn spawn_tick(&self) -> u64 {
165        self.spawn_tick
166    }
167
168    /// Tick when this rider boarded (for ride-time metrics).
169    #[must_use]
170    pub const fn board_tick(&self) -> Option<u64> {
171        self.board_tick
172    }
173
174    /// Opaque consumer-attached tag. The engine doesn't interpret this
175    /// value; consumers use it to correlate riders with external
176    /// identifiers (e.g. a game-side sim id, a player id, a freight
177    /// shipment id) without maintaining a parallel `RiderId → u64` map.
178    /// Defaults to `0`, which is reserved by convention for "untagged."
179    #[must_use]
180    pub const fn tag(&self) -> u64 {
181        self.tag
182    }
183}