zng_task/
progress.rs

1use core::fmt;
2use std::sync::Arc;
3
4use parking_lot::RwLock;
5use zng_state_map::{OwnedStateMap, StateMapMut, StateMapRef};
6use zng_txt::Txt;
7use zng_unit::{Factor, FactorPercent, FactorUnits as _};
8use zng_var::impl_from_and_into_var;
9
10/// Status update about a task progress.
11#[derive(Clone)]
12pub struct Progress {
13    factor: Factor,
14    msg: Txt,
15    meta: Arc<RwLock<OwnedStateMap<Progress>>>,
16}
17impl Progress {
18    /// New indeterminate.
19    pub fn indeterminate() -> Self {
20        Self::new(-1.fct())
21    }
22
23    /// New completed.
24    pub fn complete() -> Self {
25        Self::new(1.fct())
26    }
27
28    /// New with a factor of completion.
29    ///
30    /// The `factor` must be in the `0..=1` range, with a rounding error of `0.001`, values outside this range
31    /// are converted to indeterminate.
32    pub fn from_fct(factor: impl Into<Factor>) -> Self {
33        Self::new(factor.into())
34    }
35
36    /// New with completed `n` of `total`.
37    pub fn from_n_of(n: usize, total: usize) -> Self {
38        Self::new(Self::normalize_n_of(n, total))
39    }
40
41    /// Set the display message about the task status update.
42    pub fn with_msg(mut self, msg: impl Into<Txt>) -> Self {
43        self.msg = msg.into();
44        self
45    }
46
47    /// Set custom status metadata for writing.
48    ///
49    /// Note that metadata is shared between all clones of `self`.
50    pub fn with_meta_mut(self, meta: impl FnOnce(StateMapMut<Progress>)) -> Self {
51        meta(self.meta.write().borrow_mut());
52        self
53    }
54
55    /// Combine the factor completed [`fct`] with another `factor`.
56    ///
57    /// [`fct`]: Self::fct
58    pub fn and_fct(mut self, factor: impl Into<Factor>) -> Self {
59        if self.is_indeterminate() {
60            return self;
61        }
62        let factor = Self::normalize_factor(factor.into());
63        if factor < 0.fct() {
64            // indeterminate
65            self.factor = -1.fct();
66        } else {
67            self.factor = (self.factor + factor) / 2.fct();
68        }
69        self
70    }
71
72    /// Combine the factor completed [`fct`] with another factor computed from `n` of `total`.
73    ///
74    /// [`fct`]: Self::fct
75    pub fn and_n_of(self, n: usize, total: usize) -> Self {
76        self.and_fct(Self::normalize_n_of(n, total))
77    }
78
79    /// Replace the [`fct`] value with a new `factor`.
80    ///
81    /// [`fct`]: Self::fct
82    pub fn with_fct(mut self, factor: impl Into<Factor>) -> Self {
83        self.factor = Self::normalize_factor(factor.into());
84        self
85    }
86
87    /// Replace the [`fct`] value with a new factor computed from `n` of `total`.
88    ///
89    /// [`fct`]: Self::fct
90    pub fn with_n_of(mut self, n: usize, total: usize) -> Self {
91        self.factor = Self::normalize_n_of(n, total);
92        self
93    }
94
95    /// Factor completed.
96    ///
97    /// Is `-1.fct()` for indeterminate, otherwise is a value in the `0..=1` range, `1.fct()` indicates task completion.
98    pub fn fct(&self) -> Factor {
99        self.factor
100    }
101
102    /// Factor of completion cannot be known.
103    pub fn is_indeterminate(&self) -> bool {
104        self.factor < 0.fct()
105    }
106
107    /// Task has completed.
108    pub fn is_complete(&self) -> bool {
109        self.fct() >= 1.fct()
110    }
111
112    /// Display text about the task status update.
113    pub fn msg(&self) -> Txt {
114        self.msg.clone()
115    }
116
117    /// Borrow the custom status metadata for reading.
118    pub fn with_meta<T>(&self, visitor: impl FnOnce(StateMapRef<Progress>) -> T) -> T {
119        visitor(self.meta.read().borrow())
120    }
121
122    fn normalize_factor(mut value: Factor) -> Factor {
123        if value.0 < 0.0 {
124            if value.0 > -0.001 {
125                value.0 = 0.0;
126            } else {
127                // too wrong, indeterminate
128                value.0 = -1.0;
129            }
130        } else if value.0 > 1.0 {
131            if value.0 < 1.001 {
132                value.0 = 1.0;
133            } else {
134                value.0 = -1.0;
135            }
136        } else if !value.0.is_finite() {
137            value.0 = -1.0;
138        }
139        value
140    }
141
142    fn normalize_n_of(n: usize, total: usize) -> Factor {
143        if n > total {
144            -1.fct() // invalid, indeterminate
145        } else if total == 0 {
146            1.fct() // 0 of 0, complete
147        } else {
148            Self::normalize_factor(Factor(n as f32 / total as f32))
149        }
150    }
151
152    fn new(value: Factor) -> Self {
153        Self {
154            factor: Self::normalize_factor(value),
155            msg: Txt::from_static(""),
156            meta: Arc::new(RwLock::new(OwnedStateMap::new())),
157        }
158    }
159}
160impl fmt::Debug for Progress {
161    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162        f.debug_struct("TaskStatus")
163            .field("factor", &self.factor)
164            .field("message", &self.msg)
165            .finish_non_exhaustive()
166    }
167}
168impl fmt::Display for Progress {
169    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
170        if !self.msg.is_empty() {
171            write!(f, "{}", self.msg)?;
172            if !self.is_indeterminate() {
173                write!(f, " ({})", self.factor.pct())
174            } else {
175                Ok(())
176            }
177        } else if !self.is_indeterminate() {
178            write!(f, "{}", self.factor.pct())
179        } else {
180            Ok(())
181        }
182    }
183}
184impl PartialEq for Progress {
185    fn eq(&self, other: &Self) -> bool {
186        self.factor == other.factor && self.msg == other.msg && {
187            let a = self.meta.read();
188            let b = other.meta.read();
189            let a = a.borrow();
190            let b = b.borrow();
191            a.is_empty() == b.is_empty() && (a.is_empty() || Arc::ptr_eq(&self.meta, &other.meta))
192        }
193    }
194}
195impl Eq for Progress {}
196impl_from_and_into_var! {
197    fn from(completed: Factor) -> Progress {
198        Progress::from_fct(completed)
199    }
200    fn from(completed: FactorPercent) -> Progress {
201        Progress::from_fct(completed)
202    }
203    fn from(completed: f32) -> Progress {
204        Progress::from_fct(completed)
205    }
206    fn from(status: Progress) -> Factor {
207        status.fct()
208    }
209    fn from(status: Progress) -> FactorPercent {
210        status.fct().pct()
211    }
212    fn from(status: Progress) -> f32 {
213        status.fct().0
214    }
215    fn from(n_total: (usize, usize)) -> Progress {
216        Progress::from_n_of(n_total.0, n_total.1)
217    }
218    fn from(indeterminate_message: Txt) -> Progress {
219        Progress::indeterminate().with_msg(indeterminate_message)
220    }
221    fn from(indeterminate_message: &'static str) -> Progress {
222        Progress::indeterminate().with_msg(indeterminate_message)
223    }
224    fn from(indeterminate_or_completed: bool) -> Progress {
225        match indeterminate_or_completed {
226            false => Progress::indeterminate(),
227            true => Progress::from_fct(true),
228        }
229    }
230}
231
232#[cfg(test)]
233mod tests {
234    use super::*;
235
236    #[test]
237    fn fct_n1() {
238        let p = Progress::from_fct(-1.fct());
239        assert_eq!(p, Progress::indeterminate());
240    }
241
242    #[test]
243    fn fct_2() {
244        let p = Progress::from_fct(2.fct());
245        assert_eq!(p, Progress::indeterminate());
246    }
247
248    #[test]
249    fn fct_05() {
250        let p = Progress::from_fct(0.5.fct());
251        assert_eq!(p, Progress::from(0.5.fct()));
252    }
253
254    #[test]
255    fn fct_0() {
256        let p = Progress::from_fct(0.fct());
257        assert_eq!(p, Progress::from(0.fct()));
258    }
259
260    #[test]
261    fn fct_1() {
262        let p = Progress::from_fct(1.fct());
263        assert_eq!(p, Progress::from(1.fct()));
264    }
265
266    #[test]
267    fn zero_of_zero() {
268        let p = Progress::from_n_of(0, 0);
269        assert_eq!(p, Progress::complete());
270    }
271
272    #[test]
273    fn ten_of_ten() {
274        let p = Progress::from_n_of(10, 10);
275        assert_eq!(p, Progress::complete());
276    }
277
278    #[test]
279    fn ten_of_one() {
280        let p = Progress::from_n_of(10, 1);
281        assert_eq!(p, Progress::indeterminate());
282    }
283
284    #[test]
285    fn five_of_ten() {
286        let p = Progress::from_n_of(5, 10);
287        assert_eq!(p, Progress::from(50.pct()));
288    }
289}