Why Modifying Ammo.js Objects in Collision Loops is Dangerous
Modifying ammo.js objects, such as rigid bodies or collision shapes, directly inside a collision detection iteration loop can lead to critical memory corruption, application crashes, and unpredictable physics behavior. This article explains the underlying mechanics of why these direct modifications are dangerous in the WebAssembly/Emscripten-compiled environment of ammo.js and provides the safest methods for handling collision-triggered changes.
The Problem with WebAssembly and C++ Memory
Ammo.js is a direct port of the Bullet Physics engine written in C++. It runs in JavaScript via Emscripten and WebAssembly (Wasm). Unlike native JavaScript objects, which are managed by a garbage collector, ammo.js objects reside in a restricted, manually-managed WebAssembly memory heap.
When you query collisions in ammo.js, you typically iterate through the dispatcher’s contact manifolds. During this iteration, the physics engine holds direct pointers to the active rigid bodies and collision structures involved in those contacts.
Iterator Invalidation and Memory Corruption
If you destroy, remove, or significantly alter an ammo.js object (such as changing its mass, collision flags, or removing it from the dynamics world) while inside this iteration loop, you cause several immediate issues:
- Use-After-Free Errors: Removing a rigid body from
the world or deleting it from memory frees its allocated heap space. The
collision loop, still running, may attempt to read or write to that
freed memory address, resulting in a fatal
RuntimeError: memory access out of boundscrash. - Array Shifting and Pointer Invalidation: When an object is removed from the physics world, the engine often reorders its internal arrays to close the gap. This shifts the indices of the remaining active objects. The loop currently iterating through these structures will point to the wrong memory addresses, leading to silent data corruption or skipped collisions.
- State Desynchronization: Modifying the transform or velocity of an object mid-iteration disrupts the physics solver. The solver expects a static snapshot of the world while resolving constraints. Modifying properties mid-loop results in unstable physics behavior, such as objects clipping through walls or jittering violently.
The Safe Solution: Deferred Execution
To safely modify physics objects based on collision events, you must defer any state changes or deletions until after the physics simulation step has completely finished.
- Queue the Changes: During the collision loop, do not modify the ammo.js objects. Instead, record the target objects and the intended actions (e.g., deletion, force application, or layer changes) into a standard JavaScript array or queue.
- Process Post-Step: Once the
dynamicsWorld.stepSimulation()function has fully resolved and the collision loop has exited, iterate through your custom queue. - Apply the Modifications safely: Perform the removals, modifications, or deletions on the queued objects outside of the physics engine’s internal solver loop.