maple_core/reactive/
signal_vec.rs

1use std::cell::RefCell;
2use std::rc::Rc;
3
4use crate::{TemplateList, TemplateResult};
5
6use super::*;
7
8/// A reactive [`Vec`].
9/// This is more effective than using a [`Signal<Vec>`](Signal) because it allows fine grained
10/// reactivity within the `Vec`.
11pub struct SignalVec<T: 'static> {
12    signal: Signal<RefCell<Vec<T>>>,
13    /// A list of past changes that is accessed by subscribers.
14    /// Cleared when all subscribers are called.
15    changes: Rc<RefCell<Vec<VecDiff<T>>>>,
16}
17
18impl<T: 'static> SignalVec<T> {
19    /// Create a new empty `SignalVec`.
20    pub fn new() -> Self {
21        Self {
22            signal: Signal::new(RefCell::new(Vec::new())),
23            changes: Rc::new(RefCell::new(Vec::new())),
24        }
25    }
26
27    /// Create a new `SignalVec` with existing values from a [`Vec`].
28    pub fn with_values(values: Vec<T>) -> Self {
29        Self {
30            signal: Signal::new(RefCell::new(values)),
31            changes: Rc::new(RefCell::new(Vec::new())),
32        }
33    }
34
35    /// Get the current pending changes that will be applied to the `SignalVec`.
36    pub fn changes(&self) -> &Rc<RefCell<Vec<VecDiff<T>>>> {
37        &self.changes
38    }
39
40    /// Returns the inner backing [`Signal`] used to store the data. This method should used with
41    /// care as unintentionally modifying the [`Vec`] will not trigger any updates and cause
42    /// potential future problems.
43    pub fn inner_signal(&self) -> &Signal<RefCell<Vec<T>>> {
44        &self.signal
45    }
46
47    pub fn replace(&self, values: Vec<T>) {
48        self.add_change(VecDiff::Replace { values });
49
50        self.trigger_and_apply_changes();
51    }
52
53    pub fn insert(&self, index: usize, value: T) {
54        self.add_change(VecDiff::Insert { index, value });
55
56        self.trigger_and_apply_changes();
57    }
58
59    pub fn update(&self, index: usize, value: T) {
60        self.add_change(VecDiff::Update { index, value })
61    }
62
63    pub fn remove(&self, index: usize) {
64        self.add_change(VecDiff::Remove { index });
65
66        self.trigger_and_apply_changes();
67    }
68
69    pub fn swap(&self, index1: usize, index2: usize) {
70        self.add_change(VecDiff::Swap { index1, index2 });
71
72        self.trigger_and_apply_changes();
73    }
74
75    pub fn push(&self, value: T) {
76        self.add_change(VecDiff::Push { value });
77
78        self.trigger_and_apply_changes();
79    }
80
81    pub fn pop(&self) {
82        self.add_change(VecDiff::Pop);
83
84        self.trigger_and_apply_changes();
85    }
86
87    pub fn clear(&self) {
88        self.add_change(VecDiff::Clear);
89
90        self.trigger_and_apply_changes();
91    }
92
93    fn add_change(&self, change: VecDiff<T>) {
94        self.changes.borrow_mut().push(change);
95    }
96
97    fn trigger_and_apply_changes(&self) {
98        self.signal.trigger_subscribers();
99
100        for change in self.changes.take() {
101            change.apply_to_vec(&mut self.signal.get().borrow_mut());
102        }
103    }
104
105    /// Creates a derived `SignalVec`.
106    ///
107    /// # Example
108    /// ```
109    /// use maple_core::prelude::*;
110    ///
111    /// let my_vec = SignalVec::with_values(vec![1, 2, 3]);
112    /// let squared = my_vec.map(|x| *x * *x);
113    ///
114    /// assert_eq!(*squared.inner_signal().get().borrow(), vec![1, 4, 9]);
115    ///
116    /// my_vec.push(4);
117    /// assert_eq!(*squared.inner_signal().get().borrow(), vec![1, 4, 9, 16]);
118    ///
119    /// my_vec.swap(0, 1);
120    /// assert_eq!(*squared.inner_signal().get().borrow(), vec![4, 1, 9, 16]);
121    /// ```
122    pub fn map<U: Clone>(&self, f: impl Fn(&T) -> U + 'static) -> SignalVec<U> {
123        let signal = self.inner_signal().clone();
124        let changes = Rc::clone(&self.changes());
125        let f = Rc::new(f);
126
127        create_effect_initial(move || {
128            let derived = SignalVec::with_values(
129                signal.get().borrow().iter().map(|value| f(value)).collect(),
130            );
131
132            let effect = {
133                let derived = derived.clone();
134                let signal = signal.clone();
135                move || {
136                    signal.get(); // subscribe to signal
137                    for change in changes.borrow().iter() {
138                        match change {
139                            VecDiff::Replace { values } => {
140                                derived.replace(values.iter().map(|value| f(value)).collect())
141                            }
142                            VecDiff::Insert { index, value } => derived.insert(*index, f(value)),
143                            VecDiff::Update { index, value } => derived.update(*index, f(value)),
144                            VecDiff::Remove { index } => derived.remove(*index),
145                            VecDiff::Swap { index1, index2 } => derived.swap(*index1, *index2),
146                            VecDiff::Push { value } => derived.push(f(value)),
147                            VecDiff::Pop => derived.pop(),
148                            VecDiff::Clear => derived.clear(),
149                        }
150                    }
151                }
152            };
153
154            (Rc::new(effect), derived)
155        })
156    }
157}
158
159impl SignalVec<TemplateResult> {
160    /// Create a [`TemplateList`] from the `SignalVec`.
161    pub fn template_list(&self) -> TemplateList {
162        TemplateList::from(self.clone())
163    }
164}
165
166impl<T: 'static + Clone> SignalVec<T> {
167    /// Create a [`Vec`] from a [`SignalVec`]. The returned [`Vec`] is cloned from the data which
168    /// requires `T` to be `Clone`.
169    ///
170    /// # Example
171    /// ```
172    /// use maple_core::prelude::*;
173    ///
174    /// let signal = SignalVec::with_values(vec![1, 2, 3]);
175    /// assert_eq!(signal.to_vec(), vec![1, 2, 3]);
176    /// ```
177    pub fn to_vec(&self) -> Vec<T> {
178        self.signal.get().borrow().clone()
179    }
180}
181
182impl<T: 'static> Default for SignalVec<T> {
183    fn default() -> Self {
184        Self::new()
185    }
186}
187
188impl<T: 'static> Clone for SignalVec<T> {
189    fn clone(&self) -> Self {
190        Self {
191            signal: self.signal.clone(),
192            changes: Rc::clone(&self.changes),
193        }
194    }
195}
196
197/// An enum describing the changes applied on a [`SignalVec`].
198#[derive(Debug, Clone, PartialEq, Eq)]
199pub enum VecDiff<T> {
200    Replace { values: Vec<T> },
201    Insert { index: usize, value: T },
202    Update { index: usize, value: T },
203    Remove { index: usize },
204    Swap { index1: usize, index2: usize },
205    Push { value: T },
206    Pop,
207    Clear,
208}
209
210impl<T> VecDiff<T> {
211    pub fn apply_to_vec(self, v: &mut Vec<T>) {
212        match self {
213            VecDiff::Replace { values } => *v = values,
214            VecDiff::Insert { index, value } => v.insert(index, value),
215            VecDiff::Update { index, value } => v[index] = value,
216            VecDiff::Remove { index } => {
217                v.remove(index);
218            }
219            VecDiff::Swap { index1, index2 } => v.swap(index1, index2),
220            VecDiff::Push { value } => v.push(value),
221            VecDiff::Pop => {
222                v.pop();
223            }
224            VecDiff::Clear => v.clear(),
225        }
226    }
227}
228
229#[cfg(test)]
230mod tests {
231    use super::*;
232
233    #[test]
234    fn signal_vec() {
235        let my_vec = SignalVec::new();
236        assert_eq!(*my_vec.inner_signal().get().borrow(), Vec::<i32>::new());
237
238        my_vec.push(3);
239        assert_eq!(*my_vec.inner_signal().get().borrow(), vec![3]);
240
241        my_vec.push(4);
242        assert_eq!(*my_vec.inner_signal().get().borrow(), vec![3, 4]);
243
244        my_vec.pop();
245        assert_eq!(*my_vec.inner_signal().get().borrow(), vec![3]);
246    }
247
248    #[test]
249    fn map() {
250        let my_vec = SignalVec::with_values(vec![1, 2, 3]);
251        let squared = my_vec.map(|x| *x * *x);
252
253        assert_eq!(*squared.inner_signal().get().borrow(), vec![1, 4, 9]);
254
255        my_vec.push(4);
256        assert_eq!(*squared.inner_signal().get().borrow(), vec![1, 4, 9, 16]);
257
258        my_vec.pop();
259        assert_eq!(*squared.inner_signal().get().borrow(), vec![1, 4, 9]);
260    }
261
262    #[test]
263    fn map_chain() {
264        let my_vec = SignalVec::with_values(vec![1, 2, 3]);
265        let squared = my_vec.map(|x| *x * 2);
266        let quadrupled = squared.map(|x| *x * 2);
267
268        assert_eq!(*quadrupled.inner_signal().get().borrow(), vec![4, 8, 12]);
269
270        my_vec.push(4);
271        assert_eq!(
272            *quadrupled.inner_signal().get().borrow(),
273            vec![4, 8, 12, 16]
274        );
275
276        my_vec.pop();
277        assert_eq!(*quadrupled.inner_signal().get().borrow(), vec![4, 8, 12]);
278    }
279
280    #[test]
281    fn map_chain_temporary() {
282        let my_vec = SignalVec::with_values(vec![1, 2, 3]);
283        let quadrupled = my_vec.map(|x| *x * 2).map(|x| *x * 2);
284
285        assert_eq!(*quadrupled.inner_signal().get().borrow(), vec![4, 8, 12]);
286
287        my_vec.push(4);
288        assert_eq!(
289            *quadrupled.inner_signal().get().borrow(),
290            vec![4, 8, 12, 16]
291        );
292
293        my_vec.pop();
294        assert_eq!(*quadrupled.inner_signal().get().borrow(), vec![4, 8, 12]);
295    }
296
297    #[test]
298    fn map_inner_scope() {
299        let my_vec = SignalVec::with_values(vec![1, 2, 3]);
300        let quadrupled;
301
302        let doubled = my_vec.map(|x| *x * 2);
303        assert_eq!(*doubled.inner_signal().get().borrow(), vec![2, 4, 6]);
304
305        quadrupled = doubled.map(|x| *x * 2);
306        assert_eq!(*quadrupled.inner_signal().get().borrow(), vec![4, 8, 12]);
307
308        drop(doubled);
309        assert_eq!(*quadrupled.inner_signal().get().borrow(), vec![4, 8, 12]);
310
311        my_vec.push(4);
312        assert_eq!(
313            *quadrupled.inner_signal().get().borrow(),
314            vec![4, 8, 12, 16]
315        );
316    }
317}