Skip to main content

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