From 141c94f48a092cae987f0af36372bd25dd42144a Mon Sep 17 00:00:00 2001 From: Ben Hansen Date: Sun, 24 Oct 2021 17:37:30 -0600 Subject: [PATCH] wasm now loading --- Cargo.lock | 1 + code/showcase/pong/Cargo.toml | 4 + code/showcase/pong/index.html | 22 +++ code/showcase/pong/src/lib.rs | 252 +++++++++++++++++++++++++++ code/showcase/pong/src/main.rs | 239 +------------------------ code/showcase/pong/src/render/mod.rs | 11 +- code/showcase/pong/src/system.rs | 26 ++- 7 files changed, 305 insertions(+), 250 deletions(-) create mode 100644 code/showcase/pong/index.html create mode 100644 code/showcase/pong/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index df6f09f2..1abad359 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1760,6 +1760,7 @@ dependencies = [ "fs_extra", "getrandom", "glob", + "instant", "log", "naga", "pollster", diff --git a/code/showcase/pong/Cargo.toml b/code/showcase/pong/Cargo.toml index b102f739..31958b7a 100644 --- a/code/showcase/pong/Cargo.toml +++ b/code/showcase/pong/Cargo.toml @@ -4,6 +4,9 @@ version = "0.1.0" authors = ["Ben Hansen "] edition = "2018" +[lib] +crate-type = ["cdylib", "rlib"] + [dependencies] winit = "0.25" anyhow = "1.0" @@ -15,6 +18,7 @@ wgpu_glyph = "0.15" rand = "0.8" rodio = { version = "0.14", default-features = false, features = ["wav"] } log = "0.4" +instant = "0.1" [target.'cfg(target_arch = "wasm32")'.dependencies] console_error_panic_hook = "0.1.6" diff --git a/code/showcase/pong/index.html b/code/showcase/pong/index.html new file mode 100644 index 00000000..cfea3687 --- /dev/null +++ b/code/showcase/pong/index.html @@ -0,0 +1,22 @@ + + + + + + + Pong with WASM + + + + + + \ No newline at end of file diff --git a/code/showcase/pong/src/lib.rs b/code/showcase/pong/src/lib.rs new file mode 100644 index 00000000..4a6de8ea --- /dev/null +++ b/code/showcase/pong/src/lib.rs @@ -0,0 +1,252 @@ +mod input; +mod render; +mod sound; +mod state; +mod system; +mod util; + +use input::Input; +use system::System; + +#[cfg(target_arch="wasm32")] +use wasm_bindgen::prelude::*; + +use winit::dpi::PhysicalSize; +use winit::event::*; +use winit::event_loop::{ControlFlow, EventLoop}; +use winit::window::{Fullscreen, WindowBuilder}; + +#[cfg_attr(target_arch="wasm32", wasm_bindgen(start))] +pub fn start() { + #[cfg(target_arch = "wasm32")] + { + console_log::init_with_level(log::Level::Info).expect("Could't initialize logger"); + std::panic::set_hook(Box::new(console_error_panic_hook::hook)); + } + + let event_loop = EventLoop::new(); + let monitor = event_loop.primary_monitor().unwrap(); + let video_mode = monitor.video_modes().next(); + let window = WindowBuilder::new() + .with_visible(false) + .with_title("Pong") + .with_fullscreen(video_mode.map(|vm| Fullscreen::Exclusive(vm))) + .build(&event_loop) + .unwrap(); + + if window.fullscreen().is_none() { + window.set_inner_size(PhysicalSize::new(512, 512)); + } + + window.set_cursor_visible(false); + + #[cfg(target_arch = "wasm32")] + { + use winit::platform::web::WindowExtWebSys; + 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 cavas to document body."); + } + + log::info!("Setup..."); + + let mut render = pollster::block_on(render::Render::new(&window, window.inner_size())); + let mut state = state::State { + ball: state::Ball { + position: (0.0, 0.0).into(), + velocity: (0.0, 0.0).into(), + radius: 0.05, + visible: true, + }, + player1: state::Player { + position: (-0.8, 0.0).into(), + size: (0.05, 0.4).into(), + score: 0, + visible: true, + }, + player2: state::Player { + position: (0.8, 0.0).into(), + size: (0.05, 0.4).into(), + score: 0, + visible: true, + }, + title_text: state::Text { + position: (20.0, 20.0).into(), + color: (1.0, 1.0, 1.0, 1.0).into(), + text: String::from("PONG"), + size: 64.0, + ..Default::default() + }, + play_button: state::Text { + position: (40.0, 100.0).into(), + color: (1.0, 1.0, 1.0, 1.0).into(), + text: String::from("Play"), + size: 32.0, + centered: false, + ..Default::default() + }, + quit_button: state::Text { + position: (40.0, 160.0).into(), + color: (1.0, 1.0, 1.0, 1.0).into(), + text: String::from("Quit"), + size: 32.0, + ..Default::default() + }, + player1_score: state::Text { + position: (render.width() * 0.25, 20.0).into(), + color: (1.0, 1.0, 1.0, 1.0).into(), + text: String::from("0"), + size: 32.0, + ..Default::default() + }, + player2_score: state::Text { + position: (render.width() * 0.75, 20.0).into(), + color: (1.0, 1.0, 1.0, 1.0).into(), + text: String::from("0"), + size: 32.0, + ..Default::default() + }, + win_text: state::Text { + position: (render.width() * 0.5, render.height() * 0.5).into(), + bounds: (render.width(), state::UNBOUNDED_F32).into(), + size: 32.0, + centered: true, + ..Default::default() + }, + game_state: state::GameState::MainMenu, + }; + + log::info!("Sound..."); + + let sound_system = sound::SoundSystem::new(); + let sound_pack = sound::SoundPack::new(); + let mut events = Vec::new(); + let mut input = Input::new(); + + log::info!("Initializing Systems..."); + + let mut menu_system = system::MenuSystem; + let mut serving_system = system::ServingSystem::new(); + let mut play_system = system::PlaySystem; + let ball_system = system::BallSystem; + let mut game_over_system = system::GameOverSystem::new(); + + let mut visiblity_system = system::VisibilitySystem; + visiblity_system.start(&mut state); + + menu_system.start(&mut state); + + window.set_visible(true); + + log::info!("Event Loop..."); + + event_loop.run(move |event, _, control_flow| { + *control_flow = if state.game_state == state::GameState::Quiting { + ControlFlow::Exit + } else { + ControlFlow::Poll + }; + + match event { + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => { + state.game_state = state::GameState::Quiting; + } + Event::WindowEvent { + event: + WindowEvent::KeyboardInput { + input: + KeyboardInput { + state: element_state, + virtual_keycode: Some(key), + .. + }, + .. + }, + .. + } => { + let input_handled = match state.game_state { + state::GameState::Quiting => true, + _ => input.update(key, element_state), + }; + if !input_handled { + process_input(element_state, key, control_flow); + } + } + Event::RedrawRequested(_) => { + for event in &events { + match event { + state::Event::FocusChanged | state::Event::ButtonPressed => { + sound_system.queue(sound_pack.bounce()); + } + state::Event::BallBounce(_pos) => { + sound_system.queue(sound_pack.bounce()); + } + state::Event::Score(_) => { + sound_system.queue(sound_pack.bounce()); + } + } + } + events.clear(); + + visiblity_system.update_state(&input, &mut state, &mut events); + match state.game_state { + state::GameState::MainMenu => { + menu_system.update_state(&input, &mut state, &mut events); + if state.game_state == state::GameState::Serving { + serving_system.start(&mut state); + } + } + state::GameState::Serving => { + serving_system.update_state(&input, &mut state, &mut events); + play_system.update_state(&input, &mut state, &mut events); + if state.game_state == state::GameState::Playing { + play_system.start(&mut state); + } + } + state::GameState::Playing => { + ball_system.update_state(&input, &mut state, &mut events); + play_system.update_state(&input, &mut state, &mut events); + if state.game_state == state::GameState::Serving { + serving_system.start(&mut state); + } else if state.game_state == state::GameState::GameOver { + game_over_system.start(&mut state); + } + } + state::GameState::GameOver => { + game_over_system.update_state(&input, &mut state, &mut events); + if state.game_state == state::GameState::MainMenu { + menu_system.start(&mut state); + } + } + state::GameState::Quiting => {} + } + + render.render_state(&state); + if state.game_state != state::GameState::Quiting { + window.request_redraw(); + } + } + _ => {} + } + }); +} + +fn process_input( + element_state: ElementState, + keycode: VirtualKeyCode, + control_flow: &mut ControlFlow, +) { + match (keycode, element_state) { + (VirtualKeyCode::Escape, ElementState::Pressed) => { + *control_flow = ControlFlow::Exit; + } + _ => {} + } +} diff --git a/code/showcase/pong/src/main.rs b/code/showcase/pong/src/main.rs index be011f76..879ae810 100644 --- a/code/showcase/pong/src/main.rs +++ b/code/showcase/pong/src/main.rs @@ -1,238 +1,3 @@ -mod input; -mod render; -mod sound; -mod state; -mod system; -mod util; - -use input::Input; -use system::System; - -use winit::event::*; -use winit::event_loop::{ControlFlow, EventLoop}; -use winit::window::{Fullscreen, WindowBuilder}; - fn main() { - let event_loop = EventLoop::new(); - let monitor = event_loop.primary_monitor().unwrap(); - let video_mode = monitor.video_modes().next().unwrap(); - let window = WindowBuilder::new() - .with_visible(false) - .with_title("Pong") - .with_fullscreen(Some(Fullscreen::Exclusive(video_mode.clone()))) - .build(&event_loop) - .unwrap(); - window.set_cursor_visible(false); - - #[cfg(target_arch = "wasm32")] - { - use winit::platform::web::WindowExtWebSys; - console_log::init_with_level(log::Level::Info).expect("Could't initialize logger"); - std::panic::set_hook(Box::new(console_error_panic_hook::hook)); - 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 cavas to document body."); - } - - log::info!("Setup..."); - - let mut render = pollster::block_on(render::Render::new(&window, &video_mode)); - let mut state = state::State { - ball: state::Ball { - position: (0.0, 0.0).into(), - velocity: (0.0, 0.0).into(), - radius: 0.05, - visible: true, - }, - player1: state::Player { - position: (-0.8, 0.0).into(), - size: (0.05, 0.4).into(), - score: 0, - visible: true, - }, - player2: state::Player { - position: (0.8, 0.0).into(), - size: (0.05, 0.4).into(), - score: 0, - visible: true, - }, - title_text: state::Text { - position: (20.0, 20.0).into(), - color: (1.0, 1.0, 1.0, 1.0).into(), - text: String::from("PONG"), - size: 64.0, - ..Default::default() - }, - play_button: state::Text { - position: (40.0, 100.0).into(), - color: (1.0, 1.0, 1.0, 1.0).into(), - text: String::from("Play"), - size: 32.0, - centered: false, - ..Default::default() - }, - quit_button: state::Text { - position: (40.0, 160.0).into(), - color: (1.0, 1.0, 1.0, 1.0).into(), - text: String::from("Quit"), - size: 32.0, - ..Default::default() - }, - player1_score: state::Text { - position: (render.width() * 0.25, 20.0).into(), - color: (1.0, 1.0, 1.0, 1.0).into(), - text: String::from("0"), - size: 32.0, - ..Default::default() - }, - player2_score: state::Text { - position: (render.width() * 0.75, 20.0).into(), - color: (1.0, 1.0, 1.0, 1.0).into(), - text: String::from("0"), - size: 32.0, - ..Default::default() - }, - win_text: state::Text { - position: (render.width() * 0.5, render.height() * 0.5).into(), - bounds: (render.width(), state::UNBOUNDED_F32).into(), - size: 32.0, - centered: true, - ..Default::default() - }, - game_state: state::GameState::MainMenu, - }; - - log::info!("Sound..."); - - let sound_system = sound::SoundSystem::new(); - let sound_pack = sound::SoundPack::new(); - let mut events = Vec::new(); - let mut input = Input::new(); - - log::info!("Initializing Systems..."); - - let mut menu_system = system::MenuSystem; - let mut serving_system = system::ServingSystem::new(); - let mut play_system = system::PlaySystem; - let ball_system = system::BallSystem; - let mut game_over_system = system::GameOverSystem::new(); - - let mut visiblity_system = system::VisibilitySystem; - visiblity_system.start(&mut state); - - menu_system.start(&mut state); - - window.set_visible(true); - - log::info!("Event Loop..."); - - event_loop.run(move |event, _, control_flow| { - *control_flow = if state.game_state == state::GameState::Quiting { - ControlFlow::Exit - } else { - ControlFlow::Poll - }; - - match event { - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => { - state.game_state = state::GameState::Quiting; - } - Event::WindowEvent { - event: - WindowEvent::KeyboardInput { - input: - KeyboardInput { - state: element_state, - virtual_keycode: Some(key), - .. - }, - .. - }, - .. - } => { - let input_handled = match state.game_state { - state::GameState::Quiting => true, - _ => input.update(key, element_state), - }; - if !input_handled { - process_input(element_state, key, control_flow); - } - } - Event::RedrawRequested(_) => { - for event in &events { - match event { - state::Event::FocusChanged | state::Event::ButtonPressed => { - sound_system.queue(sound_pack.bounce()); - } - state::Event::BallBounce(_pos) => { - sound_system.queue(sound_pack.bounce()); - } - state::Event::Score(_) => { - sound_system.queue(sound_pack.bounce()); - } - } - } - events.clear(); - - visiblity_system.update_state(&input, &mut state, &mut events); - match state.game_state { - state::GameState::MainMenu => { - menu_system.update_state(&input, &mut state, &mut events); - if state.game_state == state::GameState::Serving { - serving_system.start(&mut state); - } - } - state::GameState::Serving => { - serving_system.update_state(&input, &mut state, &mut events); - play_system.update_state(&input, &mut state, &mut events); - if state.game_state == state::GameState::Playing { - play_system.start(&mut state); - } - } - state::GameState::Playing => { - ball_system.update_state(&input, &mut state, &mut events); - play_system.update_state(&input, &mut state, &mut events); - if state.game_state == state::GameState::Serving { - serving_system.start(&mut state); - } else if state.game_state == state::GameState::GameOver { - game_over_system.start(&mut state); - } - } - state::GameState::GameOver => { - game_over_system.update_state(&input, &mut state, &mut events); - if state.game_state == state::GameState::MainMenu { - menu_system.start(&mut state); - } - } - state::GameState::Quiting => {} - } - - render.render_state(&state); - if state.game_state != state::GameState::Quiting { - window.request_redraw(); - } - } - _ => {} - } - }); -} - -fn process_input( - element_state: ElementState, - keycode: VirtualKeyCode, - control_flow: &mut ControlFlow, -) { - match (keycode, element_state) { - (VirtualKeyCode::Escape, ElementState::Pressed) => { - *control_flow = ControlFlow::Exit; - } - _ => {} - } -} + pong::start(); +} \ No newline at end of file diff --git a/code/showcase/pong/src/render/mod.rs b/code/showcase/pong/src/render/mod.rs index 0a1d1079..d6ee63f6 100644 --- a/code/showcase/pong/src/render/mod.rs +++ b/code/showcase/pong/src/render/mod.rs @@ -3,7 +3,7 @@ mod buffer; use std::iter; use wgpu_glyph::{ab_glyph, Section, Text}; -use winit::monitor::VideoMode; +use winit::dpi::PhysicalSize; use winit::window::Window; use buffer::*; @@ -36,7 +36,7 @@ impl Render { self.config.height as f32 } - pub async fn new(window: &Window, video_mode: &VideoMode) -> Self { + pub async fn new(window: &Window, size: PhysicalSize) -> Self { // The instance is a handle to our GPU // BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU let instance = wgpu::Instance::new(wgpu::Backends::all()); @@ -54,14 +54,13 @@ impl Render { &wgpu::DeviceDescriptor { label: None, features: wgpu::Features::empty(), - limits: wgpu::Limits::default(), + limits: wgpu::Limits::downlevel_webgl2_defaults(), }, None, // Trace path ) .await .unwrap(); - let size = video_mode.size(); let config = wgpu::SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format: surface.get_preferred_format(&adapter).unwrap(), @@ -193,12 +192,14 @@ impl Render { self.staging_belt.finish(); self.queue.submit(iter::once(encoder.finish())); frame.present(); + } Err(wgpu::SurfaceError::Outdated) => { + log::info!("Outdated surface texture"); self.surface.configure(&self.device, &self.config); } Err(e) => { - eprintln!("Error: {}", e); + log::error!("Error: {}", e); } } } diff --git a/code/showcase/pong/src/system.rs b/code/showcase/pong/src/system.rs index 3020649c..f408c2db 100644 --- a/code/showcase/pong/src/system.rs +++ b/code/showcase/pong/src/system.rs @@ -67,15 +67,19 @@ impl System for MenuSystem { events.push(state::Event::FocusChanged); state.play_button.focused = false; state.quit_button.focused = true; + log::info!("Quit selected"); } else if state.quit_button.focused && input.ui_up_pressed() { events.push(state::Event::FocusChanged); state.quit_button.focused = false; state.play_button.focused = true; + log::info!("Play selected"); } if state.play_button.focused && input.enter_pressed { + log::info!("Starting game"); events.push(state::Event::ButtonPressed); state.game_state = state::GameState::Serving; + log::info!("Quitting"); } else if state.quit_button.focused && input.enter_pressed { events.push(state::Event::ButtonPressed); state.game_state = state::GameState::Quiting; @@ -118,6 +122,7 @@ impl System for PlaySystem { } if state.player1.score > 2 || state.player2.score > 2 { + log::info!("Gameover"); state.game_state = state::GameState::GameOver; } } @@ -156,10 +161,12 @@ impl System for BallSystem { } if state.ball.position.x > 1.0 { + log::info!("Player 1 scored"); state.player1.score += 1; state.game_state = state::GameState::Serving; events.push(state::Event::Score(0)); } else if state.ball.position.x < -1.0 { + log::info!("Player 1 scored"); state.player2.score += 1; state.game_state = state::GameState::Serving; events.push(state::Event::Score(1)); @@ -168,20 +175,20 @@ impl System for BallSystem { } pub struct ServingSystem { - last_time: std::time::Instant, + last_time: instant::Instant, } impl ServingSystem { pub fn new() -> Self { Self { - last_time: std::time::Instant::now(), + last_time: instant::Instant::now(), } } } impl System for ServingSystem { fn start(&mut self, state: &mut state::State) { - self.last_time = std::time::Instant::now(); + self.last_time = instant::Instant::now(); let direction = state.ball.position.x.signum(); state.ball.position = (0.0, 0.0).into(); state.ball.velocity = cgmath::Vector2::unit_x() * direction * -util::BALL_SPEED; @@ -195,29 +202,30 @@ impl System for ServingSystem { state: &mut state::State, _events: &mut Vec, ) { - let current_time = std::time::Instant::now(); + let current_time = instant::Instant::now(); let delta_time = current_time - self.last_time; if delta_time.as_secs_f32() > 2.0 { + log::info!("Serving..."); state.game_state = state::GameState::Playing; } } } pub struct GameOverSystem { - last_time: std::time::Instant, + last_time: instant::Instant, } impl GameOverSystem { pub fn new() -> Self { Self { - last_time: std::time::Instant::now(), + last_time: instant::Instant::now(), } } } impl System for GameOverSystem { fn start(&mut self, state: &mut state::State) { - self.last_time = std::time::Instant::now(); + self.last_time = instant::Instant::now(); state.player1_score.text = format!("{}", state.player1.score); state.player2_score.text = format!("{}", state.player2.score); @@ -227,6 +235,8 @@ impl System for GameOverSystem { } else { String::from("Player 2 wins!") }; + + log::info!("{}", state.win_text.text); } fn update_state( @@ -235,7 +245,7 @@ impl System for GameOverSystem { state: &mut state::State, _events: &mut Vec, ) { - let current_time = std::time::Instant::now(); + let current_time = instant::Instant::now(); let delta_time = current_time - self.last_time; if delta_time.as_secs_f32() > 1.0 { state.game_state = state::GameState::MainMenu;