Compare commits

...

1 commit

Author SHA1 Message Date
Orion Henry
bc9f0583be swap out vec for seq_tree in observer for big patch 2022-11-03 12:10:29 -05:00
9 changed files with 130 additions and 26 deletions

View file

@ -557,7 +557,7 @@ impl Automerge {
Reflect::set(&result, &(*index as f64).into(), &sub_val)?; Reflect::set(&result, &(*index as f64).into(), &sub_val)?;
Ok(result.into()) Ok(result.into())
} }
Patch::DeleteSeq { index, .. } => self.sub_splice(result, *index, 1, &[], meta), Patch::DeleteSeq { index, .. } => self.sub_splice(result, *index, 1, vec![], meta),
Patch::Insert { index, values, .. } => self.sub_splice(result, *index, 0, values, meta), Patch::Insert { index, values, .. } => self.sub_splice(result, *index, 0, values, meta),
Patch::Increment { prop, value, .. } => { Patch::Increment { prop, value, .. } => {
if let Prop::Seq(index) = prop { if let Prop::Seq(index) = prop {
@ -650,16 +650,16 @@ impl Automerge {
self.wrap_object(result, datatype, &id, meta) self.wrap_object(result, datatype, &id, meta)
} }
fn sub_splice( fn sub_splice<'a, I: IntoIterator<Item = &'a (Value<'a>, ObjId)>>(
&self, &self,
o: Array, o: Array,
index: usize, index: usize,
num_del: usize, num_del: usize,
values: &[(Value<'_>, ObjId)], values: I,
meta: &JsValue, meta: &JsValue,
) -> Result<Object, JsValue> { ) -> Result<Object, JsValue> {
let args: Array = values let args: Array = values
.iter() .into_iter()
.map(|v| self.maybe_wrap_object(alloc(&v.0), &v.1, meta)) .map(|v| self.maybe_wrap_object(alloc(&v.0), &v.1, meta))
.collect::<Result<_, _>>()?; .collect::<Result<_, _>>()?;
args.unshift(&(num_del as u32).into()); args.unshift(&(num_del as u32).into());

View file

@ -1,7 +1,7 @@
#![allow(dead_code)] #![allow(dead_code)]
use crate::interop::{alloc, js_set}; use crate::interop::{alloc, js_set};
use automerge::{ObjId, OpObserver, Parents, Prop, Value}; use automerge::{ObjId, OpObserver, Parents, Prop, Value, SequenceTree};
use js_sys::{Array, Object}; use js_sys::{Array, Object};
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
@ -45,7 +45,7 @@ pub(crate) enum Patch {
obj: ObjId, obj: ObjId,
path: Vec<(ObjId, Prop)>, path: Vec<(ObjId, Prop)>,
index: usize, index: usize,
values: Vec<(Value<'static>, ObjId)>, values: SequenceTree<(Value<'static>, ObjId)>,
}, },
Increment { Increment {
obj: ObjId, obj: ObjId,
@ -91,11 +91,13 @@ impl OpObserver for Observer {
} }
} }
let path = parents.path(); let path = parents.path();
let mut values = SequenceTree::new();
values.push(value);
let patch = Patch::Insert { let patch = Patch::Insert {
path, path,
obj, obj,
index, index,
values: vec![value], values,
}; };
self.patches.push(patch); self.patches.push(patch);
} }

View file

@ -77,6 +77,7 @@ mod op_set;
mod op_tree; mod op_tree;
mod parents; mod parents;
mod query; mod query;
mod sequence_tree;
mod storage; mod storage;
pub mod sync; pub mod sync;
pub mod transaction; pub mod transaction;
@ -105,6 +106,7 @@ pub use op_observer::OpObserver;
pub use op_observer::Patch; pub use op_observer::Patch;
pub use op_observer::VecOpObserver; pub use op_observer::VecOpObserver;
pub use parents::Parents; pub use parents::Parents;
pub use sequence_tree::SequenceTree;
pub use types::{ActorId, ChangeHash, ObjType, OpType, Prop}; pub use types::{ActorId, ChangeHash, ObjType, OpType, Prop};
pub use value::{ScalarValue, Value}; pub use value::{ScalarValue, Value};
pub use values::Values; pub use values::Values;

View file

@ -4,21 +4,22 @@ use std::{
mem, mem,
}; };
pub type SequenceTree<T> = SequenceTreeInternal<T, 25>; pub(crate) const B: usize = 500;
pub type SequenceTree<T> = SequenceTreeInternal<T>;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct SequenceTreeInternal<T> { pub struct SequenceTreeInternal<T> {
root_node: Option<SequenceTreeNode<T, B>>, root_node: Option<SequenceTreeNode<T>>,
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
struct SequenceTreeNode<T> { struct SequenceTreeNode<T> {
elements: Vec<T>, elements: Vec<T>,
children: Vec<SequenceTreeNode<T, B>>, children: Vec<SequenceTreeNode<T>>,
length: usize, length: usize,
} }
impl<T> SequenceTreeInternal<T, B> impl<T> SequenceTreeInternal<T>
where where
T: Clone + Debug, T: Clone + Debug,
{ {
@ -38,7 +39,7 @@ where
} }
/// Create an iterator through the sequence. /// Create an iterator through the sequence.
pub fn iter(&self) -> Iter<'_, T, B> { pub fn iter(&self) -> Iter<'_, T> {
Iter { Iter {
inner: self, inner: self,
index: 0, index: 0,
@ -145,7 +146,7 @@ where
} }
} }
impl<T> SequenceTreeNode<T, B> impl<T> SequenceTreeNode<T>
where where
T: Clone + Debug, T: Clone + Debug,
{ {
@ -157,7 +158,7 @@ where
} }
} }
pub fn len(&self) -> usize { pub(crate) fn len(&self) -> usize {
self.length self.length
} }
@ -380,7 +381,7 @@ where
l l
} }
pub fn remove(&mut self, index: usize) -> T { pub(crate) fn remove(&mut self, index: usize) -> T {
let original_len = self.len(); let original_len = self.len();
if self.is_leaf() { if self.is_leaf() {
let v = self.remove_from_leaf(index); let v = self.remove_from_leaf(index);
@ -423,7 +424,7 @@ where
} }
} }
fn merge(&mut self, middle: T, successor_sibling: SequenceTreeNode<T, B>) { fn merge(&mut self, middle: T, successor_sibling: SequenceTreeNode<T>) {
self.elements.push(middle); self.elements.push(middle);
self.elements.extend(successor_sibling.elements); self.elements.extend(successor_sibling.elements);
self.children.extend(successor_sibling.children); self.children.extend(successor_sibling.children);
@ -431,7 +432,7 @@ where
assert!(self.is_full()); assert!(self.is_full());
} }
pub fn set(&mut self, index: usize, element: T) -> T { pub(crate) fn set(&mut self, index: usize, element: T) -> T {
if self.is_leaf() { if self.is_leaf() {
let old_element = self.elements.get_mut(index).unwrap(); let old_element = self.elements.get_mut(index).unwrap();
mem::replace(old_element, element) mem::replace(old_element, element)
@ -455,7 +456,7 @@ where
} }
} }
pub fn get(&self, index: usize) -> Option<&T> { pub(crate) fn get(&self, index: usize) -> Option<&T> {
if self.is_leaf() { if self.is_leaf() {
return self.elements.get(index); return self.elements.get(index);
} else { } else {
@ -475,7 +476,7 @@ where
None None
} }
pub fn get_mut(&mut self, index: usize) -> Option<&mut T> { pub(crate) fn get_mut(&mut self, index: usize) -> Option<&mut T> {
if self.is_leaf() { if self.is_leaf() {
return self.elements.get_mut(index); return self.elements.get_mut(index);
} else { } else {
@ -496,7 +497,7 @@ where
} }
} }
impl<T> Default for SequenceTreeInternal<T, B> impl<T> Default for SequenceTreeInternal<T>
where where
T: Clone + Debug, T: Clone + Debug,
{ {
@ -505,7 +506,7 @@ where
} }
} }
impl<T> PartialEq for SequenceTreeInternal<T, B> impl<T> PartialEq for SequenceTreeInternal<T>
where where
T: Clone + Debug + PartialEq, T: Clone + Debug + PartialEq,
{ {
@ -514,13 +515,13 @@ where
} }
} }
impl<'a, T> IntoIterator for &'a SequenceTreeInternal<T, B> impl<'a, T> IntoIterator for &'a SequenceTreeInternal<T>
where where
T: Clone + Debug, T: Clone + Debug,
{ {
type Item = &'a T; type Item = &'a T;
type IntoIter = Iter<'a, T, B>; type IntoIter = Iter<'a, T>;
fn into_iter(self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
Iter { Iter {
@ -530,12 +531,13 @@ where
} }
} }
#[derive(Debug)]
pub struct Iter<'a, T> { pub struct Iter<'a, T> {
inner: &'a SequenceTreeInternal<T, B>, inner: &'a SequenceTreeInternal<T>,
index: usize, index: usize,
} }
impl<'a, T> Iterator for Iter<'a, T, B> impl<'a, T> Iterator for Iter<'a, T>
where where
T: Clone + Debug, T: Clone + Debug,
{ {

View file

@ -0,0 +1,30 @@
//import * as Automerge from "@automerge/automerge"
import * as Automerge from "@automerge/automerge"
import * as fs from "fs"
const start = new Date()
let state = Automerge.from({text: new Automerge.Text()})
type WithInsert = [number, number, string]
type WithoutInsert = [number, number]
const edits: Array<WithInsert | WithoutInsert> = JSON.parse(fs.readFileSync("./edits.json", {encoding: "utf8"}))
state = Automerge.change(state, doc => {
const start2 = new Date()
for (let i = 0; i < edits.length; i++) {
if (i % 1000 === 0) {
const elapsed2 = (new Date() as any) - (start2 as any)
console.log(`processed 1000 edits in ${elapsed2}`)
}
let edit = edits[i]
let [start, del, values] = edit
doc.text.deleteAt!(start, del)
if (values != null) {
doc.text.insertAt!(start, ...values)
}
}
})
let elapsed = (new Date() as any) - (start as any)
console.log(`Done in ${elapsed} ms`)

View file

@ -1,5 +1,5 @@
const { edits, finalText } = require('./editing-trace') const { edits, finalText } = require('./editing-trace')
const Automerge = require('../automerge-wasm') const Automerge = require('@automerge/automerge-wasm')
const start = new Date() const start = new Date()

View file

@ -4,9 +4,18 @@
"main": "wasm-text.js", "main": "wasm-text.js",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"build": "webpack",
"wasm": "0x -D prof wasm-text.js" "wasm": "0x -D prof wasm-text.js"
}, },
"devDependencies": { "devDependencies": {
"0x": "^4.11.0" "0x": "^4.11.0"
},
"dependencies": {
"@automerge/automerge": "^2.0.0-beta.4",
"ts-loader": "^9.4.1",
"typescript": "^4.8.4",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0",
"webpack-node-externals": "^3.0.0"
} }
} }

