summaryrefslogtreecommitdiff
path: root/examples/doom/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'examples/doom/src/main.rs')
-rw-r--r--examples/doom/src/main.rs202
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)
+}