Skip to main content

crdt_kit/
gcounter.rs

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