WasmCoffee Docs v0.0.12

WasmCoffee Reference Manual

Quick guide to the embedded creative coding library, direct software framebuffers, linear memory intrinsics, and Web host bridge in WasmCoffee.

Overview & Architecture

WasmCoffee is an in-browser development environment and compiler host for code written with Java-inspired syntax, compiled directly to WebAssembly GC (Wasm-GC) binaries.

đź’ˇ Pure Browser Execution

The Odin-based compiler runs in a dedicated Web Worker off the main thread. Your code is compiled to Wasm-GC binary bytes and launched via native WebAssembly.instantiate() with no network calls or server functions.

Auto-Import System

Unlike traditional development environments that require extensive import statements or external build configurations (Maven, Gradle), WasmCoffee bundles a suite of high-performance creative coding, game development, and linear memory utilities directly inside the compiler core.

Whenever your program refers to any embedded class, such as Surface, Color, Vec2i, Rect, Mathf, or Rng, the compiler automatically injects and compiles that type into your final WebAssembly binary.

Execution & Wasm Start Section

Static initializers in WasmCoffee are inserted into a WebAssembly start function and arranged in topological dependency order.

  • Eager Initialization: When your module is instantiated with WebAssembly.instantiate(), all static variables are initialized before any function or export executes.
  • Dead Code Pruning: Classes never reached by your program have their static initializers pruned at compile time. (This is still a work in progress though.)

Surface High-Performance 2D Framebuffer

The Surface class wraps a contiguous region of WebAssembly linear memory as a 32-bit RGBA/ABGR pixel canvas. It provides hardware-speed scanline clearing, clipped rectangle fills, Bresenham line rendering, and surface-to-surface blitting. It's also expanding all the time, as I find gaps, so check back here now and again to see if there's anything new.

Class Declaration

Java
public final class Surface {
    public final int base;     // Linear memory byte address of framebuffer
    public final int width;    // Framebuffer width in pixels
    public final int height;   // Framebuffer height in pixels
    public final int byteSize; // width * height * 4 bytes

    public Surface(int base, int width, int height);
}

Methods

MethodParametersDescription
clear(int color)color: 32-bit packed colorFills entire surface using fast Memory.fill32().
setPixel(int x, int y, int color)x, y, colorSets pixel with boundary bounds clipping.
getPixel(int x, int y)x, yReturns packed 32-bit pixel color, or 0 if out of bounds.
fillRect(int x, int y, int w, int h, int color)x, y, w, h, colorFills a clipped rectangle with fast scanline Memory.fill32().
drawLine(int x0, int y0, int x1, int y1, int color)Coordinates + colorBresenham line rasterizer with clipped pixel writes.
copyTo(Surface dst, int dx, int dy)dst, dx, dyBlits this surface onto dst with clipping.

Example Usage

Java
// Initialize a 600x400 framebuffer past static segments
Surface surf = new Surface(Memory.base(), 600, 400);
surf.clear(Color.fromRgb(0x0A0D14));
surf.fillRect(50, 50, 200, 100, Color.fromRgb(0x4ECDC4));
surf.drawLine(0, 0, 600, 400, Color.WHITE);

Color Palette & ABGR Memory Packing

On little-endian architectures, WebAssembly 32-bit linear memory integers map to ImageData as 0xAABBGGRR (Alpha in highest byte, Red in lowest byte). The Color class bridges standard human-friendly RGB/RGBA representations and hex literals with the native blitter format.

Constants

Color.BLACK (0xFF000000), Color.WHITE (0xFFFFFFFF), Color.RED (0xFF0000FF), Color.GREEN (0xFF00FF00), Color.BLUE (0xFFFF0000), Color.YELLOW, Color.CYAN, Color.MAGENTA, Color.TRANSPARENT.

Methods

MethodParametersReturnDescription
rgb(int r, int g, int b)0..255 channelsintPacks RGB channels with 100% opacity (alpha=255).
rgba(int r, int g, int b, int a)0..255 channelsintPacks RGBA channels into 32-bit integer.
fromRgb(int rgbHex)0xRRGGBB hexintConverts 24-bit hex literal (e.g. 0xFF5533).
fromRgba(int rgbaHex)0xRRGGBBAA hexintConverts 32-bit hex literal.
red(int c), green(int c), blue(int c), alpha(int c)int cintExtracts 0..255 channel value.
lerp(int c1, int c2, double t)Colors + factor 0..1intPer-channel linear interpolation.

Vec2i 2D Integer Vector Record

A lightweight, immutable record representing 2D integer coordinates. Ideal for tilemaps, grid positions, discrete cell offsets, and velocity vectors.

Java
public record Vec2i(int x, int y) {
    public Vec2i add(Vec2i o); // Returns new Vec2i(x + o.x, y + o.y)
    public Vec2i sub(Vec2i o); // Returns new Vec2i(x - o.x, y - o.y)
}

Vec2d 2D Double Vector Record

An immutable record for floating-point 2D physics, forces, velocity, and distance calculations.

MethodDescription
add(Vec2d o) / sub(Vec2d o)Vector addition and subtraction.
mul(double scalar)Scalar multiplication.
dot(Vec2d o)Dot product of two vectors.
distSq(Vec2d o) / dist(Vec2d o)Squared and Euclidean distance between points.
length() / normalize()Vector magnitude and unit direction vector.

