Ammo.js — это JavaScript-порт известной физической библиотеки Bullet Physics, скомпилированный с помощью Emscripten. Понимание внутреннего управления памятью в этом контексте критически важно для стабильной работы сложных приложений и предотвращения утечек.
Основной механизм работы с памятью в Ammo.js — Emscripten heap, представляющий собой линейный блок памяти, управляемый вручную. Heap состоит из следующих сегментов:
Все объекты Bullet, такие как btRigidBody,
btCollisionShape, btDynamicsWorld, создаются
внутри этой памяти. Управление осуществляется через вызовы конструктора
и деструктора, например:
let shape = new Ammo.btBoxShape(new Ammo.btVector3(1, 1, 1));
Каждый вызов new Ammo.* выделяет память в Emscripten
heap, которая не будет освобождена автоматически сборщиком мусора
JavaScript. Без явного удаления объекты будут сохраняться в памяти до
конца жизни страницы.
Для предотвращения утечек необходимо использовать методы
.delete() у каждого объекта, созданного в heap:
shape.delete();
Ключевые принципы:
Всегда удалять временные объекты. В вычислениях часто создаются временные векторы или матрицы:
let vec = new Ammo.btVector3(0, 10, 0);
// использование vec
vec.delete();
Игнорирование таких объектов ведёт к накоплению неиспользуемой памяти.
Удаление комплексных объектов. Объекты с
внутренними зависимостями, например btRigidBody, часто
требуют последовательного удаления:
rigidBody.getMotionState().delete();
rigidBody.delete();Управление формами коллизий. Любая форма
(btCollisionShape) должна быть удалена после
использования:
let shape = new Ammo.btSphereShape(1);
dynamicsWorld.addRigidBody(rigidBody);
// после удаления тела
dynamicsWorld.removeRigidBody(rigidBody);
shape.delete();Утечки в Ammo.js возникают из-за неприменения .delete()
к объектам в heap. Основные признаки:
Отслеживать можно с помощью инструментов разработчика браузера (Memory profiler, Performance tab) и простого подсчёта объектов:
console.log(Ammo._malloc_stats());
Создание пулов объектов Для часто используемых временных объектов имеет смысл создать пул:
const tempVecPool = [];
for (let i = 0; i < 10; i++) {
tempVecPool.push(new Ammo.btVector3(0, 0, 0));
}
// использовать vec = tempVecPool[i]Минимизация выделений внутри цикла симуляции
Любые new Ammo.btVector3 или
new Ammo.btTransform внутри игрового цикла должны быть либо
переиспользуемыми, либо сразу удаляемыми после применения.
Последовательное удаление объектов при окончании сцены При полной очистке мира рекомендуется:
btRigidBody) и их MotionState.btCollisionShape).btDynamicsWorld и его конфигурации
(btDispatcher, btBroadphaseInterface).Пример безопасного полного удаления:
for (let i = dynamicsWorld.getNumCollisionObjects() - 1; i >= 0; i--) {
let obj = dynamicsWorld.getCollisionObjectArray().at(i);
let body = Ammo.castObject(obj, Ammo.btRigidBody);
dynamicsWorld.removeRigidBody(body);
if (body.getMotionState()) {
body.getMotionState().delete();
}
body.getCollisionShape().delete();
body.delete();
}
dynamicsWorld.delete();
dispatcher.delete();
broadphase.delete();
solver.delete();
collisionConfiguration.delete();
Поскольку JavaScript использует автоматический сборщик мусора, а Ammo.js — ручное управление, важно четко разделять области:
Array,
Object) очищаются автоматически..delete().Невыполнение этого правила приводит к классической «утечке памяти в heap», которая не видна в обычном JS профайлере и может занимать сотни мегабайт при долгих симуляциях.
Использовать вспомогательные функции для создания и удаления объектов:
function createVector(x, y, z) {
return new Ammo.btVector3(x, y, z);
}
function deleteVector(vec) {
vec.delete();
}Не смешивать временные объекты и глобальные: временные всегда удалять сразу после использования, глобальные — при уничтожении сцены.
Регулярно профилировать память при разработке больших симуляций.
Эффективное управление памятью в Ammo.js требует дисциплины: каждое выделение в Emscripten heap должно иметь соответствующее удаление. Только такой подход обеспечивает стабильную работу физических симуляций и предотвращает накопление неиспользуемых объектов.