mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-01 22:31:14 +00:00
Add support for recursive dependency tracking across PropertyTracker instances
By default PropertyTracker::evaluate() registers the currently evaluating binding/tracker as a dependency. This should help with repeaters and other scenarios where in the run-time we use property trackers but want to track the overall "dirtyness" in the window with regards to whether a redraw is needed or not. The new evaluate_as_dependency_root() function allows skipping this mechanism and is used for the two trackers in the window.
This commit is contained in:
parent
e63baa5766
commit
3bd5b0eadf
5 changed files with 115 additions and 16 deletions
|
@ -228,6 +228,22 @@ struct PropertyTracker
|
|||
return result;
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
auto evaluate_as_dependency_root(const F &f) const -> std::enable_if_t<std::is_same_v<decltype(f()), void>>
|
||||
{
|
||||
cbindgen_private::sixtyfps_property_tracker_evaluate_as_dependency_root(
|
||||
&inner, [](void *f) { (*reinterpret_cast<const F *>(f))(); }, const_cast<F *>(&f));
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
auto evaluate_as_dependency_root(const F &f) const
|
||||
-> std::enable_if_t<!std::is_same_v<decltype(f()), void>, decltype(f())>
|
||||
{
|
||||
decltype(f()) result;
|
||||
this->evaluate_as_dependency_root([&] { result = f(); });
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
cbindgen_private::PropertyTrackerOpaque inner;
|
||||
};
|
||||
|
|
|
@ -42,3 +42,25 @@ TEST_CASE("Basic SharedVector API", "[vector]")
|
|||
REQUIRE(vec[2] == 10);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Property Tracker")
|
||||
{
|
||||
using namespace sixtyfps;
|
||||
PropertyTracker tracker1;
|
||||
PropertyTracker tracker2;
|
||||
Property<int> prop(42);
|
||||
|
||||
auto r = tracker1.evaluate([&]() { return tracker2.evaluate([&]() { return prop.get(); }); });
|
||||
REQUIRE(r == 42);
|
||||
|
||||
prop.set(1);
|
||||
REQUIRE(tracker2.is_dirty());
|
||||
REQUIRE(tracker1.is_dirty());
|
||||
|
||||
r = tracker1.evaluate(
|
||||
[&]() { return tracker2.evaluate_as_dependency_root([&]() { return prop.get(); }); });
|
||||
REQUIRE(r == 1);
|
||||
prop.set(100);
|
||||
REQUIRE(tracker2.is_dirty());
|
||||
REQUIRE(!tracker1.is_dirty());
|
||||
}
|
|
@ -167,6 +167,18 @@ struct BindingHolder<B = ()> {
|
|||
binding: B,
|
||||
}
|
||||
|
||||
impl BindingHolder {
|
||||
fn register_self_as_dependency(
|
||||
self: Pin<&Self>,
|
||||
property_that_will_notify: *mut DependencyListHead,
|
||||
) {
|
||||
let node = DependencyNode::for_binding(self);
|
||||
let mut dep_nodes = self.dep_nodes.borrow_mut();
|
||||
let node = dep_nodes.push_front(node);
|
||||
unsafe { DependencyListHead::append(property_that_will_notify, node.get_ref() as *const _) }
|
||||
}
|
||||
}
|
||||
|
||||
fn alloc_binding_holder<B: BindingCallable + 'static>(binding: B) -> *mut BindingHolder {
|
||||
/// Safety: _self must be a pointer that comes from a `Box<BindingHolder<B>>::into_raw()`
|
||||
unsafe fn binding_drop<B>(_self: *mut BindingHolder) {
|
||||
|
@ -447,12 +459,7 @@ impl PropertyHandle {
|
|||
fn register_as_dependency_to_current_binding(&self) {
|
||||
if CURRENT_BINDING.is_set() {
|
||||
CURRENT_BINDING.with(|cur_binding| {
|
||||
let node = DependencyNode::for_binding(cur_binding);
|
||||
let mut dep_nodes = cur_binding.dep_nodes.borrow_mut();
|
||||
let node = dep_nodes.push_front(node);
|
||||
unsafe {
|
||||
DependencyListHead::append(self.dependencies(), node.get_ref() as *const _)
|
||||
}
|
||||
cur_binding.register_self_as_dependency(self.dependencies());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1302,9 +1309,27 @@ impl PropertyTracker {
|
|||
}
|
||||
|
||||
/// Evaluate the function, and record dependencies of properties accessed whithin 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<R>(self: Pin<&Self>, f: impl FnOnce() -> R) -> R {
|
||||
if CURRENT_BINDING.is_set() {
|
||||
CURRENT_BINDING.with(|cur_binding| {
|
||||
cur_binding.register_self_as_dependency(
|
||||
self.holder.dependencies.as_ptr() as *mut DependencyListHead
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
self.evaluate_as_dependency_root(f)
|
||||
}
|
||||
|
||||
/// Evaluate the function, and record dependencies of properties accessed whithin 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<R>(self: Pin<&Self>, f: impl FnOnce() -> R) -> R {
|
||||
// clear all the nodes so that we can start from scratch
|
||||
*self.holder.dep_nodes.borrow_mut() = Default::default();
|
||||
|
||||
// Safety: it is safe to project the holder as we don't implement drop or unpin
|
||||
let pinned_holder = unsafe { self.map_unchecked(|s| &s.holder) };
|
||||
let r = CURRENT_BINDING.set(pinned_holder, f);
|
||||
|
@ -1351,6 +1376,28 @@ fn test_property_listener_scope() {
|
|||
assert!(ok);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nested_property_trackers() {
|
||||
let tracker1 = Box::pin(PropertyTracker::default());
|
||||
let tracker2 = Box::pin(PropertyTracker::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());
|
||||
}
|
||||
|
||||
#[cfg(feature = "ffi")]
|
||||
pub(crate) mod ffi {
|
||||
use super::*;
|
||||
|
@ -1758,6 +1805,7 @@ pub(crate) mod ffi {
|
|||
}
|
||||
|
||||
/// Call the callback with the user data. Any properties access within the callback will be registered.
|
||||
/// Any currently evaluated bindings or property trackers will be notified if accessed properties are changed.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn sixtyfps_property_tracker_evaluate(
|
||||
handle: *const PropertyTrackerOpaque,
|
||||
|
@ -1767,6 +1815,17 @@ pub(crate) mod ffi {
|
|||
Pin::new_unchecked(&*(handle as *const PropertyTracker)).evaluate(|| callback(user_data))
|
||||
}
|
||||
|
||||
/// Call the callback with the user data. Any properties access within the callback will be registered.
|
||||
/// Any currently evaluated bindings or property trackers will be not notified if accessed properties are changed.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn sixtyfps_property_tracker_evaluate_as_dependency_root(
|
||||
handle: *const PropertyTrackerOpaque,
|
||||
callback: extern "C" fn(user_data: *mut c_void),
|
||||
user_data: *mut c_void,
|
||||
) {
|
||||
Pin::new_unchecked(&*(handle as *const PropertyTracker))
|
||||
.evaluate_as_dependency_root(|| callback(user_data))
|
||||
}
|
||||
/// Query if the property tracker is dirty
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn sixtyfps_property_tracker_is_dirty(
|
||||
|
|
|
@ -285,7 +285,7 @@ impl GraphicsWindow {
|
|||
|
||||
{
|
||||
if self.meta_property_listener.as_ref().is_dirty() {
|
||||
self.meta_property_listener.as_ref().evaluate(|| {
|
||||
self.meta_property_listener.as_ref().evaluate_as_dependency_root(|| {
|
||||
self.apply_geometry_constraint(component.as_ref().layout_info());
|
||||
component.as_ref().apply_layout(self.get_geometry());
|
||||
|
||||
|
|
|
@ -780,7 +780,8 @@ impl QtWindow {
|
|||
let component_rc = self.self_weak.upgrade().unwrap().component();
|
||||
let component = ComponentRc::borrow_pin(&component_rc);
|
||||
|
||||
self.meta_property_listener.as_ref().evaluate_if_dirty(|| {
|
||||
if self.meta_property_listener.as_ref().is_dirty() {
|
||||
self.meta_property_listener.as_ref().evaluate_as_dependency_root(|| {
|
||||
self.apply_geometry_constraint(component.as_ref().layout_info());
|
||||
component.as_ref().apply_layout(Default::default());
|
||||
|
||||
|
@ -789,9 +790,10 @@ impl QtWindow {
|
|||
self.apply_window_properties(window_item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let cache = self.cache.clone();
|
||||
self.redraw_listener.as_ref().evaluate(|| {
|
||||
self.redraw_listener.as_ref().evaluate_as_dependency_root(|| {
|
||||
let mut renderer = QtItemRenderer {
|
||||
painter,
|
||||
cache,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue