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}