/* * OpenClonk, http://www.openclonk.org * * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/ * Copyright (c) 2011-2016, The OpenClonk Team and contributors * * Distributed under the terms of the ISC license; see accompanying file * "COPYING" for details. * * "Clonk" is a registered trademark of Matthes Bender, used with permission. * See accompanying file "TRADEMARK" for details. * * To redistribute this file separately, substitute the full license texts * for the above references. */ /* synchronization helper classes */ #ifndef INC_StdSync #define INC_StdSync #ifdef _WIN32 #include "platform/C4windowswrapper.h" class CStdCSec { public: CStdCSec() { InitializeCriticalSection(&sec); } virtual ~CStdCSec() { DeleteCriticalSection(&sec); } protected: CRITICAL_SECTION sec; public: virtual void Enter() { EnterCriticalSection(&sec); } virtual void Leave() { LeaveCriticalSection(&sec); } }; class CStdEvent { public: CStdEvent(bool fManualReset) { hEvent = CreateEvent(nullptr, fManualReset, false, nullptr); } ~CStdEvent() { CloseHandle(hEvent); } protected: HANDLE hEvent; public: void Set() { SetEvent(hEvent); } void Pulse() { PulseEvent(hEvent); } void Reset() { ResetEvent(hEvent); } bool WaitFor(int iMillis) { return WaitForSingleObject(hEvent, iMillis) == WAIT_OBJECT_0; } HANDLE GetEvent() { return hEvent; } }; #else // Value to specify infinite wait. #define INFINITE (~0u) #if defined(HAVE_PTHREAD) #include class CStdCSec { public: CStdCSec() { pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(&mutex, &attr); } virtual ~CStdCSec() { pthread_mutex_destroy(&mutex); } protected: pthread_mutex_t mutex; public: virtual void Enter() { pthread_mutex_lock(&mutex); } virtual void Leave() { pthread_mutex_unlock(&mutex); } }; class CStdEvent { public: CStdEvent(bool fManualReset) : fManualReset(fManualReset), fSet(false) { pthread_cond_init(&cond, nullptr); pthread_mutex_init(&mutex, nullptr); } ~CStdEvent() { pthread_cond_destroy(&cond); pthread_mutex_destroy(&mutex); } protected: pthread_cond_t cond; pthread_mutex_t mutex; bool fManualReset, fSet; public: void Set() { pthread_mutex_lock(&mutex); fSet = true; pthread_cond_broadcast(&cond); pthread_mutex_unlock(&mutex); } void Pulse() { pthread_cond_broadcast(&cond); } void Reset() { pthread_mutex_lock(&mutex); fSet = false; pthread_mutex_unlock(&mutex); } bool WaitFor(unsigned int iMillis) { pthread_mutex_lock(&mutex); // Already set? while (!fSet) { // Use pthread_cond_wait or pthread_cond_timedwait depending on wait length. Check return value. // Note this will temporarily unlock the mutex, so no deadlock should occur. timespec ts = { static_cast(iMillis / 1000), static_cast((iMillis % 1000) * 1000000) }; if (0 != (iMillis != INFINITE ? pthread_cond_timedwait(&cond, &mutex, &ts) : pthread_cond_wait(&cond, &mutex))) { pthread_mutex_unlock(&mutex); return false; } } // Reset flag, release mutex, done. if (!fManualReset) fSet = false; pthread_mutex_unlock(&mutex); return true; } }; #else // Some stubs to silence the compiler class CStdCSec { public: CStdCSec() { } virtual ~CStdCSec() { } virtual void Enter() { } virtual void Leave() { } }; class CStdEvent { public: CStdEvent(bool) { } ~CStdEvent() { } void Set() { } void Pulse() { } void Reset() { } bool WaitFor(int) { return false; } }; #endif // HAVE_PTHREAD #endif // _WIN32 class CStdLock { public: CStdLock(CStdCSec *pSec) : sec(pSec) { sec->Enter(); } ~CStdLock() { Clear(); } protected: CStdCSec *sec; public: void Clear() { if (sec) sec->Leave(); sec = nullptr; } }; class CStdCSecExCallback { public: // is called with CSec exlusive locked! virtual void OnShareFree(class CStdCSecEx *pCSec) = 0; virtual ~CStdCSecExCallback() = default; }; class CStdCSecEx : public CStdCSec { public: CStdCSecEx() : ShareFreeEvent(false) { } CStdCSecEx(CStdCSecExCallback *pCallb) : lShareCnt(0), ShareFreeEvent(false), pCallbClass(pCallb) { } ~CStdCSecEx() override = default; protected: // share counter long lShareCnt{0}; // event: exclusive access permitted CStdEvent ShareFreeEvent; // callback CStdCSecExCallback *pCallbClass{nullptr}; public: // (cycles forever if shared locked by calling thread!) void Enter() override { // lock CStdCSec::Enter(); // wait for share-free while (lShareCnt) { // reset event ShareFreeEvent.Reset(); // leave section for waiting CStdCSec::Leave(); // wait ShareFreeEvent.WaitFor(INFINITE); // reenter section CStdCSec::Enter(); } } void Leave() override { // set event ShareFreeEvent.Set(); // unlock CStdCSec::Leave(); } void EnterShared() { // lock CStdCSec::Enter(); // add share lShareCnt++; // unlock CStdCSec::Leave(); } void LeaveShared() { // lock CStdCSec::Enter(); // remove share if (!--lShareCnt) { // do callback if (pCallbClass) pCallbClass->OnShareFree(this); // set event ShareFreeEvent.Set(); } // unlock CStdCSec::Leave(); } }; class CStdShareLock { public: CStdShareLock(CStdCSecEx *pSec) : sec(pSec) { sec->EnterShared(); } ~CStdShareLock() { Clear(); } protected: CStdCSecEx *sec; public: void Clear() { if (sec) sec->LeaveShared(); sec = nullptr; } }; /* Debug helper class: Set current thread in Set(); assert that it's still the same thread in Check(); */ class StdThreadCheck { #if defined(_DEBUG) && defined(_WIN32) DWORD idThread; public: StdThreadCheck() : idThread(0) {} inline void Set() { idThread = ::GetCurrentThreadId(); } inline void Check() { assert(idThread == ::GetCurrentThreadId()); } #else public: inline void Set() {} inline void Check() { } #endif }; #endif // INC_StdSync