Thirty Days of Rust: Day Five

Today I wanted to take it easy a little bit and try some WebAssembly with Rust. Over the years I’ve gotten very used to JavaScript, but now that I’m doing this challenge, I wanted to dip my feet into WebAssembly, so that’s exactly what I did. I found a book that basically told me everything I needed to know, and then I got started.



Rust Setup

So instead of a new Rust app, I needed to make a Rust lib, which I could do with:

$ cargo new day5 --lib

Then I added two things to my Cargo.toml so it looked like this:

[package]
name = "day5"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib", "rlib"]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
wasm-bindgen = "0.2.78"

Then I went ahead and installed wasm-pack with cargo:

$ cargo install wasm-pack



Rust Code

The only thing I wanted this app to do was add two numbers together. It’s probably too simple for this challenge to really mean anything, but I didn’t want to spend too much time on this because tomorrow I want to rebuild my hangman game in the browser and I figured today could be a little bit shorter.

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn add(n1: i32, n2: i32) -> i32 
    return n1 + n2;

So that was simple, as I expected and now I just had to pack it together with:

$ wasm-pack build



JS Setup

$ pnpm init wasm-app www
$ cd www
$ pnpm i

That’s all, now I just imported the wasm file and console logged the result of the add function:

import * as wasm from "../pkg";

console.log(wasm.add(21, 19));

Now I could just run pnpm serve and open up localhost:8080 to get to my app. When I opened up the console, it showed the logged value of 40.
That’s about it from me today, but I look forward to tomorrow and sorry if this one was a little less exciting.


Source link

This little known javascript physics library blew my mind!

In my previous game dev attempts with javascript I always struggled with physics engine performance. I always defaulted to matter.js – it’s good documentation and plentiful examples outweighed the performance gains of other available libraries. I was very excited when I first learned about WASM and near-native performance it provides, but for the longest time Box2D was the only viable choice in that area and I truely hated using it. It had poor documentation and felt very archaic to use.

Now, it seems like my woes might be over. In comes a new contender – Rapier.rs.

Rapier.rs logo
Rapier home

Rapier.rs is a rust physics library compiled to WASM with javscript bindings and good documentation. I was able to set it up in around 30 minutes and it provided an massive, instant boost to app performance.

Rapier remained more stable and allowed me to add thousands of more active physics bodies to the world.

Links:

  • Example from my last article with Rapier.rs instead of matter +300% performance LIVE
  • Github repo

nbrpJOCJQu.gif

Active bodies Matter FPS Rapier FPS
4500 38 120
6000 21 79
7500 4 60
9000 0 – crashed 42
10000 0 – crashed 31
12000 0 – crashed 22
15000 0 – crashed 16



Why you need to consider Rapier for your js physics needs



1. Performance

Javascript can’t compare to an optimized Rust library compiled to WASM
WASM is just this fast



2. Documentation

Rapier page provides a good overview of the key features, information how to get started and an in-depth API documentation. All of this for Rust, Rust+bevy and Javascript.



3. Modern developer experience

I found Rapier API very intuitive to work with, imho making it by far the best choice out of the few performant. It comes with typescript support. Resulting code is readable and easy to reason with.

import("@dimforge/rapier2d").then((RAPIER) => 
  // Use the RAPIER module here.
  let gravity =  x: 0.0, y: 9.81 ;
  let world = new RAPIER.World(gravity);

  // Create the ground
  let groundColliderDesc = RAPIER.ColliderDesc.cuboid(10.0, 0.1);
  world.createCollider(groundColliderDesc);

  // Create a dynamic rigid-body.
  let rigidBodyDesc = RAPIER.RigidBodyDesc.newDynamic().setTranslation(
    0.0,
    1.0
  );
  let rigidBody = world.createRigidBody(rigidBodyDesc);

  // Create a cuboid collider attached to the dynamic rigidBody.
  let colliderDesc = RAPIER.ColliderDesc.cuboid(0.5, 0.5);
  let collider = world.createCollider(colliderDesc, rigidBody.handle);

  // Game loop. Replace by your own game loop system.
  let gameLoop = () => 
    // Step the simulation forward.
    world.step();

    // Get and print the rigid-body's position.
    let position = rigidBody.translation();
    console.log("Rigid-body position: ", position.x, position.y);

    setTimeout(gameLoop, 16);
  ;

  gameLoop();
);



4. Cross-platform determinism & snapshotting

  • Running the same simulation, with the same initial conditions on different machines or distributions of Rapier (rust/bevy/js) will yield the same result.

  • Easy data saving and restoring.It is possible to take a snapshot of the whole physics world with world.takeSnapshot. This results in a byte array of type Uint8Array that may be saved on the disk, sent through the network, etc. The snapshot can then be restored with let world = World.restoreSnapshot(snapshot);.



What’s next?

I am excited to keep working with Rapier, but in the meanwhile I think a proper physics benchmark is in order. The ones I’ve found while doing research were a bit dated.



Other: Vite usage errors

I’ve ran into some issues adding Rapier to my Vite project, the solution can be found here: https://github.com/dimforge/rapier.js/issues/49


Source link