Skip to main content

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::{IntoVar, Var, const_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: Var<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: u64, total: u64) -> 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 IntoVar<Txt>) -> Self {
43        self.msg = msg.into_var();
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: u64, total: u64) -> 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: u64, total: u64) -> 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) -> Var<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: u64, total: u64) -> 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 f64 / total as f64) as f32))
149        }
150    }
151
152    fn new(value: Factor) -> Self {
153        Self {
154            factor: Self::normalize_factor(value),
155            msg: const_var(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("msg", &self.msg.get())
165            .finish_non_exhaustive()
166    }
167}
168impl fmt::Display for Progress {
169    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
170        let msg = self.msg.get();
171        if !msg.is_empty() {
172            write!(f, "{msg}")?;
173            if !self.is_indeterminate() {
174                write!(f, " ({})", self.factor.pct())
175            } else {
176                Ok(())
177            }
178        } else if !self.is_indeterminate() {
179            write!(f, "{}", self.factor.pct())
180        } else {
181            Ok(())
182        }
183    }
184}
185impl PartialEq for Progress {
186    fn eq(&self, other: &Self) -> bool {
187        self.factor == other.factor
188            && if self.msg.capabilities().is_const() && other.msg.capabilities().is_const() {
189                self.msg.with(|a| other.msg.with(|b| a == b))
190            } else {
191                self.msg.var_eq(&other.msg)
192            }
193            && {
194                let a = self.meta.read();
195                let b = other.meta.read();
196                let a = a.borrow();
197                let b = b.borrow();
198                a.is_empty() == b.is_empty() && (a.is_empty() || Arc::ptr_eq(&self.meta, &other.meta))
199            }
200    }
201}
202impl Eq for Progress {}
203impl_from_and_into_var! {
204    fn from(completed: Factor) -> Progress {
205        Progress::from_fct(completed)
206    }
207    fn from(completed: FactorPercent) -> Progress {
208        Progress::from_fct(completed)
209    }
210    fn from(completed: f32) -> Progress {
211        Progress::from_fct(completed)
212    }
213    fn from(status: Progress) -> Factor {
214        status.fct()
215    }
216    fn from(status: Progress) -> FactorPercent {
217        status.fct().pct()
218    }
219    fn from(status: Progress) -> f32 {
220        status.fct().0
221    }
222    fn from(n_total: (u64, u64)) -> Progress {
223        Progress::from_n_of(n_total.0, n_total.1)
224    }
225    fn from(indeterminate_message: Txt) -> Progress {
226        Progress::indeterminate().with_msg(indeterminate_message)
227    }
228    fn from(indeterminate_message: &'static str) -> Progress {
229        Progress::indeterminate().with_msg(indeterminate_message)
230    }
231    fn from(indeterminate_or_completed: bool) -> Progress {
232        match indeterminate_or_completed {
233            false => Progress::indeterminate(),
234            true => Progress::from_fct(true),
235        }
236    }
237}
238
239#[cfg(test)]
240mod tests {
241    use super::*;
242
243    #[test]
244    fn fct_n1() {
245        let p = Progress::from_fct(-1.fct());
246        assert_eq!(p, Progress::indeterminate());
247    }
248
249    #[test]
250    fn fct_2() {
251        let p = Progress::from_fct(2.fct());
252        assert_eq!(p, Progress::indeterminate());
253    }
254
255    #[test]
256    fn fct_05() {
257        let p = Progress::from_fct(0.5.fct());
258        assert_eq!(p, Progress::from(0.5.fct()));
259    }
260
261    #[test]
262    fn fct_0() {
263        let p = Progress::from_fct(0.fct());
264        assert_eq!(p, Progress::from(0.fct()));
265    }
266
267    #[test]
268    fn fct_1() {
269        let p = Progress::from_fct(1.fct());
270        assert_eq!(p, Progress::from(1.fct()));
271    }
272
273    #[test]
274    fn zero_of_zero() {
275        let p = Progress::from_n_of(0, 0);
276        assert_eq!(p, Progress::complete());
277    }
278
279    #[test]
280    fn ten_of_ten() {
281        let p = Progress::from_n_of(10, 10);
282        assert_eq!(p, Progress::complete());
283    }
284
285    #[test]
286    fn ten_of_one() {
287        let p = Progress::from_n_of(10, 1);
288        assert_eq!(p, Progress::indeterminate());
289    }
290
291    #[test]
292    fn five_of_ten() {
293        let p = Progress::from_n_of(5, 10);
294        assert_eq!(p, Progress::from(50.pct()));
295    }
296}