use std::{
cell::{Cell, RefCell},
cmp::Ordering,
collections::BinaryHeap,
pin::Pin,
time::Duration,
};
use slotmap::{SlotMap, new_key_type};
thread_local! {
pub(crate) static TIMER_COUNTER: Cell<u128> = const { Cell::new(0) };
pub(crate) static TASKS: RefCell<SlotMap<TaskId, Task>> = RefCell::default();
pub(crate) static TIMERS: RefCell<BinaryHeap<Timer>> = RefCell::default();
static MOST_RECENT: Cell<Option<u64>> = const { Cell::new(None) };
pub(crate) static ALL_CALLS: Cell<usize> = const { Cell::new(0) };
}
pub(crate) enum Task {
Once(Pin<Box<dyn Future<Output = ()>>>),
Repeated {
func: Box<dyn FnMut() -> Pin<Box<dyn Future<Output = ()>>>>,
interval: Duration,
concurrent_calls: usize,
},
RepeatedSerial {
func: Box<dyn SerialClosure>,
interval: Duration,
},
RepeatedSerialBusy {
interval: Duration,
},
Invalid,
}
pub(crate) trait SerialClosure {
fn call<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = ()> + 'a>>;
}
impl<F: AsyncFnMut()> SerialClosure for F {
fn call<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = ()> + 'a>> {
Box::pin(self())
}
}
new_key_type! {
#[expect(missing_docs)] pub struct TaskId;
}
#[derive(Debug)]
pub(crate) struct Timer {
pub(crate) task: TaskId,
pub(crate) time: u64,
pub(crate) counter: u128,
}
impl Ord for Timer {
fn cmp(&self, other: &Self) -> Ordering {
self.time
.cmp(&other.time)
.then_with(|| self.counter.cmp(&other.counter))
.reverse()
}
}
impl PartialOrd for Timer {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for Timer {
fn eq(&self, other: &Self) -> bool {
self.time == other.time
}
}
impl Eq for Timer {}
pub(crate) fn next_counter() -> u128 {
TIMER_COUNTER.with(|c| {
let v = c.get();
c.set(v + 1);
v
})
}
pub(crate) fn update_ic0_timer() {
TIMERS.with_borrow(|timers| {
let soonest_timer = timers.peek().map(|timer| timer.time);
let should_change = match (soonest_timer, MOST_RECENT.get()) {
(Some(timer), Some(recent)) => timer < recent,
(Some(_), None) => true,
_ => false,
};
if should_change {
ic0::global_timer_set(soonest_timer.unwrap());
MOST_RECENT.set(soonest_timer);
}
});
}
pub(crate) fn update_ic0_timer_clean() {
MOST_RECENT.set(None);
update_ic0_timer();
}
impl Task {
pub(crate) fn increment_concurrent(&mut self) {
if let Task::Repeated {
concurrent_calls, ..
} = self
{
*concurrent_calls += 1;
}
}
pub(crate) fn decrement_concurrent(&mut self) {
if let Task::Repeated {
concurrent_calls, ..
} = self
{
if *concurrent_calls > 0 {
*concurrent_calls -= 1;
}
}
}
}
pub(crate) fn increment_all_calls() {
ALL_CALLS.set(ALL_CALLS.get() + 1);
}
pub(crate) fn decrement_all_calls() {
let current = ALL_CALLS.get();
if current > 0 {
ALL_CALLS.set(current - 1);
}
}