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}