Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify some more #383

Merged
merged 8 commits into from
Feb 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions examples/fastbar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,4 @@ fn main() {
// Perform a long sequence of many simple computations monitored by a
// default progress bar.
many_units_of_easy_work(N, "Default progress bar ");

// Perform the same sequence of many simple computations, but only redraw
// after each 0.005% of additional progress.
many_units_of_easy_work(N, "Draw delta is 0.005% ");

// Perform the same sequence of many simple computations, but only redraw
// after each 0.01% of additional progress.
many_units_of_easy_work(N, "Draw delta is 0.01% ");
}
110 changes: 34 additions & 76 deletions src/draw_target.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::io;
use std::sync::{Arc, RwLock, RwLockWriteGuard};
use std::time::Instant;
use std::time::{Duration, Instant};

use console::Term;

Expand All @@ -23,53 +23,31 @@ impl ProgressDrawTarget {
///
/// For more information see `ProgressDrawTarget::to_term`.
pub fn stdout() -> ProgressDrawTarget {
ProgressDrawTarget::term(Term::buffered_stdout(), 15)
ProgressDrawTarget::term(Term::buffered_stdout(), 20)
}

/// Draw to a buffered stderr terminal at a max of 15 times a second.
///
/// This is the default draw target for progress bars. For more
/// information see `ProgressDrawTarget::to_term`.
pub fn stderr() -> ProgressDrawTarget {
ProgressDrawTarget::term(Term::buffered_stderr(), 15)
ProgressDrawTarget::term(Term::buffered_stderr(), 20)
}

/// Draw to a buffered stdout terminal at a max of `refresh_rate` times a second.
///
/// For more information see `ProgressDrawTarget::to_term`.
pub fn stdout_with_hz(refresh_rate: u64) -> ProgressDrawTarget {
pub fn stdout_with_hz(refresh_rate: u8) -> ProgressDrawTarget {
ProgressDrawTarget::term(Term::buffered_stdout(), refresh_rate)
}

/// Draw to a buffered stderr terminal at a max of `refresh_rate` times a second.
///
/// For more information see `ProgressDrawTarget::to_term`.
pub fn stderr_with_hz(refresh_rate: u64) -> ProgressDrawTarget {
pub fn stderr_with_hz(refresh_rate: u8) -> ProgressDrawTarget {
ProgressDrawTarget::term(Term::buffered_stderr(), refresh_rate)
}

/// Draw to a buffered stdout terminal without max framerate.
///
/// This is useful when data is known to come in very slowly and
/// not rendering some updates would be a problem (for instance
/// when messages are used extensively).
///
/// For more information see `ProgressDrawTarget::to_term`.
pub fn stdout_nohz() -> ProgressDrawTarget {
ProgressDrawTarget::term(Term::buffered_stdout(), None)
}

/// Draw to a buffered stderr terminal without max framerate.
///
/// This is useful when data is known to come in very slowly and
/// not rendering some updates would be a problem (for instance
/// when messages are used extensively).
///
/// For more information see `ProgressDrawTarget::to_term`.
pub fn stderr_nohz() -> ProgressDrawTarget {
ProgressDrawTarget::term(Term::buffered_stderr(), None)
}

pub(crate) fn new_remote(state: Arc<RwLock<MultiProgressState>>, idx: usize) -> Self {
Self {
kind: ProgressDrawTargetKind::Remote { state, idx },
Expand All @@ -84,30 +62,12 @@ impl ProgressDrawTarget {
/// useless escape codes in that file.
///
/// Will panic if refresh_rate is `Some(0)`. To disable rate limiting use `None` instead.
#[allow(clippy::wrong_self_convention)]
#[deprecated(since = "0.16.0", note = "Use `ProgressDrawTarget::term` instead")]
pub fn to_term(term: Term, refresh_rate: impl Into<Option<u64>>) -> ProgressDrawTarget {
ProgressDrawTarget::term(term, refresh_rate)
}

/// Draw to a terminal, optionally with a specific refresh rate.
///
/// Progress bars are by default drawn to terminals however if the
/// terminal is not user attended the entire progress bar will be
/// hidden. This is done so that piping to a file will not produce
/// useless escape codes in that file.
///
/// Will panic if refresh_rate is `Some(0)`. To disable rate limiting use `None` instead.
pub fn term(term: Term, refresh_rate: impl Into<Option<u64>>) -> ProgressDrawTarget {
pub fn term(term: Term, refresh_rate: u8) -> ProgressDrawTarget {
ProgressDrawTarget {
kind: ProgressDrawTargetKind::Term {
term,
last_line_count: 0,
leaky_bucket: refresh_rate.into().map(|rate| LeakyBucket {
bucket: MAX_GROUP_SIZE,
leak_rate: rate as f64,
last_update: Instant::now(),
}),
rate_limiter: RateLimiter::new(refresh_rate),
draw_state: ProgressDrawState::new(Vec::new(), false),
},
}
Expand Down Expand Up @@ -161,16 +121,11 @@ impl ProgressDrawTarget {
ProgressDrawTargetKind::Term {
term,
last_line_count,
leaky_bucket,
rate_limiter,
draw_state,
} => {
let has_capacity = leaky_bucket
.as_mut()
.map(|b| b.try_add_work(now))
.unwrap_or(true);

draw_state.force_draw = force_draw;
match force_draw || has_capacity {
match force_draw || rate_limiter.allow(now) {
true => Some(Drawable::Term {
term,
last_line_count,
Expand Down Expand Up @@ -237,7 +192,7 @@ enum ProgressDrawTargetKind {
Term {
term: Term,
last_line_count: usize,
leaky_bucket: Option<LeakyBucket>,
rate_limiter: RateLimiter,
draw_state: ProgressDrawState,
},
Remote {
Expand Down Expand Up @@ -359,37 +314,40 @@ impl Drop for DrawStateWrapper<'_> {
}

#[derive(Debug)]
struct LeakyBucket {
leak_rate: f64,
last_update: Instant,
bucket: f64,
struct RateLimiter {
interval: u16, // in milliseconds
capacity: u8,
prev: Instant,
}

/// Rate limit but allow occasional bursts above desired rate
impl LeakyBucket {
/// try to add some work to the bucket
/// return false if the bucket is already full and the work should be skipped
fn try_add_work(&mut self, now: Instant) -> bool {
self.leak(now);
if self.bucket < MAX_GROUP_SIZE {
self.bucket += 1.0;
true
} else {
false
impl RateLimiter {
fn new(rate: u8) -> Self {
Self {
interval: 1000 / (rate as u16), // between 3 and 1000 milliseconds
capacity: MAX_BURST,
prev: Instant::now(),
}
}

fn leak(&mut self, now: Instant) {
let ticks = (now - self.last_update).as_secs_f64() * self.leak_rate;
self.bucket -= ticks;
if self.bucket < 0.0 {
self.bucket = 0.0;
fn allow(&mut self, now: Instant) -> bool {
let elapsed = now - self.prev;
let remaining = (MAX_BURST - self.capacity) as u128;
self.capacity += Ord::min(remaining, elapsed.as_millis() / self.interval as u128) as u8;
let interval_nanos = self.interval as u128 * 1_000_000;
self.prev = now - Duration::from_nanos((elapsed.as_nanos() % interval_nanos) as u64);

match self.capacity.checked_sub(1) {
Some(new) => {
self.capacity = new;
true
}
None => false,
}
self.last_update = now;
}
}

const MAX_GROUP_SIZE: f64 = 32.0;
const MAX_BURST: u8 = 20;

/// The drawn state of an element.
#[derive(Clone, Debug)]
Expand Down
5 changes: 3 additions & 2 deletions src/progress_bar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,8 +300,9 @@ impl ProgressBar {
/// This can be useful if the progress bars made a large jump or was paused for a prolonged
/// time.
pub fn reset_eta(&self) {
self.state().update(Instant::now(), false, |state| {
state.est.reset(state.pos);
let now = Instant::now();
self.state().update(now, false, |state| {
state.est.reset(state.pos, now);
});
}

Expand Down
Loading