microlock 0.3.1

A crate for waiting: Small locks and other timing things!
Documentation
use std::{sync::Mutex, time::Duration};

use crate::{
    timer::{Timed, Timer, TimerDuration},
    Lock, UntimedLock,
};

/// A timed lock. This can be locked and unlocked, and it will unlock on
/// its own after a timeout, if specified (see [`UntimedLock`] for a
/// non-expiring lock). If a thread calls wait_here on a locked lock, it will
/// wait until the lock is unlocked by another thread, or the lock expires.
pub struct TimedLock {
    /// The inner lock, which is used as the implementation for actual locking
    /// calls and the `locked` state.
    inner: UntimedLock,
    /// The data specifying how the lock is currently meant to act. (Most
    /// importantly, it contains the Timer)
    timer: Mutex<Timer>,
}

impl TimedLock {
    /// Creates a new timed lock that is either unlocked or locked.
    pub const fn new(locked: bool) -> Self {
        let duration = if locked {
            TimerDuration::Infinite
        } else {
            TimerDuration::Elapsed
        };
        Self {
            inner: UntimedLock::new(locked),
            timer: Mutex::new(Timer::new_const(duration)),
        }
    }

    /// Creates a new timed lock that is either unlocked or locked.
    pub fn new_for(duration: TimerDuration) -> Self {
        Self {
            inner: UntimedLock::new(!duration.is_elapsed()),
            timer: Mutex::new(Timer::new(duration)),
        }
    }

    /// Creates a new timed lock that is unlocked.
    pub const fn unlocked() -> Self {
        Self::new(false)
    }

    /// Creates a new timed lock that is locked.
    pub const fn locked() -> Self {
        Self::new(true)
    }

    /// Returns the inner timer. This is None if the lock is in untimed mode
    /// or if it is currently unlocked. The timer may already have elapsed
    /// if the lock wasn't unlocked explicitly and nobody was waiting on it
    /// to trigger an update.
    pub fn get_timer(&self) -> Timer {
        *self.timer.lock().unwrap()
    }
}

impl Timed for TimedLock {
    fn has_elapsed(&self) -> bool {
        !self.is_locked()
    }

    /// Returns the current time left until unlocking based on the knowledge
    /// the lock has. This may be incorrect if the lock is relocked later on.
    fn time_left(&self) -> TimerDuration {
        if !self.is_locked() {
            return TimerDuration::Elapsed;
        }
        self.timer.lock().unwrap().time_left()
    }
}

impl TimedLock {
    /// Locks the lock for some duration. If the lock is already locked, the
    /// old target will be replaced with the new one. This will not wake up
    /// any waiting threads, even if relocking is necessary.
    pub fn lock_for(&self, duration: TimerDuration) {
        if duration == TimerDuration::Elapsed {
            self.unlock();
            return;
        }

        *self.timer.lock().unwrap() = Timer::new(duration);

        self.relock();
    }

    /// Relocks (without waking up waiting threads) the lock with a different duration,
    /// taken from the function given the current time left as the input.
    ///
    /// If the lock is unlocked, the function will instead be given `if_unlocked`.
    pub fn change_lock_time(
        &self,
        f: impl FnOnce(TimerDuration) -> TimerDuration,
        if_unlocked: TimerDuration,
    ) {
        if !self.is_locked() {
            self.lock_for(f(if_unlocked));
            return;
        }

        let timer = self.timer.lock().unwrap();

        let old_time = timer.time_left();
        let new_time = f(old_time);

        drop(timer);

        self.lock_for(new_time);
    }

    /// Relocks (without waking up waiting threads) the lock with a duration that is
    /// either the current lock duration (=> no change), or the given duration,
    /// whichever is shorter.
    ///
    /// If the lock is unlocked, `lock_if_unlocked` decides whether to leave it
    /// unlocked or lock it with the given duration.
    pub fn reduce_lock_time(&self, duration: TimerDuration, lock_if_unlocked: bool) {
        self.change_lock_time(
            |old_time| old_time.min(duration),
            if lock_if_unlocked {
                TimerDuration::Infinite
            } else {
                TimerDuration::Elapsed
            },
        );
    }

    /// Relocks (without waking up waiting threads) the lock with a duration that is
    /// either the current lock duration (=> no change), or the given duration,
    /// whichever is longer.
    pub fn increase_lock_time(&self, duration: TimerDuration) {
        self.change_lock_time(|old_time| old_time.max(duration), TimerDuration::Elapsed);
    }

    pub fn lock_for_ms(&self, duration_ms: u64) {
        self.lock_for(TimerDuration::from(duration_ms))
    }

    /// Unlocks the inner untimed lock and then locks it again. This does *not* wake
    /// up the threads waiting on this lock.
    ///
    /// If the lock is not currently locked, it will be.
    fn relock(&self) {
        if self.inner.is_locked() {
            self.inner.unlock();
        }
        self.inner.lock();
    }
}

impl Lock for TimedLock {
    /// Checks if the lock should still be locked based on the time, then
    /// returns that. This updating is NOT necessary to release waiting
    /// threads.
    fn is_locked(&self) -> bool {
        let timer = self.timer.lock().unwrap();
        if timer.has_elapsed() {
            drop(timer);
            self.unlock();
            return false;
        }
        true
    }

    fn lock(&self) {
        self.lock_for(TimerDuration::Infinite);
    }

    fn try_lock(&self) -> bool {
        *self.timer.lock().unwrap() = Timer::new(TimerDuration::Infinite);

        if self.inner.is_locked() {
            self.inner.unlock();
        }
        self.inner.try_lock()
    }

    fn unlock(&self) {
        *self.timer.lock().unwrap() = Timer::new(TimerDuration::Elapsed);
        self.inner.unlock()
    }

    fn wait_here(&self) {
        while self.is_locked() {
            let time = self.timer.lock().unwrap().time_left();
            self.inner.wait_here_for(time);
        }
    }

    fn wait_here_for(&self, timeout: TimerDuration) {
        let timer = Timer::new(timeout);
        while !timer.has_elapsed() && self.is_locked() {
            let time = self.timer.lock().unwrap().time_left();
            self.inner.wait_here_for(time.min(timer.time_left()));
        }
    }

    fn wait_here_for_ms(&self, timeout_ms: u64) {
        self.wait_here_for(Duration::from_millis(timeout_ms).into());
    }
}