/* * 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 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(0); } // i.e. NULL static T* Deleted() { return reinterpret_cast(1); } // Also an invalid pointer. static T** AllocArray(int capacity) { return (T**)sk_calloc_throw(sizeof(T*) * capacity); // All cells == Empty(). } 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