use std::{
fmt::Debug,
pin::Pin,
task::{Context, Poll},
};
use crate::Reply;
use pin_project_lite::pin_project;
use tokio::sync::broadcast;
use tokio_stream::wrappers::BroadcastStream;
#[derive(Debug, Clone)]
pub struct State<T, ReplyParams> {
value: T,
tx: broadcast::Sender<ReplyParams>,
}
impl<T, ReplyParams> zlink_core::notified::State<T, ReplyParams> for State<T, ReplyParams>
where
T: Into<ReplyParams> + Clone + Debug + Send,
ReplyParams: Clone + Send + 'static + Debug,
{
type Stream = Stream<ReplyParams>;
fn new(value: T) -> Self {
let (tx, _) = broadcast::channel(1);
Self { value, tx }
}
async fn set(&mut self, value: T) {
self.value = value.clone();
let _ = self.tx.send(value.into());
}
fn get(&self) -> T {
self.value.clone()
}
fn stream(&self) -> Stream<ReplyParams> {
Stream {
inner: self.tx.subscribe().into(),
cached: None,
once: false,
}
}
fn stream_once(&self) -> Stream<ReplyParams> {
Stream {
inner: self.tx.subscribe().into(),
cached: Some(self.get().into()),
once: true,
}
}
}
pin_project! {
#[derive(Debug)]
pub struct Stream<ReplyParams> {
#[pin]
inner: BroadcastStream<ReplyParams>,
cached: Option<ReplyParams>,
once: bool,
}
}
impl<ReplyParams> futures_util::Stream for Stream<ReplyParams>
where
ReplyParams: Clone + Send + 'static,
{
type Item = Reply<ReplyParams>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let this = self.project();
if *this.once {
return Poll::Ready(
this.cached
.take()
.map(|reply| Reply::new(Some(reply)).set_continues(Some(false))),
);
}
let mut stream = this.inner;
loop {
match futures_util::ready!(stream.as_mut().poll_next(cx)) {
Some(Ok(reply)) => {
*this.cached = Some(reply.clone());
break Poll::Ready(Some(Reply::new(Some(reply)).set_continues(Some(true))));
}
Some(Err(_)) => continue,
None => {
break Poll::Ready(
this.cached
.take()
.map(|reply| Reply::new(Some(reply)).set_continues(Some(false))),
);
}
}
}
}
}