diff --git a/automerge/Cargo.toml b/automerge/Cargo.toml index ae95fa4e..c56ee24d 100644 --- a/automerge/Cargo.toml +++ b/automerge/Cargo.toml @@ -43,3 +43,8 @@ proptest = { version = "^1.0.0", default-features = false, features = ["std"] } serde_json = { version = "^1.0.73", features=["float_roundtrip"], default-features=true } maplit = { version = "^1.0" } decorum = "0.3.1" +criterion = "0.3.5" + +[[bench]] +name = "map" +harness = false diff --git a/automerge/benches/map.rs b/automerge/benches/map.rs new file mode 100644 index 00000000..19141d29 --- /dev/null +++ b/automerge/benches/map.rs @@ -0,0 +1,48 @@ +use automerge::{transaction::Transactable, Automerge, ROOT}; +use criterion::{criterion_group, criterion_main, Criterion}; + +fn query_single(doc: &Automerge, rounds: u32) { + for _ in 0..rounds { + // repeatedly get the last key + doc.get(ROOT, (rounds - 1).to_string()).unwrap(); + } +} + +fn query_range(doc: &Automerge, rounds: u32) { + for i in 0..rounds { + doc.get(ROOT, i.to_string()).unwrap(); + } +} + +fn put_doc(doc: &mut Automerge, rounds: u32) { + for i in 0..rounds { + let mut tx = doc.transaction(); + tx.put(ROOT, i.to_string(), "value").unwrap(); + tx.commit(); + } +} + +fn bench(c: &mut Criterion) { + let mut group = c.benchmark_group("map"); + + let rounds = 10_000; + let mut doc = Automerge::new(); + put_doc(&mut doc, rounds); + + group.bench_function("query single", |b| b.iter(|| query_single(&doc, rounds))); + + group.bench_function("query range", |b| b.iter(|| query_range(&doc, rounds))); + + group.bench_function("put", |b| { + b.iter_batched( + Automerge::new, + |mut doc| put_doc(&mut doc, rounds), + criterion::BatchSize::LargeInput, + ) + }); + + group.finish(); +} + +criterion_group!(benches, bench); +criterion_main!(benches); diff --git a/automerge/src/lib.rs b/automerge/src/lib.rs index bb39de25..f4888506 100644 --- a/automerge/src/lib.rs +++ b/automerge/src/lib.rs @@ -67,6 +67,7 @@ mod indexed_cache; mod keys; mod keys_at; mod legacy; +mod object_data; mod op_observer; mod op_set; mod op_tree; diff --git a/automerge/src/object_data.rs b/automerge/src/object_data.rs new file mode 100644 index 00000000..98b86f9c --- /dev/null +++ b/automerge/src/object_data.rs @@ -0,0 +1,176 @@ +use std::ops::RangeBounds; +use std::sync::{Arc, Mutex}; + +use crate::clock::Clock; +use crate::op_tree::{OpSetMetadata, OpTreeInternal}; +use crate::query::{self, TreeQuery}; +use crate::types::{Key, ObjId}; +use crate::types::{Op, OpId}; +use crate::{query::Keys, query::KeysAt, ObjType}; + +#[derive(Debug, Default, Clone, PartialEq)] +pub(crate) struct MapOpsCache { + pub(crate) last: Option<(Key, usize)>, +} + +impl MapOpsCache { + fn lookup<'a, Q: TreeQuery<'a>>(&self, query: &mut Q) -> bool { + query.cache_lookup_map(self) + } + + fn update<'a, Q: TreeQuery<'a>>(&mut self, query: &Q) { + query.cache_update_map(self); + // TODO: fixup the cache (reordering etc.) + } +} + +#[derive(Debug, Default, Clone, PartialEq)] +pub(crate) struct SeqOpsCache { + // last insertion (list index, tree index, whether the last op was an insert, opid to be inserted) + // TODO: invalidation + pub(crate) last: Option<(usize, usize, bool, OpId)>, +} + +impl SeqOpsCache { + fn lookup<'a, Q: TreeQuery<'a>>(&self, query: &mut Q) -> bool { + query.cache_lookup_seq(self) + } + + fn update<'a, Q: TreeQuery<'a>>(&mut self, query: &Q) { + query.cache_update_seq(self); + // TODO: fixup the cache (reordering etc.) + } +} + +/// Stores the data for an object. +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct ObjectData { + cache: ObjectDataCache, + /// The type of this object. + typ: ObjType, + /// The operations pertaining to this object. + pub(crate) ops: OpTreeInternal, + /// The id of the parent object, root has no parent. + pub(crate) parent: Option, +} + +#[derive(Debug, Clone)] +pub(crate) enum ObjectDataCache { + Map(Arc>), + Seq(Arc>), +} + +impl PartialEq for ObjectDataCache { + fn eq(&self, other: &ObjectDataCache) -> bool { + match (self, other) { + (ObjectDataCache::Map(_), ObjectDataCache::Map(_)) => true, + (ObjectDataCache::Map(_), ObjectDataCache::Seq(_)) => false, + (ObjectDataCache::Seq(_), ObjectDataCache::Map(_)) => false, + (ObjectDataCache::Seq(_), ObjectDataCache::Seq(_)) => true, + } + } +} + +impl ObjectData { + pub(crate) fn root() -> Self { + ObjectData { + cache: ObjectDataCache::Map(Default::default()), + typ: ObjType::Map, + ops: Default::default(), + parent: None, + } + } + + pub(crate) fn new(typ: ObjType, parent: Option) -> Self { + let internal = match typ { + ObjType::Map | ObjType::Table => ObjectDataCache::Map(Default::default()), + ObjType::List | ObjType::Text => ObjectDataCache::Seq(Default::default()), + }; + ObjectData { + cache: internal, + typ, + ops: Default::default(), + parent, + } + } + + pub(crate) fn keys(&self) -> Option> { + self.ops.keys() + } + + pub(crate) fn keys_at(&self, clock: Clock) -> Option> { + self.ops.keys_at(clock) + } + + pub(crate) fn range<'a, R: RangeBounds>( + &'a self, + range: R, + meta: &'a OpSetMetadata, + ) -> Option> { + self.ops.range(range, meta) + } + + pub(crate) fn range_at<'a, R: RangeBounds>( + &'a self, + range: R, + meta: &'a OpSetMetadata, + clock: Clock, + ) -> Option> { + self.ops.range_at(range, meta, clock) + } + + pub(crate) fn search<'a, 'b: 'a, Q>(&'b self, mut query: Q, metadata: &OpSetMetadata) -> Q + where + Q: TreeQuery<'a>, + { + match self { + ObjectData { + ops, + cache: ObjectDataCache::Map(cache), + .. + } => { + let mut cache = cache.lock().unwrap(); + if !cache.lookup(&mut query) { + query = ops.search(query, metadata); + } + cache.update(&query); + query + } + ObjectData { + ops, + cache: ObjectDataCache::Seq(cache), + .. + } => { + let mut cache = cache.lock().unwrap(); + if !cache.lookup(&mut query) { + query = ops.search(query, metadata); + } + cache.update(&query); + query + } + } + } + + pub(crate) fn update(&mut self, index: usize, f: F) + where + F: FnOnce(&mut Op), + { + self.ops.update(index, f) + } + + pub(crate) fn remove(&mut self, index: usize) -> Op { + self.ops.remove(index) + } + + pub(crate) fn insert(&mut self, index: usize, op: Op) { + self.ops.insert(index, op) + } + + pub(crate) fn typ(&self) -> ObjType { + self.typ + } + + pub(crate) fn get(&self, index: usize) -> Option<&Op> { + self.ops.get(index) + } +} diff --git a/automerge/src/op_set.rs b/automerge/src/op_set.rs index 7928fce9..80a5d77e 100644 --- a/automerge/src/op_set.rs +++ b/automerge/src/op_set.rs @@ -1,7 +1,7 @@ use crate::clock::Clock; use crate::exid::ExId; use crate::indexed_cache::IndexedCache; -use crate::op_tree::OpTree; +use crate::object_data::ObjectData; use crate::query::{self, OpIdSearch, TreeQuery}; use crate::types::{self, ActorId, Key, ObjId, Op, OpId, OpType}; use crate::{ObjType, OpObserver}; @@ -14,8 +14,8 @@ pub(crate) type OpSet = OpSetInternal; #[derive(Debug, Clone, PartialEq)] pub(crate) struct OpSetInternal { - /// The map of objects to their type and ops. - trees: HashMap, + /// The map of objects to their data. + objects: HashMap, /// The number of operations in the opset. length: usize, /// Metadata about the operations in this opset. @@ -24,10 +24,10 @@ pub(crate) struct OpSetInternal { impl OpSetInternal { pub(crate) fn new() -> Self { - let mut trees: HashMap<_, _, _> = Default::default(); - trees.insert(ObjId::root(), OpTree::new()); + let mut objects: HashMap<_, _, _> = Default::default(); + objects.insert(ObjId::root(), ObjectData::root()); OpSetInternal { - trees, + objects, length: 0, m: OpSetMetadata { actors: IndexedCache::new(), @@ -45,7 +45,7 @@ impl OpSetInternal { } pub(crate) fn iter(&self) -> Iter<'_> { - let mut objs: Vec<_> = self.trees.keys().collect(); + let mut objs: Vec<_> = self.objects.keys().collect(); objs.sort_by(|a, b| self.m.lamport_cmp(a.0, b.0)); Iter { inner: self, @@ -56,22 +56,22 @@ impl OpSetInternal { } pub(crate) fn parent_object(&self, obj: &ObjId) -> Option<(ObjId, Key)> { - let parent = self.trees.get(obj)?.parent?; + let parent = self.objects.get(obj)?.parent?; let key = self.search(&parent, OpIdSearch::new(obj.0)).key().unwrap(); Some((parent, key)) } pub(crate) fn keys(&self, obj: ObjId) -> Option> { - if let Some(tree) = self.trees.get(&obj) { - tree.internal.keys() + if let Some(object) = self.objects.get(&obj) { + object.keys() } else { None } } pub(crate) fn keys_at(&self, obj: ObjId, clock: Clock) -> Option> { - if let Some(tree) = self.trees.get(&obj) { - tree.internal.keys_at(clock) + if let Some(object) = self.objects.get(&obj) { + object.keys_at(clock) } else { None } @@ -82,8 +82,8 @@ impl OpSetInternal { obj: ObjId, range: R, ) -> Option> { - if let Some(tree) = self.trees.get(&obj) { - tree.internal.range(range, &self.m) + if let Some(tree) = self.objects.get(&obj) { + tree.range(range, &self.m) } else { None } @@ -95,8 +95,8 @@ impl OpSetInternal { range: R, clock: Clock, ) -> Option> { - if let Some(tree) = self.trees.get(&obj) { - tree.internal.range_at(range, &self.m, clock) + if let Some(tree) = self.objects.get(&obj) { + tree.range_at(range, &self.m, clock) } else { None } @@ -106,8 +106,8 @@ impl OpSetInternal { where Q: TreeQuery<'a>, { - if let Some(tree) = self.trees.get(obj) { - tree.internal.search(query, &self.m) + if let Some(object) = self.objects.get(obj) { + object.search(query, &self.m) } else { query } @@ -115,20 +115,20 @@ impl OpSetInternal { pub(crate) fn replace(&mut self, obj: &ObjId, index: usize, f: F) where - F: FnMut(&mut Op), + F: FnOnce(&mut Op), { - if let Some(tree) = self.trees.get_mut(obj) { - tree.internal.update(index, f) + if let Some(object) = self.objects.get_mut(obj) { + object.update(index, f) } } pub(crate) fn remove(&mut self, obj: &ObjId, index: usize) -> Op { // this happens on rollback - be sure to go back to the old state - let tree = self.trees.get_mut(obj).unwrap(); + let object = self.objects.get_mut(obj).unwrap(); self.length -= 1; - let op = tree.internal.remove(index); + let op = object.remove(index); if let OpType::Make(_) = &op.action { - self.trees.remove(&op.id.into()); + self.objects.remove(&op.id.into()); } op } @@ -139,19 +139,12 @@ impl OpSetInternal { pub(crate) fn insert(&mut self, index: usize, obj: &ObjId, element: Op) { if let OpType::Make(typ) = element.action { - self.trees.insert( - element.id.into(), - OpTree { - internal: Default::default(), - objtype: typ, - parent: Some(*obj), - }, - ); + self.objects + .insert(element.id.into(), ObjectData::new(typ, Some(*obj))); } - if let Some(tree) = self.trees.get_mut(obj) { - //let tree = self.trees.get_mut(&element.obj).unwrap(); - tree.internal.insert(index, element); + if let Some(object) = self.objects.get_mut(obj) { + object.insert(index, element); self.length += 1; } } @@ -244,13 +237,13 @@ impl OpSetInternal { } pub(crate) fn object_type(&self, id: &ObjId) -> Option { - self.trees.get(id).map(|tree| tree.objtype) + self.objects.get(id).map(|object| object.typ()) } #[cfg(feature = "optree-visualisation")] pub(crate) fn visualise(&self) -> String { let mut out = Vec::new(); - let graph = super::visualisation::GraphVisualisation::construct(&self.trees, &self.m); + let graph = super::visualisation::GraphVisualisation::construct(&self.objects, &self.m); dot::render(&graph, &mut out).unwrap(); String::from_utf8_lossy(&out[..]).to_string() } @@ -285,8 +278,8 @@ impl<'a> Iterator for Iter<'a> { fn next(&mut self) -> Option { let mut result = None; for obj in self.objs.iter().skip(self.index) { - let tree = self.inner.trees.get(obj)?; - result = tree.internal.get(self.sub_index).map(|op| (*obj, op)); + let object = self.inner.objects.get(obj)?; + result = object.get(self.sub_index).map(|op| (*obj, op)); if result.is_some() { self.sub_index += 1; break; diff --git a/automerge/src/op_tree.rs b/automerge/src/op_tree.rs index 908522d5..79e110af 100644 --- a/automerge/src/op_tree.rs +++ b/automerge/src/op_tree.rs @@ -6,36 +6,15 @@ use std::{ }; pub(crate) use crate::op_set::OpSetMetadata; +use crate::types::{Op, OpId}; use crate::{ clock::Clock, query::{self, Index, QueryResult, TreeQuery}, }; -use crate::{ - types::{ObjId, Op, OpId}, - ObjType, -}; use std::collections::HashSet; pub(crate) const B: usize = 16; -#[derive(Debug, Clone, PartialEq)] -pub(crate) struct OpTree { - pub(crate) internal: OpTreeInternal, - pub(crate) objtype: ObjType, - /// The id of the parent object, root has no parent. - pub(crate) parent: Option, -} - -impl OpTree { - pub(crate) fn new() -> Self { - Self { - internal: Default::default(), - objtype: ObjType::Map, - parent: None, - } - } -} - #[derive(Clone, Debug)] pub(crate) struct OpTreeInternal { pub(crate) root_node: Option, @@ -167,7 +146,7 @@ impl OpTreeInternal { // this replaces get_mut() because it allows the indexes to update correctly pub(crate) fn update(&mut self, index: usize, f: F) where - F: FnMut(&mut Op), + F: FnOnce(&mut Op), { if self.len() > index { self.root_node.as_mut().unwrap().update(index, f); @@ -692,36 +671,36 @@ mod tests { #[test] fn insert() { - let mut t: OpTree = OpTree::new(); + let mut t = OpTreeInternal::new(); - t.internal.insert(0, op()); - t.internal.insert(1, op()); - t.internal.insert(0, op()); - t.internal.insert(0, op()); - t.internal.insert(0, op()); - t.internal.insert(3, op()); - t.internal.insert(4, op()); + t.insert(0, op()); + t.insert(1, op()); + t.insert(0, op()); + t.insert(0, op()); + t.insert(0, op()); + t.insert(3, op()); + t.insert(4, op()); } #[test] fn insert_book() { - let mut t: OpTree = OpTree::new(); + let mut t = OpTreeInternal::new(); for i in 0..100 { - t.internal.insert(i % 2, op()); + t.insert(i % 2, op()); } } #[test] fn insert_book_vec() { - let mut t: OpTree = OpTree::new(); + let mut t = OpTreeInternal::new(); let mut v = Vec::new(); for i in 0..100 { - t.internal.insert(i % 3, op()); + t.insert(i % 3, op()); v.insert(i % 3, op()); - assert_eq!(v, t.internal.iter().cloned().collect::>()) + assert_eq!(v, t.iter().cloned().collect::>()) } } } diff --git a/automerge/src/query.rs b/automerge/src/query.rs index 06225885..790c88d3 100644 --- a/automerge/src/query.rs +++ b/automerge/src/query.rs @@ -1,3 +1,4 @@ +use crate::object_data::{MapOpsCache, SeqOpsCache}; use crate::op_tree::{OpSetMetadata, OpTreeNode}; use crate::types::{Clock, Counter, ElemId, Op, OpId, OpType, ScalarValue}; use fxhash::FxBuildHasher; @@ -7,6 +8,7 @@ use std::fmt::Debug; mod elem_id_pos; mod insert; +mod insert_prop; mod keys; mod keys_at; mod len; @@ -25,6 +27,7 @@ mod seek_op_with_patch; pub(crate) use elem_id_pos::ElemIdPos; pub(crate) use insert::InsertNth; +pub(crate) use insert_prop::InsertProp; pub(crate) use keys::Keys; pub(crate) use keys_at::KeysAt; pub(crate) use len::Len; @@ -50,6 +53,24 @@ pub(crate) struct CounterData { } pub(crate) trait TreeQuery<'a> { + fn cache_lookup_map(&mut self, _cache: &MapOpsCache) -> bool { + // by default we haven't found something in the cache + false + } + + fn cache_update_map(&self, _cache: &mut MapOpsCache) { + // by default we don't have anything to update in the cache + } + + fn cache_lookup_seq(&mut self, _cache: &SeqOpsCache) -> bool { + // by default we haven't found something in the cache + false + } + + fn cache_update_seq(&self, _cache: &mut SeqOpsCache) { + // by default we don't have anything to update in the cache + } + #[inline(always)] fn query_node_with_metadata( &mut self, diff --git a/automerge/src/query/insert.rs b/automerge/src/query/insert.rs index 6f69474c..f5fecf9b 100644 --- a/automerge/src/query/insert.rs +++ b/automerge/src/query/insert.rs @@ -1,13 +1,15 @@ use crate::error::AutomergeError; use crate::op_tree::OpTreeNode; use crate::query::{QueryResult, TreeQuery}; -use crate::types::{ElemId, Key, Op, HEAD}; +use crate::types::{ElemId, Key, Op, OpId, HEAD}; use std::fmt::Debug; #[derive(Debug, Clone, PartialEq)] pub(crate) struct InsertNth { /// the index in the realised list that we want to insert at target: usize, + /// OpId of the op we are trying to find a location for. + id: OpId, /// the number of visible operations seen seen: usize, //pub pos: usize, @@ -22,7 +24,7 @@ pub(crate) struct InsertNth { } impl InsertNth { - pub(crate) fn new(target: usize) -> Self { + pub(crate) fn new(target: usize, opid: OpId) -> Self { let (valid, last_valid_insert) = if target == 0 { (Some(0), Some(HEAD)) } else { @@ -30,6 +32,7 @@ impl InsertNth { }; InsertNth { target, + id: opid, seen: 0, n: 0, valid, @@ -62,6 +65,23 @@ impl InsertNth { } impl<'a> TreeQuery<'a> for InsertNth { + fn cache_lookup_seq(&mut self, cache: &crate::object_data::SeqOpsCache) -> bool { + if let Some((last_target_index, last_tree_index, insert, last_id)) = cache.last { + if insert && last_target_index + 1 == self.target { + // we can use the cached value + let key = ElemId(last_id); + self.last_valid_insert = Some(key); + self.n = last_tree_index + 1; + return true; + } + } + false + } + + fn cache_update_seq(&self, cache: &mut crate::object_data::SeqOpsCache) { + cache.last = Some((self.target, self.pos(), true, self.id)); + } + fn query_node(&mut self, child: &OpTreeNode) -> QueryResult { // if this node has some visible elements then we may find our target within let mut num_vis = child.index.visible_len(); diff --git a/automerge/src/query/insert_prop.rs b/automerge/src/query/insert_prop.rs new file mode 100644 index 00000000..73078d58 --- /dev/null +++ b/automerge/src/query/insert_prop.rs @@ -0,0 +1,68 @@ +use crate::op_tree::{OpSetMetadata, OpTreeNode}; +use crate::query::{binary_search_by, QueryResult, TreeQuery}; +use crate::types::{Key, Op}; +use std::fmt::Debug; + +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct InsertProp<'a> { + key: Key, + pub(crate) ops: Vec<&'a Op>, + pub(crate) ops_pos: Vec, + pub(crate) pos: usize, + start: Option, +} + +impl<'a> InsertProp<'a> { + pub(crate) fn new(prop: usize) -> Self { + InsertProp { + key: Key::Map(prop), + ops: vec![], + ops_pos: vec![], + pos: 0, + start: None, + } + } +} + +impl<'a> TreeQuery<'a> for InsertProp<'a> { + fn cache_lookup_map(&mut self, cache: &crate::object_data::MapOpsCache) -> bool { + if let Some((last_key, last_pos)) = cache.last { + if last_key == self.key { + self.start = Some(last_pos); + } + } + // don't have all of the result yet + false + } + + fn cache_update_map(&self, cache: &mut crate::object_data::MapOpsCache) { + cache.last = None + } + + fn query_node_with_metadata( + &mut self, + child: &'a OpTreeNode, + m: &OpSetMetadata, + ) -> QueryResult { + let start = if let Some(start) = self.start { + debug_assert!(binary_search_by(child, |op| m.key_cmp(&op.key, &self.key)) >= start); + start + } else { + binary_search_by(child, |op| m.key_cmp(&op.key, &self.key)) + }; + self.start = Some(start); + self.pos = start; + for pos in start..child.len() { + let op = child.get(pos).unwrap(); + if op.key != self.key { + break; + } + if op.visible() { + self.ops.push(op); + self.ops_pos.push(pos); + } + self.pos += 1; + } + QueryResult::Finish + } +} diff --git a/automerge/src/query/nth.rs b/automerge/src/query/nth.rs index 3924fc62..f6d47215 100644 --- a/automerge/src/query/nth.rs +++ b/automerge/src/query/nth.rs @@ -40,6 +40,14 @@ impl<'a> Nth<'a> { } impl<'a> TreeQuery<'a> for Nth<'a> { + fn cache_lookup_seq(&mut self, _cache: &crate::object_data::SeqOpsCache) -> bool { + false + } + + fn cache_update_seq(&self, cache: &mut crate::object_data::SeqOpsCache) { + cache.last = None; + } + fn query_node(&mut self, child: &OpTreeNode) -> QueryResult { let mut num_vis = child.index.visible_len(); if child.index.has_visible(&self.last_seen) { diff --git a/automerge/src/query/prop.rs b/automerge/src/query/prop.rs index 7fcb8559..8cbfb358 100644 --- a/automerge/src/query/prop.rs +++ b/automerge/src/query/prop.rs @@ -1,7 +1,6 @@ use crate::op_tree::{OpSetMetadata, OpTreeNode}; use crate::query::{binary_search_by, QueryResult, TreeQuery}; use crate::types::{Key, Op}; -use std::fmt::Debug; #[derive(Debug, Clone, PartialEq)] pub(crate) struct Prop<'a> { @@ -9,6 +8,7 @@ pub(crate) struct Prop<'a> { pub(crate) ops: Vec<&'a Op>, pub(crate) ops_pos: Vec, pub(crate) pos: usize, + start: Option, } impl<'a> Prop<'a> { @@ -18,17 +18,38 @@ impl<'a> Prop<'a> { ops: vec![], ops_pos: vec![], pos: 0, + start: None, } } } impl<'a> TreeQuery<'a> for Prop<'a> { + fn cache_lookup_map(&mut self, cache: &crate::object_data::MapOpsCache) -> bool { + if let Some((last_key, last_pos)) = cache.last { + if last_key == self.key { + self.start = Some(last_pos); + } + } + // don't have all of the result yet + false + } + + fn cache_update_map(&self, cache: &mut crate::object_data::MapOpsCache) { + cache.last = self.start.map(|start| (self.key, start)); + } + fn query_node_with_metadata( &mut self, child: &'a OpTreeNode, m: &OpSetMetadata, ) -> QueryResult { - let start = binary_search_by(child, |op| m.key_cmp(&op.key, &self.key)); + let start = if let Some(start) = self.start { + debug_assert!(binary_search_by(child, |op| m.key_cmp(&op.key, &self.key)) >= start); + start + } else { + binary_search_by(child, |op| m.key_cmp(&op.key, &self.key)) + }; + self.start = Some(start); self.pos = start; for pos in start..child.len() { let op = child.get(pos).unwrap(); diff --git a/automerge/src/transaction/inner.rs b/automerge/src/transaction/inner.rs index ebfb20ce..7a42a5ee 100644 --- a/automerge/src/transaction/inner.rs +++ b/automerge/src/transaction/inner.rs @@ -210,7 +210,7 @@ impl TransactionInner { ) -> Result { let id = self.next_id(); - let query = doc.ops.search(&obj, query::InsertNth::new(index)); + let query = doc.ops.search(&obj, query::InsertNth::new(index, id)); let key = query.key()?; @@ -255,7 +255,7 @@ impl TransactionInner { let id = self.next_id(); let prop_index = doc.ops.m.props.cache(prop.clone()); - let query = doc.ops.search(&obj, query::Prop::new(prop_index)); + let query = doc.ops.search(&obj, query::InsertProp::new(prop_index)); // no key present to delete if query.ops.is_empty() && action == OpType::Delete { diff --git a/automerge/src/visualisation.rs b/automerge/src/visualisation.rs index 5e6dae6f..6107eeee 100644 --- a/automerge/src/visualisation.rs +++ b/automerge/src/visualisation.rs @@ -1,3 +1,4 @@ +use crate::object_data::ObjectData; use crate::types::ObjId; use fxhash::FxHasher; use std::{borrow::Cow, collections::HashMap, hash::BuildHasherDefault}; @@ -42,16 +43,12 @@ pub(crate) struct GraphVisualisation<'a> { impl<'a> GraphVisualisation<'a> { pub(super) fn construct( - trees: &'a HashMap< - crate::types::ObjId, - crate::op_tree::OpTree, - BuildHasherDefault, - >, + objects: &'a HashMap>, metadata: &'a crate::op_set::OpSetMetadata, ) -> GraphVisualisation<'a> { let mut nodes = HashMap::new(); - for (obj_id, tree) in trees { - if let Some(root_node) = &tree.internal.root_node { + for (obj_id, object_data) in objects { + if let Some(root_node) = &object_data.ops.root_node { let tree_id = Self::construct_nodes(root_node, obj_id, &mut nodes, metadata); let obj_tree_id = NodeId::default(); nodes.insert(