1use std::fmt::{Debug, Formatter};
2use std::io;
3use std::sync::{Arc, RwLock};
4use std::thread::panicking;
5#[cfg(not(target_arch = "wasm32"))]
6use std::time::Instant;
7
8use crate::draw_target::{
9 visual_line_count, DrawState, DrawStateWrapper, LineAdjust, LineType, ProgressDrawTarget,
10 VisualLines,
11};
12use crate::progress_bar::ProgressBar;
13#[cfg(target_arch = "wasm32")]
14use web_time::Instant;
15
16#[derive(Debug, Clone)]
18pub struct MultiProgress {
19 pub(crate) state: Arc<RwLock<MultiState>>,
20}
21
22impl Default for MultiProgress {
23 fn default() -> Self {
24 Self::with_draw_target(ProgressDrawTarget::stderr())
25 }
26}
27
28impl MultiProgress {
29 pub fn new() -> Self {
38 Self::default()
39 }
40
41 pub fn with_draw_target(draw_target: ProgressDrawTarget) -> Self {
43 Self {
44 state: Arc::new(RwLock::new(MultiState::new(draw_target))),
45 }
46 }
47
48 pub fn set_draw_target(&self, target: ProgressDrawTarget) {
52 let mut state = self.state.write().unwrap();
53 state.draw_target.disconnect(Instant::now());
54 state.draw_target = target;
55 }
56
57 pub fn set_move_cursor(&self, move_cursor: bool) {
62 self.state
63 .write()
64 .unwrap()
65 .draw_target
66 .set_move_cursor(move_cursor);
67 }
68
69 pub fn set_alignment(&self, alignment: MultiProgressAlignment) {
71 self.state.write().unwrap().alignment = alignment;
72 }
73
74 pub fn add(&self, pb: ProgressBar) -> ProgressBar {
86 self.internalize(InsertLocation::End, pb)
87 }
88
89 pub fn insert(&self, index: usize, pb: ProgressBar) -> ProgressBar {
101 self.internalize(InsertLocation::Index(index), pb)
102 }
103
104 pub fn insert_from_back(&self, index: usize, pb: ProgressBar) -> ProgressBar {
117 self.internalize(InsertLocation::IndexFromBack(index), pb)
118 }
119
120 pub fn insert_before(&self, before: &ProgressBar, pb: ProgressBar) -> ProgressBar {
129 self.internalize(InsertLocation::Before(before.index().unwrap()), pb)
130 }
131
132 pub fn insert_after(&self, after: &ProgressBar, pb: ProgressBar) -> ProgressBar {
141 self.internalize(InsertLocation::After(after.index().unwrap()), pb)
142 }
143
144 pub fn remove(&self, pb: &ProgressBar) {
151 let mut state = pb.state();
152 let idx = match &state.draw_target.remote() {
153 Some((state, idx)) => {
154 assert!(Arc::ptr_eq(&self.state, state));
156 *idx
157 }
158 _ => return,
159 };
160
161 state.draw_target = ProgressDrawTarget::hidden();
162 self.state.write().unwrap().remove_idx(idx);
163 }
164
165 fn internalize(&self, location: InsertLocation, pb: ProgressBar) -> ProgressBar {
166 let mut state = self.state.write().unwrap();
167 let idx = state.insert(location);
168 drop(state);
169
170 pb.set_draw_target(ProgressDrawTarget::new_remote(self.state.clone(), idx));
171 pb
172 }
173
174 pub fn println<I: AsRef<str>>(&self, msg: I) -> io::Result<()> {
179 let mut state = self.state.write().unwrap();
180 state.println(msg, Instant::now())
181 }
182
183 pub fn suspend<F: FnOnce() -> R, R>(&self, f: F) -> R {
193 let mut state = self.state.write().unwrap();
194 state.suspend(f, Instant::now())
195 }
196
197 pub fn clear(&self) -> io::Result<()> {
198 self.state.write().unwrap().clear(Instant::now())
199 }
200
201 pub fn is_hidden(&self) -> bool {
202 self.state.read().unwrap().is_hidden()
203 }
204}
205
206#[derive(Debug)]
207pub(crate) struct MultiState {
208 members: Vec<MultiStateMember>,
210 free_set: Vec<usize>,
213 ordering: Vec<usize>,
215 draw_target: ProgressDrawTarget,
217 alignment: MultiProgressAlignment,
219 orphan_lines: Vec<LineType>,
222 zombie_lines_count: VisualLines,
224}
225
226impl MultiState {
227 fn new(draw_target: ProgressDrawTarget) -> Self {
228 Self {
229 members: vec![],
230 free_set: vec![],
231 ordering: vec![],
232 draw_target,
233 alignment: MultiProgressAlignment::default(),
234 orphan_lines: Vec::new(),
235 zombie_lines_count: VisualLines::default(),
236 }
237 }
238
239 pub(crate) fn mark_zombie(&mut self, index: usize) {
240 let width = self.width().map(usize::from);
241
242 let member = &mut self.members[index];
243
244 if index != self.ordering.first().copied().unwrap() {
247 member.is_zombie = true;
248 return;
249 }
250
251 let line_count = member
252 .draw_state
253 .as_ref()
254 .zip(width)
255 .map(|(d, width)| d.visual_line_count(.., width))
256 .unwrap_or_default();
257
258 self.zombie_lines_count = self.zombie_lines_count.saturating_add(line_count);
260
261 self.draw_target
263 .adjust_last_line_count(LineAdjust::Keep(line_count));
264
265 self.remove_idx(index);
266 }
267
268 pub(crate) fn draw(
269 &mut self,
270 mut force_draw: bool,
271 extra_lines: Option<Vec<LineType>>,
272 now: Instant,
273 ) -> io::Result<()> {
274 if panicking() {
275 return Ok(());
276 }
277
278 let width = match self.width() {
279 Some(width) => width as usize,
280 None => return Ok(()),
281 };
282
283 debug_assert_eq!(
285 extra_lines.is_some(),
286 extra_lines.as_ref().map(Vec::len).unwrap_or_default() > 0
287 );
288
289 let mut reap_indices = vec![];
290
291 let mut adjust = VisualLines::default();
293 for &index in &self.ordering {
294 let member = &self.members[index];
295 if !member.is_zombie {
296 break;
297 }
298
299 let line_count = member
300 .draw_state
301 .as_ref()
302 .map(|d| d.visual_line_count(.., width))
303 .unwrap_or_default();
304 self.zombie_lines_count += line_count;
306
307 adjust += line_count;
309
310 reap_indices.push(index);
311 }
312
313 if extra_lines.is_some() {
317 self.draw_target
318 .adjust_last_line_count(LineAdjust::Clear(self.zombie_lines_count));
319 self.zombie_lines_count = VisualLines::default();
320 }
321
322 let orphan_visual_line_count = visual_line_count(&self.orphan_lines, width);
323 force_draw |= orphan_visual_line_count > VisualLines::default();
324 let mut drawable = match self.draw_target.drawable(force_draw, now) {
325 Some(drawable) => drawable,
326 None => return Ok(()),
327 };
328
329 let mut draw_state = drawable.state();
330 draw_state.alignment = self.alignment;
331
332 if let Some(extra_lines) = &extra_lines {
333 draw_state.lines.extend_from_slice(extra_lines.as_slice());
334 }
335
336 draw_state.lines.append(&mut self.orphan_lines);
338
339 for index in &self.ordering {
340 let member = &self.members[*index];
341 if let Some(state) = &member.draw_state {
342 draw_state.lines.extend_from_slice(&state.lines[..]);
343 }
344 }
345
346 drop(draw_state);
347 let drawable = drawable.draw();
348
349 for index in reap_indices {
350 self.remove_idx(index);
351 }
352
353 if extra_lines.is_none() {
356 self.draw_target
357 .adjust_last_line_count(LineAdjust::Keep(adjust));
358 }
359
360 drawable
361 }
362
363 pub(crate) fn println<I: AsRef<str>>(&mut self, msg: I, now: Instant) -> io::Result<()> {
364 let msg = msg.as_ref();
365
366 let lines: Vec<LineType> = match msg.is_empty() {
368 false => msg.lines().map(|l| LineType::Text(Into::into(l))).collect(),
369 true => vec![LineType::Empty],
370 };
371
372 self.draw(true, Some(lines), now)
373 }
374
375 pub(crate) fn draw_state(&mut self, idx: usize) -> DrawStateWrapper<'_> {
376 let member = self.members.get_mut(idx).unwrap();
377 let state = member.draw_state.get_or_insert(DrawState::default());
380
381 DrawStateWrapper::for_multi(state, &mut self.orphan_lines)
382 }
383
384 pub(crate) fn is_hidden(&self) -> bool {
385 self.draw_target.is_hidden()
386 }
387
388 pub(crate) fn suspend<F: FnOnce() -> R, R>(&mut self, f: F, now: Instant) -> R {
389 self.clear(now).unwrap();
390 let ret = f();
391 self.draw(true, None, Instant::now()).unwrap();
392 ret
393 }
394
395 pub(crate) fn width(&self) -> Option<u16> {
396 self.draw_target.width()
397 }
398
399 fn insert(&mut self, location: InsertLocation) -> usize {
400 let idx = if let Some(idx) = self.free_set.pop() {
401 self.members[idx] = MultiStateMember::default();
402 idx
403 } else {
404 self.members.push(MultiStateMember::default());
405 self.members.len() - 1
406 };
407
408 match location {
409 InsertLocation::End => self.ordering.push(idx),
410 InsertLocation::Index(pos) => {
411 let pos = Ord::min(pos, self.ordering.len());
412 self.ordering.insert(pos, idx);
413 }
414 InsertLocation::IndexFromBack(pos) => {
415 let pos = self.ordering.len().saturating_sub(pos);
416 self.ordering.insert(pos, idx);
417 }
418 InsertLocation::After(after_idx) => {
419 let pos = self.ordering.iter().position(|i| *i == after_idx).unwrap();
420 self.ordering.insert(pos + 1, idx);
421 }
422 InsertLocation::Before(before_idx) => {
423 let pos = self.ordering.iter().position(|i| *i == before_idx).unwrap();
424 self.ordering.insert(pos, idx);
425 }
426 }
427
428 assert_eq!(
429 self.len(),
430 self.ordering.len(),
431 "Draw state is inconsistent"
432 );
433
434 idx
435 }
436
437 fn clear(&mut self, now: Instant) -> io::Result<()> {
438 match self.draw_target.drawable(true, now) {
439 Some(mut drawable) => {
440 drawable.adjust_last_line_count(LineAdjust::Clear(self.zombie_lines_count));
442 self.zombie_lines_count = VisualLines::default();
443 drawable.clear()
444 }
445 None => Ok(()),
446 }
447 }
448
449 fn remove_idx(&mut self, idx: usize) {
450 if self.free_set.contains(&idx) {
451 return;
452 }
453
454 self.members[idx] = MultiStateMember::default();
455 self.free_set.push(idx);
456 self.ordering.retain(|&x| x != idx);
457
458 assert_eq!(
459 self.len(),
460 self.ordering.len(),
461 "Draw state is inconsistent"
462 );
463 }
464
465 fn len(&self) -> usize {
466 self.members.len() - self.free_set.len()
467 }
468}
469
470#[derive(Default)]
471struct MultiStateMember {
472 draw_state: Option<DrawState>,
475 is_zombie: bool,
477}
478
479impl Debug for MultiStateMember {
480 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
481 f.debug_struct("MultiStateElement")
482 .field("draw_state", &self.draw_state)
483 .field("is_zombie", &self.is_zombie)
484 .finish_non_exhaustive()
485 }
486}
487
488#[derive(Debug, Copy, Clone)]
505pub enum MultiProgressAlignment {
506 Top,
507 Bottom,
508}
509
510impl Default for MultiProgressAlignment {
511 fn default() -> Self {
512 Self::Top
513 }
514}
515
516enum InsertLocation {
517 End,
518 Index(usize),
519 IndexFromBack(usize),
520 After(usize),
521 Before(usize),
522}
523
524#[cfg(test)]
525mod tests {
526 use crate::{MultiProgress, ProgressBar, ProgressDrawTarget};
527
528 #[test]
529 fn late_pb_drop() {
530 let pb = ProgressBar::new(10);
531 let mpb = MultiProgress::new();
532 #[allow(clippy::redundant_clone)]
535 mpb.add(pb.clone());
536 }
537
538 #[test]
539 fn progress_bar_sync_send() {
540 let _: Box<dyn Sync> = Box::new(ProgressBar::new(1));
541 let _: Box<dyn Send> = Box::new(ProgressBar::new(1));
542 let _: Box<dyn Sync> = Box::new(MultiProgress::new());
543 let _: Box<dyn Send> = Box::new(MultiProgress::new());
544 }
545
546 #[test]
547 fn multi_progress_hidden() {
548 let mpb = MultiProgress::with_draw_target(ProgressDrawTarget::hidden());
549 let pb = mpb.add(ProgressBar::new(123));
550 pb.finish();
551 }
552
553 #[test]
554 fn multi_progress_modifications() {
555 let mp = MultiProgress::new();
556 let p0 = mp.add(ProgressBar::new(1));
557 let p1 = mp.add(ProgressBar::new(1));
558 let p2 = mp.add(ProgressBar::new(1));
559 let p3 = mp.add(ProgressBar::new(1));
560 mp.remove(&p2);
561 mp.remove(&p1);
562 let p4 = mp.insert(1, ProgressBar::new(1));
563
564 let state = mp.state.read().unwrap();
565 assert_eq!(state.members.len(), 4);
567 assert_eq!(state.len(), 3);
568
569 match state.free_set.last() {
571 Some(1) => {
572 assert_eq!(state.ordering, vec![0, 2, 3]);
573 assert!(state.members[1].draw_state.is_none());
574 assert_eq!(p4.index().unwrap(), 2);
575 }
576 Some(2) => {
577 assert_eq!(state.ordering, vec![0, 1, 3]);
578 assert!(state.members[2].draw_state.is_none());
579 assert_eq!(p4.index().unwrap(), 1);
580 }
581 _ => unreachable!(),
582 }
583
584 assert_eq!(p0.index().unwrap(), 0);
585 assert_eq!(p1.index(), None);
586 assert_eq!(p2.index(), None);
587 assert_eq!(p3.index().unwrap(), 3);
588 }
589
590 #[test]
591 fn multi_progress_insert_from_back() {
592 let mp = MultiProgress::new();
593 let p0 = mp.add(ProgressBar::new(1));
594 let p1 = mp.add(ProgressBar::new(1));
595 let p2 = mp.add(ProgressBar::new(1));
596 let p3 = mp.insert_from_back(1, ProgressBar::new(1));
597 let p4 = mp.insert_from_back(10, ProgressBar::new(1));
598
599 let state = mp.state.read().unwrap();
600 assert_eq!(state.ordering, vec![4, 0, 1, 3, 2]);
601 assert_eq!(p0.index().unwrap(), 0);
602 assert_eq!(p1.index().unwrap(), 1);
603 assert_eq!(p2.index().unwrap(), 2);
604 assert_eq!(p3.index().unwrap(), 3);
605 assert_eq!(p4.index().unwrap(), 4);
606 }
607
608 #[test]
609 fn multi_progress_insert_after() {
610 let mp = MultiProgress::new();
611 let p0 = mp.add(ProgressBar::new(1));
612 let p1 = mp.add(ProgressBar::new(1));
613 let p2 = mp.add(ProgressBar::new(1));
614 let p3 = mp.insert_after(&p2, ProgressBar::new(1));
615 let p4 = mp.insert_after(&p0, ProgressBar::new(1));
616
617 let state = mp.state.read().unwrap();
618 assert_eq!(state.ordering, vec![0, 4, 1, 2, 3]);
619 assert_eq!(p0.index().unwrap(), 0);
620 assert_eq!(p1.index().unwrap(), 1);
621 assert_eq!(p2.index().unwrap(), 2);
622 assert_eq!(p3.index().unwrap(), 3);
623 assert_eq!(p4.index().unwrap(), 4);
624 }
625
626 #[test]
627 fn multi_progress_insert_before() {
628 let mp = MultiProgress::new();
629 let p0 = mp.add(ProgressBar::new(1));
630 let p1 = mp.add(ProgressBar::new(1));
631 let p2 = mp.add(ProgressBar::new(1));
632 let p3 = mp.insert_before(&p0, ProgressBar::new(1));
633 let p4 = mp.insert_before(&p2, ProgressBar::new(1));
634
635 let state = mp.state.read().unwrap();
636 assert_eq!(state.ordering, vec![3, 0, 1, 4, 2]);
637 assert_eq!(p0.index().unwrap(), 0);
638 assert_eq!(p1.index().unwrap(), 1);
639 assert_eq!(p2.index().unwrap(), 2);
640 assert_eq!(p3.index().unwrap(), 3);
641 assert_eq!(p4.index().unwrap(), 4);
642 }
643
644 #[test]
645 fn multi_progress_insert_before_and_after() {
646 let mp = MultiProgress::new();
647 let p0 = mp.add(ProgressBar::new(1));
648 let p1 = mp.add(ProgressBar::new(1));
649 let p2 = mp.add(ProgressBar::new(1));
650 let p3 = mp.insert_before(&p0, ProgressBar::new(1));
651 let p4 = mp.insert_after(&p3, ProgressBar::new(1));
652 let p5 = mp.insert_after(&p3, ProgressBar::new(1));
653 let p6 = mp.insert_before(&p1, ProgressBar::new(1));
654
655 let state = mp.state.read().unwrap();
656 assert_eq!(state.ordering, vec![3, 5, 4, 0, 6, 1, 2]);
657 assert_eq!(p0.index().unwrap(), 0);
658 assert_eq!(p1.index().unwrap(), 1);
659 assert_eq!(p2.index().unwrap(), 2);
660 assert_eq!(p3.index().unwrap(), 3);
661 assert_eq!(p4.index().unwrap(), 4);
662 assert_eq!(p5.index().unwrap(), 5);
663 assert_eq!(p6.index().unwrap(), 6);
664 }
665
666 #[test]
667 fn multi_progress_multiple_remove() {
668 let mp = MultiProgress::new();
669 let p0 = mp.add(ProgressBar::new(1));
670 let p1 = mp.add(ProgressBar::new(1));
671 mp.remove(&p0);
673 mp.remove(&p0);
674 mp.remove(&p0);
675
676 let state = mp.state.read().unwrap();
677 assert_eq!(state.members.len(), 2);
679 assert_eq!(state.free_set.len(), 1);
680 assert_eq!(state.len(), 1);
681 assert!(state.members[0].draw_state.is_none());
682 assert_eq!(state.free_set.last(), Some(&0));
683
684 assert_eq!(state.ordering, vec![1]);
685 assert_eq!(p0.index(), None);
686 assert_eq!(p1.index().unwrap(), 1);
687 }
688
689 #[test]
690 fn mp_no_crash_double_add() {
691 let mp = MultiProgress::new();
692 let pb = mp.add(ProgressBar::new(10));
693 mp.add(pb);
694 }
695}