| 1 | use std::fmt::{Debug, Formatter}; |
| 2 | use std::io; |
| 3 | use std::sync::{Arc, RwLock}; |
| 4 | use std::thread::panicking; |
| 5 | #[cfg (not(target_arch = "wasm32" ))] |
| 6 | use std::time::Instant; |
| 7 | |
| 8 | use crate::draw_target::{ |
| 9 | visual_line_count, DrawState, DrawStateWrapper, LineAdjust, ProgressDrawTarget, VisualLines, |
| 10 | }; |
| 11 | use crate::progress_bar::ProgressBar; |
| 12 | #[cfg (target_arch = "wasm32" )] |
| 13 | use web_time::Instant; |
| 14 | |
| 15 | /// Manages multiple progress bars from different threads |
| 16 | #[derive (Debug, Clone)] |
| 17 | pub struct MultiProgress { |
| 18 | pub(crate) state: Arc<RwLock<MultiState>>, |
| 19 | } |
| 20 | |
| 21 | impl Default for MultiProgress { |
| 22 | fn default() -> Self { |
| 23 | Self::with_draw_target(ProgressDrawTarget::stderr()) |
| 24 | } |
| 25 | } |
| 26 | |
| 27 | impl 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)] |
| 206 | pub(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 | |
| 225 | impl 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)] |
| 472 | struct 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 | |
| 480 | impl 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)] |
| 506 | pub enum MultiProgressAlignment { |
| 507 | Top, |
| 508 | Bottom, |
| 509 | } |
| 510 | |
| 511 | impl Default for MultiProgressAlignment { |
| 512 | fn default() -> Self { |
| 513 | Self::Top |
| 514 | } |
| 515 | } |
| 516 | |
| 517 | enum InsertLocation { |
| 518 | End, |
| 519 | Index(usize), |
| 520 | IndexFromBack(usize), |
| 521 | After(usize), |
| 522 | Before(usize), |
| 523 | } |
| 524 | |
| 525 | #[cfg (test)] |
| 526 | mod 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 | |