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;
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<Array, JsValue> {
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<JsValue, JsValue> {
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::<Array>() {
@ -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<JsValue, JsValue> {
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<E: automerge::Exportable>(&self, val: E) -> JsValue {
self.0.export(val).into()
fn export(&self, val: ExId) -> JsValue {
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
.as_string()
.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::{
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<u32, Range<usize>>, len: usize) {
}
fn export_objid(id: &ObjId, actors: &IndexedCache<ActorId>) -> amp::ObjectId {
if id.0 == ROOT {
if id == &ObjId::root() {
amp::ObjectId::Root
} else {
export_opid(&id.0, actors).into()

View file

@ -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

View file

@ -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<std::io::Error> for AutomergeError {

View file

@ -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<T> {
pub cache: Vec<T>,
pub cache: Vec<Rc<T>>,
lookup: HashMap<T, usize>,
}
@ -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<usize> {
self.lookup.get(&item).cloned()
pub fn lookup(&self, item: &T) -> Option<usize> {
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<T> IntoIterator for IndexedCache<T> {
type Item = T;
type Item = Rc<T>;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {

View file

@ -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<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 {
pub fn new() -> Self {
Automerge {
@ -262,24 +292,40 @@ impl Automerge {
// PropAt::()
// NthAt::()
pub fn keys(&self, obj: OpId) -> Vec<String> {
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<String> {
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<String> {
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<String> {
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<P: Into<Prop>, V: Into<Value>>(
&mut self,
obj: OpId,
obj: &ExId,
prop: P,
value: V,
) -> Result<Option<OpId>, AutomergeError> {
) -> Result<Option<ExId>, 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<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>>(
&mut self,
obj: OpId,
obj: &ExId,
index: usize,
value: V,
) -> Result<OpId, AutomergeError> {
let obj = obj.into();
) -> Result<Option<ExId>, 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<V: Into<Value>>(
&mut self,
obj: ObjId,
index: usize,
value: V,
) -> Result<Option<OpId>, 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<P: Into<Prop>>(
&mut self,
obj: OpId,
obj: &ExId,
prop: P,
value: i64,
) -> Result<OpId, AutomergeError> {
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<P: Into<Prop>>(&mut self, obj: OpId, prop: P) -> Result<OpId, AutomergeError> {
// 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<P: Into<Prop>>(&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<Value>,
) -> Result<Vec<OpId>, AutomergeError> {
) -> Result<Vec<ExId>, 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<Vec<OpId>, AutomergeError> {
) -> Result<Vec<ExId>, 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<String, AutomergeError> {
let obj = obj.into();
pub fn text(&self, obj: &ExId) -> Result<String, AutomergeError> {
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<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 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<P: Into<Prop>>(
&self,
obj: OpId,
obj: &ExId,
prop: P,
) -> Result<Option<(Value, OpId)>, AutomergeError> {
) -> Result<Option<(Value, ExId)>, AutomergeError> {
Ok(self.values(obj, prop.into())?.first().cloned())
}
pub fn value_at<P: Into<Prop>>(
&self,
obj: OpId,
obj: &ExId,
prop: P,
heads: &[ChangeHash],
) -> Result<Option<(Value, OpId)>, AutomergeError> {
) -> Result<Option<(Value, ExId)>, AutomergeError> {
Ok(self.values_at(obj, prop, heads)?.first().cloned())
}
pub fn values<P: Into<Prop>>(
&self,
obj: OpId,
obj: &ExId,
prop: P,
) -> Result<Vec<(Value, OpId)>, AutomergeError> {
let obj = obj.into();
) -> Result<Vec<(Value, ExId)>, 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<P: Into<Prop>>(
&self,
obj: OpId,
obj: &ExId,
prop: P,
heads: &[ChangeHash],
) -> Result<Vec<(Value, OpId)>, AutomergeError> {
) -> Result<Vec<(Value, ExId)>, 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<I: Importable>(&self, s: &str) -> Result<I, AutomergeError> {
if let Some(x) = I::from(s) {
Ok(x)
pub fn import(&self, s: &str) -> Result<ExId, AutomergeError> {
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<E: Exportable>(&self, id: E) -> String {
fn to_string<E: Exportable>(&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(())
}

View file

@ -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<Self>
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<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 {
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 {

View file

@ -5,9 +5,9 @@ use std::fs;
fn replay_trace(commands: Vec<(usize, usize, Vec<Value>)>) -> 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

View file

@ -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());