Skip to main content

liminal_server/console/
wall.rs

1//! The one wall-clock reading the console's records are timed from.
2//!
3//! The wall clock is read once, when the hub is built, and every message's
4//! time is that reading plus the monotonic time elapsed since. A per-message
5//! clock read could fail (a clock set before the epoch) or step backwards;
6//! this cannot, and its one failure happens at start, where it is named.
7
8use std::time::{Instant, SystemTime, UNIX_EPOCH};
9
10use super::error::ConsoleError;
11
12/// A wall-clock reading in milliseconds since the epoch, paired with the
13/// monotonic instant it was taken at.
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub struct WallBase {
16    unix_ms: u64,
17    at: Instant,
18}
19
20impl WallBase {
21    /// Reads the wall clock now.
22    ///
23    /// # Errors
24    /// Returns [`ConsoleError::Clock`] when the wall clock reads before the
25    /// epoch or past what milliseconds in a `u64` can hold.
26    pub fn now() -> Result<Self, ConsoleError> {
27        let at = Instant::now();
28        let since_epoch = SystemTime::now()
29            .duration_since(UNIX_EPOCH)
30            .map_err(|error| ConsoleError::Clock {
31                reason: format!("the wall clock reads before the epoch: {error}"),
32            })?;
33        let unix_ms = u64::try_from(since_epoch.as_millis()).map_err(|_| ConsoleError::Clock {
34            reason: "the wall clock is past u64 milliseconds".to_owned(),
35        })?;
36        Ok(Self { unix_ms, at })
37    }
38
39    /// A base fixed at `unix_ms` for `at`, for records whose times must be
40    /// reproducible.
41    #[must_use]
42    pub const fn fixed(unix_ms: u64, at: Instant) -> Self {
43        Self { unix_ms, at }
44    }
45
46    /// Wall time, in milliseconds since the epoch, of the monotonic instant
47    /// `seen_at`. An instant before the base reads as the base.
48    ///
49    /// # Errors
50    /// Returns [`ConsoleError::Clock`] when the sum passes u64 milliseconds.
51    pub fn unix_ms_at(&self, seen_at: Instant) -> Result<u64, ConsoleError> {
52        self.unix_ms_of(seen_at.max(self.at))
53    }
54
55    /// Wall time, in milliseconds since the epoch, of the monotonic instant
56    /// `at`, before or after the base.
57    ///
58    /// # Errors
59    /// Returns [`ConsoleError::Clock`] when it falls outside u64
60    /// milliseconds.
61    pub fn unix_ms_of(&self, at: Instant) -> Result<u64, ConsoleError> {
62        let wall = if at >= self.at {
63            u64::try_from(at.duration_since(self.at).as_millis())
64                .ok()
65                .and_then(|after| self.unix_ms.checked_add(after))
66        } else {
67            u64::try_from(self.at.duration_since(at).as_millis())
68                .ok()
69                .and_then(|before| self.unix_ms.checked_sub(before))
70        };
71        wall.ok_or_else(|| ConsoleError::Clock {
72            reason: "a time is outside u64 milliseconds since the epoch".to_owned(),
73        })
74    }
75}