dope-runtime 0.30.1

Thread-per-core execution and process lifecycle for dope
Documentation
use std::{mem, ops::ControlFlow, pin, task};

use dope_core::driver::{self, result::poll, route, route::indices, schedule, schedule::ready};
use drive::{clock, turn};
use schedule::clock::{Moment, Sample};
use turn::{modes, routes, scope};

use crate::executor::{self, boot, drive, root};

/// Evidence that one root and its installed application reached a fixed point.
#[must_use = "fixed-point evidence and its root output must be observed"]
pub struct Fixed<'d, T> {
    output: T,
    now: Moment<'d>,
}

impl<'d, T> Fixed<'d, T> {
    pub(crate) const fn new(output: T, now: Moment<'d>) -> Self {
        Self { output, now }
    }

    pub const fn output(&self) -> &T {
        &self.output
    }

    pub fn into_output(self) -> T {
        self.output
    }

    pub const fn now(&self) -> Moment<'d> {
        self.now
    }
}

const _: () = {
    assert!(mem::size_of::<Fixed<'_, ()>>() == mem::size_of::<Moment<'_>>());
    assert!(mem::align_of::<Fixed<'_, ()>>() == mem::align_of::<Moment<'_>>());
};

pub(crate) struct Block<'d, T, C> {
    state: T,
    source: C,
    slot: ready::Slot<'d, routes::Wake>,
}

enum Attempt<R, O> {
    Complete(Result<O, drive::Error>),
    Cancel(R, drive::Error),
}

enum Outcome<O, P> {
    Complete(O, P),
    Shutdown(Option<O>),
}

impl<O, P> Outcome<O, P> {
    fn complete(self) -> Result<(O, P), drive::Error> {
        match self {
            Self::Complete(output, proof) => Ok((output, proof)),
            Self::Shutdown(_) => Err(drive::Error::Shutdown),
        }
    }
}

impl<R, O> Attempt<R, O> {
    fn finish<'d>(self) -> Result<O, drive::Error>
    where
        R: boot::Root<'d, Output = O>,
    {
        match self {
            Self::Complete(result) => result,
            Self::Cancel(root, source) => {
                R::cancel(root);
                Err(source)
            }
        }
    }
}

impl<'run, 'driver, 'd, C> Block<'d, scope::Boot<'run, 'driver, 'd>, C>
where
    C: clock::Source,
{
    pub(crate) fn run_boot<R>(
        start: Result<Self, drive::Error>,
        root: R,
        shutdown: impl FnMut() -> turn::Status,
    ) -> Result<R::Output, drive::Error>
    where
        R: boot::Root<'d>,
    {
        match start {
            Ok(block) => block.poll_boot(root, shutdown),
            Err(source) => Attempt::Cancel(root, source),
        }
        .finish()
    }

    fn poll_boot<R>(
        mut self,
        mut root: R,
        mut shutdown: impl FnMut() -> turn::Status,
    ) -> Attempt<R, R::Output>
    where
        R: boot::Root<'d>,
    {
        let wake = self.slot.target();
        let (mut polled, output) = {
            let mut cause = boot::Cause::Start;
            loop {
                let tick = self.source.tick();
                let ready = match self.state.next_root(&self.slot, tick, &mut shutdown) {
                    Ok(ready) => ready,
                    Err(source) => return Attempt::Cancel(root, source),
                };
                let polled = if matches!(cause, boot::Cause::Start) || ready.is_root() {
                    let (polled, output) = ready.poll_boot(root, wake, cause);
                    match output {
                        boot::Poll::Pending(next) => {
                            root = next;
                            polled
                        }
                        boot::Poll::Ready(output) => break (polled, output),
                    }
                } else {
                    ready.skip()
                };
                cause = boot::Cause::Wake;
                let parked = polled.park();
                match parked.status() {
                    turn::Status::Active => {}
                    turn::Status::Shutdown => return Attempt::Cancel(root, drive::Error::Shutdown),
                }
                let idle = match parked.sync() {
                    Ok(Some(idle)) => idle,
                    Ok(None) => continue,
                    Err(error) => return Attempt::Cancel(root, error.into()),
                };
                let progress = idle.progress();
                if let Err(error) = idle.wait(progress, self.source) {
                    return Attempt::Cancel(root, error.into());
                }
            }
        };
        Attempt::Complete(loop {
            let parked = polled.park();
            match parked.status() {
                turn::Status::Active => {}
                turn::Status::Shutdown => break Err(drive::Error::Shutdown),
            }
            let idle = match parked.sync() {
                Ok(idle) => idle,
                Err(error) => break Err(error.into()),
            };
            if let Some(idle) = idle {
                let progress = idle.progress();
                if matches!(progress, schedule::Progress::Quiescent) {
                    match idle.sync() {
                        Ok(poll::Commit::Drained) => break Ok(output),
                        Ok(poll::Commit::Pending) => {}
                        Err(error) => break Err(error.into()),
                    }
                } else if let Err(error) = idle.wait(progress, self.source) {
                    break Err(error.into());
                }
            }
            let tick = self.source.tick();
            polled = match self.state.next_root(&self.slot, tick, &mut shutdown) {
                Ok(ready) => ready.skip(),
                Err(error) => break Err(error),
            };
        })
    }
}

