logicaffeine_runtime/task.rs
1//! Tasks — the unit the scheduler multiplexes.
2//!
3//! A task is a small state machine: each `poll` advances it until it next wants
4//! to block (on a channel, a select, or a timer), yield, spawn, or exit. The
5//! scheduler drives `poll`, parks the task on blocking steps, and resumes it
6//! (delivering any value) when the block clears. The interpreter and VM each
7//! implement [`Task`] over their own continuation; the toy tasks in the tests
8//! implement it by hand.
9
10use crate::channel::ChanId;
11use crate::payload::RtPayload;
12
13/// A scheduler-assigned task handle.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
15pub struct TaskId(pub u64);
16
17/// One arm of a `Select` (`Await the first of:`).
18#[derive(Debug, Clone)]
19pub enum SelectArm {
20 /// `Receive x from ch:` — ready when `ch` has a value (or a waiting sender).
21 Recv(ChanId),
22 /// `After d ticks:` — fires after `d` logical ticks if no earlier arm wins.
23 Timeout(u64),
24}
25
26/// What a task asks the scheduler to do after a `poll`.
27pub enum TaskStep<'t> {
28 /// Cooperative yield — re-enqueue me behind the other ready tasks.
29 Yield,
30 /// Block until a value can be received from `ch`; resume with that value
31 /// (delivered as `TaskCtx::resumed_with`).
32 Recv(ChanId),
33 /// Block until `ch` has room, then hand it `payload`; resume.
34 Send(ChanId, RtPayload),
35 /// Non-blocking send: hand `payload` to `ch` if it can be taken right now
36 /// (a waiting receiver or room in the buffer); resume immediately with
37 /// `RtPayload::Bool(true)` on success, `Bool(false)` if it would have blocked.
38 TrySend(ChanId, RtPayload),
39 /// Non-blocking receive: resume immediately with a value if one is available
40 /// (in `TaskCtx::resumed_with`), or `RtPayload::Nothing` if the channel is empty.
41 TryRecv(ChanId),
42 /// Block on a choice over several arms; resume with the winning arm's index
43 /// in `TaskCtx::selected_arm` (and its value in `resumed_with` for a recv arm).
44 Select(Vec<SelectArm>),
45 /// Block for `ticks` logical ticks.
46 Sleep(u64),
47 /// Create a channel with the given capacity; resume immediately with its
48 /// `ChanId` (delivered in `TaskCtx::new_chan`).
49 NewChan(Option<usize>),
50 /// Spawn a child task; resume immediately with its `TaskId` (in `TaskCtx::spawned`).
51 Spawn(Box<dyn Task<'t> + 't>),
52 /// Spawn a child *by descriptor* (function index + already-materialised
53 /// arguments) rather than a boxed body — resume with its `TaskId`. The boxed
54 /// [`TaskStep::Spawn`] cannot cross an OS-thread boundary (the body is `!Send`);
55 /// the work-stealing driver uses this `Send` form so any worker can build the
56 /// child locally from the descriptor. (The cooperative driver builds inline and
57 /// keeps using `Spawn`.)
58 SpawnDesc { func: u16, args: Vec<RtPayload>, want_handle: bool },
59 /// Block until task `TaskId` finishes; resume with its result payload
60 /// (`Nothing` if that task was aborted). Backs awaiting a task handle.
61 Await(TaskId),
62 /// Cancel task `TaskId` (backs `Stop handle`); resume immediately. The
63 /// aborted task never polls again and its awaiters observe an aborted result.
64 Abort(TaskId),
65 /// Close channel `ChanId`; resume immediately. Subsequent receives on the
66 /// drained channel return `Nothing` rather than blocking.
67 Close(ChanId),
68 /// Blocked on external async I/O (a network `await` — `Connect`/`Listen`/peer
69 /// messaging) that the scheduler itself cannot service: it has no reactor. The task is
70 /// parked `BlockedIo` and re-polled by the async drive loop after it yields to the host
71 /// reactor (tokio natively, the browser event loop on wasm). A purely channel/timer
72 /// program never produces this, so its behavior is byte-identical.
73 IoPending,
74 /// Finished, with a result payload.
75 Exit(RtPayload),
76}
77
78/// Per-`poll` context: what the task was resumed with.
79pub struct TaskCtx {
80 /// The value delivered by the blocking step that just resumed this task
81 /// (the received value for `Recv`/winning `Select` recv arm; `Nothing` otherwise).
82 pub resumed_with: RtPayload,
83 /// For a resumed `Select`, the index of the arm that won; `None` otherwise.
84 pub selected_arm: Option<usize>,
85 /// For a resumed `NewChan`, the id of the freshly-created channel.
86 pub new_chan: Option<ChanId>,
87 /// For a resumed `Spawn`, the id of the spawned task (its handle).
88 pub spawned: Option<TaskId>,
89}
90
91/// A schedulable unit of work. `'t` ties any spawned children to the lifetime of
92/// borrowed data the task closes over (e.g. the interpreter's borrowed AST).
93pub trait Task<'t> {
94 /// Advance the task until its next scheduling point.
95 fn poll(&mut self, ctx: &mut TaskCtx) -> TaskStep<'t>;
96
97 /// Scheduling priority — higher runs first under [`crate::SchedulePolicy::Priority`].
98 /// Defaults to 0 (all tasks equal).
99 fn priority(&self) -> u8 {
100 0
101 }
102
103 /// Output lines the task produced during the *just-finished* `poll` slice, in
104 /// order. The work-stealing driver collects these per slice and flushes them in
105 /// the deterministic pick-order apply, so concurrent output matches the
106 /// cooperative interleaving byte-for-byte. Cooperative tasks write straight to
107 /// their sink and leave this empty (the default).
108 fn take_output(&mut self) -> Vec<String> {
109 Vec::new()
110 }
111}