Skip to main content

nmbrs_runtime/
lifecycle.rs

1// Copyright 2024-2026 Jonathan Shook
2// SPDX-License-Identifier: Apache-2.0
3
4//! Lifecycle event vocabulary — the kind-tag [`EventType`]
5//! plus its [`SubjectKind`].
6//!
7//! [`EventType`] names the lifecycle slot that triggered a
8//! render (`on_phase_end`, `on_update`, …); [`SubjectKind`]
9//! names what kind of subject the firing context represents
10//! (session / phase / iteration / scope). Together they are
11//! the shared lifecycle vocabulary used by the readout binder
12//! (to validate and dispatch readouts) and by the checkpoint
13//! log (to tie a durable data record to its lifecycle kind-tag).
14//!
15//! See SRD-63 §4.1. Two kinds of events exist:
16//!
17//! - **Lifecycle events** — fire exactly once per
18//!   `(slot, subject)`. `_start` and `_end` are
19//!   delaminated; nothing fires twice for the same
20//!   subject under one slot.
21//! - **Refresh events** — fire repeatedly while the
22//!   subject is in flight. Currently only [`EventType::Update`].
23//!
24//! Push 2 wires `Update` and `PhaseEnd` through the activity
25//! pipeline; the remaining variants are reachable from Push 3
26//! onward as the workload-side `readouts:` parser maps slot
27//! names to events and Push 4 wires wildcard binding to
28//! cover scope and session lifecycles.
29
30#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
31pub enum EventType {
32    /// `on_session_start` — the run is opening.
33    SessionStart,
34    /// `on_session_end` — the run is closing. Final render
35    /// for any session-scoped readout.
36    SessionEnd,
37
38    /// `on_phase_start` — a phase is opening. Equivalent to
39    /// today's deleted phase-starting log row when the
40    /// `phase_starting` readout is bound.
41    PhaseStart,
42    /// `on_phase_end` — a phase is closing. The ✓ DONE line
43    /// (`phase_outcome` readout) is bound here today.
44    PhaseEnd,
45
46    /// `on_each_start` — a `for_each` / `for_combinations`
47    /// iteration is opening. The current scope-ancestor
48    /// header (`· for_each profile=…`) becomes the
49    /// `scope_header` readout bound here in Push 3.
50    EachStart,
51    /// `on_each_end` — an iteration is closing.
52    EachEnd,
53
54    /// `on_scope_start` — a non-iteration scope group is
55    /// opening.
56    ScopeStart,
57    /// `on_scope_end` — a non-iteration scope group is
58    /// closing.
59    ScopeEnd,
60
61    /// `on_update` — periodic refresh tick. Today's inline
62    /// progress thread fires this at 0.5 s; the TUI fires
63    /// it per-frame. Drives the live status content.
64    Update,
65}
66
67impl EventType {
68    /// Lower-snake-case name matching the `readouts:` slot
69    /// keyword (`on_update`, `on_phase_end`, …). Used by
70    /// the `trace` diagnostic readout and by Push 3's
71    /// workload-block parser.
72    pub fn slot_name(self) -> &'static str {
73        match self {
74            EventType::SessionStart => "on_session_start",
75            EventType::SessionEnd => "on_session_end",
76            EventType::PhaseStart => "on_phase_start",
77            EventType::PhaseEnd => "on_phase_end",
78            EventType::EachStart => "on_each_start",
79            EventType::EachEnd => "on_each_end",
80            EventType::ScopeStart => "on_scope_start",
81            EventType::ScopeEnd => "on_scope_end",
82            EventType::Update => "on_update",
83        }
84    }
85
86    /// What kind of subject the context that fires this
87    /// event represents. Used by the binder to validate at
88    /// bind-time that bound readouts accept the slot's
89    /// subject kind. `Update` rides on the surrounding
90    /// phase, so it reports `Phase`.
91    pub fn subject_kind(self) -> SubjectKind {
92        match self {
93            EventType::SessionStart | EventType::SessionEnd => SubjectKind::Session,
94            EventType::PhaseStart | EventType::PhaseEnd | EventType::Update => SubjectKind::Phase,
95            EventType::EachStart | EventType::EachEnd => SubjectKind::Iteration,
96            EventType::ScopeStart | EventType::ScopeEnd => SubjectKind::Scope,
97        }
98    }
99}
100
101/// What kind of subject a context (and a render) is
102/// scoped to. Determined by the firing event and the
103/// surface that built the context. Builtins declare
104/// which kinds they accept; the binder validates at
105/// bake-time.
106#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
107pub enum SubjectKind {
108    /// The whole run. Used for `on_session_start` /
109    /// `on_session_end`.
110    Session,
111    /// A single phase activity. Used for `on_phase_start` /
112    /// `on_phase_end` / `on_update`.
113    Phase,
114    /// One iteration of a `for_each` / `for_combinations`
115    /// scope. Used for `on_each_start` / `on_each_end`.
116    Iteration,
117    /// A non-iteration scope group (`do_while` /
118    /// `do_until`). Used for `on_scope_start` /
119    /// `on_scope_end`.
120    Scope,
121}
122
123impl SubjectKind {
124    /// Lower-snake-case name for the storage / replay
125    /// surface. Stored in the `readout_snapshots.subject_kind`
126    /// column so `nmbrs replay` can group rows by subject.
127    pub fn as_str(self) -> &'static str {
128        match self {
129            SubjectKind::Session => "session",
130            SubjectKind::Phase => "phase",
131            SubjectKind::Iteration => "iteration",
132            SubjectKind::Scope => "scope",
133        }
134    }
135}