interval_task/
runner.rs

1#![doc = include_str!("runner.md")]
2
3use educe::Educe;
4use std::sync::Arc;
5use std::thread::{self, JoinHandle};
6use std::time::{Duration, Instant};
7
8pub fn new_runner<T: 'static>(
9    interval: Duration,
10    ctx_func: impl Send + 'static + FnOnce() -> T,
11    task: impl Send + 'static + FnMut(&mut T) -> bool,
12) -> Runner<T> {
13    Runner::new(interval, ctx_func, task)
14}
15
16struct Task<T> {
17    /// return [`true`] to break the loop and stop runner
18    /// don't forget you can call [`Runner::join`] to wait for runner thread to finish
19    /// for arg, see [`Task<T>::ctx_func`]
20    task: Box<dyn Send + 'static + FnMut(&mut T) -> bool>,
21
22    /// This will be executed inside runner thread when started
23    /// And will be passed as arg to [`Task<T>::task`]
24    ctx_func: Box<dyn Send + 'static + FnOnce() -> T>,
25
26    interval: Duration,
27}
28unsafe impl<T> Send for Task<T> {}
29unsafe impl<T> Sync for Task<T> {}
30
31/// the basic runner
32///
33/// the runner can only [`Runner::start`] once.
34/// drop the runner or return `true` in task to stop it.
35/// you can take the thread join handle to wait until it stopped.
36#[derive(Educe, Default)]
37#[educe(Debug)]
38pub struct Runner<T> {
39    pub thread: Option<JoinHandle<()>>,
40    guard: Option<Arc<()>>,
41
42    #[educe(Debug(ignore))]
43    t: Option<Task<T>>,
44}
45unsafe impl<T> Send for Runner<T> {}
46unsafe impl<T> Sync for Runner<T> {}
47
48impl<T: 'static> Runner<T> {
49    pub fn new(
50        interval: Duration,
51        ctx_func: impl Send + 'static + FnOnce() -> T,
52        task: impl Send + 'static + FnMut(&mut T) -> bool,
53    ) -> Self {
54        Runner {
55            t: Some(Task {
56                interval,
57                task: Box::new(task),
58                ctx_func: Box::new(ctx_func),
59            }),
60            guard: None,
61            thread: None,
62        }
63    }
64
65    pub fn start(&mut self) -> Result<(), &str> {
66        if self.thread.is_some() {
67            return Ok(());
68        }
69
70        let handle = Arc::new(());
71        let handle_weak = Arc::downgrade(&handle);
72
73        self.guard = Some(handle);
74
75        let task = self.t.take().unwrap();
76        self.thread = Some(thread::spawn(move || {
77            if handle_weak.upgrade().is_none() {
78                return;
79            }
80
81            let interval = task.interval;
82            let mut ctx = (task.ctx_func)();
83            let mut task = task.task;
84
85            let mut last_cost = Duration::from_nanos(0);
86
87            loop {
88                let frame_start = Instant::now() - last_cost;
89
90                if task(&mut ctx) {
91                    break;
92                };
93
94                if handle_weak.upgrade().is_none() {
95                    return;
96                }
97
98                let last_cost_start = Instant::now();
99                if let Some(gap) = interval.checked_sub(frame_start.elapsed()) {
100                    spin_sleep::sleep(gap);
101                    last_cost = last_cost_start.elapsed() - gap;
102                } else {
103                    last_cost = last_cost_start.elapsed();
104                }
105            }
106        }));
107        Ok(())
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use std::{cell::Cell, rc::Rc};
114
115    use super::*;
116
117    fn normal_internal(fps: u64) {
118        let interval = Duration::from_micros(1_000_000 / fps);
119        let mut runner = new_runner(
120            interval,
121            || Rc::new(Cell::new(0)),
122            move |count| {
123                count.set(count.get() + 1);
124                count.get() == fps
125            },
126        );
127
128        let start = Instant::now();
129        runner.start().unwrap();
130        runner.thread.map(|j| j.join());
131        println!("Elapsed: {:?}", start.elapsed());
132    }
133
134    #[test]
135    fn runner_test_60_fps() {
136        normal_internal(60)
137    }
138
139    #[test]
140    fn runner_test_120_fps() {
141        normal_internal(120)
142    }
143
144    #[test]
145    fn runner_test_144_fps() {
146        normal_internal(144)
147    }
148
149    #[test]
150    fn runner_test_240_fps() {
151        normal_internal(240)
152    }
153
154    #[test]
155    fn runner_test_1000_fps() {
156        normal_internal(1000)
157    }
158}