202 lines
5.8 KiB
Rust
202 lines
5.8 KiB
Rust
pub use canary_types::*;
|
|
use parking_lot::Mutex;
|
|
|
|
/// Low-level script API callbacks.
|
|
pub trait ScriptAbi {
|
|
fn start_draw(&self);
|
|
fn draw_indexed(&self, vertices: &[MeshVertex], indices: &[MeshIndex]);
|
|
fn with_draw_commands(&self, f: impl FnOnce(&[DrawCommand]));
|
|
}
|
|
|
|
pub trait ScriptInstance {
|
|
fn update(&mut self, dt: f32);
|
|
fn draw(&mut self, f: impl FnOnce(&[DrawCommand]));
|
|
fn on_cursor_event(&mut self, kind: CursorEventKind, at: Vec2);
|
|
}
|
|
|
|
#[non_exhaustive]
|
|
pub enum DrawCommand {
|
|
Mesh {
|
|
vertices: Vec<MeshVertex>,
|
|
indices: Vec<MeshIndex>,
|
|
},
|
|
}
|
|
|
|
pub struct WasmtimeRuntime {
|
|
engine: wasmtime::Engine,
|
|
}
|
|
|
|
impl WasmtimeRuntime {
|
|
pub fn new() -> anyhow::Result<Self> {
|
|
let mut config = wasmtime::Config::new();
|
|
config.wasm_simd(true);
|
|
config.wasm_bulk_memory(true);
|
|
config.cranelift_opt_level(wasmtime::OptLevel::Speed);
|
|
|
|
let engine = wasmtime::Engine::new(&config)?;
|
|
|
|
Ok(Self { engine })
|
|
}
|
|
|
|
pub fn load_module<T: ScriptAbi>(
|
|
&self,
|
|
abi: T,
|
|
module: &[u8],
|
|
) -> anyhow::Result<WasmtimeScript<T>> {
|
|
let module = wasmtime::Module::new(&self.engine, module)?;
|
|
let mut store = wasmtime::Store::new(&self.engine, abi);
|
|
let mut linker = wasmtime::Linker::new(&self.engine);
|
|
WasmtimeScript::link(&mut linker)?;
|
|
let instance = linker.instantiate(&mut store, &module)?;
|
|
let update = instance.get_typed_func(&mut store, "update")?;
|
|
let draw = instance.get_typed_func(&mut store, "draw")?;
|
|
let on_cursor_event = instance.get_typed_func(&mut store, "on_cursor_event")?;
|
|
|
|
let bind_panel = instance.get_typed_func::<u32, u32, _>(&mut store, "bind_panel")?;
|
|
bind_panel.call(&mut store, 0u32)?;
|
|
|
|
Ok(WasmtimeScript {
|
|
store,
|
|
update,
|
|
draw,
|
|
on_cursor_event,
|
|
})
|
|
}
|
|
}
|
|
|
|
pub struct WasmtimeScript<T> {
|
|
store: wasmtime::Store<T>,
|
|
update: wasmtime::TypedFunc<f32, ()>,
|
|
draw: wasmtime::TypedFunc<(), ()>,
|
|
on_cursor_event: wasmtime::TypedFunc<(u32, f32, f32), ()>,
|
|
}
|
|
|
|
impl<T: ScriptAbi> WasmtimeScript<T> {
|
|
pub fn link(linker: &mut wasmtime::Linker<T>) -> anyhow::Result<()> {
|
|
let module = "env";
|
|
|
|
linker.func_wrap(
|
|
module,
|
|
"draw_indexed",
|
|
|mut caller: wasmtime::Caller<'_, T>,
|
|
vertices_ptr: u32,
|
|
vertices_num: u32,
|
|
indices_ptr: u32,
|
|
indices_num: u32| {
|
|
let vertices = Self::get_memory_slice(&mut caller, vertices_ptr, vertices_num);
|
|
let indices = Self::get_memory_slice(&mut caller, indices_ptr, indices_num);
|
|
caller.data().draw_indexed(vertices, indices);
|
|
},
|
|
)?;
|
|
|
|
linker.func_wrap(
|
|
module,
|
|
"UiPanel_drawTriangle",
|
|
|caller: wasmtime::Caller<'_, T>,
|
|
_panel: u32,
|
|
x1: f32,
|
|
y1: f32,
|
|
x2: f32,
|
|
y2: f32,
|
|
x3: f32,
|
|
y3: f32,
|
|
r: f32,
|
|
g: f32,
|
|
b: f32,
|
|
a: f32| {
|
|
let v1 = Vec2 { x: x1, y: y1 };
|
|
let v2 = Vec2 { x: x2, y: y2 };
|
|
let v3 = Vec2 { x: x3, y: y3 };
|
|
let color = Color { r, g, b, a };
|
|
|
|
let vertices = [
|
|
MeshVertex {
|
|
position: v1,
|
|
color,
|
|
},
|
|
MeshVertex {
|
|
position: v2,
|
|
color,
|
|
},
|
|
MeshVertex {
|
|
position: v3,
|
|
color,
|
|
},
|
|
];
|
|
|
|
let indices = [0, 1, 2];
|
|
|
|
caller.data().draw_indexed(&vertices, &indices);
|
|
},
|
|
)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn get_memory_slice<D: bytemuck::Pod>(
|
|
caller: &mut wasmtime::Caller<'_, T>,
|
|
ptr: u32,
|
|
num: u32,
|
|
) -> &'static [D] {
|
|
let len = num as usize * std::mem::size_of::<D>();
|
|
let bytes = Self::get_memory_slice_bytes(caller, ptr as usize, len);
|
|
bytemuck::cast_slice(bytes)
|
|
}
|
|
|
|
fn get_memory_slice_bytes(
|
|
caller: &mut wasmtime::Caller<'_, T>,
|
|
ptr: usize,
|
|
len: usize,
|
|
) -> &'static [u8] {
|
|
let memory = caller.get_export("memory").unwrap().into_memory().unwrap();
|
|
if ptr + len > memory.data_size(&caller) {
|
|
panic!("Attempted wasm memory read is out-of-bounds!");
|
|
}
|
|
|
|
unsafe {
|
|
let ptr = memory.data_ptr(caller).add(ptr);
|
|
std::slice::from_raw_parts(ptr, len)
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T: ScriptAbi> ScriptInstance for WasmtimeScript<T> {
|
|
fn update(&mut self, dt: f32) {
|
|
self.update.call(&mut self.store, dt).unwrap();
|
|
}
|
|
|
|
fn draw(&mut self, f: impl FnOnce(&[DrawCommand])) {
|
|
self.store.data().start_draw();
|
|
self.draw.call(&mut self.store, ()).unwrap();
|
|
self.store.data().with_draw_commands(f);
|
|
}
|
|
|
|
fn on_cursor_event(&mut self, kind: CursorEventKind, at: Vec2) {
|
|
self.on_cursor_event
|
|
.call(&mut self.store, (kind as u32, at.x, at.y))
|
|
.unwrap();
|
|
}
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub struct SimpleScriptAbi {
|
|
draw_cmds: Mutex<Vec<DrawCommand>>,
|
|
}
|
|
|
|
impl ScriptAbi for SimpleScriptAbi {
|
|
fn start_draw(&self) {
|
|
self.draw_cmds.lock().clear();
|
|
}
|
|
|
|
fn draw_indexed(&self, vertices: &[MeshVertex], indices: &[MeshIndex]) {
|
|
self.draw_cmds.lock().push(DrawCommand::Mesh {
|
|
vertices: vertices.to_vec(),
|
|
indices: indices.to_vec(),
|
|
})
|
|
}
|
|
|
|
fn with_draw_commands(&self, f: impl FnOnce(&[DrawCommand])) {
|
|
f(self.draw_cmds.lock().as_slice());
|
|
}
|
|
}
|