How to Reuse Matter.Vector Instances in Matter.js
High performance in web physics simulations often degrades due to
garbage collection (GC) pauses caused by frequent object creation inside
the update loop. In Matter.js, vector calculations are among the most
frequent operations, typically instantiating new coordinate objects
every frame. By pre-allocating dedicated Matter.Vector
instances and utilizing Matter.js's built-in target parameters or direct
property mutation, you can completely eliminate runtime allocations and
maintain a stable, stutter-free frame rate.
The Allocation Bottleneck
By default, helper methods such as
Matter.Vector.create(x, y) or standard operations like
Matter.Vector.add(v1, v2) create and return a new
{ x, y } object literal. When this occurs hundreds or
thousands of times per second across active rigid bodies, the JavaScript
runtime allocates memory rapidly, triggering periodic garbage collection
cycles that drop frame rates.
Utilizing Output Targets in Matter.Vector Methods
Many developers overlook that Matter.js mathematical operations
accept an optional output vector parameter. Instead of
returning a newly allocated object, the method writes the calculation
results directly into the provided instance.
Methods supporting an output vector include:
Matter.Vector.add(vectorA, vectorB, output)Matter.Vector.sub(vectorA, vectorB, output)Matter.Vector.mult(vector, scalar, output)Matter.Vector.div(vector, scalar, output)Matter.Vector.perp(vector, negate, output)Matter.Vector.normalise(vector, output)
Implementing Pre-Allocated Scratchpad Vectors
To avoid allocation during simulation ticks, create module-level or closure-scoped scratchpad vectors once during application initialization. Reuse these references exclusively inside your loop.
// Pre-allocate scratchpad vectors outside the game loop
const scratchA = Matter.Vector.create(0, 0);
const scratchB = Matter.Vector.create(0, 0);
const forceTarget = Matter.Vector.create(0, 0);
function applyCustomForce(body, targetPosition) {
// Calculate displacement without allocating: scratchA = targetPosition - body.position
Matter.Vector.sub(targetPosition, body.position, scratchA);
// Normalize the displacement vector in-place: scratchB = normalise(scratchA)
Matter.Vector.normalise(scratchA, scratchB);
// Scale direction by force magnitude: forceTarget = scratchB * 0.005
Matter.Vector.mult(scratchB, 0.005, forceTarget);
// Apply the resulting force directly
Matter.Body.applyForce(body, body.position, forceTarget);
}Direct Property Mutation
When performing simple assignments, avoid calling
Vector.create() to update coordinates. Mutate the existing
vector's x and y properties directly:
// Inefficient (allocates a new object)
body.velocity = Matter.Vector.create(0, 5);
// Efficient (zero allocation)
body.velocity.x = 0;
body.velocity.y = 5;Implementing a Vector Pool for Dynamic Operations
For systems where vector requirements scale dynamically (such as particle effects or raycasting), implement a fixed-size pool to handle temporary allocations without invoking the garbage collector:
class VectorPool {
constructor(size = 50) {
this.pool = Array.from({ length: size }, () => Matter.Vector.create(0, 0));
this.index = 0;
}
get(x = 0, y = 0) {
if (this.index >= this.pool.length) {
this.index = 0; // Wrap around for single-frame scratchpads
}
const vec = this.pool[this.index++];
vec.x = x;
vec.y = y;
return vec;
}
reset() {
this.index = 0;
}
}
// Reset the pool pointer at the start of each frame
Events.on(engine, 'beforeUpdate', () => {
vectorPool.reset();
});Using pre-allocated scratchpad instances, taking advantage of the
output parameter in Matter.Vector methods, and
mutating coordinates directly will remove memory pressure from the
browser's GC, ensuring consistent simulation performance under heavy
physics loads.