cob: Have clock value for empty COB state

By using `0` as the clock value for an empty state (without operations),
we simplify transaction code. Previously, there was no clock value for
empty states, which meant that we had to sometimes use an `Option`.
This commit is contained in:
Alexis Sellier 2023-01-01 18:26:40 +01:00
parent c568feb700
commit 5ee1f42b6a
No known key found for this signature in database
7 changed files with 31 additions and 37 deletions

View File

@ -48,8 +48,8 @@ pub fn evaluate(root: Oid, graph: &Dag<Oid, Change>, rng: fastrand::Rng) -> hist
clock + entry.contents().len() as Clock - 1 clock + entry.contents().len() as Clock - 1
}) })
.max() .max()
.map(|n| n + 1) .unwrap_or_default() // When there are no operations, the clock is zero.
.unwrap_or_default(); + 1;
log::trace!("change '{}' accepted", c.change.id()); log::trace!("change '{}' accepted", c.change.id());
entries.insert(*entry.id(), EntryWithClock { entry, clock }); entries.insert(*entry.id(), EntryWithClock { entry, clock });

View File

@ -58,7 +58,7 @@ impl History {
timestamp, timestamp,
}; };
let mut entries = HashMap::new(); let mut entries = HashMap::new();
entries.insert(id, EntryWithClock::from(root_entry)); entries.insert(id, EntryWithClock::root(root_entry));
create_dag(&id, &entries) create_dag(&id, &entries)
} }

View File

@ -131,11 +131,11 @@ pub struct EntryWithClock {
pub clock: Clock, pub clock: Clock,
} }
impl From<Entry> for EntryWithClock { impl EntryWithClock {
fn from(entry: Entry) -> Self { pub fn root(entry: Entry) -> Self {
Self { Self {
entry, entry,
clock: Clock::default(), clock: 1 as Clock, // The root entry has a clock value of `1`.
} }
} }
} }

View File

