bettertui_engine 0.1.1

High-performance terminal UI framework
Documentation
use std::sync::Arc;
use std::time::{Duration, Instant, SystemTime};

pub trait Clock: Send {
    fn now_ms(&self) -> u64;
    fn now(&self) -> Duration;
    fn is_monotonic(&self) -> bool {
        true
    }
}

#[derive(Debug, Clone)]
pub struct SystemClock {
    epoch: Instant,
}

impl Default for SystemClock {
    fn default() -> Self {
        Self::new()
    }
}

impl SystemClock {
    pub fn new() -> Self {
        Self { epoch: Instant::now() }
    }
}

impl Clock for SystemClock {
    fn now_ms(&self) -> u64 {
        SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap_or_default().as_millis() as u64
    }
    fn now(&self) -> Duration {
        self.epoch.elapsed()
    }
}

#[derive(Debug)]
struct ManualClockInner {
    time: Duration,
    timers: Vec<ScheduledTimer>,
    next_id: u64,
}

#[derive(Debug)]
pub struct ManualClock {
    inner: Arc<std::sync::Mutex<ManualClockInner>>,
}

#[derive(Debug)]
struct ScheduledTimer {
    id: u64,
    fire_at: Duration,
    delay: Duration,
    repeat: bool,
    callback_fn: TimerFn,
}

type TimerFn = fn();

impl ManualClock {
    pub fn new() -> Self {
        Self {
            inner: Arc::new(std::sync::Mutex::new(ManualClockInner {
                time: Duration::ZERO,
                timers: Vec::new(),
                next_id: 1,
            })),
        }
    }

    pub fn advance(&mut self, ms: u64) {
        let mut inner = self.inner.lock().unwrap();
        let target = inner.time + Duration::from_millis(ms);
        Self::advance_to_inner(&mut inner, target);
    }

    pub fn advance_duration(&mut self, duration: Duration) {
        let mut inner = self.inner.lock().unwrap();
        let target = inner.time + duration;
        Self::advance_to_inner(&mut inner, target);
    }

    pub fn set_time(&mut self, time: Duration) {
        let mut inner = self.inner.lock().unwrap();
        if time > inner.time {
            Self::advance_to_inner(&mut inner, time);
        } else {
            inner.time = time;
        }
    }

    pub fn run_all(&mut self) {
        let mut inner = self.inner.lock().unwrap();
        loop {
            let next = Self::next_fire_time_inner(&inner);
            match next {
                Some(t) if t <= inner.time => Self::fire_due_timers_inner(&mut inner),
                Some(t) => Self::advance_to_inner(&mut inner, t),
                None => break,
            }
        }
    }

    pub fn elapsed(&self) -> Duration {
        self.inner.lock().unwrap().time
    }
    pub fn elapsed_ms(&self) -> u64 {
        self.inner.lock().unwrap().time.as_millis() as u64
    }

    pub fn set_timeout(&mut self, callback: TimerFn, delay_ms: u64) -> u64 {
        let mut inner = self.inner.lock().unwrap();
        let id = inner.next_id;
        inner.next_id += 1;
        let now = inner.time;
        inner.timers.push(ScheduledTimer {
            id,
            fire_at: now + Duration::from_millis(delay_ms),
            delay: Duration::from_millis(delay_ms),
            repeat: false,
            callback_fn: callback,
        });
        id
    }

    pub fn set_interval(&mut self, callback: TimerFn, interval_ms: u64) -> u64 {
        let mut inner = self.inner.lock().unwrap();
        let id = inner.next_id;
        inner.next_id += 1;
        let now = inner.time;
        inner.timers.push(ScheduledTimer {
            id,
            fire_at: now + Duration::from_millis(interval_ms),
            delay: Duration::from_millis(interval_ms),
            repeat: true,
            callback_fn: callback,
        });
        id
    }

    pub fn clear_timeout(&mut self, id: u64) {
        let mut inner = self.inner.lock().unwrap();
        inner.timers.retain(|t| t.id != id);
    }

    fn advance_to_inner(inner: &mut ManualClockInner, target: Duration) {
        loop {
            let next = Self::next_fire_time_inner(inner);
            match next {
                Some(t) if t <= target => {
                    inner.time = t;
                    Self::fire_due_timers_inner(inner);
                }
                _ => {
                    inner.time = target;
                    break;
                }
            }
        }
    }

    fn next_fire_time_inner(inner: &ManualClockInner) -> Option<Duration> {
        inner.timers.iter().map(|t| t.fire_at).min()
    }

    fn fire_due_timers_inner(inner: &mut ManualClockInner) {
        let due: Vec<usize> =
            inner.timers.iter().enumerate().filter(|(_, t)| t.fire_at <= inner.time).map(|(i, _)| i).collect();

        for idx in due.into_iter().rev() {
            let timer = inner.timers.remove(idx);
            (timer.callback_fn)();
            if timer.repeat {
                inner.timers.push(ScheduledTimer { fire_at: inner.time + timer.delay, ..timer });
            }
        }
    }

    pub fn clear_interval(&mut self, id: u64) {
        self.clear_timeout(id);
    }
}

impl Clone for ManualClock {
    fn clone(&self) -> Self {
        Self { inner: Arc::clone(&self.inner) }
    }
}

impl Clock for ManualClock {
    fn now_ms(&self) -> u64 {
        self.inner.lock().unwrap().time.as_millis() as u64
    }
    fn now(&self) -> Duration {
        self.inner.lock().unwrap().time
    }
}

impl Default for ManualClock {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};

    #[test]
    fn manual_clock_starts_at_zero() {
        let clock = ManualClock::new();
        assert_eq!(clock.now_ms(), 0);
    }

    #[test]
    fn manual_clock_advance() {
        let mut clock = ManualClock::new();
        clock.advance(100);
        assert_eq!(clock.now_ms(), 100);
    }

    #[test]
    fn manual_clock_set_timeout() {
        static FIRED: AtomicBool = AtomicBool::new(false);
        FIRED.store(false, Ordering::SeqCst);
        let mut clock = ManualClock::new();
        clock.set_timeout(
            || {
                FIRED.store(true, Ordering::SeqCst);
            },
            50,
        );
        clock.advance(49);
        assert!(!FIRED.load(Ordering::SeqCst));
        clock.advance(2);
        assert!(FIRED.load(Ordering::SeqCst));
    }

    #[test]
    fn manual_clock_run_all() {
        static COUNT: AtomicU64 = AtomicU64::new(0);
        COUNT.store(0, Ordering::SeqCst);
        let mut clock = ManualClock::new();
        clock.set_timeout(
            || {
                COUNT.fetch_add(1, Ordering::SeqCst);
            },
            10,
        );
        clock.set_timeout(
            || {
                COUNT.fetch_add(1, Ordering::SeqCst);
            },
            20,
        );
        clock.run_all();
        assert_eq!(COUNT.load(Ordering::SeqCst), 2);
    }

    #[test]
    fn clock_trait_object() {
        let sys: Box<dyn Clock> = Box::new(SystemClock::new());
        assert!(sys.is_monotonic());
        let manual: Box<dyn Clock> = Box::new(ManualClock::new());
        assert_eq!(manual.now_ms(), 0);
    }
}