use std::{
sync::{
Arc,
atomic::{AtomicU64, Ordering},
},
time::Duration,
};
use crate::executor::BackgroundExecutor;
pub struct Debouncer {
generation: Arc<AtomicU64>,
}
impl Debouncer {
pub fn new() -> Self {
Self {
generation: Arc::new(AtomicU64::new(0)),
}
}
pub fn debounce(
&self,
executor: &BackgroundExecutor,
duration: Duration,
callback: impl FnOnce() + Send + 'static,
) {
let generation = self.generation.fetch_add(1, Ordering::SeqCst) + 1;
let current = self.generation.clone();
let timer = executor.clone();
executor
.spawn(async move {
timer.timer(duration).await;
if current.load(Ordering::SeqCst) == generation {
callback();
}
})
.detach();
}
}
impl Default for Debouncer {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::platform::TestDispatcher;
#[test]
fn only_last_request_runs() {
let dispatcher = TestDispatcher::new(0);
let executor = BackgroundExecutor::new(std::sync::Arc::new(dispatcher.clone()));
let debouncer = Debouncer::new();
let hits = Arc::new(std::sync::atomic::AtomicUsize::new(0));
for _ in 0..3 {
let hits = hits.clone();
debouncer.debounce(&executor, Duration::from_millis(50), move || {
hits.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
});
}
dispatcher.advance_clock(Duration::from_millis(200));
dispatcher.run_until_parked();
assert_eq!(hits.load(std::sync::atomic::Ordering::SeqCst), 1);
}
#[test]
fn stale_request_is_cancelled() {
let dispatcher = TestDispatcher::new(0);
let executor = BackgroundExecutor::new(std::sync::Arc::new(dispatcher.clone()));
let debouncer = Debouncer::new();
let hits = Arc::new(std::sync::atomic::AtomicUsize::new(0));
let hits_first = hits.clone();
debouncer.debounce(&executor, Duration::from_millis(50), move || {
hits_first.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
});
dispatcher.advance_clock(Duration::from_millis(200));
dispatcher.run_until_parked();
let hits_second = hits.clone();
debouncer.debounce(&executor, Duration::from_millis(50), move || {
hits_second.fetch_add(10, std::sync::atomic::Ordering::SeqCst);
});
dispatcher.advance_clock(Duration::from_millis(200));
dispatcher.run_until_parked();
assert_eq!(hits.load(std::sync::atomic::Ordering::SeqCst), 11);
}
}