use std::{
sync::{Arc, Mutex, Weak, atomic::{AtomicUsize, Ordering}},
thread::{self, JoinHandle},
time::{Duration, Instant},
};
use crate::{
FastHashMap,
callbacks::{
Redraw, DontRedraw, TimerCallback, TimerCallbackInfo, RefAny,
TimerCallbackReturn, TimerCallbackType, UpdateScreen,
},
app_resources::AppResources,
};
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum TerminateTimer {
Terminate,
Continue,
}
static MAX_DAEMON_ID: AtomicUsize = AtomicUsize::new(0);
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TimerId { id: usize }
impl TimerId {
pub fn new() -> Self {
TimerId { id: MAX_DAEMON_ID.fetch_add(1, Ordering::SeqCst) }
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct Timer {
pub created: Instant,
pub last_run: Option<Instant>,
pub delay: Option<Duration>,
pub interval: Option<Duration>,
pub timeout: Option<Duration>,
pub callback: TimerCallback,
}
impl Timer {
pub fn new(callback: TimerCallbackType) -> Self {
Timer {
created: Instant::now(),
last_run: None,
delay: None,
interval: None,
timeout: None,
callback: TimerCallback(callback),
}
}
#[inline]
pub fn with_delay(mut self, delay: Duration) -> Self {
self.delay = Some(delay);
self
}
#[inline]
pub fn with_interval(mut self, interval: Duration) -> Self {
self.interval = Some(interval);
self
}
#[inline]
pub fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout = Some(timeout);
self
}
pub fn invoke<'a>(&mut self, info: TimerCallbackInfo<'a>) -> TimerCallbackReturn {
let instant_now = Instant::now();
let delay = self.delay.unwrap_or_else(|| Duration::from_millis(0));
if let Some(timeout) = self.timeout {
if instant_now - self.created > timeout {
return (DontRedraw, TerminateTimer::Terminate);
}
}
if let Some(interval) = self.interval {
let last_run = match self.last_run {
Some(s) => s,
None => self.created + delay,
};
if instant_now - last_run < interval {
return (DontRedraw, TerminateTimer::Continue);
}
}
let res = (self.callback.0)(info);
self.last_run = Some(instant_now);
res
}
}
pub struct DropCheck(Arc<()>);
pub struct Task {
join_handle: Option<JoinHandle<UpdateScreen>>,
dropcheck: Weak<()>,
pub after_completion_timer: Option<Timer>,
}
pub type TaskCallback<U> = fn(Arc<Mutex<U>>, DropCheck) -> UpdateScreen;
impl Task {
pub fn new<U: Send + 'static>(data: Arc<Mutex<U>>, callback: TaskCallback<U>) -> Self {
let thread_check = Arc::new(());
let thread_weak = Arc::downgrade(&thread_check);
let thread_handle = thread::spawn(move || callback(data, DropCheck(thread_check)));
Self {
join_handle: Some(thread_handle),
dropcheck: thread_weak,
after_completion_timer: None,
}
}
#[inline]
pub fn then(mut self, timer: Timer) -> Self {
self.after_completion_timer = Some(timer);
self
}
pub fn is_finished(&self) -> bool {
self.dropcheck.upgrade().is_none()
}
}
impl Drop for Task {
fn drop(&mut self) {
if let Some(thread_handle) = self.join_handle.take() {
let _ = thread_handle.join().unwrap();
}
}
}
pub struct Thread<T> {
join_handle: Option<JoinHandle<T>>,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum BlockError {
ArcUnlockError,
ThreadJoinError,
MutexIntoInnerError,
}
impl<T> Thread<T> {
pub fn new<U>(initial_data: U, callback: fn(U) -> T) -> Self where T: Send + 'static, U: Send + 'static {
Self {
join_handle: Some(thread::spawn(move || callback(initial_data))),
}
}
pub fn block(mut self) -> Result<T, BlockError> {
let handle = self.join_handle.take().unwrap();
let data = handle.join().map_err(|_| BlockError::ThreadJoinError)?;
Ok(data)
}
}
impl<T> Drop for Thread<T> {
fn drop(&mut self) {
if self.join_handle.take().is_some() {
panic!("Thread has not been await()-ed correctly!");
}
}
}
#[must_use]
pub fn run_all_timers(
timers: &mut FastHashMap<TimerId, Timer>,
data: &mut RefAny,
resources: &mut AppResources,
) -> UpdateScreen {
let mut should_update_screen = DontRedraw;
let mut timers_to_terminate = Vec::new();
for (key, timer) in timers.iter_mut() {
let (should_update, should_terminate) = timer.invoke(TimerCallbackInfo {
state: data,
app_resources: resources,
});
if should_update == Redraw {
should_update_screen = Redraw;
}
if should_terminate == TerminateTimer::Terminate {
timers_to_terminate.push(key.clone());
}
}
for key in timers_to_terminate {
timers.remove(&key);
}
should_update_screen
}
#[must_use]
pub fn clean_up_finished_tasks<T>(
tasks: &mut Vec<Task>,
timers: &mut FastHashMap<TimerId, Timer>,
) -> UpdateScreen {
let old_count = tasks.len();
let mut timers_to_add = Vec::new();
tasks.retain(|task| {
if task.is_finished() {
if let Some(timer) = task.after_completion_timer {
timers_to_add.push((TimerId::new(), timer));
}
false
} else {
true
}
});
let timers_is_empty = timers_to_add.is_empty();
let new_count = tasks.len();
for (timer_id, timer) in timers_to_add {
timers.insert(timer_id, timer);
}
if old_count == new_count && timers_is_empty {
DontRedraw
} else {
Redraw
}
}