Skip to main content

elevator_core/components/
route.rs

1//! Multi-leg route planning for riders.
2
3use serde::{Deserialize, Serialize};
4
5use crate::entity::EntityId;
6use crate::ids::GroupId;
7
8/// How to travel between two stops.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10#[non_exhaustive]
11pub enum TransportMode {
12    /// Use any elevator in the given dispatch group.
13    #[serde(alias = "Elevator")]
14    Group(GroupId),
15    /// Use a specific line (pinned routing).
16    Line(EntityId),
17    /// Walk between adjacent stops.
18    Walk,
19}
20
21impl std::fmt::Display for TransportMode {
22    /// `Line` delegates to `EntityId`'s `Debug` since slotmap keys do not
23    /// implement `Display`; the doctest pins the prefix so the format is
24    /// observable even though the trailing key id is opaque.
25    ///
26    /// ```
27    /// # use elevator_core::components::TransportMode;
28    /// # use elevator_core::ids::GroupId;
29    /// # use elevator_core::entity::EntityId;
30    /// assert_eq!(format!("{}", TransportMode::Group(GroupId(0))), "group GroupId(0)");
31    /// assert_eq!(format!("{}", TransportMode::Walk), "walk");
32    /// let line_str = format!("{}", TransportMode::Line(EntityId::default()));
33    /// assert!(line_str.starts_with("line "), "unexpected: {line_str}");
34    /// ```
35    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36        match self {
37            Self::Group(g) => write!(f, "group {g}"),
38            Self::Line(eid) => write!(f, "line {eid:?}"),
39            Self::Walk => f.write_str("walk"),
40        }
41    }
42}
43
44/// One segment of a multi-leg route.
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct RouteLeg {
47    /// Origin stop entity.
48    pub from: EntityId,
49    /// Destination stop entity.
50    pub to: EntityId,
51    /// Transport mode for this leg.
52    pub via: TransportMode,
53}
54
55/// A rider's full route, possibly spanning multiple elevator groups.
56#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct Route {
58    /// Ordered legs of the route.
59    pub legs: Vec<RouteLeg>,
60    /// Index into `legs` for the leg currently being traversed.
61    pub current_leg: usize,
62}
63
64impl Route {
65    /// Create a direct single-leg route via one elevator group.
66    #[must_use]
67    pub fn direct(from: EntityId, to: EntityId, group: GroupId) -> Self {
68        Self {
69            legs: vec![RouteLeg {
70                from,
71                to,
72                via: TransportMode::Group(group),
73            }],
74            current_leg: 0,
75        }
76    }
77
78    /// Get the current leg, if any remain.
79    #[must_use]
80    pub fn current(&self) -> Option<&RouteLeg> {
81        self.legs.get(self.current_leg)
82    }
83
84    /// Advance to the next leg. Returns true if there are more legs.
85    pub const fn advance(&mut self) -> bool {
86        self.current_leg += 1;
87        self.current_leg < self.legs.len()
88    }
89
90    /// Whether all legs have been completed.
91    #[must_use]
92    pub const fn is_complete(&self) -> bool {
93        self.current_leg >= self.legs.len()
94    }
95
96    /// Whether the current leg is the final leg of the route.
97    ///
98    /// Useful when an event fires *before* [`advance`](Self::advance) runs
99    /// — e.g. `RiderExited` (loading phase) precedes the route advance
100    /// (transient phase next tick), so consumers that need to know if the
101    /// exit terminates the journey check `is_last_leg`, not `is_complete`.
102    #[must_use]
103    pub const fn is_last_leg(&self) -> bool {
104        self.current_leg + 1 == self.legs.len()
105    }
106
107    /// The destination of the current leg.
108    #[must_use]
109    pub fn current_destination(&self) -> Option<EntityId> {
110        self.current().map(|leg| leg.to)
111    }
112
113    /// The final destination of the entire route.
114    #[must_use]
115    pub fn final_destination(&self) -> Option<EntityId> {
116        self.legs.last().map(|leg| leg.to)
117    }
118}