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 web_time::Instant;
14
15/// Manages multiple progress bars from different threads
16#[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 /// Creates a new multi progress object.
29 ///
30 /// Progress bars added to this object by default draw directly to stderr, and refresh
31 /// a maximum of 15 times a second. To change the refresh rate [set] the [draw target] to
32 /// one with a different refresh rate.
33 ///
34 /// [set]: MultiProgress::set_draw_target
35 /// [draw target]: ProgressDrawTarget
36 pub fn new() -> Self {
37 Self::default()
38 }
39
40 /// Creates a new multi progress object with the given draw target.
41 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 /// Sets a different draw target for the multiprogress bar.
48 ///
49 /// Use [`MultiProgress::with_draw_target`] to set the draw target during creation.
50 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 /// Set whether we should try to move the cursor when possible instead of clearing lines.
57 ///
58 /// This can reduce flickering, but do not enable it if you intend to change the number of
59 /// progress bars.
60 pub fn set_move_cursor(&self, move_cursor: bool) {
61 self.state
62 .write()
63 .unwrap()
64 .draw_target
65 .set_move_cursor(move_cursor);
66 }
67
68 /// Set alignment flag
69 pub fn set_alignment(&self, alignment: MultiProgressAlignment) {
70 self.state.write().unwrap().alignment = alignment;
71 }
72
73 /// Adds a progress bar.
74 ///
75 /// The progress bar added will have the draw target changed to a
76 /// remote draw target that is intercepted by the multi progress
77 /// object overriding custom [`ProgressDrawTarget`] settings.
78 ///
79 /// The progress bar will be positioned below all other bars currently
80 /// in the [`MultiProgress`].
81 ///
82 /// Adding a progress bar that is already a member of the [`MultiProgress`]
83 /// will have no effect.
84 pub fn add(&self, pb: ProgressBar) -> ProgressBar {
85 self.internalize(InsertLocation::End, pb)
86 }
87
88 /// Inserts a progress bar.
89 ///
90 /// The progress bar inserted at position `index` will have the draw
91 /// target changed to a remote draw target that is intercepted by the
92 /// multi progress object overriding custom [`ProgressDrawTarget`] settings.
93 ///
94 /// If `index >= MultiProgressState::objects.len()`, the progress bar
95 /// is added to the end of the list.
96 ///
97 /// Inserting a progress bar that is already a member of the [`MultiProgress`]
98 /// will have no effect.
99 pub fn insert(&self, index: usize, pb: ProgressBar) -> ProgressBar {
100 self.internalize(InsertLocation::Index(index), pb)
101 }
102
103 /// Inserts a progress bar from the back.
104 ///
105 /// The progress bar inserted at position `MultiProgressState::objects.len() - index`
106 /// will have the draw target changed to a remote draw target that is
107 /// intercepted by the multi progress object overriding custom
108 /// [`ProgressDrawTarget`] settings.
109 ///
110 /// If `index >= MultiProgressState::objects.len()`, the progress bar
111 /// is added to the start of the list.
112 ///
113 /// Inserting a progress bar that is already a member of the [`MultiProgress`]
114 /// will have no effect.
115 pub fn insert_from_back(&self, index: usize, pb: ProgressBar) -> ProgressBar {
116 self.internalize(InsertLocation::IndexFromBack(index), pb)
117 }
118
119 /// Inserts a progress bar before an existing one.
120 ///
121 /// The progress bar added will have the draw target changed to a
122 /// remote draw target that is intercepted by the multi progress
123 /// object overriding custom [`ProgressDrawTarget`] settings.
124 ///
125 /// Inserting a progress bar that is already a member of the [`MultiProgress`]
126 /// will have no effect.
127 pub fn insert_before(&self, before: &ProgressBar, pb: ProgressBar) -> ProgressBar {
128 self.internalize(InsertLocation::Before(before.index().unwrap()), pb)
129 }
130
131 /// Inserts a progress bar after an existing one.
132 ///
133 /// The progress bar added will have the draw target changed to a
134 /// remote draw target that is intercepted by the multi progress
135 /// object overriding custom [`ProgressDrawTarget`] settings.
136 ///
137 /// Inserting a progress bar that is already a member of the [`MultiProgress`]
138 /// will have no effect.
139 pub fn insert_after(&self, after: &ProgressBar, pb: ProgressBar) -> ProgressBar {
140 self.internalize(InsertLocation::After(after.index().unwrap()), pb)
141 }
142
143 /// Removes a progress bar.
144 ///
145 /// The progress bar is removed only if it was previously inserted or added
146 /// by the methods [`MultiProgress::insert`] or [`MultiProgress::add`].
147 /// If the passed progress bar does not satisfy the condition above,
148 /// the `remove` method does nothing.
149 pub fn remove(&self, pb: &ProgressBar) {
150 let mut state = pb.state();
151 let idx = match &state.draw_target.remote() {
152 Some((state, idx)) => {
153 // Check that this progress bar is owned by the current MultiProgress.
154 assert!(Arc::ptr_eq(&self.state, state));
155 *idx
156 }
157 _ => return,
158 };
159
160 state.draw_target = ProgressDrawTarget::hidden();
161 self.state.write().unwrap().remove_idx(idx);
162 }
163
164 fn internalize(&self, location: InsertLocation, pb: ProgressBar) -> ProgressBar {
165 let mut state = self.state.write().unwrap();
166 let idx = state.insert(location);
167 drop(state);
168
169 pb.set_draw_target(ProgressDrawTarget::new_remote(self.state.clone(), idx));
170 pb
171 }
172
173 /// Print a log line above all progress bars in the [`MultiProgress`]
174 ///
175 /// If the draw target is hidden (e.g. when standard output is not a terminal), `println()`
176 /// will not do anything.
177 pub fn println<I: AsRef<str>>(&self, msg: I) -> io::Result<()> {
178 let mut state = self.state.write().unwrap();
179 state.println(msg, Instant::now())
180 }
181
182 /// Hide all progress bars temporarily, execute `f`, then redraw the [`MultiProgress`]
183 ///
184 /// Executes 'f' even if the draw target is hidden.
185 ///
186 /// Useful for external code that writes to the standard output.
187 ///
188 /// **Note:** The internal lock is held while `f` is executed. Other threads trying to print
189 /// anything on the progress bar will be blocked until `f` finishes.
190 /// Therefore, it is recommended to avoid long-running operations in `f`.
191 pub fn suspend<F: FnOnce() -> R, R>(&self, f: F) -> R {
192 let mut state = self.state.write().unwrap();
193 state.suspend(f, Instant::now())
194 }
195
196 pub fn clear(&self) -> io::Result<()> {
197 self.state.write().unwrap().clear(Instant::now())
198 }
199
200 pub fn is_hidden(&self) -> bool {
201 self.state.read().unwrap().is_hidden()
202 }
203}
204
205#[derive(Debug)]
206pub(crate) struct MultiState {
207 /// The collection of states corresponding to progress bars
208 members: Vec<MultiStateMember>,
209 /// Set of removed bars, should have corresponding members in the `members` vector with a
210 /// `draw_state` of `None`.
211 free_set: Vec<usize>,
212 /// Indices to the `draw_states` to maintain correct visual order
213 ordering: Vec<usize>,
214 /// Target for draw operation for MultiProgress
215 draw_target: ProgressDrawTarget,
216 /// Controls how the multi progress is aligned if some of its progress bars get removed, default is `Top`
217 alignment: MultiProgressAlignment,
218 /// Lines to be drawn above everything else in the MultiProgress. These specifically come from
219 /// calling `ProgressBar::println` on a pb that is connected to a `MultiProgress`.
220 orphan_lines: Vec<String>,
221 /// The count of currently visible zombie lines.
222 zombie_lines_count: VisualLines,
223}
224
225impl MultiState {
226 fn new(draw_target: ProgressDrawTarget) -> Self {
227 Self {
228 members: vec![],
229 free_set: vec![],
230 ordering: vec![],
231 draw_target,
232 alignment: MultiProgressAlignment::default(),
233 orphan_lines: Vec::new(),
234 zombie_lines_count: VisualLines::default(),
235 }
236 }
237
238 pub(crate) fn mark_zombie(&mut self, index: usize) {
239 let width = self.width().map(usize::from);
240
241 let member = &mut self.members[index];
242
243 // If the zombie is the first visual bar then we can reap it right now instead of
244 // deferring it to the next draw.
245 if index != self.ordering.first().copied().unwrap() {
246 member.is_zombie = true;
247 return;
248 }
249
250 let line_count = member
251 .draw_state
252 .as_ref()
253 .zip(width)
254 .map(|(d, width)| d.visual_line_count(.., width))
255 .unwrap_or_default();
256
257 // Track the total number of zombie lines on the screen
258 self.zombie_lines_count = self.zombie_lines_count.saturating_add(line_count);
259
260 // Make `DrawTarget` forget about the zombie lines so that they aren't cleared on next draw.
261 self.draw_target
262 .adjust_last_line_count(LineAdjust::Keep(line_count));
263
264 self.remove_idx(index);
265 }
266
267 pub(crate) fn draw(
268 &mut self,
269 mut force_draw: bool,
270 extra_lines: Option<Vec<String>>,
271 now: Instant,
272 ) -> io::Result<()> {
273 if panicking() {
274 return Ok(());
275 }
276
277 let width = match self.width() {
278 Some(width) => width as usize,
279 None => return Ok(()),
280 };
281
282 // Assumption: if extra_lines is not None, then it has at least one line
283 debug_assert_eq!(
284 extra_lines.is_some(),
285 extra_lines.as_ref().map(Vec::len).unwrap_or_default() > 0
286 );
287
288 let mut reap_indices = vec![];
289
290 // Reap all consecutive 'zombie' progress bars from head of the list.
291 let mut adjust = VisualLines::default();
292 for &index in &self.ordering {
293 let member = &self.members[index];
294 if !member.is_zombie {
295 break;
296 }
297
298 let line_count = member
299 .draw_state
300 .as_ref()
301 .map(|d| d.visual_line_count(.., width))
302 .unwrap_or_default();
303 // Track the total number of zombie lines on the screen.
304 self.zombie_lines_count += line_count;
305
306 // Track the number of zombie lines that will be drawn by this call to draw.
307 adjust += line_count;
308
309 reap_indices.push(index);
310 }
311
312 // If this draw is due to a `println`, then we need to erase all the zombie lines.
313 // This is because `println` is supposed to appear above all other elements in the
314 // `MultiProgress`.
315 if extra_lines.is_some() {
316 self.draw_target
317 .adjust_last_line_count(LineAdjust::Clear(self.zombie_lines_count));
318 self.zombie_lines_count = VisualLines::default();
319 }
320
321 let orphan_visual_line_count = visual_line_count(&self.orphan_lines, width);
322 force_draw |= orphan_visual_line_count > VisualLines::default();
323 let mut drawable = match self.draw_target.drawable(force_draw, now) {
324 Some(drawable) => drawable,
325 None => return Ok(()),
326 };
327
328 let mut draw_state = drawable.state();
329 draw_state.orphan_lines_count = self.orphan_lines.len();
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 draw_state.orphan_lines_count += extra_lines.len();
335 }
336
337 // Add lines from `ProgressBar::println` call.
338 draw_state.lines.append(&mut self.orphan_lines);
339
340 for index in &self.ordering {
341 let member = &self.members[*index];
342 if let Some(state) = &member.draw_state {
343 draw_state.lines.extend_from_slice(&state.lines[..]);
344 }
345 }
346
347 drop(draw_state);
348 let drawable = drawable.draw();
349
350 for index in reap_indices {
351 self.remove_idx(index);
352 }
353
354 // The zombie lines were drawn for the last time, so make `DrawTarget` forget about them
355 // so they aren't cleared on next draw.
356 if extra_lines.is_none() {
357 self.draw_target
358 .adjust_last_line_count(LineAdjust::Keep(adjust));
359 }
360
361 drawable
362 }
363
364 pub(crate) fn println<I: AsRef<str>>(&mut self, msg: I, now: Instant) -> io::Result<()> {
365 let msg = msg.as_ref();
366
367 // If msg is "", make sure a line is still printed
368 let lines: Vec<String> = match msg.is_empty() {
369 false => msg.lines().map(Into::into).collect(),
370 true => vec![String::new()],
371 };
372
373 self.draw(true, Some(lines), now)
374 }
375
376 pub(crate) fn draw_state(&mut self, idx: usize) -> DrawStateWrapper<'_> {
377 let member = self.members.get_mut(idx).unwrap();
378 // alignment is handled by the `MultiProgress`'s underlying draw target, so there is no
379 // point in propagating it here.
380 let state = member.draw_state.get_or_insert(DrawState::default());
381
382 DrawStateWrapper::for_multi(state, &mut self.orphan_lines)
383 }
384
385 pub(crate) fn is_hidden(&self) -> bool {
386 self.draw_target.is_hidden()
387 }
388
389 pub(crate) fn suspend<F: FnOnce() -> R, R>(&mut self, f: F, now: Instant) -> R {
390 self.clear(now).unwrap();
391 let ret = f();
392 self.draw(true, None, Instant::now()).unwrap();
393 ret
394 }
395
396 pub(crate) fn width(&self) -> Option<u16> {
397 self.draw_target.width()
398 }
399
400 fn insert(&mut self, location: InsertLocation) -> usize {
401 let idx = if let Some(idx) = self.free_set.pop() {
402 self.members[idx] = MultiStateMember::default();
403 idx
404 } else {
405 self.members.push(MultiStateMember::default());
406 self.members.len() - 1
407 };
408
409 match location {
410 InsertLocation::End => self.ordering.push(idx),
411 InsertLocation::Index(pos) => {
412 let pos = Ord::min(pos, self.ordering.len());
413 self.ordering.insert(pos, idx);
414 }
415 InsertLocation::IndexFromBack(pos) => {
416 let pos = self.ordering.len().saturating_sub(pos);
417 self.ordering.insert(pos, idx);
418 }
419 InsertLocation::After(after_idx) => {
420 let pos = self.ordering.iter().position(|i| *i == after_idx).unwrap();
421 self.ordering.insert(pos + 1, idx);
422 }
423 InsertLocation::Before(before_idx) => {
424 let pos = self.ordering.iter().position(|i| *i == before_idx).unwrap();
425 self.ordering.insert(pos, idx);
426 }
427 }
428
429 assert_eq!(
430 self.len(),
431 self.ordering.len(),
432 "Draw state is inconsistent"
433 );
434
435 idx
436 }
437
438 fn clear(&mut self, now: Instant) -> io::Result<()> {
439 match self.draw_target.drawable(true, now) {
440 Some(mut drawable) => {
441 // Make the clear operation also wipe out zombie lines
442 drawable.adjust_last_line_count(LineAdjust::Clear(self.zombie_lines_count));
443 self.zombie_lines_count = VisualLines::default();
444 drawable.clear()
445 }
446 None => Ok(()),
447 }
448 }
449
450 fn remove_idx(&mut self, idx: usize) {
451 if self.free_set.contains(&idx) {
452 return;
453 }
454
455 self.members[idx] = MultiStateMember::default();
456 self.free_set.push(idx);
457 self.ordering.retain(|&x| x != idx);
458
459 assert_eq!(
460 self.len(),
461 self.ordering.len(),
462 "Draw state is inconsistent"
463 );
464 }
465
466 fn len(&self) -> usize {
467 self.members.len() - self.free_set.len()
468 }
469}
470
471#[derive(Default)]
472struct MultiStateMember {
473 /// Draw state will be `None` for members that haven't been drawn before, or for entries that
474 /// correspond to something in the free set.
475 draw_state: Option<DrawState>,
476 /// Whether the corresponding progress bar (more precisely, `BarState`) has been dropped.
477 is_zombie: bool,
478}
479
480impl Debug for MultiStateMember {
481 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
482 f&mut DebugStruct<'_, '_>.debug_struct("MultiStateElement")
483 .field("draw_state", &self.draw_state)
484 .field(name:"is_zombie", &self.is_zombie)
485 .finish_non_exhaustive()
486 }
487}
488
489/// Vertical alignment of a multi progress.
490///
491/// The alignment controls how the multi progress is aligned if some of its progress bars get removed.
492/// E.g. [`Top`](MultiProgressAlignment::Top) alignment (default), when _progress bar 2_ is removed:
493/// ```ignore
494/// [0/100] progress bar 1 [0/100] progress bar 1
495/// [0/100] progress bar 2 => [0/100] progress bar 3
496/// [0/100] progress bar 3
497/// ```
498///
499/// [`Bottom`](MultiProgressAlignment::Bottom) alignment
500/// ```ignore
501/// [0/100] progress bar 1
502/// [0/100] progress bar 2 => [0/100] progress bar 1
503/// [0/100] progress bar 3 [0/100] progress bar 3
504/// ```
505#[derive(Debug, Copy, Clone)]
506pub enum MultiProgressAlignment {
507 Top,
508 Bottom,
509}
510
511impl Default for MultiProgressAlignment {
512 fn default() -> Self {
513 Self::Top
514 }
515}
516
517enum InsertLocation {
518 End,
519 Index(usize),
520 IndexFromBack(usize),
521 After(usize),
522 Before(usize),
523}
524
525#[cfg(test)]
526mod tests {
527 use crate::{MultiProgress, ProgressBar, ProgressDrawTarget};
528
529 #[test]
530 fn late_pb_drop() {
531 let pb = ProgressBar::new(10);
532 let mpb = MultiProgress::new();
533 // This clone call is required to trigger a now fixed bug.
534 // See <https://github.com/console-rs/indicatif/pull/141> for context
535 #[allow(clippy::redundant_clone)]
536 mpb.add(pb.clone());
537 }
538
539 #[test]
540 fn progress_bar_sync_send() {
541 let _: Box<dyn Sync> = Box::new(ProgressBar::new(1));
542 let _: Box<dyn Send> = Box::new(ProgressBar::new(1));
543 let _: Box<dyn Sync> = Box::new(MultiProgress::new());
544 let _: Box<dyn Send> = Box::new(MultiProgress::new());
545 }
546
547 #[test]
548 fn multi_progress_hidden() {
549 let mpb = MultiProgress::with_draw_target(ProgressDrawTarget::hidden());
550 let pb = mpb.add(ProgressBar::new(123));
551 pb.finish();
552 }
553
554 #[test]
555 fn multi_progress_modifications() {
556 let mp = MultiProgress::new();
557 let p0 = mp.add(ProgressBar::new(1));
558 let p1 = mp.add(ProgressBar::new(1));
559 let p2 = mp.add(ProgressBar::new(1));
560 let p3 = mp.add(ProgressBar::new(1));
561 mp.remove(&p2);
562 mp.remove(&p1);
563 let p4 = mp.insert(1, ProgressBar::new(1));
564
565 let state = mp.state.read().unwrap();
566 // the removed place for p1 is reused
567 assert_eq!(state.members.len(), 4);
568 assert_eq!(state.len(), 3);
569
570 // free_set may contain 1 or 2
571 match state.free_set.last() {
572 Some(1) => {
573 assert_eq!(state.ordering, vec![0, 2, 3]);
574 assert!(state.members[1].draw_state.is_none());
575 assert_eq!(p4.index().unwrap(), 2);
576 }
577 Some(2) => {
578 assert_eq!(state.ordering, vec![0, 1, 3]);
579 assert!(state.members[2].draw_state.is_none());
580 assert_eq!(p4.index().unwrap(), 1);
581 }
582 _ => unreachable!(),
583 }
584
585 assert_eq!(p0.index().unwrap(), 0);
586 assert_eq!(p1.index(), None);
587 assert_eq!(p2.index(), None);
588 assert_eq!(p3.index().unwrap(), 3);
589 }
590
591 #[test]
592 fn multi_progress_insert_from_back() {
593 let mp = MultiProgress::new();
594 let p0 = mp.add(ProgressBar::new(1));
595 let p1 = mp.add(ProgressBar::new(1));
596 let p2 = mp.add(ProgressBar::new(1));
597 let p3 = mp.insert_from_back(1, ProgressBar::new(1));
598 let p4 = mp.insert_from_back(10, ProgressBar::new(1));
599
600 let state = mp.state.read().unwrap();
601 assert_eq!(state.ordering, vec![4, 0, 1, 3, 2]);
602 assert_eq!(p0.index().unwrap(), 0);
603 assert_eq!(p1.index().unwrap(), 1);
604 assert_eq!(p2.index().unwrap(), 2);
605 assert_eq!(p3.index().unwrap(), 3);
606 assert_eq!(p4.index().unwrap(), 4);
607 }
608
609 #[test]
610 fn multi_progress_insert_after() {
611 let mp = MultiProgress::new();
612 let p0 = mp.add(ProgressBar::new(1));
613 let p1 = mp.add(ProgressBar::new(1));
614 let p2 = mp.add(ProgressBar::new(1));
615 let p3 = mp.insert_after(&p2, ProgressBar::new(1));
616 let p4 = mp.insert_after(&p0, ProgressBar::new(1));
617
618 let state = mp.state.read().unwrap();
619 assert_eq!(state.ordering, vec![0, 4, 1, 2, 3]);
620 assert_eq!(p0.index().unwrap(), 0);
621 assert_eq!(p1.index().unwrap(), 1);
622 assert_eq!(p2.index().unwrap(), 2);
623 assert_eq!(p3.index().unwrap(), 3);
624 assert_eq!(p4.index().unwrap(), 4);
625 }
626
627 #[test]
628 fn multi_progress_insert_before() {
629 let mp = MultiProgress::new();
630 let p0 = mp.add(ProgressBar::new(1));
631 let p1 = mp.add(ProgressBar::new(1));
632 let p2 = mp.add(ProgressBar::new(1));
633 let p3 = mp.insert_before(&p0, ProgressBar::new(1));
634 let p4 = mp.insert_before(&p2, ProgressBar::new(1));
635
636 let state = mp.state.read().unwrap();
637 assert_eq!(state.ordering, vec![3, 0, 1, 4, 2]);
638 assert_eq!(p0.index().unwrap(), 0);
639 assert_eq!(p1.index().unwrap(), 1);
640 assert_eq!(p2.index().unwrap(), 2);
641 assert_eq!(p3.index().unwrap(), 3);
642 assert_eq!(p4.index().unwrap(), 4);
643 }
644
645 #[test]
646 fn multi_progress_insert_before_and_after() {
647 let mp = MultiProgress::new();
648 let p0 = mp.add(ProgressBar::new(1));
649 let p1 = mp.add(ProgressBar::new(1));
650 let p2 = mp.add(ProgressBar::new(1));
651 let p3 = mp.insert_before(&p0, ProgressBar::new(1));
652 let p4 = mp.insert_after(&p3, ProgressBar::new(1));
653 let p5 = mp.insert_after(&p3, ProgressBar::new(1));
654 let p6 = mp.insert_before(&p1, ProgressBar::new(1));
655
656 let state = mp.state.read().unwrap();
657 assert_eq!(state.ordering, vec![3, 5, 4, 0, 6, 1, 2]);
658 assert_eq!(p0.index().unwrap(), 0);
659 assert_eq!(p1.index().unwrap(), 1);
660 assert_eq!(p2.index().unwrap(), 2);
661 assert_eq!(p3.index().unwrap(), 3);
662 assert_eq!(p4.index().unwrap(), 4);
663 assert_eq!(p5.index().unwrap(), 5);
664 assert_eq!(p6.index().unwrap(), 6);
665 }
666
667 #[test]
668 fn multi_progress_multiple_remove() {
669 let mp = MultiProgress::new();
670 let p0 = mp.add(ProgressBar::new(1));
671 let p1 = mp.add(ProgressBar::new(1));
672 // double remove beyond the first one have no effect
673 mp.remove(&p0);
674 mp.remove(&p0);
675 mp.remove(&p0);
676
677 let state = mp.state.read().unwrap();
678 // the removed place for p1 is reused
679 assert_eq!(state.members.len(), 2);
680 assert_eq!(state.free_set.len(), 1);
681 assert_eq!(state.len(), 1);
682 assert!(state.members[0].draw_state.is_none());
683 assert_eq!(state.free_set.last(), Some(&0));
684
685 assert_eq!(state.ordering, vec![1]);
686 assert_eq!(p0.index(), None);
687 assert_eq!(p1.index().unwrap(), 1);
688 }
689
690 #[test]
691 fn mp_no_crash_double_add() {
692 let mp = MultiProgress::new();
693 let pb = mp.add(ProgressBar::new(10));
694 mp.add(pb);
695 }
696}
697

Provided by KDAB

Privacy Policy
Learn Rust with the experts
Find out more