Let a variable hold a list
A variable could only be a number, a text or a yes/no, so every set of values had to be smuggled through a string - which is where Tetris got its unreadable "03066326048763714" colours from. A list is now a value of its own, with an item kind (number, text, yes/no, colour) that decides how the inspector reads the one comma-separated line it is typed as, and eight nodes to work with: List, Fill, Count, Item at, Set item, Sub list, Join lists, Index of. Two traps came with it. An array is typeof 'object', so every place that told a value from an entity had to learn the difference, or a list would have been passed around as a game object. And a value was compared by identity in three places - with lists that marks everything as an overridden property after each reload, so those comparisons now compare contents. There is no list of objects: a list is written to the project file and an object is a live handle that no reload survives. A set of objects stays a list of names. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c6f907b4e0
commit
a9c3111377
25 files changed
+469
-25
No files matched your search
@@ -22,6 +22,8 @@ import {
|
||||
coerceValue,
|
||||
COMMON_PROPS,
|
||||
isCommonProp,
|
||||
LIST_ITEM_KINDS,
|
||||
type ListItemKind,
|
||||
type PropertyDef,
|
||||
type PropertySchema,
|
||||
schemaFor,
|
||||
@@ -150,8 +152,10 @@ function normalizeInstanceEdits(raw: unknown): Record<string, InstanceEdit> {
|
||||
const offset = asRecord(e.editorOffset);
|
||||
if (typeof offset.x === 'number' || typeof offset.y === 'number')
|
||||
edit.editorOffset = { x: toNumber(offset.x), y: toNumber(offset.y) };
|
||||
for (const [key, v] of Object.entries(asRecord(e.overrides)))
|
||||
if (typeof v === 'number' || typeof v === 'string' || typeof v === 'boolean') edit.overrides[key] = v;
|
||||
for (const [key, v] of Object.entries(asRecord(e.overrides))) {
|
||||
const value = asStoredValue(v);
|
||||
if (value !== undefined) edit.overrides[key] = value;
|
||||
}
|
||||
if (!isEmptyEdit(edit)) edits[path] = edit;
|
||||
}
|
||||
return edits;
|
||||
@@ -169,7 +173,10 @@ function normalizeVariables(raw: unknown): PropertyDef[] {
|
||||
// Common property keys are reserved – a variable of that name would shadow the switch.
|
||||
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key) || seen.has(key) || isCommonProp(key)) continue;
|
||||
seen.add(key);
|
||||
const def = variableDef(key, kind);
|
||||
const items = (LIST_ITEM_KINDS as readonly string[]).includes(String(v.items))
|
||||
? (v.items as ListItemKind)
|
||||
: undefined;
|
||||
const def = variableDef(key, kind, undefined, items);
|
||||
def.default = coerceValue(def, (v.default ?? def.default) as PropertyValue);
|
||||
defs.push(def);
|
||||
}
|
||||
@@ -287,6 +294,20 @@ function asRecord(value: unknown): Record<string, unknown> {
|
||||
return value && typeof value === 'object' ? (value as Record<string, unknown>) : {};
|
||||
}
|
||||
|
||||
/**
|
||||
* A value from an untyped slot (instance edits carry no schema to coerce against). Lists are kept
|
||||
* but flattened away one level, so nothing nested can enter the model behind the type's back.
|
||||
*/
|
||||
function asStoredValue(value: unknown): PropertyValue | undefined {
|
||||
if (typeof value === 'number' || typeof value === 'string' || typeof value === 'boolean') return value;
|
||||
if (Array.isArray(value))
|
||||
return value.filter(
|
||||
(item): item is number | string | boolean =>
|
||||
typeof item === 'number' || typeof item === 'string' || typeof item === 'boolean',
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function toNumber(value: unknown): number {
|
||||
const n = Number(value);
|
||||
return Number.isFinite(n) ? n : 0;
|
||||
|
||||
Reference in new issue
Block a user