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
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

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 "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());
}

View File

@@ -43,88 +43,131 @@
#include "lltimer.h"
#include "timing.h"
#include <apr_thread_mutex.h>
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;

View File

@@ -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");