cpclib_asm/
progress.rs

1use core::time::Duration;
2use std::sync::{Arc, LazyLock, Mutex, MutexGuard};
3
4use cpclib_common::camino::Utf8Path;
5use cpclib_common::itertools::Itertools;
6#[cfg(feature = "indicatif")]
7use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
8
9static PROGRESS: LazyLock<Arc<Mutex<Progress>>> =
10    LazyLock::new(|| Arc::new(Mutex::new(Progress::new())));
11
12const REFRESH_RATE: Duration = Duration::from_millis(250);
13const PROGRESS_STYLE: &str = "{prefix:.bold.dim>8}  [{bar}] {pos:>3}/{len:3} {wide_msg}";
14const PASS_STYLE: &str = "{prefix:.bold.dim>8}  [{bar}] ";
15
16#[cfg(feature = "indicatif")]
17pub struct Progress {
18    multi: MultiProgress,
19    parse: CountedProgress,
20    load: CountedProgress,
21    save: Option<CountedProgress>,
22    pass: Option<(usize, ProgressBar)>
23}
24
25#[cfg(not(feature = "indicatif"))]
26pub struct Progress {
27    parse: CountedProgress,
28    load: CountedProgress,
29    save: Option<CountedProgress>,
30    pass: Option<(usize, usize, usize)> // pass, nb ivisited, nb to do
31}
32
33pub fn normalize(path: &Utf8Path) -> &str {
34    path.file_name().unwrap()
35}
36
37#[cfg(feature = "indicatif")]
38// TODO add the multiprogess bar as a field and never pass it as an argument
39// it will allow to reduce duplicated code with indicatf/no indicatif versions
40struct CountedProgress {
41    bar: Option<ProgressBar>,
42    current_items: hashbag::HashBag<String>,
43    nb_expected: u64,
44    nb_done: u64,
45    prefix: &'static str,
46    index: usize,
47    freeze_amount: bool
48}
49
50#[cfg(not(feature = "indicatif"))]
51struct CountedProgress {
52    current_items: hashbag::HashBag<String>,
53    nb_expected: u64,
54    nb_done: u64,
55    prefix: &'static str,
56    index: usize,
57    freeze_amount: bool,
58    last_tick: std::time::SystemTime
59}
60
61#[cfg(feature = "indicatif")]
62impl CountedProgress {
63    pub fn new(kind: &'static str, index: usize, freeze_amount: bool) -> Self {
64        CountedProgress {
65            bar: None,
66            current_items: hashbag::HashBag::new(),
67            nb_done: 0,
68            nb_expected: 0,
69            prefix: kind,
70            index,
71            freeze_amount
72        }
73    }
74
75    fn add_item(&mut self, item: &str, multi: &MultiProgress) {
76        if !self.freeze_amount {
77            self.nb_expected += 1;
78        }
79        self.current_items.insert(item.into());
80        self.update_visual(multi);
81    }
82
83    fn add_items<'a>(&mut self, items: impl Iterator<Item = &'a str>, multi: &MultiProgress) {
84        let mut count = 0;
85        for item in items {
86            self.current_items.insert(String::from(item));
87            count += 1;
88        }
89
90        if !self.freeze_amount {
91            self.nb_expected += count;
92        }
93        self.update_visual(multi);
94    }
95
96    fn remove_item(&mut self, item: &str, multi: &MultiProgress) {
97        self.nb_done += 1;
98        self.current_items.remove(item);
99        self.update_visual(multi);
100    }
101
102    fn finished(&mut self) {
103        if let Some(bar) = self.bar.as_mut() {
104            bar.finish()
105        }
106    }
107
108    fn update_visual(&mut self, multi: &MultiProgress) {
109        let visible = self.bar.is_some();
110
111        if self.nb_done == self.nb_expected {
112            if visible {
113                self.bar.as_ref().map(|bar| {
114                    bar.set_message("");
115                    bar.set_position(self.nb_done);
116                    bar.set_length(self.nb_expected);
117
118                    bar.tick();
119
120                    // multi.remove(bar);
121                });
122                // self.bar = None;
123            }
124        }
125        else {
126            let content = self.current_items.iter().join(", ");
127
128            if !visible {
129                self.bar = Some(multi.add(ProgressBar::new(self.nb_expected)));
130                self.bar.as_ref().map(|bar| {
131                    bar.set_style(
132                        ProgressStyle::with_template(PROGRESS_STYLE)
133                            .unwrap()
134                            .progress_chars("=> ")
135                    );
136                    bar.set_prefix(self.prefix);
137                });
138            }
139
140            self.bar.as_ref().map(|bar| {
141                bar.set_message(content);
142                bar.set_position(self.nb_done);
143                bar.set_length(self.nb_expected);
144                bar.tick();
145            });
146        }
147    }
148}
149
150#[cfg(not(feature = "indicatif"))]
151impl CountedProgress {
152    pub fn new(kind: &'static str, index: usize, freeze_amount: bool) -> Self {
153        CountedProgress {
154            current_items: hashbag::HashBag::new(),
155            nb_done: 0,
156            nb_expected: 0,
157            prefix: kind,
158            index,
159            freeze_amount,
160            last_tick: std::time::SystemTime::now()
161        }
162    }
163
164    fn add_item(&mut self, item: &str) {
165        if !self.freeze_amount {
166            self.nb_expected += 1;
167        }
168        self.current_items.insert(item.into());
169        self.update_visual();
170    }
171
172    fn add_items<'a>(&mut self, items: impl Iterator<Item = &'a str>) {
173        let mut count = 0;
174        for item in items {
175            self.current_items.insert(String::from(item));
176            count += 1;
177        }
178
179        if !self.freeze_amount {
180            self.nb_expected += count;
181        }
182        self.update_visual();
183    }
184
185    fn remove_item(&mut self, item: &str) {
186        self.nb_done += 1;
187        self.current_items.remove(item);
188        self.update_visual();
189    }
190
191    fn finished(&mut self) {}
192
193    fn update_visual(&mut self) {
194        const HZ: u128 = 1000 / 15;
195
196        if self.last_tick.elapsed().unwrap().as_millis() >= HZ {
197            self.really_show();
198
199            self.last_tick = std::time::SystemTime::now();
200        }
201    }
202
203    fn really_show(&self) {
204        let content = self.current_items.iter().join(", ");
205        let other_content = &content[..70.min(content.len())];
206        let extra = if other_content.len() != content.len() {
207            "..."
208        }
209        else {
210            ""
211        };
212
213        println!(
214            "{} [{}/{}] {}{}",
215            self.prefix, self.nb_done, self.nb_expected, other_content, extra
216        );
217    }
218}
219
220#[cfg(feature = "indicatif")]
221fn new_spinner() -> ProgressBar {
222    let bar = ProgressBar::new_spinner();
223
224    bar.set_style(
225        ProgressStyle::with_template("{spinner:.blue} {msg}")
226            // For more spinners check out the cli-spinners project:
227            // https://github.com/sindresorhus/cli-spinners/blob/master/spinners.json
228            .unwrap()
229            .tick_strings(&[
230                "▹▹▹▹▹",
231                "▸▹▹▹▹",
232                "▹▸▹▹▹",
233                "▹▹▸▹▹",
234                "▹▹▹▸▹",
235                "▹▹▹▹▸",
236                "▪▪▪▪▪"
237            ])
238    );
239    bar.enable_steady_tick(REFRESH_RATE);
240    bar
241}
242
243impl Default for Progress {
244    fn default() -> Self {
245        Self::new()
246    }
247}
248
249impl Progress {
250    pub fn progress() -> MutexGuard<'static, Progress> {
251        PROGRESS.lock().unwrap()
252    }
253
254    #[cfg(feature = "indicatif")]
255    pub fn new() -> Self {
256        let multi = MultiProgress::new();
257        multi.set_move_cursor(true);
258
259        Progress {
260            multi,
261            load: CountedProgress::new("  Load", 0, false),
262            parse: CountedProgress::new(" Parse", 1, false),
263            save: None,
264            pass: None
265        }
266    }
267
268    #[cfg(not(feature = "indicatif"))]
269    pub fn new() -> Self {
270        Progress {
271            load: CountedProgress::new("  Load", 0, false),
272            parse: CountedProgress::new(" Parse", 1, false),
273            save: None,
274            pass: None
275        }
276    }
277
278    pub fn add_parse(&mut self, ident: &str) {
279        #[cfg(feature = "indicatif")]
280        self.parse.add_item(ident, &self.multi);
281
282        #[cfg(not(feature = "indicatif"))]
283        self.parse.add_item(ident);
284    }
285
286    pub fn add_parses<'a>(&mut self, items: impl Iterator<Item = &'a str>) {
287        #[cfg(feature = "indicatif")]
288        self.parse.add_items(items, &self.multi);
289
290        #[cfg(not(feature = "indicatif"))]
291        self.parse.add_items(items);
292    }
293
294    pub fn remove_parse(&mut self, ident: &str) {
295        #[cfg(feature = "indicatif")]
296        self.parse.remove_item(ident, &self.multi);
297
298        #[cfg(not(feature = "indicatif"))]
299        self.parse.remove_item(ident);
300    }
301
302    pub fn add_load(&mut self, ident: &str) {
303        #[cfg(feature = "indicatif")]
304        self.load.add_item(ident, &self.multi);
305
306        #[cfg(not(feature = "indicatif"))]
307        self.load.add_item(ident);
308    }
309
310    pub fn add_loads<'a>(&mut self, items: impl Iterator<Item = &'a str>) {
311        #[cfg(feature = "indicatif")]
312        self.load.add_items(items, &self.multi);
313
314        #[cfg(not(feature = "indicatif"))]
315        self.load.add_items(items);
316    }
317
318    pub fn remove_load(&mut self, ident: &str) {
319        #[cfg(feature = "indicatif")]
320        self.load.remove_item(ident, &self.multi);
321
322        #[cfg(not(feature = "indicatif"))]
323        self.load.remove_item(ident);
324    }
325
326    #[cfg(feature = "indicatif")]
327    pub fn new_pass(&mut self) {
328        if self.pass.is_none() {
329            let bar = ProgressBar::new(0);
330            bar.set_style(
331                ProgressStyle::with_template(PASS_STYLE)
332                    .unwrap()
333                    .progress_chars("=> ")
334            );
335            self.pass = Some((0, self.multi.add(bar)));
336        }
337        else {
338            // todo change pass numbering
339        }
340
341        self.pass.as_mut().map(|(pass, bar)| {
342            *pass += 1;
343            bar.set_prefix(format!("Pass {}", *pass));
344            bar.set_position(0);
345            bar.set_length(0);
346        });
347    }
348
349    #[cfg(not(feature = "indicatif"))]
350    pub fn new_pass(&mut self) {
351        if self.pass.is_none() {
352            self.pass = Some((0, 0, 0));
353        }
354
355        self.pass
356            .as_mut()
357            .map(|pass: &mut (usize, usize, usize)| *pass = (pass.0 + 1, 0, 0));
358    }
359
360    #[cfg(feature = "indicatif")]
361    pub fn add_visited_to_pass(&mut self, amount: u64) {
362        self.pass.as_mut().unwrap().1.inc(amount);
363    }
364
365    #[cfg(not(feature = "indicatif"))]
366    pub fn add_visited_to_pass(&mut self, amount: u64) {
367        self.pass.as_mut().unwrap().1 += amount as usize;
368    }
369
370    #[cfg(feature = "indicatif")]
371    pub fn add_expected_to_pass(&mut self, amount: u64) {
372        self.pass.as_mut().unwrap().1.inc_length(amount);
373    }
374
375    #[cfg(not(feature = "indicatif"))]
376    pub fn add_expected_to_pass(&mut self, amount: u64) {
377        self.pass.as_mut().unwrap().2 += amount as usize;
378    }
379
380    pub fn create_save_bar(&mut self, amount: u64) {
381        let mut bar = CountedProgress::new("  Save", 2, true);
382        bar.nb_expected = amount;
383        self.save = Some(bar);
384    }
385
386    pub fn add_save(&mut self, ident: &str) {
387        #[cfg(feature = "indicatif")]
388        self.save.as_mut().unwrap().add_item(ident, &self.multi);
389
390        #[cfg(not(feature = "indicatif"))]
391        self.save.as_mut().unwrap().add_item(ident);
392    }
393
394    pub fn remove_save(&mut self, ident: &str) {
395        #[cfg(feature = "indicatif")]
396        self.save.as_mut().unwrap().remove_item(ident, &self.multi);
397
398        #[cfg(not(feature = "indicatif"))]
399        self.save.as_mut().unwrap().remove_item(ident);
400    }
401
402    pub fn finish_save(&mut self) {
403        self.save.as_mut().unwrap().finished();
404    }
405
406    /// Add the progress bar for a file to read
407    #[cfg(feature = "indicatif")]
408    pub fn add_bar(&self, msg: &str) -> ProgressBar {
409        let bar = new_spinner();
410        let bar = self.multi.add(bar);
411        bar.set_message(msg.to_owned());
412        bar
413    }
414
415    #[cfg(feature = "indicatif")]
416    /// Remove the progress bar of the current file
417    pub fn remove_bar_ok(&self, bar: &ProgressBar) {
418        bar.disable_steady_tick();
419        bar.finish_and_clear();
420        bar.tick();
421        self.multi.remove(bar);
422    }
423
424    #[cfg(feature = "indicatif")]
425    pub fn remove_bar_err(&self, bar: &ProgressBar, msg: &str) {
426        bar.disable_steady_tick();
427        bar.abandon_with_message(msg.to_owned());
428        bar.tick();
429        self.multi.remove(bar);
430    }
431}