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::request_door_open`](crate::sim::Simulation::request_door_open),
38/// [`Simulation::request_door_close`](crate::sim::Simulation::request_door_close),
39/// [`Simulation::hold_door_open`](crate::sim::Simulation::hold_door_open),
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 pub const fn tick(&mut self) -> DoorTransition {
113 match self {
114 Self::Closed => DoorTransition::None,
115 Self::Opening {
116 ticks_remaining,
117 open_duration,
118 close_duration,
119 } => {
120 if *ticks_remaining <= 1 {
121 let od = *open_duration;
122 let cd = *close_duration;
123 *self = Self::Open {
124 ticks_remaining: od,
125 close_duration: cd,
126 };
127 DoorTransition::FinishedOpening
128 } else {
129 *ticks_remaining -= 1;
130 DoorTransition::None
131 }
132 }
133 Self::Open {
134 ticks_remaining,
135 close_duration,
136 } => {
137 if *ticks_remaining <= 1 {
138 let cd = *close_duration;
139 *self = Self::Closing {
140 ticks_remaining: cd,
141 };
142 DoorTransition::FinishedOpen
143 } else {
144 *ticks_remaining -= 1;
145 DoorTransition::None
146 }
147 }
148 Self::Closing { ticks_remaining } => {
149 if *ticks_remaining <= 1 {
150 *self = Self::Closed;
151 DoorTransition::FinishedClosing
152 } else {
153 *ticks_remaining -= 1;
154 DoorTransition::None
155 }
156 }
157 }
158 }
159}