redux/
callback.rs

1#[cfg(feature = "serializable_callbacks")]
2use linkme::distributed_slice;
3
4pub use paste;
5use serde::{Deserialize, Serialize};
6use std::borrow::Cow;
7
8pub struct AnyAction(pub Box<dyn std::any::Any>);
9
10#[cfg(feature = "serializable_callbacks")]
11#[distributed_slice]
12pub static CALLBACKS: [(&str, fn(&str, Box<dyn std::any::Any>) -> AnyAction)];
13
14#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
15pub struct Callback<T> {
16    #[serde(skip, default = "default_fun_ptr")]
17    fun_ptr: Option<fn(T) -> AnyAction>,
18    pub fun_name: Cow<'static, str>,
19}
20
21fn default_fun_ptr<T>() -> Option<fn(T) -> AnyAction> {
22    None
23}
24
25impl<T: 'static> Callback<T> {
26    pub fn new(name: &'static str, fun_ptr: fn(T) -> AnyAction) -> Self {
27        Self {
28            fun_ptr: Some(fun_ptr),
29            fun_name: Cow::Borrowed(name),
30        }
31    }
32
33    pub fn call<Action>(&self, args: T) -> Action
34    where
35        Action: From<AnyAction>,
36    {
37        if let Some(fun) = self.fun_ptr {
38            return fun(args).into();
39        }
40
41        #[cfg(not(feature = "serializable_callbacks"))]
42        unimplemented!();
43
44        #[cfg(feature = "serializable_callbacks")]
45        {
46            // We reach this point only when the callback was deserialized
47            for (name, fun) in CALLBACKS {
48                if name == &self.fun_name {
49                    return fun(std::any::type_name::<T>(), Box::new(args)).into();
50                }
51            }
52
53            panic!("callback function {} not found", self.fun_name)
54        }
55    }
56}
57
58#[macro_export]
59macro_rules! _callback {
60    ($callback_name:ident, $action_ty:ty, $arg:tt, $arg_type:ty, $body:expr) => {{
61        use $crate::{AnyAction, Callback};
62
63        #[cfg(feature = "serializable_callbacks")]
64        use {$crate::CALLBACKS, linkme::distributed_slice};
65
66        redux::paste::paste! {
67            #[allow(unused)] // $arg is marked as unused, but it's used in `$body`
68            fn convert_impl($arg: $arg_type) -> AnyAction {
69                let action: $action_ty = ($body).into();
70                AnyAction(Box::new(action))
71            }
72
73            fn $callback_name(call_type: &str, args: Box<dyn std::any::Any>) -> AnyAction {
74                #[cfg(feature = "serializable_callbacks")]
75                {
76                    #[distributed_slice(CALLBACKS)]
77                    static CALLBACK_DESERIALIZE: (&str, fn(&str, Box<dyn std::any::Any>) -> AnyAction) = (
78                        stringify!($callback_name),
79                        $callback_name,
80                    );
81                }
82
83                let $arg = *args.downcast::<$arg_type>()
84                    .expect(&format!(
85                        "Invalid argument type: {}, expected: {}",
86                        call_type,
87                        stringify!($arg_type)));
88
89                convert_impl($arg)
90            }
91        }
92
93        Callback::new(stringify!($callback_name), convert_impl)
94    }};
95}
96
97/// Creates a callback instance. Must accept a single argument, so `()`
98/// should be used when no arguments are needed and tuples where
99/// more than one value need to be passed.
100///
101/// # Example
102///
103/// ```ignore
104/// callback!(task_done_callback(result: String) -> Action {
105///     SomeAction { result }
106/// })
107///
108/// callback!(multiple_arguments_callback((arg1: u64, arg2: u64)) -> Action {
109///     MultipleArgumentsAction { value: arg1 + arg2 }
110/// })
111/// ```
112#[macro_export]
113macro_rules! callback {
114    ($callback_name:ident(($($var:ident : $typ:ty),+)) -> $action_ty:ty $body:block) => {
115        $crate::_callback!($callback_name, $action_ty, ($($var),+), ($($typ),+), $body)
116    };
117    ($callback_name:ident($var:ident : $typ:ty) -> $action_ty:ty $body:block) => {
118        $crate::_callback!($callback_name, $action_ty, $var, $typ, $body)
119    };
120}