use std::sync::{Arc, Mutex};
use std::time::Duration;
use tokio::task::JoinHandle;
pub trait Debouncer: Send + Sync {
fn trigger(&self);
fn cancel_pending(&self) -> bool;
}
pub trait ValueDebouncer<T>: Send + Sync {
fn trigger(&self, value: T);
fn cancel_pending(&self) -> bool;
}
pub struct TimeDebouncers;
impl TimeDebouncers {
pub fn trailing<F>(delay: Duration, action: F) -> Arc<TrailingDebouncer>
where
F: Fn() + Send + Sync + 'static,
{
Arc::new(TrailingDebouncer::new(delay, action))
}
pub fn leading<F>(window: Duration, action: F) -> Arc<LeadingDebouncer>
where
F: Fn() + Send + Sync + 'static,
{
Arc::new(LeadingDebouncer::new(window, action))
}
pub fn trailing_value<T, F>(delay: Duration, action: F) -> Arc<TrailingValueDebouncer<T>>
where
T: Clone + Send + Sync + 'static,
F: Fn(T) + Send + Sync + 'static,
{
Arc::new(TrailingValueDebouncer::new(delay, action))
}
}
pub struct TrailingDebouncer {
delay: Duration,
action: Arc<dyn Fn() + Send + Sync>,
handle: Mutex<Option<JoinHandle<()>>>,
}
impl TrailingDebouncer {
fn new<F>(delay: Duration, action: F) -> Self
where
F: Fn() + Send + Sync + 'static,
{
assert!(!delay.is_zero(), "delay must be > 0");
Self {
delay,
action: Arc::new(action),
handle: Mutex::new(None),
}
}
}
impl Debouncer for TrailingDebouncer {
fn trigger(&self) {
let mut guard = self.handle.lock().unwrap();
if let Some(h) = guard.take() {
h.abort();
}
let action = Arc::clone(&self.action);
let delay = self.delay;
let handle = tokio::spawn(async move {
tokio::time::sleep(delay).await;
action();
});
*guard = Some(handle);
}
fn cancel_pending(&self) -> bool {
let mut guard = self.handle.lock().unwrap();
if let Some(h) = guard.take() {
h.abort();
true
} else {
false
}
}
}
pub struct LeadingDebouncer {
window: Duration,
action: Arc<dyn Fn() + Send + Sync>,
next_allowed: Mutex<std::time::Instant>,
}
impl LeadingDebouncer {
fn new<F>(window: Duration, action: F) -> Self
where
F: Fn() + Send + Sync + 'static,
{
assert!(!window.is_zero(), "window must be > 0");
Self {
window,
action: Arc::new(action),
next_allowed: Mutex::new(std::time::Instant::now() - window),
}
}
}
impl Debouncer for LeadingDebouncer {
fn trigger(&self) {
let now = std::time::Instant::now();
let mut guard = self.next_allowed.lock().unwrap();
if now < *guard {
return;
}
*guard = now + self.window;
drop(guard);
(self.action)();
}
fn cancel_pending(&self) -> bool {
false
}
}
pub struct TrailingValueDebouncer<T> {
delay: Duration,
action: Arc<dyn Fn(T) + Send + Sync>,
latest: Arc<Mutex<Option<T>>>,
handle: Mutex<Option<JoinHandle<()>>>,
}
impl<T> TrailingValueDebouncer<T>
where
T: Clone + Send + Sync + 'static,
{
fn new<F>(delay: Duration, action: F) -> Self
where
F: Fn(T) + Send + Sync + 'static,
{
assert!(!delay.is_zero(), "delay must be > 0");
Self {
delay,
action: Arc::new(action),
latest: Arc::new(Mutex::new(None)),
handle: Mutex::new(None),
}
}
}
impl<T> ValueDebouncer<T> for TrailingValueDebouncer<T>
where
T: Clone + Send + Sync + 'static,
{
fn trigger(&self, value: T) {
*self.latest.lock().unwrap() = Some(value);
let mut guard = self.handle.lock().unwrap();
if let Some(h) = guard.take() {
h.abort();
}
let latest = Arc::clone(&self.latest);
let action = Arc::clone(&self.action);
let delay = self.delay;
let handle = tokio::spawn(async move {
tokio::time::sleep(delay).await;
let val = latest.lock().unwrap().take();
if let Some(v) = val {
action(v);
}
});
*guard = Some(handle);
}
fn cancel_pending(&self) -> bool {
let mut guard = self.handle.lock().unwrap();
let had = guard.is_some();
if let Some(h) = guard.take() {
h.abort();
}
*self.latest.lock().unwrap() = None;
had
}
}