use std::future::Future; use tokio::sync::{mpsc, watch}; pub use tokio::task::JoinError; #[derive(Debug)] pub struct Publisher { sender: watch::Sender, } impl Publisher { pub async fn all_unsubscribed(&self) { self.sender.closed().await } pub fn publish(&self, value: T) { self.sender.send_replace(value); } pub fn publish_with bool>(&self, maybe_modify: F) { self.sender.send_if_modified(maybe_modify); } } #[derive(Debug)] pub struct PublisherStream { receiver: mpsc::Receiver>, } impl PublisherStream { /// Returns `None` when no more subscriptions can ever be made pub async fn wait(&mut self) -> Option> { self.receiver.recv().await } } #[derive(Debug)] pub struct Signal { sender: watch::Sender, publisher_sender: mpsc::Sender>, } impl Signal { pub fn new + Send + 'static>( initial: T, producer: impl FnOnce(PublisherStream) -> Fut, ) -> (Self, impl Future>) where R: Send + 'static, { let (sender, _) = watch::channel(initial); let (publisher_sender, publisher_receiver) = mpsc::channel(1); let publisher_stream = PublisherStream { receiver: publisher_receiver, }; let producer_join_handle = tokio::spawn(producer(publisher_stream)); ( Self { publisher_sender, sender, }, producer_join_handle, ) } pub fn subscribe(&self) -> Result, ProducerExited> { let receiver = self.sender.subscribe(); if self.sender.receiver_count() == 1 { if let Err(mpsc::error::TrySendError::Closed(_)) = self.publisher_sender.try_send(Publisher { sender: self.sender.clone(), }) { return Err(ProducerExited); } } Ok(Subscription { receiver }) } } pub struct Subscription { receiver: watch::Receiver, } #[derive(Debug, Clone, Copy)] pub struct ProducerExited; impl Subscription { pub async fn changed(&mut self) -> Result<(), ProducerExited> { self.receiver.changed().await.map_err(|_| ProducerExited) } pub fn get(&mut self) -> T::Owned where T: ToOwned, { self.receiver.borrow_and_update().to_owned() } pub async fn for_each>(mut self, mut func: impl FnMut(T::Owned) -> Fut) where T: ToOwned, { loop { func(self.get()).await; if self.changed().await.is_err() { return; } } } }