mujoco-react 12.0.0 → 12.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,6 +16,19 @@ Composable [React Three Fiber](https://docs.pmnd.rs/react-three-fiber) wrapper a
16
16
  npm install mujoco-react three @react-three/fiber @react-three/drei
17
17
  ```
18
18
 
19
+ ## Running policies and simulation workloads
20
+
21
+ Use `const runtime = await api.createRuntime()` after `onReady`, then await
22
+ `runtime.observe()` and `runtime.advance({ steps, controls })`. Physics runs in a
23
+ dedicated worker; commands reject when busy instead of accumulating. The runtime
24
+ supports cancellation, reset, full snapshots, synchronized camera observations,
25
+ and separate physics/capture timing. Use `<CameraView>` for GPU-only previews.
26
+
27
+ The viewer has an 8 ms / 100-step per-frame budget. Equivalent inline scene
28
+ configs preserve the loaded model. See the [simulation runtime guide](https://dadd.mintlify.app/guides/simulation-runtime)
29
+ for ownership, VLA loops, bundling, and performance limits, or try the
30
+ [worker demo](https://mujoco-react-example.pages.dev/?demo=runtime).
31
+
19
32
  ## Vite Plugin and Type-Safe Names
20
33
 
21
34
  Use the Vite plugin to generate declaration merging for actuator, sensor, body, joint, site, geom, and keyframe names:
@@ -36,6 +49,10 @@ export default defineConfig({
36
49
  });
37
50
  ```
38
51
 
52
+ The plugin also preserves worker asset URLs in Vite development by excluding
53
+ `mujoco-react` from dependency prebundling. Without it, set
54
+ `optimizeDeps: { exclude: ["mujoco-react"] }` when using the worker runtime.
55
+
39
56
  The plugin writes `src/mujoco-register.gen.ts` during dev and build. Commit that generated file. Vite auto-loads it, so app code imports generated values from `mujoco-react`, not from the generated file:
40
57
 
41
58
  ```ts
@@ -1153,8 +1170,8 @@ const frame = await apiRef.current?.captureCameraFrame({
1153
1170
  ```
1154
1171
 
1155
1172
  **Live streams.** `useCameraStream(canvasRef, options)` renders a camera into a
1156
- DOM `<canvas>` every frame (offscreen render → blit: no PNG round-trip, no render-
1157
- loop takeover, splat scenes stream safely). Call it inside `<MujocoCanvas>`; the
1173
+ DOM `<canvas>` at 10 FPS by default (offscreen render → GPU readback → blit).
1174
+ It avoids PNG encoding but still pays readback and pixel-conversion costs. Call it inside `<MujocoCanvas>`; the
1158
1175
  canvas can live anywhere in your DOM:
1159
1176
 
1160
1177
  ```tsx
@@ -1167,8 +1184,8 @@ function WristStream({ canvasRef }) {
1167
1184
  For a transparent picture-in-picture overlay, use `<CameraView>` (or
1168
1185
  `useCameraViewport` to track your own element). It scissors into the main canvas —
1169
1186
  cheaper, but it takes over the render loop while mounted (incompatible with
1170
- postprocessing, occluded by opaque DOM), so prefer `useCameraStream` for panel
1171
- tiles:
1187
+ postprocessing, occluded by opaque DOM), so use `useCameraStream` only when HTML compositing requires a separate canvas.
1188
+ Prefer GPU viewports for live simulation previews:
1172
1189
 
1173
1190
  ```tsx
1174
1191
  <MujocoCanvas config={config}>
@@ -0,0 +1,137 @@
1
+ // src/runtime/engine.ts
2
+ var displayFields = [
3
+ "qpos",
4
+ "qvel",
5
+ "ctrl",
6
+ "act",
7
+ "plastic",
8
+ "sensordata",
9
+ "xpos",
10
+ "xquat",
11
+ "xmat",
12
+ "geom_xpos",
13
+ "geom_xmat",
14
+ "site_xpos",
15
+ "site_xmat",
16
+ "cam_xpos",
17
+ "cam_xmat",
18
+ "flexvert_xpos",
19
+ "ten_length",
20
+ "wrap_xpos",
21
+ "cvel",
22
+ "cfrc_ext",
23
+ "qfrc_applied",
24
+ "xfrc_applied",
25
+ "qfrc_bias",
26
+ "ten_wrapadr",
27
+ "ten_wrapnum"
28
+ ];
29
+ var SimulationEngine = class {
30
+ constructor(mj, initial) {
31
+ this.mj = mj;
32
+ this.initial = initial;
33
+ mj.FS.writeFile("/runtime.mjb", initial.model);
34
+ const vfs = new mj.MjVFS();
35
+ try {
36
+ this.model = mj.MjModel.from_binary_path("/runtime.mjb", vfs);
37
+ } finally {
38
+ vfs.delete();
39
+ mj.FS.unlink("/runtime.mjb");
40
+ }
41
+ this.data = new mj.MjData(this.model);
42
+ try {
43
+ this.reset();
44
+ } catch (error) {
45
+ this.dispose();
46
+ throw error;
47
+ }
48
+ }
49
+ model;
50
+ data;
51
+ reset() {
52
+ this.mj.mj_resetData(this.model, this.data);
53
+ this.restore(this.initial.state);
54
+ }
55
+ restore(state) {
56
+ if (state.signature !== this.mj.mjtState.mjSTATE_INTEGRATION.value || state.values.length !== this.mj.mj_stateSize(this.model, state.signature) || !state.values.every(Number.isFinite)) throw new Error("Invalid runtime snapshot");
57
+ this.mj.mj_setState(this.model, this.data, Array.from(state.values), state.signature);
58
+ this.mj.mj_forward(this.model, this.data);
59
+ }
60
+ setControls(controls) {
61
+ if (controls) {
62
+ if (controls.length !== this.data.ctrl.length || !controls.every(Number.isFinite)) throw new Error("Invalid runtime controls");
63
+ this.data.ctrl.set(controls);
64
+ }
65
+ }
66
+ step() {
67
+ const expected = this.data.time + this.model.opt.timestep;
68
+ this.mj.mj_step(this.model, this.data);
69
+ if (Math.abs(this.data.time - expected) > 1e-7 || !this.data.qpos.every(Number.isFinite)) {
70
+ throw new Error("Physics became unstable or reset unexpectedly");
71
+ }
72
+ }
73
+ save() {
74
+ const signature = this.mj.mjtState.mjSTATE_INTEGRATION.value;
75
+ const buffer = new this.mj.DoubleBuffer(this.mj.mj_stateSize(this.model, signature));
76
+ try {
77
+ this.mj.mj_getState(this.model, this.data, buffer, signature);
78
+ return { signature, values: new Float64Array(buffer.GetView()) };
79
+ } finally {
80
+ buffer.delete();
81
+ }
82
+ }
83
+ snapshot(complete = true) {
84
+ const integration = complete ? this.save() : void 0;
85
+ if (integration) this.mj.mj_forward(this.model, this.data);
86
+ try {
87
+ return {
88
+ time: this.data.time,
89
+ fields: Object.fromEntries(displayFields.map((key) => [key, new Float64Array(this.data[key])])),
90
+ contacts: complete ? this.contacts() : [],
91
+ ...integration ? { integration } : {}
92
+ };
93
+ } finally {
94
+ if (integration) this.mj.mj_setState(this.model, this.data, Array.from(integration.values), integration.signature);
95
+ }
96
+ }
97
+ contacts() {
98
+ const result = [], vector = this.data.contact;
99
+ const names = this.model.names, addresses = this.model.name_geomadr;
100
+ const name = (id) => {
101
+ if (id < 0) return "flex";
102
+ const start = addresses[id];
103
+ let end = start;
104
+ while (end < names.length && names[end]) end++;
105
+ return new TextDecoder().decode(names.subarray(start, end));
106
+ };
107
+ try {
108
+ for (let i = 0; i < this.data.ncon; i++) {
109
+ const c = vector.get(i);
110
+ if (!c) continue;
111
+ try {
112
+ result.push({
113
+ geom1: c.geom1,
114
+ geom2: c.geom2,
115
+ geom1Name: name(c.geom1),
116
+ geom2Name: name(c.geom2),
117
+ pos: Array.from(c.pos),
118
+ depth: c.dist
119
+ });
120
+ } finally {
121
+ c.delete();
122
+ }
123
+ }
124
+ } finally {
125
+ vector.delete();
126
+ }
127
+ return result;
128
+ }
129
+ dispose() {
130
+ this.data.delete();
131
+ this.model.delete();
132
+ }
133
+ };
134
+
135
+ export { SimulationEngine, displayFields };
136
+ //# sourceMappingURL=chunk-U2Y2BODT.js.map
137
+ //# sourceMappingURL=chunk-U2Y2BODT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/runtime/engine.ts"],"names":[],"mappings":"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","file":"chunk-U2Y2BODT.js","sourcesContent":["import type { MainModule, MjModel, MjData } from '@mujoco/mujoco';\nimport type { ContactInfo } from '../types';\n\nexport const displayFields = ['qpos', 'qvel', 'ctrl', 'act', 'plastic', 'sensordata', 'xpos', 'xquat', 'xmat',\n 'geom_xpos', 'geom_xmat', 'site_xpos', 'site_xmat', 'cam_xpos', 'cam_xmat',\n 'flexvert_xpos', 'ten_length', 'wrap_xpos', 'cvel', 'cfrc_ext', 'qfrc_applied', 'xfrc_applied', 'qfrc_bias', 'ten_wrapadr', 'ten_wrapnum'] as const;\nexport interface EngineState { signature: number; values: Float64Array }\nexport interface EngineInitialization { model: Uint8Array; state: EngineState }\nexport interface DisplayState {\n time: number;\n fields: Record<typeof displayFields[number], Float64Array>;\n contacts: ContactInfo[];\n integration?: EngineState;\n}\nexport class SimulationEngine {\n readonly model: MjModel;\n readonly data: MjData;\n constructor(private mj: MainModule, private initial: EngineInitialization) {\n mj.FS.writeFile('/runtime.mjb', initial.model);\n const vfs = new mj.MjVFS();\n try { this.model = mj.MjModel.from_binary_path('/runtime.mjb', vfs); }\n finally { vfs.delete(); mj.FS.unlink('/runtime.mjb'); }\n this.data = new mj.MjData(this.model);\n try { this.reset(); } catch (error) { this.dispose(); throw error; }\n }\n reset() {\n this.mj.mj_resetData(this.model, this.data);\n this.restore(this.initial.state);\n }\n restore(state: EngineState) {\n if (state.signature !== this.mj.mjtState.mjSTATE_INTEGRATION.value ||\n state.values.length !== this.mj.mj_stateSize(this.model, state.signature) ||\n !state.values.every(Number.isFinite)) throw new Error('Invalid runtime snapshot');\n this.mj.mj_setState(this.model, this.data, Array.from(state.values), state.signature);\n this.mj.mj_forward(this.model, this.data);\n }\n setControls(controls?: Float64Array) {\n if (controls) {\n if (controls.length !== this.data.ctrl.length || !controls.every(Number.isFinite)) throw new Error('Invalid runtime controls');\n this.data.ctrl.set(controls);\n }\n }\n step() {\n const expected = this.data.time + this.model.opt.timestep;\n this.mj.mj_step(this.model, this.data);\n if (Math.abs(this.data.time - expected) > 1e-7 || !this.data.qpos.every(Number.isFinite)) {\n throw new Error('Physics became unstable or reset unexpectedly');\n }\n }\n save(): EngineState {\n const signature = this.mj.mjtState.mjSTATE_INTEGRATION.value;\n const buffer = new this.mj.DoubleBuffer(this.mj.mj_stateSize(this.model, signature));\n try {\n this.mj.mj_getState(this.model, this.data, buffer, signature);\n return { signature, values: new Float64Array(buffer.GetView()) };\n } finally { buffer.delete(); }\n }\n snapshot(complete = true): DisplayState {\n const integration = complete ? this.save() : undefined;\n // mj_step integrates qpos after computing derived fields. Refresh completed\n // observations at their reported time, preserving solver warmstart exactly.\n if (integration) this.mj.mj_forward(this.model, this.data);\n try { return {\n time: this.data.time,\n fields: Object.fromEntries(displayFields.map(key => [key, new Float64Array(this.data[key])])) as DisplayState['fields'],\n contacts: complete ? this.contacts() : [],\n ...(integration ? { integration } : {}),\n }; } finally {\n if (integration) this.mj.mj_setState(this.model, this.data, Array.from(integration.values), integration.signature);\n }\n }\n private contacts(): ContactInfo[] {\n const result: ContactInfo[] = [], vector = this.data.contact;\n const names = this.model.names, addresses = this.model.name_geomadr;\n const name = (id: number) => {\n if (id < 0) return 'flex';\n const start = addresses[id]; let end = start;\n while (end < names.length && names[end]) end++;\n return new TextDecoder().decode(names.subarray(start, end));\n };\n try {\n for (let i = 0; i < this.data.ncon; i++) {\n const c = vector.get(i);\n if (!c) continue;\n try { result.push({ geom1: c.geom1, geom2: c.geom2, geom1Name: name(c.geom1), geom2Name: name(c.geom2),\n pos: Array.from(c.pos) as [number, number, number], depth: c.dist }); }\n finally { c.delete(); }\n }\n } finally { vector.delete(); }\n return result;\n }\n dispose() { this.data.delete(); this.model.delete(); }\n}\n"]}
@@ -315,8 +315,8 @@ function prepareCaptureCamera(camera, options, fallbackCamera, width, height) {
315
315
  applyProjectionMatrix(camera, options.projectionMatrix);
316
316
  applyCameraPose(camera, options, fallbackCamera);
317
317
  }
318
- function readRenderTargetToCanvas(renderer, target, canvas, context, pixels, imageData, width, height, outputColorSpace, flipX = false) {
319
- renderer.readRenderTargetPixels(target, 0, 0, width, height, pixels);
318
+ function readRenderTargetToCanvas(renderer, target, canvas, context, pixels, imageData, width, height, outputColorSpace, flipX = false, readback = true) {
319
+ if (readback) renderer.readRenderTargetPixels(target, 0, 0, width, height, pixels);
320
320
  const rowBytes = width * 4;
321
321
  const encodeSrgb = outputColorSpace === THREE.SRGBColorSpace;
322
322
  for (let y = 0; y < height; y += 1) {
@@ -532,6 +532,15 @@ function createCameraFrameCaptureSession(renderer, scene, fallbackCamera, option
532
532
  const drawContext = context;
533
533
  const pixels = new Uint8Array(width * height * 4);
534
534
  const imageData = drawContext.createImageData(width, height);
535
+ let inFlight = false, disposed = false;
536
+ function assertAvailable() {
537
+ if (disposed) throw new Error("Camera capture session is disposed");
538
+ if (inFlight) throw new Error("Camera capture session is busy; await the current capture");
539
+ }
540
+ function releaseResources() {
541
+ target.dispose();
542
+ if (isolatedRenderer && !isolatedRenderer.cached) isolatedRenderer.renderer.dispose();
543
+ }
535
544
  function resolveCaptureOptions(nextOptions = {}) {
536
545
  const captureOptions = { ...options, ...nextOptions };
537
546
  if (shouldUseRenderIsolation(captureOptions) !== shouldUseRenderIsolation(options)) {
@@ -614,9 +623,11 @@ function createCameraFrameCaptureSession(renderer, scene, fallbackCamera, option
614
623
  };
615
624
  }
616
625
  function capture(nextOptions = {}) {
626
+ assertAvailable();
617
627
  return renderPreparedCapture(resolveCaptureOptions(nextOptions));
618
628
  }
619
629
  function capturePixels(nextOptions = {}) {
630
+ assertAvailable();
620
631
  const captureOptions = resolveCaptureOptions(nextOptions);
621
632
  renderCaptureToTarget(captureOptions, () => {
622
633
  sessionRenderer.readRenderTargetPixels(target, 0, 0, width, height, pixels);
@@ -643,6 +654,16 @@ function createCameraFrameCaptureSession(renderer, scene, fallbackCamera, option
643
654
  return { ...tensor, camera, source: result.source };
644
655
  }
645
656
  async function captureAsync(nextOptions = {}) {
657
+ assertAvailable();
658
+ inFlight = true;
659
+ try {
660
+ return await captureAsyncInternal(nextOptions);
661
+ } finally {
662
+ inFlight = false;
663
+ if (disposed) releaseResources();
664
+ }
665
+ }
666
+ async function captureAsyncInternal(nextOptions = {}) {
646
667
  const captureOptions = resolveCaptureOptions(nextOptions);
647
668
  runCapturePreRenderHooks(scene);
648
669
  scene.updateMatrixWorld(true);
@@ -707,7 +728,25 @@ function createCameraFrameCaptureSession(renderer, scene, fallbackCamera, option
707
728
  restoreRendererState(sessionRenderer, previousState);
708
729
  }
709
730
  }
710
- return renderPreparedCapture(captureOptions);
731
+ let readback;
732
+ renderCaptureToTarget(captureOptions, () => {
733
+ readback = sessionRenderer.readRenderTargetPixelsAsync(target, 0, 0, width, height, pixels);
734
+ });
735
+ await readback;
736
+ readRenderTargetToCanvas(
737
+ sessionRenderer,
738
+ target,
739
+ canvas,
740
+ drawContext,
741
+ pixels,
742
+ imageData,
743
+ width,
744
+ height,
745
+ sessionRenderer.outputColorSpace,
746
+ captureOptions.flipX ?? false,
747
+ false
748
+ );
749
+ return { canvas, camera, width, height, source: getCameraFrameCaptureSource(captureOptions) };
711
750
  }
712
751
  return {
713
752
  width,
@@ -756,10 +795,9 @@ function createCameraFrameCaptureSession(renderer, scene, fallbackCamera, option
756
795
  return { ...result, blob, type };
757
796
  },
758
797
  dispose() {
759
- target.dispose();
760
- if (isolatedRenderer && !isolatedRenderer.cached) {
761
- isolatedRenderer.renderer.dispose();
762
- }
798
+ if (disposed) return;
799
+ disposed = true;
800
+ if (!inFlight) releaseResources();
763
801
  }
764
802
  };
765
803
  }
@@ -1433,5 +1471,5 @@ function clamp01(value) {
1433
1471
  */
1434
1472
 
1435
1473
  export { CAMERA_FRAME_CAPTURE_PRE_RENDER_USER_DATA_KEY, CAMERA_FRAME_CAPTURE_RENDER_USER_DATA_KEY, CAPTURE_EXCLUDE_KEY, ModelActuators, ModelBodies, ModelCameras, ModelGeoms, ModelJoints, ModelKeyframes, ModelResources, ModelSensors, ModelSites, ScenarioLighting, SplatEnvironment, SplatEnvironmentReadinessStatus, VisualScenarioEffects, captureCameraFrame, captureCameraFrameBlob, captureCameraFrameTensor, createCameraFrameCaptureSession, createCaptureCamera, createPairedSplatEnvironment, createSparkSplatViewerUrl, createSplatEnvironmentUserData, createSplatSceneConfig, createVisualScenarioExecutionContext, dataUrlToPolicyImageTensor, getContact, getScenarioBackground, getScenarioCameraPosition, getSplatEnvironmentReadiness, imageDataToPolicyImageTensor, pixelsToPolicyImageTensor, prepareCaptureCamera, registerModelResources, renderCameraFrameToCanvas, useSplatEnvironment, useSplatSceneConfig, useVisualScenarioEffects, useVisualScenarioExecutionContext, withContacts, withSplatEnvironment };
1436
- //# sourceMappingURL=chunk-7BCIHJMW.js.map
1437
- //# sourceMappingURL=chunk-7BCIHJMW.js.map
1474
+ //# sourceMappingURL=chunk-XD3AY7XZ.js.map
1475
+ //# sourceMappingURL=chunk-XD3AY7XZ.js.map