Skip to main content

ready_active_safe/
time.rs

1//! Clock, instant, and deadline abstractions for lifecycle timing.
2//!
3//! The base [`Machine`](crate::Machine) trait intentionally has no concept of
4//! time. Time composes externally through your runtime by encoding time into
5//! events (for example `Event::Tick(Instant)`), or by generating timeout events
6//! when a [`Deadline`] expires.
7//!
8//! This module provides small building blocks so time can be:
9//!
10//! - **Real** in production (see [`SystemClock`] with `std`)
11//! - **Virtual** in tests and simulation (see [`ManualClock`])
12//!
13//! It is deliberately **not** a timer scheduler. Scheduling is a runtime concern.
14
15use core::cell::Cell;
16use core::time::Duration;
17
18/// A source of time for lifecycles.
19///
20/// A clock returns the current [`Instant`]. The meaning of "now" is determined
21/// by the implementation:
22///
23/// - [`ManualClock`] is deterministic and controlled by tests.
24/// - [`SystemClock`] is monotonic time since the clock was created.
25pub trait Clock {
26    /// Returns the current instant.
27    fn now(&self) -> Instant;
28}
29
30/// A monotonic instant represented as nanoseconds since an arbitrary origin.
31///
32/// This type is intentionally small and `no_std` friendly. It is suitable for:
33///
34/// - deadlines (`Deadline`)
35/// - timeouts and elapsed-time calculations
36/// - deterministic replay (when driven by a virtual clock)
37#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
38pub struct Instant(u64);
39
40impl Instant {
41    /// Creates an [`Instant`] from a nanosecond counter.
42    #[must_use]
43    pub const fn from_nanos(nanos: u64) -> Self {
44        Self(nanos)
45    }
46
47    /// Returns this instant as nanoseconds since an arbitrary origin.
48    #[must_use]
49    pub const fn as_nanos(self) -> u64 {
50        self.0
51    }
52
53    /// Returns the duration between `earlier` and `self`, if `self >= earlier`.
54    #[must_use]
55    pub const fn checked_duration_since(self, earlier: Self) -> Option<Duration> {
56        if self.0 >= earlier.0 {
57            Some(Duration::from_nanos(self.0 - earlier.0))
58        } else {
59            None
60        }
61    }
62
63    /// Returns the result of adding `duration` to this instant, or `None` on overflow.
64    #[must_use]
65    pub fn checked_add(self, duration: Duration) -> Option<Self> {
66        let nanos = u128::from(self.0).checked_add(duration.as_nanos())?;
67        let nanos_u64 = u64::try_from(nanos).ok()?;
68        Some(Self(nanos_u64))
69    }
70
71    /// Returns the result of subtracting `duration` from this instant, or `None` on underflow.
72    #[must_use]
73    pub fn checked_sub(self, duration: Duration) -> Option<Self> {
74        let nanos = u128::from(self.0).checked_sub(duration.as_nanos())?;
75        let nanos_u64 = u64::try_from(nanos).ok()?;
76        Some(Self(nanos_u64))
77    }
78}
79
80/// A deadline expressed as an [`Instant`].
81#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
82pub struct Deadline {
83    at: Instant,
84}
85
86impl Deadline {
87    /// Creates a deadline at the given instant.
88    #[must_use]
89    pub const fn at(at: Instant) -> Self {
90        Self { at }
91    }
92
93    /// Returns the instant at which the deadline expires.
94    #[must_use]
95    pub const fn instant(self) -> Instant {
96        self.at
97    }
98
99    /// Returns `true` if the deadline has expired at `now`.
100    #[must_use]
101    pub const fn is_expired(self, now: Instant) -> bool {
102        now.as_nanos() >= self.at.as_nanos()
103    }
104}
105
106/// A deterministic clock for tests and simulation.
107///
108/// This clock is controlled explicitly via [`ManualClock::set`] and
109/// [`ManualClock::advance`].
110#[derive(Debug)]
111pub struct ManualClock {
112    now: Cell<Instant>,
113}
114
115impl ManualClock {
116    /// Creates a manual clock starting at the provided instant.
117    #[must_use]
118    pub const fn new(start: Instant) -> Self {
119        Self {
120            now: Cell::new(start),
121        }
122    }
123
124    /// Sets the current instant.
125    pub fn set(&self, now: Instant) {
126        self.now.set(now);
127    }
128
129    /// Advances the clock by the provided duration.
130    ///
131    /// Saturates at `u64::MAX` nanoseconds on overflow.
132    pub fn advance(&self, by: Duration) {
133        let next = self
134            .now
135            .get()
136            .checked_add(by)
137            .unwrap_or_else(|| Instant::from_nanos(u64::MAX));
138        self.now.set(next);
139    }
140}
141
142impl Clock for ManualClock {
143    fn now(&self) -> Instant {
144        self.now.get()
145    }
146}
147
148/// A monotonic system clock.
149///
150/// This clock requires `std` and measures time as "elapsed since creation".
151/// It is appropriate for deadlines and timeouts where only monotonic progress
152/// matters.
153#[cfg(feature = "std")]
154#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
155#[derive(Debug, Clone)]
156pub struct SystemClock {
157    origin: std::time::Instant,
158}
159
160#[cfg(feature = "std")]
161impl SystemClock {
162    /// Creates a new system clock.
163    #[must_use]
164    pub fn new() -> Self {
165        Self {
166            origin: std::time::Instant::now(),
167        }
168    }
169}
170
171#[cfg(feature = "std")]
172impl Default for SystemClock {
173    fn default() -> Self {
174        Self::new()
175    }
176}
177
178#[cfg(feature = "std")]
179impl Clock for SystemClock {
180    fn now(&self) -> Instant {
181        let nanos = self.origin.elapsed().as_nanos().min(u128::from(u64::MAX));
182        let nanos_u64 = u64::try_from(nanos).map_or(u64::MAX, |nanos| nanos);
183        Instant::from_nanos(nanos_u64)
184    }
185}