zng_var/var_impl/
context_var.rs

1//! Context vars
2
3use std::{any::TypeId, fmt, ops, sync::Arc};
4
5use smallbox::SmallBox;
6use zng_app_context::{AppLocalId, ContextLocal, ContextLocalKeyProvider};
7
8use crate::{
9    AnyVar, AnyVarHookArgs, AnyVarModify, AnyVarValue, BoxAnyVarValue, ContextInitHandle, DynAnyVar, DynWeakAnyVar, IntoVar, Var,
10    VarCapability, VarHandle, VarImpl, VarInstanceTag, VarUpdateId, VarValue, WeakVarImpl,
11};
12
13///<span data-del-macro-root></span> Declares new [`ContextVar<T>`] static items.
14///
15/// # Examples
16///
17/// ```
18/// # use zng_var::context_var;
19/// # #[derive(Debug, Clone, PartialEq)]
20/// # struct NotConst(u8);
21/// # fn init_val() -> NotConst { NotConst(10) }
22/// #
23/// context_var! {
24///     /// A public documented context var.
25///     pub static FOO_VAR: u8 = 10;
26///
27///     // A private context var.
28///     static BAR_VAR: NotConst = init_val();
29///
30///     // A var that *inherits* from another.
31///     pub static DERIVED_VAR: u8 = FOO_VAR;
32/// }
33/// ```
34///
35/// # Default Value
36///
37/// All context variable have a default fallback value that is used when the variable is not set in the context.
38///
39/// The default value is instantiated once per app and is the value of the variable when it is not set in the context,
40/// any value [`IntoVar<T>`] is allowed, including other variables.
41///
42/// The default value can also be a [`Var::map`] to another context var, but note that mapping vars are contextualized,
43/// meaning that they evaluate the mapping in each different context read, so a context var with mapping value
44/// read in a thousand widgets will generate a thousand different mapping vars, but if the same var mapping is set
45/// in the root widget, the thousand widgets will all use the same mapping var.
46///
47/// # Naming Convention
48///
49/// It is recommended that the type name ends with the `_VAR` suffix.
50///
51/// # Context Local
52///
53/// If you are only interested in sharing a contextual value you can use the [`context_local!`] macro instead.
54///
55/// [`context_local!`]: crate::__context_var_local
56#[macro_export]
57macro_rules! context_var {
58    ($(
59        $(#[$attr:meta])*
60        $vis:vis static $NAME:ident: $Type:ty = $default:expr;
61    )+) => {$(
62        $(#[$attr])*
63        $vis static $NAME: $crate::ContextVar<$Type> = {
64            $crate::__context_var_local! {
65                static CTX: $crate::AnyVar = $crate::context_var_init::<$Type>($default);
66            }
67            static VAR: std::sync::OnceLock<$crate::Var<$Type>> = std::sync::OnceLock::new();
68            $crate::ContextVar::new(&CTX, &VAR)
69        };
70    )+}
71}
72
73#[doc(hidden)]
74pub use zng_app_context::context_local as __context_var_local;
75
76#[doc(hidden)]
77pub fn context_var_init<T: VarValue>(init: impl IntoVar<T>) -> AnyVar {
78    init.into_var().into()
79}
80
81impl<T: VarValue> ContextLocalKeyProvider for ContextVar<T> {
82    fn context_local_key(&'static self) -> AppLocalId {
83        self.ctx.context_local_key()
84    }
85}
86
87/// Represents a named contextual variable.
88///
89/// This type dereferences to the actual context [`Var<T>`]. It also implements [`IntoVar<T>`]
90/// that converts to the context var, you can assign it directly to properties.
91///
92/// See [`context_var!`] for more details about declaring and using context vars. See [`contextual_var`] for more details about
93/// contextualizing variables.
94///
95/// [`contextual_var`]: crate::contextual_var
96pub struct ContextVar<T: VarValue> {
97    ctx: &'static ContextLocal<AnyVar>,
98    var: &'static std::sync::OnceLock<Var<T>>,
99}
100impl<T: VarValue> Copy for ContextVar<T> {}
101impl<T: VarValue> Clone for ContextVar<T> {
102    fn clone(&self) -> Self {
103        *self
104    }
105}
106
107impl<T: VarValue> ContextVar<T> {
108    #[doc(hidden)]
109    pub const fn new(ctx: &'static ContextLocal<AnyVar>, var: &'static std::sync::OnceLock<Var<T>>) -> Self {
110        Self { ctx, var }
111    }
112
113    /// Reference the actual context var.
114    ///
115    /// The variable is [`CONTEXT`] capable, you can call [`current_context`]
116    /// to get the current calling context actual variable. See [`contextual_var`] for more details about contextualizing variables.
117    ///
118    /// Note that `ContextVar<T>` also dereferences to this var.
119    ///
120    /// [`CONTEXT`]: VarCapability::CONTEXT
121    /// [`current_context`]: crate::Var::current_context
122    /// [`contextual_var`]: crate::contextual_var
123    pub fn as_var(&self) -> &Var<T> {
124        self.var
125            .get_or_init(|| Var::new_any(AnyVar(DynAnyVar::Context(ContextVarImpl(self.ctx)))))
126    }
127
128    /// Runs `action` with this context var representing the other `var` in the current thread.
129    ///
130    /// The `var` must be `Some` and must be the [`current_context`], not another contextual variable. The `var`
131    /// is moved to the context storage during the call and them returned to the `var`. The `var` value type must be `T`.
132    ///
133    /// Note that the `var` must be the same for subsequent calls in the same *context*, otherwise [contextualized]
134    /// variables may not update their binding, in widgets you must re-init the descendants if you replace the `var`.
135    ///
136    /// [contextualized]: crate::contextual_var
137    /// [`current_context`]: crate::Var::current_context
138    pub fn with_context<R>(self, id: ContextInitHandle, var: &mut Option<Arc<AnyVar>>, action: impl FnOnce() -> R) -> R {
139        #[cfg(debug_assertions)]
140        {
141            let var = var.as_ref().expect("context `var` not set");
142            assert!(var.value_is::<T>(), "context `var` not of the expected value type `T`");
143            assert!(!var.capabilities().is_contextual(), "context `var` must be current_context");
144        }
145        self.ctx.with_context_var(var, move || id.with_context(action))
146    }
147
148    /// Runs `action` with this context var representing the other `var` in the current thread.
149    ///
150    /// Note that the `var` must be the same for subsequent calls in the same *context*, otherwise [contextualized]
151    /// variables may not update their binding, in widgets you must re-init the descendants if you replace the `var`.
152    ///
153    /// The `var` is converted into var, the actual var, boxed and placed in a new `Arc`, you can use the [`with_context`]
154    /// method to avoid doing this in a hot path.
155    ///
156    /// [contextualized]: crate::contextual_var
157    /// [`with_context`]: Self::with_context
158    pub fn with_context_var<R>(self, id: ContextInitHandle, var: impl IntoVar<T>, action: impl FnOnce() -> R) -> R {
159        let mut var = Some(Arc::new(var.into_var().as_any().current_context()));
160        self.with_context(id, &mut var, action)
161    }
162}
163impl<T: VarValue> ops::Deref for ContextVar<T> {
164    type Target = Var<T>;
165
166    fn deref(&self) -> &Self::Target {
167        self.as_var()
168    }
169}
170impl<T: VarValue> IntoVar<T> for ContextVar<T> {
171    fn into_var(self) -> Var<T> {
172        self.as_var().clone()
173    }
174}
175impl<T: VarValue> From<ContextVar<T>> for Var<T> {
176    fn from(v: ContextVar<T>) -> Self {
177        v.as_var().clone()
178    }
179}
180impl<T: VarValue> From<ContextVar<T>> for AnyVar {
181    fn from(v: ContextVar<T>) -> Self {
182        v.as_any().clone()
183    }
184}
185pub(crate) struct ContextVarImpl(&'static ContextLocal<AnyVar>);
186impl fmt::Debug for ContextVarImpl {
187    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188        f.debug_tuple("ContextVar").finish_non_exhaustive() // TODO add context var name
189    }
190}
191impl PartialEq for ContextVarImpl {
192    fn eq(&self, other: &Self) -> bool {
193        std::ptr::eq(self.0, other.0)
194    }
195}
196impl VarImpl for ContextVarImpl {
197    fn clone_dyn(&self) -> DynAnyVar {
198        DynAnyVar::Context(Self(self.0))
199    }
200
201    fn value_type(&self) -> TypeId {
202        self.0.get_clone().0.value_type()
203    }
204
205    #[cfg(feature = "type_names")]
206    fn value_type_name(&self) -> &'static str {
207        self.0.get().0.value_type_name()
208    }
209
210    fn strong_count(&self) -> usize {
211        1
212    }
213
214    fn var_eq(&self, other: &DynAnyVar) -> bool {
215        match other {
216            DynAnyVar::Context(b) => self == b,
217            _ => false,
218        }
219    }
220
221    fn var_instance_tag(&self) -> VarInstanceTag {
222        self.0.get().0.var_instance_tag()
223    }
224
225    fn downgrade(&self) -> DynWeakAnyVar {
226        DynWeakAnyVar::Context(Self(self.0))
227    }
228
229    fn capabilities(&self) -> VarCapability {
230        self.0.get().0.capabilities() | VarCapability::CONTEXT | VarCapability::MODIFY_CHANGES
231    }
232
233    fn with(&self, visitor: &mut dyn FnMut(&dyn AnyVarValue)) {
234        self.0.get().0.with(visitor);
235    }
236
237    fn get(&self) -> BoxAnyVarValue {
238        self.0.get().0.get()
239    }
240
241    fn set(&self, new_value: BoxAnyVarValue) -> bool {
242        self.0.get().0.set(new_value)
243    }
244
245    fn update(&self) -> bool {
246        self.0.get().0.update()
247    }
248
249    fn modify(&self, modify: SmallBox<dyn FnMut(&mut AnyVarModify) + Send + 'static, smallbox::space::S4>) -> bool {
250        self.0.get().0.modify(modify)
251    }
252
253    fn hook(&self, on_new: SmallBox<dyn FnMut(&AnyVarHookArgs) -> bool + Send + 'static, smallbox::space::S4>) -> VarHandle {
254        self.0.get().0.hook(on_new)
255    }
256
257    fn last_update(&self) -> VarUpdateId {
258        self.0.get().0.last_update()
259    }
260
261    fn modify_info(&self) -> crate::animation::ModifyInfo {
262        self.0.get().0.modify_info()
263    }
264
265    fn modify_importance(&self) -> usize {
266        self.0.get().0.modify_importance()
267    }
268
269    fn is_animating(&self) -> bool {
270        self.0.get().0.is_animating()
271    }
272
273    fn hook_animation_stop(&self, handler: crate::animation::AnimationStopFn) -> VarHandle {
274        self.0.get().0.hook_animation_stop(handler)
275    }
276
277    fn current_context(&self) -> DynAnyVar {
278        // is already contextualized, but no downside calling current_context again, it just clones
279        self.0.get().0.current_context()
280    }
281}
282impl WeakVarImpl for ContextVarImpl {
283    fn clone_dyn(&self) -> DynWeakAnyVar {
284        DynWeakAnyVar::Context(Self(self.0))
285    }
286
287    fn strong_count(&self) -> usize {
288        1
289    }
290
291    fn upgrade(&self) -> Option<DynAnyVar> {
292        Some(DynAnyVar::Context(Self(self.0)))
293    }
294}