#![forbid(unsafe_code)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SimulationClock {
tick: usize,
tick_duration: f64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SimulationClockError {
InvalidTickDuration,
TickOverflow,
NonFiniteElapsed,
}
impl SimulationClock {
pub fn new(tick_duration: f64) -> Result<Self, SimulationClockError> {
Self::at_tick(0, tick_duration)
}
pub fn at_tick(tick: usize, tick_duration: f64) -> Result<Self, SimulationClockError> {
if !tick_duration.is_finite() || tick_duration <= 0.0 {
return Err(SimulationClockError::InvalidTickDuration);
}
let elapsed = tick as f64 * tick_duration;
if !elapsed.is_finite() {
return Err(SimulationClockError::NonFiniteElapsed);
}
Ok(Self {
tick,
tick_duration,
})
}
pub fn tick(&self) -> usize {
self.tick
}
pub fn tick_duration(&self) -> f64 {
self.tick_duration
}
pub fn elapsed(&self) -> f64 {
self.tick as f64 * self.tick_duration
}
pub fn advance(&mut self) -> Result<f64, SimulationClockError> {
self.advance_by(1)
}
pub fn advance_by(&mut self, steps: usize) -> Result<f64, SimulationClockError> {
self.tick = self
.tick
.checked_add(steps)
.ok_or(SimulationClockError::TickOverflow)?;
let elapsed = self.elapsed();
if !elapsed.is_finite() {
return Err(SimulationClockError::NonFiniteElapsed);
}
Ok(elapsed)
}
}
pub fn elapsed_for(tick_duration: f64, ticks: usize) -> Option<f64> {
if !tick_duration.is_finite() || tick_duration <= 0.0 {
return None;
}
let elapsed = tick_duration * ticks as f64;
elapsed.is_finite().then_some(elapsed)
}
#[cfg(test)]
mod tests {
use super::{SimulationClock, SimulationClockError, elapsed_for};
#[test]
fn advances_clock_in_ticks() {
let mut clock = SimulationClock::new(0.5).unwrap();
assert_eq!(clock.elapsed(), 0.0);
assert_eq!(clock.advance().unwrap(), 0.5);
assert_eq!(clock.advance_by(3).unwrap(), 2.0);
assert_eq!(clock.tick(), 4);
assert_eq!(clock.tick_duration(), 0.5);
}
#[test]
fn can_start_at_existing_tick() {
let clock = SimulationClock::at_tick(3, 0.25).unwrap();
assert_eq!(clock.tick(), 3);
assert_eq!(clock.elapsed(), 0.75);
assert_eq!(elapsed_for(0.25, 3), Some(0.75));
}
#[test]
fn rejects_invalid_duration() {
assert_eq!(
SimulationClock::new(0.0),
Err(SimulationClockError::InvalidTickDuration)
);
assert_eq!(elapsed_for(f64::NAN, 2), None);
}
#[test]
fn rejects_non_finite_elapsed_time() {
assert_eq!(
SimulationClock::at_tick(usize::MAX, f64::MAX),
Err(SimulationClockError::NonFiniteElapsed)
);
}
}