freya_core/lifecycle/
memo.rs

1use std::{
2    mem::MaybeUninit,
3    ops::Deref,
4};
5
6use crate::{
7    prelude::{
8        ReadRef,
9        State,
10        spawn,
11        use_hook,
12    },
13    reactive_context::ReactiveContext,
14};
15
16pub fn use_memo<T: 'static + PartialEq>(callback: impl FnMut() -> T + 'static) -> Memo<T> {
17    use_hook(|| Memo::create(callback))
18}
19
20pub struct Memo<T> {
21    state: State<T>,
22}
23
24impl<T> Clone for Memo<T> {
25    fn clone(&self) -> Self {
26        *self
27    }
28}
29impl<T> Copy for Memo<T> {}
30
31/// Allow calling the states as functions.
32/// Limited to `Copy` values only.
33impl<T: Copy + PartialEq + 'static> Deref for Memo<T> {
34    type Target = dyn Fn() -> T;
35
36    fn deref(&self) -> &Self::Target {
37        unsafe { Memo::deref_impl(self) }
38    }
39}
40
41impl<T: PartialEq> Memo<T> {
42    /// Adapted from https://github.com/DioxusLabs/dioxus/blob/a4aef33369894cd6872283d6d7d265303ae63913/packages/signals/src/read.rs#L246
43    /// SAFETY: You must call this function directly with `self` as the argument.
44    /// This function relies on the size of the object you return from the deref
45    /// being the same as the object you pass in
46    #[doc(hidden)]
47    unsafe fn deref_impl<'a>(memo: &Memo<T>) -> &'a dyn Fn() -> T
48    where
49        Self: Sized + 'a,
50        T: Clone + 'static,
51    {
52        // https://github.com/dtolnay/case-studies/tree/master/callable-types
53
54        // First we create a closure that captures something with the Same in memory layout as Self (MaybeUninit<Self>).
55        let uninit_callable = MaybeUninit::<Self>::uninit();
56        // Then move that value into the closure. We assume that the closure now has a in memory layout of Self.
57        let uninit_closure = move || Memo::read(unsafe { &*uninit_callable.as_ptr() }).clone();
58
59        // Check that the size of the closure is the same as the size of Self in case the compiler changed the layout of the closure.
60        let size_of_closure = std::mem::size_of_val(&uninit_closure);
61        assert_eq!(size_of_closure, std::mem::size_of::<Self>());
62
63        // Then cast the lifetime of the closure to the lifetime of &self.
64        fn cast_lifetime<'a, T>(_a: &T, b: &'a T) -> &'a T {
65            b
66        }
67        let reference_to_closure = cast_lifetime(
68            {
69                // The real closure that we will never use.
70                &uninit_closure
71            },
72            #[allow(clippy::missing_transmute_annotations)]
73            // We transmute self into a reference to the closure. This is safe because we know that the closure has the same memory layout as Self so &Closure == &Self.
74            unsafe {
75                std::mem::transmute(memo)
76            },
77        );
78
79        // Cast the closure to a trait object.
80        reference_to_closure as &_
81    }
82}
83
84impl<T: 'static + PartialEq> Memo<T> {
85    pub fn create(mut callback: impl FnMut() -> T + 'static) -> Memo<T> {
86        let (rx, rc) = ReactiveContext::new_for_task();
87        let mut state = State::create(ReactiveContext::run(rc.clone(), &mut callback));
88        spawn(async move {
89            loop {
90                rx.notified().await;
91                state.set_if_modified(ReactiveContext::run(rc.clone(), &mut callback));
92            }
93        });
94        Memo { state }
95    }
96
97    pub fn read(&self) -> ReadRef<'static, T> {
98        self.state.read()
99    }
100
101    pub fn peek(&self) -> ReadRef<'static, T> {
102        self.state.peek()
103    }
104}