Skip to main content

elevator_core/components/
units.rs

1//! Physical quantity newtypes for compile-time unit safety.
2
3use serde::{Deserialize, Serialize};
4use std::fmt;
5
6/// Weight / mass (always non-negative).
7///
8/// Used for rider weight, elevator load, and weight capacity.
9///
10/// ```
11/// # use elevator_core::components::Weight;
12/// let w = Weight::from(75.0);
13/// assert_eq!(w.value(), 75.0);
14/// assert_eq!(format!("{w}"), "75.00kg");
15/// ```
16#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
17#[serde(transparent)]
18pub struct Weight {
19    /// The inner f64 value.
20    pub(crate) value: f64,
21}
22
23impl Weight {
24    /// Zero weight.
25    pub const ZERO: Self = Self { value: 0.0 };
26
27    /// The inner value.
28    #[must_use]
29    pub const fn value(self) -> f64 {
30        self.value
31    }
32}
33
34impl fmt::Display for Weight {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        write!(f, "{:.2}kg", self.value)
37    }
38}
39
40impl From<f64> for Weight {
41    fn from(value: f64) -> Self {
42        debug_assert!(
43            value.is_finite() && value >= 0.0,
44            "Weight must be finite and non-negative, got {value}"
45        );
46        Self { value }
47    }
48}
49
50impl std::ops::Add for Weight {
51    type Output = Self;
52    fn add(self, rhs: Self) -> Self {
53        Self {
54            value: self.value + rhs.value,
55        }
56    }
57}
58
59impl std::ops::AddAssign for Weight {
60    fn add_assign(&mut self, rhs: Self) {
61        self.value += rhs.value;
62    }
63}
64
65impl std::ops::Sub for Weight {
66    type Output = Self;
67    fn sub(self, rhs: Self) -> Self {
68        Self {
69            value: (self.value - rhs.value).max(0.0),
70        }
71    }
72}
73
74impl std::ops::SubAssign for Weight {
75    fn sub_assign(&mut self, rhs: Self) {
76        self.value = (self.value - rhs.value).max(0.0);
77    }
78}
79
80/// Maximum travel speed (always non-negative, distance units per second).
81///
82/// ```
83/// # use elevator_core::components::Speed;
84/// let s = Speed::from(2.0);
85/// assert_eq!(format!("{s}"), "2.00m/s");
86/// ```
87#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
88#[serde(transparent)]
89pub struct Speed {
90    /// The inner f64 value.
91    pub(crate) value: f64,
92}
93
94impl Speed {
95    /// The inner value.
96    #[must_use]
97    pub const fn value(self) -> f64 {
98        self.value
99    }
100}
101
102impl fmt::Display for Speed {
103    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104        write!(f, "{:.2}m/s", self.value)
105    }
106}
107
108impl From<f64> for Speed {
109    fn from(value: f64) -> Self {
110        debug_assert!(
111            value.is_finite() && value >= 0.0,
112            "Speed must be finite and non-negative, got {value}"
113        );
114        Self { value }
115    }
116}
117
118/// Acceleration / deceleration rate (always non-negative, distance units per second²).
119///
120/// ```
121/// # use elevator_core::components::Accel;
122/// let a = Accel::from(1.5);
123/// assert_eq!(format!("{a}"), "1.50m/s²");
124/// ```
125#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
126#[serde(transparent)]
127pub struct Accel {
128    /// The inner f64 value.
129    pub(crate) value: f64,
130}
131
132impl Accel {
133    /// The inner value.
134    #[must_use]
135    pub const fn value(self) -> f64 {
136        self.value
137    }
138}
139
140impl fmt::Display for Accel {
141    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142        write!(f, "{:.2}m/s²", self.value)
143    }
144}
145
146impl From<f64> for Accel {
147    fn from(value: f64) -> Self {
148        debug_assert!(
149            value.is_finite() && value >= 0.0,
150            "Accel must be finite and non-negative, got {value}"
151        );
152        Self { value }
153    }
154}