use crate::reactive::local::{LocalScope, Wake};
use amethystate_core::SignalSubscription;
use futures_core::Stream;
use std::collections::VecDeque;
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use std::task::{Context, Poll};
use uuid::Uuid;
pub trait Watchable {
type Item: Clone + Send + 'static;
fn watch_id(&self) -> Uuid;
fn filterable(_item: &Self::Item) -> bool {
true
}
fn watch_raw<F>(&self, callback: F) -> SignalSubscription
where
F: Fn(&Self::Item, Option<Uuid>) + Send + Sync + 'static;
}
pub struct Immediate;
pub struct Local<'a> {
scope: &'a mut LocalScope,
coalesce: bool,
}
#[must_use = "a Watch subscribes to nothing until register() is called"]
pub struct Watch<W, D> {
source: W,
external: bool,
delivery: D,
}
impl<W: Watchable> Watch<W, Immediate> {
pub(crate) fn new(source: W) -> Self {
Self {
source,
external: false,
delivery: Immediate,
}
}
pub fn local<'a>(self, scope: &'a mut LocalScope) -> Watch<W, Local<'a>> {
Watch {
source: self.source,
external: self.external,
delivery: Local {
scope,
coalesce: true,
},
}
}
pub fn register<F>(self, callback: F) -> SignalSubscription
where
F: Fn(&W::Item) + Send + Sync + 'static,
{
self.register_with_source(move |item, _| callback(item))
}
pub fn register_with_source<F>(self, callback: F) -> SignalSubscription
where
F: Fn(&W::Item, Option<Uuid>) + Send + Sync + 'static,
{
let mine = self.external.then(|| self.source.watch_id());
self.source.watch_raw(move |item, source| {
if mine.is_some() && source == mine && W::filterable(item) {
return;
}
callback(item, source);
})
}
}
impl<W: Watchable> Watch<W, Local<'_>> {
pub fn every(mut self) -> Self {
self.delivery.coalesce = false;
self
}
pub fn register<F>(self, mut callback: F)
where
F: FnMut(&W::Item) + 'static,
{
self.register_with_source(move |item, _| callback(item))
}
pub fn register_with_source<F>(self, mut callback: F)
where
F: FnMut(&W::Item, Option<Uuid>) + 'static,
{
let mine = self.external.then(|| self.source.watch_id());
let coalesce = self.delivery.coalesce;
let wake = self.delivery.scope.wake_handle();
type Queued<I> = Vec<(I, Option<Uuid>)>;
let queue: Arc<Mutex<Queued<W::Item>>> = Arc::new(Mutex::new(Vec::new()));
let sink = Arc::clone(&queue);
let sub = self.source.watch_raw(move |item, source| {
if mine.is_some() && source == mine && W::filterable(item) {
return;
}
{
let mut queued = sink.lock().unwrap();
if coalesce {
queued.clear();
}
queued.push((item.clone(), source));
}
wake.signal();
});
self.delivery.scope.add(
sub,
Box::new(move || {
let batch = std::mem::take(&mut *queue.lock().unwrap());
for (item, source) in batch {
callback(&item, source);
}
}),
);
}
}
impl<W: Watchable> Watch<W, Immediate> {
pub fn stream(self) -> ChangeStream<W::Item> {
let mine = self.external.then(|| self.source.watch_id());
let queue: Arc<Mutex<VecDeque<W::Item>>> = Arc::new(Mutex::new(VecDeque::new()));
let wake = Arc::new(Wake::default());
let sink = Arc::clone(&queue);
let signal = Arc::clone(&wake);
let sub = self.source.watch_raw(move |item, source| {
if mine.is_some() && source == mine && W::filterable(item) {
return;
}
sink.lock().unwrap().push_back(item.clone());
signal.signal();
});
ChangeStream {
queue,
wake,
_sub: sub,
}
}
}
#[must_use = "dropping the stream ends the subscription"]
pub struct ChangeStream<T> {
queue: Arc<Mutex<VecDeque<T>>>,
wake: Arc<Wake>,
_sub: SignalSubscription,
}
impl<T> Stream for ChangeStream<T> {
type Item = T;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<T>> {
if let Some(item) = self.queue.lock().unwrap().pop_front() {
return Poll::Ready(Some(item));
}
self.wake.park(cx);
match self.queue.lock().unwrap().pop_front() {
Some(item) => Poll::Ready(Some(item)),
None => Poll::Pending,
}
}
}
impl<W, D> Watch<W, D> {
pub fn external(mut self) -> Self {
self.external = true;
self
}
}
impl<K, V, S, M, D> Watch<crate::ReactiveMap<K, V, S, M>, D>
where
K: crate::ReactiveMapKey,
V: crate::ReactiveMapValue,
S: crate::store::Store,
M: crate::AccessMode,
{
pub fn key(self, key: K) -> Watch<crate::reactive::map::KeyOf<K, V, S, M>, D> {
Watch {
source: crate::reactive::map::KeyOf::new(self.source, key),
external: self.external,
delivery: self.delivery,
}
}
}