diff --git a/wasm/README.md b/wasm/README.md index 3eb78fd9..2938ec5c 100644 --- a/wasm/README.md +++ b/wasm/README.md @@ -123,8 +123,8 @@ joint using `data.jnt('myjoint')`. For more details and examples of how to use named access, please refer to the [named access tests](tests/bindings_test.ts#L1876-L2378) and [documentation](https://mujoco.readthedocs.io/en/stable/python.html#named-access). ### Memory Management -Embind-wrapped C++ object handles created or returned into JavaScript live on the -WebAssembly heap and are **not** garbage-collected by the JS runtime. +Embind-wrapped C++ object handles created or returned into JavaScript live on +the WebAssembly heap and are **not** garbage-collected by the JS runtime. Any heap-allocated C++ object exposed to JS (e.g. via `new Module.MyClass(...)` or returned as a pointer/reference from a binding) must be explicitly freed @@ -144,19 +144,26 @@ is an error). In JS code paths that may throw or return early, ensure deletion happens in finally blocks or wrap lifetime management to avoid leaks. > [!IMPORTANT] -> _Embind's documentation strongly recommends that JavaScript code explicitly deletes any C++ object handles it has received._ +> _Embind's documentation strongly recommends that JavaScript code explicitly +> deletes any C++ object handles it has received._ ### Copy vs. Reference -When interacting with MuJoCo objects through the WASM bindings, it's important to understand how data is accessed. Properties on objects like `MjModel` and `MjData` can expose data in two ways: by copy or by reference. +When interacting with MuJoCo objects through the WASM bindings, it's important +to understand how data is accessed. Properties on objects like `MjModel` and +`MjData` can expose data in two ways: by copy or by reference. #### 1. By Copy (Value-based access) -Some properties return a copy of the data at the time of access. This is common for complex data structures that need to be marshalled from C++ to JavaScript. +Some properties return a copy of the data at the time of access. This is common +for complex data structures that need to be marshalled from C++ to JavaScript. -A key example is `MjData.contact`. When you access `data.contact`, you get an object containing a copy of the contacts at that specific moment in the simulation. +A key example is `MjData.contact`. When you access `data.contact`, you get an +object containing a copy of the contacts at that specific moment in the +simulation. -If you step the simulation forward, they will not be updated. You must access `data.contact` again to get the new contact information. +If you step the simulation forward, they will not be updated. You must access +`data.contact` again to get the new contact information. The object you get is a JavaScript proxy interface generated by [Emscripten’s Embind library](https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html#built-in-type-conversions) when you expose a `std::vector` using `register_vector`. It is essentially a "bridge" object. @@ -193,9 +200,14 @@ newContacts.delete(); #### 2. By Reference (View-based access) -Many properties, especially large numerical arrays, return a live view directly into the WebAssembly memory. This is highly efficient as it avoids copying large amounts of data. +Many properties, especially large numerical arrays, return a live view directly +into the WebAssembly memory. This is highly efficient as it avoids copying large +amounts of data. -A key example is `MjData.qpos` (joint positions). When you get a reference to this array, it points directly to the simulation's state data. Any changes in the simulation (e.g., after a call to `mj_step`) will be immediately reflected in this array. +A key example is `MjData.qpos` (joint positions). When you get a reference to +this array, it points directly to the simulation's state data. Any changes in +the simulation (e.g., after a call to `mj_step`) will be immediately reflected +in this array. ```typescript // `qpos` is a live view into the simulation state. @@ -215,9 +227,13 @@ data.delete(); ### Data Layout: Row-Major Matrices -When a function from the MuJoCo C API returns a matrix (or needs a matrix as input), these are represented in the JavaScript bindings as flat, one-dimensional `TypedArray`'s. The elements are stored in row-major order. +When a function from the MuJoCo C API returns a matrix (or needs a matrix as +input), these are represented in the JavaScript bindings as flat, +one-dimensional `TypedArray`'s. The elements are stored in row-major order. -For example, a 3x10 matrix will be returned as a flat array with 30 elements. The first 10 elements represent the first row, the next 10 represent the second row, and so on. +For example, a 3x10 matrix will be returned as a flat array with 30 elements. +The first 10 elements represent the first row, the next 10 represent the second +row, and so on. Example: Accessing an element at `(row, col)` ```typescript @@ -232,13 +248,19 @@ const element = matrix[i * nCols + j]; ### Working with Out Parameters -Many functions in the MuJoCo C API use "out parameters" to return data. This means instead of returning a value, they write the result into one of the arguments passed to them by reference (using pointers). In our JavaScript bindings, you'll need to handle these cases specifically. +Many functions in the MuJoCo C API use "out parameters" to return data. This +means instead of returning a value, they write the result into one of the +arguments passed to them by reference (using pointers). In our JavaScript +bindings, you'll need to handle these cases specifically. There are two main scenarios you'll encounter: #### 1. Array-like Out Parameters -When a function expects a pointer to a primitive type (like `mjtNum*` or `int*`) to write an array of values, you need to pre-allocate memory for the result on the JavaScript side. We provide helper classes for this: `mujoco.Uint8Buffer`, `mujoco.DoubleBuffer`, `mujoco.FloatBuffer`, and `mujoco.IntBuffer`. +When a function expects a pointer to a primitive type (like `mjtNum*` or `int*`) +to write an array of values, you need to pre-allocate memory for the result on +the JavaScript side. We provide helper classes for this: `mujoco.Uint8Buffer`, +`mujoco.DoubleBuffer`, `mujoco.FloatBuffer`, and `mujoco.IntBuffer`. Here's how to use them: @@ -249,7 +271,8 @@ Here's how to use them: Example: Rotating a vector -The function `mju_rotVecQuat` rotates a vector `vec` by a quaternion `quat` and stores the result in the `res` out parameter. +The function `mju_rotVecQuat` rotates a vector `vec` by a quaternion `quat` and +stores the result in the `res` out parameter. ```typescript // Create a buffer to hold the 3D vector result. @@ -274,11 +297,14 @@ try { #### 2. Struct Out Parameters (e.g., mjvCamera*, mjvScene*) -When a function modifies a struct passed by pointer, you should pass an instance of the corresponding JavaScript wrapper class. The underlying C++ struct will be modified in place. +When a function modifies a struct passed by pointer, you should pass an instance +of the corresponding JavaScript wrapper class. The underlying C++ struct will be + modified in place. Example: Updating a scene -The function `mjv_updateScene` populates an `mjvScene` object with information from `mjModel` and `mjData`. +The function `mjv_updateScene` populates an `mjvScene` object with information +from `mjModel` and `mjData`. ```typescript // Create instances of the necessary structs. const model = mujoco.MjModel.loadFromXML(xmlContent); @@ -312,7 +338,8 @@ data.delete(); model.delete(); ``` -As with buffers, you are responsible for managing the memory of these struct instances and must call `.delete()` on them when you are finished. +As with buffers, you are responsible for managing the memory of these struct + instances and must call `.delete()` on them when you are finished. ### Enums Access via `.value`: @@ -327,13 +354,16 @@ Scalar constants will be accessed the same way they are on python, simply: mujoco.mjNEQDATA ``` -Due to Embind limitations, more complex constants that are not scalar, but are represented in more dimensions are exposed as functions. E.g to use `mujoco.mjFRAMESTRING` you will need to call a function: +Due to Embind limitations, more complex constants that are not scalar, but are +represented in more dimensions are exposed as functions. E.g. to use +`mujoco.mjFRAMESTRING` you will need to call a function: ```javascript mujoco.get_mjFRAMESTRING() ``` -This will return a javascript array representation of the values in MuJoCo `mjFRAMESTRING`. +This will return a javascript array representation of the values in MuJoCo +`mjFRAMESTRING`. ## Development