A small, self-contained Odin library for driving a real browser
CanvasRenderingContext2D in batched mode from WebAssembly.
Instead of calling the Canvas 2D API one method at a time across the WASM↔JS boundary — one
bridge crossing per fillRect / arc / fill, which is expensive at 60fps — you record a whole
frame's worth of draw operations into one packed byte buffer in Odin, then flush it to the
browser with a single foreign call. The JS side walks the buffer and replays each op onto the
context.
This library was extracted from OdinDOM's canvas_cmd package to stand alone: it
contains only the batched canvas draw operations plus the minimal bootstrap needed to create a
canvas and run an animation loop. No general DOM manipulation, no Web Audio, no pixel buffers.
| Path | What it is |
|---|---|
batch/batch.odin |
The command buffer + every draw op (rects, paths, arcs, text, transforms, gradients, shadows, sprites). |
batch/canvas.odin |
Tiny bootstrap: Handle type, get_element_by_id, canvas_create, canvas_get_context, start_animation_loop, add_event_listener. |
web/batchiness.js |
The JS glue: Odin's js_wasm32 runtime hooks (odin_env) plus the batch_env bridge that replays the command buffer. |
docs/ |
A runnable example app (a tree + a swimming duck) — build target for GitHub Pages. |
Requires the Odin compiler and Node (for http-server).
./run.sh # builds the docs example, serves on http://localhost:9000
./build.sh # rebuild the docs WASM only (no server)Then open http://localhost:9000/docs/index.html.
Never open the HTML via
file://— the page fetches its.wasmover HTTP, and the browser blocks that with a CORS error. Always serve over HTTP (./run.sh).
Build flags (library mode, driven by JS-called exports rather than main()):
-target:js_wasm32 -o:size -no-entry-point.
Your app is a package main that imports batch and exports two "c"-convention procs the JS
loader calls:
batchiness_main :: proc "c" ()— runs once after instantiation.batchiness_invoke_callback :: proc "c" (id: u32)— dispatched for every animation frame and registered DOM event, keyed by an app-chosenid.
context = runtime.default_context() must be the first line of every exported "c" proc — it
re-establishes Odin's runtime state at each JS→Odin boundary crossing.
package main
import "base:runtime"
import "../batch"
ctx: batch.Handle
cmd: batch.Buffer
@(export)
batchiness_main :: proc "c" () {
context = runtime.default_context()
canvas := batch.canvas_create(batch.get_element_by_id("app"), 480, 300)
ctx = batch.canvas_get_context(canvas)
batch.start_animation_loop(0) // callback id 0 fires once per frame
}
@(export)
batchiness_invoke_callback :: proc "c" (id: u32) {
context = runtime.default_context()
batch.reset(&cmd) // rewind the buffer
batch.set_fill(&cmd, "#3f8fc4")
batch.fill_rect(&cmd, 20, 20, 200, 120) // record ops...
batch.flush(ctx, &cmd) // ...and replay them in one crossing
}Load it from a page:
<div id="app"></div>
<script src="./batchiness.js"></script>
<script>Batchiness.instantiate("example.wasm");</script>batch.Buffer is a fixed-capacity (1 MiB) preallocated arena, reused every frame — no per-frame
heap churn. The lifecycle is always:
batch.reset(&cmd)at the start of the frame.- Record draw ops (
fill_rect,begin_path/arc/fill,linear_gradient,draw_sprite, …). batch.flush(ctx, &cmd)— one foreign call replays the whole frame.
Detailed artwork (a tree, a character) can be baked once into an offscreen canvas, then blitted cheaply every frame instead of re-encoding its paths:
// once:
batch.bake_begin(&cmd, sprite_id, w, h)
// ... draw the art ...
batch.bake_end(&cmd)
// each frame:
batch.draw_sprite(&cmd, sprite_id, x, y)State (fill/stroke/line width/font/alpha/line cap), rectangles, paths (move/line/arc/ellipse/bezier/
rect/fill/stroke/clip), text (fill_text/stroke_text + synchronous measure_text), transforms
(translate/scale/rotate/set_transform), linear & radial gradients, shadows, and sprites. See
batch/batch.odin for the full op list; the wire format is documented at the top of that file and
must stay in sync with the switch in web/batchiness.js.
MIT — see LICENSE.