canic_core/api/
timer.rs

1use crate::ops::runtime::timer::{TimerId, TimerOps};
2use std::{cell::RefCell, future::Future, rc::Rc, thread::LocalKey, time::Duration};
3
4///
5/// TimerApi
6/// Lifecycle timer api façade for macro-expanded entrypoints.
7///
8
9///
10/// ApiTimerHandle
11/// Opaque handle for scheduled timers (no direct access to TimerId).
12///
13
14#[derive(Clone, Copy, Debug, Eq, PartialEq)]
15pub struct ApiTimerHandle(TimerId);
16
17///
18/// TimerSlot
19/// Opaque timer slot handle for guarded scheduling.
20///
21
22pub type TimerSlot = LocalKey<RefCell<Option<ApiTimerHandle>>>;
23
24/// Public, stable surface for macro-expanded code in downstream crates.
25/// Performs no logic; delegates to internal TimerOps.
26pub fn set_lifecycle_timer(
27    delay: Duration,
28    label: impl Into<String>,
29    task: impl Future<Output = ()> + 'static,
30) -> ApiTimerHandle {
31    ApiTimerHandle(TimerOps::set(delay, label, task))
32}
33
34/// Schedule a one-shot timer only if the slot is empty.
35/// Returns true when a new timer was scheduled.
36pub fn set_guarded(
37    slot: &'static TimerSlot,
38    delay: Duration,
39    label: impl Into<String>,
40    task: impl Future<Output = ()> + 'static,
41) -> bool {
42    slot.with_borrow_mut(|entry| {
43        if entry.is_some() {
44            return false;
45        }
46
47        let id = TimerOps::set(delay, label, task);
48        *entry = Some(ApiTimerHandle(id));
49        true
50    })
51}
52
53/// Schedule a repeating timer. Task produces a fresh Future on each tick.
54pub fn set_interval<F, Fut>(interval: Duration, label: impl Into<String>, task: F) -> ApiTimerHandle
55where
56    F: FnMut() -> Fut + 'static,
57    Fut: Future<Output = ()> + 'static,
58{
59    ApiTimerHandle(TimerOps::set_interval(interval, label, task))
60}
61
62/// Schedule a guarded init timer that installs a repeating interval timer.
63pub fn set_guarded_interval<FInit, InitFut, FTick, TickFut>(
64    slot: &'static TimerSlot,
65    init_delay: Duration,
66    init_label: impl Into<String>,
67    init_task: FInit,
68    interval: Duration,
69    interval_label: impl Into<String>,
70    tick_task: FTick,
71) -> bool
72where
73    FInit: FnOnce() -> InitFut + 'static,
74    InitFut: Future<Output = ()> + 'static,
75    FTick: FnMut() -> TickFut + 'static,
76    TickFut: Future<Output = ()> + 'static,
77{
78    let init_label = init_label.into();
79    let interval_label = interval_label.into();
80
81    slot.with_borrow_mut(|entry| {
82        if entry.is_some() {
83            return false;
84        }
85
86        let init_id_cell = Rc::new(RefCell::new(None));
87        let init_id_cell_task = Rc::clone(&init_id_cell);
88
89        let init_id = TimerOps::set(init_delay, init_label, async move {
90            init_task().await;
91
92            let init_id = init_id_cell_task.borrow();
93            let Some(init_id) = init_id.as_ref() else {
94                return;
95            };
96
97            let still_armed = slot.with_borrow(|slot_val| slot_val.as_ref() == Some(init_id));
98            if !still_armed {
99                return;
100            }
101
102            let interval_id = TimerOps::set_interval(interval, interval_label, tick_task);
103            let interval_handle = ApiTimerHandle(interval_id);
104
105            // Atomically replace the slot value and clear the previous timer id.
106            // This prevents orphaned timers if callers clear around the handover.
107            slot.with_borrow_mut(|slot_val| {
108                let old = slot_val.replace(interval_handle);
109                if let Some(old_id) = old
110                    && old_id != interval_handle
111                {
112                    TimerOps::clear(old_id.0);
113                }
114            });
115        });
116
117        let init_handle = ApiTimerHandle(init_id);
118        *init_id_cell.borrow_mut() = Some(init_handle);
119        *entry = Some(init_handle);
120        true
121    })
122}
123
124/// Optional cancellation.
125pub fn clear_lifecycle_timer(handle: ApiTimerHandle) {
126    TimerOps::clear(handle.0);
127}