use crate::{DeviceId, Timestamp, VersionVector};
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ClockOrdering {
Before,
After,
Equal,
Concurrent,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct VectorClock {
clock: VersionVector,
device_id: DeviceId,
}
impl VectorClock {
pub fn new(device_id: DeviceId) -> Self {
let mut clock = HashMap::new();
clock.insert(device_id.clone(), 0);
Self { clock, device_id }
}
pub fn from_version_vector(device_id: DeviceId, clock: VersionVector) -> Self {
Self { clock, device_id }
}
pub fn tick(&mut self) -> Timestamp {
let entry = self.clock.entry(self.device_id.clone()).or_insert(0);
*entry += 1;
*entry
}
pub fn get_time(&self, device_id: &DeviceId) -> Option<Timestamp> {
self.clock.get(device_id).copied()
}
pub fn current_time(&self) -> Timestamp {
self.clock.get(&self.device_id).copied().unwrap_or(0)
}
pub fn merge(&mut self, other: &VectorClock) {
for (device_id, ×tamp) in &other.clock {
let entry = self.clock.entry(device_id.clone()).or_insert(0);
*entry = (*entry).max(timestamp);
}
}
pub fn compare(&self, other: &VectorClock) -> ClockOrdering {
let mut less_than = false;
let mut greater_than = false;
let mut all_devices = self.clock.keys().collect::<Vec<_>>();
for device in other.clock.keys() {
if !all_devices.contains(&device) {
all_devices.push(device);
}
}
for device_id in all_devices {
let self_time = self.clock.get(device_id).copied().unwrap_or(0);
let other_time = other.clock.get(device_id).copied().unwrap_or(0);
match self_time.cmp(&other_time) {
Ordering::Less => less_than = true,
Ordering::Greater => greater_than = true,
Ordering::Equal => {}
}
}
match (less_than, greater_than) {
(false, false) => ClockOrdering::Equal,
(true, false) => ClockOrdering::Before,
(false, true) => ClockOrdering::After,
(true, true) => ClockOrdering::Concurrent,
}
}
pub fn happened_before(&self, other: &VectorClock) -> bool {
matches!(self.compare(other), ClockOrdering::Before)
}
pub fn happened_after(&self, other: &VectorClock) -> bool {
matches!(self.compare(other), ClockOrdering::After)
}
pub fn is_concurrent(&self, other: &VectorClock) -> bool {
matches!(self.compare(other), ClockOrdering::Concurrent)
}
pub fn device_id(&self) -> &DeviceId {
&self.device_id
}
pub fn as_version_vector(&self) -> &VersionVector {
&self.clock
}
pub fn into_version_vector(self) -> VersionVector {
self.clock
}
pub fn with_device_id(&self, device_id: DeviceId) -> Self {
Self {
clock: self.clock.clone(),
device_id,
}
}
pub fn update(&mut self, device_id: DeviceId, timestamp: Timestamp) {
let entry = self.clock.entry(device_id).or_insert(0);
*entry = (*entry).max(timestamp);
}
pub fn sum(&self) -> Timestamp {
self.clock.values().sum()
}
pub fn is_empty(&self) -> bool {
self.clock.is_empty()
}
pub fn len(&self) -> usize {
self.clock.len()
}
pub fn devices(&self) -> Vec<DeviceId> {
self.clock.keys().cloned().collect()
}
}
impl Default for VectorClock {
fn default() -> Self {
Self::new("default".to_string())
}
}
pub trait CausalOrdering {
fn precedes(&self, other: &Self) -> bool;
fn concurrent_with(&self, other: &Self) -> bool;
}
impl CausalOrdering for VectorClock {
fn precedes(&self, other: &Self) -> bool {
self.happened_before(other)
}
fn concurrent_with(&self, other: &Self) -> bool {
self.is_concurrent(other)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_vector_clock_creation() {
let clock = VectorClock::new("device-1".to_string());
assert_eq!(clock.get_time(&"device-1".to_string()), Some(0));
assert_eq!(clock.device_id(), "device-1");
}
#[test]
fn test_tick() {
let mut clock = VectorClock::new("device-1".to_string());
assert_eq!(clock.tick(), 1);
assert_eq!(clock.tick(), 2);
assert_eq!(clock.get_time(&"device-1".to_string()), Some(2));
}
#[test]
fn test_merge() {
let mut clock1 = VectorClock::new("device-1".to_string());
let mut clock2 = VectorClock::new("device-2".to_string());
clock1.tick();
clock1.tick();
clock2.tick();
clock1.merge(&clock2);
assert_eq!(clock1.get_time(&"device-1".to_string()), Some(2));
assert_eq!(clock1.get_time(&"device-2".to_string()), Some(1));
}
#[test]
fn test_compare_equal() {
let clock1 = VectorClock::new("device-1".to_string());
let clock2 = VectorClock::new("device-1".to_string());
assert_eq!(clock1.compare(&clock2), ClockOrdering::Equal);
}
#[test]
fn test_compare_before() {
let clock1 = VectorClock::new("device-1".to_string());
let mut clock2 = VectorClock::new("device-1".to_string());
clock2.tick();
assert_eq!(clock1.compare(&clock2), ClockOrdering::Before);
assert!(clock1.happened_before(&clock2));
}
#[test]
fn test_compare_after() {
let mut clock1 = VectorClock::new("device-1".to_string());
let clock2 = VectorClock::new("device-1".to_string());
clock1.tick();
assert_eq!(clock1.compare(&clock2), ClockOrdering::After);
assert!(clock1.happened_after(&clock2));
}
#[test]
fn test_compare_concurrent() {
let mut clock1 = VectorClock::new("device-1".to_string());
let mut clock2 = VectorClock::new("device-2".to_string());
clock1.tick();
clock2.tick();
assert_eq!(clock1.compare(&clock2), ClockOrdering::Concurrent);
assert!(clock1.is_concurrent(&clock2));
}
#[test]
fn test_complex_merge() {
let mut clock1 = VectorClock::new("device-1".to_string());
let mut clock2 = VectorClock::new("device-2".to_string());
let mut clock3 = VectorClock::new("device-3".to_string());
clock1.tick();
clock1.tick();
clock2.tick();
clock2.merge(&clock1);
clock2.tick();
clock3.tick();
clock1.merge(&clock2);
clock1.merge(&clock3);
assert_eq!(clock1.get_time(&"device-1".to_string()), Some(2));
assert_eq!(clock1.get_time(&"device-2".to_string()), Some(2));
assert_eq!(clock1.get_time(&"device-3".to_string()), Some(1));
}
}