Make memos lazy

This commit is contained in:
Greg Johnston 2022-08-11 07:00:43 -04:00
parent 3e4adcad21
commit eb4d5abff9
3 changed files with 101 additions and 54 deletions

View file

@ -90,10 +90,16 @@ where
if let Some(transition) = self.runtime.running_transition() {
todo!()
} else {
self.runtime
.memo((self.scope, self.id), |memo_state: &MemoState<T>| {
f(&memo_state.value.borrow())
})
self.runtime.memo(
(self.scope, self.id),
|memo_state: &MemoState<T>| match &*memo_state.value.borrow() {
Some(v) => f(v),
None => {
memo_state.run((self.scope, self.id));
f(memo_state.value.borrow().as_ref().unwrap())
}
},
)
}
}
@ -114,7 +120,7 @@ where
{
runtime: &'static Runtime,
f: Box<RefCell<dyn FnMut(Option<&T>) -> T>>,
value: RefCell<T>,
value: RefCell<Option<T>>,
t_value: RefCell<Option<T>>,
sources: RefCell<HashSet<Source>>,
subscribers: RefCell<HashSet<Subscriber>>,
@ -148,12 +154,11 @@ where
{
pub fn new(runtime: &'static Runtime, f: impl FnMut(Option<&T>) -> T + 'static) -> Self {
let f = Box::new(RefCell::new(f));
let value = (f.borrow_mut())(None);
Self {
runtime,
f,
value: RefCell::new(value),
value: RefCell::new(None),
sources: Default::default(),
t_value: Default::default(),
subscribers: Default::default(),
@ -196,8 +201,8 @@ where
self.runtime.push_stack(Subscriber::Memo(id));
// actually run the effect
let v = { (self.f.borrow_mut())(Some(&self.value.borrow())) };
*self.value.borrow_mut() = v;
let v = { (self.f.borrow_mut())(self.value.borrow().as_ref()) };
*self.value.borrow_mut() = Some(v);
// notify subscribers
let subs = { self.subscribers.borrow().clone() };

View file

@ -0,0 +1,87 @@
use leptos_reactive::create_scope;
#[test]
fn basic_memo() {
create_scope(|cx| {
let a = cx.create_memo(|_| 5);
assert_eq!(a(), 5);
})
.dispose()
}
#[test]
fn memo_with_computed_value() {
create_scope(|cx| {
let (a, set_a) = cx.create_signal(0);
let (b, set_b) = cx.create_signal(0);
let c = cx.create_memo(move |_| a() + b());
assert_eq!(c(), 0);
set_a(|a| *a = 5);
assert_eq!(c(), 5);
set_b(|b| *b = 1);
assert_eq!(c(), 6);
})
.dispose()
}
#[test]
fn nested_memos() {
create_scope(|cx| {
let (a, set_a) = cx.create_signal(0);
let (b, set_b) = cx.create_signal(0);
let c = cx.create_memo(move |_| a() + b());
let d = cx.create_memo(move |_| c() * 2);
let e = cx.create_memo(move |_| d() + 1);
assert_eq!(d(), 0);
set_a(|a| *a = 5);
assert_eq!(c(), 5);
assert_eq!(d(), 10);
assert_eq!(e(), 11);
set_b(|b| *b = 1);
assert_eq!(c(), 6);
assert_eq!(d(), 12);
assert_eq!(e(), 13);
})
.dispose()
}
#[test]
fn memo_runs_only_when_inputs_change() {
use std::{cell::Cell, rc::Rc};
create_scope(|cx| {
let call_count = Rc::new(Cell::new(0));
let (a, set_a) = cx.create_signal(0);
let (b, set_b) = cx.create_signal(0);
let (c, set_c) = cx.create_signal(0);
// pretend that this is some kind of expensive computation and we need to access its its value often
// we could do this with a derived signal, but that would re-run the computation
// memos should only run when their inputs actually change: this is the only point
let c = cx.create_memo({
let call_count = call_count.clone();
move |_| {
call_count.set(call_count.get() + 1);
a() + b() + c()
}
});
assert_eq!(call_count.get(), 1);
// here we access the value a bunch of times
assert_eq!(c(), 0);
assert_eq!(c(), 0);
assert_eq!(c(), 0);
assert_eq!(c(), 0);
assert_eq!(c(), 0);
// we've still only called the memo calculation once
assert_eq!(call_count.get(), 1);
// and we only call it again when an input changes
set_a(|n| *n = 1);
assert_eq!(c(), 1);
assert_eq!(call_count.get(), 2);
})
.dispose()
}

View file

@ -25,48 +25,3 @@ fn derived_signals() {
})
.dispose()
}
#[test]
fn basic_memo() {
create_scope(|cx| {
let a = cx.create_memo(|_| 5);
assert_eq!(a(), 5);
})
.dispose()
}
#[test]
fn memo_with_computed_value() {
create_scope(|cx| {
let (a, set_a) = cx.create_signal(0);
let (b, set_b) = cx.create_signal(0);
let c = cx.create_memo(move |_| a() + b());
assert_eq!(c(), 0);
set_a(|a| *a = 5);
assert_eq!(c(), 5);
set_b(|b| *b = 1);
assert_eq!(c(), 6);
})
.dispose()
}
#[test]
fn nested_memos() {
create_scope(|cx| {
let (a, set_a) = cx.create_signal(0);
let (b, set_b) = cx.create_signal(0);
let c = cx.create_memo(move |_| a() + b());
let d = cx.create_memo(move |_| c() * 2);
let e = cx.create_memo(move |_| d() + 1);
assert_eq!(d(), 0);
set_a(|a| *a = 5);
assert_eq!(c(), 5);
assert_eq!(d(), 10);
assert_eq!(e(), 11);
set_b(|b| *b = 1);
assert_eq!(c(), 6);
assert_eq!(d(), 12);
assert_eq!(e(), 13);
})
.dispose()
}