use std::rc::Rc; use pixels::wgpu::Color; use pixels::PixelsBuilder; use pixels::SurfaceTexture; use rand::distributions::Distribution; use rand::distributions::Standard; use wasm_bindgen::prelude::*; use winit::dpi::LogicalSize; use winit::event::Event; use winit::event_loop::EventLoop; use winit::window::Window; use winit_input_helper::WinitInputHelper; #[wasm_bindgen(start)] async fn start() -> Result<(), JsValue> { return run().await.map_err(|err| JsValue::from(err.to_string())); } async fn run() -> anyhow::Result<()> { let event_loop = EventLoop::new(); let window = Rc::new(Window::new(&event_loop)?); #[cfg(target_arch = "wasm32")] { use winit::platform::web::WindowExtWebSys; // Retrieve current width and height dimensions of browser client window let get_window_size = || { let client_window = web_sys::window().unwrap(); LogicalSize::new( client_window.inner_width().unwrap().as_f64().unwrap(), client_window.inner_height().unwrap().as_f64().unwrap(), ) }; let window = Rc::clone(&window); // Initialize winit window with current dimensions of browser client window.set_inner_size(get_window_size()); let client_window = web_sys::window().unwrap(); // Attach winit canvas to body element web_sys::window() .and_then(|win| win.document()) .and_then(|doc| doc.body()) .and_then(|body| { body.append_child(&web_sys::Element::from(window.canvas())) .ok() }) .expect("couldn't append canvas to document body"); // Listen for resize event on browser client. Adjust winit window dimensions // on event trigger let closure = wasm_bindgen::closure::Closure::wrap(Box::new(move |_e: web_sys::Event| { let size = get_window_size(); window.set_inner_size(size) }) as Box); client_window .add_event_listener_with_callback("resize", closure.as_ref().unchecked_ref()) .unwrap(); closure.forget(); } let mut input = WinitInputHelper::new(); let window_size = window.inner_size(); let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, window.as_ref()); let width = window_size.width / 8; let height = window_size.height / 8; web_sys::console::log_1(&format!("{} x {}", width, height).into()); let mut pixels = PixelsBuilder::new(width, height, surface_texture) .clear_color(Color { r: 0.0, g: 0.0, b: 0.0, a: 1.0, }) .build_async() .await?; let mut life = Life::new(width as usize, height as usize); event_loop.run(move |event, _, control_flow| { control_flow.set_poll(); if let Event::RedrawRequested(_) = event { life.draw(pixels.frame_mut()); pixels.render().expect("failed to render"); } if input.update(&event) { if input.close_requested() || input.destroyed() { control_flow.set_exit(); return; }; life.update(); window.request_redraw(); }; }); } #[derive(Clone, Copy, Debug)] enum Cell { Alive, Dead { since: u8 }, } #[derive(Debug)] struct Grid { pub width: usize, pub height: usize, pub cells: Vec>, } enum FrontGrid { A, B, } struct Life { pub width: usize, pub height: usize, grid_a: Grid, grid_b: Grid, front_grid: FrontGrid, } impl Cell { pub fn is_alive(&self) -> bool { match self { Cell::Alive => true, Cell::Dead { .. } => false, } } } impl Grid { pub fn blank(width: usize, height: usize) -> Self { Self { width, height, cells: vec![vec![Cell::Dead { since: 0xff }; width]; height], } } pub fn with_random_borders(width: usize, height: usize) -> Self { let mut ret = Self::blank(width, height); ret.randomize_border(); ret } pub fn randomize_border(&mut self) { for col in 0..self.width { self.cells[self.height - 1][col] = rand::random(); } } pub fn draw(&self, frame: &mut [u8]) { for row in 0..self.height { for col in 0..self.width { let pixel_idx = 4 * (self.width * row + col); match self.cells[row][col] { Cell::Alive => { frame[pixel_idx + 0] = 0xff; // R frame[pixel_idx + 1] = 0xff; // G frame[pixel_idx + 2] = 0xff; // B frame[pixel_idx + 3] = 0xff; // A } Cell::Dead { since } => { frame[pixel_idx + 0] = 0xff - since.saturating_mul(16); // R frame[pixel_idx + 1] = if since < 0x8 { 0 } else { (since - 0x8).saturating_mul(0x8) / 2 }; // G frame[pixel_idx + 2] = 0xff; // B frame[pixel_idx + 3] = 0xff - since; // A } }; } } } pub fn num_alive_neighbors(&self, row: usize, col: usize) -> u8 { let mut ret = 0; for i in row.saturating_sub(1)..=(row + 1).min(self.height - 1) { for j in col.saturating_sub(1)..=(col + 1).min(self.width - 1) { if self.cells[i][j].is_alive() { ret += 1; } } } if self.cells[row][col].is_alive() { ret -= 1; } ret } pub fn new_state(&self, row: usize, col: usize) -> Cell { match self.cells[row][col] { Cell::Alive => { if (2..=3).contains(&self.num_alive_neighbors(row, col)) { Cell::Alive } else { Cell::Dead { since: 0 } } } Cell::Dead { since } => { if self.num_alive_neighbors(row, col) == 3 { Cell::Alive } else { Cell::Dead { since: since.saturating_add(1), } } } } } } impl Life { pub fn new(width: usize, height: usize) -> Self { Life { width, height, grid_a: Grid::with_random_borders(width, height), grid_b: Grid::blank(width, height), front_grid: FrontGrid::A, } } pub fn draw(&self, frame: &mut [u8]) { self.front_grid().draw(frame); } pub fn update(&mut self) { let (width, height) = self.dimensions(); let (front, back) = self.grids(); for row in 0..height { for col in 0..width { back.cells[row][col] = front.new_state(row, col); } } back.randomize_border(); self.swap_grids(); } fn dimensions(&self) -> (usize, usize) { (self.width, self.height) } fn grids(&mut self) -> (&Grid, &mut Grid) { match self.front_grid { FrontGrid::A => (&self.grid_a, &mut self.grid_b), FrontGrid::B => (&self.grid_b, &mut self.grid_a), } } fn front_grid(&self) -> &Grid { match self.front_grid { FrontGrid::A => &self.grid_a, FrontGrid::B => &self.grid_b, } } fn swap_grids(&mut self) { self.front_grid = match self.front_grid { FrontGrid::A => FrontGrid::B, FrontGrid::B => FrontGrid::A, } } } impl Distribution for Standard { fn sample(&self, rng: &mut R) -> Cell { if rng.gen() { Cell::Alive } else { Cell::Dead { since: 0xff } } } }