Amp-Thread-ID: https://ampcode.com/threads/T-01a0b382-1e79-712b-abd1-d29201c9bb5e Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
123 lines
4.0 KiB
JavaScript
123 lines
4.0 KiB
JavaScript
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);
|
|
});
|