Scriptable properties (ECS-safe subset): @Computed and @OnSpawn
Adds two annotations that give properties/models a scriptable feel WITHOUT reattaching behavior to data — both reduce to code the data-oriented model already emits: - @Computed field on a property: a derived value that is NOT stored; x.field expands inline to its expression (bare names read as fields of x) at each use. Zero storage, zero runtime dispatch. Reuses qualify_fields (now non-destructive, base-node based); a g_computed registry keeps derived fields out of the layout. - @OnSpawn(Model) on a handler: a constructor that runs at each spawn of Model with the model's properties bound by name. Spawn statically knows the model, so no runtime dispatch; emit_spawn binds the properties and inlines the hook body. examples/annotations.ludic now exercises @Handles/@Queries/@Computed/@OnSpawn (output 3 25 0 0); test.sh 15/15, fixpoint holds, goldens byte-identical. Deferred (need more machinery, by design): @OnDespawn (despawn doesn't statically know the entity's model) and @OnChange (needs change-tracking). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
ecead0564e
commit
1951af99e9
9 changed files with 4177 additions and 3503 deletions
|
|
@ -282,12 +282,14 @@ fn already_loaded(full: ptr) -> bool {
|
|||
fn parse_one_decl() -> void {
|
||||
let is_export = false
|
||||
let qspec: Node = ptr_null()
|
||||
let onspawn_model: ptr = ptr_null()
|
||||
while is_op("@") {
|
||||
pi = pi + 1; let a = eat_id() # collect a leading @annotation
|
||||
if streq(a, "export") { is_export = true }
|
||||
if streq(a, "Queries") { qspec = parse_queries_anno() } # @Queries(these: [...], on: ...)
|
||||
else { if streq(a, "Queries") { qspec = parse_queries_anno() } # @Queries(these: [...], on: ...)
|
||||
else { if streq(a, "OnSpawn") { eat_op("("); onspawn_model = eat_id(); eat_op(")") } # @OnSpawn(Model)
|
||||
else { if is_op("(") { let d = 0 # any other @anno(args) — parsed and skipped
|
||||
while true { if is_op("(") { d = d + 1 }; if is_op(")") { d = d - 1 }; pi = pi + 1; if d == 0 { break } } } }
|
||||
while true { if is_op("(") { d = d + 1 }; if is_op(")") { d = d - 1 }; pi = pi + 1; if d == 0 { break } } } } } }
|
||||
skipnl()
|
||||
}
|
||||
if is_id("import") { pi = pi + 1
|
||||
|
|
@ -303,6 +305,9 @@ fn parse_one_decl() -> void {
|
|||
if is_id("model") { push(prog, parse_archetype()); return }
|
||||
if is_id("handler") {
|
||||
let h = parse_system()
|
||||
if not ptr_is_null(onspawn_model) { # @OnSpawn(Model): a spawn hook, not a phased handler
|
||||
register_onspawn(onspawn_model, h.a); return
|
||||
}
|
||||
if not ptr_is_null(qspec) { # @Queries wraps the body in its S_QUERY
|
||||
qspec.a = h.a
|
||||
let wrap = node(N_BLOCK); push(wrap.kids, qspec); h.a = wrap
|
||||
|
|
@ -346,6 +351,8 @@ fn maybe_splice_runtime() -> void {
|
|||
|
||||
fn parse_program() -> void {
|
||||
prog = new []Node
|
||||
g_computed = new []Node
|
||||
g_onspawn = new []Node
|
||||
loaded_paths = new []ptr
|
||||
skipnl()
|
||||
g_game_name = "Ludic"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue