From 74a8af6ca6b1a35e2fd5e99a3a464b7dfb02f897 Mon Sep 17 00:00:00 2001 From: Alex Good Date: Fri, 24 Dec 2021 23:00:59 +0000 Subject: [PATCH 1/6] Run the js_test in CI We add a script for running the js tests in `scripts/ci/js_tests`. This script can also be run locally. We move the `automerge-js` package to below the `automerge-wasm` crate as it is specifically testing the wasm interface. We also add an action to the github actions workflow for CI to run the js tests. --- .github/workflows/ci.yaml | 9 +++++++++ .../automerge-js}/.gitignore | 0 .../automerge-js}/package.json | 2 +- .../automerge-js}/src/columnar.js | 0 .../automerge-js}/src/common.js | 0 .../automerge-js}/src/constants.js | 0 .../automerge-js}/src/counter.js | 0 .../automerge-js}/src/encoding.js | 0 .../automerge-js}/src/index.js | 0 .../automerge-js}/src/numbers.js | 0 .../automerge-js}/src/proxies.js | 0 .../automerge-js}/src/sync.js | 0 .../automerge-js}/src/text.js | 0 .../automerge-js}/src/uuid.js | 0 .../automerge-js}/test/basic_test.js | 0 .../automerge-js}/test/columnar_test.js | 0 .../automerge-js}/test/helpers.js | 0 .../automerge-js}/test/legacy_tests.js | 0 .../automerge-js}/test/sync_test.js | 0 .../automerge-js}/test/text_test.js | 0 .../automerge-js}/test/uuid_test.js | 0 scripts/ci/js_tests | 17 +++++++++++++++++ scripts/ci/run | 1 + 23 files changed, 28 insertions(+), 1 deletion(-) rename {automerge-js => automerge-wasm/automerge-js}/.gitignore (100%) rename {automerge-js => automerge-wasm/automerge-js}/package.json (86%) rename {automerge-js => automerge-wasm/automerge-js}/src/columnar.js (100%) rename {automerge-js => automerge-wasm/automerge-js}/src/common.js (100%) rename {automerge-js => automerge-wasm/automerge-js}/src/constants.js (100%) rename {automerge-js => automerge-wasm/automerge-js}/src/counter.js (100%) rename {automerge-js => automerge-wasm/automerge-js}/src/encoding.js (100%) rename {automerge-js => automerge-wasm/automerge-js}/src/index.js (100%) rename {automerge-js => automerge-wasm/automerge-js}/src/numbers.js (100%) rename {automerge-js => automerge-wasm/automerge-js}/src/proxies.js (100%) rename {automerge-js => automerge-wasm/automerge-js}/src/sync.js (100%) rename {automerge-js => automerge-wasm/automerge-js}/src/text.js (100%) rename {automerge-js => automerge-wasm/automerge-js}/src/uuid.js (100%) rename {automerge-js => automerge-wasm/automerge-js}/test/basic_test.js (100%) rename {automerge-js => automerge-wasm/automerge-js}/test/columnar_test.js (100%) rename {automerge-js => automerge-wasm/automerge-js}/test/helpers.js (100%) rename {automerge-js => automerge-wasm/automerge-js}/test/legacy_tests.js (100%) rename {automerge-js => automerge-wasm/automerge-js}/test/sync_test.js (100%) rename {automerge-js => automerge-wasm/automerge-js}/test/text_test.js (100%) rename {automerge-js => automerge-wasm/automerge-js}/test/uuid_test.js (100%) create mode 100755 scripts/ci/js_tests diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 90d81636..5bdb2bed 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -53,6 +53,15 @@ jobs: with: command: check ${{ matrix.checks }} + js_tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install wasm-pack + run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + - name: run tests + run: ./scripts/ci/js_tests + linux: runs-on: ubuntu-latest strategy: diff --git a/automerge-js/.gitignore b/automerge-wasm/automerge-js/.gitignore similarity index 100% rename from automerge-js/.gitignore rename to automerge-wasm/automerge-js/.gitignore diff --git a/automerge-js/package.json b/automerge-wasm/automerge-js/package.json similarity index 86% rename from automerge-js/package.json rename to automerge-wasm/automerge-js/package.json index 17018429..f0e65a18 100644 --- a/automerge-js/package.json +++ b/automerge-wasm/automerge-js/package.json @@ -10,7 +10,7 @@ "mocha": "^9.1.1" }, "dependencies": { - "automerge-wasm": "file:../automerge-wasm", + "automerge-wasm": "file:../dev", "fast-sha256": "^1.3.0", "pako": "^2.0.4", "uuid": "^8.3" diff --git a/automerge-js/src/columnar.js b/automerge-wasm/automerge-js/src/columnar.js similarity index 100% rename from automerge-js/src/columnar.js rename to automerge-wasm/automerge-js/src/columnar.js diff --git a/automerge-js/src/common.js b/automerge-wasm/automerge-js/src/common.js similarity index 100% rename from automerge-js/src/common.js rename to automerge-wasm/automerge-js/src/common.js diff --git a/automerge-js/src/constants.js b/automerge-wasm/automerge-js/src/constants.js similarity index 100% rename from automerge-js/src/constants.js rename to automerge-wasm/automerge-js/src/constants.js diff --git a/automerge-js/src/counter.js b/automerge-wasm/automerge-js/src/counter.js similarity index 100% rename from automerge-js/src/counter.js rename to automerge-wasm/automerge-js/src/counter.js diff --git a/automerge-js/src/encoding.js b/automerge-wasm/automerge-js/src/encoding.js similarity index 100% rename from automerge-js/src/encoding.js rename to automerge-wasm/automerge-js/src/encoding.js diff --git a/automerge-js/src/index.js b/automerge-wasm/automerge-js/src/index.js similarity index 100% rename from automerge-js/src/index.js rename to automerge-wasm/automerge-js/src/index.js diff --git a/automerge-js/src/numbers.js b/automerge-wasm/automerge-js/src/numbers.js similarity index 100% rename from automerge-js/src/numbers.js rename to automerge-wasm/automerge-js/src/numbers.js diff --git a/automerge-js/src/proxies.js b/automerge-wasm/automerge-js/src/proxies.js similarity index 100% rename from automerge-js/src/proxies.js rename to automerge-wasm/automerge-js/src/proxies.js diff --git a/automerge-js/src/sync.js b/automerge-wasm/automerge-js/src/sync.js similarity index 100% rename from automerge-js/src/sync.js rename to automerge-wasm/automerge-js/src/sync.js diff --git a/automerge-js/src/text.js b/automerge-wasm/automerge-js/src/text.js similarity index 100% rename from automerge-js/src/text.js rename to automerge-wasm/automerge-js/src/text.js diff --git a/automerge-js/src/uuid.js b/automerge-wasm/automerge-js/src/uuid.js similarity index 100% rename from automerge-js/src/uuid.js rename to automerge-wasm/automerge-js/src/uuid.js diff --git a/automerge-js/test/basic_test.js b/automerge-wasm/automerge-js/test/basic_test.js similarity index 100% rename from automerge-js/test/basic_test.js rename to automerge-wasm/automerge-js/test/basic_test.js diff --git a/automerge-js/test/columnar_test.js b/automerge-wasm/automerge-js/test/columnar_test.js similarity index 100% rename from automerge-js/test/columnar_test.js rename to automerge-wasm/automerge-js/test/columnar_test.js diff --git a/automerge-js/test/helpers.js b/automerge-wasm/automerge-js/test/helpers.js similarity index 100% rename from automerge-js/test/helpers.js rename to automerge-wasm/automerge-js/test/helpers.js diff --git a/automerge-js/test/legacy_tests.js b/automerge-wasm/automerge-js/test/legacy_tests.js similarity index 100% rename from automerge-js/test/legacy_tests.js rename to automerge-wasm/automerge-js/test/legacy_tests.js diff --git a/automerge-js/test/sync_test.js b/automerge-wasm/automerge-js/test/sync_test.js similarity index 100% rename from automerge-js/test/sync_test.js rename to automerge-wasm/automerge-js/test/sync_test.js diff --git a/automerge-js/test/text_test.js b/automerge-wasm/automerge-js/test/text_test.js similarity index 100% rename from automerge-js/test/text_test.js rename to automerge-wasm/automerge-js/test/text_test.js diff --git a/automerge-js/test/uuid_test.js b/automerge-wasm/automerge-js/test/uuid_test.js similarity index 100% rename from automerge-js/test/uuid_test.js rename to automerge-wasm/automerge-js/test/uuid_test.js diff --git a/scripts/ci/js_tests b/scripts/ci/js_tests new file mode 100755 index 00000000..7d55db77 --- /dev/null +++ b/scripts/ci/js_tests @@ -0,0 +1,17 @@ +THIS_SCRIPT=$(dirname "$0"); +WASM_PROJECT=$THIS_SCRIPT/../../automerge-wasm; +JS_PROJECT=$THIS_SCRIPT/../../automerge-wasm/automerge-js; + +# This will take care of running wasm-pack +yarn --cwd $WASM_PROJECT build; +# If the dependencies are already installed we delete automerge-wasm. This makes +# this script usable for iterative development. +if [ -d $JS_PROJECT/node_modules/automerge-wasm ]; then + rm -rf $JS_PROJECT/node_modules/automerge-wasm +fi +# --check-files forces yarn to check if the local dep has changed +yarn --cwd $JS_PROJECT install --check-files; +yarn --cwd $JS_PROJECT test; + + + diff --git a/scripts/ci/run b/scripts/ci/run index c03f2991..42367e10 100755 --- a/scripts/ci/run +++ b/scripts/ci/run @@ -6,3 +6,4 @@ set -eou pipefail ./scripts/ci/build-test ./scripts/ci/docs ./scripts/ci/advisory +./scripts/ci/js_tests From 29820f9d50c0b5f56fbbf735037e45129938283d Mon Sep 17 00:00:00 2001 From: Alex Good Date: Mon, 27 Dec 2021 12:59:13 +0000 Subject: [PATCH 2/6] wip --- automerge-wasm/src/lib.rs | 37 +-- automerge/src/change.rs | 13 +- automerge/src/clock.rs | 22 +- automerge/src/columnar.rs | 25 +- automerge/src/external_types.rs | 89 ++++++ automerge/src/indexed_cache.rs | 5 + automerge/src/lib.rs | 292 ++++++++++-------- automerge/src/op_set.rs | 58 +++- automerge/src/op_tree.rs | 6 +- automerge/src/query.rs | 2 +- automerge/src/query/len.rs | 2 +- automerge/src/query/list_vals.rs | 4 +- automerge/src/query/prop.rs | 4 +- automerge/src/types.rs | 58 ++-- automerge/src/value.rs | 2 +- automerge/tests/helpers/mod.rs | 201 +++---------- automerge/tests/test.rs | 491 ++++++++++++++++--------------- edit-trace/Cargo.toml | 3 + edit-trace/src/main.rs | 6 +- todo.adoc | 3 + 20 files changed, 703 insertions(+), 620 deletions(-) create mode 100644 automerge/src/external_types.rs create mode 100644 todo.adoc diff --git a/automerge-wasm/src/lib.rs b/automerge-wasm/src/lib.rs index 80a3d65f..f3bbda98 100644 --- a/automerge-wasm/src/lib.rs +++ b/automerge-wasm/src/lib.rs @@ -8,6 +8,7 @@ use std::collections::{HashMap, HashSet}; use std::convert::TryFrom; use std::convert::TryInto; use std::fmt::Display; +use std::str::FromStr; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; @@ -149,7 +150,7 @@ impl Automerge { } pub fn keys(&mut self, obj: JsValue, heads: JsValue) -> Result { - let obj = self.import(obj)?; + let obj: automerge::ObjId = self.import(obj)?; let result = if let Some(heads) = get_heads(heads) { self.0.keys_at(obj, &heads) } else { @@ -162,7 +163,7 @@ impl Automerge { } pub fn text(&mut self, obj: JsValue, heads: JsValue) -> Result { - let obj = self.import(obj)?; + let obj: automerge::ObjId = self.import(obj)?; if let Some(heads) = get_heads(heads) { self.0.text_at(obj, &heads) } else { @@ -179,7 +180,7 @@ impl Automerge { delete_count: JsValue, text: JsValue, ) -> Result<(), JsValue> { - let obj = self.import(obj)?; + let obj: automerge::ObjId = self.import(obj)?; let start = to_usize(start, "start")?; let delete_count = to_usize(delete_count, "deleteCount")?; let mut vals = vec![]; @@ -214,7 +215,7 @@ impl Automerge { value: JsValue, datatype: JsValue, ) -> Result { - let obj = self.import(obj)?; + let obj: automerge::ObjId = self.import(obj)?; //let key = self.insert_pos_for_index(&obj, prop)?; let index: Result<_, JsValue> = index .as_f64() @@ -235,7 +236,7 @@ impl Automerge { value: JsValue, datatype: JsValue, ) -> Result { - let obj = self.import(obj)?; + let obj: automerge::ObjId = self.import(obj)?; let prop = self.import_prop(prop)?; let value = self.import_value(value, datatype)?; let opid = self.0.set(obj, prop, value).map_err(to_js_err)?; @@ -246,7 +247,7 @@ impl Automerge { } pub fn inc(&mut self, obj: JsValue, prop: JsValue, value: JsValue) -> Result<(), JsValue> { - let obj = self.import(obj)?; + let obj: automerge::ObjId = self.import(obj)?; let prop = self.import_prop(prop)?; let value: f64 = value .as_f64() @@ -257,7 +258,7 @@ impl Automerge { } pub fn value(&mut self, obj: JsValue, prop: JsValue, heads: JsValue) -> Result { - let obj = self.import(obj)?; + let obj: automerge::ObjId = self.import(obj)?; let result = Array::new(); let prop = to_prop(prop); let heads = get_heads(heads); @@ -284,7 +285,7 @@ impl Automerge { } pub fn values(&mut self, obj: JsValue, arg: JsValue, heads: JsValue) -> Result { - let obj = self.import(obj)?; + let obj: automerge::ObjId = self.import(obj)?; let result = Array::new(); let prop = to_prop(arg); if let Ok(prop) = prop { @@ -316,7 +317,7 @@ impl Automerge { } pub fn length(&mut self, obj: JsValue, heads: JsValue) -> Result { - let obj = self.import(obj)?; + let obj: automerge::ObjId = self.import(obj)?; if let Some(heads) = get_heads(heads) { Ok((self.0.length_at(obj, &heads) as f64).into()) } else { @@ -325,7 +326,7 @@ impl Automerge { } pub fn del(&mut self, obj: JsValue, prop: JsValue) -> Result<(), JsValue> { - let obj = self.import(obj)?; + let obj: automerge::ObjId = self.import(obj)?; let prop = to_prop(prop)?; self.0.del(obj, prop).map_err(to_js_err)?; Ok(()) @@ -442,16 +443,18 @@ impl Automerge { } } - fn export(&self, val: E) -> JsValue { - self.0.export(val).into() + fn export(&self, val: D) -> JsValue { + val.to_string().into() } - fn import(&self, id: JsValue) -> Result { - let id_str = id + fn import(&self, id: JsValue) -> Result + where F::Err: std::fmt::Display + { + id .as_string() - .ok_or("invalid opid/objid/elemid") - .map_err(to_js_err)?; - self.0.import(&id_str).map_err(to_js_err) + .ok_or("invalid opid/objid/elemid")? + .parse::() + .map_err(to_js_err) } fn import_prop(&mut self, prop: JsValue) -> Result { diff --git a/automerge/src/change.rs b/automerge/src/change.rs index 4d3984e5..80fe571c 100644 --- a/automerge/src/change.rs +++ b/automerge/src/change.rs @@ -7,8 +7,8 @@ use crate::decoding::{Decodable, InvalidChangeError}; use crate::encoding::{Encodable, DEFLATE_MIN_SIZE}; use crate::legacy as amp; use crate::{ - ActorId, AutomergeError, ElemId, IndexedCache, Key, ObjId, Op, OpId, OpType, Transaction, HEAD, - ROOT, + types::{ObjId, OpId}, + ActorId, AutomergeError, ElemId, IndexedCache, Key, Op, OpType, Transaction, HEAD, }; use core::ops::Range; use flate2::{ @@ -417,10 +417,9 @@ fn increment_range_map(ranges: &mut HashMap>, len: usize) { } fn export_objid(id: &ObjId, actors: &IndexedCache) -> amp::ObjectId { - if id.0 == ROOT { - amp::ObjectId::Root - } else { - export_opid(&id.0, actors).into() + match id { + ObjId::Root => amp::ObjectId::Root, + ObjId::Op(op) => export_opid(op, actors).into() } } @@ -433,7 +432,7 @@ fn export_elemid(id: &ElemId, actors: &IndexedCache) -> amp::ElementId } fn export_opid(id: &OpId, actors: &IndexedCache) -> amp::OpId { - amp::OpId(id.0, actors.get(id.1).clone()) + amp::OpId(id.counter(), actors.get(id.actor()).clone()) } fn export_op(op: &Op, actors: &IndexedCache, props: &IndexedCache) -> amp::Op { diff --git a/automerge/src/clock.rs b/automerge/src/clock.rs index 979885b3..7edab530 100644 --- a/automerge/src/clock.rs +++ b/automerge/src/clock.rs @@ -1,4 +1,4 @@ -use crate::OpId; +use crate::types::OpId; use fxhash::FxBuildHasher; use std::cmp; use std::collections::HashMap; @@ -19,8 +19,8 @@ impl Clock { } pub fn covers(&self, id: &OpId) -> bool { - if let Some(val) = self.0.get(&id.1) { - val >= &id.0 + if let Some(val) = self.0.get(&id.actor()) { + val >= &id.counter() } else { false } @@ -38,15 +38,15 @@ mod tests { clock.include(1, 20); clock.include(2, 10); - assert!(clock.covers(&OpId(10, 1))); - assert!(clock.covers(&OpId(20, 1))); - assert!(!clock.covers(&OpId(30, 1))); + assert!(clock.covers(&OpId::new(10, 1))); + assert!(clock.covers(&OpId::new(20, 1))); + assert!(!clock.covers(&OpId::new(30, 1))); - assert!(clock.covers(&OpId(5, 2))); - assert!(clock.covers(&OpId(10, 2))); - assert!(!clock.covers(&OpId(15, 2))); + assert!(clock.covers(&OpId::new(5, 2))); + assert!(clock.covers(&OpId::new(10, 2))); + assert!(!clock.covers(&OpId::new(15, 2))); - assert!(!clock.covers(&OpId(1, 3))); - assert!(!clock.covers(&OpId(100, 3))); + assert!(!clock.covers(&OpId::new(1, 3))); + assert!(!clock.covers(&OpId::new(100, 3))); } } diff --git a/automerge/src/columnar.rs b/automerge/src/columnar.rs index 3a1df3cb..3339a68e 100644 --- a/automerge/src/columnar.rs +++ b/automerge/src/columnar.rs @@ -11,8 +11,7 @@ use std::{ str, }; -use crate::ROOT; -use crate::{ActorId, ElemId, Key, ObjId, ObjType, OpId, OpType, ScalarValue}; +use crate::{ActorId, ElemId, Key, ObjType, OpType, ScalarValue, types::{ObjId, OpId}}; use crate::legacy as amp; use amp::SortedVec; @@ -686,15 +685,15 @@ impl KeyEncoder { self.ctr.append_null(); self.str.append_value(props[i].clone()); } - Key::Seq(ElemId(OpId(0, 0))) => { + Key::Seq(ElemId(opid)) if opid.actor() == 0 && opid.counter() == 0 => { // HEAD self.actor.append_null(); self.ctr.append_value(0); self.str.append_null(); } - Key::Seq(ElemId(OpId(ctr, actor))) => { - self.actor.append_value(actors[actor]); - self.ctr.append_value(ctr); + Key::Seq(ElemId(opid)) => { + self.actor.append_value(actors[opid.actor()]); + self.ctr.append_value(opid.counter()); self.str.append_null(); } } @@ -773,8 +772,8 @@ impl SuccEncoder { fn append(&mut self, succ: &[OpId], actors: &[usize]) { self.num.append_value(succ.len()); for s in succ.iter() { - self.ctr.append_value(s.0); - self.actor.append_value(actors[s.1]); + self.ctr.append_value(s.counter()); + self.actor.append_value(actors[s.actor()]); } } @@ -845,14 +844,14 @@ impl ObjEncoder { } fn append(&mut self, obj: &ObjId, actors: &[usize]) { - match obj.0 { - ROOT => { + match obj { + ObjId::Root => { self.actor.append_null(); self.ctr.append_null(); } - OpId(ctr, actor) => { - self.actor.append_value(actors[actor]); - self.ctr.append_value(ctr); + ObjId::Op(opid) => { + self.actor.append_value(actors[opid.actor()]); + self.ctr.append_value(opid.counter()); } } } diff --git a/automerge/src/external_types.rs b/automerge/src/external_types.rs new file mode 100644 index 00000000..4411f3a9 --- /dev/null +++ b/automerge/src/external_types.rs @@ -0,0 +1,89 @@ +use std::{str::FromStr, borrow::Cow, fmt::Display}; + +use crate::{ActorId, types::OpId, op_tree::OpSetMetadata}; + +const ROOT_STR: &str = "_root"; + +#[derive(Copy, Debug, PartialEq, Clone, Hash, Eq)] +pub struct ExternalOpId<'a> { + counter: u64, + actor: Cow<'a, ActorId>, +} + +impl<'a> ExternalOpId<'a> { + pub(crate) fn from_internal(opid: OpId, metadata: &OpSetMetadata) -> Option { + metadata.actors.get_safe(opid.actor()).map(|actor| { + ExternalOpId{ + counter: opid.counter(), + actor: actor.into(), + } + }) + } + + pub(crate) fn into_opid(self, metadata: &mut OpSetMetadata) -> OpId { + let actor = metadata.actors.cache(self.actor); + OpId::new(self.counter, actor) + } +} + +#[derive(Debug, PartialEq, Clone, Hash, Eq)] +pub enum ExternalObjId<'a> { + Root, + Op(ExternalOpId<'a>), +} + +impl<'a> From> for ExternalObjId<'a> { + fn from(op: ExternalOpId) -> Self { + ExternalObjId::Op(op) + } +} + +#[derive(thiserror::Error, Debug)] +pub enum ParseError { + #[error("op IDs should have the format @")] + BadFormat, + #[error("the counter of an opid should be a positive integer")] + InvalidCounter, + #[error("the actor of an opid should be valid hex encoded bytes")] + InvalidActor, +} + +impl FromStr for ExternalOpId<'static> { + type Err = ParseError; + + fn from_str(s: &str) -> Result { + let mut parts = s.split("@"); + let first_part = parts.next().ok_or(ParseError::BadFormat)?; + let second_part = parts.next().ok_or(ParseError::BadFormat)?; + let counter: u64 = first_part.parse().map_err(|_| ParseError::InvalidCounter)?; + let actor: ActorId = second_part.parse().map_err(|_| ParseError::InvalidActor)?; + Ok(ExternalOpId{counter, actor}) + } +} + +impl<'a> FromStr for ExternalObjId<'a> { + type Err = ParseError; + + fn from_str(s: &str) -> Result { + if s == ROOT_STR { + Ok(ExternalObjId::Root) + } else { + Ok(s.parse::()?.into()) + } + } +} + +impl Display for ExternalOpId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}@{}", self.counter, self.actor) + } +} + +impl Display for ExternalObjId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Root => write!(f, "{}", ROOT_STR), + Self::Op(op) => write!(f, "{}", op), + } + } +} diff --git a/automerge/src/indexed_cache.rs b/automerge/src/indexed_cache.rs index 21ffd75b..43a0fa2c 100644 --- a/automerge/src/indexed_cache.rs +++ b/automerge/src/indexed_cache.rs @@ -43,6 +43,11 @@ where &self.cache[index] } + // Todo replace all uses of `get` with this + pub fn get_safe(&self, index: usize) -> Option<&T> { + self.cache.get(index) + } + pub fn sorted(&self) -> IndexedCache { let mut sorted = Self::new(); self.cache.iter().sorted().cloned().for_each(|item| { diff --git a/automerge/src/lib.rs b/automerge/src/lib.rs index c2595c68..92340fd5 100644 --- a/automerge/src/lib.rs +++ b/automerge/src/lib.rs @@ -45,13 +45,15 @@ mod op_tree; mod query; mod types; mod value; +mod external_types; +pub use external_types::{ExternalOpId as OpId, ExternalObjId as ObjId}; use change::{encode_document, export_change}; use clock::Clock; use indexed_cache::IndexedCache; use op_set::OpSet; use std::collections::{HashMap, HashSet, VecDeque}; -use types::{ElemId, Key, ObjId, Op, HEAD}; +use types::{ElemId, Key, ObjId as InternalObjId, Op, HEAD}; use unicode_segmentation::UnicodeSegmentation; pub use change::{decode_change, Change}; @@ -59,9 +61,9 @@ pub use error::AutomergeError; pub use legacy::Change as ExpandedChange; pub use sync::{BloomFilter, SyncHave, SyncMessage, SyncState}; pub use types::{ - ActorId, ChangeHash, Export, Exportable, Importable, ObjType, OpId, OpType, Patch, Peer, Prop, - ROOT, + ActorId, ChangeHash, ObjType, OpType, Patch, Peer, Prop, }; +use types::{OpId as InternalOpId, Export, Exportable, Importable}; pub use value::{ScalarValue, Value}; #[derive(Debug, Clone)] @@ -96,25 +98,25 @@ impl Automerge { pub fn set_actor(&mut self, actor: ActorId) { self.ensure_transaction_closed(); - self.actor = Some(self.ops.m.actors.cache(actor)) + self.actor = Some(self.ops.m.borrow_mut().actors.cache(actor)) } fn random_actor(&mut self) -> ActorId { let actor = ActorId::from(uuid::Uuid::new_v4().as_bytes().to_vec()); - self.actor = Some(self.ops.m.actors.cache(actor.clone())); + self.actor = Some(self.ops.m.borrow_mut().actors.cache(actor.clone())); actor } pub fn get_actor(&mut self) -> ActorId { if let Some(actor) = self.actor { - self.ops.m.actors[actor].clone() + self.ops.m.borrow().actors[actor].clone() } else { self.random_actor() } } pub fn maybe_get_actor(&self) -> Option { - self.actor.map(|i| self.ops.m.actors[i].clone()) + self.actor.map(|i| self.ops.m.borrow().actors[i].clone()) } fn get_actor_index(&mut self) -> usize { @@ -139,7 +141,7 @@ impl Automerge { max_op: 0, transaction: None, }; - am.actor = Some(am.ops.m.actors.cache(actor)); + am.actor = Some(am.ops.m.borrow_mut().actors.cache(actor)); am } @@ -199,7 +201,8 @@ impl Automerge { pub fn ensure_transaction_closed(&mut self) { if let Some(tx) = self.transaction.take() { - self.update_history(export_change(&tx, &self.ops.m.actors, &self.ops.m.props)); + let change = export_change(&tx, &self.ops.m.borrow_mut().actors, &self.ops.m.borrow().props); + self.update_history(change); } } @@ -224,9 +227,9 @@ impl Automerge { } } - fn next_id(&mut self) -> OpId { + fn next_id(&mut self) -> InternalOpId { let tx = self.tx(); - OpId(tx.start_op + tx.operations.len() as u64, tx.actor) + InternalOpId::new(tx.start_op + tx.operations.len() as u64, tx.actor) } fn insert_local_op(&mut self, op: Op, pos: usize, succ_pos: &[usize]) { @@ -262,22 +265,26 @@ impl Automerge { // PropAt::() // NthAt::() - pub fn keys(&self, obj: OpId) -> Vec { + pub fn keys>(&self, obj: O) -> Vec { + let obj = self.import_objid(obj.into()); let q = self.ops.search(obj.into(), query::Keys::new()); q.keys.iter().map(|k| self.export(*k)).collect() } - pub fn keys_at(&self, obj: OpId, heads: &[ChangeHash]) -> Vec { + pub fn keys_at>(&mut self, obj: O, heads: &[ChangeHash]) -> Vec { + let obj = self.import_objid(obj.into()); let clock = self.clock_at(heads); let q = self.ops.search(obj.into(), query::KeysAt::new(clock)); q.keys.iter().map(|k| self.export(*k)).collect() } - pub fn length(&self, obj: OpId) -> usize { + pub fn length>(&self, obj: O) -> usize { + let obj = self.import_objid(obj.into()); self.ops.search(obj.into(), query::Len::new(obj.into())).len } - pub fn length_at(&self, obj: OpId, heads: &[ChangeHash]) -> usize { + pub fn length_at>(&self, obj: O, heads: &[ChangeHash]) -> usize { + let obj = self.import_objid(obj.into()); let clock = self.clock_at(heads); self.ops.search(obj.into(), query::LenAt::new(clock)).len } @@ -300,23 +307,24 @@ impl Automerge { /// - The object does not exist /// - The key is the wrong type for the object /// - The key does not exist in the object - pub fn set, V: Into>( + pub fn set, P: Into, V: Into>( &mut self, - obj: OpId, + obj: O, prop: P, value: V, ) -> Result, AutomergeError> { let value = value.into(); + let obj = self.import_objid(obj.into()); self.local_op(obj.into(), prop.into(), value.into()) } - pub fn insert>( + pub fn insert, V: Into>( &mut self, - obj: OpId, + obj: O, index: usize, value: V, ) -> Result { - let obj = obj.into(); + let obj = self.import_objid(obj.into()).into(); let id = self.next_id(); let query = self.ops.search(obj, query::InsertNth::new(index)); @@ -338,16 +346,16 @@ impl Automerge { self.ops.insert(query.pos, op.clone()); self.tx().operations.push(op); - Ok(id) + Ok(self.export_opid(id).unwrap()) } - pub fn inc>( + pub fn inc, P: Into>( &mut self, - obj: OpId, + obj: O, prop: P, value: i64, ) -> Result { - match self.local_op(obj.into(), prop.into(), OpType::Inc(value))? { + match self.local_op(self.import_objid(obj.into()).into(), prop.into(), OpType::Inc(value))? { Some(opid) => Ok(opid), None => { panic!("increment should always create a new op") @@ -355,9 +363,9 @@ impl Automerge { } } - pub fn del>(&mut self, obj: OpId, prop: P) -> Result { + pub fn del, P: Into>(&mut self, obj: O, prop: P) -> Result { // TODO: Should we also no-op multiple delete operations? - match self.local_op(obj.into(), prop.into(), OpType::Del)? { + match self.local_op(self.import_objid(obj.into()).into(), prop.into(), OpType::Del)? { Some(opid) => Ok(opid), None => { panic!("delete should always create a new op") @@ -367,27 +375,28 @@ impl Automerge { /// Splice new elements into the given sequence. Returns a vector of the OpIds used to insert /// the new elements - pub fn splice( + pub fn splice>( &mut self, - obj: OpId, + obj: O, mut pos: usize, del: usize, vals: Vec, ) -> Result, AutomergeError> { + let obj = obj.into(); for _ in 0..del { - self.del(obj, pos)?; + self.del(obj.clone(), pos)?; } let mut result = Vec::with_capacity(vals.len()); for v in vals { - result.push(self.insert(obj, pos, v)?); + result.push(self.insert(obj.clone(), pos, v)?); pos += 1; } Ok(result) } - pub fn splice_text( + pub fn splice_text>( &mut self, - obj: OpId, + obj: O, pos: usize, del: usize, text: &str, @@ -399,8 +408,8 @@ impl Automerge { self.splice(obj, pos, del, vals) } - pub fn text(&self, obj: OpId) -> Result { - let obj = obj.into(); + pub fn text>(&self, obj: O) -> Result { + let obj = self.import_objid(obj.into()).into(); let query = self.ops.search(obj, query::ListVals::new(obj)); let mut buffer = String::new(); for q in &query.ops { @@ -411,9 +420,9 @@ impl Automerge { Ok(buffer) } - pub fn text_at(&self, obj: OpId, heads: &[ChangeHash]) -> Result { + pub fn text_at>(&self, obj: O, heads: &[ChangeHash]) -> Result { let clock = self.clock_at(heads); - let obj = obj.into(); + let obj = self.import_objid(obj.into()).into(); let query = self.ops.search(obj, query::ListValsAt::new(clock)); let mut buffer = String::new(); for q in &query.ops { @@ -427,38 +436,38 @@ impl Automerge { // TODO - I need to return these OpId's here **only** to get // the legacy conflicts format of { [opid]: value } // Something better? - pub fn value>( + pub fn value, P: Into>( &self, - obj: OpId, + obj: O, prop: P, ) -> Result, AutomergeError> { Ok(self.values(obj, prop.into())?.first().cloned()) } - pub fn value_at>( + pub fn value_at, P: Into>( &self, - obj: OpId, + obj: O, prop: P, heads: &[ChangeHash], ) -> Result, AutomergeError> { Ok(self.values_at(obj, prop, heads)?.first().cloned()) } - pub fn values>( + pub fn values, P: Into>( &self, - obj: OpId, + obj: O, prop: P, ) -> Result, AutomergeError> { - let obj = obj.into(); + let obj = self.import_objid(obj.into()).into(); let result = match prop.into() { Prop::Map(p) => { - let prop = self.ops.m.props.lookup(p); + let prop = self.ops.m.borrow().props.lookup(p); if let Some(p) = prop { self.ops .search(obj, query::Prop::new(obj, p)) .ops .into_iter() - .map(|o| o.into()) + .map(|o| self.labelled_value(&o)) .collect() } else { vec![] @@ -469,30 +478,30 @@ impl Automerge { .search(obj, query::Nth::new(n)) .ops .into_iter() - .map(|o| o.into()) + .map(|o| self.labelled_value(&o)) .collect(), }; Ok(result) } - pub fn values_at>( + pub fn values_at, P: Into>( &self, - obj: OpId, + obj: O, prop: P, heads: &[ChangeHash], ) -> Result, AutomergeError> { let prop = prop.into(); - let obj = obj.into(); + let obj = self.import_objid(obj.into()).into(); let clock = self.clock_at(heads); let result = match prop { Prop::Map(p) => { - let prop = self.ops.m.props.lookup(p); + let prop = self.ops.m.borrow().props.lookup(p); if let Some(p) = prop { self.ops .search(obj, query::PropAt::new(p, clock)) .ops .into_iter() - .map(|o| o.into()) + .map(|o| self.labelled_value(&o)) .collect() } else { vec![] @@ -503,7 +512,7 @@ impl Automerge { .search(obj, query::NthAt::new(n, clock)) .ops .into_iter() - .map(|o| o.into()) + .map(|o| self.labelled_value(&o)) .collect(), }; Ok(result) @@ -552,7 +561,7 @@ impl Automerge { fn local_op( &mut self, - obj: ObjId, + obj: InternalObjId, prop: Prop, action: OpType, ) -> Result, AutomergeError> { @@ -564,7 +573,7 @@ impl Automerge { fn local_map_op( &mut self, - obj: ObjId, + obj: InternalObjId, prop: String, action: OpType, ) -> Result, AutomergeError> { @@ -573,7 +582,7 @@ impl Automerge { } let id = self.next_id(); - let prop = self.ops.m.props.cache(prop); + let prop = self.ops.m.borrow_mut().props.cache(prop); let query = self.ops.search(obj, query::Prop::new(obj, prop)); match (&query.ops[..], &action) { @@ -606,12 +615,12 @@ impl Automerge { self.insert_local_op(op, query.pos, &query.ops_pos); - Ok(Some(id)) + Ok(Some(self.export_opid(id).unwrap())) } fn local_list_op( &mut self, - obj: ObjId, + obj: InternalObjId, index: usize, action: OpType, ) -> Result, AutomergeError> { @@ -649,7 +658,7 @@ impl Automerge { self.insert_local_op(op, query.pos, &query.ops_pos); - Ok(Some(id)) + Ok(Some(self.export_opid(id).unwrap())) } fn is_causally_ready(&self, change: &Change) -> bool { @@ -675,17 +684,17 @@ impl Automerge { .iter_ops() .enumerate() .map(|(i, c)| { - let actor = self.ops.m.actors.cache(change.actor_id().clone()); - let id = OpId(change.start_op + i as u64, actor); + let actor = self.ops.m.borrow_mut().actors.cache(change.actor_id().clone()); + let id = InternalOpId::new(change.start_op + i as u64, actor); // FIXME dont need to_string() - let obj: ObjId = self.import(&c.obj.to_string()).unwrap(); + let obj: InternalObjId = self.import(&c.obj.to_string()).unwrap(); let pred = c .pred .iter() .map(|i| self.import(&i.to_string()).unwrap()) .collect(); let key = match &c.key { - legacy::Key::Map(n) => Key::Map(self.ops.m.props.cache(n.to_string())), + legacy::Key::Map(n) => Key::Map(self.ops.m.borrow_mut().props.cache(n.to_string())), legacy::Key::Seq(legacy::ElementId::Head) => Key::Seq(HEAD), // FIXME dont need to_string() legacy::Key::Seq(legacy::ElementId::Id(i)) => { @@ -727,8 +736,8 @@ impl Automerge { let bytes = encode_document( &c, ops.as_slice(), - &self.ops.m.actors, - &self.ops.m.props.cache, + &self.ops.m.borrow().actors, + &self.ops.m.borrow().props.cache, ); if bytes.is_ok() { self.saved = self.get_heads().iter().copied().collect(); @@ -899,7 +908,7 @@ impl Automerge { pub fn get_last_local_change(&mut self) -> Option<&Change> { self.ensure_transaction_closed(); if let Some(actor) = &self.actor { - let actor = &self.ops.m.actors[*actor]; + let actor = &self.ops.m.borrow().actors[*actor]; return self.history.iter().rev().find(|c| c.actor_id() == actor); } None @@ -930,7 +939,7 @@ impl Automerge { to_see.push(*h); } } - let actor = self.ops.m.actors.lookup(c.actor_id().clone()).unwrap(); + let actor = self.ops.m.borrow().actors.lookup(c.actor_id().clone()).unwrap(); clock.include(actor, c.max_op()); seen.insert(hash); } @@ -1006,7 +1015,7 @@ impl Automerge { let history_index = self.history.len(); self.states - .entry(self.ops.m.actors.cache(change.actor_id().clone())) + .entry(self.ops.m.borrow_mut().actors.cache(change.actor_id().clone())) .or_default() .push(history_index); @@ -1023,7 +1032,7 @@ impl Automerge { self.deps.insert(change.hash); } - pub fn import(&self, s: &str) -> Result { + pub(crate) fn import(&self, s: &str) -> Result { if let Some(x) = I::from(s) { Ok(x) } else { @@ -1037,17 +1046,36 @@ impl Automerge { let actor = self .ops .m + .borrow() .actors .lookup(actor) .ok_or_else(|| AutomergeError::InvalidOpId(s.to_owned()))?; - Ok(I::wrap(OpId(counter, actor))) + Ok(I::wrap(InternalOpId::new(counter, actor))) } } - pub fn export(&self, id: E) -> String { + fn import_opid(&self, opid: &OpId) -> InternalOpId { + opid.into_opid(&mut *self.ops.m.borrow_mut()) + } + + fn export_opid(&self, opid: InternalOpId) -> Option { + OpId::from_internal(opid, &self.ops.m.borrow_mut()) + } + + fn import_objid>(&self, objid: O) -> InternalObjId { + match objid.as_ref() { + ObjId::Root => InternalObjId::Root, + ObjId::Op(external_op) => { + let op = self.import_opid(external_op); + InternalObjId::Op(op) + } + } + } + + pub(crate) fn export(&self, id: E) -> String { match id.export() { - Export::Id(id) => format!("{}@{}", id.counter(), self.ops.m.actors[id.actor()]), - Export::Prop(index) => self.ops.m.props[index].clone(), + Export::Id(id) => format!("{}@{}", id.counter(), self.ops.m.borrow().actors[id.actor()]), + Export::Prop(index) => self.ops.m.borrow().props[index].clone(), Export::Special(s) => s, } } @@ -1066,7 +1094,7 @@ impl Automerge { let id = self.export(i.id); let obj = self.export(i.obj); let key = match i.key { - Key::Map(n) => self.ops.m.props[n].clone(), + Key::Map(n) => self.ops.m.borrow().props[n].clone(), Key::Seq(n) => self.export(n), }; let value: String = match &i.action { @@ -1093,6 +1121,16 @@ impl Automerge { pub fn visualise_optree(&self) -> String { self.ops.visualise() } + + fn labelled_value(&self, op: &Op) -> (Value, OpId) { + let id = self.export_opid(op.id).unwrap(); + let value = match &op.action { + OpType::Make(obj_type) => Value::Object(*obj_type), + OpType::Set(scalar) => Value::Scalar(scalar.clone()), + _ => panic!("expected a make or set op"), + }; + (value, id) + } } #[derive(Debug, Clone)] @@ -1123,9 +1161,9 @@ mod tests { fn insert_op() -> Result<(), AutomergeError> { let mut doc = Automerge::new(); doc.set_actor(ActorId::random()); - doc.set(ROOT, "hello", "world")?; + doc.set(ObjId::Root, "hello", "world")?; assert!(doc.pending_ops() == 1); - doc.value(ROOT, "hello")?; + doc.value(ObjId::Root, "hello")?; Ok(()) } @@ -1133,17 +1171,17 @@ mod tests { fn test_list() -> Result<(), AutomergeError> { let mut doc = Automerge::new(); doc.set_actor(ActorId::random()); - let list_id = doc.set(ROOT, "items", Value::list())?.unwrap(); - doc.set(ROOT, "zzz", "zzzval")?; - assert!(doc.value(ROOT, "items")?.unwrap().1 == list_id); + let list_id = doc.set(ObjId::Root, "items", Value::list())?.unwrap().into(); + doc.set(ObjId::Root, "zzz", "zzzval")?; + assert!(doc.value(ObjId::Root, "items")?.unwrap().1 == list_id); doc.insert(list_id, 0, "a")?; doc.insert(list_id, 0, "b")?; doc.insert(list_id, 2, "c")?; doc.insert(list_id, 1, "d")?; - assert!(doc.value(list_id, 0)?.unwrap().0 == "b".into()); - assert!(doc.value(list_id, 1)?.unwrap().0 == "d".into()); - assert!(doc.value(list_id, 2)?.unwrap().0 == "a".into()); - assert!(doc.value(list_id, 3)?.unwrap().0 == "c".into()); + assert!(doc.value(list_id.clone(), 0)?.unwrap().0 == "b".into()); + assert!(doc.value(list_id.clone(), 1)?.unwrap().0 == "d".into()); + assert!(doc.value(list_id.clone(), 2)?.unwrap().0 == "a".into()); + assert!(doc.value(list_id.clone(), 3)?.unwrap().0 == "c".into()); assert!(doc.length(list_id) == 4); doc.save()?; Ok(()) @@ -1153,22 +1191,22 @@ mod tests { fn test_del() -> Result<(), AutomergeError> { let mut doc = Automerge::new(); doc.set_actor(ActorId::random()); - doc.set(ROOT, "xxx", "xxx")?; - assert!(!doc.values(ROOT, "xxx")?.is_empty()); - doc.del(ROOT, "xxx")?; - assert!(doc.values(ROOT, "xxx")?.is_empty()); + doc.set(ObjId::Root, "xxx", "xxx")?; + assert!(!doc.values(ObjId::Root, "xxx")?.is_empty()); + doc.del(ObjId::Root, "xxx")?; + assert!(doc.values(ObjId::Root, "xxx")?.is_empty()); Ok(()) } #[test] fn test_inc() -> Result<(), AutomergeError> { let mut doc = Automerge::new(); - let id = doc.set(ROOT, "counter", Value::counter(10))?.unwrap(); - assert!(doc.value(ROOT, "counter")? == Some((Value::counter(10), id))); - doc.inc(ROOT, "counter", 10)?; - assert!(doc.value(ROOT, "counter")? == Some((Value::counter(20), id))); - doc.inc(ROOT, "counter", -5)?; - assert!(doc.value(ROOT, "counter")? == Some((Value::counter(15), id))); + let id = doc.set(ObjId::Root, "counter", Value::counter(10))?.unwrap(); + assert!(doc.value(ObjId::Root, "counter")? == Some((Value::counter(10), id))); + doc.inc(ObjId::Root, "counter", 10)?; + assert!(doc.value(ObjId::Root, "counter")? == Some((Value::counter(20), id))); + doc.inc(ObjId::Root, "counter", -5)?; + assert!(doc.value(ObjId::Root, "counter")? == Some((Value::counter(15), id))); Ok(()) } @@ -1176,15 +1214,15 @@ mod tests { fn test_save_incremental() -> Result<(), AutomergeError> { let mut doc = Automerge::new(); - doc.set(ROOT, "foo", 1)?; + doc.set(ObjId::Root, "foo", 1)?; let save1 = doc.save().unwrap(); - doc.set(ROOT, "bar", 2)?; + doc.set(ObjId::Root, "bar", 2)?; let save2 = doc.save_incremental(); - doc.set(ROOT, "baz", 3)?; + doc.set(ObjId::Root, "baz", 3)?; let save3 = doc.save_incremental(); @@ -1202,7 +1240,7 @@ mod tests { let mut doc_a = Automerge::load(&save_a)?; let mut doc_b = Automerge::load(&save_b)?; - assert!(doc_a.values(ROOT, "baz")? == doc_b.values(ROOT, "baz")?); + assert!(doc_a.values(ObjId::Root, "baz")? == doc_b.values(ObjId::Root, "baz")?); assert!(doc_a.save().unwrap() == doc_b.save().unwrap()); @@ -1212,7 +1250,7 @@ mod tests { #[test] fn test_save_text() -> Result<(), AutomergeError> { let mut doc = Automerge::new(); - let text = doc.set(ROOT, "text", Value::text())?.unwrap(); + let text = doc.set(ObjId::Root, "text", Value::text())?.unwrap(); let heads1 = doc.commit(None, None); doc.splice_text(text, 0, 0, "hello world")?; let heads2 = doc.commit(None, None); @@ -1231,50 +1269,50 @@ mod tests { fn test_props_vals_at() -> Result<(), AutomergeError> { let mut doc = Automerge::new(); doc.set_actor("aaaa".try_into().unwrap()); - doc.set(ROOT, "prop1", "val1")?; + doc.set(ObjId::Root, "prop1", "val1")?; doc.commit(None, None); let heads1 = doc.get_heads(); - doc.set(ROOT, "prop1", "val2")?; + doc.set(ObjId::Root, "prop1", "val2")?; doc.commit(None, None); let heads2 = doc.get_heads(); - doc.set(ROOT, "prop2", "val3")?; + doc.set(ObjId::Root, "prop2", "val3")?; doc.commit(None, None); let heads3 = doc.get_heads(); - doc.del(ROOT, "prop1")?; + doc.del(ObjId::Root, "prop1")?; doc.commit(None, None); let heads4 = doc.get_heads(); - doc.set(ROOT, "prop3", "val4")?; + doc.set(ObjId::Root, "prop3", "val4")?; doc.commit(None, None); let heads5 = doc.get_heads(); - assert!(doc.keys_at(ROOT, &heads1) == vec!["prop1".to_owned()]); - assert!(doc.value_at(ROOT, "prop1", &heads1)?.unwrap().0 == Value::str("val1")); - assert!(doc.value_at(ROOT, "prop2", &heads1)? == None); - assert!(doc.value_at(ROOT, "prop3", &heads1)? == None); + assert!(doc.keys_at(ObjId::Root, &heads1) == vec!["prop1".to_owned()]); + assert!(doc.value_at(ObjId::Root, "prop1", &heads1)?.unwrap().0 == Value::str("val1")); + assert!(doc.value_at(ObjId::Root, "prop2", &heads1)? == None); + assert!(doc.value_at(ObjId::Root, "prop3", &heads1)? == None); - assert!(doc.keys_at(ROOT, &heads2) == vec!["prop1".to_owned()]); - assert!(doc.value_at(ROOT, "prop1", &heads2)?.unwrap().0 == Value::str("val2")); - assert!(doc.value_at(ROOT, "prop2", &heads2)? == None); - assert!(doc.value_at(ROOT, "prop3", &heads2)? == None); + assert!(doc.keys_at(ObjId::Root, &heads2) == vec!["prop1".to_owned()]); + assert!(doc.value_at(ObjId::Root, "prop1", &heads2)?.unwrap().0 == Value::str("val2")); + assert!(doc.value_at(ObjId::Root, "prop2", &heads2)? == None); + assert!(doc.value_at(ObjId::Root, "prop3", &heads2)? == None); - assert!(doc.keys_at(ROOT, &heads3) == vec!["prop1".to_owned(), "prop2".to_owned()]); - assert!(doc.value_at(ROOT, "prop1", &heads3)?.unwrap().0 == Value::str("val2")); - assert!(doc.value_at(ROOT, "prop2", &heads3)?.unwrap().0 == Value::str("val3")); - assert!(doc.value_at(ROOT, "prop3", &heads3)? == None); + assert!(doc.keys_at(ObjId::Root, &heads3) == vec!["prop1".to_owned(), "prop2".to_owned()]); + assert!(doc.value_at(ObjId::Root, "prop1", &heads3)?.unwrap().0 == Value::str("val2")); + assert!(doc.value_at(ObjId::Root, "prop2", &heads3)?.unwrap().0 == Value::str("val3")); + assert!(doc.value_at(ObjId::Root, "prop3", &heads3)? == None); - assert!(doc.keys_at(ROOT, &heads4) == vec!["prop2".to_owned()]); - assert!(doc.value_at(ROOT, "prop1", &heads4)? == None); - assert!(doc.value_at(ROOT, "prop2", &heads4)?.unwrap().0 == Value::str("val3")); - assert!(doc.value_at(ROOT, "prop3", &heads4)? == None); + assert!(doc.keys_at(ObjId::Root, &heads4) == vec!["prop2".to_owned()]); + assert!(doc.value_at(ObjId::Root, "prop1", &heads4)? == None); + assert!(doc.value_at(ObjId::Root, "prop2", &heads4)?.unwrap().0 == Value::str("val3")); + assert!(doc.value_at(ObjId::Root, "prop3", &heads4)? == None); - assert!(doc.keys_at(ROOT, &heads5) == vec!["prop2".to_owned(), "prop3".to_owned()]); - assert!(doc.value_at(ROOT, "prop1", &heads5)? == None); - assert!(doc.value_at(ROOT, "prop2", &heads5)?.unwrap().0 == Value::str("val3")); - assert!(doc.value_at(ROOT, "prop3", &heads5)?.unwrap().0 == Value::str("val4")); + assert!(doc.keys_at(ObjId::Root, &heads5) == vec!["prop2".to_owned(), "prop3".to_owned()]); + assert!(doc.value_at(ObjId::Root, "prop1", &heads5)? == None); + assert!(doc.value_at(ObjId::Root, "prop2", &heads5)?.unwrap().0 == Value::str("val3")); + assert!(doc.value_at(ObjId::Root, "prop3", &heads5)?.unwrap().0 == Value::str("val4")); - assert!(doc.keys_at(ROOT, &[]).is_empty()); - assert!(doc.value_at(ROOT, "prop1", &[])? == None); - assert!(doc.value_at(ROOT, "prop2", &[])? == None); - assert!(doc.value_at(ROOT, "prop3", &[])? == None); + assert!(doc.keys_at(ObjId::Root, &[]).is_empty()); + assert!(doc.value_at(ObjId::Root, "prop1", &[])? == None); + assert!(doc.value_at(ObjId::Root, "prop2", &[])? == None); + assert!(doc.value_at(ObjId::Root, "prop3", &[])? == None); Ok(()) } @@ -1283,7 +1321,7 @@ mod tests { let mut doc = Automerge::new(); doc.set_actor("aaaa".try_into().unwrap()); - let list = doc.set(ROOT, "list", Value::list())?.unwrap(); + let list = doc.set(ObjId::Root, "list", Value::list())?.unwrap(); let heads1 = doc.commit(None, None); doc.insert(list, 0, Value::int(10))?; diff --git a/automerge/src/op_set.rs b/automerge/src/op_set.rs index 537cb80f..923b8cad 100644 --- a/automerge/src/op_set.rs +++ b/automerge/src/op_set.rs @@ -1,9 +1,11 @@ use crate::op_tree::OpTreeInternal; use crate::query::TreeQuery; -use crate::{ActorId, IndexedCache, Key, ObjId, Op, OpId}; +use crate::{ActorId, IndexedCache, Key, types::{ObjId, OpId}, Op}; use fxhash::FxBuildHasher; use std::cmp::Ordering; use std::collections::HashMap; +use std::rc::Rc; +use std::cell::RefCell; pub(crate) type OpSet = OpSetInternal<16>; @@ -12,7 +14,7 @@ pub(crate) struct OpSetInternal { trees: HashMap, FxBuildHasher>, objs: Vec, length: usize, - pub m: OpSetMetadata, + pub m: Rc>, } impl OpSetInternal { @@ -21,10 +23,10 @@ impl OpSetInternal { trees: Default::default(), objs: Default::default(), length: 0, - m: OpSetMetadata { + m: Rc::new(RefCell::new(OpSetMetadata { actors: IndexedCache::new(), props: IndexedCache::new(), - }, + })), } } @@ -41,7 +43,7 @@ impl OpSetInternal { Q: TreeQuery, { if let Some(tree) = self.trees.get(&obj) { - tree.search(query, &self.m) + tree.search(query, &*self.m.borrow()) } else { query } @@ -83,7 +85,7 @@ impl OpSetInternal { .entry(element.obj) .or_insert_with(|| { let pos = objs - .binary_search_by(|probe| m.lamport_cmp(probe.0, element.obj.0)) + .binary_search_by(|probe| m.borrow().lamport_cmp(probe, &element.obj)) .unwrap_err(); objs.insert(pos, element.obj); Default::default() @@ -162,14 +164,42 @@ impl OpSetMetadata { } } - pub fn lamport_cmp(&self, left: OpId, right: OpId) -> Ordering { - match (left, right) { - (OpId(0, _), OpId(0, _)) => Ordering::Equal, - (OpId(0, _), OpId(_, _)) => Ordering::Less, - (OpId(_, _), OpId(0, _)) => Ordering::Greater, - // FIXME - this one seems backwards to me - why - is values() returning in the wrong order? - (OpId(a, x), OpId(b, y)) if a == b => self.actors[y].cmp(&self.actors[x]), - (OpId(a, _), OpId(b, _)) => a.cmp(&b), + pub fn lamport_cmp(&self, left: S, right: S) -> Ordering { + S::cmp(self, left, right) + } +} + +/// Lamport timestamps which don't contain their actor ID directly and therefore need access to +/// some metadata to compare their actor ID parts +pub(crate) trait SuccinctLamport { + fn cmp(m: &OpSetMetadata, left: Self, right: Self) -> Ordering; +} + +impl SuccinctLamport for OpId { + fn cmp(m: &OpSetMetadata, left: Self, right: Self) -> Ordering { + match (left.counter(), right.counter()) { + (0, 0) => Ordering::Equal, + (0, _) => Ordering::Less, + (_, 0) => Ordering::Greater, + (a, b) if a == b => m.actors[right.actor()].cmp(&m.actors[left.actor()]), + (a, b) => a.cmp(&b), } } } + +impl SuccinctLamport for ObjId { + fn cmp(m: &OpSetMetadata, left: Self, right: Self) -> Ordering { + match (left, right) { + (ObjId::Root, ObjId::Root) => Ordering::Equal, + (ObjId::Root, ObjId::Op(_)) => Ordering::Less, + (ObjId::Op(_), ObjId::Root) => Ordering::Greater, + (ObjId::Op(left_op), ObjId::Op(right_op)) => ::cmp(m, left_op, right_op), + } + } +} + +impl SuccinctLamport for &ObjId { + fn cmp(m: &OpSetMetadata, left: Self, right: Self) -> Ordering { + ::cmp(m, *left, *right) + } +} diff --git a/automerge/src/op_tree.rs b/automerge/src/op_tree.rs index 6142a7bf..a9d72353 100644 --- a/automerge/src/op_tree.rs +++ b/automerge/src/op_tree.rs @@ -6,7 +6,7 @@ use std::{ pub(crate) use crate::op_set::OpSetMetadata; use crate::query::{Index, QueryResult, TreeQuery}; -use crate::{Op, OpId}; +use crate::types::{Op, OpId}; use std::collections::HashSet; #[allow(dead_code)] @@ -628,12 +628,12 @@ struct CounterData { #[cfg(test)] mod tests { use crate::legacy as amp; - use crate::{Op, OpId}; + use crate::types::{Op, OpId}; use super::*; fn op(n: usize) -> Op { - let zero = OpId(0, 0); + let zero = OpId::new(0, 0); Op { change: n, id: zero, diff --git a/automerge/src/query.rs b/automerge/src/query.rs index 15ac6fd6..662ad8ae 100644 --- a/automerge/src/query.rs +++ b/automerge/src/query.rs @@ -1,5 +1,5 @@ use crate::op_tree::{OpSetMetadata, OpTreeNode}; -use crate::{Clock, ElemId, Op, OpId, OpType, ScalarValue}; +use crate::{Clock, ElemId, Op, ScalarValue, types::{OpId, OpType}}; use fxhash::FxBuildHasher; use std::cmp::Ordering; use std::collections::{HashMap, HashSet}; diff --git a/automerge/src/query/len.rs b/automerge/src/query/len.rs index 494b3515..b73e804a 100644 --- a/automerge/src/query/len.rs +++ b/automerge/src/query/len.rs @@ -1,6 +1,6 @@ use crate::op_tree::OpTreeNode; use crate::query::{QueryResult, TreeQuery}; -use crate::ObjId; +use crate::types::ObjId; use std::fmt::Debug; #[derive(Debug, Clone, PartialEq)] diff --git a/automerge/src/query/list_vals.rs b/automerge/src/query/list_vals.rs index c19ac4ad..aec8f878 100644 --- a/automerge/src/query/list_vals.rs +++ b/automerge/src/query/list_vals.rs @@ -1,6 +1,6 @@ use crate::op_tree::{OpSetMetadata, OpTreeNode}; use crate::query::{binary_search_by, is_visible, visible_op, QueryResult, TreeQuery}; -use crate::{ElemId, ObjId, Op}; +use crate::{ElemId, types::ObjId, Op}; use std::fmt::Debug; #[derive(Debug, Clone, PartialEq)] @@ -26,7 +26,7 @@ impl TreeQuery for ListVals { child: &OpTreeNode, m: &OpSetMetadata, ) -> QueryResult { - let start = binary_search_by(child, |op| m.lamport_cmp(op.obj.0, self.obj.0)); + let start = binary_search_by(child, |op| m.lamport_cmp(op.obj, self.obj)); let mut counters = Default::default(); for pos in start..child.len() { let op = child.get(pos).unwrap(); diff --git a/automerge/src/query/prop.rs b/automerge/src/query/prop.rs index ac4b2bca..aa125c9d 100644 --- a/automerge/src/query/prop.rs +++ b/automerge/src/query/prop.rs @@ -1,6 +1,6 @@ use crate::op_tree::{OpSetMetadata, OpTreeNode}; use crate::query::{binary_search_by, is_visible, visible_op, QueryResult, TreeQuery}; -use crate::{Key, ObjId, Op}; +use crate::{Key, types::ObjId, Op}; use std::fmt::Debug; #[derive(Debug, Clone, PartialEq)] @@ -31,7 +31,7 @@ impl TreeQuery for Prop { m: &OpSetMetadata, ) -> QueryResult { let start = binary_search_by(child, |op| { - m.lamport_cmp(op.obj.0, self.obj.0) + m.lamport_cmp(op.obj, self.obj) .then_with(|| m.key_cmp(&op.key, &self.key)) }); let mut counters = Default::default(); diff --git a/automerge/src/types.rs b/automerge/src/types.rs index f00beed3..4adcf393 100644 --- a/automerge/src/types.rs +++ b/automerge/src/types.rs @@ -10,7 +10,6 @@ use std::str::FromStr; use tinyvec::{ArrayVec, TinyVec}; pub(crate) const HEAD: ElemId = ElemId(OpId(0, 0)); -pub const ROOT: OpId = OpId(0, 0); const ROOT_STR: &str = "_root"; const HEAD_STR: &str = "_head"; @@ -161,17 +160,17 @@ pub enum OpType { } #[derive(Debug)] -pub enum Export { +pub(crate) enum Export { Id(OpId), Special(String), Prop(usize), } -pub trait Exportable { +pub(crate) trait Exportable { fn export(&self) -> Export; } -pub trait Importable { +pub(crate) trait Importable { fn wrap(id: OpId) -> Self; fn from(s: &str) -> Option where @@ -179,33 +178,32 @@ pub trait Importable { } impl OpId { + pub(crate) fn new(counter: u64, actor: usize) -> OpId { + OpId(counter, actor) + } + #[inline] pub fn counter(&self) -> u64 { self.0 } #[inline] - pub fn actor(&self) -> usize { + pub(crate) fn actor(&self) -> usize { self.1 } } impl Exportable for ObjId { fn export(&self) -> Export { - if self.0 == ROOT { - Export::Special(ROOT_STR.to_owned()) - } else { - Export::Id(self.0) + match self { + ObjId::Root => Export::Special(ROOT_STR.to_owned()), + ObjId::Op(o) => Export::Id(*o) } } } impl Exportable for &ObjId { fn export(&self) -> Export { - if self.0 == ROOT { - Export::Special(ROOT_STR.to_owned()) - } else { - Export::Id(self.0) - } + (*self).export() } } @@ -236,11 +234,11 @@ impl Exportable for Key { impl Importable for ObjId { fn wrap(id: OpId) -> Self { - ObjId(id) + ObjId::Op(id) } fn from(s: &str) -> Option { if s == ROOT_STR { - Some(ROOT.into()) + Some(ObjId::Root) } else { None } @@ -251,12 +249,8 @@ impl Importable for OpId { fn wrap(id: OpId) -> Self { id } - fn from(s: &str) -> Option { - if s == ROOT_STR { - Some(ROOT) - } else { - None - } + fn from(_s: &str) -> Option { + None } } @@ -275,7 +269,10 @@ impl Importable for ElemId { impl From for ObjId { fn from(o: OpId) -> Self { - ObjId(o) + match (o.counter(), o.actor()) { + (0,0) => ObjId::Root, + (_,_) => ObjId::Op(o), + } } } @@ -352,10 +349,19 @@ impl Key { } #[derive(Debug, Clone, PartialOrd, Ord, Eq, PartialEq, Copy, Hash, Default)] -pub struct OpId(pub u64, pub usize); +pub(crate) struct OpId(u64, usize); -#[derive(Debug, Clone, Copy, PartialOrd, Eq, PartialEq, Ord, Hash, Default)] -pub(crate) struct ObjId(pub OpId); +#[derive(Debug, Clone, Copy, PartialOrd, Eq, PartialEq, Ord, Hash)] +pub(crate) enum ObjId{ + Root, + Op(OpId), +} + +impl Default for ObjId { + fn default() -> Self { + Self::Root + } +} #[derive(Debug, Clone, Copy, PartialOrd, Eq, PartialEq, Ord, Hash, Default)] pub(crate) struct ElemId(pub OpId); diff --git a/automerge/src/value.rs b/automerge/src/value.rs index 333c1f53..2859073e 100644 --- a/automerge/src/value.rs +++ b/automerge/src/value.rs @@ -1,4 +1,4 @@ -use crate::{error, ObjType, Op, OpId, OpType}; +use crate::{error, ObjType, Op, types::OpId, OpType}; use serde::{Deserialize, Serialize}; use smol_str::SmolStr; use std::convert::TryFrom; diff --git a/automerge/tests/helpers/mod.rs b/automerge/tests/helpers/mod.rs index d93a211b..40ee7faf 100644 --- a/automerge/tests/helpers/mod.rs +++ b/automerge/tests/helpers/mod.rs @@ -1,3 +1,5 @@ +use automerge::ObjId; + use std::{collections::HashMap, convert::TryInto, hash::Hash}; use serde::ser::{SerializeMap, SerializeSeq}; @@ -84,10 +86,9 @@ pub fn sorted_actors() -> (automerge::ActorId, automerge::ActorId) { #[macro_export] macro_rules! assert_doc { ($doc: expr, $expected: expr) => {{ - use $crate::helpers::{realize, ExportableOpId}; + use $crate::helpers::realize; let realized = realize($doc); - let to_export: RealizedObject> = $expected.into(); - let exported = to_export.export($doc); + let exported: RealizedObject = $expected.into(); if realized != exported { let serde_right = serde_json::to_string_pretty(&realized).unwrap(); let serde_left = serde_json::to_string_pretty(&exported).unwrap(); @@ -105,10 +106,9 @@ macro_rules! assert_doc { #[macro_export] macro_rules! assert_obj { ($doc: expr, $obj_id: expr, $prop: expr, $expected: expr) => {{ - use $crate::helpers::{realize_prop, ExportableOpId}; + use $crate::helpers::realize_prop; let realized = realize_prop($doc, $obj_id, $prop); - let to_export: RealizedObject> = $expected.into(); - let exported = to_export.export($doc); + let exported: RealizedObject = $expected.into(); if realized != exported { let serde_right = serde_json::to_string_pretty(&realized).unwrap(); let serde_left = serde_json::to_string_pretty(&exported).unwrap(); @@ -145,7 +145,7 @@ macro_rules! map { (@inner { $($opid:expr => $value:expr),* }) => { { use std::collections::HashMap; - let mut inner: HashMap, RealizedObject>> = HashMap::new(); + let mut inner: HashMap = HashMap::new(); $( let _ = inner.insert($opid.into(), $value.into()); )* @@ -159,9 +159,8 @@ macro_rules! map { ($($key:expr => $inner:tt),*) => { { use std::collections::HashMap; - use crate::helpers::ExportableOpId; let _cap = map!(@count $($key),*); - let mut _map: HashMap, RealizedObject>>> = ::std::collections::HashMap::with_capacity(_cap); + let mut _map: HashMap> = ::std::collections::HashMap::with_capacity(_cap); $( let inner = map!(@inner $inner); let _ = _map.insert($key.to_string(), inner); @@ -194,7 +193,7 @@ macro_rules! list { (@inner { $($opid:expr => $value:expr),* }) => { { use std::collections::HashMap; - let mut inner: HashMap, RealizedObject>> = HashMap::new(); + let mut inner: HashMap = HashMap::new(); $( let _ = inner.insert($opid.into(), $value.into()); )* @@ -204,9 +203,8 @@ macro_rules! list { ($($inner:tt,)+) => { list!($($inner),+) }; ($($inner:tt),*) => { { - use crate::helpers::ExportableOpId; let _cap = list!(@count $($inner),*); - let mut _list: Vec, RealizedObject>>> = Vec::new(); + let mut _list: Vec> = Vec::new(); $( //println!("{}", stringify!($inner)); let inner = list!(@inner $inner); @@ -217,26 +215,6 @@ macro_rules! list { } } -/// Translate an op ID produced by one document to an op ID which can be understood by -/// another -/// -/// The current API of automerge exposes OpIds of the form (u64, usize) where the first component -/// is the counter of an actors lamport timestamp and the second component is the index into an -/// array of actor IDs stored by the document where the opid was generated. Obviously this is not -/// portable between documents as the index of the actor array is unlikely to match between two -/// documents. This function translates between the two representations. -/// -/// At some point we will probably change the API to not be document specific but this function -/// allows us to write tests first. -pub fn translate_obj_id( - from: &automerge::Automerge, - to: &automerge::Automerge, - id: automerge::OpId, -) -> automerge::OpId { - let exported = from.export(id); - to.import(&exported).unwrap() -} - pub fn mk_counter(value: i64) -> automerge::ScalarValue { automerge::ScalarValue::Counter(value) } @@ -253,13 +231,13 @@ impl std::fmt::Display for ExportedOpId { /// A `RealizedObject` is a representation of all the current values in a document - including /// conflicts. #[derive(PartialEq, Debug)] -pub enum RealizedObject { - Map(HashMap>>), - Sequence(Vec>>), +pub enum RealizedObject { + Map(HashMap>), + Sequence(Vec>), Value(automerge::ScalarValue), } -impl serde::Serialize for RealizedObject { +impl serde::Serialize for RealizedObject { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, @@ -271,7 +249,7 @@ impl serde::Serialize for RealizedObject { let kvs_serded = kvs .iter() .map(|(opid, value)| (opid.to_string(), value)) - .collect::>>(); + .collect::>(); map_ser.serialize_entry(k, &kvs_serded)?; } map_ser.end() @@ -282,7 +260,7 @@ impl serde::Serialize for RealizedObject { let kvs_serded = elem .iter() .map(|(opid, value)| (opid.to_string(), value)) - .collect::>>(); + .collect::>(); list_ser.serialize_element(&kvs_serded)?; } list_ser.end() @@ -292,40 +270,40 @@ impl serde::Serialize for RealizedObject { } } -pub fn realize(doc: &automerge::Automerge) -> RealizedObject { - realize_obj(doc, automerge::ROOT, automerge::ObjType::Map) +pub fn realize(doc: &automerge::Automerge) -> RealizedObject { + realize_obj(doc, ObjId::Root, automerge::ObjType::Map) } pub fn realize_prop>( doc: &automerge::Automerge, - obj_id: automerge::OpId, + obj_id: automerge::ObjId, prop: P, -) -> RealizedObject { +) -> RealizedObject { let (val, obj_id) = doc.value(obj_id, prop).unwrap().unwrap(); match val { - automerge::Value::Object(obj_type) => realize_obj(doc, obj_id, obj_type), + automerge::Value::Object(obj_type) => realize_obj(doc, obj_id.into(), obj_type), automerge::Value::Scalar(v) => RealizedObject::Value(v), } } pub fn realize_obj( doc: &automerge::Automerge, - obj_id: automerge::OpId, + obj_id: automerge::ObjId, objtype: automerge::ObjType, -) -> RealizedObject { +) -> RealizedObject { match objtype { automerge::ObjType::Map | automerge::ObjType::Table => { let mut result = HashMap::new(); - for key in doc.keys(obj_id) { - result.insert(key.clone(), realize_values(doc, obj_id, key)); + for key in doc.keys(obj_id.clone()) { + result.insert(key.clone(), realize_values(doc, obj_id.clone(), key)); } RealizedObject::Map(result) } automerge::ObjType::List | automerge::ObjType::Text => { - let length = doc.length(obj_id); + let length = doc.length(obj_id.clone()); let mut result = Vec::with_capacity(length); for i in 0..length { - result.push(realize_values(doc, obj_id, i)); + result.push(realize_values(doc, obj_id.clone(), i)); } RealizedObject::Sequence(result) } @@ -334,55 +312,25 @@ pub fn realize_obj( fn realize_values>( doc: &automerge::Automerge, - obj_id: automerge::OpId, + obj_id: automerge::ObjId, key: K, -) -> HashMap> { - let mut values_by_opid = HashMap::new(); +) -> HashMap { + let mut values_by_objid: HashMap = HashMap::new(); for (value, opid) in doc.values(obj_id, key).unwrap() { let realized = match value { - automerge::Value::Object(objtype) => realize_obj(doc, opid, objtype), + automerge::Value::Object(objtype) => realize_obj(doc, opid.clone().into(), objtype), automerge::Value::Scalar(v) => RealizedObject::Value(v), }; - let exported_opid = ExportedOpId(doc.export(opid)); - values_by_opid.insert(exported_opid, realized); + values_by_objid.insert(opid.into(), realized); } - values_by_opid + values_by_objid } -impl<'a> RealizedObject> { - pub fn export(self, doc: &automerge::Automerge) -> RealizedObject { - match self { - Self::Map(kvs) => RealizedObject::Map( - kvs.into_iter() - .map(|(k, v)| { - ( - k, - v.into_iter() - .map(|(k, v)| (k.export(doc), v.export(doc))) - .collect(), - ) - }) - .collect(), - ), - Self::Sequence(values) => RealizedObject::Sequence( - values - .into_iter() - .map(|v| { - v.into_iter() - .map(|(k, v)| (k.export(doc), v.export(doc))) - .collect() - }) - .collect(), - ), - Self::Value(v) => RealizedObject::Value(v), - } - } -} -impl<'a, O: Into>, I: Into>>> - From>> for RealizedObject> +impl> + From>> for RealizedObject { - fn from(values: HashMap<&str, HashMap>) -> Self { + fn from(values: HashMap<&str, HashMap>) -> Self { let intoed = values .into_iter() .map(|(k, v)| { @@ -396,10 +344,10 @@ impl<'a, O: Into>, I: Into> } } -impl<'a, O: Into>, I: Into>>> - From>> for RealizedObject> +impl> + From>> for RealizedObject { - fn from(values: Vec>) -> Self { + fn from(values: Vec>) -> Self { RealizedObject::Sequence( values .into_iter() @@ -409,94 +357,31 @@ impl<'a, O: Into>, I: Into> } } -impl From for RealizedObject> { +impl From for RealizedObject { fn from(b: bool) -> Self { RealizedObject::Value(b.into()) } } -impl From for RealizedObject> { +impl From for RealizedObject { fn from(u: usize) -> Self { let v = u.try_into().unwrap(); RealizedObject::Value(automerge::ScalarValue::Int(v)) } } -impl From for RealizedObject> { +impl From for RealizedObject { fn from(s: automerge::ScalarValue) -> Self { RealizedObject::Value(s) } } -impl From<&str> for RealizedObject> { +impl From<&str> for RealizedObject { fn from(s: &str) -> Self { RealizedObject::Value(automerge::ScalarValue::Str(s.into())) } } -#[derive(Eq, PartialEq, Hash)] -pub enum ExportableOpId<'a> { - Native(automerge::OpId), - Translate(Translate<'a>), -} - -impl<'a> ExportableOpId<'a> { - fn export(self, doc: &automerge::Automerge) -> ExportedOpId { - let oid = match self { - Self::Native(oid) => oid, - Self::Translate(Translate { from, opid }) => translate_obj_id(from, doc, opid), - }; - ExportedOpId(doc.export(oid)) - } -} - -pub struct Translate<'a> { - from: &'a automerge::Automerge, - opid: automerge::OpId, -} - -impl<'a> PartialEq for Translate<'a> { - fn eq(&self, other: &Self) -> bool { - self.from.maybe_get_actor().unwrap() == other.from.maybe_get_actor().unwrap() - && self.opid == other.opid - } -} - -impl<'a> Eq for Translate<'a> {} - -impl<'a> Hash for Translate<'a> { - fn hash(&self, state: &mut H) { - self.from.maybe_get_actor().unwrap().hash(state); - self.opid.hash(state); - } -} - -pub trait OpIdExt { - fn native(self) -> ExportableOpId<'static>; - fn translate(self, doc: &automerge::Automerge) -> ExportableOpId<'_>; -} - -impl OpIdExt for automerge::OpId { - /// Use this opid directly when exporting - fn native(self) -> ExportableOpId<'static> { - ExportableOpId::Native(self) - } - - /// Translate this OpID from `doc` when exporting - fn translate(self, doc: &automerge::Automerge) -> ExportableOpId<'_> { - ExportableOpId::Translate(Translate { - from: doc, - opid: self, - }) - } -} - -impl From for ExportableOpId<'_> { - fn from(oid: automerge::OpId) -> Self { - ExportableOpId::Native(oid) - } -} - /// Pretty print the contents of a document #[allow(dead_code)] pub fn pretty_print(doc: &automerge::Automerge) { diff --git a/automerge/tests/test.rs b/automerge/tests/test.rs index 8dcc51df..9bf8a0ea 100644 --- a/automerge/tests/test.rs +++ b/automerge/tests/test.rs @@ -1,16 +1,16 @@ -use automerge::Automerge; +use automerge::{Automerge, ObjId}; mod helpers; #[allow(unused_imports)] use helpers::{ mk_counter, new_doc, new_doc_with_actor, pretty_print, realize, realize_obj, sorted_actors, - translate_obj_id, OpIdExt, RealizedObject, + RealizedObject, }; #[test] fn no_conflict_on_repeated_assignment() { let mut doc = Automerge::new(); - doc.set(automerge::ROOT, "foo", 1).unwrap(); - let op = doc.set(automerge::ROOT, "foo", 2).unwrap().unwrap(); + doc.set(ObjId::Root, "foo", 1).unwrap(); + let op = doc.set(ObjId::Root, "foo", 2).unwrap().unwrap(); assert_doc!( &doc, map! { @@ -22,45 +22,45 @@ fn no_conflict_on_repeated_assignment() { #[test] fn no_change_on_repeated_map_set() { let mut doc = new_doc(); - doc.set(automerge::ROOT, "foo", 1).unwrap(); - assert!(doc.set(automerge::ROOT, "foo", 1).unwrap().is_none()); + doc.set(ObjId::Root, "foo", 1).unwrap(); + assert!(doc.set(ObjId::Root, "foo", 1).unwrap().is_none()); } #[test] fn no_change_on_repeated_list_set() { let mut doc = new_doc(); - let list_id = doc - .set(automerge::ROOT, "list", automerge::Value::list()) + let list_id: ObjId = doc + .set(ObjId::Root, "list", automerge::Value::list()) .unwrap() - .unwrap(); - doc.insert(list_id, 0, 1).unwrap(); - doc.set(list_id, 0, 1).unwrap(); + .unwrap().into(); + doc.insert(list_id.clone(), 0, 1).unwrap(); + doc.set(list_id.clone(), 0, 1).unwrap(); assert!(doc.set(list_id, 0, 1).unwrap().is_none()); } #[test] fn no_change_on_list_insert_followed_by_set_of_same_value() { let mut doc = new_doc(); - let list_id = doc - .set(automerge::ROOT, "list", automerge::Value::list()) + let list_id: ObjId = doc + .set(ObjId::Root, "list", automerge::Value::list()) .unwrap() - .unwrap(); - doc.insert(list_id, 0, 1).unwrap(); - assert!(doc.set(list_id, 0, 1).unwrap().is_none()); + .unwrap().into(); + doc.insert(list_id.clone(), 0, 1).unwrap(); + assert!(doc.set(list_id.clone(), 0, 1).unwrap().is_none()); } #[test] fn repeated_map_assignment_which_resolves_conflict_not_ignored() { let mut doc1 = new_doc(); let mut doc2 = new_doc(); - doc1.set(automerge::ROOT, "field", 123).unwrap(); + doc1.set(ObjId::Root, "field", 123).unwrap(); doc2.merge(&mut doc1); - doc2.set(automerge::ROOT, "field", 456).unwrap(); - doc1.set(automerge::ROOT, "field", 789).unwrap(); + doc2.set(ObjId::Root, "field", 456).unwrap(); + doc1.set(ObjId::Root, "field", 789).unwrap(); doc1.merge(&mut doc2); - assert_eq!(doc1.values(automerge::ROOT, "field").unwrap().len(), 2); + assert_eq!(doc1.values(ObjId::Root, "field").unwrap().len(), 2); - let op = doc1.set(automerge::ROOT, "field", 123).unwrap().unwrap(); + let op = doc1.set(ObjId::Root, "field", 123).unwrap().unwrap(); assert_doc!( &doc1, map! { @@ -75,16 +75,15 @@ fn repeated_map_assignment_which_resolves_conflict_not_ignored() { fn repeated_list_assignment_which_resolves_conflict_not_ignored() { let mut doc1 = new_doc(); let mut doc2 = new_doc(); - let list_id = doc1 - .set(automerge::ROOT, "list", automerge::Value::list()) + let list_id: ObjId = doc1 + .set(ObjId::Root, "list", automerge::Value::list()) .unwrap() - .unwrap(); - doc1.insert(list_id, 0, 123).unwrap(); + .unwrap().into(); + doc1.insert(list_id.clone(), 0, 123).unwrap(); doc2.merge(&mut doc1); - let list_id_in_doc2 = translate_obj_id(&doc1, &doc2, list_id); - doc2.set(list_id_in_doc2, 0, 456).unwrap().unwrap(); + doc2.set(list_id.clone(), 0, 456).unwrap().unwrap(); doc1.merge(&mut doc2); - let doc1_op = doc1.set(list_id, 0, 789).unwrap().unwrap(); + let doc1_op = doc1.set(list_id.clone(), 0, 789).unwrap().unwrap(); assert_doc!( &doc1, @@ -101,14 +100,14 @@ fn repeated_list_assignment_which_resolves_conflict_not_ignored() { #[test] fn list_deletion() { let mut doc = new_doc(); - let list_id = doc - .set(automerge::ROOT, "list", automerge::Value::list()) + let list_id: ObjId = doc + .set(ObjId::Root, "list", automerge::Value::list()) .unwrap() - .unwrap(); - let op1 = doc.insert(list_id, 0, 123).unwrap(); - doc.insert(list_id, 1, 456).unwrap(); - let op3 = doc.insert(list_id, 2, 789).unwrap(); - doc.del(list_id, 1).unwrap(); + .unwrap().into(); + let op1 = doc.insert(list_id.clone(), 0, 123).unwrap(); + doc.insert(list_id.clone(), 1, 456).unwrap(); + let op3 = doc.insert(list_id.clone(), 2, 789).unwrap(); + doc.del(list_id.clone(), 1).unwrap(); assert_doc!( &doc, map! { @@ -124,28 +123,28 @@ fn list_deletion() { fn merge_concurrent_map_prop_updates() { let mut doc1 = new_doc(); let mut doc2 = new_doc(); - let op1 = doc1.set(automerge::ROOT, "foo", "bar").unwrap().unwrap(); - let hello = doc2 - .set(automerge::ROOT, "hello", "world") + let op1 = doc1.set(ObjId::Root, "foo", "bar").unwrap().unwrap(); + let hello: ObjId = doc2 + .set(ObjId::Root, "hello", "world") .unwrap() - .unwrap(); + .unwrap().into(); doc1.merge(&mut doc2); assert_eq!( - doc1.value(automerge::ROOT, "foo").unwrap().unwrap().0, + doc1.value(ObjId::Root, "foo").unwrap().unwrap().0, "bar".into() ); assert_doc!( &doc1, map! { - "foo" => { op1 => "bar" }, - "hello" => { hello.translate(&doc2) => "world" }, + "foo" => { op1.clone() => "bar" }, + "hello" => { hello.clone() => "world" }, } ); doc2.merge(&mut doc1); assert_doc!( &doc2, map! { - "foo" => { op1.translate(&doc1) => "bar" }, + "foo" => { op1 => "bar" }, "hello" => { hello => "world" }, } ); @@ -157,12 +156,12 @@ fn add_concurrent_increments_of_same_property() { let mut doc1 = new_doc(); let mut doc2 = new_doc(); let counter_id = doc1 - .set(automerge::ROOT, "counter", mk_counter(0)) + .set(ObjId::Root, "counter", mk_counter(0)) .unwrap() .unwrap(); doc2.merge(&mut doc1); - doc1.inc(automerge::ROOT, "counter", 1).unwrap(); - doc2.inc(automerge::ROOT, "counter", 2).unwrap(); + doc1.inc(ObjId::Root, "counter", 1).unwrap(); + doc2.inc(ObjId::Root, "counter", 2).unwrap(); doc1.merge(&mut doc2); assert_doc!( &doc1, @@ -181,17 +180,17 @@ fn add_increments_only_to_preceeded_values() { // create a counter in doc1 let doc1_counter_id = doc1 - .set(automerge::ROOT, "counter", mk_counter(0)) + .set(ObjId::Root, "counter", mk_counter(0)) .unwrap() .unwrap(); - doc1.inc(automerge::ROOT, "counter", 1).unwrap(); + doc1.inc(ObjId::Root, "counter", 1).unwrap(); // create a counter in doc2 let doc2_counter_id = doc2 - .set(automerge::ROOT, "counter", mk_counter(0)) + .set(ObjId::Root, "counter", mk_counter(0)) .unwrap() .unwrap(); - doc2.inc(automerge::ROOT, "counter", 3).unwrap(); + doc2.inc(ObjId::Root, "counter", 3).unwrap(); // The two values should be conflicting rather than added doc1.merge(&mut doc2); @@ -200,8 +199,8 @@ fn add_increments_only_to_preceeded_values() { &doc1, map! { "counter" => { - doc1_counter_id.native() => mk_counter(1), - doc2_counter_id.translate(&doc2) => mk_counter(3), + doc1_counter_id => mk_counter(1), + doc2_counter_id => mk_counter(3), } } ); @@ -211,8 +210,8 @@ fn add_increments_only_to_preceeded_values() { fn concurrent_updates_of_same_field() { let mut doc1 = new_doc(); let mut doc2 = new_doc(); - let set_one_opid = doc1.set(automerge::ROOT, "field", "one").unwrap().unwrap(); - let set_two_opid = doc2.set(automerge::ROOT, "field", "two").unwrap().unwrap(); + let set_one_opid = doc1.set(ObjId::Root, "field", "one").unwrap().unwrap(); + let set_two_opid = doc2.set(ObjId::Root, "field", "two").unwrap().unwrap(); doc1.merge(&mut doc2); @@ -220,8 +219,8 @@ fn concurrent_updates_of_same_field() { &doc1, map! { "field" => { - set_one_opid.native() => "one", - set_two_opid.translate(&doc2) => "two", + set_one_opid => "one", + set_two_opid => "two", } } ); @@ -231,15 +230,14 @@ fn concurrent_updates_of_same_field() { fn concurrent_updates_of_same_list_element() { let mut doc1 = new_doc(); let mut doc2 = new_doc(); - let list_id = doc1 - .set(automerge::ROOT, "birds", automerge::Value::list()) + let list_id: ObjId = doc1 + .set(ObjId::Root, "birds", automerge::Value::list()) .unwrap() - .unwrap(); - doc1.insert(list_id, 0, "finch").unwrap(); + .unwrap().into(); + doc1.insert(list_id.clone(), 0, "finch").unwrap(); doc2.merge(&mut doc1); - let set_one_op = doc1.set(list_id, 0, "greenfinch").unwrap().unwrap(); - let list_id_in_doc2 = translate_obj_id(&doc1, &doc2, list_id); - let set_op_two = doc2.set(list_id_in_doc2, 0, "goldfinch").unwrap().unwrap(); + let set_one_op = doc1.set(list_id.clone(), 0, "greenfinch").unwrap().unwrap(); + let set_op_two = doc2.set(list_id.clone(), 0, "goldfinch").unwrap().unwrap(); doc1.merge(&mut doc2); @@ -248,8 +246,8 @@ fn concurrent_updates_of_same_list_element() { map! { "birds" => { list_id => list![{ - set_one_op.native() => "greenfinch", - set_op_two.translate(&doc2) => "goldfinch", + set_one_op => "greenfinch", + set_op_two => "goldfinch", }] } } @@ -262,15 +260,15 @@ fn assignment_conflicts_of_different_types() { let mut doc2 = new_doc(); let mut doc3 = new_doc(); let op_one = doc1 - .set(automerge::ROOT, "field", "string") + .set(ObjId::Root, "field", "string") .unwrap() .unwrap(); let op_two = doc2 - .set(automerge::ROOT, "field", automerge::Value::list()) + .set(ObjId::Root, "field", automerge::Value::list()) .unwrap() .unwrap(); let op_three = doc3 - .set(automerge::ROOT, "field", automerge::Value::map()) + .set(ObjId::Root, "field", automerge::Value::map()) .unwrap() .unwrap(); @@ -281,9 +279,9 @@ fn assignment_conflicts_of_different_types() { &doc1, map! { "field" => { - op_one.native() => "string", - op_two.translate(&doc2) => list!{}, - op_three.translate(&doc3) => map!{}, + op_one => "string", + op_two => list!{}, + op_three => map!{}, } } ); @@ -294,24 +292,24 @@ fn changes_within_conflicting_map_field() { let mut doc1 = new_doc(); let mut doc2 = new_doc(); let op_one = doc1 - .set(automerge::ROOT, "field", "string") + .set(ObjId::Root, "field", "string") .unwrap() .unwrap(); - let map_id = doc2 - .set(automerge::ROOT, "field", automerge::Value::map()) + let map_id: ObjId = doc2 + .set(ObjId::Root, "field", automerge::Value::map()) .unwrap() - .unwrap(); - let set_in_doc2 = doc2.set(map_id, "innerKey", 42).unwrap().unwrap(); + .unwrap().into(); + let set_in_doc2 = doc2.set(map_id.clone(), "innerKey", 42).unwrap().unwrap(); doc1.merge(&mut doc2); assert_doc!( &doc1, map! { "field" => { - op_one.native() => "string", - map_id.translate(&doc2) => map!{ + op_one => "string", + map_id => map!{ "innerKey" => { - set_in_doc2.translate(&doc2) => 42, + set_in_doc2 => 42, } } } @@ -325,27 +323,26 @@ fn changes_within_conflicting_list_element() { let mut doc1 = new_doc_with_actor(actor1); let mut doc2 = new_doc_with_actor(actor2); let list_id = doc1 - .set(automerge::ROOT, "list", automerge::Value::list()) + .set(ObjId::Root, "list", automerge::Value::list()) .unwrap() .unwrap(); - doc1.insert(list_id, 0, "hello").unwrap(); + doc1.insert(list_id.clone(), 0, "hello").unwrap(); doc2.merge(&mut doc1); let map_in_doc1 = doc1 - .set(list_id, 0, automerge::Value::map()) + .set(list_id.clone(), 0, automerge::Value::map()) .unwrap() .unwrap(); - let set_map1 = doc1.set(map_in_doc1, "map1", true).unwrap().unwrap(); - let set_key1 = doc1.set(map_in_doc1, "key", 1).unwrap().unwrap(); + let set_map1 = doc1.set(map_in_doc1.clone(), "map1", true).unwrap().unwrap(); + let set_key1 = doc1.set(map_in_doc1.clone(), "key", 1).unwrap().unwrap(); - let list_id_in_doc2 = translate_obj_id(&doc1, &doc2, list_id); let map_in_doc2 = doc2 - .set(list_id_in_doc2, 0, automerge::Value::map()) + .set(list_id.clone(), 0, automerge::Value::map()) .unwrap() .unwrap(); doc1.merge(&mut doc2); - let set_map2 = doc2.set(map_in_doc2, "map2", true).unwrap().unwrap(); - let set_key2 = doc2.set(map_in_doc2, "key", 2).unwrap().unwrap(); + let set_map2 = doc2.set(map_in_doc2.clone(), "map2", true).unwrap().unwrap(); + let set_key2 = doc2.set(map_in_doc2.clone(), "key", 2).unwrap().unwrap(); doc1.merge(&mut doc2); @@ -355,13 +352,13 @@ fn changes_within_conflicting_list_element() { "list" => { list_id => list![ { - map_in_doc2.translate(&doc2) => map!{ - "map2" => { set_map2.translate(&doc2) => true }, - "key" => { set_key2.translate(&doc2) => 2 }, + map_in_doc2 => map!{ + "map2" => { set_map2 => true }, + "key" => { set_key2 => 2 }, }, - map_in_doc1.native() => map!{ - "key" => { set_key1.native() => 1 }, - "map1" => { set_map1.native() => true }, + map_in_doc1 => map!{ + "key" => { set_key1 => 1 }, + "map1" => { set_map1 => true }, } } ] @@ -376,20 +373,20 @@ fn concurrently_assigned_nested_maps_should_not_merge() { let mut doc2 = new_doc(); let doc1_map_id = doc1 - .set(automerge::ROOT, "config", automerge::Value::map()) + .set(ObjId::Root, "config", automerge::Value::map()) .unwrap() .unwrap(); let doc1_field = doc1 - .set(doc1_map_id, "background", "blue") + .set(doc1_map_id.clone(), "background", "blue") .unwrap() .unwrap(); let doc2_map_id = doc2 - .set(automerge::ROOT, "config", automerge::Value::map()) + .set(ObjId::Root, "config", automerge::Value::map()) .unwrap() .unwrap(); let doc2_field = doc2 - .set(doc2_map_id, "logo_url", "logo.png") + .set(doc2_map_id.clone(), "logo_url", "logo.png") .unwrap() .unwrap(); @@ -399,11 +396,11 @@ fn concurrently_assigned_nested_maps_should_not_merge() { &doc1, map! { "config" => { - doc1_map_id.native() => map!{ - "background" => {doc1_field.native() => "blue"} + doc1_map_id => map!{ + "background" => {doc1_field => "blue"} }, - doc2_map_id.translate(&doc2) => map!{ - "logo_url" => {doc2_field.translate(&doc2) => "logo.png"} + doc2_map_id => map!{ + "logo_url" => {doc2_field => "logo.png"} } } } @@ -418,16 +415,15 @@ fn concurrent_insertions_at_different_list_positions() { assert!(doc1.maybe_get_actor().unwrap() < doc2.maybe_get_actor().unwrap()); let list_id = doc1 - .set(automerge::ROOT, "list", automerge::Value::list()) + .set(ObjId::Root, "list", automerge::Value::list()) .unwrap() .unwrap(); - let one = doc1.insert(list_id, 0, "one").unwrap(); - let three = doc1.insert(list_id, 1, "three").unwrap(); + let one = doc1.insert(list_id.clone(), 0, "one").unwrap(); + let three = doc1.insert(list_id.clone(), 1, "three").unwrap(); doc2.merge(&mut doc1); - let two = doc1.splice(list_id, 1, 0, vec!["two".into()]).unwrap()[0]; - let list_id_in_doc2 = translate_obj_id(&doc1, &doc2, list_id); - let four = doc2.insert(list_id_in_doc2, 2, "four").unwrap(); + let two = doc1.splice(list_id.clone(), 1, 0, vec!["two".into()]).unwrap()[0].clone(); + let four = doc2.insert(list_id.clone(), 2, "four").unwrap(); doc1.merge(&mut doc2); @@ -436,10 +432,10 @@ fn concurrent_insertions_at_different_list_positions() { map! { "list" => { list_id => list![ - {one.native() => "one"}, - {two.native() => "two"}, - {three.native() => "three"}, - {four.translate(&doc2) => "four"}, + {one => "one"}, + {two => "two"}, + {three => "three"}, + {four => "four"}, ] } } @@ -454,15 +450,14 @@ fn concurrent_insertions_at_same_list_position() { assert!(doc1.maybe_get_actor().unwrap() < doc2.maybe_get_actor().unwrap()); let list_id = doc1 - .set(automerge::ROOT, "birds", automerge::Value::list()) + .set(ObjId::Root, "birds", automerge::Value::list()) .unwrap() .unwrap(); - let parakeet = doc1.insert(list_id, 0, "parakeet").unwrap(); + let parakeet = doc1.insert(list_id.clone(), 0, "parakeet").unwrap(); doc2.merge(&mut doc1); - let list_id_in_doc2 = translate_obj_id(&doc1, &doc2, list_id); - let starling = doc1.insert(list_id, 1, "starling").unwrap(); - let chaffinch = doc2.insert(list_id_in_doc2, 1, "chaffinch").unwrap(); + let starling = doc1.insert(list_id.clone(), 1, "starling").unwrap(); + let chaffinch = doc2.insert(list_id.clone(), 1, "chaffinch").unwrap(); doc1.merge(&mut doc2); assert_doc!( @@ -471,13 +466,13 @@ fn concurrent_insertions_at_same_list_position() { "birds" => { list_id => list![ { - parakeet.native() => "parakeet", + parakeet => "parakeet", }, { - starling.native() => "starling", + starling => "starling", }, { - chaffinch.translate(&doc2) => "chaffinch", + chaffinch => "chaffinch", }, ] }, @@ -489,11 +484,11 @@ fn concurrent_insertions_at_same_list_position() { fn concurrent_assignment_and_deletion_of_a_map_entry() { let mut doc1 = new_doc(); let mut doc2 = new_doc(); - doc1.set(automerge::ROOT, "bestBird", "robin").unwrap(); + doc1.set(ObjId::Root, "bestBird", "robin").unwrap(); doc2.merge(&mut doc1); - doc1.del(automerge::ROOT, "bestBird").unwrap(); + doc1.del(ObjId::Root, "bestBird").unwrap(); let set_two = doc2 - .set(automerge::ROOT, "bestBird", "magpie") + .set(ObjId::Root, "bestBird", "magpie") .unwrap() .unwrap(); @@ -503,7 +498,7 @@ fn concurrent_assignment_and_deletion_of_a_map_entry() { &doc1, map! { "bestBird" => { - set_two.translate(&doc2) => "magpie", + set_two => "magpie", } } ); @@ -514,25 +509,24 @@ fn concurrent_assignment_and_deletion_of_list_entry() { let mut doc1 = new_doc(); let mut doc2 = new_doc(); let list_id = doc1 - .set(automerge::ROOT, "birds", automerge::Value::list()) + .set(ObjId::Root, "birds", automerge::Value::list()) .unwrap() .unwrap(); - let blackbird = doc1.insert(list_id, 0, "blackbird").unwrap(); - doc1.insert(list_id, 1, "thrush").unwrap(); - let goldfinch = doc1.insert(list_id, 2, "goldfinch").unwrap(); + let blackbird = doc1.insert(list_id.clone(), 0, "blackbird").unwrap(); + doc1.insert(list_id.clone(), 1, "thrush").unwrap(); + let goldfinch = doc1.insert(list_id.clone(), 2, "goldfinch").unwrap(); doc2.merge(&mut doc1); - let starling = doc1.set(list_id, 1, "starling").unwrap().unwrap(); + let starling = doc1.set(list_id.clone(), 1, "starling").unwrap().unwrap(); - let list_id_in_doc2 = translate_obj_id(&doc1, &doc2, list_id); - doc2.del(list_id_in_doc2, 1).unwrap(); + doc2.del(list_id.clone(), 1).unwrap(); assert_doc!( &doc2, map! { - "birds" => {list_id.translate(&doc1) => list![ - { blackbird.translate(&doc1) => "blackbird"}, - { goldfinch.translate(&doc1) => "goldfinch"}, + "birds" => {list_id.clone() => list![ + { blackbird.clone() => "blackbird"}, + { goldfinch.clone() => "goldfinch"}, ]} } ); @@ -540,10 +534,10 @@ fn concurrent_assignment_and_deletion_of_list_entry() { assert_doc!( &doc1, map! { - "birds" => {list_id => list![ - { blackbird => "blackbird" }, - { starling => "starling" }, - { goldfinch => "goldfinch" }, + "birds" => {list_id.clone() => list![ + { blackbird.clone() => "blackbird" }, + { starling.clone() => "starling" }, + { goldfinch.clone() => "goldfinch" }, ]} } ); @@ -567,31 +561,30 @@ fn insertion_after_a_deleted_list_element() { let mut doc1 = new_doc(); let mut doc2 = new_doc(); let list_id = doc1 - .set(automerge::ROOT, "birds", automerge::Value::list()) + .set(ObjId::Root, "birds", automerge::Value::list()) .unwrap() .unwrap(); - let blackbird = doc1.insert(list_id, 0, "blackbird").unwrap(); - doc1.insert(list_id, 1, "thrush").unwrap(); - doc1.insert(list_id, 2, "goldfinch").unwrap(); + let blackbird = doc1.insert(list_id.clone(), 0, "blackbird").unwrap(); + doc1.insert(list_id.clone(), 1, "thrush").unwrap(); + doc1.insert(list_id.clone(), 2, "goldfinch").unwrap(); doc2.merge(&mut doc1); - doc1.splice(list_id, 1, 2, Vec::new()).unwrap(); + doc1.splice(list_id.clone(), 1, 2, Vec::new()).unwrap(); - let list_id_in_doc2 = translate_obj_id(&doc1, &doc2, list_id); let starling = doc2 - .splice(list_id_in_doc2, 2, 0, vec!["starling".into()]) - .unwrap()[0]; + .splice(list_id.clone(), 2, 0, vec!["starling".into()]) + .unwrap()[0].clone(); doc1.merge(&mut doc2); assert_doc!( &doc1, map! { - "birds" => {list_id => list![ - { blackbird.native() => "blackbird" }, - { starling.translate(&doc2) => "starling" } + "birds" => {list_id.clone() => list![ + { blackbird.clone() => "blackbird" }, + { starling.clone() => "starling" } ]} } ); @@ -600,9 +593,9 @@ fn insertion_after_a_deleted_list_element() { assert_doc!( &doc2, map! { - "birds" => {list_id.translate(&doc1) => list![ - { blackbird.translate(&doc1) => "blackbird" }, - { starling.native() => "starling" } + "birds" => {list_id => list![ + { blackbird => "blackbird" }, + { starling => "starling" } ]} } ); @@ -613,29 +606,28 @@ fn concurrent_deletion_of_same_list_element() { let mut doc1 = new_doc(); let mut doc2 = new_doc(); let list_id = doc1 - .set(automerge::ROOT, "birds", automerge::Value::list()) + .set(ObjId::Root, "birds", automerge::Value::list()) .unwrap() .unwrap(); - let albatross = doc1.insert(list_id, 0, "albatross").unwrap(); - doc1.insert(list_id, 1, "buzzard").unwrap(); - let cormorant = doc1.insert(list_id, 2, "cormorant").unwrap(); + let albatross = doc1.insert(list_id.clone(), 0, "albatross").unwrap(); + doc1.insert(list_id.clone(), 1, "buzzard").unwrap(); + let cormorant = doc1.insert(list_id.clone(), 2, "cormorant").unwrap(); doc2.merge(&mut doc1); - doc1.del(list_id, 1).unwrap(); + doc1.del(list_id.clone(), 1).unwrap(); - let list_id_in_doc2 = translate_obj_id(&doc1, &doc2, list_id); - doc2.del(list_id_in_doc2, 1).unwrap(); + doc2.del(list_id.clone(), 1).unwrap(); doc1.merge(&mut doc2); assert_doc!( &doc1, map! { - "birds" => {list_id => list![ - { albatross => "albatross" }, - { cormorant => "cormorant" } + "birds" => {list_id.clone() => list![ + { albatross.clone() => "albatross" }, + { cormorant.clone() => "cormorant" } ]} } ); @@ -644,9 +636,9 @@ fn concurrent_deletion_of_same_list_element() { assert_doc!( &doc2, map! { - "birds" => {list_id.translate(&doc1) => list![ - { albatross.translate(&doc1) => "albatross" }, - { cormorant.translate(&doc1) => "cormorant" } + "birds" => {list_id => list![ + { albatross => "albatross" }, + { cormorant => "cormorant" } ]} } ); @@ -658,48 +650,47 @@ fn concurrent_updates_at_different_levels() { let mut doc2 = new_doc(); let animals = doc1 - .set(automerge::ROOT, "animals", automerge::Value::map()) + .set(ObjId::Root, "animals", automerge::Value::map()) .unwrap() .unwrap(); let birds = doc1 - .set(animals, "birds", automerge::Value::map()) + .set(animals.clone(), "birds", automerge::Value::map()) .unwrap() .unwrap(); - doc1.set(birds, "pink", "flamingo").unwrap().unwrap(); - doc1.set(birds, "black", "starling").unwrap().unwrap(); + doc1.set(birds.clone(), "pink", "flamingo").unwrap().unwrap(); + doc1.set(birds.clone(), "black", "starling").unwrap().unwrap(); let mammals = doc1 - .set(animals, "mammals", automerge::Value::list()) + .set(animals.clone(), "mammals", automerge::Value::list()) .unwrap() .unwrap(); - let badger = doc1.insert(mammals, 0, "badger").unwrap(); + let badger = doc1.insert(mammals.clone(), 0, "badger").unwrap(); doc2.merge(&mut doc1); doc1.set(birds, "brown", "sparrow").unwrap().unwrap(); - let animals_in_doc2 = translate_obj_id(&doc1, &doc2, animals); - doc2.del(animals_in_doc2, "birds").unwrap(); + doc2.del(animals, "birds").unwrap(); doc1.merge(&mut doc2); assert_obj!( &doc1, - automerge::ROOT, + ObjId::Root, "animals", map! { "mammals" => { - mammals => list![{ badger => "badger" }], + mammals.clone() => list![{ badger.clone() => "badger" }], } } ); assert_obj!( &doc2, - automerge::ROOT, + ObjId::Root, "animals", map! { "mammals" => { - mammals.translate(&doc1) => list![{ badger.translate(&doc1) => "badger" }], + mammals => list![{ badger => "badger" }], } } ); @@ -711,21 +702,20 @@ fn concurrent_updates_of_concurrently_deleted_objects() { let mut doc2 = new_doc(); let birds = doc1 - .set(automerge::ROOT, "birds", automerge::Value::map()) + .set(ObjId::Root, "birds", automerge::Value::map()) .unwrap() .unwrap(); let blackbird = doc1 - .set(birds, "blackbird", automerge::Value::map()) + .set(birds.clone(), "blackbird", automerge::Value::map()) .unwrap() .unwrap(); - doc1.set(blackbird, "feathers", "black").unwrap().unwrap(); + doc1.set(blackbird.clone(), "feathers", "black").unwrap().unwrap(); doc2.merge(&mut doc1); - doc1.del(birds, "blackbird").unwrap(); + doc1.del(birds.clone(), "blackbird").unwrap(); - translate_obj_id(&doc1, &doc2, blackbird); - doc2.set(blackbird, "beak", "orange").unwrap(); + doc2.set(blackbird.clone(), "beak", "orange").unwrap(); doc1.merge(&mut doc2); @@ -746,14 +736,14 @@ fn does_not_interleave_sequence_insertions_at_same_position() { let mut doc2 = new_doc_with_actor(actor2); let wisdom = doc1 - .set(automerge::ROOT, "wisdom", automerge::Value::list()) + .set(ObjId::Root, "wisdom", automerge::Value::list()) .unwrap() .unwrap(); doc2.merge(&mut doc1); let doc1elems = doc1 .splice( - wisdom, + wisdom.clone(), 0, 0, vec![ @@ -766,10 +756,9 @@ fn does_not_interleave_sequence_insertions_at_same_position() { ) .unwrap(); - let wisdom_in_doc2 = translate_obj_id(&doc1, &doc2, wisdom); let doc2elems = doc2 .splice( - wisdom_in_doc2, + wisdom.clone(), 0, 0, vec![ @@ -788,16 +777,16 @@ fn does_not_interleave_sequence_insertions_at_same_position() { &doc1, map! { "wisdom" => {wisdom => list![ - {doc1elems[0].native() => "to"}, - {doc1elems[1].native() => "be"}, - {doc1elems[2].native() => "is"}, - {doc1elems[3].native() => "to"}, - {doc1elems[4].native() => "do"}, - {doc2elems[0].translate(&doc2) => "to"}, - {doc2elems[1].translate(&doc2) => "do"}, - {doc2elems[2].translate(&doc2) => "is"}, - {doc2elems[3].translate(&doc2) => "to"}, - {doc2elems[4].translate(&doc2) => "be"}, + {doc1elems[0].clone() => "to"}, + {doc1elems[1].clone() => "be"}, + {doc1elems[2].clone() => "is"}, + {doc1elems[3].clone() => "to"}, + {doc1elems[4].clone() => "do"}, + {doc2elems[0].clone() => "to"}, + {doc2elems[1].clone() => "do"}, + {doc2elems[2].clone() => "is"}, + {doc2elems[3].clone() => "to"}, + {doc2elems[4].clone() => "be"}, ]} } ); @@ -811,20 +800,19 @@ fn mutliple_insertions_at_same_list_position_with_insertion_by_greater_actor_id( let mut doc2 = new_doc_with_actor(actor2); let list = doc1 - .set(automerge::ROOT, "list", automerge::Value::list()) + .set(ObjId::Root, "list", automerge::Value::list()) .unwrap() .unwrap(); - let two = doc1.insert(list, 0, "two").unwrap(); + let two = doc1.insert(list.clone(), 0, "two").unwrap(); doc2.merge(&mut doc1); - let list_in_doc2 = translate_obj_id(&doc1, &doc2, list); - let one = doc2.insert(list_in_doc2, 0, "one").unwrap(); + let one = doc2.insert(list.clone(), 0, "one").unwrap(); assert_doc!( &doc2, map! { - "list" => { list.translate(&doc1) => list![ - { one.native() => "one" }, - { two.translate(&doc1) => "two" }, + "list" => { list => list![ + { one => "one" }, + { two => "two" }, ]} } ); @@ -838,20 +826,19 @@ fn mutliple_insertions_at_same_list_position_with_insertion_by_lesser_actor_id() let mut doc2 = new_doc_with_actor(actor2); let list = doc1 - .set(automerge::ROOT, "list", automerge::Value::list()) + .set(ObjId::Root, "list", automerge::Value::list()) .unwrap() .unwrap(); - let two = doc1.insert(list, 0, "two").unwrap(); + let two = doc1.insert(list.clone(), 0, "two").unwrap(); doc2.merge(&mut doc1); - let list_in_doc2 = translate_obj_id(&doc1, &doc2, list); - let one = doc2.insert(list_in_doc2, 0, "one").unwrap(); + let one = doc2.insert(list.clone(), 0, "one").unwrap(); assert_doc!( &doc2, map! { - "list" => { list.translate(&doc1) => list![ - { one.native() => "one" }, - { two.translate(&doc1) => "two" }, + "list" => { list => list![ + { one => "one" }, + { two => "two" }, ]} } ); @@ -863,31 +850,68 @@ fn insertion_consistent_with_causality() { let mut doc2 = new_doc(); let list = doc1 - .set(automerge::ROOT, "list", automerge::Value::list()) + .set(ObjId::Root, "list", automerge::Value::list()) .unwrap() .unwrap(); - let four = doc1.insert(list, 0, "four").unwrap(); + let four = doc1.insert(list.clone(), 0, "four").unwrap(); doc2.merge(&mut doc1); - let list_in_doc2 = translate_obj_id(&doc1, &doc2, list); - let three = doc2.insert(list_in_doc2, 0, "three").unwrap(); + let three = doc2.insert(list.clone(), 0, "three").unwrap(); doc1.merge(&mut doc2); - let two = doc1.insert(list, 0, "two").unwrap(); + let two = doc1.insert(list.clone(), 0, "two").unwrap(); doc2.merge(&mut doc1); - let one = doc2.insert(list_in_doc2, 0, "one").unwrap(); + let one = doc2.insert(list.clone(), 0, "one").unwrap(); assert_doc!( &doc2, map! { - "list" => {list.translate(&doc1) => list![ - {one.native() => "one"}, - {two.translate(&doc1) => "two"}, - {three.native() => "three" }, - {four.translate(&doc1) => "four"}, + "list" => {list => list![ + {one => "one"}, + {two => "two"}, + {three => "three" }, + {four => "four"}, ]} } ); } +#[test] +fn should_handle_arbitrary_depth_nesting() { + let mut doc1 = new_doc(); + let a = doc1.set(ObjId::Root, "a", automerge::Value::map()).unwrap().unwrap(); + let b = doc1.set(a.clone(), "b", automerge::Value::map()).unwrap().unwrap(); + let c = doc1.set(b.clone(), "c", automerge::Value::map()).unwrap().unwrap(); + let d = doc1.set(c.clone(), "d", automerge::Value::map()).unwrap().unwrap(); + let e = doc1.set(d.clone(), "e", automerge::Value::map()).unwrap().unwrap(); + let f = doc1.set(e.clone(), "f", automerge::Value::map()).unwrap().unwrap(); + let g = doc1.set(f.clone(), "g", automerge::Value::map()).unwrap().unwrap(); + let h = doc1.set(g.clone(), "h", "h").unwrap().unwrap(); + let j = doc1.set(f.clone(), "i", "j").unwrap().unwrap(); + + assert_doc!( + &doc1, + map!{ + "a" => {a => map!{ + "b" => {b => map!{ + "c" => {c => map!{ + "d" => {d => map!{ + "e" => {e => map!{ + "f" => {f => map!{ + "g" => {g => map!{ + "h" => {h => "h"} + }}, + "i" => {j => "j"}, + }} + }} + }} + }} + }} + }} + } + ); + + Automerge::load(&doc1.save().unwrap()).unwrap(); +} + #[test] fn save_and_restore_empty() { let mut doc = new_doc(); @@ -900,26 +924,25 @@ fn save_and_restore_empty() { fn save_restore_complex() { let mut doc1 = new_doc(); let todos = doc1 - .set(automerge::ROOT, "todos", automerge::Value::list()) + .set(ObjId::Root, "todos", automerge::Value::list()) .unwrap() .unwrap(); - let first_todo = doc1.insert(todos, 0, automerge::Value::map()).unwrap(); - doc1.set(first_todo, "title", "water plants") + let first_todo = doc1.insert(todos.clone(), 0, automerge::Value::map()).unwrap(); + doc1.set(first_todo.clone(), "title", "water plants") .unwrap() .unwrap(); - let first_done = doc1.set(first_todo, "done", false).unwrap().unwrap(); + let first_done = doc1.set(first_todo.clone(), "done", false).unwrap().unwrap(); let mut doc2 = new_doc(); doc2.merge(&mut doc1); - let first_todo_in_doc2 = translate_obj_id(&doc1, &doc2, first_todo); let weed_title = doc2 - .set(first_todo_in_doc2, "title", "weed plants") + .set(first_todo.clone(), "title", "weed plants") .unwrap() .unwrap(); let kill_title = doc1 - .set(first_todo, "title", "kill plants") + .set(first_todo.clone(), "title", "kill plants") .unwrap() .unwrap(); doc1.merge(&mut doc2); @@ -929,13 +952,13 @@ fn save_restore_complex() { assert_doc!( &reloaded, map! { - "todos" => {todos.translate(&doc1) => list![ - {first_todo.translate(&doc1) => map!{ + "todos" => {todos => list![ + {first_todo => map!{ "title" => { - weed_title.translate(&doc2) => "weed plants", - kill_title.translate(&doc1) => "kill plants", + weed_title => "weed plants", + kill_title => "kill plants", }, - "done" => {first_done.translate(&doc1) => false}, + "done" => {first_done => false}, }} ]} } diff --git a/edit-trace/Cargo.toml b/edit-trace/Cargo.toml index 68d47433..375c8995 100644 --- a/edit-trace/Cargo.toml +++ b/edit-trace/Cargo.toml @@ -5,6 +5,9 @@ edition = "2018" license = "MIT" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[[bin]] +name = "edit-trace" +bench = false [dependencies] automerge = { path = "../automerge" } diff --git a/edit-trace/src/main.rs b/edit-trace/src/main.rs index 94fde72c..7e2284af 100644 --- a/edit-trace/src/main.rs +++ b/edit-trace/src/main.rs @@ -1,4 +1,4 @@ -use automerge::{Automerge, AutomergeError, Value, ROOT}; +use automerge::{Automerge, AutomergeError, Value, ObjId}; use std::fs; use std::time::Instant; @@ -19,12 +19,12 @@ fn main() -> Result<(), AutomergeError> { let mut doc = Automerge::new(); let now = Instant::now(); - let text = doc.set(ROOT, "text", Value::text()).unwrap().unwrap(); + let text: ObjId = doc.set(ObjId::Root, "text", Value::text()).unwrap().unwrap().into(); for (i, (pos, del, vals)) in commands.into_iter().enumerate() { if i % 1000 == 0 { println!("Processed {} edits in {} ms", i, now.elapsed().as_millis()); } - doc.splice(text, pos, del, vals)?; + doc.splice(text.clone(), pos, del, vals)?; } let _ = doc.save(); println!("Done in {} ms", now.elapsed().as_millis()); diff --git a/todo.adoc b/todo.adoc new file mode 100644 index 00000000..8bafc3c4 --- /dev/null +++ b/todo.adoc @@ -0,0 +1,3 @@ +* Add an `ElementId::Head` +* Make all fields of Transaction private +* Remove panics from From implementations in value.rs From 65751aeb45b86bfb4bec464a05acda4a8082c740 Mon Sep 17 00:00:00 2001 From: Alex Good Date: Mon, 27 Dec 2021 15:01:01 +0000 Subject: [PATCH 3/6] add external opid --- automerge/src/external_types.rs | 63 ++++++---- automerge/src/lib.rs | 134 ++++++++++---------- automerge/tests/helpers/mod.rs | 57 ++++----- automerge/tests/test.rs | 212 ++++++++++++++++---------------- 4 files changed, 236 insertions(+), 230 deletions(-) diff --git a/automerge/src/external_types.rs b/automerge/src/external_types.rs index 4411f3a9..197a3011 100644 --- a/automerge/src/external_types.rs +++ b/automerge/src/external_types.rs @@ -1,27 +1,28 @@ -use std::{str::FromStr, borrow::Cow, fmt::Display}; +use std::{borrow::Cow, fmt::Display, str::FromStr}; -use crate::{ActorId, types::OpId, op_tree::OpSetMetadata}; +use crate::{op_tree::OpSetMetadata, types::OpId, ActorId}; const ROOT_STR: &str = "_root"; -#[derive(Copy, Debug, PartialEq, Clone, Hash, Eq)] -pub struct ExternalOpId<'a> { +#[derive(Debug, PartialEq, Clone, Hash, Eq)] +pub struct ExternalOpId { counter: u64, - actor: Cow<'a, ActorId>, + actor: ActorId, } -impl<'a> ExternalOpId<'a> { - pub(crate) fn from_internal(opid: OpId, metadata: &OpSetMetadata) -> Option { - metadata.actors.get_safe(opid.actor()).map(|actor| { - ExternalOpId{ +impl ExternalOpId { + pub(crate) fn from_internal(opid: &OpId, metadata: &OpSetMetadata) -> Option { + metadata + .actors + .get_safe(opid.actor()) + .map(|actor| ExternalOpId { counter: opid.counter(), - actor: actor.into(), - } - }) + actor: actor.clone(), + }) } - pub(crate) fn into_opid(self, metadata: &mut OpSetMetadata) -> OpId { - let actor = metadata.actors.cache(self.actor); + pub(crate) fn into_opid(&self, metadata: &mut OpSetMetadata) -> OpId { + let actor = metadata.actors.cache(self.actor.clone()); OpId::new(self.counter, actor) } } @@ -29,12 +30,27 @@ impl<'a> ExternalOpId<'a> { #[derive(Debug, PartialEq, Clone, Hash, Eq)] pub enum ExternalObjId<'a> { Root, - Op(ExternalOpId<'a>), + Op(Cow<'a, ExternalOpId>), } -impl<'a> From> for ExternalObjId<'a> { +impl<'a> ExternalObjId<'a> { + pub fn into_owned(self) -> ExternalObjId<'static> { + match self { + Self::Root => ExternalObjId::Root, + Self::Op(cow) => ExternalObjId::Op(Cow::<'static, _>::Owned(cow.into_owned().into())), + } + } +} + +impl<'a> From<&'a ExternalOpId> for ExternalObjId<'a> { + fn from(op: &'a ExternalOpId) -> Self { + ExternalObjId::Op(Cow::Borrowed(op)) + } +} + +impl From for ExternalObjId<'static> { fn from(op: ExternalOpId) -> Self { - ExternalObjId::Op(op) + ExternalObjId::Op(Cow::Owned(op)) } } @@ -48,7 +64,7 @@ pub enum ParseError { InvalidActor, } -impl FromStr for ExternalOpId<'static> { +impl FromStr for ExternalOpId { type Err = ParseError; fn from_str(s: &str) -> Result { @@ -56,19 +72,20 @@ impl FromStr for ExternalOpId<'static> { let first_part = parts.next().ok_or(ParseError::BadFormat)?; let second_part = parts.next().ok_or(ParseError::BadFormat)?; let counter: u64 = first_part.parse().map_err(|_| ParseError::InvalidCounter)?; - let actor: ActorId = second_part.parse().map_err(|_| ParseError::InvalidActor)?; - Ok(ExternalOpId{counter, actor}) + let actor: ActorId = second_part.parse().map_err(|_| ParseError::InvalidActor)?; + Ok(ExternalOpId { counter, actor }) } } -impl<'a> FromStr for ExternalObjId<'a> { +impl FromStr for ExternalObjId<'static> { type Err = ParseError; fn from_str(s: &str) -> Result { if s == ROOT_STR { Ok(ExternalObjId::Root) } else { - Ok(s.parse::()?.into()) + let op = s.parse::()?.into(); + Ok(ExternalObjId::Op(Cow::Owned(op))) } } } @@ -79,7 +96,7 @@ impl Display for ExternalOpId { } } -impl Display for ExternalObjId { +impl<'a> Display for ExternalObjId<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Root => write!(f, "{}", ROOT_STR), diff --git a/automerge/src/lib.rs b/automerge/src/lib.rs index 92340fd5..b7a3714f 100644 --- a/automerge/src/lib.rs +++ b/automerge/src/lib.rs @@ -201,7 +201,7 @@ impl Automerge { pub fn ensure_transaction_closed(&mut self) { if let Some(tx) = self.transaction.take() { - let change = export_change(&tx, &self.ops.m.borrow_mut().actors, &self.ops.m.borrow().props); + let change = export_change(&tx, &self.ops.m.borrow().actors, &self.ops.m.borrow().props); self.update_history(change); } } @@ -265,25 +265,25 @@ impl Automerge { // PropAt::() // NthAt::() - pub fn keys>(&self, obj: O) -> Vec { - let obj = self.import_objid(obj.into()); + pub fn keys<'a, O: Into>>(&self, obj: O) -> Vec { + let obj = self.import_objid(obj); let q = self.ops.search(obj.into(), query::Keys::new()); q.keys.iter().map(|k| self.export(*k)).collect() } - pub fn keys_at>(&mut self, obj: O, heads: &[ChangeHash]) -> Vec { + pub fn keys_at<'a, O: Into>>(&mut self, obj: O, heads: &[ChangeHash]) -> Vec { let obj = self.import_objid(obj.into()); let clock = self.clock_at(heads); let q = self.ops.search(obj.into(), query::KeysAt::new(clock)); q.keys.iter().map(|k| self.export(*k)).collect() } - pub fn length>(&self, obj: O) -> usize { + pub fn length<'a, O: Into>>(&self, obj: O) -> usize { let obj = self.import_objid(obj.into()); self.ops.search(obj.into(), query::Len::new(obj.into())).len } - pub fn length_at>(&self, obj: O, heads: &[ChangeHash]) -> usize { + pub fn length_at<'a, O: Into>>(&self, obj: O, heads: &[ChangeHash]) -> usize { let obj = self.import_objid(obj.into()); let clock = self.clock_at(heads); self.ops.search(obj.into(), query::LenAt::new(clock)).len @@ -307,7 +307,7 @@ impl Automerge { /// - The object does not exist /// - The key is the wrong type for the object /// - The key does not exist in the object - pub fn set, P: Into, V: Into>( + pub fn set<'a, O: Into>, P: Into, V: Into>( &mut self, obj: O, prop: P, @@ -318,7 +318,7 @@ impl Automerge { self.local_op(obj.into(), prop.into(), value.into()) } - pub fn insert, V: Into>( + pub fn insert<'a, O: Into>, V: Into>( &mut self, obj: O, index: usize, @@ -346,10 +346,10 @@ impl Automerge { self.ops.insert(query.pos, op.clone()); self.tx().operations.push(op); - Ok(self.export_opid(id).unwrap()) + Ok(self.export_opid(&id).unwrap()) } - pub fn inc, P: Into>( + pub fn inc<'a, O: Into>, P: Into>( &mut self, obj: O, prop: P, @@ -363,7 +363,7 @@ impl Automerge { } } - pub fn del, P: Into>(&mut self, obj: O, prop: P) -> Result { + pub fn del<'a, O: Into>, P: Into>(&mut self, obj: O, prop: P) -> Result { // TODO: Should we also no-op multiple delete operations? match self.local_op(self.import_objid(obj.into()).into(), prop.into(), OpType::Del)? { Some(opid) => Ok(opid), @@ -375,7 +375,7 @@ impl Automerge { /// Splice new elements into the given sequence. Returns a vector of the OpIds used to insert /// the new elements - pub fn splice>( + pub fn splice<'a, O: Into>>( &mut self, obj: O, mut pos: usize, @@ -394,7 +394,7 @@ impl Automerge { Ok(result) } - pub fn splice_text>( + pub fn splice_text<'a, O: Into>>( &mut self, obj: O, pos: usize, @@ -408,7 +408,7 @@ impl Automerge { self.splice(obj, pos, del, vals) } - pub fn text>(&self, obj: O) -> Result { + pub fn text<'a, O: Into>>(&self, obj: O) -> Result { let obj = self.import_objid(obj.into()).into(); let query = self.ops.search(obj, query::ListVals::new(obj)); let mut buffer = String::new(); @@ -420,7 +420,7 @@ impl Automerge { Ok(buffer) } - pub fn text_at>(&self, obj: O, heads: &[ChangeHash]) -> Result { + pub fn text_at<'a, O: Into>>(&self, obj: O, heads: &[ChangeHash]) -> Result { let clock = self.clock_at(heads); let obj = self.import_objid(obj.into()).into(); let query = self.ops.search(obj, query::ListValsAt::new(clock)); @@ -436,7 +436,7 @@ impl Automerge { // TODO - I need to return these OpId's here **only** to get // the legacy conflicts format of { [opid]: value } // Something better? - pub fn value, P: Into>( + pub fn value<'a, O: Into>, P: Into>( &self, obj: O, prop: P, @@ -444,7 +444,7 @@ impl Automerge { Ok(self.values(obj, prop.into())?.first().cloned()) } - pub fn value_at, P: Into>( + pub fn value_at<'a, O: Into>, P: Into>( &self, obj: O, prop: P, @@ -453,7 +453,7 @@ impl Automerge { Ok(self.values_at(obj, prop, heads)?.first().cloned()) } - pub fn values, P: Into>( + pub fn values<'a, O: Into>, P: Into>( &self, obj: O, prop: P, @@ -484,14 +484,14 @@ impl Automerge { Ok(result) } - pub fn values_at, P: Into>( + pub fn values_at<'a, O: Into>, P: Into>( &self, obj: O, prop: P, heads: &[ChangeHash], ) -> Result, AutomergeError> { let prop = prop.into(); - let obj = self.import_objid(obj.into()).into(); + let obj = self.import_objid(obj).into(); let clock = self.clock_at(heads); let result = match prop { Prop::Map(p) => { @@ -615,7 +615,7 @@ impl Automerge { self.insert_local_op(op, query.pos, &query.ops_pos); - Ok(Some(self.export_opid(id).unwrap())) + Ok(Some(self.export_opid(&id).unwrap())) } fn local_list_op( @@ -658,7 +658,7 @@ impl Automerge { self.insert_local_op(op, query.pos, &query.ops_pos); - Ok(Some(self.export_opid(id).unwrap())) + Ok(Some(self.export_opid(&id).unwrap())) } fn is_causally_ready(&self, change: &Change) -> bool { @@ -1058,15 +1058,15 @@ impl Automerge { opid.into_opid(&mut *self.ops.m.borrow_mut()) } - fn export_opid(&self, opid: InternalOpId) -> Option { + fn export_opid(&self, opid: &InternalOpId) -> Option { OpId::from_internal(opid, &self.ops.m.borrow_mut()) } - fn import_objid>(&self, objid: O) -> InternalObjId { - match objid.as_ref() { + fn import_objid<'a, A: Into>>(&self, objid: A) -> InternalObjId { + match objid.into() { ObjId::Root => InternalObjId::Root, ObjId::Op(external_op) => { - let op = self.import_opid(external_op); + let op = self.import_opid(&external_op); InternalObjId::Op(op) } } @@ -1123,7 +1123,7 @@ impl Automerge { } fn labelled_value(&self, op: &Op) -> (Value, OpId) { - let id = self.export_opid(op.id).unwrap(); + let id = self.export_opid(&op.id).unwrap(); let value = match &op.action { OpType::Make(obj_type) => Value::Object(*obj_type), OpType::Set(scalar) => Value::Scalar(scalar.clone()), @@ -1174,14 +1174,14 @@ mod tests { let list_id = doc.set(ObjId::Root, "items", Value::list())?.unwrap().into(); doc.set(ObjId::Root, "zzz", "zzzval")?; assert!(doc.value(ObjId::Root, "items")?.unwrap().1 == list_id); - doc.insert(list_id, 0, "a")?; - doc.insert(list_id, 0, "b")?; - doc.insert(list_id, 2, "c")?; - doc.insert(list_id, 1, "d")?; - assert!(doc.value(list_id.clone(), 0)?.unwrap().0 == "b".into()); - assert!(doc.value(list_id.clone(), 1)?.unwrap().0 == "d".into()); - assert!(doc.value(list_id.clone(), 2)?.unwrap().0 == "a".into()); - assert!(doc.value(list_id.clone(), 3)?.unwrap().0 == "c".into()); + doc.insert(&list_id, 0, "a")?; + doc.insert(&list_id, 0, "b")?; + doc.insert(&list_id, 2, "c")?; + doc.insert(&list_id, 1, "d")?; + assert!(doc.value(&list_id, 0)?.unwrap().0 == "b".into()); + assert!(doc.value(&list_id, 1)?.unwrap().0 == "d".into()); + assert!(doc.value(&list_id, 2)?.unwrap().0 == "a".into()); + assert!(doc.value(&list_id, 3)?.unwrap().0 == "c".into()); assert!(doc.length(list_id) == 4); doc.save()?; Ok(()) @@ -1202,11 +1202,11 @@ mod tests { fn test_inc() -> Result<(), AutomergeError> { let mut doc = Automerge::new(); let id = doc.set(ObjId::Root, "counter", Value::counter(10))?.unwrap(); - assert!(doc.value(ObjId::Root, "counter")? == Some((Value::counter(10), id))); + assert!(doc.value(ObjId::Root, "counter")? == Some((Value::counter(10), id.clone()))); doc.inc(ObjId::Root, "counter", 10)?; - assert!(doc.value(ObjId::Root, "counter")? == Some((Value::counter(20), id))); + assert!(doc.value(ObjId::Root, "counter")? == Some((Value::counter(20), id.clone()))); doc.inc(ObjId::Root, "counter", -5)?; - assert!(doc.value(ObjId::Root, "counter")? == Some((Value::counter(15), id))); + assert!(doc.value(ObjId::Root, "counter")? == Some((Value::counter(15), id.clone()))); Ok(()) } @@ -1252,15 +1252,15 @@ mod tests { let mut doc = Automerge::new(); let text = doc.set(ObjId::Root, "text", Value::text())?.unwrap(); let heads1 = doc.commit(None, None); - doc.splice_text(text, 0, 0, "hello world")?; + doc.splice_text(&text, 0, 0, "hello world")?; let heads2 = doc.commit(None, None); - doc.splice_text(text, 6, 0, "big bad ")?; + doc.splice_text(&text, 6, 0, "big bad ")?; let heads3 = doc.commit(None, None); - assert!(&doc.text(text)? == "hello big bad world"); - assert!(&doc.text_at(text, &heads1)?.is_empty()); - assert!(&doc.text_at(text, &heads2)? == "hello world"); - assert!(&doc.text_at(text, &heads3)? == "hello big bad world"); + assert!(&doc.text(&text)? == "hello big bad world"); + assert!(&doc.text_at(&text, &heads1)?.is_empty()); + assert!(&doc.text_at(&text, &heads2)? == "hello world"); + assert!(&doc.text_at(&text, &heads3)? == "hello big bad world"); Ok(()) } @@ -1324,44 +1324,44 @@ mod tests { let list = doc.set(ObjId::Root, "list", Value::list())?.unwrap(); let heads1 = doc.commit(None, None); - doc.insert(list, 0, Value::int(10))?; + doc.insert(&list, 0, Value::int(10))?; let heads2 = doc.commit(None, None); - doc.set(list, 0, Value::int(20))?; - doc.insert(list, 0, Value::int(30))?; + doc.set(&list, 0, Value::int(20))?; + doc.insert(&list, 0, Value::int(30))?; let heads3 = doc.commit(None, None); - doc.set(list, 1, Value::int(40))?; - doc.insert(list, 1, Value::int(50))?; + doc.set(&list, 1, Value::int(40))?; + doc.insert(&list, 1, Value::int(50))?; let heads4 = doc.commit(None, None); - doc.del(list, 2)?; + doc.del(&list, 2)?; let heads5 = doc.commit(None, None); - doc.del(list, 0)?; + doc.del(&list, 0)?; let heads6 = doc.commit(None, None); - assert!(doc.length_at(list, &heads1) == 0); - assert!(doc.value_at(list, 0, &heads1)?.is_none()); + assert!(doc.length_at(&list, &heads1) == 0); + assert!(doc.value_at(&list, 0, &heads1)?.is_none()); - assert!(doc.length_at(list, &heads2) == 1); - assert!(doc.value_at(list, 0, &heads2)?.unwrap().0 == Value::int(10)); + assert!(doc.length_at(&list, &heads2) == 1); + assert!(doc.value_at(&list, 0, &heads2)?.unwrap().0 == Value::int(10)); - assert!(doc.length_at(list, &heads3) == 2); - assert!(doc.value_at(list, 0, &heads3)?.unwrap().0 == Value::int(30)); - assert!(doc.value_at(list, 1, &heads3)?.unwrap().0 == Value::int(20)); + assert!(doc.length_at(&list, &heads3) == 2); + assert!(doc.value_at(&list, 0, &heads3)?.unwrap().0 == Value::int(30)); + assert!(doc.value_at(&list, 1, &heads3)?.unwrap().0 == Value::int(20)); - assert!(doc.length_at(list, &heads4) == 3); - assert!(doc.value_at(list, 0, &heads4)?.unwrap().0 == Value::int(30)); - assert!(doc.value_at(list, 1, &heads4)?.unwrap().0 == Value::int(50)); - assert!(doc.value_at(list, 2, &heads4)?.unwrap().0 == Value::int(40)); + assert!(doc.length_at(&list, &heads4) == 3); + assert!(doc.value_at(&list, 0, &heads4)?.unwrap().0 == Value::int(30)); + assert!(doc.value_at(&list, 1, &heads4)?.unwrap().0 == Value::int(50)); + assert!(doc.value_at(&list, 2, &heads4)?.unwrap().0 == Value::int(40)); - assert!(doc.length_at(list, &heads5) == 2); - assert!(doc.value_at(list, 0, &heads5)?.unwrap().0 == Value::int(30)); - assert!(doc.value_at(list, 1, &heads5)?.unwrap().0 == Value::int(50)); + assert!(doc.length_at(&list, &heads5) == 2); + assert!(doc.value_at(&list, 0, &heads5)?.unwrap().0 == Value::int(30)); + assert!(doc.value_at(&list, 1, &heads5)?.unwrap().0 == Value::int(50)); - assert!(doc.length_at(list, &heads6) == 1); - assert!(doc.value_at(list, 0, &heads6)?.unwrap().0 == Value::int(50)); + assert!(doc.length_at(&list, &heads6) == 1); + assert!(doc.value_at(&list, 0, &heads6)?.unwrap().0 == Value::int(50)); Ok(()) } diff --git a/automerge/tests/helpers/mod.rs b/automerge/tests/helpers/mod.rs index 40ee7faf..bbcad074 100644 --- a/automerge/tests/helpers/mod.rs +++ b/automerge/tests/helpers/mod.rs @@ -25,7 +25,7 @@ pub fn sorted_actors() -> (automerge::ActorId, automerge::ActorId) { /// This macro makes it easy to make assertions about a document. It is called with two arguments, /// the first is a reference to an `automerge::Automerge`, the second is an instance of -/// `RealizedObject`. +/// `RealizedObject`. /// /// What - I hear you ask - is a `RealizedObject`? It's a fully hydrated version of the contents of /// an automerge document. You don't need to think about this too much though because you can @@ -67,22 +67,11 @@ pub fn sorted_actors() -> (automerge::ActorId, automerge::ActorId) { /// map!{ /// "field" => { /// op1 => "one", -/// op2.translate(&doc2) => "two" +/// op2 => "two" /// } /// } /// ); /// ``` -/// -/// ## Translating OpIds -/// -/// One thing you may have noticed in the example above is the `op2.translate(&doc2)` call. What is -/// that doing there? Well, the problem is that automerge OpIDs (in the current API) are specific -/// to a document. Using an opid from one document in a different document will not work. Therefore -/// this module defines an `OpIdExt` trait with a `translate` method on it. This method takes a -/// document and converts the opid into something which knows how to be compared with opids from -/// another document by using the document you pass to `translate`. Again, all you really need to -/// know is that when constructing a document for comparison you should call `translate(fromdoc)` -/// on opids which come from a document other than the one you pass to `assert_doc`. #[macro_export] macro_rules! assert_doc { ($doc: expr, $expected: expr) => {{ @@ -147,7 +136,7 @@ macro_rules! map { use std::collections::HashMap; let mut inner: HashMap = HashMap::new(); $( - let _ = inner.insert($opid.into(), $value.into()); + let _ = inner.insert(ObjId::from((&$opid)).into_owned(), $value.into()); )* inner } @@ -195,7 +184,7 @@ macro_rules! list { use std::collections::HashMap; let mut inner: HashMap = HashMap::new(); $( - let _ = inner.insert($opid.into(), $value.into()); + let _ = inner.insert(ObjId::from(&$opid).into_owned(), $value.into()); )* inner } @@ -231,13 +220,13 @@ impl std::fmt::Display for ExportedOpId { /// A `RealizedObject` is a representation of all the current values in a document - including /// conflicts. #[derive(PartialEq, Debug)] -pub enum RealizedObject { - Map(HashMap>), - Sequence(Vec>), +pub enum RealizedObject<'a> { + Map(HashMap, RealizedObject<'a>>>), + Sequence(Vec, RealizedObject<'a>>>), Value(automerge::ScalarValue), } -impl serde::Serialize for RealizedObject { +impl serde::Serialize for RealizedObject<'static> { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, @@ -270,7 +259,7 @@ impl serde::Serialize for RealizedObject { } } -pub fn realize(doc: &automerge::Automerge) -> RealizedObject { +pub fn realize<'a>(doc: &automerge::Automerge) -> RealizedObject<'a> { realize_obj(doc, ObjId::Root, automerge::ObjType::Map) } @@ -278,7 +267,7 @@ pub fn realize_prop>( doc: &automerge::Automerge, obj_id: automerge::ObjId, prop: P, -) -> RealizedObject { +) -> RealizedObject<'static> { let (val, obj_id) = doc.value(obj_id, prop).unwrap().unwrap(); match val { automerge::Value::Object(obj_type) => realize_obj(doc, obj_id.into(), obj_type), @@ -290,7 +279,7 @@ pub fn realize_obj( doc: &automerge::Automerge, obj_id: automerge::ObjId, objtype: automerge::ObjType, -) -> RealizedObject { +) -> RealizedObject<'static> { match objtype { automerge::ObjType::Map | automerge::ObjType::Table => { let mut result = HashMap::new(); @@ -314,7 +303,7 @@ fn realize_values>( doc: &automerge::Automerge, obj_id: automerge::ObjId, key: K, -) -> HashMap { +) -> HashMap, RealizedObject<'static>> { let mut values_by_objid: HashMap = HashMap::new(); for (value, opid) in doc.values(obj_id, key).unwrap() { let realized = match value { @@ -327,10 +316,10 @@ fn realize_values>( } -impl> - From>> for RealizedObject +impl<'a, I: Into>> + From, I>>> for RealizedObject<'a> { - fn from(values: HashMap<&str, HashMap>) -> Self { + fn from(values: HashMap<&str, HashMap, I>>) -> Self { let intoed = values .into_iter() .map(|(k, v)| { @@ -344,39 +333,39 @@ impl> } } -impl> - From>> for RealizedObject +impl<'a, I: Into>> + From, I>>> for RealizedObject<'a> { - fn from(values: Vec>) -> Self { + fn from(values: Vec, I>>) -> Self { RealizedObject::Sequence( values .into_iter() - .map(|v| v.into_iter().map(|(k, v)| (k.into(), v.into())).collect()) + .map(|v| v.into_iter().map(|(k, v)| (k, v.into())).collect()) .collect(), ) } } -impl From for RealizedObject { +impl From for RealizedObject<'static> { fn from(b: bool) -> Self { RealizedObject::Value(b.into()) } } -impl From for RealizedObject { +impl From for RealizedObject<'static> { fn from(u: usize) -> Self { let v = u.try_into().unwrap(); RealizedObject::Value(automerge::ScalarValue::Int(v)) } } -impl From for RealizedObject { +impl From for RealizedObject<'static> { fn from(s: automerge::ScalarValue) -> Self { RealizedObject::Value(s) } } -impl From<&str> for RealizedObject { +impl From<&str> for RealizedObject<'static> { fn from(s: &str) -> Self { RealizedObject::Value(automerge::ScalarValue::Str(s.into())) } diff --git a/automerge/tests/test.rs b/automerge/tests/test.rs index 9bf8a0ea..4be44b97 100644 --- a/automerge/tests/test.rs +++ b/automerge/tests/test.rs @@ -29,24 +29,24 @@ fn no_change_on_repeated_map_set() { #[test] fn no_change_on_repeated_list_set() { let mut doc = new_doc(); - let list_id: ObjId = doc + let list_id = doc .set(ObjId::Root, "list", automerge::Value::list()) .unwrap() .unwrap().into(); - doc.insert(list_id.clone(), 0, 1).unwrap(); - doc.set(list_id.clone(), 0, 1).unwrap(); + doc.insert(&list_id, 0, 1).unwrap(); + doc.set(&list_id, 0, 1).unwrap(); assert!(doc.set(list_id, 0, 1).unwrap().is_none()); } #[test] fn no_change_on_list_insert_followed_by_set_of_same_value() { let mut doc = new_doc(); - let list_id: ObjId = doc + let list_id = doc .set(ObjId::Root, "list", automerge::Value::list()) .unwrap() - .unwrap().into(); - doc.insert(list_id.clone(), 0, 1).unwrap(); - assert!(doc.set(list_id.clone(), 0, 1).unwrap().is_none()); + .unwrap(); + doc.insert(&list_id, 0, 1).unwrap(); + assert!(doc.set(&list_id, 0, 1).unwrap().is_none()); } #[test] @@ -75,15 +75,15 @@ fn repeated_map_assignment_which_resolves_conflict_not_ignored() { fn repeated_list_assignment_which_resolves_conflict_not_ignored() { let mut doc1 = new_doc(); let mut doc2 = new_doc(); - let list_id: ObjId = doc1 + let list_id = doc1 .set(ObjId::Root, "list", automerge::Value::list()) .unwrap() - .unwrap().into(); - doc1.insert(list_id.clone(), 0, 123).unwrap(); + .unwrap(); + doc1.insert(&list_id, 0, 123).unwrap(); doc2.merge(&mut doc1); - doc2.set(list_id.clone(), 0, 456).unwrap().unwrap(); + doc2.set(&list_id, 0, 456).unwrap().unwrap(); doc1.merge(&mut doc2); - let doc1_op = doc1.set(list_id.clone(), 0, 789).unwrap().unwrap(); + let doc1_op = doc1.set(&list_id, 0, 789).unwrap().unwrap(); assert_doc!( &doc1, @@ -100,14 +100,14 @@ fn repeated_list_assignment_which_resolves_conflict_not_ignored() { #[test] fn list_deletion() { let mut doc = new_doc(); - let list_id: ObjId = doc + let list_id = doc .set(ObjId::Root, "list", automerge::Value::list()) .unwrap() - .unwrap().into(); - let op1 = doc.insert(list_id.clone(), 0, 123).unwrap(); - doc.insert(list_id.clone(), 1, 456).unwrap(); - let op3 = doc.insert(list_id.clone(), 2, 789).unwrap(); - doc.del(list_id.clone(), 1).unwrap(); + .unwrap(); + let op1 = doc.insert(&list_id, 0, 123).unwrap(); + doc.insert(&list_id, 1, 456).unwrap(); + let op3 = doc.insert(&list_id.clone(), 2, 789).unwrap(); + doc.del(&list_id, 1).unwrap(); assert_doc!( &doc, map! { @@ -124,10 +124,10 @@ fn merge_concurrent_map_prop_updates() { let mut doc1 = new_doc(); let mut doc2 = new_doc(); let op1 = doc1.set(ObjId::Root, "foo", "bar").unwrap().unwrap(); - let hello: ObjId = doc2 + let hello = doc2 .set(ObjId::Root, "hello", "world") .unwrap() - .unwrap().into(); + .unwrap(); doc1.merge(&mut doc2); assert_eq!( doc1.value(ObjId::Root, "foo").unwrap().unwrap().0, @@ -136,8 +136,8 @@ fn merge_concurrent_map_prop_updates() { assert_doc!( &doc1, map! { - "foo" => { op1.clone() => "bar" }, - "hello" => { hello.clone() => "world" }, + "foo" => { op1 => "bar" }, + "hello" => { hello => "world" }, } ); doc2.merge(&mut doc1); @@ -230,14 +230,14 @@ fn concurrent_updates_of_same_field() { fn concurrent_updates_of_same_list_element() { let mut doc1 = new_doc(); let mut doc2 = new_doc(); - let list_id: ObjId = doc1 + let list_id = doc1 .set(ObjId::Root, "birds", automerge::Value::list()) .unwrap() - .unwrap().into(); + .unwrap(); doc1.insert(list_id.clone(), 0, "finch").unwrap(); doc2.merge(&mut doc1); - let set_one_op = doc1.set(list_id.clone(), 0, "greenfinch").unwrap().unwrap(); - let set_op_two = doc2.set(list_id.clone(), 0, "goldfinch").unwrap().unwrap(); + let set_one_op = doc1.set(&list_id, 0, "greenfinch").unwrap().unwrap(); + let set_op_two = doc2.set(&list_id, 0, "goldfinch").unwrap().unwrap(); doc1.merge(&mut doc2); @@ -295,11 +295,11 @@ fn changes_within_conflicting_map_field() { .set(ObjId::Root, "field", "string") .unwrap() .unwrap(); - let map_id: ObjId = doc2 + let map_id = doc2 .set(ObjId::Root, "field", automerge::Value::map()) .unwrap() - .unwrap().into(); - let set_in_doc2 = doc2.set(map_id.clone(), "innerKey", 42).unwrap().unwrap(); + .unwrap(); + let set_in_doc2 = doc2.set(&map_id, "innerKey", 42).unwrap().unwrap(); doc1.merge(&mut doc2); assert_doc!( @@ -326,23 +326,23 @@ fn changes_within_conflicting_list_element() { .set(ObjId::Root, "list", automerge::Value::list()) .unwrap() .unwrap(); - doc1.insert(list_id.clone(), 0, "hello").unwrap(); + doc1.insert(&list_id, 0, "hello").unwrap(); doc2.merge(&mut doc1); let map_in_doc1 = doc1 - .set(list_id.clone(), 0, automerge::Value::map()) + .set(&list_id, 0, automerge::Value::map()) .unwrap() .unwrap(); - let set_map1 = doc1.set(map_in_doc1.clone(), "map1", true).unwrap().unwrap(); - let set_key1 = doc1.set(map_in_doc1.clone(), "key", 1).unwrap().unwrap(); + let set_map1 = doc1.set(&map_in_doc1, "map1", true).unwrap().unwrap(); + let set_key1 = doc1.set(&map_in_doc1, "key", 1).unwrap().unwrap(); let map_in_doc2 = doc2 - .set(list_id.clone(), 0, automerge::Value::map()) + .set(&list_id, 0, automerge::Value::map()) .unwrap() .unwrap(); doc1.merge(&mut doc2); - let set_map2 = doc2.set(map_in_doc2.clone(), "map2", true).unwrap().unwrap(); - let set_key2 = doc2.set(map_in_doc2.clone(), "key", 2).unwrap().unwrap(); + let set_map2 = doc2.set(&map_in_doc2, "map2", true).unwrap().unwrap(); + let set_key2 = doc2.set(&map_in_doc2, "key", 2).unwrap().unwrap(); doc1.merge(&mut doc2); @@ -419,11 +419,11 @@ fn concurrent_insertions_at_different_list_positions() { .unwrap() .unwrap(); - let one = doc1.insert(list_id.clone(), 0, "one").unwrap(); - let three = doc1.insert(list_id.clone(), 1, "three").unwrap(); + let one = doc1.insert(&list_id, 0, "one").unwrap(); + let three = doc1.insert(&list_id, 1, "three").unwrap(); doc2.merge(&mut doc1); - let two = doc1.splice(list_id.clone(), 1, 0, vec!["two".into()]).unwrap()[0].clone(); - let four = doc2.insert(list_id.clone(), 2, "four").unwrap(); + let two = doc1.splice(&list_id, 1, 0, vec!["two".into()]).unwrap()[0].clone(); + let four = doc2.insert(&list_id, 2, "four").unwrap(); doc1.merge(&mut doc2); @@ -453,11 +453,11 @@ fn concurrent_insertions_at_same_list_position() { .set(ObjId::Root, "birds", automerge::Value::list()) .unwrap() .unwrap(); - let parakeet = doc1.insert(list_id.clone(), 0, "parakeet").unwrap(); + let parakeet = doc1.insert(&list_id, 0, "parakeet").unwrap(); doc2.merge(&mut doc1); - let starling = doc1.insert(list_id.clone(), 1, "starling").unwrap(); - let chaffinch = doc2.insert(list_id.clone(), 1, "chaffinch").unwrap(); + let starling = doc1.insert(&list_id, 1, "starling").unwrap(); + let chaffinch = doc2.insert(&list_id, 1, "chaffinch").unwrap(); doc1.merge(&mut doc2); assert_doc!( @@ -512,21 +512,21 @@ fn concurrent_assignment_and_deletion_of_list_entry() { .set(ObjId::Root, "birds", automerge::Value::list()) .unwrap() .unwrap(); - let blackbird = doc1.insert(list_id.clone(), 0, "blackbird").unwrap(); - doc1.insert(list_id.clone(), 1, "thrush").unwrap(); - let goldfinch = doc1.insert(list_id.clone(), 2, "goldfinch").unwrap(); + let blackbird = doc1.insert(&list_id, 0, "blackbird").unwrap(); + doc1.insert(&list_id, 1, "thrush").unwrap(); + let goldfinch = doc1.insert(&list_id, 2, "goldfinch").unwrap(); doc2.merge(&mut doc1); - let starling = doc1.set(list_id.clone(), 1, "starling").unwrap().unwrap(); + let starling = doc1.set(&list_id, 1, "starling").unwrap().unwrap(); - doc2.del(list_id.clone(), 1).unwrap(); + doc2.del(&list_id, 1).unwrap(); assert_doc!( &doc2, map! { - "birds" => {list_id.clone() => list![ - { blackbird.clone() => "blackbird"}, - { goldfinch.clone() => "goldfinch"}, + "birds" => {list_id => list![ + { blackbird => "blackbird"}, + { goldfinch => "goldfinch"}, ]} } ); @@ -535,9 +535,9 @@ fn concurrent_assignment_and_deletion_of_list_entry() { &doc1, map! { "birds" => {list_id.clone() => list![ - { blackbird.clone() => "blackbird" }, + { blackbird => "blackbird" }, { starling.clone() => "starling" }, - { goldfinch.clone() => "goldfinch" }, + { goldfinch => "goldfinch" }, ]} } ); @@ -566,15 +566,15 @@ fn insertion_after_a_deleted_list_element() { .unwrap(); let blackbird = doc1.insert(list_id.clone(), 0, "blackbird").unwrap(); - doc1.insert(list_id.clone(), 1, "thrush").unwrap(); - doc1.insert(list_id.clone(), 2, "goldfinch").unwrap(); + doc1.insert(&list_id, 1, "thrush").unwrap(); + doc1.insert(&list_id, 2, "goldfinch").unwrap(); doc2.merge(&mut doc1); - doc1.splice(list_id.clone(), 1, 2, Vec::new()).unwrap(); + doc1.splice(&list_id, 1, 2, Vec::new()).unwrap(); let starling = doc2 - .splice(list_id.clone(), 2, 0, vec!["starling".into()]) + .splice(&list_id, 2, 0, vec!["starling".into()]) .unwrap()[0].clone(); doc1.merge(&mut doc2); @@ -582,9 +582,9 @@ fn insertion_after_a_deleted_list_element() { assert_doc!( &doc1, map! { - "birds" => {list_id.clone() => list![ - { blackbird.clone() => "blackbird" }, - { starling.clone() => "starling" } + "birds" => {list_id => list![ + { blackbird => "blackbird" }, + { starling => "starling" } ]} } ); @@ -611,14 +611,14 @@ fn concurrent_deletion_of_same_list_element() { .unwrap(); let albatross = doc1.insert(list_id.clone(), 0, "albatross").unwrap(); - doc1.insert(list_id.clone(), 1, "buzzard").unwrap(); - let cormorant = doc1.insert(list_id.clone(), 2, "cormorant").unwrap(); + doc1.insert(&list_id, 1, "buzzard").unwrap(); + let cormorant = doc1.insert(&list_id, 2, "cormorant").unwrap(); doc2.merge(&mut doc1); - doc1.del(list_id.clone(), 1).unwrap(); + doc1.del(&list_id, 1).unwrap(); - doc2.del(list_id.clone(), 1).unwrap(); + doc2.del(&list_id, 1).unwrap(); doc1.merge(&mut doc2); @@ -654,23 +654,23 @@ fn concurrent_updates_at_different_levels() { .unwrap() .unwrap(); let birds = doc1 - .set(animals.clone(), "birds", automerge::Value::map()) + .set(&animals, "birds", automerge::Value::map()) .unwrap() .unwrap(); - doc1.set(birds.clone(), "pink", "flamingo").unwrap().unwrap(); - doc1.set(birds.clone(), "black", "starling").unwrap().unwrap(); + doc1.set(&birds, "pink", "flamingo").unwrap().unwrap(); + doc1.set(&birds, "black", "starling").unwrap().unwrap(); let mammals = doc1 - .set(animals.clone(), "mammals", automerge::Value::list()) + .set(&animals, "mammals", automerge::Value::list()) .unwrap() .unwrap(); - let badger = doc1.insert(mammals.clone(), 0, "badger").unwrap(); + let badger = doc1.insert(&mammals, 0, "badger").unwrap(); doc2.merge(&mut doc1); - doc1.set(birds, "brown", "sparrow").unwrap().unwrap(); + doc1.set(&birds, "brown", "sparrow").unwrap().unwrap(); - doc2.del(animals, "birds").unwrap(); + doc2.del(&animals, "birds").unwrap(); doc1.merge(&mut doc2); assert_obj!( @@ -679,7 +679,7 @@ fn concurrent_updates_at_different_levels() { "animals", map! { "mammals" => { - mammals.clone() => list![{ badger.clone() => "badger" }], + mammals => list![{ badger => "badger" }], } } ); @@ -706,16 +706,16 @@ fn concurrent_updates_of_concurrently_deleted_objects() { .unwrap() .unwrap(); let blackbird = doc1 - .set(birds.clone(), "blackbird", automerge::Value::map()) + .set(&birds, "blackbird", automerge::Value::map()) .unwrap() .unwrap(); - doc1.set(blackbird.clone(), "feathers", "black").unwrap().unwrap(); + doc1.set(&blackbird, "feathers", "black").unwrap().unwrap(); doc2.merge(&mut doc1); - doc1.del(birds.clone(), "blackbird").unwrap(); + doc1.del(&birds, "blackbird").unwrap(); - doc2.set(blackbird.clone(), "beak", "orange").unwrap(); + doc2.set(&blackbird, "beak", "orange").unwrap(); doc1.merge(&mut doc2); @@ -743,7 +743,7 @@ fn does_not_interleave_sequence_insertions_at_same_position() { let doc1elems = doc1 .splice( - wisdom.clone(), + &wisdom, 0, 0, vec![ @@ -758,7 +758,7 @@ fn does_not_interleave_sequence_insertions_at_same_position() { let doc2elems = doc2 .splice( - wisdom.clone(), + &wisdom, 0, 0, vec![ @@ -777,16 +777,16 @@ fn does_not_interleave_sequence_insertions_at_same_position() { &doc1, map! { "wisdom" => {wisdom => list![ - {doc1elems[0].clone() => "to"}, - {doc1elems[1].clone() => "be"}, - {doc1elems[2].clone() => "is"}, - {doc1elems[3].clone() => "to"}, - {doc1elems[4].clone() => "do"}, - {doc2elems[0].clone() => "to"}, - {doc2elems[1].clone() => "do"}, - {doc2elems[2].clone() => "is"}, - {doc2elems[3].clone() => "to"}, - {doc2elems[4].clone() => "be"}, + {doc1elems[0] => "to"}, + {doc1elems[1] => "be"}, + {doc1elems[2] => "is"}, + {doc1elems[3] => "to"}, + {doc1elems[4] => "do"}, + {doc2elems[0] => "to"}, + {doc2elems[1] => "do"}, + {doc2elems[2] => "is"}, + {doc2elems[3] => "to"}, + {doc2elems[4] => "be"}, ]} } ); @@ -803,10 +803,10 @@ fn mutliple_insertions_at_same_list_position_with_insertion_by_greater_actor_id( .set(ObjId::Root, "list", automerge::Value::list()) .unwrap() .unwrap(); - let two = doc1.insert(list.clone(), 0, "two").unwrap(); + let two = doc1.insert(&list, 0, "two").unwrap(); doc2.merge(&mut doc1); - let one = doc2.insert(list.clone(), 0, "one").unwrap(); + let one = doc2.insert(&list, 0, "one").unwrap(); assert_doc!( &doc2, map! { @@ -829,10 +829,10 @@ fn mutliple_insertions_at_same_list_position_with_insertion_by_lesser_actor_id() .set(ObjId::Root, "list", automerge::Value::list()) .unwrap() .unwrap(); - let two = doc1.insert(list.clone(), 0, "two").unwrap(); + let two = doc1.insert(&list, 0, "two").unwrap(); doc2.merge(&mut doc1); - let one = doc2.insert(list.clone(), 0, "one").unwrap(); + let one = doc2.insert(&list, 0, "one").unwrap(); assert_doc!( &doc2, map! { @@ -853,13 +853,13 @@ fn insertion_consistent_with_causality() { .set(ObjId::Root, "list", automerge::Value::list()) .unwrap() .unwrap(); - let four = doc1.insert(list.clone(), 0, "four").unwrap(); + let four = doc1.insert(&list, 0, "four").unwrap(); doc2.merge(&mut doc1); - let three = doc2.insert(list.clone(), 0, "three").unwrap(); + let three = doc2.insert(&list, 0, "three").unwrap(); doc1.merge(&mut doc2); - let two = doc1.insert(list.clone(), 0, "two").unwrap(); + let two = doc1.insert(&list, 0, "two").unwrap(); doc2.merge(&mut doc1); - let one = doc2.insert(list.clone(), 0, "one").unwrap(); + let one = doc2.insert(&list, 0, "one").unwrap(); assert_doc!( &doc2, @@ -878,14 +878,14 @@ fn insertion_consistent_with_causality() { fn should_handle_arbitrary_depth_nesting() { let mut doc1 = new_doc(); let a = doc1.set(ObjId::Root, "a", automerge::Value::map()).unwrap().unwrap(); - let b = doc1.set(a.clone(), "b", automerge::Value::map()).unwrap().unwrap(); - let c = doc1.set(b.clone(), "c", automerge::Value::map()).unwrap().unwrap(); - let d = doc1.set(c.clone(), "d", automerge::Value::map()).unwrap().unwrap(); - let e = doc1.set(d.clone(), "e", automerge::Value::map()).unwrap().unwrap(); - let f = doc1.set(e.clone(), "f", automerge::Value::map()).unwrap().unwrap(); - let g = doc1.set(f.clone(), "g", automerge::Value::map()).unwrap().unwrap(); - let h = doc1.set(g.clone(), "h", "h").unwrap().unwrap(); - let j = doc1.set(f.clone(), "i", "j").unwrap().unwrap(); + let b = doc1.set(&a, "b", automerge::Value::map()).unwrap().unwrap(); + let c = doc1.set(&b, "c", automerge::Value::map()).unwrap().unwrap(); + let d = doc1.set(&c, "d", automerge::Value::map()).unwrap().unwrap(); + let e = doc1.set(&d, "e", automerge::Value::map()).unwrap().unwrap(); + let f = doc1.set(&e, "f", automerge::Value::map()).unwrap().unwrap(); + let g = doc1.set(&f, "g", automerge::Value::map()).unwrap().unwrap(); + let h = doc1.set(&g, "h", "h").unwrap().unwrap(); + let j = doc1.set(&f, "i", "j").unwrap().unwrap(); assert_doc!( &doc1, @@ -929,7 +929,7 @@ fn save_restore_complex() { .unwrap(); let first_todo = doc1.insert(todos.clone(), 0, automerge::Value::map()).unwrap(); - doc1.set(first_todo.clone(), "title", "water plants") + doc1.set(&first_todo, "title", "water plants") .unwrap() .unwrap(); let first_done = doc1.set(first_todo.clone(), "done", false).unwrap().unwrap(); @@ -942,7 +942,7 @@ fn save_restore_complex() { .unwrap(); let kill_title = doc1 - .set(first_todo.clone(), "title", "kill plants") + .set(&first_todo, "title", "kill plants") .unwrap() .unwrap(); doc1.merge(&mut doc2); From 1f50c386b8429fb7ff48811358034356ecfb3011 Mon Sep 17 00:00:00 2001 From: Alex Good Date: Mon, 27 Dec 2021 16:57:24 +0000 Subject: [PATCH 4/6] Benches --- automerge/src/lib.rs | 122 ++++++++++++++++++++++++++++--------- edit-trace/benches/main.rs | 6 +- edit-trace/src/main.rs | 4 +- 3 files changed, 98 insertions(+), 34 deletions(-) diff --git a/automerge/src/lib.rs b/automerge/src/lib.rs index b7a3714f..4e07a02e 100644 --- a/automerge/src/lib.rs +++ b/automerge/src/lib.rs @@ -40,13 +40,13 @@ mod sync; mod visualisation; mod error; +mod external_types; mod op_set; mod op_tree; mod query; mod types; mod value; -mod external_types; -pub use external_types::{ExternalOpId as OpId, ExternalObjId as ObjId}; +pub use external_types::{ExternalObjId as ObjId, ExternalOpId as OpId}; use change::{encode_document, export_change}; use clock::Clock; @@ -60,10 +60,8 @@ pub use change::{decode_change, Change}; pub use error::AutomergeError; pub use legacy::Change as ExpandedChange; pub use sync::{BloomFilter, SyncHave, SyncMessage, SyncState}; -pub use types::{ - ActorId, ChangeHash, ObjType, OpType, Patch, Peer, Prop, -}; -use types::{OpId as InternalOpId, Export, Exportable, Importable}; +pub use types::{ActorId, ChangeHash, ObjType, OpType, Patch, Peer, Prop}; +use types::{Export, Exportable, Importable, OpId as InternalOpId}; pub use value::{ScalarValue, Value}; #[derive(Debug, Clone)] @@ -201,7 +199,8 @@ impl Automerge { pub fn ensure_transaction_closed(&mut self) { if let Some(tx) = self.transaction.take() { - let change = export_change(&tx, &self.ops.m.borrow().actors, &self.ops.m.borrow().props); + let change = + export_change(&tx, &self.ops.m.borrow().actors, &self.ops.m.borrow().props); self.update_history(change); } } @@ -316,6 +315,7 @@ impl Automerge { let value = value.into(); let obj = self.import_objid(obj.into()); self.local_op(obj.into(), prop.into(), value.into()) + .map(|o| o.map(|o| self.export_opid(&o).unwrap())) } pub fn insert<'a, O: Into>, V: Into>( @@ -325,6 +325,16 @@ impl Automerge { value: V, ) -> Result { let obj = self.import_objid(obj.into()).into(); + let internal_op = self.insert_internal(obj, index, value)?; + Ok(self.export_opid(&internal_op).unwrap()) + } + + fn insert_internal>( + &mut self, + obj: InternalObjId, + index: usize, + value: V, + ) -> Result { let id = self.next_id(); let query = self.ops.search(obj, query::InsertNth::new(index)); @@ -346,7 +356,7 @@ impl Automerge { self.ops.insert(query.pos, op.clone()); self.tx().operations.push(op); - Ok(self.export_opid(&id).unwrap()) + Ok(id) } pub fn inc<'a, O: Into>, P: Into>( @@ -355,17 +365,35 @@ impl Automerge { prop: P, value: i64, ) -> Result { - match self.local_op(self.import_objid(obj.into()).into(), prop.into(), OpType::Inc(value))? { - Some(opid) => Ok(opid), + match self.local_op( + self.import_objid(obj.into()).into(), + prop.into(), + OpType::Inc(value), + )? { + Some(opid) => Ok(self.export_opid(&opid).unwrap()), None => { panic!("increment should always create a new op") } } } - pub fn del<'a, O: Into>, P: Into>(&mut self, obj: O, prop: P) -> Result { + pub fn del<'a, O: Into>, P: Into>( + &mut self, + obj: O, + prop: P, + ) -> Result { + let obj = self.import_objid(obj); + self.del_internal(obj, prop) + .map(|o| self.export_opid(&o).unwrap()) + } + + fn del_internal>( + &mut self, + obj: InternalObjId, + prop: P, + ) -> Result { // TODO: Should we also no-op multiple delete operations? - match self.local_op(self.import_objid(obj.into()).into(), prop.into(), OpType::Del)? { + match self.local_op(obj, prop.into(), OpType::Del)? { Some(opid) => Ok(opid), None => { panic!("delete should always create a new op") @@ -382,16 +410,20 @@ impl Automerge { del: usize, vals: Vec, ) -> Result, AutomergeError> { - let obj = obj.into(); + let obj = self.import_objid(obj); for _ in 0..del { - self.del(obj.clone(), pos)?; + self.del_internal(obj, pos)?; } let mut result = Vec::with_capacity(vals.len()); for v in vals { - result.push(self.insert(obj.clone(), pos, v)?); + result.push(self.insert_internal(obj, pos, v)?); pos += 1; } - Ok(result) + let exported = result + .into_iter() + .map(|o| self.export_opid(&o).unwrap()) + .collect::>(); + Ok(exported) } pub fn splice_text<'a, O: Into>>( @@ -420,7 +452,11 @@ impl Automerge { Ok(buffer) } - pub fn text_at<'a, O: Into>>(&self, obj: O, heads: &[ChangeHash]) -> Result { + pub fn text_at<'a, O: Into>>( + &self, + obj: O, + heads: &[ChangeHash], + ) -> Result { let clock = self.clock_at(heads); let obj = self.import_objid(obj.into()).into(); let query = self.ops.search(obj, query::ListValsAt::new(clock)); @@ -564,7 +600,7 @@ impl Automerge { obj: InternalObjId, prop: Prop, action: OpType, - ) -> Result, AutomergeError> { + ) -> Result, AutomergeError> { match prop { Prop::Map(s) => self.local_map_op(obj, s, action), Prop::Seq(n) => self.local_list_op(obj, n, action), @@ -576,7 +612,7 @@ impl Automerge { obj: InternalObjId, prop: String, action: OpType, - ) -> Result, AutomergeError> { + ) -> Result, AutomergeError> { if prop.is_empty() { return Err(AutomergeError::EmptyStringKey); } @@ -615,7 +651,7 @@ impl Automerge { self.insert_local_op(op, query.pos, &query.ops_pos); - Ok(Some(self.export_opid(&id).unwrap())) + Ok(Some(id)) } fn local_list_op( @@ -623,7 +659,7 @@ impl Automerge { obj: InternalObjId, index: usize, action: OpType, - ) -> Result, AutomergeError> { + ) -> Result, AutomergeError> { let query = self.ops.search(obj, query::Nth::new(index)); let id = self.next_id(); @@ -658,7 +694,7 @@ impl Automerge { self.insert_local_op(op, query.pos, &query.ops_pos); - Ok(Some(self.export_opid(&id).unwrap())) + Ok(Some(id)) } fn is_causally_ready(&self, change: &Change) -> bool { @@ -684,7 +720,12 @@ impl Automerge { .iter_ops() .enumerate() .map(|(i, c)| { - let actor = self.ops.m.borrow_mut().actors.cache(change.actor_id().clone()); + let actor = self + .ops + .m + .borrow_mut() + .actors + .cache(change.actor_id().clone()); let id = InternalOpId::new(change.start_op + i as u64, actor); // FIXME dont need to_string() let obj: InternalObjId = self.import(&c.obj.to_string()).unwrap(); @@ -694,7 +735,9 @@ impl Automerge { .map(|i| self.import(&i.to_string()).unwrap()) .collect(); let key = match &c.key { - legacy::Key::Map(n) => Key::Map(self.ops.m.borrow_mut().props.cache(n.to_string())), + legacy::Key::Map(n) => { + Key::Map(self.ops.m.borrow_mut().props.cache(n.to_string())) + } legacy::Key::Seq(legacy::ElementId::Head) => Key::Seq(HEAD), // FIXME dont need to_string() legacy::Key::Seq(legacy::ElementId::Id(i)) => { @@ -939,7 +982,13 @@ impl Automerge { to_see.push(*h); } } - let actor = self.ops.m.borrow().actors.lookup(c.actor_id().clone()).unwrap(); + let actor = self + .ops + .m + .borrow() + .actors + .lookup(c.actor_id().clone()) + .unwrap(); clock.include(actor, c.max_op()); seen.insert(hash); } @@ -1015,7 +1064,13 @@ impl Automerge { let history_index = self.history.len(); self.states - .entry(self.ops.m.borrow_mut().actors.cache(change.actor_id().clone())) + .entry( + self.ops + .m + .borrow_mut() + .actors + .cache(change.actor_id().clone()), + ) .or_default() .push(history_index); @@ -1074,7 +1129,11 @@ impl Automerge { pub(crate) fn export(&self, id: E) -> String { match id.export() { - Export::Id(id) => format!("{}@{}", id.counter(), self.ops.m.borrow().actors[id.actor()]), + Export::Id(id) => format!( + "{}@{}", + id.counter(), + self.ops.m.borrow().actors[id.actor()] + ), Export::Prop(index) => self.ops.m.borrow().props[index].clone(), Export::Special(s) => s, } @@ -1171,7 +1230,10 @@ mod tests { fn test_list() -> Result<(), AutomergeError> { let mut doc = Automerge::new(); doc.set_actor(ActorId::random()); - let list_id = doc.set(ObjId::Root, "items", Value::list())?.unwrap().into(); + let list_id = doc + .set(ObjId::Root, "items", Value::list())? + .unwrap() + .into(); doc.set(ObjId::Root, "zzz", "zzzval")?; assert!(doc.value(ObjId::Root, "items")?.unwrap().1 == list_id); doc.insert(&list_id, 0, "a")?; @@ -1201,7 +1263,9 @@ mod tests { #[test] fn test_inc() -> Result<(), AutomergeError> { let mut doc = Automerge::new(); - let id = doc.set(ObjId::Root, "counter", Value::counter(10))?.unwrap(); + let id = doc + .set(ObjId::Root, "counter", Value::counter(10))? + .unwrap(); assert!(doc.value(ObjId::Root, "counter")? == Some((Value::counter(10), id.clone()))); doc.inc(ObjId::Root, "counter", 10)?; assert!(doc.value(ObjId::Root, "counter")? == Some((Value::counter(20), id.clone()))); diff --git a/edit-trace/benches/main.rs b/edit-trace/benches/main.rs index fed72f1e..fb5540f7 100644 --- a/edit-trace/benches/main.rs +++ b/edit-trace/benches/main.rs @@ -1,13 +1,13 @@ -use automerge::{Automerge, Value, ROOT}; +use automerge::{Automerge, Value, ObjId}; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; use std::fs; fn replay_trace(commands: Vec<(usize, usize, Vec)>) -> Automerge { let mut doc = Automerge::new(); - let text = doc.set(ROOT, "text", Value::text()).unwrap().unwrap(); + let text = doc.set(ObjId::Root, "text", Value::text()).unwrap().unwrap(); for (pos, del, vals) in commands { - doc.splice(text, pos, del, vals).unwrap(); + doc.splice(&text, pos, del, vals).unwrap(); } doc.commit(None, None); doc diff --git a/edit-trace/src/main.rs b/edit-trace/src/main.rs index 7e2284af..85b437b7 100644 --- a/edit-trace/src/main.rs +++ b/edit-trace/src/main.rs @@ -19,12 +19,12 @@ fn main() -> Result<(), AutomergeError> { let mut doc = Automerge::new(); let now = Instant::now(); - let text: ObjId = doc.set(ObjId::Root, "text", Value::text()).unwrap().unwrap().into(); + let text = doc.set(ObjId::Root, "text", Value::text()).unwrap().unwrap(); for (i, (pos, del, vals)) in commands.into_iter().enumerate() { if i % 1000 == 0 { println!("Processed {} edits in {} ms", i, now.elapsed().as_millis()); } - doc.splice(text.clone(), pos, del, vals)?; + doc.splice(&text, pos, del, vals)?; } let _ = doc.save(); println!("Done in {} ms", now.elapsed().as_millis()); From 8441fccea2ffe81faf50fe0d8c8188627458ee15 Mon Sep 17 00:00:00 2001 From: Alex Good Date: Mon, 27 Dec 2021 18:13:51 +0000 Subject: [PATCH 5/6] Add LRU cache for external object ID lookup --- automerge/Cargo.toml | 1 + automerge/src/external_types.rs | 9 +++++--- automerge/src/lib.rs | 2 +- automerge/src/op_set.rs | 39 ++++++++++++++++++++++++++++++++- 4 files changed, 46 insertions(+), 5 deletions(-) diff --git a/automerge/Cargo.toml b/automerge/Cargo.toml index 6a0f81e7..d05dfe6d 100644 --- a/automerge/Cargo.toml +++ b/automerge/Cargo.toml @@ -26,6 +26,7 @@ tinyvec = { version = "^1.5.1", features = ["alloc"] } unicode-segmentation = "1.7.1" serde = { version = "^1.0", features=["derive"] } dot = { version = "0.1.4", optional = true } +lru = "^0.7.1" [dependencies.web-sys] version = "^0.3.55" diff --git a/automerge/src/external_types.rs b/automerge/src/external_types.rs index 197a3011..edaeef7d 100644 --- a/automerge/src/external_types.rs +++ b/automerge/src/external_types.rs @@ -21,9 +21,12 @@ impl ExternalOpId { }) } - pub(crate) fn into_opid(&self, metadata: &mut OpSetMetadata) -> OpId { - let actor = metadata.actors.cache(self.actor.clone()); - OpId::new(self.counter, actor) + pub(crate) fn counter(&self) -> u64 { + self.counter + } + + pub(crate) fn actor(&self) -> &ActorId { + &self.actor } } diff --git a/automerge/src/lib.rs b/automerge/src/lib.rs index 4e07a02e..dab21f23 100644 --- a/automerge/src/lib.rs +++ b/automerge/src/lib.rs @@ -1110,7 +1110,7 @@ impl Automerge { } fn import_opid(&self, opid: &OpId) -> InternalOpId { - opid.into_opid(&mut *self.ops.m.borrow_mut()) + self.ops.m.borrow_mut().import_opid(opid) } fn export_opid(&self, opid: &InternalOpId) -> Option { diff --git a/automerge/src/op_set.rs b/automerge/src/op_set.rs index 923b8cad..fc6dd82c 100644 --- a/automerge/src/op_set.rs +++ b/automerge/src/op_set.rs @@ -1,11 +1,15 @@ use crate::op_tree::OpTreeInternal; use crate::query::TreeQuery; use crate::{ActorId, IndexedCache, Key, types::{ObjId, OpId}, Op}; +use crate::external_types::ExternalOpId; use fxhash::FxBuildHasher; use std::cmp::Ordering; use std::collections::HashMap; use std::rc::Rc; use std::cell::RefCell; +use std::fmt::Debug; + +const EXTERNAL_OP_CACHE_SIZE: usize = 100; pub(crate) type OpSet = OpSetInternal<16>; @@ -26,6 +30,7 @@ impl OpSetInternal { m: Rc::new(RefCell::new(OpSetMetadata { actors: IndexedCache::new(), props: IndexedCache::new(), + external_op_cache: lru::LruCache::with_hasher(EXTERNAL_OP_CACHE_SIZE, FxBuildHasher::default()) })), } } @@ -150,12 +155,33 @@ impl<'a, const B: usize> Iterator for Iter<'a, B> { } } -#[derive(Clone, Debug)] pub(crate) struct OpSetMetadata { pub actors: IndexedCache, pub props: IndexedCache, + external_op_cache: lru::LruCache, } +impl Debug for OpSetMetadata { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("OpSetMetadata") + .field("actors", &self.actors) + .field("props", &self.props) + .field("external_op_cache", &format_args!("LruCache with {} keys", self.external_op_cache.len())) + .finish() + } +} + +impl Clone for OpSetMetadata { + fn clone(&self) -> Self { + OpSetMetadata { + actors: self.actors.clone(), + props: self.props.clone(), + external_op_cache: lru::LruCache::with_hasher(EXTERNAL_OP_CACHE_SIZE, FxBuildHasher::default()), + } + } +} + + impl OpSetMetadata { pub fn key_cmp(&self, left: &Key, right: &Key) -> Ordering { match (left, right) { @@ -167,6 +193,17 @@ impl OpSetMetadata { pub fn lamport_cmp(&self, left: S, right: S) -> Ordering { S::cmp(self, left, right) } + + pub fn import_opid(&mut self, ext_opid: &ExternalOpId) -> OpId { + if let Some(opid) = self.external_op_cache.get(ext_opid) { + *opid + } else { + let actor = self.actors.cache(ext_opid.actor().clone()); + let opid = OpId::new(ext_opid.counter(), actor); + self.external_op_cache.put(ext_opid.clone(), opid); + opid + } + } } /// Lamport timestamps which don't contain their actor ID directly and therefore need access to From b9624f5f65cdfaf7e3ab65a35f9546904331c4ef Mon Sep 17 00:00:00 2001 From: Alex Good Date: Mon, 27 Dec 2021 18:44:30 +0000 Subject: [PATCH 6/6] Cache the last object ID used --- automerge/Cargo.toml | 1 - automerge/src/op_set.rs | 44 ++++++++++++----------------------------- 2 files changed, 13 insertions(+), 32 deletions(-) diff --git a/automerge/Cargo.toml b/automerge/Cargo.toml index d05dfe6d..6a0f81e7 100644 --- a/automerge/Cargo.toml +++ b/automerge/Cargo.toml @@ -26,7 +26,6 @@ tinyvec = { version = "^1.5.1", features = ["alloc"] } unicode-segmentation = "1.7.1" serde = { version = "^1.0", features=["derive"] } dot = { version = "0.1.4", optional = true } -lru = "^0.7.1" [dependencies.web-sys] version = "^0.3.55" diff --git a/automerge/src/op_set.rs b/automerge/src/op_set.rs index fc6dd82c..c814c069 100644 --- a/automerge/src/op_set.rs +++ b/automerge/src/op_set.rs @@ -9,8 +9,6 @@ use std::rc::Rc; use std::cell::RefCell; use std::fmt::Debug; -const EXTERNAL_OP_CACHE_SIZE: usize = 100; - pub(crate) type OpSet = OpSetInternal<16>; #[derive(Debug, Clone)] @@ -30,7 +28,7 @@ impl OpSetInternal { m: Rc::new(RefCell::new(OpSetMetadata { actors: IndexedCache::new(), props: IndexedCache::new(), - external_op_cache: lru::LruCache::with_hasher(EXTERNAL_OP_CACHE_SIZE, FxBuildHasher::default()) + last_opid: None, })), } } @@ -155,30 +153,13 @@ impl<'a, const B: usize> Iterator for Iter<'a, B> { } } +#[derive(Debug, Clone)] pub(crate) struct OpSetMetadata { pub actors: IndexedCache, pub props: IndexedCache, - external_op_cache: lru::LruCache, -} - -impl Debug for OpSetMetadata { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("OpSetMetadata") - .field("actors", &self.actors) - .field("props", &self.props) - .field("external_op_cache", &format_args!("LruCache with {} keys", self.external_op_cache.len())) - .finish() - } -} - -impl Clone for OpSetMetadata { - fn clone(&self) -> Self { - OpSetMetadata { - actors: self.actors.clone(), - props: self.props.clone(), - external_op_cache: lru::LruCache::with_hasher(EXTERNAL_OP_CACHE_SIZE, FxBuildHasher::default()), - } - } + // For the common case of many subsequent operations on the same object we cache the last + // object we looked up + last_opid: Option<(ExternalOpId, OpId)>, } @@ -195,14 +176,15 @@ impl OpSetMetadata { } pub fn import_opid(&mut self, ext_opid: &ExternalOpId) -> OpId { - if let Some(opid) = self.external_op_cache.get(ext_opid) { - *opid - } else { - let actor = self.actors.cache(ext_opid.actor().clone()); - let opid = OpId::new(ext_opid.counter(), actor); - self.external_op_cache.put(ext_opid.clone(), opid); - opid + if let Some((last_ext, last_int)) = &self.last_opid { + if last_ext == ext_opid { + return *last_int; + } } + let actor = self.actors.cache(ext_opid.actor().clone()); + let opid = OpId::new(ext_opid.counter(), actor); + self.last_opid = Some((ext_opid.clone(), opid)); + opid } }