Files
2022-10-26 12:25:11 +08:00

807 lines
23 KiB
C++

#ifndef _CIRCUITQUEUE_H_
#define _CIRCUITQUEUE_H_
#include <windows.h>
#include <stdio.h>
#include <assert.h>
//=============================================================================================================================
/// 원형 큐 클래스 템플릿
/**
@author
Baek Sang Hyun
@since
2004. 5. 30
@history
090416 - waverix : add-in method for double buffering
090424 - waverix : select lock functionality
*/
//=============================================================================================================================
template<typename T>
class CircuitQueue;
typedef CircuitQueue<BYTE> CircuitQueueByte;
#ifdef __NA000000_090415_DOUBLE_BUFFERING_FOR_SAFELY_CONNECTION__
template<typename T>
class CircuitQueue
{
public:
CircuitQueue()
: m_bNeedLock(0)
, m_pData(NULL), m_nLength(0), m_nSize(0)
, m_nHead(0), m_nTail(0), m_nExtraSize(0)
{
InitializeCriticalSectionAndSpinCount(&m_cs, 4000);
}
virtual ~CircuitQueue()
{
if(m_pData)
delete [] m_pData;
DeleteCriticalSection(&m_cs);
}
void LockQueue()
{
if(m_bNeedLock)
EnterCriticalSection(&m_cs);
};
void UnlockQueue()
{
if(m_bNeedLock)
LeaveCriticalSection(&m_cs);
}
//=============================================================================================================================
/**
@remarks
큐를 생성한다.
@param nSize
큐 사이즈
@param nExtraSize
패킷이 버퍼 뒤쪽에서 앞쪽으로 나누어 들어갈 때 패킷을 이어주기 위해 확보할
버퍼 뒤쪽 여분의 메모리 크기(실제 여분 메모리 = sizeof(T) * nExtraSize)
*/
//=============================================================================================================================
void Create(int nSize, int nExtraSize = 0, BOOLEAN bDisableLock = 0)
{
if(bDisableLock == false)
m_bNeedLock = true;
//LockQueue();
if(m_pData)
delete [] m_pData;
m_pData = new T[nSize + nExtraSize];
m_nSize = nSize;
m_nExtraSize = nExtraSize;
//UnlockQueue()
}
//=============================================================================================================================
/**
@remarks
큐를 청소한다.
*/
//=============================================================================================================================
inline void Clear()
{
LockQueue();
m_nLength = m_nHead = m_nTail = 0;
UnlockQueue();
}
// (WAVERIX) (BUG-FIX) 예기치 않은 락 해제 상태 방지하기 위한 작업 + 코드 중복 감소
#define __GetSpace() (m_nSize - m_nLength)
#define __GetLength() (m_nLength)
#define __GetBackDataCount() (m_nSize - m_nHead)
#define __GetReadPtr() (m_pData + m_nHead)
#define __GetWritePtr() (m_pData + m_nTail)
/// 큐의 남은 공간을 리턴한다.
inline int GetSpace()
{
int iRet;
LockQueue();
iRet = __GetSpace();
UnlockQueue();
return iRet;
}
/// 현재 사용중인 큐의 크기를 리턴한다.
inline int GetLength()
{
int iRet;
LockQueue();
iRet = __GetLength();
UnlockQueue();
return iRet;
}
/// 데이터가 큐 뒤쪽으로 꽉 차서 앞으로 이어지는 경우, 뒤쪽 끝까지의 데이터 개수를 리턴한다.
inline int GetBackDataCount()
{
int iRet;
LockQueue();
iRet = __GetBackDataCount();
UnlockQueue();
return iRet;
}
/// 데이터를 읽을 포인터(Head)를 리턴한다.
inline T* GetReadPtr()
{
T *pRet;
LockQueue();
pRet = __GetReadPtr();
UnlockQueue();
return pRet;
}
/// 데이터를 쓸 포인터(Tail)를 리턴한다.
inline T* GetWritePtr()
{
T *pRet;
LockQueue();
pRet = __GetWritePtr();
UnlockQueue();
return pRet;
}
/// 잘리지 않고 한번에 읽을 수 있는 길이를 리턴한다.
inline int GetReadableLen()
{
int iRet;
LockQueue();
if(m_nHead == m_nTail) iRet = __GetLength() > 0 ? m_nSize - m_nHead: 0;
else if(m_nHead < m_nTail) iRet = m_nTail - m_nHead;
else iRet = m_nSize - m_nHead;
UnlockQueue();
return iRet;
}
/// 잘리지 않고 한번에 쓰기 가능한 길이를 리턴한다.
inline int GetWritableLen()
{
int iRet;
LockQueue();
if(m_nHead == m_nTail) iRet = __GetLength() > 0 ? 0 : m_nSize - m_nTail;
else if(m_nHead < m_nTail) iRet = m_nSize - m_nTail;
else iRet = m_nHead - m_nTail;
UnlockQueue();
return iRet;
}
//=============================================================================================================================
/**
@remarks
큐에 데이터를 넣는다.
@param pSrc
큐에 삽입할 데이터 포인터
@param nSize
큐에 삽입할 데이터의 길이
@retval BOOL
정상적으로 데이터 삽입이 끝나면 TRUE를 리턴한다.
큐가 꽉차서 삽입할 수 없는 경우 FALSE를 리턴한다.
*/
//=============================================================================================================================
BOOL Enqueue(T* pSrc, int nSize)
{
LockQueue();
if(__GetSpace() < nSize)
{
UnlockQueue();
return FALSE;
}
// pData가 NULL인 경우에는 데이터 큐잉 없이 tail과 length만 갱신한다.
if(pSrc != NULL)
{
if(m_nHead <= m_nTail)
{
// 1. head가 tail보다 앞에 있는 경우
int nBackSpaceCount = m_nSize - m_nTail;
if(nBackSpaceCount >= nSize)
{
// 뒤쪽에 남은 공간이 복사할 양보다 같거나 큰 경우 한번에 복사한다.
memcpy(m_pData + m_nTail, pSrc, sizeof(T) * nSize);
}
else
{
// 뒤쪽에 남은 공간이 복사할 양보다 작은 경우 두번에 나누어 복사한다.
memcpy(m_pData + m_nTail, pSrc, sizeof(T) * nBackSpaceCount);
memcpy(m_pData, pSrc + nBackSpaceCount, sizeof(T) * (nSize - nBackSpaceCount));
}
}
else
{
// 2. head가 tail보다 뒤에 있는 경우
memcpy(m_pData + m_nTail, pSrc, sizeof(T) * nSize);
}
}
m_nTail += nSize;
m_nTail %= m_nSize;
m_nLength += nSize;
UnlockQueue();
return TRUE;
}
//=============================================================================================================================
/**
@remarks
큐에서 데이터를 꺼낸다.
@param pTar
큐에서 꺼낸 데이터를 복사할 버퍼의 포인터
@param nSize
큐에서 꺼낼 데이터 길이
@retval BOOL
정상적으로 데이터를 꺼내면 TRUE를 리턴한다.
꺼낼 데이터가 nSize보다 적은 경우 FALSE를 리턴한다.
*/
//=============================================================================================================================
BOOL Dequeue(T* pTar, int nSize)
{
LockQueue();
// Peek과 로직 동일
if(m_nLength < nSize)
{
UnlockQueue();
return FALSE;
}
// 내용을 받을 포인터가 NULL이 아닌 경우에만 메모리를 복사한다.
if(pTar != NULL)
{
if(m_nHead < m_nTail)
{
// 1. head가 tail보다 앞에 있는 경우
memcpy(pTar, m_pData + m_nHead, sizeof(T) * nSize);
}
else
{
// 2. head가 tail보다 뒤에 있는 경우
int backDataCount = __GetBackDataCount();
if(backDataCount >= nSize)
{
// 뒤쪽에 남은 데이터가 꺼내갈 양보다 같거나 큰 경우 nSize만큼 한번에 복사한다.
memcpy(pTar, m_pData + m_nHead, sizeof(T) * nSize);
}
else
{
// 뒤쪽에 남은 데이터가 꺼내갈 양보다 작은 경우 두번에 나누어 복사한다.
memcpy(pTar, m_pData + m_nHead, sizeof(T) * backDataCount);
memcpy(pTar + backDataCount, m_pData, sizeof(T) * (nSize - backDataCount));
}
}
}
//////////////////////////////////////////////////////////////////////////
m_nHead += nSize;
m_nHead %= m_nSize;
m_nLength -= nSize;
UnlockQueue();
return TRUE;
}
//=============================================================================================================================
/**
@remarks
큐에서 데이터를 꺼내지 않고 복사만 해온다.
@param pTar
큐의 데이터를 복사할 버퍼의 포인터
@param nSize
큐에서 복사할 데이터 길이
@retval BOOL
정상적으로 데이터를 복사하면 TRUE를 리턴한다.
꺼낼 데이터가 nSize보다 적은 경우 FALSE를 리턴한다.
*/
//=============================================================================================================================
BOOL Peek(T* pTar, int nSize)
{
LockQueue();
// Dequeue와 로직 동일
if(m_nLength < nSize)
{
UnlockQueue();
return FALSE;
}
// 내용을 받을 포인터가 NULL이 아닌 경우에만 메모리를 복사한다.
if(pTar != NULL)
{
if(m_nHead < m_nTail)
{
// 1. head가 tail보다 앞에 있는 경우
memcpy(pTar, m_pData + m_nHead, sizeof(T) * nSize);
}
else
{
// 2. head가 tail보다 뒤에 있는 경우
int backDataCount = __GetBackDataCount();
if(backDataCount >= nSize)
{
// 뒤쪽에 남은 데이터가 꺼내갈 양보다 같거나 큰 경우 nSize만큼 한번에 복사한다.
memcpy(pTar, m_pData + m_nHead, sizeof(T) * nSize);
}
else
{
// 뒤쪽에 남은 데이터가 꺼내갈 양보다 작은 경우 두번에 나누어 복사한다.
memcpy(pTar, m_pData + m_nHead, sizeof(T) * backDataCount);
memcpy(pTar + backDataCount, m_pData, sizeof(T) * (nSize - backDataCount));
}
}
}
UnlockQueue();
return TRUE;
}
//=============================================================================================================================
/**
@remarks
패킷이 버퍼 뒤쪽에서 앞쪽으로 나누어질 때 패킷이 이어지도록 여분의 공간에 카피한다.
@param nSize
뒤쪽에서 잘려서 앞쪽으로 이어진 데이터의 길이
*/
//=============================================================================================================================
inline void CopyHeadDataToExtraBuffer(int nSize)
{
assert(nSize <= m_nExtraSize);
LockQueue();
// 버퍼 맨 뒷쪽 여분 버퍼에다가 버퍼 맨 앞쪽에 있는 데이터를 쓴다.
memcpy(m_pData + m_nSize, m_pData, nSize);
UnlockQueue();
}
BOOL CopyLocalToSecondBuffer(CircuitQueue<T>* pThreadUnsafeDoubleBuffer, int* pRemainedRatio)
{
if(this == pThreadUnsafeDoubleBuffer)
return FALSE;
// assert(pThreadUnsafeDoubleBuffer->(m_nSize - m_nLength) >= m_nLength)
pThreadUnsafeDoubleBuffer->Clear();
LockQueue();
*pRemainedRatio = int((m_nSize - m_nLength) * 100 / m_nSize);
int readableLength = int(sizeof(T) * m_nLength);
if(readableLength)
{
if(m_nHead < m_nTail)
{
// 1. head가 tail보다 앞에 있는 경우
CopyMemory(pThreadUnsafeDoubleBuffer->m_pData, m_pData + m_nHead, readableLength);
pThreadUnsafeDoubleBuffer->m_nLength = readableLength;
}
else
{
// 2. head가 tail보다 뒤에 있는 경우
int backDataCount = __GetBackDataCount();
CopyMemory(pThreadUnsafeDoubleBuffer->m_pData, m_pData + m_nHead, backDataCount);
if(readableLength > backDataCount)
{
CopyMemory(pThreadUnsafeDoubleBuffer->m_pData + backDataCount,
m_pData,
readableLength - backDataCount);
}
pThreadUnsafeDoubleBuffer->m_nLength = readableLength;
}
}
UnlockQueue();
return TRUE;
}
// (WAVERIX) (BUG-FIX) 예기치 않은 락 해제 상태 방지하기 위한 작업 + 코드 중복 감소
#undef __GetSpace
#undef __GetLength
#undef __GetBackDataCount
#undef __GetReadPtr
#undef __GetWritePtr
protected:
CRITICAL_SECTION m_cs;
BOOLEAN m_bNeedLock; // 해당 CircuitQueue에 Lock을 걸 필요가 있는가?
T* m_pData; /// 큐 버퍼 포인터
int m_nLength; /// 현재 데이터 길이
int m_nSize; /// 큐 버퍼의 전체 크기
int m_nHead; /// 현재 데이터가 시작되는 곳의 위치
int m_nTail; /// 현재 데이터가 써질 곳의 위치
int m_nExtraSize; /// 큐 뒤쪽에 여분으로 잡을 버퍼 크기
};
#else //!__NA000000_090415_DOUBLE_BUFFERING_FOR_SAFELY_CONNECTION__
template<typename T>
class CircuitQueue
{
public:
CircuitQueue()
: m_pData( NULL ), m_nLength( 0 ), m_nSize( 0 ), m_nHead( 0 ), m_nTail( 0 )
{
InitializeCriticalSection( &m_cs );
}
virtual ~CircuitQueue()
{
if( m_pData ) delete [] m_pData;
DeleteCriticalSection( &m_cs );
}
//=============================================================================================================================
/**
@remarks
큐를 생성한다.
@param nSize
큐 사이즈
@param nExtraSize
패킷이 버퍼 뒤쪽에서 앞쪽으로 나누어 들어갈 때 패킷을 이어주기 위해 확보할
버퍼 뒤쪽 여분의 메모리 크기(실제 여분 메모리 = sizeof(T) * nExtraSize )
*/
//=============================================================================================================================
void Create( int nSize, int nExtraSize = 0 )
{
EnterCriticalSection( &m_cs );
if( m_pData ) delete [] m_pData;
m_pData = new T[nSize + nExtraSize];
m_nSize = nSize;
m_nExtraSize = nExtraSize;
LeaveCriticalSection( &m_cs );
}
//=============================================================================================================================
/**
@remarks
큐를 청소한다.
*/
//=============================================================================================================================
inline void Clear()
{
EnterCriticalSection( &m_cs );
m_nLength = 0;
m_nHead = 0;
m_nTail = 0;
LeaveCriticalSection( &m_cs );
}
/// 큐의 남은 공간을 리턴한다.
inline int GetSpace()
{
int iRet;
EnterCriticalSection( &m_cs );
iRet = m_nSize - m_nLength;
LeaveCriticalSection( &m_cs );
return iRet;
}
/// 현재 사용중인 큐의 크기를 리턴한다.
inline int GetLength()
{
int iRet;
EnterCriticalSection( &m_cs );
iRet = m_nLength;
LeaveCriticalSection( &m_cs );
return iRet;
}
/// 데이터가 큐 뒤쪽으로 꽉 차서 앞으로 이어지는 경우, 뒤쪽 끝까지의 데이터 개수를 리턴한다.
inline int GetBackDataCount()
{
int iRet;
EnterCriticalSection( &m_cs );
iRet = m_nSize - m_nHead;
LeaveCriticalSection( &m_cs );
return iRet;
}
/// 데이터를 읽을 포인터(Head)를 리턴한다.
inline T* GetReadPtr()
{
T *pRet;
EnterCriticalSection( &m_cs );
pRet = m_pData + m_nHead;
LeaveCriticalSection( &m_cs );
return pRet;
}
/// 데이터를 쓸 포인터(Tail)를 리턴한다.
inline T* GetWritePtr()
{
T *pRet;
EnterCriticalSection( &m_cs );
pRet = m_pData + m_nTail;
LeaveCriticalSection( &m_cs );
return pRet;
}
/// 잘리지 않고 한번에 읽을 수 있는 길이를 리턴한다.
inline int GetReadableLen()
{
int iRet;
EnterCriticalSection( &m_cs );
if( m_nHead == m_nTail ) iRet = GetLength() > 0 ? m_nSize - m_nHead: 0;
else if( m_nHead < m_nTail ) iRet = m_nTail - m_nHead;
else iRet = m_nSize - m_nHead;
LeaveCriticalSection( &m_cs );
return iRet;
}
/// 잘리지 않고 한번에 쓰기 가능한 길이를 리턴한다.
inline int GetWritableLen()
{
int iRet;
EnterCriticalSection( &m_cs );
if( m_nHead == m_nTail ) iRet = GetLength() > 0 ? 0 : m_nSize - m_nTail;
else if( m_nHead < m_nTail ) iRet = m_nSize - m_nTail;
else iRet = m_nHead - m_nTail;
LeaveCriticalSection( &m_cs );
return iRet;
}
//=============================================================================================================================
/**
@remarks
큐에 데이터를 넣는다.
@param pSrc
큐에 삽입할 데이터 포인터
@param nSize
큐에 삽입할 데이터의 길이
@retval BOOL
정상적으로 데이터 삽입이 끝나면 TRUE를 리턴한다.
큐가 꽉차서 삽입할 수 없는 경우 FALSE를 리턴한다.
*/
//=============================================================================================================================
inline BOOL Enqueue( T *pSrc, int nSize )
{
EnterCriticalSection( &m_cs );
if( GetSpace() < nSize )
{
LeaveCriticalSection( &m_cs );
return FALSE;
}
// pData가 NULL인 경우에는 데이터 큐잉 없이 tail과 length만 갱신한다.
if( pSrc != NULL )
{
if( m_nHead <= m_nTail )
{
// 1. head가 tail보다 앞에 있는 경우
int nBackSpaceCount = m_nSize - m_nTail;
if( nBackSpaceCount >= nSize )
{
// 뒤쪽에 남은 공간이 복사할 양보다 같거나 큰 경우 한번에 복사한다.
memcpy( m_pData + m_nTail, pSrc, sizeof(T) * nSize );
}
else
{
// 뒤쪽에 남은 공간이 복사할 양보다 작은 경우 두번에 나누어 복사한다.
memcpy( m_pData + m_nTail, pSrc, sizeof(T) * nBackSpaceCount );
memcpy( m_pData, pSrc + nBackSpaceCount, sizeof(T) * ( nSize - nBackSpaceCount ) );
}
}
else
{
// 2. head가 tail보다 뒤에 있는 경우
memcpy( m_pData + m_nTail, pSrc, sizeof(T) * nSize );
}
}
m_nTail += nSize;
m_nTail %= m_nSize;
m_nLength += nSize;
LeaveCriticalSection( &m_cs );
return TRUE;
}
//=============================================================================================================================
/**
@remarks
큐에서 데이터를 꺼낸다.
@param pTar
큐에서 꺼낸 데이터를 복사할 버퍼의 포인터
@param nSize
큐에서 꺼낼 데이터 길이
@retval BOOL
정상적으로 데이터를 꺼내면 TRUE를 리턴한다.
꺼낼 데이터가 nSize보다 적은 경우 FALSE를 리턴한다.
*/
//=============================================================================================================================
inline BOOL Dequeue( T *pTar, int nSize )
{
EnterCriticalSection( &m_cs );
if( !Peek( pTar, nSize ) )
{
LeaveCriticalSection( &m_cs );
return FALSE;
}
m_nHead += nSize;
m_nHead %= m_nSize;
m_nLength -= nSize;
LeaveCriticalSection( &m_cs );
return TRUE;
}
//=============================================================================================================================
/**
@remarks
큐에서 데이터를 꺼내지 않고 복사만 해온다.
@param pTar
큐의 데이터를 복사할 버퍼의 포인터
@param nSize
큐에서 복사할 데이터 길이
@retval BOOL
정상적으로 데이터를 복사하면 TRUE를 리턴한다.
꺼낼 데이터가 nSize보다 적은 경우 FALSE를 리턴한다.
*/
//=============================================================================================================================
inline BOOL Peek( T *pTar, int nSize )
{
EnterCriticalSection( &m_cs );
if( m_nLength < nSize )
{
LeaveCriticalSection( &m_cs );
return FALSE;
}
// 내용을 받을 포인터가 NULL이 아닌 경우에만 메모리를 복사한다.
if( pTar != NULL )
{
if( m_nHead < m_nTail )
{
// 1. head가 tail보다 앞에 있는 경우
memcpy( pTar, m_pData + m_nHead, sizeof(T) * nSize );
}
else
{
// 2. head가 tail보다 뒤에 있는 경우
if( GetBackDataCount() >= nSize )
{
// 뒤쪽에 남은 데이터가 꺼내갈 양보다 같거나 큰 경우 nSize만큼 한번에 복사한다.
memcpy( pTar, m_pData + m_nHead, sizeof(T) * nSize );
}
else
{
// 뒤쪽에 남은 데이터가 꺼내갈 양보다 작은 경우 두번에 나누어 복사한다.
memcpy( pTar, m_pData + m_nHead, sizeof(T) * GetBackDataCount() );
memcpy( pTar + GetBackDataCount(), m_pData, sizeof(T) * ( nSize - GetBackDataCount() ) );
}
}
}
LeaveCriticalSection( &m_cs );
return TRUE;
}
//=============================================================================================================================
/**
@remarks
패킷이 버퍼 뒤쪽에서 앞쪽으로 나누어질 때 패킷이 이어지도록 여분의 공간에 카피한다.
@param nSize
뒤쪽에서 잘려서 앞쪽으로 이어진 데이터의 길이
*/
//=============================================================================================================================
inline void CopyHeadDataToExtraBuffer( int nSize )
{
assert( nSize <= m_nExtraSize );
EnterCriticalSection( &m_cs );
// 버퍼 맨 뒷쪽 여분 버퍼에다가 버퍼 맨 앞쪽에 있는 데이터를 쓴다.
memcpy( m_pData + m_nSize, m_pData, nSize );
LeaveCriticalSection( &m_cs );
}
#ifdef __NA000000_090415_DOUBLE_BUFFERING_FOR_SAFELY_CONNECTION__
BOOL CopyLocalToSecondBuffer(CircuitQueue<T>* pThreadUnsafeDoubleBuffer, int* pRemainedRatio)
{
if(this == pThreadUnsafeDoubleBuffer)
return FALSE;
// assert(pThreadUnsafeDoubleBuffer->(m_nSize - m_nLength) >= m_nLength)
pThreadUnsafeDoubleBuffer->Clear();
EnterCriticalSection(&m_cs);
*pRemainedRatio = int((m_nSize - m_nLength) * 100 / m_nSize);
int readableLength = int(sizeof(T) * m_nLength);
if(readableLength)
{
if(m_nHead < m_nTail)
{
// 1. head가 tail보다 앞에 있는 경우
CopyMemory(pThreadUnsafeDoubleBuffer->m_pData, m_pData + m_nHead, readableLength);
pThreadUnsafeDoubleBuffer->m_nLength = readableLength;
}
else
{
// 2. head가 tail보다 뒤에 있는 경우
int backDataCount = m_nSize - m_nHead;
CopyMemory(pThreadUnsafeDoubleBuffer->m_pData, m_pData + m_nHead, backDataCount);
if(readableLength > backDataCount)
{
CopyMemory(pThreadUnsafeDoubleBuffer->m_pData + backDataCount,
m_pData,
readableLength - backDataCount);
}
pThreadUnsafeDoubleBuffer->m_nLength = readableLength;
}
}
LeaveCriticalSection(&m_cs);
return TRUE;
}
#endif //__NA000000_090415_DOUBLE_BUFFERING_FOR_SAFELY_CONNECTION__
protected:
CRITICAL_SECTION m_cs;
T *m_pData; /// 큐 버퍼 포인터
int m_nLength; /// 현재 데이터 길이
int m_nSize; /// 큐 버퍼의 전체 크기
int m_nHead; /// 현재 데이터가 시작되는 곳의 위치
int m_nTail; /// 현재 데이터가 써질 곳의 위치
int m_nExtraSize; /// 큐 뒤쪽에 여분으로 잡을 버퍼 크기
};
#endif //!__NA000000_090415_DOUBLE_BUFFERING_FOR_SAFELY_CONNECTION__
#endif //_CIRCUITQUEUE_H_