1use core::cmp::Ordering;
7use core::fmt;
8use core::hash::{Hash, Hasher};
9use core::marker::PhantomData;
10use core::num::NonZeroU64;
11use core::ops::{Add, AddAssign, Neg, Sub, SubAssign};
12use core::time::Duration;
13
14#[cfg(test)]
15mod tests;
16
17const NANOS_PER_SECOND: u128 = 1_000_000_000;
18
19pub(crate) const HUNDRED_NANOSECOND_TICKS: NonZeroU64 = NonZeroU64::new(10_000_000).unwrap();
21
22pub trait Timeline: 'static {
28 fn ticks_per_second() -> NonZeroU64;
30}
31
32pub struct TimePoint<T: Timeline> {
55 ticks: u64,
56 timeline: PhantomData<fn() -> T>,
57}
58
59pub struct Ticks<T: Timeline> {
62 count: i64,
63 timeline: PhantomData<fn() -> T>,
64}
65
66impl<T: Timeline> TimePoint<T> {
67 #[must_use]
69 pub const fn from_ticks(ticks: u64) -> Self {
70 Self {
71 ticks,
72 timeline: PhantomData,
73 }
74 }
75
76 #[must_use]
78 pub const fn ticks(self) -> u64 {
79 self.ticks
80 }
81
82 #[must_use]
85 pub fn checked_since(self, earlier: Self) -> Option<Ticks<T>> {
86 let distance = i128::from(self.ticks) - i128::from(earlier.ticks);
87 i64::try_from(distance).ok().map(Ticks::new)
88 }
89
90 #[must_use]
92 pub fn checked_add(self, ticks: Ticks<T>) -> Option<Self> {
93 let moved = i128::from(self.ticks) + i128::from(ticks.count);
94 u64::try_from(moved).ok().map(Self::from_ticks)
95 }
96
97 #[must_use]
99 pub fn checked_sub(self, ticks: Ticks<T>) -> Option<Self> {
100 let moved = i128::from(self.ticks) - i128::from(ticks.count);
101 u64::try_from(moved).ok().map(Self::from_ticks)
102 }
103}
104
105impl<T: Timeline> Ticks<T> {
106 pub const ZERO: Self = Self::new(0);
108
109 #[must_use]
111 pub const fn new(count: i64) -> Self {
112 Self {
113 count,
114 timeline: PhantomData,
115 }
116 }
117
118 #[must_use]
120 pub const fn count(self) -> i64 {
121 self.count
122 }
123
124 #[must_use]
126 pub const fn is_negative(self) -> bool {
127 self.count < 0
128 }
129
130 #[must_use]
133 pub fn to_duration(self) -> Option<Duration> {
134 (!self.is_negative()).then(|| self.abs_duration())
135 }
136
137 #[must_use]
140 pub fn abs_duration(self) -> Duration {
141 let per_second = T::ticks_per_second().get();
142 let magnitude = self.count.unsigned_abs();
143 let seconds = magnitude / per_second;
144 let remainder = u128::from(magnitude % per_second);
145 let nanos = remainder * NANOS_PER_SECOND / u128::from(per_second);
147 Duration::new(
148 seconds,
149 u32::try_from(nanos).expect("below one second of nanoseconds"),
150 )
151 }
152
153 #[must_use]
155 pub fn from_duration(duration: Duration) -> Option<Self> {
156 let per_second = u128::from(T::ticks_per_second().get());
157 let whole = u128::from(duration.as_secs()).checked_mul(per_second)?;
158 let part = u128::from(duration.subsec_nanos()) * per_second / NANOS_PER_SECOND;
159 i64::try_from(whole.checked_add(part)?).ok().map(Self::new)
160 }
161
162 #[must_use]
164 pub fn checked_add(self, other: Self) -> Option<Self> {
165 self.count.checked_add(other.count).map(Self::new)
166 }
167
168 #[must_use]
170 pub fn checked_sub(self, other: Self) -> Option<Self> {
171 self.count.checked_sub(other.count).map(Self::new)
172 }
173
174 #[must_use]
176 pub fn checked_neg(self) -> Option<Self> {
177 self.count.checked_neg().map(Self::new)
178 }
179}
180
181impl<T: Timeline> Clone for TimePoint<T> {
185 fn clone(&self) -> Self {
186 *self
187 }
188}
189
190impl<T: Timeline> Copy for TimePoint<T> {}
191
192impl<T: Timeline> PartialEq for TimePoint<T> {
193 fn eq(&self, other: &Self) -> bool {
194 self.ticks == other.ticks
195 }
196}
197
198impl<T: Timeline> Eq for TimePoint<T> {}
199
200impl<T: Timeline> PartialOrd for TimePoint<T> {
201 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
202 Some(self.cmp(other))
203 }
204}
205
206impl<T: Timeline> Ord for TimePoint<T> {
207 fn cmp(&self, other: &Self) -> Ordering {
208 self.ticks.cmp(&other.ticks)
209 }
210}
211
212impl<T: Timeline> Hash for TimePoint<T> {
213 fn hash<H: Hasher>(&self, state: &mut H) {
214 self.ticks.hash(state);
215 }
216}
217
218impl<T: Timeline> fmt::Debug for TimePoint<T> {
219 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220 f.debug_struct("TimePoint")
221 .field("timeline", &core::any::type_name::<T>())
222 .field("ticks", &self.ticks)
223 .finish()
224 }
225}
226
227impl<T: Timeline> Clone for Ticks<T> {
228 fn clone(&self) -> Self {
229 *self
230 }
231}
232
233impl<T: Timeline> Copy for Ticks<T> {}
234
235impl<T: Timeline> PartialEq for Ticks<T> {
236 fn eq(&self, other: &Self) -> bool {
237 self.count == other.count
238 }
239}
240
241impl<T: Timeline> Eq for Ticks<T> {}
242
243impl<T: Timeline> PartialOrd for Ticks<T> {
244 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
245 Some(self.cmp(other))
246 }
247}
248
249impl<T: Timeline> Ord for Ticks<T> {
250 fn cmp(&self, other: &Self) -> Ordering {
251 self.count.cmp(&other.count)
252 }
253}
254
255impl<T: Timeline> Hash for Ticks<T> {
256 fn hash<H: Hasher>(&self, state: &mut H) {
257 self.count.hash(state);
258 }
259}
260
261impl<T: Timeline> Default for Ticks<T> {
262 fn default() -> Self {
263 Self::ZERO
264 }
265}
266
267impl<T: Timeline> fmt::Debug for Ticks<T> {
268 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
269 f.debug_struct("Ticks")
270 .field("timeline", &core::any::type_name::<T>())
271 .field("count", &self.count)
272 .finish()
273 }
274}
275
276impl<T: Timeline> Sub for TimePoint<T> {
280 type Output = Ticks<T>;
281
282 fn sub(self, earlier: Self) -> Ticks<T> {
283 self.checked_since(earlier)
284 .expect("the distance between two time points overflowed")
285 }
286}
287
288impl<T: Timeline> Add<Ticks<T>> for TimePoint<T> {
289 type Output = Self;
290
291 fn add(self, ticks: Ticks<T>) -> Self {
292 self.checked_add(ticks)
293 .expect("a time point moved outside its timeline")
294 }
295}
296
297impl<T: Timeline> Sub<Ticks<T>> for TimePoint<T> {
298 type Output = Self;
299
300 fn sub(self, ticks: Ticks<T>) -> Self {
301 self.checked_sub(ticks)
302 .expect("a time point moved outside its timeline")
303 }
304}
305
306impl<T: Timeline> AddAssign<Ticks<T>> for TimePoint<T> {
307 fn add_assign(&mut self, ticks: Ticks<T>) {
308 *self = *self + ticks;
309 }
310}
311
312impl<T: Timeline> SubAssign<Ticks<T>> for TimePoint<T> {
313 fn sub_assign(&mut self, ticks: Ticks<T>) {
314 *self = *self - ticks;
315 }
316}
317
318impl<T: Timeline> Add for Ticks<T> {
319 type Output = Self;
320
321 fn add(self, other: Self) -> Self {
322 self.checked_add(other).expect("a tick count overflowed")
323 }
324}
325
326impl<T: Timeline> Sub for Ticks<T> {
327 type Output = Self;
328
329 fn sub(self, other: Self) -> Self {
330 self.checked_sub(other).expect("a tick count overflowed")
331 }
332}
333
334impl<T: Timeline> Neg for Ticks<T> {
335 type Output = Self;
336
337 fn neg(self) -> Self {
338 self.checked_neg().expect("a tick count overflowed")
339 }
340}
341
342impl<T: Timeline> AddAssign for Ticks<T> {
343 fn add_assign(&mut self, other: Self) {
344 *self = *self + other;
345 }
346}
347
348impl<T: Timeline> SubAssign for Ticks<T> {
349 fn sub_assign(&mut self, other: Self) {
350 *self = *self - other;
351 }
352}