Let a list hold structs, and read or write variables in bundles
A list item may now be a struct. The depth is bounded in the declaration
rather than by a comment: FieldKind (number|text|boolean|color) says what a
struct field holds, ListItemKind adds struct on top and says what a list
holds, so list -> struct -> primitive is the deepest storable shape and
sameValue stays finite. A struct item renders inline in the List tab, which
is why it needs no path in the slot address.
Four paths silently assumed a list item was a primitive: sameValue compared
items with ===, textOf would have printed [object Object], asListItem turned
a struct into text on Set item and Fill, and Index of used indexOf, which is
reference equality and could never have found a struct.
Tetris now runs on bundles. shapes is 112 {x, y} structs instead of 224 loose
numbers, piece and the candidate are structs, so Next move hands back a whole
candidate and each strand packs only the field it changes, since mergeStruct
leaves the rest standing. Declared behavior ports drop 49 -> 35, the master's
variables 18 -> 13, its graph 84/94 -> 69/76 nodes/edges.
Get variables and Set variables declare their ports from a list of variable
names, the way Pack and Expand already do, so one node can read or write
several at once. Renaming a variable rewires them, because there the port key
is the variable name.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9d62dfea7b
commit
f60358c951
32 files changed
+1083
-253
No files matched your search
@@ -4,7 +4,16 @@
|
||||
*/
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { BUILT_IN_NODES } from '../../src/core/graph/nodes';
|
||||
import { type NodeContext, type RunResult, requireNodeDef, type Value } from '../../src/core/graph';
|
||||
import {
|
||||
createEmptyGraph,
|
||||
type DeclaredPort,
|
||||
NO_BEHAVIORS,
|
||||
type NodeContext,
|
||||
requireNodeDef,
|
||||
resolveDef,
|
||||
type RunResult,
|
||||
type Value,
|
||||
} from '../../src/core/graph';
|
||||
import type { PropertyBag } from '../../src/core/model/Properties';
|
||||
import { fakeContext, type FakeContext, fakeEntity, fakeHandle, NO_POINTER, runDef } from '../helpers';
|
||||
|
||||
@@ -14,6 +23,8 @@ interface Case {
|
||||
inputs?: Record<string, Value>;
|
||||
params?: PropertyBag;
|
||||
context?: Partial<NodeContext>;
|
||||
/** Ports the node declares for itself – for the types whose ports are variables it names. */
|
||||
ports?: DeclaredPort[];
|
||||
/** Expected data outputs, compared exactly. */
|
||||
outputs?: Record<string, Value>;
|
||||
/** Exec outputs the node would have grown in a graph – only for types whose ports are derived. */
|
||||
@@ -30,7 +41,8 @@ interface Case {
|
||||
|
||||
const run = (type: string, c: Case): { result: RunResult; ctx: FakeContext } => {
|
||||
const ctx = fakeContext(c.context);
|
||||
const registered = requireNodeDef(type);
|
||||
const node = { id: 'n', type, x: 0, y: 0, params: c.params ?? {}, ports: c.ports };
|
||||
const registered = c.ports ? resolveDef(node, createEmptyGraph(), NO_BEHAVIORS)! : requireNodeDef(type);
|
||||
const grown = (c.derivedOutputs ?? []).map((key) => ({ key, label: key, type: 'exec' as const }));
|
||||
const def = grown.length > 0 ? { ...registered, outputs: [...registered.outputs, ...grown] } : registered;
|
||||
return { result: def.run!(ctx, c.inputs ?? {}, c.params ?? {}, def), ctx };
|
||||
@@ -98,6 +110,31 @@ const CASES: Record<string, Case[]> = {
|
||||
{ name: 'an unknown variable reads as null', params: { variable: 'nope' }, outputs: { value: null } },
|
||||
{ name: 'no variable chosen reads as null', outputs: { value: null } },
|
||||
],
|
||||
GetVariables: [
|
||||
{
|
||||
name: 'reads one output per variable it names',
|
||||
ports: [
|
||||
{ key: 'speed', kind: 'number' },
|
||||
{ key: 'nope', kind: 'number' },
|
||||
],
|
||||
outputs: { speed: 100, nope: null },
|
||||
},
|
||||
{ name: 'without a variable it reads nothing', outputs: {} },
|
||||
],
|
||||
SetVariables: [
|
||||
{
|
||||
name: 'writes every variable it names, each keeping its declared type',
|
||||
ports: [{ key: 'speed', kind: 'number' }],
|
||||
inputs: { speed: '250' },
|
||||
next: 'exec',
|
||||
after: (ctx) => expect(ctx.variables.speed).toBe(250),
|
||||
},
|
||||
{
|
||||
name: 'without a variable it writes nothing',
|
||||
next: 'exec',
|
||||
after: (ctx) => expect(ctx.variables).toEqual({ speed: 100 }),
|
||||
},
|
||||
],
|
||||
SetVariable: [
|
||||
{
|
||||
name: 'writes a variable, keeping the declared type',
|
||||
|
||||
Reference in new issue
Block a user