Repository navigation
feat(world): dispatch on_zone_loaded/2 and on_zone_unloaded/2 - #575
Merged
Merged
Conversation
Both callbacks were declared in asobi_world, bridged in asobi_lua_world, documented in three guides, and never called from anywhere in src. A game relying on the documented hook to seed a lazily-created zone did not fail: it got a blank zone_state and went looking for the bug in its own script. on_zone_loaded/2 now runs when the zone manager creates a zone on demand and that zone has nothing to restore. The callback belongs to the world's game state - the world VM, for a Lua game - so the zone asks the world server for it rather than running it itself, and it declines to tick until the answer lands. Both directions are casts: the world server calls into a zone it has just had created (asobi_zone:sync/1 on the join path), so a blocking call the other way would deadlock every join that opens a zone. A zone whose seed does not arrive within a second starts blank and logs. The Lua bridge nests what the world script returns under game_state, which is the key init_zone_state/2 builds the zone VM's game_state from - a flat table would reach the zone and then be invisible to its script. An empty return stays no seed at all, so the usual `if zone_state == nil` guard still fires. on_zone_unloaded/2 is dispatched from the manager's cleanup rather than the zone's terminate/2, so a crashed zone reports as well as a reaped one. Nothing fires on world teardown, where the manager stops before the zones. Closes #574
🟡 Code Coverage — 76.8%9455 of 12315 lines covered. 🟡 ELP Lint — 192 warnings212 diagnostics found. See job logs for details. ✅ ELP eqWAlizeNo diagnostics. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #574.
Both callbacks were declared, bridged into
asobi_lua_world, documented in three guides, and never dispatched fromsrc/. The tests did not catch it because they asserted membership inasobi_world:behaviour_info(callbacks)- a tautology over the declaration - and called the bridge function directly.What now happens
on_zone_loaded/2runs whenasobi_zone_managerstarts a zone on demand and that zone has nothing to restore:generate_world/2built for it;on_zone_unloaded/2runs from the manager'scleanup_zone/2- the one place that sees a reaped zone and a crashed one alike, sinceterminate/2does not run on a kill. It does not fire on world teardown: the instance supervisor stops the manager before the zone supervisor, so the zones' DOWNs are never processed.Why it is a two-cast round trip
The callback runs against the world's game state, which only the world server holds - for
asobi_lua_worldthat is the world VM, and the 200 ms budget inguides/security-trust-model.mdis a world-VM budget. So the zone cannot run it itself.It cannot call the world server either: the world server synchronously calls into a zone it has just had created (
asobi_zone:sync/1inbind_player_zone/4), so a blocking call the other way deadlocks both for two timeouts on every join that opens a zone.So it is two casts, and the zone declines to tick until the answer lands - a tick before the seed would run the spawner and
handle_effectsagainst exactly the blankzone_statethe hook exists to fill in.?ZONE_SEED_DEADLINE(1s) bounds it: a zone that is never answered starts blank, logszone_seed_timeout, and ticks. A zone that never ticks is worse than a blank one.The Lua shape
init_zone_state/2builds the zone VM'sgame_stateout of thegame_statekey of the zone_state it is handed. A flat table from the world script would reach the zone and then be invisible to it, so the bridge nests the return under that key. An empty return is still no seed, soif zone_state == nilstill fires on the zone's first tick.Tests
asobi_zone_lifecycle_hooks_testsdrives a realasobi_world_instance: the hook fires on a lazy load, what it returned is in the zone'szone_state, a pre-warmed zone is not seeded, a stopped zone reports unloaded, and a re-created zone is seeded again.asobi_lua_world_integration_testsruns the whole chain through a Lua world and asserts on what the zone script sees on its first tick, not on the bridge function.handle_call({ensure_zone, ...})is now guarded likerevive_zoneis, so a malformed coords answers{error, invalid_coords}instead of function_clausing atransientchild of aone_for_allsupervisor.Checks
fmt --check,xref,dialyzer,elp eqwalize-all(157 errors, unchanged fromorigin/main),elp lint,ex_doc, and the fulleunitsuite (2413 tests, 0 failures) all run locally.