mirror of
https://github.com/exoticorn/microw8.git
synced 2026-01-20 11:16:42 +01:00
make both native and browser runtime optional
This commit is contained in:
15
Cargo.toml
15
Cargo.toml
@@ -5,8 +5,13 @@ edition = "2021"
|
|||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["native", "browser"]
|
||||||
|
native = ["wasmtime"]
|
||||||
|
browser = ["warp", "tokio", "tokio-stream", "webbrowser"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
wasmtime = "0.30"
|
wasmtime = { version = "0.30", optional = true }
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
minifb = { version = "0.20", default-features = false, features = ["x11"] }
|
minifb = { version = "0.20", default-features = false, features = ["x11"] }
|
||||||
notify = "4"
|
notify = "4"
|
||||||
@@ -15,7 +20,7 @@ curlywas = { git = "https://github.com/exoticorn/curlywas.git", rev = "196719b"
|
|||||||
wat = "1"
|
wat = "1"
|
||||||
uw8-tool = { path = "uw8-tool" }
|
uw8-tool = { path = "uw8-tool" }
|
||||||
same-file = "1"
|
same-file = "1"
|
||||||
warp = "0.3.2"
|
warp = { version = "0.3.2", optional = true }
|
||||||
tokio = { version = "1.17.0", features = ["sync", "rt"] }
|
tokio = { version = "1.17.0", features = ["sync", "rt"], optional = true }
|
||||||
tokio-stream = { version = "0.1.8", features = ["sync"] }
|
tokio-stream = { version = "0.1.8", features = ["sync"], optional = true }
|
||||||
webbrowser = "0.6.0"
|
webbrowser = { version = "0.6.0", optional = true }
|
||||||
278
src/lib.rs
278
src/lib.rs
@@ -1,272 +1,22 @@
|
|||||||
use std::io::prelude::*;
|
|
||||||
use std::path::Path;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
use std::time::Duration;
|
|
||||||
use std::{fs::File, thread, time::Instant};
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use minifb::{Key, Window, WindowOptions};
|
|
||||||
use wasmtime::{
|
|
||||||
Engine, GlobalType, Memory, MemoryType, Module, Mutability, Store, TypedFunc, ValType,
|
|
||||||
};
|
|
||||||
|
|
||||||
mod filewatcher;
|
mod filewatcher;
|
||||||
|
#[cfg(feature = "native")]
|
||||||
|
mod run_native;
|
||||||
|
#[cfg(feature = "browser")]
|
||||||
mod run_web;
|
mod run_web;
|
||||||
|
|
||||||
pub use filewatcher::FileWatcher;
|
pub use filewatcher::FileWatcher;
|
||||||
|
#[cfg(feature = "native")]
|
||||||
|
pub use run_native::MicroW8;
|
||||||
|
#[cfg(feature = "browser")]
|
||||||
pub use run_web::RunWebServer;
|
pub use run_web::RunWebServer;
|
||||||
|
|
||||||
static GAMEPAD_KEYS: &'static [Key] = &[
|
use anyhow::Result;
|
||||||
Key::Up,
|
|
||||||
Key::Down,
|
|
||||||
Key::Left,
|
|
||||||
Key::Right,
|
|
||||||
Key::Z,
|
|
||||||
Key::X,
|
|
||||||
Key::A,
|
|
||||||
Key::S,
|
|
||||||
];
|
|
||||||
|
|
||||||
pub struct MicroW8 {
|
pub trait Runtime {
|
||||||
engine: Engine,
|
fn is_open(&self) -> bool;
|
||||||
loader_module: Module,
|
fn set_timeout(&mut self, _timeout: u32) {
|
||||||
window: Window,
|
eprintln!("Warning: runtime doesn't support timeout");
|
||||||
window_buffer: Vec<u32>,
|
|
||||||
instance: Option<UW8Instance>,
|
|
||||||
timeout: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct UW8Instance {
|
|
||||||
store: Store<()>,
|
|
||||||
memory: Memory,
|
|
||||||
end_frame: TypedFunc<(), ()>,
|
|
||||||
update: TypedFunc<(), ()>,
|
|
||||||
start_time: Instant,
|
|
||||||
module: Vec<u8>,
|
|
||||||
watchdog: Arc<Mutex<UW8WatchDog>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for UW8Instance {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
if let Ok(mut watchdog) = self.watchdog.lock() {
|
|
||||||
watchdog.stop = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
fn load(&mut self, module_data: &[u8]) -> Result<()>;
|
||||||
|
fn run_frame(&mut self) -> Result<()>;
|
||||||
struct UW8WatchDog {
|
}
|
||||||
interupt: wasmtime::InterruptHandle,
|
|
||||||
timeout: u32,
|
|
||||||
stop: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MicroW8 {
|
|
||||||
pub fn new() -> Result<MicroW8> {
|
|
||||||
let engine = wasmtime::Engine::new(wasmtime::Config::new().interruptable(true))?;
|
|
||||||
|
|
||||||
let loader_module =
|
|
||||||
wasmtime::Module::new(&engine, include_bytes!("../platform/bin/loader.wasm"))?;
|
|
||||||
|
|
||||||
let mut options = WindowOptions::default();
|
|
||||||
options.scale = minifb::Scale::X2;
|
|
||||||
options.scale_mode = minifb::ScaleMode::AspectRatioStretch;
|
|
||||||
options.resize = true;
|
|
||||||
let mut window = Window::new("MicroW8", 320, 240, options)?;
|
|
||||||
window.limit_update_rate(Some(std::time::Duration::from_micros(16666)));
|
|
||||||
|
|
||||||
Ok(MicroW8 {
|
|
||||||
engine,
|
|
||||||
loader_module,
|
|
||||||
window,
|
|
||||||
window_buffer: vec![0u32; 320 * 240],
|
|
||||||
instance: None,
|
|
||||||
timeout: 30,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_open(&self) -> bool {
|
|
||||||
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 {
|
|
||||||
*v = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_from_file<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
|
|
||||||
self.reset();
|
|
||||||
|
|
||||||
let mut module = vec![];
|
|
||||||
File::open(path)?.read_to_end(&mut module)?;
|
|
||||||
self.load_from_memory(&module)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_from_memory(&mut self, module_data: &[u8]) -> Result<()> {
|
|
||||||
self.reset();
|
|
||||||
|
|
||||||
let mut store = wasmtime::Store::new(&self.engine, ());
|
|
||||||
|
|
||||||
let memory = wasmtime::Memory::new(&mut store, MemoryType::new(4, Some(4)))?;
|
|
||||||
|
|
||||||
let mut linker = wasmtime::Linker::new(&self.engine);
|
|
||||||
linker.define("env", "memory", memory.clone())?;
|
|
||||||
|
|
||||||
let loader_instance = linker.instantiate(&mut store, &self.loader_module)?;
|
|
||||||
let load_uw8 = loader_instance.get_typed_func::<i32, i32, _>(&mut store, "load_uw8")?;
|
|
||||||
|
|
||||||
let platform_data = include_bytes!("../platform/bin/platform.uw8");
|
|
||||||
memory.data_mut(&mut store)[..platform_data.len()].copy_from_slice(platform_data);
|
|
||||||
let platform_length =
|
|
||||||
load_uw8.call(&mut store, platform_data.len() as i32)? as u32 as usize;
|
|
||||||
let platform_module =
|
|
||||||
wasmtime::Module::new(&self.engine, &memory.data(&store)[..platform_length])?;
|
|
||||||
|
|
||||||
memory.data_mut(&mut store)[..module_data.len()].copy_from_slice(module_data);
|
|
||||||
let module_length = load_uw8.call(&mut store, module_data.len() as i32)? as u32 as usize;
|
|
||||||
let module = wasmtime::Module::new(&self.engine, &memory.data(&store)[..module_length])?;
|
|
||||||
|
|
||||||
linker.func_wrap("env", "acos", |v: f32| v.acos())?;
|
|
||||||
linker.func_wrap("env", "asin", |v: f32| v.asin())?;
|
|
||||||
linker.func_wrap("env", "atan", |v: f32| v.atan())?;
|
|
||||||
linker.func_wrap("env", "atan2", |x: f32, y: f32| x.atan2(y))?;
|
|
||||||
linker.func_wrap("env", "cos", |v: f32| v.cos())?;
|
|
||||||
linker.func_wrap("env", "exp", |v: f32| v.exp())?;
|
|
||||||
linker.func_wrap("env", "log", |v: f32| v.ln())?;
|
|
||||||
linker.func_wrap("env", "sin", |v: f32| v.sin())?;
|
|
||||||
linker.func_wrap("env", "tan", |v: f32| v.tan())?;
|
|
||||||
linker.func_wrap("env", "pow", |a: f32, b: f32| a.powf(b))?;
|
|
||||||
for i in 9..64 {
|
|
||||||
linker.func_wrap("env", &format!("reserved{}", i), || {})?;
|
|
||||||
}
|
|
||||||
for i in 0..16 {
|
|
||||||
linker.define(
|
|
||||||
"env",
|
|
||||||
&format!("g_reserved{}", i),
|
|
||||||
wasmtime::Global::new(
|
|
||||||
&mut store,
|
|
||||||
GlobalType::new(ValType::I32, Mutability::Const),
|
|
||||||
0.into(),
|
|
||||||
)?,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let platform_instance = linker.instantiate(&mut store, &platform_module)?;
|
|
||||||
|
|
||||||
for export in platform_instance.exports(&mut store) {
|
|
||||||
linker.define(
|
|
||||||
"env",
|
|
||||||
export.name(),
|
|
||||||
export
|
|
||||||
.into_func()
|
|
||||||
.expect("platform surely only exports functions"),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
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")?;
|
|
||||||
|
|
||||||
self.instance = Some(UW8Instance {
|
|
||||||
store,
|
|
||||||
memory,
|
|
||||||
end_frame,
|
|
||||||
update,
|
|
||||||
start_time: Instant::now(),
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
let framebuffer = &memory[120..];
|
|
||||||
let palette = &memory[0x13000..];
|
|
||||||
for i in 0..320 * 240 {
|
|
||||||
let offset = framebuffer[i] as usize * 4;
|
|
||||||
self.window_buffer[i] = 0xff000000
|
|
||||||
| ((palette[offset + 0] as u32) << 16)
|
|
||||||
| ((palette[offset + 1] as u32) << 8)
|
|
||||||
| palette[offset + 2] as u32;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.window.is_key_pressed(Key::R, minifb::KeyRepeat::No) {
|
|
||||||
self.load_from_memory(&instance.module)?;
|
|
||||||
} else if result.is_ok() {
|
|
||||||
self.instance = Some(instance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.window
|
|
||||||
.update_with_buffer(&self.window_buffer, 320, 240)?;
|
|
||||||
|
|
||||||
result?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
107
src/main.rs
107
src/main.rs
@@ -1,24 +1,26 @@
|
|||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
use std::process;
|
use std::process;
|
||||||
use std::time::Duration;
|
|
||||||
use std::{
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
process::exit,
|
|
||||||
};
|
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use pico_args::Arguments;
|
use pico_args::Arguments;
|
||||||
use uw8::{FileWatcher, MicroW8, RunWebServer};
|
#[cfg(feature = "native")]
|
||||||
|
use uw8::MicroW8;
|
||||||
|
#[cfg(feature = "browser")]
|
||||||
|
use uw8::RunWebServer;
|
||||||
|
#[cfg(any(feature = "native", feature = "browser"))]
|
||||||
|
use uw8::Runtime;
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let mut args = Arguments::from_env();
|
let mut args = Arguments::from_env();
|
||||||
|
|
||||||
match args.subcommand()?.as_ref().map(|s| s.as_str()) {
|
match args.subcommand()?.as_deref() {
|
||||||
Some("version") => {
|
Some("version") => {
|
||||||
println!("{}", env!("CARGO_PKG_VERSION"));
|
println!("{}", env!("CARGO_PKG_VERSION"));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
#[cfg(any(feature = "native", feature = "browser"))]
|
||||||
Some("run") => run(args),
|
Some("run") => run(args),
|
||||||
Some("pack") => pack(args),
|
Some("pack") => pack(args),
|
||||||
Some("unpack") => unpack(args),
|
Some("unpack") => unpack(args),
|
||||||
@@ -28,7 +30,8 @@ fn main() -> Result<()> {
|
|||||||
println!("uw8 {}", env!("CARGO_PKG_VERSION"));
|
println!("uw8 {}", env!("CARGO_PKG_VERSION"));
|
||||||
println!();
|
println!();
|
||||||
println!("Usage:");
|
println!("Usage:");
|
||||||
println!(" uw8 run [-t/--timeout <frames>] [-w/--watch] [-p/--pack] [-u/--uncompressed] [-l/--level] [-o/--output <out-file>] <file>");
|
#[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 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>");
|
||||||
@@ -42,6 +45,7 @@ fn main() -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(any(feature = "native", feature = "browser"))]
|
||||||
fn run(mut args: Arguments) -> Result<()> {
|
fn run(mut args: Arguments) -> Result<()> {
|
||||||
let watch_mode = args.contains(["-w", "--watch"]);
|
let watch_mode = args.contains(["-w", "--watch"]);
|
||||||
let timeout: Option<u32> = args.opt_value_from_str(["-t", "--timeout"])?;
|
let timeout: Option<u32> = args.opt_value_from_str(["-t", "--timeout"])?;
|
||||||
@@ -66,11 +70,14 @@ fn run(mut args: Arguments) -> Result<()> {
|
|||||||
config.output_path = Some(path);
|
config.output_path = Some(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "native")]
|
||||||
let run_browser = args.contains(["-b", "--browser"]);
|
let run_browser = args.contains(["-b", "--browser"]);
|
||||||
|
#[cfg(not(feature = "native"))]
|
||||||
|
let run_browser = args.contains(["-b", "--browser"]) || true;
|
||||||
|
|
||||||
let filename = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
|
let filename = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
|
||||||
|
|
||||||
let mut watcher = FileWatcher::builder();
|
let mut watcher = uw8::FileWatcher::builder();
|
||||||
|
|
||||||
if watch_mode {
|
if watch_mode {
|
||||||
watcher.add_file(&filename);
|
watcher.add_file(&filename);
|
||||||
@@ -78,57 +85,44 @@ fn run(mut args: Arguments) -> Result<()> {
|
|||||||
|
|
||||||
let watcher = watcher.build()?;
|
let watcher = watcher.build()?;
|
||||||
|
|
||||||
if !run_browser {
|
use std::process::exit;
|
||||||
let mut uw8 = MicroW8::new()?;
|
|
||||||
|
|
||||||
if let Some(timeout) = timeout {
|
let mut runtime: Box<dyn Runtime> = if !run_browser {
|
||||||
uw8.set_timeout(timeout);
|
#[cfg(not(feature = "native"))]
|
||||||
|
unimplemented!();
|
||||||
|
#[cfg(feature = "native")]
|
||||||
|
Box::new(MicroW8::new()?)
|
||||||
|
} else {
|
||||||
|
#[cfg(not(feature = "browser"))]
|
||||||
|
unimplemented!();
|
||||||
|
#[cfg(feature = "browser")]
|
||||||
|
Box::new(RunWebServer::new())
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(timeout) = timeout {
|
||||||
|
runtime.set_timeout(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(err) = start_cart(&filename, &mut *runtime, &config) {
|
||||||
|
eprintln!("Load error: {}", err);
|
||||||
|
if !watch_mode {
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while runtime.is_open() {
|
||||||
|
if watcher.poll_changed_file()?.is_some() {
|
||||||
|
if let Err(err) = start_cart(&filename, &mut *runtime, &config) {
|
||||||
|
eprintln!("Load error: {}", err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(err) = start_cart(&filename, &mut uw8, &config) {
|
if let Err(err) = runtime.run_frame() {
|
||||||
eprintln!("Load error: {}", err);
|
eprintln!("Runtime error: {}", err);
|
||||||
if !watch_mode {
|
if !watch_mode {
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while uw8.is_open() {
|
|
||||||
if watcher.poll_changed_file()?.is_some() {
|
|
||||||
if let Err(err) = start_cart(&filename, &mut uw8, &config) {
|
|
||||||
eprintln!("Load error: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(err) = uw8.run_frame() {
|
|
||||||
eprintln!("Runtime error: {}", err);
|
|
||||||
if !watch_mode {
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let mut server = RunWebServer::new();
|
|
||||||
match load_cart(&filename, &config) {
|
|
||||||
Ok(cart) => server.load_module(&cart)?,
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!("Load error: {}", err);
|
|
||||||
if !watch_mode {
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loop {
|
|
||||||
if watcher.poll_changed_file()?.is_some() {
|
|
||||||
match load_cart(&filename, &config) {
|
|
||||||
Ok(cart) => server.load_module(&cart)?,
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!("Load error: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
std::thread::sleep(Duration::from_millis(100));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -165,10 +159,11 @@ fn load_cart(filename: &Path, config: &Config) -> Result<Vec<u8>> {
|
|||||||
Ok(cart)
|
Ok(cart)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_cart(filename: &Path, uw8: &mut MicroW8, config: &Config) -> Result<()> {
|
#[cfg(any(feature = "native", feature = "browser"))]
|
||||||
|
fn start_cart(filename: &Path, runtime: &mut dyn Runtime, config: &Config) -> Result<()> {
|
||||||
let cart = load_cart(filename, config)?;
|
let cart = load_cart(filename, config)?;
|
||||||
|
|
||||||
if let Err(err) = uw8.load_from_memory(&cart) {
|
if let Err(err) = runtime.load(&cart) {
|
||||||
eprintln!("Load error: {}", err);
|
eprintln!("Load error: {}", err);
|
||||||
Err(err)
|
Err(err)
|
||||||
} else {
|
} else {
|
||||||
@@ -208,7 +203,7 @@ fn unpack(mut args: Arguments) -> Result<()> {
|
|||||||
let in_file = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
|
let in_file = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
|
||||||
let out_file = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
|
let out_file = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
|
||||||
|
|
||||||
uw8_tool::unpack_file(&in_file, &out_file).into()
|
uw8_tool::unpack_file(&in_file, &out_file)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile(mut args: Arguments) -> Result<()> {
|
fn compile(mut args: Arguments) -> Result<()> {
|
||||||
|
|||||||
260
src/run_native.rs
Normal file
260
src/run_native.rs
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::time::Duration;
|
||||||
|
use std::{thread, time::Instant};
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use minifb::{Key, Window, WindowOptions};
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UW8Instance {
|
||||||
|
store: Store<()>,
|
||||||
|
memory: Memory,
|
||||||
|
end_frame: TypedFunc<(), ()>,
|
||||||
|
update: TypedFunc<(), ()>,
|
||||||
|
start_time: Instant,
|
||||||
|
module: Vec<u8>,
|
||||||
|
watchdog: Arc<Mutex<UW8WatchDog>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<MicroW8> {
|
||||||
|
let engine = wasmtime::Engine::new(wasmtime::Config::new().interruptable(true))?;
|
||||||
|
|
||||||
|
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 mut window = Window::new("MicroW8", 320, 240, options)?;
|
||||||
|
window.limit_update_rate(Some(std::time::Duration::from_micros(16666)));
|
||||||
|
|
||||||
|
Ok(MicroW8 {
|
||||||
|
engine,
|
||||||
|
loader_module,
|
||||||
|
window,
|
||||||
|
window_buffer: vec![0u32; 320 * 240],
|
||||||
|
instance: None,
|
||||||
|
timeout: 30,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&mut self) {
|
||||||
|
self.instance = None;
|
||||||
|
for v in &mut self.window_buffer {
|
||||||
|
*v = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl super::Runtime for MicroW8 {
|
||||||
|
fn is_open(&self) -> bool {
|
||||||
|
self.window.is_open() && !self.window.is_key_down(Key::Escape)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_timeout(&mut self, timeout: u32) {
|
||||||
|
self.timeout = timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load(&mut self, module_data: &[u8]) -> Result<()> {
|
||||||
|
self.reset();
|
||||||
|
|
||||||
|
let mut store = wasmtime::Store::new(&self.engine, ());
|
||||||
|
|
||||||
|
let memory = wasmtime::Memory::new(&mut store, MemoryType::new(4, Some(4)))?;
|
||||||
|
|
||||||
|
let mut linker = wasmtime::Linker::new(&self.engine);
|
||||||
|
linker.define("env", "memory", memory)?;
|
||||||
|
|
||||||
|
let loader_instance = linker.instantiate(&mut store, &self.loader_module)?;
|
||||||
|
let load_uw8 = loader_instance.get_typed_func::<i32, i32, _>(&mut store, "load_uw8")?;
|
||||||
|
|
||||||
|
let platform_data = include_bytes!("../platform/bin/platform.uw8");
|
||||||
|
memory.data_mut(&mut store)[..platform_data.len()].copy_from_slice(platform_data);
|
||||||
|
let platform_length =
|
||||||
|
load_uw8.call(&mut store, platform_data.len() as i32)? as u32 as usize;
|
||||||
|
let platform_module =
|
||||||
|
wasmtime::Module::new(&self.engine, &memory.data(&store)[..platform_length])?;
|
||||||
|
|
||||||
|
memory.data_mut(&mut store)[..module_data.len()].copy_from_slice(module_data);
|
||||||
|
let module_length = load_uw8.call(&mut store, module_data.len() as i32)? as u32 as usize;
|
||||||
|
let module = wasmtime::Module::new(&self.engine, &memory.data(&store)[..module_length])?;
|
||||||
|
|
||||||
|
linker.func_wrap("env", "acos", |v: f32| v.acos())?;
|
||||||
|
linker.func_wrap("env", "asin", |v: f32| v.asin())?;
|
||||||
|
linker.func_wrap("env", "atan", |v: f32| v.atan())?;
|
||||||
|
linker.func_wrap("env", "atan2", |x: f32, y: f32| x.atan2(y))?;
|
||||||
|
linker.func_wrap("env", "cos", |v: f32| v.cos())?;
|
||||||
|
linker.func_wrap("env", "exp", |v: f32| v.exp())?;
|
||||||
|
linker.func_wrap("env", "log", |v: f32| v.ln())?;
|
||||||
|
linker.func_wrap("env", "sin", |v: f32| v.sin())?;
|
||||||
|
linker.func_wrap("env", "tan", |v: f32| v.tan())?;
|
||||||
|
linker.func_wrap("env", "pow", |a: f32, b: f32| a.powf(b))?;
|
||||||
|
for i in 9..64 {
|
||||||
|
linker.func_wrap("env", &format!("reserved{}", i), || {})?;
|
||||||
|
}
|
||||||
|
for i in 0..16 {
|
||||||
|
linker.define(
|
||||||
|
"env",
|
||||||
|
&format!("g_reserved{}", i),
|
||||||
|
wasmtime::Global::new(
|
||||||
|
&mut store,
|
||||||
|
GlobalType::new(ValType::I32, Mutability::Const),
|
||||||
|
0.into(),
|
||||||
|
)?,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let platform_instance = linker.instantiate(&mut store, &platform_module)?;
|
||||||
|
|
||||||
|
for export in platform_instance.exports(&mut store) {
|
||||||
|
linker.define(
|
||||||
|
"env",
|
||||||
|
export.name(),
|
||||||
|
export
|
||||||
|
.into_func()
|
||||||
|
.expect("platform surely only exports functions"),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
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")?;
|
||||||
|
|
||||||
|
self.instance = Some(UW8Instance {
|
||||||
|
store,
|
||||||
|
memory,
|
||||||
|
end_frame,
|
||||||
|
update,
|
||||||
|
start_time: Instant::now(),
|
||||||
|
module: module_data.into(),
|
||||||
|
watchdog,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.window.is_key_pressed(Key::R, minifb::KeyRepeat::No) {
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use std::{
|
use std::{
|
||||||
|
net::SocketAddr,
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
thread,
|
thread,
|
||||||
};
|
};
|
||||||
@@ -46,18 +47,23 @@ impl RunWebServer {
|
|||||||
warp::sse::reply(warp::sse::keep_alive().stream(event_stream(&server_tx)))
|
warp::sse::reply(warp::sse::keep_alive().stream(event_stream(&server_tx)))
|
||||||
});
|
});
|
||||||
|
|
||||||
let server_future =
|
let socket_addr = "127.0.0.1:3030"
|
||||||
warp::serve(html.or(cart).or(events)).bind(([127, 0, 0, 1], 3030));
|
.parse::<SocketAddr>()
|
||||||
println!("Point browser at 127.0.0.1:3030");
|
.expect("Failed to parse socket address");
|
||||||
let _ignore_result = webbrowser::open("http://127.0.0.1:3030");
|
|
||||||
|
let server_future = warp::serve(html.or(cart).or(events)).bind(socket_addr);
|
||||||
|
println!("Point browser at http://{}", socket_addr);
|
||||||
|
let _ignore_result = webbrowser::open(&format!("http://{}", socket_addr));
|
||||||
server_future.await
|
server_future.await
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
RunWebServer { cart, tx }
|
RunWebServer { cart, tx }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn load_module(&mut self, module_data: &[u8]) -> Result<()> {
|
impl super::Runtime for RunWebServer {
|
||||||
|
fn load(&mut self, module_data: &[u8]) -> Result<()> {
|
||||||
if let Ok(mut lock) = self.cart.lock() {
|
if let Ok(mut lock) = self.cart.lock() {
|
||||||
lock.clear();
|
lock.clear();
|
||||||
lock.extend_from_slice(module_data);
|
lock.extend_from_slice(module_data);
|
||||||
@@ -65,4 +71,19 @@ impl RunWebServer {
|
|||||||
let _ignore_result = self.tx.send(());
|
let _ignore_result = self.tx.send(());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_open(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_frame(&mut self) -> Result<()> {
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for RunWebServer {
|
||||||
|
fn default() -> RunWebServer {
|
||||||
|
RunWebServer::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
13
test.cwa
Normal file
13
test.cwa
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import "env.memory" memory(4);
|
||||||
|
import "env.printString" fn print(i32);
|
||||||
|
|
||||||
|
export fn upd() {
|
||||||
|
}
|
||||||
|
|
||||||
|
start fn start() {
|
||||||
|
print(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
data 0 {
|
||||||
|
"Press " i8(0xe0) " and " i8(0xe1) " to adjust, " i8(0xcc) " to commit." i8(0)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user