1use std::borrow::Cow;
2use std::io;
3use std::sync::Arc;
4use std::time::Duration;
5#[cfg(not(target_arch = "wasm32"))]
6use std::time::Instant;
7
8#[cfg(target_arch = "wasm32")]
9use instant::Instant;
10use portable_atomic::{AtomicU64, AtomicU8, Ordering};
11
12use crate::draw_target::ProgressDrawTarget;
13use crate::style::ProgressStyle;
14
15pub(crate) struct BarState {
16 pub(crate) draw_target: ProgressDrawTarget,
17 pub(crate) on_finish: ProgressFinish,
18 pub(crate) style: ProgressStyle,
19 pub(crate) state: ProgressState,
20 pub(crate) tab_width: usize,
21}
22
23impl BarState {
24 pub(crate) fn new(
25 len: Option<u64>,
26 draw_target: ProgressDrawTarget,
27 pos: Arc<AtomicPosition>,
28 ) -> Self {
29 Self {
30 draw_target,
31 on_finish: ProgressFinish::default(),
32 style: ProgressStyle::default_bar(),
33 state: ProgressState::new(len, pos),
34 tab_width: DEFAULT_TAB_WIDTH,
35 }
36 }
37
38 pub(crate) fn finish_using_style(&mut self, now: Instant, finish: ProgressFinish) {
41 self.state.status = Status::DoneVisible;
42 match finish {
43 ProgressFinish::AndLeave => {
44 if let Some(len) = self.state.len {
45 self.state.pos.set(len);
46 }
47 }
48 ProgressFinish::WithMessage(msg) => {
49 if let Some(len) = self.state.len {
50 self.state.pos.set(len);
51 }
52 self.state.message = TabExpandedString::new(msg, self.tab_width);
53 }
54 ProgressFinish::AndClear => {
55 if let Some(len) = self.state.len {
56 self.state.pos.set(len);
57 }
58 self.state.status = Status::DoneHidden;
59 }
60 ProgressFinish::Abandon => {}
61 ProgressFinish::AbandonWithMessage(msg) => {
62 self.state.message = TabExpandedString::new(msg, self.tab_width);
63 }
64 }
65
66 let _ = self.draw(true, now);
69 }
70
71 pub(crate) fn reset(&mut self, now: Instant, mode: Reset) {
72 self.state.est.reset(now);
75
76 if let Reset::Elapsed | Reset::All = mode {
77 self.state.started = now;
78 }
79
80 if let Reset::All = mode {
81 self.state.pos.reset(now);
82 self.state.status = Status::InProgress;
83
84 for tracker in self.style.format_map.values_mut() {
85 tracker.reset(&self.state, now);
86 }
87
88 let _ = self.draw(false, now);
89 }
90 }
91
92 pub(crate) fn update(&mut self, now: Instant, f: impl FnOnce(&mut ProgressState), tick: bool) {
93 f(&mut self.state);
94 if tick {
95 self.tick(now);
96 }
97 }
98
99 pub(crate) fn set_length(&mut self, now: Instant, len: u64) {
100 self.state.len = Some(len);
101 self.update_estimate_and_draw(now);
102 }
103
104 pub(crate) fn inc_length(&mut self, now: Instant, delta: u64) {
105 if let Some(len) = self.state.len {
106 self.state.len = Some(len.saturating_add(delta));
107 }
108 self.update_estimate_and_draw(now);
109 }
110
111 pub(crate) fn set_tab_width(&mut self, tab_width: usize) {
112 self.tab_width = tab_width;
113 self.state.message.set_tab_width(tab_width);
114 self.state.prefix.set_tab_width(tab_width);
115 self.style.set_tab_width(tab_width);
116 }
117
118 pub(crate) fn set_style(&mut self, style: ProgressStyle) {
119 self.style = style;
120 self.style.set_tab_width(self.tab_width);
121 }
122
123 pub(crate) fn tick(&mut self, now: Instant) {
124 self.state.tick = self.state.tick.saturating_add(1);
125 self.update_estimate_and_draw(now);
126 }
127
128 pub(crate) fn update_estimate_and_draw(&mut self, now: Instant) {
129 let pos = self.state.pos.pos.load(Ordering::Relaxed);
130 self.state.est.record(pos, now);
131
132 for tracker in self.style.format_map.values_mut() {
133 tracker.tick(&self.state, now);
134 }
135
136 let _ = self.draw(false, now);
137 }
138
139 pub(crate) fn println(&mut self, now: Instant, msg: &str) {
140 let width = self.draw_target.width();
141 let mut drawable = match self.draw_target.drawable(true, now) {
142 Some(drawable) => drawable,
143 None => return,
144 };
145
146 let mut draw_state = drawable.state();
147 let lines: Vec<String> = msg.lines().map(Into::into).collect();
148 if lines.is_empty() {
150 draw_state.lines.push(String::new());
151 } else {
152 draw_state.lines.extend(lines);
153 }
154
155 draw_state.orphan_lines_count = draw_state.lines.len();
156 if let Some(width) = width {
157 if !matches!(self.state.status, Status::DoneHidden) {
158 self.style
159 .format_state(&self.state, &mut draw_state.lines, width);
160 }
161 }
162
163 drop(draw_state);
164 let _ = drawable.draw();
165 }
166
167 pub(crate) fn suspend<F: FnOnce() -> R, R>(&mut self, now: Instant, f: F) -> R {
168 if let Some((state, _)) = self.draw_target.remote() {
169 return state.write().unwrap().suspend(f, now);
170 }
171
172 if let Some(drawable) = self.draw_target.drawable(true, now) {
173 let _ = drawable.clear();
174 }
175
176 let ret = f();
177 let _ = self.draw(true, Instant::now());
178 ret
179 }
180
181 pub(crate) fn draw(&mut self, mut force_draw: bool, now: Instant) -> io::Result<()> {
182 let width = self.draw_target.width();
183
184 force_draw |= self.state.is_finished();
187 let mut drawable = match self.draw_target.drawable(force_draw, now) {
188 Some(drawable) => drawable,
189 None => return Ok(()),
190 };
191
192 let mut draw_state = drawable.state();
193
194 if let Some(width) = width {
195 if !matches!(self.state.status, Status::DoneHidden) {
196 self.style
197 .format_state(&self.state, &mut draw_state.lines, width);
198 }
199 }
200
201 drop(draw_state);
202 drawable.draw()
203 }
204}
205
206impl Drop for BarState {
207 fn drop(&mut self) {
208 if self.state.is_finished() {
211 self.draw_target.mark_zombie();
212 return;
213 }
214
215 self.finish_using_style(Instant::now(), self.on_finish.clone());
216
217 self.draw_target.mark_zombie();
219 }
220}
221
222pub(crate) enum Reset {
223 Eta,
224 Elapsed,
225 All,
226}
227
228#[non_exhaustive]
230pub struct ProgressState {
231 pos: Arc<AtomicPosition>,
232 len: Option<u64>,
233 pub(crate) tick: u64,
234 pub(crate) started: Instant,
235 status: Status,
236 est: Estimator,
237 pub(crate) message: TabExpandedString,
238 pub(crate) prefix: TabExpandedString,
239}
240
241impl ProgressState {
242 pub(crate) fn new(len: Option<u64>, pos: Arc<AtomicPosition>) -> Self {
243 let now = Instant::now();
244 Self {
245 pos,
246 len,
247 tick: 0,
248 status: Status::InProgress,
249 started: now,
250 est: Estimator::new(now),
251 message: TabExpandedString::NoTabs("".into()),
252 prefix: TabExpandedString::NoTabs("".into()),
253 }
254 }
255
256 pub fn is_finished(&self) -> bool {
258 match self.status {
259 Status::InProgress => false,
260 Status::DoneVisible => true,
261 Status::DoneHidden => true,
262 }
263 }
264
265 pub fn fraction(&self) -> f32 {
267 let pos = self.pos.pos.load(Ordering::Relaxed);
268 let pct = match (pos, self.len) {
269 (_, None) => 0.0,
270 (_, Some(0)) => 1.0,
271 (0, _) => 0.0,
272 (pos, Some(len)) => pos as f32 / len as f32,
273 };
274 pct.clamp(0.0, 1.0)
275 }
276
277 pub fn eta(&self) -> Duration {
279 if self.is_finished() {
280 return Duration::new(0, 0);
281 }
282
283 let len = match self.len {
284 Some(len) => len,
285 None => return Duration::new(0, 0),
286 };
287
288 let pos = self.pos.pos.load(Ordering::Relaxed);
289
290 let sps = self.est.steps_per_second(Instant::now());
291
292 if sps == 0.0 {
295 return Duration::new(0, 0);
296 }
297
298 secs_to_duration(len.saturating_sub(pos) as f64 / sps)
299 }
300
301 pub fn duration(&self) -> Duration {
303 if self.len.is_none() || self.is_finished() {
304 return Duration::new(0, 0);
305 }
306 self.started.elapsed().saturating_add(self.eta())
307 }
308
309 pub fn per_sec(&self) -> f64 {
311 if let Status::InProgress = self.status {
312 self.est.steps_per_second(Instant::now())
313 } else {
314 self.pos() as f64 / self.started.elapsed().as_secs_f64()
315 }
316 }
317
318 pub fn elapsed(&self) -> Duration {
319 self.started.elapsed()
320 }
321
322 pub fn pos(&self) -> u64 {
323 self.pos.pos.load(Ordering::Relaxed)
324 }
325
326 pub fn set_pos(&mut self, pos: u64) {
327 self.pos.set(pos);
328 }
329
330 #[allow(clippy::len_without_is_empty)]
331 pub fn len(&self) -> Option<u64> {
332 self.len
333 }
334
335 pub fn set_len(&mut self, len: u64) {
336 self.len = Some(len);
337 }
338}
339
340#[derive(Debug, PartialEq, Eq, Clone)]
341pub(crate) enum TabExpandedString {
342 NoTabs(Cow<'static, str>),
343 WithTabs {
344 original: Cow<'static, str>,
345 expanded: String,
346 tab_width: usize,
347 },
348}
349
350impl TabExpandedString {
351 pub(crate) fn new(s: Cow<'static, str>, tab_width: usize) -> Self {
352 let expanded = s.replace('\t', &" ".repeat(tab_width));
353 if s == expanded {
354 Self::NoTabs(s)
355 } else {
356 Self::WithTabs {
357 original: s,
358 expanded,
359 tab_width,
360 }
361 }
362 }
363
364 pub(crate) fn expanded(&self) -> &str {
365 match &self {
366 Self::NoTabs(s) => {
367 debug_assert!(!s.contains('\t'));
368 s
369 }
370 Self::WithTabs { expanded, .. } => expanded,
371 }
372 }
373
374 pub(crate) fn set_tab_width(&mut self, new_tab_width: usize) {
375 if let Self::WithTabs {
376 original,
377 expanded,
378 tab_width,
379 } = self
380 {
381 if *tab_width != new_tab_width {
382 *tab_width = new_tab_width;
383 *expanded = original.replace('\t', &" ".repeat(new_tab_width));
384 }
385 }
386 }
387}
388
389#[derive(Debug)]
406pub(crate) struct Estimator {
407 smoothed_steps_per_sec: f64,
408 double_smoothed_steps_per_sec: f64,
409 prev_steps: u64,
410 prev_time: Instant,
411 start_time: Instant,
412}
413
414impl Estimator {
415 fn new(now: Instant) -> Self {
416 Self {
417 smoothed_steps_per_sec: 0.0,
418 double_smoothed_steps_per_sec: 0.0,
419 prev_steps: 0,
420 prev_time: now,
421 start_time: now,
422 }
423 }
424
425 fn record(&mut self, new_steps: u64, now: Instant) {
426 if new_steps <= self.prev_steps || now <= self.prev_time {
428 if new_steps < self.prev_steps {
431 self.prev_steps = new_steps;
432 self.reset(now);
433 }
434 return;
435 }
436
437 let delta_steps = new_steps - self.prev_steps;
438 let delta_t = duration_to_secs(now - self.prev_time);
439
440 let new_steps_per_second = delta_steps as f64 / delta_t;
442
443 let weight = estimator_weight(delta_t);
445 self.smoothed_steps_per_sec =
446 self.smoothed_steps_per_sec * weight + new_steps_per_second * (1.0 - weight);
447
448 let delta_t_start = duration_to_secs(now - self.start_time);
455 let total_weight = 1.0 - estimator_weight(delta_t_start);
456 let normalized_smoothed_steps_per_sec = self.smoothed_steps_per_sec / total_weight;
457
458 self.double_smoothed_steps_per_sec = self.double_smoothed_steps_per_sec * weight
460 + normalized_smoothed_steps_per_sec * (1.0 - weight);
461
462 self.prev_steps = new_steps;
463 self.prev_time = now;
464 }
465
466 pub(crate) fn reset(&mut self, now: Instant) {
469 self.smoothed_steps_per_sec = 0.0;
470 self.double_smoothed_steps_per_sec = 0.0;
471
472 self.prev_time = now;
474 self.start_time = now;
475 }
476
477 fn steps_per_second(&self, now: Instant) -> f64 {
479 let delta_t = duration_to_secs(now - self.prev_time);
484 let reweight = estimator_weight(delta_t);
485
486 let delta_t_start = duration_to_secs(now - self.start_time);
506 let total_weight = 1.0 - estimator_weight(delta_t_start);
507
508 let sps = self.smoothed_steps_per_sec * reweight / total_weight;
512 let dsps = self.double_smoothed_steps_per_sec * reweight + sps * (1.0 - reweight);
513 dsps / total_weight
514 }
515}
516
517pub(crate) struct AtomicPosition {
518 pub(crate) pos: AtomicU64,
519 capacity: AtomicU8,
520 prev: AtomicU64,
521 start: Instant,
522}
523
524impl AtomicPosition {
525 pub(crate) fn new() -> Self {
526 Self {
527 pos: AtomicU64::new(0),
528 capacity: AtomicU8::new(MAX_BURST),
529 prev: AtomicU64::new(0),
530 start: Instant::now(),
531 }
532 }
533
534 pub(crate) fn allow(&self, now: Instant) -> bool {
535 if now < self.start {
536 return false;
537 }
538
539 let mut capacity = self.capacity.load(Ordering::Acquire);
540 let prev = self.prev.load(Ordering::Acquire);
542 let elapsed = (now - self.start).as_nanos() as u64;
544 let diff = elapsed.saturating_sub(prev);
546
547 if capacity == 0 && diff < INTERVAL {
551 return false;
552 }
553
554 let (new, remainder) = ((diff / INTERVAL), (diff % INTERVAL));
559 capacity = Ord::min(MAX_BURST as u128, (capacity as u128) + (new as u128) - 1) as u8;
562
563 self.capacity.store(capacity, Ordering::Release);
565 self.prev.store(elapsed - remainder, Ordering::Release);
566 true
567 }
568
569 fn reset(&self, now: Instant) {
570 self.set(0);
571 let elapsed = (now.saturating_duration_since(self.start)).as_millis() as u64;
572 self.prev.store(elapsed, Ordering::Release);
573 }
574
575 pub(crate) fn inc(&self, delta: u64) {
576 self.pos.fetch_add(delta, Ordering::SeqCst);
577 }
578
579 pub(crate) fn set(&self, pos: u64) {
580 self.pos.store(pos, Ordering::Release);
581 }
582}
583
584const INTERVAL: u64 = 1_000_000;
585const MAX_BURST: u8 = 10;
586
587#[derive(Clone, Debug)]
596pub enum ProgressFinish {
597 AndLeave,
601 WithMessage(Cow<'static, str>),
605 AndClear,
609 Abandon,
613 AbandonWithMessage(Cow<'static, str>),
617}
618
619impl Default for ProgressFinish {
620 fn default() -> Self {
621 Self::AndClear
622 }
623}
624
625fn estimator_weight(age: f64) -> f64 {
650 const EXPONENTIAL_WEIGHTING_SECONDS: f64 = 15.0;
651 0.1_f64.powf(age / EXPONENTIAL_WEIGHTING_SECONDS)
652}
653
654fn duration_to_secs(d: Duration) -> f64 {
655 d.as_secs() as f64 + f64::from(d.subsec_nanos()) / 1_000_000_000f64
656}
657
658fn secs_to_duration(s: f64) -> Duration {
659 let secs = s.trunc() as u64;
660 let nanos = (s.fract() * 1_000_000_000f64) as u32;
661 Duration::new(secs, nanos)
662}
663
664#[derive(Debug)]
665pub(crate) enum Status {
666 InProgress,
667 DoneVisible,
668 DoneHidden,
669}
670
671pub(crate) const DEFAULT_TAB_WIDTH: usize = 8;
672
673#[cfg(test)]
674mod tests {
675 use super::*;
676 use crate::ProgressBar;
677
678 #[allow(clippy::uninlined_format_args)]
680 #[test]
681 fn test_steps_per_second() {
682 let test_rate = |items_per_second| {
683 let mut now = Instant::now();
684 let mut est = Estimator::new(now);
685 let mut pos = 0;
686
687 for _ in 0..20 {
688 pos += items_per_second;
689 now += Duration::from_secs(1);
690 est.record(pos, now);
691 }
692 let avg_steps_per_second = est.steps_per_second(now);
693
694 assert!(avg_steps_per_second > 0.0);
695 assert!(avg_steps_per_second.is_finite());
696
697 let absolute_error = (avg_steps_per_second - items_per_second as f64).abs();
698 let relative_error = absolute_error / items_per_second as f64;
699 assert!(
700 relative_error < 1.0 / 1e9,
701 "Expected rate: {}, actual: {}, relative error: {}",
702 items_per_second,
703 avg_steps_per_second,
704 relative_error
705 );
706 };
707
708 test_rate(1);
709 test_rate(1_000);
710 test_rate(1_000_000);
711 test_rate(1_000_000_000);
712 test_rate(1_000_000_001);
713 test_rate(100_000_000_000);
714 test_rate(1_000_000_000_000);
715 test_rate(100_000_000_000_000);
716 test_rate(1_000_000_000_000_000);
717 }
718
719 #[test]
720 fn test_double_exponential_ave() {
721 let mut now = Instant::now();
722 let mut est = Estimator::new(now);
723 let mut pos = 0;
724
725 let weight = 15;
727
728 for _ in 0..weight {
729 pos += 1;
730 now += Duration::from_secs(1);
731 est.record(pos, now);
732 }
733 now += Duration::from_secs(weight);
734
735 let single_target = 0.09 / 0.99;
739
740 let double_target = (0.9 * single_target + 0.09) / 0.99;
743 assert_eq!(est.steps_per_second(now), double_target);
744 }
745
746 #[test]
747 fn test_estimator_rewind_position() {
748 let mut now = Instant::now();
749 let mut est = Estimator::new(now);
750
751 now += Duration::from_secs(1);
752 est.record(1, now);
753
754 now += Duration::from_secs(1);
756 est.record(0, now);
757
758 now += Duration::from_secs(1);
760 est.record(1, now);
761 assert_eq!(est.steps_per_second(now), 1.0);
762
763 let pb = ProgressBar::hidden();
765 pb.set_length(10);
766 pb.set_position(1);
767 pb.tick();
768 pb.set_position(0);
770 }
771
772 #[test]
773 fn test_reset_eta() {
774 let mut now = Instant::now();
775 let mut est = Estimator::new(now);
776
777 now += Duration::from_secs(1);
779 est.record(2, now);
780 est.reset(now);
781
782 now += Duration::from_secs(1);
784 est.record(3, now);
785 assert_eq!(est.steps_per_second(now), 1.0);
786 }
787
788 #[test]
789 fn test_duration_stuff() {
790 let duration = Duration::new(42, 100_000_000);
791 let secs = duration_to_secs(duration);
792 assert_eq!(secs_to_duration(secs), duration);
793 }
794
795 #[test]
796 fn test_atomic_position_large_time_difference() {
797 let atomic_position = AtomicPosition::new();
798 let later = atomic_position.start + Duration::from_nanos(INTERVAL * u64::from(u8::MAX));
799 atomic_position.allow(later);
801 }
802}