canic_core/api/
timer.rs

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