use std::{ collections::VecDeque, sync::atomic::Ordering, time::{Duration, SystemTime}, }; use crate::{progress, unit}; const THROTTLE_INTERVAL: Duration = Duration::from_secs(1); const ONCE_A_SECOND: Duration = Duration::from_secs(1); #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)] struct State { observed: Duration, last_value: progress::Step, elapsed_values: VecDeque<(Duration, progress::Step)>, last_update_duration: Duration, precomputed_throughput: Option, } impl State { fn new(value: progress::Step, elapsed: Duration) -> Self { State { observed: elapsed, last_value: value, elapsed_values: { let mut v = VecDeque::with_capacity(6); // default frames per second v.push_back((elapsed, value)); v }, last_update_duration: elapsed, precomputed_throughput: None, } } fn compute_throughput(&mut self) -> progress::Step { let mut observed: Duration = self.elapsed_values.iter().map(|e| e.0).sum(); while !self.elapsed_values.is_empty() && observed > ONCE_A_SECOND { let candidate = self .elapsed_values .front() .map(|e| e.0) .expect("at least one item as we are in the checked loop"); if observed.checked_sub(candidate).unwrap_or_default() <= ONCE_A_SECOND { break; } observed -= candidate; self.elapsed_values.pop_front(); } let observed_value: progress::Step = self.elapsed_values.iter().map(|e| e.1).sum(); ((observed_value as f64 / observed.as_secs_f64()) * ONCE_A_SECOND.as_secs_f64()) as progress::Step } fn update(&mut self, value: progress::Step, elapsed: Duration) -> Option { self.observed += elapsed; self.elapsed_values .push_back((elapsed, value.saturating_sub(self.last_value))); self.last_value = value; if self.observed - self.last_update_duration > THROTTLE_INTERVAL { self.precomputed_throughput = Some(self.compute_throughput()); self.last_update_duration = self.observed; } self.throughput() } fn throughput(&self) -> Option { self.precomputed_throughput.map(|tp| unit::display::Throughput { value_change_in_timespan: tp, timespan: ONCE_A_SECOND, }) } } /// A utility to compute throughput of a set of progress values usually available to a renderer. #[derive(Default)] pub struct Throughput { sorted_by_key: Vec<(progress::Key, State)>, updated_at: Option, elapsed: Option, } impl Throughput { /// Called at the beginning of the drawing of a renderer to remember at which time progress values are /// going to be updated with [`update_and_get(…)`][Throughput::update_and_get()]. pub fn update_elapsed(&mut self) { let now = SystemTime::now(); self.elapsed = self.updated_at.and_then(|then| now.duration_since(then).ok()); self.updated_at = Some(now); } /// Lookup or create the progress value at `key` and set its current `progress`, returning its computed /// throughput. pub fn update_and_get( &mut self, key: &progress::Key, progress: Option<&progress::Value>, ) -> Option { progress.and_then(|progress| { self.elapsed .and_then(|elapsed| match self.sorted_by_key.binary_search_by_key(key, |t| t.0) { Ok(index) => self.sorted_by_key[index] .1 .update(progress.step.load(Ordering::SeqCst), elapsed), Err(index) => { let state = State::new(progress.step.load(Ordering::SeqCst), elapsed); let tp = state.throughput(); self.sorted_by_key.insert(index, (*key, state)); tp } }) }) } /// Compare the keys in `sorted_values` with our internal state and remove all missing tasks from it. /// /// This should be called after [`update_and_get(…)`][Throughput::update_and_get()] to pick up removed/finished /// progress. pub fn reconcile(&mut self, sorted_values: &[(progress::Key, progress::Task)]) { self.sorted_by_key .retain(|(key, _)| sorted_values.binary_search_by_key(key, |e| e.0).is_ok()); } }