cob: Compute implicit monotonic clock on changes
Signed-off-by: Alexis Sellier <alexis@radicle.xyz>
This commit is contained in:
parent
1a5b68c2b3
commit
e83d47ff45
|
|
@ -113,11 +113,12 @@ impl ChangeGraph {
|
|||
let child_commits = outgoing_edges
|
||||
.map(|e| *self.graph[e.target()].id())
|
||||
.collect::<Vec<_>>();
|
||||
(node, child_commits)
|
||||
|
||||
(node, idx, child_commits)
|
||||
});
|
||||
let history = {
|
||||
let root_change = &self.graph[*root];
|
||||
evaluate(*root_change.id(), items)
|
||||
evaluate(*root_change.id(), &self.graph, items)
|
||||
};
|
||||
CollaborativeObject {
|
||||
typename,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@
|
|||
use std::{collections::HashMap, ops::ControlFlow};
|
||||
|
||||
use git_ext::Oid;
|
||||
use petgraph::{visit::EdgeRef, EdgeDirection};
|
||||
|
||||
use crate::history::entry::{EntryId, EntryWithClock};
|
||||
use crate::{change::Change, history, pruning_fold};
|
||||
|
||||
/// # Panics
|
||||
|
|
@ -14,11 +16,13 @@ use crate::{change::Change, history, pruning_fold};
|
|||
/// If the change corresponding to the root OID is not in `items`
|
||||
pub fn evaluate<'b>(
|
||||
root: Oid,
|
||||
items: impl Iterator<Item = (&'b Change, Vec<Oid>)>,
|
||||
graph: &petgraph::Graph<Change, ()>,
|
||||
items: impl Iterator<Item = (&'b Change, petgraph::graph::NodeIndex<u32>, Vec<Oid>)>,
|
||||
) -> history::History {
|
||||
let entries = pruning_fold::pruning_fold(
|
||||
HashMap::new(),
|
||||
items.map(|(change, children)| ChangeWithChildren {
|
||||
HashMap::<EntryId, EntryWithClock>::new(),
|
||||
items.map(|(change, idx, children)| ChangeWithChildren {
|
||||
idx,
|
||||
change,
|
||||
child_commits: children,
|
||||
}),
|
||||
|
|
@ -31,8 +35,18 @@ pub fn evaluate<'b>(
|
|||
ControlFlow::Break(entries)
|
||||
}
|
||||
Ok(entry) => {
|
||||
// Get parent commits and calculate this node's clock based on theirs.
|
||||
let incoming = graph.edges_directed(c.idx, EdgeDirection::Incoming);
|
||||
let clock = incoming
|
||||
.into_iter()
|
||||
.map(|e| entries[&graph[e.source()].id.into()].clock())
|
||||
.max()
|
||||
.map(|n| n + 1)
|
||||
.unwrap_or_default();
|
||||
log::trace!("change '{}' accepted", c.change.id());
|
||||
entries.insert((*c.change.id()).into(), entry);
|
||||
|
||||
entries.insert(*entry.id(), EntryWithClock { entry, clock });
|
||||
|
||||
ControlFlow::Continue(entries)
|
||||
}
|
||||
},
|
||||
|
|
@ -60,6 +74,7 @@ fn evaluate_change(
|
|||
}
|
||||
|
||||
struct ChangeWithChildren<'a> {
|
||||
idx: petgraph::graph::NodeIndex<u32>,
|
||||
change: &'a Change,
|
||||
child_commits: Vec<Oid>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ use petgraph::visit::Walker as _;
|
|||
use crate::pruning_fold;
|
||||
|
||||
pub mod entry;
|
||||
pub use entry::{Contents, Entry, EntryId};
|
||||
pub use entry::{Clock, Contents, Entry, EntryId, EntryWithClock};
|
||||
|
||||
#[derive(
|
||||
Clone, Copy, Debug, Default, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize,
|
||||
|
|
@ -29,7 +29,7 @@ pub enum HistoryType {
|
|||
/// The DAG of changes making up the history of a collaborative object.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct History {
|
||||
graph: petgraph::Graph<Entry, (), petgraph::Directed, u32>,
|
||||
graph: petgraph::Graph<EntryWithClock, (), petgraph::Directed, u32>,
|
||||
indices: HashMap<EntryId, petgraph::graph::NodeIndex<u32>>,
|
||||
}
|
||||
|
||||
|
|
@ -66,12 +66,12 @@ impl History {
|
|||
contents,
|
||||
};
|
||||
let mut entries = HashMap::new();
|
||||
entries.insert(id, root_entry.clone());
|
||||
let NewGraph { graph, indices } = create_petgraph(&root_entry.id, &entries);
|
||||
entries.insert(id, EntryWithClock::from(root_entry));
|
||||
let NewGraph { graph, indices } = create_petgraph(&id, &entries);
|
||||
Self { graph, indices }
|
||||
}
|
||||
|
||||
pub fn new<Id>(root: Id, entries: HashMap<EntryId, Entry>) -> Result<Self, CreateError>
|
||||
pub fn new<Id>(root: Id, entries: HashMap<EntryId, EntryWithClock>) -> Result<Self, CreateError>
|
||||
where
|
||||
Id: Into<EntryId>,
|
||||
{
|
||||
|
|
@ -84,6 +84,16 @@ impl History {
|
|||
}
|
||||
}
|
||||
|
||||
/// Get the current value of the logical clock.
|
||||
/// This is the maximum value of all tips.
|
||||
pub fn clock(&self) -> Clock {
|
||||
self.graph
|
||||
.externals(petgraph::Direction::Outgoing)
|
||||
.map(|n| self.graph[n].clock)
|
||||
.max()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// A topological (parents before children) traversal of the dependency
|
||||
/// graph of this history. This is analagous to
|
||||
/// [`std::iter::Iterator::fold`] in that it folds every change into an
|
||||
|
|
@ -92,13 +102,13 @@ impl History {
|
|||
/// `ControlFlow::Break`.
|
||||
pub fn traverse<F, A>(&self, init: A, f: F) -> A
|
||||
where
|
||||
F: for<'r> FnMut(A, &'r Entry) -> ControlFlow<A, A>,
|
||||
F: for<'r> FnMut(A, &'r EntryWithClock) -> ControlFlow<A, A>,
|
||||
{
|
||||
let topo = petgraph::visit::Topo::new(&self.graph);
|
||||
#[allow(clippy::let_and_return)]
|
||||
let items = topo.iter(&self.graph).map(|idx| {
|
||||
let node = &self.graph[idx];
|
||||
node
|
||||
let entry = &self.graph[idx];
|
||||
entry
|
||||
});
|
||||
pruning_fold::pruning_fold(init, items, f)
|
||||
}
|
||||
|
|
@ -131,7 +141,10 @@ impl History {
|
|||
std::iter::empty::<git2::Oid>(),
|
||||
new_contents,
|
||||
);
|
||||
let new_ix = self.graph.add_node(new_entry);
|
||||
let new_ix = self.graph.add_node(EntryWithClock {
|
||||
entry: new_entry,
|
||||
clock: self.clock() + 1,
|
||||
});
|
||||
for tip in tips {
|
||||
let tip_ix = self.indices.get(&tip.into()).unwrap();
|
||||
self.graph.update_edge(*tip_ix, new_ix, ());
|
||||
|
|
@ -140,11 +153,14 @@ impl History {
|
|||
}
|
||||
|
||||
struct NewGraph {
|
||||
graph: petgraph::Graph<Entry, (), petgraph::Directed, u32>,
|
||||
graph: petgraph::Graph<EntryWithClock, (), petgraph::Directed, u32>,
|
||||
indices: HashMap<EntryId, petgraph::graph::NodeIndex<u32>>,
|
||||
}
|
||||
|
||||
fn create_petgraph<'a>(root: &'a EntryId, entries: &'a HashMap<EntryId, Entry>) -> NewGraph {
|
||||
fn create_petgraph<'a>(
|
||||
root: &'a EntryId,
|
||||
entries: &'a HashMap<EntryId, EntryWithClock>,
|
||||
) -> NewGraph {
|
||||
let mut graph = petgraph::Graph::new();
|
||||
let mut indices = HashMap::<EntryId, petgraph::graph::NodeIndex<u32>>::new();
|
||||
let root = entries.get(root).unwrap().clone();
|
||||
|
|
@ -153,8 +169,8 @@ fn create_petgraph<'a>(root: &'a EntryId, entries: &'a HashMap<EntryId, Entry>)
|
|||
let mut to_process = vec![root];
|
||||
while let Some(entry) = to_process.pop() {
|
||||
let entry_ix = indices[&entry.id];
|
||||
for child_id in entry.children {
|
||||
let child = entries[&child_id].clone();
|
||||
for child_id in entry.children() {
|
||||
let child = entries[child_id].clone();
|
||||
let child_ix = graph.add_node(child.clone());
|
||||
indices.insert(child.id, child_ix);
|
||||
graph.update_edge(entry_ix, child_ix, ());
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ use crate::pruning_fold;
|
|||
/// This is the change payload.
|
||||
pub type Contents = Vec<u8>;
|
||||
|
||||
/// Logical clock used to track causality in change graph.
|
||||
pub type Clock = u64;
|
||||
|
||||
/// A unique identifier for a history entry.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq, PartialOrd, Ord)]
|
||||
pub struct EntryId(Oid);
|
||||
|
|
@ -107,3 +110,46 @@ impl pruning_fold::GraphNode for Entry {
|
|||
&self.children
|
||||
}
|
||||
}
|
||||
|
||||
/// Wraps an [`Entry`], adding a logical clock to it.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct EntryWithClock {
|
||||
pub entry: Entry,
|
||||
pub clock: Clock,
|
||||
}
|
||||
|
||||
impl From<Entry> for EntryWithClock {
|
||||
fn from(entry: Entry) -> Self {
|
||||
Self {
|
||||
entry,
|
||||
clock: Clock::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EntryWithClock {
|
||||
/// Get the clock value.
|
||||
pub fn clock(&self) -> Clock {
|
||||
self.clock
|
||||
}
|
||||
}
|
||||
|
||||
impl pruning_fold::GraphNode for EntryWithClock {
|
||||
type Id = EntryId;
|
||||
|
||||
fn id(&self) -> &Self::Id {
|
||||
&self.entry.id
|
||||
}
|
||||
|
||||
fn child_ids(&self) -> &[Self::Id] {
|
||||
&self.entry.children
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for EntryWithClock {
|
||||
type Target = Entry;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.entry
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue