В библиотеке Oimo.js для физического моделирования часто требуется высокая производительность при симуляции большого количества тел. Использование SharedArrayBuffer позволяет передавать данные между основным потоком JavaScript и рабочими потоками (Web Workers) без копирования, что критично для сложных и интенсивных симуляций.
SharedArrayBuffer создаёт общий блок памяти, доступный одновременно из нескольких потоков. Это позволяет одновременно обновлять позиции, скорости и другие физические параметры тел в многопоточном окружении.
const buffer = new SharedArrayBuffer(1024); // 1024 байта общей памяти
const floatView = new Float32Array(buffer);
Здесь floatView обеспечивает доступ к общей памяти в
виде массива чисел с плавающей запятой. Любые изменения из одного потока
становятся видимыми в другом без дополнительной сериализации или
копирования данных.
Oimo.js поддерживает настройку World с использованием Web Workers. Для этого создаётся отдельный рабочий поток, который обрабатывает шаги симуляции. Основной поток отвечает за рендеринг и взаимодействие с пользователем.
import { World, Body, Box, Vec3 } from 'oimo';
const world = new World({
timestep: 1/60,
broadphase: 2, // SAP
worldScale: 1,
random: true
});
// Создание тел
const box = world.add({
type: "box",
size: [1, 1, 1],
pos: [0, 5, 0],
move: true
});
Для передачи данных в SharedArrayBuffer требуется подготовить массивы
для хранения ключевых параметров: позиции (position),
ориентации (quaternion), линейной и угловой скорости
(velocity, angularVelocity).
const numBodies = world.bodies.length;
const sab = new SharedArrayBuffer(Float32Array.BYTES_PER_ELEMENT * numBodies * 10);
const stateArray = new Float32Array(sab);
Здесь каждый объект использует 10 элементов: 3 для позиции, 4 для кватерниона, 3 для линейной скорости. Этот формат позволяет рабочему потоку и основному потоку синхронизировать состояние без конфликтов.
Рабочий поток выполняет физический шаг и обновляет массив
stateArray. Основной поток читает массив и обновляет
визуальные объекты.
function updateState() {
for (let i = 0; i < world.bodies.length; i++) {
const body = world.bodies[i];
const offset = i * 10;
stateArray[offset] = body.getPosition().x;
stateArray[offset + 1] = body.getPosition().y;
stateArray[offset + 2] = body.getPosition().z;
const q = body.getQuaternion();
stateArray[offset + 3] = q.x;
stateArray[offset + 4] = q.y;
stateArray[offset + 5] = q.z;
stateArray[offset + 6] = q.w;
const v = body.getVelocity();
stateArray[offset + 7] = v.x;
stateArray[offset + 8] = v.y;
stateArray[offset + 9] = v.z;
}
}
Главное — соблюдать последовательность записи и чтения, чтобы основной поток корректно интерпретировал данные. Любые нарушения структуры массива могут привести к некорректной визуализации или ошибкам симуляции.
При одновременном доступе к SharedArrayBuffer может возникнуть
гонка данных. Для предотвращения этого используются
методы объекта Atomics:
Atomics.store() — безопасная запись;Atomics.load() — безопасное чтение;Atomics.exchange() — атомарная замена значения.Пример безопасного обновления позиции:
Atomics.store(stateArray, offset, body.getPosition().x);
const x = Atomics.load(stateArray, offset);
Использование Atomics особенно важно при симуляции с
большим количеством тел и интенсивным рендерингом, когда рабочий поток
может обновлять состояние параллельно с основным потоком.
Для полной многопоточности создаётся Web Worker, который получает SharedArrayBuffer при инициализации:
const worker = new Worker('physicsWorker.js');
worker.postMessage({ buffer: sab, numBodies });
Внутри physicsWorker.js:
self.onmess age = function(e) {
const { buffer, numBodies } = e.data;
const stateArray = new Float32Array(buffer);
function step() {
world.step();
for (let i = 0; i < numBodies; i++) {
const body = world.bodies[i];
const offset = i * 10;
stateArray[offset] = body.getPosition().x;
// остальное аналогично
}
requestAnimationFrame(step);
}
step();
};
Основной поток просто читает stateArray и обновляет
объекты визуализации, без блокировки физического потока.
Использование SharedArrayBuffer и Web Workers в Oimo.js позволяет:
Совмещение многопоточности, Atomics и SharedArrayBuffer создаёт мощный инструмент для профессиональных физических симуляций на JavaScript, обеспечивая производительность, близкую к нативным приложениям.