Ручное освобождение объектов через Ammo.destroy()

Ammo.js является портом известной библиотеки физического движка Bullet Physics для JavaScript. В отличие от управляемых языков, таких как Java или C#, в JavaScript память управляется сборщиком мусора, однако Ammo.js использует WebAssembly и объекты на его стороне требуют явного освобождения. Игнорирование этого может привести к утечкам памяти при работе с крупными сценами и динамическими симуляциями.

Принцип работы памяти в Ammo.js

Все объекты физического мира (btRigidBody, btCollisionShape, btDynamicsWorld и т.д.) создаются с помощью конструктора new Ammo.[Object]() и хранятся в WebAssembly heap. JavaScript-объект лишь ссылается на этот внутренний блок памяти. Когда объект больше не нужен, простое удаление ссылки в JS не освобождает память в WebAssembly. Для этого используется:

Ammo.destroy(object);

Когда необходимо использовать Ammo.destroy()

  1. Физические тела и коллайдеры После удаления тела из динамического мира необходимо освободить его и связанные с ним формы коллизий:
dynamicsWorld.removeRigidBody(body);
Ammo.destroy(body.getCollisionShape());
Ammo.destroy(body);
  1. Констрейнты (ограничения) Если сцена содержит шарнирные соединения или другие ограничения, их тоже нужно удалять:
dynamicsWorld.removeConstraint(constraint);
Ammo.destroy(constraint);
  1. Буферы и временные объекты Ammo.js часто возвращает временные объекты через методы, например btVector3, btQuaternion. Если они создаются вручную через new Ammo.btVector3(), их также следует уничтожать:
let tmpVec = new Ammo.btVector3(1, 0, 0);
// использование tmpVec
Ammo.destroy(tmpVec);

Правильная последовательность удаления объектов

Ошибки в порядке освобождения объектов приводят к crash или неопределённому поведению. Рекомендуется следующая последовательность:

  1. Удалить объекты из мира: dynamicsWorld.removeRigidBody(body) или dynamicsWorld.removeConstraint(constraint).
  2. Освободить вспомогательные объекты, такие как формы коллизий (btCollisionShape) или временные векторы.
  3. Освободить сам объект (btRigidBody, btConstraint).

Пример правильного удаления тела с коллайдером:

let shape = body.getCollisionShape();
dynamicsWorld.removeRigidBody(body);
Ammo.destroy(body);
Ammo.destroy(shape);

Массовое удаление объектов

При удалении большого количества объектов в сцене имеет смысл использовать циклы для освобождения всех тел и форм:

for (let i = dynamicsWorld.getNumCollisionObjects() - 1; i >= 0; i--) {
    let obj = dynamicsWorld.getCollisionObjectArray().at(i);
    let body = Ammo.castObject(obj, Ammo.btRigidBody);
    let shape = body.getCollisionShape();

    dynamicsWorld.removeRigidBody(body);
    Ammo.destroy(body);
    Ammo.destroy(shape);
}

Этот подход предотвращает накопление неосвобождённой памяти, особенно при генерации и удалении сотен объектов на каждой итерации симуляции.

Важные нюансы

  • Не освобождать объект дважды. После вызова Ammo.destroy() ссылка в JS остаётся, но объект уже недействителен. Повторный вызов приведёт к ошибке.
  • Освобождение форм коллизий важно, так как большинство объектов создаёт отдельные btCollisionShape. Неочищенные формы создают «утечки» даже при удалении тела.
  • Временные объекты (btVector3, btTransform) создаются часто для вычислений. Если они создаются внутри цикла или функции, обязательно уничтожать их после использования.

Использование вспомогательных функций для освобождения

Для упрощения управления памятью удобно создавать функции:

function destroyRigidBody(world, body) {
    let shape = body.getCollisionShape();
    world.removeRigidBody(body);
    Ammo.destroy(body);
    Ammo.destroy(shape);
}

Такой подход минимизирует ошибки и обеспечивает единообразное освобождение всех объектов.

Итоговые рекомендации

  • Любой объект, созданный через new Ammo.[Object], требует вызова Ammo.destroy() при завершении использования.
  • Временные объекты также должны уничтожаться.
  • Сначала удалять из мира/сцены, потом освобождать память.
  • Использовать вспомогательные функции для стандартных операций освобождения памяти.

Правильное управление памятью через Ammo.destroy() обеспечивает стабильность приложения, предотвращает утечки и снижает нагрузку на браузер при длительных симуляциях с большим количеством динамических объектов.