Transition Trace, Telemetry & Introspection¶
fsmc provides rich, non-intrusive transition trace telemetry, observer callbacks, and deterministic tick-based timer management with 0 bytes dynamic memory allocation and zero performance overhead.
1. fsm::dispatch_result & fsm::step_result¶
Every call to dispatch() or post_async() returns an fsm::dispatch_result, while periodic calls to step() return an fsm::step_result. Both structs carry an optional fsm::transition_trace:
namespace fsm {
enum class dispatch_status : std::uint8_t {
success, // Transition executed successfully
deferred, // Event was deferred by the active state
guard_rejected, // Matching transition found, but guard evaluated to false
unhandled // No transition defined for (current_state, event)
};
enum class step_status : std::uint8_t {
steady, // Machine remains nominally in active state
transitioned // A continuous transition condition fired
};
enum class transition_kind : std::uint8_t {
external,
internal
};
struct transition_trace {
std::string_view source{};
std::string_view target{};
std::string_view event{};
std::string_view guard{};
std::string_view action{};
transition_kind kind{transition_kind::external};
[[nodiscard]] constexpr bool is_internal() const noexcept;
[[nodiscard]] constexpr bool is_external() const noexcept;
};
struct dispatch_result {
dispatch_status status = dispatch_status::unhandled;
std::optional<transition_trace> trace = std::nullopt;
[[nodiscard]] constexpr bool is_success() const noexcept;
[[nodiscard]] constexpr bool is_deferred() const noexcept;
[[nodiscard]] constexpr bool is_guard_rejected() const noexcept;
[[nodiscard]] constexpr bool is_unhandled() const noexcept;
[[nodiscard]] constexpr bool is_ok() const noexcept; // success || deferred
[[nodiscard]] constexpr std::string_view to_string() const noexcept;
};
struct step_result {
step_status status = step_status::steady;
std::optional<transition_trace> trace = std::nullopt;
[[nodiscard]] constexpr bool has_transitioned() const noexcept;
[[nodiscard]] constexpr bool is_steady() const noexcept;
[[nodiscard]] constexpr explicit operator bool() const noexcept { return has_transitioned(); }
[[nodiscard]] constexpr std::string_view to_string() const noexcept;
};
} // namespace fsm
2. Zero-Allocation Flight Recorder Telemetry¶
For post-mortem auditing, blackbox event logging, and hard real-time diagnostics, fsmc provides fsm::TraceBuffer<Capacity> and fsm::flight_recorder_observer<Capacity> (flight_recorder.hpp).
Architectural Properties:¶
- 0 Bytes Dynamic Allocation: Storage resides entirely on the stack, in
.bss, or within the FSM object. - O(1) Push and Query Time: Constant-time circular ring buffer recording with deterministic wrap-around.
- Chronological Logical Indexing:
recorder[0]always accesses the oldest recorded entry, andrecorder[size - 1]accesses the newest, irrespective of internal circular wrap-around.
#include <fsm/backend/cpp/runtime/fsm.hpp>
#include <fsm/backend/cpp/runtime/flight_recorder.hpp>
// 1. Configure FSM with a 64-entry Blackbox Flight Recorder
using FmsEngine = fsm::make_fsm<
FlightTable,
fsm::with_initial_state<PreflightState>,
fsm::with_trace_buffer<64>,
fsm::with_timer_capacity<8>
>;
FmsEngine sm;
// 2. Dispatch transitions and advance deterministic ticks
sm.dispatch(EvArmed{});
sm.tick(100); // Advances internal timers and updates flight recorder tick timestamp
sm.dispatch(EvTakeoff{});
// 3. Inspect recent trace entries or dump table
auto& recorder = sm.observer().recorder();
if (auto last = recorder.last_entry(); last.has_value()) {
std::cout << "Last transition at tick " << last->tick
<< ": " << last->source_state << " -> " << last->target_state << "\n";
}
// 4. Dump formatted ASCII flight recorder audit table
recorder.dump(std::cout);
Formatted Output Dump Example:¶
=== FSM Flight Recorder Audit Trace (2/64 entries) ===
TICK SOURCE EVENT TARGET STATUS
--------------------------------------------------------------------------------------------
0 Preflight EvArmed Armed TAKEN
100 Armed EvTakeoff Ascending TAKEN
3. Transition Observers¶
You can attach a compile-time or runtime observer callback to monitor transitions globally across the system:
struct transition_info {
std::string_view source;
std::string_view target;
std::string_view event;
dispatch_status status = dispatch_status::success;
transition_kind kind = transition_kind::external;
};
Attach a custom observer to the FSM instance:
sm.set_observer([](const fsm::transition_info& info) {
if (info.is_success()) {
CAN_Bus_SendLog(info.source.data(), info.target.data(), info.event.data());
}
});
4. Deterministic Tick-Based Timer Manager¶
In hard real-time and embedded systems, operating system background timers (std::thread, POSIX timers) introduce non-determinism and thread scheduling jitter.
fsmc provides a fully integrated, zero-allocation synchronous timer engine configured via fsm::with_timer_capacity<MaxTimers>:
using MyFsm = fsm::make_fsm<
MyTable,
fsm::with_initial_state<IdleState>,
fsm::with_timer_capacity<8> // Allocate fixed 8-timer manager (0 heap allocations)
>;
MyFsm sm;
// Advance time synchronously in your periodic control loop
void periodic_task_10ms() {
uint64_t dt_ms = 10;
// 1. tick(dt) decrements active timers and fires expired timed transitions
sm.tick(dt_ms);
// 2. step() evaluates continuous conditions or during actions
sm.step();
}
The underlying fsm::deterministic_timer_manager<N> can also be used as a standalone timer utility:
#include <fsm/backend/cpp/runtime/deterministic_timer.hpp>
fsm::deterministic_timer_manager<16> timers;
timers.start_timer(1001 /* timer_id */, 500 /* duration_ms */, false /* periodic */);
timers.tick(10, [](uint32_t expired_id) {
std::cout << "Timer expired: " << expired_id << "\n";
});
5. Zero-Heap Time-Travel Snapshot Recorder (fsm::snapshot_recorder)¶
For high-integrity systems, Digital Twin synchronization, Hardware-in-the-Loop (HIL) fault recovery, and time-travel replay debugging, fsmc provides fsm::snapshot_recorder<Capacity, MaxSize> (snapshot_recorder.hpp).
Architectural Properties:¶
- Zero Dynamic Allocation: Fixed \(O(1)\) ring buffer residing entirely on the stack or in static memory.
- Integrity Validation: Snapshots are packed with byte-level state identifiers and CRC/checksum headers (
fsm::state_snapshot_entry). - Time-Travel Rollback: Instantly restores active variant state, discrete registers, and timers to any prior recorded checkpoint.
Complete Usage Example:¶
#include <fsm/backend/cpp/runtime/fsm.hpp>
#include <fsm/backend/cpp/runtime/snapshot_recorder.hpp>
// 1. Instantiate state machine and a 16-entry zero-heap snapshot recorder
MyFSM ecu;
fsm::snapshot_recorder<16, 512> recorder;
// 2. Record nominal state checkpoints with timestamp and custom tag
recorder.record(ecu, 100 /* checkpoint tag: Standby */, 1000 /* timestamp_ms */);
// 3. Dispatch event into operational state
ecu.dispatch(EvStart{});
recorder.record(ecu, 200 /* checkpoint tag: HealthyDrive */, 2000 /* timestamp_ms */);
// 4. Anomaly detected (e.g. radiator coolant leak or sensor glitch)
ecu.dispatch(EvOverheat{});
assert(ecu.is_in_state<LimpHome>());
// 5. Time-Travel: Supervisory controller rolls back to checkpoint 200 (HealthyDrive)
bool restored = recorder.rewind_to_checkpoint(ecu, 200);
if (restored) {
assert(ecu.is_in_state<Drive>());
std::cout << "Successfully rolled back ECU to HealthyDrive!\n";
}
// Or unwind relative steps backwards:
recorder.rollback(ecu, 1 /* step back */);
API Reference:¶
| Method | Signature | Description |
|---|---|---|
record |
bool record(const FSM& fsm, uint32_t id = 0, uint64_t ts = 0) |
Captures binary snapshot into circular ring buffer. Returns true on success. |
rollback |
bool rollback(FSM& fsm, size_t steps = 1) |
Unwinds state by steps chronological snapshots. |
rewind_to_checkpoint |
bool rewind_to_checkpoint(FSM& fsm, uint32_t checkpoint_id) |
Scans backward and restores the newest snapshot matching checkpoint_id. |
latest |
const Entry* latest() const |
Returns pointer to the most recently recorded snapshot entry. |
oldest |
const Entry* oldest() const |
Returns pointer to the oldest recorded snapshot entry still in buffer. |
count |
size_t count() const |
Current number of active snapshots stored in the recorder (\(0 \le \text{count} \le \text{Capacity}\)). |
clear |
void clear() |
Flushes all recorded snapshots in \(O(1)\). |