Ammo.js представляет собой порт физического движка Bullet Physics, написанного на C++, в среду JavaScript и WebAssembly. Исходный код Bullet компилируется при помощи инструмента Emscripten, который преобразует C++ в WebAssembly (WASM) и генерирует JavaScript-обвязку для взаимодействия с браузерной средой.
В основе процесса лежит следующая цепочка:
Emscripten выступает в роли компилятора, рантайма и системы интеграции с веб-платформой.
Emscripten — это инструментальная цепочка, основанная на LLVM, предназначенная для компиляции C и C++ кода в WebAssembly и JavaScript. Она обеспечивает:
Emscripten не просто компилирует код — он создаёт полноценную среду выполнения, в которой C++-программа может работать в браузере.
WebAssembly — это низкоуровневый бинарный формат, предназначенный для выполнения в браузере с высокой производительностью. Он работает внутри JavaScript-движка, но исполняется отдельно от JS-кода.
Ключевые особенности WebAssembly:
Bullet Physics компилируется в WASM-модуль, который затем подключается к странице через JavaScript.
Bullet Physics — кроссплатформенный физический движок, написанный на C++. Он содержит:
Ammo.js не переписывает этот код на JavaScript, а компилирует его.
Процесс включает несколько стадий:
C++ код компилируется в LLVM intermediate representation (IR).
LLVM IR транслируется в WASM.
Emscripten создаёт JavaScript-файл, который:
Пример базовой команды:
emcc bullet.cpp -O3 \
-s WASM=1 \
-s MODULARIZE=1 \
-s EXPORT_NAME="Ammo" \
-s ALLOW_MEMORY_GROWTH=1 \
-o ammo.js
-O3 — оптимизация-s WASM=1 — генерация WebAssembly-s MODULARIZE=1 — экспорт в виде функции-s EXPORT_NAME — имя модуля-s ALLOW_MEMORY_GROWTH — динамическое расширение
памятиWebAssembly использует линейную память — один непрерывный блок байтов. В отличие от Jav * aScript:
Emscripten эмулирует:
Память представлена как ArrayBuffer, поверх которого
создаются типизированные представления:
Emscripten предоставляет два механизма экспорта:
Через флаг:
-s EXPORTED_FUNCTIONS="['_myFunction']"
Функции становятся доступными из JavaScript.
Embind — система биндингов, позволяющая автоматически связывать C++ классы с JavaScript.
Пример C++ кода:
#include <emscripten/bind.h>
using namespace emscripten;
class Vec3 {
public:
float x, y, z;
Vec3(float x, float y, float z) : x(x), y(y), z(z) {}
};
EMSCRIPTEN_BINDINGS(my_module) {
class_<Vec3>("Vec3")
.constructor<float, float, float>()
.property("x", &Vec3::x)
.property("y", &Vec3::y)
.property("z", &Vec3::z);
}
В Jav * aScript:
const v = new Ammo.Vec3(1, 2, 3);
Современная версия Ammo.js компилируется с флагом
MODULARIZE=1, поэтому загрузка выглядит так:
Ammo().then((AmmoLib) => {
const btVector3 = AmmoLib.btVector3;
});
Модуль инициализируется асинхронно, поскольку загрузка WASM — асинхронная операция.
В C++:
newdeleteВ Ammo.js:
const vec = new Ammo.btVector3(1, 2, 3);
Ammo.destroy(vec);
Отсутствие вызова destroy() приводит к утечкам памяти в
WASM-heap.
Emscripten не подключает сборщик мусора JavaScript к управлению C++ объектами.
Emscripten поддерживает:
Флаги:
-s MINIMAL_RUNTIME=1
-s FILESYSTEM=0
-s ASSERTIONS=0
Это уменьшает размер финального .wasm и
.js.
Ранние версии Ammo.js использовали asm.js. Это был подмножество JavaScript, оптимизированное под JIT.
WebAssembly обеспечивает:
Современные сборки используют WASM.
Браузеры поддерживают потоковую компиляцию:
WebAssembly.instantiateStreaming(fetch("ammo.wasm"));
Emscripten автоматически использует этот механизм при поддержке сервера с правильным MIME-типом:
application/wasm
При передаче данных между JS и WASM используются:
Пример выделения памяти:
const ptr = Ammo._malloc(12);
Ammo.HEAPF32[ptr >> 2] = 1.0;
Сдвиг >> 2 используется, поскольку Float32
занимает 4 байта.
WebAssembly:
Emscripten реализует:
Emscripten поддерживает pthreads через Web Workers:
-s USE_PTHREADS=1
Требуется:
В контексте Ammo.js многопоточность используется редко из-за ограничений браузера.
Флаги для отладки:
-g
-s ASSERTIONS=2
-s SAFE_HEAP=1
Позволяют:
Также возможно использование sourcemaps.
Emscripten совместим с:
Для Bullet используется CMake с кастомным toolchain-файлом Emscripten.
Обычно включает:
Glue-код:
Emscripten runtime содержит:
Он обеспечивает мост между C++ ABI и JavaScript.
Факторы, влияющие на производительность:
Вызовы между JS и WASM имеют стоимость, поэтому критические вычисления выполняются полностью внутри WASM.
Совместимость зависит от:
Обновление Emscripten может менять ABI и требовать пересборки.
Компиляция через Emscripten делает возможным использование высокопроизводительной C++ физики в среде JavaScript без переписывания алгоритмов.