mirror of https://github.com/tongzx/nt5src
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
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
|
|
|
|
|