Skip to main content

logicaffeine_compile/concurrency/
bridge.rs

1//! The async↔scheduler bridge.
2//!
3//! The tree-walker executes statements in an `async fn`. To run concurrent tasks
4//! on the (synchronous, poll-based) [`logicaffeine_runtime`] scheduler, each
5//! concurrency op writes a [`BlockingRequest`] into a per-task side-channel
6//! ([`YieldState`]) and `.await`s a [`YieldFuture`], which returns `Poll::Pending`
7//! once — suspending the interpreter's async stack at exactly that point. The
8//! task's driver ([`super::driver::InterpreterTask`]) reads the request, maps it
9//! to a scheduler `TaskStep`, and on the next poll delivers the scheduler's
10//! resume value back through the same channel.
11
12use std::cell::RefCell;
13use std::future::Future;
14use std::pin::Pin;
15use std::rc::Rc;
16use std::task::{Context, Poll};
17
18use logicaffeine_runtime::{ChanId, RtPayload, SelectArm, Task, TaskId};
19
20/// A blocking (or non-blocking-but-scheduler-routed) request the interpreter
21/// hands to the scheduler.
22pub enum BlockingRequest<'a> {
23    /// Create a channel of the given capacity (`None` = config default).
24    NewChan(Option<usize>),
25    /// Send a value into a channel (blocks if full).
26    Send(ChanId, RtPayload),
27    /// Non-blocking send; resume with `Payload(Bool(success))`.
28    TrySend(ChanId, RtPayload),
29    /// Receive a value from a channel (blocks if empty).
30    Recv(ChanId),
31    /// Non-blocking receive; resume with the value, or `Payload(Nothing)` if empty.
32    TryRecv(ChanId),
33    /// Await the first ready arm of a select.
34    Select(Vec<SelectArm>),
35    /// Sleep for some logical ticks.
36    Sleep(u64),
37    /// Spawn a child task; resume with its `TaskId`.
38    Spawn(Box<dyn Task<'a> + 'a>),
39    /// Await a task's completion; resume with its result payload.
40    Await(TaskId),
41    /// Abort a task.
42    Abort(TaskId),
43    /// Close a channel.
44    Close(ChanId),
45}
46
47/// What the scheduler delivers back to the interpreter on resume.
48pub enum ResumeValue {
49    /// Nothing meaningful (e.g. after `Send`/`Sleep`/`Spawn`-fire-and-forget).
50    None,
51    /// A received value (after `Recv` / a winning select recv arm).
52    Payload(RtPayload),
53    /// A freshly-created channel id (after `NewChan`).
54    Chan(ChanId),
55    /// A spawned task's id (after `Spawn`).
56    Task(TaskId),
57    /// The winning arm of a `Select`: its index plus the received payload
58    /// (`Nothing` for a timeout arm).
59    Select { arm: usize, payload: RtPayload },
60}
61
62impl ResumeValue {
63    /// The received payload, or `Nothing`.
64    pub fn into_payload(self) -> RtPayload {
65        match self {
66            ResumeValue::Payload(p) => p,
67            _ => RtPayload::Nothing,
68        }
69    }
70    /// The created channel id, if this resume was from a `NewChan`.
71    pub fn into_chan(self) -> Option<ChanId> {
72        match self {
73            ResumeValue::Chan(c) => Some(c),
74            _ => None,
75        }
76    }
77    /// The spawned task id, if this resume was from a `Spawn`.
78    pub fn into_task(self) -> Option<TaskId> {
79        match self {
80            ResumeValue::Task(t) => Some(t),
81            _ => None,
82        }
83    }
84    /// The winning arm index + its payload, if this resume was from a `Select`.
85    pub fn into_select(self) -> Option<(usize, RtPayload)> {
86        match self {
87            ResumeValue::Select { arm, payload } => Some((arm, payload)),
88            _ => None,
89        }
90    }
91}
92
93/// The per-task side-channel: the interpreter writes a `request`, the driver
94/// delivers a `resume`.
95pub struct YieldState<'a> {
96    pub request: Option<BlockingRequest<'a>>,
97    pub resume: ResumeValue,
98}
99
100impl<'a> YieldState<'a> {
101    pub fn new() -> Self {
102        YieldState { request: None, resume: ResumeValue::None }
103    }
104}
105
106impl<'a> Default for YieldState<'a> {
107    fn default() -> Self {
108        YieldState::new()
109    }
110}
111
112/// A shared handle to a task's side-channel (cloned between the interpreter and
113/// its driving `InterpreterTask`).
114pub type Yield<'a> = Rc<RefCell<YieldState<'a>>>;
115
116/// The future a concurrency op `.await`s: it returns `Poll::Pending` exactly once
117/// (the request was written just before), suspending the interpreter's async
118/// stack; the next poll (driven by the scheduler) resolves to the resume value.
119pub struct YieldFuture<'a> {
120    yielded: bool,
121    ys: Yield<'a>,
122}
123
124impl<'a> YieldFuture<'a> {
125    pub fn new(ys: Yield<'a>) -> Self {
126        YieldFuture { yielded: false, ys }
127    }
128}
129
130impl<'a> Future for YieldFuture<'a> {
131    type Output = ResumeValue;
132    fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<ResumeValue> {
133        if !self.yielded {
134            self.yielded = true;
135            Poll::Pending
136        } else {
137            let mut st = self.ys.borrow_mut();
138            Poll::Ready(std::mem::replace(&mut st.resume, ResumeValue::None))
139        }
140    }
141}