use alloc::{boxed::Box, rc::Rc};
use core::{cell::RefCell, fmt::Debug, time::Duration};
use executor_core::{DefaultExecutor, LocalExecutor, Task};
use nami_core::watcher::Context;
use crate::{
Signal,
utils::sleep,
watcher::{WatcherManager, WatcherManagerGuard},
};
use nami_core::{SignalIdentity, observe::Origin};
pub struct Debounce<S, E>
where
S: Signal,
{
signal: S,
duration: Duration,
watchers: WatcherManager<S::Output>,
executor: E,
timer: Rc<RefCell<Option<Box<dyn Task<()>>>>>,
guard: Rc<RefCell<Option<S::Guard>>>,
}
impl<S, E> Debug for Debounce<S, E>
where
S: Signal + Debug,
E: Debug,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Debounce")
.field("signal", &self.signal)
.field("duration", &self.duration)
.field("watchers", &"<...>")
.field("executor", &self.executor)
.field("timer", &"<...>")
.field("guard", &"<...>")
.finish()
}
}
impl<S, E> Clone for Debounce<S, E>
where
S: Signal,
E: Clone,
{
fn clone(&self) -> Self {
Self {
signal: self.signal.clone(),
duration: self.duration,
watchers: self.watchers.clone(),
executor: self.executor.clone(),
timer: self.timer.clone(),
guard: self.guard.clone(),
}
}
}
impl<S, E> Debounce<S, E>
where
E: LocalExecutor + Clone + 'static,
S: Signal,
{
#[track_caller]
pub fn with_executor(signal: S, duration: Duration, executor: E) -> Self {
let timer = Rc::default();
let origin = Origin::capture::<Self>(SignalIdentity::from_rc(&timer));
Self {
signal,
watchers: WatcherManager::with_origin(origin),
duration,
executor,
timer,
guard: Rc::default(),
}
}
}
impl<S> Debounce<S, DefaultExecutor>
where
S: Signal,
{
pub fn new(signal: S, duration: Duration) -> Self
where
S: Signal,
S::Output: Clone + 'static,
{
Self::with_executor(signal, duration, executor_core::DefaultExecutor)
}
}
impl<S, E> Signal for Debounce<S, E>
where
S: Signal,
S::Output: Clone + 'static,
E: LocalExecutor + Clone + 'static,
{
type Output = S::Output;
type Guard = WatcherManagerGuard<S::Output>;
fn get(&self) -> Self::Output {
self.signal.get()
}
fn watch(&self, watcher: impl Fn(Context<Self::Output>) + 'static) -> Self::Guard {
let signal = self.signal.clone();
let watchers = self.watchers.clone();
let executor = self.executor.clone();
let timer = self.timer.clone();
let duration = self.duration;
let _signal_guard = self.guard.borrow_mut().get_or_insert_with(|| {
signal.watch(move |ctx| {
let _previous_task = timer.borrow_mut().take();
let watchers = watchers.clone();
let timer = timer.clone();
if watchers.is_empty() {
return;
}
let delayed_context = ctx;
let task = executor.spawn_local(async move {
sleep(duration).await;
if watchers.is_empty() {
return;
}
let context = delayed_context;
watchers.notify(&context);
});
*timer.borrow_mut() = Some(Box::new(task));
})
});
self.watchers.register_as_guard(watcher)
}
}