1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
use crate::traits::Grow; /// A vectorized counter that can only grow /// /// # Panics /// /// Any function involving two or more `GCounter`s (viz. `le` and `merge`) will panic (via /// `assert_eq!`) if their counts vectors are not the same length. I'd prefer to check this at /// compile time (as much as possible) instead, but /// /// - avoiding C++'s template mess is part of what makes Rust great /// - Rust doesn't have [const generics](https://rust-lang.github.io/rfcs/2000-const-generics.html) /// yet /// - this library is meant to be as simple and expository as possible, so I'd like to avoid /// fancier things like [`generic_array`](https://docs.rs/generic-array/0.14.4/generic_array/) /// /// As mentioned above, operations panic when trying dealing with two or more `GCounter`s of /// incompatible sizes: /// /// ```should_panic /// // this will panic /// use cvrdt_exposition::{GCounter, Grow}; /// let x = GCounter::new((0, vec![0])); /// let y = GCounter::new((1, vec![0, 0])); /// x.merge(&y); /// ``` /// /// # Difference from references /// /// In the [comprehensive study paper](https://hal.inria.fr/inria-00555588/) and the [Wikipedia /// article](https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type), the vectorized /// `GCounter` presumes a local `myID()` function that tells our local `GCounter` the index to /// update in its counts array. This detail isn't necessary for understanding how their pseudocode /// works, but it _is_ required if you're trying to implement a `GCounter` in real code. As such, /// we explicitly include the `id` as a member of our `GCounter` struct, and make the _arbitrary_ /// choice that when merging two `GCounter`s, we take the minimum of their two `id`s as the new /// one. /// /// # Examples /// /// Example usage, including demonstrating some properties: /// /// ``` /// use cvrdt_exposition::{GCounter, Grow}; /// let mut x = GCounter::new((0, vec![0; 3])); /// x.add(()); /// assert_eq!(x.payload(), (0, vec![1, 0, 0])); /// assert_eq!(x.query(&()), 1); /// let mut y = GCounter::new((1, vec![0; 3])); /// y.add(()); /// y.add(()); /// assert_eq!(x.merge(&y).payload(), (0, vec![1, 2, 0])); /// let z = GCounter::new((2, vec![0, 0, 3])); /// assert!(x.le(&x.merge(&y).merge(&z))); /// assert_eq!(x.merge(&y).merge(&z).payload(), (0, vec![1, 2, 3])); /// assert_eq!(x.merge(&y.merge(&z)).payload(), x.merge(&y).merge(&z).payload()); /// ``` #[derive(Debug, Clone)] pub struct GCounter { /// The index for this local `GCounter` where all increments occur pub id: usize, /// The vector of counts pub counts: Vec<u64>, } impl GCounter { fn compatible_len(&self, other: &Self) -> usize { assert_eq!( self.counts.len(), other.counts.len(), "Incompatible lengths" ); self.counts.len() } } impl Grow for GCounter { type Payload = (usize, Vec<u64>); type Update = (); type Query = (); type Value = u64; fn new(payload: Self::Payload) -> Self { GCounter { id: payload.0, counts: payload.1, } } fn payload(&self) -> Self::Payload { (self.id, self.counts.clone()) } fn add(&mut self, _update: Self::Update) { self.counts[self.id] += 1; } fn le(&self, other: &Self) -> bool { let n = self.compatible_len(other); (0..n).all(|i| self.counts[i] <= other.counts[i]) } fn merge(&self, other: &Self) -> Self { let n = self.compatible_len(other); GCounter { id: self.id.min(other.id), // arbitrary counts: (0..n) .map(|i| self.counts[i].max(other.counts[i])) .collect(), } } fn query(&self, _query: &Self::Query) -> Self::Value { self.counts.iter().sum() } } #[cfg(test)] mod tests { use super::*; use crate::grow_properties; use proptest::prelude::*; static MAX_SIZE: usize = 100; fn sized(n: usize) -> impl Strategy<Value = GCounter> { prop::collection::vec(any::<u64>(), n) .prop_flat_map(|counts| { let len = counts.len(); (0..len, Just(counts)) }) .prop_map(|(id, counts)| GCounter { id, counts }) } fn two() -> impl Strategy<Value = (GCounter, GCounter)> { (1..MAX_SIZE).prop_flat_map(|n| (sized(n), sized(n))) } fn three() -> impl Strategy<Value = (GCounter, GCounter, GCounter)> { (1..MAX_SIZE).prop_flat_map(|n| (sized(n), sized(n), sized(n))) } fn cvrdt_and_update() -> impl Strategy<Value = (GCounter, ())> { (1..MAX_SIZE).prop_flat_map(sized).prop_map(|g| (g, ())) } grow_properties!(two, three, cvrdt_and_update); }