librl is a small C wrapper layer around raylib, with build targets for:
- Desktop static library (
.a) - WebAssembly module (
lib/librl.js+lib/librl.wasm) - WebAssembly static archive (
.wasm.a)
librl is a binding-oriented runtime layer around raylib that provides stable handles, cross-platform asset loading, and shared runtime plumbing for non-C hosts and embedded scripting runtimes. It is still an active work in progress.
Key goals:
- Handle-based APIs for resources:
- Colors, fonts, models, textures, sprite3d, and camera3d are exposed as
rl_handle_tIDs instead of raw raylib structs. - This keeps bindings simpler and safer by avoiding direct pointer/struct lifetime management in host languages.
- Colors, fonts, models, textures, sprite3d, and camera3d are exposed as
- Thin host/runtime shell:
- Keep platform bootstrap, frame boundaries, and resource ownership on the host side.
- Let higher-level hosts or scripts describe gameplay and per-frame presentation against a stable handle-based API.
- Cached asset systems:
- Loader and resource subsystems are designed for reuse/caching instead of one-shot loads.
- Model/font/color systems keep runtime-owned instances keyed by handles.
- Cross-platform file I/O abstraction:
- Unified loading path across desktop and web builds.
- Web can fetch/store assets in browser-backed storage (IndexedDB).
- Desktop can read/write local files through the same higher-level loader path.
- LRU-backed loader behavior:
rl_loaderuses LRU cache infrastructure to reduce repeated decode/fetch work.- This is especially useful for wasm/browser workflows where fetch/decode churn can be costly.
- URL/path normalization helpers:
- File and URL normalization now share one path utility flow.
- URL normalization preserves scheme/authority/query/fragment and normalizes URL path segments.
- Script-driven iteration:
- The current reference direction is a thin native/wasm host with Lua driving gameplay and frame generation.
- Longer-term, the same host boundary should support evaluating alternate scripting backends without redesigning the core runtime.
librlis moving toward a thin-host model where a native or wasm app owns platform/bootstrap concerns and scripts drive gameplay and per-frame presentation.- The current reference path for that is the Lua module:
- host initializes the Lua module
- Lua script provides
get_config(),init(),update(frame), andshutdown() - Lua emits transient frame commands for draw/audio work
- host drains those commands during its normal frame loop
- Lua-side helper modules now exist for common resource types:
- color
- model
- texture
- sprite2d
- sprite3d
- sound
- music
- camera3d
- font
- The C example is now primarily a thin host shell around that workflow.
src/librl runtime and integration codedeps/wgutils/shared C utility/runtime modules (path/fileio/fetch/etc.)include/public headersbindings/JS/Nim binding helperstests/desktop and web test harnesses
make- Desktop C toolchain (
gcc,ar) - Emscripten SDK on
PATH(emcc,emar) for wasm builds - raylib dependency source compatible with this project (fetched by
make deps)
Build raylib dependency first:
make depsBuild desktop static library:
make desktopBuild wasm JS module:
make wasmBuild wasm static archive:
make wasm_archiveBuild all targets:
makeLua module is now built as a separate module artifact (not compiled into core librl):
make -C modules/lua deps
make -C modules/lua
make -C modules/lua lua_module_test_desktoplibrl modules are linked in at build time (not runtime-loaded).
- Build the module archive you want:
make -C modules/lua desktop- Link it with your app (plus its deps), alongside
librl:
-L./lib -lrl ./modules/lua/lib/librl_lua.a ./modules/lua/deps/liblua/lib/liblua.a- Initialize the module from your app:
const rl_module_api_t *api = NULL;
void *module_state = NULL;
rl_module_host_api_t host = {0}; // set log/alloc/event callbacks as needed
char error[256] = {0};
if (rl_module_init("lua", &host, &api, &module_state, error, sizeof(error)) != 0) {
// handle error
}- Per-frame, call
api->update(module_state, dt)if provided, then call:
rl_module_deinit_instance(api, module_state);on shutdown.
For the current Lua-driven workflow, see:
Run full test suite from repo root:
make testRun test targets directly:
make -C tests test
make -C tests test_desktop
make -C tests test_wasmDebug mode:
make DEV=1 desktop
make DEV=1 wasmClean outputs:
make clean- Desktop archive:
lib/librl.a(orlib/librl_d.awithDEV=1) - Wasm JS loader:
lib/librl.js - Type declarations for JS tooling:
lib/librl.d.ts(copied fromtypes/librl.d.tsduring build) - Wasm archive:
lib/librl.wasm.a(orlib/librl_d.wasm.awithDEV=1)
rl_model_create()requires a ready window/graphics context. If model loading fails, it substitutes a visible placeholder cube.- Keep the README high-level. Exact C APIs, Lua bindings, frame command details, and script-facing runtime surface belong in the docs below.
- API surface documentation: see API.md.
- Binding documentation: see BINDINGS.md.
- Maintainer-focused build/runtime notes: see DEV_NOTES.md.
- So, so many things. See TODO.md for the list of things I'm playing around with.
See LICENSE.