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.
This commit is contained in:
Aleric Inglewood
2011-08-08 19:21:27 +02:00
parent 5e69c9fa05
commit e95ee85804
6 changed files with 227 additions and 69 deletions

View File

@@ -15,6 +15,7 @@ include_directories(
) )
set(llcommon_SOURCE_FILES set(llcommon_SOURCE_FILES
aiframetimer.cpp
aiaprpool.cpp aiaprpool.cpp
imageids.cpp imageids.cpp
indra_constants.cpp indra_constants.cpp
@@ -84,6 +85,7 @@ set(llcommon_SOURCE_FILES
set(llcommon_HEADER_FILES set(llcommon_HEADER_FILES
CMakeLists.txt CMakeLists.txt
aiframetimer.h
aiaprpool.h aiaprpool.h
aithreadsafe.h aithreadsafe.h
bitpack.h bitpack.h

View File

@@ -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 <http://www.gnu.org/licenses/>.
*
* 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)
{
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*
* 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<AIFrameTimer> 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

View File

@@ -35,6 +35,8 @@
#include "u64.h" #include "u64.h"
#include "llframetimer.h" #include "llframetimer.h"
#include "aiframetimer.h"
#include "aiaprpool.h"
// Local constants. // Local constants.
static F64 const USEC_PER_SECOND = 1000000.0; static F64 const USEC_PER_SECOND = 1000000.0;
@@ -50,41 +52,40 @@ F64 LLFrameTimer::sFrameTime = 0.0; // Current time in seconds since app
S32 LLFrameTimer::sFrameCount = 0; // Current frame number (number of frames since application start). 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::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. U64 LLFrameTimer::sFrameDeltaTime = 0; // Microseconds between last two calls to LLFrameTimer::updateFrameTimeAndCount.
// Mutex for the above.
apr_thread_mutex_t* LLFrameTimer::sGlobalMutex;
// static // static
void LLFrameTimer::updateFrameTime() void LLFrameTimer::global_initialization(void)
{ {
sTotalTime = totalTime(); apr_thread_mutex_create(&sGlobalMutex, APR_THREAD_MUTEX_UNNESTED, AIAPRRootPool::get()());
sTotalSeconds = U64_to_F64(sTotalTime) * USEC_TO_SEC_F64;
sFrameTime = U64_to_F64(sTotalTime - sStartTotalTime) * USEC_TO_SEC_F64;
} }
// static // static
void LLFrameTimer::updateFrameTimeAndCount() void LLFrameTimer::updateFrameTime(void)
{
llassert(is_main_thread());
sTotalTime = totalTime();
sTotalSeconds = U64_to_F64(sTotalTime) * 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)
{ {
updateFrameTime(); updateFrameTime();
sFrameDeltaTime = sTotalTime - sPrevTotalTime; sFrameDeltaTime = sTotalTime - sPrevTotalTime;
sPrevTotalTime = sTotalTime; sPrevTotalTime = sTotalTime;
++sFrameCount; ++sFrameCount;
}
void LLFrameTimer::reset(F32 expiration) // Handle AIFrameTimer expiration and callbacks.
if (AIFrameTimer::sNextExpiration <= sFrameTime)
{ {
llassert(!mPaused); AIFrameTimer::handleExpiration(sFrameTime);
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;
} }
// Don't combine pause/unpause with start/stop // Don't combine pause/unpause with start/stop
@@ -95,38 +96,38 @@ void LLFrameTimer::stop()
// foo.unpause() // unpauses // foo.unpause() // unpauses
// F32 elapsed = foo.getElapsedTimeF32() // does not include time between pause() and unpause() // 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) // 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) 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. mStartTime = sFrameTime - mStartTime; // Abuse mStartTime to store the elapsed time so far.
} }
mPaused = true; mPaused = true;
} }
void LLFrameTimer::unpause() void LLFrameTimer::unpause(void)
{ {
llassert(is_main_thread());
if (mPaused) 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. mStartTime = sFrameTime - mStartTime; // Set mStartTime consistent with the elapsed time so far.
} }
mPaused = false; mPaused = false;
} }
void LLFrameTimer::setTimerExpirySec(F32 expiration)
{
llassert(!mPaused);
mExpiry = mStartTime + expiration;
}
void LLFrameTimer::setExpiryAt(F64 seconds_since_epoch) void LLFrameTimer::setExpiryAt(F64 seconds_since_epoch)
{ {
llassert(is_main_thread());
llassert(!mPaused); llassert(!mPaused);
// Only the main thread writes to sFrameTime, so there is no need for locking.
mStartTime = sFrameTime; mStartTime = sFrameTime;
mExpiry = seconds_since_epoch - (USEC_TO_SEC_F64 * sStartTotalTime); 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; F64 expires_at = U64_to_F64(sStartTotalTime) * USEC_TO_SEC_F64;
expires_at += mExpiry; expires_at += mExpiry;
@@ -135,31 +136,47 @@ F64 LLFrameTimer::expiresAt() const
bool LLFrameTimer::checkExpirationAndReset(F32 expiration) 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 true;
} }
return false; return false;
} }
// static F32 LLFrameTimer::getElapsedTimeAndResetF32(void)
F32 LLFrameTimer::getFrameDeltaTimeF32()
{ {
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); return (F32)(U64_to_F64(sFrameDeltaTime) * USEC_TO_SEC_F64);
} }
// static // static
// Return seconds since the current frame started // 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; U64 frame_time = totalTime() - sTotalTime;
return (F32)(U64_to_F64(frame_time) * USEC_TO_SEC_F64); return (F32)(U64_to_F64(frame_time) * USEC_TO_SEC_F64);
} }
// Glue code to avoid full class .h file #includes // Glue code to avoid full class .h file #includes
F32 getCurrentFrameTime() F32 getCurrentFrameTime(void)
{ {
return (F32)(LLFrameTimer::getCurrentFrameTime()); return (F32)(LLFrameTimer::getCurrentFrameTime());
} }