View file

@ -0,0 +1,22 @@
{
"compilerOptions": {
"target": "es2016",
"sourceMap": false,
"declaration": true,
"resolveJsonModule": true,
"module": "commonjs",
"moduleResolution": "node",
"noImplicitAny": false,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"outDir": "./dist"
},
"include": [ "./automerge-js.ts" ],
"exclude": [
"./dist/**/*",
"./node_modules"
]
}

View file

@ -0,0 +1,37 @@
const path = require('path');
const nodeExternals = require('webpack-node-externals');
// the most basic webpack config for node or web targets for automerge-wasm
const serverConfig = {
// basic setup for bundling a node package
target: 'node',
externals: [nodeExternals()],
externalsPresets: { node: true },
entry: './automerge-js.ts',
module: { rules: [ { use: 'ts-loader' } ] },
output: {
filename: 'node.js',
path: path.resolve(__dirname, 'dist'),
},
mode: "development", // or production
};
const clientConfig = {
experiments: { asyncWebAssembly: true },
target: 'web',
entry: './automerge-js.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'public'),
},
mode: "development", // or production
performance: { // we dont want the wasm blob to generate warnings
hints: false,
maxEntrypointSize: 512000,
maxAssetSize: 512000
}
};
//module.exports = [serverConfig, clientConfig];
module.exports = [serverConfig];