mark encode/decode/serde

This commit is contained in:
Orion Henry 2022-01-28 12:24:58 -05:00
parent b794f4803d
commit a2e433348a
12 changed files with 203 additions and 149 deletions

View file

@ -3,7 +3,7 @@ const assert = require('assert')
const util = require('util')
const { BloomFilter } = require('./helpers/sync')
const Automerge = require('..')
const { MAP, LIST, TEXT, initSyncState, decodeSyncMessage, decodeSyncState, encodeSyncState }= Automerge
const { MAP, LIST, TEXT, encodeChange, decodeChange, initSyncState, decodeSyncMessage, decodeSyncState, encodeSyncState }= Automerge
// str to uint8array
function en(str) {
@ -460,6 +460,15 @@ describe('Automerge', () => {
doc.insert(list, 2, "A")
spans = doc.spans(list);
assert.deepStrictEqual(spans, [ 'aa', [ [ 'bold', 'boolean', true ] ], 'AbA', [], 'cc' ])
// make sure save/load can handle marks
let doc2 = Automerge.load(doc.save())
spans = doc2.spans(list);
assert.deepStrictEqual(spans, [ 'aa', [ [ 'bold', 'boolean', true ] ], 'AbA', [], 'cc' ])
assert.deepStrictEqual(doc.getHeads(), doc2.getHeads())
assert.deepStrictEqual(doc.save(), doc2.save())
})
it.only('should handle overlapping marks', () => {
@ -489,6 +498,17 @@ describe('Automerge', () => {
[],
]
)
// mark sure encode decode can handle marks
let all = doc.getChanges([])
let decoded = all.map((c) => decodeChange(c))
let encoded = decoded.map((c) => encodeChange(c))
let doc2 = Automerge.init();
doc2.applyChanges(encoded)
assert.deepStrictEqual(doc.spans(list) , doc2.spans(list))
assert.deepStrictEqual(doc.save(), doc2.save())
})
})

View file

