fix(events): a listener's return ends that listener, not the dispatch
Listeners are compiled into one @ev_<E> function and return branched to its exit, so every listener declared after one that returned early - and every foreign listener - never heard the event. The per-kind listener shape (return unless it is my kind) answered only the first-declared kind. examples/events/answer.ludic holds it; seed regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
45e14dbb99
commit
f5ab5cf88a
8 changed files with 11240 additions and 11128 deletions
7
changes/listener-return.md
Normal file
7
changes/listener-return.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
bump: patch
|
||||
type: fix
|
||||
**A `return` inside an `@On` listener ends that listener, not the whole dispatch.** Listeners are
|
||||
compiled into one `@ev_<E>` function, and `return` branched to its exit: every listener declared
|
||||
after the one that returned, and every foreign listener, never heard the event. The shape it
|
||||
broke is the common one - a listener per kind that returns early for every kind but its own -
|
||||
where only the first-declared kind was ever answered. `examples/events/answer.ludic` holds it.
|
||||
24
examples/events/answer.ludic
Normal file
24
examples/events/answer.ludic
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# answer.ludic — one listener per kind: each answers only for its own and returns early for
|
||||
# every other. A `return` ends that listener, never the dispatch, so the listener for kind 2
|
||||
# still hears the event after the one for kind 1 has returned.
|
||||
#
|
||||
# Running it prints: 10 20 0
|
||||
program Answer {
|
||||
event cancellable Ask { kind: int }
|
||||
var answer: int = 0
|
||||
|
||||
@On(Ask) handler One { if kind != 1 { return }; answer = 10; cancel }
|
||||
@On(Ask) handler Two { if kind != 2 { return }; answer = 20; cancel }
|
||||
|
||||
function ask(k: int) -> int {
|
||||
answer = 0
|
||||
if emit Ask(kind: k) != 0 { return answer }
|
||||
return 0
|
||||
}
|
||||
|
||||
entry {
|
||||
print(ask(1))
|
||||
print(ask(2))
|
||||
print(ask(3))
|
||||
}
|
||||
}
|
||||
|
|
@ -441,6 +441,7 @@ function ondisable_body(prop: pointer) -> Node {
|
|||
# Gated on `len(g_events) > 0`, so a program with no events is byte-identical.
|
||||
var g_events: []Node # each: an N_EVENT node (s = name, kids = payload fields, ival=1 if cancellable)
|
||||
var g_onlisten: []Node # each: N_BLOCK, s = event name, a = listener body block
|
||||
var g_ret_label: pointer = "ret" # where `return` goes: the function's exit, or inside an event dispatch the end of the one listener
|
||||
var g_cancel_addr: pointer = null # EV3: address of the current cancellable dispatch's flag (null outside one)
|
||||
|
||||
function register_event(n: Node) -> void { push(g_events, n) }
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ function emit_return(st: Node) -> void {
|
|||
let v = emit_expr(st.a)
|
||||
store_at(llty(ret_ty), coerce_code(v, ret_ty), "%retval")
|
||||
}
|
||||
emit(" br label %ret\n")
|
||||
emit(` br label %{g_ret_label}\n`)
|
||||
g_term = true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -652,7 +652,18 @@ function emit_event_fns() -> void {
|
|||
let base = nloc # listeners share the params but not each other's locals
|
||||
var i = 0
|
||||
while i < len(g_onlisten) {
|
||||
if (g_onlisten[i].s == en) { nloc = base; g_term = false; emit_block(g_onlisten[i].a) }
|
||||
if (g_onlisten[i].s == en) {
|
||||
# a `return` ends this listener, not the dispatch: the next one and the foreign ones still run
|
||||
nloc = base
|
||||
g_term = false
|
||||
let next = lbl("evnext")
|
||||
g_ret_label = next
|
||||
emit_block(g_onlisten[i].a)
|
||||
g_ret_label = "ret"
|
||||
if not g_term { emit(` br label %{next}\n`) }
|
||||
emit(`{next}:\n`)
|
||||
g_term = false
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
# the open half: walk the foreign callback array in registration order
|
||||
|
|
|
|||
11160
selfhost/ludicc.seed.ll
11160
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -688,6 +688,7 @@ function cmd_dev_test() -> int {
|
|||
|
||||
feat_case("events/promote", "", "100 101 200 201", "promote.ludic (@Public -> model_<M>_spawn/despawn events)")
|
||||
feat_case("events/cancel", "", "0 1 92", "cancel.ludic (cancellable event + cancel + emit-as-expression)")
|
||||
feat_case("events/answer", "", "10 20 0", "answer.ludic (a listener's return ends that listener, not the dispatch)")
|
||||
|
||||
# EV2 the world table: the mod reflection ABI, callable from Ludic by name.
|
||||
net_case("ecs/world_get", "50 1 7")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue