Skip to content
Snippets Groups Projects
Commit 90d6d488 authored by Moritz Langenstein's avatar Moritz Langenstein
Browse files

(ml5717) Added delta serialisation + deserialisation

parent 4945a43d
No related branches found
No related tags found
No related merge requests found
[submodule "vec-map"]
path = vec-map
url = git@gitlab.doc.ic.ac.uk:ml5717/vec-map.git
...@@ -3,10 +3,6 @@ name = "drawing-crdt" ...@@ -3,10 +3,6 @@ name = "drawing-crdt"
version = "0.1.0" version = "0.1.0"
authors = ["Moritz Langenstein <ml5717@ic.ac.uk>"] authors = ["Moritz Langenstein <ml5717@ic.ac.uk>"]
edition = "2018" edition = "2018"
build = "build.rs"
[build-dependencies]
capnpc = "0.11.1"
[lib] [lib]
crate-type = ["cdylib", "rlib"] crate-type = ["cdylib", "rlib"]
...@@ -18,10 +14,11 @@ default = ["console_error_panic_hook"] ...@@ -18,10 +14,11 @@ default = ["console_error_panic_hook"]
wasm-bindgen = "0.2" wasm-bindgen = "0.2"
js-sys = "0.3.33" js-sys = "0.3.33"
uuid = "0.8.1" uuid = "0.8.1"
capnp = "0.11.1"
serde = "1.0.104" serde = "1.0.104"
serde_derive = "1.0.104" serde_derive = "1.0.104"
serde-wasm-bindgen = "0.1.3" serde-wasm-bindgen = "0.1.3"
vec_map = { path = "vec-map", features = ["serde"] }
bincode = "1.2.1"
# The `console_error_panic_hook` crate provides better debugging of panics by # The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires # logging them with `console.error`. This is great for development, but requires
...@@ -40,5 +37,5 @@ wee_alloc = { version = "0.4.2", optional = true } ...@@ -40,5 +37,5 @@ wee_alloc = { version = "0.4.2", optional = true }
wasm-bindgen-test = "0.2" wasm-bindgen-test = "0.2"
[profile.release] [profile.release]
# Tell `rustc` to optimize for small code size. opt-level = 3
opt-level = "s" lto = true
extern crate capnpc;
fn main() {
capnpc::CompilerCommand::new()
.src_prefix("schema")
.file("schema/crdt.capnp")
.run()
.expect("Compiling Cap'n Proto schema");
}
\ No newline at end of file
@0x8e28fa32ad5a1cb1; # unique file ID, generated by `capnp id`
struct Point {
x @0 :Int32;
y @1 :Int32;
width @2 :Float32;
colour @3 :UInt32;
}
struct Stroke {
points @0 :List(Point);
}
This diff is collapsed.
#[macro_use]
extern crate serde_derive;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use std::option::Option;
use js_sys::Error; use js_sys::Error;
use serde_wasm_bindgen::to_value as to_js_value; use serde_wasm_bindgen::to_value as to_js_value;
use std::collections::HashMap; use std::collections::HashMap;
use std::option::Option;
use vec_map::VecMap;
#[cfg(feature = "wee_alloc")] #[cfg(feature = "wee_alloc")]
#[global_allocator] #[global_allocator]
...@@ -15,43 +13,35 @@ static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; ...@@ -15,43 +13,35 @@ static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
mod utils; mod utils;
mod crdt; mod crdt;
use crdt::{CRDT, EventListener, Point, IntervalUnion, StrokeDelta, StrokeID}; use crdt::{EventListener, IntervalUnion, Point, StrokeDelta, StrokeID, CRDT};
#[wasm_bindgen] #[wasm_bindgen]
extern "C" { extern "C" {
#[derive(Clone)] #[derive(Clone)]
pub type WasmEventListener; pub type WasmEventListener;
#[wasm_bindgen(structural, method)] #[wasm_bindgen(structural, method)]
pub fn on_stroke(this: &WasmEventListener, stroke: String, points: JsValue); pub fn on_stroke(this: &WasmEventListener, stroke: String, points: JsValue);
#[wasm_bindgen(structural, method)] #[wasm_bindgen(structural, method)]
pub fn on_interval(this: &WasmEventListener, stroke: String, intervals: JsValue); pub fn on_interval(this: &WasmEventListener, stroke: String, intervals: JsValue);
#[wasm_bindgen(structural, method)] #[wasm_bindgen(structural, method)]
pub fn on_broadcast(this: &WasmEventListener, deltas: JsValue); pub fn on_deltas(this: &WasmEventListener, deltas: Box<[u8]>);
} }
impl EventListener for WasmEventListener { impl EventListener for WasmEventListener {
fn on_stroke(&self, stroke: String, points: &Vec<Option<Point>>) { fn on_stroke(&self, stroke: String, points: &VecMap<Point>) {
#[allow(unused_must_use)] { // TODO: Error handling self.on_stroke(stroke, to_js_value(points).unwrap())
to_js_value(points).map(|points| self.on_stroke(stroke, points)); }
fn on_interval(&self, stroke: String, intervals: &IntervalUnion<f32>) {
self.on_interval(stroke, to_js_value(intervals).unwrap())
} }
}
fn on_deltas(&self, deltas: &HashMap<StrokeID, StrokeDelta>) {
fn on_interval(&self, stroke: String, intervals: &IntervalUnion) { self.on_deltas(bincode::serialize(deltas).unwrap().into_boxed_slice())
#[allow(unused_must_use)] { // TODO: Error handling
to_js_value(intervals).map(|intervals| self.on_interval(stroke, intervals));
} }
}
fn on_broadcast(&self, deltas: &HashMap<StrokeID, StrokeDelta>) {
/*#[allow(unused_must_use)] { // TODO: Error handling
to_js_value(deltas).map(|deltas| self.on_broadcast(deltas));
}*/
let deltas = to_js_value(deltas).unwrap();
self.on_broadcast(deltas);
}
} }
#[wasm_bindgen] #[wasm_bindgen]
...@@ -59,58 +49,67 @@ pub struct WasmCRDT(CRDT); ...@@ -59,58 +49,67 @@ pub struct WasmCRDT(CRDT);
#[wasm_bindgen] #[wasm_bindgen]
impl WasmCRDT { impl WasmCRDT {
// WARNING: The CRDT actually obtains "ownership" of the event listener, but it can // WARNING: The CRDT actually obtains "ownership" of the event listener, but it can
// still be modified and destructed from inside JavaScript // still be modified and destructed from inside JavaScript
// Pass in a unique object with unique callbacks into the constructor without // Pass in a unique object with unique callbacks into the constructor without
// other references to them to uphold this contract // other references to them to uphold this contract
#[wasm_bindgen(constructor)] #[wasm_bindgen(constructor)]
pub fn new(event_listener: &WasmEventListener) -> WasmCRDT { pub fn new(event_listener: &WasmEventListener) -> WasmCRDT {
WasmCRDT(CRDT::new(Box::new((*event_listener).clone()))) WasmCRDT(CRDT::new(Box::new((*event_listener).clone())))
} }
pub fn set_user(&mut self, user: &str) -> bool { pub fn set_user(&mut self, user: &str) -> bool {
self.0.set_user(user) self.0.set_user(user)
} }
pub fn get_user(&self) -> Option<String> { pub fn get_user(&self) -> Option<String> {
self.0.get_user() self.0.get_user()
} }
pub fn add_stroke(&mut self, x: i32, y: i32, weight: f32, colour: &str) -> Option<String> { pub fn add_stroke(&mut self, x: i32, y: i32, weight: f32, colour: &str) -> Option<String> {
self.0.add_stroke(x, y, weight, colour) self.0.add_stroke(x, y, weight, colour)
} }
pub fn add_point(&mut self, stroke: &str, x: i32, y: i32, weight: f32, colour: &str) -> bool { pub fn add_point(&mut self, stroke: &str, x: i32, y: i32, weight: f32, colour: &str) -> bool {
self.0.add_point(stroke, x, y, weight, colour) self.0.add_point(stroke, x, y, weight, colour)
} }
pub fn erase_stroke(&mut self, stroke: &str, from: f32, to: f32) -> bool { pub fn erase_stroke(&mut self, stroke: &str, from: f32, to: f32) -> bool {
self.0.erase_stroke(stroke, from, to) self.0.erase_stroke(stroke, from, to)
} }
pub fn get_stroke_points(&self, stroke: &str) -> Result<JsValue, JsValue> { pub fn get_stroke_points(&self, stroke: &str) -> Result<JsValue, JsValue> {
let points = match self.0.get_stroke_points(stroke) { let points = match self.0.get_stroke_points(stroke) {
Some(points) => points, Some(points) => points,
None => return Err(Error::new("Unknown stroke ID").into()), None => return Err(Error::new("Unknown stroke ID").into()),
}; };
Ok(to_js_value(points)?) Ok(to_js_value(points)?)
} }
pub fn get_stroke_intervals(&self, stroke: &str) -> Result<JsValue, JsValue> { pub fn get_stroke_intervals(&self, stroke: &str) -> Result<JsValue, JsValue> {
let intervals = match self.0.get_stroke_intervals(stroke) { let intervals = match self.0.get_stroke_intervals(stroke) {
Some(intervals) => intervals, Some(intervals) => intervals,
None => return Err(Error::new("Unknown stroke ID").into()), None => return Err(Error::new("Unknown stroke ID").into()),
}; };
Ok(to_js_value(intervals)?) Ok(to_js_value(intervals)?)
} }
pub fn fetch_events(&mut self) { pub fn fetch_events(&mut self) {
self.0.fetch_events() self.0.fetch_events()
} }
pub fn fetch_broadcasts(&mut self) { pub fn fetch_deltas(&mut self) {
self.0.fetch_broadcasts() self.0.fetch_deltas()
} }
pub fn apply_deltas(&mut self, deltas: Box<[u8]>) -> Result<(), JsValue> {
let deltas = match bincode::deserialize(&deltas) {
Ok(deltas) => deltas,
Err(error) => return Err(Error::new(&format!("{:?}", error)).into()),
};
Ok(self.0.apply_deltas(deltas))
}
} }
Subproject commit 9fd05f23024ef98ebaae809f31b30f9c2c644707
import * as wasm from "drawing-crdt" import * as wasm from "drawing-crdt"
const broadcasts = []
const crdt = new wasm.WasmCRDT({ const crdt = new wasm.WasmCRDT({
on_stroke: (stroke, points) => { on_stroke: (stroke, points) => {
console.log("on_stroke:", stroke, points) console.log("on_stroke:", stroke, points)
...@@ -7,8 +9,9 @@ const crdt = new wasm.WasmCRDT({ ...@@ -7,8 +9,9 @@ const crdt = new wasm.WasmCRDT({
on_interval: (stroke, intervals) => { on_interval: (stroke, intervals) => {
console.log("on_interval", stroke, intervals) console.log("on_interval", stroke, intervals)
}, },
on_broadcast: (deltas) => { on_deltas: (deltas) => {
console.log("on_broadcast:", deltas) console.log("on_deltas:", deltas)
broadcasts.push(deltas)
}, },
}) })
...@@ -22,9 +25,9 @@ console.log("pre fetch events") ...@@ -22,9 +25,9 @@ console.log("pre fetch events")
crdt.fetch_events(); crdt.fetch_events();
console.log("post fetch events") console.log("post fetch events")
console.log("pre fetch broadcasts") console.log("pre fetch deltas")
crdt.fetch_broadcasts(); crdt.fetch_deltas();
console.log("post fetch broadcasts") console.log("post fetch deltas")
crdt.erase_stroke(stroke_id, 0.0, 0.25) crdt.erase_stroke(stroke_id, 0.0, 0.25)
crdt.erase_stroke(stroke_id, 0.5, 2.25) crdt.erase_stroke(stroke_id, 0.5, 2.25)
...@@ -34,6 +37,28 @@ console.log("pre fetch events") ...@@ -34,6 +37,28 @@ console.log("pre fetch events")
crdt.fetch_events(); crdt.fetch_events();
console.log("post fetch events") console.log("post fetch events")
console.log("pre fetch broadcasts") console.log("pre fetch deltas")
crdt.fetch_broadcasts(); crdt.fetch_deltas();
console.log("post fetch broadcasts") console.log("post fetch deltas")
const crdt2 = new wasm.WasmCRDT({
on_stroke: (stroke, points) => {
console.log("on_stroke2:", stroke, points)
},
on_interval: (stroke, intervals) => {
console.log("on_interval2", stroke, intervals)
},
on_deltas: (deltas) => {
console.log("on_deltas:2", deltas)
},
})
console.log("pre apply 2nd deltas + fetch events")
crdt2.apply_deltas(broadcasts[1])
crdt2.fetch_events()
console.log("post apply 2nd deltas + fetch events")
console.log("pre apply 1st deltas + fetch events")
crdt2.apply_deltas(broadcasts[0])
crdt2.fetch_events()
console.log("post apply 1st deltas + fetch events")
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment