pub trait Operation<State> {
fn apply(&self, tree: &mut State);
}
pub struct Opset<E: Operation<S> + Ord, S: Clone> {
ops: Vec<E>,
states: Vec<(usize, S)>,
cache_gap: usize,
}
impl<E: Operation<S> + Ord, S: Clone> Opset<E, S> {
pub fn new(initial_state: S, cache_gap: usize) -> Self {
Opset {
ops: Vec::new(),
cache_gap,
states: vec![(0, initial_state)],
}
}
pub fn update(&mut self, edit: E) {
let insert_point = self
.ops
.binary_search(&edit)
.expect_err("two ops had the same timestamp");
self.ops.insert(insert_point, edit);
self.recalculate(insert_point);
}
pub fn update_from_iter<I: std::iter::Iterator<Item = E>>(&mut self, ops: I) {
let mut least_insert_point = None;
for edit in ops {
let insert_point = self
.ops
.binary_search(&edit)
.expect_err("two ops had the same timestamp");
self.ops.insert(insert_point, edit);
least_insert_point = match least_insert_point {
Some(prev) if prev < insert_point => Some(prev),
_ => Some(insert_point),
};
}
if let Some(least_insert_point) = least_insert_point {
self.recalculate(least_insert_point);
}
}
fn recalculate(&mut self, insert_point: usize) {
let index_of_first_bad_state =
match self.states.binary_search_by_key(&insert_point, |(n, _)| *n) {
Ok(n) => n + 1,
Err(n) => n,
};
self.states.truncate(index_of_first_bad_state);
let (mut applied_ops, mut state) = self.states.pop().unwrap();
while applied_ops < self.ops.len() {
if self.states.len() == 0
|| self.states.last().unwrap().0 + self.cache_gap <= applied_ops
{
self.states.push((applied_ops, state.clone()));
}
self.ops[applied_ops].apply(&mut state);
applied_ops += 1;
}
self.states.push((applied_ops, state));
}
pub fn state(&self) -> &S {
&self
.states
.last()
.expect("somehow state cache was empty?")
.1
}
}
#[cfg(test)]
mod test {
use super::*;
#[derive(PartialOrd, Ord, Debug, Clone, Eq, PartialEq)]
struct TestEdit {
timestamp: usize,
value: usize,
}
impl Operation<Vec<usize>> for TestEdit {
fn apply(&self, state: &mut Vec<usize>) {
state.push(self.value);
}
}
#[test]
fn various_ops_work() {
let mut crdt = Opset::new(vec![0], 2);
crdt.update(TestEdit {
timestamp: 10,
value: 1,
});
assert_eq!(crdt.state(), &[0, 1]);
assert_eq!(crdt.states.len(), 2);
crdt.update(TestEdit {
timestamp: 5,
value: 2,
});
assert_eq!(crdt.state(), &[0, 2, 1]);
assert_eq!(crdt.states.len(), 2);
crdt.update(TestEdit {
timestamp: 15,
value: 3,
});
assert_eq!(crdt.state(), &[0, 2, 1, 3]);
assert_eq!(crdt.states.len(), 3);
crdt.update(TestEdit {
timestamp: 12,
value: 4,
});
assert_eq!(crdt.state(), &[0, 2, 1, 4, 3]);
assert_eq!(crdt.states.len(), 3);
crdt.update(TestEdit {
timestamp: 11,
value: 5,
});
assert_eq!(crdt.state(), &[0, 2, 1, 5, 4, 3]);
assert_eq!(crdt.states.len(), 4);
}
#[test]
fn various_ops_work_with_iter() {
let mut crdt = Opset::new(vec![0], 2);
let ops = vec![
TestEdit {
timestamp: 10,
value: 1,
},
TestEdit {
timestamp: 5,
value: 2,
},
TestEdit {
timestamp: 15,
value: 3,
},
TestEdit {
timestamp: 12,
value: 4,
},
TestEdit {
timestamp: 11,
value: 5,
},
];
crdt.update_from_iter(ops.into_iter());
assert_eq!(crdt.state(), &[0, 2, 1, 5, 4, 3]);
assert_eq!(crdt.states.len(), 4);
}
}