190 lines
6.4 KiB
Rust
190 lines
6.4 KiB
Rust
// 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<Box<dyn Backend>> {
|
|
let backend = wasmtime::WasmtimeBackend::new()?;
|
|
Ok(Box::new(backend))
|
|
}
|
|
|
|
/// A WebAssembly runtime backend.
|
|
pub trait Backend {
|
|
fn load_module(&self, abi: Arc<ScriptAbi>, module: &[u8]) -> anyhow::Result<Arc<dyn Instance>>;
|
|
}
|
|
|
|
/// 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<u8>) -> u32;
|
|
|
|
fn update(&self, panel_ud: u32, dt: f32);
|
|
|
|
fn draw(&self, panel_ud: u32) -> Vec<DrawCommand>;
|
|
|
|
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<u8>);
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub struct ScriptAbi {
|
|
draw_cmds: Mutex<Vec<DrawCommand>>,
|
|
font_store: Arc<text::FontStore>,
|
|
font_families: Mutex<HashMap<String, u32>>,
|
|
loaded_fonts: RwLock<Vec<Arc<text::Font>>>,
|
|
text_layouts: RwLock<Slab<text::TextLayout>>,
|
|
message_store: RwLock<Slab<Vec<u8>>>,
|
|
panels: RwLock<Slab<PanelAbi>>,
|
|
}
|
|
|
|
impl ScriptAbi {
|
|
pub fn new(font_store: Arc<text::FontStore>) -> 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<u8>) -> 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<u8>) {
|
|
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<Vec<u8>> {
|
|
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<VecDeque<Vec<u8>>>,
|
|
}
|