use core::{time, ptr, mem};
use core::cell::Cell;
use core::sync::atomic::{AtomicPtr, Ordering};
use super::BoxFnPtr;
extern crate alloc;
use alloc::boxed::Box;
mod ffi {
pub use core::ffi::c_void;
type DWORD = u32;
type BOOL = i32;
#[repr(C)]
pub struct FileTime {
pub low_date_time: DWORD,
pub high_date_time: DWORD,
}
pub type Callback = unsafe extern "system" fn(cb_inst: *mut c_void, ctx: *mut c_void, timer: *mut c_void);
extern "system" {
pub fn CloseThreadpoolTimer(ptr: *mut c_void);
pub fn CreateThreadpoolTimer(cb: Callback, user_data: *mut c_void, env: *mut c_void) -> *mut c_void;
pub fn SetThreadpoolTimerEx(timer: *mut c_void, pftDueTime: *mut FileTime, msPeriod: DWORD, msWindowLength: DWORD) -> BOOL;
pub fn IsThreadpoolTimerSet(timer: *mut c_void) -> BOOL;
pub fn WaitForThreadpoolTimerCallbacks(timer: *mut c_void, fCancelPendingCallbacks: BOOL);
}
}
unsafe extern "system" fn timer_callback(_: *mut ffi::c_void, data: *mut ffi::c_void, _: *mut ffi::c_void) {
if !data.is_null() {
let cb: fn() -> () = mem::transmute(data);
(cb)();
}
}
unsafe extern "system" fn timer_callback_unsafe(_: *mut ffi::c_void, data: *mut ffi::c_void, _: *mut ffi::c_void) {
if !data.is_null() {
let cb: unsafe fn() -> () = mem::transmute(data);
(cb)();
}
}
unsafe extern "system" fn timer_callback_generic<T: FnMut() -> ()>(_: *mut ffi::c_void, data: *mut ffi::c_void, _: *mut ffi::c_void) {
if !data.is_null() {
let cb = &mut *(data as *mut T);
(cb)();
}
}
enum CallbackVariant {
Trivial(*mut ffi::c_void),
Boxed(Box<dyn FnMut()>),
}
pub struct Callback {
variant: CallbackVariant,
ffi_cb: ffi::Callback,
}
impl Callback {
pub unsafe fn raw(ffi_cb: ffi::Callback, data: *mut ffi::c_void) -> Self {
Self {
variant: CallbackVariant::Trivial(data),
ffi_cb,
}
}
pub fn plain(cb: fn()) -> Self {
Self {
variant: CallbackVariant::Trivial(cb as _),
ffi_cb: timer_callback,
}
}
pub fn unsafe_plain(cb: unsafe fn()) -> Self {
Self {
variant: CallbackVariant::Trivial(cb as _),
ffi_cb: timer_callback_unsafe,
}
}
pub fn closure<F: 'static + FnMut()>(cb: F) -> Self {
Self {
variant: CallbackVariant::Boxed(Box::new(cb)),
ffi_cb: timer_callback_generic::<F>,
}
}
}
pub struct Timer {
inner: AtomicPtr<ffi::c_void>,
data: Cell<BoxFnPtr>,
}
impl Timer {
#[inline]
pub const unsafe fn uninit() -> Self {
Self {
inner: AtomicPtr::new(ptr::null_mut()),
data: Cell::new(BoxFnPtr::null()),
}
}
#[inline(always)]
fn get_inner(&self) -> *mut ffi::c_void {
let inner = self.inner.load(Ordering::Acquire);
debug_assert!(!inner.is_null(), "Timer has not been initialized");
inner
}
#[inline(always)]
pub fn is_init(&self) -> bool {
!self.inner.load(Ordering::Acquire).is_null()
}
#[must_use]
pub fn init(&self, cb: Callback) -> bool {
if self.is_init() {
return false;
}
let ffi_cb = cb.ffi_cb;
let (data, ffi_data) = match cb.variant {
CallbackVariant::Trivial(data) => (BoxFnPtr::null(), data),
CallbackVariant::Boxed(cb) => unsafe {
let raw = Box::into_raw(cb);
(BoxFnPtr(mem::transmute(raw)), raw as *mut ffi::c_void)
},
};
let handle = unsafe {
ffi::CreateThreadpoolTimer(ffi_cb, ffi_data, ptr::null_mut())
};
match self.inner.compare_exchange(ptr::null_mut(), handle, Ordering::SeqCst, Ordering::Acquire) {
Ok(_) => match handle.is_null() {
true => false,
false => {
self.data.set(data);
true
},
},
Err(_) => {
unsafe {
ffi::CloseThreadpoolTimer(handle);
}
false
}
}
}
pub fn new(cb: Callback) -> Option<Self> {
let ffi_cb = cb.ffi_cb;
let (data, ffi_data) = match cb.variant {
CallbackVariant::Trivial(data) => (BoxFnPtr::null(), data),
CallbackVariant::Boxed(cb) => unsafe {
let raw = Box::into_raw(cb);
(BoxFnPtr(mem::transmute(raw)), raw as *mut ffi::c_void)
},
};
let handle = unsafe {
ffi::CreateThreadpoolTimer(ffi_cb, ffi_data, ptr::null_mut())
};
if handle.is_null() {
return None;
}
Some(Self {
inner: AtomicPtr::new(handle),
data: Cell::new(data),
})
}
pub fn schedule_interval(&self, timeout: time::Duration, interval: time::Duration) -> bool {
let mut ticks = i64::from(timeout.subsec_nanos() / 100);
ticks += (timeout.as_secs() * 10_000_000) as i64;
let ticks = -ticks;
let interval = interval.as_millis() as u32;
unsafe {
let mut time: ffi::FileTime = mem::transmute(ticks);
ffi::SetThreadpoolTimerEx(self.get_inner(), &mut time, interval, 0);
}
true
}
#[inline]
pub fn is_scheduled(&self) -> bool {
let handle = self.get_inner();
unsafe {
ffi::IsThreadpoolTimerSet(handle) != 0
}
}
#[inline]
pub fn cancel(&self) {
let handle = self.get_inner();
unsafe {
ffi::SetThreadpoolTimerEx(handle, ptr::null_mut(), 0, 0);
ffi::WaitForThreadpoolTimerCallbacks(handle, 1);
}
}
}
impl Drop for Timer {
fn drop(&mut self) {
let handle = self.inner.load(Ordering::Relaxed);
if !handle.is_null() {
self.cancel();
unsafe {
ffi::CloseThreadpoolTimer(handle);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn init_plain_fn() {
let mut timer = unsafe {
Timer::uninit()
};
fn cb() {
}
let closure = || {
};
assert!(timer.init(Callback::plain(cb)));
let ptr = timer.inner.load(Ordering::Relaxed);
assert!(!ptr.is_null());
assert!(timer.data.get_mut().is_null());
assert!(!timer.init(Callback::closure(closure)));
assert!(!ptr.is_null());
assert_eq!(ptr, timer.inner.load(Ordering::Relaxed));
assert!(timer.data.get_mut().is_null());
}
#[test]
fn init_closure() {
let mut timer = unsafe {
Timer::uninit()
};
fn cb() {
}
let closure = || {
};
assert!(timer.init(Callback::closure(closure)));
let ptr = timer.inner.load(Ordering::Relaxed);
assert!(!ptr.is_null());
assert!(!timer.data.get_mut().is_null());
assert!(!timer.init(Callback::plain(cb)));
assert!(!ptr.is_null());
assert_eq!(ptr, timer.inner.load(Ordering::Relaxed));
assert!(!timer.data.get_mut().is_null());
}
}