1 | // Copyright © SixtyFPS GmbH <info@slint.dev> |
2 | // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 |
3 | |
4 | /*! |
5 | Property binding engine. |
6 | |
7 | The current implementation uses lots of heap allocation but that can be optimized later using |
8 | thin dst container, and intrusive linked list |
9 | */ |
10 | |
11 | // cSpell: ignore rustflags |
12 | |
13 | #![allow (unsafe_code)] |
14 | #![warn (missing_docs)] |
15 | |
16 | /// A singled linked list whose nodes are pinned |
17 | mod single_linked_list_pin { |
18 | #![allow (unsafe_code)] |
19 | use alloc::boxed::Box; |
20 | use core::pin::Pin; |
21 | |
22 | type NodePtr<T> = Option<Pin<Box<SingleLinkedListPinNode<T>>>>; |
23 | struct SingleLinkedListPinNode<T> { |
24 | next: NodePtr<T>, |
25 | value: T, |
26 | } |
27 | |
28 | pub struct SingleLinkedListPinHead<T>(NodePtr<T>); |
29 | impl<T> Default for SingleLinkedListPinHead<T> { |
30 | fn default() -> Self { |
31 | Self(None) |
32 | } |
33 | } |
34 | |
35 | impl<T> Drop for SingleLinkedListPinHead<T> { |
36 | fn drop(&mut self) { |
37 | // Use a loop instead of relying on the Drop of NodePtr to avoid recursion |
38 | while let Some(mut x) = core::mem::take(&mut self.0) { |
39 | // Safety: we don't touch the `x.value` which is the one protected by the Pin |
40 | self.0 = core::mem::take(unsafe { &mut Pin::get_unchecked_mut(x.as_mut()).next }); |
41 | } |
42 | } |
43 | } |
44 | |
45 | impl<T> SingleLinkedListPinHead<T> { |
46 | pub fn push_front(&mut self, value: T) -> Pin<&T> { |
47 | self.0 = Some(Box::pin(SingleLinkedListPinNode { next: self.0.take(), value })); |
48 | // Safety: we can project from SingleLinkedListPinNode |
49 | unsafe { Pin::new_unchecked(&self.0.as_ref().unwrap().value) } |
50 | } |
51 | |
52 | #[allow (unused)] |
53 | pub fn iter(&self) -> impl Iterator<Item = Pin<&T>> { |
54 | struct I<'a, T>(&'a NodePtr<T>); |
55 | |
56 | impl<'a, T> Iterator for I<'a, T> { |
57 | type Item = Pin<&'a T>; |
58 | fn next(&mut self) -> Option<Self::Item> { |
59 | if let Some(x) = &self.0 { |
60 | let r = unsafe { Pin::new_unchecked(&x.value) }; |
61 | self.0 = &x.next; |
62 | Some(r) |
63 | } else { |
64 | None |
65 | } |
66 | } |
67 | } |
68 | I(&self.0) |
69 | } |
70 | } |
71 | |
72 | #[test ] |
73 | fn test_list() { |
74 | let mut head = SingleLinkedListPinHead::default(); |
75 | head.push_front(1); |
76 | head.push_front(2); |
77 | head.push_front(3); |
78 | assert_eq!( |
79 | head.iter().map(|x: Pin<&i32>| *x.get_ref()).collect::<std::vec::Vec<i32>>(), |
80 | std::vec![3, 2, 1] |
81 | ); |
82 | } |
83 | #[test ] |
84 | fn big_list() { |
85 | // should not stack overflow |
86 | let mut head = SingleLinkedListPinHead::default(); |
87 | for x in 0..100000 { |
88 | head.push_front(x); |
89 | } |
90 | } |
91 | } |
92 | |
93 | pub(crate) mod dependency_tracker { |
94 | //! This module contains an implementation of a double linked list that can be used |
95 | //! to track dependency, such that when a node is dropped, the nodes are automatically |
96 | //! removed from the list. |
97 | //! This is unsafe to use for various reason, so it is kept internal. |
98 | |
99 | use core::cell::Cell; |
100 | use core::pin::Pin; |
101 | |
102 | #[repr (transparent)] |
103 | pub struct DependencyListHead<T>(Cell<*const DependencyNode<T>>); |
104 | |
105 | impl<T> Default for DependencyListHead<T> { |
106 | fn default() -> Self { |
107 | Self(Cell::new(core::ptr::null())) |
108 | } |
109 | } |
110 | impl<T> Drop for DependencyListHead<T> { |
111 | fn drop(&mut self) { |
112 | unsafe { DependencyListHead::drop(self as *mut Self) }; |
113 | } |
114 | } |
115 | |
116 | impl<T> DependencyListHead<T> { |
117 | pub unsafe fn mem_move(from: *mut Self, to: *mut Self) { |
118 | (*to).0.set((*from).0.get()); |
119 | if let Some(next) = (*from).0.get().as_ref() { |
120 | debug_assert_eq!(from as *const _, next.prev.get() as *const _); |
121 | next.debug_assert_valid(); |
122 | next.prev.set(to as *const _); |
123 | next.debug_assert_valid(); |
124 | } |
125 | } |
126 | |
127 | /// Swap two list head |
128 | pub fn swap(from: Pin<&Self>, to: Pin<&Self>) { |
129 | Cell::swap(&from.0, &to.0); |
130 | unsafe { |
131 | if let Some(n) = from.0.get().as_ref() { |
132 | debug_assert_eq!(n.prev.get() as *const _, &to.0 as *const _); |
133 | n.prev.set(&from.0 as *const _); |
134 | n.debug_assert_valid(); |
135 | } |
136 | |
137 | if let Some(n) = to.0.get().as_ref() { |
138 | debug_assert_eq!(n.prev.get() as *const _, &from.0 as *const _); |
139 | n.prev.set(&to.0 as *const _); |
140 | n.debug_assert_valid(); |
141 | } |
142 | } |
143 | } |
144 | |
145 | /// Return true is the list is empty |
146 | pub fn is_empty(&self) -> bool { |
147 | self.0.get().is_null() |
148 | } |
149 | |
150 | /// Remove all the nodes from the list; |
151 | pub fn clear(self: Pin<&Self>) { |
152 | unsafe { |
153 | if let Some(n) = self.0.get().as_ref() { |
154 | n.debug_assert_valid(); |
155 | n.prev.set(core::ptr::null()); |
156 | } |
157 | } |
158 | self.0.set(core::ptr::null()); |
159 | } |
160 | |
161 | pub unsafe fn drop(_self: *mut Self) { |
162 | if let Some(next) = (*_self).0.get().as_ref() { |
163 | debug_assert_eq!(_self as *const _, next.prev.get() as *const _); |
164 | next.debug_assert_valid(); |
165 | next.prev.set(core::ptr::null()); |
166 | next.debug_assert_valid(); |
167 | } |
168 | } |
169 | pub fn append(&self, node: Pin<&DependencyNode<T>>) { |
170 | unsafe { |
171 | node.remove(); |
172 | node.debug_assert_valid(); |
173 | let old = self.0.get(); |
174 | if let Some(x) = old.as_ref() { |
175 | x.debug_assert_valid(); |
176 | } |
177 | self.0.set(node.get_ref() as *const DependencyNode<_>); |
178 | node.next.set(old); |
179 | node.prev.set(&self.0 as *const _); |
180 | if let Some(old) = old.as_ref() { |
181 | old.prev.set((&node.next) as *const _); |
182 | old.debug_assert_valid(); |
183 | } |
184 | node.debug_assert_valid(); |
185 | } |
186 | } |
187 | |
188 | pub fn for_each(&self, mut f: impl FnMut(&T)) { |
189 | unsafe { |
190 | let mut next = self.0.get(); |
191 | while let Some(node) = next.as_ref() { |
192 | node.debug_assert_valid(); |
193 | next = node.next.get(); |
194 | f(&node.binding); |
195 | } |
196 | } |
197 | } |
198 | } |
199 | |
200 | /// The node is owned by the binding; so the binding is always valid |
201 | /// The next and pref |
202 | pub struct DependencyNode<T> { |
203 | next: Cell<*const DependencyNode<T>>, |
204 | /// This is either null, or a pointer to a pointer to ourself |
205 | prev: Cell<*const Cell<*const DependencyNode<T>>>, |
206 | binding: T, |
207 | } |
208 | |
209 | impl<T> DependencyNode<T> { |
210 | pub fn new(binding: T) -> Self { |
211 | Self { next: Cell::new(core::ptr::null()), prev: Cell::new(core::ptr::null()), binding } |
212 | } |
213 | |
214 | /// Assert that the invariant of `next` and `prev` are met. |
215 | pub fn debug_assert_valid(&self) { |
216 | unsafe { |
217 | debug_assert!( |
218 | self.prev.get().is_null() |
219 | || (*self.prev.get()).get() == self as *const DependencyNode<T> |
220 | ); |
221 | debug_assert!( |
222 | self.next.get().is_null() |
223 | || (*self.next.get()).prev.get() |
224 | == (&self.next) as *const Cell<*const DependencyNode<T>> |
225 | ); |
226 | // infinite loop? |
227 | debug_assert_ne!(self.next.get(), self as *const DependencyNode<T>); |
228 | debug_assert_ne!( |
229 | self.prev.get(), |
230 | (&self.next) as *const Cell<*const DependencyNode<T>> |
231 | ); |
232 | } |
233 | } |
234 | |
235 | pub fn remove(&self) { |
236 | self.debug_assert_valid(); |
237 | unsafe { |
238 | if let Some(prev) = self.prev.get().as_ref() { |
239 | prev.set(self.next.get()); |
240 | } |
241 | if let Some(next) = self.next.get().as_ref() { |
242 | next.debug_assert_valid(); |
243 | next.prev.set(self.prev.get()); |
244 | next.debug_assert_valid(); |
245 | } |
246 | } |
247 | self.prev.set(core::ptr::null()); |
248 | self.next.set(core::ptr::null()); |
249 | } |
250 | } |
251 | |
252 | impl<T> Drop for DependencyNode<T> { |
253 | fn drop(&mut self) { |
254 | self.remove(); |
255 | } |
256 | } |
257 | } |
258 | |
259 | type DependencyListHead = dependency_tracker::DependencyListHead<*const BindingHolder>; |
260 | type DependencyNode = dependency_tracker::DependencyNode<*const BindingHolder>; |
261 | |
262 | use alloc::boxed::Box; |
263 | use alloc::rc::Rc; |
264 | use core::cell::{Cell, RefCell, UnsafeCell}; |
265 | use core::marker::PhantomPinned; |
266 | use core::pin::Pin; |
267 | |
268 | /// if a DependencyListHead points to that value, it is because the property is actually |
269 | /// constant and cannot have dependencies |
270 | static CONSTANT_PROPERTY_SENTINEL: u32 = 0; |
271 | |
272 | /// The return value of a binding |
273 | #[derive (Copy, Clone, Debug, Eq, PartialEq)] |
274 | enum BindingResult { |
275 | /// The binding is a normal binding, and we keep it to re-evaluate it once it is dirty |
276 | KeepBinding, |
277 | /// The value of the property is now constant after the binding was evaluated, so |
278 | /// the binding can be removed. |
279 | RemoveBinding, |
280 | } |
281 | |
282 | struct BindingVTable { |
283 | drop: unsafe fn(_self: *mut BindingHolder), |
284 | evaluate: unsafe fn(_self: *mut BindingHolder, value: *mut ()) -> BindingResult, |
285 | mark_dirty: unsafe fn(_self: *const BindingHolder, was_dirty: bool), |
286 | intercept_set: unsafe fn(_self: *const BindingHolder, value: *const ()) -> bool, |
287 | intercept_set_binding: |
288 | unsafe fn(_self: *const BindingHolder, new_binding: *mut BindingHolder) -> bool, |
289 | } |
290 | |
291 | /// A binding trait object can be used to dynamically produces values for a property. |
292 | /// |
293 | /// # Safety |
294 | /// |
295 | /// IS_TWO_WAY_BINDING cannot be true if Self is not a TwoWayBinding |
296 | unsafe trait BindingCallable { |
297 | /// This function is called by the property to evaluate the binding and produce a new value. The |
298 | /// previous property value is provided in the value parameter. |
299 | unsafe fn evaluate(self: Pin<&Self>, value: *mut ()) -> BindingResult; |
300 | |
301 | /// This function is used to notify the binding that one of the dependencies was changed |
302 | /// and therefore this binding may evaluate to a different value, too. |
303 | fn mark_dirty(self: Pin<&Self>) {} |
304 | |
305 | /// Allow the binding to intercept what happens when the value is set. |
306 | /// The default implementation returns false, meaning the binding will simply be removed and |
307 | /// the property will get the new value. |
308 | /// When returning true, the call was intercepted and the binding will not be removed, |
309 | /// but the property will still have that value |
310 | unsafe fn intercept_set(self: Pin<&Self>, _value: *const ()) -> bool { |
311 | false |
312 | } |
313 | |
314 | /// Allow the binding to intercept what happens when the value is set. |
315 | /// The default implementation returns false, meaning the binding will simply be removed. |
316 | /// When returning true, the call was intercepted and the binding will not be removed. |
317 | unsafe fn intercept_set_binding(self: Pin<&Self>, _new_binding: *mut BindingHolder) -> bool { |
318 | false |
319 | } |
320 | |
321 | /// Set to true if and only if Self is a TwoWayBinding<T> |
322 | const IS_TWO_WAY_BINDING: bool = false; |
323 | } |
324 | |
325 | unsafe impl<F: Fn(*mut ()) -> BindingResult> BindingCallable for F { |
326 | unsafe fn evaluate(self: Pin<&Self>, value: *mut ()) -> BindingResult { |
327 | self(value) |
328 | } |
329 | } |
330 | |
331 | #[cfg (feature = "std" )] |
332 | use std::thread_local; |
333 | #[cfg (feature = "std" )] |
334 | scoped_tls_hkt::scoped_thread_local!(static CURRENT_BINDING : for<'a> Option<Pin<&'a BindingHolder>>); |
335 | |
336 | #[cfg (all(not(feature = "std" ), feature = "unsafe-single-threaded" ))] |
337 | mod unsafe_single_threaded { |
338 | use super::BindingHolder; |
339 | use core::cell::Cell; |
340 | use core::pin::Pin; |
341 | use core::ptr::null; |
342 | pub(super) struct FakeThreadStorage(Cell<*const BindingHolder>); |
343 | impl FakeThreadStorage { |
344 | pub const fn new() -> Self { |
345 | Self(Cell::new(null())) |
346 | } |
347 | pub fn set<T>(&self, value: Option<Pin<&BindingHolder>>, f: impl FnOnce() -> T) -> T { |
348 | let old = self.0.replace(value.map_or(null(), |v| v.get_ref() as *const BindingHolder)); |
349 | let res = f(); |
350 | let new = self.0.replace(old); |
351 | assert_eq!(new, value.map_or(null(), |v| v.get_ref() as *const BindingHolder)); |
352 | res |
353 | } |
354 | pub fn is_set(&self) -> bool { |
355 | !self.0.get().is_null() |
356 | } |
357 | pub fn with<T>(&self, f: impl FnOnce(Option<Pin<&BindingHolder>>) -> T) -> T { |
358 | let local = unsafe { self.0.get().as_ref().map(|x| Pin::new_unchecked(x)) }; |
359 | let res = f(local); |
360 | assert_eq!(self.0.get(), local.map_or(null(), |v| v.get_ref() as *const BindingHolder)); |
361 | res |
362 | } |
363 | } |
364 | // Safety: the unsafe_single_threaded feature means we will only be called from a single thread |
365 | unsafe impl Send for FakeThreadStorage {} |
366 | unsafe impl Sync for FakeThreadStorage {} |
367 | } |
368 | #[cfg (all(not(feature = "std" ), feature = "unsafe-single-threaded" ))] |
369 | static CURRENT_BINDING: unsafe_single_threaded::FakeThreadStorage = |
370 | unsafe_single_threaded::FakeThreadStorage::new(); |
371 | |
372 | /// Evaluate a function, but do not register any property dependencies if that function |
373 | /// get the value of properties |
374 | pub fn evaluate_no_tracking<T>(f: impl FnOnce() -> T) -> T { |
375 | CURRENT_BINDING.set(t:None, f) |
376 | } |
377 | |
378 | /// Return true if there is currently a binding being evaluated so that access to |
379 | /// properties register dependencies to that binding. |
380 | pub fn is_currently_tracking() -> bool { |
381 | CURRENT_BINDING.is_set() && CURRENT_BINDING.with(|x: Option>| x.is_some()) |
382 | } |
383 | |
384 | /// This structure erase the `B` type with a vtable. |
385 | #[repr (C)] |
386 | struct BindingHolder<B = ()> { |
387 | /// Access to the list of binding which depends on this binding |
388 | dependencies: Cell<usize>, |
389 | /// The binding own the nodes used in the dependencies lists of the properties |
390 | /// From which we depend. |
391 | dep_nodes: Cell<single_linked_list_pin::SingleLinkedListPinHead<DependencyNode>>, |
392 | vtable: &'static BindingVTable, |
393 | /// The binding is dirty and need to be re_evaluated |
394 | dirty: Cell<bool>, |
395 | /// Specify that B is a `TwoWayBinding<T>` |
396 | is_two_way_binding: bool, |
397 | pinned: PhantomPinned, |
398 | #[cfg (slint_debug_property)] |
399 | pub debug_name: String, |
400 | |
401 | binding: B, |
402 | } |
403 | |
404 | impl BindingHolder { |
405 | fn register_self_as_dependency( |
406 | self: Pin<&Self>, |
407 | property_that_will_notify: *mut DependencyListHead, |
408 | #[cfg (slint_debug_property)] other_debug_name: &str, |
409 | ) { |
410 | let node: DependencyNode<*const BindingHolder> = DependencyNode::new(self.get_ref() as *const _); |
411 | let mut dep_nodes: SingleLinkedListPinHead> = self.dep_nodes.take(); |
412 | let node: Pin<&DependencyNode<*const …>> = dep_nodes.push_front(node); |
413 | unsafe { DependencyListHead::append(&*property_that_will_notify, node) } |
414 | self.dep_nodes.set(val:dep_nodes); |
415 | } |
416 | } |
417 | |
418 | fn alloc_binding_holder<B: BindingCallable + 'static>(binding: B) -> *mut BindingHolder { |
419 | /// Safety: _self must be a pointer that comes from a `Box<BindingHolder<B>>::into_raw()` |
420 | unsafe fn binding_drop<B>(_self: *mut BindingHolder) { |
421 | drop(Box::from_raw(_self as *mut BindingHolder<B>)); |
422 | } |
423 | |
424 | /// Safety: _self must be a pointer to a `BindingHolder<B>` |
425 | /// and value must be a pointer to T |
426 | unsafe fn evaluate<B: BindingCallable>( |
427 | _self: *mut BindingHolder, |
428 | value: *mut (), |
429 | ) -> BindingResult { |
430 | let pinned_holder = Pin::new_unchecked(&*_self); |
431 | CURRENT_BINDING.set(Some(pinned_holder), || { |
432 | Pin::new_unchecked(&((*(_self as *mut BindingHolder<B>)).binding)).evaluate(value) |
433 | }) |
434 | } |
435 | |
436 | /// Safety: _self must be a pointer to a `BindingHolder<B>` |
437 | unsafe fn mark_dirty<B: BindingCallable>(_self: *const BindingHolder, _: bool) { |
438 | Pin::new_unchecked(&((*(_self as *const BindingHolder<B>)).binding)).mark_dirty() |
439 | } |
440 | |
441 | /// Safety: _self must be a pointer to a `BindingHolder<B>` |
442 | unsafe fn intercept_set<B: BindingCallable>( |
443 | _self: *const BindingHolder, |
444 | value: *const (), |
445 | ) -> bool { |
446 | Pin::new_unchecked(&((*(_self as *const BindingHolder<B>)).binding)).intercept_set(value) |
447 | } |
448 | |
449 | unsafe fn intercept_set_binding<B: BindingCallable>( |
450 | _self: *const BindingHolder, |
451 | new_binding: *mut BindingHolder, |
452 | ) -> bool { |
453 | Pin::new_unchecked(&((*(_self as *const BindingHolder<B>)).binding)) |
454 | .intercept_set_binding(new_binding) |
455 | } |
456 | |
457 | trait HasBindingVTable { |
458 | const VT: &'static BindingVTable; |
459 | } |
460 | impl<B: BindingCallable> HasBindingVTable for B { |
461 | const VT: &'static BindingVTable = &BindingVTable { |
462 | drop: binding_drop::<B>, |
463 | evaluate: evaluate::<B>, |
464 | mark_dirty: mark_dirty::<B>, |
465 | intercept_set: intercept_set::<B>, |
466 | intercept_set_binding: intercept_set_binding::<B>, |
467 | }; |
468 | } |
469 | |
470 | let holder: BindingHolder<B> = BindingHolder { |
471 | dependencies: Cell::new(0), |
472 | dep_nodes: Default::default(), |
473 | vtable: <B as HasBindingVTable>::VT, |
474 | dirty: Cell::new(true), // starts dirty so it evaluates the property when used |
475 | is_two_way_binding: B::IS_TWO_WAY_BINDING, |
476 | pinned: PhantomPinned, |
477 | #[cfg (slint_debug_property)] |
478 | debug_name: Default::default(), |
479 | binding, |
480 | }; |
481 | Box::into_raw(Box::new(holder)) as *mut BindingHolder |
482 | } |
483 | |
484 | #[repr (transparent)] |
485 | #[derive (Default)] |
486 | struct PropertyHandle { |
487 | /// The handle can either be a pointer to a binding, or a pointer to the list of dependent properties. |
488 | /// The two least significant bit of the pointer are flags, as the pointer will be aligned. |
489 | /// The least significant bit (`0b01`) tells that the binding is borrowed. So no two reference to the |
490 | /// binding exist at the same time. |
491 | /// The second to last bit (`0b10`) tells that the pointer points to a binding. Otherwise, it is the head |
492 | /// node of the linked list of dependent binding |
493 | handle: Cell<usize>, |
494 | } |
495 | |
496 | impl core::fmt::Debug for PropertyHandle { |
497 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
498 | let handle: usize = self.handle.get(); |
499 | write!( |
500 | f, |
501 | "PropertyHandle {{ handle: 0x {:x}, locked: {}, binding: {} }}" , |
502 | handle & !0b11, |
503 | (handle & 0b01) == 0b01, |
504 | (handle & 0b10) == 0b10 |
505 | ) |
506 | } |
507 | } |
508 | |
509 | impl PropertyHandle { |
510 | /// The lock flag specify that we can get reference to the Cell or unsafe cell |
511 | fn lock_flag(&self) -> bool { |
512 | self.handle.get() & 0b1 == 1 |
513 | } |
514 | /// Sets the lock_flag. |
515 | /// Safety: the lock flag must not be unset if there exist reference to what's inside the cell |
516 | unsafe fn set_lock_flag(&self, set: bool) { |
517 | self.handle.set(if set { self.handle.get() | 0b1 } else { self.handle.get() & !0b1 }) |
518 | } |
519 | |
520 | /// Access the value. |
521 | /// Panics if the function try to recursively access the value |
522 | fn access<R>(&self, f: impl FnOnce(Option<Pin<&mut BindingHolder>>) -> R) -> R { |
523 | assert!(!self.lock_flag(), "Recursion detected" ); |
524 | unsafe { |
525 | self.set_lock_flag(true); |
526 | scopeguard::defer! { self.set_lock_flag(false); } |
527 | let handle = self.handle.get(); |
528 | let binding = if handle & 0b10 == 0b10 { |
529 | Some(Pin::new_unchecked(&mut *((handle & !0b11) as *mut BindingHolder))) |
530 | } else { |
531 | None |
532 | }; |
533 | f(binding) |
534 | } |
535 | } |
536 | |
537 | fn remove_binding(&self) { |
538 | assert!(!self.lock_flag(), "Recursion detected" ); |
539 | let val = self.handle.get(); |
540 | if val & 0b10 == 0b10 { |
541 | unsafe { |
542 | self.set_lock_flag(true); |
543 | let binding = (val & !0b11) as *mut BindingHolder; |
544 | let const_sentinel = (&CONSTANT_PROPERTY_SENTINEL) as *const u32 as usize; |
545 | if (*binding).dependencies.get() == const_sentinel { |
546 | self.handle.set(const_sentinel); |
547 | (*binding).dependencies.set(0); |
548 | } else { |
549 | DependencyListHead::mem_move( |
550 | (*binding).dependencies.as_ptr() as *mut DependencyListHead, |
551 | self.handle.as_ptr() as *mut DependencyListHead, |
552 | ); |
553 | } |
554 | ((*binding).vtable.drop)(binding); |
555 | } |
556 | debug_assert!(self.handle.get() & 0b11 == 0); |
557 | } |
558 | } |
559 | |
560 | /// Safety: the BindingCallable must be valid for the type of this property |
561 | unsafe fn set_binding<B: BindingCallable + 'static>( |
562 | &self, |
563 | binding: B, |
564 | #[cfg (slint_debug_property)] debug_name: &str, |
565 | ) { |
566 | let binding = alloc_binding_holder::<B>(binding); |
567 | #[cfg (slint_debug_property)] |
568 | { |
569 | (*binding).debug_name = debug_name.into(); |
570 | } |
571 | self.set_binding_impl(binding); |
572 | } |
573 | |
574 | /// Implementation of Self::set_binding. |
575 | fn set_binding_impl(&self, binding: *mut BindingHolder) { |
576 | let previous_binding_intercepted = self.access(|b| { |
577 | b.is_some_and(|b| unsafe { |
578 | // Safety: b is a BindingHolder<T> |
579 | (b.vtable.intercept_set_binding)(&*b as *const BindingHolder, binding) |
580 | }) |
581 | }); |
582 | |
583 | if previous_binding_intercepted { |
584 | return; |
585 | } |
586 | |
587 | self.remove_binding(); |
588 | debug_assert!((binding as usize) & 0b11 == 0); |
589 | debug_assert!(self.handle.get() & 0b11 == 0); |
590 | let const_sentinel = (&CONSTANT_PROPERTY_SENTINEL) as *const u32 as usize; |
591 | let is_constant = self.handle.get() == const_sentinel; |
592 | unsafe { |
593 | if is_constant { |
594 | (*binding).dependencies.set(const_sentinel); |
595 | } else { |
596 | DependencyListHead::mem_move( |
597 | self.handle.as_ptr() as *mut DependencyListHead, |
598 | (*binding).dependencies.as_ptr() as *mut DependencyListHead, |
599 | ); |
600 | } |
601 | } |
602 | self.handle.set((binding as usize) | 0b10); |
603 | if !is_constant { |
604 | self.mark_dirty( |
605 | #[cfg (slint_debug_property)] |
606 | "" , |
607 | ); |
608 | } |
609 | } |
610 | |
611 | fn dependencies(&self) -> *mut DependencyListHead { |
612 | assert!(!self.lock_flag(), "Recursion detected" ); |
613 | if (self.handle.get() & 0b10) != 0 { |
614 | self.access(|binding| binding.unwrap().dependencies.as_ptr() as *mut DependencyListHead) |
615 | } else { |
616 | self.handle.as_ptr() as *mut DependencyListHead |
617 | } |
618 | } |
619 | |
620 | // `value` is the content of the unsafe cell and will be only dereferenced if the |
621 | // handle is not locked. (Upholding the requirements of UnsafeCell) |
622 | unsafe fn update<T>(&self, value: *mut T) { |
623 | let remove = self.access(|binding| { |
624 | if let Some(mut binding) = binding { |
625 | if binding.dirty.get() { |
626 | // clear all the nodes so that we can start from scratch |
627 | binding.dep_nodes.set(Default::default()); |
628 | let r = (binding.vtable.evaluate)( |
629 | binding.as_mut().get_unchecked_mut() as *mut BindingHolder, |
630 | value as *mut (), |
631 | ); |
632 | binding.dirty.set(false); |
633 | if r == BindingResult::RemoveBinding { |
634 | return true; |
635 | } |
636 | } |
637 | } |
638 | false |
639 | }); |
640 | if remove { |
641 | self.remove_binding() |
642 | } |
643 | } |
644 | |
645 | /// Register this property as a dependency to the current binding being evaluated |
646 | fn register_as_dependency_to_current_binding( |
647 | self: Pin<&Self>, |
648 | #[cfg (slint_debug_property)] debug_name: &str, |
649 | ) { |
650 | if CURRENT_BINDING.is_set() { |
651 | CURRENT_BINDING.with(|cur_binding| { |
652 | if let Some(cur_binding) = cur_binding { |
653 | let dependencies = self.dependencies(); |
654 | if !core::ptr::eq( |
655 | unsafe { *(dependencies as *mut *const u32) }, |
656 | (&CONSTANT_PROPERTY_SENTINEL) as *const u32, |
657 | ) { |
658 | cur_binding.register_self_as_dependency( |
659 | dependencies, |
660 | #[cfg (slint_debug_property)] |
661 | debug_name, |
662 | ); |
663 | } |
664 | } |
665 | }); |
666 | } |
667 | } |
668 | |
669 | fn mark_dirty(&self, #[cfg (slint_debug_property)] debug_name: &str) { |
670 | #[cfg (not(slint_debug_property))] |
671 | let debug_name = "" ; |
672 | unsafe { |
673 | let dependencies = self.dependencies(); |
674 | assert!( |
675 | !core::ptr::eq( |
676 | *(dependencies as *mut *const u32), |
677 | (&CONSTANT_PROPERTY_SENTINEL) as *const u32, |
678 | ), |
679 | "Constant property being changed {debug_name}" |
680 | ); |
681 | mark_dependencies_dirty(dependencies) |
682 | }; |
683 | } |
684 | |
685 | fn set_constant(&self) { |
686 | unsafe { |
687 | let dependencies = self.dependencies(); |
688 | if !core::ptr::eq( |
689 | *(dependencies as *mut *const u32), |
690 | (&CONSTANT_PROPERTY_SENTINEL) as *const u32, |
691 | ) { |
692 | DependencyListHead::drop(dependencies); |
693 | *(dependencies as *mut *const u32) = (&CONSTANT_PROPERTY_SENTINEL) as *const u32 |
694 | } |
695 | } |
696 | } |
697 | } |
698 | |
699 | impl Drop for PropertyHandle { |
700 | fn drop(&mut self) { |
701 | self.remove_binding(); |
702 | debug_assert!(self.handle.get() & 0b11 == 0); |
703 | if self.handle.get() as *const u32 != (&CONSTANT_PROPERTY_SENTINEL) as *const u32 { |
704 | unsafe { |
705 | DependencyListHead::drop(self.handle.as_ptr() as *mut _); |
706 | } |
707 | } |
708 | } |
709 | } |
710 | |
711 | /// Safety: the dependency list must be valid and consistent |
712 | unsafe fn mark_dependencies_dirty(dependencies: *mut DependencyListHead) { |
713 | debug_assert!(!core::ptr::eq( |
714 | *(dependencies as *mut *const u32), |
715 | (&CONSTANT_PROPERTY_SENTINEL) as *const u32, |
716 | )); |
717 | DependencyListHead::for_each(&*dependencies, |binding: &*const BindingHolder| { |
718 | let binding: &BindingHolder = &**binding; |
719 | let was_dirty: bool = binding.dirty.replace(val:true); |
720 | (binding.vtable.mark_dirty)(binding as *const BindingHolder, was_dirty); |
721 | |
722 | assert!( |
723 | !core::ptr::eq( |
724 | *(binding.dependencies.as_ptr() as *mut *const u32), |
725 | (&CONSTANT_PROPERTY_SENTINEL) as *const u32, |
726 | ), |
727 | "Const property marked as dirty" |
728 | ); |
729 | |
730 | if !was_dirty { |
731 | mark_dependencies_dirty(dependencies:binding.dependencies.as_ptr() as *mut DependencyListHead) |
732 | } |
733 | }); |
734 | } |
735 | |
736 | /// Types that can be set as bindings for a `Property<T>` |
737 | pub trait Binding<T> { |
738 | /// Evaluate the binding and return the new value |
739 | fn evaluate(&self, old_value: &T) -> T; |
740 | } |
741 | |
742 | impl<T, F: Fn() -> T> Binding<T> for F { |
743 | fn evaluate(&self, _value: &T) -> T { |
744 | self() |
745 | } |
746 | } |
747 | |
748 | /// A Property that allow binding that track changes |
749 | /// |
750 | /// Property can have an assigned value, or binding. |
751 | /// When a binding is assigned, it is lazily evaluated on demand |
752 | /// when calling `get()`. |
753 | /// When accessing another property from a binding evaluation, |
754 | /// a dependency will be registered, such that when the property |
755 | /// change, the binding will automatically be updated |
756 | #[repr (C)] |
757 | pub struct Property<T> { |
758 | /// This is usually a pointer, but the least significant bit tells what it is |
759 | handle: PropertyHandle, |
760 | /// This is only safe to access when the lock flag is not set on the handle. |
761 | value: UnsafeCell<T>, |
762 | pinned: PhantomPinned, |
763 | /// Enabled only if compiled with `RUSTFLAGS='--cfg slint_debug_property'` |
764 | /// Note that adding this flag will also tell the rust compiler to set this |
765 | /// and that this will not work with C++ because of binary incompatibility |
766 | #[cfg (slint_debug_property)] |
767 | pub debug_name: RefCell<String>, |
768 | } |
769 | |
770 | impl<T: core::fmt::Debug + Clone> core::fmt::Debug for Property<T> { |
771 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
772 | #[cfg (slint_debug_property)] |
773 | write!(f, "[{}]=" , self.debug_name.borrow())?; |
774 | write!( |
775 | f, |
776 | "Property( {:?}{})" , |
777 | self.get_internal(), |
778 | if self.is_dirty() { " (dirty)" } else { "" } |
779 | ) |
780 | } |
781 | } |
782 | |
783 | impl<T: Default> Default for Property<T> { |
784 | fn default() -> Self { |
785 | Self { |
786 | handle: Default::default(), |
787 | value: Default::default(), |
788 | pinned: PhantomPinned, |
789 | #[cfg (slint_debug_property)] |
790 | debug_name: Default::default(), |
791 | } |
792 | } |
793 | } |
794 | |
795 | impl<T: Clone> Property<T> { |
796 | /// Create a new property with this value |
797 | pub fn new(value: T) -> Self { |
798 | Self { |
799 | handle: Default::default(), |
800 | value: UnsafeCell::new(value), |
801 | pinned: PhantomPinned, |
802 | #[cfg (slint_debug_property)] |
803 | debug_name: Default::default(), |
804 | } |
805 | } |
806 | |
807 | /// Same as [`Self::new`] but with a 'static string use for debugging only |
808 | pub fn new_named(value: T, _name: &'static str) -> Self { |
809 | Self { |
810 | handle: Default::default(), |
811 | value: UnsafeCell::new(value), |
812 | pinned: PhantomPinned, |
813 | #[cfg (slint_debug_property)] |
814 | debug_name: _name.to_owned().into(), |
815 | } |
816 | } |
817 | |
818 | /// Get the value of the property |
819 | /// |
820 | /// This may evaluate the binding if there is a binding and it is dirty |
821 | /// |
822 | /// If the function is called directly or indirectly from a binding evaluation |
823 | /// of another Property, a dependency will be registered. |
824 | /// |
825 | /// Panics if this property is get while evaluating its own binding or |
826 | /// cloning the value. |
827 | pub fn get(self: Pin<&Self>) -> T { |
828 | unsafe { self.handle.update(self.value.get()) }; |
829 | let handle = unsafe { Pin::new_unchecked(&self.handle) }; |
830 | handle.register_as_dependency_to_current_binding( |
831 | #[cfg (slint_debug_property)] |
832 | self.debug_name.borrow().as_str(), |
833 | ); |
834 | self.get_internal() |
835 | } |
836 | |
837 | /// Same as get() but without registering a dependency |
838 | /// |
839 | /// This allow to optimize bindings that know that they might not need to |
840 | /// re_evaluate themselves when the property change or that have registered |
841 | /// the dependency in another way. |
842 | /// |
843 | /// ## Example |
844 | /// ``` |
845 | /// use std::rc::Rc; |
846 | /// use i_slint_core::Property; |
847 | /// let prop1 = Rc::pin(Property::new(100)); |
848 | /// let prop2 = Rc::pin(Property::<i32>::default()); |
849 | /// prop2.as_ref().set_binding({ |
850 | /// let prop1 = prop1.clone(); // in order to move it into the closure. |
851 | /// move || { prop1.as_ref().get_untracked() + 30 } |
852 | /// }); |
853 | /// assert_eq!(prop2.as_ref().get(), 130); |
854 | /// prop1.set(200); |
855 | /// // changing prop1 do not affect the prop2 binding because no dependency was registered |
856 | /// assert_eq!(prop2.as_ref().get(), 130); |
857 | /// ``` |
858 | pub fn get_untracked(self: Pin<&Self>) -> T { |
859 | unsafe { self.handle.update(self.value.get()) }; |
860 | self.get_internal() |
861 | } |
862 | |
863 | /// Get the cached value without registering any dependencies or executing any binding |
864 | pub fn get_internal(&self) -> T { |
865 | self.handle.access(|_| { |
866 | // Safety: PropertyHandle::access ensure that the value is locked |
867 | unsafe { (*self.value.get()).clone() } |
868 | }) |
869 | } |
870 | |
871 | /// Change the value of this property |
872 | /// |
873 | /// If other properties have binding depending of this property, these properties will |
874 | /// be marked as dirty. |
875 | // FIXME pub fn set(self: Pin<&Self>, t: T) { |
876 | pub fn set(&self, t: T) |
877 | where |
878 | T: PartialEq, |
879 | { |
880 | let previous_binding_intercepted = self.handle.access(|b| { |
881 | b.is_some_and(|b| unsafe { |
882 | // Safety: b is a BindingHolder<T> |
883 | (b.vtable.intercept_set)(&*b as *const BindingHolder, &t as *const T as *const ()) |
884 | }) |
885 | }); |
886 | if !previous_binding_intercepted { |
887 | self.handle.remove_binding(); |
888 | } |
889 | |
890 | // Safety: PropertyHandle::access ensure that the value is locked |
891 | let has_value_changed = self.handle.access(|_| unsafe { |
892 | *self.value.get() != t && { |
893 | *self.value.get() = t; |
894 | true |
895 | } |
896 | }); |
897 | if has_value_changed { |
898 | self.handle.mark_dirty( |
899 | #[cfg (slint_debug_property)] |
900 | self.debug_name.borrow().as_str(), |
901 | ); |
902 | } |
903 | } |
904 | |
905 | /// Set a binding to this property. |
906 | /// |
907 | /// Bindings are evaluated lazily from calling get, and the return value of the binding |
908 | /// is the new value. |
909 | /// |
910 | /// If other properties have bindings depending of this property, these properties will |
911 | /// be marked as dirty. |
912 | /// |
913 | /// Closures of type `Fn()->T` implements `Binding<T>` and can be used as a binding |
914 | /// |
915 | /// ## Example |
916 | /// ``` |
917 | /// use std::rc::Rc; |
918 | /// use i_slint_core::Property; |
919 | /// let prop1 = Rc::pin(Property::new(100)); |
920 | /// let prop2 = Rc::pin(Property::<i32>::default()); |
921 | /// prop2.as_ref().set_binding({ |
922 | /// let prop1 = prop1.clone(); // in order to move it into the closure. |
923 | /// move || { prop1.as_ref().get() + 30 } |
924 | /// }); |
925 | /// assert_eq!(prop2.as_ref().get(), 130); |
926 | /// prop1.set(200); |
927 | /// // A change in prop1 forced the binding on prop2 to re_evaluate |
928 | /// assert_eq!(prop2.as_ref().get(), 230); |
929 | /// ``` |
930 | //FIXME pub fn set_binding(self: Pin<&Self>, f: impl Binding<T> + 'static) { |
931 | pub fn set_binding(&self, binding: impl Binding<T> + 'static) { |
932 | // Safety: This will make a binding callable for the type T |
933 | unsafe { |
934 | self.handle.set_binding( |
935 | move |val: *mut ()| { |
936 | let val = &mut *(val as *mut T); |
937 | *val = binding.evaluate(val); |
938 | BindingResult::KeepBinding |
939 | }, |
940 | #[cfg (slint_debug_property)] |
941 | self.debug_name.borrow().as_str(), |
942 | ) |
943 | } |
944 | self.handle.mark_dirty( |
945 | #[cfg (slint_debug_property)] |
946 | self.debug_name.borrow().as_str(), |
947 | ); |
948 | } |
949 | |
950 | /// Any of the properties accessed during the last evaluation of the closure called |
951 | /// from the last call to evaluate is potentially dirty. |
952 | pub fn is_dirty(&self) -> bool { |
953 | self.handle.access(|binding| binding.is_some_and(|b| b.dirty.get())) |
954 | } |
955 | |
956 | /// Internal function to mark the property as dirty and notify dependencies, regardless of |
957 | /// whether the property value has actually changed or not. |
958 | pub fn mark_dirty(&self) { |
959 | self.handle.mark_dirty( |
960 | #[cfg (slint_debug_property)] |
961 | self.debug_name.borrow().as_str(), |
962 | ) |
963 | } |
964 | |
965 | /// Mark that this property will never be modified again and that no tracking should be done |
966 | pub fn set_constant(&self) { |
967 | self.handle.set_constant(); |
968 | } |
969 | } |
970 | |
971 | #[test ] |
972 | fn properties_simple_test() { |
973 | use pin_weak::rc::PinWeak; |
974 | use std::rc::Rc; |
975 | fn g(prop: &Property<i32>) -> i32 { |
976 | unsafe { Pin::new_unchecked(prop).get() } |
977 | } |
978 | |
979 | #[derive (Default)] |
980 | struct Component { |
981 | width: Property<i32>, |
982 | height: Property<i32>, |
983 | area: Property<i32>, |
984 | } |
985 | |
986 | let compo = Rc::pin(Component::default()); |
987 | let w = PinWeak::downgrade(compo.clone()); |
988 | compo.area.set_binding(move || { |
989 | let compo = w.upgrade().unwrap(); |
990 | g(&compo.width) * g(&compo.height) |
991 | }); |
992 | compo.width.set(4); |
993 | compo.height.set(8); |
994 | assert_eq!(g(&compo.width), 4); |
995 | assert_eq!(g(&compo.height), 8); |
996 | assert_eq!(g(&compo.area), 4 * 8); |
997 | |
998 | let w = PinWeak::downgrade(compo.clone()); |
999 | compo.width.set_binding(move || { |
1000 | let compo = w.upgrade().unwrap(); |
1001 | g(&compo.height) * 2 |
1002 | }); |
1003 | assert_eq!(g(&compo.width), 8 * 2); |
1004 | assert_eq!(g(&compo.height), 8); |
1005 | assert_eq!(g(&compo.area), 8 * 8 * 2); |
1006 | } |
1007 | |
1008 | impl<T: PartialEq + Clone + 'static> Property<T> { |
1009 | /// Link two property such that any change to one property is affecting the other property as if they |
1010 | /// where, in fact, a single property. |
1011 | /// The value or binding of prop2 is kept. |
1012 | pub fn link_two_way(prop1: Pin<&Self>, prop2: Pin<&Self>) { |
1013 | struct TwoWayBinding<T> { |
1014 | common_property: Pin<Rc<Property<T>>>, |
1015 | } |
1016 | unsafe impl<T: PartialEq + Clone + 'static> BindingCallable for TwoWayBinding<T> { |
1017 | unsafe fn evaluate(self: Pin<&Self>, value: *mut ()) -> BindingResult { |
1018 | *(value as *mut T) = self.common_property.as_ref().get(); |
1019 | BindingResult::KeepBinding |
1020 | } |
1021 | |
1022 | unsafe fn intercept_set(self: Pin<&Self>, value: *const ()) -> bool { |
1023 | self.common_property.as_ref().set((*(value as *const T)).clone()); |
1024 | true |
1025 | } |
1026 | |
1027 | unsafe fn intercept_set_binding( |
1028 | self: Pin<&Self>, |
1029 | new_binding: *mut BindingHolder, |
1030 | ) -> bool { |
1031 | self.common_property.handle.set_binding_impl(new_binding); |
1032 | true |
1033 | } |
1034 | |
1035 | const IS_TWO_WAY_BINDING: bool = true; |
1036 | } |
1037 | |
1038 | #[cfg (slint_debug_property)] |
1039 | let debug_name = format!("<{}<=>{}>" , prop1.debug_name.borrow(), prop2.debug_name.borrow()); |
1040 | |
1041 | let value = prop2.get_internal(); |
1042 | |
1043 | let prop1_handle_val = prop1.handle.handle.get(); |
1044 | if prop1_handle_val & 0b10 == 0b10 { |
1045 | // Safety: the handle is a pointer to a binding |
1046 | let holder = unsafe { &*((prop1_handle_val & !0b11) as *const BindingHolder) }; |
1047 | if holder.is_two_way_binding { |
1048 | unsafe { |
1049 | // Safety: the handle is a pointer to a binding whose B is a TwoWayBinding<T> |
1050 | let holder = |
1051 | &*((prop1_handle_val & !0b11) as *const BindingHolder<TwoWayBinding<T>>); |
1052 | // Safety: TwoWayBinding's T is the same as the type for both properties |
1053 | prop2.handle.set_binding( |
1054 | TwoWayBinding { common_property: holder.binding.common_property.clone() }, |
1055 | #[cfg (slint_debug_property)] |
1056 | debug_name.as_str(), |
1057 | ); |
1058 | } |
1059 | prop2.set(value); |
1060 | return; |
1061 | } |
1062 | }; |
1063 | |
1064 | let prop2_handle_val = prop2.handle.handle.get(); |
1065 | let handle = if prop2_handle_val & 0b10 == 0b10 { |
1066 | // Safety: the handle is a pointer to a binding |
1067 | let holder = unsafe { &*((prop2_handle_val & !0b11) as *const BindingHolder) }; |
1068 | if holder.is_two_way_binding { |
1069 | unsafe { |
1070 | // Safety: the handle is a pointer to a binding whose B is a TwoWayBinding<T> |
1071 | let holder = |
1072 | &*((prop2_handle_val & !0b11) as *const BindingHolder<TwoWayBinding<T>>); |
1073 | // Safety: TwoWayBinding's T is the same as the type for both properties |
1074 | prop1.handle.set_binding( |
1075 | TwoWayBinding { common_property: holder.binding.common_property.clone() }, |
1076 | #[cfg (slint_debug_property)] |
1077 | debug_name.as_str(), |
1078 | ); |
1079 | } |
1080 | return; |
1081 | } |
1082 | // If prop2 is a binding, just "steal it" |
1083 | prop2.handle.handle.set(0); |
1084 | PropertyHandle { handle: Cell::new(prop2_handle_val) } |
1085 | } else { |
1086 | PropertyHandle::default() |
1087 | }; |
1088 | |
1089 | let common_property = Rc::pin(Property { |
1090 | handle, |
1091 | value: UnsafeCell::new(value), |
1092 | pinned: PhantomPinned, |
1093 | #[cfg (slint_debug_property)] |
1094 | debug_name: debug_name.clone().into(), |
1095 | }); |
1096 | // Safety: TwoWayBinding's T is the same as the type for both properties |
1097 | unsafe { |
1098 | prop1.handle.set_binding( |
1099 | TwoWayBinding { common_property: common_property.clone() }, |
1100 | #[cfg (slint_debug_property)] |
1101 | debug_name.as_str(), |
1102 | ); |
1103 | prop2.handle.set_binding( |
1104 | TwoWayBinding { common_property }, |
1105 | #[cfg (slint_debug_property)] |
1106 | debug_name.as_str(), |
1107 | ); |
1108 | } |
1109 | } |
1110 | } |
1111 | |
1112 | #[test ] |
1113 | fn property_two_ways_test() { |
1114 | let p1 = Rc::pin(Property::new(42)); |
1115 | let p2 = Rc::pin(Property::new(88)); |
1116 | |
1117 | let depends = Box::pin(Property::new(0)); |
1118 | depends.as_ref().set_binding({ |
1119 | let p1 = p1.clone(); |
1120 | move || p1.as_ref().get() + 8 |
1121 | }); |
1122 | assert_eq!(depends.as_ref().get(), 42 + 8); |
1123 | Property::link_two_way(p1.as_ref(), p2.as_ref()); |
1124 | assert_eq!(p1.as_ref().get(), 88); |
1125 | assert_eq!(p2.as_ref().get(), 88); |
1126 | assert_eq!(depends.as_ref().get(), 88 + 8); |
1127 | p2.as_ref().set(5); |
1128 | assert_eq!(p1.as_ref().get(), 5); |
1129 | assert_eq!(p2.as_ref().get(), 5); |
1130 | assert_eq!(depends.as_ref().get(), 5 + 8); |
1131 | p1.as_ref().set(22); |
1132 | assert_eq!(p1.as_ref().get(), 22); |
1133 | assert_eq!(p2.as_ref().get(), 22); |
1134 | assert_eq!(depends.as_ref().get(), 22 + 8); |
1135 | } |
1136 | |
1137 | #[test ] |
1138 | fn property_two_ways_test_binding() { |
1139 | let p1 = Rc::pin(Property::new(42)); |
1140 | let p2 = Rc::pin(Property::new(88)); |
1141 | let global = Rc::pin(Property::new(23)); |
1142 | p2.as_ref().set_binding({ |
1143 | let global = global.clone(); |
1144 | move || global.as_ref().get() + 9 |
1145 | }); |
1146 | |
1147 | let depends = Box::pin(Property::new(0)); |
1148 | depends.as_ref().set_binding({ |
1149 | let p1 = p1.clone(); |
1150 | move || p1.as_ref().get() + 8 |
1151 | }); |
1152 | |
1153 | Property::link_two_way(p1.as_ref(), p2.as_ref()); |
1154 | assert_eq!(p1.as_ref().get(), 23 + 9); |
1155 | assert_eq!(p2.as_ref().get(), 23 + 9); |
1156 | assert_eq!(depends.as_ref().get(), 23 + 9 + 8); |
1157 | global.as_ref().set(55); |
1158 | assert_eq!(p1.as_ref().get(), 55 + 9); |
1159 | assert_eq!(p2.as_ref().get(), 55 + 9); |
1160 | assert_eq!(depends.as_ref().get(), 55 + 9 + 8); |
1161 | } |
1162 | |
1163 | #[test ] |
1164 | fn property_two_ways_recurse_from_binding() { |
1165 | let xx = Rc::pin(Property::new(0)); |
1166 | |
1167 | let p1 = Rc::pin(Property::new(42)); |
1168 | let p2 = Rc::pin(Property::new(88)); |
1169 | let global = Rc::pin(Property::new(23)); |
1170 | |
1171 | let done = Rc::new(Cell::new(false)); |
1172 | xx.set_binding({ |
1173 | let p1 = p1.clone(); |
1174 | let p2 = p2.clone(); |
1175 | let global = global.clone(); |
1176 | let xx_weak = pin_weak::rc::PinWeak::downgrade(xx.clone()); |
1177 | move || { |
1178 | if !done.get() { |
1179 | done.set(true); |
1180 | Property::link_two_way(p1.as_ref(), p2.as_ref()); |
1181 | let xx = xx_weak.upgrade().unwrap(); |
1182 | p1.as_ref().set_binding(move || xx.as_ref().get() + 9); |
1183 | } |
1184 | global.as_ref().get() + 2 |
1185 | } |
1186 | }); |
1187 | assert_eq!(xx.as_ref().get(), 23 + 2); |
1188 | assert_eq!(p1.as_ref().get(), 23 + 2 + 9); |
1189 | assert_eq!(p2.as_ref().get(), 23 + 2 + 9); |
1190 | |
1191 | global.as_ref().set(55); |
1192 | assert_eq!(p1.as_ref().get(), 55 + 2 + 9); |
1193 | assert_eq!(p2.as_ref().get(), 55 + 2 + 9); |
1194 | assert_eq!(xx.as_ref().get(), 55 + 2); |
1195 | } |
1196 | |
1197 | #[test ] |
1198 | fn property_two_ways_binding_of_two_way_binding_first() { |
1199 | let p1_1 = Rc::pin(Property::new(2)); |
1200 | let p1_2 = Rc::pin(Property::new(4)); |
1201 | Property::link_two_way(p1_1.as_ref(), p1_2.as_ref()); |
1202 | |
1203 | assert_eq!(p1_1.as_ref().get(), 4); |
1204 | assert_eq!(p1_2.as_ref().get(), 4); |
1205 | |
1206 | let p2 = Rc::pin(Property::new(3)); |
1207 | Property::link_two_way(p1_1.as_ref(), p2.as_ref()); |
1208 | |
1209 | assert_eq!(p1_1.as_ref().get(), 3); |
1210 | assert_eq!(p1_2.as_ref().get(), 3); |
1211 | assert_eq!(p2.as_ref().get(), 3); |
1212 | |
1213 | p1_1.set(6); |
1214 | |
1215 | assert_eq!(p1_1.as_ref().get(), 6); |
1216 | assert_eq!(p1_2.as_ref().get(), 6); |
1217 | assert_eq!(p2.as_ref().get(), 6); |
1218 | |
1219 | p1_2.set(8); |
1220 | |
1221 | assert_eq!(p1_1.as_ref().get(), 8); |
1222 | assert_eq!(p1_2.as_ref().get(), 8); |
1223 | assert_eq!(p2.as_ref().get(), 8); |
1224 | |
1225 | p2.set(7); |
1226 | |
1227 | assert_eq!(p1_1.as_ref().get(), 7); |
1228 | assert_eq!(p1_2.as_ref().get(), 7); |
1229 | assert_eq!(p2.as_ref().get(), 7); |
1230 | } |
1231 | |
1232 | #[test ] |
1233 | fn property_two_ways_binding_of_two_way_binding_second() { |
1234 | let p1 = Rc::pin(Property::new(2)); |
1235 | let p2_1 = Rc::pin(Property::new(3)); |
1236 | let p2_2 = Rc::pin(Property::new(5)); |
1237 | Property::link_two_way(p2_1.as_ref(), p2_2.as_ref()); |
1238 | |
1239 | assert_eq!(p2_1.as_ref().get(), 5); |
1240 | assert_eq!(p2_2.as_ref().get(), 5); |
1241 | |
1242 | Property::link_two_way(p1.as_ref(), p2_2.as_ref()); |
1243 | |
1244 | assert_eq!(p1.as_ref().get(), 5); |
1245 | assert_eq!(p2_1.as_ref().get(), 5); |
1246 | assert_eq!(p2_2.as_ref().get(), 5); |
1247 | |
1248 | p1.set(6); |
1249 | |
1250 | assert_eq!(p1.as_ref().get(), 6); |
1251 | assert_eq!(p2_1.as_ref().get(), 6); |
1252 | assert_eq!(p2_2.as_ref().get(), 6); |
1253 | |
1254 | p2_1.set(7); |
1255 | |
1256 | assert_eq!(p1.as_ref().get(), 7); |
1257 | assert_eq!(p2_1.as_ref().get(), 7); |
1258 | assert_eq!(p2_2.as_ref().get(), 7); |
1259 | |
1260 | p2_2.set(9); |
1261 | |
1262 | assert_eq!(p1.as_ref().get(), 9); |
1263 | assert_eq!(p2_1.as_ref().get(), 9); |
1264 | assert_eq!(p2_2.as_ref().get(), 9); |
1265 | } |
1266 | |
1267 | #[test ] |
1268 | fn property_two_ways_binding_of_two_two_way_bindings() { |
1269 | let p1_1 = Rc::pin(Property::new(2)); |
1270 | let p1_2 = Rc::pin(Property::new(4)); |
1271 | Property::link_two_way(p1_1.as_ref(), p1_2.as_ref()); |
1272 | assert_eq!(p1_1.as_ref().get(), 4); |
1273 | assert_eq!(p1_2.as_ref().get(), 4); |
1274 | |
1275 | let p2_1 = Rc::pin(Property::new(3)); |
1276 | let p2_2 = Rc::pin(Property::new(5)); |
1277 | Property::link_two_way(p2_1.as_ref(), p2_2.as_ref()); |
1278 | |
1279 | assert_eq!(p2_1.as_ref().get(), 5); |
1280 | assert_eq!(p2_2.as_ref().get(), 5); |
1281 | |
1282 | Property::link_two_way(p1_1.as_ref(), p2_2.as_ref()); |
1283 | |
1284 | assert_eq!(p1_1.as_ref().get(), 5); |
1285 | assert_eq!(p1_2.as_ref().get(), 5); |
1286 | assert_eq!(p2_1.as_ref().get(), 5); |
1287 | assert_eq!(p2_2.as_ref().get(), 5); |
1288 | |
1289 | p1_1.set(6); |
1290 | assert_eq!(p1_1.as_ref().get(), 6); |
1291 | assert_eq!(p1_2.as_ref().get(), 6); |
1292 | assert_eq!(p2_1.as_ref().get(), 6); |
1293 | assert_eq!(p2_2.as_ref().get(), 6); |
1294 | |
1295 | p1_2.set(8); |
1296 | assert_eq!(p1_1.as_ref().get(), 8); |
1297 | assert_eq!(p1_2.as_ref().get(), 8); |
1298 | assert_eq!(p2_1.as_ref().get(), 8); |
1299 | assert_eq!(p2_2.as_ref().get(), 8); |
1300 | |
1301 | p2_1.set(7); |
1302 | assert_eq!(p1_1.as_ref().get(), 7); |
1303 | assert_eq!(p1_2.as_ref().get(), 7); |
1304 | assert_eq!(p2_1.as_ref().get(), 7); |
1305 | assert_eq!(p2_2.as_ref().get(), 7); |
1306 | |
1307 | p2_2.set(9); |
1308 | assert_eq!(p1_1.as_ref().get(), 9); |
1309 | assert_eq!(p1_2.as_ref().get(), 9); |
1310 | assert_eq!(p2_1.as_ref().get(), 9); |
1311 | assert_eq!(p2_2.as_ref().get(), 9); |
1312 | } |
1313 | |
1314 | mod change_tracker; |
1315 | pub use change_tracker::*; |
1316 | mod properties_animations; |
1317 | pub use crate::items::StateInfo; |
1318 | pub use properties_animations::*; |
1319 | |
1320 | struct StateInfoBinding<F> { |
1321 | dirty_time: Cell<Option<crate::animations::Instant>>, |
1322 | binding: F, |
1323 | } |
1324 | |
1325 | unsafe impl<F: Fn() -> i32> crate::properties::BindingCallable for StateInfoBinding<F> { |
1326 | unsafe fn evaluate(self: Pin<&Self>, value: *mut ()) -> BindingResult { |
1327 | // Safety: We should only set this binding on a property of type StateInfo |
1328 | let value: &mut StateInfo = &mut *(value as *mut StateInfo); |
1329 | let new_state: i32 = (self.binding)(); |
1330 | let timestamp: Option = self.dirty_time.take(); |
1331 | if new_state != value.current_state { |
1332 | value.previous_state = value.current_state; |
1333 | value.change_time = timestamp.unwrap_or_else(crate::animations::current_tick); |
1334 | value.current_state = new_state; |
1335 | } |
1336 | BindingResult::KeepBinding |
1337 | } |
1338 | |
1339 | fn mark_dirty(self: Pin<&Self>) { |
1340 | if self.dirty_time.get().is_none() { |
1341 | self.dirty_time.set(val:Some(crate::animations::current_tick())) |
1342 | } |
1343 | } |
1344 | } |
1345 | |
1346 | /// Sets a binding that returns a state to a StateInfo property |
1347 | pub fn set_state_binding(property: Pin<&Property<StateInfo>>, binding: impl Fn() -> i32 + 'static) { |
1348 | let bind_callable: StateInfoBinding … + 'static> = StateInfoBinding { dirty_time: Cell::new(None), binding }; |
1349 | // Safety: The StateInfoBinding is a BindingCallable for type StateInfo |
1350 | unsafe { |
1351 | property.handle.set_binding( |
1352 | bind_callable, |
1353 | #[cfg (slint_debug_property)] |
1354 | property.debug_name.borrow().as_str(), |
1355 | ) |
1356 | } |
1357 | } |
1358 | |
1359 | #[doc (hidden)] |
1360 | pub trait PropertyDirtyHandler { |
1361 | fn notify(self: Pin<&Self>); |
1362 | } |
1363 | |
1364 | impl PropertyDirtyHandler for () { |
1365 | fn notify(self: Pin<&Self>) {} |
1366 | } |
1367 | |
1368 | impl<F: Fn()> PropertyDirtyHandler for F { |
1369 | fn notify(self: Pin<&Self>) { |
1370 | (self.get_ref())() |
1371 | } |
1372 | } |
1373 | |
1374 | /// This structure allow to run a closure that queries properties, and can report |
1375 | /// if any property we accessed have become dirty |
1376 | pub struct PropertyTracker<DirtyHandler = ()> { |
1377 | holder: BindingHolder<DirtyHandler>, |
1378 | } |
1379 | |
1380 | impl Default for PropertyTracker<()> { |
1381 | fn default() -> Self { |
1382 | static VT: &BindingVTable = &BindingVTable { |
1383 | drop: |_| (), |
1384 | evaluate: |_, _| BindingResult::KeepBinding, |
1385 | mark_dirty: |_, _| (), |
1386 | intercept_set: |_, _| false, |
1387 | intercept_set_binding: |_, _| false, |
1388 | }; |
1389 | |
1390 | let holder: BindingHolder = BindingHolder { |
1391 | dependencies: Cell::new(0), |
1392 | dep_nodes: Default::default(), |
1393 | vtable: VT, |
1394 | dirty: Cell::new(true), // starts dirty so it evaluates the property when used |
1395 | is_two_way_binding: false, |
1396 | pinned: PhantomPinned, |
1397 | binding: (), |
1398 | #[cfg (slint_debug_property)] |
1399 | debug_name: "<PropertyTracker<()>>" .into(), |
1400 | }; |
1401 | Self { holder } |
1402 | } |
1403 | } |
1404 | |
1405 | impl<DirtyHandler> Drop for PropertyTracker<DirtyHandler> { |
1406 | fn drop(&mut self) { |
1407 | unsafe { |
1408 | DependencyListHead::drop(self.holder.dependencies.as_ptr() as *mut DependencyListHead); |
1409 | } |
1410 | } |
1411 | } |
1412 | |
1413 | impl<DirtyHandler: PropertyDirtyHandler> PropertyTracker<DirtyHandler> { |
1414 | #[cfg (slint_debug_property)] |
1415 | /// set the debug name when `cfg(slint_debug_property` |
1416 | pub fn set_debug_name(&mut self, debug_name: String) { |
1417 | self.holder.debug_name = debug_name; |
1418 | } |
1419 | |
1420 | /// Register this property tracker as a dependency to the current binding/property tracker being evaluated |
1421 | pub fn register_as_dependency_to_current_binding(self: Pin<&Self>) { |
1422 | if CURRENT_BINDING.is_set() { |
1423 | CURRENT_BINDING.with(|cur_binding| { |
1424 | if let Some(cur_binding) = cur_binding { |
1425 | debug_assert!(!core::ptr::eq( |
1426 | self.holder.dependencies.get() as *const u32, |
1427 | (&CONSTANT_PROPERTY_SENTINEL) as *const u32, |
1428 | )); |
1429 | cur_binding.register_self_as_dependency( |
1430 | self.holder.dependencies.as_ptr() as *mut DependencyListHead, |
1431 | #[cfg (slint_debug_property)] |
1432 | &self.holder.debug_name, |
1433 | ); |
1434 | } |
1435 | }); |
1436 | } |
1437 | } |
1438 | |
1439 | /// Any of the properties accessed during the last evaluation of the closure called |
1440 | /// from the last call to evaluate is potentially dirty. |
1441 | pub fn is_dirty(&self) -> bool { |
1442 | self.holder.dirty.get() |
1443 | } |
1444 | |
1445 | /// Evaluate the function, and record dependencies of properties accessed within this function. |
1446 | /// If this is called during the evaluation of another property binding or property tracker, then |
1447 | /// any changes to accessed properties will also mark the other binding/tracker dirty. |
1448 | pub fn evaluate<R>(self: Pin<&Self>, f: impl FnOnce() -> R) -> R { |
1449 | self.register_as_dependency_to_current_binding(); |
1450 | self.evaluate_as_dependency_root(f) |
1451 | } |
1452 | |
1453 | /// Evaluate the function, and record dependencies of properties accessed within this function. |
1454 | /// If this is called during the evaluation of another property binding or property tracker, then |
1455 | /// any changes to accessed properties will not propagate to the other tracker. |
1456 | pub fn evaluate_as_dependency_root<R>(self: Pin<&Self>, f: impl FnOnce() -> R) -> R { |
1457 | // clear all the nodes so that we can start from scratch |
1458 | self.holder.dep_nodes.set(Default::default()); |
1459 | |
1460 | // Safety: it is safe to project the holder as we don't implement drop or unpin |
1461 | let pinned_holder = unsafe { |
1462 | self.map_unchecked(|s| { |
1463 | core::mem::transmute::<&BindingHolder<DirtyHandler>, &BindingHolder<()>>(&s.holder) |
1464 | }) |
1465 | }; |
1466 | let r = CURRENT_BINDING.set(Some(pinned_holder), f); |
1467 | self.holder.dirty.set(false); |
1468 | r |
1469 | } |
1470 | |
1471 | /// Call [`Self::evaluate`] if and only if it is dirty. |
1472 | /// But register a dependency in any case. |
1473 | pub fn evaluate_if_dirty<R>(self: Pin<&Self>, f: impl FnOnce() -> R) -> Option<R> { |
1474 | self.register_as_dependency_to_current_binding(); |
1475 | self.is_dirty().then(|| self.evaluate_as_dependency_root(f)) |
1476 | } |
1477 | |
1478 | /// Mark this PropertyTracker as dirty |
1479 | pub fn set_dirty(&self) { |
1480 | self.holder.dirty.set(true); |
1481 | unsafe { mark_dependencies_dirty(self.holder.dependencies.as_ptr() as *mut _) }; |
1482 | } |
1483 | |
1484 | /// Sets the specified callback handler function, which will be called if any |
1485 | /// properties that this tracker depends on becomes dirty. |
1486 | /// |
1487 | /// The `handler` `PropertyDirtyHandler` is a trait which is implemented for |
1488 | /// any `Fn()` closure |
1489 | /// |
1490 | /// Note that the handler will be invoked immediately when a property is modified or |
1491 | /// marked as dirty. In particular, the involved property are still in a locked |
1492 | /// state and should not be accessed while the handler is run. This function can be |
1493 | /// useful to mark some work to be done later. |
1494 | pub fn new_with_dirty_handler(handler: DirtyHandler) -> Self { |
1495 | /// Safety: _self must be a pointer to a `BindingHolder<DirtyHandler>` |
1496 | unsafe fn mark_dirty<B: PropertyDirtyHandler>( |
1497 | _self: *const BindingHolder, |
1498 | was_dirty: bool, |
1499 | ) { |
1500 | if !was_dirty { |
1501 | Pin::new_unchecked(&(*(_self as *const BindingHolder<B>)).binding).notify(); |
1502 | } |
1503 | } |
1504 | |
1505 | trait HasBindingVTable { |
1506 | const VT: &'static BindingVTable; |
1507 | } |
1508 | impl<B: PropertyDirtyHandler> HasBindingVTable for B { |
1509 | const VT: &'static BindingVTable = &BindingVTable { |
1510 | drop: |_| (), |
1511 | evaluate: |_, _| BindingResult::KeepBinding, |
1512 | mark_dirty: mark_dirty::<B>, |
1513 | intercept_set: |_, _| false, |
1514 | intercept_set_binding: |_, _| false, |
1515 | }; |
1516 | } |
1517 | |
1518 | let holder = BindingHolder { |
1519 | dependencies: Cell::new(0), |
1520 | dep_nodes: Default::default(), |
1521 | vtable: <DirtyHandler as HasBindingVTable>::VT, |
1522 | dirty: Cell::new(true), // starts dirty so it evaluates the property when used |
1523 | is_two_way_binding: false, |
1524 | pinned: PhantomPinned, |
1525 | binding: handler, |
1526 | #[cfg (slint_debug_property)] |
1527 | debug_name: "<PropertyTracker>" .into(), |
1528 | }; |
1529 | Self { holder } |
1530 | } |
1531 | } |
1532 | |
1533 | #[test ] |
1534 | fn test_property_listener_scope() { |
1535 | let scope = Box::pin(PropertyTracker::default()); |
1536 | let prop1 = Box::pin(Property::new(42)); |
1537 | assert!(scope.is_dirty()); // It is dirty at the beginning |
1538 | |
1539 | let r = scope.as_ref().evaluate(|| prop1.as_ref().get()); |
1540 | assert_eq!(r, 42); |
1541 | assert!(!scope.is_dirty()); // It is no longer dirty |
1542 | prop1.as_ref().set(88); |
1543 | assert!(scope.is_dirty()); // now dirty for prop1 changed. |
1544 | let r = scope.as_ref().evaluate(|| prop1.as_ref().get() + 1); |
1545 | assert_eq!(r, 89); |
1546 | assert!(!scope.is_dirty()); |
1547 | let r = scope.as_ref().evaluate(|| 12); |
1548 | assert_eq!(r, 12); |
1549 | assert!(!scope.is_dirty()); |
1550 | prop1.as_ref().set(1); |
1551 | assert!(!scope.is_dirty()); |
1552 | scope.as_ref().evaluate_if_dirty(|| panic!("should not be dirty" )); |
1553 | scope.set_dirty(); |
1554 | let mut ok = false; |
1555 | scope.as_ref().evaluate_if_dirty(|| ok = true); |
1556 | assert!(ok); |
1557 | } |
1558 | |
1559 | #[test ] |
1560 | fn test_nested_property_trackers() { |
1561 | let tracker1 = Box::pin(PropertyTracker::default()); |
1562 | let tracker2 = Box::pin(PropertyTracker::default()); |
1563 | let prop = Box::pin(Property::new(42)); |
1564 | |
1565 | let r = tracker1.as_ref().evaluate(|| tracker2.as_ref().evaluate(|| prop.as_ref().get())); |
1566 | assert_eq!(r, 42); |
1567 | |
1568 | prop.as_ref().set(1); |
1569 | assert!(tracker2.as_ref().is_dirty()); |
1570 | assert!(tracker1.as_ref().is_dirty()); |
1571 | |
1572 | let r = tracker1 |
1573 | .as_ref() |
1574 | .evaluate(|| tracker2.as_ref().evaluate_as_dependency_root(|| prop.as_ref().get())); |
1575 | assert_eq!(r, 1); |
1576 | prop.as_ref().set(100); |
1577 | assert!(tracker2.as_ref().is_dirty()); |
1578 | assert!(!tracker1.as_ref().is_dirty()); |
1579 | } |
1580 | |
1581 | #[test ] |
1582 | fn test_property_dirty_handler() { |
1583 | let call_flag = Rc::new(Cell::new(false)); |
1584 | let tracker = Box::pin(PropertyTracker::new_with_dirty_handler({ |
1585 | let call_flag = call_flag.clone(); |
1586 | move || { |
1587 | (*call_flag).set(true); |
1588 | } |
1589 | })); |
1590 | let prop = Box::pin(Property::new(42)); |
1591 | |
1592 | let r = tracker.as_ref().evaluate(|| prop.as_ref().get()); |
1593 | |
1594 | assert_eq!(r, 42); |
1595 | assert!(!tracker.as_ref().is_dirty()); |
1596 | assert!(!call_flag.get()); |
1597 | |
1598 | prop.as_ref().set(100); |
1599 | assert!(tracker.as_ref().is_dirty()); |
1600 | assert!(call_flag.get()); |
1601 | |
1602 | // Repeated changes before evaluation should not trigger further |
1603 | // change handler calls, otherwise it would be a notification storm. |
1604 | call_flag.set(false); |
1605 | prop.as_ref().set(101); |
1606 | assert!(tracker.as_ref().is_dirty()); |
1607 | assert!(!call_flag.get()); |
1608 | } |
1609 | |
1610 | #[test ] |
1611 | fn test_property_tracker_drop() { |
1612 | let outer_tracker = Box::pin(PropertyTracker::default()); |
1613 | let inner_tracker = Box::pin(PropertyTracker::default()); |
1614 | let prop = Box::pin(Property::new(42)); |
1615 | |
1616 | let r = |
1617 | outer_tracker.as_ref().evaluate(|| inner_tracker.as_ref().evaluate(|| prop.as_ref().get())); |
1618 | assert_eq!(r, 42); |
1619 | |
1620 | drop(inner_tracker); |
1621 | prop.as_ref().set(200); // don't crash |
1622 | } |
1623 | |
1624 | #[test ] |
1625 | fn test_nested_property_tracker_dirty() { |
1626 | let outer_tracker = Box::pin(PropertyTracker::default()); |
1627 | let inner_tracker = Box::pin(PropertyTracker::default()); |
1628 | let prop = Box::pin(Property::new(42)); |
1629 | |
1630 | let r = |
1631 | outer_tracker.as_ref().evaluate(|| inner_tracker.as_ref().evaluate(|| prop.as_ref().get())); |
1632 | assert_eq!(r, 42); |
1633 | |
1634 | assert!(!outer_tracker.is_dirty()); |
1635 | assert!(!inner_tracker.is_dirty()); |
1636 | |
1637 | // Let's pretend that there was another dependency unaccounted first, mark the inner tracker as dirty |
1638 | // by hand. |
1639 | inner_tracker.as_ref().set_dirty(); |
1640 | assert!(outer_tracker.is_dirty()); |
1641 | } |
1642 | |
1643 | #[test ] |
1644 | #[allow (clippy::redundant_closure)] |
1645 | fn test_nested_property_tracker_evaluate_if_dirty() { |
1646 | let outer_tracker = Box::pin(PropertyTracker::default()); |
1647 | let inner_tracker = Box::pin(PropertyTracker::default()); |
1648 | let prop = Box::pin(Property::new(42)); |
1649 | |
1650 | let mut cache = 0; |
1651 | let mut cache_or_evaluate = || { |
1652 | if let Some(x) = inner_tracker.as_ref().evaluate_if_dirty(|| prop.as_ref().get() + 1) { |
1653 | cache = x; |
1654 | } |
1655 | cache |
1656 | }; |
1657 | let r = outer_tracker.as_ref().evaluate(|| cache_or_evaluate()); |
1658 | assert_eq!(r, 43); |
1659 | assert!(!outer_tracker.is_dirty()); |
1660 | assert!(!inner_tracker.is_dirty()); |
1661 | prop.as_ref().set(11); |
1662 | assert!(outer_tracker.is_dirty()); |
1663 | assert!(inner_tracker.is_dirty()); |
1664 | let r = outer_tracker.as_ref().evaluate(|| cache_or_evaluate()); |
1665 | assert_eq!(r, 12); |
1666 | } |
1667 | |
1668 | #[cfg (feature = "ffi" )] |
1669 | pub(crate) mod ffi; |
1670 | |