Chasm is designed to be embedded. The entire runtime is a single header file, include it in your engine, set up the arenas, and call your script's lifecycle functions.
How It Works
When Chasm compiles a script, it generates a C file that:
- Includes
chasm_rt.h - Declares
chasm_on_init(ChasmCtx*),chasm_on_tick(ChasmCtx*, double),chasm_on_draw(ChasmCtx*)(orchasm_main(ChasmCtx*)) - References any
extern fndeclarations you provided as C function calls
Your engine provides the ChasmCtx (which holds the three arenas) and calls those functions at the right times.
Minimal Embedding
#include "chasm_rt.h"
// The arenas, memory blocks the script allocates from
static char frame_mem[16 * 1024 * 1024]; // 16 MB frame arena
static char script_mem[32 * 1024 * 1024]; // 32 MB script arena
static char persist_mem[64 * 1024 * 1024]; // 64 MB persistent arena
int main(void) {
ChasmCtx ctx;
CHASM_CTX_INIT(&ctx,
frame_mem, sizeof(frame_mem),
script_mem, sizeof(script_mem),
persist_mem, sizeof(persist_mem)
);
// Call on_init once
chasm_on_init(&ctx);
// Game loop
while (!should_quit()) {
double dt = get_delta_time();
// Reset frame arena, frees all @frame attrs
chasm_frame_reset(&ctx);
chasm_on_tick(&ctx, dt);
chasm_on_draw(&ctx);
present_frame();
}
return 0;
}
ChasmCtx
ChasmCtx is a plain C struct containing three ChasmArena values, one for each lifetime. The script allocates all its memory from these arenas:
typedef struct {
ChasmArena frame;
ChasmArena script;
ChasmArena persistent;
} ChasmCtx;
The arenas are bump allocators: allocation is a pointer increment, deallocation is resetting the pointer. No malloc, no fragmentation.
Arena Reset
The frame arena must be reset at the start of every tick. The script arena is reset on hot-reload. The persistent arena is never reset:
chasm_frame_reset(&ctx); // call every tick
chasm_script_reset(&ctx); // call on hot-reload
// persistent, never reset
Hot Reload
To hot-reload a script:
- Compile the new
.chasm→ new.c→ new shared library (.dylib/.so) dlclosethe old librarydlopenthe new librarychasm_script_reset(&ctx), resets script attrs- Call
chasm_on_init(&ctx)only if you want to re-run init (usually you don't) - Continue the game loop, the next
chasm_on_tickuses the new code
The Chasm CLI's watch --engine raylib does this automatically via a sentinel file mechanism.
Providing Native Functions
Any function declared as extern fn in a Chasm script must be provided by your engine as a C function. The naming convention is chasm_fnname:
// Chasm script: extern fn draw_rect(x: float, y: float, w: float, h: float, color: int) -> void
// Your engine provides:
void chasm_draw_rect(ChasmCtx *ctx, double x, double y, double w, double h, int64_t color) {
your_engine_draw_rect((float)x, (float)y, (float)w, (float)h, (uint32_t)color);
}
Every C function that Chasm calls receives ChasmCtx* as the first argument, followed by the parameters in order.
Arena Sizes
Choose arena sizes based on your script's needs:
| Arena | Suggested size | Holds |
|---|---|---|
frame | 4–16 MB | Per-tick scratch: candidate lists, temp arrays |
script | 16–64 MB | Game state: entity arrays, strings, active data |
persistent | 32–128 MB | Assets: textures handles, level data, fonts |
Larger arenas cost RAM at startup but allow bigger scripts. The compiler will abort at runtime if an arena overflows, increase the size and recompile.