use std::cell::{Cell, RefCell};
use std::rc::{Rc, Weak};
use std::sync::Arc;
use std::time::Duration;
use super::{Dispatcher, DispatcherOperation, DispatcherPriority};
use crate::reactive::{Disposable, IDisposable};
use crate::utilities::HandlerList;
use crate::BoxedValue;
crate::ferro_thread_local! {
static ACTIVE_TIMERS_COUNT: Cell<i32> = const { Cell::new(0) };
}
pub struct DispatcherTimer {
weak_self: Weak<DispatcherTimer>,
dispatcher: Arc<Dispatcher>,
priority: DispatcherPriority,
interval: Cell<Duration>,
operation: RefCell<Option<DispatcherOperation>>,
is_enabled: Cell<bool>,
due_time_in_ms: Cell<i64>,
tick: HandlerList<dyn Fn(&DispatcherTimer)>,
tag: RefCell<Option<BoxedValue>>,
}
impl DispatcherTimer {
#[allow(dead_code)]
pub(crate) fn active_timers_count() -> i32 {
ACTIVE_TIMERS_COUNT.with(Cell::get)
}
pub fn new() -> Rc<DispatcherTimer> {
Self::with_interval(Duration::ZERO, DispatcherPriority::BACKGROUND, &Dispatcher::current_dispatcher())
}
pub fn with_priority(priority: DispatcherPriority) -> Rc<DispatcherTimer> {
Self::with_interval(Duration::ZERO, priority, &Dispatcher::current_dispatcher())
}
pub fn with_priority_and_dispatcher(
priority: DispatcherPriority,
dispatcher: &Arc<Dispatcher>,
) -> Rc<DispatcherTimer> {
Self::with_interval(Duration::ZERO, priority, dispatcher)
}
pub fn with_interval(
interval: Duration,
priority: DispatcherPriority,
dispatcher: &Arc<Dispatcher>,
) -> Rc<DispatcherTimer> {
dispatcher.verify_access();
DispatcherPriority::validate(priority, "priority");
if priority == DispatcherPriority::INACTIVE {
panic!("Specified priority is not valid. (parameter 'priority')");
}
if interval.as_millis() > i32::MAX as u128 {
panic!("interval: TimeSpan period must be less than or equal to Int32.MaxValue.");
}
Rc::new_cyclic(|weak_self| DispatcherTimer {
weak_self: weak_self.clone(),
dispatcher: dispatcher.clone(),
priority,
interval: Cell::new(interval),
operation: RefCell::new(None),
is_enabled: Cell::new(false),
due_time_in_ms: Cell::new(0),
tick: HandlerList::new(),
tag: RefCell::new(None),
})
}
pub fn with_callback(
interval: Duration,
priority: DispatcherPriority,
callback: impl Fn(&DispatcherTimer) + 'static,
) -> Rc<DispatcherTimer> {
Self::with_callback_and_dispatcher(interval, priority, &Dispatcher::current_dispatcher(), callback)
}
pub fn with_callback_and_dispatcher(
interval: Duration,
priority: DispatcherPriority,
dispatcher: &Arc<Dispatcher>,
callback: impl Fn(&DispatcherTimer) + 'static,
) -> Rc<DispatcherTimer> {
let timer = Self::with_interval(interval, priority, dispatcher);
timer.tick.add(Rc::new(callback));
timer.start();
timer
}
pub fn dispatcher(&self) -> &Arc<Dispatcher> {
&self.dispatcher
}
pub fn is_enabled(&self) -> bool {
self.is_enabled.get()
}
pub fn set_is_enabled(&self, value: bool) {
if !value && self.is_enabled.get() {
self.stop();
} else if value && !self.is_enabled.get() {
self.start();
}
}
pub fn interval(&self) -> Duration {
self.interval.get()
}
pub fn set_interval(&self, value: Duration) {
self.dispatcher.verify_access();
let mut update_os_timer = false;
self.interval.set(value);
if self.is_enabled.get() {
self.due_time_in_ms.set(self.dispatcher.now() + value.as_millis() as i64);
update_os_timer = true;
}
if update_os_timer {
self.dispatcher.reschedule_timers();
}
}
pub fn start(&self) {
self.dispatcher.verify_access();
if !self.is_enabled.get() {
self.is_enabled.set(true);
ACTIVE_TIMERS_COUNT.with(|count| count.set(count.get() + 1));
self.restart();
}
}
pub fn stop(&self) {
self.dispatcher.verify_access();
let mut update_os_timer = false;
if self.is_enabled.get() {
self.is_enabled.set(false);
ACTIVE_TIMERS_COUNT.with(|count| count.set(count.get() - 1));
update_os_timer = true;
let operation = self.operation.borrow_mut().take();
if let Some(operation) = operation {
operation.abort();
}
}
if update_os_timer {
self.dispatcher.remove_timer(self);
}
}
pub fn run(
action: impl Fn() -> bool + 'static,
interval: Duration,
priority: DispatcherPriority,
) -> Rc<dyn IDisposable> {
let timer = DispatcherTimer::with_priority(priority);
timer.set_interval(interval);
timer.tick.add(Rc::new(move |timer: &DispatcherTimer| {
if !action() {
timer.stop();
}
}));
timer.start();
Disposable::create(move || timer.stop())
}
pub fn run_once(action: impl Fn() + 'static, interval: Duration, priority: DispatcherPriority) -> Rc<dyn IDisposable> {
let interval = if interval != Duration::ZERO { interval } else { Duration::from_nanos(100) };
let timer = DispatcherTimer::with_priority(priority);
timer.set_interval(interval);
timer.tick.add(Rc::new(move |timer: &DispatcherTimer| {
action();
timer.stop();
}));
timer.start();
Disposable::create(move || timer.stop())
}
pub fn tick(&self, handler: impl Fn(&DispatcherTimer) + 'static) -> Rc<dyn IDisposable> {
let token = self.tick.add(Rc::new(handler));
let timer = self.weak_self.clone();
Disposable::create(move || {
if let Some(timer) = timer.upgrade() {
timer.tick.remove(token);
}
})
}
pub fn tag(&self) -> Option<BoxedValue> {
self.tag.borrow().clone()
}
pub fn set_tag(&self, value: Option<BoxedValue>) {
*self.tag.borrow_mut() = value;
}
fn restart(&self) {
if self.operation.borrow().is_some() {
return;
}
let Some(this) = self.weak_self.upgrade() else {
return;
};
let timer = this.clone();
let operation =
self.dispatcher.invoke_async_local_with_priority(move || timer.fire_tick(), DispatcherPriority::INACTIVE);
*self.operation.borrow_mut() = Some(operation);
self.due_time_in_ms.set(self.dispatcher.now() + self.interval.get().as_millis() as i64);
if self.interval.get().as_millis() == 0 && self.dispatcher.check_access() {
self.promote();
} else {
self.dispatcher.add_timer(&this);
}
}
pub(crate) fn promote(&self) {
let operation = self.operation.borrow().clone();
if let Some(operation) = operation {
operation.set_priority(self.priority);
}
}
pub(crate) fn due_time_in_ms(&self) -> i64 {
self.due_time_in_ms.get()
}
pub(crate) fn shift_due_time(&self, shift: i64) {
self.due_time_in_ms.set(self.due_time_in_ms.get().wrapping_add(shift));
}
fn fire_tick(&self) {
let operation = self.operation.borrow_mut().take();
drop(operation);
for (_, handler) in self.tick.snapshot().iter() {
handler(self);
}
if self.is_enabled.get() {
self.restart();
}
}
}