YieldFrame: pause wasm environment till end of frame
This commit is contained in:
parent
13b61a6def
commit
03a8ae9a24
@ -7,8 +7,17 @@ use crate::go_builder::GoBuilder;
|
|||||||
mod go_builder;
|
mod go_builder;
|
||||||
|
|
||||||
fn main() -> VResult<()> {
|
fn main() -> VResult<()> {
|
||||||
let builder = GoBuilder::new(&PathBuf::from("example_project"))?;
|
let builder = GoBuilder::new(
|
||||||
|
&PathBuf::from("example_project"))?;
|
||||||
let wasm = builder.build()?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
31
crates/player/src/engine_api.rs
Normal file
31
crates/player/src/engine_api.rs
Normal file
@ -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<ExecutorState>) -> VResult<()> {
|
||||||
|
linker.func_wrap("viperid", "YieldFrame", yield_frame)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn yield_frame(caller: Caller<ExecutorState>) -> 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("<device wants to display a frame>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HostError for YieldFrame {
|
||||||
|
|
||||||
|
}
|
@ -1,9 +1,16 @@
|
|||||||
|
use std::{fmt::Display, mem};
|
||||||
|
|
||||||
use viperid::VResult;
|
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<ExecutorState>,
|
||||||
|
resumable: Resumable
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct ExecutorState {
|
||||||
wasi: wasi::StockWasi
|
wasi: wasi::StockWasi
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -16,7 +23,9 @@ impl ExecutorState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_entry_point(wasm: &[u8]) -> VResult<()> {
|
|
||||||
|
impl Executor {
|
||||||
|
pub fn new(wasm: &[u8]) -> VResult<Self> {
|
||||||
let engine = Engine::default();
|
let engine = Engine::default();
|
||||||
|
|
||||||
let module = Module::new(&engine, wasm)?;
|
let module = Module::new(&engine, wasm)?;
|
||||||
@ -25,13 +34,94 @@ pub fn run_entry_point(wasm: &[u8]) -> VResult<()> {
|
|||||||
|
|
||||||
let mut linker = <Linker<ExecutorState>>::new(&engine);
|
let mut linker = <Linker<ExecutorState>>::new(&engine);
|
||||||
wasi::integrate(&mut linker, |hs| &mut hs.wasi)?;
|
wasi::integrate(&mut linker, |hs| &mut hs.wasi)?;
|
||||||
|
engine_api::integrate(&mut linker);
|
||||||
|
|
||||||
let instance = linker
|
let instance = linker
|
||||||
.instantiate(&mut store, &module)?
|
.instantiate(&mut store, &module)?
|
||||||
.start(&mut store)?;
|
.start(&mut store)?;
|
||||||
|
|
||||||
let main = instance.get_typed_func::<(), ()>(&store, "_start")?;
|
let main = instance.get_typed_func::<(), ()>(&store, "_start")?;
|
||||||
main.call(&mut store, ())?;
|
|
||||||
|
|
||||||
|
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(())
|
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<ExecutorState>) -> Resumable {
|
||||||
|
let result: Result<TypedResumableCall<()>, 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::<YieldFrame>() {
|
||||||
|
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(())
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,4 +1,5 @@
|
|||||||
|
mod engine_api;
|
||||||
mod executor;
|
mod executor;
|
||||||
mod wasi;
|
mod wasi;
|
||||||
|
|
||||||
pub use executor::run_entry_point;
|
pub use executor::Executor;
|
1
example_project/.gitignore
vendored
1
example_project/.gitignore
vendored
@ -0,0 +1 @@
|
|||||||
|
build/
|
Binary file not shown.
1
example_project/go.work
Normal file
1
example_project/go.work
Normal file
@ -0,0 +1 @@
|
|||||||
|
go 1.21.5
|
@ -2,7 +2,6 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime/debug"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -10,7 +9,13 @@ func main() {
|
|||||||
for {
|
for {
|
||||||
fmt.Printf("Hello! %d", count)
|
fmt.Printf("Hello! %d", count)
|
||||||
count++
|
count++
|
||||||
|
/*
|
||||||
debug.PrintStack()
|
debug.PrintStack()
|
||||||
panic("pizza!")
|
*/
|
||||||
|
YieldFrame()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:wasmimport viperid YieldFrame
|
||||||
|
//go:noescape
|
||||||
|
func YieldFrame()
|
||||||
|
Loading…
Reference in New Issue
Block a user