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#[derive(Clone)]
12pub struct Progress {
13 factor: Factor,
14 msg: Txt,
15 meta: Arc<RwLock<OwnedStateMap<Progress>>>,
16}
17impl Progress {
18 pub fn indeterminate() -> Self {
20 Self::new(-1.fct())
21 }
22
23 pub fn complete() -> Self {
25 Self::new(1.fct())
26 }
27
28 pub fn from_fct(factor: impl Into<Factor>) -> Self {
33 Self::new(factor.into())
34 }
35
36 pub fn from_n_of(n: usize, total: usize) -> Self {
38 Self::new(Self::normalize_n_of(n, total))
39 }
40
41 pub fn with_msg(mut self, msg: impl Into<Txt>) -> Self {
43 self.msg = msg.into();
44 self
45 }
46
47 pub fn with_meta_mut(self, meta: impl FnOnce(StateMapMut<Progress>)) -> Self {
51 meta(self.meta.write().borrow_mut());
52 self
53 }
54
55 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 self.factor = -1.fct();
66 } else {
67 self.factor = (self.factor + factor) / 2.fct();
68 }
69 self
70 }
71
72 pub fn and_n_of(self, n: usize, total: usize) -> Self {
76 self.and_fct(Self::normalize_n_of(n, total))
77 }
78
79 pub fn with_fct(mut self, factor: impl Into<Factor>) -> Self {
83 self.factor = Self::normalize_factor(factor.into());
84 self
85 }
86
87 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 pub fn fct(&self) -> Factor {
99 self.factor
100 }
101
102 pub fn is_indeterminate(&self) -> bool {
104 self.factor < 0.fct()
105 }
106
107 pub fn is_complete(&self) -> bool {
109 self.fct() >= 1.fct()
110 }
111
112 pub fn msg(&self) -> Txt {
114 self.msg.clone()
115 }
116
117 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 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() } else if total == 0 {
146 1.fct() } 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}