use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct LogicalClock {
counter: Arc<AtomicU64>,
}
impl LogicalClock {
pub fn new() -> Self {
Self {
counter: Arc::new(AtomicU64::new(0)),
}
}
pub fn tick(&self) -> u64 {
self.counter.fetch_add(1, Ordering::SeqCst) + 1
}
pub fn current(&self) -> u64 {
self.counter.load(Ordering::SeqCst)
}
pub fn advance_to(&self, target: u64) {
loop {
let current = self.counter.load(Ordering::SeqCst);
if current >= target {
break;
}
if self
.counter
.compare_exchange(current, target, Ordering::SeqCst, Ordering::SeqCst)
.is_ok()
{
break;
}
}
}
pub fn merge(&self, other: u64) {
loop {
let current = self.counter.load(Ordering::SeqCst);
let new_val = current.max(other) + 1;
if self
.counter
.compare_exchange(current, new_val, Ordering::SeqCst, Ordering::SeqCst)
.is_ok()
{
break;
}
}
}
}
impl Default for LogicalClock {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::LogicalClock;
#[test]
fn new_starts_at_zero() {
assert_eq!(LogicalClock::new().current(), 0);
}
#[test]
fn tick_is_monotonic() {
let clock = LogicalClock::new();
assert_eq!(clock.tick(), 1);
assert_eq!(clock.tick(), 2);
assert_eq!(clock.tick(), 3);
}
#[test]
fn merge_takes_max_plus_one() {
let clock = LogicalClock::new();
clock.merge(10);
assert_eq!(clock.current(), 11);
clock.merge(100);
assert_eq!(clock.current(), 101);
clock.merge(1);
assert_eq!(clock.current(), 102);
}
#[test]
fn clone_shares_counter() {
let clock = LogicalClock::new();
let clone = clock.clone();
clone.tick();
assert_eq!(clock.current(), 1);
}
#[test]
fn advance_to_aligns_to_max_without_plus_one() {
let clock = LogicalClock::new();
clock.advance_to(42);
assert_eq!(clock.current(), 42);
clock.advance_to(1);
assert_eq!(clock.current(), 42);
clock.advance_to(42);
assert_eq!(clock.current(), 42);
assert_eq!(clock.tick(), 43);
}
}