Skip to content

feat(lua): asobi_lua_match_shared bridge for encode-once broadcast#41

Merged
Taure merged 2 commits intomainfrom
feat/lua-match-shared-bridge
May 5, 2026
Merged

feat(lua): asobi_lua_match_shared bridge for encode-once broadcast#41
Taure merged 2 commits intomainfrom
feat/lua-match-shared-bridge

Conversation

@Taure
Copy link
Copy Markdown
Contributor

@Taure Taure commented May 5, 2026

Summary

Companion to asobi#117.

  • New asobi_lua_match_shared bridge module that delegates init/join/leave/handle_input/tick/vote_* to asobi_lua_match and exports get_state/1 calling Lua's one-arg get_state(state)
  • asobi_lua_config reads a new state_strategy global from match.lua and adds state_strategy => shared to the mode config so asobi's resolver picks this bridge

Lua scripts opt in by adding to match.lua:

```lua
state_strategy = "shared"

function get_state(state)
return { ... } -- one shared payload, no per-player view
end
```

Why

asobi#117 enables encode-once-per-tick broadcast for shared-state games via an optional get_state/1 callback. Static Erlang exports + Lua's lack of arity make a single dual-mode bridge awkward, so the cleanest expression is two bridge modules selected by a script-level flag. Existing {lua, Script} configs continue resolving to asobi_lua_match (per-player) — backward compatible.

Test plan

  • rebar3 fmt --check clean
  • rebar3 xref clean
  • rebar3 dialyzer clean
  • ~/bin/elp eqwalize-all zero new errors (43 pre-existing)
  • ~/bin/elp lint zero new warnings on changed lines
  • rebar3 eunit 201/201 green
    • new asobi_lua_match_shared_tests (init/lifecycle/get_state/1)
    • new config test asserts state_strategy = "shared" end-to-end resolves to asobi_lua_match_shared
  • Pin bump asobi after asobi#117 merges
  • Re-bench via asobi-bench to confirm p99 improvement

Taure added 2 commits May 5, 2026 17:28
Companion to asobi#117. Adds a thin bridge module that exports
get_state/1 (shared-payload variant) and delegates everything else to
asobi_lua_match. Selected by declaring `state_strategy = "shared"` in
match.lua, which asobi_lua_config now reads and propagates to the mode
config so asobi_game_modes resolves to this bridge.

Lua scripts opting in must define one-arg `get_state(state)` returning
the world view. The match server then JSON-encodes once per tick and
broadcasts the same binary to every player (see asobi#117 for the
broadcast-side change).

Pin bump pending the upstream merge.
@Taure Taure merged commit ebca1de into main May 5, 2026
15 checks passed
@Taure Taure deleted the feat/lua-match-shared-bridge branch May 5, 2026 15:44
Taure added a commit that referenced this pull request May 5, 2026
)

handle_input/3 in both asobi_lua_match and asobi_lua_world bridges no
longer wraps the Luerl call in bounded_eval (spawn + monitor +
heap_limit). At realistic input rates (200 players × 10 Hz = 2k
inputs/sec) the per-call spawn overhead dominated actual Lua work and
caused tail-latency stalls on the BEAM scheduler.

Bench delta (asobi-bench, 200 bots, 30s, 10 Hz):
- p99.9: ~2945ms -> ~1860ms (-37%)
- max:   ~3750ms -> ~2065ms (-45%)
- inputs throughput: ~26k -> ~41k per 30s window (+56%)

Trade-off documented in ADR 0002 and pinned by tests:
- match_handle_input_no_wall_clock_timeout_test (match bridge)
- world_handle_input_no_wall_clock_timeout_test (world bridge)
- prop_lua_error_containment splits crash modes: tick still tests
  infinite_loop containment; input_crash_mode excludes it (would wedge
  the property runner — by design).

Trust model updated in guides/security-trust-model.md with a new
"Per-callback isolation" table and an explicit "handle_input is not a
sandbox boundary" section.

Also includes the project ADR convention (0000) and retroactive ADR
0001 documenting the asobi_lua_match_shared bridge that shipped in
#41.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant