/* * 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 "SkMath.h" #include "SkTemplates.h" #include "SkTypes.h" // Traits requires: // static const Key& GetKey(const T&) { ... } // static uint32_t Hash(const Key&) { ... } // We'll look on T for these by default, or you can pass a custom Traits type. template // Larger -> more memory efficient, but slower. class SkTDynamicHash { public: SkTDynamicHash() : fCount(0), fDeleted(0), fCapacity(0), fArray(nullptr) { SkASSERT(this->validate()); } ~SkTDynamicHash() { sk_free(fArray); } class Iter { public: explicit Iter(SkTDynamicHash* hash) : fHash(hash), fCurrentIndex(-1) { SkASSERT(hash); ++(*this); } bool done() const { SkASSERT(fCurrentIndex <= fHash->fCapacity); return fCurrentIndex == fHash->fCapacity; } T& operator*() const { SkASSERT(!this->done()); return *this->current(); } void operator++() { do { fCurrentIndex++; } while (!this->done() && (this->current() == Empty() || this->current() == Deleted())); } private: T* current() const { return fHash->fArray[fCurrentIndex]; } SkTDynamicHash* fHash; int fCurrentIndex; }; class ConstIter { public: explicit ConstIter(const SkTDynamicHash* hash) : fHash(hash), fCurrentIndex(-1) { SkASSERT(hash); ++(*this); } bool done() const { SkASSERT(fCurrentIndex <= fHash->fCapacity); return fCurrentIndex == fHash->fCapacity; } const T& operator*() const { SkASSERT(!this->done()); return *this->current(); } void operator++() { do { fCurrentIndex++; } while (!this->done() && (this->current() == Empty() || this->current() == Deleted())); } private: const T* current() const { return fHash->fArray[fCurrentIndex]; } const SkTDynamicHash* fHash; int fCurrentIndex; }; int count() const { return fCount; } // Return the entry with this key if we have it, otherwise nullptr. T* find(const Key& key) const { int index = this->firstIndex(key); for (int round = 0; round < fCapacity; round++) { SkASSERT(index >= 0 && index < fCapacity); T* candidate = fArray[index]; if (Empty() == candidate) { return nullptr; } if (Deleted() != candidate && GetKey(*candidate) == key) { return candidate; } index = this->nextIndex(index, round); } SkASSERT(fCapacity == 0); return nullptr; } // Add an entry with this key. We require that no entry with newEntry's key is already present. void add(T* newEntry) { SkASSERT(nullptr == this->find(GetKey(*newEntry))); this->maybeGrow(); this->innerAdd(newEntry); SkASSERT(this->validate()); } // Remove the entry with this key. We require that an entry with this key is present. void remove(const Key& key) { SkASSERT(this->find(key)); this->innerRemove(key); SkASSERT(this->validate()); } void rewind() { if (fArray) { sk_bzero(fArray, sizeof(T*)* fCapacity); } fCount = 0; fDeleted = 0; } void reset() { fCount = 0; fDeleted = 0; fCapacity = 0; sk_free(fArray); fArray = nullptr; } 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++) { SkASSERT(index >= 0 && index < fCapacity); const T* candidate = fArray[index]; if (Empty() == candidate || Deleted() == candidate || GetKey(*candidate) == key) { return round; } index = this->nextIndex(index, round); } SkASSERT(fCapacity == 0); return 0; } private: // We have two special values to indicate an empty or deleted entry. static T* Empty() { return reinterpret_cast(0); } // i.e. nullptr static T* Deleted() { return reinterpret_cast(1); } // Also an invalid pointer. bool validate() const { #define SKTDYNAMICHASH_CHECK(x) SkASSERT(x); if (!(x)) return false static const int kLarge = 50; // Arbitrary, tweak to suit your patience. // O(1) checks, always done. // Is capacity sane? SKTDYNAMICHASH_CHECK(SkIsPow2(fCapacity)); // O(N) checks, skipped when very large. if (fCount < kLarge * kLarge) { // Are fCount and fDeleted correct, and are all elements findable? int count = 0, deleted = 0; for (int i = 0; i < fCapacity; i++) { if (Deleted() == fArray[i]) { deleted++; } else if (Empty() != fArray[i]) { count++; SKTDYNAMICHASH_CHECK(this->find(GetKey(*fArray[i]))); } } SKTDYNAMICHASH_CHECK(count == fCount); SKTDYNAMICHASH_CHECK(deleted == fDeleted); } // O(N^2) checks, skipped when large. if (fCount < kLarge) { // 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(!(GetKey(*fArray[i]) == GetKey(*fArray[j]))); } } } #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++) { SkASSERT(index >= 0 && index < fCapacity); 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(fCapacity == 0); } void innerRemove(const Key& key) { const int firstIndex = this->firstIndex(key); int index = firstIndex; for (int round = 0; round < fCapacity; round++) { SkASSERT(index >= 0 && index < fCapacity); const T* candidate = fArray[index]; if (Deleted() != candidate && GetKey(*candidate) == key) { fDeleted++; fCount--; fArray[index] = Deleted(); return; } index = this->nextIndex(index, round); } SkASSERT(fCapacity == 0); } void maybeGrow() { if (100 * (fCount + fDeleted + 1) > fCapacity * kGrowPercent) { auto newCapacity = fCapacity > 0 ? fCapacity : 4; // Only grow the storage when most non-empty entries are // in active use. Otherwise, just purge the tombstones. if (fCount > fDeleted) { newCapacity *= 2; } SkASSERT(newCapacity > fCount + 1); this->resize(newCapacity); } } void resize(int newCapacity) { SkDEBUGCODE(int oldCount = fCount;) int oldCapacity = fCapacity; SkAutoTMalloc oldArray(fArray); fCount = fDeleted = 0; fCapacity = newCapacity; fArray = (T**)sk_calloc_throw(sizeof(T*) * fCapacity); for (int i = 0; i < oldCapacity; i++) { T* entry = oldArray[i]; if (Empty() != entry && Deleted() != entry) { this->innerAdd(entry); } } SkASSERT(oldCount == fCount); } // 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(); } static const Key& GetKey(const T& t) { return Traits::GetKey(t); } static uint32_t Hash(const Key& key) { return Traits::Hash(key); } 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