diff --git a/CMakeLists.txt b/CMakeLists.txt index e6939fd..f1c8817 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,5 +4,3 @@ set (CMAKE_CXX_STANDARD 11) project(state_machine) add_executable(usm_test test/usm_test.cpp) - -#install(TARGETS state_machine RUNTIME DESTINATION bin) diff --git a/include/usm.hpp b/include/usm.hpp index d33b96d..dad217c 100644 --- a/include/usm.hpp +++ b/include/usm.hpp @@ -1,71 +1,99 @@ #pragma once /* - * Copyright (c) 2019 Julian Kent. All rights reserved. - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * * Neither the name of libgnc nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * - * - * For usage examples, look at the tests. +* Copyright (c) 2019 Julian Kent. All rights reserved. +* +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* * Redistributions of source code must retain the above copyright notice, this +* list of conditions and the following disclaimer. +* +* * Redistributions in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materials provided with the distribution. +* +* * Neither the name of libgnc nor the names of its +* contributors may be used to endorse or promote products derived from +* this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* +* +* +* For usage examples, look at the tests. */ namespace usm { -enum Transition { REPEAT, NEXT, NEXT2, NEXT3, NEXT4, ERROR }; +enum Transition { REPEAT, NEXT1, NEXT2, NEXT3, NEXT4, ERROR }; -template class StateMachine { -public: - StateMachine(StateEnum startingState); - void iterateOnce(); +template +class StateMachine { + public: + StateMachine(StateEnum startingState); + void iterateOnce(); - StateEnum getState(); + StateEnum getState(); -protected: - virtual Transition runCurrentState(StateEnum currentState) = 0; // a big switch - virtual StateEnum chooseNextState(StateEnum currentState, Transition transition) = 0; // nested switches + protected: + virtual Transition runCurrentState( + StateEnum currentState) = 0; // a big switch + virtual StateEnum chooseNextState( + StateEnum currentState, Transition transition) = 0; // nested switches -private: - StateEnum m_currentState; + private: + StateEnum m_currentState; }; /*---------------IMPLEMENTATION------------------*/ template StateMachine::StateMachine(StateEnum startingState) - : m_currentState(startingState) -{ -} + : m_currentState(startingState) {} -template void StateMachine::iterateOnce() -{ - m_currentState = chooseNextState(m_currentState, runCurrentState(m_currentState)); +template +void StateMachine::iterateOnce() { + Transition t = runCurrentState(m_currentState); + if (t != REPEAT) m_currentState = chooseNextState(m_currentState, t); } -template StateEnum StateMachine::getState() { return m_currentState; } +template +StateEnum StateMachine::getState() { + return m_currentState; +} } + +/*---------------MACROS TO MAKE TRANSITION TABLES EASY------------------*/ + +// clang-format off +#define USM_TABLE(current_state, error, ...) \ +switch (current_state) { \ + __VA_ARGS__; \ + default: break; \ +} \ +return error + +#define USM_STATE(transition, start_state, ...) \ + case start_state: \ + switch (transition) { \ + __VA_ARGS__; \ + default: break; \ + } \ + break + +#define USM_MAP(transition, next_state) \ + case transition: return next_state + +// clang-format on diff --git a/test/usm_test.cpp b/test/usm_test.cpp index b258811..20dc6c7 100644 --- a/test/usm_test.cpp +++ b/test/usm_test.cpp @@ -6,228 +6,251 @@ using namespace usm; /* - * Copyright (c) 2019 Julian Kent. All rights reserved. - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * * Neither the name of libgnc nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * +* Copyright (c) 2019 Julian Kent. All rights reserved. +* +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* * Redistributions of source code must retain the above copyright notice, this +* list of conditions and the following disclaimer. +* +* * Redistributions in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materials provided with the distribution. +* +* * Neither the name of libgnc nor the names of its +* contributors may be used to endorse or promote products derived from +* this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* +* */ +/* + * Implement a basic state machine with 2 paths (A & B), and error state, + * and a transition table. Use this as an example for your use case! + */ + namespace test { -enum TestStates { START, PATH_A_1, PATH_A_2, END, PATH_B_1, PATH_B_2, PATH_B_3, CLEANUP }; + +enum TestStates { + START, + PATH_A_1, + PATH_A_2, + END, + PATH_B_1, + PATH_B_2, + PATH_B_3, + CLEANUP +}; class TestStateMachine final : public StateMachine { -public: - TestStateMachine() - : StateMachine(START) - { + public: + TestStateMachine() : StateMachine(START) {} + + bool path_a = true; + bool trigger_error_once = false; + + bool start_called = false; + bool a1_called = false; + bool a2_called = false; + bool b1_called = false; + bool b2_called = false; + bool b3_called = false; + bool end_called = false; + bool cleanup_called = false; + + protected: + Transition runCurrentState(TestStates currentState) override { + if (trigger_error_once) { + trigger_error_once = false; + return Transition::ERROR; } - bool path_a = true; - bool trigger_error = false; + // clang-format off + switch(currentState) { + case START: return start(); + case PATH_A_1: return a1(); + case PATH_A_2: return a2(); + case END: return end(); + case PATH_B_1: return b1(); + case PATH_B_2: return b2(); + case PATH_B_3: return b3(); + case CLEANUP: return cleanup(); + } + // clang-format on + } -protected: - Transition runCurrentState(TestStates currentState) override - { - if (trigger_error) - return Transition::ERROR; + TestStates chooseNextState(TestStates currentState, + Transition transition) override { + // clang-format off + USM_TABLE(currentState, CLEANUP, + USM_STATE(transition, START, USM_MAP(NEXT1, PATH_A_1); + USM_MAP(NEXT2, PATH_B_1)); + USM_STATE(transition, PATH_A_1, USM_MAP(NEXT1, PATH_A_2); + USM_MAP(ERROR, PATH_B_3)); + USM_STATE(transition, PATH_A_2, USM_MAP(NEXT1, END)); + USM_STATE(transition, PATH_B_1, USM_MAP(NEXT1, PATH_B_2)); + USM_STATE(transition, PATH_B_2, USM_MAP(NEXT1, PATH_B_3)); + USM_STATE(transition, PATH_B_3, USM_MAP(NEXT1, END)); + USM_STATE(transition, CLEANUP, USM_MAP(NEXT1, END)) + ); + // clang-format on + } - switch (currentState) { - case START: - return start(); + private: + Transition start() { + start_called = true; + return path_a ? Transition::NEXT1 : NEXT2; + } + Transition a1() { + a1_called = true; + return Transition::NEXT1; + } + Transition a2() { + a2_called = true; + return Transition::NEXT1; + } + Transition b1() { + b1_called = true; + return Transition::NEXT1; + } + Transition b2() { + b2_called = true; + return Transition::NEXT1; + } + Transition b3() { + b3_called = true; + return Transition::NEXT1; + } + Transition end() { + end_called = true; + return Transition::REPEAT; + } + Transition cleanup() { + cleanup_called = true; + return Transition::NEXT1; + } +}; - case PATH_A_1: - return a1(); +void runTests_path_a() { + TestStateMachine m; + assert(m.getState() == START); - case PATH_A_2: - return a2(); + assert(!m.start_called); + m.iterateOnce(); + assert(m.start_called); + assert(m.getState() == PATH_A_1); - case END: - return end(); + assert(!m.a1_called); + m.iterateOnce(); + assert(m.a1_called); + assert(m.getState() == PATH_A_2); - case PATH_B_1: - return b1(); + assert(!m.a2_called); + m.iterateOnce(); + assert(m.a2_called); + assert(m.getState() == END); - case PATH_B_2: - return b2(); + assert(!m.end_called); + m.iterateOnce(); + assert(m.end_called); +} - case PATH_B_3: - return b3(); +void runTests_path_b() { + TestStateMachine m; + m.path_a = false; + assert(m.getState() == START); - case CLEANUP: - return cleanup(); - } - return Transition::ERROR; - } + assert(!m.start_called); + m.iterateOnce(); + assert(m.start_called); + assert(m.getState() == PATH_B_1); - TestStates chooseNextState(test::TestStates currentState, Transition transition) override - { - //TODO: make a DSL with templates or macros to make this easier to read - - switch (currentState) { - case START: - switch (transition) { - case Transition::NEXT: - return PATH_A_1; - case Transition::NEXT2: - return PATH_B_1; - default: - return CLEANUP; - } - break; - case PATH_A_1: - switch (transition) { - case Transition::NEXT: - return PATH_A_2; - case Transition::ERROR: - return PATH_B_3; - default: - return CLEANUP; - } - break; - case PATH_A_2: - switch (transition) { - case Transition::NEXT: - return END; - default: - return CLEANUP; - } - break; - case PATH_B_1: - switch (transition) { - case Transition::NEXT: - return PATH_B_2; - default: - return CLEANUP; - } - break; - case PATH_B_2: - switch (transition) { - case Transition::NEXT: - return PATH_B_3; - default: - return CLEANUP; - } - break; - case PATH_B_3: - switch (transition) { - case Transition::NEXT: - return END; - default: - return CLEANUP; - } - break; - case CLEANUP: - switch (transition) { - case Transition::NEXT: - return END; - default: - return CLEANUP; - } - break; - default: - return CLEANUP; - } - return CLEANUP; - } + assert(!m.b1_called); + m.iterateOnce(); + assert(m.b1_called); + assert(m.getState() == PATH_B_2); -private: - Transition start() - { - if (path_a) - return Transition::NEXT; - else - return Transition::NEXT2; - } - Transition a1() { return Transition::NEXT; } - Transition a2() { return Transition::NEXT; } - Transition b1() { return Transition::NEXT; } - Transition b2() { return Transition::NEXT; } - Transition b3() { return Transition::NEXT; } - Transition end() { return Transition::REPEAT; } - Transition cleanup() { return Transition::NEXT; } -}; + assert(!m.b2_called); + m.iterateOnce(); + assert(m.b2_called); + assert(m.getState() == PATH_B_3); -void runTests_path_a() -{ - TestStateMachine m; - assert(m.getState() == START); - m.iterateOnce(); - assert(m.getState() == PATH_A_1); - m.iterateOnce(); - assert(m.getState() == PATH_A_2); - m.iterateOnce(); - assert(m.getState() == END); -} + assert(!m.b3_called); + m.iterateOnce(); + assert(m.b3_called); + assert(m.getState() == END); -void runTests_path_b() -{ - TestStateMachine m; - m.path_a = false; - assert(m.getState() == START); - m.iterateOnce(); - assert(m.getState() == PATH_B_1); - m.iterateOnce(); - assert(m.getState() == PATH_B_2); - m.iterateOnce(); - assert(m.getState() == PATH_B_3); - m.iterateOnce(); - assert(m.getState() == END); + assert(!m.end_called); + m.iterateOnce(); + assert(m.end_called); } -void runTests_default_error() -{ - TestStateMachine m; - assert(m.getState() == START); - m.iterateOnce(); - assert(m.getState() == PATH_A_1); - m.iterateOnce(); - assert(m.getState() == PATH_A_2); - m.trigger_error = true; - m.iterateOnce(); - assert(m.getState() == CLEANUP); - m.iterateOnce(); - assert(m.getState() == CLEANUP); +void runTests_default_error() { + TestStateMachine m; + assert(m.getState() == START); + + assert(!m.start_called); + m.iterateOnce(); + assert(m.start_called); + assert(m.getState() == PATH_A_1); + + assert(!m.a1_called); + m.iterateOnce(); + assert(m.a1_called); + assert(m.getState() == PATH_A_2); + + m.trigger_error_once = true; + m.iterateOnce(); + assert(m.getState() == CLEANUP); + + assert(!m.cleanup_called); + m.iterateOnce(); + assert(m.cleanup_called); + assert(m.getState() == END); + + assert(!m.end_called); + m.iterateOnce(); + assert(m.end_called); } -void runTests_custom_error() -{ - TestStateMachine m; - assert(m.getState() == START); - m.iterateOnce(); - assert(m.getState() == PATH_A_1); - m.trigger_error = true; - m.iterateOnce(); - assert(m.getState() == PATH_B_3); - m.trigger_error = false; - m.iterateOnce(); - assert(m.getState() == END); +void runTests_custom_error() { + TestStateMachine m; + assert(m.getState() == START); + + assert(!m.start_called); + m.iterateOnce(); + assert(m.start_called); + assert(m.getState() == PATH_A_1); + + m.trigger_error_once = true; + m.iterateOnce(); + assert(m.getState() == PATH_B_3); + + assert(!m.b3_called); + m.iterateOnce(); + assert(m.b3_called); + assert(m.getState() == END); + + assert(!m.end_called); + m.iterateOnce(); + assert(m.end_called); + assert(m.getState() == END); } + } int main(void) @@ -241,3 +264,6 @@ int main(void) std::cout << std::endl << "****************" << std::endl; return 0; } + + +