use core::{cell::Cell, mem::ManuallyDrop, ptr};
use crate::{
sys,
types::{Callable, CallbackSig, ErasedCallback, ErasedPtr, Sig},
ui::NoWindow,
};
pub struct Timer(ManuallyDrop<ErasedCallback<'static, TimerSig, TimerHeader>>);
enum TimerSig {}
impl Sig for TimerSig {
type Args<'a: 'b, 'b> = ();
type Output = Option<u32>;
}
impl<F: FnMut() -> Option<u32>> Callable<TimerSig> for F {
fn call<'a: 'b, 'b>(&mut self, (): ()) -> Option<u32> {
self()
}
}
struct TimerHeader {
id: Cell<*mut sys::AppTimer>,
flags: Cell<TimerFlags>,
}
bitflags::bitflags! {
#[derive(Clone, Copy, Default)]
struct TimerFlags: u8 {
const RUNNING = 1;
const DETACHED = 1 << 1;
const DROPPED = 1 << 2;
}
}
impl Timer {
fn new<F: FnMut() -> Option<u32> + NoWindow + 'static>(timeout_ms: u32, callback: F) -> Timer {
let inner = ErasedCallback::new(
TimerHeader {
id: Cell::new(ptr::null_mut()),
flags: Cell::default(),
},
callback,
);
let id = unsafe {
sys::app_timer_register(timeout_ms, Some(inner.trampoline()), inner.as_raw())
};
inner.header().id.set(id);
Timer(ManuallyDrop::new(inner))
}
pub fn repeating<F: FnMut() -> Option<u32> + NoWindow + 'static>(
timeout_ms: u32,
f: F,
) -> Timer {
Self::new(timeout_ms, f)
}
pub fn oneshot<F: FnOnce() + NoWindow + 'static>(timeout_ms: u32, f: F) -> Timer {
let mut f = Some(f);
Self::new(timeout_ms, move || {
if let Some(f) = f.take() {
f();
}
None
})
}
pub fn reschedule(&mut self, timeout_ms: u32) {
let header = self.0.header();
let id = header.id.get();
if !id.is_null() {
if unsafe { sys::app_timer_reschedule(id, timeout_ms) } {
return;
}
unsafe { sys::app_timer_cancel(id) };
}
let id = unsafe {
sys::app_timer_register(timeout_ms, Some(self.0.trampoline()), self.0.as_raw())
};
header.id.set(id);
}
pub fn is_pending(&self) -> bool {
!self.0.header().id.get().is_null()
}
pub fn detach(self) {
self.0
.header()
.flags
.set(self.0.header().flags.get() | TimerFlags::DETACHED);
if self.is_pending() || self.0.header().flags.get().contains(TimerFlags::RUNNING) {
core::mem::forget(self);
}
}
}
impl CallbackSig for TimerSig {
unsafe extern "C" fn trampoline<F: Callable<TimerSig>>(ptr: *mut core::ffi::c_void) {
let data = unsafe { ErasedPtr::<TimerHeader>::from_raw(ptr) };
let header = unsafe { data.header() };
header.id.set(ptr::null_mut());
header.flags.set(header.flags.get() | TimerFlags::RUNNING);
let rearm = unsafe { data.call::<TimerSig, F>(()) };
header.flags.set(header.flags.get() & !TimerFlags::RUNNING);
if header.flags.get().contains(TimerFlags::DROPPED) {
if !header.id.get().is_null() {
unsafe { sys::app_timer_cancel(header.id.get()) }
}
unsafe { data.free::<F>() };
return;
}
if header.id.get().is_null() {
match rearm {
Some(duration) => header.id.set(unsafe {
sys::app_timer_register(duration, Some(Self::trampoline::<F>), ptr)
}),
None if header.flags.get().contains(TimerFlags::DETACHED) => unsafe {
data.free::<F>();
},
None => (),
}
}
}
}
impl Drop for Timer {
fn drop(&mut self) {
let header = self.0.header();
let flags = header.flags.get();
if flags.contains(TimerFlags::RUNNING) {
header.flags.set(flags | TimerFlags::DROPPED);
} else {
let id = header.id.get();
if !id.is_null() {
unsafe { sys::app_timer_cancel(id) };
}
unsafe { ManuallyDrop::drop(&mut self.0) };
}
}
}