use alloc::sync::Arc;
use core::fmt;
use executor_core::LocalExecutor;
use nami::binding::BindingMailbox;
use nami::stream::SignalStream;
use nami::watcher::Context as WatcherContext;
use nami::{Binding, Signal, binding as nami_binding};
extern crate alloc;
pub struct Subscribed<T: Clone + 'static> {
binding: Binding<T>,
}
impl<T: Clone + 'static> Subscribed<T> {
#[must_use]
pub const fn from_binding(binding: Binding<T>) -> Self {
Self { binding }
}
#[must_use]
pub const fn as_binding(&self) -> &Binding<T> {
&self.binding
}
}
impl<T: Clone + 'static> Clone for Subscribed<T> {
fn clone(&self) -> Self {
Self {
binding: self.binding.clone(),
}
}
}
impl<T: Clone + 'static> Subscribed<T> {
#[must_use]
pub fn get(&self) -> T {
Signal::get(&self.binding)
}
#[must_use]
pub fn stream(&self) -> SignalStream<Binding<T>> {
SignalStream::new(self.binding.clone())
}
#[must_use]
pub fn map<U, F>(&self, f: F) -> Subscribed<U>
where
U: Clone + 'static,
F: Fn(T) -> U + Clone + 'static,
{
Subscribed {
binding: Binding::mapping(&self.binding, f, |_, _| {}),
}
}
}
impl<T: Clone + 'static> Signal for Subscribed<T> {
type Output = T;
type Guard = <Binding<T> as Signal>::Guard;
fn get(&self) -> T {
Signal::get(&self.binding)
}
fn watch(&self, watcher: impl Fn(WatcherContext<T>) + 'static) -> Self::Guard {
self.binding.watch(watcher)
}
}
impl<T: fmt::Debug + Clone + 'static> fmt::Debug for Subscribed<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Subscribed")
.field("value", &self.get())
.finish()
}
}
pub struct SubscribedSink<T: 'static> {
mailbox: Arc<BindingMailbox<T>>,
}
impl<T: 'static> SubscribedSink<T> {
pub fn set(&self, value: T)
where
T: Send + 'static,
{
self.mailbox.handle(move |b| b.set(value));
}
pub fn handle(&self, job: impl FnOnce(&mut Binding<T>) + Send + 'static) {
self.mailbox.handle(job);
}
}
impl<T: 'static> Clone for SubscribedSink<T> {
fn clone(&self) -> Self {
Self {
mailbox: Arc::clone(&self.mailbox),
}
}
}
impl<T: 'static> fmt::Debug for SubscribedSink<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SubscribedSink").finish_non_exhaustive()
}
}
#[must_use]
pub fn subscribed<T: Clone + Send + 'static>(initial: T) -> (Subscribed<T>, SubscribedSink<T>) {
let binding = nami_binding(initial);
let mailbox = binding.mailbox();
(
Subscribed { binding },
SubscribedSink {
mailbox: Arc::new(mailbox),
},
)
}
#[must_use]
pub fn subscribed_with_executor<T, E>(initial: T, executor: E) -> (Subscribed<T>, SubscribedSink<T>)
where
T: Clone + Send + 'static,
E: LocalExecutor,
{
let binding = nami_binding(initial);
let mailbox = binding.mailbox_with_executor(executor);
(
Subscribed { binding },
SubscribedSink {
mailbox: Arc::new(mailbox),
},
)
}
#[cfg(test)]
mod tests {
use super::*;
use core::cell::Cell;
use std::rc::Rc;
fn make<T: Clone + 'static>(value: T) -> Subscribed<T> {
let binding: Binding<T> = nami_binding(value);
Subscribed::from_binding(binding)
}
#[test]
fn snapshot_returns_initial_value() {
let sub = make(7_u32);
assert_eq!(sub.get(), 7);
}
#[test]
fn map_derives_value() {
let sub = make(3_u32);
let doubled = sub.map(|x: u32| x * 2);
assert_eq!(doubled.get(), 6);
}
#[test]
fn watch_fires_on_local_set() {
let sub = make(0_u32);
let captured = Rc::new(Cell::new(0_u32));
let captured_for_watcher = Rc::clone(&captured);
let _guard = sub.watch(move |ctx| {
captured_for_watcher.set(*ctx.value());
});
sub.as_binding().set(42);
assert_eq!(captured.get(), 42);
assert_eq!(sub.get(), 42);
}
#[test]
fn map_propagates_upstream_set() {
let sub = make(10_i32);
let plus_one = sub.map(|x: i32| x + 1);
assert_eq!(plus_one.get(), 11);
sub.as_binding().set(20);
assert_eq!(plus_one.get(), 21);
}
}