elevator_core/door.rs
1//! Door open/close finite-state machine.
2
3use serde::{Deserialize, Serialize};
4
5/// State machine for elevator doors.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7#[non_exhaustive]
8pub enum DoorState {
9 /// Doors are fully closed.
10 Closed,
11 /// Doors are in the process of opening.
12 Opening {
13 /// Ticks left in the opening transition.
14 ticks_remaining: u32,
15 /// How many ticks the door stays open once fully opened.
16 open_duration: u32,
17 /// How many ticks the closing transition takes.
18 close_duration: u32,
19 },
20 /// Doors are fully open and holding.
21 Open {
22 /// Ticks left before the doors begin closing.
23 ticks_remaining: u32,
24 /// How many ticks the closing transition takes.
25 close_duration: u32,
26 },
27 /// Doors are in the process of closing.
28 Closing {
29 /// Ticks left in the closing transition.
30 ticks_remaining: u32,
31 },
32}
33
34/// A manual door-control command submitted by game code.
35///
36/// Submitted via
37/// [`Simulation::open_door`](crate::sim::Simulation::open_door),
38/// [`Simulation::close_door`](crate::sim::Simulation::close_door),
39/// [`Simulation::hold_door`](crate::sim::Simulation::hold_door),
40/// and [`Simulation::cancel_door_hold`](crate::sim::Simulation::cancel_door_hold).
41/// Commands are queued on the target elevator and processed at the start of
42/// the door phase; those that are not yet valid stay queued until they are.
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
44#[non_exhaustive]
45pub enum DoorCommand {
46 /// Open the doors now (or on arrival at the next stop).
47 Open,
48 /// Close the doors now (or as soon as loading is done).
49 Close,
50 /// Extend the open dwell by `ticks`. Cumulative across calls.
51 HoldOpen {
52 /// Additional ticks to hold the doors open.
53 ticks: u32,
54 },
55 /// Cancel any pending hold extension.
56 CancelHold,
57}
58
59/// Transition emitted when the door state changes phase.
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
61#[non_exhaustive]
62pub enum DoorTransition {
63 /// No phase change occurred this tick.
64 None,
65 /// Doors just finished opening and are now fully open.
66 FinishedOpening,
67 /// Doors just finished holding open and are about to close.
68 FinishedOpen,
69 /// Doors just finished closing and are now fully closed.
70 FinishedClosing,
71}
72
73impl std::fmt::Display for DoorState {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 match self {
76 Self::Closed => write!(f, "Closed"),
77 Self::Opening {
78 ticks_remaining, ..
79 } => write!(f, "Opening({ticks_remaining})"),
80 Self::Open {
81 ticks_remaining, ..
82 } => write!(f, "Open({ticks_remaining})"),
83 Self::Closing { ticks_remaining } => write!(f, "Closing({ticks_remaining})"),
84 }
85 }
86}
87
88impl DoorState {
89 /// Returns `true` if the doors are fully open.
90 #[must_use]
91 pub const fn is_open(&self) -> bool {
92 matches!(self, Self::Open { .. })
93 }
94
95 /// Returns `true` if the doors are fully closed.
96 #[must_use]
97 pub const fn is_closed(&self) -> bool {
98 matches!(self, Self::Closed)
99 }
100
101 /// Begin opening the door.
102 #[must_use]
103 pub const fn request_open(transition_ticks: u32, open_ticks: u32) -> Self {
104 Self::Opening {
105 ticks_remaining: transition_ticks,
106 open_duration: open_ticks,
107 close_duration: transition_ticks,
108 }
109 }
110
111 /// Advance the door state by one tick. Returns the transition that occurred.
112 ///
113 /// This drives the FSM's timer only — it does **not** consult rider
114 /// safety (e.g. inflight boarders blocking the threshold) before
115 /// transitioning `Open → Closing`. The doors phase
116 /// (`systems::doors`) layers the safety check on top of this FSM:
117 /// an `Open` cycle whose rider-safety predicate is still active
118 /// has its `ticks_remaining` reset, so `tick` here observes the
119 /// reset and stays in `Open` for another cycle.
120 pub const fn tick(&mut self) -> DoorTransition {
121 match self {
122 Self::Closed => DoorTransition::None,
123 Self::Opening {
124 ticks_remaining,
125 open_duration,
126 close_duration,
127 } => {
128 if *ticks_remaining <= 1 {
129 let od = *open_duration;
130 let cd = *close_duration;
131 *self = Self::Open {
132 ticks_remaining: od,
133 close_duration: cd,
134 };
135 DoorTransition::FinishedOpening
136 } else {
137 *ticks_remaining -= 1;
138 DoorTransition::None
139 }
140 }
141 Self::Open {
142 ticks_remaining,
143 close_duration,
144 } => {
145 if *ticks_remaining <= 1 {
146 let cd = *close_duration;
147 *self = Self::Closing {
148 ticks_remaining: cd,
149 };
150 DoorTransition::FinishedOpen
151 } else {
152 *ticks_remaining -= 1;
153 DoorTransition::None
154 }
155 }
156 Self::Closing { ticks_remaining } => {
157 if *ticks_remaining <= 1 {
158 *self = Self::Closed;
159 DoorTransition::FinishedClosing
160 } else {
161 *ticks_remaining -= 1;
162 DoorTransition::None
163 }
164 }
165 }
166 }
167}