Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

3154 lines
82 KiB

#ifndef _COLLECTION_HXX_INCLUDED
#define _COLLECTION_HXX_INCLUDED
// asserts
//
// #define COLLAssert to point to your favorite assert function per #include
#ifdef COLLAssert
#else // !COLLAssert
#define COLLAssert Assert
#endif // COLLAssert
#ifdef DHTAssert
#else // !DHTAssert
#define DHTAssert COLLAssert
#endif // DHTAssert
#include "dht.hxx"
#include <memory.h>
#include <minmax.h>
#pragma warning ( disable : 4786 ) // we allow huge symbol names
namespace COLL {
//////////////////////////////////////////////////////////////////////////////////////////
// CInvasiveList
//
// Implements an "invasive" doubly linked list of objects. The list is "invasive"
// because part of its state is embedded directly in the objects it contains. An
// additional property of this list class is that the head of the list can be relocated
// without updating the state of any of the contained objects.
//
// CObject = class representing objects in the list. each class must contain
// storage for a CElement for embedded list state
// OffsetOfILE = inline function returning the offset of the CElement contained
// in the CObject
typedef SIZE_T (*PfnOffsetOf)();
template< class CObject, PfnOffsetOf OffsetOfILE >
class CInvasiveList
{
public:
// invasive list element state (embedded in linked objects)
class CElement
{
public:
// ctor / dtor
CElement() : m_pilePrev( (CElement*)-1 ), m_pileNext( (CElement*)-1 ) {}
~CElement() {}
private:
CElement& operator=( CElement& ); // disallowed
friend class CInvasiveList< CObject, OffsetOfILE >;
CElement* m_pilePrev;
CElement* m_pileNext;
};
public:
// ctor / dtor
CInvasiveList();
~CInvasiveList();
// operators
CInvasiveList& operator=( const CInvasiveList& il );
// API
BOOL FEmpty() const;
BOOL FMember( CObject* const pobj ) const;
CObject* Prev( CObject* const pobj ) const;
CObject* Next( CObject* const pobj ) const;
CObject* PrevMost() const;
CObject* NextMost() const;
void InsertAsPrevMost( CObject* const pobj );
void InsertAsNextMost( CObject* const pobj );
void Remove( CObject* const pobj );
void Empty();
private:
// internal functions
CObject* _PobjFromPile( CElement* const pile ) const;
CElement* _PileFromPobj( CObject* const pobj ) const;
private:
CElement* m_pilePrevMost;
CElement* m_pileNextMost;
};
// ctor
template< class CObject, PfnOffsetOf OffsetOfILE >
inline CInvasiveList< CObject, OffsetOfILE >::
CInvasiveList()
{
// start with an empty list
Empty();
}
// dtor
template< class CObject, PfnOffsetOf OffsetOfILE >
inline CInvasiveList< CObject, OffsetOfILE >::
~CInvasiveList()
{
}
// assignment operator
template< class CObject, PfnOffsetOf OffsetOfILE >
inline CInvasiveList< CObject, OffsetOfILE >& CInvasiveList< CObject, OffsetOfILE >::
operator=( const CInvasiveList& il )
{
m_pilePrevMost = il.m_pilePrevMost;
m_pileNextMost = il.m_pileNextMost;
return *this;
}
// returns fTrue if the list is empty
template< class CObject, PfnOffsetOf OffsetOfILE >
inline BOOL CInvasiveList< CObject, OffsetOfILE >::
FEmpty() const
{
return m_pilePrevMost == _PileFromPobj( NULL );
}
// returns fTrue if the specified object is a member of this list
//
// NOTE: this function currently returns fTrue if the specified object is a
// member of any list!
template< class CObject, PfnOffsetOf OffsetOfILE >
inline BOOL CInvasiveList< CObject, OffsetOfILE >::
FMember( CObject* const pobj ) const
{
#ifdef EXPENSIVE_DEBUG
for ( CObject* pobjT = PrevMost(); pobjT && pobjT != pobj; pobjT = Next( pobjT ) )
{
}
return pobjT == pobj;
#else // !DEBUG
CElement* const pile = _PileFromPobj( pobj );
COLLAssert( ( ( DWORD_PTR( pile->m_pilePrev ) + DWORD_PTR( pile->m_pileNext ) ) == -2 ) ==
( pile->m_pilePrev == (CElement*)-1 && pile->m_pileNext == (CElement*)-1 ) );
return ( DWORD_PTR( pile->m_pilePrev ) + DWORD_PTR( pile->m_pileNext ) ) != -2;
#endif // DEBUG
}
// returns the prev object to the given object in the list
template< class CObject, PfnOffsetOf OffsetOfILE >
inline CObject* CInvasiveList< CObject, OffsetOfILE >::
Prev( CObject* const pobj ) const
{
return _PobjFromPile( _PileFromPobj( pobj )->m_pilePrev );
}
// returns the next object to the given object in the list
template< class CObject, PfnOffsetOf OffsetOfILE >
inline CObject* CInvasiveList< CObject, OffsetOfILE >::
Next( CObject* const pobj ) const
{
return _PobjFromPile( _PileFromPobj( pobj )->m_pileNext );
}
// returns the prev-most object to the given object in the list
template< class CObject, PfnOffsetOf OffsetOfILE >
inline CObject* CInvasiveList< CObject, OffsetOfILE >::
PrevMost() const
{
return _PobjFromPile( m_pilePrevMost );
}
// returns the next-most object to the given object in the list
template< class CObject, PfnOffsetOf OffsetOfILE >
inline CObject* CInvasiveList< CObject, OffsetOfILE >::
NextMost() const
{
return _PobjFromPile( m_pileNextMost );
}
// inserts the given object as the prev-most object in the list
template< class CObject, PfnOffsetOf OffsetOfILE >
inline void CInvasiveList< CObject, OffsetOfILE >::
InsertAsPrevMost( CObject* const pobj )
{
CElement* const pile = _PileFromPobj( pobj );
// this object had better not already be in the list
COLLAssert( !FMember( pobj ) );
// this object had better not already be in any list
COLLAssert( pile->m_pilePrev == (CElement*)-1 );
COLLAssert( pile->m_pileNext == (CElement*)-1 );
// the list is empty
if ( m_pilePrevMost == _PileFromPobj( NULL ) )
{
// insert this element as the only element in the list
pile->m_pilePrev = _PileFromPobj( NULL );
pile->m_pileNext = _PileFromPobj( NULL );
m_pilePrevMost = pile;
m_pileNextMost = pile;
}
// the list is not empty
else
{
// insert this element at the prev-most position in the list
pile->m_pilePrev = _PileFromPobj( NULL );
pile->m_pileNext = m_pilePrevMost;
m_pilePrevMost->m_pilePrev = pile;
m_pilePrevMost = pile;
}
}
// inserts the given object as the next-most object in the list
template< class CObject, PfnOffsetOf OffsetOfILE >
inline void CInvasiveList< CObject, OffsetOfILE >::
InsertAsNextMost( CObject* const pobj )
{
CElement* const pile = _PileFromPobj( pobj );
// this object had better not already be in the list
COLLAssert( !FMember( pobj ) );
// this object had better not already be in any list
COLLAssert( pile->m_pilePrev == (CElement*)-1 );
COLLAssert( pile->m_pileNext == (CElement*)-1 );
// the list is empty
if ( m_pileNextMost == _PileFromPobj( NULL ) )
{
// insert this element as the only element in the list
pile->m_pilePrev = _PileFromPobj( NULL );
pile->m_pileNext = _PileFromPobj( NULL );
m_pilePrevMost = pile;
m_pileNextMost = pile;
}
// the list is not empty
else
{
// insert this element at the next-most position in the list
pile->m_pilePrev = m_pileNextMost;
pile->m_pileNext = _PileFromPobj( NULL );
m_pileNextMost->m_pileNext = pile;
m_pileNextMost = pile;
}
}
// removes the given object from the list
template< class CObject, PfnOffsetOf OffsetOfILE >
inline void CInvasiveList< CObject, OffsetOfILE >::
Remove( CObject* const pobj )
{
CElement* const pile = _PileFromPobj( pobj );
// this object had better already be in the list
COLLAssert( FMember( pobj ) );
// there is an element after us in the list
if ( pile->m_pileNext != _PileFromPobj( NULL ) )
{
// fix up its prev element to be our prev element (if any)
pile->m_pileNext->m_pilePrev = pile->m_pilePrev;
}
else
{
// set the next-most element to be our prev element (if any)
m_pileNextMost = pile->m_pilePrev;
}
// there is an element before us in the list
if ( pile->m_pilePrev != _PileFromPobj( NULL ) )
{
// fix up its next element to be our next element (if any)
pile->m_pilePrev->m_pileNext = pile->m_pileNext;
}
else
{
// set the prev-most element to be our next element (if any)
m_pilePrevMost = pile->m_pileNext;
}
// mark ourself as not in any list
pile->m_pilePrev = (CElement*)-1;
pile->m_pileNext = (CElement*)-1;
}
// resets the list to the empty state
template< class CObject, PfnOffsetOf OffsetOfILE >
inline void CInvasiveList< CObject, OffsetOfILE >::
Empty()
{
m_pilePrevMost = _PileFromPobj( NULL );
m_pileNextMost = _PileFromPobj( NULL );
}
// converts a pointer to an ILE to a pointer to the object
template< class CObject, PfnOffsetOf OffsetOfILE >
inline CObject* CInvasiveList< CObject, OffsetOfILE >::
_PobjFromPile( CElement* const pile ) const
{
return (CObject*)( (BYTE*)pile - OffsetOfILE() );
}
// converts a pointer to an object to a pointer to the ILE
template< class CObject, PfnOffsetOf OffsetOfILE >
inline CInvasiveList< CObject, OffsetOfILE >::CElement* CInvasiveList< CObject, OffsetOfILE >::
_PileFromPobj( CObject* const pobj ) const
{
return (CElement*)( (BYTE*)pobj + OffsetOfILE() );
}
//////////////////////////////////////////////////////////////////////////////////////////
// CApproximateIndex
//
// Implements a dynamically resizable table of entries indexed approximately by key
// ranges of a specified uncertainty. Accuracy and exact ordering are sacrificied for
// improved performance and concurrency. This index is optimized for a set of records
// whose keys occupy a fairly dense range of values. The index is designed to handle
// key ranges that can wrap around zero. As such, the indexed key range can not span
// more than half the numerical precision of the key.
//
// CKey = class representing keys used to order entries in the mesh table.
// this class must support all the standard math operators. wrap-
// around in the key values is supported
// CEntry = class indexed by the mesh table. this class must contain storage
// for a CInvasiveContext class
// OffsetOfIC = inline function returning the offset of the CInvasiveContext
// contained in the CEntry
//
// You must use the DECLARE_APPROXIMATE_INDEX macro to declare this class.
template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
class CApproximateIndex
{
public:
// class containing context needed per CEntry
class CInvasiveContext
{
public:
CInvasiveContext() {}
~CInvasiveContext() {}
static SIZE_T OffsetOfILE() { return OffsetOfIC() + OffsetOf( CInvasiveContext, m_ile ); }
private:
CInvasiveList< CEntry, OffsetOfILE >::CElement m_ile;
};
// API Error Codes
enum ERR
{
errSuccess,
errInvalidParameter,
errOutOfMemory,
errEntryNotFound,
errNoCurrentEntry,
errKeyRangeExceeded,
};
// API Lock Context
class CLock;
public:
// ctor / dtor
CApproximateIndex( const int Rank );
~CApproximateIndex();
// API
ERR ErrInit( const CKey dkeyPrecision,
const CKey dkeyUncertainty,
const double dblSpeedSizeTradeoff );
void Term();
void LockKeyPtr( const CKey& key, CEntry* const pentry, CLock* const plock );
void UnlockKeyPtr( CLock* const plock );
long CmpKey( const CKey& key1, const CKey& key2 ) const;
CKey KeyRangeFirst() const;
CKey KeyRangeLast() const;
CKey KeyInsertLeast() const;
CKey KeyInsertMost() const;
ERR ErrRetrieveEntry( CLock* const plock, CEntry** const ppentry ) const;
ERR ErrInsertEntry( CLock* const plock, CEntry* const pentry, const BOOL fNextMost = fTrue );
ERR ErrDeleteEntry( CLock* const plock );
ERR ErrReserveEntry( CLock* const plock );
void UnreserveEntry( CLock* const plock );
void MoveBeforeFirst( CLock* const plock );
ERR ErrMoveNext( CLock* const plock );
ERR ErrMovePrev( CLock* const plock );
void MoveAfterLast( CLock* const plock );
void MoveBeforeKeyPtr( const CKey& key, CEntry* const pentry, CLock* const plock );
void MoveAfterKeyPtr( const CKey& key, CEntry* const pentry, CLock* const plock );
public:
// bucket used for containing index entries that have approximately
// the same key
class CBucket
{
public:
// bucket ID
typedef unsigned long ID;
public:
CBucket() {}
~CBucket() {}
CBucket& operator=( const CBucket& bucket )
{
m_id = bucket.m_id;
m_cPin = bucket.m_cPin;
m_il = bucket.m_il;
return *this;
}
public:
ID m_id;
unsigned long m_cPin;
CInvasiveList< CEntry, CInvasiveContext::OffsetOfILE > m_il;
};
// table that contains our buckets
typedef CDynamicHashTable< CBucket::ID, CBucket > CBucketTable;
public:
// API Lock Context
class CLock
{
public:
CLock() {}
~CLock() {}
private:
friend class CApproximateIndex< CKey, CEntry, OffsetOfIC >;
CBucketTable::CLock m_lock;
CBucket m_bucket;
CEntry* m_pentryPrev;
CEntry* m_pentry;
CEntry* m_pentryNext;
};
private:
CBucket::ID _IdFromKeyPtr( const CKey& key, CEntry* const pentry ) const;
CBucket::ID _DeltaId( const CBucket::ID id, const long did ) const;
long _SubId( const CBucket::ID id1, const CBucket::ID id2 ) const;
long _CmpId( const CBucket::ID id1, const CBucket::ID id2 ) const;
CInvasiveContext* _PicFromPentry( CEntry* const pentry ) const;
BOOL _FExpandIdRange( const CBucket::ID idNew );
ERR _ErrInsertBucket( CLock* const plock );
ERR _ErrInsertEntry( CLock* const plock, CEntry* const pentry );
ERR _ErrMoveNext( CLock* const plock );
ERR _ErrMovePrev( CLock* const plock );
private:
// never updated
long m_shfKeyPrecision;
long m_shfKeyUncertainty;
long m_shfBucketHash;
long m_shfFillMSB;
CBucket::ID m_maskBucketKey;
CBucket::ID m_maskBucketPtr;
CBucket::ID m_maskBucketID;
long m_didRangeMost;
//BYTE m_rgbReserved1[ 0 ];
// seldom updated
CCriticalSection m_critUpdateIdRange;
long m_cidRange;
CBucket::ID m_idRangeFirst;
CBucket::ID m_idRangeLast;
BYTE m_rgbReserved2[ 16 ];
// commonly updated
CBucketTable m_bt;
//BYTE m_rgbReserved3[ 0 ];
};
// ctor
template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CApproximateIndex< CKey, CEntry, OffsetOfIC >::
CApproximateIndex( const int Rank )
: m_critUpdateIdRange( CLockBasicInfo( CSyncBasicInfo( "CApproximateIndex::m_critUpdateIdRange" ), Rank - 1, 0 ) ),
m_bt( Rank )
{
}
// dtor
template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CApproximateIndex< CKey, CEntry, OffsetOfIC >::
~CApproximateIndex()
{
}
// initializes the approximate index using the given parameters. if the index
// cannot be initialized, errOutOfMemory is returned
template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CApproximateIndex< CKey, CEntry, OffsetOfIC >::ERR CApproximateIndex< CKey, CEntry, OffsetOfIC >::
ErrInit( const CKey dkeyPrecision,
const CKey dkeyUncertainty,
const double dblSpeedSizeTradeoff )
{
// validate all parameters
if ( dkeyPrecision <= dkeyUncertainty ||
dkeyUncertainty < CKey( 0 ) ||
dblSpeedSizeTradeoff < 0.0 || dblSpeedSizeTradeoff > 1.0 )
{
return errInvalidParameter;
}
// init our parameters
const CBucket::ID cbucketHashMin = CBucket::ID( ( 1.0 - dblSpeedSizeTradeoff ) * OSSyncGetProcessorCount() );
CKey maskKey;
for ( m_shfKeyPrecision = 0, maskKey = 0;
dkeyPrecision > CKey( 1 ) << m_shfKeyPrecision && m_shfKeyPrecision < sizeof( CKey ) * 8;
maskKey |= CKey( 1 ) << m_shfKeyPrecision++ )
{
}
for ( m_shfKeyUncertainty = 0;
dkeyUncertainty > CKey( 1 ) << m_shfKeyUncertainty && m_shfKeyUncertainty < sizeof( CKey ) * 8;
m_shfKeyUncertainty++ )
{
}
for ( m_shfBucketHash = 0, m_maskBucketPtr = 0;
cbucketHashMin > CBucket::ID( 1 ) << m_shfBucketHash && m_shfBucketHash < sizeof( CBucket::ID ) * 8;
m_maskBucketPtr |= CBucket::ID( 1 ) << m_shfBucketHash++ )
{
}
m_maskBucketKey = CBucket::ID( maskKey >> m_shfKeyUncertainty );
m_shfFillMSB = sizeof( CBucket::ID ) * 8 - m_shfKeyPrecision + m_shfKeyUncertainty - m_shfBucketHash;
m_shfFillMSB = max( m_shfFillMSB, 0 );
m_maskBucketID = ( ~CBucket::ID( 0 ) ) >> m_shfFillMSB;
// if our parameters leave us with too much or too little precision for
// our bucket IDs, fail. "too much" precision would allow our bucket IDs
// to span more than half the precision of our bucket ID and cause our
// wrap-around-aware comparisons to fail. "too little" precision would
// give us too few bucket IDs to allow us to hash efficiently
//
// NOTE: we check for hash efficiency in the worst case so that we don't
// suddenly return errInvalidParameter on some new monster machine
const CBucket::ID cbucketHashMax = CBucket::ID( 1.0 * OSSyncGetProcessorCountMax() );
for ( long shfBucketHashMax = 0;
cbucketHashMax > CBucket::ID( 1 ) << shfBucketHashMax && shfBucketHashMax < sizeof( CBucket::ID ) * 8;
shfBucketHashMax++ )
{
}
long shfFillMSBMin;
shfFillMSBMin = sizeof( CBucket::ID ) * 8 - m_shfKeyPrecision + m_shfKeyUncertainty - shfBucketHashMax;
shfFillMSBMin = max( shfFillMSBMin, 0 );
if ( shfFillMSBMin < 0 ||
shfFillMSBMin > sizeof( CBucket::ID ) * 8 - shfBucketHashMax )
{
return errInvalidParameter;
}
// limit the ID range to within half the precision of the bucket ID
m_didRangeMost = m_maskBucketID >> 1;
// init our bucket ID range to be empty
m_cidRange = 0;
m_idRangeFirst = 0;
m_idRangeLast = 0;
// initialize the bucket table
if ( m_bt.ErrInit( 5.0, 1.0 ) != errSuccess )
{
Term();
return errOutOfMemory;
}
return errSuccess;
}
// terminates the approximate index. this function can be called even if the
// index has never been initialized or is only partially initialized
//
// NOTE: any data stored in the index at this time will be lost!
template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline void CApproximateIndex< CKey, CEntry, OffsetOfIC >::
Term()
{
// terminate the bucket table
m_bt.Term();
}
// acquires a lock on the specified key and entry pointer and returns the lock
// in the provided lock context
template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline void CApproximateIndex< CKey, CEntry, OffsetOfIC >::
LockKeyPtr( const CKey& key, CEntry* const pentry, CLock* const plock )
{
// compute the bucket ID for this key and entry pointer
plock->m_bucket.m_id = _IdFromKeyPtr( key, pentry );
// write lock this bucket ID in the bucket table
m_bt.WriteLockKey( plock->m_bucket.m_id, &plock->m_lock );
// fetch this bucket from the bucket table if it exists. if it doesn't
// exist, the bucket will start out empty and have the above bucket ID
plock->m_bucket.m_cPin = 0;
plock->m_bucket.m_il.Empty();
(void)m_bt.ErrRetrieveEntry( &plock->m_lock, &plock->m_bucket );
// the entry is in this bucket
if ( plock->m_bucket.m_il.FMember( pentry ) )
{
// set our currency to be on this entry in the bucket
plock->m_pentryPrev = NULL;
plock->m_pentry = pentry;
plock->m_pentryNext = NULL;
}
// the entry is not in this bucket
else
{
// set our currency to be before the first entry in this bucket
plock->m_pentryPrev = NULL;
plock->m_pentry = NULL;
plock->m_pentryNext = plock->m_bucket.m_il.PrevMost();
}
// if this bucket isn't pinned, it had better be represented by the valid
// bucket ID range of the index
COLLAssert( !plock->m_bucket.m_cPin ||
( _CmpId( plock->m_bucket.m_id, m_idRangeFirst ) >= 0 &&
_CmpId( plock->m_bucket.m_id, m_idRangeLast ) <= 0 ) );
}
// releases the lock in the specified lock context
template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline void CApproximateIndex< CKey, CEntry, OffsetOfIC >::
UnlockKeyPtr( CLock* const plock )
{
// if this bucket isn't pinned, it had better be represented by the valid
// bucket ID range of the index
COLLAssert( !plock->m_bucket.m_cPin ||
( _CmpId( plock->m_bucket.m_id, m_idRangeFirst ) >= 0 &&
_CmpId( plock->m_bucket.m_id, m_idRangeLast ) <= 0 ) );
// write unlock this bucket ID in the bucket table
m_bt.WriteUnlockKey( &plock->m_lock );
}
// compares two keys as they would be seen relative to each other by the
// approximate index
template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline long CApproximateIndex< CKey, CEntry, OffsetOfIC >::
CmpKey( const CKey& key1, const CKey& key2 ) const
{
return _CmpId( _IdFromKeyPtr( key1, NULL ), _IdFromKeyPtr( key2, NULL ) );
}
// returns the first key in the current key range. this key is guaranteed to
// be at least as small as the key of any record currently in the index given
// the precision and uncertainty of the index
template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CKey CApproximateIndex< CKey, CEntry, OffsetOfIC >::
KeyRangeFirst() const
{
return CKey( m_idRangeFirst >> m_shfBucketHash ) << m_shfKeyUncertainty;
}
// returns the last key in the current key range. this key is guaranteed to
// be at least as large as the key of any record currently in the index given
// the precision and uncertainty of the index
template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CKey CApproximateIndex< CKey, CEntry, OffsetOfIC >::
KeyRangeLast() const
{
return CKey( m_idRangeLast >> m_shfBucketHash ) << m_shfKeyUncertainty;
}
// returns the smallest key that could be successfully inserted into the index
template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CKey CApproximateIndex< CKey, CEntry, OffsetOfIC >::
KeyInsertLeast() const
{
const CBucket::ID cBucketHash = 1 << m_shfBucketHash;
CBucket::ID idFirstLeast = m_idRangeLast - m_didRangeMost;
idFirstLeast = idFirstLeast + ( cBucketHash - idFirstLeast % cBucketHash ) % cBucketHash;
return CKey( idFirstLeast >> m_shfBucketHash ) << m_shfKeyUncertainty;
}
// returns the largest key that could be successfully inserted into the index
template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CKey CApproximateIndex< CKey, CEntry, OffsetOfIC >::
KeyInsertMost() const
{
const CBucket::ID cBucketHash = 1 << m_shfBucketHash;
CBucket::ID idLastMost = m_idRangeFirst + m_didRangeMost;
idLastMost = idLastMost - ( idLastMost + 1 ) % cBucketHash;
return CKey( idLastMost >> m_shfBucketHash ) << m_shfKeyUncertainty;
}
// retrieves the entry corresponding to the key and entry pointer locked by the
// specified lock context. if there is no entry for this key, errEntryNotFound
// will be returned
template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CApproximateIndex< CKey, CEntry, OffsetOfIC >::ERR CApproximateIndex< CKey, CEntry, OffsetOfIC >::
ErrRetrieveEntry( CLock* const plock, CEntry** const ppentry ) const
{
// return the current entry. if the current entry is NULL, then there is
// no current entry
*ppentry = plock->m_pentry;
return *ppentry ? errSuccess : errEntryNotFound;
}
// inserts a new entry corresponding to the key and entry pointer locked by the
// specified lock context. fNextMost biases the position the entry will take
// when inserted in the index. if the new entry cannot be inserted,
// errOutOfMemory will be returned. if inserting the new entry will cause the
// key space to become too large, errKeyRangeExceeded will be returned
//
// NOTE: it is illegal to attempt to insert an entry into the index that is
// already in the index
template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CApproximateIndex< CKey, CEntry, OffsetOfIC >::ERR CApproximateIndex< CKey, CEntry, OffsetOfIC >::
ErrInsertEntry( CLock* const plock, CEntry* const pentry, const BOOL fNextMost )
{
CBucketTable::ERR err;
// this entry had better not already be in the index
COLLAssert( !plock->m_bucket.m_il.FMember( pentry ) );
// pin the bucket on behalf of the entry to insert
plock->m_bucket.m_cPin++;
// insert this entry at the selected end of the current bucket
if ( fNextMost )
{
plock->m_bucket.m_il.InsertAsNextMost( pentry );
}
else
{
plock->m_bucket.m_il.InsertAsPrevMost( pentry );
}
// try to update this bucket in the bucket table
if ( ( err = m_bt.ErrReplaceEntry( &plock->m_lock, plock->m_bucket ) ) != CBucketTable::errSuccess )
{
COLLAssert( err == CBucketTable::errNoCurrentEntry );
// the bucket does not yet exist, so try to insert it in the bucket table
return _ErrInsertEntry( plock, pentry );
}
// we succeeded in updating the bucket
else
{
// set the current entry to the newly inserted entry
plock->m_pentryPrev = NULL;
plock->m_pentry = pentry;
plock->m_pentryNext = NULL;
return errSuccess;
}
}
// deletes the entry corresponding to the key and entry pointer locked by the
// specified lock context. if there is no entry for this key, errNoCurrentEntry
// will be returned
template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CApproximateIndex< CKey, CEntry, OffsetOfIC >::ERR CApproximateIndex< CKey, CEntry, OffsetOfIC >::
ErrDeleteEntry( CLock* const plock )
{
// there is a current entry
if ( plock->m_pentry )
{
// save the current entry's prev and next pointers so that we can
// recover our currency when it is deleted
plock->m_pentryPrev = plock->m_bucket.m_il.Prev( plock->m_pentry );
plock->m_pentryNext = plock->m_bucket.m_il.Next( plock->m_pentry );
// delete the current entry from this bucket
plock->m_bucket.m_il.Remove( plock->m_pentry );
// unpin the bucket on behalf of this entry
plock->m_bucket.m_cPin--;
// update the bucket in the bucket table. it is OK if the bucket is
// empty because empty buckets are deleted in _ErrMoveNext/_ErrMovePrev
const CBucketTable::ERR err = m_bt.ErrReplaceEntry( &plock->m_lock, plock->m_bucket );
COLLAssert( err == CBucketTable::errSuccess );
// set our currency to no current entry
plock->m_pentry = NULL;
return errSuccess;
}
// there is no current entry
else
{
// return no current entry
return errNoCurrentEntry;
}
}
// reserves room to insert a new entry corresponding to the key and entry
// pointer locked by the specified lock context. if room for the new entry
// cannot be reserved, errOutOfMemory will be returned. if reserving the new
// entry will cause the key space to become too large, errKeyRangeExceeded
// will be returned
//
// NOTE: once room is reserved, it must be unreserved via UnreserveEntry()
template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CApproximateIndex< CKey, CEntry, OffsetOfIC >::ERR CApproximateIndex< CKey, CEntry, OffsetOfIC >::
ErrReserveEntry( CLock* const plock )
{
// pin the locked bucket
plock->m_bucket.m_cPin++;
// we failed to update the pin count on the bucket in the index because the
// bucket doesn't exist
CBucketTable::ERR errBT;
if ( ( errBT = m_bt.ErrReplaceEntry( &plock->m_lock, plock->m_bucket ) ) != CBucketTable::errSuccess )
{
COLLAssert( errBT == CBucketTable::errNoCurrentEntry );
// insert this bucket in the bucket table
ERR err;
if ( ( err = _ErrInsertBucket( plock ) ) != errSuccess )
{
COLLAssert( err == errOutOfMemory || err == errKeyRangeExceeded );
// we cannot insert the bucket so unpin the locked bucket and fail
// the reservation
plock->m_bucket.m_cPin--;
return err;
}
}
return errSuccess;
}
// removes a reservation made with ErrReserveEntry()
template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline void CApproximateIndex< CKey, CEntry, OffsetOfIC >::
UnreserveEntry( CLock* const plock )
{
// unpin the locked bucket
plock->m_bucket.m_cPin--;
// update the pin count on the bucket in the index. this cannot fail
// because we know the bucket exists because it is pinned
CBucketTable::ERR errBT = m_bt.ErrReplaceEntry( &plock->m_lock, plock->m_bucket );
COLLAssert( errBT == CBucketTable::errSuccess );
}
// sets up the specified lock context in preparation for scanning all entries
// in the index by ascending key value, give or take the key uncertainty
//
// NOTE: this function will acquire a lock that must eventually be released
// via UnlockKeyPtr()
template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline void CApproximateIndex< CKey, CEntry, OffsetOfIC >::
MoveBeforeFirst( CLock* const plock )
{
// we will start scanning at the first bucket ID believed to be present in
// the index (it could have been emptied by now)
plock->m_bucket.m_id = m_idRangeFirst;
// write lock this bucket ID in the bucket table
m_bt.WriteLockKey( plock->m_bucket.m_id, &plock->m_lock );
// fetch this bucket from the bucket table if it exists. if it doesn't
// exist, the bucket will start out empty and have the above bucket ID
plock->m_bucket.m_cPin = 0;
plock->m_bucket.m_il.Empty();
(void)m_bt.ErrRetrieveEntry( &plock->m_lock, &plock->m_bucket );
// set our currency to be before the first entry in this bucket
plock->m_pentryPrev = NULL;
plock->m_pentry = NULL;
plock->m_pentryNext = plock->m_bucket.m_il.PrevMost();
}
// moves the specified lock context to the next key and entry pointer in the
// index by ascending key value, give or take the key uncertainty. if the end
// of the index is reached, errNoCurrentEntry is returned
//
// NOTE: this function will acquire a lock that must eventually be released
// via UnlockKeyPtr()
template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CApproximateIndex< CKey, CEntry, OffsetOfIC >::ERR CApproximateIndex< CKey, CEntry, OffsetOfIC >::
ErrMoveNext( CLock* const plock )
{
// move to the next entry in this bucket
plock->m_pentryPrev = NULL;
plock->m_pentry = plock->m_pentry ?
plock->m_bucket.m_il.Next( plock->m_pentry ) :
plock->m_pentryNext;
plock->m_pentryNext = NULL;
// we still have no current entry
if ( !plock->m_pentry )
{
// possibly advance to the next bucket
return _ErrMoveNext( plock );
}
// we now have a current entry
else
{
// we're done
return errSuccess;
}
}
// moves the specified lock context to the next key and entry pointer in the
// index by descending key value, give or take the key uncertainty. if the
// start of the index is reached, errNoCurrentEntry is returned
//
// NOTE: this function will acquire a lock that must eventually be released
// via UnlockKeyPtr()
template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CApproximateIndex< CKey, CEntry, OffsetOfIC >::ERR CApproximateIndex< CKey, CEntry, OffsetOfIC >::
ErrMovePrev( CLock* const plock )
{
// move to the prev entry in this bucket
plock->m_pentryNext = NULL;
plock->m_pentry = plock->m_pentry ?
plock->m_bucket.m_il.Prev( plock->m_pentry ) :
plock->m_pentryPrev;
plock->m_pentryPrev = NULL;
// we still have no current entry
if ( !plock->m_pentry )
{
// possibly advance to the prev bucket
return _ErrMovePrev( plock );
}
// we now have a current entry
else
{
// we're done
return errSuccess;
}
}
// sets up the specified lock context in preparation for scanning all entries
// in the index by descending key value, give or take the key uncertainty
//
// NOTE: this function will acquire a lock that must eventually be released
// via UnlockKeyPtr()
template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline void CApproximateIndex< CKey, CEntry, OffsetOfIC >::
MoveAfterLast( CLock* const plock )
{
// we will start scanning at the last bucket ID believed to be present in
// the index (it could have been emptied by now)
plock->m_bucket.m_id = m_idRangeLast;
// write lock this bucket ID in the bucket table
m_bt.WriteLockKey( plock->m_bucket.m_id, &plock->m_lock );
// fetch this bucket from the bucket table if it exists. if it doesn't
// exist, the bucket will start out empty and have the above bucket ID
plock->m_bucket.m_cPin = 0;
plock->m_bucket.m_il.Empty();
(void)m_bt.ErrRetrieveEntry( &plock->m_lock, &plock->m_bucket );
// set our currency to be after the last entry in this bucket
plock->m_pentryPrev = plock->m_bucket.m_il.NextMost();
plock->m_pentry = NULL;
plock->m_pentryNext = NULL;
}
// sets up the specified lock context in preparation for scanning all entries
// greater than or approximately equal to the specified key and entry pointer
// in the index by ascending key value, give or take the key uncertainty
//
// NOTE: this function will acquire a lock that must eventually be released
// via UnlockKeyPtr()
//
// NOTE: even though this function may land between two valid entries in
// the index, the currency will not be on one of those entries until
// ErrMoveNext() or ErrMovePrev() has been called
template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline void CApproximateIndex< CKey, CEntry, OffsetOfIC >::
MoveBeforeKeyPtr( const CKey& key, CEntry* const pentry, CLock* const plock )
{
// we will start scanning at the bucket ID formed from the given key and
// entry pointer
plock->m_bucket.m_id = _IdFromKeyPtr( key, pentry );
// write lock this bucket ID in the bucket table
m_bt.WriteLockKey( plock->m_bucket.m_id, &plock->m_lock );
// fetch this bucket from the bucket table if it exists. if it doesn't
// exist, the bucket will start out empty and have the above bucket ID
plock->m_bucket.m_cPin = 0;
plock->m_bucket.m_il.Empty();
(void)m_bt.ErrRetrieveEntry( &plock->m_lock, &plock->m_bucket );
// set our currency to be before the first entry in this bucket
plock->m_pentryPrev = NULL;
plock->m_pentry = NULL;
plock->m_pentryNext = plock->m_bucket.m_il.PrevMost();
}
// sets up the specified lock context in preparation for scanning all entries
// less than or approximately equal to the specified key and entry pointer
// in the index by descending key value, give or take the key uncertainty
//
// NOTE: this function will acquire a lock that must eventually be released
// via UnlockKeyPtr()
//
// NOTE: even though this function may land between two valid entries in
// the index, the currency will not be on one of those entries until
// ErrMoveNext() or ErrMovePrev() has been called
template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline void CApproximateIndex< CKey, CEntry, OffsetOfIC >::
MoveAfterKeyPtr( const CKey& key, CEntry* const pentry, CLock* const plock )
{
// we will start scanning at the bucket ID formed from the given key and
// entry pointer
plock->m_bucket.m_id = _IdFromKeyPtr( key, pentry );
// write lock this bucket ID in the bucket table
m_bt.WriteLockKey( plock->m_bucket.m_id, &plock->m_lock );
// fetch this bucket from the bucket table if it exists. if it doesn't
// exist, the bucket will start out empty and have the above bucket ID
plock->m_bucket.m_cPin = 0;
plock->m_bucket.m_il.Empty();
(void)m_bt.ErrRetrieveEntry( &plock->m_lock, &plock->m_bucket );
// set our currency to be after the last entry in this bucket
plock->m_pentryPrev = plock->m_bucket.m_il.NextMost();
plock->m_pentry = NULL;
plock->m_pentryNext = NULL;
}
// transforms the given key and entry pointer into a bucket ID
template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CApproximateIndex< CKey, CEntry, OffsetOfIC >::CBucket::ID CApproximateIndex< CKey, CEntry, OffsetOfIC >::
_IdFromKeyPtr( const CKey& key, CEntry* const pentry ) const
{
// we compute the bucket ID such that each uncertainty range is split into
// several buckets, each of which are indexed by the pointer. we do this
// to provide maximum concurrency while accessing any particular range of
// keys. the reason we use the pointer in the calculation is that we want
// to minimize the number of times the user has to update the position of
// an entry due to a key change yet we need some property of the entry
// over which we can reproducibly hash
const CBucket::ID iBucketKey = CBucket::ID( key >> m_shfKeyUncertainty );
const CBucket::ID iBucketPtr = CBucket::ID( DWORD_PTR( pentry ) / sizeof( CEntry ) );
return ( ( iBucketKey & m_maskBucketKey ) << m_shfBucketHash ) + ( iBucketPtr & m_maskBucketPtr );
}
// performs a wrap-around insensitive delta of a bucket ID by an offset
template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CApproximateIndex< CKey, CEntry, OffsetOfIC >::CBucket::ID CApproximateIndex< CKey, CEntry, OffsetOfIC >::
_DeltaId( const CBucket::ID id, const long did ) const
{
return ( id + CBucket::ID( did ) ) & m_maskBucketID;
}
// performs a wrap-around insensitive subtraction of two bucket IDs
template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline long CApproximateIndex< CKey, CEntry, OffsetOfIC >::
_SubId( const CBucket::ID id1, const CBucket::ID id2 ) const
{
// munge bucket IDs to fill the Most Significant Bit of a long so that we
// can make a wrap-around aware subtraction
const long lid1 = id1 << m_shfFillMSB;
const long lid2 = id2 << m_shfFillMSB;
// munge the result back into the same scale as the bucket IDs
return CBucket::ID( ( lid1 - lid2 ) >> m_shfFillMSB );
}
// performs a wrap-around insensitive comparison of two bucket IDs
template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline long CApproximateIndex< CKey, CEntry, OffsetOfIC >::
_CmpId( const CBucket::ID id1, const CBucket::ID id2 ) const
{
// munge bucket IDs to fill the Most Significant Bit of a long so that we
// can make a wrap-around aware comparison
const long lid1 = id1 << m_shfFillMSB;
const long lid2 = id2 << m_shfFillMSB;
return lid1 - lid2;
}
// converts a pointer to an entry to a pointer to the invasive context
template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CApproximateIndex< CKey, CEntry, OffsetOfIC >::CInvasiveContext* CApproximateIndex< CKey, CEntry, OffsetOfIC >::
_PicFromPentry( CEntry* const pentry ) const
{
return (CInvasiveContext*)( (BYTE*)pentry + OffsetOfIC() );
}
// tries to expand the bucket ID range by adding the new bucket ID. if this
// cannot be done without violating the range constraints, fFalse will be
// returned
template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline BOOL CApproximateIndex< CKey, CEntry, OffsetOfIC >::
_FExpandIdRange( const CBucket::ID idNew )
{
// fetch the current ID range
const long cidRange = m_cidRange;
const CBucket::ID idFirst = m_idRangeFirst;
const CBucket::ID idLast = m_idRangeLast;
const long didRange = _SubId( idLast, idFirst );
COLLAssert( didRange >= 0 );
COLLAssert( didRange <= m_didRangeMost );
COLLAssert( cidRange >= 0 );
COLLAssert( cidRange <= m_didRangeMost + 1 );
// if there are no entries in the ID range then simply set the ID range to
// exactly contain this new bucket ID
if ( !cidRange )
{
m_cidRange = 1;
m_idRangeFirst = idNew;
m_idRangeLast = idNew;
return fTrue;
}
// compute the valid range for the new first ID and new last ID. these
// points and the above points form four ranges in a circular number
// line containing all possible bucket IDs:
//
// ( idFirstMic, idFirst ) Possible extension of the ID range
// [ idFirst, idLast ] The current ID range
// ( idLast, idLastMax ) Possible extension of the ID range
// [ idLastMax, idFirstMic ] Cannot be part of the ID range
//
// these ranges will never overlap due to the restriction that the
// ID range cannot meet or exceed half the number of bucket IDs
//
// NOTE: due to a quirk in 2's complement arithmetic where the 2's
// complement negative of the smallest negative number is itself, the
// inclusive range tests fail when idFirst == idLast and idNew ==
// idFirstMic == idLastMax or when idFirstMic == idLastMax and idnew ==
// idFirst == idLast. we have added special logic to handle these
// cases correctly
const CBucket::ID idFirstMic = _DeltaId( idFirst, -( m_didRangeMost - didRange + 1 ) );
const CBucket::ID idLastMax = _DeltaId( idLast, m_didRangeMost - didRange + 1 );
// if the new bucket ID is already part of this ID range, no change
// is needed
if ( _CmpId( idFirstMic, idNew ) != 0 && _CmpId( idLastMax, idNew ) != 0 &&
_CmpId( idFirst, idNew ) <= 0 && _CmpId( idNew, idLast ) <= 0 )
{
m_cidRange = cidRange + 1;
return fTrue;
}
// if the new bucket ID cannot be a part of this ID range, fail the
// expansion
if ( _CmpId( idFirst, idNew ) != 0 && _CmpId( idLast, idNew ) != 0 &&
_CmpId( idLastMax, idNew ) <= 0 && _CmpId( idNew, idFirstMic ) <= 0 )
{
return fFalse;
}
// compute the new ID range including this new bucket ID
CBucket::ID idFirstNew = idFirst;
CBucket::ID idLastNew = idLast;
if ( _CmpId( idFirstMic, idNew ) < 0 && _CmpId( idNew, idFirst ) < 0 )
{
idFirstNew = idNew;
}
else
{
COLLAssert( _CmpId( idLast, idNew ) < 0 && _CmpId( idNew, idLastMax ) < 0 );
idLastNew = idNew;
}
// the new ID range should be larger than the old ID range and should
// include the new bucket ID
COLLAssert( _CmpId( idFirstNew, idFirst ) <= 0 );
COLLAssert( _CmpId( idLast, idLastNew ) <= 0 );
COLLAssert( _SubId( idLastNew, idFirstNew ) > 0 );
COLLAssert( _SubId( idLastNew, idFirstNew ) <= m_didRangeMost );
COLLAssert( _CmpId( idFirstNew, idNew ) <= 0 );
COLLAssert( _CmpId( idNew, idLastNew ) <= 0 );
// update the key range to include the new bucket ID
m_cidRange = cidRange + 1;
m_idRangeFirst = idFirstNew;
m_idRangeLast = idLastNew;
return fTrue;
}
// inserts a new bucket in the bucket table
template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CApproximateIndex< CKey, CEntry, OffsetOfIC >::ERR CApproximateIndex< CKey, CEntry, OffsetOfIC >::
_ErrInsertBucket( CLock* const plock )
{
// try to update the bucket ID range and subrange of the index to include
// this new bucket ID
m_critUpdateIdRange.Enter();
const BOOL fRangeUpdated = _FExpandIdRange( plock->m_bucket.m_id );
m_critUpdateIdRange.Leave();
// if the update failed, fail the bucket insertion
if ( !fRangeUpdated )
{
return errKeyRangeExceeded;
}
// the bucket does not yet exist, so try to insert it in the bucket table
CBucketTable::ERR err;
if ( ( err = m_bt.ErrInsertEntry( &plock->m_lock, plock->m_bucket ) ) != CBucketTable::errSuccess )
{
COLLAssert( err == CBucketTable::errOutOfMemory );
// we cannot do the insert so fail
m_critUpdateIdRange.Enter();
m_cidRange--;
m_critUpdateIdRange.Leave();
return errOutOfMemory;
}
return errSuccess;
}
// performs an entry insertion that must insert a new bucket in the bucket table
template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CApproximateIndex< CKey, CEntry, OffsetOfIC >::ERR CApproximateIndex< CKey, CEntry, OffsetOfIC >::
_ErrInsertEntry( CLock* const plock, CEntry* const pentry )
{
ERR err;
// insert this bucket in the bucket table
if ( ( err = _ErrInsertBucket( plock ) ) != errSuccess )
{
COLLAssert( err == errOutOfMemory || err == errKeyRangeExceeded );
// we cannot insert the bucket so undo the list insertion and fail
plock->m_bucket.m_il.Remove( pentry );
plock->m_bucket.m_cPin--;
return err;
}
// set the current entry to the newly inserted entry
plock->m_pentryPrev = NULL;
plock->m_pentry = pentry;
plock->m_pentryNext = NULL;
return errSuccess;
}
// performs a move next that possibly goes to the next bucket. we won't go to
// the next bucket if we are already at the last bucket ID
template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CApproximateIndex< CKey, CEntry, OffsetOfIC >::ERR CApproximateIndex< CKey, CEntry, OffsetOfIC >::
_ErrMoveNext( CLock* const plock )
{
// set our currency to be after the last entry in this bucket
plock->m_pentryPrev = plock->m_bucket.m_il.NextMost();
plock->m_pentry = NULL;
plock->m_pentryNext = NULL;
// scan forward until we have a current entry or we are at or beyond the
// last bucket ID
while ( !plock->m_pentry && _CmpId( plock->m_bucket.m_id, m_idRangeLast ) < 0 )
{
// we are currently at the first bucket ID and that bucket isn't pinned
if ( !plock->m_bucket.m_cPin )
{
// delete this empty bucket (if it exists)
const CBucketTable::ERR err = m_bt.ErrDeleteEntry( &plock->m_lock );
COLLAssert( err == CBucketTable::errSuccess ||
err == CBucketTable::errNoCurrentEntry );
// advance the first bucket ID by one so that subsequent searches
// do not scan through this empty bucket unnecessarily
m_critUpdateIdRange.Enter();
if ( m_idRangeFirst == plock->m_bucket.m_id )
{
m_idRangeFirst = _DeltaId( m_idRangeFirst, 1 );
}
if ( err == CBucketTable::errSuccess )
{
m_cidRange--;
}
m_critUpdateIdRange.Leave();
}
// unlock the current bucket ID in the bucket table
m_bt.WriteUnlockKey( &plock->m_lock );
// this bucket ID may not be in the valid bucket ID range
if ( _CmpId( m_idRangeFirst, plock->m_bucket.m_id ) > 0 ||
_CmpId( plock->m_bucket.m_id, m_idRangeLast ) > 0 )
{
// we can get the critical section protecting the bucket ID range
if ( m_critUpdateIdRange.FTryEnter() )
{
// this bucket ID is not in the valid bucket ID range
if ( _CmpId( m_idRangeFirst, plock->m_bucket.m_id ) > 0 ||
_CmpId( plock->m_bucket.m_id, m_idRangeLast ) > 0 )
{
// go to the first valid bucket ID
plock->m_bucket.m_id = m_idRangeFirst;
}
// this bucket ID is in the valid bucket ID range
else
{
// advance to the next bucket ID
plock->m_bucket.m_id = _DeltaId( plock->m_bucket.m_id, 1 );
}
m_critUpdateIdRange.Leave();
}
// we cannot get the critical section protecting the bucket ID range
else
{
// advance to the next bucket ID
plock->m_bucket.m_id = _DeltaId( plock->m_bucket.m_id, 1 );
}
}
// this bucket may be in the valid bucket ID range
else
{
// advance to the next bucket ID
plock->m_bucket.m_id = _DeltaId( plock->m_bucket.m_id, 1 );
}
// write lock this bucket ID in the bucket table
m_bt.WriteLockKey( plock->m_bucket.m_id, &plock->m_lock );
// fetch this bucket from the bucket table if it exists. if it doesn't
// exist, the bucket will start out empty and have the above bucket ID
plock->m_bucket.m_cPin = 0;
plock->m_bucket.m_il.Empty();
(void)m_bt.ErrRetrieveEntry( &plock->m_lock, &plock->m_bucket );
// set our currency to be the first entry in this bucket
plock->m_pentryPrev = NULL;
plock->m_pentry = plock->m_bucket.m_il.PrevMost();
plock->m_pentryNext = NULL;
}
// return the status of our currency
return plock->m_pentry ? errSuccess : errNoCurrentEntry;
}
// performs a move prev that goes possibly to the prev bucket. we won't go to
// the prev bucket if we are already at the first bucket ID
template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CApproximateIndex< CKey, CEntry, OffsetOfIC >::ERR CApproximateIndex< CKey, CEntry, OffsetOfIC >::
_ErrMovePrev( CLock* const plock )
{
// set our currency to be before the first entry in this bucket
plock->m_pentryPrev = NULL;
plock->m_pentry = NULL;
plock->m_pentryNext = plock->m_bucket.m_il.PrevMost();
// scan backward until we have a current entry or we are at or before the
// first bucket ID
while ( !plock->m_pentry && _CmpId( m_idRangeFirst, plock->m_bucket.m_id ) < 0 )
{
// we are currently at the last bucket ID and that bucket isn't pinned
if ( !plock->m_bucket.m_cPin )
{
// delete this empty bucket (if it exists)
const CBucketTable::ERR err = m_bt.ErrDeleteEntry( &plock->m_lock );
COLLAssert( err == CBucketTable::errSuccess ||
err == CBucketTable::errNoCurrentEntry );
// retreat the last bucket ID by one so that subsequent searches
// do not scan through this empty bucket unnecessarily
m_critUpdateIdRange.Enter();
if ( m_idRangeLast == plock->m_bucket.m_id )
{
m_idRangeLast = _DeltaId( m_idRangeLast, -1 );
}
if ( err == CBucketTable::errSuccess )
{
m_cidRange--;
}
m_critUpdateIdRange.Leave();
}
// unlock the current bucket ID in the bucket table
m_bt.WriteUnlockKey( &plock->m_lock );
// this bucket ID may not be in the valid bucket ID range
if ( _CmpId( m_idRangeFirst, plock->m_bucket.m_id ) > 0 ||
_CmpId( plock->m_bucket.m_id, m_idRangeLast ) > 0 )
{
// we can get the critical section protecting the bucket ID range
if ( m_critUpdateIdRange.FTryEnter() )
{
// this bucket ID is not in the valid bucket ID range
if ( _CmpId( m_idRangeFirst, plock->m_bucket.m_id ) > 0 ||
_CmpId( plock->m_bucket.m_id, m_idRangeLast ) > 0 )
{
// go to the last valid bucket ID
plock->m_bucket.m_id = m_idRangeLast;
}
// this bucket ID is in the valid bucket ID range
else
{
// retreat to the previous bucket ID
plock->m_bucket.m_id = _DeltaId( plock->m_bucket.m_id, -1 );
}
m_critUpdateIdRange.Leave();
}
// we cannot get the critical section protecting the bucket ID range
else
{
// retreat to the previous bucket ID
plock->m_bucket.m_id = _DeltaId( plock->m_bucket.m_id, -1 );
}
}
// this bucket may be in the valid bucket ID range
else
{
// retreat to the previous bucket ID
plock->m_bucket.m_id = _DeltaId( plock->m_bucket.m_id, -1 );
}
// write lock this bucket ID in the bucket table
m_bt.WriteLockKey( plock->m_bucket.m_id, &plock->m_lock );
// fetch this bucket from the bucket table if it exists. if it doesn't
// exist, the bucket will start out empty and have the above bucket ID
plock->m_bucket.m_cPin = 0;
plock->m_bucket.m_il.Empty();
(void)m_bt.ErrRetrieveEntry( &plock->m_lock, &plock->m_bucket );
// set our currency to be the last entry in this bucket
plock->m_pentryPrev = NULL;
plock->m_pentry = plock->m_bucket.m_il.NextMost();
plock->m_pentryNext = NULL;
}
// return the status of our currency
return plock->m_pentry ? errSuccess : errNoCurrentEntry;
}
#define DECLARE_APPROXIMATE_INDEX( CKey, CEntry, OffsetOfIC, Typedef ) \
\
typedef CApproximateIndex< CKey, CEntry, OffsetOfIC > Typedef; \
\
inline ULONG_PTR Typedef::CBucketTable::CKeyEntry:: \
Hash( const CBucket::ID& id ) \
{ \
return id; \
} \
\
inline ULONG_PTR Typedef::CBucketTable::CKeyEntry:: \
Hash() const \
{ \
return m_entry.m_id; \
} \
\
inline BOOL Typedef::CBucketTable::CKeyEntry:: \
FEntryMatchesKey( const CBucket::ID& id ) const \
{ \
return m_entry.m_id == id; \
} \
\
inline void Typedef::CBucketTable::CKeyEntry:: \
SetEntry( const CBucket& bucket ) \
{ \
m_entry = bucket; \
} \
\
inline void Typedef::CBucketTable::CKeyEntry:: \
GetEntry( CBucket* const pbucket ) const \
{ \
*pbucket = m_entry; \
}
//////////////////////////////////////////////////////////////////////////////////////////
// CPool
//
// Implements a pool of objects that can be inserted and deleted quickly in arbitrary
// order.
//
// CObject = class representing objects in the pool. each class must contain
// storage for a CInvasiveContext for embedded pool state
// OffsetOfIC = inline function returning the offset of the CInvasiveContext
// contained in the CObject
template< class CObject, PfnOffsetOf OffsetOfIC >
class CPool
{
public:
// class containing context needed per CObject
class CInvasiveContext
{
public:
CInvasiveContext() {}
~CInvasiveContext() {}
static SIZE_T OffsetOfILE() { return OffsetOfIC() + OffsetOf( CInvasiveContext, m_ile ); }
private:
CInvasiveList< CObject, OffsetOfILE >::CElement m_ile;
};
// API Error Codes
enum ERR
{
errSuccess,
errInvalidParameter,
errOutOfMemory,
errObjectNotFound,
errOutOfObjects,
errNoCurrentObject,
};
// API Lock Context
class CLock;
public:
// ctor / dtor
CPool();
~CPool();
// API
ERR ErrInit( const double dblSpeedSizeTradeoff );
void Term();
void Insert( CObject* const pobj );
ERR ErrRemove( CObject** const ppobj, const BOOL fWait = fTrue );
void BeginPoolScan( CLock* const plock );
ERR ErrGetNextObject( CLock* const plock, CObject** const ppobj );
ERR ErrRemoveCurrentObject( CLock* const plock );
void EndPoolScan( CLock* const plock );
DWORD Cobject();
DWORD CWaiter();
DWORD CRemove();
DWORD CRemoveWait();
private:
// bucket used for containing objects in the pool
class CBucket
{
public:
CBucket() : m_crit( CLockBasicInfo( CSyncBasicInfo( "CPool::CBucket::m_crit" ), 0, 0 ) ) {}
~CBucket() {}
public:
CCriticalSection m_crit;
CInvasiveList< CObject, CInvasiveContext::OffsetOfILE > m_il;
BYTE m_rgbReserved[20];
};
public:
// API Lock Context
class CLock
{
public:
CLock() {}
~CLock() {}
private:
friend class CPool< CObject, OffsetOfIC >;
CBucket* m_pbucket;
CObject* m_pobj;
CObject* m_pobjNext;
};
private:
void _GetNextObject( CLock* const plock );
static void* _PvMEMIAlign( void* const pv, const size_t cbAlign );
static void* _PvMEMIUnalign( void* const pv );
static void* _PvMEMAlloc( const size_t cbSize, const size_t cbAlign = 1 );
static void _MEMFree( void* const pv );
private:
// never updated
DWORD m_cbucket;
CBucket* m_rgbucket;
BYTE m_rgbReserved1[24];
// commonly updated
CSemaphore m_semObjectCount;
DWORD m_cRemove;
DWORD m_cRemoveWait;
BYTE m_rgbReserved2[20];
};
// ctor
template< class CObject, PfnOffsetOf OffsetOfIC >
inline CPool< CObject, OffsetOfIC >::
CPool()
: m_semObjectCount( CSyncBasicInfo( "CPool::m_semObjectCount" ) )
{
}
// dtor
template< class CObject, PfnOffsetOf OffsetOfIC >
inline CPool< CObject, OffsetOfIC >::
~CPool()
{
// nop
}
// initializes the pool using the given parameters. if the pool cannot be
// initialized, errOutOfMemory is returned
template< class CObject, PfnOffsetOf OffsetOfIC >
inline CPool< CObject, OffsetOfIC >::ERR CPool< CObject, OffsetOfIC >::
ErrInit( const double dblSpeedSizeTradeoff )
{
// validate all parameters
if ( dblSpeedSizeTradeoff < 0.0 || dblSpeedSizeTradeoff > 1.0 )
{
return errInvalidParameter;
}
// allocate our bucket array, one per CPU, on a cache-line boundary
m_cbucket = OSSyncGetProcessorCount();
const SIZE_T cbrgbucket = sizeof( CBucket ) * m_cbucket;
if ( !( m_rgbucket = (CBucket*)_PvMEMAlloc( cbrgbucket, cbCacheLine ) ) )
{
return errOutOfMemory;
}
// setup our bucket array
for ( DWORD ibucket = 0; ibucket < m_cbucket; ibucket++ )
{
new( m_rgbucket + ibucket ) CBucket;
}
// init out stats
m_cRemove = 0;
m_cRemoveWait = 0;
return errSuccess;
}
// terminates the pool. this function can be called even if the pool has never
// been initialized or is only partially initialized
//
// NOTE: any data stored in the pool at this time will be lost!
template< class CObject, PfnOffsetOf OffsetOfIC >
inline void CPool< CObject, OffsetOfIC >::
Term()
{
// free our bucket array
if ( m_rgbucket )
{
for ( DWORD ibucket = 0; ibucket < m_cbucket; ibucket++ )
{
m_rgbucket[ ibucket ].~CBucket();
}
_MEMFree( m_rgbucket );
m_rgbucket = NULL;
}
// remove any free counts on our semaphore
while ( m_semObjectCount.FTryAcquire() )
{
}
}
// inserts the given object into the pool
template< class CObject, PfnOffsetOf OffsetOfIC >
inline void CPool< CObject, OffsetOfIC >::
Insert( CObject* const pobj )
{
// add the given object to the bucket for this CPU. we use one bucket per
// CPU to reduce cache sloshing. if we cannot lock the bucket for this CPU,
// we will try another bucket instead of blocking
DWORD ibucketBase;
DWORD ibucket;
ibucketBase = OSSyncGetCurrentProcessor();
ibucket = 0;
do {
CBucket* const pbucket = m_rgbucket + ( ibucketBase + ibucket++ ) % m_cbucket;
if ( ibucket < m_cbucket )
{
if ( !pbucket->m_crit.FTryEnter() )
{
continue;
}
}
else
{
pbucket->m_crit.Enter();
}
pbucket->m_il.InsertAsNextMost( pobj );
pbucket->m_crit.Leave();
break;
}
while ( fTrue );
// increment the object count
m_semObjectCount.Release();
}
// removes an object from the pool, optionally waiting until an object can be
// removed. if an object can be removed, errSuccess is returned. if an
// object cannot be immediately removed and waiting is not desired,
// errOutOfObjects will be returned
template< class CObject, PfnOffsetOf OffsetOfIC >
inline CPool< CObject, OffsetOfIC >::ERR CPool< CObject, OffsetOfIC >::
ErrRemove( CObject** const ppobj, const BOOL fWait )
{
// reserve an object for removal from the pool by acquiring a count on the
// object count semaphore. if we get a count, we are allowed to remove an
// object from the pool. acquire a count in the requested mode, i.e. wait
// or do not wait for a count
if ( !m_semObjectCount.FTryAcquire() )
{
if ( !fWait )
{
return errOutOfObjects;
}
else
{
m_cRemoveWait++;
m_semObjectCount.FAcquire( cmsecInfinite );
}
}
// we are now entitled to an object from the pool, so scan all buckets for
// an object to remove until we find one. start with the bucket for the
// current CPU to reduce cache sloshing
DWORD ibucketBase;
DWORD ibucket;
ibucketBase = OSSyncGetCurrentProcessor();
ibucket = 0;
*ppobj = NULL;
do {
CBucket* const pbucket = m_rgbucket + ( ibucketBase + ibucket++ ) % m_cbucket;
if ( pbucket->m_il.FEmpty() )
{
continue;
}
if ( ibucket < m_cbucket )
{
if ( !pbucket->m_crit.FTryEnter() )
{
continue;
}
}
else
{
pbucket->m_crit.Enter();
}
if ( !pbucket->m_il.FEmpty() )
{
*ppobj = pbucket->m_il.PrevMost();
pbucket->m_il.Remove( *ppobj );
}
pbucket->m_crit.Leave();
}
while ( *ppobj == NULL );
// return the object
m_cRemove++;
return errSuccess;
}
// sets up the specified lock context in preparation for scanning all objects
// in the pool
//
// NOTE: this function will acquire a lock that must eventually be released
// via EndPoolScan()
template< class CObject, PfnOffsetOf OffsetOfIC >
inline void CPool< CObject, OffsetOfIC >::
BeginPoolScan( CLock* const plock )
{
// we will start in the first bucket
plock->m_pbucket = m_rgbucket;
// lock this bucket
plock->m_pbucket->m_crit.Enter();
// set out currency to be before the first object in this bucket
plock->m_pobj = NULL;
plock->m_pobjNext = plock->m_pbucket->m_il.PrevMost();
}
// retrieves the next object in the pool locked by the specified lock context.
// if there are no more objects to be scanned, errNoCurrentObject is returned
template< class CObject, PfnOffsetOf OffsetOfIC >
inline CPool< CObject, OffsetOfIC >::ERR CPool< CObject, OffsetOfIC >::
ErrGetNextObject( CLock* const plock, CObject** const ppobj )
{
// move to the next object in this bucket
plock->m_pobj = plock->m_pobj ?
plock->m_pbucket->m_il.Next( plock->m_pobj ) :
plock->m_pobjNext;
plock->m_pobjNext = NULL;
// we still have no current object
if ( !plock->m_pobj )
{
// possibly advance to the next bucket
_GetNextObject( plock );
}
// return the current object, if any
*ppobj = plock->m_pobj;
return plock->m_pobj ? errSuccess : errNoCurrentObject;
}
// removes the current object in the pool locaked by the specified lock context
// from the pool. if there is no current object, errNoCurrentObject will be
// returned
template< class CObject, PfnOffsetOf OffsetOfIC >
inline CPool< CObject, OffsetOfIC >::ERR CPool< CObject, OffsetOfIC >::
ErrRemoveCurrentObject( CLock* const plock )
{
// there is a current object and we can remove that object from the pool
//
// NOTE: we must get a count from the semaphore to remove an object from
// the pool
if ( plock->m_pobj && m_semObjectCount.FTryAcquire() )
{
// save the current object's next pointer so that we can recover our
// currency when it is deleted
plock->m_pobjNext = plock->m_pbucket->m_il.Next( plock->m_pobj );
// delete the current object from this bucket
plock->m_pbucket->m_il.Remove( plock->m_pobj );
// set our currency to no current object
plock->m_pobj = NULL;
return errSuccess;
}
// there is no current object
else
{
// return no current object
return errNoCurrentObject;
}
}
// ends the scan of all objects in the pool associated with the specified lock
// context and releases all locks held
template< class CObject, PfnOffsetOf OffsetOfIC >
inline void CPool< CObject, OffsetOfIC >::
EndPoolScan( CLock* const plock )
{
// unlock the current bucket
plock->m_pbucket->m_crit.Leave();
}
// returns the current count of objects in the pool
template< class CObject, PfnOffsetOf OffsetOfIC >
inline DWORD CPool< CObject, OffsetOfIC >::
Cobject()
{
// the number of objects in the pool is equal to the available count on the
// object count semaphore
return m_semObjectCount.CAvail();
}
// returns the number of waiters for objects in the pool
template< class CObject, PfnOffsetOf OffsetOfIC >
inline DWORD CPool< CObject, OffsetOfIC >::
CWaiter()
{
// the number of waiters on the pool is equal to the waiter count on the
// object count semaphore
return m_semObjectCount.CWait();
}
// returns the number of times on object has been successfully removed from the
// pool
template< class CObject, PfnOffsetOf OffsetOfIC >
inline DWORD CPool< CObject, OffsetOfIC >::
CRemove()
{
return m_cRemove;
}
// returns the number of waits that occurred while removing objects from the
// pool
template< class CObject, PfnOffsetOf OffsetOfIC >
inline DWORD CPool< CObject, OffsetOfIC >::
CRemoveWait()
{
return m_cRemoveWait;
}
// performs a move next that possibly goes to the next bucket. we won't go to
// the next bucket if we are already at the last bucket
template< class CObject, PfnOffsetOf OffsetOfIC >
inline void CPool< CObject, OffsetOfIC >::
_GetNextObject( CLock* const plock )
{
// set our currency to be after the last object in this bucket
plock->m_pobj = NULL;
plock->m_pobjNext = NULL;
// scan forward until we have a current object or we are at or beyond the
// last bucket
while ( !plock->m_pobj && plock->m_pbucket < m_rgbucket + m_cbucket - 1 )
{
// unlock the current bucket
plock->m_pbucket->m_crit.Leave();
// advance to the next bucket
plock->m_pbucket++;
// lock this bucket
plock->m_pbucket->m_crit.Enter();
// set our currency to be the first object in this bucket
plock->m_pobj = plock->m_pbucket->m_il.PrevMost();
plock->m_pobjNext = NULL;
}
}
// calculate the address of the aligned block and store its offset (for free)
template< class CObject, PfnOffsetOf OffsetOfIC >
inline void* CPool< CObject, OffsetOfIC >::
_PvMEMIAlign( void* const pv, const size_t cbAlign )
{
// round up to the nearest cache line
// NOTE: this formula always forces an offset of at least 1 byte
const ULONG_PTR ulp = ULONG_PTR( pv );
const ULONG_PTR ulpAligned = ( ( ulp + cbAlign ) / cbAlign ) * cbAlign;
const ULONG_PTR ulpOffset = ulpAligned - ulp;
COLLAssert( ulpOffset > 0 );
COLLAssert( ulpOffset <= cbAlign );
COLLAssert( ulpOffset == BYTE( ulpOffset ) ); // must fit into a single BYTE
// store the offset
BYTE *const pbAligned = (BYTE*)ulpAligned;
pbAligned[ -1 ] = BYTE( ulpOffset );
// return the aligned block
return (void*)pbAligned;
}
// retrieve the offset of the real block being freed
template< class CObject, PfnOffsetOf OffsetOfIC >
inline void* CPool< CObject, OffsetOfIC >::
_PvMEMIUnalign( void* const pv )
{
// read the offset of the real block
BYTE *const pbAligned = (BYTE*)pv;
const BYTE bOffset = pbAligned[ -1 ];
COLLAssert( bOffset > 0 );
// return the real unaligned block
return (void*)( pbAligned - bOffset );
}
template< class CObject, PfnOffsetOf OffsetOfIC >
inline void* CPool< CObject, OffsetOfIC >::
_PvMEMAlloc( const size_t cbSize, const size_t cbAlign )
{
void* const pv = new BYTE[ cbSize + cbAlign ];
if ( pv )
{
return _PvMEMIAlign( pv, cbAlign );
}
return NULL;
}
template< class CObject, PfnOffsetOf OffsetOfIC >
inline void CPool< CObject, OffsetOfIC >::
_MEMFree( void* const pv )
{
if ( pv )
{
delete [] _PvMEMIUnalign( pv );
}
}
////////////////////////////////////////////////////////////////////////////////
// CArray
//
// Implements a dynamically resized array of entries stored for efficient
// iteration.
//
// CEntry = class representing entries stored in the array
//
// NOTE: the user must provide CEntry::CEntry() and CEntry::operator=()
template< class CEntry >
class CArray
{
public:
// API Error Codes
enum ERR
{
errSuccess,
errInvalidParameter,
errOutOfMemory,
};
public:
CArray();
CArray( const size_t centry, CEntry* const rgentry );
~CArray();
ERR ErrClone( const CArray& array );
ERR ErrSetSize( const size_t centry );
ERR ErrSetEntry( const size_t ientry, const CEntry& entry );
void SetEntry( const CEntry* const pentry, const CEntry& entry );
size_t Size() const;
const CEntry* Entry( const size_t ientry ) const;
private:
size_t m_centry;
CEntry* m_rgentry;
BOOL m_fInPlace;
};
template< class CEntry >
inline CArray< CEntry >::
CArray()
: m_centry( 0 ),
m_rgentry( NULL ),
m_fInPlace( fTrue )
{
}
template< class CEntry >
inline CArray< CEntry >::
CArray( const size_t centry, CEntry* const rgentry )
: m_centry( centry ),
m_rgentry( rgentry ),
m_fInPlace( fTrue )
{
}
template< class CEntry >
inline CArray< CEntry >::
~CArray()
{
ErrSetSize( 0 );
}
// clones an existing array
template< class CEntry >
inline CArray< CEntry >::ERR CArray< CEntry >::
ErrClone( const CArray& array )
{
CEntry* rgentryNew = NULL;
size_t ientryCopy = 0;
if ( array.m_centry )
{
if ( !( rgentryNew = new CEntry[ array.m_centry ] ) )
{
return errOutOfMemory;
}
}
for ( ientryCopy = 0; ientryCopy < array.m_centry; ientryCopy++ )
{
rgentryNew[ ientryCopy ] = array.m_rgentry[ ientryCopy ];
}
if ( !m_fInPlace )
{
delete [] m_rgentry;
}
m_centry = array.m_centry;
m_rgentry = rgentryNew;
m_fInPlace = fFalse;
rgentryNew = NULL;
delete [] rgentryNew;
return errSuccess;
}
// sets the size of the array
template< class CEntry >
inline CArray< CEntry >::ERR CArray< CEntry >::
ErrSetSize( const size_t centry )
{
CEntry* rgentryNew = NULL;
size_t ientryCopy = 0;
if ( Size() != centry )
{
if ( centry )
{
if ( !( rgentryNew = new CEntry[ centry ] ) )
{
return errOutOfMemory;
}
for ( ientryCopy = 0; ientryCopy < Size(); ientryCopy++ )
{
rgentryNew[ ientryCopy ] = *Entry( ientryCopy );
}
if ( !m_fInPlace )
{
delete [] m_rgentry;
}
m_centry = centry;
m_rgentry = rgentryNew;
m_fInPlace = fFalse;
rgentryNew = NULL;
}
else
{
if ( !m_fInPlace )
{
delete [] m_rgentry;
}
m_centry = 0;
m_rgentry = NULL;
m_fInPlace = fTrue;
}
}
delete [] rgentryNew;
return errSuccess;
}
// sets the Nth entry of the array, growing the array if necessary
template< class CEntry >
inline CArray< CEntry >::ERR CArray< CEntry >::
ErrSetEntry( const size_t ientry, const CEntry& entry )
{
ERR err = errSuccess;
size_t centryReq = ientry + 1;
if ( Size() < centryReq )
{
if ( ( err = ErrSetSize( centryReq ) ) != errSuccess )
{
return err;
}
}
SetEntry( Entry( ientry ), entry );
return errSuccess;
}
// sets an existing entry of the array
template< class CEntry >
inline void CArray< CEntry >::
SetEntry( const CEntry* const pentry, const CEntry& entry )
{
*const_cast< CEntry* >( pentry ) = entry;
}
// returns the current size of the array
template< class CEntry >
inline size_t CArray< CEntry >::
Size() const
{
return m_centry;
}
// returns a pointer to the Nth entry of the array or NULL if it is empty
template< class CEntry >
inline const CEntry* CArray< CEntry >::
Entry( const size_t ientry ) const
{
return ientry < m_centry ? m_rgentry + ientry : NULL;
}
////////////////////////////////////////////////////////////////////////////////
// CTable
//
// Implements a table of entries identified by a key and stored for efficient
// lookup and iteration. The keys need not be unique.
//
// CKey = class representing keys used to identify entries
// CEntry = class representing entries stored in the table
//
// NOTE: the user must implement the CKeyEntry::Cmp() functions and provide
// CEntry::CEntry() and CEntry::operator=()
template< class CKey, class CEntry >
class CTable
{
public:
class CKeyEntry
: public CEntry
{
public:
// Cmp() return values:
//
// < 0 this entry < specified entry / key
// = 0 this entry = specified entry / key
// > 0 this entry > specified entry / key
int Cmp( const CKeyEntry& keyentry ) const;
int Cmp( const CKey& key ) const;
};
// API Error Codes
enum ERR
{
errSuccess,
errInvalidParameter,
errOutOfMemory,
errKeyChange,
};
public:
CTable();
CTable( const size_t centry, CEntry* const rgentry, const BOOL fInOrder = fFalse );
ERR ErrLoad( const size_t centry, const CEntry* const rgentry );
ERR ErrClone( const CTable& table );
ERR ErrUpdateEntry( const CEntry* const pentry, const CEntry& entry );
size_t Size() const;
const CEntry* Entry( const size_t ientry ) const;
const CEntry* SeekLT( const CKey& key ) const;
const CEntry* SeekLE( const CKey& key ) const;
const CEntry* SeekEQ( const CKey& key ) const;
const CEntry* SeekHI( const CKey& key ) const;
const CEntry* SeekGE( const CKey& key ) const;
const CEntry* SeekGT( const CKey& key ) const;
private:
typedef size_t (CTable< CKey, CEntry >::*PfnSearch)( const CKey& key, const BOOL fHigh ) const;
private:
const CKeyEntry& _Entry( const size_t ikeyentry ) const;
void _SetEntry( const size_t ikeyentry, const CKeyEntry& keyentry );
void _SwapEntry( const size_t ikeyentry1, const size_t ikeyentry2 );
size_t _LinearSearch( const CKey& key, const BOOL fHigh ) const;
size_t _BinarySearch( const CKey& key, const BOOL fHigh ) const;
void _InsertionSort( const size_t ikeyentryMinIn, const size_t ikeyentryMaxIn );
void _QuickSort( const size_t ikeyentryMinIn, const size_t ikeyentryMaxIn );
private:
CArray< CKeyEntry > m_arrayKeyEntry;
PfnSearch m_pfnSearch;
};
template< class CKey, class CEntry >
inline CTable< CKey, CEntry >::
CTable()
: m_pfnSearch( _LinearSearch )
{
}
// loads the table over an existing array of entries. if the entries are not
// in order then they will be sorted in place
template< class CKey, class CEntry >
inline CTable< CKey, CEntry >::
CTable( const size_t centry, CEntry* const rgentry, const BOOL fInOrder )
: m_arrayKeyEntry( centry, reinterpret_cast< CKeyEntry* >( rgentry ) )
{
size_t n;
size_t log2n;
for ( n = Size(), log2n = 0; n; n = n / 2, log2n++ );
if ( 2 * log2n < Size() )
{
if ( !fInOrder )
{
_QuickSort( 0, Size() );
}
m_pfnSearch = _BinarySearch;
}
else
{
if ( !fInOrder )
{
_InsertionSort( 0, Size() );
}
m_pfnSearch = _LinearSearch;
}
}
// loads an array of entries into the table. additional entries may also be
// loaded into the table via this function
template< class CKey, class CEntry >
inline CTable< CKey, CEntry >::ERR CTable< CKey, CEntry >::
ErrLoad( const size_t centry, const CEntry* const rgentry )
{
CArray< CKeyEntry >::ERR err = CArray< CKeyEntry >::errSuccess;
size_t ientry = 0;
size_t ientryMin = Size();
size_t ientryMax = Size() + centry;
const CKeyEntry* rgkeyentry = reinterpret_cast< const CKeyEntry* >( rgentry );
if ( ( err = m_arrayKeyEntry.ErrSetSize( Size() + centry ) ) != CArray< CKeyEntry >::errSuccess )
{
COLLAssert( err == CArray< CKeyEntry >::errOutOfMemory );
return errOutOfMemory;
}
for ( ientry = ientryMin; ientry < ientryMax; ientry++ )
{
err = m_arrayKeyEntry.ErrSetEntry( ientry, rgkeyentry[ ientry - ientryMin ] );
COLLAssert( err == CArray< CKeyEntry >::errSuccess );
}
size_t n;
size_t log2n;
for ( n = Size(), log2n = 0; n; n = n / 2, log2n++ );
if ( 2 * log2n < centry )
{
_QuickSort( 0, Size() );
}
else
{
_InsertionSort( 0, Size() );
}
if ( 2 * log2n < Size() )
{
m_pfnSearch = _BinarySearch;
}
else
{
m_pfnSearch = _LinearSearch;
}
return errSuccess;
}
// clones an existing table
template< class CKey, class CEntry >
inline CTable< CKey, CEntry >::ERR CTable< CKey, CEntry >::
ErrClone( const CTable& table )
{
CArray< CKeyEntry >::ERR err = CArray< CKeyEntry >::errSuccess;
if ( ( err = m_arrayKeyEntry.ErrClone( table.m_arrayKeyEntry ) ) != CArray< CKeyEntry >::errSuccess )
{
COLLAssert( err == CArray< CKeyEntry >::errOutOfMemory );
return errOutOfMemory;
}
m_pfnSearch = table.m_pfnSearch;
return errSuccess;
}
// updates an existing entry in the table as long as it doesn't change
// that entry's position in the table
template< class CKey, class CEntry >
inline CTable< CKey, CEntry >::ERR CTable< CKey, CEntry >::
ErrUpdateEntry( const CEntry* const pentry, const CEntry& entry )
{
ERR err = errSuccess;
const CKeyEntry* pkeyentry = reinterpret_cast< const CKeyEntry* >( pentry );
const CKeyEntry& keyentry = reinterpret_cast< const CKeyEntry& >( entry );
if ( !pkeyentry->Cmp( keyentry ) )
{
m_arrayKeyEntry.SetEntry( pkeyentry, keyentry );
err = errSuccess;
}
else
{
err = errKeyChange;
}
return err;
}
// returns the current size of the table
template< class CKey, class CEntry >
inline size_t CTable< CKey, CEntry >::
Size() const
{
return m_arrayKeyEntry.Size();
}
// returns a pointer to the Nth entry of the table or NULL if it is empty
template< class CKey, class CEntry >
inline const CEntry* CTable< CKey, CEntry >::
Entry( const size_t ientry ) const
{
return static_cast< const CEntry* >( m_arrayKeyEntry.Entry( ientry ) );
}
// the following group of functions return a pointer to an entry whose key
// matches the specified key according to the given criteria:
//
// Suffix Description Positional bias
//
// LT less than high
// LE less than or equal to low
// EQ equal to low
// HI equal to high
// GE greater than or equal to high
// GT greater than low
//
// if no matching entry was found then NULL will be returned
//
// "positional bias" means that the function will land on a matching entry
// whose position is closest to the low / high end of the table
template< class CKey, class CEntry >
inline const CEntry* CTable< CKey, CEntry >::
SeekLT( const CKey& key ) const
{
const size_t ikeyentry = (this->*m_pfnSearch)( key, fFalse );
if ( ikeyentry < Size() &&
_Entry( ikeyentry ).Cmp( key ) < 0 )
{
return Entry( ikeyentry );
}
else
{
return Entry( ikeyentry - 1 );
}
}
template< class CKey, class CEntry >
inline const CEntry* CTable< CKey, CEntry >::
SeekLE( const CKey& key ) const
{
const size_t ikeyentry = (this->*m_pfnSearch)( key, fFalse );
if ( ikeyentry < Size() &&
_Entry( ikeyentry ).Cmp( key ) <= 0 )
{
return Entry( ikeyentry );
}
else
{
return Entry( ikeyentry - 1 );
}
}
template< class CKey, class CEntry >
inline const CEntry* CTable< CKey, CEntry >::
SeekEQ( const CKey& key ) const
{
const size_t ikeyentry = (this->*m_pfnSearch)( key, fFalse );
if ( ikeyentry < Size() &&
_Entry( ikeyentry ).Cmp( key ) == 0 )
{
return Entry( ikeyentry );
}
else
{
return NULL;
}
}
template< class CKey, class CEntry >
inline const CEntry* CTable< CKey, CEntry >::
SeekHI( const CKey& key ) const
{
const size_t ikeyentry = (this->*m_pfnSearch)( key, fTrue );
if ( ikeyentry > 0 &&
_Entry( ikeyentry - 1 ).Cmp( key ) == 0 )
{
return Entry( ikeyentry - 1 );
}
else
{
return NULL;
}
}
template< class CKey, class CEntry >
inline const CEntry* CTable< CKey, CEntry >::
SeekGE( const CKey& key ) const
{
const size_t ikeyentry = (this->*m_pfnSearch)( key, fTrue );
if ( ikeyentry > 0 &&
_Entry( ikeyentry - 1 ).Cmp( key ) == 0 )
{
return Entry( ikeyentry - 1 );
}
else
{
return Entry( ikeyentry );
}
}
template< class CKey, class CEntry >
inline const CEntry* CTable< CKey, CEntry >::
SeekGT( const CKey& key ) const
{
return Entry( (this->*m_pfnSearch)( key, fTrue ) );
}
template< class CKey, class CEntry >
inline const CTable< CKey, CEntry >::CKeyEntry& CTable< CKey, CEntry >::
_Entry( const size_t ikeyentry ) const
{
return *( m_arrayKeyEntry.Entry( ikeyentry ) );
}
template< class CKey, class CEntry >
inline void CTable< CKey, CEntry >::
_SetEntry( const size_t ikeyentry, const CKeyEntry& keyentry )
{
m_arrayKeyEntry.SetEntry( m_arrayKeyEntry.Entry( ikeyentry ), keyentry );
}
template< class CKey, class CEntry >
inline void CTable< CKey, CEntry >::
_SwapEntry( const size_t ikeyentry1, const size_t ikeyentry2 )
{
CKeyEntry keyentryT;
keyentryT = _Entry( ikeyentry1 );
_SetEntry( ikeyentry1, _Entry( ikeyentry2 ) );
_SetEntry( ikeyentry2, keyentryT );
}
template< class CKey, class CEntry >
inline size_t CTable< CKey, CEntry >::
_LinearSearch( const CKey& key, const BOOL fHigh ) const
{
for ( size_t ikeyentry = 0; ikeyentry < Size(); ikeyentry++ )
{
const int cmp = _Entry( ikeyentry ).Cmp( key );
if ( !( cmp < 0 || cmp == 0 && fHigh ) )
{
break;
}
}
return ikeyentry;
}
template< class CKey, class CEntry >
inline size_t CTable< CKey, CEntry >::
_BinarySearch( const CKey& key, const BOOL fHigh ) const
{
size_t ikeyentryMin = 0;
size_t ikeyentryMax = Size();
while ( ikeyentryMin < ikeyentryMax )
{
const size_t ikeyentryMid = ikeyentryMin + ( ikeyentryMax - ikeyentryMin ) / 2;
const int cmp = _Entry( ikeyentryMid ).Cmp( key );
if ( cmp < 0 || cmp == 0 && fHigh )
{
ikeyentryMin = ikeyentryMid + 1;
}
else
{
ikeyentryMax = ikeyentryMid;
}
}
return ikeyentryMax;
}
template< class CKey, class CEntry >
inline void CTable< CKey, CEntry >::
_InsertionSort( const size_t ikeyentryMinIn, const size_t ikeyentryMaxIn )
{
size_t ikeyentryLast;
size_t ikeyentryFirst;
CKeyEntry keyentryKey;
for ( ikeyentryFirst = ikeyentryMinIn, ikeyentryLast = ikeyentryMinIn + 1;
ikeyentryLast < ikeyentryMaxIn;
ikeyentryFirst = ikeyentryLast++ )
{
if ( _Entry( ikeyentryFirst ).Cmp( _Entry( ikeyentryLast ) ) > 0 )
{
keyentryKey = _Entry( ikeyentryLast );
_SetEntry( ikeyentryLast, _Entry( ikeyentryFirst ) );
while ( ikeyentryFirst-- >= ikeyentryMinIn + 1 &&
_Entry( ikeyentryFirst ).Cmp( keyentryKey ) > 0 )
{
_SetEntry( ikeyentryFirst + 1, _Entry( ikeyentryFirst ) );
}
_SetEntry( ikeyentryFirst + 1, keyentryKey );
}
}
}
template< class CKey, class CEntry >
inline void CTable< CKey, CEntry >::
_QuickSort( const size_t ikeyentryMinIn, const size_t ikeyentryMaxIn )
{
// quicksort cutoff
const size_t ckeyentryMin = 32;
// partition stack (used to reduce levels of recursion)
const size_t cpartMax = 16;
size_t cpart = 0;
struct
{
size_t ikeyentryMin;
size_t ikeyentryMax;
} rgpart[ cpartMax ];
// current partition = partition passed in arguments
size_t ikeyentryMin = ikeyentryMinIn;
size_t ikeyentryMax = ikeyentryMaxIn;
// _QuickSort current partition
for ( ; ; )
{
// if this partition is small enough, insertion sort it
if ( ikeyentryMax - ikeyentryMin < ckeyentryMin )
{
_InsertionSort( ikeyentryMin, ikeyentryMax );
// if there are no more partitions to sort, we're done
if ( !cpart )
{
break;
}
// pop a partition off the stack and make it the current partition
ikeyentryMin = rgpart[ --cpart ].ikeyentryMin;
ikeyentryMax = rgpart[ cpart ].ikeyentryMax;
continue;
}
// determine divisor by sorting the first, middle, and last entries and
// taking the resulting middle entry as the divisor
size_t ikeyentryFirst = ikeyentryMin;
size_t ikeyentryMid = ikeyentryMin + ( ikeyentryMax - ikeyentryMin ) / 2;
size_t ikeyentryLast = ikeyentryMax - 1;
if ( _Entry( ikeyentryFirst ).Cmp( _Entry( ikeyentryMid ) ) > 0 )
{
_SwapEntry( ikeyentryFirst, ikeyentryMid );
}
if ( _Entry( ikeyentryFirst ).Cmp( _Entry( ikeyentryLast ) ) > 0 )
{
_SwapEntry( ikeyentryFirst, ikeyentryLast );
}
if ( _Entry( ikeyentryMid ).Cmp( _Entry( ikeyentryLast ) ) > 0 )
{
_SwapEntry( ikeyentryMid, ikeyentryLast );
}
// sort large partition into two smaller partitions (<=, >)
do {
// advance past all entries <= the divisor
while ( ikeyentryFirst <= ikeyentryLast &&
_Entry( ikeyentryFirst ).Cmp( _Entry( ikeyentryMin ) ) <= 0 )
{
ikeyentryFirst++;
}
// advance past all entries > the divisor
while ( ikeyentryFirst <= ikeyentryLast &&
_Entry( ikeyentryLast ).Cmp( _Entry( ikeyentryMin ) ) > 0 )
{
ikeyentryLast--;
}
// if we have found a pair to swap, swap them and continue
if ( ikeyentryFirst < ikeyentryLast )
{
_SwapEntry( ikeyentryFirst++, ikeyentryLast-- );
}
}
while ( ikeyentryFirst <= ikeyentryLast );
// move the divisor to the end of the <= partition
_SwapEntry( ikeyentryMin, ikeyentryLast );
// determine the limits of the smaller and larger sub-partitions
size_t ikeyentrySmallMin;
size_t ikeyentrySmallMax;
size_t ikeyentryLargeMin;
size_t ikeyentryLargeMax;
if ( ikeyentryMax - ikeyentryFirst == 0 )
{
ikeyentryLargeMin = ikeyentryMin;
ikeyentryLargeMax = ikeyentryLast;
ikeyentrySmallMin = ikeyentryLast;
ikeyentrySmallMax = ikeyentryMax;
}
else if ( ikeyentryMax - ikeyentryFirst > ikeyentryFirst - ikeyentryMin )
{
ikeyentrySmallMin = ikeyentryMin;
ikeyentrySmallMax = ikeyentryFirst;
ikeyentryLargeMin = ikeyentryFirst;
ikeyentryLargeMax = ikeyentryMax;
}
else
{
ikeyentryLargeMin = ikeyentryMin;
ikeyentryLargeMax = ikeyentryFirst;
ikeyentrySmallMin = ikeyentryFirst;
ikeyentrySmallMax = ikeyentryMax;
}
// push the larger sub-partition or recurse if the stack is full
if ( cpart < cpartMax )
{
rgpart[ cpart ].ikeyentryMin = ikeyentryLargeMin;
rgpart[ cpart++ ].ikeyentryMax = ikeyentryLargeMax;
}
else
{
_QuickSort( ikeyentryLargeMin, ikeyentryLargeMax );
}
// set our current partition to be the smaller sub-partition
ikeyentryMin = ikeyentrySmallMin;
ikeyentryMax = ikeyentrySmallMax;
}
}
}; // namespace COLL
using namespace COLL;
#endif // _COLLECTION_HXX_INCLUDED