// Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 /*! Property binding engine. The current implementation uses lots of heap allocation but that can be optimized later using thin dst container, and intrusive linked list */ // cSpell: ignore rustflags #![allow(unsafe_code)] #![warn(missing_docs)] /// A singly linked list whose nodes are pinned in raw allocations. /// Nodes are also referenced through external raw pointers in the /// dependency tracking system. mod single_linked_list_pin { #![allow(unsafe_code)] use core::pin::Pin; use core::ptr::NonNull; type NodePtr = Option>>; struct SingleLinkedListPinNode { next: NodePtr, value: T, } pub struct SingleLinkedListPinHead(NodePtr); impl Default for SingleLinkedListPinHead { fn default() -> Self { Self(None) } } impl Drop for SingleLinkedListPinHead { fn drop(&mut self) { // Iterative drop to avoid stack overflow on long lists. let mut cur = self.0.take(); while let Some(node) = cur { // Safety: we own this node. // drop_in_place keeps the value at its pinned address. unsafe { cur = (*node.as_ptr()).next; core::ptr::drop_in_place(&raw mut (*node.as_ptr()).value); alloc::alloc::dealloc( node.as_ptr().cast(), core::alloc::Layout::new::>(), ); } } } } impl SingleLinkedListPinHead { pub fn push_front(&mut self, value: T) -> Pin<&T> { let node = SingleLinkedListPinNode { next: self.0.take(), value }; // Safety: raw allocation, written once and never moved let ptr = unsafe { let layout = core::alloc::Layout::new::>(); let mem = alloc::alloc::alloc(layout) as *mut SingleLinkedListPinNode; assert!(!mem.is_null(), "allocation failed"); core::ptr::write(mem, node); NonNull::new_unchecked(mem) }; self.0 = Some(ptr); // Safety: the value is pinned because we never move it out of the allocation unsafe { Pin::new_unchecked(&(*ptr.as_ptr()).value) } } #[allow(unused)] pub fn iter(&self) -> impl Iterator> { struct I<'a, T>(&'a NodePtr); impl<'a, T> Iterator for I<'a, T> { type Item = Pin<&'a T>; fn next(&mut self) -> Option { if let Some(node) = self.0 { // Safety: node is a valid allocation we own let r = unsafe { Pin::new_unchecked(&(*node.as_ptr()).value) }; self.0 = unsafe { &(*node.as_ptr()).next }; Some(r) } else { None } } } I(&self.0) } /// Returns true if the list is empty pub fn is_empty(&self) -> bool { self.0.is_none() } } #[test] fn test_list() { let mut head = SingleLinkedListPinHead::default(); head.push_front(1); head.push_front(2); head.push_front(3); assert_eq!( head.iter().map(|x: Pin<&i32>| *x.get_ref()).collect::>(), std::vec![3, 2, 1] ); } #[test] fn big_list() { // should not stack overflow let mut head = SingleLinkedListPinHead::default(); for x in 0..100000 { head.push_front(x); } } } pub(crate) mod dependency_tracker { //! This module contains an implementation of a double linked list that can be used //! to track dependency, such that when a node is dropped, the nodes are automatically //! removed from the list. //! This is unsafe to use for various reason, so it is kept internal. use core::cell::Cell; use core::pin::Pin; #[repr(transparent)] pub struct DependencyListHead(Cell<*const DependencyNode>); impl Default for DependencyListHead { fn default() -> Self { Self(Cell::new(core::ptr::null())) } } impl Drop for DependencyListHead { fn drop(&mut self) { unsafe { DependencyListHead::drop(self as *mut Self) }; } } impl DependencyListHead { pub unsafe fn mem_move(from: *mut Self, to: *mut Self) { unsafe { (*to).0.set((*from).0.get()); if let Some(next) = (*from).0.get().as_ref() { debug_assert_eq!(from as *const _, next.prev.get() as *const _); next.debug_assert_valid(); next.prev.set(to as *const _); next.debug_assert_valid(); } } } /// Swap two list head pub fn swap(from: Pin<&Self>, to: Pin<&Self>) { Cell::swap(&from.0, &to.0); unsafe { if let Some(n) = from.0.get().as_ref() { debug_assert_eq!(n.prev.get() as *const _, &to.0 as *const _); n.prev.set(&from.0 as *const _); n.debug_assert_valid(); } if let Some(n) = to.0.get().as_ref() { debug_assert_eq!(n.prev.get() as *const _, &from.0 as *const _); n.prev.set(&to.0 as *const _); n.debug_assert_valid(); } } } /// Return true is the list is empty pub fn is_empty(&self) -> bool { self.0.get().is_null() } pub unsafe fn drop(_self: *mut Self) { unsafe { if let Some(next) = (*_self).0.get().as_ref() { #[cfg(not(miri))] debug_assert_eq!(_self as *const _, next.prev.get() as *const _); next.debug_assert_valid(); next.prev.set(core::ptr::null()); next.debug_assert_valid(); } } } pub fn append(&self, node: Pin<&DependencyNode>) { unsafe { node.remove(); node.debug_assert_valid(); let old = self.0.get(); if let Some(x) = old.as_ref() { x.debug_assert_valid(); } self.0.set(node.get_ref() as *const DependencyNode<_>); node.next.set(old); node.prev.set(&self.0 as *const _); if let Some(old) = old.as_ref() { old.prev.set((&node.next) as *const _); old.debug_assert_valid(); } node.debug_assert_valid(); } } pub fn for_each(&self, mut f: impl FnMut(&T)) { unsafe { let mut next = self.0.get(); while let Some(node) = next.as_ref() { node.debug_assert_valid(); next = node.next.get(); f(&node.binding); } } } /// Returns the first node of the list, if any pub fn take_head(&self) -> Option where T: Copy, { unsafe { if let Some(node) = self.0.get().as_ref() { node.debug_assert_valid(); node.remove(); Some(node.binding) } else { None } } } } /// The node is owned by the binding; so the binding is always valid /// The next and pref pub struct DependencyNode { next: Cell<*const DependencyNode>, /// This is either null, or a pointer to a pointer to ourself prev: Cell<*const Cell<*const DependencyNode>>, binding: T, } impl DependencyNode { pub fn new(binding: T) -> Self { Self { next: Cell::new(core::ptr::null()), prev: Cell::new(core::ptr::null()), binding } } /// Assert that the invariant of `next` and `prev` are met. pub fn debug_assert_valid(&self) { // Under Miri with Tree Borrows, reading through prev/next creates // foreign accesses that conflict with active protectors. #[cfg(not(miri))] unsafe { debug_assert!( self.prev.get().is_null() || core::ptr::eq((*self.prev.get()).get(), self) ); debug_assert!( self.next.get().is_null() || core::ptr::eq((*self.next.get()).prev.get(), &self.next) ); // infinite loop? debug_assert_ne!(self.next.get(), self as *const DependencyNode); debug_assert_ne!( self.prev.get(), (&self.next) as *const Cell<*const DependencyNode> ); } } pub fn remove(&self) { self.debug_assert_valid(); unsafe { if let Some(prev) = self.prev.get().as_ref() { prev.set(self.next.get()); } if let Some(next) = self.next.get().as_ref() { next.debug_assert_valid(); next.prev.set(self.prev.get()); next.debug_assert_valid(); } } self.prev.set(core::ptr::null()); self.next.set(core::ptr::null()); } } impl Drop for DependencyNode { fn drop(&mut self) { self.remove(); } } } type DependencyListHead = dependency_tracker::DependencyListHead<*const BindingHolder>; type DependencyNode = dependency_tracker::DependencyNode<*const BindingHolder>; use alloc::boxed::Box; use core::any::Any; use core::cell::{Cell, RefCell, UnsafeCell}; use core::ffi::c_void; use core::marker::PhantomPinned; use core::pin::Pin; /// if a DependencyListHead points to that value, it is because the property is actually /// constant and cannot have dependencies static CONSTANT_PROPERTY_SENTINEL: u32 = 0; #[inline(always)] fn const_sentinel() -> *mut () { (&CONSTANT_PROPERTY_SENTINEL) as *const u32 as *mut () } /// The return value of a binding #[derive(Copy, Clone, Debug, Eq, PartialEq)] enum BindingResult { /// The binding is a normal binding, and we keep it to re-evaluate it once it is dirty KeepBinding, /// The value of the property is now constant after the binding was evaluated, so /// the binding can be removed. RemoveBinding, } struct BindingVTable { drop: unsafe fn(_self: *mut BindingHolder), evaluate: unsafe fn(_self: *const BindingHolder, value: *mut c_void) -> BindingResult, mark_dirty: unsafe fn(_self: *const BindingHolder, was_dirty: bool), intercept_set: unsafe fn(_self: *const BindingHolder, value: *const c_void) -> bool, intercept_set_binding: unsafe fn(_self: *const BindingHolder, new_binding: *mut BindingHolder) -> bool, velocity: unsafe fn(_self: *const BindingHolder) -> Option, common_property: unsafe fn(_self: *const BindingHolder) -> Option<*const dyn Any>, } /// A binding trait object can be used to dynamically produces values for a property. trait BindingCallable { /// This function is called by the property to evaluate the binding and produce a new value. The /// previous property value is provided in the value parameter. fn evaluate(self: Pin<&Self>, value: &mut T) -> BindingResult; /// This function is used to notify the binding that one of the dependencies was changed /// and therefore this binding may evaluate to a different value, too. fn mark_dirty(self: Pin<&Self>) {} /// Allow the binding to intercept what happens when the value is set. /// The default implementation returns false, meaning the binding will simply be removed and /// the property will get the new value. /// When returning true, the call was intercepted and the binding will not be removed, /// but the property will still have that value fn intercept_set(self: Pin<&Self>, _value: &T) -> bool { false } /// Allow the binding to intercept what happens when the value is set. /// The default implementation returns false, meaning the binding will simply be removed. /// When returning true, the call was intercepted and the binding will not be removed. unsafe fn intercept_set_binding(self: Pin<&Self>, _new_binding: *mut BindingHolder) -> bool { false } /// Returns the current velocity in the property's units per second so a spring retarget can /// maintain velocity. Non spring bindings return None fn velocity(self: Pin<&Self>) -> Option { None } /// For a two-way binding, returns its common property: a `Pin>>`, /// or a `MappedCommonProperty` when the binding maps to it. fn common_property(self: Pin<&Self>) -> Option<&dyn Any> { None } } impl BindingResult> BindingCallable for F { fn evaluate(self: Pin<&Self>, value: &mut T) -> BindingResult { self(value) } } /// Stores a raw pointer to the binding currently being evaluated. mod current_binding_storage { use super::BindingHolder; use core::cell::Cell; #[cfg(feature = "std")] std::thread_local! { static CURRENT_BINDING: Cell<*const BindingHolder> = const { Cell::new(core::ptr::null()) }; } #[cfg(feature = "std")] pub(super) fn set(value: Option<*const BindingHolder>, f: impl FnOnce() -> T) -> T { CURRENT_BINDING.with(|cell| { let old = cell.replace(value.unwrap_or(core::ptr::null())); let res = f(); cell.set(old); res }) } #[cfg(feature = "std")] pub(super) fn with(f: impl FnOnce(Option<*const BindingHolder>) -> T) -> T { CURRENT_BINDING.with(|cell| { let ptr = cell.get(); f(if ptr.is_null() { None } else { Some(ptr) }) }) } #[cfg(all(not(feature = "std"), feature = "unsafe-single-threaded"))] static CURRENT_BINDING: ScopedRawPtr = ScopedRawPtr(Cell::new(core::ptr::null())); #[cfg(all(not(feature = "std"), feature = "unsafe-single-threaded"))] struct ScopedRawPtr(Cell<*const BindingHolder>); // Safety: the unsafe_single_threaded feature means only one thread accesses this #[cfg(all(not(feature = "std"), feature = "unsafe-single-threaded"))] unsafe impl Send for ScopedRawPtr {} #[cfg(all(not(feature = "std"), feature = "unsafe-single-threaded"))] unsafe impl Sync for ScopedRawPtr {} #[cfg(all(not(feature = "std"), feature = "unsafe-single-threaded"))] pub(super) fn set(value: Option<*const BindingHolder>, f: impl FnOnce() -> T) -> T { let old = CURRENT_BINDING.0.replace(value.unwrap_or(core::ptr::null())); let res = f(); CURRENT_BINDING.0.set(old); res } #[cfg(all(not(feature = "std"), feature = "unsafe-single-threaded"))] pub(super) fn with(f: impl FnOnce(Option<*const BindingHolder>) -> T) -> T { let ptr = CURRENT_BINDING.0.get(); f(if ptr.is_null() { None } else { Some(ptr) }) } } /// Evaluate a function without registering any property dependencies. pub fn evaluate_no_tracking(f: impl FnOnce() -> T) -> T { current_binding_storage::set(None, f) } /// Returns true if a binding is currently being evaluated /// so that property accesses register dependencies. pub fn is_currently_tracking() -> bool { current_binding_storage::with(|x| x.is_some()) } /// This structure erase the `B` type with a vtable. #[repr(C)] struct BindingHolder { /// Head of the list of bindings that depend on this binding. dependencies: Cell<*mut ()>, /// Nodes that link this binding into the dependency lists of /// the properties it reads. /// UnsafeCell allows in-place mutation without moving the allocation. dep_nodes: UnsafeCell>, vtable: &'static BindingVTable, /// The binding is dirty and need to be re_evaluated dirty: Cell, pinned: PhantomPinned, #[cfg(slint_debug_property)] pub debug_name: alloc::string::String, binding: B, } impl BindingHolder { /// Registers this binding as a dependency of the given property. fn register_self_as_dependency( self_ptr: *const BindingHolder, property_that_will_notify: *mut DependencyListHead, #[cfg(slint_debug_property)] _other_debug_name: &str, ) { let node = DependencyNode::new(self_ptr); // Safety: self_ptr is valid and pinned unsafe { let dep_nodes = &mut *(*self_ptr).dep_nodes.get(); let node = dep_nodes.push_front(node); DependencyListHead::append(&*property_that_will_notify, node); } } } fn alloc_binding_holder + 'static>(binding: B) -> *mut BindingHolder { /// Safety: _self must be a pointer that comes from a `Box>::into_raw()` unsafe fn binding_drop(_self: *mut BindingHolder) { unsafe { drop(Box::from_raw(_self as *mut BindingHolder)); } } /// Safety: _self must be a pointer to a `BindingHolder` /// and value must be a pointer to T unsafe fn evaluate>( _self: *const BindingHolder, value: *mut c_void, ) -> BindingResult { unsafe { Pin::new_unchecked(&((*(_self as *const BindingHolder)).binding)) .evaluate(&mut *(value as *mut T)) } } /// Safety: _self must be a pointer to a `BindingHolder` unsafe fn mark_dirty>(_self: *const BindingHolder, _: bool) { unsafe { Pin::new_unchecked(&((*(_self as *const BindingHolder)).binding)).mark_dirty() } } /// Safety: _self must be a pointer to a `BindingHolder` unsafe fn intercept_set>( _self: *const BindingHolder, value: *const c_void, ) -> bool { unsafe { Pin::new_unchecked(&((*(_self as *const BindingHolder)).binding)) .intercept_set(&*(value as *const T)) } } unsafe fn intercept_set_binding>( _self: *const BindingHolder, new_binding: *mut BindingHolder, ) -> bool { unsafe { Pin::new_unchecked(&((*(_self as *const BindingHolder)).binding)) .intercept_set_binding(new_binding) } } /// Safety: _self must be a pointer to a `BindingHolder` unsafe fn velocity>(_self: *const BindingHolder) -> Option { unsafe { Pin::new_unchecked(&((*(_self as *const BindingHolder)).binding)).velocity() } } /// Safety: _self must be a pointer to a `BindingHolder` unsafe fn common_property>( _self: *const BindingHolder, ) -> Option<*const dyn Any> { unsafe { Pin::new_unchecked(&((*(_self as *const BindingHolder)).binding)) .common_property() .map(|a| a as *const dyn Any) } } trait HasBindingVTable { const VT: &'static BindingVTable; } impl> HasBindingVTable for B { const VT: &'static BindingVTable = &BindingVTable { drop: binding_drop::, evaluate: evaluate::, mark_dirty: mark_dirty::, intercept_set: intercept_set::, intercept_set_binding: intercept_set_binding::, velocity: velocity::, common_property: common_property::, }; } let holder: BindingHolder = BindingHolder { dependencies: Cell::new(core::ptr::null_mut()), dep_nodes: Default::default(), vtable: >::VT, dirty: Cell::new(true), // starts dirty so it evaluates the property when used pinned: PhantomPinned, #[cfg(slint_debug_property)] debug_name: Default::default(), binding, }; Box::into_raw(Box::new(holder)) as *mut BindingHolder } #[repr(transparent)] #[derive(Default)] struct PropertyHandle { /// Either a pointer to a binding or the head of the dependent-properties list. /// The two least significant bits are flags (the pointer is always aligned). /// Bit 0 (`0b01`): the binding is borrowed. /// Bit 1 (`0b10`): the value is a pointer to a binding. handle: Cell<*mut ()>, } const BINDING_BORROWED: usize = 0b01; const BINDING_POINTER_TO_BINDING: usize = 0b10; const BINDING_POINTER_MASK: usize = !(BINDING_POINTER_TO_BINDING | BINDING_BORROWED); impl core::fmt::Debug for PropertyHandle { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let handle = self.handle.get(); write!( f, "PropertyHandle {{ handle: 0x{:x}, locked: {}, binding: {} }}", handle.addr() & !0b11, self.lock_flag(), PropertyHandle::is_pointer_to_binding(handle) ) } } impl PropertyHandle { /// The lock flag specifies that we can get a reference to the Cell or unsafe cell #[inline] fn lock_flag(&self) -> bool { self.handle.get().addr() & BINDING_BORROWED != 0 } /// Sets the lock_flag. /// Safety: the lock flag must not be unset if there exist references to what's inside the cell unsafe fn set_lock_flag(&self, set: bool) { self.handle.set(if set { self.handle.get().map_addr(|a| a | BINDING_BORROWED) } else { self.handle.get().map_addr(|a| a & !BINDING_BORROWED) }) } #[inline] fn is_pointer_to_binding(handle: *mut ()) -> bool { handle.addr() & BINDING_POINTER_TO_BINDING != 0 } /// Get the pointer **without locking** if the handle points to a pointer otherwise None #[inline] fn pointer_to_binding(handle: *mut ()) -> Option<*mut BindingHolder> { if Self::is_pointer_to_binding(handle) { Some(handle.map_addr(|a| a & BINDING_POINTER_MASK) as *mut BindingHolder) } else { None } } /// The handle is not borrowed to any other binding /// and the handle does not point to another binding #[inline] fn has_no_binding_or_lock(handle: *mut ()) -> bool { handle.addr() & (BINDING_BORROWED | BINDING_POINTER_TO_BINDING) == 0 } /// Access the value. /// Panics if the function try to recursively access the value fn access(&self, f: impl FnOnce(Option>) -> R) -> R { #[cfg(slint_debug_property)] if self.lock_flag() { unsafe { let handle = self.handle.get(); if let Some(binding_pointer) = Self::pointer_to_binding(handle) { let binding = &mut *(binding_pointer); let debug_name = &binding.debug_name; panic!("Recursion detected with property {debug_name}"); } } } assert!(!self.lock_flag(), "Recursion detected"); unsafe { self.set_lock_flag(true); scopeguard::defer! { self.set_lock_flag(false); } let handle = self.handle.get(); let binding = Self::pointer_to_binding(handle).map(|pointer| Pin::new_unchecked(&mut *(pointer))); f(binding) } } /// Transfer the dependency list from the current binding back to the /// handle and return the now-detached binding pointer. The binding is /// **not** dropped; the caller is responsible for its lifetime. /// /// Returns `None` when the handle does not point to a binding. fn detach_binding(&self) -> Option<*mut BindingHolder> { let binding = Self::pointer_to_binding(self.handle.get())?; unsafe { let const_sentinel = const_sentinel(); if (*binding).dependencies.get() == const_sentinel { self.handle.set(const_sentinel); } else { DependencyListHead::mem_move( (*binding).dependencies.as_ptr() as *mut DependencyListHead, self.handle.as_ptr() as *mut DependencyListHead, ); } (*binding).dependencies.set(core::ptr::null_mut()); } Some(binding) } /// Returns the velocity reported by the currently installed binding, if any (see /// `BindingCallable::velocity`). Used to carry velocity over across a retarget. fn current_velocity(&self) -> Option { self.access(|b| { b.and_then(|b| unsafe { // Safety: b is a valid BindingHolder (b.vtable.velocity)(&*b as *const BindingHolder) }) }) } fn remove_binding(&self) { assert!(!self.lock_flag(), "Recursion detected"); if let Some(binding) = self.detach_binding() { unsafe { ((*binding).vtable.drop)(binding); } } debug_assert!(Self::has_no_binding_or_lock(self.handle.get())); } /// Safety: the BindingCallable must be valid for the type of this property unsafe fn set_binding + 'static>( &self, binding: B, #[cfg(slint_debug_property)] debug_name: &str, ) { let binding = alloc_binding_holder::(binding); #[cfg(slint_debug_property)] unsafe { (*binding).debug_name = debug_name.into(); } self.set_binding_impl(binding); } /// Implementation of Self::set_binding. fn set_binding_impl(&self, binding: *mut BindingHolder) { let previous_binding_intercepted = self.access(|b| { b.is_some_and(|b| unsafe { // Safety: b is a BindingHolder (b.vtable.intercept_set_binding)(&*b as *const BindingHolder, binding) }) }); if previous_binding_intercepted { return; } self.remove_binding(); debug_assert!(Self::has_no_binding_or_lock(binding as *mut ())); debug_assert!(Self::has_no_binding_or_lock(self.handle.get())); let const_sentinel = const_sentinel(); let is_constant = self.handle.get() == const_sentinel; unsafe { if is_constant { (*binding).dependencies.set(const_sentinel); } else { DependencyListHead::mem_move( self.handle.as_ptr() as *mut DependencyListHead, (*binding).dependencies.as_ptr() as *mut DependencyListHead, ); } } self.handle.set((binding as *mut ()).map_addr(|a| a | BINDING_POINTER_TO_BINDING)); if !is_constant { self.mark_dirty( #[cfg(slint_debug_property)] "", ); } } fn dependencies(&self) -> *mut DependencyListHead { assert!(!self.lock_flag(), "Recursion detected"); if Self::is_pointer_to_binding(self.handle.get()) { self.access(|binding| binding.unwrap().dependencies.as_ptr() as *mut DependencyListHead) } else { self.handle.as_ptr() as *mut DependencyListHead } } // `value` is the content of the unsafe cell and will be only dereferenced if the // handle is not locked. (Upholding the requirements of UnsafeCell) unsafe fn update(&self, value: *mut T) { let binding_ptr = Self::pointer_to_binding(self.handle.get()); let remove = self.access(|binding| { if let Some(binding) = binding && binding.dirty.get() { // Safety: binding is Some so binding_ptr is too let binding_ptr = unsafe { binding_ptr.unwrap_unchecked() }; // clear all the nodes so that we can start from scratch unsafe { *(*binding_ptr).dep_nodes.get() = Default::default() }; let r = unsafe { current_binding_storage::set(Some(binding_ptr), || { ((*binding_ptr).vtable.evaluate)(binding_ptr, value as *mut c_void) }) }; unsafe { (*binding_ptr).dirty.set(false) }; if r == BindingResult::RemoveBinding { return true; } } false }); if remove { self.remove_binding() } } /// Register this property as a dependency to the current binding being evaluated fn register_as_dependency_to_current_binding( self: Pin<&Self>, #[cfg(slint_debug_property)] debug_name: &str, ) { current_binding_storage::with(|cur_binding| { if let Some(cur_binding) = cur_binding { let dependencies = self.dependencies(); if unsafe { *(dependencies as *mut *mut ()) } != const_sentinel() { BindingHolder::register_self_as_dependency( cur_binding, dependencies, #[cfg(slint_debug_property)] debug_name, ); } } }); } fn mark_dirty(&self, #[cfg(slint_debug_property)] debug_name: &str) { #[cfg(not(slint_debug_property))] let debug_name = ""; unsafe { let dependencies = self.dependencies(); assert!( *(dependencies as *mut *mut ()) != const_sentinel(), "Constant property being changed {debug_name}" ); mark_dependencies_dirty(dependencies) }; } fn set_constant(&self) { unsafe { let dependencies = self.dependencies(); let const_sentinel = const_sentinel(); if *(dependencies as *mut *mut ()) != const_sentinel { DependencyListHead::drop(dependencies); *(dependencies as *mut *mut ()) = const_sentinel; } } } fn is_constant(&self) -> bool { let dependencies = self.dependencies(); // Safety: dependencies is a valid pointer to a DependencyListHead (Cell<*mut ()> internally) unsafe { *(dependencies as *mut *mut ()) == const_sentinel() } } } impl Drop for PropertyHandle { fn drop(&mut self) { self.remove_binding(); debug_assert!(Self::has_no_binding_or_lock(self.handle.get())); if self.handle.get() != const_sentinel() { unsafe { DependencyListHead::drop(self.handle.as_ptr() as *mut _); } } } } /// Safety: the dependency list must be valid and consistent unsafe fn mark_dependencies_dirty(dependencies: *mut DependencyListHead) { unsafe { debug_assert!(*(dependencies as *mut *mut ()) != const_sentinel()); DependencyListHead::for_each(&*dependencies, |binding| { let binding: &BindingHolder = &**binding; let was_dirty = binding.dirty.replace(true); (binding.vtable.mark_dirty)(binding as *const BindingHolder, was_dirty); assert!( binding.dependencies.get() != const_sentinel(), "Const property marked as dirty" ); if !was_dirty { mark_dependencies_dirty(binding.dependencies.as_ptr() as *mut DependencyListHead) } }); } } /// Types that can be set as bindings for a `Property` pub trait Binding { /// Evaluate the binding and return the new value fn evaluate(&self, old_value: &T) -> T; } impl T> Binding for F { fn evaluate(&self, _value: &T) -> T { self() } } /// A Property that allows a binding that tracks changes /// /// Property can have an assigned value, or a binding. /// When a binding is assigned, it is lazily evaluated on demand /// when calling `get()`. /// When accessing another property from a binding evaluation, /// a dependency will be registered, such that when the property /// change, the binding will automatically be updated #[repr(C)] pub struct Property { /// This is usually a pointer, but the least significant bit tells what it is handle: PropertyHandle, /// This is only safe to access when the lock flag is not set on the handle. value: UnsafeCell, pinned: PhantomPinned, /// Enabled only if compiled with `RUSTFLAGS='--cfg slint_debug_property'` /// Note that adding this flag will also tell the rust compiler to set this /// and that this will not work with C++ because of binary incompatibility #[cfg(slint_debug_property)] pub debug_name: RefCell, } impl core::fmt::Debug for Property { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { #[cfg(slint_debug_property)] write!(f, "[{}]=", self.debug_name.borrow())?; write!( f, "Property({:?}{})", self.get_internal(), if self.is_dirty() { " (dirty)" } else { "" } ) } } impl Default for Property { fn default() -> Self { Self { handle: Default::default(), value: Default::default(), pinned: PhantomPinned, #[cfg(slint_debug_property)] debug_name: Default::default(), } } } impl Property { /// Create a new property with this value pub fn new(value: T) -> Self { Self { handle: Default::default(), value: UnsafeCell::new(value), pinned: PhantomPinned, #[cfg(slint_debug_property)] debug_name: Default::default(), } } /// Same as [`Self::new`] but with a 'static string use for debugging only pub fn new_named(value: T, _name: &'static str) -> Self { Self { handle: Default::default(), value: UnsafeCell::new(value), pinned: PhantomPinned, #[cfg(slint_debug_property)] debug_name: RefCell::new(_name.into()), } } /// Get the value of the property /// /// This may evaluate the binding if there is a binding and it is dirty /// /// If the function is called directly or indirectly from a binding evaluation /// of another Property, a dependency will be registered. /// /// Panics if this property is get while evaluating its own binding or /// cloning the value. pub fn get(self: Pin<&Self>) -> T { unsafe { self.handle.update(self.value.get()) }; let handle = unsafe { Pin::new_unchecked(&self.handle) }; handle.register_as_dependency_to_current_binding( #[cfg(slint_debug_property)] self.debug_name.borrow().as_str(), ); self.get_internal() } /// Same as get() but without registering a dependency /// /// This allow to optimize bindings that know that they might not need to /// re_evaluate themselves when the property change or that have registered /// the dependency in another way. /// /// ## Example /// ``` /// use std::rc::Rc; /// use i_slint_core::Property; /// let prop1 = Rc::pin(Property::new(100)); /// let prop2 = Rc::pin(Property::::default()); /// prop2.as_ref().set_binding({ /// let prop1 = prop1.clone(); // in order to move it into the closure. /// move || { prop1.as_ref().get_untracked() + 30 } /// }); /// assert_eq!(prop2.as_ref().get(), 130); /// prop1.set(200); /// // changing prop1 do not affect the prop2 binding because no dependency was registered /// assert_eq!(prop2.as_ref().get(), 130); /// ``` pub fn get_untracked(self: Pin<&Self>) -> T { unsafe { self.handle.update(self.value.get()) }; self.get_internal() } /// Register this property as a dependency of the current tracking scope /// without evaluating any binding. /// Use this when you only need the tracking scope to be notified on /// future changes, not the current value. /// /// Unlike [`Self::get`], this doesn't evaluate a dirty binding, /// so the caller won't be notified about a pending evaluation that /// hasn't run yet. /// Only use this when the property has no binding or when its binding /// is known to be already evaluated. pub fn register_as_dependency(self: Pin<&Self>) { let handle = unsafe { Pin::new_unchecked(&self.handle) }; handle.register_as_dependency_to_current_binding( #[cfg(slint_debug_property)] self.debug_name.borrow().as_str(), ); } /// Get the cached value without registering any dependencies or executing any binding pub fn get_internal(&self) -> T { self.handle.access(|_| { // Safety: PropertyHandle::access ensure that the value is locked unsafe { (*self.value.get()).clone() } }) } /// Change the value of this property /// /// If other properties have binding depending of this property, these properties will /// be marked as dirty. // FIXME pub fn set(self: Pin<&Self>, t: T) { pub fn set(&self, t: T) where T: PartialEq, { let previous_binding_intercepted = self.handle.access(|b| { b.is_some_and(|b| unsafe { // Safety: b is a BindingHolder (b.vtable.intercept_set)( &*b as *const BindingHolder, (&t as *const T).cast::(), ) }) }); if !previous_binding_intercepted { self.handle.remove_binding(); } // Safety: PropertyHandle::access ensure that the value is locked let has_value_changed = self.handle.access(|_| unsafe { *self.value.get() != t && { *self.value.get() = t; true } }); if has_value_changed { self.handle.mark_dirty( #[cfg(slint_debug_property)] self.debug_name.borrow().as_str(), ); } } /// Set a binding to this property. /// /// Bindings are evaluated lazily from calling get, and the return value of the binding /// is the new value. /// /// If other properties have bindings depending of this property, these properties will /// be marked as dirty. /// /// Closures of type `Fn()->T` implements `Binding` and can be used as a binding /// /// ## Example /// ``` /// use std::rc::Rc; /// use i_slint_core::Property; /// let prop1 = Rc::pin(Property::new(100)); /// let prop2 = Rc::pin(Property::::default()); /// prop2.as_ref().set_binding({ /// let prop1 = prop1.clone(); // in order to move it into the closure. /// move || { prop1.as_ref().get() + 30 } /// }); /// assert_eq!(prop2.as_ref().get(), 130); /// prop1.set(200); /// // A change in prop1 forced the binding on prop2 to re_evaluate /// assert_eq!(prop2.as_ref().get(), 230); /// ``` //FIXME pub fn set_binding(self: Pin<&Self>, f: impl Binding + 'static) { pub fn set_binding(&self, binding: impl Binding + 'static) { // Safety: This will make a binding callable for the type T unsafe { self.handle.set_binding( move |val: &mut T| { *val = binding.evaluate(val); BindingResult::KeepBinding }, #[cfg(slint_debug_property)] self.debug_name.borrow().as_str(), ) } self.handle.mark_dirty( #[cfg(slint_debug_property)] self.debug_name.borrow().as_str(), ); } /// Returns true if the property has currently a binding (like an animation, ...), otherwise false pub fn has_binding(&self) -> bool { PropertyHandle::pointer_to_binding(self.handle.handle.get()).is_some() } /// Any of the properties accessed during the last evaluation of the closure called /// from the last call to evaluate is potentially dirty. pub fn is_dirty(&self) -> bool { self.handle.access(|binding| binding.is_some_and(|b| b.dirty.get())) } /// Internal function to mark the property as dirty and notify dependencies, regardless of /// whether the property value has actually changed or not. pub fn mark_dirty(&self) { self.handle.mark_dirty( #[cfg(slint_debug_property)] self.debug_name.borrow().as_str(), ) } /// Mark that this property will never be modified again and that no tracking should be done pub fn set_constant(&self) { self.handle.set_constant(); } /// Returns true if set_constant was called on this property pub fn is_constant(&self) -> bool { self.handle.is_constant() } } #[test] fn properties_simple_test() { use pin_weak::rc::PinWeak; use std::rc::Rc; fn g(prop: &Property) -> i32 { unsafe { Pin::new_unchecked(prop).get() } } #[derive(Default)] struct Component { width: Property, height: Property, area: Property, } let compo = Rc::pin(Component::default()); let w = PinWeak::downgrade(compo.clone()); compo.area.set_binding(move || { let compo = w.upgrade().unwrap(); g(&compo.width) * g(&compo.height) }); compo.width.set(4); compo.height.set(8); assert_eq!(g(&compo.width), 4); assert_eq!(g(&compo.height), 8); assert_eq!(g(&compo.area), 4 * 8); let w = PinWeak::downgrade(compo.clone()); compo.width.set_binding(move || { let compo = w.upgrade().unwrap(); g(&compo.height) * 2 }); assert_eq!(g(&compo.width), 8 * 2); assert_eq!(g(&compo.height), 8); assert_eq!(g(&compo.area), 8 * 8 * 2); } mod change_tracker; mod erased_bindings; mod two_way_binding; pub use change_tracker::*; pub use erased_bindings::*; mod properties_animations; pub use properties_animations::*; /// Value of the state property /// A state is just the current state, but also has information about the previous state and the moment it changed #[derive(Copy, Clone, Debug, PartialEq, Default)] #[repr(C)] pub struct StateInfo { /// The current state value pub current_state: i32, /// The previous state pub previous_state: i32, /// The instant in which the state changed last pub change_time: crate::animations::Instant, } struct StateInfoBinding { dirty_time: Cell>, binding: F, _phantom: core::marker::PhantomData T>, } impl i32, T> crate::properties::BindingCallable for StateInfoBinding where T: Default + From + 'static, StateInfo: TryFrom, { fn evaluate(self: Pin<&Self>, value: &mut T) -> BindingResult { let new_state = (self.binding)(); let timestamp = self.dirty_time.take(); // The conversion only fails on the property's initial value // (`Value::Void` in the interpreter); start from the default then. let mut state_info: StateInfo = core::mem::take(value).try_into().unwrap_or_default(); if new_state != state_info.current_state { state_info.previous_state = state_info.current_state; state_info.change_time = timestamp.unwrap_or_else(crate::animations::current_tick); state_info.current_state = new_state; } *value = T::from(state_info); BindingResult::KeepBinding } fn mark_dirty(self: Pin<&Self>) { if self.dirty_time.get().is_none() { self.dirty_time.set(Some(crate::animations::current_tick())) } } } /// Sets a binding that returns a state index to a property that stores /// state-tracking information. The property type `T` must be convertible /// to/from [`StateInfo`]: `Property` itself, or a type-erased /// storage like the interpreter's `Property`. pub fn set_state_binding(property: Pin<&Property>, binding: impl Fn() -> i32 + 'static) where T: Default + From + 'static, StateInfo: TryFrom, { let bind_callable = StateInfoBinding { dirty_time: Cell::new(None), binding, _phantom: core::marker::PhantomData, }; unsafe { property.handle.set_binding( bind_callable, #[cfg(slint_debug_property)] property.debug_name.borrow().as_str(), ) } } #[doc(hidden)] pub trait PropertyDirtyHandler { fn notify(self: Pin<&Self>); } impl PropertyDirtyHandler for () { fn notify(self: Pin<&Self>) {} } impl PropertyDirtyHandler for F { fn notify(self: Pin<&Self>) { (self.get_ref())() } } /// A PropertyTracker tracks which properties are accessed during evaluation, /// and can notify when those properties change. /// /// The `NEEDS_SET_DIRTY` const parameter controls whether this tracker /// supports being dirtied externally via [`PropertyTracker::set_dirty`]. /// When `false` (the default), the tracker can be more efficient: it will /// skip registering itself as a dependency of outer bindings if it has no /// tracked dependencies of its own, since there is no external way to dirty it. pub struct PropertyTracker { holder: BindingHolder, } impl Default for PropertyTracker { fn default() -> Self { static VT: &BindingVTable = &BindingVTable { drop: |_| (), evaluate: |_, _| BindingResult::KeepBinding, mark_dirty: |_, _| (), intercept_set: |_, _| false, intercept_set_binding: |_, _| false, velocity: |_| None, common_property: |_| None, }; let holder = BindingHolder { dependencies: Cell::new(core::ptr::null_mut()), dep_nodes: Default::default(), vtable: VT, dirty: Cell::new(true), // starts dirty so it evaluates the property when used pinned: PhantomPinned, binding: (), #[cfg(slint_debug_property)] debug_name: ">".into(), }; Self { holder } } } impl Drop for PropertyTracker { fn drop(&mut self) { unsafe { DependencyListHead::drop(self.holder.dependencies.as_ptr() as *mut DependencyListHead); } } } impl PropertyTracker { #[cfg(slint_debug_property)] /// set the debug name when `cfg(slint_debug_property` pub fn set_debug_name(&mut self, debug_name: alloc::string::String) { self.holder.debug_name = debug_name; } /// Registers this property tracker as a dependency to the current binding being evaluated. pub fn register_as_dependency_to_current_binding(self: Pin<&Self>) { // Safety: only reading dep_nodes, not moving it if !NEEDS_SET_DIRTY && unsafe { (*self.holder.dep_nodes.get()).is_empty() } { return; } current_binding_storage::with(|cur_binding| { if let Some(cur_binding) = cur_binding { debug_assert!(self.holder.dependencies.get() != const_sentinel()); BindingHolder::register_self_as_dependency( cur_binding, self.holder.dependencies.as_ptr() as *mut DependencyListHead, #[cfg(slint_debug_property)] &self.holder.debug_name, ); } }); } /// Any of the properties accessed during the last evaluation of the closure called /// from the last call to evaluate is potentially dirty. pub fn is_dirty(&self) -> bool { self.holder.dirty.get() } /// Evaluate the function, and record dependencies of properties accessed within this function. /// If this is called during the evaluation of another property binding or property tracker, then /// any changes to accessed properties will also mark the other binding/tracker dirty. pub fn evaluate(self: Pin<&Self>, f: impl FnOnce() -> R) -> R { let r = self.evaluate_as_dependency_root(f); self.register_as_dependency_to_current_binding(); r } /// Evaluate the function, and record dependencies of properties accessed within this function. /// If this is called during the evaluation of another property binding or property tracker, then /// any changes to accessed properties will not propagate to the other tracker. pub fn evaluate_as_dependency_root(self: Pin<&Self>, f: impl FnOnce() -> R) -> R { // clear all the nodes so that we can start from scratch unsafe { *self.holder.dep_nodes.get() = Default::default() }; let holder_ptr = &raw const self.holder as *const BindingHolder; let r = current_binding_storage::set(Some(holder_ptr), f); self.holder.dirty.set(false); r } /// Call [`Self::evaluate`] if and only if it is dirty. /// But register a dependency in any case. pub fn evaluate_if_dirty(self: Pin<&Self>, f: impl FnOnce() -> R) -> Option { let r = self.is_dirty().then(|| self.evaluate_as_dependency_root(f)); self.register_as_dependency_to_current_binding(); r } /// Sets the specified callback handler function, which will be called if any /// properties that this tracker depends on becomes dirty. /// /// The `handler` `PropertyDirtyHandler` is a trait which is implemented for /// any `Fn()` closure /// /// Note that the handler will be invoked immediately when a property is modified or /// marked as dirty. In particular, the involved property are still in a locked /// state and should not be accessed while the handler is run. This function can be /// useful to mark some work to be done later. pub fn new_with_dirty_handler(handler: DirtyHandler) -> Self { /// Safety: _self must be a pointer to a `BindingHolder` unsafe fn mark_dirty( _self: *const BindingHolder, was_dirty: bool, ) { if !was_dirty { unsafe { Pin::new_unchecked(&(*(_self as *const BindingHolder)).binding).notify() }; } } trait HasBindingVTable { const VT: &'static BindingVTable; } impl HasBindingVTable for B { const VT: &'static BindingVTable = &BindingVTable { drop: |_| (), evaluate: |_, _| BindingResult::KeepBinding, mark_dirty: mark_dirty::, intercept_set: |_, _| false, intercept_set_binding: |_, _| false, velocity: |_| None, common_property: |_| None, }; } let holder = BindingHolder { dependencies: Cell::new(core::ptr::null_mut()), dep_nodes: Default::default(), vtable: ::VT, dirty: Cell::new(true), // starts dirty so it evaluates the property when used pinned: PhantomPinned, binding: handler, #[cfg(slint_debug_property)] debug_name: "".into(), }; Self { holder } } } impl PropertyTracker { /// Mark this PropertyTracker as dirty pub fn set_dirty(&self) { self.holder.dirty.set(true); unsafe { mark_dependencies_dirty(self.holder.dependencies.as_ptr() as *mut _) }; } } #[test] fn test_property_handler_binding() { use core::ptr::without_provenance_mut; assert_eq!( PropertyHandle::has_no_binding_or_lock(without_provenance_mut(BINDING_BORROWED)), false ); assert_eq!( PropertyHandle::has_no_binding_or_lock(without_provenance_mut(BINDING_POINTER_TO_BINDING)), false ); assert_eq!( PropertyHandle::has_no_binding_or_lock(without_provenance_mut( BINDING_BORROWED | BINDING_POINTER_TO_BINDING )), false ); assert_eq!(PropertyHandle::has_no_binding_or_lock(core::ptr::null_mut()), true); } #[test] fn test_property_listener_scope() { let scope = Box::pin(PropertyTracker::default()); let prop1 = Box::pin(Property::new(42)); assert!(scope.is_dirty()); // It is dirty at the beginning let r = scope.as_ref().evaluate(|| prop1.as_ref().get()); assert_eq!(r, 42); assert!(!scope.is_dirty()); // It is no longer dirty prop1.as_ref().set(88); assert!(scope.is_dirty()); // now dirty for prop1 changed. let r = scope.as_ref().evaluate(|| prop1.as_ref().get() + 1); assert_eq!(r, 89); assert!(!scope.is_dirty()); let r = scope.as_ref().evaluate(|| 12); assert_eq!(r, 12); assert!(!scope.is_dirty()); prop1.as_ref().set(1); assert!(!scope.is_dirty()); scope.as_ref().evaluate_if_dirty(|| panic!("should not be dirty")); scope.set_dirty(); let mut ok = false; scope.as_ref().evaluate_if_dirty(|| ok = true); assert!(ok); } #[test] fn test_nested_property_trackers() { let tracker1 = Box::pin(::default()); let tracker2 = Box::pin(::default()); let prop = Box::pin(Property::new(42)); let r = tracker1.as_ref().evaluate(|| tracker2.as_ref().evaluate(|| prop.as_ref().get())); assert_eq!(r, 42); prop.as_ref().set(1); assert!(tracker2.as_ref().is_dirty()); assert!(tracker1.as_ref().is_dirty()); let r = tracker1 .as_ref() .evaluate(|| tracker2.as_ref().evaluate_as_dependency_root(|| prop.as_ref().get())); assert_eq!(r, 1); prop.as_ref().set(100); assert!(tracker2.as_ref().is_dirty()); assert!(!tracker1.as_ref().is_dirty()); } #[test] fn test_property_dirty_handler() { let call_flag = std::rc::Rc::new(Cell::new(false)); let tracker = Box::pin(PropertyTracker::::new_with_dirty_handler({ let call_flag = call_flag.clone(); move || { (*call_flag).set(true); } })); let prop = Box::pin(Property::new(42)); let r = tracker.as_ref().evaluate(|| prop.as_ref().get()); assert_eq!(r, 42); assert!(!tracker.as_ref().is_dirty()); assert!(!call_flag.get()); prop.as_ref().set(100); assert!(tracker.as_ref().is_dirty()); assert!(call_flag.get()); // Repeated changes before evaluation should not trigger further // change handler calls, otherwise it would be a notification storm. call_flag.set(false); prop.as_ref().set(101); assert!(tracker.as_ref().is_dirty()); assert!(!call_flag.get()); } #[test] fn test_property_tracker_drop() { let outer_tracker = Box::pin(::default()); let inner_tracker = Box::pin(::default()); let prop = Box::pin(Property::new(42)); let r = outer_tracker.as_ref().evaluate(|| inner_tracker.as_ref().evaluate(|| prop.as_ref().get())); assert_eq!(r, 42); drop(inner_tracker); prop.as_ref().set(200); // don't crash } #[test] fn test_nested_property_tracker_dirty() { let outer_tracker = Box::pin(PropertyTracker::::default()); let inner_tracker = Box::pin(PropertyTracker::::default()); let prop = Box::pin(Property::new(42)); let r = outer_tracker.as_ref().evaluate(|| inner_tracker.as_ref().evaluate(|| prop.as_ref().get())); assert_eq!(r, 42); assert!(!outer_tracker.is_dirty()); assert!(!inner_tracker.is_dirty()); // Let's pretend that there was another dependency unaccounted first, mark the inner tracker as dirty // by hand. inner_tracker.as_ref().set_dirty(); assert!(outer_tracker.is_dirty()); } #[test] #[allow(clippy::redundant_closure)] fn test_nested_property_tracker_evaluate_if_dirty() { let outer_tracker = Box::pin(::default()); let inner_tracker = Box::pin(::default()); let prop = Box::pin(Property::new(42)); let mut cache = 0; let mut cache_or_evaluate = || { if let Some(x) = inner_tracker.as_ref().evaluate_if_dirty(|| prop.as_ref().get() + 1) { cache = x; } cache }; let r = outer_tracker.as_ref().evaluate(|| cache_or_evaluate()); assert_eq!(r, 43); assert!(!outer_tracker.is_dirty()); assert!(!inner_tracker.is_dirty()); prop.as_ref().set(11); assert!(outer_tracker.is_dirty()); assert!(inner_tracker.is_dirty()); let r = outer_tracker.as_ref().evaluate(|| cache_or_evaluate()); assert_eq!(r, 12); } #[cfg(feature = "ffi")] pub(crate) mod ffi;