1use std::borrow::Cow;
2use std::io;
3use std::sync::{Arc, OnceLock};
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::{LineType, 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 dec_length(&mut self, now: Instant, delta: u64) {
117 if let Some(len) = self.state.len {
118 self.state.len = Some(len.saturating_sub(delta));
119 }
120 self.update_estimate_and_draw(now);
121 }
122
123 pub(crate) fn set_tab_width(&mut self, tab_width: usize) {
124 self.tab_width = tab_width;
125 self.state.message.set_tab_width(tab_width);
126 self.state.prefix.set_tab_width(tab_width);
127 self.style.set_tab_width(tab_width);
128 }
129
130 pub(crate) fn set_style(&mut self, style: ProgressStyle) {
131 self.style = style;
132 self.style.set_tab_width(self.tab_width);
133 }
134
135 pub(crate) fn tick(&mut self, now: Instant) {
136 self.state.tick = self.state.tick.saturating_add(1);
137 self.update_estimate_and_draw(now);
138 }
139
140 pub(crate) fn update_estimate_and_draw(&mut self, now: Instant) {
141 let pos = self.state.pos.pos.load(Ordering::Relaxed);
142 self.state.est.record(pos, now);
143
144 for tracker in self.style.format_map.values_mut() {
145 tracker.tick(&self.state, now);
146 }
147
148 let _ = self.draw(false, now);
149 }
150
151 pub(crate) fn println(&mut self, now: Instant, msg: &str) {
152 let width = self.draw_target.width();
153 let mut drawable = match self.draw_target.drawable(true, now) {
154 Some(drawable) => drawable,
155 None => return,
156 };
157
158 let mut draw_state = drawable.state();
159 let lines: Vec<LineType> = msg.lines().map(|l| LineType::Text(Into::into(l))).collect();
160 if lines.is_empty() {
162 draw_state.lines.push(LineType::Empty);
163 } else {
164 draw_state.lines.extend(lines);
165 }
166
167 if let Some(width) = width {
168 if !matches!(self.state.status, Status::DoneHidden) {
169 self.style
170 .format_state(&self.state, &mut draw_state.lines, width);
171 }
172 }
173
174 drop(draw_state);
175 let _ = drawable.draw();
176 }
177
178 pub(crate) fn suspend<F: FnOnce() -> R, R>(&mut self, now: Instant, f: F) -> R {
179 if let Some((state, _)) = self.draw_target.remote() {
180 return state.write().unwrap().suspend(f, now);
181 }
182
183 if let Some(drawable) = self.draw_target.drawable(true, now) {
184 let _ = drawable.clear();
185 }
186
187 let ret = f();
188 let _ = self.draw(true, Instant::now());
189 ret
190 }
191
192 pub(crate) fn draw(&mut self, mut force_draw: bool, now: Instant) -> io::Result<()> {
193 force_draw |= self.state.is_finished();
196 let mut drawable = match self.draw_target.drawable(force_draw, now) {
197 Some(drawable) => drawable,
198 None => return Ok(()),
199 };
200
201 let width = drawable.width();
203
204 let mut draw_state = drawable.state();
205
206 if let Some(width) = width {
207 if !matches!(self.state.status, Status::DoneHidden) {
208 self.style
209 .format_state(&self.state, &mut draw_state.lines, width);
210 }
211 }
212
213 drop(draw_state);
214 drawable.draw()
215 }
216}
217
218impl Drop for BarState {
219 fn drop(&mut self) {
220 if self.state.is_finished() {
223 self.draw_target.mark_zombie();
224 return;
225 }
226
227 self.finish_using_style(Instant::now(), self.on_finish.clone());
228
229 self.draw_target.mark_zombie();
231 }
232}
233
234pub(crate) enum Reset {
235 Eta,
236 Elapsed,
237 All,
238}
239
240#[non_exhaustive]
242pub struct ProgressState {
243 pos: Arc<AtomicPosition>,
244 len: Option<u64>,
245 pub(crate) tick: u64,
246 pub(crate) started: Instant,
247 status: Status,
248 est: Estimator,
249 pub(crate) message: TabExpandedString,
250 pub(crate) prefix: TabExpandedString,
251}
252
253impl ProgressState {
254 pub(crate) fn new(len: Option<u64>, pos: Arc<AtomicPosition>) -> Self {
255 let now = Instant::now();
256 Self {
257 pos,
258 len,
259 tick: 0,
260 status: Status::InProgress,
261 started: now,
262 est: Estimator::new(now),
263 message: TabExpandedString::NoTabs("".into()),
264 prefix: TabExpandedString::NoTabs("".into()),
265 }
266 }
267
268 pub fn is_finished(&self) -> bool {
270 match self.status {
271 Status::InProgress => false,
272 Status::DoneVisible => true,
273 Status::DoneHidden => true,
274 }
275 }
276
277 pub fn fraction(&self) -> f32 {
279 let pos = self.pos.pos.load(Ordering::Relaxed);
280 let pct = match (pos, self.len) {
281 (_, None) => 0.0,
282 (_, Some(0)) => 1.0,
283 (0, _) => 0.0,
284 (pos, Some(len)) => pos as f32 / len as f32,
285 };
286 pct.clamp(0.0, 1.0)
287 }
288
289 pub fn eta(&self) -> Duration {
291 if self.is_finished() {
292 return Duration::new(0, 0);
293 }
294
295 let len = match self.len {
296 Some(len) => len,
297 None => return Duration::new(0, 0),
298 };
299
300 let pos = self.pos.pos.load(Ordering::Relaxed);
301
302 let sps = self.est.steps_per_second(Instant::now());
303
304 if sps == 0.0 {
307 return Duration::new(0, 0);
308 }
309
310 secs_to_duration(len.saturating_sub(pos) as f64 / sps)
311 }
312
313 pub fn duration(&self) -> Duration {
315 if self.len.is_none() || self.is_finished() {
316 return Duration::new(0, 0);
317 }
318 self.started.elapsed().saturating_add(self.eta())
319 }
320
321 pub fn per_sec(&self) -> f64 {
323 if let Status::InProgress = self.status {
324 self.est.steps_per_second(Instant::now())
325 } else {
326 self.pos() as f64 / self.started.elapsed().as_secs_f64()
327 }
328 }
329
330 pub fn elapsed(&self) -> Duration {
331 self.started.elapsed()
332 }
333
334 pub fn pos(&self) -> u64 {
335 self.pos.pos.load(Ordering::Relaxed)
336 }
337
338 pub fn set_pos(&mut self, pos: u64) {
339 self.pos.set(pos);
340 }
341
342 #[allow(clippy::len_without_is_empty)]
343 pub fn len(&self) -> Option<u64> {
344 self.len
345 }
346
347 pub fn set_len(&mut self, len: u64) {
348 self.len = Some(len);
349 }
350}
351
352#[derive(Debug, PartialEq, Eq, Clone)]
353pub(crate) enum TabExpandedString {
354 NoTabs(Cow<'static, str>),
355 WithTabs {
356 original: Cow<'static, str>,
357 expanded: OnceLock<String>,
358 tab_width: usize,
359 },
360}
361
362impl TabExpandedString {
363 pub(crate) fn new(s: Cow<'static, str>, tab_width: usize) -> Self {
364 if !s.contains('\t') {
365 Self::NoTabs(s)
366 } else {
367 Self::WithTabs {
368 original: s,
369 tab_width,
370 expanded: OnceLock::new(),
371 }
372 }
373 }
374
375 pub(crate) fn expanded(&self) -> &str {
376 match &self {
377 Self::NoTabs(s) => {
378 debug_assert!(!s.contains('\t'));
379 s
380 }
381 Self::WithTabs {
382 original,
383 tab_width,
384 expanded,
385 } => expanded.get_or_init(|| original.replace('\t', &" ".repeat(*tab_width))),
386 }
387 }
388
389 pub(crate) fn set_tab_width(&mut self, new_tab_width: usize) {
390 if let Self::WithTabs {
391 expanded,
392 tab_width,
393 ..
394 } = self
395 {
396 if *tab_width != new_tab_width {
397 *tab_width = new_tab_width;
398 expanded.take();
399 }
400 }
401 }
402}
403
404#[derive(Debug)]
421pub(crate) struct Estimator {
422 smoothed_steps_per_sec: f64,
423 double_smoothed_steps_per_sec: f64,
424 prev_steps: u64,
425 prev_time: Instant,
426 start_time: Instant,
427}
428
429impl Estimator {
430 fn new(now: Instant) -> Self {
431 Self {
432 smoothed_steps_per_sec: 0.0,
433 double_smoothed_steps_per_sec: 0.0,
434 prev_steps: 0,
435 prev_time: now,
436 start_time: now,
437 }
438 }
439
440 fn record(&mut self, new_steps: u64, now: Instant) {
441 if new_steps <= self.prev_steps || now <= self.prev_time {
443 if new_steps < self.prev_steps {
446 self.prev_steps = new_steps;
447 self.reset(now);
448 }
449 return;
450 }
451
452 let delta_steps = new_steps - self.prev_steps;
453 let delta_t = duration_to_secs(now - self.prev_time);
454
455 let new_steps_per_second = delta_steps as f64 / delta_t;
457
458 let weight = estimator_weight(delta_t);
460 self.smoothed_steps_per_sec =
461 self.smoothed_steps_per_sec * weight + new_steps_per_second * (1.0 - weight);
462
463 let delta_t_start = duration_to_secs(now - self.start_time);
470 let total_weight = 1.0 - estimator_weight(delta_t_start);
471 let normalized_smoothed_steps_per_sec = self.smoothed_steps_per_sec / total_weight;
472
473 self.double_smoothed_steps_per_sec = self.double_smoothed_steps_per_sec * weight
475 + normalized_smoothed_steps_per_sec * (1.0 - weight);
476
477 self.prev_steps = new_steps;
478 self.prev_time = now;
479 }
480
481 pub(crate) fn reset(&mut self, now: Instant) {
484 self.smoothed_steps_per_sec = 0.0;
485 self.double_smoothed_steps_per_sec = 0.0;
486
487 self.prev_time = now;
489 self.start_time = now;
490 }
491
492 fn steps_per_second(&self, now: Instant) -> f64 {
494 let delta_t = duration_to_secs(now - self.prev_time);
499 let reweight = estimator_weight(delta_t);
500
501 let delta_t_start = duration_to_secs(now - self.start_time);
521 let total_weight = 1.0 - estimator_weight(delta_t_start);
522
523 let sps = self.smoothed_steps_per_sec * reweight / total_weight;
527 let dsps = self.double_smoothed_steps_per_sec * reweight + sps * (1.0 - reweight);
528 dsps / total_weight
529 }
530}
531
532pub(crate) struct AtomicPosition {
533 pub(crate) pos: AtomicU64,
534 capacity: AtomicU8,
535 prev: AtomicU64,
536 start: Instant,
537}
538
539impl AtomicPosition {
540 pub(crate) fn new() -> Self {
541 Self {
542 pos: AtomicU64::new(0),
543 capacity: AtomicU8::new(MAX_BURST),
544 prev: AtomicU64::new(0),
545 start: Instant::now(),
546 }
547 }
548
549 pub(crate) fn allow(&self, now: Instant) -> bool {
550 if now < self.start {
551 return false;
552 }
553
554 let mut capacity = self.capacity.load(Ordering::Acquire);
555 let prev = self.prev.load(Ordering::Acquire);
557 let elapsed = (now - self.start).as_nanos() as u64;
559 let diff = elapsed.saturating_sub(prev);
561
562 if capacity == 0 && diff < INTERVAL {
566 return false;
567 }
568
569 let (new, remainder) = ((diff / INTERVAL), (diff % INTERVAL));
574 capacity = Ord::min(MAX_BURST as u128, (capacity as u128) + (new as u128) - 1) as u8;
577
578 self.capacity.store(capacity, Ordering::Release);
580 self.prev.store(elapsed - remainder, Ordering::Release);
581 true
582 }
583
584 fn reset(&self, now: Instant) {
585 self.set(0);
586 let elapsed = (now.saturating_duration_since(self.start)).as_nanos() as u64;
587 self.prev.store(elapsed, Ordering::Release);
588 }
589
590 pub(crate) fn inc(&self, delta: u64) {
591 self.pos.fetch_add(delta, Ordering::SeqCst);
592 }
593
594 pub(crate) fn dec(&self, delta: u64) {
595 self.pos.fetch_sub(delta, Ordering::SeqCst);
596 }
597
598 pub(crate) fn set(&self, pos: u64) {
599 self.pos.store(pos, Ordering::Release);
600 }
601}
602
603const INTERVAL: u64 = 1_000_000;
604const MAX_BURST: u8 = 10;
605
606#[derive(Clone, Debug)]
615pub enum ProgressFinish {
616 AndLeave,
620 WithMessage(Cow<'static, str>),
624 AndClear,
628 Abandon,
632 AbandonWithMessage(Cow<'static, str>),
636}
637
638impl Default for ProgressFinish {
639 fn default() -> Self {
640 Self::AndClear
641 }
642}
643
644fn estimator_weight(age: f64) -> f64 {
669 const EXPONENTIAL_WEIGHTING_SECONDS: f64 = 15.0;
670 0.1_f64.powf(age / EXPONENTIAL_WEIGHTING_SECONDS)
671}
672
673fn duration_to_secs(d: Duration) -> f64 {
674 d.as_secs() as f64 + f64::from(d.subsec_nanos()) / 1_000_000_000f64
675}
676
677fn secs_to_duration(s: f64) -> Duration {
678 let secs = s.trunc() as u64;
679 let nanos = (s.fract() * 1_000_000_000f64) as u32;
680 Duration::new(secs, nanos)
681}
682
683#[derive(Debug)]
684pub(crate) enum Status {
685 InProgress,
686 DoneVisible,
687 DoneHidden,
688}
689
690pub(crate) const DEFAULT_TAB_WIDTH: usize = 8;
691
692#[cfg(test)]
693mod tests {
694 use super::*;
695 use crate::ProgressBar;
696
697 #[allow(clippy::uninlined_format_args)]
699 #[test]
700 fn test_steps_per_second() {
701 let test_rate = |items_per_second| {
702 let mut now = Instant::now();
703 let mut est = Estimator::new(now);
704 let mut pos = 0;
705
706 for _ in 0..20 {
707 pos += items_per_second;
708 now += Duration::from_secs(1);
709 est.record(pos, now);
710 }
711 let avg_steps_per_second = est.steps_per_second(now);
712
713 assert!(avg_steps_per_second > 0.0);
714 assert!(avg_steps_per_second.is_finite());
715
716 let absolute_error = (avg_steps_per_second - items_per_second as f64).abs();
717 let relative_error = absolute_error / items_per_second as f64;
718 assert!(
719 relative_error < 1.0 / 1e9,
720 "Expected rate: {}, actual: {}, relative error: {}",
721 items_per_second,
722 avg_steps_per_second,
723 relative_error
724 );
725 };
726
727 test_rate(1);
728 test_rate(1_000);
729 test_rate(1_000_000);
730 test_rate(1_000_000_000);
731 test_rate(1_000_000_001);
732 test_rate(100_000_000_000);
733 test_rate(1_000_000_000_000);
734 test_rate(100_000_000_000_000);
735 test_rate(1_000_000_000_000_000);
736 }
737
738 #[test]
739 fn test_double_exponential_ave() {
740 let mut now = Instant::now();
741 let mut est = Estimator::new(now);
742 let mut pos = 0;
743
744 let weight = 15;
746
747 for _ in 0..weight {
748 pos += 1;
749 now += Duration::from_secs(1);
750 est.record(pos, now);
751 }
752 now += Duration::from_secs(weight);
753
754 let single_target = 0.09 / 0.99;
758
759 let double_target = (0.9 * single_target + 0.09) / 0.99;
762 assert_eq!(est.steps_per_second(now), double_target);
763 }
764
765 #[test]
766 fn test_estimator_rewind_position() {
767 let mut now = Instant::now();
768 let mut est = Estimator::new(now);
769
770 now += Duration::from_secs(1);
771 est.record(1, now);
772
773 now += Duration::from_secs(1);
775 est.record(0, now);
776
777 now += Duration::from_secs(1);
779 est.record(1, now);
780 assert_eq!(est.steps_per_second(now), 1.0);
781
782 let pb = ProgressBar::hidden();
784 pb.set_length(10);
785 pb.set_position(1);
786 pb.tick();
787 pb.set_position(0);
789 }
790
791 #[test]
792 fn test_reset_eta() {
793 let mut now = Instant::now();
794 let mut est = Estimator::new(now);
795
796 now += Duration::from_secs(1);
798 est.record(2, now);
799 est.reset(now);
800
801 now += Duration::from_secs(1);
803 est.record(3, now);
804 assert_eq!(est.steps_per_second(now), 1.0);
805 }
806
807 #[test]
808 fn test_duration_stuff() {
809 let duration = Duration::new(42, 100_000_000);
810 let secs = duration_to_secs(duration);
811 assert_eq!(secs_to_duration(secs), duration);
812 }
813
814 #[test]
815 fn test_atomic_position_large_time_difference() {
816 let atomic_position = AtomicPosition::new();
817 let later = atomic_position.start + Duration::from_nanos(INTERVAL * u64::from(u8::MAX));
818 atomic_position.allow(later);
820 }
821
822 #[test]
823 fn test_atomic_position_reset() {
824 const ELAPSE_TIME: Duration = Duration::from_millis(20);
825 let mut pos = AtomicPosition::new();
826 pos.reset(pos.start + ELAPSE_TIME);
827
828 assert_eq!(*pos.pos.get_mut(), 0);
830 assert_eq!(*pos.prev.get_mut(), ELAPSE_TIME.as_nanos() as u64);
831 }
832}