use crate::hooks::use_signal::{Signal, use_signal};
use std::time::{Duration, Instant};
pub fn use_debounce<T>(value: T, delay: Duration) -> T
where
T: Clone + PartialEq + Send + Sync + 'static,
{
let debounced = use_signal(|| value.clone());
let last_value = use_signal(|| value.clone());
let last_delay = use_signal(|| delay);
let generation = use_signal(|| 0u64);
if delay.is_zero() {
if last_value.get() != value {
last_value.set(value.clone());
}
if last_delay.get() != delay {
last_delay.set(delay);
}
if debounced.get() != value {
debounced.set(value);
}
return debounced.get();
}
let value_changed = last_value.get() != value;
let delay_changed = last_delay.get() != delay;
if value_changed {
last_value.set(value);
}
if delay_changed {
last_delay.set(delay);
}
if value_changed || delay_changed {
generation.update(|g| *g = g.wrapping_add(1));
let expected_generation = generation.get();
let generation_clone = generation.clone();
let last_value_clone = last_value.clone();
let debounced_clone = debounced.clone();
let wait = delay;
std::thread::spawn(move || {
std::thread::sleep(wait);
if generation_clone.get() == expected_generation {
let latest = last_value_clone.get();
if debounced_clone.get() != latest {
debounced_clone.set(latest);
}
}
});
}
debounced.get()
}
#[derive(Clone)]
pub struct DebounceHandle {
pending: Signal<bool>,
last_trigger: Signal<Instant>,
delay: Duration,
}
impl DebounceHandle {
pub fn trigger(&self) {
self.pending.set(true);
self.last_trigger.set(Instant::now());
}
pub fn is_ready(&self) -> bool {
self.pending.get() && self.last_trigger.get().elapsed() >= self.delay
}
pub fn reset(&self) {
self.pending.set(false);
}
pub fn is_pending(&self) -> bool {
self.pending.get()
}
}
pub fn use_debounce_handle(delay: Duration) -> DebounceHandle {
let pending = use_signal(|| false);
let last_trigger = use_signal(Instant::now);
DebounceHandle {
pending,
last_trigger,
delay,
}
}
pub fn use_throttle<T>(value: T, interval: Duration) -> T
where
T: Clone + Send + Sync + 'static,
{
let throttled = use_signal(|| value.clone());
let last_update = use_signal(Instant::now);
if last_update.get().elapsed() >= interval {
throttled.set(value);
last_update.set(Instant::now());
}
throttled.get()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hooks::context::{HookContext, with_hooks};
use std::sync::{Arc, RwLock};
#[test]
fn test_use_debounce_compiles() {
fn _test() {
let _debounced = use_debounce("test".to_string(), Duration::from_millis(300));
}
}
#[test]
fn test_use_throttle_compiles() {
fn _test() {
let _throttled = use_throttle(42, Duration::from_millis(100));
}
}
#[test]
fn test_debounce_handle_compiles() {
fn _test() {
let handle = use_debounce_handle(Duration::from_millis(300));
handle.trigger();
let _ = handle.is_ready();
handle.reset();
}
}
#[test]
fn test_use_debounce_updates_after_delay() {
let ctx = Arc::new(RwLock::new(HookContext::new()));
let first = with_hooks(ctx.clone(), || {
use_debounce("a".to_string(), Duration::from_millis(30))
});
assert_eq!(first, "a");
let second = with_hooks(ctx.clone(), || {
use_debounce("b".to_string(), Duration::from_millis(30))
});
assert_eq!(second, "a");
std::thread::sleep(Duration::from_millis(60));
let third = with_hooks(ctx.clone(), || {
use_debounce("b".to_string(), Duration::from_millis(30))
});
assert_eq!(third, "b");
}
#[test]
fn test_use_debounce_respects_updated_delay_for_pending_value() {
let ctx = Arc::new(RwLock::new(HookContext::new()));
let first = with_hooks(ctx.clone(), || {
use_debounce("a".to_string(), Duration::from_millis(100))
});
assert_eq!(first, "a");
let second = with_hooks(ctx.clone(), || {
use_debounce("b".to_string(), Duration::from_millis(100))
});
assert_eq!(second, "a");
let third = with_hooks(ctx.clone(), || {
use_debounce("b".to_string(), Duration::from_millis(10))
});
assert_eq!(third, "a");
std::thread::sleep(Duration::from_millis(30));
let fourth = with_hooks(ctx.clone(), || {
use_debounce("b".to_string(), Duration::from_millis(10))
});
assert_eq!(fourth, "b");
}
}