1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
//! This module provides interface to wrap a stackful synchronous code into an
//! asynchronous command loop.
//!
//! **NOTE** A Drone platform crate may re-export this module with its own
//! additions under the same name, in which case it should be used instead.

#![allow(clippy::wildcard_imports)]

use crate::{fib, fib::Fiber};
use core::{future::Future, mem::ManuallyDrop, pin::Pin};

type SessFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;

/// The trait for declaring a synchronous command loop.
///
/// This trait uses only associated items, thus it doesn't require the type to
/// ever be instantiated.
pub trait ProcLoop: Send + 'static {
    /// Token type that allows suspending the task while waiting for a request
    /// result.
    type Context: Context<Self::Req, Self::ReqRes>;

    /// `enum` of all possible commands.
    type Cmd: Send + 'static;

    /// `union` of all possible command results.
    type CmdRes: Send + 'static;

    /// `enum` of all possible requests.
    type Req: Send + 'static;

    /// `union` of all possible request results.
    type ReqRes: Send + 'static;

    /// Size of the process stack in bytes.
    const STACK_SIZE: usize;

    /// The commands runner.
    ///
    /// See [`ProcLoop`] for examples.
    fn run_cmd(cmd: Self::Cmd, context: Self::Context) -> Self::CmdRes;

    /// Runs on the process creation.
    #[inline]
    fn on_create() {}

    /// Runs inside the synchronous context before the command loop.
    #[inline]
    fn on_enter() {}

    /// Runs on the process destruction.
    #[inline]
    fn on_drop() {}
}

/// A session type for the synchronous command loop [`ProcLoop`].
///
/// A type that implements this trait should wrap the fiber for the command
/// loop.
pub trait Sess: Send {
    /// The command loop interface.
    type ProcLoop: ProcLoop;

    /// Fiber that runs the command loop.
    type Fiber: Fiber<
            Input = In<<Self::ProcLoop as ProcLoop>::Cmd, <Self::ProcLoop as ProcLoop>::ReqRes>,
            Yield = Out<<Self::ProcLoop as ProcLoop>::Req, <Self::ProcLoop as ProcLoop>::CmdRes>,
            Return = !,
        > + Send;

    /// Request error type.
    type Error: Send;

    /// Returns a pinned mutable reference to the fiber.
    fn fib(&mut self) -> Pin<&mut Self::Fiber>;

    /// Returns a future that will return a result for the request `req`.
    fn run_req(
        &mut self,
        req: <Self::ProcLoop as ProcLoop>::Req,
    ) -> SessFuture<'_, Result<<Self::ProcLoop as ProcLoop>::ReqRes, Self::Error>>;

    /// Returns a future that will return a result for the command `cmd`.
    fn cmd(
        &mut self,
        cmd: <Self::ProcLoop as ProcLoop>::Cmd,
    ) -> SessFuture<'_, Result<<Self::ProcLoop as ProcLoop>::CmdRes, Self::Error>> {
        let mut input = In::from_cmd(cmd);
        Box::pin(async move {
            loop {
                let fib::Yielded(output) = self.fib().resume(input);
                input = match output {
                    Out::Req(req) => In::from_req_res(self.run_req(req).await?),
                    Out::CmdRes(res) => break Ok(res),
                }
            }
        })
    }
}

/// A token that allows suspending synchronous code.
pub trait Context<Req, ReqRes>: Copy + 'static {
    /// Creates a new token.
    ///
    /// # Safety
    ///
    /// It is unsafe to create a token inside an inappropriate context.
    unsafe fn new() -> Self;

    /// Makes a new request `req`.
    ///
    /// This method suspends execution of the current task allowing to escape
    /// from synchronous code.
    fn req(self, req: Req) -> ReqRes;
}

/// [`Sess::Fiber`] input.
///
/// See also [`Out`].
pub union In<Cmd, ReqRes> {
    /// Command to run by the command loop.
    cmd: ManuallyDrop<Cmd>,
    /// Result for the last request.
    req_res: ManuallyDrop<ReqRes>,
}

/// [`Sess::Fiber`] output.
///
/// See also [`In`].
pub enum Out<Req, CmdRes> {
    /// Request that the command loop is waiting for.
    Req(Req),
    /// Result for the last command.
    CmdRes(CmdRes),
}

impl<Cmd, ReqRes> In<Cmd, ReqRes> {
    /// Creates a new command input.
    pub fn from_cmd(cmd: Cmd) -> Self {
        Self { cmd: ManuallyDrop::new(cmd) }
    }

    /// Creates a new request result input.
    pub fn from_req_res(req_res: ReqRes) -> Self {
        Self { req_res: ManuallyDrop::new(req_res) }
    }

    /// Interprets the input as a command.
    ///
    /// # Safety
    ///
    /// Whether the input is really a command object is unchecked.
    pub unsafe fn into_cmd(self) -> Cmd {
        ManuallyDrop::into_inner(unsafe { self.cmd })
    }

    /// Interprets the input as a request result.
    ///
    /// # Safety
    ///
    /// Whether the input is really a request result object is unchecked.
    pub unsafe fn into_req_res(self) -> ReqRes {
        ManuallyDrop::into_inner(unsafe { self.req_res })
    }
}