1use std::time::Duration;
2use std::thread;
3use crossbeam_channel::Sender;
4use crate::message::Message;
5
6pub struct TimerHandle {
8 _cancel: Option<std::sync::mpsc::Sender<()>>,
9}
10
11pub struct Timer;
13
14impl Timer {
15 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 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 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}