blob: 823fde8db1a0be681c47cc8e6457c574dff268e9 [file] [log] [blame]
/*
* Copyright 2013 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SkTDynamicHash_DEFINED
#define SkTDynamicHash_DEFINED
#include "SkTypes.h"
template <typename T, typename KEY, const KEY& (KEY_FROM_T)(const T&),
uint32_t (HASH_FROM_KEY)(const Key&), bool (EQ_T_KEY)(const T&, const Key&)>
class SkTDynamicHash {
private:
T* fStorage[4];
T** fArray;
int fCapacity;
int fCountUsed;
int fCountDeleted;
const T* deletedValue() const { return reinterpret_cast<const T*>(-1); }
uint32_t hashMask() const { return fCapacity - 1; }
int hashToIndex(uint32_t hash) const {
return ((hash >> 16) ^ hash) & (fCapacity - 1);
}
public:
SkTDynamicHash() {
sk_bzero(fStorage, sizeof(fStorage));
fArray = fStorage;
fCapacity = SK_ARRAY_COUNT(fStorage);
fCountUsed = fCountDeleted = 0;
}
~SkTDynamicHash() {
if (fArray != fStorage) {
sk_free(fArray);
}
}
T* find(const KEY& key) {
const T* const deleted = this->deletedValue();
const unsigned mask = this->hashMask();
int index = this->hashToIndex(HASH_FROM_KEY(key));
int delta = 1;
do {
T* candidate = fArray[index];
if (NULL == candidate) {
return NULL;
}
if (deleted != candidate && EQ_T_KEY(*candidate, key)) {
return candidate;
}
index = (index + delta) & mask;
delta <<= 1;
} while (delta <= fCapacity);
return NULL;
}
bool add(const KEY& key, T* newElement, bool autoGrow = true) {
const T* const deleted = this->deletedValue();
for (;;) {
const unsigned mask = this->hashMask();
int index = this->hashToIndex(HASH_FROM_KEY(key));
int delta = 1;
do {
const T* candidate = fArray[index];
if (NULL == candidate || deleted == candidate) {
fArray[index] = newElement;
fCountUsed += 1;
if (deleted == candidate) {
SkASSERT(fCountDeleted > 0);
fCountDeleted -= 1;
}
return true;
}
index = (index + delta) & mask;
delta <<= 1;
} while (delta <= fCapacity);
if (autoGrow) {
this->grow();
} else {
return false;
}
}
SkASSERT(!"never get here");
return false;
}
void remove(const KEY& key) {
const T* const deleted = this->deletedValue();
const unsigned mask = this->hashMask();
int index = this->hashToIndex(HASH_FROM_KEY(key));
int delta = 1;
for (;;) {
const T* candidate = fArray[index];
SkASSERT(candidate);
if (deleted != candidate && EQ_T_KEY(*candidate, key)) {
fArray[index] = const_cast<T*>(deleted);
fCountUsed -= 1;
fCountDeleted += 1;
break;
}
index = (index + delta) & mask;
delta <<= 1;
SkASSERT(delta <= fCapacity);
}
this->checkStrink();
}
private:
int countCollisions(const Key& key) const {
const T* const deleted = this->deletedValue();
const unsigned mask = this->hashMask();
int index = this->hashToIndex(HASH_FROM_KEY(key));
int delta = 1;
int collisionCount = 0;
for (;;) {
const T* candidate = fArray[index];
SkASSERT(candidate);
if (deleted != candidate && EQ_T_KEY(*candidate, key)) {
break;
}
index = (index + delta) & mask;
delta <<= 1;
collisionCount += 1;
SkASSERT(delta <= fCapacity);
}
return collisionCount;
}
void grow() {
const T* const deleted = this->deletedValue();
#if 0
SkDebugf("growing from %d: used=%d\n", fCapacity, fCountUsed);
for (int i = 0; i < fCapacity; ++i) {
T* elem = fArray[i];
if (NULL == elem || deleted == elem) {
continue;
}
SkDebugf(" entry[%d] had %d collisions\n", i, countCollisions(KEY_FROM_T(*elem)));
}
#endif
int oldCapacity = fCapacity;
T** oldArray = fArray;
int newCapacity = oldCapacity << 1;
T** newArray = (T**)sk_malloc_throw(sizeof(T*) * newCapacity);
sk_bzero(newArray, sizeof(T*) * newCapacity);
SkDEBUGCODE(int oldCountUsed = fCountUsed;)
fArray = newArray;
fCapacity = newCapacity;
fCountUsed = 0;
fCountDeleted = 0;
for (int i = 0; i < oldCapacity; ++i) {
T* elem = oldArray[i];
if (NULL == elem || deleted == elem) {
continue;
}
SkDEBUGCODE(bool success =) this->add(KEY_FROM_T(*elem), elem, false);
SkASSERT(success);
}
SkASSERT(oldCountUsed == fCountUsed);
if (oldArray != fStorage) {
sk_free(oldArray);
}
}
void checkStrink() {
// todo: based on density and deadspace (fCountDeleted), consider
// shrinking fArray and repopulating it.
}
};
#endif