Compare commits
19 commits
main
...
experiment
Author | SHA1 | Date | |
---|---|---|---|
|
295d9a9c22 | ||
|
633e05a847 | ||
|
7d20572c49 | ||
|
d9b35c16a2 | ||
|
f25500f81b | ||
|
57aad148da | ||
|
4a7924dc60 | ||
|
e51137fc28 | ||
|
5250ad4840 | ||
|
bf546b2ab4 | ||
|
0ae73981ac | ||
|
537af55d5c | ||
|
4dbe29ad8f | ||
|
49fad13843 | ||
|
682f60c774 | ||
|
148e52545b | ||
|
e2b44091e2 | ||
|
0d84123ad7 | ||
|
175596beee |
13 changed files with 425 additions and 88 deletions
|
@ -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
48
automerge/benches/map.rs
Normal 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);
|
|
@ -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;
|
||||
|
|
176
automerge/src/object_data.rs
Normal file
176
automerge/src/object_data.rs
Normal 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)
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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<_>>())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
|
68
automerge/src/query/insert_prop.rs
Normal file
68
automerge/src/query/insert_prop.rs
Normal 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
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in a new issue