sdl3/
timer.rs

1use crate::sys;
2use libc::c_void;
3use std::ptr::NonNull;
4
5/// Constructs a new timer using the boxed closure `callback`.
6///
7/// The timer is started immediately and will be canceled either:
8///
9/// * When the timer is dropped.
10/// * When the callback returns a non-positive continuation interval.
11///
12/// The callback is run in a thread that is created and managed internally
13/// by SDL3 from C. The callback **must not panic!**
14#[must_use = "if unused the Timer will be dropped immediately"]
15#[doc(alias = "SDL_AddTimer")]
16pub fn add_timer(delay: u32, callback: TimerCallback) -> Timer {
17    unsafe {
18        // Allocate the callback on the heap and get a raw pointer.
19        let callback_ptr = Box::into_raw(Box::new(callback));
20
21        // Call SDL_AddTimer with the appropriate function signature.
22        let timer_id =
23            sys::timer::SDL_AddTimer(delay, Some(c_timer_callback), callback_ptr as *mut c_void);
24
25        Timer {
26            callback: Some(NonNull::new(callback_ptr).unwrap()),
27            raw: timer_id,
28        }
29    }
30}
31
32/// Gets the number of milliseconds elapsed since the timer subsystem was initialized.
33///
34/// It's recommended to use another library for timekeeping, such as `time`.
35#[doc(alias = "SDL_GetTicks")]
36pub fn ticks() -> u64 {
37    unsafe { sys::timer::SDL_GetTicks() }
38}
39
40/// Sleeps the current thread for the specified amount of milliseconds.
41///
42/// It's recommended to use `std::thread::sleep()` instead.
43#[doc(alias = "SDL_Delay")]
44pub fn delay(ms: u32) {
45    unsafe { sys::timer::SDL_Delay(ms) }
46}
47
48#[doc(alias = "SDL_GetPerformanceCounter")]
49pub fn performance_counter() -> u64 {
50    unsafe { sys::timer::SDL_GetPerformanceCounter() }
51}
52
53#[doc(alias = "SDL_GetPerformanceFrequency")]
54pub fn performance_frequency() -> u64 {
55    unsafe { sys::timer::SDL_GetPerformanceFrequency() }
56}
57
58/// Type alias for the timer callback function.
59pub type TimerCallback = Box<dyn FnMut() -> u32 + Send + 'static>;
60
61pub struct Timer {
62    callback: Option<NonNull<TimerCallback>>,
63    raw: sys::timer::SDL_TimerID,
64}
65
66impl Timer {
67    /// Returns the closure as a trait-object and cancels the timer
68    /// by consuming it.
69    pub fn into_inner(mut self) -> TimerCallback {
70        unsafe {
71            sys::timer::SDL_RemoveTimer(self.raw);
72            if let Some(callback_ptr) = self.callback.take() {
73                // Reconstruct the Box from the raw pointer.
74                Box::from_raw(callback_ptr.as_ptr())
75            } else {
76                panic!("Timer callback already taken");
77            }
78        }
79    }
80}
81
82impl Drop for Timer {
83    #[inline]
84    #[doc(alias = "SDL_RemoveTimer")]
85    fn drop(&mut self) {
86        unsafe {
87            sys::timer::SDL_RemoveTimer(self.raw);
88            if let Some(callback_ptr) = self.callback.take() {
89                // Reclaim the Box and drop it.
90                let _ = Box::from_raw(callback_ptr.as_ptr());
91            }
92        }
93    }
94}
95
96extern "C" fn c_timer_callback(
97    userdata: *mut c_void,
98    _timer_id: sys::timer::SDL_TimerID,
99    _interval: u32,
100) -> u32 {
101    let callback_ptr = userdata as *mut TimerCallback;
102    unsafe { (*callback_ptr)() }
103}
104
105#[cfg(not(target_os = "macos"))]
106#[cfg(test)]
107mod test {
108    use std::sync::{Arc, Mutex};
109    use std::time::Duration;
110
111    use crate::timer::add_timer;
112
113    #[test]
114    fn test_timer_runs_multiple_times() {
115        let _sdl_context = crate::sdl::init().unwrap();
116        //let timer_subsystem = sdl_context.timer().unwrap();
117
118        let local_num = Arc::new(Mutex::new(0));
119        let timer_num = local_num.clone();
120
121        let _timer = add_timer(
122            20,
123            Box::new(move || {
124                let mut num = timer_num.lock().unwrap();
125                if *num < 9 {
126                    *num += 1;
127                    20
128                } else {
129                    0
130                }
131            }),
132        );
133
134        std::thread::sleep(Duration::from_millis(250));
135        let num = local_num.lock().unwrap();
136        assert_eq!(*num, 9);
137    }
138
139    #[test]
140    fn test_timer_runs_at_least_once() {
141        let _sdl_context = crate::sdl::init().unwrap();
142        //let timer_subsystem = sdl_context.timer().unwrap();
143
144        let local_flag = Arc::new(Mutex::new(false));
145        let timer_flag = local_flag.clone();
146
147        let _timer = add_timer(
148            20,
149            Box::new(move || {
150                let mut flag = timer_flag.lock().unwrap();
151                *flag = true;
152                0
153            }),
154        );
155
156        std::thread::sleep(Duration::from_millis(50));
157        let flag = local_flag.lock().unwrap();
158        assert_eq!(*flag, true);
159    }
160
161    #[test]
162    fn test_timer_can_be_recreated() {
163        let sdl_context = crate::sdl::init().unwrap();
164        //let timer_subsystem = sdl_context.timer().unwrap();
165
166        let local_num = Arc::new(Mutex::new(0));
167        let timer_num = local_num.clone();
168
169        // Run the timer once and reclaim its closure.
170        let timer_1 = add_timer(
171            20,
172            Box::new(move || {
173                let mut num = timer_num.lock().unwrap();
174                *num += 1;
175                0
176            }),
177        );
178
179        // Reclaim closure after timer runs.
180        std::thread::sleep(Duration::from_millis(50));
181        let closure = timer_1.into_inner();
182
183        // Create a second timer and increment again.
184        let _timer_2 = add_timer(20, closure);
185        std::thread::sleep(Duration::from_millis(50));
186
187        // Check that timer was incremented twice.
188        let num = local_num.lock().unwrap();
189        assert_eq!(*num, 2);
190    }
191}