@ -6,7 +6,7 @@ use crate::exid::ExId;
use crate::op_set::OpSet;
use crate::types::{
ActorId, ChangeHash, Clock, ElemId, Export, Exportable, Key, ObjId, Op, OpId, OpType, Patch,
ScalarValue, Value, MarkData,
ScalarValue, Value,
};
use crate::{legacy, query, types, ObjType};
use crate::{AutomergeError, Change, Prop};
@ -322,26 +322,25 @@ impl Automerge {
value: V,
) -> Result<Option<ExId>, AutomergeError> {
let obj = self.exid_to_obj(obj)?;
if let Some(id) = self.do_insert(obj, index, value)? {
let value = value.into();
if let Some(id) = self.do_insert(obj, index, value.into())? {
Ok(Some(self.id_to_exid(id)))
} else {
Ok(None)
}
}
fn do_insert<V: Into<Value>>(
fn do_insert(
&mut self,
obj: ObjId,
index: usize,
value: V,
action: OpType,
) -> Result<Option<OpId>, AutomergeError> {
let id = self.next_id();
let value = value.into();
let query = self.ops.search(obj, query::InsertNth::new(index));
let key = query.key()?;
let action = value.into();
let is_make = matches!(&action, OpType::Make(_));
let op = Op {
@ -399,7 +398,7 @@ impl Automerge {
let mut results = Vec::new();
for v in vals {
// insert()
let id = self.do_insert(obj, pos, v.clone())?;
let id = self.do_insert(obj, pos, v.into())?;
if let Some(id) = id {
results.push(self.id_to_exid(id));
}
@ -465,8 +464,11 @@ impl Automerge {
value: ScalarValue,
) -> Result<(), AutomergeError> {
let obj = self.exid_to_obj(obj)?;
let query = self.ops.search(obj, query::Mark::new(start, end));
self.do_insert(obj, start, OpType::mark(mark.into(), start_sticky, value))?;
self.do_insert(obj, end, OpType::Unmark(end_sticky))?;
/*
let (a, b) = query.ops()?;
let (pos, key) = a;
let id = self.next_id();
@ -497,6 +499,7 @@ impl Automerge {
};
self.ops.insert(pos, op.clone());
self.tx().operations.push(op);
*/
Ok(())
}

View file

@ -11,7 +11,7 @@ use std::{
str,
};
use crate::types::{ActorId, ElemId, Key, ObjId, ObjType, Op, OpId, OpType, ScalarValue};
use crate::types::{ActorId, ElemId, Key, ObjId, ObjType, Op, OpId, OpType, ScalarValue, MarkData };
use crate::legacy as amp;
use amp::SortedVec;
@ -134,6 +134,15 @@ impl<'a> Iterator for OperationIterator<'a> {
Action::MakeTable => OpType::Make(ObjType::Table),
Action::Del => OpType::Del,
Action::Inc => OpType::Inc(value.to_i64()?),
Action::Mark => {
// mark has 3 things in the val column
let name = value.to_string()?;
let sticky = self.value.next()?.to_bool()?;
let value = self.value.next()?;
OpType::Mark(MarkData { name, sticky, value })
}
Action::Unmark => OpType::Unmark(value.to_bool()?),
Action::Unused => panic!("invalid action"),
};
Some(amp::Op {
action,
@ -175,6 +184,15 @@ impl<'a> Iterator for DocOpIterator<'a> {
Action::MakeTable => OpType::Make(ObjType::Table),
Action::Del => OpType::Del,
Action::Inc => OpType::Inc(value.to_i64()?),
Action::Mark => {
// mark has 3 things in the val column
let name = value.to_string()?;
let sticky = self.value.next()?.to_bool()?;
let value = self.value.next()?;
OpType::Mark(MarkData { name, sticky, value })
}
Action::Unmark => OpType::Unmark(value.to_bool()?),
Action::Unused => panic!("invalid action"),
};
Some(DocOp {
actor,
@ -1064,8 +1082,16 @@ impl DocOpEncoder {
self.val.append_null();
Action::Del
}
amp::OpType::Mark(_) => unimplemented!(),
amp::OpType::Unmark(_) => unimplemented!(),
amp::OpType::Mark(m) => {
self.val.append_value(&m.name.clone().into(), actors);
self.val.append_value(&m.sticky.into(), actors);
self.val.append_value(&m.value.clone().into(), actors);
Action::Mark
}
amp::OpType::Unmark(s) => {
self.val.append_value(&(*s).into(), actors);
Action::Unmark
}
amp::OpType::Make(kind) => {
self.val.append_null();
match kind {
@ -1172,8 +1198,16 @@ impl ColumnEncoder {
self.val.append_null();
Action::Del
}
OpType::Mark(_) => unimplemented!(),
OpType::Unmark(_) => unimplemented!(),
OpType::Mark(m) => {
self.val.append_value2(&m.name.clone().into(), actors);
self.val.append_value2(&m.sticky.into(), actors);
self.val.append_value2(&m.value.clone().into(), actors);
Action::Mark
}
OpType::Unmark(s) => {
self.val.append_value2(&(*s).into(), actors);
Action::Unmark
}
OpType::Make(kind) => {
self.val.append_null();
match kind {
@ -1279,8 +1313,11 @@ pub(crate) enum Action {
MakeText,
Inc,
MakeTable,
Mark,
Unused, // final bit is used to mask `Make` actions
Unmark,
}
const ACTIONS: [Action; 7] = [
const ACTIONS: [Action; 10] = [
Action::MakeMap,
Action::Set,
Action::MakeList,
@ -1288,6 +1325,9 @@ const ACTIONS: [Action; 7] = [
Action::MakeText,
Action::Inc,
Action::MakeTable,
Action::Mark,
Action::Unused,
Action::Unmark,
];
impl Decodable for Action {

View file

@ -49,6 +49,12 @@ impl Serialize for Op {
match &self.action {
OpType::Inc(n) => op.serialize_field("value", &n)?,
OpType::Set(value) => op.serialize_field("value", &value)?,
OpType::Mark(m) => {
op.serialize_field("name", &m.name)?;
op.serialize_field("sticky", &m.sticky)?;
op.serialize_field("value", &m.value)?;
}
OpType::Unmark(s) => op.serialize_field("sticky", &s)?,
_ => {}
}
op.serialize_field("pred", &self.pred)?;
@ -70,6 +76,8 @@ pub(crate) enum RawOpType {
Del,
Inc,
Set,
Mark,
Unmark,
}
impl Serialize for RawOpType {
@ -85,6 +93,8 @@ impl Serialize for RawOpType {
RawOpType::Del => "del",
RawOpType::Inc => "inc",
RawOpType::Set => "set",
RawOpType::Mark => "mark",
RawOpType::Unmark => "unmark",
};
serializer.serialize_str(s)
}
@ -103,6 +113,8 @@ impl<'de> Deserialize<'de> for RawOpType {
"del",
"inc",
"set",
"mark",
"unmark",
];
// TODO: Probably more efficient to deserialize to a `&str`
let raw_type = String::deserialize(deserializer)?;
@ -114,6 +126,8 @@ impl<'de> Deserialize<'de> for RawOpType {
"del" => Ok(RawOpType::Del),
"inc" => Ok(RawOpType::Inc),
"set" => Ok(RawOpType::Set),
"mark" => Ok(RawOpType::Mark),
"unmark" => Ok(RawOpType::Unmark),
other => Err(Error::unknown_variant(other, VARIANTS)),
}
}
@ -144,6 +158,8 @@ impl<'de> Deserialize<'de> for Op {
let mut insert: Option<bool> = None;
let mut datatype: Option<DataType> = None;
let mut value: Option<Option<ScalarValue>> = None;
let mut name: Option<String> = None;
let mut sticky: Option<bool> = None;
let mut ref_id: Option<OpId> = None;
while let Some(field) = map.next_key::<String>()? {
match field.as_ref() {
@ -167,6 +183,8 @@ impl<'de> Deserialize<'de> for Op {
"insert" => read_field("insert", &mut insert, &mut map)?,
"datatype" => read_field("datatype", &mut datatype, &mut map)?,
"value" => read_field("value", &mut value, &mut map)?,
"name" => read_field("name", &mut name, &mut map)?,
"sticky" => read_field("sticky", &mut sticky, &mut map)?,
"ref" => read_field("ref", &mut ref_id, &mut map)?,
_ => return Err(Error::unknown_field(&field, FIELDS)),
}
@ -182,6 +200,30 @@ impl<'de> Deserialize<'de> for Op {
RawOpType::MakeList => OpType::Make(ObjType::List),
RawOpType::MakeText => OpType::Make(ObjType::Text),
RawOpType::Del => OpType::Del,
RawOpType::Mark => {
let name = name.ok_or_else(|| Error::missing_field("mark(name)"))?;
let sticky = sticky.unwrap_or(false);
let value = if let Some(datatype) = datatype {
let raw_value = value
.ok_or_else(|| Error::missing_field("value"))?
.unwrap_or(ScalarValue::Null);
raw_value.as_datatype(datatype).map_err(|e| {
Error::invalid_value(
Unexpected::Other(e.unexpected.as_str()),
&e.expected.as_str(),
)
})?
} else {
value
.ok_or_else(|| Error::missing_field("value"))?
.unwrap_or(ScalarValue::Null)
};
OpType::mark(name, sticky, value)
}
RawOpType::Unmark => {
let sticky = sticky.unwrap_or(true);
OpType::Unmark(sticky)
}
RawOpType::Set => {
let value = if let Some(datatype) = datatype {
let raw_value = value

View file

@ -15,8 +15,8 @@ impl Serialize for OpType {
OpType::Make(ObjType::Table) => RawOpType::MakeTable,
OpType::Make(ObjType::List) => RawOpType::MakeList,
OpType::Make(ObjType::Text) => RawOpType::MakeText,
OpType::Mark(_) => unimplemented!(),
OpType::Unmark(_) => unimplemented!(),
OpType::Mark(_) => RawOpType::Mark,
OpType::Unmark(_) => RawOpType::Unmark,
OpType::Del => RawOpType::Del,
OpType::Inc(_) => RawOpType::Inc,
OpType::Set(_) => RawOpType::Set,

View file

@ -2,4 +2,3 @@ mod element_id;
mod key;
mod object_id;
mod opid;
mod scalar_value;

View file

@ -1,57 +0,0 @@
use std::fmt;
use smol_str::SmolStr;
use crate::value::ScalarValue;
impl From<&str> for ScalarValue {
fn from(s: &str) -> Self {
ScalarValue::Str(s.into())
}
}
impl From<i64> for ScalarValue {
fn from(n: i64) -> Self {
ScalarValue::Int(n)
}
}
impl From<u64> for ScalarValue {
fn from(n: u64) -> Self {
ScalarValue::Uint(n)
}
}
impl From<i32> for ScalarValue {
fn from(n: i32) -> Self {
ScalarValue::Int(n as i64)
}
}
impl From<bool> for ScalarValue {
fn from(b: bool) -> Self {
ScalarValue::Boolean(b)
}
}
impl From<char> for ScalarValue {
fn from(c: char) -> Self {
ScalarValue::Str(SmolStr::new(c.to_string()))
}
}
impl fmt::Display for ScalarValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ScalarValue::Bytes(b) => write!(f, "\"{:?}\"", b),
ScalarValue::Str(s) => write!(f, "\"{}\"", s),
ScalarValue::Int(i) => write!(f, "{}", i),
ScalarValue::Uint(i) => write!(f, "{}", i),
ScalarValue::F64(n) => write!(f, "{:.324}", n),
ScalarValue::Counter(c) => write!(f, "Counter: {}", c),
ScalarValue::Timestamp(i) => write!(f, "Timestamp: {}", i),
ScalarValue::Boolean(b) => write!(f, "{}", b),
ScalarValue::Null => write!(f, "null"),
}
}
}

View file

@ -12,7 +12,6 @@ mod len;
mod len_at;
mod list_vals;
mod list_vals_at;
mod mark;
mod nth;
mod nth_at;
mod prop;
@ -27,7 +26,6 @@ pub(crate) use len::Len;
pub(crate) use len_at::LenAt;
pub(crate) use list_vals::ListVals;
pub(crate) use list_vals_at::ListValsAt;
pub(crate) use mark::Mark;
pub(crate) use nth::Nth;
pub(crate) use nth_at::NthAt;
pub(crate) use prop::Prop;

View file

@ -1,71 +0,0 @@
use crate::AutomergeError;
use crate::query::{QueryResult, TreeQuery};
use crate::types::{ElemId, Key, Op};
use std::fmt::Debug;
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct Mark<const B: usize> {
start: usize,
end: usize,
pos: usize,
seen: usize,
_ops: Vec<(usize, Key)>,
count: usize,
last_seen: Option<ElemId>,
last_insert: Option<ElemId>,
}
impl<const B: usize> Mark<B> {
pub fn new(start: usize, end: usize) -> Self {
Mark {
start,
end,
pos: 0,
seen: 0,
_ops: Vec::new(),
count: 0,
last_seen: None,
last_insert: None,
}
}
pub fn ops(&self) -> Result<((usize,Key),(usize,Key)),AutomergeError> {
if self._ops.len() == 2 {
Ok((self._ops[0], self._ops[1]))
} else if self._ops.len() == 1 {
Ok((self._ops[0], (self.pos + 1, self.last_insert.into())))
} else {
Err(AutomergeError::Fail)
}
}
}
impl<const B: usize> TreeQuery<B> for Mark<B> {
/*
fn query_node(&mut self, _child: &OpTreeNode<B>) -> QueryResult {
unimplemented!()
}
*/
fn query_element(&mut self, element: &Op) -> QueryResult {
// find location to insert
// mark or set
if element.insert {
if self.seen >= self.end {
self._ops.push((self.pos + 1, self.last_insert.into()));
return QueryResult::Finish;
}
if self.seen >= self.start && self._ops.is_empty() {
self._ops.push((self.pos, self.last_insert.into()));
}
self.last_seen = None;
self.last_insert = element.elemid();
}
if self.last_seen.is_none() && element.visible() {
self.seen += 1;
self.last_seen = element.elemid()
}
self.pos += 1;
QueryResult::Next
}
}

View file

@ -53,9 +53,11 @@ impl<const B: usize> Spans<B> {
if self.changed && (self.seen_at_last_mark != self.seen_at_this_mark || self.seen_at_last_mark.is_none() && self.seen_at_this_mark.is_none()) {
self.changed = false;
self.seen_at_last_mark = self.seen_at_this_mark;
let mut marks : Vec<_> = self.marks.iter().map(|(key, val)| (key.clone(), val.clone())).collect();
marks.sort_by(|(k1,_),(k2,_)| k1.cmp(k2));
self.spans.push(Span {
pos: self.seen,
marks: self.marks.iter().map(|(key, val)| (key.clone(), val.clone())).collect()
marks,
});
}
}

View file

@ -164,6 +164,12 @@ pub enum OpType {
Unmark(bool),
}
impl OpType {
pub (crate) fn mark(name: String, sticky: bool, value: ScalarValue) -> Self {
OpType::Mark(MarkData { name, sticky, value })
}
}
#[derive(PartialEq, Debug, Clone)]
pub struct MarkData {
pub name: String,

View file

@ -375,7 +375,79 @@ impl ScalarValue {
}
}
pub fn to_bool(self) -> Option<bool> {
match self {
ScalarValue::Boolean(b) => Some(b),
_ => None,
}
}
pub fn to_string(self) -> Option<String> {
match self {
ScalarValue::Str(s) => Some(s.to_string()),
_ => None,
}
}
pub fn counter(n: i64) -> ScalarValue {
ScalarValue::Counter(n.into())
}
}
impl From<&str> for ScalarValue {
fn from(s: &str) -> Self {
ScalarValue::Str(s.into())
}
}
impl From<String> for ScalarValue {
fn from(s: String) -> Self {
ScalarValue::Str(s.into())
}
}
impl From<i64> for ScalarValue {
fn from(n: i64) -> Self {
ScalarValue::Int(n)
}
}
impl From<u64> for ScalarValue {
fn from(n: u64) -> Self {
ScalarValue::Uint(n)
}
}
impl From<i32> for ScalarValue {
fn from(n: i32) -> Self {
ScalarValue::Int(n as i64)
}
}
impl From<bool> for ScalarValue {
fn from(b: bool) -> Self {
ScalarValue::Boolean(b)
}
}
impl From<char> for ScalarValue {
fn from(c: char) -> Self {
ScalarValue::Str(SmolStr::new(c.to_string()))
}
}
impl fmt::Display for ScalarValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ScalarValue::Bytes(b) => write!(f, "\"{:?}\"", b),
ScalarValue::Str(s) => write!(f, "\"{}\"", s),
ScalarValue::Int(i) => write!(f, "{}", i),
ScalarValue::Uint(i) => write!(f, "{}", i),
ScalarValue::F64(n) => write!(f, "{:.324}", n),
ScalarValue::Counter(c) => write!(f, "Counter: {}", c),
ScalarValue::Timestamp(i) => write!(f, "Timestamp: {}", i),
ScalarValue::Boolean(b) => write!(f, "{}", b),
ScalarValue::Null => write!(f, "null"),
}
}
}