Let a behavior be a node in another graph
A behavior that has a Start and an End node can now be dropped into any other graph as a node of its own. What is declared on the start node becomes the inputs of that node, what hangs on the end node its outputs; the port list is derived from the sub graph, not copied, so declaring a port in one graph grows the node in the other without touching it. The node carries the behavior's name and the palette lists behaviors by name, so a call is one grab instead of a node plus a dropdown. An embedded behavior has no object and therefore no place for variables: every call starts on the asset defaults and communicates only through its ports. That is the conservative direction - state can still be added later, and shared state already has a home in a behavior object in the scene. Node ids are unique per graph only, so each call gets its own pulse cache; without that, two graphs would collide. Renaming a port used to silently drop the value typed on the calling node. It is carried along now, project wide, like the edges.
This commit is contained in:
1 parent
64e034ffb9
commit
6702ff8abe
31 files changed
+1402
-107
No files matched your search
@@ -6,7 +6,9 @@ import {
|
||||
type GraphNode,
|
||||
portsCompatible,
|
||||
} from '../graph/Graph';
|
||||
import { isTerminalType, resolveDef, terminalNode } from '../graph/nested';
|
||||
import { nodeDef } from '../graph/NodeRegistry';
|
||||
import { behaviorLookup } from '../model/behaviorPrograms';
|
||||
import { newId } from '../ids';
|
||||
import type { PropertyBag, PropertyValue } from '../model/Properties';
|
||||
import type { Project } from '../model/Project';
|
||||
@@ -42,7 +44,12 @@ export class AddNodeCommand implements Command {
|
||||
|
||||
execute(project: Project): void {
|
||||
requireNodeDef(this.node.type);
|
||||
requireBehavior(project, this.assetId).graph.nodes.push(this.node);
|
||||
const graph = requireBehavior(project, this.assetId).graph;
|
||||
// The two terminals give the behavior its signature; a second one of either would make
|
||||
// "which ports does this behavior have" ambiguous.
|
||||
if (isTerminalType(this.node.type) && terminalNode(graph, this.node.type))
|
||||
throw new CommandError(`This behavior already has a ${nodeDef(this.node.type)?.label} node`);
|
||||
graph.nodes.push(this.node);
|
||||
}
|
||||
|
||||
undo(project: Project): void {
|
||||
@@ -153,8 +160,9 @@ export class ConnectCommand implements Command {
|
||||
const toNode = findNode(graph, this.edge.to.node);
|
||||
if (!fromNode || !toNode) throw new CommandError('Node not found');
|
||||
if (fromNode === toNode) throw new CommandError('A node cannot connect to itself');
|
||||
const out = requireNodeDef(fromNode.type).outputs.find((p) => p.key === this.edge.from.port);
|
||||
const inp = requireNodeDef(toNode.type).inputs.find((p) => p.key === this.edge.to.port);
|
||||
const lookup = behaviorLookup(project);
|
||||
const out = resolveDef(fromNode, lookup)?.outputs.find((p) => p.key === this.edge.from.port);
|
||||
const inp = resolveDef(toNode, lookup)?.inputs.find((p) => p.key === this.edge.to.port);
|
||||
if (!out || !inp) throw new CommandError('Port not found');
|
||||
if (!portsCompatible(out.type, inp.type))
|
||||
throw new CommandError(`Cannot connect ${out.type} to ${inp.type}`);
|
||||
|
||||
Reference in new issue
Block a user