feat: add render graph driven renderer architecture

Amp-Thread-ID: https://ampcode.com/threads/T-019f9d91-77c1-7206-a60f-ed6554ce92ab

Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
This commit is contained in:
Amp
2026-07-27 02:53:44 +00:00
co-authored by heaust
parent 8a8369706b
commit d4e8634f67
290 changed files with 48804 additions and 1995 deletions
+494
View File
@@ -0,0 +1,494 @@
import assert from "node:assert/strict";
import test from "node:test";
import { createFxNodeHeadless } from "@lib/headless-runtime.js";
import { commandId, nodeId, type GraphState } from "@lib/core/types.js";
import { rebindBoundEngineAuthority } from "@lib/composition/bound-engine.js";
import type { FxNodeCompositionData } from "@lib/composition/types.js";
import { canonicalJsonEqual } from "@lib/core/json.js";
import { admitStructuredData } from "@lib/core/json.js";
import { PERSISTENCE_LIMITS } from "@lib/composition/bound-document.js";
import { APPLICATION_COMPILED, APPLICATION_HEADLESS } from "./application.js";
const composition = {
...APPLICATION_COMPILED.source,
id: "runtime-test",
version: 91,
nodes: {
value: APPLICATION_COMPILED.source.nodes["fxnode.shader.value"]!,
frame: APPLICATION_COMPILED.source.nodes["fxnode.common.frame"]!,
},
};
const runtime = createFxNodeHeadless(composition);
const request = (version: number, command: Parameters<typeof runtime.transition>[1]["command"]) => ({
commandId: commandId(`c${version}`),
expectedVersion: version,
source: "api" as const,
command,
});
test("bound runtime materializes, edits, resets, undoes, redoes, saves and loads", () => {
assert.equal(runtime.emptyDocument().catalogVersion, 91);
const materialized = runtime.materializeNode("direct", "value");
assert.equal(materialized.typeId, "value");
let state = runtime.createEngine(runtime.emptyDocument());
let result = runtime.transition(
state,
request(0, { type: "node.add", nodeId: nodeId("n"), nodeType: "value", position: { x: 1, y: 2 } }),
);
assert.equal(result.status, "committed");
if (result.status !== "committed") return;
state = result.state;
const key = Object.keys(state.document.nodes.n!.parameters)[0]!;
result = runtime.transition(
state,
request(1, { type: "node.parameter", id: nodeId("n"), key, value: { kind: "number", value: 4 } }),
);
assert.equal(result.status, "committed");
if (result.status !== "committed") return;
state = result.state;
result = runtime.transition(state, request(2, { type: "node.parameter-reset", id: nodeId("n"), key }));
assert.equal(result.status, "committed");
if (result.status !== "committed") return;
state = result.state;
result = runtime.transition(state, request(3, { type: "undo" }));
assert.equal(result.status, "committed");
if (result.status !== "committed") return;
result = runtime.transition(result.state, request(4, { type: "redo" }));
assert.equal(result.status, "committed");
if (result.status !== "committed") return;
const saved = runtime.save(result.state.document),
decoded = runtime.decodeGraphDocument(saved);
assert.equal(decoded.ok, true);
const loaded = runtime.load(result.state, saved);
assert.equal(loaded.ok, true);
assert.equal(
runtime.serializeGraphDocument(result.state.document),
runtime.serializeGraphDocument(decoded.ok ? decoded.value : result.state.document),
);
});
test("bound runtime rejects unknown add and invalid initial engine", () => {
const state = runtime.createEngine(runtime.emptyDocument());
const bad = { type: "node.add", nodeId: nodeId("x"), nodeType: "missing", position: { x: 0, y: 0 } } as never;
const result = runtime.transition(state, request(0, bad));
assert.equal(result.status, "rejected");
if (result.status === "rejected") assert.equal(result.error.code, "node.type-unknown");
assert.throws(() =>
runtime.createEngine({
...runtime.emptyDocument(),
nodes: { x: { ...runtime.materializeNode("x", "value"), size: { x: 0, y: 1 } } },
}),
);
assert.throws(() => runtime.createEngine({ ...runtime.emptyDocument(), catalogVersion: 999 }));
assert.throws(() =>
runtime.createEngine({
...runtime.emptyDocument(),
links: {
wrong: {
id: "actual",
fromNodeId: "a",
fromSocketId: "a:out",
toNodeId: "b",
toSocketId: "b:in",
muted: false,
extensions: {},
},
},
} as never),
);
});
test("graph state decoding is exact for current composition and ignores only observational version", () => {
const document = {
...runtime.emptyDocument("state-source"),
nodes: { n: runtime.materializeNode("n", "value", { x: 4, y: 5 }) },
},
state = runtime.createEngine(document),
snapshot = runtime.getState(state),
input = structuredClone(snapshot),
nested = input.nodes[0]!;
const decoded = runtime.decodeGraphState(input);
assert.equal(decoded.ok, true);
assert.deepEqual(input, snapshot);
assert.equal(Object.isFrozen(input), false);
assert.equal(Object.isFrozen(nested), false);
if (!decoded.ok) return;
assert.equal(decoded.value.nodes.n?.known, true);
assert.ok(Object.isFrozen(decoded.value));
const spoofed = runtime.decodeGraphState({ ...snapshot, version: Number.MAX_SAFE_INTEGER });
assert.equal(spoofed.ok, true);
const missingKnown = { ...snapshot, nodes: snapshot.nodes.map(({ known: _, ...node }) => node) },
wrongKnown = { ...snapshot, nodes: snapshot.nodes.map((node) => ({ ...node, known: false })) },
foreign = { ...snapshot, catalogVersion: 999 };
for (const value of [missingKnown, wrongKnown, foreign]) {
const result = runtime.decodeGraphState(value);
assert.equal(result.ok, false);
if (!result.ok) assert.equal(result.issues[0]?.code, "state.inexact");
}
const malformed = runtime.decodeGraphState({ ...snapshot, extra: true });
assert.equal(malformed.ok, false);
if (!malformed.ok) assert.equal(malformed.issues[0]?.code, "state.shape");
const failed = structuredClone(wrongKnown),
failedNested = failed.nodes[0]!;
runtime.decodeGraphState(failed);
assert.equal(Object.isFrozen(failed), false);
assert.equal(Object.isFrozen(failedNested), false);
assert.deepEqual(failed, wrongKnown);
const cycle: any = {};
cycle.self = cycle;
const sparse: any = structuredClone(snapshot);
sparse.nodes.length = 2;
const nonfinite: any = structuredClone(snapshot);
nonfinite.nodes[0].position.x = Infinity;
const overLimit: any = { ...snapshot, nodes: Array(100_001).fill(null) };
for (const value of [cycle, sparse, nonfinite, overLimit])
assert.doesNotThrow(() => assert.equal(runtime.decodeGraphState(value).ok, false));
});
test("maximum admitted durable layout round-trips through getState and state decoding", () => {
const base = runtime.save({
...runtime.emptyDocument("state-limit"),
nodes: { n: runtime.materializeNode("n", "value") },
}),
metrics = admitStructuredData(base, PERSISTENCE_LIMITS);
assert.equal(metrics.ok, true);
if (!metrics.ok) return;
const paddingLength = PERSISTENCE_LIMITS.maxValues - metrics.metrics.values - 1,
layout = { ...base, metadata: { padding: Array(paddingLength).fill(null) } } as typeof base,
admitted = admitStructuredData(layout, PERSISTENCE_LIMITS);
assert.equal(admitted.ok, true);
if (!admitted.ok) return;
assert.equal(admitted.metrics.values, PERSISTENCE_LIMITS.maxValues);
const document = runtime.decodeGraphDocument(layout);
assert.equal(document.ok, true);
if (!document.ok) return;
const state = runtime.getState(runtime.createEngine(document.value)),
decoded = runtime.decodeGraphState(state);
assert.equal(decoded.ok, true);
if (decoded.ok) assert.equal((decoded.value.metadata.padding as readonly unknown[]).length, paddingLength);
});
test("state replacement commits once and is one-step undoable and redoable", () => {
const targetDocument = {
...runtime.emptyDocument("replacement"),
nodes: { n: runtime.materializeNode("n", "value", { x: 7, y: 8 }) },
},
target = runtime.getState(runtime.createEngine(targetDocument));
let state = runtime.createEngine(runtime.emptyDocument("before"), 2);
const replaced = runtime.replaceState(state, { commandId: commandId("replace"), expectedVersion: 0, target });
assert.equal(replaced.status, "committed");
if (replaced.status !== "committed") return;
assert.equal(replaced.state.version, 1);
assert.equal(replaced.state.document.graphId, "replacement");
assert.equal(replaced.state.undo.length, 1);
assert.equal(replaced.mutationEnvelope.cause, "api");
assert.deepEqual(
replaced.mutationEnvelope.mutations.map((m) => m.kind),
["document.replaced"],
);
state = replaced.state;
const undo = runtime.transition(state, request(1, { type: "undo" }));
assert.equal(undo.status, "committed");
if (undo.status !== "committed") return;
assert.equal(undo.state.version, 2);
assert.equal(undo.state.document.graphId, "before");
assert.equal(undo.state.redo.length, 1);
const redo = runtime.transition(undo.state, request(2, { type: "redo" }));
assert.equal(redo.status, "committed");
if (redo.status !== "committed") return;
assert.equal(redo.state.version, 3);
assert.equal(redo.state.document.graphId, "replacement");
const noop = runtime.replaceState(redo.state, {
commandId: commandId("noop"),
expectedVersion: 3,
target: runtime.getState(redo.state),
});
assert.equal(noop.status, "noop");
assert.equal(noop.state, redo.state);
const withoutHistory = runtime.createEngine(runtime.emptyDocument("zero"), 0),
zero = runtime.replaceState(withoutHistory, { commandId: commandId("zero"), expectedVersion: 0, target });
assert.equal(zero.status, "committed");
if (zero.status === "committed") assert.equal(zero.state.undo.length, 0);
});
test("state replacement checks staleness before inspecting hostile input and noops before overflow", () => {
const state = runtime.createEngine(runtime.emptyDocument());
let inspections = 0;
const hostile = Object.defineProperty({}, "graphId", {
enumerable: true,
get() {
inspections++;
throw new Error("must not inspect");
},
}) as GraphState<typeof composition>;
const stale = runtime.replaceState(state, {
commandId: commandId("stale-state"),
expectedVersion: 1,
target: hostile,
});
assert.equal(stale.status, "rejected");
assert.equal(stale.state, state);
assert.equal(inspections, 0);
if (stale.status === "rejected") assert.equal(stale.error.code, "version.stale");
const current = runtime.replaceState(state, {
commandId: commandId("hostile-state"),
expectedVersion: 0,
target: hostile,
});
assert.equal(current.status, "rejected");
assert.equal(inspections, 0);
if (current.status === "rejected") assert.equal(current.error.code, "data.inspect");
const maximum = { ...state, version: Number.MAX_SAFE_INTEGER } as typeof state,
target = runtime.getState(state),
noop = runtime.replaceState(maximum, {
commandId: commandId("maximum-noop"),
expectedVersion: Number.MAX_SAFE_INTEGER,
target,
});
assert.equal(noop.status, "noop");
assert.equal(noop.state, maximum);
const changed = { ...target, graphId: "changed" as typeof target.graphId },
overflow = runtime.replaceState(maximum, {
commandId: commandId("maximum-change"),
expectedVersion: Number.MAX_SAFE_INTEGER,
target: changed,
});
assert.equal(overflow.status, "rejected");
assert.equal(overflow.state, maximum);
if (overflow.status === "rejected") assert.equal(overflow.error.code, "version.overflow");
});
test("state replacement preserves bounded prior history, clears redo, and ignores spoofed target version", () => {
const target = runtime.getState(
runtime.createEngine({
...runtime.emptyDocument("target"),
nodes: { target: runtime.materializeNode("target", "value") },
}),
);
let state = runtime.createEngine(runtime.emptyDocument("history"), 2),
first = runtime.transition(
state,
request(0, { type: "node.add", nodeId: nodeId("a"), nodeType: "value", position: { x: 0, y: 0 } }),
);
assert.equal(first.status, "committed");
if (first.status !== "committed") return;
state = first.state;
const second = runtime.transition(
state,
request(1, { type: "node.add", nodeId: nodeId("b"), nodeType: "value", position: { x: 0, y: 0 } }),
);
assert.equal(second.status, "committed");
if (second.status !== "committed") return;
const undone = runtime.transition(second.state, request(2, { type: "undo" }));
assert.equal(undone.status, "committed");
if (undone.status !== "committed") return;
assert.equal(undone.state.redo.length, 1);
const replaced = runtime.replaceState(undone.state, {
commandId: commandId("history-replace"),
expectedVersion: 3,
target: { ...target, version: 999 } as GraphState<typeof composition>,
});
assert.equal(replaced.status, "committed");
if (replaced.status !== "committed") return;
assert.equal(replaced.state.version, 4);
assert.equal(replaced.state.redo.length, 0);
assert.equal(replaced.state.undo.length, 2);
assert.deepEqual(
replaced.state.undo.at(-1)?.forward.map((m) => m.kind),
["document.replaced"],
);
assert.deepEqual(
replaced.state.undo.at(-1)?.inverse.map((m) => m.kind),
["document.replaced"],
);
const undoIdentity = replaced.state.undo,
redoIdentity = replaced.state.redo,
entryIdentity = replaced.state.undo[0],
noop = runtime.replaceState(replaced.state, {
commandId: commandId("history-noop"),
expectedVersion: 4,
target: runtime.getState(replaced.state),
});
assert.equal(noop.status, "noop");
assert.equal(noop.state.undo, undoIdentity);
assert.equal(noop.state.redo, redoIdentity);
assert.equal(noop.state.undo[0], entryIdentity);
const limited = runtime.createEngine(runtime.emptyDocument("limited"), 1),
added = runtime.transition(
limited,
request(0, { type: "node.add", nodeId: nodeId("old"), nodeType: "value", position: { x: 0, y: 0 } }),
);
assert.equal(added.status, "committed");
if (added.status !== "committed") return;
const one = runtime.replaceState(added.state, {
commandId: commandId("limited-replace"),
expectedVersion: 1,
target,
});
assert.equal(one.status, "committed");
if (one.status === "committed") {
assert.equal(one.state.undo.length, 1);
assert.deepEqual(
one.state.undo[0]?.forward.map((m) => m.kind),
["document.replaced"],
);
}
});
test("composition runtimes isolate overlapping IDs; unknown versions roundtrip read-only", () => {
const other = createFxNodeHeadless({
...composition,
id: "other",
version: 92,
nodes: { ...composition.nodes, value: { ...composition.nodes.value, title: "Other" } },
});
assert.equal(other.materializeNode("a", "value").label, "Other");
const raw = runtime.save({ ...runtime.emptyDocument(), nodes: { a: runtime.materializeNode("a", "value") } });
const unknownVersion = { ...raw, nodes: raw.nodes.map((n) => ({ ...n, typeVersion: n.typeVersion + 1 })) };
const decoded = runtime.decodeGraphDocument(unknownVersion);
assert.equal(decoded.ok, true);
if (!decoded.ok) return;
assert.equal(decoded.value.nodes.a!.known, false);
assert.equal(runtime.parseGraphDocument(runtime.serializeGraphDocument(decoded.value)).ok, true);
let state = runtime.createEngine(decoded.value);
const key = Object.keys(state.document.nodes.a!.parameters)[0]!;
const edit = runtime.transition(state, request(0, { type: "node.parameter-reset", id: nodeId("a"), key }));
assert.equal(edit.status, "rejected");
const malformed = { ...raw, nodes: raw.nodes.map((n) => ({ ...n, parameters: {} })) };
assert.equal(runtime.decodeGraphDocument(malformed).ok, false);
});
test("foreign catalog versions normalize and future node payloads remain opaque", () => {
const raw = runtime.save({ ...runtime.emptyDocument(), nodes: { a: runtime.materializeNode("a", "value") } });
const foreign = { ...raw, catalogVersion: 999 };
const normalized = runtime.decodeGraphDocument(foreign);
assert.equal(normalized.ok, true);
if (!normalized.ok) return;
assert.equal(normalized.value.catalogVersion, 91);
assert.equal(runtime.save(normalized.value).catalogVersion, 91);
const loaded = runtime.load(runtime.createEngine(runtime.emptyDocument()), foreign);
assert.equal(loaded.ok, true);
if (loaded.ok) assert.equal(loaded.snapshotEnvelope.snapshot.catalogVersion, 91);
const future = {
...raw,
nodes: raw.nodes.map((n) => ({
...n,
typeId: "future.node",
typeVersion: 12,
parameters: { future: { history: [] } },
extensions: { session: { selection: [1] } },
futureField: { hovered: true },
sockets: [
{
...n.sockets[0]!,
dataType: "future-socket",
accepts: ["future-source"],
defaultValue: null,
metadata: { runtimeVersion: 7 },
},
],
})),
};
const decoded = runtime.decodeGraphDocument(future);
assert.equal(decoded.ok, true);
if (!decoded.ok) return;
assert.equal(decoded.value.nodes.a!.known, false);
assert.equal(JSON.stringify(runtime.save(decoded.value)), JSON.stringify(future));
});
test("authority rebind preserves graph version for presentation changes and resets history", () => {
const source: FxNodeCompositionData = composition,
candidateSource: FxNodeCompositionData = { ...source, theme: { ...source.theme, background: "#123456" } },
current = createFxNodeHeadless(source),
candidate = createFxNodeHeadless(candidateSource);
let state = current.createEngine(current.emptyDocument(), 7),
result = current.transition(state, {
commandId: commandId("add"),
expectedVersion: 0,
source: "api",
command: { type: "node.add", nodeId: nodeId("n"), nodeType: "value", position: { x: 0, y: 0 } },
});
assert.equal(result.status, "committed");
if (result.status !== "committed") return;
state = result.state;
assert.equal(state.undo.length, 1);
const rebound = rebindBoundEngineAuthority(state, current, candidate, { commandId: commandId("rebind") });
assert.equal(rebound.ok, true);
if (!rebound.ok) return;
assert.equal(rebound.graphChanged, false);
assert.equal(rebound.state.version, state.version);
assert.equal(rebound.state.historyLimit, 7);
assert.equal(rebound.state.undo.length, 0);
assert.equal(rebound.state.redo.length, 0);
assert.equal(rebound.state.document.nodes.n!.known, true);
assert.equal(canonicalJsonEqual({ b: 1, a: 2 }, { a: 2, b: 1 }), true);
const left = Object.create(null) as Record<string, unknown>,
right = Object.create(null) as Record<string, unknown>;
left.__proto__ = { value: 1 };
right.__proto__ = { value: 2 };
assert.equal(canonicalJsonEqual(left, right), false);
});
test("authority rebind promotes opaque nodes and explicitly removed definitions remain opaque", () => {
const full: FxNodeCompositionData = composition,
withoutFrame: FxNodeCompositionData = { ...full, nodes: { value: full.nodes.value! } },
old = createFxNodeHeadless(withoutFrame),
candidate = createFxNodeHeadless(full);
const authored = candidate.save({
...candidate.emptyDocument(),
nodes: { f: candidate.materializeNode("f", "frame") },
}),
opaque = old.decodeGraphDocument(authored);
assert.equal(opaque.ok, true);
if (!opaque.ok) return;
assert.equal(opaque.value.nodes.f!.known, false);
const promoted = rebindBoundEngineAuthority(old.createEngine(opaque.value), old, candidate, {
commandId: commandId("promote"),
});
assert.equal(promoted.ok, true);
if (!promoted.ok || !promoted.graphChanged) return;
assert.equal(promoted.state.version, 1);
assert.equal(promoted.state.document.nodes.f!.known, true);
assert.equal(promoted.mutationEnvelope.cause, "composition");
assert.equal(promoted.mutationEnvelope.mutations[0]?.kind, "document.replaced");
assert.deepEqual(old.save(opaque.value), candidate.save(promoted.state.document));
const current = createFxNodeHeadless(full),
removed = createFxNodeHeadless(withoutFrame),
known = current.createEngine({ ...current.emptyDocument(), nodes: { f: current.materializeNode("f", "frame") } });
const rejected = rebindBoundEngineAuthority(known, current, removed, { commandId: commandId("reject") });
assert.equal(rejected.ok, false);
if (rejected.ok) return;
assert.equal(rejected.state, known);
assert.equal(rejected.issues[0]?.code, "composition.node-demotion");
const accepted = rebindBoundEngineAuthority(known, current, removed, {
commandId: commandId("remove"),
removedNodeTypes: new Set(["frame"]),
});
assert.equal(accepted.ok, true);
if (!accepted.ok) return;
assert.equal(accepted.graphChanged, true);
assert.equal(accepted.state.document.nodes.f!.known, false);
assert.equal(accepted.state.document.nodes.f!.id, "f");
});
test("graph-changing authority rebind rejects version overflow atomically", () => {
const full: FxNodeCompositionData = composition,
removedSource: FxNodeCompositionData = { ...full, nodes: { value: full.nodes.value! } },
current = createFxNodeHeadless(full),
removed = createFxNodeHeadless(removedSource);
const normal = current.createEngine({
...current.emptyDocument(),
nodes: { f: current.materializeNode("f", "frame") },
}),
state = { ...normal, version: Number.MAX_SAFE_INTEGER, undo: [{ forward: [], inverse: [] }] } as typeof normal;
const result = rebindBoundEngineAuthority(state, current, removed, {
commandId: commandId("overflow"),
removedNodeTypes: new Set(["frame"]),
});
assert.equal(result.ok, false);
if (result.ok) return;
assert.equal(result.state, state);
assert.equal(result.issues[0]?.code, "version.overflow");
assert.equal(result.state.undo.length, 1);
});