zng_var/var_impl/
contextual_var.rs

1//! Context dependent unwrapping mapping var
2
3use std::{
4    any::{Any, TypeId},
5    fmt,
6    sync::{Arc, Weak},
7};
8
9use parking_lot::{Mutex, RwLock};
10use smallbox::{SmallBox, smallbox};
11use zng_app_context::context_local;
12
13use crate::{AnyVar, DynAnyVar, DynWeakAnyVar, Var, VarHandle, VarImpl, VarInstanceTag, VarValue, WeakVarImpl};
14
15use super::VarCapability;
16
17/// Create a type erased contextualized variable.
18///
19/// The `context_init` closure must produce variables of the same `value_type`. The value type is needed to
20/// avoid contextualizing the variable for calls of [`AnyVar::downcast`] and [`AnyVar::value_type`] that often
21/// happen outside of the variable final context, such as in widget property capture.
22///
23/// See [`contextual_var`] for more details about contextualized variables.
24pub fn any_contextual_var(context_init: impl FnMut() -> AnyVar + Send + 'static, value_type: TypeId) -> AnyVar {
25    any_contextual_var_impl(smallbox!(ContextInitFnMut(context_init)), value_type)
26}
27pub(super) fn any_contextual_var_impl(context_init: ContextInitFn, value_type: TypeId) -> AnyVar {
28    AnyVar(DynAnyVar::Contextual(ContextualVar::new(context_init, value_type)))
29}
30
31/// Create a contextualized variable.
32///
33/// This is useful for declaring variables that depend on the contextual state on first usage to actually determinate the value.
34///
35/// # Examples
36///
37/// Basic usage:
38///
39/// ```rust
40/// # macro_rules! fake{($($tt:tt)*) => {}}
41/// # fake! {
42/// widget_set! {
43///     self;
44///     padding = contextual_var(|| WINDOW.vars().safe_padding().map(|p| SideOffsets::from(*p)));
45/// };
46/// # }
47/// ```
48///
49/// The example above shows the declaration of a default widget property `padding` that depends on the contextual `WINDOW.vars` value.
50/// When the padding property reads the variable for the first time (on `UiNode::init`) the contextual may be different from the
51/// declaration, so closure will eval to produce a contextualized inner variable. If the widget is moved to another window the closure
52/// will be called again to get a new contextualized inner variable.
53///
54/// This variable is for advanced usage like this, where you need a contextual value and there is no *CONTEXT_VAR* that provides the value.
55/// Note that you **do not need this** to contextualize context vars, they already are context aware.
56///
57/// # Capabilities
58///
59/// When the returned variable is used in a new context for the first time the `context_init` closure is called
60/// to produce the actual variable in that context.
61///
62/// If a clone of the returned variable is moved to another context the `context_init` closure is called again
63/// to init that clone.
64///
65/// If the return variable is *mapped* the mapping var is also context aware and will also delay init until first usage.
66///
67/// If [`AnyVar::capabilities`] is called in a new context the `context_init` is not called, the capabilities for an unloaded
68/// contextual var is `CONTEXT | MODIFY_CHANGES`, if the context is loaded the inner variable capabilities is included.
69pub fn contextual_var<T: VarValue>(mut context_init: impl FnMut() -> Var<T> + Send + 'static) -> Var<T> {
70    Var::new_any(any_contextual_var(move || context_init().into(), TypeId::of::<T>()))
71}
72
73pub(super) type ContextInitFn = SmallBox<dyn ContextInitFnImpl, smallbox::space::S8>;
74// not using a FnMut here so that the source can be inspected (WhenVarBuilder does this)
75pub(crate) trait ContextInitFnImpl: Send + Any {
76    fn init(&mut self) -> AnyVar;
77}
78struct ContextInitFnMut<F>(F);
79impl<F: FnMut() -> AnyVar + Send + 'static> ContextInitFnImpl for ContextInitFnMut<F> {
80    fn init(&mut self) -> AnyVar {
81        self.0()
82    }
83}
84
85pub(super) struct ContextualVarData {
86    pub(super) init: Arc<Mutex<ContextInitFn>>,
87    ctx: RwLock<(AnyVar, ContextInitHandle)>,
88}
89
90pub(crate) struct ContextualVar(pub(super) Box<ContextualVarData>);
91impl Clone for ContextualVar {
92    fn clone(&self) -> Self {
93        Self(Box::new(ContextualVarData {
94            init: self.0.init.clone(),
95            ctx: RwLock::new((no_ctx_var(self.value_type()), ContextInitHandle::no_context())),
96        }))
97    }
98}
99impl fmt::Debug for ContextualVar {
100    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101        let mut b = f.debug_struct("ContextualVar");
102        b.field("init", &Arc::as_ptr(&self.0.init));
103        if let Some(ctx) = self.0.ctx.try_read() {
104            if ctx.1.is_no_context() {
105                b.field("ctx", &"<no context>");
106            } else {
107                b.field("ctx.handle", &ctx.1);
108                b.field("ctx.var", &ctx.0.0);
109            }
110        } else {
111            b.field("ctx", &"<locked>");
112        }
113
114        b.finish()
115    }
116}
117impl PartialEq for ContextualVar {
118    fn eq(&self, other: &Self) -> bool {
119        Arc::ptr_eq(&self.0.init, &other.0.init) && {
120            let a = self.0.ctx.read_recursive();
121            let b = other.0.ctx.read_recursive();
122            a.1 == b.1 && a.0.var_eq(&b.0)
123        }
124    }
125}
126impl ContextualVar {
127    pub fn new(init: ContextInitFn, value_type: TypeId) -> Self {
128        Self(Box::new(ContextualVarData {
129            init: Arc::new(Mutex::new(init)),
130            ctx: RwLock::new((no_ctx_var(value_type), ContextInitHandle::no_context())),
131        }))
132    }
133
134    fn load(&self) -> parking_lot::MappedRwLockReadGuard<'_, AnyVar> {
135        let ctx = self.0.ctx.read();
136        let id = ContextInitHandle::current();
137        if ctx.1 == id {
138            parking_lot::RwLockReadGuard::map(ctx, |f| &f.0)
139        } else {
140            drop(ctx);
141            let mut ctx = self.0.ctx.write();
142            if ctx.1 != id {
143                ctx.0 = self.0.init.lock().init();
144                if ctx.0.capabilities().is_contextual() {
145                    ctx.0 = ctx.0.current_context();
146                }
147                ctx.1 = id;
148            }
149            let ctx = parking_lot::RwLockWriteGuard::downgrade(ctx);
150            parking_lot::RwLockReadGuard::map(ctx, |f| &f.0)
151        }
152    }
153}
154impl VarImpl for ContextualVar {
155    fn clone_dyn(&self) -> DynAnyVar {
156        DynAnyVar::Contextual(self.clone())
157    }
158
159    fn current_context(&self) -> DynAnyVar {
160        self.load().0.current_context()
161    }
162
163    fn value_type(&self) -> std::any::TypeId {
164        let ctx = self.0.ctx.read();
165        let (var, ctx) = &*ctx;
166        if ctx.is_no_context() {
167            var.with(|v| v.downcast_ref::<NoContext>().unwrap().value_type)
168        } else {
169            var.value_type()
170        }
171    }
172
173    #[cfg(feature = "type_names")]
174    fn value_type_name(&self) -> &'static str {
175        self.load().0.value_type_name()
176    }
177
178    fn strong_count(&self) -> usize {
179        self.load().0.strong_count()
180    }
181
182    fn var_eq(&self, other: &DynAnyVar) -> bool {
183        match other {
184            DynAnyVar::Contextual(o) => self == o,
185            _ => false,
186        }
187    }
188
189    fn var_instance_tag(&self) -> VarInstanceTag {
190        self.load().0.var_instance_tag()
191    }
192
193    fn downgrade(&self) -> DynWeakAnyVar {
194        DynWeakAnyVar::Contextual(WeakContextualVar(Box::new(WeakContextualVarData {
195            init: Arc::downgrade(&self.0.init),
196            value_type: self.value_type(),
197        })))
198    }
199
200    fn capabilities(&self) -> VarCapability {
201        let mut caps = VarCapability::CONTEXT | VarCapability::MODIFY_CHANGES;
202        let ctx = self.0.ctx.read();
203        if ctx.1 == ContextInitHandle::current() {
204            let mut inner = ctx.0.capabilities();
205            inner.remove(VarCapability::CONTEXT_CHANGES);
206            caps |= inner;
207        }
208        caps
209    }
210
211    fn with(&self, visitor: &mut dyn FnMut(&dyn crate::AnyVarValue)) {
212        self.load().0.with(visitor);
213    }
214
215    fn get(&self) -> crate::BoxAnyVarValue {
216        self.load().0.get()
217    }
218
219    fn set(&self, new_value: crate::BoxAnyVarValue) -> bool {
220        self.load().0.set(new_value)
221    }
222
223    fn update(&self) -> bool {
224        self.load().0.update()
225    }
226
227    fn modify(&self, modify: SmallBox<dyn FnMut(&mut super::AnyVarModify) + Send + 'static, smallbox::space::S4>) -> bool {
228        self.load().0.modify(modify)
229    }
230
231    fn hook(&self, on_new: SmallBox<dyn FnMut(&crate::AnyVarHookArgs) -> bool + Send + 'static, smallbox::space::S4>) -> super::VarHandle {
232        self.load().0.hook(on_new)
233    }
234
235    fn last_update(&self) -> crate::VarUpdateId {
236        self.load().0.last_update()
237    }
238
239    fn modify_info(&self) -> crate::animation::ModifyInfo {
240        self.load().0.modify_info()
241    }
242
243    fn modify_importance(&self) -> usize {
244        self.load().0.modify_importance()
245    }
246
247    fn is_animating(&self) -> bool {
248        self.load().0.is_animating()
249    }
250
251    fn hook_animation_stop(&self, handler: crate::animation::AnimationStopFn) -> VarHandle {
252        self.load().0.hook_animation_stop(handler)
253    }
254}
255
256#[derive(Clone)]
257
258struct WeakContextualVarData {
259    init: Weak<Mutex<ContextInitFn>>,
260    value_type: TypeId,
261}
262
263#[derive(Clone)]
264pub(crate) struct WeakContextualVar(Box<WeakContextualVarData>);
265impl fmt::Debug for WeakContextualVar {
266    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
267        f.debug_struct("WeakContextualVar").field("init", &self.0.init.as_ptr()).finish()
268    }
269}
270impl WeakVarImpl for WeakContextualVar {
271    fn clone_dyn(&self) -> DynWeakAnyVar {
272        DynWeakAnyVar::Contextual(self.clone())
273    }
274
275    fn strong_count(&self) -> usize {
276        self.0.init.strong_count()
277    }
278
279    fn upgrade(&self) -> Option<DynAnyVar> {
280        self.0.init.upgrade().map(|init| {
281            DynAnyVar::Contextual(ContextualVar(Box::new(ContextualVarData {
282                init,
283                ctx: RwLock::new((no_ctx_var(self.0.value_type), ContextInitHandle::no_context())),
284            })))
285        })
286    }
287}
288
289fn no_ctx_var(value_type: TypeId) -> AnyVar {
290    crate::const_var(NoContext { value_type }).into()
291}
292
293#[derive(Debug, PartialEq, Clone)]
294struct NoContext {
295    value_type: TypeId,
296}
297
298#[derive(Default)]
299struct ContextInitHandleMarker;
300
301/// Identifies the unique context a [`contextual_var`] is in.
302///
303/// Each node that sets context-vars have an unique ID, it is different after each (re)init. The contextual var
304/// records this ID, and rebuilds when it has changed. The contextualized inner vars are retained locally to the clone
305/// of the contextual var.
306#[derive(Clone, Default)]
307pub struct ContextInitHandle(Option<Arc<ContextInitHandleMarker>>);
308context_local! {
309    static CONTEXT_INIT_ID: ContextInitHandleMarker = ContextInitHandleMarker;
310}
311impl ContextInitHandle {
312    /// Generates a new unique handle.
313    pub fn new() -> Self {
314        Self(Some(Arc::new(ContextInitHandleMarker)))
315    }
316
317    /// Identifies the state before first contextualization.
318    ///
319    /// This is the default value.
320    pub const fn no_context() -> Self {
321        Self(None)
322    }
323
324    /// Gets the current context handle.
325    ///
326    /// # Panics
327    ///
328    /// Panics is not called in an app context at least, never returns [`no_context`].
329    ///
330    /// [`no_context`]: Self::no_context
331    pub fn current() -> Self {
332        Self(Some(CONTEXT_INIT_ID.get()))
333    }
334
335    /// Handle represents the state before first contextualization.
336    pub fn is_no_context(&self) -> bool {
337        self.0.is_none()
338    }
339
340    /// Runs `action` with `self` as the current context ID.
341    ///
342    /// Note that [`ContextVar::with_context`] already calls this method.
343    ///
344    /// # Panics
345    ///
346    /// Panics if the handle [`is_no_context`].
347    ///
348    /// [`is_no_context`]: Self::is_no_context
349    /// [`ContextVar::with_context`]: crate::ContextVar::with_context
350    #[inline(always)]
351    pub fn with_context<R>(&self, action: impl FnOnce() -> R) -> R {
352        let mut opt = self.0.clone();
353        CONTEXT_INIT_ID.with_context(&mut opt, action)
354    }
355
356    /// Create a weak handle that can be used to monitor this handle without holding it.
357    pub fn downgrade(&self) -> WeakContextInitHandle {
358        match &self.0 {
359            Some(a) => WeakContextInitHandle(Arc::downgrade(a)),
360            None => WeakContextInitHandle(std::sync::Weak::new()),
361        }
362    }
363}
364impl fmt::Debug for ContextInitHandle {
365    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
366        f.debug_tuple("WeakContextInitHandle")
367            .field(&match &self.0 {
368                Some(a) => std::sync::Arc::as_ptr(a),
369                None => std::ptr::null(),
370            })
371            .finish()
372    }
373}
374impl PartialEq for ContextInitHandle {
375    fn eq(&self, other: &Self) -> bool {
376        match (&self.0, &other.0) {
377            (Some(a), Some(b)) => Arc::ptr_eq(a, b),
378            (None, None) => true,
379            _ => false,
380        }
381    }
382}
383impl Eq for ContextInitHandle {}
384
385/// Weak [`ContextInitHandle`].
386#[derive(Clone, Default)]
387pub struct WeakContextInitHandle(std::sync::Weak<ContextInitHandleMarker>);
388impl WeakContextInitHandle {
389    /// Returns `true` if the strong handle still exists.
390    pub fn is_alive(&self) -> bool {
391        self.0.strong_count() > 0
392    }
393}
394impl fmt::Debug for WeakContextInitHandle {
395    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
396        f.debug_tuple("WeakContextInitHandle")
397            .field(&std::sync::Weak::as_ptr(&self.0))
398            .finish()
399    }
400}
401impl PartialEq for WeakContextInitHandle {
402    fn eq(&self, other: &Self) -> bool {
403        std::sync::Weak::ptr_eq(&self.0, &other.0)
404    }
405}
406impl Eq for WeakContextInitHandle {}
407impl std::hash::Hash for WeakContextInitHandle {
408    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
409        let i = std::sync::Weak::as_ptr(&self.0) as usize;
410        std::hash::Hash::hash(&i, state)
411    }
412}