Amp-Thread-ID: https://ampcode.com/threads/T-01a01380-b478-77d0-84a0-102880a5c5ae Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
188 lines
5.0 KiB
JavaScript
188 lines
5.0 KiB
JavaScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
import {
|
|
animateInstance,
|
|
canonicalAstExample,
|
|
classifyInstance,
|
|
compileAndSwitch,
|
|
computePipelineExample,
|
|
connectFromWorker,
|
|
createInstance,
|
|
createVelocityColumn,
|
|
customRenderPipelineExample,
|
|
defaultPipelineExample,
|
|
dropActiveGraph,
|
|
fluentGraphExample,
|
|
fxNodeExportExample,
|
|
importGltf,
|
|
jsoGraphExample,
|
|
loadCompleteScene,
|
|
pickNearest,
|
|
setVelocity,
|
|
simpleSceneShader,
|
|
updateInstance,
|
|
} from "../examples/cookbook/index.js";
|
|
|
|
test("cookbook graph recipes produce canonical addon-owned ASTs", () => {
|
|
const canonical = canonicalAstExample();
|
|
assert.equal(canonical.ast.id, "shared_dag");
|
|
assert.equal(
|
|
(canonical.source.match(/\(ref "source" "value"\)/g) ?? []).length,
|
|
2,
|
|
);
|
|
assert.deepEqual(
|
|
[jsoGraphExample().id, fluentGraphExample().id],
|
|
["jso_graph", "fluent_graph"],
|
|
);
|
|
|
|
const fxnode = fxNodeExportExample();
|
|
assert.equal(fxnode.kind, "yawn-render-graph");
|
|
assert.equal(fxnode.pipelines.render.length, 4);
|
|
|
|
const defaults = defaultPipelineExample();
|
|
assert.deepEqual(
|
|
defaults.pipelines.render.map(({ name }) => name),
|
|
[
|
|
"ground_plane",
|
|
"gltf_standard",
|
|
"gltf_standard_double_sided",
|
|
"frame_out",
|
|
],
|
|
);
|
|
assert.equal(defaults.pipelines.compute.length, 1);
|
|
|
|
const custom = customRenderPipelineExample();
|
|
assert.equal(custom.pipelines.render[0].shader, simpleSceneShader);
|
|
assert.match(simpleSceneShader, /@vertex fn vertex_main/);
|
|
|
|
const compute = computePipelineExample().pipelines.compute[0];
|
|
assert.deepEqual(
|
|
[compute.entry, compute.dispatch],
|
|
["initialize", [4, 1, 1]],
|
|
);
|
|
});
|
|
|
|
test("cookbook graph lifecycle recipe serializes, switches, and drops", async () => {
|
|
const calls = [];
|
|
const core = {
|
|
compileGraph(source) {
|
|
calls.push(["compile", source]);
|
|
return Promise.resolve({ compiledId: [3, 4] });
|
|
},
|
|
switchCompiledGraph(id) {
|
|
calls.push(["switch", id]);
|
|
return Promise.resolve();
|
|
},
|
|
switchToImmediate() {
|
|
calls.push(["immediate"]);
|
|
return Promise.resolve();
|
|
},
|
|
dropCompiledGraph(id) {
|
|
calls.push(["drop", id]);
|
|
return Promise.resolve();
|
|
},
|
|
};
|
|
const graph = { id: "lifecycle", revision: 1, nodes: [] };
|
|
const compiled = await compileAndSwitch(core, graph);
|
|
await dropActiveGraph(core, compiled);
|
|
assert.match(calls[0][1], /^\(yawn-graph 1/);
|
|
assert.deepEqual(calls.slice(1), [
|
|
["switch", [3, 4]],
|
|
["immediate"],
|
|
["drop", [3, 4]],
|
|
]);
|
|
});
|
|
|
|
test("cookbook mutation recipes use handles and SOA writes directly", async () => {
|
|
const calls = [];
|
|
const column = {
|
|
write(slot, values) {
|
|
calls.push(["velocity", slot, values]);
|
|
},
|
|
};
|
|
const core = {
|
|
allocateArray(layout) {
|
|
calls.push(["allocate", layout]);
|
|
return Promise.resolve(column);
|
|
},
|
|
setInstanceTransform(handle, transform) {
|
|
calls.push(["transform", handle, transform]);
|
|
},
|
|
setInstanceType(handle, words) {
|
|
calls.push(["type", handle, words]);
|
|
},
|
|
};
|
|
const instance = {
|
|
handle: [7, 2],
|
|
setTransform(transform) {
|
|
calls.push(["wrapped-transform", transform]);
|
|
},
|
|
setType(words) {
|
|
calls.push(["wrapped-type", words]);
|
|
},
|
|
};
|
|
const mesh = {
|
|
createInstance(transform) {
|
|
calls.push(["create", transform]);
|
|
return Promise.resolve(instance);
|
|
},
|
|
};
|
|
const transform = Array.from({ length: 16 }, (_, index) => index);
|
|
const words = Array(16).fill(1);
|
|
|
|
assert.equal(await createInstance(mesh, transform), instance);
|
|
updateInstance(instance, transform, words);
|
|
const velocity = await createVelocityColumn(core);
|
|
setVelocity(velocity, instance, [1, 2, 3]);
|
|
animateInstance(core, instance, transform);
|
|
classifyInstance(core, instance, words);
|
|
|
|
assert.deepEqual(calls.find(([name]) => name === "allocate")[1], {
|
|
name: "instance.velocity",
|
|
domain: "instance",
|
|
scalar: "f32",
|
|
lanes: 4,
|
|
});
|
|
assert.deepEqual(
|
|
calls.find(([name]) => name === "velocity"),
|
|
["velocity", 7, [1, 2, 3, 0]],
|
|
);
|
|
assert.ok(calls.some(([name]) => name === "transform"));
|
|
assert.ok(calls.some(([name]) => name === "type"));
|
|
});
|
|
|
|
test("cookbook picking and worker-to-worker recipes use public facades", async () => {
|
|
const picked = await pickNearest(
|
|
{
|
|
pickRay: async () => ({
|
|
epoch: 5,
|
|
hits: [{ instance: [9, 3], distance: 2 }],
|
|
}),
|
|
},
|
|
[0, 0, 0],
|
|
[0, 0, -1],
|
|
);
|
|
assert.deepEqual(picked.hits[0].instance.handle, [9, 3]);
|
|
|
|
class Port extends EventTarget {
|
|
postMessage() {}
|
|
start() {
|
|
queueMicrotask(() =>
|
|
this.dispatchEvent(
|
|
new MessageEvent("message", {
|
|
data: { type: "soa-init", arrays: [] },
|
|
}),
|
|
),
|
|
);
|
|
}
|
|
terminate() {}
|
|
}
|
|
const core = await connectFromWorker(new Port());
|
|
assert.equal(core.constructor.name, "YawnCore");
|
|
core.dispose();
|
|
|
|
assert.equal(typeof importGltf, "function");
|
|
assert.equal(typeof loadCompleteScene, "function");
|
|
});
|