use crate::crdt::{Crdt, DeviceAware};
use crate::{DeviceId, SyncResult};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GCounter {
counts: HashMap<DeviceId, u64>,
device_id: DeviceId,
}
impl GCounter {
pub fn new(device_id: DeviceId) -> Self {
let mut counts = HashMap::new();
counts.insert(device_id.clone(), 0);
Self { counts, device_id }
}
pub fn increment(&mut self, delta: u64) -> u64 {
let entry = self.counts.entry(self.device_id.clone()).or_insert(0);
*entry = entry.saturating_add(delta);
self.value()
}
pub fn inc(&mut self) -> u64 {
self.increment(1)
}
pub fn value(&self) -> u64 {
self.counts.values().sum()
}
pub fn get_device_count(&self, device_id: &DeviceId) -> u64 {
self.counts.get(device_id).copied().unwrap_or(0)
}
pub fn counts(&self) -> &HashMap<DeviceId, u64> {
&self.counts
}
pub fn device_count(&self) -> usize {
self.counts.len()
}
}
impl Crdt for GCounter {
fn merge(&mut self, other: &Self) -> SyncResult<()> {
for (device_id, &count) in &other.counts {
let entry = self.counts.entry(device_id.clone()).or_insert(0);
*entry = (*entry).max(count);
}
Ok(())
}
fn dominated_by(&self, other: &Self) -> bool {
for (device_id, &count) in &self.counts {
let other_count = other.counts.get(device_id).copied().unwrap_or(0);
if count > other_count {
return false;
}
}
true
}
}
impl DeviceAware for GCounter {
fn device_id(&self) -> &DeviceId {
&self.device_id
}
fn set_device_id(&mut self, device_id: DeviceId) {
if let Some(count) = self.counts.remove(&self.device_id) {
self.counts.insert(device_id.clone(), count);
}
self.device_id = device_id;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_g_counter_creation() {
let counter = GCounter::new("device-1".to_string());
assert_eq!(counter.value(), 0);
assert_eq!(counter.device_id(), "device-1");
}
#[test]
fn test_g_counter_increment() {
let mut counter = GCounter::new("device-1".to_string());
counter.increment(5);
assert_eq!(counter.value(), 5);
counter.increment(3);
assert_eq!(counter.value(), 8);
}
#[test]
fn test_g_counter_inc() {
let mut counter = GCounter::new("device-1".to_string());
counter.inc();
counter.inc();
counter.inc();
assert_eq!(counter.value(), 3);
}
#[test]
fn test_g_counter_merge() {
let mut counter1 = GCounter::new("device-1".to_string());
let mut counter2 = GCounter::new("device-2".to_string());
counter1.increment(5);
counter2.increment(3);
counter1.merge(&counter2).ok();
assert_eq!(counter1.value(), 8);
assert_eq!(counter1.get_device_count(&"device-1".to_string()), 5);
assert_eq!(counter1.get_device_count(&"device-2".to_string()), 3);
}
#[test]
fn test_g_counter_merge_same_device() {
let mut counter1 = GCounter::new("device-1".to_string());
let mut counter2 = GCounter::new("device-1".to_string());
counter1.increment(5);
counter2.increment(3);
counter1.merge(&counter2).ok();
assert_eq!(counter1.value(), 5);
}
#[test]
fn test_g_counter_dominated_by() {
let mut counter1 = GCounter::new("device-1".to_string());
let mut counter2 = GCounter::new("device-1".to_string());
counter1.increment(3);
counter2.increment(5);
assert!(counter1.dominated_by(&counter2));
assert!(!counter2.dominated_by(&counter1));
}
#[test]
fn test_g_counter_device_count() {
let mut counter1 = GCounter::new("device-1".to_string());
let mut counter2 = GCounter::new("device-2".to_string());
let mut counter3 = GCounter::new("device-3".to_string());
counter1.increment(1);
counter2.increment(1);
counter3.increment(1);
counter1.merge(&counter2).ok();
counter1.merge(&counter3).ok();
assert_eq!(counter1.device_count(), 3);
}
}