Add lamport clock
Signed-off-by: Alexis Sellier <alexis@radicle.xyz>
This commit is contained in:
parent
0482757ce9
commit
64862b3ab3
|
|
@ -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<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<u64> for LClock {
|
||||||
|
fn from(counter: u64) -> Self {
|
||||||
|
Self {
|
||||||
|
counter: Max::from(counter),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#![allow(clippy::collapsible_if)]
|
#![allow(clippy::collapsible_if)]
|
||||||
#![allow(clippy::collapsible_else_if)]
|
#![allow(clippy::collapsible_else_if)]
|
||||||
#![allow(clippy::type_complexity)]
|
#![allow(clippy::type_complexity)]
|
||||||
|
pub mod clock;
|
||||||
pub mod lwwmap;
|
pub mod lwwmap;
|
||||||
pub mod lwwreg;
|
pub mod lwwreg;
|
||||||
pub mod lwwset;
|
pub mod lwwset;
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use crate::Semilattice;
|
use crate::Semilattice;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||||
|
#[serde(transparent)]
|
||||||
pub struct Max<T>(T);
|
pub struct Max<T>(T);
|
||||||
|
|
||||||
impl<T> Max<T> {
|
impl<T> Max<T> {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use radicle::cob::Timestamp;
|
||||||
use radicle::crypto::{PublicKey, Signature, Signer};
|
use radicle::crypto::{PublicKey, Signature, Signer};
|
||||||
use radicle::hash;
|
use radicle::hash;
|
||||||
|
|
||||||
|
use crate::clock::LClock;
|
||||||
use crate::lwwreg::LWWReg;
|
use crate::lwwreg::LWWReg;
|
||||||
use crate::lwwset::LWWSet;
|
use crate::lwwset::LWWSet;
|
||||||
|
|
||||||
|
|
@ -31,6 +32,8 @@ pub struct Change {
|
||||||
author: Author,
|
author: Author,
|
||||||
/// The time at which this change was authored.
|
/// The time at which this change was authored.
|
||||||
timestamp: Timestamp,
|
timestamp: Timestamp,
|
||||||
|
/// Lamport clock.
|
||||||
|
clock: LClock,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Change {
|
impl Change {
|
||||||
|
|
@ -244,11 +247,12 @@ impl Thread {
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Actor<G> {
|
pub struct Actor<G> {
|
||||||
signer: G,
|
signer: G,
|
||||||
|
clock: LClock,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<G: Signer> Actor<G> {
|
impl<G: Signer> Actor<G> {
|
||||||
/// Create a new thread.
|
/// 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(
|
self.change(
|
||||||
Action::Thread {
|
Action::Thread {
|
||||||
title: title.to_owned(),
|
title: title.to_owned(),
|
||||||
|
|
@ -258,7 +262,7 @@ impl<G: Signer> Actor<G> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new comment.
|
/// 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(
|
self.change(
|
||||||
Action::Comment {
|
Action::Comment {
|
||||||
comment: Comment::new(String::from(body), parent),
|
comment: Comment::new(String::from(body), parent),
|
||||||
|
|
@ -268,28 +272,30 @@ impl<G: Signer> Actor<G> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a tag.
|
/// 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)
|
self.change(Action::Tag { tag }, timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove a tag.
|
/// 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)
|
self.change(Action::Untag { tag }, timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new redaction.
|
/// 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)
|
self.change(Action::Redact { id }, timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new change.
|
/// 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 author = *self.signer.public_key();
|
||||||
|
let clock = self.clock.tick();
|
||||||
|
|
||||||
Change {
|
Change {
|
||||||
action,
|
action,
|
||||||
author,
|
author,
|
||||||
timestamp,
|
timestamp,
|
||||||
|
clock: clock.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -397,15 +403,18 @@ mod tests {
|
||||||
|
|
||||||
let mut changes = Vec::new();
|
let mut changes = Vec::new();
|
||||||
let mut permutations: [Vec<Change>; N] = array::from_fn(|_| Vec::new());
|
let mut permutations: [Vec<Change>; N] = array::from_fn(|_| Vec::new());
|
||||||
|
let mut clock = LClock::default();
|
||||||
let author = PublicKey::from([0; 32]);
|
let author = PublicKey::from([0; 32]);
|
||||||
|
|
||||||
for action in gen.take(g.size().min(8)) {
|
for action in gen.take(g.size().min(8)) {
|
||||||
let timestamp = Timestamp::now() + rng.u64(0..3);
|
let timestamp = Timestamp::now() + rng.u64(0..3);
|
||||||
|
|
||||||
|
clock.tick();
|
||||||
changes.push(Change {
|
changes.push(Change {
|
||||||
action,
|
action,
|
||||||
author,
|
author,
|
||||||
timestamp,
|
timestamp,
|
||||||
|
clock,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -420,7 +429,7 @@ mod tests {
|
||||||
|
|
||||||
#[quickcheck]
|
#[quickcheck]
|
||||||
fn prop_invariants(log: Changes<3>) {
|
fn prop_invariants(log: Changes<3>) {
|
||||||
let bob = Actor::<MockSigner>::default();
|
let mut bob = Actor::<MockSigner>::default();
|
||||||
let b0 = bob.thread("The Thread", Timestamp::now());
|
let b0 = bob.thread("The Thread", Timestamp::now());
|
||||||
let t = Thread::new(b0);
|
let t = Thread::new(b0);
|
||||||
let [p1, p2, p3] = log.permutations;
|
let [p1, p2, p3] = log.permutations;
|
||||||
|
|
@ -440,8 +449,8 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_invariants() {
|
fn test_invariants() {
|
||||||
let alice = Actor::<MockSigner>::default();
|
let mut alice = Actor::<MockSigner>::default();
|
||||||
let bob = Actor::<MockSigner>::default();
|
let mut bob = Actor::<MockSigner>::default();
|
||||||
let time = Timestamp::now();
|
let time = Timestamp::now();
|
||||||
|
|
||||||
let b0 = bob.thread("Dinner Ingredients", time);
|
let b0 = bob.thread("Dinner Ingredients", time);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue