From d4591828c87d4e119fa753f4d05bb314d89eb9e0 Mon Sep 17 00:00:00 2001 From: Aleric Inglewood Date: Fri, 10 Feb 2012 01:37:43 +0100 Subject: [PATCH] Finished AIFrameTimer. Added documentation. Added AIFrameTimer::isRunning. Fixed a bug: mCallback was deleted before it was used. --- indra/llcommon/aiframetimer.cpp | 34 +++++++++++++++++++++++++-- indra/llcommon/aiframetimer.h | 41 ++++++++++++++++++++++++--------- 2 files changed, 62 insertions(+), 13 deletions(-) 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); };