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
@@ -1,10 +1,12 @@
|
||||
/**
|
||||
* A list is deliberately flat – a list of lists would need a nested editor and a deep compare on
|
||||
* every override, and nothing so far wants one: a board is addressed as `row · width + column`.
|
||||
* A list holds primitives or structs. Depth stops there and it stops in the declaration, not by
|
||||
* convention: a list says what its items are (`ListItemKind`, struct included), a struct field
|
||||
* says what it holds (`FieldKind`, primitives only). So `list → struct → primitive` is the deepest
|
||||
* a stored value goes, which is what keeps the deep compare and the editor finite.
|
||||
*/
|
||||
export type ListValue = readonly (number | string | boolean)[];
|
||||
export type ListValue = readonly (number | string | boolean | StructValue)[];
|
||||
|
||||
/** What a struct field may hold: no field is itself a struct, for the same reason a list is flat. */
|
||||
/** What a struct field may hold. A field is never itself a struct – see `ListValue`. */
|
||||
export type FieldValue = number | string | boolean | ListValue;
|
||||
|
||||
/** Several named values travelling as one, so a bundle needs one port instead of one per part. */
|
||||
@@ -30,7 +32,7 @@ export const isStruct = (value: unknown): value is StructValue =>
|
||||
*/
|
||||
export function textOf(value: PropertyValue | undefined): string {
|
||||
if (value === undefined) return '';
|
||||
if (isList(value)) return value.join(', ');
|
||||
if (isList(value)) return value.map((item) => textOf(item)).join(', ');
|
||||
if (isStruct(value))
|
||||
return Object.entries(value)
|
||||
.map(([field, item]) => `${field}: ${textOf(item)}`)
|
||||
@@ -42,12 +44,11 @@ export function textOf(value: PropertyValue | undefined): string {
|
||||
export function sameValue(a: PropertyValue | undefined, b: PropertyValue | undefined): boolean {
|
||||
if (isList(a) || isList(b)) {
|
||||
if (!isList(a) || !isList(b) || a.length !== b.length) return false;
|
||||
return a.every((item, i) => item === b[i]);
|
||||
return a.every((item, i) => sameValue(item, b[i]));
|
||||
}
|
||||
if (isStruct(a) || isStruct(b)) {
|
||||
if (!isStruct(a) || !isStruct(b)) return false;
|
||||
const keys = Object.keys(a);
|
||||
// Fields are never structs, so this recurses exactly one level deep.
|
||||
return keys.length === Object.keys(b).length && keys.every((key) => sameValue(a[key], b[key]));
|
||||
}
|
||||
return a === b;
|
||||
|
||||
Reference in new issue
Block a user