crdt: Include timestamp in `Change`
We can then use the real change timestamp for our COBs. Signed-off-by: Alexis Sellier <alexis@radicle.xyz>
This commit is contained in:
parent
d061afdab1
commit
64ce15dbe2
|
|
@ -2,10 +2,9 @@ use std::{fmt, time};
|
|||
|
||||
pub use dialoguer::console::style;
|
||||
|
||||
use radicle::cob::Timestamp;
|
||||
use radicle::cob::{ObjectId, Timestamp};
|
||||
use radicle::node::NodeId;
|
||||
use radicle::profile::Profile;
|
||||
use radicle_cob::ObjectId;
|
||||
|
||||
use crate::terminal as term;
|
||||
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ impl History {
|
|||
|
||||
/// Get the current history timestamp.
|
||||
/// This is the latest timestamp of any tip.
|
||||
pub fn timestamp(&self) -> Clock {
|
||||
pub fn timestamp(&self) -> Timestamp {
|
||||
self.graph
|
||||
.externals(petgraph::Direction::Outgoing)
|
||||
.map(|n| self.graph[n].timestamp())
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use std::collections::BTreeMap;
|
|||
use radicle_crypto::{PublicKey, Signature, Signer};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::clock;
|
||||
use crate::clock::Lamport;
|
||||
|
||||
/// Identifies a change.
|
||||
|
|
@ -21,6 +22,8 @@ pub struct Change<A> {
|
|||
pub author: ActorId,
|
||||
/// Lamport clock.
|
||||
pub clock: Lamport,
|
||||
/// Timestamp of this change.
|
||||
pub timestamp: clock::Physical,
|
||||
}
|
||||
|
||||
impl<A> Change<A> {
|
||||
|
|
@ -101,10 +104,12 @@ impl<G: Signer, A: Clone + Serialize> Actor<G, A> {
|
|||
pub fn change(&mut self, action: A) -> Change<A> {
|
||||
let author = *self.signer.public_key();
|
||||
let clock = self.clock;
|
||||
let timestamp = clock::Physical::now();
|
||||
let change = Change {
|
||||
action,
|
||||
author,
|
||||
clock,
|
||||
timestamp,
|
||||
};
|
||||
self.changes.insert((self.clock, author), change.clone());
|
||||
self.clock.tick();
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
use std::time::SystemTime;
|
||||
use std::time::UNIX_EPOCH;
|
||||
|
||||
use num_traits::Bounded;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
|
@ -54,3 +57,59 @@ impl Bounded for Lamport {
|
|||
Self::from(u64::max_value())
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<u64> for Physical {
|
||||
fn from(seconds: u64) -> Self {
|
||||
Self { seconds }
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add<u64> 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_value(),
|
||||
}
|
||||
}
|
||||
|
||||
fn max_value() -> Self {
|
||||
Self {
|
||||
seconds: u64::max_value(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ pub mod patch;
|
|||
pub mod store;
|
||||
pub mod thread;
|
||||
|
||||
pub use radicle_crdt::clock::Physical as Timestamp;
|
||||
|
||||
pub use cob::{
|
||||
identity, object::collaboration::error, CollaborativeObject, Contents, Create, Entry, History,
|
||||
ObjectId, TypeName, Update,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use num_traits::Bounded;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
pub use radicle_crdt::clock::Physical as Timestamp;
|
||||
|
||||
/// Author.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct Author {
|
||||
|
|
@ -23,55 +23,6 @@ impl Author {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Copy, Clone, PartialOrd, PartialEq, Ord, Eq, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct Timestamp {
|
||||
seconds: u64,
|
||||
}
|
||||
|
||||
impl Timestamp {
|
||||
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 std::ops::Add<u64> for Timestamp {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: u64) -> Self::Output {
|
||||
Self {
|
||||
seconds: self.seconds + rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Bounded for Timestamp {
|
||||
fn min_value() -> Self {
|
||||
Self {
|
||||
seconds: u64::min_value(),
|
||||
}
|
||||
}
|
||||
|
||||
fn max_value() -> Self {
|
||||
Self {
|
||||
seconds: u64::max_value(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum ReactionError {
|
||||
#[error("invalid reaction")]
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@ impl store::FromHistory for Issue {
|
|||
action,
|
||||
author: *entry.actor(),
|
||||
clock: entry.clock().into(),
|
||||
timestamp: entry.timestamp().into(),
|
||||
}) {
|
||||
log::warn!("Error applying change to issue state: {err}");
|
||||
return ControlFlow::Break(acc);
|
||||
|
|
@ -168,6 +169,7 @@ impl Issue {
|
|||
action,
|
||||
author: change.author,
|
||||
clock: change.clock,
|
||||
timestamp: change.timestamp,
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
|
@ -275,16 +277,17 @@ impl<'a, 'g> IssueMut<'a, 'g> {
|
|||
.store
|
||||
.update(self.id, msg, action.clone(), signer)
|
||||
.map_err(Error::Store)?;
|
||||
let clock = cob.history().clock();
|
||||
|
||||
let clock = cob.history().clock().into();
|
||||
let timestamp = cob.history().timestamp().into();
|
||||
let change = Change {
|
||||
author: *signer.public_key(),
|
||||
action,
|
||||
clock: clock.into(),
|
||||
author: *signer.public_key(),
|
||||
clock,
|
||||
timestamp,
|
||||
};
|
||||
self.issue.apply(change)?;
|
||||
|
||||
Ok((clock.into(), *signer.public_key()))
|
||||
Ok((clock, *signer.public_key()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,11 +13,10 @@ use radicle_crdt::{ActorId, ChangeId, LWWReg, LWWSet, Max, Redactable, Semilatti
|
|||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::cob::common::{Author, Tag};
|
||||
use crate::cob::common::{Author, Tag, Timestamp};
|
||||
use crate::cob::thread;
|
||||
use crate::cob::thread::CommentId;
|
||||
use crate::cob::thread::Thread;
|
||||
use crate::cob::Timestamp;
|
||||
use crate::cob::{store, ObjectId, TypeName};
|
||||
use crate::crypto::{PublicKey, Signer};
|
||||
use crate::git;
|
||||
|
|
@ -31,6 +30,7 @@ pub use clock::Lamport as Clock;
|
|||
pub static TYPENAME: Lazy<TypeName> =
|
||||
Lazy::new(|| FromStr::from_str("xyz.radicle.patch").expect("type name is valid"));
|
||||
|
||||
/// Patch change.
|
||||
pub type Change = crdt::Change<Action>;
|
||||
|
||||
/// Identifier for a patch.
|
||||
|
|
@ -240,8 +240,7 @@ impl Patch {
|
|||
pub fn apply_one(&mut self, change: Change) -> Result<(), ApplyError> {
|
||||
let id = change.id();
|
||||
let author = Author::new(change.author);
|
||||
// FIXME(cloudhead): Use commit timestamp.
|
||||
let timestamp = Timestamp::default();
|
||||
let timestamp = change.timestamp;
|
||||
|
||||
match change.action {
|
||||
Action::Edit {
|
||||
|
|
@ -318,6 +317,7 @@ impl Patch {
|
|||
action,
|
||||
author: change.author,
|
||||
clock: change.clock,
|
||||
timestamp,
|
||||
}]);
|
||||
} else {
|
||||
return Err(ApplyError::Missing(revision));
|
||||
|
|
@ -344,6 +344,7 @@ impl store::FromHistory for Patch {
|
|||
action,
|
||||
author: *entry.actor(),
|
||||
clock: entry.clock().into(),
|
||||
timestamp: entry.timestamp().into(),
|
||||
}]) {
|
||||
log::warn!("Error applying change to patch state: {err}");
|
||||
return ControlFlow::Break(acc);
|
||||
|
|
@ -669,20 +670,21 @@ impl<'a, 'g> PatchMut<'a, 'g> {
|
|||
action: Action,
|
||||
signer: &G,
|
||||
) -> Result<ChangeId, Error> {
|
||||
let change = Change {
|
||||
author: *signer.public_key(),
|
||||
action: action.clone(),
|
||||
clock: self.clock.tick(),
|
||||
};
|
||||
self.patch.apply([change])?;
|
||||
|
||||
let cob = self
|
||||
.store
|
||||
.update(self.id, msg, action, signer)
|
||||
.update(self.id, msg, action.clone(), signer)
|
||||
.map_err(Error::Store)?;
|
||||
let clock = cob.history().clock();
|
||||
let clock = cob.history().clock().into();
|
||||
let timestamp = cob.history().timestamp().into();
|
||||
let change = Change {
|
||||
action,
|
||||
author: *signer.public_key(),
|
||||
clock,
|
||||
timestamp,
|
||||
};
|
||||
self.patch.apply_one(change)?;
|
||||
|
||||
Ok((clock.into(), *signer.public_key()))
|
||||
Ok((clock, *signer.public_key()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -868,12 +870,14 @@ mod test {
|
|||
|
||||
let mut changes = Vec::new();
|
||||
let mut permutations: [Vec<Change>; N] = array::from_fn(|_| Vec::new());
|
||||
let timestamp = Timestamp::now() + rng.u64(..60);
|
||||
|
||||
for (clock, action) in gen.take(g.size()) {
|
||||
changes.push(Change {
|
||||
action,
|
||||
author,
|
||||
clock,
|
||||
timestamp,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ use once_cell::sync::Lazy;
|
|||
use radicle_crdt as crdt;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::cob::common::{Reaction, Tag};
|
||||
use crate::cob::common::{Reaction, Tag, Timestamp};
|
||||
use crate::cob::store;
|
||||
use crate::cob::{History, Timestamp, TypeName};
|
||||
use crate::cob::{History, TypeName};
|
||||
use crate::crypto::Signer;
|
||||
|
||||
use crdt::clock::Lamport;
|
||||
|
|
@ -114,6 +114,7 @@ impl store::FromHistory for Thread {
|
|||
action,
|
||||
author: *entry.actor(),
|
||||
clock: entry.clock().into(),
|
||||
timestamp: entry.timestamp().into(),
|
||||
}]);
|
||||
ControlFlow::Continue(acc)
|
||||
} else {
|
||||
|
|
@ -187,15 +188,13 @@ impl Thread {
|
|||
}
|
||||
|
||||
pub fn apply(&mut self, changes: impl IntoIterator<Item = Change<Action>>) {
|
||||
// FIXME(cloudhead): Use commit timestamp.
|
||||
let timestamp = Timestamp::default();
|
||||
|
||||
for change in changes.into_iter() {
|
||||
let id = change.id();
|
||||
|
||||
match change.action {
|
||||
Action::Comment { body, reply_to } => {
|
||||
let present = Redactable::Present(Comment::new(body, reply_to, timestamp));
|
||||
let present =
|
||||
Redactable::Present(Comment::new(body, reply_to, change.timestamp));
|
||||
|
||||
match self.comments.entry(id) {
|
||||
Entry::Vacant(e) => {
|
||||
|
|
@ -436,12 +435,14 @@ mod tests {
|
|||
|
||||
let mut changes = Vec::new();
|
||||
let mut permutations: [Vec<Change<Action>>; N] = array::from_fn(|_| Vec::new());
|
||||
let timestamp = Timestamp::now() + rng.u64(..60);
|
||||
|
||||
for (clock, action) in gen.take(g.size().min(8)) {
|
||||
changes.push(Change {
|
||||
action,
|
||||
author,
|
||||
clock,
|
||||
timestamp,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue