Created
November 8, 2024 20:52
-
-
Save dgerrells/cc8557b392df44c9888fcc70a010de46 to your computer and use it in GitHub Desktop.
how fast is rust
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| [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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| [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"] | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #/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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| pub mod thread_pool; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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), | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #![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); | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #![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() | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #![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 | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!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> |
Author
Hi back,
Optimizations are hard. I did try fast inverse and it was slower, This is
because v8 will use a highly optimized version under the hood where as the
inverse function will not run natively.
Number 2 is interesting but maybe not in the way you think.
The crux here is the moment we bin the particles no matter bin size we hit
the same fundamental issue which is that the particle data itself is not
stored in order of pixel data traversal. Using your idea it would be "bin
ordered".
The key thing we want is for particle data to be iterated in the same order
as pixel data. One "pxiel" location can have N particles located at that
point so bin size would be massive. Sorting solves this but that was much
slower when I tried it.
Most of my tests were done on my m1 mac which was rendering at very high
resolution. Lower resolution absolutely made everything run faster but less
than linearly.
I am sure there is a way to maintain a morton ordered slab of particle data
such that it would maintain order during physics traversal. This would
perfectly align with pixel resolution.You do not need to maintain order
within a pixel boundary only from pixel to pixel. That is the cheat.
Cheers.
…On Mon, Jun 15, 2026 at 11:20 AM Mike Lezhnin ***@***.***> wrote:
***@***.**** commented on this gist.
------------------------------
Hi there. I've read your blog-post for this gist, good stuff :)
I thought I'd comment on two things that came to my mind while looking at
your optimization struggles.
1.
you could ever so slightly improve the speed for the main processing
code - instead of taking sqrt of the distance, use it in its squared form,
then later when you want to compute something / distance, use fast
inverse square root.
2.
I imagine (and you mention that as well), you are losing some
performance due to cache misses when placing your particles into the buffer
on their positions. I have a solution, of sorts. In short - first put the
particles into bins based on where on the image they should land, then go
bin by bin and put points from each bin into the buffer.
Say your canvas is 800x400 or so, and for each pixel we use one u8. That
is 320 KB. Say our L1 cache is about 30KB.
I'd say split it into 10-20 buckets, I dunno. Now we go over the points,
apply physics, and immediately store x and y of the particle as u16 in one
of running buffers.
By the end of this we have a number of buffers with point coordinates,
such that each buffer lands nicely in a tiny stripe of the image buffer, so
it hopefully would reduce cache misses by a lot. Overall, that would be
about 25% memory overhead, which might be very much worth it.
The non-trivial part here is handling the buffers - one probably needs a
system that allocates new buffers as you fill them up... or just use
vectors and enjoy amortized "constant" insertion times.
—
Reply to this email directly, view it on GitHub
<https://gist.github.com/dgerrells/cc8557b392df44c9888fcc70a010de46#gistcomment-6200924>
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABILGJXORS2WWU5MSBRNDQ35AA5ADBFKMF2HI4TJMJ2XIZLTSOBKK5TBNR2WLJZYHE4TQMZRG6SG4YLNMWUGCY3UN5ZF62LEQKSXMYLMOVS2I5DSOVS2I3TBNVS3W5DIOJSWCZC7OBQXE5DJMNUXAYLOORPWCY3UNF3GS5DZQKSXMYLMOVS2IZ3JON2KI3TBNVS2W5DIOJSWCZC7OR4XAZNMON2WE2TFMN2F65DZOBS2WR3JON2EG33NNVSW45FGORXXA2LDOOIYFJDUPFYGLJDHNFZXJJLWMFWHKZNJGEZTGOJYGAZDKNVHORZGSZ3HMVZKMY3SMVQXIZI>
.
You are receiving this email because you authored the thread.
Triage notifications on the go with GitHub Mobile for iOS
<https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
or Android
<https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>
.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi there. I've read your blog-post for this gist, good stuff :)
I thought I'd comment on two things that came to my mind while looking at your optimization struggles.
you could ever so slightly improve the speed for the main processing code - instead of taking sqrt of the distance, use it in its squared form, then later when you want to compute
something / distance, use fast inverse square root.I imagine (and you mention that as well), you are losing some performance due to cache misses when placing your particles into the buffer on their positions. I have a solution, of sorts. In short - first put the particles into bins based on where on the image they should land, then go bin by bin and put points from each bin into the buffer.
Say your canvas is 800x400 or so, and for each pixel we use one u8. That is 320 KB. Say our L1 cache is about 30KB.
I'd say split it into 10-20 buckets, I dunno. Now we go over the points, apply physics, and immediately store x and y of the particle as u16 in one of running buffers.
By the end of this we have a number of buffers with point coordinates, such that each buffer lands nicely in a tiny stripe of the image buffer, so it hopefully would reduce cache misses by a lot. Overall, that would be about 25% memory overhead, which might be very much worth it.
The non-trivial part here is handling the buffers - one probably needs a system that allocates new buffers as you fill them up... or just use vectors and enjoy amortized "constant" insertion times.