Skip to main content

bacnet_time/
host.rs

1//! [`HostClock`]: a host's `std::time::Instant` on the cores' timeline.
2
3use std::time::Instant as StdInstant;
4
5use crate::{Duration, Instant};
6
7/// Where a [`HostClock::centred`] origin sits on the timeline: 2^63 ns.
8const MIDPOINT: Instant = Instant::from_nanos(1 << 63);
9
10/// Maps a host's monotonic clock, as `std::time::Instant`, onto the
11/// nanosecond timeline the cores take, and back. Both ways are exact for
12/// every instant the timeline holds, so a timer set at the host instant of
13/// a core's deadline never wakes before that deadline is due.
14///
15/// The clock fixes one host instant, its origin, to one point of the
16/// timeline:
17///
18/// - [`HostClock::new`] puts the origin at [`Instant::ZERO`]: the usual
19///   choice, with the origin taken as the host starts its cores. A host
20///   instant before the origin maps to [`Instant::ZERO`].
21/// - [`HostClock::centred`] puts it at 2^63 ns, the middle of the timeline,
22///   so instants up to about 292 years either side of it map exactly. A
23///   host that also hands the cores times from before it started (a
24///   restored or backdated record, say) takes this one.
25///
26/// [`now`](Self::now) reads the host clock through a function the host
27/// chooses, `std::time::Instant::now` unless [`reading`](Self::reading)
28/// names another. An async runtime whose time can be paused in tests (Tokio's
29/// `tokio::time::Instant::now().into_std()`, say) passes its own, so the
30/// cores' deadlines follow the runtime's time.
31///
32/// ```
33/// use bacnet_time::{Duration, HostClock, Instant};
34///
35/// let origin = std::time::Instant::now();
36/// let clock = HostClock::new(origin);
37/// let later = origin + std::time::Duration::from_millis(250);
38/// assert_eq!(clock.instant(later), Instant::from_millis(250));
39/// assert_eq!(clock.to_std(Instant::from_millis(250)), Some(later));
40///
41/// // Centred: an instant before the origin keeps its distance from it.
42/// let clock = HostClock::centred(later);
43/// assert_eq!(clock.instant(later) - clock.instant(origin), Duration::from_millis(250));
44/// assert_eq!(clock.to_std(clock.instant(origin)), Some(origin));
45/// ```
46#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
47#[derive(Debug, Clone, Copy)]
48pub struct HostClock {
49    /// The host instant at `zero`.
50    origin: StdInstant,
51    /// The point of the timeline `origin` maps to.
52    zero: Instant,
53    /// What [`now`](Self::now) reads.
54    source: fn() -> StdInstant,
55}
56
57impl HostClock {
58    /// A clock that maps `origin` to [`Instant::ZERO`] and reads
59    /// `std::time::Instant::now`. Host instants before `origin` map to
60    /// [`Instant::ZERO`], and those more than about 584 years after it to
61    /// [`Instant::MAX`].
62    #[must_use]
63    pub fn new(origin: StdInstant) -> Self {
64        Self {
65            origin,
66            zero: Instant::ZERO,
67            source: StdInstant::now,
68        }
69    }
70
71    /// A clock that maps `origin` to the middle of the timeline, 2^63 ns,
72    /// and reads `std::time::Instant::now`: host instants up to about 292
73    /// years either side of `origin` map exactly, and those further away
74    /// saturate at [`Instant::ZERO`] or [`Instant::MAX`].
75    #[must_use]
76    pub fn centred(origin: StdInstant) -> Self {
77        Self {
78            origin,
79            zero: MIDPOINT,
80            source: StdInstant::now,
81        }
82    }
83
84    /// This clock, with [`now`](Self::now) reading `now` instead. `now`
85    /// must give instants on the same monotonic clock as the origin.
86    #[must_use]
87    pub fn reading(self, now: fn() -> StdInstant) -> Self {
88        Self {
89            source: now,
90            ..self
91        }
92    }
93
94    /// The host instant this clock is built on.
95    #[must_use]
96    pub fn origin(&self) -> StdInstant {
97        self.origin
98    }
99
100    /// The timeline's time now, from the clock's source.
101    #[must_use]
102    pub fn now(&self) -> Instant {
103        self.instant((self.source)())
104    }
105
106    /// `at` on the timeline, saturating as the constructor says.
107    ///
108    /// The difference is taken from the later of `at` and the origin. On
109    /// Windows, `std` reports an earlier instant less than one counter tick
110    /// (100 ns) away as no time at all, which would put an instant just
111    /// before the origin on the origin itself.
112    #[must_use]
113    pub fn instant(&self, at: StdInstant) -> Instant {
114        if at >= self.origin {
115            self.zero + Duration::saturating_from_core(at.duration_since(self.origin))
116        } else {
117            self.zero - Duration::saturating_from_core(self.origin.duration_since(at))
118        }
119    }
120
121    /// The host instant that `at` on the timeline stands for, or `None` when
122    /// the host's clock can't represent it. A host sleeping until a core's
123    /// deadline takes `None` as no deadline: that far out, it never comes.
124    #[must_use]
125    pub fn to_std(&self, at: Instant) -> Option<StdInstant> {
126        if at >= self.zero {
127            let after = at.saturating_duration_since(self.zero);
128            self.origin
129                .checked_add(core::time::Duration::from_nanos(after.as_nanos()))
130        } else {
131            let before = self.zero.saturating_duration_since(at);
132            self.origin
133                .checked_sub(core::time::Duration::from_nanos(before.as_nanos()))
134        }
135    }
136}