Modules and contracts
Last updated on
How a module is declared, how modules depend on each other, how contracts are published and read, and the Init, Api, Start and Stop phases.
A module is one gameplay concern in its own folder, modules/<id>/. Modules never reach into each other's files; they talk through contracts — a table of functions one module publishes and others ask for by name. The registry (core/shared/registry.lua) and the lifecycle (core/shared/lifecycle.lua) run all of this.
Declaring a module
modules/<id>/module.lua is a shared_script, so it runs on both sides.
local M = OPX.Modules.Declare{
id = 'thing',
side = 'both', -- 'server', 'client' or 'both'
fatal = false, -- true: the resource reports a failed boot if this module fails
requires = { 'character' }, -- hard dependencies
optional = { 'downed' }, -- soft dependencies: only change the order
}| Field | Meaning |
|---|---|
id | Required. Unique string; declaring an id twice raises. |
side | Where the module runs. Default 'both'. On the other side it is marked absent. |
requires | Modules that must be running. If one is missing, disabled or failed, this module is unavailable and never runs. |
optional | Modules that run first when present. Nothing happens if they are missing. |
fatal | true: a failure of this module makes OPX.Modules.Run answer false. |
Declarereturns the module's own table (M). Its other files get it back withOPX.Modules.Get('<id>')and hang their functions on it.- The runtime only reads
Settingsand the four phase functions fromM.
API: OPX.Modules.Declare.
Settings
M.Settings is OPX.Config.MODULES[id], the table set by config/<id>.lua.
Do not copy M.Settings into a file-scope local
The platform runs every shared_script before any server_script. Three config files are server scripts (config/server.lua, config/vehicles.lua, config/theme.lua), so at declare time their module still sees an empty table. The registry re-points M.Settings at the real table once every file has loaded (OPX.Modules.Rebind). Read M.Settings inside your phases.
enabled = false in the config turns the module off: it is marked disabled and its dependants become unavailable.
The lifecycle
Phases run in dependency order (a module runs after everything it requires or lists as optional). A cycle is refused at boot with module dependency cycle: a -> b -> a.
| Phase | Use it to | Rules |
|---|---|---|
M.Init() | Build state, read settings, add tables (OPX.Schema.Add), declare tunables (OPX.Tune.Declare). | Do not call other modules. |
M.Api() | Publish your contract with OPX.Api.Provide. | Contracts may only be published here. |
M.Start() | Register events, commands, scheduler jobs; read the database. | Runs on a thread and may yield. |
M.Stop() | Release what you hold: focus, controls, cameras, jobs. | Runs in reverse order, also for a module that failed half-way. |
- Between
ApiandStartthe server publishes the tunables and applies the schema, soStartis the first phase that may touch the database. - Each phase yields one frame (
Wait(0)) after each module, resetting the client's instruction budget (Two runtimes).
Module states
/opx.modules (server) and /opx.client (client) print one line per module with its state and reason — see diagnostics.
| State | Meaning |
|---|---|
declared | Waiting to run. |
started | All phases ran. |
disabled | enabled = false in its config. |
absent | Its side is the other runtime. |
unavailable | A required module is missing, disabled or failed. The reason names it. |
failed | A phase raised. Reason: <phase> failed: <error>. |
stopped | Stop ran. |
When a module fails, every contract it already published is withdrawn and its dependants are dropped after the phase.
Contracts
A plain table of functions with a name and a version — the only supported way for one module to use another.
-- modules/thing/server/main.lua
function M.Api()
OPX.Api.Provide('thing', 1, {
DoIt = function(source) ... end,
})
end
-- another module
local thing = OPX.Api.Get('thing') -- nil when 'thing' is not running
if thing then thing.DoIt(source) end| Function | Use |
|---|---|
OPX.Api.Provide(name, version, table) | Publish. Only one module may provide a name; a second one raises. |
OPX.Api.Get(name, minimum?) | Read. nil when nobody provides it (or only an older version). For soft dependencies. |
OPX.Api.Require(name, minimum?) | Read or raise. For a module listed in requires. |
OPX.Api.Versions() | Every published contract, name -> version. |
- Contracts live in one Lua VM: server and client each have their own set, and a separate resource cannot call them.
- Each module documents its contract on its
serverandclientpages (module list).
Call OPX.Api.Get when you need it, not at file scope
Contracts exist only after the Api phase. Resolve them in Start or at the moment of use.
Overview
opx_infinity is the whole OPX//77 framework in one Open77 resource — a small core runtime and 36 modules — next to the client library opx_lib.
Events and channels
The three event prefixes opx_infinity uses (opx:net, opx:on, opx:in), who can hear each one, and how to build a name with OPX.Event.