Skip to content

Commit

Permalink
Add networked-dom-v0.2 protocol (#54) (#210)
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcusLongmuir authored Jan 20, 2025
1 parent 4962804 commit b3f15f5
Show file tree
Hide file tree
Showing 143 changed files with 14,179 additions and 5,423 deletions.
45 changes: 45 additions & 0 deletions build-utils/rebuildOnDependencyChangesPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { createRequire } from "node:module";

import { spawn } from "child_process";
import { PluginBuild } from "esbuild";
import kill from "tree-kill";

let runningProcess: ReturnType<typeof spawn> | undefined;

export const rebuildOnDependencyChangesPlugin = {
name: "watch-dependencies",
setup(build: PluginBuild) {
build.onResolve({ filter: /.*/ }, (args) => {
// Include dependent packages in the watch list
if (args.kind === "import-statement") {
if (!args.path.startsWith(".")) {
const require = createRequire(args.resolveDir);
let resolved;
try {
resolved = require.resolve(args.path);
} catch {
return;
}
return {
external: true,
watchFiles: [resolved],
};
}
}
});
build.onEnd(async () => {
console.log("Build finished. (Re)starting process");
if (runningProcess) {
await new Promise<void>((resolve) => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
kill(runningProcess!.pid!, "SIGTERM", (err) => {
resolve();
});
});
}
runningProcess = spawn("npm", ["run", "start-server"], {
stdio: "inherit",
});
});
},
};
21 changes: 12 additions & 9 deletions examples/mml-web-client-example/build.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
import * as esbuild from "esbuild";

import { rebuildOnDependencyChangesPlugin } from "../../build-utils/rebuildOnDependencyChangesPlugin";

const buildMode = "--build";
const watchMode = "--watch";

const helpString = `Mode must be provided as one of ${buildMode} or ${watchMode}`;

const args = process.argv.splice(2);

if (args.length !== 1) {
console.error(helpString);
process.exit(1);
}

const mode = args[0];

