use std::cmp::Ordering;
use std::collections::binary_heap::BinaryHeap;
use std::sync::{mpsc, Arc, Mutex};
use std::thread;
use std::time::{Duration, Instant};
use instant_iter::IntoInstantIter;
pub struct Planner {
job_queue: Arc<Mutex<BinaryHeap<Job>>>,
job_processor_tx: Arc<Mutex<Option<mpsc::Sender<()>>>>,
}
impl Planner {
#[allow(missing_docs)]
pub fn new() -> Planner {
Planner {
job_queue: Arc::new(Mutex::new(BinaryHeap::new())),
job_processor_tx: Arc::new(Mutex::new(None)),
}
}
pub fn add<T: Iterator<Item = Instant> + Send + Sync + 'static>(
&mut self,
callback: impl Fn() -> () + Send + Sync + 'static,
times: impl IntoInstantIter<IterType = T>,
)
{
let job = Job::new(callback, times)
.expect("Added job with no execution times");
let job_next_time = job.next_time;
self.job_queue.lock().unwrap().push(job);
let is_earliest_job = match self.job_queue.lock().unwrap().peek() {
Some(earliest_job) => job_next_time == earliest_job.next_time,
None => false,
};
if self.is_started() && is_earliest_job {
Self::spawn_waker(
self.job_processor_tx.lock().unwrap().clone(),
job_next_time.duration_since(Instant::now()),
);
}
if !self.is_started() {
self.start();
}
}
pub fn start(&mut self) {
if self.is_started() {
return;
}
let (job_processor_tx, job_processor_rx) = mpsc::channel();
*self.job_processor_tx.lock().unwrap() = Some(job_processor_tx.clone());
let job_queue = self.job_queue.clone();
let job_processor_tx = self.job_processor_tx.clone();
Self::spawn("planner", move || loop {
let mut job_queue_locked = job_queue.lock().unwrap();
let next_time = job_queue_locked.peek().map(|job| job.next_time);
let now = Instant::now();
if next_time.is_none() {
*job_processor_tx.lock().unwrap() = None;
break;
}
if next_time.unwrap() <= now {
let job = job_queue_locked.pop().expect(
"Job disappeared from queue while queue was locked",
);
let spawn_callback = job.callback.clone();
Self::spawn("exec_callback", move || (*spawn_callback)());
job.to_next_time()
.map(|new_job| job_queue_locked.push(new_job));
continue;
}
drop(job_queue_locked);
Self::spawn_waker(
job_processor_tx.lock().unwrap().clone(),
next_time.unwrap().duration_since(Instant::now()),
);
job_processor_rx
.recv()
.expect("Couldn't listen for waking messages");
});
}
fn is_started(&self) -> bool {
self.job_processor_tx.lock().unwrap().is_some()
}
fn spawn_waker(
job_processor_tx: Option<mpsc::Sender<()>>,
duration: Duration,
)
{
Self::spawn("waker", move || {
thread::sleep(duration);
job_processor_tx.map(|tx| tx.send(()));
});
}
fn spawn(
name: impl ::std::fmt::Display,
callback: impl FnOnce() -> () + Send + 'static,
)
{
let name = format!("{}_{}", env!("CARGO_PKG_NAME"), name);
thread::Builder::new()
.name(name.into())
.spawn(callback)
.expect("Failed to spawn thread with name");
}
}
struct Job {
callback: Arc<Fn() -> () + Send + Sync + 'static>,
next_time: Instant,
rest_times: Box<Iterator<Item = Instant> + Send + Sync>,
}
impl Job {
fn new<T: Iterator<Item = Instant> + Send + Sync + 'static>(
callback: impl Fn() -> () + Send + Sync + 'static,
times: impl IntoInstantIter<IterType = T>,
) -> Option<Job>
{
let mut times = times.into_instant_iter();
times.next().map(|next_time| Job {
callback: Arc::new(callback),
next_time,
rest_times: Box::new(times),
})
}
fn to_next_time(mut self) -> Option<Job> {
self.rest_times.next().map(|new_next_time| Job {
callback: self.callback,
next_time: new_next_time,
rest_times: self.rest_times,
})
}
}
impl Ord for Job {
fn cmp(&self, other: &Job) -> Ordering {
other.next_time.cmp(&self.next_time)
}
}
impl PartialOrd for Job {
fn partial_cmp(&self, other: &Job) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Eq for Job {}
impl PartialEq for Job {
fn eq(&self, other: &Job) -> bool { self.next_time == other.next_time }
}