Пул объектов: переиспользование btVector3 и btTransform

В высокопроизводительных приложениях на Ammo.js, особенно в симуляциях физики с множеством объектов, критически важно минимизировать создание и удаление объектов в памяти. Частая динамическая аллокация объектов типа btVector3 и btTransform приводит к нагрузке на сборщик мусора и падению производительности. Для решения этой проблемы применяется пул объектов (object pool), позволяющий переиспользовать уже созданные экземпляры.


Основные объекты для пула

  • btVector3 — класс для представления 3D-векторов. Используется для позиций, сил, скоростей, направлений.
  • btTransform — класс для описания трансформаций (позиция + ориентация) объектов в мире.

Создание этих объектов внутри цикла обновления физики может быть крайне затратным. Пул объектов позволяет хранить заранее созданные экземпляры и получать их по мере необходимости, возвращая обратно после использования.


Создание пула объектов

Пул объектов можно реализовать как простой массив. Для btVector3 и btTransform выделяются отдельные пулы:

class AmmoPool {
    constructor() {
        this.vector3Pool = [];
        this.transformPool = [];
    }

    getVector3(x = 0, y = 0, z = 0) {
        let vec = this.vector3Pool.pop();
        if (!vec) {
            vec = new Ammo.btVector3(x, y, z);
        } else {
            vec.setValue(x, y, z);
        }
        return vec;
    }

    releaseVector3(vec) {
        this.vector3Pool.push(vec);
    }

    getTransform() {
        let tr = this.transformPool.pop();
        if (!tr) {
            tr = new Ammo.btTransform();
        }
        tr.setIdentity();
        return tr;
    }

    releaseTransform(tr) {
        this.transformPool.push(tr);
    }
}

Ключевые моменты:

  • Метод getVector3 возвращает вектор с заданными координатами, создавая новый только если пул пуст.
  • Метод getTransform возвращает трансформацию с начальной идентичной ориентацией.
  • После использования объект возвращается в пул методом releaseVector3 или releaseTransform.

Переиспользование btVector3

btVector3 часто создаются при расчетах силы, импульса, скорости или в коллизиях. Без пула каждый кадр может генерировать десятки новых объектов:

function applyForce(body, force, pool) {
    const btForce = pool.getVector3(force.x, force.y, force.z);
    body.applyCentralForce(btForce);
    pool.releaseVector3(btForce);
}

Выигрыши от пула:

  • Исключение лишних аллокаций в горячих циклах.
  • Уменьшение частоты срабатывания сборщика мусора.
  • Стабильное время отклика симуляции.

Переиспользование btTransform

btTransform используется при позиционировании объектов в мире, чтении результатов коллизий или при телах с кинематикой:

function updateBodyTransform(body, position, quaternion, pool) {
    const tr = pool.getTransform();
    const origin = pool.getVector3(position.x, position.y, position.z);
    tr.setOrigin(origin);
    tr.setRotation(new Ammo.btQuaternion(quaternion.x, quaternion.y, quaternion.z, quaternion.w));
    body.setWorldTransform(tr);
    pool.releaseVector3(origin);
    pool.releaseTransform(tr);
}

Особенности:

  • Перед установкой трансформации желательно вызвать setIdentity(), чтобы сбросить предыдущие данные.
  • При работе с кватернионами важно не создавать новые btQuaternion внутри цикла, а также рассмотреть пул для них при необходимости.
  • Переиспользование btTransform сокращает создание временных объектов для каждого тела на каждом кадре.

Расширенные подходы к пулу

  1. Предвыделение объектов: можно создать заранее фиксированное количество объектов в пуле для уменьшения затрат на динамическую аллокацию.
  2. Разделение пулов по типам задач: отдельный пул для силы, другой — для трансформаций коллизий, третий — для кинематических расчетов.
  3. Автоматический рост пула: если пул пуст, создавать новые объекты; но при этом регулярно анализировать размер пула, чтобы не держать слишком много неиспользуемых объектов.
  4. Рециклирование вложенных объектов: при использовании btTransform, содержащего btVector3, следует убедиться, что внутренние векторы тоже берутся из пула.

Практические рекомендации

  • Любой временный объект внутри игрового или физического цикла должен браться из пула.
  • Всегда возвращать объект в пул сразу после использования.
  • Избегать создания объектов в горячих функциях, таких как onUpdate, tick или обработчики столкновений.
  • Проверять утечки памяти: если объект не возвращен в пул, это может привести к росту использования памяти.
  • Пул не должен быть глобальным в одном экземпляре для всей сцены без учета жизненного цикла объектов — предпочтительнее создавать отдельный пул для группы связанных задач.

Итоговое влияние на производительность

Переиспользование btVector3 и btTransform через пул объектов позволяет:

  • Снизить частоту сборки мусора.
  • Стабилизировать кадр рендеринга и физического цикла.
  • Уменьшить время отклика в симуляциях с сотнями и тысячами объектов.
  • Сделать код более предсказуемым с точки зрения выделения памяти и управления объектами.

Применение таких подходов становится критически важным при разработке сложных 3D симуляций и игр на базе Ammo.js, где производительность напрямую зависит от количества временных объектов, создаваемых каждый кадр.