
@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
Craig's Angular Rust Spectacular!
By Craig Spence
Craig's Angular Rust Spectacular!
- 2,984
 
   
   
  