State machines: asset, tab, State branch and Set state nodes (#51)
A state machine is an asset whose graph holds one `State` node per state (no ports, name as title), edited in a new "State machine" tab that reuses the graph canvas: right-click places "State n", snap toggle shared with the Behavior tab via SnapToggle. `statemachine` is a value kind like texture (port, literal node, variable, struct field, tagged list item). Behaviors use it through `State branch` (one exec output per state) and `Set state` (one exec input per state; the interpreter now hands `run` the entry port). States live per behavior instance in NodeContext.states, with no initial state. Ports derive from the inline picker or a wired `State machine` literal; BehaviorLookup became GraphLookup with behavior(id) and machine(id). The Graph tab is labelled "Behavior" (key unchanged). Unit tests (state-machine, kinds, catalog, ports), e2e step 26 and README added. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
ce42d16657
commit
e7d82bd1a9
60 files changed
+1269
-213
No files matched your search
@@ -2,9 +2,9 @@ import { describe, expect, it } from 'vitest';
|
||||
import { CollapseNodesCommand } from '../../src/core/commands/graphCommands';
|
||||
import { RemovePortCommand, SetPortWriteCommand } from '../../src/core/commands/portCommands';
|
||||
import { AddVariableCommand, RenameVariableCommand } from '../../src/core/commands/variableCommands';
|
||||
import { type GraphNode, GraphInterpreter, inlineValue, resolveDef } from '../../src/core/graph';
|
||||
import { type GraphNode, GraphInterpreter, inlineValue, NO_LOOKUP, resolveDef } from '../../src/core/graph';
|
||||
import type { BehaviorAsset } from '../../src/core/model/Asset';
|
||||
import { behaviorLookup } from '../../src/core/model/behaviorPrograms';
|
||||
import { graphLookup } from '../../src/core/model/graphLookup';
|
||||
import { fakeContext, graphEditor, makeBehavior, makeProject } from '../helpers';
|
||||
|
||||
function setup(variables = ['count', 'copy']) {
|
||||
@@ -19,12 +19,12 @@ function run(
|
||||
variables: Record<string, number> = { count: 5, copy: 0 },
|
||||
) {
|
||||
const ctx = fakeContext({ variables: { ...variables } });
|
||||
new GraphInterpreter(behavior.graph, behaviorLookup(fixture.project)).fire('OnStart', ctx);
|
||||
new GraphInterpreter(behavior.graph, graphLookup(fixture.project)).fire('OnStart', ctx);
|
||||
return ctx.variables;
|
||||
}
|
||||
|
||||
const ports = (behavior: BehaviorAsset, node: GraphNode, side: 'inputs' | 'outputs') =>
|
||||
resolveDef(node, behavior.graph, () => null)![side].map((p) => p.key);
|
||||
resolveDef(node, behavior.graph, NO_LOOKUP)![side].map((p) => p.key);
|
||||
|
||||
const edgeCount = (behavior: BehaviorAsset) => behavior.graph.edges.length;
|
||||
|
||||
@@ -126,7 +126,7 @@ describe('the Variables node', () => {
|
||||
const start = edit.addNode('OnStart');
|
||||
const node = edit.variables(['count', 'label', 'on', 'items'], { write: true, params: { label: 'hi' } });
|
||||
edit.connect(start, 'exec', node, 'exec');
|
||||
const carried = resolveDef(node, behavior.graph, () => null)!
|
||||
const carried = resolveDef(node, behavior.graph, NO_LOOKUP)!
|
||||
.inputs.filter((p) => p.type !== 'exec')
|
||||
.map((p) => [p.key, inlineValue(node, p)]);
|
||||
expect(Object.fromEntries(carried)).toEqual({ count: 0, label: 'hi', on: false, items: [] });
|
||||
@@ -237,7 +237,7 @@ describe('switching Set on a Variables node', () => {
|
||||
|
||||
describe('Variables node titles', () => {
|
||||
const titleOf = (behavior: BehaviorAsset, node: GraphNode) =>
|
||||
resolveDef(node, behavior.graph, () => null)!.label;
|
||||
resolveDef(node, behavior.graph, NO_LOOKUP)!.label;
|
||||
|
||||
it('follow a renamed variable once collapsed', () => {
|
||||
const { fixture, behavior, edit } = setup();
|
||||
|
||||
Reference in new issue
Block a user