1use std::ops::Deref;
14use tokio::sync::watch;
15
16#[derive(Debug)]
18pub struct Watchable<T> {
19 sender: watch::Sender<T>,
20}
21
22impl<T: Clone + Send + Sync + 'static> Watchable<T> {
23 pub fn new(value: T) -> Self {
25 let (sender, _) = watch::channel(value);
26 Self { sender }
27 }
28
29 pub fn get(&self) -> T {
31 self.sender.borrow().clone()
32 }
33
34 pub fn set(&self, value: T) {
36 self.sender.send_modify(|v| *v = value);
39 }
40
41 pub fn modify<F>(&self, f: F)
43 where
44 F: FnOnce(&mut T),
45 {
46 self.sender.send_modify(f);
47 }
48
49 pub fn watch(&self) -> Watcher<T> {
51 Watcher {
52 receiver: self.sender.subscribe(),
53 }
54 }
55
56 pub fn sender(&self) -> &watch::Sender<T> {
58 &self.sender
59 }
60
61 pub fn receiver_count(&self) -> usize {
63 self.sender.receiver_count()
64 }
65}
66
67impl<T: Clone + Default + Send + Sync + 'static> Default for Watchable<T> {
68 fn default() -> Self {
69 Self::new(T::default())
70 }
71}
72
73#[derive(Debug)]
75pub struct Watcher<T> {
76 receiver: watch::Receiver<T>,
77}
78
79impl<T: Clone> Watcher<T> {
80 pub async fn changed(&mut self) -> Result<(), watch::error::RecvError> {
85 self.receiver.changed().await
86 }
87
88 pub fn borrow(&self) -> T {
90 self.receiver.borrow().clone()
91 }
92
93 pub fn borrow_ref(&self) -> impl Deref<Target = T> + '_ {
95 self.receiver.borrow()
96 }
97
98 pub fn has_changed(&self) -> bool {
100 self.receiver.has_changed().unwrap_or(false)
101 }
102
103 pub fn mark_unchanged(&mut self) {
105 self.receiver.mark_unchanged();
106 }
107}
108
109impl<T: Clone> Clone for Watcher<T> {
110 fn clone(&self) -> Self {
111 Self {
112 receiver: self.receiver.clone(),
113 }
114 }
115}
116
117pub struct CombinedWatcher<T1, T2> {
119 watcher1: Watcher<T1>,
120 watcher2: Watcher<T2>,
121}
122
123impl<T1: Clone, T2: Clone> CombinedWatcher<T1, T2> {
124 pub fn new(watcher1: Watcher<T1>, watcher2: Watcher<T2>) -> Self {
126 Self { watcher1, watcher2 }
127 }
128
129 pub async fn changed(&mut self) -> Result<(), watch::error::RecvError> {
131 tokio::select! {
132 result = self.watcher1.changed() => result,
133 result = self.watcher2.changed() => result,
134 }
135 }
136
137 pub fn borrow(&self) -> (T1, T2) {
139 (self.watcher1.borrow(), self.watcher2.borrow())
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146 use std::sync::Arc;
147 use std::time::Duration;
148 use tokio::time::timeout;
149
150 #[test]
151 fn test_get_returns_current_value() {
152 let watchable = Watchable::new(42);
153 assert_eq!(watchable.get(), 42);
154 }
155
156 #[test]
157 fn test_set_updates_value() {
158 let watchable = Watchable::new(0);
159 watchable.set(100);
160 assert_eq!(watchable.get(), 100);
161 }
162
163 #[tokio::test]
164 async fn test_watch_notified_on_change() {
165 let watchable = Arc::new(Watchable::new(0));
166 let mut watcher = watchable.watch();
167
168 let w = watchable.clone();
170 tokio::spawn(async move {
171 tokio::time::sleep(Duration::from_millis(10)).await;
172 w.set(42);
173 });
174
175 let result = timeout(Duration::from_millis(100), watcher.changed()).await;
177 assert!(result.is_ok());
178 assert_eq!(watcher.borrow(), 42);
179 }
180
181 #[tokio::test]
182 async fn test_multiple_watchers() {
183 let watchable = Arc::new(Watchable::new(0));
184 let mut watcher1 = watchable.watch();
185 let mut watcher2 = watchable.watch();
186
187 watchable.set(99);
188
189 let r1 = timeout(Duration::from_millis(50), watcher1.changed()).await;
191 let r2 = timeout(Duration::from_millis(50), watcher2.changed()).await;
192
193 assert!(r1.is_ok());
194 assert!(r2.is_ok());
195 assert_eq!(watcher1.borrow(), 99);
196 assert_eq!(watcher2.borrow(), 99);
197 }
198
199 #[test]
200 fn test_watch_borrow_returns_current() {
201 let watchable = Watchable::new("hello".to_string());
202 let watcher = watchable.watch();
203 assert_eq!(watcher.borrow(), "hello");
204
205 watchable.set("world".to_string());
206 assert_eq!(watcher.borrow(), "world");
208 }
209
210 #[test]
211 fn test_modify_in_place() {
212 let watchable = Watchable::new(vec![1, 2, 3]);
213 watchable.modify(|v| v.push(4));
214 assert_eq!(watchable.get(), vec![1, 2, 3, 4]);
215 }
216
217 #[test]
218 fn test_watchable_with_option() {
219 let watchable: Watchable<Option<String>> = Watchable::new(None);
220 assert_eq!(watchable.get(), None);
221
222 watchable.set(Some("test".to_string()));
223 assert_eq!(watchable.get(), Some("test".to_string()));
224 }
225
226 #[test]
227 fn test_default_watchable() {
228 let watchable: Watchable<i32> = Watchable::default();
229 assert_eq!(watchable.get(), 0);
230 }
231
232 #[test]
233 fn test_receiver_count() {
234 let watchable = Watchable::new(0);
235 assert_eq!(watchable.receiver_count(), 0);
236
237 let _w1 = watchable.watch();
238 assert_eq!(watchable.receiver_count(), 1);
239
240 let _w2 = watchable.watch();
241 assert_eq!(watchable.receiver_count(), 2);
242 }
243
244 #[test]
245 fn test_watcher_has_changed() {
246 let watchable = Watchable::new(0);
247 let watcher = watchable.watch();
248
249 assert!(!watcher.has_changed());
251
252 watchable.set(1);
254 assert!(watcher.has_changed());
255 }
256
257 #[tokio::test]
258 async fn test_combined_watcher() {
259 let w1 = Watchable::new(1);
260 let w2 = Watchable::new("a".to_string());
261
262 let watcher1 = w1.watch();
263 let watcher2 = w2.watch();
264
265 let mut combined = CombinedWatcher::new(watcher1, watcher2);
266
267 let (v1, v2) = combined.borrow();
269 assert_eq!(v1, 1);
270 assert_eq!(v2, "a");
271
272 w1.set(2);
274
275 let result = timeout(Duration::from_millis(50), combined.changed()).await;
277 assert!(result.is_ok());
278 }
279
280 #[test]
281 fn test_watcher_clone() {
282 let watchable = Watchable::new(42);
283 let watcher1 = watchable.watch();
284 let watcher2 = watcher1.clone();
285
286 assert_eq!(watcher1.borrow(), watcher2.borrow());
287 }
288
289 #[tokio::test]
290 async fn test_mark_unchanged() {
291 let watchable = Watchable::new(0);
292 let mut watcher = watchable.watch();
293
294 watchable.set(1);
295 assert!(watcher.has_changed());
296
297 watcher.mark_unchanged();
298 assert!(!watcher.has_changed());
299 }
300}