Compare commits

...

9 commits
main ... exid

Author SHA1 Message Date
Alex Good
e8061be6bf Fix tests 2022-01-01 20:11:55 +00:00
Orion Henry
853d51429e ExId to ObjId 2022-01-01 12:46:23 -05:00
Orion Henry
501d8954c7 move automerge and exid into its own files 2022-01-01 12:22:28 -05:00
Orion Henry
447595f120 Remove rc 2022-01-01 10:46:23 -05:00
Orion Henry
8e83310ea3 comments 2021-12-31 20:10:05 -05:00
Orion Henry
149ab50b3e dont panic if we have a bad exid.idx 2021-12-31 20:03:32 -05:00
Orion Henry
f06a3e3928 cleanup get tests working 2021-12-31 19:57:10 -05:00
Orion Henry
3bb4bd79b9 fix bench 2021-12-31 16:59:10 -05:00
Orion Henry
a09de2302b wip 2021-12-31 16:46:20 -05:00
35 changed files with 1872 additions and 1942 deletions

View file

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

View file

@ -36,3 +36,4 @@ pretty_assertions = "1.0.0"
proptest = { version = "^1.0.0", default-features = false, features = ["std"] }
serde_json = { version = "^1.0.73", features=["float_roundtrip"], default-features=true }
maplit = { version = "^1.0" }
decorum = "0.3.1"

