portlight 0.0.2

Cross-platform window management for audio plugins
Documentation
use std::cell::{Cell, RefCell};
use std::cmp::Ordering;
use std::collections::{BinaryHeap, HashMap};
use std::rc::Rc;
use std::time::{Duration, Instant};

use crate::{EventLoop, Result};

pub type TimerId = usize;

pub struct TimerState {
    timer_id: TimerId,
    duration: Duration,
    event_loop: EventLoop,
    handler: RefCell<Box<dyn FnMut()>>,
}

impl TimerState {
    fn handle_timer(&self) -> Option<()> {
        self.handler.borrow_mut()();
        Some(())
    }

    pub fn repeat<F>(
        event_loop: &EventLoop,
        duration: Duration,
        handler: F,
    ) -> Result<Rc<TimerState>>
    where
        F: FnMut() + 'static,
    {
        let now = Instant::now();

        let timer_id = event_loop.state.timers.next_id.get();
        event_loop.state.timers.next_id.set(timer_id + 1);

        let state = Rc::new(TimerState {
            timer_id,
            duration,
            event_loop: event_loop.clone(),
            handler: RefCell::new(Box::new(handler)),
        });

        event_loop.state.timers.timers.borrow_mut().insert(timer_id, Rc::clone(&state));

        event_loop.state.timers.queue.borrow_mut().push(QueueEntry {
            time: now + duration,
            timer_id,
        });

        Ok(state)
    }

    pub fn cancel(&self) {
        let timers = &self.event_loop.state.timers;
        timers.timers.borrow_mut().remove(&self.timer_id);
    }
}

#[derive(Clone)]
struct QueueEntry {
    time: Instant,
    timer_id: TimerId,
}

impl PartialEq for QueueEntry {
    fn eq(&self, other: &Self) -> bool {
        self.time == other.time
    }
}

impl Eq for QueueEntry {}

impl PartialOrd for QueueEntry {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.time.cmp(&other.time).reverse())
    }
}

impl Ord for QueueEntry {
    fn cmp(&self, other: &Self) -> Ordering {
        self.time.cmp(&other.time).reverse()
    }
}

pub struct Timers {
    next_id: Cell<TimerId>,
    timers: RefCell<HashMap<usize, Rc<TimerState>>>,
    queue: RefCell<BinaryHeap<QueueEntry>>,
}

impl Timers {
    pub fn new() -> Timers {
        Timers {
            next_id: Cell::new(0),
            timers: RefCell::new(HashMap::new()),
            queue: RefCell::new(BinaryHeap::new()),
        }
    }

    pub fn next_time(&self) -> Option<Instant> {
        self.queue.borrow().peek().map(|e| e.time)
    }

    pub fn poll(&self) {
        let now = Instant::now();

        // Check with < and not <= so that we don't process a timer twice during this loop
        while self.next_time().map_or(false, |t| t < now) {
            let next = self.queue.borrow_mut().pop().unwrap();

            // If we don't find the timer in `self.timers`, it has been canceled
            let timer_state = self.timers.borrow().get(&next.timer_id).cloned();
            if let Some(timer_state) = timer_state {
                timer_state.handle_timer();

                // If we fall behind by more than one timer interval, reset the timer's phase
                let next_time = (next.time + timer_state.duration).max(now);

                self.queue.borrow_mut().push(QueueEntry {
                    time: next_time,
                    timer_id: next.timer_id,
                })
            }
        }
    }
}