Rect 2D Integer Bounding Box Record

An immutable record for axis-aligned bounding boxes (AABB), spatial bounds checks, and collision detection.

Java
public record Rect(int x, int y, int width, int height) {
    public int minX(); public int maxX();
    public int minY(); public int maxY();
    public boolean contains(int px, int py);
    public boolean intersects(Rect other);
    public static Rect clampToBounds(int cx, int cy, int bound, int maxW, int maxH);
}

Mathf Creative Coding & Game Math

A mathematics utility class containing trigonometric constants and common game-loop calculations.

MemberSignatureDescription
clampclamp(double val, double min, double max)
clamp(int val, int min, int max)
Clamps value within range [min, max].
lerplerp(double a, double b, double t)Linear interpolation: a + (b - a) * t.
normalizeAnglenormalizeAngle(double angle)Wraps radian angle into [-π, π].
degToRad / radToDegAngle conversionMultiplies by π/180 or 180/π.
PI, TWO_PI, HALF_PIdouble constantsPrecomputed circle radian constants.

Rng Random numbers, we need them

A seedable pseudo-random number generator designed for deterministic numbers, particle generation, and procedural generation without heap allocations. I would not use this for anything where you need real random numbers, but, for games and fun projects, it's pretty handy.

Java
Rng rng = new Rng(System.nanoTime());
int dice = rng.nextInt(6) + 1;     // 1 to 6
double angle = rng.range(0.0, Mathf.TWO_PI); // 0 to 2*PI

Integer & Long Bit-Twiddling Intrinsics

Integer and Long bit manipulation methods compile directly into native WebAssembly hardware instructions.

MethodWasm InstructionDescription
Integer.bitCount(int i)
Long.bitCount(long i)
i32.popcnt
i64.popcnt
Returns the number of one-bits in two's complement binary representation (population count).
Integer.numberOfLeadingZeros(int i)
Long.numberOfLeadingZeros(long i)
i32.clz
i64.clz
Returns the number of zero bits preceding the highest-order one-bit (count leading zeros).
Integer.numberOfTrailingZeros(int i)
Long.numberOfTrailingZeros(long i)
i32.ctz
i64.ctz
Returns the number of zero bits following the lowest-order one-bit (count trailing zeros).
Integer.rotateLeft(int i, int distance)
Long.rotateLeft(long i, int distance)
i32.rotl
i64.rotl
Rotates the two's complement binary representation left by the specified bit distance.
Integer.rotateRight(int i, int distance)
Long.rotateRight(long i, int distance)
i32.rotr
i64.rotr
Rotates the two's complement binary representation right by the specified bit distance.
Integer.highestOneBit(int i)
Long.highestOneBit(long i)
Intrinsic helperReturns a value with at most a single one-bit, in the position of the highest-order one-bit.
Integer.lowestOneBit(int i)
Long.lowestOneBit(long i)
Intrinsic helperReturns a value with at most a single one-bit, in the position of the lowest-order one-bit (i & -i).
Integer.toBinaryString(int i)
Integer.toHexString(int i)
In-module formatterConverts integer to unsigned binary or hexadecimal string representation.

Constant Folding in switch Labels

Compile-time constant expressions on static final int fields as well as standard wrapper constants (such as Integer.MAX_VALUE and Integer.MIN_VALUE) fold at compile time and can be used directly as case labels in both switch statements and modern switch expressions:

Java
static final int FLAG_READ = 1, FLAG_WRITE = 2;

String describe(int mask) {
    return switch (mask) {
        case FLAG_READ | FLAG_WRITE -> "read-write";
        case Integer.MAX_VALUE -> "max-value";
        default -> "custom: " + Integer.toBinaryString(mask);
    };
}

Memory.* WebAssembly Linear Memory Intrinsics

Direct access to WebAssembly linear memory without method call overhead. Calls to Memory.* compile directly into raw Wasm load/store opcodes (i32.load, i32.store, memory.fill).

IntrinsicDescription
Memory.base()Returns safe starting byte offset past all static segments and the 1 MiB boundary scratch band (≥ 1 MiB).
Memory.fill32(int addr, int count, int val)Fills count consecutive 32-bit words with val.
Memory.put32(int addr, int val) / Memory.get32(int addr)Stores/loads a 32-bit word at linear memory byte address.
Memory.put8(int addr, int val) / Memory.get8(int addr)Stores/loads an 8-bit unsigned byte.
Memory.put16(int addr, int val) / Memory.get16(int addr)Stores/loads a 16-bit halfword.
Memory.fill(int dest, int val, int count)Byte-level memory fill.

Direct Framebuffer Graphics ABI

To activate direct linear-memory framebuffer rendering, export the following methods from your main class:

Java
public class MyGame {
    public static int wasm_get_pixels() { return Memory.base(); }
    public static int wasm_get_width()  { return 600; }
    public static int wasm_get_height() { return 400; }

    public static void wasm_update(double dt) {
        // Called every frame (60 FPS) before the host blits pixels to <canvas>
    }