@ -433,7 +433,7 @@ impl<'a> Issues<'a> {
tx.tag(tags.to_owned(), []); tx.tag(tags.to_owned(), []);
})?; })?;
// Just a sanity check that our clock is advancing as expected. // Just a sanity check that our clock is advancing as expected.
debug_assert_eq!(clock.get(), 2); debug_assert_eq!(clock.get(), 3);
Ok(IssueMut { Ok(IssueMut {
id, id,
@ -549,6 +549,8 @@ mod test {
let created = issues let created = issues
.create("My first issue", "Blah blah blah.", &[], &signer) .create("My first issue", "Blah blah blah.", &[], &signer)
.unwrap(); .unwrap();
assert_eq!(created.clock().get(), 3);
let (id, created) = (created.id, created.issue); let (id, created) = (created.id, created.issue);
let issue = issues.get(&id).unwrap().unwrap(); let issue = issues.get(&id).unwrap().unwrap();
@ -645,7 +647,7 @@ mod test {
let mut issue = issues let mut issue = issues
.create("My first issue", "Blah blah blah.", &[], &signer) .create("My first issue", "Blah blah blah.", &[], &signer)
.unwrap(); .unwrap();
let root = OpId::initial(author); let root = OpId::root(author);
let c1 = issue.comment("Hi hi hi.", root, &signer).unwrap(); let c1 = issue.comment("Hi hi hi.", root, &signer).unwrap();
let c2 = issue.comment("Ha ha ha.", root, &signer).unwrap(); let c2 = issue.comment("Ha ha ha.", root, &signer).unwrap();
@ -706,8 +708,8 @@ mod test {
.create("My first issue", "Blah blah blah.", &[], &signer) .create("My first issue", "Blah blah blah.", &[], &signer)
.unwrap(); .unwrap();
// The initial thread op id is always the same. // The root thread op id is always the same.
let c0 = OpId::initial(author); let c0 = OpId::root(author);
issue.comment("Ho ho ho.", c0, &signer).unwrap(); issue.comment("Ho ho ho.", c0, &signer).unwrap();
issue.comment("Ha ha ha.", c0, &signer).unwrap(); issue.comment("Ha ha ha.", c0, &signer).unwrap();

View File

@ -24,6 +24,10 @@ impl OpId {
Self(Lamport::initial(), actor) Self(Lamport::initial(), actor)
} }
pub fn root(actor: ActorId) -> Self {
Self(Lamport::initial().tick(), actor)
}
/// Get operation id clock. /// Get operation id clock.
pub fn clock(&self) -> Lamport { pub fn clock(&self) -> Lamport {
self.0 self.0
@ -153,7 +157,7 @@ impl<G: Signer, A: Clone> Actor<G, A> {
/// Create a new operation. /// Create a new operation.
pub fn op(&mut self, action: A) -> Op<A> { pub fn op(&mut self, action: A) -> Op<A> {
let author = *self.signer.public_key(); let author = *self.signer.public_key();
let clock = self.clock; let clock = self.clock.tick();
let timestamp = clock::Physical::now(); let timestamp = clock::Physical::now();
let op = Op { let op = Op {
action, action,
@ -162,7 +166,6 @@ impl<G: Signer, A: Clone> Actor<G, A> {
timestamp, timestamp,
}; };
self.ops.insert((self.clock, author), op.clone()); self.ops.insert((self.clock, author), op.clone());
self.clock.tick();
op op
} }

View File

@ -787,7 +787,7 @@ impl<'a> Patches<'a> {
tx.tag(tags.to_owned(), []); tx.tag(tags.to_owned(), []);
})?; })?;
// Just a sanity check that our clock is advancing as expected. // Just a sanity check that our clock is advancing as expected.
debug_assert_eq!(clock.get(), 2); debug_assert_eq!(clock.get(), 3);
Ok(PatchMut::new(id, patch, clock, self)) Ok(PatchMut::new(id, patch, clock, self))
} }
@ -1023,7 +1023,7 @@ mod test {
) )
.unwrap(); .unwrap();
assert_eq!(patch.clock, clock::Lamport::from(2)); assert_eq!(patch.clock.get(), 3);
let id = patch.id; let id = patch.id;
let patch = patches.get(&id).unwrap().unwrap(); let patch = patches.get(&id).unwrap().unwrap();
@ -1238,15 +1238,15 @@ mod test {
) )
.unwrap(); .unwrap();
assert_eq!(patch.clock.get(), 2); assert_eq!(patch.clock.get(), 3);
assert_eq!(patch.description(), Some("Blah blah blah.")); assert_eq!(patch.description(), Some("Blah blah blah."));
assert_eq!(patch.version(), 0); assert_eq!(patch.version(), 0);
let (r1, t1) = patch let (r1, t1) = patch
.update("I've made changes.", base, rev1_oid, &signer) .update("I've made changes.", base, rev1_oid, &signer)
.unwrap(); .unwrap();
assert_eq!(r1.clock().get(), 3); assert_eq!(r1.clock().get(), 4);
assert_eq!(t1.clock().get(), 4); assert_eq!(t1.clock().get(), 5);
let id = patch.id; let id = patch.id;
let patch = patches.get(&id).unwrap().unwrap(); let patch = patches.get(&id).unwrap().unwrap();

View File

@ -194,17 +194,19 @@ where
pub struct Transaction<T: FromHistory> { pub struct Transaction<T: FromHistory> {
actor: ActorId, actor: ActorId,
start: Lamport, start: Lamport,
clock: Option<Lamport>, clock: Lamport,
actions: Vec<T::Action>, actions: Vec<T::Action>,
} }
impl<T: FromHistory> Transaction<T> { impl<T: FromHistory> Transaction<T> {
/// Create a new transaction. /// Create a new transaction.
pub fn new(actor: ActorId, clock: Lamport) -> Self { pub fn new(actor: ActorId, clock: Lamport) -> Self {
let start = clock;
Self { Self {
actor, actor,
start: clock, start,
clock: Some(clock), clock,
actions: Vec::new(), actions: Vec::new(),
} }
} }
@ -225,7 +227,7 @@ impl<T: FromHistory> Transaction<T> {
let mut tx = Transaction { let mut tx = Transaction {
actor, actor,
start: Lamport::initial(), start: Lamport::initial(),
clock: None, clock: Lamport::initial(),
actions: Vec::new(), actions: Vec::new(),
}; };
operations(&mut tx); operations(&mut tx);
@ -235,7 +237,7 @@ impl<T: FromHistory> Transaction<T> {
let (id, cob, clock) = store.create(message, actions, signer)?; let (id, cob, clock) = store.create(message, actions, signer)?;
// The history clock should be in sync with the tx clock. // The history clock should be in sync with the tx clock.
assert_eq!(Some(clock), tx.clock); assert_eq!(clock, tx.clock);
Ok((id, cob, clock)) Ok((id, cob, clock))
} }
@ -243,20 +245,7 @@ impl<T: FromHistory> Transaction<T> {
/// Add an operation to this transaction. /// Add an operation to this transaction.
pub fn push(&mut self, action: T::Action) -> cob::OpId { pub fn push(&mut self, action: T::Action) -> cob::OpId {
self.actions.push(action); self.actions.push(action);
OpId::new(self.clock.tick(), self.actor)
// If our clock already had a value, it means this isn't the first operation
// of this COB. In that case we 'tick' the clock and return the new clock
// value.
//
// Otherwise, it means it was the first operation of our COB. In that case
// we set our clock to the initial clock value (0), and return that.
if let Some(ref mut clock) = self.clock {
OpId::new(clock.tick(), self.actor)
} else {
self.clock = Some(Lamport::initial());
OpId::initial(self.actor)
}
} }
/// Commit transaction. /// Commit transaction.
@ -279,7 +268,7 @@ impl<T: FromHistory> Transaction<T> {
let timestamp = cob.history().timestamp().into(); let timestamp = cob.history().timestamp().into();
// The history clock should be in sync with the tx clock. // The history clock should be in sync with the tx clock.
assert_eq!(Some(cob.history().clock()), self.clock.map(|c| c.get())); assert_eq!(cob.history().clock(), self.clock.get());
// Start the clock from where the transcation clock started. // Start the clock from where the transcation clock started.
let mut clock = self.start; let mut clock = self.start;