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).
|
||||
|
||||
## Usage Guide
|
||||
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.
|
||||
### 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.
|
||||
|
||||
### Install
|
||||
```sh
|
||||
npm install mujoco
|
||||
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
|
||||
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
|
||||
import loadMujoco from 'mujoco';
|
||||
Be careful to call `.delete()` exactly once per created object (double-delete
|
||||
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();
|
||||
|
||||
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);
|
||||
```
|
||||
> [!IMPORTANT]
|
||||
> _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.
|
||||
|
||||
#### 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.
|
||||
|
||||
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:
|
||||
```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:
|
||||
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)
|
||||
|
||||
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.
|
||||
@@ -183,9 +212,13 @@ mujoco.mj_step(model, data);
|
||||
|
||||
// `qpos` is automatically updated.
|
||||
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
|
||||
|
||||
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.
|
||||
@@ -202,11 +235,13 @@ 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.
|
||||
|
||||
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`.
|
||||
|
||||
Here's how to use them:
|
||||
@@ -242,6 +277,7 @@ 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.
|
||||
|
||||
Example: Updating a scene
|
||||
|
||||
Reference in New Issue
Block a user