indicatif/
multi.rs

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/// Manages multiple progress bars from different threads
17#[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    /// Creates a new multi progress object.
30    ///
31    /// Progress bars added to this object by default draw directly to stderr, and refresh
32    /// a maximum of 15 times a second. To change the refresh rate [set] the [draw target] to
33    /// one with a different refresh rate.
34    ///
35    /// [set]: MultiProgress::set_draw_target
36    /// [draw target]: ProgressDrawTarget
37    pub fn new() -> Self {
38        Self::default()
39    }
40
41    /// Creates a new multi progress object with the given draw target.
42    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    /// Sets a different draw target for the multiprogress bar.
49    ///
50    /// Use [`MultiProgress::with_draw_target`] to set the draw target during creation.
51    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    /// Set whether we should try to move the cursor when possible instead of clearing lines.
58    ///
59    /// This can reduce flickering, but do not enable it if you intend to change the number of
60    /// progress bars.
61    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    /// Set alignment flag
70    pub fn set_alignment(&self, alignment: MultiProgressAlignment) {
71        self.state.write().unwrap().alignment = alignment;
72    }
73
74    /// Adds a progress bar.
75    ///
76    /// The progress bar added will have the draw target changed to a
77    /// remote draw target that is intercepted by the multi progress
78    /// object overriding custom [`ProgressDrawTarget`] settings.
79    ///
80    /// The progress bar will be positioned below all other bars currently
81    /// in the [`MultiProgress`].
82    ///
83    /// Adding a progress bar that is already a member of the [`MultiProgress`]
84    /// will have no effect.
85    pub fn add(&self, pb: ProgressBar) -> ProgressBar {
86        self.internalize(InsertLocation::End, pb)
87    }
88
89    /// Inserts a progress bar.
90    ///
91    /// The progress bar inserted at position `index` will have the draw
92    /// target changed to a remote draw target that is intercepted by the
93    /// multi progress object overriding custom [`ProgressDrawTarget`] settings.
94    ///
95    /// If `index >= MultiProgressState::objects.len()`, the progress bar
96    /// is added to the end of the list.
97    ///
98    /// Inserting a progress bar that is already a member of the [`MultiProgress`]
99    /// will have no effect.
100    pub fn insert(&self, index: usize, pb: ProgressBar) -> ProgressBar {
101        self.internalize(InsertLocation::Index(index), pb)
102    }
103
104    /// Inserts a progress bar from the back.
105    ///
106    /// The progress bar inserted at position `MultiProgressState::objects.len() - index`
107    /// will have the draw target changed to a remote draw target that is
108    /// intercepted by the multi progress object overriding custom
109    /// [`ProgressDrawTarget`] settings.
110    ///
111    /// If `index >= MultiProgressState::objects.len()`, the progress bar
112    /// is added to the start of the list.
113    ///
114    /// Inserting a progress bar that is already a member of the [`MultiProgress`]
115    /// will have no effect.
116    pub fn insert_from_back(&self, index: usize, pb: ProgressBar) -> ProgressBar {
117        self.internalize(InsertLocation::IndexFromBack(index), pb)
118    }
119
120    /// Inserts a progress bar before an existing one.
121    ///
122    /// The progress bar added will have the draw target changed to a
123    /// remote draw target that is intercepted by the multi progress
124    /// object overriding custom [`ProgressDrawTarget`] settings.
125    ///
126    /// Inserting a progress bar that is already a member of the [`MultiProgress`]
127    /// will have no effect.
128    pub fn insert_before(&self, before: &ProgressBar, pb: ProgressBar) -> ProgressBar {
129        self.internalize(InsertLocation::Before(before.index().unwrap()), pb)
130    }
131
132    /// Inserts a progress bar after an existing one.
133    ///
134    /// The progress bar added will have the draw target changed to a
135    /// remote draw target that is intercepted by the multi progress
136    /// object overriding custom [`ProgressDrawTarget`] settings.
137    ///
138    /// Inserting a progress bar that is already a member of the [`MultiProgress`]
139    /// will have no effect.
140    pub fn insert_after(&self, after: &ProgressBar, pb: ProgressBar) -> ProgressBar {
141        self.internalize(InsertLocation::After(after.index().unwrap()), pb)
142    }
143
144    /// Removes a progress bar.
145    ///
146    /// The progress bar is removed only if it was previously inserted or added
147    /// by the methods [`MultiProgress::insert`] or [`MultiProgress::add`].
148    /// If the passed progress bar does not satisfy the condition above,
149    /// the `remove` method does nothing.
150    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                // Check that this progress bar is owned by the current MultiProgress.
155                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    /// Print a log line above all progress bars in the [`MultiProgress`]
175    ///
176    /// If the draw target is hidden (e.g. when standard output is not a terminal), `println()`
177    /// will not do anything.
178    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    /// Hide all progress bars temporarily, execute `f`, then redraw the [`MultiProgress`]
184    ///
185    /// Executes 'f' even if the draw target is hidden.
186    ///
187    /// Useful for external code that writes to the standard output.
188    ///
189    /// **Note:** The internal lock is held while `f` is executed. Other threads trying to print
190    /// anything on the progress bar will be blocked until `f` finishes.
191    /// Therefore, it is recommended to avoid long-running operations in `f`.
192    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    /// The collection of states corresponding to progress bars
209    members: Vec<MultiStateMember>,
210    /// Set of removed bars, should have corresponding members in the `members` vector with a
211    /// `draw_state` of `None`.
212    free_set: Vec<usize>,
213    /// Indices to the `draw_states` to maintain correct visual order
214    ordering: Vec<usize>,
215    /// Target for draw operation for MultiProgress
216    draw_target: ProgressDrawTarget,
217    /// Controls how the multi progress is aligned if some of its progress bars get removed, default is `Top`
218    alignment: MultiProgressAlignment,
219    /// Lines to be drawn above everything else in the MultiProgress. These specifically come from
220    /// calling `ProgressBar::println` on a pb that is connected to a `MultiProgress`.
221    orphan_lines: Vec<LineType>,
222    /// The count of currently visible zombie lines.
223    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 the zombie is the first visual bar then we can reap it right now instead of
245        // deferring it to the next draw.
246        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        // Track the total number of zombie lines on the screen
259        self.zombie_lines_count = self.zombie_lines_count.saturating_add(line_count);
260
261        // Make `DrawTarget` forget about the zombie lines so that they aren't cleared on next draw.
262        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        // Assumption: if extra_lines is not None, then it has at least one line
284        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        // Reap all consecutive 'zombie' progress bars from head of the list.
292        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            // Track the total number of zombie lines on the screen.
305            self.zombie_lines_count += line_count;
306
307            // Track the number of zombie lines that will be drawn by this call to draw.
308            adjust += line_count;
309
310            reap_indices.push(index);
311        }
312
313        // If this draw is due to a `println`, then we need to erase all the zombie lines.
314        // This is because `println` is supposed to appear above all other elements in the
315        // `MultiProgress`.
316        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        // Add lines from `ProgressBar::println` call.
337        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        // The zombie lines were drawn for the last time, so make `DrawTarget` forget about them
354        // so they aren't cleared on next draw.
355        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        // If msg is "", make sure a line is still printed
367        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        // alignment is handled by the `MultiProgress`'s underlying draw target, so there is no
378        // point in propagating it here.
379        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                // Make the clear operation also wipe out zombie lines
441                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 will be `None` for members that haven't been drawn before, or for entries that
473    /// correspond to something in the free set.
474    draw_state: Option<DrawState>,
475    /// Whether the corresponding progress bar (more precisely, `BarState`) has been dropped.
476    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/// Vertical alignment of a multi progress.
489///
490/// The alignment controls how the multi progress is aligned if some of its progress bars get removed.
491/// E.g. [`Top`](MultiProgressAlignment::Top) alignment (default), when _progress bar 2_ is removed:
492/// ```ignore
493/// [0/100] progress bar 1        [0/100] progress bar 1
494/// [0/100] progress bar 2   =>   [0/100] progress bar 3
495/// [0/100] progress bar 3
496/// ```
497///
498/// [`Bottom`](MultiProgressAlignment::Bottom) alignment
499/// ```ignore
500/// [0/100] progress bar 1
501/// [0/100] progress bar 2   =>   [0/100] progress bar 1
502/// [0/100] progress bar 3        [0/100] progress bar 3
503/// ```
504#[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        // This clone call is required to trigger a now fixed bug.
533        // See <https://github.com/console-rs/indicatif/pull/141> for context
534        #[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        // the removed place for p1 is reused
566        assert_eq!(state.members.len(), 4);
567        assert_eq!(state.len(), 3);
568
569        // free_set may contain 1 or 2
570        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        // double remove beyond the first one have no effect
672        mp.remove(&p0);
673        mp.remove(&p0);
674        mp.remove(&p0);
675
676        let state = mp.state.read().unwrap();
677        // the removed place for p1 is reused
678        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}