Skip to main content

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}