From ae8ad58579f27c34b3e784397012335c4929f616 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Fri, 4 Sep 2026 23:26:20 -0500 Subject: [PATCH 1/8] feat(state_machine): generate the example's HFSM from its model at build time The example shipped ~2400 lines of generated C++ and no model. The machine could not be regenerated, edited, or checked, and the code had drifted: regenerating Complex.json with the current generator produces roughly two thousand different lines, three years of generator fixes that the example never got. The model is the source now, and the C++ is a build product like an object file. main/Complex.json is checked in; the three generated files are not. CMake runs the generator at configure time and lists Complex.json in CMAKE_CONFIGURE_DEPENDS, so editing the model regenerates on the next build rather than the next clean. Only the model-specific files are taken from the generator's output. It also emits its own state_base.hpp, deep_history_state.hpp, shallow_history_state.hpp and magic_enum.hpp, and this component already provides those -- espp's are the adapted ones the rest of the codebase expects, and putting the generator's copies on the include path would shadow them. Needs node >= 18; npx fetches the generator on demand, pinned to a commit so builds are reproducible. -DHFSM_GEN_COMMAND points the build at a local checkout instead, which is also how to build offline. The esp-idf CI image has no node, so that matrix entry installs it. Verified on esp-idf v6.1 / esp32: clean configure and build, and an edit to Complex.json reaching the compiled source on the next build. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01E4hRq1q5VGPjNn2dnkkqBy --- .github/workflows/build.yml | 5 + components/state_machine/README.md | 18 + components/state_machine/example/README.md | 36 + .../state_machine/example/main/CMakeLists.txt | 72 +- .../state_machine/example/main/Complex.json | 707 +++++++ .../example/main/Complex_event_data.hpp | 10 - .../example/main/Complex_generated_states.cpp | 1783 ----------------- .../example/main/Complex_generated_states.hpp | 676 ------- .../example/main/Complex_metadata.json | 5 - 9 files changed, 836 insertions(+), 2476 deletions(-) create mode 100644 components/state_machine/example/main/Complex.json delete mode 100644 components/state_machine/example/main/Complex_event_data.hpp delete mode 100644 components/state_machine/example/main/Complex_generated_states.cpp delete mode 100644 components/state_machine/example/main/Complex_generated_states.hpp delete mode 100644 components/state_machine/example/main/Complex_metadata.json diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f39e533dcb..d3a8cd719c 100755 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -298,6 +298,11 @@ jobs: target: esp32s3 - path: 'components/state_machine/example' target: esp32 + # this example GENERATES its state machine from Complex.json + # at configure time, and the esp-idf image has no node + command: >- + (command -v node >/dev/null || (apt-get update && apt-get install -y nodejs npm)) && + IDF_COMPONENT_MANAGER=0 idf.py build - path: 'components/stream_frame/example' target: esp32 - path: 'components/sx126x/example' diff --git a/components/state_machine/README.md b/components/state_machine/README.md index d21afa86bd..e763a66e96 100644 --- a/components/state_machine/README.md +++ b/components/state_machine/README.md @@ -11,6 +11,24 @@ Note: This is a generic HFSM implementation - it should be used with generated code or a manually written state machine, as it provides no functionality on its own. +## Generating a state machine + +`state_machine` is the runtime; the machines themselves are generated +from a model by [webgme-hfsm][repo]. You can model and generate one +entirely in the browser — no install, no server — with the +[**HFSM Playground**][playground], or from the command line: + +```sh +npx -p webgme-hfsm hfsm-gen my_machine.json -o generated +``` + +The example wires that into its CMake, so its C++ is generated at build +time from [`Complex.json`](example/main/Complex.json) rather than +checked in — see [the example README](example/README.md). + +[repo]: https://github.com/finger563/webgme-hfsm +[playground]: https://finger563.github.io/webgme-hfsm/ + ## Example This example shows an example of running the below HFSM on an ESP32 in a diff --git a/components/state_machine/example/README.md b/components/state_machine/example/README.md index a4b9243e20..801a00557c 100644 --- a/components/state_machine/example/README.md +++ b/components/state_machine/example/README.md @@ -8,6 +8,42 @@ CLI to manually spawn events and trace the execution). For more information, see ![hfsm](https://user-images.githubusercontent.com/213467/230950083-d4d8a483-31a7-43ac-8822-b1e28d552984.png) +## The state machine is generated, not checked in + +The HFSM's C++ is generated from [`main/Complex.json`](main/Complex.json) +every time the example is configured. The model is the source; the C++ is +a build product, like an object file. + +This needs **node (>= 18)** on your PATH — nothing else, and nothing to +install by hand. `npx` fetches the generator on demand: + +``` +idf.py build # generates main/Complex.json -> build/.../hfsm/, then builds +``` + +Editing the model regenerates on the next build; you do not need to clean. + +To edit the machine, open it in the **[HFSM Playground][playground]** — a +browser-based editor and code generator, no install and no server. Load +`main/Complex.json` with **Open file…**, or jump straight to this +example's machine: + +[**Open this example's HFSM in the playground**][this-model] + +Save the edited model back over `main/Complex.json` and rebuild. + +If you are working on the generator itself, point the build at your +checkout instead of npx: + +``` +idf.py -DHFSM_GEN_COMMAND="node;/path/to/webgme-hfsm/bin/hfsm-gen.js" build +``` + +The same flag is the way to build without network access. + +[playground]: https://finger563.github.io/webgme-hfsm/ +[this-model]: https://finger563.github.io/webgme-hfsm/?example=Complex&view=diagram + ## How to use example ### Build and Flash diff --git a/components/state_machine/example/main/CMakeLists.txt b/components/state_machine/example/main/CMakeLists.txt index a941e22ba7..966018f6de 100644 --- a/components/state_machine/example/main/CMakeLists.txt +++ b/components/state_machine/example/main/CMakeLists.txt @@ -1,2 +1,70 @@ -idf_component_register(SRC_DIRS "." - INCLUDE_DIRS ".") +# The HFSM's C++ is GENERATED from Complex.json, not checked in. +# +# The model is the source; the C++ is a build product, the same as an +# object file. Keeping both in git meant they could disagree, and they +# did: the committed copy was generated in 2023 and had drifted from +# what the generator produces by roughly two thousand lines. +# +# Requires node (>= 18). Everything else comes from npx on demand. + +set(HFSM_MODEL "${CMAKE_CURRENT_LIST_DIR}/Complex.json") + +# Pinned so a build is reproducible. This is a git ref rather than an +# npm version because the packaging fix that makes `npx webgme-hfsm` +# work at all is not in a release yet; switch to "webgme-hfsm@^1.8.0" +# once it is published. +set(HFSM_GEN_SPEC "github:finger563/webgme-hfsm#638226b3e1e1c0002298d68f7237bcf7ca908a27" + CACHE STRING "npm/npx spec for the webgme-hfsm code generator") + +# Set this to skip npx entirely -- e.g. a local checkout while working +# on the generator: +# idf.py -DHFSM_GEN_COMMAND="node;/path/to/webgme-hfsm/bin/hfsm-gen.js" build +set(HFSM_GEN_COMMAND "" CACHE STRING + "Override the generator invocation (a ;-separated command)") + +# Re-run cmake when the model changes, so editing it regenerates the +# C++ on the next build rather than on the next clean. +set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${HFSM_MODEL}") + +set(HFSM_RAW_DIR "${CMAKE_CURRENT_BINARY_DIR}/hfsm-raw") +set(HFSM_DIR "${CMAKE_CURRENT_BINARY_DIR}/hfsm") + +if(HFSM_GEN_COMMAND) + set(_hfsm_cmd ${HFSM_GEN_COMMAND}) +else() + find_program(NPX_EXECUTABLE npx) + if(NOT NPX_EXECUTABLE) + message(FATAL_ERROR + "This example generates its state machine from ${HFSM_MODEL} and needs " + "node (>= 18) with npx on PATH.\n" + " Install node: https://nodejs.org\n" + " Or point at a local generator checkout:\n" + " idf.py -DHFSM_GEN_COMMAND=\"node;/path/to/webgme-hfsm/bin/hfsm-gen.js\" build") + endif() + set(_hfsm_cmd "${NPX_EXECUTABLE}" -y -p "${HFSM_GEN_SPEC}" hfsm-gen) +endif() + +message(STATUS "hfsm: generating C++ from ${HFSM_MODEL}") +execute_process( + COMMAND ${_hfsm_cmd} "${HFSM_MODEL}" -o "${HFSM_RAW_DIR}" + RESULT_VARIABLE _hfsm_result + OUTPUT_VARIABLE _hfsm_out + ERROR_VARIABLE _hfsm_err) +if(NOT _hfsm_result EQUAL 0) + message(FATAL_ERROR "hfsm generation failed:\n${_hfsm_out}${_hfsm_err}") +endif() + +# Take ONLY the model-specific files. The generator also emits its own +# state_base.hpp / deep_history_state.hpp / shallow_history_state.hpp / +# magic_enum.hpp, and this component already provides those -- espp's +# are the adapted ones the rest of the codebase expects. Putting the +# generator's copies on the include path would shadow them. +file(MAKE_DIRECTORY "${HFSM_DIR}") +foreach(_f Complex_generated_states.hpp Complex_generated_states.cpp + Complex_event_data.hpp) + configure_file("${HFSM_RAW_DIR}/${_f}" "${HFSM_DIR}/${_f}" COPYONLY) +endforeach() + +idf_component_register( + SRCS "hfsm_example.cpp" "${HFSM_DIR}/Complex_generated_states.cpp" + INCLUDE_DIRS "." "${HFSM_DIR}") diff --git a/components/state_machine/example/main/Complex.json b/components/state_machine/example/main/Complex.json new file mode 100644 index 0000000000..8bcd1f68fc --- /dev/null +++ b/components/state_machine/example/main/Complex.json @@ -0,0 +1,707 @@ +{ + "root": "/c", + "namespace": "espp::state_machine", + "objects": { + "/c": { + "name": "Complex", + "type": "State Machine", + "position": { + "x": 450, + "y": 238 + }, + "Declarations": "bool goToEnd = false;\nbool goToChoice = true;\nbool goToHistory = false;\nbool nextState = false;\nbool killedState = false;\nbool someGuard = true;\nbool someTest = true;\n\nint someNumber = 40;\nint someValue = 50;", + "Includes": "#include " + }, + "/c/3": { + "name": "EVENT1", + "type": "Event", + "position": { + "x": 100, + "y": 100 + } + }, + "/c/3/a": { + "name": "last_time", + "type": "Field", + "position": { + "x": 100, + "y": 100 + }, + "Default": "0" + }, + "/c/A": { + "name": "External Transition", + "type": "External Transition", + "position": { + "x": 100, + "y": 100 + }, + "pointers": { + "dst": "/c/G", + "src": "/c/T" + } + }, + "/c/C": { + "name": "External Transition", + "type": "External Transition", + "position": { + "x": 100, + "y": 100 + }, + "Event": "EVENT3", + "pointers": { + "dst": "/c/v/G", + "src": "/c/T" + } + }, + "/c/E": { + "name": "External Transition", + "type": "External Transition", + "position": { + "x": 100, + "y": 100 + }, + "Event": "EVENT2", + "pointers": { + "dst": "/c/T", + "src": "/c/v" + } + }, + "/c/F": { + "name": "External Transition", + "type": "External Transition", + "position": { + "x": 100, + "y": 100 + }, + "pointers": { + "dst": "/c/G", + "src": "/c/v" + } + }, + "/c/G": { + "name": "End State", + "type": "End State", + "position": { + "x": 457, + "y": 665 + } + }, + "/c/I": { + "name": "External Transition", + "type": "External Transition", + "position": { + "x": 343, + "y": 189 + }, + "Event": "EVENT4", + "Guard": "someTest", + "pointers": { + "dst": "/c/X", + "src": "/c/Y" + } + }, + "/c/L": { + "name": "External Transition", + "type": "External Transition", + "position": { + "x": 100, + "y": 100 + }, + "Event": "ENDEVENT", + "pointers": { + "dst": "/c/Y", + "src": "/c/T" + } + }, + "/c/Q": { + "name": "External Transition", + "type": "External Transition", + "position": { + "x": 100, + "y": 100 + }, + "Event": "EVENT4", + "pointers": { + "dst": "/c/T/o", + "src": "/c/v" + } + }, + "/c/T": { + "name": "State3", + "type": "State", + "position": { + "x": 596, + "y": 481 + } + }, + "/c/T/0": { + "name": "ChildState2", + "type": "State", + "position": { + "x": 617, + "y": 504 + }, + "Timer Period": 0.1 + }, + "/c/T/A": { + "name": "Shallow History Pseudostate", + "type": "Shallow History Pseudostate", + "position": { + "x": 495, + "y": 404 + } + }, + "/c/T/I": { + "name": "External Transition", + "type": "External Transition", + "position": { + "x": 100, + "y": 100 + }, + "pointers": { + "dst": "/c/T/W", + "src": "/c/T/v" + } + }, + "/c/T/L": { + "name": "External Transition", + "type": "External Transition", + "position": { + "x": 100, + "y": 100 + }, + "Event": "EVENT1", + "pointers": { + "dst": "/c/T/0", + "src": "/c/T/W" + } + }, + "/c/T/W": { + "name": "ChildState", + "type": "State", + "position": { + "x": 564, + "y": 433 + }, + "Timer Period": 0.1 + }, + "/c/T/h": { + "name": "External Transition", + "type": "External Transition", + "position": { + "x": 100, + "y": 100 + }, + "Event": "ENDEVENT", + "pointers": { + "dst": "/c/T/z", + "src": "/c/T/0" + } + }, + "/c/T/j": { + "name": "External Transition", + "type": "External Transition", + "position": { + "x": 100, + "y": 100 + }, + "Event": "EVENT2", + "pointers": { + "dst": "/c/T/w", + "src": "/c/T/0" + } + }, + "/c/T/o": { + "name": "Deep History Pseudostate", + "type": "Deep History Pseudostate", + "position": { + "x": 510, + "y": 422 + } + }, + "/c/T/p": { + "name": "External Transition", + "type": "External Transition", + "position": { + "x": 100, + "y": 100 + }, + "Event": "EVENT3", + "pointers": { + "dst": "/c/T/W", + "src": "/c/T/w" + } + }, + "/c/T/v": { + "name": "Initial", + "type": "Initial", + "position": { + "x": 527, + "y": 431 + } + }, + "/c/T/w": { + "name": "ChildState3", + "type": "State", + "position": { + "x": 677, + "y": 429 + }, + "Timer Period": 0.1 + }, + "/c/T/z": { + "name": "End State", + "type": "End State", + "position": { + "x": 618, + "y": 557 + } + }, + "/c/X": { + "name": "Choice Pseudostate", + "type": "Choice Pseudostate", + "position": { + "x": 495, + "y": 298 + } + }, + "/c/Y": { + "name": "State 1", + "type": "State", + "position": { + "x": 583, + "y": 221 + }, + "Entry": "printf(\"SerialTask :: initializing State 1\\n\");", + "Exit": "printf(\"Exiting State 1\\n\");", + "Tick": "printf(\"SerialTask::State 1::tick()\\n\");", + "Timer Period": 0.1 + }, + "/c/Y/X": { + "name": "Internal Transition", + "type": "Internal Transition", + "position": { + "x": 511, + "y": 229 + }, + "Event": "EVENT2", + "Guard": "someNumber > someValue" + }, + "/c/Y/t": { + "name": "Internal Transition", + "type": "Internal Transition", + "position": { + "x": 511, + "y": 214 + }, + "Action": "int testVal = 32;\nfor (int i=0; ilog("\033[36mTRANSITION::ACTION for /c/m\033[0m"); - - //::::/c/m::::Action:::: - - // State : entry for: /c/Y - _root->COMPLEX_OBJ__STATE_1_OBJ.entry(); - - // initialize our new active state - _root->COMPLEX_OBJ__STATE_1_OBJ.initialize(); -}; - -void Root::handle_all_events(void) { - GeneratedEventBase *e; - // get the next event and check if it's nullptr - while ((e = event_factory.get_next_event())) { - [[maybe_unused]] bool did_handle = handleEvent(e); - log("\033[0mHANDLED " + e->to_string() + (did_handle ? ": \033[32mtrue" : ": \033[31mfalse") + - "\033[0m"); - // free the memory that was allocated when it was spawned - consume_event(e); - } -} - -void Root::terminate(void) { - // will call exit() and exitChildren() on _activeState if it - // exists - exitChildren(); -}; - -void Root::restart(void) { - terminate(); - initialize(); -}; - -bool Root::has_stopped(void) { - bool reachedEnd = false; - // Get the currently active leaf state - const StateBase *activeLeaf = getActiveLeaf(); - if (activeLeaf != nullptr && activeLeaf != this && - activeLeaf == static_cast(&_root->COMPLEX_OBJ__END_STATE_OBJ)) { - reachedEnd = true; - } - return reachedEnd; -}; - -bool Root::handleEvent(GeneratedEventBase *event) { - bool handled = false; - - // Get the currently active leaf state - StateBase *activeLeaf = getActiveLeaf(); - - if (activeLeaf != nullptr && activeLeaf != this) { - // have the active leaf handle the event, this will bubble up until - // the event is handled or it reaches the root. - handled = activeLeaf->handleEvent(event); - } - - return handled; -} - -/* * * Definitions for State_1 : /c/Y * * */ - -// User Definitions for the HFSM -//::::/c/Y::::Definitions:::: - -void Root::State_1::initialize(void) { - // if we're a leaf state, make sure we're active - makeActive(); -} - -void Root::State_1::entry(void) { - _root->log("\033[36mENTRY::State_1::/c/Y\033[0m"); - // Entry action for this state - //::::/c/Y::::Entry:::: - [[maybe_unused]] int a = 2; - printf("SerialTask :: initializing State 1\n"); -} - -void Root::State_1::exit(void) { - _root->log("\033[36mEXIT::State_1::/c/Y\033[0m"); - // Call the Exit Action for this state - //::::/c/Y::::Exit:::: - printf("Exiting State 1\n"); -} - -void Root::State_1::tick(void) { - _root->log("\033[36mTICK::State_1::/c/Y\033[0m"); - // Call the Tick Action for this state - //::::/c/Y::::Tick:::: - printf("SerialTask::State 1::tick()\n"); - if (_activeState != nullptr && _activeState != this) - _activeState->tick(); -} - -double Root::State_1::getTimerPeriod(void) { return (double)(0.1); } - -bool Root::State_1::handleEvent(GeneratedEventBase *event) { - bool handled = false; - - // take care of all event types that this branch will not handle - - // for more consistent run-time performnace - switch (event->get_type()) { - case EventType::ENDEVENT: - case EventType::EVENT3: - handled = true; - break; - default: - handled = false; - break; - } - - if (handled) { - // we didn't actually handle the event, but return anyway - return false; - } - - // handle internal transitions first - switch (event->get_type()) { - case EventType::EVENT1: - if (false) { // makes generation easier :) - } - //::::/c/Y/t::::Guard:::: - else if (_root->someNumber < _root->someValue) { - _root->log("\033[37mGUARD [ _root->someNumber < _root->someValue ] for INTERNAL " - "TRANSITION:/c/Y/t evaluated to TRUE\033[0m"); - // run transition action - //::::/c/Y/t::::Action:::: - int testVal = 32; - for (int i = 0; i < testVal; i++) { - printf("Action iterating: %d\n", i); - } - // make sure nothing else handles this event - handled = true; - } - break; - case EventType::EVENT2: - if (false) { // makes generation easier :) - } - //::::/c/Y/X::::Guard:::: - else if (_root->someNumber > _root->someValue) { - _root->log("\033[37mGUARD [ _root->someNumber > _root->someValue ] for INTERNAL " - "TRANSITION:/c/Y/X evaluated to TRUE\033[0m"); - // run transition action - //::::/c/Y/X::::Action:::: - - // make sure nothing else handles this event - handled = true; - } - break; - default: - handled = false; - break; - } - if (!handled) { - // handle external transitions here - switch (event->get_type()) { - case EventType::EVENT4: - if (false) { - } // makes generation easier :) - //::::/c/I::::Guard:::: - else if (_root->someTest) { - _root->log("\033[37mGUARD [ _root->someTest ] for EXTERNAL TRANSITION:/c/I evaluated to " - "TRUE\033[0m"); - // Going into a choice pseudo-state, let it handle its - // guards and perform the state transition - if (false) { - } // makes geneeration easier :) - //::::/c/h::::Guard:::: - else if (_root->goToHistory) { - _root->log("\033[37mGUARD [ _root->goToHistory ] for EXTERNAL TRANSITION:/c/h evaluated " - "to TRUE\033[0m"); - // Transitioning states! - // Call all from prev state down exits - _root->COMPLEX_OBJ__STATE_1_OBJ.exitChildren(); - // State : exit for: /c/Y - _root->COMPLEX_OBJ__STATE_1_OBJ.exit(); - // External Transition : Action for: /c/I - _root->log("\033[36mTRANSITION::ACTION for /c/I\033[0m"); - - //::::/c/I::::Action:::: - - // External Transition : Action for: /c/h - _root->log("\033[36mTRANSITION::ACTION for /c/h\033[0m"); - - //::::/c/h::::Action:::: - - // State : entry for: /c/T - _root->COMPLEX_OBJ__STATE3_OBJ.entry(); - _root->log( - "\033[31mSTATE TRANSITION: State_1->State3::Shallow_History_Pseudostate\033[0m"); - - // going into shallow history pseudo-state - _root->COMPLEX_OBJ__STATE3_OBJ.setShallowHistory(); - // make sure nothing else handles this event - handled = true; - } - //::::/c/k::::Guard:::: - else if (_root->nextState) { - _root->log("\033[37mGUARD [ _root->nextState ] for EXTERNAL TRANSITION:/c/k evaluated to " - "TRUE\033[0m"); - // Transitioning states! - // Call all from prev state down exits - _root->COMPLEX_OBJ__STATE_1_OBJ.exitChildren(); - // State : exit for: /c/Y - _root->COMPLEX_OBJ__STATE_1_OBJ.exit(); - // External Transition : Action for: /c/I - _root->log("\033[36mTRANSITION::ACTION for /c/I\033[0m"); - - //::::/c/I::::Action:::: - - // External Transition : Action for: /c/k - _root->log("\033[36mTRANSITION::ACTION for /c/k\033[0m"); - - //::::/c/k::::Action:::: - - // State : entry for: /c/v - _root->COMPLEX_OBJ__STATE_2_OBJ.entry(); - _root->log("\033[31mSTATE TRANSITION: State_1->State_2\033[0m"); - - // going into regular state - _root->COMPLEX_OBJ__STATE_2_OBJ.initialize(); - // make sure nothing else handles this event - handled = true; - } else if (true) { - _root->log("\033[37mNO GUARD on EXTERNAL TRANSITION:/c/r\033[0m"); - // Transitioning states! - // Call all from prev state down exits - _root->COMPLEX_OBJ__STATE_1_OBJ.exitChildren(); - // State : exit for: /c/Y - _root->COMPLEX_OBJ__STATE_1_OBJ.exit(); - // External Transition : Action for: /c/I - _root->log("\033[36mTRANSITION::ACTION for /c/I\033[0m"); - - //::::/c/I::::Action:::: - - // External Transition : Action for: /c/r - _root->log("\033[36mTRANSITION::ACTION for /c/r\033[0m"); - - //::::/c/r::::Action:::: - - // State : entry for: /c/T - _root->COMPLEX_OBJ__STATE3_OBJ.entry(); - _root->log("\033[31mSTATE TRANSITION: State_1->State3\033[0m"); - - // going into regular state - _root->COMPLEX_OBJ__STATE3_OBJ.initialize(); - // make sure nothing else handles this event - handled = true; - } - } - break; - default: - handled = false; - break; - } - } - // can't buble up, we are a root state. - return handled; -} -/* * * Definitions for State_2 : /c/v * * */ - -// User Definitions for the HFSM -//::::/c/v::::Definitions:::: - -void Root::State_2::initialize(void) { - // External Transition : Action for: /c/v/u - _root->log("\033[36mTRANSITION::ACTION for /c/v/u\033[0m"); - - //::::/c/v/u::::Action:::: - - // State : entry for: /c/v/K - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE_OBJ.entry(); - - // initialize our new active state - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE_OBJ.initialize(); -} - -void Root::State_2::entry(void) { - _root->log("\033[36mENTRY::State_2::/c/v\033[0m"); - // Entry action for this state - //::::/c/v::::Entry:::: -} - -void Root::State_2::exit(void) { - _root->log("\033[36mEXIT::State_2::/c/v\033[0m"); - // Call the Exit Action for this state - //::::/c/v::::Exit:::: -} - -void Root::State_2::tick(void) { - _root->log("\033[36mTICK::State_2::/c/v\033[0m"); - // Call the Tick Action for this state - //::::/c/v::::Tick:::: - - if (_activeState != nullptr && _activeState != this) - _activeState->tick(); -} - -double Root::State_2::getTimerPeriod(void) { return (double)(0); } - -bool Root::State_2::handleEvent(GeneratedEventBase *event) { - bool handled = false; - - // take care of all event types that this branch will not handle - - // for more consistent run-time performnace - switch (event->get_type()) { - case EventType::ENDEVENT: - case EventType::EVENT1: - handled = true; - break; - default: - handled = false; - break; - } - - if (handled) { - // we didn't actually handle the event, but return anyway - return false; - } - - // handle internal transitions first - switch (event->get_type()) { - default: - handled = false; - break; - } - if (!handled) { - // handle external transitions here - switch (event->get_type()) { - case EventType::EVENT4: - if (false) { - } // makes generation easier :) - else if (true) { - _root->log("\033[37mNO GUARD on EXTERNAL TRANSITION:/c/Q\033[0m"); - // Transitioning states! - // Call all from prev state down exits - _root->COMPLEX_OBJ__STATE_2_OBJ.exitChildren(); - // State : exit for: /c/v - _root->COMPLEX_OBJ__STATE_2_OBJ.exit(); - // External Transition : Action for: /c/Q - _root->log("\033[36mTRANSITION::ACTION for /c/Q\033[0m"); - - //::::/c/Q::::Action:::: - - // State : entry for: /c/T - _root->COMPLEX_OBJ__STATE3_OBJ.entry(); - _root->log("\033[31mSTATE TRANSITION: State_2->State3::Deep_History_Pseudostate\033[0m"); - - // going into deep history pseudo-state - _root->COMPLEX_OBJ__STATE3_OBJ.setDeepHistory(); - // make sure nothing else handles this event - handled = true; - } - break; - case EventType::EVENT2: - if (false) { - } // makes generation easier :) - else if (true) { - _root->log("\033[37mNO GUARD on EXTERNAL TRANSITION:/c/E\033[0m"); - // Transitioning states! - // Call all from prev state down exits - _root->COMPLEX_OBJ__STATE_2_OBJ.exitChildren(); - // State : exit for: /c/v - _root->COMPLEX_OBJ__STATE_2_OBJ.exit(); - // External Transition : Action for: /c/E - _root->log("\033[36mTRANSITION::ACTION for /c/E\033[0m"); - - //::::/c/E::::Action:::: - - // State : entry for: /c/T - _root->COMPLEX_OBJ__STATE3_OBJ.entry(); - _root->log("\033[31mSTATE TRANSITION: State_2->State3\033[0m"); - - // going into regular state - _root->COMPLEX_OBJ__STATE3_OBJ.initialize(); - // make sure nothing else handles this event - handled = true; - } - break; - case EventType::EVENT3: - if (false) { - } // makes generation easier :) - else if (true) { - _root->log("\033[37mNO GUARD on EXTERNAL TRANSITION:/c/t\033[0m"); - // Transitioning states! - // Call all from prev state down exits - _root->COMPLEX_OBJ__STATE_2_OBJ.exitChildren(); - // State : exit for: /c/v - _root->COMPLEX_OBJ__STATE_2_OBJ.exit(); - // External Transition : Action for: /c/t - _root->log("\033[36mTRANSITION::ACTION for /c/t\033[0m"); - - //::::/c/t::::Action:::: - - // State : entry for: /c/T - _root->COMPLEX_OBJ__STATE3_OBJ.entry(); - _root->log("\033[31mSTATE TRANSITION: State_2->State3::Shallow_History_Pseudostate\033[0m"); - - // going into shallow history pseudo-state - _root->COMPLEX_OBJ__STATE3_OBJ.setShallowHistory(); - // make sure nothing else handles this event - handled = true; - } - break; - default: - handled = false; - break; - } - } - // can't buble up, we are a root state. - return handled; -} -/* * * Definitions for State_2::ChildState : /c/v/K * * */ - -// User Definitions for the HFSM -//::::/c/v/K::::Definitions:::: - -void Root::State_2::ChildState::initialize(void) { - // if we're a leaf state, make sure we're active - makeActive(); -} - -void Root::State_2::ChildState::entry(void) { - _root->log("\033[36mENTRY::State_2::ChildState::/c/v/K\033[0m"); - // Entry action for this state - //::::/c/v/K::::Entry:::: -} - -void Root::State_2::ChildState::exit(void) { - _root->log("\033[36mEXIT::State_2::ChildState::/c/v/K\033[0m"); - // Call the Exit Action for this state - //::::/c/v/K::::Exit:::: -} - -void Root::State_2::ChildState::tick(void) { - _root->log("\033[36mTICK::State_2::ChildState::/c/v/K\033[0m"); - // Call the Tick Action for this state - //::::/c/v/K::::Tick:::: - - if (_activeState != nullptr && _activeState != this) - _activeState->tick(); -} - -double Root::State_2::ChildState::getTimerPeriod(void) { return (double)(0.1); } - -bool Root::State_2::ChildState::handleEvent(GeneratedEventBase *event) { - bool handled = false; - - // take care of all event types that this branch will not handle - - // for more consistent run-time performnace - switch (event->get_type()) { - case EventType::ENDEVENT: - handled = true; - break; - default: - handled = false; - break; - } - - if (handled) { - // we didn't actually handle the event, but return anyway - return false; - } - - // handle internal transitions first - switch (event->get_type()) { - default: - handled = false; - break; - } - if (!handled) { - // handle external transitions here - switch (event->get_type()) { - case EventType::EVENT1: - if (false) { - } // makes generation easier :) - else if (true) { - _root->log("\033[37mNO GUARD on EXTERNAL TRANSITION:/c/v/S\033[0m"); - // Transitioning states! - // Call all from prev state down exits - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE_OBJ.exitChildren(); - // State : exit for: /c/v/K - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE_OBJ.exit(); - // External Transition : Action for: /c/v/S - _root->log("\033[36mTRANSITION::ACTION for /c/v/S\033[0m"); - - //::::/c/v/S::::Action:::: - - // State : entry for: /c/v/e - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE2_OBJ.entry(); - _root->log("\033[31mSTATE TRANSITION: State_2::ChildState->State_2::ChildState2\033[0m"); - - // going into regular state - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE2_OBJ.initialize(); - // make sure nothing else handles this event - handled = true; - } - break; - default: - handled = false; - break; - } - } - if (!handled) { - // now check parent states - handled = _parentState->handleEvent(event); - } - return handled; -} -/* * * Definitions for State_2::ChildState2 : /c/v/e * * */ - -// User Definitions for the HFSM -//::::/c/v/e::::Definitions:::: - -void Root::State_2::ChildState2::initialize(void) { - // if we're a leaf state, make sure we're active - makeActive(); -} - -void Root::State_2::ChildState2::entry(void) { - _root->log("\033[36mENTRY::State_2::ChildState2::/c/v/e\033[0m"); - // Entry action for this state - //::::/c/v/e::::Entry:::: -} - -void Root::State_2::ChildState2::exit(void) { - _root->log("\033[36mEXIT::State_2::ChildState2::/c/v/e\033[0m"); - // Call the Exit Action for this state - //::::/c/v/e::::Exit:::: -} - -void Root::State_2::ChildState2::tick(void) { - _root->log("\033[36mTICK::State_2::ChildState2::/c/v/e\033[0m"); - // Call the Tick Action for this state - //::::/c/v/e::::Tick:::: - - if (_activeState != nullptr && _activeState != this) - _activeState->tick(); -} - -double Root::State_2::ChildState2::getTimerPeriod(void) { return (double)(0.1); } - -bool Root::State_2::ChildState2::handleEvent(GeneratedEventBase *event) { - bool handled = false; - - // take care of all event types that this branch will not handle - - // for more consistent run-time performnace - switch (event->get_type()) { - case EventType::ENDEVENT: - case EventType::EVENT1: - handled = true; - break; - default: - handled = false; - break; - } - - if (handled) { - // we didn't actually handle the event, but return anyway - return false; - } - - // handle internal transitions first - switch (event->get_type()) { - default: - handled = false; - break; - } - if (!handled) { - // handle external transitions here - switch (event->get_type()) { - case EventType::EVENT2: - if (false) { - } // makes generation easier :) - else if (true) { - _root->log("\033[37mNO GUARD on EXTERNAL TRANSITION:/c/v/W\033[0m"); - // Transitioning states! - // Call all from prev state down exits - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE2_OBJ.exitChildren(); - // State : exit for: /c/v/e - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE2_OBJ.exit(); - // External Transition : Action for: /c/v/W - _root->log("\033[36mTRANSITION::ACTION for /c/v/W\033[0m"); - - //::::/c/v/W::::Action:::: - - // State : entry for: /c/v/z - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ.entry(); - _root->log("\033[31mSTATE TRANSITION: State_2::ChildState2->State_2::ChildState3\033[0m"); - - // going into regular state - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ.initialize(); - // make sure nothing else handles this event - handled = true; - } - break; - default: - handled = false; - break; - } - } - if (!handled) { - // now check parent states - handled = _parentState->handleEvent(event); - } - return handled; -} -/* * * Definitions for State_2::ChildState3 : /c/v/z * * */ - -// User Definitions for the HFSM -//::::/c/v/z::::Definitions:::: - -void Root::State_2::ChildState3::initialize(void) { - // External Transition : Action for: /c/v/z/8 - _root->log("\033[36mTRANSITION::ACTION for /c/v/z/8\033[0m"); - - //::::/c/v/z/8::::Action:::: - - // State : entry for: /c/v/z/6 - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ__GRAND_OBJ.entry(); - - // initialize our new active state - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ__GRAND_OBJ.initialize(); -} - -void Root::State_2::ChildState3::entry(void) { - _root->log("\033[36mENTRY::State_2::ChildState3::/c/v/z\033[0m"); - // Entry action for this state - //::::/c/v/z::::Entry:::: -} - -void Root::State_2::ChildState3::exit(void) { - _root->log("\033[36mEXIT::State_2::ChildState3::/c/v/z\033[0m"); - // Call the Exit Action for this state - //::::/c/v/z::::Exit:::: -} - -void Root::State_2::ChildState3::tick(void) { - _root->log("\033[36mTICK::State_2::ChildState3::/c/v/z\033[0m"); - // Call the Tick Action for this state - //::::/c/v/z::::Tick:::: - - if (_activeState != nullptr && _activeState != this) - _activeState->tick(); -} - -double Root::State_2::ChildState3::getTimerPeriod(void) { return (double)(0); } - -bool Root::State_2::ChildState3::handleEvent(GeneratedEventBase *event) { - bool handled = false; - - // take care of all event types that this branch will not handle - - // for more consistent run-time performnace - switch (event->get_type()) { - case EventType::ENDEVENT: - case EventType::EVENT1: - handled = true; - break; - default: - handled = false; - break; - } - - if (handled) { - // we didn't actually handle the event, but return anyway - return false; - } - - // handle internal transitions first - switch (event->get_type()) { - default: - handled = false; - break; - } - if (!handled) { - // handle external transitions here - switch (event->get_type()) { - case EventType::EVENT3: - if (false) { - } // makes generation easier :) - //::::/c/v/P::::Guard:::: - else if (_root->someGuard) { - _root->log("\033[37mGUARD [ _root->someGuard ] for EXTERNAL TRANSITION:/c/v/P evaluated to " - "TRUE\033[0m"); - // Transitioning states! - // Call all from prev state down exits - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ.exitChildren(); - // State : exit for: /c/v/z - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ.exit(); - // External Transition : Action for: /c/v/P - _root->log("\033[36mTRANSITION::ACTION for /c/v/P\033[0m"); - - //::::/c/v/P::::Action:::: - - // State : entry for: /c/v/K - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE_OBJ.entry(); - _root->log("\033[31mSTATE TRANSITION: State_2::ChildState3->State_2::ChildState\033[0m"); - - // going into regular state - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE_OBJ.initialize(); - // make sure nothing else handles this event - handled = true; - } - break; - default: - handled = false; - break; - } - } - if (!handled) { - // now check parent states - handled = _parentState->handleEvent(event); - } - return handled; -} -/* * * Definitions for State_2::ChildState3::Grand : /c/v/z/6 * * */ - -// User Definitions for the HFSM -//::::/c/v/z/6::::Definitions:::: - -void Root::State_2::ChildState3::Grand::initialize(void) { - // if we're a leaf state, make sure we're active - makeActive(); -} - -void Root::State_2::ChildState3::Grand::entry(void) { - _root->log("\033[36mENTRY::State_2::ChildState3::Grand::/c/v/z/6\033[0m"); - // Entry action for this state - //::::/c/v/z/6::::Entry:::: -} - -void Root::State_2::ChildState3::Grand::exit(void) { - _root->log("\033[36mEXIT::State_2::ChildState3::Grand::/c/v/z/6\033[0m"); - // Call the Exit Action for this state - //::::/c/v/z/6::::Exit:::: -} - -void Root::State_2::ChildState3::Grand::tick(void) { - _root->log("\033[36mTICK::State_2::ChildState3::Grand::/c/v/z/6\033[0m"); - // Call the Tick Action for this state - //::::/c/v/z/6::::Tick:::: - - if (_activeState != nullptr && _activeState != this) - _activeState->tick(); -} - -double Root::State_2::ChildState3::Grand::getTimerPeriod(void) { return (double)(0.1); } - -bool Root::State_2::ChildState3::Grand::handleEvent(GeneratedEventBase *event) { - bool handled = false; - - // take care of all event types that this branch will not handle - - // for more consistent run-time performnace - switch (event->get_type()) { - case EventType::ENDEVENT: - handled = true; - break; - default: - handled = false; - break; - } - - if (handled) { - // we didn't actually handle the event, but return anyway - return false; - } - - // handle internal transitions first - switch (event->get_type()) { - default: - handled = false; - break; - } - if (!handled) { - // handle external transitions here - switch (event->get_type()) { - case EventType::EVENT1: - if (false) { - } // makes generation easier :) - else if (true) { - _root->log("\033[37mNO GUARD on EXTERNAL TRANSITION:/c/v/z/z\033[0m"); - // Transitioning states! - // Call all from prev state down exits - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ__GRAND_OBJ.exitChildren(); - // State : exit for: /c/v/z/6 - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ__GRAND_OBJ.exit(); - // External Transition : Action for: /c/v/z/z - _root->log("\033[36mTRANSITION::ACTION for /c/v/z/z\033[0m"); - - //::::/c/v/z/z::::Action:::: - - // State : entry for: /c/v/z/c - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ__GRAND2_OBJ.entry(); - _root->log("\033[31mSTATE TRANSITION: " - "State_2::ChildState3::Grand->State_2::ChildState3::Grand2\033[0m"); - - // going into regular state - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ__GRAND2_OBJ.initialize(); - // make sure nothing else handles this event - handled = true; - } - break; - default: - handled = false; - break; - } - } - if (!handled) { - // now check parent states - handled = _parentState->handleEvent(event); - } - return handled; -} -/* * * Definitions for State_2::ChildState3::Grand2 : /c/v/z/c * * */ - -// User Definitions for the HFSM -//::::/c/v/z/c::::Definitions:::: - -void Root::State_2::ChildState3::Grand2::initialize(void) { - // if we're a leaf state, make sure we're active - makeActive(); -} - -void Root::State_2::ChildState3::Grand2::entry(void) { - _root->log("\033[36mENTRY::State_2::ChildState3::Grand2::/c/v/z/c\033[0m"); - // Entry action for this state - //::::/c/v/z/c::::Entry:::: -} - -void Root::State_2::ChildState3::Grand2::exit(void) { - _root->log("\033[36mEXIT::State_2::ChildState3::Grand2::/c/v/z/c\033[0m"); - // Call the Exit Action for this state - //::::/c/v/z/c::::Exit:::: -} - -void Root::State_2::ChildState3::Grand2::tick(void) { - _root->log("\033[36mTICK::State_2::ChildState3::Grand2::/c/v/z/c\033[0m"); - // Call the Tick Action for this state - //::::/c/v/z/c::::Tick:::: - - if (_activeState != nullptr && _activeState != this) - _activeState->tick(); -} - -double Root::State_2::ChildState3::Grand2::getTimerPeriod(void) { return (double)(0.1); } - -bool Root::State_2::ChildState3::Grand2::handleEvent(GeneratedEventBase *event) { - bool handled = false; - - // take care of all event types that this branch will not handle - - // for more consistent run-time performnace - switch (event->get_type()) { - default: - handled = false; - break; - } - - if (handled) { - // we didn't actually handle the event, but return anyway - return false; - } - - // handle internal transitions first - switch (event->get_type()) { - default: - handled = false; - break; - } - if (!handled) { - // handle external transitions here - switch (event->get_type()) { - case EventType::ENDEVENT: - if (false) { - } // makes generation easier :) - else if (true) { - _root->log("\033[37mNO GUARD on EXTERNAL TRANSITION:/c/v/z/9\033[0m"); - // Going into an end pseudo-state that is not the root end state, - // follow its parent end transition - if (false) { - } else if (true) { - _root->log("\033[37mNO GUARD on EXTERNAL TRANSITION:/c/v/F\033[0m"); - // Going into a choice pseudo-state, let it handle its - // guards and perform the state transition - if (false) { - } // makes geneeration easier :) - //::::/c/v/g::::Guard:::: - else if (_root->killedState) { - _root->log("\033[37mGUARD [ _root->killedState ] for EXTERNAL TRANSITION:/c/v/g " - "evaluated to TRUE\033[0m"); - // Going into an end pseudo-state that is not the root end state, - // follow its parent end transition - if (false) { - } else if (true) { - _root->log("\033[37mNO GUARD on EXTERNAL TRANSITION:/c/F\033[0m"); - // Transitioning states! - // Call all from prev state down exits - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ__GRAND2_OBJ.exitChildren(); - // State : exit for: /c/v/z/c - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ__GRAND2_OBJ.exit(); - // State : exit for: /c/v/z - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ.exit(); - // State : exit for: /c/v - _root->COMPLEX_OBJ__STATE_2_OBJ.exit(); - // External Transition : Action for: /c/v/z/9 - _root->log("\033[36mTRANSITION::ACTION for /c/v/z/9\033[0m"); - - //::::/c/v/z/9::::Action:::: - - // External Transition : Action for: /c/v/F - _root->log("\033[36mTRANSITION::ACTION for /c/v/F\033[0m"); - - //::::/c/v/F::::Action:::: - - // External Transition : Action for: /c/v/g - _root->log("\033[36mTRANSITION::ACTION for /c/v/g\033[0m"); - - //::::/c/v/g::::Action:::: - - // External Transition : Action for: /c/F - _root->log("\033[36mTRANSITION::ACTION for /c/F\033[0m"); - - //::::/c/F::::Action:::: - - _root->log( - "\033[31mSTATE TRANSITION: State_2::ChildState3::Grand2->End_State\033[0m"); - - // going into end pseudo-state THIS SHOULD BE TOP LEVEL END STATE - _root->COMPLEX_OBJ__END_STATE_OBJ.makeActive(); - // make sure nothing else handles this event - handled = true; - } - } else if (true) { - _root->log("\033[37mNO GUARD on EXTERNAL TRANSITION:/c/v/2\033[0m"); - // Transitioning states! - // Call all from prev state down exits - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ__GRAND2_OBJ.exitChildren(); - // State : exit for: /c/v/z/c - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ__GRAND2_OBJ.exit(); - // State : exit for: /c/v/z - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ.exit(); - // External Transition : Action for: /c/v/z/9 - _root->log("\033[36mTRANSITION::ACTION for /c/v/z/9\033[0m"); - - //::::/c/v/z/9::::Action:::: - - // External Transition : Action for: /c/v/F - _root->log("\033[36mTRANSITION::ACTION for /c/v/F\033[0m"); - - //::::/c/v/F::::Action:::: - - // External Transition : Action for: /c/v/2 - _root->log("\033[36mTRANSITION::ACTION for /c/v/2\033[0m"); - - //::::/c/v/2::::Action:::: - - // State : entry for: /c/v/z - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ.entry(); - _root->log("\033[31mSTATE TRANSITION: " - "State_2::ChildState3::Grand2->State_2::ChildState3\033[0m"); - - // going into regular state - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ.initialize(); - // make sure nothing else handles this event - handled = true; - } - } - } - break; - case EventType::EVENT2: - if (false) { - } // makes generation easier :) - else if (true) { - _root->log("\033[37mNO GUARD on EXTERNAL TRANSITION:/c/v/z/R\033[0m"); - // Going into a choice pseudo-state, let it handle its - // guards and perform the state transition - if (false) { - } // makes geneeration easier :) - //::::/c/v/z/j::::Guard:::: - else if (_root->goToEnd) { - _root->log("\033[37mGUARD [ _root->goToEnd ] for EXTERNAL TRANSITION:/c/v/z/j evaluated " - "to TRUE\033[0m"); - // Going into an end pseudo-state that is not the root end state, - // follow its parent end transition - if (false) { - } else if (true) { - _root->log("\033[37mNO GUARD on EXTERNAL TRANSITION:/c/v/F\033[0m"); - // Going into a choice pseudo-state, let it handle its - // guards and perform the state transition - if (false) { - } // makes geneeration easier :) - //::::/c/v/g::::Guard:::: - else if (_root->killedState) { - _root->log("\033[37mGUARD [ _root->killedState ] for EXTERNAL TRANSITION:/c/v/g " - "evaluated to TRUE\033[0m"); - // Going into an end pseudo-state that is not the root end state, - // follow its parent end transition - if (false) { - } else if (true) { - _root->log("\033[37mNO GUARD on EXTERNAL TRANSITION:/c/F\033[0m"); - // Transitioning states! - // Call all from prev state down exits - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ__GRAND2_OBJ.exitChildren(); - // State : exit for: /c/v/z/c - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ__GRAND2_OBJ.exit(); - // State : exit for: /c/v/z - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ.exit(); - // State : exit for: /c/v - _root->COMPLEX_OBJ__STATE_2_OBJ.exit(); - // External Transition : Action for: /c/v/z/R - _root->log("\033[36mTRANSITION::ACTION for /c/v/z/R\033[0m"); - - //::::/c/v/z/R::::Action:::: - - // External Transition : Action for: /c/v/z/j - _root->log("\033[36mTRANSITION::ACTION for /c/v/z/j\033[0m"); - - //::::/c/v/z/j::::Action:::: - - // External Transition : Action for: /c/v/F - _root->log("\033[36mTRANSITION::ACTION for /c/v/F\033[0m"); - - //::::/c/v/F::::Action:::: - - // External Transition : Action for: /c/v/g - _root->log("\033[36mTRANSITION::ACTION for /c/v/g\033[0m"); - - //::::/c/v/g::::Action:::: - - // External Transition : Action for: /c/F - _root->log("\033[36mTRANSITION::ACTION for /c/F\033[0m"); - - //::::/c/F::::Action:::: - - _root->log( - "\033[31mSTATE TRANSITION: State_2::ChildState3::Grand2->End_State\033[0m"); - - // going into end pseudo-state THIS SHOULD BE TOP LEVEL END STATE - _root->COMPLEX_OBJ__END_STATE_OBJ.makeActive(); - // make sure nothing else handles this event - handled = true; - } - } else if (true) { - _root->log("\033[37mNO GUARD on EXTERNAL TRANSITION:/c/v/2\033[0m"); - // Transitioning states! - // Call all from prev state down exits - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ__GRAND2_OBJ.exitChildren(); - // State : exit for: /c/v/z/c - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ__GRAND2_OBJ.exit(); - // State : exit for: /c/v/z - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ.exit(); - // External Transition : Action for: /c/v/z/R - _root->log("\033[36mTRANSITION::ACTION for /c/v/z/R\033[0m"); - - //::::/c/v/z/R::::Action:::: - - // External Transition : Action for: /c/v/z/j - _root->log("\033[36mTRANSITION::ACTION for /c/v/z/j\033[0m"); - - //::::/c/v/z/j::::Action:::: - - // External Transition : Action for: /c/v/F - _root->log("\033[36mTRANSITION::ACTION for /c/v/F\033[0m"); - - //::::/c/v/F::::Action:::: - - // External Transition : Action for: /c/v/2 - _root->log("\033[36mTRANSITION::ACTION for /c/v/2\033[0m"); - - //::::/c/v/2::::Action:::: - - // State : entry for: /c/v/z - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ.entry(); - _root->log("\033[31mSTATE TRANSITION: " - "State_2::ChildState3::Grand2->State_2::ChildState3\033[0m"); - - // going into regular state - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ.initialize(); - // make sure nothing else handles this event - handled = true; - } - } - } - //::::/c/v/z/g::::Guard:::: - else if (_root->goToChoice) { - _root->log("\033[37mGUARD [ _root->goToChoice ] for EXTERNAL TRANSITION:/c/v/z/g " - "evaluated to TRUE\033[0m"); - // Going into a choice pseudo-state, let it handle its - // guards and perform the state transition - if (false) { - } // makes geneeration easier :) - //::::/c/h::::Guard:::: - else if (_root->goToHistory) { - _root->log("\033[37mGUARD [ _root->goToHistory ] for EXTERNAL TRANSITION:/c/h " - "evaluated to TRUE\033[0m"); - // Transitioning states! - // Call all from prev state down exits - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ__GRAND2_OBJ.exitChildren(); - // State : exit for: /c/v/z/c - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ__GRAND2_OBJ.exit(); - // State : exit for: /c/v/z - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ.exit(); - // State : exit for: /c/v - _root->COMPLEX_OBJ__STATE_2_OBJ.exit(); - // External Transition : Action for: /c/v/z/R - _root->log("\033[36mTRANSITION::ACTION for /c/v/z/R\033[0m"); - - //::::/c/v/z/R::::Action:::: - - // External Transition : Action for: /c/v/z/g - _root->log("\033[36mTRANSITION::ACTION for /c/v/z/g\033[0m"); - - //::::/c/v/z/g::::Action:::: - - // External Transition : Action for: /c/h - _root->log("\033[36mTRANSITION::ACTION for /c/h\033[0m"); - - //::::/c/h::::Action:::: - - // State : entry for: /c/T - _root->COMPLEX_OBJ__STATE3_OBJ.entry(); - _root->log("\033[31mSTATE TRANSITION: " - "State_2::ChildState3::Grand2->State3::Shallow_History_Pseudostate\033[0m"); - - // going into shallow history pseudo-state - _root->COMPLEX_OBJ__STATE3_OBJ.setShallowHistory(); - // make sure nothing else handles this event - handled = true; - } - //::::/c/k::::Guard:::: - else if (_root->nextState) { - _root->log("\033[37mGUARD [ _root->nextState ] for EXTERNAL TRANSITION:/c/k evaluated " - "to TRUE\033[0m"); - // Transitioning states! - // Call all from prev state down exits - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ__GRAND2_OBJ.exitChildren(); - // State : exit for: /c/v/z/c - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ__GRAND2_OBJ.exit(); - // State : exit for: /c/v/z - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ.exit(); - // State : exit for: /c/v - _root->COMPLEX_OBJ__STATE_2_OBJ.exit(); - // External Transition : Action for: /c/v/z/R - _root->log("\033[36mTRANSITION::ACTION for /c/v/z/R\033[0m"); - - //::::/c/v/z/R::::Action:::: - - // External Transition : Action for: /c/v/z/g - _root->log("\033[36mTRANSITION::ACTION for /c/v/z/g\033[0m"); - - //::::/c/v/z/g::::Action:::: - - // External Transition : Action for: /c/k - _root->log("\033[36mTRANSITION::ACTION for /c/k\033[0m"); - - //::::/c/k::::Action:::: - - // State : entry for: /c/v - _root->COMPLEX_OBJ__STATE_2_OBJ.entry(); - _root->log("\033[31mSTATE TRANSITION: State_2::ChildState3::Grand2->State_2\033[0m"); - - // going into regular state - _root->COMPLEX_OBJ__STATE_2_OBJ.initialize(); - // make sure nothing else handles this event - handled = true; - } else if (true) { - _root->log("\033[37mNO GUARD on EXTERNAL TRANSITION:/c/r\033[0m"); - // Transitioning states! - // Call all from prev state down exits - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ__GRAND2_OBJ.exitChildren(); - // State : exit for: /c/v/z/c - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ__GRAND2_OBJ.exit(); - // State : exit for: /c/v/z - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ.exit(); - // State : exit for: /c/v - _root->COMPLEX_OBJ__STATE_2_OBJ.exit(); - // External Transition : Action for: /c/v/z/R - _root->log("\033[36mTRANSITION::ACTION for /c/v/z/R\033[0m"); - - //::::/c/v/z/R::::Action:::: - - // External Transition : Action for: /c/v/z/g - _root->log("\033[36mTRANSITION::ACTION for /c/v/z/g\033[0m"); - - //::::/c/v/z/g::::Action:::: - - // External Transition : Action for: /c/r - _root->log("\033[36mTRANSITION::ACTION for /c/r\033[0m"); - - //::::/c/r::::Action:::: - - // State : entry for: /c/T - _root->COMPLEX_OBJ__STATE3_OBJ.entry(); - _root->log("\033[31mSTATE TRANSITION: State_2::ChildState3::Grand2->State3\033[0m"); - - // going into regular state - _root->COMPLEX_OBJ__STATE3_OBJ.initialize(); - // make sure nothing else handles this event - handled = true; - } - } else if (true) { - _root->log("\033[37mNO GUARD on EXTERNAL TRANSITION:/c/v/z/O\033[0m"); - // Transitioning states! - // Call all from prev state down exits - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ__GRAND2_OBJ.exitChildren(); - // State : exit for: /c/v/z/c - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ__GRAND2_OBJ.exit(); - // External Transition : Action for: /c/v/z/R - _root->log("\033[36mTRANSITION::ACTION for /c/v/z/R\033[0m"); - - //::::/c/v/z/R::::Action:::: - - // External Transition : Action for: /c/v/z/O - _root->log("\033[36mTRANSITION::ACTION for /c/v/z/O\033[0m"); - - //::::/c/v/z/O::::Action:::: - - // State : entry for: /c/v/z/c - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ__GRAND2_OBJ.entry(); - _root->log("\033[31mSTATE TRANSITION: " - "State_2::ChildState3::Grand2->State_2::ChildState3::Grand2\033[0m"); - - // going into regular state - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ__GRAND2_OBJ.initialize(); - // make sure nothing else handles this event - handled = true; - } - } - break; - case EventType::EVENT1: - if (false) { - } // makes generation easier :) - else if (true) { - _root->log("\033[37mNO GUARD on EXTERNAL TRANSITION:/c/v/z/a\033[0m"); - // Transitioning states! - // Call all from prev state down exits - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ__GRAND2_OBJ.exitChildren(); - // State : exit for: /c/v/z/c - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ__GRAND2_OBJ.exit(); - // External Transition : Action for: /c/v/z/a - _root->log("\033[36mTRANSITION::ACTION for /c/v/z/a\033[0m"); - - //::::/c/v/z/a::::Action:::: - - // State : entry for: /c/v/z/6 - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ__GRAND_OBJ.entry(); - _root->log("\033[31mSTATE TRANSITION: " - "State_2::ChildState3::Grand2->State_2::ChildState3::Grand\033[0m"); - - // going into regular state - _root->COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ__GRAND_OBJ.initialize(); - // make sure nothing else handles this event - handled = true; - } - break; - default: - handled = false; - break; - } - } - if (!handled) { - // now check parent states - handled = _parentState->handleEvent(event); - } - return handled; -} -/* * * Definitions for State3 : /c/T * * */ - -// User Definitions for the HFSM -//::::/c/T::::Definitions:::: - -void Root::State3::initialize(void) { - // External Transition : Action for: /c/T/I - _root->log("\033[36mTRANSITION::ACTION for /c/T/I\033[0m"); - - //::::/c/T/I::::Action:::: - - // State : entry for: /c/T/W - _root->COMPLEX_OBJ__STATE3_OBJ__CHILDSTATE_OBJ.entry(); - - // initialize our new active state - _root->COMPLEX_OBJ__STATE3_OBJ__CHILDSTATE_OBJ.initialize(); -} - -void Root::State3::entry(void) { - _root->log("\033[36mENTRY::State3::/c/T\033[0m"); - // Entry action for this state - //::::/c/T::::Entry:::: -} - -void Root::State3::exit(void) { - _root->log("\033[36mEXIT::State3::/c/T\033[0m"); - // Call the Exit Action for this state - //::::/c/T::::Exit:::: -} - -void Root::State3::tick(void) { - _root->log("\033[36mTICK::State3::/c/T\033[0m"); - // Call the Tick Action for this state - //::::/c/T::::Tick:::: - - if (_activeState != nullptr && _activeState != this) - _activeState->tick(); -} - -double Root::State3::getTimerPeriod(void) { return (double)(0); } - -bool Root::State3::handleEvent(GeneratedEventBase *event) { - bool handled = false; - - // take care of all event types that this branch will not handle - - // for more consistent run-time performnace - switch (event->get_type()) { - case EventType::EVENT1: - handled = true; - break; - default: - handled = false; - break; - } - - if (handled) { - // we didn't actually handle the event, but return anyway - return false; - } - - // handle internal transitions first - switch (event->get_type()) { - default: - handled = false; - break; - } - if (!handled) { - // handle external transitions here - switch (event->get_type()) { - case EventType::ENDEVENT: - if (false) { - } // makes generation easier :) - else if (true) { - _root->log("\033[37mNO GUARD on EXTERNAL TRANSITION:/c/L\033[0m"); - // Transitioning states! - // Call all from prev state down exits - _root->COMPLEX_OBJ__STATE3_OBJ.exitChildren(); - // State : exit for: /c/T - _root->COMPLEX_OBJ__STATE3_OBJ.exit(); - // External Transition : Action for: /c/L - _root->log("\033[36mTRANSITION::ACTION for /c/L\033[0m"); - - //::::/c/L::::Action:::: - - // State : entry for: /c/Y - _root->COMPLEX_OBJ__STATE_1_OBJ.entry(); - _root->log("\033[31mSTATE TRANSITION: State3->State_1\033[0m"); - - // going into regular state - _root->COMPLEX_OBJ__STATE_1_OBJ.initialize(); - // make sure nothing else handles this event - handled = true; - } - break; - case EventType::EVENT2: - if (false) { - } // makes generation easier :) - else if (true) { - _root->log("\033[37mNO GUARD on EXTERNAL TRANSITION:/c/z\033[0m"); - // Transitioning states! - // Call all from prev state down exits - _root->COMPLEX_OBJ__STATE3_OBJ.exitChildren(); - // State : exit for: /c/T - _root->COMPLEX_OBJ__STATE3_OBJ.exit(); - // External Transition : Action for: /c/z - _root->log("\033[36mTRANSITION::ACTION for /c/z\033[0m"); - - //::::/c/z::::Action:::: - - // State : entry for: /c/v - _root->COMPLEX_OBJ__STATE_2_OBJ.entry(); - _root->log("\033[31mSTATE TRANSITION: State3->State_2\033[0m"); - - // going into regular state - _root->COMPLEX_OBJ__STATE_2_OBJ.initialize(); - // make sure nothing else handles this event - handled = true; - } - break; - case EventType::EVENT4: - if (false) { - } // makes generation easier :) - else if (true) { - _root->log("\033[37mNO GUARD on EXTERNAL TRANSITION:/c/w\033[0m"); - // Transitioning states! - // Call all from prev state down exits - _root->COMPLEX_OBJ__STATE3_OBJ.exitChildren(); - // State : exit for: /c/T - _root->COMPLEX_OBJ__STATE3_OBJ.exit(); - // External Transition : Action for: /c/w - _root->log("\033[36mTRANSITION::ACTION for /c/w\033[0m"); - - //::::/c/w::::Action:::: - - // State : entry for: /c/v - _root->COMPLEX_OBJ__STATE_2_OBJ.entry(); - _root->log("\033[31mSTATE TRANSITION: State3->State_2::Deep_History_Pseudostate\033[0m"); - - // going into deep history pseudo-state - _root->COMPLEX_OBJ__STATE_2_OBJ.setDeepHistory(); - // make sure nothing else handles this event - handled = true; - } - break; - case EventType::EVENT3: - if (false) { - } // makes generation easier :) - else if (true) { - _root->log("\033[37mNO GUARD on EXTERNAL TRANSITION:/c/C\033[0m"); - // Transitioning states! - // Call all from prev state down exits - _root->COMPLEX_OBJ__STATE3_OBJ.exitChildren(); - // State : exit for: /c/T - _root->COMPLEX_OBJ__STATE3_OBJ.exit(); - // External Transition : Action for: /c/C - _root->log("\033[36mTRANSITION::ACTION for /c/C\033[0m"); - - //::::/c/C::::Action:::: - - // State : entry for: /c/v - _root->COMPLEX_OBJ__STATE_2_OBJ.entry(); - _root->log("\033[31mSTATE TRANSITION: State3->State_2::Shallow_History_Pseudostate\033[0m"); - - // going into shallow history pseudo-state - _root->COMPLEX_OBJ__STATE_2_OBJ.setShallowHistory(); - // make sure nothing else handles this event - handled = true; - } - break; - default: - handled = false; - break; - } - } - // can't buble up, we are a root state. - return handled; -} -/* * * Definitions for State3::ChildState2 : /c/T/0 * * */ - -// User Definitions for the HFSM -//::::/c/T/0::::Definitions:::: - -void Root::State3::ChildState2::initialize(void) { - // if we're a leaf state, make sure we're active - makeActive(); -} - -void Root::State3::ChildState2::entry(void) { - _root->log("\033[36mENTRY::State3::ChildState2::/c/T/0\033[0m"); - // Entry action for this state - //::::/c/T/0::::Entry:::: -} - -void Root::State3::ChildState2::exit(void) { - _root->log("\033[36mEXIT::State3::ChildState2::/c/T/0\033[0m"); - // Call the Exit Action for this state - //::::/c/T/0::::Exit:::: -} - -void Root::State3::ChildState2::tick(void) { - _root->log("\033[36mTICK::State3::ChildState2::/c/T/0\033[0m"); - // Call the Tick Action for this state - //::::/c/T/0::::Tick:::: - - if (_activeState != nullptr && _activeState != this) - _activeState->tick(); -} - -double Root::State3::ChildState2::getTimerPeriod(void) { return (double)(0.1); } - -bool Root::State3::ChildState2::handleEvent(GeneratedEventBase *event) { - bool handled = false; - - // take care of all event types that this branch will not handle - - // for more consistent run-time performnace - switch (event->get_type()) { - case EventType::EVENT1: - handled = true; - break; - default: - handled = false; - break; - } - - if (handled) { - // we didn't actually handle the event, but return anyway - return false; - } - - // handle internal transitions first - switch (event->get_type()) { - default: - handled = false; - break; - } - if (!handled) { - // handle external transitions here - switch (event->get_type()) { - case EventType::ENDEVENT: - if (false) { - } // makes generation easier :) - else if (true) { - _root->log("\033[37mNO GUARD on EXTERNAL TRANSITION:/c/T/h\033[0m"); - // Going into an end pseudo-state that is not the root end state, - // follow its parent end transition - if (false) { - } else if (true) { - _root->log("\033[37mNO GUARD on EXTERNAL TRANSITION:/c/A\033[0m"); - // Transitioning states! - // Call all from prev state down exits - _root->COMPLEX_OBJ__STATE3_OBJ__CHILDSTATE2_OBJ.exitChildren(); - // State : exit for: /c/T/0 - _root->COMPLEX_OBJ__STATE3_OBJ__CHILDSTATE2_OBJ.exit(); - // State : exit for: /c/T - _root->COMPLEX_OBJ__STATE3_OBJ.exit(); - // External Transition : Action for: /c/T/h - _root->log("\033[36mTRANSITION::ACTION for /c/T/h\033[0m"); - - //::::/c/T/h::::Action:::: - - // External Transition : Action for: /c/A - _root->log("\033[36mTRANSITION::ACTION for /c/A\033[0m"); - - //::::/c/A::::Action:::: - - _root->log("\033[31mSTATE TRANSITION: State3::ChildState2->End_State\033[0m"); - - // going into end pseudo-state THIS SHOULD BE TOP LEVEL END STATE - _root->COMPLEX_OBJ__END_STATE_OBJ.makeActive(); - // make sure nothing else handles this event - handled = true; - } - } - break; - case EventType::EVENT2: - if (false) { - } // makes generation easier :) - else if (true) { - _root->log("\033[37mNO GUARD on EXTERNAL TRANSITION:/c/T/j\033[0m"); - // Transitioning states! - // Call all from prev state down exits - _root->COMPLEX_OBJ__STATE3_OBJ__CHILDSTATE2_OBJ.exitChildren(); - // State : exit for: /c/T/0 - _root->COMPLEX_OBJ__STATE3_OBJ__CHILDSTATE2_OBJ.exit(); - // External Transition : Action for: /c/T/j - _root->log("\033[36mTRANSITION::ACTION for /c/T/j\033[0m"); - - //::::/c/T/j::::Action:::: - - // State : entry for: /c/T/w - _root->COMPLEX_OBJ__STATE3_OBJ__CHILDSTATE3_OBJ.entry(); - _root->log("\033[31mSTATE TRANSITION: State3::ChildState2->State3::ChildState3\033[0m"); - - // going into regular state - _root->COMPLEX_OBJ__STATE3_OBJ__CHILDSTATE3_OBJ.initialize(); - // make sure nothing else handles this event - handled = true; - } - break; - default: - handled = false; - break; - } - } - if (!handled) { - // now check parent states - handled = _parentState->handleEvent(event); - } - return handled; -} -/* * * Definitions for State3::ChildState : /c/T/W * * */ - -// User Definitions for the HFSM -//::::/c/T/W::::Definitions:::: - -void Root::State3::ChildState::initialize(void) { - // if we're a leaf state, make sure we're active - makeActive(); -} - -void Root::State3::ChildState::entry(void) { - _root->log("\033[36mENTRY::State3::ChildState::/c/T/W\033[0m"); - // Entry action for this state - //::::/c/T/W::::Entry:::: -} - -void Root::State3::ChildState::exit(void) { - _root->log("\033[36mEXIT::State3::ChildState::/c/T/W\033[0m"); - // Call the Exit Action for this state - //::::/c/T/W::::Exit:::: -} - -void Root::State3::ChildState::tick(void) { - _root->log("\033[36mTICK::State3::ChildState::/c/T/W\033[0m"); - // Call the Tick Action for this state - //::::/c/T/W::::Tick:::: - - if (_activeState != nullptr && _activeState != this) - _activeState->tick(); -} - -double Root::State3::ChildState::getTimerPeriod(void) { return (double)(0.1); } - -bool Root::State3::ChildState::handleEvent(GeneratedEventBase *event) { - bool handled = false; - - // take care of all event types that this branch will not handle - - // for more consistent run-time performnace - switch (event->get_type()) { - default: - handled = false; - break; - } - - if (handled) { - // we didn't actually handle the event, but return anyway - return false; - } - - // handle internal transitions first - switch (event->get_type()) { - default: - handled = false; - break; - } - if (!handled) { - // handle external transitions here - switch (event->get_type()) { - case EventType::EVENT1: - if (false) { - } // makes generation easier :) - else if (true) { - _root->log("\033[37mNO GUARD on EXTERNAL TRANSITION:/c/T/L\033[0m"); - // Transitioning states! - // Call all from prev state down exits - _root->COMPLEX_OBJ__STATE3_OBJ__CHILDSTATE_OBJ.exitChildren(); - // State : exit for: /c/T/W - _root->COMPLEX_OBJ__STATE3_OBJ__CHILDSTATE_OBJ.exit(); - // External Transition : Action for: /c/T/L - _root->log("\033[36mTRANSITION::ACTION for /c/T/L\033[0m"); - - //::::/c/T/L::::Action:::: - - // State : entry for: /c/T/0 - _root->COMPLEX_OBJ__STATE3_OBJ__CHILDSTATE2_OBJ.entry(); - _root->log("\033[31mSTATE TRANSITION: State3::ChildState->State3::ChildState2\033[0m"); - - // going into regular state - _root->COMPLEX_OBJ__STATE3_OBJ__CHILDSTATE2_OBJ.initialize(); - // make sure nothing else handles this event - handled = true; - } - break; - default: - handled = false; - break; - } - } - if (!handled) { - // now check parent states - handled = _parentState->handleEvent(event); - } - return handled; -} -/* * * Definitions for State3::ChildState3 : /c/T/w * * */ - -// User Definitions for the HFSM -//::::/c/T/w::::Definitions:::: - -void Root::State3::ChildState3::initialize(void) { - // if we're a leaf state, make sure we're active - makeActive(); -} - -void Root::State3::ChildState3::entry(void) { - _root->log("\033[36mENTRY::State3::ChildState3::/c/T/w\033[0m"); - // Entry action for this state - //::::/c/T/w::::Entry:::: -} - -void Root::State3::ChildState3::exit(void) { - _root->log("\033[36mEXIT::State3::ChildState3::/c/T/w\033[0m"); - // Call the Exit Action for this state - //::::/c/T/w::::Exit:::: -} - -void Root::State3::ChildState3::tick(void) { - _root->log("\033[36mTICK::State3::ChildState3::/c/T/w\033[0m"); - // Call the Tick Action for this state - //::::/c/T/w::::Tick:::: - - if (_activeState != nullptr && _activeState != this) - _activeState->tick(); -} - -double Root::State3::ChildState3::getTimerPeriod(void) { return (double)(0.1); } - -bool Root::State3::ChildState3::handleEvent(GeneratedEventBase *event) { - bool handled = false; - - // take care of all event types that this branch will not handle - - // for more consistent run-time performnace - switch (event->get_type()) { - case EventType::EVENT1: - handled = true; - break; - default: - handled = false; - break; - } - - if (handled) { - // we didn't actually handle the event, but return anyway - return false; - } - - // handle internal transitions first - switch (event->get_type()) { - default: - handled = false; - break; - } - if (!handled) { - // handle external transitions here - switch (event->get_type()) { - case EventType::EVENT3: - if (false) { - } // makes generation easier :) - else if (true) { - _root->log("\033[37mNO GUARD on EXTERNAL TRANSITION:/c/T/p\033[0m"); - // Transitioning states! - // Call all from prev state down exits - _root->COMPLEX_OBJ__STATE3_OBJ__CHILDSTATE3_OBJ.exitChildren(); - // State : exit for: /c/T/w - _root->COMPLEX_OBJ__STATE3_OBJ__CHILDSTATE3_OBJ.exit(); - // External Transition : Action for: /c/T/p - _root->log("\033[36mTRANSITION::ACTION for /c/T/p\033[0m"); - - //::::/c/T/p::::Action:::: - - // State : entry for: /c/T/W - _root->COMPLEX_OBJ__STATE3_OBJ__CHILDSTATE_OBJ.entry(); - _root->log("\033[31mSTATE TRANSITION: State3::ChildState3->State3::ChildState\033[0m"); - - // going into regular state - _root->COMPLEX_OBJ__STATE3_OBJ__CHILDSTATE_OBJ.initialize(); - // make sure nothing else handles this event - handled = true; - } - break; - default: - handled = false; - break; - } - } - if (!handled) { - // now check parent states - handled = _parentState->handleEvent(event); - } - return handled; -} diff --git a/components/state_machine/example/main/Complex_generated_states.hpp b/components/state_machine/example/main/Complex_generated_states.hpp deleted file mode 100644 index f0ce378c7b..0000000000 --- a/components/state_machine/example/main/Complex_generated_states.hpp +++ /dev/null @@ -1,676 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "deep_history_state.hpp" -#include "magic_enum.hpp" -#include "shallow_history_state.hpp" -#include "state_base.hpp" - -#include "Complex_event_data.hpp" - -// User Includes for the HFSM -//::::/c::::Includes:::: -#include - -namespace espp::state_machine::Complex { - -typedef std::function LogCallback; - -enum class EventType { - ENDEVENT, - EVENT1, - EVENT2, - EVENT3, - EVENT4, -}; // ENUMS GENERATED FROM MODEL - -/** - * @brief Class representing all events that this HFSM can respond - * to / handle. Used as abstract interface for handleEvent(). - */ -class GeneratedEventBase : public EventBase { -protected: - EventType type; - -public: - explicit GeneratedEventBase(const EventType &t) - : type(t) {} - virtual ~GeneratedEventBase() {} - EventType get_type() const { return type; } - virtual std::string to_string() const { return std::string(magic_enum::enum_name(type)); } -}; // Class GeneratedEventBase - -/** - * @brief Class representing all events that this HFSM can respond - * to / handle. Intended to be created / managed by the - * EventFactory (below). - */ -template class Event : public GeneratedEventBase { - T data; - -public: - explicit Event(const EventType &t, const T &d) - : GeneratedEventBase(t) - , data(d) {} - virtual ~Event() {} - T get_data() const { return data; } -}; // Class Event - -// free the memory associated with the event -static void consume_event(GeneratedEventBase *e) { delete e; } - -typedef Event ENDEVENTEvent; -typedef Event EVENT1Event; -typedef Event EVENT2Event; -typedef Event EVENT3Event; -typedef Event EVENT4Event; - -/** - * @brief Class handling all Event creation, memory management, and - * ordering. - */ -class EventFactory { -public: - ~EventFactory(void) { clear_events(); } - - void set_log_callback(LogCallback cb) { log_callback_ = cb; } - - void spawn_ENDEVENT_event(const ENDEVENTEventData &data) { - log("\033[32mSPAWN: ENDEVENT\033[0m"); - GeneratedEventBase *new_event = new ENDEVENTEvent{EventType::ENDEVENT, data}; - std::lock_guard lock(queue_mutex_); - events_.push_back(new_event); - queue_cv_.notify_one(); - } - void spawn_EVENT1_event(const EVENT1EventData &data) { - log("\033[32mSPAWN: EVENT1\033[0m"); - GeneratedEventBase *new_event = new EVENT1Event{EventType::EVENT1, data}; - std::lock_guard lock(queue_mutex_); - events_.push_back(new_event); - queue_cv_.notify_one(); - } - void spawn_EVENT2_event(const EVENT2EventData &data) { - log("\033[32mSPAWN: EVENT2\033[0m"); - GeneratedEventBase *new_event = new EVENT2Event{EventType::EVENT2, data}; - std::lock_guard lock(queue_mutex_); - events_.push_back(new_event); - queue_cv_.notify_one(); - } - void spawn_EVENT3_event(const EVENT3EventData &data) { - log("\033[32mSPAWN: EVENT3\033[0m"); - GeneratedEventBase *new_event = new EVENT3Event{EventType::EVENT3, data}; - std::lock_guard lock(queue_mutex_); - events_.push_back(new_event); - queue_cv_.notify_one(); - } - void spawn_EVENT4_event(const EVENT4EventData &data) { - log("\033[32mSPAWN: EVENT4\033[0m"); - GeneratedEventBase *new_event = new EVENT4Event{EventType::EVENT4, data}; - std::lock_guard lock(queue_mutex_); - events_.push_back(new_event); - queue_cv_.notify_one(); - } - - // Returns the number of events in the queue - size_t get_num_events(void) { - std::lock_guard lock(queue_mutex_); - return events_.size(); - } - - // Blocks until an event is available - void wait_for_events(void) { - std::unique_lock lock(queue_mutex_); - queue_cv_.wait(lock); - } - - // Blocks until an event is available or the timeout is reached - void sleep_until_event(float seconds) { - std::unique_lock lock(queue_mutex_); - queue_cv_.wait_for(lock, std::chrono::duration(seconds)); - } - - // Blocks until an event is available - GeneratedEventBase *get_next_event_blocking(void) { - std::unique_lock lock(queue_mutex_); - queue_cv_.wait(lock, [this] { return events_.size() > 0; }); - GeneratedEventBase *ptr = events_.front(); - events_.pop_front(); // remove the event from the Q - return ptr; - } - - // Retrieves the pointer to the next event in the queue, or - // nullptr if it doesn't exist - GeneratedEventBase *get_next_event(void) { - std::lock_guard lock(queue_mutex_); - GeneratedEventBase *ptr = nullptr; - if (events_.size()) { - ptr = events_.front(); - events_.pop_front(); // remove the event from the Q - } - return ptr; - } - - // Clears the event queue and frees all event memory - void clear_events(void) { - GeneratedEventBase *ptr = get_next_event(); - while (ptr != nullptr) { - consume_event(ptr); - ptr = get_next_event(); - } - } - - std::string to_string(void) { - std::lock_guard lock(queue_mutex_); - std::string qStr = "[ "; - for (int i = 0; i < events_.size(); i++) { - qStr += events_[i]->to_string(); - } - qStr += " ]"; - return qStr; - } - -protected: - void log(std::string_view msg) { - if (log_callback_) { - log_callback_(msg); - } - } - - std::deque events_; - std::mutex queue_mutex_; - std::condition_variable queue_cv_; - LogCallback log_callback_{nullptr}; -}; // class EventFactory - -/** - * @brief The ROOT of the HFSM - contains the declarations from - * the user as well as the entire substate tree. - */ -class Root : public StateBase { -public: - // User Declarations for the HFSM - //::::/c::::Declarations:::: - bool goToEnd = false; - bool goToChoice = true; - bool goToHistory = false; - bool nextState = false; - bool killedState = false; - bool someGuard = true; - bool someTest = true; - - int someNumber = 40; - int someValue = 50; - -protected: - void log(const std::string &msg) { - if (log_callback_) { - log_callback_(msg); - } - } - - LogCallback log_callback_{nullptr}; - -public: - // event factory for spawning / ordering events - EventFactory event_factory; - - void set_log_callback(LogCallback cb) { - log_callback_ = cb; - event_factory.set_log_callback(cb); - } - - // helper functions for spawning events into the HFSM - void spawn_ENDEVENT_event(const ENDEVENTEventData &data) { - event_factory.spawn_ENDEVENT_event(data); - } - void spawn_EVENT1_event(const EVENT1EventData &data) { event_factory.spawn_EVENT1_event(data); } - void spawn_EVENT2_event(const EVENT2EventData &data) { event_factory.spawn_EVENT2_event(data); } - void spawn_EVENT3_event(const EVENT3EventData &data) { event_factory.spawn_EVENT3_event(data); } - void spawn_EVENT4_event(const EVENT4EventData &data) { event_factory.spawn_EVENT4_event(data); } - - // Constructors - Root() - : StateBase() - , COMPLEX_OBJ__STATE_1_OBJ(this, this) - , COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE_OBJ(this, &COMPLEX_OBJ__STATE_2_OBJ) - , COMPLEX_OBJ__STATE_2_OBJ__DEEP_HISTORY_PSEUDOSTATE_OBJ(&COMPLEX_OBJ__STATE_2_OBJ) - , COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE2_OBJ(this, &COMPLEX_OBJ__STATE_2_OBJ) - , COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ__GRAND_OBJ( - this, &COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ) - , COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ__GRAND2_OBJ( - this, &COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ) - , COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ(this, &COMPLEX_OBJ__STATE_2_OBJ) - , COMPLEX_OBJ__STATE_2_OBJ__SHALLOW_HISTORY_PSEUDOSTATE_OBJ(&COMPLEX_OBJ__STATE_2_OBJ) - , COMPLEX_OBJ__STATE_2_OBJ(this, this) - , COMPLEX_OBJ__STATE3_OBJ__CHILDSTATE2_OBJ(this, &COMPLEX_OBJ__STATE3_OBJ) - , COMPLEX_OBJ__STATE3_OBJ__SHALLOW_HISTORY_PSEUDOSTATE_OBJ(&COMPLEX_OBJ__STATE3_OBJ) - , COMPLEX_OBJ__STATE3_OBJ__DEEP_HISTORY_PSEUDOSTATE_OBJ(&COMPLEX_OBJ__STATE3_OBJ) - , COMPLEX_OBJ__STATE3_OBJ__CHILDSTATE_OBJ(this, &COMPLEX_OBJ__STATE3_OBJ) - , COMPLEX_OBJ__STATE3_OBJ__CHILDSTATE3_OBJ(this, &COMPLEX_OBJ__STATE3_OBJ) - , COMPLEX_OBJ__STATE3_OBJ(this, this) - , COMPLEX_OBJ__END_STATE_OBJ(this) - , _root(this) {} - ~Root(void) {} - - /** - * @brief Fully initializes the HFSM. Runs the HFSM Initialization - * code from the model, then sets the inital state and runs the - * initial transition and entry actions accordingly. - */ - void initialize(void); - - /** - * @brief Returns true if there are any events in the event queue. - */ - bool has_events(void) { return event_factory.get_num_events() > 0; } - - /** - * @brief Sleeps until an event is available or the current state's timer - * period expires, then returns. This will block until an event is - * available. The amount of time spent sleeping is determined by the - * current state's timer period. - */ - void sleep_until_event(void) { - event_factory.sleep_until_event(getActiveLeaf()->getTimerPeriod()); - } - - /** - * @brief Waits for an event to be available, then returns. - * This will block until an event is available. - */ - void wait_for_events(void) { event_factory.wait_for_events(); } - - /** - * @brief Handles all events in the event queue, ensuring to free the - * memory. This will ensure that any events spawned from other event - * transitions / actions are handled. Returns once there are no more - * events in the queue to process. - */ - void handle_all_events(void); - - /** - * @brief Terminates the HFSM, calling exit functions for the - * active leaf state upwards through its parents all the way to - * the root. - */ - void terminate(void); - - /** - * @brief Restarts the HFSM by calling terminate and then - * initialize. - */ - void restart(void); - - /** - * @brief Returns true if the HFSM has reached its END State - */ - bool has_stopped(void); - - /** - * @brief Calls handleEvent on the activeLeaf. - * - * @param[in] EventBase* Event needing to be handled - * - * @return true if event is consumed, false otherwise - */ - bool handleEvent(EventBase *event) { - return handleEvent(static_cast(event)); - } - - /** - * @brief Calls handleEvent on the activeLeaf. - * - * @param[in] EventBase* Event needing to be handled - * - * @return true if event is consumed, false otherwise - */ - bool handleEvent(GeneratedEventBase *event); - - // Child Substates - // Declaration for State_1 : /c/Y - class State_1 : public StateBase { - public: - // User Declarations for the State - //::::/c/Y::::Declarations:::: - - public: - // Pointer to the root of the HFSM. - Root *_root; - - // Constructors - State_1(Root *root, StateBase *parent) - : StateBase(parent) - , _root(root) {} - ~State_1(void) {} - - // StateBase Interface - void initialize(void); - void entry(void); - void exit(void); - void tick(void); - double getTimerPeriod(void); - virtual bool handleEvent(EventBase *event) { - return handleEvent(static_cast(event)); - } - virtual bool handleEvent(GeneratedEventBase *event); - }; - // Declaration for State_2 : /c/v - class State_2 : public StateBase { - public: - // User Declarations for the State - //::::/c/v::::Declarations:::: - - public: - // Pointer to the root of the HFSM. - Root *_root; - - // Constructors - State_2(Root *root, StateBase *parent) - : StateBase(parent) - , _root(root) {} - ~State_2(void) {} - - // StateBase Interface - void initialize(void); - void entry(void); - void exit(void); - void tick(void); - double getTimerPeriod(void); - virtual bool handleEvent(EventBase *event) { - return handleEvent(static_cast(event)); - } - virtual bool handleEvent(GeneratedEventBase *event); - - // Declaration for State_2::ChildState : /c/v/K - class ChildState : public StateBase { - public: - // User Declarations for the State - //::::/c/v/K::::Declarations:::: - - public: - // Pointer to the root of the HFSM. - Root *_root; - - // Constructors - ChildState(Root *root, StateBase *parent) - : StateBase(parent) - , _root(root) {} - ~ChildState(void) {} - - // StateBase Interface - void initialize(void); - void entry(void); - void exit(void); - void tick(void); - double getTimerPeriod(void); - virtual bool handleEvent(EventBase *event) { - return handleEvent(static_cast(event)); - } - virtual bool handleEvent(GeneratedEventBase *event); - }; - // Declaration for State_2::ChildState2 : /c/v/e - class ChildState2 : public StateBase { - public: - // User Declarations for the State - //::::/c/v/e::::Declarations:::: - - public: - // Pointer to the root of the HFSM. - Root *_root; - - // Constructors - ChildState2(Root *root, StateBase *parent) - : StateBase(parent) - , _root(root) {} - ~ChildState2(void) {} - - // StateBase Interface - void initialize(void); - void entry(void); - void exit(void); - void tick(void); - double getTimerPeriod(void); - virtual bool handleEvent(EventBase *event) { - return handleEvent(static_cast(event)); - } - virtual bool handleEvent(GeneratedEventBase *event); - }; - // Declaration for State_2::ChildState3 : /c/v/z - class ChildState3 : public StateBase { - public: - // User Declarations for the State - //::::/c/v/z::::Declarations:::: - - public: - // Pointer to the root of the HFSM. - Root *_root; - - // Constructors - ChildState3(Root *root, StateBase *parent) - : StateBase(parent) - , _root(root) {} - ~ChildState3(void) {} - - // StateBase Interface - void initialize(void); - void entry(void); - void exit(void); - void tick(void); - double getTimerPeriod(void); - virtual bool handleEvent(EventBase *event) { - return handleEvent(static_cast(event)); - } - virtual bool handleEvent(GeneratedEventBase *event); - - // Declaration for State_2::ChildState3::Grand : /c/v/z/6 - class Grand : public StateBase { - public: - // User Declarations for the State - //::::/c/v/z/6::::Declarations:::: - - public: - // Pointer to the root of the HFSM. - Root *_root; - - // Constructors - Grand(Root *root, StateBase *parent) - : StateBase(parent) - , _root(root) {} - ~Grand(void) {} - - // StateBase Interface - void initialize(void); - void entry(void); - void exit(void); - void tick(void); - double getTimerPeriod(void); - virtual bool handleEvent(EventBase *event) { - return handleEvent(static_cast(event)); - } - virtual bool handleEvent(GeneratedEventBase *event); - }; - // Declaration for State_2::ChildState3::Grand2 : /c/v/z/c - class Grand2 : public StateBase { - public: - // User Declarations for the State - //::::/c/v/z/c::::Declarations:::: - - public: - // Pointer to the root of the HFSM. - Root *_root; - - // Constructors - Grand2(Root *root, StateBase *parent) - : StateBase(parent) - , _root(root) {} - ~Grand2(void) {} - - // StateBase Interface - void initialize(void); - void entry(void); - void exit(void); - void tick(void); - double getTimerPeriod(void); - virtual bool handleEvent(EventBase *event) { - return handleEvent(static_cast(event)); - } - virtual bool handleEvent(GeneratedEventBase *event); - }; - }; - }; - // Declaration for State3 : /c/T - class State3 : public StateBase { - public: - // User Declarations for the State - //::::/c/T::::Declarations:::: - - public: - // Pointer to the root of the HFSM. - Root *_root; - - // Constructors - State3(Root *root, StateBase *parent) - : StateBase(parent) - , _root(root) {} - ~State3(void) {} - - // StateBase Interface - void initialize(void); - void entry(void); - void exit(void); - void tick(void); - double getTimerPeriod(void); - virtual bool handleEvent(EventBase *event) { - return handleEvent(static_cast(event)); - } - virtual bool handleEvent(GeneratedEventBase *event); - - // Declaration for State3::ChildState2 : /c/T/0 - class ChildState2 : public StateBase { - public: - // User Declarations for the State - //::::/c/T/0::::Declarations:::: - - public: - // Pointer to the root of the HFSM. - Root *_root; - - // Constructors - ChildState2(Root *root, StateBase *parent) - : StateBase(parent) - , _root(root) {} - ~ChildState2(void) {} - - // StateBase Interface - void initialize(void); - void entry(void); - void exit(void); - void tick(void); - double getTimerPeriod(void); - virtual bool handleEvent(EventBase *event) { - return handleEvent(static_cast(event)); - } - virtual bool handleEvent(GeneratedEventBase *event); - }; - // Declaration for State3::ChildState : /c/T/W - class ChildState : public StateBase { - public: - // User Declarations for the State - //::::/c/T/W::::Declarations:::: - - public: - // Pointer to the root of the HFSM. - Root *_root; - - // Constructors - ChildState(Root *root, StateBase *parent) - : StateBase(parent) - , _root(root) {} - ~ChildState(void) {} - - // StateBase Interface - void initialize(void); - void entry(void); - void exit(void); - void tick(void); - double getTimerPeriod(void); - virtual bool handleEvent(EventBase *event) { - return handleEvent(static_cast(event)); - } - virtual bool handleEvent(GeneratedEventBase *event); - }; - // Declaration for State3::ChildState3 : /c/T/w - class ChildState3 : public StateBase { - public: - // User Declarations for the State - //::::/c/T/w::::Declarations:::: - - public: - // Pointer to the root of the HFSM. - Root *_root; - - // Constructors - ChildState3(Root *root, StateBase *parent) - : StateBase(parent) - , _root(root) {} - ~ChildState3(void) {} - - // StateBase Interface - void initialize(void); - void entry(void); - void exit(void); - void tick(void); - double getTimerPeriod(void); - virtual bool handleEvent(EventBase *event) { - return handleEvent(static_cast(event)); - } - virtual bool handleEvent(GeneratedEventBase *event); - }; - }; - - // END STATE - /** - * @brief This is the terminal END STATE for the HFSM, after which no - * events or other actions will be processed. - */ - class End_State : public StateBase { - public: - explicit End_State(StateBase *parent) - : StateBase(parent) {} - void entry(void) {} - void exit(void) {} - void tick(void) {} - // Simply returns true since the END STATE trivially handles all - // events. - bool handleEvent(EventBase *event) { return true; } - bool handleEvent(GeneratedEventBase *event) { return true; } - }; - - // State Objects - State_1 COMPLEX_OBJ__STATE_1_OBJ; - State_2::ChildState COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE_OBJ; - DeepHistoryState COMPLEX_OBJ__STATE_2_OBJ__DEEP_HISTORY_PSEUDOSTATE_OBJ; - State_2::ChildState2 COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE2_OBJ; - State_2::ChildState3::Grand COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ__GRAND_OBJ; - State_2::ChildState3::Grand2 COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ__GRAND2_OBJ; - State_2::ChildState3 COMPLEX_OBJ__STATE_2_OBJ__CHILDSTATE3_OBJ; - ShallowHistoryState COMPLEX_OBJ__STATE_2_OBJ__SHALLOW_HISTORY_PSEUDOSTATE_OBJ; - State_2 COMPLEX_OBJ__STATE_2_OBJ; - State3::ChildState2 COMPLEX_OBJ__STATE3_OBJ__CHILDSTATE2_OBJ; - ShallowHistoryState COMPLEX_OBJ__STATE3_OBJ__SHALLOW_HISTORY_PSEUDOSTATE_OBJ; - DeepHistoryState COMPLEX_OBJ__STATE3_OBJ__DEEP_HISTORY_PSEUDOSTATE_OBJ; - State3::ChildState COMPLEX_OBJ__STATE3_OBJ__CHILDSTATE_OBJ; - State3::ChildState3 COMPLEX_OBJ__STATE3_OBJ__CHILDSTATE3_OBJ; - State3 COMPLEX_OBJ__STATE3_OBJ; - // END state object - End_State COMPLEX_OBJ__END_STATE_OBJ; - // Keep a _root for easier templating, it will point to us - Root *_root; -}; // class Root - -}; // namespace espp::state_machine::Complex diff --git a/components/state_machine/example/main/Complex_metadata.json b/components/state_machine/example/main/Complex_metadata.json deleted file mode 100644 index 4d677bea02..0000000000 --- a/components/state_machine/example/main/Complex_metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "projectID": "guest+Examples", - "timeStamp": "2023-08-08T19:38:25.130Z", - "pluginVersion": "0.1.0" -} \ No newline at end of file From 2a01d0b4712f405a09cbafbbdc3847f5c07778ef Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Fri, 4 Sep 2026 23:35:51 -0500 Subject: [PATCH 2/8] Generate only the machine, using the generator's --no-support The build was taking the generator's whole output and copying three files out of it. The generator can now be asked for those three directly, so the copy step goes away. It matters beyond tidiness: the four files being skipped are a second copy of the runtime this component already provides, and they would sit on the include path in front of espp's. espp's are the ones the rest of the codebase is built against, and its magic_enum (0.9.5) is newer than the generator's (0.8.0). Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01E4hRq1q5VGPjNn2dnkkqBy --- components/state_machine/example/README.md | 5 ++++ .../state_machine/example/main/CMakeLists.txt | 25 ++++++++----------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/components/state_machine/example/README.md b/components/state_machine/example/README.md index 801a00557c..1e9671723b 100644 --- a/components/state_machine/example/README.md +++ b/components/state_machine/example/README.md @@ -23,6 +23,11 @@ idf.py build # generates main/Complex.json -> build/.../hfsm/, then builds Editing the model regenerates on the next build; you do not need to clean. +Only the machine itself is generated. The shared runtime it builds on +— `state_base.hpp`, the history states, `magic_enum.hpp` — comes from +this component, via the generator's `--no-support` flag; espp's copies +are the ones the rest of the codebase is built against. + To edit the machine, open it in the **[HFSM Playground][playground]** — a browser-based editor and code generator, no install and no server. Load `main/Complex.json` with **Open file…**, or jump straight to this diff --git a/components/state_machine/example/main/CMakeLists.txt b/components/state_machine/example/main/CMakeLists.txt index 966018f6de..0bb556498f 100644 --- a/components/state_machine/example/main/CMakeLists.txt +++ b/components/state_machine/example/main/CMakeLists.txt @@ -13,7 +13,7 @@ set(HFSM_MODEL "${CMAKE_CURRENT_LIST_DIR}/Complex.json") # npm version because the packaging fix that makes `npx webgme-hfsm` # work at all is not in a release yet; switch to "webgme-hfsm@^1.8.0" # once it is published. -set(HFSM_GEN_SPEC "github:finger563/webgme-hfsm#638226b3e1e1c0002298d68f7237bcf7ca908a27" +set(HFSM_GEN_SPEC "github:finger563/webgme-hfsm#3f4652e5b3c89b80e1ba7befb856f22aa959c02e" CACHE STRING "npm/npx spec for the webgme-hfsm code generator") # Set this to skip npx entirely -- e.g. a local checkout while working @@ -26,7 +26,6 @@ set(HFSM_GEN_COMMAND "" CACHE STRING # C++ on the next build rather than on the next clean. set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${HFSM_MODEL}") -set(HFSM_RAW_DIR "${CMAKE_CURRENT_BINARY_DIR}/hfsm-raw") set(HFSM_DIR "${CMAKE_CURRENT_BINARY_DIR}/hfsm") if(HFSM_GEN_COMMAND) @@ -44,9 +43,18 @@ else() set(_hfsm_cmd "${NPX_EXECUTABLE}" -y -p "${HFSM_GEN_SPEC}" hfsm-gen) endif() +# --no-support: generate THIS machine and not the shared runtime. +# +# The generator otherwise also emits state_base.hpp, +# deep_history_state.hpp, shallow_history_state.hpp and magic_enum.hpp, +# which are the same for every machine. This component already provides +# them -- espp's are the ones the rest of the codebase is built against, +# and its magic_enum is newer than the generator's -- so a second copy +# beside the generated machine would land on the include path and +# shadow them. message(STATUS "hfsm: generating C++ from ${HFSM_MODEL}") execute_process( - COMMAND ${_hfsm_cmd} "${HFSM_MODEL}" -o "${HFSM_RAW_DIR}" + COMMAND ${_hfsm_cmd} "${HFSM_MODEL}" -o "${HFSM_DIR}" --no-support RESULT_VARIABLE _hfsm_result OUTPUT_VARIABLE _hfsm_out ERROR_VARIABLE _hfsm_err) @@ -54,17 +62,6 @@ if(NOT _hfsm_result EQUAL 0) message(FATAL_ERROR "hfsm generation failed:\n${_hfsm_out}${_hfsm_err}") endif() -# Take ONLY the model-specific files. The generator also emits its own -# state_base.hpp / deep_history_state.hpp / shallow_history_state.hpp / -# magic_enum.hpp, and this component already provides those -- espp's -# are the adapted ones the rest of the codebase expects. Putting the -# generator's copies on the include path would shadow them. -file(MAKE_DIRECTORY "${HFSM_DIR}") -foreach(_f Complex_generated_states.hpp Complex_generated_states.cpp - Complex_event_data.hpp) - configure_file("${HFSM_RAW_DIR}/${_f}" "${HFSM_DIR}/${_f}" COPYONLY) -endforeach() - idf_component_register( SRCS "hfsm_example.cpp" "${HFSM_DIR}/Complex_generated_states.cpp" INCLUDE_DIRS "." "${HFSM_DIR}") From 4b8bc7d623bd7477039b149ffd2a353148a06722 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Fri, 4 Sep 2026 23:42:31 -0500 Subject: [PATCH 3/8] Use the released generator from npm now that v1.8.0 is out The pin was a git ref for two reasons, and the release settles both: `npx webgme-hfsm@1.7.0` could not run at all, and --no-support did not exist. 1.8.0 is the first release with either. ^1.8.0 rather than an exact version: reproducible enough for a build that regenerates from a checked-in model, and picks up generator fixes without a commit here. Verified against the published package: a clean esp-idf v6.1 / esp32 build, and the npm CLI's output is byte-identical to the repo's. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01E4hRq1q5VGPjNn2dnkkqBy --- components/state_machine/example/main/CMakeLists.txt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/components/state_machine/example/main/CMakeLists.txt b/components/state_machine/example/main/CMakeLists.txt index 0bb556498f..868c690e9a 100644 --- a/components/state_machine/example/main/CMakeLists.txt +++ b/components/state_machine/example/main/CMakeLists.txt @@ -9,11 +9,10 @@ set(HFSM_MODEL "${CMAKE_CURRENT_LIST_DIR}/Complex.json") -# Pinned so a build is reproducible. This is a git ref rather than an -# npm version because the packaging fix that makes `npx webgme-hfsm` -# work at all is not in a release yet; switch to "webgme-hfsm@^1.8.0" -# once it is published. -set(HFSM_GEN_SPEC "github:finger563/webgme-hfsm#3f4652e5b3c89b80e1ba7befb856f22aa959c02e" +# Pinned to a minor range so a build is reproducible but picks up +# fixes. 1.8.0 is the first release the CLI can be installed from at +# all, and the first with --no-support. +set(HFSM_GEN_SPEC "webgme-hfsm@^1.8.0" CACHE STRING "npm/npx spec for the webgme-hfsm code generator") # Set this to skip npx entirely -- e.g. a local checkout while working From 92f5aee33bce4d07661a897d7e86f9b03a28cc0b Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Sat, 5 Sep 2026 09:01:57 -0500 Subject: [PATCH 4/8] =?UTF-8?q?fix(state=5Fmachine):=20address=20#776=20re?= =?UTF-8?q?view=20=E2=80=94=20node>=3D18=20guard,=20non-interactive=20npx,?= =?UTF-8?q?=20stale-artifact=20cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - build.yml: the state_machine example's node guard only checked presence, so a base image / apt source with node <18 would still fail. Check node's major version and install Node 20 (>=18) from NodeSource when it is missing or too old. - README: pin the documented generator to `npx -y -p webgme-hfsm@^1.8.0` -- `-y` for non-interactive/CI use, and the version pin (now that 1.8.0 is released) matches the example's CMake spec for reproducible generation. - example CMake: the generator only overwrites the files it emits, so a machine renamed/removed in the model left an obsolete generated .cpp in the persistent binary dir for CMake to keep compiling. Wipe + recreate the output dir before generation so a regen cannot succeed against stale artifacts. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/build.yml | 3 ++- components/state_machine/README.md | 5 ++++- components/state_machine/example/main/CMakeLists.txt | 8 ++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d3a8cd719c..6bc41c0669 100755 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -301,7 +301,8 @@ jobs: # this example GENERATES its state machine from Complex.json # at configure time, and the esp-idf image has no node command: >- - (command -v node >/dev/null || (apt-get update && apt-get install -y nodejs npm)) && + node_major=$(node -v 2>/dev/null | sed -E 's/^v([0-9]+).*/\1/'); + if [ "${node_major:-0}" -lt 18 ]; then apt-get update && apt-get install -y curl && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && apt-get install -y nodejs; fi && IDF_COMPONENT_MANAGER=0 idf.py build - path: 'components/stream_frame/example' target: esp32 diff --git a/components/state_machine/README.md b/components/state_machine/README.md index e763a66e96..b2d8de3935 100644 --- a/components/state_machine/README.md +++ b/components/state_machine/README.md @@ -19,9 +19,12 @@ entirely in the browser — no install, no server — with the [**HFSM Playground**][playground], or from the command line: ```sh -npx -p webgme-hfsm hfsm-gen my_machine.json -o generated +npx -y -p webgme-hfsm@^1.8.0 hfsm-gen my_machine.json -o generated ``` +(`-y` skips npx's install prompt for non-interactive/CI use; the `@^1.8.0` +pin matches the example's CMake so generation is reproducible.) + The example wires that into its CMake, so its C++ is generated at build time from [`Complex.json`](example/main/Complex.json) rather than checked in — see [the example README](example/README.md). diff --git a/components/state_machine/example/main/CMakeLists.txt b/components/state_machine/example/main/CMakeLists.txt index 868c690e9a..31e6fc2d4b 100644 --- a/components/state_machine/example/main/CMakeLists.txt +++ b/components/state_machine/example/main/CMakeLists.txt @@ -51,6 +51,14 @@ endif() # and its magic_enum is newer than the generator's -- so a second copy # beside the generated machine would land on the include path and # shadow them. +# Clear stale artifacts first: the generator only overwrites the files it emits, +# so a machine renamed or removed in the model would otherwise leave an obsolete +# generated .cpp behind in the (persistent) binary dir for CMake to keep +# compiling. Wipe + recreate the output dir so a regeneration cannot succeed +# against stale output. +file(REMOVE_RECURSE "${HFSM_DIR}") +file(MAKE_DIRECTORY "${HFSM_DIR}") + message(STATUS "hfsm: generating C++ from ${HFSM_MODEL}") execute_process( COMMAND ${_hfsm_cmd} "${HFSM_MODEL}" -o "${HFSM_DIR}" --no-support From b29c8eeac80d803454e79fd966bbdb24b8348447 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Sat, 5 Sep 2026 09:16:46 -0500 Subject: [PATCH 5/8] Make HFSM generation a function the component provides The example carried ~50 lines of CMake to find npx, run the generator and wire up the output. Anyone generating a second machine would have copied it, and the copies would have drifted. espp_generate_hfsm() now lives in the component's project_include.cmake, which ESP-IDF includes before any component's CMakeLists, so every component can call it. The example is four lines. It finds the generated file names rather than making the caller name them: they come from the machine's name in the model, so hardcoding Complex_generated_states.cpp meant knowing what your own model was called and keeping that in step by hand. The call needs an `if(NOT CMAKE_BUILD_EARLY_EXPANSION)` guard, which is not obvious and is documented at both call site and README: ESP-IDF's early requirements pass includes component CMakeLists WITHOUT project_include.cmake, so the function does not exist yet and the build fails with "Unknown CMake command". That pass does not read SRCS either, so the empty variables are what it expects. The cache variables are ESPP_-prefixed now that they are the component's interface rather than one example's local detail. Verified on esp-idf v6.1 / esp32: clean build, a model edit reaching the compiled source without a clean, the documented ESPP_HFSM_GEN_COMMAND override, and both error paths (a model that is not there, a misspelled argument) reporting which call was wrong. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01E4hRq1q5VGPjNn2dnkkqBy --- components/state_machine/README.md | 37 ++++- components/state_machine/example/README.md | 6 +- .../state_machine/example/main/CMakeLists.txt | 85 ++--------- .../state_machine/project_include.cmake | 136 ++++++++++++++++++ 4 files changed, 190 insertions(+), 74 deletions(-) create mode 100644 components/state_machine/project_include.cmake diff --git a/components/state_machine/README.md b/components/state_machine/README.md index b2d8de3935..4e37745387 100644 --- a/components/state_machine/README.md +++ b/components/state_machine/README.md @@ -25,9 +25,40 @@ npx -y -p webgme-hfsm@^1.8.0 hfsm-gen my_machine.json -o generated (`-y` skips npx's install prompt for non-interactive/CI use; the `@^1.8.0` pin matches the example's CMake so generation is reproducible.) -The example wires that into its CMake, so its C++ is generated at build -time from [`Complex.json`](example/main/Complex.json) rather than -checked in — see [the example README](example/README.md). +### Generating from your own CMake + +This component provides `espp_generate_hfsm()`, so any component can turn a +model into C++ at configure time: + +```cmake +if(NOT CMAKE_BUILD_EARLY_EXPANSION) + espp_generate_hfsm( + MODEL "${CMAKE_CURRENT_LIST_DIR}/my_machine.json" + OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/hfsm" + SOURCES_VAR hfsm_srcs + INCLUDE_DIR_VAR hfsm_inc) +endif() + +idf_component_register(SRCS "main.cpp" ${hfsm_srcs} + INCLUDE_DIRS "." ${hfsm_inc}) +``` + +The guard is needed because ESP-IDF does not load a component's +`project_include.cmake` during its early requirements pass — and that pass +does not read `SRCS` either, so the empty variables are what it expects. + +It regenerates whenever the model changes, finds the generated file names +rather than making you name them, and leaves out the shared runtime this +component already provides (pass `WITH_SUPPORT` if you want the generator's +copies instead). `NAMESPACE` overrides the model's own. + +Requires **node >= 18**; `npx` fetches the generator. Set +`-DESPP_HFSM_GEN_COMMAND="node;/path/to/webgme-hfsm/bin/hfsm-gen.js"` to use a +local checkout, which is also how to build offline. + +The example uses exactly this, so its C++ is generated at build time from +[`Complex.json`](example/main/Complex.json) rather than checked in — see +[the example README](example/README.md). [repo]: https://github.com/finger563/webgme-hfsm [playground]: https://finger563.github.io/webgme-hfsm/ diff --git a/components/state_machine/example/README.md b/components/state_machine/example/README.md index 1e9671723b..2c4258cedb 100644 --- a/components/state_machine/example/README.md +++ b/components/state_machine/example/README.md @@ -14,6 +14,10 @@ The HFSM's C++ is generated from [`main/Complex.json`](main/Complex.json) every time the example is configured. The model is the source; the C++ is a build product, like an object file. +The generation is one call to `espp_generate_hfsm()`, provided by the +`state_machine` component — see its README to use it from your own +component. + This needs **node (>= 18)** on your PATH — nothing else, and nothing to install by hand. `npx` fetches the generator on demand: @@ -41,7 +45,7 @@ If you are working on the generator itself, point the build at your checkout instead of npx: ``` -idf.py -DHFSM_GEN_COMMAND="node;/path/to/webgme-hfsm/bin/hfsm-gen.js" build +idf.py -DESPP_HFSM_GEN_COMMAND="node;/path/to/webgme-hfsm/bin/hfsm-gen.js" build ``` The same flag is the way to build without network access. diff --git a/components/state_machine/example/main/CMakeLists.txt b/components/state_machine/example/main/CMakeLists.txt index 31e6fc2d4b..db2a334ba1 100644 --- a/components/state_machine/example/main/CMakeLists.txt +++ b/components/state_machine/example/main/CMakeLists.txt @@ -1,74 +1,19 @@ -# The HFSM's C++ is GENERATED from Complex.json, not checked in. +# The HFSM's C++ is GENERATED from Complex.json, not checked in: the +# model is the source, the C++ is a build product. # -# The model is the source; the C++ is a build product, the same as an -# object file. Keeping both in git meant they could disagree, and they -# did: the committed copy was generated in 2023 and had drifted from -# what the generator produces by roughly two thousand lines. -# -# Requires node (>= 18). Everything else comes from npx on demand. - -set(HFSM_MODEL "${CMAKE_CURRENT_LIST_DIR}/Complex.json") - -# Pinned to a minor range so a build is reproducible but picks up -# fixes. 1.8.0 is the first release the CLI can be installed from at -# all, and the first with --no-support. -set(HFSM_GEN_SPEC "webgme-hfsm@^1.8.0" - CACHE STRING "npm/npx spec for the webgme-hfsm code generator") - -# Set this to skip npx entirely -- e.g. a local checkout while working -# on the generator: -# idf.py -DHFSM_GEN_COMMAND="node;/path/to/webgme-hfsm/bin/hfsm-gen.js" build -set(HFSM_GEN_COMMAND "" CACHE STRING - "Override the generator invocation (a ;-separated command)") - -# Re-run cmake when the model changes, so editing it regenerates the -# C++ on the next build rather than on the next clean. -set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${HFSM_MODEL}") - -set(HFSM_DIR "${CMAKE_CURRENT_BINARY_DIR}/hfsm") - -if(HFSM_GEN_COMMAND) - set(_hfsm_cmd ${HFSM_GEN_COMMAND}) -else() - find_program(NPX_EXECUTABLE npx) - if(NOT NPX_EXECUTABLE) - message(FATAL_ERROR - "This example generates its state machine from ${HFSM_MODEL} and needs " - "node (>= 18) with npx on PATH.\n" - " Install node: https://nodejs.org\n" - " Or point at a local generator checkout:\n" - " idf.py -DHFSM_GEN_COMMAND=\"node;/path/to/webgme-hfsm/bin/hfsm-gen.js\" build") - endif() - set(_hfsm_cmd "${NPX_EXECUTABLE}" -y -p "${HFSM_GEN_SPEC}" hfsm-gen) -endif() - -# --no-support: generate THIS machine and not the shared runtime. -# -# The generator otherwise also emits state_base.hpp, -# deep_history_state.hpp, shallow_history_state.hpp and magic_enum.hpp, -# which are the same for every machine. This component already provides -# them -- espp's are the ones the rest of the codebase is built against, -# and its magic_enum is newer than the generator's -- so a second copy -# beside the generated machine would land on the include path and -# shadow them. -# Clear stale artifacts first: the generator only overwrites the files it emits, -# so a machine renamed or removed in the model would otherwise leave an obsolete -# generated .cpp behind in the (persistent) binary dir for CMake to keep -# compiling. Wipe + recreate the output dir so a regeneration cannot succeed -# against stale output. -file(REMOVE_RECURSE "${HFSM_DIR}") -file(MAKE_DIRECTORY "${HFSM_DIR}") - -message(STATUS "hfsm: generating C++ from ${HFSM_MODEL}") -execute_process( - COMMAND ${_hfsm_cmd} "${HFSM_MODEL}" -o "${HFSM_DIR}" --no-support - RESULT_VARIABLE _hfsm_result - OUTPUT_VARIABLE _hfsm_out - ERROR_VARIABLE _hfsm_err) -if(NOT _hfsm_result EQUAL 0) - message(FATAL_ERROR "hfsm generation failed:\n${_hfsm_out}${_hfsm_err}") +# espp_generate_hfsm comes from the state_machine component's +# project_include.cmake. ESP-IDF does not load those during its early +# requirements pass, so the call is guarded -- SRCS and INCLUDE_DIRS +# are not read in that pass either, and the empty variables below are +# what it expects to see. +if(NOT CMAKE_BUILD_EARLY_EXPANSION) + espp_generate_hfsm( + MODEL "${CMAKE_CURRENT_LIST_DIR}/Complex.json" + OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/hfsm" + SOURCES_VAR hfsm_srcs + INCLUDE_DIR_VAR hfsm_inc) endif() idf_component_register( - SRCS "hfsm_example.cpp" "${HFSM_DIR}/Complex_generated_states.cpp" - INCLUDE_DIRS "." "${HFSM_DIR}") + SRCS "hfsm_example.cpp" ${hfsm_srcs} + INCLUDE_DIRS "." ${hfsm_inc}) diff --git a/components/state_machine/project_include.cmake b/components/state_machine/project_include.cmake new file mode 100644 index 0000000000..f6a323a69d --- /dev/null +++ b/components/state_machine/project_include.cmake @@ -0,0 +1,136 @@ +# espp_generate_hfsm() -- turn an HFSM model into C++ at configure time. +# +# Included by the ESP-IDF build system before any component's +# CMakeLists is processed, so every component can call it. +# +# The model is the source and the C++ is a build product, like an +# object file. Checking generated code in lets the two disagree, and +# they do: this component's example carried a copy generated in 2023 +# that had drifted from the generator by roughly two thousand lines. +# +# Requires node (>= 18). The generator itself comes from npx on demand. +# +# espp_generate_hfsm( +# MODEL # required +# OUTPUT_DIR # required, wiped each configure +# [SOURCES_VAR ] # -> generated .cpp files +# [INCLUDE_DIR_VAR ] # -> dir for INCLUDE_DIRS +# [NAMESPACE ] # -n, else the model's own +# [WITH_SUPPORT] # also emit the shared runtime +# ) +# +# Typical use, from a component's CMakeLists.txt: +# +# espp_generate_hfsm( +# MODEL "${CMAKE_CURRENT_LIST_DIR}/Complex.json" +# OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/hfsm" +# SOURCES_VAR hfsm_srcs +# INCLUDE_DIR_VAR hfsm_inc) +# +# idf_component_register(SRCS "main.cpp" ${hfsm_srcs} +# INCLUDE_DIRS "." ${hfsm_inc}) + +# Pinned to a minor range: reproducible enough for a build that +# regenerates from a checked-in model, and picks up generator fixes +# without a commit here. 1.8.0 is the first release the CLI can be +# installed from at all, and the first with --no-support. +set(ESPP_HFSM_GEN_SPEC "webgme-hfsm@^1.8.0" + CACHE STRING "npm/npx spec for the webgme-hfsm code generator") + +# Skip npx entirely -- a local checkout while working on the +# generator, and the way to build with no network: +# idf.py -DESPP_HFSM_GEN_COMMAND="node;/path/to/webgme-hfsm/bin/hfsm-gen.js" build +set(ESPP_HFSM_GEN_COMMAND "" CACHE STRING + "Override the generator invocation (a ;-separated command)") + +function(espp_generate_hfsm) + cmake_parse_arguments(HFSM + "WITH_SUPPORT" + "MODEL;OUTPUT_DIR;SOURCES_VAR;INCLUDE_DIR_VAR;NAMESPACE" + "" + ${ARGN}) + + if(HFSM_UNPARSED_ARGUMENTS) + message(FATAL_ERROR + "espp_generate_hfsm: unknown argument(s): ${HFSM_UNPARSED_ARGUMENTS}") + endif() + if(NOT HFSM_MODEL) + message(FATAL_ERROR "espp_generate_hfsm: MODEL is required") + endif() + if(NOT HFSM_OUTPUT_DIR) + message(FATAL_ERROR "espp_generate_hfsm: OUTPUT_DIR is required") + endif() + if(NOT EXISTS "${HFSM_MODEL}") + message(FATAL_ERROR "espp_generate_hfsm: no such model: ${HFSM_MODEL}") + endif() + + # Re-run cmake when the model changes, so editing it regenerates on + # the next build rather than the next clean. A function does not open + # a directory scope, so this lands on the caller's directory. + set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${HFSM_MODEL}") + + if(ESPP_HFSM_GEN_COMMAND) + set(_cmd ${ESPP_HFSM_GEN_COMMAND}) + else() + find_program(ESPP_NPX_EXECUTABLE npx) + if(NOT ESPP_NPX_EXECUTABLE) + message(FATAL_ERROR + "espp_generate_hfsm needs node (>= 18) with npx on PATH to generate " + "${HFSM_MODEL}.\n" + " Install node: https://nodejs.org\n" + " Or point at a local generator checkout:\n" + " idf.py -DESPP_HFSM_GEN_COMMAND=\"node;/path/to/webgme-hfsm/bin/hfsm-gen.js\" build") + endif() + # --yes so npx never stops to ask whether to install the package, + # which in CI would hang rather than fail + set(_cmd "${ESPP_NPX_EXECUTABLE}" --yes -p "${ESPP_HFSM_GEN_SPEC}" hfsm-gen) + endif() + + set(_args "${HFSM_MODEL}" -o "${HFSM_OUTPUT_DIR}") + if(NOT HFSM_WITH_SUPPORT) + # Generate THIS machine and not the shared runtime: state_base.hpp, + # the history states and magic_enum.hpp are the same for every + # machine, and this component already provides them. espp's are the + # ones the rest of the codebase is built against -- and its + # magic_enum is newer than the generator's -- so a second copy + # beside the generated machine would shadow them on the include + # path. + list(APPEND _args --no-support) + endif() + if(HFSM_NAMESPACE) + list(APPEND _args -n "${HFSM_NAMESPACE}") + endif() + + # Clear stale artifacts first. The generator only overwrites what it + # emits, so a machine renamed or removed in the model would leave an + # obsolete .cpp behind in the (persistent) binary dir for CMake to go + # on compiling. + file(REMOVE_RECURSE "${HFSM_OUTPUT_DIR}") + file(MAKE_DIRECTORY "${HFSM_OUTPUT_DIR}") + + message(STATUS "hfsm: generating C++ from ${HFSM_MODEL}") + execute_process( + COMMAND ${_cmd} ${_args} + RESULT_VARIABLE _result + OUTPUT_VARIABLE _out + ERROR_VARIABLE _err) + if(NOT _result EQUAL 0) + message(FATAL_ERROR "hfsm generation failed for ${HFSM_MODEL}:\n${_out}${_err}") + endif() + + # Found rather than assumed: the file names come from the machine's + # name in the model, so a caller would otherwise have to know what + # its own model is called and keep that in step by hand. + file(GLOB _sources "${HFSM_OUTPUT_DIR}/*.cpp") + if(NOT _sources) + message(FATAL_ERROR + "hfsm: ${HFSM_MODEL} generated no .cpp -- does it contain a State Machine?") + endif() + + if(HFSM_SOURCES_VAR) + set(${HFSM_SOURCES_VAR} "${_sources}" PARENT_SCOPE) + endif() + if(HFSM_INCLUDE_DIR_VAR) + set(${HFSM_INCLUDE_DIR_VAR} "${HFSM_OUTPUT_DIR}" PARENT_SCOPE) + endif() +endfunction() From d7c2478b12e6f7dd98dc10ca763802b277ab5a01 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 5 Sep 2026 15:12:18 +0000 Subject: [PATCH 6/8] Sort generated HFSM sources Co-authored-by: finger563 <213467+finger563@users.noreply.github.com> --- components/state_machine/project_include.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/components/state_machine/project_include.cmake b/components/state_machine/project_include.cmake index f6a323a69d..47ef45f4d4 100644 --- a/components/state_machine/project_include.cmake +++ b/components/state_machine/project_include.cmake @@ -122,6 +122,7 @@ function(espp_generate_hfsm) # name in the model, so a caller would otherwise have to know what # its own model is called and keep that in step by hand. file(GLOB _sources "${HFSM_OUTPUT_DIR}/*.cpp") + list(SORT _sources) if(NOT _sources) message(FATAL_ERROR "hfsm: ${HFSM_MODEL} generated no .cpp -- does it contain a State Machine?") From 9aa95ece64bb46baf4e58b869b229670130add6c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 5 Sep 2026 18:58:55 +0000 Subject: [PATCH 7/8] Harden HFSM generation and Node setup Co-authored-by: finger563 <213467+finger563@users.noreply.github.com> --- .github/workflows/build.yml | 13 ++++++++++- .../state_machine/project_include.cmake | 22 ++++++++++++++----- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6bc41c0669..4c2b911723 100755 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -302,7 +302,18 @@ jobs: # at configure time, and the esp-idf image has no node command: >- node_major=$(node -v 2>/dev/null | sed -E 's/^v([0-9]+).*/\1/'); - if [ "${node_major:-0}" -lt 18 ]; then apt-get update && apt-get install -y curl && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && apt-get install -y nodejs; fi && + if [ "${node_major:-0}" -lt 18 ]; then + apt-get update && + apt-get install -y ca-certificates curl gnupg && + install -d -m 0755 /etc/apt/keyrings && + curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | + gpg --dearmor --yes -o /etc/apt/keyrings/nodesource.gpg && + chmod a+r /etc/apt/keyrings/nodesource.gpg && + printf '%s\n' 'deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main' | + tee /etc/apt/sources.list.d/nodesource.list >/dev/null && + apt-get update && + apt-get install -y nodejs; + fi && IDF_COMPONENT_MANAGER=0 idf.py build - path: 'components/stream_frame/example' target: esp32 diff --git a/components/state_machine/project_include.cmake b/components/state_machine/project_include.cmake index 47ef45f4d4..939d59574a 100644 --- a/components/state_machine/project_include.cmake +++ b/components/state_machine/project_include.cmake @@ -64,6 +64,18 @@ function(espp_generate_hfsm) message(FATAL_ERROR "espp_generate_hfsm: no such model: ${HFSM_MODEL}") endif() + get_filename_component(_output_dir "${HFSM_OUTPUT_DIR}" ABSOLUTE + BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}") + get_filename_component(_binary_dir "${CMAKE_BINARY_DIR}" ABSOLUTE) + file(RELATIVE_PATH _output_relative "${_binary_dir}" "${_output_dir}") + if(_output_relative STREQUAL "" OR + _output_relative MATCHES "^\\.\\.(/|$)" OR + IS_ABSOLUTE "${_output_relative}") + message(FATAL_ERROR + "espp_generate_hfsm: OUTPUT_DIR must be inside CMAKE_BINARY_DIR: " + "${HFSM_OUTPUT_DIR}") + endif() + # Re-run cmake when the model changes, so editing it regenerates on # the next build rather than the next clean. A function does not open # a directory scope, so this lands on the caller's directory. @@ -86,7 +98,7 @@ function(espp_generate_hfsm) set(_cmd "${ESPP_NPX_EXECUTABLE}" --yes -p "${ESPP_HFSM_GEN_SPEC}" hfsm-gen) endif() - set(_args "${HFSM_MODEL}" -o "${HFSM_OUTPUT_DIR}") + set(_args "${HFSM_MODEL}" -o "${_output_dir}") if(NOT HFSM_WITH_SUPPORT) # Generate THIS machine and not the shared runtime: state_base.hpp, # the history states and magic_enum.hpp are the same for every @@ -105,8 +117,8 @@ function(espp_generate_hfsm) # emits, so a machine renamed or removed in the model would leave an # obsolete .cpp behind in the (persistent) binary dir for CMake to go # on compiling. - file(REMOVE_RECURSE "${HFSM_OUTPUT_DIR}") - file(MAKE_DIRECTORY "${HFSM_OUTPUT_DIR}") + file(REMOVE_RECURSE "${_output_dir}") + file(MAKE_DIRECTORY "${_output_dir}") message(STATUS "hfsm: generating C++ from ${HFSM_MODEL}") execute_process( @@ -121,7 +133,7 @@ function(espp_generate_hfsm) # Found rather than assumed: the file names come from the machine's # name in the model, so a caller would otherwise have to know what # its own model is called and keep that in step by hand. - file(GLOB _sources "${HFSM_OUTPUT_DIR}/*.cpp") + file(GLOB _sources "${_output_dir}/*.cpp") list(SORT _sources) if(NOT _sources) message(FATAL_ERROR @@ -132,6 +144,6 @@ function(espp_generate_hfsm) set(${HFSM_SOURCES_VAR} "${_sources}" PARENT_SCOPE) endif() if(HFSM_INCLUDE_DIR_VAR) - set(${HFSM_INCLUDE_DIR_VAR} "${HFSM_OUTPUT_DIR}" PARENT_SCOPE) + set(${HFSM_INCLUDE_DIR_VAR} "${_output_dir}" PARENT_SCOPE) endif() endfunction() From 910adeb7d9c8135d5411e4c6c2890ac5d3ffd067 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 5 Sep 2026 19:41:50 +0000 Subject: [PATCH 8/8] fix(state_machine): quote CI generator setup safely Co-authored-by: finger563 <213467+finger563@users.noreply.github.com> --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4c2b911723..13fe62190f 100755 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -301,7 +301,7 @@ jobs: # this example GENERATES its state machine from Complex.json # at configure time, and the esp-idf image has no node command: >- - node_major=$(node -v 2>/dev/null | sed -E 's/^v([0-9]+).*/\1/'); + node_major=$(node -v 2>/dev/null | sed -E "s/^v([0-9]+).*/\1/"); if [ "${node_major:-0}" -lt 18 ]; then apt-get update && apt-get install -y ca-certificates curl gnupg && @@ -309,7 +309,7 @@ jobs: curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor --yes -o /etc/apt/keyrings/nodesource.gpg && chmod a+r /etc/apt/keyrings/nodesource.gpg && - printf '%s\n' 'deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main' | + printf "%s\n" "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list >/dev/null && apt-get update && apt-get install -y nodejs;