mod runtime; use std::num::NonZeroU32; use std::path::{Path, PathBuf}; use std::rc::Rc; use eyre::{ContextCompat, Result, bail, eyre}; use runtime::{Runtime, SCREEN_HEIGHT, SCREEN_WIDTH}; use softbuffer::{Context as SoftbufferContext, Surface}; use winit::application::ApplicationHandler; use winit::dpi::{LogicalSize, Size}; use winit::event::{ElementState, WindowEvent}; use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop}; use winit::keyboard::{KeyCode, PhysicalKey}; use winit::window::{Window, WindowAttributes, WindowLevel}; const GUEST_MODULE: &str = "examples/doom/out/puredoom.wasm"; const DEFAULT_TITLE: &str = "tinywasm doom"; fn main() -> Result<()> { pretty_env_logger::init(); let wad_path = std::env::args().nth(1).map(PathBuf::from).context("usage: cargo run -p tinywasm-doom -- ")?; if !wad_path.exists() { bail!("WAD not found: {}", wad_path.display()); } let guest_path = Path::new(GUEST_MODULE); if !guest_path.exists() { bail!("guest module missing: {}. Run ./examples/doom/build.sh first", guest_path.display()); } let event_loop = EventLoop::new()?; let mut app = DoomApp::new(wad_path, guest_path.to_path_buf())?; event_loop.run_app(&mut app)?; Ok(()) } struct DoomApp { runtime: Runtime, window: Option>, softbuffer_context: Option>>, softbuffer_surface: Option, Rc>>, } impl DoomApp { fn new(wad_path: PathBuf, guest_path: PathBuf) -> Result { Ok(Self { runtime: Runtime::new(wad_path, guest_path)?, window: None, softbuffer_context: None, softbuffer_surface: None, }) } fn present(&mut self) -> Result<()> { let Some(surface) = self.softbuffer_surface.as_mut() else { return Ok(()); }; surface .resize(NonZeroU32::new(SCREEN_WIDTH as u32).unwrap(), NonZeroU32::new(SCREEN_HEIGHT as u32).unwrap()) .map_err(|err| eyre!(err.to_string()))?; let mut buffer = surface.buffer_mut().map_err(|err| eyre!(err.to_string()))?; self.runtime.write_framebuffer(&mut buffer)?; buffer.present().map_err(|err| eyre!(err.to_string()))?; Ok(()) } } impl ApplicationHandler for DoomApp { fn resumed(&mut self, event_loop: &ActiveEventLoop) { event_loop.set_control_flow(ControlFlow::Poll); let size = LogicalSize::new((SCREEN_WIDTH * 2) as f64, (SCREEN_HEIGHT * 2) as f64); let attributes = WindowAttributes::default() .with_title(DEFAULT_TITLE) .with_inner_size(Size::Logical(size)) .with_resizable(false) .with_window_level(if cfg!(target_os = "linux") { WindowLevel::AlwaysOnTop } else { WindowLevel::Normal }); let window = Rc::new(event_loop.create_window(attributes).expect("create window")); let context = SoftbufferContext::new(window.clone()).expect("create softbuffer context"); let surface = Surface::new(&context, window.clone()).expect("create softbuffer surface"); self.softbuffer_context = Some(context); self.softbuffer_surface = Some(surface); self.window = Some(window.clone()); window.set_cursor_visible(false); window.request_redraw(); } fn window_event(&mut self, event_loop: &ActiveEventLoop, _window_id: winit::window::WindowId, event: WindowEvent) { match event { WindowEvent::CloseRequested => event_loop.exit(), WindowEvent::RedrawRequested => { if let Err(err) = self.present() { log::error!("present failed: {err:?}"); event_loop.exit(); } } WindowEvent::KeyboardInput { event, .. } => { if let Some(key) = doom_key(&event.physical_key) { let result = if matches!(event.state, ElementState::Pressed) { self.runtime.key_down(i32::from(key)) } else { self.runtime.key_up(i32::from(key)) }; if let Err(err) = result { log::error!("keyboard event failed: {err:?}"); event_loop.exit(); } } } _ => {} } } fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { if let Err(err) = self.runtime.tick() { log::error!("doom tick failed: {err:?}"); event_loop.exit(); return; } if let Some(window) = &self.window { window.request_redraw(); } if self.runtime.host_state.borrow().exit_code.is_some() { event_loop.exit(); } } } fn doom_key(key: &PhysicalKey) -> Option { let code = match key { PhysicalKey::Code(KeyCode::Enter) => 13, PhysicalKey::Code(KeyCode::Escape) => 27, PhysicalKey::Code(KeyCode::ArrowLeft) => 0xac, PhysicalKey::Code(KeyCode::ArrowRight) => 0xae, PhysicalKey::Code(KeyCode::ArrowUp) => 0xad, PhysicalKey::Code(KeyCode::ArrowDown) => 0xaf, PhysicalKey::Code(KeyCode::ControlLeft | KeyCode::ControlRight) => 0x80 + 0x1d, PhysicalKey::Code(KeyCode::ShiftLeft | KeyCode::ShiftRight) => 0xb6, PhysicalKey::Code(KeyCode::AltLeft | KeyCode::AltRight) => 0xb8, PhysicalKey::Code(KeyCode::Space) => b' ', PhysicalKey::Code(KeyCode::F2) => 0x80 + 0x3c, PhysicalKey::Code(KeyCode::F3) => 0x80 + 0x3d, PhysicalKey::Code(KeyCode::F4) => 0x80 + 0x3e, PhysicalKey::Code(KeyCode::F5) => 0x80 + 0x3f, PhysicalKey::Code(KeyCode::F6) => 0x80 + 0x40, PhysicalKey::Code(KeyCode::F7) => 0x80 + 0x41, PhysicalKey::Code(KeyCode::F8) => 0x80 + 0x42, PhysicalKey::Code(KeyCode::F9) => 0x80 + 0x43, PhysicalKey::Code(KeyCode::F10) => 0x80 + 0x44, PhysicalKey::Code(KeyCode::F11) => 0x80 + 0x57, PhysicalKey::Code(KeyCode::Equal) => b'=', PhysicalKey::Code(KeyCode::Minus) => b'-', PhysicalKey::Code(KeyCode::KeyA) => b'a', PhysicalKey::Code(KeyCode::KeyB) => b'b', PhysicalKey::Code(KeyCode::KeyC) => b'c', PhysicalKey::Code(KeyCode::KeyD) => b'd', PhysicalKey::Code(KeyCode::KeyE) => b'e', PhysicalKey::Code(KeyCode::KeyF) => b'f', PhysicalKey::Code(KeyCode::KeyG) => b'g', PhysicalKey::Code(KeyCode::KeyH) => b'h', PhysicalKey::Code(KeyCode::KeyI) => b'i', PhysicalKey::Code(KeyCode::KeyJ) => b'j', PhysicalKey::Code(KeyCode::KeyK) => b'k', PhysicalKey::Code(KeyCode::KeyL) => b'l', PhysicalKey::Code(KeyCode::KeyM) => b'm', PhysicalKey::Code(KeyCode::KeyN) => b'n', PhysicalKey::Code(KeyCode::KeyO) => b'o', PhysicalKey::Code(KeyCode::KeyP) => b'p', PhysicalKey::Code(KeyCode::KeyQ) => b'q', PhysicalKey::Code(KeyCode::KeyR) => b'r', PhysicalKey::Code(KeyCode::KeyS) => b's', PhysicalKey::Code(KeyCode::KeyT) => b't', PhysicalKey::Code(KeyCode::KeyU) => b'u', PhysicalKey::Code(KeyCode::KeyV) => b'v', PhysicalKey::Code(KeyCode::KeyW) => b'w', PhysicalKey::Code(KeyCode::KeyX) => b'x', PhysicalKey::Code(KeyCode::KeyY) => b'y', PhysicalKey::Code(KeyCode::KeyZ) => b'z', PhysicalKey::Code(KeyCode::Digit0) => b'0', PhysicalKey::Code(KeyCode::Digit1) => b'1', PhysicalKey::Code(KeyCode::Digit2) => b'2', PhysicalKey::Code(KeyCode::Digit3) => b'3', PhysicalKey::Code(KeyCode::Digit4) => b'4', PhysicalKey::Code(KeyCode::Digit5) => b'5', PhysicalKey::Code(KeyCode::Digit6) => b'6', PhysicalKey::Code(KeyCode::Digit7) => b'7', PhysicalKey::Code(KeyCode::Digit8) => b'8', PhysicalKey::Code(KeyCode::Digit9) => b'9', _ => return None, }; Some(code) }