Skip to content

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 from clight.idl. A clight bindgen command for mods (other languages, .clidl files 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 function clight::<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).