rapier.js icon indicating copy to clipboard operation
rapier.js copied to clipboard

Unstable core features

Open Noobulater opened this issue 1 year ago • 4 comments

Hi all, love the work you are all doing here, but recently ran into an issue with some core features, such as setting position and velocity data, leading to a WASM unreachable error.

image

This instability was almost enough to make me swap physics libaries, so I hope it gets addressed.

Here's the situation: This is on a Velocity based Kinematic body. I need to run 1-15 "TICKS" as fast as possible, each completing a round trip of user input -> update bodies with new forces -> run physics simulation -> report body's physics state back to the simulation. However, doing so at a high frequency will lead to a crash (not guarenteed, and more frequent the higher the tick rate)

Noobulater avatar Jun 22 '24 10:06 Noobulater

if (p != null)
    body.setTranslation(new Vector3(p[0], p[1], p[2]))

  if (q != null)
    body.setRotation(new Quaternion(q[0] || 0, q[1] || 0, q[2] || 0, q[3] || 1))

  if (v != null)
    body.setLinvel(new Vector3(v[0], v[1], v[2]))

  if (aV != null)
    body.setAngvel(new Vector3(aV[0], aV[1], aV[2]))
for (const cID in components) {
      const { Controller, body, rigidBodyCollider, grounded } = components[cID]
      if (Controller && rigidBodyCollider) {
        const vel = body.linvel()

        if (vel.x > 0 || vel.x < 0)
          vel.x /= body.mass()

        if (vel.y > 0 || vel.y < 0)
          vel.y /= body.mass()

        if (vel.z > 0 || vel.z < 0)
          vel.z /= body.mass()

        if (grounded)
          vel.y = 0
        else
          vel.y -= 0.25

        Controller.computeColliderMovement(
          rigidBodyCollider,           // The collider we would like to move.
          vel, // The movement we would like to apply if there wasn’t any obstacle.
        )
        const movement = Controller.computedMovement()
        if (movement.x > 0 || movement.x < 0)
          movement.x /= physics.timestep
        if (movement.y > 0 || movement.y < 0)
          movement.y /= physics.timestep
        if (movement.z > 0 || movement.z < 0)
          movement.z /= physics.timestep

        body.setLinvel(movement)

        components[cID].grounded = Controller.computedGrounded()
      }
    }

Relevant code in case it helps. All values are of the right type.

Noobulater avatar Jun 22 '24 11:06 Noobulater

Tried downgrading to 0.11.1, but still got the issue.

Edit: After a lot of attempts to diagonse this, I still get the issue, but I can't recreate a state where it fails consistently. The snapshot I load when the error occurs work if loaded manually, which makes me think there is some sort of race condition internally with the library.

This issue is a deal breaker for me and sadly I can't use this library until it's addressed.

Noobulater avatar Jun 22 '24 11:06 Noobulater

Got this reply on the discord, but not sure it works since I've switched off the library

Hello. 
I encountered this problem once. It was caused by some references of Rapier body objects that were kept in an array after the bodies were removed from the physics world. I think it messed up the lifecycle somehow.
I fixed it by only keeping their "handle" and not a reference to the full object.
I hope it can help.

Noobulater avatar Jun 24 '24 12:06 Noobulater

I'm having this issue when dynamically adding/removing rigid bodies while the render loop is running. I have made sure to queue actions which add/remove from the physics world until after the step function is completed.

No matter what I do i run into this issue, I have made sure there is no async logic, and made sure to only store references to the rigid body / collider handles and not the direct objects.

Unfortunately this library is currently too unstable to rely on.

I've tried to narrow it down, but it seems to be something to do with dynamic RigidBodies and colliders:

// Player.ts
export class Player {
  private id: string;
  private rigidBodyHandle: RAPIER.RigidBodyHandle | null;
  private colliderHandle: RAPIER.ColliderHandle | null;

  constructor(world: RAPIER.World, position: Position = { x: 0, y: 10, z: 0 }) {
    this.id = uuidv4();

    const rigidBodyDesc: RAPIER.RigidBodyDesc = RAPIER.RigidBodyDesc.dynamic() // <-- changing to fixed - error doesn't occur
        .setTranslation(position.x, position.y, position.z);
    
    const rigidBody = world.createRigidBody(rigidBodyDesc);
    this.rigidBodyHandle = rigidBody.handle;

    const colliderDesc = RAPIER.ColliderDesc.capsule(0.5, 0.5); // <-- commenting out - error doesn't occur
    const collider = world.createCollider(colliderDesc, rigidBody); // <-- commenting out - error doesn't occur

    this.colliderHandle = collider.handle; // <-- commenting out - error doesn't occur
  }

