Embedding in C

Hosting Chasm scripts in a custom C engine using chasm_rt.h and the embedding API.

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:

  1. Includes chasm_rt.h
  2. Declares chasm_on_init(ChasmCtx*), chasm_on_tick(ChasmCtx*, double), chasm_on_draw(ChasmCtx*) (or chasm_main(ChasmCtx*))
  3. References any extern fn declarations 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:

  1. Compile the new .chasm → new .c → new shared library (.dylib/.so)
  2. dlclose the old library
  3. dlopen the new library
  4. chasm_script_reset(&ctx), resets script attrs
  5. Call chasm_on_init(&ctx) only if you want to re-run init (usually you don't)
  6. Continue the game loop, the next chasm_on_tick uses 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:

ArenaSuggested sizeHolds
frame4–16 MBPer-tick scratch: candidate lists, temp arrays
script16–64 MBGame state: entity arrays, strings, active data
persistent32–128 MBAssets: 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.