From e95ee858044d75fd4ce8e2799a22b5dc7bc29ccc Mon Sep 17 00:00:00 2001 From: Aleric Inglewood Date: Mon, 8 Aug 2011 19:21:27 +0200 Subject: [PATCH] Make LLFrameTimer thread-safe. LLFrameTimer::sFrameTime is accessed by the texture thread as well. Although the only consequences are that it's possible for a timer in the texture thread to time out too early (or to never time out when it's started) when it reads this variable at the same time as that it is updated, which is pretty inlikely, it's just not-done to leave anything thread-unsafe when it's known to be thread-unsafe. This patch also adds a framework for AIFrameTimer, but that isn't implemented yet. --- indra/llcommon/CMakeLists.txt | 2 + indra/llcommon/aiframetimer.cpp | 39 ++++++++++++ indra/llcommon/aiframetimer.h | 54 +++++++++++++++++ indra/llcommon/llframetimer.cpp | 97 +++++++++++++++++------------- indra/llcommon/llframetimer.h | 101 +++++++++++++++++++++++--------- indra/newview/llappviewer.cpp | 3 + 6 files changed, 227 insertions(+), 69 deletions(-) create mode 100644 indra/llcommon/aiframetimer.cpp create mode 100644 indra/llcommon/aiframetimer.h diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index f08ab8892..d0576212c 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -15,6 +15,7 @@ include_directories( ) set(llcommon_SOURCE_FILES + aiframetimer.cpp aiaprpool.cpp imageids.cpp indra_constants.cpp @@ -84,6 +85,7 @@ set(llcommon_SOURCE_FILES set(llcommon_HEADER_FILES CMakeLists.txt + aiframetimer.h aiaprpool.h aithreadsafe.h bitpack.h diff --git a/indra/llcommon/aiframetimer.cpp b/indra/llcommon/aiframetimer.cpp new file mode 100644 index 000000000..66da2f02c --- /dev/null +++ b/indra/llcommon/aiframetimer.cpp @@ -0,0 +1,39 @@ +/** + * @file aiframetimer.cpp + * + * Copyright (c) 2011, 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. + * + * 06/08/2011 + * - Initial version, written by Aleric Inglewood @ SL + */ + +#include "linden_common.h" + +#include "aiframetimer.h" + +F64 AIFrameTimer::sNextExpiration; + +void AIFrameTimer::handleExpiration(F64 current_frame_time) +{ +} + diff --git a/indra/llcommon/aiframetimer.h b/indra/llcommon/aiframetimer.h new file mode 100644 index 000000000..c3e380d69 --- /dev/null +++ b/indra/llcommon/aiframetimer.h @@ -0,0 +1,54 @@ +/** + * @file aiframetimer.h + * @brief Implementation of AIFrameTimer. + * + * Copyright (c) 2011, 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. + * + * 05/08/2011 + * Initial version, written by Aleric Inglewood @ SL + */ + +#ifndef AIFRAMETIMER_H +#define AIFRAMETIMER_H + +#include "llframetimer.h" + +class LL_COMMON_API AIFrameTimer +{ + private: + F64 mExpire; // Time at which the timer expires, in seconds since application start (compared to LLFrameTimer::sFrameTime). + static std::set sTimerList; // List with all running timers. + + friend class LLFrameTimer; + static F64 sNextExpiration; // Cache of smallest value in sTimerList. + + public: + AIFrameTimer(F64 expiration) : mExpire(LLFrameTimer::getElapsedSeconds() + expiration) { } + + static void handleExpiration(F64 current_frame_time); + + friend bool operator<(AIFrameTimer const& ft1, AIFrameTimer const& ft2) { return ft1.mExpire < ft2.mExpire; } +}; + + +#endif diff --git a/indra/llcommon/llframetimer.cpp b/indra/llcommon/llframetimer.cpp index d05b9875c..73059eca3 100644 --- a/indra/llcommon/llframetimer.cpp +++ b/indra/llcommon/llframetimer.cpp @@ -35,6 +35,8 @@ #include "u64.h" #include "llframetimer.h" +#include "aiframetimer.h" +#include "aiaprpool.h" // Local constants. static F64 const USEC_PER_SECOND = 1000000.0; @@ -44,47 +46,46 @@ static F64 const USEC_TO_SEC_F64 = 0.000001; U64 const LLFrameTimer::sStartTotalTime = totalTime(); // Application start in microseconds since epoch. U64 LLFrameTimer::sTotalTime = LLFrameTimer::sStartTotalTime; // Current time in microseconds since epoch, updated at least once per frame. F64 LLFrameTimer::sTotalSeconds = // Current time in seconds since epoch, updated together with LLFrameTimer::sTotalTime. - U64_to_F64(LLFrameTimer::sTotalTime) * USEC_TO_SEC_F64; + U64_to_F64(LLFrameTimer::sTotalTime) * USEC_TO_SEC_F64; F64 LLFrameTimer::sFrameTime = 0.0; // Current time in seconds since application start, updated together with LLFrameTimer::sTotalTime. // Updated exactly once per frame: S32 LLFrameTimer::sFrameCount = 0; // Current frame number (number of frames since application start). U64 LLFrameTimer::sPrevTotalTime = LLFrameTimer::sStartTotalTime; // Previous (frame) time in microseconds since epoch, updated once per frame. U64 LLFrameTimer::sFrameDeltaTime = 0; // Microseconds between last two calls to LLFrameTimer::updateFrameTimeAndCount. +// Mutex for the above. +apr_thread_mutex_t* LLFrameTimer::sGlobalMutex; // static -void LLFrameTimer::updateFrameTime() +void LLFrameTimer::global_initialization(void) { + apr_thread_mutex_create(&sGlobalMutex, APR_THREAD_MUTEX_UNNESTED, AIAPRRootPool::get()()); +} + +// static +void LLFrameTimer::updateFrameTime(void) +{ + llassert(is_main_thread()); sTotalTime = totalTime(); sTotalSeconds = U64_to_F64(sTotalTime) * USEC_TO_SEC_F64; - sFrameTime = U64_to_F64(sTotalTime - sStartTotalTime) * USEC_TO_SEC_F64; -} + F64 new_frame_time = U64_to_F64(sTotalTime - sStartTotalTime) * USEC_TO_SEC_F64; + apr_thread_mutex_lock(sGlobalMutex); + sFrameTime = new_frame_time; + apr_thread_mutex_unlock(sGlobalMutex); +} // static -void LLFrameTimer::updateFrameTimeAndCount() +void LLFrameTimer::updateFrameTimeAndCount(void) { updateFrameTime(); sFrameDeltaTime = sTotalTime - sPrevTotalTime; sPrevTotalTime = sTotalTime; ++sFrameCount; -} -void LLFrameTimer::reset(F32 expiration) -{ - llassert(!mPaused); - mStartTime = sFrameTime; - mExpiry = sFrameTime + expiration; -} - -void LLFrameTimer::start(F32 expiration) -{ - reset(expiration); - mRunning = true; // Start, if not already started. -} - -void LLFrameTimer::stop() -{ - llassert(!mPaused); - mRunning = false; + // Handle AIFrameTimer expiration and callbacks. + if (AIFrameTimer::sNextExpiration <= sFrameTime) + { + AIFrameTimer::handleExpiration(sFrameTime); + } } // Don't combine pause/unpause with start/stop @@ -95,38 +96,38 @@ void LLFrameTimer::stop() // foo.unpause() // unpauses // F32 elapsed = foo.getElapsedTimeF32() // does not include time between pause() and unpause() // Note: elapsed would also be valid with no unpause() call (= time run until pause() called) -void LLFrameTimer::pause() +void LLFrameTimer::pause(void) { + llassert(is_main_thread()); if (!mPaused) { + // Only the main thread writes to sFrameTime, so there is no need for locking. mStartTime = sFrameTime - mStartTime; // Abuse mStartTime to store the elapsed time so far. } mPaused = true; } -void LLFrameTimer::unpause() +void LLFrameTimer::unpause(void) { + llassert(is_main_thread()); if (mPaused) { + // Only the main thread writes to sFrameTime, so there is no need for locking. mStartTime = sFrameTime - mStartTime; // Set mStartTime consistent with the elapsed time so far. } mPaused = false; } -void LLFrameTimer::setTimerExpirySec(F32 expiration) -{ - llassert(!mPaused); - mExpiry = mStartTime + expiration; -} - void LLFrameTimer::setExpiryAt(F64 seconds_since_epoch) { + llassert(is_main_thread()); llassert(!mPaused); + // Only the main thread writes to sFrameTime, so there is no need for locking. mStartTime = sFrameTime; mExpiry = seconds_since_epoch - (USEC_TO_SEC_F64 * sStartTotalTime); } -F64 LLFrameTimer::expiresAt() const +F64 LLFrameTimer::expiresAt(void) const { F64 expires_at = U64_to_F64(sStartTotalTime) * USEC_TO_SEC_F64; expires_at += mExpiry; @@ -135,31 +136,47 @@ F64 LLFrameTimer::expiresAt() const bool LLFrameTimer::checkExpirationAndReset(F32 expiration) { - if (hasExpired()) + llassert(!mPaused); + F64 frame_time = getElapsedSeconds(); + if (frame_time >= mExpiry) { - reset(expiration); + mStartTime = frame_time; + mExpiry = mStartTime + expiration; return true; } return false; } -// static -F32 LLFrameTimer::getFrameDeltaTimeF32() +F32 LLFrameTimer::getElapsedTimeAndResetF32(void) { + llassert(mRunning && !mPaused); + F64 frame_time = getElapsedSeconds(); + F32 elapsed_time = (F32)(frame_time - mStartTime); + mExpiry = mStartTime = frame_time; + return elapsed_time; +} + +// static +// Return number of seconds between the last two frames. +F32 LLFrameTimer::getFrameDeltaTimeF32(void) +{ + llassert(is_main_thread()); + // Only the main thread writes to sFrameDeltaTime, so there is no need for locking. return (F32)(U64_to_F64(sFrameDeltaTime) * USEC_TO_SEC_F64); } - -// static +// static // Return seconds since the current frame started -F32 LLFrameTimer::getCurrentFrameTime() +F32 LLFrameTimer::getCurrentFrameTime(void) { + llassert(is_main_thread()); + // Only the main thread writes to sTotalTime, so there is no need for locking. U64 frame_time = totalTime() - sTotalTime; return (F32)(U64_to_F64(frame_time) * USEC_TO_SEC_F64); } // Glue code to avoid full class .h file #includes -F32 getCurrentFrameTime() +F32 getCurrentFrameTime(void) { return (F32)(LLFrameTimer::getCurrentFrameTime()); } diff --git a/indra/llcommon/llframetimer.h b/indra/llcommon/llframetimer.h index e737a884e..bae3e5615 100644 --- a/indra/llcommon/llframetimer.h +++ b/indra/llcommon/llframetimer.h @@ -43,88 +43,131 @@ #include "lltimer.h" #include "timing.h" +#include class LL_COMMON_API LLFrameTimer { public: // Create an LLFrameTimer and start it. After creation it is running and in the state expired (hasExpired will return true). - LLFrameTimer(void) : mStartTime(sFrameTime), mExpiry(0), mRunning(true), mPaused(false) { } + LLFrameTimer(void) : mExpiry(0), mRunning(true), mPaused(false) { if (!sGlobalMutex) global_initialization(); setAge(0.0); } + + // Atomic reads of static variables. // Return the number of seconds since the start of the application. - static F64 getElapsedSeconds() + static F64 getElapsedSeconds(void) { // Loses msec precision after ~4.5 hours... - return sFrameTime; + apr_thread_mutex_lock(sGlobalMutex); + F64 res = sFrameTime; + apr_thread_mutex_unlock(sGlobalMutex); + return res; } // Return a low precision usec since epoch. - static U64 getTotalTime() + static U64 getTotalTime(void) { - llassert(sTotalTime); - return sTotalTime; + // sTotalTime is only accessed by the main thread, so no locking is necessary. + llassert(is_main_thread()); + //apr_thread_mutex_lock(sGlobalMutex); + U64 res = sTotalTime; + //apr_thread_mutex_unlock(sGlobalMutex); + llassert(res); + return res; } // Return a low precision seconds since epoch. - static F64 getTotalSeconds() + static F64 getTotalSeconds(void) { - return sTotalSeconds; + // sTotalSeconds is only accessed by the main thread, so no locking is necessary. + llassert(is_main_thread()); + //apr_thread_mutex_lock(sGlobalMutex); + F64 res = sTotalSeconds; + //apr_thread_mutex_unlock(sGlobalMutex); + return res; + } + + // Return current frame number (the number of frames since application start). + static U32 getFrameCount(void) + { + // sFrameCount is only accessed by the main thread, so no locking is necessary. + llassert(is_main_thread()); + //apr_thread_mutex_lock(sGlobalMutex); + U32 res = sFrameCount; + //apr_thread_mutex_unlock(sGlobalMutex); + return res; } // Call this method once per frame to update the current frame time. // This is actually called at some other times as well. - static void updateFrameTime(); + static void updateFrameTime(void); // Call this method once, and only once, per frame to update the current frame count and sFrameDeltaTime. - static void updateFrameTimeAndCount(); - - // Return current frame number (the number of frames since application start). - static U32 getFrameCount() { return sFrameCount; } + static void updateFrameTimeAndCount(void); // Return duration of last frame in seconds. - static F32 getFrameDeltaTimeF32(); + static F32 getFrameDeltaTimeF32(void); // Return seconds since the current frame started - static F32 getCurrentFrameTime(); + static F32 getCurrentFrameTime(void); // MANIPULATORS - void reset(F32 expiration = 0.f); // Same as start() but leaves mRunning off when called after stop(). - void start(F32 expiration = 0.f); // Reset and (re)start with expiration. - void stop(); // Stop running. + void reset(F32 expiration = 0.f) // Same as start() but leaves mRunning off when called after stop(). + { + llassert(!mPaused); + mStartTime = getElapsedSeconds(); + mExpiry = mStartTime + expiration; + } + + void start(F32 expiration = 0.f) // Reset and (re)start with expiration. + { + reset(expiration); + mRunning = true; // Start, if not already started. + } + + void stop(void) // Stop running. + { + llassert(!mPaused); + mRunning = false; + } void pause(); // Mark elapsed time so far. void unpause(); // Move 'start' time in order to decrement time between pause and unpause from ElapsedTime. - void setTimerExpirySec(F32 expiration); + void setTimerExpirySec(F32 expiration) { llassert(!mPaused); mExpiry = mStartTime + expiration; } + void setExpiryAt(F64 seconds_since_epoch); bool checkExpirationAndReset(F32 expiration); // Returns true when expired. Only resets if expired. - F32 getElapsedTimeAndResetF32() { F32 t = getElapsedTimeF32(); reset(); return t; } - void setAge(const F64 age) { llassert(!mPaused); mStartTime = sFrameTime - age; } + F32 getElapsedTimeAndResetF32(void); + void setAge(const F64 age) { llassert(!mPaused); mStartTime = getElapsedSeconds() - age; } // ACCESSORS - bool hasExpired() const { return sFrameTime >= mExpiry; } - F32 getElapsedTimeF32() const { llassert(mRunning); return mPaused ? (F32)mStartTime : (F32)(sFrameTime - mStartTime); } + bool hasExpired() const { return getElapsedSeconds() >= mExpiry; } + F32 getElapsedTimeF32() const { llassert(mRunning); return mPaused ? (F32)mStartTime : (F32)(getElapsedSeconds() - mStartTime); } bool getStarted() const { return mRunning; } // return the seconds since epoch when this timer will expire. F64 expiresAt() const; -protected: - // A single, high resolution timer that drives all LLFrameTimers - // *NOTE: no longer used. - //static LLTimer sInternalTimer; +public: + // Do one-time initialization of the static members. + static void global_initialization(void); +protected: // - // Aplication constants + // Application constants // // Application start in microseconds since epoch. static U64 const sStartTotalTime; // - // Data updated per frame + // Global data. // + // More than one thread are accessing (some of) these variables, therefore we need locking. + static apr_thread_mutex_t* sGlobalMutex; + // Current time in seconds since application start, updated together with sTotalTime. static F64 sFrameTime; diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 3b1436474..50feb455a 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -560,6 +560,9 @@ bool LLAppViewer::init() // we run the "program crashed last time" error handler below. // + // We can call this early. + LLFrameTimer::global_initialization(); + // Need to do this initialization before we do anything else, since anything // that touches files should really go through the lldir API gDirUtilp->initAppDirs("SecondLife");