First
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Electronic Arts Inc. All rights reserved.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <EABase/eabase.h>
|
||||
|
||||
#include "eathread/eathread.h"
|
||||
#include "eathread/eathread_thread.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <sstream>
|
||||
#include <type_traits>
|
||||
|
||||
namespace EA
|
||||
{
|
||||
namespace Thread
|
||||
{
|
||||
EA::Thread::AssertionFailureFunction gpAssertionFailureFunction = NULL;
|
||||
void* gpAssertionFailureContext = NULL;
|
||||
|
||||
EATHREADLIB_API ThreadId EA::Thread::GetThreadId()
|
||||
{
|
||||
return std::this_thread::get_id();
|
||||
}
|
||||
|
||||
EATHREADLIB_API ThreadId EA::Thread::GetThreadId(EA::Thread::SysThreadId id)
|
||||
{
|
||||
EAThreadDynamicData* const pTDD = EA::Thread::FindThreadDynamicData(id);
|
||||
if(pTDD)
|
||||
{
|
||||
return pTDD->mpComp->mThread.get_id();
|
||||
}
|
||||
|
||||
return EA::Thread::kThreadIdInvalid;
|
||||
}
|
||||
|
||||
EATHREADLIB_API SysThreadId EA::Thread::GetSysThreadId(ThreadId threadId)
|
||||
{
|
||||
EAThreadDynamicData* tdd = EA::Thread::FindThreadDynamicData(threadId);
|
||||
if (tdd && tdd->mpComp)
|
||||
return tdd->mpComp->mThread.native_handle();
|
||||
|
||||
ThreadId threadIdCurrent = GetThreadId();
|
||||
if(threadId == threadIdCurrent)
|
||||
{
|
||||
#if defined(EA_PLATFORM_MICROSOFT)
|
||||
std::thread::id stdId = std::this_thread::get_id();
|
||||
EAT_COMPILETIME_ASSERT(sizeof(_Thrd_t) == sizeof(std::thread::id));
|
||||
return ((_Thrd_t&)stdId)._Hnd;
|
||||
#elif EA_POSIX_THREADS_AVAILABLE && defined(_YVALS)
|
||||
std::thread::id stdId = std::this_thread::get_id();
|
||||
EAT_COMPILETIME_ASSERT(sizeof(_Thrd_t) == sizeof(std::thread::id));
|
||||
return reinterpret_cast<_Thrd_t>(stdId);
|
||||
#else
|
||||
#error Platform not supported yet.
|
||||
#endif
|
||||
}
|
||||
|
||||
EAT_ASSERT_MSG(false, "Failed to find associated EAThreadDynamicData for this thread.\n");
|
||||
return SysThreadId();
|
||||
}
|
||||
|
||||
EATHREADLIB_API SysThreadId EA::Thread::GetSysThreadId()
|
||||
{
|
||||
// There currently isn't a means to directly get the current SysThreadId, so we do it indirectly:
|
||||
return GetSysThreadId(std::this_thread::get_id());
|
||||
}
|
||||
|
||||
EATHREADLIB_API ThreadTime EA::Thread::GetThreadTime()
|
||||
{
|
||||
using namespace std::chrono;
|
||||
auto nowMs = duration_cast<milliseconds>(system_clock::now().time_since_epoch());
|
||||
return nowMs.count();
|
||||
}
|
||||
|
||||
EATHREADLIB_API int GetThreadPriority()
|
||||
{
|
||||
// No way to query or set thread priority through standard C++11 thread library.
|
||||
// On some platforms this could be implemented through platform specific APIs
|
||||
return kThreadPriorityDefault;
|
||||
}
|
||||
|
||||
EATHREADLIB_API bool SetThreadPriority(int nPriority)
|
||||
{
|
||||
// No way to query or set thread priority through standard C++11 thread library.
|
||||
// On some platforms this could be implemented through platform specific APIs
|
||||
return false;
|
||||
}
|
||||
|
||||
EATHREADLIB_API void SetThreadProcessor(int nProcessor)
|
||||
{
|
||||
// No way to query or set thread processor through standard C++11 thread library.
|
||||
// On some platforms this could be implemented through platform specific APIs
|
||||
}
|
||||
|
||||
EATHREADLIB_API int GetThreadProcessor()
|
||||
{
|
||||
// No way to query or set thread processor through standard C++11 thread library.
|
||||
// On some platforms this could be implemented through platform specific APIs
|
||||
return 0;
|
||||
}
|
||||
|
||||
EATHREADLIB_API int GetProcessorCount()
|
||||
{
|
||||
return static_cast<int>(std::thread::hardware_concurrency());
|
||||
}
|
||||
|
||||
EATHREADLIB_API void ThreadSleep(const ThreadTime& timeRelative)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(timeRelative));
|
||||
}
|
||||
|
||||
void ThreadEnd(intptr_t threadReturnValue)
|
||||
{
|
||||
// No way to end a thread through standard C++11 thread library.
|
||||
// On some platforms this could be implemented through platform specific APIs
|
||||
EAT_ASSERT_MSG(false, "ThreadEnd is not implemented for C++11 threads.\n");
|
||||
}
|
||||
|
||||
EATHREADLIB_API void EA::Thread::SetThreadAffinityMask(const EA::Thread::ThreadId& id, ThreadAffinityMask nAffinityMask)
|
||||
{
|
||||
// Update the affinity mask in the thread dynamic data cache.
|
||||
EAThreadDynamicData* const pTDD = FindThreadDynamicData(id);
|
||||
if(pTDD)
|
||||
{
|
||||
pTDD->mnThreadAffinityMask = nAffinityMask;
|
||||
}
|
||||
|
||||
#if EATHREAD_THREAD_AFFINITY_MASK_SUPPORTED
|
||||
// Call the Windows library function.
|
||||
#endif
|
||||
}
|
||||
|
||||
EATHREADLIB_API EA::Thread::ThreadAffinityMask EA::Thread::GetThreadAffinityMask(const EA::Thread::ThreadId& id)
|
||||
{
|
||||
// Update the affinity mask in the thread dynamic data cache.
|
||||
EAThreadDynamicData* const pTDD = FindThreadDynamicData(id);
|
||||
if(pTDD)
|
||||
{
|
||||
return pTDD->mnThreadAffinityMask;
|
||||
}
|
||||
|
||||
return kThreadAffinityMaskAny;
|
||||
}
|
||||
|
||||
EATHREADLIB_API void SetAssertionFailureFunction(AssertionFailureFunction pAssertionFailureFunction, void* pContext)
|
||||
{
|
||||
gpAssertionFailureFunction = pAssertionFailureFunction;
|
||||
gpAssertionFailureContext = pContext;
|
||||
}
|
||||
|
||||
EATHREADLIB_API void AssertionFailure(const char* pExpression)
|
||||
{
|
||||
if(gpAssertionFailureFunction)
|
||||
gpAssertionFailureFunction(pExpression, gpAssertionFailureContext);
|
||||
}
|
||||
|
||||
void* GetThreadStackBase()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// This can be removed once all remaining synchronization primitives are implemented in terms of C++11 APIs
|
||||
uint32_t EA::Thread::RelativeTimeoutFromAbsoluteTimeout(ThreadTime timeoutAbsolute)
|
||||
{
|
||||
EAT_ASSERT((timeoutAbsolute == kTimeoutImmediate) || (timeoutAbsolute > EATHREAD_MIN_ABSOLUTE_TIME)); // Assert that the user didn't make the mistake of treating time as relative instead of absolute.
|
||||
|
||||
DWORD timeoutRelative = 0;
|
||||
|
||||
if (timeoutAbsolute == kTimeoutNone)
|
||||
{
|
||||
timeoutRelative = 0xffffffff;
|
||||
}
|
||||
else if (timeoutAbsolute == kTimeoutImmediate)
|
||||
{
|
||||
timeoutRelative = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
ThreadTime timeCurrent(GetThreadTime());
|
||||
timeoutRelative = (timeoutAbsolute > timeCurrent) ? static_cast<DWORD>(timeoutAbsolute - timeCurrent) : 0;
|
||||
}
|
||||
|
||||
EAT_ASSERT((timeoutRelative == 0xffffffff) || (timeoutRelative < 100000000)); // Assert that the timeout is a sane value and didn't wrap around.
|
||||
|
||||
return timeoutRelative;
|
||||
}
|
||||
|
||||
// Implement native_handle_type comparison as a memcmp() - may need platform specific implementations on some future platforms.
|
||||
bool Equals(const SysThreadId& a, const SysThreadId& b)
|
||||
{
|
||||
static_assert((std::is_fundamental<SysThreadId>::value || std::is_pointer<SysThreadId>::value || std::is_pod<SysThreadId>::value),
|
||||
"SysThreadId should be comparable using memcmp()");
|
||||
return memcmp(&a, &b, sizeof(SysThreadId)) == 0;
|
||||
}
|
||||
|
||||
namespace detail
|
||||
{
|
||||
// Override the default EAThreadToString implementation
|
||||
#define EAThreadIdToString_CUSTOM_IMPLEMENTATION
|
||||
ThreadIdToStringBuffer::ThreadIdToStringBuffer(EA::Thread::ThreadId threadId)
|
||||
{
|
||||
std::stringstream formatStream;
|
||||
formatStream << threadId;
|
||||
strncpy(mBuf, formatStream.str().c_str(), BufSize - 1);
|
||||
mBuf[BufSize - 1] = '\0';
|
||||
}
|
||||
|
||||
SysThreadIdToStringBuffer::SysThreadIdToStringBuffer(EA::Thread::SysThreadId sysThreadId)
|
||||
{
|
||||
strncpy(mBuf, "Unknown", BufSize - 1);
|
||||
mBuf[BufSize - 1] = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Electronic Arts Inc. All rights reserved.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "eathread/eathread_mutex.h"
|
||||
|
||||
EAMutexData::EAMutexData() : mnLockCount(0) {}
|
||||
|
||||
EA::Thread::MutexParameters::MutexParameters(bool /*bIntraProcess*/, const char* pName)
|
||||
{
|
||||
if(pName)
|
||||
{
|
||||
strncpy(mName, pName, sizeof(mName)-1);
|
||||
mName[sizeof(mName)-1] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
mName[0] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
EA::Thread::Mutex::Mutex(const MutexParameters* pMutexParameters, bool bDefaultParameters)
|
||||
{
|
||||
if(!pMutexParameters && bDefaultParameters)
|
||||
{
|
||||
MutexParameters parameters;
|
||||
Init(¶meters);
|
||||
}
|
||||
else
|
||||
{
|
||||
Init(pMutexParameters);
|
||||
}
|
||||
}
|
||||
|
||||
EA::Thread::Mutex::~Mutex()
|
||||
{
|
||||
EAT_ASSERT(mMutexData.mnLockCount == 0);
|
||||
}
|
||||
|
||||
bool EA::Thread::Mutex::Init(const MutexParameters* pMutexParameters)
|
||||
{
|
||||
if (pMutexParameters)
|
||||
{
|
||||
mMutexData.mnLockCount = 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int EA::Thread::Mutex::Lock(const ThreadTime& timeoutAbsolute)
|
||||
{
|
||||
if (timeoutAbsolute == kTimeoutNone)
|
||||
{
|
||||
mMutexData.mMutex.lock();
|
||||
}
|
||||
else
|
||||
{
|
||||
std::chrono::milliseconds timeoutAbsoluteMs(timeoutAbsolute);
|
||||
std::chrono::time_point<std::chrono::system_clock> timeout_time(timeoutAbsoluteMs);
|
||||
if (!mMutexData.mMutex.try_lock_until(timeout_time))
|
||||
{
|
||||
return kResultTimeout;
|
||||
}
|
||||
}
|
||||
|
||||
EAT_ASSERT((mMutexData.mThreadId = EA::Thread::GetThreadId()) != kThreadIdInvalid);
|
||||
EAT_ASSERT(mMutexData.mnLockCount >= 0);
|
||||
|
||||
return ++mMutexData.mnLockCount; // This is safe to do because we have the lock.
|
||||
}
|
||||
|
||||
int EA::Thread::Mutex::Unlock()
|
||||
{
|
||||
EAT_ASSERT(mMutexData.mThreadId == EA::Thread::GetThreadId());
|
||||
EAT_ASSERT(mMutexData.mnLockCount > 0);
|
||||
|
||||
const int nReturnValue(--mMutexData.mnLockCount); // This is safe to do because we have the lock.
|
||||
mMutexData.mMutex.unlock();
|
||||
return nReturnValue;
|
||||
}
|
||||
|
||||
int EA::Thread::Mutex::GetLockCount() const
|
||||
{
|
||||
return mMutexData.mnLockCount;
|
||||
}
|
||||
|
||||
bool EA::Thread::Mutex::HasLock() const
|
||||
{
|
||||
#if EAT_ASSERT_ENABLED
|
||||
return (mMutexData.mnLockCount > 0) && (mMutexData.mThreadId == EA::Thread::GetThreadId());
|
||||
#else
|
||||
return (mMutexData.mnLockCount > 0); // This is the best we can do, though it is of limited use, since it doesn't tell you if you are the thread with the lock.
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Electronic Arts Inc. All rights reserved.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "eathread/eathread_semaphore.h"
|
||||
@@ -0,0 +1,488 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Electronic Arts Inc. All rights reserved.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "eathread/eathread_thread.h"
|
||||
#include "eathread/eathread.h"
|
||||
#include "eathread/eathread_sync.h"
|
||||
#include "eathread/eathread_callstack.h"
|
||||
#include "eathread/internal/eathread_global.h"
|
||||
|
||||
namespace EA
|
||||
{
|
||||
namespace Thread
|
||||
{
|
||||
extern Allocator* gpAllocator;
|
||||
|
||||
static AtomicInt32 nLastProcessor = 0;
|
||||
const size_t kMaxThreadDynamicDataCount = 128;
|
||||
|
||||
struct EAThreadGlobalVars
|
||||
{
|
||||
char gThreadDynamicData[kMaxThreadDynamicDataCount][sizeof(EAThreadDynamicData)];
|
||||
AtomicInt32 gThreadDynamicDataAllocated[kMaxThreadDynamicDataCount];
|
||||
Mutex gThreadDynamicMutex;
|
||||
};
|
||||
EATHREAD_GLOBALVARS_CREATE_INSTANCE;
|
||||
|
||||
EAThreadDynamicData* AllocateThreadDynamicData()
|
||||
{
|
||||
for (size_t i(0); i < kMaxThreadDynamicDataCount; ++i)
|
||||
{
|
||||
if (EATHREAD_GLOBALVARS.gThreadDynamicDataAllocated[i].SetValueConditional(1, 0))
|
||||
return (EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData[i];
|
||||
}
|
||||
|
||||
// This is a safety fallback mechanism. In practice it won't be used in almost all situations.
|
||||
if (gpAllocator)
|
||||
return (EAThreadDynamicData*)gpAllocator->Alloc(sizeof(EAThreadDynamicData));
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void FreeThreadDynamicData(EAThreadDynamicData* pEAThreadDynamicData)
|
||||
{
|
||||
pEAThreadDynamicData->~EAThreadDynamicData();
|
||||
if ((pEAThreadDynamicData >= (EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData) && (pEAThreadDynamicData < ((EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData + kMaxThreadDynamicDataCount)))
|
||||
{
|
||||
EATHREAD_GLOBALVARS.gThreadDynamicDataAllocated[pEAThreadDynamicData - (EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData].SetValue(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Assume the data was allocated via the fallback mechanism.
|
||||
if (gpAllocator)
|
||||
{
|
||||
gpAllocator->Free(pEAThreadDynamicData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EAThreadDynamicData* FindThreadDynamicData(ThreadId threadId)
|
||||
{
|
||||
for (size_t i(0); i < kMaxThreadDynamicDataCount; ++i)
|
||||
{
|
||||
EAThreadDynamicData* const pTDD = (EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData[i];
|
||||
if (pTDD->mpComp && pTDD->mpComp->mThread.get_id() == threadId)
|
||||
return pTDD;
|
||||
}
|
||||
return nullptr; // This is no practical way we can find the data unless thread-specific storage was involved.
|
||||
}
|
||||
|
||||
EAThreadDynamicData* FindThreadDynamicData(EA::Thread::ThreadUniqueId threadId)
|
||||
{
|
||||
for (size_t i(0); i < kMaxThreadDynamicDataCount; ++i)
|
||||
{
|
||||
EAThreadDynamicData* const pTDD = (EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData[i];
|
||||
if (pTDD->mUniqueThreadId == threadId)
|
||||
return pTDD;
|
||||
}
|
||||
return nullptr; // This is no practical way we can find the data unless thread-specific storage was involved.
|
||||
}
|
||||
|
||||
EAThreadDynamicData* FindThreadDynamicData(EA::Thread::SysThreadId sysThreadId)
|
||||
{
|
||||
for (size_t i(0); i < kMaxThreadDynamicDataCount; ++i)
|
||||
{
|
||||
EAThreadDynamicData* const pTDD = (EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData[i];
|
||||
if (pTDD->mpComp && pTDD->mpComp->mThread.native_handle() == sysThreadId)
|
||||
return pTDD;
|
||||
}
|
||||
|
||||
// NOTE: This function does not support finding externally created threads due to limitations in the CPP11 std::thread API.
|
||||
// At the time of writing, it is not possible to retrieve the thread object of a thread not created by the CPP11 API.
|
||||
return nullptr; // This is no practical way we can find the data unless thread-specific storage was involved.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EA_DISABLE_VC_WARNING(4355) // this used in base member initializer list - should be safe in this context
|
||||
EAThreadDynamicData::EAThreadDynamicData(void* userFunc, void* userContext, void* userWrapperFunc, ThreadFunc threadFunc) :
|
||||
mnRefCount(2), // Init ref count to 2, one corresponding release happens on threadFunc exit and the other when Thread class is destroyed or Begin is called again
|
||||
mStatus(EA::Thread::Thread::kStatusNone),
|
||||
mpComp(nullptr)
|
||||
{
|
||||
mpComp = new EAThreadComposite();
|
||||
|
||||
if(mpComp)
|
||||
mpComp->mThread = std::thread(threadFunc, this, userFunc, userContext, userWrapperFunc); // This doesn't spawn CPP11 threads when created within the EAThreadComposite constructor.
|
||||
}
|
||||
|
||||
|
||||
EAThreadDynamicData::EAThreadDynamicData(EA::Thread::ThreadUniqueId uniqueThreadId, const char* pThreadName) :
|
||||
mnRefCount(2), // Init ref count to 2, one corresponding release happens on threadFunc exit and the other when Thread class is destroyed or Begin is called again
|
||||
mStatus(EA::Thread::Thread::kStatusNone),
|
||||
mpComp(nullptr),
|
||||
mUniqueThreadId(uniqueThreadId)
|
||||
{
|
||||
strncpy(mName, pThreadName, EATHREAD_NAME_SIZE);
|
||||
mName[EATHREAD_NAME_SIZE - 1] = 0;
|
||||
}
|
||||
|
||||
EA_RESTORE_VC_WARNING()
|
||||
|
||||
|
||||
EAThreadDynamicData::~EAThreadDynamicData()
|
||||
{
|
||||
if (mpComp->mThread.joinable())
|
||||
mpComp->mThread.detach();
|
||||
|
||||
if(mpComp)
|
||||
delete mpComp;
|
||||
|
||||
mpComp = nullptr;
|
||||
|
||||
// the threads, promises, and futures in this class will
|
||||
// allocate memory with the Concurrency runtime new/delete operators.
|
||||
// If you're crashing in here with access violations on process exit,
|
||||
// then you likely have a static instance of EA::Thread::Thread somewhere
|
||||
// that's being destructed after your memory system is uninitialized
|
||||
// leaving dangling pointers to bad memory. Attempt to change
|
||||
// these static instances to be constructed/destructed with the scope
|
||||
// of normal app operation.
|
||||
}
|
||||
|
||||
|
||||
void EAThreadDynamicData::AddRef()
|
||||
{
|
||||
mnRefCount.Increment();
|
||||
}
|
||||
|
||||
|
||||
void EAThreadDynamicData::Release()
|
||||
{
|
||||
if(mnRefCount.Decrement() == 0)
|
||||
EA::Thread::FreeThreadDynamicData(this);
|
||||
}
|
||||
|
||||
namespace EA
|
||||
{
|
||||
namespace Thread
|
||||
{
|
||||
ThreadParameters::ThreadParameters() :
|
||||
mpStack(NULL),
|
||||
mnStackSize(0),
|
||||
mnPriority(kThreadPriorityDefault),
|
||||
mnProcessor(kProcessorDefault),
|
||||
mpName(""),
|
||||
mbDisablePriorityBoost(false)
|
||||
{
|
||||
}
|
||||
|
||||
RunnableFunctionUserWrapper Thread::sGlobalRunnableFunctionUserWrapper = NULL;
|
||||
RunnableClassUserWrapper Thread::sGlobalRunnableClassUserWrapper = NULL;
|
||||
AtomicInt32 Thread::sDefaultProcessor = kProcessorAny;
|
||||
|
||||
RunnableFunctionUserWrapper Thread::GetGlobalRunnableFunctionUserWrapper()
|
||||
{
|
||||
return sGlobalRunnableFunctionUserWrapper;
|
||||
}
|
||||
|
||||
void Thread::SetGlobalRunnableFunctionUserWrapper(RunnableFunctionUserWrapper pUserWrapper)
|
||||
{
|
||||
if (sGlobalRunnableFunctionUserWrapper != NULL)
|
||||
{
|
||||
// Can only be set once in entire game.
|
||||
EAT_ASSERT(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
sGlobalRunnableFunctionUserWrapper = pUserWrapper;
|
||||
}
|
||||
}
|
||||
|
||||
RunnableClassUserWrapper Thread::GetGlobalRunnableClassUserWrapper()
|
||||
{
|
||||
return sGlobalRunnableClassUserWrapper;
|
||||
}
|
||||
|
||||
void Thread::SetGlobalRunnableClassUserWrapper(RunnableClassUserWrapper pUserWrapper)
|
||||
{
|
||||
if (sGlobalRunnableClassUserWrapper != NULL)
|
||||
{
|
||||
// Can only be set once in entire game.
|
||||
EAT_ASSERT(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
sGlobalRunnableClassUserWrapper = pUserWrapper;
|
||||
}
|
||||
}
|
||||
|
||||
Thread::Thread()
|
||||
{
|
||||
mThreadData.mpData = NULL;
|
||||
}
|
||||
|
||||
|
||||
Thread::Thread(const Thread& t) :
|
||||
mThreadData(t.mThreadData)
|
||||
{
|
||||
if (mThreadData.mpData)
|
||||
mThreadData.mpData->AddRef();
|
||||
}
|
||||
|
||||
|
||||
Thread& Thread::operator=(const Thread& t)
|
||||
{
|
||||
// We don't synchronize access to mpData; we assume that the user
|
||||
// synchronizes it or this Thread instances is used from a single thread.
|
||||
if (t.mThreadData.mpData)
|
||||
t.mThreadData.mpData->AddRef();
|
||||
|
||||
if (mThreadData.mpData)
|
||||
mThreadData.mpData->Release();
|
||||
|
||||
mThreadData = t.mThreadData;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
Thread::~Thread()
|
||||
{
|
||||
// We don't synchronize access to mpData; we assume that the user
|
||||
// synchronizes it or this Thread instances is used from a single thread.
|
||||
if (mThreadData.mpData)
|
||||
mThreadData.mpData->Release();
|
||||
}
|
||||
|
||||
static void RunnableFunctionInternal(EAThreadDynamicData* tdd, void* userFunc, void* userContext, void* userWrapperFunc)
|
||||
{
|
||||
tdd->mStatus = Thread::kStatusRunning;
|
||||
tdd->mpStackBase = EA::Thread::GetStackBase();
|
||||
RunnableFunction pFunction = (RunnableFunction)userFunc;
|
||||
|
||||
if (userWrapperFunc)
|
||||
{
|
||||
RunnableFunctionUserWrapper pWrapperFunction = (RunnableFunctionUserWrapper)userWrapperFunc;
|
||||
// if user wrapper is specified, call user wrapper and pass down the pFunction and pContext
|
||||
tdd->mpComp->mReturnPromise.set_value(pWrapperFunction(pFunction, userContext));
|
||||
}
|
||||
else
|
||||
{
|
||||
tdd->mpComp->mReturnPromise.set_value(pFunction(userContext));
|
||||
}
|
||||
|
||||
tdd->mStatus = Thread::kStatusEnded;
|
||||
tdd->Release(); // Matches an implicit AddRef in EAThreadDynamicData constructor
|
||||
}
|
||||
|
||||
|
||||
ThreadId Thread::Begin(RunnableFunction pFunction, void* pContext, const ThreadParameters* pTP, RunnableFunctionUserWrapper pUserWrapper)
|
||||
{
|
||||
// Check there is an entry for the current thread context in our ThreadDynamicData array.
|
||||
ThreadUniqueId threadUniqueId;
|
||||
EAThreadGetUniqueId(threadUniqueId);
|
||||
if(!FindThreadDynamicData(threadUniqueId))
|
||||
{
|
||||
EAThreadDynamicData* pData = new(AllocateThreadDynamicData()) EAThreadDynamicData(threadUniqueId, "external");
|
||||
if(pData)
|
||||
{
|
||||
pData->AddRef(); // AddRef for ourselves, to be released upon this Thread class being deleted or upon Begin being called again for a new thread.
|
||||
// Do no AddRef for thread execution because this is not an EAThread managed thread.
|
||||
}
|
||||
}
|
||||
|
||||
if (mThreadData.mpData)
|
||||
mThreadData.mpData->Release(); // Matches an implicit AddRef in EAThreadDynamicData constructor
|
||||
|
||||
// C++11 Threads don't support user-supplied stacks. A user-supplied stack pointer
|
||||
// here would be a waste of user memory, and so we assert that mpStack == NULL.
|
||||
EAT_ASSERT(!pTP || (pTP->mpStack == NULL));
|
||||
|
||||
// We use the pData temporary throughout this function because it's possible that mThreadData.mpData could be
|
||||
// modified as we are executing, in particular in the case that mThreadData.mpData is destroyed and changed
|
||||
// during execution.
|
||||
EAThreadDynamicData* pDataAddr = AllocateThreadDynamicData();
|
||||
EAT_ASSERT(pDataAddr != nullptr);
|
||||
EAThreadDynamicData* pData = new(pDataAddr) EAThreadDynamicData(pFunction, pContext, pUserWrapper, RunnableFunctionInternal); // Note that we use a special new here which doesn't use the heap.
|
||||
EAT_ASSERT(pData != nullptr);
|
||||
mThreadData.mpData = pData;
|
||||
if (pTP)
|
||||
SetName(pTP->mpName);
|
||||
|
||||
return pData->mpComp->mThread.get_id();
|
||||
}
|
||||
|
||||
static void RunnableObjectInternal(EAThreadDynamicData* tdd, void* userFunc, void* userContext, void* userWrapperFunc)
|
||||
{
|
||||
tdd->mStatus = Thread::kStatusRunning;
|
||||
IRunnable* pRunnable = (IRunnable*)userFunc;
|
||||
|
||||
if (userWrapperFunc)
|
||||
{
|
||||
RunnableClassUserWrapper pWrapperFunction = (RunnableClassUserWrapper)userWrapperFunc;
|
||||
// if user wrapper is specified, call user wrapper and pass down the pFunction and pContext
|
||||
tdd->mpComp->mReturnPromise.set_value(pWrapperFunction(pRunnable, userContext));
|
||||
}
|
||||
else
|
||||
{
|
||||
tdd->mpComp->mReturnPromise.set_value(pRunnable->Run(userContext));
|
||||
}
|
||||
|
||||
tdd->mStatus = Thread::kStatusEnded;
|
||||
tdd->Release(); // Matches implicit AddRef in EAThreadDynamicData constructor
|
||||
}
|
||||
|
||||
|
||||
ThreadId Thread::Begin(IRunnable* pRunnable, void* pContext, const ThreadParameters* pTP, RunnableClassUserWrapper pUserWrapper)
|
||||
{
|
||||
if (mThreadData.mpData)
|
||||
mThreadData.mpData->Release(); // Matches an implicit AddRef in EAThreadDynamicData constructor
|
||||
|
||||
// C++11 Threads don't support user-supplied stacks. A user-supplied stack pointer
|
||||
// here would be a waste of user memory, and so we assert that mpStack == NULL.
|
||||
EAT_ASSERT(!pTP || (pTP->mpStack == NULL));
|
||||
|
||||
// We use the pData temporary throughout this function because it's possible that mThreadData.mpData could be
|
||||
// modified as we are executing, in particular in the case that mThreadData.mpData is destroyed and changed
|
||||
// during execution.
|
||||
EAThreadDynamicData* pDataAddr = AllocateThreadDynamicData();
|
||||
EAT_ASSERT(pDataAddr != nullptr);
|
||||
EAThreadDynamicData* pData = new(pDataAddr) EAThreadDynamicData(pRunnable, pContext, pUserWrapper, RunnableObjectInternal); // Note that we use a special new here which doesn't use the heap.
|
||||
EAT_ASSERT(pData != nullptr);
|
||||
mThreadData.mpData = pData;
|
||||
if (pTP)
|
||||
SetName(pTP->mpName);
|
||||
|
||||
EAT_ASSERT(pData && pData->mpComp);
|
||||
return pData->mpComp->mThread.get_id();
|
||||
}
|
||||
|
||||
Thread::Status Thread::WaitForEnd(const ThreadTime& timeoutAbsolute, intptr_t* pThreadReturnValue)
|
||||
{
|
||||
// The mThreadData memory is shared between threads and when
|
||||
// reading it we must be synchronized.
|
||||
EAReadWriteBarrier();
|
||||
|
||||
// A mutex lock around mpData is not needed below because
|
||||
// mpData is never allowed to go from non-NULL to NULL.
|
||||
// Todo: Consider that there may be a subtle race condition here if
|
||||
// the user immediately calls WaitForEnd right after calling Begin.
|
||||
if (mThreadData.mpData && mThreadData.mpData->mpComp)
|
||||
{
|
||||
// We must not call WaitForEnd from the thread we are waiting to end. That would result in a deadlock.
|
||||
EAT_ASSERT(mThreadData.mpData->mpComp->mThread.get_id() != GetThreadId());
|
||||
|
||||
std::chrono::milliseconds timeoutAbsoluteMs(timeoutAbsolute);
|
||||
std::chrono::time_point<std::chrono::system_clock> timeoutTime(timeoutAbsoluteMs);
|
||||
if (mThreadData.mpData->mpComp->mReturnFuture.wait_until(timeoutTime) == std::future_status::timeout)
|
||||
{
|
||||
return kStatusRunning;
|
||||
}
|
||||
|
||||
if (pThreadReturnValue)
|
||||
{
|
||||
mThreadData.mpData->mReturnValue = mThreadData.mpData->mpComp->mReturnFuture.get();
|
||||
*pThreadReturnValue = mThreadData.mpData->mReturnValue;
|
||||
}
|
||||
|
||||
mThreadData.mpData->mpComp->mThread.join();
|
||||
|
||||
return kStatusEnded; // A thread was created, so it must have ended.
|
||||
}
|
||||
else
|
||||
{
|
||||
// Else the user hasn't started the thread yet, so we wait until the user starts it.
|
||||
// Ideally, what we really want to do here is wait for some kind of signal.
|
||||
// Instead for the time being we do a polling loop.
|
||||
while ((!mThreadData.mpData) && (GetThreadTime() < timeoutAbsolute))
|
||||
{
|
||||
ThreadSleep(1);
|
||||
}
|
||||
if (mThreadData.mpData)
|
||||
return WaitForEnd(timeoutAbsolute);
|
||||
}
|
||||
return kStatusNone; // No thread has been started.
|
||||
}
|
||||
|
||||
Thread::Status Thread::GetStatus(intptr_t* pThreadReturnValue) const
|
||||
{
|
||||
if (mThreadData.mpData && mThreadData.mpData->mpComp)
|
||||
{
|
||||
auto status = static_cast<Thread::Status>(mThreadData.mpData->mStatus.GetValue());
|
||||
if (pThreadReturnValue && status == kStatusEnded)
|
||||
{
|
||||
if (mThreadData.mpData->mpComp->mGetStatusFuture.valid())
|
||||
mThreadData.mpData->mReturnValue = mThreadData.mpData->mpComp->mGetStatusFuture.get();
|
||||
*pThreadReturnValue = mThreadData.mpData->mReturnValue;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
return kStatusNone;
|
||||
}
|
||||
|
||||
int Thread::GetPriority() const
|
||||
{
|
||||
// No way to query or set thread priority through standard C++11 thread library.
|
||||
// On some platforms this could be implemented through platform specific APIs using native_handle()
|
||||
return kThreadPriorityDefault;
|
||||
}
|
||||
|
||||
|
||||
bool Thread::SetPriority(int nPriority)
|
||||
{
|
||||
// No way to query or set thread priority through standard C++11 thread library.
|
||||
// On some platforms this could be implemented through platform specific APIs using native_handle()
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void Thread::SetProcessor(int nProcessor)
|
||||
{
|
||||
// No way to query or set thread priority through standard C++11 thread library.
|
||||
// On some platforms this could be implemented through platform specific APIs using native_handle()
|
||||
}
|
||||
|
||||
void EA::Thread::Thread::SetAffinityMask(EA::Thread::ThreadAffinityMask nAffinityMask)
|
||||
{
|
||||
if(mThreadData.mpData)
|
||||
{
|
||||
EA::Thread::SetThreadAffinityMask(nAffinityMask);
|
||||
}
|
||||
}
|
||||
|
||||
EA::Thread::ThreadAffinityMask EA::Thread::Thread::GetAffinityMask()
|
||||
{
|
||||
if(mThreadData.mpData)
|
||||
{
|
||||
return mThreadData.mpData->mnThreadAffinityMask;
|
||||
}
|
||||
|
||||
return kThreadAffinityMaskAny;
|
||||
}
|
||||
|
||||
void Thread::Wake()
|
||||
{
|
||||
// No way to wake a thread through standard C++11 thread library.
|
||||
// On some platforms this could be implemented through platform specific APIs using native_handle()
|
||||
}
|
||||
|
||||
|
||||
const char* Thread::GetName() const
|
||||
{
|
||||
if (mThreadData.mpData)
|
||||
return mThreadData.mpData->mName;
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
void Thread::SetName(const char* pName)
|
||||
{
|
||||
if (mThreadData.mpData && pName)
|
||||
{
|
||||
strncpy(mThreadData.mpData->mName, pName, EATHREAD_NAME_SIZE);
|
||||
mThreadData.mpData->mName[EATHREAD_NAME_SIZE - 1] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
ThreadId Thread::GetId() const
|
||||
{
|
||||
if (mThreadData.mpData && mThreadData.mpData->mpComp)
|
||||
return mThreadData.mpData->mpComp->mThread.get_id();
|
||||
return kThreadIdInvalid;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user