Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
cob: Compute implicit monotonic clock on changes
Alexis Sellier committed 3 years ago
commit e83d47ff451e276013fd46d2dc426ee8b24828d5
parent 1a5b68c2b3d47928cf1331e09b8e2a4546c95a69
4 files changed +97 -19
modified radicle-cob/src/change_graph.rs
@@ -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,
modified radicle-cob/src/change_graph/evaluation.rs
@@ -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>,
}
modified radicle-cob/src/history.rs
@@ -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, ());
modified radicle-cob/src/history/entry.rs
@@ -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
+
    }
+
}