Merge pull request #126 from mosaic-org/wasm-launchable

Load WASM from Layouts (Includes PR #125)
This commit is contained in:
Brooks Rady 2020-12-30 11:41:02 +00:00 committed by GitHub
commit c7db38d0c5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 165 additions and 68 deletions

View file

@ -101,6 +101,8 @@ impl Display for ErrorContext {
pub enum ContextType { pub enum ContextType {
Screen(ScreenContext), Screen(ScreenContext),
Pty(PtyContext), Pty(PtyContext),
#[cfg(feature = "wasm-wip")]
Plugin(PluginContext),
App(AppContext), App(AppContext),
IPCServer, IPCServer,
StdinHandler, StdinHandler,
@ -115,6 +117,8 @@ impl Display for ContextType {
match *self { match *self {
ContextType::Screen(c) => write!(f, "{}screen_thread: {}{:?}", purple, green, c), ContextType::Screen(c) => write!(f, "{}screen_thread: {}{:?}", purple, green, c),
ContextType::Pty(c) => write!(f, "{}pty_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::App(c) => write!(f, "{}main_thread: {}{:?}", purple, green, c),
ContextType::IPCServer => write!(f, "{}ipc_server: {}AcceptInput", purple, green), ContextType::IPCServer => write!(f, "{}ipc_server: {}AcceptInput", purple, green),
ContextType::StdinHandler => { 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)] #[derive(Debug, Clone, Copy, PartialEq)]
pub enum AppContext { pub enum AppContext {
Exit, Exit,

View file

@ -140,6 +140,8 @@ pub struct Layout {
pub parts: Vec<Layout>, pub parts: Vec<Layout>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub split_size: Option<SplitSize>, pub split_size: Option<SplitSize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub plugin: Option<PathBuf>,
} }
impl Layout { impl Layout {
@ -171,6 +173,18 @@ impl Layout {
} }
total_panes 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<PositionAndSize> { pub fn position_panes_in_space(&self, space: &PositionAndSize) -> Vec<PositionAndSize> {
split_space(space, &self) split_space(space, &self)
} }

View file

@ -159,6 +159,18 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: Opt) {
) = channel(); ) = channel();
let mut send_pty_instructions = let mut send_pty_instructions =
SenderWithContext::new(err_ctx, SenderType::Sender(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): ( let (send_app_instructions, receive_app_instructions): (
SyncSender<(AppInstruction, ErrorContext)>, SyncSender<(AppInstruction, ErrorContext)>,
Receiver<(AppInstruction, ErrorContext)>, Receiver<(AppInstruction, ErrorContext)>,
@ -188,9 +200,17 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: Opt) {
.name("pty".to_string()) .name("pty".to_string())
.spawn({ .spawn({
let mut command_is_executing = command_is_executing.clone(); let mut command_is_executing = command_is_executing.clone();
#[cfg(feature = "wasm-wip")]
let send_plugin_instructions = send_plugin_instructions.clone();
move || { move || {
if let Some(layout) = maybe_layout { 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); pty_bus.spawn_terminals_for_layout(layout);
} else { } else {
let pid = pty_bus.spawn_terminal(None); let pid = pty_bus.spawn_terminal(None);
@ -396,83 +416,89 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: Opt) {
thread::Builder::new() thread::Builder::new()
.name("wasm".to_string()) .name("wasm".to_string())
.spawn(move || { .spawn(move || {
// TODO: Clone shared state here use crate::errors::PluginContext;
move || -> Result<(), Box<dyn std::error::Error>> { use crate::wasm_vm::{mosaic_imports, wasi_stdout};
use crate::wasm_vm::{mosaic_imports, wasi_stdout}; use std::io;
use std::io; use wasmer::{ChainableNamedResolver, Instance, Module, Store, Value};
use wasmer::{ChainableNamedResolver, Instance, Module, Store, Value}; use wasmer_wasi::{Pipe, WasiState};
use wasmer_wasi::{Pipe, WasiState};
let store = Store::default(); let store = Store::default();
println!("Compiling module..."); loop {
// FIXME: Cache this compiled module on disk. I could use `(de)serialize_to_file()` for that let (event, mut err_ctx) = receive_plugin_instructions
let module = if let Ok(m) = Module::from_file(&store, "strider.wasm") { .recv()
m .expect("failed to receive event on channel");
} else { err_ctx.add_call(ContextType::Plugin(PluginContext::from(&event)));
return Ok(()); // Just abort this thread quietly if the WASM isn't found // 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 output = Pipe::new();
let input = Pipe::new(); let input = Pipe::new();
let mut wasi_env = WasiState::new("mosaic") let mut wasi_env = WasiState::new("mosaic")
.env("CLICOLOR_FORCE", "1") .env("CLICOLOR_FORCE", "1")
.preopen(|p| { .preopen(|p| {
p.directory(".") // FIXME: Change this to a more meaningful dir p.directory(".") // FIXME: Change this to a more meaningful dir
.alias(".") .alias(".")
.read(true) .read(true)
.write(true) .write(true)
.create(true) .create(true)
})? }).unwrap()
.stdin(Box::new(input)) .stdin(Box::new(input))
.stdout(Box::new(output)) .stdout(Box::new(output))
.finalize()?; .finalize().unwrap();
let wasi = wasi_env.import_object(&module)?; let wasi = wasi_env.import_object(&module).unwrap();
let mosaic = mosaic_imports(&store, &wasi_env); let mosaic = mosaic_imports(&store, &wasi_env);
let instance = Instance::new(&module, &mosaic.chain_back(wasi))?; let instance = Instance::new(&module, &mosaic.chain_back(wasi)).unwrap();
let start = instance.exports.get_function("_start")?; let start = instance.exports.get_function("_start").unwrap();
let handle_key = instance.exports.get_function("handle_key")?; let handle_key = instance.exports.get_function("handle_key").unwrap();
let draw = instance.exports.get_function("draw")?; let draw = instance.exports.get_function("draw").unwrap();
// This eventually calls the `.init()` method // This eventually calls the `.init()` method
start.call(&[])?; start.call(&[]).unwrap();
#[warn(clippy::never_loop)] #[warn(clippy::never_loop)]
loop { loop {
let (cols, rows) = (80, 24); //terminal::size()?; let (cols, rows) = (80, 24); //terminal::size()?;
draw.call(&[Value::I32(rows), Value::I32(cols)])?; draw.call(&[Value::I32(rows), Value::I32(cols)]).unwrap();
// Needed because raw mode doesn't implicitly return to the start of the line // Needed because raw mode doesn't implicitly return to the start of the line
write!( write!(
io::stdout(), io::stdout(),
"{}\n\r", "{}\n\r",
wasi_stdout(&wasi_env) wasi_stdout(&wasi_env)
.lines() .lines()
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("\n\r") .join("\n\r")
)?; ).unwrap();
/* match event::read().unwrap() { /* match event::read().unwrap() {
Event::Key(KeyEvent { Event::Key(KeyEvent {
code: KeyCode::Char('q'), code: KeyCode::Char('q'),
.. ..
}) => break, }) => break,
Event::Key(e) => { Event::Key(e) => {
wasi_write_string(&wasi_env, serde_json::to_string(&e).unwrap()); wasi_write_string(&wasi_env, serde_json::to_string(&e).unwrap());
handle_key.call(&[])?; handle_key.call(&[])?;
}
_ => (),
} */
break;
} }
_ => (), debug_log_to_file("WASM module loaded and exited cleanly :)".to_string()).unwrap();
} */ }
break; 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 // TODO: currently we don't push this into active_threads
@ -566,11 +592,15 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: Opt) {
AppInstruction::Exit => { AppInstruction::Exit => {
let _ = send_screen_instructions.send(ScreenInstruction::Quit); let _ = send_screen_instructions.send(ScreenInstruction::Quit);
let _ = send_pty_instructions.send(PtyInstruction::Quit); let _ = send_pty_instructions.send(PtyInstruction::Quit);
#[cfg(feature = "wasm-wip")]
let _ = send_plugin_instructions.send(PluginInstruction::Quit);
break; break;
} }
AppInstruction::Error(backtrace) => { AppInstruction::Error(backtrace) => {
let _ = send_screen_instructions.send(ScreenInstruction::Quit); let _ = send_screen_instructions.send(ScreenInstruction::Quit);
let _ = send_pty_instructions.send(PtyInstruction::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); os_input.unset_raw_mode(0);
let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1); 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); let error = format!("{}\n{}", goto_start_of_last_line, backtrace);

View file

@ -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

View file

@ -1,7 +1,17 @@
use std::process::{Command, Stdio}; use std::{
path::PathBuf,
process::{Command, Stdio},
};
use wasmer::{imports, Function, ImportObject, Store}; use wasmer::{imports, Function, ImportObject, Store};
use wasmer_wasi::WasiEnv; use wasmer_wasi::WasiEnv;
#[derive(Clone, Debug)]
pub enum PluginInstruction {
Load(PathBuf),
Unload(u32),
Quit,
}
// Plugin API ----------------------------------------------------------------- // Plugin API -----------------------------------------------------------------
pub fn mosaic_imports(store: &Store, wasi_env: &WasiEnv) -> ImportObject { pub fn mosaic_imports(store: &Store, wasi_env: &WasiEnv) -> ImportObject {