zellij/zellij-server/src/background_jobs.rs
Aram Drevekenin 341f9eb8c8
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
2023-03-27 19:19:34 +02:00

124 lines
4.6 KiB
Rust

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;
use crate::screen::ScreenInstruction;
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,
}
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)?;
err_ctx.add_call(ContextType::BackgroundJob((&event).into()));
let job = event.clone();
match event {
BackgroundJob::DisplayPaneError(pane_ids, text) => {
if job_already_running(job, &mut running_jobs) {
continue;
}
task::spawn({
let senders = bus.senders.clone();
async move {
let _ = senders.send_to_screen(
ScreenInstruction::AddRedPaneFrameColorOverride(
pane_ids.clone(),
Some(text),
),
);
task::sleep(std::time::Duration::from_millis(FLASH_DURATION_MS)).await;
let _ = senders.send_to_screen(
ScreenInstruction::ClearPaneFrameColorOverride(pane_ids),
);
}
});
},
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(());
},
}
}
}
fn job_already_running(
job: BackgroundJob,
running_jobs: &mut HashMap<BackgroundJob, Instant>,
) -> bool {
match running_jobs.get_mut(&job) {
Some(current_running_job_start_time) => {
if current_running_job_start_time.elapsed() > Duration::from_millis(FLASH_DURATION_MS) {
*current_running_job_start_time = Instant::now();
false
} else {
true
}
},
None => {
running_jobs.insert(job.clone(), Instant::now());
false
},
}
}