// ReadWriteLock.cpp: implementation of the CReadWriteLock class.
//
//////////////////////////////////////////////////////////////////////

#include "dncmni.h"


//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

#undef DPF_MODNAME
#define DPF_MODNAME "CReadWriteLock::CReadWriteLock"

CReadWriteLock::CReadWriteLock() :
	m_hReadSem(0),
	m_hWriteSem(0),
	m_nReaderWaitingCount(0),
	m_nWriterWaitingCount(0),
	m_nActiveCount(0),
	m_fCritSecInited(FALSE)

#ifdef DEBUG
	,m_dwWriteThread(0)
#endif
{
	DPF_ENTER();
	DPF_EXIT();
}

#undef DPF_MODNAME
#define DPF_MODNAME "CReadWriteLock::~CReadWriteLock"

CReadWriteLock::~CReadWriteLock()
{
	DPF_ENTER();

	DNASSERT(m_nActiveCount == 0);

	if (m_fCritSecInited)
	{
		DNDeleteCriticalSection(&m_csWrite);
	}
	if (m_hReadSem)
	{
		CloseHandle(m_hReadSem);
	}
	if (m_hWriteSem)
	{
		CloseHandle(m_hWriteSem);
	}

	DPF_EXIT();
}

#undef DPF_MODNAME
#define DPF_MODNAME "CReadWriteLock::Init"

BOOL CReadWriteLock::Init()
{
	DPF_ENTER();

	// Core will attempt to initialize us multiple times, just take the first
	if (!m_fCritSecInited)
	{
		m_hReadSem = CreateSemaphore(0,0,MAXLONG,0);
		if (!m_hReadSem)
		{
			goto error;
		}

		m_hWriteSem = CreateSemaphore(0,0,MAXLONG,0);
		if (!m_hWriteSem)
		{
			goto error;
		}

		if (!DNInitializeCriticalSection(&m_csWrite))
		{
			goto error;
		}
		m_fCritSecInited = TRUE;
	}

	DPF_EXIT();

	return TRUE;

error:
	if (m_hReadSem)
	{
		CloseHandle(m_hReadSem);
		m_hReadSem = 0;
	}
	if (m_hWriteSem)
	{
		CloseHandle(m_hWriteSem);
		m_hWriteSem = 0;
	}

	DPF_EXIT();

	return FALSE;
}

#undef DPF_MODNAME
#define DPF_MODNAME "CReadWriteLock::EnterReadLock"

void CReadWriteLock::EnterReadLock()
{
	DPF_ENTER();

	DNASSERT(m_fCritSecInited);

	DNEnterCriticalSection(&m_csWrite);

	// If there is a Writer writing or waiting to write, they have priority
	BOOL fWaitOnWriters = (m_nWriterWaitingCount || (m_nActiveCount < 0));

	if (fWaitOnWriters)
	{
		m_nReaderWaitingCount++;
	}
	else
	{
		m_nActiveCount++;
	}
	DNLeaveCriticalSection(&m_csWrite);

	if (fWaitOnWriters)
	{
		WaitForSingleObject(m_hReadSem, INFINITE);
	}

	DPF_EXIT();
}

#undef DPF_MODNAME
#define DPF_MODNAME "CReadWriteLock::LeaveLock"

void CReadWriteLock::LeaveLock()
{
	DPF_ENTER();

	DNASSERT(m_fCritSecInited);

	DNEnterCriticalSection(&m_csWrite);
	if (m_nActiveCount > 0)
	{
		m_nActiveCount--;
	}
	else
	{
		DEBUG_ONLY(m_dwWriteThread = 0);
		m_nActiveCount++;
	}

	HANDLE hSem = 0;
	LONG lCount = 1;

	if (m_nActiveCount == 0)
	{
		if (m_nWriterWaitingCount > 0)
		{
			m_nActiveCount = -1;
			m_nWriterWaitingCount--;
			hSem = m_hWriteSem;
		}
		else if (m_nReaderWaitingCount > 0)
		{
			m_nActiveCount = m_nReaderWaitingCount;
			m_nReaderWaitingCount = 0;
			hSem = m_hReadSem;
			lCount = m_nActiveCount;
		}
	}

	DNLeaveCriticalSection(&m_csWrite);

	if (hSem)
	{
		ReleaseSemaphore(hSem, lCount, 0);
	}

	DPF_EXIT();
}

#undef DPF_MODNAME
#define DPF_MODNAME "CReadWriteLock::EnterWriteLock"

void CReadWriteLock::EnterWriteLock()
{
	DPF_ENTER();

	DNASSERT(m_fCritSecInited);

	DNEnterCriticalSection(&m_csWrite);

	BOOL fAvailable = (m_nActiveCount == 0);

	if (fAvailable)
	{
		m_nActiveCount = -1;
	}
	else
	{
		DNASSERT(m_dwWriteThread != GetCurrentThreadId()); // No re-entrance!
		m_nWriterWaitingCount++;
	}

	DNLeaveCriticalSection(&m_csWrite);

	if (!fAvailable)
	{
		WaitForSingleObject(m_hWriteSem, INFINITE);
	}
	DEBUG_ONLY(m_dwWriteThread = GetCurrentThreadId());

	DPF_EXIT();
}