eazygit 0.5.1

A fast TUI for Git with staging, conflicts, rebase, and palette-first UX
Documentation
//! Throttle and debounce utilities for rate limiting.
//!
//! Provides mechanisms to limit how often expensive operations run,
//! improving performance for high-frequency events.

use std::time::{Duration, Instant};

/// Throttle - ensures an action runs at most once per interval.
pub struct Throttle {
    interval: Duration,
    last_run: Option<Instant>,
}

impl Throttle {
    /// Create a new throttle with the given interval.
    pub fn new(interval: Duration) -> Self {
        Self {
            interval,
            last_run: None,
        }
    }
    
    /// Create a throttle from milliseconds.
    pub fn from_millis(ms: u64) -> Self {
        Self::new(Duration::from_millis(ms))
    }
    
    /// Check if the action should run now.
    pub fn should_run(&mut self) -> bool {
        let now = Instant::now();
        if let Some(last) = self.last_run {
            if now.duration_since(last) < self.interval {
                return false;
            }
        }
        self.last_run = Some(now);
        true
    }
    
    /// Run a function if throttle allows.
    pub fn run<F, T>(&mut self, f: F) -> Option<T>
    where
        F: FnOnce() -> T,
    {
        if self.should_run() {
            Some(f())
        } else {
            None
        }
    }
    
    /// Reset the throttle.
    pub fn reset(&mut self) {
        self.last_run = None;
    }
}

/// Debounce - delays action until no calls for a duration.
pub struct Debounce {
    delay: Duration,
    last_call: Option<Instant>,
    pending: bool,
}

impl Debounce {
    /// Create a new debounce with the given delay.
    pub fn new(delay: Duration) -> Self {
        Self {
            delay,
            last_call: None,
            pending: false,
        }
    }
    
    /// Create a debounce from milliseconds.
    pub fn from_millis(ms: u64) -> Self {
        Self::new(Duration::from_millis(ms))
    }
    
    /// Record a call (resets the timer).
    pub fn call(&mut self) {
        self.last_call = Some(Instant::now());
        self.pending = true;
    }
    
    /// Check if the debounced action is ready to fire.
    pub fn is_ready(&self) -> bool {
        if !self.pending {
            return false;
        }
        if let Some(last) = self.last_call {
            Instant::now().duration_since(last) >= self.delay
        } else {
            false
        }
    }
    
    /// Fire if ready, returns whether it fired.
    pub fn fire_if_ready(&mut self) -> bool {
        if self.is_ready() {
            self.pending = false;
            true
        } else {
            false
        }
    }
    
    /// Reset the debounce.
    pub fn reset(&mut self) {
        self.last_call = None;
        self.pending = false;
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::thread;
    
    #[test]
    fn test_throttle() {
        let mut throttle = Throttle::from_millis(50);
        
        assert!(throttle.should_run()); // First call always runs
        assert!(!throttle.should_run()); // Too soon
        
        thread::sleep(Duration::from_millis(60));
        assert!(throttle.should_run()); // Enough time passed
    }
    
    #[test]
    fn test_debounce() {
        let mut debounce = Debounce::from_millis(50);
        
        debounce.call();
        assert!(!debounce.is_ready()); // Not enough time
        
        thread::sleep(Duration::from_millis(60));
        assert!(debounce.is_ready()); // Ready now
        assert!(debounce.fire_if_ready());
        assert!(!debounce.is_ready()); // Already fired
    }
}