leptos_sync_core/crdt/basic/
counter.rs1use super::{replica_id::ReplicaId, traits::{CRDT, Mergeable}};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct GCounter {
10 increments: HashMap<ReplicaId, u64>,
11}
12
13impl GCounter {
14 pub fn new() -> Self {
15 Self {
16 increments: HashMap::new(),
17 }
18 }
19
20 pub fn increment(&mut self, replica_id: ReplicaId) {
21 *self.increments.entry(replica_id).or_insert(0) += 1;
22 }
23
24 pub fn value(&self) -> u64 {
25 self.increments.values().sum()
26 }
27
28 pub fn replica_value(&self, replica_id: ReplicaId) -> u64 {
29 self.increments.get(&replica_id).copied().unwrap_or(0)
30 }
31
32 pub fn len(&self) -> usize {
33 self.increments.len()
34 }
35}
36
37impl Default for GCounter {
38 fn default() -> Self {
39 Self::new()
40 }
41}
42
43impl Mergeable for GCounter {
44 type Error = std::io::Error;
45
46 fn merge(&mut self, other: &Self) -> Result<(), Self::Error> {
47 for (replica_id, increment) in &other.increments {
48 let current = self.increments.entry(*replica_id).or_insert(0);
49 *current = (*current).max(*increment);
50 }
51 Ok(())
52 }
53
54 fn has_conflict(&self, _other: &Self) -> bool {
55 false
57 }
58}
59
60impl CRDT for GCounter {
61 fn replica_id(&self) -> &ReplicaId {
62 static DEFAULT_REPLICA: std::sync::LazyLock<ReplicaId> = std::sync::LazyLock::new(|| ReplicaId::from(uuid::Uuid::nil()));
65 &DEFAULT_REPLICA
66 }
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72
73 #[test]
74 fn test_gcounter_creation() {
75 let counter = GCounter::new();
76 assert_eq!(counter.value(), 0);
77 assert_eq!(counter.len(), 0);
78 }
79
80 #[test]
81 fn test_gcounter_operations() {
82 let mut counter = GCounter::new();
83 let replica_id = ReplicaId::default();
84
85 counter.increment(replica_id);
86 counter.increment(replica_id);
87
88 assert_eq!(counter.value(), 2);
89 assert_eq!(counter.replica_value(replica_id), 2);
90 }
91
92 #[test]
93 fn test_gcounter_merge() {
94 let mut counter1 = GCounter::new();
95 let mut counter2 = GCounter::new();
96 let replica_id1 = ReplicaId::default();
97 let replica_id2 = ReplicaId::default();
98
99 counter1.increment(replica_id1);
100 counter1.increment(replica_id1);
101 counter2.increment(replica_id2);
102 counter2.increment(replica_id2);
103 counter2.increment(replica_id2);
104
105 counter1.merge(&counter2).unwrap();
106
107 assert_eq!(counter1.value(), 5);
108 assert_eq!(counter1.replica_value(replica_id1), 2);
109 assert_eq!(counter1.replica_value(replica_id2), 3);
110 }
111
112 #[test]
113 fn test_gcounter_no_conflicts() {
114 let counter1 = GCounter::new();
115 let counter2 = GCounter::new();
116
117 assert!(!counter1.has_conflict(&counter2));
119 }
120
121 #[test]
122 fn test_gcounter_serialization() {
123 let mut counter = GCounter::new();
124 let replica_id = ReplicaId::default();
125
126 counter.increment(replica_id);
127 counter.increment(replica_id);
128
129 let serialized = serde_json::to_string(&counter).unwrap();
130 let deserialized: GCounter = serde_json::from_str(&serialized).unwrap();
131
132 assert_eq!(counter.value(), deserialized.value());
133 assert_eq!(counter.replica_value(replica_id), deserialized.replica_value(replica_id));
134 }
135
136 #[test]
137 fn test_gcounter_maximum_merge() {
138 let mut counter1 = GCounter::new();
139 let mut counter2 = GCounter::new();
140 let replica_id = ReplicaId::default();
141
142 counter1.increments.insert(replica_id, 5);
144 counter2.increments.insert(replica_id, 3);
145
146 counter1.merge(&counter2).unwrap();
147
148 assert_eq!(counter1.replica_value(replica_id), 5);
150 }
151}