diff --git a/indra/llcommon/aiframetimer.cpp b/indra/llcommon/aiframetimer.cpp index cb57bb701..3c102a9c7 100644 --- a/indra/llcommon/aiframetimer.cpp +++ b/indra/llcommon/aiframetimer.cpp @@ -27,8 +27,38 @@ * - Initial version, written by Aleric Inglewood @ SL */ -#include "linden_common.h" +// An AIFrameTimer object provides a callback API for timer events. +// +// Typical usage: +// +// // Any thread. +// AIFrameTimer timer; +// +// ... +// // Any thread (after successful construction is guaranteed). +// timer.create(5.5, boost::bind(&the_callback, )); // Call the_callback() in 5.5 seconds. +// +// The callback function is always called by the main thread and should therefore +// be light weight. +// +// If timer.cancel() is called before the timer expires, then the callback +// function isn't called. If cancel() is called by another thread than the +// main thread, then it is possible that the callback function is called +// even while still inside cancel(), but as soon as cancel() returned it +// is guarenteed that the callback function won't be called anymore. +// Hence, if the callback function is a member of some object then +// cancel() must be called before the destruction of that object (ie from +// it's destructor). Calling cancel() multiple times is not a problem. +// Note: if cancel() is called while the callback function is being +// called then cancel() will block until the callback function returned. +// +// The timer object can be reused (by calling create() again), but +// only after either the callback function was called, or after cancel() +// returned. Most notably, you can call create() again from inside the +// callback function to "restart" the timer. +// +#include "linden_common.h" #include "aiframetimer.h" static F64 const NEVER = 1e16; // 317 million years. @@ -115,7 +145,7 @@ void AIFrameTimer::handleExpiration(F64 current_frame_time) // function here because the trylock fails. // // Assuming the main thread arrived here, there are two possible states - // for the other thread that tries to delete the call back function, + // for the other thread that tries to delete the callback function, // right after calling the cancel() function too: // // 1. It hasn't obtained the first lock yet, we obtain the handle.mMutex diff --git a/indra/llcommon/aiframetimer.h b/indra/llcommon/aiframetimer.h index 3b997560d..cb50caec5 100644 --- a/indra/llcommon/aiframetimer.h +++ b/indra/llcommon/aiframetimer.h @@ -61,20 +61,37 @@ class LL_COMMON_API AIFrameTimer // See aiframetimer.cpp for more notes. class AIRunningFrameTimer { private: - F64 mExpire; // Time at which the timer expires, in seconds since application start (compared to LLFrameTimer::sFrameTime). - Signal* mCallback; - AIFrameTimer* mTimer; + F64 mExpire; // Time at which the timer expires, in seconds since application start (compared to LLFrameTimer::sFrameTime). + AIFrameTimer* mTimer; // The actual timer. + // Can be mutable, since only the mExpire is used for ordering this object in the multiset AIFrameTimer::sTimerList. + mutable Signal* mCallback; // Pointer to callback struct, or NULL when the object wasn't added to sTimerList yet. public: - AIRunningFrameTimer(F64 expiration, AIFrameTimer* timer) : mExpire(LLFrameTimer::getElapsedSeconds() + expiration), mCallback(new Signal), mTimer(timer) { } + AIRunningFrameTimer(F64 expiration, AIFrameTimer* timer) : mExpire(LLFrameTimer::getElapsedSeconds() + expiration), mCallback(NULL), mTimer(timer) { } ~AIRunningFrameTimer() { delete mCallback; } - void init(signal_type::slot_type const& slot) const { mCallback->mSignal.connect(slot); } + // This function is called after the final object was added to sTimerList (where it is initialized in-place). + void init(signal_type::slot_type const& slot) const + { + // We may only call init() once. + llassert(!mCallback); + mCallback = new Signal; + mCallback->mSignal.connect(slot); + } + + // Order AIFrameTimer::sTimerList so that the timer that expires first is up front. friend bool operator<(AIRunningFrameTimer const& ft1, AIRunningFrameTimer const& ft2) { return ft1.mExpire < ft2.mExpire; } void do_callback(void) const { mCallback->mSignal(); } F64 expiration(void) const { return mExpire; } AIFrameTimer* getTimer(void) const { return mTimer; } + +#if LL_DEBUG + // May not copy this object after it was initialized. + AIRunningFrameTimer(AIRunningFrameTimer const& running_frame_timer) : + mExpire(running_frame_timer.mExpire), mCallback(running_frame_timer.mCallback), mTimer(running_frame_timer.mTimer) + { llassert(!mCallback); } +#endif }; typedef std::multiset timer_list_type; @@ -98,12 +115,12 @@ class LL_COMMON_API AIFrameTimer // Actual initialization used by AIFrameTimer::create. void init(timer_list_type::iterator const& running_timer, signal_type::slot_type const& slot) - { - // Locking AIFrameTimer::sMutex is not neccessary here, because we're creating - // the object and no other thread knows of mRunningTimer at this point. - mRunningTimer = running_timer; - mRunningTimer->init(slot); - } + { + // Locking AIFrameTimer::sMutex is not neccessary here, because we're creating + // the object and no other thread knows of mRunningTimer at this point. + mRunningTimer = running_timer; + mRunningTimer->init(slot); + } private: // LLMutex has no assignment operator. @@ -129,6 +146,8 @@ class LL_COMMON_API AIFrameTimer void create(F64 expiration, signal_type::slot_type const& slot); void cancel(void); + bool isRunning(void) const { bool running; sMutex.lock(); running = mHandle.mRunningTimer != sTimerList.end(); sMutex.unlock(); return running; } + protected: static void handleExpiration(F64 current_frame_time); }; diff --git a/indra/llinventory/llsaleinfo.cpp b/indra/llinventory/llsaleinfo.cpp index b7afb28ad..e51e35e6e 100644 --- a/indra/llinventory/llsaleinfo.cpp +++ b/indra/llinventory/llsaleinfo.cpp @@ -30,8 +30,8 @@ * $/LicenseInfo$ */ -#include #include "linden_common.h" +#include #include "llsaleinfo.h" diff --git a/indra/newview/statemachine/CMakeLists.txt b/indra/newview/statemachine/CMakeLists.txt index a95431b0f..86f19107e 100644 --- a/indra/newview/statemachine/CMakeLists.txt +++ b/indra/newview/statemachine/CMakeLists.txt @@ -39,6 +39,7 @@ set(statemachine_SOURCE_FILES aifilepicker.cpp aifetchinventoryfolder.cpp aievent.cpp + aitimer.cpp ) set(statemachine_HEADER_FILES @@ -48,6 +49,7 @@ set(statemachine_HEADER_FILES aidirpicker.h aifetchinventoryfolder.h aievent.h + aitimer.h ) set_source_files_properties(${statemachine_HEADER_FILES} 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); diff --git a/indra/newview/statemachine/aitimer.cpp b/indra/newview/statemachine/aitimer.cpp new file mode 100644 index 000000000..5a37a6991 --- /dev/null +++ b/indra/newview/statemachine/aitimer.cpp @@ -0,0 +1,96 @@ +/** + * @file aitimer.cpp + * @brief Implementation of AITimer + * + * Copyright (c) 2012, 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. + * + * 07/02/2012 + * Initial version, written by Aleric Inglewood @ SL + */ + +#include "linden_common.h" +#include "aitimer.h" + +enum timer_state_type { + AITimer_start = AIStateMachine::max_state, + AITimer_expired +}; + +char const* AITimer::state_str_impl(state_type run_state) const +{ + switch(run_state) + { + AI_CASE_RETURN(AITimer_start); + AI_CASE_RETURN(AITimer_expired); + } + return "UNKNOWN STATE"; +} + +void AITimer::initialize_impl(void) +{ + llassert(!mFrameTimer.isRunning()); + set_state(AITimer_start); +} + +void AITimer::expired(void) +{ + set_state(AITimer_expired); +} + +void AITimer::multiplex_impl(void) +{ + switch (mRunState) + { + case AITimer_start: + { + mFrameTimer.create(mInterval, boost::bind(&AITimer::expired, this)); + idle(); + break; + } + case AITimer_expired: + { + finish(); + break; + } + } +} + +void AITimer::abort_impl(void) +{ + mFrameTimer.cancel(); +} + +void AITimer::finish_impl(void) +{ + // Kill object by default. + // This can be overridden by calling run() from the callback function. + kill(); +} + +void AIPersistentTimer::finish_impl(void) +{ + // Don't kill object by default. + if (aborted()) + kill(); + // Callback function should always call kill() or run(). +} diff --git a/indra/newview/statemachine/aitimer.h b/indra/newview/statemachine/aitimer.h new file mode 100644 index 000000000..06d3e2ef4 --- /dev/null +++ b/indra/newview/statemachine/aitimer.h @@ -0,0 +1,118 @@ +/** + * @file aitimer.h + * @brief Generate a timer event + * + * Copyright (c) 2012, 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. + * + * 07/02/2012 + * Initial version, written by Aleric Inglewood @ SL + */ + +#ifndef AITIMER_H +#define AITIMER_H + +#include "aistatemachine.h" +#include "aiframetimer.h" + +// A timer state machine. +// +// Before calling run(), call setInterval() to pass needed parameters. +// +// When the state machine finishes it calls the callback, use parameter _1, +// (success) to check whether or not the statemachine actually timed out or +// was cancelled. The boolean is true when it expired and false if the +// state machine was aborted. +// +// Objects of this type can be reused multiple times, see +// also the documentation of AIStateMachine. +// +// Typical usage: +// +// AITimer* timer = new AITimer; +// +// timer->setInterval(5.5); // 5.5 seconds time out interval. +// timer->run(...); // Start timer and pass callback; see AIStateMachine. +// +// The default behavior is to call the callback and then delete the AITimer object. +// One can call run() again from the callback function to get a repeating expiration. +// You can call run(...) with parameters too, but using run() without parameters will +// just reuse the old ones (call the same callback). +// +class AITimer : public AIStateMachine { + private: + AIFrameTimer mFrameTimer; //!< The actual timer that this object wraps. + F64 mInterval; //!< Input variable: interval after which the event will be generated, in seconds. + + public: + AITimer(void) : mInterval(0) { DoutEntering(dc::statemachine, "AITimer(void) [" << (void*)this << "]"); } + + /** + * @brief Set the interval after which the timer should expire. + * + * @param interval Amount of time in seconds before the timer will expire. + * @param True if the timer should be deleted after it expires; false means it will keep firing at regular intervals. + * + * Call abort() at any time to stop the timer (and delete the AITimer object). + */ + void setInterval(F64 interval) { mInterval = interval; } + + /** + * @brief Get the expiration interval. + * + * @returns expiration interval in seconds. + */ + F64 getInterval(void) const { return mInterval; } + + protected: + // Call finish() (or abort()), not delete. + /*virtual*/ ~AITimer() { DoutEntering(dc::statemachine, "~AITimer() [" << (void*)this << "]"); mFrameTimer.cancel(); } + + // Handle initializing the object. + /*virtual*/ void initialize_impl(void); + + // Handle mRunState. + /*virtual*/ void multiplex_impl(void); + + // Handle aborting from current bs_run state. + /*virtual*/ void abort_impl(void); + + // Handle cleaning up from initialization (or post abort) state. + /*virtual*/ void finish_impl(void); + + // Implemenation of state_str for run states. + /*virtual*/ char const* state_str_impl(state_type run_state) const; + + private: + // This is the callback for mFrameTimer. + void expired(void); +}; + +// Same as above but does not delete itself automatically by default after use. +// Call kill() on it yourself (from the callback function) when you're done with it! +class AIPersistentTimer : public AITimer { + protected: + // Handle cleaning up from initialization (or post abort) state. + /*virtual*/ void finish_impl(void); +}; + +#endif