gemini_adk_rs/
clock.rs

1//! The time source the runtime reads, so that time is an input you control.
2//!
3//! Every decision the runtime makes from elapsed time reads a [`Clock`]:
4//!
5//! - temporal patterns ("sustained for 5 s");
6//! - phase durations;
7//! - resolver cache expiry;
8//! - the `session:` timing signals (`silence_ms`, `elapsed_ms`, …);
9//! - mutation-journal timestamps.
10//!
11//! In production that is the [`SystemClock`]. In a test or a replay it is a
12//! [`ManualClock`] you advance yourself, so the same inputs give the same
13//! decisions every run. [`replay_session`](crate::live::replay::replay_session)
14//! drives one from the recorded frame timestamps.
15//!
16//! The clock travels with [`State`](crate::state::State): every clone and
17//! delta view shares it, so a component that can read state can read the
18//! time without another parameter.
19//!
20//! ```
21//! use std::sync::Arc;
22//! use std::time::Duration;
23//! use gemini_adk_rs::clock::{Clock, ManualClock};
24//! use gemini_adk_rs::State;
25//!
26//! let clock = Arc::new(ManualClock::new());
27//! let state = State::new().with_clock(clock.clone());
28//!
29//! let t0 = state.clock().now();
30//! clock.advance(Duration::from_secs(5));
31//! assert_eq!(state.clock().now() - t0, Duration::from_secs(5));
32//! ```
33
34use std::fmt;
35use std::sync::Arc;
36use std::sync::atomic::{AtomicU64, Ordering};
37use std::time::{Duration, Instant, SystemTime};
38
39/// A source of monotonic and wall-clock time.
40pub trait Clock: Send + Sync + fmt::Debug {
41    /// The current monotonic instant. Use for durations and deadlines.
42    fn now(&self) -> Instant;
43
44    /// The current wall-clock time. Use for timestamps that leave the process.
45    fn system_time(&self) -> SystemTime;
46
47    /// Time elapsed since `earlier`, per this clock. Saturates at zero.
48    fn since(&self, earlier: Instant) -> Duration {
49        self.now().saturating_duration_since(earlier)
50    }
51}
52
53/// A shared, dynamically dispatched clock.
54pub type SharedClock = Arc<dyn Clock>;
55
56/// The real clock: [`Instant::now`] and [`SystemTime::now`].
57#[derive(Debug, Default, Clone, Copy)]
58pub struct SystemClock;
59
60impl Clock for SystemClock {
61    fn now(&self) -> Instant {
62        Instant::now()
63    }
64
65    fn system_time(&self) -> SystemTime {
66        SystemTime::now()
67    }
68}
69
70/// The default clock: a shared [`SystemClock`].
71pub fn system_clock() -> SharedClock {
72    Arc::new(SystemClock)
73}
74
75/// A clock that moves only when told to.
76///
77/// It starts at the moment of construction and never goes backwards:
78/// [`set_elapsed`](Self::set_elapsed) to an earlier point is ignored, so
79/// out-of-order inputs cannot make a duration negative.
80#[derive(Debug)]
81pub struct ManualClock {
82    origin: Instant,
83    system_origin: SystemTime,
84    elapsed_nanos: AtomicU64,
85}
86
87impl Default for ManualClock {
88    fn default() -> Self {
89        Self::new()
90    }
91}
92
93impl ManualClock {
94    /// A clock whose wall time starts at the current system time.
95    pub fn new() -> Self {
96        Self::starting_at(SystemTime::now())
97    }
98
99    /// A clock whose wall time starts at `system_origin` (for example, the
100    /// first timestamp of a recording).
101    pub fn starting_at(system_origin: SystemTime) -> Self {
102        Self {
103            origin: Instant::now(),
104            system_origin,
105            elapsed_nanos: AtomicU64::new(0),
106        }
107    }
108
109    /// Move the clock forward by `by`.
110    pub fn advance(&self, by: Duration) {
111        self.elapsed_nanos
112            .fetch_add(saturating_nanos(by), Ordering::AcqRel);
113    }
114
115    /// Move the clock to `elapsed` after its origin, if that is later than
116    /// where it is now.
117    pub fn set_elapsed(&self, elapsed: Duration) {
118        self.elapsed_nanos
119            .fetch_max(saturating_nanos(elapsed), Ordering::AcqRel);
120    }
121
122    /// Time elapsed since the clock's origin.
123    pub fn elapsed(&self) -> Duration {
124        Duration::from_nanos(self.elapsed_nanos.load(Ordering::Acquire))
125    }
126}
127
128impl Clock for ManualClock {
129    fn now(&self) -> Instant {
130        self.origin + self.elapsed()
131    }
132
133    fn system_time(&self) -> SystemTime {
134        self.system_origin + self.elapsed()
135    }
136}
137
138fn saturating_nanos(d: Duration) -> u64 {
139    u64::try_from(d.as_nanos()).unwrap_or(u64::MAX)
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145
146    #[test]
147    fn manual_clock_moves_only_when_told() {
148        let clock = ManualClock::starting_at(SystemTime::UNIX_EPOCH);
149        let t0 = clock.now();
150        assert_eq!(clock.now(), t0);
151
152        clock.advance(Duration::from_millis(250));
153        assert_eq!(clock.since(t0), Duration::from_millis(250));
154        assert_eq!(
155            clock.system_time(),
156            SystemTime::UNIX_EPOCH + Duration::from_millis(250)
157        );
158    }
159
160    #[test]
161    fn manual_clock_never_goes_backwards() {
162        let clock = ManualClock::new();
163        clock.set_elapsed(Duration::from_secs(10));
164        clock.set_elapsed(Duration::from_secs(3));
165        assert_eq!(clock.elapsed(), Duration::from_secs(10));
166    }
167
168    #[test]
169    fn since_saturates_for_a_future_instant() {
170        let clock = ManualClock::new();
171        let later = clock.now() + Duration::from_secs(1);
172        assert_eq!(clock.since(later), Duration::ZERO);
173    }
174}