asobi_lua_loader (asobi v0.75.1)
View SourceLoads Lua scripts into a hardened Luerl state.
The state is built on top of luerl:init/0 and then has every dangerous
standard-library entry point cleared:
os.execute,os.exit,os.getenv,os.remove,os.rename,os.tmpnameio(the whole library)dofile,loadfile,load,loadstringpackage(the whole library) — replaced by anasobi_lua-controlledrequire/1so scripts can still split logic across files
require/1 resolves names relative to the directory of the script that
was loaded. Names must match [a-zA-Z_][a-zA-Z0-9_]*(\\.[a-zA-Z_][a-zA-Z0-9_]*)*,
so dotted module paths work (require("bots.chaser") →
<base>/bots/chaser.lua) but parent traversal (..), absolute paths,
and arbitrary characters are rejected. Module results are cached in a
private _ASOBI_LOADED table so repeat require calls return the same
value.
math.random and math.sqrt are overridden to call into the Erlang
rand and math modules respectively — Luerl's defaults are slower
and less deterministic than the BEAM equivalents.
Use init_sandboxed/0 when you need a hardened state with no script
attached (e.g. for evaluating a config.lua manifest); use new/1 to
load a specific script and pin its base directory for require.
Summary
Functions
Collect the Luerl state carried in a bridge state map, every so often.
Types
Functions
Collect the Luerl state carried in a bridge state map, every so often.
Call this once per tick from a bridge that holds a long-lived lua_state.
Luerl never collects such a state on its own, so without this the per-tick
luerl:encode/2 of entities or inputs accumulates in it forever - and
call/4 copies the whole state into its eval worker and back on every
callback, so the cost of a tick grows with everything the state has ever
allocated. Collect on the calling process: it costs no extra copy there, and
luerl:gc/1 runs no Lua code so it cannot hang on a script.
game_state is the one Luerl value asobi holds between callbacks. It is
unreachable from the Lua root set while Erlang is between them, so the
collector would free the tables underneath it and the next luerl:decode/2
on it would crash. It is rooted in a global for the duration of the collection
and unrooted again straight after, so a script never observes the anchor.
Bookkeeping is kept under lua_gc in the same map. Set
{asobi, [{lua_gc, false}]} to turn the collector off entirely.
-spec init_sandboxed() -> dynamic().
-spec new(binary() | string(), non_neg_integer()) -> {ok, dynamic()} | {error, term()}.
-spec new(binary() | string(), non_neg_integer(), pre_install()) -> {ok, dynamic()} | {error, term()}.