diff --git a/README.md b/README.md index edb819a..8edf3f5 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ Runs which can be a binary WebAssembly module, an `.uw8` cart, a wat (Web Options: +-t, --timeout FRAMES : Sets the timeout in frames (1/60s) -w, --watch : Reloads the given file every time it changes on disk. -p, --pack : Pack the file into an .uw8 cart before running it and print the resulting size. -u, --uncompressed : Use the uncompressed uw8 format for packing. diff --git a/site/content/docs.md b/site/content/docs.md index 111a083..f06b001 100644 --- a/site/content/docs.md +++ b/site/content/docs.md @@ -284,6 +284,8 @@ Runs `` which can be a binary WebAssembly module, an `.uw8` cart, a wat (W Options: +* `-t FRAMES`, `--timeout FRAMES` : Sets the timeout in frames (1/60s). If the start or update function runs longer than this it is forcibly interupted +and execution of the cart is stopped. Defaults to 30 (0.5s) * `-w`, `--watch`: Reloads the given file every time it changes on disk. * `-p`, `--pack`: Pack the file into an `.uw8` cart before running it and print the resulting size. * `-u`, `--uncompressed`: Use the uncompressed `uw8` format for packing. diff --git a/src/lib.rs b/src/lib.rs index fe89e4b..5d64f9f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,8 @@ use std::io::prelude::*; use std::path::Path; -use std::{fs::File, time::Instant}; +use std::sync::{Arc, Mutex}; +use std::time::Duration; +use std::{fs::File, thread, time::Instant}; use anyhow::Result; use minifb::{Key, Window, WindowOptions}; @@ -8,7 +10,16 @@ use wasmtime::{ Engine, GlobalType, Memory, MemoryType, Module, Mutability, Store, TypedFunc, ValType, }; -static GAMEPAD_KEYS: &'static [Key] = &[Key::Up, Key::Down, Key::Left, Key::Right, Key::Z, Key::X, Key::A, Key::S]; +static GAMEPAD_KEYS: &'static [Key] = &[ + Key::Up, + Key::Down, + Key::Left, + Key::Right, + Key::Z, + Key::X, + Key::A, + Key::S, +]; pub struct MicroW8 { engine: Engine, @@ -16,6 +27,7 @@ pub struct MicroW8 { window: Window, window_buffer: Vec, instance: Option, + timeout: u32, } struct UW8Instance { @@ -24,12 +36,27 @@ struct UW8Instance { end_frame: TypedFunc<(), ()>, update: TypedFunc<(), ()>, start_time: Instant, - module: Vec + module: Vec, + watchdog: Arc>, +} + +impl Drop for UW8Instance { + fn drop(&mut self) { + if let Ok(mut watchdog) = self.watchdog.lock() { + watchdog.stop = true; + } + } +} + +struct UW8WatchDog { + interupt: wasmtime::InterruptHandle, + timeout: u32, + stop: bool, } impl MicroW8 { pub fn new() -> Result { - let engine = wasmtime::Engine::default(); + let engine = wasmtime::Engine::new(wasmtime::Config::new().interruptable(true))?; let loader_module = wasmtime::Module::new(&engine, include_bytes!("../platform/bin/loader.wasm"))?; @@ -47,6 +74,7 @@ impl MicroW8 { window, window_buffer: vec![0u32; 320 * 240], instance: None, + timeout: 30, }) } @@ -54,6 +82,10 @@ impl MicroW8 { self.window.is_open() && !self.window.is_key_down(Key::Escape) } + pub fn set_timeout(&mut self, timeout: u32) { + self.timeout = timeout; + } + fn reset(&mut self) { self.instance = None; for v in &mut self.window_buffer { @@ -130,7 +162,36 @@ impl MicroW8 { )?; } + let watchdog = Arc::new(Mutex::new(UW8WatchDog { + interupt: store.interrupt_handle()?, + timeout: self.timeout, + stop: false, + })); + + { + let watchdog = watchdog.clone(); + thread::spawn(move || loop { + thread::sleep(Duration::from_millis(17)); + if let Ok(mut watchdog) = watchdog.lock() { + if watchdog.stop { + break; + } + if watchdog.timeout > 0 { + watchdog.timeout -= 1; + if watchdog.timeout == 0 { + watchdog.interupt.interrupt(); + } + } + } else { + break; + } + }); + } + let instance = linker.instantiate(&mut store, &module)?; + if let Ok(mut watchdog) = watchdog.lock() { + watchdog.timeout = 0; + } let end_frame = platform_instance.get_typed_func::<(), (), _>(&mut store, "endFrame")?; let update = instance.get_typed_func::<(), (), _>(&mut store, "upd")?; @@ -140,19 +201,26 @@ impl MicroW8 { end_frame, update, start_time: Instant::now(), - module: module_data.into() + module: module_data.into(), + watchdog, }); Ok(()) } pub fn run_frame(&mut self) -> Result<()> { + let mut result = Ok(()); if let Some(mut instance) = self.instance.take() { { let time = instance.start_time.elapsed().as_millis() as i32; let mut gamepad: u32 = 0; for key in self.window.get_keys() { - if let Some(index) = GAMEPAD_KEYS.iter().enumerate().find(|(_, &k)| k == key).map(|(i, _)| i) { + if let Some(index) = GAMEPAD_KEYS + .iter() + .enumerate() + .find(|(_, &k)| k == key) + .map(|(i, _)| i) + { gamepad |= 1 << index; } } @@ -162,7 +230,13 @@ impl MicroW8 { mem[68..72].copy_from_slice(&gamepad.to_le_bytes()); } - instance.update.call(&mut instance.store, ())?; + if let Ok(mut watchdog) = instance.watchdog.lock() { + watchdog.timeout = self.timeout; + } + result = instance.update.call(&mut instance.store, ()); + if let Ok(mut watchdog) = instance.watchdog.lock() { + watchdog.timeout = 0; + } instance.end_frame.call(&mut instance.store, ())?; let memory = instance.memory.data(&instance.store); @@ -178,7 +252,7 @@ impl MicroW8 { if self.window.is_key_pressed(Key::R, minifb::KeyRepeat::No) { self.load_from_memory(&instance.module)?; - } else { + } else if result.is_ok() { self.instance = Some(instance); } } @@ -186,6 +260,7 @@ impl MicroW8 { self.window .update_with_buffer(&self.window_buffer, 320, 240)?; + result?; Ok(()) } } diff --git a/src/main.rs b/src/main.rs index 9da37cc..c686757 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,7 +28,7 @@ fn main() -> Result<()> { println!("uw8 {}", env!("CARGO_PKG_VERSION")); println!(); println!("Usage:"); - println!(" uw8 run [-w/--watch] [-p/--pack] [-u/--uncompressed] [-l/--level] [-o/--output ] "); + println!(" uw8 run [-t/--timeout ] [-w/--watch] [-p/--pack] [-u/--uncompressed] [-l/--level] [-o/--output ] "); println!(" uw8 pack [-u/--uncompressed] [-l/--level] "); println!(" uw8 filter-exports "); Ok(()) @@ -42,6 +42,7 @@ fn main() -> Result<()> { fn run(mut args: Arguments) -> Result<()> { let watch_mode = args.contains(["-w", "--watch"]); + let timeout: Option = args.opt_value_from_str(["-t", "--timeout"])?; let mut config = Config::default(); if args.contains(["-p", "--pack"]) { @@ -67,6 +68,10 @@ fn run(mut args: Arguments) -> Result<()> { let mut uw8 = MicroW8::new()?; + if let Some(timeout) = timeout { + uw8.set_timeout(timeout); + } + let (tx, rx) = mpsc::channel(); let mut watcher = notify::watcher(tx, Duration::from_millis(100))?;