asobi_lua_reload (asobi v0.84.0)

View Source

Shared hot-reload primitive for Lua-backed match and world states.

Both asobi_lua_match and asobi_lua_world (per-world and per-zone) keep a Luerl state alongside the path of the script that produced it. On every tick we stat the file; if its mtime has moved, we re-execute the script body against the current Luerl state — re-declaring globals and functions in place — preserving in-flight game state.

Behaviour:

  • Lua-side state (players, counters, tables) lives inside the Luerl state and survives reload, because the script body only reassigns globals and redefines functions; existing locals and table fields are not touched unless the script explicitly re-runs init().
  • Erlang-side state (the wrapper map) is otherwise untouched, except for one output field: a successful reload stamps just_reloaded => true for exactly the tick that reloaded, cleared on every other call (including a live reload_mode flip to off). asobi_lua_world reads this to drive spawn_templates_hint/1 (asobi#253); asobi_lua_match ignores it.
  • A syntax error in the new script logs a warning, remembers the new mtime (so we don't keep retrying the same broken file), and keeps running the old code until the file is fixed.
  • The _ASOBI_LOADED require cache is cleared so transitive require()d modules also re-read from disk.

Deployment-mode notes

This is a filesystem-mtime primitive. It assumes the script lives at a path the BEAM can stat(), and that "the script has changed" can be expressed as a new mtime. Two real-world deployment models fit that:

  • Local dev — Lua files mounted via a Docker bind mount or sitting under /app/game/ directly. Edit-save triggers a reload on the next tick.
  • Self-hosted prod with a host-volume mount — operator's CI/CD writes new files into the mounted directory (best practice: write to a temp file and mv for atomic swap). The next tick picks them up.

Two deployment models do NOT use this primitive at runtime, by design:

  • Sealed-bundle prod (e.g. asobi managed cloud) — the bundle is extracted once at boot to an immutable directory; mtime never changes within a container's lifetime. New deploys are container restarts on a new generation. The per-tick stat() is a no-op in this model.
  • Custom script sources (DB, S3, git, etc.) — these belong behind the planned asobi_lua_source behaviour, which will dispatch to either this filesystem implementation or an alternative loader. Until then, custom sources should arrange for files to land on disk and use this primitive, or implement reload outside it.

Operators running self-hosted with high zone counts who want to suppress per-tick stat overhead can set asobi_lua.reload_mode (or the ASOBI_LUA_RELOAD env var the release script reads) to:

  • auto (default) — mtime-poll every tick. Suitable for dev and self-hosted volume-mount setups.
  • off — never reload. Suitable for sealed-bundle prod where code changes are container restarts. The per-tick stat() is skipped.

Summary

Functions

maybe_hot_reload(State)

-spec maybe_hot_reload(map()) -> map().

reload_mode()

-spec reload_mode() -> auto | off.