Here is the code I wrote for a FSM in C++. It’s really simple yet useful:
// fsm.h
// #pragma once
#include <iostream>
#include <string>
#include <vector>
#include <map>
class FSM;
typedef void(*state_enter_method_t)(void*, void*);
typedef void(*state_exit_method_t)(void*);
typedef std::vector<std::string> state_vec_t;
class State
{
public:
State(const std::string& name, state_enter_method_t enter_method,
state_exit_method_t exit_method);
void add_transition(const std::string& next_state);
bool is_next_state_valid(const State* next_state) const;
std::string get_name() const;
void enter(FSM* fsm, void* arg) const;
void exit(FSM* fsm) const;
private:
std::string m_name;
state_enter_method_t m_enter_method;
state_exit_method_t m_exit_method;
state_vec_t m_next_states;
};
typedef std::map<std::string, State*> state_map_t;
class FSM
{
public:
FSM(const std::string& name);
~FSM();
void set_FSM_name(const std::string& name);
std::string get_FSM_name() const;
void add_state(State* state);
bool is_in_transition();
State* get_current_state() const;
State* get_prev_state() const;
State* get_next_state() const;
State* get_state_named(const std::string& state);
void request(const std::string& next_state, void* arg=NULL);
private:
std::string m_name;
State* m_cur_state;
State* m_prev_state;
State* m_next_state;
state_map_t m_state_map;
};
#define DECL_STATE(CLASS, X) inline static void __enter_##X(void* _this, void* arg) {((CLASS*)_this)->enter_##X(arg);};\
inline static void __exit_##X(void* _this) {((CLASS*)_this)->exit_##X();};\
void enter_##X(void* arg);void exit_##X();
#define ADD_STATE(CLASS, X) add_state(new State(#X, &##CLASS::__enter_##X, &##CLASS::__exit_##X))
#define ADD_TRANSITION(X, Y) get_state_named(#X)->add_transition(#Y)
#define DEF_ENTER(CLASS, X) void CLASS::enter_##X(void* arg)
#define DEF_EXIT(CLASS, X) void CLASS::exit_##X()
#define EMPTY_ENTER(CLASS, X) void CLASS::enter_##X(void*){};
#define EMPTY_EXIT(CLASS, X) void CLASS::exit_##X(){};
// fsm.cxx
// #include "fsm.h"
#include <algorithm>
State::State(const std::string& name, state_enter_method_t enter_method,
state_exit_method_t exit_method): m_name(name),
m_enter_method(enter_method),
m_exit_method(exit_method)
{
}
void State::add_transition(const std::string& next_state)
{
m_next_states.push_back(next_state);
}
bool State::is_next_state_valid(const State* next_state) const
{
return std::find(m_next_states.begin(), m_next_states.end(), next_state->get_name()) != m_next_states.end();
}
std::string State::get_name() const
{
return m_name;
}
void State::enter(FSM* fsm, void* arg) const
{
m_enter_method(fsm, arg);
}
void State::exit(FSM* fsm) const
{
m_exit_method(fsm);
}
FSM::FSM(const std::string& name): m_name(name),
m_cur_state(NULL),
m_prev_state(NULL),
m_next_state(NULL)
{
}
FSM::~FSM()
{
m_cur_state = NULL;
m_prev_state = NULL;
m_next_state = NULL;
for (state_map_t::iterator it = m_state_map.begin(); it != m_state_map.end();
++it) delete it->second;
}
void FSM::set_FSM_name(const std::string& name)
{
m_name = name;
}
std::string FSM::get_FSM_name() const
{
return m_name;
}
void FSM::add_state(State* state)
{
m_state_map[state->get_name()] = state;
}
bool FSM::is_in_transition()
{
return (m_prev_state != NULL || m_next_state != NULL);
}
State* FSM::get_current_state() const
{
return m_cur_state;
};
State* FSM::get_prev_state() const
{
return m_prev_state;
};
State* FSM::get_next_state() const
{
return m_next_state;
};
State* FSM::get_state_named(const std::string& state)
{
for (state_map_t::iterator it = m_state_map.begin(); it != m_state_map.end(); ++it)
{
State* s = it->second;
if (s->get_name() == state)
return s;
}
return NULL;
}
void FSM::request(const std::string& next_state, void* arg)
{
if (is_in_transition())
{
std::cerr << "already in transition!!" << std::endl;
return;
}
m_next_state = get_state_named(next_state);
if (!m_next_state)
{
std::cerr << "state undefined: " << next_state << std::endl;
return;
}
if (m_cur_state && !m_cur_state->is_next_state_valid(m_next_state))
{
m_next_state = NULL;
std::cerr << "no transition from " << m_cur_state->get_name() << " to " << next_state << std::endl;
return;
}
m_prev_state = m_cur_state;
m_cur_state = NULL;
if (m_prev_state)
m_prev_state->exit(this);
m_next_state->enter(this, arg);
m_cur_state = m_next_state;
m_prev_state = NULL;
m_next_state = NULL;
}
// test.cxx
// include "fsm.h"
class TestFSM : public FSM
{
public:
TestFSM(): FSM("TestFSM")
{
ADD_STATE(TestFSM, state1);
ADD_STATE(TestFSM, state2);
ADD_STATE(TestFSM, state3);
ADD_TRANSITION(state1, state2);
ADD_TRANSITION(state2, state3);
ADD_TRANSITION(state3, state1);
ADD_TRANSITION(state3, state2);
}
private:
DECL_STATE(TestFSM, state1);
DECL_STATE(TestFSM, state2);
DECL_STATE(TestFSM, state3);
};
DEF_ENTER(TestFSM, state1)
{
std::string mystr = *((std::string*)arg);
std::cout << "enter state1: " << mystr << std::endl;
}
EMPTY_EXIT(TestFSM, state1);
DEF_ENTER(TestFSM, state2)
{
int myvalue = *((int*)arg);
std::cout << "enter state2: " << myvalue << std::endl;
}
EMPTY_EXIT(TestFSM, state2)
DEF_ENTER(TestFSM, state3)
{
std::cout << "enter state3: this state doesn't use the arg, which is probably NULL ("
<< arg << ")" << std::endl;
}
DEF_EXIT(TestFSM, state3)
{
std::cout << "exit state3" << std::endl;
}
int main(int, char**)
{
TestFSM x;
std::string hello("Hello world!");
x.request("state1", &hello);
int test = 123;
int test2 = 321;
x.request("state2", &test);
x.request("state1"); // INVALID, therefore:
std::cout << "last transition was invalid so, current state is (" << x.get_current_state()->get_name()
<< ") (expected: state2)" << std::endl;
x.request("state3");
x.request("state2", &test2);
}
Output:
enter state1: Hello world!
enter state2: 123
no transition from state2 to state1
last transition was invalid so, current state is (state2) (expected: state2)
enter state3: this state doesn't use the arg, which is probably NULL (00000000)
exit state3
enter state2: 321
Basic usage:
-
Subclass FSM.
-
In the class declaration, use DECL_STATE(classname, statename).
-
In your constructor, use ADD_STATE and ADD_TRANSITION.
-
To define the state enter/exit handler, use
DEF_ENTER(classname, statename) {...}
or
DEF_EXIT(classname, statename) {...}
-
Within ENTER methods, you have access to arg, which is a void* of the arg passed in request. Cast it to whatever you want. Be careful, it might be NULL.
-
If your state doesn’t need enter or exit methods, you can use
EMPTY_ENTER(classname, statename)
or
EMPTY_EXIT((classname, statename)
which creates a dummy entry for you.
Enjoy it!