@phenomnomnominal 2023

Craig's

ANGULAR

RUST

SPECTACULAR!!

Brought to you by...

@phenomnomnominal 2023

A word from our sponsors!

@phenomnomnominal 2023

Hi, I'm Craig

@phenomnomnominal 2023

@phenomnomnominal 2023

FASTER!!!

@phenomnomnominal 2023

Sir Tow Mater

@phenomnomnominal 2023

Hey there Craig!

I have an idea to help Lightning MCqueen go even faster!

@phenomnomnominal 2023

I think It's time to get the aerodynamics Sim working on Lightning's HUD!

@phenomnomnominal 2023

@phenomnomnominal 2023

Fluid Dynamics!

@phenomnomnominal 2023

Lattice Boltzmann

Streaming:

COLLIDING:

WIKIPEDIA

@phenomnomnominal 2023

We just need to Figure out how to get the simulation running fast enough to be useful

during a race!

@phenomnomnominal 2023

SIMULATION

RENDERER

@phenomnomnominal 2023

import { CanvasRenderer } from "./renderer-canvas.js";
import { Simulation } from "./simulation.js";

const xDim = 100;
const yDim = 30;

const simulation = new Simulation(xDim, yDim, 0.1, 20, 0.02);
const renderer = new CanvasRenderer(canvas, 1);

(async () => {
  while (running) {
    simulation.simulate();
    await renderer.paint(simulation, xDim, yDim);
  }
})();

Lattice Botlzmann

HTML CANVAS API

RENDER LOOP

@phenomnomnominal 2023

import { Component, ElementRef, NgZone, ViewChild } from '@angular/core';
import { CommonModule } from '@angular/common';
import { bootstrapApplication } from '@angular/platform-browser';

import { Simulation } from './simulation';
import { CanvasRenderer } from './renderer-canvas';

@Component({
  selector: 'my-app',
  standalone: true,
  imports: [CommonModule],
  template: `
   <canvas #canvas width="600px" height="240px"></canvas>
  `,
})
export class App {
  @ViewChild('canvas')
  private _canvas: ElementRef | null = null;

  private _xDim: number = 0;
  private _yDim: number = 0;

  constructor(private zone: NgZone) {}

  ngAfterViewInit(): void {
    if (!this._canvas) {
      return;
    }

    const canvas = this._canvas.nativeElement;
    const pxPerSquare = 2;

    this._xDim = canvas.width / pxPerSquare;
    this._yDim = canvas.height / pxPerSquare;

    this.zone.runOutsideAngular(() => {
      const simulation = new Simulation(this._xDim, this._yDim, 0.1, 20, 0.02);
      initialBarrier(simulation, this._yDim);
      const renderer = new CanvasRenderer(canvas, 1, pxPerSquare);
      renderer.paint(simulation);

      let count = 0;
      const simulate = () => {
        count += 1;
        console.time('simulate');
        simulation.simulate();
        console.timeEnd('simulate');
        renderer.paint(simulation);

        if (count < 1000) {
          requestAnimationFrame(simulate);
        }
      };
      requestAnimationFrame(simulate);
    });
  }
}

bootstrapApplication(App);

function initialBarrier(simulation: Simulation, yDim: number): void {
  const barrierSize = 8;
  const x = Math.round(yDim / 3);
  for (let y = yDim / 2 - barrierSize; y <= yDim / 2 + barrierSize; y++) {
    simulation.addBarrier(x, y);
  }
}

@phenomnomnominal 2023

@phenomnomnominal 2023

@phenomnomnominal 2023

SIMULATION

πŸ”₯

RENDERER

πŸ’€

@phenomnomnominal 2023

I don't know if it's fast enough for me to use it in real time... I need it

FASTER!!!

@phenomnomnominal 2023

You heard the Boss!

It's gotta

be faster!

@phenomnomnominal 2023

You get to work on that, I need to go put on some Rust-Eze...

Kachow!

@phenomnomnominal 2023

@phenomnomnominal 2023

Ooooh! now That's a

great Idea!

Β 

@phenomnomnominal 2023

You'll have to teach me a bit of Rust

so I can help!

@phenomnomnominal 2023

Superset of JS for Web Development with static types

Systems Language emphasising safety and performance.

Garbage collection (like JS)

Memory-safe without GC

Async/event-driven

Build in concurrency and parallelism

Interpreted, so slower performance

Compiled and highly optimised

@phenomnomnominal 2023

