Add memory management section to readme
This commit is contained in:
+57
-21
@@ -122,38 +122,62 @@ 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).
|
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).
|
||||||
|
|
||||||
## Usage Guide
|
### Memory Management
|
||||||
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.
|
Embind-wrapped C++ object handles created or returned into JavaScript live on the
|
||||||
|
WebAssembly heap and are **not** garbage-collected by the JS runtime.
|
||||||
|
|
||||||
### Install
|
Any heap-allocated C++ object exposed to JS (e.g. via `new Module.MyClass(...)`
|
||||||
```sh
|
or returned as a pointer/reference from a binding) must be explicitly freed
|
||||||
npm install mujoco
|
when no longer needed to avoid memory leaks.
|
||||||
|
|
||||||
|
Use the generated `.delete()` method on wrapped instances to destroy the
|
||||||
|
underlying C++ object:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const obj = new Module.MyClass(...);
|
||||||
|
// ... use obj ...
|
||||||
|
obj.delete(); // free the C++ memory
|
||||||
```
|
```
|
||||||
|
|
||||||
```ts
|
Be careful to call `.delete()` exactly once per created object (double-delete
|
||||||
import loadMujoco from 'mujoco';
|
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.
|
||||||
|
|
||||||
const mujoco = await loadMujoco();
|
> [!IMPORTANT]
|
||||||
|
> _Embind's documentation strongly recommends that JavaScript code explicitly deletes any C++ object handles it has received._
|
||||||
const model = mujoco.MjModel.fromXMLString(`
|
|
||||||
<mujoco>
|
|
||||||
<worldbody>
|
|
||||||
<geom type="sphere" size="0.1"/>
|
|
||||||
</worldbody>
|
|
||||||
</mujoco>
|
|
||||||
`);
|
|
||||||
|
|
||||||
const data = new mujoco.MjData(model);
|
|
||||||
mujoco.mj_step(model, data);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Copy vs. Reference
|
### 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.
|
||||||
|
|
||||||
#### 1. By Copy (Value-based access)
|
#### 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 a new array containing the contacts at that specific moment in the simulation. If you step the simulation forward, this array will not be updated. You must access `data.contact` again to get the new contact information.
|
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.
|
||||||
|
|
||||||
|
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<T>`. It is essentially a "bridge" object.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export interface MjContactVec extends ClassHandle {
|
||||||
|
/** Appends a new element to the end of the vector, increasing its length by one. */
|
||||||
|
push_back(_0: MjContact): void;
|
||||||
|
|
||||||
|
/** Resizes the vector to contain the specified number of elements, filling new slots with the provided value. */
|
||||||
|
resize(_0: number, _1: MjContact): void;
|
||||||
|
|
||||||
|
/** Returns the total number of elements currently stored in the vector. */
|
||||||
|
size(): number;
|
||||||
|
|
||||||
|
/** Retrieves the element at the specified index, or returns undefined if the index is out of bounds. */
|
||||||
|
get(_0: number): MjContact | undefined;
|
||||||
|
|
||||||
|
/** Overwrites the element at the specified index; returns true if successful or false if the index is invalid. */
|
||||||
|
set(_0: number, _1: MjContact): boolean;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
```typescript
|
```typescript
|
||||||
@@ -165,9 +189,14 @@ mujoco.mj_step(model, data);
|
|||||||
|
|
||||||
// `contacts` is now stale. To get the new contacts, you must access the property again:
|
// `contacts` is now stale. To get the new contacts, you must access the property again:
|
||||||
const newContacts = data.contact;
|
const newContacts = data.contact;
|
||||||
|
|
||||||
|
// Remember to delete all created objects when they are no longer needed.
|
||||||
|
contacts.delete();
|
||||||
|
newContacts.delete();
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 2. By Reference (View-based access)
|
#### 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.
|
||||||
@@ -183,9 +212,13 @@ mujoco.mj_step(model, data);
|
|||||||
|
|
||||||
// `qpos` is automatically updated.
|
// `qpos` is automatically updated.
|
||||||
console.log(qpos[0]); // Print new position
|
console.log(qpos[0]); // Print new position
|
||||||
|
|
||||||
|
// Remember to delete all created objects when they are no longer needed.
|
||||||
|
data.delete();
|
||||||
```
|
```
|
||||||
|
|
||||||
### Data Layout: Row-Major Matrices
|
### 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.
|
||||||
@@ -202,11 +235,13 @@ const element = matrix[i * nCols + j];
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Working with Out Parameters
|
### 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:
|
There are two main scenarios you'll encounter:
|
||||||
|
|
||||||
#### 1. Array-like Out Parameters
|
#### 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:
|
Here's how to use them:
|
||||||
@@ -242,6 +277,7 @@ try {
|
|||||||
```
|
```
|
||||||
|
|
||||||
#### 2. Struct Out Parameters (e.g., mjvCamera*, mjvScene*)
|
#### 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
|
Example: Updating a scene
|
||||||
|
|||||||
Reference in New Issue
Block a user