import chalk from "chalk" import { connect } from "../src/room.js" import MockConnection, { getUserID, getPeerHandle, getPeerFootprint, send, //sendListener, broadcast, broadcastListener, terminatePeer, destructor, addEventListener, eventListeners, } from "../src/connection/MockConnection.js" import { handshake, dotDraw, dotErase, pathDraw, pathErase, } from "./benchmark.data.js" function printBenchmark(title, iterations, results) { process.stdout.write(`\n ${title} (${iterations} iterations):\n`) for (const title in results) { const { time, packets, size } = results[title] process.stdout.write( chalk` {yellow ⧗} {dim ${title}:} {yellow.inverse ${( time / (1e6 * iterations) ).toFixed( 3, )}ms / it} {magenta.inverse ${size}B} {dim in ${packets} packet(s)}\n`, ) } process.stdout.write(`\n`) } let room = null describe("drawing app mesh", () => { beforeEach(async () => { getUserID.mockClear() getPeerHandle.mockClear() getPeerFootprint.mockClear() send.mockClear() broadcast.mockClear() terminatePeer.mockClear() destructor.mockClear() addEventListener.mockClear() MockConnection.mockClear() room = await connect("data", MockConnection) eventListeners.get("messageReceived")({ detail: handshake }) }) afterEach(() => { room.disconnect() room = null }) it("communicates a single draw and erase update", () => { let dotID return new Promise((resolve) => { broadcastListener.callback = (channel, message) => { expect(channel).toEqual("y-js") expect(message.message instanceof Uint8Array).toBe(true) resolve() } dotID = room.addPath(dotDraw[0]) }).then( () => new Promise((resolve) => { broadcastListener.callback = (channel, message) => { expect(channel).toEqual("y-js") expect(message.message instanceof Uint8Array).toBe(true) resolve() } room.extendErasureIntervals(dotID, dotErase[0][0], dotErase[0][1]) }), ) }) it("benchmarks a single draw and erase update sequentially", () => { const ITERATIONS = 1000 jest.setTimeout(ITERATIONS * 10) const dotIDs = [] let prevTime let broadcasts = 0 let addTime = 0 let addPackets = 0 let addSize = 0 let eraseTime = 0 let erasePackets = 0 let eraseSize = 0 return new Promise((resolve) => { broadcastListener.callback = (channel, message) => { const currTime = process.hrtime() broadcasts += 1 if (broadcasts <= ITERATIONS) { addTime += (currTime[0] - prevTime[0]) * 1e9 + (currTime[1] - prevTime[1]) addPackets += 1 addSize += message.message.length } else { eraseTime += (currTime[0] - prevTime[0]) * 1e9 + (currTime[1] - prevTime[1]) erasePackets += 1 eraseSize += message.message.length } prevTime = process.hrtime() if (broadcasts < ITERATIONS) { dotIDs.push(room.addPath(dotDraw[0])) } else if (broadcasts < ITERATIONS * 2) { room.extendErasureIntervals( dotIDs[broadcasts - ITERATIONS], dotErase[0][0], dotErase[0][1], ) } else { printBenchmark("single draw and erase [sequential]", ITERATIONS, { addPath: { time: addTime, packets: addPackets, size: addSize }, extendErasureIntervals: { time: eraseTime, packets: erasePackets, size: eraseSize, }, }) resolve() } } prevTime = process.hrtime() dotIDs.push(room.addPath(dotDraw[0])) }) }) it("benchmarks a single draw and erase update in parallel", () => { const ITERATIONS = 1000 jest.setTimeout(ITERATIONS * 10) const dotIDs = [] let prevTime let addTime let addPackets = 0 let addSize = 0 let eraseTime let erasePackets = 0 let eraseSize = 0 let timeout return new Promise((resolve) => { broadcastListener.callback = (channel, message) => { const currTime = process.hrtime() addTime = (currTime[0] - prevTime[0]) * 1e9 + (currTime[1] - prevTime[1]) addPackets += 1 addSize += message.message.length clearTimeout(timeout) timeout = setTimeout(() => resolve(), 1000) } prevTime = process.hrtime() for (let i = 0; i < ITERATIONS; i++) { dotIDs.push(room.addPath(dotDraw[0])) } }).then( () => new Promise((resolve) => { broadcastListener.callback = (channel, message) => { const currTime = process.hrtime() eraseTime = (currTime[0] - prevTime[0]) * 1e9 + (currTime[1] - prevTime[1]) erasePackets += 1 eraseSize += message.message.length clearTimeout(timeout) timeout = setTimeout(() => { printBenchmark("single draw and erase [parallel]", ITERATIONS, { addPath: { time: addTime, packets: addPackets, size: addSize }, extendErasureIntervals: { time: eraseTime, packets: erasePackets, size: eraseSize, }, }) resolve() }, 1000) } prevTime = process.hrtime() for (let i = 0; i < ITERATIONS; i++) { room.extendErasureIntervals( dotIDs[i], dotErase[0][0], dotErase[0][1], ) } }), ) }) it("communicates a path draw and erase update", () => { let pathID let timeout return new Promise((resolve) => { broadcastListener.callback = (channel, message) => { expect(channel).toEqual("y-js") expect(message.message instanceof Uint8Array).toBe(true) clearTimeout(timeout) timeout = setTimeout(() => resolve(), 1000) } pathID = room.addPath(pathDraw[0]) for (let i = 1; i < pathDraw.lenth; i++) { room.extendPath(pathID, pathDraw[1]) } }).then( () => new Promise((resolve) => { broadcastListener.callback = (channel, message) => { expect(channel).toEqual("y-js") expect(message.message instanceof Uint8Array).toBe(true) clearTimeout(timeout) timeout = setTimeout(() => resolve(), 1000) } for (let i = 0; i < pathErase.length; i++) { room.extendErasureIntervals( pathID, pathErase[i][0], pathErase[i][1], ) } }), ) }) })