use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct VectorClock {
clock: HashMap<String, u64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CausalOrder {
Before,
After,
Concurrent,
Equal,
}
impl VectorClock {
pub fn new() -> Self {
Self {
clock: HashMap::new(),
}
}
pub fn from_node(node: &str, time: u64) -> Self {
let mut clock = HashMap::new();
clock.insert(node.to_string(), time);
Self { clock }
}
pub fn increment(&mut self, node: &str) -> u64 {
let entry = self.clock.entry(node.to_string()).or_insert(0);
*entry += 1;
*entry
}
pub fn get(&self, node: &str) -> u64 {
self.clock.get(node).copied().unwrap_or(0)
}
pub fn merge(&mut self, other: &Self) {
for (node, time) in &other.clock {
let entry = self.clock.entry(node.clone()).or_insert(0);
*entry = (*entry).max(*time);
}
}
pub fn compare(&self, other: &Self) -> CausalOrder {
let all_nodes: std::collections::HashSet<&String> =
self.clock.keys().chain(other.clock.keys()).collect();
let mut self_less = false;
let mut other_less = false;
for node in &all_nodes {
let s = self.clock.get(*node).copied().unwrap_or(0);
let o = other.clock.get(*node).copied().unwrap_or(0);
if s < o { self_less = true; }
if s > o { other_less = true; }
}
match (self_less, other_less) {
(false, false) => CausalOrder::Equal,
(true, false) => CausalOrder::Before,
(false, true) => CausalOrder::After,
(true, true) => CausalOrder::Concurrent,
}
}
pub fn happened_before_or_equal(&self, other: &Self) -> bool {
matches!(self.compare(other), CausalOrder::Before | CausalOrder::Equal)
}
pub fn is_concurrent(&self, other: &Self) -> bool {
self.compare(other) == CausalOrder::Concurrent
}
pub fn node_count(&self) -> usize {
self.clock.len()
}
pub fn total_time(&self) -> u64 {
self.clock.values().sum()
}
pub fn nodes(&self) -> Vec<&String> {
self.clock.keys().collect()
}
pub fn to_json(&self) -> String {
serde_json::to_string(self).unwrap_or_default()
}
pub fn from_json(s: &str) -> Option<Self> {
serde_json::from_str(s).ok()
}
}
impl fmt::Display for VectorClock {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut entries: Vec<_> = self.clock.iter().collect();
entries.sort_by_key(|(k, _)| *k);
write!(f, "VC{{")?;
for (i, (node, time)) in entries.iter().enumerate() {
if i > 0 { write!(f, ", ")?; }
write!(f, "{}:{}", node, time)?;
}
write!(f, "}}")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_increment() {
let mut vc = VectorClock::new();
assert_eq!(vc.increment("a"), 1);
assert_eq!(vc.increment("a"), 2);
assert_eq!(vc.increment("b"), 1);
assert_eq!(vc.get("a"), 2);
assert_eq!(vc.get("b"), 1);
assert_eq!(vc.get("c"), 0); }
#[test]
fn test_compare_equal() {
let mut a = VectorClock::new();
a.increment("a");
assert_eq!(a.compare(&a), CausalOrder::Equal);
}
#[test]
fn test_compare_before() {
let mut a = VectorClock::new();
a.increment("a");
let mut b = VectorClock::new();
b.increment("a"); b.increment("a");
assert_eq!(a.compare(&b), CausalOrder::Before);
assert_eq!(b.compare(&a), CausalOrder::After);
}
#[test]
fn test_compare_concurrent() {
let mut a = VectorClock::new();
a.increment("a");
let mut b = VectorClock::new();
b.increment("b");
assert_eq!(a.compare(&b), CausalOrder::Concurrent);
assert!(a.is_concurrent(&b));
}
#[test]
fn test_happened_before() {
let mut a = VectorClock::new();
a.increment("a");
let mut b = a.clone();
b.increment("a");
assert!(a.happened_before_or_equal(&b));
assert!(!b.happened_before_or_equal(&a));
}
#[test]
fn test_merge() {
let mut a = VectorClock::new();
a.increment("a");
let mut b = VectorClock::new();
b.increment("b");
a.merge(&b); assert_eq!(a.get("a"), 1);
assert_eq!(a.get("b"), 1);
}
#[test]
fn test_merge_takes_max() {
let mut a = VectorClock::from_node("x", 5);
let mut b = VectorClock::from_node("x", 10);
a.merge(&b);
assert_eq!(a.get("x"), 10);
}
#[test]
fn test_display() {
let mut vc = VectorClock::new();
vc.increment("forgemaster");
vc.increment("oracle1");
let s = format!("{}", vc);
assert!(s.contains("forgemaster:1"));
assert!(s.contains("oracle1:1"));
}
#[test]
fn test_json_roundtrip() {
let mut vc = VectorClock::new();
vc.increment("a");
vc.increment("b");
let json = vc.to_json();
let restored = VectorClock::from_json(&json).unwrap();
assert_eq!(vc, restored);
}
#[test]
fn test_total_time() {
let mut vc = VectorClock::new();
vc.increment("a"); vc.increment("a"); vc.increment("b"); assert_eq!(vc.total_time(), 3);
}
#[test]
fn test_three_node_causality() {
let mut a = VectorClock::new();
a.increment("a");
let mut b = a.clone();
b.increment("b");
let mut c = b.clone();
c.increment("c");
assert_eq!(a.compare(&c), CausalOrder::Before);
assert_eq!(c.compare(&a), CausalOrder::After);
assert!(a.happened_before_or_equal(&c));
}
}