From 35c779873be08d7bbe5a534eac12e9d406176e95 Mon Sep 17 00:00:00 2001
From: Moritz Langenstein <ml5717@ic.ac.uk>
Date: Wed, 1 Jan 2020 18:32:47 +0000
Subject: [PATCH] (ml5717) Optimised UI->NW message path + benchmarks

---
 __benchmarks__/benchmarks.js | 249 +++++++++++++-----------
 __benchmarks__/data.js       |  94 +++++++++-
 src/queue.js                 |  77 ++++----
 src/wasm-crdt.js             | 279 ++++++++++++++++++++-------
 src/y-crdt.js                | 353 +++++++++++++++++++++++++----------
 5 files changed, 741 insertions(+), 311 deletions(-)

diff --git a/__benchmarks__/benchmarks.js b/__benchmarks__/benchmarks.js
index be4e8ed..f98ed29 100644
--- a/__benchmarks__/benchmarks.js
+++ b/__benchmarks__/benchmarks.js
@@ -2,6 +2,9 @@ import { test, equal, ok, notOk } from "zora"
 
 import chalk from "chalk"
 
+import MessagePack from "what-the-pack"
+const { decode } = MessagePack.initialize(2 ** 11)
+
 import { connect } from "../src/room.js"
 
 //import CRDT, { syncStep1, syncDone } from "../src/wasm-crdt.js"
@@ -164,8 +167,8 @@ function appendBenchmark(filename, iterations, results) {
   console.info(JSON.stringify({ filename, iterations, results }))
 }
 
