lithtech/engine/clientfx/shared/bankedlist_impl.h

771 lines
16 KiB
C++

//////////////////////////////////////////////////////////////////////////////
// Banked List implementation
#include "bankedlist.h"
#include <stdio.h>
// A page of items
// (Internal class for internal use only..)
template <typename T>
class CBankedListBank
{
public:
CBankedListBank();
private:
typedef CBankedList<T> t_Parent;
typedef CBankedListBank<T> t_This;
typedef T t_Type;
#ifdef __PSX2
friend class t_Parent;
friend class t_Parent::t_Iter;
#else
friend t_Parent;
friend t_Parent::t_Iter;
#endif
// Is this bank currently full?
LTBOOL IsFull();
// Is this bank currently empty?
LTBOOL IsEmpty();
// Is this bank currently packed? (i.e. are the used items in contiguous usage?)
LTBOOL IsPacked();
// Is this index in use?
LTBOOL IsUsed(uint32 nIndex);
// Get the index of pPtr (returns k_SizeOfBank if it isn't in this bank)
uint32 GetIndexAtPtr(t_Type *pPtr);
// Get object nIndex
t_Type &GetRefAtIndex(uint32 nIndex);
// How many items are currently in use?
uint32 GetSize();
// What's your minimum in-use index? (returns k_SizeOfBank if empty)
uint32 GetFirstUsed();
// What's your maximum in-use index? (returns k_SizeOfBank if empty)
uint32 GetLastUsed();
// What's your minimum available index? (returns k_SizeOfBank if full)
uint32 GetFirstOpen();
// What's your maximum in-use index? (returns k_SizeOfBank if full)
uint32 GetLastOpen();
// Remove the item at index nIndex
void Remove(uint32 nIndex);
// Allocate an item
T *New(typename t_Parent::t_NewOp eOp);
// Read-only bank pointer access
t_This *GetNext();
t_This *GetPrev();
// Remove this bank from the list
void RemoveList();
// Insert this bank into the list after pTail
void AppendList(t_This *pTail);
// Insert this bank into the list before pHead
void InsertList(t_This *pHead);
private: // Intended for internal access only
enum { k_DataPitch = ((sizeof(t_Type) + (sizeof(int) - 1)) / sizeof(int)) };
enum { k_SizeOfData = t_Parent::k_SizeOfBank * k_DataPitch };
// The usage flags for this bank
uint32 m_nUsage;
// Pointer to the next/prev banks in the list
t_This *m_pNextBank, *m_pPrevBank;
// The actual data (Note : This is an int array to avoid the ctor/dtor calls of the
// actual type. Also, this conveniently provides word alignment to the allocations..)
int m_aData[k_SizeOfData];
};
//////////////////////////////////////////////////////////////////////////////
// CBankedList<T>::t_Iter implementation
template <typename T>
CBankedListIter<T>::CBankedListIter() : m_pCurBank(LTNULL), m_nIndex(0)
{
}
template <typename T>
CBankedListIter<T>::CBankedListIter(const t_This &iOther) : m_pCurBank(iOther.m_pCurBank), m_nIndex(iOther.m_nIndex)
{
}
template <typename T>
CBankedListIter<T>::CBankedListIter(t_Bank *pBank, uint32 nIndex) : m_pCurBank(pBank), m_nIndex(nIndex)
{
}
template <typename T>
LTBOOL CBankedListIter<T>::IsValid()
{
// Invalid bank? Not valid..
if (!m_pCurBank)
return LTFALSE;
// Invalid index? Not valid..
if (m_nIndex >= t_Parent::k_SizeOfBank)
return LTFALSE;
// You're valid, as long as the bank says you're in use
return m_pCurBank->IsUsed(m_nIndex);
}
template <typename T>
T &CBankedListIter<T>::DeRef()
{
// Not pointing at anything? Uh... Make something up...
if (!m_pCurBank)
{
return *((T*)LTNULL);
}
return m_pCurBank->GetRefAtIndex(m_nIndex);
}
template <typename T>
void CBankedListIter<T>::Next()
{
if (!m_pCurBank)
return;
do
{
if (m_nIndex < t_Parent::k_SizeOfBank)
++m_nIndex;
else
{
m_pCurBank = m_pCurBank->GetNext();
m_nIndex = 0;
}
} while (m_pCurBank && (!m_pCurBank->IsUsed(m_nIndex)));
}
template <typename T>
void CBankedListIter<T>::Prev()
{
if (!m_pCurBank)
return;
do
{
if (m_nIndex > 0)
--m_nIndex;
else
{
m_pCurBank = m_pCurBank->GetPrev();
if (m_pCurBank)
m_nIndex = m_pCurBank->GetLastUsed();
}
} while (m_pCurBank && (!m_pCurBank->IsUsed(m_nIndex)));
}
template <typename T>
void CBankedListIter<T>::Copy(const t_This &iOther)
{
m_pCurBank = iOther.m_pCurBank;
m_nIndex = iOther.m_nIndex;
}
template <typename T>
CBankedListBank<T> *CBankedListIter<T>::GetBank()
{
return m_pCurBank;
}
template <typename T>
uint32 CBankedListIter<T>::GetIndex()
{
return m_nIndex;
}
//////////////////////////////////////////////////////////////////////////////
// CBankedList<T>::t_Bank implementation
template <typename T>
CBankedListBank<T>::CBankedListBank() : m_nUsage(0), m_pNextBank(LTNULL), m_pPrevBank(LTNULL)
{
}
template <typename T>
LTBOOL CBankedListBank<T>::IsFull()
{
return m_nUsage == 0xFFFFFFFF;
}
template <typename T>
LTBOOL CBankedListBank<T>::IsEmpty()
{
return m_nUsage == 0;
}
template <typename T>
LTBOOL CBankedListBank<T>::IsPacked()
{
// Returns TRUE if the "used" bits are contiguous
return (m_nUsage & (m_nUsage + 1)) == 0;
}
template <typename T>
LTBOOL CBankedListBank<T>::IsUsed(uint32 nIndex)
{
return (m_nUsage & (1 << nIndex)) != 0;
}
template <typename T>
uint32 CBankedListBank<T>::GetIndexAtPtr(T *pPtr)
{
if (((int*)pPtr >= m_aData) && ((int*)pPtr < &m_aData[k_SizeOfData]))
return ((int*)pPtr - m_aData) / k_DataPitch;
else
return t_Parent::k_SizeOfBank;
}
template <typename T>
T &CBankedListBank<T>::GetRefAtIndex(uint32 nIndex)
{
// Note : It is guaranteed that this won't be called with nIndex > k_SizeOfBank
// due to the iterator being the only one to call this function
return *((T*)(&m_aData[nIndex * k_DataPitch]));
}
// How many items are currently in use?
template <typename T>
uint32 CBankedListBank<T>::GetSize()
{
// This is a keen little trick I found in a Dr. Dobbs for counting bits without any branches..
uint32 nTemp;
nTemp = ((m_nUsage & 0xAAAAAAAA) >> 1) + (m_nUsage & 0x55555555);
nTemp = ((nTemp & 0xCCCCCCCC) >> 2) + (nTemp & 0x33333333);
nTemp = ((nTemp & 0xF0F0F0F0) >> 4) + (nTemp & 0x0F0F0F0F);
nTemp = ((nTemp & 0xFF00FF00) >> 8) + (nTemp & 0x00FF00FF);
return ((nTemp & 0xFFFF0000) >> 16) + (nTemp & 0x0000FFFF);
}
template <typename T>
uint32 CBankedListBank<T>::GetFirstUsed()
{
// Note : I'm sure there's a more clever way of doing this...
uint32 nIndex = 0;
uint32 nMask = 1;
while (nIndex < t_Parent::k_SizeOfBank)
{
if ((m_nUsage & nMask) != 0)
return nIndex;
++nIndex;
nMask = nMask << 1;
}
// If it got here, it's empty
return nIndex;
}
template <typename T>
uint32 CBankedListBank<T>::GetLastUsed()
{
// Note : I'm sure there's a more clever way of doing this...
uint32 nIndex = t_Parent::k_SizeOfBank - 1;
uint32 nMask = 1 << nIndex;
while (nIndex > 0)
{
if ((m_nUsage & nMask) != 0)
return nIndex;
--nIndex;
nMask = nMask >> 1;
}
// If it got here, it's either 0 or empty
return (m_nUsage == 1) ? 0 : t_Parent::k_SizeOfBank;
}
template <typename T>
uint32 CBankedListBank<T>::GetFirstOpen()
{
// Note : I'm sure there's a more clever way of doing this...
uint32 nIndex = 0;
uint32 nMask = 1;
while (nIndex < t_Parent::k_SizeOfBank)
{
if ((m_nUsage & nMask) == 0)
return nIndex;
++nIndex;
nMask = nMask << 1;
}
// If it got here, it's full
return nIndex;
}
template <typename T>
uint32 CBankedListBank<T>::GetLastOpen()
{
// Note : I'm sure there's a more clever way of doing this...
uint32 nIndex = t_Parent::k_SizeOfBank - 1;
uint32 nMask = 1 << nIndex;
while (nIndex > 0)
{
if ((m_nUsage & nMask) == 0)
return nIndex;
--nIndex;
nMask = nMask >> 1;
}
// If it got here, it's either 0 or empty
return (m_nUsage == 1) ? 0 : t_Parent::k_SizeOfBank;
}
template <typename T>
void CBankedListBank<T>::Remove(uint32 nIndex)
{
m_nUsage &= ~(1 << nIndex);
}
template <typename T>
T* CBankedListBank<T>::New(typename t_Parent::t_NewOp eOp)
{
// Get our index
uint32 nIndex = t_Parent::k_SizeOfBank;
if (eOp == t_Parent::eNewOp_Pack)
{
// Fail if we're full
if (IsFull())
return LTNULL;
nIndex = GetFirstOpen();
}
else if (eOp == t_Parent::eNewOp_Append)
{
// Fail if we can't append..
if (IsUsed(t_Parent::k_SizeOfBank - 1) || (GetNext() != LTNULL))
return LTNULL;
if (IsEmpty())
nIndex = 0;
else
nIndex = GetLastUsed() + 1;
}
// Mark it as used
m_nUsage |= (1 << nIndex);
// Return the pointer..
return &GetRefAtIndex(nIndex);
}
template <typename T>
CBankedListBank<T> *CBankedListBank<T>::GetNext()
{
return m_pNextBank;
}
template <typename T>
CBankedListBank<T> *CBankedListBank<T>::GetPrev()
{
return m_pPrevBank;
}
template <typename T>
void CBankedListBank<T>::RemoveList()
{
if (m_pPrevBank)
m_pPrevBank->m_pNextBank = m_pNextBank;
if (m_pNextBank)
m_pNextBank->m_pPrevBank = m_pPrevBank;
}
template <typename T>
void CBankedListBank<T>::AppendList(t_This *pTail)
{
if (pTail)
{
if (pTail->m_pNextBank)
pTail->m_pNextBank->m_pPrevBank = this;
m_pNextBank = pTail->m_pNextBank;
m_pPrevBank = pTail;
pTail->m_pNextBank = this;
}
else
{
m_pNextBank = LTNULL;
m_pPrevBank = LTNULL;
}
}
template <typename T>
void CBankedListBank<T>::InsertList(t_This *pHead)
{
if (pHead)
{
if (pHead->m_pPrevBank)
pHead->m_pPrevBank->m_pNextBank = this;
m_pNextBank = pHead;
m_pPrevBank = pHead->m_pPrevBank;
pHead->m_pPrevBank = this;
}
else
{
m_pNextBank = LTNULL;
m_pPrevBank = LTNULL;
}
}
//////////////////////////////////////////////////////////////////////////////
// CBankedList<T> implementation
template <typename T>
CBankedList<T>::CBankedList() : m_pBankHead(LTNULL), m_pBankTail(LTNULL), m_pEmptyHead(LTNULL), m_nSize(0)
{
}
template <typename T>
CBankedList<T>::~CBankedList()
{
Term();
}
template <typename T>
void CBankedList<T>::Init()
{
Term();
}
template <typename T>
void CBankedList<T>::Term()
{
if (m_pEmptyHead)
{
DeleteBankList(m_pEmptyHead);
m_pEmptyHead = LTNULL;
}
if (m_pBankHead)
{
DeleteBankList(m_pBankHead);
m_pBankHead = LTNULL;
}
m_pBankTail = LTNULL;
m_nSize = 0;
}
template <typename T>
typename CBankedList<T>::t_Iter CBankedList<T>::GetHead()
{
return t_Iter(m_pBankHead, (m_pBankHead) ? m_pBankHead->GetFirstUsed() : 0);
}
template <typename T>
typename CBankedList<T>::t_Iter CBankedList<T>::GetTail()
{
return t_Iter(m_pBankTail, (m_pBankTail) ? m_pBankTail->GetLastUsed() : 0);
}
template <typename T>
typename CBankedList<T>::t_Iter CBankedList<T>::Remove(const t_Iter &iPos)
{
// Make sure we've got a valid position
if (!iPos.IsValid())
return iPos;
// Set up the return value
t_Iter iRet(iPos);
// Get the item we're pointing at
t_Bank *pBank = iRet.GetBank();
uint32 nIndex = iRet.GetIndex();
// Move to the next item
++iRet;
// Remove the item from the bank
pBank->Remove(nIndex);
// Remember that we removed one
--m_nSize;
// Remove the bank from the list if it's empty
if (pBank->IsEmpty())
MoveToEmptyList(pBank);
return iRet;
}
template <typename T>
typename CBankedList<T>::t_Iter CBankedList<T>::Find(T *pPtr)
{
// Start at the first bank
t_Bank *pFinger = m_pBankHead;
uint32 nIndex = 0;
while (pFinger)
{
// Try to find it in this bank
nIndex = pFinger->GetIndexAtPtr(pPtr);
// Jump out of the loop if we found it
if (nIndex < k_SizeOfBank)
break;
// Move to the next bank
pFinger = pFinger->GetNext();
}
// Return the result
return t_Iter(pFinger, nIndex);
}
template <typename T>
void CBankedList<T>::Remove(T *pPtr)
{
Remove(Find(pPtr));
}
template <typename T>
void CBankedList<T>::Append(const T &_Item)
{
new(this, eNewOp_Append) T(_Item);
}
template <typename T>
void CBankedList<T>::Delete(t_Type *pPtr, bool bCallDtor)
{
if (!pPtr)
return;
#ifdef __PSX2
// Call the dtor
if (bCallDtor)
delete pPtr;
// pPtr->~T();
else
free(pPtr);
#else
// Find the pointer in the list
t_Iter iPtr = Find(pPtr);
// Jump out if we didn't find it
if (!iPtr.IsValid())
{
ASSERT(!"CBankedList<T>::Delete was called on an unknown pointer");
return;
}
// Call the dtor
if (bCallDtor)
pPtr->~T();
// Delete it
iPtr.GetBank()->Remove(iPtr.GetIndex());
// Remember that we removed one
--m_nSize;
// Remove the bank if it's empty
if (iPtr.GetBank()->IsEmpty())
MoveToEmptyList(iPtr.GetBank());
// Report
/*
if (g_pLTClient)
{
g_pLTClient->CPrint("BankedList::Delete(%d) : %d", sizeof(t_Type), GetSize());
}
*/
#endif
}
template <typename T>
void *operator new(size_t, CBankedList<T> &, T* ptr) { return ptr; } // Internal ctor helper
template <typename T>
void operator delete(void*, CBankedList<T> &cBank, T* ptr) { cBank.Delete(ptr); }
template <typename T>
T *CBankedList<T>::New(t_NewOp eOp, bool bCallCtor)
{
#ifdef __PSX2
T *pNewItem;
pNewItem = new T;
#else
// Get our bank
t_Bank *pBank = LTNULL;
if (eOp == eNewOp_Pack)
{
// Find the first available bank
pBank = m_pBankHead;
while (pBank && pBank->IsFull())
pBank = pBank->GetNext();
}
else if (eOp == eNewOp_Append)
{
pBank = m_pBankTail;
// Get a new bank if this one's been used all the way..
if (pBank && pBank->IsUsed(k_SizeOfBank - 1))
pBank = LTNULL;
}
// Get a new bank if we need to..
if (!pBank)
{
pBank = GetNewBank();
}
// Fail if we just can't do it...
if (!pBank)
{
ASSERT(!"Unable to allocate a bank in CBankedList<T>::New");
return LTNULL;
}
// Get a new one from the bank
T *pNewItem = pBank->New(eOp);
// Remember that we added one...
if (pNewItem)
++m_nSize;
// Call the ctor
::new(*this, pNewItem) T;
// Report
/*
if (g_pLTClient)
{
g_pLTClient->CPrint("BankedList::New(%d) : %d", sizeof(t_Type), GetSize());
}
*/
#endif
return pNewItem;
}
template <typename T>
LTBOOL CBankedList<T>::IsEmpty()
{
return m_pBankHead == LTNULL;
}
template <typename T>
uint32 CBankedList<T>::GetSize()
{
return m_nSize;
}
template <typename T>
uint32 CBankedList<T>::GetFootprint()
{
// Start out with the size of the list...
uint32 nSize = sizeof(*this);
// Add in the active banks
t_Bank *pFinger = m_pBankHead;
while (pFinger)
{
nSize += sizeof(*pFinger);
pFinger = pFinger->GetNext();
}
// Add in the empty banks
t_Bank *pFinger = m_pEmptyHead;
while (pFinger)
{
nSize += sizeof(*pFinger);
pFinger = pFinger->GetNext();
}
}
template <typename T>
float CBankedList<T>::GetUsage()
{
// Count the number of slots used/total
uint32 nUsedSlots = 0;
uint32 nTotalSlots = 0;
// Only count the active banks
t_Bank *pFinger = m_pBankHead;
while (pFinger)
{
nTotalSlots += k_SizeOfBank;
nUsedSlots += pFinger->GetSize();
pFinger = pFinger->GetNext();
}
// Empty lists are considered entirely used
if (!nTotalSlots)
return 1.0f;
else
return (float)nUsedSlots / (float)nTotalSlots;
}
template <typename T>
void CBankedList<T>::DeleteBankList(t_Bank *pHead)
{
t_Bank *pFinger = pHead;
while (pFinger)
{
t_Bank *pDeleteMe = pFinger;
pFinger = pFinger->GetNext();
debug_delete(pDeleteMe);
}
}
template <typename T>
void CBankedList<T>::MoveToEmptyList(t_Bank *pBank)
{
// Update the head pointer
if (pBank == m_pBankHead)
m_pBankHead = m_pBankHead->GetNext();
// Update the tail pointer
if (pBank == m_pBankTail)
m_pBankTail = m_pBankTail->GetPrev();
// Remove the bank from the active list
pBank->RemoveList();
// Add it to the empty list
pBank->InsertList(m_pEmptyHead);
m_pEmptyHead = pBank;
}
template <typename T>
typename CBankedList<T>::t_Bank *CBankedList<T>::GetNewBank()
{
t_Bank *pResult;
// If we've got an empty list available...
if (m_pEmptyHead)
{
// Use the head of the empty list
pResult = m_pEmptyHead;
// Move the empty list forward
m_pEmptyHead = m_pEmptyHead->GetNext();
// Remove it from the empty list
pResult->RemoveList();
}
// Otherwise make a new one
else
{
pResult = debug_new(t_Bank);
}
// Add it to the end of the list
pResult->AppendList(m_pBankTail);
// Point the tail at it
m_pBankTail = pResult;
// Point the head at it if we're empty
if (!m_pBankHead)
m_pBankHead = pResult;
// Here ya go...
return pResult;
}
template <typename T>
LTBOOL CBankedList<T>::PreAlloc(uint32 nMinItems)
{
// Calculate how many banks we'd need..
uint32 nBanksNeeded = (nMinItems + k_SizeOfBank - 1) / k_SizeOfBank;
// Find out what we've currently got allocated
t_Bank *pFinger = m_pEmptyHead;
while (nBanksNeeded && pFinger)
{
--nBanksNeeded;
pFinger = pFinger->GetNext();
}
// Allocate new banks
while (nBanksNeeded)
{
t_Bank *pNewBank = debug_new(t_Bank);
// Add them to the empty list
pNewBank->InsertList(m_pEmptyHead);
m_pEmptyHead = pNewBank;
}
}
template <typename T>
void CBankedList<T>::Flush()
{
if (m_pEmptyHead)
{
DeleteBankList(m_pEmptyHead);
m_pEmptyHead = LTNULL;
}
}