cob: Move clock out of `Issue`
The clock shouldn't be part of the issue state, but rather the local "actor" state. Signed-off-by: Alexis Sellier <alexis@radicle.xyz>
This commit is contained in:
parent
c5225c64cd
commit
09a8b46f34
|
|
@ -234,7 +234,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
}
|
||||
Operation::List => {
|
||||
for result in issues.all()? {
|
||||
let (id, issue) = result?;
|
||||
let (id, issue, _) = result?;
|
||||
println!("{} {}", id, issue.title());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -328,7 +328,7 @@ async fn issues_handler(
|
|||
.all()?
|
||||
.into_iter()
|
||||
.filter_map(|r| r.ok())
|
||||
.map(|(id, issue)| {
|
||||
.map(|(id, issue, _)| {
|
||||
json!({
|
||||
"id": id,
|
||||
"author": issue.author(),
|
||||
|
|
|
|||
|
|
@ -77,7 +77,6 @@ pub struct Issue {
|
|||
title: LWWReg<Max<String>, clock::Lamport>,
|
||||
status: LWWReg<Max<Status>, clock::Lamport>,
|
||||
thread: Thread,
|
||||
clock: clock::Lamport,
|
||||
}
|
||||
|
||||
impl Semilattice for Issue {
|
||||
|
|
@ -94,7 +93,6 @@ impl Default for Issue {
|
|||
title: Max::from(String::default()).into(),
|
||||
status: Max::from(Status::default()).into(),
|
||||
thread: Thread::default(),
|
||||
clock: clock::Lamport::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -104,10 +102,13 @@ impl store::FromHistory for Issue {
|
|||
&*TYPENAME
|
||||
}
|
||||
|
||||
fn from_history(history: &radicle_cob::History) -> Result<Self, store::Error> {
|
||||
Ok(history.traverse(Self::default(), |mut acc, entry| {
|
||||
fn from_history(
|
||||
history: &radicle_cob::History,
|
||||
) -> Result<(Self, clock::Lamport), store::Error> {
|
||||
let mut clock = clock::Lamport::default();
|
||||
let obj = history.traverse(Self::default(), |mut acc, entry| {
|
||||
if let Ok(change) = Change::decode(entry.contents()) {
|
||||
if let Err(err) = acc.apply(change) {
|
||||
if let Err(err) = acc.apply(change, &mut clock) {
|
||||
log::warn!("Error applying change to issue state: {err}");
|
||||
return ControlFlow::Break(acc);
|
||||
}
|
||||
|
|
@ -115,7 +116,9 @@ impl store::FromHistory for Issue {
|
|||
return ControlFlow::Break(acc);
|
||||
}
|
||||
ControlFlow::Continue(acc)
|
||||
}))
|
||||
});
|
||||
|
||||
Ok((obj, clock))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -147,32 +150,31 @@ impl Issue {
|
|||
self.thread.comments().map(|(id, comment)| (id, comment))
|
||||
}
|
||||
|
||||
pub fn clock(&self) -> &clock::Lamport {
|
||||
&self.clock
|
||||
}
|
||||
|
||||
pub fn timeline(&self) -> Vec<Action> {
|
||||
todo!();
|
||||
}
|
||||
|
||||
pub fn apply(&mut self, change: Change) -> Result<(), Error> {
|
||||
pub fn apply(&mut self, change: Change, clock: &mut clock::Lamport) -> Result<(), Error> {
|
||||
match change.action {
|
||||
Action::Title { title } => {
|
||||
self.title.set(title, change.clock);
|
||||
clock.merge(change.clock);
|
||||
}
|
||||
Action::Lifecycle { status } => {
|
||||
self.status.set(status, change.clock);
|
||||
clock.merge(change.clock);
|
||||
}
|
||||
Action::Thread { action } => {
|
||||
self.thread.apply([crdt::Change {
|
||||
action,
|
||||
author: change.author,
|
||||
clock: change.clock,
|
||||
}]);
|
||||
self.thread.apply(
|
||||
[crdt::Change {
|
||||
action,
|
||||
author: change.author,
|
||||
clock: change.clock,
|
||||
}],
|
||||
clock,
|
||||
);
|
||||
}
|
||||
}
|
||||
self.clock.merge(change.clock);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -300,7 +302,7 @@ impl<'a, 'g> IssueMut<'a, 'g> {
|
|||
) -> Result<ChangeId, Error> {
|
||||
let id = change.id();
|
||||
|
||||
self.issue.apply(change.clone())?;
|
||||
self.issue.apply(change.clone(), &mut self.clock)?;
|
||||
self.store
|
||||
.update(self.id, msg, change, signer)
|
||||
.map_err(|e| Error::Store(store::Error::from(e)))?;
|
||||
|
|
@ -342,19 +344,19 @@ impl<'a> Issues<'a> {
|
|||
|
||||
/// Get an issue.
|
||||
pub fn get(&self, id: &ObjectId) -> Result<Option<Issue>, store::Error> {
|
||||
self.raw.get(id)
|
||||
self.raw.get(id).map(|r| r.map(|(i, _clock)| i))
|
||||
}
|
||||
|
||||
/// Get an issue mutably.
|
||||
pub fn get_mut<'g>(&'g mut self, id: &ObjectId) -> Result<IssueMut<'a, 'g>, store::Error> {
|
||||
let issue = self
|
||||
let (issue, clock) = self
|
||||
.raw
|
||||
.get(id)?
|
||||
.ok_or_else(move || store::Error::NotFound(TYPENAME.clone(), *id))?;
|
||||
|
||||
Ok(IssueMut {
|
||||
id: *id,
|
||||
clock: issue.clock,
|
||||
clock,
|
||||
issue,
|
||||
store: self,
|
||||
})
|
||||
|
|
@ -375,10 +377,10 @@ impl<'a> Issues<'a> {
|
|||
action: Action::Title { title },
|
||||
clock: clock::Lamport::default(),
|
||||
};
|
||||
let (id, issue): (_, Issue) = self.raw.create("Create issue", change, signer)?;
|
||||
let (id, issue, clock) = self.raw.create("Create issue", change, signer)?;
|
||||
let mut issue = IssueMut {
|
||||
id,
|
||||
clock: issue.clock,
|
||||
clock,
|
||||
issue,
|
||||
store: self,
|
||||
};
|
||||
|
|
@ -600,13 +602,14 @@ mod test {
|
|||
let issues = issues
|
||||
.all()
|
||||
.unwrap()
|
||||
.map(|r| r.map(|(_, i, _)| i))
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(issues.len(), 3);
|
||||
|
||||
issues.iter().find(|(_, i)| i.title() == "First").unwrap();
|
||||
issues.iter().find(|(_, i)| i.title() == "Second").unwrap();
|
||||
issues.iter().find(|(_, i)| i.title() == "Third").unwrap();
|
||||
issues.iter().find(|i| i.title() == "First").unwrap();
|
||||
issues.iter().find(|i| i.title() == "Second").unwrap();
|
||||
issues.iter().find(|i| i.title() == "Third").unwrap();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
#![allow(clippy::large_enum_variant)]
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use radicle_crdt::Change;
|
||||
use radicle_crdt::{Change, Lamport};
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::cob;
|
||||
|
|
@ -21,7 +21,7 @@ pub trait FromHistory: Sized {
|
|||
/// The object type name.
|
||||
fn type_name() -> &'static TypeName;
|
||||
/// Create an object from a history.
|
||||
fn from_history(history: &History) -> Result<Self, Error>;
|
||||
fn from_history(history: &History) -> Result<(Self, Lamport), Error>;
|
||||
}
|
||||
|
||||
/// Store error.
|
||||
|
|
@ -107,7 +107,7 @@ impl<'a, T: FromHistory> Store<'a, T> {
|
|||
message: &'static str,
|
||||
change: Change<A>,
|
||||
signer: &G,
|
||||
) -> Result<(ObjectId, T), Error> {
|
||||
) -> Result<(ObjectId, T, Lamport), Error> {
|
||||
let cob = cob::create(
|
||||
self.raw,
|
||||
signer,
|
||||
|
|
@ -120,30 +120,32 @@ impl<'a, T: FromHistory> Store<'a, T> {
|
|||
contents: change.encode(),
|
||||
},
|
||||
)?;
|
||||
let object = T::from_history(cob.history())?;
|
||||
let (object, clock) = T::from_history(cob.history())?;
|
||||
|
||||
Ok((*cob.id(), object))
|
||||
Ok((*cob.id(), object, clock))
|
||||
}
|
||||
|
||||
/// Get an object.
|
||||
pub fn get(&self, id: &ObjectId) -> Result<Option<T>, Error> {
|
||||
pub fn get(&self, id: &ObjectId) -> Result<Option<(T, Lamport)>, Error> {
|
||||
let cob = cob::get(self.raw, T::type_name(), id)?;
|
||||
|
||||
if let Some(cob) = cob {
|
||||
let obj = T::from_history(cob.history())?;
|
||||
Ok(Some(obj))
|
||||
let (obj, clock) = T::from_history(cob.history())?;
|
||||
Ok(Some((obj, clock)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Return all objects.
|
||||
pub fn all(&self) -> Result<impl Iterator<Item = Result<(ObjectId, T), Error>>, Error> {
|
||||
pub fn all(
|
||||
&self,
|
||||
) -> Result<impl Iterator<Item = Result<(ObjectId, T, Lamport), Error>>, Error> {
|
||||
let raw = cob::list(self.raw, T::type_name())?;
|
||||
|
||||
Ok(raw.into_iter().map(|o| {
|
||||
let obj = T::from_history(o.history())?;
|
||||
Ok((*o.id(), obj))
|
||||
let (obj, clock) = T::from_history(o.history())?;
|
||||
Ok((*o.id(), obj, clock))
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -87,15 +87,18 @@ impl store::FromHistory for Thread {
|
|||
&*TYPENAME
|
||||
}
|
||||
|
||||
fn from_history(history: &History) -> Result<Self, store::Error> {
|
||||
Ok(history.traverse(Thread::default(), |mut acc, entry| {
|
||||
fn from_history(history: &History) -> Result<(Self, Lamport), store::Error> {
|
||||
let mut clock = Lamport::default();
|
||||
let obj = history.traverse(Thread::default(), |mut acc, entry| {
|
||||
if let Ok(change) = Change::decode(entry.contents()) {
|
||||
acc.apply([change]);
|
||||
acc.apply([change], &mut clock);
|
||||
ControlFlow::Continue(acc)
|
||||
} else {
|
||||
ControlFlow::Break(acc)
|
||||
}
|
||||
}))
|
||||
});
|
||||
|
||||
Ok((obj, clock))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -153,7 +156,11 @@ impl Thread {
|
|||
.map(|(a, r)| (a, r))
|
||||
}
|
||||
|
||||
pub fn apply(&mut self, changes: impl IntoIterator<Item = Change<Action>>) {
|
||||
pub fn apply(
|
||||
&mut self,
|
||||
changes: impl IntoIterator<Item = Change<Action>>,
|
||||
clock: &mut Lamport,
|
||||
) {
|
||||
for change in changes.into_iter() {
|
||||
let id = change.id();
|
||||
|
||||
|
|
@ -217,6 +224,7 @@ impl Thread {
|
|||
});
|
||||
}
|
||||
}
|
||||
clock.merge(change.clock);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -413,6 +421,7 @@ mod tests {
|
|||
let (_, signer, repository) = radicle::test::setup::context(&tmp);
|
||||
let store =
|
||||
radicle::cob::store::Store::<Thread>::open(*signer.public_key(), &repository).unwrap();
|
||||
let mut clock = Lamport::default();
|
||||
|
||||
let mut alice = Actor::new(signer);
|
||||
|
||||
|
|
@ -420,14 +429,14 @@ mod tests {
|
|||
let a2 = alice.comment("Second comment", None);
|
||||
|
||||
let mut expected = Thread::default();
|
||||
expected.apply([a1.clone(), a2.clone()]);
|
||||
expected.apply([a1.clone(), a2.clone()], &mut clock);
|
||||
|
||||
let (id, _) = store.create("Thread created", a1, &alice.signer).unwrap();
|
||||
let (id, _, _) = store.create("Thread created", a1, &alice.signer).unwrap();
|
||||
store
|
||||
.update(id, "Thread updated", a2, &alice.signer)
|
||||
.unwrap();
|
||||
|
||||
let actual = store.get(&id).unwrap().unwrap();
|
||||
let (actual, _) = store.get(&id).unwrap().unwrap();
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
|
@ -506,15 +515,16 @@ mod tests {
|
|||
fn prop_invariants(log: Changes<3>) {
|
||||
let t = Thread::default();
|
||||
let [p1, p2, p3] = log.permutations;
|
||||
let mut clock = Lamport::default();
|
||||
|
||||
let mut t1 = t.clone();
|
||||
t1.apply(p1);
|
||||
t1.apply(p1, &mut clock);
|
||||
|
||||
let mut t2 = t.clone();
|
||||
t2.apply(p2);
|
||||
t2.apply(p2, &mut clock);
|
||||
|
||||
let mut t3 = t;
|
||||
t3.apply(p3);
|
||||
t3.apply(p3, &mut clock);
|
||||
|
||||
assert_eq!(t1, t2);
|
||||
assert_eq!(t2, t3);
|
||||
|
|
|
|||
Loading…
Reference in New Issue