dimforge / rapier.js

Official JavaScript bindings for the Rapier physics engine.
https://rapier.rs
Apache License 2.0
427 stars 58 forks source link

Make the API more idiomatic and as flexible as the Rust API #1

Open sebcrozet opened 4 years ago

sebcrozet commented 4 years ago

Problems with the current bindings

The current JS bindings need to be reworked to address the following problems:

  1. The JS bindings generated by wasm-bindgen are great, but they require the end-user to do manual memory management for any struct returned from rust. For example rigidBody.linvel() will return a Vector which has to be explicitly freed by the end-user with vector.free(). This is very bad because it is very easy to forget to free something, which will ultimately lead to leaks.
  2. The API exposed through JS is very different from the Rust API. Some differences are justified, but others are not. In particular, JS bindings expose a monolithic World structure which is convenient but will become quite limited in the future once rapier exposes more pipelines than the physics pipeline.
  3. The fact that the JS API is very different from the Rust API makes it more difficult to design. Two different APIs imply twice as much design work.

New design

In order to address these problems, we should modify our bindings by splitting them into two elements:

Another bonus of the high-level bindings is that they will allow us to design a more JS-idomatic API by defining, e.g., builder patterns.

About backward compatibility

This change will necessarily be a breaking change. However we should attempt to limit as much as possible the amount of breaking changes. To do so we will:

sebcrozet commented 4 years ago

See the new-api branch for an overview of the ongoing changes.

Jarred-Sumner commented 3 years ago

Suggestion: To reduce GC pressure and let you delete some code, instead of creating new objects in https://github.com/dimforge/rapier.js/blob/master/src/dynamics/rigid_body.rs#L9 and https://github.com/dimforge/rapier.js/blob/master/src.ts/dynamics/rigid_body.ts#L57-L58, create a Float64Array or Float32Array inside the WASM memory for each new RigidBody's position, rotation and then update that in Rust directly. For any other temporary Vector-like objects, you could have a few extra Float64Array's preallocated that you pass it a byteOffset to write to, so you don't have to create new objects.

You can allocate these arrays in the WASM memory directly, which means Rust can update it without a JS<>WASM API call and without creating new objects on each of those calls.

From there, you can keep the same interface for the JS Vector objects by overriding getters/setters:

class Vector3 {
  array: Float64Array;

  get x() {
    return this.array[0];
  }

  set x(v: number) {
    this.array[0] = v;
  }

  get y() {
    return this.array[1];
  }

  set y(v: number) {
    this.array[1] = v;
  }

  get z() {
    return this.array[2];
  }

  set z(v: number) {
    this.array[2] = v;
  }
}

This will make Rapier's JS bindings faster because, as far as I can tell, Rapier.js creates a new JavaScript object every time you want to get the translation() of a RigidBody which will likely cause garbage collection freezes.