// Copyright (c) 2022 Marceline Cramer // SPDX-License-Identifier: LGPL-3.0-or-later //! This module defines backends for WebAssembly execution. //! //! Canary is designed to support multiple WebAssembly runtimes for different //! purposes. Currently, [wasmtime](https://wasmtime.dev) is the only one //! implemented, but in the future, [wasm3](https://github.com/wasm3/wasm3) //! will also be provided. use std::collections::VecDeque; use super::*; pub mod wasmtime; /// Creates the default WebAssembly backend. /// /// Currently, only ever creates [wasmtime::WasmtimeBackend]. pub fn make_default_backend() -> anyhow::Result> { let backend = wasmtime::WasmtimeBackend::new()?; Ok(Box::new(backend)) } /// A WebAssembly runtime backend. pub trait Backend { fn load_module(&self, abi: Arc, module: &[u8]) -> anyhow::Result>; } /// An instance of a WebAssembly module. /// /// All self parameters to this trait's functions are immutable, so the /// implementation must provide interior mutability. This allows instances /// to intelligently optimize the execution of their scripts, for example, by /// allowing the execution of multiple ABI calls from multiple threads when /// a script supports the WebAssembly multithreading extension. pub trait Instance { /// Binds script data to a Canary panel. /// /// To "bind" a Canary panel to a Canary script, this function must be /// called. It passes the ID of a panel to the script, the name of the /// protocol that this panel will be using, plus an initialization /// message, and the script returns an integer as userdata. All panel /// events will be identified to the script with this userdata as the first /// argument. /// /// The intended usecase for this userdata is to contain a pointer. A /// Canary script can allocate some high-level object in memory, and when /// a panel is bound, the script will return a pointer to that object as the /// userdata. Then, when the runtime calls back into the script, the /// userdata will be reinterpreted as a pointer and a method can be called /// on that object in memory. fn bind_panel(&self, panel: PanelId, protocol: &str, msg: Vec) -> u32; fn update(&self, panel_ud: u32, dt: f32); fn draw(&self, panel_ud: u32) -> Vec; fn on_resize(&self, panel_ud: u32, new_size: Vec2); fn on_cursor_event(&self, panel_ud: u32, kind: CursorEventKind, at: Vec2); fn on_message(&self, panel_ud: u32, msg: Vec); } #[derive(Default)] pub struct ScriptAbi { draw_cmds: Mutex>, font_store: Arc, font_families: Mutex>, loaded_fonts: RwLock>>, text_layouts: RwLock>, message_store: RwLock>>, panels: RwLock>, } impl ScriptAbi { pub fn new(font_store: Arc) -> Self { Self { font_store, ..Default::default() } } /// Allocates a new ID and host-side storage for a panel. pub fn create_panel(&self) -> PanelId { let abi = PanelAbi::default(); let id = self.panels.write().insert(abi); PanelId(id) } pub fn start_draw(&self) { let mut lock = self.draw_cmds.lock(); lock.clear(); } pub fn draw_indexed(&self, vertices: &[MeshVertex], indices: &[MeshIndex]) { self.draw_cmds.lock().push(DrawCommand::Mesh { vertices: vertices.to_vec(), indices: indices.to_vec(), }) } pub fn draw_text_layout(&self, id: u32, offset: Vec2, scale: f32, color: Color) { // TODO multiple fonts per layout let layouts = self.text_layouts.read(); let layout = layouts.get(id as usize).unwrap(); let glyphs = layout.glyphs.as_slice(); let loaded = self.loaded_fonts.read(); let font = loaded.get(layout.font_id as usize).unwrap(); let cmds = font.draw(glyphs, offset, scale, color); self.draw_cmds.lock().extend(cmds.into_iter()); } pub fn with_draw_commands(&self, f: impl FnOnce(&[DrawCommand])) { f(self.draw_cmds.lock().as_slice()); } pub fn font_load(&self, family: &str) -> u32 { let mut family_cache = self.font_families.lock(); if let Some(cached) = family_cache.get(family) { return *cached; } let font = self.font_store.load_font(family); let mut loaded = self.loaded_fonts.write(); let id = loaded.len() as u32; family_cache.insert(family.to_string(), id); loaded.push(font); id } pub fn text_layout_new(&self, font_id: u32, text: &str) -> u32 { let loaded = self.loaded_fonts.read(); let font = loaded.get(font_id as usize).unwrap(); let layout = font.shape(text); self.text_layouts.write().insert(layout) as u32 } pub fn text_layout_delete(&self, id: u32) { self.text_layouts.write().remove(id as usize); } pub fn text_layout_get_bounds(&self, id: u32, dst: &mut Rect) { let src = self.text_layouts.read().get(id as usize).unwrap().bounds; let _ = std::mem::replace(dst, src); } pub fn message_new(&self, data: Vec) -> u32 { let mut store = self.message_store.write(); let id = store.insert(data) as u32; id } pub fn message_free(&self, id: u32) { let mut store = self.message_store.write(); store.remove(id as usize); } pub fn message_get_len(&self, id: u32) -> u32 { self.message_store.read().get(id as usize).unwrap().len() as u32 } pub fn message_get_data(&self, id: u32, dst: &mut [u8]) { let store = self.message_store.read(); let src = store.get(id as usize).unwrap(); dst.copy_from_slice(src); } pub fn panel_send_message(&self, id: u32, message: Vec) { if let Some(panel) = self.panels.read().get(id as usize) { panel.outgoing_messages.write().push_back(message); } } pub fn recv_panel_messages(&self, id: PanelId) -> Vec> { if let Some(panel) = self.panels.read().get(id.0) { panel.outgoing_messages.write().drain(..).collect() } else { Vec::new() } } } #[derive(Default)] pub struct PanelAbi { outgoing_messages: RwLock>>, }