← All posts

Rust & WebAssembly for 120 FPS Browser Game Physics

Discover how Rust and WebAssembly push browser games to buttery-smooth 120 FPS collision detection without breaking a sweat.

What is WebAssembly and Rust in Browser Game Physics?

WebAssembly (Wasm) is a low-level binary instruction format designed to run in modern web browsers at near-native speed. Rust is a statically typed systems programming language focused on safety, concurrency, and raw performance. Together, Rust and WebAssembly allow indie developers to compile complex physics engines—traditionally reserved for desktop or console builds—directly into the browser, achieving ultra-smooth 120 FPS collision detection and deterministic calculations without the garbage collection stutter typical of standard JavaScript.

If you have ever coded a custom physics loop in vanilla JavaScript, you likely know the gut-punch feeling of watching your frame rate plummet from a silky 60 FPS down to a choppy, cinematic 18 frames per second the moment forty bouncing polygons enter the viewport. Browsers are miraculous things, but JavaScript’s dynamic typing and runtime garbage collection occasionally decide to take a tea break right when your game needs them most.

Enter the dynamic duo of the modern web: Rust and WebAssembly (Wasm). Developers across the indie scene and community subreddits are shouting about how this stack unlocks desktop-grade performance inside a simple tab. Let us dive into why this matters for browser games, how the mechanics work under the hood, and how you can harness this raw speed for buttery-smooth collision detection.

The JavaScript Bottleneck vs. Wasm Speed

To understand why your current web game lags when things get chaotic, we have to look at how browsers process code. JavaScript is an interpreted-and-JIT-compiled language. When you run a collision detection loop checking hundreds of bounding boxes every sixteen milliseconds, the JavaScript engine has to constantly guess types, allocate memory, and occasionally pause everything to clean up unused objects via garbage collection.

WebAssembly changes the rules entirely.


+-------------------------------------------------+
|               Modern Web Browser                |
|                                                 |
|  +-------------------+   +-------------------+  |
|  |    JavaScript     |   | WebAssembly (Wasm)|  |
|  |  DOM Manipulation |   | Physics & Math    |  |
|  |  UI / Rendering   |   | 120 FPS Loops     |  |
|  +---------+---------+   +---------+---------+  |
|            |                       |            |
|            +-----------+-----------+            |
|                        v                        |
|             +---------------------+             |
|             | HTML5 Canvas / WebGL|             |
|             +---------------------+             |
+-------------------------------------------------+

Wasm provides a compact binary code format that browsers can load, validate, and compile at blinding speeds. Because Rust compiles straight down to these lean binary instructions without a heavy runtime environment, your physics calculations run at speeds comparable to C++.

Key Performance Benefits

  • Zero Garbage Collection Pauses: Rust manages memory manually using an ownership model, meaning no random memory-cleanup freezes mid-match.
  • Predictable Execution Times: CPU cache locality is vastly superior, preventing micro-stutters during heavy spatial partitioning queries.
  • SIMD Support: Modern browsers support Single Instruction, Multiple Data through Wasm, letting you process multiple vector math calculations simultaneously.

Anatomy of a 120 FPS Rust Physics Loop

When community developers discuss moving physics to Rust, the conversation usually centers around spatial partitioning algorithms—like Quadtrees or Sweep-and-Prune—implemented in Rust structs and compiled to Wasm.

Below is a simplified conceptual look at how a Rust loop handles raw velocity integration and boundary checks before passing raw float arrays back to JavaScript for rendering via HTML5 Canvas or WebGL.


// A high-performance Rust struct designed for Wasm memory layout
pub struct Particle {
    pub x: f32,
    pub y: f32,
    pub vx: f32,
    pub vy: f32,
    pub radius: f32,
}

impl Particle {
    pub fn update(&mut self, dt: f32, bounds_width: f32, bounds_height: f32) {
        self.x += self.vx * dt;
        self.y += self.vy * dt;

        // Simple elastic wall collision
        if self.x - self.radius < 0.0 || self.x + self.radius > bounds_width {
            self.vx *= -1.0;
        }
        if self.y - self.radius < 0.0 || self.y + self.radius > bounds_height {
            self.vy *= -1.0;
        }
    }
}

By keeping the heavy numerical crunching inside Rust's memory space and only passing a pointer to a flat Float32Array across the JavaScript boundary, overhead is kept virtually non-existent.

Performance Benchmarks: JS vs Wasm

While exact benchmarks fluctuate wildly depending on the user's hardware and browser engine (V8, SpiderMonkey, or JavaScriptCore), community experiments consistently point to staggering differences in heavy mathematical workloads.

MetricVanilla JavaScript (ES6)Rust compiled to WebAssembly
5,000 Rigid Bodies (AABB Collisions)~22 FPS (Frequent GC spikes)~115–120 FPS (Rock solid)
Memory Allocation OverheadHigh (Dynamic object creation)Near Zero (Pre-allocated linear buffers)
Load & Parse TimeInstantFast (Compact binary payload)
Cross-Browser ConsistencyVariable (JIT engine quirks)Highly Consistent

Note: Benchmarks reflect ideal test conditions with pre-allocated TypedArrays. Real-world frame rates depend on DOM interaction overhead and rendering bottlenecks.

Developer Consensus and Community Insights

If you spend an afternoon scrolling through GitHub discussions or developer forums dedicated to web game engines, a clear consensus emerges regarding Rust and Wasm: do not rewrite your entire game in Rust.

The general wisdom from experienced indie web developers is a hybrid approach:

1. Keep UI and Game State in JS/TS: Use JavaScript for what it is great at—DOM manipulation, handling input listeners, audio playback, and asset loading.

2. Offload Math-Heavy Subsystems to Wasm: Write your pathfinding, particle systems, procedural generation, and broad-phase collision detection in Rust, then compile it to a .wasm module.

3. Mind the JS-Wasm Border: Constantly passing complex objects back and forth across the JavaScript-WebAssembly boundary creates a performance tax. Instead, share a single linear memory buffer (a TypedArray) that both environments can read and write to directly.

Key Takeaways

  • Speed Upgrade: Rust + WebAssembly brings near-native execution speed to web browsers, making 120 FPS physics loops entirely feasible for indie browser games.
  • Say Goodbye to JIT Stutter: Manual memory management in Rust eliminates unpredictable garbage collection pauses that ruin high-score runs.
  • The Hybrid Rule: Build your UI and input handling in JavaScript, but delegate heavy vector math and collision detection to your compiled Wasm binary.
  • Memory Sharing is Crucial: Optimize performance by using shared linear memory buffers rather than passing complex objects back and forth across the JS/Wasm bridge.

Thanks for reading. Browse more from the Wobblox blog, or jump straight into all 100 free games.