Skip to content

WASM toolchains (C, C++ and others)

Status: Implemented for wasm32-wasip1 with the SDK’s wasi-sdk. The threaded target (wasm32-wasip1-threads), wasm-opt and 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 / and mods/<id>/data/ read/write at /data, and stdout / stderr going to the log.
  • CLIGHT_MOD_ENTRY(fn) © or CLIGHT_MOD(Class) (C++) generates the exports clight_mod_api_version, clight_mod_main, clight_guest_alloc and clight_guest_free (and clight_mixins when the module declares CL_MIXINs). The optional hot reload exports clight_mod_save_state(buf, cap) -> u32 and clight_mod_restore_state(data, len) -> cl_result come from CLIGHT_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 (_initialize runs the C/C++ constructors once; there is no main).

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_str and 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).