Add the Structs page for struct assets (#253)

New page Structs with sidebar entry. Behaviors, Architecture and Development point to it. Check total 1500.
admin committed 2026-09-28 06:04:49 +02:00
1 parent 40027db357
commit 2a1ce85981
5 files changed
+98 -5

No files matched your search

+2 -2
@@ -73,9 +73,9 @@
```
web/index.html Vite source page (dev) index.html built page (generated, served)
src/core/model/ Project (object holders), Asset + AssetTypes, SceneObject, Properties (value types), hierarchy, walk (generic parent/child walks), instance (container content resolved from its template), usages (where an asset is used), container (the cycle guard), placement (what may be dropped where), texture (frames, the slice grid, a frame's collision outlines), frameOutline (#193: a frame's outlines as a collider wears them, centred, a pixel to a unit, cut into pieces once per drawn list), traceCollision (#161: a frame's outlines read off its picture — the pixels from an alpha threshold on are solid, each 4-connected island is walked round along the pixel edges and cut down to a few corners without ever crossing itself; crumbs below 1 % of the frame drop out), generated (pictures that are drawn rather than uploaded, #185: the key `gen:<assetId>@<revision>`, the register of readers that tell the picture path an asset's canvas size and revision, and the `fingerprint` pixel art and vector art make their revision with), vectorart (#129: the canvas as a property, which ring is worth drawing, and the revision as a fingerprint of the drawing rather than a counter, a texture fill's picture included) + vectorPath (what a shape draws, as plain commands: closed rings fill, every ring strokes, a segment is a line while neither of its ends carries a handle; a paint resolved to sorted stops or the picture it names) + vectorPaint (#186: gradients and texture fills — where a paint lies when it is switched on, the colour between two stops, adding, moving and removing a stop, a paint moved with its shape, and the rule that a texture fill takes uploaded pictures only) + vectorBoolean (#187: union, subtract, intersect and exclude on the sampled outlines, each shape read by the even-odd rule; the result rings in the order Pixi's hole detection needs, every outline followed by its holes and preceded by the islands inside them) + vectorCurves (#208: every sampled point remembers its cubic and its `t`, so a run of result points along one cubic becomes that cubic again, cut with de Casteljau), pixelart (#130: the palette a picture is painted in, how the layers of a frame come together, the sheet of all frames, a frame id read as a cut of it, and the revision) + pixelCel (the file of one layer on one frame, and its SHA-256 name worked out in the browser) + pixelDraw (what the tools do to the indices: brush, line, rectangle, ellipse, fill, resize about an anchor, merge, a palette colour taken out) + pixelEdit (every edit of layers, frames and cels as a function from one state to the next), polygon (an outline checked, cut into convex pieces and clipped to a rectangle), tile (the three kinds and their block shapes, reading a tile's pictures, the animation step), autotile (which quarter tile a neighbourhood asks for, the 47/16 tiles a block produces, 1 for a plain tile), tilemap (the cells of a layer, which neighbours a cell is joined to, the cells a stroke or a rectangle covers), tilemapCollision (the solid cells of a map, drawn in or out by their tile's collision scale and merged into rectangles, one group per one-way angle and per surface (`tileMaterials`, #193), the outlined cells as polygons — an autotile's outlines cut into its quarters and pieced together per cell like its picture —, where one sits in the world, and which cells of a placed map a world rectangle can reach, in cells (`cellsWithin`, #184)), tilemapMemo (what a map's cells work out to, kept while they hold the same: a version per cell map compared cell by cell, since strokes paint in place), animation (which frame at time t, a row's value there, what a row may aim at, the players on an object; KeyRef and its lookups) + animationPreview (the root at a frame, written into copies), curve (#247: a drawn easing evaluated at t — each segment a cubic solved for t by bisection, held to its rules by `sanitizedCurve`: ends at 0 and 1, no point overtaking another, no handle past its neighbour — and which keys and `Lerp` nodes use one) + curvePresets (the built-in eases traced as curves, a segment halved only where it strays), graphLookup (what a graph sees of the project: behaviors to run, state machines to read, curves to ease along), Scope (how long a shared value lives: per instance, one for the run, or one per scene and rebuilt with every scene that loads, #164 — for a state machine's current state and for the values of a Variables asset), collisionLayers (which colliders may meet: the project's named layers, a collider's layer and mask, and the one rule `layersMeet` the engine, Solids and the event pair search all answer by)
src/core/model/ Project (object holders), Asset + AssetTypes, SceneObject, Properties (value types), hierarchy, walk (generic parent/child walks), instance (container content resolved from its template), usages (where an asset is used), container (the cycle guard), placement (what may be dropped where), texture (frames, the slice grid, a frame's collision outlines), frameOutline (#193: a frame's outlines as a collider wears them, centred, a pixel to a unit, cut into pieces once per drawn list), traceCollision (#161: a frame's outlines read off its picture — the pixels from an alpha threshold on are solid, each 4-connected island is walked round along the pixel edges and cut down to a few corners without ever crossing itself; crumbs below 1 % of the frame drop out), generated (pictures that are drawn rather than uploaded, #185: the key `gen:<assetId>@<revision>`, the register of readers that tell the picture path an asset's canvas size and revision, and the `fingerprint` pixel art and vector art make their revision with), vectorart (#129: the canvas as a property, which ring is worth drawing, and the revision as a fingerprint of the drawing rather than a counter, a texture fill's picture included) + vectorPath (what a shape draws, as plain commands: closed rings fill, every ring strokes, a segment is a line while neither of its ends carries a handle; a paint resolved to sorted stops or the picture it names) + vectorPaint (#186: gradients and texture fills — where a paint lies when it is switched on, the colour between two stops, adding, moving and removing a stop, a paint moved with its shape, and the rule that a texture fill takes uploaded pictures only) + vectorBoolean (#187: union, subtract, intersect and exclude on the sampled outlines, each shape read by the even-odd rule; the result rings in the order Pixi's hole detection needs, every outline followed by its holes and preceded by the islands inside them) + vectorCurves (#208: every sampled point remembers its cubic and its `t`, so a run of result points along one cubic becomes that cubic again, cut with de Casteljau), pixelart (#130: the palette a picture is painted in, how the layers of a frame come together, the sheet of all frames, a frame id read as a cut of it, and the revision) + pixelCel (the file of one layer on one frame, and its SHA-256 name worked out in the browser) + pixelDraw (what the tools do to the indices: brush, line, rectangle, ellipse, fill, resize about an anchor, merge, a palette colour taken out) + pixelEdit (every edit of layers, frames and cels as a function from one state to the next), polygon (an outline checked, cut into convex pieces and clipped to a rectangle), tile (the three kinds and their block shapes, reading a tile's pictures, the animation step), autotile (which quarter tile a neighbourhood asks for, the 47/16 tiles a block produces, 1 for a plain tile), tilemap (the cells of a layer, which neighbours a cell is joined to, the cells a stroke or a rectangle covers), tilemapCollision (the solid cells of a map, drawn in or out by their tile's collision scale and merged into rectangles, one group per one-way angle and per surface (`tileMaterials`, #193), the outlined cells as polygons — an autotile's outlines cut into its quarters and pieced together per cell like its picture —, where one sits in the world, and which cells of a placed map a world rectangle can reach, in cells (`cellsWithin`, #184)), tilemapMemo (what a map's cells work out to, kept while they hold the same: a version per cell map compared cell by cell, since strokes paint in place), animation (which frame at time t, a row's value there, what a row may aim at, the players on an object; KeyRef and its lookups) + animationPreview (the root at a frame, written into copies), curve (#247: a drawn easing evaluated at t — each segment a cubic solved for t by bisection, held to its rules by `sanitizedCurve`: ends at 0 and 1, no point overtaking another, no handle past its neighbour — and which keys and `Lerp` nodes use one) + curvePresets (the built-in eases traced as curves, a segment halved only where it strays), graphLookup (what a graph sees of the project: behaviors to run, state machines to read, curves to ease along), structTypes (#253: Struct assets as types — every typed struct variable, struct field and `Pack`/`Expand` found and brought in line with its asset, a renamed field carried through every value first, the cycle rule), Scope (how long a shared value lives: per instance, one for the run, or one per scene and rebuilt with every scene that loads, #164 — for a state machine's current state and for the values of a Variables asset), collisionLayers (which colliders may meet: the project's named layers, a collider's layer and mask, and the one rule `layersMeet` the engine, Solids and the event pair search all answer by)
src/core/properties/ PropertySchema, kinds (one row per value kind), listItems (an item's kind, read off its value), resolve (the only override merge), deviation (what is gold), collider/camera/bounds geometry (with the side a one-way collider blocks from, and the rectangle around a shape a broad phase looks at first, #184), convex (capsules and polygons: rounded convex pieces against each other), ray (where a ray meets a shape, in double precision), rigidbody (what the engine reads off a rigidbody, a collider's material and the scene), aspect, enabled, editorRun, assetRef/textureRef (how a reference reads), declaredRows (the row a Property node declares in a shader or particle graph, and the params that node has in both)
src/core/util/ list (take out, put back and move an item — what a command's undo is made of), vec (dot, the closest point on a segment — shared by the collider maths, the outline hit area and the polygon grips), color (`mixColor`, channel by channel — keys of an animation, stops of a gradient, the particles' Mix colour)
src/core/util/ list (take out, put back and move an item — what a command's undo is made of), vec (dot, the closest point on a segment — shared by the collider maths, the outline hit area and the polygon grips), color (`mixColor`, channel by channel — keys of an animation, stops of a gradient, the particles' Mix colour), recorder (#253: assignments taken back last first — the undo of an edit whose reach is not known in advance)
src/core/commands/ undoable commands (pixelArtCommands: one command runs any pixel art edit and keeps the state before it, #130), CommandStack, SetBagValueCommand base, RenameCommand base (undone by renaming back: variable, struct field, port), MoveCommand base (undone by moving back: tile picture, tilemap layer, vector shape), vectorArtCommands (#129: the shapes of a drawing, one gesture one command), one import command shape for texture/font/sound, require.ts (validated lookups, and what an instance may not change), naming.ts
src/core/graph/ graph model, NodeRegistry/NodeContext/values (a travelling list may hold live objects, `storedList`
narrows one down to what a PropertyBag may hold, #157), interpreter, dropTarget, nested (behaviors as nodes),
+3 -2
@@ -230,8 +230,9 @@ playing use the same two words.
dropped on load, in a node setting or on the way through a graph. There is no depth limit either.
A struct inside a struct field is **declared** (`fields` on the field, like a struct variable),
while a list still declares nothing about its items, so a struct item in a list takes its shape
from its value at any depth. Named struct types that several variables share are a topic of their
own. `Pack`, `Expand` and `Item at` take nested data apart **one level at a time**: a struct in a
from its value at any depth. A struct may also follow a **struct asset** instead of declaring its
fields itself (#253): its Type row picks one, and `Pack`/`Expand` have a Type setting that makes
their ports the asset's fields — see [Structs](Structs). `Pack`, `Expand` and `Item at` take nested data apart **one level at a time**: a struct in a
struct is two `Expand` nodes in a row, and there is no path syntax like `a.b[2].c`. `+ Item`
offers every kind, on a list of its own and on one a struct field holds alike. A live entity is
an object on a data edge too, so it carries a `Symbol` brand (`ENTITY_HANDLE`) that no struct
+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 # 1486 editor checks, screenshots in /tmp/gm-e2e-*.png
node tools/seed-e2e.mjs && node tools/e2e.mjs # 1500 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
+91
@@ -0,0 +1,91 @@
A **struct asset** is a named shape for structs. Instead of typing the same fields into every
struct variable that holds, say, a piece of loot, you declare `Loot` once — `Title`, `Amount`,
`Rare` with their defaults — and let the variables, struct fields and `Pack`/`Expand` nodes that
hold loot **follow** it. Change `Loot`, and all of them change with it.
A struct that declares its own fields, as every struct did before, still works exactly as it did.
A type is something you give a struct, not something it needs.
## Making one
**+ → Struct** creates the asset; it stands in the assets panel under *Structs*. Its inspector has
a **FIELDS** section that works like the VARIABLES section of a behavior or a Variables asset:
- **+ Field** adds a field of any kind — a number, a text, a yes/no, a colour, a texture, an asset,
an object, a list or a struct.
- The name field renames it, the remove button at the end of the row removes it.
- The value next to it is the field's **default**: what a struct of this type starts with.
- **Open** shows the fields in a tab of their own, as it does for variables.
A field may itself be a struct with a type. What it may not be is its own type, directly or through
another type: `Loot` cannot hold a `Loot`, and if `Chest` holds a `Loot`, `Loot` cannot hold a
`Chest`. Such a value would never end. The type picker does not offer those types. A **list** field
is fine — it starts empty.
## Giving a struct a type
Every struct variable, and every struct field inside one, has a **Type** row under its head. It
reads **Own fields** while the struct declares its fields itself. Its menu offers:
- **Own fields** — the struct keeps the fields it has now and declares them itself again.
- **every struct asset** — the struct takes on that asset's fields. What its value already holds
under the same name stays, if it fits the field's kind. Every other field starts at the default
the asset declares. A field the asset does not have goes.
- **Make struct type** — turns the fields the struct holds by itself into a new struct asset,
named after the variable. The value it holds now becomes the asset's defaults, so nothing reads
differently afterwards. This is the way to turn an existing struct into a type.
With a type, the fields are no longer edited at the variable: there is no **+ Field**, and the
field names cannot be changed or removed there. The **values** stay per variable, and a placed
object may still override them, as with any variable.
## Pack and Expand
`Pack` and `Expand` have a **Type** setting. With a type, their ports are the asset's fields — one
input per field on `Pack`, one output per field on `Expand` — and they follow the asset from then
on. The FIELDS section of such a node lists them without letting you change them. Without a type,
the fields are declared on the node by hand as before (**+ Field**, **From variable**).
## When the asset changes
Every change to a struct asset's fields reaches every place of that type at once, and **one undo**
takes all of it back:
| Change on the asset | What happens at each place |
|---|---|
| Field added | Every value gets it, at the field's default. `Pack`/`Expand` get a port. |
| Field removed | It leaves every value. The port goes, and with it the edge on it and the value typed into it. |
| Field renamed | Every value keeps its value under the new name, overrides included. The port is renamed and keeps its edge. |
| Default changed | Nothing: a default is what a *new* struct of the type starts with. |
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.
## 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.
## 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.
## 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).
## Under the hood
A typed struct keeps a **copy** of the type's fields in its declaration (`fields`), next to
`structType`, the asset's id. Everything that reads a struct's fields therefore works unchanged.
`syncStructTypes` in `src/core/model/structTypes.ts` brings every copy, every value kept for it and
every typed `Pack`/`Expand` back in line; it runs after each change to a struct asset and when a
project is loaded, so a hand-edited file heals too. Its writes go through a `Recorder`
(`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.
+1
@@ -5,6 +5,7 @@
- [Scenes](Scenes)
- [Scene editing](Scene-editing)
- [Behaviors](Behaviors)
- [Structs](Structs)
- [Colliders](Colliders)
- [Physics](Physics)
- [Textures](Textures)