Skip to main content

elevator_core/components/
rider.rs

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