import assert from "node:assert/strict"; import test from "node:test"; import { Worker } from "node:worker_threads"; import { SharedProfile } from "./profile.js"; import { YawnCore } from "./index.js"; test("shared profiles replace unread samples and exclude overlapping reads/writes", () => { const writer = new SharedProfile(new SharedArrayBuffer(512)); const reader = new SharedProfile(writer.buffer); assert.equal(reader.read(), undefined); writer.write(JSON.stringify({ frame: 1, name: "GPU → SAB" })); writer.write(JSON.stringify({ frame: 2, name: "light + opaque" })); assert.deepEqual(reader.read(), { frame: 2, name: "light + opaque" }); assert.equal(reader.read(), undefined); Atomics.store(writer.header, 0, 1); assert.equal(writer.write('{"frame":3}'), false); assert.equal(reader.read(), undefined); Atomics.store(writer.header, 0, 0); assert.equal(writer.write(" ".repeat(513)), false); assert.equal(writer.write('{"frame":4}'), true); writer.clear(); assert.equal(reader.read(), undefined); }); test("concurrent shared profile publication never mixes samples", async () => { const reader = new SharedProfile(); const done = new Int32Array(new SharedArrayBuffer(4)); const worker = new Worker( ` const {workerData} = require('node:worker_threads'); (async () => { const {SharedProfile} = await import(workerData.module); const writer = new SharedProfile(workerData.buffer); for (let frame = 1; frame <= 2000; frame++) { const json = JSON.stringify({frame, values: Array(128).fill(frame * 3 + 7), name: 'GPU → SAB'}); while (!writer.write(json)) await new Promise(r => setImmediate(r)); } Atomics.store(new Int32Array(workerData.done), 0, 1); })(); `, { eval: true, workerData: { module: new URL("./profile.js", import.meta.url).href, buffer: reader.buffer, done: done.buffer, }, }, ); const exit = new Promise((resolve, reject) => { worker.once("error", reject); worker.once("exit", (code) => code ? reject(new Error(`Worker exit ${code}`)) : resolve(), ); }); try { let last = 0; while (!Atomics.load(done, 0) || Atomics.load(reader.header, 0) === 2) { const sample = reader.read(); if (sample) { assert.ok(sample.frame > last); assert.deepEqual(sample.values, Array(128).fill(sample.frame * 3 + 7)); assert.equal(sample.name, "GPU → SAB"); last = sample.frame; } await new Promise((r) => setImmediate(r)); } await exit; assert.equal(last, 2000); } finally { await worker.terminate(); } }); test("onProfile reads SAB without result messages and stops on unsubscribe/dispose", async (t) => { t.mock.timers.enable({ apis: ["setInterval"] }); const messages = []; let receive; let writer; const worker = { addEventListener(type, listener) { if (type === "message") receive = listener; }, postMessage(message) { messages.push(message.type); writer = new SharedProfile(message.profileBuffer); queueMicrotask(() => receive({ data: { request: message.request, result: { buffer: new SharedArrayBuffer(64), features: [], rows: [], }, }, }), ); }, terminate() {}, }; const core = new YawnCore({}, { workerFactory: () => worker }); await core.ready; const samples = []; const stop = core.onProfile((sample) => samples.push(sample)); writer.write('{"frame":9,"milliseconds":1.25}'); t.mock.timers.tick(99); assert.equal(samples.length, 0); t.mock.timers.tick(1); assert.deepEqual(samples, [{ frame: 9, milliseconds: 1.25 }]); t.mock.timers.tick(100); assert.equal(samples.length, 1); assert.deepEqual(messages, ["init"]); stop(); writer.write('{"frame":10}'); t.mock.timers.tick(100); assert.equal(samples.length, 1); core.onProfile((sample) => samples.push(sample)); core.dispose(); t.mock.timers.tick(100); assert.equal(samples.length, 1); });