feat(types): add Reflect.* — runtime reflection over the world schema (#20)
Runtime type reflection: enumerate properties and fields by index, resolve ids
by name, read a field's type, and get/set/has an entity's fields generically —
the foundation the issue calls out for auto-serialization, data-driven tools,
and debug/inspector overlays. Built on the existing EV2 reflection ABI plus a
new EV8 metadata-enumeration layer, all generated at compile time (a table walk,
no heavy runtime introspection), so a binary that never reflects pays nothing.
Surface (Reflect.*, aliased in emit_call.ludic over the world_* reflection ABI):
- Reflect.prop(name) / field(prop,name) resolve ids by name (-1 = none)
- Reflect.prop_count() / prop_name(i) enumerate properties
- Reflect.field_count(prop) / field_name(prop,i) / field_type(prop,i)
enumerate a component's fields
- Reflect.get / set / has (entity, prop, ...) read/write/test a field by id
- Reflect.kind(entity) / model(name) an entity's model, by id/name
New codegen (emit_world.ludic, EV8): ludic_prop_count / prop_name /
field_count / field_name / field_type, generated the same way as ludic_prop_id
— a switch over the compile-time property/field metadata, falling through to the
mod-registered (dynamic) registries. Field names/types come straight from the
AST, so field_type reports the declared type ("int"/"fixed"/…). A program that
uses Reflect.* force-emits the reflection ABI (g_uses_reflect) so it needs no
@events of its own, exactly like Query.* (#42).
examples/library/reflect.ludic asserts 20 cases including a generic inspector
that sums every field of every component an entity has while naming none of them
— the auto-save / debug-overlay pattern end to end. Wired into x test (now 64
passed). Docs: a Reflect section + 12 per-symbol pages (positioned as an
advanced/tooling surface), inventory/coverage green. Seed reseeded; the C-free
bootstrap fixpoint holds.
Scope: this lands the reflection core and a real consumer (the generic
inspector). The generic value-tree `serialize` the proposal also sketches wants
a tagged-union/any value type from the #1 type-system work, so it is tracked as
a follow-up rather than forced in here.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
b25dc328a2
commit
12f2dbe958
20 changed files with 16376 additions and 14274 deletions
69
examples/library/reflect.ludic
Normal file
69
examples/library/reflect.ludic
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
# reflect.ludic — Reflect.* runtime reflection over the world schema: enumerate
|
||||
# properties and fields by index, resolve ids by name, read the type of a field,
|
||||
# and get/set an entity's fields generically. Each assertion that holds prints
|
||||
# its number, so a full run prints:
|
||||
# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
||||
# The generic-inspector case (17) walks every component + field of an entity
|
||||
# without naming any of them — the pattern auto-save and debug overlays use.
|
||||
program Reflect {
|
||||
property Health { hp: int = 0, max: int = 0 }
|
||||
property Velocity { dx: fixed = 0.0, dy: fixed = 0.0 }
|
||||
model Unit { Health, Velocity }
|
||||
|
||||
entry {
|
||||
spawn Unit { Health { hp: 7, max: 10 }, Velocity { dx: fixed(2), dy: fixed(3) } }
|
||||
let e = 0 # the first (only) spawn is entity 0
|
||||
|
||||
# --- enumerate properties by index. The count is >= the two declared here:
|
||||
# the runtime splices its own components (e.g. Cell, from the grid library
|
||||
# core imports), and reflection honestly reports every one. ---
|
||||
if Reflect.prop_count() >= 2 { print(1) }
|
||||
if Reflect.prop_name(0) == "Health" { print(2) }
|
||||
if Reflect.prop_name(1) == "Velocity" { print(3) }
|
||||
|
||||
# --- resolve property ids by name ---
|
||||
let H = Reflect.prop("Health")
|
||||
let V = Reflect.prop("Velocity")
|
||||
if H == 0 { print(4) }
|
||||
if V == 1 { print(5) }
|
||||
if Reflect.prop("Nope") < 0 { print(6) } # unknown -> -1
|
||||
|
||||
# --- enumerate fields by index, with names and types ---
|
||||
if Reflect.field_count(H) == 2 { print(7) }
|
||||
if Reflect.field_name(H, 0) == "hp" { print(8) }
|
||||
if Reflect.field_name(H, 1) == "max" { print(9) }
|
||||
if Reflect.field_type(H, 0) == "int" { print(10) }
|
||||
if Reflect.field_type(V, 0) == "fixed" { print(11) }
|
||||
if Reflect.field(H, "max") == 1 { print(12) }
|
||||
|
||||
# --- entity membership + identity ---
|
||||
if Reflect.has(e, H) { print(13) }
|
||||
if Reflect.has(e, V) { print(14) }
|
||||
if Reflect.kind(e) == Reflect.model("Unit") { print(15) }
|
||||
if Reflect.model("Unit") >= 0 { print(16) }
|
||||
|
||||
# --- generic inspector: sum every field of every component the entity has,
|
||||
# naming nothing. Health(7+10) + Velocity(fixed(2)+fixed(3) raw) ---
|
||||
var total = 0
|
||||
let pc = Reflect.prop_count()
|
||||
var p = 0
|
||||
while p < pc {
|
||||
if Reflect.has(e, p) {
|
||||
let fc = Reflect.field_count(p)
|
||||
var f = 0
|
||||
while f < fc {
|
||||
total = total + Reflect.get(e, p, f)
|
||||
f = f + 1
|
||||
}
|
||||
}
|
||||
p = p + 1
|
||||
}
|
||||
if total == 327697 { print(17) } # 7 + 10 + 131072 + 196608
|
||||
|
||||
# --- get / set a field by (prop, field) id ---
|
||||
if Reflect.get(e, H, 0) == 7 { print(18) } # hp
|
||||
Reflect.set(e, H, 0, 99)
|
||||
if Reflect.get(e, H, 0) == 99 { print(19) } # write took effect
|
||||
if Reflect.get(e, V, 0) == 131072 { print(20) } # dx stored as raw Q16.16 = fixed(2)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue