diff options
Diffstat (limited to 'cli/util/progress_bar')
-rw-r--r-- | cli/util/progress_bar/draw_thread.rs | 218 | ||||
-rw-r--r-- | cli/util/progress_bar/mod.rs | 219 | ||||
-rw-r--r-- | cli/util/progress_bar/renderer.rs | 2 |
3 files changed, 199 insertions, 240 deletions
diff --git a/cli/util/progress_bar/draw_thread.rs b/cli/util/progress_bar/draw_thread.rs deleted file mode 100644 index 89e8ab53f..000000000 --- a/cli/util/progress_bar/draw_thread.rs +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. - -use console_static_text::ConsoleStaticText; -use deno_core::parking_lot::Mutex; -use std::sync::atomic::AtomicU64; -use std::sync::atomic::Ordering; -use std::sync::Arc; -use std::time::Duration; -use std::time::SystemTime; - -use crate::util::console::console_size; - -use super::renderer::ProgressBarRenderer; -use super::renderer::ProgressData; -use super::renderer::ProgressDataDisplayEntry; - -#[derive(Clone, Debug)] -pub struct ProgressBarEntry { - id: usize, - pub message: String, - pos: Arc<AtomicU64>, - total_size: Arc<AtomicU64>, - draw_thread: DrawThread, -} - -impl ProgressBarEntry { - pub fn position(&self) -> u64 { - self.pos.load(Ordering::Relaxed) - } - - pub fn set_position(&self, new_pos: u64) { - self.pos.store(new_pos, Ordering::Relaxed); - } - - pub fn total_size(&self) -> u64 { - self.total_size.load(Ordering::Relaxed) - } - - pub fn set_total_size(&self, new_size: u64) { - self.total_size.store(new_size, Ordering::Relaxed); - } - - pub fn finish(&self) { - self.draw_thread.finish_entry(self.id); - } - - pub fn percent(&self) -> f64 { - let pos = self.pos.load(Ordering::Relaxed) as f64; - let total_size = self.total_size.load(Ordering::Relaxed) as f64; - if total_size == 0f64 { - 0f64 - } else { - pos / total_size - } - } -} - -#[derive(Debug)] -struct InternalState { - start_time: SystemTime, - // this ensures only one draw thread is running - drawer_id: usize, - keep_alive_count: usize, - has_draw_thread: bool, - total_entries: usize, - entries: Vec<ProgressBarEntry>, - static_text: ConsoleStaticText, - renderer: Box<dyn ProgressBarRenderer>, -} - -#[derive(Clone, Debug)] -pub struct DrawThread { - state: Arc<Mutex<InternalState>>, -} - -impl DrawThread { - pub fn new(renderer: Box<dyn ProgressBarRenderer>) -> Self { - Self { - state: Arc::new(Mutex::new(InternalState { - start_time: SystemTime::now(), - drawer_id: 0, - keep_alive_count: 0, - has_draw_thread: false, - total_entries: 0, - entries: Vec::new(), - static_text: ConsoleStaticText::new(|| { - let size = console_size().unwrap(); - console_static_text::ConsoleSize { - cols: Some(size.cols as u16), - rows: Some(size.rows as u16), - } - }), - renderer, - })), - } - } - - pub fn add_entry(&self, message: String) -> ProgressBarEntry { - let mut internal_state = self.state.lock(); - let id = internal_state.total_entries; - let entry = ProgressBarEntry { - id, - draw_thread: self.clone(), - message, - pos: Default::default(), - total_size: Default::default(), - }; - internal_state.entries.push(entry.clone()); - internal_state.total_entries += 1; - internal_state.keep_alive_count += 1; - - if !internal_state.has_draw_thread { - self.start_draw_thread(&mut internal_state); - } - - entry - } - - fn finish_entry(&self, entry_id: usize) { - let mut internal_state = self.state.lock(); - - if let Ok(index) = internal_state - .entries - .binary_search_by(|e| e.id.cmp(&entry_id)) - { - internal_state.entries.remove(index); - self.decrement_keep_alive(&mut internal_state); - } - } - - pub fn increment_clear(&self) { - let mut internal_state = self.state.lock(); - internal_state.keep_alive_count += 1; - } - - pub fn decrement_clear(&self) { - let mut internal_state = self.state.lock(); - self.decrement_keep_alive(&mut internal_state); - } - - fn decrement_keep_alive(&self, internal_state: &mut InternalState) { - internal_state.keep_alive_count -= 1; - - if internal_state.keep_alive_count == 0 { - internal_state.static_text.eprint_clear(); - // bump the drawer id to exit the draw thread - internal_state.drawer_id += 1; - internal_state.has_draw_thread = false; - } - } - - fn start_draw_thread(&self, internal_state: &mut InternalState) { - internal_state.drawer_id += 1; - internal_state.start_time = SystemTime::now(); - internal_state.has_draw_thread = true; - let drawer_id = internal_state.drawer_id; - let internal_state = self.state.clone(); - tokio::task::spawn_blocking(move || { - let mut previous_size = console_size().unwrap(); - loop { - let mut delay_ms = 120; - { - let mut internal_state = internal_state.lock(); - // exit if not the current draw thread - if internal_state.drawer_id != drawer_id { - break; - } - - let size = console_size().unwrap(); - if size != previous_size { - // means the user is actively resizing the console... - // wait a little bit until they stop resizing - previous_size = size; - delay_ms = 200; - } else if !internal_state.entries.is_empty() { - let preferred_entry = internal_state - .entries - .iter() - .find(|e| e.percent() > 0f64) - .or_else(|| internal_state.entries.iter().last()) - .unwrap(); - let text = internal_state.renderer.render(ProgressData { - duration: internal_state.start_time.elapsed().unwrap(), - terminal_width: size.cols, - pending_entries: internal_state.entries.len(), - total_entries: internal_state.total_entries, - display_entry: ProgressDataDisplayEntry { - message: preferred_entry.message.clone(), - position: preferred_entry.position(), - total_size: preferred_entry.total_size(), - }, - percent_done: { - let mut total_percent_sum = 0f64; - for entry in &internal_state.entries { - total_percent_sum += entry.percent(); - } - total_percent_sum += (internal_state.total_entries - - internal_state.entries.len()) - as f64; - total_percent_sum / (internal_state.total_entries as f64) - }, - }); - - internal_state.static_text.eprint_with_size( - &text, - console_static_text::ConsoleSize { - cols: Some(size.cols as u16), - rows: Some(size.rows as u16), - }, - ); - } - } - - std::thread::sleep(Duration::from_millis(delay_ms)); - } - }); - } -} diff --git a/cli/util/progress_bar/mod.rs b/cli/util/progress_bar/mod.rs index 83292e2d1..8651e2d20 100644 --- a/cli/util/progress_bar/mod.rs +++ b/cli/util/progress_bar/mod.rs @@ -1,13 +1,23 @@ // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. +use std::sync::atomic::AtomicU64; +use std::sync::atomic::Ordering; +use std::sync::Arc; +use std::time::SystemTime; + +use deno_core::parking_lot::Mutex; +use deno_runtime::ops::tty::ConsoleSize; + use crate::colors; -use self::draw_thread::DrawThread; -use self::draw_thread::ProgressBarEntry; +use self::renderer::ProgressBarRenderer; +use self::renderer::ProgressData; +use self::renderer::ProgressDataDisplayEntry; -use super::console::console_size; +use super::draw_thread::DrawThread; +use super::draw_thread::DrawThreadGuard; +use super::draw_thread::DrawThreadRenderer; -mod draw_thread; mod renderer; // Inspired by Indicatif, but this custom implementation allows @@ -46,29 +56,196 @@ pub enum ProgressBarStyle { } #[derive(Clone, Debug)] +struct ProgressBarEntry { + id: usize, + pub message: String, + pos: Arc<AtomicU64>, + total_size: Arc<AtomicU64>, + progress_bar: ProgressBarInner, +} + +impl ProgressBarEntry { + pub fn position(&self) -> u64 { + self.pos.load(Ordering::Relaxed) + } + + pub fn set_position(&self, new_pos: u64) { + self.pos.store(new_pos, Ordering::Relaxed); + } + + pub fn total_size(&self) -> u64 { + self.total_size.load(Ordering::Relaxed) + } + + pub fn set_total_size(&self, new_size: u64) { + self.total_size.store(new_size, Ordering::Relaxed); + } + + pub fn finish(&self) { + self.progress_bar.finish_entry(self.id); + } + + pub fn percent(&self) -> f64 { + let pos = self.pos.load(Ordering::Relaxed) as f64; + let total_size = self.total_size.load(Ordering::Relaxed) as f64; + if total_size == 0f64 { + 0f64 + } else { + pos / total_size + } + } +} + +#[derive(Debug)] +struct InternalState { + /// If this guard exists, then it means the progress + /// bar is displaying in the draw thread. + draw_thread_guard: Option<DrawThreadGuard>, + start_time: SystemTime, + keep_alive_count: usize, + total_entries: usize, + entries: Vec<ProgressBarEntry>, +} + +#[derive(Clone, Debug)] +struct ProgressBarInner { + state: Arc<Mutex<InternalState>>, + renderer: Arc<dyn ProgressBarRenderer>, +} + +impl ProgressBarInner { + fn new(renderer: Arc<dyn ProgressBarRenderer>) -> Self { + Self { + state: Arc::new(Mutex::new(InternalState { + draw_thread_guard: None, + start_time: SystemTime::now(), + keep_alive_count: 0, + total_entries: 0, + entries: Vec::new(), + })), + renderer, + } + } + + pub fn add_entry(&self, message: String) -> ProgressBarEntry { + let mut internal_state = self.state.lock(); + let id = internal_state.total_entries; + let entry = ProgressBarEntry { + id, + message, + pos: Default::default(), + total_size: Default::default(), + progress_bar: self.clone(), + }; + internal_state.entries.push(entry.clone()); + internal_state.total_entries += 1; + internal_state.keep_alive_count += 1; + + self.maybe_start_draw_thread(&mut internal_state); + + entry + } + + fn finish_entry(&self, entry_id: usize) { + let mut internal_state = self.state.lock(); + + if let Ok(index) = internal_state + .entries + .binary_search_by(|e| e.id.cmp(&entry_id)) + { + internal_state.entries.remove(index); + self.decrement_keep_alive(&mut internal_state); + } + } + + pub fn increment_clear(&self) { + let mut internal_state = self.state.lock(); + internal_state.keep_alive_count += 1; + } + + pub fn decrement_clear(&self) { + let mut internal_state = self.state.lock(); + self.decrement_keep_alive(&mut internal_state); + } + + fn decrement_keep_alive(&self, state: &mut InternalState) { + state.keep_alive_count -= 1; + + if state.keep_alive_count == 0 { + // drop the guard to remove this from the draw thread + state.draw_thread_guard.take(); + } + } + + fn maybe_start_draw_thread(&self, internal_state: &mut InternalState) { + if internal_state.draw_thread_guard.is_none() + && internal_state.keep_alive_count > 0 + { + internal_state.start_time = SystemTime::now(); + internal_state.draw_thread_guard = + Some(DrawThread::add_entry(0, Arc::new(self.clone()))); + } + } +} + +impl DrawThreadRenderer for ProgressBarInner { + fn render(&self, size: &ConsoleSize) -> String { + let data = { + let state = self.state.lock(); + if state.entries.is_empty() { + return String::new(); + } + let preferred_entry = state + .entries + .iter() + .find(|e| e.percent() > 0f64) + .or_else(|| state.entries.iter().last()) + .unwrap(); + ProgressData { + duration: state.start_time.elapsed().unwrap(), + terminal_width: size.cols, + pending_entries: state.entries.len(), + total_entries: state.total_entries, + display_entry: ProgressDataDisplayEntry { + message: preferred_entry.message.clone(), + position: preferred_entry.position(), + total_size: preferred_entry.total_size(), + }, + percent_done: { + let mut total_percent_sum = 0f64; + for entry in &state.entries { + total_percent_sum += entry.percent(); + } + total_percent_sum += + (state.total_entries - state.entries.len()) as f64; + total_percent_sum / (state.total_entries as f64) + }, + } + }; + self.renderer.render(data) + } +} + +#[derive(Clone, Debug)] pub struct ProgressBar { - draw_thread: Option<DrawThread>, + inner: Option<ProgressBarInner>, } impl ProgressBar { /// Checks if progress bars are supported pub fn are_supported() -> bool { - atty::is(atty::Stream::Stderr) - && log::log_enabled!(log::Level::Info) - && console_size() - .map(|s| s.cols > 0 && s.rows > 0) - .unwrap_or(false) + DrawThread::is_supported() } pub fn new(style: ProgressBarStyle) -> Self { Self { - draw_thread: match Self::are_supported() { - true => Some(DrawThread::new(match style { + inner: match Self::are_supported() { + true => Some(ProgressBarInner::new(match style { ProgressBarStyle::DownloadBars => { - Box::new(renderer::BarProgressBarRenderer) + Arc::new(renderer::BarProgressBarRenderer) } ProgressBarStyle::TextOnly => { - Box::new(renderer::TextOnlyProgressBarRenderer) + Arc::new(renderer::TextOnlyProgressBarRenderer) } })), false => None, @@ -77,9 +254,9 @@ impl ProgressBar { } pub fn update(&self, msg: &str) -> UpdateGuard { - match &self.draw_thread { - Some(draw_thread) => { - let entry = draw_thread.add_entry(msg.to_string()); + match &self.inner { + Some(inner) => { + let entry = inner.add_entry(msg.to_string()); UpdateGuard { maybe_entry: Some(entry), } @@ -95,15 +272,15 @@ impl ProgressBar { } pub fn clear_guard(&self) -> ClearGuard { - if let Some(draw_thread) = &self.draw_thread { - draw_thread.increment_clear(); + if let Some(inner) = &self.inner { + inner.increment_clear(); } ClearGuard { pb: self.clone() } } fn decrement_clear(&self) { - if let Some(draw_thread) = &self.draw_thread { - draw_thread.decrement_clear(); + if let Some(inner) = &self.inner { + inner.decrement_clear(); } } } diff --git a/cli/util/progress_bar/renderer.rs b/cli/util/progress_bar/renderer.rs index 75a4cafed..d8fa1769d 100644 --- a/cli/util/progress_bar/renderer.rs +++ b/cli/util/progress_bar/renderer.rs @@ -23,7 +23,7 @@ pub struct ProgressData { pub duration: Duration, } -pub trait ProgressBarRenderer: Send + std::fmt::Debug { +pub trait ProgressBarRenderer: Send + Sync + std::fmt::Debug { fn render(&self, data: ProgressData) -> String; } |