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}