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