1 /*
2  * Copyright (c) 2007-2013 Scott Lembcke and Howling Moon Software
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to deal
6  * in the Software without restriction, including without limitation the rights
7  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8  * copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in
12  * all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20  * SOFTWARE.
21  */
22 module dchip.cpHashSet;
23 
24 import dchip.chipmunk;
25 import dchip.chipmunk_private;
26 import dchip.chipmunk_types;
27 import dchip.cpArray;
28 import dchip.prime;
29 
30 struct cpHashSetBin
31 {
32     void* elt;
33     cpHashValue hash;
34     cpHashSetBin* next;
35 }
36 
37 struct cpHashSet
38 {
39     uint entries, size;
40 
41     cpHashSetEqlFunc eql;
42     void* default_value;
43 
44     cpHashSetBin** table;
45     cpHashSetBin* pooledBins;
46 
47     cpArray* allocatedBuffers;
48 }
49 
50 void cpHashSetFree(cpHashSet* set)
51 {
52     if (set)
53     {
54         cpfree(set.table);
55 
56         cpArrayFreeEach(set.allocatedBuffers, &cpfree);
57         cpArrayFree(set.allocatedBuffers);
58 
59         cpfree(set);
60     }
61 }
62 
63 cpHashSet* cpHashSetNew(int size, cpHashSetEqlFunc eqlFunc)
64 {
65     cpHashSet* set = cast(cpHashSet*)cpcalloc(1, cpHashSet.sizeof);
66 
67     set.size    = next_prime(size);
68     set.entries = 0;
69 
70     set.eql = eqlFunc;
71     set.default_value = null;
72 
73     set.table      = cast(cpHashSetBin**)cpcalloc(set.size, (cpHashSetBin*).sizeof);
74     set.pooledBins = null;
75 
76     set.allocatedBuffers = cpArrayNew(0);
77 
78     return set;
79 }
80 
81 void cpHashSetSetDefaultValue(cpHashSet* set, void* default_value)
82 {
83     set.default_value = default_value;
84 }
85 
86 int setIsFull(cpHashSet* set)
87 {
88     return (set.entries >= set.size);
89 }
90 
91 void cpHashSetResize(cpHashSet* set)
92 {
93     // Get the next approximate doubled prime.
94     uint newSize = next_prime(set.size + 1);
95 
96     // Allocate a new table.
97     cpHashSetBin** newTable = cast(cpHashSetBin**)cpcalloc(newSize, (cpHashSetBin*).sizeof);
98 
99     // Iterate over the chains.
100     for (uint i = 0; i < set.size; i++)
101     {
102         // Rehash the bins into the new table.
103         cpHashSetBin* bin = set.table[i];
104 
105         while (bin)
106         {
107             cpHashSetBin* next = bin.next;
108 
109             cpHashValue idx = bin.hash % newSize;
110             bin.next     = newTable[idx];
111             newTable[idx] = bin;
112 
113             bin = next;
114         }
115     }
116 
117     cpfree(set.table);
118 
119     set.table = newTable;
120     set.size  = newSize;
121 }
122 
123 void recycleBin(cpHashSet* set, cpHashSetBin* bin)
124 {
125     bin.next       = set.pooledBins;
126     set.pooledBins = bin;
127     bin.elt        = null;
128 }
129 
130 cpHashSetBin* getUnusedBin(cpHashSet* set)
131 {
132     cpHashSetBin* bin = set.pooledBins;
133 
134     if (bin)
135     {
136         set.pooledBins = bin.next;
137         return bin;
138     }
139     else
140     {
141         // Pool is exhausted, make more
142         int count = CP_BUFFER_BYTES / cpHashSetBin.sizeof;
143         cpAssertHard(count, "Internal Error: Buffer size is too small.");
144 
145         cpHashSetBin* buffer = cast(cpHashSetBin*)cpcalloc(1, CP_BUFFER_BYTES);
146         cpArrayPush(set.allocatedBuffers, buffer);
147 
148         // push all but the first one, return it instead
149         for (int i = 1; i < count; i++)
150             recycleBin(set, buffer + i);
151 
152         return buffer;
153     }
154 }
155 
156 int cpHashSetCount(cpHashSet* set)
157 {
158     return set.entries;
159 }
160 
161 void* cpHashSetInsert(cpHashSet* set, cpHashValue hash, void* ptr, void* data, cpHashSetTransFunc trans)
162 {
163     cpHashValue idx = hash % set.size;
164 
165     // Find the bin with the matching element.
166     cpHashSetBin* bin = set.table[idx];
167 
168     while (bin && !set.eql(ptr, bin.elt))
169         bin = bin.next;
170 
171     // Create it if necessary.
172     if (!bin)
173     {
174         bin       = getUnusedBin(set);
175         bin.hash = hash;
176         bin.elt  = (trans ? trans(ptr, data) : data);
177 
178         bin.next       = set.table[idx];
179         set.table[idx] = bin;
180 
181         set.entries++;
182 
183         if (setIsFull(set))
184             cpHashSetResize(set);
185     }
186 
187     return bin.elt;
188 }
189 
190 void* cpHashSetRemove(cpHashSet* set, cpHashValue hash, void* ptr)
191 {
192     cpHashValue idx = hash % set.size;
193 
194     cpHashSetBin** prev_ptr = &set.table[idx];
195     cpHashSetBin * bin      = set.table[idx];
196 
197     // Find the bin
198     while (bin && !set.eql(ptr, bin.elt))
199     {
200         prev_ptr = &bin.next;
201         bin      = bin.next;
202     }
203 
204     // Remove it if it exists.
205     if (bin)
206     {
207         // Update the previous linked list pointer
208         (*prev_ptr) = bin.next;
209         set.entries--;
210 
211         void* elt = bin.elt;
212         recycleBin(set, bin);
213 
214         return elt;
215     }
216 
217     return null;
218 }
219 
220 void* cpHashSetFind(cpHashSet* set, cpHashValue hash, void* ptr)
221 {
222     cpHashValue idx   = hash % set.size;
223     cpHashSetBin* bin = set.table[idx];
224 
225     while (bin && !set.eql(ptr, bin.elt))
226         bin = bin.next;
227 
228     return (bin ? bin.elt : set.default_value);
229 }
230 
231 void cpHashSetEach(cpHashSet* set, cpHashSetIteratorFunc func, void* data)
232 {
233     for (uint i = 0; i < set.size; i++)
234     {
235         cpHashSetBin* bin = set.table[i];
236 
237         while (bin)
238         {
239             cpHashSetBin* next = bin.next;
240             func(bin.elt, data);
241             bin = next;
242         }
243     }
244 }
245 
246 void cpHashSetFilter(cpHashSet* set, cpHashSetFilterFunc func, void* data)
247 {
248     for (uint i = 0; i < set.size; i++)
249     {
250         // The rest works similarly to cpHashSetRemove() above.
251         cpHashSetBin** prev_ptr = &set.table[i];
252         cpHashSetBin * bin      = set.table[i];
253 
254         while (bin)
255         {
256             cpHashSetBin* next = bin.next;
257 
258             if (func(bin.elt, data))
259             {
260                 prev_ptr = &bin.next;
261             }
262             else
263             {
264                 (*prev_ptr) = next;
265 
266                 set.entries--;
267                 recycleBin(set, bin);
268             }
269 
270             bin = next;
271         }
272     }
273 }