Skip to main content

ant_quic/
watchable.rs

1// Copyright 2024 Saorsa Labs Ltd.
2//
3// This Saorsa Network Software is licensed under the General Public License (GPL), version 3.
4// Please see the file LICENSE-GPL, or visit <http://www.gnu.org/licenses/> for the full text.
5//
6// Full details available at https://saorsalabs.com/licenses
7
8//! Watchable state pattern
9//!
10//! Provides reactive state observation without polling or lock contention.
11//! Based on tokio::sync::watch for efficient notification of state changes.
12
13use std::ops::Deref;
14use tokio::sync::watch;
15
16/// A value that can be watched for changes
17#[derive(Debug)]
18pub struct Watchable<T> {
19    sender: watch::Sender<T>,
20}
21
22impl<T: Clone + Send + Sync + 'static> Watchable<T> {
23    /// Create a new watchable with initial value
24    pub fn new(value: T) -> Self {
25        let (sender, _) = watch::channel(value);
26        Self { sender }
27    }
28
29    /// Get the current value
30    pub fn get(&self) -> T {
31        self.sender.borrow().clone()
32    }
33
34    /// Set a new value, notifying all watchers
35    pub fn set(&self, value: T) {
36        // Use send_modify to ensure the value is always updated,
37        // even when there are no active receivers
38        self.sender.send_modify(|v| *v = value);
39    }
40
41    /// Modify the value in place
42    pub fn modify<F>(&self, f: F)
43    where
44        F: FnOnce(&mut T),
45    {
46        self.sender.send_modify(f);
47    }
48
49    /// Create a watcher for this value
50    pub fn watch(&self) -> Watcher<T> {
51        Watcher {
52            receiver: self.sender.subscribe(),
53        }
54    }
55
56    /// Get a reference to the sender (for advanced use cases)
57    pub fn sender(&self) -> &watch::Sender<T> {
58        &self.sender
59    }
60
61    /// Check if there are any active watchers
62    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/// A watcher that receives updates from a Watchable
74#[derive(Debug)]
75pub struct Watcher<T> {
76    receiver: watch::Receiver<T>,
77}
78
79impl<T: Clone> Watcher<T> {
80    /// Wait for the value to change
81    ///
82    /// Returns `Ok(())` when the value has changed, or `Err` if the
83    /// sender was dropped.
84    pub async fn changed(&mut self) -> Result<(), watch::error::RecvError> {
85        self.receiver.changed().await
86    }
87
88    /// Get the current value (cloned)
89    pub fn borrow(&self) -> T {
90        self.receiver.borrow().clone()
91    }
92
93    /// Get a reference to the current value
94    pub fn borrow_ref(&self) -> impl Deref<Target = T> + '_ {
95        self.receiver.borrow()
96    }
97
98    /// Check if the value has changed since last check
99    pub fn has_changed(&self) -> bool {
100        self.receiver.has_changed().unwrap_or(false)
101    }
102
103    /// Mark the current value as seen
104    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
117/// Extension to combine multiple watchers
118pub struct CombinedWatcher<T1, T2> {
119    watcher1: Watcher<T1>,
120    watcher2: Watcher<T2>,
121}
122
123impl<T1: Clone, T2: Clone> CombinedWatcher<T1, T2> {
124    /// Create a new combined watcher
125    pub fn new(watcher1: Watcher<T1>, watcher2: Watcher<T2>) -> Self {
126        Self { watcher1, watcher2 }
127    }
128
129    /// Wait for either value to change
130    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    /// Get both current values
138    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        // Spawn task to update value
169        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        // Wait for change
176        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        // Both watchers should see the change
190        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        // borrow() returns current even without calling changed()
207        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        // Initially no change
250        assert!(!watcher.has_changed());
251
252        // After set, has_changed returns true
253        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        // Get current values
268        let (v1, v2) = combined.borrow();
269        assert_eq!(v1, 1);
270        assert_eq!(v2, "a");
271
272        // Update one value
273        w1.set(2);
274
275        // Combined should detect change
276        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}