Typed lists: struct type for list items, Add/Set item with Type (#254)

admin committed 2026-09-28 07:23:12 +02:00
1 parent 2a1ce85981
commit d4f6ca3e98
3 files changed
+70 -14

No files matched your search

+5 -2
@@ -156,10 +156,13 @@ playing use the same two words.
`Select` (`true` if the condition holds, else `false` — the same action with one of two values,
such as a walk or an idle animation for one `Play animation`; `Branch` is for two different actions),
lists `List` (a constant), `Fill` (one value repeated — how an empty board is made), `Count`,
`Item at`, `Set item` (a *new* list, the one that came in is left alone), `Add item` (a new
`Item at`, `Set item` (a *new* list, the one that came in is left alone; with a **Type** it
sets only a struct with exactly that struct asset's fields, like `Add item`), `Add item` (a new
list with `value` as exactly one more item: a struct or a list goes in whole, where `Join lists`
would take it apart; at the end, or before `index`, and an index outside the list appends;
the item takes the kind the list's items share, like `Set item`), `Sub list`,
the item takes the kind the list's items share, like `Set item`; with a **Type** it adds only a
struct with exactly that struct asset's fields and reports anything else in the console, see
[Structs](Structs)), `Sub list`,
`Join lists`, `Index of` (by identity where the items are objects), structs `Pack`/`Expand`,
`Length`, `Distance`, `Normalize`, `Axis input` (four keys → a
normalized direction, so diagonals are not faster), `Compare` (two numbers count as equal up to
+1 -1
@@ -15,7 +15,7 @@ npm run build # writes dist/ and regenerates index.html
php tools/make-user.php <name> <password> [admin|user] # an account; every tool signs in (see Accounts)
php tests/php/accounts.test.php # the API's account rules, no server needed (CI's gate job)
php tests/php/snapshots.test.php # the snapshot store (#231), the same way
node tools/seed-e2e.mjs && node tools/e2e.mjs # 1500 editor checks, screenshots in /tmp/gm-e2e-*.png
node tools/seed-e2e.mjs && node tools/e2e.mjs # 1513 editor checks, screenshots in /tmp/gm-e2e-*.png
GM_STEPS=20 node tools/e2e.mjs # or 1-12, or 6,20 — a subset while working on one step
./e2e.sh start [GM_STEPS] # the same run detached, see below
./e2e.sh status # live progress, or the last run's result
+64 -11
@@ -61,23 +61,63 @@ takes all of it back:
This also works through several levels: if `Chest` holds a `Loot` and a variable holds a `Chest`, a
field renamed on `Loot` is renamed inside that variable's value too.
## Typed lists
A list says nothing about its items by default: each item carries its own kind, and a struct item
its own fields. Such a list is **mixed**. A list can also be **typed**: every item is then one
struct asset.
Every list variable, and every list field inside a struct, has an **Items** row under its head. It
reads **Mixed** until you pick a type. Its menu offers:
- **Mixed** — the list declares nothing again. Its items keep their type each (see below), so
nothing is lost.
- **every struct asset** — every item takes on that asset's fields, keeping what it holds under the
same name and taking the default for the rest, exactly like a struct given a type. A list that
holds **anything but structs** (a number, a text, a list) cannot take a type: the edit is refused
and the console says how many items are in the way. Placed objects count too — an override
holding a number refuses the type as well.
- **Make struct type** — makes a new struct asset out of the list's items and types the list with
it. The asset gets **every field any item has**, at the value of the first item that has it, so
no item loses a field.
In a typed list:
- **+ Item** adds an item of the type at its defaults straight away. There is no kind to pick.
- The items' fields are the type's: their names cannot be changed, and there is no **+ Field** and
no remove button on a field. The **values** are edited as before.
- A list field of the type is typed through it: if `Bag` has a list `inside` of `Loot`, the `inside`
of every `Bag` item is a typed list of `Loot` too.
### A type for one item
In a **mixed** list, every struct item has a **Type** row of its own, as does a struct field inside
such an item. Pick a struct asset there, and that one item takes on its fields and follows it, while
the other items stay as they are. **Own fields** turns it back.
### When the asset changes
Typed items follow their asset like every other place of that type — the table above applies to
them too: a field added, removed or renamed reaches every typed item, in the list's own value, in
the overrides of placed objects and in lists nested inside other items, in one undo step.
## Deleting a struct asset
The delete dialog counts the structs and `Pack`/`Expand` nodes that use it. After deleting, each of
them **keeps the fields it last had**, now as its own fields — no value is lost, only the link. Its
Type row reads **Own fields** again.
The delete dialog counts the structs, the typed lists and the `Pack`/`Expand` nodes that use it.
After deleting, each of them **keeps the fields it last had**, now as its own fields — no value is
lost, only the link. A Type row reads **Own fields** again, an Items row **Mixed**.
## In the running game
Nothing changes there. A struct value is the same plain bundle of fields, with or without a type:
the type does not travel with the value, so `Equals`, `Index of`, saving and loading treat it as
before. Edges do not check the type either — any struct fits any struct port.
A struct value is the same plain bundle of fields, with or without a type: the type does not travel
with the value, so `Equals`, `Index of`, saving and loading treat it as before. Edges do not check
the type either — any struct fits any struct port.
## What it does not do (yet)
Lists stay **untyped**: a list of structs still takes each item's shape from the item itself, and
**+ Item → Struct** starts an empty struct. Typed lists, where every item is one struct type, are
[issue #254](https://git.nockal.com/meleki/game-maker/issues/254).
The places that check are **Add item** and **Set item**, each with its **Type** setting. With a
type they take a value only if it is a struct with **exactly** the type's field names — none
missing, none more. Anything else is not added or set: the list comes out as it went in, and the
console names the value and the type. An item taken out of a typed list, and a `Pack` with the same
type, always fit. Without a type (or with one that was deleted) both take anything, as before.
## Under the hood
@@ -89,3 +129,16 @@ project is loaded, so a hand-edited file heals too. Its writes go through a `Rec
(`src/core/util/recorder.ts`), which is what makes an edit of unknown reach one undo step. A struct
asset declares its fields as `variables`, so the variable commands and the inspector's variable
rows edit them unchanged.
A list declares nothing about its items, so an item carries its type itself: a typed list names the
asset in `itemType`, and every struct item in it holds the asset's id under the key `$struct`. A
struct item with a type of its own in a mixed list holds the same key. No field can have that name,
since field names never start with `$`. The same sync brings every marked item into shape, wherever
it sits. A run never sees the key: it is taken out where values enter the game
(`withoutItemTypes` in `src/core/model/Properties.ts`), which is why `Add item` and `Set item` can
compare field names exactly.
A typed item keeps a field's value as plain JSON, the way a declared struct does: an asset id or a
texture id is a text, a color a number. What kind a field is comes from the type, so the item rows
draw each field as the type declares it — an asset field with its asset menu, not as the text it
holds.