~dkellner/chronofold

8242096236703d71eb417ad8b2a0cce26b7008bc — Dominik Kellner 1 year, 7 months ago 4ed0090
Store root entry in the log
M README.md => README.md +2 -2
@@ 35,7 35,7 @@ let mut cfold_b = cfold_a.clone();
let ops_a: Vec<Op<AuthorId, char>> = {
    let mut session = cfold_a.session("alice");
    session.splice(
        LogIndex(15)..LogIndex(15),
        LogIndex(16)..LogIndex(16),
        " - a data structure for versioned text".chars(),
    );
    session.iter_ops().map(Op::cloned).collect()


@@ 44,7 44,7 @@ let ops_a: Vec<Op<AuthorId, char>> = {
// ... while Bob fixes a typo.
let ops_b: Vec<Op<AuthorId, char>> = {
    let mut session = cfold_b.session("bob");
    session.insert_after(Some(LogIndex(10)), 'o');
    session.insert_after(LogIndex(11), 'o');
    session.iter_ops().map(Op::cloned).collect()
};


M src/change.rs => src/change.rs +4 -1
@@ 1,7 1,8 @@
/// An entry in the chronofold's log.
#[derive(PartialEq, Eq, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(untagged))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Change<T> {
    Root,
    Insert(T),
    Delete,
}


@@ 11,6 12,7 @@ impl<T> Change<T> {
    pub fn as_ref(&self) -> Change<&T> {
        use Change::*;
        match *self {
            Root => Root,
            Insert(ref x) => Insert(x),
            Delete => Delete,
        }


@@ 22,6 24,7 @@ impl<T: Clone> Change<&T> {
    pub fn cloned(self) -> Change<T> {
        use Change::*;
        match self {
            Root => Root,
            Insert(x) => Insert(x.clone()),
            Delete => Delete,
        }

M src/error.rs => src/error.rs +1 -0
@@ 53,6 53,7 @@ impl<A: fmt::Debug + fmt::Display + Copy, T> From<&Op<A, T>> for DebugOp<A> {
            id: source.id,
            reference: source.reference,
            change: match source.change {
                Root => Root,
                Insert(_) => Insert(Omitted),
                Delete => Delete,
            },

M src/index.rs => src/index.rs +9 -3
@@ 42,9 42,15 @@ impl<A: Author, T> Chronofold<A, T> {
    ///   1. `index` is the first index (causal order).
    ///   2. `index` is out of bounds.
    pub(crate) fn index_before(&self, index: LogIndex) -> Option<LogIndex> {
        self.iter_log_indices_causal_range(..index)
            .map(|(_, idx)| idx)
            .last()
        if matches!(self.log.get(index.0), Some(Change::Root)) {
            Some(index)
        } else if let Some(reference) = self.references.get(&index) {
            self.iter_log_indices_causal_range(reference..index)
                .map(|(_, idx)| idx)
                .last()
        } else {
            None
        }
    }

    /// Returns the next log index (causal order).

M src/internal.rs => src/internal.rs +29 -23
@@ 15,20 15,31 @@ impl<A: Author, T> Chronofold<A, T> {
        reference: Option<LogIndex>,
        change: &Change<T>,
    ) -> Option<LogIndex> {
        match change {
            Change::Delete => reference, // deletes have priority
            _ => {
        match (reference, change) {
            (_, Change::Delete) => reference, // deletes have priority
            (None, Change::Root) => reference,
            (_, Change::Root) => {
                // Roots cannot reference other entries.
                // XXX: Should we cover this by the type system?
                unreachable!()
            }
            (Some(reference), _change) => {
                if let Some((_, idx)) = self
                    .iter_log_indices_causal_range(..) // TODO: performance
                    .filter(|(_, i)| self.references.get(i) == reference)
                    .iter_log_indices_causal_range(reference..)
                    .filter(|(_, i)| self.references.get(i) == Some(reference))
                    .filter(|(c, i)| matches!(c, Change::Delete) || self.timestamp(i).unwrap() > id)
                    .last()
                {
                    self.iter_subtree(idx).last()
                } else {
                    reference
                    Some(reference)
                }
            }
            (None, _change) => {
                // Non-roots have to reference another entry.
                // XXX: Should we cover this by the type system?
                unreachable!()
            }
        }
    }



@@ 67,8 78,8 @@ impl<A: Author, T> Chronofold<A, T> {
            next_index = self.next_indices.get(&idx);
            self.next_indices.set(idx, Some(new_index));
        } else {
            next_index = self.root;
            self.root = Some(new_index);
            // Inserting another root will result in two disjunct subsequences.
            next_index = None;
        }

        // Append to the chronofold's log and secondary logs.


@@ 94,7 105,7 @@ impl<A: Author, T> Chronofold<A, T> {
    pub(crate) fn apply_local_changes<I>(
        &mut self,
        author: A,
        reference: Option<LogIndex>,
        reference: LogIndex,
        changes: I,
    ) -> Option<LogIndex>
    where


@@ 103,10 114,10 @@ impl<A: Author, T> Chronofold<A, T> {
        let mut last_id = None;
        let mut last_next_index = None;

        let mut predecessor = reference.map(|r| match self.find_last_delete(r) {
        let mut predecessor = match self.find_last_delete(reference) {
            Some(idx) => idx,
            None => r,
        });
            None => reference,
        };

        let mut changes = changes.into_iter();
        if let Some(first_change) = changes.next() {


@@ 116,31 127,26 @@ impl<A: Author, T> Chronofold<A, T> {

            // Set the predecessors next index to our new change's index while
            // keeping it's previous next index for ourselves.
            if let Some(idx) = predecessor {
                last_next_index = Some(self.next_indices.get(&idx));
                self.next_indices.set(idx, Some(new_index));
            } else {
                last_next_index = Some(self.root);
                self.root = Some(new_index);
            }
            last_next_index = Some(self.next_indices.get(&predecessor));
            self.next_indices.set(predecessor, Some(new_index));

            self.log.push(first_change);
            self.authors.set(new_index, author);
            self.index_shifts.set(new_index, IndexShift(0));
            self.references.set(new_index, predecessor);
            self.references.set(new_index, Some(predecessor));

            predecessor = Some(new_index);
            predecessor = new_index;
        }

        for change in changes {
            let new_index = RelativeNextIndex::default().add(predecessor.as_ref().unwrap());
            let new_index = RelativeNextIndex::default().add(&predecessor);
            let id = Timestamp(new_index, author);
            last_id = Some(id);

            // Append to the chronofold's log and secondary logs.
            self.log.push(change);

            predecessor = Some(new_index);
            predecessor = new_index;
        }

        if let (Some(id), Some(next_index)) = (last_id, last_next_index) {

M src/iter.rs => src/iter.rs +21 -13
@@ 13,11 13,14 @@ impl<A: Author, T> Chronofold<A, T> {
    where
        R: RangeBounds<LogIndex>,
    {
        let current = match range.start_bound() {
            Bound::Unbounded => self.root,
        let mut current = match range.start_bound() {
            Bound::Unbounded => self.index_after(self.root),
            Bound::Included(idx) => Some(*idx),
            Bound::Excluded(idx) => self.index_after(*idx),
        };
        if let Some((Some(Change::Root), idx)) = current.map(|idx| (self.get(idx), idx)) {
            current = self.index_after(idx);
        }
        let first_excluded = match range.end_bound() {
            Bound::Unbounded => None,
            Bound::Included(idx) => self.index_after(*idx),


@@ 35,7 38,7 @@ impl<A: Author, T> Chronofold<A, T> {
    /// The first item is always `root`.
    pub(crate) fn iter_subtree(&self, root: LogIndex) -> impl Iterator<Item = LogIndex> + '_ {
        let mut subtree: HashSet<LogIndex> = HashSet::new();
        self.iter_log_indices_causal_range(..)
        self.iter_log_indices_causal_range(root..)
            .filter_map(move |(_, idx)| {
                if idx == root || subtree.contains(&self.references.get(&idx)?) {
                    subtree.insert(idx);


@@ 215,10 218,10 @@ mod tests {
    fn iter_subtree() {
        let mut cfold = Chronofold::<u8, char>::default();
        cfold.session(1).extend("013".chars());
        cfold.session(1).insert_after(Some(LogIndex(1)), '2');
        cfold.session(1).insert_after(LogIndex(2), '2');
        assert_eq!(
            vec![LogIndex(1), LogIndex(3), LogIndex(2)],
            cfold.iter_subtree(LogIndex(1)).collect::<Vec<_>>()
            vec![LogIndex(2), LogIndex(4), LogIndex(3)],
            cfold.iter_subtree(LogIndex(2)).collect::<Vec<_>>()
        );
    }



@@ 226,24 229,29 @@ mod tests {
    fn iter_ops() {
        let mut cfold = Chronofold::<u8, char>::default();
        cfold.session(1).extend("Hi!".chars());
        let op0 = Op::new(Timestamp(LogIndex(0), 1), None, Change::Insert(&'H'));
        let op0 = Op::new(Timestamp(LogIndex(0), 0), None, Change::Root);
        let op1 = Op::new(
            Timestamp(LogIndex(1), 1),
            Some(Timestamp(LogIndex(0), 1)),
            Change::Insert(&'i'),
            Some(Timestamp(LogIndex(0), 0)),
            Change::Insert(&'H'),
        );
        let op2 = Op::new(
            Timestamp(LogIndex(2), 1),
            Some(Timestamp(LogIndex(1), 1)),
            Change::Insert(&'i'),
        );
        let op3 = Op::new(
            Timestamp(LogIndex(3), 1),
            Some(Timestamp(LogIndex(2), 1)),
            Change::Insert(&'!'),
        );
        assert_eq!(
            vec![op0.clone(), op1.clone()],
            cfold.iter_ops(..LogIndex(2)).collect::<Vec<_>>()
            vec![op0.clone(), op1.clone(), op2.clone()],
            cfold.iter_ops(..LogIndex(3)).collect::<Vec<_>>()
        );
        assert_eq!(
            vec![op1, op2],
            cfold.iter_ops(LogIndex(1)..).collect::<Vec<_>>()
            vec![op2, op3],
            cfold.iter_ops(LogIndex(2)..).collect::<Vec<_>>()
        );
    }


M src/lib.rs => src/lib.rs +26 -15
@@ 35,7 35,7 @@
//! let ops_a: Vec<Op<AuthorId, char>> = {
//!     let mut session = cfold_a.session("alice");
//!     session.splice(
//!         LogIndex(15)..LogIndex(15),
//!         LogIndex(16)..LogIndex(16),
//!         " - a data structure for versioned text".chars(),
//!     );
//!     session.iter_ops().map(Op::cloned).collect()


@@ 44,7 44,7 @@
//! // ... while Bob fixes a typo.
//! let ops_b: Vec<Op<AuthorId, char>> = {
//!     let mut session = cfold_b.session("bob");
//!     session.insert_after(Some(LogIndex(10)), 'o');
//!     session.insert_after(LogIndex(11), 'o');
//!     session.iter_ops().map(Op::cloned).collect()
//! };
//!


@@ 133,7 133,7 @@ extern crate serde;
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Chronofold<A, T> {
    log: Vec<Change<T>>,
    root: Option<LogIndex>,
    root: LogIndex,
    #[cfg_attr(
        feature = "serde",
        serde(bound(


@@ 151,8 151,27 @@ pub struct Chronofold<A, T> {

impl<A: Author, T> Chronofold<A, T> {
    /// Constructs a new, empty chronofold.
    pub fn new() -> Self {
        Self::default()
    pub fn new(author: A) -> Self {
        let root_idx = LogIndex(0);
        let mut version = Version::default();
        version.inc(&Timestamp(root_idx, author));
        let mut next_indices = OffsetMap::default();
        next_indices.set(root_idx, None);
        let mut authors = RangeFromMap::default();
        authors.set(root_idx, author);
        let mut index_shifts = RangeFromMap::default();
        index_shifts.set(root_idx, IndexShift(0));
        let mut references = OffsetMap::default();
        references.set(root_idx, None);
        Self {
            log: vec![Change::Root],
            root: LogIndex(0),
            version,
            next_indices,
            authors,
            index_shifts,
            references,
        }
    }

    /// Returns `true` if the chronofold contains no elements.


@@ 204,16 223,8 @@ impl<A: Author, T> Chronofold<A, T> {
    }
}

impl<A: Author, T> Default for Chronofold<A, T> {
impl<A: Author + Default, T> Default for Chronofold<A, T> {
    fn default() -> Self {
        Self {
            log: Vec::default(),
            root: None,
            version: Version::default(),
            next_indices: OffsetMap::default(),
            authors: RangeFromMap::default(),
            index_shifts: RangeFromMap::default(),
            references: OffsetMap::default(),
        }
        Self::new(A::default())
    }
}

M src/session.rs => src/session.rs +17 -9
@@ 1,6 1,6 @@
use std::ops::{Bound, RangeBounds};

use crate::{Author, Change, Chronofold, LogIndex, Op};
use crate::{Author, Change, Chronofold, LogIndex, Op, Timestamp};

/// An editing session tied to one author.
///


@@ 45,23 45,24 @@ impl<'a, A: Author, T> Session<'a, A, T> {
    /// element's log index.
    pub fn push_back(&mut self, value: T) -> LogIndex {
        if let Some((_, last_index)) = self.chronofold.iter().last() {
            self.insert_after(Some(last_index), value)
            self.insert_after(last_index, value)
        } else {
            self.insert_after(None, value)
            // no non-deleted entries left
            self.insert_after(self.as_ref().root, value)
        }
    }

    /// Prepends an element to the chronofold and returns the new element's log
    /// index.
    pub fn push_front(&mut self, value: T) -> LogIndex {
        self.insert_after(None, value)
        self.insert_after(self.as_ref().root, value)
    }

    /// Inserts an element after the element with log index `index` and returns
    /// the new element's log index.
    ///
    /// If `index == None`, the element will be inserted at the beginning.
    pub fn insert_after(&mut self, index: Option<LogIndex>, value: T) -> LogIndex {
    pub fn insert_after(&mut self, index: LogIndex, value: T) -> LogIndex {
        self.apply_change(index, Change::Insert(value))
    }



@@ 70,7 71,7 @@ impl<'a, A: Author, T> Session<'a, A, T> {
    /// Note that this just marks the element as deleted, not actually modify
    /// the log apart from appending a `Change::Delete`.
    pub fn remove(&mut self, index: LogIndex) {
        self.apply_change(Some(index), Change::Delete);
        self.apply_change(index, Change::Delete);
    }

    /// Extends the chronofold with the contents of `iter`, returns the log


@@ 92,7 93,8 @@ impl<'a, A: Author, T> Session<'a, A, T> {
            Bound::Unbounded => None,
            Bound::Included(idx) => self.chronofold.index_before(*idx),
            Bound::Excluded(idx) => Some(*idx),
        };
        }
        .unwrap_or(self.as_ref().root);
        let to_remove: Vec<LogIndex> = self
            .chronofold
            .iter_range(range)


@@ 104,11 106,17 @@ impl<'a, A: Author, T> Session<'a, A, T> {
        self.apply_changes(last_idx, replace_with.into_iter().map(Change::Insert))
    }

    fn apply_change(&mut self, reference: Option<LogIndex>, change: Change<T>) -> LogIndex {
    pub fn create_root(&mut self) -> LogIndex {
        let new_index = LogIndex(self.chronofold.log.len());
        self.chronofold
            .apply_change(Timestamp(new_index, self.author), None, Change::Root)
    }

    fn apply_change(&mut self, reference: LogIndex, change: Change<T>) -> LogIndex {
        self.apply_changes(reference, Some(change)).unwrap()
    }

    fn apply_changes<I>(&mut self, reference: Option<LogIndex>, changes: I) -> Option<LogIndex>
    fn apply_changes<I>(&mut self, reference: LogIndex, changes: I) -> Option<LogIndex>
    where
        I: IntoIterator<Item = Change<T>>,
    {

M tests/api.rs => tests/api.rs +4 -4
@@ 32,7 32,7 @@ fn get() {
    let mut cfold = Chronofold::<u8, char>::default();
    cfold.session(1).extend("abc".chars());
    assert_eq!(Some(&'b'), vec.get(1));
    assert_eq!(Some(&Change::Insert('b')), cfold.get(LogIndex(1)));
    assert_eq!(Some(&Change::Insert('b')), cfold.get(LogIndex(2)));
}

#[test]


@@ 56,7 56,7 @@ fn insert_after() {
            vec.insert(2, 'o');
        },
        |cfold_session| {
            cfold_session.insert_after(Some(LogIndex(1)), 'o');
            cfold_session.insert_after(LogIndex(2), 'o');
        },
    );
}


@@ 106,7 106,7 @@ fn splice() {
            vec.splice(3..3, "bar".chars());
        },
        |cfold_session| {
            cfold_session.splice(LogIndex(3)..LogIndex(3), "bar".chars());
            cfold_session.splice(LogIndex(4)..LogIndex(4), "bar".chars());
        },
    );



@@ 130,7 130,7 @@ fn splice() {
            vec.splice(3..3, "bar".chars());
        },
        |cfold_session| {
            cfold_session.splice(LogIndex(3)..LogIndex(3), "bar".chars());
            cfold_session.splice(LogIndex(4)..LogIndex(4), "bar".chars());
        },
    );
}

M tests/corner_cases.rs => tests/corner_cases.rs +9 -9
@@ 37,10 37,10 @@ fn concurrent_replacements() {
        "foobaz123",
        "foobar",
        |s| {
            s.splice(LogIndex(3).., "123".chars());
            s.splice(LogIndex(4).., "123".chars());
        },
        |s| {
            s.splice(LogIndex(3).., "baz".chars());
            s.splice(LogIndex(4).., "baz".chars());
        },
    );
}


@@ 54,10 54,10 @@ fn concurrent_insertion_deletion() {
        "0!",
        "01",
        |s| {
            s.insert_after(Some(LogIndex(1)), '!');
            s.insert_after(LogIndex(2), '!');
        },
        |s| {
            s.remove(LogIndex(1));
            s.remove(LogIndex(2));
        },
    );



@@ 67,10 67,10 @@ fn concurrent_insertion_deletion() {
        "01",
        |s| {
            s.extend("23".chars());
            s.insert_after(Some(LogIndex(1)), '!');
            s.insert_after(LogIndex(2), '!');
        },
        |s| {
            s.remove(LogIndex(1));
            s.remove(LogIndex(2));
        },
    );



@@ 79,11 79,11 @@ fn concurrent_insertion_deletion() {
        "023!",
        "01",
        |s| {
            s.insert_after(Some(LogIndex(1)), '!');
            s.insert_after(LogIndex(2), '!');
        },
        |s| {
            s.extend("23".chars());
            s.remove(LogIndex(1));
            s.remove(LogIndex(2));
        },
    );
}


@@ 94,7 94,7 @@ fn insert_referencing_deleted_element() {
    let mut session = cfold.session(1);
    let idx = session.push_back('!');
    session.clear();
    session.insert_after(Some(idx), '?');
    session.insert_after(idx, '?');
    assert_eq!("?", format!("{}", cfold));
}


M tests/errors.rs => tests/errors.rs +11 -7
@@ 2,26 2,30 @@ use chronofold::{Change, Chronofold, ChronofoldError, LogIndex, Op, Timestamp};

#[test]
fn unknown_timestamp() {
    let mut cfold = Chronofold::<u8, char>::new();
    let unknown = Timestamp(LogIndex(0), 42);
    let mut cfold = Chronofold::<u8, char>::default();
    let unknown = Timestamp(LogIndex(1), 42);
    let op = Op::new(
        Timestamp(LogIndex(0), 1),
        Timestamp(LogIndex(1), 1),
        Some(unknown),
        Change::Insert('!'),
    );
    let err = cfold.apply(op.clone()).unwrap_err();
    assert_eq!(ChronofoldError::UnknownReference(op), err);
    assert_eq!("unknown reference <0, 42>", format!("{}", err));
    assert_eq!("unknown reference <1, 42>", format!("{}", err));
}

#[test]
fn existing_timestamp() {
    // Applying the same op twice results in a
    // `ChronofoldError::ExistingTimestamp`:
    let mut cfold = Chronofold::<u8, char>::new();
    let op = Op::new(Timestamp(LogIndex(0), 1), None, Change::Insert('.'));
    let mut cfold = Chronofold::<u8, char>::default();
    let op = Op::new(
        Timestamp(LogIndex(1), 1),
        Some(Timestamp(LogIndex(0), 0)),
        Change::Insert('.'),
    );
    assert_eq!(Ok(()), cfold.apply(op.clone()));
    let err = cfold.apply(op.clone()).unwrap_err();
    assert_eq!(ChronofoldError::ExistingTimestamp(op), err);
    assert_eq!("existing timestamp <0, 1>", format!("{}", err));
    assert_eq!("existing timestamp <1, 1>", format!("{}", err));
}

M tests/serialization.rs => tests/serialization.rs +5 -5
@@ 3,7 3,7 @@ use chronofold::{Chronofold, LogIndex};

#[test]
fn roundtrip() {
    let mut cfold = Chronofold::<usize, char>::new();
    let mut cfold = Chronofold::<usize, char>::default();
    cfold.session(1).extend("Hello world!".chars());
    let json = serde_json::to_string(&cfold).unwrap();
    eprintln!("{}", json);


@@ 12,18 12,18 @@ fn roundtrip() {

#[test]
fn empty() {
    let cfold = Chronofold::<usize, char>::new();
    assert_json_max_len(&cfold, 132);
    let cfold = Chronofold::<usize, char>::default();
    assert_json_max_len(&cfold, 166);
}

#[test]
fn local_edits_only() {
    let mut cfold = Chronofold::<usize, char>::new();
    let mut cfold = Chronofold::<usize, char>::default();
    cfold.session(1).extend("Hello world!".chars());
    cfold
        .session(1)
        .splice(LogIndex(6)..LogIndex(11), "cfold".chars());
    assert_json_max_len(&cfold, 390);
    assert_json_max_len(&cfold, 616);
}

fn assert_json_max_len(cfold: &Chronofold<usize, char>, max_len: usize) {

M tests/version.rs => tests/version.rs +8 -7
@@ 17,7 17,7 @@ fn partial_order() {

#[test]
fn iter_newer_ops() {
    let mut cfold = Chronofold::<u8, char>::new();
    let mut cfold = Chronofold::<u8, char>::default();
    cfold.session(1).extend("foo".chars());
    let v1 = cfold.version().clone();
    cfold.session(1).push_back('!');


@@ 25,8 25,8 @@ fn iter_newer_ops() {

    assert_eq!(
        vec![
            Op::new(t(3, 1), Some(t(2, 1)), Change::Insert(&'!')),
            Op::new(t(4, 2), Some(t(3, 1)), Change::Insert(&'?'))
            Op::new(t(4, 1), Some(t(3, 1)), Change::Insert(&'!')),
            Op::new(t(5, 2), Some(t(4, 1)), Change::Insert(&'?'))
        ],
        cfold.iter_newer_ops(&v1).collect::<Vec<_>>()
    );


@@ 35,11 35,12 @@ fn iter_newer_ops() {
    v2.inc(&Timestamp(LogIndex(1), 3));
    assert_eq!(
        vec![
            Op::new(t(0, 1), None, Change::Insert(&'f')),
            Op::new(t(1, 1), Some(t(0, 1)), Change::Insert(&'o')),
            Op::new(t(0, 0), None, Change::Root),
            Op::new(t(1, 1), Some(t(0, 0)), Change::Insert(&'f')),
            Op::new(t(2, 1), Some(t(1, 1)), Change::Insert(&'o')),
            Op::new(t(3, 1), Some(t(2, 1)), Change::Insert(&'!')),
            Op::new(t(4, 2), Some(t(3, 1)), Change::Insert(&'?'))
            Op::new(t(3, 1), Some(t(2, 1)), Change::Insert(&'o')),
            Op::new(t(4, 1), Some(t(3, 1)), Change::Insert(&'!')),
            Op::new(t(5, 2), Some(t(4, 1)), Change::Insert(&'?'))
        ],
        cfold.iter_newer_ops(&v2).collect::<Vec<_>>()
    );