1use std::fmt;
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
8pub struct WorldTick(pub u64);
9
10impl WorldTick {
11 pub fn zero() -> Self {
13 Self(0)
14 }
15
16 pub fn next(self) -> Self {
18 Self(self.0 + 1)
19 }
20}
21
22impl fmt::Display for WorldTick {
23 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24 write!(f, "tick:{}", self.0)
25 }
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct WorldClock {
35 pub tick: WorldTick,
37 pub epoch: DateTime<Utc>,
39 pub wall_time: DateTime<Utc>,
41 pub hz: f64,
43 pub time_scale: f64,
45}
46
47impl WorldClock {
48 pub fn new(hz: f64) -> Self {
50 let now = Utc::now();
51 Self {
52 tick: WorldTick::zero(),
53 epoch: now,
54 wall_time: now,
55 hz,
56 time_scale: 1.0,
57 }
58 }
59
60 pub fn advance(&mut self) {
62 self.tick = self.tick.next();
63 self.wall_time = Utc::now();
64 }
65
66 pub fn elapsed_seconds(&self) -> f64 {
68 let dur = self.wall_time.signed_duration_since(self.epoch);
69 dur.num_milliseconds() as f64 / 1000.0
70 }
71
72 pub fn simulated_seconds(&self) -> f64 {
74 (self.tick.0 as f64 / self.hz) * self.time_scale
75 }
76}
77
78impl Default for WorldClock {
79 fn default() -> Self {
80 Self::new(1.0)
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87
88 #[test]
89 fn starts_at_zero() {
90 let clock = WorldClock::default();
91 assert_eq!(clock.tick, WorldTick::zero());
92 }
93
94 #[test]
95 fn advance_increments() {
96 let mut clock = WorldClock::default();
97 clock.advance();
98 assert_eq!(clock.tick, WorldTick(1));
99 clock.advance();
100 assert_eq!(clock.tick, WorldTick(2));
101 }
102
103 #[test]
104 fn simulated_seconds_at_1hz() {
105 let mut clock = WorldClock::new(1.0);
106 clock.tick = WorldTick(10);
107 assert!((clock.simulated_seconds() - 10.0).abs() < 1e-9);
108 }
109
110 #[test]
111 fn world_tick_ordering() {
112 assert!(WorldTick(0) < WorldTick(1));
113 assert!(WorldTick(100) > WorldTick(42));
114 assert_eq!(WorldTick(7), WorldTick(7));
115 }
116}