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, indices: Vec, }, } pub struct WasmtimeRuntime { engine: wasmtime::Engine, } impl WasmtimeRuntime { pub fn new() -> anyhow::Result { 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( &self, abi: T, module: &[u8], ) -> anyhow::Result> { 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::(&mut store, "bind_panel")?; bind_panel.call(&mut store, 0u32)?; Ok(WasmtimeScript { store, update, draw, on_cursor_event, }) } } pub struct WasmtimeScript { store: wasmtime::Store, update: wasmtime::TypedFunc, draw: wasmtime::TypedFunc<(), ()>, on_cursor_event: wasmtime::TypedFunc<(u32, f32, f32), ()>, } impl WasmtimeScript { pub fn link(linker: &mut wasmtime::Linker) -> 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( caller: &mut wasmtime::Caller<'_, T>, ptr: u32, num: u32, ) -> &'static [D] { let len = num as usize * std::mem::size_of::(); 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 ScriptInstance for WasmtimeScript { 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>, } 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()); } }