use super::clock::Clock;
#[cfg(feature = "tokio")]
use crate::Cmd;
use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct Debouncer {
pending_until: Option<Instant>,
}
impl Default for Debouncer {
fn default() -> Self {
Self::new()
}
}
impl Debouncer {
pub fn new() -> Self {
Self {
pending_until: None,
}
}
#[cfg(feature = "tokio")]
pub fn trigger<Msg>(&mut self, delay: Duration, msg: Msg) -> Cmd<Msg>
where
Msg: Clone + Send + 'static,
{
let fire_at = Instant::now() + delay;
self.pending_until = Some(fire_at);
Cmd::delay(delay, msg)
}
pub fn mark_trigger(&mut self, delay: Duration) {
let fire_at = Instant::now() + delay;
self.pending_until = Some(fire_at);
}
pub fn should_fire(&mut self) -> bool {
match self.pending_until {
Some(until) if Instant::now() >= until => {
self.pending_until = None;
true
}
Some(_) => false, None => false, }
}
pub fn is_pending(&self) -> bool {
self.pending_until.is_some()
}
pub fn cancel(&mut self) {
self.pending_until = None;
}
pub fn reset(&mut self) {
self.pending_until = None;
}
}
#[derive(Debug, Clone)]
pub struct DebouncerWithClock<C: Clock> {
clock: C,
pending_until: Option<Duration>,
}
impl<C: Clock> DebouncerWithClock<C> {
pub fn new(clock: C) -> Self {
Self {
clock,
pending_until: None,
}
}
#[cfg(feature = "tokio")]
pub fn trigger<Msg>(&mut self, delay: Duration, msg: Msg) -> Cmd<Msg>
where
Msg: Clone + Send + 'static,
{
let fire_at = self.clock.now() + delay;
self.pending_until = Some(fire_at);
Cmd::delay(delay, msg)
}
pub fn mark_trigger(&mut self, delay: Duration) {
let fire_at = self.clock.now() + delay;
self.pending_until = Some(fire_at);
}
pub fn should_fire(&mut self) -> bool {
match self.pending_until {
Some(until) if self.clock.now() >= until => {
self.pending_until = None;
true
}
Some(_) => false,
None => false,
}
}
pub fn is_pending(&self) -> bool {
self.pending_until.is_some()
}
pub fn cancel(&mut self) {
self.pending_until = None;
}
pub fn reset(&mut self) {
self.pending_until = None;
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
#[test]
#[cfg(feature = "tokio")]
fn test_debouncer_basic() {
let mut debouncer = Debouncer::new();
let _cmd = debouncer.trigger::<()>(Duration::from_millis(10), ());
assert!(debouncer.is_pending());
thread::sleep(Duration::from_millis(15));
assert!(debouncer.should_fire());
assert!(!debouncer.is_pending());
}
#[test]
fn test_debouncer_reset_on_trigger() {
use crate::testing::FakeClock;
let clock = FakeClock::new();
let mut debouncer = DebouncerWithClock::new(clock.clone());
debouncer.mark_trigger(Duration::from_millis(50));
clock.advance(Duration::from_millis(30));
assert!(!debouncer.should_fire());
debouncer.mark_trigger(Duration::from_millis(50));
clock.advance(Duration::from_millis(30));
assert!(!debouncer.should_fire());
clock.advance(Duration::from_millis(25));
assert!(debouncer.should_fire()); }
#[test]
#[cfg(feature = "tokio")]
fn test_debouncer_cancel() {
let mut debouncer = Debouncer::new();
let _cmd = debouncer.trigger::<()>(Duration::from_millis(10), ());
debouncer.cancel();
thread::sleep(Duration::from_millis(15));
assert!(!debouncer.should_fire()); }
#[test]
#[cfg(feature = "tokio")]
fn test_debouncer_double_fire_protection() {
let mut debouncer = Debouncer::new();
let _cmd = debouncer.trigger::<()>(Duration::from_millis(10), ());
thread::sleep(Duration::from_millis(15));
assert!(debouncer.should_fire()); assert!(!debouncer.should_fire()); }
#[test]
fn test_debouncer_mark_trigger_basic() {
let mut debouncer = Debouncer::new();
debouncer.mark_trigger(Duration::from_millis(10));
assert!(debouncer.is_pending());
thread::sleep(Duration::from_millis(15));
assert!(debouncer.should_fire());
assert!(!debouncer.is_pending());
}
#[test]
fn test_debouncer_cancel_without_tokio() {
let mut debouncer = Debouncer::new();
debouncer.mark_trigger(Duration::from_millis(10));
debouncer.cancel();
thread::sleep(Duration::from_millis(15));
assert!(!debouncer.should_fire()); }
}