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 task: Box<dyn Send + 'static + FnMut(&mut T) -> bool>,
21
22 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#[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}