summaryrefslogtreecommitdiffstats
path: root/zellij-server/src
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2023-03-27 19:19:34 +0200
committerGitHub <noreply@github.com>2023-03-27 19:19:34 +0200
commit341f9eb8c8771a59b2e4d238ba49ba88c3720d6d (patch)
tree51205536dd0789efb770dbe0095af7210a60eed3 /zellij-server/src
parent7b609b053f3aaf466258e12be53d57614c8884c7 (diff)
feat(plugins): async plugin loading (#2327)
* work * refactor(plugins): break down start plugin async function * work * loading messages * nice ui * floating panes and error handling * cleanups and conflicting plugin/direction * find exact pane when relayouting * fix plugin pane titles * kill loading tasks on exit * refactor: move stuff around * style(fmt): rustfmt * various fixes and refactors
Diffstat (limited to 'zellij-server/src')
-rw-r--r--zellij-server/src/background_jobs.rs42
-rw-r--r--zellij-server/src/panes/plugin_pane.rs40
-rw-r--r--zellij-server/src/plugins/mod.rs38
-rw-r--r--zellij-server/src/plugins/start_plugin.rs471
-rw-r--r--zellij-server/src/plugins/wasm_bridge.rs510
-rw-r--r--zellij-server/src/route.rs16
-rw-r--r--zellij-server/src/screen.rs112
-rw-r--r--zellij-server/src/tab/layout_applier.rs61
-rw-r--r--zellij-server/src/tab/mod.rs146
-rw-r--r--zellij-server/src/ui/loading_indication.rs260
-rw-r--r--zellij-server/src/ui/mod.rs1
-rw-r--r--zellij-server/src/unit/screen_tests.rs3
12 files changed, 1404 insertions, 296 deletions
diff --git a/zellij-server/src/background_jobs.rs b/zellij-server/src/background_jobs.rs
index c612c9d57..8061b4a47 100644
--- a/zellij-server/src/background_jobs.rs
+++ b/zellij-server/src/background_jobs.rs
@@ -2,6 +2,10 @@ use zellij_utils::async_std::task;
use zellij_utils::errors::{prelude::*, BackgroundJobContext, ContextType};
use std::collections::HashMap;
+use std::sync::{
+ atomic::{AtomicBool, Ordering},
+ Arc,
+};
use std::time::{Duration, Instant};
use crate::panes::PaneId;
@@ -11,6 +15,8 @@ use crate::thread_bus::Bus;
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum BackgroundJob {
DisplayPaneError(Vec<PaneId>, String),
+ AnimatePluginLoading(u32), // u32 - plugin_id
+ StopPluginLoadingAnimation(u32), // u32 - plugin_id
Exit,
}
@@ -18,16 +24,22 @@ impl From<&BackgroundJob> for BackgroundJobContext {
fn from(background_job: &BackgroundJob) -> Self {
match *background_job {
BackgroundJob::DisplayPaneError(..) => BackgroundJobContext::DisplayPaneError,
+ BackgroundJob::AnimatePluginLoading(..) => BackgroundJobContext::AnimatePluginLoading,
+ BackgroundJob::StopPluginLoadingAnimation(..) => {
+ BackgroundJobContext::StopPluginLoadingAnimation
+ },
BackgroundJob::Exit => BackgroundJobContext::Exit,
}
}
}
static FLASH_DURATION_MS: u64 = 1000;
+static PLUGIN_ANIMATION_OFFSET_DURATION_MD: u64 = 500;
pub(crate) fn background_jobs_main(bus: Bus<BackgroundJob>) -> Result<()> {
let err_context = || "failed to write to pty".to_string();
let mut running_jobs: HashMap<BackgroundJob, Instant> = HashMap::new();
+ let mut loading_plugins: HashMap<u32, Arc<AtomicBool>> = HashMap::new(); // u32 - plugin_id
loop {
let (event, mut err_ctx) = bus.recv().with_context(err_context)?;
@@ -54,7 +66,37 @@ pub(crate) fn background_jobs_main(bus: Bus<BackgroundJob>) -> Result<()> {
}
});
},
+ BackgroundJob::AnimatePluginLoading(pid) => {
+ let loading_plugin = Arc::new(AtomicBool::new(true));
+ if job_already_running(job, &mut running_jobs) {
+ continue;
+ }
+ task::spawn({
+ let senders = bus.senders.clone();
+ let loading_plugin = loading_plugin.clone();
+ async move {
+ while loading_plugin.load(Ordering::SeqCst) {
+ let _ = senders.send_to_screen(
+ ScreenInstruction::ProgressPluginLoadingOffset(pid),
+ );
+ task::sleep(std::time::Duration::from_millis(
+ PLUGIN_ANIMATION_OFFSET_DURATION_MD,
+ ))
+ .await;
+ }
+ }
+ });
+ loading_plugins.insert(pid, loading_plugin);
+ },
+ BackgroundJob::StopPluginLoadingAnimation(pid) => {
+ if let Some(loading_plugin) = loading_plugins.remove(&pid) {
+ loading_plugin.store(false, Ordering::SeqCst);
+ }
+ },
BackgroundJob::Exit => {
+ for loading_plugin in loading_plugins.values() {
+ loading_plugin.store(false, Ordering::SeqCst);
+ }
return Ok(());
},
}
diff --git a/zellij-server/src/panes/plugin_pane.rs b/zellij-server/src/panes/plugin_pane.rs
index c6896f90d..28828907e 100644
--- a/zellij-server/src/panes/plugin_pane.rs
+++ b/zellij-server/src/panes/plugin_pane.rs
@@ -6,7 +6,10 @@ use crate::panes::{grid::Grid, sixel::SixelImageStore, LinkHandler, PaneId};
use crate::plugins::PluginInstruction;
use crate::pty::VteBytes;
use crate::tab::Pane;
-use crate::ui::pane_boundaries_frame::{FrameParams, PaneFrame};
+use crate::ui::{
+ loading_indication::LoadingIndication,
+ pane_boundaries_frame::{FrameParams, PaneFrame},
+};
use crate::ClientId;
use std::cell::RefCell;
use std::rc::Rc;
@@ -67,6 +70,7 @@ pub(crate) struct PluginPane {
borderless: bool,
pane_frame_color_override: Option<(PaletteColor, Option<String>)>,
invoked_with: Option<Run>,
+ loading_indication: LoadingIndication,
}
impl PluginPane {
@@ -81,10 +85,13 @@ impl PluginPane {
terminal_emulator_color_codes: Rc<RefCell<HashMap<usize, String>>>,
link_handler: Rc<RefCell<LinkHandler>>,
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
+ currently_connected_clients: Vec<ClientId>,
style: Style,
invoked_with: Option<Run>,
) -> Self {
- Self {
+ let loading_indication = LoadingIndication::new(title.clone()).with_colors(style.colors);
+ let initial_loading_message = loading_indication.to_string();
+ let mut plugin = PluginPane {
pid,
should_render: HashMap::new(),
selectable: true,
@@ -108,7 +115,12 @@ impl PluginPane {
style,
pane_frame_color_override: None,
invoked_with,
+ loading_indication,
+ };
+ for client_id in currently_connected_clients {
+ plugin.handle_plugin_bytes(client_id, initial_loading_message.as_bytes().to_vec());
}
+ plugin
}
}
@@ -513,6 +525,24 @@ impl Pane for PluginPane {
fn set_title(&mut self, title: String) {
self.pane_title = title;
}
+ fn update_loading_indication(&mut self, loading_indication: LoadingIndication) {
+ if self.loading_indication.ended {
+ return;
+ }
+ self.loading_indication.merge(loading_indication);
+ self.handle_plugin_bytes_for_all_clients(
+ self.loading_indication.to_string().as_bytes().to_vec(),
+ );
+ }
+ fn progress_animation_offset(&mut self) {
+ if self.loading_indication.ended {
+ return;
+ }
+ self.loading_indication.progress_animation_offset();
+ self.handle_plugin_bytes_for_all_clients(
+ self.loading_indication.to_string().as_bytes().to_vec(),
+ );
+ }
}
impl PluginPane {
@@ -527,4 +557,10 @@ impl PluginPane {
fn set_client_should_render(&mut self, client_id: ClientId, should_render: bool) {
self.should_render.insert(client_id, should_render);
}
+ fn handle_plugin_bytes_for_all_clients(&mut self, bytes: VteBytes) {
+ let client_ids: Vec<ClientId> = self.grids.keys().copied().collect();
+ for client_id in client_ids {
+ self.handle_plugin_bytes(client_id, bytes.clone());
+ }
+ }
}
diff --git a/zellij-server/src/plugins/mod.rs b/zellij-server/src/plugins/mod.rs
index 1cd034490..e1884b1e6 100644
--- a/zellij-server/src/plugins/mod.rs
+++ b/zellij-server/src/plugins/mod.rs
@@ -1,8 +1,10 @@
+mod start_plugin;
mod wasm_bridge;
use log::info;
use std::{collections::HashMap, fs, path::PathBuf};
use wasmer::Store;
+use crate::screen::ScreenInstruction;
use crate::{pty::PtyInstruction, thread_bus::Bus, ClientId};
use wasm_bridge::WasmBridge;
@@ -20,7 +22,14 @@ use zellij_utils::{
#[derive(Clone, Debug)]
pub enum PluginInstruction {
- Load(RunPlugin, usize, ClientId, Size), // plugin metadata, tab_index, client_ids
+ Load(
+ Option<bool>, // should float
+ Option<String>, // pane title
+ RunPlugin,
+ usize, // tab index
+ ClientId,
+ Size,
+ ),
Update(Vec<(Option<u32>, Option<ClientId>, Event)>), // Focused plugin / broadcast, client_id, event data
Unload(u32), // plugin_id
Resize(u32, usize, usize), // plugin_id, columns, rows
@@ -33,6 +42,7 @@ pub enum PluginInstruction {
usize, // tab_index
ClientId,
),
+ ApplyCachedEvents(u32), // u32 is the plugin id
Exit,
}
@@ -47,6 +57,7 @@ impl From<&PluginInstruction> for PluginContext {
PluginInstruction::AddClient(_) => PluginContext::AddClient,
PluginInstruction::RemoveClient(_) => PluginContext::RemoveClient,
PluginInstruction::NewTab(..) => PluginContext::NewTab,
+ PluginInstruction::ApplyCachedEvents(..) => PluginContext::ApplyCachedEvents,
}
}
}
@@ -69,8 +80,21 @@ pub(crate) fn plugin_thread_main(
let (event, mut err_ctx) = bus.recv().expect("failed to receive event on channel");
err_ctx.add_call(ContextType::Plugin((&event).into()));
match event {
- PluginInstruction::Load(run, tab_index, client_id, size) => {
- wasm_bridge.load_plugin(&run, tab_index, size, client_id)?;
+ PluginInstruction::Load(should_float, pane_title, run, tab_index, client_id, size) => {
+ match wasm_bridge.load_plugin(&run, tab_index, size, client_id) {
+ Ok(plugin_id) => {
+ drop(bus.senders.send_to_screen(ScreenInstruction::AddPlugin(
+ should_float,
+ run,
+ pane_title,
+ tab_index,
+ plugin_id,
+ )));
+ },
+ Err(e) => {
+ log::error!("Failed to load plugin: {e}");
+ },
+ }
},
PluginInstruction::Update(updates) => {
wasm_bridge.update_plugins(updates)?;
@@ -126,7 +150,13 @@ pub(crate) fn plugin_thread_main(
client_id,
)));
},
- PluginInstruction::Exit => break,
+ PluginInstruction::ApplyCachedEvents(plugin_id) => {
+ wasm_bridge.apply_cached_events(plugin_id)?;
+ },
+ PluginInstruction::Exit => {
+ wasm_bridge.cleanup();
+ break;
+ },
}
}
info!("wasm main thread exits");
diff --git a/zellij-server/src/plugins/start_plugin.rs b/zellij-server/src/plugins/start_plugin.rs
new file mode 100644
index 000000000..28000bd15
--- /dev/null
+++ b/zellij-server/src/plugins/start_plugin.rs
@@ -0,0 +1,471 @@
+use crate::plugins::wasm_bridge::{wasi_read_string, zellij_exports, PluginEnv, PluginMap};
+use highway::{HighwayHash, PortableHash};
+use log::info;
+use semver::Version;
+use std::{
+ collections::{HashMap, HashSet},
+ fmt, fs,
+ path::PathBuf,
+ sync::{Arc, Mutex},
+ time::Instant,
+};
+use url::Url;
+use wasmer::{ChainableNamedResolver, Instance, Module, Store};
+use wasmer_wasi::{Pipe, WasiState};
+
+use crate::{
+ logging_pipe::LoggingPipe, screen::ScreenInstruction, thread_bus::ThreadSenders,
+ ui::loading_indication::LoadingIndication, ClientId,
+};
+
+use zellij_utils::{
+ consts::{VERSION, ZELLIJ_CACHE_DIR, ZELLIJ_TMP_DIR},
+ errors::prelude::*,
+ input::plugins::PluginConfig,
+ pane_size::Size,
+};
+
+/// Custom error for plugin version mismatch.
+///
+/// This is thrown when, during starting a plugin, it is detected that the plugin version doesn't
+/// match the zellij version. This is treated as a fatal error and leads to instantaneous
+/// termination.
+#[derive(Debug)]
+pub struct VersionMismatchError {
+ zellij_version: String,
+ plugin_version: String,
+ plugin_path: PathBuf,
+ // true for builtin plugins
+ builtin: bool,
+}
+
+impl std::error::Error for VersionMismatchError {}
+
+impl VersionMismatchError {
+ pub fn new(
+ zellij_version: &str,
+ plugin_version: &str,
+ plugin_path: &PathBuf,
+ builtin: bool,
+ ) -> Self {
+ VersionMismatchError {
+ zellij_version: zellij_version.to_owned(),
+ plugin_version: plugin_version.to_owned(),
+ plugin_path: plugin_path.to_owned(),
+ builtin,
+ }
+ }
+}
+
+impl fmt::Display for VersionMismatchError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let first_line = if self.builtin {
+ "It seems your version of zellij was built with outdated core plugins."
+ } else {
+ "If you're seeing this error a plugin version doesn't match the current
+zellij version."
+ };
+
+ write!(
+ f,
+ "{}
+Detected versions:
+
+- Plugin version: {}
+- Zellij version: {}
+- Offending plugin: {}
+
+If you're a user:
+ Please contact the distributor of your zellij version and report this error
+ to them.
+
+If you're a developer:
+ Please run zellij with updated plugins. The easiest way to achieve this
+ is to build zellij with `cargo xtask install`. Also refer to the docs:
+ https://github.com/zellij-org/zellij/blob/main/CONTRIBUTING.md#building
+",
+ first_line,
+ self.plugin_version.trim_end(),
+ self.zellij_version.trim_end(),
+ self.plugin_path.display()
+ )
+ }
+}
+
+// Returns `Ok` if the plugin version matches the zellij version.
+// Returns an `Err` otherwise.
+fn assert_plugin_version(instance: &Instance, plugin_env: &PluginEnv) -> Result<()> {
+ let err_context = || {
+ format!(
+ "failed to determine plugin version for plugin {}",
+ plugin_env.plugin.path.display()
+ )
+ };
+
+ let plugin_version_func = match instance.exports.get_function("plugin_version") {
+ Ok(val) => val,
+ Err(_) => {
+ return Err(anyError::new(VersionMismatchError::new(
+ VERSION,
+ "Unavailable",
+ &plugin_env.plugin.path,
+ plugin_env.plugin.is_builtin(),
+ )))
+ },
+ };
+
+ let plugin_version = plugin_version_func
+ .call(&[])
+ .map_err(anyError::new)
+ .and_then(|_| wasi_read_string(&plugin_env.wasi_env))
+ .and_then(|string| Version::parse(&string).context("failed to parse plugin version"))
+ .with_context(err_context)?;
+ let zellij_version = Version::parse(VERSION)
+ .context("failed to parse zellij version")
+ .with_context(err_context)?;
+ if plugin_version != zellij_version {
+ return Err(anyError::new(VersionMismatchError::new(
+ VERSION,
+ &plugin_version.to_string(),
+ &plugin_env.plugin.path,
+ plugin_env.plugin.is_builtin(),
+ )));
+ }
+
+ Ok(())
+}
+
+fn load_plugin_instance(instance: &mut Instance) -> Result<()> {
+ let err_context = || format!("failed to load plugin from instance {instance:#?}");
+
+ let load_function = instance
+ .exports
+ .get_function("_start")
+ .with_context(err_context)?;
+ // This eventually calls the `.load()` method
+ load_function.call(&[]).with_context(err_context)?;
+ Ok(())
+}
+
+pub fn start_plugin(
+ plugin_id: u32,
+ client_id: ClientId,
+ plugin: &PluginConfig,
+ tab_index: usize,
+ plugin_dir: PathBuf,
+ plugin_cache: Arc<Mutex<HashMap<PathBuf, Module>>>,
+ senders: ThreadSenders,
+ mut store: Store,
+ plugin_map: Arc<Mutex<PluginMap>>,
+ size: Size,
+ connected_clients: Arc<Mutex<Vec<ClientId>>>,
+ loading_indication: &mut LoadingIndication,
+) -> Result<()> {
+ let err_context = || format!("failed to start plugin {plugin:#?} for client {client_id}");
+ let plugin_own_data_dir = ZELLIJ_CACHE_DIR.join(Url::from(&plugin.location).to_string());
+ create_plugin_fs_entries(&plugin_own_data_dir)?;
+
+ loading_indication.indicate_loading_plugin_from_memory();
+ let _ = senders.send_to_screen(ScreenInstruction::UpdatePluginLoadingStage(
+ plugin_id,
+ loading_indication.clone(),
+ ));
+ let (module, cache_hit) = {
+ let mut plugin_cache = plugin_cache.lock().unwrap();
+ let (module, cache_hit) = load_module_from_memory(&mut *plugin_cache, &plugin.path);
+ (module, cache_hit)
+ };
+
+ let module = match module {
+ Some(module) => {
+ loading_indication.indicate_loading_plugin_from_memory_success();
+ let _ = senders.send_to_screen(ScreenInstruction::UpdatePluginLoadingStage(
+ plugin_id,
+ loading_indication.clone(),
+ ));
+ module
+ },
+ None => {
+ loading_indication.indicate_loading_plugin_from_memory_notfound();
+ loading_indication.indicate_loading_plugin_from_hd_cache();
+ let _ = senders.send_to_screen(ScreenInstruction::UpdatePluginLoadingStage(
+ plugin_id,
+ loading_indication.clone(),
+ ));
+
+ let (wasm_bytes, cached_path) = plugin_bytes_and_cache_path(&plugin, &plugin_dir);
+ let timer = std::time::Instant::now();
+ match load_module_from_hd_cache(&mut store, &plugin.path, &timer, &cached_path) {
+ Ok(module) => {
+ loading_indication.indicate_loading_plugin_from_hd_cache_success();
+ let _ = senders.send_to_screen(ScreenInstruction::UpdatePluginLoadingStage(
+ plugin_id,
+ loading_indication.clone(),
+ ));
+ module
+ },
+ Err(_e) => {
+ loading_indication.indicate_loading_plugin_from_hd_cache_notfound();
+ loading_indication.indicate_compiling_plugin();
+ let _ = senders.send_to_screen(ScreenInstruction::UpdatePluginLoadingStage(
+ plugin_id,
+ loading_indication.clone(),
+ ));
+ let module =
+ compile_module(&mut store, &plugin.path, &timer, &cached_path, wasm_bytes)?;
+ loading_indication.indicate_compiling_plugin_success();
+ let _ = senders.send_to_screen(ScreenInstruction::UpdatePluginLoadingStage(
+ plugin_id,
+ loading_indication.clone(),
+ ));
+ module
+ },
+ }
+ },
+ };
+
+ let (instance, plugin_env) = create_plugin_instance_and_environment(
+ plugin_id,
+ client_id,
+ plugin,
+ &module,
+ tab_index,
+ plugin_own_data_dir,
+ senders.clone(),
+ &mut store,
+ )?;
+
+ if !cache_hit {
+ // Check plugin version
+ assert_plugin_version(&instance, &plugin_env).with_context(err_context)?;
+ }
+
+ // Only do an insert when everything went well!
+ let cloned_plugin = plugin.clone();
+ {
+ let mut plugin_cache = plugin_cache.lock().unwrap();
+ plugin_cache.insert(cloned_plugin.path, module);
+ }
+
+ let mut main_user_instance = instance.clone();
+ let main_user_env = plugin_env.clone();
+ loading_indication.indicate_starting_plugin();
+ let _ = senders.send_to_screen(ScreenInstruction::UpdatePluginLoadingStage(
+ plugin_id,
+ loading_indication.clone(),
+ ));
+ load_plugin_instance(&mut main_user_instance).with_context(err_context)?;
+ loading_indication.indicate_starting_plugin_success();
+ loading_indication.indicate_writing_plugin_to_cache();
+ let _ = senders.send_to_screen(ScreenInstruction::UpdatePluginLoadingStage(
+ plugin_id,
+ loading_indication.clone(),
+ ));
+
+ {
+ let mut plugin_map = plugin_map.lock().unwrap();
+ plugin_map.insert(
+ (plugin_id, client_id),
+ (main_user_instance, main_user_env, (size.rows, size.cols)),
+ );
+ }
+
+ loading_indication.indicate_writing_plugin_to_cache_success();
+ let _ = senders.send_to_screen(ScreenInstruction::UpdatePluginLoadingStage(
+ plugin_id,
+ loading_indication.clone(),
+ ));
+
+ let connected_clients: Vec<ClientId> =
+ connected_clients.lock().unwrap().iter().copied().collect();
+ if !connected_clients.is_empty() {
+ loading_indication.indicate_cloning_plugin_for_other_clients();
+ let _ = senders.send_to_screen(ScreenInstruction::UpdatePluginLoadingStage(
+ plugin_id,
+ loading_indication.clone(),
+ ));
+ let mut plugin_map = plugin_map.lock().unwrap();
+ for client_id in connected_clients {
+ let (instance, new_plugin_env) =
+ clone_plugin_for_client(&plugin_env, client_id, &instance, &mut store)?;
+ plugin_map.insert(
+ (plugin_id, client_id),
+ (instance, new_plugin_env, (size.rows, size.cols)),
+ );
+ }
+ loading_indication.indicate_cloning_plugin_for_other_clients_success();
+ let _ = senders.send_to_screen(ScreenInstruction::UpdatePluginLoadingStage(
+ plugin_id,
+ loading_indication.clone(),
+ ));
+ }
+ loading_indication.end();
+ let _ = senders.send_to_screen(ScreenInstruction::UpdatePluginLoadingStage(
+ plugin_id,
+ loading_indication.clone(),
+ ));
+ Ok(())
+}
+
+fn create_plugin_fs_entries(plugin_own_data_dir: &PathBuf) -> Result<()> {
+ let err_context = || "failed to create plugin fs entries";
+ // Create filesystem entries mounted into WASM.
+ // We create them here to get expressive error messages in case they fail.
+ fs::create_dir_all(&plugin_own_data_dir)
+ .with_context(|| format!("failed to create datadir in {plugin_own_data_dir:?}"))
+ .with_context(err_context)?;
+ fs::create_dir_all(ZELLIJ_TMP_DIR.as_path())
+ .with_context(|| format!("failed to create tmpdir at {:?}", &ZELLIJ_TMP_DIR.as_path()))
+ .with_context(err_context)?;
+ Ok(())
+}
+
+fn compile_module(
+ store: &mut Store,
+ plugin_path: &PathBuf,
+ timer: &Instant,
+ cached_path: &PathBuf,
+ wasm_bytes: Vec<u8>,
+) -> Result<Module> {
+ let err_context = || "failed to recover cache dir";
+ fs::create_dir_all(ZELLIJ_CACHE_DIR.to_owned())
+ .map_err(anyError::new)
+ .and_then(|_| {
+ // compile module
+ Module::new(&*store, &wasm_bytes).map_err(anyError::new)
+ })
+ .map(|m| {
+ // serialize module to HD cache for faster loading in the future
+ m.serialize_to_file(&cached_path).map_err(anyError::new)?;
+ log::info!(
+ "Compiled plugin '{}' in {:?}",
+ plugin_path.display(),
+ timer.elapsed()
+ );
+ Ok(m)
+ })
+ .with_context(err_context)?
+}
+
+fn load_module_from_hd_cache(
+ store: &mut Store,
+ plugin_path: &PathBuf,
+ timer: &Instant,
+ cached_path: &PathBuf,
+) -> Result<Module> {
+ let module = unsafe { Module::deserialize_from_file(&*store, &cached_path)? };
+ log::info!(
+ "Loaded plugin '{}' from cache folder at '{}' in {:?}",
+ plugin_path.display(),
+ ZELLIJ_CACHE_DIR.display(),
+ timer.elapsed(),
+ );
+ Ok(module)
+}
+
+fn plugin_bytes_and_cache_path(plugin: &PluginConfig, plugin_dir: &PathBuf) -> (Vec<u8>, PathBuf) {
+ let err_context = || "failed to get plugin bytes and cached path";
+ // Populate plugin module cache for this plugin!
+ // Is it in the cache folder already?
+ if plugin._allow_exec_host_cmd {
+ info!(
+ "Plugin({:?}) is able to run any host command, this may lead to some security issues!",
+ plugin.path
+ );
+ }
+ // The plugins blob as stored on the filesystem
+ let wasm_bytes = plugin
+ .resolve_wasm_bytes(&plugin_dir)
+ .with_context(err_context)
+ .fatal();
+ let hash: String = PortableHash::default()
+ .hash256(&wasm_bytes)
+ .iter()
+ .map(ToString::to_string)
+ .collect();
+ let cached_path = ZELLIJ_CACHE_DIR.join(&hash);
+ (wasm_bytes, cached_path)
+}
+
+fn load_module_from_memory(
+ plugin_cache: &mut HashMap<PathBuf, Module>,
+ plugin_path: &PathBuf,
+) -> (Option<Module>, bool) {
+ let module = plugin_cache.remove(plugin_path);