Expand render graph playground editing

Amp-Thread-ID: https://ampcode.com/threads/T-01a033d4-0e44-708e-af0e-6166283416ff
Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
This commit is contained in:
Amp
2026-08-25 05:18:14 +00:00
co-authored by heaust
parent d71da85307
commit e7546e4b78
20 changed files with 3058 additions and 303 deletions
+12 -2
View File
@@ -206,7 +206,13 @@ type RpcRequest = WorkerRequest extends infer R
: never;
const STARTUP_TIMEOUT_MS = 5_000;
const emptySelection = (): FxNodeSelectionSnapshot =>
Object.freeze({ nodeCount: 0, linkCount: 0, canRemove: false, mute: Object.freeze({ enabled: false as const }) });
Object.freeze({
nodeIds: Object.freeze([]),
nodeCount: 0,
linkCount: 0,
canRemove: false,
mute: Object.freeze({ enabled: false as const }),
});
const reservedCanvases = new WeakSet<HTMLCanvasElement>();
@@ -750,7 +756,11 @@ class FxNodeViewClient implements FxNodeView {
if (this.detachedError) return;
if (data.type === "view.frame") this.consumeFrame(data);
else if (data.type === "view.selection.host") {
this.selection = Object.freeze({ ...data.projection, mute: Object.freeze({ ...data.projection.mute }) });
this.selection = Object.freeze({
...data.projection,
nodeIds: Object.freeze([...data.projection.nodeIds]),
mute: Object.freeze({ ...data.projection.mute }),
});
this.selectionReady();
this.publishHost(this.hostSnapshot.colorPickerOpen);
} else if (data.type === "view.node-menu.result") {
+2
View File
@@ -53,6 +53,8 @@ export interface FxNodeActionOptions {
readonly expectedVersion?: number;
}
export interface FxNodeSelectionSnapshot {
/** Worker-authoritative IDs of the selected nodes in this view. */
readonly nodeIds: readonly string[];
readonly nodeCount: number;
readonly linkCount: number;
readonly canRemove: boolean;
+7 -2
View File
@@ -11,7 +11,7 @@ import { validCommand } from "../commands/validate.js";
import type { FxNodeCamera, FxNodeSelectionSnapshot, FxNodeViewport } from "./host-types.js";
import { fxNodeDevicePixels, FXNODE_VIEW_LIMITS } from "./view-limits.js";
export const PROTOCOL_VERSION = 3 as const;
export const PROTOCOL_VERSION = 4 as const;
export type ViewId = string;
export type Camera = FxNodeCamera;
export type VersionExpectation = { readonly kind: "current" } | { readonly kind: "exact"; readonly version: number };
@@ -818,8 +818,13 @@ function validHostSnapshot(v: unknown): v is HostInteractionSnapshot {
function validSelectionProjection(v: unknown): v is FxNodeSelectionSnapshot {
if (
!record(v) ||
!exact(v, ["nodeCount", "linkCount", "canRemove", "mute"]) ||
!exact(v, ["nodeIds", "nodeCount", "linkCount", "canRemove", "mute"]) ||
!Array.isArray(v.nodeIds) ||
v.nodeIds.length > 1024 ||
v.nodeIds.some((id) => typeof id !== "string" || id.length === 0 || id.length > 512) ||
new Set(v.nodeIds).size !== v.nodeIds.length ||
!nonnegativeSafeInteger(v.nodeCount) ||
v.nodeCount !== v.nodeIds.length ||
!nonnegativeSafeInteger(v.linkCount) ||
v.canRemove !== (v.nodeCount > 0 || v.linkCount > 0) ||
!record(v.mute)
+10 -5
View File
@@ -483,9 +483,8 @@ function isStandardNode(id: import("../core/types.js").NodeId): boolean {
}
function publishSelection(view: WorkerView, force = false): void {
if (!state || !application) return;
const nodes = [...view.session.selectedNodes]
.map((id) => state!.document.nodes[id])
.filter((node): node is NonNullable<typeof node> => !!node),
const nodeIds = [...view.session.selectedNodes].filter((id) => !!state!.document.nodes[id]),
nodes = nodeIds.map((id) => state!.document.nodes[id]).filter((node): node is NonNullable<typeof node> => !!node),
linkCount = [...view.session.selectedLinks].filter((id) => state!.document.links[id]).length,
eligible = nodes.filter((node) => isStandardNode(node.id)),
mute = eligible.length
@@ -498,8 +497,14 @@ function publishSelection(view: WorkerView, force = false): void {
: "mixed") as "all-muted" | "all-unmuted" | "mixed",
}
: { enabled: false as const },
projection = { nodeCount: nodes.length, linkCount, canRemove: nodes.length + linkCount > 0, mute },
signature = `${projection.nodeCount}|${projection.linkCount}|${+projection.canRemove}|${mute.enabled ? mute.state : "disabled"}`;
projection = {
nodeIds,
nodeCount: nodes.length,
linkCount,
canRemove: nodes.length + linkCount > 0,
mute,
},
signature = `${JSON.stringify(nodeIds)}|${projection.linkCount}|${+projection.canRemove}|${mute.enabled ? mute.state : "disabled"}`;
if (!force && signature === view.selectionSignature) return;
view.selectionSignature = signature;
post({ protocol: PROTOCOL_VERSION, type: "view.selection.host", viewId: view.id, projection });