blob: 47412c34b12e2cee6233371ce5f7d890a1edbb1d [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"
#include "SkMath.h"
template <typename T,
typename Key,
const Key& (GetKey)(const T&),
uint32_t (Hash)(const Key&),
bool (Equal)(const T&, const Key&),
int kGrowPercent = 75, // Larger -> more memory efficient, but slower.
int kShrinkPercent = 25>
class SkTDynamicHash {
static const int kMinCapacity = 4; // Smallest capacity we allow.
public:
SkTDynamicHash(int initialCapacity=64/sizeof(T*)) {
this->reset(SkNextPow2(initialCapacity > kMinCapacity ? initialCapacity : kMinCapacity));
SkASSERT(this->validate());
}
~SkTDynamicHash() {
sk_free(fArray);
}
int count() const { return fCount; }
// Return the entry with this key if we have it, otherwise NULL.
T* find(const Key& key) const {
int index = this->firstIndex(key);
for (int round = 0; round < fCapacity; round++) {
T* candidate = fArray[index];
if (Empty() == candidate) {
return NULL;
}
if (Deleted() != candidate && Equal(*candidate, key)) {
return candidate;
}
index = this->nextIndex(index, round);
}
SkASSERT(0); // find: should be unreachable
return NULL;
}
// Add an entry with this key. We require that no entry with newEntry's key is already present.
void add(T* newEntry) {
SkASSERT(NULL == this->find(GetKey(*newEntry)));
this->maybeGrow();
SkASSERT(this->validate());
this->innerAdd(newEntry);
SkASSERT(this->validate());
}
// Remove the entry with this key. We reqire that an entry with this key is present.
void remove(const Key& key) {
SkASSERT(NULL != this->find(key));
this->innerRemove(key);
SkASSERT(this->validate());
this->maybeShrink();
SkASSERT(this->validate());
}
protected:
// These methods are used by tests only.
int capacity() const { return fCapacity; }
// How many collisions do we go through before finding where this entry should be inserted?
int countCollisions(const Key& key) const {
int index = this->firstIndex(key);
for (int round = 0; round < fCapacity; round++) {
const T* candidate = fArray[index];
if (Empty() == candidate || Deleted() == candidate || Equal(*candidate, key)) {
return round;
}
index = this->nextIndex(index, round);
}
SkASSERT(0); // countCollisions: should be unreachable
return -1;
}
private:
// We have two special values to indicate an empty or deleted entry.
static T* Empty() { return reinterpret_cast<T*>(0); } // i.e. NULL
static T* Deleted() { return reinterpret_cast<T*>(1); } // Also an invalid pointer.
static T** AllocArray(int capacity) {
T** array = (T**)sk_malloc_throw(sizeof(T*) * capacity);
sk_bzero(array, sizeof(T*) * capacity); // All cells == Empty().
return array;
}
void reset(int capacity) {
fCount = 0;
fDeleted = 0;
fCapacity = capacity;
fArray = AllocArray(fCapacity);
}
bool validate() const {
#define SKTDYNAMICHASH_CHECK(x) SkASSERT((x)); if (!(x)) return false
// Is capacity sane?
SKTDYNAMICHASH_CHECK(SkIsPow2(fCapacity));
SKTDYNAMICHASH_CHECK(fCapacity >= kMinCapacity);
// Is fCount correct?
int count = 0;
for (int i = 0; i < fCapacity; i++) {
if (Empty() != fArray[i] && Deleted() != fArray[i]) {
count++;
}
}
SKTDYNAMICHASH_CHECK(count == fCount);
// Is fDeleted correct?
int deleted = 0;
for (int i = 0; i < fCapacity; i++) {
if (Deleted() == fArray[i]) {
deleted++;
}
}
SKTDYNAMICHASH_CHECK(deleted == fDeleted);
// Are all entries findable?
for (int i = 0; i < fCapacity; i++) {
if (Empty() == fArray[i] || Deleted() == fArray[i]) {
continue;
}
SKTDYNAMICHASH_CHECK(NULL != this->find(GetKey(*fArray[i])));
}
// Are all entries unique?
for (int i = 0; i < fCapacity; i++) {
if (Empty() == fArray[i] || Deleted() == fArray[i]) {
continue;
}
for (int j = i+1; j < fCapacity; j++) {
if (Empty() == fArray[j] || Deleted() == fArray[j]) {
continue;
}
SKTDYNAMICHASH_CHECK(fArray[i] != fArray[j]);
SKTDYNAMICHASH_CHECK(!Equal(*fArray[i], GetKey(*fArray[j])));
SKTDYNAMICHASH_CHECK(!Equal(*fArray[j], GetKey(*fArray[i])));
}
}
#undef SKTDYNAMICHASH_CHECK
return true;
}
void innerAdd(T* newEntry) {
const Key& key = GetKey(*newEntry);
int index = this->firstIndex(key);
for (int round = 0; round < fCapacity; round++) {
const T* candidate = fArray[index];
if (Empty() == candidate || Deleted() == candidate) {
if (Deleted() == candidate) {
fDeleted--;
}
fCount++;
fArray[index] = newEntry;
return;
}
index = this->nextIndex(index, round);
}
SkASSERT(0); // add: should be unreachable
}
void innerRemove(const Key& key) {
const int firstIndex = this->firstIndex(key);
int index = firstIndex;
for (int round = 0; round < fCapacity; round++) {
const T* candidate = fArray[index];
if (Deleted() != candidate && Equal(*candidate, key)) {
fDeleted++;
fCount--;
fArray[index] = Deleted();
return;
}
index = this->nextIndex(index, round);
}
SkASSERT(0); // innerRemove: should be unreachable
}
void maybeGrow() {
if (fCount + fDeleted + 1 > (fCapacity * kGrowPercent) / 100) {
resize(fCapacity * 2);
}
}
void maybeShrink() {
if (fCount < (fCapacity * kShrinkPercent) / 100 && fCapacity / 2 > kMinCapacity) {
resize(fCapacity / 2);
}
}
void resize(int newCapacity) {
SkDEBUGCODE(int oldCount = fCount;)
int oldCapacity = fCapacity;
T** oldArray = fArray;
reset(newCapacity);
for (int i = 0; i < oldCapacity; i++) {
T* entry = oldArray[i];
if (Empty() != entry && Deleted() != entry) {
this->add(entry);
}
}
SkASSERT(oldCount == fCount);
sk_free(oldArray);
}
// fCapacity is always a power of 2, so this masks the correct low bits to index into our hash.
uint32_t hashMask() const { return fCapacity - 1; }
int firstIndex(const Key& key) const {
return Hash(key) & this->hashMask();
}
// Given index at round N, what is the index to check at N+1? round should start at 0.
int nextIndex(int index, int round) const {
// This will search a power-of-two array fully without repeating an index.
return (index + round + 1) & this->hashMask();
}
int fCount; // Number of non Empty(), non Deleted() entries in fArray.
int fDeleted; // Number of Deleted() entries in fArray.
int fCapacity; // Number of entries in fArray. Always a power of 2.
T** fArray;
};
#endif