-function captureHeapUsage() {
-  for (let i = 0; i < 10; i++) {
+function captureHeapUsage(n = 10) {
+  for (let i = 0; i < n; i++) {
     window.gc()
   }
 
@@ -270,7 +273,7 @@ function runBidirectionalBenchmark(
           // eslint-disable-next-line no-async-promise-executor
           new Promise(async (resolve) => {
             userID.uuid = "61a540d6-4522-48c7-a660-1ed501503cb7"
-
+            //console.log(0)
             room = await connect("room", CRDT, MockConnection)
             getEventListener(
               "room",
@@ -278,7 +281,7 @@ function runBidirectionalBenchmark(
             )(createMessageReceivedEvent(handshake, "tw-ml"))
 
             connectRAM = captureHeapUsage()
-
+            //console.log(1)
             return resolve()
           })
             .then(
@@ -290,10 +293,10 @@ function runBidirectionalBenchmark(
                     currTime = window.performance.now()
 
                     equal(channel, "crdt")
-                    ok(message.message instanceof Uint8Array)
+                    ok(message /*.message*/ instanceof Uint8Array)
 
                     addPackets[addPackets.length - 1].push(message)
-                    addSize += message.message.length
+                    addSize += /*message.*/ message.length
 
                     clearTimeout(timeout)
                     timeout = setTimeout(() => {
@@ -329,10 +332,10 @@ function runBidirectionalBenchmark(
             )
             .then(async () => {
               broadcastListener.callback = null
-
+              //console.log(2)
               await dumpBSON(addPacketsFilename, addPackets)
               addPackets = null
-
+              //console.log(3)
               addRAM = captureHeapUsage()
             })
             .then(
@@ -344,10 +347,10 @@ function runBidirectionalBenchmark(
                     currTime = window.performance.now()
 
                     equal(channel, "crdt")
-                    ok(message.message instanceof Uint8Array)
+                    ok(message /*.message*/ instanceof Uint8Array)
 
                     erasePackets[erasePackets.length - 1].push(message)
-                    eraseSize += message.message.length
+                    eraseSize += /*message.*/ message.length
 
                     clearTimeout(timeout)
                     timeout = setTimeout(() => {
@@ -383,10 +386,10 @@ function runBidirectionalBenchmark(
             )
             .then(async () => {
               broadcastListener.callback = null
-
+              //console.log(4)
               await dumpBSON(erasePacketsFilename, erasePackets)
               erasePackets = null
-
+              console.debug("5\n")
               eraseRAM = captureHeapUsage()
             })
             .then(
@@ -397,13 +400,14 @@ function runBidirectionalBenchmark(
 
                     equal(uid, remoteUserID)
                     equal(channel, "crdt")
-                    ok(message.message instanceof Uint8Array)
+                    ok(message /*.message*/ instanceof Uint8Array)
 
                     syncLocTime = (currTime - prevTime) * 1e3
 
                     syncPackets.push(message)
-                    syncSize += message.message.length
 
+                    syncSize += /*message.*/ message.length
+                    //console.log(syncSize, performance.memory.usedJSHeapSize)
                     clearTimeout(timeout)
                     timeout = setTimeout(
                       () => syncOnSendGroup(syncPackets, resolve),
@@ -412,7 +416,7 @@ function runBidirectionalBenchmark(
                   }
 
                   prevTime = window.performance.now()
-
+                  //console.log(performance.memory.usedJSHeapSize)
                   getEventListener(
                     "room",
                     "messageReceived",
@@ -421,7 +425,7 @@ function runBidirectionalBenchmark(
             )
             .then(async () => {
               sendListener.callback = null
-
+              console.debug("6\n")
               await dumpBSON(syncPacketsFilename, syncPackets)
               syncPackets = null
 
@@ -429,7 +433,7 @@ function runBidirectionalBenchmark(
 
               room.disconnect()
               room = null
-
+              //console.log(7)
               disconnectRAM = captureHeapUsage()
             })
             .then(
@@ -437,7 +441,7 @@ function runBidirectionalBenchmark(
                 // eslint-disable-next-line no-async-promise-executor
                 new Promise(async (resolve) => {
                   userID.uuid = "5c9e550b-3de8-4a32-80e1-80c08c19891a"
-
+                  //console.log(8)
                   updateRoom = await connect("update", CRDT, MockConnection)
                   getEventListener(
                     "update",
@@ -447,7 +451,7 @@ function runBidirectionalBenchmark(
                   connectUpdRAM = captureHeapUsage()
 
                   addPackets = await loadBSON(addPacketsFilename)
-
+                  //console.log(9)
                   return resolve()
                 }),
             )
@@ -521,7 +525,7 @@ function runBidirectionalBenchmark(
                 addEventListener,
               )
               addEventListener = null
-
+              //console.log(10)
               updateRoom.removeEventListener(
                 "removedIntervalsChange",
                 eraseEventListener,
@@ -534,7 +538,7 @@ function runBidirectionalBenchmark(
               addEvents = null
 
               addRemRAM = captureHeapUsage()
-
+              //console.log(11)
               erasePackets = await loadBSON(erasePacketsFilename)
             })
             .then(
@@ -595,7 +599,7 @@ function runBidirectionalBenchmark(
                 eraseEventListener,
               )
               eraseEventListener = null
-
+              //console.log(12)
               erasePackets = null
 
               await dumpBSON(eraseEventsFilename, eraseEvents)
@@ -605,7 +609,7 @@ function runBidirectionalBenchmark(
 
               updateRoom.disconnect()
               updateRoom = null
-
+              //console.log(13)
               disconnectUpdRAM = captureHeapUsage()
             })
             .then(
@@ -613,7 +617,7 @@ function runBidirectionalBenchmark(
                 // eslint-disable-next-line no-async-promise-executor
                 new Promise(async (resolve) => {
                   userID.uuid = "a2108f84-3785-4696-8dd5-fb89b38d4f7f"
-
+                  //console.log(14)
                   syncRoom = await connect("sync", CRDT, MockConnection)
                   getEventListener(
                     "sync",
@@ -623,7 +627,7 @@ function runBidirectionalBenchmark(
                   connectSyncRAM = captureHeapUsage()
 
                   syncPackets = await loadBSON(syncPacketsFilename)
-
+                  console.debug("15\n")
                   return resolve()
                 }),
             )
@@ -639,6 +643,12 @@ function runBidirectionalBenchmark(
                       syncEvents.push({ add: event })
                     } else {
                       syncEvents += 1
+
+                      if (syncEvents % 5000 == 1) {
+                        captureHeapUsage(1)
+                      }
+
+                      //console.log(syncEvents, performance.memory.usedJSHeapSize)
                     }
 
                     clearTimeout(timeout)
@@ -663,6 +673,12 @@ function runBidirectionalBenchmark(
                       syncEvents.push({ erase: event })
                     } else {
                       syncEvents += 1
+
+                      if (syncEvents % 5000 == 1) {
+                        captureHeapUsage(1)
+                      }
+
+                      //console.log(syncEvents, performance.memory.usedJSHeapSize)
                     }
 
                     clearTimeout(timeout)
@@ -684,7 +700,6 @@ function runBidirectionalBenchmark(
                     "removedIntervalsChange",
                     eraseEventListener,
                   )
-
                   prevTime = window.performance.now()
 
                   for (const syncPacket of syncPackets) {
@@ -693,12 +708,16 @@ function runBidirectionalBenchmark(
                       "messageReceived",
                     )(createMessageReceivedEvent(syncPacket))
                   }
+
+                  syncPackets = null
+
+                  captureHeapUsage(1)
                 }),
             )
             .then(async () => {
               syncRoom.removeEventListener("addOrUpdatePath", addEventListener)
               addEventListener = null
-
+              console.debug("16\n")
               syncRoom.removeEventListener(
                 "removedIntervalsChange",
                 eraseEventListener,
@@ -714,14 +733,14 @@ function runBidirectionalBenchmark(
 
               syncRoom.disconnect()
               syncRoom = null
-
+              //console.log(17)
               disconnectSyncRAM = captureHeapUsage()
             })
             .then(async () => {
               if (!BENCHMARK) {
                 return
               }
-
+              //console.log(18)
               addPackets = (await loadBSON(addPacketsFilename)).reduce(
                 (sum, packets) => sum + packets.length,
                 0,
@@ -740,7 +759,7 @@ function runBidirectionalBenchmark(
 
               syncEvents = await loadBSON(syncEventsFilename)
               syncEvents = syncEventsCache ? syncEvents.length : syncEvents
-
+              //console.log(19)
               const results = {
                 addPath: {
                   timeLoc: addLocTime,
@@ -970,6 +989,8 @@ function addOnInitFrontendParallel(
       room.endPath(drawPathID)
     }
 
+    captureHeapUsage(1)
+
     setTimeout(addPath, 0, sj + BLOCKSIZE)
   }
 
@@ -1014,6 +1035,8 @@ function eraseOnInitFrontendParallel(
       }
     }
 
+    captureHeapUsage(5)
+
     setTimeout(erasePath, 0, sj + BLOCKSIZE)
   }
 
@@ -1045,6 +1068,8 @@ function addOnInitBackendParallel(addPackets, BLOCKSIZE) {
       )(createMessageReceivedEvent(packets[j]))
     }
 
+    captureHeapUsage(5)
+
     setTimeout(addPath, 0, sj + BLOCKSIZE)
   }
 
@@ -1076,6 +1101,8 @@ function eraseOnInitBackendParallel(erasePackets, BLOCKSIZE) {
       )(createMessageReceivedEvent(packets[j]))
     }
 
+    captureHeapUsage(1)
+
     setTimeout(erasePath, 0, sj + BLOCKSIZE)
   }
 
@@ -1096,21 +1123,25 @@ function eraseOnEventGroupParallel(
 }
 
 function syncOnSendGroupVerify(syncPackets, resolve) {
-  let syncDonePacket = -1
+  let syncDonePacketIndex = -1
+
+  const syncDonePacket = decode(MessagePack.Buffer.from(syncDone))
 
   syncPackets.forEach((packet, i) => {
+    packet = decode(MessagePack.Buffer.from(packet))
+
     if (
-      packet.message.length == syncDone.message.length &&
-      JSON.stringify(Object.assign({}, packet, { uuid: undefined })) ==
-        JSON.stringify(syncDone)
+      packet.message.length == syncDonePacket.message.length &&
+      stringify(Object.assign({}, packet, { uuid: undefined })) ==
+        stringify(Object.assign({}, syncDonePacket, { uuid: undefined }))
     ) {
-      equal(syncDonePacket, -1)
+      equal(syncDonePacketIndex, -1)
 
-      syncDonePacket = i
+      syncDonePacketIndex = i
     }
   })
 
-  equal(syncDonePacket, syncPackets.length - 1)
+  equal(syncDonePacketIndex, syncPackets.length - 1)
 
   resolve()
 }
@@ -1229,6 +1260,42 @@ test("benchmark", async (t) => {
   const ITERATIONSLIST = [10, 25, 50, 75, 100, 250, 500]
   const BLOCKSIZE = 10 // TODO: pass as parameter from CRDT
 
+  await t.test("communicates a dot draw and erase update", async (/*t*/) => {
+    return runBidirectionalBenchmark(
+      null /* BENCHMARK */,
+      null /* FILENAME */,
+      [1] /* ITERATIONSLIST */,
+      BLOCKSIZE /* BLOCKSIZE */,
+      dotDraw /* addData */,
+      dotErase /* eraseData */,
+      addOnInitFrontendSequential /* addOnInitFrontend */,
+      1000 /* addBroadcastGroupTimeout */,
+      addOnBroadcastGroupParallel /* addOnBroadcastGroup */,
+      ".dot-ver-add-packets.json" /* addPacketsFilename */,
+      eraseOnInitFrontendSequential /* eraseOnInitFrontend */,
+      1000 /* eraseBroadcastGroupTimeout */,
+      eraseOnBroadcastGroupParallel /* eraseOnBroadcastGroup */,
+      ".dot-ver-erase-packets.json" /* erasePacketsFilename */,
+      1000 /* syncSendGroupTimeout */,
+      syncOnSendGroupVerify /* syncOnSendGroup */,
+      ".dot-ver-sync-packets.json" /* syncPacketsFilename */,
+      addOnInitBackendSequential /* addOnInitBackend */,
+      1000 /* addEventGroupTimeout */,
+      addOnEventGroupVerify /* addOnEventGroup */,
+      true /* addEventsCache */,
+      ".dot-ver-add-events.json" /* addEventsFilename */,
+      eraseOnInitBackendSequential /* eraseOnInitBackend */,
+      1000 /* eraseEventGroupTimeout */,
+      eraseOnEventGroupVerify /* eraseOnEventGroupTimeout */,
+      true /* eraseEventsCache */,
+      ".dot-ver-erase-events.json" /* eraseEventsFilename */,
+      1000 /* syncEventGroupTimeout */,
+      syncOnEventGroupVerify /* syncOnEventGroup */,
+      true /* syncEventsCache */,
+      ".dot-ver-sync-events.json" /* syncEventsFilename */,
+    )
+  })
+
   await t.test(
     "benchmarks a dot draw and erase update sequentially",
     async (/*t*/) => {
@@ -1307,6 +1374,42 @@ test("benchmark", async (t) => {
     },
   )
 
+  await t.test("communicates a path draw and erase update", async (/*t*/) => {
+    return runBidirectionalBenchmark(
+      null /* BENCHMARK */,
+      null /* FILENAME */,
+      [1] /* ITERATIONSLIST */,
+      BLOCKSIZE /* BLOCKSIZE */,
+      pathDraw /* addData */,
+      pathErase /* eraseData */,
+      addOnInitFrontendSequential /* addOnInitFrontend */,
+      1000 /* addBroadcastGroupTimeout */,
+      addOnBroadcastGroupParallel /* addOnBroadcastGroup */,
+      ".path-ver-add-packets.json" /* addPacketsFilename */,
+      eraseOnInitFrontendSequential /* eraseOnInitFrontend */,
+      1000 /* eraseBroadcastGroupTimeout */,
+      eraseOnBroadcastGroupParallel /* eraseOnBroadcastGroup */,
+      ".path-ver-erase-packets.json" /* erasePacketsFilename */,
+      1000 /* syncSendGroupTimeout */,
+      syncOnSendGroupVerify /* syncOnSendGroup */,
+      ".path-ver-sync-packets.json" /* syncPacketsFilename */,
+      addOnInitBackendSequential /* addOnInitBackend */,
+      1000 /* addEventGroupTimeout */,
+      addOnEventGroupVerify /* addOnEventGroup */,
+      true /* addEventsCache */,
+      ".path-ver-add-events.json" /* addEventsFilename */,
+      eraseOnInitBackendSequential /* eraseOnInitBackend */,
+      1000 /* eraseEventGroupTimeout */,
+      eraseOnEventGroupVerify /* eraseOnEventGroupTimeout */,
+      true /* eraseEventsCache */,
+      ".path-ver-erase-events.json" /* eraseEventsFilename */,
+      1000 /* syncEventGroupTimeout */,
+      syncOnEventGroupVerify /* syncOnEventGroup */,
+      true /* syncEventsCache */,
+      ".path-ver-sync-events.json" /* syncEventsFilename */,
+    )
+  })
+
   await t.test(
     "benchmarks a path draw and erase update sequentially",
     async (/*t*/) => {
@@ -1384,76 +1487,4 @@ test("benchmark", async (t) => {
       )
     },
   )
-
-  await t.test("communicates a single draw and erase update", async (/*t*/) => {
-    return runBidirectionalBenchmark(
-      null /* BENCHMARK */,
-      null /* FILENAME */,
-      [1] /* ITERATIONSLIST */,
-      BLOCKSIZE /* BLOCKSIZE */,
-      dotDraw /* addData */,
-      dotErase /* eraseData */,
-      addOnInitFrontendSequential /* addOnInitFrontend */,
-      1000 /* addBroadcastGroupTimeout */,
-      addOnBroadcastGroupParallel /* addOnBroadcastGroup */,
-      ".dot-ver-add-packets.json" /* addPacketsFilename */,
-      eraseOnInitFrontendSequential /* eraseOnInitFrontend */,
-      1000 /* eraseBroadcastGroupTimeout */,
-      eraseOnBroadcastGroupParallel /* eraseOnBroadcastGroup */,
-      ".dot-ver-erase-packets.json" /* erasePacketsFilename */,
-      1000 /* syncSendGroupTimeout */,
-      syncOnSendGroupVerify /* syncOnSendGroup */,
-      ".dot-ver-sync-packets.json" /* syncPacketsFilename */,
-      addOnInitBackendSequential /* addOnInitBackend */,
-      1000 /* addEventGroupTimeout */,
-      addOnEventGroupVerify /* addOnEventGroup */,
-      true /* addEventsCache */,
-      ".dot-ver-add-events.json" /* addEventsFilename */,
-      eraseOnInitBackendSequential /* eraseOnInitBackend */,
-      1000 /* eraseEventGroupTimeout */,
-      eraseOnEventGroupVerify /* eraseOnEventGroupTimeout */,
-      true /* eraseEventsCache */,
-      ".dot-ver-erase-events.json" /* eraseEventsFilename */,
-      1000 /* syncEventGroupTimeout */,
-      syncOnEventGroupVerify /* syncOnEventGroup */,
-      true /* syncEventsCache */,
-      ".dot-ver-sync-events.json" /* syncEventsFilename */,
-    )
-  })
-
-  await t.test("communicates a path draw and erase update", async (/*t*/) => {
-    return runBidirectionalBenchmark(
-      null /* BENCHMARK */,
-      null /* FILENAME */,
-      [1] /* ITERATIONSLIST */,
-      BLOCKSIZE /* BLOCKSIZE */,
-      pathDraw /* addData */,
-      pathErase /* eraseData */,
-      addOnInitFrontendSequential /* addOnInitFrontend */,
-      1000 /* addBroadcastGroupTimeout */,
-      addOnBroadcastGroupParallel /* addOnBroadcastGroup */,
-      ".path-ver-add-packets.json" /* addPacketsFilename */,
-      eraseOnInitFrontendSequential /* eraseOnInitFrontend */,
-      1000 /* eraseBroadcastGroupTimeout */,
-      eraseOnBroadcastGroupParallel /* eraseOnBroadcastGroup */,
-      ".path-ver-erase-packets.json" /* erasePacketsFilename */,
-      1000 /* syncSendGroupTimeout */,
-      syncOnSendGroupVerify /* syncOnSendGroup */,
-      ".path-ver-sync-packets.json" /* syncPacketsFilename */,
-      addOnInitBackendSequential /* addOnInitBackend */,
-      1000 /* addEventGroupTimeout */,
-      addOnEventGroupVerify /* addOnEventGroup */,
-      true /* addEventsCache */,
-      ".path-ver-add-events.json" /* addEventsFilename */,
-      eraseOnInitBackendSequential /* eraseOnInitBackend */,
-      1000 /* eraseEventGroupTimeout */,
-      eraseOnEventGroupVerify /* eraseOnEventGroupTimeout */,
-      true /* eraseEventsCache */,
-      ".path-ver-erase-events.json" /* eraseEventsFilename */,
-      1000 /* syncEventGroupTimeout */,
-      syncOnEventGroupVerify /* syncOnEventGroup */,
-      true /* syncEventsCache */,
-      ".path-ver-sync-events.json" /* syncEventsFilename */,
-    )
-  })
 })
diff --git a/__benchmarks__/data.js b/__benchmarks__/data.js
index 0301ac8..d0e1467 100644
--- a/__benchmarks__/data.js
+++ b/__benchmarks__/data.js
@@ -1,10 +1,90 @@
-export const handshake = {
-  uuid: "42c4566d-0cfe-4c00-a645-254b7887e477",
-  message: Uint8Array.of(162, 109, 108),
-  slice: 0,
-  length: 1,
-  compressed: false,
-}
+export const handshake = Uint8Array.of(
+  133,
+  164,
+  117,
+  117,
+  105,
+  100,
+  217,
+  36,
+  57,
+  49,
+  101,
+  97,
+  99,
+  52,
+  56,
+  49,
+  45,
+  48,
+  48,
+  56,
+  48,
+  45,
+  52,
+  53,
+  53,
+  97,
+  45,
+  97,
+  98,
+  98,
+  56,
+  45,
+  52,
+  102,
+  48,
+  50,
+  101,
+  102,
+  49,
+  48,
+  54,
+  100,
+  56,
+  50,
+  167,
+  109,
+  101,
+  115,
+  115,
+  97,
+  103,
+  101,
+  196,
+  3,
+  162,
+  109,
+  108,
+  166,
+  111,
+  102,
+  102,
+  115,
+  101,
+  116,
+  0,
+  166,
+  108,
+  101,
+  110,
+  103,
+  116,
+  104,
+  3,
+  170,
+  99,
+  111,
+  109,
+  112,
+  114,
+  101,
+  115,
+  115,
+  101,
+  100,
+  194,
+)
 
 export const dotDraw = [[209, 88, 5.0, "#0000ff"]]
 export const dotErase = [[0, [[0, 0]]]]
diff --git a/src/queue.js b/src/queue.js
index 5d76e81..beddedb 100644
--- a/src/queue.js
+++ b/src/queue.js
@@ -9,6 +9,7 @@ const MESSAGE_SLICE_SIZE = 2 ** 10 // 1KB
 
 const { encode, decode } = MessagePack.initialize(MESSAGE_BUFFER_SIZE)
 
+const queue = []
 const buffer = {}
 
 onmessage = (event) => {
@@ -24,7 +25,7 @@ onmessage = (event) => {
 
     const uuid = uuidv4()
 
-    //console.log("send in", message)
+    //console.log("send in", JSON.stringify(message))
 
     message = encode(message)
 
@@ -32,64 +33,72 @@ onmessage = (event) => {
       message = pako.deflate(message)
     }
 
-    for (
-      let offset = 0;
-      offset < message.length;
-      offset += MESSAGE_SLICE_SIZE
-    ) {
-      event.data.message = {
+    const sender = (offset) => {
+      event.data.message = encode({
         uuid,
         message: message.subarray(offset, offset + MESSAGE_SLICE_SIZE),
-        slice: offset / MESSAGE_SLICE_SIZE,
-        length: Math.ceil(message.length / MESSAGE_SLICE_SIZE),
+        offset,
+        length: message.length,
         compressed,
-      }
+      })
 
-      //console.log("send out", event.data)
+      //console.log(JSON.stringify([...event.data.message]), event.data.message.length, "send out")
 
       self.postMessage(event.data)
+
+      offset += MESSAGE_SLICE_SIZE
+
+      if (offset < message.length) {
+        setTimeout(() => sender(offset), 5)
+      } else {
+        queue.shift()
+
+        if (queue.length > 0) queue[0](0)
+      }
     }
+
+    queue.push(sender)
+
+    if (queue.length == 1) queue[0](0)
   } else if (event.data.method == "received") {
-    let message = event.data.message.message
+    const packet = decode(MessagePack.Buffer.from(event.data.message))
+    let message = packet.message
 
-    //console.log("receive in", message)
+    //console.log("receive in", JSON.stringify(message))
 
-    if (event.data.message.length > 1) {
+    if (packet.length > MESSAGE_SLICE_SIZE) {
       let messages = buffer[event.data.uid]
       if (!messages) {
         messages = {}
         buffer[event.data.uid] = messages
       }
 
-      let slices = messages[event.data.message.uuid]
+      let slices = messages[packet.uuid]
       if (!slices) {
-        slices = []
-        messages[event.data.message.uuid] = slices
+        slices = {
+          message: new Uint8Array(packet.length),
+          length: 0,
+        }
+        messages[packet.uuid] = slices
       }
 
-      slices.push(event.data.message)
+      // WARNING: Assumes packets only arrive once (but may arrive out-of-order)
+      slices.length += packet.message.length
+      slices.message.set(packet.message, packet.offset)
+
+      if (slices.length < slices.message.length) {
+        delete packet.uuid
+        delete packet.message
 
-      if (slices.length < slices[slices.length - 1].length) {
         return
       }
 
-      message = new Uint8Array(
-        slices.reduce((acc, s) => acc + s.message.length, 0),
-      )
-
-      slices.sort((a, b) => a.slice - b.slice)
-
-      let offset = 0
-
-      for (const slice of slices) {
-        message.set(slice.message, offset)
-        offset += slice.message.length
-      }
+      message = slices.message
 
-      delete messages[event.data.message.uuid]
+      delete messages[packet.uuid]
     }
 
-    if (event.data.message.compressed) {
+    if (packet.compressed) {
       message = pako.inflate(Uint8Array.from(message))
     }
 
@@ -97,7 +106,7 @@ onmessage = (event) => {
 
     event.data.message = message
 
-    //console.log("receive out", event.data)
+    //console.log("receive out", JSON.stringify(event.data))
 
     self.postMessage(event.data)
   }
diff --git a/src/wasm-crdt.js b/src/wasm-crdt.js
index 72c623d..05f6a28 100644
--- a/src/wasm-crdt.js
+++ b/src/wasm-crdt.js
@@ -249,66 +249,219 @@ export default class WasmCRDTWrapper {
   }
 }
 
-export const syncStep1 = {
-  uuid: "6e20b20d-e1d8-405d-8a61-d56cb1c47a24",
-  message: Uint8Array.of(
-    130,
-    164,
-    116,
-    121,
-    112,
-    101,
-    171,
-    115,
-    121,
-    110,
-    99,
-    32,
-    115,
-    116,
-    101,
-    112,
-    32,
-    49,
-    167,
-    109,
-    101,
-    115,
-    115,
-    97,
-    103,
-    101,
-    196,
-    3,
-    0,
-    0,
-    0,
-  ),
-  slice: 0,
-  length: 1,
-  compressed: false,
-}
-
-export const syncDone = {
-  message: Uint8Array.of(
-    129,
-    164,
-    116,
-    121,
-    112,
-    101,
-    169,
-    115,
-    121,
-    110,
-    99,
-    32,
-    100,
-    111,
-    110,
-    101,
-  ),
-  slice: 0,
-  length: 1,
-  compressed: false,
-}
+export const syncStep1 = Uint8Array.of(
+  133,
+  164,
+  117,
+  117,
+  105,
+  100,
+  217,
+  36,
+  50,
+  99,
+  50,
+  97,
+  97,
+  49,
+  102,
+  51,
+  45,
+  55,
+  102,
+  99,
+  100,
+  45,
+  52,
+  56,
+  48,
+  51,
+  45,
+  97,
+  101,
+  52,
+  51,
+  45,
+  48,
+  57,
+  101,
+  97,
+  98,
+  100,
+  56,
+  53,
+  97,
+  54,
+  48,
+  57,
+  167,
+  109,
+  101,
+  115,
+  115,
+  97,
+  103,
+  101,
+  196,
+  31,
+  130,
+  164,
+  116,
+  121,
+  112,
+  101,
+  171,
+  115,
+  121,
+  110,
+  99,
+  32,
+  115,
+  116,
+  101,
+  112,
+  32,
+  49,
+  167,
+  109,
+  101,
+  115,
+  115,
+  97,
+  103,
+  101,
+  196,
+  3,
+  0,
+  0,
+  0,
+  166,
+  111,
+  102,
+  102,
+  115,
+  101,
+  116,
+  0,
+  166,
+  108,
+  101,
+  110,
+  103,
+  116,
+  104,
+  31,
+  170,
+  99,
+  111,
+  109,
+  112,
+  114,
+  101,
+  115,
+  115,
+  101,
+  100,
+  194,
+)
+
+export const syncDone = Uint8Array.of(
+  133,
+  164,
+  117,
+  117,
+  105,
+  100,
+  217,
+  36,
+  101,
+  48,
+  56,
+  98,
+  56,
+  102,
+  54,
+  53,
+  45,
+  101,
+  50,
+  54,
+  49,
+  45,
+  52,
+  98,
+  55,
+  100,
+  45,
+  97,
+  50,
+  51,
+  57,
+  45,
+  101,
+  50,
+  51,
+  55,
+  98,
+  51,
+  55,
+  52,
+  50,
+  52,
+  54,
+  53,
+  167,
+  109,
+  101,
+  115,
+  115,
+  97,
+  103,
+  101,
+  196,
+  16,
+  129,
+  164,
+  116,
+  121,
+  112,
+  101,
+  169,
+  115,
+  121,
+  110,
+  99,
+  32,
+  100,
+  111,
+  110,
+  101,
+  166,
+  111,
+  102,
+  102,
+  115,
+  101,
+  116,
+  0,
+  166,
+  108,
+  101,
+  110,
+  103,
+  116,
+  104,
+  16,
+  170,
+  99,
+  111,
+  109,
+  112,
+  114,
+  101,
+  115,
+  115,
+  101,
+  100,
+  194,
+)
diff --git a/src/y-crdt.js b/src/y-crdt.js
index 8823161..20fd9c1 100644
--- a/src/y-crdt.js
+++ b/src/y-crdt.js
@@ -205,9 +205,11 @@ export default class YjsCRDTWrapper extends Y.AbstractConnector {
   receiveMessage(uid, message) {
     super.receiveMessage(uid, message)
 
-    this.room.dispatchEvent(
-      new CustomEvent("peerSyncedWithUs", { detail: uid }),
-    )
+    if (message && message.type === "sync done") {
+      this.room.dispatchEvent(
+        new CustomEvent("peerSyncedWithUs", { detail: uid }),
+      )
+    }
   }
 
   reportConnectionQuality(uid, quality) {
@@ -227,7 +229,9 @@ export default class YjsCRDTWrapper extends Y.AbstractConnector {
   send(uid, message) {
     let compressed = true
 
-    if (message.type === "sync step 1") {
+    if (!message) {
+      compressed = false
+    } else if (message.type === "sync step 1") {
       compressed = false
 
       this.room.dispatchEvent(
@@ -257,97 +261,250 @@ export default class YjsCRDTWrapper extends Y.AbstractConnector {
 
 Y.extend("y-crdt", YjsCRDTWrapper)
 
-export const syncStep1 = {
-  uuid: "6e20b20d-e1d8-405d-8a61-d56cb1c47a24",
-  message: Uint8Array.of(
-    133,
-    164,
-    116,
-    121,
-    112,
-    101,
-    171,
-    115,
-    121,
-    110,
-    99,
-    32,
-    115,
-    116,
-    101,
-    112,
-    32,
-    49,
-    168,
-    115,
-    116,
-    97,
-    116,
-    101,
-    83,
-    101,
-    116,
-    128,
-    169,
-    100,
-    101,
-    108,
-    101,
-    116,
-    101,
-    83,
-    101,
-    116,
-    128,
-    175,
-    112,
-    114,
-    111,
-    116,
-    111,
-    99,
-    111,
-    108,
-    86,
-    101,
-    114,
-    115,
-    105,
-    111,
-    110,
-    11,
-    164,
-    97,
-    117,
-    116,
-    104,
-    192,
-  ),
-  slice: 0,
-  length: 1,
-  compressed: false,
-}
-
-export const syncDone = {
-  message: Uint8Array.of(
-    129,
-    164,
-    116,
-    121,
-    112,
-    101,
-    169,
-    115,
-    121,
-    110,
-    99,
-    32,
-    100,
-    111,
-    110,
-    101,
-  ),
-  slice: 0,
-  length: 1,
-  compressed: false,
-}
+export const syncStep1 = Uint8Array.of(
+  133,
+  164,
+  117,
+  117,
+  105,
+  100,
+  217,
+  36,
+  102,
+  97,
+  52,
+  56,
+  101,
+  57,
+  100,
+  52,
+  45,
+  97,
+  53,
+  53,
+  101,
+  45,
+  52,
+  99,
+  57,
+  102,
+  45,
+  97,
+  55,
+  50,
+  98,
+  45,
+  99,
+  100,
+  56,
+  49,
+  100,
+  56,
+  54,
+  57,
+  101,
+  49,
+  49,
+  100,
+  167,
+  109,
+  101,
+  115,
+  115,
+  97,
+  103,
+  101,
+  196,
+  62,
+  133,
+  164,
+  116,
+  121,
+  112,
+  101,
+  171,
+  115,
+  121,
+  110,
+  99,
+  32,
+  115,
+  116,
+  101,
+  112,
+  32,
+  49,
+  168,
+  115,
+  116,
+  97,
+  116,
+  101,
+  83,
+  101,
+  116,
+  128,
+  169,
+  100,
+  101,
+  108,
+  101,
+  116,
+  101,
+  83,
+  101,
+  116,
+  128,
+  175,
+  112,
+  114,
+  111,
+  116,
+  111,
+  99,
+  111,
+  108,
+  86,
+  101,
+  114,
+  115,
+  105,
+  111,
+  110,
+  11,
+  164,
+  97,
+  117,
+  116,
+  104,
+  192,
+  166,
+  111,
+  102,
+  102,
+  115,
+  101,
+  116,
+  0,
+  166,
+  108,
+  101,
+  110,
+  103,
+  116,
+  104,
+  62,
+  170,
+  99,
+  111,
+  109,
+  112,
+  114,
+  101,
+  115,
+  115,
+  101,
+  100,
+  194,
+)
+
+export const syncDone = Uint8Array.of(
+  133,
+  164,
+  117,
+  117,
+  105,
+  100,
+  217,
+  36,
+  52,
+  48,
+  102,
+  49,
+  57,
+  55,
+  53,
+  48,
+  45,
+  101,
+  51,
+  57,
+  99,
+  45,
+  52,
+  98,
+  48,
+  49,
+  45,
+  57,
+  97,
+  100,
+  48,
+  45,
+  50,
+  53,
+  53,
+  50,
+  49,
+  57,
+  52,
+  50,
+  49,
+  48,
+  57,
+  56,
+  167,
+  109,
+  101,
+  115,
+  115,
+  97,
+  103,
+  101,
+  196,
+  16,
+  129,
+  164,
+  116,
+  121,
+  112,
+  101,
+  169,
+  115,
+  121,
+  110,
+  99,
+  32,
+  100,
+  111,
+  110,
+  101,
+  166,
+  111,
+  102,
+  102,
+  115,
+  101,
+  116,
+  0,
+  166,
+  108,
+  101,
+  110,
+  103,
+  116,
+  104,
+  16,
+  170,
+  99,
+  111,
+  109,
+  112,
+  114,
+  101,
+  115,
+  115,
+  101,
+  100,
+  194,
+)
-- 
GitLab