TS/Vite/Webpack/Babel

Jest/Vitest/Playwright

@phenomnomnominal 2023

Well that's all pretty Neat!

But what does it look like?

@phenomnomnominal 2023

const myNumber = 3;

let myString = 'Hello World!';
let my_int = 3;

let mut my_string = "Hello World!";

Immutable by default

opt in to mutability

@phenomnomnominal 2023

import { Simulation } from "./simulation";

const sim = new Simulation(100, 30);
mod simulation;

use simulation::Simulation;

fn main() {
  let mut sim = Simulation::new(100, 30);
}

Move the Simulation into scope

Main entry point

convention for instantiation

@phenomnomnominal 2023

let count = 0;
while (count < 1000) {
  count += 1;

  sim.simulate();
  renderer.paint(sim, xDim, yDim);
}
let mut count = 0;
while count < 100 {
  count += 1;

  sim.simulate();
  renderer.paint(&sim, x_dim, y_dim);
}

optional SynTax

Explicit passing by reference

@phenomnomnominal 2023

fn initial_barrier(sim: &mut Simulation, y_dim: u64) {
  let size = 8;
  let x = y_dim / 3;
  let y_mid = y_dim / 2;
  for y in y_mid - size..y_mid + size {
    sim.add_barrier(x, y);
  }
}
function initialBarrier(sim: Simulation, yDim: number) {
  const size = 8;
  const x = yDim / 3;
  const yMid = yDim / 2;
  for (let y = yMid - size; y <= yMid + size; y++) {
    sim.addBarrier(x, y);
  }
}

Different naming conventions

Range syntax

parameter constraints

Specific types

FN keyword

@phenomnomnominal 2023

pub struct Color {
    pub red: u64,
    pub blue: u64,
    pub green: u64,
}
export type Color = {
  red: number;
  blue: number;
  green: number;
};

Specific types

Explicit visibility

@phenomnomnominal 2023

export class Colors {
  private _red: Array<number> = [];
  private _green: Array<number> = [];
  private _blue: Array<number> = [];

  constructor() { /* ... */ }
} 
pub struct Colors {
    red: Vec<u64>,
    blue: Vec<u64>,
    green: Vec<u64>,
}

impl Colors {
  pub fn new() -> Colors {
    let n = N_COLORS as usize;
    let mut red = vec![0; n];
    let mut blue = vec![0; n];
    let mut green = vec![0; n];
    //...
    Colors { red, blue, green }
  }
}

Private by default

impl defines an implementation

New function

Returns the struct

Macro syntax

@phenomnomnominal 2023

Gosh!

That looks like a programming language!

@phenomnomnominal 2023

export class Simulation {
  public simulate() {
    for (var step = 0; step < this._steps; step++) {
      this._collide();
      this._stream();
    }
  }

  private _collide() {
    const omega = 1 / (3 * this._viscosity + 0.5);

    for (let y = 1; y < this._yDim - 1; y++) {
      for (let x = 1; x < this._xDim - 1; x++) {
        const i = x + y * this._xDim;
        
        const rho = this._n0[i] + this._nN[i] + this._nS[i] + this._nE[i] + this._nW[i] + this._nNW[i] + this._nNE[i] + this._nSW[i] + this._nSE[i];
        this._rho[i] = rho;

        const uX = (this._nE[i] + this._nNE[i] + this._nSE[i] - this._nW[i] - this._nNW[i] - this._nSW[i]) / rho;
        this._uX[i] = uX;

        const uY = (this._nN[i] + this._nNE[i] + this._nNW[i] - this._nS[i] - this._nSE[i] - this._nSW[i]) / rho;
        this._uY[i] = uY;

        const one9thRho = NINTH * rho;
        const one36thRho = THIRTY_SIXTH * rho;
        const uX3 = 3 * uX;
        const uY3 = 3 * uY;
        const uX2 = uX * uX;
        const uY2 = uY * uY;
        const uXuY2 = 2 * uX * uY;
        const u2 = uX2 + uY2;
        const u215 = 1.5 * u2;

        this._n0[i] += omega * (FOUR_NINTHS * rho * (1 - u215) - this._n0[i]);
        this._nE[i] += omega * (one9thRho * (1 + uX3 + 4.5 * uX2 - u215) - this._nE[i]);
        this._nW[i] += omega * (one9thRho * (1 - uX3 + 4.5 * uX2 - u215) - this._nW[i]);
        this._nN[i] += omega * (one9thRho * (1 + uY3 + 4.5 * uY2 - u215) - this._nN[i]);
        this._nS[i] += omega * (one9thRho * (1 - uY3 + 4.5 * uY2 - u215) - this._nS[i]);
        this._nNE[i] += omega * (one36thRho * (1 + uX3 + uY3 + 4.5 * (u2 + uXuY2) - u215) - this._nNE[i]);
        this._nSE[i] += omega * (one36thRho * (1 + uX3 - uY3 + 4.5 * (u2 - uXuY2) - u215) - this._nSE[i]);
        this._nNW[i] += omega * (one36thRho * (1 - uX3 + uY3 + 4.5 * (u2 - uXuY2) - u215) - this._nNW[i]);
        this._nSW[i] += omega * (one36thRho * (1 - uX3 - uY3 + 4.5 * (u2 + uXuY2) - u215) - this._nSW[i]);
      }
    }

    for (var y = 1; y < this._yDim - 2; y++) {
      // at right end, copy left-flowing densities from next row to the left
      this._nW[this._xDim - 1 + y * this._xDim] = this._nW[this._xDim - 2 + y * this._xDim];
      this._nNW[this._xDim - 1 + y * this._xDim] = this._nNW[this._xDim - 2 + y * this._xDim];
      this._nSW[this._xDim - 1 + y * this._xDim] = this._nSW[this._xDim - 2 + y * this._xDim];
    }
  }

