Decouple Matter.js Physics for Headless Benchmarks
By default, Matter.js couples simulation updates to the browser
display refresh rate using Matter.Runner and
requestAnimationFrame. Decoupling physics updates from
screen rendering allows you to run headless simulations at maximum
processor speed, making it possible to execute automated stress tests,
profile computational bottlenecks, and measure engine throughput. This
guide demonstrates how to bypass Matter.js rendering utilities,
implement a manual execution loop, and collect accurate benchmark
metrics.
Why Avoid Matter.Runner and Matter.Render
Matter.Runner synchronizes simulation steps with the
screen's refresh cycle (typically 60Hz or 144Hz). It calculates a
dynamic delta based on wall-clock time and caps the execution rate to
prevent physics updates from outpacing frame delivery. Similarly,
Matter.Render consumes significant CPU and GPU overhead
drawing canvas elements.
To benchmark raw physics performance, both must be eliminated:
- Omit
Matter.Renderentirely to run in environments like Node.js or web workers without DOM dependencies. - Replace
Matter.Runnerwith a tight, synchronous loop that callsMatter.Engine.updatedirectly.
Implementing a Synchronous Headless Loop
To execute physics calculations as fast as possible, call
Engine.update() inside a standard iteration loop. Passing a
fixed delta (such as 1000 / 60 for 16.666ms)
ensures deterministic physics across benchmark runs regardless of
hardware execution speed.
import Matter from 'matter-js';
const { Engine, World, Bodies } = Matter;
// 1. Initialize the Engine without Runner or Render
const engine = Engine.create();
// 2. Populate the World
const ground = Bodies.rectangle(400, 610, 810, 60, { isStatic: true });
const stack = [];
for (let i = 0; i < 500; i++) {
stack.push(Bodies.circle(400, 100, 10));
}
World.add(engine.world, [ground, ...stack]);
// 3. Define Benchmark Parameters
const TOTAL_FRAMES = 1000;
const FIXED_DELTA = 1000 / 60; // 16.66ms per step
// 4. Run the Headless Execution Loop
const startTime = performance.now();
for (let frame = 0; frame < TOTAL_FRAMES; frame++) {
Matter.Engine.update(engine, FIXED_DELTA);
}
const endTime = performance.now();
const durationMs = endTime - startTime;
// 5. Calculate Metrics
const framesPerSecond = (TOTAL_FRAMES / (durationMs / 1000)).toFixed(2);
const msPerFrame = (durationMs / TOTAL_FRAMES).toFixed(3);
console.log(`Simulated ${TOTAL_FRAMES} frames in ${durationMs.toFixed(2)}ms`);
console.log(`Throughput: ${framesPerSecond} steps/sec (${msPerFrame} ms/step)`);Best Practices for Accurate Benchmarking
When running headless Matter.js tests, apply these practices to isolate physics computation:
- Enforce Fixed Time Steps: Never pass fluctuating
dynamic delta times to
Engine.update()during benchmarking. Dynamic time-stepping alters collision resolution counts and introduces non-deterministic behavior between runs. - Include a Warmup Phase: Modern JavaScript engines
(such as V8) optimize code dynamically via JIT compilation. Run several
hundred updates before recording
performance.now()to ensure JIT optimization completes before timing begins. - Control Garbage Collection: Avoid instantiating new bodies, vectors, or arrays inside the iteration loop. Pre-allocate required entities to ensure memory allocation overhead does not skew your simulation measurements.