use std::{
borrow::Borrow,
cell::RefCell,
collections::{HashMap, LinkedList},
rc::Rc,
sync::Arc,
};
use async_trait::async_trait;
use uuid::Uuid;
pub trait Actionable: Sync + Send {
fn name(&self) -> &'static str;
fn list() -> Vec<Self>
where
Self: Sized;
fn try_from_name(name: &str) -> Option<Self>
where
Self: Sized;
fn keybinding(&self) -> Option<Keybinding> {
None
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum Keybinding {
Shift(&'static str),
Super(&'static str),
Alt(&'static str),
Ctrl(&'static str),
Just(&'static str),
}
type Reducer<A, S> = Box<dyn Fn(&A, &mut S)>;
type ArcMiddleware<A, S> = Arc<Box<dyn Middleware<A, S>>>;
pub struct Store<A, S>
where
A: Actionable + Clone + std::fmt::Debug + Send + Sync + 'static,
S: Clone + std::fmt::Debug + Default + Send + Sync + 'static,
{
sender: &'static Sender<A>,
state: RefCell<S>,
reducer: RefCell<Option<Reducer<A, S>>>,
middlewares: RefCell<LinkedList<ArcMiddleware<A, S>>>,
selectors: RefCell<HashMap<Uuid, Selector<S>>>,
}
impl<
A: Actionable + Clone + std::fmt::Debug + Send + Sync + 'static,
S: Clone + std::fmt::Debug + Default + Send + Sync + 'static,
> std::fmt::Debug for Store<A, S>
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Store")
.field("state", &self.state)
.field("reducer", &self.reducer.borrow().is_some())
.field("middlewares", &self.middlewares.borrow().len())
.field("selectors", &self.selectors.borrow().len())
.finish()
}
}
#[async_trait]
pub trait Middleware<A, S>: Sync + Send {
async fn pre_reduce(&self, _: Arc<A>, _: Arc<S>);
async fn post_reduce(&self, _: Arc<A>, _: Arc<S>);
}
type Selection<S> = Rc<dyn Fn(&S, &S) -> bool + 'static>;
#[derive(Clone)]
struct Selector<S: Clone> {
name: &'static str,
selector: Selection<S>,
callback: Rc<dyn Fn(&S) + 'static>,
}
impl<A, S> Store<A, S>
where
A: Actionable + Clone + std::fmt::Debug + Send + Sync + 'static,
S: Clone + std::fmt::Debug + Default + Send + Sync + 'static,
{
pub fn new(sender: &'static Sender<A>) -> Self {
Self {
sender,
reducer: Default::default(),
state: Default::default(),
middlewares: Default::default(),
selectors: Default::default(),
}
}
pub fn init(
self: &Rc<Self>,
reducer: impl Fn(&A, &mut S) + 'static,
send_action: impl Fn(A) + Send + Sync + 'static,
) -> Box<dyn Fn(A)> {
*self.sender.try_write().unwrap() = Some(Box::new(send_action));
*self.reducer.borrow_mut() = Some(Box::new(reducer));
let s = self.clone();
Box::new(move |a| s.reduce(a))
}
pub fn reduce(self: &Rc<Self>, action: A) {
log::trace!("[gstore] Reduce {:?}", action);
let old_state = Arc::new(self.state.borrow().clone());
let middlewares: LinkedList<Arc<Box<dyn Middleware<A, S>>>> =
self.middlewares.borrow().iter().cloned().collect();
let mi_a = Arc::new(action.clone());
let mi_s = old_state.clone();
async_std::task::spawn(async move {
for mi in middlewares {
mi.pre_reduce(mi_a.clone(), mi_s.clone()).await;
}
});
if let Some(r) = &*self.reducer.borrow() {
r(&action, &mut *self.state.borrow_mut());
}
self.call_selectors(old_state.borrow());
let middlewares: LinkedList<Arc<Box<dyn Middleware<A, S>>>> =
self.middlewares.borrow().iter().cloned().collect();
let mi_a = Arc::new(action);
let mi_s = Arc::new(self.state.borrow().clone());
async_std::task::spawn(async move {
for mi in middlewares {
mi.post_reduce(mi_a.clone(), mi_s.clone()).await;
}
});
}
pub fn append(self: &Rc<Self>, middleware: Box<dyn Middleware<A, S>>) {
self.middlewares
.borrow_mut()
.push_back(Arc::new(middleware));
}
fn call_selectors(&self, old_state: &S) {
let borrow = self.selectors.borrow().clone();
for selector in borrow.values() {
log::trace!("call selector for {}", selector.name);
let sel = &selector.selector;
let state = &*self.state.borrow();
let updated = sel(old_state, state);
if updated {
let cb = &selector.callback;
cb(state);
}
}
}
#[allow(clippy::result_unit_err)]
pub fn select(
self: &Rc<Self>,
selector: impl Fn(&S, &S) -> bool + 'static,
handler: impl Fn(&S) + 'static,
) -> Result<Rc<dyn Fn()>, ()> {
self.select_dbg("", selector, handler)
}
#[allow(clippy::result_unit_err)]
pub fn select_dbg(
self: &Rc<Self>,
name: &'static str,
selector: impl Fn(&S, &S) -> bool + 'static,
handler: impl Fn(&S) + 'static,
) -> Result<Rc<dyn Fn()>, ()> {
handler(&*self.state.borrow());
let id = Uuid::new_v4();
self.selectors.borrow_mut().insert(
id,
Selector {
name,
selector: Rc::new(selector),
callback: Rc::new(handler),
},
);
let s = self.clone();
Ok(Rc::new(move || {
s.selectors.borrow_mut().remove(&id);
}))
}
pub fn dispatch(self: &Rc<Self>, action: impl Into<A>) {
async_std::task::block_on(async {
if let Some(sender) = &*self.sender.read().await {
sender(action.into());
}
})
}
}
pub type Sender<A> = async_std::sync::RwLock<Option<Box<dyn Fn(A) + Sync + Send + 'static>>>;
#[macro_export]
macro_rules! store {
(
$action:ty, $state:ty
) => {
impl Into<&str> for $action {
fn into(self) -> &'static str {
$crate::Actionable::name(&self)
}
}
impl Into<&str> for &$action {
fn into(self) -> &'static str {
$crate::Actionable::name(self)
}
}
type Selector = dyn Fn(&$state, &$state) -> bool;
static SEND_EVENT: $crate::once_cell::sync::Lazy<$crate::Sender<Action>> =
$crate::once_cell::sync::Lazy::new(|| Default::default());
type Store = $crate::Store<$action, $state>;
thread_local! {
static STORE: std::rc::Rc<Store> = std::rc::Rc::new(Store::new(&SEND_EVENT));
}
pub(crate) fn store() -> std::rc::Rc<Store> {
STORE.with(|s| s.clone())
}
};
}