diff --git a/radicle-crdt/src/clock.rs b/radicle-crdt/src/clock.rs new file mode 100644 index 00000000..cac71c0e --- /dev/null +++ b/radicle-crdt/src/clock.rs @@ -0,0 +1,39 @@ +use serde::{Deserialize, Serialize}; + +use crate::ord::Max; + +/// Lamport clock. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[serde(transparent)] +pub struct LClock { + counter: Max, +} + +impl LClock { + /// Return the clock value. + pub fn get(&self) -> u64 { + *self.counter.get() + } + + /// Increment clock and return new value. + /// Must be called before sending a message. + pub fn tick(&mut self) -> u64 { + self.counter.incr(); + self.get() + } + + /// Merge clock with another clock, and increment value. + /// Must be called whenever a message is received. + pub fn merge(&mut self, other: Self) { + self.counter.merge(other.counter); + self.counter.incr(); + } +} + +impl From for LClock { + fn from(counter: u64) -> Self { + Self { + counter: Max::from(counter), + } + } +} diff --git a/radicle-crdt/src/lib.rs b/radicle-crdt/src/lib.rs index 7840ebaa..9f1a2fe0 100644 --- a/radicle-crdt/src/lib.rs +++ b/radicle-crdt/src/lib.rs @@ -1,6 +1,7 @@ #![allow(clippy::collapsible_if)] #![allow(clippy::collapsible_else_if)] #![allow(clippy::type_complexity)] +pub mod clock; pub mod lwwmap; pub mod lwwreg; pub mod lwwset; diff --git a/radicle-crdt/src/ord.rs b/radicle-crdt/src/ord.rs index 6338c5aa..ed254724 100644 --- a/radicle-crdt/src/ord.rs +++ b/radicle-crdt/src/ord.rs @@ -6,6 +6,7 @@ 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 { diff --git a/radicle-crdt/src/thread.rs b/radicle-crdt/src/thread.rs index 02378d6b..24b3b41f 100644 --- a/radicle-crdt/src/thread.rs +++ b/radicle-crdt/src/thread.rs @@ -8,6 +8,7 @@ use radicle::cob::Timestamp; use radicle::crypto::{PublicKey, Signature, Signer}; use radicle::hash; +use crate::clock::LClock; use crate::lwwreg::LWWReg; use crate::lwwset::LWWSet; @@ -31,6 +32,8 @@ pub struct Change { author: Author, /// The time at which this change was authored. timestamp: Timestamp, + /// Lamport clock. + clock: LClock, } impl Change { @@ -244,11 +247,12 @@ impl Thread { #[derive(Default)] pub struct Actor { signer: G, + clock: LClock, } impl Actor { /// Create a new thread. - pub fn thread(&self, title: &str, timestamp: Timestamp) -> Change { + pub fn thread(&mut self, title: &str, timestamp: Timestamp) -> Change { self.change( Action::Thread { title: title.to_owned(), @@ -258,7 +262,7 @@ impl Actor { } /// Create a new comment. - pub fn comment(&self, body: &str, timestamp: Timestamp, parent: ChangeId) -> Change { + pub fn comment(&mut self, body: &str, timestamp: Timestamp, parent: ChangeId) -> Change { self.change( Action::Comment { comment: Comment::new(String::from(body), parent), @@ -268,28 +272,30 @@ impl Actor { } /// Add a tag. - pub fn tag(&self, tag: TagId, timestamp: Timestamp) -> Change { + pub fn tag(&mut self, tag: TagId, timestamp: Timestamp) -> Change { self.change(Action::Tag { tag }, timestamp) } /// Remove a tag. - pub fn untag(&self, tag: TagId, timestamp: Timestamp) -> Change { + pub fn untag(&mut self, tag: TagId, timestamp: Timestamp) -> Change { self.change(Action::Untag { tag }, timestamp) } /// Create a new redaction. - pub fn redact(&self, id: ChangeId, timestamp: Timestamp) -> Change { + pub fn redact(&mut self, id: ChangeId, timestamp: Timestamp) -> Change { self.change(Action::Redact { id }, timestamp) } /// Create a new change. - pub fn change(&self, action: Action, timestamp: Timestamp) -> Change { + pub fn change(&mut self, action: Action, timestamp: Timestamp) -> Change { let author = *self.signer.public_key(); + let clock = self.clock.tick(); Change { action, author, timestamp, + clock: clock.into(), } } @@ -397,15 +403,18 @@ mod tests { let mut changes = Vec::new(); let mut permutations: [Vec; N] = array::from_fn(|_| Vec::new()); + let mut clock = LClock::default(); let author = PublicKey::from([0; 32]); for action in gen.take(g.size().min(8)) { let timestamp = Timestamp::now() + rng.u64(0..3); + clock.tick(); changes.push(Change { action, author, timestamp, + clock, }); } @@ -420,7 +429,7 @@ mod tests { #[quickcheck] fn prop_invariants(log: Changes<3>) { - let bob = Actor::::default(); + let mut bob = Actor::::default(); let b0 = bob.thread("The Thread", Timestamp::now()); let t = Thread::new(b0); let [p1, p2, p3] = log.permutations; @@ -440,8 +449,8 @@ mod tests { #[test] fn test_invariants() { - let alice = Actor::::default(); - let bob = Actor::::default(); + let mut alice = Actor::::default(); + let mut bob = Actor::::default(); let time = Timestamp::now(); let b0 = bob.thread("Dinner Ingredients", time);