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}