Skip to main content

sprite_core/
timer.rs

1use std::time::Duration;
2use std::thread;
3use crossbeam_channel::Sender;
4use crate::message::Message;
5
6/// A handle to a scheduled timer. Drop it to cancel (best-effort).
7pub struct TimerHandle {
8    _cancel: Option<std::sync::mpsc::Sender<()>>,
9}
10
11/// Schedule delayed and repeating messages.
12pub struct Timer;
13
14impl Timer {
15    /// Run a closure after a delay.
16    pub fn set_timeout<F>(delay: Duration, f: F) -> TimerHandle
17    where F: FnOnce() + Send + 'static,
18    {
19        let (cancel_tx, cancel_rx) = std::sync::mpsc::channel();
20        thread::spawn(move || {
21            match cancel_rx.recv_timeout(delay) {
22                Ok(()) => return,
23                Err(_) => f(),
24            }
25        });
26        TimerHandle { _cancel: Some(cancel_tx) }
27    }
28
29    /// Send a message after a delay.
30    pub fn send_after(tx: Sender<Message>, delay: Duration, msg: Message) -> TimerHandle {
31        Self::set_timeout(delay, move || { let _ = tx.send(msg); })
32    }
33
34    /// Repeatedly send a message every interval.
35    pub fn set_interval(tx: Sender<Message>, interval: Duration, msg: Message) -> TimerHandle {
36        let (cancel_tx, cancel_rx) = std::sync::mpsc::channel();
37        thread::spawn(move || {
38            loop {
39                match cancel_rx.recv_timeout(interval) {
40                    Ok(()) => break,
41                    Err(_) => { if tx.send(msg.clone()).is_err() { break; } }
42                }
43            }
44        });
45        TimerHandle { _cancel: Some(cancel_tx) }
46    }
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52    use crossbeam_channel::unbounded;
53    #[test]
54    fn timeout_fires() {
55        let (tx, rx) = unbounded();
56        let _ = Timer::send_after(tx, Duration::from_millis(10), Message::text("hi"));
57        let msg = rx.recv_timeout(Duration::from_millis(100)).unwrap();
58        assert_eq!(msg.as_str(), Some("hi"));
59    }
60    #[test]
61    fn interval_fires() {
62        let (tx, rx) = unbounded();
63        let _ = Timer::set_interval(tx, Duration::from_millis(5), Message::text("tick"));
64        let mut count = 0;
65        while rx.recv_timeout(Duration::from_millis(50)).is_ok() {
66            count += 1;
67            if count >= 3 { break; }
68        }
69        assert!(count >= 3);
70    }
71}