use core::fmt;
use std::time::{Duration, Instant};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u8)]
pub enum TaskPriority {
Input = 0,
UserBlocking = 1,
Normal = 2,
Timer = 3,
BestEffort = 4,
Idle = 5,
}
impl TaskPriority {
pub const COUNT: usize = 6;
#[inline]
#[must_use]
pub const fn as_index(self) -> usize {
self as usize
}
#[inline]
#[must_use]
pub const fn from_index(index: usize) -> Option<Self> {
match index {
0 => Some(Self::Input),
1 => Some(Self::UserBlocking),
2 => Some(Self::Normal),
3 => Some(Self::Timer),
4 => Some(Self::BestEffort),
5 => Some(Self::Idle),
_ => None,
}
}
}
impl fmt::Display for TaskPriority {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Input => write!(f, "Input"),
Self::UserBlocking => write!(f, "UserBlocking"),
Self::Normal => write!(f, "Normal"),
Self::Timer => write!(f, "Timer"),
Self::BestEffort => write!(f, "BestEffort"),
Self::Idle => write!(f, "Idle"),
}
}
}
impl Default for TaskPriority {
#[inline]
fn default() -> Self {
Self::Normal
}
}
pub struct Task {
callback: Box<dyn FnOnce()>,
priority: TaskPriority,
run_at: Option<Instant>,
}
impl Task {
#[inline]
pub fn new(priority: TaskPriority, callback: impl FnOnce() + 'static) -> Self {
Self {
callback: Box::new(callback),
priority,
run_at: None,
}
}
#[inline]
pub fn delayed(
priority: TaskPriority,
delay: Duration,
callback: impl FnOnce() + 'static,
) -> Self {
Self {
callback: Box::new(callback),
priority,
run_at: Some(Instant::now() + delay),
}
}
#[inline]
#[must_use]
pub fn priority(&self) -> TaskPriority {
self.priority
}
#[inline]
#[must_use]
pub fn is_ready(&self) -> bool {
match self.run_at {
None => true,
Some(at) => Instant::now() >= at,
}
}
#[inline]
#[must_use]
pub fn time_until_ready(&self) -> Duration {
match self.run_at {
None => Duration::ZERO,
Some(at) => at.saturating_duration_since(Instant::now()),
}
}
#[inline]
#[must_use]
pub fn run_at(&self) -> Option<Instant> {
self.run_at
}
#[inline]
pub fn run(self) {
(self.callback)();
}
}
impl fmt::Debug for Task {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Task")
.field("priority", &self.priority)
.field("run_at", &self.run_at)
.finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::Cell;
use std::rc::Rc;
#[test]
fn task_executes_callback() {
let called = Rc::new(Cell::new(false));
let called2 = called.clone();
let task = Task::new(TaskPriority::Normal, move || called2.set(true));
assert!(!called.get());
task.run();
assert!(called.get());
}
#[test]
fn task_ready_when_no_delay() {
let task = Task::new(TaskPriority::Input, || {});
assert!(task.is_ready());
assert_eq!(task.time_until_ready(), Duration::ZERO);
assert!(task.run_at().is_none());
}
#[test]
fn task_not_ready_with_future_delay() {
let task = Task::delayed(TaskPriority::Timer, Duration::from_secs(60), || {});
assert!(!task.is_ready());
assert!(task.time_until_ready() > Duration::ZERO);
assert!(task.run_at().is_some());
}
#[test]
fn task_ready_with_zero_delay() {
let task = Task::delayed(TaskPriority::Timer, Duration::ZERO, || {});
assert!(task.is_ready());
}
#[test]
fn priority_ordering() {
assert!(TaskPriority::Input < TaskPriority::UserBlocking);
assert!(TaskPriority::UserBlocking < TaskPriority::Normal);
assert!(TaskPriority::Normal < TaskPriority::Timer);
assert!(TaskPriority::Timer < TaskPriority::BestEffort);
assert!(TaskPriority::BestEffort < TaskPriority::Idle);
}
#[test]
fn priority_index_roundtrip() {
for i in 0..TaskPriority::COUNT {
let p = TaskPriority::from_index(i).unwrap();
assert_eq!(p.as_index(), i);
}
assert!(TaskPriority::from_index(TaskPriority::COUNT).is_none());
}
#[test]
fn default_priority_is_normal() {
assert_eq!(TaskPriority::default(), TaskPriority::Normal);
}
#[test]
fn task_debug_format() {
let task = Task::new(TaskPriority::Input, || {});
let debug = format!("{:?}", task);
assert!(debug.contains("Input"));
}
}