WASM toolchains (C, C++ and others)
Status: Implemented for
wasm32-wasip1with the SDK’s wasi-sdk. The threaded target (wasm32-wasip1-threads),wasm-optand binding generators for other languages are Planned.
C and C++ mods are WebAssembly modules run by Wasmtime, in the same sandbox as every other mod. They are compiled ahead of time with Cranelift on first load and cached as .cwasm files under the game’s cache/clight/ folder, so later launches load them quickly.
Supported targets
| Target | Use |
|---|---|
wasm32-wasip1 |
Every C and C++ mod. One instance per mod; its calls are serialized by the per-mod lock. |
wasm32-wasip1-threads |
Planned, for parallel callbacks (worldgen, sensors) over a shared memory. |
C++ exceptions are off (-fno-exceptions); errors come back as clight::Expected values.
C and C++ with wasi-sdk
clight setup installs the official wasi-sdk (34) into the SDK, and clight build compiles with it. The flags live in one file, clight-sdk/wasm/flags.txt, which clight build and cmake/CLightMod.cmake both read:
| Key | Flags |
|---|---|
| always | --target=wasm32-wasip1 |
c / cxx |
-std=c17 / -std=c++23 -fno-exceptions |
dev / release |
-O1 -g / -O2 -DNDEBUG, both with -fvisibility=hidden |
link |
-mexec-model=reactor -Wl,--export-table -Wl,--gc-sections and --export-if-defined= for clight_mod_api_version, clight_mod_main, clight_mod_unload, clight_guest_alloc, clight_guest_free, clight_mod_save_state, clight_mod_restore_state |
link-release |
-Wl,--strip-debug |
The guest ABI, as clight.h declares it under __wasm__:
- every API function is an import from module
"clight"named like the C function (cl_log,cl_event_subscribe, …); WASI is linked too, with the mod folder mounted read-only at/andmods/<id>/data/read/write at/data, andstdout/stderrgoing to the log. CLIGHT_MOD_ENTRY(fn)© orCLIGHT_MOD(Class)(C++) generates the exportsclight_mod_api_version,clight_mod_main,clight_guest_allocandclight_guest_free(andclight_mixinswhen the module declaresCL_MIXINs). The optional hot reload exportsclight_mod_save_state(buf, cap) -> u32andclight_mod_restore_state(data, len) -> cl_resultcome fromCLIGHT_MOD_HOT_STATE/CLIGHT_MOD_SAVE_STATE/CLIGHT_MOD_RESTORE_STATE(API 1.20).- callbacks you pass to the API (event handlers, commands, behaviour functions) are indices into the exported
__indirect_function_table. - the module is a WASI reactor (
_initializeruns the C/C++ constructors once; there is nomain).
A minimal C mod built this way imports only the clight functions it calls.
Performance expectations
AOT-compiled WebAssembly runs game logic close to native speed. Each host call crosses the sandbox boundary (bounds checks, copying strings and structs), so keep tight loops inside your own code and call the API for results.
Data exchange with the host
- Handles are integers, so they pass for free.
cl_strand struct pointers point into your linear memory. The host reads and writes there with bounds checks.- Event payloads are copied into a per-mod scratch area in your memory before the handler runs, and mutable fields are read back after it returns (a few hundred bytes).
- Host-owned data is never mapped into your memory; strings and arrays the engine returns are copied into buffers you provide.
Other languages
Anything that compiles to wasm32-wasip1, exports the entry functions and calls imports by name can use the ABI (Zig can @cImport("clight.h"), Rust can declare the imports). The three supported languages are C, C++ (both through WebAssembly) and Lua; others have no SDK templates, and generated bindings for them are planned.
Limits and traps
- Linear memory is capped at
limits.memory_mb(default 256 MiB, at most 4096). - Epoch interruption enforces a deadline per call: a call over its limit traps.
- Traps are contained: the call is aborted and logged (with function names in dev builds), and the game keeps running (Permissions and security).