1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
use std::{
borrow::Cow,
fmt::{Debug, Display},
};
use crate::{work::Work, CloseError};
use super::{ProgressMonitor, ProgressMonitorDivision};
/// A child monitor references a parent monitor.
/// It monitors a subset of it's parent's total work, named `parent_work`.
/// This monitors own scale is declared by `sub_work` and can be arbitrary.
/// A child monitor, as a main monitor, tracks its progress. See `sub_work_completed`.
/// Whenever work is being made in a child monitor, the parents progress is increased relative amount.
///
/// Example:
/// Given a ChildMonitor with
/// - parent_work == 10
/// - sub_work == 1000
/// When a work(500) is submitted
/// Then the parent.work(5) is submitted.
#[derive(Debug)]
pub struct ChildMonitor<'n, 'p, W: Work, P: ProgressMonitor<W>> {
name: Cow<'n, str>,
/// A reference to the parent progress monitor. It must be mutable. When we do some work, our parent must also advance.
parent: &'p mut P,
/// Tells how much work of the parent is handled by this child.
parent_work: W,
/// Provides a new scale to work with. If this work is completed, the parent_work is completed.
sub_work: W,
/// How much sub_work was completed.
sub_work_completed: W,
/// Tracks thr amount of work submitted to the parent. Must equal `parent_work` when closing this child monitor!
parent_work_submitted: W,
closed: Option<Result<(), CloseError>>,
}
impl<'n, 'p, W: Work, P: ProgressMonitor<W>> ChildMonitor<'n, 'p, W, P> {
pub fn new(name: Cow<'n, str>, parent: &'p mut P, parent_work: W, sub_work: W) -> Self {
Self {
name,
parent,
parent_work,
sub_work,
sub_work_completed: W::zero(),
parent_work_submitted: W::zero(),
closed: None,
}
}
pub fn name(&self) -> Cow<'n, str> {
self.name.clone()
}
}
impl<'n, 'p, W: Work, P: ProgressMonitor<W>> ProgressMonitor<W> for ChildMonitor<'n, 'p, W, P> {
fn worked<A: Into<W>>(&mut self, amount_of_work: A) {
let amount_of_work: W = amount_of_work.into();
// Advance the work we have done, while preventing overshooting.
let now: W = (self.sub_work_completed.clone() + amount_of_work.clone()).unwrap();
if now > self.sub_work {
// Would overshoot! Just clamp to maximum work possible.
self.sub_work_completed = self.sub_work.clone();
} else {
self.sub_work_completed = now;
}
let finished = self.sub_work_completed == self.sub_work;
// We have to advance our parent work.
if !finished {
// If this child monitor is not yet finished, we can dispatch parent work normally.
// There is the possibility that the given work was more than our total sub work. We have to catch that and clamp.
let amount_of_work: W = W::min(&amount_of_work, &self.sub_work).clone();
let parent_worked: W = W::parent_work_done_when(
amount_of_work,
self.sub_work.clone(),
self.parent_work.clone(),
);
self.parent.worked(parent_worked.clone());
let new_parent_work_submitted =
W::add(self.parent_work_submitted.clone(), parent_worked)
.unwrap()
.clone();
self.parent_work_submitted = new_parent_work_submitted;
} else {
// If this child monitor did all its work, we dispatch all the remaining parent work.
// Why? We advance the parent work with relative work done.
// Based on the actual work type W, this might only be computable with a loss of precision.
// For example by truncating floating point data.
// This may result in us not advancing the parent progress enough, so we simply push the remaining work.
let remaining_parent_work =
self.parent_work.clone() - self.parent_work_submitted.clone();
self.parent.worked(remaining_parent_work.clone());
let new_parent_work_submitted =
W::add(self.parent_work_submitted.clone(), remaining_parent_work)
.unwrap()
.clone();
self.parent_work_submitted = new_parent_work_submitted;
}
}
fn total(&self) -> &W {
&self.sub_work
}
fn completed(&self) -> &W {
&self.sub_work_completed
}
fn remaining(&self) -> Cow<W> {
Cow::Owned(self.sub_work.clone() - self.sub_work_completed.clone())
}
fn close(&mut self) -> Result<(), crate::CloseError> {
if self.closed.is_none() {
let work_left = self.remaining();
let result = if work_left.as_ref() == &W::zero() {
Ok(())
} else {
Err(crate::CloseError { msg: format!("Must not close progress monitor {self:#?} when work left is {work_left} which is != 0.") })
};
self.closed = Some(result.clone()); // Clone is ok, as our happy path is Copy.
result
} else {
// TODO: Forbid multiple closes?
self.closed.clone().unwrap()
}
}
}
impl<'p2, 'n2, 'p, 'n, N, W, A1, A2, P> ProgressMonitorDivision<'p, 'n, N, W, A1, A2>
for ChildMonitor<'n2, 'p2, W, P>
where
Self: ProgressMonitor<W> + Sized,
N: Into<Cow<'n, str>>,
W: Work,
A1: Into<W>,
A2: Into<W>,
P: ProgressMonitor<W>,
{
fn new_child(
&'p mut self,
name: N,
amount_of_parent_work: A1,
amount_of_child_work: A2,
) -> ChildMonitor<'n, 'p, W, Self> {
let amount_of_parent_work: W = amount_of_parent_work.into();
let amount_of_child_work: W = amount_of_child_work.into();
// TODO: As Result?
assert!(&amount_of_parent_work <= self.remaining().as_ref());
ChildMonitor::new(
name.into(),
self,
amount_of_parent_work,
amount_of_child_work,
)
}
}
impl<'n, 'p, W: Work, T: ProgressMonitor<W>> Display for ChildMonitor<'n, 'p, W, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"{}/{}",
self.sub_work_completed, self.sub_work
))
}
}
impl<'n, 'p, W: Work, T: ProgressMonitor<W>> Drop for ChildMonitor<'n, 'p, W, T> {
fn drop(&mut self) {
match &self.closed {
Some(result) => match result {
Ok(()) => { /* do nothing */ }
Err(err) => {
tracing::error!(
"SubMonitor was not successfully closed. Reason: {}",
err.msg
);
}
},
None => {
tracing::warn!("close() was not called on {self:?}!");
self.close().expect("Successful close");
}
}
}
}