661 lines
16 KiB
C++
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
|