openclonk/src/platform/StdSync.h

300 lines
5.8 KiB
C++

/*
* 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 <pthread.h>
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<time_t>(iMillis / 1000),
static_cast<long>((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