use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use tokio::sync::broadcast;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChannelError {
Lagged,
Closed,
Full,
}
impl std::fmt::Display for ChannelError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Lagged => f.write_str("channel subscriber lagged"),
Self::Closed => f.write_str("channel closed"),
Self::Full => f.write_str("channel full"),
}
}
}
impl std::error::Error for ChannelError {}
#[derive(Debug, Clone)]
pub struct ChannelPayload(Arc<[u8]>);
impl ChannelPayload {
#[must_use]
pub fn from_bytes(bytes: Vec<u8>) -> Self {
Self(bytes.into())
}
#[must_use]
pub fn from_static(bytes: &'static [u8]) -> Self {
Self(bytes.into())
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
}
#[derive(Clone, Debug)]
pub struct Broadcast {
tx: broadcast::Sender<ChannelPayload>,
capacity: usize,
subscriber_count: Arc<AtomicUsize>,
}
impl Broadcast {
#[must_use]
pub fn new(capacity: usize) -> Option<Self> {
if capacity == 0 {
return None;
}
let (tx, _) = broadcast::channel(capacity);
Some(Self {
tx,
capacity,
subscriber_count: Arc::new(AtomicUsize::new(0)),
})
}
#[must_use]
pub fn capacity(&self) -> usize {
self.capacity
}
#[must_use]
pub fn subscriber_count(&self) -> usize {
self.subscriber_count.load(Ordering::Relaxed)
}
pub fn publish(&self, payload: ChannelPayload) -> Result<usize, ChannelError> {
match self.tx.send(payload) {
Ok(n) => Ok(n),
Err(_) => Err(ChannelError::Closed),
}
}
#[must_use]
pub fn subscribe(&self) -> Subscription {
self.subscriber_count.fetch_add(1, Ordering::Relaxed);
Subscription {
rx: self.tx.subscribe(),
count: self.subscriber_count.clone(),
}
}
}
#[derive(Debug)]
pub struct Subscription {
pub rx: broadcast::Receiver<ChannelPayload>,
count: Arc<AtomicUsize>,
}
impl Subscription {
pub async fn recv(&mut self) -> Result<ChannelPayload, ChannelError> {
self.rx.recv().await.map_err(|e| match e {
broadcast::error::RecvError::Closed => ChannelError::Closed,
broadcast::error::RecvError::Lagged(_) => ChannelError::Lagged,
})
}
}
impl Drop for Subscription {
fn drop(&mut self) {
loop {
let current = self.count.load(Ordering::Relaxed);
if current == 0 {
break;
}
if self
.count
.compare_exchange_weak(current, current - 1, Ordering::Relaxed, Ordering::Relaxed)
.is_ok()
{
break;
}
}
}
}