terrazzo_client/signal/derive.rs
1use std::fmt::Debug;
2
3use super::XSignal;
4use super::depth::Depth;
5use crate::debug_correlation_id::DebugCorrelationId;
6use crate::prelude::OrElseLog as _;
7use crate::prelude::diagnostics::debug;
8use crate::prelude::diagnostics::debug_span;
9use crate::prelude::diagnostics::trace;
10use crate::prelude::diagnostics::warn;
11use crate::string::XString;
12
13impl<T> XSignal<T> {
14 pub fn derive<U>(
15 &self,
16 name: impl Into<XString>,
17 to: impl Fn(&T) -> U + 'static,
18 from: impl Fn(&T, &U) -> Option<T> + 'static,
19 ) -> XSignal<U>
20 where
21 T: Debug + 'static,
22 U: Debug + Eq + 'static,
23 {
24 let main = self;
25 let main_weak = main.downgrade();
26 let main_name =
27 DebugCorrelationId::new(|| format!("Main:{}", main.0.producer.name()).into());
28
29 let derived = XSignal::new(
30 name,
31 to(main.0.current_value.lock().or_throw("main").value()),
32 );
33 let derived_weak = derived.downgrade();
34 let derived_name =
35 DebugCorrelationId::new(|| format!("Derived:{}", derived.0.producer.name()).into());
36
37 let span = debug_span! { "Derived signal", main = %main_name, derived = %derived_name };
38 let _span = span.clone().entered();
39 debug!(main = %main_name, derived = %derived_name, "Make");
40
41 {
42 // Update main when derived changes
43 let main_weak = main_weak.clone();
44 let derived_weak = derived_weak.clone();
45 let span = span.clone();
46 trace!("Derived updates Main");
47 let derived_producer = &derived.0.producer;
48 let consumer =
49 derived_producer.register(derived_name, Depth::zero(), move |_version| {
50 let _span = span.enter();
51 debug!("Derived updated");
52 let Some(derived) = derived_weak.upgrade() else {
53 warn!("Derived is dropped but triggered");
54 debug_assert!(false);
55 return;
56 };
57 let Some(main) = main_weak.upgrade() else {
58 warn!("Main is dropped but subscribed");
59 debug_assert!(false);
60 return;
61 };
62 let t = from(
63 main.0.current_value.lock().or_throw("main").value(),
64 derived.0.current_value.lock().or_throw("derived").value(),
65 );
66 if let Some(t) = t {
67 main.force(t);
68 }
69 });
70 // If main is dropped there is no need to try to update it.
71 let mut on_main_drop = main.0.on_drop.lock().or_throw("on_drop");
72 on_main_drop.push(Box::new(move || drop(consumer)));
73 }
74
75 {
76 // Update derived when main changes
77 let main_weak = main_weak.clone();
78 let derived_weak = derived_weak.clone();
79 let span = span.clone();
80 trace!("Main updates Derived");
81 let main_producer = &main.0.producer;
82 let consumer = main_producer.register(main_name, Depth::zero(), move |_version| {
83 let _span = span.enter();
84 debug!("Main updated");
85 let Some(main) = main_weak.upgrade() else {
86 warn!("Main is dropped but triggered");
87 debug_assert!(false);
88 return;
89 };
90 let Some(derived) = derived_weak.upgrade() else {
91 warn!("Derived is dropped but subscribed");
92 debug_assert!(false);
93 return;
94 };
95 let t = to(main.0.current_value.lock().or_throw("main").value());
96 derived.set(t);
97 });
98 // If derived is dropped there is no need to try to update it.
99 let mut on_drop = derived.0.on_drop.lock().or_throw("on_drop");
100 on_drop.push(Box::new(move || drop(consumer)));
101 }
102
103 return derived;
104 }
105
106 pub fn view<U>(&self, name: impl Into<XString>, to: impl Fn(&T) -> U + 'static) -> XSignal<U>
107 where
108 T: Debug + 'static,
109 U: Debug + Eq + 'static,
110 {
111 self.derive(name, to, |_, _| None)
112 }
113}
114
115/// A handy utility function to add diffing logic when using [derived] signals.
116///
117/// ```
118/// # use std::cell::Cell;
119/// # use autoclone::autoclone;
120/// # use terrazzo_client::prelude::*;
121/// # #[autoclone]
122/// # fn main() {
123/// let main = XSignal::new("main", "1".to_owned());
124/// let compute_derived = Ptr::new(Cell::new(0));
125/// let compute_main = Ptr::new(Cell::new(0));
126///
127/// let derived_nodiff = main.derive(
128/// "derived",
129/// /* to: */
130/// move |main| {
131/// # autoclone!(compute_derived);
132/// compute_derived.set(compute_derived.get() + 1);
133/// main.parse::<i32>().unwrap()
134/// },
135/// /* from: */
136/// move |_main: &String, derived: &i32| {
137/// # autoclone!(compute_main);
138/// compute_main.set(compute_main.get() + 1);
139/// Some(derived.to_string())
140/// },
141/// );
142///
143/// # assert_eq!("1", main.get_value_untracked());
144/// # assert_eq!(1, derived_nodiff.get_value_untracked());
145/// # assert_eq!(1, compute_derived.take());
146/// # assert_eq!(0, compute_main.take());
147///
148/// // 1. Updating `main` updates `derived`
149/// // 2. Which updates `main` again
150/// // 3. Which updates `derived` but to the same value
151/// main.set("2");
152/// # assert_eq!("2", main.get_value_untracked());
153/// # assert_eq!(2, derived_nodiff.get_value_untracked());
154/// assert_eq!(2, compute_derived.take());
155/// assert_eq!(1, compute_main.take());
156///
157/// // Updating `main` to the same value is a no-op.
158/// main.set("2");
159/// # assert_eq!("2", main.get_value_untracked());
160/// # assert_eq!(2, derived_nodiff.get_value_untracked());
161/// assert_eq!(0, compute_derived.take());
162/// assert_eq!(0, compute_main.take());
163///
164/// // Reset ...
165/// # drop(derived_nodiff);
166/// # main.set("1");
167///
168/// let derived_diff = main.derive(
169/// "derived",
170/// /* to: */
171/// move |main| {
172/// autoclone!(compute_derived);
173/// compute_derived.set(compute_derived.get() + 1);
174/// main.parse::<i32>().unwrap()
175/// },
176/// /* from: */
177/// if_change(move |_main: &String, derived: &i32| {
178/// autoclone!(compute_main);
179/// compute_main.set(compute_main.get() + 1);
180/// Some(derived.to_string())
181/// }),
182/// );
183///
184/// # assert_eq!("1", main.get_value_untracked());
185/// # assert_eq!(1, derived_diff.get_value_untracked());
186/// # compute_derived.set(0);
187/// # compute_main.set(0);
188///
189/// // Updating `main` updates `derived`, which updates `main` again but to the same value.
190/// main.set("2");
191/// # assert_eq!("2", main.get_value_untracked());
192/// # assert_eq!(2, derived_diff.get_value_untracked());
193/// assert_eq!(1, compute_derived.take());
194/// assert_eq!(1, compute_main.take());
195///
196/// // Updating `main` to the same value is a no-op.
197/// main.set("2");
198/// # assert_eq!("2", main.get_value_untracked());
199/// # assert_eq!(2, derived_diff.get_value_untracked());
200/// assert_eq!(0, compute_derived.take());
201/// assert_eq!(0, compute_main.take());
202/// # }
203/// ```
204///
205/// [derived]: crate::prelude::XSignal::derive
206pub fn if_change<T: Eq, U>(
207 from: impl Fn(&T, &U) -> Option<T> + 'static,
208) -> impl Fn(&T, &U) -> Option<T> + 'static {
209 move |old_t, u| from(old_t, u).filter(|new_t| new_t != old_t)
210}