2002-12-13 06:18:09 +00:00
|
|
|
/*
|
|
|
|
|
* If you're going to use threads, remember this:
|
|
|
|
|
*
|
|
|
|
|
* Threads suck.
|
|
|
|
|
*
|
|
|
|
|
* If there's any way to avoid them, take it! Threaded code an order of
|
|
|
|
|
* magnitude more complicated, harder to debug and harder to make robust.
|
|
|
|
|
*
|
|
|
|
|
* That said, here are a few helpers for when they're unavoidable. (Use
|
|
|
|
|
* SDL for the rest.)
|
|
|
|
|
*/
|
|
|
|
|
|
2003-02-16 04:01:45 +00:00
|
|
|
#include "global.h"
|
2002-12-13 06:18:09 +00:00
|
|
|
|
|
|
|
|
#include "RageThreads.h"
|
2002-12-21 05:13:45 +00:00
|
|
|
#include "RageTimer.h"
|
|
|
|
|
#include "RageLog.h"
|
|
|
|
|
#include "RageUtil.h"
|
2003-08-05 01:25:33 +00:00
|
|
|
#include "SDL_thread.h"
|
2003-07-27 03:08:02 +00:00
|
|
|
#include "SDL_utils.h"
|
|
|
|
|
|
|
|
|
|
#include <signal.h>
|
|
|
|
|
|
|
|
|
|
/* SDL threads aren't quite enough. We need to be able to suspend or
|
|
|
|
|
* kill all threads, including the main one. SDL doesn't count the
|
|
|
|
|
* main thread as a thread. So, we'll have to do this nonportably. */
|
|
|
|
|
#if defined(LINUX)
|
|
|
|
|
#define PID_BASED_THREADS
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#define MAX_THREADS 128
|
|
|
|
|
|
|
|
|
|
struct ThreadSlot
|
|
|
|
|
{
|
|
|
|
|
char name[1024];
|
|
|
|
|
Uint32 threadid;
|
|
|
|
|
|
2003-08-05 01:25:33 +00:00
|
|
|
/* Format this beforehand, since it's easier to do that than to do it under crash conditions. */
|
|
|
|
|
char ThreadFormattedOutput[1024];
|
|
|
|
|
|
2003-07-27 03:08:02 +00:00
|
|
|
bool used;
|
|
|
|
|
|
|
|
|
|
#if defined(PID_BASED_THREADS)
|
|
|
|
|
/* Keep a list of child PIDs, so we can send them SIGKILL. This has an
|
|
|
|
|
* added bonus: if this is corrupted, we'll just send signals and they'll
|
|
|
|
|
* fail; we won't blow up (unless we're root). */
|
|
|
|
|
int pid;
|
|
|
|
|
#endif
|
|
|
|
|
|
2003-10-01 08:20:03 +00:00
|
|
|
#if defined(WIN32)
|
|
|
|
|
HANDLE ThreadHandle;
|
|
|
|
|
#endif
|
|
|
|
|
|
2003-08-05 01:25:33 +00:00
|
|
|
#undef CHECKPOINT_COUNT
|
2003-08-08 09:03:30 +00:00
|
|
|
#define CHECKPOINT_COUNT 5
|
2003-08-05 01:25:33 +00:00
|
|
|
struct ThreadCheckpoint
|
|
|
|
|
{
|
|
|
|
|
const char *File, *Message;
|
|
|
|
|
int Line;
|
|
|
|
|
char FormattedBuf[1024];
|
|
|
|
|
|
|
|
|
|
ThreadCheckpoint() { Set( NULL, 0, NULL ); }
|
|
|
|
|
void Set(const char *File_, int Line_, const char *Message_=NULL);
|
|
|
|
|
const char *GetFormattedCheckpoint();
|
|
|
|
|
};
|
|
|
|
|
ThreadCheckpoint Checkpoints[CHECKPOINT_COUNT];
|
|
|
|
|
int CurCheckpoint, NumCheckpoints;
|
|
|
|
|
const char *GetFormattedCheckpoint( int lineno );
|
|
|
|
|
|
2003-07-27 03:08:02 +00:00
|
|
|
/* Used to bootstrap the thread: */
|
|
|
|
|
int (*fn)(void *);
|
|
|
|
|
void *data;
|
|
|
|
|
|
2003-08-05 01:25:33 +00:00
|
|
|
ThreadSlot() { Init(); }
|
|
|
|
|
void Init()
|
2003-07-27 03:08:02 +00:00
|
|
|
{
|
|
|
|
|
used = false;
|
2003-08-05 01:25:33 +00:00
|
|
|
CurCheckpoint = NumCheckpoints = 0;
|
2003-07-27 03:08:02 +00:00
|
|
|
#if defined(PID_BASED_THREADS)
|
|
|
|
|
pid = -1;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SetupThisThread();
|
|
|
|
|
void ShutdownThisThread();
|
|
|
|
|
};
|
|
|
|
|
|
2003-08-05 01:25:33 +00:00
|
|
|
void ThreadSlot::ThreadCheckpoint::Set(const char *File_, int Line_, const char *Message_)
|
|
|
|
|
{
|
|
|
|
|
File=File_;
|
|
|
|
|
Line=Line_;
|
|
|
|
|
Message=Message_;
|
|
|
|
|
sprintf( FormattedBuf, " %s:%i %s",
|
|
|
|
|
File, Line, Message? Message:"" );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const char *ThreadSlot::ThreadCheckpoint::GetFormattedCheckpoint()
|
|
|
|
|
{
|
|
|
|
|
if( File == NULL )
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
/* Make sure it's terminated: */
|
|
|
|
|
FormattedBuf [ sizeof(FormattedBuf)-1 ] = 0;
|
|
|
|
|
|
|
|
|
|
return FormattedBuf;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const char *ThreadSlot::GetFormattedCheckpoint( int lineno )
|
|
|
|
|
{
|
|
|
|
|
if( lineno >= CHECKPOINT_COUNT || lineno >= NumCheckpoints )
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
if( NumCheckpoints == CHECKPOINT_COUNT )
|
|
|
|
|
{
|
|
|
|
|
lineno += CurCheckpoint;
|
|
|
|
|
lineno %= CHECKPOINT_COUNT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Checkpoints[lineno].GetFormattedCheckpoint();
|
|
|
|
|
}
|
2003-07-27 03:08:02 +00:00
|
|
|
|
|
|
|
|
static ThreadSlot g_ThreadSlots[MAX_THREADS];
|
|
|
|
|
static RageMutex g_ThreadSlotsLock;
|
|
|
|
|
|
|
|
|
|
static int FindEmptyThreadSlot()
|
|
|
|
|
{
|
|
|
|
|
LockMut(g_ThreadSlotsLock);
|
|
|
|
|
for( int entry = 0; entry < MAX_THREADS; ++entry )
|
|
|
|
|
{
|
|
|
|
|
if( g_ThreadSlots[entry].used )
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
g_ThreadSlots[entry].used = true;
|
|
|
|
|
return entry;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RageException::Throw("Out of thread slots!");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int GetCurThreadSlot()
|
|
|
|
|
{
|
|
|
|
|
Uint32 ThisThread = SDL_ThreadID();
|
|
|
|
|
|
|
|
|
|
for( int entry = 0; entry < MAX_THREADS; ++entry )
|
|
|
|
|
{
|
|
|
|
|
if( !g_ThreadSlots[entry].used )
|
|
|
|
|
continue;
|
|
|
|
|
if( g_ThreadSlots[entry].threadid == ThisThread )
|
|
|
|
|
return entry;
|
|
|
|
|
}
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
RageThread::RageThread()
|
|
|
|
|
{
|
|
|
|
|
thr = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RageThread::~RageThread()
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void ThreadSlot::SetupThisThread()
|
|
|
|
|
{
|
|
|
|
|
#if defined(PID_BASED_THREADS)
|
|
|
|
|
pid = getpid();
|
|
|
|
|
#endif
|
2003-07-27 03:13:40 +00:00
|
|
|
|
|
|
|
|
#ifdef _WINDOWS
|
2003-10-01 08:20:03 +00:00
|
|
|
const HANDLE CurProc = GetCurrentProcess();
|
|
|
|
|
int ret = DuplicateHandle( CurProc, GetCurrentThread(), CurProc,
|
|
|
|
|
&ThreadHandle, 0, false, DUPLICATE_SAME_ACCESS );
|
|
|
|
|
if( !ret )
|
|
|
|
|
LOG->Warn( werr_ssprintf( GetLastError(), "DuplicateHandle(%p, %p) failed",
|
|
|
|
|
CurProc, GetCurrentThread() ) );
|
2003-07-27 03:13:40 +00:00
|
|
|
#endif
|
|
|
|
|
|
2003-07-27 03:08:02 +00:00
|
|
|
threadid = SDL_ThreadID();
|
2003-08-05 01:25:33 +00:00
|
|
|
|
|
|
|
|
sprintf(ThreadFormattedOutput, "Thread %08x (%s)", threadid, name);
|
|
|
|
|
CHECKPOINT;
|
2003-07-27 03:08:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ThreadSlot::ShutdownThisThread()
|
|
|
|
|
{
|
2003-09-03 05:10:23 +00:00
|
|
|
#ifdef _WINDOWS
|
2003-10-01 08:20:03 +00:00
|
|
|
CloseHandle( ThreadHandle );
|
2003-09-03 05:10:23 +00:00
|
|
|
#endif
|
|
|
|
|
|
2003-08-05 01:25:33 +00:00
|
|
|
Init();
|
2003-07-27 03:08:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int StartThread( void *p )
|
|
|
|
|
{
|
|
|
|
|
ThreadSlot *thr = (ThreadSlot *) p;
|
|
|
|
|
|
|
|
|
|
thr->SetupThisThread();
|
|
|
|
|
|
|
|
|
|
int ret = thr->fn(thr->data);
|
|
|
|
|
|
|
|
|
|
thr->ShutdownThisThread();
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RageThread::Create( int (*fn)(void *), void *data )
|
|
|
|
|
{
|
|
|
|
|
/* Don't create a thread that's already running: */
|
|
|
|
|
ASSERT( thr == NULL );
|
|
|
|
|
|
|
|
|
|
int slotno = FindEmptyThreadSlot();
|
|
|
|
|
ThreadSlot &slot = g_ThreadSlots[slotno];
|
|
|
|
|
slot.fn = fn;
|
|
|
|
|
slot.data = data;
|
|
|
|
|
|
|
|
|
|
if( name == "" )
|
|
|
|
|
{
|
|
|
|
|
LOG->Warn("Created a thread without naming it first.");
|
|
|
|
|
|
|
|
|
|
/* If you don't name it, I will: */
|
|
|
|
|
strcpy(slot.name, "Joe");
|
|
|
|
|
} else {
|
|
|
|
|
strcpy(slot.name, name.c_str());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Start a thread using our own startup function. */
|
|
|
|
|
thr = SDL_CreateThread( StartThread, &slot );
|
|
|
|
|
if( thr == NULL )
|
|
|
|
|
RageException::Throw( "Thread creation failed: %s", SDL_GetError() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct SetupMainThread
|
|
|
|
|
{
|
|
|
|
|
SetupMainThread()
|
|
|
|
|
{
|
|
|
|
|
int slot = FindEmptyThreadSlot();
|
2003-08-05 01:25:33 +00:00
|
|
|
strcpy( g_ThreadSlots[slot].name, "Main thread" );
|
2003-07-27 03:08:02 +00:00
|
|
|
g_ThreadSlots[slot].SetupThisThread();
|
|
|
|
|
}
|
|
|
|
|
} SetupMainThreadObj;
|
|
|
|
|
|
|
|
|
|
const char *RageThread::GetCurThreadName()
|
|
|
|
|
{
|
|
|
|
|
int slot = GetCurThreadSlot();
|
|
|
|
|
if(slot==-1)
|
|
|
|
|
return "???";
|
|
|
|
|
|
|
|
|
|
/* This function may be called in crash conditions, so guarantee the string
|
|
|
|
|
* is null-terminated. */
|
|
|
|
|
g_ThreadSlots[slot].name[ sizeof(g_ThreadSlots[slot].name)-1] = 0;
|
|
|
|
|
|
|
|
|
|
return g_ThreadSlots[slot].name;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int RageThread::Wait()
|
|
|
|
|
{
|
|
|
|
|
ASSERT( thr != NULL );
|
|
|
|
|
|
|
|
|
|
int ret;
|
|
|
|
|
SDL_WaitThread(thr, &ret);
|
2003-08-06 05:50:34 +00:00
|
|
|
thr = NULL;
|
2003-07-27 03:08:02 +00:00
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
2003-07-27 07:00:13 +00:00
|
|
|
void RageThread::HaltAllThreads( bool Kill )
|
2003-07-27 03:08:02 +00:00
|
|
|
{
|
|
|
|
|
#if defined(PID_BASED_THREADS)
|
2003-07-27 07:00:13 +00:00
|
|
|
/* Send a SIGSTOP to all other threads. If we send a SIGKILL, pthreads
|
|
|
|
|
* will "helpfully" propagate it to the other threads, and we'll get
|
|
|
|
|
* killed, too.
|
|
|
|
|
*
|
|
|
|
|
* This isn't ideal, since it can cause the process to background
|
|
|
|
|
* as far as the shell is concerned, so the shell prompt can display
|
|
|
|
|
* before the crash handler actually displays a message. */
|
2003-07-27 03:08:02 +00:00
|
|
|
int ThisThread = getpid();
|
|
|
|
|
for( int entry = 0; entry < MAX_THREADS; ++entry )
|
|
|
|
|
{
|
|
|
|
|
if( !g_ThreadSlots[entry].used )
|
|
|
|
|
continue;
|
|
|
|
|
const int pid = g_ThreadSlots[entry].pid;
|
|
|
|
|
if( pid <= 0 || pid == ThisThread )
|
|
|
|
|
continue;
|
2003-07-27 07:00:13 +00:00
|
|
|
kill( pid, Kill? SIGKILL:SIGSTOP );
|
2003-07-27 03:08:02 +00:00
|
|
|
}
|
2003-10-01 08:20:03 +00:00
|
|
|
#elif defined(WIN32)
|
|
|
|
|
const int ThisThreadID = GetCurrentThreadId();
|
|
|
|
|
for( int entry = 0; entry < MAX_THREADS; ++entry )
|
|
|
|
|
{
|
|
|
|
|
if( !g_ThreadSlots[entry].used )
|
|
|
|
|
continue;
|
|
|
|
|
if( ThisThreadID == (int) g_ThreadSlots[entry].threadid )
|
|
|
|
|
continue;
|
2003-11-13 00:39:36 +00:00
|
|
|
#ifdef _XBOX
|
|
|
|
|
SuspendThread( g_ThreadSlots[entry].ThreadHandle );
|
|
|
|
|
#else
|
2003-10-01 08:20:03 +00:00
|
|
|
TerminateThread( g_ThreadSlots[entry].ThreadHandle, 0 );
|
2003-11-13 00:39:36 +00:00
|
|
|
#endif
|
2003-10-01 08:20:03 +00:00
|
|
|
}
|
2003-07-27 03:08:02 +00:00
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2003-11-24 00:23:18 +00:00
|
|
|
/* Normally, checkpoints are only seen in crash logs. It's occasionally useful
|
|
|
|
|
* to see them in logs, but this outputs a huge amount of text. */
|
|
|
|
|
static bool g_LogCheckpoints = false;
|
|
|
|
|
void Checkpoints::LogCheckpoints( bool on )
|
|
|
|
|
{
|
|
|
|
|
g_LogCheckpoints = on;
|
|
|
|
|
}
|
2003-08-05 01:25:33 +00:00
|
|
|
|
2003-11-24 00:23:18 +00:00
|
|
|
void Checkpoints::SetCheckpoint( const char *file, int line, const char *message )
|
2003-08-05 01:25:33 +00:00
|
|
|
{
|
|
|
|
|
int slotno = GetCurThreadSlot();
|
|
|
|
|
ASSERT( slotno != -1 );
|
|
|
|
|
|
|
|
|
|
ThreadSlot &slot = g_ThreadSlots[slotno];
|
|
|
|
|
|
|
|
|
|
slot.Checkpoints[slot.CurCheckpoint].Set( file, line, message );
|
|
|
|
|
|
2003-11-24 00:23:18 +00:00
|
|
|
if( g_LogCheckpoints )
|
|
|
|
|
LOG->Trace( "%s", slot.Checkpoints[slot.CurCheckpoint].FormattedBuf );
|
|
|
|
|
|
2003-08-05 01:25:33 +00:00
|
|
|
++slot.CurCheckpoint;
|
|
|
|
|
slot.NumCheckpoints = max( slot.NumCheckpoints, slot.CurCheckpoint );
|
|
|
|
|
slot.CurCheckpoint %= CHECKPOINT_COUNT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* This is called under crash conditions. Be careful. */
|
2003-11-24 00:23:18 +00:00
|
|
|
static const char *GetCheckpointLog( int slotno, int lineno )
|
2003-08-05 01:25:33 +00:00
|
|
|
{
|
|
|
|
|
static char ret[1024*32];
|
|
|
|
|
ret[0] = 0;
|
|
|
|
|
|
|
|
|
|
ThreadSlot &slot = g_ThreadSlots[slotno];
|
|
|
|
|
if( !slot.used )
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
if( lineno != 0 )
|
|
|
|
|
return slot.GetFormattedCheckpoint( lineno-1 );
|
|
|
|
|
|
|
|
|
|
slot.ThreadFormattedOutput[sizeof(slot.ThreadFormattedOutput)-1] = 0;
|
|
|
|
|
strcat(ret, slot.ThreadFormattedOutput);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
2003-11-24 00:23:18 +00:00
|
|
|
const char *Checkpoints::GetLogs( const char *delim )
|
2003-08-05 01:25:33 +00:00
|
|
|
{
|
|
|
|
|
static char ret[1024*32];
|
|
|
|
|
ret[0] = 0;
|
|
|
|
|
|
|
|
|
|
for( int slotno = 0; slotno < MAX_THREADS; ++slotno )
|
|
|
|
|
{
|
|
|
|
|
const char *buf = GetCheckpointLog( slotno, 0 );
|
|
|
|
|
if( buf == NULL )
|
|
|
|
|
break;
|
|
|
|
|
strcat( ret, buf );
|
|
|
|
|
strcat( ret, delim );
|
|
|
|
|
|
|
|
|
|
for( int line = 1; (buf = GetCheckpointLog( slotno, line )) != NULL; ++line )
|
|
|
|
|
{
|
|
|
|
|
strcat( ret, buf );
|
|
|
|
|
strcat( ret, delim );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2002-12-13 06:18:09 +00:00
|
|
|
RageMutex::RageMutex()
|
|
|
|
|
{
|
|
|
|
|
Locked = 0;
|
|
|
|
|
mut = SDL_CreateMutex();
|
|
|
|
|
mutwait = SDL_CreateMutex();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RageMutex::~RageMutex()
|
|
|
|
|
{
|
|
|
|
|
SDL_DestroyMutex(mut);
|
|
|
|
|
SDL_DestroyMutex(mutwait);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RageMutex::Lock()
|
|
|
|
|
{
|
|
|
|
|
while(1)
|
|
|
|
|
{
|
|
|
|
|
SDL_LockMutex(mut);
|
|
|
|
|
if(!Locked || LockedBy == SDL_ThreadID())
|
|
|
|
|
{
|
|
|
|
|
if(!Locked)
|
|
|
|
|
{
|
|
|
|
|
/* This mutex is now locked. */
|
|
|
|
|
SDL_LockMutex(mutwait);
|
|
|
|
|
LockedBy = SDL_ThreadID();
|
|
|
|
|
} /* (else it was already locked and we're just increasing the counter) */
|
|
|
|
|
Locked++;
|
|
|
|
|
SDL_UnlockMutex(mut);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SDL_UnlockMutex(mut);
|
|
|
|
|
|
|
|
|
|
/* Someone else is locking it. Wait until it's available and try again. */
|
|
|
|
|
SDL_LockMutex(mutwait);
|
|
|
|
|
SDL_UnlockMutex(mutwait);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RageMutex::Unlock()
|
|
|
|
|
{
|
|
|
|
|
SDL_LockMutex(mut);
|
|
|
|
|
ASSERT(Locked);
|
|
|
|
|
ASSERT(LockedBy == SDL_ThreadID());
|
|
|
|
|
|
|
|
|
|
Locked--;
|
|
|
|
|
if(!Locked)
|
|
|
|
|
{
|
|
|
|
|
LockedBy = 0;
|
|
|
|
|
SDL_UnlockMutex(mutwait);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SDL_UnlockMutex(mut);
|
|
|
|
|
}
|
|
|
|
|
|
2002-12-21 05:13:45 +00:00
|
|
|
LockMutex::LockMutex(RageMutex &mut, const char *file_, int line_):
|
|
|
|
|
mutex(mut),
|
|
|
|
|
file(file_),
|
|
|
|
|
line(line_),
|
|
|
|
|
locked_at(RageTimer::GetTimeSinceStart())
|
|
|
|
|
{
|
|
|
|
|
mutex.Lock();
|
2002-12-27 22:11:55 +00:00
|
|
|
locked = true;
|
2002-12-21 05:13:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LockMutex::~LockMutex()
|
|
|
|
|
{
|
2002-12-27 22:11:55 +00:00
|
|
|
if(locked)
|
|
|
|
|
mutex.Unlock();
|
2002-12-21 05:13:45 +00:00
|
|
|
}
|
|
|
|
|
|
2002-12-27 22:11:55 +00:00
|
|
|
void LockMutex::Unlock()
|
|
|
|
|
{
|
2003-11-24 00:23:18 +00:00
|
|
|
ASSERT( locked );
|
2002-12-27 22:11:55 +00:00
|
|
|
locked = false;
|
|
|
|
|
|
|
|
|
|
mutex.Unlock();
|
2002-12-27 23:01:45 +00:00
|
|
|
|
2003-11-24 00:23:18 +00:00
|
|
|
if( file && locked_at != -1 )
|
2002-12-27 23:01:45 +00:00
|
|
|
{
|
2003-11-24 00:23:18 +00:00
|
|
|
const float dur = RageTimer::GetTimeSinceStart() - locked_at;
|
|
|
|
|
if( dur > 0.015f )
|
|
|
|
|
LOG->Trace( "Lock at %s:%i took %f", file, line, dur );
|
2002-12-27 23:01:45 +00:00
|
|
|
}
|
2002-12-27 22:11:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2002-12-13 06:18:09 +00:00
|
|
|
/*
|
|
|
|
|
-----------------------------------------------------------------------------
|
|
|
|
|
File: RageThreads
|
|
|
|
|
|
|
|
|
|
Copyright (c) 2001-2002 by the person(s) listed below. All rights reserved.
|
|
|
|
|
Glenn Maynard
|
|
|
|
|
-----------------------------------------------------------------------------
|
|
|
|
|
*/
|