Ammo.js Memory Cleanup on Vue Component Unmount
This article explains how to properly manage and release memory allocated by the Ammo.js physics engine when a Vue.js component unmounts. Since Ammo.js is a WebAssembly (Wasm) port of the C++ Bullet Physics library, JavaScript’s native garbage collector cannot automatically free its memory, making manual cleanup essential to prevent severe memory leaks in single-page applications.
The Challenge with Ammo.js and WebAssembly
Because Ammo.js runs in a WebAssembly context, objects created via
the new Ammo.bt* constructor are allocated on the
Emscripten heap. When a Vue component is unmounted and destroyed, the
JavaScript references to these objects are lost, but the allocated
memory on the Wasm heap remains. To prevent memory leaks, you must
explicitly free these objects using Ammo.destroy() within
Vue’s onBeforeUnmount or onUnmounted lifecycle
hooks.
Step-by-Step Cleanup Process
To completely clean up Ammo.js, you must dismantle the physics world from the inside out.
1. Remove and Destroy Rigid Bodies
First, iterate through all active rigid bodies in your scene. You must remove them from the physics world, destroy their motion states, destroy their collision shapes, and finally destroy the rigid bodies themselves.
// Example array tracking your active rigid bodies
const rigidBodies = [];
function cleanupBodies(physicsWorld) {
rigidBodies.forEach((body) => {
// Remove from the physics world
physicsWorld.removeRigidBody(body);
// Destroy motion state
const motionState = body.getMotionState();
if (motionState) {
Ammo.destroy(motionState);
}
// Destroy collision shape
const shape = body.getCollisionShape();
if (shape) {
Ammo.destroy(shape);
}
// Destroy the rigid body itself
Ammo.destroy(body);
});
// Clear the array reference
rigidBodies.length = 0;
}2. Destroy the Physics World and Core Setup Objects
Once the bodies are removed, you must destroy the infrastructure of the physics world in the reverse order of their creation.
function cleanupPhysicsWorld(worldComponents) {
const {
dynamicsWorld,
solver,
overlappingPairCache,
dispatcher,
collisionConfiguration
} = worldComponents;
// Destroy the world
if (dynamicsWorld) Ammo.destroy(dynamicsWorld);
// Destroy world infrastructure
if (solver) Ammo.destroy(solver);
if (overlappingPairCache) Ammo.destroy(overlappingPairCache);
if (dispatcher) Ammo.destroy(dispatcher);
if (collisionConfiguration) Ammo.destroy(collisionConfiguration);
}3. Implement the Vue Lifecycle Hook
In your Vue component, integrate these cleanup routines inside the
onBeforeUnmount hook (for Vue 3 Composition API) to ensure
memory is freed before the component is destroyed.
<script setup>
import { onBeforeUnmount, onMounted } from 'vue';
// Keep track of Ammo objects in your component's scope
let physicsWorld;
let solver;
let overlappingPairCache;
let dispatcher;
let collisionConfiguration;
const rigidBodies = [];
onMounted(() => {
// Initialize your Ammo.js world here
});
onBeforeUnmount(() => {
// 1. Clean up all rigid bodies, shapes, and motion states
rigidBodies.forEach((body) => {
if (physicsWorld) {
physicsWorld.removeRigidBody(body);
}
const motionState = body.getMotionState();
if (motionState) Ammo.destroy(motionState);
const shape = body.getCollisionShape();
if (shape) Ammo.destroy(shape);
Ammo.destroy(body);
});
rigidBodies.length = 0;
// 2. Clean up auxiliary math vectors created during updates
// (e.g., Ammo.btVector3, Ammo.btQuaternion instances)
if (typeof temporaryVector !== 'undefined') {
Ammo.destroy(temporaryVector);
}
// 3. Destroy physics world core structures
if (physicsWorld) Ammo.destroy(physicsWorld);
if (solver) Ammo.destroy(solver);
if (overlappingPairCache) Ammo.destroy(overlappingPairCache);
if (dispatcher) Ammo.destroy(dispatcher);
if (collisionConfiguration) Ammo.destroy(collisionConfiguration);
// 4. Nullify references to trigger JavaScript garbage collection
physicsWorld = null;
solver = null;
overlappingPairCache = null;
dispatcher = null;
collisionConfiguration = null;
});
</script>
By explicitly invoking Ammo.destroy() on every
instantiated Bullet C++ object, you ensure that WebAssembly heap memory
is released back to the system, keeping your Vue application’s memory
footprint stable.