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
7
docs/language/reflect/_section.md
Normal file
7
docs/language/reflect/_section.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
id: reflect
|
||||
title: Reflect
|
||||
order: 31
|
||||
---
|
||||
|
||||
Runtime reflection over the world schema — inspect properties, fields, and entity state by name and by index. It powers the conveniences game devs consume without writing reflection code: automatic save/load, data-driven tools, and debug/inspector overlays. Enumerate with <a href="reflect-prop_count"><code>Reflect.prop_count</code></a>/<a href="reflect-prop_name"><code>Reflect.prop_name</code></a> and <a href="reflect-field_count"><code>Reflect.field_count</code></a>/<a href="reflect-field_name"><code>Reflect.field_name</code></a>/<a href="reflect-field_type"><code>Reflect.field_type</code></a>; resolve ids with <a href="reflect-prop"><code>Reflect.prop</code></a>/<a href="reflect-field"><code>Reflect.field</code></a>; read and write an entity's fields with <a href="reflect-get"><code>Reflect.get</code></a>/<a href="reflect-set"><code>Reflect.set</code></a>/<a href="reflect-has"><code>Reflect.has</code></a>; and identify its model with <a href="reflect-kind"><code>Reflect.kind</code></a>/<a href="reflect-model"><code>Reflect.model</code></a>. The metadata tables are generated at compile time (the same reflection ABI a foreign mod binds), so introspection is a table walk, not heavy runtime machinery. This is an advanced/tooling surface — most developers get its benefits through built-in features. Related: <a href="world"><code>World</code></a>, <a href="query"><code>Query</code></a>.
|
||||
30
docs/language/reflect/reflect-field.md
Normal file
30
docs/language/reflect/reflect-field.md
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
id: reflect-field
|
||||
name: Reflect.field
|
||||
category: reflect
|
||||
kind: namespace-method
|
||||
tokens: Reflect.field
|
||||
sig: Reflect.field(prop, name) -> int
|
||||
tip: The field id of a named field within a property, or -1.
|
||||
order: 2
|
||||
ns: Reflect
|
||||
member: field
|
||||
---
|
||||
|
||||
Resolves a field name within property <code>prop</code> to its field id (index), or <code>-1</code> if the property has no such field. Pair it with <a href="reflect-get"><code>Reflect.get</code></a>/<a href="reflect-set"><code>Reflect.set</code></a> to read or write that field.
|
||||
|
||||
Parameters:
|
||||
- `prop` — a property id (from <a href="reflect-prop"><code>Reflect.prop</code></a>)
|
||||
- `name` — the field name
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
property Health { hp: int = 0, max: int = 0 }
|
||||
model Unit { Health }
|
||||
entry {
|
||||
let H = Reflect.prop("Health")
|
||||
let mx = Reflect.field(H, "max")
|
||||
print(mx)
|
||||
}
|
||||
}
|
||||
```
|
||||
27
docs/language/reflect/reflect-field_count.md
Normal file
27
docs/language/reflect/reflect-field_count.md
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
id: reflect-field_count
|
||||
name: Reflect.field_count
|
||||
category: reflect
|
||||
kind: namespace-method
|
||||
tokens: Reflect.field_count
|
||||
sig: Reflect.field_count(prop) -> int
|
||||
tip: How many fields a property has.
|
||||
order: 5
|
||||
ns: Reflect
|
||||
member: field_count
|
||||
---
|
||||
|
||||
Returns the number of fields in property <code>prop</code>. Walk <code>0 .. Reflect.field_count(prop)</code> with <a href="reflect-field_name"><code>Reflect.field_name</code></a> and <a href="reflect-field_type"><code>Reflect.field_type</code></a> to enumerate a component's shape.
|
||||
|
||||
Parameters:
|
||||
- `prop` — a property id (from <a href="reflect-prop"><code>Reflect.prop</code></a>)
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
property Health { hp: int = 0, max: int = 0 }
|
||||
model Unit { Health }
|
||||
entry {
|
||||
print(Reflect.field_count(Reflect.prop("Health"))) # 2
|
||||
}
|
||||
}
|
||||
```
|
||||
29
docs/language/reflect/reflect-field_name.md
Normal file
29
docs/language/reflect/reflect-field_name.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
id: reflect-field_name
|
||||
name: Reflect.field_name
|
||||
category: reflect
|
||||
kind: namespace-method
|
||||
tokens: Reflect.field_name
|
||||
sig: Reflect.field_name(prop, index) -> string
|
||||
tip: The name of the field at an index within a property.
|
||||
order: 6
|
||||
ns: Reflect
|
||||
member: field_name
|
||||
---
|
||||
|
||||
Returns the name of the field at <code>index</code> within property <code>prop</code> (<code>0 .. Reflect.field_count(prop)</code>), or the empty string if out of range. The key half of a generic serializer or inspector row.
|
||||
|
||||
Parameters:
|
||||
- `prop` — a property id (from <a href="reflect-prop"><code>Reflect.prop</code></a>)
|
||||
- `index` — the field index
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
property Health { hp: int = 0, max: int = 0 }
|
||||
model Unit { Health }
|
||||
entry {
|
||||
let H = Reflect.prop("Health")
|
||||
print(Reflect.field_name(H, 0)) # hp
|
||||
}
|
||||
}
|
||||
```
|
||||
29
docs/language/reflect/reflect-field_type.md
Normal file
29
docs/language/reflect/reflect-field_type.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
id: reflect-field_type
|
||||
name: Reflect.field_type
|
||||
category: reflect
|
||||
kind: namespace-method
|
||||
tokens: Reflect.field_type
|
||||
sig: Reflect.field_type(prop, index) -> string
|
||||
tip: The type name of the field at an index within a property.
|
||||
order: 7
|
||||
ns: Reflect
|
||||
member: field_type
|
||||
---
|
||||
|
||||
Returns the declared type name of the field at <code>index</code> within property <code>prop</code> — <code>"int"</code>, <code>"fixed"</code>, <code>"bool"</code>, and so on. A serializer uses it to format each value correctly, and an inspector uses it to pick the right editor widget.
|
||||
|
||||
Parameters:
|
||||
- `prop` — a property id (from <a href="reflect-prop"><code>Reflect.prop</code></a>)
|
||||
- `index` — the field index
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
property Velocity { dx: fixed = 0.0, dy: fixed = 0.0 }
|
||||
model Mover { Velocity }
|
||||
entry {
|
||||
let V = Reflect.prop("Velocity")
|
||||
print(Reflect.field_type(V, 0)) # fixed
|
||||
}
|
||||
}
|
||||
```
|
||||
31
docs/language/reflect/reflect-get.md
Normal file
31
docs/language/reflect/reflect-get.md
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
id: reflect-get
|
||||
name: Reflect.get
|
||||
category: reflect
|
||||
kind: namespace-method
|
||||
tokens: Reflect.get
|
||||
sig: Reflect.get(entity, prop, field) -> int
|
||||
tip: Read one field of an entity by (prop, field) id.
|
||||
order: 8
|
||||
ns: Reflect
|
||||
member: get
|
||||
---
|
||||
|
||||
Reads the value of field <code>field</code> of property <code>prop</code> on <code>entity</code>, as an <code>int</code>. A <code>fixed</code> field comes back as its raw Q16.16 bits (the same integer <code>fixed</code> stores), so a generic reader can move it without interpreting it. Same as <a href="world-get"><code>World.get</code></a>, under the reflection namespace.
|
||||
|
||||
Parameters:
|
||||
- `entity` — the entity to read
|
||||
- `prop` — a property id (from <a href="reflect-prop"><code>Reflect.prop</code></a>)
|
||||
- `field` — a field id (from <a href="reflect-field"><code>Reflect.field</code></a>)
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
property Health { hp: int = 0 }
|
||||
model Unit { Health }
|
||||
entry {
|
||||
spawn Unit { Health { hp: 7 } }
|
||||
let H = Reflect.prop("Health")
|
||||
print(Reflect.get(0, H, Reflect.field(H, "hp"))) # 7
|
||||
}
|
||||
}
|
||||
```
|
||||
29
docs/language/reflect/reflect-has.md
Normal file
29
docs/language/reflect/reflect-has.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
id: reflect-has
|
||||
name: Reflect.has
|
||||
category: reflect
|
||||
kind: namespace-method
|
||||
tokens: Reflect.has
|
||||
sig: Reflect.has(entity, prop) -> bool
|
||||
tip: Does an entity carry a property?
|
||||
order: 10
|
||||
ns: Reflect
|
||||
member: has
|
||||
---
|
||||
|
||||
Returns whether <code>entity</code> carries property <code>prop</code>. A generic walker checks it before reading a component's fields, so it visits only the components an entity actually has.
|
||||
|
||||
Parameters:
|
||||
- `entity` — the entity to test
|
||||
- `prop` — a property id (from <a href="reflect-prop"><code>Reflect.prop</code></a>)
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
property Health { hp: int = 0 }
|
||||
model Unit { Health }
|
||||
entry {
|
||||
spawn Unit { Health { hp: 7 } }
|
||||
if Reflect.has(0, Reflect.prop("Health")) { print(1) }
|
||||
}
|
||||
}
|
||||
```
|
||||
28
docs/language/reflect/reflect-kind.md
Normal file
28
docs/language/reflect/reflect-kind.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
id: reflect-kind
|
||||
name: Reflect.kind
|
||||
category: reflect
|
||||
kind: namespace-method
|
||||
tokens: Reflect.kind
|
||||
sig: Reflect.kind(entity) -> int
|
||||
tip: The model id of an entity.
|
||||
order: 11
|
||||
ns: Reflect
|
||||
member: kind
|
||||
---
|
||||
|
||||
Returns the model (archetype) id of <code>entity</code> — which kind of thing it is. Compare it against <a href="reflect-model"><code>Reflect.model</code></a> to branch on an entity's type, or use it to label a save record. Same as <a href="world-kind"><code>World.kind</code></a>, under the reflection namespace.
|
||||
|
||||
Parameters:
|
||||
- `entity` — the entity to identify
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
property Health { hp: int = 0 }
|
||||
model Unit { Health }
|
||||
entry {
|
||||
spawn Unit { Health { hp: 7 } }
|
||||
if Reflect.kind(0) == Reflect.model("Unit") { print(1) }
|
||||
}
|
||||
}
|
||||
```
|
||||
27
docs/language/reflect/reflect-model.md
Normal file
27
docs/language/reflect/reflect-model.md
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
id: reflect-model
|
||||
name: Reflect.model
|
||||
category: reflect
|
||||
kind: namespace-method
|
||||
tokens: Reflect.model
|
||||
sig: Reflect.model(name) -> int
|
||||
tip: The model id for a model name, or -1 if unknown.
|
||||
order: 12
|
||||
ns: Reflect
|
||||
member: model
|
||||
---
|
||||
|
||||
Resolves a model (archetype) name to its id, or <code>-1</code> if no such model exists. The counterpart to <a href="reflect-kind"><code>Reflect.kind</code></a>: resolve the name once, then compare entities' kinds against it.
|
||||
|
||||
Parameters:
|
||||
- `name` — the model name
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
property Health { hp: int = 0 }
|
||||
model Unit { Health }
|
||||
entry {
|
||||
print(Reflect.model("Unit"))
|
||||
}
|
||||
}
|
||||
```
|
||||
28
docs/language/reflect/reflect-prop.md
Normal file
28
docs/language/reflect/reflect-prop.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
id: reflect-prop
|
||||
name: Reflect.prop
|
||||
category: reflect
|
||||
kind: namespace-method
|
||||
tokens: Reflect.prop
|
||||
sig: Reflect.prop(name) -> int
|
||||
tip: The property id for a property name, or -1 if unknown.
|
||||
order: 1
|
||||
ns: Reflect
|
||||
member: prop
|
||||
---
|
||||
|
||||
Resolves a property name to its stable id, or <code>-1</code> if no such property exists. The id is what every other <code>Reflect.*</code> (and <a href="query"><code>Query</code></a>) call takes. Same as <a href="world-prop_id"><code>World.prop_id</code></a>, under the reflection namespace.
|
||||
|
||||
Parameters:
|
||||
- `name` — the property name
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
property Health { hp: int = 0 }
|
||||
model Unit { Health }
|
||||
entry {
|
||||
let H = Reflect.prop("Health")
|
||||
print(H)
|
||||
}
|
||||
}
|
||||
```
|
||||
24
docs/language/reflect/reflect-prop_count.md
Normal file
24
docs/language/reflect/reflect-prop_count.md
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
id: reflect-prop_count
|
||||
name: Reflect.prop_count
|
||||
category: reflect
|
||||
kind: namespace-method
|
||||
tokens: Reflect.prop_count
|
||||
sig: Reflect.prop_count() -> int
|
||||
tip: How many properties the world schema defines.
|
||||
order: 3
|
||||
ns: Reflect
|
||||
member: prop_count
|
||||
---
|
||||
|
||||
Returns the number of properties in the world — every declared <code>property</code>, plus any the runtime or a mod registered. Walk <code>0 .. Reflect.prop_count()</code> with <a href="reflect-prop_name"><code>Reflect.prop_name</code></a> to enumerate the whole schema.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
property Health { hp: int = 0 }
|
||||
model Unit { Health }
|
||||
entry {
|
||||
print(Reflect.prop_count())
|
||||
}
|
||||
}
|
||||
```
|
||||
32
docs/language/reflect/reflect-prop_name.md
Normal file
32
docs/language/reflect/reflect-prop_name.md
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
id: reflect-prop_name
|
||||
name: Reflect.prop_name
|
||||
category: reflect
|
||||
kind: namespace-method
|
||||
tokens: Reflect.prop_name
|
||||
sig: Reflect.prop_name(index) -> string
|
||||
tip: The name of the property at an index (or "").
|
||||
order: 4
|
||||
ns: Reflect
|
||||
member: prop_name
|
||||
---
|
||||
|
||||
Returns the name of the property at <code>index</code> (<code>0 .. Reflect.prop_count()</code>), or the empty string if out of range. The reverse of <a href="reflect-prop"><code>Reflect.prop</code></a> — together they let a generic tool list every component by name.
|
||||
|
||||
Parameters:
|
||||
- `index` — the property index
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
property Health { hp: int = 0 }
|
||||
model Unit { Health }
|
||||
entry {
|
||||
var p = 0
|
||||
let n = Reflect.prop_count()
|
||||
while p < n {
|
||||
print(Reflect.prop_name(p))
|
||||
p = p + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
33
docs/language/reflect/reflect-set.md
Normal file
33
docs/language/reflect/reflect-set.md
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
id: reflect-set
|
||||
name: Reflect.set
|
||||
category: reflect
|
||||
kind: namespace-method
|
||||
tokens: Reflect.set
|
||||
sig: Reflect.set(entity, prop, field, value)
|
||||
tip: Write one field of an entity by (prop, field) id.
|
||||
order: 9
|
||||
ns: Reflect
|
||||
member: set
|
||||
---
|
||||
|
||||
Writes <code>value</code> into field <code>field</code> of property <code>prop</code> on <code>entity</code>. For a <code>fixed</code> field, pass the raw Q16.16 bits. This is how a generic loader restores saved state and how a debug overlay applies an edit. Same as <a href="world-set"><code>World.set</code></a>, under the reflection namespace.
|
||||
|
||||
Parameters:
|
||||
- `entity` — the entity to modify
|
||||
- `prop` — a property id (from <a href="reflect-prop"><code>Reflect.prop</code></a>)
|
||||
- `field` — a field id (from <a href="reflect-field"><code>Reflect.field</code></a>)
|
||||
- `value` — the integer value to store
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
property Health { hp: int = 0 }
|
||||
model Unit { Health }
|
||||
entry {
|
||||
spawn Unit { Health { hp: 7 } }
|
||||
let H = Reflect.prop("Health")
|
||||
Reflect.set(0, H, Reflect.field(H, "hp"), 99)
|
||||
print(Reflect.get(0, H, 0)) # 99
|
||||
}
|
||||
}
|
||||
```
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -234,6 +234,24 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
|
|||
if (meth == "nearest") { bare = "query_nearest"; push(labels, "prop"); push(labels, "pos"); push(labels, "x_field"); push(labels, "y_field"); push(labels, "x"); push(labels, "y") }
|
||||
if (meth == "within") { bare = "query_within"; push(labels, "prop"); push(labels, "pos"); push(labels, "x"); push(labels, "y"); push(labels, "radius"); push(labels, "x_field"); push(labels, "y_field") }
|
||||
}
|
||||
# Reflect.* — runtime type reflection over the world schema (the EV2/EV8 ABI).
|
||||
# Enumerate properties and fields by index, resolve ids by name, and read/write
|
||||
# a field by (prop, field) id — the foundation for auto-serialization and debug
|
||||
# inspectors. Reads the same generated metadata a foreign mod binds.
|
||||
if (ns == "Reflect") {
|
||||
if (meth == "prop") { bare = "world_prop_id"; push(labels, "name") }
|
||||
if (meth == "field") { bare = "world_field_id"; push(labels, "prop"); push(labels, "name") }
|
||||
if (meth == "prop_count") { bare = "world_prop_count" }
|
||||
if (meth == "prop_name") { bare = "world_prop_name"; push(labels, "index") }
|
||||
if (meth == "field_count") { bare = "world_field_count"; push(labels, "prop") }
|
||||
if (meth == "field_name") { bare = "world_field_name"; push(labels, "prop"); push(labels, "index") }
|
||||
if (meth == "field_type") { bare = "world_field_type"; push(labels, "prop"); push(labels, "index") }
|
||||
if (meth == "get") { bare = "world_get"; push(labels, "entity"); push(labels, "prop"); push(labels, "field") }
|
||||
if (meth == "set") { bare = "world_set"; push(labels, "entity"); push(labels, "prop"); push(labels, "field"); push(labels, "value") }
|
||||
if (meth == "has") { bare = "world_has"; push(labels, "entity"); push(labels, "prop") }
|
||||
if (meth == "kind") { bare = "world_kind"; push(labels, "entity") }
|
||||
if (meth == "model") { bare = "world_model_id"; push(labels, "name") }
|
||||
}
|
||||
if (bare == null) { perr(`unknown builtin {ns}.{meth}`) }
|
||||
reorder_named(e, labels)
|
||||
let id = node(E_ID); id.s = bare; e.a = id
|
||||
|
|
@ -378,6 +396,12 @@ function emit_call(e: Node) -> Val {
|
|||
if (name == "world_attach_dyn") { let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]); emit(" call void @ludic_attach_dyn(i32 "); emit(a.code); emit(", i32 "); emit(b.code); emit(")\n"); return val("0", "void") }
|
||||
if (name == "world_detach_dyn") { let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]); emit(" call void @ludic_detach_dyn(i32 "); emit(a.code); emit(", i32 "); emit(b.code); emit(")\n"); return val("0", "void") }
|
||||
if (name == "world_spawn") { let a = emit_expr(e.kids[0]); return val(emit_bind(`call i32 @ludic_spawn(i32 {a.code})`), "int") }
|
||||
# EV8 — schema enumeration, walking property/field metadata by index (Reflect.*).
|
||||
if (name == "world_prop_count") { return val(emit_bind("call i32 @ludic_prop_count()"), "int") }
|
||||
if (name == "world_prop_name") { let a = emit_expr(e.kids[0]); return val(emit_bind(`call ptr @ludic_prop_name(i32 {a.code})`), "string") }
|
||||
if (name == "world_field_count") { let a = emit_expr(e.kids[0]); return val(emit_bind(`call i32 @ludic_field_count(i32 {a.code})`), "int") }
|
||||
if (name == "world_field_name") { let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]); return val(emit_bind(`call ptr @ludic_field_name(i32 {a.code}, i32 {b.code})`), "string") }
|
||||
if (name == "world_field_type") { let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]); return val(emit_bind(`call ptr @ludic_field_type(i32 {a.code}, i32 {b.code})`), "string") }
|
||||
if is_intrinsic(name) { return emit_intrinsic(name, e) }
|
||||
if is_intrinsic2(name) { return emit_intrinsic2(name, e) }
|
||||
if is_math_builtin(name) { return emit_math_builtin(name, e) }
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ function emit_program() -> void {
|
|||
var i = 0
|
||||
while i < len(prog) { if prog[i].kind == N_FN { emit_fn(prog[i]) }; i = i + 1 }
|
||||
if len(g_events) > 0 { emit_event_fns() } # EV0: @ev_<E> event-dispatch functions
|
||||
if has_ecs() and (len(g_events) > 0 or g_uses_query) { emit_world_table() } # EV2: the mod reflection ABI (also powers Query.*)
|
||||
if has_ecs() and (len(g_events) > 0 or g_uses_query or g_uses_reflect) { emit_world_table() } # EV2/EV8: the mod reflection ABI (also powers Query.* / Reflect.*)
|
||||
if has_ecs() { emit_ecs_allocator(); emit_snapshot() }
|
||||
if has_ecs() { emit_net() } # N2/N3: @Sync serializers + @Owned storage (gated internally)
|
||||
if has_ui() { emit_ui_build() }
|
||||
|
|
|
|||
|
|
@ -294,6 +294,118 @@ function emit_world_table() -> void {
|
|||
emit("hitb:\n ret i32 %e\n")
|
||||
emit("cont:\n %e1 = add i32 %e, 1\n br label %loop\n")
|
||||
emit("none:\n ret i32 -1\n}\n\n")
|
||||
|
||||
emit_world_reflect_enum()
|
||||
}
|
||||
|
||||
# EV8 — schema enumeration: walk the property/field metadata by index, not just
|
||||
# by name. Powers the Reflect.* namespace (auto-serialization, debug inspectors)
|
||||
# so a mod can list every component and field without knowing them up front.
|
||||
# Compile-time props take ids 0..NC-1; ids >= NC are mod-registered (dynamic),
|
||||
# whose field names are unknown (i32 fields) so they report "" / "int".
|
||||
function emit_world_reflect_enum() -> void {
|
||||
let empty = emit_str_const("")
|
||||
let tint = emit_str_const("int")
|
||||
|
||||
# count the compile-time components (== the first dynamic prop id)
|
||||
var ncomp = 0
|
||||
var i = 0
|
||||
while i < len(prog) { if prog[i].kind == N_COMP { ncomp = ncomp + 1 }; i = i + 1 }
|
||||
let NC = itoa(ncomp)
|
||||
|
||||
# ludic_prop_count() -> total property count (compile-time + mod-registered)
|
||||
emit("define i32 @ludic_prop_count() {\nentry:\n")
|
||||
emit(" %d = load i32, ptr @dyn_count\n %n = add i32 %d, "); emit(NC); emit("\n ret i32 %n\n}\n\n")
|
||||
|
||||
# ludic_prop_name(i) -> the i-th property's name (or "" if out of range)
|
||||
emit("define ptr @ludic_prop_name(i32 %i) {\nentry:\n")
|
||||
var k = 0; i = 0
|
||||
while i < len(prog) {
|
||||
if prog[i].kind == N_COMP {
|
||||
let sc = emit_str_const(prog[i].s); let sk = itoa(k)
|
||||
emit(" %pe"); emit(sk); emit(" = icmp eq i32 %i, "); emit(sk); emit("\n")
|
||||
emit(" br i1 %pe"); emit(sk); emit(", label %ph"); emit(sk); emit(", label %pn"); emit(sk); emit("\n")
|
||||
emit("ph"); emit(sk); emit(":\n ret ptr "); emit(sc); emit("\n")
|
||||
emit("pn"); emit(sk); emit(":\n")
|
||||
k = k + 1
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
emit(" %dyn = sub i32 %i, "); emit(NC); emit("\n %dc = load i32, ptr @dyn_count\n")
|
||||
emit(" %ib = icmp uge i32 %dyn, %dc\n br i1 %ib, label %pbad, label %pdyn\n")
|
||||
emit("pdyn:\n %np = getelementptr inbounds [32 x ptr], ptr @dynName, i32 0, i32 %dyn\n %nm = load ptr, ptr %np\n ret ptr %nm\n")
|
||||
emit("pbad:\n ret ptr "); emit(empty); emit("\n}\n\n")
|
||||
|
||||
# ludic_field_count(prop) -> the number of fields of a property
|
||||
emit("define i32 @ludic_field_count(i32 %p) {\nentry:\n")
|
||||
k = 0; i = 0
|
||||
while i < len(prog) {
|
||||
if prog[i].kind == N_COMP {
|
||||
let c = prog[i]; let sk = itoa(k)
|
||||
emit(" %fce"); emit(sk); emit(" = icmp eq i32 %p, "); emit(sk); emit("\n")
|
||||
emit(" br i1 %fce"); emit(sk); emit(", label %fch"); emit(sk); emit(", label %fcn"); emit(sk); emit("\n")
|
||||
emit("fch"); emit(sk); emit(":\n ret i32 "); emit(itoa(len(c.kids))); emit("\n")
|
||||
emit("fcn"); emit(sk); emit(":\n")
|
||||
k = k + 1
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
emit(" %dyn = sub i32 %p, "); emit(NC); emit("\n %dc = load i32, ptr @dyn_count\n")
|
||||
emit(" %ib = icmp uge i32 %dyn, %dc\n br i1 %ib, label %fcbad, label %fcdyn\n")
|
||||
emit("fcdyn:\n %fp = getelementptr inbounds [32 x i32], ptr @dynF, i32 0, i32 %dyn\n %fn = load i32, ptr %fp\n ret i32 %fn\n")
|
||||
emit("fcbad:\n ret i32 0\n}\n\n")
|
||||
|
||||
# ludic_field_name(prop, f) -> the f-th field's name of a property (or "")
|
||||
emit("define ptr @ludic_field_name(i32 %p, i32 %f) {\nentry:\n")
|
||||
k = 0; i = 0
|
||||
while i < len(prog) {
|
||||
if prog[i].kind == N_COMP {
|
||||
let c = prog[i]; let sk = itoa(k)
|
||||
emit(" %fne"); emit(sk); emit(" = icmp eq i32 %p, "); emit(sk); emit("\n")
|
||||
emit(" br i1 %fne"); emit(sk); emit(", label %fnk"); emit(sk); emit(", label %fnn"); emit(sk); emit("\n")
|
||||
emit("fnk"); emit(sk); emit(":\n")
|
||||
var f = 0
|
||||
while f < len(c.kids) {
|
||||
let fc = emit_str_const(c.kids[f].s); let fk = `{sk}_{itoa(f)}`
|
||||
emit(" %fnfe"); emit(fk); emit(" = icmp eq i32 %f, "); emit(itoa(f)); emit("\n")
|
||||
emit(" br i1 %fnfe"); emit(fk); emit(", label %fnfh"); emit(fk); emit(", label %fnfn"); emit(fk); emit("\n")
|
||||
emit("fnfh"); emit(fk); emit(":\n ret ptr "); emit(fc); emit("\n")
|
||||
emit("fnfn"); emit(fk); emit(":\n")
|
||||
f = f + 1
|
||||
}
|
||||
emit(" ret ptr "); emit(empty); emit("\n")
|
||||
emit("fnn"); emit(sk); emit(":\n")
|
||||
k = k + 1
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
emit(" ret ptr "); emit(empty); emit("\n}\n\n")
|
||||
|
||||
# ludic_field_type(prop, f) -> the f-th field's type name (or "int" fallback)
|
||||
emit("define ptr @ludic_field_type(i32 %p, i32 %f) {\nentry:\n")
|
||||
k = 0; i = 0
|
||||
while i < len(prog) {
|
||||
if prog[i].kind == N_COMP {
|
||||
let c = prog[i]; let sk = itoa(k)
|
||||
emit(" %fte"); emit(sk); emit(" = icmp eq i32 %p, "); emit(sk); emit("\n")
|
||||
emit(" br i1 %fte"); emit(sk); emit(", label %ftk"); emit(sk); emit(", label %ftn"); emit(sk); emit("\n")
|
||||
emit("ftk"); emit(sk); emit(":\n")
|
||||
var f = 0
|
||||
while f < len(c.kids) {
|
||||
let tc = emit_str_const(c.kids[f].ty); let fk = `{sk}_{itoa(f)}`
|
||||
emit(" %ftfe"); emit(fk); emit(" = icmp eq i32 %f, "); emit(itoa(f)); emit("\n")
|
||||
emit(" br i1 %ftfe"); emit(fk); emit(", label %ftfh"); emit(fk); emit(", label %ftfn"); emit(fk); emit("\n")
|
||||
emit("ftfh"); emit(fk); emit(":\n ret ptr "); emit(tc); emit("\n")
|
||||
emit("ftfn"); emit(fk); emit(":\n")
|
||||
f = f + 1
|
||||
}
|
||||
emit(" ret ptr "); emit(tint); emit("\n")
|
||||
emit("ftn"); emit(sk); emit(":\n")
|
||||
k = k + 1
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
emit(" ret ptr "); emit(tint); emit("\n}\n\n")
|
||||
}
|
||||
|
||||
# N5 — the drivable sim (NETWORKING-DESIGN §5). The per-frame phases the auto-loop
|
||||
|
|
|
|||
|
|
@ -163,6 +163,7 @@ function p_postfix() -> Node {
|
|||
if is_op(".") { pi = pi + 1; let m = node(E_MEMBER); m.a = e; m.s = eat_id(); e = m
|
||||
if e.a.kind == E_ID and e.a.s == "Regex" { g_uses_regex = true } # splice the regex runtime on demand
|
||||
if e.a.kind == E_ID and e.a.s == "Query" { g_uses_query = true } # splice the ECS spatial-query runtime on demand
|
||||
if e.a.kind == E_ID and e.a.s == "Reflect" { g_uses_reflect = true } # force-emit the reflection ABI (Reflect.* reads the world schema)
|
||||
}
|
||||
else { if is_op("[") { pi = pi + 1; let lo = expr()
|
||||
if is_op("..") { pi = pi + 1; let sl = node(E_SLICE); sl.a = e; sl.b = lo; sl.c = expr(); eat_op("]"); e = sl } # s[a..b] substring
|
||||
|
|
@ -376,6 +377,7 @@ var loaded_paths: []pointer
|
|||
var cur_dir: pointer
|
||||
var g_uses_regex: bool = false # a program mentioned Regex.* -> splice the regex runtime
|
||||
var g_uses_query: bool = false # a program mentioned Query.* -> splice the query runtime + reflection ABI
|
||||
var g_uses_reflect: bool = false # a program mentioned Reflect.* -> force-emit the reflection ABI
|
||||
|
||||
function already_loaded(full: pointer) -> bool {
|
||||
var i = 0
|
||||
|
|
@ -554,6 +556,7 @@ function parse_program() -> void {
|
|||
g_toggled_layers = new []pointer
|
||||
g_uses_regex = false
|
||||
g_uses_query = false
|
||||
g_uses_reflect = false
|
||||
loaded_paths = new []pointer
|
||||
skipnl()
|
||||
g_game_name = "Ludic"
|
||||
|
|
|
|||
30085
selfhost/ludicc.seed.ll
30085
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
|
|
@ -106,6 +106,7 @@ function cmd_test() -> int {
|
|||
feat_case("library/grid", "", "1 2 3 4 5 6 7 8 9 10 11 12 13", "grid.ludic (Grid line/flood/line_of_sight + A* pathfinding over the tilemap)")
|
||||
feat_case("library/anim", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34", "anim.ludic (Anim frame/once/pingpong/cell + Tween progress/loop/yoyo/ease/number/round/point/tint)")
|
||||
feat_case("library/query", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18", "query.ludic (Query count/first/nearest/within — ECS spatial queries over the reflection ABI)")
|
||||
feat_case("library/reflect", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20", "reflect.ludic (Reflect prop/field enumeration + type + get/set/has/kind — runtime reflection over the world schema)")
|
||||
feat_case("library/logging", "", "0 5 2 1", "logging.ludic (Log levels, set_level/level threshold, structured fields)")
|
||||
# Os known-folders/arch and Fs.list read the BSD utsname/dirent layout, so
|
||||
# their asserted values are macOS-specific; skip off Darwin (see is_darwin).
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue