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)> }
32
33pub fn normalize(path: &Utf8Path) -> &str {
34 path.file_name().unwrap()
35}
36
37#[cfg(feature = "indicatif")]
38struct 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 });
122 }
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 .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 }
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 #[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 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}