actuate/compose/
mod.rs

1use crate::{
2    composer::{ComposePtr, Node, Runtime},
3    data::Data,
4    use_context, use_ref, Scope, ScopeData, ScopeState,
5};
6use alloc::borrow::Cow;
7use alloc::rc::Rc;
8use core::{
9    any::TypeId,
10    cell::{Cell, RefCell, UnsafeCell},
11    fmt, mem,
12};
13use slotmap::{DefaultKey, SlotMap};
14
15mod catch;
16pub use self::catch::{catch, Catch};
17
18mod dyn_compose;
19pub use self::dyn_compose::{dyn_compose, DynCompose};
20
21mod from_fn;
22pub use self::from_fn::{from_fn, FromFn};
23
24mod from_iter;
25pub use self::from_iter::{from_iter, FromIter};
26
27mod memo;
28pub use self::memo::{memo, Memo};
29
30/// A composable function.
31///
32/// For a dynamically-typed composable, see [`DynCompose`].
33///
34/// Composables are the building blocks of reactivity in Actuate.
35/// A composable is essentially a function that is re-run whenever its state (or its parent state) is changed.
36/// Composables may return one or more children, that run after their parent.
37///
38/// When a composable is re-run, we call that "recomposition".
39/// For example, on the initial composition, hooks may initialize their state.
40/// Then on recomposition, hooks update their state from the last set value.
41///
42/// Triggering a state update will recompose each parent, and then each child,
43/// until either a [`Memo`] is reached or the composition is complete.
44///
45/// [`Memo`] is special in that it will only recompose in two cases:
46/// 1. It's provided dependencies have changed (see [`memo()`] for more)
47/// 2. Its own state has changed, which will then trigger the above parent-to-child process for its children.
48#[must_use = "Composables do nothing unless composed or returned from other composables."]
49pub trait Compose: Data {
50    /// Compose this function.
51    fn compose(cx: Scope<Self>) -> impl Compose;
52
53    #[doc(hidden)]
54    fn name() -> Option<Cow<'static, str>> {
55        let name = core::any::type_name::<Self>();
56        Some(
57            name.split('<')
58                .next()
59                .unwrap_or(name)
60                .split("::")
61                .last()
62                .unwrap_or(name)
63                .into(),
64        )
65    }
66}
67
68impl Compose for () {
69    fn compose(cx: Scope<Self>) -> impl Compose {
70        let _ = cx;
71    }
72}
73
74impl<C: Compose> Compose for Option<C> {
75    fn compose(cx: Scope<Self>) -> impl Compose {
76        let child_key = use_ref(&cx, || Cell::new(None));
77
78        let rt = Runtime::current();
79        let mut nodes = rt.nodes.borrow_mut();
80
81        if let Some(content) = &*cx.me() {
82            if let Some(key) = child_key.get() {
83                let last = nodes.get_mut(key).unwrap();
84
85                let ptr = content as *const dyn AnyCompose;
86                let ptr: *const dyn AnyCompose = unsafe { mem::transmute(ptr) };
87
88                *last.compose.borrow_mut() = ComposePtr::Ptr(ptr);
89
90                drop(nodes);
91
92                rt.queue(key);
93            } else {
94                let ptr: *const dyn AnyCompose =
95                    unsafe { mem::transmute(content as *const dyn AnyCompose) };
96                let key = nodes.insert(Rc::new(Node {
97                    compose: RefCell::new(crate::composer::ComposePtr::Ptr(ptr)),
98                    scope: ScopeData::default(),
99                    parent: Some(rt.current_key.get()),
100                    children: RefCell::new(Vec::new()),
101                    child_idx: 0,
102                }));
103                child_key.set(Some(key));
104
105                nodes
106                    .get(rt.current_key.get())
107                    .unwrap()
108                    .children
109                    .borrow_mut()
110                    .push(key);
111
112                let child_state = &nodes[key].scope;
113
114                *child_state.contexts.borrow_mut() = cx.contexts.borrow().clone();
115                child_state
116                    .contexts
117                    .borrow_mut()
118                    .values
119                    .extend(cx.child_contexts.borrow().values.clone());
120
121                drop(nodes);
122
123                rt.queue(key);
124            }
125        } else if let Some(key) = child_key.get() {
126            child_key.set(None);
127
128            drop_node(&mut nodes, key);
129        }
130    }
131}
132
133// TODO replace with non-recursive algorithm.
134fn drop_node(nodes: &mut SlotMap<DefaultKey, Rc<Node>>, key: DefaultKey) {
135    let node = nodes[key].clone();
136    if let Some(parent) = node.parent {
137        let parent = nodes.get_mut(parent).unwrap();
138        parent.children.borrow_mut().retain(|&x| x != key);
139    }
140
141    let children = node.children.borrow().clone();
142    for key in children {
143        drop_node(nodes, key)
144    }
145
146    nodes.remove(key);
147}
148
149/// Composable error.
150///
151/// This can be handled by a parent composable with [`Catch`].
152#[derive(Data, thiserror::Error)]
153#[actuate(path = "crate")]
154pub struct Error {
155    make_error: Box<dyn Fn() -> Box<dyn core::error::Error>>,
156}
157
158impl Error {
159    /// Create a new composable error.
160    pub fn new(error: impl core::error::Error + Clone + 'static) -> Self {
161        Self {
162            make_error: Box::new(move || Box::new(error.clone())),
163        }
164    }
165}
166
167impl fmt::Debug for Error {
168    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
169        (self.make_error)().fmt(f)
170    }
171}
172
173impl fmt::Display for Error {
174    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
175        (self.make_error)().fmt(f)
176    }
177}
178
179impl<C: Compose> Compose for Result<C, Error> {
180    fn compose(cx: Scope<Self>) -> impl Compose {
181        let catch_cx = use_context::<CatchContext>(&cx).unwrap();
182
183        let child_key = use_ref(&cx, || Cell::new(None));
184
185        let rt = Runtime::current();
186
187        match &*cx.me() {
188            Ok(content) => {
189                if let Some(key) = child_key.get() {
190                    let mut nodes = rt.nodes.borrow_mut();
191                    let last = nodes.get_mut(key).unwrap();
192
193                    let ptr = content as *const dyn AnyCompose;
194                    let ptr: *const dyn AnyCompose = unsafe { mem::transmute(ptr) };
195
196                    *last.compose.borrow_mut() = ComposePtr::Ptr(ptr);
197
198                    drop(nodes);
199
200                    rt.queue(key);
201                } else {
202                    let mut nodes = rt.nodes.borrow_mut();
203                    let ptr: *const dyn AnyCompose =
204                        unsafe { mem::transmute(content as *const dyn AnyCompose) };
205                    let key = nodes.insert(Rc::new(Node {
206                        compose: RefCell::new(crate::composer::ComposePtr::Ptr(ptr)),
207                        scope: ScopeData::default(),
208                        parent: Some(rt.current_key.get()),
209                        children: RefCell::new(Vec::new()),
210                        child_idx: 0,
211                    }));
212                    child_key.set(Some(key));
213
214                    nodes
215                        .get(rt.current_key.get())
216                        .unwrap()
217                        .children
218                        .borrow_mut()
219                        .push(key);
220
221                    let child_state = &nodes[key].scope;
222
223                    *child_state.contexts.borrow_mut() = cx.contexts.borrow().clone();
224                    child_state
225                        .contexts
226                        .borrow_mut()
227                        .values
228                        .extend(cx.child_contexts.borrow().values.clone());
229
230                    drop(nodes);
231
232                    rt.queue(key);
233                }
234            }
235            Err(error) => {
236                let mut nodes = rt.nodes.borrow_mut();
237
238                if let Some(key) = child_key.get() {
239                    drop_node(&mut nodes, key);
240                }
241
242                (catch_cx.f)((error.make_error)())
243            }
244        }
245    }
246}
247
248pub(crate) struct CatchContext {
249    f: Rc<dyn Fn(Box<dyn core::error::Error>)>,
250}
251
252impl CatchContext {
253    pub(crate) fn new(f: impl Fn(Box<dyn core::error::Error>) + 'static) -> Self {
254        Self { f: Rc::new(f) }
255    }
256}
257
258macro_rules! impl_tuples {
259    ($($t:tt : $idx:tt),*) => {
260        unsafe impl<$($t: Data),*> Data for ($($t,)*) {}
261
262        impl<$($t: Compose),*> Compose for ($($t,)*) {
263            fn compose(cx: Scope<Self>) -> impl Compose {
264                $({
265                    let ptr: *const dyn AnyCompose = unsafe { mem::transmute(&cx.me().$idx as *const dyn AnyCompose) };
266                    let (key, _) = use_node(&cx, ComposePtr::Ptr(ptr), $idx);
267
268                    let rt = Runtime::current();
269                    rt.queue(key)
270                })*
271            }
272
273            fn name() -> Option<Cow<'static, str>> {
274                None
275            }
276        }
277    };
278}
279
280impl_tuples!(T1:0);
281impl_tuples!(T1:0, T2:1);
282impl_tuples!(T1:0, T2:1, T3:2);
283impl_tuples!(T1:0, T2:1, T3:2, T4:3);
284impl_tuples!(T1:0, T2:1, T3:2, T4:3, T5:4);
285impl_tuples!(T1:0, T2:1, T3:2, T4:3, T5:4, T6:5);
286impl_tuples!(T1:0, T2:1, T3:2, T4:3, T5:4, T6:5, T7:6);
287impl_tuples!(T1:0, T2:1, T3:2, T4:3, T5:4, T6:5, T7:6, T8:7);
288
289impl<C> Compose for Vec<C>
290where
291    C: Compose,
292{
293    fn compose(cx: Scope<Self>) -> impl Compose {
294        for (idx, item) in cx.me().iter().enumerate() {
295            let ptr: *const dyn AnyCompose =
296                unsafe { mem::transmute(item as *const dyn AnyCompose) };
297            let (key, _) = use_node(&cx, ComposePtr::Ptr(ptr), idx);
298
299            let rt = Runtime::current();
300            rt.queue(key);
301        }
302    }
303}
304
305fn use_node(
306    cx: ScopeState<'_>,
307    compose_ptr: ComposePtr,
308    child_idx: usize,
309) -> (DefaultKey, &Rc<Node>) {
310    let mut compose_ptr_cell = Some(compose_ptr);
311
312    let (key, node) = use_ref(cx, || {
313        let rt = Runtime::current();
314        let mut nodes = rt.nodes.borrow_mut();
315
316        let key = nodes.insert(Rc::new(Node {
317            compose: RefCell::new(compose_ptr_cell.take().unwrap()),
318            scope: ScopeData::default(),
319            parent: Some(rt.current_key.get()),
320            children: RefCell::new(Vec::new()),
321            child_idx,
322        }));
323
324        nodes
325            .get(rt.current_key.get())
326            .unwrap()
327            .children
328            .borrow_mut()
329            .push(key);
330
331        let child_state = &nodes[key].scope;
332        *child_state.contexts.borrow_mut() = cx.contexts.borrow().clone();
333        child_state
334            .contexts
335            .borrow_mut()
336            .values
337            .extend(cx.child_contexts.borrow().values.clone());
338
339        (key, nodes[key].clone())
340    });
341
342    // Reborrow the pointer to the node's composable.
343    if let Some(compose_ptr) = compose_ptr_cell.take() {
344        *node.compose.borrow_mut() = compose_ptr;
345    }
346
347    (*key, node)
348}
349
350pub(crate) trait AnyCompose {
351    fn data_id(&self) -> TypeId;
352
353    fn as_ptr_mut(&mut self) -> *mut ();
354
355    unsafe fn reborrow(&mut self, ptr: *mut ());
356
357    /// Safety: The caller must ensure `&self` is valid for the lifetime of `state`.
358    unsafe fn any_compose(&self, state: &ScopeData);
359
360    fn name(&self) -> Option<Cow<'static, str>>;
361}
362
363impl<C> AnyCompose for C
364where
365    C: Compose + Data,
366{
367    fn data_id(&self) -> TypeId {
368        typeid::of::<C>()
369    }
370
371    fn as_ptr_mut(&mut self) -> *mut () {
372        self as *mut Self as *mut ()
373    }
374
375    unsafe fn reborrow(&mut self, ptr: *mut ()) {
376        core::ptr::swap(self, ptr as _);
377    }
378
379    unsafe fn any_compose(&self, state: &ScopeData) {
380        // Reset the hook index.
381        state.hook_idx.set(0);
382
383        // Increment the scope's current generation.
384        state.generation.set(state.generation.get() + 1);
385
386        // Transmute the lifetime of `&Self`, `&ScopeData`, and the `Scope` containing both to the same`'a`.
387        // Safety: `self` and `state` are guranteed to have the same lifetime..
388        let state: ScopeState = unsafe { mem::transmute(state) };
389        let cx: Scope<'_, C> = Scope { me: self, state };
390        let cx: Scope<'_, C> = unsafe { mem::transmute(cx) };
391
392        // Cell for the Box used to re-allocate this composable.
393        let cell: &UnsafeCell<Option<Box<dyn AnyCompose>>> = use_ref(&cx, || UnsafeCell::new(None));
394        // Safety: This cell is only accessed by this composable.
395        let cell = unsafe { &mut *cell.get() };
396
397        let child_key_cell = use_ref(&cx, || Cell::new(None));
398
399        let rt = Runtime::current();
400
401        if cell.is_none() {
402            #[cfg(feature = "tracing")]
403            if let Some(name) = C::name() {
404                tracing::trace!("Compose: {}", name);
405            }
406
407            let child = C::compose(cx);
408
409            if child.data_id() == typeid::of::<()>() {
410                return;
411            }
412
413            let child: Box<dyn AnyCompose> = Box::new(child);
414            let mut child: Box<dyn AnyCompose> = unsafe { mem::transmute(child) };
415
416            let mut nodes = rt.nodes.borrow_mut();
417
418            unsafe {
419                if let Some(key) = child_key_cell.get() {
420                    let last = nodes.get_mut(key).unwrap();
421                    child.reborrow(last.compose.borrow_mut().as_ptr_mut());
422                } else {
423                    let child_key = nodes.insert(Rc::new(Node {
424                        compose: RefCell::new(crate::composer::ComposePtr::Boxed(child)),
425                        scope: ScopeData::default(),
426                        parent: Some(rt.current_key.get()),
427                        children: RefCell::new(Vec::new()),
428                        child_idx: 0,
429                    }));
430                    child_key_cell.set(Some(child_key));
431
432                    nodes
433                        .get(rt.current_key.get())
434                        .unwrap()
435                        .children
436                        .borrow_mut()
437                        .push(child_key);
438
439                    let child_state = &nodes[child_key].scope;
440
441                    *child_state.contexts.borrow_mut() = cx.contexts.borrow().clone();
442                    child_state
443                        .contexts
444                        .borrow_mut()
445                        .values
446                        .extend(cx.child_contexts.borrow().values.clone());
447                }
448            }
449        }
450
451        if let Some(key) = child_key_cell.get() {
452            rt.queue(key)
453        }
454    }
455
456    fn name(&self) -> Option<Cow<'static, str>> {
457        C::name()
458    }
459}