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> {}