Skip to main content

ferroui_base/threading/
dispatcher_timer.rs

1use std::cell::{Cell, RefCell};
2use std::rc::{Rc, Weak};
3use std::sync::Arc;
4use std::time::Duration;
5
6use super::{Dispatcher, DispatcherOperation, DispatcherPriority};
7use crate::reactive::{Disposable, IDisposable};
8use crate::utilities::HandlerList;
9use crate::BoxedValue;
10
11crate::ferro_thread_local! {
12    static ACTIVE_TIMERS_COUNT: Cell<i32> = const { Cell::new(0) };
13}
14
15/// A timer that is integrated into the [`Dispatcher`] queues, and will be
16/// processed after a given amount of time at a specified priority.
17///
18/// Timers belong to the thread of their dispatcher: they are created,
19/// started and stopped there, and that is where `tick` handlers run. To
20/// start a timer from another thread, post a job that creates it.
21pub struct DispatcherTimer {
22    weak_self: Weak<DispatcherTimer>,
23    dispatcher: Arc<Dispatcher>,
24    priority: DispatcherPriority,
25    interval: Cell<Duration>,
26    operation: RefCell<Option<DispatcherOperation>>,
27    is_enabled: Cell<bool>,
28    // used by Dispatcher
29    due_time_in_ms: Cell<i64>,
30    tick: HandlerList<dyn Fn(&DispatcherTimer)>,
31    tag: RefCell<Option<BoxedValue>>,
32}
33
34impl DispatcherTimer {
35    /// The number of started timers on the calling thread.
36    #[allow(dead_code)]
37    pub(crate) fn active_timers_count() -> i32 {
38        ACTIVE_TIMERS_COUNT.with(Cell::get)
39    }
40
41    /// Creates a timer that uses the current thread's dispatcher to process
42    /// the timer event at background priority.
43    pub fn new() -> Rc<DispatcherTimer> {
44        Self::with_interval(Duration::ZERO, DispatcherPriority::BACKGROUND, &Dispatcher::current_dispatcher())
45    }
46
47    /// Creates a timer that uses the current thread's dispatcher to process
48    /// the timer event at the specified priority.
49    pub fn with_priority(priority: DispatcherPriority) -> Rc<DispatcherTimer> {
50        Self::with_interval(Duration::ZERO, priority, &Dispatcher::current_dispatcher())
51    }
52
53    /// Creates a timer that uses the specified dispatcher to process the
54    /// timer event at the specified priority.
55    pub fn with_priority_and_dispatcher(
56        priority: DispatcherPriority,
57        dispatcher: &Arc<Dispatcher>,
58    ) -> Rc<DispatcherTimer> {
59        Self::with_interval(Duration::ZERO, priority, dispatcher)
60    }
61
62    /// Creates a timer that uses the specified dispatcher to process the
63    /// timer event at the specified priority after the specified interval.
64    ///
65    /// # Panics
66    /// Panics when the priority is invalid or inactive, when the interval is
67    /// longer than `i32::MAX` milliseconds and when called from a thread
68    /// other than the dispatcher's.
69    pub fn with_interval(
70        interval: Duration,
71        priority: DispatcherPriority,
72        dispatcher: &Arc<Dispatcher>,
73    ) -> Rc<DispatcherTimer> {
74        dispatcher.verify_access();
75
76        DispatcherPriority::validate(priority, "priority");
77        if priority == DispatcherPriority::INACTIVE {
78            panic!("Specified priority is not valid. (parameter 'priority')");
79        }
80
81        if interval.as_millis() > i32::MAX as u128 {
82            panic!("interval: TimeSpan period must be less than or equal to Int32.MaxValue.");
83        }
84
85        Rc::new_cyclic(|weak_self| DispatcherTimer {
86            weak_self: weak_self.clone(),
87            dispatcher: dispatcher.clone(),
88            priority,
89            interval: Cell::new(interval),
90            operation: RefCell::new(None),
91            is_enabled: Cell::new(false),
92            due_time_in_ms: Cell::new(0),
93            tick: HandlerList::new(),
94            tag: RefCell::new(None),
95        })
96    }
97
98    /// Creates a timer that is bound to the current thread's dispatcher and
99    /// will be processed after the specified interval, at the specified
100    /// priority, calling the specified handler. The timer is started.
101    pub fn with_callback(
102        interval: Duration,
103        priority: DispatcherPriority,
104        callback: impl Fn(&DispatcherTimer) + 'static,
105    ) -> Rc<DispatcherTimer> {
106        Self::with_callback_and_dispatcher(interval, priority, &Dispatcher::current_dispatcher(), callback)
107    }
108
109    /// Creates a timer that is bound to the specified dispatcher and will be
110    /// processed after the specified interval, at the specified priority,
111    /// calling the specified handler. The timer is started.
112    pub fn with_callback_and_dispatcher(
113        interval: Duration,
114        priority: DispatcherPriority,
115        dispatcher: &Arc<Dispatcher>,
116        callback: impl Fn(&DispatcherTimer) + 'static,
117    ) -> Rc<DispatcherTimer> {
118        let timer = Self::with_interval(interval, priority, dispatcher);
119        timer.tick.add(Rc::new(callback));
120        timer.start();
121        timer
122    }
123
124    /// Gets the dispatcher this timer is associated with.
125    pub fn dispatcher(&self) -> &Arc<Dispatcher> {
126        &self.dispatcher
127    }
128
129    /// Gets whether the timer is running.
130    pub fn is_enabled(&self) -> bool {
131        self.is_enabled.get()
132    }
133
134    /// Starts or stops the timer.
135    pub fn set_is_enabled(&self, value: bool) {
136        if !value && self.is_enabled.get() {
137            self.stop();
138        } else if value && !self.is_enabled.get() {
139            self.start();
140        }
141    }
142
143    /// Gets the time between timer ticks.
144    pub fn interval(&self) -> Duration {
145        self.interval.get()
146    }
147
148    /// Sets the time between timer ticks.
149    pub fn set_interval(&self, value: Duration) {
150        self.dispatcher.verify_access();
151        let mut update_os_timer = false;
152
153        self.interval.set(value);
154
155        if self.is_enabled.get() {
156            self.due_time_in_ms.set(self.dispatcher.now() + value.as_millis() as i64);
157            update_os_timer = true;
158        }
159
160        if update_os_timer {
161            self.dispatcher.reschedule_timers();
162        }
163    }
164
165    /// Starts the timer.
166    pub fn start(&self) {
167        self.dispatcher.verify_access();
168        if !self.is_enabled.get() {
169            self.is_enabled.set(true);
170            ACTIVE_TIMERS_COUNT.with(|count| count.set(count.get() + 1));
171
172            self.restart();
173        }
174    }
175
176    /// Stops the timer.
177    pub fn stop(&self) {
178        self.dispatcher.verify_access();
179        let mut update_os_timer = false;
180
181        if self.is_enabled.get() {
182            self.is_enabled.set(false);
183            ACTIVE_TIMERS_COUNT.with(|count| count.set(count.get() - 1));
184            update_os_timer = true;
185
186            // If the operation is in the queue, abort it.
187            let operation = self.operation.borrow_mut().take();
188            if let Some(operation) = operation {
189                operation.abort();
190            }
191        }
192
193        if update_os_timer {
194            self.dispatcher.remove_timer(self);
195        }
196    }
197
198    /// Starts a new timer.
199    ///
200    /// `action` is called on each tick; the timer stops when it returns
201    /// `false`. Disposing the returned value stops the timer.
202    pub fn run(
203        action: impl Fn() -> bool + 'static,
204        interval: Duration,
205        priority: DispatcherPriority,
206    ) -> Rc<dyn IDisposable> {
207        let timer = DispatcherTimer::with_priority(priority);
208        timer.set_interval(interval);
209
210        timer.tick.add(Rc::new(move |timer: &DispatcherTimer| {
211            if !action() {
212                timer.stop();
213            }
214        }));
215
216        timer.start();
217
218        Disposable::create(move || timer.stop())
219    }
220
221    /// Runs a method once, after the specified interval.
222    ///
223    /// Disposing the returned value cancels the timer.
224    pub fn run_once(action: impl Fn() + 'static, interval: Duration, priority: DispatcherPriority) -> Rc<dyn IDisposable> {
225        // One tick (100ns) when the interval is zero.
226        let interval = if interval != Duration::ZERO { interval } else { Duration::from_nanos(100) };
227
228        let timer = DispatcherTimer::with_priority(priority);
229        timer.set_interval(interval);
230
231        timer.tick.add(Rc::new(move |timer: &DispatcherTimer| {
232            action();
233            timer.stop();
234        }));
235
236        timer.start();
237
238        Disposable::create(move || timer.stop())
239    }
240
241    /// Occurs when the specified timer interval has elapsed and the timer is
242    /// enabled.
243    pub fn tick(&self, handler: impl Fn(&DispatcherTimer) + 'static) -> Rc<dyn IDisposable> {
244        let token = self.tick.add(Rc::new(handler));
245        let timer = self.weak_self.clone();
246        Disposable::create(move || {
247            if let Some(timer) = timer.upgrade() {
248                timer.tick.remove(token);
249            }
250        })
251    }
252
253    /// Any data that the caller wants to pass along with the timer.
254    pub fn tag(&self) -> Option<BoxedValue> {
255        self.tag.borrow().clone()
256    }
257
258    pub fn set_tag(&self, value: Option<BoxedValue>) {
259        *self.tag.borrow_mut() = value;
260    }
261
262    fn restart(&self) {
263        if self.operation.borrow().is_some() {
264            // Timer has already been restarted, e.g. start was called from the tick handler.
265            return;
266        }
267        let Some(this) = self.weak_self.upgrade() else {
268            return;
269        };
270
271        // BeginInvoke a new operation.
272        let timer = this.clone();
273        let operation =
274            self.dispatcher.invoke_async_local_with_priority(move || timer.fire_tick(), DispatcherPriority::INACTIVE);
275        *self.operation.borrow_mut() = Some(operation);
276
277        self.due_time_in_ms.set(self.dispatcher.now() + self.interval.get().as_millis() as i64);
278
279        if self.interval.get().as_millis() == 0 && self.dispatcher.check_access() {
280            // shortcut - just promote the item now
281            self.promote();
282        } else {
283            self.dispatcher.add_timer(&this);
284        }
285    }
286
287    // called from Dispatcher
288    pub(crate) fn promote(&self) {
289        // Simply promote the operation to it's desired priority.
290        let operation = self.operation.borrow().clone();
291        if let Some(operation) = operation {
292            operation.set_priority(self.priority);
293        }
294    }
295
296    pub(crate) fn due_time_in_ms(&self) -> i64 {
297        self.due_time_in_ms.get()
298    }
299
300    /// Moves the due time when the dispatcher switches to another clock.
301    pub(crate) fn shift_due_time(&self, shift: i64) {
302        self.due_time_in_ms.set(self.due_time_in_ms.get().wrapping_add(shift));
303    }
304
305    fn fire_tick(&self) {
306        // The operation has been invoked, so forget about it.
307        let operation = self.operation.borrow_mut().take();
308        drop(operation);
309
310        // The dispatcher thread is calling us because item's priority
311        // was changed from inactive to something else.
312        for (_, handler) in self.tick.snapshot().iter() {
313            handler(self);
314        }
315
316        // If we are still enabled, start the timer again.
317        if self.is_enabled.get() {
318            self.restart();
319        }
320    }
321}