save_incremental()

This commit is contained in:
Orion Henry 2021-12-06 13:01:27 -05:00
parent 0cc815ef74
commit bbfb2337d5
5 changed files with 142 additions and 65 deletions
TODO.js
automerge-wasm
automerge/src

View file

@ -1,7 +0,0 @@
1. remove the need for a toJS() by using a proxy
1. get del and overwrites to work properly (pred,succ)
1. makeList
1. insert, del, overwrite on a list
1. cursors

View file

@ -264,13 +264,19 @@ impl Automerge {
self.0.del(&obj, prop).map_err(to_js_err)
}
pub fn save(&self) -> Result<Uint8Array, JsValue> {
pub fn save(&mut self) -> Result<Uint8Array, JsValue> {
self.0
.save()
.map(|v| js_sys::Uint8Array::from(v.as_slice()))
.map_err(to_js_err)
}
pub fn save_incremental(&mut self) -> JsValue {
self.0
.save_incremental()
.map(|v| js_sys::Uint8Array::from(v.as_slice())).into()
}
#[wasm_bindgen(js_name = applyChanges)]
pub fn apply_changes(&mut self, changes: Array) -> Result<(), JsValue> {
let deps: Result<Vec<js_sys::Uint8Array>, _> =
@ -411,6 +417,12 @@ impl Automerge {
} else if let Some(s) = value.as_string() {
// FIXME - we need to detect str vs int vs float vs bool here :/
Ok(am::ScalarValue::Str(s.into()).into())
} else if let Some(n) = value.as_f64() {
if n.round() == n {
Ok(am::ScalarValue::Int((n as i64).into()).into())
} else {
Ok(am::ScalarValue::F64(n.into()).into())
}
} else if let Some(o) = to_objtype(&value) {
Ok(o.into())
} else if let Ok(o) = &value.dyn_into::<Uint8Array>() {

View file

@ -151,5 +151,38 @@ describe('Automerge', () => {
assert.deepEqual(doc.value(text, 12),["str","?"])
doc.commit()
})
it('should be able save all or incrementally', () => {
let doc = Automerge.init()
doc.begin()
doc.set("_root", "foo", 1)
doc.commit()
let save1 = doc.save()
doc.begin()
doc.set("_root", "bar", 2)
doc.commit()
let save2 = doc.save_incremental()
doc.begin()
doc.set("_root", "baz", 3)
doc.commit()
let save3 = doc.save_incremental()
let saveA = doc.save();
let saveB = new Uint8Array([... save1, ...save2, ...save3]);
assert.notDeepEqual(saveA, saveB);
let docA = Automerge.load(saveA);
let docB = Automerge.load(saveB);
assert.deepEqual(docA.keys("_root"), docB.keys("_root"));
assert.deepEqual(docA.save(), docB.save());
})
})
})

View file

