1use crate::effect::create_effect;
2use crate::signal::{create_signal, ReadSignal};
3
4pub struct Memo<T> {
5 read: ReadSignal<T>,
6}
7
8impl<T: Clone> Memo<T> {
9 pub fn get(&self) -> T {
10 self.read.get()
11 }
12
13 pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
14 self.read.with(f)
15 }
16}
17
18impl<T> Clone for Memo<T> {
19 fn clone(&self) -> Self {
20 Self {
21 read: self.read.clone(),
22 }
23 }
24}
25
26pub fn create_memo<T, F>(f: F) -> Memo<T>
28where
29 F: Fn() -> T + 'static,
30 T: PartialEq + Clone + 'static,
31{
32 let initial = f();
33 let (read, write) = create_signal(initial);
34
35 create_effect(move || {
36 let new_value = f();
37 write.set_if_changed(new_value);
38 });
39
40 Memo { read }
41}
42
43#[cfg(test)]
44mod tests {
45 use super::*;
46 use crate::signal::create_signal;
47 use std::cell::RefCell;
48 use std::rc::Rc;
49
50 #[test]
51 fn test_memo_basic() {
52 let (count, set_count) = create_signal(2);
53 let doubled = create_memo(move || count.get() * 2);
54
55 assert_eq!(doubled.get(), 4);
56
57 set_count.set(5);
58 assert_eq!(doubled.get(), 10);
59 }
60
61 #[test]
62 fn test_memo_caching() {
63 let (count, set_count) = create_signal(1);
64 let compute_count = Rc::new(RefCell::new(0));
65 let compute_count_clone = compute_count.clone();
66
67 let doubled = create_memo(move || {
68 *compute_count_clone.borrow_mut() += 1;
69 count.get() * 2
70 });
71
72 assert_eq!(doubled.get(), 2);
73 assert_eq!(doubled.get(), 2);
74 let initial_count = *compute_count.borrow();
75
76 set_count.set(2);
77 assert_eq!(doubled.get(), 4);
78 assert_eq!(*compute_count.borrow(), initial_count + 1);
79 }
80
81 #[test]
82 fn test_memo_only_updates_on_change() {
83 let (count, set_count) = create_signal(1);
84 let compute_count = Rc::new(RefCell::new(0));
85 let compute_count_clone = compute_count.clone();
86
87 let is_even = create_memo(move || {
88 *compute_count_clone.borrow_mut() += 1;
89 count.get() % 2 == 0
90 });
91
92 assert!(!is_even.get());
93 let count_after_init = *compute_count.borrow();
94
95 set_count.set(3);
96 assert!(!is_even.get());
97 assert_eq!(*compute_count.borrow(), count_after_init + 1);
98
99 set_count.set(4);
100 assert!(is_even.get());
101 assert_eq!(*compute_count.borrow(), count_after_init + 2);
102 }
103
104 #[test]
105 fn test_memo_chain() {
106 let (count, set_count) = create_signal(1);
107
108 let doubled = {
109 let count = count.clone();
110 create_memo(move || count.get() * 2)
111 };
112
113 let quadrupled = {
114 let doubled = doubled.clone();
115 create_memo(move || doubled.get() * 2)
116 };
117
118 assert_eq!(count.get(), 1);
119 assert_eq!(doubled.get(), 2);
120 assert_eq!(quadrupled.get(), 4);
121
122 set_count.set(3);
123
124 assert_eq!(count.get(), 3);
125 assert_eq!(doubled.get(), 6);
126 assert_eq!(quadrupled.get(), 12);
127 }
128
129 #[test]
130 fn test_memo_does_not_notify_when_unchanged() {
131 let (count, set_count) = create_signal(1);
132 let is_even = create_memo(move || count.get() % 2 == 0);
133
134 let downstream_runs = Rc::new(RefCell::new(0usize));
135 let downstream_clone = downstream_runs.clone();
136 crate::effect::create_effect(move || {
137 let _ = is_even.get();
138 *downstream_clone.borrow_mut() += 1;
139 });
140
141 assert_eq!(*downstream_runs.borrow(), 1);
142
143 set_count.set(3); assert_eq!(*downstream_runs.borrow(), 1);
145
146 set_count.set(4); assert_eq!(*downstream_runs.borrow(), 2);
148 }
149}