telar_reactive_core/
source.rs1use crate::{Memo, ReadSignal, RwSignal, memo};
4
5pub trait Source {
16 type Value;
17 fn read(&self) -> Self::Value;
18}
19
20impl<T: Clone + 'static> Source for RwSignal<T> {
21 type Value = T;
22 fn read(&self) -> T {
23 self.get()
24 }
25}
26
27impl<T: Clone + 'static> Source for ReadSignal<T> {
28 type Value = T;
29 fn read(&self) -> T {
30 self.get()
31 }
32}
33
34impl<T: Clone + 'static> Source for Memo<T> {
35 type Value = T;
36 fn read(&self) -> T {
37 self.get()
38 }
39}
40
41impl Source for f32 {
44 type Value = f32;
45 fn read(&self) -> f32 {
46 *self
47 }
48}
49
50impl Source for bool {
51 type Value = bool;
52 fn read(&self) -> bool {
53 *self
54 }
55}
56
57pub fn derive<S, U>(source: S, map: impl Fn(S::Value) -> U + 'static) -> Memo<U>
65where
66 S: Source + 'static,
67 U: PartialEq + 'static,
68{
69 memo(move || map(source.read()))
70}
71
72pub fn derive_pair<A, B, U>(
75 first: A,
76 second: B,
77 map: impl Fn(A::Value, B::Value) -> U + 'static,
78) -> Memo<U>
79where
80 A: Source + 'static,
81 B: Source + 'static,
82 U: PartialEq + 'static,
83{
84 memo(move || map(first.read(), second.read()))
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90 use crate::{reset_runtime, signal};
91
92 #[test]
93 fn a_derived_value_follows_its_source() {
94 reset_runtime();
95 let source = signal(2i32);
96 let doubled = derive(source.clone(), |n| n * 2);
97 assert_eq!(doubled.get(), 4, "seeded from the source, not a default");
98 source.set(5);
99 assert_eq!(doubled.get(), 10);
100 }
101
102 #[test]
103 fn a_pair_recomputes_when_either_half_moves() {
104 reset_runtime();
105 let level = signal(10i32);
106 let charging = signal(false);
107 let label = derive_pair(
108 level.read_only(),
109 charging.read_only(),
110 |level, charging| format!("{level}{}", if charging { "+" } else { "" }),
111 );
112 assert_eq!(label.get(), "10");
113 charging.set(true);
114 assert_eq!(label.get(), "10+");
115 level.set(11);
116 assert_eq!(label.get(), "11+");
117 }
118
119 #[test]
122 fn a_derivation_outlives_the_call_that_made_it() {
123 reset_runtime();
124 let source = signal(1i32);
125 let derived = derive(source.clone(), |n| n * 10);
126 let read: Box<dyn Fn() -> i32> = Box::new(move || derived.get());
127 source.set(7);
128 assert_eq!(read(), 70, "whatever holds the derivation keeps it alive");
129 }
130
131 #[test]
133 fn a_plain_value_reads_as_itself() {
134 assert_eq!(Source::read(&0.5f32), 0.5);
135 assert!(Source::read(&true));
136 }
137
138 #[test]
140 fn a_derivation_is_itself_a_source() {
141 reset_runtime();
142 let source = signal(3i32);
143 let once = derive(source.clone(), |n| n + 1);
144 let twice = derive(once, |n| n * 2);
145 assert_eq!(twice.get(), 8);
146 source.set(4);
147 assert_eq!(twice.get(), 10);
148 }
149}