use std::cell::RefCell;
use std::fmt;
use std::marker::PhantomData;
use std::ops::Deref;
use std::rc::Rc;
use implicit_clone::ImplicitClone;
use crate::functional::{hook, Hook, HookContext};
use crate::html::IntoPropValue;
use crate::Callback;
type DispatchFn<T> = Rc<dyn Fn(<T as Reducible>::Action)>;
pub trait Reducible {
type Action;
fn reduce(self: Rc<Self>, action: Self::Action) -> Rc<Self>;
}
struct UseReducer<T>
where
T: Reducible,
{
current_state: Rc<RefCell<Rc<T>>>,
dispatch: DispatchFn<T>,
}
pub struct UseReducerHandle<T>
where
T: Reducible,
{
current_state: Rc<RefCell<Rc<T>>>,
deref_history: RefCell<Vec<Rc<T>>>,
dispatch: DispatchFn<T>,
}
impl<T> UseReducerHandle<T>
where
T: Reducible,
{
pub fn dispatch(&self, value: T::Action) {
(self.dispatch)(value)
}
pub fn dispatcher(&self) -> UseReducerDispatcher<T> {
UseReducerDispatcher {
dispatch: self.dispatch.clone(),
}
}
}
impl<T> Deref for UseReducerHandle<T>
where
T: Reducible,
{
type Target = T;
fn deref(&self) -> &Self::Target {
let rc = match self.current_state.try_borrow() {
Ok(shared) => Rc::clone(&*shared),
Err(_) => {
let history = self.deref_history.borrow();
Rc::clone(history.last().expect("deref_history is never empty"))
}
};
let ptr: *const T = Rc::as_ptr(&rc);
{
let mut history = self.deref_history.borrow_mut();
if !Rc::ptr_eq(history.last().expect("deref_history is never empty"), &rc) {
history.push(rc);
}
}
unsafe { &*ptr }
}
}
impl<T> Clone for UseReducerHandle<T>
where
T: Reducible,
{
fn clone(&self) -> Self {
let snapshot = match self.current_state.try_borrow() {
Ok(shared) => Rc::clone(&*shared),
Err(_) => {
let history = self.deref_history.borrow();
Rc::clone(history.last().expect("deref_history is never empty"))
}
};
Self {
current_state: Rc::clone(&self.current_state),
deref_history: RefCell::new(vec![snapshot]),
dispatch: Rc::clone(&self.dispatch),
}
}
}
impl<T> fmt::Debug for UseReducerHandle<T>
where
T: Reducible + fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let value = if let Ok(rc_ref) = self.current_state.try_borrow() {
format!("{:?}", *rc_ref)
} else {
let history = self.deref_history.borrow();
format!(
"{:?}",
**history.last().expect("deref_history is never empty")
)
};
f.debug_struct("UseReducerHandle")
.field("value", &value)
.finish()
}
}
impl<T> PartialEq for UseReducerHandle<T>
where
T: Reducible + PartialEq,
{
fn eq(&self, rhs: &Self) -> bool {
let self_snapshot = self.deref_history.borrow();
let rhs_snapshot = rhs.deref_history.borrow();
*self_snapshot[0] == *rhs_snapshot[0]
}
}
impl<T> ImplicitClone for UseReducerHandle<T> where T: Reducible {}
pub struct UseReducerDispatcher<T>
where
T: Reducible,
{
dispatch: DispatchFn<T>,
}
impl<T> Clone for UseReducerDispatcher<T>
where
T: Reducible,
{
fn clone(&self) -> Self {
Self {
dispatch: Rc::clone(&self.dispatch),
}
}
}
impl<T> fmt::Debug for UseReducerDispatcher<T>
where
T: Reducible + fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("UseReducerDispatcher").finish()
}
}
impl<T> PartialEq for UseReducerDispatcher<T>
where
T: Reducible,
{
fn eq(&self, rhs: &Self) -> bool {
#[allow(ambiguous_wide_pointer_comparisons)]
Rc::ptr_eq(&self.dispatch, &rhs.dispatch)
}
}
impl<T> ImplicitClone for UseReducerDispatcher<T> where T: Reducible {}
impl<T> From<UseReducerDispatcher<T>> for Callback<<T as Reducible>::Action>
where
T: Reducible,
{
fn from(val: UseReducerDispatcher<T>) -> Self {
Callback { cb: val.dispatch }
}
}
impl<T> IntoPropValue<Callback<<T as Reducible>::Action>> for UseReducerDispatcher<T>
where
T: Reducible,
{
fn into_prop_value(self) -> Callback<<T as Reducible>::Action> {
Callback { cb: self.dispatch }
}
}
impl<T> UseReducerDispatcher<T>
where
T: Reducible,
{
pub fn dispatch(&self, value: T::Action) {
(self.dispatch)(value)
}
pub fn to_callback(&self) -> Callback<<T as Reducible>::Action> {
Callback {
cb: self.dispatch.clone(),
}
}
}
fn use_reducer_base<'hook, T>(
init_fn: impl 'hook + FnOnce() -> T,
should_render_fn: fn(&T, &T) -> bool,
) -> impl 'hook + Hook<Output = UseReducerHandle<T>>
where
T: Reducible + 'static,
{
struct HookProvider<'hook, T, F>
where
T: Reducible + 'static,
F: 'hook + FnOnce() -> T,
{
_marker: PhantomData<&'hook ()>,
init_fn: F,
should_render_fn: fn(&T, &T) -> bool,
}
impl<'hook, T, F> Hook for HookProvider<'hook, T, F>
where
T: Reducible + 'static,
F: 'hook + FnOnce() -> T,
{
type Output = UseReducerHandle<T>;
fn run(self, ctx: &mut HookContext) -> Self::Output {
let Self {
init_fn,
should_render_fn,
..
} = self;
let state = ctx.next_state(move |re_render| {
let val = Rc::new(RefCell::new(Rc::new(init_fn())));
let should_render_fn = Rc::new(should_render_fn);
UseReducer {
current_state: val.clone(),
dispatch: Rc::new(move |action: T::Action| {
let should_render = {
let should_render_fn = should_render_fn.clone();
let mut val = val.borrow_mut();
let next_val = (*val).clone().reduce(action);
let should_render = should_render_fn(&next_val, &val);
*val = next_val;
should_render
};
if should_render {
re_render()
}
}),
}
});
let current_state = state.current_state.clone();
let snapshot = state.current_state.borrow().clone();
let dispatch = state.dispatch.clone();
UseReducerHandle {
current_state,
deref_history: RefCell::new(vec![snapshot]),
dispatch,
}
}
}
HookProvider {
_marker: PhantomData,
init_fn,
should_render_fn,
}
}
#[hook]
pub fn use_reducer<T, F>(init_fn: F) -> UseReducerHandle<T>
where
T: Reducible + 'static,
F: FnOnce() -> T,
{
use_reducer_base(init_fn, |a, b| !address_eq(a, b))
}
#[hook]
pub fn use_reducer_eq<T, F>(init_fn: F) -> UseReducerHandle<T>
where
T: Reducible + PartialEq + 'static,
F: FnOnce() -> T,
{
use_reducer_base(init_fn, |a, b| !address_eq(a, b) && a != b)
}
fn address_eq<T>(a: &T, b: &T) -> bool {
std::ptr::eq(a as *const T, b as *const T)
}