mirror of
https://github.com/exoticorn/microw8.git
synced 2026-01-20 11:16:42 +01:00
first somewhat working version of uw8 using gpu window
This commit is contained in:
994
Cargo.lock
generated
994
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -7,13 +7,13 @@ edition = "2021"
|
||||
|
||||
[features]
|
||||
default = ["native", "browser"]
|
||||
native = ["wasmtime", "minifb", "cpal", "rubato", "winapi" ]
|
||||
native = ["wasmtime", "uw8-window", "cpal", "rubato" ]
|
||||
browser = ["warp", "tokio", "tokio-stream", "webbrowser"]
|
||||
|
||||
[dependencies]
|
||||
wasmtime = { version = "0.37.0", optional = true }
|
||||
anyhow = "1"
|
||||
minifb = { version = "0.22", default-features = false, features = ["x11"], optional = true }
|
||||
uw8-window = { path = "uw8-window", optional = true }
|
||||
notify = "4"
|
||||
pico-args = "0.4"
|
||||
curlywas = { git = "https://github.com/exoticorn/curlywas.git", rev = "0e7ea50" }
|
||||
@@ -27,4 +27,3 @@ webbrowser = { version = "0.6.0", optional = true }
|
||||
ansi_term = "0.12.1"
|
||||
cpal = { version = "0.13.5", optional = true }
|
||||
rubato = { version = "0.11.0", optional = true }
|
||||
winapi = { version = "0.3.9", features = ["timeapi"], optional = true }
|
||||
|
||||
@@ -4,31 +4,13 @@ use std::{thread, time::Instant};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use cpal::traits::*;
|
||||
use minifb::{Key, Window, WindowOptions};
|
||||
use rubato::Resampler;
|
||||
use wasmtime::{
|
||||
Engine, GlobalType, Memory, MemoryType, Module, Mutability, Store, TypedFunc, ValType,
|
||||
};
|
||||
|
||||
static GAMEPAD_KEYS: &[Key] = &[
|
||||
Key::Up,
|
||||
Key::Down,
|
||||
Key::Left,
|
||||
Key::Right,
|
||||
Key::Z,
|
||||
Key::X,
|
||||
Key::A,
|
||||
Key::S,
|
||||
];
|
||||
|
||||
pub struct MicroW8 {
|
||||
engine: Engine,
|
||||
loader_module: Module,
|
||||
window: Window,
|
||||
window_buffer: Vec<u32>,
|
||||
instance: Option<UW8Instance>,
|
||||
timeout: u32,
|
||||
disable_audio: bool,
|
||||
tx: mpsc::SyncSender<Vec<u8>>,
|
||||
}
|
||||
|
||||
struct UW8Instance {
|
||||
@@ -37,7 +19,6 @@ struct UW8Instance {
|
||||
end_frame: TypedFunc<(), ()>,
|
||||
update: Option<TypedFunc<(), ()>>,
|
||||
start_time: Instant,
|
||||
next_frame: Instant,
|
||||
module: Vec<u8>,
|
||||
watchdog: Arc<Mutex<UW8WatchDog>>,
|
||||
sound: Option<Uw8Sound>,
|
||||
@@ -58,11 +39,6 @@ struct UW8WatchDog {
|
||||
|
||||
impl MicroW8 {
|
||||
pub fn new(timeout: Option<u32>) -> Result<MicroW8> {
|
||||
#[cfg(target_os = "windows")]
|
||||
unsafe {
|
||||
winapi::um::timeapi::timeBeginPeriod(1);
|
||||
}
|
||||
|
||||
let mut config = wasmtime::Config::new();
|
||||
config.cranelift_opt_level(wasmtime::OptLevel::Speed);
|
||||
if timeout.is_some() {
|
||||
@@ -73,44 +49,65 @@ impl MicroW8 {
|
||||
let loader_module =
|
||||
wasmtime::Module::new(&engine, include_bytes!("../platform/bin/loader.wasm"))?;
|
||||
|
||||
let options = WindowOptions {
|
||||
scale: minifb::Scale::X2,
|
||||
scale_mode: minifb::ScaleMode::AspectRatioStretch,
|
||||
resize: true,
|
||||
..Default::default()
|
||||
};
|
||||
let window = Window::new("MicroW8", 320, 240, options)?;
|
||||
let (tx, rx) = mpsc::sync_channel::<Vec<u8>>(1);
|
||||
|
||||
Ok(MicroW8 {
|
||||
std::thread::spawn(move || {
|
||||
let mut state = State {
|
||||
engine,
|
||||
loader_module,
|
||||
window,
|
||||
window_buffer: vec![0u32; 320 * 240],
|
||||
disable_audio: false,
|
||||
instance: None,
|
||||
timeout: timeout.unwrap_or(0),
|
||||
disable_audio: false,
|
||||
};
|
||||
|
||||
uw8_window::run(move |framebuffer, gamepad, reset| {
|
||||
if let Ok(module_data) = rx.try_recv() {
|
||||
if let Err(err) = state.load(&module_data) {
|
||||
eprintln!("Failed to load module: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
state
|
||||
.run_frame(framebuffer, gamepad, reset)
|
||||
.unwrap_or_else(|err| {
|
||||
eprintln!("Runtime error: {}", err);
|
||||
Instant::now()
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
Ok(MicroW8 { tx })
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.instance = None;
|
||||
for v in &mut self.window_buffer {
|
||||
*v = 0;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disable_audio(&mut self) {
|
||||
self.disable_audio = true;
|
||||
}
|
||||
pub fn disable_audio(&mut self) {}
|
||||
}
|
||||
|
||||
impl super::Runtime for MicroW8 {
|
||||
fn is_open(&self) -> bool {
|
||||
self.window.is_open() && !self.window.is_key_down(Key::Escape)
|
||||
true
|
||||
}
|
||||
|
||||
fn load(&mut self, module_data: &[u8]) -> Result<()> {
|
||||
self.reset();
|
||||
self.tx.send(module_data.into())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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, ());
|
||||
store.set_epoch_deadline(60);
|
||||
@@ -183,7 +180,6 @@ impl super::Runtime for MicroW8 {
|
||||
end_frame,
|
||||
update,
|
||||
start_time: Instant::now(),
|
||||
next_frame: Instant::now(),
|
||||
module: module_data.into(),
|
||||
watchdog,
|
||||
sound,
|
||||
@@ -191,36 +187,22 @@ impl super::Runtime for MicroW8 {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_frame(&mut self) -> Result<()> {
|
||||
let mut result = Ok(());
|
||||
if let Some(mut instance) = self.instance.take() {
|
||||
{
|
||||
if let Some(sleep) = instance.next_frame.checked_duration_since(Instant::now()) {
|
||||
std::thread::sleep(sleep);
|
||||
}
|
||||
}
|
||||
|
||||
fn run_frame(
|
||||
&mut self,
|
||||
framebuffer: &mut dyn uw8_window::Framebuffer,
|
||||
gamepad: u32,
|
||||
reset: bool,
|
||||
) -> Result<Instant> {
|
||||
let now = Instant::now();
|
||||
let mut result = Ok(now);
|
||||
if let Some(mut instance) = self.instance.take() {
|
||||
let time = (now - instance.start_time).as_millis() as i32;
|
||||
{
|
||||
let offset = ((time as u32 as i64 * 6) % 100 - 50) / 6;
|
||||
instance.next_frame = now + Duration::from_millis((16 - offset) as u64);
|
||||
result = Ok(now + Duration::from_millis((16 - offset) as u64));
|
||||
}
|
||||
|
||||
{
|
||||
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)
|
||||
{
|
||||
gamepad |= 1 << index;
|
||||
}
|
||||
}
|
||||
|
||||
let mem = instance.memory.data_mut(&mut instance.store);
|
||||
mem[64..68].copy_from_slice(&time.to_le_bytes());
|
||||
mem[68..72].copy_from_slice(&gamepad.to_le_bytes());
|
||||
@@ -228,7 +210,9 @@ impl super::Runtime for MicroW8 {
|
||||
|
||||
instance.store.set_epoch_deadline(self.timeout as u64);
|
||||
if let Some(ref update) = instance.update {
|
||||
result = update.call(&mut instance.store, ());
|
||||
if let Err(err) = update.call(&mut instance.store, ()) {
|
||||
result = Err(err);
|
||||
}
|
||||
}
|
||||
instance.end_frame.call(&mut instance.store, ())?;
|
||||
|
||||
@@ -243,28 +227,18 @@ impl super::Runtime for MicroW8 {
|
||||
})?;
|
||||
}
|
||||
|
||||
let framebuffer = &memory[120..(120 + 320 * 240)];
|
||||
let palette = &memory[0x13000..];
|
||||
for (i, &color_index) in framebuffer.iter().enumerate() {
|
||||
let offset = color_index as usize * 4;
|
||||
self.window_buffer[i] = 0xff000000
|
||||
| ((palette[offset] as u32) << 16)
|
||||
| ((palette[offset + 1] as u32) << 8)
|
||||
| palette[offset + 2] as u32;
|
||||
}
|
||||
let framebuffer_mem = &memory[120..(120 + 320 * 240)];
|
||||
let palette_mem = &memory[0x13000..];
|
||||
framebuffer.update(framebuffer_mem, palette_mem);
|
||||
|
||||
if self.window.is_key_pressed(Key::R, minifb::KeyRepeat::No) {
|
||||
if reset {
|
||||
self.load(&instance.module)?;
|
||||
} else if result.is_ok() {
|
||||
self.instance = Some(instance);
|
||||
}
|
||||
}
|
||||
|
||||
self.window
|
||||
.update_with_buffer(&self.window_buffer, 320, 240)?;
|
||||
|
||||
result?;
|
||||
Ok(())
|
||||
Ok(result?)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,17 @@ use std::time::Instant;
|
||||
use crate::Framebuffer;
|
||||
use minifb::{Key, Window, WindowOptions};
|
||||
|
||||
static GAMEPAD_KEYS: &[Key] = &[
|
||||
Key::Up,
|
||||
Key::Down,
|
||||
Key::Left,
|
||||
Key::Right,
|
||||
Key::Z,
|
||||
Key::X,
|
||||
Key::A,
|
||||
Key::S,
|
||||
];
|
||||
|
||||
pub fn run(mut update: Box<dyn FnMut(&mut dyn Framebuffer, u32, bool) -> Instant + 'static>) -> ! {
|
||||
#[cfg(target_os = "windows")]
|
||||
unsafe {
|
||||
|
||||
@@ -10,6 +10,9 @@ use winit::{
|
||||
window::{Fullscreen, WindowBuilder},
|
||||
};
|
||||
|
||||
#[cfg(unix)]
|
||||
use winit::platform::unix::EventLoopExtUnix;
|
||||
|
||||
pub struct Window {
|
||||
event_loop: EventLoop<()>,
|
||||
window: winit::window::Window,
|
||||
@@ -23,7 +26,7 @@ pub struct Window {
|
||||
impl Window {
|
||||
pub fn new() -> Result<Window> {
|
||||
async fn create() -> Result<Window> {
|
||||
let event_loop = EventLoop::new();
|
||||
let event_loop = EventLoop::new_any_thread();
|
||||
let window = WindowBuilder::new()
|
||||
.with_inner_size(PhysicalSize::new(640u32, 480))
|
||||
.with_min_inner_size(PhysicalSize::new(320u32, 240))
|
||||
|
||||
Reference in New Issue
Block a user