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}