Compare commits

...

5 commits

Author SHA1 Message Date
Orion Henry
8e83310ea3 comments 2021-12-31 20:10:05 -05:00
Orion Henry
149ab50b3e dont panic if we have a bad exid.idx 2021-12-31 20:03:32 -05:00
Orion Henry
f06a3e3928 cleanup get tests working 2021-12-31 19:57:10 -05:00
Orion Henry
3bb4bd79b9 fix bench 2021-12-31 16:59:10 -05:00
Orion Henry
a09de2302b wip 2021-12-31 16:46:20 -05:00
9 changed files with 349 additions and 292 deletions

View file

@ -1,6 +1,6 @@
extern crate web_sys; extern crate web_sys;
use automerge as am; 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 js_sys::{Array, Object, Reflect, Uint8Array};
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use serde::Serialize; use serde::Serialize;
@ -151,9 +151,9 @@ impl Automerge {
pub fn keys(&mut self, obj: JsValue, heads: JsValue) -> Result<Array, JsValue> { pub fn keys(&mut self, obj: JsValue, heads: JsValue) -> Result<Array, JsValue> {
let obj = self.import(obj)?; let obj = self.import(obj)?;
let result = if let Some(heads) = get_heads(heads) { let result = if let Some(heads) = get_heads(heads) {
self.0.keys_at(obj, &heads) self.0.keys_at(&obj, &heads)
} else { } else {
self.0.keys(obj) self.0.keys(&obj)
} }
.iter() .iter()
.map(|s| JsValue::from_str(s)) .map(|s| JsValue::from_str(s))
@ -164,9 +164,9 @@ impl Automerge {
pub fn text(&mut self, obj: JsValue, heads: JsValue) -> Result<JsValue, JsValue> { pub fn text(&mut self, obj: JsValue, heads: JsValue) -> Result<JsValue, JsValue> {
let obj = self.import(obj)?; let obj = self.import(obj)?;
if let Some(heads) = get_heads(heads) { if let Some(heads) = get_heads(heads) {
self.0.text_at(obj, &heads) self.0.text_at(&obj, &heads)
} else { } else {
self.0.text(obj) self.0.text(&obj)
} }
.map_err(to_js_err) .map_err(to_js_err)
.map(|t| t.into()) .map(|t| t.into())
@ -185,7 +185,7 @@ impl Automerge {
let mut vals = vec![]; let mut vals = vec![];
if let Some(t) = text.as_string() { if let Some(t) = text.as_string() {
self.0 self.0
.splice_text(obj, start, delete_count, &t) .splice_text(&obj, start, delete_count, &t)
.map_err(to_js_err)?; .map_err(to_js_err)?;
} else { } else {
if let Ok(array) = text.dyn_into::<Array>() { if let Ok(array) = text.dyn_into::<Array>() {
@ -201,7 +201,7 @@ impl Automerge {
} }
} }
self.0 self.0
.splice(obj, start, delete_count, vals) .splice(&obj, start, delete_count, vals)
.map_err(to_js_err)?; .map_err(to_js_err)?;
} }
Ok(()) Ok(())
@ -223,9 +223,12 @@ impl Automerge {
let value = self.import_value(value, datatype)?; let value = self.import_value(value, datatype)?;
let opid = self let opid = self
.0 .0
.insert(obj, index as usize, value) .insert(&obj, index as usize, value)
.map_err(to_js_err)?; .map_err(to_js_err)?;
Ok(self.export(opid)) match opid {
Some(opid) => Ok(self.export(opid)),
None => Ok(JsValue::null()),
}
} }
pub fn set( pub fn set(
@ -238,7 +241,7 @@ impl Automerge {
let obj = self.import(obj)?; let obj = self.import(obj)?;
let prop = self.import_prop(prop)?; let prop = self.import_prop(prop)?;
let value = self.import_value(value, datatype)?; 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 { match opid {
Some(opid) => Ok(self.export(opid)), Some(opid) => Ok(self.export(opid)),
None => Ok(JsValue::null()), None => Ok(JsValue::null()),
@ -252,7 +255,7 @@ impl Automerge {
.as_f64() .as_f64()
.ok_or("inc needs a numberic value") .ok_or("inc needs a numberic value")
.map_err(to_js_err)?; .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(()) Ok(())
} }
@ -263,9 +266,9 @@ impl Automerge {
let heads = get_heads(heads); let heads = get_heads(heads);
if let Ok(prop) = prop { if let Ok(prop) = prop {
let value = if let Some(h) = heads { let value = if let Some(h) = heads {
self.0.value_at(obj, prop, &h) self.0.value_at(&obj, prop, &h)
} else { } else {
self.0.value(obj, prop) self.0.value(&obj, prop)
} }
.map_err(to_js_err)?; .map_err(to_js_err)?;
match value { match value {
@ -289,9 +292,9 @@ impl Automerge {
let prop = to_prop(arg); let prop = to_prop(arg);
if let Ok(prop) = prop { if let Ok(prop) = prop {
let values = if let Some(heads) = get_heads(heads) { let values = if let Some(heads) = get_heads(heads) {
self.0.values_at(obj, prop, &heads) self.0.values_at(&obj, prop, &heads)
} else { } else {
self.0.values(obj, prop) self.0.values(&obj, prop)
} }
.map_err(to_js_err)?; .map_err(to_js_err)?;
for value in values { for value in values {
@ -318,16 +321,16 @@ impl Automerge {
pub fn length(&mut self, obj: JsValue, heads: JsValue) -> Result<JsValue, JsValue> { pub fn length(&mut self, obj: JsValue, heads: JsValue) -> Result<JsValue, JsValue> {
let obj = self.import(obj)?; let obj = self.import(obj)?;
if let Some(heads) = get_heads(heads) { 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 { } 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> { pub fn del(&mut self, obj: JsValue, prop: JsValue) -> Result<(), JsValue> {
let obj = self.import(obj)?; let obj = self.import(obj)?;
let prop = to_prop(prop)?; 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(()) Ok(())
} }
@ -442,11 +445,11 @@ impl Automerge {
} }
} }
fn export<E: automerge::Exportable>(&self, val: E) -> JsValue { fn export(&self, val: ExId) -> JsValue {
self.0.export(val).into() val.to_string().into()
} }
fn import<I: automerge::Importable>(&self, id: JsValue) -> Result<I, JsValue> { fn import(&self, id: JsValue) -> Result<ExId, JsValue> {
let id_str = id let id_str = id
.as_string() .as_string()
.ok_or("invalid opid/objid/elemid") .ok_or("invalid opid/objid/elemid")

View file

@ -8,7 +8,6 @@ use crate::encoding::{Encodable, DEFLATE_MIN_SIZE};
use crate::legacy as amp; use crate::legacy as amp;
use crate::{ use crate::{
ActorId, AutomergeError, ElemId, IndexedCache, Key, ObjId, Op, OpId, OpType, Transaction, HEAD, ActorId, AutomergeError, ElemId, IndexedCache, Key, ObjId, Op, OpId, OpType, Transaction, HEAD,
ROOT,
}; };
use core::ops::Range; use core::ops::Range;
use flate2::{ use flate2::{
@ -417,7 +416,7 @@ fn increment_range_map(ranges: &mut HashMap<u32, Range<usize>>, len: usize) {
} }
fn export_objid(id: &ObjId, actors: &IndexedCache<ActorId>) -> amp::ObjectId { fn export_objid(id: &ObjId, actors: &IndexedCache<ActorId>) -> amp::ObjectId {
if id.0 == ROOT { if id == &ObjId::root() {
amp::ObjectId::Root amp::ObjectId::Root
} else { } else {
export_opid(&id.0, actors).into() export_opid(&id.0, actors).into()

View file

@ -11,7 +11,6 @@ use std::{
str, str,
}; };
use crate::ROOT;
use crate::{ActorId, ElemId, Key, ObjId, ObjType, OpId, OpType, ScalarValue}; use crate::{ActorId, ElemId, Key, ObjId, ObjType, OpId, OpType, ScalarValue};
use crate::legacy as amp; use crate::legacy as amp;
@ -846,7 +845,7 @@ impl ObjEncoder {
fn append(&mut self, obj: &ObjId, actors: &[usize]) { fn append(&mut self, obj: &ObjId, actors: &[usize]) {
match obj.0 { match obj.0 {
ROOT => { OpId(ctr, _) if ctr == 0 => {
self.actor.append_null(); self.actor.append_null();
self.ctr.append_null(); self.ctr.append_null();
} }
@ -951,7 +950,7 @@ impl ChangeEncoder {
index_by_hash.insert(hash, index); index_by_hash.insert(hash, index);
} }
self.actor 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); self.seq.append_value(change.seq);
// FIXME iterops.count is crazy slow // FIXME iterops.count is crazy slow
self.max_op self.max_op

View file

@ -17,6 +17,8 @@ pub enum AutomergeError {
InvalidSeq(u64), InvalidSeq(u64),
#[error("index {0} is out of bounds")] #[error("index {0} is out of bounds")]
InvalidIndex(usize), InvalidIndex(usize),
#[error("generic automerge error")]
Fail,
} }
impl From<std::io::Error> for AutomergeError { impl From<std::io::Error> for AutomergeError {

View file

@ -2,10 +2,11 @@ use itertools::Itertools;
use std::collections::HashMap; use std::collections::HashMap;
use std::hash::Hash; use std::hash::Hash;
use std::ops::Index; use std::ops::Index;
use std::rc::Rc;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct IndexedCache<T> { pub(crate) struct IndexedCache<T> {
pub cache: Vec<T>, pub cache: Vec<Rc<T>>,
lookup: HashMap<T, usize>, lookup: HashMap<T, usize>,
} }
@ -25,14 +26,14 @@ where
*n *n
} else { } else {
let n = self.cache.len(); let n = self.cache.len();
self.cache.push(item.clone()); self.cache.push(Rc::new(item.clone()));
self.lookup.insert(item, n); self.lookup.insert(item, n);
n n
} }
} }
pub fn lookup(&self, item: T) -> Option<usize> { pub fn lookup(&self, item: &T) -> Option<usize> {
self.lookup.get(&item).cloned() self.lookup.get(item).cloned()
} }
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
@ -48,7 +49,7 @@ where
self.cache.iter().sorted().cloned().for_each(|item| { self.cache.iter().sorted().cloned().for_each(|item| {
let n = sorted.cache.len(); let n = sorted.cache.len();
sorted.cache.push(item.clone()); sorted.cache.push(item.clone());
sorted.lookup.insert(item, n); sorted.lookup.insert(item.as_ref().clone(), n);
}); });
sorted sorted
} }
@ -63,7 +64,7 @@ where
} }
impl<T> IntoIterator for IndexedCache<T> { impl<T> IntoIterator for IndexedCache<T> {
type Item = T; type Item = Rc<T>;
type IntoIter = std::vec::IntoIter<Self::Item>; type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {

View file

@ -46,12 +46,14 @@ mod query;
mod types; mod types;
mod value; mod value;
use std::rc::Rc;
use std::fmt;
use change::{encode_document, export_change}; use change::{encode_document, export_change};
use clock::Clock; use clock::Clock;
use indexed_cache::IndexedCache; use indexed_cache::IndexedCache;
use op_set::OpSet; use op_set::OpSet;
use std::collections::{HashMap, HashSet, VecDeque}; 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; use unicode_segmentation::UnicodeSegmentation;
pub use change::{decode_change, Change}; pub use change::{decode_change, Change};
@ -59,8 +61,7 @@ pub use error::AutomergeError;
pub use legacy::Change as ExpandedChange; pub use legacy::Change as ExpandedChange;
pub use sync::{BloomFilter, SyncHave, SyncMessage, SyncState}; pub use sync::{BloomFilter, SyncHave, SyncMessage, SyncState};
pub use types::{ pub use types::{
ActorId, ChangeHash, Export, Exportable, Importable, ObjType, OpId, OpType, Patch, Peer, Prop, ActorId, ChangeHash, ObjType, OpType, Patch, Peer, Prop,
ROOT,
}; };
pub use value::{ScalarValue, Value}; pub use value::{ScalarValue, Value};
@ -78,6 +79,35 @@ pub struct Automerge {
transaction: Option<Transaction>, transaction: Option<Transaction>,
} }
#[derive(Debug, Clone)]
pub enum ExId {
Root,
Id(u64, Rc<ActorId>, 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 { impl Automerge {
pub fn new() -> Self { pub fn new() -> Self {
Automerge { Automerge {
@ -262,24 +292,40 @@ impl Automerge {
// PropAt::() // PropAt::()
// NthAt::() // NthAt::()
pub fn keys(&self, obj: OpId) -> Vec<String> { pub fn keys(&self, obj: &ExId) -> Vec<String> {
let q = self.ops.search(obj.into(), query::Keys::new()); if let Ok(obj) = self.exid_to_obj(obj) {
q.keys.iter().map(|k| self.export(*k)).collect() 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<String> { pub fn keys_at(&self, obj: &ExId, heads: &[ChangeHash]) -> Vec<String> {
let clock = self.clock_at(heads); if let Ok(obj) = self.exid_to_obj(obj) {
let q = self.ops.search(obj.into(), query::KeysAt::new(clock)); let clock = self.clock_at(heads);
q.keys.iter().map(|k| self.export(*k)).collect() 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 { pub fn length(&self, obj: &ExId) -> usize {
self.ops.search(obj.into(), query::Len::new(obj.into())).len 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 { pub fn length_at(&self, obj: &ExId, heads: &[ChangeHash]) -> usize {
let clock = self.clock_at(heads); if let Ok(obj) = self.exid_to_obj(obj) {
self.ops.search(obj.into(), query::LenAt::new(clock)).len 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 // set(obj, prop, value) - value can be scalar or objtype
@ -302,32 +348,73 @@ impl Automerge {
/// - The key does not exist in the object /// - The key does not exist in the object
pub fn set<P: Into<Prop>, V: Into<Value>>( pub fn set<P: Into<Prop>, V: Into<Value>>(
&mut self, &mut self,
obj: OpId, obj: &ExId,
prop: P, prop: P,
value: V, value: V,
) -> Result<Option<OpId>, AutomergeError> { ) -> Result<Option<ExId>, AutomergeError> {
let obj = self.exid_to_obj(obj)?;
let value = value.into(); 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<ObjId,AutomergeError> {
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<V: Into<Value>>( pub fn insert<V: Into<Value>>(
&mut self, &mut self,
obj: OpId, obj: &ExId,
index: usize, index: usize,
value: V, value: V,
) -> Result<OpId, AutomergeError> { ) -> Result<Option<ExId>, AutomergeError> {
let obj = obj.into(); 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<V: Into<Value>>(
&mut self,
obj: ObjId,
index: usize,
value: V,
) -> Result<Option<OpId>, AutomergeError> {
let id = self.next_id(); let id = self.next_id();
let query = self.ops.search(obj, query::InsertNth::new(index)); let query = self.ops.search(obj, query::InsertNth::new(index));
let key = query.key()?; let key = query.key()?;
let value = value.into(); let value = value.into();
let action = value.into();
let is_make = matches!(&action, OpType::Make(_));
let op = Op { let op = Op {
change: self.history.len(), change: self.history.len(),
id, id,
action: value.into(), action,
obj, obj,
key, key,
succ: Default::default(), succ: Default::default(),
@ -338,60 +425,63 @@ impl Automerge {
self.ops.insert(query.pos, op.clone()); self.ops.insert(query.pos, op.clone());
self.tx().operations.push(op); self.tx().operations.push(op);
Ok(id) if is_make {
Ok(Some(id))
} else {
Ok(None)
}
} }
pub fn inc<P: Into<Prop>>( pub fn inc<P: Into<Prop>>(
&mut self, &mut self,
obj: OpId, obj: &ExId,
prop: P, prop: P,
value: i64, value: i64,
) -> Result<OpId, AutomergeError> { ) -> Result<(), AutomergeError> {
match self.local_op(obj.into(), prop.into(), OpType::Inc(value))? { let obj = self.exid_to_obj(obj)?;
Some(opid) => Ok(opid), self.local_op(obj.into(), prop.into(), OpType::Inc(value))?;
None => { Ok(())
panic!("increment should always create a new op")
}
}
} }
pub fn del<P: Into<Prop>>(&mut self, obj: OpId, prop: P) -> Result<OpId, AutomergeError> { pub fn del<P: Into<Prop>>(&mut self, obj: &ExId, prop: P) -> Result<(), AutomergeError> {
// TODO: Should we also no-op multiple delete operations? let obj = self.exid_to_obj(obj)?;
match self.local_op(obj.into(), prop.into(), OpType::Del)? { self.local_op(obj.into(), prop.into(), OpType::Del)?;
Some(opid) => Ok(opid), Ok(())
None => {
panic!("delete should always create a new op")
}
}
} }
/// Splice new elements into the given sequence. Returns a vector of the OpIds used to insert /// Splice new elements into the given sequence. Returns a vector of the OpIds used to insert
/// the new elements /// the new elements
pub fn splice( pub fn splice(
&mut self, &mut self,
obj: OpId, obj: &ExId,
mut pos: usize, mut pos: usize,
del: usize, del: usize,
vals: Vec<Value>, vals: Vec<Value>,
) -> Result<Vec<OpId>, AutomergeError> { ) -> Result<Vec<ExId>, AutomergeError> {
let obj = self.exid_to_obj(obj)?;
for _ in 0..del { 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 { 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; pos += 1;
} }
Ok(result) Ok(results)
} }
pub fn splice_text( pub fn splice_text(
&mut self, & mut self,
obj: OpId, obj: &ExId,
pos: usize, pos: usize,
del: usize, del: usize,
text: &str, text: &str,
) -> Result<Vec<OpId>, AutomergeError> { ) -> Result<Vec<ExId>, AutomergeError> {
let mut vals = vec![]; let mut vals = vec![];
for c in text.to_owned().graphemes(true) { for c in text.to_owned().graphemes(true) {
vals.push(c.into()); vals.push(c.into());
@ -399,8 +489,8 @@ impl Automerge {
self.splice(obj, pos, del, vals) self.splice(obj, pos, del, vals)
} }
pub fn text(&self, obj: OpId) -> Result<String, AutomergeError> { pub fn text(&self, obj: &ExId) -> Result<String, AutomergeError> {
let obj = obj.into(); let obj = self.exid_to_obj(obj)?;
let query = self.ops.search(obj, query::ListVals::new(obj)); let query = self.ops.search(obj, query::ListVals::new(obj));
let mut buffer = String::new(); let mut buffer = String::new();
for q in &query.ops { for q in &query.ops {
@ -411,9 +501,9 @@ impl Automerge {
Ok(buffer) Ok(buffer)
} }
pub fn text_at(&self, obj: OpId, heads: &[ChangeHash]) -> Result<String, AutomergeError> { pub fn text_at(&self, obj: &ExId, heads: &[ChangeHash]) -> Result<String, AutomergeError> {
let obj = self.exid_to_obj(obj)?;
let clock = self.clock_at(heads); let clock = self.clock_at(heads);
let obj = obj.into();
let query = self.ops.search(obj, query::ListValsAt::new(clock)); let query = self.ops.search(obj, query::ListValsAt::new(clock));
let mut buffer = String::new(); let mut buffer = String::new();
for q in &query.ops { for q in &query.ops {
@ -429,36 +519,36 @@ impl Automerge {
// Something better? // Something better?
pub fn value<P: Into<Prop>>( pub fn value<P: Into<Prop>>(
&self, &self,
obj: OpId, obj: &ExId,
prop: P, prop: P,
) -> Result<Option<(Value, OpId)>, AutomergeError> { ) -> Result<Option<(Value, ExId)>, AutomergeError> {
Ok(self.values(obj, prop.into())?.first().cloned()) Ok(self.values(obj, prop.into())?.first().cloned())
} }
pub fn value_at<P: Into<Prop>>( pub fn value_at<P: Into<Prop>>(
&self, &self,
obj: OpId, obj: &ExId,
prop: P, prop: P,
heads: &[ChangeHash], heads: &[ChangeHash],
) -> Result<Option<(Value, OpId)>, AutomergeError> { ) -> Result<Option<(Value, ExId)>, AutomergeError> {
Ok(self.values_at(obj, prop, heads)?.first().cloned()) Ok(self.values_at(obj, prop, heads)?.first().cloned())
} }
pub fn values<P: Into<Prop>>( pub fn values<P: Into<Prop>>(
&self, &self,
obj: OpId, obj: &ExId,
prop: P, prop: P,
) -> Result<Vec<(Value, OpId)>, AutomergeError> { ) -> Result<Vec<(Value, ExId)>, AutomergeError> {
let obj = obj.into(); let obj = self.exid_to_obj(obj)?;
let result = match prop.into() { let result = match prop.into() {
Prop::Map(p) => { Prop::Map(p) => {
let prop = self.ops.m.props.lookup(p); let prop = self.ops.m.props.lookup(&p);
if let Some(p) = prop { if let Some(p) = prop {
self.ops self.ops
.search(obj, query::Prop::new(obj, p)) .search(obj, query::Prop::new(obj, p))
.ops .ops
.into_iter() .into_iter()
.map(|o| o.into()) .map(|o| (o.value(), self.id_to_exid(o.id)))
.collect() .collect()
} else { } else {
vec![] vec![]
@ -469,7 +559,7 @@ impl Automerge {
.search(obj, query::Nth::new(n)) .search(obj, query::Nth::new(n))
.ops .ops
.into_iter() .into_iter()
.map(|o| o.into()) .map(|o| (o.value(), self.id_to_exid(o.id)))
.collect(), .collect(),
}; };
Ok(result) Ok(result)
@ -477,22 +567,22 @@ impl Automerge {
pub fn values_at<P: Into<Prop>>( pub fn values_at<P: Into<Prop>>(
&self, &self,
obj: OpId, obj: &ExId,
prop: P, prop: P,
heads: &[ChangeHash], heads: &[ChangeHash],
) -> Result<Vec<(Value, OpId)>, AutomergeError> { ) -> Result<Vec<(Value, ExId)>, AutomergeError> {
let prop = prop.into(); let prop = prop.into();
let obj = obj.into(); let obj = self.exid_to_obj(obj)?;
let clock = self.clock_at(heads); let clock = self.clock_at(heads);
let result = match prop { let result = match prop {
Prop::Map(p) => { Prop::Map(p) => {
let prop = self.ops.m.props.lookup(p); let prop = self.ops.m.props.lookup(&p);
if let Some(p) = prop { if let Some(p) = prop {
self.ops self.ops
.search(obj, query::PropAt::new(p, clock)) .search(obj, query::PropAt::new(p, clock))
.ops .ops
.into_iter() .into_iter()
.map(|o| o.into()) .map(|o| (o.value(), self.id_to_exid(o.id)))
.collect() .collect()
} else { } else {
vec![] vec![]
@ -503,7 +593,7 @@ impl Automerge {
.search(obj, query::NthAt::new(n, clock)) .search(obj, query::NthAt::new(n, clock))
.ops .ops
.into_iter() .into_iter()
.map(|o| o.into()) .map(|o| (o.value(), self.id_to_exid(o.id)))
.collect(), .collect(),
}; };
Ok(result) Ok(result)
@ -576,21 +666,12 @@ impl Automerge {
let prop = self.ops.m.props.cache(prop); let prop = self.ops.m.props.cache(prop);
let query = self.ops.search(obj, query::Prop::new(obj, prop)); let query = self.ops.search(obj, query::Prop::new(obj, prop));
match (&query.ops[..], &action) { if query.ops.len() == 1 && query.ops[0].is_noop(&action) {
// If there are no conflicts for this value and the old operation and the new operation are return Ok(None);
// 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);
}
_ => {}
} }
let is_make = matches!(&action, OpType::Make(_));
let pred = query.ops.iter().map(|op| op.id).collect(); let pred = query.ops.iter().map(|op| op.id).collect();
let op = Op { let op = Op {
@ -606,7 +687,11 @@ impl Automerge {
self.insert_local_op(op, query.pos, &query.ops_pos); 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( fn local_list_op(
@ -621,21 +706,12 @@ impl Automerge {
let pred = query.ops.iter().map(|op| op.id).collect(); let pred = query.ops.iter().map(|op| op.id).collect();
let key = query.key()?; let key = query.key()?;
match (&query.ops[..], &action) { if query.ops.len() == 1 && query.ops[0].is_noop(&action) {
// If there are no conflicts for this value and the old operation and the new operation are return Ok(None);
// 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);
}
_ => {}
} }
let is_make = matches!(&action, OpType::Make(_));
let op = Op { let op = Op {
change: self.history.len(), change: self.history.len(),
id, id,
@ -649,7 +725,11 @@ impl Automerge {
self.insert_local_op(op, query.pos, &query.ops_pos); 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 { fn is_causally_ready(&self, change: &Change) -> bool {
@ -677,20 +757,19 @@ impl Automerge {
.map(|(i, c)| { .map(|(i, c)| {
let actor = self.ops.m.actors.cache(change.actor_id().clone()); let actor = self.ops.m.actors.cache(change.actor_id().clone());
let id = OpId(change.start_op + i as u64, actor); let id = OpId(change.start_op + i as u64, actor);
// FIXME dont need to_string() let obj = match c.obj {
let obj: ObjId = self.import(&c.obj.to_string()).unwrap(); legacy::ObjectId::Root => ObjId::root(),
legacy::ObjectId::Id(id) => ObjId(OpId(id.0, self.ops.m.actors.cache(id.1))),
};
let pred = c let pred = c
.pred .pred
.iter() .iter()
.map(|i| self.import(&i.to_string()).unwrap()) .map(|i| OpId(i.0, self.ops.m.actors.cache(i.1.clone())))
.collect(); .collect();
let key = match &c.key { 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.props.cache(n.to_string())),
legacy::Key::Seq(legacy::ElementId::Head) => Key::Seq(HEAD), legacy::Key::Seq(legacy::ElementId::Head) => Key::Seq(HEAD),
// FIXME dont need to_string() legacy::Key::Seq(legacy::ElementId::Id(i)) => Key::Seq(ElemId(OpId(i.0, self.ops.m.actors.cache(i.1.clone()))))
legacy::Key::Seq(legacy::ElementId::Id(i)) => {
Key::Seq(self.import(&i.to_string()).unwrap())
}
}; };
Op { Op {
change: change_id, change: change_id,
@ -724,11 +803,13 @@ impl Automerge {
let c: Vec<_> = self.history.iter().map(|c| c.decode()).collect(); let c: Vec<_> = self.history.iter().map(|c| c.decode()).collect();
let ops: Vec<_> = self.ops.iter().cloned().collect(); let ops: Vec<_> = self.ops.iter().cloned().collect();
// TODO - can we make encode_document error free // 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( let bytes = encode_document(
&c, &c,
ops.as_slice(), ops.as_slice(),
&self.ops.m.actors, &self.ops.m.actors,
&self.ops.m.props.cache, &tmp
); );
if bytes.is_ok() { if bytes.is_ok() {
self.saved = self.get_heads().iter().copied().collect(); self.saved = self.get_heads().iter().copied().collect();
@ -930,7 +1011,7 @@ impl Automerge {
to_see.push(*h); 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()); clock.include(actor, c.max_op());
seen.insert(hash); seen.insert(hash);
} }
@ -1023,9 +1104,9 @@ impl Automerge {
self.deps.insert(change.hash); self.deps.insert(change.hash);
} }
pub fn import<I: Importable>(&self, s: &str) -> Result<I, AutomergeError> { pub fn import(&self, s: &str) -> Result<ExId, AutomergeError> {
if let Some(x) = I::from(s) { if s == "_root" {
Ok(x) Ok(ExId::Root)
} else { } else {
let n = s let n = s
.find('@') .find('@')
@ -1038,13 +1119,13 @@ impl Automerge {
.ops .ops
.m .m
.actors .actors
.lookup(actor) .lookup(&actor)
.ok_or_else(|| AutomergeError::InvalidOpId(s.to_owned()))?; .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<E: Exportable>(&self, id: E) -> String { fn to_string<E: Exportable>(&self, id: E) -> String {
match id.export() { match id.export() {
Export::Id(id) => format!("{}@{}", id.counter(), self.ops.m.actors[id.actor()]), Export::Id(id) => format!("{}@{}", id.counter(), self.ops.m.actors[id.actor()]),
Export::Prop(index) => self.ops.m.props[index].clone(), Export::Prop(index) => self.ops.m.props[index].clone(),
@ -1063,11 +1144,11 @@ impl Automerge {
"succ" "succ"
); );
for i in self.ops.iter() { for i in self.ops.iter() {
let id = self.export(i.id); let id = self.to_string(i.id);
let obj = self.export(i.obj); let obj = self.to_string(i.obj);
let key = match i.key { let key = match i.key {
Key::Map(n) => self.ops.m.props[n].clone(), 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 { let value: String = match &i.action {
OpType::Set(value) => format!("{}", value), OpType::Set(value) => format!("{}", value),
@ -1075,8 +1156,8 @@ impl Automerge {
OpType::Inc(obj) => format!("inc{}", obj), OpType::Inc(obj) => format!("inc{}", obj),
OpType::Del => format!("del{}", 0), OpType::Del => format!("del{}", 0),
}; };
let pred: Vec<_> = i.pred.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.export(*id)).collect(); let succ: Vec<_> = i.succ.iter().map(|id| self.to_string(*id)).collect();
log!( log!(
" {:12} {:12} {:12} {} {:?} {:?}", " {:12} {:12} {:12} {} {:?} {:?}",
id, id,
@ -1123,9 +1204,9 @@ mod tests {
fn insert_op() -> Result<(), AutomergeError> { fn insert_op() -> Result<(), AutomergeError> {
let mut doc = Automerge::new(); let mut doc = Automerge::new();
doc.set_actor(ActorId::random()); doc.set_actor(ActorId::random());
doc.set(ROOT, "hello", "world")?; doc.set(&ROOT, "hello", "world")?;
assert!(doc.pending_ops() == 1); assert!(doc.pending_ops() == 1);
doc.value(ROOT, "hello")?; doc.value(&ROOT, "hello")?;
Ok(()) Ok(())
} }
@ -1133,18 +1214,18 @@ mod tests {
fn test_list() -> Result<(), AutomergeError> { fn test_list() -> Result<(), AutomergeError> {
let mut doc = Automerge::new(); let mut doc = Automerge::new();
doc.set_actor(ActorId::random()); doc.set_actor(ActorId::random());
let list_id = doc.set(ROOT, "items", Value::list())?.unwrap(); let list_id = doc.set(&ROOT, "items", Value::list())?.unwrap();
doc.set(ROOT, "zzz", "zzzval")?; doc.set(&ROOT, "zzz", "zzzval")?;
assert!(doc.value(ROOT, "items")?.unwrap().1 == list_id); assert!(doc.value(&ROOT, "items")?.unwrap().1 == list_id);
doc.insert(list_id, 0, "a")?; doc.insert(&list_id, 0, "a")?;
doc.insert(list_id, 0, "b")?; doc.insert(&list_id, 0, "b")?;
doc.insert(list_id, 2, "c")?; doc.insert(&list_id, 2, "c")?;
doc.insert(list_id, 1, "d")?; doc.insert(&list_id, 1, "d")?;
assert!(doc.value(list_id, 0)?.unwrap().0 == "b".into()); 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, 1)?.unwrap().0 == "d".into());
assert!(doc.value(list_id, 2)?.unwrap().0 == "a".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, 3)?.unwrap().0 == "c".into());
assert!(doc.length(list_id) == 4); assert!(doc.length(&list_id) == 4);
doc.save()?; doc.save()?;
Ok(()) Ok(())
} }
@ -1153,22 +1234,22 @@ mod tests {
fn test_del() -> Result<(), AutomergeError> { fn test_del() -> Result<(), AutomergeError> {
let mut doc = Automerge::new(); let mut doc = Automerge::new();
doc.set_actor(ActorId::random()); doc.set_actor(ActorId::random());
doc.set(ROOT, "xxx", "xxx")?; doc.set(&ROOT, "xxx", "xxx")?;
assert!(!doc.values(ROOT, "xxx")?.is_empty()); assert!(!doc.values(&ROOT, "xxx")?.is_empty());
doc.del(ROOT, "xxx")?; doc.del(&ROOT, "xxx")?;
assert!(doc.values(ROOT, "xxx")?.is_empty()); assert!(doc.values(&ROOT, "xxx")?.is_empty());
Ok(()) Ok(())
} }
#[test] #[test]
fn test_inc() -> Result<(), AutomergeError> { fn test_inc() -> Result<(), AutomergeError> {
let mut doc = Automerge::new(); let mut doc = Automerge::new();
let id = doc.set(ROOT, "counter", Value::counter(10))?.unwrap(); doc.set(&ROOT, "counter", Value::counter(10))?;
assert!(doc.value(ROOT, "counter")? == Some((Value::counter(10), id))); assert!(doc.value(&ROOT, "counter")?.unwrap().0 == Value::counter(10));
doc.inc(ROOT, "counter", 10)?; doc.inc(&ROOT, "counter", 10)?;
assert!(doc.value(ROOT, "counter")? == Some((Value::counter(20), id))); assert!(doc.value(&ROOT, "counter")?.unwrap().0 == Value::counter(20));
doc.inc(ROOT, "counter", -5)?; doc.inc(&ROOT, "counter", -5)?;
assert!(doc.value(ROOT, "counter")? == Some((Value::counter(15), id))); assert!(doc.value(&ROOT, "counter")?.unwrap().0 == Value::counter(15));
Ok(()) Ok(())
} }
@ -1176,15 +1257,15 @@ mod tests {
fn test_save_incremental() -> Result<(), AutomergeError> { fn test_save_incremental() -> Result<(), AutomergeError> {
let mut doc = Automerge::new(); let mut doc = Automerge::new();
doc.set(ROOT, "foo", 1)?; doc.set(&ROOT, "foo", 1)?;
let save1 = doc.save().unwrap(); let save1 = doc.save().unwrap();
doc.set(ROOT, "bar", 2)?; doc.set(&ROOT, "bar", 2)?;
let save2 = doc.save_incremental(); let save2 = doc.save_incremental();
doc.set(ROOT, "baz", 3)?; doc.set(&ROOT, "baz", 3)?;
let save3 = doc.save_incremental(); let save3 = doc.save_incremental();
@ -1202,7 +1283,7 @@ mod tests {
let mut doc_a = Automerge::load(&save_a)?; let mut doc_a = Automerge::load(&save_a)?;
let mut doc_b = Automerge::load(&save_b)?; 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()); assert!(doc_a.save().unwrap() == doc_b.save().unwrap());
@ -1212,17 +1293,17 @@ mod tests {
#[test] #[test]
fn test_save_text() -> Result<(), AutomergeError> { fn test_save_text() -> Result<(), AutomergeError> {
let mut doc = Automerge::new(); 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); 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); 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); let heads3 = doc.commit(None, None);
assert!(&doc.text(text)? == "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, &heads1)?.is_empty());
assert!(&doc.text_at(text, &heads2)? == "hello world"); assert!(&doc.text_at(&text, &heads2)? == "hello world");
assert!(&doc.text_at(text, &heads3)? == "hello big bad world"); assert!(&doc.text_at(&text, &heads3)? == "hello big bad world");
Ok(()) Ok(())
} }
@ -1231,50 +1312,50 @@ mod tests {
fn test_props_vals_at() -> Result<(), AutomergeError> { fn test_props_vals_at() -> Result<(), AutomergeError> {
let mut doc = Automerge::new(); let mut doc = Automerge::new();
doc.set_actor("aaaa".try_into().unwrap()); doc.set_actor("aaaa".try_into().unwrap());
doc.set(ROOT, "prop1", "val1")?; doc.set(&ROOT, "prop1", "val1")?;
doc.commit(None, None); doc.commit(None, None);
let heads1 = doc.get_heads(); let heads1 = doc.get_heads();
doc.set(ROOT, "prop1", "val2")?; doc.set(&ROOT, "prop1", "val2")?;
doc.commit(None, None); doc.commit(None, None);
let heads2 = doc.get_heads(); let heads2 = doc.get_heads();
doc.set(ROOT, "prop2", "val3")?; doc.set(&ROOT, "prop2", "val3")?;
doc.commit(None, None); doc.commit(None, None);
let heads3 = doc.get_heads(); let heads3 = doc.get_heads();
doc.del(ROOT, "prop1")?; doc.del(&ROOT, "prop1")?;
doc.commit(None, None); doc.commit(None, None);
let heads4 = doc.get_heads(); let heads4 = doc.get_heads();
doc.set(ROOT, "prop3", "val4")?; doc.set(&ROOT, "prop3", "val4")?;
doc.commit(None, None); doc.commit(None, None);
let heads5 = doc.get_heads(); let heads5 = doc.get_heads();
assert!(doc.keys_at(ROOT, &heads1) == vec!["prop1".to_owned()]); 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, "prop1", &heads1)?.unwrap().0 == Value::str("val1"));
assert!(doc.value_at(ROOT, "prop2", &heads1)? == None); assert!(doc.value_at(&ROOT, "prop2", &heads1)? == None);
assert!(doc.value_at(ROOT, "prop3", &heads1)? == None); assert!(doc.value_at(&ROOT, "prop3", &heads1)? == None);
assert!(doc.keys_at(ROOT, &heads2) == vec!["prop1".to_owned()]); 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, "prop1", &heads2)?.unwrap().0 == Value::str("val2"));
assert!(doc.value_at(ROOT, "prop2", &heads2)? == None); assert!(doc.value_at(&ROOT, "prop2", &heads2)? == None);
assert!(doc.value_at(ROOT, "prop3", &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.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, "prop1", &heads3)?.unwrap().0 == Value::str("val2"));
assert!(doc.value_at(ROOT, "prop2", &heads3)?.unwrap().0 == Value::str("val3")); assert!(doc.value_at(&ROOT, "prop2", &heads3)?.unwrap().0 == Value::str("val3"));
assert!(doc.value_at(ROOT, "prop3", &heads3)? == None); assert!(doc.value_at(&ROOT, "prop3", &heads3)? == None);
assert!(doc.keys_at(ROOT, &heads4) == vec!["prop2".to_owned()]); assert!(doc.keys_at(&ROOT, &heads4) == vec!["prop2".to_owned()]);
assert!(doc.value_at(ROOT, "prop1", &heads4)? == None); 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, "prop2", &heads4)?.unwrap().0 == Value::str("val3"));
assert!(doc.value_at(ROOT, "prop3", &heads4)? == None); assert!(doc.value_at(&ROOT, "prop3", &heads4)? == None);
assert!(doc.keys_at(ROOT, &heads5) == vec!["prop2".to_owned(), "prop3".to_owned()]); 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, "prop1", &heads5)? == None);
assert!(doc.value_at(ROOT, "prop2", &heads5)?.unwrap().0 == Value::str("val3")); 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.value_at(&ROOT, "prop3", &heads5)?.unwrap().0 == Value::str("val4"));
assert!(doc.keys_at(ROOT, &[]).is_empty()); assert!(doc.keys_at(&ROOT, &[]).is_empty());
assert!(doc.value_at(ROOT, "prop1", &[])? == None); assert!(doc.value_at(&ROOT, "prop1", &[])? == None);
assert!(doc.value_at(ROOT, "prop2", &[])? == None); assert!(doc.value_at(&ROOT, "prop2", &[])? == None);
assert!(doc.value_at(ROOT, "prop3", &[])? == None); assert!(doc.value_at(&ROOT, "prop3", &[])? == None);
Ok(()) Ok(())
} }
@ -1283,47 +1364,47 @@ mod tests {
let mut doc = Automerge::new(); let mut doc = Automerge::new();
doc.set_actor("aaaa".try_into().unwrap()); 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); 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); let heads2 = doc.commit(None, None);
doc.set(list, 0, Value::int(20))?; doc.set(&list, 0, Value::int(20))?;
doc.insert(list, 0, Value::int(30))?; doc.insert(&list, 0, Value::int(30))?;
let heads3 = doc.commit(None, None); let heads3 = doc.commit(None, None);
doc.set(list, 1, Value::int(40))?; doc.set(&list, 1, Value::int(40))?;
doc.insert(list, 1, Value::int(50))?; doc.insert(&list, 1, Value::int(50))?;
let heads4 = doc.commit(None, None); let heads4 = doc.commit(None, None);
doc.del(list, 2)?; doc.del(&list, 2)?;
let heads5 = doc.commit(None, None); let heads5 = doc.commit(None, None);
doc.del(list, 0)?; doc.del(&list, 0)?;
let heads6 = doc.commit(None, None); let heads6 = doc.commit(None, None);
assert!(doc.length_at(list, &heads1) == 0); assert!(doc.length_at(&list, &heads1) == 0);
assert!(doc.value_at(list, 0, &heads1)?.is_none()); assert!(doc.value_at(&list, 0, &heads1)?.is_none());
assert!(doc.length_at(list, &heads2) == 1); assert!(doc.length_at(&list, &heads2) == 1);
assert!(doc.value_at(list, 0, &heads2)?.unwrap().0 == Value::int(10)); assert!(doc.value_at(&list, 0, &heads2)?.unwrap().0 == Value::int(10));
assert!(doc.length_at(list, &heads3) == 2); assert!(doc.length_at(&list, &heads3) == 2);
assert!(doc.value_at(list, 0, &heads3)?.unwrap().0 == Value::int(30)); 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.value_at(&list, 1, &heads3)?.unwrap().0 == Value::int(20));
assert!(doc.length_at(list, &heads4) == 3); assert!(doc.length_at(&list, &heads4) == 3);
assert!(doc.value_at(list, 0, &heads4)?.unwrap().0 == Value::int(30)); 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, 1, &heads4)?.unwrap().0 == Value::int(50));
assert!(doc.value_at(list, 2, &heads4)?.unwrap().0 == Value::int(40)); assert!(doc.value_at(&list, 2, &heads4)?.unwrap().0 == Value::int(40));
assert!(doc.length_at(list, &heads5) == 2); assert!(doc.length_at(&list, &heads5) == 2);
assert!(doc.value_at(list, 0, &heads5)?.unwrap().0 == Value::int(30)); 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.value_at(&list, 1, &heads5)?.unwrap().0 == Value::int(50));
assert!(doc.length_at(list, &heads6) == 1); assert!(doc.length_at(&list, &heads6) == 1);
assert!(doc.value_at(list, 0, &heads6)?.unwrap().0 == Value::int(50)); assert!(doc.value_at(&list, 0, &heads6)?.unwrap().0 == Value::int(50));
Ok(()) Ok(())
} }

View file

@ -1,6 +1,6 @@
use crate::error; use crate::error;
use crate::legacy as amp; use crate::legacy as amp;
use crate::ScalarValue; use crate::{ Value, ScalarValue };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::cmp::Eq; use std::cmp::Eq;
use std::convert::TryFrom; use std::convert::TryFrom;
@ -10,7 +10,7 @@ use std::str::FromStr;
use tinyvec::{ArrayVec, TinyVec}; use tinyvec::{ArrayVec, TinyVec};
pub(crate) const HEAD: ElemId = ElemId(OpId(0, 0)); 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 ROOT_STR: &str = "_root";
const HEAD_STR: &str = "_head"; const HEAD_STR: &str = "_head";
@ -161,23 +161,16 @@ pub enum OpType {
} }
#[derive(Debug)] #[derive(Debug)]
pub enum Export { pub(crate) enum Export {
Id(OpId), Id(OpId),
Special(String), Special(String),
Prop(usize), Prop(usize),
} }
pub trait Exportable { pub(crate) trait Exportable {
fn export(&self) -> Export; fn export(&self) -> Export;
} }
pub trait Importable {
fn wrap(id: OpId) -> Self;
fn from(s: &str) -> Option<Self>
where
Self: std::marker::Sized;
}
impl OpId { impl OpId {
#[inline] #[inline]
pub fn counter(&self) -> u64 { 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<Self> {
if s == ROOT_STR {
Some(ROOT.into())
} else {
None
}
}
}
impl Importable for OpId {
fn wrap(id: OpId) -> Self {
id
}
fn from(s: &str) -> Option<Self> {
if s == ROOT_STR {
Some(ROOT)
} else {
None
}
}
}
impl Importable for ElemId {
fn wrap(id: OpId) -> Self {
ElemId(id)
}
fn from(s: &str) -> Option<Self> {
if s == HEAD_STR {
Some(HEAD)
} else {
None
}
}
}
impl From<OpId> for ObjId { impl From<OpId> for ObjId {
fn from(o: OpId) -> Self { fn from(o: OpId) -> Self {
ObjId(o) ObjId(o)
@ -352,11 +306,17 @@ impl Key {
} }
#[derive(Debug, Clone, PartialOrd, Ord, Eq, PartialEq, Copy, Hash, Default)] #[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)] #[derive(Debug, Clone, Copy, PartialOrd, Eq, PartialEq, Ord, Hash, Default)]
pub(crate) struct ObjId(pub OpId); 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)] #[derive(Debug, Clone, Copy, PartialOrd, Eq, PartialEq, Ord, Hash, Default)]
pub(crate) struct ElemId(pub OpId); pub(crate) struct ElemId(pub OpId);
@ -374,7 +334,11 @@ pub(crate) struct Op {
impl Op { impl Op {
pub fn is_del(&self) -> bool { 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 { 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)] #[allow(dead_code)]
pub fn dump(&self) -> String { pub fn dump(&self) -> String {
match &self.action { match &self.action {

View file

@ -5,9 +5,9 @@ use std::fs;
fn replay_trace(commands: Vec<(usize, usize, Vec<Value>)>) -> Automerge { fn replay_trace(commands: Vec<(usize, usize, Vec<Value>)>) -> Automerge {
let mut doc = Automerge::new(); 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 { 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.commit(None, None);
doc doc

View file

@ -19,12 +19,12 @@ fn main() -> Result<(), AutomergeError> {
let mut doc = Automerge::new(); let mut doc = Automerge::new();
let now = Instant::now(); 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() { for (i, (pos, del, vals)) in commands.into_iter().enumerate() {
if i % 1000 == 0 { if i % 1000 == 0 {
println!("Processed {} edits in {} ms", i, now.elapsed().as_millis()); 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(); let _ = doc.save();
println!("Done in {} ms", now.elapsed().as_millis()); println!("Done in {} ms", now.elapsed().as_millis());