Files
workinf_Blender_Wasm/web/protocol/modifier-graph.ts
2026-08-12 04:47:48 -04:00

79 lines
3.2 KiB
TypeScript

import type { MeshSummaryIR, ModifierIR, SceneSnapshotIR } from "./scene-ir";
export interface ModifierGraphNode {
uuid: string;
meshId: string;
modifier: ModifierIR;
dependencies: string[];
}
export interface ModifierDependencyGraph {
nodes: ModifierGraphNode[];
order: string[];
cycles: string[][];
missingDependencies: string[];
}
export interface ModifierEvaluationReport {
status: "EVALUATED" | "METADATA_ONLY" | "BLOCKED";
order: string[];
cycles: string[][];
blockedBy: string[];
}
export function buildModifierDependencyGraph(snapshot: Pick<SceneSnapshotIR, "meshes">): ModifierDependencyGraph {
const nodes: ModifierGraphNode[] = [];
const known = new Set<string>();
for (const mesh of snapshot.meshes) {
let previous: string | undefined;
for (const modifier of mesh.modifierStack ?? []) {
const dependencies = Array.from(new Set([...(modifier.dependsOn ?? []), ...(previous ? [previous] : [])]));
nodes.push({ uuid: modifier.uuid, meshId: mesh.id, modifier, dependencies });
known.add(modifier.uuid);
previous = modifier.uuid;
}
}
const nodeById = new Map(nodes.map((node) => [node.uuid, node]));
const state = new Map<string, 0 | 1 | 2>();
const order: string[] = [];
const cycles: string[][] = [];
const missingDependencies: string[] = [];
const path: string[] = [];
const visit = (uuid: string): void => {
const current = state.get(uuid) ?? 0;
if (current === 2) return;
if (current === 1) {
const start = path.indexOf(uuid);
cycles.push(start >= 0 ? [...path.slice(start), uuid] : [uuid]);
return;
}
state.set(uuid, 1);
path.push(uuid);
for (const dependency of nodeById.get(uuid)?.dependencies ?? []) {
if (known.has(dependency)) visit(dependency);
else missingDependencies.push(`${uuid}:${dependency}`);
}
path.pop();
state.set(uuid, 2);
order.push(uuid);
};
for (const node of nodes) visit(node.uuid);
return { nodes, order, cycles, missingDependencies: Array.from(new Set(missingDependencies)).sort() };
}
export function evaluateModifierDependencyGraph(snapshot: Pick<SceneSnapshotIR, "meshes">): ModifierEvaluationReport {
const graph = buildModifierDependencyGraph(snapshot);
const blockedBy = graph.nodes.filter((node) => node.modifier.evaluationStatus === "BLOCKED").map((node) => node.uuid);
if (graph.cycles.length > 0) return { status: "BLOCKED", order: graph.order, cycles: graph.cycles, blockedBy: [...blockedBy, "MODIFIER_DEPENDENCY_CYCLE"] };
if (graph.missingDependencies.length > 0) return { status: "BLOCKED", order: graph.order, cycles: [], blockedBy: [...blockedBy, ...graph.missingDependencies] };
if (blockedBy.length > 0) return { status: "BLOCKED", order: graph.order, cycles: [], blockedBy };
if (graph.nodes.some((node) => node.modifier.evaluationStatus === "METADATA_ONLY")) {
return { status: "METADATA_ONLY", order: graph.order, cycles: [], blockedBy: [] };
}
return { status: "EVALUATED", order: graph.order, cycles: [], blockedBy: [] };
}
export function evaluateMeshModifierStack(mesh: MeshSummaryIR): ModifierEvaluationReport {
return evaluateModifierDependencyGraph({ meshes: [mesh] });
}