From 2fa054d88f04f8b4fb057d7640268ede3c9fb046 Mon Sep 17 00:00:00 2001 From: Mats Nilsson Date: Thu, 13 Feb 2025 10:52:57 +0100 Subject: [PATCH] Create plantuml from StateMachine --- atos/modules/ObjectControl/CMakeLists.txt | 10 + .../ObjectControl/inc/statemachine.hpp | 2 + .../ObjectControl/src/dump_state_machine.cpp | 368 ++++++++++++++++++ 3 files changed, 380 insertions(+) create mode 100644 atos/modules/ObjectControl/src/dump_state_machine.cpp diff --git a/atos/modules/ObjectControl/CMakeLists.txt b/atos/modules/ObjectControl/CMakeLists.txt index a2b90c7f1..4acc9c0e4 100644 --- a/atos/modules/ObjectControl/CMakeLists.txt +++ b/atos/modules/ObjectControl/CMakeLists.txt @@ -85,3 +85,13 @@ install(TARGETS ${OBJECT_CONTROL_TARGET} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ) + +add_executable(dump_state_machine + ${CMAKE_CURRENT_SOURCE_DIR}/src/dump_state_machine.cpp +) +target_link_libraries(dump_state_machine + ${ATOS_COMMON_LIBRARY} +) +target_include_directories(dump_state_machine PUBLIC SYSTEM + ${CMAKE_CURRENT_SOURCE_DIR}/inc +) diff --git a/atos/modules/ObjectControl/inc/statemachine.hpp b/atos/modules/ObjectControl/inc/statemachine.hpp index 2bd3e6edf..0281cf0e7 100644 --- a/atos/modules/ObjectControl/inc/statemachine.hpp +++ b/atos/modules/ObjectControl/inc/statemachine.hpp @@ -10,7 +10,9 @@ #include #include #include "roschannels/objstatechangechannel.hpp" +#include "roschannels/commandchannels.hpp" #include "roschannels/statechange.hpp" +#include "util.h" class ObjectControl; diff --git a/atos/modules/ObjectControl/src/dump_state_machine.cpp b/atos/modules/ObjectControl/src/dump_state_machine.cpp new file mode 100644 index 000000000..1705b7a73 --- /dev/null +++ b/atos/modules/ObjectControl/src/dump_state_machine.cpp @@ -0,0 +1,368 @@ +// +// Copyright (c) 2016-2020 Kris Jusiak (kris at jusiak dot net) +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#include "statemachine.hpp" + +namespace sml = boost::sml; + +inline void do_indent(std::ostream& out, unsigned int indent) { out << std::string(indent, ' '); } + +// use this to track initialization for orthogonal states +bool state_initialized = false; // NOLINT(misc-definitions-in-headers) +// use this to track which submachines have already been dumped so they don't get dumped twice +std::vector completed_submachines; // NOLINT(misc-definitions-in-headers) + +/** allows for checking if the type is sml::front::seq_ + * This type is used by sml when there are lists of actions. + */ +template +struct is_seq_ : sml::aux::false_type {}; // NOLINT(readability-identifier-naming) +template +struct is_seq_> : sml::aux::true_type {}; // NOLINT(readability-identifier-naming) + +/** allows for checking if the type is sml::front::not_ + * This type is used by sml inside of guards, when the guard value is negated with ! + * + * The partial specialization matches if the type passed in is sml::front::not_, causing the struct to + * inherit from sml::aux::true_type, which gives it a member called "value" that is set to true. + * If the type passed doesn't match sml::front::not_, it'll match the generic is_not_ which inherits + * from sml::aux::false_type, giving it a member called "value" that is set to false. + */ +template +struct is_not_ : sml::aux::false_type {}; // NOLINT(readability-identifier-naming) +template +struct is_not_> : sml::aux::true_type {}; // NOLINT(readability-identifier-naming) + +/** provides access to the template parameter type of an sml::front::not_ + */ +template +struct strip_not_ { + using type = T; +}; // NOLINT(readability-identifier-naming) +template +struct strip_not_> { + using type = T; +}; // NOLINT(readability-identifier-naming) + +/** allows for checking if the type is sml::front::and_ + * This type is used by sml inside of guards when two guard functions are combined with && + */ +template +struct is_and_ : sml::aux::false_type {}; // NOLINT(readability-identifier-naming) +template +struct is_and_> : sml::aux::true_type {}; // NOLINT(readability-identifier-naming) + +/** allows for checking if the type is sml::front::or_ + * This type is used by sml inside of guards when two guard functions are combined with || + */ +template +struct is_or_ : sml::aux::false_type {}; // NOLINT(readability-identifier-naming) +template +struct is_or_> : sml::aux::true_type {}; // NOLINT(readability-identifier-naming) + +/** uses std::tuple_element and std::tuple to access the Nth type in a parameter pack + */ +template +using NthTypeOf = typename std::tuple_element>::type; + +/** gets the size of a parameter pack + * this isn't really necessary, sizeof...(Ts) can be used directly instead + */ +template +struct count { // NOLINT(readability-identifier-naming) + static const std::size_t value = sizeof...(Ts); // NOLINT(readability-identifier-naming) +}; + +/** allows for checking if the type is sml::aux::zero_wrapper + * sml puts this around types inside of guards and event sequences + */ +template +struct is_zero_wrapper : sml::aux::false_type {}; // NOLINT(readability-identifier-naming) +template +struct is_zero_wrapper> : sml::aux::true_type {}; // NOLINT(readability-identifier-naming) + +/** if T is a zero wrapper, ::type will be the inner type. if not, it will be T. + */ +template +struct strip_zero_wrapper { + using type = T; +}; // NOLINT(readability-identifier-naming) +template +struct strip_zero_wrapper> { + using type = T; +}; // NOLINT(readability-identifier-naming) + +/** accesses the type of a state-machine, sml::back::sm + */ +template +struct submachine_type { + using type = T; +}; // NOLINT(readability-identifier-naming) +template +struct submachine_type> { + using type = typename T::sm; +}; // NOLINT(readability-identifier-naming) + +/** print the types inside a sml::front::seq_ + * These types came from a list of actions. + */ +template +struct print_seq_types { // NOLINT(readability-identifier-naming) + template + static void func(std::ostream& out) { + constexpr auto param_pack_empty = (sizeof...(Ts) == I); + if constexpr (!param_pack_empty) { // NOLINT(readability-braces-around-statements,bugprone-suspicious-semicolon) + using current_type = NthTypeOf; + if constexpr (is_seq_::value) { // NOLINT(readability-braces-around-statements) + // handle nested seq_ types, these happen when there are 3 or more actions + print_seq_types::template func<0>(out); + } else { // NOLINT(readability-misleading-indentation) + // print this param directly + out << sml::aux::string::type>{}.c_str(); + } + if constexpr (I + 1 < sizeof...(Ts)) { // NOLINT(readability-braces-around-statements,bugprone-suspicious-semicolon) + out << ",\\n "; + } + print_seq_types::template func(out); + } + } +}; +template +struct print_seq_types> { // NOLINT(readability-identifier-naming) + template + static void func(std::ostream& out) { + print_seq_types::template func<0>(out); + } +}; + +/** print the types inside a guard + * These can be a functor, an sml::front::not_, an sml::front::and_, or an sml::front::or_ which makes + * this one more complicated. They also involve the zero_wrapper. + * The various partial specializations handle all of the possible types. + */ +template +struct print_guard { // NOLINT(readability-identifier-naming) + template + static void func(std::ostream& out, const std::string& sep = "") { + constexpr auto param_pack_empty = (sizeof...(Ts) == I); + if constexpr (!param_pack_empty) { // NOLINT(readability-braces-around-statements,bugprone-suspicious-semicolon) + using current_type = NthTypeOf; + if constexpr (is_zero_wrapper< + current_type>::value) { // NOLINT(readability-braces-around-statements,bugprone-suspicious-semicolon) + // unwrap the zero_wrapper and put it back into the recursion, it could be anything + print_guard::type>::template func<0>(out); + } else { // NOLINT(readability-misleading-indentation) + // it's just a functor, print it + out << sml::aux::string{}.c_str(); + } + + // if we're not at the end, print the separator + if constexpr (I + 1 < sizeof...(Ts)) { // NOLINT(readability-braces-around-statements,bugprone-suspicious-semicolon) + if (!sep.empty()) { + out << sep; + } + } + + // keep the recursion going, call for the next type in the parameter pack + print_guard::template func(out, sep); + } + } +}; +template +struct print_guard> { // NOLINT(readability-identifier-naming) + template + static void func(std::ostream& out, const std::string& /*sep*/ = "") { + out << "!" << sml::aux::string::type>{}.c_str(); + } +}; +template +struct print_guard> { // NOLINT(readability-identifier-naming) + template + static void func(std::ostream& out, const std::string& /*sep*/ = "") { + constexpr auto param_pack_empty = (sizeof...(Ts) == I); + if constexpr (!param_pack_empty) { + print_guard::template func(out, " &&\\n "); + } + } +}; +template +struct print_guard> { // NOLINT(readability-identifier-naming) + template + static void func(std::ostream& out, const std::string& /*sep*/ = "") { + constexpr auto param_pack_empty = (sizeof...(Ts) == I); + if constexpr (!param_pack_empty) { + print_guard::template func(out, " ||\\n "); + } + } +}; + +// forward declaration of dump_transitions +template +struct dump_transitions; + +template +void dump_transition(std::ostream& out) noexcept { + constexpr auto src_is_sub_sm = + !sml::aux::is_same, sml::back::get_sub_sms>::value; + constexpr auto dst_is_sub_sm = + !sml::aux::is_same, sml::back::get_sub_sms>::value; + + std::string src_state, dst_state; + // NOLINTNEXTLINE(readability-braces-around-statements) + if constexpr (src_is_sub_sm) { + src_state = std::string{sml::aux::string::type>{}.c_str()}; + } else { // NOLINT(readability-misleading-indentation) + src_state = std::string{sml::aux::string{}.c_str()}; + } + + // NOLINTNEXTLINE(readability-braces-around-statements) + if constexpr (dst_is_sub_sm) { + dst_state = std::string{sml::aux::string::type>{}.c_str()}; + } else { // NOLINT(readability-misleading-indentation) + dst_state = std::string{sml::aux::string{}.c_str()}; + } + + const auto dst_internal = sml::aux::is_same::value; + + const auto has_event = !sml::aux::is_same::value; + const auto has_guard = !sml::aux::is_same::value; + const auto has_action = !sml::aux::is_same::value; + + if (has_event && has_action && sml::aux::is_same::value) { + do_indent(out, N); + out << src_state << " : " << boost::sml::aux::get_type_name() << " / defer\n"; + return; + } + + if (dst_state == "terminate") { + dst_state = "[*]"; + } + + if (T::initial) { + if (state_initialized) { // create an orthogonal section + do_indent(out, N); + out << "--\n"; + } + + state_initialized = true; + do_indent(out, N); + out << "[*] --> " << src_state << "\n"; + } + + // NOLINTNEXTLINE(readability-braces-around-statements, bugprone-suspicious-semicolon) + if constexpr (src_is_sub_sm) { + auto already_in = + std::find(completed_submachines.begin(), completed_submachines.end(), src_state) != completed_submachines.end(); + if (!already_in) { + completed_submachines.push_back(src_state); + constexpr int indent = N + 2; + do_indent(out, N); + out << "state " << src_state << " {\n"; + bool prev_state = state_initialized; + state_initialized = false; + dump_transitions::template func(out); + do_indent(out, N); + out << "}\n"; + state_initialized = prev_state; + } + } + + do_indent(out, N); + out << src_state; + if (!dst_internal) { + out << " --> " << dst_state; + } + + if (has_event || has_guard || has_action) { + out << " :"; + } + + if (has_event) { + out << " " << std::string{sml::aux::string{}.c_str()}; + } + + if (has_guard) { + out << "\\n ["; + print_guard::template func<0>(out); + out << "]"; + } + + if (has_action) { + out << " /\\n "; + + if constexpr (is_seq_::value) { // NOLINT(readability-braces-around-statements) + out << "("; + print_seq_types::template func<0>(out); + out << ")"; + } else { // NOLINT(readability-misleading-indentation) + out << sml::aux::string{}.c_str(); + } + } + + out << "\n"; + + // NOLINTNEXTLINE(readability-braces-around-statements, bugprone-suspicious-semicolon) + if constexpr (dst_is_sub_sm) { + auto already_in = + std::find(completed_submachines.begin(), completed_submachines.end(), dst_state) != completed_submachines.end(); + if (!already_in) { + completed_submachines.push_back(dst_state); + constexpr int indent = N + 2; + do_indent(out, N); + out << "state " << dst_state << " {\n"; + bool prev_state = state_initialized; + state_initialized = false; + dump_transitions::template func(out); + do_indent(out, N); + out << "}\n"; + state_initialized = prev_state; + } + } +} + +// this template allows iterating through the types in the parameter pack Ts... +// I is the counter +// INDENT is the current indentation level (for the state machine or sub-state machine) +template +void apply_dump_transition(std::ostream& out) { + // iteration is finished when I == the size of the parameter pack + constexpr auto param_pack_empty = (sizeof...(Ts) == I); + if constexpr (!param_pack_empty) { // NOLINT(readability-braces-around-statements,bugprone-suspicious-semicolon) + // run the dump_transition function to print this sml::front::transition type + dump_transition>(out); + // iteration isn't finished, keep going + apply_dump_transition(out); + } +} + +// SFINAE type +template +struct dump_transitions { // NOLINT(readability-identifier-naming) + template + static void func(std::ostream&) {} +}; + +// Partial specialization for sml::aux::type_list. This grants access to the +// types inside the type list, which are sml::front::transition types, so they can +// be passed to apply_dump_transition. +template +struct dump_transitions> { // NOLINT(readability-identifier-naming) + template + static void func(std::ostream& out) { + apply_dump_transition(out); + } +}; + +template +void dump(std::ostream& out) noexcept { + out << "@startuml\n\n"; + dump_transitions::transitions>::template func<0>(out); + out << "\n@enduml\n"; +} + +int main() { dump(std::cout); }