  getId(): string {
    return this.id;
  }

  removePlayer(world: RAPIER.World): void {
    if (!world) return;

    if (this.colliderHandle !== null) {
        const collider = world.getCollider(this.colliderHandle);
        if (collider) world.removeCollider(collider, true);
        this.colliderHandle = null;
    }

    if (this.rigidBodyHandle !== null) {
        const rigidBody = world.getRigidBody(this.rigidBodyHandle);
        if (rigidBody) world.removeRigidBody(rigidBody);
        this.rigidBodyHandle = null;
    }
  }
}

I'm adding/removing players via the World.ts class I've defined:

// World.ts
import * as RAPIER from '@dimforge/rapier3d-compat';
import { Position } from '../../shared/types';
import { Player } from './Player';

export class World {

  private players: Map<string, Player> = new Map();
  private world: RAPIER.World;

  constructor() {
    const gravity: Position = { x: 0, y: -9.81, z: 0 };
    this.world = new RAPIER.World(gravity);

    // Create ground
    const groundCollider = RAPIER.ColliderDesc.cuboid(50.0, 0.1, 50.0)
      .setFriction(0.8)
      .setRestitution(0)
      .setFrictionCombineRule(RAPIER.CoefficientCombineRule.Max);
    this.world.createCollider(groundCollider);
  }

  addNewPlayer(): Player {
    const player = new Player(this.world);
    this.players.set(player.getId(), player);
    return player;
  }

  removePlayer(id: string): void {
    const player = this.players.get(id);
    if (!player) return;
    this.players.delete(id);
    player.removePlayer(this.world);
  }

  step(): void {
    try {
      this.world.step();
    } catch (error) {
      console.log('error', error);
    }   
  }
}

World.ts is being called from index.ts as such:

// index.ts
import { WebSocketServer, Server, WebSocket } from 'ws';
import * as RAPIER from '@dimforge/rapier3d-compat';

import { World } from './World';

const wss = new WebSocketServer({ port: 3001 });
await RAPIER.init();

const world = new World();
const connections: Map<string, WebSocket> = new Map();

type Command = () => void;
const pendingCommands: Command[] = [];

wss.on('connection', (ws: WebSocket) => {

  let playerId: string | undefined;

  // Attempt to queue and delay actions until after step function
  pendingCommands.push(() => {
    const player = world.addNewPlayer();
    playerId = player.getId();
    connections.set(playerId, ws);

    console.log(`> Player connected: ${playerId}`);

    ws.on('close', () => {
      pendingCommands.push(() => {
        if (!playerId) return;
          console.log(`> Player disconnected: ${playerId}`);
          world.removePlayer(playerId);
          connections.delete(playerId);
        });
      });
    });
});

const TICK_RATE = 20; // 20 updates per second
const TICK_INTERVAL = 1000 / TICK_RATE;
let lastTime = performance.now();

function gameLoop() {
  const now = performance.now();
  const delta = now - lastTime;

  if (delta >= TICK_INTERVAL) {
    lastTime = now;

    world.step();

    // Process any pending commands
    while (pendingCommands.length > 0) {
      const command = pendingCommands.shift();
      command?.();
    }
  }

  setImmediate(gameLoop);
}

gameLoop();

Error:

asm://wasm/0062c87e:1


RuntimeError: unreachable
    at wasm://wasm/0062c87e:wasm-function[1033]:0x154305
    at wasm://wasm/0062c87e:wasm-function[1680]:0x17345b
    at wasm://wasm/0062c87e:wasm-function[1458]:0x16ea48
    at wasm://wasm/0062c87e:wasm-function[99]:0x976b8
    at wasm://wasm/0062c87e:wasm-function[904]:0x148d34
    at wasm://wasm/0062c87e:wasm-function[26]:0x174e3
    at wasm://wasm/0062c87e:wasm-function[184]:0xc5ed1
    at step (/Users/.../Documents/Projects/.../server/node_modules/@dimforge/rapier3d-compat/rapier_wasm3d.js:3750:14)
    at step (/Users/.../Documents/Projects/.../server/node_modules/@dimforge/gen3d/gen3d/pipeline/physics_pipeline.ts:69:22)
    at step (/Users/.../Documents/Projects/.../server/node_modules/@dimforge/gen3d/gen3d/pipeline/world.ts:269:30)

jacknkandy avatar Jul 21 '25 12:07 jacknkandy