1use crate::protocol::TimeStateData;
2use crate::sim::error::TimeError;
3use std::time::Instant;
4use tokio::time::Duration;
5
6const MIN_POLL_DELAY_US: f64 = 100.0;
7const MAX_POLL_DELAY_US: f64 = 1_000_000.0;
8
9#[derive(Debug, Clone)]
10pub struct TimeStatus {
11 pub state: TimeStateData,
12 pub elapsed_ticks: u64,
13 pub elapsed_time_us: u64,
14 pub speed: f64,
15}
16
17#[derive(Debug, Clone)]
18pub struct StepResult {
19 pub requested_us: u64,
20 pub advanced_ticks: u64,
21 pub advanced_us: u64,
22}
23
24#[derive(Debug)]
25pub struct TimeEngine {
26 state: TimeStateData,
27 speed: f64,
28 elapsed_ticks: u64,
29 remainder_us: f64,
30 last_wallclock: Option<Instant>,
31}
32
33impl Default for TimeEngine {
34 fn default() -> Self {
35 Self {
36 state: TimeStateData::Paused,
37 speed: 1.0,
38 elapsed_ticks: 0,
39 remainder_us: 0.0,
40 last_wallclock: None,
41 }
42 }
43}
44
45impl TimeEngine {
46 pub fn reset(&mut self) {
47 *self = Self::default();
48 }
49
50 pub fn status(&self, tick_duration_us: u32) -> TimeStatus {
51 let tick_us = tick_duration_us as u64;
52 TimeStatus {
53 state: self.state,
54 elapsed_ticks: self.elapsed_ticks,
55 elapsed_time_us: self.elapsed_ticks.saturating_mul(tick_us),
56 speed: self.speed,
57 }
58 }
59
60 pub fn start(&mut self) -> Result<(), TimeError> {
61 if self.state == TimeStateData::Running {
62 return Err(TimeError::AlreadyRunning);
63 }
64 self.state = TimeStateData::Running;
65 self.last_wallclock = Some(Instant::now());
66 Ok(())
67 }
68
69 pub fn pause(&mut self) -> Result<(), TimeError> {
70 if self.state == TimeStateData::Paused {
71 return Err(TimeError::AlreadyPaused);
72 }
73 self.state = TimeStateData::Paused;
74 self.last_wallclock = None;
75 self.remainder_us = 0.0;
76 Ok(())
77 }
78
79 pub fn set_speed(&mut self, speed: f64) -> Result<(), TimeError> {
80 if !speed.is_finite() || speed <= 0.0 {
81 return Err(TimeError::InvalidSpeed(speed));
82 }
83 self.speed = speed;
84 Ok(())
85 }
86
87 pub fn speed(&self) -> f64 {
88 self.speed
89 }
90
91 pub fn is_running(&self) -> bool {
92 self.state == TimeStateData::Running
93 }
94
95 pub fn step_ticks(
96 &mut self,
97 tick_duration_us: u32,
98 duration_us: u64,
99 ) -> Result<StepResult, TimeError> {
100 if self.state == TimeStateData::Running {
101 return Err(TimeError::StepWhileRunning);
102 }
103 let tick_us = tick_duration_us as u64;
104 let ticks = if tick_us == 0 {
105 0
106 } else {
107 duration_us / tick_us
108 };
109 let advanced_us = ticks.saturating_mul(tick_us);
110 Ok(StepResult {
111 requested_us: duration_us,
112 advanced_ticks: ticks,
113 advanced_us,
114 })
115 }
116
117 pub fn tick_realtime_due(&mut self, tick_duration_us: u32) -> u64 {
118 if self.state != TimeStateData::Running {
119 return 0;
120 }
121 let now = Instant::now();
122 let Some(last) = self.last_wallclock else {
123 self.last_wallclock = Some(now);
124 return 0;
125 };
126 let delta = now.duration_since(last).as_secs_f64() * 1_000_000.0 * self.speed;
127 self.last_wallclock = Some(now);
128 self.remainder_us += delta;
129
130 let tick_us = tick_duration_us as f64;
131 if tick_us <= 0.0 {
132 return 0;
133 }
134 let ticks = (self.remainder_us / tick_us).floor() as u64;
135 if ticks == 0 {
136 return 0;
137 }
138 self.remainder_us -= ticks as f64 * tick_us;
139 ticks
140 }
141
142 pub fn realtime_poll_delay(&self, tick_duration_us: u32) -> Duration {
143 if self.state != TimeStateData::Running {
144 return Duration::from_millis(5);
145 }
146 if tick_duration_us == 0 {
147 return Duration::from_millis(1);
148 }
149 let tick_us = tick_duration_us as f64;
150 let remaining_sim_us = (tick_us - self.remainder_us).max(0.0);
151 let wall_us = if self.speed > 0.0 {
152 (remaining_sim_us / self.speed).ceil()
153 } else {
154 tick_us.ceil()
155 };
156 let clamped_wall_us = wall_us.clamp(MIN_POLL_DELAY_US, MAX_POLL_DELAY_US);
157 Duration::from_micros(clamped_wall_us as u64)
158 }
159
160 pub fn advance_ticks(&mut self, ticks: u64) {
161 self.elapsed_ticks = self.elapsed_ticks.saturating_add(ticks);
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::TimeEngine;
168 use crate::protocol::TimeStateData;
169 use tokio::time::Duration;
170
171 #[test]
172 fn realtime_poll_delay_scales_with_tick_duration() {
173 let engine = TimeEngine {
174 state: TimeStateData::Running,
175 speed: 1.0,
176 remainder_us: 0.0,
177 ..TimeEngine::default()
178 };
179 let delay = engine.realtime_poll_delay(20_000);
180 assert!(
181 delay >= Duration::from_millis(20),
182 "expected >=20ms delay, got {delay:?}"
183 );
184 }
185
186 #[test]
187 fn realtime_poll_delay_is_capped_to_one_second() {
188 let engine = TimeEngine {
189 state: TimeStateData::Running,
190 speed: 0.001,
191 remainder_us: 0.0,
192 ..TimeEngine::default()
193 };
194 let delay = engine.realtime_poll_delay(1_000_000);
195 assert_eq!(
196 delay,
197 Duration::from_secs(1),
198 "poll delay should be capped for extreme slow speeds"
199 );
200 }
201}