diff --git a/crates/editor/src/main.rs b/crates/editor/src/main.rs index 84cbba2..2b15cc4 100644 --- a/crates/editor/src/main.rs +++ b/crates/editor/src/main.rs @@ -7,8 +7,17 @@ use crate::go_builder::GoBuilder; mod go_builder; fn main() -> VResult<()> { - let builder = GoBuilder::new(&PathBuf::from("example_project"))?; + let builder = GoBuilder::new( + &PathBuf::from("example_project"))?; let wasm = builder.build()?; - player::run_entry_point(&wasm)?; + let mut executor = player::Executor::new(&wasm)?; + + while executor.is_running() { + println!("update started"); + executor.update(); + println!("update completed"); + executor.get_error()?; + } + Ok(()) } diff --git a/crates/player/src/engine_api.rs b/crates/player/src/engine_api.rs new file mode 100644 index 0000000..2cf295c --- /dev/null +++ b/crates/player/src/engine_api.rs @@ -0,0 +1,31 @@ +use std::fmt::Display; + +use viperid::VResult; +use wasmi::{core::{HostError, Trap}, Caller, Linker}; + +use crate::executor::ExecutorState; + +pub(crate) fn integrate(linker: &mut Linker) -> VResult<()> { + linker.func_wrap("viperid", "YieldFrame", yield_frame)?; + + Ok(()) +} + +fn yield_frame(caller: Caller) -> Result<(), Trap> { + Err(Trap::from(YieldFrame {})) +} + +#[derive(Debug)] +pub(crate) struct YieldFrame { + +} + +impl Display for YieldFrame { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("") + } +} + +impl HostError for YieldFrame { + +} diff --git a/crates/player/src/executor.rs b/crates/player/src/executor.rs index 051bd3a..8df5794 100644 --- a/crates/player/src/executor.rs +++ b/crates/player/src/executor.rs @@ -1,9 +1,16 @@ +use std::{fmt::Display, mem}; + use viperid::VResult; -use wasmi::{Engine, Linker, Module, Store}; +use wasmi::{core::{HostError, Trap}, Engine, Linker, Module, Store, TypedFunc, TypedResumableCall, TypedResumableInvocation}; -use crate::wasi::{self, StockWasi}; +use crate::{engine_api::{self, YieldFrame}, wasi::{self, StockWasi}}; -struct ExecutorState { +pub struct Executor { + store: Store, + resumable: Resumable +} + +pub(crate) struct ExecutorState { wasi: wasi::StockWasi } @@ -16,22 +23,105 @@ impl ExecutorState { } } -pub fn run_entry_point(wasm: &[u8]) -> VResult<()> { - let engine = Engine::default(); - let module = Module::new(&engine, wasm)?; +impl Executor { + pub fn new(wasm: &[u8]) -> VResult { + let engine = Engine::default(); - let mut store = Store::new(&engine, ExecutorState::new()); + let module = Module::new(&engine, wasm)?; - let mut linker = >::new(&engine); - wasi::integrate(&mut linker, |hs| &mut hs.wasi)?; + let mut store = Store::new(&engine, ExecutorState::new()); - let instance = linker - .instantiate(&mut store, &module)? - .start(&mut store)?; + let mut linker = >::new(&engine); + wasi::integrate(&mut linker, |hs| &mut hs.wasi)?; + engine_api::integrate(&mut linker); - let main = instance.get_typed_func::<(), ()>(&store, "_start")?; - main.call(&mut store, ())?; + let instance = linker + .instantiate(&mut store, &module)? + .start(&mut store)?; - Ok(()) -} \ No newline at end of file + let main = instance.get_typed_func::<(), ()>(&store, "_start")?; + + Ok(Executor { + store, + resumable: Resumable::Unstarted(main) + }) + } + + pub fn is_running(&self) -> bool { + self.resumable.is_running() + } + + pub fn get_error(&self) -> VResult<()> { + if let Err(e) = self.resumable.get_error() { + anyhow::bail!("executor error: {}", e); + } + Ok(()) + } + + pub fn update(&mut self) { + let mut tmp = Resumable::Failed(wasmi::Error::Trap(Trap::new("unexpected crash during update"))); + mem::swap(&mut tmp, &mut self.resumable); + let mut tmp2 = tmp.resume(&mut self.store); + mem::swap(&mut tmp2, &mut self.resumable); + } +} + +enum Resumable { + Unstarted(TypedFunc<(), ()>), + Paused(TypedResumableInvocation<()>), + Failed(wasmi::Error), + Completed(()) +} + + +impl Resumable { + pub fn resume(self, store: &mut Store) -> Resumable { + let result: Result, wasmi::Error> = match self { + Resumable::Failed(f) => return Resumable::Failed(f), + Resumable::Completed(c) => return Resumable::Completed(c), + Resumable::Unstarted(u) => { + match u.call_resumable(store, ()) { + Ok(o) => Ok(o), + Err(e) => Err(wasmi::Error::Trap(e)) + } + } + Resumable::Paused(p) => { + let trap = p.host_error(); + + if let Some(_) = trap.downcast_ref::() { + p.resume(store, &[]) + } else { + Err(wasmi::Error::Trap(Trap::new(format!("unexpected host error: {}", trap)))) + } + } + }; + + match result { + Ok(o) => match o { + TypedResumableCall::Finished(f) => Resumable::Completed(f), + TypedResumableCall::Resumable(p) => Resumable::Paused(p) + } + Err(e) => Resumable::Failed(e), + } + } + + fn is_running(&self) -> bool { + match self { + Resumable::Unstarted(_) => true, + Resumable::Paused(_) => true, + Resumable::Failed(_) => false, + Resumable::Completed(_) => false + } + } + + fn get_error(&self) -> Result<(), &wasmi::Error> { + match self { + Resumable::Unstarted(_) => (), + Resumable::Paused(_) => (), + Resumable::Failed(e) => return Err(e), + Resumable::Completed(_) => () + }; + Ok(()) + } +} diff --git a/crates/player/src/lib.rs b/crates/player/src/lib.rs index 2b1f410..70a82d6 100644 --- a/crates/player/src/lib.rs +++ b/crates/player/src/lib.rs @@ -1,4 +1,5 @@ +mod engine_api; mod executor; mod wasi; -pub use executor::run_entry_point; \ No newline at end of file +pub use executor::Executor; \ No newline at end of file diff --git a/example_project/.gitignore b/example_project/.gitignore index e69de29..d163863 100644 --- a/example_project/.gitignore +++ b/example_project/.gitignore @@ -0,0 +1 @@ +build/ \ No newline at end of file diff --git a/example_project/build/game.wasm b/example_project/build/game.wasm deleted file mode 100644 index e6f815a..0000000 Binary files a/example_project/build/game.wasm and /dev/null differ diff --git a/example_project/go.work b/example_project/go.work new file mode 100644 index 0000000..b777406 --- /dev/null +++ b/example_project/go.work @@ -0,0 +1 @@ +go 1.21.5 diff --git a/example_project/main.go b/example_project/main.go index e2a5282..a551b99 100644 --- a/example_project/main.go +++ b/example_project/main.go @@ -2,7 +2,6 @@ package main import ( "fmt" - "runtime/debug" ) func main() { @@ -10,7 +9,13 @@ func main() { for { fmt.Printf("Hello! %d", count) count++ - debug.PrintStack() - panic("pizza!") + /* + debug.PrintStack() + */ + YieldFrame() } } + +//go:wasmimport viperid YieldFrame +//go:noescape +func YieldFrame()