wip threadless debouncer
parent
fe87e12256
commit
8b70bbdf15
@ -0,0 +1,234 @@
|
||||
use std::{time::{Instant, Duration}, collections::VecDeque, marker::PhantomData, sync::{Mutex, Condvar, MutexGuard, Arc}};
|
||||
|
||||
pub struct DebouncerTx<RawEvent, DebouncedEvent, FoldFn> {
|
||||
debouncer: Arc<Debouncer<DebouncedEvent>>,
|
||||
fold: FoldFn,
|
||||
_raw_event_marker: PhantomData<RawEvent>,
|
||||
}
|
||||
|
||||
impl<RawEvent, DebouncedEvent, FoldFn> DebouncerTx<RawEvent, DebouncedEvent, FoldFn>
|
||||
where
|
||||
FoldFn: Fn(Option<DebouncedEvent>, RawEvent) -> DebouncedEvent,
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
pub struct DebouncerRx<DebouncedEvent> {
|
||||
debouncer: Arc<Debouncer<DebouncedEvent>>
|
||||
}
|
||||
|
||||
impl<DebouncedEvent> DebouncerRx<DebouncedEvent> {
|
||||
|
||||
}
|
||||
|
||||
pub struct DebouncerClosedError;
|
||||
|
||||
struct Debouncer<T> {
|
||||
state: Mutex<DebouncerState<T>>,
|
||||
debounce_time: Duration,
|
||||
queue_wait_cvar: Condvar,
|
||||
event_ready_wait_cvar: Condvar,
|
||||
}
|
||||
|
||||
impl<T> Debouncer<T> {
|
||||
// TODO: error on closed channel?
|
||||
fn push<R, F>(&self, raw_event: R, fold: F)
|
||||
where
|
||||
F: Fn(Option<T>, R) -> T,
|
||||
{
|
||||
let now = Instant::now();
|
||||
|
||||
let push_outcome = {
|
||||
let mut state_guard = self.state.lock().unwrap();
|
||||
state_guard.push_latest(raw_event, fold, now, self.debounce_time)
|
||||
};
|
||||
|
||||
if matches!(push_outcome, PushOutcome::NewAcc) {
|
||||
// If we pushed a new event accumulator, wake up one rx thread which was waiting for
|
||||
// the queue to be non-empty. Don't wake up all the rx threads because only one of
|
||||
// them can consume the acc from the queue; therefore, we should wait until another
|
||||
// acc is added to the queue before waking up another thread.
|
||||
self.queue_wait_cvar.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
fn pop(&self) -> Result<T, DebouncerClosedError> {
|
||||
enum PopOutcome<T> {
|
||||
Event(T),
|
||||
Shutdown,
|
||||
}
|
||||
|
||||
let mut state_guard = self.state.lock().unwrap();
|
||||
|
||||
if state_guard.shutdown {
|
||||
return Err(DebouncerClosedError);
|
||||
}
|
||||
|
||||
let event = loop {
|
||||
// If the queue is empty, wait for a tx thread to notify us that a new acc has been
|
||||
// added to the queue.
|
||||
if state_guard.is_empty() {
|
||||
state_guard = self.queue_wait_cvar.wait(state_guard).unwrap();
|
||||
|
||||
// We may have been unparked because someone wants to shut down the debouncer, so
|
||||
// check the shutdown flag and return if it is set.
|
||||
if state_guard.shutdown {
|
||||
return Err(DebouncerClosedError);
|
||||
}
|
||||
}
|
||||
|
||||
let event = loop {
|
||||
let Some(event) = state_guard.peek_earliest() else {
|
||||
break None;
|
||||
};
|
||||
|
||||
let now = Instant::now();
|
||||
|
||||
match event.ready_time.checked_duration_since(now) {
|
||||
Some(wait_time) => {
|
||||
// Wait the amount of time between now and the `ready_time` of the event.
|
||||
// This is done using a condvar so the sleep can be interrupted if someone
|
||||
// wants to shut down the debouncer.
|
||||
(state_guard, _) = self.event_ready_wait_cvar
|
||||
.wait_timeout(state_guard, wait_time)
|
||||
.unwrap();
|
||||
|
||||
// Again, we may have been unparked because someone wants to shut down the
|
||||
// debouncer, so check the shutdown flag and return if it is set.
|
||||
if state_guard.shutdown {
|
||||
return Err(DebouncerClosedError);
|
||||
}
|
||||
|
||||
continue;
|
||||
},
|
||||
|
||||
None => {
|
||||
break state_guard.pop_oldest_acc();
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
let Some(event) = event else {
|
||||
continue;
|
||||
};
|
||||
|
||||
break event;
|
||||
};
|
||||
|
||||
// FIXME: replace above with this (set outcome rather than early return)
|
||||
let result = 'result: {
|
||||
if state_guard.shutdown {
|
||||
break 'result PopOutcome::Shutdown;
|
||||
}
|
||||
|
||||
// TODO
|
||||
todo!()
|
||||
};
|
||||
|
||||
match result {
|
||||
PopOutcome::Event(event) => Ok(event),
|
||||
PopOutcome::Shutdown => {
|
||||
state_guard
|
||||
.pop_oldest_acc_discard_none()
|
||||
.ok_or(DebouncerClosedError)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: non-blocking `try_pop`?
|
||||
}
|
||||
|
||||
struct DebouncerState<T> {
|
||||
event_queue: VecDeque<EventAcc<T>>,
|
||||
shutdown: bool,
|
||||
}
|
||||
|
||||
impl<T> DebouncerState<T> {
|
||||
fn is_empty(&self) -> bool {
|
||||
self.event_queue.is_empty()
|
||||
}
|
||||
|
||||
fn peek_oldest(&self) -> Option<&EventAcc<T>> {
|
||||
self.event_queue.front()
|
||||
}
|
||||
|
||||
fn pop_oldest(&mut self) -> Option<EventAcc<T>> {
|
||||
self.event_queue.pop_front()
|
||||
}
|
||||
|
||||
fn pop_oldest_acc(&mut self) -> Option<T> {
|
||||
self.pop_oldest().and_then(EventAcc::into_acc)
|
||||
}
|
||||
|
||||
fn pop_oldest_acc_discard_none(&mut self) -> Option<T> {
|
||||
while let Some(event) = self.pop_oldest() {
|
||||
if let Some(acc) = event.acc {
|
||||
return Some(acc);
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
fn push_latest<R, F>(&mut self, raw_event: R, f: F, now: Instant, debounce_time: Duration)
|
||||
-> PushOutcome
|
||||
where
|
||||
F: FnOnce(Option<T>, R) -> T,
|
||||
{
|
||||
match self.event_queue
|
||||
.back_mut()
|
||||
.and_then(|event| (event.ready_time > now).then_some(event))
|
||||
{
|
||||
Some(event) => {
|
||||
event.fold(raw_event, f);
|
||||
PushOutcome::NoNewAcc
|
||||
},
|
||||
|
||||
None => {
|
||||
let ready_time = now + debounce_time;
|
||||
let event = EventAcc::new_from_fold(raw_event, f, ready_time);
|
||||
self.event_queue.push_back(event);
|
||||
PushOutcome::NewAcc
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum PushOutcome {
|
||||
NewAcc,
|
||||
NoNewAcc,
|
||||
}
|
||||
|
||||
struct EventAcc<T> {
|
||||
// The accumulator is stored as an `Option` because it is temporarily set to `None` during
|
||||
// `EventAcc::fold`, so that the old accumulator can be moved into the user-provided fold
|
||||
// function rather than borrowing it. `MaybeUninit` cannot be used for this because the
|
||||
// user-provided fold function may panic, which would leave the accumulator in an uninitialised
|
||||
// state, potentially causing UB later.
|
||||
acc: Option<T>,
|
||||
ready_time: Instant,
|
||||
}
|
||||
|
||||
impl<T> EventAcc<T> {
|
||||
fn new_from_fold<R, F>(raw_event: R, f: F, ready_time: Instant) -> Self
|
||||
where
|
||||
F: FnOnce(Option<T>, R) -> T,
|
||||
{
|
||||
Self {
|
||||
acc: Some(f(None, raw_event)),
|
||||
ready_time,
|
||||
}
|
||||
}
|
||||
|
||||
fn fold<R, F>(&mut self, raw_event: R, f: F)
|
||||
where
|
||||
F: FnOnce(Option<T>, R) -> T,
|
||||
{
|
||||
let acc = self.acc.take();
|
||||
self.acc = Some(f(acc, raw_event));
|
||||
}
|
||||
|
||||
fn into_acc(self) -> Option<T> {
|
||||
self.acc
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue