WASM using c3
First Steps with C3 and Raylib on WebAssembly
Hi! This is my first blog post on this website, and hopefully, it’s the first of many, as I have some free time at the moment.
Today, I spent a few hours experimenting with the programming language C3 and linking it with a static file from Raylib, a popular library for creating web applications. My goal was to build something interactive and deploy it to the web with minimal hassle.
I’ve set up a repository to link C3 with Raylib and stress-test it using a bunny-rendering benchmark, inspired by this bunnymark benchmark.
Here are some key things to keep in mind when compiling C3 and Raylib to WebAssembly (WASM):
-
Limited WASM Support in C3:
C3 has limited support for compiling to WASM. The standard library isn’t usable in WASM, so any standard functions need to be rewritten or excluded. For example, I attempted to randomly assign colors to a rabbit, but there was no compile-time error, so I had to debug why WASM wasn’t loading properly. In hindsight, I should have known, given that the--link-libc=no
flag was used in the examples.Example command:
c3c compile --reloc=none --target wasm32 -g0 --link-libc=no --no-entry main.c3 raylib.c3
-
Custom Raylib JavaScript File:
Raylib APIs need to be called through a customraylib.js
file. As shown in the command above, there’s no static Raylib file (raylib.a
) linked directly to WASM. Instead, Raylib APIs are accessed through JavaScript using WebAssembly.instantiateStreaming. For example,raylib::init_window
would be called in JavaScript like this:function InitWindow(width, height, title_ptr) { this.ctx.canvas.width = width; this.ctx.canvas.height = height; const buffer = this.wasm.instance.exports.memory.buffer; document.title = cstr_by_ptr(buffer, title_ptr); } const raylibObject = { raylib: { InitWindow: InitWindow }, }; WebAssembly.instantiateStreaming(fetch("main.wasm"), raylibObject).then( (obj) => obj.instance.exports.exported_func(), );
To actaully link it together, I was lucky to find raylib.js this repo (Thanks tsoding!!). I only needed to add a few functions for handling clicks. The file had
RaylibJs
class which need to be turned into object from above example and usedProxy
class to do that. I actaully never seen that API being used outside of this.function make_environment(env) { return new Proxy(env, { get(_target, prop, _receiver) { if (env[prop] !== undefined) { return env[prop].bind(env); } return (...args) => { throw new Error(`NOT IMPLEMENTED: ${prop} ${args}`); }; }, }); } class RaylibJs { // will have all raylib functions ... IsMouseButtonPressed(key) { return this.currentIsMouseButtonPressed == key; } // entrypoints to fetch wasm and start it. start(wasmPath, canvasId) { ... this.wasm = await WebAssembly.instantiateStreaming(fetch(wasmPath), { env: make_environment(this), }); // Call the main functions from wasm object this.wasm.instance.exports.main(); } }
Overall, this was an interesting project to spend a few hours on, and maybe in the future, I’ll explore compiling the C3 standard library to WASM.
Below are the results!