/** * @file aistatemachine.h * @brief State machine base class * * Copyright (c) 2010, Aleric Inglewood. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution. * * CHANGELOG * and additional copyright holders. * * 01/03/2010 * Initial version, written by Aleric Inglewood @ SL */ #ifndef AISTATEMACHINE_H #define AISTATEMACHINE_H #include "aithreadsafe.h" #include "llfasttimer.h" #include //! // A AIStateMachine is a base class that allows derived classes to // go through asynchronous states, while the code still appears to // be more or less sequential. // // These state machine objects can be reused to build more complex // objects. // // It is important to note that each state has a duality: the object // can have a state that will cause a corresponding function to be // called; and often that function will end with changing the state // again, to signal that it was handled. It is easy to confuse the // function of a state with the state at the end of the function. // For example, the state "initialize" could cause the member // function 'init()' to be called, and at the end one would be // inclined to set the state to "initialized". However, this is the // wrong approach: the correct use of state names does reflect the // functions that will be called, never the function that just was // called. // // Each (derived) class goes through a series of states as follows: // // Creation // | // v // (idle) <----. Idle until run() is called. // | | // Initialize | Calls initialize_impl(). // | | // | (idle) | Idle until cont() or set_state() is called. // | | ^ | // v v | | // .-------. | | // | Run |_, | Call multiplex_impl() until idle(), abort() or finish() is called. // '-------' | // | | | // v | | // Abort | | Calls abort_impl(). // | | | // v v | // Finish | Calls finish_impl() (which may call kill()) or // | | | the callback function passed to run(), if any, // | v | // | Callback | which may call kill() and/or run(). // | | | | // | | `-----' // v v // Killed Delete the statemachine (all statemachines must be allocated with new). // // Each state causes corresponding code to be called. // Finish cleans up whatever is done by Initialize. // Abort should clean up additional things done by Run. // // The Run state is entered by calling run(). // // While the base class is in the Run state, it is the derived class // that goes through different states. The state variable of the derived // class is only valid while the base class is in the state Run. // // A derived class can exit the Run state by calling one of two methods: // abort() in case of failure, or finish() in case of success. // Respectively these set the state to Abort and Finish. // // finish_impl may call kill() for a (default) destruction upon finish. // Even in that case the callback (passed to run()) may call run() again, // which overrides the request for a default kill. Or, if finish_impl // doesn't call kill() the callback may call kill() to request the // destruction of the state machine object. // // State machines are run from the "idle" part of the viewer main loop. // Often a state machine has nothing to do however. In that case it can // call the method idle(). This will stop the state machine until // external code changes it's state (by calling set_state()), or calls // cont() to continue with the last state. // // The methods of the derived class call set_state() to change their // own state within the bs_run state, or by calling either abort() // or finish(). // // Restarting a finished state machine can also be done by calling run(), // which will cause a re-initialize. // // Derived classes should implement the following constants: // // static state_type const min_state = first_state; // static state_type const max_state = last_state + 1; // // Where first_state should be equal to BaseClass::max_state. // These should represent the minimum and (one past) the maximum // values of mRunState. // // virtual void initialize_impl(void) // // Initializes the derived class. // // virtual void multiplex_impl(void); // // This method should handle mRunState in a switch. // For example: // // switch(mRunState) // { // case foo: // handle_foo(); // break; // case wait_state: // if (still_waiting()) // { // idle(); // break; // } // set_state(working); // /*fall-through*/ // case working: // do_work(); // if (failure()) // abort(); // break; // case etc: // etc(); // finish(); // break; // } // // virtual void abort_impl(void); // // A call to this method should bring the object to a state // where finish_impl() can be called. // // virtual void finish_impl(void); // // Should cleanup whatever init_impl() did, or any of the // states of the object where multiplex_impl() calls finish(). // Call kill() from here to make that the default behavior // (state machine is deleted unless the callback calls run()). // // virtual char const* state_str_impl(state_type run_state); // // Should return a stringified value of run_state. // class AIStateMachine { //! The type of mState enum base_state_type { bs_initialize, bs_run, bs_abort, bs_finish, bs_callback, bs_killed }; //! The type of mActive enum active_type { as_idle, // State machine is on neither list. as_queued, // State machine is on continued_statemachines list. as_active // State machine is on active_statemachines list. }; public: typedef U32 state_type; //!< The type of mRunState //! Integral value equal to the state with the lowest value. static state_type const min_state = bs_initialize; //! Integral value one more than the state with the highest value. static state_type const max_state = bs_killed + 1; private: base_state_type mState; //!< State of the base class. bool mIdle; //!< True if this state machine is not running. bool mAborted; //!< True after calling abort() and before calling run(). active_type mActive; //!< Whether statemachine is idle, queued to be added to the active list, or already on the active list. S64 mSleep; //!< Non-zero while the state machine is sleeping. LLMutex mIdleActive; //!< Used for atomic operations on the pair mIdle / mActive. // Callback facilities. // From within an other state machine: AIStateMachine* mParent; //!< The parent object that started this state machine, or NULL if there isn't any. state_type mNewParentState; //!< The state at which the parent should continue upon a successful finish. bool mAbortParent; //!< If true, abort parent on abort(). Otherwise continue as normal. // From outside a state machine: struct callback_type { typedef boost::signals2::signal signal_type; callback_type(signal_type::slot_type const& slot) { connection = signal.connect(slot); } ~callback_type() { connection.disconnect(); } void callback(bool success) const { signal(success); } private: boost::signals2::connection connection; signal_type signal; }; callback_type* mCallback; //!< Pointer to signal/connection, or NULL when not connected. static AIThreadSafeSimpleDC sMaxCount; //!< Number of cpu clocks below which we start a new state machine within the same frame. protected: //! State of the derived class. Only valid if mState == bs_run. Call set_state to change. state_type mRunState; public: //! Create a non-running state machine. AIStateMachine(void) : mState(bs_initialize), mIdle(true), mAborted(true), mActive(as_idle), mSleep(0), mParent(NULL), mCallback(NULL) { updateSettings(); } protected: //! The user should call 'kill()', not delete a AIStateMachine (derived) directly. virtual ~AIStateMachine() { llassert(mState == bs_killed && mActive == as_idle); } public: //! Halt the state machine until cont() is called. void idle(void); //! Temporarily halt the state machine. void yield_frame(unsigned int frames) { mSleep = -(S64)frames; } //! Temporarily halt the state machine. void yield_ms(unsigned int ms) { mSleep = LLFastTimer::getCPUClockCount64() + LLFastTimer::countsPerSecond() * ms / 1000; } //! Continue running after calling idle. void cont(void); //--------------------------------------- // Changing the state. //! Change state to bs_run. May only be called after creation or after returning from finish(). // If parent is non-NULL, change the parent state machine's state to new_parent_state // upon finish, or in the case of an abort and when abort_parent is true, call parent->abort() instead. void run(AIStateMachine* parent, state_type new_parent_state, bool abort_parent = true); //! Change state to 'bs_run'. May only be called after creation or after returning from finish(). // Does not cause a callback. void run(void) { run(NULL, 0, false); } //! The same as above, but pass the result of a boost::bind with _1. // // Here _1, if present, will be replaced with a bool indicating success. // // For example: // // // struct Foo { void callback(AIStateMachineDerived* ptr, bool success); }; // ... // AIStateMachineDerived* magic = new AIStateMachineDerived; // Deleted by callback // // Call foo_ptr->callback(magic, _1) on finish. // state_machine->run(boost::bind(&Foo::callback, foo_ptr, magic, _1)); // // // or // // // struct Foo { void callback(bool success, AIStateMachineDerived const& magic); }; // ... // AIStateMachineDerived magic; // // Call foo_ptr->callback(_1, magic) on finish. // magic.run(boost::bind(&Foo::callback, foo_ptr, _1, magic)); // // // or // // // static void callback(void* userdata); // ... // AIStateMachineDerived magic; // // Call callback(userdata) on finish. // magic.run(boost::bind(&callback, userdata)); // void run(callback_type::signal_type::slot_type const& slot); //! Change state to 'bs_abort'. May only be called while in the bs_run state. void abort(void); //! Change state to 'bs_finish'. May only be called while in the bs_run state. void finish(void); //! Refine state while in the bs_run state. May only be called while in the bs_run state. void set_state(state_type run_state); //! Change state to 'bs_killed'. May only be called while in the bs_finish state. void kill(void); //--------------------------------------- // Other. //! Called whenever the StateMachineMaxTime setting is changed. static void updateSettings(void); //--------------------------------------- // Accessors. //! Return true if state machine was aborted (can be used in finish_impl). bool aborted(void) const { return mAborted; } //! Return true if the derived class is running (also when we are idle). bool running(void) const { return mState == bs_run; } //! Return true if the derived class is running but idle. bool waiting(void) const { return mState == bs_run && mIdle; } // Use some safebool idiom (http://www.artima.com/cppsource/safebool.html) rather than operator bool. typedef state_type AIStateMachine::* const bool_type; //! Return true if state machine successfully finished. operator bool_type() const { return ((mState == bs_initialize || mState == bs_callback) && !mAborted) ? &AIStateMachine::mRunState : 0; } //! Return a stringified state, for debugging purposes. char const* state_str(state_type state); private: static void mainloop(void*); void multiplex(U64 current_time); protected: //--------------------------------------- // Derived class implementations. // Handle initializing the object. virtual void initialize_impl(void) = 0; // Handle mRunState. virtual void multiplex_impl(void) = 0; // Handle aborting from current bs_run state. virtual void abort_impl(void) = 0; // Handle cleaning up from initialization (or post abort) state. virtual void finish_impl(void) = 0; // Implemenation of state_str for run states. virtual char const* state_str_impl(state_type run_state) const = 0; }; // This case be used in state_str_impl. #define AI_CASE_RETURN(x) do { case x: return #x; } while(0) #endif