644 lines
16 KiB
C++
644 lines
16 KiB
C++
// Copyright (C) 2007 Id Software, Inc.
|
|
//
|
|
|
|
#ifndef __HASHINDEX_H__
|
|
#define __HASHINDEX_H__
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
Fast hash table for indexes and arrays.
|
|
Does not allocate memory until the first key/index pair is added.
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
template< class type, int Max, int NullIndex, int DEFAULT_HASH_SIZE, int DEFAULT_HASH_GRANULARITY >
|
|
class idHashIndexBase {
|
|
public:
|
|
static const int NULL_INDEX = NullIndex;
|
|
typedef type Type;
|
|
|
|
idHashIndexBase( void );
|
|
idHashIndexBase( const int initialHashSize, const int initialIndexSize );
|
|
~idHashIndexBase( void );
|
|
|
|
// returns total size of allocated memory
|
|
size_t Allocated( void ) const;
|
|
size_t ReducedAllocated( void ) const;
|
|
// returns total size of allocated memory including size of hash index type
|
|
size_t Size( void ) const;
|
|
|
|
idHashIndexBase & operator=( const idHashIndexBase &other );
|
|
// add an index to the hash, assumes the index has not yet been added to the hash
|
|
void Add( const int key, const int index );
|
|
// remove an index from the hash
|
|
void Remove( const int key, const int index );
|
|
// get the first index from the hash, returns NULL_INDEX if empty hash entry
|
|
int GetFirst( const int key ) const;
|
|
// get the next index from the hash, returns NULL_INDEX if at the end of the hash chain
|
|
int GetNext( const int index ) const;
|
|
// insert an entry into the index and add it to the hash, increasing all indexes >= index
|
|
void InsertIndex( const int key, const int index );
|
|
// remove an entry from the index and remove it from the hash, decreasing all indexes >= index
|
|
void RemoveIndex( const int key, const int index );
|
|
// clear the hash
|
|
void Clear( void );
|
|
// clear and resize
|
|
void Clear( const int newHashSize, const int newIndexSize );
|
|
// free allocated memory
|
|
void Free( void );
|
|
// allocate memory, only used internally
|
|
void Allocate( const int newHashSize, const int newIndexSize );
|
|
// get size of hash table
|
|
int GetHashSize( void ) const;
|
|
// get size of the index
|
|
int GetIndexSize( void ) const;
|
|
// set granularity
|
|
void SetGranularity( const int newGranularity );
|
|
// force resizing the index, current hash table stays intact
|
|
void ResizeIndex( const int newIndexSize );
|
|
// returns number in the range [0-100] representing the spread over the hash table
|
|
int GetSpread( void ) const;
|
|
// returns a key for a string
|
|
int GenerateKey( const char *string, bool caseSensitive = true ) const;
|
|
// returns a key for a vector
|
|
int GenerateKey( const idVec3 &v ) const;
|
|
// returns a key for two integers
|
|
int GenerateKey( const unsigned int n1, const unsigned int n2 ) const;
|
|
// returns a key for a single integer
|
|
int GenerateKey( const int n ) const;
|
|
// returns a key a pointer type
|
|
int GenerateKey( const void* n ) const;
|
|
|
|
// returns a key for a file name
|
|
int GenerateKeyForFileName( const char *string ) const;
|
|
|
|
void Swap( idHashIndexBase &rhs );
|
|
|
|
bool Verify( void );
|
|
|
|
void Write( idFile* file ) const;
|
|
void Read( idFile* file );
|
|
|
|
public:
|
|
void Init( const int initialHashSize, const int initialIndexSize );
|
|
|
|
private:
|
|
int hashSize;
|
|
Type* hash;
|
|
int indexSize;
|
|
Type* indexChain;
|
|
int granularity;
|
|
int hashMask;
|
|
|
|
void CheckBounds() {
|
|
assert( hashSize < Max && indexSize < Max && "size too big" );
|
|
}
|
|
};
|
|
|
|
#define HASHINDEX_TEMPLATE_HEADER template< class type, int Max, int NullIndex, int DEFAULT_HASH_SIZE, int DEFAULT_HASH_GRANULARITY >
|
|
#define HASHINDEX_TEMPLATE_TAG idHashIndexBase< type, Max, NullIndex, DEFAULT_HASH_SIZE, DEFAULT_HASH_GRANULARITY >
|
|
|
|
/*
|
|
================
|
|
idHashIndexBase::idHashIndexBase
|
|
================
|
|
*/
|
|
HASHINDEX_TEMPLATE_HEADER
|
|
ID_INLINE HASHINDEX_TEMPLATE_TAG::idHashIndexBase( void ) {
|
|
Init( DEFAULT_HASH_SIZE, 0 );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idHashIndexBase::idHashIndexBase
|
|
================
|
|
*/
|
|
HASHINDEX_TEMPLATE_HEADER
|
|
ID_INLINE HASHINDEX_TEMPLATE_TAG::idHashIndexBase( const int initialHashSize, const int initialIndexSize ) {
|
|
Init( initialHashSize, initialIndexSize );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idHashIndexBase::~idHashIndexBase
|
|
================
|
|
*/
|
|
HASHINDEX_TEMPLATE_HEADER
|
|
ID_INLINE HASHINDEX_TEMPLATE_TAG::~idHashIndexBase( void ) {
|
|
Free();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idHashIndexBase::Init
|
|
================
|
|
*/
|
|
HASHINDEX_TEMPLATE_HEADER
|
|
ID_INLINE void HASHINDEX_TEMPLATE_TAG::Init( const int initialHashSize, const int initialIndexSize ) {
|
|
assert( idMath::IsPowerOfTwo( initialHashSize ) );
|
|
|
|
hashSize = initialHashSize;
|
|
hash = NULL;
|
|
indexSize = initialIndexSize;
|
|
indexChain = NULL;
|
|
CheckBounds();
|
|
granularity = DEFAULT_HASH_GRANULARITY;
|
|
hashMask = hashSize - 1;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idHashIndexBase::Allocate
|
|
================
|
|
*/
|
|
HASHINDEX_TEMPLATE_HEADER
|
|
ID_INLINE void HASHINDEX_TEMPLATE_TAG::Allocate( const int newHashSize, const int newIndexSize ) {
|
|
assert( idMath::IsPowerOfTwo( newHashSize ) );
|
|
|
|
Free();
|
|
hashSize = newHashSize;
|
|
hash = new type[hashSize];
|
|
memset( hash, 0xff, hashSize * sizeof( hash[0] ) );
|
|
indexSize = newIndexSize;
|
|
indexChain = new type[indexSize];
|
|
CheckBounds();
|
|
memset( indexChain, 0xff, indexSize * sizeof( indexChain[0] ) );
|
|
hashMask = hashSize - 1;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idHashIndexBase::Free
|
|
============
|
|
*/
|
|
HASHINDEX_TEMPLATE_HEADER
|
|
ID_INLINE void HASHINDEX_TEMPLATE_TAG::Free( void ) {
|
|
delete[] hash;
|
|
hash = NULL;
|
|
delete[] indexChain;
|
|
indexChain = NULL;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idHashIndexBase::ResizeIndex
|
|
================
|
|
*/
|
|
HASHINDEX_TEMPLATE_HEADER
|
|
ID_INLINE void HASHINDEX_TEMPLATE_TAG::ResizeIndex( const int newIndexSize ) {
|
|
type *oldIndexChain;
|
|
int mod, newSize;
|
|
|
|
if ( newIndexSize <= indexSize ) {
|
|
return;
|
|
}
|
|
|
|
mod = newIndexSize % granularity;
|
|
if ( !mod ) {
|
|
newSize = newIndexSize;
|
|
} else {
|
|
newSize = newIndexSize + granularity - mod;
|
|
}
|
|
|
|
if ( indexChain == NULL ) {
|
|
indexSize = newSize;
|
|
return;
|
|
}
|
|
|
|
oldIndexChain = indexChain;
|
|
indexChain = new type[newSize];
|
|
memcpy( indexChain, oldIndexChain, indexSize * sizeof( indexChain[0] ) );
|
|
memset( indexChain + indexSize, 0xff, (newSize - indexSize) * sizeof( indexChain[0] ) );
|
|
delete[] oldIndexChain;
|
|
indexSize = newSize;
|
|
CheckBounds();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idHashIndexBase::GetSpread
|
|
================
|
|
*/
|
|
HASHINDEX_TEMPLATE_HEADER
|
|
ID_INLINE int HASHINDEX_TEMPLATE_TAG::GetSpread( void ) const {
|
|
int i, index, totalItems, average, error, e;
|
|
type *numHashItems;
|
|
|
|
if ( hash == NULL ) {
|
|
return 100;
|
|
}
|
|
|
|
totalItems = 0;
|
|
numHashItems = new type[hashSize];
|
|
for ( i = 0; i < hashSize; i++ ) {
|
|
numHashItems[i] = 0;
|
|
for ( index = hash[i]; index >= 0; index = indexChain[index] ) {
|
|
numHashItems[i]++;
|
|
}
|
|
totalItems += numHashItems[i];
|
|
}
|
|
// if no items in hash
|
|
if ( totalItems <= 1 ) {
|
|
delete[] numHashItems;
|
|
return 100;
|
|
}
|
|
average = totalItems / hashSize;
|
|
error = 0;
|
|
for ( i = 0; i < hashSize; i++ ) {
|
|
e = idMath::Abs( numHashItems[i] - average );
|
|
if ( e > 1 ) {
|
|
error += e - 1;
|
|
}
|
|
}
|
|
delete[] numHashItems;
|
|
return 100 - (error * 100 / totalItems);
|
|
}
|
|
|
|
/*
|
|
================
|
|
idHashIndexBase::Add
|
|
================
|
|
*/
|
|
HASHINDEX_TEMPLATE_HEADER
|
|
ID_INLINE void HASHINDEX_TEMPLATE_TAG::Add( const int key, const int index ) {
|
|
int h;
|
|
|
|
assert( index >= 0 );
|
|
if ( hash == NULL ) {
|
|
int size = ( index >= indexSize ? index + 1 : indexSize ) + granularity - 1;
|
|
Allocate( hashSize, size - size % granularity );
|
|
} else if ( index >= indexSize ) {
|
|
ResizeIndex( index + 1 );
|
|
}
|
|
|
|
h = key & hashMask;
|
|
|
|
#ifdef _DEBUG
|
|
int oldChain = indexChain[ index ];
|
|
int oldHash = hash[ h ];
|
|
#endif
|
|
assert( oldChain == NULL_INDEX && oldHash != index );
|
|
|
|
indexChain[ index ] = hash[h];
|
|
hash[ h ] = index;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idHashIndexBase::Allocated
|
|
================
|
|
*/
|
|
HASHINDEX_TEMPLATE_HEADER
|
|
ID_INLINE size_t HASHINDEX_TEMPLATE_TAG::Allocated( void ) const {
|
|
size_t total = 0;
|
|
if( hash ) {
|
|
total += hashSize * sizeof( hash[ 0 ] );
|
|
}
|
|
if(indexChain) {
|
|
total += indexSize * sizeof( indexChain[ 0 ] );
|
|
}
|
|
return total;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idHashIndexBase::Size
|
|
================
|
|
*/
|
|
HASHINDEX_TEMPLATE_HEADER
|
|
ID_INLINE size_t HASHINDEX_TEMPLATE_TAG::Size( void ) const {
|
|
return sizeof( *this ) + Allocated();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idHashIndexBase::operator=
|
|
================
|
|
*/
|
|
HASHINDEX_TEMPLATE_HEADER
|
|
ID_INLINE HASHINDEX_TEMPLATE_TAG& HASHINDEX_TEMPLATE_TAG::operator=( const idHashIndexBase &other ) {
|
|
if( &other == this ) {
|
|
return *this;
|
|
}
|
|
|
|
granularity = other.granularity;
|
|
hashMask = other.hashMask;
|
|
|
|
delete[] hash;
|
|
hash = NULL;
|
|
|
|
delete[] indexChain;
|
|
indexChain = NULL;
|
|
|
|
hashSize = other.hashSize;
|
|
indexSize = other.indexSize;
|
|
CheckBounds();
|
|
|
|
if ( other.hash ) {
|
|
hash = new type[hashSize];
|
|
memcpy( hash, other.hash, hashSize * sizeof( hash[0] ) );
|
|
}
|
|
if ( other.indexChain ) {
|
|
indexChain = new type[indexSize];
|
|
memcpy( indexChain, other.indexChain, indexSize * sizeof( indexChain[0] ) );
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idHashIndexBase::Remove
|
|
================
|
|
*/
|
|
HASHINDEX_TEMPLATE_HEADER
|
|
ID_INLINE void HASHINDEX_TEMPLATE_TAG::Remove( const int key, const int index ) {
|
|
int k = key & hashMask;
|
|
|
|
if ( hash == NULL ) {
|
|
return;
|
|
}
|
|
if ( hash[k] == index ) {
|
|
hash[k] = indexChain[index];
|
|
}
|
|
else {
|
|
for ( int i = hash[k]; i != NULL_INDEX; i = indexChain[i] ) {
|
|
if ( indexChain[i] == index ) {
|
|
indexChain[i] = indexChain[index];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
indexChain[index] = NULL_INDEX;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idHashIndexBase::GetFirst
|
|
================
|
|
*/
|
|
HASHINDEX_TEMPLATE_HEADER
|
|
ID_INLINE int HASHINDEX_TEMPLATE_TAG::GetFirst( const int key ) const {
|
|
return hash ? hash[ key & hashMask ] : NULL_INDEX;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idHashIndexBase::GetNext
|
|
================
|
|
*/
|
|
HASHINDEX_TEMPLATE_HEADER
|
|
ID_INLINE int HASHINDEX_TEMPLATE_TAG::GetNext( const int index ) const {
|
|
assert( index >= 0 && index < indexSize );
|
|
return indexChain ? indexChain[ index ] : NULL_INDEX;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idHashIndexBase::InsertIndex
|
|
================
|
|
*/
|
|
HASHINDEX_TEMPLATE_HEADER
|
|
ID_INLINE void HASHINDEX_TEMPLATE_TAG::InsertIndex( const int key, const int index ) {
|
|
int i, max;
|
|
|
|
if ( hash != NULL ) {
|
|
max = index;
|
|
for ( i = 0; i < hashSize; i++ ) {
|
|
if ( hash[i] >= index ) {
|
|
hash[i]++;
|
|
if ( hash[i] > max ) {
|
|
max = hash[i];
|
|
}
|
|
}
|
|
}
|
|
for ( i = 0; i < indexSize; i++ ) {
|
|
if ( indexChain[i] >= index ) {
|
|
indexChain[i]++;
|
|
if ( indexChain[i] > max ) {
|
|
max = indexChain[i];
|
|
}
|
|
}
|
|
}
|
|
if ( max >= indexSize ) {
|
|
ResizeIndex( max + 1 );
|
|
}
|
|
for ( i = max; i > index; i-- ) {
|
|
indexChain[i] = indexChain[i-1];
|
|
}
|
|
indexChain[index] = NULL_INDEX;
|
|
}
|
|
Add( key, index );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idHashIndexBase::RemoveIndex
|
|
================
|
|
*/
|
|
HASHINDEX_TEMPLATE_HEADER
|
|
ID_INLINE void HASHINDEX_TEMPLATE_TAG::RemoveIndex( const int key, const int index ) {
|
|
int i, max;
|
|
|
|
Remove( key, index );
|
|
if ( hash != NULL ) {
|
|
max = index;
|
|
for ( i = 0; i < hashSize; i++ ) {
|
|
if ( hash[i] >= index ) {
|
|
if ( hash[i] > max ) {
|
|
max = hash[i];
|
|
}
|
|
hash[i]--;
|
|
}
|
|
}
|
|
for ( i = 0; i < indexSize; i++ ) {
|
|
if ( indexChain[i] >= index ) {
|
|
if ( indexChain[i] > max ) {
|
|
max = indexChain[i];
|
|
}
|
|
indexChain[i]--;
|
|
}
|
|
}
|
|
for ( i = index; i < max; i++ ) {
|
|
indexChain[i] = indexChain[i+1];
|
|
}
|
|
indexChain[max] = NULL_INDEX;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idHashIndexBase::Clear
|
|
================
|
|
*/
|
|
HASHINDEX_TEMPLATE_HEADER
|
|
ID_INLINE void HASHINDEX_TEMPLATE_TAG::Clear( void ) {
|
|
if ( hash != NULL ) {
|
|
memset( hash, 0xff, hashSize * sizeof( hash[0] ) );
|
|
}
|
|
if ( indexChain != NULL ) {
|
|
memset( indexChain, 0xff, indexSize * sizeof( indexChain[0] ) );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idHashIndexBase::Clear
|
|
================
|
|
*/
|
|
HASHINDEX_TEMPLATE_HEADER
|
|
ID_INLINE void HASHINDEX_TEMPLATE_TAG::Clear( const int newHashSize, const int newIndexSize ) {
|
|
Free();
|
|
hashSize = newHashSize;
|
|
indexSize = newIndexSize;
|
|
CheckBounds();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idHashIndexBase::GetHashSize
|
|
================
|
|
*/
|
|
HASHINDEX_TEMPLATE_HEADER
|
|
ID_INLINE int HASHINDEX_TEMPLATE_TAG::GetHashSize( void ) const {
|
|
return hashSize;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idHashIndexBase::GetIndexSize
|
|
================
|
|
*/
|
|
HASHINDEX_TEMPLATE_HEADER
|
|
ID_INLINE int HASHINDEX_TEMPLATE_TAG::GetIndexSize( void ) const {
|
|
return indexSize;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idHashIndexBase::SetGranularity
|
|
================
|
|
*/
|
|
HASHINDEX_TEMPLATE_HEADER
|
|
ID_INLINE void HASHINDEX_TEMPLATE_TAG::SetGranularity( const int newGranularity ) {
|
|
assert( newGranularity > 0 );
|
|
granularity = newGranularity;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idHashIndexBase::GenerateKey
|
|
================
|
|
*/
|
|
HASHINDEX_TEMPLATE_HEADER
|
|
ID_INLINE int HASHINDEX_TEMPLATE_TAG::GenerateKey( const char *string, bool caseSensitive ) const {
|
|
if ( caseSensitive ) {
|
|
return ( idStr::Hash( string ) & hashMask );
|
|
} else {
|
|
return ( idStr::IHash( string ) & hashMask );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idHashIndexBase::GenerateKey
|
|
================
|
|
*/
|
|
HASHINDEX_TEMPLATE_HEADER
|
|
ID_INLINE int HASHINDEX_TEMPLATE_TAG::GenerateKey( const idVec3 &v ) const {
|
|
return ( ( idMath::Ftoi( v[0] ) + idMath::Ftoi( v[1] ) + idMath::Ftoi( v[2] ) ) & hashMask );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idHashIndexBase::GenerateKey
|
|
================
|
|
*/
|
|
HASHINDEX_TEMPLATE_HEADER
|
|
ID_INLINE int HASHINDEX_TEMPLATE_TAG::GenerateKey( const unsigned int n1, const unsigned int n2 ) const {
|
|
return ( ( n1 + n2 ) & hashMask );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idHashIndexBase::GenerateKey
|
|
================
|
|
*/
|
|
HASHINDEX_TEMPLATE_HEADER
|
|
ID_INLINE int HASHINDEX_TEMPLATE_TAG::GenerateKey( const int n ) const {
|
|
return n & hashMask;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idHashIndexBase::GenerateKey
|
|
================
|
|
*/
|
|
HASHINDEX_TEMPLATE_HEADER
|
|
ID_INLINE int HASHINDEX_TEMPLATE_TAG::GenerateKey( const void* n ) const {
|
|
return ( ( ( UINT_PTR ) n ) & hashMask );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idHashIndexBase::GenerateKeyForFileName
|
|
================
|
|
*/
|
|
HASHINDEX_TEMPLATE_HEADER
|
|
ID_INLINE int HASHINDEX_TEMPLATE_TAG::GenerateKeyForFileName( const char *string ) const {
|
|
return idStr::FileNameHash( string, hashSize );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idHashIndexBase::Verify
|
|
================
|
|
*/
|
|
HASHINDEX_TEMPLATE_HEADER
|
|
ID_INLINE bool HASHINDEX_TEMPLATE_TAG::Verify( void ) {
|
|
int i, j;
|
|
int c;
|
|
int count = GetHashSize();
|
|
for ( i = 0; i < count; i++ ) {
|
|
for ( c = GetFirst( i ), j = 0; c != NULL_INDEX && j < GetIndexSize(); j++, c = GetNext( c ) ) {
|
|
}
|
|
if ( j == GetIndexSize() ) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idHashIndexBase::Swap
|
|
================
|
|
*/
|
|
HASHINDEX_TEMPLATE_HEADER
|
|
ID_INLINE void HASHINDEX_TEMPLATE_TAG::Swap( idHashIndexBase &rhs ) {
|
|
idSwap( hashSize, rhs.hashSize );
|
|
idSwap( hash, rhs.hash );
|
|
idSwap( indexSize, rhs.indexSize );
|
|
idSwap( indexChain, rhs.indexChain );
|
|
idSwap( granularity, rhs.granularity );
|
|
idSwap( hashMask, rhs.hashMask );
|
|
}
|
|
|
|
// Read and Write are in HashIndexImpl.h to avoid dependency issues
|
|
|
|
#undef HASHINDEX_TEMPLATE_HEADER
|
|
#undef HASHINDEX_TEMPLATE_TAG
|
|
|
|
typedef idHashIndexBase< short, SHRT_MAX, -1, 256, 256 > idHashIndexShort;
|
|
typedef idHashIndexBase< unsigned short, USHRT_MAX - 1, USHRT_MAX, 256, 256 > idHashIndexUShort;
|
|
typedef idHashIndexBase< int, INT_MAX, -1, 1024, 1024 > idHashIndexInt;
|
|
|
|
#ifdef SD_USE_HASHINDEX_16
|
|
typedef idHashIndexShort idHashIndex;
|
|
#else
|
|
typedef idHashIndexInt idHashIndex;
|
|
#endif
|
|
|
|
#endif /* !__HASHINDEX_H__ */
|