1use std::time::{Duration, Instant};
4
5use elara_core::{PerceptualTime, StateTime};
6
7pub struct PerceptualClock {
10 value: PerceptualTime,
12 last_update: Instant,
14}
15
16const MAX_PERCEPTUAL_TICK: Duration = Duration::from_millis(100);
17
18impl PerceptualClock {
19 pub fn new() -> Self {
21 let now = Instant::now();
22 PerceptualClock {
23 value: PerceptualTime::ZERO,
24 last_update: now,
25 }
26 }
27
28 pub fn tick(&mut self) -> PerceptualTime {
31 let now = Instant::now();
32 let elapsed = now.duration_since(self.last_update);
33
34 let clamped = if elapsed > MAX_PERCEPTUAL_TICK {
35 MAX_PERCEPTUAL_TICK
36 } else {
37 elapsed
38 };
39
40 self.value = self.value.saturating_add(clamped);
41 self.last_update = now;
42 self.value
43 }
44
45 pub fn now(&self) -> PerceptualTime {
47 self.value
48 }
49
50 pub fn is_advancing(&self) -> bool {
52 true
54 }
55}
56
57impl Default for PerceptualClock {
58 fn default() -> Self {
59 Self::new()
60 }
61}
62
63pub struct StateClock {
66 value: StateTime,
68 rate: f64,
70 convergence_target: Option<StateTime>,
72 max_correction_per_tick: Duration,
74}
75
76impl StateClock {
77 pub fn new() -> Self {
79 StateClock {
80 value: StateTime::ZERO,
81 rate: 1.0,
82 convergence_target: None,
83 max_correction_per_tick: Duration::from_millis(10),
84 }
85 }
86
87 pub fn advance(&mut self, dt: Duration) -> StateTime {
90 let base_advance_us = (dt.as_micros() as f64 * self.rate) as i64;
92
93 let correction = if let Some(target) = self.convergence_target {
95 let error = target.as_micros() - self.value.as_micros();
96 let max_correction = self.max_correction_per_tick.as_micros() as i64;
97
98 let correction = (error as f64 * 0.1) as i64;
100 correction.clamp(-max_correction, max_correction)
101 } else {
102 0
103 };
104
105 self.value = StateTime::from_micros(self.value.as_micros() + base_advance_us + correction);
106 self.value
107 }
108
109 pub fn now(&self) -> StateTime {
111 self.value
112 }
113
114 pub fn set_convergence_target(&mut self, target: StateTime) {
116 self.convergence_target = Some(target);
117 }
118
119 pub fn clear_convergence_target(&mut self) {
121 self.convergence_target = None;
122 }
123
124 pub fn set_rate(&mut self, rate: f64) {
127 self.rate = rate.clamp(0.5, 2.0);
128 }
129
130 pub fn rate(&self) -> f64 {
132 self.rate
133 }
134
135 pub fn sync_to(&mut self, target: StateTime) {
138 if target > self.value {
139 self.value = target;
140 }
141 }
142}
143
144impl Default for StateClock {
145 fn default() -> Self {
146 Self::new()
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
155 fn test_perceptual_clock_monotonic() {
156 let mut clock = PerceptualClock::new();
157
158 let t1 = clock.tick();
159 std::thread::sleep(Duration::from_millis(10));
160 let t2 = clock.tick();
161
162 assert!(t2 > t1);
163 }
164
165 #[test]
166 fn test_state_clock_advance() {
167 let mut clock = StateClock::new();
168
169 let t1 = clock.now();
170 clock.advance(Duration::from_millis(100));
171 let t2 = clock.now();
172
173 assert!(t2 > t1);
174 let diff = t2.as_micros() - t1.as_micros();
176 assert!((99_000..=101_000).contains(&diff));
177 }
178
179 #[test]
180 fn test_state_clock_rate() {
181 let mut clock = StateClock::new();
182
183 clock.set_rate(2.0);
185 clock.advance(Duration::from_millis(100));
186
187 let value = clock.now().as_micros();
189 assert!((190_000..=210_000).contains(&value));
190 }
191
192 #[test]
193 fn test_state_clock_convergence() {
194 let mut clock = StateClock::new();
195
196 clock.set_convergence_target(StateTime::from_millis(1000));
198
199 for _ in 0..100 {
201 clock.advance(Duration::from_millis(10));
202 }
203
204 let value = clock.now().as_millis();
206 assert!(value > 1000); }
208}