From 2d03f82359717d9004bf11f72c157417954d229c Mon Sep 17 00:00:00 2001 From: Vlad Eminovici Date: Sun, 17 Oct 2021 17:28:22 +0300 Subject: [PATCH] Add the gcounter --- README.md | 18 +++ euklid-clocks/src/gcounter.rs | 238 ++++++++++++++++++++++++++++++++++ euklid-clocks/src/lib.rs | 2 + euklid-clocks/src/vclock.rs | 4 + 4 files changed, 262 insertions(+) create mode 100644 euklid-clocks/src/gcounter.rs diff --git a/README.md b/README.md index b4911da..1fc8c9f 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,24 @@ For a full example go to the [clock_dvv.rs](https://github.com/veminovici/euklid
+### GCounter +The crate **GCounter** structure is an implementation of the **g-counter** CRDT. + +```rust +use euklid_clocks::*; +use std::iter::FromIterator; + +let mut a = GCounter::::from_iter([(1, 10), (2, 0), (3, 20)]); +let b = GCounter::::from_iter([(1, 5), (2, 5)]); + +// Merging the two counters. +a |= b; + +assert_eq!(a.value(), 10 + 5 + 20); +``` + +
+ ### Resources - [Vector Clocks Revisited](https://riak.com/posts/technical/vector-clocks-revisited/index.html?p=9545.html) - [Vector Clocks Revisited Part2: Dotted Version Vectors](https://riak.com/posts/technical/vector-clocks-revisited-part-2-dotted-version-vectors/index.html) diff --git a/euklid-clocks/src/gcounter.rs b/euklid-clocks/src/gcounter.rs new file mode 100644 index 0000000..e9ae8a6 --- /dev/null +++ b/euklid-clocks/src/gcounter.rs @@ -0,0 +1,238 @@ +use crate::{CmRDT, CvRDT, Dot, VClock}; + +use std::fmt::Debug; +use std::iter::FromIterator; +use std::ops::BitOrAssign; + +/// A grow-counter CDRT +/// +/// # Example +/// +/// ```rust +/// use euklid_clocks::*; +/// use std::iter::FromIterator; +/// +/// let mut a = GCounter::::from_iter([(1, 10), (2, 0), (3, 20)]); +/// let b = GCounter::::from_iter([(1, 5), (2, 5)]); +/// +/// // Merging the two counters. +/// a |= b; +/// +/// assert_eq!(a.value(), 10 + 5 + 20); +/// ``` +pub struct GCounter { + counters: VClock, +} + +// +// Public api +// + +impl Default for GCounter { + fn default() -> Self { + Self { + counters: Default::default(), + } + } +} + +impl GCounter { + /// Creates a new instace of a gcounter. + pub fn new() -> Self { + Self::default() + } + + /// Merges the current counters with a given dot value. + pub fn merge_dot(&mut self, dot: Dot) { + self.apply_op(dot); + } +} + +impl GCounter { + /// Returns the value of the counter. + pub fn value(&self) -> u64 { + self.counters.iter().map(|d| d.counter).sum() + } +} + +// +// CRDT +// + +impl CmRDT for GCounter { + type Op = Dot; + + fn apply_op(&mut self, op: Self::Op) { + self.counters |= op; + } +} + +impl CvRDT for GCounter { + fn merge(&mut self, other: Self) { + self.counters |= other.counters; + } +} + +// +// Operations +// + +impl BitOrAssign> for GCounter { + fn bitor_assign(&mut self, rhs: Dot) { + self.merge_dot(rhs); + } +} + +impl BitOrAssign for GCounter { + fn bitor_assign(&mut self, rhs: Self) { + self.merge(rhs); + } +} + +// +// Froms +// + +impl FromIterator for GCounter { + fn from_iter>(iter: T) -> Self { + let xs = iter + .into_iter() + .map(|a| (a, 0u64)) + .collect::>(); + + Self { + counters: VClock::from_iter(xs), + } + } +} + +impl FromIterator<(A, u64)> for GCounter { + fn from_iter>(iter: T) -> Self { + Self { + counters: VClock::from_iter(iter), + } + } +} + +// +// Formatting +// + +impl Debug for GCounter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.counters) + } +} + +// +// Tests +// + +#[cfg(test)] +mod tests { + use super::*; + use quickcheck_macros::quickcheck; + + #[test] + fn test_new() { + type A = i32; + let gc = GCounter::::new(); + assert!(gc.counters.is_empty()); + assert_eq!(gc.counters.len(), 0); + } + + #[quickcheck] + fn test_from_iter(len: usize) -> bool { + let len = len % 100; + let mut actors = Vec::with_capacity(len); + for i in 0..len { + actors.push(i as i32); + } + + let gc = GCounter::::from_iter(actors); + gc.counters.len() == len + } + + #[quickcheck] + fn test_from_pairs(len: usize) -> bool { + let len = len % 100; + let mut pairs = Vec::with_capacity(len); + let mut ttl = 0; + for i in 0..len { + ttl += (i + 10) as u64; + pairs.push((i as i32, (i + 10) as u64)); + } + + let gc = GCounter::::from_iter(pairs); + assert_eq!(ttl, gc.value()); + gc.counters.len() == len + } + + #[test] + fn test_debug() { + let gc = GCounter::::from_iter([1, 2, 3]); + let s = format!("{:?}", gc); + assert!(!s.is_empty()) + } + + #[test] + fn test_bitor_assign_dot() { + let mut gc = GCounter::::new(); + + gc |= Dot::new(1, 10); + assert_eq!(gc.counters.len(), 1); + assert_eq!(gc.value(), 10); + + gc |= Dot::new(2, 20); + assert_eq!(gc.counters.len(), 2); + assert_eq!(gc.value(), 30); + } + + #[test] + fn test_bitor_assign_gcounter() { + let mut a = GCounter::::from_iter([(1, 10), (2, 0), (3, 20)]); + let b = GCounter::::from_iter([(1, 5), (2, 5)]); + + a |= b; + + assert_eq!(a.counters.len(), 3); + assert_eq!(a.value(), 10 + 5 + 20); + } + + #[test] + fn test_crdt_apply_op() { + let mut gc = GCounter::::new(); + + gc.apply_op(Dot::new(1, 10)); + assert_eq!(gc.counters.len(), 1); + assert_eq!(gc.value(), 10); + + gc.apply_op(Dot::new(2, 20)); + assert_eq!(gc.counters.len(), 2); + assert_eq!(gc.value(), 30); + } + + #[test] + fn test_crdt_merge() { + let mut a = GCounter::::from_iter([(1, 10), (2, 0), (3, 20)]); + let b = GCounter::::from_iter([(1, 5), (2, 5)]); + + a.merge(b); + + assert_eq!(a.counters.len(), 3); + assert_eq!(a.value(), 10 + 5 + 20); + } + + #[test] + fn test_merge_dot() { + let mut gc = GCounter::::new(); + + gc.merge_dot(Dot::new(1, 10)); + assert_eq!(gc.counters.len(), 1); + assert_eq!(gc.value(), 10); + + gc.merge_dot(Dot::new(2, 20)); + assert_eq!(gc.counters.len(), 2); + assert_eq!(gc.value(), 30); + } +} diff --git a/euklid-clocks/src/lib.rs b/euklid-clocks/src/lib.rs index fa11ee5..8274200 100644 --- a/euklid-clocks/src/lib.rs +++ b/euklid-clocks/src/lib.rs @@ -6,6 +6,7 @@ mod causalord; mod dot; mod dvv; +mod gcounter; mod vclock; /// An operation based CRDT. @@ -28,4 +29,5 @@ pub trait CvRDT { pub use crate::causalord::*; pub use crate::dot::Dot; pub use crate::dvv::Dvv; +pub use crate::gcounter::GCounter; pub use crate::vclock::*; diff --git a/euklid-clocks/src/vclock.rs b/euklid-clocks/src/vclock.rs index 09c9134..7678e52 100644 --- a/euklid-clocks/src/vclock.rs +++ b/euklid-clocks/src/vclock.rs @@ -198,6 +198,10 @@ impl std::iter::IntoIterator for VClock { } } +// +// Froms +// + impl FromIterator for VClock { fn from_iter>(iter: T) -> Self { let xs = iter