    // Optional input hooks:
    public static void wasm_click(int x, int y) {}
    public static void wasm_pointer_down(int x, int y) {}
    public static void wasm_pointer_move(int x, int y) {}
    public static void wasm_pointer_up() {}
}

The host runtime creates a zero-copy ImageData view over the linear memory buffer and renders it via ctx.putImageData(imageData, 0, 0) on every animation frame.

Keyboard Input & Sound

Connect keyboard events and sound effects to your game using the Web host bridge imports:

Java
final class Web {
    @Import(module = "web") static native void bindKeys(String handler);
    @Import(module = "web") static native String keyCode(int eventId);
    @Import(module = "web") static native void playSound(int soundId);
}

public class Main {
    public static void onKey(int eventId, boolean down) {
        String key = Web.keyCode(eventId);
        if (down && key.equals("ArrowUp")) {
            Web.playSound(1); // 1 = high blip, -1 = low thud
        }
    }

    public static void main(String[] args) {
        Web.bindKeys("onKey");
    }
}

Web Host Bridge API Reference

The Web class provides direct bindings to the browser host environment via the @Import(module = "web") ABI. It enables full access to HTML5 Canvas 2D hardware-accelerated vector drawing, DOM element manipulation, keyboard and pointer events, audio synthesis, monotonic performance timers, and local storage.

đź’ˇ Handle-Based Object Protocol

Host objects (DOM elements, Canvas 2D contexts, linear and radial gradients) are represented to your program as opaque int handles. Handles 1 and 2 are reserved for document and window. A lookup that does not find an object returns handle 0 (null).

Complete Web Bridge Declarations

Copy and paste this declaration into your project (or import individual methods as needed) to communicate with the browser host:

Web.java
final class Web {
    // --- DOM & Elements ---
    @Import(module = "web") static native int byId(String id);
    @Import(module = "web") static native int createElement(String tag);
    @Import(module = "web") static native void appendChild(int parent, int child);
    @Import(module = "web") static native void setStyle(int el, String prop, String val);
    @Import(module = "web") static native void setInnerText(int el, String text);
    @Import(module = "web") static native void addEventListener(int el, String type, String handler, int arg);

    // --- Canvas 2D Context & Transformations ---
    @Import(module = "web") static native int getContext(int canvas, String kind);
    @Import(module = "web") static native void save(int ctx);
    @Import(module = "web") static native void restore(int ctx);
    @Import(module = "web") static native void translate(int ctx, double x, double y);
    @Import(module = "web") static native void rotate(int ctx, double angle);
    @Import(module = "web") static native void scale(int ctx, double sx, double sy);
    @Import(module = "web") static native void setTransform(int ctx, double a, double b, double c, double d, double e, double f);
    @Import(module = "web") static native void resetTransform(int ctx);
    @Import(module = "web") static native void setGlobalAlpha(int ctx, double alpha);

    // --- Rectangles & Screen Clearing ---
    @Import(module = "web") static native void fillRect(int ctx, double x, double y, double w, double h);
    @Import(module = "web") static native void strokeRect(int ctx, double x, double y, double w, double h);
    @Import(module = "web") static native void clearRect(int ctx, double x, double y, double w, double h);
    @Import(module = "web") static native void rect(int ctx, double x, double y, double w, double h);

    // --- Path Construction & Curves ---
    @Import(module = "web") static native void beginPath(int ctx);
    @Import(module = "web") static native void closePath(int ctx);
    @Import(module = "web") static native void moveTo(int ctx, double x, double y);
    @Import(module = "web") static native void lineTo(int ctx, double x, double y);
    @Import(module = "web") static native void arc(int ctx, double x, double y, double r, double a0, double a1);
    @Import(module = "web") static native void ellipse(int ctx, double x, double y, double rx, double ry, double rot, double a0, double a1);
    @Import(module = "web") static native void quadraticCurveTo(int ctx, double cx, double cy, double x, double y);
    @Import(module = "web") static native void bezierCurveTo(int ctx, double c1x, double c1y, double c2x, double c2y, double x, double y);
    @Import(module = "web") static native void fill(int ctx);
    @Import(module = "web") static native void stroke(int ctx);

    // --- Styles, Line Attributes & Gradients ---
    @Import(module = "web") static native void setFillStyle(int ctx, String css);
    @Import(module = "web") static native void setStrokeStyle(int ctx, String css);
    @Import(module = "web") static native void setLineWidth(int ctx, double w);
    @Import(module = "web") static native void setLineCap(int ctx, String cap);
    @Import(module = "web") static native void setLineJoin(int ctx, String join);
    @Import(module = "web") static native void setLineDash(int ctx, double a, double b);
    @Import(module = "web") static native void setShadowColor(int ctx, String color);
    @Import(module = "web") static native void setShadowBlur(int ctx, double blur);
    @Import(module = "web") static native int createLinearGradient(int ctx, double x0, double y0, double x1, double y1);
    @Import(module = "web") static native int createRadialGradient(int ctx, double x0, double y0, double r0, double x1, double y1, double r1);
    @Import(module = "web") static native void addColorStop(int grad, double offset, String color);
    @Import(module = "web") static native void setFillStyleGradient(int ctx, int grad);
    @Import(module = "web") static native void setStrokeStyleGradient(int ctx, int grad);

