/* LICENSE BEGIN This file is part of the SixtyFPS Project -- https://sixtyfps.io Copyright (c) 2020 Olivier Goffart Copyright (c) 2020 Simon Hausmann SPDX-License-Identifier: GPL-3.0-only This file is also available under commercial licensing terms. Please contact info@sixtyfps.io for more information. LICENSE END */ /*! Support for timers. Timers are just a bunch of callbacks sorted by expiry date. */ #![warn(missing_docs)] use std::cell::{Cell, RefCell}; type TimerCallback = Box; type SingleShotTimerCallback = Box; /// The TimerMode specifies what should happen after the timer fired. /// /// Used by the [`Timer::start`] function. #[derive(Copy, Clone)] pub enum TimerMode { /// A SingleShot timer is fired only once. SingleShot, /// A Repeated timer is fired repeatedly until it is stopped. Repeated, } /// Timer is a handle to the timer system that allows triggering a callback to be called /// after a specified period of time. #[derive(Default)] pub struct Timer { id: Cell>, } impl Timer { /// Starts the timer with the given mode and duration, in order for the callback to called when the /// timer fires. If the timer has been started previously and not fired yet, then it will be restarted. /// /// Arguments: /// * `mode`: The timer mode to apply, i.e. whether to repeatedly fire the timer or just once. /// * `duration`: The duration from now until when the timer should fire. /// * `callback`: The function to call when the time has been reached or exceeded. pub fn start( &self, mode: TimerMode, duration: std::time::Duration, callback: impl Fn() + 'static, ) { CURRENT_TIMERS.with(|timers| { let mut timers = timers.borrow_mut(); let id = timers.start_or_restart_timer( self.id.get(), mode, duration, CallbackVariant::MultiFire(Box::new(callback)), ); self.id.set(Some(id)); }) } /// Starts the timer with the duration, in order for the callback to called when the /// timer fires. It is fired only once and then deleted. /// /// Arguments: /// * `duration`: The duration from now until when the timer should fire. /// * `callback`: The function to call when the time has been reached or exceeded. pub fn single_shot(duration: std::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; }) } /// Stops the previously started timer. Does nothing if the timer has never been started. A stopped /// timer cannot be restarted with restart() -- instead you need to call start(). pub fn stop(&self) { if let Some(id) = self.id.take() { CURRENT_TIMERS.with(|timers| { timers.borrow_mut().remove_timer(id); }); } } /// Restarts the timer, if it was previously started. 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); }); } } /// Returns true if the timer is running; false otherwise. 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() { CURRENT_TIMERS.with(|timers| { timers.borrow_mut().remove_timer(id); }) } } } enum CallbackVariant { Empty, MultiFire(TimerCallback), SingleShot(SingleShotTimerCallback), } impl CallbackVariant { fn invoke(self) -> Self { match self { CallbackVariant::Empty => CallbackVariant::Empty, CallbackVariant::MultiFire(cb) => { cb(); CallbackVariant::MultiFire(cb) } CallbackVariant::SingleShot(cb) => { cb(); CallbackVariant::Empty } } } } struct TimerData { duration: std::time::Duration, mode: TimerMode, running: bool, /// Set to true when it is removed when the callback is still running removed: bool, callback: CallbackVariant, } #[derive(Clone, Copy)] struct ActiveTimer { id: usize, timeout: instant::Instant, } /// TimerList provides the interface to the event loop for activating times and /// determining the nearest timeout. #[derive(Default)] pub struct TimerList { timers: vec_arena::Arena, active_timers: Vec, /// If a callback is currently running, this is the id of the currently running callback callback_active: Option, } impl TimerList { /// Returns the timeout of the timer that should fire the soonest, or None if there /// is no timer active. pub fn next_timeout() -> Option { CURRENT_TIMERS.with(|timers| { timers .borrow() .active_timers .first() .map(|first_active_timer| first_active_timer.timeout) }) } /// Activates any expired timers by calling their callback function. Returns true if any timers were /// activated; false otherwise. pub fn maybe_activate_timers() -> bool { let now = instant::Instant::now(); // Shortcut: Is there any timer worth activating? 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; // The active timer list is cleared here and not-yet-fired ones are inserted below, in order to allow // timer callbacks to register their own timers. let timers_to_process = std::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; timers.borrow_mut().callback_active = Some(active_timer.id); let callback = std::mem::replace( &mut timers.borrow_mut().timers[active_timer.id].callback, CallbackVariant::Empty, ); let callback = callback.invoke(); let mut timers = timers.borrow_mut(); timers.timers[active_timer.id].callback = callback; timers.callback_active = None; if timers.timers[active_timer.id].removed { timers.timers.remove(active_timer.id); } else if matches!(timers.timers[active_timer.id].mode, TimerMode::Repeated) { timers.activate_timer(active_timer.id); } } else { timers.borrow_mut().register_active_timer(active_timer); } } any_activated }) } fn start_or_restart_timer( &mut self, id: Option, mode: TimerMode, duration: std::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::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); } } } thread_local!(static CURRENT_TIMERS : RefCell = RefCell::default()); fn lower_bound(vec: &Vec, 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(not(target_arch = "wasm32"))] 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, } impl Drop for WrapFn { fn drop(&mut self) { if let Some(x) = self.drop_user_data { x(self.user_data) } } } /// Start a timer with the given duration in millisecond. /// Returns the timer id. /// The timer MUST be stopped with sixtyfps_timer_stop #[no_mangle] pub extern "C" fn sixtyfps_timer_start( duration: u64, callback: extern "C" fn(*mut c_void), user_data: *mut c_void, drop_user_data: Option, ) -> i64 { let wrap = WrapFn { callback, user_data, drop_user_data }; let timer = Timer::default(); timer.start(TimerMode::Repeated, core::time::Duration::from_millis(duration), move || { (wrap.callback)(wrap.user_data) }); timer.id.get().map(|x| x as i64).unwrap_or(-1) } /// Execute a callback with a delay in milisecond #[no_mangle] pub extern "C" fn sixtyfps_timer_singleshot( delay: u64, callback: extern "C" fn(*mut c_void), user_data: *mut c_void, drop_user_data: Option, ) { let wrap = WrapFn { callback, user_data, drop_user_data }; Timer::single_shot(core::time::Duration::from_millis(delay), move || { (wrap.callback)(wrap.user_data) }); } /// Stop a timer and free its raw data #[no_mangle] pub extern "C" fn sixtyfps_timer_stop(id: i64) { if id == -1 { return; } let timer = Timer { id: Cell::new(Some(id as _)) }; timer.stop() } }