wayle_core/property/
mod.rs1mod serde;
5mod stream;
6
7use std::sync::{
8 Arc,
9 atomic::{AtomicUsize, Ordering},
10};
11
12use futures::stream::Stream;
13use tokio::sync::{Notify, watch};
14use tokio_stream::wrappers::WatchStream;
15
16use self::stream::SubscribedStream;
17
18pub type PropertyStream<T> = Box<dyn Stream<Item = T> + Send + Unpin>;
20
21#[derive(Clone)]
32pub struct Property<T: Clone + Send + Sync + 'static> {
33 tx: watch::Sender<T>,
34 rx: watch::Receiver<T>,
35
36 subscriber_count: Arc<AtomicUsize>,
37 subscriber_notify: Arc<Notify>,
38}
39
40impl<T: Clone + Send + Sync + 'static> Property<T> {
41 #[doc(hidden)]
50 pub fn new(initial: T) -> Self {
51 let (tx, rx) = watch::channel(initial);
52
53 Self {
54 tx,
55 rx,
56 subscriber_count: Arc::new(AtomicUsize::new(0)),
57 subscriber_notify: Arc::new(Notify::new()),
58 }
59 }
60
61 #[doc(hidden)]
75 pub fn set(&self, new_value: T)
76 where
77 T: PartialEq,
78 {
79 self.tx.send_if_modified(|current| {
80 if *current != new_value {
81 *current = new_value;
82 return true;
83 }
84
85 false
86 });
87 }
88
89 pub fn replace(&self, new_value: T) {
101 self.tx.send_modify(|current| *current = new_value);
102 }
103
104 pub fn get(&self) -> T {
113 self.rx.borrow().clone()
114 }
115
116 pub fn watch(&self) -> impl Stream<Item = T> + Send + 'static {
135 SubscribedStream::new(
136 WatchStream::new(self.rx.clone()),
137 Arc::clone(&self.subscriber_count),
138 Arc::clone(&self.subscriber_notify),
139 )
140 }
141
142 pub fn has_subscribers(&self) -> bool {
144 self.subscriber_count.load(Ordering::Acquire) > 0
145 }
146
147 pub async fn wait_for_subscribers(&self) {
170 while !self.has_subscribers() {
171 self.subscriber_notify.notified().await;
172 }
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use std::task::Poll;
179
180 use futures::{poll, stream::StreamExt};
181
182 use super::*;
183
184 #[test]
185 fn set_updates_value() {
186 let property = Property::new(42);
187 property.set(100);
188
189 assert_eq!(property.get(), 100);
190 }
191
192 #[tokio::test]
193 async fn set_skips_notification_when_unchanged() {
194 let property = Property::new(42);
195 let mut stream = property.watch();
196
197 assert_eq!(stream.next().await, Some(42));
198
199 property.set(42);
200 assert_eq!(poll!(stream.next()), Poll::Pending);
201 }
202
203 #[tokio::test]
204 async fn notifies_watchers_on_change() {
205 let property = Property::new(1);
206 let mut stream = property.watch();
207
208 assert_eq!(stream.next().await, Some(1));
209
210 property.set(2);
211 assert_eq!(stream.next().await, Some(2));
212 }
213
214 #[test]
215 fn no_subscribers_initially() {
216 let property = Property::new(0);
217
218 assert!(!property.has_subscribers());
219 }
220
221 #[test]
222 fn tracks_subscriber_lifetime() {
223 let property = Property::new(0);
224
225 let stream = property.watch();
226 assert!(property.has_subscribers());
227
228 drop(stream);
229 assert!(!property.has_subscribers());
230 }
231
232 #[test]
233 fn tracks_multiple_subscribers() {
234 let property = Property::new(0);
235
236 let stream_a = property.watch();
237 let stream_b = property.watch();
238 let stream_c = property.watch();
239 assert!(property.has_subscribers());
240
241 drop(stream_a);
242 assert!(property.has_subscribers());
243
244 drop(stream_b);
245 assert!(property.has_subscribers());
246
247 drop(stream_c);
248 assert!(!property.has_subscribers());
249 }
250
251 #[test]
252 fn clones_share_subscriber_count() {
253 let property = Property::new(0);
254 let cloned = property.clone();
255
256 let _stream = cloned.watch();
257
258 assert!(property.has_subscribers());
259 }
260
261 #[tokio::test]
262 async fn wait_for_subscribers_resolves_on_first_watcher() {
263 let property = Property::new(0);
264 let waiting = property.clone();
265
266 let (ready_tx, ready_rx) = tokio::sync::oneshot::channel::<()>();
267
268 let waiter = tokio::spawn(async move {
269 let _ = ready_tx.send(());
270 waiting.wait_for_subscribers().await;
271 });
272
273 ready_rx.await.unwrap();
274 let _stream = property.watch();
275
276 waiter.await.unwrap();
277 }
278
279 #[tokio::test]
280 async fn wait_for_subscribers_returns_immediately_if_already_watched() {
281 let property = Property::new(0);
282 let _stream = property.watch();
283
284 property.wait_for_subscribers().await;
285 }
286}