1use std::time::Duration;
4
5use elara_core::{NodeId, PerceptualTime, RealityWindow, StateTime, TimePosition};
6
7use crate::{NetworkModel, PerceptualClock, StateClock};
8
9#[derive(Clone, Debug)]
11pub struct TimeEngineConfig {
12 pub Hp_min: Duration,
14 pub Hp_max: Duration,
16 pub Hc_min: Duration,
18 pub Hc_max: Duration,
20 pub k1_jitter: f64,
22 pub k2_reorder: f64,
24 pub k3_loss: f64,
26 pub k4_jitter_correct: f64,
28 pub tick_interval: Duration,
30}
31
32impl Default for TimeEngineConfig {
33 fn default() -> Self {
34 TimeEngineConfig {
36 Hp_min: Duration::from_millis(40),
37 Hp_max: Duration::from_millis(300),
38 Hc_min: Duration::from_millis(80),
39 Hc_max: Duration::from_millis(600),
40 k1_jitter: 2.5,
41 k2_reorder: 15.0,
42 k3_loss: 150.0,
43 k4_jitter_correct: 2.0,
44 tick_interval: Duration::from_millis(10),
45 }
46 }
47}
48
49impl TimeEngineConfig {
50 pub fn low_bandwidth() -> Self {
52 TimeEngineConfig {
53 Hp_min: Duration::from_millis(80),
54 Hp_max: Duration::from_millis(500),
55 Hc_min: Duration::from_millis(150),
56 Hc_max: Duration::from_millis(1000),
57 k1_jitter: 3.0,
58 k2_reorder: 20.0,
59 k3_loss: 200.0,
60 k4_jitter_correct: 2.5,
61 tick_interval: Duration::from_millis(15),
62 }
63 }
64}
65
66pub struct TimeEngine {
68 perceptual: PerceptualClock,
70 state: StateClock,
72 network: NetworkModel,
74 Hp: Duration,
76 Hc: Duration,
78 config: TimeEngineConfig,
80}
81
82impl TimeEngine {
83 pub fn new() -> Self {
85 Self::with_config(TimeEngineConfig::default())
86 }
87
88 pub fn with_config(config: TimeEngineConfig) -> Self {
90 TimeEngine {
91 perceptual: PerceptualClock::new(),
92 state: StateClock::new(),
93 network: NetworkModel::new(),
94 Hp: config.Hp_min,
95 Hc: config.Hc_min,
96 config,
97 }
98 }
99
100 pub fn tick(&mut self) {
103 self.perceptual.tick();
105
106 self.state.advance(self.config.tick_interval);
108
109 self.adjust_horizons();
111 }
112
113 pub fn τp(&self) -> PerceptualTime {
115 self.perceptual.now()
116 }
117
118 pub fn tau_p(&self) -> PerceptualTime {
119 self.perceptual.now()
120 }
121
122 pub fn τs(&self) -> StateTime {
124 self.state.now()
125 }
126
127 pub fn tau_s(&self) -> StateTime {
128 self.state.now()
129 }
130
131 pub fn Hp(&self) -> Duration {
133 self.Hp
134 }
135
136 pub fn Hc(&self) -> Duration {
138 self.Hc
139 }
140
141 pub fn reality_window(&self) -> RealityWindow {
143 RealityWindow::new(self.state.now(), self.Hc, self.Hp)
144 }
145
146 pub fn classify_time(&self, t: StateTime) -> TimePosition {
148 self.reality_window().classify(t)
149 }
150
151 pub fn update_from_packet(&mut self, peer: NodeId, remote_time: StateTime, seq: u16) {
153 let local_time = self.state.now().as_secs_f64();
154 let remote_time_f = remote_time.as_secs_f64();
155 self.network
156 .update_from_packet(peer, local_time, remote_time_f, seq);
157 }
158
159 pub fn record_reorder(&mut self, depth: u32) {
161 self.network.record_reorder(depth);
162 }
163
164 pub fn record_loss(&mut self, lost: u32, total: u32) {
166 self.network.record_loss(lost, total);
167 }
168
169 pub fn stability_score(&self) -> f64 {
171 self.network.stability_score
172 }
173
174 fn adjust_horizons(&mut self) {
176 let net = &self.network;
177 let cfg = &self.config;
178
179 let Hp_raw = cfg.Hp_min.as_secs_f64()
181 + cfg.k1_jitter * net.jitter
182 + cfg.k2_reorder * net.reorder_depth as f64 * 0.001
183 + cfg.k3_loss * net.loss_rate;
184
185 self.Hp = Duration::from_secs_f64(
186 Hp_raw.clamp(cfg.Hp_min.as_secs_f64(), cfg.Hp_max.as_secs_f64()),
187 );
188
189 let Hc_raw = cfg.Hc_min.as_secs_f64() + cfg.k4_jitter_correct * net.jitter;
191
192 self.Hc = Duration::from_secs_f64(
193 Hc_raw.clamp(cfg.Hc_min.as_secs_f64(), cfg.Hc_max.as_secs_f64()),
194 );
195 }
196
197 pub fn correction_weight(&self, delay: Duration) -> f64 {
200 let Hc = self.Hc.as_secs_f64();
201 let delay_secs = delay.as_secs_f64();
202 (1.0 - delay_secs / Hc).clamp(0.0, 1.0)
203 }
204
205 pub fn network(&self) -> &NetworkModel {
207 &self.network
208 }
209
210 pub fn state_clock_mut(&mut self) -> &mut StateClock {
212 &mut self.state
213 }
214}
215
216impl Default for TimeEngine {
217 fn default() -> Self {
218 Self::new()
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225
226 #[test]
227 fn test_time_engine_tick() {
228 let mut engine = TimeEngine::new();
229
230 let τp1 = engine.tau_p();
231 let τs1 = engine.tau_s();
232
233 engine.tick();
234
235 let τp2 = engine.tau_p();
236 let τs2 = engine.tau_s();
237
238 assert!(τp2 >= τp1);
240 assert!(τs2 >= τs1);
241 }
242
243 #[test]
244 fn test_reality_window() {
245 let engine = TimeEngine::new();
246 let rw = engine.reality_window();
247
248 assert!(rw.left() < rw.τs);
250 assert!(rw.right() > rw.τs);
251 }
252
253 #[test]
254 fn test_horizon_adaptation() {
255 let mut engine = TimeEngine::new();
256
257 let initial_Hp = engine.Hp();
258
259 engine.network.jitter = 0.1; engine.network.loss_rate = 0.1; engine.adjust_horizons();
263
264 assert!(engine.Hp() > initial_Hp);
266 }
267
268 #[test]
269 fn test_correction_weight() {
270 let engine = TimeEngine::new();
271
272 assert!((engine.correction_weight(Duration::ZERO) - 1.0).abs() < 0.01);
274
275 assert!(engine.correction_weight(engine.Hc()) < 0.01);
277
278 let half_Hc = engine.Hc() / 2;
280 let weight = engine.correction_weight(half_Hc);
281 assert!((weight - 0.5).abs() < 0.1);
282 }
283}