diff --git a/radicle-crdt/src/gmap.rs b/radicle-crdt/src/gmap.rs new file mode 100644 index 00000000..abe4141a --- /dev/null +++ b/radicle-crdt/src/gmap.rs @@ -0,0 +1,108 @@ +use std::collections::btree_map::{Entry, IntoIter}; +use std::collections::BTreeMap; +use std::ops::Deref; + +use crate::Semilattice; + +/// Grow-only map. +/// +/// Conflicting elements are merged via the [`Semilattice`] instance. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct GMap { + inner: BTreeMap, +} + +impl GMap { + pub fn singleton(key: K, value: V) -> Self { + Self { + inner: BTreeMap::from_iter([(key, value)]), + } + } + + pub fn insert(&mut self, key: K, value: V) { + match self.inner.entry(key) { + Entry::Occupied(mut e) => { + e.get_mut().merge(value); + } + Entry::Vacant(e) => { + e.insert(value); + } + } + } +} + +impl FromIterator<(K, V)> for GMap { + fn from_iter>(iter: I) -> Self { + let mut map = GMap::default(); + for (k, v) in iter.into_iter() { + map.insert(k, v); + } + map + } +} + +impl Extend<(K, V)> for GMap { + fn extend>(&mut self, iter: I) { + for (k, v) in iter.into_iter() { + self.insert(k, v); + } + } +} + +impl IntoIterator for GMap { + type Item = (K, V); + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.inner.into_iter() + } +} + +impl Default for GMap { + fn default() -> Self { + Self { + inner: BTreeMap::default(), + } + } +} + +impl Semilattice for GMap { + fn merge(&mut self, other: Self) { + for (k, v) in other.into_iter() { + self.insert(k, v); + } + } +} + +impl Deref for GMap { + type Target = BTreeMap; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +#[cfg(test)] +mod tests { + use quickcheck_macros::quickcheck; + + use super::*; + use crate::ord::Max; + + #[quickcheck] + fn prop_semilattice( + a: Vec<(u8, Max)>, + b: Vec<(u8, Max)>, + c: Vec<(u8, Max)>, + mix: Vec<(u8, Max)>, + ) { + let mut a = GMap::from_iter(a); + let mut b = GMap::from_iter(b); + let c = GMap::from_iter(c); + + a.extend(mix.clone()); + b.extend(mix); + + crate::test::assert_laws(&a, &b, &c); + } +} diff --git a/radicle-crdt/src/lib.rs b/radicle-crdt/src/lib.rs index f9b58a53..fe80343f 100644 --- a/radicle-crdt/src/lib.rs +++ b/radicle-crdt/src/lib.rs @@ -4,6 +4,7 @@ #![allow(clippy::type_complexity)] pub mod change; pub mod clock; +pub mod gmap; pub mod lwwmap; pub mod lwwreg; pub mod lwwset; @@ -17,6 +18,7 @@ pub mod test; pub use change::*; pub use clock::Lamport; +pub use gmap::GMap; pub use lwwmap::LWWMap; pub use lwwreg::LWWReg; pub use lwwset::LWWSet;