use std::cell::RefCell;
use std::ops::Deref;
use std::rc::Rc;
use std::sync::mpsc::{channel, Receiver, Sender, TryRecvError};
pub type Middleware<A, S> = Box<dyn Fn(&Store<A, S>, Box<dyn Fn(&Store<A, S>, A)>, A)>;
pub fn middleware<F, A, S>(implementation: F) -> Middleware<A, S>
where
F: Fn(&Store<A, S>, Box<dyn Fn(&Store<A, S>, A)>, A) + 'static,
A: Clone + Eq + 'static,
S: LifecycleState + Clone + Eq + Default + 'static,
{
Box::new(implementation)
}
pub trait LifecycleState {
fn running(&self) -> bool;
}
#[derive(Clone)]
pub struct Store<A, S>
where
A: Clone + Eq + 'static,
S: LifecycleState + Clone + Eq + Default + 'static,
{
callbacks: Rc<RefCell<Vec<Callback<A, S>>>>,
reducer: Rc<Box<dyn Fn(A, S) -> S + 'static>>,
state: Rc<RefCell<S>>,
middlewares: Rc<Vec<Middleware<A, S>>>,
receive_from_bg: Rc<Receiver<A>>,
send_to_store: Sender<A>,
}
impl<A, S> Store<A, S>
where
A: Clone + Eq + 'static,
S: LifecycleState + Clone + Eq + Default + 'static,
{
pub fn new<F>(reducer: F, middlewares: Vec<Middleware<A, S>>) -> Self
where
F: Fn(A, S) -> S + 'static,
{
let (sender, receiver) = channel();
Store {
state: Rc::new(RefCell::new(S::default())),
reducer: Rc::new(Box::new(reducer)),
callbacks: Rc::new(RefCell::new(Vec::new())),
middlewares: Rc::new(middlewares),
receive_from_bg: Rc::new(receiver),
send_to_store: sender,
}
}
pub fn dispatch(&self, action: A) {
self.dispatch_with_middleware(action, 0);
}
pub fn select<R, F>(&self, selector: F) -> R
where
F: Fn(&S) -> R,
{
let state = self.state.borrow();
let r: R = selector(&*state);
return r;
}
pub fn register<U, C>(&self, select: U, call: C)
where
U: Fn(&S, &S) -> bool + 'static,
C: Fn(&S) + 'static,
{
let callback = Callback {
latest: RefCell::new(None),
select: Box::new(select),
run: Box::new(move |_a: A, s: &S| call(s)),
};
let mut callbacks = self.callbacks.take();
callbacks.push(callback);
self.callbacks.replace(callbacks);
}
pub fn sender(&self) -> Sender<A> {
self.send_to_store.clone()
}
pub(crate) fn try_receive(&self) -> Result<A, TryRecvError> {
self.receive_from_bg.try_recv()
}
fn dispatch_with_middleware(&self, action: A, index: usize) {
match self.middlewares.get(index) {
None => self.internal_dispatch(action),
Some(middleware) => middleware(
&self,
Box::new(move |store, action| store.dispatch_with_middleware(action, index + 1)),
action,
),
}
}
fn internal_dispatch(&self, action: A) {
let new_state: S = self.reducer.as_ref()(action.clone(), self.state.borrow().clone());
self.state.replace(new_state.clone());
for callback in self.callbacks.borrow().deref() {
let latest = callback.latest.take();
match latest {
None => {
callback.latest.replace(Some(new_state.clone()));
callback.run.deref()(action.clone(), &new_state);
}
Some(former_state) => {
if callback.select.deref()(&former_state, &new_state) {
callback.latest.replace(Some(new_state.clone()));
callback.run.deref()(action.clone(), &new_state);
}
}
}
}
}
}
struct Callback<A, S>
where
A: Clone + Eq + 'static,
S: LifecycleState + Clone + Eq + Default + 'static,
{
latest: RefCell<Option<S>>,
select: Box<dyn Fn(&S, &S) -> bool>,
run: Box<dyn Fn(A, &S)>,
}
#[macro_export]
macro_rules! slice {
(
$state:ident {
$($state_field:ident: $state_field_type:ty = $state_field_initializer:expr),*
}
$action:ident {
$(
$action_field:ident $( ( $($action_field_arg:ty),*) )?
),*
}
) => {
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct $state {
$(
pub $state_field: $state_field_type
),*
}
impl Default for $state {
fn default() -> Self {
$state {
$($state_field: $state_field_initializer),*
}
}
}
#[derive(Clone, Eq, PartialEq, Debug)]
pub enum $action {
$($action_field$(( $($action_field_arg),* ))?),*
}
}
}
#[macro_export]
macro_rules! store {
(
$(
$state_field:ident: $action_field:ident =
$($slice_path:ident)::* :: {$slice_state:ident, $slice_action:ident, $slice_reducer:ident}
),*
) => {
#[derive(Clone, Eq, PartialEq, Debug, Default)]
pub struct State {
$(
pub $state_field: $($slice_path)::*::$slice_state,
)*
running: bool
}
impl gstore::prelude::LifecycleState for State {
fn running(&self) -> bool {
self.running
}
}
#[derive(Clone, Eq, PartialEq, Debug)]
pub enum Action {
$(
$action_field( $($slice_path)::*::$slice_action ),
)*
Start,
Stop
}
pub fn root_reducer(action: Action, state: State) -> State {
match action {
$(
Action::$action_field(slice_action) => State {
$state_field: $($slice_path)::*::$slice_reducer(slice_action, state.$state_field),
..state
},
)*
Action::Start => State { running: true, ..state},
Action::Stop => State { running: false, ..state},
}
}
pub type Store = gstore::prelude::Store<Action, State>;
}
}
#[macro_export]
macro_rules! use_select {
(
$selector_name:ident: $t:ty = |$store:ident| $selector:expr
) => {{
unsafe {
if _RENDER_STORE_REF.is_none() {
*_RENDER_STORE_REF = Some($store.clone())
}
}
let $store = $store.clone();
$store.register(
|a, b| $selector(a) != $selector(b),
|state| unsafe {
for callback in _RENDER_CALLBACKS.deref() {
callback()
}
},
);
}
fn $selector_name() -> $t {
unsafe { _RENDER_STORE_REF.as_ref().unwrap().select($selector) }
}};
}
#[cfg(test)]
mod test {
use crate::store;
use std::cell::RefCell;
use std::ops::Deref;
use std::rc::Rc;
#[derive(Clone, Debug, Eq, PartialEq)]
enum Action {
Inc,
Dec,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
struct State {
value: u8,
}
impl store::LifecycleState for State {
fn running(&self) -> bool {
false
}
}
type Store = store::Store<Action, State>;
#[test]
fn test_store_send_and_reduce() {
let store = Store::new(
|action, state| match action {
Action::Inc => State {
value: state.value + 1,
},
Action::Dec => State {
value: state.value - 1,
},
},
vec![],
);
assert_eq!(store.select(|s| s.value), 0);
store.dispatch(Action::Inc);
assert_eq!(store.select(|s| s.value), 1);
store.dispatch(Action::Dec);
assert_eq!(store.select(|s| s.value), 0);
}
#[test]
fn test_store_middlewares() {
let logs: Rc<RefCell<Vec<String>>> = Rc::new(RefCell::new(Vec::new()));
let l1 = logs.clone();
let l2 = logs.clone();
let store = Store::new(
|action, state| match action {
Action::Inc => State {
value: state.value + 1,
},
Action::Dec => State {
value: state.value - 1,
},
},
vec![
Box::new(move |store, next, action| {
let mut a = l1.take();
a.push(format!("Handling {:?}", action));
l1.replace(a);
next(store, action)
}),
Box::new(move |store, next, action| {
next(store, action.clone());
let mut a = l2.take();
a.push(format!("Handled {:?}", action));
l2.replace(a);
}),
],
);
store.dispatch(Action::Inc);
let l = logs.deref().take();
assert_eq!(
l,
vec![String::from("Handling Inc"), String::from("Handled Inc")]
);
logs.replace(l);
store.dispatch(Action::Dec);
let l = logs.deref().take();
assert_eq!(
l,
vec![
String::from("Handling Inc"),
String::from("Handled Inc"),
String::from("Handling Dec"),
String::from("Handled Dec")
]
);
}
}