summaryrefslogtreecommitdiff
path: root/cli/util/progress_bar
diff options
context:
space:
mode:
authorDavid Sherret <dsherret@users.noreply.github.com>2022-12-19 11:19:33 -0500
committerGitHub <noreply@github.com>2022-12-19 11:19:33 -0500
commit2afac5bf78536060a53012c4f938b0390e8827de (patch)
tree2c4cd772d5b35543a8d31de47428de7ee3134042 /cli/util/progress_bar
parent383d40a33bee842a7462fb157f5faca263598d87 (diff)
refactor(progress bars): global control for drawing (#17091)
This PR adds the concept of a global `DrawThread`, which can receive multiple renderers to draw information on the screen (note: the underlying thread is released back to tokio when it's not rendering). It also separates the concept of progress bars from the existing "draw thread". This makes it trivial for us to do stuff like show permission prompts and progress bars at the same time in the future. The reason this is global is because the process' tty stderr is also a global concept.
Diffstat (limited to 'cli/util/progress_bar')
-rw-r--r--cli/util/progress_bar/draw_thread.rs218
-rw-r--r--cli/util/progress_bar/mod.rs219
-rw-r--r--cli/util/progress_bar/renderer.rs2
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;
}