    // --- Text & Typography ---
    @Import(module = "web") static native void setFont(int ctx, String font);
    @Import(module = "web") static native void setTextAlign(int ctx, String align);
    @Import(module = "web") static native void setTextBaseline(int ctx, String baseline);
    @Import(module = "web") static native void fillText(int ctx, String text, double x, double y);
    @Import(module = "web") static native void strokeText(int ctx, String text, double x, double y);
    @Import(module = "web") static native double measureText(int ctx, String text);

    // --- Animation, Events, Storage & Audio ---
    @Import(module = "web") static native void requestFrame(String exportName);
    @Import(module = "web") static native void bindKeys(String exportName);
    @Import(module = "web") static native void bindPointer(String exportName);
    @Import(module = "web") static native String keyCode(int eventId);
    @Import(module = "web") static native double now();
    @Import(module = "web") static native void playSound(int soundId);
    @Import(module = "web") static native void setThrust(boolean on);
    @Import(module = "web") static native String storageGet(String key);
    @Import(module = "web") static native void storageSet(String key, String value);
    @Import(module = "web") static native void drawTileColumn(int ctx, int tile, int texX, double dx, double dy, double dw, double dh, double shade);
}

DOM & Handle Management

Retrieve and manipulate browser elements directly from your program:

MethodReturnDescription
byId(String id)intFinds a DOM element by its ID attribute (e.g. "game", "fps"). Returns an integer handle, or 0 if not found.
createElement(String tag)intCreates a new DOM element with the specified tag name (e.g. "button", "div") and appends it to the stage container. Returns its handle.
appendChild(int parent, int child)voidAppends the child element handle to the parent element handle in the DOM hierarchy.
setStyle(int el, String prop, String val)voidSets an inline CSS style property on the specified element (e.g. setStyle(btn, "color", "#4ecdc4")).
setInnerText(int el, String text)voidSets the plain text content (innerText) of the specified DOM element.
addEventListener(int el, String type, String handler, int arg)voidAttaches a DOM event listener (e.g. "click") to an element. When fired, calls the exported program method named handler, passing arg. Cleaned up on Stop.

Canvas 2D Context & Transformations

Acquire rendering contexts and manage the 2D coordinate transformation matrix stack:

MethodReturnDescription
getContext(int canvas, String kind)intObtains a 2D rendering context handle for the given canvas element (pass "2d"). Returns 0 on failure.
save(int ctx)voidPushes the current state of the drawing context (transformations, styles, clip, font) onto the state stack.
restore(int ctx)voidPops the most recently saved drawing state from the stack, restoring all attributes and matrices.
translate(int ctx, double x, double y)voidTranslates the canvas coordinate origin by (x, y) units.
rotate(int ctx, double angle)voidRotates the canvas coordinate space clockwise around the current origin by angle radians.
scale(int ctx, double sx, double sy)voidScales the canvas coordinate space horizontally by sx and vertically by sy.
setTransform(int ctx, double a, double b, double c, double d, double e, double f)voidSets the 2D affine transformation matrix directly to [a c e; b d f; 0 0 1].
resetTransform(int ctx)voidResets the transformation matrix to the identity matrix.
setGlobalAlpha(int ctx, double alpha)voidSets the global opacity applied to subsequent drawing operations (clamped between 0.0 and 1.0).

Rectangles & Screen Clearing

Direct rectangle rendering operations:

MethodReturnDescription
fillRect(int ctx, double x, double y, double w, double h)voidDraws a filled rectangle at (x, y) with dimensions w Ă— h using the current fill style.
strokeRect(int ctx, double x, double y, double w, double h)voidOutlines a rectangle at (x, y) with dimensions w Ă— h using the current stroke style and line width.
clearRect(int ctx, double x, double y, double w, double h)voidClears the specified rectangular area to fully transparent black (rgba(0,0,0,0)).
rect(int ctx, double x, double y, double w, double h)voidAdds a closed rectangular sub-path at (x, y) with size w Ă— h to the current path.

Path Construction & Curves

Vector path construction, Bézier curves, arcs, and filling/stroking:

MethodReturnDescription
beginPath(int ctx)voidStarts a new path by emptying the current list of sub-paths.
closePath(int ctx)voidDraws a straight line from the current position back to the beginning of the current sub-path.
moveTo(int ctx, double x, double y)voidMoves the starting point of a new sub-path to coordinates (x, y) without drawing.
lineTo(int ctx, double x, double y)voidConnects the last point in the sub-path to coordinates (x, y) with a straight line.
arc(int ctx, double x, double y, double r, double a0, double a1)voidAdds a circular arc centered at (x, y) with radius r from start angle a0 to end angle a1 (in radians).
ellipse(int ctx, double x, double y, double rx, double ry, double rot, double a0, double a1)voidAdds an elliptical arc centered at (x, y) with radii rx, ry, rotated by rot radians, from a0 to a1.
quadraticCurveTo(int ctx, double cx, double cy, double x, double y)voidAdds a quadratic Bézier curve from the current pen position to (x, y) using control point (cx, cy).
bezierCurveTo(int ctx, double c1x, double c1y, double c2x, double c2y, double x, double y)voidAdds a cubic Bézier curve from the current pen position to (x, y) using control points (c1x, c1y) and (c2x, c2y).
fill(int ctx)voidFills the active or given path with the current fill style using the non-zero winding rule.
stroke(int ctx)voidStrokes the outlines of the active or given path with the current stroke style and line properties.

