Replace the five variable nodes with one Variables node
A single node now lists any number of variables. Every entry is an output that reads the current value whenever it is pulled; an entry switched to Set is also an input and is written when the flow arrives. Exec pins only exist while at least one entry writes, and all inputs are read before any write, so one node can swap two variables. Get/Set variable, Get/Set variables and Variable are gone. Old project files are converted on load: each old node becomes a Variables node with the same variables, edges and typed values. A written port without a typed value now shows the zero of its kind on the node body, matching what the inspector shows and what the run writes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
854ae2e756
commit
7c63c884ef
40 files changed
+895
-590
No files matched your search
@@ -83,11 +83,7 @@ import { Branch } from './flow/Branch';
|
||||
import { Repeat } from './flow/Repeat';
|
||||
import { RunBehavior } from './flow/RunBehavior';
|
||||
import { Sequence } from './flow/Sequence';
|
||||
import { GetVariable } from './variable/GetVariable';
|
||||
import { GetVariables } from './variable/GetVariables';
|
||||
import { SetVariable } from './variable/SetVariable';
|
||||
import { SetVariables } from './variable/SetVariables';
|
||||
import { Variable } from './variable/Variable';
|
||||
import { Variables } from './variable/Variables';
|
||||
|
||||
/**
|
||||
* Importing this module registers the built-in nodes. The palette sorts by category and label, so
|
||||
@@ -113,11 +109,7 @@ export const BUILT_IN_NODES: readonly NodeDef[] = [
|
||||
RunBehavior,
|
||||
BehaviorEnd,
|
||||
// variables
|
||||
Variable,
|
||||
GetVariable,
|
||||
GetVariables,
|
||||
SetVariable,
|
||||
SetVariables,
|
||||
Variables,
|
||||
// data: references and properties
|
||||
Self,
|
||||
Owner,
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { type PropertyBag, type PropertyValue, textOf } from '../../model/Properties';
|
||||
import type { PropertyValue } from '../../model/Properties';
|
||||
import { OBJECT_PROPERTY_SUGGESTIONS, type PropertyDef } from '../../properties/PropertySchema';
|
||||
import type { NodeContext } from '../NodeContext';
|
||||
import type { DerivedShape, NodeDef, PortDef, PortSource, RunResult } from '../NodeRegistry';
|
||||
import { asBoolean, asNumber, type Value, writeTyped } from '../values';
|
||||
import type { NodeDef, PortDef } from '../NodeRegistry';
|
||||
import { asBoolean, asNumber } from '../values';
|
||||
|
||||
/** The property a Get/Set property node names: typed freely, or picked from the built-in ones. */
|
||||
export const PROPERTY_PARAM: PropertyDef = {
|
||||
@@ -34,40 +33,6 @@ export const collisionOutputs = (): PortDef[] => [
|
||||
|
||||
export const keyParam: PropertyDef = { key: 'key', label: 'Key', kind: 'key', default: 'ArrowRight' };
|
||||
|
||||
/** Options are empty on purpose: the graph inspector fills in the behavior's variables. */
|
||||
export const variableParam: PropertyDef = {
|
||||
key: 'variable',
|
||||
label: 'Variable',
|
||||
kind: 'enum',
|
||||
default: '',
|
||||
options: [],
|
||||
};
|
||||
|
||||
const variableKey = (params: PropertyBag) => textOf(params.variable ?? '');
|
||||
|
||||
export const readVariable = (ctx: NodeContext, params: PropertyBag): Record<string, Value> => ({
|
||||
value: ctx.variables[variableKey(params)] ?? null,
|
||||
});
|
||||
|
||||
export function writeVariable(
|
||||
ctx: NodeContext,
|
||||
inputs: Record<string, Value>,
|
||||
params: PropertyBag,
|
||||
): RunResult {
|
||||
const key = variableKey(params);
|
||||
if (key) writeTyped(ctx.variables, key, inputs.value);
|
||||
return { next: 'exec' };
|
||||
}
|
||||
|
||||
/** Once a variable is picked, the title names it: "Get speed", or just "speed". */
|
||||
export const variableTitle =
|
||||
(verb: string) =>
|
||||
({ node }: PortSource): DerivedShape => {
|
||||
const name = variableKey(node.params);
|
||||
if (!name) return {};
|
||||
return { label: verb ? `${verb} ${name}` : name };
|
||||
};
|
||||
|
||||
/** No `assetTypes`: any placeable asset is a valid answer here. */
|
||||
export const assetParam: PropertyDef = { key: 'asset', label: 'Asset', kind: 'asset', default: '' };
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import type { NodeDef } from '../../NodeRegistry';
|
||||
import { any, readVariable, variableParam, variableTitle } from '../shared';
|
||||
|
||||
export const GetVariable: NodeDef = {
|
||||
type: 'GetVariable',
|
||||
label: 'Get variable',
|
||||
category: 'variable',
|
||||
description: "Reads one of this behavior's variables.",
|
||||
inputs: [],
|
||||
outputs: [any('value')],
|
||||
params: [variableParam],
|
||||
derivePorts: variableTitle('Get'),
|
||||
run: (ctx, _inputs, params) => ({ outputs: readVariable(ctx, params) }),
|
||||
};
|
||||
@@ -1,24 +0,0 @@
|
||||
import type { NodeDef } from '../../NodeRegistry';
|
||||
import { GET_VARIABLES } from '../../nested';
|
||||
import type { Value } from '../../values';
|
||||
|
||||
/**
|
||||
* The plural of Get variable: one output per variable picked, so a graph reading a handful of them
|
||||
* is one node instead of a column of five. The ports are declared on the node, the way Expand
|
||||
* declares the fields of a struct.
|
||||
*/
|
||||
export const GetVariables: NodeDef = {
|
||||
type: GET_VARIABLES,
|
||||
label: 'Get variables',
|
||||
category: 'variable',
|
||||
description: "Reads several of this behavior's variables at once, one output per variable.",
|
||||
inputs: [],
|
||||
outputs: [],
|
||||
params: [],
|
||||
declaresPorts: 'outputs',
|
||||
run: (ctx, _inputs, _params, def) => {
|
||||
const outputs: Record<string, Value> = {};
|
||||
for (const port of def.outputs) outputs[port.key] = ctx.variables[port.key] ?? null;
|
||||
return { outputs };
|
||||
},
|
||||
};
|
||||
@@ -1,14 +0,0 @@
|
||||
import type { NodeDef } from '../../NodeRegistry';
|
||||
import { any, exec, variableParam, variableTitle, writeVariable } from '../shared';
|
||||
|
||||
export const SetVariable: NodeDef = {
|
||||
type: 'SetVariable',
|
||||
label: 'Set variable',
|
||||
category: 'variable',
|
||||
description: "Writes one of this behavior's variables.",
|
||||
inputs: [exec(), any('value')],
|
||||
outputs: [exec()],
|
||||
params: [variableParam],
|
||||
derivePorts: variableTitle('Set'),
|
||||
run: writeVariable,
|
||||
};
|
||||
@@ -1,25 +0,0 @@
|
||||
import type { NodeDef } from '../../NodeRegistry';
|
||||
import { SET_VARIABLES } from '../../nested';
|
||||
import { writeTyped } from '../../values';
|
||||
import { exec } from '../shared';
|
||||
|
||||
/**
|
||||
* The plural of Set variable: one input per variable picked. An input left unconnected writes the
|
||||
* value typed on the node, exactly as it does on the singular node – the node writes every variable
|
||||
* it lists, every time it runs.
|
||||
*/
|
||||
export const SetVariables: NodeDef = {
|
||||
type: SET_VARIABLES,
|
||||
label: 'Set variables',
|
||||
category: 'variable',
|
||||
description: "Writes several of this behavior's variables at once, one input per variable.",
|
||||
inputs: [exec()],
|
||||
outputs: [exec()],
|
||||
params: [],
|
||||
declaresPorts: 'inputs',
|
||||
run: (ctx, inputs, _params, def) => {
|
||||
for (const port of def.inputs)
|
||||
if (port.type !== 'exec') writeTyped(ctx.variables, port.key, inputs[port.key]);
|
||||
return { next: 'exec' };
|
||||
},
|
||||
};
|
||||
@@ -1,18 +0,0 @@
|
||||
import type { NodeDef } from '../../NodeRegistry';
|
||||
import { any, exec, readVariable, variableParam, variableTitle, writeVariable } from '../shared';
|
||||
|
||||
/** Get and Set in one node: the output can be read at any time, an arriving flow writes the input. */
|
||||
export const Variable: NodeDef = {
|
||||
type: 'Variable',
|
||||
label: 'Variable',
|
||||
category: 'variable',
|
||||
description:
|
||||
"Reads one of this behavior's variables at any time, and writes it when the flow arrives. " +
|
||||
'A read after the write sees the new value.',
|
||||
inputs: [exec(), any('value')],
|
||||
outputs: [exec(), any('value')],
|
||||
params: [variableParam],
|
||||
derivePorts: variableTitle(''),
|
||||
read: readVariable,
|
||||
run: writeVariable,
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
import { portDefOf, VARIABLES } from '../../nested';
|
||||
import { type NodeDef, withInlineDefault } from '../../NodeRegistry';
|
||||
import { writeTyped } from '../../values';
|
||||
import { exec } from '../shared';
|
||||
|
||||
/**
|
||||
* Every variable picked is an output, read at the moment it is asked for. One switched to Set is an
|
||||
* input too; the flow pins exist only while at least one is.
|
||||
*/
|
||||
export const Variables: NodeDef = {
|
||||
type: VARIABLES,
|
||||
label: 'Variables',
|
||||
category: 'variable',
|
||||
description:
|
||||
"Reads this behavior's variables at any time. Those switched to Set are written together when " +
|
||||
'the flow arrives, and a read after that sees the new value.',
|
||||
inputs: [],
|
||||
outputs: [],
|
||||
params: [],
|
||||
declaresPorts: 'both',
|
||||
declaredPort: (port) => withInlineDefault(portDefOf(port)),
|
||||
derivePorts: ({ node }) => {
|
||||
const ports = node.ports ?? [];
|
||||
const only = ports.length === 1 ? ports[0]?.key : undefined;
|
||||
const shape = ports.some((p) => p.write) ? { inputs: [exec()], outputs: [exec()] } : {};
|
||||
return only ? { ...shape, label: only } : shape;
|
||||
},
|
||||
read: (ctx) => ctx.variables,
|
||||
run: (ctx, inputs, _params, def) => {
|
||||
for (const port of def.inputs)
|
||||
if (port.type !== 'exec') writeTyped(ctx.variables, port.key, inputs[port.key]);
|
||||
return { next: 'exec' };
|
||||
},
|
||||
};
|
||||
Reference in new issue
Block a user