zng_var/var_impl/
shared_var.rs

1//! Read-write variable that stores values in a shared storage to save space.
2
3use std::{
4    mem,
5    sync::{Arc, Weak},
6};
7
8use parking_lot::{Mutex, RwLock};
9use smallvec::SmallVec;
10
11use crate::{AnyVar, VARS, Var, VarUpdateId, VarValue, animation::ModifyInfo};
12
13use super::*;
14
15/// New read/write shared reference variable.
16pub fn var<T: VarValue>(initial_value: T) -> Var<T> {
17    Var::new_any(any_var(BoxAnyVarValue::new(initial_value)))
18}
19
20/// New read/write shared reference type-erased variable that has initial value derived from `source`.
21///
22/// This function is useful for creating custom mapping outputs, the new variable
23/// starts with the same [`AnyVar::last_update`] and animation handle as the `source`.
24pub fn var_derived<T: VarValue>(initial_value: T, source: &AnyVar) -> Var<T> {
25    Var::new_any(any_var_derived(BoxAnyVarValue::new(initial_value), source))
26}
27
28/// New read/write shared reference type-erased variable.
29pub fn any_var(initial_value: BoxAnyVarValue) -> AnyVar {
30    AnyVar(DynAnyVar::Shared(SharedVar::new(
31        initial_value,
32        VarUpdateId::never(),
33        ModifyInfo::never(),
34    )))
35}
36
37/// New read/write shared reference type-erased variable that has initial value derived from `source`.
38///
39/// This function is useful for creating custom mapping outputs, the new variable
40/// starts with the same [`AnyVar::last_update`] and animation handle as the `source`.
41pub fn any_var_derived(initial_value: BoxAnyVarValue, source: &AnyVar) -> AnyVar {
42    AnyVar(DynAnyVar::Shared(SharedVar::new(
43        initial_value,
44        source.0.last_update(),
45        source.0.modify_info(),
46    )))
47}
48
49/// Variable for state properties (`is_*`, `has_*`).
50///
51/// State variables are `bool` probes that are set by the property, they are created automatically
52/// by the property default when used in `when` expressions, but can be created manually.
53pub fn var_state() -> Var<bool> {
54    var(false)
55}
56
57/// Variable for getter properties (`get_*`, `actual_*`).
58///
59/// Getter variables are inited with a default value that is overridden by the property on node init and updated
60/// by the property when the internal state they track changes. They are created automatically by the property
61/// default when used in `when` expressions, but can be created manually.
62pub fn var_getter<T: VarValue + Default>() -> Var<T> {
63    var(T::default())
64}
65
66pub(super) struct VarData {
67    pub(super) value: RwLock<(BoxAnyVarValue, VarUpdateId, ModifyInfo)>,
68    hooks: MutexHooks,
69}
70
71#[derive(Clone)]
72pub(crate) struct SharedVar(pub(super) Arc<VarData>);
73impl fmt::Debug for SharedVar {
74    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75        let mut b = f.debug_struct("SharedVar");
76        b.field("var_instance_tag()", &Arc::as_ptr(&self.0));
77        b.field("strong_count()", &self.strong_count());
78
79        if let Some(value) = self.0.value.try_read() {
80            b.field("value", &value.0.detailed_debug());
81            b.field("last_update", &value.1);
82            b.field("modify_info", &value.2);
83        } else {
84            b.field("value", &"<locked>");
85        }
86
87        b.field("hooks", &self.0.hooks);
88
89        b.finish()
90    }
91}
92impl SharedVar {
93    pub(crate) fn new(value: BoxAnyVarValue, last_update: VarUpdateId, modify_info: ModifyInfo) -> Self {
94        Self(Arc::new(VarData {
95            value: RwLock::new((value, last_update, modify_info)),
96            hooks: MutexHooks::default(),
97        }))
98    }
99
100    pub(super) fn downgrade_typed(&self) -> WeakSharedVar {
101        WeakSharedVar(Arc::downgrade(&self.0))
102    }
103}
104impl PartialEq for SharedVar {
105    fn eq(&self, other: &Self) -> bool {
106        Arc::ptr_eq(&self.0, &other.0)
107    }
108}
109impl VarImpl for SharedVar {
110    fn clone_dyn(&self) -> DynAnyVar {
111        DynAnyVar::Shared(self.clone())
112    }
113
114    fn current_context(&self) -> DynAnyVar {
115        self.clone_dyn()
116    }
117
118    fn value_type(&self) -> TypeId {
119        self.0.value.read().0.type_id()
120    }
121
122    #[cfg(feature = "type_names")]
123    fn value_type_name(&self) -> &'static str {
124        let value = self.0.value.read();
125        value.0.type_name()
126    }
127
128    fn strong_count(&self) -> usize {
129        Arc::strong_count(&self.0)
130    }
131
132    fn var_eq(&self, other: &DynAnyVar) -> bool {
133        match other {
134            DynAnyVar::Shared(v) => self == v,
135            _ => false,
136        }
137    }
138
139    fn var_instance_tag(&self) -> VarInstanceTag {
140        VarInstanceTag(Arc::as_ptr(&self.0) as usize)
141    }
142
143    fn downgrade(&self) -> DynWeakAnyVar {
144        DynWeakAnyVar::Shared(self.downgrade_typed())
145    }
146
147    fn capabilities(&self) -> VarCapability {
148        VarCapability::NEW | VarCapability::MODIFY | VarCapability::SHARE
149    }
150
151    fn with(&self, visitor: &mut dyn FnMut(&dyn AnyVarValue)) {
152        let value = self.0.value.read();
153        visitor(&*value.0);
154    }
155
156    fn get(&self) -> BoxAnyVarValue {
157        self.0.value.read().0.clone_boxed()
158    }
159
160    fn set(&self, new_value: BoxAnyVarValue) -> bool {
161        self.modify_impl(ValueOrModify::Value(new_value));
162        true
163    }
164
165    fn update(&self) -> bool {
166        self.modify(smallbox!(|v: &mut AnyVarModify| {
167            v.update();
168        }))
169    }
170
171    fn modify(&self, modify: SmallBox<dyn FnMut(&mut AnyVarModify) + Send + 'static, smallbox::space::S4>) -> bool {
172        self.modify_impl(ValueOrModify::Modify(modify));
173        true
174    }
175
176    fn hook(&self, on_new: HookFn) -> VarHandle {
177        self.0.hooks.push(on_new)
178    }
179
180    fn last_update(&self) -> VarUpdateId {
181        self.0.value.read().1
182    }
183
184    fn modify_info(&self) -> ModifyInfo {
185        self.0.value.read().2.clone()
186    }
187
188    fn modify_importance(&self) -> usize {
189        self.0.value.read().2.importance()
190    }
191
192    fn is_animating(&self) -> bool {
193        self.0.value.read().2.is_animating()
194    }
195
196    fn hook_animation_stop(&self, handler: AnimationStopFn) -> VarHandle {
197        self.0.value.read().2.hook_animation_stop(handler)
198    }
199}
200impl SharedVar {
201    fn modify_impl(&self, value_or_modify: ValueOrModify) {
202        let name = value_type_name(self);
203        let var = self.clone();
204        // not weak ref here because some vars are spawned modified just to notify something and dropped
205        VARS.schedule_update(name, move || {
206            let mut value = var.0.value.write();
207
208            // verify if contextual animation can still set
209            let current_modify = VARS.current_modify();
210            if current_modify.importance() < value.2.importance() {
211                return;
212            }
213            value.2 = current_modify;
214
215            // modify
216            let mut m = AnyVarModify {
217                value: &mut value.0,
218                update: VarModifyUpdate::empty(),
219                tags: vec![],
220                custom_importance: None,
221            };
222            match value_or_modify {
223                ValueOrModify::Value(v) => {
224                    m.set(v);
225                }
226                ValueOrModify::Modify(mut f) => (f)(&mut m),
227            }
228
229            let AnyVarModify {
230                update,
231                tags,
232                custom_importance,
233                ..
234            } = m;
235
236            if let Some(i) = custom_importance {
237                value.2.importance = i;
238            }
239
240            if update.contains(VarModifyUpdate::UPDATE) {
241                value.1 = VARS.update_id();
242
243                let value = parking_lot::RwLockWriteGuard::downgrade(value);
244                let args = AnyVarHookArgs::new(
245                    var.var_instance_tag(),
246                    &*value.0,
247                    update.contains(VarModifyUpdate::REQUESTED),
248                    &tags,
249                );
250                var.0.hooks.notify(&args);
251            }
252        });
253    }
254}
255// both boxes are space::S4, so can't implement `set` as `modify` without alloc
256// this type and `modify_impl` work around that
257enum ValueOrModify {
258    Value(BoxAnyVarValue),
259    Modify(SmallBox<dyn FnMut(&mut AnyVarModify) + Send + 'static, smallbox::space::S4>),
260}
261
262#[derive(Clone)]
263pub(crate) struct WeakSharedVar(Weak<VarData>);
264impl fmt::Debug for WeakSharedVar {
265    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
266        f.debug_tuple("WeakSharedVar").field(&self.0.as_ptr()).finish()
267    }
268}
269impl WeakSharedVar {
270    pub(super) fn upgrade_typed(&self) -> Option<SharedVar> {
271        self.0.upgrade().map(SharedVar)
272    }
273}
274impl WeakVarImpl for WeakSharedVar {
275    fn clone_dyn(&self) -> DynWeakAnyVar {
276        DynWeakAnyVar::Shared(self.clone())
277    }
278
279    fn strong_count(&self) -> usize {
280        self.0.strong_count()
281    }
282
283    fn upgrade(&self) -> Option<DynAnyVar> {
284        Some(DynAnyVar::Shared(self.upgrade_typed()?))
285    }
286}
287
288#[derive(Default)]
289pub(super) struct MutexHooks {
290    h: Mutex<SmallVec<[(HookFn, VarHandlerOwner); 1]>>,
291}
292impl MutexHooks {
293    pub fn push(&self, on_new: HookFn) -> VarHandle {
294        let (owner, handle) = VarHandle::new();
295        self.h.lock().push((on_new, owner));
296        handle
297    }
298
299    pub fn notify(&self, args: &AnyVarHookArgs) {
300        let mut hooks = mem::take(&mut *self.h.lock());
301
302        hooks.retain(|(f, handle)| handle.is_alive() && f(args));
303
304        if !hooks.is_empty() {
305            let mut hs = self.h.lock();
306            if hs.capacity() > hooks.capacity() {
307                hs.append(&mut hooks);
308            } else {
309                hooks.append(&mut *hs);
310                *hs = hooks;
311            }
312        }
313    }
314}
315impl fmt::Debug for MutexHooks {
316    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
317        if let Some(h) = self.h.try_lock() {
318            let mut b = f.debug_list();
319            for (_, h) in h.iter() {
320                b.entry(h);
321            }
322            b.finish()
323        } else {
324            write!(f, "<locked>")
325        }
326    }
327}
328
329pub(super) type HookFn = SmallBox<dyn FnMut(&AnyVarHookArgs) -> bool + Send + 'static, smallbox::space::S4>;