Resources
A resource is a folder of Lua scripts and data that the server runs as one unit: a game mode, a library other resources call, an auth resource that decides who gets in. Each running resource has its own Lua VM, its own registrations (event handlers, commands, timers, exports) and owns what it creates. Starting, stopping and reloading a resource is always all of it at once.
Resource folder
Resources live in the resources/ directory next to server.toml; the server reads it from its working directory and refuses to start without it. Each subdirectory with a valid resource.toml is an installed resource, named after the directory.
resources/
my_mode/
resource.toml
server.lua -- [server] scripts
spawn.lua -- require("spawn")
util/strings.lua -- require("util/strings")
maps/arena.toml -- dlmp.load_map("arena")
ui/index.html -- [client] files
migrations/sqlite/001_init.sql -- [storage.migrations] sqliteA minimal resource is a manifest and one script:
name = "my_mode"
type = "gamemode"
[server]
scripts = ["server.lua"]Manifest
resource.toml is strict: an unknown key or section, a wrong type or a broken rule refuses the resource's start.
| Key | Type | Required | Rules |
|---|---|---|---|
name | string | yes | Not empty, equal to the directory name. A resource with [client] files or [storage] needs a name of [a-z0-9_-], 1..31 bytes. |
type | string | yes | "gamemode" or "library". The runtime treats both alike; the type tells a reader what the resource is for. |
[server] scripts | array of strings | yes, not empty | Paths relative to the resource folder, with no absolute part, . or ... They run in this order in the resource's VM, sharing its globals. |
[server] exports | array of strings | no | Names of global functions other resources may call (see Calling other resources). Not empty, no duplicates. Each must name a function once the scripts have run, or the start fails. |
[server] dependencies | array of strings | no | Resources that must be running before this one starts. Not empty, no duplicates, not the resource itself. |
[client] files | array of strings | no | Files the resource's views load on clients; see Client files and views. |
[storage.migrations] sqlite | array of strings | with [storage] | Migration files of the resource's storage, applied in order. Not empty, no duplicates, relative paths as for scripts. |
Dependencies and start order
A dependency is a start-order contract, not a runtime lock:
- A resource starts only while every resource in its
dependenciesis running; otherwise the start fails and says which dependency is missing. - The startup list is ordered so that dependencies start first; resources with no order between them keep the order of the list. A dependency that is not itself in the startup list, or a cycle, stops the server before anything starts.
- A dependency may be stopped or reloaded while its dependents run; the server logs a warning. Calls into it raise an error until it runs again.
dlmp.importworks only for declared dependencies, so the manifest names every resource a resource calls.
Modules: require
require(name) loads a Lua file of the same resource. It is not Lua's package system: there is no package table, no search path, no C modules and no way to require another resource's files.
"spawn"loadsspawn.lua,"util/strings"loadsutil/strings.lua, relative to the resource folder. A segment uses only letters, digits,_and-;/separates directories. Names are case-sensitive, so match the file name exactly. There is noinit.lualookup and no dotteda.bform.- The file must resolve inside the resource folder, also through symbolic links.
- A module runs once per VM; later calls return the cached value. A module that returns nothing is cached as
true. - A module that raises an error is not cached, and the error reaches the caller of
require. A circularrequire(a->b->a) raises an error naming the chain. requireworks at load time and later from callbacks. Either way the module body runs as load-time code: it may register handlers, commands and timers, but a call that reads or changes the game raises an error. It gets a fresh time limit.- A reload starts with an empty cache.
-- spawn.lua
local M = {}
function M.give_loadout(player)
player:give_weapon(24, 100)
player:set_armor(50)
end
return M-- server.lua
local spawn = require("spawn")
dlmp.add_event_handler("player_spawn", dlmp.root, function(player)
spawn.give_loadout(player)
end)Calling other resources
Resources share no Lua state. One resource calls another through the functions the callee lists in [server] exports and the caller imports with dlmp.import (ADR 0025, ADR 0027).
- An export is a global function of the callee, looked up by name after its scripts ran. A function defined in a module becomes an export only when the main script makes it a global of that name.
dlmp.import(name)raises an error unlessnameis in the caller's[server] dependencies. It returns a proxy whose functions are looked up by name on each call, so the proxy keeps working across a reload of the callee.- A call into a resource that is not running, a name it does not export, or an error inside the callee raises an error at the call site; catch it with
pcallwhere the caller can go on without the answer. - Arguments and results are copied between the VMs. Only plain data crosses:
nil, booleans, numbers, strings and tables of these, nested at most 16 levels deep. Functions, handles and cyclic tables raise an error, so pass a player'sid(), not the player. - The callee runs with its own time limit, and
dlmp.this_resourcenames the callee while it runs. Calls nested more than 16 deep raise an error.
[server]
scripts = ["server.lua"]
dependencies = ["accounts"]local accounts = dlmp.import("accounts")
dlmp.add_event_handler("player_connect", dlmp.root, function(player)
local account = accounts.account_id(player:id())
print(("player %d is account %s"):format(player:id(), tostring(account)))
end)Client files and views
A resource can show web pages on players' screens as views. The server keeps all authority: a page shows what the resource sends and proposes the actions the resource declared (ADR 0049).
[client] fileslists every file a page may load; a client downloads exactly these. Paths, extensions and size limits are in Resource client files. A path or limit violation refuses the start.- The server reads and hashes the files when the resource starts and serves that snapshot until the resource stops, even if the files on disk change. Every start, a reload included, announces a new generation of the files to the players.
dlmp.open_viewopens a view for one player; the stop of the resource closes its views and withdraws its files.
Storage
A resource with [storage.migrations] sqlite = [...] gets its own SQL namespace as dlmp.db; without the section dlmp.db is nil. The server applies the migrations before the resource's scripts run, and every request answers through a callback in a later server step. The model, the limits and the backend rules are in ADR 0054.
Lifecycle
Start
- The server reads and checks the manifest, the scripts, the client files and the migration files.
- Every dependency must be running.
- With
[storage], the resource's namespace opens and its migrations run; the start waits until the queue of the resource's previous run is empty. - A fresh Lua VM gets the sandboxed standard library and the
dlmptable. - The scripts run in manifest order as load-time code: they register handlers, commands, timers and console commands; reading or changing the game raises an error.
- The exports are looked up.
- The client files are announced to the players.
resource_startis raised on the resource'sdlmp.resource_root. Its handlers are the first place the resource may act on the game: create vehicles, set the spawn point, show widgets.
If any step fails, the start is refused and everything it made is taken back: registrations, the elements its resource_start handlers created, its client files. A name that is already running cannot start a second time.
Stop
resource_stopis raised on the resource'sdlmp.resource_root. An error in a handler is logged; the stop goes on.- When the resource is the server's auth resource, every player and every waiting connection is disconnected: with it gone, nobody vouches for them (ADR 0052).
- Everything the resource owns is removed, including what its
resource_stophandlers created; see Elements. - Its event handlers, commands, console commands, timers and exports are revoked. Callbacks still waiting for storage answers are dropped; the writes already queued still run.
- The VM is dropped, and with it every handle and module the resource held.
Reload
A reload is a stop followed by a fresh start from disk: the manifest, the scripts and the client files are read again, and the new run is a new resource with a new VM. Nothing carries over except what the resource wrote to its storage. If the new start fails, the resource stays stopped.
Server shutdown
The console shutdown, Ctrl-C and SIGTERM stop every resource with its resource_stop, each one after the resources that depend on it, so a resource_stop handler can still call its dependencies. The auth resource's stop disconnects nobody here. The server then waits until every storage queue is empty and exits.
Startup list and console
[resources] startup in server.toml names the resources that start with the server, by directory name (default ["demo"]; the environment variable DARQLINEMP_RESOURCES_STARTUP takes a comma-separated list instead). They start in dependency order, and one that is missing or fails to start stops the server. The [auth] resource, when set, must be in this list. The file and its overrides are described in ADR 0050.
Other installed resources start from the server console:
resource list
resource start <name>
resource stop <name>
resource reload <name>A failure there is logged and leaves the other resources running.
