use_simulation_clock/
lib.rs1#![forbid(unsafe_code)]
2#[derive(Debug, Clone, Copy, PartialEq)]
19pub struct SimulationClock {
20 tick: usize,
21 tick_duration: f64,
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum SimulationClockError {
26 InvalidTickDuration,
27 TickOverflow,
28 NonFiniteElapsed,
29}
30
31impl SimulationClock {
32 pub fn new(tick_duration: f64) -> Result<Self, SimulationClockError> {
33 Self::at_tick(0, tick_duration)
34 }
35
36 pub fn at_tick(tick: usize, tick_duration: f64) -> Result<Self, SimulationClockError> {
37 if !tick_duration.is_finite() || tick_duration <= 0.0 {
38 return Err(SimulationClockError::InvalidTickDuration);
39 }
40
41 let elapsed = tick as f64 * tick_duration;
42 if !elapsed.is_finite() {
43 return Err(SimulationClockError::NonFiniteElapsed);
44 }
45
46 Ok(Self {
47 tick,
48 tick_duration,
49 })
50 }
51
52 pub fn tick(&self) -> usize {
53 self.tick
54 }
55
56 pub fn tick_duration(&self) -> f64 {
57 self.tick_duration
58 }
59
60 pub fn elapsed(&self) -> f64 {
61 self.tick as f64 * self.tick_duration
62 }
63
64 pub fn advance(&mut self) -> Result<f64, SimulationClockError> {
65 self.advance_by(1)
66 }
67
68 pub fn advance_by(&mut self, steps: usize) -> Result<f64, SimulationClockError> {
69 self.tick = self
70 .tick
71 .checked_add(steps)
72 .ok_or(SimulationClockError::TickOverflow)?;
73
74 let elapsed = self.elapsed();
75 if !elapsed.is_finite() {
76 return Err(SimulationClockError::NonFiniteElapsed);
77 }
78
79 Ok(elapsed)
80 }
81}
82
83pub fn elapsed_for(tick_duration: f64, ticks: usize) -> Option<f64> {
84 if !tick_duration.is_finite() || tick_duration <= 0.0 {
85 return None;
86 }
87
88 let elapsed = tick_duration * ticks as f64;
89 elapsed.is_finite().then_some(elapsed)
90}
91
92#[cfg(test)]
93mod tests {
94 use super::{SimulationClock, SimulationClockError, elapsed_for};
95
96 #[test]
97 fn advances_clock_in_ticks() {
98 let mut clock = SimulationClock::new(0.5).unwrap();
99
100 assert_eq!(clock.elapsed(), 0.0);
101 assert_eq!(clock.advance().unwrap(), 0.5);
102 assert_eq!(clock.advance_by(3).unwrap(), 2.0);
103 assert_eq!(clock.tick(), 4);
104 assert_eq!(clock.tick_duration(), 0.5);
105 }
106
107 #[test]
108 fn can_start_at_existing_tick() {
109 let clock = SimulationClock::at_tick(3, 0.25).unwrap();
110
111 assert_eq!(clock.tick(), 3);
112 assert_eq!(clock.elapsed(), 0.75);
113 assert_eq!(elapsed_for(0.25, 3), Some(0.75));
114 }
115
116 #[test]
117 fn rejects_invalid_duration() {
118 assert_eq!(
119 SimulationClock::new(0.0),
120 Err(SimulationClockError::InvalidTickDuration)
121 );
122 assert_eq!(elapsed_for(f64::NAN, 2), None);
123 }
124
125 #[test]
126 fn rejects_non_finite_elapsed_time() {
127 assert_eq!(
128 SimulationClock::at_tick(usize::MAX, f64::MAX),
129 Err(SimulationClockError::NonFiniteElapsed)
130 );
131 }
132}