zng_var/
vars.rs

1use std::{mem, thread::ThreadId, time::Duration};
2
3use zng_app_context::{app_local, context_local};
4use zng_time::INSTANT_APP;
5
6use crate::animation::AnimationTimer;
7
8use self::types::ArcCowVar;
9
10use super::{
11    animation::{Animations, ModifyInfo},
12    *,
13};
14
15/// Represents the last time a variable was mutated or the current update cycle.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, bytemuck::NoUninit)]
17#[repr(transparent)]
18pub struct VarUpdateId(u32);
19impl VarUpdateId {
20    /// ID that is never new.
21    pub const fn never() -> Self {
22        VarUpdateId(0)
23    }
24
25    fn next(&mut self) {
26        if self.0 == u32::MAX {
27            self.0 = 1;
28        } else {
29            self.0 += 1;
30        }
31    }
32}
33impl Default for VarUpdateId {
34    fn default() -> Self {
35        Self::never()
36    }
37}
38
39pub(super) type VarUpdateFn = Box<dyn FnOnce() + Send>;
40
41app_local! {
42    pub(crate) static VARS_SV: VarsService = VarsService::new();
43}
44
45context_local! {
46    pub(crate) static VARS_MODIFY_CTX: Option<ModifyInfo> = None;
47}
48
49pub(crate) struct VarsService {
50    pub(super) ans: Animations,
51
52    update_id: VarUpdateId,
53
54    updates: Mutex<Vec<(ModifyInfo, VarUpdateFn)>>,
55    updating_thread: Option<ThreadId>,
56    updates_after: Mutex<Vec<(ModifyInfo, VarUpdateFn)>>,
57
58    app_waker: Option<Box<dyn Fn() + Send + Sync>>,
59    modify_trace: Option<Box<dyn Fn(&'static str) + Send + Sync>>,
60
61    perm: Mutex<Vec<Box<dyn Any + Send>>>,
62}
63impl VarsService {
64    pub(crate) fn new() -> Self {
65        Self {
66            ans: Animations::new(),
67            update_id: VarUpdateId(1),
68            updates: Mutex::new(vec![]),
69            updating_thread: None,
70            updates_after: Mutex::new(vec![]),
71            app_waker: None,
72            modify_trace: None,
73            perm: Mutex::new(vec![]),
74        }
75    }
76
77    pub(crate) fn wake_app(&self) {
78        if let Some(w) = &self.app_waker {
79            w()
80        }
81    }
82}
83
84/// Variable updates and animation service.
85pub struct VARS;
86impl VARS {
87    /// Id of the current vars update in the app scope.
88    ///
89    /// Variable with [`AnyVar::last_update`] equal to this are *new*.
90    pub fn update_id(&self) -> VarUpdateId {
91        VARS_SV.read().update_id
92    }
93
94    /// Read-write that defines if animations are enabled on the app.
95    ///
96    /// The value is the same as [`sys_animations_enabled`], if set the variable disconnects from system config.
97    ///
98    /// [`sys_animations_enabled`]: Self::sys_animations_enabled
99    pub fn animations_enabled(&self) -> ArcCowVar<bool, ArcVar<bool>> {
100        VARS_SV.read().ans.animations_enabled.clone()
101    }
102
103    /// Read-only that tracks if animations are enabled in the operating system.
104    ///
105    /// This is `true` by default, it updates when the operating system config changes.
106    pub fn sys_animations_enabled(&self) -> ReadOnlyArcVar<bool> {
107        VARS_SV.read().ans.sys_animations_enabled.read_only()
108    }
109
110    /// Variable that defines the global frame duration, the default is 60fps `(1.0 / 60.0).secs()`.
111    pub fn frame_duration(&self) -> ArcVar<Duration> {
112        VARS_SV.read().ans.frame_duration.clone()
113    }
114
115    /// Variable that defines a global scale for the elapsed time of animations.
116    pub fn animation_time_scale(&self) -> ArcVar<Factor> {
117        VARS_SV.read().ans.animation_time_scale.clone()
118    }
119
120    /// Info about the current context when requesting variable modification.
121    ///
122    /// If is currently inside a [`VARS.animate`] closure, or inside a [`Var::modify`] closure requested by an animation, or inside
123    /// an [`AnimationController`], returns the info that was collected at the moment the animation was requested. Outside of animations
124    /// gets an info with [`importance`] guaranteed to override the [`modify_importance`].
125    ///
126    /// [`importance`]: ModifyInfo::importance
127    /// [`modify_importance`]: AnyVar::modify_importance
128    /// [`AnimationController`]: animation::AnimationController
129    /// [`VARS.animate`]: VARS::animate
130    pub fn current_modify(&self) -> ModifyInfo {
131        match VARS_MODIFY_CTX.get_clone() {
132            Some(current) => current, // override set by modify and animation closures.
133            None => VARS_SV.read().ans.current_modify.clone(),
134        }
135    }
136
137    /// Adds an `animation` closure that is called every frame to update captured variables, starting after next frame.
138    ///
139    /// This is used to implement all [`Var<T>`] animations, it enables any kind of variable animation,
140    /// including multiple variables.
141    ///
142    /// Returns an [`AnimationHandle`] that can be used to monitor the animation status and to [`stop`] or to
143    /// make the animation [`perm`].
144    ///
145    /// # Variable Control
146    ///
147    /// Animations assume *control* of a variable on the first time they cause its value to be new, after this
148    /// moment the [`AnyVar::is_animating`] value is `true` and [`AnyVar::modify_importance`] is the animation's importance,
149    /// until the animation stops. Only one animation can control a variable at a time, if an animation loses control of a
150    /// variable all attempts to modify it from inside the animation are ignored.
151    ///
152    /// Later started animations steal control from previous animations, update, modify or set calls also remove the variable
153    /// from being affected by a running animation, even if just set to an equal value, that is, not actually updated.
154    ///
155    /// # Nested Animations
156    ///
157    /// Other animations can be started from inside the animation closure, these *nested* animations have the same importance
158    /// as the *parent* animation, the animation handle is different and [`AnyVar::is_animating`] is `false` if the nested animation
159    /// is dropped before the *parent* animation. But because the animations share the same importance the parent animation can
160    /// set the variable again.
161    ///
162    /// # Examples
163    ///
164    /// The example animates a `text` variable from `"Animation at 0%"` to `"Animation at 100%"`, when the animation
165    /// stops the `completed` variable is set to `true`.
166    ///
167    /// ```
168    /// # use zng_var::{*, animation::easing};
169    /// # use zng_txt::*;
170    /// # use zng_unit::*;
171    /// # use zng_clone_move::*;
172    /// #
173    /// fn animate_text(text: &impl Var<Txt>, completed: &impl Var<bool>) {
174    ///     let transition = animation::Transition::new(0u8, 100);
175    ///     let mut prev_value = 101;
176    ///     VARS.animate(clmv!(text, completed, |animation| {
177    ///         let step = easing::expo(animation.elapsed_stop(1.secs()));
178    ///         let value = transition.sample(step);
179    ///         if value != prev_value {
180    ///             if value == 100 {
181    ///                 animation.stop();
182    ///                 let _ = completed.set(true);
183    ///             }
184    ///             let _ = text.set(formatx!("Animation at {value}%"));
185    ///             prev_value = value;
186    ///         }
187    ///     }))
188    ///     .perm()
189    /// }
190    /// ```
191    ///
192    /// Note that the animation can be stopped from the inside, the closure parameter is an [`Animation`]. In
193    /// the example this is the only way to stop the animation, because [`perm`] was called. Animations hold a clone
194    /// of the variables they affect and exist for the duration of the app if not stopped, causing the app to wake and call the
195    /// animation closure for every frame.
196    ///
197    /// This method is the most basic animation interface, used to build all other animations, its rare that you
198    /// will need to use it directly, most of the time animation effects can be composted using the [`Var`] easing and mapping
199    /// methods.
200    ///
201    /// ```
202    /// # use zng_var::{*, animation::easing};
203    /// # use zng_txt::*;
204    /// # use zng_unit::*;
205    /// # fn demo() {
206    /// let value = var(0u8);
207    /// let text = value.map(|v| formatx!("Animation at {v}%"));
208    /// value.ease(100, 1.secs(), easing::expo);
209    /// # }
210    /// ```
211    ///
212    /// # Optimization Tips
213    ///
214    /// When no animation is running the app *sleeps* awaiting for an external event, update request or timer elapse, when at least one
215    /// animation is running the app awakes every [`VARS.frame_duration`]. You can use [`Animation::sleep`] to *pause* the animation
216    /// for a duration, if all animations are sleeping the app is also sleeping.
217    ///
218    /// Animations lose control over a variable permanently when a newer animation modifies the var or
219    /// the var is modified directly, but even if the animation can't control any variables **it keeps running**.
220    /// This happens because the system has no insight of all side effects caused by the `animation` closure. You
221    /// can use the [`VARS.current_modify`] and [`AnyVar::modify_importance`] to detect when the animation no longer affects
222    /// any variables and stop the animation to avoid awaking the app for no reason.
223    ///
224    /// These optimizations are already implemented by the animations provided as methods of [`Var<T>`].
225    ///
226    /// # External Controller
227    ///
228    /// The animation can be controlled from the inside using the [`Animation`] reference, it can be stopped using the returned
229    /// [`AnimationHandle`], and it can also be controlled by a registered [`AnimationController`] that can manage multiple
230    /// animations at the same time, see [`with_animation_controller`] for more details.
231    ///
232    /// [`AnimationHandle`]: animation::AnimationHandle
233    /// [`AnimationController`]: animation::AnimationController
234    /// [`Animation`]: animation::Animation
235    /// [`Animation::sleep`]: animation::Animation::sleep
236    /// [`stop`]: animation::AnimationHandle::stop
237    /// [`perm`]: animation::AnimationHandle::perm
238    /// [`with_animation_controller`]: Self::with_animation_controller
239    /// [`VARS.frame_duration`]: VARS::frame_duration
240    /// [`VARS.current_modify`]: VARS::current_modify
241    pub fn animate<A>(&self, animation: A) -> animation::AnimationHandle
242    where
243        A: FnMut(&animation::Animation) + Send + 'static,
244    {
245        Animations::animate(animation)
246    }
247
248    /// Calls `animate` while `controller` is registered as the animation controller.
249    ///
250    /// The `controller` is notified of animation events for each animation spawned by `animate` and can affect then with the same
251    /// level of access as [`VARS.animate`]. Only one controller can affect animations at a time.
252    ///
253    /// This can be used to manage multiple animations at the same time, or to get [`VARS.animate`] level of access to an animation
254    /// that is not implemented to allow such access. Note that animation implementers are not required to support the full
255    /// [`Animation`] API, for example, there is no guarantee that a restart requested by the controller will repeat the same animation.
256    ///
257    /// The controller can start new animations, these animations will have the same controller if not overridden, you can
258    /// use this method and the `()` controller to avoid this behavior.
259    ///
260    /// [`Animation`]: animation::Animation
261    /// [`VARS.animate`]: VARS::animate
262    pub fn with_animation_controller<R>(&self, controller: impl animation::AnimationController, animate: impl FnOnce() -> R) -> R {
263        let controller: Box<dyn animation::AnimationController> = Box::new(controller);
264        let mut opt = Some(Arc::new(controller));
265        animation::VARS_ANIMATION_CTRL_CTX.with_context(&mut opt, animate)
266    }
267
268    pub(super) fn schedule_update(&self, update: VarUpdateFn, type_name: &'static str) {
269        let vars = VARS_SV.read();
270        if let Some(trace) = &vars.modify_trace {
271            trace(type_name);
272        }
273        let cur_modify = match VARS_MODIFY_CTX.get_clone() {
274            Some(current) => current, // override set by modify and animation closures.
275            None => vars.ans.current_modify.clone(),
276        };
277
278        if let Some(id) = vars.updating_thread {
279            if std::thread::current().id() == id {
280                // is binding request, enqueue for immediate exec.
281                vars.updates.lock().push((cur_modify, update));
282            } else {
283                // is request from app task thread when we are already updating, enqueue for exec after current update.
284                vars.updates_after.lock().push((cur_modify, update));
285            }
286        } else {
287            // request from any app thread,
288            vars.updates.lock().push((cur_modify, update));
289            vars.wake_app();
290        }
291    }
292
293    /// Keep the `value` alive for  the app lifetime.
294    pub fn perm(&self, value: impl Any + Send) {
295        let value = Box::new(value);
296        VARS_SV.read().perm.lock().push(value);
297    }
298
299    pub(crate) fn wake_app(&self) {
300        VARS_SV.read().wake_app();
301    }
302}
303
304/// VARS APP integration.
305#[expect(non_camel_case_types)]
306pub struct VARS_APP;
307impl VARS_APP {
308    /// Register a closure called when [`apply_updates`] should be called because there are changes pending.
309    ///
310    /// # Panics
311    ///
312    /// Panics if already called for the current app. This must be called by app framework implementers only.
313    ///
314    /// [`apply_updates`]: Self::apply_updates
315    pub fn init_app_waker(&self, waker: impl Fn() + Send + Sync + 'static) {
316        let mut vars = VARS_SV.write();
317        assert!(vars.app_waker.is_none());
318        vars.app_waker = Some(Box::new(waker));
319    }
320
321    /// Register a closure called when a variable modify is about to be scheduled. The
322    /// closure parameter is the type name of the variable type.
323    ///
324    /// # Panics
325    ///
326    /// Panics if already called for the current app. This must be called by app framework implementers only.
327    pub fn init_modify_trace(&self, trace: impl Fn(&'static str) + Send + Sync + 'static) {
328        let mut vars = VARS_SV.write();
329        assert!(vars.modify_trace.is_none());
330        vars.modify_trace = Some(Box::new(trace));
331    }
332
333    /// If [`apply_updates`] will do anything.
334    ///
335    /// [`apply_updates`]: Self::apply_updates
336    pub fn has_pending_updates(&self) -> bool {
337        !VARS_SV.write().updates.get_mut().is_empty()
338    }
339
340    /// Sets the `sys_animations_enabled` read-only variable.
341    pub fn set_sys_animations_enabled(&self, enabled: bool) {
342        VARS_SV.read().ans.sys_animations_enabled.set(enabled);
343    }
344
345    /// Apply all pending updates, call hooks and update bindings.
346    ///
347    /// This must be called by app framework implementers only.
348    pub fn apply_updates(&self) {
349        let _s = tracing::trace_span!("VARS").entered();
350        let _t = INSTANT_APP.pause_for_update();
351        Self::apply_updates_and_after(0)
352    }
353    fn apply_updates_and_after(depth: u8) {
354        let mut vars = VARS_SV.write();
355
356        match depth {
357            0 => {
358                vars.update_id.next();
359                vars.ans.animation_start_time = None;
360            }
361            10 => {
362                // high-pressure from worker threads, skip
363                return;
364            }
365            _ => {}
366        }
367
368        // updates requested by other threads while was applying updates
369        let mut updates = mem::take(vars.updates_after.get_mut());
370        // normal updates
371        if updates.is_empty() {
372            updates = mem::take(vars.updates.get_mut());
373        } else {
374            updates.append(vars.updates.get_mut());
375        }
376        // apply pending updates
377        if !updates.is_empty() {
378            debug_assert!(vars.updating_thread.is_none());
379            vars.updating_thread = Some(std::thread::current().id());
380
381            drop(vars);
382            update_each_and_bindings(updates, 0);
383
384            vars = VARS_SV.write();
385            vars.updating_thread = None;
386
387            if !vars.updates_after.get_mut().is_empty() {
388                drop(vars);
389                Self::apply_updates_and_after(depth + 1)
390            }
391        }
392
393        fn update_each_and_bindings(updates: Vec<(ModifyInfo, VarUpdateFn)>, depth: u16) {
394            if depth == 1000 {
395                tracing::error!(
396                    "updated variable bindings 1000 times, probably stuck in an infinite loop\n\
397                    will skip next updates"
398                );
399                return;
400            }
401
402            for (info, update) in updates {
403                VARS_MODIFY_CTX.with_context(&mut Some(Arc::new(Some(info))), update);
404
405                let mut vars = VARS_SV.write();
406                let updates = mem::take(vars.updates.get_mut());
407                if !updates.is_empty() {
408                    drop(vars);
409                    update_each_and_bindings(updates, depth + 1);
410                }
411            }
412        }
413    }
414
415    /// Does one animation frame if the frame duration has elapsed.
416    ///
417    /// This must be called by app framework implementers only.
418    pub fn update_animations(&self, timer: &mut impl AnimationTimer) {
419        Animations::update_animations(timer)
420    }
421
422    /// Register the next animation frame, if there are any active animations.
423    ///
424    /// This must be called by app framework implementers only.
425    pub fn next_deadline(&self, timer: &mut impl AnimationTimer) {
426        Animations::next_deadline(timer)
427    }
428}