From 42285e71cf538852590c02f3f5b8f05321013bea Mon Sep 17 00:00:00 2001 From: Fintan Halpenny Date: Fri, 20 Jun 2025 16:54:15 +0200 Subject: [PATCH] chore: remove radicle-crdt The `radicle-crdt` crate is no longer used by any of the other crates. Choose to remove the crate so that it no longer needs to build and test as part of our workspace. --- Cargo.lock | 14 -- crates/radicle-crdt/Cargo.toml | 24 ---- crates/radicle-crdt/src/clock.rs | 155 --------------------- crates/radicle-crdt/src/gmap.rs | 118 ---------------- crates/radicle-crdt/src/gset.rs | 97 ------------- crates/radicle-crdt/src/immutable.rs | 52 ------- crates/radicle-crdt/src/lib.rs | 121 ----------------- crates/radicle-crdt/src/lwwmap.rs | 187 -------------------------- crates/radicle-crdt/src/lwwreg.rs | 134 ------------------ crates/radicle-crdt/src/lwwset.rs | 161 ---------------------- crates/radicle-crdt/src/ord.rs | 129 ------------------ crates/radicle-crdt/src/redactable.rs | 111 --------------- crates/radicle-crdt/src/test.rs | 111 --------------- 13 files changed, 1414 deletions(-) delete mode 100644 crates/radicle-crdt/Cargo.toml delete mode 100644 crates/radicle-crdt/src/clock.rs delete mode 100644 crates/radicle-crdt/src/gmap.rs delete mode 100644 crates/radicle-crdt/src/gset.rs delete mode 100644 crates/radicle-crdt/src/immutable.rs delete mode 100644 crates/radicle-crdt/src/lib.rs delete mode 100644 crates/radicle-crdt/src/lwwmap.rs delete mode 100644 crates/radicle-crdt/src/lwwreg.rs delete mode 100644 crates/radicle-crdt/src/lwwset.rs delete mode 100644 crates/radicle-crdt/src/ord.rs delete mode 100644 crates/radicle-crdt/src/redactable.rs delete mode 100644 crates/radicle-crdt/src/test.rs diff --git a/Cargo.lock b/Cargo.lock index 57961bd4..7ccabc77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2455,20 +2455,6 @@ dependencies = [ "thiserror 1.0.69", ] -[[package]] -name = "radicle-crdt" -version = "0.1.0" -dependencies = [ - "fastrand", - "num-traits", - "qcheck", - "qcheck-macros", - "radicle-crypto", - "serde", - "tempfile", - "thiserror 1.0.69", -] - [[package]] name = "radicle-crypto" version = "0.12.0" diff --git a/crates/radicle-crdt/Cargo.toml b/crates/radicle-crdt/Cargo.toml deleted file mode 100644 index fa99ff11..00000000 --- a/crates/radicle-crdt/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "radicle-crdt" -version = "0.1.0" -license.workspace = true -edition.workspace = true -rust-version.workspace = true - -[features] -test = ["fastrand", "qcheck"] - -[dependencies] -fastrand = { workspace = true, optional = true } -num-traits = { version = "0.2.15", default-features = false, features = ["std"] } -qcheck = { workspace = true, optional = true } -radicle-crypto = { workspace = true } -serde = { workspace = true } -thiserror = { workspace = true } - -[dev-dependencies] -fastrand = { workspace = true } -qcheck = { workspace = true } -qcheck-macros = { workspace = true } -radicle-crypto = { workspace = true, features = ["test"] } -tempfile = { workspace = true } diff --git a/crates/radicle-crdt/src/clock.rs b/crates/radicle-crdt/src/clock.rs deleted file mode 100644 index 7e5699a4..00000000 --- a/crates/radicle-crdt/src/clock.rs +++ /dev/null @@ -1,155 +0,0 @@ -use std::fmt; -use std::str::FromStr; -use std::time::SystemTime; -use std::time::UNIX_EPOCH; - -use num_traits::Bounded; -use serde::{Deserialize, Serialize}; -use thiserror::Error; - -use crate::ord::Max; -use crate::Semilattice as _; - -/// Lamport clock. -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -#[serde(transparent)] -pub struct Lamport { - counter: Max, -} - -impl Lamport { - /// Return the clock value. - pub fn get(&self) -> u64 { - *self.counter.get() - } - - /// The initial value of the clock. - pub fn initial() -> Self { - Self::default() - } - - /// Increment clock and return new value. - /// Must be called before sending a message. - pub fn tick(&mut self) -> Self { - self.counter.incr(); - *self - } - - /// Merge clock with another clock, and increment value. - /// Must be called whenever a message is received. - pub fn merge(&mut self, other: Self) -> Self { - self.counter.merge(other.counter); - self.tick() - } - - /// Reset clock to default state. - pub fn reset(&mut self) { - self.counter = Max::default(); - } -} - -impl fmt::Display for Lamport { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.get()) - } -} - -impl From for Lamport { - fn from(counter: u64) -> Self { - Self { - counter: Max::from(counter), - } - } -} - -impl From for u64 { - fn from(arg: Lamport) -> Self { - arg.get() - } -} - -/// Error decoding an operation from an entry. -#[derive(Error, Debug)] -pub enum LamportError { - #[error("invalid lamport clock value")] - Invalid, -} - -impl FromStr for Lamport { - type Err = LamportError; - - fn from_str(s: &str) -> Result { - Self::try_from(s) - } -} - -impl TryFrom<&str> for Lamport { - type Error = LamportError; - - fn try_from(s: &str) -> Result { - let v = s.parse::().map_err(|_| LamportError::Invalid)?; - Ok(v.into()) - } -} - -impl Bounded for Lamport { - fn min_value() -> Self { - Self::from(u64::MIN) - } - - fn max_value() -> Self { - Self::from(u64::MAX) - } -} - -/// Physical clock. Tracks real-time by the second. -#[derive(Debug, Default, Copy, Clone, PartialOrd, PartialEq, Ord, Eq, Serialize, Deserialize)] -#[serde(transparent)] -pub struct Physical { - seconds: u64, -} - -impl Physical { - pub fn new(seconds: u64) -> Self { - Self { seconds } - } - - pub fn now() -> Self { - #[allow(clippy::unwrap_used)] // Safe because Unix was already invented! - let duration = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); - - Self { - seconds: duration.as_secs(), - } - } - - pub fn as_secs(&self) -> u64 { - self.seconds - } -} - -impl From for Physical { - fn from(seconds: u64) -> Self { - Self { seconds } - } -} - -impl std::ops::Add for Physical { - type Output = Self; - - fn add(self, rhs: u64) -> Self::Output { - Self { - seconds: self.seconds + rhs, - } - } -} - -impl Bounded for Physical { - fn min_value() -> Self { - Self { seconds: u64::MIN } - } - - fn max_value() -> Self { - Self { seconds: u64::MAX } - } -} diff --git a/crates/radicle-crdt/src/gmap.rs b/crates/radicle-crdt/src/gmap.rs deleted file mode 100644 index 9377ec00..00000000 --- a/crates/radicle-crdt/src/gmap.rs +++ /dev/null @@ -1,118 +0,0 @@ -use std::collections::btree_map::{Entry, IntoIter, IntoKeys}; -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 get_mut(&mut self, key: &K) -> Option<&mut V> { - self.inner.get_mut(key) - } - - 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 GMap { - pub fn into_keys(self) -> IntoKeys { - self.inner.into_keys() - } -} - -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 qcheck_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/crates/radicle-crdt/src/gset.rs b/crates/radicle-crdt/src/gset.rs deleted file mode 100644 index 4a4c0975..00000000 --- a/crates/radicle-crdt/src/gset.rs +++ /dev/null @@ -1,97 +0,0 @@ -use std::collections::btree_map::{IntoKeys, Keys}; -use std::ops::Deref; - -use crate::GMap; -use crate::Semilattice; - -/// Grow-only set. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct GSet { - inner: GMap, -} - -impl GSet { - pub fn singleton(key: K) -> Self { - Self { - inner: GMap::from_iter([(key, ())]), - } - } - - pub fn insert(&mut self, key: K) { - self.inner.insert(key, ()); - } - - pub fn iter(&self) -> Keys<'_, K, ()> { - self.inner.keys() - } -} - -impl FromIterator for GSet { - fn from_iter>(iter: I) -> Self { - let mut map = GSet::default(); - for k in iter.into_iter() { - map.insert(k); - } - map - } -} - -impl Extend for GSet { - fn extend>(&mut self, iter: I) { - for k in iter.into_iter() { - self.insert(k); - } - } -} - -impl IntoIterator for GSet { - type Item = K; - type IntoIter = IntoKeys; - - fn into_iter(self) -> Self::IntoIter { - self.inner.into_keys() - } -} - -impl Default for GSet { - fn default() -> Self { - Self { - inner: GMap::default(), - } - } -} - -impl Semilattice for GSet { - fn merge(&mut self, other: Self) { - for k in other.into_iter() { - self.insert(k); - } - } -} - -impl Deref for GSet { - type Target = GMap; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -#[cfg(test)] -mod tests { - use qcheck_macros::quickcheck; - - use super::*; - - #[quickcheck] - fn prop_semilattice(a: Vec, b: Vec, c: Vec, mix: Vec) { - let mut a = GSet::from_iter(a); - let mut b = GSet::from_iter(b); - let c = GSet::from_iter(c); - - a.extend(mix.clone()); - b.extend(mix); - - crate::test::assert_laws(&a, &b, &c); - } -} diff --git a/crates/radicle-crdt/src/immutable.rs b/crates/radicle-crdt/src/immutable.rs deleted file mode 100644 index 252b2a1b..00000000 --- a/crates/radicle-crdt/src/immutable.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::Semilattice; - -/// A [`Semilattice`] that panics when attempting to merge inequal elements. -/// Use this for types that will never merge. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Immutable(pub T); - -impl Immutable { - /// Create a new immutable object. - pub fn new(inner: T) -> Self { - Self(inner) - } -} - -impl std::ops::Deref for Immutable { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl Semilattice for Immutable { - fn merge(&mut self, other: Self) { - if self.0 != other.0 { - panic!("Immutable::merge: Cannot merge inequal objects"); - } - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - #[should_panic] - fn test_merge_inequal() { - let mut a = Immutable::new(0); - let b = Immutable::new(1); - - a.merge(b); - } - - #[test] - fn test_merge_equal() { - let mut a = Immutable::new(1); - let b = Immutable::new(1); - - a.merge(b); - assert_eq!(a, b); - } -} diff --git a/crates/radicle-crdt/src/lib.rs b/crates/radicle-crdt/src/lib.rs deleted file mode 100644 index b5d40df7..00000000 --- a/crates/radicle-crdt/src/lib.rs +++ /dev/null @@ -1,121 +0,0 @@ -#![allow(clippy::collapsible_if)] -#![allow(clippy::bool_assert_comparison)] -#![allow(clippy::collapsible_else_if)] -#![allow(clippy::type_complexity)] -pub mod clock; -pub mod gmap; -pub mod gset; -pub mod immutable; -pub mod lwwmap; -pub mod lwwreg; -pub mod lwwset; -pub mod ord; -pub mod redactable; - -#[cfg(any(test, feature = "test"))] -pub mod test; - -//////////////////////////////////////////////////////////////////////////////// - -pub use clock::Lamport; -pub use gmap::GMap; -pub use gset::GSet; -pub use immutable::Immutable; -pub use lwwmap::LWWMap; -pub use lwwreg::LWWReg; -pub use lwwset::LWWSet; -pub use ord::{Max, Min}; -pub use redactable::Redactable; - -//////////////////////////////////////////////////////////////////////////////// - -/// A join-semilattice. -pub trait Semilattice: Sized { - /// Merge an other semilattice into this one. - /// - /// This operation should obbey the semilattice laws and should thus be idempotent, - /// associative and commutative. - fn merge(&mut self, other: Self); - - /// Like [`Semilattice::merge`] but takes and returns a new semilattice. - fn join(mut self, other: Self) -> Self { - self.merge(other); - self - } -} - -impl Semilattice for Option { - fn merge(&mut self, other: Self) { - match (self, other) { - (this @ None, other @ Some(_)) => { - *this = other; - } - (Some(ref mut a), Some(b)) => { - a.merge(b); - } - (Some(_), None) => {} - (None, None) => {} - } - } -} - -impl Semilattice for () { - fn merge(&mut self, _other: Self) {} -} - -impl Semilattice for bool { - fn merge(&mut self, other: Self) { - match (&self, other) { - (false, true) => *self = true, - (true, false) => *self = true, - (false, false) | (true, true) => {} - } - } -} - -pub fn fold(i: impl IntoIterator) -> S -where - S: Semilattice + Default, -{ - i.into_iter().fold(S::default(), S::join) -} - -#[cfg(test)] -mod tests { - use crate::{test, Max, Min, Semilattice}; - use qcheck_macros::quickcheck; - - #[quickcheck] - fn prop_option_laws(a: Max, b: Max, c: Max) { - test::assert_laws(&a, &b, &c); - } - - #[quickcheck] - fn prop_bool_laws(a: bool, b: bool, c: bool) { - test::assert_laws(&a, &b, &c); - } - - #[test] - fn test_bool() { - assert_eq!(false.join(false), false); - assert_eq!(true.join(true), true); - assert_eq!(true.join(false), true); - assert_eq!(false.join(true), true); - } - - #[test] - fn test_option() { - assert_eq!(None::<()>.join(None), None); - assert_eq!(None::<()>.join(Some(())), Some(())); - assert_eq!(Some(()).join(None), Some(())); - assert_eq!(Some(()).join(Some(())), Some(())); - assert_eq!( - Some(Max::from(0)).join(Some(Max::from(1))), - Some(Max::from(1)) - ); - assert_eq!( - Some(Min::from(0)).join(Some(Min::from(1))), - Some(Min::from(0)) - ); - } -} diff --git a/crates/radicle-crdt/src/lwwmap.rs b/crates/radicle-crdt/src/lwwmap.rs deleted file mode 100644 index 19a92929..00000000 --- a/crates/radicle-crdt/src/lwwmap.rs +++ /dev/null @@ -1,187 +0,0 @@ -use crate::gmap::GMap; -use crate::lwwreg::LWWReg; -use crate::{clock, Semilattice}; - -/// Last-Write-Wins Map. -/// -/// In case a value is added and removed under a key at the same time, -/// the "add" takes precedence over the "remove". -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct LWWMap { - inner: GMap, C>>, -} - -impl LWWMap { - pub fn singleton(key: K, value: V, clock: C) -> Self { - Self { - inner: GMap::singleton(key, LWWReg::new(Some(value), clock)), - } - } - - pub fn get(&self, key: &K) -> Option<&V> { - let Some(value) = self.inner.get(key) else { - // If the element was never added, return nothing. - return None; - }; - value.get().as_ref() - } - - pub fn insert(&mut self, key: K, value: V, clock: C) { - self.inner.insert(key, LWWReg::new(Some(value), clock)); - } - - pub fn remove(&mut self, key: K, clock: C) { - self.inner.insert(key, LWWReg::new(None, clock)); - } - - pub fn contains_key(&self, key: &K) -> bool { - let Some(value) = self.inner.get(key) else { - // If the element was never added, return false. - return false; - }; - value.get().is_some() - } - - pub fn iter(&self) -> impl Iterator { - self.inner - .iter() - .filter_map(|(k, v)| v.get().as_ref().map(|v| (k, v))) - } - - pub fn len(&self) -> usize { - self.iter().count() - } - - pub fn is_empty(&self) -> bool { - self.iter().next().is_none() - } -} - -impl Default for LWWMap { - fn default() -> Self { - Self { - inner: GMap::default(), - } - } -} - -impl FromIterator<(K, V, C)> for LWWMap { - fn from_iter>(iter: I) -> Self { - let mut map = LWWMap::default(); - for (k, v, c) in iter.into_iter() { - map.insert(k, v, c); - } - map - } -} - -impl Extend<(K, V, C)> for LWWMap { - fn extend>(&mut self, iter: I) { - for (k, v, c) in iter.into_iter() { - self.insert(k, v, c); - } - } -} - -impl Semilattice for LWWMap -where - K: Ord, - V: Semilattice, - C: Ord, -{ - fn merge(&mut self, other: Self) { - self.inner.merge(other.inner); - } -} - -#[cfg(test)] -mod tests { - use qcheck_macros::quickcheck; - - use super::*; - use crate::ord::Max; - - #[quickcheck] - fn prop_semilattice( - a: Vec<(u8, Max, u16)>, - b: Vec<(u8, Max, u16)>, - c: Vec<(u8, Max, u16)>, - mix: Vec<(u8, Max, u16)>, - ) { - let mut a = LWWMap::from_iter(a); - let mut b = LWWMap::from_iter(b); - let c = LWWMap::from_iter(c); - - a.extend(mix.clone()); - b.extend(mix); - - crate::test::assert_laws(&a, &b, &c); - } - - #[test] - fn test_insert() { - let mut map = LWWMap::default(); - - map.insert('a', Max::from(1), 0); - map.insert('b', Max::from(2), 0); - map.insert('c', Max::from(3), 0); - - assert_eq!(map.get(&'a'), Some(&Max::from(1))); - assert_eq!(map.get(&'b'), Some(&Max::from(2))); - assert_eq!(map.get(&'?'), None); - - let values = map.iter().collect::)>>(); - assert!(values.contains(&(&'a', &Max::from(1)))); - assert!(values.contains(&(&'b', &Max::from(2)))); - assert!(values.contains(&(&'c', &Max::from(3)))); - assert_eq!(values.len(), 3); - } - - #[test] - fn test_insert_remove() { - let mut map = LWWMap::default(); - - map.insert('a', Max::from("alice"), 1); - assert!(map.contains_key(&'a')); - - map.remove('a', 0); - assert!(map.contains_key(&'a')); - - map.remove('a', 1); - assert!(map.contains_key(&'a')); // Add takes precedence over remove. - assert!(map.iter().any(|(c, _)| *c == 'a')); - - map.remove('a', 2); - assert!(!map.contains_key(&'a')); - assert!(!map.iter().any(|(c, _)| *c == 'a')); - } - - #[test] - fn test_is_empty() { - let mut map = LWWMap::default(); - assert!(map.is_empty()); - - map.insert('a', Max::from("alice"), 1); - assert!(!map.is_empty()); - - map.remove('a', 2); - assert!(map.is_empty()); - } - - #[test] - fn test_remove_insert() { - let mut map = LWWMap::default(); - - map.insert('a', Max::from("alice"), 1); - assert_eq!(map.get(&'a'), Some(&Max::from("alice"))); - - map.remove('a', 2); - assert!(!map.contains_key(&'a')); - - map.insert('a', Max::from("alice"), 1); - assert!(!map.contains_key(&'a')); - - map.insert('a', Max::from("amy"), 2); - assert_eq!(map.get(&'a'), Some(&Max::from("amy"))); - } -} diff --git a/crates/radicle-crdt/src/lwwreg.rs b/crates/radicle-crdt/src/lwwreg.rs deleted file mode 100644 index 9c88701f..00000000 --- a/crates/radicle-crdt/src/lwwreg.rs +++ /dev/null @@ -1,134 +0,0 @@ -use num_traits::Bounded; - -use crate::clock; -use crate::ord::Max; -use crate::Semilattice; - -/// Last-Write-Wins Register. -/// -/// In case of conflict, uses the [`Semilattice`] instance of `T` to merge. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct LWWReg { - clock: Max, - value: T, -} - -impl LWWReg { - pub fn initial(value: T) -> Self - where - C: Default, - { - Self { - clock: Max::from(C::default()), - value, - } - } - - pub fn new(value: T, clock: C) -> Self { - Self { - clock: Max::from(clock), - value, - } - } - - pub fn set(&mut self, value: impl Into, clock: C) { - let clock = Max::from(clock); - let value = value.into(); - - if clock == self.clock { - self.value.merge(value); - } else if clock > self.clock { - self.clock.merge(clock); - self.value = value; - } - } - - pub fn get(&self) -> &T { - &self.value - } - - pub fn clock(&self) -> &Max { - &self.clock - } - - pub fn into_inner(self) -> (T, C) { - (self.value, self.clock.into_inner()) - } -} - -impl Default for LWWReg { - fn default() -> Self { - Self { - clock: Max::default(), - value: T::default(), - } - } -} - -impl Semilattice for LWWReg -where - T: Semilattice, - C: PartialOrd, -{ - fn merge(&mut self, other: Self) { - self.set(other.value, other.clock.into_inner()); - } -} - -#[cfg(test)] -mod tests { - use qcheck_macros::quickcheck; - - use super::*; - use crate::Min; - - #[quickcheck] - fn prop_semilattice(a: (Max, u16), b: (Max, u16), c: (Max, u16)) { - let a = LWWReg::new(a.0, a.1); - let b = LWWReg::new(b.0, b.1); - let c = LWWReg::new(c.0, c.1); - - crate::test::assert_laws(&a, &b, &c); - } - - #[test] - fn test_merge() { - let a = LWWReg::new(Max::from(0), 0); - let b = LWWReg::new(Max::from(1), 0); - - assert_eq!(a.join(b).get(), &Max::from(1)); - - let a = LWWReg::new(Min::from(0), 0); - let b = LWWReg::new(Min::from(1), 0); - - assert_eq!(a.join(b).get(), &Min::from(0)); - } - - #[test] - fn test_set_get() { - let mut reg = LWWReg::new(Max::from(42), 1); - assert_eq!(*reg.get(), Max::from(42)); - - reg.set(84, 0); - assert_eq!(*reg.get(), Max::from(42)); - - reg.set(84, 2); - assert_eq!(*reg.get(), Max::from(84)); - - // Smaller value, same clock: smaller value loses. - reg.set(42, 2); - assert_eq!(*reg.get(), Max::from(84)); - - // Bigger value, same clock: bigger value wins. - reg.set(168, 2); - assert_eq!(*reg.get(), Max::from(168)); - - // Smaller value, newer clock: smaller value wins. - reg.set(42, 3); - assert_eq!(*reg.get(), Max::from(42)); - - // Same value, newer clock: newer clock is set. - reg.set(42, 4); - assert_eq!(*reg.clock(), Max::from(4)); - } -} diff --git a/crates/radicle-crdt/src/lwwset.rs b/crates/radicle-crdt/src/lwwset.rs deleted file mode 100644 index 06a9b883..00000000 --- a/crates/radicle-crdt/src/lwwset.rs +++ /dev/null @@ -1,161 +0,0 @@ -use crate::clock; -use crate::{lwwmap::LWWMap, Semilattice}; - -/// Last-Write-Wins Set. -/// -/// In case the same value is added and removed at the same time, -/// the "add" takes precedence over the "remove". -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct LWWSet { - inner: LWWMap, -} - -impl LWWSet { - pub fn singleton(value: T, clock: C) -> Self { - Self { - inner: LWWMap::from_iter([(value, (), clock)]), - } - } - - pub fn insert(&mut self, value: T, clock: C) { - self.inner.insert(value, (), clock); - } - - pub fn remove(&mut self, value: T, clock: C) { - self.inner.remove(value, clock); - } - - pub fn contains(&self, value: &T) -> bool { - self.inner.contains_key(value) - } - - pub fn iter(&self) -> impl Iterator { - self.inner.iter().map(|(k, _)| k) - } - - pub fn is_empty(&self) -> bool { - self.inner.is_empty() - } -} - -impl Default for LWWSet { - fn default() -> Self { - Self { - inner: LWWMap::default(), - } - } -} - -impl FromIterator<(T, C)> for LWWSet { - fn from_iter>(iter: I) -> Self { - let mut set = LWWSet::default(); - for (v, c) in iter.into_iter() { - set.insert(v, c); - } - set - } -} - -impl Extend<(T, C)> for LWWSet { - fn extend>(&mut self, iter: I) { - for (v, c) in iter.into_iter() { - self.insert(v, c); - } - } -} - -impl Semilattice for LWWSet -where - T: Ord, - C: Ord + Default, -{ - fn merge(&mut self, other: Self) { - self.inner.merge(other.inner); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use qcheck_macros::quickcheck; - - #[quickcheck] - fn prop_semilattice( - a: Vec<(u8, u16)>, - b: Vec<(u8, u16)>, - c: Vec<(u8, u16)>, - mix: Vec<(u8, u16)>, - ) { - let mut a = LWWSet::from_iter(a); - let mut b = LWWSet::from_iter(b); - let c = LWWSet::from_iter(c); - - a.extend(mix.clone()); - b.extend(mix); - - crate::test::assert_laws(&a, &b, &c); - } - - #[test] - fn test_insert() { - let mut set = LWWSet::default(); - - set.insert('a', 0); - set.insert('b', 0); - set.insert('c', 0); - - assert!(set.contains(&'a')); - assert!(set.contains(&'b')); - assert!(!set.contains(&'?')); - - let values = set.iter().cloned().collect::>(); - assert!(values.contains(&'a')); - assert!(values.contains(&'b')); - assert!(values.contains(&'c')); - assert_eq!(values.len(), 3); - } - - #[test] - fn test_insert_remove() { - let mut set = LWWSet::default(); - - set.insert('a', 1); - assert!(set.contains(&'a')); - - set.remove('a', 0); - assert!(set.contains(&'a')); - - set.remove('a', 1); - assert!(set.contains(&'a')); // Add takes precedence over remove. - assert!(set.iter().any(|c| *c == 'a')); - - set.remove('a', 2); - assert!(!set.contains(&'a')); - assert!(!set.iter().any(|c| *c == 'a')); - - set.insert('b', 3); - set.remove('b', 3); - assert!(set.contains(&'b')); // Insert precedence. - - set.remove('c', 3); - set.insert('c', 3); - assert!(set.contains(&'c')); // Insert precedence. - } - - #[test] - fn test_remove_insert() { - let mut set = LWWSet::default(); - - set.insert('a', 1); - assert!(set.contains(&'a')); - - set.remove('a', 2); - assert!(!set.contains(&'a')); - - set.insert('a', 1); - assert!(!set.contains(&'a')); - - set.insert('a', 2); - assert!(set.contains(&'a')); - } -} diff --git a/crates/radicle-crdt/src/ord.rs b/crates/radicle-crdt/src/ord.rs deleted file mode 100644 index 056445a2..00000000 --- a/crates/radicle-crdt/src/ord.rs +++ /dev/null @@ -1,129 +0,0 @@ -use std::{cmp, ops}; - -use num_traits::Bounded; -use serde::{Deserialize, Serialize}; - -use crate::Semilattice; - -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -#[serde(transparent)] -pub struct Max(T); - -impl Max { - pub fn get(&self) -> &T { - &self.0 - } - - pub fn into_inner(self) -> T { - self.0 - } -} - -impl Max { - pub fn incr(&mut self) { - self.0 = self.0.saturating_add(&T::one()); - } -} - -impl Default for Max -where - T: Bounded, -{ - fn default() -> Self { - Self(T::min_value()) - } -} - -impl ops::Deref for Max { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl From for Max { - fn from(t: T) -> Self { - Self(t) - } -} - -impl Semilattice for Max { - fn merge(&mut self, other: Self) { - if other.0 > self.0 { - self.0 = other.0; - } - } -} - -impl Bounded for Max { - fn min_value() -> Self { - Self::from(T::min_value()) - } - - fn max_value() -> Self { - Self::from(T::max_value()) - } -} - -#[allow(clippy::derive_ord_xor_partial_ord)] -#[derive(Clone, Copy, Debug, PartialEq, Eq, Ord, Serialize, Deserialize)] -#[serde(transparent)] -pub struct Min(pub T); - -impl Default for Min -where - T: Bounded, -{ - fn default() -> Self { - Self(T::max_value()) - } -} - -impl ops::Deref for Min { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl From for Min { - fn from(t: T) -> Self { - Self(t) - } -} - -impl cmp::PartialOrd for Min -where - T: PartialOrd, -{ - fn partial_cmp(&self, other: &Self) -> Option { - other.0.partial_cmp(&self.0) - } -} - -impl Semilattice for Min { - fn merge(&mut self, other: Self) { - if other.0 < self.0 { - self.0 = other.0; - } - } -} - -#[cfg(any(test, feature = "test"))] -mod arbitrary { - use super::*; - - impl qcheck::Arbitrary for Max { - fn arbitrary(g: &mut qcheck::Gen) -> Self { - Self::from(T::arbitrary(g)) - } - } - - impl qcheck::Arbitrary for Min { - fn arbitrary(g: &mut qcheck::Gen) -> Self { - Self::from(T::arbitrary(g)) - } - } -} diff --git a/crates/radicle-crdt/src/redactable.rs b/crates/radicle-crdt/src/redactable.rs deleted file mode 100644 index 6061f49a..00000000 --- a/crates/radicle-crdt/src/redactable.rs +++ /dev/null @@ -1,111 +0,0 @@ -use crate::Semilattice; - -/// An object that can be either present or removed. -/// -/// The "redacted" state is the top-most element and takes precedence -/// over other states. -/// -/// There is no `Default` instance, since this is not a "bounded" semilattice. -/// -/// Nb. The merge rules are such that if two redactables with different -/// values present are merged; the result is redacted. This is the preserve -/// the semilattice laws. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Redactable { - /// When the object is present. - Present(T), - /// When the object has been removed. - Redacted, -} - -impl Redactable { - pub fn get(&self) -> Option<&T> { - match self { - Self::Present(val) => Some(val), - Self::Redacted => None, - } - } - - pub fn get_mut(&mut self) -> Option<&mut T> { - match self { - Self::Present(ref mut val) => Some(val), - Self::Redacted => None, - } - } -} - -impl From> for Redactable { - fn from(option: Option) -> Self { - match option { - Some(v) => Self::Present(v), - None => Self::Redacted, - } - } -} - -impl From> for Option { - fn from(redactable: Redactable) -> Self { - match redactable { - Redactable::Present(v) => Some(v), - Redactable::Redacted => None, - } - } -} - -impl<'a, T> From<&'a Redactable> for Option<&'a T> { - fn from(redactable: &'a Redactable) -> Self { - redactable.get() - } -} - -impl Semilattice for Redactable { - fn merge(&mut self, other: Self) { - match (&self, other) { - (Self::Redacted, _) => {} - (Self::Present(_), Self::Redacted) => { - *self = Self::Redacted; - } - (Self::Present(a), Self::Present(b)) => { - if a != &b { - *self = Self::Redacted; - } - } - } - } -} - -#[cfg(test)] -mod test { - use qcheck_macros::quickcheck; - - use super::*; - use crate::test; - - #[quickcheck] - fn prop_invariants(a: Option, b: Option, c: Option) { - let a = Redactable::from(a); - let b = Redactable::from(b); - let c = Redactable::from(c); - - test::assert_laws(&a, &b, &c); - } - - #[test] - fn test_redacted() { - let a = Redactable::Present(0); - let b = Redactable::Redacted; - - assert_eq!(a.join(b), Redactable::Redacted); - assert_eq!(b.join(a), Redactable::Redacted); - assert_eq!(a.join(a), a); - } - - #[test] - fn test_both_present() { - let a = Redactable::Present(0); - let b = Redactable::Present(1); - - assert_eq!(a.join(b), Redactable::Redacted); - assert_eq!(a.join(b), b.join(a)); - } -} diff --git a/crates/radicle-crdt/src/test.rs b/crates/radicle-crdt/src/test.rs deleted file mode 100644 index e07d708b..00000000 --- a/crates/radicle-crdt/src/test.rs +++ /dev/null @@ -1,111 +0,0 @@ -use std::fmt::Debug; -use std::rc::Rc; - -use super::*; - -/// Generate test values following a weight distribution. -pub struct WeightedGenerator<'a, T, C> { - cases: Vec Option + 'a>>, - rng: fastrand::Rng, - ctx: C, -} - -impl Iterator for WeightedGenerator<'_, T, C> { - type Item = T; - - fn next(&mut self) -> Option { - let cases = self.cases.len(); - - loop { - let r = self.rng.usize(0..cases); - let g = &self.cases[r]; - - if let Some(val) = g(&mut self.ctx, self.rng.clone()) { - return Some(val); - } - } - } -} - -impl<'a, T, C: Default> WeightedGenerator<'a, T, C> { - /// Create a new distribution. - pub fn new(rng: fastrand::Rng) -> Self { - Self { - cases: Vec::new(), - rng, - ctx: C::default(), - } - } - - /// Add a new variant with a given weight and generator function. - pub fn variant( - mut self, - weight: usize, - generator: impl Fn(&mut C, fastrand::Rng) -> Option + 'a, - ) -> Self { - let gen = Rc::new(generator); - for _ in 0..weight { - self.cases.push(gen.clone()); - } - self - } -} - -/// Assert semilattice ACI laws. -pub fn assert_laws(a: &S, b: &S, c: &S) { - assert_associative(a, b, c); - assert_commutative(a, b); - assert_commutative(b, c); - assert_idempotent(a); - assert_idempotent(b); - assert_idempotent(c); -} - -pub fn assert_associative(a: &S, b: &S, c: &S) { - // (a ^ b) ^ c - let s1 = a.clone().join(b.clone()).join(c.clone()); - // a ^ (b ^ c) - let s2 = a.clone().join(b.clone().join(c.clone())); - // (a ^ b) ^ c = a ^ (b ^ c) - assert_eq!(s1, s2, "associativity"); -} - -pub fn assert_commutative(a: &S, b: &S) { - // a ^ b - let s1 = a.clone().join(b.clone()); - // b ^ a - let s2 = b.clone().join(a.clone()); - // a ^ b = b ^ a - assert_eq!(s1, s2, "commutativity"); -} - -pub fn assert_idempotent(a: &S) { - // a ^ a - let s1 = a.clone().join(a.clone()); - // a - let s2 = a.clone(); - // a ^ a = a - assert_eq!(s1, s2, "idempotence"); -} - -#[test] -fn test_generator() { - let rng = fastrand::Rng::with_seed(0); - let dist = WeightedGenerator::::new(rng) - .variant(1, |_, _| Some('a')) - .variant(2, |_, _| Some('b')) - .variant(4, |_, _| Some('c')) - .variant(8, |_, _| Some('d')); - - let values = dist.take(1000).collect::>(); - - let a = values.iter().filter(|c| **c == 'a').count(); - let b = values.iter().filter(|c| **c == 'b').count(); - let c = values.iter().filter(|c| **c == 'c').count(); - let d = values.iter().filter(|c| **c == 'd').count(); - - assert_eq!(a, 63); - assert_eq!(b, 151); - assert_eq!(c, 255); - assert_eq!(d, 531); -}