Access Bullet Profile Timers in Ammo.js

This article explains how to access and utilize the internal Bullet physics profiling timers within Ammo.js to measure simulation performance. You will learn how to expose the C++ profile manager classes to JavaScript, retrieve timing data, and recursively traverse the physics step breakdown.

Expose Bullet Profiler in ammo.idl

Ammo.js is a direct port of the Bullet physics engine compiled via Emscripten. Because the performance profiling classes are not always exposed in default Ammo.js builds, you must ensure they are defined in your ammo.idl file before compilation.

Add the following WebIDL definitions to your ammo.idl to expose the necessary C++ profiling interfaces:

interface CProfileIterator {
    void First();
    void Next();
    boolean Is_Done();
    boolean Is_Root();
    void Enter_Child(long index);
    void Enter_Parent();
    DOMString Get_Current_Name();
    float Get_Current_Total_Time();
    long Get_Current_Total_Calls();
};

interface CProfileManager {
    static void Reset();
    static void Increment_Frame_Counter();
    static CProfileIterator Get_Iterator();
    static void Release_Iterator(CProfileIterator iterator);
    static float Get_Time_Since_Reset();
};

Enable Profiling in Your Code

To gather profiling data, the internal Bullet timers must be updated each frame. Call Increment_Frame_Counter at the end of your game loop, right after stepping the physics world.

// Step the physics world
physicsWorld.stepSimulation(deltaTime, 10);

// Increment the profiler frame counter
Ammo.CProfileManager.Increment_Frame_Counter();

Retrieve and Traverse Profile Data

Once the frame counter is incremented, use the CProfileIterator to navigate the hierarchical execution tree of the physics engine (such as collision detection, constraint solving, and integration).

Here is a recursive JavaScript function to extract and print the profiling hierarchy:

function dumpProfileData() {
    let iterator = Ammo.CProfileManager.Get_Iterator();
    
    console.log("--- Physics Profile Frame ---");
    traverseNode(iterator, 0);
    
    Ammo.CProfileManager.Release_Iterator(iterator);
}

function traverseNode(iterator, depth) {
    iterator.First();
    let indent = "  ".repeat(depth);

    while (!iterator.Is_Done()) {
        let name = iterator.Get_Current_Name();
        let totalTime = iterator.Get_Current_Total_Time();
        let totalCalls = iterator.Get_Current_Total_Calls();

        console.log(`${indent}${name}: ${totalTime.toFixed(3)} ms (${totalCalls} calls)`);

        // Enter child nodes recursively
        iterator.Enter_Child(0); // Index is typically ignored or managed internally
        traverseNode(iterator, depth + 1);
        iterator.Enter_Parent();

        iterator.Next();
    }
}

Reset Timers

To clear accumulated timing statistics and start a fresh profiling session, invoke the static reset method:

Ammo.CProfileManager.Reset();