Skip to content

Instantly share code, notes, and snippets.

@Eastwooder
Forked from dgerrells/cargo_no_wasm
Created January 13, 2025 18:52
Show Gist options
  • Save Eastwooder/429a3d6249a7be6fdaf66b2d6a54cd35 to your computer and use it in GitHub Desktop.
Save Eastwooder/429a3d6249a7be6fdaf66b2d6a54cd35 to your computer and use it in GitHub Desktop.

Revisions

  1. @dgerrells dgerrells created this gist Nov 8, 2024.
    13 changes: 13 additions & 0 deletions cargo_no_wasm
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,13 @@
    [package]
    name = "how_fast_is_rust"
    version = "0.1.0"
    edition = "2021"

    [dependencies]
    minifb = "0.27"
    rand = "0.8"
    wasm-bindgen = "0.2.95"

    [profile.release]
    opt-level = 3
    jemalloc = true
    17 changes: 17 additions & 0 deletions cargo_wasm
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,17 @@
    [package]
    name = "how_fast_is_rust"
    version = "0.1.0"
    edition = "2021"

    [dependencies]
    getrandom = { version = "0.2", features = ["js"] }
    rand = "0.8"
    wasm-bindgen = "0.2.95"

    [profile.release]
    opt-level = 3
    lto = true

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

    4 changes: 4 additions & 0 deletions gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,4 @@
    #/binbash
    cargo build --target=wasm32-unknown-unknown --release
    wasm-bindgen ./target/wasm32-unknown-unknown/release/how_fast_is_rust.wasm --out-dir /sabby-rust-basic --target web
    wasm-opt /how_fast_is_rust_bg.wasm -o /how_fast_is_rust_bg.wasm -O3 --strip-debug
    1 change: 1 addition & 0 deletions lib
    Original file line number Diff line number Diff line change
    @@ -0,0 +1 @@
    pub mod thread_pool;
    3 changes: 3 additions & 0 deletions readme
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,3 @@
    # how fast is rust

    This contains all the various versions and iterations for a project with the goal of simulating as many particles as possible. There are several versions as it works towards the end of simd and multi-threaded. There are also a few wasm versions with the required tooling setup. This does require two different cargo toml files. There is a build web script which runs 3 steps to produce a fast and small wasm file but you will need to update the input output locations.
    85 changes: 85 additions & 0 deletions thread_pool
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,85 @@
    use std::sync::{Arc, Mutex, Condvar, mpsc};
    use std::thread;
    use std::thread::JoinHandle;
    // I will be real, chatgpt helped me in this file.
    // I don't like it and over time I am sure I will get better.
    pub struct ThreadPool {
    workers: Vec<Worker>,
    sender: mpsc::Sender<Message>,
    job_count: Arc<(Mutex<usize>, Condvar)>,
    }

    type Job = Box<dyn FnOnce() + Send + 'static>;

    enum Message {
    NewJob(Job),
    }

    impl ThreadPool {
    pub fn new(size: usize) -> ThreadPool {
    assert!(size > 0); // nifty

    let (sender, receiver) = mpsc::channel();
    let receiver = Arc::new(Mutex::new(receiver));

    let job_count = Arc::new((Mutex::new(0), Condvar::new()));

    let mut workers = Vec::with_capacity(size);
    for id in 0..size {
    workers.push(Worker::new(id, Arc::clone(&receiver), Arc::clone(&job_count)));
    }

    ThreadPool {
    workers,
    sender,
    job_count,
    }
    }

    pub fn execute<F>(&self, f: F)
    where
    F: FnOnce() + Send + 'static,
    {
    let (lock, _) = &*self.job_count;
    *lock.lock().unwrap() += 1;

    let job = Box::new(f);
    self.sender.send(Message::NewJob(job)).unwrap();
    }

    pub fn wait_for_completion(&self) {
    let (lock, cvar) = &*self.job_count;
    let mut count = lock.lock().unwrap();
    while *count > 0 {
    count = cvar.wait(count).unwrap();
    }
    }
    }

    pub struct Worker {
    id: usize,
    handle: Option<JoinHandle<()>>,
    }

    impl Worker {
    fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Message>>>, job_count: Arc<(Mutex<usize>, Condvar)>) -> Worker {
    let handle = thread::spawn(move || loop {
    let message = receiver.lock().unwrap().recv();
    match message {
    Ok(Message::NewJob(job)) => {
    job();
    let (lock, cvar) = &*job_count;
    let mut count = lock.lock().unwrap();
    *count -= 1;
    cvar.notify_all();
    }
    Err(_) => break,
    }
    });

    Worker {
    id,
    handle: Some(handle),
    }
    }
    }
    104 changes: 104 additions & 0 deletions v1
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,104 @@
    use minifb::{Key, MouseButton, Window, WindowOptions};
    use rand::Rng;
    use std::time::Instant;

    const WIDTH: usize = 1200;
    const HEIGHT: usize = 800;
    const PARTICLE_COUNT: usize = 5_000_000;
    const GRAVITY_STRENGTH: f32 = 205.5*8.0;
    #[repr(align(16))]
    struct Particle {
    x: f32,
    y: f32,
    dx: f32,
    dy: f32,
    }

    fn generate_particles(count: usize, width: usize, height: usize) -> Vec<Particle> {
    let mut rng = rand::thread_rng();
    let mut particles = Vec::with_capacity(count);

    for _ in 0..count {
    particles.push(Particle {
    x: rng.gen_range(0.0..width as f32),
    y: rng.gen_range(0.0..height as f32),
    dx: rng.gen_range(-1.0..1.0),
    dy: rng.gen_range(-1.0..1.0),
    });
    }

    particles
    }

    fn main() {
    let mut particles = generate_particles(PARTICLE_COUNT, WIDTH, HEIGHT);
    let mut buffer: Vec<u32> = vec![0; WIDTH * HEIGHT];
    let mut window = Window::new("Safu", WIDTH, HEIGHT, WindowOptions::default())
    .unwrap_or_else(|e| {
    panic!("{}", e);
    });

    let mut mouse_pos = (WIDTH as f32 / 2.0, HEIGHT as f32 / 2.0);
    let mut mouse_pressed = false;
    let friction_per_second = 0.99_f32.powf(60.0);

    let mut last_frame_time = Instant::now();
    let mut frame_count = 0;
    let mut last_fps_time = Instant::now();
    let mut fps = 0;

    while window.is_open() && !window.is_key_down(Key::Escape) {
    let now = Instant::now();
    let delta_time = now.duration_since(last_frame_time).as_secs_f32();
    last_frame_time = now;

    if let Some((mx, my)) = window.get_mouse_pos(minifb::MouseMode::Clamp) {
    mouse_pos = (mx, my);
    }

    mouse_pressed = window.get_mouse_down(MouseButton::Left);
    let dt = delta_time;
    for particle in particles.iter_mut() {
    if mouse_pressed {
    let dx = mouse_pos.0 - particle.x;
    let dy = mouse_pos.1 - particle.y;
    let distance = (dx * dx + dy * dy).sqrt();

    if distance > 0.2 {
    let inv_gravity = GRAVITY_STRENGTH / distance;
    particle.dx += dx * inv_gravity * dt;
    particle.dy += dy * inv_gravity * dt;
    }
    }

    let friction = friction_per_second.powf(dt);
    particle.dx *= friction;
    particle.dy *= friction;
    particle.x += particle.dx * dt;
    particle.y += particle.dy * dt;
    }

    buffer.iter_mut().for_each(|pixel| *pixel = 0);
    for particle in &particles {
    let x = particle.x as usize;
    let y = particle.y as usize;
    let red = (particle.x / WIDTH as f32 * 255.0 * 0.8) as u32;
    let green = (particle.y / HEIGHT as f32 * 255.0 * 0.8) as u32;
    let blue = (255.0*0.5) as u32;
    let color = (red << 16) | (green << 8) | blue;
    let idx = (y.max(0).min(HEIGHT-1) * WIDTH + x.max(0).min(WIDTH-1));
    buffer[idx] = color;
    }

    window.update_with_buffer(&buffer, WIDTH, HEIGHT).unwrap();

    frame_count += 1;
    if now.duration_since(last_fps_time).as_secs_f32() >= 1.0 {
    fps = frame_count;
    frame_count = 0;
    last_fps_time = now;

    println!("FPS: {} {}", fps, delta_time);
    }
    }
    }
    122 changes: 122 additions & 0 deletions v2
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,122 @@
    use minifb::{Key, MouseButton, Window, WindowOptions};
    use std::mem::MaybeUninit;
    use rand::Rng;
    use std::time::{Instant};

    const WIDTH: usize = 1200;
    const HEIGHT: usize = 800;
    const PARTICLE_COUNT: usize = 5_000_000;
    const GRAVITY_STRENGTH: f32 = 200.5;
    const MAX_PERF_SAMPLE_FRAMES: usize = 1000;

    fn main() {
    let mut particles: Box<[MaybeUninit<f32>]> = Box::new_uninit_slice(PARTICLE_COUNT*4);

    for i in 0..(PARTICLE_COUNT * 4) {
    particles[i].write(0.0);
    }
    let mut particles: Box<[f32]> = unsafe { std::mem::transmute(particles) };

    let mut rng = rand::thread_rng();
    let mut buffer: Vec<u32> = vec![0; WIDTH * HEIGHT];
    let mut window = Window::new("Safu", WIDTH, HEIGHT, WindowOptions::default())
    .unwrap_or_else(|e| {
    panic!("{}", e);
    });
    window.set_target_fps(120);

    let mut i: usize = 0;
    while i < PARTICLE_COUNT {
    let idx = i*4;
    particles[idx] = rng.gen_range(0.0..WIDTH as f32);
    particles[idx+1] = rng.gen_range(0.0..HEIGHT as f32);
    particles[idx+2] = rng.gen_range(-1.0..1.0);
    particles[idx+3] = rng.gen_range(-1.0..1.0);
    i += 1;
    }

    let mut mouse_pos = (WIDTH as f32 / 2.0, HEIGHT as f32 / 2.0);
    let mut mouse_pressed = false;
    let friction_per_second = 0.99_f32.powf(60.0);

    let mut frame_count = 0;
    let mut last_fps_time = Instant::now();
    let mut last_frame_time = Instant::now();
    let mut simulation_times = vec![];
    let mut drawing_times = vec![];
    let mut fps = 0;

    while window.is_open() && !window.is_key_down(Key::Escape) {
    let now = Instant::now();
    let delta_time = now.duration_since(last_frame_time).as_secs_f32();
    last_frame_time = now;

    let sim_start = Instant::now();

    if let Some((mx, my)) = window.get_mouse_pos(minifb::MouseMode::Clamp) {
    mouse_pos = (mx, my);
    }

    mouse_pressed = window.get_mouse_down(MouseButton::Left);

    for particle in particles.chunks_mut(4) {
    if mouse_pressed {
    let dx = mouse_pos.0 - particle[0];
    let dy = mouse_pos.1 - particle[1];
    let distance = (dx * dx + dy * dy).sqrt();

    if distance > 1.0 {
    let inv_gravity = GRAVITY_STRENGTH / distance;
    particle[2] += dx * inv_gravity * 8.0 * delta_time;
    particle[3] += dy * inv_gravity * 8.0 * delta_time;
    }
    }

    let friction = friction_per_second.powf(delta_time);
    particle[2] *= friction;
    particle[3] *= friction;
    particle[0] += particle[2] * delta_time;
    particle[1] += particle[3] * delta_time;
    }

    simulation_times.push(sim_start.elapsed().as_secs_f32());
    if simulation_times.len() > MAX_PERF_SAMPLE_FRAMES {
    simulation_times.remove(0);
    }

    let draw_start = Instant::now();
    buffer.iter_mut().for_each(|pixel| *pixel = 0);

    for particle in particles.chunks_mut(4) {
    let x = particle[0] as usize;
    let y = particle[1] as usize;
    if x < WIDTH && y < HEIGHT && x > 0 && y > 0 {
    let red = (particle[0] / WIDTH as f32 * 255.0 * 0.8) as u32;
    let green = (particle[1] / HEIGHT as f32 * 255.0 * 0.8) as u32;
    let blue = (255.0*0.5) as u32;
    let color = (red << 16) | (green << 8) | blue;
    buffer[y * WIDTH + x] = color;
    }
    }
    window.update_with_buffer(&buffer, WIDTH, HEIGHT).unwrap();

    drawing_times.push(draw_start.elapsed().as_secs_f32());
    if drawing_times.len() > MAX_PERF_SAMPLE_FRAMES {
    drawing_times.remove(0);
    }

    frame_count += 1;
    if now.duration_since(last_fps_time).as_secs_f32() >= 1.0 {
    fps = frame_count;
    frame_count = 0;
    last_fps_time = now;

    let avg_simulation_time: f32 = simulation_times.iter().sum::<f32>() / simulation_times.len() as f32;
    let avg_drawing_time: f32 = drawing_times.iter().sum::<f32>() / drawing_times.len() as f32;

    println!("FPS: {}", fps);
    println!("Avg Simulation Time: {:.5}", avg_simulation_time*1000.0);
    println!("Avg Drawing Time: {:.5}", avg_drawing_time*1000.0);
    }
    }
    }
    139 changes: 139 additions & 0 deletions v3
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,139 @@
    use minifb::{Key, MouseButton, Window, WindowOptions};
    use rand::Rng;
    use std::mem::MaybeUninit;
    use std::time::Instant;
    use rayon::prelude::*;

    const WIDTH: usize = 1200;
    const HEIGHT: usize = 800;
    const PARTICLE_COUNT: usize = 20_000_000;
    const GRAVITY_STRENGTH: f32 = 200.5;
    const MAX_PERF_SAMPLE_FRAMES: usize = 100;

    fn main() {
    let mut particles: Box<[MaybeUninit<f32>]> = Box::new_uninit_slice(PARTICLE_COUNT * 4);

    for i in 0..(PARTICLE_COUNT * 4) {
    particles[i].write(0.0);
    }
    let mut particles: Box<[f32]> = unsafe { std::mem::transmute(particles) };

    let mut rng = rand::thread_rng();
    let mut buffer: Vec<u32> = vec![0; WIDTH * HEIGHT];
    let mut window =
    Window::new("Safu", WIDTH, HEIGHT, WindowOptions::default()).unwrap_or_else(|e| {
    panic!("{}", e);
    });
    window.set_target_fps(120);

    let mut i: usize = 0;
    while i < PARTICLE_COUNT {
    let idx = i * 4;
    particles[idx] = rng.gen_range(0.0..WIDTH as f32);
    particles[idx + 1] = rng.gen_range(0.0..HEIGHT as f32);
    particles[idx + 2] = rng.gen_range(-1.0..1.0);
    particles[idx + 3] = rng.gen_range(-1.0..1.0);
    i += 1;
    }

    let mut mouse_pos = (WIDTH as f32 / 2.0, HEIGHT as f32 / 2.0);
    let mut mouse_pressed = false;
    let friction_per_second = 0.99_f32.powf(60.0);

    let mut frame_count = 0;
    let mut last_fps_time = Instant::now();
    let mut last_frame_time = Instant::now();
    let mut simulation_times = vec![];
    let mut drawing_times = vec![];
    let mut fps = 0;

    while window.is_open() && !window.is_key_down(Key::Escape) {
    let now = Instant::now();
    let delta_time = now.duration_since(last_frame_time).as_secs_f32();
    last_frame_time = now;

    let sim_start = Instant::now();

    if let Some((mx, my)) = window.get_mouse_pos(minifb::MouseMode::Clamp) {
    mouse_pos = (mx, my);
    }

    mouse_pressed = window.get_mouse_down(MouseButton::Left);

    particles.par_chunks_mut(4).with_min_len(10000).for_each(|particle| {
    if mouse_pressed {
    let dx = mouse_pos.0 - particle[0];
    let dy = mouse_pos.1 - particle[1];
    let distance = (dx * dx + dy * dy).sqrt();

    if distance > 1.0 {
    let inv_gravity = GRAVITY_STRENGTH / distance;
    particle[2] += dx * inv_gravity * 8.0 * delta_time;
    particle[3] += dy * inv_gravity * 8.0 * delta_time;
    }
    }

    let friction = friction_per_second.powf(delta_time);
    particle[2] *= friction;
    particle[3] *= friction;
    particle[0] += particle[2] * delta_time;
    particle[1] += particle[3] * delta_time;
    });

    simulation_times.push(sim_start.elapsed().as_secs_f32());
    if simulation_times.len() > MAX_PERF_SAMPLE_FRAMES {
    simulation_times.remove(0);
    }

    let draw_start = Instant::now();
    buffer.iter_mut().for_each(|pixel| *pixel = 0);

    for particle in particles.chunks_mut(4) {
    let x = particle[0] as usize;
    let y = particle[1] as usize;
    if x >= WIDTH || y >= HEIGHT || x <= 0 || y <= 0 {
    continue;
    }

    let red = (particle[0] as f32 / WIDTH as f32 * 255.0 * 0.8) as u8;
    let green = (particle[1] as f32 / HEIGHT as f32 * 255.0 * 0.8) as u8;
    let blue = (255.0 * 0.5) as u8;

    let buffer_index = y * WIDTH + x;
    let existing_color = buffer[buffer_index];

    let existing_red = ((existing_color >> 16) & 0xFF) as u8;
    let existing_green = ((existing_color >> 8) & 0xFF) as u8;
    let existing_blue = (existing_color & 0xFF) as u8;

    let combined_red = existing_red.saturating_add(red);
    let combined_green = existing_green.saturating_add(green);
    let combined_blue = existing_blue.saturating_add(blue);

    let combined_color = ((combined_red as u32) << 16) | ((combined_green as u32) << 8) | (combined_blue as u32);
    buffer[buffer_index] = combined_color;
    }
    window.update_with_buffer(&buffer, WIDTH, HEIGHT).unwrap();

    drawing_times.push(draw_start.elapsed().as_secs_f32());
    if drawing_times.len() > MAX_PERF_SAMPLE_FRAMES {
    drawing_times.remove(0);
    }

    frame_count += 1;
    if now.duration_since(last_fps_time).as_secs_f32() >= 1.0 {
    fps = frame_count;
    frame_count = 0;
    last_fps_time = now;

    let avg_simulation_time: f32 =
    simulation_times.iter().sum::<f32>() / simulation_times.len() as f32;
    let avg_drawing_time: f32 =
    drawing_times.iter().sum::<f32>() / drawing_times.len() as f32;

    println!("FPS: {}", fps);
    println!("Avg Simulation Time: {:.5}", avg_simulation_time * 1000.0);
    println!("Avg Drawing Time: {:.5}", avg_drawing_time * 1000.0);
    }
    }
    }
    162 changes: 162 additions & 0 deletions v4
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,162 @@
    use minifb::{Key, MouseButton, Window, WindowOptions};
    use rand::Rng;
    use std::sync::mpsc;
    use std::thread;
    use std::time::Instant;

    const WIDTH: usize = 1200;
    const HEIGHT: usize = 800;
    const PARTICLE_COUNT: usize = 20_000_000;
    const THREAD_COUNT: usize = 8;
    const PARTICLE_STRIDE: usize = 4;
    const GRAVITY_STRENGTH: f32 = 200.5;
    const MAX_PERF_SAMPLE_FRAMES: usize = 100;

    fn main() {
    let mut particles: Box<[f32]> = vec![0.0; PARTICLE_COUNT * PARTICLE_STRIDE].into_boxed_slice();

    let mut rng = rand::thread_rng();
    let mut buffer: Vec<u32> = vec![0; WIDTH * HEIGHT];
    let mut p_count_buffer = vec![0 as u8; WIDTH * HEIGHT];
    let mut window = Window::new("Safu", WIDTH, HEIGHT, WindowOptions::default()).unwrap_or_else(|e| {
    panic!("{}", e);
    });
    window.set_target_fps(120);

    let mut i: usize = 0;
    while i < PARTICLE_COUNT {
    let idx = i * PARTICLE_STRIDE;
    particles[idx] = rng.gen_range(0.0..WIDTH as f32);
    particles[idx + 1] = rng.gen_range(0.0..HEIGHT as f32);
    particles[idx + 2] = rng.gen_range(-1.0..1.0);
    particles[idx + 3] = rng.gen_range(-1.0..1.0);
    i += 1;
    }

    let mut mouse_pos = (WIDTH as f32 / 2.0, HEIGHT as f32 / 2.0);
    let mut mouse_pressed = false;
    let friction_per_second = 0.99_f32.powf(60.0);

    let mut frame_count = 0;
    let mut last_fps_time = Instant::now();
    let mut last_frame_time = Instant::now();
    let mut simulation_times = vec![];
    let mut drawing_times = vec![];
    let mut fps = 0;

    while window.is_open() && !window.is_key_down(Key::Escape) {
    let now = Instant::now();
    let delta_time = now.duration_since(last_frame_time).as_secs_f32();
    last_frame_time = now;

    let sim_start = Instant::now();

    if let Some((mx, my)) = window.get_mouse_pos(minifb::MouseMode::Clamp) {
    mouse_pos = (mx, my);
    }
    mouse_pressed = window.get_mouse_down(MouseButton::Left);

    let (tx, rx) = mpsc::channel();
    let chunk_size = PARTICLE_COUNT / THREAD_COUNT;
    let particles_ptr = particles.as_mut_ptr();

    for t in 0..THREAD_COUNT {
    let tx = tx.clone();
    let mouse_pos = mouse_pos;
    let mut buffer = vec![0 as u8; WIDTH * HEIGHT];

    let start = t * chunk_size * PARTICLE_STRIDE;
    let end = if t == THREAD_COUNT - 1 {
    PARTICLE_COUNT * PARTICLE_STRIDE
    } else {
    (t + 1) * chunk_size * PARTICLE_STRIDE
    };

    unsafe {
    let particles_chunk = std::slice::from_raw_parts_mut(particles_ptr.add(start), end - start);

    thread::spawn(move || {
    for particle in particles_chunk.chunks_mut(PARTICLE_STRIDE) {
    if mouse_pressed {
    let dx = mouse_pos.0 - particle[0];
    let dy = mouse_pos.1 - particle[1];
    let distance = dx * dx + dy * dy;

    if distance > 2.0 {
    let inv_gravity = GRAVITY_STRENGTH / distance.sqrt();
    particle[2] += dx * inv_gravity * 8.0 * delta_time;
    particle[3] += dy * inv_gravity * 8.0 * delta_time;
    }
    }

    let friction = friction_per_second.powf(delta_time);
    particle[2] *= friction;
    particle[3] *= friction;
    particle[0] += particle[2] * delta_time;
    particle[1] += particle[3] * delta_time;

    let x = particle[0] as usize;
    let y = particle[1] as usize;
    if x >= WIDTH || y >= HEIGHT || x <= 0 || y <= 0 {
    continue;
    }

    let buffer_index = y * WIDTH + x;
    let p_count = buffer[buffer_index];
    buffer[buffer_index] = p_count.saturating_add(1);
    }

    tx.send(buffer).unwrap();
    });
    }
    }

    drop(tx);
    p_count_buffer.iter_mut().for_each(|c| *c = 0);
    for local_buffer in rx {
    for (i, &count) in local_buffer.iter().enumerate() {
    p_count_buffer[i] = p_count_buffer[i].saturating_add(count);
    }
    }

    simulation_times.push(sim_start.elapsed().as_secs_f32());
    if simulation_times.len() > MAX_PERF_SAMPLE_FRAMES {
    simulation_times.remove(0);
    }

    let draw_start = Instant::now();
    buffer.iter_mut().for_each(|pixel| *pixel = 0);
    for (i, &count) in p_count_buffer.iter().enumerate() {
    let x = i % WIDTH;
    let y = i / WIDTH;
    let r = (x as f32 / WIDTH as f32 * 12.0 * count as f32) as u8;
    let g = (y as f32 / HEIGHT as f32 * 12.0 * count as f32) as u8;
    let b = (10.0 * count as f32) as u8;

    buffer[i] = ((r as u32) << 16) | ((g as u32) << 8) | (b as u32);
    }

    window.update_with_buffer(&buffer, WIDTH, HEIGHT).unwrap();

    drawing_times.push(draw_start.elapsed().as_secs_f32());
    if drawing_times.len() > MAX_PERF_SAMPLE_FRAMES {
    drawing_times.remove(0);
    }

    frame_count += 1;
    if now.duration_since(last_fps_time).as_secs_f32() >= 1.0 {
    fps = frame_count;
    frame_count = 0;
    last_fps_time = now;

    let avg_simulation_time: f32 =
    simulation_times.iter().sum::<f32>() / simulation_times.len() as f32;
    let avg_drawing_time: f32 =
    drawing_times.iter().sum::<f32>() / drawing_times.len() as f32;

    println!("FPS: {}", fps);
    println!("Avg Simulation Time: {:.5}", avg_simulation_time * 1000.0);
    println!("Avg Drawing Time: {:.5}", avg_drawing_time * 1000.0);
    }
    }
    }
    173 changes: 173 additions & 0 deletions v5
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,173 @@
    use how_fast_is_rust::thread_pool::ThreadPool;
    use minifb::{Key, MouseButton, Window, WindowOptions};
    use rand::Rng;
    use std::time::Instant;

    const WIDTH: usize = 1200;
    const HEIGHT: usize = 800;
    const PARTICLE_COUNT: usize = 10_000_000;
    const THREAD_COUNT: usize = 8;
    const PARTICLE_STRIDE: usize = 2;
    const GRAVITY_STRENGTH: f32 = 200.5;
    const MAX_PERF_SAMPLE_FRAMES: usize = 100;

    fn main() {
    let mut shared_pos_buffer = vec![0.0; PARTICLE_COUNT * PARTICLE_STRIDE].into_boxed_slice();
    let mut shared_vel_buffer = vec![0.0; PARTICLE_COUNT * PARTICLE_STRIDE].into_boxed_slice();
    let mut shared_p_count_buffer = vec![0 as u8; WIDTH * HEIGHT * THREAD_COUNT].into_boxed_slice();

    let mut rng = rand::thread_rng();
    let mut frame_buffer: Vec<u32> = vec![0; WIDTH * HEIGHT];
    let mut p_count_buffer = vec![0 as u8; WIDTH * HEIGHT];

    let mut window = Window::new("Safu", WIDTH, HEIGHT, WindowOptions::default()).unwrap_or_else(|e| {
    panic!("{}", e);
    });
    window.set_target_fps(120);

    let mut i: usize = 0;
    while i < PARTICLE_COUNT {
    let idx = i * PARTICLE_STRIDE;
    shared_pos_buffer[idx] = rng.gen_range(0.0..WIDTH as f32);
    shared_pos_buffer[idx + 1] = rng.gen_range(0.0..HEIGHT as f32);
    shared_vel_buffer[idx] = rng.gen_range(-2.0..2.0);
    shared_vel_buffer[idx + 1] = rng.gen_range(-2.0..2.0);
    i += 1;
    }

    let mut mouse_pos = (WIDTH as f32 / 2.0, HEIGHT as f32 / 2.0);
    let mut mouse_pressed = false;
    let friction_per_second = 0.99_f32.powf(60.0);

    let mut frame_count = 0;
    let mut last_fps_time = Instant::now();
    let mut last_frame_time = Instant::now();
    let mut simulation_times = vec![];
    let mut drawing_times = vec![];
    let mut fps = 0;

    let pool = ThreadPool::new(THREAD_COUNT);
    let chunk_size = PARTICLE_COUNT / THREAD_COUNT;
    let shared_pos_buff_ptr = shared_pos_buffer.as_mut_ptr();
    let shared_vel_buff_ptr = shared_vel_buffer.as_mut_ptr();
    let shared_p_count_buff_ptr = shared_p_count_buffer.as_mut_ptr();

    while window.is_open() && !window.is_key_down(Key::Escape) {
    let now = Instant::now();
    let delta_time = now.duration_since(last_frame_time).as_secs_f32();
    last_frame_time = now;

    let sim_start = Instant::now();

    if let Some((mx, my)) = window.get_mouse_pos(minifb::MouseMode::Clamp) {
    mouse_pos = (mx, my);
    }
    mouse_pressed = window.get_mouse_down(MouseButton::Left);


    // clear p_counts
    shared_p_count_buffer.iter_mut().for_each(|c| *c = 0);
    for t in 0..THREAD_COUNT {
    let mouse_pos = mouse_pos;

    let p_data_start = t * chunk_size * PARTICLE_STRIDE;
    let p_data_end = if t == THREAD_COUNT - 1 {
    PARTICLE_COUNT * PARTICLE_STRIDE
    } else {
    (t + 1) * chunk_size * PARTICLE_STRIDE
    };
    let p_count_start = t * WIDTH * HEIGHT;
    let p_count_end = if t == THREAD_COUNT - 1 {
    WIDTH * HEIGHT
    } else {
    (t + 1) * WIDTH * HEIGHT
    };

    unsafe {
    let pos_chunk = std::slice::from_raw_parts_mut(shared_pos_buff_ptr.add(p_data_start), p_data_end - p_data_start);
    let vel_chunk = std::slice::from_raw_parts_mut(shared_vel_buff_ptr.add(p_data_start), p_data_end - p_data_start);
    let p_count_chunk = std::slice::from_raw_parts_mut(shared_p_count_buff_ptr.add(p_count_start), p_count_end - p_count_start);
    let friction = friction_per_second.powf(delta_time);
    pool.execute(move || {
    for (pos, vel) in pos_chunk.chunks_mut(2).zip(vel_chunk.chunks_mut(2)) {
    if mouse_pressed {
    let dx = mouse_pos.0 - pos[0];
    let dy = mouse_pos.1 - pos[1];
    let distance = (dx * dx + dy * dy).sqrt();

    if distance > 2.0 {
    let inv_gravity = GRAVITY_STRENGTH / distance;
    vel[0] += dx * inv_gravity * 8.0 * delta_time;
    vel[1] += dy * inv_gravity * 8.0 * delta_time;
    }
    }

    vel[0] *= friction;
    vel[1] *= friction;
    pos[0] += vel[0] * delta_time;
    pos[1] += vel[1] * delta_time;

    let x = pos[0] as usize;
    let y = pos[1] as usize;
    if x >= WIDTH || y >= HEIGHT || x <= 0 || y <= 0 {
    continue;
    }

    let buffer_index = y * WIDTH + x;
    let p_count = p_count_chunk[buffer_index];
    p_count_chunk[buffer_index] = p_count.saturating_add(1);
    }
    });
    }
    }

    pool.wait_for_completion();

    simulation_times.push(sim_start.elapsed().as_secs_f32());
    if simulation_times.len() > MAX_PERF_SAMPLE_FRAMES {
    simulation_times.remove(0);
    }

    let draw_start = Instant::now();
    p_count_buffer.iter_mut().for_each(|c| *c = 0);
    for counts in shared_p_count_buffer.chunks_mut(WIDTH*HEIGHT) {
    for (i, &count) in counts.iter().enumerate() {
    p_count_buffer[i] = p_count_buffer[i].saturating_add(count);
    }
    }

    frame_buffer.iter_mut().for_each(|pixel| *pixel = 0);
    for (i, &count) in p_count_buffer.iter().enumerate() {
    let x = i % WIDTH;
    let y = i / WIDTH;
    let r = (x as f32 / WIDTH as f32 * 55.0 * count.clamp(0, 4) as f32) as u8;
    let g = (y as f32 / HEIGHT as f32 * 55.0 * count.clamp(0, 4) as f32) as u8;
    let b = (55.0 * count.clamp(0, 4) as f32) as u8;

    frame_buffer[i] = ((r as u32) << 16) | ((g as u32) << 8) | (b as u32);
    }

    window.update_with_buffer(&frame_buffer, WIDTH, HEIGHT).unwrap();

    drawing_times.push(draw_start.elapsed().as_secs_f32());
    if drawing_times.len() > MAX_PERF_SAMPLE_FRAMES {
    drawing_times.remove(0);
    }

    frame_count += 1;
    if now.duration_since(last_fps_time).as_secs_f32() >= 1.0 {
    fps = frame_count;
    frame_count = 0;
    last_fps_time = now;

    let avg_simulation_time: f32 =
    simulation_times.iter().sum::<f32>() / simulation_times.len() as f32;
    let avg_drawing_time: f32 =
    drawing_times.iter().sum::<f32>() / drawing_times.len() as f32;

    println!("FPS: {}", fps);
    println!("Avg Simulation Time: {:.5}", avg_simulation_time * 1000.0);
    println!("Avg Drawing Time: {:.5}", avg_drawing_time * 1000.0);
    }
    }
    }
    181 changes: 181 additions & 0 deletions v6
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,181 @@
    #![feature(portable_simd)]

    use std::simd::{cmp::SimdPartialOrd, f32x8, simd_swizzle, StdFloat};
    use how_fast_is_rust::thread_pool::ThreadPool;
    use minifb::{Key, MouseButton, Window, WindowOptions};
    use rand::Rng;
    use std::time::Instant;

    const WIDTH: usize = 1200;
    const HEIGHT: usize = 800;
    const PARTICLE_COUNT: usize = 100_000_000;
    const THREAD_COUNT: usize = 8;
    const PARTICLE_STRIDE: usize = 2;
    const GRAVITY_STRENGTH: f32 = 200.5 * 7.0;
    const MAX_PERF_SAMPLE_FRAMES: usize = 10;

    fn main() {
    let mut shared_pos_buffer = vec![0.0; PARTICLE_COUNT * PARTICLE_STRIDE].into_boxed_slice();
    let mut shared_vel_buffer = vec![0.0; PARTICLE_COUNT * PARTICLE_STRIDE].into_boxed_slice();
    let mut shared_p_count_buffer = vec![0 as u8; WIDTH * HEIGHT * THREAD_COUNT].into_boxed_slice();

    let mut rng = rand::thread_rng();
    let mut frame_buffer: Vec<u32> = vec![0; WIDTH * HEIGHT];
    let mut p_count_buffer = vec![0 as u8; WIDTH * HEIGHT];

    let mut window = Window::new("Safu", WIDTH, HEIGHT, WindowOptions::default()).unwrap_or_else(|e| {
    panic!("{}", e);
    });
    window.set_target_fps(120);

    let mut i: usize = 0;
    while i < PARTICLE_COUNT {
    let idx = i * PARTICLE_STRIDE;
    shared_pos_buffer[idx] = rng.gen_range(0.0..WIDTH as f32);
    shared_pos_buffer[idx + 1] = rng.gen_range(0.0..HEIGHT as f32);
    shared_vel_buffer[idx] = rng.gen_range(-2.0..2.0);
    shared_vel_buffer[idx + 1] = rng.gen_range(-2.0..2.0);
    i += 1;
    }

    let mut mouse_pos = (WIDTH as f32 / 2.0, HEIGHT as f32 / 2.0);
    let mut mouse_pressed = false;
    let friction_per_second = 0.99_f32.powf(60.0);

    let mut frame_count = 0;
    let mut last_fps_time = Instant::now();
    let mut last_frame_time = Instant::now();
    let mut simulation_times = vec![];
    let mut drawing_times = vec![];
    let mut fps = 0;

    let pool = ThreadPool::new(THREAD_COUNT);
    let chunk_size = PARTICLE_COUNT / THREAD_COUNT;
    let shared_pos_buff_ptr = shared_pos_buffer.as_mut_ptr();
    let shared_vel_buff_ptr = shared_vel_buffer.as_mut_ptr();
    let shared_p_count_buff_ptr = shared_p_count_buffer.as_mut_ptr();

    while window.is_open() && !window.is_key_down(Key::Escape) {
    let now = Instant::now();
    let delta_time = now.duration_since(last_frame_time).as_secs_f32();
    last_frame_time = now;

    let sim_start = Instant::now();

    if let Some((mx, my)) = window.get_mouse_pos(minifb::MouseMode::Clamp) {
    mouse_pos = (mx, my);
    }
    mouse_pressed = window.get_mouse_down(MouseButton::Left);

    // clear p_counts
    shared_p_count_buffer.iter_mut().for_each(|c| *c = 0);
    for t in 0..THREAD_COUNT {
    let mouse_pos = mouse_pos;

    let p_data_start = t * chunk_size * PARTICLE_STRIDE;
    let p_data_end = if t == THREAD_COUNT - 1 {
    PARTICLE_COUNT * PARTICLE_STRIDE
    } else {
    (t + 1) * chunk_size * PARTICLE_STRIDE
    };
    let p_count_start = t * WIDTH * HEIGHT;
    let p_count_end = if t == THREAD_COUNT - 1 {
    WIDTH * HEIGHT
    } else {
    (t + 1) * WIDTH * HEIGHT
    };

    unsafe {
    let pos_chunk = std::slice::from_raw_parts_mut(shared_pos_buff_ptr.add(p_data_start), p_data_end - p_data_start);
    let vel_chunk = std::slice::from_raw_parts_mut(shared_vel_buff_ptr.add(p_data_start), p_data_end - p_data_start);
    let p_count_chunk = std::slice::from_raw_parts_mut(shared_p_count_buff_ptr.add(p_count_start), p_count_end - p_count_start);
    let friction = friction_per_second.powf(delta_time);
    pool.execute(move || {
    let gravity_vec = f32x8::splat(GRAVITY_STRENGTH);
    let delta_time_vec = f32x8::splat(delta_time);
    let friction_vec = f32x8::splat(friction);
    let mouse_pos_vec = f32x8::from_array([mouse_pos.0, mouse_pos.1, mouse_pos.0, mouse_pos.1, mouse_pos.0, mouse_pos.1, mouse_pos.0, mouse_pos.1]);
    let threshold_vec = f32x8::splat(2.0);

    for (pos, vel) in pos_chunk.chunks_exact_mut(8).zip(vel_chunk.chunks_exact_mut(8)) {
    let mut pos_vec = f32x8::from_slice(pos);
    let mut vel_vec = f32x8::from_slice(vel);

    if mouse_pressed {
    let delta = mouse_pos_vec - pos_vec;
    let distance_sq = delta * delta;
    let distance_vec: f32x8 = (simd_swizzle!(distance_sq, [1, 0, 3, 2, 5, 4, 7, 6]) + distance_sq).sqrt();
    let mask = distance_vec.simd_gt(threshold_vec);
    let inv_gravity = gravity_vec / distance_vec;
    let force = delta * inv_gravity * delta_time_vec;
    vel_vec = mask.select(vel_vec + force, vel_vec);
    }

    vel_vec *= friction_vec;
    pos_vec += vel_vec * delta_time_vec;

    pos.copy_from_slice(pos_vec.as_array());
    vel.copy_from_slice(vel_vec.as_array());

    for j in (0..8).step_by(2) {
    let x = pos[j] as usize;
    let y = pos[j + 1] as usize;
    let buffer_index = y.max(0).min(HEIGHT-1) * WIDTH + x.max(0).min(WIDTH-1);
    let p_count = p_count_chunk.get_unchecked(buffer_index);
    p_count_chunk[buffer_index] = p_count.saturating_add(1);
    }
    }
    });
    }
    }

    pool.wait_for_completion();

    simulation_times.push(sim_start.elapsed().as_secs_f32());
    if simulation_times.len() > MAX_PERF_SAMPLE_FRAMES {
    simulation_times.remove(0);
    }

    let draw_start = Instant::now();
    p_count_buffer.iter_mut().for_each(|c| *c = 0);
    for counts in shared_p_count_buffer.chunks_mut(WIDTH*HEIGHT) {
    for (i, &count) in counts.iter().enumerate() {
    p_count_buffer[i] = p_count_buffer[i].saturating_add(count);
    }
    }

    frame_buffer.iter_mut().for_each(|pixel| *pixel = 0);
    for (i, &count) in p_count_buffer.iter().enumerate() {
    let x = i % WIDTH;
    let y = i / WIDTH;
    let r = (x as f32 / WIDTH as f32 * 10.0 * count.clamp(0, 30) as f32) as u8;
    let g = (y as f32 / HEIGHT as f32 * 10.0 * count.clamp(0, 30) as f32) as u8;
    let b = (10.0 * count.clamp(0, 30) as f32) as u8;

    frame_buffer[i] = ((r as u32) << 16) | ((g as u32) << 8) | (b as u32);
    }

    window.update_with_buffer(&frame_buffer, WIDTH, HEIGHT).unwrap();

    drawing_times.push(draw_start.elapsed().as_secs_f32());
    if drawing_times.len() > MAX_PERF_SAMPLE_FRAMES {
    drawing_times.remove(0);
    }

    frame_count += 1;
    if now.duration_since(last_fps_time).as_secs_f32() >= 1.0 {
    fps = frame_count;
    frame_count = 0;
    last_fps_time = now;

    let avg_simulation_time: f32 =
    simulation_times.iter().sum::<f32>() / simulation_times.len() as f32;
    let avg_drawing_time: f32 =
    drawing_times.iter().sum::<f32>() / drawing_times.len() as f32;

    println!("FPS: {}", fps);
    println!("Avg Simulation Time: {:.5}", avg_simulation_time * 1000.0);
    println!("Avg Drawing Time: {:.5}", avg_drawing_time * 1000.0);
    }
    }
    }
    216 changes: 216 additions & 0 deletions v7
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,216 @@
    use minifb::{Key, MouseButton, Window, WindowOptions};
    use rand::Rng;
    use std::time::{Instant};
    use std::f32::consts::PI;

    const WIDTH: usize = 600;
    const HEIGHT: usize = 400;
    const PARTICLE_COUNT: usize = 80_000;
    const GRAVITY_STRENGTH: f32 = 105.0;
    #[repr(align(16))]
    struct Particle {
    x: f32,
    y: f32,
    angle: f32,
    speed: f32,
    _type: u8,
    }

    fn main() {
    let width = WIDTH as f32;
    let height = HEIGHT as f32;
    let spawn_width = 100;
    let spawn_height = 100;
    let mut rng = rand::thread_rng();
    let mut particles = Vec::with_capacity(PARTICLE_COUNT);
    for _ in 0..PARTICLE_COUNT {
    particles.push(Particle {
    x: rng.gen_range((WIDTH / 2 - spawn_width / 2) as f32..(WIDTH/2 + spawn_width/2) as f32),
    y: rng.gen_range((HEIGHT / 2 - spawn_height / 2) as f32..(HEIGHT/2 + spawn_height/2) as f32),
    angle: rng.gen_range(0.0..360.0) * PI / 180.0,
    speed: 80.0 + rng.gen_range(0.0..10.0),
    _type: rng.gen_range(0..40),
    });
    }

    let mut frame_buff: Vec<u32> = vec![0; WIDTH * HEIGHT];
    let mut trail_buff: Vec<f32> = vec![0.0; WIDTH * HEIGHT];
    let mut window = Window::new("Safu", WIDTH, HEIGHT, WindowOptions::default())
    .unwrap_or_else(|e| {
    panic!("{}", e);
    });

    let mut mouse_pos = (WIDTH as f32 / 2.0, HEIGHT as f32 / 2.0);
    let mut mouse_pressed = false;

    let deposition: f32 = 3.0;
    let sense_dist: f32 = 18.0;
    let sense_angle: f32 = PI / 180.0 * 18.0;
    let rotate_angle_dist: f32 = PI / 180.0 * 12.0;
    let rotation_speed: f32 = 4.0;
    let sensor_size: i32 = 1;
    let diffusion_rate: f32 = 24.0;
    let blur_size = 1;

    let mut last_frame_time = Instant::now();

    while window.is_open() && !window.is_key_down(Key::Escape) {
    let now = Instant::now();
    let delta_time = now.duration_since(last_frame_time).as_secs_f32();
    last_frame_time = now;

    if let Some((mx, my)) = window.get_mouse_pos(minifb::MouseMode::Clamp) {
    mouse_pos = (mx, my);
    }

    mouse_pressed = window.get_mouse_down(MouseButton::Left);

    for p in particles.iter_mut() {
    let dt = delta_time;
    let mut nx = p.x + p.speed * p.angle.cos() * dt;
    let mut ny = p.y + p.speed * p.angle.sin() * dt;

    if mouse_pressed {
    let dx = mouse_pos.0 - nx;
    let dy = mouse_pos.1 - ny;
    let dist = (dx*dx + dy*dy).sqrt();
    if dist < 110.0 {
    nx -= dx * GRAVITY_STRENGTH/dist *dt * 1.5;
    ny -= dy * GRAVITY_STRENGTH/dist *dt * 1.5;
    p.angle *= -1.0;
    }
    nx += dx * GRAVITY_STRENGTH/dist *dt * 0.25;
    ny += dy * GRAVITY_STRENGTH/dist *dt * 0.25;
    }

    if nx <= 0.0 || nx >= width || ny <= 0.0 || ny >= height {
    p.angle = rng.gen_range(-1.0..1.0) * 2.0 * PI;
    continue;
    }

    p.x = nx;
    p.y = ny;

    if p._type <= 2 {
    trail_buff[p.y as usize * WIDTH + p.x as usize] -= deposition * dt;
    } else if p._type == 9 {
    trail_buff[p.y as usize * WIDTH + p.x as usize] -= deposition * dt * 2.30;
    } else {
    trail_buff[p.y as usize * WIDTH + p.x as usize] += deposition * dt;
    }

    let front = sense(&p, 0.0, sense_dist, sensor_size, &trail_buff);
    let left = sense(&p, -sense_angle, sense_dist, sensor_size, &trail_buff);
    let right = sense(&p, sense_angle, sense_dist, sensor_size, &trail_buff);

    if front > left && front > right {
    continue;
    }
    if front < left && front < right {
    if rng.gen_range(0.0..1.0) > 0.5 {
    p.angle += rotate_angle_dist * rotation_speed * dt;
    } else {
    p.angle -= rotate_angle_dist * rotation_speed * dt;
    }
    continue;
    }

    if left < right {
    p.angle += rotate_angle_dist * rotation_speed * dt;
    } else {
    p.angle -= rotate_angle_dist * rotation_speed * dt;
    }
    }
    diffuse(&mut trail_buff, diffusion_rate, delta_time, blur_size);

    frame_buff.iter_mut().for_each(|pixel| *pixel = 0);
    for y in 0..HEIGHT {
    for x in 0..WIDTH {
    let index = y * WIDTH + x;
    let trail_intensity = trail_buff[index];
    let intensity_byte = (trail_intensity * 255.0).min(255.0).max(0.0) as f32;
    let sx = x as f32 / width;
    let sy = y as f32 / height;
    let r = (intensity_byte*sy) as u32;
    let g = (intensity_byte*sx) as u32;
    let b = (intensity_byte*0.5) as u32;
    let packed_pixel = (r << 16) | (g << 8) | b;
    frame_buff[index] = packed_pixel;
    }
    }
    for particle in particles.iter() {
    let x = particle.x as usize;
    let y = particle.y as usize;

    if x < WIDTH && y < HEIGHT && x > 0 && y > 0 {
    let red = (particle.y / height * 255.0 * 0.1) as u32;
    let green = (particle.x / width * 255.0 * 0.1) as u32;
    let blue = (255.0 * 0.1) as u32;
    let current_color = frame_buff[y * WIDTH + x];
    let current_red = (current_color >> 16) & 0xFF;
    let current_green = (current_color >> 8) & 0xFF;
    let current_blue = current_color & 0xFF;
    let blended_red = (current_red + red).min(255);
    let blended_green = (current_green + green).min(255);
    let blended_blue = (current_blue + blue).min(255);
    let blended_color = (blended_red << 16) | (blended_green << 8) | blended_blue;

    frame_buff[y * WIDTH + x] = blended_color;
    }
    }

    window.update_with_buffer(&frame_buff, WIDTH, HEIGHT).unwrap();
    }
    }

    #[inline]
    fn sense(p: &Particle, angle_offset: f32, sense_dist: f32, sense_size: i32, trail_map: &Vec<f32>) -> f32 {
    let sensor_angle = p.angle + angle_offset;
    let sense_x = p.x + sense_dist * sensor_angle.cos();
    let sense_y = p.y + sense_dist * sensor_angle.sin();

    let mut sum: f32 = 0.0;
    for offset_x in -sense_size..sense_size {
    for offset_y in -sense_size..sense_size {
    let sample_x = (sense_x as i32 + offset_x).clamp(0, WIDTH as i32 - 1);
    let sample_y = (sense_y as i32 + offset_y).clamp(0, HEIGHT as i32 - 1);

    sum += trail_map[sample_y as usize * WIDTH + sample_x as usize]
    }
    }

    sum
    }

    fn diffuse(trail_map: &mut Vec<f32>, diffusion_rate: f32, dt: f32, blur_size: isize) {
    let mut new_map = trail_map.clone();
    let df: f32 = dt * diffusion_rate;
    for y in 0..HEIGHT {
    for x in 0..WIDTH {
    let index = y * WIDTH + x;
    let current_value = trail_map[index];
    let mut total_contribution = current_value;
    let mut count = 1.0;
    for dy in -blur_size..=blur_size {
    for dx in -blur_size..=blur_size {
    if dx == 0 && dy == 0 {
    continue;
    }

    let nx = x as isize + dx;
    let ny = y as isize + dy;
    if nx >= 0 && nx < WIDTH as isize && ny >= 0 && ny < HEIGHT as isize {
    let neighbor_index = ny as usize * WIDTH + nx as usize;
    total_contribution += trail_map[neighbor_index];
    count += 1.0;
    }
    }
    }
    let new_value = (current_value + (df * (total_contribution / count))) * (1.0-df);
    new_map[index] = new_value;
    }
    }
    for (i, val) in new_map.iter().enumerate() {
    trail_map[i] = *val;
    }
    }
    111 changes: 111 additions & 0 deletions wasm_basic_html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,111 @@
    <!DOCTYPE html>
    <html lang="en" style="user-select: none">
    <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Sabby Rust</title>
    <link rel="icon" href="./favicon.ico" type="image/x-icon" />
    <style>
    body,
    html,
    div,
    canvas {
    -webkit-user-select: none;
    -moz-user-select: none;
    -webkit-touch-callout: none;
    -ms-user-select: none;
    user-select: none;
    outline: none;
    }
    </style>
    </head>
    <body
    style="
    margin: 0;
    width: 100vw;
    height: 100vh;
    background: black;
    -webkit-touch-callout: none;
    overflow: hidden;
    "
    >
    <div>
    <canvas id="canvas"></canvas>
    </div>
    <script type="module">
    import init, { InitInput, TickInput, init as initSimulation, tick } from './how_fast_is_rust.js';

    async function runSimulation() {
    const instance = await init();
    const particle_count = 500_000;
    const gravity = 205.5 * 8.0;

    const canvas = document.getElementById('canvas');
    canvas.style.touchAction = 'none';
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    const ctx = canvas.getContext('2d');
    let imageData = ctx.createImageData(canvas.width, canvas.height);
    let frameBuffer = new Uint32Array(imageData.data.buffer);

    function resize() {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    imageData = ctx.createImageData(canvas.width, canvas.height);
    frameBuffer = new Uint32Array(imageData.data.buffer);
    initSimulation(new InitInput(canvas.width, canvas.height, particle_count, gravity));
    }
    resize();

    let mousePressed = false;
    let mouseX = canvas.width / 2;
    let mouseY = canvas.height / 2;

    window.addEventListener('resize', resize);
    window.addEventListener('mousedown', () => mousePressed = true);
    window.addEventListener('mouseup', () => mousePressed = false);
    window.addEventListener('mousemove', (event) => {
    mouseX = event.offsetX;
    mouseY = event.offsetY;
    });
    window.addEventListener('touchend', (event) => {
    mousePressed = false;
    });

    window.addEventListener('touchmove', (event) => {
    mousePressed = true;
    mouseX = event.touches[0].clientX;
    mouseY = event.touches[0].clientY;
    });
    window.addEventListener('touchstart', (event) => {
    event.preventDefault();
    mousePressed = true;
    mouseX = event.touches[0].clientX;
    mouseY = event.touches[0].clientY;
    });


    let prev = 0;
    function render(timestamp = 0) {
    const dt = (timestamp - prev)/1000;
    prev = timestamp;

    const frame_buff_ptr = tick(new TickInput(dt, mouseX, mouseY, mousePressed));
    const buff = new Uint8Array(instance.memory.buffer, frame_buff_ptr, canvas.width * canvas.height*4);
    for(let i = 0; i < frameBuffer.length;i++) {
    const r = buff[i*4];
    const g = buff[i*4+1];
    const b = buff[i*4+2];
    const a = buff[i*4+3];
    frameBuffer[i] = (r << 24) | (g << 16) | (b << 8) | a;
    }
    ctx.putImageData(imageData, 0, 0);
    ctx.drawImage(canvas, 0, 0);
    requestAnimationFrame(render);
    }
    render();
    }
    runSimulation().catch(e => console.error("Could not run simulation:", e));
    </script>
    </body>
    </html>
    148 changes: 148 additions & 0 deletions wasm_simple
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,148 @@
    #![no_main]
    use rand::Rng;
    use wasm_bindgen::prelude::*;

    #[repr(align(16))]
    struct Particle {
    x: f32,
    y: f32,
    // sx: f32,
    // sy: f32,
    dx: f32,
    dy: f32,
    }

    static mut PARTICLES: Vec<Particle> = Vec::new();
    static mut BUFFER: Vec<u32> = Vec::new();
    static mut WIDTH: usize = 1;
    static mut HEIGHT: usize = 1;
    static mut GRAVITY_STRENGTH: f32 = 205.5 * 6.0;

    #[wasm_bindgen]
    pub struct TickInput {
    pub dt: f32,
    pub touch_x: f32,
    pub touch_y: f32,
    pub is_touch_down: bool,
    }

    #[wasm_bindgen]
    impl TickInput {
    #[wasm_bindgen(constructor)]
    pub fn new(dt: f32, touch_x: f32, touch_y: f32, is_touch_down: bool) -> TickInput {
    TickInput { dt, touch_x, touch_y, is_touch_down }
    }
    }

    fn generate_particles(count: usize, width: usize, height: usize) -> Vec<Particle> {
    let mut rng = rand::thread_rng();
    let mut particles = Vec::with_capacity(count);

    for _ in 0..count {
    let x = rng.gen_range(0.0..width as f32);
    let y = rng.gen_range(0.0..height as f32);
    particles.push(Particle {
    x,
    y,
    // sx: x,
    // sy: y,
    dx: rng.gen_range(-1.0..1.0),
    dy: rng.gen_range(-1.0..1.0),
    });
    }

    particles
    }

    #[wasm_bindgen]
    pub struct InitInput {
    pub width: usize,
    pub height: usize,
    pub particle_count: usize,
    pub gravity: f32,
    }

    #[wasm_bindgen]
    impl InitInput {
    #[wasm_bindgen(constructor)]
    pub fn new(width: usize, height: usize, particle_count: usize, gravity: f32) -> InitInput {
    InitInput { width, height, particle_count, gravity }
    }
    }

    #[wasm_bindgen]
    pub fn init(input: InitInput) {
    unsafe {
    WIDTH = input.width;
    HEIGHT = input.height;
    if PARTICLES.len() != input.particle_count {
    PARTICLES = generate_particles(input.particle_count, WIDTH, HEIGHT);
    }
    BUFFER = vec![0; WIDTH * HEIGHT];
    }
    }

    #[wasm_bindgen]
    pub fn tick(input: TickInput) -> *const u32 {
    let friction_per_second = 0.985_f32.powf(60.0);
    let dt = input.dt;
    let touch_x = input.touch_x;
    let touch_y = input.touch_y;
    let is_touch_down = input.is_touch_down;
    unsafe {
    for particle in PARTICLES.iter_mut() {
    if is_touch_down {
    let dx = touch_x - particle.x;
    let dy = touch_y - particle.y;
    let distance = (dx * dx + dy * dy).sqrt();

    if distance > 0.2 {
    let inv_gravity = GRAVITY_STRENGTH / distance;
    particle.dx += dx * inv_gravity * dt;
    particle.dy += dy * inv_gravity * dt;
    }
    }
    // let dx = particle.sx - particle.x;
    // let dy = particle.sy - particle.y;
    // let distance = (dx * dx + dy * dy).sqrt();
    // if distance > 4.0 {
    // let mut inv_gravity = 200.0 / distance;
    // if distance > 16.0 {
    // inv_gravity *= 2.5;
    // }
    // particle.dx += dx * inv_gravity * dt;
    // particle.dy += dy * inv_gravity * dt;
    // }
    // if distance > 30.0 {
    // let inv_gravity = 2_000.0 / distance;
    // particle.dx += dx * inv_gravity * dt;
    // particle.dy += dy * inv_gravity * dt;
    // }

    let friction = friction_per_second.powf(dt);
    particle.dx *= friction;
    particle.dy *= friction;
    particle.x += particle.dx * dt;
    particle.y += particle.dy * dt;
    }

    BUFFER.iter_mut().for_each(|pixel| *pixel = 0);
    for particle in &PARTICLES {
    let x = particle.x as usize;
    let y = particle.y as usize;
    let idx = y.max(0).min(HEIGHT-1) * WIDTH + x.max(0).min(WIDTH-1);

    let old_c = BUFFER[idx];
    let r = (old_c >> 24) & 0xFF;
    let g = (old_c >> 16) & 0xFF;
    let b = (old_c >> 8) & 0xFF;
    let red = r + (particle.y / HEIGHT as f32 * 255.0 * 0.5) as u32;
    let green = g + (particle.x / WIDTH as f32 * 255.0 * 0.5) as u32;
    let blue = b + (255.0 * 0.8) as u32;
    let color = (red.min(255) << 24) | (green.min(255) << 16) | (blue.min(255) << 8) | 255;
    BUFFER[idx] = color;
    }

    BUFFER.as_ptr()
    }
    }
    263 changes: 263 additions & 0 deletions wasm_slime
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,263 @@
    #![no_main]
    use wasm_bindgen::prelude::*;
    use rand::Rng;
    use std::f32::consts::PI;

    const GRAVITY_STRENGTH: f32 = 105.0;

    #[repr(align(16))]
    struct Particle {
    x: f32,
    y: f32,
    angle: f32,
    speed: f32,
    _type: u8,
    }

    #[wasm_bindgen]
    pub struct ParticleSimulator {
    width: usize,
    height: usize,
    particles: Vec<Particle>,
    frame_buff: Vec<u32>,
    trail_buff: Vec<f32>,
    rng: rand::rngs::ThreadRng,
    options: SimulationOptions,
    }

    #[wasm_bindgen]
    pub struct SimulationOptions {
    particle_count: usize,
    blur_size: isize,
    deposition: f32,
    sense_dist: f32,
    sense_angle: f32,
    rotate_angle_dist: f32,
    rotation_speed: f32,
    sensor_size: i32,
    diffusion_rate: f32,
    }

    #[wasm_bindgen]
    impl ParticleSimulator {
    #[wasm_bindgen(constructor)]
    pub fn new() -> Self {
    utils::set_panic_hook();
    let options = SimulationOptions {
    particle_count: 80_000,
    blur_size: 1,
    deposition: 3.0,
    sense_dist: 20.0,
    sense_angle: PI / 180.0 * 15.0,
    rotate_angle_dist: PI / 180.0 * 12.0,
    rotation_speed: 4.0,
    sensor_size: 1,
    diffusion_rate: 24.0,
    };

    let rng = rand::thread_rng();
    ParticleSimulator {
    width: 0,
    height: 0,
    particles: Vec::with_capacity(options.particle_count),
    frame_buff: vec![0; 1],
    trail_buff: vec![0.0; 1],
    rng,
    options,
    }
    }

    #[no_mangle]
    pub fn init(&mut self, width: usize, height: usize, particle_count: usize, blur_size: isize, diffusion_rate: f32, deposition: f32 , sense_size: i32, speed: f32) {
    self.width = width;
    self.height = height;
    self.options.particle_count = particle_count;
    self.options.blur_size = blur_size;
    self.options.sensor_size = sense_size;
    self.options.diffusion_rate = diffusion_rate;
    self.options.deposition = deposition;
    self.particles.clear();
    self.particles.reserve(particle_count);
    self.frame_buff = vec![0; width * height];
    self.trail_buff = vec![0.0; width * height];

    let spawn_width = self.width/4;
    let spawn_height = self.height/4;

    for _ in 0..particle_count {
    self.particles.push(Particle {
    x: self.rng.gen_range((self.width / 2 - spawn_width / 2) as f32..(self.width/2 + spawn_width/2) as f32),
    y: self.rng.gen_range((self.height / 2 - spawn_height / 2) as f32..(self.height/2 + spawn_height/2) as f32),
    angle: self.rng.gen_range(0.0..360.0) * PI / 180.0,
    speed: speed + self.rng.gen_range(0.0..10.0),
    _type: self.rng.gen_range(0..80),
    });
    }
    }

    #[no_mangle]
    pub fn simulate(&mut self, mouse_x: f32, mouse_y: f32, mouse_pressed: bool, delta_time: f64) -> *const u32 {
    let dt = delta_time as f32;
    for p in self.particles.iter_mut() {
    let mut nx = p.x + p.speed * p.angle.cos() * dt;
    let mut ny = p.y + p.speed * p.angle.sin() * dt;

    if mouse_pressed {
    let dx = mouse_x - nx;
    let dy = mouse_y - ny;
    let dist = (dx*dx + dy*dy).sqrt();
    if dist < 80.0 {
    nx -= dx * GRAVITY_STRENGTH/dist * dt * 1.5;
    ny -= dy * GRAVITY_STRENGTH/dist * dt * 1.5;
    p.angle *= -1.0;
    }
    nx += dx * GRAVITY_STRENGTH/dist * dt * 0.25;
    ny += dy * GRAVITY_STRENGTH/dist * dt * 0.25;
    }

    if nx <= 0.0 || nx >= self.width as f32 || ny <= 0.0 || ny >= self.height as f32 {
    p.angle = self.rng.gen_range(-1.0..1.0) * 2.0 * PI;
    continue;
    }

    p.x = nx;
    p.y = ny;

    if p._type <= 3 {
    self.trail_buff[p.y as usize * self.width + p.x as usize] -= self.options.deposition * dt;
    } else if p._type == 4 {
    self.trail_buff[p.y as usize * self.width + p.x as usize] -= self.options.deposition * dt * 1.30;
    } else {
    self.trail_buff[p.y as usize * self.width + p.x as usize] += self.options.deposition * dt;
    }

    let front = sense(p, &self.trail_buff, 0.0, self.options.sense_dist, self.options.sensor_size, self.width, self.height);
    let left = sense(p, &self.trail_buff, -self.options.sense_angle, self.options.sense_dist, self.options.sensor_size, self.width, self.height);
    let right = sense(p, &self.trail_buff, self.options.sense_angle, self.options.sense_dist, self.options.sensor_size, self.width, self.height);
    // let front: f32 = 0.0;
    // let left: f32 = 0.0;
    // let right: f32 = 0.0;

    if front > left && front > right {
    continue;
    }
    if front < left && front < right {
    if self.rng.gen_range(0.0..1.0) > 0.5 {
    p.angle += self.options.rotate_angle_dist * self.options.rotation_speed * dt;
    } else {
    p.angle -= self.options.rotate_angle_dist * self.options.rotation_speed * dt;
    }
    continue;
    }

    if left < right {
    p.angle += self.options.rotate_angle_dist * self.options.rotation_speed * dt;
    } else {
    p.angle -= self.options.rotate_angle_dist * self.options.rotation_speed * dt;
    }
    }

    self.diffuse(self.options.diffusion_rate, dt, self.options.blur_size);

    self.frame_buff.iter_mut().for_each(|pixel| *pixel = 0);
    for y in 0..self.height {
    for x in 0..self.width {
    let index = y * self.width + x;
    let trail_intensity = self.trail_buff[index];
    let intensity_byte = (trail_intensity * 255.0).min(255.0).max(0.0) as f32;
    let sx = x as f32 / self.width as f32;
    let sy = y as f32 / self.height as f32;
    let r = (intensity_byte*sx) as u32;
    let g = (intensity_byte*sy) as u32;
    let b = (intensity_byte*(1.0-sx)) as u32;
    let packed_pixel = (r << 24) | (g << 16) | (b << 8 ) | 255;
    self.frame_buff[index] = packed_pixel;
    }
    }

    for particle in self.particles.iter() {
    let x = particle.x as usize;
    let y = particle.y as usize;

    if x < self.width && y < self.height && x > 0 && y > 0 {
    let sx = x as f32 / self.width as f32;
    let sy = y as f32 / self.height as f32;
    let red = (sx * 255.0 * 0.15) as u8;
    let green = (sy * 255.0 * 0.15) as u8;
    let blue = (255.0 * (1.0-sx) * 0.15) as u8;
    let current_color = self.frame_buff[y * self.width + x];
    let current_red = ((current_color >> 24) & 0xFFFFFF) as u8;
    let current_green = ((current_color >> 16) & 0xFFFFFF) as u8;
    let current_blue = ((current_color >> 8) & 0xFFFFFF) as u8;
    let blended_red = current_red.saturating_add(red);
    let blended_green = current_green.saturating_add(green);
    let blended_blue = current_blue.saturating_add(blue);
    let blended_color: u32 = ((blended_red as u32) << 24) | ((blended_green as u32) << 16) | ((blended_blue as u32) << 8 ) | 255;
    self.frame_buff[y * self.width + x] = blended_color;
    }
    }

    self.frame_buff.as_ptr()
    }

    fn diffuse(&mut self, diffusion_rate: f32, dt: f32, blur_size: isize) {
    let mut new_map = self.trail_buff.clone();
    let df: f32 = dt * diffusion_rate;
    for y in 0..self.height {
    for x in 0..self.width {
    let index = y * self.width + x;
    let current_value = self.trail_buff[index];
    let mut total_contribution = current_value;
    let mut count = 1.0;
    for dy in -blur_size..=blur_size {
    for dx in -blur_size..=blur_size {
    if dx == 0 && dy == 0 {
    continue;
    }

    let nx = x as isize + dx;
    let ny = y as isize + dy;
    if nx >= 0 && nx < self.width as isize && ny >= 0 && ny < self.height as isize {
    let neighbor_index = ny as usize * self.width + nx as usize;
    total_contribution += self.trail_buff[neighbor_index];
    count += 1.0;
    }
    }
    }
    let new_value = (current_value + (df * (total_contribution / count))) * (1.0 - df);
    new_map[index] = new_value;
    }
    }
    for (i, val) in new_map.iter().enumerate() {
    self.trail_buff[i] = *val;
    }
    }
    }

    mod utils {
    pub fn set_panic_hook() {
    // https://github.com/rustwasm/console_error_panic_hook#readme
    #[cfg(feature = "console_error_panic_hook")]
    {
    console_error_panic_hook::set_once();
    }
    }
    }

    fn sense(p: &Particle, trail_buff: &Vec<f32>, angle_offset: f32, sense_dist: f32, sense_size: i32, width: usize, height: usize,) -> f32 {
    let sensor_angle = p.angle + angle_offset;
    let sense_x = p.x + sense_dist * sensor_angle.cos();
    let sense_y = p.y + sense_dist * sensor_angle.sin();

    let mut sum: f32 = 0.0;
    for offset_x in -sense_size..sense_size {
    for offset_y in -sense_size..sense_size {
    let sample_x = (sense_x as i32 + offset_x).clamp(0, width as i32 - 1);
    let sample_y = (sense_y as i32 + offset_y).clamp(0, height as i32 - 1);

    sum += trail_buff[sample_y as usize * width + sample_x as usize];
    }
    }

    sum
    }
    110 changes: 110 additions & 0 deletions wasm_slime_html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,110 @@
    <!DOCTYPE html>
    <html lang="en" style="user-select: none">
    <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Sabby Rust</title>
    <link rel="icon" href="./favicon.ico" type="image/x-icon" />
    <style>
    body,
    html,
    div,
    canvas {
    -webkit-user-select: none;
    -moz-user-select: none;
    -webkit-touch-callout: none;
    -ms-user-select: none;
    user-select: none;
    outline: none;
    }
    </style>
    </head>
    <body
    style="
    margin: 0;
    width: 100vw;
    height: 100vh;
    background: black;
    -webkit-touch-callout: none;
    overflow: hidden;
    "
    >
    <div>
    <canvas id="canvas"></canvas>
    </div>
    <script type="module">
    import init, { ParticleSimulator } from './how_fast_is_rust.js';

    async function runSimulation() {
    const instance = await init();

    const canvas = document.getElementById('canvas');
    canvas.style.touchAction = 'none';
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    const ctx = canvas.getContext('2d');
    let imageData = ctx.createImageData(canvas.width, canvas.height);
    let frameBuffer = new Uint32Array(imageData.data.buffer);

    const simulator = new ParticleSimulator();
    function resize() {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    imageData = ctx.createImageData(canvas.width, canvas.height);
    frameBuffer = new Uint32Array(imageData.data.buffer);
    simulator.init(canvas.width, canvas.height, 50000, 0, 14, 5.5, 1, 80);
    }
    resize();

    let mousePressed = false;
    let mouseX = canvas.width / 2;
    let mouseY = canvas.height / 2;

    window.addEventListener('resize', resize);
    window.addEventListener('mousedown', () => mousePressed = true);
    window.addEventListener('mouseup', () => mousePressed = false);
    window.addEventListener('mousemove', (event) => {
    mouseX = event.offsetX;
    mouseY = event.offsetY;
    });
    window.addEventListener('touchend', (event) => {
    mousePressed = false;
    });

    window.addEventListener('touchmove', (event) => {
    mousePressed = true;
    mouseX = event.touches[0].clientX;
    mouseY = event.touches[0].clientY;
    });
    window.addEventListener('touchstart', (event) => {
    event.preventDefault();
    mousePressed = true;
    mouseX = event.touches[0].clientX;
    mouseY = event.touches[0].clientY;
    });


    let prev = 0;
    function render(timestamp = 0) {
    const dt = (timestamp - prev)/1000;
    prev = timestamp;
    const frame_buff_ptr = simulator.simulate(mouseX, mouseY, mousePressed, 0.016);
    const buff = new Uint8Array(instance.memory.buffer, frame_buff_ptr, canvas.width * canvas.height*4);
    for(let i = 0; i < frameBuffer.length;i++) {
    const r = buff[i*4];
    const g = buff[i*4+1];
    const b = buff[i*4+2];
    const a = buff[i*4+3];
    frameBuffer[i] = (r << 24) | (g << 16) | (b << 8) | a;
    }
    ctx.putImageData(imageData, 0, 0);
    // ctx.scale(2,2);
    ctx.drawImage(canvas, 0, 0);
    requestAnimationFrame(render);
    }
    render();
    }
    runSimulation().catch(e => console.error("Could not run simulation:", e));
    </script>
    </body>
    </html>