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}