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