Skip to main content

crdt_kit/
gcounter.rs

1use std::collections::BTreeMap;
2
3use crate::Crdt;
4
5/// A grow-only counter (G-Counter).
6///
7/// Each replica maintains its own count. The total value is the sum of all
8/// replica counts. This counter can only be incremented, never decremented.
9///
10/// # Example
11///
12/// ```
13/// use crdt_kit::prelude::*;
14///
15/// let mut c1 = GCounter::new("node-1");
16/// c1.increment();
17/// c1.increment();
18///
19/// let mut c2 = GCounter::new("node-2");
20/// c2.increment();
21///
22/// c1.merge(&c2);
23/// assert_eq!(c1.value(), 3);
24/// ```
25#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct GCounter {
27    actor: String,
28    counts: BTreeMap<String, u64>,
29}
30
31impl GCounter {
32    /// Create a new G-Counter for the given actor/replica ID.
33    pub fn new(actor: impl Into<String>) -> Self {
34        Self {
35            actor: actor.into(),
36            counts: BTreeMap::new(),
37        }
38    }
39
40    /// Increment this replica's count by 1.
41    pub fn increment(&mut self) {
42        *self.counts.entry(self.actor.clone()).or_insert(0) += 1;
43    }
44
45    /// Increment this replica's count by `n`.
46    pub fn increment_by(&mut self, n: u64) {
47        *self.counts.entry(self.actor.clone()).or_insert(0) += n;
48    }
49
50    /// Get the total counter value across all replicas.
51    #[must_use]
52    pub fn value(&self) -> u64 {
53        self.counts.values().sum()
54    }
55
56    /// Get this replica's actor ID.
57    #[must_use]
58    pub fn actor(&self) -> &str {
59        &self.actor
60    }
61
62    /// Get the count for a specific actor.
63    #[must_use]
64    pub fn count_for(&self, actor: &str) -> u64 {
65        self.counts.get(actor).copied().unwrap_or(0)
66    }
67}
68
69impl Crdt for GCounter {
70    fn merge(&mut self, other: &Self) {
71        for (actor, &count) in &other.counts {
72            let entry = self.counts.entry(actor.clone()).or_insert(0);
73            *entry = (*entry).max(count);
74        }
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81
82    #[test]
83    fn new_counter_is_zero() {
84        let c = GCounter::new("a");
85        assert_eq!(c.value(), 0);
86    }
87
88    #[test]
89    fn increment_increases_value() {
90        let mut c = GCounter::new("a");
91        c.increment();
92        assert_eq!(c.value(), 1);
93        c.increment();
94        assert_eq!(c.value(), 2);
95    }
96
97    #[test]
98    fn increment_by() {
99        let mut c = GCounter::new("a");
100        c.increment_by(5);
101        assert_eq!(c.value(), 5);
102    }
103
104    #[test]
105    fn merge_takes_max() {
106        let mut c1 = GCounter::new("a");
107        c1.increment();
108        c1.increment();
109
110        let mut c2 = GCounter::new("a");
111        c2.increment();
112
113        // c1 has a=2, c2 has a=1, merge should keep a=2
114        c1.merge(&c2);
115        assert_eq!(c1.value(), 2);
116    }
117
118    #[test]
119    fn merge_different_actors() {
120        let mut c1 = GCounter::new("a");
121        c1.increment();
122
123        let mut c2 = GCounter::new("b");
124        c2.increment();
125        c2.increment();
126
127        c1.merge(&c2);
128        assert_eq!(c1.value(), 3);
129    }
130
131    #[test]
132    fn merge_is_commutative() {
133        let mut c1 = GCounter::new("a");
134        c1.increment();
135
136        let mut c2 = GCounter::new("b");
137        c2.increment();
138        c2.increment();
139
140        let mut left = c1.clone();
141        left.merge(&c2);
142
143        let mut right = c2.clone();
144        right.merge(&c1);
145
146        assert_eq!(left.value(), right.value());
147    }
148
149    #[test]
150    fn merge_is_idempotent() {
151        let mut c1 = GCounter::new("a");
152        c1.increment();
153
154        let mut c2 = GCounter::new("b");
155        c2.increment();
156
157        c1.merge(&c2);
158        let after_first = c1.clone();
159        c1.merge(&c2);
160
161        assert_eq!(c1, after_first);
162    }
163
164    #[test]
165    fn count_for_actor() {
166        let mut c = GCounter::new("a");
167        c.increment();
168        c.increment();
169        assert_eq!(c.count_for("a"), 2);
170        assert_eq!(c.count_for("b"), 0);
171    }
172}