  private _stream(): void {
    for (let y = this._yDim - 2; y > 0; y--) {
      for (let x = 1; x < this._xDim - 1; x++) {
        this._nN[x + y * this._xDim] = this._nN[x + (y - 1) * this._xDim];
        this._nNW[x + y * this._xDim] = this._nNW[x + 1 + (y - 1) * this._xDim];
      }
    }
    
    for (let y = this._yDim - 2; y > 0; y--) {
      for (let x = this._xDim - 2; x > 0; x--) {
        this._nE[x + y * this._xDim] = this._nE[x - 1 + y * this._xDim];
        this._nNE[x + y * this._xDim] = this._nNE[x - 1 + (y - 1) * this._xDim];
      }
    }
    
    for (let y = 1; y < this._yDim - 1; y++) {
      for (let x = this._xDim - 2; x > 0; x--) {
        this._nS[x + y * this._xDim] = this._nS[x + (y + 1) * this._xDim];
        this._nSE[x + y * this._xDim] = this._nSE[x - 1 + (y + 1) * this._xDim];
      }
    }
    
    for (let y = 1; y < this._yDim - 1; y++) {
      for (let x = 1; x < this._xDim - 1; x++) {
        this._nW[x + y * this._xDim] = this._nW[x + 1 + y * this._xDim];
        this._nSW[x + y * this._xDim] = this._nSW[x + 1 + (y + 1) * this._xDim];
      }
    }

    for (let y = 1; y < this._yDim - 1; y++) {
      for (let x = 1; x < this._xDim - 1; x++) {
        if (this._barrier[x + y * this._xDim]) {
          const index = x + y * this._xDim;
          this._nE[x + 1 + y * this._xDim] = this._nW[index];
          this._nW[x - 1 + y * this._xDim] = this._nE[index];
          this._nN[x + (y + 1) * this._xDim] = this._nS[index];
          this._nS[x + (y - 1) * this._xDim] = this._nN[index];
          this._nNE[x + 1 + (y + 1) * this._xDim] = this._nSW[index];
          this._nNW[x - 1 + (y + 1) * this._xDim] = this._nSE[index];
          this._nSE[x + 1 + (y - 1) * this._xDim] = this._nNW[index];
          this._nSW[x - 1 + (y - 1) * this._xDim] = this._nNE[index];
        }
      }
    }
  }
}

@phenomnomnominal 2023

impl Simulation {
  pub fn simulate(&mut self) {
    for _ in 0..self.steps {
      self.collide();
      self.stream();
    }
  }

