Skip to main content

grit_core/
clock.rs

1//! Injected time. Library code paths never call `SystemTime::now()` directly
2//! (Design Invariant 3); everything flows through a [`Clock`] chosen by the
3//! caller at open time.
4
5use std::sync::Arc;
6use std::sync::atomic::{AtomicI64, Ordering};
7
8/// Milliseconds since the Unix epoch, UTC.
9pub type TimestampMs = i64;
10
11/// A source of wall-clock time. Injected into [`crate::Grit`] via
12/// [`crate::Options`]; tests inject [`ManualClock`] for full determinism.
13///
14/// Contract: implementations should return non-negative values (times at or
15/// after the Unix epoch). grit clamps negative readings to 0 wherever they
16/// would feed an HLC — the sortable HLC encoding is only order-preserving
17/// for non-negative wall times.
18///
19/// # Example
20/// ```
21/// use grit_core::{Clock, ManualClock};
22/// let clock = ManualClock::new(1_000);
23/// assert_eq!(clock.now_ms(), 1_000);
24/// clock.advance_ms(5);
25/// assert_eq!(clock.now_ms(), 1_005);
26/// ```
27pub trait Clock: Send + Sync {
28    /// Current wall time in milliseconds since the Unix epoch.
29    fn now_ms(&self) -> TimestampMs;
30}
31
32/// The real system clock. This is the *caller's* choice of default — grit
33/// itself only ever sees the trait.
34#[derive(Debug, Clone, Copy, Default)]
35pub struct SystemClock;
36
37impl Clock for SystemClock {
38    fn now_ms(&self) -> TimestampMs {
39        // A system clock set before 1970 saturates to 0 rather than panicking
40        // (see the Clock contract).
41        std::time::SystemTime::now()
42            .duration_since(std::time::UNIX_EPOCH)
43            .map_or(0, |d| d.as_millis() as TimestampMs)
44    }
45}
46
47/// A deterministic, manually-advanced clock for tests and simulations.
48///
49/// # Example
50/// ```
51/// use grit_core::{Clock, ManualClock};
52/// let c = ManualClock::new(42);
53/// c.set_ms(100);
54/// assert_eq!(c.now_ms(), 100);
55/// ```
56#[derive(Debug, Default)]
57pub struct ManualClock {
58    now: AtomicI64,
59}
60
61impl ManualClock {
62    /// Create a clock frozen at `now_ms`.
63    pub fn new(now_ms: TimestampMs) -> Self {
64        Self {
65            now: AtomicI64::new(now_ms),
66        }
67    }
68
69    /// Move the clock forward by `delta_ms`.
70    pub fn advance_ms(&self, delta_ms: i64) {
71        self.now.fetch_add(delta_ms, Ordering::SeqCst);
72    }
73
74    /// Set the clock to an absolute time.
75    pub fn set_ms(&self, now_ms: TimestampMs) {
76        self.now.store(now_ms, Ordering::SeqCst);
77    }
78}
79
80impl Clock for ManualClock {
81    fn now_ms(&self) -> TimestampMs {
82        self.now.load(Ordering::SeqCst)
83    }
84}
85
86impl<C: Clock + ?Sized> Clock for Arc<C> {
87    fn now_ms(&self) -> TimestampMs {
88        (**self).now_ms()
89    }
90}