import { describe, expect, it, vi } from "vitest"; import { registerToolcraftRendererPipeline, type ToolcraftRendererPipelinePassContract, } from "./renderer-pipeline-registration"; import { createToolcraftRendererPipelineRuntime, type ToolcraftRendererPipelineInvalidation, type ToolcraftRendererPipelineRuntime, } from "./renderer-pipeline-runtime"; type TestResource = Readonly<{ id: string }>; type TestPasses = { decode: ToolcraftRendererPipelinePassContract< string, TestResource, readonly [resourceId: string] >; export: ToolcraftRendererPipelinePassContract; preprocess: ToolcraftRendererPipelinePassContract< string, TestResource, readonly [sourceId: string] >; }; function decodeKey(source: unknown, amount: unknown = 1) { return { "effect.amount": amount, "source.asset": source, }; } function createRegistration(runtimeId = "test-renderer-v1") { return registerToolcraftRendererPipeline()({ interactionInvalidation: [], passes: [ { cacheKey: ["source.asset", "effect.amount"], id: "decode", inputs: ["source.asset"], invalidatedBy: ["source.asset", "effect.amount"], kind: "decode", lifecycle: { cache: "retained-resource", resourceScope: "renderer" }, output: "source", quality: "full", runsOn: "worker", }, { cacheKey: ["source.asset"], id: "preprocess", inputs: ["source.asset"], invalidatedBy: ["source.asset"], kind: "preprocess", lifecycle: { cache: "retained-resource", resourceScope: "source" }, output: "intermediate", quality: "full", runsOn: "worker", }, { id: "export", inputs: ["source.asset"], invalidatedBy: ["source.asset"], kind: "export", lifecycle: { cache: "none", resourceScope: "call" }, output: "export", quality: "export", runsOn: "export-only", }, ], runtimeId, }); } function createDeferred() { let resolve!: (value: T) => void; const promise = new Promise((resolvePromise) => { resolve = resolvePromise; }); return { promise, resolve }; } let resourceInvocation = 0; async function getTestResource( runtime: ToolcraftRendererPipelineRuntime, registration: ReturnType, passId: "decode" | "preprocess", resourceKey: readonly [string], create: () => PromiseLike | TestResource, dispose: (resource: TestResource) => PromiseLike | void, ): Promise { let resource!: TestResource; const source = `resource-${++resourceInvocation}`; if (passId === "decode") { await runtime.runPass( registration.getPass("decode"), decodeKey(source), async (context) => { resource = await context.getOrCreateResource(resourceKey, create, dispose); return "pixels"; }, ); } else { await runtime.runPass( registration.getPass("preprocess"), { "source.asset": source }, async (context) => { resource = await context.getOrCreateResource(resourceKey, create, dispose); return "pixels"; }, ); } return resource; } describe("Toolcraft renderer pipeline runtime", () => { it("executes memoized passes once and records deterministic cache timing", async () => { const times = [10, 16]; const registration = createRegistration(); const runtime = createToolcraftRendererPipelineRuntime(registration, { now: () => times.shift() ?? 16, }); const decode = registration.getPass("decode"); const work = vi.fn().mockResolvedValueOnce("pixels").mockResolvedValueOnce("new"); await expect(runtime.runPass(decode, decodeKey("source-a"), work)).resolves.toBe( "pixels", ); await expect(runtime.runPass(decode, decodeKey("source-a"), work)).resolves.toBe( "pixels", ); expect(work).toHaveBeenCalledTimes(1); expect(runtime.getSnapshot().passes.decode).toEqual({ activeResources: 0, cacheHits: 1, cacheMisses: 1, durationMax: 6, durationTotal: 6, executions: 1, resourceCreations: 0, resourceDisposals: 0, transfers: 0, }); }); it("uses collision-safe keys and retains only the latest memoized key", async () => { const registration = createRegistration(); const runtime = createToolcraftRendererPipelineRuntime(registration); const decode = registration.getPass("decode"); const work = vi.fn((value: string) => value); await runtime.runPass(decode, decodeKey("a", "b"), () => work("pair")); await runtime.runPass(decode, decodeKey("a|b", ""), () => work("joined")); await runtime.runPass(decode, decodeKey("a", "b"), () => work("pair-again")); expect(work).toHaveBeenCalledTimes(3); expect(runtime.getSnapshot().passes.decode).toMatchObject({ cacheHits: 0, cacheMisses: 3, executions: 3, }); }); it("compiles cache values in descriptor order independent of object key order", async () => { const registration = createRegistration(); const runtime = createToolcraftRendererPipelineRuntime(registration); const decode = registration.getPass("decode"); const work = vi.fn().mockResolvedValue("pixels"); await runtime.runPass( decode, { "source.asset": "source-a", "effect.amount": 1 }, work, ); await runtime.runPass(decode, decodeKey("source-a"), work); expect(work).toHaveBeenCalledTimes(1); expect(runtime.getSnapshot().passes.decode.cacheHits).toBe(1); }); it("rejects malformed descriptor cache input without executing work", async () => { const registration = createRegistration(); const runtime = createToolcraftRendererPipelineRuntime(registration); const decode = registration.getPass("decode"); const exportPass = registration.getPass("export"); const work = vi.fn().mockResolvedValue("pixels"); await expect( runtime.runPass(decode, ["source-a", 1] as never, work), ).rejects.toThrow( 'Renderer pipeline pass "decode" cache input must be an object with exactly: source.asset, effect.amount.', ); await expect( runtime.runPass(decode, { "source.asset": "source-a" } as never, work), ).rejects.toThrow( 'Renderer pipeline pass "decode" cache input must have exactly own keys: source.asset, effect.amount.', ); await expect( runtime.runPass( decode, { "effect.amount": 1, "source.asset": "source-a", extra: true, } as never, work, ), ).rejects.toThrow( 'Renderer pipeline pass "decode" cache input must have exactly own keys: source.asset, effect.amount.', ); await expect( runtime.runPass(exportPass, {} as never, work), ).rejects.toThrow( 'Renderer pipeline pass "export" does not accept cache input.', ); await expect( runtime.runPass(decode, decodeKey("malformed-resource"), (context) => context.getOrCreateResource( "gpu" as never, () => ({ id: "gpu" }), () => undefined, ).then(() => "pixels"), ), ).rejects.toThrow('Renderer pipeline pass "decode" resource key must be an array.'); expect(work).not.toHaveBeenCalled(); expect(runtime.getSnapshot().passes.decode.executions).toBe(1); }); it("deduplicates concurrent work for the same pass key", async () => { const registration = createRegistration(); const runtime = createToolcraftRendererPipelineRuntime(registration); const decode = registration.getPass("decode"); let resolveWork!: (value: string) => void; const work = vi.fn( () => new Promise((resolve) => { resolveWork = resolve; }), ); const first = runtime.runPass(decode, decodeKey("source-a"), work); const second = runtime.runPass(decode, decodeKey("source-a"), work); resolveWork("pixels"); await expect(Promise.all([first, second])).resolves.toEqual(["pixels", "pixels"]); expect(work).toHaveBeenCalledTimes(1); expect(runtime.getSnapshot().passes.decode).toMatchObject({ cacheHits: 1, cacheMisses: 1, executions: 1, }); }); it("deduplicates A-B-A pending work while retaining only the latest completed key", async () => { const registration = createRegistration(); const runtime = createToolcraftRendererPipelineRuntime(registration); const decode = registration.getPass("decode"); const workA = createDeferred(); const workB = createDeferred(); const firstAWork = vi.fn(() => workA.promise); const workForB = vi.fn(() => workB.promise); const duplicateAWork = vi.fn(() => "duplicate-a"); const firstA = runtime.runPass(decode, decodeKey("a"), firstAWork); const firstB = runtime.runPass(decode, decodeKey("b"), workForB); const secondA = runtime.runPass(decode, decodeKey("a"), duplicateAWork); workB.resolve("result-b"); await expect(firstB).resolves.toBe("result-b"); workA.resolve("result-a"); await expect(Promise.all([firstA, secondA])).resolves.toEqual([ "result-a", "result-a", ]); await expect( runtime.runPass(decode, decodeKey("a"), () => "unexpected-a"), ).resolves.toBe("result-a"); await expect(runtime.runPass(decode, decodeKey("b"), () => "new-b")).resolves.toBe( "new-b", ); expect(firstAWork).toHaveBeenCalledTimes(1); expect(workForB).toHaveBeenCalledTimes(1); expect(duplicateAWork).not.toHaveBeenCalled(); expect(runtime.getSnapshot().passes.decode).toMatchObject({ cacheHits: 2, cacheMisses: 3, executions: 3, }); }); it("keeps the completed fallback available while its requested replacement is pending", async () => { const registration = createRegistration(); const runtime = createToolcraftRendererPipelineRuntime(registration); const decode = registration.getPass("decode"); const replacement = createDeferred(); const workA = vi.fn(() => "result-a"); const workB = vi.fn(() => replacement.promise); const duplicateA = vi.fn(() => "duplicate-a"); await expect(runtime.runPass(decode, decodeKey("a"), workA)).resolves.toBe( "result-a", ); const pendingB = runtime.runPass(decode, decodeKey("b"), workB); await expect( runtime.runPass(decode, decodeKey("a"), duplicateA), ).resolves.toBe("result-a"); replacement.resolve("result-b"); await expect(pendingB).resolves.toBe("result-b"); await expect( runtime.runPass(decode, decodeKey("a"), duplicateA), ).resolves.toBe("result-a"); expect(workA).toHaveBeenCalledTimes(1); expect(workB).toHaveBeenCalledTimes(1); expect(duplicateA).not.toHaveBeenCalled(); }); it("times rejected async work, removes it from cache, and permits retry", async () => { const times = [20, 23, 30, 35]; const registration = createRegistration(); const runtime = createToolcraftRendererPipelineRuntime(registration, { now: () => times.shift() ?? 35, }); const decode = registration.getPass("decode"); const failure = new Error("decode failed"); const work = vi.fn().mockRejectedValueOnce(failure).mockResolvedValueOnce("pixels"); await expect(runtime.runPass(decode, decodeKey("source-a"), work)).rejects.toBe(failure); await expect(runtime.runPass(decode, decodeKey("source-a"), work)).resolves.toBe( "pixels", ); expect(work).toHaveBeenCalledTimes(2); expect(runtime.getSnapshot().passes.decode).toMatchObject({ cacheHits: 0, cacheMisses: 2, durationMax: 5, durationTotal: 8, executions: 2, }); }); it("invalidates latest cache entries by source key and by pass", async () => { const registration = createRegistration(); const runtime = createToolcraftRendererPipelineRuntime(registration); const decode = registration.getPass("decode"); const work = vi.fn().mockResolvedValue("pixels"); await runtime.runPass(decode, decodeKey("source-a"), work); await runtime.invalidateSource("source-a").cleanup; await runtime.runPass(decode, decodeKey("source-a"), work); await runtime.invalidatePass(decode).cleanup; await runtime.runPass(decode, decodeKey("source-a"), work); expect(work).toHaveBeenCalledTimes(3); }); it("uses Map SameValueZero equality for source invalidation", async () => { const registration = createRegistration(); const runtime = createToolcraftRendererPipelineRuntime(registration); const decode = registration.getPass("decode"); const work = vi.fn().mockResolvedValue("pixels"); await runtime.runPass(decode, decodeKey(-0), work); await runtime.invalidateSource(0).cleanup; await runtime.runPass(decode, decodeKey(-0), work); await runtime.runPass(decode, decodeKey(Number.NaN), work); await runtime.invalidateSource(Number.NaN).cleanup; await runtime.runPass(decode, decodeKey(Number.NaN), work); expect(work).toHaveBeenCalledTimes(4); }); it("reuses cache entries with Map SameValueZero key equality", async () => { const registration = createRegistration(); const runtime = createToolcraftRendererPipelineRuntime(registration); const decode = registration.getPass("decode"); const work = vi.fn() .mockResolvedValueOnce("zero") .mockResolvedValueOnce("nan"); await expect(runtime.runPass(decode, decodeKey(-0), work)).resolves.toBe("zero"); await expect(runtime.runPass(decode, decodeKey(+0), work)).resolves.toBe("zero"); await expect(runtime.runPass(decode, decodeKey(Number.NaN), work)).resolves.toBe("nan"); await expect(runtime.runPass(decode, decodeKey(Number.NaN), work)).resolves.toBe("nan"); expect(work).toHaveBeenCalledTimes(2); expect(runtime.getSnapshot().passes.decode).toMatchObject({ cacheHits: 2, cacheMisses: 2, executions: 2, }); }); it("always executes no-cache passes and rejects foreign pass handles", async () => { const registration = createRegistration(); const runtime = createToolcraftRendererPipelineRuntime(registration); const exportPass = registration.getPass("export"); const work = vi.fn().mockResolvedValue("file"); await runtime.runPass(exportPass, undefined, work); await runtime.runPass(exportPass, undefined, work); expect(work).toHaveBeenCalledTimes(2); expect(runtime.getSnapshot().passes.export).toMatchObject({ cacheHits: 0, cacheMisses: 2, executions: 2, }); await expect( runtime.runPass(createRegistration("other-v1").getPass("export"), undefined, () => "x"), ).rejects.toThrow( 'Renderer pipeline pass "export" does not belong to registration "test-renderer-v1".', ); }); it("inserts execution and cache state before publishing to reentrant subscribers", async () => { const registration = createRegistration(); const runtime = createToolcraftRendererPipelineRuntime(registration); const decode = registration.getPass("decode"); const work = vi.fn().mockResolvedValue("pixels"); let invalidation: ToolcraftRendererPipelineInvalidation | undefined; const unsubscribe = runtime.subscribe(() => { if (!invalidation && runtime.getSnapshot().passes.decode.executions === 1) { invalidation = runtime.invalidatePass(decode); } }); await runtime.runPass(decode, decodeKey("source-a"), work); await invalidation?.cleanup; unsubscribe(); await runtime.runPass(decode, decodeKey("source-a"), work); expect(work).toHaveBeenCalledTimes(2); }); it("records transfers and returns deeply immutable snapshots without metadata", () => { const registration = createRegistration(); const runtime = createToolcraftRendererPipelineRuntime(registration); const decode = registration.getPass("decode"); runtime.recordTransfer(decode, 2); const snapshot = runtime.getSnapshot(); expect(snapshot.runtimeId).toBe("test-renderer-v1"); expect(snapshot.passes.decode.transfers).toBe(2); expect(Object.isFrozen(snapshot)).toBe(true); expect(Object.isFrozen(snapshot.passes)).toBe(true); expect(Object.isFrozen(snapshot.passes.decode)).toBe(true); expect(snapshot.passes.decode).not.toHaveProperty("cacheKey"); expect(snapshot.passes.decode).not.toHaveProperty("lifecycle"); expect(runtime.memoizedCachePolicy).toBe("latest-completed"); }); it("rejects unregistered descriptors at the JavaScript boundary", () => { expect(() => createToolcraftRendererPipelineRuntime({ interactionInvalidation: [], passes: [], runtimeId: "raw-v1", } as never), ).toThrow( "Renderer pipeline runtime requires a compiled executable registration.", ); }); it("fully disposes once and rejects new work after disposal", async () => { const registration = createRegistration(); const runtime = createToolcraftRendererPipelineRuntime(registration); const decode = registration.getPass("decode"); const disposeResource = vi.fn(); await getTestResource( runtime, registration, "decode", ["shared"], () => ({ id: "renderer" }), disposeResource, ); await Promise.all([runtime.dispose(), runtime.dispose()]); await runtime.dispose(); expect(disposeResource).toHaveBeenCalledTimes(1); expect(runtime.getSnapshot().disposed).toBe(true); await expect(runtime.runPass(decode, decodeKey("disposed"), () => "pixels")).rejects.toThrow( 'Renderer pipeline runtime "test-renderer-v1" is disposed.', ); }); });