use crate::network::NodeId;
use std::collections::BTreeMap;
pub type Micros = u64;
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum ClockError {
#[error("cannot move virtual clock backwards from {now} to {target}")]
Backwards {
now: Micros,
target: Micros,
},
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct SkewSchedule {
segments: Vec<(Micros, i64)>,
}
impl SkewSchedule {
pub fn constant(skew_micros: i64) -> Self {
Self {
segments: vec![(0, skew_micros)],
}
}
pub fn piecewise(segments: Vec<(Micros, i64)>) -> Self {
assert!(
segments.windows(2).all(|w| w[0].0 < w[1].0),
"skew schedule segments must have strictly increasing start times"
);
Self { segments }
}
pub fn skew_at(&self, at: Micros) -> i64 {
let mut skew = 0;
for &(start, value) in &self.segments {
if start > at {
break;
}
skew = value;
}
skew
}
}
#[derive(Debug, Default)]
pub struct Clock {
now: Micros,
skews: BTreeMap<NodeId, SkewSchedule>,
}
impl Clock {
pub fn new() -> Self {
Self::default()
}
pub fn now(&self) -> Micros {
self.now
}
pub fn advance_to(&mut self, target: Micros) -> Result<(), ClockError> {
if target < self.now {
return Err(ClockError::Backwards {
now: self.now,
target,
});
}
self.now = target;
Ok(())
}
pub fn advance_by(&mut self, delta: Micros) -> Micros {
self.now += delta;
self.now
}
pub fn set_skew(&mut self, node: NodeId, schedule: SkewSchedule) {
self.skews.insert(node, schedule);
}
pub fn node_now(&self, node: NodeId) -> i64 {
let skew = self.skews.get(&node).map_or(0, |s| s.skew_at(self.now));
self.now as i64 + skew
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn clock_only_moves_forward() {
let mut clock = Clock::new();
assert_eq!(clock.now(), 0);
assert_eq!(clock.advance_by(100), 100);
clock.advance_to(250).unwrap();
clock.advance_to(250).unwrap();
assert_eq!(
clock.advance_to(100),
Err(ClockError::Backwards {
now: 250,
target: 100
})
);
}
#[test]
fn piecewise_skew_switches_at_segment_starts() {
let schedule = SkewSchedule::piecewise(vec![(100, -50), (500, 200)]);
assert_eq!(schedule.skew_at(0), 0);
assert_eq!(schedule.skew_at(99), 0);
assert_eq!(schedule.skew_at(100), -50);
assert_eq!(schedule.skew_at(499), -50);
assert_eq!(schedule.skew_at(500), 200);
}
#[test]
#[should_panic(expected = "strictly increasing")]
fn piecewise_rejects_unsorted_segments() {
let _ = SkewSchedule::piecewise(vec![(100, 1), (50, 2)]);
}
#[test]
fn node_now_applies_constant_skew() {
let node = NodeId(7);
let mut clock = Clock::new();
clock.set_skew(node, SkewSchedule::constant(250));
assert_eq!(clock.node_now(node), 250);
clock.advance_by(100);
assert_eq!(clock.node_now(node), 350);
assert_eq!(clock.node_now(NodeId(8)), 100);
}
}