Skip to main content

bb_runtime/
exec_state.rs

1//! `ExecState` — the engine's per-poll execution-state bundle.
2//!
3//! Groups the fields that walk the DAG cascade together: the
4//! frontier work queue, the slot table, per-execution liveness,
5//! parked async ops, in-cycle completions, function-call
6//! invocation frames, and the monotonic ID allocator.
7//!
8//! `Engine` carries one `exec: ExecState` field rather than nine
9//! sibling fields. The architectural grouping matches the poll
10//! cycle: frontier drains seed downstream ops while completions
11//! match against parked async suspensions.
12//!
13//! The timer scheduler and inbound-envelope context map live on
14//! [`crate::framework::FrameworkComponents`] alongside the other
15//! syscall-driven primitives (`HoldTable`, `SerializeQueue`,
16//! `RecordBuffer`, `EventSource`). Syscalls (`Sleep`, `Interval`,
17//! `Pulse`) drive the scheduler; RX gates + `wire.Send` forwarding
18//! drive inbound context lookup. Treating them as framework
19//! primitives matches their consumers.
20
21use std::collections::{HashMap, VecDeque};
22
23use crate::engine::call_context::CallContext;
24use crate::engine::pending_async::{ExecutionState, PendingAsync};
25use crate::ids::{CommandId, ExecId, NodeSiteId, OpRef};
26use crate::runtime::PendingCompletion;
27use crate::slot_value::SlotValue;
28
29/// Monotonic per-Node ID source. Single-threaded reads/writes so the
30/// ingress queue stays the only cross-thread sync primitive.
31///
32/// `OpRef` has no counter - it packs `(graph_idx, node_idx)`
33/// positionally at install time; see [`crate::ids::OpRef::pack`].
34#[derive(Default)]
35pub struct IdAllocator {
36    /// Source of fresh `ExecId`s minted at every entry point.
37    pub next_exec_id: u64,
38    /// Source of fresh `CommandId`s for async dispatch suspensions.
39    pub next_command_id: u64,
40    /// Source of fresh `NodeSiteId`s for graph installation.
41    pub next_node_site_id: u64,
42}
43
44/// Per-poll execution-state bundle held on `Engine`.
45pub struct ExecState {
46    /// In-cycle DAG-walking queue: ops ready to fire now, paired
47    /// with the execution they belong to.
48    pub frontier: VecDeque<(OpRef, ExecId)>,
49    /// Per-execution slot storage keyed by `(NodeSiteId, ExecId)`.
50    pub slot_table: HashMap<(NodeSiteId, ExecId), Option<Box<dyn SlotValue>>>,
51    /// Per-execution liveness tracker (output counter used for GC
52    /// bookkeeping when an execution finishes).
53    pub execution_state: HashMap<ExecId, ExecutionState>,
54    /// Suspended ops awaiting `CommandId` completion. Matched
55    /// against `pending_completions` on the engine's completion drain.
56    pub pending_async: HashMap<CommandId, PendingAsync>,
57    /// Completions captured during a dispatch hook. Drained by the
58    /// engine and matched against `pending_async`.
59    pub pending_completions: Vec<PendingCompletion>,
60    /// Active function-call invocations keyed by the body's
61    /// `ExecId`. Populated when `OpDispatch::FunctionCall` fires;
62    /// removed as outputs are forwarded back to the caller.
63    pub pending_calls: HashMap<ExecId, CallContext>,
64    /// Monotonic ID source for `ExecId` / `CommandId` /
65    /// `NodeSiteId` / `OpRef`.
66    pub ids: IdAllocator,
67}
68
69impl Default for ExecState {
70    fn default() -> Self {
71        Self::new()
72    }
73}
74
75impl ExecState {
76    /// Construct a fresh `ExecState` with empty queues and zero
77    /// counters.
78    pub fn new() -> Self {
79        Self {
80            frontier: VecDeque::new(),
81            slot_table: HashMap::new(),
82            execution_state: HashMap::new(),
83            pending_async: HashMap::new(),
84            pending_completions: Vec::new(),
85            pending_calls: HashMap::new(),
86            ids: IdAllocator::default(),
87        }
88    }
89}