View File

@@ -43,88 +43,131 @@
#include "lltimer.h" #include "lltimer.h"
#include "timing.h" #include "timing.h"
#include <apr_thread_mutex.h>
class LL_COMMON_API LLFrameTimer class LL_COMMON_API LLFrameTimer
{ {
public: public:
// Create an LLFrameTimer and start it. After creation it is running and in the state expired (hasExpired will return true). // 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. // 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... // 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. // Return a low precision usec since epoch.
static U64 getTotalTime() static U64 getTotalTime(void)
{ {
llassert(sTotalTime); // sTotalTime is only accessed by the main thread, so no locking is necessary.
return sTotalTime; 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. // 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. // Call this method once per frame to update the current frame time.
// This is actually called at some other times as well. // 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. // Call this method once, and only once, per frame to update the current frame count and sFrameDeltaTime.
static void updateFrameTimeAndCount(); static void updateFrameTimeAndCount(void);
// Return current frame number (the number of frames since application start).
static U32 getFrameCount() { return sFrameCount; }
// Return duration of last frame in seconds. // Return duration of last frame in seconds.
static F32 getFrameDeltaTimeF32(); static F32 getFrameDeltaTimeF32(void);
// Return seconds since the current frame started // Return seconds since the current frame started
static F32 getCurrentFrameTime(); static F32 getCurrentFrameTime(void);
// MANIPULATORS // MANIPULATORS
void reset(F32 expiration = 0.f); // Same as start() but leaves mRunning off when called after stop(). 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. 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 pause(); // Mark elapsed time so far.
void unpause(); // Move 'start' time in order to decrement time between pause and unpause from ElapsedTime. 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); void setExpiryAt(F64 seconds_since_epoch);
bool checkExpirationAndReset(F32 expiration); // Returns true when expired. Only resets if expired. bool checkExpirationAndReset(F32 expiration); // Returns true when expired. Only resets if expired.
F32 getElapsedTimeAndResetF32() { F32 t = getElapsedTimeF32(); reset(); return t; } F32 getElapsedTimeAndResetF32(void);
void setAge(const F64 age) { llassert(!mPaused); mStartTime = sFrameTime - age; } void setAge(const F64 age) { llassert(!mPaused); mStartTime = getElapsedSeconds() - age; }
// ACCESSORS // ACCESSORS
bool hasExpired() const { return sFrameTime >= mExpiry; } bool hasExpired() const { return getElapsedSeconds() >= mExpiry; }
F32 getElapsedTimeF32() const { llassert(mRunning); return mPaused ? (F32)mStartTime : (F32)(sFrameTime - mStartTime); } F32 getElapsedTimeF32() const { llassert(mRunning); return mPaused ? (F32)mStartTime : (F32)(getElapsedSeconds() - mStartTime); }
bool getStarted() const { return mRunning; } bool getStarted() const { return mRunning; }
// return the seconds since epoch when this timer will expire. // return the seconds since epoch when this timer will expire.
F64 expiresAt() const; F64 expiresAt() const;
protected: public:
// A single, high resolution timer that drives all LLFrameTimers // Do one-time initialization of the static members.
// *NOTE: no longer used. static void global_initialization(void);
//static LLTimer sInternalTimer;
protected:
// //
// Aplication constants // Application constants
// //
// Application start in microseconds since epoch. // Application start in microseconds since epoch.
static U64 const sStartTotalTime; 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. // Current time in seconds since application start, updated together with sTotalTime.
static F64 sFrameTime; static F64 sFrameTime;

View File

@@ -560,6 +560,9 @@ bool LLAppViewer::init()
// we run the "program crashed last time" error handler below. // 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 // Need to do this initialization before we do anything else, since anything
// that touches files should really go through the lldir API // that touches files should really go through the lldir API
gDirUtilp->initAppDirs("SecondLife"); gDirUtilp->initAppDirs("SecondLife");