Compare commits
5 commits
Author | SHA1 | Date | |
---|---|---|---|
|
8e83310ea3 | ||
|
149ab50b3e | ||
|
f06a3e3928 | ||
|
3bb4bd79b9 | ||
|
a09de2302b |
9 changed files with 349 additions and 292 deletions
|
@ -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")
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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> {
|
||||||
|
if let Ok(obj) = self.exid_to_obj(obj) {
|
||||||
let q = self.ops.search(obj.into(), query::Keys::new());
|
let q = self.ops.search(obj.into(), query::Keys::new());
|
||||||
q.keys.iter().map(|k| self.export(*k)).collect()
|
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> {
|
||||||
|
if let Ok(obj) = self.exid_to_obj(obj) {
|
||||||
let clock = self.clock_at(heads);
|
let clock = self.clock_at(heads);
|
||||||
let q = self.ops.search(obj.into(), query::KeysAt::new(clock));
|
let q = self.ops.search(obj.into(), query::KeysAt::new(clock));
|
||||||
q.keys.iter().map(|k| self.export(*k)).collect()
|
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 {
|
||||||
|
if let Ok(obj) = self.exid_to_obj(obj) {
|
||||||
self.ops.search(obj.into(), query::Len::new(obj.into())).len
|
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 {
|
||||||
|
if let Ok(obj) = self.exid_to_obj(obj) {
|
||||||
let clock = self.clock_at(heads);
|
let clock = self.clock_at(heads);
|
||||||
self.ops.search(obj.into(), query::LenAt::new(clock)).len
|
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,20 +666,11 @@ 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
|
|
||||||
// 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);
|
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();
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
if is_make {
|
||||||
Ok(Some(id))
|
Ok(Some(id))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn local_list_op(
|
fn local_list_op(
|
||||||
|
@ -621,20 +706,11 @@ 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
|
|
||||||
// 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);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
let is_make = matches!(&action, OpType::Make(_));
|
||||||
|
|
||||||
let op = Op {
|
let op = Op {
|
||||||
change: self.history.len(),
|
change: self.history.len(),
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
if is_make {
|
||||||
Ok(Some(id))
|
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(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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());
|
||||||
|
|
Loading…
Reference in a new issue