trellis_runner/engine/event.rs
1use crate::progress::Progress;
2use crate::Termination;
3
4/// A batch of progress signals emitted during a single solver iteration.
5///
6/// The engine aggregates low-level `Progress` signals produced by the
7/// procedure and convergence subsystem into a single batch.
8///
9/// Policies consume this batch to make decisions about:
10/// - convergence
11/// - stagnation
12/// - checkpointing
13/// - termination
14#[derive(Debug)]
15pub struct EventBatch<F> {
16 /// All progress signals emitted during the current iteration.
17 pub events: Vec<Progress<F>>,
18}
19
20impl<F> Default for EventBatch<F> {
21 fn default() -> Self {
22 Self::new()
23 }
24}
25
26impl<F> EventBatch<F> {
27 /// Generate and empty batch
28 pub fn new() -> Self {
29 Self { events: vec![] }
30 }
31 /// Adds a progress event to the batch.
32 pub fn add(mut self, event: Progress<F>) -> Self {
33 self.events.push(event);
34 self
35 }
36}
37
38/// Action returned by the policy layer after evaluating an [`EventBatch`]
39/// and the current engine context.
40///
41/// This is the *only control signal* that influences the engine loop.
42///
43/// The engine reacts deterministically to this value.
44#[derive(Clone, Debug, PartialEq)]
45pub enum EngineAction {
46 /// Continue normal execution with no side effects.
47 Continue,
48
49 /// Request that the engine persists a checkpoint of the current state.
50 ///
51 /// This does not stop execution; it is a side-effect request.
52 EmitCheckpoint(CheckpointReason),
53
54 /// Request termination of the solver.
55 ///
56 /// This immediately ends execution and propagates the termination reason
57 /// to the final [`EngineOutput`].
58 Stop(Termination),
59}
60
61/// Reason for emitting a checkpoint.
62///
63/// This distinguishes between scheduled persistence and semantic triggers
64/// (e.g. stagnation recovery or user-driven requests).
65#[derive(Clone, Debug, PartialEq)]
66pub enum CheckpointReason {
67 /// Checkpoint triggered on a fixed schedule (e.g. every N iterations).
68 Scheduled,
69
70 /// Checkpoint triggered due to stagnation detection.
71 ///
72 /// Typically used for recovery or restart strategies.
73 Stagnation,
74
75 /// Checkpoint triggered by an external user or system request.
76 UserRequest,
77}
78
79/// High-level lifecycle events emitted by the engine.
80///
81/// These events are used for:
82/// - observers / logging
83/// - external monitoring
84/// - UI updates
85///
86/// They are distinct from [`Progress`], which represents *numerical solver signals*.
87#[derive(Debug)]
88pub enum EngineSignal<F> {
89 /// Engine has completed initialisation and is ready to iterate.
90 Initialised,
91
92 /// A single progress signal emitted during iteration.
93 Progress(Progress<F>),
94
95 /// Engine has completed a single iteration
96 Iterated,
97
98 /// A checkpoint has been successfully persisted.
99 CheckpointSaved,
100
101 /// A checkpoint has been requested.
102 CheckpointRequested(CheckpointReason),
103
104 /// Engine has terminated for any reason.
105 Termination(Termination),
106}
107
108impl<F> EngineSignal<F> {
109 /// Returns a stable string tag identifying the event kind.
110 pub fn as_tag(&self) -> &'static str {
111 match self {
112 Self::Initialised => "initialised",
113 Self::Progress(_) => "progress",
114 Self::Iterated => "iterated",
115 Self::CheckpointSaved => "checkpoint_saved",
116 Self::Termination(_) => "termination",
117 Self::CheckpointRequested(_) => "checkpoint_requested",
118 }
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125 #[test]
126 fn event_batch_accumulates_events() {
127 let batch = EventBatch::new()
128 .add(Progress::Measure(1.0))
129 .add(Progress::Measure(2.0));
130
131 assert_eq!(batch.events.len(), 2);
132 }
133}