Skip to main content

crdt_kit/
gcounter.rs

1use alloc::collections::BTreeMap;
2use alloc::string::String;
3
4use crate::{Crdt, DeltaCrdt};
5
6/// A grow-only counter (G-Counter).
7///
8/// Each replica maintains its own count. The total value is the sum of all
9/// replica counts. This counter can only be incremented, never decremented.
10///
11/// # Example
12///
13/// ```
14/// use crdt_kit::prelude::*;
15///
16/// let mut c1 = GCounter::new("node-1");
17/// c1.increment();
18/// c1.increment();
19///
20/// let mut c2 = GCounter::new("node-2");
21/// c2.increment();
22///
23/// c1.merge(&c2);
24/// assert_eq!(c1.value(), 3);
25/// ```
26#[derive(Debug, Clone, PartialEq, Eq)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
28pub struct GCounter {
29    actor: String,
30    counts: BTreeMap<String, u64>,
31}
32
33impl GCounter {
34    /// Create a new G-Counter for the given actor/replica ID.
35    pub fn new(actor: impl Into<String>) -> Self {
36        Self {
37            actor: actor.into(),
38            counts: BTreeMap::new(),
39        }
40    }
41
42    /// Increment this replica's count by 1.
43    pub fn increment(&mut self) {
44        *self.counts.entry(self.actor.clone()).or_insert(0) += 1;
45    }
46
47    /// Increment this replica's count by `n`.
48    pub fn increment_by(&mut self, n: u64) {
49        *self.counts.entry(self.actor.clone()).or_insert(0) += n;
50    }
51
52    /// Get the total counter value across all replicas.
53    #[must_use]
54    pub fn value(&self) -> u64 {
55        self.counts.values().sum()
56    }
57
58    /// Get this replica's actor ID.
59    #[must_use]
60    pub fn actor(&self) -> &str {
61        &self.actor
62    }
63
64    /// Get the count for a specific actor.
65    #[must_use]
66    pub fn count_for(&self, actor: &str) -> u64 {
67        self.counts.get(actor).copied().unwrap_or(0)
68    }
69}
70
71impl Crdt for GCounter {
72    fn merge(&mut self, other: &Self) {
73        for (actor, &count) in &other.counts {
74            let entry = self.counts.entry(actor.clone()).or_insert(0);
75            *entry = (*entry).max(count);
76        }
77    }
78}
79
80/// Delta for [`GCounter`]: only the entries where `self` is ahead of `other`.
81#[derive(Debug, Clone, PartialEq, Eq)]
82#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
83pub struct GCounterDelta {
84    counts: BTreeMap<String, u64>,
85}
86
87impl DeltaCrdt for GCounter {
88    type Delta = GCounterDelta;
89
90    fn delta(&self, other: &Self) -> GCounterDelta {
91        let mut counts = BTreeMap::new();
92        for (actor, &self_count) in &self.counts {
93            let other_count = other.counts.get(actor).copied().unwrap_or(0);
94            if self_count > other_count {
95                counts.insert(actor.clone(), self_count);
96            }
97        }
98        GCounterDelta { counts }
99    }
100
101    fn apply_delta(&mut self, delta: &GCounterDelta) {
102        for (actor, &count) in &delta.counts {
103            let entry = self.counts.entry(actor.clone()).or_insert(0);
104            *entry = (*entry).max(count);
105        }
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn new_counter_is_zero() {
115        let c = GCounter::new("a");
116        assert_eq!(c.value(), 0);
117    }
118
119    #[test]
120    fn increment_increases_value() {
121        let mut c = GCounter::new("a");
122        c.increment();
123        assert_eq!(c.value(), 1);
124        c.increment();
125        assert_eq!(c.value(), 2);
126    }
127
128    #[test]
129    fn increment_by() {
130        let mut c = GCounter::new("a");
131        c.increment_by(5);
132        assert_eq!(c.value(), 5);
133    }
134
135    #[test]
136    fn merge_takes_max() {
137        let mut c1 = GCounter::new("a");
138        c1.increment();
139        c1.increment();
140
141        let mut c2 = GCounter::new("a");
142        c2.increment();
143
144        // c1 has a=2, c2 has a=1, merge should keep a=2
145        c1.merge(&c2);
146        assert_eq!(c1.value(), 2);
147    }
148
149    #[test]
150    fn merge_different_actors() {
151        let mut c1 = GCounter::new("a");
152        c1.increment();
153
154        let mut c2 = GCounter::new("b");
155        c2.increment();
156        c2.increment();
157
158        c1.merge(&c2);
159        assert_eq!(c1.value(), 3);
160    }
161
162    #[test]
163    fn merge_is_commutative() {
164        let mut c1 = GCounter::new("a");
165        c1.increment();
166
167        let mut c2 = GCounter::new("b");
168        c2.increment();
169        c2.increment();
170
171        let mut left = c1.clone();
172        left.merge(&c2);
173
174        let mut right = c2.clone();
175        right.merge(&c1);
176
177        assert_eq!(left.value(), right.value());
178    }
179
180    #[test]
181    fn merge_is_idempotent() {
182        let mut c1 = GCounter::new("a");
183        c1.increment();
184
185        let mut c2 = GCounter::new("b");
186        c2.increment();
187
188        c1.merge(&c2);
189        let after_first = c1.clone();
190        c1.merge(&c2);
191
192        assert_eq!(c1, after_first);
193    }
194
195    #[test]
196    fn count_for_actor() {
197        let mut c = GCounter::new("a");
198        c.increment();
199        c.increment();
200        assert_eq!(c.count_for("a"), 2);
201        assert_eq!(c.count_for("b"), 0);
202    }
203
204    #[test]
205    fn delta_contains_only_new_entries() {
206        let mut c1 = GCounter::new("a");
207        c1.increment();
208        c1.increment();
209
210        let mut c2 = GCounter::new("b");
211        c2.increment();
212
213        let d = c1.delta(&c2);
214        // c1 has a=2, c2 has b=1 — delta should contain a=2
215        assert_eq!(d.counts.get("a"), Some(&2));
216        assert!(!d.counts.contains_key("b"));
217    }
218
219    #[test]
220    fn apply_delta_updates_state() {
221        let mut c1 = GCounter::new("a");
222        c1.increment();
223        c1.increment();
224
225        let mut c2 = GCounter::new("b");
226        c2.increment();
227
228        let d = c1.delta(&c2);
229        c2.apply_delta(&d);
230        assert_eq!(c2.value(), 3);
231    }
232
233    #[test]
234    fn delta_is_empty_when_equal() {
235        let mut c1 = GCounter::new("a");
236        c1.increment();
237
238        let c2 = c1.clone();
239        let d = c1.delta(&c2);
240        assert!(d.counts.is_empty());
241    }
242
243    #[test]
244    fn delta_equivalent_to_full_merge() {
245        let mut c1 = GCounter::new("a");
246        c1.increment();
247        c1.increment();
248
249        let mut c2 = GCounter::new("b");
250        c2.increment();
251
252        // Full merge path
253        let mut full = c2.clone();
254        full.merge(&c1);
255
256        // Delta path
257        let mut via_delta = c2.clone();
258        let d = c1.delta(&c2);
259        via_delta.apply_delta(&d);
260
261        assert_eq!(full.value(), via_delta.value());
262    }
263}