Let a Start input or a variable name the state machine it carries
A sub-behavior that gets its machine through a Start input had no state ports on Is in state, Set state and the other state nodes. Their ports came only from an inline pick or a State machine literal, so a machine handed in from outside left them empty. - A statemachine port of a Start node can now name a machine (DeclaredPort.machine). The inspector shows the pick next to the port name. SetPortMachineCommand sets it with undo. - machineIdOf follows an edge from such a port to that machine, so the state nodes it feeds offer its states. - A Run behavior node has the machine picked in advance on that input. - A different pick there is allowed, but the node warns, because the called graph asks for the states of its own machine. - A statemachine variable of a Variables node can name a machine the same way, picked next to the variable's row. - The inspector signature of a node now includes the picked machine. Before, the pick changed the ports but the dropdown kept its text until the node was selected again. - A local machine handed in still starts in no state every call. That rule from #229 is unchanged. Tests: - tests/core/start-machine.test.ts (7): ports with and without a machine and undo, save and load, the preset value moving a shared machine through the call, the warning, a port of another kind, the inspector signature, a Variables node. - Counter checks: without the new branch in machineIdOf 2 tests fail, without the machine in the signature 1 test fails. - Gate on melektaus-atx: 239 files, 3599 tests, CHECK EXIT 0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3f12a77aea
commit
d80d5cce26
8 files changed
+284
-10
No files matched your search
@@ -25,7 +25,7 @@ import {
|
||||
} from '../../core/graph';
|
||||
import type { GraphAsset } from '../../core/model/Asset';
|
||||
import { graphLookup } from '../../core/model/graphLookup';
|
||||
import { findGraphAsset, type Project } from '../../core/model/Project';
|
||||
import { findAssetOf, findGraphAsset, type Project } from '../../core/model/Project';
|
||||
import { type PropertyValue, textOf } from '../../core/model/Properties';
|
||||
import {
|
||||
isListNode,
|
||||
@@ -62,7 +62,14 @@ export function nodeSignature(project: Project, assetId: string, nodeId: string)
|
||||
.filter((p) => edgesInto(asset.graph, node.id, p.key).length > 0)
|
||||
.map((p) => p.key)
|
||||
: [];
|
||||
const declared = (node.ports ?? []).map((p) => `${p.key}:${p.kind}${p.write ? ':set' : ''}`).join(',');
|
||||
// The machine a port carries is drawn as the picker's text, its name included.
|
||||
const machineOf = (id: string) => findAssetOf(project, id, 'statemachine')?.name ?? '';
|
||||
const declared = (node.ports ?? [])
|
||||
.map(
|
||||
(p) =>
|
||||
`${p.key}:${p.kind}${p.write ? ':set' : ''}${p.machine ? `:${p.machine}:${machineOf(p.machine)}` : ''}`,
|
||||
)
|
||||
.join(',');
|
||||
// They decide which rows the inspector builds, and two behaviors may share a name in the title.
|
||||
const target = `${textOf(node.params[OBJECT_TYPE_PARAM] ?? '')}:${textOf(node.params[OBJECT_BEHAVIOR_PARAM] ?? '')}`;
|
||||
// The transition rows and the picked name are drawn, not filled in by `refreshNodeSection`, so
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
RemovePortCommand,
|
||||
RenamePortCommand,
|
||||
RepointPortCommand,
|
||||
SetPortMachineCommand,
|
||||
SetPortWriteCommand,
|
||||
} from '../../core/commands/portCommands';
|
||||
import {
|
||||
@@ -32,6 +33,7 @@ import {
|
||||
import { isPrivateVariable } from '../../core/model/Asset';
|
||||
import { findAssetOf, findGraphAsset, type Project } from '../../core/model/Project';
|
||||
import { textOf } from '../../core/model/Properties';
|
||||
import { assetRefChoices, NO_ASSET_LABEL } from '../../core/properties/assetRef';
|
||||
import { DECLARED_PORT_KINDS, VARIABLE_KINDS } from '../../core/properties/kinds';
|
||||
import { allShaderParamRows, type PropertyDef, schemaForAsset } from '../../core/properties/PropertySchema';
|
||||
import {
|
||||
@@ -187,7 +189,12 @@ function buildPickedPortSection(
|
||||
else if (ports.length === 0) addNote(host, `No ${plural} picked yet. Each one adds a port.`);
|
||||
|
||||
for (const port of ports) {
|
||||
const toggle = writeToggle(host, assetId, node, port);
|
||||
// A machine a variable holds is a running value; which one its states come from is picked here.
|
||||
const carries = noun === 'Variable' && port.kind === 'statemachine';
|
||||
const toggle = [
|
||||
...(carries ? [machinePick(host, assetId, node, port)] : []),
|
||||
...writeToggle(host, assetId, node, port),
|
||||
];
|
||||
if (custom) {
|
||||
portRow(host, assetId, node, port, portNameField(host, assetId, node, port), toggle);
|
||||
continue;
|
||||
@@ -266,7 +273,14 @@ export function buildPortSection(
|
||||
);
|
||||
|
||||
const blocks = ports.map((port) =>
|
||||
portRow(host, assetId, node, port, portNameField(host, assetId, node, port)),
|
||||
portRow(
|
||||
host,
|
||||
assetId,
|
||||
node,
|
||||
port,
|
||||
portNameField(host, assetId, node, port),
|
||||
onStart && port.kind === 'statemachine' ? [machinePick(host, assetId, node, port)] : [],
|
||||
),
|
||||
);
|
||||
sortBlocks(blocks, (from, to) => host.state.execute(new MovePortCommand(assetId, node.id, from, to)));
|
||||
}
|
||||
@@ -280,6 +294,32 @@ function portNameField(host: InspectorHost, assetId: string, node: GraphNode, po
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Which machine a start node's input or a Variables node's variable carries: what the state nodes
|
||||
* behind it offer, and for a start node what a node running the behavior has picked before anyone
|
||||
* picks there.
|
||||
*/
|
||||
function machinePick(host: InspectorHost, assetId: string, node: GraphNode, port: DeclaredPort): Dropdown {
|
||||
const project = host.state.project;
|
||||
const machine = port.machine ? findAssetOf(project, port.machine, 'statemachine') : undefined;
|
||||
const gone = port.machine !== undefined && machine === undefined;
|
||||
const pick = new Dropdown(machine?.name ?? (gone ? 'Gone' : NO_ASSET_LABEL), {
|
||||
variant: gone ? 'danger' : 'default',
|
||||
});
|
||||
const set = (id: string) => host.state.execute(new SetPortMachineCommand(assetId, node.id, port.key, id));
|
||||
pick.onClick = () => {
|
||||
const items: MenuItem[] = [
|
||||
{ label: NO_ASSET_LABEL, onSelect: () => set('') },
|
||||
...assetRefChoices(project, ['statemachine']).map((a) => ({
|
||||
label: a.name,
|
||||
onSelect: () => set(a.id),
|
||||
})),
|
||||
];
|
||||
new Menu(items).openBelow(pick);
|
||||
};
|
||||
return pick;
|
||||
}
|
||||
|
||||
function writeToggle(host: InspectorHost, assetId: string, node: GraphNode, port: DeclaredPort): Widget[] {
|
||||
const box = new Checkbox(port.write === true);
|
||||
box.onChange = (on) => host.state.execute(new SetPortWriteCommand(assetId, node.id, port.key, on));
|
||||
|
||||
Reference in new issue
Block a user