use crate::{NodeId, Snapshot, Status, Total};
use alloc::vec::Vec;
use core::time::Duration;
const BELOW_ONE: f64 = 1.0 - f64::EPSILON / 2.0;
#[derive(Default)]
pub struct ProgressSmoother {
leaves: Vec<Pace>,
}
impl core::fmt::Debug for ProgressSmoother {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("ProgressSmoother")
.field("tracked_paces", &self.leaves.len())
.finish()
}
}
struct Pace {
node: NodeId,
total: u64,
count: u64,
since: Duration,
step: f64,
interval: f64,
changes: u32,
last_step: f64,
shown: f64,
seen: bool,
}
impl Pace {
fn new(node: NodeId, total: u64, count: u64, now: Duration) -> Self {
Self {
node,
total,
count,
since: now,
step: 0.0,
interval: 0.0,
changes: 0,
last_step: 0.0,
shown: 0.0,
seen: true,
}
}
fn learn(&mut self, step: f64, interval: f64) {
let weight = if self.changes == 0 || interval < self.interval {
1.0
} else {
0.25
};
self.step += (step - self.step) * weight;
self.interval += (interval - self.interval) * weight;
self.changes = self.changes.saturating_add(1);
}
fn estimate(&mut self, count: u64, now: Duration) -> f64 {
if count > self.count {
let step = (count - self.count) as f64;
let elapsed = now.saturating_sub(self.since).as_secs_f64();
if elapsed > 0.0 {
self.learn(step, elapsed);
self.since = now;
}
self.last_step = step;
self.count = count;
}
let total = self.total as f64;
let mut estimate = self.count as f64;
if self.count < self.total && self.changes != 0 && self.interval > 0.0 {
let elapsed = now.saturating_sub(self.since).as_secs_f64();
estimate += (self.step * elapsed / self.interval)
.min(self.step)
.min(self.last_step)
.min((total - estimate) / 2.0);
}
let mut fraction = (estimate / total).min(1.0);
if self.count < self.total {
fraction = fraction.min(BELOW_ONE);
}
self.shown = self.shown.max(fraction);
self.shown
}
}
impl ProgressSmoother {
pub fn new() -> Self {
Self::default()
}
pub fn display_fraction(&mut self, snapshot: &Snapshot, now: Duration) -> Option<f64> {
for pace in &mut self.leaves {
pace.seen = false;
}
let estimate = self.node(snapshot, now);
self.leaves.retain(|pace| pace.seen);
estimate
}
fn node(&mut self, node: &Snapshot, now: Duration) -> Option<f64> {
if node.children.is_empty() {
return match (node.status, node.total) {
(Status::Running, Total::Exact(total) | Total::Estimated(total))
if total != 0 && !node.overflowed =>
{
Some(self.leaf(node.id, node.completed, total, now))
}
_ => node.fraction(),
};
}
if let Status::Finished(_) = node.status {
return node.fraction();
}
let mut sum = 0.0;
for child in &node.children {
sum += child.weight as f64;
}
let mut result = 0.0;
for child in &node.children {
result += child.weight as f64 / sum * self.node(child, now).unwrap_or(0.0);
}
Some(result.clamp(0.0, 1.0))
}
fn leaf(&mut self, node: NodeId, count: u64, total: u64, now: Duration) -> f64 {
let index = match self.leaves.binary_search_by_key(&node, |pace| pace.node) {
Ok(index) => index,
Err(index) => {
self.leaves
.insert(index, Pace::new(node, total, count, now));
index
}
};
let pace = &mut self.leaves[index];
if pace.total != total {
*pace = Pace::new(node, total, count, now);
}
pace.seen = true;
pace.estimate(count, now)
}
}