Compare commits
9 commits
Author | SHA1 | Date | |
---|---|---|---|
|
e8061be6bf | ||
|
853d51429e | ||
|
501d8954c7 | ||
|
447595f120 | ||
|
8e83310ea3 | ||
|
149ab50b3e | ||
|
f06a3e3928 | ||
|
3bb4bd79b9 | ||
|
a09de2302b |
35 changed files with 1872 additions and 1942 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, ObjId, Prop, Value};
|
||||||
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: ObjId) -> 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<ObjId, 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")
|
||||||
|
|
|
@ -36,3 +36,4 @@ pretty_assertions = "1.0.0"
|
||||||
proptest = { version = "^1.0.0", default-features = false, features = ["std"] }
|
proptest = { version = "^1.0.0", default-features = false, features = ["std"] }
|
||||||
serde_json = { version = "^1.0.73", features=["float_roundtrip"], default-features=true }
|
serde_json = { version = "^1.0.73", features=["float_roundtrip"], default-features=true }
|
||||||
maplit = { version = "^1.0" }
|
maplit = { version = "^1.0" }
|
||||||
|
decorum = "0.3.1"
|
||||||
|
|
1338
automerge/src/automerge.rs
Normal file
1338
automerge/src/automerge.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::automerge::Transaction;
|
||||||
use crate::columnar::{
|
use crate::columnar::{
|
||||||
ChangeEncoder, ChangeIterator, ColumnEncoder, DepsIterator, DocChange, DocOp, DocOpEncoder,
|
ChangeEncoder, ChangeIterator, ColumnEncoder, DepsIterator, DocChange, DocOp, DocOpEncoder,
|
||||||
DocOpIterator, OperationIterator, COLUMN_TYPE_DEFLATE,
|
DocOpIterator, OperationIterator, COLUMN_TYPE_DEFLATE,
|
||||||
|
@ -5,11 +6,11 @@ use crate::columnar::{
|
||||||
use crate::decoding;
|
use crate::decoding;
|
||||||
use crate::decoding::{Decodable, InvalidChangeError};
|
use crate::decoding::{Decodable, InvalidChangeError};
|
||||||
use crate::encoding::{Encodable, DEFLATE_MIN_SIZE};
|
use crate::encoding::{Encodable, DEFLATE_MIN_SIZE};
|
||||||
|
use crate::error::AutomergeError;
|
||||||
|
use crate::indexed_cache::IndexedCache;
|
||||||
use crate::legacy as amp;
|
use crate::legacy as amp;
|
||||||
use crate::{
|
use crate::types;
|
||||||
ActorId, AutomergeError, ElemId, IndexedCache, Key, ObjId, Op, OpId, OpType, Transaction, HEAD,
|
use crate::types::{ActorId, ElemId, Key, ObjId, Op, OpId, OpType};
|
||||||
ROOT,
|
|
||||||
};
|
|
||||||
use core::ops::Range;
|
use core::ops::Range;
|
||||||
use flate2::{
|
use flate2::{
|
||||||
bufread::{DeflateDecoder, DeflateEncoder},
|
bufread::{DeflateDecoder, DeflateEncoder},
|
||||||
|
@ -417,7 +418,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()
|
||||||
|
@ -425,7 +426,7 @@ fn export_objid(id: &ObjId, actors: &IndexedCache<ActorId>) -> amp::ObjectId {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn export_elemid(id: &ElemId, actors: &IndexedCache<ActorId>) -> amp::ElementId {
|
fn export_elemid(id: &ElemId, actors: &IndexedCache<ActorId>) -> amp::ElementId {
|
||||||
if id == &HEAD {
|
if id == &types::HEAD {
|
||||||
amp::ElementId::Head
|
amp::ElementId::Head
|
||||||
} else {
|
} else {
|
||||||
export_opid(&id.0, actors).into()
|
export_opid(&id.0, actors).into()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::OpId;
|
use crate::types::OpId;
|
||||||
use fxhash::FxBuildHasher;
|
use fxhash::FxBuildHasher;
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
|
@ -11,8 +11,7 @@ use std::{
|
||||||
str,
|
str,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::ROOT;
|
use crate::types::{ActorId, ElemId, Key, ObjId, ObjType, Op, OpId, OpType, ScalarValue};
|
||||||
use crate::{ActorId, ElemId, Key, ObjId, ObjType, OpId, OpType, ScalarValue};
|
|
||||||
|
|
||||||
use crate::legacy as amp;
|
use crate::legacy as amp;
|
||||||
use amp::SortedVec;
|
use amp::SortedVec;
|
||||||
|
@ -20,10 +19,10 @@ use flate2::bufread::DeflateDecoder;
|
||||||
use smol_str::SmolStr;
|
use smol_str::SmolStr;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
|
use crate::indexed_cache::IndexedCache;
|
||||||
use crate::{
|
use crate::{
|
||||||
decoding::{BooleanDecoder, Decodable, Decoder, DeltaDecoder, RleDecoder},
|
decoding::{BooleanDecoder, Decodable, Decoder, DeltaDecoder, RleDecoder},
|
||||||
encoding::{BooleanEncoder, ColData, DeltaEncoder, Encodable, RleEncoder},
|
encoding::{BooleanEncoder, ColData, DeltaEncoder, Encodable, RleEncoder},
|
||||||
IndexedCache, Op,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
impl Encodable for Action {
|
impl Encodable for Action {
|
||||||
|
@ -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
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::decoding;
|
use crate::decoding;
|
||||||
|
use crate::types::ScalarValue;
|
||||||
use crate::value::DataType;
|
use crate::value::DataType;
|
||||||
use crate::ScalarValue;
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
|
@ -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 {
|
||||||
|
|
33
automerge/src/exid.rs
Normal file
33
automerge/src/exid.rs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
use crate::ActorId;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ExId {
|
||||||
|
Root,
|
||||||
|
Id(u64, 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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,8 +31,8 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
|
|
|
@ -2,8 +2,8 @@ mod serde_impls;
|
||||||
mod utility_impls;
|
mod utility_impls;
|
||||||
use std::iter::FromIterator;
|
use std::iter::FromIterator;
|
||||||
|
|
||||||
|
pub(crate) use crate::types::{ActorId, ChangeHash, ObjType, OpType, ScalarValue};
|
||||||
pub(crate) use crate::value::DataType;
|
pub(crate) use crate::value::DataType;
|
||||||
pub(crate) use crate::{ActorId, ChangeHash, ObjType, OpType, ScalarValue};
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use smol_str::SmolStr;
|
use smol_str::SmolStr;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use serde::{de, Deserialize, Deserializer};
|
use serde::{de, Deserialize, Deserializer};
|
||||||
use smol_str::SmolStr;
|
use smol_str::SmolStr;
|
||||||
|
|
||||||
use crate::ScalarValue;
|
use crate::types::ScalarValue;
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for ScalarValue {
|
impl<'de> Deserialize<'de> for ScalarValue {
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
|
1295
automerge/src/lib.rs
1295
automerge/src/lib.rs
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,7 @@
|
||||||
|
use crate::indexed_cache::IndexedCache;
|
||||||
use crate::op_tree::OpTreeInternal;
|
use crate::op_tree::OpTreeInternal;
|
||||||
use crate::query::TreeQuery;
|
use crate::query::TreeQuery;
|
||||||
use crate::{ActorId, IndexedCache, Key, ObjId, Op, OpId};
|
use crate::types::{ActorId, Key, ObjId, Op, OpId};
|
||||||
use fxhash::FxBuildHasher;
|
use fxhash::FxBuildHasher;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
|
@ -6,7 +6,7 @@ use std::{
|
||||||
|
|
||||||
pub(crate) use crate::op_set::OpSetMetadata;
|
pub(crate) use crate::op_set::OpSetMetadata;
|
||||||
use crate::query::{Index, QueryResult, TreeQuery};
|
use crate::query::{Index, QueryResult, TreeQuery};
|
||||||
use crate::{Op, OpId};
|
use crate::types::{Op, OpId};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
@ -628,7 +628,7 @@ struct CounterData {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::legacy as amp;
|
use crate::legacy as amp;
|
||||||
use crate::{Op, OpId};
|
use crate::types::{Op, OpId};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::op_tree::{OpSetMetadata, OpTreeNode};
|
use crate::op_tree::{OpSetMetadata, OpTreeNode};
|
||||||
use crate::{Clock, ElemId, Op, OpId, OpType, ScalarValue};
|
use crate::types::{Clock, ElemId, Op, OpId, OpType, ScalarValue};
|
||||||
use fxhash::FxBuildHasher;
|
use fxhash::FxBuildHasher;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
|
use crate::error::AutomergeError;
|
||||||
use crate::op_tree::OpTreeNode;
|
use crate::op_tree::OpTreeNode;
|
||||||
use crate::query::{QueryResult, TreeQuery, VisWindow};
|
use crate::query::{QueryResult, TreeQuery, VisWindow};
|
||||||
use crate::{AutomergeError, ElemId, Key, Op, HEAD};
|
use crate::types::{ElemId, Key, Op, HEAD};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::op_tree::OpTreeNode;
|
use crate::op_tree::OpTreeNode;
|
||||||
use crate::query::{QueryResult, TreeQuery, VisWindow};
|
use crate::query::{QueryResult, TreeQuery, VisWindow};
|
||||||
use crate::Key;
|
use crate::types::Key;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::query::{QueryResult, TreeQuery, VisWindow};
|
use crate::query::{QueryResult, TreeQuery, VisWindow};
|
||||||
use crate::{Clock, Key, Op};
|
use crate::types::{Clock, Key, Op};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
|
|
@ -1,17 +1,15 @@
|
||||||
use crate::op_tree::OpTreeNode;
|
use crate::op_tree::OpTreeNode;
|
||||||
use crate::query::{QueryResult, TreeQuery};
|
use crate::query::{QueryResult, TreeQuery};
|
||||||
use crate::ObjId;
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub(crate) struct Len<const B: usize> {
|
pub(crate) struct Len<const B: usize> {
|
||||||
obj: ObjId,
|
|
||||||
pub len: usize,
|
pub len: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const B: usize> Len<B> {
|
impl<const B: usize> Len<B> {
|
||||||
pub fn new(obj: ObjId) -> Self {
|
pub fn new() -> Self {
|
||||||
Len { obj, len: 0 }
|
Len { len: 0 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::query::{QueryResult, TreeQuery, VisWindow};
|
use crate::query::{QueryResult, TreeQuery, VisWindow};
|
||||||
use crate::{Clock, ElemId, Op};
|
use crate::types::{Clock, ElemId, Op};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::op_tree::{OpSetMetadata, OpTreeNode};
|
use crate::op_tree::{OpSetMetadata, OpTreeNode};
|
||||||
use crate::query::{binary_search_by, is_visible, visible_op, QueryResult, TreeQuery};
|
use crate::query::{binary_search_by, is_visible, visible_op, QueryResult, TreeQuery};
|
||||||
use crate::{ElemId, ObjId, Op};
|
use crate::types::{ElemId, ObjId, Op};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::query::{QueryResult, TreeQuery, VisWindow};
|
use crate::query::{QueryResult, TreeQuery, VisWindow};
|
||||||
use crate::{Clock, ElemId, Op};
|
use crate::types::{Clock, ElemId, Op};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
|
use crate::error::AutomergeError;
|
||||||
use crate::op_tree::OpTreeNode;
|
use crate::op_tree::OpTreeNode;
|
||||||
use crate::query::{QueryResult, TreeQuery, VisWindow};
|
use crate::query::{QueryResult, TreeQuery, VisWindow};
|
||||||
use crate::{AutomergeError, ElemId, Key, Op};
|
use crate::types::{ElemId, Key, Op};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::query::{QueryResult, TreeQuery, VisWindow};
|
use crate::query::{QueryResult, TreeQuery, VisWindow};
|
||||||
use crate::{Clock, ElemId, Op};
|
use crate::types::{Clock, ElemId, Op};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::op_tree::{OpSetMetadata, OpTreeNode};
|
use crate::op_tree::{OpSetMetadata, OpTreeNode};
|
||||||
use crate::query::{binary_search_by, is_visible, visible_op, QueryResult, TreeQuery};
|
use crate::query::{binary_search_by, is_visible, visible_op, QueryResult, TreeQuery};
|
||||||
use crate::{Key, ObjId, Op};
|
use crate::types::{Key, ObjId, Op};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::op_tree::{OpSetMetadata, OpTreeNode};
|
use crate::op_tree::{OpSetMetadata, OpTreeNode};
|
||||||
use crate::query::{binary_search_by, QueryResult, TreeQuery, VisWindow};
|
use crate::query::{binary_search_by, QueryResult, TreeQuery, VisWindow};
|
||||||
use crate::{Clock, Key, Op};
|
use crate::types::{Clock, Key, Op};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::op_tree::{OpSetMetadata, OpTreeNode};
|
use crate::op_tree::{OpSetMetadata, OpTreeNode};
|
||||||
use crate::query::{binary_search_by, QueryResult, TreeQuery};
|
use crate::query::{binary_search_by, QueryResult, TreeQuery};
|
||||||
use crate::{Key, Op, HEAD};
|
use crate::types::{Key, Op, HEAD};
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
|
|
@ -6,9 +6,10 @@ use std::{
|
||||||
io::Write,
|
io::Write,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::types::Patch;
|
||||||
use crate::{
|
use crate::{
|
||||||
decoding, decoding::Decoder, encoding, encoding::Encodable, Automerge, AutomergeError, Change,
|
decoding, decoding::Decoder, encoding, encoding::Encodable, Automerge, AutomergeError, Change,
|
||||||
ChangeHash, Patch,
|
ChangeHash,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod bloom;
|
mod bloom;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use crate::error;
|
use crate::error;
|
||||||
use crate::legacy as amp;
|
use crate::legacy as amp;
|
||||||
use crate::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;
|
||||||
|
@ -9,8 +8,11 @@ use std::fmt;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use tinyvec::{ArrayVec, TinyVec};
|
use tinyvec::{ArrayVec, TinyVec};
|
||||||
|
|
||||||
|
pub(crate) use crate::clock::Clock;
|
||||||
|
pub(crate) use crate::value::{ScalarValue, Value};
|
||||||
|
|
||||||
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 +163,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 +229,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 +308,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 +336,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 +355,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 {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::{error, ObjType, Op, OpId, OpType};
|
use crate::error;
|
||||||
|
use crate::types::{ObjType, Op, OpId, OpType};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use smol_str::SmolStr;
|
use smol_str::SmolStr;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
|
@ -24,7 +24,7 @@ pub(crate) struct Node<'a, const B: usize> {
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) enum NodeType<'a, const B: usize> {
|
pub(crate) enum NodeType<'a, const B: usize> {
|
||||||
ObjRoot(crate::ObjId),
|
ObjRoot(crate::types::ObjId),
|
||||||
ObjTreeNode(&'a crate::op_tree::OpTreeNode<B>),
|
ObjTreeNode(&'a crate::op_tree::OpTreeNode<B>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,7 +225,7 @@ impl OpTableRow {
|
||||||
|
|
||||||
impl OpTableRow {
|
impl OpTableRow {
|
||||||
fn create(
|
fn create(
|
||||||
op: &super::Op,
|
op: &super::types::Op,
|
||||||
metadata: &crate::op_set::OpSetMetadata,
|
metadata: &crate::op_set::OpSetMetadata,
|
||||||
actor_shorthands: &HashMap<usize, String>,
|
actor_shorthands: &HashMap<usize, String>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
@ -236,8 +236,8 @@ impl OpTableRow {
|
||||||
crate::OpType::Inc(v) => format!("inc {}", v),
|
crate::OpType::Inc(v) => format!("inc {}", v),
|
||||||
};
|
};
|
||||||
let prop = match op.key {
|
let prop = match op.key {
|
||||||
crate::Key::Map(k) => metadata.props[k].clone(),
|
crate::types::Key::Map(k) => metadata.props[k].clone(),
|
||||||
crate::Key::Seq(e) => print_opid(&e.0, actor_shorthands),
|
crate::types::Key::Seq(e) => print_opid(&e.0, actor_shorthands),
|
||||||
};
|
};
|
||||||
let succ = op
|
let succ = op
|
||||||
.succ
|
.succ
|
||||||
|
@ -254,6 +254,6 @@ impl OpTableRow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_opid(opid: &crate::OpId, actor_shorthands: &HashMap<usize, String>) -> String {
|
fn print_opid(opid: &crate::types::OpId, actor_shorthands: &HashMap<usize, String>) -> String {
|
||||||
format!("{}@{}", opid.counter(), actor_shorthands[&opid.actor()])
|
format!("{}@{}", opid.counter(), actor_shorthands[&opid.actor()])
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
use std::{collections::HashMap, convert::TryInto, hash::Hash};
|
use std::{
|
||||||
|
collections::{BTreeMap, BTreeSet},
|
||||||
|
convert::TryInto,
|
||||||
|
hash::Hash,
|
||||||
|
};
|
||||||
|
|
||||||
use serde::ser::{SerializeMap, SerializeSeq};
|
use serde::ser::{SerializeMap, SerializeSeq};
|
||||||
|
|
||||||
|
@ -42,7 +46,7 @@ pub fn sorted_actors() -> (automerge::ActorId, automerge::ActorId) {
|
||||||
/// map!{
|
/// map!{
|
||||||
/// "todos" => {
|
/// "todos" => {
|
||||||
/// todos => list![
|
/// todos => list![
|
||||||
/// { todo => map!{ title = "water plants" } }
|
/// { map!{ title = "water plants" } }
|
||||||
/// ]
|
/// ]
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
|
@ -50,9 +54,9 @@ pub fn sorted_actors() -> (automerge::ActorId, automerge::ActorId) {
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// This might look more complicated than you were expecting. Why are there OpIds (`todos`, `todo`,
|
/// This might look more complicated than you were expecting. Why is the first element in the list
|
||||||
/// `title`) in there? Well the `RealizedObject` contains all the changes in the document tagged by
|
/// wrapped in braces? Because every property in an automerge document can have multiple
|
||||||
/// OpId. This makes it easy to test for conflicts:
|
/// conflicting values we must capture all of these.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// let mut doc1 = automerge::Automerge::new();
|
/// let mut doc1 = automerge::Automerge::new();
|
||||||
|
@ -70,33 +74,20 @@ pub fn sorted_actors() -> (automerge::ActorId, automerge::ActorId) {
|
||||||
/// }
|
/// }
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
///
|
|
||||||
/// ## Translating OpIds
|
|
||||||
///
|
|
||||||
/// One thing you may have noticed in the example above is the `op2.translate(&doc2)` call. What is
|
|
||||||
/// that doing there? Well, the problem is that automerge OpIDs (in the current API) are specific
|
|
||||||
/// to a document. Using an opid from one document in a different document will not work. Therefore
|
|
||||||
/// this module defines an `OpIdExt` trait with a `translate` method on it. This method takes a
|
|
||||||
/// document and converts the opid into something which knows how to be compared with opids from
|
|
||||||
/// another document by using the document you pass to `translate`. Again, all you really need to
|
|
||||||
/// know is that when constructing a document for comparison you should call `translate(fromdoc)`
|
|
||||||
/// on opids which come from a document other than the one you pass to `assert_doc`.
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! assert_doc {
|
macro_rules! assert_doc {
|
||||||
($doc: expr, $expected: expr) => {{
|
($doc: expr, $expected: expr) => {{
|
||||||
use $crate::helpers::{realize, ExportableOpId};
|
use $crate::helpers::realize;
|
||||||
let realized = realize($doc);
|
let realized = realize($doc);
|
||||||
let to_export: RealizedObject<ExportableOpId<'_>> = $expected.into();
|
let expected_obj = $expected.into();
|
||||||
let exported = to_export.export($doc);
|
if realized != expected_obj {
|
||||||
if realized != exported {
|
|
||||||
let serde_right = serde_json::to_string_pretty(&realized).unwrap();
|
let serde_right = serde_json::to_string_pretty(&realized).unwrap();
|
||||||
let serde_left = serde_json::to_string_pretty(&exported).unwrap();
|
let serde_left = serde_json::to_string_pretty(&expected_obj).unwrap();
|
||||||
panic!(
|
panic!(
|
||||||
"documents didn't match\n expected\n{}\n got\n{}",
|
"documents didn't match\n expected\n{}\n got\n{}",
|
||||||
&serde_left, &serde_right
|
&serde_left, &serde_right
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
pretty_assertions::assert_eq!(realized, exported);
|
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,63 +96,52 @@ macro_rules! assert_doc {
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! assert_obj {
|
macro_rules! assert_obj {
|
||||||
($doc: expr, $obj_id: expr, $prop: expr, $expected: expr) => {{
|
($doc: expr, $obj_id: expr, $prop: expr, $expected: expr) => {{
|
||||||
use $crate::helpers::{realize_prop, ExportableOpId};
|
use $crate::helpers::realize_prop;
|
||||||
let realized = realize_prop($doc, $obj_id, $prop);
|
let realized = realize_prop($doc, $obj_id, $prop);
|
||||||
let to_export: RealizedObject<ExportableOpId<'_>> = $expected.into();
|
let expected_obj = $expected.into();
|
||||||
let exported = to_export.export($doc);
|
if realized != expected_obj {
|
||||||
if realized != exported {
|
|
||||||
let serde_right = serde_json::to_string_pretty(&realized).unwrap();
|
let serde_right = serde_json::to_string_pretty(&realized).unwrap();
|
||||||
let serde_left = serde_json::to_string_pretty(&exported).unwrap();
|
let serde_left = serde_json::to_string_pretty(&expected_obj).unwrap();
|
||||||
panic!(
|
panic!(
|
||||||
"documents didn't match\n expected\n{}\n got\n{}",
|
"documents didn't match\n expected\n{}\n got\n{}",
|
||||||
&serde_left, &serde_right
|
&serde_left, &serde_right
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
pretty_assertions::assert_eq!(realized, exported);
|
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct `RealizedObject::Map`. This macro takes a nested set of curl braces. The outer set is
|
/// Construct `RealizedObject::Map`. This macro takes a nested set of curl braces. The outer set is
|
||||||
/// the keys of the map, the inner set is the opid tagged values:
|
/// the keys of the map, the inner set is the set of values for that key:
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// map!{
|
/// map!{
|
||||||
/// "key" => {
|
/// "key" => {
|
||||||
/// opid1 => "value1",
|
/// "value1",
|
||||||
/// opid2 => "value2",
|
/// "value2",
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// The map above would represent a map with a conflict on the "key" property. The values can be
|
/// The map above would represent a map with a conflict on the "key" property. The values can be
|
||||||
/// anything which implements `Into<RealizedObject<ExportableOpId<'_>>`. Including nested calls to
|
/// anything which implements `Into<RealizedObject>`. Including nested calls to `map!` or `list!`.
|
||||||
/// `map!` or `list!`.
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! map {
|
macro_rules! map {
|
||||||
(@single $($x:tt)*) => (());
|
(@inner { $($value:expr,)+ }) => { map!(@inner { $($value),+ }) };
|
||||||
(@count $($rest:expr),*) => (<[()]>::len(&[$(map!(@single $rest)),*]));
|
(@inner { $($value:expr),* }) => {
|
||||||
|
|
||||||
(@inner { $($opid:expr => $value:expr,)+ }) => { map!(@inner { $($opid => $value),+ }) };
|
|
||||||
(@inner { $($opid:expr => $value:expr),* }) => {
|
|
||||||
{
|
{
|
||||||
use std::collections::HashMap;
|
use std::collections::BTreeSet;
|
||||||
let mut inner: HashMap<ExportableOpId<'_>, RealizedObject<ExportableOpId<'_>>> = HashMap::new();
|
let mut inner: BTreeSet<RealizedObject> = BTreeSet::new();
|
||||||
$(
|
$(
|
||||||
let _ = inner.insert($opid.into(), $value.into());
|
let _ = inner.insert($value.into());
|
||||||
)*
|
)*
|
||||||
inner
|
inner
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
//(&inner $map:expr, $opid:expr => $value:expr, $($tail:tt),*) => {
|
|
||||||
//$map.insert($opid.into(), $value.into());
|
|
||||||
//}
|
|
||||||
($($key:expr => $inner:tt,)+) => { map!($($key => $inner),+) };
|
($($key:expr => $inner:tt,)+) => { map!($($key => $inner),+) };
|
||||||
($($key:expr => $inner:tt),*) => {
|
($($key:expr => $inner:tt),*) => {
|
||||||
{
|
{
|
||||||
use std::collections::HashMap;
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
use crate::helpers::ExportableOpId;
|
let mut _map: BTreeMap<String, BTreeSet<RealizedObject>> = ::std::collections::BTreeMap::new();
|
||||||
let _cap = map!(@count $($key),*);
|
|
||||||
let mut _map: HashMap<String, HashMap<ExportableOpId<'_>, RealizedObject<ExportableOpId<'_>>>> = ::std::collections::HashMap::with_capacity(_cap);
|
|
||||||
$(
|
$(
|
||||||
let inner = map!(@inner $inner);
|
let inner = map!(@inner $inner);
|
||||||
let _ = _map.insert($key.to_string(), inner);
|
let _ = _map.insert($key.to_string(), inner);
|
||||||
|
@ -171,32 +151,32 @@ macro_rules! map {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct `RealizedObject::Sequence`. This macro represents a sequence of opid tagged values
|
/// Construct `RealizedObject::Sequence`. This macro represents a sequence of values
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// list![
|
/// list![
|
||||||
/// {
|
/// {
|
||||||
/// opid1 => "value1",
|
/// "value1",
|
||||||
/// opid2 => "value2",
|
/// "value2",
|
||||||
/// }
|
/// }
|
||||||
/// ]
|
/// ]
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// The list above would represent a list with a conflict on the 0 index. The values can be
|
/// The list above would represent a list with a conflict on the 0 index. The values can be
|
||||||
/// anything which implements `Into<RealizedObject<ExportableOpId<'_>>` including nested calls to
|
/// anything which implements `Into<RealizedObject>` including nested calls to
|
||||||
/// `map!` or `list!`.
|
/// `map!` or `list!`.
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! list {
|
macro_rules! list {
|
||||||
(@single $($x:tt)*) => (());
|
(@single $($x:tt)*) => (());
|
||||||
(@count $($rest:tt),*) => (<[()]>::len(&[$(list!(@single $rest)),*]));
|
(@count $($rest:tt),*) => (<[()]>::len(&[$(list!(@single $rest)),*]));
|
||||||
|
|
||||||
(@inner { $($opid:expr => $value:expr,)+ }) => { list!(@inner { $($opid => $value),+ }) };
|
(@inner { $($value:expr,)+ }) => { list!(@inner { $($value),+ }) };
|
||||||
(@inner { $($opid:expr => $value:expr),* }) => {
|
(@inner { $($value:expr),* }) => {
|
||||||
{
|
{
|
||||||
use std::collections::HashMap;
|
use std::collections::BTreeSet;
|
||||||
let mut inner: HashMap<ExportableOpId<'_>, RealizedObject<ExportableOpId<'_>>> = HashMap::new();
|
let mut inner: BTreeSet<RealizedObject> = BTreeSet::new();
|
||||||
$(
|
$(
|
||||||
let _ = inner.insert($opid.into(), $value.into());
|
let _ = inner.insert($value.into());
|
||||||
)*
|
)*
|
||||||
inner
|
inner
|
||||||
}
|
}
|
||||||
|
@ -204,9 +184,8 @@ macro_rules! list {
|
||||||
($($inner:tt,)+) => { list!($($inner),+) };
|
($($inner:tt,)+) => { list!($($inner),+) };
|
||||||
($($inner:tt),*) => {
|
($($inner:tt),*) => {
|
||||||
{
|
{
|
||||||
use crate::helpers::ExportableOpId;
|
|
||||||
let _cap = list!(@count $($inner),*);
|
let _cap = list!(@count $($inner),*);
|
||||||
let mut _list: Vec<HashMap<ExportableOpId<'_>, RealizedObject<ExportableOpId<'_>>>> = Vec::new();
|
let mut _list: Vec<BTreeSet<RealizedObject>> = Vec::new();
|
||||||
$(
|
$(
|
||||||
//println!("{}", stringify!($inner));
|
//println!("{}", stringify!($inner));
|
||||||
let inner = list!(@inner $inner);
|
let inner = list!(@inner $inner);
|
||||||
|
@ -217,26 +196,6 @@ macro_rules! list {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Translate an op ID produced by one document to an op ID which can be understood by
|
|
||||||
/// another
|
|
||||||
///
|
|
||||||
/// The current API of automerge exposes OpIds of the form (u64, usize) where the first component
|
|
||||||
/// is the counter of an actors lamport timestamp and the second component is the index into an
|
|
||||||
/// array of actor IDs stored by the document where the opid was generated. Obviously this is not
|
|
||||||
/// portable between documents as the index of the actor array is unlikely to match between two
|
|
||||||
/// documents. This function translates between the two representations.
|
|
||||||
///
|
|
||||||
/// At some point we will probably change the API to not be document specific but this function
|
|
||||||
/// allows us to write tests first.
|
|
||||||
pub fn translate_obj_id(
|
|
||||||
from: &automerge::Automerge,
|
|
||||||
to: &automerge::Automerge,
|
|
||||||
id: automerge::OpId,
|
|
||||||
) -> automerge::OpId {
|
|
||||||
let exported = from.export(id);
|
|
||||||
to.import(&exported).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn mk_counter(value: i64) -> automerge::ScalarValue {
|
pub fn mk_counter(value: i64) -> automerge::ScalarValue {
|
||||||
automerge::ScalarValue::Counter(value)
|
automerge::ScalarValue::Counter(value)
|
||||||
}
|
}
|
||||||
|
@ -252,14 +211,72 @@ impl std::fmt::Display for ExportedOpId {
|
||||||
|
|
||||||
/// A `RealizedObject` is a representation of all the current values in a document - including
|
/// A `RealizedObject` is a representation of all the current values in a document - including
|
||||||
/// conflicts.
|
/// conflicts.
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, PartialOrd, Ord, Eq, Hash, Debug)]
|
||||||
pub enum RealizedObject<Oid: PartialEq + Eq + Hash> {
|
pub enum RealizedObject {
|
||||||
Map(HashMap<String, HashMap<Oid, RealizedObject<Oid>>>),
|
Map(BTreeMap<String, BTreeSet<RealizedObject>>),
|
||||||
Sequence(Vec<HashMap<Oid, RealizedObject<Oid>>>),
|
Sequence(Vec<BTreeSet<RealizedObject>>),
|
||||||
Value(automerge::ScalarValue),
|
Value(OrdScalarValue),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl serde::Serialize for RealizedObject<ExportedOpId> {
|
// A copy of automerge::ScalarValue which uses decorum::Total for floating point values. This makes the type
|
||||||
|
// orderable, which is useful when we want to compare conflicting values of a register in an
|
||||||
|
// automerge document.
|
||||||
|
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
|
||||||
|
pub enum OrdScalarValue {
|
||||||
|
Bytes(Vec<u8>),
|
||||||
|
Str(smol_str::SmolStr),
|
||||||
|
Int(i64),
|
||||||
|
Uint(u64),
|
||||||
|
F64(decorum::Total<f64>),
|
||||||
|
Counter(i64),
|
||||||
|
Timestamp(i64),
|
||||||
|
Boolean(bool),
|
||||||
|
Null,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<automerge::ScalarValue> for OrdScalarValue {
|
||||||
|
fn from(v: automerge::ScalarValue) -> Self {
|
||||||
|
match v {
|
||||||
|
automerge::ScalarValue::Bytes(v) => OrdScalarValue::Bytes(v),
|
||||||
|
automerge::ScalarValue::Str(v) => OrdScalarValue::Str(v),
|
||||||
|
automerge::ScalarValue::Int(v) => OrdScalarValue::Int(v),
|
||||||
|
automerge::ScalarValue::Uint(v) => OrdScalarValue::Uint(v),
|
||||||
|
automerge::ScalarValue::F64(v) => OrdScalarValue::F64(decorum::Total::from(v)),
|
||||||
|
automerge::ScalarValue::Counter(v) => OrdScalarValue::Counter(v),
|
||||||
|
automerge::ScalarValue::Timestamp(v) => OrdScalarValue::Timestamp(v),
|
||||||
|
automerge::ScalarValue::Boolean(v) => OrdScalarValue::Boolean(v),
|
||||||
|
automerge::ScalarValue::Null => OrdScalarValue::Null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&OrdScalarValue> for automerge::ScalarValue {
|
||||||
|
fn from(v: &OrdScalarValue) -> Self {
|
||||||
|
match v {
|
||||||
|
OrdScalarValue::Bytes(v) => automerge::ScalarValue::Bytes(v.clone()),
|
||||||
|
OrdScalarValue::Str(v) => automerge::ScalarValue::Str(v.clone()),
|
||||||
|
OrdScalarValue::Int(v) => automerge::ScalarValue::Int(*v),
|
||||||
|
OrdScalarValue::Uint(v) => automerge::ScalarValue::Uint(*v),
|
||||||
|
OrdScalarValue::F64(v) => automerge::ScalarValue::F64(v.into_inner()),
|
||||||
|
OrdScalarValue::Counter(v) => automerge::ScalarValue::Counter(*v),
|
||||||
|
OrdScalarValue::Timestamp(v) => automerge::ScalarValue::Timestamp(*v),
|
||||||
|
OrdScalarValue::Boolean(v) => automerge::ScalarValue::Boolean(*v),
|
||||||
|
OrdScalarValue::Null => automerge::ScalarValue::Null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl serde::Serialize for OrdScalarValue {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
let s = automerge::ScalarValue::from(self);
|
||||||
|
s.serialize(serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl serde::Serialize for RealizedObject {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
S: serde::Serializer,
|
S: serde::Serializer,
|
||||||
|
@ -267,23 +284,17 @@ impl serde::Serialize for RealizedObject<ExportedOpId> {
|
||||||
match self {
|
match self {
|
||||||
Self::Map(kvs) => {
|
Self::Map(kvs) => {
|
||||||
let mut map_ser = serializer.serialize_map(Some(kvs.len()))?;
|
let mut map_ser = serializer.serialize_map(Some(kvs.len()))?;
|
||||||
for (k, kvs) in kvs {
|
for (k, vs) in kvs {
|
||||||
let kvs_serded = kvs
|
let vs_serded = vs.iter().collect::<Vec<&RealizedObject>>();
|
||||||
.iter()
|
map_ser.serialize_entry(k, &vs_serded)?;
|
||||||
.map(|(opid, value)| (opid.to_string(), value))
|
|
||||||
.collect::<HashMap<String, &RealizedObject<ExportedOpId>>>();
|
|
||||||
map_ser.serialize_entry(k, &kvs_serded)?;
|
|
||||||
}
|
}
|
||||||
map_ser.end()
|
map_ser.end()
|
||||||
}
|
}
|
||||||
Self::Sequence(elems) => {
|
Self::Sequence(elems) => {
|
||||||
let mut list_ser = serializer.serialize_seq(Some(elems.len()))?;
|
let mut list_ser = serializer.serialize_seq(Some(elems.len()))?;
|
||||||
for elem in elems {
|
for elem in elems {
|
||||||
let kvs_serded = elem
|
let vs_serded = elem.iter().collect::<Vec<&RealizedObject>>();
|
||||||
.iter()
|
list_ser.serialize_element(&vs_serded)?;
|
||||||
.map(|(opid, value)| (opid.to_string(), value))
|
|
||||||
.collect::<HashMap<String, &RealizedObject<ExportedOpId>>>();
|
|
||||||
list_ser.serialize_element(&kvs_serded)?;
|
|
||||||
}
|
}
|
||||||
list_ser.end()
|
list_ser.end()
|
||||||
}
|
}
|
||||||
|
@ -292,30 +303,30 @@ impl serde::Serialize for RealizedObject<ExportedOpId> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn realize(doc: &automerge::Automerge) -> RealizedObject<ExportedOpId> {
|
pub fn realize(doc: &automerge::Automerge) -> RealizedObject {
|
||||||
realize_obj(doc, automerge::ROOT, automerge::ObjType::Map)
|
realize_obj(doc, &automerge::ROOT, automerge::ObjType::Map)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn realize_prop<P: Into<automerge::Prop>>(
|
pub fn realize_prop<P: Into<automerge::Prop>>(
|
||||||
doc: &automerge::Automerge,
|
doc: &automerge::Automerge,
|
||||||
obj_id: automerge::OpId,
|
obj_id: &automerge::ObjId,
|
||||||
prop: P,
|
prop: P,
|
||||||
) -> RealizedObject<ExportedOpId> {
|
) -> RealizedObject {
|
||||||
let (val, obj_id) = doc.value(obj_id, prop).unwrap().unwrap();
|
let (val, obj_id) = doc.value(obj_id, prop).unwrap().unwrap();
|
||||||
match val {
|
match val {
|
||||||
automerge::Value::Object(obj_type) => realize_obj(doc, obj_id, obj_type),
|
automerge::Value::Object(obj_type) => realize_obj(doc, &obj_id, obj_type),
|
||||||
automerge::Value::Scalar(v) => RealizedObject::Value(v),
|
automerge::Value::Scalar(v) => RealizedObject::Value(OrdScalarValue::from(v)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn realize_obj(
|
pub fn realize_obj(
|
||||||
doc: &automerge::Automerge,
|
doc: &automerge::Automerge,
|
||||||
obj_id: automerge::OpId,
|
obj_id: &automerge::ObjId,
|
||||||
objtype: automerge::ObjType,
|
objtype: automerge::ObjType,
|
||||||
) -> RealizedObject<ExportedOpId> {
|
) -> RealizedObject {
|
||||||
match objtype {
|
match objtype {
|
||||||
automerge::ObjType::Map | automerge::ObjType::Table => {
|
automerge::ObjType::Map | automerge::ObjType::Table => {
|
||||||
let mut result = HashMap::new();
|
let mut result = BTreeMap::new();
|
||||||
for key in doc.keys(obj_id) {
|
for key in doc.keys(obj_id) {
|
||||||
result.insert(key.clone(), realize_values(doc, obj_id, key));
|
result.insert(key.clone(), realize_values(doc, obj_id, key));
|
||||||
}
|
}
|
||||||
|
@ -334,166 +345,63 @@ pub fn realize_obj(
|
||||||
|
|
||||||
fn realize_values<K: Into<automerge::Prop>>(
|
fn realize_values<K: Into<automerge::Prop>>(
|
||||||
doc: &automerge::Automerge,
|
doc: &automerge::Automerge,
|
||||||
obj_id: automerge::OpId,
|
obj_id: &automerge::ObjId,
|
||||||
key: K,
|
key: K,
|
||||||
) -> HashMap<ExportedOpId, RealizedObject<ExportedOpId>> {
|
) -> BTreeSet<RealizedObject> {
|
||||||
let mut values_by_opid = HashMap::new();
|
let mut values = BTreeSet::new();
|
||||||
for (value, opid) in doc.values(obj_id, key).unwrap() {
|
for (value, objid) in doc.values(obj_id, key).unwrap() {
|
||||||
let realized = match value {
|
let realized = match value {
|
||||||
automerge::Value::Object(objtype) => realize_obj(doc, opid, objtype),
|
automerge::Value::Object(objtype) => realize_obj(doc, &objid, objtype),
|
||||||
automerge::Value::Scalar(v) => RealizedObject::Value(v),
|
automerge::Value::Scalar(v) => RealizedObject::Value(OrdScalarValue::from(v)),
|
||||||
};
|
};
|
||||||
let exported_opid = ExportedOpId(doc.export(opid));
|
values.insert(realized);
|
||||||
values_by_opid.insert(exported_opid, realized);
|
|
||||||
}
|
}
|
||||||
values_by_opid
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> RealizedObject<ExportableOpId<'a>> {
|
|
||||||
pub fn export(self, doc: &automerge::Automerge) -> RealizedObject<ExportedOpId> {
|
|
||||||
match self {
|
|
||||||
Self::Map(kvs) => RealizedObject::Map(
|
|
||||||
kvs.into_iter()
|
|
||||||
.map(|(k, v)| {
|
|
||||||
(
|
|
||||||
k,
|
|
||||||
v.into_iter()
|
|
||||||
.map(|(k, v)| (k.export(doc), v.export(doc)))
|
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
),
|
|
||||||
Self::Sequence(values) => RealizedObject::Sequence(
|
|
||||||
values
|
values
|
||||||
.into_iter()
|
|
||||||
.map(|v| {
|
|
||||||
v.into_iter()
|
|
||||||
.map(|(k, v)| (k.export(doc), v.export(doc)))
|
|
||||||
.collect()
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
),
|
|
||||||
Self::Value(v) => RealizedObject::Value(v),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, O: Into<ExportableOpId<'a>>, I: Into<RealizedObject<ExportableOpId<'a>>>>
|
impl<I: Into<RealizedObject>> From<BTreeMap<&str, BTreeSet<I>>> for RealizedObject {
|
||||||
From<HashMap<&str, HashMap<O, I>>> for RealizedObject<ExportableOpId<'a>>
|
fn from(values: BTreeMap<&str, BTreeSet<I>>) -> Self {
|
||||||
{
|
|
||||||
fn from(values: HashMap<&str, HashMap<O, I>>) -> Self {
|
|
||||||
let intoed = values
|
let intoed = values
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(k, v)| {
|
.map(|(k, v)| (k.to_string(), v.into_iter().map(|v| v.into()).collect()))
|
||||||
(
|
|
||||||
k.to_string(),
|
|
||||||
v.into_iter().map(|(k, v)| (k.into(), v.into())).collect(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect();
|
.collect();
|
||||||
RealizedObject::Map(intoed)
|
RealizedObject::Map(intoed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, O: Into<ExportableOpId<'a>>, I: Into<RealizedObject<ExportableOpId<'a>>>>
|
impl<I: Into<RealizedObject>> From<Vec<BTreeSet<I>>> for RealizedObject {
|
||||||
From<Vec<HashMap<O, I>>> for RealizedObject<ExportableOpId<'a>>
|
fn from(values: Vec<BTreeSet<I>>) -> Self {
|
||||||
{
|
|
||||||
fn from(values: Vec<HashMap<O, I>>) -> Self {
|
|
||||||
RealizedObject::Sequence(
|
RealizedObject::Sequence(
|
||||||
values
|
values
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|v| v.into_iter().map(|(k, v)| (k.into(), v.into())).collect())
|
.map(|v| v.into_iter().map(|v| v.into()).collect())
|
||||||
.collect(),
|
.collect(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<bool> for RealizedObject<ExportableOpId<'_>> {
|
impl From<bool> for RealizedObject {
|
||||||
fn from(b: bool) -> Self {
|
fn from(b: bool) -> Self {
|
||||||
RealizedObject::Value(b.into())
|
RealizedObject::Value(OrdScalarValue::Boolean(b))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<usize> for RealizedObject<ExportableOpId<'_>> {
|
impl From<usize> for RealizedObject {
|
||||||
fn from(u: usize) -> Self {
|
fn from(u: usize) -> Self {
|
||||||
let v = u.try_into().unwrap();
|
let v = u.try_into().unwrap();
|
||||||
RealizedObject::Value(automerge::ScalarValue::Int(v))
|
RealizedObject::Value(OrdScalarValue::Int(v))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<automerge::ScalarValue> for RealizedObject<ExportableOpId<'_>> {
|
impl From<automerge::ScalarValue> for RealizedObject {
|
||||||
fn from(s: automerge::ScalarValue) -> Self {
|
fn from(s: automerge::ScalarValue) -> Self {
|
||||||
RealizedObject::Value(s)
|
RealizedObject::Value(OrdScalarValue::from(s))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&str> for RealizedObject<ExportableOpId<'_>> {
|
impl From<&str> for RealizedObject {
|
||||||
fn from(s: &str) -> Self {
|
fn from(s: &str) -> Self {
|
||||||
RealizedObject::Value(automerge::ScalarValue::Str(s.into()))
|
RealizedObject::Value(OrdScalarValue::Str(smol_str::SmolStr::from(s)))
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, Hash)]
|
|
||||||
pub enum ExportableOpId<'a> {
|
|
||||||
Native(automerge::OpId),
|
|
||||||
Translate(Translate<'a>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> ExportableOpId<'a> {
|
|
||||||
fn export(self, doc: &automerge::Automerge) -> ExportedOpId {
|
|
||||||
let oid = match self {
|
|
||||||
Self::Native(oid) => oid,
|
|
||||||
Self::Translate(Translate { from, opid }) => translate_obj_id(from, doc, opid),
|
|
||||||
};
|
|
||||||
ExportedOpId(doc.export(oid))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Translate<'a> {
|
|
||||||
from: &'a automerge::Automerge,
|
|
||||||
opid: automerge::OpId,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> PartialEq for Translate<'a> {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.from.maybe_get_actor().unwrap() == other.from.maybe_get_actor().unwrap()
|
|
||||||
&& self.opid == other.opid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Eq for Translate<'a> {}
|
|
||||||
|
|
||||||
impl<'a> Hash for Translate<'a> {
|
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
||||||
self.from.maybe_get_actor().unwrap().hash(state);
|
|
||||||
self.opid.hash(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait OpIdExt {
|
|
||||||
fn native(self) -> ExportableOpId<'static>;
|
|
||||||
fn translate(self, doc: &automerge::Automerge) -> ExportableOpId<'_>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OpIdExt for automerge::OpId {
|
|
||||||
/// Use this opid directly when exporting
|
|
||||||
fn native(self) -> ExportableOpId<'static> {
|
|
||||||
ExportableOpId::Native(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Translate this OpID from `doc` when exporting
|
|
||||||
fn translate(self, doc: &automerge::Automerge) -> ExportableOpId<'_> {
|
|
||||||
ExportableOpId::Translate(Translate {
|
|
||||||
from: doc,
|
|
||||||
opid: self,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<automerge::OpId> for ExportableOpId<'_> {
|
|
||||||
fn from(oid: automerge::OpId) -> Self {
|
|
||||||
ExportableOpId::Native(oid)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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