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
|
||||
|
||||
[features]
|
||||
default = ["native", "browser"]
|
||||
native = ["wasmtime"]
|
||||
browser = ["warp", "tokio", "tokio-stream", "webbrowser"]
|
||||
|
||||
[dependencies]
|
||||
wasmtime = "0.30"
|
||||
wasmtime = { version = "0.30", optional = true }
|
||||
anyhow = "1"
|
||||
minifb = { version = "0.20", default-features = false, features = ["x11"] }
|
||||
notify = "4"
|
||||
@@ -15,7 +20,7 @@ curlywas = { git = "https://github.com/exoticorn/curlywas.git", rev = "196719b"
|
||||
wat = "1"
|
||||
uw8-tool = { path = "uw8-tool" }
|
||||
same-file = "1"
|
||||
warp = "0.3.2"
|
||||
tokio = { version = "1.17.0", features = ["sync", "rt"] }
|
||||
tokio-stream = { version = "0.1.8", features = ["sync"] }
|
||||
webbrowser = "0.6.0"
|
||||
warp = { version = "0.3.2", optional = true }
|
||||
tokio = { version = "1.17.0", features = ["sync", "rt"], optional = true }
|
||||
tokio-stream = { version = "0.1.8", features = ["sync"], optional = true }
|
||||
webbrowser = { version = "0.6.0", optional = true }
|
||||
276
src/lib.rs
276
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;
|
||||
#[cfg(feature = "native")]
|
||||
mod run_native;
|
||||
#[cfg(feature = "browser")]
|
||||
mod run_web;
|
||||
|
||||
pub use filewatcher::FileWatcher;
|
||||
#[cfg(feature = "native")]
|
||||
pub use run_native::MicroW8;
|
||||
#[cfg(feature = "browser")]
|
||||
pub use run_web::RunWebServer;
|
||||
|
||||
static GAMEPAD_KEYS: &'static [Key] = &[
|
||||
Key::Up,
|
||||
Key::Down,
|
||||
Key::Left,
|
||||
Key::Right,
|
||||
Key::Z,
|
||||
Key::X,
|
||||
Key::A,
|
||||
Key::S,
|
||||
];
|
||||
use anyhow::Result;
|
||||
|
||||
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 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(())
|
||||
pub trait Runtime {
|
||||
fn is_open(&self) -> bool;
|
||||
fn set_timeout(&mut self, _timeout: u32) {
|
||||
eprintln!("Warning: runtime doesn't support timeout");
|
||||
}
|
||||
fn load(&mut self, module_data: &[u8]) -> Result<()>;
|
||||
fn run_frame(&mut self) -> Result<()>;
|
||||
}
|
||||
81
src/main.rs
81
src/main.rs
@@ -1,24 +1,26 @@
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process;
|
||||
use std::time::Duration;
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
process::exit,
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
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<()> {
|
||||
let mut args = Arguments::from_env();
|
||||
|
||||
match args.subcommand()?.as_ref().map(|s| s.as_str()) {
|
||||
match args.subcommand()?.as_deref() {
|
||||
Some("version") => {
|
||||
println!("{}", env!("CARGO_PKG_VERSION"));
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(any(feature = "native", feature = "browser"))]
|
||||
Some("run") => run(args),
|
||||
Some("pack") => pack(args),
|
||||
Some("unpack") => unpack(args),
|
||||
@@ -28,7 +30,8 @@ fn main() -> Result<()> {
|
||||
println!("uw8 {}", env!("CARGO_PKG_VERSION"));
|
||||
println!();
|
||||
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 unpack <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<()> {
|
||||
let watch_mode = args.contains(["-w", "--watch"]);
|
||||
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);
|
||||
}
|
||||
|
||||
#[cfg(feature = "native")]
|
||||
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 mut watcher = FileWatcher::builder();
|
||||
let mut watcher = uw8::FileWatcher::builder();
|
||||
|
||||
if watch_mode {
|
||||
watcher.add_file(&filename);
|
||||
@@ -78,58 +85,45 @@ fn run(mut args: Arguments) -> Result<()> {
|
||||
|
||||
let watcher = watcher.build()?;
|
||||
|
||||
if !run_browser {
|
||||
let mut uw8 = MicroW8::new()?;
|
||||
use std::process::exit;
|
||||
|
||||
let mut runtime: Box<dyn Runtime> = if !run_browser {
|
||||
#[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 {
|
||||
uw8.set_timeout(timeout);
|
||||
runtime.set_timeout(timeout);
|
||||
}
|
||||
|
||||
if let Err(err) = start_cart(&filename, &mut uw8, &config) {
|
||||
if let Err(err) = start_cart(&filename, &mut *runtime, &config) {
|
||||
eprintln!("Load error: {}", err);
|
||||
if !watch_mode {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
while uw8.is_open() {
|
||||
while runtime.is_open() {
|
||||
if watcher.poll_changed_file()?.is_some() {
|
||||
if let Err(err) = start_cart(&filename, &mut uw8, &config) {
|
||||
if let Err(err) = start_cart(&filename, &mut *runtime, &config) {
|
||||
eprintln!("Load error: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(err) = uw8.run_frame() {
|
||||
if let Err(err) = runtime.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(())
|
||||
}
|
||||
@@ -165,10 +159,11 @@ fn load_cart(filename: &Path, config: &Config) -> Result<Vec<u8>> {
|
||||
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)?;
|
||||
|
||||
if let Err(err) = uw8.load_from_memory(&cart) {
|
||||
if let Err(err) = runtime.load(&cart) {
|
||||
eprintln!("Load error: {}", err);
|
||||
Err(err)
|
||||
} 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 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<()> {
|
||||
|
||||
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 std::{
|
||||
net::SocketAddr,
|
||||
sync::{Arc, Mutex},
|
||||
thread,
|
||||
};
|
||||
@@ -46,18 +47,23 @@ impl RunWebServer {
|
||||
warp::sse::reply(warp::sse::keep_alive().stream(event_stream(&server_tx)))
|
||||
});
|
||||
|
||||
let server_future =
|
||||
warp::serve(html.or(cart).or(events)).bind(([127, 0, 0, 1], 3030));
|
||||
println!("Point browser at 127.0.0.1:3030");
|
||||
let _ignore_result = webbrowser::open("http://127.0.0.1:3030");
|
||||
let socket_addr = "127.0.0.1:3030"
|
||||
.parse::<SocketAddr>()
|
||||
.expect("Failed to parse socket address");
|
||||
|
||||
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
|
||||
});
|
||||
});
|
||||
|
||||
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() {
|
||||
lock.clear();
|
||||
lock.extend_from_slice(module_data);
|
||||
@@ -65,4 +71,19 @@ impl RunWebServer {
|
||||
let _ignore_result = self.tx.send(());
|
||||
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