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, ProgressDrawTarget, VisualLines,
10};
11use crate::progress_bar::ProgressBar;
12#[cfg(target_arch = "wasm32")]
13use instant::Instant;
14
15#[derive(Debug, Clone)]
17pub struct MultiProgress {
18 pub(crate) state: Arc<RwLock<MultiState>>,
19}
20
21impl Default for MultiProgress {
22 fn default() -> Self {
23 Self::with_draw_target(ProgressDrawTarget::stderr())
24 }
25}
26
27impl MultiProgress {
28 pub fn new() -> Self {
37 Self::default()
38 }
39
40 pub fn with_draw_target(draw_target: ProgressDrawTarget) -> Self {
42 Self {
43 state: Arc::new(RwLock::new(MultiState::new(draw_target))),
44 }
45 }
46
47 pub fn set_draw_target(&self, target: ProgressDrawTarget) {
51 let mut state = self.state.write().unwrap();
52 state.draw_target.disconnect(Instant::now());
53 state.draw_target = target;
54 }
55
56 pub fn set_move_cursor(&self, move_cursor: bool) {
61 self.state.write().unwrap().move_cursor = move_cursor;
62 }
63
64 pub fn set_alignment(&self, alignment: MultiProgressAlignment) {
66 self.state.write().unwrap().alignment = alignment;
67 }
68
69 pub fn add(&self, pb: ProgressBar) -> ProgressBar {
81 self.internalize(InsertLocation::End, pb)
82 }
83
84 pub fn insert(&self, index: usize, pb: ProgressBar) -> ProgressBar {
96 self.internalize(InsertLocation::Index(index), pb)
97 }
98
99 pub fn insert_from_back(&self, index: usize, pb: ProgressBar) -> ProgressBar {
112 self.internalize(InsertLocation::IndexFromBack(index), pb)
113 }
114
115 pub fn insert_before(&self, before: &ProgressBar, pb: ProgressBar) -> ProgressBar {
124 self.internalize(InsertLocation::Before(before.index().unwrap()), pb)
125 }
126
127 pub fn insert_after(&self, after: &ProgressBar, pb: ProgressBar) -> ProgressBar {
136 self.internalize(InsertLocation::After(after.index().unwrap()), pb)
137 }
138
139 pub fn remove(&self, pb: &ProgressBar) {
146 let mut state = pb.state();
147 let idx = match &state.draw_target.remote() {
148 Some((state, idx)) => {
149 assert!(Arc::ptr_eq(&self.state, state));
151 *idx
152 }
153 _ => return,
154 };
155
156 state.draw_target = ProgressDrawTarget::hidden();
157 self.state.write().unwrap().remove_idx(idx);
158 }
159
160 fn internalize(&self, location: InsertLocation, pb: ProgressBar) -> ProgressBar {
161 let mut state = self.state.write().unwrap();
162 let idx = state.insert(location);
163 drop(state);
164
165 pb.set_draw_target(ProgressDrawTarget::new_remote(self.state.clone(), idx));
166 pb
167 }
168
169 pub fn println<I: AsRef<str>>(&self, msg: I) -> io::Result<()> {
174 let mut state = self.state.write().unwrap();
175 state.println(msg, Instant::now())
176 }
177
178 pub fn suspend<F: FnOnce() -> R, R>(&self, f: F) -> R {
188 let mut state = self.state.write().unwrap();
189 state.suspend(f, Instant::now())
190 }
191
192 pub fn clear(&self) -> io::Result<()> {
193 self.state.write().unwrap().clear(Instant::now())
194 }
195
196 pub fn is_hidden(&self) -> bool {
197 self.state.read().unwrap().is_hidden()
198 }
199}
200
201#[derive(Debug)]
202pub(crate) struct MultiState {
203 members: Vec<MultiStateMember>,
205 free_set: Vec<usize>,
208 ordering: Vec<usize>,
210 draw_target: ProgressDrawTarget,
212 move_cursor: bool,
214 alignment: MultiProgressAlignment,
216 orphan_lines: Vec<String>,
219 zombie_lines_count: VisualLines,
221}
222
223impl MultiState {
224 fn new(draw_target: ProgressDrawTarget) -> Self {
225 Self {
226 members: vec![],
227 free_set: vec![],
228 ordering: vec![],
229 draw_target,
230 move_cursor: false,
231 alignment: MultiProgressAlignment::default(),
232 orphan_lines: Vec::new(),
233 zombie_lines_count: VisualLines::default(),
234 }
235 }
236
237 pub(crate) fn mark_zombie(&mut self, index: usize) {
238 let width = self.width().map(usize::from);
239
240 let member = &mut self.members[index];
241
242 if index != self.ordering.first().copied().unwrap() {
245 member.is_zombie = true;
246 return;
247 }
248
249 let line_count = member
250 .draw_state
251 .as_ref()
252 .zip(width)
253 .map(|(d, width)| d.visual_line_count(.., width))
254 .unwrap_or_default();
255
256 self.zombie_lines_count = self.zombie_lines_count.saturating_add(line_count);
258
259 self.draw_target
261 .adjust_last_line_count(LineAdjust::Keep(line_count));
262
263 self.remove_idx(index);
264 }
265
266 pub(crate) fn draw(
267 &mut self,
268 mut force_draw: bool,
269 extra_lines: Option<Vec<String>>,
270 now: Instant,
271 ) -> io::Result<()> {
272 if panicking() {
273 return Ok(());
274 }
275
276 let width = match self.width() {
277 Some(width) => width as usize,
278 None => return Ok(()),
279 };
280
281 debug_assert_eq!(
283 extra_lines.is_some(),
284 extra_lines.as_ref().map(Vec::len).unwrap_or_default() > 0
285 );
286
287 let mut reap_indices = vec![];
288
289 let mut adjust = VisualLines::default();
291 for &index in &self.ordering {
292 let member = &self.members[index];
293 if !member.is_zombie {
294 break;
295 }
296
297 let line_count = member
298 .draw_state
299 .as_ref()
300 .map(|d| d.visual_line_count(.., width))
301 .unwrap_or_default();
302 self.zombie_lines_count += line_count;
304
305 adjust += line_count;
307
308 reap_indices.push(index);
309 }
310
311 if extra_lines.is_some() {
315 self.draw_target
316 .adjust_last_line_count(LineAdjust::Clear(self.zombie_lines_count));
317 self.zombie_lines_count = VisualLines::default();
318 }
319
320 let orphan_visual_line_count = visual_line_count(&self.orphan_lines, width);
321 force_draw |= orphan_visual_line_count > VisualLines::default();
322 let mut drawable = match self.draw_target.drawable(force_draw, now) {
323 Some(drawable) => drawable,
324 None => return Ok(()),
325 };
326
327 let mut draw_state = drawable.state();
328 draw_state.orphan_lines_count = self.orphan_lines.len();
329 draw_state.alignment = self.alignment;
330
331 if let Some(extra_lines) = &extra_lines {
332 draw_state.lines.extend_from_slice(extra_lines.as_slice());
333 draw_state.orphan_lines_count += extra_lines.len();
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<String> = match msg.is_empty() {
368 false => msg.lines().map(Into::into).collect(),
369 true => vec![String::new()],
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 {
380 move_cursor: self.move_cursor,
381 ..Default::default()
382 });
383
384 DrawStateWrapper::for_multi(state, &mut self.orphan_lines)
385 }
386
387 pub(crate) fn is_hidden(&self) -> bool {
388 self.draw_target.is_hidden()
389 }
390
391 pub(crate) fn suspend<F: FnOnce() -> R, R>(&mut self, f: F, now: Instant) -> R {
392 self.clear(now).unwrap();
393 let ret = f();
394 self.draw(true, None, Instant::now()).unwrap();
395 ret
396 }
397
398 pub(crate) fn width(&self) -> Option<u16> {
399 self.draw_target.width()
400 }
401
402 fn insert(&mut self, location: InsertLocation) -> usize {
403 let idx = if let Some(idx) = self.free_set.pop() {
404 self.members[idx] = MultiStateMember::default();
405 idx
406 } else {
407 self.members.push(MultiStateMember::default());
408 self.members.len() - 1
409 };
410
411 match location {
412 InsertLocation::End => self.ordering.push(idx),
413 InsertLocation::Index(pos) => {
414 let pos = Ord::min(pos, self.ordering.len());
415 self.ordering.insert(pos, idx);
416 }
417 InsertLocation::IndexFromBack(pos) => {
418 let pos = self.ordering.len().saturating_sub(pos);
419 self.ordering.insert(pos, idx);
420 }
421 InsertLocation::After(after_idx) => {
422 let pos = self.ordering.iter().position(|i| *i == after_idx).unwrap();
423 self.ordering.insert(pos + 1, idx);
424 }
425 InsertLocation::Before(before_idx) => {
426 let pos = self.ordering.iter().position(|i| *i == before_idx).unwrap();
427 self.ordering.insert(pos, idx);
428 }
429 }
430
431 assert_eq!(
432 self.len(),
433 self.ordering.len(),
434 "Draw state is inconsistent"
435 );
436
437 idx
438 }
439
440 fn clear(&mut self, now: Instant) -> io::Result<()> {
441 match self.draw_target.drawable(true, now) {
442 Some(mut drawable) => {
443 drawable.adjust_last_line_count(LineAdjust::Clear(self.zombie_lines_count));
445 self.zombie_lines_count = VisualLines::default();
446 drawable.clear()
447 }
448 None => Ok(()),
449 }
450 }
451
452 fn remove_idx(&mut self, idx: usize) {
453 if self.free_set.contains(&idx) {
454 return;
455 }
456
457 self.members[idx] = MultiStateMember::default();
458 self.free_set.push(idx);
459 self.ordering.retain(|&x| x != idx);
460
461 assert_eq!(
462 self.len(),
463 self.ordering.len(),
464 "Draw state is inconsistent"
465 );
466 }
467
468 fn len(&self) -> usize {
469 self.members.len() - self.free_set.len()
470 }
471}
472
473#[derive(Default)]
474struct MultiStateMember {
475 draw_state: Option<DrawState>,
478 is_zombie: bool,
480}
481
482impl Debug for MultiStateMember {
483 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
484 f.debug_struct("MultiStateElement")
485 .field("draw_state", &self.draw_state)
486 .field("is_zombie", &self.is_zombie)
487 .finish_non_exhaustive()
488 }
489}
490
491#[derive(Debug, Copy, Clone)]
508pub enum MultiProgressAlignment {
509 Top,
510 Bottom,
511}
512
513impl Default for MultiProgressAlignment {
514 fn default() -> Self {
515 Self::Top
516 }
517}
518
519enum InsertLocation {
520 End,
521 Index(usize),
522 IndexFromBack(usize),
523 After(usize),
524 Before(usize),
525}
526
527#[cfg(test)]
528mod tests {
529 use crate::{MultiProgress, ProgressBar, ProgressDrawTarget};
530
531 #[test]
532 fn late_pb_drop() {
533 let pb = ProgressBar::new(10);
534 let mpb = MultiProgress::new();
535 #[allow(clippy::redundant_clone)]
538 mpb.add(pb.clone());
539 }
540
541 #[test]
542 fn progress_bar_sync_send() {
543 let _: Box<dyn Sync> = Box::new(ProgressBar::new(1));
544 let _: Box<dyn Send> = Box::new(ProgressBar::new(1));
545 let _: Box<dyn Sync> = Box::new(MultiProgress::new());
546 let _: Box<dyn Send> = Box::new(MultiProgress::new());
547 }
548
549 #[test]
550 fn multi_progress_hidden() {
551 let mpb = MultiProgress::with_draw_target(ProgressDrawTarget::hidden());
552 let pb = mpb.add(ProgressBar::new(123));
553 pb.finish();
554 }
555
556 #[test]
557 fn multi_progress_modifications() {
558 let mp = MultiProgress::new();
559 let p0 = mp.add(ProgressBar::new(1));
560 let p1 = mp.add(ProgressBar::new(1));
561 let p2 = mp.add(ProgressBar::new(1));
562 let p3 = mp.add(ProgressBar::new(1));
563 mp.remove(&p2);
564 mp.remove(&p1);
565 let p4 = mp.insert(1, ProgressBar::new(1));
566
567 let state = mp.state.read().unwrap();
568 assert_eq!(state.members.len(), 4);
570 assert_eq!(state.len(), 3);
571
572 match state.free_set.last() {
574 Some(1) => {
575 assert_eq!(state.ordering, vec![0, 2, 3]);
576 assert!(state.members[1].draw_state.is_none());
577 assert_eq!(p4.index().unwrap(), 2);
578 }
579 Some(2) => {
580 assert_eq!(state.ordering, vec![0, 1, 3]);
581 assert!(state.members[2].draw_state.is_none());
582 assert_eq!(p4.index().unwrap(), 1);
583 }
584 _ => unreachable!(),
585 }
586
587 assert_eq!(p0.index().unwrap(), 0);
588 assert_eq!(p1.index(), None);
589 assert_eq!(p2.index(), None);
590 assert_eq!(p3.index().unwrap(), 3);
591 }
592
593 #[test]
594 fn multi_progress_insert_from_back() {
595 let mp = MultiProgress::new();
596 let p0 = mp.add(ProgressBar::new(1));
597 let p1 = mp.add(ProgressBar::new(1));
598 let p2 = mp.add(ProgressBar::new(1));
599 let p3 = mp.insert_from_back(1, ProgressBar::new(1));
600 let p4 = mp.insert_from_back(10, ProgressBar::new(1));
601
602 let state = mp.state.read().unwrap();
603 assert_eq!(state.ordering, vec![4, 0, 1, 3, 2]);
604 assert_eq!(p0.index().unwrap(), 0);
605 assert_eq!(p1.index().unwrap(), 1);
606 assert_eq!(p2.index().unwrap(), 2);
607 assert_eq!(p3.index().unwrap(), 3);
608 assert_eq!(p4.index().unwrap(), 4);
609 }
610
611 #[test]
612 fn multi_progress_insert_after() {
613 let mp = MultiProgress::new();
614 let p0 = mp.add(ProgressBar::new(1));
615 let p1 = mp.add(ProgressBar::new(1));
616 let p2 = mp.add(ProgressBar::new(1));
617 let p3 = mp.insert_after(&p2, ProgressBar::new(1));
618 let p4 = mp.insert_after(&p0, ProgressBar::new(1));
619
620 let state = mp.state.read().unwrap();
621 assert_eq!(state.ordering, vec![0, 4, 1, 2, 3]);
622 assert_eq!(p0.index().unwrap(), 0);
623 assert_eq!(p1.index().unwrap(), 1);
624 assert_eq!(p2.index().unwrap(), 2);
625 assert_eq!(p3.index().unwrap(), 3);
626 assert_eq!(p4.index().unwrap(), 4);
627 }
628
629 #[test]
630 fn multi_progress_insert_before() {
631 let mp = MultiProgress::new();
632 let p0 = mp.add(ProgressBar::new(1));
633 let p1 = mp.add(ProgressBar::new(1));
634 let p2 = mp.add(ProgressBar::new(1));
635 let p3 = mp.insert_before(&p0, ProgressBar::new(1));
636 let p4 = mp.insert_before(&p2, ProgressBar::new(1));
637
638 let state = mp.state.read().unwrap();
639 assert_eq!(state.ordering, vec![3, 0, 1, 4, 2]);
640 assert_eq!(p0.index().unwrap(), 0);
641 assert_eq!(p1.index().unwrap(), 1);
642 assert_eq!(p2.index().unwrap(), 2);
643 assert_eq!(p3.index().unwrap(), 3);
644 assert_eq!(p4.index().unwrap(), 4);
645 }
646
647 #[test]
648 fn multi_progress_insert_before_and_after() {
649 let mp = MultiProgress::new();
650 let p0 = mp.add(ProgressBar::new(1));
651 let p1 = mp.add(ProgressBar::new(1));
652 let p2 = mp.add(ProgressBar::new(1));
653 let p3 = mp.insert_before(&p0, ProgressBar::new(1));
654 let p4 = mp.insert_after(&p3, ProgressBar::new(1));
655 let p5 = mp.insert_after(&p3, ProgressBar::new(1));
656 let p6 = mp.insert_before(&p1, ProgressBar::new(1));
657
658 let state = mp.state.read().unwrap();
659 assert_eq!(state.ordering, vec![3, 5, 4, 0, 6, 1, 2]);
660 assert_eq!(p0.index().unwrap(), 0);
661 assert_eq!(p1.index().unwrap(), 1);
662 assert_eq!(p2.index().unwrap(), 2);
663 assert_eq!(p3.index().unwrap(), 3);
664 assert_eq!(p4.index().unwrap(), 4);
665 assert_eq!(p5.index().unwrap(), 5);
666 assert_eq!(p6.index().unwrap(), 6);
667 }
668
669 #[test]
670 fn multi_progress_multiple_remove() {
671 let mp = MultiProgress::new();
672 let p0 = mp.add(ProgressBar::new(1));
673 let p1 = mp.add(ProgressBar::new(1));
674 mp.remove(&p0);
676 mp.remove(&p0);
677 mp.remove(&p0);
678
679 let state = mp.state.read().unwrap();
680 assert_eq!(state.members.len(), 2);
682 assert_eq!(state.free_set.len(), 1);
683 assert_eq!(state.len(), 1);
684 assert!(state.members[0].draw_state.is_none());
685 assert_eq!(state.free_set.last(), Some(&0));
686
687 assert_eq!(state.ordering, vec![1]);
688 assert_eq!(p0.index(), None);
689 assert_eq!(p1.index().unwrap(), 1);
690 }
691
692 #[test]
693 fn mp_no_crash_double_add() {
694 let mp = MultiProgress::new();
695 let pb = mp.add(ProgressBar::new(10));
696 mp.add(pb);
697 }
698}