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<_>>()
);