const buildOptions: esbuild.BuildOptions = {
entryPoints: ["src/index.ts"],
write: true,
Expand All @@ -15,17 +26,9 @@ const buildOptions: esbuild.BuildOptions = {
packages: "external",
sourcemap: true,
target: "node14",
plugins: mode === watchMode ? [rebuildOnDependencyChangesPlugin] : [],
};

const args = process.argv.splice(2);

if (args.length !== 1) {
console.error(helpString);
process.exit(1);
}

const mode = args[0];

switch (mode) {
case buildMode:
esbuild.build(buildOptions).catch(() => process.exit(1));
Expand Down
4 changes: 2 additions & 2 deletions examples/mml-web-client-example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
"scripts": {
"type-check": "tsc --noEmit",
"build": "tsx ./build.ts --build",
"iterate": "npm run build && npm run serve",
"serve": "node --enable-source-maps ./build/index.js",
"iterate": "tsx ./build.ts --watch",
"start-server": "node --enable-source-maps ./build/index.js",
"lint": "eslint \"./**/*.{js,jsx,ts,tsx}\" --max-warnings 0",
"lint-fix": "eslint \"./**/*.{js,jsx,ts,tsx}\" --fix"
},
Expand Down
9 changes: 4 additions & 5 deletions examples/networked-dom-web-client-example/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import * as fs from "fs";
import * as path from "path";
import * as url from "url";

import { NetworkedDOM } from "@mml-io/networked-dom-document";
import { EditableNetworkedDOM, LocalObservableDOMFactory } from "@mml-io/networked-dom-server";
import * as chokidar from "chokidar";
import express, { Request } from "express";
import enableWs from "express-ws";
import { EditableNetworkedDOM, LocalObservableDOMFactory } from "@mml-io/networked-dom-server";
import * as fs from "fs";
import * as path from "path";
import * as url from "url";
import ws from "ws";

const dirname = url.fileURLToPath(new URL(".", import.meta.url));
Expand Down
33 changes: 33 additions & 0 deletions examples/protocol-benchmarks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Networked DOM Protocol Benchmarks

This directory contains a set of benchmarks for the Networked DOM Protocol encoding and decoding compared with JSON.

As of writing the benchmarks show that the binary encoding is faster and smaller than JSON on a test data set.

Notes:
- The test data set uses expressive key names which are therefore included in the JSON output (field names are not included in the binary encoding).
- The test data set does not use multi-byte characters which is more representative of the data that the protocol is used for. (However the binary encoding is still likely to be faster even in this case).

## Encoding Results

```
Ran on Node v20.11.1 on Apple M1 Max 16" MacBook Pro
Binary x 947 ops/sec ±3.68% (92 runs sampled)
JSON x 317 ops/sec ±0.52% (91 runs sampled)
Fastest is Binary
Binary byte length : 229153
JSON byte length : 703303
Binary is 0.3258 the length of JSON
JSON is 3.0691 the length of Binary
```

## Decoding Results

```
Ran on Node v20.11.1 on Apple M1 Max 16" MacBook Pro
Binary x 568 ops/sec ±0.40% (92 runs sampled)
JSON x 245 ops/sec ±0.33% (90 runs sampled)
Fastest is Binary
```
19 changes: 19 additions & 0 deletions examples/protocol-benchmarks/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "@mml-io/protocol-benchmarks",
"version": "0.18.1",
"private": true,
"type": "module",
"scripts": {
"build": "tsc --project ./tsconfig.json",
"type-check": "tsc --noEmit",
"lint": "eslint \"./**/*.{js,jsx,ts,tsx}\" --max-warnings 0",
"lint-fix": "eslint \"./**/*.{js,jsx,ts,tsx}\" --fix",
"benchmark-encoding": "npm run build && node ./build/encoding.js",
"benchmark-decoding": "npm run build && node ./build/decoding.js"
},
"devDependencies": {
"@mml-io/networked-dom-protocol": "^0.18.1",
"benchmark": "2.1.4",
"@types/benchmark": "2.1.5"
}
}
52 changes: 52 additions & 0 deletions examples/protocol-benchmarks/src/decoding.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import {
BufferReader,
BufferWriter,
encodeServerMessage,
NetworkedDOMV02ServerMessage,
} from "@mml-io/networked-dom-protocol";
import { decodeServerMessages } from "@mml-io/networked-dom-protocol";
import Benchmark from "benchmark";

import { prepareData } from "./prepare-data.js";

const data = prepareData(1000);

const encodedData = data.map((message) => {
const writer = new BufferWriter(256);
encodeServerMessage(message, writer);
return writer.getBuffer();
});
const encodedJSON = data.map((message) => JSON.stringify(message));

const suite = new Benchmark.Suite();
suite
.add("Binary", function () {
let totalLength = 0;
for (const message of encodedData) {
const bufferReader = new BufferReader(message);
const decoded = decodeServerMessages(bufferReader);
totalLength += (decoded[0] as NetworkedDOMV02ServerMessage).type.length;
}
if (totalLength <= 0) {
throw new Error("Invalid total length");
}
})
.add("JSON", function () {
let totalLength = 0;
for (const message of encodedJSON) {
const decoded = JSON.parse(message);
totalLength += decoded.type.length;
}
if (totalLength <= 0) {
throw new Error("Invalid total length");
}
})
// add listeners
.on("cycle", (event: Benchmark.Event) => {
console.log(String(event.target));
})
.on("complete", () => {
console.log(`Fastest is ${suite.filter("fastest").map("name")}`);
})
// run async
.run({ async: true });
49 changes: 49 additions & 0 deletions examples/protocol-benchmarks/src/encoding.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { BufferWriter, encodeServerMessage } from "@mml-io/networked-dom-protocol";
import Benchmark from "benchmark";

import { prepareData } from "./prepare-data.js";

let totalBinaryLength = 0;
let totalJSONLength = 0;

const data = prepareData(1000);
const suite = new Benchmark.Suite();
suite
.add("Binary", function () {
let totalLength = 0;
for (const message of data) {
const writer = new BufferWriter(256);
encodeServerMessage(message, writer);
const encoded = writer.getBuffer();
totalLength += encoded.length;
}
if (totalLength <= 0) {
throw new Error("Invalid total length");
}
totalBinaryLength = totalLength;
})
.add("JSON", function () {
let totalLength = 0;
for (const message of data) {
const encoded = JSON.stringify(message);
totalLength += encoded.length;
}
if (totalLength <= 0) {
throw new Error("Invalid total length");
}
totalJSONLength = totalLength;
})
// add listeners
.on("cycle", (event: Benchmark.Event) => {
console.log(String(event.target));
})
.on("complete", () => {
console.log(`Fastest is ${suite.filter("fastest").map("name")}`);

console.log(`Binary byte length : ${totalBinaryLength}`);
console.log(`JSON byte length : ${totalJSONLength}`);
console.log(`Binary is ${(totalBinaryLength / totalJSONLength).toFixed(4)} the length of JSON`);
console.log(`JSON is ${(totalJSONLength / totalBinaryLength).toFixed(4)} the length of Binary`);
})
// run async
.run({ async: true });
77 changes: 77 additions & 0 deletions examples/protocol-benchmarks/src/prepare-data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import {
NetworkedDOMV02ChildrenAddedDiff,
NetworkedDOMV02ServerMessage,
} from "@mml-io/networked-dom-protocol";

export function prepareData(size: number): Array<NetworkedDOMV02ServerMessage> {
const data: Array<NetworkedDOMV02ChildrenAddedDiff> = [];
for (let i = 0; i < size; i++) {
data.push({
type: "childrenAdded",
nodeId: i + 1,
previousNodeId: i + 2,
addedNodes: [
{
type: "element",
nodeId: i + 3,
tag: "m-cube",
attributes: [
["color" + i, "red"],
["x", "1"],
["y", "2"],
["z", "3"],
],
children: [
{
type: "element",
nodeId: i + 4,
tag: "m-cube",
attributes: [
["color" + i, "blue"],
["x" + i, "4"],
["y", "5"],
["z", "6"],
["rx" + i, "4"],
["ry", "5"],
["rz", "6"],
],
children: [],
},
{
type: "element",
nodeId: i + 5,
tag: "m-cube",
attributes: [
["color" + i, "blue"],
["x" + i, "4"],
["y", "5"],
["z", "6"],
["rx" + i, "4"],
["ry", "5"],
["rz", "6"],
],
children: [],
},
{
type: "element",
nodeId: i + 6,
tag: "m-cube",
attributes: [
["empty", ""],
["color" + i, "blue"],
["x" + i, "4"],
["y", "5"],
["z", "6"],
["rx" + i, "4"],
["ry", "5"],
["rz", "6"],
],
children: [],
},
],
},
],
});
}
return data;
}
17 changes: 17 additions & 0 deletions examples/protocol-benchmarks/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"compilerOptions": {
"target": "es5",
"module": "ESNext",
"moduleResolution": "node",
"lib": ["es6"],
"allowJs": true,
"outDir": "build",
"rootDir": "src",
"strict": true,
"noImplicitAny": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/**/*"],
"exclude": ["src/**/*.spec.ts", "node_modules"]
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const jestConfig: JestConfigWithTsJest = {
collectCoverage: true,
testEnvironment: "jsdom",
coverageDirectory: "coverage",
coverageReporters: ["lcov", "text"],
coverageReporters: ["lcov"],
extensionsToTreatAsEsm: [".ts"],
moduleNameMapper: {
"^(\\.{1,2}/.*)\\.js$": "$1",
Expand Down
Loading

0 comments on commit b3f15f5

Please sign in to comment.