Files
GTASource/rage/scaleform/Src/GKernel/GThreads_ps3.cpp
expvintl 419f2e4752 init
2025-02-23 17:40:52 +08:00

661 lines
16 KiB
C++

/**********************************************************************
Filename : GThreads_ps3.cpp
Content : PPU thread support
Created : May 2009
Authors : Vladislav Merker
Copyright : (c) 2009 Scaleform Corp. All Rights Reserved.
Licensees may use this file in accordance with the valid Scaleform
Commercial License Agreement provided with the software.
This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR ANY PURPOSE.
**********************************************************************/
#if __PS3 // RAGE - PS3 only
#include "GThreads.h"
#include "GHash.h"
#ifndef GFC_NO_THREADSUPPORT
#include "GTimer.h"
#include <sys/ppu_thread.h>
#include <sys/synchronization.h>
//#include <sys/sys_time.h>
#include <sys/timer.h>
#include <stdio.h> // RAGE - for sprintf
#define PPU_THREAD_STACK_SIZE 0x20000
// *** Classes Implemented
class GMutex;
class GWaitCondition;
class GThread;
// *** Mutex implementation
// Interface used internally in a mutex
class GMutex_AreadyLockedAcquireInterface: public GAcquireInterface
{
public:
// Mutex we belong to
GMutex *pMutex;
GMutex_AreadyLockedAcquireInterface() { pMutex = 0; }
// Acquire interface implementation
virtual bool CanAcquire();
virtual bool TryAcquire();
virtual bool TryAcquireCommit();
virtual bool TryAcquireCancel();
// GInterface - no implementation
virtual void AddRef() {}
virtual void Release(UInt flags = 0) {}
};
// Acquire interface implementation
bool GMutex_AreadyLockedAcquireInterface::CanAcquire() { return true; }
bool GMutex_AreadyLockedAcquireInterface::TryAcquire() { return pMutex->TryAcquire(); }
bool GMutex_AreadyLockedAcquireInterface::TryAcquireCommit() { return pMutex->TryAcquireCommit(); }
bool GMutex_AreadyLockedAcquireInterface::TryAcquireCancel() { return pMutex->TryAcquireCancel(); }
// *** Internal mutex implementation structure
class GMutexImpl: public GNewOverrideBase<GStat_Default_Mem>
{
sys_mutex_t MutexId;
bool Recursive;
sys_ppu_thread_t LockedBy;
UInt LockCount;
GMutex_AreadyLockedAcquireInterface AreadyLockedAcquire;
friend class GWaitConditionImpl;
public:
GMutexImpl(GMutex* pmutex, bool recursive = true);
~GMutexImpl();
// Locking functions
void Lock();
bool TryLock();
void Unlock(GMutex* pmutex);
bool IsLockedByAnotherThread() const;
bool IsSignaled() const;
GAcquireInterface* GetAcquireInterface(GMutex* pmutex);
};
// Constructor and destructor
GMutexImpl::GMutexImpl(GMutex* pmutex, bool recursive)
{
AreadyLockedAcquire.pMutex = pmutex;
Recursive = recursive;
LockCount = 0;
sys_mutex_attribute_t mutexAttr;
sys_mutex_attribute_initialize(mutexAttr);
if(Recursive)
mutexAttr.attr_recursive = SYS_SYNC_RECURSIVE;
int err = sys_mutex_create(&MutexId, &mutexAttr);
GASSERT(err == CELL_OK);
GUNUSED(err);
}
GMutexImpl::~GMutexImpl()
{
int err = sys_mutex_destroy(MutexId);
GASSERT(err == CELL_OK);
GUNUSED(err);
}
// Lock and try lock
void GMutexImpl::Lock()
{
while(sys_mutex_lock(MutexId, 0) != CELL_OK);
LockCount++;
sys_ppu_thread_get_id(&LockedBy);
}
bool GMutexImpl::TryLock()
{
if(sys_mutex_trylock(MutexId) == CELL_OK)
{
LockCount++;
sys_ppu_thread_get_id(&LockedBy);
return true;
}
return false;
}
// Unlock
void GMutexImpl::Unlock(GMutex* pmutex)
{
sys_ppu_thread_t thrId;
sys_ppu_thread_get_id(&thrId);
GASSERT(thrId == LockedBy && LockCount > 0);
GUNUSED(thrId);
UInt lockCount;
LockCount--;
lockCount = LockCount;
GMutex::CallableHandlers handlers;
pmutex->GetCallableHandlers(&handlers);
sys_mutex_unlock(MutexId);
if(lockCount == 0)
handlers.CallWaitHandlers();
}
bool GMutexImpl::IsLockedByAnotherThread() const
{
if(LockCount == 0)
return false;
sys_ppu_thread_t thrId;
sys_ppu_thread_get_id(&thrId);
if(thrId != LockedBy)
return true;
return false;
}
bool GMutexImpl::IsSignaled() const
{
return LockCount == 0;
}
// Obtain the acquisition interface
GAcquireInterface* GMutexImpl::GetAcquireInterface(GMutex* pmutex)
{
if(LockCount && !IsLockedByAnotherThread())
return &AreadyLockedAcquire;
return pmutex;
}
// *** Actual implementation of GMutex
GMutex::GMutex(bool recursive, bool multiWait):
GWaitable(multiWait) { pImpl = new GMutexImpl(this, recursive); }
GMutex::~GMutex() { delete pImpl; }
// Lock, try lock and unlock
void GMutex::Lock() { pImpl->Lock(); }
bool GMutex::TryLock() { return pImpl->TryLock(); }
void GMutex::Unlock() { pImpl->Unlock(this); }
bool GMutex::IsLockedByAnotherThread() { return pImpl->IsLockedByAnotherThread(); }
bool GMutex::IsSignaled() const { return pImpl->IsSignaled(); }
// Obtain the acquisition interface
GAcquireInterface* GMutex::GetAcquireInterface() { return pImpl->GetAcquireInterface(this); }
// Acquire interface implementation
bool GMutex::CanAcquire() { return !IsLockedByAnotherThread(); }
bool GMutex::TryAcquire() { return TryLock(); }
bool GMutex::TryAcquireCommit() { return true; }
bool GMutex::TryAcquireCancel() { Unlock(); return true; }
// ***** Wait Condition implementation
// Internal implementation class
class GWaitConditionImpl: public GNewOverrideBase<GStat_Default_Mem>
{
sys_mutex_t MutexId;
sys_cond_t CondvId;
public:
GWaitConditionImpl();
~GWaitConditionImpl();
// Release mutex and wait for condition. The mutex is re-aqured after the wait
bool Wait(GMutex *pmutex, UInt delay = GFC_WAIT_INFINITE);
// Notify a condition, releasing at one object waiting
// and releasing all objects waiting
void Notify();
void NotifyAll();
};
// Constructor and destructor
GWaitConditionImpl::GWaitConditionImpl()
{
sys_mutex_attribute_t mutexAttr;
sys_mutex_attribute_initialize(mutexAttr);
int err = sys_mutex_create(&MutexId, &mutexAttr);
GASSERT(err == CELL_OK);
GUNUSED(err);
sys_cond_attribute_t condvAttr;
sys_cond_attribute_initialize(condvAttr);
err = sys_cond_create(&CondvId, MutexId, &condvAttr);
GASSERT(err == CELL_OK);
}
GWaitConditionImpl::~GWaitConditionImpl()
{
int err = sys_cond_destroy(CondvId);
GASSERT(err == CELL_OK);
GUNUSED(err);
err = sys_mutex_destroy(MutexId);
GASSERT(err == CELL_OK);
}
// Release mutex and wait for condition. The mutex is re-aqured after the wait
bool GWaitConditionImpl::Wait(GMutex *pmutex, UInt delay)
{
bool ret = true;
UInt lockCount = pmutex->pImpl->LockCount;
// Mutex must have been locked
if(lockCount == 0)
return false;
sys_mutex_lock(MutexId, 0);
if(pmutex->pImpl->Recursive)
{
// Release the recursive mutex N times
pmutex->pImpl->LockCount = 0;
for(UInt i = 0; i < lockCount; i++)
sys_mutex_unlock(pmutex->pImpl->MutexId);
pmutex->CallWaitHandlers();
}
else
{
pmutex->pImpl->LockCount = 0;
sys_mutex_unlock(pmutex->pImpl->MutexId);
pmutex->CallWaitHandlers();
}
if(delay == GFC_WAIT_INFINITE)
sys_cond_wait(CondvId, 0);
else
{ // Timeout period in microseconds
usecond_t timeout = delay * 1000;
int err = sys_cond_wait(CondvId, timeout);
GASSERT(err == CELL_OK || err == ETIMEDOUT);
if(err)
ret = false;
}
sys_mutex_unlock(MutexId);
// Re-aquire the mutex
for(UInt i = 0; i < lockCount; i++)
pmutex->Lock();
return ret;
}
// Notify a condition, releasing the least object in a queue
void GWaitConditionImpl::Notify()
{
sys_mutex_lock(MutexId, 0);
sys_cond_signal(CondvId);
sys_mutex_unlock(MutexId);
}
// Notify a condition, releasing all objects waiting
void GWaitConditionImpl::NotifyAll()
{
sys_mutex_lock(MutexId, 0);
sys_cond_signal_all(CondvId);
sys_mutex_unlock(MutexId);
}
// *** Actual implementation of GWaitCondition
GWaitCondition::GWaitCondition() { pImpl = new GWaitConditionImpl; }
GWaitCondition::~GWaitCondition() { delete pImpl; }
// Wait for condition
bool GWaitCondition::Wait(GMutex *pmutex, UInt delay) { return pImpl->Wait(pmutex, delay); }
// Notification
void GWaitCondition::Notify() { pImpl->Notify(); }
void GWaitCondition::NotifyAll() { pImpl->NotifyAll(); }
// *** GThread constructors and destructor
// RAGE - this is so I can init some TLS variables
void* GThread::EngineInitCallbackData = NULL;
void (*GThread::EngineInitCallbackFn)(void*) = NULL;
GThread::GThread(UPInt stackSize, int processor): GWaitable(1)
{
CreateParams params;
params.stackSize = stackSize;
params.processor = processor;
Init(params);
}
GThread::GThread(GThread::ThreadFn threadFunction, void* userHandle, UPInt stackSize,
int processor, GThread::ThreadState initialState): GWaitable(1)
{
CreateParams params(threadFunction, userHandle, stackSize, processor, initialState);
Init(params);
}
GThread::GThread(const CreateParams& params): GWaitable(1)
{
Init(params);
}
void GThread::Init(const CreateParams& params)
{
ThreadFlags = 0;
ThreadHandle = 0;
ExitCode = 0;
SuspendCount = 0;
StackSize = params.stackSize;
Processor = params.processor;
Priority = params.priority;
ThreadFunction = params.threadFunction;
UserHandle = params.userHandle;
if(params.initialState != NotRunning)
Start(params.initialState);
}
GThread::~GThread()
{
ThreadHandle = 0;
}
// *** Overridable user functions
// Default Run implementation
SInt GThread::Run()
{
return (ThreadFunction) ? ThreadFunction(this, UserHandle) : 0;
}
void GThread::OnExit()
{
}
// Finishes the thread and releases internal reference to it
void GThread::FinishAndRelease()
{
CallableHandlers handlers;
GetCallableHandlers(&handlers);
ThreadFlags &= (UInt32)~(GFC_THREAD_STARTED);
ThreadFlags |= GFC_THREAD_FINISHED;
Release();
handlers.CallWaitHandlers();
}
// *** ThreadList - used to tack all created threads
class GThreadList: public GNewOverrideBase<GStat_Default_Mem>
{
struct ThreadHashOp
{
size_t operator()(const GThread* ptr)
{ return (((size_t)ptr) >> 6) ^ (size_t)ptr; }
};
GHashSet<GThread*, ThreadHashOp> ThreadSet;
GMutex ThreadMutex;
GWaitCondition ThreadsEmpty;
sys_ppu_thread_t RootThreadId;
static GThreadList* volatile pRunningThreads;
void addThread(GThread *pthr)
{
GMutex::Locker lock(&ThreadMutex);
ThreadSet.Add(pthr);
}
void removeThread(GThread *pthr)
{
GMutex::Locker lock(&ThreadMutex);
ThreadSet.Remove(pthr);
if(ThreadSet.GetSize() == 0)
ThreadsEmpty.Notify();
}
void finishAllThreads()
{
// Only original root thread can call this
sys_ppu_thread_t thrId;
sys_ppu_thread_get_id(&thrId);
GASSERT(thrId == RootThreadId);
GUNUSED(thrId);
GMutex::Locker lock(&ThreadMutex);
while(ThreadSet.GetSize() != 0)
ThreadsEmpty.Wait(&ThreadMutex);
}
public:
GThreadList() { sys_ppu_thread_get_id(&RootThreadId); }
~GThreadList() {}
static void AddRunningThread(GThread *pthr)
{
if(!pRunningThreads)
{
pRunningThreads = new GThreadList;
GASSERT(pRunningThreads);
}
pRunningThreads->addThread(pthr);
}
static void RemoveRunningThread(GThread *pthr)
{
GASSERT(pRunningThreads);
pRunningThreads->removeThread(pthr);
}
static void FinishAllThreads()
{
if(pRunningThreads)
{
pRunningThreads->finishAllThreads();
delete pRunningThreads;
pRunningThreads = 0;
}
}
};
// By default, we have no thread list
GThreadList* volatile GThreadList::pRunningThreads = 0;
void GThread::FinishAllThreads()
{
GThreadList::FinishAllThreads();
}
SInt GThread::PRun()
{
if(ThreadFlags & GFC_THREAD_START_SUSPENDED)
{
Suspend();
ThreadFlags &= (UInt32)~GFC_THREAD_START_SUSPENDED;
}
ExitCode = Run();
return ExitCode;
}
bool GThread::GetExitFlag() const
{
return (ThreadFlags & GFC_THREAD_EXIT) != 0;
}
void GThread::SetExitFlag(bool exitFlag)
{
if(exitFlag)
ThreadFlags |= GFC_THREAD_EXIT;
else
ThreadFlags &= (UInt32) ~GFC_THREAD_EXIT;
}
// State functions
bool GThread::IsFinished() const { return (ThreadFlags & GFC_THREAD_FINISHED) != 0; }
bool GThread::IsSuspended() const { return SuspendCount > 0; }
GThread::ThreadState GThread::GetThreadState() const
{
if(IsSuspended()) return Suspended;
if(ThreadFlags & GFC_THREAD_STARTED) return Running;
return NotRunning;
}
// ***** Thread management
// The actual first function called on thread start
void GThread_PpuThreadStartFn(uint64_t phandle)
{
GThread* pthr = (GThread*)(uintptr_t)phandle;
// RAGE - added this so we can init TLS data
if (GThread::EngineInitCallbackFn)
{
GThread::EngineInitCallbackFn(GThread::EngineInitCallbackData);
}
SInt ret = pthr->PRun();
pthr->Exit(ret);
}
/* static */
int GThread::GetOSPriority(ThreadPriority p)
//static inline int MapToSystemPrority(GThread::ThreadPriority p)
{
switch(p)
{
case GThread::CriticalPriority: return 0;
case GThread::HighestPriority: return 300;
case GThread::AboveNormalPriority: return 600;
case GThread::NormalPriority: return 1000;
case GThread::BelowNormalPriority: return 1500;
case GThread::LowestPriority: return 2500;
case GThread::IdlePriority: return 3071;
}
return 1000;
}
bool GThread::Start(ThreadState initialState)
{
if(initialState == NotRunning)
return false;
// If the thread is already running then wait until its
// finished to begin running this thread
if((GetThreadState() != NotRunning) && !Wait())
return false;
// Default values of stack size and thread priority
size_t stackSize = PPU_THREAD_STACK_SIZE;
int thrPrio = GThread::GetOSPriority(NormalPriority);
ExitCode = 0;
SuspendCount = 0;
ThreadFlags = (initialState == Running) ? 0 : GFC_THREAD_START_SUSPENDED;
// AddRef to us until the thread is finished
AddRef();
GThreadList::AddRunningThread(this);
if(StackSize != PPU_THREAD_STACK_SIZE || Priority != NormalPriority)
{
stackSize = StackSize;
thrPrio = GThread::GetOSPriority(Priority);
}
// RAGE - synthesize unique thread name so SN Tuner doesn't complain about duplicate threads.
// A better fix would be to expose the thread name to the creator.
char tname[32];
sprintf(tname,"GThread %p",this);
int err = sys_ppu_thread_create(&ThreadHandle, GThread_PpuThreadStartFn, (uintptr_t)this,
thrPrio, stackSize, 0, tname);
GASSERT(err == CELL_OK);
if(err)
{
ThreadFlags = 0;
Release();
GThreadList::RemoveRunningThread(this);
return false;
}
return true;
}
// Suspend doesn't supported
bool GThread::Suspend() { return false; }
// Resume doesn't supported
bool GThread::Resume() { return false; }
// Quits with an exit code
void GThread::Exit(SInt exitCode)
{
OnExit();
FinishAndRelease();
GThreadList::RemoveRunningThread(this);
sys_ppu_thread_exit((uint64_t)exitCode);
}
// *** Sleep functions
bool GThread::Sleep(UInt secs)
{
sys_timer_sleep(secs);
return true;
}
bool GThread::MSleep(UInt msecs)
{
sys_timer_usleep(msecs * 1000);
return true;
}
int GThread::GetCPUCount()
{
return true;
}
// *** GLock static member
sys_lwmutex_attribute_t GLock::LockAttr = { SYS_SYNC_PRIORITY, SYS_SYNC_RECURSIVE };
#endif // GFC_NO_THREADSUPPORT
#endif // RAGE - PS3 only