// // Copyright 2003-2004 David Abrahams and Aleksey Gurtovoy. All Rights Reserved // #include "boost/mpl/int.hpp" #include "boost/mpl/fold.hpp" #include "boost/mpl/prior.hpp" #include "boost/mpl/count.hpp" #include "boost/mpl/insert.hpp" #include #include #include #include "boost/mpl/vector/vector20.hpp" #include "boost/assert.hpp" #include #include #include #include #if defined(BOOST_DINKUMWARE_STDLIB) && BOOST_DINKUMWARE_STDLIB < 310 namespace std { using ::clock_t; } #endif namespace mpl = boost::mpl; using namespace mpl::placeholders; // A metafunction that returns the Event associated with a transition. template struct transition_event { typedef typename Transition::event type; }; // A metafunction computing the maximum of a transition's source and // end states. template struct transition_max_state { typedef typename mpl::int_< (Transition::current_state > Transition::next_state) ? Transition::current_state : Transition::next_state > type; }; template class state_machine; // Generates a singleton runtime lookup table that maps current state // to a function that makes the FSM take its transition on the given // Event type. template struct dispatch_table { private: // This is a table of these function pointers. typedef int (*cell)(Fsm&, int, Event const&); // Compute the maximum state value in the Fsm so we know how big // to make the table BOOST_STATIC_CONSTANT( int, max_state = ( mpl::fold , mpl::if_< mpl::greater,_1> , transition_max_state<_2> , _1 > >::type::value ) ); // A function object for use with mpl::for_each that stuffs // transitions into cells. struct init_cell { init_cell(dispatch_table* self_) : self(self_) {} // Cell initializer function object, used with mpl::for_each template void operator()(Transition const&) const { self->entries[Transition::current_state] = &Transition::execute; } dispatch_table* self; }; public: // initialize the dispatch table for a given Event and Fsm dispatch_table() { // Initialize cells for no transition for (int i = 0; i <= max_state; ++i) { // VC7.1 seems to need the two-phase assignment. cell call_no_transition = &state_machine::call_no_transition; entries[i] = call_no_transition; } // Go back and fill in cells for matching transitions. mpl::for_each< mpl::filter_view< Stt , boost::is_same, Event> > >(init_cell(this)); } // The singleton instance. static const dispatch_table instance; public: // data members cell entries[max_state + 1]; }; // This declares the statically-initialized dispatch_table instance. template const dispatch_table dispatch_table::instance; // CRTP base class for state machines. Pass the actual FSM class as // the Derived parameter. template class state_machine { public: // Member functions // Main function used by clients of the derived FSM to make // transitions. template int process_event(Event const& evt) { typedef typename Derived::transition_table stt; typedef dispatch_table table; // Call the action return this->m_state = table::instance.entries[this->m_state]( *static_cast(this), this->m_state, evt); } // Getter that returns the current state of the FSM int current_state() const { return this->m_state; } private: template friend class dispatch_table; template static int call_no_transition(Derived& fsm, int state, Event const& e) { return fsm.no_transition(state, e); } // Default no-transition handler. Can be replaced in the Derived // FSM class. template int no_transition(int state, Event const& e) { BOOST_ASSERT(false); return state; } protected: // interface for the derived class template state_machine(State state) // Construct with an initial state : m_state(state) { } state_machine() : m_state(Derived::initial_state) // Construct with the default initial_state { } // Template used to form rows in the transition table template< int CurrentState , class Event , int NextState , void (Derived::*action)(Event const&) > struct row { BOOST_STATIC_CONSTANT(int, current_state = CurrentState); BOOST_STATIC_CONSTANT(int, next_state = NextState); typedef Event event; // Take the transition action and return the next state. static int execute(Derived& fsm, int state, Event const& evt) { BOOST_ASSERT(state == current_state); (fsm.*action)(evt); return next_state; } }; private: // data members int m_state; }; namespace // Concrete FSM implementation { // events struct play {}; struct stop {}; struct pause {}; struct open_close {}; // A "complicated" event type that carries some data. struct cd_detected { cd_detected(std::string name, std::vector durations) : name(name) , track_durations(durations) {} std::string name; std::vector track_durations; }; // Concrete FSM implementation class player : public state_machine { // The list of FSM states enum states { Empty, Open, Stopped, Playing, Paused , initial_state = Empty }; #ifdef __MWERKS__ public: // Codewarrior bug workaround. Tested at 0x3202 #endif // transition actions void start_playback(play const&) { std::cout << "player::start_playback\n"; } void open_drawer(open_close const&) { std::cout << "player::open_drawer\n"; } void close_drawer(open_close const&) { std::cout << "player::close_drawer\n"; } void store_cd_info(cd_detected const&) { std::cout << "player::store_cd_info\n"; } void stop_playback(stop const&) { std::cout << "player::stop_playback\n"; } void pause_playback(pause const&) { std::cout << "player::pause_playback\n"; } void resume_playback(play const&) { std::cout << "player::resume_playback\n"; } void stop_and_open(open_close const&) { std::cout << "player::stop_and_open\n"; } #ifdef __MWERKS__ private: #endif friend class state_machine; typedef player p; // makes transition table cleaner // Transition table struct transition_table : mpl::vector11< // Start Event Next Action // +---------+-------------+---------+---------------------+ row < Stopped , play , Playing , &p::start_playback >, row < Stopped , open_close , Open , &p::open_drawer >, // +---------+-------------+---------+---------------------+ row < Open , open_close , Empty , &p::close_drawer >, // +---------+-------------+---------+---------------------+ row < Empty , open_close , Open , &p::open_drawer >, row < Empty , cd_detected , Stopped , &p::store_cd_info >, // +---------+-------------+---------+---------------------+ row < Playing , stop , Stopped , &p::stop_playback >, row < Playing , pause , Paused , &p::pause_playback >, row < Playing , open_close , Open , &p::stop_and_open >, // +---------+-------------+---------+---------------------+ row < Paused , play , Playing , &p::resume_playback >, row < Paused , stop , Stopped , &p::stop_playback >, row < Paused , open_close , Open , &p::stop_and_open > // +---------+-------------+---------+---------------------+ > {}; // Replaces the default no-transition response. template int no_transition(int state, Event const& e) { std::cout << "no transition from state " << state << " on event " << typeid(e).name() << std::endl; return state; } }; // // Testing utilities. // static char const* const state_names[] = { "Empty", "Open", "Stopped", "Playing", "Paused" }; void pstate(player const& p) { std::cout << " -> " << state_names[p.current_state()] << std::endl; } void test() { player p; p.process_event(open_close()); pstate(p); p.process_event(open_close()); pstate(p); p.process_event( cd_detected( "louie, louie" , std::vector( /* track lengths */ ) ) ); pstate(p); p.process_event(play()); pstate(p); p.process_event(pause()); pstate(p); p.process_event(play()); pstate(p); p.process_event(stop()); pstate(p); } } int main() { test(); return 0; }