// // 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::from_state > Transition::to_state) ? Transition::from_state : Transition::to_state > type; }; // A metafunction computing the maximum state index in the FSM template struct max_state : mpl::fold< typename Fsm::transition_table , mpl::int_ , mpl::if_< mpl::greater,_1> , transition_max_state<_2> , _1 > > {}; // For a given Event type and Fsm, generates a singleton (runtime) // mapping from current state to next-state/action template struct dispatch_table { void warnIng_suppress(); // Some compilers warn when all constructors are private private: typedef void (*action_function)(Fsm&, Event const&); struct cell // The target of the mapping { action_function action; int to_state; }; // Cell initializer function object, used with mpl::for_each struct init_cell { init_cell(dispatch_table* self_) : self(self_) {} template void operator()(Transition) const { self->entries[Transition::from_state].to_state = Transition::to_state; self->entries[Transition::from_state].action = &Transition::execute; } dispatch_table* self; }; // A function that can be used in cells where there is no transition. static void no_transition(Fsm& fsm, Event const& e) { fsm.no_transition(e); } 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::type::value; ++i) { entries[i].action = &no_transition; } // Go back and fill in cells for matching transitions. mpl::for_each< mpl::filter_view< typename Fsm::transition_table , boost::is_same, Event> > >(init_cell(this)); } // The singleton instance. static const dispatch_table instance; public: // data members cell entries[max_state::type::value + 1]; }; template const dispatch_table dispatch_table::instance; // Ultimate base class for all event_dispatchers. struct default_event_dispatcher { void dispatch(); // needed for using declaration below; never defined }; // event_dispatchers for all Event types are assembled in an // inheritance chain, thereby creating an overload set among all // dispatch() functions. Multiple dispatchers for each event may // appear in the same chain; these could be eliminated through more // metaprogramming, but are harmless at runtime. template struct event_dispatcher : Base { typedef typename Transition::event event; typedef typename Transition::fsm_t fsm_t; static int dispatch( fsm_t& fsm, int state, event const& evt) { // Get a reference to the event's dispatch table dispatch_table const& table = dispatch_table::instance; // Call the action table.entries[state].action(fsm,evt); // return the new state. return table.entries[state].to_state; } using Base::dispatch; // create a true overload set }; // A metafunction that generates the dispatcher chain for a given // Transitions table. template struct generate_dispatcher : mpl::fold< Transitions , default_event_dispatcher , event_dispatcher<_2,_1> > { }; // 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) { // Generate the dispatcher type. typedef typename generate_dispatcher< typename Derived::transition_table >::type dispatcher; // Dispatch the event. this->m_state = dispatcher::dispatch( *static_cast(this) , this->m_state , evt ); // Return the new state return this->m_state; } // Getter that returns the current state of the FSM int current_state() const { return this->m_state; } // Default no-transition handler. Can be replaced in the Derived // FSM class. template void no_transition(Event const& e) { BOOST_ASSERT(false); } 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 From , class Event , int To , void (Derived::*action)(Event const&) > struct row { BOOST_STATIC_CONSTANT(int, from_state = From); BOOST_STATIC_CONSTANT(int, to_state = To); typedef Event event; typedef Derived fsm_t; // Do the transition action. static void execute(Derived& fsm, Event const& evt) { (fsm.*action)(evt); } }; private: int m_state; }; namespace // Concrete FSM implementation { // events struct play {}; struct stop {}; struct pause {}; struct drawer {}; // 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 { #if defined(__MWERKS__) // tested at 0x3003 public: #endif // transition actions void start_playback(play const&) { std::cout << "player::start_playback\n"; } void open_drawer(drawer const&) { std::cout << "player::open_drawer\n"; } void close_drawer(drawer 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(drawer const&) { std::cout << "player::stop_and_open\n"; } public: friend class state_machine; enum states { empty, open, stopped, playing, paused , initial_state = empty }; typedef player p; // makes transition table cleaner // Transition table typedef mpl::vector11< // Source Event Target Action // +---------+-------------+---------+---------------------+ row < stopped , play , playing , &p::start_playback >, // +---------+-------------+---------+---------------------+ row < stopped , drawer , open , &p::open_drawer >, // +---------+-------------+---------+---------------------+ row < open , drawer , empty , &p::close_drawer >, // +---------+-------------+---------+---------------------+ row < empty , drawer , 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 , drawer , open , &p::stop_and_open >, // +---------+-------------+---------+---------------------+ row < paused , play , playing , &p::resume_playback >, // +---------+-------------+---------+---------------------+ row < paused , stop , stopped , &p::stop_playback >, // +---------+-------------+---------+---------------------+ row < paused , drawer , open , &p::stop_and_open > // +---------+-------------+---------+---------------------+ > transition_table; }; // // 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(drawer()); pstate(p); p.process_event(drawer()); 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; }