///|
/// CycleHook — zero-cost trait for intercepting the runtime cyclic loop.
///
/// Design rationale:
/// - MoonBit open traits monomorphize at compile time → zero vtable overhead
/// - Each hook method has a default no-op semantic; implementors override only
/// what they need
/// - The hook sits between ProcessImage and the Nic layer, enabling:
/// • Pre-cycle output injection (plant model / controller writes)
/// • Post-cycle input observation (data logging / verdict / co-sim)
/// • Cycle-level fault injection (WKC manipulation, frame drop)
/// • Cycle-level breakpoint / single-step support for HIL debugging
///
/// Performance contract:
/// - `on_pre_cycle` and `on_post_cycle` execute inline in the hot path
/// - Implementations MUST be bounded O(1) or O(slave_count)
/// - No heap allocation in the default (NullCycleHook) path
///|
/// CycleSnapshot captures the minimal state exposed to hooks at each cycle boundary.
pub(all) struct CycleSnapshot {
/// Monotonically increasing cycle counter (from Telemetry.cycles).
cycle : Int64
/// The CycleResult from the most recent PDO exchange (post-cycle only).
result : CycleResult?
/// Current DC synchronization state.
dc_state : DcState
/// Whether the runtime will trigger recovery after this cycle.
recovery_pending : Bool
} derive(Debug)
///|
/// CycleDirective tells the runtime what action to take after a hook returns.
pub(all) enum CycleDirective {
/// Continue normal execution.
Continue
/// Skip this cycle's PDO exchange (pre-cycle only: outputs are not sent).
SkipCycle
/// Request immediate graceful stop after this cycle.
StopGraceful
} derive(Eq, Debug)
///|
/// CycleHook — the runtime calls these methods at each cycle boundary.
///
/// Trait methods use `Self` (not `&Self`) following MoonBit trait convention.
/// The runtime passes `CycleSnapshot` by value (16-24 bytes, stack-allocated).
pub(open) trait CycleHook {
/// Called before PDO exchange. Receives mutable access to ProcessImage
/// outputs via the PdoContext. Return a directive controlling cycle execution.
fn on_pre_cycle(Self, CycleSnapshot, @protocol.PdoContext) -> CycleDirective
/// Called after PDO exchange with the cycle result. Receives the updated
/// ProcessImage inputs via PdoContext. Return a directive for the next step.
fn on_post_cycle(Self, CycleSnapshot, @protocol.PdoContext) -> CycleDirective
/// Called when the runtime encounters a fault condition (timeout, WKC error,
/// DC drift). The hook may log, record, or override recovery behavior.
fn on_fault(Self, CycleSnapshot, @hal.EcError) -> CycleDirective
}
///|
/// NullCycleHook — default no-op hook. Compiles to nothing in monomorphized code.
pub struct NullCycleHook {
_unused : Unit
}
///|
pub fn NullCycleHook::new() -> NullCycleHook {
{ _unused: () }
}
///|
pub impl CycleHook for NullCycleHook with fn on_pre_cycle(self, _snap, _ctx) {
ignore(self)
Continue
}
///|
pub impl CycleHook for NullCycleHook with fn on_post_cycle(self, _snap, _ctx) {
ignore(self)
Continue
}
///|
pub impl CycleHook for NullCycleHook with fn on_fault(self, _snap, _err) {
ignore(self)
Continue
}