Component.define makes a live Component Schema, with fields and Awake (#80) #92

Merged
crowmaki merged 2 commits from component-define into main 2026-09-06 03:09:24 +00:00
Owner

Closes #80. Part of #75.

What

A Script writes local Mover = Component.define("game.mover", { speed = 1.5, enabled = true }) and gets a live Component Schema in Core.

  • Component.define reads the defaults table; a number picks F64, a boolean picks Bool, anything else raises naming the field, which fails the load. Fields are sorted by name so the layout does not depend on table iteration order. define is legal only while a Script loads.
  • Every Script-defined Schema registers an on_add trampoline. It recovers the Plugin State through the published Interface, writes the defaults into Core storage, then runs Awake under pcall with a traceback handler, all before component_add returns. A Luau error in Awake is logged with the traceback and the Component stays.
  • An instance reaches Luau as a tagged userdata holding the Component Handle and its class record. It is cached weakly per Handle, so == is identity within a frame. Reads and writes go through component_data and the Schema field offset; the VM holds no Component state. Unknown fields, mistyped values, and a destroyed Component raise, the last naming read or write.
  • A second Script defining a taken name fails its load with the owning Script named. The defining Script reloading replaces the Schema, and Core migrates the instances by field name.

One divergence from the issue text: it says "a shared metatable per class". This uses one metatable for every class, since the userdata carries its class pointer and a Luau userdata tag maps to exactly one metatable. Behaviour is identical.

Tests

tests/luau/component_test.odin covers every acceptance criterion from Luau source strings only. The harness the two luau test files shared by copy now lives in tests/luau/harness.odin.

just check and just test pass (420 tests across six suites).

Closes #80. Part of #75. ## What A Script writes `local Mover = Component.define("game.mover", { speed = 1.5, enabled = true })` and gets a live Component Schema in Core. - `Component.define` reads the defaults table; a number picks `F64`, a boolean picks `Bool`, anything else raises naming the field, which fails the load. Fields are sorted by name so the layout does not depend on table iteration order. `define` is legal only while a Script loads. - Every Script-defined Schema registers an `on_add` trampoline. It recovers the Plugin State through the published Interface, writes the defaults into Core storage, then runs `Awake` under `pcall` with a traceback handler, all before `component_add` returns. A Luau error in `Awake` is logged with the traceback and the Component stays. - An instance reaches Luau as a tagged userdata holding the Component Handle and its class record. It is cached weakly per Handle, so `==` is identity within a frame. Reads and writes go through `component_data` and the Schema field offset; the VM holds no Component state. Unknown fields, mistyped values, and a destroyed Component raise, the last naming `read` or `write`. - A second Script defining a taken name fails its load with the owning Script named. The defining Script reloading replaces the Schema, and Core migrates the instances by field name. One divergence from the issue text: it says "a shared metatable per class". This uses one metatable for every class, since the userdata carries its class pointer and a Luau userdata tag maps to exactly one metatable. Behaviour is identical. ## Tests `tests/luau/component_test.odin` covers every acceptance criterion from Luau source strings only. The harness the two luau test files shared by copy now lives in `tests/luau/harness.odin`. `just check` and `just test` pass (420 tests across six suites).
A Script writes Component.define(name, defaults) and gets a Component
Schema registered in Core, with each default's Luau type picking the
field Kind. Instances reach Luau as a userdata carrying the Component
Handle; field reads and writes go through component_data and the Schema
offset, so no Component state lives in the VM. The on_add trampoline
applies the defaults and runs Awake before component_add returns. A
second Script defining a taken name fails its load; the defining Script
reloading replaces the Schema and migrates instances.
crowmaki deleted branch component-define 2026-09-06 03:09:24 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
crowmaki/cinnabar!92
No description provided.