  fn collide(&mut self) {
    let omega = 1.0 / (3.0 * self.viscosity + 0.5);
    
    for y in 1..self.y_dim - 1 {
      for x in 1..self.x_dim - 1 {
        let index = self.get_index(x, y);

        let rho = self.n_0[index] + self.n_n[index] + self.n_s[index] + self.n_e[index] + self.n_w[index] + self.n_nw[index] + self.n_ne[index] + self.n_sw[index] + self.n_se[index];
        self.rho[index] = rho;

        let u_x = (self.n_e[index] + self.n_ne[index] + self.n_se[index] - self.n_w[index] - self.n_nw[index] - self.n_sw[index]) / rho;
        self.u_x[index] = u_x;

        let u_y = (self.n_n[index] + self.n_ne[index] + self.n_nw[index] - self.n_s[index] - self.n_se[index] - self.n_sw[index]) / rho;
        self.u_y[index] = u_y;

        let ninth_rho = NINTH * rho;
        let thirty_sixth_rho = THIRTY_SIXTH * rho;
        let u_x3 = 3.0 * u_x;
        let u_y3 = 3.0 * u_y;
        let u_x2 = u_x * u_x;
        let u_y2 = u_y * u_y;
        let u_x_u_y2 = 2.0 * u_x * u_y;
        let u2 = u_x2 + u_y2;
        let u215 = 1.5 * u2;

        self.n_0[index] += omega * (FOUR_NINTHS * rho * (1.0 - u215) - self.n_0[index]);

        self.n_e[index] += omega * (ninth_rho * (1.0 + u_x3 + 4.5 * u_x2 - u215) - self.n_e[index]);
        self.n_w[index] += omega * (ninth_rho * (1.0 - u_x3 + 4.5 * u_x2 - u215) - self.n_w[index]);
        self.n_n[index] += omega * (ninth_rho * (1.0 + u_y3 + 4.5 * u_y2 - u215) - self.n_n[index]);
        self.n_s[index] += omega * (ninth_rho * (1.0 - u_y3 + 4.5 * u_y2 - u215) - self.n_s[index]);
        self.n_ne[index] += omega * (thirty_sixth_rho * (1.0 + u_x3 + u_y3 + 4.5 * (u2 + u_x_u_y2) - u215) - self.n_ne[index]);
        self.n_se[index] += omega * (thirty_sixth_rho * (1.0 + u_x3 - u_y3 + 4.5 * (u2 - u_x_u_y2) - u215) - self.n_se[index]);
        self.n_nw[index] += omega * (thirty_sixth_rho * (1.0 - u_x3 + u_y3 + 4.5 * (u2 - u_x_u_y2) - u215) - self.n_nw[index]);
        self.n_sw[index] += omega * (thirty_sixth_rho * (1. - u_x3 - u_y3 + 4.5 * (u2 + u_x_u_y2) - u215) - self.n_sw[index]);
      }
    }

    for y in 1..self.y_dim - 2 {
      let index = self.get_index(self.x_dim - 1, y);
      let index_w = self.get_index(self.x_dim - 2, y);
      self.n_w[index] = self.n_w[index_w];
      let index_nw = self.get_index(self.x_dim - 2, y);
      self.n_nw[index] = self.n_nw[index_nw];
      let index_sw = self.get_index(self.x_dim - 2, y);
      self.n_sw[index] = self.n_sw[index_sw];
    }
  }

  fn stream(&mut self) {
    for y in (1..self.y_dim - 1).rev() {
      for x in 1..(self.x_dim - 1) {
        let index = self.get_index(x, y);
        let index_n = self.get_index(x, y - 1);
        self.n_n[index] = self.n_n[index_n];
        let index_nw = self.get_index(x + 1, y - 1);
        self.n_nw[index] = self.n_nw[index_nw];
      }
    }
    for y in (1..self.y_dim - 1).rev() {
      for x in (1..self.x_dim - 1).rev() {
        let index = self.get_index(x, y);
        let index_e = self.get_index(x - 1, y);
        self.n_e[index] = self.n_e[index_e];
        let index_ne = self.get_index(x - 1, y - 1);
        self.n_ne[index] = self.n_ne[index_ne];
      }
    }
    for y in 1..self.y_dim - 1 {
      for x in (1..self.x_dim - 1).rev() {
        let index = self.get_index(x, y);
        let index_s = self.get_index(x, y + 1);
        self.n_s[index] = self.n_s[index_s];
        let index_se = self.get_index(x - 1, y + 1);
        self.n_se[index] = self.n_se[index_se];
      }
    }
    for y in 1..self.y_dim - 1 {
      for x in 1..self.x_dim - 1 {
        let index = self.get_index(x, y);
        let index_w = self.get_index(x + 1, y);
        self.n_w[index] = self.n_w[index_w];
        let index_sw = self.get_index(x + 1, y + 1);
        self.n_sw[index] = self.n_sw[index_sw];
      }
    }

    for y in 1..self.y_dim - 1 {
      for x in 1..self.x_dim - 1 {
        let index = self.get_index(x, y);
        if self.barrier[index] {
          let index_e = self.get_index(x + 1, y);
          self.n_e[index_e] = self.n_w[index];
          let index_w = self.get_index(x - 1, y);
          self.n_w[index_w] = self.n_e[index];
          let index_n = self.get_index(x, y + 1);
          self.n_n[index_n] = self.n_s[index];
          let index_s = self.get_index(x, y - 1);
          self.n_s[index_s] = self.n_n[index];
          let index_ne = self.get_index(x + 1, y + 1);
          self.n_ne[index_ne] = self.n_sw[index];
          let index_nw = self.get_index(x - 1, y + 1);
          self.n_nw[index_nw] = self.n_se[index];
          let index_se = self.get_index(x + 1, y - 1);
          self.n_se[index_se] = self.n_nw[index];
          let index_sw = self.get_index(x - 1, y - 1);
          self.n_sw[index_sw] = self.n_ne[index];
        }
      }
    }
  }
}