1338
automerge/src/automerge.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,3 +1,4 @@
use crate::automerge::Transaction;
use crate::columnar::{
ChangeEncoder, ChangeIterator, ColumnEncoder, DepsIterator, DocChange, DocOp, DocOpEncoder,
DocOpIterator, OperationIterator, COLUMN_TYPE_DEFLATE,
@ -5,11 +6,11 @@ use crate::columnar::{
use crate::decoding;
use crate::decoding::{Decodable, InvalidChangeError};
use crate::encoding::{Encodable, DEFLATE_MIN_SIZE};
use crate::error::AutomergeError;
use crate::indexed_cache::IndexedCache;
use crate::legacy as amp;
use crate::{
ActorId, AutomergeError, ElemId, IndexedCache, Key, ObjId, Op, OpId, OpType, Transaction, HEAD,
ROOT,
};
use crate::types;
use crate::types::{ActorId, ElemId, Key, ObjId, Op, OpId, OpType};
use core::ops::Range;
use flate2::{
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 {
if id.0 == ROOT {
if id == &ObjId::root() {
amp::ObjectId::Root
} else {
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 {
if id == &HEAD {
if id == &types::HEAD {
amp::ElementId::Head
} else {
export_opid(&id.0, actors).into()

View file

@ -1,4 +1,4 @@
use crate::OpId;
use crate::types::OpId;
use fxhash::FxBuildHasher;
use std::cmp;
use std::collections::HashMap;

View file

@ -11,8 +11,7 @@ use std::{
str,
};
use crate::ROOT;
use crate::{ActorId, ElemId, Key, ObjId, ObjType, OpId, OpType, ScalarValue};
use crate::types::{ActorId, ElemId, Key, ObjId, ObjType, Op, OpId, OpType, ScalarValue};
use crate::legacy as amp;
use amp::SortedVec;
@ -20,10 +19,10 @@ use flate2::bufread::DeflateDecoder;
use smol_str::SmolStr;
use tracing::instrument;
use crate::indexed_cache::IndexedCache;
use crate::{
decoding::{BooleanDecoder, Decodable, Decoder, DeltaDecoder, RleDecoder},
encoding::{BooleanEncoder, ColData, DeltaEncoder, Encodable, RleEncoder},
IndexedCache, Op,
};
impl Encodable for Action {
@ -846,7 +845,7 @@ impl ObjEncoder {
fn append(&mut self, obj: &ObjId, actors: &[usize]) {
match obj.0 {
ROOT => {
OpId(ctr, _) if ctr == 0 => {
self.actor.append_null();
self.ctr.append_null();
}
@ -951,7 +950,7 @@ impl ChangeEncoder {
index_by_hash.insert(hash, index);
}
self.actor
.append_value(actors.lookup(change.actor_id.clone()).unwrap()); //actors.iter().position(|a| a == &change.actor_id).unwrap());
.append_value(actors.lookup(&change.actor_id).unwrap()); //actors.iter().position(|a| a == &change.actor_id).unwrap());
self.seq.append_value(change.seq);
// FIXME iterops.count is crazy slow
self.max_op

View file

@ -1,6 +1,6 @@
use crate::decoding;
use crate::types::ScalarValue;
use crate::value::DataType;
use crate::ScalarValue;
use thiserror::Error;
#[derive(Error, Debug)]
@ -17,6 +17,8 @@ pub enum AutomergeError {
InvalidSeq(u64),
#[error("index {0} is out of bounds")]
InvalidIndex(usize),
#[error("generic automerge error")]
Fail,
}
impl From<std::io::Error> for AutomergeError {

33
automerge/src/exid.rs Normal file
View 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),
}
}
}

View file

@ -31,8 +31,8 @@ where
}
}
pub fn lookup(&self, item: T) -> Option<usize> {
self.lookup.get(&item).cloned()
pub fn lookup(&self, item: &T) -> Option<usize> {
self.lookup.get(item).cloned()
}
pub fn len(&self) -> usize {

View file

@ -2,8 +2,8 @@ mod serde_impls;
mod utility_impls;
use std::iter::FromIterator;
pub(crate) use crate::types::{ActorId, ChangeHash, ObjType, OpType, ScalarValue};
pub(crate) use crate::value::DataType;
pub(crate) use crate::{ActorId, ChangeHash, ObjType, OpType, ScalarValue};
use serde::{Deserialize, Serialize};
use smol_str::SmolStr;

View file

@ -1,7 +1,7 @@
use serde::{de, Deserialize, Deserializer};
use smol_str::SmolStr;
use crate::ScalarValue;
use crate::types::ScalarValue;
impl<'de> Deserialize<'de> for ScalarValue {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,7 @@
use crate::indexed_cache::IndexedCache;
use crate::op_tree::OpTreeInternal;
use crate::query::TreeQuery;
use crate::{ActorId, IndexedCache, Key, ObjId, Op, OpId};
use crate::types::{ActorId, Key, ObjId, Op, OpId};
use fxhash::FxBuildHasher;
use std::cmp::Ordering;
use std::collections::HashMap;

View file

@ -6,7 +6,7 @@ use std::{
pub(crate) use crate::op_set::OpSetMetadata;
use crate::query::{Index, QueryResult, TreeQuery};
use crate::{Op, OpId};
use crate::types::{Op, OpId};
use std::collections::HashSet;
#[allow(dead_code)]
@ -628,7 +628,7 @@ struct CounterData {
#[cfg(test)]
mod tests {
use crate::legacy as amp;
use crate::{Op, OpId};
use crate::types::{Op, OpId};
use super::*;

View file

@ -1,5 +1,5 @@
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 std::cmp::Ordering;
use std::collections::{HashMap, HashSet};

View file

@ -1,6 +1,7 @@
use crate::error::AutomergeError;
use crate::op_tree::OpTreeNode;
use crate::query::{QueryResult, TreeQuery, VisWindow};
use crate::{AutomergeError, ElemId, Key, Op, HEAD};
use crate::types::{ElemId, Key, Op, HEAD};
use std::fmt::Debug;
#[derive(Debug, Clone, PartialEq)]

View file

@ -1,6 +1,6 @@
use crate::op_tree::OpTreeNode;
use crate::query::{QueryResult, TreeQuery, VisWindow};
use crate::Key;
use crate::types::Key;
use std::fmt::Debug;
#[derive(Debug, Clone, PartialEq)]

View file

@ -1,5 +1,5 @@
use crate::query::{QueryResult, TreeQuery, VisWindow};
use crate::{Clock, Key, Op};
use crate::types::{Clock, Key, Op};
use std::fmt::Debug;
#[derive(Debug, Clone, PartialEq)]

View file

@ -1,17 +1,15 @@
use crate::op_tree::OpTreeNode;
use crate::query::{QueryResult, TreeQuery};
use crate::ObjId;
use std::fmt::Debug;
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct Len<const B: usize> {
obj: ObjId,
pub len: usize,
}
impl<const B: usize> Len<B> {
pub fn new(obj: ObjId) -> Self {
Len { obj, len: 0 }
pub fn new() -> Self {
Len { len: 0 }
}
}

View file

@ -1,5 +1,5 @@
use crate::query::{QueryResult, TreeQuery, VisWindow};
use crate::{Clock, ElemId, Op};
use crate::types::{Clock, ElemId, Op};
use std::fmt::Debug;
#[derive(Debug, Clone, PartialEq)]

View file

@ -1,6 +1,6 @@
use crate::op_tree::{OpSetMetadata, OpTreeNode};
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;
#[derive(Debug, Clone, PartialEq)]

View file

@ -1,5 +1,5 @@
use crate::query::{QueryResult, TreeQuery, VisWindow};
use crate::{Clock, ElemId, Op};
use crate::types::{Clock, ElemId, Op};
use std::fmt::Debug;
#[derive(Debug, Clone, PartialEq)]

View file

@ -1,6 +1,7 @@
use crate::error::AutomergeError;
use crate::op_tree::OpTreeNode;
use crate::query::{QueryResult, TreeQuery, VisWindow};
use crate::{AutomergeError, ElemId, Key, Op};
use crate::types::{ElemId, Key, Op};
use std::fmt::Debug;
#[derive(Debug, Clone, PartialEq)]

View file

@ -1,5 +1,5 @@
use crate::query::{QueryResult, TreeQuery, VisWindow};
use crate::{Clock, ElemId, Op};
use crate::types::{Clock, ElemId, Op};
use std::fmt::Debug;
#[derive(Debug, Clone, PartialEq)]

View file

@ -1,6 +1,6 @@
use crate::op_tree::{OpSetMetadata, OpTreeNode};
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;
#[derive(Debug, Clone, PartialEq)]

View file

@ -1,6 +1,6 @@
use crate::op_tree::{OpSetMetadata, OpTreeNode};
use crate::query::{binary_search_by, QueryResult, TreeQuery, VisWindow};
use crate::{Clock, Key, Op};
use crate::types::{Clock, Key, Op};
use std::fmt::Debug;
#[derive(Debug, Clone, PartialEq)]

View file

@ -1,6 +1,6 @@
use crate::op_tree::{OpSetMetadata, OpTreeNode};
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::fmt::Debug;

View file

@ -6,9 +6,10 @@ use std::{
io::Write,
};
use crate::types::Patch;
use crate::{
decoding, decoding::Decoder, encoding, encoding::Encodable, Automerge, AutomergeError, Change,
ChangeHash, Patch,
ChangeHash,
};
mod bloom;

View file

@ -1,6 +1,5 @@
use crate::error;
use crate::legacy as amp;
use crate::ScalarValue;
use serde::{Deserialize, Serialize};
use std::cmp::Eq;
use std::convert::TryFrom;
@ -9,8 +8,11 @@ use std::fmt;
use std::str::FromStr;
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 const ROOT: OpId = OpId(0, 0);
pub(crate) const ROOT: OpId = OpId(0, 0);
const ROOT_STR: &str = "_root";
const HEAD_STR: &str = "_head";
@ -161,23 +163,16 @@ pub enum OpType {
}
#[derive(Debug)]
pub enum Export {
pub(crate) enum Export {
Id(OpId),
Special(String),
Prop(usize),
}
pub trait Exportable {
pub(crate) trait Exportable {
fn export(&self) -> Export;
}
pub trait Importable {
fn wrap(id: OpId) -> Self;
fn from(s: &str) -> Option<Self>
where
Self: std::marker::Sized;
}
impl OpId {
#[inline]
pub fn counter(&self) -> u64 {
@ -234,45 +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 {
fn from(o: OpId) -> Self {
ObjId(o)
@ -352,11 +308,17 @@ impl Key {
}
#[derive(Debug, Clone, PartialOrd, Ord, Eq, PartialEq, Copy, Hash, Default)]
pub struct OpId(pub u64, pub usize);
pub(crate) struct OpId(pub u64, pub usize);
#[derive(Debug, Clone, Copy, PartialOrd, Eq, PartialEq, Ord, Hash, Default)]
pub(crate) struct ObjId(pub OpId);
impl ObjId {
pub fn root() -> Self {
ObjId(OpId(0, 0))
}
}
#[derive(Debug, Clone, Copy, PartialOrd, Eq, PartialEq, Ord, Hash, Default)]
pub(crate) struct ElemId(pub OpId);
@ -374,7 +336,11 @@ pub(crate) struct Op {
impl Op {
pub fn is_del(&self) -> bool {
matches!(self.action, OpType::Del)
matches!(&self.action, OpType::Del)
}
pub fn is_noop(&self, action: &OpType) -> bool {
matches!((&self.action, action), (OpType::Set(n), OpType::Set(m)) if n == m)
}
pub fn overwrites(&self, other: &Op) -> bool {
@ -389,6 +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)]
pub fn dump(&self) -> String {
match &self.action {

View file

@ -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 smol_str::SmolStr;
use std::convert::TryFrom;

View file

@ -24,7 +24,7 @@ pub(crate) struct Node<'a, const B: usize> {
#[derive(Clone)]
pub(crate) enum NodeType<'a, const B: usize> {
ObjRoot(crate::ObjId),
ObjRoot(crate::types::ObjId),
ObjTreeNode(&'a crate::op_tree::OpTreeNode<B>),
}
@ -225,7 +225,7 @@ impl OpTableRow {
impl OpTableRow {
fn create(
op: &super::Op,
op: &super::types::Op,
metadata: &crate::op_set::OpSetMetadata,
actor_shorthands: &HashMap<usize, String>,
) -> Self {
@ -236,8 +236,8 @@ impl OpTableRow {
crate::OpType::Inc(v) => format!("inc {}", v),
};
let prop = match op.key {
crate::Key::Map(k) => metadata.props[k].clone(),
crate::Key::Seq(e) => print_opid(&e.0, actor_shorthands),
crate::types::Key::Map(k) => metadata.props[k].clone(),
crate::types::Key::Seq(e) => print_opid(&e.0, actor_shorthands),
};
let succ = op
.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()])
}

View file

@ -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};
@ -42,7 +46,7 @@ pub fn sorted_actors() -> (automerge::ActorId, automerge::ActorId) {
/// map!{
/// "todos" => {
/// 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`,
/// `title`) in there? Well the `RealizedObject` contains all the changes in the document tagged by
/// OpId. This makes it easy to test for conflicts:
/// This might look more complicated than you were expecting. Why is the first element in the list
/// wrapped in braces? Because every property in an automerge document can have multiple
/// conflicting values we must capture all of these.
///
/// ```rust
/// 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_rules! assert_doc {
($doc: expr, $expected: expr) => {{
use $crate::helpers::{realize, ExportableOpId};
use $crate::helpers::realize;
let realized = realize($doc);
let to_export: RealizedObject<ExportableOpId<'_>> = $expected.into();
let exported = to_export.export($doc);
if realized != exported {
let expected_obj = $expected.into();
if realized != expected_obj {
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!(
"documents didn't match\n expected\n{}\n got\n{}",
&serde_left, &serde_right
);
}
pretty_assertions::assert_eq!(realized, exported);
}};
}
@ -105,63 +96,52 @@ macro_rules! assert_doc {
#[macro_export]
macro_rules! assert_obj {
($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 to_export: RealizedObject<ExportableOpId<'_>> = $expected.into();
let exported = to_export.export($doc);
if realized != exported {
let expected_obj = $expected.into();
if realized != expected_obj {
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!(
"documents didn't match\n expected\n{}\n got\n{}",
&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
/// 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!{
/// "key" => {
/// opid1 => "value1",
/// opid2 => "value2",
/// "value1",
/// "value2",
/// }
/// }
/// ```
///
/// 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
/// `map!` or `list!`.
/// anything which implements `Into<RealizedObject>`. Including nested calls to `map!` or `list!`.
#[macro_export]
macro_rules! map {
(@single $($x:tt)*) => (());
(@count $($rest:expr),*) => (<[()]>::len(&[$(map!(@single $rest)),*]));
(@inner { $($opid:expr => $value:expr,)+ }) => { map!(@inner { $($opid => $value),+ }) };
(@inner { $($opid:expr => $value:expr),* }) => {
(@inner { $($value:expr,)+ }) => { map!(@inner { $($value),+ }) };
(@inner { $($value:expr),* }) => {
{
use std::collections::HashMap;
let mut inner: HashMap<ExportableOpId<'_>, RealizedObject<ExportableOpId<'_>>> = HashMap::new();
use std::collections::BTreeSet;
let mut inner: BTreeSet<RealizedObject> = BTreeSet::new();
$(
let _ = inner.insert($opid.into(), $value.into());
let _ = inner.insert($value.into());
)*
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),*) => {
{
use std::collections::HashMap;
use crate::helpers::ExportableOpId;
let _cap = map!(@count $($key),*);
let mut _map: HashMap<String, HashMap<ExportableOpId<'_>, RealizedObject<ExportableOpId<'_>>>> = ::std::collections::HashMap::with_capacity(_cap);
use std::collections::{BTreeMap, BTreeSet};
let mut _map: BTreeMap<String, BTreeSet<RealizedObject>> = ::std::collections::BTreeMap::new();
$(
let inner = map!(@inner $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![
/// {
/// opid1 => "value1",
/// opid2 => "value2",
/// "value1",
/// "value2",
/// }
/// ]
/// ```
///
/// 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!`.
#[macro_export]
macro_rules! list {
(@single $($x:tt)*) => (());
(@count $($rest:tt),*) => (<[()]>::len(&[$(list!(@single $rest)),*]));
(@inner { $($opid:expr => $value:expr,)+ }) => { list!(@inner { $($opid => $value),+ }) };
(@inner { $($opid:expr => $value:expr),* }) => {
(@inner { $($value:expr,)+ }) => { list!(@inner { $($value),+ }) };
(@inner { $($value:expr),* }) => {
{
use std::collections::HashMap;
let mut inner: HashMap<ExportableOpId<'_>, RealizedObject<ExportableOpId<'_>>> = HashMap::new();
use std::collections::BTreeSet;
let mut inner: BTreeSet<RealizedObject> = BTreeSet::new();
$(
let _ = inner.insert($opid.into(), $value.into());
let _ = inner.insert($value.into());
)*
inner
}
@ -204,9 +184,8 @@ macro_rules! list {
($($inner:tt,)+) => { list!($($inner),+) };
($($inner:tt),*) => {
{
use crate::helpers::ExportableOpId;
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));
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 {
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
/// conflicts.
#[derive(PartialEq, Debug)]
pub enum RealizedObject<Oid: PartialEq + Eq + Hash> {
Map(HashMap<String, HashMap<Oid, RealizedObject<Oid>>>),
Sequence(Vec<HashMap<Oid, RealizedObject<Oid>>>),
Value(automerge::ScalarValue),
#[derive(PartialEq, PartialOrd, Ord, Eq, Hash, Debug)]
pub enum RealizedObject {
Map(BTreeMap<String, BTreeSet<RealizedObject>>),
Sequence(Vec<BTreeSet<RealizedObject>>),
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>
where
S: serde::Serializer,
@ -267,23 +284,17 @@ impl serde::Serialize for RealizedObject<ExportedOpId> {
match self {
Self::Map(kvs) => {
let mut map_ser = serializer.serialize_map(Some(kvs.len()))?;
for (k, kvs) in kvs {
let kvs_serded = kvs
.iter()
.map(|(opid, value)| (opid.to_string(), value))
.collect::<HashMap<String, &RealizedObject<ExportedOpId>>>();
map_ser.serialize_entry(k, &kvs_serded)?;
for (k, vs) in kvs {
let vs_serded = vs.iter().collect::<Vec<&RealizedObject>>();
map_ser.serialize_entry(k, &vs_serded)?;
}
map_ser.end()
}
Self::Sequence(elems) => {
let mut list_ser = serializer.serialize_seq(Some(elems.len()))?;
for elem in elems {
let kvs_serded = elem
.iter()
.map(|(opid, value)| (opid.to_string(), value))
.collect::<HashMap<String, &RealizedObject<ExportedOpId>>>();
list_ser.serialize_element(&kvs_serded)?;
let vs_serded = elem.iter().collect::<Vec<&RealizedObject>>();
list_ser.serialize_element(&vs_serded)?;
}
list_ser.end()
}
@ -292,30 +303,30 @@ impl serde::Serialize for RealizedObject<ExportedOpId> {
}
}
pub fn realize(doc: &automerge::Automerge) -> RealizedObject<ExportedOpId> {
realize_obj(doc, automerge::ROOT, automerge::ObjType::Map)
pub fn realize(doc: &automerge::Automerge) -> RealizedObject {
realize_obj(doc, &automerge::ROOT, automerge::ObjType::Map)
}
pub fn realize_prop<P: Into<automerge::Prop>>(
doc: &automerge::Automerge,
obj_id: automerge::OpId,
obj_id: &automerge::ObjId,
prop: P,
) -> RealizedObject<ExportedOpId> {
) -> RealizedObject {
let (val, obj_id) = doc.value(obj_id, prop).unwrap().unwrap();
match val {
automerge::Value::Object(obj_type) => realize_obj(doc, obj_id, obj_type),
automerge::Value::Scalar(v) => RealizedObject::Value(v),
automerge::Value::Object(obj_type) => realize_obj(doc, &obj_id, obj_type),
automerge::Value::Scalar(v) => RealizedObject::Value(OrdScalarValue::from(v)),
}
}
pub fn realize_obj(
doc: &automerge::Automerge,
obj_id: automerge::OpId,
obj_id: &automerge::ObjId,
objtype: automerge::ObjType,
) -> RealizedObject<ExportedOpId> {
) -> RealizedObject {
match objtype {
automerge::ObjType::Map | automerge::ObjType::Table => {
let mut result = HashMap::new();
let mut result = BTreeMap::new();
for key in doc.keys(obj_id) {
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>>(
doc: &automerge::Automerge,
obj_id: automerge::OpId,
obj_id: &automerge::ObjId,
key: K,
) -> HashMap<ExportedOpId, RealizedObject<ExportedOpId>> {
let mut values_by_opid = HashMap::new();
for (value, opid) in doc.values(obj_id, key).unwrap() {
) -> BTreeSet<RealizedObject> {
let mut values = BTreeSet::new();
for (value, objid) in doc.values(obj_id, key).unwrap() {
let realized = match value {
automerge::Value::Object(objtype) => realize_obj(doc, opid, objtype),
automerge::Value::Scalar(v) => RealizedObject::Value(v),
automerge::Value::Object(objtype) => realize_obj(doc, &objid, objtype),
automerge::Value::Scalar(v) => RealizedObject::Value(OrdScalarValue::from(v)),
};
let exported_opid = ExportedOpId(doc.export(opid));
values_by_opid.insert(exported_opid, realized);
values.insert(realized);
}
values_by_opid
values
}
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
.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>>>>
From<HashMap<&str, HashMap<O, I>>> for RealizedObject<ExportableOpId<'a>>
{
fn from(values: HashMap<&str, HashMap<O, I>>) -> Self {
impl<I: Into<RealizedObject>> From<BTreeMap<&str, BTreeSet<I>>> for RealizedObject {
fn from(values: BTreeMap<&str, BTreeSet<I>>) -> Self {
let intoed = values
.into_iter()
.map(|(k, v)| {
(
k.to_string(),
v.into_iter().map(|(k, v)| (k.into(), v.into())).collect(),
)
})
.map(|(k, v)| (k.to_string(), v.into_iter().map(|v| v.into()).collect()))
.collect();
RealizedObject::Map(intoed)
}
}
impl<'a, O: Into<ExportableOpId<'a>>, I: Into<RealizedObject<ExportableOpId<'a>>>>
From<Vec<HashMap<O, I>>> for RealizedObject<ExportableOpId<'a>>
{
fn from(values: Vec<HashMap<O, I>>) -> Self {
impl<I: Into<RealizedObject>> From<Vec<BTreeSet<I>>> for RealizedObject {
fn from(values: Vec<BTreeSet<I>>) -> Self {
RealizedObject::Sequence(
values
.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(),
)
}
}
impl From<bool> for RealizedObject<ExportableOpId<'_>> {
impl From<bool> for RealizedObject {
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 {
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 {
RealizedObject::Value(s)
RealizedObject::Value(OrdScalarValue::from(s))
}
}
impl From<&str> for RealizedObject<ExportableOpId<'_>> {
impl From<&str> for RealizedObject {
fn from(s: &str) -> Self {
RealizedObject::Value(automerge::ScalarValue::Str(s.into()))
}
}
#[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)
RealizedObject::Value(OrdScalarValue::Str(smol_str::SmolStr::from(s)))
}
}

File diff suppressed because it is too large Load diff

View file

@ -5,9 +5,9 @@ use std::fs;
fn replay_trace(commands: Vec<(usize, usize, Vec<Value>)>) -> Automerge {
let mut doc = Automerge::new();
let text = doc.set(ROOT, "text", Value::text()).unwrap().unwrap();
let text = doc.set(&ROOT, "text", Value::text()).unwrap().unwrap();
for (pos, del, vals) in commands {
doc.splice(text, pos, del, vals).unwrap();
doc.splice(&text, pos, del, vals).unwrap();
}
doc.commit(None, None);
doc

View file

@ -19,12 +19,12 @@ fn main() -> Result<(), AutomergeError> {
let mut doc = Automerge::new();
let now = Instant::now();
let text = doc.set(ROOT, "text", Value::text()).unwrap().unwrap();
let text = doc.set( &ROOT, "text", Value::text()).unwrap().unwrap();
for (i, (pos, del, vals)) in commands.into_iter().enumerate() {
if i % 1000 == 0 {
println!("Processed {} edits in {} ms", i, now.elapsed().as_millis());
}
doc.splice(text, pos, del, vals)?;
doc.splice(&text, pos, del, vals)?;
}
let _ = doc.save();
println!("Done in {} ms", now.elapsed().as_millis());