Styles, Line Attributes & Gradients

Coloring, line caps, dash patterns, drop shadows, and linear/radial gradient handles:

MethodReturnDescription
setFillStyle(int ctx, String css)voidSets the solid color or CSS style used for filling shapes (e.g. "#4ecdc4", "rgba(255, 0, 0, 0.5)").
setStrokeStyle(int ctx, String css)voidSets the solid color or CSS style used for stroking shape outlines.
setLineWidth(int ctx, double w)voidSets the thickness of stroked lines in canvas pixel units.
setLineCap(int ctx, String cap)voidSets the shape used to draw line endpoints ("butt", "round", or "square").
setLineJoin(int ctx, String join)voidSets the shape used to join two line segments ("miter", "round", or "bevel").
setLineDash(int ctx, double a, double b)voidSets the dashed line pattern with segment length a and gap length b (pass b < 0 for uniform pattern).
setShadowColor(int ctx, String color)voidSets the color of the drop shadow effect (e.g. "rgba(0, 0, 0, 0.4)").
setShadowBlur(int ctx, double blur)voidSets the level of Gaussian blur applied to shadows (in pixels).
createLinearGradient(int ctx, double x0, double y0, double x1, double y1)intCreates a linear gradient handle along the line from (x0, y0) to (x1, y1). Returns gradient handle.
createRadialGradient(int ctx, double x0, double y0, double r0, double x1, double y1, double r1)intCreates a radial gradient handle between circle (x0, y0, r0) and circle (x1, y1, r1). Returns gradient handle.
addColorStop(int grad, double offset, String color)voidAdds a color stop to a gradient handle at offset (from 0.0 to 1.0).
setFillStyleGradient(int ctx, int grad)voidSets the canvas context's fill style to the specified gradient handle.
setStrokeStyleGradient(int ctx, int grad)voidSets the canvas context's stroke style to the specified gradient handle.

Text & Typography

Render vector text and measure glyph bounding widths:

MethodReturnDescription
setFont(int ctx, String font)voidSets the current font properties using standard CSS syntax (e.g. "bold 18px 'JetBrains Mono', monospace").
setTextAlign(int ctx, String align)voidSets horizontal text alignment: "left", "right", "center", "start", or "end".
setTextBaseline(int ctx, String baseline)voidSets vertical text baseline: "top", "middle", "bottom", "alphabetic", or "hanging".
fillText(int ctx, String text, double x, double y)voidDraws filled text characters at position (x, y) using the current fillStyle and font.
strokeText(int ctx, String text, double x, double y)voidDraws stroked glyph outlines for text at (x, y) using current strokeStyle and lineWidth.
measureText(int ctx, String text)doubleMeasures and returns the rendered width of the text string in pixels under the current font.

Animation Loop, Events, Audio & Platform

Frame scheduling, input dispatch, sound, timers, and storage persistence:

MethodReturnDescription
requestFrame(String exportName)voidHooks an exported zero-argument method into requestAnimationFrame. Invoked at up to 60 FPS.
bindKeys(String exportName)voidBinds keyboard events to an exported method with signature void handler(int eventId, boolean isDown).
bindPointer(String exportName)voidBinds pointer events to an exported method with signature void handler(int kind, double x, double y) (kind: 0=move, 1=down, 2=up).
keyCode(int eventId)StringResolves a keyboard event integer handle to its standard key name (e.g. "ArrowUp", "KeyW", "Space", "Enter").
now()doubleReturns a high-resolution monotonic timestamp in milliseconds from performance.now().
playSound(int soundId)voidTriggers the built-in Web Audio retro sound synthesizer (1 = high pickup chirp, 0 = bounce click, -1 = game-over thud).
setThrust(boolean on)voidSignals engine/thrust state to the host runtime. Inert in standard IDE; usable by custom host listeners.
storageGet(String key)StringRetrieves a string value stored under key in the browser's localStorage (returns null if unset).
storageSet(String key, String value)voidPersists a key-value pair into the browser's localStorage.
drawTileColumn(int ctx, int tile, int texX, double dx, double dy, double dw, double dh, double shade)voidOptimized raycaster slice renderer: blits a textured vertical column with depth shading directly onto canvas 2D.

Audio Synthesizer

WasmCoffee includes a built-in Web Audio synthesizer. Trigger retro sound effects without loading audio asset files:

Sound IDToneTypical Use Case
Web.playSound(1)High resonant chirp / blipScoring points, picking up items, success.
Web.playSound(0)Mid click / tapPaddle bounce, button click, step.
Web.playSound(-1)Low abrasive buzz / thudGame over, ball lost, wall collision.

Batched Canvas 2D: Batch & BatchCanvas

