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}