@phenomnomnominal 2024
Craig's
AMAZING
RUST
SPECTACULAR!!
Brought to you by...
@phenomnomnominal 2024
Hi, I'm Craig
@phenomnomnominal 2024
@phenomnomnominal 2024
Slides!
A word from our sponsors!
@phenomnomnominal 2024
FASTER!!!
@phenomnomnominal 2024
Sir Tow Mater
@phenomnomnominal 2024
Hey there Craig!
I have an idea to help Lightning MCqueen go even faster!
@phenomnomnominal 2024
I think It's time to get the aerodynamics Sim working on Lightning's HUD!
@phenomnomnominal 2024
@phenomnomnominal 2024
Fluid Dynamics!
@phenomnomnominal 2024
Lattice Boltzmann
Streaming:
COLLIDING:
WIKIPEDIA
@phenomnomnominal 2024
We just need to Figure out how to get the simulation running fast enough to be useful
during a race!
@phenomnomnominal 2024
SIMULATION
RENDERER
@phenomnomnominal 2024
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 2024
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 2024
@phenomnomnominal 2024
@phenomnomnominal 2024
SIMULATION
π₯
RENDERER
π
@phenomnomnominal 2024
I don't know if it's fast enough for me to use it in real time... I need it
FASTER!!!
@phenomnomnominal 2024
You heard the Boss!
It's gotta
be faster!
@phenomnomnominal 2024
You get to work on that, I need to go put on some Rust-Eze...
Kachow!
@phenomnomnominal 2024
@phenomnomnominal 2024
Ooooh! now That's a
great Idea!
Β
@phenomnomnominal 2024
You'll have to teach me a bit of Rust
so I can help!
@phenomnomnominal 2024
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 2024
TS/Vite/Webpack/Babel
Jest/Vitest/Playwright
@phenomnomnominal 2024
Well that's all pretty Neat!
But what does it look like?
@phenomnomnominal 2024
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 2024
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 2024
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 2024
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 2024
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 2024
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 2024
Gosh!
That looks like a programming language!
@phenomnomnominal 2024
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 2024
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 2024
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 2024
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 2024
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 2024
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 2024
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 2024
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 2024
Okay!
So let's write the Renderer In Rust!
@phenomnomnominal 2024
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 2024
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 2024
Fluid Dynamics!
FASTER!!!
cargo run --profile release
@phenomnomnominal 2024
Kachow!
Did someone say...
FASTER!?
@phenomnomnominal 2024
YESSIR!
She's flamin' hot now!
@phenomnomnominal 2024
Woah. How can we get tHAT runninG that fast in my browser?
Kachow!
@phenomnomnominal 2024
@phenomnomnominal 2024
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 2024
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 2024
@phenomnomnominal 2024
FASTER!!!
@phenomnomnominal 2024
Kachow!
This is amazing!
I can custom tune my DRAG REDUCTION SYSTEM as i go!
@phenomnomnominal 2024
That sounds like success to me!
@phenomnomnominal 2024
FASTER ποΈ
Aerodynamics sims π§
TYpescript Slow π
Rust FastΒ π¦
RUST + WASM π
@phenomnomnominal 2024
CODE!
@phenomnomnominal 2024
KACHOW!
@phenomnomnominal 2024
KACHOW!
@phenomnomnominal 2024
Craig's Amazing Rust Spectacular!
By Craig Spence
Craig's Amazing Rust Spectacular!
- 722