For hardware-accelerated Canvas 2D rendering without per-draw-call host crossing overhead, WasmCoffee embeds the Batch and BatchCanvas library. Wire-compatible with the batchiness protocol, it records draw calls into a compact linear memory command stream and flushes the entire frame to the browser in a single FFI crossing per frame.

⚡
Zero-Import Standard Library: No @Import declarations needed. As soon as your Java code references Batch or BatchCanvas, the compiler injects the full implementation automatically.

Batch Architecture & Command Stream

Standard Canvas 2D interop invokes JavaScript FFI calls for every rectangle, path segment, or style change. Under Batch, drawing operations pack opcodes and little-endian data (floats, ints, UTF-8 strings) into an internal arena buffer (default 1 MiB at Memory.base()). A single call to batch.flush(ctx) streams the buffer to the host runtime, which replays the opcodes directly on the canvas context.

Batch Methods Reference

Method SignatureCategoryDescription
new Batch()LifecycleConstructs a batch buffer using Memory.base() with default 1 MiB capacity.
new Batch(int base, int capacity)LifecycleConstructs a batch buffer with explicit memory offset and capacity in bytes.
void reset()LifecycleResets command stream pointer to 0 and clears overflow status for a new frame.
void flush(int ctx)LifecycleReplays all recorded draw commands onto the canvas context handle in one FFI call.
void setFill(String color)StateSets current fill style (e.g. "#4ecdc4", "rgba(10,13,20,0.5)").
void setStroke(String color)StateSets current stroke outline style.
void setLineWidth(float w)StateSets line width in pixels.
void setFont(String font)StateSets font string (e.g. "bold 13px 'Inter', sans-serif").
void setTextAlign(String align)StateSets text alignment ("left", "center", "right").
void setTextBaseline(String baseline)StateSets text baseline ("top", "middle", "bottom", "alphabetic").
void setGlobalAlpha(float alpha)StateSets global alpha transparency (0.0 to 1.0).
void setLineCap(String cap)StateSets line cap style ("butt", "round", "square").
void fillRect(float x, float y, float w, float h)PrimitivesFills rectangle at (x, y) with dimensions (w, h).
void strokeRect(float x, float y, float w, float h)PrimitivesStrokes rectangle outline at (x, y) with dimensions (w, h).
void clearRect(float x, float y, float w, float h)PrimitivesClears rectangular canvas region to transparent black.
void beginPath() / closePath()PathsBegins a new sub-path or closes current sub-path back to starting point.
void moveTo(float x, float y)PathsMoves path pen to point without drawing.
void lineTo(float x, float y)PathsAdds straight line segment from current pen position to (x, y).
void arc(float x, float y, float r, float a0, float a1)PathsAdds circular arc centered at (x, y) with radius r between angles a0 and a1.
void ellipse(float x, float y, float rx, float ry, float rot, float a0, float a1)PathsAdds elliptical arc with radii (rx, ry) and rotation angle rot.
void bezierCurveTo(float cx1, float cy1, float cx2, float cy2, float x, float y)PathsAdds cubic Bézier curve using two control points.
void fill() / stroke() / clip()PathsFills current path, strokes current path outline, or applies clipping region.
void fillText(String text, float x, float y)TextRenders filled text string at coordinates (x, y).
void strokeText(String text, float x, float y)TextRenders stroked text outline at coordinates (x, y).
void save() / restore()TransformsPushes or pops canvas 2D matrix transformation state.
void translate(float x, float y)TransformsTranslates origin by (x, y).
void scale(float x, float y)TransformsScales coordinate system by factors (x, y).
void rotate(float a)TransformsRotates coordinate system clockwise by angle a (in radians).
void linearGradient(int id, float x0, float y0, float x1, float y1)GradientsCreates linear gradient identified by id between points (x0, y0) and (x1, y1).
void radialGradient(int id, float x0, float y0, float r0, float x1, float y1, float r1)GradientsCreates radial gradient identified by id between two circles.
void addColorStop(int id, float offset, String color)GradientsAdds color stop at fractional offset (0.0 to 1.0) on gradient id.
void useGradientFill(int id) / useGradientStroke(int id)GradientsSets current fill or stroke style to gradient id.
void setShadow(String color, float blur)ShadowsEnables drop shadow / glow with specified CSS color and blur radius.
void clearShadow()ShadowsDisables drop shadows.
void bakeBegin(int id, int w, int h)SpritesRedirects subsequent draw operations into offscreen sprite canvas id of size w × h.
void bakeEnd()SpritesFinishes baking and restores rendering target to primary canvas context.
void drawSprite(int id, float dx, float dy)SpritesBlits baked offscreen sprite id onto canvas at destination (dx, dy).
void drawSpriteScaled(int id, float dx, float dy, float dw, float dh)SpritesBlits baked sprite id scaled to destination dimensions (dw, dh).
void drawSpriteSub(int id, float sx, float sy, float sw, float sh, float dx, float dy, float dw, float dh)SpritesBlits sub-rectangle of baked sprite id to destination rectangle.

BatchCanvas Bootstrap & Animation Helpers

BatchCanvas provides static utility methods for creating canvases, acquiring contexts, starting the animation loop, registering event listeners, and high-resolution timing:

