Mod and lifecycle
Status: Implemented. Entry points, the current mod, lifecycle queries and every lifecycle event. Reading the mod’s files (
cl_fs_*, Filesystem and HTTP) and the WebAssembly hot-reload state hand-over (clight_mod_save_state/clight_mod_restore_state, Hot reload) arrived in API 1.20. Mod metadata queries (info,list, version ranges) are Planned.
Function tables: core, mod. Concepts: Mod lifecycle.
Entry points (exports of a WASM mod)
| Export | Required | Signature | Generated by |
|---|---|---|---|
clight_mod_api_version |
yes | uint32_t clight_mod_api_version(void): the CLIGHT_API_VERSION the mod was built against |
CLIGHT_MOD_ENTRY |
clight_mod_main |
yes | cl_result clight_mod_main(void) |
CLIGHT_MOD_ENTRY(fn) calls fn() |
clight_mod_unload |
no | void clight_mod_unload(void) |
CLIGHT_MOD_UNLOAD(fn) |
clight_guest_alloc, clight_guest_free |
yes | The engine allocates strings and arrays it hands to the mod in guest memory | CLIGHT_MOD_ENTRY |
clight_mixins |
no | The CL_MIXIN records of the module |
CLIGHT_MOD_ENTRY when the module declares mixins |
C++ uses CLIGHT_MOD(Class), which defines all of them. Lua mods have no exports: the manifest’s runtime.entry script runs top to bottom.
The loader refuses a mod whose manifest "api" major is newer than the engine (CLL-2003) or older (CLL-2004) (Error codes). Within a major, use cl_core_has for functions added in later minor versions.
The current mod
The binding knows which mod is calling, so no function takes a mod parameter.
| C | C++ | Lua | Returns |
|---|---|---|---|
cl_str cl_mod_id(void) |
clight::mod_id() |
mod.id |
Your mod id |
cl_str cl_mod_version(void) |
clight::mod_version() |
mod.version |
Your version |
bool cl_mod_is_loaded(cl_str id) |
clight::mods::is_loaded(id) |
cl.mod.is_loaded(id) |
Whether another mod is loaded |
cl_mod_config_dir(buf, cap, &n) |
clight::mods::config_dir() |
cl.mod.config_dir() |
<game>/config/<modid>/ (created on demand) |
Lifecycle queries
| C | Description | Lua |
|---|---|---|
cl_phase cl_lifecycle_phase(void) |
CL_PHASE_CONSTRUCT, REGISTER, FROZEN, SETUP, RUNNING, SHUTDOWN |
cl.mod.lifecycle_phase() |
bool cl_registries_frozen(void) |
Blocks and items are frozen | cl.registry.registries_frozen() |
cl_env cl_environment(void) |
CL_ENV_CLIENT or CL_ENV_DEDICATED_SERVER |
cl.mod.environment() |
bool cl_server_running(void) |
A logical server exists in this process | cl.mod.server_running() |
bool cl_is_dev(void) |
Running under clight run (--clight-dev) |
cl.mod.is_dev() |
cl_context cl_current_context(void) |
The thread context of the current call | cl.mod.current_context() |
Lifecycle events
All lifecycle events use the payload cl_ev_lifecycle { cl_event_header header; cl_level level; } (level only for level_*). None can be cancelled.
| Event | C constant | Context | Side |
|---|---|---|---|
lifecycle.register |
CL_EV_LIFECYCLE_REGISTER |
MAIN | both |
lifecycle.registries_frozen |
CL_EV_LIFECYCLE_REGISTRIES_FROZEN |
MAIN | both |
lifecycle.common_setup |
CL_EV_LIFECYCLE_COMMON_SETUP |
MAIN | both |
lifecycle.client_setup |
CL_EV_LIFECYCLE_CLIENT_SETUP |
MAIN | client |
lifecycle.server_setup |
CL_EV_LIFECYCLE_SERVER_SETUP |
MAIN | server (dedicated) |
lifecycle.resources_reloaded |
CL_EV_LIFECYCLE_RESOURCES_RELOADED |
MAIN | client |
lifecycle.data_reloaded |
CL_EV_LIFECYCLE_DATA_RELOADED |
MAIN | server |
lifecycle.server_starting |
CL_EV_LIFECYCLE_SERVER_STARTING |
MAIN | server |
lifecycle.server_started |
CL_EV_LIFECYCLE_SERVER_STARTED |
MAIN | server |
lifecycle.server_stopping |
CL_EV_LIFECYCLE_SERVER_STOPPING |
MAIN | server |
lifecycle.server_stopped |
CL_EV_LIFECYCLE_SERVER_STOPPED |
MAIN | server |
lifecycle.level_load |
CL_EV_LIFECYCLE_LEVEL_LOAD |
MAIN | server |
lifecycle.level_save |
CL_EV_LIFECYCLE_LEVEL_SAVE |
TICK | server |
lifecycle.level_unload |
CL_EV_LIFECYCLE_LEVEL_UNLOAD |
MAIN | server |
lifecycle.mod_reload |
CL_EV_LIFECYCLE_MOD_RELOAD |
MAIN | both |
lifecycle.shutdown |
CL_EV_LIFECYCLE_SHUTDOWN |
MAIN | both |
In singleplayer the server events repeat every time a world is opened.
C++ clight::Mod
namespace clight {
class Mod {
public:
virtual ~Mod() = default;
virtual void on_construct() {} // the mod was loaded: subscribe here
virtual void on_register() {} // lifecycle.register
virtual void on_registries_frozen() {}
virtual void on_common_setup() {}
virtual void on_client_setup() {}
virtual void on_server_setup() {}
virtual void on_server_starting() {}
virtual void on_server_stopped() {}
virtual void on_unload() {} // at shutdown; then every lambda the wrapper keeps is freed
};
}
#define CLIGHT_MOD(Class) /* defines the exports and constructs one Class */
The object lives inside your module, so no C++ class crosses the ABI. Put CLIGHT_MOD in exactly one .cpp file.
Lua
The entry script runs during construct. Lifecycle hooks are ordinary events: cl.on("lifecycle.client_setup", fn).