SkTDynamicHash.h revision f916f9e7cf4403846c413acf8fec403e38cd8451
1/*
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
11#include "SkTypes.h"
12#include "SkMath.h"
13
14template <typename T,
15          typename Key,
16          const Key& (GetKey)(const T&),
17          uint32_t (Hash)(const Key&),
18          bool (Equal)(const T&, const Key&)>
19class SkTDynamicHash {
20public:
21    SkTDynamicHash(int initialCapacity=64/sizeof(T*))
22    : fCount(0)
23    , fCapacity(SkNextPow2(initialCapacity > 0 ? initialCapacity : 1))
24    , fArray(AllocArray(fCapacity)) {}
25
26    ~SkTDynamicHash() {
27        sk_free(fArray);
28    }
29
30    int count() const { return fCount; }
31
32    // Return the entry with this key if we have it, otherwise NULL.
33    T* find(const Key& key) const {
34        int index = this->firstIndex(key);
35        for (int round = 0; round < fCapacity; round++) {
36            T* candidate = fArray[index];
37            if (candidate == Empty()) {
38                return NULL;
39            }
40            if (candidate != Deleted() && Equal(*candidate, key)) {
41                return candidate;
42            }
43            index = this->nextIndex(index, round);
44        }
45        SkASSERT(!"find: should be unreachable");
46        return NULL;
47    }
48
49    // Add an entry with this key.
50    void add(T* newEntry) {
51        this->maybeGrow();
52
53        const Key& key = GetKey(*newEntry);
54        int index = this->firstIndex(key);
55        for (int round = 0; round < fCapacity; round++) {
56            T* candidate = fArray[index];
57            if (candidate == Empty() || candidate == Deleted()) {
58                fArray[index] = newEntry;
59                fCount++;
60                return;
61            }
62            if (Equal(*candidate, key)) {
63                fArray[index] = newEntry;
64                return;
65            }
66            index = this->nextIndex(index, round);
67        }
68        SkASSERT(!"add: should be unreachable");
69    }
70
71    // Remove entry with this key, if we have it.
72    void remove(const Key& key) {
73        this->innerRemove(key);
74        this->maybeShrink();
75    }
76
77protected:
78    // These methods are used by tests only.
79
80    int capacity() const { return fCapacity; }
81
82    // How many collisions do we go through before finding where this entry should be inserted?
83    int countCollisions(const Key& key) const {
84        int index = this->firstIndex(key);
85        for (int round = 0; round < fCapacity; round++) {
86            const T* candidate = fArray[index];
87            if (candidate == Empty() || candidate == Deleted() || Equal(*candidate, key)) {
88                return round;
89            }
90            index = this->nextIndex(index, round);
91        }
92        SkASSERT(!"countCollisions: should be unreachable");
93        return -1;
94    }
95
96private:
97    // We have two special values to indicate an empty or deleted entry.
98    static const T* Empty()   { return reinterpret_cast<const T*>(0); }  // i.e. NULL
99    static const T* Deleted() { return reinterpret_cast<const T*>(1); }  // Also an invalid pointer.
100
101    static T** AllocArray(int capacity) {
102        T** array = (T**)sk_malloc_throw(sizeof(T*) * capacity);
103        sk_bzero(array, sizeof(T*) * capacity);  // All cells == Empty().
104        return array;
105    }
106
107    void innerRemove(const Key& key) {
108        int index = this->firstIndex(key);
109        for (int round = 0; round < fCapacity; round++) {
110            const T* candidate = fArray[index];
111            if (candidate == Empty()) {
112                return;
113            }
114            if (candidate != Deleted() && Equal(*candidate, key)) {
115                fArray[index] = const_cast<T*>(Deleted());
116                fCount--;
117                return;
118            }
119            index = this->nextIndex(index, round);
120        }
121        SkASSERT(!"innerRemove: should be unreachable");
122    }
123
124    void maybeGrow() {
125        if (fCount < fCapacity / 2) {
126            return;
127        }
128
129        SkDEBUGCODE(int oldCount = fCount;)
130        int oldCapacity = fCapacity;
131        T** oldArray = fArray;
132
133        fCount = 0;
134        fCapacity *= 2;
135        fArray = AllocArray(fCapacity);
136
137        for (int i = 0; i < oldCapacity; i++) {
138            T* entry = oldArray[i];
139            if (entry != Empty() && entry != Deleted()) {
140                this->add(entry);
141            }
142        }
143        SkASSERT(oldCount == fCount);
144
145        sk_free(oldArray);
146    }
147
148    void maybeShrink() {
149        // TODO
150    }
151
152    // fCapacity is always a power of 2, so this masks the correct low bits to index into our hash.
153    uint32_t hashMask() const { return fCapacity - 1; }
154
155    int firstIndex(const Key& key) const {
156        return Hash(key) & this->hashMask();
157    }
158
159    // Given index at round N, what is the index to check at N+1?  round should start at 0.
160    int nextIndex(int index, int round) const {
161        // This will search a power-of-two array fully without repeating an index.
162        return (index + round + 1) & this->hashMask();
163    }
164
165    int fCount;     // Number of non-empty, non-deleted entries in fArray.
166    int fCapacity;  // Number of entries in fArray.  Always a power of 2.
167    T** fArray;
168};
169
170#endif
171