progress_monitor/monitor/
sub.rs

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