// (by Ariel Badichi) #include <iostream> #include <iterator> #include <string> #include <vector> #include <algorithm> #include <ctime> #include <cassert> #include <boost/assign.hpp> #include <boost/type_traits/is_same.hpp> #include <boost/mpl/vector.hpp> #include <boost/mpl/fold.hpp> #include <boost/mpl/filter_view.hpp> #include <boost/mpl/iterator_tags.hpp> #include <boost/mpl/iterator_range.hpp> #include <boost/mpl/begin.hpp> #include <boost/mpl/end.hpp> #include <boost/mpl/int.hpp> #include <boost/mpl/bool.hpp> #include <boost/mpl/deref.hpp> #include <boost/mpl/reverse.hpp> #include <boost/mpl/placeholders.hpp> namespace mpl = boost::mpl; using namespace mpl::placeholders; template<typename Derived> class state_machine; namespace details { template< typename Transition, typename Next > struct event_dispatcher { typedef typename Transition::fsm_t fsm_t; typedef typename Transition::event event; static int dispatch(fsm_t & fsm, int state, const event & e) { if (state == Transition::current_state) { Transition::execute(fsm, e); return Transition::next_state; } else { return Next::dispatch(fsm, state, e); } } }; struct default_event_dispatcher { template<typename FSM, typename Event> static int dispatch(state_machine<FSM> & m, int state, const Event & e) { return m.call_no_transition(state, e); } }; template<typename Transition> struct transition_event { typedef typename Transition::event type; }; template<typename Row> struct get_current_state_ { typedef mpl::int_<Row::current_state> type; }; template<typename Iter> struct get_current_state { typedef typename mpl::next<Iter>::type next; typedef typename mpl::deref<Iter>::type row; typedef typename mpl::eval_if< typename row::has_current_state, get_current_state_<row>, get_current_state<next> >::type type; }; template<typename Iter> struct transition_iterator { typedef mpl::forward_iterator_tag category; typedef transition_iterator type; }; template<typename Table> struct transition_view : mpl::iterator_range< transition_iterator<typename mpl::begin<Table>::type>, transition_iterator<typename mpl::end<Table>::type> > {}; template<int CurrentState, typename Row> struct transition { const static int current_state = CurrentState; const static int next_state = Row::next_state; typedef typename Row::event event; typedef typename Row::fsm_t fsm_t; static void execute(fsm_t & fsm, const event & e) { Row::execute(fsm, e); } }; template<typename Table, typename Event> struct generate_dispatcher : mpl::fold< mpl::filter_view< // One could use reverse_view from exercise 7-7 // to speed things up a little. transition_view<typename mpl::reverse<Table>::type>, boost::is_same<Event, transition_event<_> > >, default_event_dispatcher, event_dispatcher<_2, _1> > {}; } namespace boost { namespace mpl { template<typename Iter> struct next<details::transition_iterator<Iter> > : details::transition_iterator<typename mpl::next<Iter>::type> { }; template<typename Iter> struct deref<details::transition_iterator<Iter> > { typedef typename deref<Iter>::type row; typedef details::transition< details::get_current_state<Iter>::type::value, row > type; }; } } template<typename Derived> class state_machine { protected: template< int CurrentState, typename Event, int NextState, void (Derived::*action)(const Event &) > struct srow { typedef mpl::true_ has_current_state; static const int current_state = CurrentState; static const int next_state = NextState; typedef Event event; typedef Derived fsm_t; static void execute(Derived & fsm, const Event & e) { (fsm.*action)(e); } }; template< typename Event, int NextState, void (Derived::*action)(const Event &) > struct row { typedef mpl::false_ has_current_state; static const int next_state = NextState; typedef Event event; typedef Derived fsm_t; static void execute(Derived & fsm, const Event & e) { (fsm.*action)(e); } }; state_machine() : state(Derived::initial_state) { } template<typename Event> int no_transition(int state, const Event & e) { assert(0); return this->state; } public: template<typename Event> int process_event(const Event & e) { typedef typename details::generate_dispatcher< Derived::transition_table, Event >::type dispatcher; this->state = dispatcher::dispatch(*static_cast<Derived *>(this), this->state, e); return this->state; } template<typename Event> int call_no_transition(int state, const Event & e) { return static_cast<Derived *>(this)->no_transition(state, e); } private: int state; }; struct play {}; struct open_close {}; struct stop {}; struct pause {}; class cd_detected { public: typedef std::vector<std::clock_t> lengths_t; public: cd_detected(const std::string & title, const lengths_t & lengths) : m_title(&title) , m_lengths(&lengths) { } const std::string & title() const { return *m_title; } const lengths_t & lengths() const { return *m_lengths; } private: const std::string *m_title; const lengths_t *m_lengths; }; class player : public state_machine<player> { enum states { Empty, Open, Stopped, Playing, Paused , initial_state = Empty }; void start_playback(const play &); void open_drawer(const open_close &); void close_drawer(const open_close &); void store_cd_info(const cd_detected &); void stop_playback(const stop &); void pause_playback(const pause &); void resume_playback(const play &); void stop_and_open(const open_close &); friend class state_machine<player>; typedef player p; struct transition_table : mpl::vector // +-----+---------+-------------+---------+---------------------+ // | Row | Start | Event | Next | Action | // +-----+---------+-------------+---------+---------------------+ < srow< Stopped , play , Playing , &p::start_playback > , row < open_close , Open , &p::open_drawer > // +-----+---------+-------------+---------+---------------------+ , srow< Open , open_close , Empty , &p::close_drawer > // +-----+---------+-------------+---------+---------------------+ , srow< Empty , open_close , Open , &p::open_drawer > , row < cd_detected , Stopped , &p::store_cd_info > // +-----+---------+-------------+---------+---------------------+ , srow< Playing , stop , Stopped , &p::stop_playback > , row < pause , Paused , &p::pause_playback > , row < open_close , Open , &p::stop_and_open > // +-----+---------+-------------+---------+---------------------+ , srow< Paused , play , Playing , &p::resume_playback > , row < stop , Stopped , &p::stop_playback > , row < open_close , Open , &p::stop_and_open > // +-----+---------+-------------+---------+---------------------+ > {}; }; void player::start_playback(const play &) { std::cout << "Starting playback...\n"; } void player::open_drawer(const open_close &) { std::cout << "Opening drawer...\n"; } void player::close_drawer(const open_close &) { std::cout << "Closing drawer...\n"; } void player::store_cd_info(const cd_detected & cd) { std::cout << "CD info - title: \"" << cd.title() << "\" track lengths: "; std::ostream_iterator<std::clock_t> out(std::cout, " "); const cd_detected::lengths_t & lengths = cd.lengths(); std::copy(lengths.begin(), lengths.end(), out); std::cout << "\n"; } void player::stop_playback(const stop &) { std::cout << "Stopping playback...\n"; } void player::pause_playback(const pause &) { std::cout << "Pausing playback...\n"; } void player::resume_playback(const play &) { std::cout << "Resuming playback...\n"; } void player::stop_and_open(const open_close &) { std::cout << "Stopping playback and opening drawer...\n"; } int main() { using namespace boost::assign; player p; p.process_event(open_close()); p.process_event(open_close()); cd_detected::lengths_t track_lengths; track_lengths += 10, 20, 30, 60; p.process_event(cd_detected("louie, louie", track_lengths)); p.process_event(play()); p.process_event(pause()); p.process_event(play()); p.process_event(stop()); return 0; } ------------------------------------------------ Addendum by John Wismar I approached this problem differently, because I wanted a different transition table format, that looked like this: // +-----------+----------------------------------------------+ // | Start | | // | +-----------+---------+--------------------+ | // | |Event1 | Next1 | Action1 | | // | |Event2 | Next2 | Action2 | | // +-----------+-----------+---------+--------------------+---+ Here's the source for my implementation. Comments welcome!! #include <boost/mpl/vector.hpp> #include <boost/mpl/fold.hpp> #include <boost/mpl/filter_view.hpp> #include <boost/mpl/placeholders.hpp> #include <ctime> #include <vector> #include <cassert> namespace mpl = boost::mpl; using namespace mpl::placeholders; template <class OnEventEntry> struct on_event_entry_event { typedef typename OnEventEntry::event type; }; template<class StateEntry, class Next, class Event> struct event_dispatcher { typedef typename StateEntry::event_vector event_vector; typedef typename mpl::find_if<event_vector, boost::is_same<Event, on_event_entry_event<_1> > >::type event_entry_iter; typedef typename mpl::deref<event_entry_iter>::type event_entry; typedef typename event_entry::fsm_t fsm_t; static int dispatch(fsm_t &m, int state, Event const &e) { if (state == StateEntry::current_state) { event_entry::execute(m, e); return event_entry::next_state; } else { return Next::dispatch(m, state, e); } } }; template <class MyDerivedFSM> class state_machine; struct default_event_dispatcher { template <class FSM, class Event> static int dispatch(state_machine<FSM> &m, int state, Event const &e) { return m.call_no_transition(state, e); } }; template <class StateEntry, class Event> struct has_event_entry { typedef typename StateEntry::event_vector event_vector; typedef typename mpl::find_if<event_vector, boost::is_same<Event, on_event_entry_event<_1> > >::type event_entry_iter; typedef typename boost::is_same<event_entry_iter, typename mpl::end<event_vector>::type>::type iter_is_end; typedef typename boost::is_same<boost::false_type, iter_is_end>::type type; }; template <class Table, class Event> struct generate_dispatcher : mpl::fold< mpl::filter_view< Table, has_event_entry<_1, Event> >, default_event_dispatcher, event_dispatcher<_2, _1, Event> > {}; template <class MyDerivedFSM> class state_machine { public: template <class Event> int process_event (Event const &e) { typedef typename generate_dispatcher< typename MyDerivedFSM::transition_table, Event>::type dispatcher; this->state_ = dispatcher::dispatch(*static_cast<MyDerivedFSM*>(this), this->state_, e); return this->state_; } template <class Event> int no_transition (int state, Event const &e) { assert(false); return state; } template <class Event> int call_no_transition (int state, Event const &e) { return static_cast<MyDerivedFSM*>(this)->no_transition(state, e); } protected: state_machine () : state_(MyDerivedFSM::initial_state){} ~state_machine () {} template <class Event, int NextState, void (MyDerivedFSM::*action)(Event const&)> struct on_event { static int const next_state = NextState; typedef Event event; typedef MyDerivedFSM fsm_t; static void execute (MyDerivedFSM &fsm, Event const &e) { (fsm.*action)(e); } }; template <int CurrentState, class VectorOfEvents> struct in_state { static int const current_state = CurrentState; typedef VectorOfEvents event_vector; }; private: int state_; }; class Player : public state_machine<Player> { public: Player(){} ~Player(){} //Events struct Play {}; struct OpenClose {}; struct CDDetected { CDDetected(char const*, std::vector<std::clock_t> const&) {} }; struct Stop {}; struct Pause {}; private: //FSM states enum states { Empty, Open, Stopped, Playing, Paused, initial_state=Empty }; //FSM Transition actions void start_playback(Play const&){} void open_drawer(OpenClose const&){} void close_drawer(OpenClose const&){} void store_cd_info(CDDetected const &){} void stop_playback(Stop const&){} void pause_playback(Pause const &){} void resume_playback(Play const&){} void stop_and_open(OpenClose const &){} friend class state_machine<Player>; typedef Player p; struct transition_table : mpl::vector5< // +-----------+----------------------------------------------+ // | Start | | // | +-----------+---------+--------------------+ | // | | Event1 | Next1 | Action1 | | // | | Event2 | Next2 | Action2 | | // +-----------+-----------+---------+--------------------+---+ in_state< Stopped , mpl::vector2< on_event< Play , Playing , &p::start_playback >, on_event< OpenClose , Open , &p::open_drawer > > >, // +-----------+-----------+---------+--------------------+---+ in_state< Open , mpl::vector1< on_event< OpenClose , Empty , &p::close_drawer > > >, // +-----------+-----------+---------+--------------------+---+ in_state< Empty , mpl::vector2< on_event< OpenClose , Open , &p::open_drawer >, on_event< CDDetected, Stopped , &p::store_cd_info > > >, // +-----------+-----------+---------+--------------------+---+ in_state< Playing , mpl::vector3< on_event< Pause , Paused , &p::pause_playback >, on_event< Stop , Stopped , &p::stop_playback >, on_event< OpenClose , Open , &p::stop_and_open > > >, // +-----------+-----------+---------+--------------------+---+ in_state< Paused , mpl::vector3< on_event< Play , Playing , &p::resume_playback>, on_event< Stop , Stopped , &p::stop_playback >, on_event< OpenClose , Open , &p::stop_and_open > > > // +-----------+-----------+---------+--------------------+---+ > {}; }; int main(int argc, char* argv[]) { Player p; p.process_event(Player::OpenClose()); p.process_event(Player::OpenClose()); p.process_event(Player::CDDetected("louie, louie", std::vector<std::clock_t>())); p.process_event(Player::Play()); p.process_event(Player::Pause()); p.process_event(Player::Play()); p.process_event(Player::Stop()); return 0; }