//! Event throttler for position update reporting //! //! This module is fully implemented but not yet integrated with the player. //! Dead code warnings are suppressed until integration is complete. #![allow(dead_code)] use crate::utils::lock::MutexSafe; use std::collections::HashMap; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; /// Event throttler to prevent spam from frequent position updates. /// /// Tracks the last report time for each item and ensures reports are only /// sent at most once per throttle duration (default 30 seconds). pub struct EventThrottler { last_report_time: Arc>>, throttle_duration: Duration, } impl EventThrottler { /// Creates a new EventThrottler with 30 second default interval pub fn new() -> Self { Self::with_duration(Duration::from_secs(30)) } /// Creates a new EventThrottler with custom interval pub fn with_duration(duration: Duration) -> Self { Self { last_report_time: Arc::new(Mutex::new(HashMap::new())), throttle_duration: duration, } } /// Checks if enough time has elapsed since the last report for this item pub fn should_report(&self, item_id: &str) -> bool { let last_times = self.last_report_time.lock_safe(); if let Some(last_time) = last_times.get(item_id) { let elapsed = last_time.elapsed(); if elapsed < self.throttle_duration { log::debug!( "[EventThrottler] Skipping report for {}, last reported {:.1}s ago (threshold: {}s)", item_id, elapsed.as_secs_f64(), self.throttle_duration.as_secs() ); return false; } } true } /// Marks the item as reported at the current time pub fn mark_reported(&self, item_id: &str) { let mut last_times = self.last_report_time.lock_safe(); last_times.insert(item_id.to_string(), Instant::now()); log::debug!( "[EventThrottler] Marked {} as reported at {:?}", item_id, Instant::now() ); } /// Clears all tracked report times pub fn clear(&self) { let mut last_times = self.last_report_time.lock_safe(); last_times.clear(); log::debug!("[EventThrottler] Cleared all tracked report times"); } /// Removes a specific item from tracking pub fn clear_item(&self, item_id: &str) { let mut last_times = self.last_report_time.lock_safe(); last_times.remove(item_id); log::debug!("[EventThrottler] Cleared tracking for {}", item_id); } } impl Default for EventThrottler { fn default() -> Self { Self::new() } } impl Clone for EventThrottler { fn clone(&self) -> Self { Self { last_report_time: Arc::clone(&self.last_report_time), throttle_duration: self.throttle_duration, } } } #[cfg(test)] mod tests { use super::*; use std::thread; #[test] fn test_throttler_allows_first_report() { let throttler = EventThrottler::new(); assert!(throttler.should_report("item1")); } #[test] fn test_throttler_blocks_immediate_second_report() { let throttler = EventThrottler::new(); assert!(throttler.should_report("item1")); throttler.mark_reported("item1"); assert!(!throttler.should_report("item1")); } #[test] fn test_throttler_allows_report_after_duration() { let throttler = EventThrottler::with_duration(Duration::from_millis(100)); assert!(throttler.should_report("item1")); throttler.mark_reported("item1"); assert!(!throttler.should_report("item1")); thread::sleep(Duration::from_millis(150)); assert!(throttler.should_report("item1")); } #[test] fn test_throttler_handles_multiple_items() { let throttler = EventThrottler::new(); assert!(throttler.should_report("item1")); throttler.mark_reported("item1"); assert!(throttler.should_report("item2")); throttler.mark_reported("item2"); assert!(!throttler.should_report("item1")); assert!(!throttler.should_report("item2")); } #[test] fn test_throttler_clear() { let throttler = EventThrottler::new(); throttler.mark_reported("item1"); assert!(!throttler.should_report("item1")); throttler.clear(); assert!(throttler.should_report("item1")); } #[test] fn test_throttler_clear_item() { let throttler = EventThrottler::new(); throttler.mark_reported("item1"); throttler.mark_reported("item2"); assert!(!throttler.should_report("item1")); assert!(!throttler.should_report("item2")); throttler.clear_item("item1"); assert!(throttler.should_report("item1")); assert!(!throttler.should_report("item2")); } #[test] fn test_throttler_clone_shares_state() { let throttler1 = EventThrottler::new(); throttler1.mark_reported("item1"); let throttler2 = throttler1.clone(); assert!(!throttler2.should_report("item1")); throttler2.mark_reported("item2"); assert!(!throttler1.should_report("item2")); } }