Skip to main content

elevator_core/components/
line.rs

1//! Line (physical path) component — shaft, tether, track, etc.
2
3use serde::{Deserialize, Serialize};
4
5use crate::ids::GroupId;
6
7/// Physical orientation of a line.
8///
9/// This is metadata for external systems (rendering, spatial queries).
10/// The simulation always operates along a 1D axis regardless of orientation.
11#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
12#[non_exhaustive]
13pub enum Orientation {
14    /// Standard vertical elevator shaft.
15    #[default]
16    Vertical,
17    /// Angled incline (e.g., funicular).
18    Angled {
19        /// Angle from horizontal in degrees (0 = horizontal, 90 = vertical).
20        degrees: f64,
21    },
22    /// Horizontal people-mover or transit line.
23    Horizontal,
24}
25
26impl std::fmt::Display for Orientation {
27    /// Bounded precision keeps TUI/HUD output stable when `degrees` is the
28    /// result of a radians→degrees conversion that doesn't round-trip cleanly.
29    ///
30    /// ```
31    /// # use elevator_core::components::Orientation;
32    /// assert_eq!(format!("{}", Orientation::Vertical), "vertical");
33    /// assert_eq!(format!("{}", Orientation::Horizontal), "horizontal");
34    /// assert_eq!(format!("{}", Orientation::Angled { degrees: 30.0 }), "30.0°");
35    /// assert_eq!(
36    ///     format!("{}", Orientation::Angled { degrees: 22.123_456_789 }),
37    ///     "22.1°",
38    /// );
39    /// ```
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        match self {
42            Self::Vertical => f.write_str("vertical"),
43            Self::Horizontal => f.write_str("horizontal"),
44            Self::Angled { degrees } => write!(f, "{degrees:.1}°"),
45        }
46    }
47}
48
49/// 2D position on a floor plan (for spatial queries and rendering).
50#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
51pub struct SpatialPosition {
52    /// X coordinate on the floor plan.
53    pub x: f64,
54    /// Y coordinate on the floor plan.
55    pub y: f64,
56}
57
58/// Component for a line entity — the physical path an elevator car travels.
59///
60/// In a building this is a hoistway/shaft. For a space elevator it is a
61/// tether or cable. The term "line" is domain-neutral.
62///
63/// A line belongs to exactly one [`GroupId`] at a time but can be
64/// reassigned at runtime (swing-car pattern). Multiple cars may share
65/// a line (multi-car shafts); collision avoidance is left to game hooks.
66///
67/// Intrinsic properties only — relationship data (which elevators, which
68/// stops) lives in [`LineInfo`](crate::dispatch::LineInfo) on the
69/// [`ElevatorGroup`](crate::dispatch::ElevatorGroup).
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct Line {
72    /// Human-readable name.
73    pub(crate) name: String,
74    /// Dispatch group this line currently belongs to.
75    pub(crate) group: GroupId,
76    /// Physical orientation (metadata for rendering).
77    pub(crate) orientation: Orientation,
78    /// Optional floor-plan position (for spatial queries).
79    pub(crate) position: Option<SpatialPosition>,
80    /// Lowest reachable position along the line axis.
81    pub(crate) min_position: f64,
82    /// Highest reachable position along the line axis.
83    pub(crate) max_position: f64,
84    /// Maximum number of cars allowed on this line (None = unlimited).
85    pub(crate) max_cars: Option<usize>,
86}
87
88impl Line {
89    /// Human-readable name.
90    #[must_use]
91    pub fn name(&self) -> &str {
92        &self.name
93    }
94
95    /// Dispatch group this line currently belongs to.
96    #[must_use]
97    pub const fn group(&self) -> GroupId {
98        self.group
99    }
100
101    /// Physical orientation.
102    #[must_use]
103    pub const fn orientation(&self) -> Orientation {
104        self.orientation
105    }
106
107    /// Optional floor-plan position.
108    #[must_use]
109    pub const fn position(&self) -> Option<&SpatialPosition> {
110        self.position.as_ref()
111    }
112
113    /// Lowest reachable position along the line axis.
114    #[must_use]
115    pub const fn min_position(&self) -> f64 {
116        self.min_position
117    }
118
119    /// Highest reachable position along the line axis.
120    #[must_use]
121    pub const fn max_position(&self) -> f64 {
122        self.max_position
123    }
124
125    /// Maximum number of cars allowed on this line.
126    #[must_use]
127    pub const fn max_cars(&self) -> Option<usize> {
128        self.max_cars
129    }
130}