|
|
#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 __TYPENAME 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:
typename 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 );
#ifdef DEBUGGER_EXTENSION
VOID Dump( CPRINTF * pcprintf, const DWORD_PTR dwOffset = 0 ) const; VOID Scan( CPRINTF * pcprintf, VOID * pv ) const { m_bt.Scan( pcprintf, pv ); } #endif
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 >;
typename 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 __TYPENAME 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 __TYPENAME 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 __TYPENAME 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 __TYPENAME 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 __TYPENAME 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 __TYPENAME 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 __TYPENAME 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 __TYPENAME 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 __TYPENAME 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 __TYPENAME 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 __TYPENAME 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 __TYPENAME 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 __TYPENAME 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 __TYPENAME 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:
typename 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, const BOOL fMRU = fTrue ); ERR ErrRemove( CObject** const ppobj, const BOOL fWait = fTrue, const BOOL fMRU = 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 __TYPENAME 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, const BOOL fMRU ) { // 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(); }
if ( !ibucket && fMRU ) { pbucket->m_il.InsertAsPrevMost( pobj ); } else { 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 __TYPENAME CPool< CObject, OffsetOfIC >::ERR CPool< CObject, OffsetOfIC >:: ErrRemove( CObject** const ppobj, const BOOL fWait, const BOOL fMRU ) { // 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() ) { if ( !ibucket && fMRU ) { *ppobj = pbucket->m_il.PrevMost(); } else { *ppobj = pbucket->m_il.NextMost(); } 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 __TYPENAME 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 __TYPENAME 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 __TYPENAME 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 __TYPENAME 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 __TYPENAME 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 __TYPENAME 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 __TYPENAME 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 __TYPENAME 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 __TYPENAME 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
|