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 }
|
serde_json = { version = "^1.0.73", features=["float_roundtrip"], default-features=true }
|
||||||
maplit = { version = "^1.0" }
|
maplit = { version = "^1.0" }
|
||||||
decorum = "0.3.1"
|
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;
|
||||||
mod keys_at;
|
mod keys_at;
|
||||||
mod legacy;
|
mod legacy;
|
||||||
|
mod object_data;
|
||||||
mod op_observer;
|
mod op_observer;
|
||||||
mod op_set;
|
mod op_set;
|
||||||
mod op_tree;
|
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::clock::Clock;
|
||||||
use crate::exid::ExId;
|
use crate::exid::ExId;
|
||||||
use crate::indexed_cache::IndexedCache;
|
use crate::indexed_cache::IndexedCache;
|
||||||
use crate::op_tree::OpTree;
|
use crate::object_data::ObjectData;
|
||||||
use crate::query::{self, OpIdSearch, TreeQuery};
|
use crate::query::{self, OpIdSearch, TreeQuery};
|
||||||
use crate::types::{self, ActorId, Key, ObjId, Op, OpId, OpType};
|
use crate::types::{self, ActorId, Key, ObjId, Op, OpId, OpType};
|
||||||
use crate::{ObjType, OpObserver};
|
use crate::{ObjType, OpObserver};
|
||||||
|
@ -14,8 +14,8 @@ pub(crate) type OpSet = OpSetInternal;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub(crate) struct OpSetInternal {
|
pub(crate) struct OpSetInternal {
|
||||||
/// The map of objects to their type and ops.
|
/// The map of objects to their data.
|
||||||
trees: HashMap<ObjId, OpTree, FxBuildHasher>,
|
objects: HashMap<ObjId, ObjectData, FxBuildHasher>,
|
||||||
/// The number of operations in the opset.
|
/// The number of operations in the opset.
|
||||||
length: usize,
|
length: usize,
|
||||||
/// Metadata about the operations in this opset.
|
/// Metadata about the operations in this opset.
|
||||||
|
@ -24,10 +24,10 @@ pub(crate) struct OpSetInternal {
|
||||||
|
|
||||||
impl OpSetInternal {
|
impl OpSetInternal {
|
||||||
pub(crate) fn new() -> Self {
|
pub(crate) fn new() -> Self {
|
||||||
let mut trees: HashMap<_, _, _> = Default::default();
|
let mut objects: HashMap<_, _, _> = Default::default();
|
||||||
trees.insert(ObjId::root(), OpTree::new());
|
objects.insert(ObjId::root(), ObjectData::root());
|
||||||
OpSetInternal {
|
OpSetInternal {
|
||||||
trees,
|
objects,
|
||||||
length: 0,
|
length: 0,
|
||||||
m: OpSetMetadata {
|
m: OpSetMetadata {
|
||||||
actors: IndexedCache::new(),
|
actors: IndexedCache::new(),
|
||||||
|
@ -45,7 +45,7 @@ impl OpSetInternal {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn iter(&self) -> Iter<'_> {
|
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));
|
objs.sort_by(|a, b| self.m.lamport_cmp(a.0, b.0));
|
||||||
Iter {
|
Iter {
|
||||||
inner: self,
|
inner: self,
|
||||||
|
@ -56,22 +56,22 @@ impl OpSetInternal {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn parent_object(&self, obj: &ObjId) -> Option<(ObjId, Key)> {
|
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();
|
let key = self.search(&parent, OpIdSearch::new(obj.0)).key().unwrap();
|
||||||
Some((parent, key))
|
Some((parent, key))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn keys(&self, obj: ObjId) -> Option<query::Keys<'_>> {
|
pub(crate) fn keys(&self, obj: ObjId) -> Option<query::Keys<'_>> {
|
||||||
if let Some(tree) = self.trees.get(&obj) {
|
if let Some(object) = self.objects.get(&obj) {
|
||||||
tree.internal.keys()
|
object.keys()
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn keys_at(&self, obj: ObjId, clock: Clock) -> Option<query::KeysAt<'_>> {
|
pub(crate) fn keys_at(&self, obj: ObjId, clock: Clock) -> Option<query::KeysAt<'_>> {
|
||||||
if let Some(tree) = self.trees.get(&obj) {
|
if let Some(object) = self.objects.get(&obj) {
|
||||||
tree.internal.keys_at(clock)
|
object.keys_at(clock)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -82,8 +82,8 @@ impl OpSetInternal {
|
||||||
obj: ObjId,
|
obj: ObjId,
|
||||||
range: R,
|
range: R,
|
||||||
) -> Option<query::Range<'_, R>> {
|
) -> Option<query::Range<'_, R>> {
|
||||||
if let Some(tree) = self.trees.get(&obj) {
|
if let Some(tree) = self.objects.get(&obj) {
|
||||||
tree.internal.range(range, &self.m)
|
tree.range(range, &self.m)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -95,8 +95,8 @@ impl OpSetInternal {
|
||||||
range: R,
|
range: R,
|
||||||
clock: Clock,
|
clock: Clock,
|
||||||
) -> Option<query::RangeAt<'_, R>> {
|
) -> Option<query::RangeAt<'_, R>> {
|
||||||
if let Some(tree) = self.trees.get(&obj) {
|
if let Some(tree) = self.objects.get(&obj) {
|
||||||
tree.internal.range_at(range, &self.m, clock)
|
tree.range_at(range, &self.m, clock)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -106,8 +106,8 @@ impl OpSetInternal {
|
||||||
where
|
where
|
||||||
Q: TreeQuery<'a>,
|
Q: TreeQuery<'a>,
|
||||||
{
|
{
|
||||||
if let Some(tree) = self.trees.get(obj) {
|
if let Some(object) = self.objects.get(obj) {
|
||||||
tree.internal.search(query, &self.m)
|
object.search(query, &self.m)
|
||||||
} else {
|
} else {
|
||||||
query
|
query
|
||||||
}
|
}
|
||||||
|
@ -115,20 +115,20 @@ impl OpSetInternal {
|
||||||
|
|
||||||
pub(crate) fn replace<F>(&mut self, obj: &ObjId, index: usize, f: F)
|
pub(crate) fn replace<F>(&mut self, obj: &ObjId, index: usize, f: F)
|
||||||
where
|
where
|
||||||
F: FnMut(&mut Op),
|
F: FnOnce(&mut Op),
|
||||||
{
|
{
|
||||||
if let Some(tree) = self.trees.get_mut(obj) {
|
if let Some(object) = self.objects.get_mut(obj) {
|
||||||
tree.internal.update(index, f)
|
object.update(index, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn remove(&mut self, obj: &ObjId, index: usize) -> Op {
|
pub(crate) fn remove(&mut self, obj: &ObjId, index: usize) -> Op {
|
||||||
// this happens on rollback - be sure to go back to the old state
|
// 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;
|
self.length -= 1;
|
||||||
let op = tree.internal.remove(index);
|
let op = object.remove(index);
|
||||||
if let OpType::Make(_) = &op.action {
|
if let OpType::Make(_) = &op.action {
|
||||||
self.trees.remove(&op.id.into());
|
self.objects.remove(&op.id.into());
|
||||||
}
|
}
|
||||||
op
|
op
|
||||||
}
|
}
|
||||||
|
@ -139,19 +139,12 @@ impl OpSetInternal {
|
||||||
|
|
||||||
pub(crate) fn insert(&mut self, index: usize, obj: &ObjId, element: Op) {
|
pub(crate) fn insert(&mut self, index: usize, obj: &ObjId, element: Op) {
|
||||||
if let OpType::Make(typ) = element.action {
|
if let OpType::Make(typ) = element.action {
|
||||||
self.trees.insert(
|
self.objects
|
||||||
element.id.into(),
|
.insert(element.id.into(), ObjectData::new(typ, Some(*obj)));
|
||||||
OpTree {
|
|
||||||
internal: Default::default(),
|
|
||||||
objtype: typ,
|
|
||||||
parent: Some(*obj),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(tree) = self.trees.get_mut(obj) {
|
if let Some(object) = self.objects.get_mut(obj) {
|
||||||
//let tree = self.trees.get_mut(&element.obj).unwrap();
|
object.insert(index, element);
|
||||||
tree.internal.insert(index, element);
|
|
||||||
self.length += 1;
|
self.length += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -244,13 +237,13 @@ impl OpSetInternal {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn object_type(&self, id: &ObjId) -> Option<ObjType> {
|
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")]
|
#[cfg(feature = "optree-visualisation")]
|
||||||
pub(crate) fn visualise(&self) -> String {
|
pub(crate) fn visualise(&self) -> String {
|
||||||
let mut out = Vec::new();
|
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();
|
dot::render(&graph, &mut out).unwrap();
|
||||||
String::from_utf8_lossy(&out[..]).to_string()
|
String::from_utf8_lossy(&out[..]).to_string()
|
||||||
}
|
}
|
||||||
|
@ -285,8 +278,8 @@ impl<'a> Iterator for Iter<'a> {
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
let mut result = None;
|
let mut result = None;
|
||||||
for obj in self.objs.iter().skip(self.index) {
|
for obj in self.objs.iter().skip(self.index) {
|
||||||
let tree = self.inner.trees.get(obj)?;
|
let object = self.inner.objects.get(obj)?;
|
||||||
result = tree.internal.get(self.sub_index).map(|op| (*obj, op));
|
result = object.get(self.sub_index).map(|op| (*obj, op));
|
||||||
if result.is_some() {
|
if result.is_some() {
|
||||||
self.sub_index += 1;
|
self.sub_index += 1;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -6,36 +6,15 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) use crate::op_set::OpSetMetadata;
|
pub(crate) use crate::op_set::OpSetMetadata;
|
||||||
|
use crate::types::{Op, OpId};
|
||||||
use crate::{
|
use crate::{
|
||||||
clock::Clock,
|
clock::Clock,
|
||||||
query::{self, Index, QueryResult, TreeQuery},
|
query::{self, Index, QueryResult, TreeQuery},
|
||||||
};
|
};
|
||||||
use crate::{
|
|
||||||
types::{ObjId, Op, OpId},
|
|
||||||
ObjType,
|
|
||||||
};
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
pub(crate) const B: usize = 16;
|
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)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) struct OpTreeInternal {
|
pub(crate) struct OpTreeInternal {
|
||||||
pub(crate) root_node: Option<OpTreeNode>,
|
pub(crate) root_node: Option<OpTreeNode>,
|
||||||
|
@ -167,7 +146,7 @@ impl OpTreeInternal {
|
||||||
// this replaces get_mut() because it allows the indexes to update correctly
|
// this replaces get_mut() because it allows the indexes to update correctly
|
||||||
pub(crate) fn update<F>(&mut self, index: usize, f: F)
|
pub(crate) fn update<F>(&mut self, index: usize, f: F)
|
||||||
where
|
where
|
||||||
F: FnMut(&mut Op),
|
F: FnOnce(&mut Op),
|
||||||
{
|
{
|
||||||
if self.len() > index {
|
if self.len() > index {
|
||||||
self.root_node.as_mut().unwrap().update(index, f);
|
self.root_node.as_mut().unwrap().update(index, f);
|
||||||
|
@ -692,36 +671,36 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn insert() {
|
fn insert() {
|
||||||
let mut t: OpTree = OpTree::new();
|
let mut t = OpTreeInternal::new();
|
||||||
|
|
||||||
t.internal.insert(0, op());
|
t.insert(0, op());
|
||||||
t.internal.insert(1, op());
|
t.insert(1, op());
|
||||||
t.internal.insert(0, op());
|
t.insert(0, op());
|
||||||
t.internal.insert(0, op());
|
t.insert(0, op());
|
||||||
t.internal.insert(0, op());
|
t.insert(0, op());
|
||||||
t.internal.insert(3, op());
|
t.insert(3, op());
|
||||||
t.internal.insert(4, op());
|
t.insert(4, op());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn insert_book() {
|
fn insert_book() {
|
||||||
let mut t: OpTree = OpTree::new();
|
let mut t = OpTreeInternal::new();
|
||||||
|
|
||||||
for i in 0..100 {
|
for i in 0..100 {
|
||||||
t.internal.insert(i % 2, op());
|
t.insert(i % 2, op());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn insert_book_vec() {
|
fn insert_book_vec() {
|
||||||
let mut t: OpTree = OpTree::new();
|
let mut t = OpTreeInternal::new();
|
||||||
let mut v = Vec::new();
|
let mut v = Vec::new();
|
||||||
|
|
||||||
for i in 0..100 {
|
for i in 0..100 {
|
||||||
t.internal.insert(i % 3, op());
|
t.insert(i % 3, op());
|
||||||
v.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::op_tree::{OpSetMetadata, OpTreeNode};
|
||||||
use crate::types::{Clock, Counter, ElemId, Op, OpId, OpType, ScalarValue};
|
use crate::types::{Clock, Counter, ElemId, Op, OpId, OpType, ScalarValue};
|
||||||
use fxhash::FxBuildHasher;
|
use fxhash::FxBuildHasher;
|
||||||
|
@ -7,6 +8,7 @@ use std::fmt::Debug;
|
||||||
|
|
||||||
mod elem_id_pos;
|
mod elem_id_pos;
|
||||||
mod insert;
|
mod insert;
|
||||||
|
mod insert_prop;
|
||||||
mod keys;
|
mod keys;
|
||||||
mod keys_at;
|
mod keys_at;
|
||||||
mod len;
|
mod len;
|
||||||
|
@ -25,6 +27,7 @@ mod seek_op_with_patch;
|
||||||
|
|
||||||
pub(crate) use elem_id_pos::ElemIdPos;
|
pub(crate) use elem_id_pos::ElemIdPos;
|
||||||
pub(crate) use insert::InsertNth;
|
pub(crate) use insert::InsertNth;
|
||||||
|
pub(crate) use insert_prop::InsertProp;
|
||||||
pub(crate) use keys::Keys;
|
pub(crate) use keys::Keys;
|
||||||
pub(crate) use keys_at::KeysAt;
|
pub(crate) use keys_at::KeysAt;
|
||||||
pub(crate) use len::Len;
|
pub(crate) use len::Len;
|
||||||
|
@ -50,6 +53,24 @@ pub(crate) struct CounterData {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait TreeQuery<'a> {
|
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)]
|
#[inline(always)]
|
||||||
fn query_node_with_metadata(
|
fn query_node_with_metadata(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
use crate::error::AutomergeError;
|
use crate::error::AutomergeError;
|
||||||
use crate::op_tree::OpTreeNode;
|
use crate::op_tree::OpTreeNode;
|
||||||
use crate::query::{QueryResult, TreeQuery};
|
use crate::query::{QueryResult, TreeQuery};
|
||||||
use crate::types::{ElemId, Key, Op, HEAD};
|
use crate::types::{ElemId, Key, Op, OpId, HEAD};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub(crate) struct InsertNth {
|
pub(crate) struct InsertNth {
|
||||||
/// the index in the realised list that we want to insert at
|
/// the index in the realised list that we want to insert at
|
||||||
target: usize,
|
target: usize,
|
||||||
|
/// OpId of the op we are trying to find a location for.
|
||||||
|
id: OpId,
|
||||||
/// the number of visible operations seen
|
/// the number of visible operations seen
|
||||||
seen: usize,
|
seen: usize,
|
||||||
//pub pos: usize,
|
//pub pos: usize,
|
||||||
|
@ -22,7 +24,7 @@ pub(crate) struct InsertNth {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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 {
|
let (valid, last_valid_insert) = if target == 0 {
|
||||||
(Some(0), Some(HEAD))
|
(Some(0), Some(HEAD))
|
||||||
} else {
|
} else {
|
||||||
|
@ -30,6 +32,7 @@ impl InsertNth {
|
||||||
};
|
};
|
||||||
InsertNth {
|
InsertNth {
|
||||||
target,
|
target,
|
||||||
|
id: opid,
|
||||||
seen: 0,
|
seen: 0,
|
||||||
n: 0,
|
n: 0,
|
||||||
valid,
|
valid,
|
||||||
|
@ -62,6 +65,23 @@ impl InsertNth {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TreeQuery<'a> for 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 {
|
fn query_node(&mut self, child: &OpTreeNode) -> QueryResult {
|
||||||
// if this node has some visible elements then we may find our target within
|
// if this node has some visible elements then we may find our target within
|
||||||
let mut num_vis = child.index.visible_len();
|
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> {
|
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 {
|
fn query_node(&mut self, child: &OpTreeNode) -> QueryResult {
|
||||||
let mut num_vis = child.index.visible_len();
|
let mut num_vis = child.index.visible_len();
|
||||||
if child.index.has_visible(&self.last_seen) {
|
if child.index.has_visible(&self.last_seen) {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use crate::op_tree::{OpSetMetadata, OpTreeNode};
|
use crate::op_tree::{OpSetMetadata, OpTreeNode};
|
||||||
use crate::query::{binary_search_by, QueryResult, TreeQuery};
|
use crate::query::{binary_search_by, QueryResult, TreeQuery};
|
||||||
use crate::types::{Key, Op};
|
use crate::types::{Key, Op};
|
||||||
use std::fmt::Debug;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub(crate) struct Prop<'a> {
|
pub(crate) struct Prop<'a> {
|
||||||
|
@ -9,6 +8,7 @@ pub(crate) struct Prop<'a> {
|
||||||
pub(crate) ops: Vec<&'a Op>,
|
pub(crate) ops: Vec<&'a Op>,
|
||||||
pub(crate) ops_pos: Vec<usize>,
|
pub(crate) ops_pos: Vec<usize>,
|
||||||
pub(crate) pos: usize,
|
pub(crate) pos: usize,
|
||||||
|
start: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Prop<'a> {
|
impl<'a> Prop<'a> {
|
||||||
|
@ -18,17 +18,38 @@ impl<'a> Prop<'a> {
|
||||||
ops: vec![],
|
ops: vec![],
|
||||||
ops_pos: vec![],
|
ops_pos: vec![],
|
||||||
pos: 0,
|
pos: 0,
|
||||||
|
start: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TreeQuery<'a> for Prop<'a> {
|
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(
|
fn query_node_with_metadata(
|
||||||
&mut self,
|
&mut self,
|
||||||
child: &'a OpTreeNode,
|
child: &'a OpTreeNode,
|
||||||
m: &OpSetMetadata,
|
m: &OpSetMetadata,
|
||||||
) -> QueryResult {
|
) -> 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;
|
self.pos = start;
|
||||||
for pos in start..child.len() {
|
for pos in start..child.len() {
|
||||||
let op = child.get(pos).unwrap();
|
let op = child.get(pos).unwrap();
|
||||||
|
|
|
@ -210,7 +210,7 @@ impl TransactionInner {
|
||||||
) -> Result<OpId, AutomergeError> {
|
) -> Result<OpId, AutomergeError> {
|
||||||
let id = self.next_id();
|
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()?;
|
let key = query.key()?;
|
||||||
|
|
||||||
|
@ -255,7 +255,7 @@ impl TransactionInner {
|
||||||
|
|
||||||
let id = self.next_id();
|
let id = self.next_id();
|
||||||
let prop_index = doc.ops.m.props.cache(prop.clone());
|
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
|
// no key present to delete
|
||||||
if query.ops.is_empty() && action == OpType::Delete {
|
if query.ops.is_empty() && action == OpType::Delete {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::object_data::ObjectData;
|
||||||
use crate::types::ObjId;
|
use crate::types::ObjId;
|
||||||
use fxhash::FxHasher;
|
use fxhash::FxHasher;
|
||||||
use std::{borrow::Cow, collections::HashMap, hash::BuildHasherDefault};
|
use std::{borrow::Cow, collections::HashMap, hash::BuildHasherDefault};
|
||||||
|
@ -42,16 +43,12 @@ pub(crate) struct GraphVisualisation<'a> {
|
||||||
|
|
||||||
impl<'a> GraphVisualisation<'a> {
|
impl<'a> GraphVisualisation<'a> {
|
||||||
pub(super) fn construct(
|
pub(super) fn construct(
|
||||||
trees: &'a HashMap<
|
objects: &'a HashMap<crate::types::ObjId, ObjectData, BuildHasherDefault<FxHasher>>,
|
||||||
crate::types::ObjId,
|
|
||||||
crate::op_tree::OpTree,
|
|
||||||
BuildHasherDefault<FxHasher>,
|
|
||||||
>,
|
|
||||||
metadata: &'a crate::op_set::OpSetMetadata,
|
metadata: &'a crate::op_set::OpSetMetadata,
|
||||||
) -> GraphVisualisation<'a> {
|
) -> GraphVisualisation<'a> {
|
||||||
let mut nodes = HashMap::new();
|
let mut nodes = HashMap::new();
|
||||||
for (obj_id, tree) in trees {
|
for (obj_id, object_data) in objects {
|
||||||
if let Some(root_node) = &tree.internal.root_node {
|
if let Some(root_node) = &object_data.ops.root_node {
|
||||||
let tree_id = Self::construct_nodes(root_node, obj_id, &mut nodes, metadata);
|
let tree_id = Self::construct_nodes(root_node, obj_id, &mut nodes, metadata);
|
||||||
let obj_tree_id = NodeId::default();
|
let obj_tree_id = NodeId::default();
|
||||||
nodes.insert(
|
nodes.insert(
|
||||||
|
|
Loading…
Reference in a new issue