i_slint_core/properties/
change_tracker.rs

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