Skip to main content

fast_clock/
clock_synchronization.rs

1use crate::{CalibratedClock, Clock, DurationCalibration, Time};
2
3/// A pair of instants from two clocks that correspond to roughly the same point in time.
4pub struct ClockSynchronization<A: Time, B: Time> {
5    epoch_a: A::Instant,
6    epoch_b: B::Instant,
7}
8
9impl<A: Time, B: Time> ClockSynchronization<A, B> {
10    /// Creates a synchronization from a known pair of corresponding instants.
11    pub fn new(epoch_a: A::Instant, epoch_b: B::Instant) -> Self {
12        ClockSynchronization { epoch_a, epoch_b }
13    }
14
15    /// Creates a synchronization by sampling both clocks in an A-B-A pattern.
16    ///
17    /// Multiple measurements are performed and the one with least error is selected.
18    pub fn new_aba_calibrated<CA, CB>(a: &CalibratedClock<CA>, b: &CB) -> Self
19    where
20        CA: Clock<Time = A>,
21        CB: Clock<Time = B>,
22    {
23        let (a0, bt, da) = (0..3)
24            .map(|_| {
25                loop {
26                    let a0 = a.clock.now();
27                    let bt = b.now();
28                    let a1 = a.clock.now();
29                    if A::instant_cmp(a0, a1).is_le() {
30                        let d = A::instant_sub(a1, a0);
31                        let da = a.calibration.convert_to_ns(d);
32                        break (a0, bt, da);
33                    }
34                }
35            })
36            .min_by_key(|(.., da)| *da)
37            .unwrap();
38        ClockSynchronization {
39            epoch_b: bt,
40            epoch_a: A::mixed_add(a0, a.calibration.convert_from_ns(da / 2)),
41        }
42    }
43
44    /// Converts an instant from clock B's domain to clock A's domain.
45    ///
46    /// Works for instants both before and after the synchronization epoch.
47    pub fn to_a<CA, CB>(&self, t: B::Instant, a: &CA, b: &CB) -> A::Instant
48    where
49        CA: DurationCalibration<A::Duration>,
50        CB: DurationCalibration<B::Duration>,
51    {
52        if B::instant_cmp(t, self.epoch_b).is_lt() {
53            let d_b = B::instant_sub(self.epoch_b, t);
54            A::mixed_sub(self.epoch_a, a.convert_from_ns(b.convert_to_ns(d_b)))
55        } else {
56            let d_b = B::instant_sub(t, self.epoch_b);
57            A::mixed_add(self.epoch_a, a.convert_from_ns(b.convert_to_ns(d_b)))
58        }
59    }
60
61    /// Converts an instant from clock A's domain to clock B's domain.
62    ///
63    /// Works for instants both before and after the synchronization epoch.
64    pub fn to_b<CA, CB>(&self, t: A::Instant, a: &CA, b: &CB) -> B::Instant
65    where
66        CA: DurationCalibration<A::Duration>,
67        CB: DurationCalibration<B::Duration>,
68    {
69        if A::instant_cmp(t, self.epoch_a).is_lt() {
70            let d_a = A::instant_sub(self.epoch_a, t);
71            B::mixed_sub(self.epoch_b, b.convert_from_ns(a.convert_to_ns(d_a)))
72        } else {
73            let d_a = A::instant_sub(t, self.epoch_a);
74            B::mixed_add(self.epoch_b, b.convert_from_ns(a.convert_to_ns(d_a)))
75        }
76    }
77
78    /// Converts an instant from clock B's domain to clock A's domain, assuming `t` is at
79    /// or after the synchronization epoch.
80    ///
81    /// Possibly faster than [`to_a`](Self::to_a).
82    /// Behavior when `t` is before the epoch is unspecified: The function may
83    /// panic or return a meaningless value. Use [`to_a`](Self::to_a) if unsure.
84    pub fn to_a_after_epoch<CA, CB>(&self, t: B::Instant, a: &CA, b: &CB) -> A::Instant
85    where
86        CA: DurationCalibration<A::Duration>,
87        CB: DurationCalibration<B::Duration>,
88    {
89        let d_b = B::instant_sub(t, self.epoch_b);
90        A::mixed_add(self.epoch_a, a.convert_from_ns(b.convert_to_ns(d_b)))
91    }
92
93    /// Converts an instant from clock A's domain to clock B's domain, assuming `t` is at
94    /// or after the synchronization epoch.
95    ///
96    /// Possibly faster than [`to_b`](Self::to_b).
97    /// Behavior when `t` is before the epoch is unspecified: The function may
98    /// panic or return a meaningless value. Use [`to_b`](Self::to_b) if unsure.
99    pub fn to_b_after_epoch<CA, CB>(&self, t: A::Instant, a: &CA, b: &CB) -> B::Instant
100    where
101        CA: DurationCalibration<A::Duration>,
102        CB: DurationCalibration<B::Duration>,
103    {
104        let d_a = A::instant_sub(t, self.epoch_a);
105        B::mixed_add(self.epoch_b, b.convert_from_ns(a.convert_to_ns(d_a)))
106    }
107
108    /// Returns the synchronization epoch in clock A's domain.
109    pub fn epoch_a(&self) -> A::Instant {
110        self.epoch_a
111    }
112
113    /// Returns the synchronization epoch in clock B's domain.
114    pub fn epoch_b(&self) -> B::Instant {
115        self.epoch_b
116    }
117}
118
119impl<A: Time, B: Time> Clone for ClockSynchronization<A, B> {
120    fn clone(&self) -> Self {
121        *self
122    }
123}
124
125impl<A: Time, B: Time> Copy for ClockSynchronization<A, B> {}
126
127impl<A: Time, B: Time> core::fmt::Debug for ClockSynchronization<A, B>
128where
129    A::Instant: core::fmt::Debug,
130    B::Instant: core::fmt::Debug,
131{
132    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
133        f.debug_struct("ClockSynchronization")
134            .field("epoch_a", &self.epoch_a)
135            .field("epoch_b", &self.epoch_b)
136            .finish()
137    }
138}