parsli 0.0.3

Parallel status lines for Rust
Documentation
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use console::style;

use indicatif::{MultiProgress, ProgressDrawTarget};
use threadpool::ThreadPool;

use crate::{Line, LineStatus, Task};
use crate::graph::DependencyGraph;

/// The status of the pool
#[derive(Clone)]
pub enum Status {
    // The pool is not working.
    Idle,
    // The pool is working. A subset of tasks can be specified.
    Busy(Option<Vec<String>>),
}

/// A wrapper for showing progress of multiple dependent tasks
#[derive(Clone)]
pub struct Pool<S> {
    /// Number of processes
    threads: usize,
    /// Status of this pool
    status: Status,
    /// Dependency graph
    graph: Arc<RwLock<DependencyGraph<S>>>,
    /// Multi progress bar
    progress: Arc<MultiProgress>,
}


impl<S> Pool<S> where S: Clone + 'static {
    /// Creates a new process pool
    pub fn new(threads: usize) -> Self {
        Self {
            threads,
            /// Do nothing initially
            status: Status::Idle,
            /// Empty graph
            graph: Arc::new(RwLock::new(DependencyGraph::new())),
            /// High refresh rate progress bar
            progress: Arc::new(MultiProgress::with_draw_target(
                ProgressDrawTarget::stderr_with_hz(60),
            )),
        }
    }

    /// Add a new task
    pub fn add_task(&mut self, name: &str, task: Task<S>, deps: Vec<&str>) {
        let mut graph_lock = self.graph.write().unwrap();
        graph_lock.add_task(name, task);
        for dep in deps {
            graph_lock.add_dependency(name, dep);
        }
    }

    /// Start the pool for a single task
    pub fn start_single(&mut self, name: &str) {
        self.start_multi(vec![name])
    }

    /// Start the pool for multiple tasks
    pub fn start_multi(&mut self, names: Vec<&str>) {
        self.status = Status::Busy(Some(names.iter().map(|s| s.to_string()).collect()));
        self.exec();
        todo!();
    }

    /// Start all tasks
    pub fn start(&mut self) {
        self.status = Status::Busy(None);
        self.exec()
    }

    fn exec(&self) {
        let pool = ThreadPool::new(self.threads);
        for _ in 0..self.threads {
            let graph = self.graph.clone();
            let line = Line::new(self.progress.clone());
            pool.execute(move || {
                // Hide status line while waiting for the next task
                while let Some((name, task)) = line.suspend(|| {
                    // Get the next task to execute from the graph
                    let mut graph_lock = graph.write().unwrap();
                    graph_lock.pop_next()
                }) {
                    // Execute the task
                    line.update_prefix(format!("[{}]", name));
                    line.update_message(LineStatus::Idle, "waiting...".to_string());
                    match task(name.clone(), HashMap::new(), line.clone()) {
                        Ok(_s) => {
                            // Everything went fine, we can continue
                            // line.okay(); Okaying the line just gets overwritten by the new job
                        }
                        Err(e) => {
                            // Something went wrong, but we can still do other jobs
                            let prefix = style(format!("[{}]", name)).bold();
                            let symbol = style("").red();
                            let message = style(e).white();
                            line.println(format!("{} {} {}", prefix, symbol, message));
                        }
                    };
                }
            });
        }
        // Wait for tasks to finish
        pool.join();
    }
}