From d3affac417827a9d2bef270c3430dcf334cd9d00 Mon Sep 17 00:00:00 2001 From: John Wellbelove Date: Thu, 12 Mar 2026 17:06:26 +0000 Subject: [PATCH] Enforce o(log n) dispatch for messages when using fsm for c++11 and up (#1337) * Updated message handling to be worst case O(logN) * Copied optimised message handling from etl::fsm * Updated fsm generator * Updated message_router generator * Added optimised accepts() member function * Modified comment, as the FSM doesn't support a successor * Updated version and release notes * Hotfix/etl multiset iterator invalidation during erase leads to incorrect sorted order in depth first traversal (#1317) * Fixed issue for both multiset and multimap * Added std::is_sorted checks to all map/set tests * Updated with coderabbit suggestions --------- Co-authored-by: John Wellbelove * Updated release notes and version * Changed std::is_same to etl::is_same in struct type_list_is_unique (#1320) Co-authored-by: John Wellbelove * Updated release notes and version * Fix etl::rotate (#1327) Per the C++ standard, std::rotate returns first + (last - middle): * When first == middle, return last * When middle == last, return first * Added missing files from VS2022 project * Fix greater_equal and less_equal (#1331) * Align comparison operators (#1330) In functional.h, the comparison operators for equal_to and not_equal_to mismatch between the actual comparison execution and the type inference for the return type. This change adjusts it by using the same operator==() in the return type inference as used in the comparison execution. Co-authored-by: John Wellbelove * Add missing tests (#1321) * Add missing tests * Typo fixes --------- Co-authored-by: John Wellbelove * Add ETL_FORMAT_NO_FLOATING_POINT control macro for etl::format (#1329) When ETL_FORMAT_NO_FLOATING_POINT is defined, all floating-point formatting support (float, double, long double) is excluded from etl::format. This reduces code size on targets that do not require floating-point formatting. Guarded sections: - #include - float/double/long double in supported_format_types variant - float/double/long double constructors in basic_format_arg - format_floating_* functions and format_aligned_floating - formatter, formatter, formatter - Floating-point test cases in test_format.cpp Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus Co-authored-by: John Wellbelove * Manchester documentation (#1325) * manchester * Added manchester code and test * manchester * Formatting and added missing file * manchester * Some functions can only be constexpr since C++14 * manchester * Manchester decode and some refactoring * manchester * Added some missing typenames * manchester * constexpr void function not allowed in C++11 * manchester * condition on static_assert tests * manchester * revert CMakeLists.txt * Using ETL_STATIC_ASSERT * Some cleanup * manchester * Added static_assert message * manchester * Added compile time tests * manchester * Added invert manchester * Some refactoring * manchester * Disable test for now * Move ETL_NODISCARD before static * manchester * Test for valid_span * manchester * Remove redundant (?) storage specifiers for template specializations. Storage specifier already given in base template * manchester * refactoring to get rid of specialized template functions in template class * manchester * cleanup * manchester * Added documentation comments * Some refactoring * manchester * introducing namespace detail_manchester * manchester * Some refactoring * Update tests * manchester * Some refactoring * Removed possible undefined behavior by refactoring encode_span * constexpr version of encode_span * Static assertion for rare case where code doesn't work because CHAR_BIT is not the same as the number of bits in uint_least8_t * manchester * renamed valid to is_valid * manchester * renamed is_valid_span to is_valid * Using etl exceptions in ETL_ASSERT * manchester * Removed _fast functions * merged encode_in_place with encode and decode_in_place with decode * removed _span to create normal overloads of encode and decode for span * Some renaming and minor refactoring * manchester * Fix build issues * manchester * Conditionally compile manchester_decoded * Update test_manchester.cpp Removed redundant semicolon * #1258 Manchester coding * Formatting * consistency: hex literals with lower case 0x * #1258 Manchester coding * Moved copyright to top of file * Make constexpr encode/decode span functions equal for little and big endian platforms * #1258 Manchester coding * Added missing include * Added missing 8bit/64bit guards * Fixed is_valid for big endian platforms * #1258 Manchester coding * private memcpy alias * #1258 Manchester coding * Review comments * #1258 Manchester coding * Cleanup * Fix build error * #1258 Manchester coding * Add manchester documentation * #1258 Manchester coding * Preparation for GitHub pages * #1324 Manchester documentation * Some small fixes --------- Co-authored-by: Timon Zijnge * Changes from review of algorithm.h on development branch (#1340) * Add missing constexpr in algorithm.h * Fix call of nth_element 2nd argument (nth) was missing * Replace partition point with O(log(N)) algorithm The C++ standard defines O(log(N)) calls of predicate as the complexity of partition_point(). The old algorithm was linear. * Use predicate in calculation of is_permutation consistently In case of predicate not equal_to, the calculation previously returned wron results * Omit swap in selection_sort if iterators are equal * Use difference_type in rotate_general() instead of int * Typo fix in algorithm.h * Simplifications in algorithm.h Application of plain refactoring by keeping semantics * Guard against past-end iterator in etl::rotate() And fix scope of rotate_right_by_one for etl::rotate() * Support empty ranges in selection_sort * Add tests for swap_ranges * Add tests for binary_search * Add tests for find_end * Add tests for accumulate * Add tests for move_s * Added tests for is_heap and sort_heap * Remove early exit for empty input * Add adjacent_find * Add unique * Add unique_copy * Add merge * Add inplace_merge * Add partial_sort * Add partial_sort_copy * copilot review change --------- Co-authored-by: John Wellbelove Co-authored-by: Roland Reichwein Co-authored-by: Niu Zhihong Co-authored-by: Sisyphus Co-authored-by: Timon Zijnge <47081647+tzijnge@users.noreply.github.com> Co-authored-by: Timon Zijnge --- include/etl/fsm.h | 410 +++++++++++++----- include/etl/generators/fsm_generator.h | 376 ++++++++++++---- .../etl/generators/message_router_generator.h | 54 +-- include/etl/message_router.h | 54 +-- test/test_fsm.cpp | 4 +- 5 files changed, 633 insertions(+), 265 deletions(-) diff --git a/include/etl/fsm.h b/include/etl/fsm.h index 36130a47..a00206e7 100644 --- a/include/etl/fsm.h +++ b/include/etl/fsm.h @@ -62,6 +62,7 @@ SOFTWARE. #include "largest.h" #if ETL_USING_CPP11 #include "tuple.h" + #include "type_list.h" #endif #include @@ -83,14 +84,14 @@ namespace etl // For internal FSM use. typedef typename etl::larger_type::type fsm_internal_id_t; -#if ETL_USING_CPP17 && !defined(ETL_FSM_FORCE_CPP03_IMPLEMENTATION) // For C++17 and above +#if ETL_USING_CPP11 && !defined(ETL_FSM_FORCE_CPP03_IMPLEMENTATION) // For C++11 and above template class fsm_state; #else template class fsm_state; #endif @@ -195,7 +196,7 @@ namespace etl // Pass this whenever no state change is desired. // The highest unsigned value of fsm_state_id_t. static ETL_CONSTANT fsm_state_id_t No_State_Change = etl::integral_limits::max; - + // Pass this when this event also needs to be passed to the parent. static ETL_CONSTANT fsm_state_id_t Pass_To_Parent = No_State_Change - 1U; @@ -213,15 +214,15 @@ namespace etl ETL_CONSTANT fsm_state_id_t ifsm_state_helper::Self_Transition; // Compile-time: TState::ID must equal its index in the type list (0..N-1) - template struct check_ids : etl::true_type + template struct check_ids : etl::true_type { }; template struct check_ids - : etl::integral_constant::value> + : etl::integral_constant::value> { - }; + }; //*************************************************************************** /// RAII detection mechanism to catch reentrant calls to methods that might @@ -250,11 +251,11 @@ namespace etl { is_locked = false; } - + private: // Reference to the flag signifying a lock on the state machine. bool& is_locked; - + // Copy & move semantics disabled since this is a guard. fsm_reentrancy_guard(fsm_reentrancy_guard const&) ETL_DELETE; fsm_reentrancy_guard& operator= (fsm_reentrancy_guard const&) ETL_DELETE; @@ -272,7 +273,7 @@ namespace etl /// A class to store FSM states. //*************************************************************************** template - class fsm_state_pack + class fsm_state_pack { public: @@ -294,18 +295,18 @@ namespace etl /// Gets a reference to the state. //********************************* template - TState& get() - { - return etl::get(storage); + TState& get() + { + return etl::get(storage); } //********************************* /// Gets a const reference to the state. //********************************* template - const TState& get() const - { - return etl::get(storage); + const TState& get() const + { + return etl::get(storage); } private: @@ -341,14 +342,14 @@ namespace etl using private_fsm::ifsm_state_helper<>::Pass_To_Parent; using private_fsm::ifsm_state_helper<>::Self_Transition; -#if ETL_USING_CPP17 && !defined(ETL_FSM_FORCE_CPP03_IMPLEMENTATION) // For C++17 and above +#if ETL_USING_CPP11 && !defined(ETL_FSM_FORCE_CPP03_IMPLEMENTATION) // For C++11 and above template friend class fsm_state; #else template friend class etl::fsm_state; #endif @@ -560,7 +561,7 @@ namespace etl { etl::fsm_state_id_t next_state_id = p_state->process_event(message); - process_state_change(next_state_id); + process_state_change(next_state_id); } else { @@ -691,7 +692,7 @@ namespace etl p_state->on_exit_state(); next_state_id = p_state->on_enter_state(); } - + if (have_changed_state(next_state_id)) { ETL_ASSERT_OR_RETURN_VALUE(next_state_id < number_of_states, ETL_ERROR(etl::fsm_state_id_exception), p_state->get_state_id()); @@ -722,17 +723,249 @@ namespace etl }; //************************************************************************************************* - // For C++17 and above. + // For C++11 and above. //************************************************************************************************* -#if ETL_USING_CPP17 && !defined(ETL_FSM_FORCE_CPP03_IMPLEMENTATION) // For C++17 and above +#if ETL_USING_CPP11 && !defined(ETL_FSM_FORCE_CPP03_IMPLEMENTATION) // For C++11 and above //*************************************************************************** // The definition for all types. //*************************************************************************** template class fsm_state : public ifsm_state { + private: + + using message_id_sequence = etl::index_sequence; + public: + using message_types = etl::type_list; + using sorted_message_types = etl::type_list_sort_t; + + static_assert(etl::type_list_is_unique::value, "All TMessageTypes must be unique"); + static_assert(etl::type_list_all_of::value, "All TMessageTypes must satisfy the condition etl::is_message_type"); + static_assert(etl::index_sequence_is_unique::value, "All message IDs must be unique"); + + static ETL_CONSTANT etl::fsm_state_id_t STATE_ID = STATE_ID_; + + fsm_state() + : ifsm_state(STATE_ID) + { + } + + protected: + + ~fsm_state() + { + } + + TContext& get_fsm_context() const + { + return static_cast(ifsm_state::get_fsm_context()); + } + + private: + + static constexpr size_t Number_Of_Messages = sizeof...(TMessageTypes); + static constexpr etl::message_id_t Message_Id_Start = etl::type_list_type_at_index_t::ID; + + static_assert(Number_Of_Messages > 0, "Zero messages"); + + //********************************************** + // Checks that the message ids are contiguous. + //********************************************** + template = Number_Of_Messages)> + struct contiguous_impl; + + template + struct contiguous_impl : etl::true_type + { + }; + + template + struct contiguous_impl + : etl::bool_constant<(etl::type_list_type_at_index_t::ID + 1U == + etl::type_list_type_at_index_t::ID) && + contiguous_impl::value> + { + + + }; + + // The message ids are contiguous if there are 0 or 1 message types, or if each message id is one greater than the previous message id. + static constexpr bool Message_Ids_Are_Contiguous = (Number_Of_Messages <= 1U) ? true : contiguous_impl<0U>::value; + + using handler_ptr = etl::fsm_state_id_t (*)(TDerived&, const etl::imessage&); ///< Pointer to a handler function that takes a reference to the derived class and a reference to the message. + using message_dispatch_table_t = etl::array; ///< The dispatch table type. An array of handler pointers, one for each message type. + using message_id_table_t = etl::array; ///< The message id table type. An array of message ids, one for each message type. + + //******************************************** + etl::fsm_state_id_t process_event(const etl::imessage& message) + { + const etl::message_id_t id = message.get_message_id(); + + // The IDs are sorted, so an ID less than the first is not handled by this router. + if (id >= Message_Id_Start) + { + const size_t index = get_dispatch_index_from_message_id(id); + + // If the index is less than Number_Of_Messages, then we have a handler for this message type, so dispatch it. + if (index < Number_Of_Messages) + { + const etl::fsm_state_id_t new_state_id = dispatch(message, index); + + if (new_state_id != Pass_To_Parent) + { + return new_state_id; + } + } + } + +#include "etl/private/diagnostic_array_bounds_push.h" + // If we get here, then we don't have a handler for this message type, so pass it to the parent if we have one, otherwise call on_event_unknown. + return (p_parent != nullptr) ? p_parent->process_event(message) : static_cast(this)->on_event_unknown(message); +#include "etl/private/diagnostic_pop.h" + } + + //********************************************** + // Call for a single message type + //********************************************** + template + static etl::fsm_state_id_t call_on_event(TDerived& derived, const imessage& msg) + { + return derived.on_event(static_cast(msg)); + } + + //********************************************** + // Get the handler for a single message type at the index in the sorted type_list. + // This will be called for each message type to generate the dispatch table. + //********************************************** + template + static constexpr handler_ptr get_message_handler() + { + return &call_on_event>; + } + + //********************************************** + // Generate the dispatch table at compile time. + // This will create an array of handler pointers, one for each message type. + //********************************************** + template + static constexpr message_dispatch_table_t make_message_dispatch_table(etl::index_sequence) + { + return message_dispatch_table_t{ { get_message_handler()... } }; + } + + //********************************************** + // Get the message id for a single message type at an index in the sorted type_list. + // This will be called for each message type to generate the message id table. + //********************************************** + template + static constexpr etl::message_id_t get_message_id_from_index() + { + return etl::type_list_type_at_index_t::ID; + } + + //********************************************** + // Generate the message id table at compile time. + // This will create an array of message ids, one for each message type. + //********************************************** + template + static constexpr message_id_table_t make_message_id_table(etl::index_sequence) + { + return message_id_table_t{ { get_message_id_from_index()... } }; + } + + //********************************************** + // Get the dispatch index for a message id. + // This will be used at runtime to find the handler for a message id. + // If the message ids are contiguous, we can calculate the index directly. If they are not contiguous, we need to do a binary search. + // This will return Number_Of_Messages if the message id is not found. + //********************************************** + static size_t get_dispatch_index_from_message_id(etl::message_id_t id) + { + if ETL_IF_CONSTEXPR(Message_Ids_Are_Contiguous) + { + // The IDs are contiguous, so we can calculate the index directly. + return static_cast(id - Message_Id_Start); + } + else + { + // The IDs are not contiguous, so we need to do a binary search. + size_t left = 0; + size_t right = Number_Of_Messages; + + while (left < right) + { + size_t mid = (left + right) / 2; + + if (message_id_table[mid] == id) + { + return mid; + } + else if (message_id_table[mid] < id) + { + left = mid + 1; + } + else + { + right = mid; + } + } + } + + return Number_Of_Messages; // Not found + } + + //********************************************** + // Dispatch the message to the appropriate handler based on the index in the dispatch table. + //********************************************** + etl::fsm_state_id_t dispatch(const etl::imessage& msg, size_t index) + { + return message_dispatch_table[index](static_cast(*this), msg); + } + + //********************************************** + // The dispatch table is generated at compile time. The dispatch table contains pointers to the on_receive handlers for each message type. + //********************************************** + static ETL_INLINE_VAR constexpr message_dispatch_table_t message_dispatch_table = + etl::fsm_state::make_message_dispatch_table(etl::make_index_sequence::Number_Of_Messages>{}); + + //********************************************** + // The message id table is generated at compile time. The message id table contains the corresponding message ids for each message type. + //********************************************** + static ETL_INLINE_VAR constexpr message_id_table_t message_id_table = + etl::fsm_state::make_message_id_table(etl::make_index_sequence::Number_Of_Messages>{}); + }; + +#if ETL_USING_CPP11 && !ETL_USING_CPP17 + template + constexpr const typename etl::fsm_state::message_dispatch_table_t + etl::fsm_state::message_dispatch_table; + + template + constexpr const typename etl::fsm_state::message_id_table_t + etl::fsm_state::message_id_table; +#endif + + /// Definition of STATE_ID + template + ETL_CONSTANT etl::fsm_state_id_t fsm_state::STATE_ID; + + //*************************************************************************** + // The definition for no messages. + //*************************************************************************** + template + class fsm_state : public ifsm_state + { + private: + + using message_id_sequence = etl::index_sequence<>; + + public: + + using message_types = etl::type_list<>; + using sorted_message_types = etl::type_list<>; + static ETL_CONSTANT etl::fsm_state_id_t STATE_ID = STATE_ID_; fsm_state() @@ -753,59 +986,24 @@ namespace etl private: - //******************************************** - struct result_t - { - bool was_handled; - etl::fsm_state_id_t state_id; - }; - //******************************************** etl::fsm_state_id_t process_event(const etl::imessage& message) { - etl::fsm_state_id_t new_state_id; - - const bool was_handled = (process_event_type(message, new_state_id) || ...); - - if (!was_handled || (new_state_id == Pass_To_Parent)) - { - new_state_id = (p_parent != nullptr) ? p_parent->process_event(message) : static_cast(this)->on_event_unknown(message); - } - - return new_state_id; - } - - //******************************************** - template - bool process_event_type(const etl::imessage& msg, etl::fsm_state_id_t& new_state_id) - { - if (TMessage::ID == msg.get_message_id()) - { - new_state_id = static_cast(this)->on_event(static_cast(msg)); - return true; - } - else - { - return false; - } + return (p_parent != nullptr) ? p_parent->process_event(message) : static_cast(this)->on_event_unknown(message); } }; - /// Definition of STATE_ID - template - ETL_CONSTANT etl::fsm_state_id_t fsm_state::STATE_ID; - #else //************************************************************************************************* -// For C++14 and below. +// For C++03 and below. //************************************************************************************************* //*************************************************************************** // The definition for all 16 message types. //*************************************************************************** - template class fsm_state : public ifsm_state { @@ -864,10 +1062,10 @@ namespace etl //*************************************************************************** // Specialisation for 15 message types. //*************************************************************************** - template class fsm_state : public ifsm_state { @@ -925,10 +1123,10 @@ namespace etl //*************************************************************************** // Specialisation for 14 message types. //*************************************************************************** - template class fsm_state : public ifsm_state { @@ -985,10 +1183,10 @@ namespace etl //*************************************************************************** // Specialisation for 13 message types. //*************************************************************************** - template class fsm_state : public ifsm_state { @@ -1044,9 +1242,9 @@ namespace etl //*************************************************************************** // Specialisation for 12 message types. //*************************************************************************** - template class fsm_state : public ifsm_state { @@ -1101,9 +1299,9 @@ namespace etl //*************************************************************************** // Specialisation for 11 message types. //*************************************************************************** - template class fsm_state : public ifsm_state { @@ -1157,9 +1355,9 @@ namespace etl //*************************************************************************** // Specialisation for 10 message types. //*************************************************************************** - template class fsm_state : public ifsm_state { @@ -1212,9 +1410,9 @@ namespace etl //*************************************************************************** // Specialisation for 9 message types. //*************************************************************************** - template class fsm_state : public ifsm_state { @@ -1266,8 +1464,8 @@ namespace etl //*************************************************************************** // Specialisation for 8 message types. //*************************************************************************** - template class fsm_state : public ifsm_state { @@ -1318,8 +1516,8 @@ namespace etl //*************************************************************************** // Specialisation for 7 message types. //*************************************************************************** - template class fsm_state : public ifsm_state { @@ -1369,8 +1567,8 @@ namespace etl //*************************************************************************** // Specialisation for 6 message types. //*************************************************************************** - template class fsm_state : public ifsm_state { @@ -1419,8 +1617,8 @@ namespace etl //*************************************************************************** // Specialisation for 5 message types. //*************************************************************************** - template class fsm_state : public ifsm_state { @@ -1468,7 +1666,7 @@ namespace etl //*************************************************************************** // Specialisation for 4 message types. //*************************************************************************** - template class fsm_state : public ifsm_state { @@ -1515,7 +1713,7 @@ namespace etl //*************************************************************************** // Specialisation for 3 message types. //*************************************************************************** - template class fsm_state : public ifsm_state { @@ -1561,7 +1759,7 @@ namespace etl //*************************************************************************** // Specialisation for 2 message types. //*************************************************************************** - template class fsm_state : public ifsm_state { @@ -1606,7 +1804,7 @@ namespace etl //*************************************************************************** // Specialisation for 1 message type. //*************************************************************************** - template class fsm_state : public ifsm_state { @@ -1680,10 +1878,10 @@ namespace etl } }; - template ETL_CONSTANT etl::fsm_state_id_t fsm_state::STATE_ID; #endif diff --git a/include/etl/generators/fsm_generator.h b/include/etl/generators/fsm_generator.h index 0887af6c..98d4c6b0 100644 --- a/include/etl/generators/fsm_generator.h +++ b/include/etl/generators/fsm_generator.h @@ -74,6 +74,7 @@ cog.outl("//******************************************************************** #include "largest.h" #if ETL_USING_CPP11 #include "tuple.h" + #include "type_list.h" #endif #include @@ -95,20 +96,20 @@ namespace etl // For internal FSM use. typedef typename etl::larger_type::type fsm_internal_id_t; -#if ETL_USING_CPP17 && !defined(ETL_FSM_FORCE_CPP03_IMPLEMENTATION) // For C++17 and above +#if ETL_USING_CPP11 && !defined(ETL_FSM_FORCE_CPP03_IMPLEMENTATION) // For C++11 and above template class fsm_state; #else /*[[[cog import cog cog.outl("template ") + cog.out(" ") + cog.outl(" typename>") cog.outl("class fsm_state;") ]]]*/ /*[[[end]]]*/ @@ -214,7 +215,7 @@ namespace etl // Pass this whenever no state change is desired. // The highest unsigned value of fsm_state_id_t. static ETL_CONSTANT fsm_state_id_t No_State_Change = etl::integral_limits::max; - + // Pass this when this event also needs to be passed to the parent. static ETL_CONSTANT fsm_state_id_t Pass_To_Parent = No_State_Change - 1U; @@ -232,15 +233,15 @@ namespace etl ETL_CONSTANT fsm_state_id_t ifsm_state_helper::Self_Transition; // Compile-time: TState::ID must equal its index in the type list (0..N-1) - template struct check_ids : etl::true_type + template struct check_ids : etl::true_type { }; template struct check_ids - : etl::integral_constant::value> + : etl::integral_constant::value> { - }; + }; //*************************************************************************** /// RAII detection mechanism to catch reentrant calls to methods that might @@ -269,11 +270,11 @@ namespace etl { is_locked = false; } - + private: // Reference to the flag signifying a lock on the state machine. bool& is_locked; - + // Copy & move semantics disabled since this is a guard. fsm_reentrancy_guard(fsm_reentrancy_guard const&) ETL_DELETE; fsm_reentrancy_guard& operator= (fsm_reentrancy_guard const&) ETL_DELETE; @@ -291,7 +292,7 @@ namespace etl /// A class to store FSM states. //*************************************************************************** template - class fsm_state_pack + class fsm_state_pack { public: @@ -313,18 +314,18 @@ namespace etl /// Gets a reference to the state. //********************************* template - TState& get() - { - return etl::get(storage); + TState& get() + { + return etl::get(storage); } //********************************* /// Gets a const reference to the state. //********************************* template - const TState& get() const - { - return etl::get(storage); + const TState& get() const + { + return etl::get(storage); } private: @@ -360,20 +361,20 @@ namespace etl using private_fsm::ifsm_state_helper<>::Pass_To_Parent; using private_fsm::ifsm_state_helper<>::Self_Transition; -#if ETL_USING_CPP17 && !defined(ETL_FSM_FORCE_CPP03_IMPLEMENTATION) // For C++17 and above +#if ETL_USING_CPP11 && !defined(ETL_FSM_FORCE_CPP03_IMPLEMENTATION) // For C++11 and above template friend class fsm_state; #else /*[[[cog import cog cog.outl(" template ") + cog.out(" ") + cog.outl(" typename>") ]]]*/ /*[[[end]]]*/ friend class etl::fsm_state; @@ -586,7 +587,7 @@ namespace etl { etl::fsm_state_id_t next_state_id = p_state->process_event(message); - process_state_change(next_state_id); + process_state_change(next_state_id); } else { @@ -717,7 +718,7 @@ namespace etl p_state->on_exit_state(); next_state_id = p_state->on_enter_state(); } - + if (have_changed_state(next_state_id)) { ETL_ASSERT_OR_RETURN_VALUE(next_state_id < number_of_states, ETL_ERROR(etl::fsm_state_id_exception), p_state->get_state_id()); @@ -748,17 +749,249 @@ namespace etl }; //************************************************************************************************* - // For C++17 and above. + // For C++11 and above. //************************************************************************************************* -#if ETL_USING_CPP17 && !defined(ETL_FSM_FORCE_CPP03_IMPLEMENTATION) // For C++17 and above +#if ETL_USING_CPP11 && !defined(ETL_FSM_FORCE_CPP03_IMPLEMENTATION) // For C++11 and above //*************************************************************************** // The definition for all types. //*************************************************************************** template class fsm_state : public ifsm_state { + private: + + using message_id_sequence = etl::index_sequence; + public: + using message_types = etl::type_list; + using sorted_message_types = etl::type_list_sort_t; + + static_assert(etl::type_list_is_unique::value, "All TMessageTypes must be unique"); + static_assert(etl::type_list_all_of::value, "All TMessageTypes must satisfy the condition etl::is_message_type"); + static_assert(etl::index_sequence_is_unique::value, "All message IDs must be unique"); + + static ETL_CONSTANT etl::fsm_state_id_t STATE_ID = STATE_ID_; + + fsm_state() + : ifsm_state(STATE_ID) + { + } + + protected: + + ~fsm_state() + { + } + + TContext& get_fsm_context() const + { + return static_cast(ifsm_state::get_fsm_context()); + } + + private: + + static constexpr size_t Number_Of_Messages = sizeof...(TMessageTypes); + static constexpr etl::message_id_t Message_Id_Start = etl::type_list_type_at_index_t::ID; + + static_assert(Number_Of_Messages > 0, "Zero messages"); + + //********************************************** + // Checks that the message ids are contiguous. + //********************************************** + template = Number_Of_Messages)> + struct contiguous_impl; + + template + struct contiguous_impl : etl::true_type + { + }; + + template + struct contiguous_impl + : etl::bool_constant<(etl::type_list_type_at_index_t::ID + 1U == + etl::type_list_type_at_index_t::ID) && + contiguous_impl::value> + { + + + }; + + // The message ids are contiguous if there are 0 or 1 message types, or if each message id is one greater than the previous message id. + static constexpr bool Message_Ids_Are_Contiguous = (Number_Of_Messages <= 1U) ? true : contiguous_impl<0U>::value; + + using handler_ptr = etl::fsm_state_id_t (*)(TDerived&, const etl::imessage&); ///< Pointer to a handler function that takes a reference to the derived class and a reference to the message. + using message_dispatch_table_t = etl::array; ///< The dispatch table type. An array of handler pointers, one for each message type. + using message_id_table_t = etl::array; ///< The message id table type. An array of message ids, one for each message type. + + //******************************************** + etl::fsm_state_id_t process_event(const etl::imessage& message) + { + const etl::message_id_t id = message.get_message_id(); + + // The IDs are sorted, so an ID less than the first is not handled by this router. + if (id >= Message_Id_Start) + { + const size_t index = get_dispatch_index_from_message_id(id); + + // If the index is less than Number_Of_Messages, then we have a handler for this message type, so dispatch it. + if (index < Number_Of_Messages) + { + const etl::fsm_state_id_t new_state_id = dispatch(message, index); + + if (new_state_id != Pass_To_Parent) + { + return new_state_id; + } + } + } + +#include "etl/private/diagnostic_array_bounds_push.h" + // If we get here, then we don't have a handler for this message type, so pass it to the parent if we have one, otherwise call on_event_unknown. + return (p_parent != nullptr) ? p_parent->process_event(message) : static_cast(this)->on_event_unknown(message); +#include "etl/private/diagnostic_pop.h" + } + + //********************************************** + // Call for a single message type + //********************************************** + template + static etl::fsm_state_id_t call_on_event(TDerived& derived, const imessage& msg) + { + return derived.on_event(static_cast(msg)); + } + + //********************************************** + // Get the handler for a single message type at the index in the sorted type_list. + // This will be called for each message type to generate the dispatch table. + //********************************************** + template + static constexpr handler_ptr get_message_handler() + { + return &call_on_event>; + } + + //********************************************** + // Generate the dispatch table at compile time. + // This will create an array of handler pointers, one for each message type. + //********************************************** + template + static constexpr message_dispatch_table_t make_message_dispatch_table(etl::index_sequence) + { + return message_dispatch_table_t{ { get_message_handler()... } }; + } + + //********************************************** + // Get the message id for a single message type at an index in the sorted type_list. + // This will be called for each message type to generate the message id table. + //********************************************** + template + static constexpr etl::message_id_t get_message_id_from_index() + { + return etl::type_list_type_at_index_t::ID; + } + + //********************************************** + // Generate the message id table at compile time. + // This will create an array of message ids, one for each message type. + //********************************************** + template + static constexpr message_id_table_t make_message_id_table(etl::index_sequence) + { + return message_id_table_t{ { get_message_id_from_index()... } }; + } + + //********************************************** + // Get the dispatch index for a message id. + // This will be used at runtime to find the handler for a message id. + // If the message ids are contiguous, we can calculate the index directly. If they are not contiguous, we need to do a binary search. + // This will return Number_Of_Messages if the message id is not found. + //********************************************** + static size_t get_dispatch_index_from_message_id(etl::message_id_t id) + { + if ETL_IF_CONSTEXPR(Message_Ids_Are_Contiguous) + { + // The IDs are contiguous, so we can calculate the index directly. + return static_cast(id - Message_Id_Start); + } + else + { + // The IDs are not contiguous, so we need to do a binary search. + size_t left = 0; + size_t right = Number_Of_Messages; + + while (left < right) + { + size_t mid = (left + right) / 2; + + if (message_id_table[mid] == id) + { + return mid; + } + else if (message_id_table[mid] < id) + { + left = mid + 1; + } + else + { + right = mid; + } + } + } + + return Number_Of_Messages; // Not found + } + + //********************************************** + // Dispatch the message to the appropriate handler based on the index in the dispatch table. + //********************************************** + etl::fsm_state_id_t dispatch(const etl::imessage& msg, size_t index) + { + return message_dispatch_table[index](static_cast(*this), msg); + } + + //********************************************** + // The dispatch table is generated at compile time. The dispatch table contains pointers to the on_receive handlers for each message type. + //********************************************** + static ETL_INLINE_VAR constexpr message_dispatch_table_t message_dispatch_table = + etl::fsm_state::make_message_dispatch_table(etl::make_index_sequence::Number_Of_Messages>{}); + + //********************************************** + // The message id table is generated at compile time. The message id table contains the corresponding message ids for each message type. + //********************************************** + static ETL_INLINE_VAR constexpr message_id_table_t message_id_table = + etl::fsm_state::make_message_id_table(etl::make_index_sequence::Number_Of_Messages>{}); + }; + +#if ETL_USING_CPP11 && !ETL_USING_CPP17 + template + constexpr const typename etl::fsm_state::message_dispatch_table_t + etl::fsm_state::message_dispatch_table; + + template + constexpr const typename etl::fsm_state::message_id_table_t + etl::fsm_state::message_id_table; +#endif + + /// Definition of STATE_ID + template + ETL_CONSTANT etl::fsm_state_id_t fsm_state::STATE_ID; + + //*************************************************************************** + // The definition for no messages. + //*************************************************************************** + template + class fsm_state : public ifsm_state + { + private: + + using message_id_sequence = etl::index_sequence<>; + + public: + + using message_types = etl::type_list<>; + using sorted_message_types = etl::type_list<>; + static ETL_CONSTANT etl::fsm_state_id_t STATE_ID = STATE_ID_; fsm_state() @@ -779,51 +1012,16 @@ namespace etl private: - //******************************************** - struct result_t - { - bool was_handled; - etl::fsm_state_id_t state_id; - }; - //******************************************** etl::fsm_state_id_t process_event(const etl::imessage& message) { - etl::fsm_state_id_t new_state_id; - - const bool was_handled = (process_event_type(message, new_state_id) || ...); - - if (!was_handled || (new_state_id == Pass_To_Parent)) - { - new_state_id = (p_parent != nullptr) ? p_parent->process_event(message) : static_cast(this)->on_event_unknown(message); - } - - return new_state_id; - } - - //******************************************** - template - bool process_event_type(const etl::imessage& msg, etl::fsm_state_id_t& new_state_id) - { - if (TMessage::ID == msg.get_message_id()) - { - new_state_id = static_cast(this)->on_event(static_cast(msg)); - return true; - } - else - { - return false; - } + return (p_parent != nullptr) ? p_parent->process_event(message) : static_cast(this)->on_event_unknown(message); } }; - /// Definition of STATE_ID - template - ETL_CONSTANT etl::fsm_state_id_t fsm_state::STATE_ID; - #else //************************************************************************************************* -// For C++14 and below. +// For C++03 and below. //************************************************************************************************* /*[[[cog import cog @@ -833,14 +1031,14 @@ namespace etl cog.outl("//***************************************************************************") cog.outl("// The definition for all %s message types." % Handlers) cog.outl("//***************************************************************************") - cog.outl("template " % Handlers) + cog.out(" ") + cog.outl(" typename T%s = void>" % Handlers) cog.outl("class fsm_state : public ifsm_state") cog.outl("{") cog.outl("public:") @@ -896,26 +1094,26 @@ namespace etl else: cog.outl("// Specialisation for %d message types." % n) cog.outl("//***************************************************************************") - cog.outl("template " % n) - cog.out("class fsm_state" % n) + cog.out("class fsm_state : public ifsm_state") + cog.outl(" void> : public ifsm_state") cog.outl("{") cog.outl("public:") cog.outl("") @@ -966,13 +1164,13 @@ namespace etl cog.outl("// Specialisation for 0 message types.") cog.outl("//***************************************************************************") cog.outl("template ") - cog.out("class fsm_state : public ifsm_state") + cog.outl(" void> : public ifsm_state") cog.outl("{") cog.outl("public:") cog.outl("") @@ -1002,18 +1200,18 @@ namespace etl cog.outl("};") cog.outl("") - cog.outl("template " % Handlers) - cog.out("ETL_CONSTANT etl::fsm_state_id_t fsm_state" % Handlers) + cog.out("ETL_CONSTANT etl::fsm_state_id_t fsm_state::STATE_ID;" % Handlers) + cog.out(" T%s," % n) + cog.outl(" T%s>::STATE_ID;" % Handlers) ]]]*/ /*[[[end]]]*/ #endif diff --git a/include/etl/generators/message_router_generator.h b/include/etl/generators/message_router_generator.h index eeec698f..312848b6 100644 --- a/include/etl/generators/message_router_generator.h +++ b/include/etl/generators/message_router_generator.h @@ -525,33 +525,31 @@ namespace etl //*********************************************** void receive(const etl::imessage& msg) ETL_OVERRIDE { - etl::message_id_t id = msg.get_message_id(); - size_t index = Number_Of_Messages; + const etl::message_id_t id = msg.get_message_id(); // The IDs are sorted, so an ID less than the first is not handled by this router. if (id >= Message_Id_Start) { - index = get_dispatch_index_from_message_id(id); + const size_t index = get_dispatch_index_from_message_id(id); + + // If the index is less than Number_Of_Messages, then we have a handler for this message type, so dispatch it. + if (index < Number_Of_Messages) + { + dispatch(msg, index); + return; + } } - // If the index is less than Number_Of_Messages, then we have a handler for this message type, so dispatch it. - if (index < Number_Of_Messages) + // We don't have a handler for this message type, so pass it to a successor if there is one, or call on_receive_unknown() if there isn't. + if (has_successor()) { - dispatch(msg, index); + get_successor().receive(msg); } else { - // We don't have a handler for this message type, so pass it to a successor if there is one, or call on_receive_unknown() if there isn't. - if (has_successor()) - { - get_successor().receive(msg); - } - else - { #include "etl/private/diagnostic_array_bounds_push.h" - static_cast(this)->on_receive_unknown(msg); + static_cast(this)->on_receive_unknown(msg); #include "etl/private/diagnostic_pop.h" - } } } @@ -601,29 +599,17 @@ namespace etl //*********************************************** bool accepts(etl::message_id_t id) const ETL_OVERRIDE { - size_t index = Number_Of_Messages; - - // The IDs are sorted, so an ID less than the first is not handled by this router. if (id >= Message_Id_Start) { - index = get_dispatch_index_from_message_id(id); + const size_t index = get_dispatch_index_from_message_id(id); + + if (index < Number_Of_Messages) + { + return true; + } } - if (index < Number_Of_Messages) - { - return true; - } - else - { - if (has_successor()) - { - return get_successor().accepts(id); - } - else - { - return false; - } - } + return has_successor() ? get_successor().accepts(id) : false; } //******************************************** diff --git a/include/etl/message_router.h b/include/etl/message_router.h index e9b79d30..7e510466 100644 --- a/include/etl/message_router.h +++ b/include/etl/message_router.h @@ -513,33 +513,31 @@ namespace etl //*********************************************** void receive(const etl::imessage& msg) ETL_OVERRIDE { - etl::message_id_t id = msg.get_message_id(); - size_t index = Number_Of_Messages; + const etl::message_id_t id = msg.get_message_id(); // The IDs are sorted, so an ID less than the first is not handled by this router. if (id >= Message_Id_Start) { - index = get_dispatch_index_from_message_id(id); + const size_t index = get_dispatch_index_from_message_id(id); + + // If the index is less than Number_Of_Messages, then we have a handler for this message type, so dispatch it. + if (index < Number_Of_Messages) + { + dispatch(msg, index); + return; + } } - // If the index is less than Number_Of_Messages, then we have a handler for this message type, so dispatch it. - if (index < Number_Of_Messages) + // We don't have a handler for this message type, so pass it to a successor if there is one, or call on_receive_unknown() if there isn't. + if (has_successor()) { - dispatch(msg, index); + get_successor().receive(msg); } else { - // We don't have a handler for this message type, so pass it to a successor if there is one, or call on_receive_unknown() if there isn't. - if (has_successor()) - { - get_successor().receive(msg); - } - else - { #include "etl/private/diagnostic_array_bounds_push.h" - static_cast(this)->on_receive_unknown(msg); + static_cast(this)->on_receive_unknown(msg); #include "etl/private/diagnostic_pop.h" - } } } @@ -589,29 +587,17 @@ namespace etl //*********************************************** bool accepts(etl::message_id_t id) const ETL_OVERRIDE { - size_t index = Number_Of_Messages; - - // The IDs are sorted, so an ID less than the first is not handled by this router. if (id >= Message_Id_Start) { - index = get_dispatch_index_from_message_id(id); + const size_t index = get_dispatch_index_from_message_id(id); + + if (index < Number_Of_Messages) + { + return true; + } } - if (index < Number_Of_Messages) - { - return true; - } - else - { - if (has_successor()) - { - return get_successor().accepts(id); - } - else - { - return false; - } - } + return has_successor() ? get_successor().accepts(id) : false; } //******************************************** diff --git a/test/test_fsm.cpp b/test/test_fsm.cpp index c6a34e8e..61030b1b 100644 --- a/test/test_fsm.cpp +++ b/test/test_fsm.cpp @@ -515,7 +515,7 @@ namespace //************************************************************************* TEST(test_fsm_emergency_stop) { - motorControl.Initialise(stateList, ETL_OR_STD17::size(stateList)); + motorControl.Initialise(stateList, ETL_OR_STD17::size(stateList)); motorControl.reset(); motorControl.ClearStatistics(); @@ -758,7 +758,7 @@ namespace CHECK_EQUAL(StateId::Running, int(motorControl.get_state().get_state_id())); auto id2 = motorControl.transition_to(StateId::Idle); - + // Now in Locked state. CHECK_EQUAL(StateId::Locked, int(id2)); CHECK_EQUAL(StateId::Locked, int(motorControl.get_state_id()));