mirror of
https://github.com/zellij-org/zellij.git
synced 2024-12-25 10:15:09 +03:00
feat(plugin): simple timers implemented via the set_timeout()
call
This commit is contained in:
commit
3e8c810f97
@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
||||
* Completions are not assets anymore, but commands `option --generate-completion [shell]` (https://github.com/zellij-org/zellij/pull/369)
|
||||
* Fixes in the default configuration `default.yaml` file. Adds initial tmux-compat keybindings `tmux.yaml` (https://github.com/zellij-org/zellij/pull/362)
|
||||
* Added the `get_plugin_ids()` query function to the plugin API (https://github.com/zellij-org/zellij/pull/392)
|
||||
* Implemented simple plugin timers via the `set_timeout()` call (https://github.com/zellij-org/zellij/pull/394)
|
||||
|
||||
## [0.5.1] - 2021-04-23
|
||||
* Change config to flag (https://github.com/zellij-org/zellij/pull/300)
|
||||
|
@ -40,7 +40,7 @@ use pty_bus::{PtyBus, PtyInstruction};
|
||||
use screen::{Screen, ScreenInstruction};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utils::consts::ZELLIJ_IPC_PIPE;
|
||||
use wasm_vm::{wasi_stdout, wasi_write_json, zellij_exports, PluginEnv, 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};
|
||||
@ -457,6 +457,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;
|
||||
@ -501,6 +502,7 @@ 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())),
|
||||
};
|
||||
@ -510,7 +512,7 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
|
||||
|
||||
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));
|
||||
@ -524,7 +526,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_json(&plugin_env.wasi_env, &event);
|
||||
wasi_write_object(&plugin_env.wasi_env, &event);
|
||||
update.call(&[]).unwrap();
|
||||
}
|
||||
}
|
||||
@ -539,7 +541,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,
|
||||
|
@ -1,9 +1,11 @@
|
||||
use serde::Serialize;
|
||||
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;
|
||||
@ -25,9 +27,11 @@ 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>>>,
|
||||
}
|
||||
@ -54,18 +58,19 @@ pub fn zellij_exports(store: &Store, plugin_env: &PluginEnv) -> ImportObject {
|
||||
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));
|
||||
}
|
||||
|
||||
@ -107,21 +112,49 @@ fn host_get_plugin_ids(plugin_env: &PluginEnv) {
|
||||
plugin_id: plugin_env.plugin_id,
|
||||
zellij_pid: process::id(),
|
||||
};
|
||||
wasi_write_json(&plugin_env.wasi_env, &ids);
|
||||
wasi_write_object(&plugin_env.wasi_env, &ids);
|
||||
}
|
||||
|
||||
fn host_open_file(plugin_env: &PluginEnv) {
|
||||
let path = PathBuf::from(wasi_stdout(&plugin_env.wasi_env).lines().next().unwrap());
|
||||
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();
|
||||
@ -135,6 +168,11 @@ pub fn wasi_write_string(wasi_env: &WasiEnv, buf: &str) {
|
||||
writeln!(wasi_file, "{}\r", buf).unwrap();
|
||||
}
|
||||
|
||||
pub fn wasi_write_json(wasi_env: &WasiEnv, object: &impl Serialize) {
|
||||
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()
|
||||
}
|
||||
|
@ -23,9 +23,7 @@ pub enum Key {
|
||||
Esc,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, PartialEq, Eq, Hash, EnumDiscriminants, ToString, Serialize, Deserialize,
|
||||
)]
|
||||
#[derive(Debug, Clone, PartialEq, EnumDiscriminants, ToString, Serialize, Deserialize)]
|
||||
#[strum_discriminants(derive(EnumString, Hash, Serialize, Deserialize))]
|
||||
#[strum_discriminants(name(EventType))]
|
||||
#[non_exhaustive]
|
||||
@ -33,6 +31,7 @@ pub enum Event {
|
||||
ModeUpdate(ModeInfo),
|
||||
TabUpdate(Vec<TabInfo>),
|
||||
KeyPress(Key),
|
||||
Timer(f64),
|
||||
}
|
||||
|
||||
/// Describes the different input modes, which change the way that keystrokes will be interpreted.
|
||||
|
@ -1,4 +1,4 @@
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use std::{io, path::Path};
|
||||
|
||||
use crate::data::*;
|
||||
@ -6,12 +6,12 @@ use crate::data::*;
|
||||
// Subscription Handling
|
||||
|
||||
pub fn subscribe(event_types: &[EventType]) {
|
||||
println!("{}", serde_json::to_string(event_types).unwrap());
|
||||
object_to_stdout(&event_types);
|
||||
unsafe { host_subscribe() };
|
||||
}
|
||||
|
||||
pub fn unsubscribe(event_types: &[EventType]) {
|
||||
println!("{}", serde_json::to_string(event_types).unwrap());
|
||||
object_to_stdout(&event_types);
|
||||
unsafe { host_unsubscribe() };
|
||||
}
|
||||
|
||||
@ -38,10 +38,14 @@ pub fn get_plugin_ids() -> PluginIds {
|
||||
// Host Functions
|
||||
|
||||
pub fn open_file(path: &Path) {
|
||||
println!("{}", path.to_string_lossy());
|
||||
object_to_stdout(&path);
|
||||
unsafe { host_open_file() };
|
||||
}
|
||||
|
||||
pub fn set_timeout(secs: f64) {
|
||||
unsafe { host_set_timeout(secs) };
|
||||
}
|
||||
|
||||
// Internal Functions
|
||||
|
||||
#[doc(hidden)]
|
||||
@ -51,6 +55,11 @@ pub fn object_from_stdin<T: DeserializeOwned>() -> T {
|
||||
serde_json::from_str(&json).unwrap()
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn object_to_stdout(object: &impl Serialize) {
|
||||
println!("{}", serde_json::to_string(object).unwrap());
|
||||
}
|
||||
|
||||
#[link(wasm_import_module = "zellij")]
|
||||
extern "C" {
|
||||
fn host_subscribe();
|
||||
@ -60,4 +69,5 @@ extern "C" {
|
||||
fn host_set_invisible_borders(invisible_borders: i32);
|
||||
fn host_get_plugin_ids();
|
||||
fn host_open_file();
|
||||
fn host_set_timeout(secs: f64);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user