#![warn(missing_docs)]
use alloc::boxed::Box;
use alloc::vec::Vec;
use core::cell::{Cell, RefCell};
use crate::animations::Instant;
type TimerCallback = Box<dyn FnMut()>;
type SingleShotTimerCallback = Box<dyn FnOnce()>;
#[derive(Copy, Clone)]
#[repr(C)]
#[non_exhaustive]
pub enum TimerMode {
SingleShot,
Repeated,
}
#[derive(Default)]
pub struct Timer {
id: Cell<Option<usize>>,
}
impl Timer {
pub fn start(
&self,
mode: TimerMode,
interval: core::time::Duration,
callback: impl FnMut() + 'static,
) {
CURRENT_TIMERS.with(|timers| {
let mut timers = timers.borrow_mut();
let id = timers.start_or_restart_timer(
self.id.get(),
mode,
interval,
CallbackVariant::MultiFire(Box::new(callback)),
);
self.id.set(Some(id));
})
}
pub fn single_shot(duration: core::time::Duration, callback: impl FnOnce() + 'static) {
CURRENT_TIMERS.with(|timers| {
let mut timers = timers.borrow_mut();
let id = timers.start_or_restart_timer(
None,
TimerMode::SingleShot,
duration,
CallbackVariant::SingleShot(Box::new(callback)),
);
timers.timers[id].removed = true;
})
}
pub fn stop(&self) {
if let Some(id) = self.id.get() {
CURRENT_TIMERS.with(|timers| {
timers.borrow_mut().deactivate_timer(id);
});
}
}
pub fn restart(&self) {
if let Some(id) = self.id.get() {
CURRENT_TIMERS.with(|timers| {
timers.borrow_mut().deactivate_timer(id);
timers.borrow_mut().activate_timer(id);
});
}
}
pub fn running(&self) -> bool {
self.id
.get()
.map(|timer_id| CURRENT_TIMERS.with(|timers| timers.borrow().timers[timer_id].running))
.unwrap_or(false)
}
}
impl Drop for Timer {
fn drop(&mut self) {
if let Some(id) = self.id.get() {
let _ = CURRENT_TIMERS.try_with(|timers| {
timers.borrow_mut().remove_timer(id);
});
}
}
}
enum CallbackVariant {
Empty,
MultiFire(TimerCallback),
SingleShot(SingleShotTimerCallback),
}
impl CallbackVariant {
fn invoke(&mut self) {
use CallbackVariant::*;
match self {
Empty => (),
MultiFire(cb) => cb(),
SingleShot(_) => {
if let SingleShot(cb) = core::mem::replace(self, Empty) {
cb();
}
}
}
}
}
struct TimerData {
duration: core::time::Duration,
mode: TimerMode,
running: bool,
removed: bool,
callback: CallbackVariant,
}
#[derive(Clone, Copy)]
struct ActiveTimer {
id: usize,
timeout: Instant,
}
#[derive(Default)]
pub struct TimerList {
timers: slab::Slab<TimerData>,
active_timers: Vec<ActiveTimer>,
callback_active: Option<usize>,
}
impl TimerList {
pub fn next_timeout() -> Option<Instant> {
CURRENT_TIMERS.with(|timers| {
timers
.borrow()
.active_timers
.first()
.map(|first_active_timer| first_active_timer.timeout)
})
}
pub fn maybe_activate_timers(now: Instant) -> bool {
if TimerList::next_timeout().map(|timeout| now < timeout).unwrap_or(false) {
return false;
}
CURRENT_TIMERS.with(|timers| {
assert!(timers.borrow().callback_active.is_none(), "Recursion in timer code");
let mut any_activated = false;
let timers_to_process = core::mem::take(&mut timers.borrow_mut().active_timers);
for active_timer in timers_to_process.into_iter() {
if active_timer.timeout <= now {
any_activated = true;
let mut callback = {
let mut timers = timers.borrow_mut();
timers.callback_active = Some(active_timer.id);
if matches!(timers.timers[active_timer.id].mode, TimerMode::Repeated) {
timers.activate_timer(active_timer.id);
}
core::mem::replace(
&mut timers.timers[active_timer.id].callback,
CallbackVariant::Empty,
)
};
callback.invoke();
let mut timers = timers.borrow_mut();
let callback_register = &mut timers.timers[active_timer.id].callback;
if matches!(callback_register, CallbackVariant::Empty) {
*callback_register = callback;
}
timers.callback_active = None;
if timers.timers[active_timer.id].removed {
timers.timers.remove(active_timer.id);
}
} else {
timers.borrow_mut().register_active_timer(active_timer);
}
}
any_activated
})
}
fn start_or_restart_timer(
&mut self,
id: Option<usize>,
mode: TimerMode,
duration: core::time::Duration,
callback: CallbackVariant,
) -> usize {
let timer_data = TimerData { duration, mode, running: false, removed: false, callback };
let inactive_timer_id = if let Some(id) = id {
self.deactivate_timer(id);
self.timers[id] = timer_data;
id
} else {
self.timers.insert(timer_data)
};
self.activate_timer(inactive_timer_id);
inactive_timer_id
}
fn deactivate_timer(&mut self, id: usize) {
let mut i = 0;
while i < self.active_timers.len() {
if self.active_timers[i].id == id {
self.active_timers.remove(i);
self.timers[id].running = false;
break;
} else {
i += 1;
}
}
}
fn activate_timer(&mut self, timer_id: usize) {
self.register_active_timer(ActiveTimer {
id: timer_id,
timeout: Instant::now() + self.timers[timer_id].duration,
});
}
fn register_active_timer(&mut self, new_active_timer: ActiveTimer) {
let insertion_index = lower_bound(&self.active_timers, |existing_timer| {
existing_timer.timeout < new_active_timer.timeout
});
self.active_timers.insert(insertion_index, new_active_timer);
self.timers[new_active_timer.id].running = true;
}
fn remove_timer(&mut self, timer_id: usize) {
self.deactivate_timer(timer_id);
if self.callback_active == Some(timer_id) {
self.timers[timer_id].removed = true;
} else {
self.timers.remove(timer_id);
}
}
}
#[cfg(all(not(feature = "std"), feature = "unsafe-single-threaded"))]
use crate::unsafe_single_threaded::thread_local;
thread_local!(static CURRENT_TIMERS : RefCell<TimerList> = RefCell::default());
fn lower_bound<T>(vec: &[T], mut less_than: impl FnMut(&T) -> bool) -> usize {
let mut left = 0;
let mut right = vec.len();
while left != right {
let mid = left + (right - left) / 2;
let value = &vec[mid];
if less_than(value) {
left = mid + 1;
} else {
right = mid;
}
}
left
}
#[cfg(feature = "ffi")]
pub(crate) mod ffi {
#![allow(unsafe_code)]
use super::*;
#[allow(non_camel_case_types)]
type c_void = ();
struct WrapFn {
callback: extern "C" fn(*mut c_void),
user_data: *mut c_void,
drop_user_data: Option<extern "C" fn(*mut c_void)>,
}
impl Drop for WrapFn {
fn drop(&mut self) {
if let Some(x) = self.drop_user_data {
x(self.user_data)
}
}
}
impl WrapFn {
fn call(&self) {
(self.callback)(self.user_data)
}
}
#[no_mangle]
pub extern "C" fn slint_timer_start(
id: i64,
mode: TimerMode,
duration: u64,
callback: extern "C" fn(*mut c_void),
user_data: *mut c_void,
drop_user_data: Option<extern "C" fn(*mut c_void)>,
) -> i64 {
let wrap = WrapFn { callback, user_data, drop_user_data };
let timer = Timer::default();
if id != -1 {
timer.id.set(Some(id as _));
}
timer.start(mode, core::time::Duration::from_millis(duration), move || wrap.call());
timer.id.take().map(|x| x as i64).unwrap_or(-1)
}
#[no_mangle]
pub extern "C" fn slint_timer_singleshot(
delay: u64,
callback: extern "C" fn(*mut c_void),
user_data: *mut c_void,
drop_user_data: Option<extern "C" fn(*mut c_void)>,
) {
let wrap = WrapFn { callback, user_data, drop_user_data };
Timer::single_shot(core::time::Duration::from_millis(delay), move || wrap.call());
}
#[no_mangle]
pub extern "C" fn slint_timer_destroy(id: i64) {
if id == -1 {
return;
}
let timer = Timer { id: Cell::new(Some(id as _)) };
drop(timer);
}
#[no_mangle]
pub extern "C" fn slint_timer_stop(id: i64) {
if id == -1 {
return;
}
let timer = Timer { id: Cell::new(Some(id as _)) };
timer.stop();
timer.id.take(); }
#[no_mangle]
pub extern "C" fn slint_timer_restart(id: i64) {
if id == -1 {
return;
}
let timer = Timer { id: Cell::new(Some(id as _)) };
timer.restart();
timer.id.take(); }
#[no_mangle]
pub extern "C" fn slint_timer_running(id: i64) -> bool {
if id == -1 {
return false;
}
let timer = Timer { id: Cell::new(Some(id as _)) };
let running = timer.running();
timer.id.take(); running
}
}
#[cfg(doctest)]
const _TIMER_TESTS: () = ();