use std::iter::Iterator;
use std::ops::{Deref, DerefMut};
use std::time::{Duration, Instant};
#[cfg(test)]
mod tests;
#[derive(Debug)]
pub struct ProgressRecord {
num: usize,
iterating_for: Duration,
size_hint: (usize, Option<usize>),
assumed_size: Option<usize>,
assumed_fraction: Option<f64>,
previous_record_tm: Option<Instant>,
started_iterating: Instant,
rolling_average_duration: Option<Duration>,
exp_average_duration: Option<Duration>,
}
impl ProgressRecord {
pub fn duration_since_start(&self) -> Duration {
self.iterating_for
}
pub fn num_done(&self) -> usize {
self.num
}
pub fn previous_record_tm(&self) -> Option<Instant> {
self.previous_record_tm
}
pub fn started_iterating(&self) -> Instant {
self.started_iterating
}
pub fn rate(&self) -> f64 {
(self.num_done() as f64) / self.duration_since_start().as_secs_f64()
}
pub fn fraction(&self) -> Option<f64> {
if self.assumed_fraction.is_some() {
return self.assumed_fraction;
}
let total = if self.size_hint.1 == Some(self.size_hint.0) {
Some(self.size_hint.0 + self.num_done())
} else if self.assumed_size.is_some() {
self.assumed_size
} else {
None
};
match total {
None => None,
Some(total) => {
let done = self.num_done();
Some((done as f64) / (total as f64))
}
}
}
pub fn assume_fraction(&mut self, f: impl Into<f64>) {
self.assumed_fraction = Some(f.into())
}
pub fn percent(&self) -> Option<f64> {
self.fraction().map(|f| f * 100.)
}
pub fn print_every_n_sec<T: std::fmt::Display>(&self, n: f32, msg: T) {
if self.should_do_every_n_sec(n) {
print!("{}", msg);
}
}
pub fn do_every_n_sec<F: Fn(&Self)>(&self, n: impl Into<f32>, f: F) {
if self.should_do_every_n_sec(n) {
f(self);
}
}
pub fn should_do_every_n_sec(&self, n: impl Into<f32>) -> bool {
let n: f32 = n.into();
let duration_since_start = self.duration_since_start();
let secs_since_start: f32 = duration_since_start.as_secs() as f32
+ duration_since_start.subsec_nanos() as f32 / 1_000_000_000.0;
match self.previous_record_tm() {
None => {
secs_since_start > n
}
Some(last_time) => {
let last_time_offset = last_time - self.started_iterating();
let last_time_offset: f32 = last_time_offset.as_secs() as f32
+ last_time_offset.subsec_nanos() as f32 / 1_000_000_000.0;
let current_step = secs_since_start / n;
let last_step = last_time_offset / n;
current_step.trunc() > last_step.trunc()
}
}
}
pub fn should_do_every_n_items(&self, n: usize) -> bool {
(self.num_done() - 1) % n == 0
}
pub fn print_every_n_items<T: std::fmt::Display>(&self, n: usize, msg: T) {
if self.should_do_every_n_items(n) {
print!("{}", msg);
}
}
pub fn do_every_n_items<F: Fn(&Self)>(&self, n: usize, f: F) {
if self.should_do_every_n_items(n) {
f(self);
}
}
pub fn rolling_average_duration(&self) -> &Option<Duration> {
&self.rolling_average_duration
}
pub fn rolling_average_rate(&self) -> Option<f64> {
self.rolling_average_duration.map(|d| 1. / d.as_secs_f64())
}
pub fn exp_average_duration(&self) -> &Option<Duration> {
&self.exp_average_duration
}
pub fn exp_average_rate(&self) -> Option<f64> {
self.exp_average_duration.map(|d| 1. / d.as_secs_f64())
}
pub fn eta(&self) -> Option<Duration> {
self.fraction()
.map(|f| self.duration_since_start().div_f64(f) - self.duration_since_start())
}
pub fn estimated_total_time(&self) -> Option<Duration> {
self.fraction()
.map(|f| self.duration_since_start().div_f64(f))
}
}
pub struct OptionalProgressRecorderIter<I> {
iter: I,
count: usize,
generate_every_count: usize,
started_iterating: Instant,
previous_record_tm: Option<Instant>,
rolling_average: Option<(usize, Vec<f64>)>,
exp_average: Option<(f64, Option<Duration>)>,
assumed_size: Option<usize>,
_fake_now: Option<Instant>,
}
pub struct ProgressRecorderIter<I>(OptionalProgressRecorderIter<I>);
impl<I> AsRef<OptionalProgressRecorderIter<I>> for ProgressRecorderIter<I> {
fn as_ref(&self) -> &OptionalProgressRecorderIter<I> {
&self.0
}
}
impl<I> AsMut<OptionalProgressRecorderIter<I>> for ProgressRecorderIter<I> {
fn as_mut(&mut self) -> &mut OptionalProgressRecorderIter<I> {
&mut self.0
}
}
impl<I: Iterator> Deref for ProgressRecorderIter<I> {
type Target = OptionalProgressRecorderIter<I>;
fn deref(&self) -> &OptionalProgressRecorderIter<I> {
&self.0
}
}
impl<I: Iterator> DerefMut for ProgressRecorderIter<I> {
fn deref_mut(&mut self) -> &mut OptionalProgressRecorderIter<I> {
&mut self.0
}
}
impl<I: Iterator> ProgressRecorderIter<I> {
pub fn new(iter: I) -> ProgressRecorderIter<I> {
ProgressRecorderIter(OptionalProgressRecorderIter::new(iter, 1))
}
pub fn assume_size(self, size: impl Into<Option<usize>>) -> Self {
let mut new = self;
new.0.assumed_size = size.into();
new
}
}
pub trait ProgressableIter<I> {
fn progress(self) -> ProgressRecorderIter<I>;
}
impl<I> ProgressableIter<I> for I
where
I: Iterator,
{
fn progress(self) -> ProgressRecorderIter<I> {
ProgressRecorderIter::new(self)
}
}
impl<I> Iterator for ProgressRecorderIter<I>
where
I: Iterator,
{
type Item = (ProgressRecord, <I as Iterator>::Item);
#[inline]
fn next(&mut self) -> Option<(ProgressRecord, <I as Iterator>::Item)> {
self.0.iter.next().map(|a| {
let fake_now = std::mem::take(&mut self.0._fake_now);
(self.0.generate_record(fake_now).unwrap(), a)
})
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.iter.size_hint()
}
#[inline]
fn count(self) -> usize {
self.0.iter.count()
}
}
impl<I: Iterator> OptionalProgressRecorderIter<I> {
pub fn new(iter: I, generate_every_count: usize) -> OptionalProgressRecorderIter<I> {
OptionalProgressRecorderIter {
iter,
count: 0,
generate_every_count,
started_iterating: Instant::now(),
previous_record_tm: None,
rolling_average: None,
exp_average: None,
assumed_size: None,
_fake_now: None,
}
}
pub fn with_rolling_average(self, size: impl Into<Option<usize>>) -> Self {
let mut res = self;
res.rolling_average = size.into().map(|size| (size, vec![0.; size]));
res
}
pub fn with_exp_average(self, rate: impl Into<Option<f64>>) -> Self {
let mut res = self;
res.exp_average = rate.into().map(|rate| (rate, None));
res
}
pub fn assume_size(self, size: impl Into<Option<usize>>) -> Self {
let mut new = self;
new.assumed_size = size.into();
new
}
fn generate_record(&mut self, fake_now: Option<Instant>) -> Option<ProgressRecord> {
self.count += 1;
if self.count % self.generate_every_count != 0 {
return None;
}
let now = fake_now.unwrap_or_else(Instant::now);
let exp_average_rate = if let Some((rate, last)) = self.exp_average {
if let Some(previous_tm) = self.previous_record_tm {
let this_duration = now - previous_tm;
let current_ema = match last {
None => this_duration,
Some(last) => this_duration.mul_f64(rate) + last.mul_f64(1. - rate),
};
self.exp_average = Some((rate, Some(current_ema)));
Some(current_ema)
} else {
None
}
} else {
None
};
let rolling_average_duration = match &mut self.rolling_average {
None => None,
Some((size, values)) => {
if let Some(previous_tm) = self.previous_record_tm {
let this_duration = (now - previous_tm).as_secs_f64();
values[self.count % *size] = this_duration;
if self.count < *size {
Some(Duration::from_secs_f64(
values[0..=self.count].iter().sum::<f64>() / (self.count as f64),
))
} else {
Some(Duration::from_secs_f64(
values.iter().sum::<f64>() / (*size as f64),
))
}
} else {
None
}
}
};
let res = ProgressRecord {
num: self.count,
iterating_for: now - self.started_iterating,
size_hint: self.iter.size_hint(),
assumed_size: self.assumed_size,
assumed_fraction: None,
started_iterating: self.started_iterating,
previous_record_tm: self.previous_record_tm,
rolling_average_duration,
exp_average_duration: exp_average_rate,
};
self.previous_record_tm = Some(now);
Some(res)
}
pub fn inner(&self) -> &I {
&self.iter
}
pub fn into_inner(self) -> I {
self.iter
}
#[cfg(test)]
fn set_fake_now(&mut self, fake_now: impl Into<Option<Instant>>) {
self._fake_now = fake_now.into();
}
}
pub trait OptionalProgressableIter<I: Iterator> {
fn optional_progress(self, generate_every_count: usize) -> OptionalProgressRecorderIter<I>;
}
impl<I> OptionalProgressableIter<I> for I
where
I: Iterator,
{
fn optional_progress(self, generate_every_count: usize) -> OptionalProgressRecorderIter<I> {
OptionalProgressRecorderIter::new(self, generate_every_count)
}
}
impl<I: Iterator> Iterator for OptionalProgressRecorderIter<I> {
type Item = (Option<ProgressRecord>, <I as Iterator>::Item);
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let fake_now = std::mem::take(&mut self._fake_now);
self.iter
.next()
.map(|a| (self.generate_record(fake_now), a))
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
#[inline]
fn count(self) -> usize {
self.iter.count()
}
}