Files
feuerwerk/src/core/graph/Graph.ts
T
rootandClaude Fable 5.1 8a7185948d Make texture and colour full kinds and drop the load-time migrations
- `Texture` and `Color` can be picked as a variable kind, a struct field
  kind and a declared port kind; a new `Texture` node hands a picture id
  into the graph. Declared, a texture is its bare id – the `$texture` tag
  stays a list-only thing.
- The `String` node is labelled "Text" like its kind; the type id stays.
- Whole-number inputs (`Item at`, `Set item`, `Sub list`, `Fill`,
  `Repeat`, `Substring`, `To text`) show no decimals (`int()` in shared.ts).
- All server projects are reseeded, so `legacyNodes.ts`,
  `migrateDeclaredLists` and the `ownerId` fallback go, with their tests.
- Note `reseed-because-of-list` moved to done/; new note `refactor-types`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 14:06:14 +02:00

95 lines
3.0 KiB
TypeScript

import type { PropertyBag } from '../model/Properties';
/** `exec` carries control flow, the others values; `any` accepts every value type. */
export type PortType = 'exec' | 'number' | 'boolean' | 'string' | 'object' | 'list' | 'struct' | 'any';
export interface PortRef {
node: string;
port: string;
}
/**
* `type` names a NodeDef in the registry; `params` holds both the node's own settings and the
* inline values of unconnected data inputs, keyed by param or port key.
*/
export interface GraphNode {
id: string;
type: string;
x: number;
y: number;
params: PropertyBag;
/**
* Ports the node declares itself, on top of the fixed ones of its type; the types that have
* them say so with `NodeDef.declaresPorts`. A behavior's start/end node is what gives the
* behavior its signature; an Expand/Pack node is what gives a struct its fields.
*/
ports?: DeclaredPort[];
/** Drawn as its title bar alone, every link meeting at one point per side. Purely visual. */
collapsed?: boolean;
}
/** A port a start/end node declares. `kind` maps to the PortType of the same name (`text` → string). */
export interface DeclaredPort {
key: string;
kind: DeclaredPortKind;
/** On a node declaring `both` sides: the port is an input too, so the node writes it. */
write?: true;
}
/** A color travels as a number and a texture as text; the kind says which field edits the value. */
export const DECLARED_PORT_KINDS = [
'number',
'boolean',
'text',
'color',
'texture',
'object',
'list',
'struct',
] as const;
export type DeclaredPortKind = (typeof DECLARED_PORT_KINDS)[number];
export function declaredPortType(kind: DeclaredPortKind): PortType {
if (kind === 'text' || kind === 'texture') return 'string';
return kind === 'color' ? 'number' : kind;
}
export interface GraphEdge {
id: string;
from: PortRef;
to: PortRef;
/** Drawn as a stub at each end instead of a curve across the graph. Purely visual. */
folded?: boolean;
}
export interface Graph {
nodes: GraphNode[];
edges: GraphEdge[];
}
export function createEmptyGraph(): Graph {
return { nodes: [], edges: [] };
}
export function findNode(graph: Graph, nodeId: string): GraphNode | undefined {
return graph.nodes.find((n) => n.id === nodeId);
}
/** Edges arriving at one input port (data inputs allow at most one, exec inputs any number). */
export function edgesInto(graph: Graph, nodeId: string, port: string): GraphEdge[] {
return graph.edges.filter((e) => e.to.node === nodeId && e.to.port === port);
}
export function edgesOutOf(graph: Graph, nodeId: string, port: string): GraphEdge[] {
return graph.edges.filter((e) => e.from.node === nodeId && e.from.port === port);
}
export function edgesOfNode(graph: Graph, nodeId: string): GraphEdge[] {
return graph.edges.filter((e) => e.from.node === nodeId || e.to.node === nodeId);
}
export function portsCompatible(from: PortType, to: PortType): boolean {
if (from === 'exec' || to === 'exec') return from === to;
return from === to || from === 'any' || to === 'any';
}