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 /// True when the rider is currently at a stop — i.e., one of the
45 /// phases for which [`Rider::current_stop`] is expected to be `Some`:
46 /// [`Waiting`](Self::Waiting), [`Boarding`](Self::Boarding),
47 /// [`Exiting`](Self::Exiting), [`Arrived`](Self::Arrived),
48 /// [`Abandoned`](Self::Abandoned), or [`Resident`](Self::Resident).
49 ///
50 /// Companion to [`is_aboard`](Self::is_aboard). Note `Boarding` and
51 /// `Exiting` are *both* aboard and at a stop: the rider is mid-transfer
52 /// between a stop and an elevator cab.
53 #[must_use]
54 pub const fn is_at_stop(&self) -> bool {
55 matches!(
56 self,
57 Self::Waiting
58 | Self::Boarding(_)
59 | Self::Exiting(_)
60 | Self::Arrived
61 | Self::Abandoned
62 | Self::Resident
63 )
64 }
65}
66
67/// Data-less companion to [`RiderPhase`] for error messages and pattern matching
68/// without requiring the inner `EntityId`.
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
70#[non_exhaustive]
71pub enum RiderPhaseKind {
72 /// Waiting at a stop.
73 Waiting,
74 /// Boarding an elevator.
75 Boarding,
76 /// Riding in an elevator.
77 Riding,
78 /// Exiting an elevator.
79 Exiting,
80 /// Walking between transfer stops.
81 Walking,
82 /// Reached final destination.
83 Arrived,
84 /// Gave up waiting.
85 Abandoned,
86 /// Parked at a stop.
87 Resident,
88}
89
90impl RiderPhase {
91 /// Return the data-less kind of this phase.
92 #[must_use]
93 pub const fn kind(&self) -> RiderPhaseKind {
94 match self {
95 Self::Waiting => RiderPhaseKind::Waiting,
96 Self::Boarding(_) => RiderPhaseKind::Boarding,
97 Self::Riding(_) => RiderPhaseKind::Riding,
98 Self::Exiting(_) => RiderPhaseKind::Exiting,
99 Self::Walking => RiderPhaseKind::Walking,
100 Self::Arrived => RiderPhaseKind::Arrived,
101 Self::Abandoned => RiderPhaseKind::Abandoned,
102 Self::Resident => RiderPhaseKind::Resident,
103 }
104 }
105}
106
107impl std::fmt::Display for RiderPhaseKind {
108 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109 match self {
110 Self::Waiting => write!(f, "Waiting"),
111 Self::Boarding => write!(f, "Boarding"),
112 Self::Riding => write!(f, "Riding"),
113 Self::Exiting => write!(f, "Exiting"),
114 Self::Walking => write!(f, "Walking"),
115 Self::Arrived => write!(f, "Arrived"),
116 Self::Abandoned => write!(f, "Abandoned"),
117 Self::Resident => write!(f, "Resident"),
118 }
119 }
120}
121
122impl std::fmt::Display for RiderPhase {
123 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124 match self {
125 Self::Waiting => write!(f, "Waiting"),
126 Self::Boarding(id) => write!(f, "Boarding({id:?})"),
127 Self::Riding(id) => write!(f, "Riding({id:?})"),
128 Self::Exiting(id) => write!(f, "Exiting({id:?})"),
129 Self::Walking => write!(f, "Walking"),
130 Self::Arrived => write!(f, "Arrived"),
131 Self::Abandoned => write!(f, "Abandoned"),
132 Self::Resident => write!(f, "Resident"),
133 }
134 }
135}
136
137/// Core component for any entity that rides elevators.
138///
139/// This is the minimum data the simulation needs. Games attach
140/// additional components (`VipTag`, `FreightData`, `PersonData`, etc.)
141/// for game-specific behavior. An entity with `Rider` but no
142/// Route component can be boarded/exited manually by game code.
143#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
144pub struct Rider {
145 /// Weight contributed to elevator load.
146 pub(crate) weight: Weight,
147 /// Current rider lifecycle phase.
148 pub(crate) phase: RiderPhase,
149 /// The stop entity this rider is currently at (while Waiting/Arrived/Abandoned/Resident).
150 pub(crate) current_stop: Option<EntityId>,
151 /// Tick when this rider was spawned.
152 pub(crate) spawn_tick: u64,
153 /// Tick when this rider boarded (for ride-time metrics).
154 pub(crate) board_tick: Option<u64>,
155 /// Opaque consumer-attached tag. The engine doesn't interpret
156 /// this value — it survives snapshot round-trip so consumers
157 /// can correlate riders with external identifiers (e.g. a
158 /// game-side sim id, a player id, a freight-shipment id) without
159 /// maintaining a parallel map keyed by `RiderId`. Defaults to 0
160 /// (no tag); `0` is reserved by convention for "untagged."
161 #[serde(default)]
162 pub(crate) tag: u64,
163}
164
165impl Rider {
166 /// Weight contributed to elevator load.
167 #[must_use]
168 pub const fn weight(&self) -> Weight {
169 self.weight
170 }
171
172 /// Current rider lifecycle phase.
173 #[must_use]
174 pub const fn phase(&self) -> RiderPhase {
175 self.phase
176 }
177
178 /// The stop entity this rider is currently at (while Waiting/Arrived/Abandoned/Resident).
179 #[must_use]
180 pub const fn current_stop(&self) -> Option<EntityId> {
181 self.current_stop
182 }
183
184 /// Tick when this rider was spawned.
185 #[must_use]
186 pub const fn spawn_tick(&self) -> u64 {
187 self.spawn_tick
188 }
189
190 /// Tick when this rider boarded (for ride-time metrics).
191 #[must_use]
192 pub const fn board_tick(&self) -> Option<u64> {
193 self.board_tick
194 }
195
196 /// Opaque consumer-attached tag. The engine doesn't interpret this
197 /// value; consumers use it to correlate riders with external
198 /// identifiers (e.g. a game-side sim id, a player id, a freight
199 /// shipment id) without maintaining a parallel `RiderId → u64` map.
200 /// Defaults to `0`, which is reserved by convention for "untagged."
201 #[must_use]
202 pub const fn tag(&self) -> u64 {
203 self.tag
204 }
205}