wowlab_types/types/sim/
time.rs1use serde::{Deserialize, Serialize};
2
3#[derive(
5 Clone, Copy, Debug, Default, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize,
6)]
7#[serde(transparent)]
8#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
9#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
10pub struct SimTime(pub u32);
13impl SimTime {
16 pub const ZERO: SimTime = SimTime(0);
17 pub const MAX: SimTime = SimTime(u32::MAX);
18
19 const MS_PER_SEC: u32 = 1000;
20 const MS_PER_SEC_F32: f32 = 1000.0;
21
22 #[inline]
23 #[must_use]
24 pub const fn from_millis(ms: u32) -> Self {
25 SimTime(ms)
26 }
27
28 #[inline]
29 #[must_use]
30 pub const fn from_secs(secs: u32) -> Self {
31 SimTime(secs * Self::MS_PER_SEC)
32 }
33
34 #[inline]
35 #[must_use]
36 #[expect(
37 clippy::cast_possible_truncation,
38 clippy::cast_sign_loss,
39 reason = "Rust float-to-int saturation is the established behavior for out-of-range times"
40 )]
41 pub fn from_secs_f32(secs: f32) -> Self {
42 SimTime((secs * Self::MS_PER_SEC_F32).round() as u32)
46 }
47
48 #[inline]
49 #[must_use]
50 #[expect(
51 clippy::cast_possible_truncation,
52 clippy::cast_sign_loss,
53 reason = "Rust float-to-int saturation is the established behavior for out-of-range times"
54 )]
55 pub fn from_secs_f64(secs: f64) -> Self {
56 SimTime((secs * crate::constants::MS_PER_SECOND).round() as u32)
57 }
58
59 #[inline]
60 #[must_use]
61 pub const fn as_millis(&self) -> u32 {
62 self.0
63 }
64
65 #[inline]
66 #[must_use]
67 #[expect(
68 clippy::cast_precision_loss,
69 reason = "f32 seconds are an explicitly lower-precision display and scheduling API"
70 )]
71 pub fn as_secs_f32(&self) -> f32 {
72 self.0 as f32 / Self::MS_PER_SEC_F32
74 }
75
76 #[inline]
77 #[must_use]
78 pub fn as_secs_f64(&self) -> f64 {
79 f64::from(self.0) / crate::constants::MS_PER_SECOND
80 }
81
82 #[inline]
83 #[must_use]
84 pub const fn saturating_sub(self, other: SimTime) -> SimTime {
85 SimTime(self.0.saturating_sub(other.0))
86 }
87
88 #[inline]
89 #[must_use]
90 pub const fn saturating_add(self, other: SimTime) -> SimTime {
91 SimTime(self.0.saturating_add(other.0))
92 }
93}
94
95impl std::ops::Add for SimTime {
96 type Output = SimTime;
97 #[inline]
98 fn add(self, rhs: SimTime) -> SimTime {
99 SimTime(self.0 + rhs.0)
100 }
101}
102
103impl std::ops::Sub for SimTime {
104 type Output = SimTime;
105 #[inline]
106 fn sub(self, rhs: SimTime) -> SimTime {
107 SimTime(self.0 - rhs.0)
108 }
109}
110
111impl std::ops::AddAssign for SimTime {
112 #[inline]
113 fn add_assign(&mut self, rhs: SimTime) {
114 self.0 += rhs.0;
115 }
116}
117
118impl std::ops::SubAssign for SimTime {
119 #[inline]
120 fn sub_assign(&mut self, rhs: SimTime) {
121 self.0 -= rhs.0;
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use googletest::prelude::*;
128 use rstest::rstest;
129
130 use super::*;
131
132 #[gtest]
133 #[rstest]
134 #[case::zero(0, SimTime(0))]
135 #[case::five(5, SimTime(5000))]
136 fn from_secs(#[case] input: u32, #[case] expected: SimTime) -> Result<()> {
137 verify_that!(SimTime::from_secs(input), eq(expected))
138 }
139
140 #[gtest]
141 #[rstest]
142 #[case::whole(1.5, SimTime(1500))]
143 #[case::sub_milli(0.001, SimTime(1))]
144 #[case::zero(0.0, SimTime(0))]
145 fn from_secs_f64(#[case] input: f64, #[case] expected: SimTime) -> Result<()> {
146 verify_that!(SimTime::from_secs_f64(input), eq(expected))
147 }
148
149 #[gtest]
150 #[rstest]
151 #[case::whole(1.5, SimTime(1500))]
152 #[case::sub_milli(0.001, SimTime(1))]
153 #[case::zero(0.0, SimTime(0))]
154 fn from_secs_f32(#[case] input: f32, #[case] expected: SimTime) -> Result<()> {
155 verify_that!(SimTime::from_secs_f32(input), eq(expected))
156 }
157
158 #[gtest]
159 #[rstest]
160 #[case::whole(SimTime(1500), 1.5)]
161 #[case::zero(SimTime(0), 0.0)]
162 fn as_secs_f64(#[case] input: SimTime, #[case] expected: f64) -> Result<()> {
163 verify_that!(input.as_secs_f64(), near(expected, 1e-9))
164 }
165
166 #[gtest]
167 #[rstest]
168 #[case::whole(SimTime(1500), 1.5)]
169 #[case::zero(SimTime(0), 0.0)]
170 fn as_secs_f32(#[case] input: SimTime, #[case] expected: f32) -> Result<()> {
171 verify_that!(input.as_secs_f32(), near(expected, 1e-4))
172 }
173
174 #[gtest]
175 #[rstest]
176 #[case::saturates(SimTime(u32::MAX), SimTime(1), SimTime(u32::MAX))]
177 #[case::normal(SimTime(100), SimTime(50), SimTime(150))]
178 fn saturating_add(
179 #[case] a: SimTime,
180 #[case] b: SimTime,
181 #[case] expected: SimTime,
182 ) -> Result<()> {
183 verify_that!(a.saturating_add(b), eq(expected))
184 }
185
186 #[gtest]
187 #[rstest]
188 #[case::underflow_floor(SimTime(3), SimTime(10), SimTime(0))]
189 #[case::normal(SimTime(10), SimTime(4), SimTime(6))]
190 fn saturating_sub(
191 #[case] a: SimTime,
192 #[case] b: SimTime,
193 #[case] expected: SimTime,
194 ) -> Result<()> {
195 verify_that!(a.saturating_sub(b), eq(expected))
196 }
197
198 #[gtest]
199 fn operator_overloads() -> Result<()> {
200 verify_that!(SimTime(10) + SimTime(5), eq(SimTime(15)))?;
201 verify_that!(SimTime(10) - SimTime(4), eq(SimTime(6)))?;
202 let mut t = SimTime(10);
203
204 t += SimTime(5);
205 verify_that!(t, eq(SimTime(15)))?;
206 t -= SimTime(3);
207
208 verify_that!(t, eq(SimTime(12)))
209 }
210}