1use crate::warnings::{signal_read_and_write_in_reactive_scope, signal_write_in_component_body};
2use crate::write::Writable;
3use crate::{read::Readable, ReadableRef, Signal};
4use crate::{read_impls, GlobalMemo};
5use crate::{CopyValue, ReadOnlySignal};
6use std::{
7    cell::RefCell,
8    ops::Deref,
9    sync::{atomic::AtomicBool, Arc},
10};
11
12use dioxus_core::prelude::*;
13use futures_util::StreamExt;
14use generational_box::{AnyStorage, BorrowResult, UnsyncStorage};
15use warnings::Warning;
16
17struct UpdateInformation<T> {
18    dirty: Arc<AtomicBool>,
19    callback: RefCell<Box<dyn FnMut() -> T>>,
20}
21
22#[doc = include_str!("../docs/memo.md")]
23#[doc(alias = "Selector")]
24#[doc(alias = "UseMemo")]
25#[doc(alias = "Memorize")]
26pub struct Memo<T: 'static> {
27    inner: Signal<T>,
28    update: CopyValue<UpdateInformation<T>>,
29}
30
31impl<T> From<Memo<T>> for ReadOnlySignal<T>
32where
33    T: PartialEq,
34{
35    fn from(val: Memo<T>) -> Self {
36        ReadOnlySignal::new(val.inner)
37    }
38}
39
40impl<T: 'static> Memo<T> {
41    #[track_caller]
43    pub fn new(f: impl FnMut() -> T + 'static) -> Self
44    where
45        T: PartialEq,
46    {
47        Self::new_with_location(f, std::panic::Location::caller())
48    }
49
50    pub fn new_with_location(
52        mut f: impl FnMut() -> T + 'static,
53        location: &'static std::panic::Location<'static>,
54    ) -> Self
55    where
56        T: PartialEq,
57    {
58        let dirty = Arc::new(AtomicBool::new(false));
59        let (tx, mut rx) = futures_channel::mpsc::unbounded();
60
61        let callback = {
62            let dirty = dirty.clone();
63            move || {
64                dirty.store(true, std::sync::atomic::Ordering::Relaxed);
65                let _ = tx.unbounded_send(());
66            }
67        };
68        let rc =
69            ReactiveContext::new_with_callback(callback, current_scope_id().unwrap(), location);
70
71        let mut recompute = move || rc.reset_and_run_in(&mut f);
73        let value = recompute();
74        let recompute = RefCell::new(Box::new(recompute) as Box<dyn FnMut() -> T>);
75        let update = CopyValue::new(UpdateInformation {
76            dirty,
77            callback: recompute,
78        });
79        let state: Signal<T> = Signal::new_with_caller(value, location);
80
81        let memo = Memo {
82            inner: state,
83            update,
84        };
85
86        spawn_isomorphic(async move {
87            while rx.next().await.is_some() {
88                while rx.try_next().is_ok() {}
90                memo.recompute();
91            }
92        });
93
94        memo
95    }
96
97    #[track_caller]
123    pub const fn global(constructor: fn() -> T) -> GlobalMemo<T>
124    where
125        T: PartialEq,
126    {
127        GlobalMemo::new(constructor)
128    }
129
130    #[tracing::instrument(skip(self))]
132    fn recompute(&self)
133    where
134        T: PartialEq,
135    {
136        let mut update_copy = self.update;
137        let update_write = update_copy.write();
138        let peak = self.inner.peek();
139        let new_value = (update_write.callback.borrow_mut())();
140        if new_value != *peak {
141            drop(peak);
142            let mut copy = self.inner;
143            copy.set(new_value);
144        }
145        update_write
147            .dirty
148            .store(false, std::sync::atomic::Ordering::Relaxed);
149    }
150
151    pub fn origin_scope(&self) -> ScopeId {
153        self.inner.origin_scope()
154    }
155
156    pub fn id(&self) -> generational_box::GenerationalBoxId {
158        self.inner.id()
159    }
160}
161
162impl<T> Readable for Memo<T>
163where
164    T: PartialEq,
165{
166    type Target = T;
167    type Storage = UnsyncStorage;
168
169    #[track_caller]
170    fn try_read_unchecked(
171        &self,
172    ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> {
173        let read = self.inner.inner.try_read_unchecked()?;
175
176        let needs_update = self
177            .update
178            .read()
179            .dirty
180            .swap(false, std::sync::atomic::Ordering::Relaxed);
181        let result = if needs_update {
182            drop(read);
183            signal_read_and_write_in_reactive_scope::allow(|| {
185                signal_write_in_component_body::allow(|| self.recompute())
186            });
187            self.inner.inner.try_read_unchecked()
188        } else {
189            Ok(read)
190        };
191        if let Ok(read) = &result {
193            if let Some(reactive_context) = ReactiveContext::current() {
194                tracing::trace!("Subscribing to the reactive context {}", reactive_context);
195                reactive_context.subscribe(read.subscribers.clone());
196            }
197        }
198        result.map(|read| <UnsyncStorage as AnyStorage>::map(read, |v| &v.value))
199    }
200
201    #[track_caller]
205    fn try_peek_unchecked(&self) -> BorrowResult<ReadableRef<'static, Self>> {
206        self.inner.try_peek_unchecked()
207    }
208}
209
210impl<T> IntoAttributeValue for Memo<T>
211where
212    T: Clone + IntoAttributeValue + PartialEq,
213{
214    fn into_value(self) -> dioxus_core::AttributeValue {
215        self.with(|f| f.clone().into_value())
216    }
217}
218
219impl<T> IntoDynNode for Memo<T>
220where
221    T: Clone + IntoDynNode + PartialEq,
222{
223    fn into_dyn_node(self) -> dioxus_core::DynamicNode {
224        self().into_dyn_node()
225    }
226}
227
228impl<T: 'static> PartialEq for Memo<T> {
229    fn eq(&self, other: &Self) -> bool {
230        self.inner == other.inner
231    }
232}
233
234impl<T: Clone> Deref for Memo<T>
235where
236    T: PartialEq,
237{
238    type Target = dyn Fn() -> T;
239
240    fn deref(&self) -> &Self::Target {
241        unsafe { Readable::deref_impl(self) }
242    }
243}
244
245read_impls!(Memo<T> where T: PartialEq);
246
247impl<T: 'static> Clone for Memo<T> {
248    fn clone(&self) -> Self {
249        *self
250    }
251}
252
253impl<T: 'static> Copy for Memo<T> {}