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.update(|current| {
38 if *current != new_value {
39 *current = new_value;
40 }
41 });
42 });
43
44 Memo { read }
45}
46
47#[cfg(test)]
48mod tests {
49 use super::*;
50 use crate::signal::create_signal;
51 use std::cell::RefCell;
52 use std::rc::Rc;
53
54 #[test]
55 fn test_memo_basic() {
56 let (count, set_count) = create_signal(2);
57 let doubled = create_memo(move || count.get() * 2);
58
59 assert_eq!(doubled.get(), 4);
60
61 set_count.set(5);
62 assert_eq!(doubled.get(), 10);
63 }
64
65 #[test]
66 fn test_memo_caching() {
67 let (count, set_count) = create_signal(1);
68 let compute_count = Rc::new(RefCell::new(0));
69 let compute_count_clone = compute_count.clone();
70
71 let doubled = create_memo(move || {
72 *compute_count_clone.borrow_mut() += 1;
73 count.get() * 2
74 });
75
76 assert_eq!(doubled.get(), 2);
77 assert_eq!(doubled.get(), 2);
78 let initial_count = *compute_count.borrow();
79
80 set_count.set(2);
81 assert_eq!(doubled.get(), 4);
82 assert_eq!(*compute_count.borrow(), initial_count + 1);
83 }
84
85 #[test]
86 fn test_memo_only_updates_on_change() {
87 let (count, set_count) = create_signal(1);
88 let compute_count = Rc::new(RefCell::new(0));
89 let compute_count_clone = compute_count.clone();
90
91 let is_even = create_memo(move || {
92 *compute_count_clone.borrow_mut() += 1;
93 count.get() % 2 == 0
94 });
95
96 assert!(!is_even.get());
97 let count_after_init = *compute_count.borrow();
98
99 set_count.set(3);
100 assert!(!is_even.get());
101 assert_eq!(*compute_count.borrow(), count_after_init + 1);
102
103 set_count.set(4);
104 assert!(is_even.get());
105 assert_eq!(*compute_count.borrow(), count_after_init + 2);
106 }
107
108 #[test]
109 fn test_memo_chain() {
110 let (count, set_count) = create_signal(1);
111
112 let doubled = {
113 let count = count.clone();
114 create_memo(move || count.get() * 2)
115 };
116
117 let quadrupled = {
118 let doubled = doubled.clone();
119 create_memo(move || doubled.get() * 2)
120 };
121
122 assert_eq!(count.get(), 1);
123 assert_eq!(doubled.get(), 2);
124 assert_eq!(quadrupled.get(), 4);
125
126 set_count.set(3);
127
128 assert_eq!(count.get(), 3);
129 assert_eq!(doubled.get(), 6);
130 assert_eq!(quadrupled.get(), 12);
131 }
132}