elevator_core/components/elevator.rs
1//! Elevator state and configuration component.
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashSet;
5
6use crate::door::DoorState;
7use crate::entity::EntityId;
8
9/// Operational phase of an elevator.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11#[non_exhaustive]
12pub enum ElevatorPhase {
13 /// Parked with no pending requests.
14 Idle,
15 /// Travelling toward a specific stop.
16 MovingToStop(EntityId),
17 /// Doors are currently opening.
18 DoorOpening,
19 /// Doors open; riders may board or exit.
20 Loading,
21 /// Doors are currently closing.
22 DoorClosing,
23 /// Stopped at a floor (doors closed, awaiting dispatch).
24 Stopped,
25}
26
27impl std::fmt::Display for ElevatorPhase {
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 match self {
30 Self::Idle => write!(f, "Idle"),
31 Self::MovingToStop(id) => write!(f, "MovingToStop({id:?})"),
32 Self::DoorOpening => write!(f, "DoorOpening"),
33 Self::Loading => write!(f, "Loading"),
34 Self::DoorClosing => write!(f, "DoorClosing"),
35 Self::Stopped => write!(f, "Stopped"),
36 }
37 }
38}
39
40/// Component for an elevator entity.
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct Elevator {
43 /// Current operational phase.
44 pub(crate) phase: ElevatorPhase,
45 /// Door finite-state machine.
46 pub(crate) door: DoorState,
47 /// Maximum travel speed (distance/tick).
48 pub(crate) max_speed: f64,
49 /// Acceleration rate (distance/tick^2).
50 pub(crate) acceleration: f64,
51 /// Deceleration rate (distance/tick^2).
52 pub(crate) deceleration: f64,
53 /// Maximum weight the car can carry.
54 pub(crate) weight_capacity: f64,
55 /// Total weight of riders currently aboard.
56 pub(crate) current_load: f64,
57 /// Entity IDs of riders currently aboard.
58 pub(crate) riders: Vec<EntityId>,
59 /// Stop entity the car is heading toward, if any.
60 pub(crate) target_stop: Option<EntityId>,
61 /// Ticks for a door open/close transition.
62 pub(crate) door_transition_ticks: u32,
63 /// Ticks the door stays fully open.
64 pub(crate) door_open_ticks: u32,
65 /// Line entity this car belongs to.
66 #[serde(alias = "group")]
67 pub(crate) line: EntityId,
68 /// Whether this elevator is currently repositioning (not serving a dispatch).
69 #[serde(default)]
70 pub(crate) repositioning: bool,
71 /// Stop entity IDs this elevator cannot serve (access restriction).
72 #[serde(default)]
73 pub(crate) restricted_stops: HashSet<EntityId>,
74 /// Speed multiplier for Inspection mode (0.0..1.0).
75 #[serde(default = "default_inspection_speed_factor")]
76 pub(crate) inspection_speed_factor: f64,
77 /// Up-direction indicator lamp: whether this car will serve upward trips.
78 ///
79 /// Auto-managed by the dispatch phase: set true when heading up (or idle),
80 /// false while actively committed to a downward trip. Affects boarding:
81 /// a rider whose next leg goes up will not board a car with `going_up=false`.
82 #[serde(default = "default_true")]
83 pub(crate) going_up: bool,
84 /// Down-direction indicator lamp: whether this car will serve downward trips.
85 ///
86 /// Auto-managed by the dispatch phase: set true when heading down (or idle),
87 /// false while actively committed to an upward trip. Affects boarding:
88 /// a rider whose next leg goes down will not board a car with `going_down=false`.
89 #[serde(default = "default_true")]
90 pub(crate) going_down: bool,
91 /// Count of rounded-floor transitions (passing-floors + arrivals). Analogous
92 /// to elevator-saga's `moveCount` scoring axis.
93 #[serde(default)]
94 pub(crate) move_count: u64,
95}
96
97/// Default inspection speed factor (25% of normal speed).
98const fn default_inspection_speed_factor() -> f64 {
99 0.25
100}
101
102/// Default value for direction indicator fields (both lamps on = idle/either direction).
103const fn default_true() -> bool {
104 true
105}
106
107impl Elevator {
108 /// Current operational phase.
109 #[must_use]
110 pub const fn phase(&self) -> ElevatorPhase {
111 self.phase
112 }
113
114 /// Door finite-state machine.
115 #[must_use]
116 pub const fn door(&self) -> &DoorState {
117 &self.door
118 }
119
120 /// Maximum travel speed (distance/tick).
121 #[must_use]
122 pub const fn max_speed(&self) -> f64 {
123 self.max_speed
124 }
125
126 /// Acceleration rate (distance/tick^2).
127 #[must_use]
128 pub const fn acceleration(&self) -> f64 {
129 self.acceleration
130 }
131
132 /// Deceleration rate (distance/tick^2).
133 #[must_use]
134 pub const fn deceleration(&self) -> f64 {
135 self.deceleration
136 }
137
138 /// Maximum weight the car can carry.
139 #[must_use]
140 pub const fn weight_capacity(&self) -> f64 {
141 self.weight_capacity
142 }
143
144 /// Total weight of riders currently aboard.
145 #[must_use]
146 pub const fn current_load(&self) -> f64 {
147 self.current_load
148 }
149
150 /// Entity IDs of riders currently aboard.
151 #[must_use]
152 pub fn riders(&self) -> &[EntityId] {
153 &self.riders
154 }
155
156 /// Stop entity the car is heading toward, if any.
157 #[must_use]
158 pub const fn target_stop(&self) -> Option<EntityId> {
159 self.target_stop
160 }
161
162 /// Ticks for a door open/close transition.
163 #[must_use]
164 pub const fn door_transition_ticks(&self) -> u32 {
165 self.door_transition_ticks
166 }
167
168 /// Ticks the door stays fully open.
169 #[must_use]
170 pub const fn door_open_ticks(&self) -> u32 {
171 self.door_open_ticks
172 }
173
174 /// Line entity this car belongs to.
175 #[must_use]
176 pub const fn line(&self) -> EntityId {
177 self.line
178 }
179
180 /// Whether this elevator is currently repositioning (not serving a dispatch).
181 #[must_use]
182 pub const fn repositioning(&self) -> bool {
183 self.repositioning
184 }
185
186 /// Stop entity IDs this elevator cannot serve (access restriction).
187 #[must_use]
188 pub const fn restricted_stops(&self) -> &HashSet<EntityId> {
189 &self.restricted_stops
190 }
191
192 /// Speed multiplier applied during Inspection mode.
193 #[must_use]
194 pub const fn inspection_speed_factor(&self) -> f64 {
195 self.inspection_speed_factor
196 }
197
198 /// Whether this car's up-direction indicator lamp is lit.
199 ///
200 /// A lit up-lamp signals the car will serve upward-travelling riders.
201 /// Both lamps lit means the car is idle and will accept either direction.
202 #[must_use]
203 pub const fn going_up(&self) -> bool {
204 self.going_up
205 }
206
207 /// Whether this car's down-direction indicator lamp is lit.
208 ///
209 /// A lit down-lamp signals the car will serve downward-travelling riders.
210 /// Both lamps lit means the car is idle and will accept either direction.
211 #[must_use]
212 pub const fn going_down(&self) -> bool {
213 self.going_down
214 }
215
216 /// Count of rounded-floor transitions this elevator has made
217 /// (both passing-floor crossings and arrivals).
218 ///
219 /// Analogous to elevator-saga's `moveCount` scoring axis.
220 #[must_use]
221 pub const fn move_count(&self) -> u64 {
222 self.move_count
223 }
224}