Skip to content

hedzr/fsm-cxx

Repository files navigation

fsm-cxx

CMake Build Matrix GitHub tag (latest SemVer)

fsm-cxx is a finite state machina library for C++17/C++20. It is header-only, light-weight but full-featured. It is designed for easy binding and friendly programmatic interface.

Features

  • Entry/exit actions
  • Event actions, guards
  • Transition actions
  • Transition conditions (input action)
  • Event payload (classes)
  • Thread Safe (safe_machine_t<>)
  • [ ] Inheritance of states and action functions
  • [ ] Documentations (NOT YET)
  • [ ] Examples (NOT YET)

See CHANGELOG

Usages

Here is a simple state machine:

#include <fsm_cxx.hh>
namespace fsm_cxx { namespace test {

    // states

    AWESOME_MAKE_ENUM(my_state,
                      Empty,
                      Error,
                      Initial,
                      Terminated,
                      Opened,
                      Closed)

    // event

    // Or:
    // FSM_DEFINE_EVENT_BEGIN(begin)
    //     int val{9}
    // FSM_DEFINE_EVENT_END()
    struct begin : public fsm_cxx::event_type<begin> {
        virtual ~begin() {}
        int val{9};
    };
    struct end : public fsm_cxx::event_type<end> {
        virtual ~end() {}
    };
    struct open : public fsm_cxx::event_type<open> {
        virtual ~open() {}
    };
    // Or:
    // FSM_DEFINE_EVENT(close)
    struct close : public fsm_cxx::event_type<close> {
        virtual ~close() {}
    };

    void test_state_meta() {
        fsm_cxx::machine_t<my_state> m;
        using M = decltype(m);

        // @formatter:off
        // states
        m.state().set(my_state::Initial).as_initial().build();
        m.state().set(my_state::Terminated).as_terminated().build();
        m.state().set(my_state::Error).as_error()
                .entry_action([](M::Event const &, M::Context &, M::State const &, M::Payload const &) { std::cerr << "          .. <error> entering" << '\n'; })
                .build();
        m.state().set(my_state::Opened)
                .guard([](M::Event const &, M::Context &, M::State const &, M::Payload const &) -> bool { return true; })
                .guard([](M::Event const &, M::Context &, M::State const &, M::Payload const &p) -> bool { return p._ok; })
                .entry_action([](M::Event const &, M::Context &, M::State const &, M::Payload const &) { std::cout << "          .. <opened> entering" << '\n'; })
                .exit_action([](M::Event const &, M::Context &, M::State const &, M::Payload const &) { std::cout << "          .. <opened> exiting" << '\n'; })
                .build();
        m.state().set(my_state::Closed)
                .entry_action([](M::Event const &, M::Context &, M::State const &, M::Payload const &) { std::cout << "          .. <closed> entering" << '\n'; })
                .exit_action([](M::Event const &, M::Context &, M::State const &, M::Payload const &) { std::cout << "          .. <closed> exiting" << '\n'; })
                .build();

        // transitions
        m.transition().set(my_state::Initial, begin{}, my_state::Closed).build();
        m.transition()
                .set(my_state::Closed, open{}, my_state::Opened)
                .guard([](M::Event const &, M::Context &, M::State const &, M::Payload const &p) -> bool { return p._ok; })
                .entry_action([](M::Event const &, M::Context &, M::State const &, M::Payload const &) { std::cout << "          .. <closed -> opened> entering" << '\n'; })
                .exit_action([](M::Event const &, M::Context &, M::State const &, M::Payload const &) { std::cout << "          .. <closed -> opened> exiting" << '\n'; })
                .build();
        m.transition().set(my_state::Opened, close{}, my_state::Closed).build()
                .transition().set(my_state::Closed, end{}, my_state::Terminated).build();
        m.transition().set(my_state::Opened, end{}, my_state::Terminated)
                .entry_action([](M::Event const &, M::Context &, M::State const &, M::Payload const &) { std::cout << "          .. <T><END>" << '\n'; })
                .build();
        // @formatter:on

        m.on_error([](fsm_cxx::Reason reason, M::State const &, M::Context &, M::Event const &, M::Payload const &) {
            std::cout << "          Error: reason = " << reason << '\n';
        });

        // debug log
        m.on_transition([&m](auto const &from, fsm_cxx::event_t const &ev, auto const &to, auto const &actions, auto const &payload) {
            std::printf("        [%s] -- %s --> [%s] (payload = %s)\n", m.state_to_sting(from).c_str(), ev.to_string().c_str(), m.state_to_sting(to).c_str(), to_string(payload).c_str());
            UNUSED(actions);
        });

        // processing

        m.step_by(begin{});
        if (!m.step_by(open{}, fsm_cxx::payload_t{false}))
            std::cout << "          E. cannot step to next with a false payload\n";
        m.step_by(open{});
        m.step_by(close{});
        m.step_by(open{});
        m.step_by(end{});

        std::printf("---- END OF test_state_meta()\n\n\n");
    }
}

int main() {
    fsm_cxx::test::test_state_meta();
    return 0;
}

Build Options

Build with CMake

  1. gcc 10+: passed
  2. clang 12+: passed
  3. msvc build tool 16.7.2, 16.8.5 (VS2019 or Build Tool) passed

ninja is optional for faster building.

# configure
cmake -S . -B build/ -G Ninja
# build
cmake --build build/
# install
cmake --install build/
# Or:cmake --build build/ --target install
#
# Sometimes sudo it:
#   sudo cmake --build build/ --target install
# Or:
#   cmake --install build/ --prefix ./install --strip
#   sudo cp -R ./install/include/* /usr/local/include/
#   sudo cp -R ./install/lib/cmake/fsm_cxx /usr/local/lib/cmake/

Other CMake Options

  1. FSM_CXX_BUILD_TESTS_EXAMPLES=OFF
  2. FSM_CXX_BUILD_DOCS=OFF
  3. ...

Thanks to JODL

Thanks to JetBrains for donating product licenses to help develop fsm-cxx jetbrains

LICENSE

Apache 2.0