@ -2,20 +2,15 @@ extern crate hex;
extern crate uuid;
extern crate web_sys;
#[cfg(target_familt = "wasm")]
macro_rules! log {
( $( $t:tt )* ) => {
#[cfg(target_family = "wasm")]
web_sys::console::log_1(&format!( $( $t )* ).into());
#[cfg(not(target_family = "wasm"))]
println!( $( $t )* );
}
}
#[cfg(not(target_familt = "wasm"))]
macro_rules! log {
( $( $t:tt )* ) => {
println!( $( $t )* );
}
}
mod change;
mod columnar;
mod decoding;
@ -62,8 +57,7 @@ pub struct Automerge {
history_index: HashMap<ChangeHash, usize>,
states: HashMap<usize, Vec<usize>>,
deps: HashSet<ChangeHash>,
//ops: Vec<Op>,
//ops: SequenceTree<Op>,
saved: Vec<ChangeHash>,
ops: OpTree,
actor: Option<usize>,
max_op: u64,
@ -81,6 +75,7 @@ impl Automerge {
states: HashMap::new(),
ops: Default::default(),
deps: Default::default(),
saved: Default::default(),
actor: None,
max_op: 0,
transaction: None,
@ -124,6 +119,7 @@ impl Automerge {
states: HashMap::new(),
ops: Default::default(),
deps: Default::default(),
saved: Default::default(),
actor: None,
max_op: 0,
transaction: None,
@ -188,11 +184,10 @@ impl Automerge {
pub fn rollback(&mut self) {
if let Some(tx) = self.transaction.take() {
for op in &tx.operations {
// FIXME - use query to make this fast
for pred_id in &op.pred {
if let Some(p) = self.ops.iter().position(|o| o.id == *pred_id) {
//if let Some(o) = self.ops.get_mut(p) {
self.ops.replace(p, |o| o.succ.retain(|i| i != pred_id));
//}
}
}
if let Some(pos) = self.ops.iter().position(|o| o.id == op.id) {
@ -295,8 +290,6 @@ impl Automerge {
}
*pos += 1;
}
//let tmp = self.ops.seek_obj(obj, &self.actors);
//assert_eq!(*pos, tmp);
}
fn scan_to_prop_start(&self, obj: &ObjId, key: &Key, pos: &mut usize) {
@ -386,14 +379,6 @@ impl Automerge {
next.pred.push(op.id);
}
});
/*
if let Some(op) = self.ops.get_mut(vpos) {
op.succ.push(next.id);
if local {
next.pred.push(op.id);
}
}
*/
}
}
@ -629,12 +614,10 @@ impl Automerge {
.map(|e| (e.into(), size_hint))
}
// idea!
// set(obj, prop, value) - value can be scalar or objtype
// insert(obj, prop, value)
// del(obj, prop)
// inc(obj, prop)
// what about AT?
// inc(obj, prop, value)
// insert(obj, index, value)
pub fn set(&mut self, obj: &ObjId, prop: Prop, value: Value) -> Result<OpId, AutomergeError> {
let (key, pos_hint) = self.import_prop(obj, prop, false)?;
@ -677,7 +660,6 @@ impl Automerge {
del: usize,
vals: Vec<Value>,
) -> Result<(), AutomergeError> {
//println!("SPLICE {}/{}/{} , {} , {:?}", pos, self.ops.len(), self.ops.list_len(obj), del, vals);
for _ in 0..del {
self.del(obj, pos.into())?;
}
@ -821,15 +803,30 @@ impl Automerge {
.collect()
}
pub fn save(&self) -> Result<Vec<u8>, AutomergeError> {
pub fn save(&mut self) -> Result<Vec<u8>, AutomergeError> {
// TODO - would be nice if I could pass an iterator instead of a collection here
let c: Vec<_> = self.history.iter().map(|c| c.decode()).collect();
// FIXME
let ops: Vec<_> = self.ops.iter().cloned().collect();
encode_document(&c, ops.as_slice(), &self.actors, &self.props.cache)
// TODO - can we make encode_document error free
let bytes = encode_document(&c, ops.as_slice(), &self.actors, &self.props.cache);
if bytes.is_ok() {
self.saved = self.get_heads().iter().copied().collect();
}
bytes
}
pub fn save_incremental(&mut self) -> Vec<u8> {
unimplemented!()
pub fn save_incremental(&mut self) -> Option<Vec<u8>> {
let changes = self.get_changes(self.saved.as_slice());
let mut bytes = vec![];
for c in changes {
bytes.extend(c.raw_bytes());
}
if !bytes.is_empty() {
self.saved = self.get_heads().iter().copied().collect();
Some(bytes)
} else {
None
}
}
/// Filter the changes down to those that are not transitive dependencies of the heads.
@ -1185,24 +1182,6 @@ impl Default for Automerge {
}
}
impl Default for OpId {
fn default() -> Self {
OpId(0, 0)
}
}
impl Default for ObjId {
fn default() -> Self {
ObjId(Default::default())
}
}
impl Default for ElemId {
fn default() -> Self {
ElemId(Default::default())
}
}
pub(crate) fn key_cmp(left: &Key, right: &Key, props: &IndexedCache<String>) -> Ordering {
match (left, right) {
(Key::Map(a), Key::Map(b)) => props[*a].cmp(&props[*b]),
@ -1362,4 +1341,49 @@ mod tests {
doc.commit()?;
Ok(())
}
#[test]
fn test_save_incremental() -> Result<(), AutomergeError> {
let mut doc = Automerge::new();
doc.begin()?;
doc.set(&ROOT, "foo".into(), 1.into())?;
doc.commit()?;
let save1 = doc.save().unwrap();
doc.begin()?;
doc.set(&ROOT, "bar".into(), 2.into())?;
doc.commit()?;
let save2 = doc.save_incremental().unwrap();
doc.begin()?;
doc.set(&ROOT, "baz".into(), 3.into())?;
doc.commit()?;
let save3 = doc.save_incremental().unwrap();
let mut save_a: Vec<u8> = vec![];
save_a.extend(&save1);
save_a.extend(&save2);
save_a.extend(&save3);
assert!(doc.save_incremental().is_none());
let save_b = doc.save().unwrap();
assert!(save_b.len() < save_a.len());
let mut doc_a = Automerge::load(&save_a)?;
let mut doc_b = Automerge::load(&save_b)?;
assert!(doc_a.values(&ROOT, "baz".into())? == doc_b.values(&ROOT, "baz".into())?);
assert!(doc_a.save().unwrap() == doc_b.save().unwrap());
doc_a.dump();
Ok(())
}
}

View file

@ -1,6 +1,3 @@
//#![allow(unused_variables)]
//#![allow(dead_code)]
extern crate hex;
extern crate uuid;
extern crate web_sys;
@ -212,6 +209,24 @@ impl From<String> for Value {
}
}
impl From<i64> for Value {
fn from(n: i64) -> Self {
Value::Scalar(amp::ScalarValue::Int(n))
}
}
impl From<i32> for Value {
fn from(n: i32) -> Self {
Value::Scalar(amp::ScalarValue::Int(n.into()))
}
}
impl From<u64> for Value {
fn from(n: u64) -> Self {
Value::Scalar(amp::ScalarValue::Uint(n))
}
}
impl From<amp::ObjType> for Value {
fn from(o: amp::ObjType) -> Self {
Value::Object(o)
@ -256,7 +271,7 @@ pub enum Prop {
Seq(usize),
}
#[derive(Debug, PartialEq, PartialOrd, Eq, Ord, Clone, Copy)]
#[derive(Debug, PartialEq, PartialOrd, Eq, Ord, Clone)]
pub struct Patch {}
impl Key {
@ -268,13 +283,13 @@ impl Key {
}
}
#[derive(Debug, Clone, PartialOrd, Ord, Eq, PartialEq, Copy, Hash)]
#[derive(Debug, Clone, PartialOrd, Ord, Eq, PartialEq, Copy, Hash, Default)]
pub struct OpId(pub u64, pub usize);
#[derive(Debug, Clone, Copy, PartialOrd, Eq, PartialEq, Ord, Hash)]
#[derive(Debug, Clone, Copy, PartialOrd, Eq, PartialEq, Ord, Hash, Default)]
pub struct ObjId(pub OpId);
#[derive(Debug, Clone, Copy, PartialOrd, Eq, PartialEq, Ord, Hash)]
#[derive(Debug, Clone, Copy, PartialOrd, Eq, PartialEq, Ord, Hash, Default)]
pub struct ElemId(pub OpId);
#[derive(Debug, Clone, PartialEq)]