use std::collections::HashMap;
use std::time::Duration;
use elara_core::{NodeId, PerceptualTime, RealityWindow, StateTime, TimePosition};
use elara_time::{TimeEngine, TimeEngineConfig};
use rand::rngs::StdRng;
use rand::{Rng, SeedableRng};
use crate::chaos::{ChaosConfig, ChaosNetwork};
#[derive(Clone, Debug)]
pub struct ClockDriftModel {
pub drift_rate: f64,
pub jitter_us: u32,
accumulated_drift: i64,
}
impl ClockDriftModel {
pub fn new(drift_rate: f64, jitter_us: u32) -> Self {
ClockDriftModel {
drift_rate,
jitter_us,
accumulated_drift: 0,
}
}
pub fn perfect() -> Self {
Self::new(1.0, 0)
}
pub fn fast() -> Self {
Self::new(1.0001, 50)
}
pub fn slow() -> Self {
Self::new(0.9999, 50)
}
pub fn unstable() -> Self {
Self::new(1.0, 500)
}
pub fn apply(&mut self, dt: Duration, rng: &mut StdRng) -> Duration {
let base_us = dt.as_micros() as f64;
let drifted_us = base_us * self.drift_rate;
let jitter = if self.jitter_us > 0 {
rng.gen_range(-(self.jitter_us as i32)..=self.jitter_us as i32) as f64
} else {
0.0
};
let final_us = (drifted_us + jitter).max(0.0) as u64;
self.accumulated_drift += final_us as i64 - dt.as_micros() as i64;
Duration::from_micros(final_us)
}
pub fn accumulated_drift(&self) -> Duration {
Duration::from_micros(self.accumulated_drift.unsigned_abs())
}
}
pub struct SimulatedNode {
pub node_id: NodeId,
pub time_engine: TimeEngine,
pub drift_model: ClockDriftModel,
rng: StdRng,
tick_count: u64,
}
impl SimulatedNode {
pub fn new(
node_id: NodeId,
config: TimeEngineConfig,
drift: ClockDriftModel,
seed: u64,
) -> Self {
SimulatedNode {
node_id,
time_engine: TimeEngine::with_config(config),
drift_model: drift,
rng: StdRng::seed_from_u64(seed),
tick_count: 0,
}
}
pub fn tick(&mut self, real_dt: Duration) {
let _local_dt = self.drift_model.apply(real_dt, &mut self.rng);
self.time_engine.tick();
self.tick_count += 1;
}
pub fn tau_p(&self) -> PerceptualTime {
self.time_engine.tau_p()
}
pub fn tau_s(&self) -> StateTime {
self.time_engine.tau_s()
}
pub fn reality_window(&self) -> RealityWindow {
self.time_engine.reality_window()
}
pub fn classify(&self, t: StateTime) -> TimePosition {
self.time_engine.classify_time(t)
}
pub fn tick_count(&self) -> u64 {
self.tick_count
}
}
pub struct TimeSimulator {
nodes: HashMap<NodeId, SimulatedNode>,
networks: HashMap<(NodeId, NodeId), ChaosNetwork>,
global_time: Duration,
tick_interval: Duration,
seed_counter: u64,
}
impl TimeSimulator {
pub fn new(tick_interval: Duration) -> Self {
TimeSimulator {
nodes: HashMap::new(),
networks: HashMap::new(),
global_time: Duration::ZERO,
tick_interval,
seed_counter: 0,
}
}
pub fn add_node(&mut self, node_id: NodeId) {
self.add_node_with_drift(node_id, ClockDriftModel::perfect());
}
pub fn add_node_with_drift(&mut self, node_id: NodeId, drift: ClockDriftModel) {
let seed = self.seed_counter;
self.seed_counter += 1;
let node = SimulatedNode::new(node_id, TimeEngineConfig::default(), drift, seed);
self.nodes.insert(node_id, node);
}
pub fn set_network(&mut self, from: NodeId, to: NodeId, config: ChaosConfig) {
let seed = self.seed_counter;
self.seed_counter += 1;
self.networks
.insert((from, to), ChaosNetwork::with_seed(config, seed));
}
pub fn run(&mut self, duration: Duration) -> SimulationResult {
let mut result = SimulationResult::new();
let ticks = (duration.as_micros() / self.tick_interval.as_micros()) as u64;
for _ in 0..ticks {
self.tick(&mut result);
}
result
}
fn tick(&mut self, result: &mut SimulationResult) {
self.global_time += self.tick_interval;
for node in self.nodes.values_mut() {
node.tick(self.tick_interval);
}
result.record_tick(self);
self.simulate_time_sync();
}
fn simulate_time_sync(&mut self) {
let node_ids: Vec<NodeId> = self.nodes.keys().copied().collect();
for from in &node_ids {
for to in &node_ids {
if from == to {
continue;
}
let sender_time = self.nodes.get(from).map(|n| n.tau_s());
if let Some(sender_time) = sender_time {
if let Some(receiver) = self.nodes.get_mut(to) {
receiver.time_engine.update_from_packet(
*from,
sender_time,
0, );
}
}
}
}
}
pub fn node(&self, id: NodeId) -> Option<&SimulatedNode> {
self.nodes.get(&id)
}
pub fn node_mut(&mut self, id: NodeId) -> Option<&mut SimulatedNode> {
self.nodes.get_mut(&id)
}
pub fn global_time(&self) -> Duration {
self.global_time
}
pub fn node_ids(&self) -> Vec<NodeId> {
self.nodes.keys().copied().collect()
}
}
#[derive(Debug, Default)]
pub struct SimulationResult {
pub total_ticks: u64,
pub max_divergence_us: i64,
pub avg_divergence_us: f64,
divergence_samples: Vec<i64>,
pub horizon_changes: u32,
pub classifications: HashMap<TimePosition, u64>,
}
impl SimulationResult {
pub fn new() -> Self {
SimulationResult::default()
}
fn record_tick(&mut self, sim: &TimeSimulator) {
self.total_ticks += 1;
let nodes: Vec<_> = sim.nodes.values().collect();
if nodes.len() >= 2 {
for i in 0..nodes.len() {
for j in (i + 1)..nodes.len() {
let t1 = nodes[i].tau_s().as_micros();
let t2 = nodes[j].tau_s().as_micros();
let divergence = (t1 - t2).abs();
self.divergence_samples.push(divergence);
self.max_divergence_us = self.max_divergence_us.max(divergence);
}
}
}
}
pub fn finalize(&mut self) {
if !self.divergence_samples.is_empty() {
let sum: i64 = self.divergence_samples.iter().sum();
self.avg_divergence_us = sum as f64 / self.divergence_samples.len() as f64;
}
}
pub fn max_divergence_ms(&self) -> f64 {
self.max_divergence_us as f64 / 1000.0
}
pub fn avg_divergence_ms(&self) -> f64 {
self.avg_divergence_us / 1000.0
}
}
pub mod scenarios {
use super::*;
pub fn perfect_pair() -> TimeSimulator {
let mut sim = TimeSimulator::new(Duration::from_millis(10));
sim.add_node(NodeId::new(1));
sim.add_node(NodeId::new(2));
sim
}
pub fn drifting_pair() -> TimeSimulator {
let mut sim = TimeSimulator::new(Duration::from_millis(10));
sim.add_node_with_drift(NodeId::new(1), ClockDriftModel::fast());
sim.add_node_with_drift(NodeId::new(2), ClockDriftModel::slow());
sim
}
pub fn small_swarm(count: usize) -> TimeSimulator {
let mut sim = TimeSimulator::new(Duration::from_millis(10));
for i in 0..count {
let drift = match i % 4 {
0 => ClockDriftModel::perfect(),
1 => ClockDriftModel::fast(),
2 => ClockDriftModel::slow(),
_ => ClockDriftModel::unstable(),
};
sim.add_node_with_drift(NodeId::new(i as u64), drift);
}
sim
}
pub fn hostile_network() -> TimeSimulator {
let mut sim = TimeSimulator::new(Duration::from_millis(10));
let node1 = NodeId::new(1);
let node2 = NodeId::new(2);
sim.add_node_with_drift(node1, ClockDriftModel::unstable());
sim.add_node_with_drift(node2, ClockDriftModel::unstable());
sim.set_network(node1, node2, ChaosConfig::hostile());
sim.set_network(node2, node1, ChaosConfig::hostile());
sim
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_perfect_clocks() {
let mut sim = scenarios::perfect_pair();
let mut result = sim.run(Duration::from_secs(10));
result.finalize();
println!(
"Perfect clocks - Max divergence: {:.3}ms",
result.max_divergence_ms()
);
assert!(result.max_divergence_ms() < 100.0);
}
#[test]
fn test_drifting_clocks() {
let mut sim = scenarios::drifting_pair();
let mut result = sim.run(Duration::from_secs(60));
result.finalize();
println!(
"Drifting clocks - Max divergence: {:.3}ms",
result.max_divergence_ms()
);
}
#[test]
fn test_small_swarm() {
let mut sim = scenarios::small_swarm(5);
let mut result = sim.run(Duration::from_secs(30));
result.finalize();
println!(
"Small swarm - Max divergence: {:.3}ms, Avg: {:.3}ms",
result.max_divergence_ms(),
result.avg_divergence_ms()
);
}
#[test]
fn test_clock_drift_model() {
let mut rng = StdRng::seed_from_u64(42);
let mut drift = ClockDriftModel::fast();
for _ in 0..1000 {
drift.apply(Duration::from_millis(10), &mut rng);
}
println!("Accumulated drift: {:?}", drift.accumulated_drift());
assert!(drift.accumulated_drift() > Duration::ZERO);
}
#[test]
fn test_reality_window_classification() {
let mut sim = scenarios::perfect_pair();
sim.run(Duration::from_secs(1));
let node = sim.node(NodeId::new(1)).unwrap();
let rw = node.reality_window();
let current = node.tau_s();
assert!(rw.contains(current));
assert_eq!(node.classify(current), TimePosition::Current);
let past = StateTime::from_millis(current.as_millis() - 10000);
assert_eq!(node.classify(past), TimePosition::TooLate);
let future = StateTime::from_millis(current.as_millis() + 10000);
assert_eq!(node.classify(future), TimePosition::TooEarly);
}
}