use std::time::Duration;
use elara_core::{NodeId, PerceptualTime, RealityWindow, StateTime, TimePosition};
use crate::{NetworkModel, PerceptualClock, StateClock};
#[derive(Clone, Debug)]
pub struct TimeEngineConfig {
pub Hp_min: Duration,
pub Hp_max: Duration,
pub Hc_min: Duration,
pub Hc_max: Duration,
pub k1_jitter: f64,
pub k2_reorder: f64,
pub k3_loss: f64,
pub k4_jitter_correct: f64,
pub tick_interval: Duration,
}
impl Default for TimeEngineConfig {
fn default() -> Self {
TimeEngineConfig {
Hp_min: Duration::from_millis(40),
Hp_max: Duration::from_millis(300),
Hc_min: Duration::from_millis(80),
Hc_max: Duration::from_millis(600),
k1_jitter: 2.5,
k2_reorder: 15.0,
k3_loss: 150.0,
k4_jitter_correct: 2.0,
tick_interval: Duration::from_millis(10),
}
}
}
impl TimeEngineConfig {
pub fn low_bandwidth() -> Self {
TimeEngineConfig {
Hp_min: Duration::from_millis(80),
Hp_max: Duration::from_millis(500),
Hc_min: Duration::from_millis(150),
Hc_max: Duration::from_millis(1000),
k1_jitter: 3.0,
k2_reorder: 20.0,
k3_loss: 200.0,
k4_jitter_correct: 2.5,
tick_interval: Duration::from_millis(15),
}
}
}
pub struct TimeEngine {
perceptual: PerceptualClock,
state: StateClock,
network: NetworkModel,
Hp: Duration,
Hc: Duration,
config: TimeEngineConfig,
}
impl TimeEngine {
pub fn new() -> Self {
Self::with_config(TimeEngineConfig::default())
}
pub fn with_config(config: TimeEngineConfig) -> Self {
TimeEngine {
perceptual: PerceptualClock::new(),
state: StateClock::new(),
network: NetworkModel::new(),
Hp: config.Hp_min,
Hc: config.Hc_min,
config,
}
}
pub fn tick(&mut self) {
self.perceptual.tick();
self.state.advance(self.config.tick_interval);
self.adjust_horizons();
}
pub fn τp(&self) -> PerceptualTime {
self.perceptual.now()
}
pub fn tau_p(&self) -> PerceptualTime {
self.perceptual.now()
}
pub fn τs(&self) -> StateTime {
self.state.now()
}
pub fn tau_s(&self) -> StateTime {
self.state.now()
}
pub fn Hp(&self) -> Duration {
self.Hp
}
pub fn Hc(&self) -> Duration {
self.Hc
}
pub fn reality_window(&self) -> RealityWindow {
RealityWindow::new(self.state.now(), self.Hc, self.Hp)
}
pub fn classify_time(&self, t: StateTime) -> TimePosition {
self.reality_window().classify(t)
}
pub fn update_from_packet(&mut self, peer: NodeId, remote_time: StateTime, seq: u16) {
let local_time = self.state.now().as_secs_f64();
let remote_time_f = remote_time.as_secs_f64();
self.network
.update_from_packet(peer, local_time, remote_time_f, seq);
}
pub fn record_reorder(&mut self, depth: u32) {
self.network.record_reorder(depth);
}
pub fn record_loss(&mut self, lost: u32, total: u32) {
self.network.record_loss(lost, total);
}
pub fn stability_score(&self) -> f64 {
self.network.stability_score
}
fn adjust_horizons(&mut self) {
let net = &self.network;
let cfg = &self.config;
let Hp_raw = cfg.Hp_min.as_secs_f64()
+ cfg.k1_jitter * net.jitter
+ cfg.k2_reorder * net.reorder_depth as f64 * 0.001
+ cfg.k3_loss * net.loss_rate;
self.Hp = Duration::from_secs_f64(
Hp_raw.clamp(cfg.Hp_min.as_secs_f64(), cfg.Hp_max.as_secs_f64()),
);
let Hc_raw = cfg.Hc_min.as_secs_f64() + cfg.k4_jitter_correct * net.jitter;
self.Hc = Duration::from_secs_f64(
Hc_raw.clamp(cfg.Hc_min.as_secs_f64(), cfg.Hc_max.as_secs_f64()),
);
}
pub fn correction_weight(&self, delay: Duration) -> f64 {
let Hc = self.Hc.as_secs_f64();
let delay_secs = delay.as_secs_f64();
(1.0 - delay_secs / Hc).clamp(0.0, 1.0)
}
pub fn network(&self) -> &NetworkModel {
&self.network
}
pub fn state_clock_mut(&mut self) -> &mut StateClock {
&mut self.state
}
pub fn drift_ms(&self) -> i64 {
0
}
}
impl Default for TimeEngine {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_time_engine_tick() {
let mut engine = TimeEngine::new();
let τp1 = engine.tau_p();
let τs1 = engine.tau_s();
engine.tick();
let τp2 = engine.tau_p();
let τs2 = engine.tau_s();
assert!(τp2 >= τp1);
assert!(τs2 >= τs1);
}
#[test]
fn test_reality_window() {
let engine = TimeEngine::new();
let rw = engine.reality_window();
assert!(rw.left() < rw.τs);
assert!(rw.right() > rw.τs);
}
#[test]
fn test_horizon_adaptation() {
let mut engine = TimeEngine::new();
let initial_Hp = engine.Hp();
engine.network.jitter = 0.1; engine.network.loss_rate = 0.1; engine.adjust_horizons();
assert!(engine.Hp() > initial_Hp);
}
#[test]
fn test_correction_weight() {
let engine = TimeEngine::new();
assert!((engine.correction_weight(Duration::ZERO) - 1.0).abs() < 0.01);
assert!(engine.correction_weight(engine.Hc()) < 0.01);
let half_Hc = engine.Hc() / 2;
let weight = engine.correction_weight(half_Hc);
assert!((weight - 0.5).abs() < 0.1);
}
}