3

I am new to finite state machines and I'm trying to understand if I should use a hierarchical state machine, or stick with the flat structure I've got to model my problem in the simplest way.

I have an "analyzer" which can be either stopped, analyzing input, analyzing output, analyzing input and output, or clearing down. I have some analyzer states in an enum called analyzer_states and the transitions between states in analyzer_transitions.

The question is: could I use a hierarchical state machine to model this more simply? Or maybe a FSM is not helping with this problem?

#include <iostream>
#include <cstdlib>
#include <string>
#include "fsm.h"

enum class analyzer_transitions
{
    start_input,
    start_output,
    clear,
    stop_input,
    stop_output,
    resume
};

enum class analyzer_states
{
    analyzing_both,
    analyzing_input,
    analyzing_output,
    clearing_both,
    clearing_input,
    clearing_output,
    stopped,
};

inline auto output(std::string s)
{
    return [s]()
    {
        std::cout << s << std::endl;
    };
}

int main()
{
    fsm<analyzer_states, analyzer_transitions> machine{ analyzer_states::stopped };

    machine.add(
        analyzer_states::stopped, analyzer_transitions::start_input, analyzer_states::analyzing_input,
        output("-> analyzing_input"));

    machine.add(
        analyzer_states::stopped, analyzer_transitions::start_output, analyzer_states::analyzing_output,
        output("-> analyzing_output"));

    machine.add(
        analyzer_states::analyzing_input, analyzer_transitions::stop_input, analyzer_states::stopped, 
        output("-> stopped"));

    machine.add(
        analyzer_states::analyzing_output, analyzer_transitions::stop_output, analyzer_states::stopped, 
        output("-> stopped"));

    machine.add(
        analyzer_states::analyzing_both, analyzer_transitions::stop_input, analyzer_states::analyzing_output, 
        output("-> analyzing_output"));

    machine.add(
        analyzer_states::analyzing_both, analyzer_transitions::stop_output, analyzer_states::analyzing_input,
        output("-> analyzing_input"));

    machine.add(
        analyzer_states::analyzing_input, analyzer_transitions::start_output, analyzer_states::analyzing_both, 
        output("-> analyzing_both"));

    machine.add(
        analyzer_states::analyzing_output, analyzer_transitions::start_input, analyzer_states::analyzing_both,
        output("-> analyzing_both"));

    machine.add(
        analyzer_states::analyzing_input, analyzer_transitions::clear, analyzer_states::clearing_input,
        output("-> clearing_input"));

    machine.add(
        analyzer_states::analyzing_output, analyzer_transitions::clear, analyzer_states::clearing_output,
        output("-> clearing_output"));

    machine.add(
        analyzer_states::analyzing_both, analyzer_transitions::clear, analyzer_states::clearing_both,
        output("-> clearing_both"));

    machine.add(
        analyzer_states::clearing_input, analyzer_transitions::resume, analyzer_states::analyzing_input,
        output("-> analyzing_input"));

    machine.add(
        analyzer_states::clearing_output, analyzer_transitions::resume, analyzer_states::analyzing_output,
        output("-> analyzing_output"));

    machine.add(
        analyzer_states::clearing_both, analyzer_transitions::resume, analyzer_states::analyzing_both,
        output("-> analyzing_both"));

    machine.to(analyzer_transitions::start_input);
    machine.to(analyzer_transitions::start_output);
    machine.to(analyzer_transitions::stop_input);
    machine.to(analyzer_transitions::clear);
    machine.to(analyzer_transitions::resume);

    std::cout << "Done...";
    std::getchar();

    return EXIT_SUCCESS;
}

The output is:

-> analyzing_input
-> analyzing_both
-> analyzing_output
-> clearing_output
-> analyzing_output

fsm.h:

#include <map>
#include <functional>
#include <atomic>

template <typename State, typename Transition>
class fsm
{
    using handler_t = std::function<void()>;

    class from_t
    {
        Transition transition_;
        State state_;

    public:
        from_t(Transition transition, State state) :
            transition_{ transition },
            state_{ state }
        {
        }

        bool operator<(const from_t& other) const
        {
            if (transition_ < other.transition_)
            {
                return true;
            }
            else if (transition_ > other.transition_)
            {
                return false;
            }
            else
            {
                return state_ < other.state_;
            }
        }
    };

    class to_t
    {
        State state_;
        handler_t handler_;

    public:
        to_t(State state, handler_t handler) :
            state_{ state },
            handler_{ handler }
        {
        }

        State state() const { return state_; }

        void operator()() const { handler_(); }
    };

    std::map<from_t, to_t> transitions_;
    std::atomic<State> state_;

public:
    fsm(State initial_state) :
        state_{ initial_state }
    {
    }

    void add(State from, Transition transition, State to, handler_t handler)
    {
        transitions_.insert({ { transition, from }, { to, handler } });
    }

    void add(State from, Transition transition, State to)
    {
        add(transition, to, from, [] {});
    }

    bool to(Transition transition)
    {
        auto found = transitions_.find({ transition, state_ });

        if (found != transitions_.end())
        {
            auto& to = found->second;

            state_ = to.state();

            to();

            return true;
        }
        else
        {
            return false;
        }
    }
};
keith
  • 5,122
  • 3
  • 21
  • 50
  • 1
    I don't think a state machine is that well suited to your problem: it has several orthogonal states and its regularity makes a state machine overkill IMO. I'd model it with three separate variables as `input: yes/no`, `output: yes/no`, and `state: stopped/analyzing/clearing`. The only overlap is that `state = stop` is equivalent to `input = output = no`. – Quentin Jan 22 '19 at 12:19
  • Can the analyser be analysing (say input) at the same time it is clearing down (say output)? If not, then I agree with Quentin. – Roy Jan 22 '19 at 17:04
  • @Roy, no it can't. – keith Jan 22 '19 at 17:22
  • Could you perhaps provide a state diagram of your "flat" state machine? With a diagram it is usually much easier to identify repetitions that can be then moved to superstates in a hierarchical state machine. – Miro Samek Jan 30 '19 at 21:20

0 Answers0