shared_framework/utils/
time_debouncers.rs1use std::sync::{Arc, Mutex};
13use std::time::Duration;
14use tokio::task::JoinHandle;
15
16pub trait Debouncer: Send + Sync {
18 fn trigger(&self);
20 fn cancel_pending(&self) -> bool;
23}
24
25pub trait ValueDebouncer<T>: Send + Sync {
27 fn trigger(&self, value: T);
29 fn cancel_pending(&self) -> bool;
31}
32
33pub struct TimeDebouncers;
35
36impl TimeDebouncers {
37 pub fn trailing<F>(delay: Duration, action: F) -> Arc<TrailingDebouncer>
40 where
41 F: Fn() + Send + Sync + 'static,
42 {
43 Arc::new(TrailingDebouncer::new(delay, action))
44 }
45
46 pub fn leading<F>(window: Duration, action: F) -> Arc<LeadingDebouncer>
49 where
50 F: Fn() + Send + Sync + 'static,
51 {
52 Arc::new(LeadingDebouncer::new(window, action))
53 }
54
55 pub fn trailing_value<T, F>(delay: Duration, action: F) -> Arc<TrailingValueDebouncer<T>>
58 where
59 T: Clone + Send + Sync + 'static,
60 F: Fn(T) + Send + Sync + 'static,
61 {
62 Arc::new(TrailingValueDebouncer::new(delay, action))
63 }
64}
65
66pub struct TrailingDebouncer {
70 delay: Duration,
71 action: Arc<dyn Fn() + Send + Sync>,
72 handle: Mutex<Option<JoinHandle<()>>>,
73}
74
75impl TrailingDebouncer {
76 fn new<F>(delay: Duration, action: F) -> Self
77 where
78 F: Fn() + Send + Sync + 'static,
79 {
80 assert!(!delay.is_zero(), "delay must be > 0");
81 Self {
82 delay,
83 action: Arc::new(action),
84 handle: Mutex::new(None),
85 }
86 }
87}
88
89impl Debouncer for TrailingDebouncer {
90 fn trigger(&self) {
91 let mut guard = self.handle.lock().unwrap();
92 if let Some(h) = guard.take() {
93 h.abort();
94 }
95 let action = Arc::clone(&self.action);
96 let delay = self.delay;
97 let handle = tokio::spawn(async move {
99 tokio::time::sleep(delay).await;
100 action();
101 });
102 *guard = Some(handle);
103 }
104
105 fn cancel_pending(&self) -> bool {
106 let mut guard = self.handle.lock().unwrap();
107 if let Some(h) = guard.take() {
108 h.abort();
109 true
110 } else {
111 false
112 }
113 }
114}
115
116pub struct LeadingDebouncer {
120 window: Duration,
121 action: Arc<dyn Fn() + Send + Sync>,
122 next_allowed: Mutex<std::time::Instant>,
123}
124
125impl LeadingDebouncer {
126 fn new<F>(window: Duration, action: F) -> Self
127 where
128 F: Fn() + Send + Sync + 'static,
129 {
130 assert!(!window.is_zero(), "window must be > 0");
131 Self {
132 window,
133 action: Arc::new(action),
134 next_allowed: Mutex::new(std::time::Instant::now() - window),
135 }
136 }
137}
138
139impl Debouncer for LeadingDebouncer {
140 fn trigger(&self) {
141 let now = std::time::Instant::now();
142 let mut guard = self.next_allowed.lock().unwrap();
143 if now < *guard {
144 return;
145 }
146 *guard = now + self.window;
147 drop(guard);
148 (self.action)();
149 }
150
151 fn cancel_pending(&self) -> bool {
152 false
153 }
154}
155
156pub struct TrailingValueDebouncer<T> {
160 delay: Duration,
161 action: Arc<dyn Fn(T) + Send + Sync>,
162 latest: Arc<Mutex<Option<T>>>,
163 handle: Mutex<Option<JoinHandle<()>>>,
164}
165
166impl<T> TrailingValueDebouncer<T>
167where
168 T: Clone + Send + Sync + 'static,
169{
170 fn new<F>(delay: Duration, action: F) -> Self
171 where
172 F: Fn(T) + Send + Sync + 'static,
173 {
174 assert!(!delay.is_zero(), "delay must be > 0");
175 Self {
176 delay,
177 action: Arc::new(action),
178 latest: Arc::new(Mutex::new(None)),
179 handle: Mutex::new(None),
180 }
181 }
182}
183
184impl<T> ValueDebouncer<T> for TrailingValueDebouncer<T>
185where
186 T: Clone + Send + Sync + 'static,
187{
188 fn trigger(&self, value: T) {
189 *self.latest.lock().unwrap() = Some(value);
190 let mut guard = self.handle.lock().unwrap();
191 if let Some(h) = guard.take() {
192 h.abort();
193 }
194 let latest = Arc::clone(&self.latest);
195 let action = Arc::clone(&self.action);
196 let delay = self.delay;
197 let handle = tokio::spawn(async move {
198 tokio::time::sleep(delay).await;
199 let val = latest.lock().unwrap().take();
200 if let Some(v) = val {
201 action(v);
202 }
203 });
204 *guard = Some(handle);
205 }
206
207 fn cancel_pending(&self) -> bool {
208 let mut guard = self.handle.lock().unwrap();
209 let had = guard.is_some();
210 if let Some(h) = guard.take() {
211 h.abort();
212 }
213 *self.latest.lock().unwrap() = None;
215 had
216 }
217}