oxigdal_sync/crdt/
g_counter.rs1use crate::crdt::{Crdt, DeviceAware};
6use crate::{DeviceId, SyncResult};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
31pub struct GCounter {
32 counts: HashMap<DeviceId, u64>,
34 device_id: DeviceId,
36}
37
38impl GCounter {
39 pub fn new(device_id: DeviceId) -> Self {
45 let mut counts = HashMap::new();
46 counts.insert(device_id.clone(), 0);
47
48 Self { counts, device_id }
49 }
50
51 pub fn increment(&mut self, delta: u64) -> u64 {
61 let entry = self.counts.entry(self.device_id.clone()).or_insert(0);
62 *entry = entry.saturating_add(delta);
63 self.value()
64 }
65
66 pub fn inc(&mut self) -> u64 {
72 self.increment(1)
73 }
74
75 pub fn value(&self) -> u64 {
79 self.counts.values().sum()
80 }
81
82 pub fn get_device_count(&self, device_id: &DeviceId) -> u64 {
92 self.counts.get(device_id).copied().unwrap_or(0)
93 }
94
95 pub fn counts(&self) -> &HashMap<DeviceId, u64> {
97 &self.counts
98 }
99
100 pub fn device_count(&self) -> usize {
102 self.counts.len()
103 }
104}
105
106impl Crdt for GCounter {
107 fn merge(&mut self, other: &Self) -> SyncResult<()> {
108 for (device_id, &count) in &other.counts {
109 let entry = self.counts.entry(device_id.clone()).or_insert(0);
110 *entry = (*entry).max(count);
111 }
112 Ok(())
113 }
114
115 fn dominated_by(&self, other: &Self) -> bool {
116 for (device_id, &count) in &self.counts {
117 let other_count = other.counts.get(device_id).copied().unwrap_or(0);
118 if count > other_count {
119 return false;
120 }
121 }
122 true
123 }
124}
125
126impl DeviceAware for GCounter {
127 fn device_id(&self) -> &DeviceId {
128 &self.device_id
129 }
130
131 fn set_device_id(&mut self, device_id: DeviceId) {
132 if let Some(count) = self.counts.remove(&self.device_id) {
134 self.counts.insert(device_id.clone(), count);
135 }
136 self.device_id = device_id;
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143
144 #[test]
145 fn test_g_counter_creation() {
146 let counter = GCounter::new("device-1".to_string());
147 assert_eq!(counter.value(), 0);
148 assert_eq!(counter.device_id(), "device-1");
149 }
150
151 #[test]
152 fn test_g_counter_increment() {
153 let mut counter = GCounter::new("device-1".to_string());
154 counter.increment(5);
155 assert_eq!(counter.value(), 5);
156 counter.increment(3);
157 assert_eq!(counter.value(), 8);
158 }
159
160 #[test]
161 fn test_g_counter_inc() {
162 let mut counter = GCounter::new("device-1".to_string());
163 counter.inc();
164 counter.inc();
165 counter.inc();
166 assert_eq!(counter.value(), 3);
167 }
168
169 #[test]
170 fn test_g_counter_merge() {
171 let mut counter1 = GCounter::new("device-1".to_string());
172 let mut counter2 = GCounter::new("device-2".to_string());
173
174 counter1.increment(5);
175 counter2.increment(3);
176
177 counter1.merge(&counter2).ok();
178
179 assert_eq!(counter1.value(), 8);
180 assert_eq!(counter1.get_device_count(&"device-1".to_string()), 5);
181 assert_eq!(counter1.get_device_count(&"device-2".to_string()), 3);
182 }
183
184 #[test]
185 fn test_g_counter_merge_same_device() {
186 let mut counter1 = GCounter::new("device-1".to_string());
187 let mut counter2 = GCounter::new("device-1".to_string());
188
189 counter1.increment(5);
190 counter2.increment(3);
191
192 counter1.merge(&counter2).ok();
193
194 assert_eq!(counter1.value(), 5);
196 }
197
198 #[test]
199 fn test_g_counter_dominated_by() {
200 let mut counter1 = GCounter::new("device-1".to_string());
201 let mut counter2 = GCounter::new("device-1".to_string());
202
203 counter1.increment(3);
204 counter2.increment(5);
205
206 assert!(counter1.dominated_by(&counter2));
207 assert!(!counter2.dominated_by(&counter1));
208 }
209
210 #[test]
211 fn test_g_counter_device_count() {
212 let mut counter1 = GCounter::new("device-1".to_string());
213 let mut counter2 = GCounter::new("device-2".to_string());
214 let mut counter3 = GCounter::new("device-3".to_string());
215
216 counter1.increment(1);
217 counter2.increment(1);
218 counter3.increment(1);
219
220 counter1.merge(&counter2).ok();
221 counter1.merge(&counter3).ok();
222
223 assert_eq!(counter1.device_count(), 3);
224 }
225}