Tutorial 5: Code Generation & Build Integration¶
In this tutorial, you will learn how to turn your verified state machine models into production-ready software:
- How
fsmcuses the Generation Gap Pattern to ensure safe, non-destructive builds. - Generating standalone and modular C++ code (C++17 and C++20).
- Integrating automated compilation into CMake (
fsmc_target_sources). - Deploying with partitioned memory domains (
InPorts,OutPorts,Registers,Services).
1. The Generation Gap Pattern: Safe, Non-Destructive Builds¶
A common pitfall with code generation tools is that modifying a model might overwrite manually written business logic.
fsmc prevents this by enforcing a strict separation between Generated Artifacts and User Code:
my_project/
├── models/
│ └── connection.sysml <-- Source model (Source of Truth)
├── src/
│ ├── main.cpp <-- User Application Code (Untouched)
│ └── connection_services.hpp <-- User Service Implementations (Untouched)
└── build/generated/
└── connection_fsm.hpp <-- Generated Artifact (Regenerated automatically)
- The generated file (
connection_fsm.hpp) is never edited manually. It lives in yourbuild/directory. - Your custom side-effects live in your services implementation (
connection_services.hpp). - Whenever you update
connection.sysml,fsmcregenerates the header in milliseconds. Your application code simply links against the updated transition table with zero lost work.
2. Generating Code from the Command Line¶
# Option A: Generate a standalone self-contained C++20 header with embedded zero-alloc runtime
fsmc -i connection.sysml -o connection_fsm.hpp --target cpp --std 20 --standalone
# Option B: Export the runtime once and generate modular lightweight headers (ideal for multi-FSM projects)
# 1. Export standalone runtime library into your project include path (creates fsm.hpp):
fsmc --export-runtime ./include/ --std 20
# 2. Generate lightweight FSM definition including external "fsm.hpp":
fsmc -i connection.sysml -o connection_fsm.hpp --target cpp --std 20 --modular
# Option C: Use custom runtime header include path
fsmc -i connection.sysml -o connection_fsm.hpp --modular="<fsm/fsm.hpp>"
[!NOTE] Roadmap Preview: Rust code generation is a planned multi-target release feature. C++ is the current production runtime.
3. Understanding the Generated Header¶
The generated connection_fsm.hpp exposes three policy-based type aliases — one for each concurrency model:
// Synchronous FSM (single-threaded, zero-allocation)
using ConnectionManager = ::fsm::make_fsm<
ConnectionManagerTable,
::fsm::with_ports<ConnectionManagerInPorts, ConnectionManagerOutPorts>,
::fsm::with_registers<ConnectionManagerRegisters>,
::fsm::with_services<ConnectionManagerServices>
>;
// Lock-free SPSC FSM (ISR producer → consumer thread)
using SpscConnectionManager = ::fsm::make_spsc_fsm<
ConnectionManagerTable,
::fsm::with_ports<ConnectionManagerInPorts, ConnectionManagerOutPorts>,
::fsm::with_registers<ConnectionManagerRegisters>,
::fsm::with_services<ConnectionManagerServices>,
::fsm::with_queue_capacity<64>
>;
// Mutex-protected thread-safe FSM (multi-consumer safe)
using ThreadSafeConnectionManager = ::fsm::make_thread_safe_fsm<
ConnectionManagerTable,
::fsm::with_ports<ConnectionManagerInPorts, ConnectionManagerOutPorts>,
::fsm::with_registers<ConnectionManagerRegisters>,
::fsm::with_services<ConnectionManagerServices>
>;
The with_* policy modifiers can be composed in any order — the compiler resolves each domain independently via template specialization.
4. Using the Generated State Machine¶
In your main.cpp:
#include <cassert>
#include <iostream>
#include "connection_fsm.hpp"
// Implement the abstract service interface generated by fsmc
struct ConcreteConnectionServices : public conn::ConnectionManagerServices {
void log(const std::string& msg) override {
std::cout << "[SERVICE LOG] " << msg << "\n";
}
};
int main() {
using namespace conn;
// Policy-based instantiation: all domains bound at compile time
ConnectionManagerRegisters reg{};
ConcreteConnectionServices srv;
ConnectionManager fsm(reg, srv);
ConnectionManagerInPorts in;
in.latency_ms = 45.0;
in.is_authenticated = true;
ConnectionManagerOutPorts out;
// Validate port range contracts (generated by fsmc from SysML v2 constraints)
assert(in.validate_contracts());
std::cout << "Initial state: " << fsm.current_state_name() << "\n";
// 1. Dispatch typed events
fsm.dispatch(ConnectCmd{}, in, out);
std::cout << "State after ConnectCmd: " << fsm.current_state_name() << "\n";
fsm.dispatch(HandshakeOk{}, in, out);
std::cout << "State after HandshakeOk: " << fsm.current_state_name() << "\n";
// 2. Sampled continuous step (periodic IEC 61131-3 / SCADE style)
fsm.step(in, out);
return 0;
}
[!NOTE] Roadmap Preview: Preview of upcoming Rust
#![no_std]application code.
In your main.rs:
use conn::connection_fsm::*;
fn main() {
let mut fsm = ConnectionManagerFsm::new(ConnectionRegisters::default());
let mut in_ports = ConnectionInPorts { latency_ms: 45.0, is_authenticated: true };
let mut out_ports = ConnectionOutPorts::default();
// 1. Dispatch typed events
fsm.dispatch(&Event::ConnectCmd, &in_ports, &mut out_ports);
fsm.dispatch(&Event::HandshakeOk, &in_ports, &mut out_ports);
// 2. Sampled continuous step
fsm.step(&in_ports, &mut out_ports);
}
[!NOTE] Roadmap Preview: Preview of upcoming ISO C99 application code.
In your main.c:
#include "connection_fsm.h"
int main(void) {
conn_fsm_t fsm;
conn_registers_t reg = {0};
conn_in_ports_t in = {.latency_ms = 45.0f, .is_authenticated = true};
conn_out_ports_t out = {0};
conn_fsm_init(&fsm, ®);
/* 1. Dispatch typed events */
conn_fsm_dispatch(&fsm, CONN_EV_CONNECT, &in, &out);
conn_fsm_dispatch(&fsm, CONN_EV_HANDSHAKE_OK, &in, &out);
/* 2. Sampled continuous step */
conn_fsm_step(&fsm, &in, &out);
return 0;
}
5. Seamless CMake Integration (fsmc_target_sources)¶
To automate state machine compilation in your C++ build pipeline, use the CMake helper function provided by fsmc:
cmake_minimum_required(VERSION 3.16)
project(my_embedded_app CXX)
find_package(fsmc REQUIRED)
add_executable(my_app
src/main.cpp
)
target_link_libraries(my_app PRIVATE fsmc::runtime)
# Automatically compile models/connection.sysml into ${CMAKE_CURRENT_BINARY_DIR}/generated_fsm/
fsmc_target_sources(my_app
DIAGRAMS models/connection.sysml
NAME ConnectionManagerFSM
STANDARD 20
STANDALONE
NAMESPACE conn
)
Next Steps¶
Now that you have mastered the complete pipeline from modeling to code generation, proceed to the capstone chapter:
Tutorial 6: Complete Real-World Case Study (Autonomous UAV Controller).