Skip to content

Single thread npm#3184

Open
MatiasManevi wants to merge 7 commits intogoogle-deepmind:mainfrom
MatiasManevi:single-thread-npm
Open

Single thread npm#3184
MatiasManevi wants to merge 7 commits intogoogle-deepmind:mainfrom
MatiasManevi:single-thread-npm

Conversation

@MatiasManevi
Copy link
Contributor

@MatiasManevi MatiasManevi commented Mar 18, 2026

This PR modifies the build pipeline to generate and package two distinct versions of the MuJoCo WASM bindings: Single-Threaded and Multi-Threaded.

Previously, users had to choose one build type at compile-time. Now, both are shipped in a single @mujoco/mujoco npm package, allowing developers to opt-in to multi-threading by changing their import path.

Fixes #3144


Key Changes

1. CMake Configuration

  • Introduced the MUJOCO_WASM_THREADS option (defaults to ON).
  • Conditioned Emscripten linker flags (-pthread, PTHREAD_POOL_SIZE) on this toggle.

2. Build Pipeline (build_steps.sh)

  • Updated the WASM build function to execute two sequential passes:
    • Pass 1: Single-thread build (outputs to package root).
    • Pass 2: Multi-thread build (outputs to package ./mt).

3. Package Exports (package.json)

  • Implemented the exports field to support subpath exports.

if(MUJOCO_WASM_THREADS)
list(APPEND EMCC_LINKER_FLAGS
"-pthread"
"-s PTHREAD_POOL_SIZE=navigator.hardwareConcurrency"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While we're here lets set this PTHREAD_POOL_SIZE=0

Copy link
Contributor Author

@MatiasManevi MatiasManevi Mar 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested it and seems that both variants are now behaving in the "same way" => multi-threading won't require COOP/COEP headers anymore. Is that expected?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way I can quickly test this (maybe a function we can call) to ensure it won't break anything?

Copy link
Contributor Author

@MatiasManevi MatiasManevi Mar 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, if 0 is the value we want, we shouldn't even set it. because its default value is zero already, wdyt?

Copy link
Contributor Author

@MatiasManevi MatiasManevi Mar 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Running this test

import { MainModule, MjVFS, MjSpec, MjModel } from "../../dist/mt/mujoco"
import loadMujoco from "../../dist/mt/mujoco.js"

declare function loadMujoco(): Promise<MainModule>;

async function main() {
  const mujoco: MainModule = await loadMujoco();

  const assetsCount = 10;
  let xml = `<mujoco>
      <asset>`;
  const cube1 = `
    v -1 -1  1
    v  1 -1  1
    v -1  1  1
    v  1  1  1
    v -1  1 -1
    v  1  1 -1
    v -1 -1 -1
    v  1 -1 -1`;
  const dummyTex = 'dummy texture data';

  for (let i = 0; i < assetsCount; i++) {
    xml += `<mesh name="cube${i}" file="cube${i}.obj"/>`;
    xml += `<texture name="tex${i}" type="2d" builtin="checker" width="16" height="16"/>`;
  }
  xml += `</asset><worldbody>`;
  for (let i = 0; i < assetsCount; i++) {
    xml += `<geom type="mesh" mesh="cube${i}"/>`;
  }
  xml += `</worldbody></mujoco>`;

  let spec: MjSpec | null = null;
  let model: MjModel | null = null;
  let vfs: MjVFS | null = null;
  try {
    spec = mujoco.parseXMLString(xml);

    vfs = new mujoco.MjVFS();
    for (let i = 0; i < assetsCount; i++) {
      vfs.addBuffer(`cube${i}.obj`, new TextEncoder().encode(cube1));
      vfs.addBuffer(`tex${i}.txt`, new TextEncoder().encode(dummyTex));
    }

    model = mujoco.mj_compile(spec, vfs);
    console.log(model.nmesh, assetsCount);
    console.log(model.ntex, assetsCount);

    for (let i = 0; i < assetsCount; i++) {
      const meshId = mujoco.mj_name2id(
        model, mujoco.mjtObj.mjOBJ_MESH.value, `cube${i}`);
      console.log(meshId, i);
      const tex = model.tex(`tex${i}`);
      console.log(tex!.name, `tex${i}`);
    }
  } finally {
    spec?.delete();
    vfs?.delete();
    model?.delete();
  }
}

main()

With PTHREAD_POOL_SIZE=0 throws an error.

image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding spec.compiler.usethread = 0; to the script would make it run, but if we are importing mujoco from /mt is that really what we want?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

WASM 3.5.0 default pthread configuration and recommended deployment strategy?

3 participants