2 Commits

4 changed files with 110 additions and 68 deletions

View File

@@ -35,7 +35,7 @@ fn main() -> Result<()> {
println!(); println!();
println!("Usage:"); println!("Usage:");
#[cfg(any(feature = "native", feature = "browser"))] #[cfg(any(feature = "native", feature = "browser"))]
println!(" uw8 run [-t/--timeout <frames>] [--b/--browser] [-w/--watch] [-p/--pack] [-u/--uncompressed] [-l/--level] [-o/--output <out-file>] <file>"); println!(" uw8 run [-t/--timeout <frames>] [--no-gpu] [--b/--browser] [-w/--watch] [-p/--pack] [-u/--uncompressed] [-l/--level] [-o/--output <out-file>] <file>");
println!(" uw8 pack [-u/--uncompressed] [-l/--level] <in-file> <out-file>"); println!(" uw8 pack [-u/--uncompressed] [-l/--level] <in-file> <out-file>");
println!(" uw8 unpack <in-file> <out-file>"); println!(" uw8 unpack <in-file> <out-file>");
println!(" uw8 compile [-d/--debug] <in-file> <out-file>"); println!(" uw8 compile [-d/--debug] <in-file> <out-file>");
@@ -54,6 +54,8 @@ fn run(mut args: Arguments) -> Result<()> {
let watch_mode = args.contains(["-w", "--watch"]); let watch_mode = args.contains(["-w", "--watch"]);
#[allow(unused)] #[allow(unused)]
let timeout: Option<u32> = args.opt_value_from_str(["-t", "--timeout"])?; let timeout: Option<u32> = args.opt_value_from_str(["-t", "--timeout"])?;
#[allow(unused)]
let gpu = !args.contains("--no-gpu");
let mut config = Config::default(); let mut config = Config::default();
if args.contains(["-p", "--pack"]) { if args.contains(["-p", "--pack"]) {
@@ -93,7 +95,7 @@ fn run(mut args: Arguments) -> Result<()> {
unimplemented!(); unimplemented!();
#[cfg(feature = "native")] #[cfg(feature = "native")]
{ {
let mut microw8 = MicroW8::new(timeout)?; let mut microw8 = MicroW8::new(timeout, gpu)?;
if disable_audio { if disable_audio {
microw8.disable_audio(); microw8.disable_audio();
} }

View File

@@ -10,7 +10,18 @@ use wasmtime::{
}; };
pub struct MicroW8 { pub struct MicroW8 {
tx: mpsc::SyncSender<Vec<u8>>, tx: mpsc::SyncSender<Option<UW8Instance>>,
rx: mpsc::Receiver<UIEvent>,
stream: Option<cpal::Stream>,
engine: Engine,
loader_module: Module,
disable_audio: bool,
module_data: Option<Vec<u8>>,
}
enum UIEvent {
Error(Result<()>),
Reset,
} }
struct UW8Instance { struct UW8Instance {
@@ -19,9 +30,8 @@ struct UW8Instance {
end_frame: TypedFunc<(), ()>, end_frame: TypedFunc<(), ()>,
update: Option<TypedFunc<(), ()>>, update: Option<TypedFunc<(), ()>>,
start_time: Instant, start_time: Instant,
module: Vec<u8>,
watchdog: Arc<Mutex<UW8WatchDog>>, watchdog: Arc<Mutex<UW8WatchDog>>,
sound: Option<Uw8Sound>, sound_tx: Option<mpsc::SyncSender<RegisterUpdate>>,
} }
impl Drop for UW8Instance { impl Drop for UW8Instance {
@@ -38,7 +48,7 @@ struct UW8WatchDog {
} }
impl MicroW8 { impl MicroW8 {
pub fn new(timeout: Option<u32>) -> Result<MicroW8> { pub fn new(timeout: Option<u32>, gpu: bool) -> Result<MicroW8> {
let mut config = wasmtime::Config::new(); let mut config = wasmtime::Config::new();
config.cranelift_opt_level(wasmtime::OptLevel::Speed); config.cranelift_opt_level(wasmtime::OptLevel::Speed);
if timeout.is_some() { if timeout.is_some() {
@@ -49,37 +59,45 @@ impl MicroW8 {
let loader_module = let loader_module =
wasmtime::Module::new(&engine, include_bytes!("../platform/bin/loader.wasm"))?; wasmtime::Module::new(&engine, include_bytes!("../platform/bin/loader.wasm"))?;
let (tx, rx) = mpsc::sync_channel::<Vec<u8>>(1); let (to_ui_tx, to_ui_rx) = mpsc::sync_channel(2);
let (from_ui_tx, from_ui_rx) = mpsc::sync_channel(1);
std::thread::spawn(move || { std::thread::spawn(move || {
let mut state = State { let mut state = State {
engine,
loader_module,
disable_audio: false,
instance: None, instance: None,
timeout: timeout.unwrap_or(0), timeout: timeout.unwrap_or(0),
}; };
uw8_window::run(move |framebuffer, gamepad, reset| { uw8_window::run(gpu, move |framebuffer, gamepad, reset| {
if let Ok(module_data) = rx.try_recv() { while let Ok(instance) = to_ui_rx.try_recv() {
if let Err(err) = state.load(&module_data) { state.instance = instance;
eprintln!("Failed to load module: {}", err);
}
} }
state if reset {
.run_frame(framebuffer, gamepad, reset) from_ui_tx.send(UIEvent::Reset).unwrap();
.unwrap_or_else(|err| { }
eprintln!("Runtime error: {}", err);
Instant::now() state.run_frame(framebuffer, gamepad).unwrap_or_else(|err| {
}) from_ui_tx.send(UIEvent::Error(Err(err))).unwrap();
Instant::now()
})
}); });
}); });
Ok(MicroW8 { tx }) Ok(MicroW8 {
tx: to_ui_tx,
rx: from_ui_rx,
stream: None,
engine,
loader_module,
disable_audio: false,
module_data: None,
})
} }
pub fn disable_audio(&mut self) {} pub fn disable_audio(&mut self) {
self.disable_audio = true;
}
} }
impl super::Runtime for MicroW8 { impl super::Runtime for MicroW8 {
@@ -88,26 +106,8 @@ impl super::Runtime for MicroW8 {
} }
fn load(&mut self, module_data: &[u8]) -> Result<()> { fn load(&mut self, module_data: &[u8]) -> Result<()> {
self.tx.send(module_data.into())?; self.stream = None;
Ok(()) self.tx.send(None)?;
}
fn run_frame(&mut self) -> Result<()> {
Ok(())
}
}
struct State {
engine: Engine,
loader_module: Module,
disable_audio: bool,
instance: Option<UW8Instance>,
timeout: u32,
}
impl State {
fn load(&mut self, module_data: &[u8]) -> Result<()> {
self.instance = None;
let mut store = wasmtime::Store::new(&self.engine, ()); let mut store = wasmtime::Store::new(&self.engine, ());
store.set_epoch_deadline(60); store.set_epoch_deadline(60);
@@ -159,39 +159,63 @@ impl State {
let end_frame = platform_instance.get_typed_func::<(), (), _>(&mut store, "endFrame")?; let end_frame = platform_instance.get_typed_func::<(), (), _>(&mut store, "endFrame")?;
let update = instance.get_typed_func::<(), (), _>(&mut store, "upd").ok(); let update = instance.get_typed_func::<(), (), _>(&mut store, "upd").ok();
let sound = if self.disable_audio { let (sound_tx, stream) = if self.disable_audio {
None (None, None)
} else { } else {
match init_sound(&self.engine, &platform_module, &module) { match init_sound(&self.engine, &platform_module, &module) {
Ok(sound) => { Ok(sound) => {
sound.stream.play()?; sound.stream.play()?;
Some(sound) (Some(sound.tx), Some(sound.stream))
} }
Err(err) => { Err(err) => {
eprintln!("Failed to init sound: {}", err); eprintln!("Failed to init sound: {}", err);
None (None, None)
} }
} }
}; };
self.instance = Some(UW8Instance { self.tx.send(Some(UW8Instance {
store, store,
memory, memory,
end_frame, end_frame,
update, update,
start_time: Instant::now(), start_time: Instant::now(),
module: module_data.into(),
watchdog, watchdog,
sound, sound_tx,
}); }))?;
self.stream = stream;
self.module_data = Some(module_data.into());
Ok(()) Ok(())
} }
fn run_frame(&mut self) -> Result<()> {
if let Ok(event) = self.rx.try_recv() {
match event {
UIEvent::Error(err) => err,
UIEvent::Reset => {
if let Some(module_data) = self.module_data.take() {
self.load(&module_data)
} else {
Ok(())
}
}
}
} else {
Ok(())
}
}
}
struct State {
instance: Option<UW8Instance>,
timeout: u32,
}
impl State {
fn run_frame( fn run_frame(
&mut self, &mut self,
framebuffer: &mut dyn uw8_window::Framebuffer, framebuffer: &mut dyn uw8_window::Framebuffer,
gamepad: u32, gamepad: u32,
reset: bool,
) -> Result<Instant> { ) -> Result<Instant> {
let now = Instant::now(); let now = Instant::now();
let mut result = Ok(now); let mut result = Ok(now);
@@ -220,20 +244,18 @@ impl State {
let mut sound_regs = [0u8; 32]; let mut sound_regs = [0u8; 32];
sound_regs.copy_from_slice(&memory[80..112]); sound_regs.copy_from_slice(&memory[80..112]);
if let Some(ref sound) = instance.sound { if let Some(ref sound_tx) = instance.sound_tx {
sound.tx.send(RegisterUpdate { let _ = sound_tx.send(RegisterUpdate {
time, time,
data: sound_regs, data: sound_regs,
})?; });
} }
let framebuffer_mem = &memory[120..(120 + 320 * 240)]; let framebuffer_mem = &memory[120..(120 + 320 * 240)];
let palette_mem = &memory[0x13000..]; let palette_mem = &memory[0x13000..];
framebuffer.update(framebuffer_mem, palette_mem); framebuffer.update(framebuffer_mem, palette_mem);
if reset { if result.is_ok() {
self.load(&instance.module)?;
} else if result.is_ok() {
self.instance = Some(instance); self.instance = Some(instance);
} }
} }

View File

@@ -35,12 +35,25 @@ pub fn run(mut update: Box<dyn FnMut(&mut dyn Framebuffer, u32, bool) -> Instant
if let Some(sleep) = next_frame.checked_duration_since(Instant::now()) { if let Some(sleep) = next_frame.checked_duration_since(Instant::now()) {
std::thread::sleep(sleep); std::thread::sleep(sleep);
} }
let mut gamepad = 0;
for key in window.get_keys() {
if let Some(index) = GAMEPAD_KEYS
.iter()
.enumerate()
.find(|(_, &k)| k == key)
.map(|(i, _)| i)
{
gamepad |= 1 << index;
}
}
next_frame = update( next_frame = update(
&mut CpuFramebuffer { &mut CpuFramebuffer {
buffer: &mut buffer, buffer: &mut buffer,
}, },
0, gamepad,
false, window.is_key_pressed(Key::R, minifb::KeyRepeat::No),
); );
window.update_with_buffer(&buffer, 320, 240).unwrap(); window.update_with_buffer(&buffer, 320, 240).unwrap();
} }

View File

@@ -3,13 +3,18 @@ use std::time::Instant;
mod cpu; mod cpu;
mod gpu; mod gpu;
pub fn run<F: 'static + FnMut(&mut dyn Framebuffer, u32, bool) -> Instant>(update: F) -> ! { pub fn run<F: 'static + FnMut(&mut dyn Framebuffer, u32, bool) -> Instant>(
match gpu::Window::new() { gpu: bool,
Ok(window) => window.run(Box::new(update)), update: F,
Err(err) => eprintln!( ) -> ! {
"Failed to create gpu window: {}\nFalling back to cpu window", if gpu {
err match gpu::Window::new() {
), Ok(window) => window.run(Box::new(update)),
Err(err) => eprintln!(
"Failed to create gpu window: {}\nFalling back to cpu window",
err
),
}
} }
cpu::run(Box::new(update)); cpu::run(Box::new(update));
} }