use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Condvar, Mutex};
use std::thread;
use std::time::{Duration, Instant};
pub struct Debounced<F>
where
F: Fn() + Send + 'static,
{
func: Arc<Mutex<F>>,
delay: Duration,
deadline: Arc<(Mutex<Option<Instant>>, Condvar)>,
started: Arc<AtomicBool>,
shutdown: Arc<AtomicBool>,
worker: Arc<Mutex<Option<thread::JoinHandle<()>>>>,
}
impl<F> Debounced<F>
where
F: Fn() + Send + 'static,
{
pub fn call(&self) {
{
let (lock, cvar) = &*self.deadline;
let mut dl = lock.lock().unwrap();
*dl = Some(Instant::now() + self.delay);
cvar.notify_one();
}
if !self.started.swap(true, Ordering::SeqCst) {
let func = Arc::clone(&self.func);
let deadline = Arc::clone(&self.deadline);
let shutdown = Arc::clone(&self.shutdown);
let worker_holder = Arc::clone(&self.worker);
let handle = thread::spawn(move || loop {
let (lock, cvar) = &*deadline;
let mut dl = lock.lock().unwrap();
while dl.is_none() && !shutdown.load(Ordering::SeqCst) {
dl = cvar.wait(dl).unwrap();
}
if shutdown.load(Ordering::SeqCst) && dl.is_none() {
break;
}
while let Some(target) = *dl {
let now = Instant::now();
if now >= target {
break;
}
let dur = target.saturating_duration_since(now);
let (new_dl, _timeout_res) = cvar.wait_timeout(dl, dur).unwrap();
dl = new_dl;
if shutdown.load(Ordering::SeqCst) && dl.is_none() {
break;
}
}
if shutdown.load(Ordering::SeqCst) && dl.is_none() {
break;
}
*dl = None;
drop(dl);
let f = func.lock().unwrap();
(*f)();
});
*worker_holder.lock().unwrap() = Some(handle);
}
}
}
impl<F> Debounced<F>
where
F: Fn() + Send + 'static,
{
pub fn stop(self) { std::mem::drop(self); }
}
impl<F> Drop for Debounced<F>
where
F: Fn() + Send + 'static,
{
fn drop(&mut self) {
if self.started.load(Ordering::SeqCst) {
self.shutdown.store(true, Ordering::SeqCst);
let (lock, cvar) = &*self.deadline;
let mut guard = lock.lock().unwrap();
*guard = None;
cvar.notify_all();
drop(guard);
if let Some(handle) = self.worker.lock().unwrap().take() {
let _ = handle.join();
}
}
}
}
pub fn debounce<F>(func: F, delay: Duration) -> Debounced<F>
where
F: Fn() + Send + 'static,
{
Debounced {
func: Arc::new(Mutex::new(func)),
delay,
deadline: Arc::new((Mutex::new(None), Condvar::new())),
started: Arc::new(AtomicBool::new(false)),
shutdown: Arc::new(AtomicBool::new(false)),
worker: Arc::new(Mutex::new(None)),
}
}