1use super::{BindingHolder, BindingResult, BindingVTable, DependencyListHead, DependencyNode};
5use alloc::boxed::Box;
6use core::cell::{Cell, UnsafeCell};
7use core::ffi::c_void;
8use core::marker::PhantomPinned;
9use core::pin::Pin;
10use core::ptr::addr_of;
11
12crate::thread_local! {static CHANGED_NODES : Pin<Box<DependencyListHead>> = Box::pin(DependencyListHead::default()) }
14
15struct ChangeTrackerInner<T, EvalFn, NotifyFn, Data> {
16 eval_fn: EvalFn,
17 notify_fn: NotifyFn,
18 value: UnsafeCell<T>,
20 data: Data,
21 evaluating: Cell<bool>,
23}
24
25#[derive(Debug)]
32pub struct ChangeTracker {
33 inner: Cell<*mut BindingHolder>,
35}
36
37impl Default for ChangeTracker {
38 fn default() -> Self {
39 Self { inner: Cell::new(core::ptr::null_mut()) }
40 }
41}
42
43impl Drop for ChangeTracker {
44 fn drop(&mut self) {
45 self.clear();
46 }
47}
48
49impl ChangeTracker {
50 pub fn init<
56 Data: 'static,
57 T: Default + PartialEq,
58 EF: Fn(&Data) -> T + 'static,
59 NF: Fn(&Data, &T) + 'static,
60 >(
61 &self,
62 data: Data,
63 eval_fn: EF,
64 notify_fn: NF,
65 ) {
66 self.init_impl(data, eval_fn, notify_fn, false);
67 }
68
69 pub fn init_delayed<
75 Data: 'static,
76 T: Default + PartialEq,
77 EF: Fn(&Data) -> T + 'static,
78 NF: Fn(&Data, &T) + 'static,
79 >(
80 &self,
81 data: Data,
82 eval_fn: EF,
83 notify_fn: NF,
84 ) {
85 self.init_impl(data, eval_fn, notify_fn, true);
86 }
87
88 fn init_impl<
89 Data: 'static,
90 T: Default + PartialEq,
91 EF: Fn(&Data) -> T + 'static,
92 NF: Fn(&Data, &T) + 'static,
93 >(
94 &self,
95 data: Data,
96 eval_fn: EF,
97 notify_fn: NF,
98 delayed: bool,
99 ) {
100 self.clear();
101 let inner = ChangeTrackerInner {
102 eval_fn,
103 notify_fn,
104 value: T::default().into(),
105 data,
106 evaluating: false.into(),
107 };
108
109 unsafe fn evaluate<
110 T: PartialEq,
111 EF: Fn(&Data) -> T + 'static,
112 NF: Fn(&Data, &T) + 'static,
113 Data: 'static,
114 >(
115 _self: *const BindingHolder,
116 _value: *mut c_void,
117 ) -> BindingResult {
118 unsafe {
119 let _self_raw = _self;
120 let _self = _self as *const BindingHolder<ChangeTrackerInner<T, EF, NF, Data>>;
121 let inner = core::ptr::addr_of!((*_self).binding).as_ref().unwrap();
122 *(*core::ptr::addr_of!((*_self).dep_nodes)).get() = Default::default();
123 assert!(!inner.evaluating.get());
124 inner.evaluating.set(true);
125 let new_value = super::current_binding_storage::set(Some(_self_raw), || {
126 (inner.eval_fn)(&inner.data)
127 });
128 {
129 let inner_value = &mut *inner.value.get();
131 if new_value != *inner_value {
132 *inner_value = new_value;
133 (inner.notify_fn)(&inner.data, inner_value);
134 }
135 }
136
137 if !inner.evaluating.replace(false) {
138 core::mem::drop(Box::from_raw(
140 _self as *mut BindingHolder<ChangeTrackerInner<T, EF, NF, Data>>,
141 ));
142 }
143 BindingResult::KeepBinding
144 }
145 }
146
147 unsafe fn drop<T, EF, NF, Data>(_self: *mut BindingHolder) {
148 unsafe {
149 let _self = _self as *mut BindingHolder<ChangeTrackerInner<T, EF, NF, Data>>;
150 let evaluating = core::ptr::addr_of!((*_self).binding)
151 .as_ref()
152 .unwrap()
153 .evaluating
154 .replace(false);
155 if !evaluating {
156 core::mem::drop(Box::from_raw(_self));
157 }
158 }
159 }
160
161 trait HasBindingVTable {
162 const VT: &'static BindingVTable;
163 }
164 impl<T: PartialEq, EF: Fn(&Data) -> T + 'static, NF: Fn(&Data, &T) + 'static, Data: 'static>
165 HasBindingVTable for ChangeTrackerInner<T, EF, NF, Data>
166 {
167 const VT: &'static BindingVTable = &BindingVTable {
168 drop: drop::<T, EF, NF, Data>,
169 evaluate: evaluate::<T, EF, NF, Data>,
170 mark_dirty: ChangeTracker::mark_dirty,
171 intercept_set: |_, _| false,
172 intercept_set_binding: |_, _| false,
173 velocity: |_| None,
174 };
175 }
176 let holder = BindingHolder {
177 dependencies: Cell::new(core::ptr::null_mut()),
178 dep_nodes: Default::default(),
179 vtable: <ChangeTrackerInner<T, EF, NF, Data> as HasBindingVTable>::VT,
180 dirty: Cell::new(false),
181 is_two_way_binding: false,
182 pinned: PhantomPinned,
183 binding: inner,
184 #[cfg(slint_debug_property)]
185 debug_name: "<ChangeTracker>".into(),
186 };
187
188 let raw = Box::into_raw(Box::new(holder));
189 unsafe { self.set_internal(raw as *mut BindingHolder) };
190 if delayed {
191 unsafe {
193 let dep_nodes = &mut *(*core::ptr::addr_of!((*raw).dep_nodes)).get();
194 let node = dep_nodes.push_front(DependencyNode::new(raw as *const BindingHolder));
195 CHANGED_NODES.with(|changed_nodes| {
196 changed_nodes.append(node);
197 });
198 }
199 return;
200 }
201 let value = unsafe {
202 let inner = core::ptr::addr_of!((*raw).binding).as_ref().unwrap();
203 super::current_binding_storage::set(Some(raw as *const BindingHolder), || {
204 (inner.eval_fn)(&inner.data)
205 })
206 };
207 unsafe {
208 *core::ptr::addr_of_mut!((*raw).binding).as_mut().unwrap().value.get_mut() = value
209 };
210 }
211
212 #[cfg(test)]
214 pub(crate) fn test_dep_node_count(&self) -> usize {
215 let inner = self.inner.get();
216 if inner.is_null() {
217 return 0;
218 }
219 unsafe { (*(*core::ptr::addr_of!((*inner).dep_nodes)).get()).iter().count() }
220 }
221
222 pub fn clear(&self) {
225 let inner = self.inner.get();
226 if !inner.is_null() {
227 unsafe {
228 let drop = (*core::ptr::addr_of!((*inner).vtable)).drop;
229 drop(inner);
230 }
231 self.inner.set(core::ptr::null_mut());
232 }
233 }
234
235 pub fn run_change_handlers() {
238 for _ in 0..10 {
239 if !Self::run_change_handlers_once() {
240 return;
241 }
242 }
243 crate::debug_log!("Slint: long changed callback chain detected");
244 }
245
246 pub fn run_change_handlers_once() -> bool {
249 CHANGED_NODES.with(|list| {
250 if list.is_empty() {
251 return false;
252 }
253 let old_list = DependencyListHead::default();
254 let old_list = core::pin::pin!(old_list);
255 DependencyListHead::swap(list.as_ref(), old_list.as_ref());
256 while let Some(node) = old_list.take_head() {
257 unsafe {
258 ((*addr_of!((*node).vtable)).evaluate)(
259 node as *mut BindingHolder,
260 core::ptr::null_mut(),
261 );
262 }
263 }
264 true
265 })
266 }
267
268 pub(super) unsafe fn mark_dirty(_self: *const BindingHolder, _was_dirty: bool) {
269 unsafe {
270 let dep_nodes = &mut *(*_self).dep_nodes.get();
272 let node_head = core::mem::take(dep_nodes);
273 if let Some(node) = node_head.iter().next() {
274 node.remove();
275 CHANGED_NODES.with(|changed_nodes| {
276 changed_nodes.append(node);
277 });
278 }
279 let other = core::mem::replace(dep_nodes, node_head);
281 debug_assert!(other.iter().next().is_none());
282 }
283 }
284
285 pub(super) unsafe fn set_internal(&self, raw: *mut BindingHolder) {
286 self.inner.set(raw);
287 }
288}
289
290#[test]
291fn change_tracker() {
292 use super::Property;
293 use std::rc::Rc;
294 let prop1 = Rc::pin(Property::new(42));
295 let prop2 = Rc::pin(Property::<i32>::default());
296 prop2.as_ref().set_binding({
297 let prop1 = prop1.clone();
298 move || prop1.as_ref().get() * 2
299 });
300
301 let change1 = ChangeTracker::default();
302 let change2 = ChangeTracker::default();
303
304 let state = Rc::new(core::cell::RefCell::new(std::string::String::new()));
305
306 change1.init(
307 (state.clone(), prop1.clone()),
308 |(_, prop1)| prop1.as_ref().get(),
309 |(state, _), val| {
310 *state.borrow_mut() += &std::format!(":1({val})");
311 },
312 );
313 change2.init(
314 (state.clone(), prop2.clone()),
315 |(_, prop2)| prop2.as_ref().get(),
316 |(state, _), val| {
317 *state.borrow_mut() += &std::format!(":2({val})");
318 },
319 );
320
321 assert_eq!(state.borrow().as_str(), "");
322 prop1.as_ref().set(10);
323 assert_eq!(state.borrow().as_str(), "");
324 prop1.as_ref().set(30);
325 assert_eq!(state.borrow().as_str(), "");
326
327 ChangeTracker::run_change_handlers();
328 assert_eq!(state.borrow().as_str(), ":1(30):2(60)");
329 ChangeTracker::run_change_handlers();
330 assert_eq!(state.borrow().as_str(), ":1(30):2(60)");
331 prop1.as_ref().set(1);
332 assert_eq!(state.borrow().as_str(), ":1(30):2(60)");
333 ChangeTracker::run_change_handlers();
334 assert_eq!(state.borrow().as_str(), ":1(30):2(60):1(1):2(2)");
335}
336
337#[test]
339fn delete_from_eval_fn() {
340 use std::cell::RefCell;
341 use std::rc::Rc;
342 use std::string::String;
343
344 let change = Rc::<RefCell<Option<ChangeTracker>>>::new(Some(ChangeTracker::default()).into());
345 let xyz = RefCell::new(String::from("*"));
346 let result = Rc::new(RefCell::new(String::new()));
347 let result2 = result.clone();
348 let another = Rc::<RefCell<Option<ChangeTracker>>>::new(Some(ChangeTracker::default()).into());
350 another.borrow().as_ref().unwrap().init_delayed(
351 (),
352 |()| unreachable!(),
353 move |(), &()| unreachable!(),
354 );
355 change.borrow().as_ref().unwrap().init_delayed(
356 change.clone(),
357 |x| {
358 x.borrow_mut().take().unwrap();
359 String::from("hi")
360 },
361 move |x, val| {
362 assert!(x.borrow().is_none());
363 assert_eq!(val, "hi");
364 xyz.borrow_mut().push_str("+");
365 assert!(xyz.borrow().as_str().starts_with("*+"));
366 result2.replace(xyz.borrow().clone());
367 another.borrow_mut().take().unwrap();
368 },
369 );
370
371 assert_eq!(result.borrow().as_str(), "");
372 ChangeTracker::run_change_handlers();
373 assert_eq!(result.borrow().as_str(), "*+");
374 ChangeTracker::run_change_handlers();
375 assert_eq!(result.borrow().as_str(), "*+");
376}
377
378#[test]
379fn change_multiple_dependencies() {
380 use super::Property;
381 use std::cell::RefCell;
382 use std::rc::Rc;
383 use std::string::String;
384 let prop1 = Rc::pin(Property::new(1));
385 let prop2 = Rc::pin(Property::new(2));
386 let prop3 = Rc::pin(Property::new(3));
387 let prop4 = Rc::pin(Property::new(4));
388 let prop_with_deps = Rc::pin(Property::new(5));
389 let result = Rc::new(RefCell::new(String::new()));
390
391 let change_tracker = ChangeTracker::default();
392 change_tracker.init(
393 result.clone(),
394 {
395 let prop1 = prop1.clone();
396 let prop2 = prop2.clone();
397 let prop3 = prop3.clone();
398 let prop4 = prop4.clone();
399 let prop_with_deps = prop_with_deps.clone();
400 move |_| {
401 prop1.as_ref().get()
402 + prop2.as_ref().get()
403 + prop3.as_ref().get()
404 + prop4.as_ref().get()
405 + prop_with_deps.as_ref().get()
406 }
407 },
408 move |result, val| {
409 *result.borrow_mut() += &std::format!("[{val}]");
410 },
411 );
412
413 assert_eq!(result.borrow().as_str(), "");
414 ChangeTracker::run_change_handlers();
415 assert_eq!(result.borrow().as_str(), "");
416
417 prop_with_deps.as_ref().set_binding({
418 let prop1 = prop1.clone();
419 let prop2 = prop2.clone();
420 move || prop1.as_ref().get() + prop2.as_ref().get()
421 });
422
423 assert_eq!(result.borrow().as_str(), "");
424 ChangeTracker::run_change_handlers();
425 assert_eq!(prop_with_deps.as_ref().get(), 3);
426 assert_eq!(result.borrow().as_str(), "[13]"); ChangeTracker::run_change_handlers();
429 assert_eq!(result.borrow().as_str(), "[13]");
430
431 prop1.as_ref().set(10);
432 assert_eq!(result.borrow().as_str(), "[13]");
433 ChangeTracker::run_change_handlers();
434 assert_eq!(result.borrow().as_str(), "[13][31]"); prop2.as_ref().set(20);
437 prop3.as_ref().set(30);
438 assert_eq!(result.borrow().as_str(), "[13][31]");
439 ChangeTracker::run_change_handlers();
440 assert_eq!(result.borrow().as_str(), "[13][31][94]"); ChangeTracker::run_change_handlers();
443 assert_eq!(result.borrow().as_str(), "[13][31][94]");
444
445 prop1.as_ref().set(20);
447 prop2.as_ref().set(10);
448 ChangeTracker::run_change_handlers();
449 assert_eq!(result.borrow().as_str(), "[13][31][94]");
450}