dope-runtime 0.43.0

Thread-per-core execution and process lifecycle for dope
use std::time;

use dope_core::driver::schedule::clock;

pub(crate) struct Elapsed;

#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub struct Range {
    origin: time::Instant,
    duration: time::Duration,
}

impl Range {
    pub(crate) fn try_add(
        origin: time::Instant,
        duration: time::Duration,
    ) -> Result<time::Instant, Self> {
        match origin.checked_add(duration) {
            Some(deadline) => Ok(deadline),
            None => Err(Self { origin, duration }),
        }
    }

    pub const fn origin(self) -> time::Instant {
        self.origin
    }

    pub const fn duration(self) -> time::Duration {
        self.duration
    }
}

pub(crate) trait Time: Copy {
    fn start(self) -> clock::Sample {
        clock::Sample::new(self.park())
    }

    fn park(self) -> time::Instant;

    fn limit(self, timeout: Option<time::Duration>) -> Option<time::Duration>;
}

pub(crate) trait Source: Time {
    fn sample(self) -> Reading;
}

pub(crate) trait Expiring: Time {
    fn tick(self) -> Result<clock::Sample, Elapsed>;
}

pub(crate) struct Reading {
    tick: clock::Sample,
    wall: time::Instant,
}

#[derive(Clone, Copy)]
pub(crate) struct Wall;

impl Time for Wall {
    fn park(self) -> time::Instant {
        self.sample().wall
    }

    fn limit(self, timeout: Option<time::Duration>) -> Option<time::Duration> {
        timeout
    }
}

impl Source for Wall {
    fn sample(self) -> Reading {
        let wall = time::Instant::now();
        Reading {
            tick: clock::Sample::new(wall),
            wall,
        }
    }
}

#[derive(Clone, Copy)]
struct Fixed<'d> {
    now: clock::Deadline<'d>,
}

impl Time for Fixed<'_> {
    fn park(self) -> time::Instant {
        self.now.into_raw()
    }

    fn limit(self, timeout: Option<time::Duration>) -> Option<time::Duration> {
        timeout.filter(time::Duration::is_zero)
    }
}

impl Source for Fixed<'_> {
    fn sample(self) -> Reading {
        Reading {
            tick: clock::Sample::new(self.now.into_raw()),
            wall: time::Instant::now(),
        }
    }
}

pub(crate) fn wall() -> Wall {
    Wall
}

pub(crate) fn at<'d>(now: clock::Deadline<'d>) -> impl Source + use<'d> {
    Fixed { now }
}

#[derive(Clone, Copy)]
pub(crate) struct Bound<S, L> {
    source: S,
    limit: L,
}

pub(crate) trait Limit: Copy {
    fn left(self, wall: time::Instant) -> time::Duration;
}

#[derive(Clone, Copy)]
pub(crate) struct Span {
    started: time::Instant,
    timeout: time::Duration,
}

impl Span {
    fn new(timeout: time::Duration) -> Self {
        Self {
            started: time::Instant::now(),
            timeout,
        }
    }
}

impl Limit for Span {
    fn left(self, wall: time::Instant) -> time::Duration {
        self.timeout
            .saturating_sub(wall.saturating_duration_since(self.started))
    }
}

#[derive(Clone, Copy)]
pub(crate) struct End(time::Instant);

impl End {
    fn try_new(duration: time::Duration) -> Result<Self, Range> {
        let origin = time::Instant::now();
        Range::try_add(origin, duration).map(Self)
    }
}

impl Limit for End {
    fn left(self, wall: time::Instant) -> time::Duration {
        self.0.saturating_duration_since(wall)
    }
}

impl<S, L> Bound<S, L> {
    const fn new(source: S, limit: L) -> Self {
        Self { source, limit }
    }
}

impl<S> Bound<S, Span> {
    pub(crate) fn for_duration(source: S, timeout: time::Duration) -> Self {
        Self::new(source, Span::new(timeout))
    }
}

impl<S> Bound<S, End> {
    pub(crate) fn until(source: S, timeout: time::Duration) -> Result<Self, Range> {
        let end = End::try_new(timeout)?;
        Ok(Self::new(source, end))
    }
}

impl<S, L> Time for Bound<S, L>
where
    S: Source,
    L: Limit,
{
    fn park(self) -> time::Instant {
        self.source.park()
    }

    fn limit(self, timeout: Option<time::Duration>) -> Option<time::Duration> {
        let timeout = self.source.limit(timeout);
        if timeout.is_some_and(|wait| wait.is_zero()) {
            return timeout;
        }
        let left = self.limit.left(time::Instant::now());
        Some(timeout.map_or(left, |wait| wait.min(left)))
    }
}

impl<S, L> Expiring for Bound<S, L>
where
    S: Source,
    L: Limit,
{
    fn tick(self) -> Result<clock::Sample, Elapsed> {
        let reading = self.source.sample();
        if self.limit.left(reading.wall).is_zero() {
            Err(Elapsed)
        } else {
            Ok(reading.tick)
        }
    }
}

const _: () = {
    assert!(std::mem::size_of::<Wall>() == 0);
    assert!(std::mem::size_of::<Fixed<'_>>() == std::mem::size_of::<clock::Deadline<'_>>());
    assert!(std::mem::size_of::<Range>() == std::mem::size_of::<(time::Instant, time::Duration)>());
    assert!(std::mem::size_of::<Bound<(), End>>() == std::mem::size_of::<time::Instant>());
    assert!(
        std::mem::size_of::<Bound<(), Span>>()
            == std::mem::size_of::<(time::Instant, time::Duration)>()
    );
};