rustolio_web/hooks/
calculated.rs1use crate::hooks::{SignalBase, SignalUpdater as _, effect::EffectStore};
12
13use super::{Scope, Signal, SignalGetter, signal_updater_callback};
14
15#[derive(Debug, PartialEq, Eq)]
16pub struct Calculated<T>(Signal<T>, usize);
17
18impl<T> Calculated<T>
19where
20 T: 'static,
21{
22 pub fn new(f: impl Fn() -> T + 'static) -> Self {
23 let id = EffectStore::new();
25 Scope::register_effect(id);
26
27 let signal = unsafe {
29 Signal::empty_always_mutable(true)
31 };
32
33 let s = signal;
34 signal_updater_callback(
35 move || !EffectStore::is_dropped(id),
36 move || {
37 s.set_unchecked(f());
38 },
39 );
40
41 Calculated(signal, id)
42 }
43}
44
45impl<T> Calculated<T>
46where
47 T: Default + 'static,
48{
49 pub fn new_result(f: impl Fn() -> rustolio_utils::Result<T> + 'static) -> Self {
51 Self::new(crate::error::convert_fn_0("Calculated", f))
52 }
53}
54
55impl<T> SignalBase<T> for Calculated<T> {
56 fn base(&self) -> Signal<T> {
57 self.0
58 }
59
60 unsafe fn globalize(&self) {
61 unsafe {
62 self.0.globalize();
64 Scope::deregister_effect(self.1);
65 }
66 }
67}
68impl<T> SignalGetter<T> for Calculated<T> where T: Clone + 'static {}
69
70impl<T> Clone for Calculated<T> {
72 fn clone(&self) -> Self {
73 *self
74 }
75}
76impl<T> Copy for Calculated<T> {}
77
78#[cfg(test)]
79mod tests {
80 use crate::prelude::*;
81
82 #[test]
83 fn test_calculated() {
84 let m = Calculated::new(|| 5);
85 assert_eq!(m.value(), 5);
86
87 let s = Signal::new(10);
88 let m = Calculated::new(move || s.value() * 2);
89 assert_eq!(s.value(), 10);
90 assert_eq!(s.peek(), 10);
91 assert_eq!(m.value(), 20);
92 assert_eq!(m.peek(), 20);
93 let old = s.set(20);
94 assert_eq!(old, 10);
95 assert_eq!(s.value(), 20);
96 assert_eq!(s.peek(), 20);
97 assert_eq!(m.value(), 40);
98 assert_eq!(m.peek(), 40);
99 let m = Calculated::new(|| "Hello".to_string());
103 let _m1 = m;
104 let _m2 = m;
105 }
106}