Skip to main content

telltale_choreography/runtime/
clock.rs

1//! System clock and RNG for production runtime
2//!
3//! These implementations use real system time and entropy sources.
4//! For deterministic simulation and testing, use the mock implementations
5//! in the `testing` module instead.
6
7use std::time::{Duration, Instant};
8
9use crate::testing::clock::{AsyncClock, Clock, Rng, WallClock};
10
11/// System clock using real time.
12///
13/// This implementation is non-deterministic and should only be used
14/// in production runtime contexts, not in simulation or replay scenarios.
15#[derive(Debug, Clone, Copy, Default)]
16pub struct SystemClock;
17
18impl SystemClock {
19    /// Get the current wall-clock time as nanoseconds since Unix epoch.
20    ///
21    /// Use this with `EnvelopeBuilder::timestamp()` when you need real timestamps
22    /// in production contexts.
23    #[must_use]
24    pub fn timestamp_ns() -> u64 {
25        std::time::SystemTime::now()
26            .duration_since(std::time::UNIX_EPOCH)
27            .unwrap_or_default()
28            .as_nanos() as u64
29    }
30}
31
32impl Clock for SystemClock {
33    fn now(&self) -> Duration {
34        static START: std::sync::OnceLock<Instant> = std::sync::OnceLock::new();
35        START.get_or_init(Instant::now).elapsed()
36    }
37
38    fn advance(&self, _duration: Duration) {
39        // Real clock cannot be advanced; this is a no-op
40    }
41}
42
43impl AsyncClock for SystemClock {
44    async fn sleep(&self, duration: Duration) {
45        #[cfg(not(target_arch = "wasm32"))]
46        {
47            tokio::time::sleep(duration).await;
48        }
49        #[cfg(target_arch = "wasm32")]
50        {
51            wasm_timer::Delay::new(duration).await.ok();
52        }
53    }
54}
55
56impl WallClock for SystemClock {
57    fn now_unix_ns(&self) -> u64 {
58        Self::timestamp_ns()
59    }
60}
61
62/// System RNG using host entropy (non-deterministic).
63///
64/// This implementation uses system time and memory addresses for entropy.
65/// For reproducible testing, use `SeededRng` instead.
66#[derive(Debug, Default)]
67pub struct SystemRng {
68    state: u64,
69}
70
71impl SystemRng {
72    /// Create a new system RNG seeded from current time.
73    #[must_use]
74    pub fn new() -> Self {
75        let seed = std::time::SystemTime::now()
76            .duration_since(std::time::UNIX_EPOCH)
77            .unwrap_or_default()
78            .as_nanos() as u64;
79        Self {
80            state: if seed == 0 { 1 } else { seed },
81        }
82    }
83}
84
85impl Rng for SystemRng {
86    fn next_u64(&mut self) -> u64 {
87        // Mix in address for additional entropy
88        let ptr = self as *mut Self as u64;
89        self.state = self
90            .state
91            .wrapping_mul(ptr)
92            .wrapping_add(0x517cc1b727220a95);
93        // xorshift64 for the output
94        self.state ^= self.state << 13;
95        self.state ^= self.state >> 7;
96        self.state ^= self.state << 17;
97        self.state
98    }
99
100    fn fork(&mut self) -> Self {
101        // Fork by mixing current state with time-based entropy
102        let fork_seed = self.next_u64()
103            ^ std::time::SystemTime::now()
104                .duration_since(std::time::UNIX_EPOCH)
105                .unwrap_or_default()
106                .as_nanos() as u64;
107        Self {
108            state: if fork_seed == 0 { 1 } else { fork_seed },
109        }
110    }
111}