Worker Execution Model

Overview

SiliconGhetto uses a worker-first execution model where the WASM game module runs in a Web Worker, not on the browser’s main thread. This keeps the page responsive and dedicates a full CPU core to the game loop.

Architecture

┌─────────────────────────────────┐     ┌─────────────────────────────────┐
│          Main Thread            │     │         Worker Thread           │
│                                 │     │                                 │
│  HTML Shell                     │     │  WASM Module                    │
│  ├─ <canvas id="sg-canvas">    │────→│  ├─ OffscreenCanvas (received)  │
│  │   (transferred to worker)   │     │  ├─ wgpu Surface                │
│  │                              │     │  ├─ sg_app game loop            │
│  ├─ Input Event Listeners      │     │  ├─ sg_scene ECS world          │
│  │   keydown, keyup            │────→│  ├─ sg_render pipeline          │
│  │   mousemove, mousedown      │     │  └─ sg_input state              │
│  │   touchstart, touchmove     │     │                                 │
│  │                              │     │  requestAnimationFrame          │
│  ├─ Overlay UI                  │     │  ├─ process input messages      │
│  │   <div id="perf-overlay">   │←────│  ├─ fixed_update (physics)      │
│  │   HUD elements              │     │  ├─ prepare_render              │
│  └─ Accessibility layer        │     │  ├─ GPU submit                  │
│                                 │     │  └─ postMessage (stats)         │
└─────────────────────────────────┘     └─────────────────────────────────┘

Startup Sequence

  1. HTML page loads — minimal shell with a <canvas> element
  2. Transfer canvascanvas.transferControlToOffscreen() creates an OffscreenCanvas
  3. Create workernew Worker("worker.js", { type: "module" })
  4. Send canvasworker.postMessage({ canvas }, [canvas]) (transferable)
  5. Worker initializes — loads WASM, creates wgpu surface from OffscreenCanvas
  6. Game loop startsrequestAnimationFrame in the worker drives the frame loop

Input Forwarding

Input events are captured on the main thread and forwarded to the worker:

// Main thread
document.addEventListener("keydown", (e) => {
    worker.postMessage({ type: "keydown", code: e.code });
});

// Worker thread
self.onmessage = (e) => {
    if (e.data.type === "keydown") {
        engine.handle_key_down(e.data.code);
    }
};

Input events are small and infrequent relative to frame rate, so postMessage overhead is negligible.

Fallback: Main Thread Mode

When OffscreenCanvas is unavailable (older browsers), the engine falls back to main-thread rendering:

  1. WASM module initializes directly with the canvas element
  2. Game loop uses requestAnimationFrame on the main thread
  3. Input events are handled directly (no postMessage)
  4. Performance may degrade under heavy DOM activity

The sg_worker crate handles capability detection and transparent fallback.

Communication Patterns

Main → Worker

MessagePurposeFrequency
canvasTransfer OffscreenCanvasOnce (startup)
keydown/keyupKeyboard inputPer key event
mousemoveMouse positionPer pointer move
resizeCanvas size changeOn window resize
focus/blurVisibility stateOn tab switch

Worker → Main

MessagePurposeFrequency
perfFPS and frame time statsEvery 60 frames
readyInitialization completeOnce
errorFatal error reportOn failure

SharedArrayBuffer (Future)

With cross-origin isolation enabled (COOP/COEP headers), SharedArrayBuffer becomes available. This enables:

  • WASM threads: Multiple threads sharing the same linear memory
  • Lock-free input: Input state written by main thread, read by worker without postMessage
  • Parallel systems: ECS systems running on multiple threads

This is a v0.3+ feature. The current architecture is designed to support it without structural changes.

Browser Compatibility

FeatureChromeFirefoxSafariEdge
Web WorkersYesYesYesYes
OffscreenCanvas69+105+16.4+79+
transferControlToOffscreen69+105+16.4+79+
SharedArrayBuffer91+79+15.2+91+

See compatibility.md for full browser support details.