impl<'d, T, C> Block<'d, T, C> {
    pub(crate) fn new(
        driver: driver::Reference<'d>,
        state: T,
        source: C,
    ) -> Result<Self, drive::Error> {
        let target = driver
            .targets::<routes::Wake>()
            .bind(indices::SlotIndex::ZERO, route::Epoch::INITIAL);
        let slot = driver.ready().make_slot(target.dispatch())?;
        Ok(Self {
            state,
            source,
            slot,
        })
    }
}

impl<'run, 'driver, 'app, 'd: 'app, D, S, C>
    Block<'d, scope::Scope<'run, 'driver, 'app, 'd, D, S>, C>
where
    D: executor::raw::App<'d>,
    C: clock::Time,
{
    pub(crate) fn run<R>(
        self,
        root: R,
        tick: impl Fn(C) -> Result<Sample, drive::Error>,
        shutdown: impl FnMut() -> turn::Status,
    ) -> Result<R::Output, drive::Error>
    where
        R: root::Root<'d>,
    {
        let (output, ()) = self
            .drive(root, tick, shutdown, |idle| {
                Ok(matches!(idle.sync()?, poll::Commit::Drained).then_some(()))
            })?
            .complete()?;
        Ok(output)
    }

    pub(crate) fn run_until<R>(
        self,
        root: R,
        tick: impl Fn(C) -> Result<Sample, drive::Error>,
        shutdown: impl FnMut() -> turn::Status,
    ) -> Result<ControlFlow<R::Output>, drive::Error>
    where
        R: root::Root<'d>,
    {
        Ok(
            match self.drive(root, tick, shutdown, |idle| {
                Ok(matches!(idle.sync()?, poll::Commit::Drained).then_some(()))
            })? {
                Outcome::Complete(output, ()) | Outcome::Shutdown(Some(output)) => {
                    ControlFlow::Break(output)
                }
                Outcome::Shutdown(None) => ControlFlow::Continue(()),
            },
        )
    }

    pub(crate) fn run_fixed<R>(
        self,
        root: R,
        tick: impl Fn(C) -> Result<Sample, drive::Error>,
        shutdown: impl FnMut() -> turn::Status,
    ) -> Result<Fixed<'d, R::Output>, drive::Error>
    where
        R: root::Root<'d>,
    {
        let (output, now) = self
            .drive(root, tick, shutdown, |idle| {
                let fixed = idle.is_fixed();
                let now = idle.now();
                let commit = idle.sync()?;
                Ok((fixed && matches!(commit, poll::Commit::Drained)).then_some(now))
            })?
            .complete()?;
        Ok(Fixed::new(output, now))
    }

    fn drive<R, P>(
        mut self,
        root: R,
        tick: impl Fn(C) -> Result<Sample, drive::Error>,
        mut shutdown: impl FnMut() -> turn::Status,
        complete: impl for<'turn> Fn(
            turn::Idle<'turn, 'd, modes::App<'turn, 'app, 'd, D, S>>,
        ) -> Result<Option<P>, drive::Error>,
    ) -> Result<Outcome<R::Output, P>, drive::Error>
    where
        R: root::Root<'d>,
    {
        let wake = self.slot.target();
        let (mut polled, output) = {
            let mut root = pin::pin!(root);
            let mut first = true;
            loop {
                let tick = tick(self.source)?;
                let ready = self.state.next_root(&self.slot, tick, &mut shutdown)?;
                let poll = first || ready.is_root();
                let (polled, output) = if poll {
                    ready.poll(root.as_mut(), wake)
                } else {
                    (ready.skip(), task::Poll::Pending)
                };
                first = false;
                match output {
                    task::Poll::Ready(output) => break (polled, output),
                    task::Poll::Pending => {
                        let parked = polled.park();
                        match parked.status() {
                            turn::Status::Active => {}
                            turn::Status::Shutdown => return Ok(Outcome::Shutdown(None)),
                        }
                        if let Some(idle) = parked.sync()? {
                            let progress = idle.progress();
                            idle.wait(progress, self.source)?;
                        }
                    }
                }
            }
        };
        loop {
            let parked = polled.park();
            match parked.status() {
                turn::Status::Active => {}
                turn::Status::Shutdown => return Ok(Outcome::Shutdown(Some(output))),
            }
            if let Some(idle) = parked.sync()?
                && let Some(proof) = complete(idle)?
            {
                return Ok(Outcome::Complete(output, proof));
            }
            let tick = tick(self.source)?;
            polled = self.state.next_app(tick, &mut shutdown)?.skip();
        }
    }
}