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}
134
135impl Rider {
136    /// Weight contributed to elevator load.
137    #[must_use]
138    pub const fn weight(&self) -> Weight {
139        self.weight
140    }
141
142    /// Current rider lifecycle phase.
143    #[must_use]
144    pub const fn phase(&self) -> RiderPhase {
145        self.phase
146    }
147
148    /// The stop entity this rider is currently at (while Waiting/Arrived/Abandoned/Resident).
149    #[must_use]
150    pub const fn current_stop(&self) -> Option<EntityId> {
151        self.current_stop
152    }
153
154    /// Tick when this rider was spawned.
155    #[must_use]
156    pub const fn spawn_tick(&self) -> u64 {
157        self.spawn_tick
158    }
159
160    /// Tick when this rider boarded (for ride-time metrics).
161    #[must_use]
162    pub const fn board_tick(&self) -> Option<u64> {
163        self.board_tick
164    }
165}