The binding generator and CLight IDL
Status: Implemented for the engine’s own generator (
tools/clight_gen/), which produces every binding and the API tables in these docs fromclight.idl. Aclight bindgencommand for mods (other languages,.clidlfiles for services) is Planned.
CLight’s promise of “the same names everywhere” rests on one file: client/clight/idl/clight.idl (with gui.idl, sound.idl and mixins.idl included at its end). Every function, struct, enum, event and payload is declared there once.
What is generated
python tools/clight_gen/clight_gen.py writes:
| Output | For |
|---|---|
client/clight/include/clight.h |
C (the ABI): types, WASM import declarations, _INIT macros |
client/clight/include/clight.hpp |
The header-only C++ wrapper (handle classes, Expected wrappers, free functions per module) |
client/clight/core/generated/* |
The engine side: the native function table and implementation declarations |
client/clight/idl/clight_api.json |
The parsed IDL, read by the other generators |
From the JSON:
| Generator | Output |
|---|---|
tools/clight_gen/backend_lua.py |
The Lua binding (client/clight/lua/generated/lua_bindings.gen.cpp) and LuaLS annotations (sdk/lua/clight.d.lua) |
tools/clight_gen/backend_wasm.py |
The WASM import thunks (client/clight/wasm/generated/) |
tools/docs/gen_api_reference.py |
The reference tables in API function index |
--check on each generator fails when a committed output is stale; tools/docs/check_docs.py fails when a doc page names API that doesn’t exist.
IDL syntax (excerpt)
module level @header("clight/level.h");
fn level_set_block(level: level, pos: block_pos, state: block_state, flags: set_block_flags) -> result @context(TICK) @side(server);
/// "minecraft:overworld"
fn level_dimension(level: level, buf: outstr) -> result @context(ANY);
struct block_pos @value { x: i32, y: i32, z: i32 }
payload block_break { level: level, pos: block_pos, state: block_state, player: entity, tool: item_stack @borrowed, xp: i32 @mut, drop_items: bool @mut }
event "block.break" : block_break @context(TICK) @side(server) @cancellable;
| Construct | Generates |
|---|---|
module <name> @header("...") |
Groups declarations; the module is the C++ namespace and the Lua namespace |
fn name(p: type, ...) -> ret @context(X) @side(Y) @since(x.y) |
cl_<name>, the table entry, the C++ method and free function, the Lua function and method |
struct name @desc { ... } |
A descriptor with struct_size first and CL_<NAME>_INIT from the defaults |
payload / event |
cl_ev_<payload> and CL_EV_<NAME>, clight::ev::<Name> |
handle, alias, enum, flags, const, callback |
The matching C types and constants |
outstr, out T, outarr T |
The two-call string pattern, an out pointer, an out array |
@owned, @borrowed, @mut, @deprecated(...), @lua(skip), @wasm(guest), @wasm(manual) |
Ownership, payload mutability, deprecation, and binding exceptions |
Naming
- C:
cl_<name>(cl_level_set_block). - C++: a method on the first handle with the receiver prefix removed (
Level::set_block), and a free functionclight::<module>::<verb>with the module prefix removed. - Lua:
cl.<namespace>.<verb>mirroring the C name, plus a method on the first handle (Lua 5.4).
ABI guard
The IDL is append only: new functions, fields, enum values and events go at the end. client/clight/idl/abi/clight_abi.snapshot.json records the shipped ABI, and a test (clight_abi_append_only, clight_gen.py --check-compat) fails if an entry is removed, reordered or changed (ABI stability and API versioning).
Planned
clight bindgen for mod authors: bindings for other WebAssembly languages, and .clidl interface files that describe a mod’s own services, events and descriptor layouts so other mods can call them from any language (Services and mod interop).