1use crate::engine::SimTime;
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct SimClock {
16 current: SimTime,
18 timestep_nanos: u64,
20 step_count: u64,
22 max_time: Option<SimTime>,
24}
25
26impl SimClock {
27 #[must_use]
33 pub fn new(timestep_secs: f64) -> Self {
34 assert!(timestep_secs > 0.0, "Timestep must be positive");
35 assert!(timestep_secs.is_finite(), "Timestep must be finite");
36
37 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
38 let timestep_nanos = (timestep_secs * 1_000_000_000.0) as u64;
39
40 Self {
41 current: SimTime::ZERO,
42 timestep_nanos,
43 step_count: 0,
44 max_time: None,
45 }
46 }
47
48 #[must_use]
50 pub const fn from_nanos(timestep_nanos: u64) -> Self {
51 Self {
52 current: SimTime::ZERO,
53 timestep_nanos,
54 step_count: 0,
55 max_time: None,
56 }
57 }
58
59 #[must_use]
61 pub const fn current_time(&self) -> SimTime {
62 self.current
63 }
64
65 #[must_use]
67 pub fn timestep_secs(&self) -> f64 {
68 contract_pre_iterator!();
69 let result = self.timestep_nanos as f64 / 1_000_000_000.0;
70 contract_post_configuration!(&"ok");
71 result
72 }
73
74 #[must_use]
76 pub fn dt(&self) -> f64 {
77 self.timestep_secs()
78 }
79
80 #[must_use]
82 pub const fn timestep_nanos(&self) -> u64 {
83 self.timestep_nanos
84 }
85
86 #[must_use]
88 pub const fn step_count(&self) -> u64 {
89 self.step_count
90 }
91
92 #[allow(clippy::missing_const_for_fn)] pub fn set_max_time(&mut self, max: SimTime) {
95 self.max_time = Some(max);
96 }
97
98 #[must_use]
100 pub fn at_max_time(&self) -> bool {
101 self.max_time.is_some_and(|max| self.current >= max)
102 }
103
104 #[allow(clippy::missing_const_for_fn)] pub fn tick(&mut self) -> SimTime {
109 self.current = self.current.add_nanos(self.timestep_nanos);
110 self.step_count += 1;
111 self.current
112 }
113
114 pub fn tick_n(&mut self, n: u64) -> SimTime {
118 for _ in 0..n {
119 self.tick();
120 }
121 self.current
122 }
123
124 #[allow(clippy::missing_const_for_fn)] pub fn set_time(&mut self, time: SimTime) {
127 self.current = time;
128 }
129
130 #[allow(clippy::missing_const_for_fn)] pub fn reset(&mut self) {
133 self.current = SimTime::ZERO;
134 self.step_count = 0;
135 }
136
137 #[must_use]
139 pub fn time_until(&self, target: SimTime) -> SimTime {
140 if target > self.current {
141 target - self.current
142 } else {
143 SimTime::ZERO
144 }
145 }
146
147 #[must_use]
149 pub fn steps_until(&self, target: SimTime) -> u64 {
150 contract_pre_iterator!();
151 let time_diff = self.time_until(target);
152 let nanos = time_diff.as_nanos();
153
154 if self.timestep_nanos == 0 {
155 contract_post_configuration!(&"ok");
156 return 0;
157 }
158
159 let result = nanos.div_ceil(self.timestep_nanos);
160 contract_post_configuration!(&"ok");
161 result
162 }
163}
164
165impl Default for SimClock {
166 fn default() -> Self {
167 Self::from_nanos(1_000_000)
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 #[test]
177 fn test_clock_creation() {
178 let clock = SimClock::new(0.001); assert_eq!(clock.current_time(), SimTime::ZERO);
181 assert!((clock.timestep_secs() - 0.001).abs() < 1e-9);
182 assert_eq!(clock.step_count(), 0);
183 }
184
185 #[test]
186 fn test_clock_tick() {
187 let mut clock = SimClock::new(0.001);
188
189 clock.tick();
190 assert_eq!(clock.step_count(), 1);
191 assert!((clock.current_time().as_secs_f64() - 0.001).abs() < 1e-9);
192
193 clock.tick();
194 assert_eq!(clock.step_count(), 2);
195 assert!((clock.current_time().as_secs_f64() - 0.002).abs() < 1e-9);
196 }
197
198 #[test]
199 fn test_clock_tick_n() {
200 let mut clock = SimClock::new(0.001);
201
202 clock.tick_n(100);
203 assert_eq!(clock.step_count(), 100);
204 assert!((clock.current_time().as_secs_f64() - 0.1).abs() < 1e-9);
205 }
206
207 #[test]
208 fn test_clock_max_time() {
209 let mut clock = SimClock::new(0.1);
210 clock.set_max_time(SimTime::from_secs(0.5));
211
212 assert!(!clock.at_max_time());
213
214 clock.tick_n(4);
215 assert!(!clock.at_max_time());
216
217 clock.tick();
218 assert!(clock.at_max_time());
219 }
220
221 #[test]
222 fn test_clock_reset() {
223 let mut clock = SimClock::new(0.001);
224
225 clock.tick_n(100);
226 assert!(clock.step_count() > 0);
227
228 clock.reset();
229 assert_eq!(clock.step_count(), 0);
230 assert_eq!(clock.current_time(), SimTime::ZERO);
231 }
232
233 #[test]
234 fn test_clock_time_until() {
235 let mut clock = SimClock::new(0.001);
236 clock.tick_n(10); let until = clock.time_until(SimTime::from_secs(0.1));
239 assert!((until.as_secs_f64() - 0.09).abs() < 1e-9);
240
241 let until_past = clock.time_until(SimTime::from_secs(0.005));
243 assert_eq!(until_past, SimTime::ZERO);
244 }
245
246 #[test]
247 fn test_clock_steps_until() {
248 let clock = SimClock::new(0.01); let steps = clock.steps_until(SimTime::from_secs(1.0));
252 assert_eq!(steps, 100);
253
254 let steps2 = clock.steps_until(SimTime::from_secs(0.015));
256 assert_eq!(steps2, 2); }
258
259 #[test]
260 fn test_clock_set_time() {
261 let mut clock = SimClock::new(0.001);
262
263 clock.set_time(SimTime::from_secs(5.0));
264 assert!((clock.current_time().as_secs_f64() - 5.0).abs() < 1e-9);
265 }
266
267 #[test]
268 fn test_clock_from_nanos() {
269 let clock = SimClock::from_nanos(1_000_000); assert_eq!(clock.current_time(), SimTime::ZERO);
272 assert_eq!(clock.timestep_nanos(), 1_000_000);
273 assert!((clock.timestep_secs() - 0.001).abs() < 1e-9);
274 assert_eq!(clock.step_count(), 0);
275 }
276
277 #[test]
278 fn test_clock_default() {
279 let clock = SimClock::default();
280
281 assert_eq!(clock.timestep_nanos(), 1_000_000); assert_eq!(clock.current_time(), SimTime::ZERO);
283 assert_eq!(clock.step_count(), 0);
284 }
285
286 #[test]
287 fn test_clock_steps_until_zero_timestep() {
288 let clock = SimClock::from_nanos(0);
290 let steps = clock.steps_until(SimTime::from_secs(1.0));
291 assert_eq!(steps, 0); }
293
294 #[test]
295 fn test_clock_at_max_time_no_max() {
296 let clock = SimClock::new(0.001);
297 assert!(!clock.at_max_time()); }
299
300 #[test]
301 fn test_clock_timestep_nanos_accessor() {
302 let clock = SimClock::new(0.5); assert_eq!(clock.timestep_nanos(), 500_000_000);
304 }
305
306 #[test]
307 fn test_clock_tick_returns_new_time() {
308 let mut clock = SimClock::new(0.1);
309 let new_time = clock.tick();
310 assert!((new_time.as_secs_f64() - 0.1).abs() < 1e-9);
311 }
312
313 #[test]
314 fn test_clock_tick_n_returns_final_time() {
315 let mut clock = SimClock::new(0.1);
316 let final_time = clock.tick_n(5);
317 assert!((final_time.as_secs_f64() - 0.5).abs() < 1e-9);
318 }
319
320 #[test]
321 fn test_clock_clone() {
322 let clock = SimClock::new(0.001);
323 let cloned = clock.clone();
324 assert_eq!(cloned.timestep_nanos(), clock.timestep_nanos());
325 assert_eq!(cloned.current_time(), clock.current_time());
326 }
327
328 #[test]
329 fn test_clock_debug() {
330 let clock = SimClock::new(0.001);
331 let debug = format!("{:?}", clock);
332 assert!(debug.contains("SimClock"));
333 }
334}
335
336#[cfg(test)]
337mod proptests {
338 use super::*;
339 use proptest::prelude::*;
340
341 proptest! {
342 #[test]
344 fn prop_time_increases(timestep in 0.0001f64..1.0, ticks in 1u64..1000) {
345 let mut clock = SimClock::new(timestep);
346 let initial = clock.current_time();
347
348 clock.tick_n(ticks);
349
350 prop_assert!(clock.current_time() > initial);
351 }
352
353 #[test]
355 fn prop_step_count_accurate(timestep in 0.0001f64..1.0, ticks in 0u64..1000) {
356 let mut clock = SimClock::new(timestep);
357
358 clock.tick_n(ticks);
359
360 prop_assert_eq!(clock.step_count(), ticks);
361 }
362
363 #[test]
365 fn prop_time_advance_correct(timestep in 0.0001f64..0.1, ticks in 1u64..100) {
366 let mut clock = SimClock::new(timestep);
367
368 clock.tick_n(ticks);
369
370 let expected = timestep * ticks as f64;
371 let actual = clock.current_time().as_secs_f64();
372
373 let tolerance = 1e-6 * expected.max(1.0);
376 prop_assert!((actual - expected).abs() < tolerance,
377 "Expected {}, got {}, diff {}", expected, actual, (actual - expected).abs());
378 }
379 }
380}