tirea_state/lattice/
g_counter.rs

1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4
5use super::Lattice;
6
7/// A grow-only counter (G-Counter) with per-node tracking.
8///
9/// Each node independently increments its own counter. The total value is the
10/// sum of all per-node counters. Merge takes the per-node maximum.
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
12#[serde(transparent)]
13pub struct GCounter(BTreeMap<String, u64>);
14
15impl GCounter {
16    /// Create an empty counter with value 0.
17    pub fn new() -> Self {
18        Self(BTreeMap::new())
19    }
20
21    /// Increment the counter for the given node by `delta`.
22    pub fn increment(&mut self, node: &str, delta: u64) {
23        let entry = self.0.entry(node.to_string()).or_insert(0);
24        *entry = entry.saturating_add(delta);
25    }
26
27    /// Returns the total counter value (sum of all nodes).
28    pub fn value(&self) -> u64 {
29        self.0.values().sum()
30    }
31
32    /// Returns the counter value for a specific node.
33    pub fn node_value(&self, node: &str) -> u64 {
34        self.0.get(node).copied().unwrap_or(0)
35    }
36}
37
38impl Default for GCounter {
39    fn default() -> Self {
40        Self::new()
41    }
42}
43
44impl Lattice for GCounter {
45    fn merge(&self, other: &Self) -> Self {
46        let mut result = self.0.clone();
47        for (node, &count) in &other.0 {
48            let entry = result.entry(node.clone()).or_insert(0);
49            *entry = (*entry).max(count);
50        }
51        Self(result)
52    }
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58    use crate::lattice::assert_lattice_laws;
59
60    #[test]
61    fn new_is_zero() {
62        let c = GCounter::new();
63        assert_eq!(c.value(), 0);
64    }
65
66    #[test]
67    fn increment_and_value() {
68        let mut c = GCounter::new();
69        c.increment("a", 3);
70        c.increment("b", 5);
71        assert_eq!(c.value(), 8);
72        assert_eq!(c.node_value("a"), 3);
73        assert_eq!(c.node_value("b"), 5);
74        assert_eq!(c.node_value("c"), 0);
75    }
76
77    #[test]
78    fn increment_same_node() {
79        let mut c = GCounter::new();
80        c.increment("a", 3);
81        c.increment("a", 7);
82        assert_eq!(c.node_value("a"), 10);
83        assert_eq!(c.value(), 10);
84    }
85
86    #[test]
87    fn lattice_laws() {
88        let mut a = GCounter::new();
89        a.increment("x", 3);
90
91        let mut b = GCounter::new();
92        b.increment("y", 5);
93
94        let mut c = GCounter::new();
95        c.increment("x", 1);
96        c.increment("y", 2);
97
98        assert_lattice_laws(&a, &b, &c);
99    }
100
101    #[test]
102    fn lattice_laws_overlapping_nodes() {
103        let mut a = GCounter::new();
104        a.increment("n", 10);
105
106        let mut b = GCounter::new();
107        b.increment("n", 7);
108        b.increment("m", 3);
109
110        let mut c = GCounter::new();
111        c.increment("n", 5);
112        c.increment("m", 8);
113
114        assert_lattice_laws(&a, &b, &c);
115    }
116
117    #[test]
118    fn merge_per_node_max() {
119        let mut a = GCounter::new();
120        a.increment("x", 3);
121        a.increment("y", 1);
122
123        let mut b = GCounter::new();
124        b.increment("x", 1);
125        b.increment("y", 5);
126        b.increment("z", 2);
127
128        let merged = a.merge(&b);
129        assert_eq!(merged.node_value("x"), 3);
130        assert_eq!(merged.node_value("y"), 5);
131        assert_eq!(merged.node_value("z"), 2);
132        assert_eq!(merged.value(), 10);
133    }
134
135    #[test]
136    fn merge_empty() {
137        let a = GCounter::new();
138        let mut b = GCounter::new();
139        b.increment("x", 5);
140
141        assert_eq!(a.merge(&b).value(), 5);
142        assert_eq!(b.merge(&a).value(), 5);
143        assert_eq!(a.merge(&a).value(), 0);
144    }
145
146    #[test]
147    fn serde_roundtrip() {
148        let mut c = GCounter::new();
149        c.increment("a", 3);
150        c.increment("b", 5);
151
152        let json = serde_json::to_value(&c).unwrap();
153        assert_eq!(json, serde_json::json!({"a": 3, "b": 5}));
154
155        let back: GCounter = serde_json::from_value(json).unwrap();
156        assert_eq!(back, c);
157    }
158
159    #[test]
160    fn serde_empty() {
161        let c = GCounter::new();
162        let json = serde_json::to_value(&c).unwrap();
163        assert_eq!(json, serde_json::json!({}));
164
165        let back: GCounter = serde_json::from_value(json).unwrap();
166        assert_eq!(back, c);
167    }
168}