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
8use portable_atomic::{AtomicU64, AtomicU8, Ordering};
9#[cfg(target_arch = "wasm32")]
10use web_time::Instant;
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 unset_length(&mut self, now: Instant) {
100 self.state.len = None;
101 self.update_estimate_and_draw(now);
102 }
103
104 pub(crate) fn set_length(&mut self, now: Instant, len: u64) {
105 self.state.len = Some(len);
106 self.update_estimate_and_draw(now);
107 }
108
109 pub(crate) fn inc_length(&mut self, now: Instant, delta: u64) {
110 if let Some(len) = self.state.len {
111 self.state.len = Some(len.saturating_add(delta));
112 }
113 self.update_estimate_and_draw(now);
114 }
115
116 pub(crate) fn set_tab_width(&mut self, tab_width: usize) {
117 self.tab_width = tab_width;
118 self.state.message.set_tab_width(tab_width);
119 self.state.prefix.set_tab_width(tab_width);
120 self.style.set_tab_width(tab_width);
121 }
122
123 pub(crate) fn set_style(&mut self, style: ProgressStyle) {
124 self.style = style;
125 self.style.set_tab_width(self.tab_width);
126 }
127
128 pub(crate) fn tick(&mut self, now: Instant) {
129 self.state.tick = self.state.tick.saturating_add(1);
130 self.update_estimate_and_draw(now);
131 }
132
133 pub(crate) fn update_estimate_and_draw(&mut self, now: Instant) {
134 let pos = self.state.pos.pos.load(Ordering::Relaxed);
135 self.state.est.record(pos, now);
136
137 for tracker in self.style.format_map.values_mut() {
138 tracker.tick(&self.state, now);
139 }
140
141 let _ = self.draw(false, now);
142 }
143
144 pub(crate) fn println(&mut self, now: Instant, msg: &str) {
145 let width = self.draw_target.width();
146 let mut drawable = match self.draw_target.drawable(true, now) {
147 Some(drawable) => drawable,
148 None => return,
149 };
150
151 let mut draw_state = drawable.state();
152 let lines: Vec<String> = msg.lines().map(Into::into).collect();
153 if lines.is_empty() {
155 draw_state.lines.push(String::new());
156 } else {
157 draw_state.lines.extend(lines);
158 }
159
160 draw_state.orphan_lines_count = draw_state.lines.len();
161 if let Some(width) = width {
162 if !matches!(self.state.status, Status::DoneHidden) {
163 self.style
164 .format_state(&self.state, &mut draw_state.lines, width);
165 }
166 }
167
168 drop(draw_state);
169 let _ = drawable.draw();
170 }
171
172 pub(crate) fn suspend<F: FnOnce() -> R, R>(&mut self, now: Instant, f: F) -> R {
173 if let Some((state, _)) = self.draw_target.remote() {
174 return state.write().unwrap().suspend(f, now);
175 }
176
177 if let Some(drawable) = self.draw_target.drawable(true, now) {
178 let _ = drawable.clear();
179 }
180
181 let ret = f();
182 let _ = self.draw(true, Instant::now());
183 ret
184 }
185
186 pub(crate) fn draw(&mut self, mut force_draw: bool, now: Instant) -> io::Result<()> {
187 let width = self.draw_target.width();
188
189 force_draw |= self.state.is_finished();
192 let mut drawable = match self.draw_target.drawable(force_draw, now) {
193 Some(drawable) => drawable,
194 None => return Ok(()),
195 };
196
197 let mut draw_state = drawable.state();
198
199 if let Some(width) = width {
200 if !matches!(self.state.status, Status::DoneHidden) {
201 self.style
202 .format_state(&self.state, &mut draw_state.lines, width);
203 }
204 }
205
206 drop(draw_state);
207 drawable.draw()
208 }
209}
210
211impl Drop for BarState {
212 fn drop(&mut self) {
213 if self.state.is_finished() {
216 self.draw_target.mark_zombie();
217 return;
218 }
219
220 self.finish_using_style(Instant::now(), self.on_finish.clone());
221
222 self.draw_target.mark_zombie();
224 }
225}
226
227pub(crate) enum Reset {
228 Eta,
229 Elapsed,
230 All,
231}
232
233#[non_exhaustive]
235pub struct ProgressState {
236 pos: Arc<AtomicPosition>,
237 len: Option<u64>,
238 pub(crate) tick: u64,
239 pub(crate) started: Instant,
240 status: Status,
241 est: Estimator,
242 pub(crate) message: TabExpandedString,
243 pub(crate) prefix: TabExpandedString,
244}
245
246impl ProgressState {
247 pub(crate) fn new(len: Option<u64>, pos: Arc<AtomicPosition>) -> Self {
248 let now = Instant::now();
249 Self {
250 pos,
251 len,
252 tick: 0,
253 status: Status::InProgress,
254 started: now,
255 est: Estimator::new(now),
256 message: TabExpandedString::NoTabs("".into()),
257 prefix: TabExpandedString::NoTabs("".into()),
258 }
259 }
260
261 pub fn is_finished(&self) -> bool {
263 match self.status {
264 Status::InProgress => false,
265 Status::DoneVisible => true,
266 Status::DoneHidden => true,
267 }
268 }
269
270 pub fn fraction(&self) -> f32 {
272 let pos = self.pos.pos.load(Ordering::Relaxed);
273 let pct = match (pos, self.len) {
274 (_, None) => 0.0,
275 (_, Some(0)) => 1.0,
276 (0, _) => 0.0,
277 (pos, Some(len)) => pos as f32 / len as f32,
278 };
279 pct.clamp(0.0, 1.0)
280 }
281
282 pub fn eta(&self) -> Duration {
284 if self.is_finished() {
285 return Duration::new(0, 0);
286 }
287
288 let len = match self.len {
289 Some(len) => len,
290 None => return Duration::new(0, 0),
291 };
292
293 let pos = self.pos.pos.load(Ordering::Relaxed);
294
295 let sps = self.est.steps_per_second(Instant::now());
296
297 if sps == 0.0 {
300 return Duration::new(0, 0);
301 }
302
303 secs_to_duration(len.saturating_sub(pos) as f64 / sps)
304 }
305
306 pub fn duration(&self) -> Duration {
308 if self.len.is_none() || self.is_finished() {
309 return Duration::new(0, 0);
310 }
311 self.started.elapsed().saturating_add(self.eta())
312 }
313
314 pub fn per_sec(&self) -> f64 {
316 if let Status::InProgress = self.status {
317 self.est.steps_per_second(Instant::now())
318 } else {
319 self.pos() as f64 / self.started.elapsed().as_secs_f64()
320 }
321 }
322
323 pub fn elapsed(&self) -> Duration {
324 self.started.elapsed()
325 }
326
327 pub fn pos(&self) -> u64 {
328 self.pos.pos.load(Ordering::Relaxed)
329 }
330
331 pub fn set_pos(&mut self, pos: u64) {
332 self.pos.set(pos);
333 }
334
335 #[allow(clippy::len_without_is_empty)]
336 pub fn len(&self) -> Option<u64> {
337 self.len
338 }
339
340 pub fn set_len(&mut self, len: u64) {
341 self.len = Some(len);
342 }
343}
344
345#[derive(Debug, PartialEq, Eq, Clone)]
346pub(crate) enum TabExpandedString {
347 NoTabs(Cow<'static, str>),
348 WithTabs {
349 original: Cow<'static, str>,
350 expanded: String,
351 tab_width: usize,
352 },
353}
354
355impl TabExpandedString {
356 pub(crate) fn new(s: Cow<'static, str>, tab_width: usize) -> Self {
357 let expanded = s.replace('\t', &" ".repeat(tab_width));
358 if s == expanded {
359 Self::NoTabs(s)
360 } else {
361 Self::WithTabs {
362 original: s,
363 expanded,
364 tab_width,
365 }
366 }
367 }
368
369 pub(crate) fn expanded(&self) -> &str {
370 match &self {
371 Self::NoTabs(s) => {
372 debug_assert!(!s.contains('\t'));
373 s
374 }
375 Self::WithTabs { expanded, .. } => expanded,
376 }
377 }
378
379 pub(crate) fn set_tab_width(&mut self, new_tab_width: usize) {
380 if let Self::WithTabs {
381 original,
382 expanded,
383 tab_width,
384 } = self
385 {
386 if *tab_width != new_tab_width {
387 *tab_width = new_tab_width;
388 *expanded = original.replace('\t', &" ".repeat(new_tab_width));
389 }
390 }
391 }
392}
393
394#[derive(Debug)]
411pub(crate) struct Estimator {
412 smoothed_steps_per_sec: f64,
413 double_smoothed_steps_per_sec: f64,
414 prev_steps: u64,
415 prev_time: Instant,
416 start_time: Instant,
417}
418
419impl Estimator {
420 fn new(now: Instant) -> Self {
421 Self {
422 smoothed_steps_per_sec: 0.0,
423 double_smoothed_steps_per_sec: 0.0,
424 prev_steps: 0,
425 prev_time: now,
426 start_time: now,
427 }
428 }
429
430 fn record(&mut self, new_steps: u64, now: Instant) {
431 if new_steps <= self.prev_steps || now <= self.prev_time {
433 if new_steps < self.prev_steps {
436 self.prev_steps = new_steps;
437 self.reset(now);
438 }
439 return;
440 }
441
442 let delta_steps = new_steps - self.prev_steps;
443 let delta_t = duration_to_secs(now - self.prev_time);
444
445 let new_steps_per_second = delta_steps as f64 / delta_t;
447
448 let weight = estimator_weight(delta_t);
450 self.smoothed_steps_per_sec =
451 self.smoothed_steps_per_sec * weight + new_steps_per_second * (1.0 - weight);
452
453 let delta_t_start = duration_to_secs(now - self.start_time);
460 let total_weight = 1.0 - estimator_weight(delta_t_start);
461 let normalized_smoothed_steps_per_sec = self.smoothed_steps_per_sec / total_weight;
462
463 self.double_smoothed_steps_per_sec = self.double_smoothed_steps_per_sec * weight
465 + normalized_smoothed_steps_per_sec * (1.0 - weight);
466
467 self.prev_steps = new_steps;
468 self.prev_time = now;
469 }
470
471 pub(crate) fn reset(&mut self, now: Instant) {
474 self.smoothed_steps_per_sec = 0.0;
475 self.double_smoothed_steps_per_sec = 0.0;
476
477 self.prev_time = now;
479 self.start_time = now;
480 }
481
482 fn steps_per_second(&self, now: Instant) -> f64 {
484 let delta_t = duration_to_secs(now - self.prev_time);
489 let reweight = estimator_weight(delta_t);
490
491 let delta_t_start = duration_to_secs(now - self.start_time);
511 let total_weight = 1.0 - estimator_weight(delta_t_start);
512
513 let sps = self.smoothed_steps_per_sec * reweight / total_weight;
517 let dsps = self.double_smoothed_steps_per_sec * reweight + sps * (1.0 - reweight);
518 dsps / total_weight
519 }
520}
521
522pub(crate) struct AtomicPosition {
523 pub(crate) pos: AtomicU64,
524 capacity: AtomicU8,
525 prev: AtomicU64,
526 start: Instant,
527}
528
529impl AtomicPosition {
530 pub(crate) fn new() -> Self {
531 Self {
532 pos: AtomicU64::new(0),
533 capacity: AtomicU8::new(MAX_BURST),
534 prev: AtomicU64::new(0),
535 start: Instant::now(),
536 }
537 }
538
539 pub(crate) fn allow(&self, now: Instant) -> bool {
540 if now < self.start {
541 return false;
542 }
543
544 let mut capacity = self.capacity.load(Ordering::Acquire);
545 let prev = self.prev.load(Ordering::Acquire);
547 let elapsed = (now - self.start).as_nanos() as u64;
549 let diff = elapsed.saturating_sub(prev);
551
552 if capacity == 0 && diff < INTERVAL {
556 return false;
557 }
558
559 let (new, remainder) = ((diff / INTERVAL), (diff % INTERVAL));
564 capacity = Ord::min(MAX_BURST as u128, (capacity as u128) + (new as u128) - 1) as u8;
567
568 self.capacity.store(capacity, Ordering::Release);
570 self.prev.store(elapsed - remainder, Ordering::Release);
571 true
572 }
573
574 fn reset(&self, now: Instant) {
575 self.set(0);
576 let elapsed = (now.saturating_duration_since(self.start)).as_nanos() as u64;
577 self.prev.store(elapsed, Ordering::Release);
578 }
579
580 pub(crate) fn inc(&self, delta: u64) {
581 self.pos.fetch_add(delta, Ordering::SeqCst);
582 }
583
584 pub(crate) fn set(&self, pos: u64) {
585 self.pos.store(pos, Ordering::Release);
586 }
587}
588
589const INTERVAL: u64 = 1_000_000;
590const MAX_BURST: u8 = 10;
591
592#[derive(Clone, Debug)]
601pub enum ProgressFinish {
602 AndLeave,
606 WithMessage(Cow<'static, str>),
610 AndClear,
614 Abandon,
618 AbandonWithMessage(Cow<'static, str>),
622}
623
624impl Default for ProgressFinish {
625 fn default() -> Self {
626 Self::AndClear
627 }
628}
629
630fn estimator_weight(age: f64) -> f64 {
655 const EXPONENTIAL_WEIGHTING_SECONDS: f64 = 15.0;
656 0.1_f64.powf(age / EXPONENTIAL_WEIGHTING_SECONDS)
657}
658
659fn duration_to_secs(d: Duration) -> f64 {
660 d.as_secs() as f64 + f64::from(d.subsec_nanos()) / 1_000_000_000f64
661}
662
663fn secs_to_duration(s: f64) -> Duration {
664 let secs = s.trunc() as u64;
665 let nanos = (s.fract() * 1_000_000_000f64) as u32;
666 Duration::new(secs, nanos)
667}
668
669#[derive(Debug)]
670pub(crate) enum Status {
671 InProgress,
672 DoneVisible,
673 DoneHidden,
674}
675
676pub(crate) const DEFAULT_TAB_WIDTH: usize = 8;
677
678#[cfg(test)]
679mod tests {
680 use super::*;
681 use crate::ProgressBar;
682
683 #[allow(clippy::uninlined_format_args)]
685 #[test]
686 fn test_steps_per_second() {
687 let test_rate = |items_per_second| {
688 let mut now = Instant::now();
689 let mut est = Estimator::new(now);
690 let mut pos = 0;
691
692 for _ in 0..20 {
693 pos += items_per_second;
694 now += Duration::from_secs(1);
695 est.record(pos, now);
696 }
697 let avg_steps_per_second = est.steps_per_second(now);
698
699 assert!(avg_steps_per_second > 0.0);
700 assert!(avg_steps_per_second.is_finite());
701
702 let absolute_error = (avg_steps_per_second - items_per_second as f64).abs();
703 let relative_error = absolute_error / items_per_second as f64;
704 assert!(
705 relative_error < 1.0 / 1e9,
706 "Expected rate: {}, actual: {}, relative error: {}",
707 items_per_second,
708 avg_steps_per_second,
709 relative_error
710 );
711 };
712
713 test_rate(1);
714 test_rate(1_000);
715 test_rate(1_000_000);
716 test_rate(1_000_000_000);
717 test_rate(1_000_000_001);
718 test_rate(100_000_000_000);
719 test_rate(1_000_000_000_000);
720 test_rate(100_000_000_000_000);
721 test_rate(1_000_000_000_000_000);
722 }
723
724 #[test]
725 fn test_double_exponential_ave() {
726 let mut now = Instant::now();
727 let mut est = Estimator::new(now);
728 let mut pos = 0;
729
730 let weight = 15;
732
733 for _ in 0..weight {
734 pos += 1;
735 now += Duration::from_secs(1);
736 est.record(pos, now);
737 }
738 now += Duration::from_secs(weight);
739
740 let single_target = 0.09 / 0.99;
744
745 let double_target = (0.9 * single_target + 0.09) / 0.99;
748 assert_eq!(est.steps_per_second(now), double_target);
749 }
750
751 #[test]
752 fn test_estimator_rewind_position() {
753 let mut now = Instant::now();
754 let mut est = Estimator::new(now);
755
756 now += Duration::from_secs(1);
757 est.record(1, now);
758
759 now += Duration::from_secs(1);
761 est.record(0, now);
762
763 now += Duration::from_secs(1);
765 est.record(1, now);
766 assert_eq!(est.steps_per_second(now), 1.0);
767
768 let pb = ProgressBar::hidden();
770 pb.set_length(10);
771 pb.set_position(1);
772 pb.tick();
773 pb.set_position(0);
775 }
776
777 #[test]
778 fn test_reset_eta() {
779 let mut now = Instant::now();
780 let mut est = Estimator::new(now);
781
782 now += Duration::from_secs(1);
784 est.record(2, now);
785 est.reset(now);
786
787 now += Duration::from_secs(1);
789 est.record(3, now);
790 assert_eq!(est.steps_per_second(now), 1.0);
791 }
792
793 #[test]
794 fn test_duration_stuff() {
795 let duration = Duration::new(42, 100_000_000);
796 let secs = duration_to_secs(duration);
797 assert_eq!(secs_to_duration(secs), duration);
798 }
799
800 #[test]
801 fn test_atomic_position_large_time_difference() {
802 let atomic_position = AtomicPosition::new();
803 let later = atomic_position.start + Duration::from_nanos(INTERVAL * u64::from(u8::MAX));
804 atomic_position.allow(later);
806 }
807
808 #[test]
809 fn test_atomic_position_reset() {
810 const ELAPSE_TIME: Duration = Duration::from_millis(20);
811 let mut pos = AtomicPosition::new();
812 pos.reset(pos.start + ELAPSE_TIME);
813
814 assert_eq!(*pos.pos.get_mut(), 0);
816 assert_eq!(*pos.prev.get_mut(), ELAPSE_TIME.as_nanos() as u64);
817 }
818}