sdl2/timer.rs
1use crate::sys;
2use libc::c_void;
3use std::marker::PhantomData;
4use std::mem;
5
6use crate::TimerSubsystem;
7
8impl TimerSubsystem {
9 /// Constructs a new timer using the boxed closure `callback`.
10 ///
11 /// The timer is started immediately, it will be cancelled either:
12 ///
13 /// * when the timer is dropped
14 /// * or when the callback returns a non-positive continuation interval
15 ///
16 /// The callback is run in a thread that is created and managed internally
17 /// by SDL2 from C. The callback *must* not panic!
18 #[must_use = "if unused the Timer will be dropped immediately"]
19 #[doc(alias = "SDL_AddTimer")]
20 pub fn add_timer<'b, 'c>(&'b self, delay: u32, callback: TimerCallback<'c>) -> Timer<'b, 'c> {
21 unsafe {
22 let callback = Box::new(callback);
23 let timer_id = sys::SDL_AddTimer(
24 delay,
25 Some(c_timer_callback),
26 mem::transmute_copy(&callback),
27 );
28
29 Timer {
30 callback: Some(callback),
31 raw: timer_id,
32 _marker: PhantomData,
33 }
34 }
35 }
36
37 /// Gets the number of milliseconds elapsed since the timer subsystem was initialized.
38 ///
39 /// It's recommended that you use another library for timekeeping, such as `time`.
40 ///
41 /// This function is not recommended in upstream SDL2 as of 2.0.18 and internally
42 /// calls the 64-bit variant and masks the result.
43 #[doc(alias = "SDL_GetTicks")]
44 pub fn ticks(&self) -> u32 {
45 // This is thread-safe as long as the ticks subsystem is inited, and
46 // tying this to `TimerSubsystem` ensures the timer subsystem can
47 // safely make calls into the ticks subsystem without invoking a
48 // thread-unsafe `SDL_TicksInit()`.
49 //
50 // This binding is offered for completeness but is debatably a relic.
51 unsafe { sys::SDL_GetTicks() }
52 }
53
54 /// Gets the number of milliseconds elapsed since the timer subsystem was initialized.
55 ///
56 /// It's recommended that you use another library for timekeeping, such as `time`.
57 #[doc(alias = "SDL_GetTicks64")]
58 pub fn ticks64(&self) -> u64 {
59 // This is thread-safe as long as the ticks subsystem is inited, and
60 // tying this to `TimerSubsystem` ensures the timer subsystem can
61 // safely make calls into the ticks subsystem without invoking a
62 // thread-unsafe `SDL_TicksInit()`.
63 //
64 // This binding is offered for completeness but is debatably a relic.
65 unsafe { sys::SDL_GetTicks64() }
66 }
67
68 /// Sleeps the current thread for the specified amount of milliseconds.
69 ///
70 /// It's recommended that you use `std::thread::sleep()` instead.
71 #[doc(alias = "SDL_Delay")]
72 pub fn delay(&self, ms: u32) {
73 // This is thread-safe as long as the ticks subsystem is inited, and
74 // tying this to `TimerSubsystem` ensures the timer subsystem can
75 // safely make calls into the ticks subsystem without invoking a
76 // thread-unsafe `SDL_TicksInit()`.
77 //
78 // This binding is offered for completeness but is debatably a relic.
79 unsafe { sys::SDL_Delay(ms) }
80 }
81
82 #[doc(alias = "SDL_GetPerformanceCounter")]
83 pub fn performance_counter(&self) -> u64 {
84 unsafe { sys::SDL_GetPerformanceCounter() }
85 }
86
87 #[doc(alias = "SDL_GetPerformanceFrequency")]
88 pub fn performance_frequency(&self) -> u64 {
89 unsafe { sys::SDL_GetPerformanceFrequency() }
90 }
91}
92
93pub type TimerCallback<'a> = Box<dyn FnMut() -> u32 + 'a + Send>;
94
95pub struct Timer<'b, 'a> {
96 callback: Option<Box<TimerCallback<'a>>>,
97 raw: sys::SDL_TimerID,
98 _marker: PhantomData<&'b ()>,
99}
100
101impl<'b, 'a> Timer<'b, 'a> {
102 /// Returns the closure as a trait-object and cancels the timer
103 /// by consuming it...
104 pub fn into_inner(mut self) -> TimerCallback<'a> {
105 *self.callback.take().unwrap()
106 }
107}
108
109impl<'b, 'a> Drop for Timer<'b, 'a> {
110 #[inline]
111 #[doc(alias = "SDL_RemoveTimer")]
112 fn drop(&mut self) {
113 // SDL_RemoveTimer returns SDL_FALSE if the timer wasn't found (impossible),
114 // or the timer has been cancelled via the callback (possible).
115 // The timer being cancelled isn't an issue, so we ignore the result.
116 unsafe { sys::SDL_RemoveTimer(self.raw) };
117 }
118}
119
120extern "C" fn c_timer_callback(_interval: u32, param: *mut c_void) -> u32 {
121 // FIXME: This is UB if the callback panics! (But will realistically
122 // crash on stack underflow.)
123 //
124 // I tried using `std::panic::catch_unwind()` here and it compiled but
125 // would not catch. Maybe wait for `c_unwind` to stabilize? Then the behavior
126 // will automatically abort the process when panicking over an `extern "C"`
127 // function.
128 let f = param as *mut TimerCallback<'_>;
129 unsafe { (*f)() }
130}
131
132#[cfg(not(target_os = "macos"))]
133#[cfg(test)]
134mod test {
135 use std::sync::{Arc, Mutex};
136 use std::time::Duration;
137
138 #[test]
139 fn test_timer() {
140 test_timer_runs_multiple_times();
141 test_timer_runs_at_least_once();
142 test_timer_can_be_recreated();
143 }
144
145 fn test_timer_runs_multiple_times() {
146 let sdl_context = crate::sdl::init().unwrap();
147 let timer_subsystem = sdl_context.timer().unwrap();
148
149 let local_num = Arc::new(Mutex::new(0));
150 let timer_num = local_num.clone();
151
152 let _timer = timer_subsystem.add_timer(
153 20,
154 Box::new(|| {
155 // increment up to 10 times (0 -> 9)
156 // tick again in 100ms after each increment
157 //
158 let mut num = timer_num.lock().unwrap();
159 if *num < 9 {
160 *num += 1;
161 20
162 } else {
163 0
164 }
165 }),
166 );
167
168 // tick the timer at least 10 times w/ 200ms of "buffer"
169 ::std::thread::sleep(Duration::from_millis(250));
170 let num = local_num.lock().unwrap(); // read the number back
171 assert_eq!(*num, 9); // it should have incremented at least 10 times...
172 }
173
174 fn test_timer_runs_at_least_once() {
175 let sdl_context = crate::sdl::init().unwrap();
176 let timer_subsystem = sdl_context.timer().unwrap();
177
178 let local_flag = Arc::new(Mutex::new(false));
179 let timer_flag = local_flag.clone();
180
181 let _timer = timer_subsystem.add_timer(
182 20,
183 Box::new(|| {
184 let mut flag = timer_flag.lock().unwrap();
185 *flag = true;
186 0
187 }),
188 );
189
190 ::std::thread::sleep(Duration::from_millis(50));
191 let flag = local_flag.lock().unwrap();
192 assert!(*flag);
193 }
194
195 fn test_timer_can_be_recreated() {
196 let sdl_context = crate::sdl::init().unwrap();
197 let timer_subsystem = sdl_context.timer().unwrap();
198
199 let local_num = Arc::new(Mutex::new(0));
200 let timer_num = local_num.clone();
201
202 // run the timer once and reclaim its closure
203 let timer_1 = timer_subsystem.add_timer(
204 20,
205 Box::new(move || {
206 let mut num = timer_num.lock().unwrap();
207 *num += 1; // increment the number
208 0 // do not run timer again
209 }),
210 );
211
212 // reclaim closure after timer runs
213 ::std::thread::sleep(Duration::from_millis(50));
214 let closure = timer_1.into_inner();
215
216 // create a second timer and increment again
217 let _timer_2 = timer_subsystem.add_timer(20, closure);
218 ::std::thread::sleep(Duration::from_millis(50));
219
220 // check that timer was incremented twice
221 let num = local_num.lock().unwrap();
222 assert_eq!(*num, 2);
223 }
224}