aura_anim_core/timing/
duration.rs1use std::{
2 ops::{Add, AddAssign},
3 time::Duration as StdDuration,
4};
5
6use crate::timing::utils::std_duration_from_secs;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
10pub struct Duration(StdDuration);
11
12impl Duration {
13 pub const ZERO: Self = Self(StdDuration::ZERO);
15
16 #[must_use]
18 pub fn is_zero(&self) -> bool {
19 self.0.is_zero()
20 }
21
22 #[must_use]
24 pub fn from_millis(millis: f64) -> Self {
25 Self(std_duration_from_secs(millis / 1000.0))
26 }
27
28 #[must_use]
30 pub fn from_secs(seconds: f64) -> Self {
31 Self(std_duration_from_secs(seconds))
32 }
33
34 #[must_use]
36 pub const fn as_millis(self) -> f64 {
37 self.0.as_secs_f64() * 1000.0
38 }
39
40 #[must_use]
42 pub const fn as_secs(self) -> f64 {
43 self.0.as_secs_f64()
44 }
45
46 pub(crate) fn checked_mul(self, rhs: u32) -> Option<Self> {
47 self.0.checked_mul(rhs).map(Self)
48 }
49
50 pub(crate) fn checked_add_delay(self, rhs: Delay) -> Option<Self> {
51 self.0.checked_add(rhs.0).map(Self)
52 }
53
54 pub(crate) fn saturating_sub(self, rhs: Self) -> Self {
55 Self(self.0.saturating_sub(rhs.0))
56 }
57
58 pub(crate) fn min(self, rhs: Self) -> Self {
59 Self(self.0.min(rhs.0))
60 }
61
62 pub(crate) fn max(self, rhs: Self) -> Self {
63 Self(self.0.max(rhs.0))
64 }
65}
66
67impl AddAssign for Duration {
68 fn add_assign(&mut self, rhs: Self) {
69 self.0 += rhs.0;
70 }
71}
72
73impl Add for Duration {
74 type Output = Self;
75
76 fn add(self, rhs: Self) -> Self::Output {
77 Self(self.0 + rhs.0)
78 }
79}
80
81impl From<StdDuration> for Duration {
82 fn from(value: StdDuration) -> Self {
83 Self(value)
84 }
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Default)]
89pub struct Delay(StdDuration);
90
91impl Delay {
92 pub const ZERO: Self = Self(StdDuration::ZERO);
94
95 #[must_use]
97 pub fn from_millis(millis: f64) -> Self {
98 Self(std_duration_from_secs(millis / 1000.0))
99 }
100
101 #[must_use]
103 pub fn from_secs(seconds: f64) -> Self {
104 Self(std_duration_from_secs(seconds))
105 }
106
107 #[must_use]
109 pub const fn as_millis(self) -> f64 {
110 self.0.as_secs_f64() * 1000.0
111 }
112}
113
114impl From<StdDuration> for Delay {
115 fn from(value: StdDuration) -> Self {
116 Self(value)
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::{Delay, Duration};
123 use float_cmp::assert_approx_eq;
124
125 #[test]
126 fn duration_constructors_sanitize_negative_and_nan_values() {
127 assert!(Duration::from_millis(-10.0).is_zero());
128 assert!(Duration::from_secs(f64::NAN).is_zero());
129 assert!(Delay::from_millis(-10.0).as_millis().abs() <= f64::EPSILON);
130 }
131
132 #[test]
133 fn duration_reports_seconds_and_milliseconds() {
134 let duration = Duration::from_millis(1_250.0);
135
136 assert_approx_eq!(f64, duration.as_secs(), 1.25);
137 assert_approx_eq!(f64, duration.as_millis(), 1_250.0);
138 }
139
140 #[test]
141 fn duration_arithmetic_is_saturating_where_expected() {
142 let short = Duration::from_millis(25.0);
143 let long = Duration::from_millis(100.0);
144
145 assert_eq!(short.saturating_sub(long), Duration::ZERO);
146 assert_eq!(short.min(long), short);
147 assert_eq!(short.max(long), long);
148 assert_approx_eq!(f64, short.checked_mul(2).unwrap().as_millis(), 50.0);
149 assert_approx_eq!(
150 f64,
151 short
152 .checked_add_delay(Delay::from_millis(25.0))
153 .unwrap()
154 .as_millis(),
155 50.0
156 );
157 }
158}