diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b48299b..7104b29 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,23 @@ jobs: toolchain: stable - run: make + generate-wasm: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + targets: wasm32-unknown-unknown + - run: make generate-wasm + - run: test -f site/generated/wgpu_game_of_life_bg.wasm + - uses: Homebrew/actions/setup-homebrew@master + - name: Install wasm-opt + run: brew update && brew install binaryen + - run: rm site/generated/wgpu_game_of_life_bg.wasm + - run: make WASM_RELEASE=1 generate-wasm + - run: test -f site/generated/wgpu_game_of_life_bg.wasm + deploy-site: if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest diff --git a/Makefile b/Makefile index cd9d715..54fd543 100644 --- a/Makefile +++ b/Makefile @@ -33,13 +33,16 @@ CLIPPY_PARAMS = --all-targets -- \ -W clippy::unreadable-literal \ -W clippy::unseparated-literal-suffix \ -W clippy::unnested_or_patterns \ - -A clippy::wildcard_dependencies + -A clippy::wildcard_dependencies \ + -D warnings CARGO_COMMAND = cargo check: $(CARGO_COMMAND) fmt --all - $(CARGO_COMMAND) clippy --tests $(CLIPPY_PARAMS) + $(CARGO_COMMAND) clippy $(CLIPPY_PARAMS) + RUSTFLAGS="--cfg=web_sys_unstable_apis" \ + $(CARGO_COMMAND) clippy --target wasm32-unknown-unknown $(CLIPPY_PARAMS) macos-app: cargo install cargo-bundle @@ -68,5 +71,4 @@ generate-wasm: serve-site: --run-devserver --watch-and-build-wasm ; - .PHONY: check macos-app run-app generate-wasm serve-wasm diff --git a/site/index.html b/site/index.html index de796b0..93e63b6 100644 --- a/site/index.html +++ b/site/index.html @@ -20,7 +20,7 @@ } body { - overscroll-behavior-y: none; + overscroll-behavior-y: none; } canvas { @@ -69,7 +69,7 @@

wgpu Game of life

href="https://developer.mozilla.org/en-US/docs/Web/API/WebGPU_API">WebGPU.

See source code.

-
+ - + \ No newline at end of file diff --git a/site/index.js b/site/index.js index 5f011eb..5dc0ed5 100644 --- a/site/index.js +++ b/site/index.js @@ -3,6 +3,7 @@ import init, { run, getRules, setNewRule, resetGame } from "./generated/wgpu_gam const ruleSelect = document.getElementById('rule'); const canvas = document.getElementById("webgpu-canvas"); const sizeElement = document.getElementById("size"); +const overlayElement = document.getElementById("overlay"); canvas.focus(); @@ -10,6 +11,7 @@ globalThis.setNewState = function (ruleIdx, size, seed) { sizeElement.textContent = size + 'x' + size; ruleSelect.value = ruleIdx; window.location.hash = `rule=${ruleIdx}&size=${size}&seed=${seed}`; + overlayElement.style.display = 'block'; } globalThis.toggleFullscreen = function () { @@ -44,6 +46,6 @@ try { } catch (e) { console.error('error', e); canvas.remove(); - document.getElementById('overlay').remove(); + overlayElement.remove(); document.getElementById('fallback').style.display = 'flex'; } diff --git a/src/lib.rs b/src/lib.rs index 448b1dc..6339a1a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ mod event_loop; +mod rules; #[cfg(target_arch = "wasm32")] use std::sync::Mutex; @@ -17,14 +18,6 @@ use winit::{ window::{Window, WindowBuilder}, }; -#[derive(Clone, Copy)] -#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] -struct Rule { - pub born: u16, - pub survives: u16, - name: &'static str, -} - #[cfg(target_arch = "wasm32")] #[derive(Debug, Clone, Copy)] pub enum CustomWinitEvent { @@ -42,88 +35,6 @@ thread_local! { pub static EVENT_LOOP_PROXY: Mutex>> = Mutex::new(None); } -#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] -impl Rule { - fn rule_array(&self) -> [u32; 2] { - [u32::from(self.born), u32::from(self.survives)] - } - - #[cfg(target_arch = "wasm32")] - pub fn name(&self) -> String { - let mut born = String::from("B"); - let mut survives = String::from("S"); - for i in 0..9 { - if self.born & (1 << i) != 0 { - born.push_str(&format!("{i}")); - } - if self.survives & (1 << i) != 0 { - survives.push_str(&format!("{i}")); - } - } - format!("{} {}/{}", self.name, born, survives) - } -} - -static RULES: [Rule; 9] = [ - Rule { - born: 0b1000, - survives: 0b1100, - name: "Conway's Life", - }, - Rule { - born: 0b0_0000_1000, - survives: 0b0_0011_1110, - name: "Maze", - }, - Rule { - born: 0b0_0000_1000, - survives: 0b0_0001_1110, - name: "Mazectric", - }, - Rule { - born: 0b1, - survives: 0b1, - name: "Gnarl", - }, - Rule { - born: 0b1_1100_1000, - survives: 0b1_1101_1000, - name: "Day & Night", - }, - Rule { - born: 0b0_0010_1000, - survives: 0b1_1011_1100, - name: "Land Rush", - }, - Rule { - born: 0b0_0100_1000, - survives: 0b1_1011_1100, - name: "Land Rush 2", - }, - Rule { - born: 0b1_1100_1000, - survives: 0b1_1110_1100, - name: "Stains", - }, - Rule { - born: 0b1_1110_0000, - survives: 0b1_1111_0000, - name: "Vote", - }, -]; - -#[cfg(target_arch = "wasm32")] -fn setup_html_canvas() -> web_sys::HtmlCanvasElement { - use web_sys::HtmlCanvasElement; - web_sys::window() - .and_then(|win| win.document()) - .and_then(|doc| { - let canvas = doc.get_element_by_id("webgpu-canvas")?; - canvas.dyn_into::().ok() - }) - .expect("Could not get canvas") -} - #[cfg(not(target_arch = "wasm32"))] pub async fn run() { env_logger::init(); @@ -175,23 +86,15 @@ pub fn reset_game() { }); } -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen(js_name = getRules)] -pub fn get_rules() -> wasm_bindgen::prelude::JsValue { - wasm_bindgen::prelude::JsValue::from( - RULES - .iter() - .copied() - .map(wasm_bindgen::prelude::JsValue::from) - .collect::(), - ) -} - #[cfg(target_arch = "wasm32")] #[wasm_bindgen] pub async fn run(rule_idx: Option, seed: Option) -> Result<(), String> { + use winit::event_loop::EventLoopBuilder; + use winit::platform::web::{EventLoopExtWebSys, WindowBuilderExtWebSys}; + std::panic::set_hook(Box::new(console_error_panic_hook::hook)); - console_log::init_with_level(log::Level::Info).expect("Couldn't initialize logger"); + console_log::init_with_level(log::Level::Info) + .map_err(|e| format!("Couldn't initialize logger: {e}"))?; let event_loop = EventLoopBuilder::::with_user_event().build(); @@ -202,10 +105,16 @@ pub async fn run(rule_idx: Option, seed: Option) -> Result<(), String> } }); - use winit::event_loop::EventLoopBuilder; - use winit::platform::web::WindowBuilderExtWebSys; + let canvas_element = web_sys::window() + .and_then(|win| win.document()) + .and_then(|doc| { + let canvas = doc.get_element_by_id("webgpu-canvas")?; + canvas.dyn_into::().ok() + }) + .ok_or("Could not get canvas element")?; + let window = WindowBuilder::new() - .with_canvas(Some(setup_html_canvas())) + .with_canvas(Some(canvas_element)) .with_prevent_default(false) .build(&event_loop) .unwrap(); @@ -216,7 +125,6 @@ pub async fn run(rule_idx: Option, seed: Option) -> Result<(), String> state.inform_js_about_state(); - use winit::platform::web::EventLoopExtWebSys; event_loop.spawn(move |event, _, control_flow| { event_loop::handle_event_loop(&event, &mut state, control_flow); }); @@ -750,10 +658,10 @@ impl State { }); let rule_idx = match rule_idx { - Some(idx) if idx < RULES.len() as u32 => idx, + Some(idx) if idx < rules::RULES.len() as u32 => idx, _ => 0, }; - let rule = &RULES[rule_idx as usize]; + let rule = &rules::RULES[rule_idx as usize]; let rule_array = rule.rule_array(); let rule_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("rule_buffer"), @@ -829,7 +737,7 @@ impl State { pub fn set_rule_idx(&mut self, new_rule_idx: u32) { self.rule_idx = new_rule_idx; - let rule = &RULES[self.rule_idx as usize]; + let rule = &rules::RULES[self.rule_idx as usize]; self.queue.write_buffer( &self.rule_buffer, 0, @@ -840,9 +748,9 @@ impl State { fn change_rule(&mut self, next: bool) { let new_rule_idx = if next { - (self.rule_idx + 1) % (RULES.len() as u32) + (self.rule_idx + 1) % (rules::RULES.len() as u32) } else if self.rule_idx == 0 { - RULES.len() as u32 - 1 + rules::RULES.len() as u32 - 1 } else { self.rule_idx - 1 }; @@ -868,7 +776,10 @@ impl State { fn on_state_change(&mut self) { self.window.set_title(&format!( "{} {}x{} {}", - RULES[self.rule_idx as usize].name, self.cells_width, self.cells_height, self.seed + rules::RULES[self.rule_idx as usize].name(), + self.cells_width, + self.cells_height, + self.seed )); #[cfg(target_arch = "wasm32")] diff --git a/src/rules.rs b/src/rules.rs new file mode 100644 index 0000000..f477690 --- /dev/null +++ b/src/rules.rs @@ -0,0 +1,91 @@ +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + +#[derive(Clone, Copy)] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +pub struct Rule { + pub born: u16, + pub survives: u16, + name: &'static str, +} + +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +impl Rule { + pub(crate) fn rule_array(&self) -> [u32; 2] { + [u32::from(self.born), u32::from(self.survives)] + } + + pub fn name(&self) -> String { + let mut born = String::from("B"); + let mut survives = String::from("S"); + for i in 0..9 { + if self.born & (1 << i) != 0 { + born.push_str(&format!("{i}")); + } + if self.survives & (1 << i) != 0 { + survives.push_str(&format!("{i}")); + } + } + format!("{} {}/{}", self.name, born, survives) + } +} + +pub static RULES: [Rule; 9] = [ + Rule { + born: 0b1000, + survives: 0b1100, + name: "Conway's Life", + }, + Rule { + born: 0b0_0000_1000, + survives: 0b0_0011_1110, + name: "Maze", + }, + Rule { + born: 0b0_0000_1000, + survives: 0b0_0001_1110, + name: "Mazectric", + }, + Rule { + born: 0b1, + survives: 0b1, + name: "Gnarl", + }, + Rule { + born: 0b1_1100_1000, + survives: 0b1_1101_1000, + name: "Day & Night", + }, + Rule { + born: 0b0_0010_1000, + survives: 0b1_1011_1100, + name: "Land Rush", + }, + Rule { + born: 0b0_0100_1000, + survives: 0b1_1011_1100, + name: "Land Rush 2", + }, + Rule { + born: 0b1_1100_1000, + survives: 0b1_1110_1100, + name: "Stains", + }, + Rule { + born: 0b1_1110_0000, + survives: 0b1_1111_0000, + name: "Vote", + }, +]; + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen(js_name = getRules)] +pub fn get_rules() -> wasm_bindgen::prelude::JsValue { + wasm_bindgen::prelude::JsValue::from( + RULES + .iter() + .copied() + .map(wasm_bindgen::prelude::JsValue::from) + .collect::(), + ) +}