use std::{collections::HashMap, sync::Arc};
use futures_util::stream::{FuturesUnordered, StreamExt};
use log::error;
use crate::{async_runtime::lock::Mutex, util::random_32, Result};
const CHANNEL_BUFFER_SIZE: usize = 1000;
pub type SubscriptionID = u32;
pub struct Publisher<T> {
subs: Mutex<HashMap<SubscriptionID, async_channel::Sender<T>>>,
subscription_buffer_size: usize,
}
impl<T: Clone> Publisher<T> {
pub fn new() -> Arc<Publisher<T>> {
Arc::new(Self {
subs: Mutex::new(HashMap::new()),
subscription_buffer_size: CHANNEL_BUFFER_SIZE,
})
}
pub fn with_buffer_size(size: usize) -> Arc<Publisher<T>> {
Arc::new(Self {
subs: Mutex::new(HashMap::new()),
subscription_buffer_size: size,
})
}
pub async fn subscribe(self: &Arc<Self>) -> Subscription<T> {
let mut subs = self.subs.lock().await;
let chan = async_channel::bounded(self.subscription_buffer_size);
let mut sub_id = random_32();
while subs.contains_key(&sub_id) {
sub_id = random_32();
}
let sub = Subscription::new(sub_id, self.clone(), chan.1);
subs.insert(sub_id, chan.0);
sub
}
pub async fn unsubscribe(self: &Arc<Self>, id: &SubscriptionID) {
self.subs.lock().await.remove(id);
}
pub async fn notify(self: &Arc<Self>, value: &T) {
let mut subs = self.subs.lock().await;
let mut results = FuturesUnordered::new();
let mut closed_subs = vec![];
for (sub_id, sub) in subs.iter() {
let result = async { (*sub_id, sub.send(value.clone()).await) };
results.push(result);
}
while let Some((id, fut_err)) = results.next().await {
if let Err(err) = fut_err {
error!("failed to notify {id}: {err}");
closed_subs.push(id);
}
}
drop(results);
for sub_id in closed_subs.iter() {
subs.remove(sub_id);
}
}
}
pub struct Subscription<T> {
id: SubscriptionID,
recv_chan: async_channel::Receiver<T>,
publisher: Arc<Publisher<T>>,
}
impl<T: Clone> Subscription<T> {
pub fn new(
id: SubscriptionID,
publisher: Arc<Publisher<T>>,
recv_chan: async_channel::Receiver<T>,
) -> Subscription<T> {
Self {
id,
recv_chan,
publisher,
}
}
pub async fn recv(&self) -> Result<T> {
let msg = self.recv_chan.recv().await?;
Ok(msg)
}
pub async fn unsubscribe(&self) {
self.publisher.unsubscribe(&self.id).await;
}
}