Skip to main content

fast_clock/
wrapping_u64.rs

1#[cfg(feature = "std")]
2use crate::std_clocks::InstantTime;
3use crate::{CalibratedClock, Clock, ClockSynchronization, DurationCalibration, Time};
4use core::cmp::{self};
5
6/// [`Time`] implementation for clocks that produce raw `u64` tick values.
7///
8/// All arithmetic uses wrapping semantics so measurements across a counter rollover
9/// remain accurate, provided the elapsed ticks fit in a `u64` half-range.
10#[derive(Copy, Clone, Debug)]
11pub struct WrappingU64Time;
12
13/// An instant in a [`WrappingU64Time`] domain. The inner value is the raw tick count.
14#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
15pub struct WrappingU64Instant(pub u64);
16
17/// A duration in a [`WrappingU64Time`] domain. The inner value is an unsigned tick count.
18#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
19pub struct WrappingU64Duration(pub u64);
20
21impl Time for WrappingU64Time {
22    type Instant = WrappingU64Instant;
23
24    type Duration = WrappingU64Duration;
25
26    #[inline]
27    fn instant_sub(a: Self::Instant, b: Self::Instant) -> Self::Duration {
28        debug_assert!(Self::instant_cmp(a, b).is_ge());
29        WrappingU64Duration(a.0.wrapping_sub(b.0))
30    }
31
32    #[inline]
33    fn duration_sub(a: Self::Duration, b: Self::Duration) -> Self::Duration {
34        WrappingU64Duration(a.0 - b.0)
35    }
36
37    #[inline]
38    fn duration_add(a: Self::Duration, b: Self::Duration) -> Self::Duration {
39        WrappingU64Duration(a.0 + b.0)
40    }
41
42    #[inline]
43    fn mixed_sub(a: Self::Instant, b: Self::Duration) -> Self::Instant {
44        WrappingU64Instant(a.0.wrapping_sub(b.0))
45    }
46
47    #[inline]
48    fn mixed_add(a: Self::Instant, b: Self::Duration) -> Self::Instant {
49        WrappingU64Instant(a.0.wrapping_add(b.0))
50    }
51
52    #[inline]
53    fn instant_cmp(a: Self::Instant, b: Self::Instant) -> cmp::Ordering {
54        (a.0 as i64).wrapping_sub(b.0 as i64).cmp(&0)
55    }
56}
57
58/// Integer multiply-shift calibration for [`WrappingU64Duration`].
59///
60/// Converts between raw ticks and nanoseconds using precomputed multiply-shift factors.
61#[derive(Clone, Copy, Debug)]
62pub struct U64Calibration {
63    to_ns: u64,
64    to_ns_shift: u32,
65    from_ns: u64,
66    from_ns_shift: u32,
67}
68
69impl U64Calibration {
70    /// Creates a calibration from a measured duration and its nanosecond equivalent.
71    pub fn new(duration: WrappingU64Duration, duration_ns: u64) -> Self {
72        assert!(duration.0 > 0);
73        assert!(duration_ns > 0);
74        let (to_ns, to_ns_shift) = make_mul_shift(duration.0, duration_ns);
75        let (from_ns, from_ns_shift) = make_mul_shift(duration_ns, duration.0);
76        U64Calibration {
77            to_ns,
78            to_ns_shift,
79            from_ns,
80            from_ns_shift,
81        }
82    }
83
84    /// Calibrates `clock` against `reference_clock`, calling `wait` until `min_duration` elapses.
85    ///
86    /// Returns the calibration and a [`ClockSynchronization`] relating the two clocks to each other
87    /// `wait` is invoked with the instant until which it should wait.
88    pub fn new_with_reference_clock<C: Clock<Time = WrappingU64Time>, R: Clock>(
89        clock: &C,
90        reference_clock: &CalibratedClock<R>,
91        min_duration: <R::Time as Time>::Duration,
92        mut wait: impl FnMut(<R::Time as Time>::Instant),
93    ) -> (Self, ClockSynchronization<R::Time, C::Time>) {
94        let s1 = ClockSynchronization::new_aba_calibrated(reference_clock, clock);
95        let wait_until = R::Time::mixed_add(s1.epoch_a(), min_duration);
96        while R::Time::instant_cmp(reference_clock.clock.now(), wait_until).is_lt() {
97            wait(wait_until);
98        }
99        let s2 = ClockSynchronization::new_aba_calibrated(reference_clock, clock);
100        (
101            Self::new(
102                C::Time::instant_sub(s2.epoch_b(), s1.epoch_b()),
103                reference_clock
104                    .calibration
105                    .convert_to_ns(R::Time::instant_sub(s2.epoch_a(), s1.epoch_a())),
106            ),
107            s2,
108        )
109    }
110
111    /// Calibrates `clock` against `std::time::Instant`, sleeping for at least `min_duration`.
112    ///
113    /// This is a convenience wrapper around [`U64Calibration::new_with_reference_clock`] based on [`std::time::Instant`] and [`std::thread::sleep`].
114    #[cfg(feature = "std")]
115    pub fn new_with_std_instant<C: Clock<Time = WrappingU64Time>>(
116        clock: &C,
117        min_duration: std::time::Duration,
118    ) -> (Self, ClockSynchronization<InstantTime, C::Time>) {
119        use crate::{InherentlyCalibrated, std_clocks::InstantClock};
120
121        Self::new_with_reference_clock(
122            clock,
123            &CalibratedClock {
124                clock: InstantClock,
125                calibration: InherentlyCalibrated,
126            },
127            min_duration,
128            |until| {
129                let now = std::time::Instant::now();
130                if let Some(remaining) = until.checked_duration_since(now) {
131                    std::thread::sleep(remaining);
132                }
133            },
134        )
135    }
136}
137
138impl DurationCalibration<WrappingU64Duration> for U64Calibration {
139    #[inline]
140    fn convert_to_ns(&self, d: WrappingU64Duration) -> u64 {
141        apply_mul_shift(d.0, self.to_ns, self.to_ns_shift)
142    }
143
144    #[inline]
145    fn convert_from_ns(&self, ns: u64) -> WrappingU64Duration {
146        WrappingU64Duration(apply_mul_shift(ns, self.from_ns, self.from_ns_shift))
147    }
148}
149
150fn make_mul_shift(from: u64, to: u64) -> (u64, u32) {
151    debug_assert!(from > 0 && from < (1 << 63));
152    debug_assert!(to > 0 && to < (1 << 63));
153
154    let l_to = 64 - to.leading_zeros();
155    let l_from = 64 - from.leading_zeros();
156    let s0 = l_from + 64 - l_to;
157
158    let shift = if (to as u128) << s0 < (from as u128) << 64 {
159        s0
160    } else {
161        s0 - 1
162    };
163
164    let mul = (((to as u128) << shift) / from as u128) as u64;
165    (mul, shift)
166}
167
168#[inline]
169fn apply_mul_shift(x: u64, mul: u64, shift: u32) -> u64 {
170    ((mul as u128 * x as u128 + (1u128 << (shift - 1))) >> shift) as u64
171}
172
173#[test]
174fn test_make_mul_shift() {
175    use std::vec::Vec;
176    let mut values: Vec<u64> = (1..61)
177        .flat_map(|s| (1..4).map(move |i| i << s))
178        .flat_map(|x| [x - 1, x, x + 1])
179        .filter(|&x| x > 0 && x < (1 << 63))
180        .collect();
181    values.sort_unstable();
182    values.dedup();
183    for &from in &values {
184        for &to in &values {
185            let (mul, shift) = make_mul_shift(from, to);
186            assert!(mul >= (1 << 63));
187            assert_eq!(apply_mul_shift(from, mul, shift), to);
188        }
189    }
190}