include/foundation/PxHashInternals.h
File members: include/foundation/PxHashInternals.h
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of NVIDIA CORPORATION nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Copyright (c) 2008-2024 NVIDIA Corporation. All rights reserved.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
#ifndef PX_HASH_INTERNALS_H
#define PX_HASH_INTERNALS_H
#include "foundation/PxAllocator.h"
#include "foundation/PxBitUtils.h"
#include "foundation/PxMathIntrinsics.h"
#include "foundation/PxBasicTemplates.h"
#include "foundation/PxHash.h"
#if PX_VC
#pragma warning(push)
#pragma warning(disable : 4127) // conditional expression is constant
#endif
#if !PX_DOXYGEN
namespace physx
{
#endif
template <class Entry, class Key, class HashFn, class GetKey, class PxAllocator, bool compacting>
class PxHashBase : private PxAllocator
{
void init(uint32_t initialTableSize, float loadFactor)
{
mBuffer = NULL;
mEntries = NULL;
mEntriesNext = NULL;
mHash = NULL;
mEntriesCapacity = 0;
mHashSize = 0;
mLoadFactor = loadFactor;
mFreeList = uint32_t(EOL);
mTimestamp = 0;
mEntriesCount = 0;
if(initialTableSize)
reserveInternal(initialTableSize);
}
public:
typedef Entry EntryType;
PxHashBase(uint32_t initialTableSize = 64, float loadFactor = 0.75f) : PxAllocator("hashBase")
{
init(initialTableSize, loadFactor);
}
PxHashBase(uint32_t initialTableSize, float loadFactor, const PxAllocator& alloc) : PxAllocator(alloc)
{
init(initialTableSize, loadFactor);
}
PxHashBase(const PxAllocator& alloc) : PxAllocator(alloc)
{
init(64, 0.75f);
}
~PxHashBase()
{
destroy(); // No need to clear()
if(mBuffer)
PxAllocator::deallocate(mBuffer);
}
static const uint32_t EOL = 0xffffffff;
PX_INLINE Entry* create(const Key& k, bool& exists)
{
uint32_t h = 0;
if(mHashSize)
{
h = hash(k);
uint32_t index = mHash[h];
while(index != EOL && !HashFn().equal(GetKey()(mEntries[index]), k))
index = mEntriesNext[index];
exists = index != EOL;
if(exists)
return mEntries + index;
}
else
exists = false;
if(freeListEmpty())
{
grow();
h = hash(k);
}
uint32_t entryIndex = freeListGetNext();
mEntriesNext[entryIndex] = mHash[h];
mHash[h] = entryIndex;
mEntriesCount++;
mTimestamp++;
return mEntries + entryIndex;
}
PX_INLINE const Entry* find(const Key& k) const
{
if(!mEntriesCount)
return NULL;
const uint32_t h = hash(k);
uint32_t index = mHash[h];
while(index != EOL && !HashFn().equal(GetKey()(mEntries[index]), k))
index = mEntriesNext[index];
return index != EOL ? mEntries + index : NULL;
}
PX_INLINE bool erase(const Key& k, Entry& e)
{
if(!mEntriesCount)
return false;
const uint32_t h = hash(k);
uint32_t* ptr = mHash + h;
while(*ptr != EOL && !HashFn().equal(GetKey()(mEntries[*ptr]), k))
ptr = mEntriesNext + *ptr;
if(*ptr == EOL)
return false;
PX_PLACEMENT_NEW(&e, Entry)(mEntries[*ptr]);
return eraseInternal(ptr);
}
PX_INLINE bool erase(const Key& k)
{
if(!mEntriesCount)
return false;
const uint32_t h = hash(k);
uint32_t* ptr = mHash + h;
while(*ptr != EOL && !HashFn().equal(GetKey()(mEntries[*ptr]), k))
ptr = mEntriesNext + *ptr;
if(*ptr == EOL)
return false;
return eraseInternal(ptr);
}
PX_INLINE uint32_t size() const
{
return mEntriesCount;
}
PX_INLINE uint32_t capacity() const
{
return mHashSize;
}
void clear()
{
if(!mHashSize || mEntriesCount == 0)
return;
destroy();
intrinsics::memSet(mHash, EOL, mHashSize * sizeof(uint32_t));
const uint32_t sizeMinus1 = mEntriesCapacity - 1;
for(uint32_t i = 0; i < sizeMinus1; i++)
{
PxPrefetchLine(mEntriesNext + i, 128);
mEntriesNext[i] = i + 1;
}
mEntriesNext[mEntriesCapacity - 1] = uint32_t(EOL);
mFreeList = 0;
mEntriesCount = 0;
}
void reserve(uint32_t size)
{
if(size > mHashSize)
reserveInternal(size);
}
PX_INLINE const Entry* getEntries() const
{
return mEntries;
}
PX_INLINE Entry* insertUnique(const Key& k)
{
PX_ASSERT(find(k) == NULL);
uint32_t h = hash(k);
uint32_t entryIndex = freeListGetNext();
mEntriesNext[entryIndex] = mHash[h];
mHash[h] = entryIndex;
mEntriesCount++;
mTimestamp++;
return mEntries + entryIndex;
}
private:
void destroy()
{
for(uint32_t i = 0; i < mHashSize; i++)
{
for(uint32_t j = mHash[i]; j != EOL; j = mEntriesNext[j])
mEntries[j].~Entry();
}
}
template <typename HK, typename GK, class A, bool comp>
PX_NOINLINE void copy(const PxHashBase<Entry, Key, HK, GK, A, comp>& other);
// free list management - if we're coalescing, then we use mFreeList to hold
// the top of the free list and it should always be equal to size(). Otherwise,
// we build a free list in the next() pointers.
PX_INLINE void freeListAdd(uint32_t index)
{
if(compacting)
{
mFreeList--;
PX_ASSERT(mFreeList == mEntriesCount);
}
else
{
mEntriesNext[index] = mFreeList;
mFreeList = index;
}
}
PX_INLINE void freeListAdd(uint32_t start, uint32_t end)
{
if(!compacting)
{
for(uint32_t i = start; i < end - 1; i++) // add the new entries to the free list
mEntriesNext[i] = i + 1;
// link in old free list
mEntriesNext[end - 1] = mFreeList;
PX_ASSERT(mFreeList != end - 1);
mFreeList = start;
}
else if(mFreeList == EOL) // don't reset the free ptr for the compacting hash unless it's empty
mFreeList = start;
}
PX_INLINE uint32_t freeListGetNext()
{
PX_ASSERT(!freeListEmpty());
if(compacting)
{
PX_ASSERT(mFreeList == mEntriesCount);
return mFreeList++;
}
else
{
uint32_t entryIndex = mFreeList;
mFreeList = mEntriesNext[mFreeList];
return entryIndex;
}
}
PX_INLINE bool freeListEmpty() const
{
if(compacting)
return mEntriesCount == mEntriesCapacity;
else
return mFreeList == EOL;
}
PX_INLINE void replaceWithLast(uint32_t index)
{
PX_PLACEMENT_NEW(mEntries + index, Entry)(mEntries[mEntriesCount]);
mEntries[mEntriesCount].~Entry();
mEntriesNext[index] = mEntriesNext[mEntriesCount];
uint32_t h = hash(GetKey()(mEntries[index]));
uint32_t* ptr;
for(ptr = mHash + h; *ptr != mEntriesCount; ptr = mEntriesNext + *ptr)
PX_ASSERT(*ptr != EOL);
*ptr = index;
}
PX_INLINE uint32_t hash(const Key& k, uint32_t hashSize) const
{
return HashFn()(k) & (hashSize - 1);
}
PX_INLINE uint32_t hash(const Key& k) const
{
return hash(k, mHashSize);
}
PX_INLINE bool eraseInternal(uint32_t* ptr)
{
const uint32_t index = *ptr;
*ptr = mEntriesNext[index];
mEntries[index].~Entry();
mEntriesCount--;
mTimestamp++;
if (compacting && index != mEntriesCount)
replaceWithLast(index);
freeListAdd(index);
return true;
}
PX_NOINLINE void reserveInternal(uint32_t size)
{
if(!PxIsPowerOfTwo(size))
size = PxNextPowerOfTwo(size);
PX_ASSERT(!(size & (size - 1)));
// decide whether iteration can be done on the entries directly
bool resizeCompact = compacting || freeListEmpty();
// define new table sizes
uint32_t oldEntriesCapacity = mEntriesCapacity;
uint32_t newEntriesCapacity = uint32_t(float(size) * mLoadFactor);
uint32_t newHashSize = size;
// allocate new common buffer and setup pointers to new tables
uint8_t* newBuffer;
uint32_t* newHash;
uint32_t* newEntriesNext;
Entry* newEntries;
{
uint32_t newHashByteOffset = 0;
uint32_t newEntriesNextBytesOffset = newHashByteOffset + newHashSize * sizeof(uint32_t);
uint32_t newEntriesByteOffset = newEntriesNextBytesOffset + newEntriesCapacity * sizeof(uint32_t);
newEntriesByteOffset += (16 - (newEntriesByteOffset & 15)) & 15;
uint32_t newBufferByteSize = newEntriesByteOffset + newEntriesCapacity * sizeof(Entry);
newBuffer = reinterpret_cast<uint8_t*>(PxAllocator::allocate(newBufferByteSize, PX_FL));
PX_ASSERT(newBuffer);
newHash = reinterpret_cast<uint32_t*>(newBuffer + newHashByteOffset);
newEntriesNext = reinterpret_cast<uint32_t*>(newBuffer + newEntriesNextBytesOffset);
newEntries = reinterpret_cast<Entry*>(newBuffer + newEntriesByteOffset);
}
// initialize new hash table
intrinsics::memSet(newHash, int32_t(EOL), newHashSize * sizeof(uint32_t));
// iterate over old entries, re-hash and create new entries
if(resizeCompact)
{
// check that old free list is empty - we don't need to copy the next entries
PX_ASSERT(compacting || mFreeList == EOL);
for(uint32_t index = 0; index < mEntriesCount; ++index)
{
uint32_t h = hash(GetKey()(mEntries[index]), newHashSize);
newEntriesNext[index] = newHash[h];
newHash[h] = index;
PX_PLACEMENT_NEW(newEntries + index, Entry)(mEntries[index]);
mEntries[index].~Entry();
}
}
else
{
// copy old free list, only required for non compact resizing
intrinsics::memCopy(newEntriesNext, mEntriesNext, mEntriesCapacity * sizeof(uint32_t));
for(uint32_t bucket = 0; bucket < mHashSize; bucket++)
{
uint32_t index = mHash[bucket];
while(index != EOL)
{
uint32_t h = hash(GetKey()(mEntries[index]), newHashSize);
newEntriesNext[index] = newHash[h];
PX_ASSERT(index != newHash[h]);
newHash[h] = index;
PX_PLACEMENT_NEW(newEntries + index, Entry)(mEntries[index]);
mEntries[index].~Entry();
index = mEntriesNext[index];
}
}
}
// swap buffer and pointers
PxAllocator::deallocate(mBuffer);
mBuffer = newBuffer;
mHash = newHash;
mHashSize = newHashSize;
mEntriesNext = newEntriesNext;
mEntries = newEntries;
mEntriesCapacity = newEntriesCapacity;
freeListAdd(oldEntriesCapacity, newEntriesCapacity);
}
void grow()
{
PX_ASSERT((mFreeList == EOL) || (compacting && (mEntriesCount == mEntriesCapacity)));
uint32_t size = mHashSize == 0 ? 16 : mHashSize * 2;
reserve(size);
}
uint8_t* mBuffer;
Entry* mEntries;
uint32_t* mEntriesNext; // same size as mEntries
uint32_t* mHash;
uint32_t mEntriesCapacity;
uint32_t mHashSize;
float mLoadFactor;
uint32_t mFreeList;
uint32_t mTimestamp;
uint32_t mEntriesCount; // number of entries
public:
class Iter
{
public:
PX_INLINE Iter(PxHashBase& b) : mBucket(0), mEntry(uint32_t(b.EOL)), mTimestamp(b.mTimestamp), mBase(b)
{
if(mBase.mEntriesCapacity > 0)
{
mEntry = mBase.mHash[0];
skip();
}
}
PX_INLINE void check() const
{
PX_ASSERT(mTimestamp == mBase.mTimestamp);
}
PX_INLINE const Entry& operator*() const
{
check();
return mBase.mEntries[mEntry];
}
PX_INLINE Entry& operator*()
{
check();
return mBase.mEntries[mEntry];
}
PX_INLINE const Entry* operator->() const
{
check();
return mBase.mEntries + mEntry;
}
PX_INLINE Entry* operator->()
{
check();
return mBase.mEntries + mEntry;
}
PX_INLINE Iter operator++()
{
check();
advance();
return *this;
}
PX_INLINE Iter operator++(int)
{
check();
Iter i = *this;
advance();
return i;
}
PX_INLINE bool done() const
{
check();
return mEntry == mBase.EOL;
}
private:
PX_INLINE void advance()
{
mEntry = mBase.mEntriesNext[mEntry];
skip();
}
PX_INLINE void skip()
{
while(mEntry == mBase.EOL)
{
if(++mBucket == mBase.mHashSize)
break;
mEntry = mBase.mHash[mBucket];
}
}
Iter& operator=(const Iter&);
uint32_t mBucket;
uint32_t mEntry;
uint32_t mTimestamp;
PxHashBase& mBase;
};
class PxEraseIterator
{
public:
PX_INLINE PxEraseIterator(PxHashBase& b): mBase(b)
{
reset();
}
PX_INLINE Entry* eraseCurrentGetNext(bool eraseCurrent)
{
if(eraseCurrent && mCurrentEntryIndexPtr)
{
mBase.eraseInternal(mCurrentEntryIndexPtr);
// if next was valid return the same ptr, if next was EOL search new hash entry
if(*mCurrentEntryIndexPtr != mBase.EOL)
return mBase.mEntries + *mCurrentEntryIndexPtr;
else
return traverseHashEntries();
}
// traverse mHash to find next entry
if(mCurrentEntryIndexPtr == NULL)
return traverseHashEntries();
const uint32_t index = *mCurrentEntryIndexPtr;
if(mBase.mEntriesNext[index] == mBase.EOL)
{
return traverseHashEntries();
}
else
{
mCurrentEntryIndexPtr = mBase.mEntriesNext + index;
return mBase.mEntries + *mCurrentEntryIndexPtr;
}
}
PX_INLINE void reset()
{
mCurrentHashIndex = 0;
mCurrentEntryIndexPtr = NULL;
}
private:
PX_INLINE Entry* traverseHashEntries()
{
mCurrentEntryIndexPtr = NULL;
while (mCurrentEntryIndexPtr == NULL && mCurrentHashIndex < mBase.mHashSize)
{
if (mBase.mHash[mCurrentHashIndex] != mBase.EOL)
{
mCurrentEntryIndexPtr = mBase.mHash + mCurrentHashIndex;
mCurrentHashIndex++;
return mBase.mEntries + *mCurrentEntryIndexPtr;
}
else
{
mCurrentHashIndex++;
}
}
return NULL;
}
PxEraseIterator& operator=(const PxEraseIterator&);
private:
uint32_t* mCurrentEntryIndexPtr;
uint32_t mCurrentHashIndex;
PxHashBase& mBase;
};
};
template <class Entry, class Key, class HashFn, class GetKey, class PxAllocator, bool compacting>
template <typename HK, typename GK, class A, bool comp>
PX_NOINLINE void
PxHashBase<Entry, Key, HashFn, GetKey, PxAllocator, compacting>::copy(const PxHashBase<Entry, Key, HK, GK, A, comp>& other)
{
reserve(other.mEntriesCount);
for(uint32_t i = 0; i < other.mEntriesCount; i++)
{
for(uint32_t j = other.mHash[i]; j != EOL; j = other.mEntriesNext[j])
{
const Entry& otherEntry = other.mEntries[j];
bool exists;
Entry* newEntry = create(GK()(otherEntry), exists);
PX_ASSERT(!exists);
PX_PLACEMENT_NEW(newEntry, Entry)(otherEntry);
}
}
}
template <class Key, class HashFn, class PxAllocator = typename PxAllocatorTraits<Key>::Type, bool Coalesced = false>
class PxHashSetBase
{
PX_NOCOPY(PxHashSetBase)
public:
struct GetKey
{
PX_INLINE const Key& operator()(const Key& e)
{
return e;
}
};
typedef PxHashBase<Key, Key, HashFn, GetKey, PxAllocator, Coalesced> BaseMap;
typedef typename BaseMap::Iter Iterator;
PxHashSetBase(uint32_t initialTableSize, float loadFactor, const PxAllocator& alloc)
: mBase(initialTableSize, loadFactor, alloc)
{
}
PxHashSetBase(const PxAllocator& alloc) : mBase(64, 0.75f, alloc)
{
}
PxHashSetBase(uint32_t initialTableSize = 64, float loadFactor = 0.75f) : mBase(initialTableSize, loadFactor)
{
}
bool insert(const Key& k)
{
bool exists;
Key* e = mBase.create(k, exists);
if(!exists)
PX_PLACEMENT_NEW(e, Key)(k);
return !exists;
}
PX_INLINE bool contains(const Key& k) const
{
return mBase.find(k) != 0;
}
PX_INLINE bool erase(const Key& k)
{
return mBase.erase(k);
}
PX_INLINE uint32_t size() const
{
return mBase.size();
}
PX_INLINE uint32_t capacity() const
{
return mBase.capacity();
}
PX_INLINE void reserve(uint32_t size)
{
mBase.reserve(size);
}
PX_INLINE void clear()
{
mBase.clear();
}
protected:
BaseMap mBase;
};
template <class Key, class Value, class HashFn, class PxAllocator = typename PxAllocatorTraits<PxPair<const Key, Value> >::Type>
class PxHashMapBase
{
PX_NOCOPY(PxHashMapBase)
public:
typedef PxPair<const Key, Value> Entry;
struct GetKey
{
PX_INLINE const Key& operator()(const Entry& e)
{
return e.first;
}
};
typedef PxHashBase<Entry, Key, HashFn, GetKey, PxAllocator, true> BaseMap;
typedef typename BaseMap::Iter Iterator;
typedef typename BaseMap::PxEraseIterator EraseIterator;
PxHashMapBase(uint32_t initialTableSize, float loadFactor, const PxAllocator& alloc)
: mBase(initialTableSize, loadFactor, alloc)
{
}
PxHashMapBase(const PxAllocator& alloc) : mBase(64, 0.75f, alloc)
{
}
PxHashMapBase(uint32_t initialTableSize = 64, float loadFactor = 0.75f) : mBase(initialTableSize, loadFactor)
{
}
bool insert(const Key /*&*/ k, const Value /*&*/ v)
{
bool exists;
Entry* e = mBase.create(k, exists);
if(!exists)
PX_PLACEMENT_NEW(e, Entry)(k, v);
return !exists;
}
Value& operator[](const Key& k)
{
bool exists;
Entry* e = mBase.create(k, exists);
if(!exists)
PX_PLACEMENT_NEW(e, Entry)(k, Value());
return e->second;
}
PX_INLINE const Entry* find(const Key& k) const
{
return mBase.find(k);
}
PX_INLINE bool erase(const Key& k)
{
return mBase.erase(k);
}
PX_INLINE bool erase(const Key& k, Entry& e)
{
return mBase.erase(k, e);
}
PX_INLINE uint32_t size() const
{
return mBase.size();
}
PX_INLINE uint32_t capacity() const
{
return mBase.capacity();
}
PX_INLINE Iterator getIterator()
{
return Iterator(mBase);
}
PX_INLINE EraseIterator getEraseIterator()
{
return EraseIterator(mBase);
}
PX_INLINE void reserve(uint32_t size)
{
mBase.reserve(size);
}
PX_INLINE void clear()
{
mBase.clear();
}
protected:
BaseMap mBase;
};
#if !PX_DOXYGEN
} // namespace physx
#endif
#if PX_VC
#pragma warning(pop)
#endif
#endif