use crate::CrdtMerge;
use fxhash::FxHashMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct GCounter {
counts: FxHashMap<String, u64>,
}
impl GCounter {
pub fn new() -> Self {
Self::default()
}
pub fn increment(&mut self, actor: &str, value: u64) {
if value == 0 {
return;
}
let count = self.counts.entry(actor.to_string()).or_insert(0);
*count += value;
}
pub fn value(&self) -> u64 {
self.counts.values().sum()
}
pub fn actor_count(&self, actor: &str) -> u64 {
*self.counts.get(actor).unwrap_or(&0)
}
pub fn counts(&self) -> impl Iterator<Item = (&String, &u64)> {
self.counts.iter()
}
}
impl CrdtMerge for GCounter {
fn merge(&mut self, other: &Self) {
for (actor, other_count) in &other.counts {
let count = self.counts.entry(actor.clone()).or_insert(0);
if *other_count > *count {
*count = *other_count;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_increment() {
let mut gc = GCounter::new();
gc.increment("A", 5);
gc.increment("B", 3);
gc.increment("A", 2);
assert_eq!(gc.value(), 10);
assert_eq!(gc.actor_count("A"), 7);
assert_eq!(gc.actor_count("B"), 3);
}
#[test]
fn test_merge() {
let mut a = GCounter::new();
a.increment("A", 5);
a.increment("B", 2);
let mut b = GCounter::new();
b.increment("B", 7);
b.increment("C", 3);
a.merge(&b);
assert_eq!(a.actor_count("A"), 5);
assert_eq!(a.actor_count("B"), 7);
assert_eq!(a.actor_count("C"), 3);
assert_eq!(a.value(), 15);
}
#[test]
fn test_merge_idempotency() {
let mut a = GCounter::new();
a.increment("A", 5);
let b = a.clone();
a.merge(&b);
assert_eq!(a, b);
}
#[test]
fn test_merge_commutativity() {
let mut a = GCounter::new();
a.increment("A", 5);
a.increment("B", 2);
let mut b = GCounter::new();
b.increment("B", 7);
b.increment("C", 3);
let mut a_merge_b = a.clone();
a_merge_b.merge(&b);
let mut b_merge_a = b.clone();
b_merge_a.merge(&a);
assert_eq!(a_merge_b, b_merge_a);
}
}