diff options
| author | Henry <mail@henrygressmann.de> | 2026-05-02 15:40:47 +0200 |
|---|---|---|
| committer | Henry <mail@henrygressmann.de> | 2026-05-02 15:45:44 +0200 |
| commit | 5ed8982ad2cb4b348046aa11c527ce6a51f02e0e (patch) | |
| tree | c5427ac3ffdeedfa37b698eeb4f2dac09859b4a6 /examples/doom/src/main.rs | |
| parent | 361b715de9ea6a859aaafe84d4bcd3d46ed70797 (diff) | |
feat: add doom example, fix import argument order
Signed-off-by: Henry <mail@henrygressmann.de>
Diffstat (limited to 'examples/doom/src/main.rs')
| -rw-r--r-- | examples/doom/src/main.rs | 202 |
1 files changed, 202 insertions, 0 deletions
diff --git a/examples/doom/src/main.rs b/examples/doom/src/main.rs new file mode 100644 index 0000000..095183d --- /dev/null +++ b/examples/doom/src/main.rs @@ -0,0 +1,202 @@ +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 -- <wad>")?; + 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<Rc<Window>>, + softbuffer_context: Option<SoftbufferContext<Rc<Window>>>, + softbuffer_surface: Option<Surface<Rc<Window>, Rc<Window>>>, +} + +impl DoomApp { + fn new(wad_path: PathBuf, guest_path: PathBuf) -> Result<Self> { + 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<u8> { + 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) +} |
