From db4d6b6eb58ae3e509695b596bdbee2bb2ba3de4 Mon Sep 17 00:00:00 2001 From: Brooks J Rady Date: Tue, 29 Dec 2020 10:24:22 +0000 Subject: [PATCH 1/3] Fully Working WebAssembly Loading! --- Cargo.lock | 101 +++++++++++++++++++++++++++++++++++----------------- Cargo.toml | 4 +-- src/main.rs | 87 +++++++++++++++++++++++++------------------- 3 files changed, 122 insertions(+), 70 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3c7bf2c6..27c91846 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -110,7 +110,7 @@ dependencies = [ "cfg-if 1.0.0", "libc", "miniz_oxide", - "object 0.22.0", + "object", "rustc-demangle", ] @@ -329,7 +329,7 @@ dependencies = [ "const_fn", "crossbeam-utils 0.8.0", "lazy_static", - "memoffset", + "memoffset 0.5.6", "scopeguard", ] @@ -832,6 +832,15 @@ version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" +[[package]] +name = "memmap2" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e73be3b7d04a0123e933fea1d50d126cc7196bbc0362c0ce426694f777194eee" +dependencies = [ + "libc", +] + [[package]] name = "memoffset" version = "0.5.6" @@ -841,6 +850,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "memoffset" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87" +dependencies = [ + "autocfg", +] + [[package]] name = "miniz_oxide" version = "0.4.3" @@ -915,20 +933,14 @@ dependencies = [ [[package]] name = "object" -version = "0.21.1" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37fd5004feb2ce328a52b0b3d01dbf4ffff72583493900ed15f22d4111c51693" +checksum = "8d3b63360ec3cb337817c2dbd47ab4a0f170d285d8e5a2064600f3def1402397" dependencies = [ "crc32fast", "indexmap", ] -[[package]] -name = "object" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d3b63360ec3cb337817c2dbd47ab4a0f170d285d8e5a2064600f3def1402397" - [[package]] name = "once_cell" version = "1.4.0" @@ -1678,8 +1690,9 @@ checksum = "93b162580e34310e5931c4b792560108b10fd14d64915d7fff8ff00180e70092" [[package]] name = "wasmer" -version = "1.0.0-alpha5" -source = "git+https://github.com/wasmerio/wasmer.git#5e2dc65463a49b08c14bf9fb48eb45fa7d79d4c2" +version = "1.0.0-rc1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe7fb8734c3e522aea0bed12315115e4c5d684c3d312db5f3ef6a8a312b1b47" dependencies = [ "cfg-if 0.1.10", "indexmap", @@ -1688,6 +1701,7 @@ dependencies = [ "thiserror", "wasmer-compiler", "wasmer-compiler-cranelift", + "wasmer-derive", "wasmer-engine", "wasmer-engine-jit", "wasmer-engine-native", @@ -1699,8 +1713,9 @@ dependencies = [ [[package]] name = "wasmer-compiler" -version = "1.0.0-alpha5" -source = "git+https://github.com/wasmerio/wasmer.git#5e2dc65463a49b08c14bf9fb48eb45fa7d79d4c2" +version = "1.0.0-rc1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97789fdc5968ea3d29528648dc2422e0c795ca195b88a59c30a56f0e52805690" dependencies = [ "enumset", "raw-cpuid", @@ -1716,8 +1731,9 @@ dependencies = [ [[package]] name = "wasmer-compiler-cranelift" -version = "1.0.0-alpha5" -source = "git+https://github.com/wasmerio/wasmer.git#5e2dc65463a49b08c14bf9fb48eb45fa7d79d4c2" +version = "1.0.0-rc1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e80c86796019ef6d4519e1a66f2b99ab73b937a4e43e723772956b3e8c8df23" dependencies = [ "cranelift-codegen", "cranelift-frontend", @@ -1732,14 +1748,28 @@ dependencies = [ "wasmer-vm", ] +[[package]] +name = "wasmer-derive" +version = "1.0.0-rc1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c74a84dc4ba0d60e9419f335734fa807097caf4938b2b44bc0703688a42b467" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "wasmer-engine" -version = "1.0.0-alpha5" -source = "git+https://github.com/wasmerio/wasmer.git#5e2dc65463a49b08c14bf9fb48eb45fa7d79d4c2" +version = "1.0.0-rc1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e787fb8e42b5ad32c1c8dcf105e42d2919dfb3ea4b8e286de3e43f306ae1457b" dependencies = [ "backtrace", "bincode", "lazy_static", + "memmap2", "more-asserts", "rustc-demangle", "serde", @@ -1753,8 +1783,9 @@ dependencies = [ [[package]] name = "wasmer-engine-jit" -version = "1.0.0-alpha5" -source = "git+https://github.com/wasmerio/wasmer.git#5e2dc65463a49b08c14bf9fb48eb45fa7d79d4c2" +version = "1.0.0-rc1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552f4252f8d7984279c55df0970ca1d42b1e4c63d918e7af1cd004e427e5008c" dependencies = [ "bincode", "cfg-if 0.1.10", @@ -1770,8 +1801,9 @@ dependencies = [ [[package]] name = "wasmer-engine-native" -version = "1.0.0-alpha5" -source = "git+https://github.com/wasmerio/wasmer.git#5e2dc65463a49b08c14bf9fb48eb45fa7d79d4c2" +version = "1.0.0-rc1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5264031a9b398a071fa128fe89fb55bc75f9c0ac5eaa7f1f9ef9efcee08afa1c" dependencies = [ "bincode", "cfg-if 0.1.10", @@ -1790,10 +1822,11 @@ dependencies = [ [[package]] name = "wasmer-object" -version = "1.0.0-alpha5" -source = "git+https://github.com/wasmerio/wasmer.git#5e2dc65463a49b08c14bf9fb48eb45fa7d79d4c2" +version = "1.0.0-rc1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22ccf03052d73b3588bd30de94db9ee949957a543d0c317122f2b87b7d1f309" dependencies = [ - "object 0.21.1", + "object", "thiserror", "wasmer-compiler", "wasmer-types", @@ -1801,24 +1834,27 @@ dependencies = [ [[package]] name = "wasmer-types" -version = "1.0.0-alpha5" -source = "git+https://github.com/wasmerio/wasmer.git#5e2dc65463a49b08c14bf9fb48eb45fa7d79d4c2" +version = "1.0.0-rc1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3ea5b135db86baf39ce45f6cf98cc97d6e4234d3f75ac56a026f94bd8b68b1" dependencies = [ "cranelift-entity", "serde", + "thiserror", ] [[package]] name = "wasmer-vm" -version = "1.0.0-alpha5" -source = "git+https://github.com/wasmerio/wasmer.git#5e2dc65463a49b08c14bf9fb48eb45fa7d79d4c2" +version = "1.0.0-rc1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d766b8db150b7e524c83b244e14a1180bf919b4f8bea6f063bae9a8e8d4156" dependencies = [ "backtrace", "cc", "cfg-if 0.1.10", "indexmap", "libc", - "memoffset", + "memoffset 0.6.1", "more-asserts", "region", "serde", @@ -1829,8 +1865,9 @@ dependencies = [ [[package]] name = "wasmer-wasi" -version = "1.0.0-alpha5" -source = "git+https://github.com/wasmerio/wasmer.git#5e2dc65463a49b08c14bf9fb48eb45fa7d79d4c2" +version = "1.0.0-rc1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b9e383c0a20fb697080b8e87613a0bb2e901a9f06ca710030b4a521ebcc398" dependencies = [ "bincode", "byteorder", diff --git a/Cargo.toml b/Cargo.toml index 41dd3175..792792e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,11 +27,11 @@ version = "1.3.0" features = ["unstable"] [dependencies.wasmer] -git = "https://github.com/wasmerio/wasmer.git" +version = "1.0.0-rc" optional = true [dependencies.wasmer-wasi] -git = "https://github.com/wasmerio/wasmer.git" +version = "1.0.0-rc" optional = true [features] diff --git a/src/main.rs b/src/main.rs index 82ce1a75..787a670a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -387,52 +387,48 @@ pub fn start(mut os_input: Box, opts: Opt) { .spawn(move || { // TODO: Clone shared state here move || -> Result<(), Box> { - use std::io; - use std::sync::{Arc, Mutex}; + use std::{ + io, + process::{Command, Stdio}, + }; use wasmer::{Exports, Function, Instance, Module, Store, Value}; - use wasmer_wasi::WasiState; + use wasmer_wasi::{Pipe, WasiEnv, WasiState}; + let store = Store::default(); println!("Compiling module..."); - // FIXME: Switch to a higher performance compiler (`Store::default()`) and cache this on disk - // I could use `(de)serialize_to_file()` for that + // FIXME: Cache this compiled module on disk. I could use `(de)serialize_to_file()` for that let module = if let Ok(m) = Module::from_file(&store, "strider.wasm") { m } else { return Ok(()); // Just abort this thread quietly if the WASM isn't found }; - // FIXME: Upstream the `Pipe` struct - //let output = fluff::Pipe::new(); - //let input = fluff::Pipe::new(); + let output = Pipe::new(); + let input = Pipe::new(); let mut wasi_env = WasiState::new("mosaic") .env("CLICOLOR_FORCE", "1") .preopen(|p| { - p.directory(".") // TODO: Change this to a more meaningful dir + p.directory(".") // FIXME: Change this to a more meaningful dir .alias(".") .read(true) .write(true) .create(true) })? - //.stdin(Box::new(input)) - //.stdout(Box::new(output)) + .stdin(Box::new(input)) + .stdout(Box::new(output)) .finalize()?; let mut import_object = wasi_env.import_object(&module)?; // FIXME: Upstream an `ImportObject` merge method let mut host_exports = Exports::new(); - /* host_exports.insert( + host_exports.insert( "host_open_file", - Function::new_native_with_env(&store, Arc::clone(&wasi_env.state), host_open_file), - ); */ - fn noop() {} - host_exports.insert("host_open_file", Function::new_native(&store, noop)); + Function::new_native_with_env(&store, wasi_env.clone(), host_open_file), + ); import_object.register("mosaic", host_exports); let instance = Instance::new(&module, &import_object)?; - // WASI requires to explicitly set the memory for the `WasiEnv` - wasi_env.set_memory(instance.exports.get_memory("memory")?.clone()); - let start = instance.exports.get_function("_start")?; let handle_key = instance.exports.get_function("handle_key")?; let draw = instance.exports.get_function("draw")?; @@ -442,40 +438,59 @@ pub fn start(mut os_input: Box, opts: Opt) { #[warn(clippy::never_loop)] loop { - break; - //let (cols, rows) = terminal::size()?; - //draw.call(&[Value::I32(rows as i32), Value::I32(cols as i32)])?; + let (cols, rows) = (80, 24); //terminal::size()?; + draw.call(&[Value::I32(rows), Value::I32(cols)])?; - // FIXME: This downcasting mess needs to be abstracted away - /* let mut state = wasi_env.state(); - let wasi_file = state.fs.stdout_mut()?.as_mut().unwrap(); - let output: &mut fluff::Pipe = wasi_file.downcast_mut().unwrap(); // Needed because raw mode doesn't implicitly return to the start of the line write!( io::stdout(), "{}\n\r", - output.to_string().lines().collect::>().join("\n\r") + wasi_stdout(&wasi_env) + .lines() + .collect::>() + .join("\n\r") )?; - output.clear(); - let wasi_file = state.fs.stdin_mut()?.as_mut().unwrap(); - let input: &mut fluff::Pipe = wasi_file.downcast_mut().unwrap(); - input.clear(); */ - - /* match event::read()? { + /* match event::read().unwrap() { Event::Key(KeyEvent { code: KeyCode::Char('q'), .. }) => break, Event::Key(e) => { - writeln!(input, "{}\r", serde_json::to_string(&e)?)?; - drop(state); - // Need to release the implicit `state` mutex or I deadlock! + wasi_write_string(&wasi_env, serde_json::to_string(&e).unwrap()); handle_key.call(&[])?; } _ => (), } */ + break; } + + fn host_open_file(wasi_env: &WasiEnv) { + Command::new("xdg-open") + .arg(format!( + "./{}", + wasi_stdout(wasi_env).lines().next().unwrap() + )) + .stderr(Stdio::null()) + .spawn() + .unwrap(); + } + + // FIXME: Unwrap city + fn wasi_stdout(wasi_env: &WasiEnv) -> String { + let mut state = wasi_env.state(); + let wasi_file = state.fs.stdout_mut().unwrap().as_mut().unwrap(); + let mut buf = String::new(); + wasi_file.read_to_string(&mut buf).unwrap(); + buf + } + + fn _wasi_write_string(wasi_env: &WasiEnv, buf: &str) { + let mut state = wasi_env.state(); + let wasi_file = state.fs.stdin_mut().unwrap().as_mut().unwrap(); + writeln!(wasi_file, "{}\r", buf).unwrap(); + } + debug_log_to_file("WASM module loaded and exited cleanly :)".to_string())?; Ok(()) }() From 0049b26bf8610c96d0a340c110c71a0d08507032 Mon Sep 17 00:00:00 2001 From: Brooks J Rady Date: Tue, 29 Dec 2020 12:49:35 +0000 Subject: [PATCH 2/3] Move some wasm code to it's own module --- src/main.rs | 51 +++++++++----------------------------------------- src/wasm_vm.rs | 41 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 42 deletions(-) create mode 100644 src/wasm_vm.rs diff --git a/src/main.rs b/src/main.rs index 787a670a..7b9c67f8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,8 @@ mod screen; mod tab; mod terminal_pane; mod utils; +#[cfg(feature = "wasm-wip")] +mod wasm_vm; use std::io::Write; use std::os::unix::net::UnixStream; @@ -387,12 +389,10 @@ pub fn start(mut os_input: Box, opts: Opt) { .spawn(move || { // TODO: Clone shared state here move || -> Result<(), Box> { - use std::{ - io, - process::{Command, Stdio}, - }; - use wasmer::{Exports, Function, Instance, Module, Store, Value}; - use wasmer_wasi::{Pipe, WasiEnv, WasiState}; + use crate::wasm_vm::{mosaic_imports, wasi_stdout}; + use std::io; + use wasmer::{ChainableNamedResolver, Instance, Module, Store, Value}; + use wasmer_wasi::{Pipe, WasiState}; let store = Store::default(); @@ -419,15 +419,9 @@ pub fn start(mut os_input: Box, opts: Opt) { .stdout(Box::new(output)) .finalize()?; - let mut import_object = wasi_env.import_object(&module)?; - // FIXME: Upstream an `ImportObject` merge method - let mut host_exports = Exports::new(); - host_exports.insert( - "host_open_file", - Function::new_native_with_env(&store, wasi_env.clone(), host_open_file), - ); - import_object.register("mosaic", host_exports); - let instance = Instance::new(&module, &import_object)?; + let wasi = wasi_env.import_object(&module)?; + let mosaic = mosaic_imports(&store, &wasi_env); + let instance = Instance::new(&module, &mosaic.chain_back(wasi))?; let start = instance.exports.get_function("_start")?; let handle_key = instance.exports.get_function("handle_key")?; @@ -464,33 +458,6 @@ pub fn start(mut os_input: Box, opts: Opt) { } */ break; } - - fn host_open_file(wasi_env: &WasiEnv) { - Command::new("xdg-open") - .arg(format!( - "./{}", - wasi_stdout(wasi_env).lines().next().unwrap() - )) - .stderr(Stdio::null()) - .spawn() - .unwrap(); - } - - // FIXME: Unwrap city - fn wasi_stdout(wasi_env: &WasiEnv) -> String { - let mut state = wasi_env.state(); - let wasi_file = state.fs.stdout_mut().unwrap().as_mut().unwrap(); - let mut buf = String::new(); - wasi_file.read_to_string(&mut buf).unwrap(); - buf - } - - fn _wasi_write_string(wasi_env: &WasiEnv, buf: &str) { - let mut state = wasi_env.state(); - let wasi_file = state.fs.stdin_mut().unwrap().as_mut().unwrap(); - writeln!(wasi_file, "{}\r", buf).unwrap(); - } - debug_log_to_file("WASM module loaded and exited cleanly :)".to_string())?; Ok(()) }() diff --git a/src/wasm_vm.rs b/src/wasm_vm.rs new file mode 100644 index 00000000..334ad0b1 --- /dev/null +++ b/src/wasm_vm.rs @@ -0,0 +1,41 @@ +use std::process::{Command, Stdio}; +use wasmer::{imports, Function, ImportObject, Store}; +use wasmer_wasi::WasiEnv; + +// Plugin API ----------------------------------------------------------------- + +pub fn mosaic_imports(store: &Store, wasi_env: &WasiEnv) -> ImportObject { + imports! { + "mosaic" => { + "host_open_file" => Function::new_native_with_env(store, wasi_env.clone(), host_open_file) + } + } +} + +fn host_open_file(wasi_env: &WasiEnv) { + Command::new("xdg-open") + .arg(format!( + "./{}", + wasi_stdout(wasi_env).lines().next().unwrap() + )) + .stderr(Stdio::null()) + .spawn() + .unwrap(); +} + +// Helper Functions ----------------------------------------------------------- + +// FIXME: Unwrap city +pub fn wasi_stdout(wasi_env: &WasiEnv) -> String { + let mut state = wasi_env.state(); + let wasi_file = state.fs.stdout_mut().unwrap().as_mut().unwrap(); + let mut buf = String::new(); + wasi_file.read_to_string(&mut buf).unwrap(); + buf +} + +pub fn _wasi_write_string(wasi_env: &WasiEnv, buf: &str) { + let mut state = wasi_env.state(); + let wasi_file = state.fs.stdin_mut().unwrap().as_mut().unwrap(); + writeln!(wasi_file, "{}\r", buf).unwrap(); +} From b0c7259266f21652944cf1c74eb42682d9c28fa1 Mon Sep 17 00:00:00 2001 From: Brooks J Rady Date: Tue, 29 Dec 2020 16:28:34 +0000 Subject: [PATCH 3/3] It's now possible to load WASM files from layouts! --- src/errors.rs | 26 +++ src/layout.rs | 14 ++ src/main.rs | 163 +++++++++++------- .../fixtures/layouts/panes-with-plugins.yaml | 17 ++ src/wasm_vm.rs | 12 +- 5 files changed, 165 insertions(+), 67 deletions(-) create mode 100644 src/tests/fixtures/layouts/panes-with-plugins.yaml diff --git a/src/errors.rs b/src/errors.rs index f2e68058..e4788e20 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -101,6 +101,8 @@ impl Display for ErrorContext { pub enum ContextType { Screen(ScreenContext), Pty(PtyContext), + #[cfg(feature = "wasm-wip")] + Plugin(PluginContext), App(AppContext), IPCServer, StdinHandler, @@ -115,6 +117,8 @@ impl Display for ContextType { match *self { ContextType::Screen(c) => write!(f, "{}screen_thread: {}{:?}", purple, green, c), ContextType::Pty(c) => write!(f, "{}pty_thread: {}{:?}", purple, green, c), + #[cfg(feature = "wasm-wip")] + ContextType::Plugin(c) => write!(f, "{}plugin_thread: {}{:?}", purple, green, c), ContextType::App(c) => write!(f, "{}main_thread: {}{:?}", purple, green, c), ContextType::IPCServer => write!(f, "{}ipc_server: {}AcceptInput", purple, green), ContextType::StdinHandler => { @@ -220,6 +224,28 @@ impl From<&PtyInstruction> for PtyContext { } } +// FIXME: This whole pattern *needs* a macro eventually, it's soul-crushing to write +#[cfg(feature = "wasm-wip")] +use crate::wasm_vm::PluginInstruction; +#[cfg(feature = "wasm-wip")] +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum PluginContext { + Load, + Unload, + Quit, +} + +#[cfg(feature = "wasm-wip")] +impl From<&PluginInstruction> for PluginContext { + fn from(plugin_instruction: &PluginInstruction) -> Self { + match *plugin_instruction { + PluginInstruction::Load(_) => PluginContext::Load, + PluginInstruction::Unload(_) => PluginContext::Unload, + PluginInstruction::Quit => PluginContext::Quit, + } + } +} + #[derive(Debug, Clone, Copy, PartialEq)] pub enum AppContext { Exit, diff --git a/src/layout.rs b/src/layout.rs index 6f110ee1..1e876532 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -140,6 +140,8 @@ pub struct Layout { pub parts: Vec, #[serde(skip_serializing_if = "Option::is_none")] pub split_size: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub plugin: Option, } impl Layout { @@ -171,6 +173,18 @@ impl Layout { } total_panes } + + // FIXME: I probably shouldn't exist, much less with PathBuf (use &Path) + #[cfg(feature = "wasm-wip")] + pub fn list_plugins(&self) -> Vec<&PathBuf> { + dbg!(&self); + let mut plugins: Vec<_> = self.parts.iter().flat_map(Layout::list_plugins).collect(); + if let Some(path) = &self.plugin { + plugins.push(path); + } + plugins + } + pub fn position_panes_in_space(&self, space: &PositionAndSize) -> Vec { split_space(space, &self) } diff --git a/src/main.rs b/src/main.rs index 7b9c67f8..526a67d2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -159,6 +159,18 @@ pub fn start(mut os_input: Box, opts: Opt) { ) = channel(); let mut send_pty_instructions = SenderWithContext::new(err_ctx, SenderType::Sender(send_pty_instructions)); + + #[cfg(feature = "wasm-wip")] + use crate::wasm_vm::PluginInstruction; + #[cfg(feature = "wasm-wip")] + let (send_plugin_instructions, receive_plugin_instructions): ( + Sender<(PluginInstruction, ErrorContext)>, + Receiver<(PluginInstruction, ErrorContext)>, + ) = channel(); + #[cfg(feature = "wasm-wip")] + let send_plugin_instructions = + SenderWithContext::new(err_ctx, SenderType::Sender(send_plugin_instructions)); + let (send_app_instructions, receive_app_instructions): ( SyncSender<(AppInstruction, ErrorContext)>, Receiver<(AppInstruction, ErrorContext)>, @@ -195,8 +207,17 @@ pub fn start(mut os_input: Box, opts: Opt) { .name("pty".to_string()) .spawn({ let mut command_is_executing = command_is_executing.clone(); + #[cfg(feature = "wasm-wip")] + let send_plugin_instructions = send_plugin_instructions.clone(); move || { if let Some(layout) = maybe_layout { + #[cfg(feature = "wasm-wip")] + for plugin_path in layout.list_plugins() { + dbg!(send_plugin_instructions + .send(PluginInstruction::Load(plugin_path.clone()))) + .unwrap(); + } + pty_bus.spawn_terminals_for_layout(layout); } else { let pid = pty_bus.spawn_terminal(None); @@ -387,83 +408,89 @@ pub fn start(mut os_input: Box, opts: Opt) { thread::Builder::new() .name("wasm".to_string()) .spawn(move || { - // TODO: Clone shared state here - move || -> Result<(), Box> { - use crate::wasm_vm::{mosaic_imports, wasi_stdout}; - use std::io; - use wasmer::{ChainableNamedResolver, Instance, Module, Store, Value}; - use wasmer_wasi::{Pipe, WasiState}; + use crate::errors::PluginContext; + use crate::wasm_vm::{mosaic_imports, wasi_stdout}; + use std::io; + use wasmer::{ChainableNamedResolver, Instance, Module, Store, Value}; + use wasmer_wasi::{Pipe, WasiState}; - let store = Store::default(); + let store = Store::default(); - println!("Compiling module..."); - // FIXME: Cache this compiled module on disk. I could use `(de)serialize_to_file()` for that - let module = if let Ok(m) = Module::from_file(&store, "strider.wasm") { - m - } else { - return Ok(()); // Just abort this thread quietly if the WASM isn't found - }; + loop { + let (event, mut err_ctx) = receive_plugin_instructions + .recv() + .expect("failed to receive event on channel"); + err_ctx.add_call(ContextType::Plugin(PluginContext::from(&event))); + // FIXME: Clueless on how many of these lines I need... + // screen.send_app_instructions.update(err_ctx); + // screen.send_pty_instructions.update(err_ctx); + match event { + PluginInstruction::Load(path) => { + // FIXME: Cache this compiled module on disk. I could use `(de)serialize_to_file()` for that + let module = Module::from_file(&store, path).unwrap(); - let output = Pipe::new(); - let input = Pipe::new(); - let mut wasi_env = WasiState::new("mosaic") - .env("CLICOLOR_FORCE", "1") - .preopen(|p| { - p.directory(".") // FIXME: Change this to a more meaningful dir - .alias(".") - .read(true) - .write(true) - .create(true) - })? - .stdin(Box::new(input)) - .stdout(Box::new(output)) - .finalize()?; + let output = Pipe::new(); + let input = Pipe::new(); + let mut wasi_env = WasiState::new("mosaic") + .env("CLICOLOR_FORCE", "1") + .preopen(|p| { + p.directory(".") // FIXME: Change this to a more meaningful dir + .alias(".") + .read(true) + .write(true) + .create(true) + }).unwrap() + .stdin(Box::new(input)) + .stdout(Box::new(output)) + .finalize().unwrap(); - let wasi = wasi_env.import_object(&module)?; - let mosaic = mosaic_imports(&store, &wasi_env); - let instance = Instance::new(&module, &mosaic.chain_back(wasi))?; + let wasi = wasi_env.import_object(&module).unwrap(); + let mosaic = mosaic_imports(&store, &wasi_env); + let instance = Instance::new(&module, &mosaic.chain_back(wasi)).unwrap(); - let start = instance.exports.get_function("_start")?; - let handle_key = instance.exports.get_function("handle_key")?; - let draw = instance.exports.get_function("draw")?; + let start = instance.exports.get_function("_start").unwrap(); + let handle_key = instance.exports.get_function("handle_key").unwrap(); + let draw = instance.exports.get_function("draw").unwrap(); - // This eventually calls the `.init()` method - start.call(&[])?; + // This eventually calls the `.init()` method + start.call(&[]).unwrap(); - #[warn(clippy::never_loop)] - loop { - let (cols, rows) = (80, 24); //terminal::size()?; - draw.call(&[Value::I32(rows), Value::I32(cols)])?; + #[warn(clippy::never_loop)] + loop { + let (cols, rows) = (80, 24); //terminal::size()?; + draw.call(&[Value::I32(rows), Value::I32(cols)]).unwrap(); - // Needed because raw mode doesn't implicitly return to the start of the line - write!( - io::stdout(), - "{}\n\r", - wasi_stdout(&wasi_env) - .lines() - .collect::>() - .join("\n\r") - )?; + // Needed because raw mode doesn't implicitly return to the start of the line + write!( + io::stdout(), + "{}\n\r", + wasi_stdout(&wasi_env) + .lines() + .collect::>() + .join("\n\r") + ).unwrap(); - /* match event::read().unwrap() { - Event::Key(KeyEvent { - code: KeyCode::Char('q'), - .. - }) => break, - Event::Key(e) => { - wasi_write_string(&wasi_env, serde_json::to_string(&e).unwrap()); - handle_key.call(&[])?; + /* match event::read().unwrap() { + Event::Key(KeyEvent { + code: KeyCode::Char('q'), + .. + }) => break, + Event::Key(e) => { + wasi_write_string(&wasi_env, serde_json::to_string(&e).unwrap()); + handle_key.call(&[])?; + } + _ => (), + } */ + break; } - _ => (), - } */ - break; + debug_log_to_file("WASM module loaded and exited cleanly :)".to_string()).unwrap(); + } + PluginInstruction::Quit => break, + i => panic!("Yo, dawg, nice job calling the wasm thread!\n {:?} is defo not implemented yet...", i), } - debug_log_to_file("WASM module loaded and exited cleanly :)".to_string())?; - Ok(()) - }() - .unwrap() - }) - .unwrap(), + } + } + ).unwrap(), ); // TODO: currently we don't push this into active_threads @@ -557,11 +584,15 @@ pub fn start(mut os_input: Box, opts: Opt) { AppInstruction::Exit => { let _ = send_screen_instructions.send(ScreenInstruction::Quit); let _ = send_pty_instructions.send(PtyInstruction::Quit); + #[cfg(feature = "wasm-wip")] + let _ = send_plugin_instructions.send(PluginInstruction::Quit); break; } AppInstruction::Error(backtrace) => { let _ = send_screen_instructions.send(ScreenInstruction::Quit); let _ = send_pty_instructions.send(PtyInstruction::Quit); + #[cfg(feature = "wasm-wip")] + let _ = send_plugin_instructions.send(PluginInstruction::Quit); os_input.unset_raw_mode(0); let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1); let error = format!("{}\n{}", goto_start_of_last_line, backtrace); diff --git a/src/tests/fixtures/layouts/panes-with-plugins.yaml b/src/tests/fixtures/layouts/panes-with-plugins.yaml new file mode 100644 index 00000000..09913bb4 --- /dev/null +++ b/src/tests/fixtures/layouts/panes-with-plugins.yaml @@ -0,0 +1,17 @@ +--- +direction: Horizontal +parts: + - direction: Vertical + parts: + - direction: Horizontal + split_size: + Percent: 20 + plugin: strider.wasm + - direction: Horizontal + split_size: + Percent: 80 + split_size: + Percent: 80 + - direction: Vertical + split_size: + Percent: 20 diff --git a/src/wasm_vm.rs b/src/wasm_vm.rs index 334ad0b1..f07d9e48 100644 --- a/src/wasm_vm.rs +++ b/src/wasm_vm.rs @@ -1,7 +1,17 @@ -use std::process::{Command, Stdio}; +use std::{ + path::PathBuf, + process::{Command, Stdio}, +}; use wasmer::{imports, Function, ImportObject, Store}; use wasmer_wasi::WasiEnv; +#[derive(Clone, Debug)] +pub enum PluginInstruction { + Load(PathBuf), + Unload(u32), + Quit, +} + // Plugin API ----------------------------------------------------------------- pub fn mosaic_imports(store: &Store, wasi_env: &WasiEnv) -> ImportObject {