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
4use super::{BindingHolder, BindingResult, BindingVTable, DependencyListHead};
5use alloc::boxed::Box;
6use core::cell::Cell;
7use core::marker::PhantomPinned;
8use core::pin::Pin;
9use core::ptr::addr_of;
10
11// TODO a pinned thread local key?
12crate::thread_local! {static CHANGED_NODES : Pin<Box<DependencyListHead>> = Box::pin(DependencyListHead::default()) }
13
14struct ChangeTrackerInner<T, EvalFn, NotifyFn, Data> {
15 eval_fn: EvalFn,
16 notify_fn: NotifyFn,
17 value: T,
18 data: Data,
19}
20
21/// A change tracker is used to run a callback when a property value changes.
22///
23/// The Change Tracker must be initialized with the [`Self::init`] method.
24///
25/// When the property changes, the ChangeTracker is added to a thread local list, and the notify
26/// callback is called when the [`Self::run_change_handlers()`] method is called
27pub struct ChangeTracker {
28 /// (Actually a `BindingHolder<ChangeTrackerInner>`)
29 inner: Cell<*mut BindingHolder>,
30}
31
32impl Default for ChangeTracker {
33 fn default() -> Self {
34 Self { inner: Cell::new(core::ptr::null_mut()) }
35 }
36}
37
38impl Drop for ChangeTracker {
39 fn drop(&mut self) {
40 self.clear();
41 }
42}
43
44impl ChangeTracker {
45 /// Initialize the change tracker with the given data and callbacks.
46 ///
47 /// The `data` is any struct that is going to be passed to the functor.
48 /// The `eval_fn` is a function that queries and return the property.
49 /// And the `notify_fn` is the callback run if the property is changed
50 pub fn init<Data, T: Default + PartialEq, EF: Fn(&Data) -> T, NF: Fn(&Data, &T)>(
51 &self,
52 data: Data,
53 eval_fn: EF,
54 notify_fn: NF,
55 ) {
56 self.clear();
57 let inner = ChangeTrackerInner { eval_fn, notify_fn, value: T::default(), data };
58
59 unsafe fn evaluate<T: PartialEq, EF: Fn(&Data) -> T, NF: Fn(&Data, &T), Data>(
60 _self: *mut BindingHolder,
61 _value: *mut (),
62 ) -> BindingResult {
63 let pinned_holder = Pin::new_unchecked(&*_self);
64 let _self = _self as *mut BindingHolder<ChangeTrackerInner<T, EF, NF, Data>>;
65 let inner = core::ptr::addr_of_mut!((*_self).binding).as_mut().unwrap();
66 let new_value =
67 super::CURRENT_BINDING.set(Some(pinned_holder), || (inner.eval_fn)(&inner.data));
68 if new_value != inner.value {
69 inner.value = new_value;
70 (inner.notify_fn)(&inner.data, &inner.value);
71 }
72 BindingResult::KeepBinding
73 }
74
75 unsafe fn drop<T, EF, NF, Data>(_self: *mut BindingHolder) {
76 core::mem::drop(Box::from_raw(
77 _self as *mut BindingHolder<ChangeTrackerInner<T, EF, NF, Data>>,
78 ));
79 }
80
81 trait HasBindingVTable {
82 const VT: &'static BindingVTable;
83 }
84 impl<T: PartialEq, EF: Fn(&Data) -> T, NF: Fn(&Data, &T), Data> HasBindingVTable
85 for ChangeTrackerInner<T, EF, NF, Data>
86 {
87 const VT: &'static BindingVTable = &BindingVTable {
88 drop: drop::<T, EF, NF, Data>,
89 evaluate: evaluate::<T, EF, NF, Data>,
90 mark_dirty: ChangeTracker::mark_dirty,
91 intercept_set: |_, _| false,
92 intercept_set_binding: |_, _| false,
93 };
94 }
95 let holder = BindingHolder {
96 dependencies: Cell::new(0),
97 dep_nodes: Default::default(),
98 vtable: <ChangeTrackerInner<T, EF, NF, Data> as HasBindingVTable>::VT,
99 dirty: Cell::new(false),
100 is_two_way_binding: false,
101 pinned: PhantomPinned,
102 binding: inner,
103 #[cfg(slint_debug_property)]
104 debug_name: "<ChangeTracker>".into(),
105 };
106
107 let raw = Box::into_raw(Box::new(holder));
108 let value = unsafe {
109 self.set_internal(raw as *mut BindingHolder);
110 let pinned_holder = Pin::new_unchecked((raw as *mut BindingHolder).as_ref().unwrap());
111 let inner = core::ptr::addr_of!((*raw).binding).as_ref().unwrap();
112 super::CURRENT_BINDING.set(Some(pinned_holder), || (inner.eval_fn)(&inner.data))
113 };
114 unsafe { core::ptr::addr_of_mut!((*raw).binding).as_mut().unwrap().value = value };
115 }
116
117 /// Clear the change tracker.
118 /// No notify function will be called after this.
119 pub fn clear(&self) {
120 let inner = self.inner.get();
121 if !inner.is_null() {
122 unsafe {
123 let drop = (*core::ptr::addr_of!((*inner).vtable)).drop;
124 drop(inner);
125 }
126 self.inner.set(core::ptr::null_mut());
127 }
128 }
129
130 /// Run all the change handler that were queued.
131 pub fn run_change_handlers() {
132 CHANGED_NODES.with(|list| {
133 let old_list = DependencyListHead::default();
134 let old_list = core::pin::pin!(old_list);
135 let mut count = 0;
136 while !list.is_empty() {
137 count += 1;
138 if count > 9 {
139 crate::debug_log!("Slint: long changed callback chain detected");
140 return;
141 }
142 DependencyListHead::swap(list.as_ref(), old_list.as_ref());
143 old_list.for_each(|node| {
144 let node = *node;
145 unsafe {
146 ((*addr_of!((*node).vtable)).evaluate)(
147 node as *mut BindingHolder,
148 core::ptr::null_mut(),
149 );
150 }
151 });
152 old_list.as_ref().clear();
153 }
154 });
155 }
156
157 pub(super) unsafe fn mark_dirty(_self: *const BindingHolder, _was_dirty: bool) {
158 // Move the dependency list node from the dependency list to the CHANGED_NODE
159 let _self = _self.as_ref().unwrap();
160 let node_head = _self.dep_nodes.take();
161 if let Some(node) = node_head.iter().next() {
162 node.remove();
163 CHANGED_NODES.with(|changed_nodes| {
164 changed_nodes.append(node);
165 });
166 }
167 _self.dep_nodes.set(node_head);
168 }
169
170 pub(super) unsafe fn set_internal(&self, raw: *mut BindingHolder) {
171 self.inner.set(raw);
172 }
173}
174
175#[test]
176fn change_tracker() {
177 use super::Property;
178 use std::rc::Rc;
179 let prop1 = Rc::pin(Property::new(42));
180 let prop2 = Rc::pin(Property::<i32>::default());
181 prop2.as_ref().set_binding({
182 let prop1 = prop1.clone();
183 move || prop1.as_ref().get() * 2
184 });
185
186 let change1 = ChangeTracker::default();
187 let change2 = ChangeTracker::default();
188
189 let state = Rc::new(core::cell::RefCell::new(std::string::String::new()));
190
191 change1.init(
192 (state.clone(), prop1.clone()),
193 |(_, prop1)| prop1.as_ref().get(),
194 |(state, _), val| {
195 *state.borrow_mut() += &std::format!(":1({val})");
196 },
197 );
198 change2.init(
199 (state.clone(), prop2.clone()),
200 |(_, prop2)| prop2.as_ref().get(),
201 |(state, _), val| {
202 *state.borrow_mut() += &std::format!(":2({val})");
203 },
204 );
205
206 assert_eq!(state.borrow().as_str(), "");
207 prop1.as_ref().set(10);
208 assert_eq!(state.borrow().as_str(), "");
209 prop1.as_ref().set(30);
210 assert_eq!(state.borrow().as_str(), "");
211
212 ChangeTracker::run_change_handlers();
213 assert_eq!(state.borrow().as_str(), ":1(30):2(60)");
214 ChangeTracker::run_change_handlers();
215 assert_eq!(state.borrow().as_str(), ":1(30):2(60)");
216 prop1.as_ref().set(1);
217 assert_eq!(state.borrow().as_str(), ":1(30):2(60)");
218 ChangeTracker::run_change_handlers();
219 assert_eq!(state.borrow().as_str(), ":1(30):2(60):1(1):2(2)");
220}
221