aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authora-kenji <aks.kenji@protonmail.com>2021-04-27 21:40:13 +0200
committera-kenji <aks.kenji@protonmail.com>2021-04-27 21:40:13 +0200
commit55d2a4cea919e3ab9fb3f0e4c383cb99f0ce2fdb (patch)
treea7728559cce50495cda7e875e1c1b16681bcde8a /src
parentConfig Loading (diff)
parentfeat(plugin): simple timers implemented via the `set_timeout()` call (diff)
downloadsl0ppij-55d2a4cea919e3ab9fb3f0e4c383cb99f0ce2fdb.tar.gz
sl0ppij-55d2a4cea919e3ab9fb3f0e4c383cb99f0ce2fdb.tar.xz
sl0ppij-55d2a4cea919e3ab9fb3f0e4c383cb99f0ce2fdb.zip
Merge branch 'main' of https://github.com/zellij-org/zellij into config-loading
Diffstat (limited to 'src')
-rw-r--r--src/common/mod.rs16
-rw-r--r--src/common/wasm_vm.rs106
2 files changed, 91 insertions, 31 deletions
diff --git a/src/common/mod.rs b/src/common/mod.rs
index a45b23a4..ec2efe58 100644
--- a/src/common/mod.rs
+++ b/src/common/mod.rs
@@ -40,8 +40,7 @@ use pty_bus::{PtyBus, PtyInstruction};
use screen::{Screen, ScreenInstruction};
use serde::{Deserialize, Serialize};
use utils::consts::ZELLIJ_IPC_PIPE;
-use wasm_vm::PluginEnv;
-use wasm_vm::{wasi_stdout, wasi_write_string, zellij_imports, PluginInstruction};
+use wasm_vm::{wasi_read_string, wasi_write_object, zellij_exports, PluginEnv, PluginInstruction};
use wasmer::{ChainableNamedResolver, Instance, Module, Store, Value};
use wasmer_wasi::{Pipe, WasiState};
use zellij_tile::data::{EventType, ModeInfo};
@@ -460,6 +459,7 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
let send_pty_instructions = send_pty_instructions.clone();
let send_screen_instructions = send_screen_instructions.clone();
let send_app_instructions = send_app_instructions.clone();
+ let send_plugin_instructions = send_plugin_instructions.clone();
let store = Store::default();
let mut plugin_id = 0;
@@ -504,16 +504,17 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
send_pty_instructions: send_pty_instructions.clone(),
send_screen_instructions: send_screen_instructions.clone(),
send_app_instructions: send_app_instructions.clone(),
+ send_plugin_instructions: send_plugin_instructions.clone(),
wasi_env,
subscriptions: Arc::new(Mutex::new(HashSet::new())),
};
- let zellij = zellij_imports(&store, &plugin_env);
+ let zellij = zellij_exports(&store, &plugin_env);
let instance = Instance::new(&module, &zellij.chain_back(wasi)).unwrap();
let start = instance.exports.get_function("_start").unwrap();
- // This eventually calls the `.init()` method
+ // This eventually calls the `.load()` method
start.call(&[]).unwrap();
plugin_map.insert(plugin_id, (instance, plugin_env));
@@ -527,10 +528,7 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
let event_type = EventType::from_str(&event.to_string()).unwrap();
if (pid.is_none() || pid == Some(i)) && subs.contains(&event_type) {
let update = instance.exports.get_function("update").unwrap();
- wasi_write_string(
- &plugin_env.wasi_env,
- &serde_json::to_string(&event).unwrap(),
- );
+ wasi_write_object(&plugin_env.wasi_env, &event);
update.call(&[]).unwrap();
}
}
@@ -545,7 +543,7 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
.call(&[Value::I32(rows as i32), Value::I32(cols as i32)])
.unwrap();
- buf_tx.send(wasi_stdout(&plugin_env.wasi_env)).unwrap();
+ buf_tx.send(wasi_read_string(&plugin_env.wasi_env)).unwrap();
}
PluginInstruction::Unload(pid) => drop(plugin_map.remove(&pid)),
PluginInstruction::Quit => break,
diff --git a/src/common/wasm_vm.rs b/src/common/wasm_vm.rs
index 29b110ae..bd98a85b 100644
--- a/src/common/wasm_vm.rs
+++ b/src/common/wasm_vm.rs
@@ -1,11 +1,15 @@
+use serde::{de::DeserializeOwned, Serialize};
use std::{
collections::HashSet,
path::PathBuf,
+ process,
sync::{mpsc::Sender, Arc, Mutex},
+ thread,
+ time::{Duration, Instant},
};
use wasmer::{imports, Function, ImportObject, Store, WasmerEnv};
use wasmer_wasi::WasiEnv;
-use zellij_tile::data::{Event, EventType};
+use zellij_tile::data::{Event, EventType, PluginIds};
use super::{
pty_bus::PtyInstruction, screen::ScreenInstruction, AppInstruction, PaneId, SenderWithContext,
@@ -23,48 +27,53 @@ pub enum PluginInstruction {
#[derive(WasmerEnv, Clone)]
pub struct PluginEnv {
pub plugin_id: u32,
+ // FIXME: This should be a big bundle of all of the channels
pub send_screen_instructions: SenderWithContext<ScreenInstruction>,
pub send_app_instructions: SenderWithContext<AppInstruction>,
- pub send_pty_instructions: SenderWithContext<PtyInstruction>, // FIXME: This should be a big bundle of all of the channels
+ pub send_pty_instructions: SenderWithContext<PtyInstruction>,
+ pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
pub wasi_env: WasiEnv,
pub subscriptions: Arc<Mutex<HashSet<EventType>>>,
}
// Plugin API ---------------------------------------------------------------------------------------------------------
-pub fn zellij_imports(store: &Store, plugin_env: &PluginEnv) -> ImportObject {
- imports! {
- "zellij" => {
- "host_subscribe" => Function::new_native_with_env(store, plugin_env.clone(), host_subscribe),
- "host_unsubscribe" => Function::new_native_with_env(store, plugin_env.clone(), host_unsubscribe),
- "host_open_file" => Function::new_native_with_env(store, plugin_env.clone(), host_open_file),
- "host_set_invisible_borders" => Function::new_native_with_env(store, plugin_env.clone(), host_set_invisible_borders),
- "host_set_max_height" => Function::new_native_with_env(store, plugin_env.clone(), host_set_max_height),
- "host_set_selectable" => Function::new_native_with_env(store, plugin_env.clone(), host_set_selectable),
+pub fn zellij_exports(store: &Store, plugin_env: &PluginEnv) -> ImportObject {
+ macro_rules! zellij_export {
+ ($($host_function:ident),+ $(,)?) => {
+ imports! {
+ "zellij" => {
+ $(stringify!($host_function) =>
+ Function::new_native_with_env(store, plugin_env.clone(), $host_function),)+
+ }
+ }
}
}
+
+ zellij_export! {
+ host_subscribe,
+ host_unsubscribe,
+ host_set_invisible_borders,
+ host_set_max_height,
+ host_set_selectable,
+ host_get_plugin_ids,
+ host_open_file,
+ host_set_timeout,
+ }
}
fn host_subscribe(plugin_env: &PluginEnv) {
let mut subscriptions = plugin_env.subscriptions.lock().unwrap();
- let new: HashSet<EventType> = serde_json::from_str(&wasi_stdout(&plugin_env.wasi_env)).unwrap();
+ let new: HashSet<EventType> = wasi_read_object(&plugin_env.wasi_env);
subscriptions.extend(new);
}
fn host_unsubscribe(plugin_env: &PluginEnv) {
let mut subscriptions = plugin_env.subscriptions.lock().unwrap();
- let old: HashSet<EventType> = serde_json::from_str(&wasi_stdout(&plugin_env.wasi_env)).unwrap();
+ let old: HashSet<EventType> = wasi_read_object(&plugin_env.wasi_env);
subscriptions.retain(|k| !old.contains(k));
}
-fn host_open_file(plugin_env: &PluginEnv) {
- let path = PathBuf::from(wasi_stdout(&plugin_env.wasi_env).lines().next().unwrap());
- plugin_env
- .send_pty_instructions
- .send(PtyInstruction::SpawnTerminal(Some(path)))
- .unwrap();
-}
-
fn host_set_selectable(plugin_env: &PluginEnv, selectable: i32) {
let selectable = selectable != 0;
plugin_env
@@ -98,10 +107,54 @@ fn host_set_invisible_borders(plugin_env: &PluginEnv, invisible_borders: i32) {
.unwrap()
}
+fn host_get_plugin_ids(plugin_env: &PluginEnv) {
+ let ids = PluginIds {
+ plugin_id: plugin_env.plugin_id,
+ zellij_pid: process::id(),
+ };
+ wasi_write_object(&plugin_env.wasi_env, &ids);
+}
+
+fn host_open_file(plugin_env: &PluginEnv) {
+ let path: PathBuf = wasi_read_object(&plugin_env.wasi_env);
+ plugin_env
+ .send_pty_instructions
+ .send(PtyInstruction::SpawnTerminal(Some(path)))
+ .unwrap();
+}
+
+fn host_set_timeout(plugin_env: &PluginEnv, secs: f64) {
+ // There is a fancy, high-performance way to do this with zero additional threads:
+ // If the plugin thread keeps a BinaryHeap of timer structs, it can manage multiple and easily `.peek()` at the
+ // next time to trigger in O(1) time. Once the wake-up time is known, the `wasm` thread can use `recv_timeout()`
+ // to wait for an event with the timeout set to be the time of the next wake up. If events come in in the meantime,
+ // they are handled, but if the timeout triggers, we replace the event from `recv()` with an
+ // `Update(pid, TimerEvent)` and pop the timer from the Heap (or reschedule it). No additional threads for as many
+ // timers as we'd like.
+ //
+ // But that's a lot of code, and this is a few lines:
+ let send_plugin_instructions = plugin_env.send_plugin_instructions.clone();
+ let update_target = Some(plugin_env.plugin_id);
+ thread::spawn(move || {
+ let start_time = Instant::now();
+ thread::sleep(Duration::from_secs_f64(secs));
+ // FIXME: The way that elapsed time is being calculated here is not exact; it doesn't take into account the
+ // time it takes an event to actually reach the plugin after it's sent to the `wasm` thread.
+ let elapsed_time = Instant::now().duration_since(start_time).as_secs_f64();
+
+ send_plugin_instructions
+ .send(PluginInstruction::Update(
+ update_target,
+ Event::Timer(elapsed_time),
+ ))
+ .unwrap();
+ });
+}
+
// Helper Functions ---------------------------------------------------------------------------------------------------
// FIXME: Unwrap city
-pub fn wasi_stdout(wasi_env: &WasiEnv) -> String {
+pub fn wasi_read_string(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();
@@ -114,3 +167,12 @@ pub fn wasi_write_string(wasi_env: &WasiEnv, buf: &str) {
let wasi_file = state.fs.stdin_mut().unwrap().as_mut().unwrap();
writeln!(wasi_file, "{}\r", buf).unwrap();
}
+
+pub fn wasi_write_object(wasi_env: &WasiEnv, object: &impl Serialize) {
+ wasi_write_string(wasi_env, &serde_json::to_string(&object).unwrap());
+}
+
+pub fn wasi_read_object<T: DeserializeOwned>(wasi_env: &WasiEnv) -> T {
+ let json = wasi_read_string(wasi_env);
+ serde_json::from_str(&json).unwrap()
+}