Skip to main content

gglib_core/download/
throttle.rs

1//! Progress throttling.
2//!
3//! Rate-limits progress *emission* so a fast transfer cannot flood a channel
4//! or a redraw loop with events nobody can read.
5//!
6//! Deliberately beside [`RateEstimator`](super::rate::RateEstimator): the two
7//! are always used together and the pairing is load-bearing. Feed the
8//! estimator every tick and throttle only what you send — throttling the
9//! measurement is what makes a reported rate drift.
10
11use std::time::{Duration, Instant};
12
13/// Rate-limiter for progress updates.
14///
15/// Ensures progress events are not emitted more frequently than the
16/// configured interval.
17pub struct ProgressThrottle {
18    last_emit: Option<Instant>,
19    min_interval: Duration,
20}
21
22impl ProgressThrottle {
23    /// Create a new throttle with the specified minimum interval.
24    pub const fn new(min_interval: Duration) -> Self {
25        Self {
26            last_emit: None,
27            min_interval,
28        }
29    }
30
31    /// Create a throttle with a default interval of 100ms.
32    pub const fn default_interval() -> Self {
33        Self::new(Duration::from_millis(100))
34    }
35
36    /// Check if enough time has passed to emit another progress update.
37    pub fn should_emit(&mut self) -> bool {
38        let now = Instant::now();
39        match self.last_emit {
40            Some(last) if now.duration_since(last) < self.min_interval => false,
41            _ => {
42                self.last_emit = Some(now);
43                true
44            }
45        }
46    }
47
48    /// Force the next check to return true.
49    pub const fn reset(&mut self) {
50        self.last_emit = None;
51    }
52}
53
54impl Default for ProgressThrottle {
55    fn default() -> Self {
56        Self::default_interval()
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63
64    #[test]
65    fn test_throttle_first_emit() {
66        let mut throttle = ProgressThrottle::new(Duration::from_millis(100));
67        assert!(throttle.should_emit()); // First call should always emit
68    }
69
70    #[test]
71    fn test_throttle_respects_interval() {
72        let mut throttle = ProgressThrottle::new(Duration::from_millis(50));
73        assert!(throttle.should_emit());
74        assert!(!throttle.should_emit()); // Too soon
75
76        std::thread::sleep(Duration::from_millis(60));
77        assert!(throttle.should_emit()); // Enough time passed
78    }
79
80    #[test]
81    fn test_throttle_reset() {
82        let mut throttle = ProgressThrottle::new(Duration::from_millis(100));
83        throttle.should_emit();
84        assert!(!throttle.should_emit());
85
86        throttle.reset();
87        assert!(throttle.should_emit()); // Reset allows immediate emit
88    }
89}