diff --git a/indra/newview/statemachine/aistatemachine.cpp b/indra/newview/statemachine/aistatemachine.cpp index cbe261cc4..bb166a9b5 100644 --- a/indra/newview/statemachine/aistatemachine.cpp +++ b/indra/newview/statemachine/aistatemachine.cpp @@ -92,17 +92,34 @@ void AIStateMachine::updateSettings(void) void AIStateMachine::run(AIStateMachine* parent, state_type new_parent_state, bool abort_parent) { DoutEntering(dc::statemachine, "AIStateMachine::run(" << (void*)parent << ", " << (parent ? parent->state_str(new_parent_state) : "NA") << ", " << abort_parent << ") [" << (void*)this << "]"); - // Must be the first time we're being run. - llassert(!mParent); - llassert(!mCallback); + // Must be the first time we're being run, or we must be called from a callback function. + llassert(!mParent || mState == bs_callback); + llassert(!mCallback || mState == bs_callback); // Can only be run when in this state. - llassert(mState == bs_initialize); - // If a parent is provided, it must be running. - llassert(!parent || parent->mState == bs_run); + llassert(mState == bs_initialize || mState == bs_callback); - mParent = parent; - mNewParentState = new_parent_state; - mAbortParent = abort_parent; + // Allow NULL to be passed as parent to signal that we want to reuse the old one. + if (parent) + { + mParent = parent; + // In that case remove any old callback! + if (mCallback) + { + delete mCallback; + mCallback = NULL; + } + + mNewParentState = new_parent_state; + mAbortParent = abort_parent; + } + + // If abort_parent is requested then a parent must be provided. + llassert(!abort_parent || mParent); + // If a parent is provided, it must be running. + llassert(!mParent || mParent->mState == bs_run); + + // Mark that run() has been called, in case we're being called from a callback function. + mState = bs_initialize; cont(); } @@ -110,14 +127,25 @@ void AIStateMachine::run(AIStateMachine* parent, state_type new_parent_state, bo void AIStateMachine::run(callback_type::signal_type::slot_type const& slot) { DoutEntering(dc::statemachine, "AIStateMachine::run() [" << (void*)this << "]"); - // Must be the first time we're being run. - llassert(!mParent); - llassert(!mCallback); + // Must be the first time we're being run, or we must be called from a callback function. + llassert(!mParent || mState == bs_callback); + llassert(!mCallback || mState == bs_callback); // Can only be run when in this state. - llassert(mState == bs_initialize); + llassert(mState == bs_initialize || mState == bs_callback); + + // Clean up any old callbacks. + mParent = NULL; + if (mCallback) + { + delete mCallback; + mCallback = NULL; + } mCallback = new callback_type(slot); + // Mark that run() has been called, in case we're being called from a callback function. + mState = bs_initialize; + cont(); } @@ -134,17 +162,18 @@ void AIStateMachine::cont(void) DoutEntering(dc::statemachine, "AIStateMachine::cont() [" << (void*)this << "]"); llassert(mIdle); mIdle = false; - if (mQueued) - return; - AIWriteAccess cscm_w(continued_statemachines_and_calling_mainloop); - cscm_w->continued_statemachines.push_back(this); - if (!cscm_w->calling_mainloop) + if (mActive == as_idle) { - Dout(dc::statemachine, "Adding AIStateMachine::mainloop to gIdleCallbacks"); - cscm_w->calling_mainloop = true; - gIdleCallbacks.addFunction(&AIStateMachine::mainloop); + AIWriteAccess cscm_w(continued_statemachines_and_calling_mainloop); + cscm_w->continued_statemachines.push_back(this); + if (!cscm_w->calling_mainloop) + { + Dout(dc::statemachine, "Adding AIStateMachine::mainloop to gIdleCallbacks"); + cscm_w->calling_mainloop = true; + gIdleCallbacks.addFunction(&AIStateMachine::mainloop); + } + mActive = as_queued; } - mQueued = true; } void AIStateMachine::set_state(state_type state) @@ -194,32 +223,44 @@ void AIStateMachine::finish(void) if (mAborted && mAbortParent) { mParent->abort(); + mParent = NULL; } else { mParent->set_state(mNewParentState); } } - mParent = NULL; } - // Set this already to bs_initialize now, so that (bool)*this evaluates to true. - mState = bs_initialize; + // After this (bool)*this evaluates to true and we can call the callback, which then is allowed to call run(). + mState = bs_callback; if (mCallback) { - mCallback->callback(!mAborted); // This can/may call kill(), in which case the whole AIStateMachine will be deleted from the mainloop. - delete mCallback; - mCallback = NULL; + // This can/may call kill() that sets mState to bs_kill and in which case the whole AIStateMachine + // will be deleted from the mainloop, or it may call run() that sets mState is set to bs_initialize + // and might change or reuse mCallback or mParent. + mCallback->callback(!mAborted); + if (mState != bs_initialize) + { + delete mCallback; + mCallback = NULL; + mParent = NULL; + } } - // Restore the request for deletion if we weren't started again from the callback. - if (default_delete && mState == bs_initialize) - mState = bs_killed; + else + { + // Not restarted by callback. Allow run() to be called later on. + mParent = NULL; + } + // Fix the final state. + if (mState == bs_callback) + mState = default_delete ? bs_killed : bs_initialize; } void AIStateMachine::kill(void) { // Should only be called from finish(). - llassert(mIdle && (mState == bs_initialize || mState == bs_finish)); - if (mState == bs_initialize) + llassert(mIdle && (mState == bs_callback || mState == bs_finish)); + if (mState == bs_callback && mActive == as_idle) { // Bump the statemachine onto the active statemachine list, or else it won't be deleted. cont(); @@ -239,6 +280,7 @@ char const* AIStateMachine::state_str(state_type state) AI_CASE_RETURN(bs_run); AI_CASE_RETURN(bs_abort); AI_CASE_RETURN(bs_finish); + AI_CASE_RETURN(bs_callback); AI_CASE_RETURN(bs_killed); } } @@ -300,7 +342,7 @@ void AIStateMachine::mainloop(void*) nonempty = true; active_statemachines.push_back(QueueElement(*iter)); Dout(dc::statemachine, "Adding " << (void*)*iter << " to active_statemachines"); - (*iter)->mQueued = false; + (*iter)->mActive = as_active; } if (nonempty) AIWriteAccess(cscm_r)->continued_statemachines.clear(); @@ -337,6 +379,7 @@ void AIStateMachine::mainloop(void*) if (statemachine.mIdle) { Dout(dc::statemachine, "Erasing " << (void*)&statemachine << " from active_statemachines"); + statemachine.mActive = as_idle; iter = active_statemachines.erase(iter); if (statemachine.mState == bs_killed) { diff --git a/indra/newview/statemachine/aistatemachine.h b/indra/newview/statemachine/aistatemachine.h index 0c905668c..67933d319 100644 --- a/indra/newview/statemachine/aistatemachine.h +++ b/indra/newview/statemachine/aistatemachine.h @@ -75,10 +75,13 @@ // Abort | | Calls abort_impl(). // | | | // v v | -// Finish | Calls finish_impl(), which may call kill() and/or the callback -// | | | function passed to run(), if any, which may call kill() and/or run(). -// | `-------' -// 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. @@ -165,6 +168,8 @@ // // 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); // @@ -177,8 +182,15 @@ class AIStateMachine { 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 @@ -192,7 +204,7 @@ class AIStateMachine { 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(). - bool mQueued; //!< True when the statemachine is queued to be added back to the active list. + 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. // Callback facilities. @@ -220,11 +232,11 @@ class AIStateMachine { public: //! Create a non-running state machine. - AIStateMachine(void) : mState(bs_initialize), mIdle(true), mAborted(true), mQueued(false), mSleep(0), mParent(NULL), mCallback(NULL) { updateSettings(); } + 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 && !mQueued); } + virtual ~AIStateMachine() { llassert(mState == bs_killed && mActive == as_idle); } public: //! Halt the state machine until cont() is called. @@ -319,7 +331,7 @@ class AIStateMachine { // 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 && !mAborted) ? &AIStateMachine::mRunState : 0; } + 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);