shape_runtime/stdlib/
deterministic.rs1use rand::Rng;
10use rand::SeedableRng;
11use rand_chacha::ChaCha8Rng;
12
13const DEFAULT_CLOCK_INCREMENT_NS: u64 = 1_000_000;
15
16pub struct DeterministicRuntime {
22 rng: ChaCha8Rng,
23 virtual_clock_ns: u64,
24 clock_increment_ns: u64,
25 seed: u64,
26}
27
28impl DeterministicRuntime {
29 pub fn new(seed: u64) -> Self {
34 Self {
35 rng: ChaCha8Rng::seed_from_u64(seed),
36 virtual_clock_ns: 0,
37 clock_increment_ns: DEFAULT_CLOCK_INCREMENT_NS,
38 seed,
39 }
40 }
41
42 pub fn with_clock_increment(seed: u64, clock_increment_ns: u64) -> Self {
44 Self {
45 rng: ChaCha8Rng::seed_from_u64(seed),
46 virtual_clock_ns: 0,
47 clock_increment_ns,
48 seed,
49 }
50 }
51
52 pub fn seed(&self) -> u64 {
54 self.seed
55 }
56
57 pub fn next_random_f64(&mut self) -> f64 {
59 self.rng.r#gen::<f64>()
60 }
61
62 pub fn next_random_range(&mut self, min: f64, max: f64) -> f64 {
64 min + self.rng.r#gen::<f64>() * (max - min)
65 }
66
67 pub fn current_time_ms(&mut self) -> f64 {
73 let ms = self.virtual_clock_ns as f64 / 1_000_000.0;
74 self.virtual_clock_ns += self.clock_increment_ns;
75 ms
76 }
77
78 pub fn advance_clock(&mut self, ns: u64) {
80 self.virtual_clock_ns += ns;
81 }
82
83 pub fn reset(&mut self) {
85 self.rng = ChaCha8Rng::seed_from_u64(self.seed);
86 self.virtual_clock_ns = 0;
87 }
88}
89
90#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
99 fn deterministic_random_same_seed() {
100 let mut a = DeterministicRuntime::new(42);
101 let mut b = DeterministicRuntime::new(42);
102 for _ in 0..100 {
103 assert_eq!(a.next_random_f64(), b.next_random_f64());
104 }
105 }
106
107 #[test]
108 fn different_seeds_differ() {
109 let mut a = DeterministicRuntime::new(1);
110 let mut b = DeterministicRuntime::new(2);
111 let any_differ = (0..100).any(|_| a.next_random_f64() != b.next_random_f64());
113 assert!(any_differ);
114 }
115
116 #[test]
117 fn random_range() {
118 let mut rt = DeterministicRuntime::new(0);
119 for _ in 0..1000 {
120 let v = rt.next_random_range(10.0, 20.0);
121 assert!((10.0..20.0).contains(&v), "out of range: {v}");
122 }
123 }
124
125 #[test]
126 fn virtual_clock_monotonic() {
127 let mut rt = DeterministicRuntime::new(0);
128 let mut prev = -1.0;
129 for _ in 0..100 {
130 let now = rt.current_time_ms();
131 assert!(now > prev, "clock not monotonic: {now} <= {prev}");
132 prev = now;
133 }
134 }
135
136 #[test]
137 fn virtual_clock_starts_at_zero() {
138 let mut rt = DeterministicRuntime::new(0);
139 assert_eq!(rt.current_time_ms(), 0.0);
140 }
141
142 #[test]
143 fn virtual_clock_default_increment() {
144 let mut rt = DeterministicRuntime::new(0);
145 let t0 = rt.current_time_ms(); let t1 = rt.current_time_ms(); assert_eq!(t0, 0.0);
148 assert_eq!(t1, 1.0);
149 }
150
151 #[test]
152 fn virtual_clock_custom_increment() {
153 let mut rt = DeterministicRuntime::with_clock_increment(0, 500_000); let t0 = rt.current_time_ms();
155 let t1 = rt.current_time_ms();
156 assert_eq!(t0, 0.0);
157 assert_eq!(t1, 0.5);
158 }
159
160 #[test]
161 fn advance_clock_manually() {
162 let mut rt = DeterministicRuntime::new(0);
163 rt.advance_clock(5_000_000_000); let ms = rt.current_time_ms();
165 assert_eq!(ms, 5000.0);
166 }
167
168 #[test]
169 fn reset_restores_initial_state() {
170 let mut rt = DeterministicRuntime::new(42);
171 let first_rand = rt.next_random_f64();
172 let first_time = rt.current_time_ms();
173
174 for _ in 0..50 {
176 rt.next_random_f64();
177 rt.current_time_ms();
178 }
179
180 rt.reset();
181 assert_eq!(rt.next_random_f64(), first_rand);
182 assert_eq!(rt.current_time_ms(), first_time);
183 }
184
185 #[test]
186 fn seed_accessor() {
187 let rt = DeterministicRuntime::new(12345);
188 assert_eq!(rt.seed(), 12345);
189 }
190}