diff --git a/automerge-wasm/src/lib.rs b/automerge-wasm/src/lib.rs index 80a3d65f..cf22b1a1 100644 --- a/automerge-wasm/src/lib.rs +++ b/automerge-wasm/src/lib.rs @@ -1,6 +1,6 @@ extern crate web_sys; use automerge as am; -use automerge::{Change, ChangeHash, Prop, Value}; +use automerge::{Change, ChangeHash, Prop, Value, ExId}; use js_sys::{Array, Object, Reflect, Uint8Array}; use serde::de::DeserializeOwned; use serde::Serialize; @@ -151,9 +151,9 @@ impl Automerge { pub fn keys(&mut self, obj: JsValue, heads: JsValue) -> Result { let obj = self.import(obj)?; let result = if let Some(heads) = get_heads(heads) { - self.0.keys_at(obj, &heads) + self.0.keys_at(&obj, &heads) } else { - self.0.keys(obj) + self.0.keys(&obj) } .iter() .map(|s| JsValue::from_str(s)) @@ -164,9 +164,9 @@ impl Automerge { pub fn text(&mut self, obj: JsValue, heads: JsValue) -> Result { let obj = self.import(obj)?; if let Some(heads) = get_heads(heads) { - self.0.text_at(obj, &heads) + self.0.text_at(&obj, &heads) } else { - self.0.text(obj) + self.0.text(&obj) } .map_err(to_js_err) .map(|t| t.into()) @@ -185,7 +185,7 @@ impl Automerge { let mut vals = vec![]; if let Some(t) = text.as_string() { self.0 - .splice_text(obj, start, delete_count, &t) + .splice_text(&obj, start, delete_count, &t) .map_err(to_js_err)?; } else { if let Ok(array) = text.dyn_into::() { @@ -201,7 +201,7 @@ impl Automerge { } } self.0 - .splice(obj, start, delete_count, vals) + .splice(&obj, start, delete_count, vals) .map_err(to_js_err)?; } Ok(()) @@ -223,9 +223,12 @@ impl Automerge { let value = self.import_value(value, datatype)?; let opid = self .0 - .insert(obj, index as usize, value) + .insert(&obj, index as usize, value) .map_err(to_js_err)?; - Ok(self.export(opid)) + match opid { + Some(opid) => Ok(self.export(opid)), + None => Ok(JsValue::null()), + } } pub fn set( @@ -238,7 +241,7 @@ impl Automerge { let obj = 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)?; + let opid = self.0.set(&obj, prop, value).map_err(to_js_err)?; match opid { Some(opid) => Ok(self.export(opid)), None => Ok(JsValue::null()), @@ -252,7 +255,7 @@ impl Automerge { .as_f64() .ok_or("inc needs a numberic value") .map_err(to_js_err)?; - self.0.inc(obj, prop, value as i64).map_err(to_js_err)?; + self.0.inc(&obj, prop, value as i64).map_err(to_js_err)?; Ok(()) } @@ -263,9 +266,9 @@ impl Automerge { let heads = get_heads(heads); if let Ok(prop) = prop { let value = if let Some(h) = heads { - self.0.value_at(obj, prop, &h) + self.0.value_at(&obj, prop, &h) } else { - self.0.value(obj, prop) + self.0.value(&obj, prop) } .map_err(to_js_err)?; match value { @@ -289,9 +292,9 @@ impl Automerge { let prop = to_prop(arg); if let Ok(prop) = prop { let values = if let Some(heads) = get_heads(heads) { - self.0.values_at(obj, prop, &heads) + self.0.values_at(&obj, prop, &heads) } else { - self.0.values(obj, prop) + self.0.values(&obj, prop) } .map_err(to_js_err)?; for value in values { @@ -318,16 +321,16 @@ impl Automerge { pub fn length(&mut self, obj: JsValue, heads: JsValue) -> Result { let obj = self.import(obj)?; if let Some(heads) = get_heads(heads) { - Ok((self.0.length_at(obj, &heads) as f64).into()) + Ok((self.0.length_at(&obj, &heads) as f64).into()) } else { - Ok((self.0.length(obj) as f64).into()) + Ok((self.0.length(&obj) as f64).into()) } } pub fn del(&mut self, obj: JsValue, prop: JsValue) -> Result<(), JsValue> { let obj = self.import(obj)?; let prop = to_prop(prop)?; - self.0.del(obj, prop).map_err(to_js_err)?; + self.0.del(&obj, prop).map_err(to_js_err)?; Ok(()) } @@ -442,11 +445,11 @@ impl Automerge { } } - fn export(&self, val: E) -> JsValue { - self.0.export(val).into() + fn export(&self, val: ExId) -> JsValue { + val.to_string().into() } - fn import(&self, id: JsValue) -> Result { + fn import(&self, id: JsValue) -> Result { let id_str = id .as_string() .ok_or("invalid opid/objid/elemid") diff --git a/automerge/src/change.rs b/automerge/src/change.rs index 4d3984e5..d3ab7144 100644 --- a/automerge/src/change.rs +++ b/automerge/src/change.rs @@ -8,7 +8,6 @@ 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, }; use core::ops::Range; use flate2::{ @@ -417,7 +416,7 @@ fn increment_range_map(ranges: &mut HashMap>, len: usize) { } fn export_objid(id: &ObjId, actors: &IndexedCache) -> amp::ObjectId { - if id.0 == ROOT { + if id == &ObjId::root() { amp::ObjectId::Root } else { export_opid(&id.0, actors).into() diff --git a/automerge/src/columnar.rs b/automerge/src/columnar.rs index 3a1df3cb..352f9aca 100644 --- a/automerge/src/columnar.rs +++ b/automerge/src/columnar.rs @@ -11,7 +11,6 @@ use std::{ str, }; -use crate::ROOT; use crate::{ActorId, ElemId, Key, ObjId, ObjType, OpId, OpType, ScalarValue}; use crate::legacy as amp; @@ -846,7 +845,7 @@ impl ObjEncoder { fn append(&mut self, obj: &ObjId, actors: &[usize]) { match obj.0 { - ROOT => { + OpId(ctr, _) if ctr == 0 => { self.actor.append_null(); self.ctr.append_null(); } @@ -951,7 +950,7 @@ impl ChangeEncoder { index_by_hash.insert(hash, index); } self.actor - .append_value(actors.lookup(change.actor_id.clone()).unwrap()); //actors.iter().position(|a| a == &change.actor_id).unwrap()); + .append_value(actors.lookup(&change.actor_id).unwrap()); //actors.iter().position(|a| a == &change.actor_id).unwrap()); self.seq.append_value(change.seq); // FIXME iterops.count is crazy slow self.max_op diff --git a/automerge/src/error.rs b/automerge/src/error.rs index ddb7092b..640cd9fd 100644 --- a/automerge/src/error.rs +++ b/automerge/src/error.rs @@ -17,6 +17,8 @@ pub enum AutomergeError { InvalidSeq(u64), #[error("index {0} is out of bounds")] InvalidIndex(usize), + #[error("generic automerge error")] + Fail, } impl From for AutomergeError { diff --git a/automerge/src/indexed_cache.rs b/automerge/src/indexed_cache.rs index 21ffd75b..533d082e 100644 --- a/automerge/src/indexed_cache.rs +++ b/automerge/src/indexed_cache.rs @@ -2,10 +2,11 @@ use itertools::Itertools; use std::collections::HashMap; use std::hash::Hash; use std::ops::Index; +use std::rc::Rc; #[derive(Debug, Clone)] pub(crate) struct IndexedCache { - pub cache: Vec, + pub cache: Vec>, lookup: HashMap, } @@ -25,14 +26,14 @@ where *n } else { let n = self.cache.len(); - self.cache.push(item.clone()); + self.cache.push(Rc::new(item.clone())); self.lookup.insert(item, n); n } } - pub fn lookup(&self, item: T) -> Option { - self.lookup.get(&item).cloned() + pub fn lookup(&self, item: &T) -> Option { + self.lookup.get(item).cloned() } pub fn len(&self) -> usize { @@ -48,7 +49,7 @@ where self.cache.iter().sorted().cloned().for_each(|item| { let n = sorted.cache.len(); sorted.cache.push(item.clone()); - sorted.lookup.insert(item, n); + sorted.lookup.insert(item.as_ref().clone(), n); }); sorted } @@ -63,7 +64,7 @@ where } impl IntoIterator for IndexedCache { - type Item = T; + type Item = Rc; type IntoIter = std::vec::IntoIter; fn into_iter(self) -> Self::IntoIter { diff --git a/automerge/src/lib.rs b/automerge/src/lib.rs index c2595c68..4f7ca9c9 100644 --- a/automerge/src/lib.rs +++ b/automerge/src/lib.rs @@ -46,12 +46,14 @@ mod query; mod types; mod value; +use std::rc::Rc; +use std::fmt; 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, Op, OpId, HEAD, Export, Exportable }; use unicode_segmentation::UnicodeSegmentation; pub use change::{decode_change, Change}; @@ -59,8 +61,7 @@ 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, }; pub use value::{ScalarValue, Value}; @@ -78,6 +79,35 @@ pub struct Automerge { transaction: Option, } +#[derive(Debug, Clone)] +pub enum ExId { + Root, + Id(u64, Rc, usize), +} + +impl PartialEq for ExId { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (ExId::Root, ExId::Root) => true, + (ExId::Id(ctr1,actor1,_), ExId::Id(ctr2,actor2,_)) if ctr1 == ctr2 && actor1 == actor2 => true, + _ => false + } + } +} + +impl Eq for ExId {} + +impl fmt::Display for ExId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ExId::Root => write!(f, "_root"), + ExId::Id(ctr,actor,_) => write!(f, "{}@{}", ctr, actor), + } + } +} + +pub const ROOT : ExId = ExId::Root; + impl Automerge { pub fn new() -> Self { Automerge { @@ -262,24 +292,40 @@ impl Automerge { // PropAt::() // NthAt::() - pub fn keys(&self, obj: OpId) -> Vec { - let q = self.ops.search(obj.into(), query::Keys::new()); - q.keys.iter().map(|k| self.export(*k)).collect() + pub fn keys(&self, obj: &ExId) -> Vec { + if let Ok(obj) = self.exid_to_obj(obj) { + let q = self.ops.search(obj.into(), query::Keys::new()); + q.keys.iter().map(|k| self.to_string(*k)).collect() + } else { + vec![] + } } - pub fn keys_at(&self, obj: OpId, heads: &[ChangeHash]) -> Vec { - 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 keys_at(&self, obj: &ExId, heads: &[ChangeHash]) -> Vec { + if let Ok(obj) = self.exid_to_obj(obj) { + let clock = self.clock_at(heads); + let q = self.ops.search(obj.into(), query::KeysAt::new(clock)); + q.keys.iter().map(|k| self.to_string(*k)).collect() + } else { + vec![] + } } - pub fn length(&self, obj: OpId) -> usize { - self.ops.search(obj.into(), query::Len::new(obj.into())).len + pub fn length(&self, obj: &ExId) -> usize { + if let Ok(obj) = self.exid_to_obj(obj) { + self.ops.search(obj.into(), query::Len::new(obj.into())).len + } else { + 0 + } } - pub fn length_at(&self, obj: OpId, heads: &[ChangeHash]) -> usize { - let clock = self.clock_at(heads); - self.ops.search(obj.into(), query::LenAt::new(clock)).len + pub fn length_at(&self, obj: &ExId, heads: &[ChangeHash]) -> usize { + if let Ok(obj) = self.exid_to_obj(obj) { + let clock = self.clock_at(heads); + self.ops.search(obj, query::LenAt::new(clock)).len + } else { + 0 + } } // set(obj, prop, value) - value can be scalar or objtype @@ -302,32 +348,73 @@ impl Automerge { /// - The key does not exist in the object pub fn set, V: Into>( &mut self, - obj: OpId, + obj: &ExId, prop: P, value: V, - ) -> Result, AutomergeError> { + ) -> Result, AutomergeError> { + let obj = self.exid_to_obj(obj)?; let value = value.into(); - self.local_op(obj.into(), prop.into(), value.into()) + if let Some(id) = self.local_op(obj, prop.into(), value.into())? { + Ok(Some(self.id_to_exid(id))) + } else { + Ok(None) + } + } + + fn exid_to_obj(&self, id: &ExId) -> Result { + match id { + ExId::Root => Ok(ObjId::root()), + ExId::Id(ctr,actor,idx) => { + // do a direct get here b/c this could be foriegn and not be within the array + // bounds + if self.ops.m.actors.cache.get(*idx) == Some(actor) { + Ok(ObjId(OpId(*ctr,*idx))) + } else { + // FIXME - make a real error + let idx = self.ops.m.actors.lookup(actor).ok_or(AutomergeError::Fail)?; + Ok(ObjId(OpId(*ctr,idx))) + } + } + } + } + + fn id_to_exid(&self, id: OpId) -> ExId { + ExId::Id(id.0, self.ops.m.actors.cache[id.1].clone(), id.1) } pub fn insert>( &mut self, - obj: OpId, + obj: &ExId, index: usize, value: V, - ) -> Result { - let obj = obj.into(); + ) -> Result, AutomergeError> { + let obj = self.exid_to_obj(obj)?; + if let Some(id) = self.do_insert(obj, index, value)? { + Ok(Some(self.id_to_exid(id))) + } else { + Ok(None) + } + } + + fn do_insert>( + &mut self, + obj: ObjId, + index: usize, + value: V, + ) -> Result, AutomergeError> { let id = self.next_id(); let query = self.ops.search(obj, query::InsertNth::new(index)); let key = query.key()?; let value = value.into(); + let action = value.into(); + let is_make = matches!(&action, OpType::Make(_)); let op = Op { change: self.history.len(), id, - action: value.into(), + action, obj, key, succ: Default::default(), @@ -338,60 +425,63 @@ impl Automerge { self.ops.insert(query.pos, op.clone()); self.tx().operations.push(op); - Ok(id) + if is_make { + Ok(Some(id)) + } else { + Ok(None) + } } pub fn inc>( &mut self, - obj: OpId, + obj: &ExId, prop: P, value: i64, - ) -> Result { - match self.local_op(obj.into(), prop.into(), OpType::Inc(value))? { - Some(opid) => Ok(opid), - None => { - panic!("increment should always create a new op") - } - } + ) -> Result<(), AutomergeError> { + let obj = self.exid_to_obj(obj)?; + self.local_op(obj.into(), prop.into(), OpType::Inc(value))?; + Ok(()) } - pub fn del>(&mut self, obj: OpId, prop: P) -> Result { - // TODO: Should we also no-op multiple delete operations? - match self.local_op(obj.into(), prop.into(), OpType::Del)? { - Some(opid) => Ok(opid), - None => { - panic!("delete should always create a new op") - } - } + pub fn del>(&mut self, obj: &ExId, prop: P) -> Result<(), AutomergeError> { + let obj = self.exid_to_obj(obj)?; + self.local_op(obj.into(), prop.into(), OpType::Del)?; + Ok(()) } /// Splice new elements into the given sequence. Returns a vector of the OpIds used to insert /// the new elements pub fn splice( &mut self, - obj: OpId, + obj: &ExId, mut pos: usize, del: usize, vals: Vec, - ) -> Result, AutomergeError> { + ) -> Result, AutomergeError> { + let obj = self.exid_to_obj(obj)?; for _ in 0..del { - self.del(obj, pos)?; + // del() + self.local_op(obj.into(), pos.into(), OpType::Del)?; } - let mut result = Vec::with_capacity(vals.len()); + let mut results = Vec::new(); for v in vals { - result.push(self.insert(obj, pos, v)?); + // insert() + let id = self.do_insert(obj, pos, v)?; + if let Some(id) = id { + results.push(self.id_to_exid(id)); + } pos += 1; } - Ok(result) + Ok(results) } pub fn splice_text( - &mut self, - obj: OpId, + & mut self, + obj: &ExId, pos: usize, del: usize, text: &str, - ) -> Result, AutomergeError> { + ) -> Result, AutomergeError> { let mut vals = vec![]; for c in text.to_owned().graphemes(true) { vals.push(c.into()); @@ -399,8 +489,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: &ExId) -> Result { + let obj = self.exid_to_obj(obj)?; let query = self.ops.search(obj, query::ListVals::new(obj)); let mut buffer = String::new(); for q in &query.ops { @@ -411,9 +501,9 @@ impl Automerge { Ok(buffer) } - pub fn text_at(&self, obj: OpId, heads: &[ChangeHash]) -> Result { + pub fn text_at(&self, obj: &ExId, heads: &[ChangeHash]) -> Result { + let obj = self.exid_to_obj(obj)?; let clock = self.clock_at(heads); - let obj = obj.into(); let query = self.ops.search(obj, query::ListValsAt::new(clock)); let mut buffer = String::new(); for q in &query.ops { @@ -429,36 +519,36 @@ impl Automerge { // Something better? pub fn value>( &self, - obj: OpId, + obj: &ExId, prop: P, - ) -> Result, AutomergeError> { + ) -> Result, AutomergeError> { Ok(self.values(obj, prop.into())?.first().cloned()) } pub fn value_at>( &self, - obj: OpId, + obj: &ExId, prop: P, heads: &[ChangeHash], - ) -> Result, AutomergeError> { + ) -> Result, AutomergeError> { Ok(self.values_at(obj, prop, heads)?.first().cloned()) } pub fn values>( &self, - obj: OpId, + obj: &ExId, prop: P, - ) -> Result, AutomergeError> { - let obj = obj.into(); + ) -> Result, AutomergeError> { + let obj = self.exid_to_obj(obj)?; let result = match prop.into() { Prop::Map(p) => { - let prop = self.ops.m.props.lookup(p); + let prop = self.ops.m.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| (o.value(), self.id_to_exid(o.id))) .collect() } else { vec![] @@ -469,7 +559,7 @@ impl Automerge { .search(obj, query::Nth::new(n)) .ops .into_iter() - .map(|o| o.into()) + .map(|o| (o.value(), self.id_to_exid(o.id))) .collect(), }; Ok(result) @@ -477,22 +567,22 @@ impl Automerge { pub fn values_at>( &self, - obj: OpId, + obj: &ExId, prop: P, heads: &[ChangeHash], - ) -> Result, AutomergeError> { + ) -> Result, AutomergeError> { let prop = prop.into(); - let obj = obj.into(); + let obj = self.exid_to_obj(obj)?; 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.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| (o.value(), self.id_to_exid(o.id))) .collect() } else { vec![] @@ -503,7 +593,7 @@ impl Automerge { .search(obj, query::NthAt::new(n, clock)) .ops .into_iter() - .map(|o| o.into()) + .map(|o| (o.value(), self.id_to_exid(o.id))) .collect(), }; Ok(result) @@ -576,21 +666,12 @@ impl Automerge { let prop = self.ops.m.props.cache(prop); let query = self.ops.search(obj, query::Prop::new(obj, prop)); - match (&query.ops[..], &action) { - // If there are no conflicts for this value and the old operation and the new operation are - // both setting the same value then we do nothing. - ( - &[Op { - action: OpType::Set(ref old_v), - .. - }], - OpType::Set(new_v), - ) if old_v == new_v => { - return Ok(None); - } - _ => {} + if query.ops.len() == 1 && query.ops[0].is_noop(&action) { + return Ok(None); } + let is_make = matches!(&action, OpType::Make(_)); + let pred = query.ops.iter().map(|op| op.id).collect(); let op = Op { @@ -606,7 +687,11 @@ impl Automerge { self.insert_local_op(op, query.pos, &query.ops_pos); - Ok(Some(id)) + if is_make { + Ok(Some(id)) + } else { + Ok(None) + } } fn local_list_op( @@ -621,21 +706,12 @@ impl Automerge { let pred = query.ops.iter().map(|op| op.id).collect(); let key = query.key()?; - match (&query.ops[..], &action) { - // If there are no conflicts for this value and the old operation and the new operation are - // both setting the same value then we do nothing. - ( - &[Op { - action: OpType::Set(ref old_v), - .. - }], - OpType::Set(new_v), - ) if old_v == new_v => { - return Ok(None); - } - _ => {} + if query.ops.len() == 1 && query.ops[0].is_noop(&action) { + return Ok(None); } + let is_make = matches!(&action, OpType::Make(_)); + let op = Op { change: self.history.len(), id, @@ -649,7 +725,11 @@ impl Automerge { self.insert_local_op(op, query.pos, &query.ops_pos); - Ok(Some(id)) + if is_make { + Ok(Some(id)) + } else { + Ok(None) + } } fn is_causally_ready(&self, change: &Change) -> bool { @@ -677,20 +757,19 @@ impl Automerge { .map(|(i, c)| { let actor = self.ops.m.actors.cache(change.actor_id().clone()); let id = OpId(change.start_op + i as u64, actor); - // FIXME dont need to_string() - let obj: ObjId = self.import(&c.obj.to_string()).unwrap(); + let obj = match c.obj { + legacy::ObjectId::Root => ObjId::root(), + legacy::ObjectId::Id(id) => ObjId(OpId(id.0, self.ops.m.actors.cache(id.1))), + }; let pred = c .pred .iter() - .map(|i| self.import(&i.to_string()).unwrap()) + .map(|i| OpId(i.0, self.ops.m.actors.cache(i.1.clone()))) .collect(); let key = match &c.key { legacy::Key::Map(n) => Key::Map(self.ops.m.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)) => { - Key::Seq(self.import(&i.to_string()).unwrap()) - } + legacy::Key::Seq(legacy::ElementId::Id(i)) => Key::Seq(ElemId(OpId(i.0, self.ops.m.actors.cache(i.1.clone())))) }; Op { change: change_id, @@ -724,11 +803,13 @@ impl Automerge { let c: Vec<_> = self.history.iter().map(|c| c.decode()).collect(); let ops: Vec<_> = self.ops.iter().cloned().collect(); // TODO - can we make encode_document error free + let tmp : Vec<_> = self.ops.m.props.cache.iter().map(|a| a.as_ref().clone()).collect(); + // FIXME : tmp let bytes = encode_document( &c, ops.as_slice(), &self.ops.m.actors, - &self.ops.m.props.cache, + &tmp ); if bytes.is_ok() { self.saved = self.get_heads().iter().copied().collect(); @@ -930,7 +1011,7 @@ impl Automerge { to_see.push(*h); } } - let actor = self.ops.m.actors.lookup(c.actor_id().clone()).unwrap(); + let actor = self.ops.m.actors.lookup(c.actor_id()).unwrap(); clock.include(actor, c.max_op()); seen.insert(hash); } @@ -1023,9 +1104,9 @@ impl Automerge { self.deps.insert(change.hash); } - pub fn import(&self, s: &str) -> Result { - if let Some(x) = I::from(s) { - Ok(x) + pub fn import(&self, s: &str) -> Result { + if s == "_root" { + Ok(ExId::Root) } else { let n = s .find('@') @@ -1038,13 +1119,13 @@ impl Automerge { .ops .m .actors - .lookup(actor) + .lookup(&actor) .ok_or_else(|| AutomergeError::InvalidOpId(s.to_owned()))?; - Ok(I::wrap(OpId(counter, actor))) + Ok(ExId::Id(counter, self.ops.m.actors.cache[actor].clone(), actor)) } } - pub fn export(&self, id: E) -> String { + fn to_string(&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(), @@ -1063,11 +1144,11 @@ impl Automerge { "succ" ); for i in self.ops.iter() { - let id = self.export(i.id); - let obj = self.export(i.obj); + let id = self.to_string(i.id); + let obj = self.to_string(i.obj); let key = match i.key { Key::Map(n) => self.ops.m.props[n].clone(), - Key::Seq(n) => self.export(n), + Key::Seq(n) => self.to_string(n), }; let value: String = match &i.action { OpType::Set(value) => format!("{}", value), @@ -1075,8 +1156,8 @@ impl Automerge { OpType::Inc(obj) => format!("inc{}", obj), OpType::Del => format!("del{}", 0), }; - let pred: Vec<_> = i.pred.iter().map(|id| self.export(*id)).collect(); - let succ: Vec<_> = i.succ.iter().map(|id| self.export(*id)).collect(); + let pred: Vec<_> = i.pred.iter().map(|id| self.to_string(*id)).collect(); + let succ: Vec<_> = i.succ.iter().map(|id| self.to_string(*id)).collect(); log!( " {:12} {:12} {:12} {} {:?} {:?}", id, @@ -1123,9 +1204,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(&ROOT, "hello", "world")?; assert!(doc.pending_ops() == 1); - doc.value(ROOT, "hello")?; + doc.value(&ROOT, "hello")?; Ok(()) } @@ -1133,18 +1214,18 @@ 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); - 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); + let list_id = doc.set(&ROOT, "items", Value::list())?.unwrap(); + doc.set(&ROOT, "zzz", "zzzval")?; + assert!(doc.value(&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.length(&list_id) == 4); doc.save()?; Ok(()) } @@ -1153,22 +1234,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(&ROOT, "xxx", "xxx")?; + assert!(!doc.values(&ROOT, "xxx")?.is_empty()); + doc.del(&ROOT, "xxx")?; + assert!(doc.values(&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))); + doc.set(&ROOT, "counter", Value::counter(10))?; + assert!(doc.value(&ROOT, "counter")?.unwrap().0 == Value::counter(10)); + doc.inc(&ROOT, "counter", 10)?; + assert!(doc.value(&ROOT, "counter")?.unwrap().0 == Value::counter(20)); + doc.inc(&ROOT, "counter", -5)?; + assert!(doc.value(&ROOT, "counter")?.unwrap().0 == Value::counter(15)); Ok(()) } @@ -1176,15 +1257,15 @@ mod tests { fn test_save_incremental() -> Result<(), AutomergeError> { let mut doc = Automerge::new(); - doc.set(ROOT, "foo", 1)?; + doc.set(&ROOT, "foo", 1)?; let save1 = doc.save().unwrap(); - doc.set(ROOT, "bar", 2)?; + doc.set(&ROOT, "bar", 2)?; let save2 = doc.save_incremental(); - doc.set(ROOT, "baz", 3)?; + doc.set(&ROOT, "baz", 3)?; let save3 = doc.save_incremental(); @@ -1202,7 +1283,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(&ROOT, "baz")? == doc_b.values(&ROOT, "baz")?); assert!(doc_a.save().unwrap() == doc_b.save().unwrap()); @@ -1212,17 +1293,17 @@ 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(&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(()) } @@ -1231,50 +1312,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(&ROOT, "prop1", "val1")?; doc.commit(None, None); let heads1 = doc.get_heads(); - doc.set(ROOT, "prop1", "val2")?; + doc.set(&ROOT, "prop1", "val2")?; doc.commit(None, None); let heads2 = doc.get_heads(); - doc.set(ROOT, "prop2", "val3")?; + doc.set(&ROOT, "prop2", "val3")?; doc.commit(None, None); let heads3 = doc.get_heads(); - doc.del(ROOT, "prop1")?; + doc.del(&ROOT, "prop1")?; doc.commit(None, None); let heads4 = doc.get_heads(); - doc.set(ROOT, "prop3", "val4")?; + doc.set(&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(&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(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(&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(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(&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(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(&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(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(&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(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(&ROOT, &[]).is_empty()); + assert!(doc.value_at(&ROOT, "prop1", &[])? == None); + assert!(doc.value_at(&ROOT, "prop2", &[])? == None); + assert!(doc.value_at(&ROOT, "prop3", &[])? == None); Ok(()) } @@ -1283,47 +1364,47 @@ 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(&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/src/types.rs b/automerge/src/types.rs index f00beed3..623b3db2 100644 --- a/automerge/src/types.rs +++ b/automerge/src/types.rs @@ -1,6 +1,6 @@ use crate::error; use crate::legacy as amp; -use crate::ScalarValue; +use crate::{ Value, ScalarValue }; use serde::{Deserialize, Serialize}; use std::cmp::Eq; use std::convert::TryFrom; @@ -10,7 +10,7 @@ use std::str::FromStr; use tinyvec::{ArrayVec, TinyVec}; pub(crate) const HEAD: ElemId = ElemId(OpId(0, 0)); -pub const ROOT: OpId = OpId(0, 0); +pub(crate) const ROOT: OpId = OpId(0, 0); const ROOT_STR: &str = "_root"; const HEAD_STR: &str = "_head"; @@ -161,23 +161,16 @@ 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 { - fn wrap(id: OpId) -> Self; - fn from(s: &str) -> Option - where - Self: std::marker::Sized; -} - impl OpId { #[inline] pub fn counter(&self) -> u64 { @@ -234,45 +227,6 @@ impl Exportable for Key { } } -impl Importable for ObjId { - fn wrap(id: OpId) -> Self { - ObjId(id) - } - fn from(s: &str) -> Option { - if s == ROOT_STR { - Some(ROOT.into()) - } else { - None - } - } -} - -impl Importable for OpId { - fn wrap(id: OpId) -> Self { - id - } - fn from(s: &str) -> Option { - if s == ROOT_STR { - Some(ROOT) - } else { - None - } - } -} - -impl Importable for ElemId { - fn wrap(id: OpId) -> Self { - ElemId(id) - } - fn from(s: &str) -> Option { - if s == HEAD_STR { - Some(HEAD) - } else { - None - } - } -} - impl From for ObjId { fn from(o: OpId) -> Self { ObjId(o) @@ -352,11 +306,17 @@ impl Key { } #[derive(Debug, Clone, PartialOrd, Ord, Eq, PartialEq, Copy, Hash, Default)] -pub struct OpId(pub u64, pub usize); +pub(crate) struct OpId(pub u64, pub usize); #[derive(Debug, Clone, Copy, PartialOrd, Eq, PartialEq, Ord, Hash, Default)] pub(crate) struct ObjId(pub OpId); +impl ObjId { + pub fn root() -> Self { + ObjId(OpId(0,0)) + } +} + #[derive(Debug, Clone, Copy, PartialOrd, Eq, PartialEq, Ord, Hash, Default)] pub(crate) struct ElemId(pub OpId); @@ -374,7 +334,11 @@ pub(crate) struct Op { impl Op { pub fn is_del(&self) -> bool { - matches!(self.action, OpType::Del) + matches!(&self.action, OpType::Del) + } + + pub fn is_noop(&self, action: &OpType) -> bool { + matches!((&self.action, action), (OpType::Set(n), OpType::Set(m)) if n == m) } pub fn overwrites(&self, other: &Op) -> bool { @@ -389,6 +353,14 @@ impl Op { } } + pub fn value(&self) -> Value { + match &self.action { + OpType::Make(obj_type) => Value::Object(*obj_type), + OpType::Set(scalar) => Value::Scalar(scalar.clone()), + _ => panic!("cant convert op into a value - {:?}", self), + } + } + #[allow(dead_code)] pub fn dump(&self) -> String { match &self.action { diff --git a/edit-trace/benches/main.rs b/edit-trace/benches/main.rs index fed72f1e..197614f6 100644 --- a/edit-trace/benches/main.rs +++ b/edit-trace/benches/main.rs @@ -5,9 +5,9 @@ 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(&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 94fde72c..db39bcdd 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 = doc.set(ROOT, "text", Value::text()).unwrap().unwrap(); + let text = doc.set( &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, pos, del, vals)?; + doc.splice(&text, pos, del, vals)?; } let _ = doc.save(); println!("Done in {} ms", now.elapsed().as_millis());