mirror of
https://github.com/Mauler125/r5sdk.git
synced 2025-02-09 19:15:03 +01:00
DirtySDK (EA's Dirty Sockets library) will be used for the LiveAPI implementation, and depends on: EABase, EAThread.
798 lines
24 KiB
C++
798 lines
24 KiB
C++
///////////////////////////////////////////////////////////////////////////////
|
|
// Copyright (c) Electronic Arts Inc. All rights reserved.
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Implements a fast, user-space mutex. Also known as a lightweight mutex.
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
#ifndef EATHREAD_EATHREAD_FUTEX_H
|
|
#define EATHREAD_EATHREAD_FUTEX_H
|
|
|
|
#include <eathread/eathread.h>
|
|
#include <eathread/eathread_atomic.h>
|
|
#include <eathread/eathread_sync.h>
|
|
#include <eathread/eathread_mutex.h>
|
|
#include <stddef.h>
|
|
|
|
#if defined(EA_PRAGMA_ONCE_SUPPORTED)
|
|
#pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result.
|
|
#endif
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// EATHREAD_MANUAL_FUTEX_ENABLED
|
|
//
|
|
// Defined as 0 or 1.
|
|
// If enabled then Futex is implemented with atomics and semaphores instead of
|
|
// via a direct system-supported lightweight mutex implementation.
|
|
//
|
|
#ifndef EATHREAD_MANUAL_FUTEX_ENABLED
|
|
#if defined(EA_PLATFORM_MICROSOFT) // VC++ has CriticalSection, which is a futex.
|
|
#define EATHREAD_MANUAL_FUTEX_ENABLED 0 // Currently 0 because that's best. Can be set to 1.
|
|
#elif defined(EA_PLATFORM_SONY)
|
|
#define EATHREAD_MANUAL_FUTEX_ENABLED 0 // Allows us to have a spin count.
|
|
#else
|
|
#define EATHREAD_MANUAL_FUTEX_ENABLED 1 // Set to 1 until we can resolve any dependencies such as PPMalloc.
|
|
#endif
|
|
#endif
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// EATHREAD_FUTEX_SPIN_COUNT
|
|
//
|
|
#ifndef EATHREAD_FUTEX_SPIN_COUNT
|
|
#define EATHREAD_FUTEX_SPIN_COUNT 256
|
|
#endif
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////
|
|
/// Futex data
|
|
///
|
|
/// This is used internally by class Futex.
|
|
/// Note that we don't use an EAThread semaphore, as the direct semaphore
|
|
/// we use here is more direct and avoid inefficiencies that result from
|
|
/// the possibility of EAThread semaphores being optimized for being
|
|
/// standalone.
|
|
///
|
|
#if !EA_THREADS_AVAILABLE
|
|
#define EA_THREAD_NONTHREADED_FUTEX 1
|
|
|
|
#if EATHREAD_MANUAL_FUTEX_ENABLED
|
|
struct EAFutexSemaphore
|
|
{
|
|
int mnCount;
|
|
};
|
|
#endif
|
|
|
|
#elif EA_USE_CPP11_CONCURRENCY
|
|
EA_DISABLE_VC_WARNING(4265 4365 4836 4571 4625 4626 4628 4193 4127 4548)
|
|
#include <mutex>
|
|
EA_RESTORE_VC_WARNING()
|
|
|
|
#elif defined(EA_PLATFORM_APPLE)
|
|
#if EATHREAD_MANUAL_FUTEX_ENABLED
|
|
#include <eathread/eathread_semaphore.h>
|
|
typedef EA::Thread::Semaphore EAFutexSemaphore;
|
|
#endif
|
|
|
|
#elif defined(EA_PLATFORM_SONY)
|
|
#if EATHREAD_MANUAL_FUTEX_ENABLED
|
|
#include <kernel/semaphore.h>
|
|
#include <eathread/internal/timings.h>
|
|
|
|
typedef SceKernelSema EAFutexSemaphore;
|
|
#endif
|
|
|
|
#elif defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE
|
|
#if EATHREAD_MANUAL_FUTEX_ENABLED
|
|
#include <semaphore.h>
|
|
typedef sem_t EAFutexSemaphore;
|
|
#endif
|
|
|
|
#elif defined(EA_PLATFORM_MICROSOFT)
|
|
|
|
// We avoid #including heavy system headers, as this file is a common header itself.
|
|
|
|
extern "C"
|
|
{
|
|
#if defined(EA_COMPILER_GNUC)
|
|
// Mingw declares these slightly differently.
|
|
struct _CRITICAL_SECTION;
|
|
__declspec(dllimport) int __stdcall InitializeCriticalSectionAndSpinCount(_CRITICAL_SECTION* pCriticalSection, unsigned long dwSpinCount);
|
|
__declspec(dllimport) void __stdcall InitializeCriticalSection(_CRITICAL_SECTION* pCriticalSection);
|
|
__declspec(dllimport) void __stdcall DeleteCriticalSection(_CRITICAL_SECTION* pCriticalSection);
|
|
__declspec(dllimport) void __stdcall EnterCriticalSection(_CRITICAL_SECTION* pCriticalSection);
|
|
__declspec(dllimport) void __stdcall LeaveCriticalSection(_CRITICAL_SECTION* pCriticalSection);
|
|
__declspec(dllimport) int __stdcall TryEnterCriticalSection(_CRITICAL_SECTION* pCriticalSection);
|
|
#else
|
|
#if !defined _Must_inspect_result_
|
|
#define _Must_inspect_result_
|
|
#endif
|
|
|
|
struct _RTL_CRITICAL_SECTION;
|
|
__declspec(dllimport) _Must_inspect_result_ int __stdcall InitializeCriticalSectionAndSpinCount(_Out_ _RTL_CRITICAL_SECTION* pCriticalSection, _In_ unsigned long dwSpinCount);
|
|
__declspec(dllimport) void __stdcall InitializeCriticalSection(_Out_ _RTL_CRITICAL_SECTION* pCriticalSection);
|
|
__declspec(dllimport) void __stdcall DeleteCriticalSection(_Inout_ _RTL_CRITICAL_SECTION* pCriticalSection);
|
|
__declspec(dllimport) void __stdcall EnterCriticalSection(_Inout_ _RTL_CRITICAL_SECTION* pCriticalSection);
|
|
__declspec(dllimport) void __stdcall LeaveCriticalSection(_Inout_ _RTL_CRITICAL_SECTION* pCriticalSection);
|
|
__declspec(dllimport) int __stdcall TryEnterCriticalSection(_Inout_ _RTL_CRITICAL_SECTION* pCriticalSection);
|
|
#endif
|
|
|
|
__declspec(dllimport) unsigned long __stdcall GetCurrentThreadId();
|
|
}
|
|
|
|
#if EATHREAD_MANUAL_FUTEX_ENABLED
|
|
typedef void* EAFutexSemaphore; // void* instead of HANDLE to avoid #including windows headers.
|
|
#endif
|
|
|
|
#else
|
|
#define EA_THREAD_NONTHREADED_FUTEX 1
|
|
|
|
#if EATHREAD_MANUAL_FUTEX_ENABLED
|
|
struct EAFutexSemaphore
|
|
{
|
|
int mnCount;
|
|
};
|
|
#endif
|
|
#endif
|
|
/////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
namespace EA
|
|
{
|
|
namespace Thread
|
|
{
|
|
#if defined(EA_COMPILER_MSVC) && EA_PLATFORM_PTR_SIZE == 8
|
|
static const int FUTEX_PLATFORM_DATA_SIZE = 40; // CRITICAL_SECTION is 40 bytes on Win64.
|
|
#elif defined(EA_COMPILER_MSVC) && EA_PLATFORM_PTR_SIZE == 4
|
|
static const int FUTEX_PLATFORM_DATA_SIZE = 32; // CRITICAL_SECTION is 24 bytes on Win32 and 28 bytes on XBox 360.
|
|
#endif
|
|
|
|
|
|
/// class Futex
|
|
///
|
|
/// A Futex is a fast user-space mutex. It works by attempting to use
|
|
/// atomic integer updates for the common case whereby the mutex is
|
|
/// not already locked. If the mutex is already locked then the futex
|
|
/// drops down to waiting on a system-level semaphore. The result is
|
|
/// that uncontested locking operations can be significantly faster
|
|
/// than contested locks. Contested locks are slightly slower than in
|
|
/// the case of a formal mutex, but usually not by much.
|
|
///
|
|
/// The Futex acts the same as a conventional mutex with respect to
|
|
/// memory synchronization. Specifically:
|
|
/// - A Lock or successful TryLock implies a read barrier (i.e. acquire).
|
|
/// - A second lock by the same thread implies no barrier.
|
|
/// - A failed TryLock implies no barrier.
|
|
/// - A final unlock by a thread implies a write barrier (i.e. release).
|
|
/// - A non-final unlock by a thread implies no barrier.
|
|
///
|
|
/// Futex limitations relative to Mutexes:
|
|
/// - Futexes cannot be inter-process.
|
|
/// - Futexes cannot be named.
|
|
/// - Futexes cannot participate in condition variables. A special
|
|
/// condition variable could be made that works with them, though.
|
|
/// - Futex locks don't have timeouts. This could probably be
|
|
/// added with some work, though users generally shouldn't need timeouts.
|
|
///
|
|
class EATHREADLIB_API Futex
|
|
{
|
|
public:
|
|
enum Result
|
|
{
|
|
kResultTimeout = -2
|
|
};
|
|
|
|
/// Futex
|
|
///
|
|
/// Creates a Futex. There are no creation options.
|
|
///
|
|
Futex();
|
|
|
|
/// ~Futex
|
|
///
|
|
/// Destroys an existing futex. The futex must not be locked by any thread
|
|
/// upon this call, otherwise the resulting behaviour is undefined.
|
|
///
|
|
~Futex();
|
|
|
|
/// TryLock
|
|
///
|
|
/// Tries to lock the futex; returns true if possible.
|
|
/// This function always returns immediately. It will return false if
|
|
/// the futex is locked by another thread, and it will return true
|
|
/// if the futex is not locked or is already locked by the current thread.
|
|
///
|
|
bool TryLock();
|
|
|
|
/// Lock
|
|
///
|
|
/// Locks the futex; returns the new lock count.
|
|
/// If the futex is locked by another thread, this call will block until
|
|
/// the futex is unlocked. If the futex is not locked or is locked by the
|
|
/// current thread, this call will return immediately.
|
|
///
|
|
void Lock();
|
|
|
|
/// Lock
|
|
///
|
|
/// Tries to lock the futex until the given time.
|
|
/// If the futex is locked by another thread, this call will block until
|
|
/// the futex is unlocked or the given time has passed. If the futex is not locked
|
|
/// or is locked by the current thread, this call will return immediately.
|
|
///
|
|
/// Return value:
|
|
/// kResultTimeout Timeout
|
|
/// > 0 The new lock count.
|
|
int Lock(const ThreadTime& timeoutAbsolute);
|
|
|
|
/// Unlock
|
|
///
|
|
/// Unlocks the futex. The futex must already be locked at least once by
|
|
/// the calling thread. Otherwise the behaviour is not defined.
|
|
///
|
|
void Unlock();
|
|
|
|
/// GetLockCount
|
|
///
|
|
/// Returns the number of locks on the futex. The return value from this
|
|
/// function is only reliable if the calling thread already has one lock on
|
|
/// the futex. Otherwise the returned value may not represent actual value
|
|
/// at any point in time, as other threads lock or unlock the futex soon after the call.
|
|
///
|
|
int GetLockCount() const;
|
|
|
|
/// HasLock
|
|
/// Returns true if the current thread has the futex locked.
|
|
bool HasLock() const;
|
|
|
|
/// SetSpinCount
|
|
/// Specifies how many times we spin while waiting to acquire the lock.
|
|
void SetSpinCount(Uint spinCount);
|
|
|
|
protected:
|
|
#if EATHREAD_MANUAL_FUTEX_ENABLED
|
|
void CreateFSemaphore();
|
|
void DestroyFSemaphore();
|
|
void SignalFSemaphore();
|
|
void WaitFSemaphore();
|
|
bool WaitFSemaphore(const ThreadTime& timeoutAbsolute);
|
|
void OnLockAcquired(ThreadUniqueId threadUniqueId);
|
|
#endif
|
|
|
|
private:
|
|
// Objects of this class are not copyable.
|
|
Futex(const Futex&){}
|
|
Futex& operator=(const Futex&){ return *this; }
|
|
|
|
protected:
|
|
#if EATHREAD_MANUAL_FUTEX_ENABLED
|
|
AtomicUWord mUseCount; /// Not the same thing as lock count, as waiters increment this value.
|
|
uint16_t mRecursionCount; /// The number of times the lock-owning thread has the mutex. This is currently uint16_t for backward compatibility with PPMalloc.
|
|
uint16_t mSpinCount; /// The number of times we spin while waiting for the lock. To do: Change these to be uint32_t once PPMalloc is no longer dependent on this.
|
|
ThreadUniqueId mThreadUniqueId; /// Unique id for owning thread; not necessarily same as type ThreadId.
|
|
EAFutexSemaphore mSemaphore; /// OS-level semaphore that waiters wait on when lock attempts failed.
|
|
#else
|
|
|
|
#if EA_USE_CPP11_CONCURRENCY
|
|
std::recursive_timed_mutex mMutex;
|
|
int mnLockCount;
|
|
std::thread::id mLockingThread;
|
|
#elif defined(EA_COMPILER_MSVC) && defined(EA_PLATFORM_MICROSOFT) // In the case of Microsoft platforms, we just use CRITICAL_SECTION, as it is essentially a futex.
|
|
// We use raw structure math because otherwise we'd expose the user to system headers,
|
|
// which breaks code and bloats builds. We validate our math in eathread_futex.cpp.
|
|
#if EA_PLATFORM_PTR_SIZE == 8
|
|
uint64_t mCRITICAL_SECTION[FUTEX_PLATFORM_DATA_SIZE / sizeof(uint64_t)];
|
|
#else
|
|
uint64_t mCRITICAL_SECTION[FUTEX_PLATFORM_DATA_SIZE / sizeof(uint64_t)];
|
|
#endif
|
|
#elif defined(EA_PLATFORM_SONY)
|
|
EA::Thread::Mutex mMutex;
|
|
Uint mSpinCount;
|
|
#else
|
|
#define EAT_FUTEX_USE_MUTEX 1
|
|
EA::Thread::Mutex mMutex;
|
|
#endif
|
|
#endif
|
|
};
|
|
|
|
|
|
|
|
/// FutexFactory
|
|
///
|
|
/// Implements a factory-based creation and destruction mechanism for class Futex.
|
|
/// A primary use of this would be to allow the Futex implementation to reside in
|
|
/// a private library while users of the class interact only with the interface
|
|
/// header and the factory. The factory provides conventional create/destroy
|
|
/// semantics which use global operator new, but also provides manual construction/
|
|
/// destruction semantics so that the user can provide for memory allocation
|
|
/// and deallocation.
|
|
///
|
|
class EATHREADLIB_API FutexFactory
|
|
{
|
|
public:
|
|
static Futex* CreateFutex(); // Internally implemented as: return new Futex;
|
|
static void DestroyFutex(Futex* pFutex); // Internally implemented as: delete pFutex;
|
|
|
|
static size_t GetFutexSize(); // Internally implemented as: return sizeof(Futex);
|
|
static Futex* ConstructFutex(void* pMemory); // Internally implemented as: return new(pMemory) Futex;
|
|
static void DestructFutex(Futex* pFutex); // Internally implemented as: pFutex->~Futex();
|
|
};
|
|
|
|
|
|
|
|
/// class AutoFutex
|
|
/// An AutoFutex locks the Futex in its constructor and
|
|
/// unlocks the Futex in its destructor (when it goes out of scope).
|
|
class EATHREADLIB_API AutoFutex
|
|
{
|
|
public:
|
|
AutoFutex(Futex& futex);
|
|
~AutoFutex();
|
|
|
|
protected:
|
|
Futex& mFutex;
|
|
|
|
// Prevent copying by default, as copying is dangerous.
|
|
AutoFutex(const AutoFutex&);
|
|
const AutoFutex& operator=(const AutoFutex&);
|
|
};
|
|
|
|
|
|
} // namespace Thread
|
|
|
|
} // namespace EA
|
|
|
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Inlines
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// EAFutexReadBarrier
|
|
//
|
|
// For futexes, which are intended to be used only in user-space and without
|
|
// talking to IO devices, DMA memory, or uncached memory, we directly use
|
|
// memory barriers.
|
|
#define EAFutexReadBarrier EAReadBarrier
|
|
#define EAFutexWriteBarrier EAWriteBarrier
|
|
#define EAFutexReadWriteBarrier EAReadWriteBarrier
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
namespace EA
|
|
{
|
|
namespace Thread
|
|
{
|
|
#if EATHREAD_MANUAL_FUTEX_ENABLED
|
|
|
|
inline Futex::Futex()
|
|
: mUseCount(0),
|
|
mRecursionCount(0),
|
|
mSpinCount(EATHREAD_FUTEX_SPIN_COUNT),
|
|
mThreadUniqueId(kThreadUniqueIdInvalid),
|
|
mSemaphore()
|
|
{
|
|
CreateFSemaphore();
|
|
}
|
|
|
|
|
|
inline Futex::~Futex()
|
|
{
|
|
EAT_ASSERT(mUseCount == 0);
|
|
|
|
DestroyFSemaphore();
|
|
}
|
|
|
|
|
|
inline void Futex::OnLockAcquired(ThreadUniqueId threadUniqueId)
|
|
{
|
|
EAFutexReadBarrier();
|
|
mThreadUniqueId = threadUniqueId;
|
|
mRecursionCount = 1;
|
|
}
|
|
|
|
|
|
inline bool Futex::TryLock()
|
|
{
|
|
ThreadUniqueId threadUniqueId;
|
|
EAThreadGetUniqueId(threadUniqueId);
|
|
|
|
if(mUseCount.SetValueConditional(1, 0)) // If we could acquire the lock... (set it to 1 if it's 0)
|
|
{
|
|
OnLockAcquired(threadUniqueId);
|
|
return true;
|
|
}
|
|
|
|
// This only happens in the case of recursion on the same thread
|
|
// This is threadsafe because the only case where this equality passes
|
|
// is when this value was set on this thread anyway.
|
|
if(EATHREAD_LIKELY(mThreadUniqueId == threadUniqueId)) // If it turns out that we already have the lock...
|
|
{
|
|
++mUseCount;
|
|
++mRecursionCount;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
inline void Futex::Lock()
|
|
{
|
|
ThreadUniqueId threadUniqueId;
|
|
EAThreadGetUniqueId(threadUniqueId);
|
|
|
|
if(mSpinCount) // If we have spinning enabled (usually true)...
|
|
{
|
|
if(mUseCount.SetValueConditional(1, 0)) // If we could acquire the lock... (set it to 1 if it's 0)
|
|
{
|
|
OnLockAcquired(threadUniqueId);
|
|
return;
|
|
}
|
|
|
|
if(mThreadUniqueId != threadUniqueId) // Don't spin if we already have the lock.
|
|
{
|
|
for(Uint count = mSpinCount; count > 0; count--) // Implement a spin lock for a number of tries.
|
|
{
|
|
// We use GetValueRaw calls below instead of atomics because we don't want atomic behavior.
|
|
if(mUseCount.GetValueRaw() > 1) // If there are multiple waiters, don't bother spinning any more, as they are already spinning themselves.
|
|
break;
|
|
|
|
if(mUseCount.GetValueRaw() == 0) // If it looks like the lock is now free, try to acquire it.
|
|
{
|
|
if(mUseCount.SetValueConditional(1, 0)) // If we could acquire the lock... (set it to 1 if it's 0)
|
|
{
|
|
OnLockAcquired(threadUniqueId);
|
|
return;
|
|
}
|
|
}
|
|
|
|
EAProcessorPause();
|
|
}
|
|
}
|
|
}
|
|
|
|
if(++mUseCount > 1) // If we could not get the lock (previous value of mUseCount was >= 1 and not 0) or we already had the lock...
|
|
{
|
|
if(mThreadUniqueId == threadUniqueId) // If we already have the lock...
|
|
{
|
|
mRecursionCount++;
|
|
return;
|
|
}
|
|
WaitFSemaphore();
|
|
}
|
|
// Else the increment was from 0 to 1, and we own the lock.
|
|
OnLockAcquired(threadUniqueId);
|
|
}
|
|
|
|
|
|
|
|
|
|
inline int Futex::Lock(const ThreadTime& timeoutAbsolute)
|
|
{
|
|
if(timeoutAbsolute == kTimeoutNone)
|
|
{
|
|
Lock();
|
|
return (int)mRecursionCount;
|
|
}
|
|
else if(timeoutAbsolute == kTimeoutImmediate)
|
|
{
|
|
if(TryLock())
|
|
return (int)mRecursionCount;
|
|
else
|
|
return kResultTimeout;
|
|
}
|
|
else
|
|
{
|
|
ThreadUniqueId threadUniqueId;
|
|
EAThreadGetUniqueId(threadUniqueId);
|
|
|
|
if(++mUseCount > 1) // If we could not get the lock (previous value of mUseCount was >= 1 and not 0) or we already had the lock...
|
|
{
|
|
if(mThreadUniqueId == threadUniqueId) // If we already have the lock...
|
|
return (int)++mRecursionCount;
|
|
|
|
if(!WaitFSemaphore(timeoutAbsolute))
|
|
{
|
|
--mUseCount;
|
|
return kResultTimeout;
|
|
}
|
|
}
|
|
// Else the increment was from 0 to 1, and we own the lock.
|
|
OnLockAcquired(threadUniqueId);
|
|
return 1; // Return mRecursionCount.
|
|
}
|
|
}
|
|
|
|
|
|
inline void Futex::Unlock()
|
|
{
|
|
#if EAT_ASSERT_ENABLED
|
|
ThreadUniqueId threadUniqueId;
|
|
EAThreadGetUniqueId(threadUniqueId);
|
|
EAT_ASSERT(mThreadUniqueId == threadUniqueId);
|
|
EAT_ASSERT((mRecursionCount > 0) && (mUseCount > 0));
|
|
#endif
|
|
|
|
if(EATHREAD_LIKELY(--mRecursionCount == 0))
|
|
{
|
|
mThreadUniqueId = kThreadUniqueIdInvalid;
|
|
|
|
// after the decrement below we will no longer own the lock
|
|
EAFutexWriteBarrier();
|
|
if(EATHREAD_UNLIKELY(--mUseCount > 0))
|
|
SignalFSemaphore();
|
|
}
|
|
else
|
|
{
|
|
// this thread still owns the lock, was recursive
|
|
--mUseCount;
|
|
}
|
|
}
|
|
|
|
|
|
inline int Futex::GetLockCount() const
|
|
{
|
|
// No atomic operation or memory barrier required, as this function only
|
|
// has validity if it is being called from the lock-owning thread. However,
|
|
// we don't at this time choose to assert that mThreadUniqueId == GetThreadId().
|
|
return (int)mRecursionCount;
|
|
}
|
|
|
|
|
|
inline bool Futex::HasLock() const
|
|
{
|
|
ThreadUniqueId threadUniqueId;
|
|
EAThreadGetUniqueId(threadUniqueId);
|
|
|
|
return (mThreadUniqueId == threadUniqueId);
|
|
}
|
|
|
|
|
|
inline void Futex::SetSpinCount(Uint spinCount)
|
|
{
|
|
mSpinCount = spinCount;
|
|
}
|
|
|
|
#else // #if EATHREAD_MANUAL_FUTEX_ENABLED
|
|
|
|
#if EA_USE_CPP11_CONCURRENCY
|
|
|
|
inline Futex::Futex() : mnLockCount(0) {}
|
|
|
|
inline Futex::~Futex() { EAT_ASSERT(!GetLockCount()); }
|
|
|
|
inline bool Futex::TryLock()
|
|
{
|
|
if (mMutex.try_lock())
|
|
{
|
|
EAT_ASSERT(mnLockCount >= 0);
|
|
EAT_ASSERT(mnLockCount == 0 || mLockingThread == std::this_thread::get_id());
|
|
++mnLockCount;
|
|
mLockingThread = std::this_thread::get_id();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
inline void Futex::Lock() { mMutex.lock(); mLockingThread = std::this_thread::get_id(); ++mnLockCount; }
|
|
|
|
inline int Futex::Lock(const ThreadTime& timeoutAbsolute)
|
|
{
|
|
if (timeoutAbsolute == kTimeoutNone)
|
|
{
|
|
if (!mMutex.try_lock())
|
|
{
|
|
return kResultTimeout;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
std::chrono::milliseconds timeoutAbsoluteMs(timeoutAbsolute);
|
|
std::chrono::time_point<std::chrono::system_clock> timeout_time(timeoutAbsoluteMs);
|
|
if (!mMutex.try_lock_until(timeout_time))
|
|
{
|
|
return kResultTimeout;
|
|
}
|
|
}
|
|
|
|
EAT_ASSERT(mnLockCount >= 0);
|
|
EAT_ASSERT(mnLockCount == 0 || mLockingThread == std::this_thread::get_id());
|
|
mLockingThread = std::this_thread::get_id();
|
|
return ++mnLockCount; // This is safe to do because we have the lock.
|
|
}
|
|
|
|
inline void Futex::Unlock()
|
|
{
|
|
EAT_ASSERT(HasLock());
|
|
--mnLockCount;
|
|
if (mnLockCount == 0)
|
|
mLockingThread = std::thread::id();
|
|
mMutex.unlock();
|
|
}
|
|
|
|
inline int Futex::GetLockCount() const { return mnLockCount; }
|
|
|
|
inline bool Futex::HasLock() const
|
|
{
|
|
if ((mnLockCount > 0) && (std::this_thread::get_id() == mLockingThread))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
inline void Futex::SetSpinCount(Uint)
|
|
{
|
|
// Not supported
|
|
}
|
|
|
|
#elif defined(EA_COMPILER_MSVC) && defined(EA_PLATFORM_MICROSOFT) // Win32, Win64, etc.
|
|
|
|
inline Futex::Futex()
|
|
{
|
|
// We use InitializeCriticalSectionAndSpinCount, as that has resulted in improved performance in practice on multiprocessors systems.
|
|
int rv = InitializeCriticalSectionAndSpinCount((_RTL_CRITICAL_SECTION*)mCRITICAL_SECTION, EATHREAD_FUTEX_SPIN_COUNT);
|
|
EAT_ASSERT(rv != 0);
|
|
EA_UNUSED(rv);
|
|
}
|
|
|
|
inline Futex::~Futex()
|
|
{
|
|
EAT_ASSERT(!GetLockCount());
|
|
DeleteCriticalSection((_RTL_CRITICAL_SECTION*)mCRITICAL_SECTION);
|
|
}
|
|
|
|
inline bool Futex::TryLock()
|
|
{
|
|
return TryEnterCriticalSection((_RTL_CRITICAL_SECTION*)mCRITICAL_SECTION) != 0;
|
|
}
|
|
|
|
inline void Futex::Lock()
|
|
{
|
|
EnterCriticalSection((_RTL_CRITICAL_SECTION*)mCRITICAL_SECTION);
|
|
}
|
|
|
|
inline int Futex::Lock(const ThreadTime& timeoutAbsolute)
|
|
{
|
|
if(timeoutAbsolute == kTimeoutNone)
|
|
{
|
|
Lock();
|
|
return GetLockCount();
|
|
}
|
|
else if(timeoutAbsolute == kTimeoutImmediate)
|
|
{
|
|
if(TryLock())
|
|
return GetLockCount();
|
|
else
|
|
return kResultTimeout;
|
|
}
|
|
else
|
|
{
|
|
while(!TryLock())
|
|
{
|
|
if(GetThreadTime() >= timeoutAbsolute)
|
|
return kResultTimeout;
|
|
ThreadSleep(1);
|
|
}
|
|
return GetLockCount();
|
|
}
|
|
}
|
|
|
|
inline void Futex::Unlock()
|
|
{
|
|
EAT_ASSERT(HasLock());
|
|
LeaveCriticalSection((_RTL_CRITICAL_SECTION*)mCRITICAL_SECTION);
|
|
}
|
|
|
|
inline int Futex::GetLockCount() const
|
|
{
|
|
// Return the RecursionCount member of RTL_CRITICAL_SECTION.
|
|
|
|
// We use raw structure math because otherwise we'd expose the user to system headers,
|
|
// which breaks code and bloats builds. We validate our math in eathread_futex.cpp.
|
|
#if EA_PLATFORM_PTR_SIZE == 8
|
|
return *((int*)mCRITICAL_SECTION + 3);
|
|
#else
|
|
return *((int*)mCRITICAL_SECTION + 2);
|
|
#endif
|
|
}
|
|
|
|
inline bool Futex::HasLock() const
|
|
{
|
|
// Check the OwningThread member of RTL_CRITICAL_SECTION.
|
|
|
|
// We use raw structure math because otherwise we'd expose the user to system headers,
|
|
// which breaks code and bloats builds. We validate our math in eathread_futex.cpp.
|
|
#if EA_PLATFORM_PTR_SIZE == 8
|
|
return (*((uint32_t*)mCRITICAL_SECTION + 4) == (uintptr_t)GetCurrentThreadId());
|
|
#else
|
|
return (*((uint32_t*)mCRITICAL_SECTION + 3) == (uintptr_t)GetCurrentThreadId());
|
|
#endif
|
|
}
|
|
|
|
inline void Futex::SetSpinCount(Uint)
|
|
{
|
|
// Not supported
|
|
}
|
|
|
|
#elif defined(EAT_FUTEX_USE_MUTEX)
|
|
|
|
inline Futex::Futex()
|
|
{ }
|
|
|
|
inline Futex::~Futex()
|
|
{ }
|
|
|
|
inline bool Futex::TryLock()
|
|
{ return mMutex.Lock(EA::Thread::kTimeoutImmediate) > 0; }
|
|
|
|
inline void Futex::Lock()
|
|
{ mMutex.Lock(); }
|
|
|
|
inline int Futex::Lock(const ThreadTime& timeoutAbsolute)
|
|
{ return mMutex.Lock(timeoutAbsolute); }
|
|
|
|
inline void Futex::Unlock()
|
|
{ mMutex.Unlock(); }
|
|
|
|
inline int Futex::GetLockCount() const
|
|
{ return mMutex.GetLockCount(); }
|
|
|
|
inline bool Futex::HasLock() const
|
|
{ return mMutex.HasLock(); }
|
|
|
|
inline void Futex::SetSpinCount(Uint)
|
|
{ }
|
|
|
|
#endif // EA_COMPILER_MSVC
|
|
|
|
#endif // EATHREAD_MANUAL_FUTEX_ENABLED
|
|
|
|
|
|
|
|
inline AutoFutex::AutoFutex(Futex& futex)
|
|
: mFutex(futex)
|
|
{
|
|
mFutex.Lock();
|
|
}
|
|
|
|
inline AutoFutex::~AutoFutex()
|
|
{
|
|
mFutex.Unlock();
|
|
}
|
|
|
|
} // namespace Thread
|
|
|
|
} // namespace EA
|
|
|
|
|
|
|
|
#endif // EATHREAD_EATHREAD_FUTEX_H
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|