WebAssembly (Emscripten)

MuJoCo-rs can be compiled to WebAssembly via Emscripten, allowing simulations to run under Node.js. Rendering and visualization are not currently supported — only headless (physics-only) execution is available.

Note

Only the wasm32-unknown-emscripten Rust target is supported. Rendering and visualization features are not available for this target.

Additional prerequisites for MuJoCo-rs

In addition to the emsdk prerequisites described in MuJoCo’s documentation, you need:

  1. Rust wasm32-unknown-emscripten target

    rustup target add wasm32-unknown-emscripten
    
  2. MuJoCo-rs repository with the submodule (if not already done during installation):

    git submodule update --init --recursive
    

Note

Emscripten’s tools (emcc, emcmake, …) must be on your PATH before building. After installing emsdk, activate it in each shell session with source /path/to/emsdk/emsdk_env.sh (see the emsdk documentation). Without this step the emcmake commands below fail with “command not found”.

Building MuJoCo

Use either the mujoco/ submodule (already initialized in the MuJoCo-rs repository) or a fresh clone of the official MuJoCo 3.9.0 release as the source directory. The official unmodified tag is sufficient for WASM — the submodule patches are only required for native/C++ features.

To clone the official release:

git clone https://github.com/google-deepmind/mujoco.git --branch 3.9.0 --depth 1

Then build (replace mujoco with the path to whichever source you chose):

emcmake cmake -S mujoco -B mujoco/build/wasm
EMCC_CFLAGS="-fwasm-exceptions" cmake --build mujoco/build/wasm --target mujoco --parallel

EMCC_CFLAGS="-fwasm-exceptions" is required because MuJoCo’s CMakeLists.txt injects -fexceptions, which generates JS-only emscripten_longjmp calls that are incompatible with the Rust side. -fwasm-exceptions overrides it (last flag wins) and switches to native WebAssembly exception handling, keeping the ABI consistent with the Rust crate.

The static archive is written to mujoco/build/wasm/lib/libmujoco.a.

Building the Rust crate

Point MUJOCO_STATIC_LINK_DIR at the lib/ directory and build with the Emscripten target. EMCC_CFLAGS must match the flag used when building MuJoCo (see Building MuJoCo for the explanation):

EMCC_CFLAGS="-fwasm-exceptions" \
    MUJOCO_STATIC_LINK_DIR=$(realpath mujoco/build/wasm/lib) \
    cargo build --example basic --target wasm32-unknown-emscripten

Cargo produces two output artifacts inside target/wasm32-unknown-emscripten/debug/examples/:

  • basic.js — Emscripten JS glue / loader

  • basic.wasm — the compiled WebAssembly module

Memory growth for complex models

The Emscripten WASM heap defaults to 16 MB. Complex models (e.g. many geoms, tendons, or connected bodies) can exceed this limit during mj_compile() and will panic with "Could not allocate memory". Add -sALLOW_MEMORY_GROWTH=1 as a linker argument via RUSTFLAGS:

EMCC_CFLAGS="-fwasm-exceptions" \
    MUJOCO_STATIC_LINK_DIR=$(realpath mujoco/build/wasm/lib) \
    RUSTFLAGS="-C link-arg=-sALLOW_MEMORY_GROWTH=1" \
    cargo build --example <your_example> --target wasm32-unknown-emscripten

Running

Use the Node.js binary bundled with emsdk, or any compatible Node.js >= 16:

NODE_BIN="$(command -v node 2>/dev/null \
    || echo '/path/to/emsdk/node/<version>/bin/node')"
"$NODE_BIN" target/wasm32-unknown-emscripten/debug/examples/basic.js

Replace <version> with the directory name found under /path/to/emsdk/node/ (e.g. 22.16.0_64bit).

Expected output: Step 0 through Step 999.