1#![allow(unsafe_code)]
14#![warn(missing_docs)]
15
16mod single_linked_list_pin {
20 #![allow(unsafe_code)]
21 use core::pin::Pin;
22 use core::ptr::NonNull;
23
24 type NodePtr<T> = Option<NonNull<SingleLinkedListPinNode<T>>>;
25 struct SingleLinkedListPinNode<T> {
26 next: NodePtr<T>,
27 value: T,
28 }
29
30 pub struct SingleLinkedListPinHead<T>(NodePtr<T>);
31 impl<T> Default for SingleLinkedListPinHead<T> {
32 fn default() -> Self {
33 Self(None)
34 }
35 }
36
37 impl<T> Drop for SingleLinkedListPinHead<T> {
38 fn drop(&mut self) {
39 let mut cur = self.0.take();
41 while let Some(node) = cur {
42 unsafe {
45 cur = (*node.as_ptr()).next;
46 core::ptr::drop_in_place(&raw mut (*node.as_ptr()).value);
47 alloc::alloc::dealloc(
48 node.as_ptr().cast(),
49 core::alloc::Layout::new::<SingleLinkedListPinNode<T>>(),
50 );
51 }
52 }
53 }
54 }
55
56 impl<T> SingleLinkedListPinHead<T> {
57 pub fn push_front(&mut self, value: T) -> Pin<&T> {
58 let node = SingleLinkedListPinNode { next: self.0.take(), value };
59 let ptr = unsafe {
61 let layout = core::alloc::Layout::new::<SingleLinkedListPinNode<T>>();
62 let mem = alloc::alloc::alloc(layout) as *mut SingleLinkedListPinNode<T>;
63 assert!(!mem.is_null(), "allocation failed");
64 core::ptr::write(mem, node);
65 NonNull::new_unchecked(mem)
66 };
67 self.0 = Some(ptr);
68 unsafe { Pin::new_unchecked(&(*ptr.as_ptr()).value) }
70 }
71
72 #[allow(unused)]
73 pub fn iter(&self) -> impl Iterator<Item = Pin<&T>> {
74 struct I<'a, T>(&'a NodePtr<T>);
75
76 impl<'a, T> Iterator for I<'a, T> {
77 type Item = Pin<&'a T>;
78 fn next(&mut self) -> Option<Self::Item> {
79 if let Some(node) = self.0 {
80 let r = unsafe { Pin::new_unchecked(&(*node.as_ptr()).value) };
82 self.0 = unsafe { &(*node.as_ptr()).next };
83 Some(r)
84 } else {
85 None
86 }
87 }
88 }
89 I(&self.0)
90 }
91
92 pub fn is_empty(&self) -> bool {
94 self.0.is_none()
95 }
96 }
97
98 #[test]
99 fn test_list() {
100 let mut head = SingleLinkedListPinHead::default();
101 head.push_front(1);
102 head.push_front(2);
103 head.push_front(3);
104 assert_eq!(
105 head.iter().map(|x: Pin<&i32>| *x.get_ref()).collect::<std::vec::Vec<i32>>(),
106 std::vec![3, 2, 1]
107 );
108 }
109 #[test]
110 fn big_list() {
111 let mut head = SingleLinkedListPinHead::default();
113 for x in 0..100000 {
114 head.push_front(x);
115 }
116 }
117}
118
119pub(crate) mod dependency_tracker {
120 use core::cell::Cell;
126 use core::pin::Pin;
127
128 #[repr(transparent)]
129 pub struct DependencyListHead<T>(Cell<*const DependencyNode<T>>);
130
131 impl<T> Default for DependencyListHead<T> {
132 fn default() -> Self {
133 Self(Cell::new(core::ptr::null()))
134 }
135 }
136 impl<T> Drop for DependencyListHead<T> {
137 fn drop(&mut self) {
138 unsafe { DependencyListHead::drop(self as *mut Self) };
139 }
140 }
141
142 impl<T> DependencyListHead<T> {
143 pub unsafe fn mem_move(from: *mut Self, to: *mut Self) {
144 unsafe {
145 (*to).0.set((*from).0.get());
146 if let Some(next) = (*from).0.get().as_ref() {
147 debug_assert_eq!(from as *const _, next.prev.get() as *const _);
148 next.debug_assert_valid();
149 next.prev.set(to as *const _);
150 next.debug_assert_valid();
151 }
152 }
153 }
154
155 pub fn swap(from: Pin<&Self>, to: Pin<&Self>) {
157 Cell::swap(&from.0, &to.0);
158 unsafe {
159 if let Some(n) = from.0.get().as_ref() {
160 debug_assert_eq!(n.prev.get() as *const _, &to.0 as *const _);
161 n.prev.set(&from.0 as *const _);
162 n.debug_assert_valid();
163 }
164
165 if let Some(n) = to.0.get().as_ref() {
166 debug_assert_eq!(n.prev.get() as *const _, &from.0 as *const _);
167 n.prev.set(&to.0 as *const _);
168 n.debug_assert_valid();
169 }
170 }
171 }
172
173 pub fn is_empty(&self) -> bool {
175 self.0.get().is_null()
176 }
177
178 pub unsafe fn drop(_self: *mut Self) {
179 unsafe {
180 if let Some(next) = (*_self).0.get().as_ref() {
181 #[cfg(not(miri))]
182 debug_assert_eq!(_self as *const _, next.prev.get() as *const _);
183 next.debug_assert_valid();
184 next.prev.set(core::ptr::null());
185 next.debug_assert_valid();
186 }
187 }
188 }
189 pub fn append(&self, node: Pin<&DependencyNode<T>>) {
190 unsafe {
191 node.remove();
192 node.debug_assert_valid();
193 let old = self.0.get();
194 if let Some(x) = old.as_ref() {
195 x.debug_assert_valid();
196 }
197 self.0.set(node.get_ref() as *const DependencyNode<_>);
198 node.next.set(old);
199 node.prev.set(&self.0 as *const _);
200 if let Some(old) = old.as_ref() {
201 old.prev.set((&node.next) as *const _);
202 old.debug_assert_valid();
203 }
204 node.debug_assert_valid();
205 }
206 }
207
208 pub fn for_each(&self, mut f: impl FnMut(&T)) {
209 unsafe {
210 let mut next = self.0.get();
211 while let Some(node) = next.as_ref() {
212 node.debug_assert_valid();
213 next = node.next.get();
214 f(&node.binding);
215 }
216 }
217 }
218
219 pub fn take_head(&self) -> Option<T>
221 where
222 T: Copy,
223 {
224 unsafe {
225 if let Some(node) = self.0.get().as_ref() {
226 node.debug_assert_valid();
227 node.remove();
228 Some(node.binding)
229 } else {
230 None
231 }
232 }
233 }
234 }
235
236 pub struct DependencyNode<T> {
239 next: Cell<*const DependencyNode<T>>,
240 prev: Cell<*const Cell<*const DependencyNode<T>>>,
242 binding: T,
243 }
244
245 impl<T> DependencyNode<T> {
246 pub fn new(binding: T) -> Self {
247 Self { next: Cell::new(core::ptr::null()), prev: Cell::new(core::ptr::null()), binding }
248 }
249
250 pub fn debug_assert_valid(&self) {
252 #[cfg(not(miri))]
255 unsafe {
256 debug_assert!(
257 self.prev.get().is_null() || core::ptr::eq((*self.prev.get()).get(), self)
258 );
259 debug_assert!(
260 self.next.get().is_null()
261 || core::ptr::eq((*self.next.get()).prev.get(), &self.next)
262 );
263 debug_assert_ne!(self.next.get(), self as *const DependencyNode<T>);
265 debug_assert_ne!(
266 self.prev.get(),
267 (&self.next) as *const Cell<*const DependencyNode<T>>
268 );
269 }
270 }
271
272 pub fn remove(&self) {
273 self.debug_assert_valid();
274 unsafe {
275 if let Some(prev) = self.prev.get().as_ref() {
276 prev.set(self.next.get());
277 }
278 if let Some(next) = self.next.get().as_ref() {
279 next.debug_assert_valid();
280 next.prev.set(self.prev.get());
281 next.debug_assert_valid();
282 }
283 }
284 self.prev.set(core::ptr::null());
285 self.next.set(core::ptr::null());
286 }
287 }
288
289 impl<T> Drop for DependencyNode<T> {
290 fn drop(&mut self) {
291 self.remove();
292 }
293 }
294}
295
296type DependencyListHead = dependency_tracker::DependencyListHead<*const BindingHolder>;
297type DependencyNode = dependency_tracker::DependencyNode<*const BindingHolder>;
298
299use alloc::boxed::Box;
300use core::cell::{Cell, RefCell, UnsafeCell};
301use core::ffi::c_void;
302use core::marker::PhantomPinned;
303use core::pin::Pin;
304
305static CONSTANT_PROPERTY_SENTINEL: u32 = 0;
308
309#[inline(always)]
310fn const_sentinel() -> *mut () {
311 (&CONSTANT_PROPERTY_SENTINEL) as *const u32 as *mut ()
312}
313
314#[derive(Copy, Clone, Debug, Eq, PartialEq)]
316enum BindingResult {
317 KeepBinding,
319 RemoveBinding,
322}
323
324struct BindingVTable {
325 drop: unsafe fn(_self: *mut BindingHolder),
326 evaluate: unsafe fn(_self: *const BindingHolder, value: *mut c_void) -> BindingResult,
327 mark_dirty: unsafe fn(_self: *const BindingHolder, was_dirty: bool),
328 intercept_set: unsafe fn(_self: *const BindingHolder, value: *const c_void) -> bool,
329 intercept_set_binding:
330 unsafe fn(_self: *const BindingHolder, new_binding: *mut BindingHolder) -> bool,
331}
332
333unsafe trait BindingCallable<T> {
339 fn evaluate(self: Pin<&Self>, value: &mut T) -> BindingResult;
342
343 fn mark_dirty(self: Pin<&Self>) {}
346
347 fn intercept_set(self: Pin<&Self>, _value: &T) -> bool {
353 false
354 }
355
356 unsafe fn intercept_set_binding(self: Pin<&Self>, _new_binding: *mut BindingHolder) -> bool {
360 false
361 }
362
363 const IS_TWO_WAY_BINDING: bool = false;
365}
366
367unsafe impl<T, F: Fn(&mut T) -> BindingResult> BindingCallable<T> for F {
368 fn evaluate(self: Pin<&Self>, value: &mut T) -> BindingResult {
369 self(value)
370 }
371}
372
373mod current_binding_storage {
375 use super::BindingHolder;
376 use core::cell::Cell;
377
378 #[cfg(feature = "std")]
379 std::thread_local! {
380 static CURRENT_BINDING: Cell<*const BindingHolder> = const { Cell::new(core::ptr::null()) };
381 }
382
383 #[cfg(feature = "std")]
384 pub(super) fn set<T>(value: Option<*const BindingHolder>, f: impl FnOnce() -> T) -> T {
385 CURRENT_BINDING.with(|cell| {
386 let old = cell.replace(value.unwrap_or(core::ptr::null()));
387 let res = f();
388 cell.set(old);
389 res
390 })
391 }
392
393 #[cfg(feature = "std")]
394 pub(super) fn with<T>(f: impl FnOnce(Option<*const BindingHolder>) -> T) -> T {
395 CURRENT_BINDING.with(|cell| {
396 let ptr = cell.get();
397 f(if ptr.is_null() { None } else { Some(ptr) })
398 })
399 }
400
401 #[cfg(all(not(feature = "std"), feature = "unsafe-single-threaded"))]
402 static CURRENT_BINDING: ScopedRawPtr = ScopedRawPtr(Cell::new(core::ptr::null()));
403
404 #[cfg(all(not(feature = "std"), feature = "unsafe-single-threaded"))]
405 struct ScopedRawPtr(Cell<*const BindingHolder>);
406 #[cfg(all(not(feature = "std"), feature = "unsafe-single-threaded"))]
408 unsafe impl Send for ScopedRawPtr {}
409 #[cfg(all(not(feature = "std"), feature = "unsafe-single-threaded"))]
410 unsafe impl Sync for ScopedRawPtr {}
411
412 #[cfg(all(not(feature = "std"), feature = "unsafe-single-threaded"))]
413 pub(super) fn set<T>(value: Option<*const BindingHolder>, f: impl FnOnce() -> T) -> T {
414 let old = CURRENT_BINDING.0.replace(value.unwrap_or(core::ptr::null()));
415 let res = f();
416 CURRENT_BINDING.0.set(old);
417 res
418 }
419
420 #[cfg(all(not(feature = "std"), feature = "unsafe-single-threaded"))]
421 pub(super) fn with<T>(f: impl FnOnce(Option<*const BindingHolder>) -> T) -> T {
422 let ptr = CURRENT_BINDING.0.get();
423 f(if ptr.is_null() { None } else { Some(ptr) })
424 }
425}
426
427pub fn evaluate_no_tracking<T>(f: impl FnOnce() -> T) -> T {
429 current_binding_storage::set(None, f)
430}
431
432pub fn is_currently_tracking() -> bool {
435 current_binding_storage::with(|x| x.is_some())
436}
437
438#[repr(C)]
440struct BindingHolder<B = ()> {
441 dependencies: Cell<*mut ()>,
443 dep_nodes: UnsafeCell<single_linked_list_pin::SingleLinkedListPinHead<DependencyNode>>,
447 vtable: &'static BindingVTable,
448 dirty: Cell<bool>,
450 is_two_way_binding: bool,
452 pinned: PhantomPinned,
453 #[cfg(slint_debug_property)]
454 pub debug_name: alloc::string::String,
455
456 binding: B,
457}
458
459impl BindingHolder {
460 fn register_self_as_dependency(
462 self_ptr: *const BindingHolder,
463 property_that_will_notify: *mut DependencyListHead,
464 #[cfg(slint_debug_property)] _other_debug_name: &str,
465 ) {
466 let node = DependencyNode::new(self_ptr);
467 unsafe {
469 let dep_nodes = &mut *(*self_ptr).dep_nodes.get();
470 let node = dep_nodes.push_front(node);
471 DependencyListHead::append(&*property_that_will_notify, node);
472 }
473 }
474}
475
476fn alloc_binding_holder<T, B: BindingCallable<T> + 'static>(binding: B) -> *mut BindingHolder {
477 unsafe fn binding_drop<B>(_self: *mut BindingHolder) {
479 unsafe {
480 drop(Box::from_raw(_self as *mut BindingHolder<B>));
481 }
482 }
483
484 unsafe fn evaluate<T, B: BindingCallable<T>>(
487 _self: *const BindingHolder,
488 value: *mut c_void,
489 ) -> BindingResult {
490 unsafe {
491 Pin::new_unchecked(&((*(_self as *const BindingHolder<B>)).binding))
492 .evaluate(&mut *(value as *mut T))
493 }
494 }
495
496 unsafe fn mark_dirty<T, B: BindingCallable<T>>(_self: *const BindingHolder, _: bool) {
498 unsafe { Pin::new_unchecked(&((*(_self as *const BindingHolder<B>)).binding)).mark_dirty() }
499 }
500
501 unsafe fn intercept_set<T, B: BindingCallable<T>>(
503 _self: *const BindingHolder,
504 value: *const c_void,
505 ) -> bool {
506 unsafe {
507 Pin::new_unchecked(&((*(_self as *const BindingHolder<B>)).binding))
508 .intercept_set(&*(value as *const T))
509 }
510 }
511
512 unsafe fn intercept_set_binding<T, B: BindingCallable<T>>(
513 _self: *const BindingHolder,
514 new_binding: *mut BindingHolder,
515 ) -> bool {
516 unsafe {
517 Pin::new_unchecked(&((*(_self as *const BindingHolder<B>)).binding))
518 .intercept_set_binding(new_binding)
519 }
520 }
521
522 trait HasBindingVTable<T> {
523 const VT: &'static BindingVTable;
524 }
525 impl<T, B: BindingCallable<T>> HasBindingVTable<T> for B {
526 const VT: &'static BindingVTable = &BindingVTable {
527 drop: binding_drop::<B>,
528 evaluate: evaluate::<T, B>,
529 mark_dirty: mark_dirty::<T, B>,
530 intercept_set: intercept_set::<T, B>,
531 intercept_set_binding: intercept_set_binding::<T, B>,
532 };
533 }
534
535 let holder: BindingHolder<B> = BindingHolder {
536 dependencies: Cell::new(core::ptr::null_mut()),
537 dep_nodes: Default::default(),
538 vtable: <B as HasBindingVTable<T>>::VT,
539 dirty: Cell::new(true), is_two_way_binding: B::IS_TWO_WAY_BINDING,
541 pinned: PhantomPinned,
542 #[cfg(slint_debug_property)]
543 debug_name: Default::default(),
544 binding,
545 };
546 Box::into_raw(Box::new(holder)) as *mut BindingHolder
547}
548
549#[repr(transparent)]
550#[derive(Default)]
551struct PropertyHandle {
552 handle: Cell<*mut ()>,
557}
558
559const BINDING_BORROWED: usize = 0b01;
560const BINDING_POINTER_TO_BINDING: usize = 0b10;
561const BINDING_POINTER_MASK: usize = !(BINDING_POINTER_TO_BINDING | BINDING_BORROWED);
562
563impl core::fmt::Debug for PropertyHandle {
564 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
565 let handle = self.handle.get();
566 write!(
567 f,
568 "PropertyHandle {{ handle: 0x{:x}, locked: {}, binding: {} }}",
569 handle.addr() & !0b11,
570 self.lock_flag(),
571 PropertyHandle::is_pointer_to_binding(handle)
572 )
573 }
574}
575
576impl PropertyHandle {
577 #[inline]
579 fn lock_flag(&self) -> bool {
580 self.handle.get().addr() & BINDING_BORROWED != 0
581 }
582 unsafe fn set_lock_flag(&self, set: bool) {
585 self.handle.set(if set {
586 self.handle.get().map_addr(|a| a | BINDING_BORROWED)
587 } else {
588 self.handle.get().map_addr(|a| a & !BINDING_BORROWED)
589 })
590 }
591
592 #[inline]
593 fn is_pointer_to_binding(handle: *mut ()) -> bool {
594 handle.addr() & BINDING_POINTER_TO_BINDING != 0
595 }
596
597 #[inline]
599 fn pointer_to_binding(handle: *mut ()) -> Option<*mut BindingHolder> {
600 if Self::is_pointer_to_binding(handle) {
601 Some(handle.map_addr(|a| a & BINDING_POINTER_MASK) as *mut BindingHolder)
602 } else {
603 None
604 }
605 }
606
607 #[inline]
610 fn has_no_binding_or_lock(handle: *mut ()) -> bool {
611 handle.addr() & (BINDING_BORROWED | BINDING_POINTER_TO_BINDING) == 0
612 }
613
614 fn access<R>(&self, f: impl FnOnce(Option<Pin<&mut BindingHolder>>) -> R) -> R {
617 #[cfg(slint_debug_property)]
618 if self.lock_flag() {
619 unsafe {
620 let handle = self.handle.get();
621 if let Some(binding_pointer) = Self::pointer_to_binding(handle) {
622 let binding = &mut *(binding_pointer);
623 let debug_name = &binding.debug_name;
624 panic!("Recursion detected with property {debug_name}");
625 }
626 }
627 }
628 assert!(!self.lock_flag(), "Recursion detected");
629 unsafe {
630 self.set_lock_flag(true);
631 scopeguard::defer! { self.set_lock_flag(false); }
632 let handle = self.handle.get();
633 let binding =
634 Self::pointer_to_binding(handle).map(|pointer| Pin::new_unchecked(&mut *(pointer)));
635 f(binding)
636 }
637 }
638
639 fn detach_binding(&self) -> Option<*mut BindingHolder> {
645 let binding = Self::pointer_to_binding(self.handle.get())?;
646 unsafe {
647 let const_sentinel = const_sentinel();
648 if (*binding).dependencies.get() == const_sentinel {
649 self.handle.set(const_sentinel);
650 } else {
651 DependencyListHead::mem_move(
652 (*binding).dependencies.as_ptr() as *mut DependencyListHead,
653 self.handle.as_ptr() as *mut DependencyListHead,
654 );
655 }
656 (*binding).dependencies.set(core::ptr::null_mut());
657 }
658 Some(binding)
659 }
660
661 fn remove_binding(&self) {
662 assert!(!self.lock_flag(), "Recursion detected");
663
664 if let Some(binding) = self.detach_binding() {
665 unsafe {
666 ((*binding).vtable.drop)(binding);
667 }
668 }
669 debug_assert!(Self::has_no_binding_or_lock(self.handle.get()));
670 }
671
672 unsafe fn set_binding<T, B: BindingCallable<T> + 'static>(
674 &self,
675 binding: B,
676 #[cfg(slint_debug_property)] debug_name: &str,
677 ) {
678 let binding = alloc_binding_holder::<T, B>(binding);
679 #[cfg(slint_debug_property)]
680 unsafe {
681 (*binding).debug_name = debug_name.into();
682 }
683 self.set_binding_impl(binding);
684 }
685
686 fn set_binding_impl(&self, binding: *mut BindingHolder) {
688 let previous_binding_intercepted = self.access(|b| {
689 b.is_some_and(|b| unsafe {
690 (b.vtable.intercept_set_binding)(&*b as *const BindingHolder, binding)
692 })
693 });
694
695 if previous_binding_intercepted {
696 return;
697 }
698
699 self.remove_binding();
700 debug_assert!(Self::has_no_binding_or_lock(binding as *mut ()));
701 debug_assert!(Self::has_no_binding_or_lock(self.handle.get()));
702 let const_sentinel = const_sentinel();
703 let is_constant = self.handle.get() == const_sentinel;
704 unsafe {
705 if is_constant {
706 (*binding).dependencies.set(const_sentinel);
707 } else {
708 DependencyListHead::mem_move(
709 self.handle.as_ptr() as *mut DependencyListHead,
710 (*binding).dependencies.as_ptr() as *mut DependencyListHead,
711 );
712 }
713 }
714 self.handle.set((binding as *mut ()).map_addr(|a| a | BINDING_POINTER_TO_BINDING));
715 if !is_constant {
716 self.mark_dirty(
717 #[cfg(slint_debug_property)]
718 "",
719 );
720 }
721 }
722
723 fn dependencies(&self) -> *mut DependencyListHead {
724 assert!(!self.lock_flag(), "Recursion detected");
725 if Self::is_pointer_to_binding(self.handle.get()) {
726 self.access(|binding| binding.unwrap().dependencies.as_ptr() as *mut DependencyListHead)
727 } else {
728 self.handle.as_ptr() as *mut DependencyListHead
729 }
730 }
731
732 unsafe fn update<T>(&self, value: *mut T) {
735 let binding_ptr = Self::pointer_to_binding(self.handle.get());
736
737 let remove = self.access(|binding| {
738 if let Some(binding) = binding
739 && binding.dirty.get()
740 {
741 let binding_ptr = unsafe { binding_ptr.unwrap_unchecked() };
743
744 unsafe { *(*binding_ptr).dep_nodes.get() = Default::default() };
746 let r = unsafe {
747 current_binding_storage::set(Some(binding_ptr), || {
748 ((*binding_ptr).vtable.evaluate)(binding_ptr, value as *mut c_void)
749 })
750 };
751 unsafe { (*binding_ptr).dirty.set(false) };
752 if r == BindingResult::RemoveBinding {
753 return true;
754 }
755 }
756 false
757 });
758 if remove {
759 self.remove_binding()
760 }
761 }
762
763 fn register_as_dependency_to_current_binding(
765 self: Pin<&Self>,
766 #[cfg(slint_debug_property)] debug_name: &str,
767 ) {
768 current_binding_storage::with(|cur_binding| {
769 if let Some(cur_binding) = cur_binding {
770 let dependencies = self.dependencies();
771 if unsafe { *(dependencies as *mut *mut ()) } != const_sentinel() {
772 BindingHolder::register_self_as_dependency(
773 cur_binding,
774 dependencies,
775 #[cfg(slint_debug_property)]
776 debug_name,
777 );
778 }
779 }
780 });
781 }
782
783 fn mark_dirty(&self, #[cfg(slint_debug_property)] debug_name: &str) {
784 #[cfg(not(slint_debug_property))]
785 let debug_name = "";
786 unsafe {
787 let dependencies = self.dependencies();
788 assert!(
789 *(dependencies as *mut *mut ()) != const_sentinel(),
790 "Constant property being changed {debug_name}"
791 );
792 mark_dependencies_dirty(dependencies)
793 };
794 }
795
796 fn set_constant(&self) {
797 unsafe {
798 let dependencies = self.dependencies();
799 let const_sentinel = const_sentinel();
800 if *(dependencies as *mut *mut ()) != const_sentinel {
801 DependencyListHead::drop(dependencies);
802 *(dependencies as *mut *mut ()) = const_sentinel;
803 }
804 }
805 }
806
807 fn is_constant(&self) -> bool {
808 let dependencies = self.dependencies();
809 unsafe { *(dependencies as *mut *mut ()) == const_sentinel() }
811 }
812}
813
814impl Drop for PropertyHandle {
815 fn drop(&mut self) {
816 self.remove_binding();
817 debug_assert!(Self::has_no_binding_or_lock(self.handle.get()));
818 if self.handle.get() != const_sentinel() {
819 unsafe {
820 DependencyListHead::drop(self.handle.as_ptr() as *mut _);
821 }
822 }
823 }
824}
825
826unsafe fn mark_dependencies_dirty(dependencies: *mut DependencyListHead) {
828 unsafe {
829 debug_assert!(*(dependencies as *mut *mut ()) != const_sentinel());
830 DependencyListHead::for_each(&*dependencies, |binding| {
831 let binding: &BindingHolder = &**binding;
832 let was_dirty = binding.dirty.replace(true);
833 (binding.vtable.mark_dirty)(binding as *const BindingHolder, was_dirty);
834
835 assert!(
836 binding.dependencies.get() != const_sentinel(),
837 "Const property marked as dirty"
838 );
839
840 if !was_dirty {
841 mark_dependencies_dirty(binding.dependencies.as_ptr() as *mut DependencyListHead)
842 }
843 });
844 }
845}
846
847pub trait Binding<T> {
849 fn evaluate(&self, old_value: &T) -> T;
851}
852
853impl<T, F: Fn() -> T> Binding<T> for F {
854 fn evaluate(&self, _value: &T) -> T {
855 self()
856 }
857}
858
859#[repr(C)]
868pub struct Property<T> {
869 handle: PropertyHandle,
871 value: UnsafeCell<T>,
873 pinned: PhantomPinned,
874 #[cfg(slint_debug_property)]
878 pub debug_name: RefCell<alloc::string::String>,
879}
880
881impl<T: core::fmt::Debug + Clone> core::fmt::Debug for Property<T> {
882 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
883 #[cfg(slint_debug_property)]
884 write!(f, "[{}]=", self.debug_name.borrow())?;
885 write!(
886 f,
887 "Property({:?}{})",
888 self.get_internal(),
889 if self.is_dirty() { " (dirty)" } else { "" }
890 )
891 }
892}
893
894impl<T: Default> Default for Property<T> {
895 fn default() -> Self {
896 Self {
897 handle: Default::default(),
898 value: Default::default(),
899 pinned: PhantomPinned,
900 #[cfg(slint_debug_property)]
901 debug_name: Default::default(),
902 }
903 }
904}
905
906impl<T: Clone> Property<T> {
907 pub fn new(value: T) -> Self {
909 Self {
910 handle: Default::default(),
911 value: UnsafeCell::new(value),
912 pinned: PhantomPinned,
913 #[cfg(slint_debug_property)]
914 debug_name: Default::default(),
915 }
916 }
917
918 pub fn new_named(value: T, _name: &'static str) -> Self {
920 Self {
921 handle: Default::default(),
922 value: UnsafeCell::new(value),
923 pinned: PhantomPinned,
924 #[cfg(slint_debug_property)]
925 debug_name: RefCell::new(_name.into()),
926 }
927 }
928
929 pub fn get(self: Pin<&Self>) -> T {
939 unsafe { self.handle.update(self.value.get()) };
940 let handle = unsafe { Pin::new_unchecked(&self.handle) };
941 handle.register_as_dependency_to_current_binding(
942 #[cfg(slint_debug_property)]
943 self.debug_name.borrow().as_str(),
944 );
945 self.get_internal()
946 }
947
948 pub fn get_untracked(self: Pin<&Self>) -> T {
970 unsafe { self.handle.update(self.value.get()) };
971 self.get_internal()
972 }
973
974 pub fn register_as_dependency(self: Pin<&Self>) {
985 let handle = unsafe { Pin::new_unchecked(&self.handle) };
986 handle.register_as_dependency_to_current_binding(
987 #[cfg(slint_debug_property)]
988 self.debug_name.borrow().as_str(),
989 );
990 }
991
992 pub fn get_internal(&self) -> T {
994 self.handle.access(|_| {
995 unsafe { (*self.value.get()).clone() }
997 })
998 }
999
1000 pub fn set(&self, t: T)
1006 where
1007 T: PartialEq,
1008 {
1009 let previous_binding_intercepted = self.handle.access(|b| {
1010 b.is_some_and(|b| unsafe {
1011 (b.vtable.intercept_set)(
1013 &*b as *const BindingHolder,
1014 (&t as *const T).cast::<c_void>(),
1015 )
1016 })
1017 });
1018 if !previous_binding_intercepted {
1019 self.handle.remove_binding();
1020 }
1021
1022 let has_value_changed = self.handle.access(|_| unsafe {
1024 *self.value.get() != t && {
1025 *self.value.get() = t;
1026 true
1027 }
1028 });
1029 if has_value_changed {
1030 self.handle.mark_dirty(
1031 #[cfg(slint_debug_property)]
1032 self.debug_name.borrow().as_str(),
1033 );
1034 }
1035 }
1036
1037 pub fn set_binding(&self, binding: impl Binding<T> + 'static) {
1064 unsafe {
1066 self.handle.set_binding(
1067 move |val: &mut T| {
1068 *val = binding.evaluate(val);
1069 BindingResult::KeepBinding
1070 },
1071 #[cfg(slint_debug_property)]
1072 self.debug_name.borrow().as_str(),
1073 )
1074 }
1075 self.handle.mark_dirty(
1076 #[cfg(slint_debug_property)]
1077 self.debug_name.borrow().as_str(),
1078 );
1079 }
1080
1081 pub fn has_binding(&self) -> bool {
1083 PropertyHandle::pointer_to_binding(self.handle.handle.get()).is_some()
1084 }
1085
1086 pub fn is_dirty(&self) -> bool {
1089 self.handle.access(|binding| binding.is_some_and(|b| b.dirty.get()))
1090 }
1091
1092 pub fn mark_dirty(&self) {
1095 self.handle.mark_dirty(
1096 #[cfg(slint_debug_property)]
1097 self.debug_name.borrow().as_str(),
1098 )
1099 }
1100
1101 pub fn set_constant(&self) {
1103 self.handle.set_constant();
1104 }
1105
1106 pub fn is_constant(&self) -> bool {
1108 self.handle.is_constant()
1109 }
1110}
1111
1112#[test]
1113fn properties_simple_test() {
1114 use pin_weak::rc::PinWeak;
1115 use std::rc::Rc;
1116 fn g(prop: &Property<i32>) -> i32 {
1117 unsafe { Pin::new_unchecked(prop).get() }
1118 }
1119
1120 #[derive(Default)]
1121 struct Component {
1122 width: Property<i32>,
1123 height: Property<i32>,
1124 area: Property<i32>,
1125 }
1126
1127 let compo = Rc::pin(Component::default());
1128 let w = PinWeak::downgrade(compo.clone());
1129 compo.area.set_binding(move || {
1130 let compo = w.upgrade().unwrap();
1131 g(&compo.width) * g(&compo.height)
1132 });
1133 compo.width.set(4);
1134 compo.height.set(8);
1135 assert_eq!(g(&compo.width), 4);
1136 assert_eq!(g(&compo.height), 8);
1137 assert_eq!(g(&compo.area), 4 * 8);
1138
1139 let w = PinWeak::downgrade(compo.clone());
1140 compo.width.set_binding(move || {
1141 let compo = w.upgrade().unwrap();
1142 g(&compo.height) * 2
1143 });
1144 assert_eq!(g(&compo.width), 8 * 2);
1145 assert_eq!(g(&compo.height), 8);
1146 assert_eq!(g(&compo.area), 8 * 8 * 2);
1147}
1148
1149mod change_tracker;
1150mod two_way_binding;
1151pub use change_tracker::*;
1152mod properties_animations;
1153pub use properties_animations::*;
1154
1155#[derive(Copy, Clone, Debug, PartialEq, Default)]
1158#[repr(C)]
1159pub struct StateInfo {
1160 pub current_state: i32,
1162 pub previous_state: i32,
1164 pub change_time: crate::animations::Instant,
1166}
1167
1168struct StateInfoBinding<F> {
1169 dirty_time: Cell<Option<crate::animations::Instant>>,
1170 binding: F,
1171}
1172
1173unsafe impl<F: Fn() -> i32> crate::properties::BindingCallable<StateInfo> for StateInfoBinding<F> {
1174 fn evaluate(self: Pin<&Self>, value: &mut StateInfo) -> BindingResult {
1175 let new_state = (self.binding)();
1176 let timestamp = self.dirty_time.take();
1177 if new_state != value.current_state {
1178 value.previous_state = value.current_state;
1179 value.change_time = timestamp.unwrap_or_else(crate::animations::current_tick);
1180 value.current_state = new_state;
1181 }
1182 BindingResult::KeepBinding
1183 }
1184
1185 fn mark_dirty(self: Pin<&Self>) {
1186 if self.dirty_time.get().is_none() {
1187 self.dirty_time.set(Some(crate::animations::current_tick()))
1188 }
1189 }
1190}
1191
1192pub fn set_state_binding(property: Pin<&Property<StateInfo>>, binding: impl Fn() -> i32 + 'static) {
1194 let bind_callable = StateInfoBinding { dirty_time: Cell::new(None), binding };
1195 unsafe {
1197 property.handle.set_binding(
1198 bind_callable,
1199 #[cfg(slint_debug_property)]
1200 property.debug_name.borrow().as_str(),
1201 )
1202 }
1203}
1204
1205#[doc(hidden)]
1206pub trait PropertyDirtyHandler {
1207 fn notify(self: Pin<&Self>);
1208}
1209
1210impl PropertyDirtyHandler for () {
1211 fn notify(self: Pin<&Self>) {}
1212}
1213
1214impl<F: Fn()> PropertyDirtyHandler for F {
1215 fn notify(self: Pin<&Self>) {
1216 (self.get_ref())()
1217 }
1218}
1219
1220pub struct PropertyTracker<const NEEDS_SET_DIRTY: bool = false, DirtyHandler = ()> {
1229 holder: BindingHolder<DirtyHandler>,
1230}
1231
1232impl<const NEEDS_SET_DIRTY: bool> Default for PropertyTracker<NEEDS_SET_DIRTY, ()> {
1233 fn default() -> Self {
1234 static VT: &BindingVTable = &BindingVTable {
1235 drop: |_| (),
1236 evaluate: |_, _| BindingResult::KeepBinding,
1237 mark_dirty: |_, _| (),
1238 intercept_set: |_, _| false,
1239 intercept_set_binding: |_, _| false,
1240 };
1241
1242 let holder = BindingHolder {
1243 dependencies: Cell::new(core::ptr::null_mut()),
1244 dep_nodes: Default::default(),
1245 vtable: VT,
1246 dirty: Cell::new(true), is_two_way_binding: false,
1248 pinned: PhantomPinned,
1249 binding: (),
1250 #[cfg(slint_debug_property)]
1251 debug_name: "<PropertyTracker<()>>".into(),
1252 };
1253 Self { holder }
1254 }
1255}
1256
1257impl<const NEEDS_SET_DIRTY: bool, DirtyHandler> Drop
1258 for PropertyTracker<NEEDS_SET_DIRTY, DirtyHandler>
1259{
1260 fn drop(&mut self) {
1261 unsafe {
1262 DependencyListHead::drop(self.holder.dependencies.as_ptr() as *mut DependencyListHead);
1263 }
1264 }
1265}
1266
1267impl<const NEEDS_SET_DIRTY: bool, DirtyHandler: PropertyDirtyHandler>
1268 PropertyTracker<NEEDS_SET_DIRTY, DirtyHandler>
1269{
1270 #[cfg(slint_debug_property)]
1271 pub fn set_debug_name(&mut self, debug_name: alloc::string::String) {
1273 self.holder.debug_name = debug_name;
1274 }
1275
1276 pub fn register_as_dependency_to_current_binding(self: Pin<&Self>) {
1278 if !NEEDS_SET_DIRTY && unsafe { (*self.holder.dep_nodes.get()).is_empty() } {
1280 return;
1281 }
1282 current_binding_storage::with(|cur_binding| {
1283 if let Some(cur_binding) = cur_binding {
1284 debug_assert!(self.holder.dependencies.get() != const_sentinel());
1285 BindingHolder::register_self_as_dependency(
1286 cur_binding,
1287 self.holder.dependencies.as_ptr() as *mut DependencyListHead,
1288 #[cfg(slint_debug_property)]
1289 &self.holder.debug_name,
1290 );
1291 }
1292 });
1293 }
1294
1295 pub fn is_dirty(&self) -> bool {
1298 self.holder.dirty.get()
1299 }
1300
1301 pub fn evaluate<R>(self: Pin<&Self>, f: impl FnOnce() -> R) -> R {
1305 let r = self.evaluate_as_dependency_root(f);
1306 self.register_as_dependency_to_current_binding();
1307 r
1308 }
1309
1310 pub fn evaluate_as_dependency_root<R>(self: Pin<&Self>, f: impl FnOnce() -> R) -> R {
1314 unsafe { *self.holder.dep_nodes.get() = Default::default() };
1316
1317 let holder_ptr = &raw const self.holder as *const BindingHolder;
1318 let r = current_binding_storage::set(Some(holder_ptr), f);
1319 self.holder.dirty.set(false);
1320 r
1321 }
1322
1323 pub fn evaluate_if_dirty<R>(self: Pin<&Self>, f: impl FnOnce() -> R) -> Option<R> {
1326 let r = self.is_dirty().then(|| self.evaluate_as_dependency_root(f));
1327 self.register_as_dependency_to_current_binding();
1328 r
1329 }
1330
1331 pub fn new_with_dirty_handler(handler: DirtyHandler) -> Self {
1342 unsafe fn mark_dirty<B: PropertyDirtyHandler>(
1344 _self: *const BindingHolder,
1345 was_dirty: bool,
1346 ) {
1347 if !was_dirty {
1348 unsafe {
1349 Pin::new_unchecked(&(*(_self as *const BindingHolder<B>)).binding).notify()
1350 };
1351 }
1352 }
1353
1354 trait HasBindingVTable {
1355 const VT: &'static BindingVTable;
1356 }
1357 impl<B: PropertyDirtyHandler> HasBindingVTable for B {
1358 const VT: &'static BindingVTable = &BindingVTable {
1359 drop: |_| (),
1360 evaluate: |_, _| BindingResult::KeepBinding,
1361 mark_dirty: mark_dirty::<B>,
1362 intercept_set: |_, _| false,
1363 intercept_set_binding: |_, _| false,
1364 };
1365 }
1366
1367 let holder = BindingHolder {
1368 dependencies: Cell::new(core::ptr::null_mut()),
1369 dep_nodes: Default::default(),
1370 vtable: <DirtyHandler as HasBindingVTable>::VT,
1371 dirty: Cell::new(true), is_two_way_binding: false,
1373 pinned: PhantomPinned,
1374 binding: handler,
1375 #[cfg(slint_debug_property)]
1376 debug_name: "<PropertyTracker>".into(),
1377 };
1378 Self { holder }
1379 }
1380}
1381
1382impl<DirtyHandler> PropertyTracker<true, DirtyHandler> {
1383 pub fn set_dirty(&self) {
1385 self.holder.dirty.set(true);
1386 unsafe { mark_dependencies_dirty(self.holder.dependencies.as_ptr() as *mut _) };
1387 }
1388}
1389
1390#[test]
1391fn test_property_handler_binding() {
1392 use core::ptr::without_provenance_mut;
1393 assert_eq!(
1394 PropertyHandle::has_no_binding_or_lock(without_provenance_mut(BINDING_BORROWED)),
1395 false
1396 );
1397 assert_eq!(
1398 PropertyHandle::has_no_binding_or_lock(without_provenance_mut(BINDING_POINTER_TO_BINDING)),
1399 false
1400 );
1401 assert_eq!(
1402 PropertyHandle::has_no_binding_or_lock(without_provenance_mut(
1403 BINDING_BORROWED | BINDING_POINTER_TO_BINDING
1404 )),
1405 false
1406 );
1407 assert_eq!(PropertyHandle::has_no_binding_or_lock(core::ptr::null_mut()), true);
1408}
1409
1410#[test]
1411fn test_property_listener_scope() {
1412 let scope = Box::pin(PropertyTracker::default());
1413 let prop1 = Box::pin(Property::new(42));
1414 assert!(scope.is_dirty()); let r = scope.as_ref().evaluate(|| prop1.as_ref().get());
1417 assert_eq!(r, 42);
1418 assert!(!scope.is_dirty()); prop1.as_ref().set(88);
1420 assert!(scope.is_dirty()); let r = scope.as_ref().evaluate(|| prop1.as_ref().get() + 1);
1422 assert_eq!(r, 89);
1423 assert!(!scope.is_dirty());
1424 let r = scope.as_ref().evaluate(|| 12);
1425 assert_eq!(r, 12);
1426 assert!(!scope.is_dirty());
1427 prop1.as_ref().set(1);
1428 assert!(!scope.is_dirty());
1429 scope.as_ref().evaluate_if_dirty(|| panic!("should not be dirty"));
1430 scope.set_dirty();
1431 let mut ok = false;
1432 scope.as_ref().evaluate_if_dirty(|| ok = true);
1433 assert!(ok);
1434}
1435
1436#[test]
1437fn test_nested_property_trackers() {
1438 let tracker1 = Box::pin(<PropertyTracker>::default());
1439 let tracker2 = Box::pin(<PropertyTracker>::default());
1440 let prop = Box::pin(Property::new(42));
1441
1442 let r = tracker1.as_ref().evaluate(|| tracker2.as_ref().evaluate(|| prop.as_ref().get()));
1443 assert_eq!(r, 42);
1444
1445 prop.as_ref().set(1);
1446 assert!(tracker2.as_ref().is_dirty());
1447 assert!(tracker1.as_ref().is_dirty());
1448
1449 let r = tracker1
1450 .as_ref()
1451 .evaluate(|| tracker2.as_ref().evaluate_as_dependency_root(|| prop.as_ref().get()));
1452 assert_eq!(r, 1);
1453 prop.as_ref().set(100);
1454 assert!(tracker2.as_ref().is_dirty());
1455 assert!(!tracker1.as_ref().is_dirty());
1456}
1457
1458#[test]
1459fn test_property_dirty_handler() {
1460 let call_flag = std::rc::Rc::new(Cell::new(false));
1461 let tracker = Box::pin(PropertyTracker::<false, _>::new_with_dirty_handler({
1462 let call_flag = call_flag.clone();
1463 move || {
1464 (*call_flag).set(true);
1465 }
1466 }));
1467 let prop = Box::pin(Property::new(42));
1468
1469 let r = tracker.as_ref().evaluate(|| prop.as_ref().get());
1470
1471 assert_eq!(r, 42);
1472 assert!(!tracker.as_ref().is_dirty());
1473 assert!(!call_flag.get());
1474
1475 prop.as_ref().set(100);
1476 assert!(tracker.as_ref().is_dirty());
1477 assert!(call_flag.get());
1478
1479 call_flag.set(false);
1482 prop.as_ref().set(101);
1483 assert!(tracker.as_ref().is_dirty());
1484 assert!(!call_flag.get());
1485}
1486
1487#[test]
1488fn test_property_tracker_drop() {
1489 let outer_tracker = Box::pin(<PropertyTracker>::default());
1490 let inner_tracker = Box::pin(<PropertyTracker>::default());
1491 let prop = Box::pin(Property::new(42));
1492
1493 let r =
1494 outer_tracker.as_ref().evaluate(|| inner_tracker.as_ref().evaluate(|| prop.as_ref().get()));
1495 assert_eq!(r, 42);
1496
1497 drop(inner_tracker);
1498 prop.as_ref().set(200); }
1500
1501#[test]
1502fn test_nested_property_tracker_dirty() {
1503 let outer_tracker = Box::pin(PropertyTracker::<true, ()>::default());
1504 let inner_tracker = Box::pin(PropertyTracker::<true, ()>::default());
1505 let prop = Box::pin(Property::new(42));
1506
1507 let r =
1508 outer_tracker.as_ref().evaluate(|| inner_tracker.as_ref().evaluate(|| prop.as_ref().get()));
1509 assert_eq!(r, 42);
1510
1511 assert!(!outer_tracker.is_dirty());
1512 assert!(!inner_tracker.is_dirty());
1513
1514 inner_tracker.as_ref().set_dirty();
1517 assert!(outer_tracker.is_dirty());
1518}
1519
1520#[test]
1521#[allow(clippy::redundant_closure)]
1522fn test_nested_property_tracker_evaluate_if_dirty() {
1523 let outer_tracker = Box::pin(<PropertyTracker>::default());
1524 let inner_tracker = Box::pin(<PropertyTracker>::default());
1525 let prop = Box::pin(Property::new(42));
1526
1527 let mut cache = 0;
1528 let mut cache_or_evaluate = || {
1529 if let Some(x) = inner_tracker.as_ref().evaluate_if_dirty(|| prop.as_ref().get() + 1) {
1530 cache = x;
1531 }
1532 cache
1533 };
1534 let r = outer_tracker.as_ref().evaluate(|| cache_or_evaluate());
1535 assert_eq!(r, 43);
1536 assert!(!outer_tracker.is_dirty());
1537 assert!(!inner_tracker.is_dirty());
1538 prop.as_ref().set(11);
1539 assert!(outer_tracker.is_dirty());
1540 assert!(inner_tracker.is_dirty());
1541 let r = outer_tracker.as_ref().evaluate(|| cache_or_evaluate());
1542 assert_eq!(r, 12);
1543}
1544
1545#[cfg(feature = "ffi")]
1546pub(crate) mod ffi;