Method SignatureReturnDescription
BatchCanvas.create(int parent, int width, int height)intCreates an HTML5 canvas element with specified dimensions and returns its integer handle.
BatchCanvas.getContext(int canvas)intReturns the 2D rendering context integer handle for the canvas.
BatchCanvas.getElementById(String id)intFinds a DOM element by id and returns its integer handle (0 if not found).
BatchCanvas.startAnimationLoop(int cbId)voidStarts a 60 FPS requestAnimationFrame loop dispatching to batchiness_invoke_callback(cbId).
BatchCanvas.addEventListener(int elem, String event, int cbId)voidAttaches a DOM event listener that dispatches callback id cbId when triggered.
BatchCanvas.log(String msg)voidLogs message string to the IDE terminal and console output.
BatchCanvas.now()doubleReturns high-resolution monotonic timestamp from performance.now() in milliseconds.
BatchCanvas.measureText(String font, String text)floatMeasures and returns the rendered pixel width of text string using the specified font.

Tutorial: Building a Bouncing Framebuffer Block

Here are two complete, standalone implementations demonstrating delta-time motion physics, wall collision bouncing, and the Direct Framebuffer ABI in WasmCoffee.

1. Classic Direct Fields (BouncingBlock.java)

Lightweight, procedural approach using primitive state variables and direct bounds checks:

BouncingBlock.java
public class BouncingBlock {
    static final int W = 600, H = 400, SIZE = 24;
    static Surface surf;
    static double x = 100.0, y = 100.0;
    static double vx = 180.0, vy = 130.0; // Pixels per second

    public static int wasm_get_pixels() { return Memory.base(); }
    public static int wasm_get_width()  { return W; }
    public static int wasm_get_height() { return H; }

    public static void wasm_update(double dt) {
        surf.clear(Color.fromRgb(0x0C1017));

        // Advance position scaled by delta time
        x += vx * dt;
        y += vy * dt;

        // Bounce off horizontal boundaries
        if (x <= 0) {
            x = 0;
            vx = -vx;
        } else if (x + SIZE >= W) {
            x = W - SIZE;
            vx = -vx;
        }

        // Bounce off vertical boundaries
        if (y <= 0) {
            y = 0;
            vy = -vy;
        } else if (y + SIZE >= H) {
            y = H - SIZE;
            vy = -vy;
        }

        // Draw block
        surf.fillRect((int) x, (int) y, SIZE, SIZE, Color.fromRgb(0x52E3C2));
    }

    public static void main(String[] args) {
        surf = new Surface(Memory.base(), W, H);
    }
}

2. Modern Java Records & Vector Math (ModernBouncer.java)

Idiomatic modern Java leveraging an immutable record, WasmCoffee's built-in Vec2d vector arithmetic (pos.add(vel.mul(dt))), and functional updates:

ModernBouncer.java
record Block(Vec2d pos, Vec2d vel, int size, int color) {
    Block move(double dt, int maxX, int maxY) {
        Vec2d nextPos = pos.add(vel.mul(dt));
        double vx = vel.x(), vy = vel.y();
        double px = nextPos.x(), py = nextPos.y();

        if (px <= 0.0) {
            px = 0.0;
            vx = -vx;
        } else if (px + size >= maxX) {
            px = maxX - size;
            vx = -vx;
        }

        if (py <= 0.0) {
            py = 0.0;
            vy = -vy;
        } else if (py + size >= maxY) {
            py = maxY - size;
            vy = -vy;
        }

        return new Block(new Vec2d(px, py), new Vec2d(vx, vy), size, color);
    }

    void draw(Surface surf) {
        surf.fillRect((int) pos.x(), (int) pos.y(), size, size, color);
    }
}

public class ModernBouncer {
    static final int W = 600, H = 400;
    static Surface surf;
    static Block block = new Block(
        new Vec2d(100.0, 100.0),
        new Vec2d(180.0, 130.0),
        24,
        Color.fromRgb(0x52E3C2)
    );

    public static int wasm_get_pixels() { return Memory.base(); }
    public static int wasm_get_width()  { return W; }
    public static int wasm_get_height() { return H; }

    public static void wasm_update(double dt) {
        surf.clear(Color.fromRgb(0x0C1017));
        block = block.move(dt, W, H);
        block.draw(surf);
    }

    public static void main(String[] args) {
        surf = new Surface(Memory.base(), W, H);
    }
}

Tutorial: Batched 2D Canvas Graphics with Batch

This tutorial demonstrates how to build a high-performance 60 FPS animation utilizing Batch, BatchCanvas, offscreen sprite baking, linear gradients, and drop shadows with a single FFI flush per frame.

Key Architectural Principles

  • Bootstrap in main(): Create the canvas, acquire the 2D context handle, bake offscreen sprites once, and start the animation loop.
  • Callback Contract: Declare public static void batchiness_invoke_callback(int cbId) to handle animation ticks (e.g. cbId == 1).
  • Per-Frame Workflow: Call batch.reset(), emit drawing commands, and finish with batch.flush(ctx).
BatchGraphicsDemo.java
// Complete Batched Canvas 2D Demo with Gradients, Shadows, Sprites & Animation

class Orb {
    float x, y, vx, vy, radius;
    String color, glow;