@phenomnomnominal 2023

Borrow Checking!

Ownership:

fn main() {
  let x_dim = 100;
  let y_dim = 30;

  let mut simulation = Simulation::new(x_dim, y_dim, 0.1, 10, 0.02);
  initial_barrier(simulation, y_dim);
  initial_barrier(simulation, y_dim);
}
error[E0382]: use of moved value: `simulation`
  --> src/main.rs:14:21
   |
12 |     let mut simulation = Simulation::new(x_dim, y_dim, 0.1, 10, 0.02);
   |         -------------- move occurs because `simulation` has type
   |                        `Simulation`, which does not implement the
   |                        `Copy` trait
13 |     initial_barrier(simulation, y_dim);
   |                     ---------- value moved here
14 |     initial_barrier(simulation, y_dim);
   |                     ^^^^^^^^^^ value used here after move
   |
note: consider changing this parameter type in function `initial_barrier` to
	  borrow instead if owning the value isn't necessary
  --> src/main.rs:26:32
   |
26 | fn initial_barrier(simulation: Simulation, y_dim: u64) {
   |    ---------------             ^^^^^^^^^^ this parameter takes ownership
   |	|                                      of the value
   |    |
   |    in this function

PASS THE SIMULATION TWICE

@phenomnomnominal 2023

Borrow Checking!

Ownership:

fn main() {
  let x_dim = 100;
  let y_dim = 30;

  let mut simulation = Simulation::new(x_dim, y_dim, 0.1, 10, 0.02);
  initial_barrier(simulation.clone());
  initial_barrier(simulation);
}

Clone the object

βœ…

@phenomnomnominal 2023

Borrow Checking!

Borrowing:

fn main() {
  let x_dim = 100;
  let y_dim = 30;

  let mut simulation = Simulation::new(x_dim, y_dim, 0.1, 10, 0.02);
  
  const sim1 = &simulation;
  
  initial_barrier(sim1, y_dim);
}

Take a reference

Pass the reference

@phenomnomnominal 2023

Borrow Checking!

Borrowing:

fn main() {
  let x_dim = 100;
  let y_dim = 30;

  let mut simulation = Simulation::new(x_dim, y_dim, 0.1, 10, 0.02);
  
  const sim1 = &mut simulation;
  const sim2 = &mut simulation;
  
  initial_barrier(sim1, y_dim);
  initial_barrier(sim2, y_dim);
}
error[E0499]: cannot borrow `simulation` as mutable more than once at a time
  --> src/main.rs:15:16
   |
14 |     let sim1 = &mut simulation;
   |                --------------- first mutable borrow occurs here
15 |     let sim2 = &mut simulation;
   |                ^^^^^^^^^^^^^^^ second mutable borrow occurs here
16 |
17 |     initial_barrier(sim1, y_dim);
   |                     ---- first borrow later used here

Take a second mutable reference

@phenomnomnominal 2023

Borrow Checking!

Borrowing:

fn main() {
  let x_dim = 100;
  let y_dim = 30;

  let mut simulation = Simulation::new(x_dim, y_dim, 0.1, 10, 0.02);
  
  const sim1 = &mut simulation;
  initial_barrier(sim1, y_dim);
  
  const sim2 = &mut simulation;
  initial_barrier(sim2, y_dim);
}

Take First mutable reference

Use it

Take Second mutable reference

Use it

βœ…

@phenomnomnominal 2023

number, bigint

i8, u32, f64, usize

boolean

bool

string

String, str, char

Structural Type System

Nominal Type System

Objects, Classes

struct, impl

Array<T>

Vec<T>

@phenomnomnominal 2023

Okay!

So let's write the Renderer In Rust!

@phenomnomnominal 2023

export class CanvasRenderer {
  // ...

  public paint(simulation: Simulation): void {
    let cIndex = 0;
    const contrast = this._contrast;
    for (let y = 0; y < simulation.yDim; y++) {
      for (let x = 0; x < simulation.xDim; x++) {
        if (simulation.barrier(x, y)) {
          this._colorSquare(x, y, 0, 0, 0, simulation.yDim);
          continue;
        }

        cIndex = Math.round(N_COLORS * (simulation.curl(x, y) * 5 * contrast + 0.5));
        if (cIndex < 0) cIndex = 0;
        if (cIndex > N_COLORS) cIndex = N_COLORS;
        const { red, green, blue } = this._colours.colour(cIndex);

        this._colorSquare(x, y, red, green, blue, simulation.yDim);
      }
    }
    this._context.putImageData(this._image, 0, 0);
  }
}

@phenomnomnominal 2023

impl RendererTerminal {
  // ..

  pub fn paint(&self, sim: &Simulation, x_dim: u64, y_dim: u64) {
    print!("\x1B[1;1H");

    let mut result = "".to_string();
    for y in 0..y_dim {
      for x in 0..x_dim {
        if sim.barrier(x, y) {
          result.push_str("\x1b[0m ");
        } else {
           let colour_value = (N_COLORS as f64) * (sim.curl(x, y) * 5.0 + 0.5);
           let colour_index = colour_value.clamp(0.0, (N_COLORS - 1) as f64).floor();
           let colour = self.colours.colour(colour_index as usize);
           result.push_str(&format!(
             "\x1b[48;2;{};{};{}m ",
             colour.red, colour.green, colour.blue
           ));
         }
      }
      result.push_str("\x1b[0m\n");
    }
    print!("{}", result);
  }
}

@phenomnomnominal 2023

Fluid Dynamics!

FASTER!!!

cargo run --profile release 

@phenomnomnominal 2023

Kachow!

Did someone say...

FASTER!?

@phenomnomnominal 2023

YESSIR!

She's flamin' hot now!

@phenomnomnominal 2023

Woah. How can we get tHAT runninG that fast in my browser?

Kachow!

@phenomnomnominal 2023

@phenomnomnominal 2023

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub struct Simulation {
  // ...
}

#[wasm_bindgen]
impl Simulation {
  // ...
  
  
  pub fn barrier_ptr(&self) -> *const bool {
  	self.barrier.as_ptr()
  }

  pub fn curl_ptr(&self) -> *const f64 {
    self.curl.as_ptr()
  }
}

RUST + WASM!

wasm-pack build --release 

WASM BINDING

WASM Module

@phenomnomnominal 2023

RUST + WASM!

import { Simulation } from "wasm-fluid";

const simulation = Simulation.new(BigInt(200), BigInt(80), 0.1, BigInt(20), 0.02);
class Renderer {

  paint(simulation, xDim, yDim) {
    const barrierPtr = simulation.barrier_ptr();
    const barrier = new Uint8Array(memory.buffer, barrierPtr, xDim * yDim);

    const curlPtr = simulation.curl_ptr();
    const curl = new Float64Array(memory.buffer, curlPtr, xDim * yDim);

    // ...
  }
}

Import WASM

Rust-like new convention

Use bigints for u64s

Access Pointer

Directly access WASM Memory

@phenomnomnominal 2023

@phenomnomnominal 2023

FASTER!!!

@phenomnomnominal 2023

Kachow!

This is amazing!

I can custom tune my DRAG REDUCTION SYSTEM as i go!

@phenomnomnominal 2023

That sounds like success to me!

@phenomnomnominal 2023

FASTER 🏎️

Aerodynamics sims πŸ’§

TYpescript Slow πŸ˜”

Rust FastΒ  πŸ¦€

RUST + WASM πŸŽ‰

@phenomnomnominal 2023

CODE!

KACHOW!

@phenomnomnominal 2023

KACHOW!

@phenomnomnominal 2023