Skip to main content

jellytau_lib/playback_reporting/
throttle.rs

1//! Event throttler for position update reporting
2//!
3//! This module is fully implemented but not yet integrated with the player.
4//! Dead code warnings are suppressed until integration is complete.
5
6#![allow(dead_code)]
7
8use crate::utils::lock::MutexSafe;
9use std::collections::HashMap;
10use std::sync::{Arc, Mutex};
11use std::time::{Duration, Instant};
12
13/// Event throttler to prevent spam from frequent position updates.
14///
15/// Tracks the last report time for each item and ensures reports are only
16/// sent at most once per throttle duration (default 30 seconds).
17pub struct EventThrottler {
18    last_report_time: Arc<Mutex<HashMap<String, Instant>>>,
19    throttle_duration: Duration,
20}
21
22impl EventThrottler {
23    /// Creates a new EventThrottler with 30 second default interval
24    pub fn new() -> Self {
25        Self::with_duration(Duration::from_secs(30))
26    }
27
28    /// Creates a new EventThrottler with custom interval
29    pub fn with_duration(duration: Duration) -> Self {
30        Self {
31            last_report_time: Arc::new(Mutex::new(HashMap::new())),
32            throttle_duration: duration,
33        }
34    }
35
36    /// Checks if enough time has elapsed since the last report for this item
37    pub fn should_report(&self, item_id: &str) -> bool {
38        let last_times = self.last_report_time.lock_safe();
39
40        if let Some(last_time) = last_times.get(item_id) {
41            let elapsed = last_time.elapsed();
42            if elapsed < self.throttle_duration {
43                log::debug!(
44                    "[EventThrottler] Skipping report for {}, last reported {:.1}s ago (threshold: {}s)",
45                    item_id,
46                    elapsed.as_secs_f64(),
47                    self.throttle_duration.as_secs()
48                );
49                return false;
50            }
51        }
52
53        true
54    }
55
56    /// Marks the item as reported at the current time
57    pub fn mark_reported(&self, item_id: &str) {
58        let mut last_times = self.last_report_time.lock_safe();
59        last_times.insert(item_id.to_string(), Instant::now());
60
61        log::debug!(
62            "[EventThrottler] Marked {} as reported at {:?}",
63            item_id,
64            Instant::now()
65        );
66    }
67
68    /// Clears all tracked report times
69    pub fn clear(&self) {
70        let mut last_times = self.last_report_time.lock_safe();
71        last_times.clear();
72        log::debug!("[EventThrottler] Cleared all tracked report times");
73    }
74
75    /// Removes a specific item from tracking
76    pub fn clear_item(&self, item_id: &str) {
77        let mut last_times = self.last_report_time.lock_safe();
78        last_times.remove(item_id);
79        log::debug!("[EventThrottler] Cleared tracking for {}", item_id);
80    }
81}
82
83impl Default for EventThrottler {
84    fn default() -> Self {
85        Self::new()
86    }
87}
88
89impl Clone for EventThrottler {
90    fn clone(&self) -> Self {
91        Self {
92            last_report_time: Arc::clone(&self.last_report_time),
93            throttle_duration: self.throttle_duration,
94        }
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101    use std::thread;
102
103    #[test]
104    fn test_throttler_allows_first_report() {
105        let throttler = EventThrottler::new();
106        assert!(throttler.should_report("item1"));
107    }
108
109    #[test]
110    fn test_throttler_blocks_immediate_second_report() {
111        let throttler = EventThrottler::new();
112        assert!(throttler.should_report("item1"));
113        throttler.mark_reported("item1");
114        assert!(!throttler.should_report("item1"));
115    }
116
117    #[test]
118    fn test_throttler_allows_report_after_duration() {
119        let throttler = EventThrottler::with_duration(Duration::from_millis(100));
120        assert!(throttler.should_report("item1"));
121        throttler.mark_reported("item1");
122        assert!(!throttler.should_report("item1"));
123
124        thread::sleep(Duration::from_millis(150));
125        assert!(throttler.should_report("item1"));
126    }
127
128    #[test]
129    fn test_throttler_handles_multiple_items() {
130        let throttler = EventThrottler::new();
131        assert!(throttler.should_report("item1"));
132        throttler.mark_reported("item1");
133
134        assert!(throttler.should_report("item2"));
135        throttler.mark_reported("item2");
136
137        assert!(!throttler.should_report("item1"));
138        assert!(!throttler.should_report("item2"));
139    }
140
141    #[test]
142    fn test_throttler_clear() {
143        let throttler = EventThrottler::new();
144        throttler.mark_reported("item1");
145        assert!(!throttler.should_report("item1"));
146
147        throttler.clear();
148        assert!(throttler.should_report("item1"));
149    }
150
151    #[test]
152    fn test_throttler_clear_item() {
153        let throttler = EventThrottler::new();
154        throttler.mark_reported("item1");
155        throttler.mark_reported("item2");
156
157        assert!(!throttler.should_report("item1"));
158        assert!(!throttler.should_report("item2"));
159
160        throttler.clear_item("item1");
161        assert!(throttler.should_report("item1"));
162        assert!(!throttler.should_report("item2"));
163    }
164
165    #[test]
166    fn test_throttler_clone_shares_state() {
167        let throttler1 = EventThrottler::new();
168        throttler1.mark_reported("item1");
169
170        let throttler2 = throttler1.clone();
171        assert!(!throttler2.should_report("item1"));
172
173        throttler2.mark_reported("item2");
174        assert!(!throttler1.should_report("item2"));
175    }
176}