save_incremental()
This commit is contained in:
parent
0cc815ef74
commit
bbfb2337d5
5 changed files with 142 additions and 65 deletions
7
TODO.js
7
TODO.js
|
@ -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
|
||||
|
|
@ -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>() {
|
||||
|
|
|
@ -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());
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
Loading…
Add table
Reference in a new issue