1use core::{time, ptr, mem};
2use core::cell::Cell;
3use core::sync::atomic::{AtomicPtr, Ordering};
4use super::BoxFnPtr;
5
6extern crate alloc;
7use alloc::boxed::Box;
8
9mod ffi {
10 pub use core::ffi::c_void;
11
12 type DWORD = u32;
13 type BOOL = i32;
14
15 #[repr(C)]
16 pub struct FileTime {
17 pub low_date_time: DWORD,
18 pub high_date_time: DWORD,
19 }
20
21 pub type Callback = unsafe extern "system" fn(cb_inst: *mut c_void, ctx: *mut c_void, timer: *mut c_void);
22
23 extern "system" {
24 pub fn CloseThreadpoolTimer(ptr: *mut c_void);
25 pub fn CreateThreadpoolTimer(cb: Callback, user_data: *mut c_void, env: *mut c_void) -> *mut c_void;
26 pub fn SetThreadpoolTimerEx(timer: *mut c_void, pftDueTime: *mut FileTime, msPeriod: DWORD, msWindowLength: DWORD) -> BOOL;
27 pub fn IsThreadpoolTimerSet(timer: *mut c_void) -> BOOL;
28 pub fn WaitForThreadpoolTimerCallbacks(timer: *mut c_void, fCancelPendingCallbacks: BOOL);
29 }
30}
31
32unsafe extern "system" fn timer_callback(_: *mut ffi::c_void, data: *mut ffi::c_void, _: *mut ffi::c_void) {
33 if !data.is_null() {
34 let cb: fn() -> () = mem::transmute(data);
35
36 (cb)();
37 }
38}
39
40unsafe extern "system" fn timer_callback_unsafe(_: *mut ffi::c_void, data: *mut ffi::c_void, _: *mut ffi::c_void) {
41 if !data.is_null() {
42 let cb: unsafe fn() -> () = mem::transmute(data);
43
44 (cb)();
45 }
46}
47
48unsafe extern "system" fn timer_callback_generic<T: FnMut() -> ()>(_: *mut ffi::c_void, data: *mut ffi::c_void, _: *mut ffi::c_void) {
49 if !data.is_null() {
50 let cb = &mut *(data as *mut T);
51
52 (cb)();
53 }
54}
55
56enum CallbackVariant {
57 Trivial(*mut ffi::c_void),
58 Boxed(Box<dyn FnMut()>),
59}
60
61pub struct Callback {
63 variant: CallbackVariant,
64 ffi_cb: ffi::Callback,
65}
66
67impl Callback {
68 pub unsafe fn raw(ffi_cb: ffi::Callback, data: *mut ffi::c_void) -> Self {
72 Self {
73 variant: CallbackVariant::Trivial(data),
74 ffi_cb,
75 }
76 }
77
78 pub fn plain(cb: fn()) -> Self {
80 Self {
81 variant: CallbackVariant::Trivial(cb as _),
82 ffi_cb: timer_callback,
83 }
84 }
85
86 pub fn unsafe_plain(cb: unsafe fn()) -> Self {
88 Self {
89 variant: CallbackVariant::Trivial(cb as _),
90 ffi_cb: timer_callback_unsafe,
91 }
92 }
93
94 pub fn closure<F: 'static + FnMut()>(cb: F) -> Self {
96 Self {
97 variant: CallbackVariant::Boxed(Box::new(cb)),
98 ffi_cb: timer_callback_generic::<F>,
99 }
100 }
101}
102
103pub struct Timer {
105 inner: AtomicPtr<ffi::c_void>,
106 data: Cell<BoxFnPtr>,
107}
108
109impl Timer {
110 #[inline]
111 pub const unsafe fn uninit() -> Self {
115 Self {
116 inner: AtomicPtr::new(ptr::null_mut()),
117 data: Cell::new(BoxFnPtr::null()),
118 }
119 }
120
121 #[inline(always)]
122 fn get_inner(&self) -> *mut ffi::c_void {
123 let inner = self.inner.load(Ordering::Acquire);
124 debug_assert!(!inner.is_null(), "Timer has not been initialized");
125 inner
126 }
127
128 #[inline(always)]
129 pub fn is_init(&self) -> bool {
131 !self.inner.load(Ordering::Acquire).is_null()
132 }
133
134 #[must_use]
135 pub fn init(&self, cb: Callback) -> bool {
143 if self.is_init() {
144 return false;
145 }
146
147 let ffi_cb = cb.ffi_cb;
148 let (data, ffi_data) = match cb.variant {
149 CallbackVariant::Trivial(data) => (BoxFnPtr::null(), data),
150 CallbackVariant::Boxed(cb) => unsafe {
151 let raw = Box::into_raw(cb);
152 (BoxFnPtr(mem::transmute(raw)), raw as *mut ffi::c_void)
153 },
154 };
155
156 let handle = unsafe {
157 ffi::CreateThreadpoolTimer(ffi_cb, ffi_data, ptr::null_mut())
158 };
159
160 match self.inner.compare_exchange(ptr::null_mut(), handle, Ordering::SeqCst, Ordering::Acquire) {
161 Ok(_) => match handle.is_null() {
162 true => false,
163 false => {
164 self.data.set(data);
166 true
167 },
168 },
169 Err(_) => {
170 unsafe {
171 ffi::CloseThreadpoolTimer(handle);
172 }
173 false
174 }
175 }
176 }
177
178 pub fn new(cb: Callback) -> Option<Self> {
182 let ffi_cb = cb.ffi_cb;
183 let (data, ffi_data) = match cb.variant {
184 CallbackVariant::Trivial(data) => (BoxFnPtr::null(), data),
185 CallbackVariant::Boxed(cb) => unsafe {
186 let raw = Box::into_raw(cb);
187 (BoxFnPtr(mem::transmute(raw)), raw as *mut ffi::c_void)
188 },
189 };
190
191 let handle = unsafe {
192 ffi::CreateThreadpoolTimer(ffi_cb, ffi_data, ptr::null_mut())
193 };
194
195 if handle.is_null() {
196 return None;
197 }
198
199 Some(Self {
200 inner: AtomicPtr::new(handle),
201 data: Cell::new(data),
202 })
203 }
204
205 pub fn schedule_interval(&self, timeout: time::Duration, interval: time::Duration) -> bool {
216 let mut ticks = i64::from(timeout.subsec_nanos() / 100);
217 ticks += (timeout.as_secs() * 10_000_000) as i64;
218 let ticks = -ticks;
219
220 let interval = interval.as_millis() as u32;
221
222 unsafe {
223 let mut time: ffi::FileTime = mem::transmute(ticks);
224 ffi::SetThreadpoolTimerEx(self.get_inner(), &mut time, interval, 0);
225 }
226
227 true
228 }
229
230 #[inline]
231 pub fn is_scheduled(&self) -> bool {
236 let handle = self.get_inner();
237 unsafe {
238 ffi::IsThreadpoolTimerSet(handle) != 0
239 }
240 }
241
242 #[inline]
243 pub fn cancel(&self) {
245 let handle = self.get_inner();
246 unsafe {
247 ffi::SetThreadpoolTimerEx(handle, ptr::null_mut(), 0, 0);
248 ffi::WaitForThreadpoolTimerCallbacks(handle, 1);
249 }
250 }
251}
252
253impl Drop for Timer {
254 fn drop(&mut self) {
255 let handle = self.inner.load(Ordering::Relaxed);
256 if !handle.is_null() {
257 self.cancel();
258 unsafe {
259 ffi::CloseThreadpoolTimer(handle);
260 }
261 }
262 }
263}
264
265#[cfg(test)]
266mod tests {
267 use super::*;
268
269 #[test]
270 fn init_plain_fn() {
271 let mut timer = unsafe {
272 Timer::uninit()
273 };
274
275 fn cb() {
276 }
277
278 let closure = || {
279 };
280
281 assert!(timer.init(Callback::plain(cb)));
282 let ptr = timer.inner.load(Ordering::Relaxed);
283 assert!(!ptr.is_null());
284 assert!(timer.data.get_mut().is_null());
285
286 assert!(!timer.init(Callback::closure(closure)));
287 assert!(!ptr.is_null());
288 assert_eq!(ptr, timer.inner.load(Ordering::Relaxed));
289 assert!(timer.data.get_mut().is_null());
290 }
291
292 #[test]
293 fn init_closure() {
294 let mut timer = unsafe {
295 Timer::uninit()
296 };
297
298 fn cb() {
299 }
300
301 let closure = || {
302 };
303
304 assert!(timer.init(Callback::closure(closure)));
305 let ptr = timer.inner.load(Ordering::Relaxed);
306 assert!(!ptr.is_null());
307 assert!(!timer.data.get_mut().is_null());
308
309 assert!(!timer.init(Callback::plain(cb)));
310 assert!(!ptr.is_null());
311 assert_eq!(ptr, timer.inner.load(Ordering::Relaxed));
312 assert!(!timer.data.get_mut().is_null());
313 }
314}