Skip to main content

zng_var/var_impl/
merge_var.rs

1//! Variable that merges multiple others.
2
3///<span data-del-macro-root></span> Initializes a new [`Var<T>`](crate::Var) with value made
4/// by merging multiple other variables.
5///
6/// # Arguments
7///
8/// All arguments are separated by comma like a function call.
9///
10/// * `var0..N`: A list of *vars*, minimal 2.
11/// * `merge`: A new closure that produces a new value from references to all variable values. `FnMut(&var0_T, ..) -> merge_T`
12///
13/// Note that the *vars* can be of any of `Var<T>`, `ContextVar<T>` or `ResponseVar<T>`, that is, already constructed
14/// var types, not all types that convert into var.
15///
16/// # Contextualized
17///
18/// The merge var is contextualized when needed, meaning if any input is [contextual] at the moment the var is created it
19/// is also contextual. The full output type of this macro is a `Var<O>`, the `O` type is defined by the output of the merge closure.
20///
21/// [contextual]: crate::VarCapability::CONTEXT
22///
23/// # Examples
24///
25/// ```
26/// # use zng_var::*;
27/// # use zng_txt::*;
28/// # macro_rules! Text { ($($tt:tt)*) => { () } }
29/// let var0: Var<Txt> = var_from("Hello");
30/// let var1: Var<Txt> = var_from("World");
31///
32/// let greeting_text = Text!(merge_var!(var0, var1, |a, b| formatx!("{a} {b}!")));
33/// ```
34#[macro_export]
35macro_rules! merge_var {
36    ($($tt:tt)+) => {
37        $crate::__merge_var! {
38            $crate,
39            $($tt)+
40        }
41    };
42}
43
44use core::fmt;
45use std::{
46    any::TypeId,
47    fmt::Write as _,
48    marker::PhantomData,
49    ops,
50    sync::{Arc, Weak},
51};
52
53use parking_lot::Mutex;
54use smallbox::{SmallBox, smallbox};
55use zng_clone_move::clmv;
56use zng_txt::Txt;
57#[doc(hidden)]
58pub use zng_var_proc_macros::merge_var as __merge_var;
59
60use crate::{
61    AnyVar, AnyVarValue, BoxAnyVarValue, ContextVar, DynAnyVar, DynWeakAnyVar, Response, ResponseVar, Var, VarHandle, VarImpl,
62    VarInstanceTag, VarValue, WeakVarImpl, any_contextual_var, any_var, contextual_var,
63};
64
65use super::VarCapability;
66
67#[doc(hidden)]
68pub fn merge_var_input<I: VarValue>(input: impl MergeInput<I>) -> AnyVar {
69    input.into_merge_input().into()
70}
71
72#[doc(hidden)]
73pub fn merge_var_with(var: &AnyVar, visitor: &mut dyn FnMut(&dyn AnyVarValue)) {
74    var.0.with(visitor);
75}
76
77#[doc(hidden)]
78pub fn merge_var_output<O: VarValue>(output: O) -> BoxAnyVarValue {
79    BoxAnyVarValue::new(output)
80}
81
82#[doc(hidden)]
83pub fn merge_var<O: VarValue>(inputs: Box<[AnyVar]>, merge: impl FnMut(&[AnyVar]) -> BoxAnyVarValue + Send + 'static) -> Var<O> {
84    Var::new_any(var_merge_impl(inputs, smallbox!(merge), TypeId::of::<O>()))
85}
86
87#[doc(hidden)]
88#[diagnostic::on_unimplemented(note = "merge_var! and expr_var! inputs can be: Var<T>, ContextVar<T> or ResponseVar<T>")]
89pub trait MergeInput<T: VarValue> {
90    fn into_merge_input(self) -> Var<T>;
91}
92impl<T: VarValue> MergeInput<T> for Var<T> {
93    fn into_merge_input(self) -> Var<T> {
94        self
95    }
96}
97impl<T: VarValue> MergeInput<T> for ContextVar<T> {
98    fn into_merge_input(self) -> Var<T> {
99        self.into()
100    }
101}
102impl<T: VarValue> MergeInput<Response<T>> for ResponseVar<T> {
103    fn into_merge_input(self) -> Var<Response<T>> {
104        self.into()
105    }
106}
107
108fn var_merge_impl(inputs: Box<[AnyVar]>, merge: MergeFn, value_type: TypeId) -> AnyVar {
109    if inputs.iter().any(|i| i.capabilities().is_contextual()) {
110        let merge = Arc::new(Mutex::new(merge));
111        return any_contextual_var(
112            move || {
113                let mut inputs = inputs.clone();
114                for v in inputs.iter_mut() {
115                    if v.capabilities().is_contextual() {
116                        *v = v.current_context();
117                    }
118                }
119                var_merge_tail(inputs, smallbox!(clmv!(merge, |inputs: &[AnyVar]| { merge.lock()(inputs) })))
120            },
121            value_type,
122        );
123    }
124    var_merge_tail(inputs, merge)
125}
126fn var_merge_tail(inputs: Box<[AnyVar]>, mut merge: MergeFn) -> AnyVar {
127    let output = any_var(merge(&inputs));
128    let data = Arc::new(MergeVarData {
129        inputs,
130        merge: Mutex::new((merge, 0)),
131        output,
132    });
133
134    for input in &data.inputs {
135        let weak = Arc::downgrade(&data);
136        input
137            .hook(move |_| {
138                if let Some(data) = weak.upgrade() {
139                    // Multiple inputs can update on the same cycle,
140                    // to avoid running merge multiple times schedule an output modify
141                    // so it runs after the current burst on the same cycle, and use
142                    // this counter to skip subsequent modify requests on the same cycle
143                    let modify_id = data.merge.lock().1;
144                    data.output.modify(clmv!(weak, |output| {
145                        if let Some(data) = weak.upgrade() {
146                            let mut m = data.merge.lock();
147                            if m.1 != modify_id {
148                                // already applied
149                                return;
150                            }
151
152                            let new_value = m.0(&data.inputs);
153                            output.set(new_value);
154                        }
155                    }));
156                    true
157                } else {
158                    false
159                }
160            })
161            .perm();
162    }
163
164    AnyVar(DynAnyVar::Merge(MergeVar(data)))
165}
166
167type MergeFn = SmallBox<dyn FnMut(&[AnyVar]) -> BoxAnyVarValue + Send + 'static, smallbox::space::S4>;
168
169struct MergeVarData {
170    inputs: Box<[AnyVar]>,
171    merge: Mutex<(MergeFn, usize)>,
172    output: AnyVar,
173}
174
175pub(crate) struct MergeVar(Arc<MergeVarData>);
176impl fmt::Debug for MergeVar {
177    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
178        let mut b = f.debug_struct("MergeVar");
179        b.field("var_instance_tag()", &self.var_instance_tag());
180        b.field("inputs", &self.0.inputs);
181        b.field("output", &self.0.output);
182        b.finish()
183    }
184}
185impl VarImpl for MergeVar {
186    fn clone_dyn(&self) -> DynAnyVar {
187        DynAnyVar::Merge(MergeVar(self.0.clone()))
188    }
189
190    fn value_type(&self) -> std::any::TypeId {
191        self.0.output.0.value_type()
192    }
193
194    #[cfg(feature = "type_names")]
195    fn value_type_name(&self) -> &'static str {
196        self.0.output.0.value_type_name()
197    }
198
199    fn strong_count(&self) -> usize {
200        Arc::strong_count(&self.0)
201    }
202
203    fn var_eq(&self, other: &DynAnyVar) -> bool {
204        match other {
205            DynAnyVar::Merge(o) => Arc::ptr_eq(&self.0, &o.0),
206            _ => false,
207        }
208    }
209
210    fn var_instance_tag(&self) -> VarInstanceTag {
211        VarInstanceTag(Arc::as_ptr(&self.0) as _)
212    }
213
214    fn downgrade(&self) -> DynWeakAnyVar {
215        DynWeakAnyVar::Merge(WeakMergeVar(Arc::downgrade(&self.0)))
216    }
217
218    fn capabilities(&self) -> VarCapability {
219        self.0.output.0.capabilities().as_always_read_only()
220    }
221
222    fn with(&self, visitor: &mut dyn FnMut(&dyn AnyVarValue)) {
223        self.0.output.0.with(visitor);
224    }
225
226    fn get(&self) -> BoxAnyVarValue {
227        self.0.output.0.get()
228    }
229
230    fn set(&self, _: BoxAnyVarValue) -> bool {
231        false
232    }
233
234    fn update(&self) -> bool {
235        false
236    }
237
238    fn modify(&self, _: SmallBox<dyn FnMut(&mut super::AnyVarModify) + Send + 'static, smallbox::space::S4>) -> bool {
239        false
240    }
241
242    fn hook(&self, on_new: SmallBox<dyn FnMut(&crate::AnyVarHookArgs) -> bool + Send + 'static, smallbox::space::S4>) -> super::VarHandle {
243        self.0.output.0.hook(on_new)
244    }
245
246    fn last_update(&self) -> crate::VarUpdateId {
247        self.0.output.0.last_update()
248    }
249
250    fn modify_info(&self) -> crate::animation::ModifyInfo {
251        self.0.output.0.modify_info()
252    }
253
254    fn modify_importance(&self) -> usize {
255        self.0.output.0.modify_importance()
256    }
257
258    fn is_animating(&self) -> bool {
259        self.0.output.0.is_animating()
260    }
261
262    fn hook_animation_stop(&self, handler: crate::animation::AnimationStopFn) -> VarHandle {
263        self.0.output.0.hook_animation_stop(handler)
264    }
265
266    fn current_context(&self) -> DynAnyVar {
267        self.clone_dyn()
268    }
269}
270
271pub(crate) struct WeakMergeVar(Weak<MergeVarData>);
272impl fmt::Debug for WeakMergeVar {
273    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
274        f.debug_tuple("WeakMergeVar").field(&self.0.as_ptr()).finish()
275    }
276}
277impl WeakVarImpl for WeakMergeVar {
278    fn clone_dyn(&self) -> DynWeakAnyVar {
279        DynWeakAnyVar::Merge(WeakMergeVar(self.0.clone()))
280    }
281
282    fn strong_count(&self) -> usize {
283        self.0.strong_count()
284    }
285
286    fn upgrade(&self) -> Option<DynAnyVar> {
287        Some(DynAnyVar::Merge(MergeVar(self.0.upgrade()?)))
288    }
289
290    fn var_eq(&self, other: &DynWeakAnyVar) -> bool {
291        match other {
292            DynWeakAnyVar::Merge(o) => self.0.ptr_eq(&o.0),
293            _ => false,
294        }
295    }
296}
297
298/// Build a [`merge_var!`] from any number of input vars of the same type `I`.
299#[derive(Clone)]
300pub struct MergeVarBuilder<I: VarValue> {
301    inputs: Vec<AnyVar>,
302    _type: PhantomData<fn() -> I>,
303}
304impl<I: VarValue> MergeVarBuilder<I> {
305    /// New empty.
306    pub fn new() -> Self {
307        Self {
308            inputs: vec![],
309            _type: PhantomData,
310        }
311    }
312
313    /// New with pre-allocated inputs.
314    pub fn with_capacity(capacity: usize) -> Self {
315        Self {
316            inputs: Vec::with_capacity(capacity),
317            _type: PhantomData,
318        }
319    }
320
321    /// Push an input.
322    pub fn push(&mut self, input: impl MergeInput<I>) {
323        self.inputs.push(input.into_merge_input().into())
324    }
325
326    /// Build the merge var.
327    pub fn build<O: VarValue>(self, merge: impl FnMut(VarMergeInputs<I>) -> O + Send + 'static) -> Var<O> {
328        if self.inputs.iter().any(|i| i.capabilities().is_contextual()) {
329            let merge = Arc::new(Mutex::new(merge));
330            return contextual_var(move || {
331                let builder = MergeVarBuilder {
332                    inputs: self.inputs.iter().map(|v| v.current_context()).collect(),
333                    _type: PhantomData,
334                };
335                builder.build_tail(clmv!(merge, |inputs| merge.lock()(inputs)))
336            });
337        }
338        self.build_tail(merge)
339    }
340    fn build_tail<O: VarValue>(self, mut merge: impl FnMut(VarMergeInputs<I>) -> O + Send + 'static) -> Var<O> {
341        let any = var_merge_impl(
342            self.inputs.into_boxed_slice(),
343            smallbox!(move |vars: &[AnyVar]| {
344                let values: Box<[BoxAnyVarValue]> = vars.iter().map(|v| v.get()).collect();
345                let out = merge(VarMergeInputs {
346                    inputs: &values[..],
347                    _type: PhantomData,
348                });
349                BoxAnyVarValue::new(out)
350            }),
351            TypeId::of::<O>(),
352        );
353        Var::new_any(any)
354    }
355}
356impl<I: VarValue> Default for MergeVarBuilder<I> {
357    fn default() -> Self {
358        Self::new()
359    }
360}
361impl<T: VarValue + AsRef<str>> MergeVarBuilder<T> {
362    /// Build to a var that joins texts placing a `separator` between each.
363    pub fn join_txt(self, separator: impl Into<Txt>) -> Var<Txt> {
364        self.join_txt_impl(separator.into())
365    }
366    fn join_txt_impl(self, separator: Txt) -> Var<Txt> {
367        self.build(move |t| {
368            let mut s = String::new();
369            let mut sep = "";
370            for t in t.iter() {
371                write!(&mut s, "{sep}{}", t.as_ref()).unwrap();
372                sep = &separator;
373            }
374            s.into()
375        })
376    }
377}
378
379/// Input arguments for the merge closure of [`MergeVarBuilder`] merge vars.
380pub struct VarMergeInputs<'a, I: VarValue> {
381    inputs: &'a [BoxAnyVarValue],
382    _type: PhantomData<&'a I>,
383}
384impl<I: VarValue> VarMergeInputs<'_, I> {
385    /// Number of inputs.
386    #[expect(clippy::len_without_is_empty)]
387    pub fn len(&self) -> usize {
388        self.inputs.len()
389    }
390
391    /// Iterate over the values.
392    pub fn iter(&self) -> impl ExactSizeIterator<Item = &I> + '_ {
393        (0..self.len()).map(move |i| &self[i])
394    }
395}
396impl<I: VarValue> ops::Index<usize> for VarMergeInputs<'_, I> {
397    type Output = I;
398
399    fn index(&self, index: usize) -> &Self::Output {
400        self.inputs[index].downcast_ref().unwrap()
401    }
402}