Skip to main content

crdt_kit/
pncounter.rs

1use crate::{Crdt, DeltaCrdt, GCounter, GCounterDelta, NodeId};
2
3/// A positive-negative counter (PN-Counter).
4///
5/// Supports both increment and decrement operations by maintaining two
6/// internal G-Counters: one for increments and one for decrements.
7/// The value is `increments - decrements`.
8///
9/// # Example
10///
11/// ```
12/// use crdt_kit::prelude::*;
13///
14/// let mut c1 = PNCounter::new(1);
15/// c1.increment();
16/// c1.increment();
17/// c1.decrement();
18/// assert_eq!(c1.value(), 1);
19///
20/// let mut c2 = PNCounter::new(2);
21/// c2.decrement();
22///
23/// c1.merge(&c2);
24/// assert_eq!(c1.value(), 0);
25/// ```
26#[derive(Debug, Clone, PartialEq, Eq)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
28pub struct PNCounter {
29    increments: GCounter,
30    decrements: GCounter,
31}
32
33impl PNCounter {
34    /// Create a new PN-Counter for the given node.
35    pub fn new(actor: NodeId) -> Self {
36        Self {
37            increments: GCounter::new(actor),
38            decrements: GCounter::new(actor),
39        }
40    }
41
42    /// Increment the counter by 1.
43    pub fn increment(&mut self) {
44        self.increments.increment();
45    }
46
47    /// Decrement the counter by 1.
48    pub fn decrement(&mut self) {
49        self.decrements.increment();
50    }
51
52    /// Get the current counter value (increments - decrements).
53    #[must_use]
54    pub fn value(&self) -> i64 {
55        self.increments.value() as i64 - self.decrements.value() as i64
56    }
57}
58
59impl Crdt for PNCounter {
60    fn merge(&mut self, other: &Self) {
61        self.increments.merge(&other.increments);
62        self.decrements.merge(&other.decrements);
63    }
64}
65
66/// Delta for [`PNCounter`]: deltas for both the increment and decrement counters.
67#[derive(Debug, Clone, PartialEq, Eq)]
68#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
69pub struct PNCounterDelta {
70    increments: GCounterDelta,
71    decrements: GCounterDelta,
72}
73
74impl DeltaCrdt for PNCounter {
75    type Delta = PNCounterDelta;
76
77    fn delta(&self, other: &Self) -> PNCounterDelta {
78        PNCounterDelta {
79            increments: self.increments.delta(&other.increments),
80            decrements: self.decrements.delta(&other.decrements),
81        }
82    }
83
84    fn apply_delta(&mut self, delta: &PNCounterDelta) {
85        self.increments.apply_delta(&delta.increments);
86        self.decrements.apply_delta(&delta.decrements);
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn new_counter_is_zero() {
96        let c = PNCounter::new(1);
97        assert_eq!(c.value(), 0);
98    }
99
100    #[test]
101    fn increment_and_decrement() {
102        let mut c = PNCounter::new(1);
103        c.increment();
104        c.increment();
105        c.decrement();
106        assert_eq!(c.value(), 1);
107    }
108
109    #[test]
110    fn can_go_negative() {
111        let mut c = PNCounter::new(1);
112        c.decrement();
113        c.decrement();
114        assert_eq!(c.value(), -2);
115    }
116
117    #[test]
118    fn merge_different_actors() {
119        let mut c1 = PNCounter::new(1);
120        c1.increment();
121        c1.increment();
122
123        let mut c2 = PNCounter::new(2);
124        c2.decrement();
125
126        c1.merge(&c2);
127        assert_eq!(c1.value(), 1);
128    }
129
130    #[test]
131    fn merge_is_commutative() {
132        let mut c1 = PNCounter::new(1);
133        c1.increment();
134
135        let mut c2 = PNCounter::new(2);
136        c2.decrement();
137        c2.decrement();
138
139        let mut left = c1.clone();
140        left.merge(&c2);
141
142        let mut right = c2.clone();
143        right.merge(&c1);
144
145        assert_eq!(left.value(), right.value());
146    }
147
148    #[test]
149    fn merge_is_idempotent() {
150        let mut c1 = PNCounter::new(1);
151        c1.increment();
152
153        let mut c2 = PNCounter::new(2);
154        c2.decrement();
155
156        c1.merge(&c2);
157        let after_first = c1.clone();
158        c1.merge(&c2);
159
160        assert_eq!(c1, after_first);
161    }
162
163    #[test]
164    fn delta_apply_equivalent_to_merge() {
165        let mut c1 = PNCounter::new(1);
166        c1.increment();
167        c1.increment();
168        c1.decrement();
169
170        let mut c2 = PNCounter::new(2);
171        c2.decrement();
172
173        let mut full = c2.clone();
174        full.merge(&c1);
175
176        let mut via_delta = c2.clone();
177        let d = c1.delta(&c2);
178        via_delta.apply_delta(&d);
179
180        assert_eq!(full.value(), via_delta.value());
181    }
182}