blob: b144d18d4810a9042e299c1ad15c4a17102c208e [file] [log] [blame]
reed@google.com5d1e5582013-07-25 14:36:15 +00001/*
2 * Copyright 2013 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#ifndef SkTDynamicHash_DEFINED
9#define SkTDynamicHash_DEFINED
10
commit-bot@chromium.orgf916f9e2013-08-05 22:31:20 +000011#include "SkMath.h"
commit-bot@chromium.org5b8bde52014-01-16 18:16:37 +000012#include "SkTemplates.h"
13#include "SkTypes.h"
reed@google.com5d1e5582013-07-25 14:36:15 +000014
commit-bot@chromium.org55bd9402014-04-02 19:17:00 +000015// Traits requires:
16// static const Key& GetKey(const T&) { ... }
17// static uint32_t Hash(const Key&) { ... }
18// We'll look on T for these by default, or you can pass a custom Traits type.
reed@google.comb14ecda2013-07-31 18:02:55 +000019template <typename T,
commit-bot@chromium.orgf916f9e2013-08-05 22:31:20 +000020 typename Key,
commit-bot@chromium.org55bd9402014-04-02 19:17:00 +000021 typename Traits = T,
commit-bot@chromium.org7a4b9fa2014-01-13 18:28:14 +000022 int kGrowPercent = 75> // Larger -> more memory efficient, but slower.
reed@google.com5d1e5582013-07-25 14:36:15 +000023class SkTDynamicHash {
reed@google.com5d1e5582013-07-25 14:36:15 +000024public:
halcanary96fcdcc2015-08-27 07:41:13 -070025 SkTDynamicHash() : fCount(0), fDeleted(0), fCapacity(0), fArray(nullptr) {
mtklein@google.comc9ab2b72013-08-12 14:51:25 +000026 SkASSERT(this->validate());
27 }
commit-bot@chromium.orgf916f9e2013-08-05 22:31:20 +000028
reed@google.com5d1e5582013-07-25 14:36:15 +000029 ~SkTDynamicHash() {
commit-bot@chromium.orgf916f9e2013-08-05 22:31:20 +000030 sk_free(fArray);
reed@google.com5d1e5582013-07-25 14:36:15 +000031 }
skia.committer@gmail.com956b3102013-07-26 07:00:58 +000032
commit-bot@chromium.org1f6cf692014-03-05 01:00:50 +000033 class Iter {
34 public:
35 explicit Iter(SkTDynamicHash* hash) : fHash(hash), fCurrentIndex(-1) {
36 SkASSERT(hash);
37 ++(*this);
38 }
39 bool done() const {
40 SkASSERT(fCurrentIndex <= fHash->fCapacity);
41 return fCurrentIndex == fHash->fCapacity;
42 }
43 T& operator*() const {
44 SkASSERT(!this->done());
45 return *this->current();
46 }
47 void operator++() {
48 do {
49 fCurrentIndex++;
50 } while (!this->done() && (this->current() == Empty() || this->current() == Deleted()));
51 }
52
53 private:
54 T* current() const { return fHash->fArray[fCurrentIndex]; }
55
56 SkTDynamicHash* fHash;
57 int fCurrentIndex;
58 };
59
robertphillips3d533ac2014-07-20 09:40:00 -070060 class ConstIter {
61 public:
62 explicit ConstIter(const SkTDynamicHash* hash) : fHash(hash), fCurrentIndex(-1) {
63 SkASSERT(hash);
64 ++(*this);
65 }
66 bool done() const {
67 SkASSERT(fCurrentIndex <= fHash->fCapacity);
68 return fCurrentIndex == fHash->fCapacity;
69 }
70 const T& operator*() const {
71 SkASSERT(!this->done());
72 return *this->current();
73 }
74 void operator++() {
75 do {
76 fCurrentIndex++;
77 } while (!this->done() && (this->current() == Empty() || this->current() == Deleted()));
78 }
79
80 private:
81 const T* current() const { return fHash->fArray[fCurrentIndex]; }
82
83 const SkTDynamicHash* fHash;
84 int fCurrentIndex;
85 };
86
commit-bot@chromium.orgf916f9e2013-08-05 22:31:20 +000087 int count() const { return fCount; }
skia.committer@gmail.com956b3102013-07-26 07:00:58 +000088
halcanary96fcdcc2015-08-27 07:41:13 -070089 // Return the entry with this key if we have it, otherwise nullptr.
commit-bot@chromium.orgf916f9e2013-08-05 22:31:20 +000090 T* find(const Key& key) const {
91 int index = this->firstIndex(key);
92 for (int round = 0; round < fCapacity; round++) {
mtklein2e09d182014-07-09 09:20:54 -070093 SkASSERT(index >= 0 && index < fCapacity);
reed@google.com5d1e5582013-07-25 14:36:15 +000094 T* candidate = fArray[index];
mtklein@google.comc9ab2b72013-08-12 14:51:25 +000095 if (Empty() == candidate) {
halcanary96fcdcc2015-08-27 07:41:13 -070096 return nullptr;
reed@google.com5d1e5582013-07-25 14:36:15 +000097 }
commit-bot@chromium.org158f6462014-04-02 17:03:09 +000098 if (Deleted() != candidate && GetKey(*candidate) == key) {
reed@google.com5d1e5582013-07-25 14:36:15 +000099 return candidate;
100 }
commit-bot@chromium.orgf916f9e2013-08-05 22:31:20 +0000101 index = this->nextIndex(index, round);
102 }
commit-bot@chromium.org7a4b9fa2014-01-13 18:28:14 +0000103 SkASSERT(fCapacity == 0);
halcanary96fcdcc2015-08-27 07:41:13 -0700104 return nullptr;
reed@google.com5d1e5582013-07-25 14:36:15 +0000105 }
skia.committer@gmail.com956b3102013-07-26 07:00:58 +0000106
mtklein@google.comc9ab2b72013-08-12 14:51:25 +0000107 // Add an entry with this key. We require that no entry with newEntry's key is already present.
commit-bot@chromium.orgf916f9e2013-08-05 22:31:20 +0000108 void add(T* newEntry) {
halcanary96fcdcc2015-08-27 07:41:13 -0700109 SkASSERT(nullptr == this->find(GetKey(*newEntry)));
commit-bot@chromium.orgf916f9e2013-08-05 22:31:20 +0000110 this->maybeGrow();
mtklein@google.comc9ab2b72013-08-12 14:51:25 +0000111 this->innerAdd(newEntry);
112 SkASSERT(this->validate());
reed@google.com5d1e5582013-07-25 14:36:15 +0000113 }
skia.committer@gmail.com956b3102013-07-26 07:00:58 +0000114
robertphillips3d533ac2014-07-20 09:40:00 -0700115 // Remove the entry with this key. We require that an entry with this key is present.
commit-bot@chromium.orgf916f9e2013-08-05 22:31:20 +0000116 void remove(const Key& key) {
bsalomon49f085d2014-09-05 13:34:00 -0700117 SkASSERT(this->find(key));
commit-bot@chromium.orgf916f9e2013-08-05 22:31:20 +0000118 this->innerRemove(key);
mtklein@google.comc9ab2b72013-08-12 14:51:25 +0000119 SkASSERT(this->validate());
commit-bot@chromium.orgf916f9e2013-08-05 22:31:20 +0000120 }
skia.committer@gmail.com956b3102013-07-26 07:00:58 +0000121
robertphillips3d533ac2014-07-20 09:40:00 -0700122 void rewind() {
bsalomon49f085d2014-09-05 13:34:00 -0700123 if (fArray) {
robertphillips3d533ac2014-07-20 09:40:00 -0700124 sk_bzero(fArray, sizeof(T*)* fCapacity);
125 }
126 fCount = 0;
127 fDeleted = 0;
128 }
129
halcanary9d524f22016-03-29 09:03:52 -0700130 void reset() {
131 fCount = 0;
132 fDeleted = 0;
133 fCapacity = 0;
134 sk_free(fArray);
135 fArray = nullptr;
robertphillips3d533ac2014-07-20 09:40:00 -0700136 }
137
commit-bot@chromium.orgf916f9e2013-08-05 22:31:20 +0000138protected:
139 // These methods are used by tests only.
140
141 int capacity() const { return fCapacity; }
142
143 // How many collisions do we go through before finding where this entry should be inserted?
144 int countCollisions(const Key& key) const {
145 int index = this->firstIndex(key);
146 for (int round = 0; round < fCapacity; round++) {
mtklein2e09d182014-07-09 09:20:54 -0700147 SkASSERT(index >= 0 && index < fCapacity);
reed@google.com5d1e5582013-07-25 14:36:15 +0000148 const T* candidate = fArray[index];
commit-bot@chromium.org158f6462014-04-02 17:03:09 +0000149 if (Empty() == candidate || Deleted() == candidate || GetKey(*candidate) == key) {
commit-bot@chromium.orgf916f9e2013-08-05 22:31:20 +0000150 return round;
reed@google.com5d1e5582013-07-25 14:36:15 +0000151 }
commit-bot@chromium.orgf916f9e2013-08-05 22:31:20 +0000152 index = this->nextIndex(index, round);
reed@google.com5d1e5582013-07-25 14:36:15 +0000153 }
commit-bot@chromium.org7a4b9fa2014-01-13 18:28:14 +0000154 SkASSERT(fCapacity == 0);
155 return 0;
reed@google.com5d1e5582013-07-25 14:36:15 +0000156 }
skia.committer@gmail.com956b3102013-07-26 07:00:58 +0000157
reed@google.com5d1e5582013-07-25 14:36:15 +0000158private:
commit-bot@chromium.orgf916f9e2013-08-05 22:31:20 +0000159 // We have two special values to indicate an empty or deleted entry.
halcanary96fcdcc2015-08-27 07:41:13 -0700160 static T* Empty() { return reinterpret_cast<T*>(0); } // i.e. nullptr
mtklein@google.comc9ab2b72013-08-12 14:51:25 +0000161 static T* Deleted() { return reinterpret_cast<T*>(1); } // Also an invalid pointer.
reed@google.comb8d17fe2013-07-25 17:42:24 +0000162
mtklein@google.comc9ab2b72013-08-12 14:51:25 +0000163 bool validate() const {
dongseong.hwang00ed9a92015-04-28 12:26:52 -0700164 #define SKTDYNAMICHASH_CHECK(x) SkASSERT(x); if (!(x)) return false
commit-bot@chromium.orgcd0bf0a2014-01-17 17:55:14 +0000165 static const int kLarge = 50; // Arbitrary, tweak to suit your patience.
mtklein@google.comc9ab2b72013-08-12 14:51:25 +0000166
commit-bot@chromium.orgcd0bf0a2014-01-17 17:55:14 +0000167 // O(1) checks, always done.
mtklein@google.comc9ab2b72013-08-12 14:51:25 +0000168 // Is capacity sane?
mtklein@google.comba0ca992013-08-20 17:03:02 +0000169 SKTDYNAMICHASH_CHECK(SkIsPow2(fCapacity));
mtklein@google.comc9ab2b72013-08-12 14:51:25 +0000170
commit-bot@chromium.orgcd0bf0a2014-01-17 17:55:14 +0000171 // O(N) checks, skipped when very large.
172 if (fCount < kLarge * kLarge) {
173 // Are fCount and fDeleted correct, and are all elements findable?
174 int count = 0, deleted = 0;
175 for (int i = 0; i < fCapacity; i++) {
176 if (Deleted() == fArray[i]) {
177 deleted++;
178 } else if (Empty() != fArray[i]) {
179 count++;
bsalomon49f085d2014-09-05 13:34:00 -0700180 SKTDYNAMICHASH_CHECK(this->find(GetKey(*fArray[i])));
commit-bot@chromium.orgcd0bf0a2014-01-17 17:55:14 +0000181 }
mtklein@google.comc9ab2b72013-08-12 14:51:25 +0000182 }
commit-bot@chromium.orgcd0bf0a2014-01-17 17:55:14 +0000183 SKTDYNAMICHASH_CHECK(count == fCount);
184 SKTDYNAMICHASH_CHECK(deleted == fDeleted);
mtklein@google.comc9ab2b72013-08-12 14:51:25 +0000185 }
186
commit-bot@chromium.orgcd0bf0a2014-01-17 17:55:14 +0000187 // O(N^2) checks, skipped when large.
188 if (fCount < kLarge) {
189 // Are all entries unique?
190 for (int i = 0; i < fCapacity; i++) {
191 if (Empty() == fArray[i] || Deleted() == fArray[i]) {
mtklein@google.comc9ab2b72013-08-12 14:51:25 +0000192 continue;
193 }
commit-bot@chromium.orgcd0bf0a2014-01-17 17:55:14 +0000194 for (int j = i+1; j < fCapacity; j++) {
195 if (Empty() == fArray[j] || Deleted() == fArray[j]) {
196 continue;
197 }
198 SKTDYNAMICHASH_CHECK(fArray[i] != fArray[j]);
commit-bot@chromium.org158f6462014-04-02 17:03:09 +0000199 SKTDYNAMICHASH_CHECK(!(GetKey(*fArray[i]) == GetKey(*fArray[j])));
commit-bot@chromium.orgcd0bf0a2014-01-17 17:55:14 +0000200 }
mtklein@google.comc9ab2b72013-08-12 14:51:25 +0000201 }
202 }
mtklein@google.comba0ca992013-08-20 17:03:02 +0000203 #undef SKTDYNAMICHASH_CHECK
mtklein@google.comc9ab2b72013-08-12 14:51:25 +0000204 return true;
205 }
206
207 void innerAdd(T* newEntry) {
208 const Key& key = GetKey(*newEntry);
commit-bot@chromium.orgf916f9e2013-08-05 22:31:20 +0000209 int index = this->firstIndex(key);
210 for (int round = 0; round < fCapacity; round++) {
mtklein2e09d182014-07-09 09:20:54 -0700211 SkASSERT(index >= 0 && index < fCapacity);
commit-bot@chromium.orgf916f9e2013-08-05 22:31:20 +0000212 const T* candidate = fArray[index];
mtklein@google.comc9ab2b72013-08-12 14:51:25 +0000213 if (Empty() == candidate || Deleted() == candidate) {
214 if (Deleted() == candidate) {
215 fDeleted--;
216 }
217 fCount++;
218 fArray[index] = newEntry;
commit-bot@chromium.orgf916f9e2013-08-05 22:31:20 +0000219 return;
reed@google.comb8d17fe2013-07-25 17:42:24 +0000220 }
mtklein@google.comc9ab2b72013-08-12 14:51:25 +0000221 index = this->nextIndex(index, round);
222 }
commit-bot@chromium.org7a4b9fa2014-01-13 18:28:14 +0000223 SkASSERT(fCapacity == 0);
mtklein@google.comc9ab2b72013-08-12 14:51:25 +0000224 }
225
226 void innerRemove(const Key& key) {
227 const int firstIndex = this->firstIndex(key);
228 int index = firstIndex;
229 for (int round = 0; round < fCapacity; round++) {
mtklein2e09d182014-07-09 09:20:54 -0700230 SkASSERT(index >= 0 && index < fCapacity);
mtklein@google.comc9ab2b72013-08-12 14:51:25 +0000231 const T* candidate = fArray[index];
commit-bot@chromium.org158f6462014-04-02 17:03:09 +0000232 if (Deleted() != candidate && GetKey(*candidate) == key) {
mtklein@google.comc9ab2b72013-08-12 14:51:25 +0000233 fDeleted++;
commit-bot@chromium.orgf916f9e2013-08-05 22:31:20 +0000234 fCount--;
mtklein@google.comc9ab2b72013-08-12 14:51:25 +0000235 fArray[index] = Deleted();
commit-bot@chromium.orgf916f9e2013-08-05 22:31:20 +0000236 return;
237 }
238 index = this->nextIndex(index, round);
reed@google.comb8d17fe2013-07-25 17:42:24 +0000239 }
commit-bot@chromium.org7a4b9fa2014-01-13 18:28:14 +0000240 SkASSERT(fCapacity == 0);
commit-bot@chromium.orgf916f9e2013-08-05 22:31:20 +0000241 }
242
243 void maybeGrow() {
commit-bot@chromium.org7a4b9fa2014-01-13 18:28:14 +0000244 if (100 * (fCount + fDeleted + 1) > fCapacity * kGrowPercent) {
245 this->resize(fCapacity > 0 ? fCapacity * 2 : 4);
mtklein@google.comc9ab2b72013-08-12 14:51:25 +0000246 }
247 }
248
249 void resize(int newCapacity) {
commit-bot@chromium.orgf916f9e2013-08-05 22:31:20 +0000250 SkDEBUGCODE(int oldCount = fCount;)
reed@google.com5d1e5582013-07-25 14:36:15 +0000251 int oldCapacity = fCapacity;
commit-bot@chromium.org7a4b9fa2014-01-13 18:28:14 +0000252 SkAutoTMalloc<T*> oldArray(fArray);
skia.committer@gmail.com956b3102013-07-26 07:00:58 +0000253
commit-bot@chromium.org7a4b9fa2014-01-13 18:28:14 +0000254 fCount = fDeleted = 0;
255 fCapacity = newCapacity;
256 fArray = (T**)sk_calloc_throw(sizeof(T*) * fCapacity);
skia.committer@gmail.com956b3102013-07-26 07:00:58 +0000257
commit-bot@chromium.orgf916f9e2013-08-05 22:31:20 +0000258 for (int i = 0; i < oldCapacity; i++) {
259 T* entry = oldArray[i];
mtklein@google.comc9ab2b72013-08-12 14:51:25 +0000260 if (Empty() != entry && Deleted() != entry) {
commit-bot@chromium.org7a4b9fa2014-01-13 18:28:14 +0000261 this->innerAdd(entry);
reed@google.com5d1e5582013-07-25 14:36:15 +0000262 }
reed@google.com5d1e5582013-07-25 14:36:15 +0000263 }
commit-bot@chromium.orgf916f9e2013-08-05 22:31:20 +0000264 SkASSERT(oldCount == fCount);
reed@google.com5d1e5582013-07-25 14:36:15 +0000265 }
skia.committer@gmail.com956b3102013-07-26 07:00:58 +0000266
commit-bot@chromium.orgf916f9e2013-08-05 22:31:20 +0000267 // fCapacity is always a power of 2, so this masks the correct low bits to index into our hash.
268 uint32_t hashMask() const { return fCapacity - 1; }
269
270 int firstIndex(const Key& key) const {
271 return Hash(key) & this->hashMask();
272 }
273
274 // Given index at round N, what is the index to check at N+1? round should start at 0.
275 int nextIndex(int index, int round) const {
276 // This will search a power-of-two array fully without repeating an index.
277 return (index + round + 1) & this->hashMask();
278 }
279
commit-bot@chromium.org55bd9402014-04-02 19:17:00 +0000280 static const Key& GetKey(const T& t) { return Traits::GetKey(t); }
281 static uint32_t Hash(const Key& key) { return Traits::Hash(key); }
282
mtklein@google.comc9ab2b72013-08-12 14:51:25 +0000283 int fCount; // Number of non Empty(), non Deleted() entries in fArray.
284 int fDeleted; // Number of Deleted() entries in fArray.
commit-bot@chromium.orgf916f9e2013-08-05 22:31:20 +0000285 int fCapacity; // Number of entries in fArray. Always a power of 2.
286 T** fArray;
reed@google.com5d1e5582013-07-25 14:36:15 +0000287};
288
289#endif