🎉 initial commit
commit
8a2863d3a3
@ -0,0 +1,2 @@
|
||||
/target
|
||||
/Cargo.lock
|
||||
@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "treacle"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
@ -0,0 +1,196 @@
|
||||
use std::{
|
||||
sync::{Arc, Mutex, Condvar, mpsc},
|
||||
time::Duration,
|
||||
thread::{self, JoinHandle}, io,
|
||||
};
|
||||
|
||||
/// A debouncer for deduplicating groups of events which occur at a similar time. Upon receiving an
|
||||
/// event (via `DebouncerController::notify_dirty`), the debouncer waits for a specified duration,
|
||||
/// during which time any additional events will be considered part of the same group. Once it has
|
||||
/// finished waiting, it will emit a single event via a `mpsc` channel.
|
||||
pub struct Debouncer<T> {
|
||||
thread: JoinHandle<()>,
|
||||
controller: Arc<DebouncerController<T>>,
|
||||
}
|
||||
|
||||
impl<T> Debouncer<T>
|
||||
where
|
||||
T: Send + 'static,
|
||||
{
|
||||
pub fn start_new(debounce_time: Duration) -> Result<(Self, mpsc::Receiver<T>), io::Error> {
|
||||
let controller = Arc::new(DebouncerController {
|
||||
state: Mutex::new(DebouncerState::new()),
|
||||
main_cvar: Condvar::new(),
|
||||
sleep_cvar: Condvar::new(),
|
||||
});
|
||||
|
||||
let (tx, rx) = mpsc::channel::<T>();
|
||||
|
||||
let thread = thread::Builder::new().spawn({
|
||||
let debounce_state = controller.clone();
|
||||
move || debounce_thread(debounce_state, tx, debounce_time)
|
||||
})?;
|
||||
|
||||
Ok((Self { thread, controller }, rx))
|
||||
}
|
||||
|
||||
pub fn controller(&self) -> &Arc<DebouncerController<T>> {
|
||||
&self.controller
|
||||
}
|
||||
|
||||
pub fn close(self) -> thread::Result<()> {
|
||||
self.controller().notify_shutdown();
|
||||
self.thread.join()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DebouncerController<T> {
|
||||
state: Mutex<DebouncerState<T>>,
|
||||
/// Condvar for notifying the debouncer that it has become dirty or that it should shutdown.
|
||||
main_cvar: Condvar,
|
||||
/// Condvar for notifying the debouncer that it should cancel sleeping and shutdown.
|
||||
sleep_cvar: Condvar,
|
||||
}
|
||||
|
||||
impl<T> DebouncerController<T> {
|
||||
/// Notify the debouncer that an event has occurred that should be debounced.
|
||||
///
|
||||
/// The event data is represented by an element of `E`, and the function `f` specifies how to
|
||||
/// fold this event into an accumulator of type `T`.
|
||||
///
|
||||
/// For example, to collect events into a list, the accumulator could be a `Vec<E>` and the
|
||||
/// fold function could be:
|
||||
/// ```no_run
|
||||
/// |acc, e| {
|
||||
/// let mut acc = acc.unwrap_or_default();
|
||||
/// acc.push(e);
|
||||
/// acc
|
||||
/// }
|
||||
/// ```
|
||||
pub fn notify_event<E, F>(&self, event_data: E, f: F)
|
||||
where
|
||||
F: FnOnce(Option<T>, E) -> T,
|
||||
{
|
||||
let mut guard = self.state.lock().unwrap();
|
||||
guard.fold(event_data, f);
|
||||
drop(guard);
|
||||
self.main_cvar.notify_one();
|
||||
}
|
||||
|
||||
fn notify_shutdown(&self) {
|
||||
let mut guard = self.state.lock().unwrap();
|
||||
guard.set_shutdown();
|
||||
drop(guard);
|
||||
self.main_cvar.notify_one();
|
||||
self.sleep_cvar.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DebouncerController<Vec<T>> {
|
||||
pub fn notify_event_push(&self, event_data: T) {
|
||||
self.notify_event(event_data, |acc, event_data| {
|
||||
let mut acc = acc.unwrap_or_default();
|
||||
acc.push(event_data);
|
||||
acc
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
struct DebouncerState<T> {
|
||||
/// The debounced data we have received so far.
|
||||
acc: Option<T>,
|
||||
/// Whether or not we should shutdown the debouncer thread.
|
||||
shutdown: bool,
|
||||
}
|
||||
|
||||
impl<T> DebouncerState<T> {
|
||||
fn new() -> Self {
|
||||
Self { acc: None, shutdown: false }
|
||||
}
|
||||
|
||||
fn get_event(&self) -> Option<DebouncerEvent> {
|
||||
if self.shutdown {
|
||||
Some(DebouncerEvent::Shutdown)
|
||||
} else if self.acc.is_some() {
|
||||
Some(DebouncerEvent::Dirty)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn should_shutdown(&self) -> bool {
|
||||
self.shutdown
|
||||
}
|
||||
|
||||
fn fold<E, F>(&mut self, event_data: E, f: F)
|
||||
where
|
||||
F: FnOnce(Option<T>, E) -> T,
|
||||
{
|
||||
let acc = self.acc.take();
|
||||
self.acc = Some(f(acc, event_data));
|
||||
}
|
||||
|
||||
fn swap_acc(&mut self) -> Option<T> {
|
||||
self.acc.take()
|
||||
}
|
||||
|
||||
fn set_shutdown(&mut self) {
|
||||
self.shutdown = true;
|
||||
}
|
||||
}
|
||||
|
||||
enum DebouncerEvent {
|
||||
/// The debouncer has been told to shut down.
|
||||
Shutdown,
|
||||
/// The accumulator has been changed.
|
||||
Dirty,
|
||||
}
|
||||
|
||||
fn debounce_thread<T>(
|
||||
debouncer: Arc<DebouncerController<T>>,
|
||||
tx: mpsc::Sender<T>,
|
||||
debounce_time: Duration
|
||||
) {
|
||||
let mut guard = debouncer.state.lock().unwrap();
|
||||
|
||||
'debounce: loop {
|
||||
let event = 'event: loop {
|
||||
// Check if either `acc` or `shutdown` is set.
|
||||
if let Some(event) = guard.get_event() {
|
||||
break 'event event;
|
||||
}
|
||||
|
||||
// If neither `acc` nor `shutdown` have been set, wait for the condvar to be notified
|
||||
// then check again. We need to check in a loop because `Condvar` allows spurious
|
||||
// wakeups.
|
||||
guard = debouncer.main_cvar.wait(guard).unwrap();
|
||||
};
|
||||
|
||||
match event {
|
||||
DebouncerEvent::Shutdown => break 'debounce,
|
||||
|
||||
DebouncerEvent::Dirty => {
|
||||
// Wait for the debounce time, or until the `sleep_cvar` is notified and the
|
||||
// `shutdown` boolean is set. Note that checking `shutdown` here is necessary
|
||||
// because `Condvar` allows spurious wakeups.
|
||||
//
|
||||
// By waiting for a condvar, we are temporarily releasing the mutex. This allows
|
||||
// the other threads to set `acc` while we are waiting.
|
||||
(guard, _) = debouncer.sleep_cvar
|
||||
.wait_timeout_while(guard, debounce_time, |state| {
|
||||
!state.should_shutdown()
|
||||
}).unwrap();
|
||||
|
||||
if guard.should_shutdown() {
|
||||
break 'debounce;
|
||||
}
|
||||
|
||||
if let Some(acc) = guard.swap_acc() {
|
||||
if tx.send(acc).is_err() {
|
||||
break 'debounce
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue