diff --git a/automerge/src/op_set.rs b/automerge/src/op_set.rs index e1fe7501..2b9ef409 100644 --- a/automerge/src/op_set.rs +++ b/automerge/src/op_set.rs @@ -160,6 +160,30 @@ impl OpSetInternal { self.length } + pub(crate) fn hint_clear(&mut self, obj: &ObjId) { + if let Some(tree) = self.trees.get_mut(obj) { + tree.internal.cache.clear(); + } + } + + pub(crate) fn hint_delete(&mut self, pos: usize, obj: &ObjId) { + if let Some(tree) = self.trees.get_mut(obj) { + tree.internal.cache.delete(pos); + } + } + + pub(crate) fn hint_shift(&mut self, index: Option, pos: usize, obj: &ObjId) { + if let Some(tree) = self.trees.get_mut(obj) { + tree.internal.cache.shift(index, pos); + } + } + + pub(crate) fn hint_insert(&mut self, index: usize, pos: usize, obj: &ObjId, element: &Op) { + if let Some(tree) = self.trees.get_mut(obj) { + tree.internal.cache.insert(index, pos, element); + } + } + pub(crate) fn insert(&mut self, index: usize, obj: &ObjId, element: Op) { if let OpType::Make(typ) = element.action { self.trees.insert( @@ -180,6 +204,8 @@ impl OpSetInternal { } pub(crate) fn insert_op(&mut self, obj: &ObjId, op: Op) -> Op { + self.hint_clear(obj); + let q = self.search(obj, query::SeekOp::new(&op)); let succ = q.succ; @@ -201,6 +227,9 @@ impl OpSetInternal { op: Op, observer: &mut Obs, ) -> Op { + // FIXME + self.hint_clear(obj); + let q = self.search(obj, query::SeekOpWithPatch::new(&op)); let query::SeekOpWithPatch { diff --git a/automerge/src/op_tree.rs b/automerge/src/op_tree.rs index c338c145..1492ee4f 100644 --- a/automerge/src/op_tree.rs +++ b/automerge/src/op_tree.rs @@ -11,16 +11,77 @@ use crate::{ query::{self, Index, QueryResult, ReplaceArgs, TreeQuery}, }; use crate::{ - types::{ObjId, Op, OpId}, + types::{Key, ObjId, Op, OpId}, ObjType, }; -use std::collections::HashSet; +use std::collections::{HashSet, VecDeque}; pub(crate) const B: usize = 16; mod iter; pub(crate) use iter::OpTreeIter; +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct ListCachePoint { + pub(crate) index: usize, // list index + pub(crate) pos: usize, // op tree position + pub(crate) key: Key, +} + +#[derive(Debug, Clone, PartialEq, Default)] +pub(crate) struct QueryCache { + index: VecDeque, +} + +const CACHE_MAX: usize = 4; + +impl QueryCache { + pub(crate) fn find(&self, index: usize) -> Option<&ListCachePoint> { + self.index.iter().find(|c| c.index == index) + } + + pub(crate) fn insert(&mut self, index: usize, pos: usize, op: &Op) { + for c in &mut self.index { + if c.pos >= pos { + c.pos += 1; + c.index += 1; + } + } + if self.index.len() >= CACHE_MAX { + self.index.pop_front(); + } + self.index.push_back(ListCachePoint { + index, + pos, + key: op.elemid_or_key(), + }); + } + + pub(crate) fn clear(&mut self) { + self.index.truncate(0) + } + + pub(crate) fn delete(&mut self, pos: usize) { + for c in &mut self.index { + if c.pos >= pos { + c.index -= 1; + } + } + self.index.retain(|c| c.pos + 1 != pos); + } + + pub(crate) fn shift(&mut self, index: Option, pos: usize) { + for c in &mut self.index { + if c.pos >= pos { + c.pos += 1; + } + } + if let Some(index) = index { + self.index.retain(|c| c.index != index); + } + } +} + #[derive(Debug, Clone, PartialEq)] pub(crate) struct OpTree { pub(crate) internal: OpTreeInternal, @@ -46,6 +107,7 @@ impl OpTree { #[derive(Clone, Debug)] pub(crate) struct OpTreeInternal { pub(crate) root_node: Option, + pub(crate) cache: QueryCache, } #[derive(Clone, Debug)] @@ -59,7 +121,10 @@ pub(crate) struct OpTreeNode { impl OpTreeInternal { /// Construct a new, empty, sequence. pub(crate) fn new() -> Self { - Self { root_node: None } + Self { + root_node: None, + cache: Default::default(), + } } /// Get the length of the sequence. @@ -121,13 +186,15 @@ impl OpTreeInternal { where Q: TreeQuery<'a>, { - self.root_node - .as_ref() - .map(|root| match query.query_node_with_metadata(root, m) { - QueryResult::Descend => root.search(&mut query, m, None), - QueryResult::Skip(skip) => root.search(&mut query, m, Some(skip)), - _ => true, - }); + if !query.read_cache(&self.cache) { + self.root_node + .as_ref() + .map(|root| match query.query_node_with_metadata(root, m) { + QueryResult::Descend => root.search(&mut query, m, None), + QueryResult::Skip(skip) => root.search(&mut query, m, Some(skip)), + _ => true, + }); + } query } diff --git a/automerge/src/query.rs b/automerge/src/query.rs index e3d2f372..f3b205b2 100644 --- a/automerge/src/query.rs +++ b/automerge/src/query.rs @@ -1,4 +1,4 @@ -use crate::op_tree::{OpSetMetadata, OpTreeNode}; +use crate::op_tree::{OpSetMetadata, OpTreeNode, QueryCache}; use crate::types::{Clock, Counter, Key, Op, OpId, OpType, ScalarValue}; use fxhash::FxBuildHasher; use std::cmp::Ordering; @@ -85,6 +85,12 @@ pub(crate) trait TreeQuery<'a> { fn query_element(&mut self, _element: &'a Op) -> QueryResult { panic!("invalid element query") } + + fn read_cache(&mut self, _cache: &QueryCache) -> bool { + false + } + + fn update_cache(&mut self, _cache: &mut QueryCache) {} } #[derive(Debug, Clone, PartialEq)] diff --git a/automerge/src/query/insert.rs b/automerge/src/query/insert.rs index 9e495c49..1f34c168 100644 --- a/automerge/src/query/insert.rs +++ b/automerge/src/query/insert.rs @@ -1,6 +1,6 @@ use crate::error::AutomergeError; use crate::op_tree::OpTreeNode; -use crate::query::{QueryResult, TreeQuery}; +use crate::query::{QueryCache, QueryResult, TreeQuery}; use crate::types::{ElemId, Key, Op, HEAD}; use std::fmt::Debug; @@ -46,16 +46,6 @@ impl InsertNth { pub(crate) fn key(&self) -> Result { self.last_valid_insert .ok_or(AutomergeError::InvalidIndex(self.target)) - //if self.target == 0 { - /* - if self.last_insert.is_none() { - Ok(HEAD.into()) - } else if self.seen == self.target && self.last_insert.is_some() { - Ok(Key::Seq(self.last_insert.unwrap())) - } else { - Err(AutomergeError::InvalidIndex(self.target)) - } - */ } } @@ -110,4 +100,18 @@ impl<'a> TreeQuery<'a> for InsertNth { self.n += 1; QueryResult::Next } + + // AXIOM: ListCachePoint is only for single item inserts + // remove cache points on update + + fn read_cache(&mut self, cache: &QueryCache) -> bool { + if self.target > 0 { + if let Some(c) = cache.find(self.target - 1) { + self.last_valid_insert = Some(c.key); + self.valid = Some(c.pos + 1); + return true; + } + } + false + } } diff --git a/automerge/src/transaction/inner.rs b/automerge/src/transaction/inner.rs index 6969e317..7090ce3d 100644 --- a/automerge/src/transaction/inner.rs +++ b/automerge/src/transaction/inner.rs @@ -76,6 +76,7 @@ impl TransactionInner { let num = self.pending_ops(); // remove in reverse order so sets are removed before makes etc... for (obj, _prop, op) in self.operations.into_iter().rev() { + doc.ops.hint_clear(&obj); for pred_id in &op.pred { if let Some(p) = doc.ops.search(&obj, OpIdSearch::new(*pred_id)).index() { doc.ops.replace(&obj, p, |o| o.remove_succ(&op)); @@ -169,7 +170,10 @@ impl TransactionInner { } if !op.is_delete() { + doc.ops.hint_shift((&prop).into(), pos, &obj); doc.ops.insert(pos, &obj, op.clone()); + } else { + doc.ops.hint_delete(pos, &obj) } self.operations.push((obj, prop, op)); @@ -223,6 +227,8 @@ impl TransactionInner { insert: true, }; + let pos = query.pos(); + doc.ops.hint_insert(index, pos, &obj, &op); doc.ops.insert(query.pos(), &obj, op.clone()); self.operations.push((obj, Prop::Seq(index), op)); diff --git a/automerge/src/types.rs b/automerge/src/types.rs index 1c67afe2..8c3c212a 100644 --- a/automerge/src/types.rs +++ b/automerge/src/types.rs @@ -575,3 +575,12 @@ impl From for wasm_bindgen::JsValue { } } } + +impl From<&Prop> for Option { + fn from(prop: &Prop) -> Self { + match prop { + Prop::Map(_) => None, + Prop::Seq(index) => Some(*index), + } + } +}