Skip to main content

ferroui_base/threading/
dispatcher_timers.rs

1use std::rc::Rc;
2
3use super::dispatcher::DispatcherState;
4use super::{Dispatcher, DispatcherPriority, DispatcherTimer};
5
6impl Dispatcher {
7    /// The current time of the platform implementation, in milliseconds.
8    ///
9    /// Dispatcher thread only; a dispatcher that has been reset reports 0.
10    pub fn now(&self) -> i64 {
11        self.verify_access();
12        self.local_impl().map_or(0, |impl_| impl_.now())
13    }
14
15    pub(crate) fn update_os_timer(&self) {
16        self.update_os_timer_locked(&mut self.lock());
17    }
18
19    pub(crate) fn update_os_timer_locked(&self, st: &mut DispatcherState) {
20        self.verify_access();
21        let next_due_time = match (st.due_time_for_timers, st.due_time_for_background_processing) {
22            (Some(timers), Some(background)) => Some(timers.min(background)),
23            (timers, background) => timers.or(background),
24        };
25        if st.os_timer_set_to == next_due_time {
26            return;
27        }
28
29        st.os_timer_set_to = next_due_time;
30        if let Some(impl_) = self.local_impl() {
31            impl_.update_timer(next_due_time);
32        }
33    }
34
35    pub(crate) fn reschedule_timers(&self) {
36        if !self.check_access() {
37            let dispatcher = self.to_arc();
38            self.post(move || dispatcher.reschedule_timers(), DispatcherPriority::SEND);
39            return;
40        }
41
42        let local = self.try_local();
43        let mut st = self.lock();
44        if !st.has_shutdown_finished {
45            let old_due_time_found = st.due_time_found;
46            let old_due_time_in_ticks = st.due_time_in_ms;
47            st.due_time_found = false;
48            st.due_time_in_ms = 0;
49
50            if let Some(local) = &local {
51                // We could do better if we sorted the list of timers.
52                for timer in local.timers.borrow().iter() {
53                    if !st.due_time_found || timer.due_time_in_ms().wrapping_sub(st.due_time_in_ms) < 0 {
54                        st.due_time_found = true;
55                        st.due_time_in_ms = timer.due_time_in_ms();
56                    }
57                }
58            }
59
60            if st.due_time_found {
61                if st.due_time_for_timers.is_none()
62                    || !old_due_time_found
63                    || old_due_time_in_ticks != st.due_time_in_ms
64                {
65                    st.due_time_for_timers = Some(st.due_time_in_ms);
66                    self.update_os_timer_locked(&mut st);
67                }
68            } else if old_due_time_found {
69                st.due_time_for_timers = None;
70                self.update_os_timer_locked(&mut st);
71            }
72        }
73    }
74
75    pub(crate) fn add_timer(&self, timer: &Rc<DispatcherTimer>) {
76        if let Some(local) = self.try_local() {
77            if !self.lock().has_shutdown_finished {
78                local.timers.borrow_mut().push(timer.clone());
79                local.timers_version.set(local.timers_version.get() + 1);
80            }
81        }
82
83        self.reschedule_timers();
84    }
85
86    pub(crate) fn remove_timer(&self, timer: &DispatcherTimer) {
87        if let Some(local) = self.try_local() {
88            if !self.lock().has_shutdown_finished {
89                let removed = {
90                    let mut timers = local.timers.borrow_mut();
91                    timers.iter().position(|t| std::ptr::eq(Rc::as_ptr(t), timer)).map(|index| timers.remove(index))
92                };
93                local.timers_version.set(local.timers_version.get() + 1);
94                drop(removed);
95            }
96        }
97
98        self.reschedule_timers();
99    }
100
101    pub(crate) fn on_os_timer(&self) {
102        let need_to_promote_timers;
103        let need_to_process_queue;
104        {
105            let Some(impl_) = self.local_impl() else {
106                return;
107            };
108            let mut st = self.lock();
109            impl_.update_timer(None);
110            st.os_timer_set_to = None;
111            let now = impl_.now();
112            need_to_promote_timers = st.due_time_for_timers.is_some_and(|due| due <= now);
113            if need_to_promote_timers {
114                st.due_time_for_timers = None;
115            }
116            need_to_process_queue = st.due_time_for_background_processing.is_some_and(|due| due <= now);
117            if need_to_process_queue {
118                st.due_time_for_background_processing = None;
119            }
120        }
121
122        if need_to_promote_timers {
123            self.promote_timers();
124        }
125        if need_to_process_queue {
126            self.execute_jobs_core(false);
127        }
128        self.update_os_timer();
129    }
130
131    pub(crate) fn promote_timers(&self) {
132        let Some(local) = self.try_local() else {
133            return;
134        };
135        if local.timers.borrow().is_empty() {
136            // Nothing can be due and nothing has to be rescheduled.
137            let st = self.lock();
138            if !st.due_time_found {
139                return;
140            }
141        }
142
143        let current_time_in_ticks = self.now();
144
145        let (has_due_timers, mut timers_version) = {
146            let st = self.lock();
147            let due = !st.has_shutdown_finished
148                && st.due_time_found
149                && st.due_time_in_ms.wrapping_sub(current_time_in_ticks) <= 0;
150            (due, local.timers_version.get())
151        };
152
153        if has_due_timers {
154            let mut i_timer = 0usize;
155
156            loop {
157                let timer = {
158                    let mut timers = local.timers.borrow_mut();
159                    let mut timer = None;
160
161                    // If the timers collection changed while we are in the middle of
162                    // looking for timers, start over.
163                    if timers_version != local.timers_version.get() {
164                        timers_version = local.timers_version.get();
165                        i_timer = 0;
166                    }
167
168                    while i_timer < timers.len() {
169                        // WARNING: this is vulnerable to wrapping
170                        if timers[i_timer].due_time_in_ms().wrapping_sub(current_time_in_ticks) <= 0 {
171                            // Remove this timer from our list.
172                            // Do not increment the index.
173                            timer = Some(timers.remove(i_timer));
174                            break;
175                        } else {
176                            i_timer += 1;
177                        }
178                    }
179                    timer
180                };
181
182                // Now that we are outside of the borrow, promote the timer.
183                match timer {
184                    Some(timer) => timer.promote(),
185                    None => break,
186                }
187            }
188        }
189
190        // finally
191        self.reschedule_timers();
192    }
193
194    #[allow(dead_code)]
195    pub(crate) fn snapshot_timers_for_unit_tests() -> Vec<Rc<DispatcherTimer>> {
196        Dispatcher::ui_thread().try_local().map(|local| local.timers.borrow().clone()).unwrap_or_default()
197    }
198}
199
200/// Test support for the other crates of the workspace: the reference gives
201/// its test assemblies access to the internal timer queue.
202impl Dispatcher {
203    /// The timers currently scheduled on the dispatcher of this thread.
204    #[doc(hidden)]
205    pub fn timers_for_unit_tests() -> Vec<Rc<DispatcherTimer>> {
206        Self::snapshot_timers_for_unit_tests()
207    }
208
209    /// Fires a scheduled timer now, whatever its due time.
210    #[doc(hidden)]
211    pub fn force_fire_timer_for_unit_tests(timer: &Rc<DispatcherTimer>) {
212        timer.promote();
213        timer.dispatcher().remove_timer(timer);
214        timer.dispatcher().run_jobs(None);
215    }
216}