    Orb(float x, float y, float vx, float vy, float r, String c, String g) {
        this.x = x; this.y = y; this.vx = vx; this.vy = vy;
        this.radius = r; this.color = c; this.glow = g;
    }

    void update(float w, float h, float dt) {
        x += vx * dt;
        y += vy * dt;
        if (x - radius < 0.0f)     { x = radius;     vx = -vx; }
        else if (x + radius > w) { x = w - radius; vx = -vx; }
        if (y - radius < 50.0f)    { y = 50.0f + radius; vy = -vy; }
        else if (y + radius > h) { y = h - radius; vy = -vy; }
    }
}

public class BatchGraphicsDemo {
    static final float W = 600.0f, H = 400.0f;
    static final int SPRITE_STAR = 1;

    static int ctx;
    static Batch batch;
    static Orb[] orbs;
    static double lastTime = 0.0;
    static float angle = 0.0f;
    static int frames = 0;

    public static void main(String[] args) {
        // 1. Acquire canvas (or create one) and 2D context
        int canvas = BatchCanvas.getElementById("game");
        if (canvas == 0) {
            int parent = BatchCanvas.getElementById("stage-container");
            canvas = BatchCanvas.create(parent, (int) W, (int) H);
        }
        ctx = BatchCanvas.getContext(canvas);

        batch = new Batch();

        // 2. Bake offscreen glowing star sprite (ID = 1)
        batch.reset();
        batch.bakeBegin(SPRITE_STAR, 32, 32);
        batch.beginPath();
        batch.arc(16.0f, 16.0f, 12.0f, 0.0f, 6.2831853f);
        batch.setFill("rgba(78, 205, 196, 0.25)");
        batch.fill();
        batch.beginPath();
        batch.moveTo(16.0f, 4.0f);  batch.lineTo(20.0f, 13.0f);
        batch.lineTo(28.0f, 16.0f); batch.lineTo(20.0f, 19.0f);
        batch.lineTo(16.0f, 28.0f); batch.lineTo(12.0f, 19.0f);
        batch.lineTo(4.0f, 16.0f);  batch.lineTo(12.0f, 13.0f);
        batch.closePath();
        batch.setFill("#4ecdc4");
        batch.fill();
        batch.bakeEnd();
        batch.flush(ctx);

        // 3. Initialize particle state
        orbs = new Orb[] {
            new Orb(150.0f, 150.0f,  160.0f,  120.0f, 18.0f, "#ffd2a1", "#e8933f"),
            new Orb(300.0f, 220.0f, -130.0f,  180.0f, 24.0f, "#4ecdc4", "#2a6f6a"),
            new Orb(450.0f, 280.0f,  180.0f, -140.0f, 16.0f, "#58a6ff", "#1f6feb"),
        };

        lastTime = BatchCanvas.now();

        // 4. Start 60 FPS animation loop dispatching callback 1
        BatchCanvas.startAnimationLoop(1);
    }

    // Callback invoked every animation frame by the browser host
    public static void batchiness_invoke_callback(int cbId) {
        if (cbId == 1) {
            render();
        }
    }

    static void render() {
        frames++;
        double now = BatchCanvas.now();
        float dt = (float) ((now - lastTime) / 1000.0);
        lastTime = now;
        if (dt <= 0.0f || dt > 0.05f) dt = 0.01667f;
        angle += 1.8f * dt;

        // Clear previous command stream
        batch.reset();

        // Clear background cleanly (no lingering trails)
        batch.clearShadow();
        batch.clearRect(0.0f, 0.0f, W, H);
        batch.setFill("#0a0d14");
        batch.fillRect(0.0f, 0.0f, W, H);

        // Top banner with Linear Gradient
        batch.linearGradient(1, 0.0f, 0.0f, W, 0.0f);
        batch.addColorStop(1, 0.0f, "#1c2638");
        batch.addColorStop(1, 0.5f, "#2e3d57");
        batch.addColorStop(1, 1.0f, "#1c2638");
        batch.useGradientFill(1);
        batch.fillRect(0.0f, 0.0f, W, 46.0f);

        batch.setFont("bold 13px 'Inter', sans-serif");
        batch.setFill("#ffd2a1");
        batch.fillText("WasmCoffee Batched Canvas2D", 16.0f, 28.0f);

        // Draw glowing orbs
        for (int i = 0; i < orbs.length; i++) {
            Orb b = orbs[i];
            b.update(W, H, dt);

            batch.setShadow(b.glow, 16.0f);
            batch.beginPath();
            batch.arc(b.x, b.y, b.radius, 0.0f, 6.2831853f);
            batch.setFill(b.color);
            batch.fill();
            batch.clearShadow();
        }

        // Rotating baked sprite at center
        batch.save();
        batch.translate(W * 0.5f, H * 0.55f);
        batch.rotate(angle);
        batch.drawSprite(SPRITE_STAR, -16.0f, -16.0f);
        batch.restore();

        // HUD Text
        batch.setFont("11px 'JetBrains Mono', monospace");
        batch.setFill("#8e9bb0");
        batch.fillText("Frame: " + frames, 16.0f, H - 14.0f);

        // Flush entire frame to browser in 1 bridge crossing
        batch.flush(ctx);
    }
}