Compare commits

...

19 commits

Author SHA1 Message Date
Andrew Jeffery
295d9a9c22 Fixup 2022-04-28 14:13:52 +01:00
Andrew Jeffery
633e05a847 Update prop query caching 2022-04-28 14:13:52 +01:00
Andrew Jeffery
7d20572c49 Update map benches 2022-04-28 14:13:52 +01:00
Andrew Jeffery
d9b35c16a2 Add initial work caching prop 2022-04-28 14:13:52 +01:00
Andrew Jeffery
f25500f81b Add map bench 2022-04-28 14:13:52 +01:00
Andrew Jeffery
57aad148da Refactor ObjectData 2022-04-28 14:13:52 +01:00
Andrew Jeffery
4a7924dc60 Fixup after rebase 2022-04-28 14:13:52 +01:00
Andrew Jeffery
e51137fc28 Invalidate cache on nth 2022-04-28 14:13:52 +01:00
Andrew Jeffery
5250ad4840 Track the type of the last cached result too 2022-04-28 14:13:52 +01:00
Andrew Jeffery
bf546b2ab4 Cache inserts 2022-04-28 14:13:52 +01:00
Andrew Jeffery
0ae73981ac Fixup visualisation 2022-04-28 14:13:52 +01:00
Andrew Jeffery
537af55d5c Fixup update 2022-04-28 14:13:52 +01:00
Andrew Jeffery
4dbe29ad8f Add cache functionality on the caches 2022-04-28 14:13:52 +01:00
Andrew Jeffery
49fad13843 Add caching infrastructure 2022-04-28 14:13:52 +01:00
Andrew Jeffery
682f60c774 Add cache structs 2022-04-28 14:13:52 +01:00
Andrew Jeffery
148e52545b Group imports 2022-04-28 14:13:52 +01:00
Andrew Jeffery
e2b44091e2 Use an enum for Maps vs seqs on objectdata 2022-04-28 14:13:52 +01:00
Andrew Jeffery
0d84123ad7 Use methods on container 2022-04-28 14:13:52 +01:00
Andrew Jeffery
175596beee Change trees to objects and use objectdata struct 2022-04-28 14:13:52 +01:00
13 changed files with 425 additions and 88 deletions

View file

@ -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

48
automerge/benches/map.rs Normal file
View file

@ -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);

View file

@ -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;

View file

@ -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<ObjId>,
}
#[derive(Debug, Clone)]
pub(crate) enum ObjectDataCache {
Map(Arc<Mutex<MapOpsCache>>),
Seq(Arc<Mutex<SeqOpsCache>>),
}
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<ObjId>) -> 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<Keys<'_>> {
self.ops.keys()
}
pub(crate) fn keys_at(&self, clock: Clock) -> Option<KeysAt<'_>> {
self.ops.keys_at(clock)
}
pub(crate) fn range<'a, R: RangeBounds<String>>(
&'a self,
range: R,
meta: &'a OpSetMetadata,
) -> Option<query::Range<'a, R>> {
self.ops.range(range, meta)
}
pub(crate) fn range_at<'a, R: RangeBounds<String>>(
&'a self,
range: R,
meta: &'a OpSetMetadata,
clock: Clock,
) -> Option<query::RangeAt<'a, R>> {
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<F>(&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)
}
}

View file

@ -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<ObjId, OpTree, FxBuildHasher>,
/// The map of objects to their data.
objects: HashMap<ObjId, ObjectData, FxBuildHasher>,
/// 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<query::Keys<'_>> {
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<query::KeysAt<'_>> {
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<query::Range<'_, R>> {
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<query::RangeAt<'_, R>> {
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<F>(&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<ObjType> {
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<Self::Item> {
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;

View file

@ -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<ObjId>,
}
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<OpTreeNode>,
@ -167,7 +146,7 @@ impl OpTreeInternal {
// this replaces get_mut() because it allows the indexes to update correctly
pub(crate) fn update<F>(&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::<Vec<_>>())
assert_eq!(v, t.iter().cloned().collect::<Vec<_>>())
}
}
}

View file

@ -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,

View file

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

View file

@ -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<usize>,
pub(crate) pos: usize,
start: Option<usize>,
}
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
}
}

View file

@ -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) {

View file

@ -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<usize>,
pub(crate) pos: usize,
start: Option<usize>,
}
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();

View file

@ -210,7 +210,7 @@ impl TransactionInner {
) -> Result<OpId, AutomergeError> {
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 {

View file

@ -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<FxHasher>,
>,
objects: &'a HashMap<crate::types::ObjId, ObjectData, BuildHasherDefault<FxHasher>>,
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(