use std::{future::Future, rc::Rc};
use yew::Callback;
use crate::{
context::Context,
store::{Reducer, Store},
subscriber::{Callable, SubscriberId},
};
pub struct Dispatch<S: Store> {
pub(crate) _subscriber_id: Option<Rc<SubscriberId<S>>>,
pub(crate) cx: Context,
}
impl<S: Store> std::fmt::Debug for Dispatch<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Dispatch")
.field("_subscriber_id", &self._subscriber_id)
.finish()
}
}
#[cfg(any(doc, feature = "doctests", target_arch = "wasm32"))]
impl<S: Store> Default for Dispatch<S> {
fn default() -> Self {
Self::global()
}
}
impl<S: Store> Dispatch<S> {
#[cfg(any(doc, feature = "doctests", target_arch = "wasm32"))]
pub fn global() -> Self {
Self::new(&Context::global())
}
pub fn new(cx: &Context) -> Self {
Self {
_subscriber_id: Default::default(),
cx: cx.clone(),
}
}
pub fn context(&self) -> &Context {
&self.cx
}
#[cfg(feature = "future")]
pub fn spawn_future<F, FU>(&self, f: F)
where
F: FnOnce(Self) -> FU,
FU: Future<Output = ()> + 'static,
{
yew::platform::spawn_local(f(self.clone()));
}
#[cfg(feature = "future")]
pub fn future_callback<E, F, FU>(&self, f: F) -> Callback<E>
where
F: Fn(Self) -> FU + 'static,
FU: Future<Output = ()> + 'static,
{
let dispatch = self.clone();
Callback::from(move |_| dispatch.spawn_future(&f))
}
#[cfg(feature = "future")]
pub fn future_callback_with<E, F, FU>(&self, f: F) -> Callback<E>
where
F: Fn(Self, E) -> FU + 'static,
FU: Future<Output = ()> + 'static,
{
let dispatch = self.clone();
Callback::from(move |e| dispatch.spawn_future(|dispatch| f(dispatch, e)))
}
pub fn subscribe<C: Callable<S>>(self, on_change: C) -> Self {
let id = self.cx.subscribe(on_change);
Self {
_subscriber_id: Some(Rc::new(id)),
cx: self.cx,
}
}
pub fn subscribe_silent<C: Callable<S>>(self, on_change: C) -> Self {
let id = self.cx.subscribe_silent(on_change);
Self {
_subscriber_id: Some(Rc::new(id)),
cx: self.cx,
}
}
pub fn get(&self) -> Rc<S> {
self.cx.get::<S>()
}
pub fn apply<R: Reducer<S>>(&self, reducer: R) {
self.cx.reduce(reducer);
}
pub fn apply_callback<E, M, F>(&self, f: F) -> Callback<E>
where
M: Reducer<S>,
F: Fn(E) -> M + 'static,
{
let context = self.cx.clone();
Callback::from(move |e| {
let msg = f(e);
context.reduce(msg);
})
}
pub fn set(&self, val: S) {
self.cx.set(val);
}
pub fn set_callback<E, F>(&self, f: F) -> Callback<E>
where
F: Fn(E) -> S + 'static,
{
let context = self.cx.clone();
Callback::from(move |e| {
let val = f(e);
context.set(val);
})
}
pub fn reduce<F>(&self, f: F)
where
F: FnOnce(Rc<S>) -> Rc<S>,
{
self.cx.reduce(f);
}
pub fn reduce_callback<F, E>(&self, f: F) -> Callback<E>
where
F: Fn(Rc<S>) -> Rc<S> + 'static,
E: 'static,
{
let context = self.cx.clone();
Callback::from(move |_| {
context.reduce(&f);
})
}
pub fn reduce_callback_with<F, E>(&self, f: F) -> Callback<E>
where
F: Fn(Rc<S>, E) -> Rc<S> + 'static,
E: 'static,
{
let context = self.cx.clone();
Callback::from(move |e: E| {
context.reduce(|x| f(x, e));
})
}
pub fn reduce_mut<F, R>(&self, f: F) -> R
where
S: Clone,
F: FnOnce(&mut S) -> R,
{
let mut result = None;
self.cx.reduce_mut(|x| {
result = Some(f(x));
});
result.expect("result not initialized")
}
pub fn reduce_mut_callback<F, R, E>(&self, f: F) -> Callback<E>
where
S: Clone,
F: Fn(&mut S) -> R + 'static,
E: 'static,
{
let context = self.cx.clone();
Callback::from(move |_| {
context.reduce_mut(|x| {
f(x);
});
})
}
pub fn reduce_mut_callback_with<F, R, E>(&self, f: F) -> Callback<E>
where
S: Clone,
F: Fn(&mut S, E) -> R + 'static,
E: 'static,
{
let context = self.cx.clone();
Callback::from(move |e: E| {
context.reduce_mut(|x| {
f(x, e);
});
})
}
}
impl<S: Store> Clone for Dispatch<S> {
fn clone(&self) -> Self {
Self {
_subscriber_id: self._subscriber_id.clone(),
cx: self.cx.clone(),
}
}
}
impl<S: Store> PartialEq for Dispatch<S> {
fn eq(&self, other: &Self) -> bool {
match (&self._subscriber_id, &other._subscriber_id) {
(Some(a), Some(b)) => Rc::ptr_eq(a, b),
_ => false,
}
}
}
#[cfg(test)]
mod tests {
use crate::{mrc::Mrc, subscriber::Subscribers};
use super::*;
#[derive(Clone, PartialEq, Eq)]
struct TestState(u32);
impl Store for TestState {
fn new(_cx: &Context) -> Self {
Self(0)
}
fn should_notify(&self, other: &Self) -> bool {
self != other
}
}
#[derive(PartialEq, Eq)]
struct TestStateNoClone(u32);
impl Store for TestStateNoClone {
fn new(_cx: &Context) -> Self {
Self(0)
}
fn should_notify(&self, other: &Self) -> bool {
self != other
}
}
struct Msg;
impl Reducer<TestState> for Msg {
fn apply(self, state: Rc<TestState>) -> Rc<TestState> {
TestState(state.0 + 1).into()
}
}
#[test]
fn apply_no_clone() {
Dispatch::new(&Context::new()).reduce(|_| TestStateNoClone(1).into());
}
#[test]
fn reduce_changes_value() {
let dispatch = Dispatch::<TestState>::new(&Context::new());
let old = dispatch.get();
dispatch.reduce(|_| TestState(1).into());
let new = dispatch.get();
assert!(old != new);
}
#[test]
fn reduce_mut_changes_value() {
let dispatch = Dispatch::<TestState>::new(&Context::new());
let old = dispatch.get();
dispatch.reduce_mut(|state| *state = TestState(1));
let new = dispatch.get();
assert!(old != new);
}
#[test]
fn reduce_does_not_require_static() {
let val = "1".to_string();
Dispatch::new(&Context::new()).reduce(|_| TestState(val.parse().unwrap()).into());
}
#[test]
fn reduce_mut_does_not_require_static() {
let val = "1".to_string();
Dispatch::new(&Context::new())
.reduce_mut(|state: &mut TestState| state.0 = val.parse().unwrap());
}
#[test]
fn set_changes_value() {
let dispatch = Dispatch::<TestState>::new(&Context::new());
let old = dispatch.get();
dispatch.set(TestState(1));
let new = dispatch.get();
assert!(old != new);
}
#[test]
fn apply_changes_value() {
let dispatch = Dispatch::<TestState>::new(&Context::new());
let old = dispatch.get();
dispatch.apply(Msg);
let new = dispatch.get();
assert!(old != new);
}
#[test]
fn dispatch_set_works() {
let dispatch = Dispatch::<TestState>::new(&Context::new());
let old = dispatch.get();
dispatch.set(TestState(1));
assert!(dispatch.get() != old)
}
#[test]
fn dispatch_set_callback_works() {
let dispatch = Dispatch::<TestState>::new(&Context::new());
let old = dispatch.get();
let cb = dispatch.set_callback(|_| TestState(1));
cb.emit(());
assert!(dispatch.get() != old)
}
#[test]
fn dispatch_reduce_mut_works() {
let dispatch = Dispatch::<TestState>::new(&Context::new());
let old = dispatch.get();
dispatch.reduce_mut(|state| state.0 += 1);
assert!(dispatch.get() != old)
}
#[test]
fn dispatch_reduce_works() {
let dispatch = Dispatch::<TestState>::new(&Context::new());
let old = dispatch.get();
dispatch.reduce(|_| TestState(1).into());
assert!(dispatch.get() != old)
}
#[test]
fn dispatch_reduce_callback_works() {
let dispatch = Dispatch::<TestState>::new(&Context::new());
let old = dispatch.get();
let cb = dispatch.reduce_callback(|_| TestState(1).into());
cb.emit(());
assert!(dispatch.get() != old)
}
#[test]
fn dispatch_reduce_mut_callback_works() {
let dispatch = Dispatch::<TestState>::new(&Context::new());
let old = dispatch.get();
let cb = dispatch.reduce_mut_callback(|state| state.0 += 1);
cb.emit(());
assert!(dispatch.get() != old)
}
#[test]
fn dispatch_reduce_callback_with_works() {
let dispatch = Dispatch::<TestState>::new(&Context::new());
let old = dispatch.get();
let cb = dispatch.reduce_callback_with(|_, _| TestState(1).into());
cb.emit(1);
assert!(dispatch.get() != old)
}
#[test]
fn dispatch_reduce_mut_callback_with_works() {
let dispatch = Dispatch::<TestState>::new(&Context::new());
let old = dispatch.get();
let cb = dispatch.reduce_mut_callback_with(|state, val| state.0 += val);
cb.emit(1);
assert!(dispatch.get() != old)
}
#[test]
fn dispatch_apply_works() {
let dispatch = Dispatch::<TestState>::new(&Context::new());
let old = dispatch.get();
dispatch.apply(Msg);
assert!(dispatch.get() != old)
}
#[test]
fn dispatch_apply_callback_works() {
let dispatch = Dispatch::<TestState>::new(&Context::new());
let old = dispatch.get();
let cb = dispatch.apply_callback(|_| Msg);
cb.emit(());
assert!(dispatch.get() != old)
}
#[test]
fn subscriber_is_notified() {
let cx = Context::new();
let flag = Mrc::new(false);
let _id = {
let flag = flag.clone();
Dispatch::<TestState>::new(&cx)
.subscribe(move |_| flag.clone().with_mut(|flag| *flag = true))
};
*flag.borrow_mut() = false;
Dispatch::<TestState>::new(&cx).reduce_mut(|state| state.0 += 1);
assert!(*flag.borrow());
}
#[test]
fn subscriber_is_not_notified_when_state_is_same() {
let cx = Context::new();
let flag = Mrc::new(false);
let dispatch = Dispatch::<TestState>::new(&cx);
dispatch.reduce_mut(|_| {});
let _id = {
let flag = flag.clone();
Dispatch::<TestState>::new(&cx)
.subscribe(move |_| flag.clone().with_mut(|flag| *flag = true))
};
*flag.borrow_mut() = false;
dispatch.reduce_mut(|state| state.0 = 0);
assert!(!*flag.borrow());
}
#[test]
fn dispatch_unsubscribes_when_dropped() {
let cx = Context::new();
let entry = cx.get_or_init_default::<Mrc<Subscribers<TestState>>>();
assert!(entry.store.borrow().borrow().0.is_empty());
let dispatch = Dispatch::<TestState>::new(&cx).subscribe(|_| ());
assert!(!entry.store.borrow().borrow().0.is_empty());
drop(dispatch);
assert!(entry.store.borrow().borrow().0.is_empty());
}
#[test]
fn dispatch_clone_and_original_unsubscribe_when_both_dropped() {
let cx = Context::new();
let entry = cx.get_or_init_default::<Mrc<Subscribers<TestState>>>();
assert!(entry.store.borrow().borrow().0.is_empty());
let dispatch = Dispatch::<TestState>::new(&cx).subscribe(|_| ());
let dispatch_clone = dispatch.clone();
assert!(!entry.store.borrow().borrow().0.is_empty());
drop(dispatch_clone);
assert!(!entry.store.borrow().borrow().0.is_empty());
drop(dispatch);
assert!(entry.store.borrow().borrow().0.is_empty());
}
}