Merge pull request 'Refactor Canary API' (#22) from refactor-api into main
Reviewed-on: #22
This commit is contained in:
commit
b3c60eb73f
|
@ -2,7 +2,7 @@ use std::collections::HashMap;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
use canary::{PanelId, ScriptAbiImpl, ScriptInstance, WasmtimeRuntime, WasmtimeScript};
|
use canary::{Panel, Runtime};
|
||||||
use glium::backend::glutin::DisplayCreationError;
|
use glium::backend::glutin::DisplayCreationError;
|
||||||
use glium::{glutin, Surface};
|
use glium::{glutin, Surface};
|
||||||
use glutin::event::{Event, WindowEvent};
|
use glutin::event::{Event, WindowEvent};
|
||||||
|
@ -23,15 +23,13 @@ pub type WindowMessageSender = EventLoopProxy<WindowMessage>;
|
||||||
|
|
||||||
pub struct Window {
|
pub struct Window {
|
||||||
pub graphics: Graphics,
|
pub graphics: Graphics,
|
||||||
pub script: WasmtimeScript<ScriptAbiImpl>,
|
pub panel: Panel,
|
||||||
pub panel: PanelId,
|
|
||||||
pub last_update: Instant,
|
pub last_update: Instant,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Window {
|
impl Window {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
script: WasmtimeScript<ScriptAbiImpl>,
|
panel: Panel,
|
||||||
panel: PanelId,
|
|
||||||
event_loop: &EventLoopWindowTarget<WindowMessage>,
|
event_loop: &EventLoopWindowTarget<WindowMessage>,
|
||||||
) -> Result<Self, DisplayCreationError> {
|
) -> Result<Self, DisplayCreationError> {
|
||||||
let wb = glutin::window::WindowBuilder::new();
|
let wb = glutin::window::WindowBuilder::new();
|
||||||
|
@ -41,7 +39,6 @@ impl Window {
|
||||||
let last_update = Instant::now();
|
let last_update = Instant::now();
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
graphics,
|
graphics,
|
||||||
script,
|
|
||||||
panel,
|
panel,
|
||||||
last_update,
|
last_update,
|
||||||
})
|
})
|
||||||
|
@ -58,17 +55,16 @@ impl Window {
|
||||||
pub fn update(&mut self) {
|
pub fn update(&mut self) {
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
let dt = now.duration_since(self.last_update).as_secs_f32();
|
let dt = now.duration_since(self.last_update).as_secs_f32();
|
||||||
self.script.update(self.panel, dt);
|
self.panel.update(dt);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(&mut self) {
|
pub fn draw(&mut self) {
|
||||||
self.script.draw(self.panel, |commands| {
|
let commands = self.panel.draw();
|
||||||
self.graphics.draw(commands);
|
self.graphics.draw(&commands);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_message(&mut self, msg: Vec<u8>) {
|
pub fn send_message(&mut self, msg: Vec<u8>) {
|
||||||
self.script.on_message(self.panel, msg);
|
self.panel.on_message(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,16 +72,19 @@ pub struct WindowStore {
|
||||||
pub ipc_sender: IpcMessageSender,
|
pub ipc_sender: IpcMessageSender,
|
||||||
pub ipc_to_window: HashMap<usize, WindowId>,
|
pub ipc_to_window: HashMap<usize, WindowId>,
|
||||||
pub windows: HashMap<WindowId, Window>,
|
pub windows: HashMap<WindowId, Window>,
|
||||||
pub runtime: WasmtimeRuntime,
|
pub runtime: Runtime,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowStore {
|
impl WindowStore {
|
||||||
pub fn new(ipc_sender: IpcMessageSender) -> Self {
|
pub fn new(ipc_sender: IpcMessageSender) -> Self {
|
||||||
|
let backend = canary::backend::make_default_backend().unwrap();
|
||||||
|
let runtime = Runtime::new(backend).unwrap();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
ipc_sender,
|
ipc_sender,
|
||||||
ipc_to_window: Default::default(),
|
ipc_to_window: Default::default(),
|
||||||
windows: Default::default(),
|
windows: Default::default(),
|
||||||
runtime: WasmtimeRuntime::new().unwrap(),
|
runtime,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,11 +119,10 @@ impl WindowStore {
|
||||||
Event::UserEvent(event) => match event {
|
Event::UserEvent(event) => match event {
|
||||||
WindowMessage::OpenWindow { id, script } => {
|
WindowMessage::OpenWindow { id, script } => {
|
||||||
println!("Opening window {} with script {:?}", id, script);
|
println!("Opening window {} with script {:?}", id, script);
|
||||||
let abi = Default::default();
|
|
||||||
let module = std::fs::read(script).unwrap();
|
let module = std::fs::read(script).unwrap();
|
||||||
let mut script = self.runtime.load_module(abi, &module).unwrap();
|
let mut script = self.runtime.load_module(&module).unwrap();
|
||||||
let panel = script.bind_panel(vec![]);
|
let panel = script.create_panel(vec![]).unwrap();
|
||||||
let window = Window::new(script, panel, &event_loop).unwrap();
|
let window = Window::new(panel, &event_loop).unwrap();
|
||||||
let window_id = window.get_id();
|
let window_id = window.get_id();
|
||||||
self.windows.insert(window_id, window);
|
self.windows.insert(window_id, window);
|
||||||
self.ipc_to_window.insert(id, window_id);
|
self.ipc_to_window.insert(id, window_id);
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
// Copyright (c) 2022 Marceline Cramer
|
// Copyright (c) 2022 Marceline Cramer
|
||||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
use canary::{CursorEventKind, ScriptInstance};
|
use canary::{CursorEventKind, Panel, Runtime, Script};
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
use std::sync::{Arc, RwLock};
|
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
@ -27,11 +26,9 @@ fn main() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
type Script = Arc<RwLock<canary::WasmtimeScript<canary::ScriptAbiImpl>>>;
|
|
||||||
|
|
||||||
struct App {
|
struct App {
|
||||||
script: Script,
|
script: Script,
|
||||||
panels: Vec<Panel>,
|
panels: Vec<PanelWindow>,
|
||||||
next_idx: usize,
|
next_idx: usize,
|
||||||
last_update: Instant,
|
last_update: Instant,
|
||||||
bind_message_buf: String,
|
bind_message_buf: String,
|
||||||
|
@ -39,13 +36,13 @@ struct App {
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub fn new(module_path: &str) -> Self {
|
pub fn new(module_path: &str) -> Self {
|
||||||
let runtime = canary::WasmtimeRuntime::new().unwrap();
|
let backend = canary::backend::make_default_backend().unwrap();
|
||||||
let abi = canary::ScriptAbiImpl::default();
|
let runtime = Runtime::new(backend).unwrap();
|
||||||
let module = std::fs::read(module_path).unwrap();
|
let module = std::fs::read(module_path).unwrap();
|
||||||
let script = runtime.load_module(abi, &module).unwrap();
|
let script = runtime.load_module(&module).unwrap();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
script: Arc::new(RwLock::new(script)),
|
script,
|
||||||
panels: vec![],
|
panels: vec![],
|
||||||
next_idx: 0,
|
next_idx: 0,
|
||||||
last_update: Instant::now(),
|
last_update: Instant::now(),
|
||||||
|
@ -58,21 +55,18 @@ impl eframe::App for App {
|
||||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||||
ctx.request_repaint();
|
ctx.request_repaint();
|
||||||
|
|
||||||
let mut script = self.script.write().unwrap();
|
|
||||||
|
|
||||||
egui::SidePanel::left("left_panel").show(ctx, |ui| {
|
egui::SidePanel::left("left_panel").show(ctx, |ui| {
|
||||||
let text_edit = egui::TextEdit::multiline(&mut self.bind_message_buf).code_editor();
|
let text_edit = egui::TextEdit::multiline(&mut self.bind_message_buf).code_editor();
|
||||||
ui.add(text_edit);
|
ui.add(text_edit);
|
||||||
|
|
||||||
if ui.button("Bind Panel").clicked() {
|
if ui.button("Bind Panel").clicked() {
|
||||||
let msg = self.bind_message_buf.as_bytes().to_vec();
|
let msg = self.bind_message_buf.as_bytes().to_vec();
|
||||||
let id = script.bind_panel(msg);
|
let panel = self.script.create_panel(msg).unwrap();
|
||||||
let index = self.next_idx;
|
let index = self.next_idx;
|
||||||
self.next_idx += 1;
|
self.next_idx += 1;
|
||||||
|
|
||||||
let panel = Panel {
|
let panel = PanelWindow {
|
||||||
script: self.script.to_owned(),
|
panel,
|
||||||
id,
|
|
||||||
index,
|
index,
|
||||||
msg_buf: String::new(),
|
msg_buf: String::new(),
|
||||||
show_msg: false,
|
show_msg: false,
|
||||||
|
@ -86,22 +80,21 @@ impl eframe::App for App {
|
||||||
self.last_update = Instant::now();
|
self.last_update = Instant::now();
|
||||||
|
|
||||||
for panel in self.panels.iter_mut() {
|
for panel in self.panels.iter_mut() {
|
||||||
script.update(panel.id, dt);
|
panel.panel.update(dt);
|
||||||
panel.show(&mut *script, ctx);
|
panel.show(ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Panel {
|
pub struct PanelWindow {
|
||||||
pub script: Script,
|
pub panel: Panel,
|
||||||
pub id: canary::PanelId,
|
|
||||||
pub index: usize,
|
pub index: usize,
|
||||||
pub msg_buf: String,
|
pub msg_buf: String,
|
||||||
pub show_msg: bool,
|
pub show_msg: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Panel {
|
impl PanelWindow {
|
||||||
pub fn show(&mut self, script: &mut impl canary::ScriptInstance, ctx: &egui::Context) {
|
pub fn show(&mut self, ctx: &egui::Context) {
|
||||||
let window_id = egui::Id::new(format!("panel_{}", self.index));
|
let window_id = egui::Id::new(format!("panel_{}", self.index));
|
||||||
egui::Window::new("Panel").id(window_id).show(ctx, |ui| {
|
egui::Window::new("Panel").id(window_id).show(ctx, |ui| {
|
||||||
egui::menu::bar(ui, |ui| {
|
egui::menu::bar(ui, |ui| {
|
||||||
|
@ -134,38 +127,37 @@ impl Panel {
|
||||||
CursorEventKind::Hover
|
CursorEventKind::Hover
|
||||||
};
|
};
|
||||||
|
|
||||||
script.on_cursor_event(self.id, kind, pos);
|
self.panel.on_cursor_event(kind, pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
let texture = egui::TextureId::Managed(0);
|
let texture = egui::TextureId::Managed(0);
|
||||||
let uv = egui::pos2(0.0, 0.0);
|
let uv = egui::pos2(0.0, 0.0);
|
||||||
let mut mesh = egui::Mesh::with_texture(texture);
|
let mut mesh = egui::Mesh::with_texture(texture);
|
||||||
|
|
||||||
script.draw(self.id, |commands| {
|
let commands = self.panel.draw();
|
||||||
for command in commands.iter() {
|
for command in commands.into_iter() {
|
||||||
let voff = mesh.vertices.len() as u32;
|
let voff = mesh.vertices.len() as u32;
|
||||||
|
|
||||||
match command {
|
match command {
|
||||||
canary::DrawCommand::Mesh { vertices, indices } => {
|
canary::DrawCommand::Mesh { vertices, indices } => {
|
||||||
for v in vertices.iter() {
|
for v in vertices.iter() {
|
||||||
use egui::epaint::Vertex;
|
use egui::epaint::Vertex;
|
||||||
let pos = egui::pos2(v.position.x, -v.position.y);
|
let pos = egui::pos2(v.position.x, -v.position.y);
|
||||||
let pos = pos.to_vec2() / 2.0 + egui::vec2(0.5, 0.5);
|
let pos = pos.to_vec2() / 2.0 + egui::vec2(0.5, 0.5);
|
||||||
let pos = rect.left_top() + pos * rect.size();
|
let pos = rect.left_top() + pos * rect.size();
|
||||||
let (r, g, b, a) = v.color.to_rgba_unmultiplied();
|
let (r, g, b, a) = v.color.to_rgba_unmultiplied();
|
||||||
let color = egui::Color32::from_rgba_unmultiplied(r, g, b, a);
|
let color = egui::Color32::from_rgba_unmultiplied(r, g, b, a);
|
||||||
let v = Vertex { pos, uv, color };
|
let v = Vertex { pos, uv, color };
|
||||||
mesh.vertices.push(v);
|
mesh.vertices.push(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
for i in indices.iter() {
|
for i in indices.iter() {
|
||||||
mesh.indices.push(i + voff);
|
mesh.indices.push(i + voff);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => unimplemented!(),
|
|
||||||
}
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
let painter = ui.painter_at(rect);
|
let painter = ui.painter_at(rect);
|
||||||
let shape = egui::Shape::mesh(mesh);
|
let shape = egui::Shape::mesh(mesh);
|
||||||
|
@ -184,7 +176,7 @@ impl Panel {
|
||||||
|
|
||||||
if ui.button("Send Message").clicked() {
|
if ui.button("Send Message").clicked() {
|
||||||
let msg = self.msg_buf.as_bytes().to_vec();
|
let msg = self.msg_buf.as_bytes().to_vec();
|
||||||
script.on_message(self.id, msg);
|
self.panel.on_message(msg);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
//! 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 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, 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, 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, msg: Vec<u8>) -> u32;
|
||||||
|
|
||||||
|
fn update(&self, panel_ud: u32, dt: f32);
|
||||||
|
|
||||||
|
fn draw(&self, panel_ud: u32) -> Vec<DrawCommand>;
|
||||||
|
|
||||||
|
fn on_cursor_event(&self, panel_ud: u32, kind: CursorEventKind, at: Vec2);
|
||||||
|
|
||||||
|
fn on_message(&self, panel_ud: u32, msg: Vec<u8>);
|
||||||
|
}
|
|
@ -0,0 +1,227 @@
|
||||||
|
use std::ops::DerefMut;
|
||||||
|
|
||||||
|
use super::{Arc, Backend, Instance, PanelId};
|
||||||
|
use crate::{DrawCommand, ScriptAbi, ScriptAbiImpl};
|
||||||
|
|
||||||
|
use canary_script::{Color, CursorEventKind, Rect, Vec2};
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
|
||||||
|
type Caller<'a> = wasmtime::Caller<'a, ScriptAbiImpl>;
|
||||||
|
type Store = wasmtime::Store<ScriptAbiImpl>;
|
||||||
|
type Linker = wasmtime::Linker<ScriptAbiImpl>;
|
||||||
|
|
||||||
|
pub struct WasmtimeBackend {
|
||||||
|
engine: wasmtime::Engine,
|
||||||
|
}
|
||||||
|
impl WasmtimeBackend {
|
||||||
|
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 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Backend for WasmtimeBackend {
|
||||||
|
fn load_module(&self, module: &[u8]) -> anyhow::Result<Arc<dyn Instance>> {
|
||||||
|
let module = wasmtime::Module::new(&self.engine, module)?;
|
||||||
|
let abi = ScriptAbiImpl::default();
|
||||||
|
let mut store = wasmtime::Store::new(&self.engine, abi);
|
||||||
|
let mut linker = Linker::new(&self.engine);
|
||||||
|
WasmtimeInstance::link(&mut linker)?;
|
||||||
|
let instance = linker.instantiate(&mut store, &module)?;
|
||||||
|
let bind_panel = instance.get_typed_func(&mut store, "bind_panel")?;
|
||||||
|
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 on_message = instance.get_typed_func(&mut store, "on_message")?;
|
||||||
|
|
||||||
|
let instance = WasmtimeInstance {
|
||||||
|
store: Mutex::new(store),
|
||||||
|
bind_panel,
|
||||||
|
update,
|
||||||
|
draw,
|
||||||
|
on_cursor_event,
|
||||||
|
on_message,
|
||||||
|
};
|
||||||
|
|
||||||
|
let instance = Arc::new(instance);
|
||||||
|
|
||||||
|
Ok(instance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct WasmtimeInstance {
|
||||||
|
store: Mutex<Store>,
|
||||||
|
bind_panel: wasmtime::TypedFunc<(u32, u32), u32>,
|
||||||
|
update: wasmtime::TypedFunc<(u32, f32), ()>,
|
||||||
|
draw: wasmtime::TypedFunc<u32, ()>,
|
||||||
|
on_cursor_event: wasmtime::TypedFunc<(u32, u32, f32, f32), ()>,
|
||||||
|
on_message: wasmtime::TypedFunc<(u32, u32), ()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WasmtimeInstance {
|
||||||
|
pub fn link(linker: &mut Linker) -> anyhow::Result<()> {
|
||||||
|
let module = "env";
|
||||||
|
|
||||||
|
linker.func_wrap(
|
||||||
|
module,
|
||||||
|
"draw_indexed",
|
||||||
|
|mut caller: Caller<'_>,
|
||||||
|
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,
|
||||||
|
"draw_text_layout",
|
||||||
|
|caller: Caller<'_>, id: u32, xoff: f32, yoff: f32, scale: f32, color: u32| {
|
||||||
|
let offset = Vec2 { x: xoff, y: yoff };
|
||||||
|
let color = Color(color);
|
||||||
|
caller.data().draw_text_layout(id, offset, scale, color);
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
linker.func_wrap(
|
||||||
|
module,
|
||||||
|
"font_load",
|
||||||
|
|mut caller: Caller<'_>, family_ptr: u32, family_len: u32| {
|
||||||
|
let family = Self::get_memory_slice_str(&mut caller, family_ptr, family_len);
|
||||||
|
caller.data().font_load(family)
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
linker.func_wrap(
|
||||||
|
module,
|
||||||
|
"text_layout_new",
|
||||||
|
|mut caller: Caller<'_>, font_id: u32, text_ptr: u32, text_len: u32| {
|
||||||
|
let text = Self::get_memory_slice_str(&mut caller, text_ptr, text_len);
|
||||||
|
caller.data().text_layout_new(font_id, text)
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
linker.func_wrap(
|
||||||
|
module,
|
||||||
|
"text_layout_delete",
|
||||||
|
|caller: Caller<'_>, id: u32| caller.data().text_layout_delete(id),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
linker.func_wrap(
|
||||||
|
module,
|
||||||
|
"text_layout_get_bounds",
|
||||||
|
|mut caller: Caller<'_>, id: u32, rect_ptr: u32| {
|
||||||
|
let rect: &mut Rect = Self::get_memory_ref(&mut caller, rect_ptr);
|
||||||
|
caller.data().text_layout_get_bounds(id, rect);
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
linker.func_wrap(
|
||||||
|
module,
|
||||||
|
"message_get_len",
|
||||||
|
|caller: Caller<'_>, id: u32| -> u32 { caller.data().message_get_len(id) },
|
||||||
|
)?;
|
||||||
|
|
||||||
|
linker.func_wrap(
|
||||||
|
module,
|
||||||
|
"message_get_data",
|
||||||
|
|mut caller: Caller<'_>, id: u32, ptr: u32| {
|
||||||
|
let ptr = ptr as usize;
|
||||||
|
let len = caller.data().message_get_len(id) as usize;
|
||||||
|
let dst = Self::get_memory_slice_bytes(&mut caller, ptr, len);
|
||||||
|
caller.data().message_get_data(id, dst)
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_memory_ref<D: bytemuck::Pod>(caller: &mut Caller<'_>, ptr: u32) -> &'static mut D {
|
||||||
|
let len = std::mem::size_of::<D>();
|
||||||
|
let bytes = Self::get_memory_slice_bytes(caller, ptr as usize, len);
|
||||||
|
bytemuck::from_bytes_mut(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_memory_slice<D: bytemuck::Pod>(
|
||||||
|
caller: &mut Caller<'_>,
|
||||||
|
ptr: u32,
|
||||||
|
num: u32,
|
||||||
|
) -> &'static mut [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_mut(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_memory_slice_str(caller: &mut Caller<'_>, ptr: u32, len: u32) -> &'static mut str {
|
||||||
|
let memory = Self::get_memory_slice_bytes(caller, ptr as usize, len as usize);
|
||||||
|
std::str::from_utf8_mut(memory).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_memory_slice_bytes(
|
||||||
|
caller: &mut Caller<'_>,
|
||||||
|
ptr: usize,
|
||||||
|
len: usize,
|
||||||
|
) -> &'static mut [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_mut(ptr, len)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Instance for WasmtimeInstance {
|
||||||
|
fn bind_panel(&self, panel: PanelId, msg: Vec<u8>) -> u32 {
|
||||||
|
let mut store = self.store.lock();
|
||||||
|
let msg = store.data().message_new(msg);
|
||||||
|
let args = (panel.0 as u32, msg);
|
||||||
|
let data = self.bind_panel.call(store.deref_mut(), args).unwrap();
|
||||||
|
store.data().message_free(msg);
|
||||||
|
data
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&self, panel_ud: u32, dt: f32) {
|
||||||
|
let mut store = self.store.lock();
|
||||||
|
self.update.call(store.deref_mut(), (panel_ud, dt)).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(&self, panel_ud: u32) -> Vec<DrawCommand> {
|
||||||
|
let mut store = self.store.lock();
|
||||||
|
store.data().start_draw();
|
||||||
|
self.draw.call(store.deref_mut(), panel_ud).unwrap();
|
||||||
|
let mut cmds = Vec::new();
|
||||||
|
store
|
||||||
|
.data()
|
||||||
|
.with_draw_commands(|slice| cmds.extend_from_slice(slice));
|
||||||
|
cmds
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_cursor_event(&self, panel_ud: u32, kind: CursorEventKind, at: Vec2) {
|
||||||
|
let mut store = self.store.lock();
|
||||||
|
self.on_cursor_event
|
||||||
|
.call(store.deref_mut(), (panel_ud, kind as u32, at.x, at.y))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_message(&self, panel_ud: u32, msg: Vec<u8>) {
|
||||||
|
let mut store = self.store.lock();
|
||||||
|
let msg = store.data().message_new(msg);
|
||||||
|
self.on_message
|
||||||
|
.call(store.deref_mut(), (panel_ud, msg))
|
||||||
|
.unwrap();
|
||||||
|
store.data().message_free(msg);
|
||||||
|
}
|
||||||
|
}
|
304
src/lib.rs
304
src/lib.rs
|
@ -7,8 +7,75 @@ use slab::Slab;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub mod backend;
|
||||||
pub mod text;
|
pub mod text;
|
||||||
|
|
||||||
|
use backend::{Backend, Instance};
|
||||||
|
|
||||||
|
/// The main interface to Canary.
|
||||||
|
pub struct Runtime {
|
||||||
|
backend: Box<dyn Backend>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Runtime {
|
||||||
|
pub fn new(backend: Box<dyn Backend>) -> anyhow::Result<Self> {
|
||||||
|
Ok(Self { backend })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_module(&self, module: &[u8]) -> anyhow::Result<Script> {
|
||||||
|
let instance = self.backend.load_module(module)?;
|
||||||
|
|
||||||
|
Ok(Script {
|
||||||
|
instance,
|
||||||
|
next_panel: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A loaded instance of a Canary script.
|
||||||
|
pub struct Script {
|
||||||
|
instance: Arc<dyn Instance>,
|
||||||
|
next_panel: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Script {
|
||||||
|
pub fn create_panel(&mut self, msg: Vec<u8>) -> anyhow::Result<Panel> {
|
||||||
|
let id = PanelId(self.next_panel);
|
||||||
|
self.next_panel += 1;
|
||||||
|
let userdata = self.instance.bind_panel(id, msg);
|
||||||
|
Ok(Panel {
|
||||||
|
instance: self.instance.clone(),
|
||||||
|
id,
|
||||||
|
userdata,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A Canary panel.
|
||||||
|
pub struct Panel {
|
||||||
|
instance: Arc<dyn Instance>,
|
||||||
|
id: PanelId,
|
||||||
|
userdata: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Panel {
|
||||||
|
pub fn update(&self, dt: f32) {
|
||||||
|
self.instance.update(self.userdata, dt);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw(&self) -> Vec<DrawCommand> {
|
||||||
|
self.instance.draw(self.userdata)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_cursor_event(&self, kind: CursorEventKind, at: Vec2) {
|
||||||
|
self.instance.on_cursor_event(self.userdata, kind, at);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_message(&self, msg: Vec<u8>) {
|
||||||
|
self.instance.on_message(self.userdata, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Proportion constant between pixels (at 96dpi) to millimeters (Canary's unit measurement).
|
/// Proportion constant between pixels (at 96dpi) to millimeters (Canary's unit measurement).
|
||||||
pub const PX_PER_MM: f32 = 25.4 / 96.0;
|
pub const PX_PER_MM: f32 = 25.4 / 96.0;
|
||||||
|
|
||||||
|
@ -37,15 +104,8 @@ pub trait ScriptAbi {
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
pub struct PanelId(pub(crate) usize);
|
pub struct PanelId(pub(crate) usize);
|
||||||
|
|
||||||
pub trait ScriptInstance {
|
|
||||||
fn bind_panel(&mut self, msg: Vec<u8>) -> PanelId;
|
|
||||||
fn update(&mut self, panel: PanelId, dt: f32);
|
|
||||||
fn draw(&mut self, panel: PanelId, f: impl FnOnce(&[DrawCommand]));
|
|
||||||
fn on_cursor_event(&mut self, panel: PanelId, kind: CursorEventKind, at: Vec2);
|
|
||||||
fn on_message(&mut self, panel: PanelId, msg: Vec<u8>);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
pub enum DrawCommand {
|
pub enum DrawCommand {
|
||||||
Mesh {
|
Mesh {
|
||||||
vertices: Vec<MeshVertex>,
|
vertices: Vec<MeshVertex>,
|
||||||
|
@ -53,234 +113,6 @@ pub enum DrawCommand {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
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 panel_datas = Default::default();
|
|
||||||
let mut linker = wasmtime::Linker::new(&self.engine);
|
|
||||||
WasmtimeScript::link(&mut linker)?;
|
|
||||||
let instance = linker.instantiate(&mut store, &module)?;
|
|
||||||
let bind_panel = instance.get_typed_func(&mut store, "bind_panel")?;
|
|
||||||
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 on_message = instance.get_typed_func(&mut store, "on_message")?;
|
|
||||||
|
|
||||||
Ok(WasmtimeScript {
|
|
||||||
store,
|
|
||||||
panel_datas,
|
|
||||||
bind_panel,
|
|
||||||
update,
|
|
||||||
draw,
|
|
||||||
on_cursor_event,
|
|
||||||
on_message,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct WasmtimeScript<T> {
|
|
||||||
store: wasmtime::Store<T>,
|
|
||||||
panel_datas: Slab<u32>,
|
|
||||||
bind_panel: wasmtime::TypedFunc<(u32, u32), u32>,
|
|
||||||
update: wasmtime::TypedFunc<(u32, f32), ()>,
|
|
||||||
draw: wasmtime::TypedFunc<u32, ()>,
|
|
||||||
on_cursor_event: wasmtime::TypedFunc<(u32, u32, f32, f32), ()>,
|
|
||||||
on_message: wasmtime::TypedFunc<(u32, u32), ()>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
"draw_text_layout",
|
|
||||||
|caller: wasmtime::Caller<'_, T>,
|
|
||||||
id: u32,
|
|
||||||
xoff: f32,
|
|
||||||
yoff: f32,
|
|
||||||
scale: f32,
|
|
||||||
color: u32| {
|
|
||||||
let offset = Vec2 { x: xoff, y: yoff };
|
|
||||||
let color = Color(color);
|
|
||||||
caller.data().draw_text_layout(id, offset, scale, color);
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
linker.func_wrap(
|
|
||||||
module,
|
|
||||||
"font_load",
|
|
||||||
|mut caller: wasmtime::Caller<'_, T>, family_ptr: u32, family_len: u32| {
|
|
||||||
let family = Self::get_memory_slice_str(&mut caller, family_ptr, family_len);
|
|
||||||
caller.data().font_load(family)
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
linker.func_wrap(
|
|
||||||
module,
|
|
||||||
"text_layout_new",
|
|
||||||
|mut caller: wasmtime::Caller<'_, T>, font_id: u32, text_ptr: u32, text_len: u32| {
|
|
||||||
let text = Self::get_memory_slice_str(&mut caller, text_ptr, text_len);
|
|
||||||
caller.data().text_layout_new(font_id, text)
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
linker.func_wrap(
|
|
||||||
module,
|
|
||||||
"text_layout_delete",
|
|
||||||
|caller: wasmtime::Caller<'_, T>, id: u32| caller.data().text_layout_delete(id),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
linker.func_wrap(
|
|
||||||
module,
|
|
||||||
"text_layout_get_bounds",
|
|
||||||
|mut caller: wasmtime::Caller<'_, T>, id: u32, rect_ptr: u32| {
|
|
||||||
let rect: &mut Rect = Self::get_memory_ref(&mut caller, rect_ptr);
|
|
||||||
caller.data().text_layout_get_bounds(id, rect);
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
linker.func_wrap(
|
|
||||||
module,
|
|
||||||
"message_get_len",
|
|
||||||
|caller: wasmtime::Caller<'_, T>, id: u32| -> u32 { caller.data().message_get_len(id) },
|
|
||||||
)?;
|
|
||||||
|
|
||||||
linker.func_wrap(
|
|
||||||
module,
|
|
||||||
"message_get_data",
|
|
||||||
|mut caller: wasmtime::Caller<'_, T>, id: u32, ptr: u32| {
|
|
||||||
let ptr = ptr as usize;
|
|
||||||
let len = caller.data().message_get_len(id) as usize;
|
|
||||||
let dst = Self::get_memory_slice_bytes(&mut caller, ptr, len);
|
|
||||||
caller.data().message_get_data(id, dst)
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_panel_data(&self, panel: PanelId) -> u32 {
|
|
||||||
*self.panel_datas.get(panel.0).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_memory_ref<D: bytemuck::Pod>(
|
|
||||||
caller: &mut wasmtime::Caller<'_, T>,
|
|
||||||
ptr: u32,
|
|
||||||
) -> &'static mut D {
|
|
||||||
let len = std::mem::size_of::<D>();
|
|
||||||
let bytes = Self::get_memory_slice_bytes(caller, ptr as usize, len);
|
|
||||||
bytemuck::from_bytes_mut(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_memory_slice<D: bytemuck::Pod>(
|
|
||||||
caller: &mut wasmtime::Caller<'_, T>,
|
|
||||||
ptr: u32,
|
|
||||||
num: u32,
|
|
||||||
) -> &'static mut [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_mut(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_memory_slice_str(
|
|
||||||
caller: &mut wasmtime::Caller<'_, T>,
|
|
||||||
ptr: u32,
|
|
||||||
len: u32,
|
|
||||||
) -> &'static mut str {
|
|
||||||
let memory = Self::get_memory_slice_bytes(caller, ptr as usize, len as usize);
|
|
||||||
std::str::from_utf8_mut(memory).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_memory_slice_bytes(
|
|
||||||
caller: &mut wasmtime::Caller<'_, T>,
|
|
||||||
ptr: usize,
|
|
||||||
len: usize,
|
|
||||||
) -> &'static mut [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_mut(ptr, len)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: ScriptAbi> ScriptInstance for WasmtimeScript<T> {
|
|
||||||
fn bind_panel(&mut self, msg: Vec<u8>) -> PanelId {
|
|
||||||
let id = self.panel_datas.insert(0);
|
|
||||||
let msg = self.store.data().message_new(msg);
|
|
||||||
let args = (id as u32, msg);
|
|
||||||
let data = self.bind_panel.call(&mut self.store, args).unwrap();
|
|
||||||
*self.panel_datas.get_mut(id).unwrap() = data;
|
|
||||||
self.store.data().message_free(msg);
|
|
||||||
PanelId(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, panel: PanelId, dt: f32) {
|
|
||||||
let data = self.get_panel_data(panel);
|
|
||||||
self.update.call(&mut self.store, (data, dt)).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw(&mut self, panel: PanelId, f: impl FnOnce(&[DrawCommand])) {
|
|
||||||
let data = self.get_panel_data(panel);
|
|
||||||
self.store.data().start_draw();
|
|
||||||
self.draw.call(&mut self.store, data).unwrap();
|
|
||||||
self.store.data().with_draw_commands(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_cursor_event(&mut self, panel: PanelId, kind: CursorEventKind, at: Vec2) {
|
|
||||||
let data = self.get_panel_data(panel);
|
|
||||||
self.on_cursor_event
|
|
||||||
.call(&mut self.store, (data, kind as u32, at.x, at.y))
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_message(&mut self, panel: PanelId, msg: Vec<u8>) {
|
|
||||||
let data = self.get_panel_data(panel);
|
|
||||||
let msg = self.store.data().message_new(msg);
|
|
||||||
self.on_message.call(&mut self.store, (data, msg)).unwrap();
|
|
||||||
self.store.data().message_free(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The standard [ScriptAbi] implementation to use.
|
/// The standard [ScriptAbi] implementation to use.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ScriptAbiImpl {
|
pub struct ScriptAbiImpl {
|
||||||
|
|
Loading…
Reference in New Issue