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}