bloom_core/
state.rs

1use std::{any::Any, collections::HashMap, hash::Hash, ops::Deref, sync::Arc};
2
3use async_channel::{bounded, Sender};
4use async_context::with_async_context_mut;
5
6use crate::{hook::Hook, Element};
7
8pub(crate) struct StateUpdate {
9    update: Box<
10        dyn FnOnce(Option<Arc<dyn Any + Send + Sync>>) -> Arc<dyn Any + Send + Sync>
11            + Send
12            + 'static,
13    >,
14    index: u16,
15}
16
17impl StateUpdate {
18    pub(crate) fn apply(self, state: &mut HashMap<u16, Arc<dyn Any + Send + Sync>>) {
19        let this_state = state.get_mut(&self.index).cloned();
20
21        let update = self.update;
22
23        let new_state = update(this_state);
24
25        state.insert(self.index, new_state);
26    }
27}
28
29/// The state object can be dereferenced to obtain the current value.
30/// ```
31/// let my_state = use_state(|| 0);
32///
33/// assert_eq!(0, *my_state);
34/// ```
35///
36/// It's update-method can be used to change the state.
37/// ```
38/// my_state.update(|value| *value + 1);
39/// ```
40/// This will trigger a re-render of the component.
41#[derive(Clone)]
42pub struct State<T> {
43    value: Arc<T>,
44    signal: Sender<()>,
45    updater: Sender<StateUpdate>,
46    index: u16,
47}
48
49impl<T> Deref for State<T> {
50    type Target = T;
51
52    fn deref(&self) -> &Self::Target {
53        self.value.as_ref()
54    }
55}
56
57impl<T> Hash for State<T> {
58    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
59        Arc::as_ptr(&self.value).hash(state);
60        self.index.hash(state);
61    }
62}
63
64impl<N, E, T> From<State<T>> for Element<N, E>
65where
66    N: From<String>,
67    T: ToString,
68{
69    fn from(value: State<T>) -> Self {
70        let value: &T = &value;
71        Element::Node(N::from(value.to_string()), Vec::new())
72    }
73}
74
75impl<T> State<T>
76where
77    T: Send + Sync + 'static,
78{
79    fn mock(value: T) -> Self {
80        let (mock_signal, _) = bounded(0);
81        let (mock_updater, _) = bounded(0);
82        State {
83            value: Arc::new(value),
84            signal: mock_signal,
85            updater: mock_updater,
86            index: 0,
87        }
88    }
89
90    pub fn update<C, R>(&self, callback: C)
91    where
92        R: Into<Arc<T>>,
93        C: FnOnce(Arc<T>) -> R + Send + Sync + 'static,
94    {
95        let current_value = self.value.clone();
96        self.updater
97            .try_send(StateUpdate {
98                update: Box::new(move |value| {
99                    let typed_value = value
100                        .map(|value| value.downcast().expect("Invalid state hook"))
101                        .unwrap_or(current_value);
102                    callback(typed_value).into()
103                }),
104                index: self.index,
105            })
106            .expect("Failed to send update");
107        let _ = self.signal.try_send(());
108    }
109}
110
111/// Analog to react's useState API.
112/// Pass a callback to build the initial state.
113/// The returned State-object can be used to read and update the state.
114pub fn use_state<T, D>(default: D) -> State<T>
115where
116    T: Send + Sync + 'static,
117    D: FnOnce() -> T,
118{
119    with_async_context_mut(|hook: Option<&mut Hook>| {
120        if let Some(hook) = hook {
121            let signal = hook.signal.clone();
122            let updater = hook.updater.clone();
123            let index = hook.state_index;
124            hook.state_index += 1;
125            if let Some(value) = hook.state.get(&index) {
126                let value: Arc<T> = value
127                    .clone()
128                    .downcast()
129                    .expect("Invalid Hook Call: Type mismatch");
130                State {
131                    value,
132                    signal,
133                    updater,
134                    index,
135                }
136            } else {
137                let value = Arc::new(default());
138                State {
139                    value,
140                    signal,
141                    updater,
142                    index,
143                }
144            }
145        } else {
146            State::mock(default())
147        }
148    })
149}