diff --git a/examples/canvas/index.html b/examples/canvas/index.html index cd7b0487..3211f75a 100644 --- a/examples/canvas/index.html +++ b/examples/canvas/index.html @@ -22,6 +22,6 @@
- + diff --git a/examples/canvas/package.json b/examples/canvas/package.json index 36aaf52f..fc723398 100644 --- a/examples/canvas/package.json +++ b/examples/canvas/package.json @@ -24,6 +24,7 @@ "ts-loader": "^9.3.1", "typescript": "^5.5.4", "vite": "^5.4.1", + "vite-plugin-node-polyfills": "^0.22.0", "webpack": "^5.74.0", "webpack-cli": "^5.1.4", "webpack-dev-server": "^5.0.4" diff --git a/examples/canvas/src/handlers.ts b/examples/canvas/src/handlers.ts index 52417b6b..45efb821 100644 --- a/examples/canvas/src/handlers.ts +++ b/examples/canvas/src/handlers.ts @@ -1,42 +1,24 @@ -import { toString as uint8ArrayToString } from "uint8arrays/to-string"; -import { ICanvas } from "./objects/canvas"; +import { TopologyObject_Operation } from "@topology-foundation/object"; +import { Canvas } from "./objects/canvas"; -// TODO: this should be superseded by wasm and main ts-topology library -export const handleCanvasMessages = (canvas: ICanvas, e: any) => { - if (e.detail.msg.topic === "topology::discovery") return; - const input = uint8ArrayToString(e.detail.msg.data); - const message = JSON.parse(input); - switch (message["type"]) { - case "object_update": { - const fn = uint8ArrayToString(new Uint8Array(message["data"])); - handleObjectUpdate(canvas, fn); - break; - } - default: { - break; - } - } -}; - -function handleObjectUpdate(canvas: ICanvas, fn: string) { +export function handleObjectOps( + canvas: Canvas, + ops: TopologyObject_Operation[], +) { // In this case we only have paint // `paint(${node.getPeerId()}, [${[x, y]}], [${painting}])` - let args = fn.replace("paint(", "").replace(")", "").split(", "); - let offset_p = args[1] - .replace("[", "") - .replace("]", "") - .split(",") - .map((s) => parseInt(s, 10)); - const offset: [number, number] = [offset_p[0], offset_p[1]]; - let rgb_p = args[2] - .replace("[", "") - .replace("]", "") - .split(",") - .map((s) => parseInt(s, 10)); - const rgb: [number, number, number] = [rgb_p[0], rgb_p[1], rgb_p[2]]; - try { - canvas.paint(args[0], offset, rgb); + for (const op of ops) { + const offset = op.args[1] + .split(",") + .map((s: string) => parseInt(s, 10)) as [number, number]; + const rgb = op.args[2].split(",").map((s: string) => parseInt(s, 10)) as [ + number, + number, + number, + ]; + canvas.paint(op.args[0], offset, rgb); + } } catch (e) { console.error(e); } diff --git a/examples/canvas/src/index.ts b/examples/canvas/src/index.ts index 72a5b14b..1b13a9dd 100644 --- a/examples/canvas/src/index.ts +++ b/examples/canvas/src/index.ts @@ -1,11 +1,13 @@ import { TopologyNode } from "@topology-foundation/node"; -import { Canvas, ICanvas } from "./objects/canvas"; +import { Canvas } from "./objects/canvas"; import { Pixel } from "./objects/pixel"; import { GCounter } from "@topology-foundation/crdt"; -import { handleCanvasMessages } from "./handlers"; +import { handleObjectOps } from "./handlers"; +import { TopologyObject } from "@topology-foundation/object"; const node = new TopologyNode(); -let canvasCRO: ICanvas; +let canvasCRO: Canvas; +let topologyObject: TopologyObject; let peers: string[] = []; let discoveryPeers: string[] = []; let objectPeers: string[] = []; @@ -61,32 +63,46 @@ async function paint_pixel(pixel: HTMLDivElement) { const [r, g, b] = canvasCRO.pixel(x, y).color(); pixel.style.backgroundColor = `rgb(${r}, ${g}, ${b})`; - node.updateObject( - canvasCRO, - `paint(${node.networkNode.peerId}, [${[x, y]}], [${painting}])`, - ); + node.updateObject(topologyObject.id, [ + { + fn: "paint", + args: [ + node.networkNode.peerId, + `${x},${y}`, + `${painting[0]},${painting[1]},${painting[2]}`, + ], + }, + ]); } async function init() { await node.start(); - node.addCustomGroupMessageHandler((e) => { - handleCanvasMessages(canvasCRO, e); + node.addCustomGroupMessageHandler("", (e) => { peers = node.networkNode.getAllPeers(); discoveryPeers = node.networkNode.getGroupPeers("topology::discovery"); - if (canvasCRO) { - objectPeers = node.networkNode.getGroupPeers(canvasCRO.getObjectId()); - } render(); }); let create_button = document.getElementById("create"); - create_button.addEventListener("click", () => { - canvasCRO = new Canvas(node.networkNode.peerId, 5, 10); - node.createObject(canvasCRO); + create_button.addEventListener("click", async () => { + canvasCRO = new Canvas(5, 10); + topologyObject = await node.createObject(); + + // message handler for the CRO + node.addCustomGroupMessageHandler(topologyObject.id, (e) => { + // on create/connect + if (topologyObject) + objectPeers = node.networkNode.getGroupPeers(topologyObject.id); + render(); + }); + + node.objectStore.subscribe(topologyObject.id, (_, obj) => { + handleObjectOps(canvasCRO, obj.operations); + }); (document.getElementById("canvasId")).innerText = - canvasCRO.getObjectId(); + topologyObject.id; render(); }); @@ -95,26 +111,45 @@ async function init() { let croId = (document.getElementById("canvasIdInput")) .value; try { - await node.subscribeObject(croId, true, "", (_, topologyObject) => { + canvasCRO = new Canvas(5, 10); + topologyObject = await node.createObject(); + + // message handler for the CRO + node.addCustomGroupMessageHandler(topologyObject.id, (e) => { + // on create/connect + if (topologyObject) + objectPeers = node.networkNode.getGroupPeers(topologyObject.id); + (document.getElementById("canvasId")).innerText = + topologyObject.id; + render(); + }); + + node.objectStore.subscribe(topologyObject.id, (_, obj) => { + handleObjectOps(canvasCRO, obj.operations); + render(); + }); + + /* + await node.subscribeObject(croId, true, ""); + node.objectStore.subscribe(croId, (_, topologyObject) => { let object: any = topologyObject; object["canvas"] = object["canvas"].map((x: any) => x.map((y: any) => { y["red"] = Object.assign(new GCounter({}), y["red"]); y["green"] = Object.assign(new GCounter({}), y["green"]); y["blue"] = Object.assign(new GCounter({}), y["blue"]); - return Object.assign(new Pixel(node.networkNode.peerId), y); - }) + return Object.assign(new Pixel(), y); + }), ); canvasCRO = Object.assign( new Canvas(node.networkNode.peerId, 0, 0), - object + object, ); + */ - (document.getElementById("canvasId")).innerText = - croId; - render(); - }); + render(); + //}); // TODO remove the need to click to time for subscribe and fetch } catch (e) { console.error("Error while connecting with CRO", croId, e); diff --git a/examples/canvas/src/objects/canvas.ts b/examples/canvas/src/objects/canvas.ts index b9cb11cf..b09ff2f1 100644 --- a/examples/canvas/src/objects/canvas.ts +++ b/examples/canvas/src/objects/canvas.ts @@ -1,35 +1,15 @@ -import { TopologyObject } from "@topology-foundation/object"; -import { IPixel, Pixel } from "./pixel"; +import { Pixel } from "./pixel"; -export interface ICanvas { +export class Canvas { width: number; height: number; - canvas: IPixel[][]; - splash( - node_id: string, - offset: [number, number], - size: [number, number], - rgb: [number, number, number], - ): void; - paint( - nodeId: string, - offset: [number, number], - rgb: [number, number, number], - ): void; - pixel(x: number, y: number): IPixel; - merge(peerCanvas: Canvas): void; -} - -export class Canvas implements TopologyObject, ICanvas { - width: number; - height: number; - canvas: IPixel[][]; + canvas: Pixel[][]; - constructor(peerId: string, width: number, height: number) { + constructor(width: number, height: number) { this.width = width; this.height = height; this.canvas = Array.from(new Array(width), () => - Array.from(new Array(height), () => new Pixel(peerId)), + Array.from(new Array(height), () => new Pixel()), ); } @@ -60,7 +40,7 @@ export class Canvas implements TopologyObject, ICanvas { this.canvas[offset[0]][offset[1]].paint(nodeId, rgb); } - pixel(x: number, y: number): IPixel { + pixel(x: number, y: number): Pixel { return this.canvas[x][y]; } diff --git a/examples/canvas/src/objects/pixel.ts b/examples/canvas/src/objects/pixel.ts index f9ec0703..1e5ff9ea 100644 --- a/examples/canvas/src/objects/pixel.ts +++ b/examples/canvas/src/objects/pixel.ts @@ -1,16 +1,6 @@ import { GCounter } from "@topology-foundation/crdt"; -export interface IPixel { - red: GCounter; - green: GCounter; - blue: GCounter; - color(): [number, number, number]; - paint(nodeId: string, rgb: [number, number, number]): void; - counters(): [GCounter, GCounter, GCounter]; - merge(peerPixel: IPixel): void; -} - -export class Pixel implements IPixel { +export class Pixel { red: GCounter; green: GCounter; blue: GCounter; diff --git a/examples/canvas/vite.config.mts b/examples/canvas/vite.config.mts index 9620831b..c63b067a 100644 --- a/examples/canvas/vite.config.mts +++ b/examples/canvas/vite.config.mts @@ -1,10 +1,11 @@ import { defineConfig } from "vite"; +import { nodePolyfills } from "vite-plugin-node-polyfills"; export default defineConfig({ build: { target: "esnext", }, - plugins: [], + plugins: [nodePolyfills()], optimizeDeps: { esbuildOptions: { target: "esnext", diff --git a/examples/chat/src/index.ts b/examples/chat/src/index.ts index 53d3b29f..36b63413 100644 --- a/examples/chat/src/index.ts +++ b/examples/chat/src/index.ts @@ -89,11 +89,7 @@ async function main() { let button_create = document.getElementById("createRoom"); button_create.addEventListener("click", async () => { chatCRO = new Chat(); - // TODO: path not used - topologyObject = await newTopologyObject( - node.networkNode.peerId, - "/tmp/chat.ts", - ); + topologyObject = await node.createObject(); node.addCustomGroupMessageHandler(topologyObject.id, (e) => { // on create/connect @@ -103,6 +99,7 @@ async function main() { }); node.objectStore.subscribe(topologyObject.id, (_, obj) => { + console.log("Received object operations: ", obj); handleObjectOps(chatCRO, obj.operations); }); @@ -123,13 +120,7 @@ async function main() { } chatCRO = new Chat(); - topologyObject = await newTopologyObject( - node.networkNode.peerId, - "", - objectId, - ); - //objectId - await node.subscribeObject(objectId, true); + topologyObject = await node.subscribeObject(objectId, true); // message handler for the CRO node.addCustomGroupMessageHandler(topologyObject.id, (e) => { @@ -140,6 +131,7 @@ async function main() { }); node.objectStore.subscribe(topologyObject.id, (_, obj) => { + console.log("Received object operations: ", obj); handleObjectOps(chatCRO, obj.operations); }); }); diff --git a/packages/node/src/handlers.ts b/packages/node/src/handlers.ts index afdd7759..2ea2e7b8 100644 --- a/packages/node/src/handlers.ts +++ b/packages/node/src/handlers.ts @@ -15,7 +15,10 @@ export async function topologyMessagesHandler( ) { let message: Message; if (stream) { + console.log("topologyMessagesHandler", stream); + console.log(stream.source); const buf = (await lp.decode(stream.source).return()).value; + console.log("topologyMessagesHandler", buf); message = Message.decode(new Uint8Array(buf ? buf.subarray() : [])); } else if (data) { message = Message.decode(data); @@ -27,6 +30,8 @@ export async function topologyMessagesHandler( return; } + console.log("topologyMessagesHandler", message); + switch (message.type) { case Message_MessageType.UPDATE: updateHandler(node, message.data); @@ -85,6 +90,7 @@ function syncHandler( // (might send reject) <- TODO: when should we reject? // process, calculate diffs, and send back + console.log("syncHandler", data); const message = Message.create({ sender: node.networkNode.peerId, diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 968d63ab..37baf5c3 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -9,7 +9,7 @@ import { import { TopologyObjectStore } from "./store/index.js"; import { topologyMessagesHandler } from "./handlers.js"; import { OPERATIONS, executeObjectOperation } from "./operations.js"; -import { TopologyObject } from "@topology-foundation/object"; +import { newTopologyObject, TopologyObject } from "@topology-foundation/object"; import * as crypto from "crypto"; export * from "./operations.js"; @@ -71,17 +71,22 @@ export class TopologyNode { this.networkNode.sendMessage(peerId, [protocol], message); } - createObject(id: string, abi?: string, bytecode?: Uint8Array) { - const object = TopologyObject.create({ + async createObject(id?: string, path?: string, abi?: string) { + const object = await newTopologyObject( + this.networkNode.peerId, + path, id, abi, - bytecode, - }); + ); executeObjectOperation( this, OPERATIONS.CREATE, TopologyObject.encode(object).finish(), ); + this.networkNode.addGroupMessageHandler(object.id, async (e) => + topologyMessagesHandler(this, undefined, e.detail.msg.data), + ); + return object; } updateObject(id: string, operations: { fn: string; args: string[] }[]) { @@ -116,6 +121,7 @@ export class TopologyNode { this.networkNode.addGroupMessageHandler(id, async (e) => topologyMessagesHandler(this, undefined, e.detail.msg.data), ); + return object; } unsubscribeObject(id: string, purge?: boolean) { diff --git a/packages/object/src/index.ts b/packages/object/src/index.ts index afbab8c8..8a0fc4f1 100644 --- a/packages/object/src/index.ts +++ b/packages/object/src/index.ts @@ -7,7 +7,7 @@ export * from "./proto/object_pb.js"; /* Creates a new TopologyObject */ export async function newTopologyObject( peerId: string, - path: string, + path?: string, id?: string, abi?: string, ): Promise { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bc377169..69314493 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -72,6 +72,9 @@ importers: vite: specifier: ^5.4.1 version: 5.4.1(@types/node@22.4.1)(terser@5.31.6) + vite-plugin-node-polyfills: + specifier: ^0.22.0 + version: 0.22.0(rollup@4.21.0)(vite@5.4.1(@types/node@22.4.1)(terser@5.31.6)) webpack: specifier: ^5.74.0 version: 5.93.0(webpack-cli@5.1.4)