1 /** 2 Copyright: Copyright (c) 2017-2018 Andrey Penechko. 3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 4 Authors: Andrey Penechko. 5 */ 6 module voxelman.container.hash.hashtableparts; 7 8 //version = DEBUG_TRACE; 9 //version = DEBUG_INVARIANT; 10 11 /// If store_values = false then table works like hashset 12 mixin template HashTablePart(KeyBucketT, bool store_values) 13 { 14 size_t length; // num of used buckets 15 size_t occupiedBuckets; // num of used + deleted buckets 16 size_t capacity; // array length 17 KeyBucketT* keyBuckets; 18 version(DEBUG_TRACE) void* debugId; 19 20 alias allocator = Alloc.instance; 21 enum USES_GC = is(Alloc == GCAllocator); 22 23 static if (store_values) 24 { 25 enum Bucket_size = KeyBucketT.sizeof + Value.sizeof; 26 mixin HashMapImpl; 27 } 28 else 29 { 30 enum Bucket_size = KeyBucketT.sizeof; 31 mixin HashSetImpl; 32 } 33 34 pragma(inline, true) 35 private static size_t getHash(Key key) { 36 import std.traits : isIntegral; 37 static if (isIntegral!Key) 38 return cast(size_t)key; 39 else 40 return hashOf(key); 41 } 42 43 @property bool empty() const { return length == 0; } 44 45 /// Returns false if no value was deleted, true otherwise 46 bool remove(Key key) 47 { 48 auto index = findIndex(key); 49 if (index == size_t.max) return false; 50 removeAtIndex(index); 51 version(DEBUG_TRACE) tracef("[%s] remove %s at %s cap %s len %s", debugId, key, index, capacity, length); 52 return true; 53 } 54 55 void clear() { 56 KeyBucketT.clearBuckets(keyBuckets[0..capacity]); 57 occupiedBuckets = length = 0; 58 version(DEBUG_INVARIANT) checkUsed(); 59 } 60 61 void reserve(size_t amount) 62 { 63 import std.stdio; 64 import voxelman.math : nextPOT; 65 66 // check current bucket array for free space (including deleted items) 67 auto requiredCapacity = (occupiedBuckets + amount) * 2; 68 if (requiredCapacity <= capacity) return; 69 70 // calculate required capacity without deleted items (since we allocate new array) 71 auto newRequiredCapacity = (length + amount) * 2; 72 auto newCapacity = nextPOT(newRequiredCapacity); 73 resize(newCapacity); 74 } 75 76 private void removeAtIndex(size_t index) 77 { 78 keyBuckets[index].markAsDeleted(); 79 --length; 80 version(DEBUG_INVARIANT) checkUsed(); 81 // TODO mark as empty if empty to the right 82 } 83 84 import std.stdio; 85 import voxelman.log; 86 private size_t findIndex(Key key) const 87 { 88 if (length == 0) return size_t.max; 89 auto index = getHash(key) & (capacity - 1); 90 version(DEBUG_TRACE) tracef("[%s] find %s at %s cap %s len %s", debugId, key, index, capacity, length); 91 while (true) { 92 //assert(!keyBuckets[index].corrupted); 93 94 if (keyBuckets[index].key == key && keyBuckets[index].used) 95 { 96 version(DEBUG_TRACE) tracef("* U @%s key %s", index, keyBuckets[index].key); 97 return index; 98 } 99 100 // we will find empty key eventually since we don't allow full hashmap 101 if (keyBuckets[index].empty) 102 { 103 version(DEBUG_TRACE) tracef("* E @%s key %s", index, keyBuckets[index].key); 104 return size_t.max; 105 } 106 index = (index + 1) & (capacity - 1); 107 } 108 } 109 110 private size_t findInsertIndex(Key key) const 111 { 112 size_t index = getHash(key) & (capacity - 1); 113 version(DEBUG_TRACE) tracef("[%s] insert %s at %s cap %s", debugId, key, index, capacity); 114 while (!keyBuckets[index].canInsert(key)) 115 { 116 version(DEBUG_TRACE) { 117 if (keyBuckets[index].used) 118 tracef(" U @%s key %s", index, keyBuckets[index].key); 119 else if (keyBuckets[index].deleted) 120 tracef(" D @%s key %s", index, keyBuckets[index].key); 121 } 122 index = (index + 1) & (capacity - 1); 123 } 124 version(DEBUG_TRACE) { 125 if (keyBuckets[index].empty) 126 tracef("* E @%s key %s", index, keyBuckets[index].key); 127 else if (keyBuckets[index].used) { 128 tracef("* U @%s key %s", index, keyBuckets[index].key); 129 } else { 130 tracef("* D @%s key %s", index, keyBuckets[index].key); 131 } 132 } 133 return index; 134 } 135 136 private void resize(size_t newCapacity) 137 { 138 import std.experimental.allocator; 139 import std.random; 140 141 version(DEBUG_TRACE) { 142 if (capacity == 0) { 143 debugId = cast(void*)uniform(0, ushort.max); 144 } 145 } 146 147 auto oldKeyBuckets = keyBuckets; 148 static if (store_values) { 149 auto oldValues = values[0..capacity]; 150 } 151 152 auto oldCapacity = capacity; 153 154 // findInsertIndex below uses new capacity 155 capacity = newCapacity; 156 occupiedBuckets = length; 157 158 if (newCapacity) 159 { 160 void[] array = allocator.allocate(Bucket_size * newCapacity); 161 setStorageArray(array, newCapacity); 162 keyBuckets[0..newCapacity] = KeyBucketT.init; 163 164 static if (store_values && USES_GC) 165 { 166 import core.memory; 167 GC.addRange(values, capacity * Value.sizeof, typeid(Value)); 168 } 169 170 version(DEBUG_TRACE) tracef("[%s] resize from %s to %s, len %s", debugId, oldCapacity, newCapacity, length); 171 foreach (i, ref bucket; oldKeyBuckets[0..oldCapacity]) 172 { 173 if (bucket.used) 174 { 175 auto index = findInsertIndex(bucket.key); 176 keyBuckets[index] = bucket; 177 version(DEBUG_TRACE) tracef(" move %s, %s -> %s", bucket.key, i, index); 178 static if (store_values) values[index] = oldValues[i]; 179 } 180 } 181 } 182 else 183 { 184 keyBuckets = null; 185 static if (store_values) values = null; 186 } 187 188 if (oldKeyBuckets) { 189 void[] arr = (cast(void*)oldKeyBuckets)[0..Bucket_size * oldCapacity]; 190 allocator.deallocate(arr); 191 } 192 } 193 194 void[] getStorage() { 195 return (cast(void*)keyBuckets)[0..Bucket_size * capacity]; 196 } 197 198 /// data must be allocated with allocator of this object 199 void setStorage(void[] data, size_t length, size_t occupiedBuckets) { 200 this.length = length; 201 this.capacity = data.length / Bucket_size; 202 this.occupiedBuckets = occupiedBuckets; 203 setStorageArray(data, capacity); 204 version(DEBUG_INVARIANT) checkUsed(); 205 } 206 207 private void setStorageArray(void[] data, size_t capacity) { 208 keyBuckets = cast(KeyBucketT*)(data.ptr); 209 static if (store_values) 210 values = cast(Value*)(data.ptr + capacity*KeyBucketT.sizeof); 211 } 212 213 private void checkUsed() { 214 import std..string; 215 auto used = calcUsed(); 216 assert(used == length, format("%s != %s", used, length)); 217 } 218 219 private size_t calcUsed() { 220 size_t totalUsed; 221 foreach (bucket; keyBuckets[0..capacity]) 222 totalUsed += bucket.used; 223 return totalUsed; 224 } 225 } 226 227 mixin template HashMapImpl() 228 { 229 import std.stdio; 230 import std..string; 231 Value* values; 232 233 alias KeyT = Key; 234 alias ValueT = Value; 235 236 /// Removes value via pointer returned by getOrCreate or opIn 237 /// Prevents extra lookup 238 void removeByPtr(Value* value) { 239 auto idx = indexFromPtr(value); 240 if (idx == size_t.max) return; 241 removeAtIndex(idx); 242 } 243 244 alias put = tryAssignValue; 245 246 void opIndexAssign(Value value, Key key) 247 { 248 tryAssignValue(key, value); 249 } 250 251 import voxelman.log; 252 private Value* assignValue(Key key, Value value) 253 { 254 size_t index = findInsertIndex(key); 255 if (keyBuckets[index].empty) { 256 ++occupiedBuckets; 257 ++length; 258 } 259 else if (keyBuckets[index].deleted) { 260 assert(keyBuckets[index].key == key, 261 "trying to insert item into deleted bucket when keys aren't equal"); 262 ++length; 263 } 264 else { 265 assert(keyBuckets[index].key == key); 266 assert(keyBuckets[index].used); 267 } 268 keyBuckets[index].assignKey(key); 269 values[index] = value; 270 version(DEBUG_INVARIANT) checkUsed(); 271 version(DEBUG_TRACE) tracef("[%s] = %s at %s, length %s", key, value, index, length); 272 return &values[index]; 273 } 274 275 private size_t indexFromPtr(Value* value) { 276 auto offset = value - values; 277 if (offset > capacity || offset < 0) return size_t.max; 278 return cast(size_t)offset; 279 } 280 281 inout(Value)* opBinaryRight(string op)(Key key) inout if (op == "in") 282 { 283 auto index = findIndex(key); 284 //tracef("in index %s", index); 285 if (index == size_t.max) return null; 286 return &values[index]; 287 } 288 289 ref inout(Value) opIndex(Key key) inout 290 { 291 import std.exception : enforce; 292 auto index = findIndex(key); 293 enforce(index != size_t.max, "Non-existing key access"); 294 return values[index]; 295 } 296 297 Value get(Key key, Value default_value = Value.init) 298 { 299 auto index = findIndex(key); 300 //tracef("get %s index %s len %s", key, index, length); 301 if (index == size_t.max) return default_value; 302 return values[index]; 303 } 304 305 Value* getOrCreate(Key key, Value default_value = Value.init) 306 { 307 auto index = findIndex(key); 308 //tracef("getOrCreate index %s", index); 309 if (index == size_t.max) return tryAssignValue(key, default_value); 310 return &values[index]; 311 } 312 313 Value* getOrCreate(Key key, out bool wasCreated, Value default_value = Value.init) 314 { 315 auto index = findIndex(key); 316 317 if (index == size_t.max) 318 { 319 wasCreated = true; 320 return tryAssignValue(key, default_value); 321 } 322 323 return &values[index]; 324 } 325 326 private Value* tryAssignValue(Key key, Value value) 327 { 328 if ((capacity >> 2) > occupiedBuckets) 329 { 330 return assignValue(key, value); 331 } 332 else 333 { 334 reserve(1); 335 return assignValue(key, value); 336 } 337 } 338 339 int opApply(scope int delegate(ref Value) del) { 340 foreach (i, ref bucket; keyBuckets[0..capacity]) 341 if (bucket.used) 342 if (auto ret = del(values[i])) 343 return ret; 344 return 0; 345 } 346 347 int opApply(scope int delegate(in Key, ref Value) del) { 348 foreach (i, ref bucket; keyBuckets[0..capacity]) 349 if (bucket.used) 350 if (auto ret = del(bucket.key, values[i])) 351 return ret; 352 return 0; 353 } 354 355 auto byKey() { 356 alias HM_TYPE = typeof(this); 357 static struct ByKey { 358 HM_TYPE* hashmap; 359 int opApply(scope int delegate(Key) del) { 360 foreach (bucket; hashmap.keyBuckets[0..hashmap.capacity]) 361 if (bucket.used) 362 if (auto ret = del(bucket.key)) 363 return ret; 364 return 0; 365 } 366 } 367 return ByKey(&this); 368 } 369 370 void printBuckets() { 371 size_t totalUsed; 372 foreach (index, bucket; keyBuckets[0..capacity]) 373 { 374 if (bucket.empty) 375 tracef("E %s %s", bucket.key, values[index]); 376 else if (bucket.used) { 377 ++totalUsed; 378 tracef("U %s %s", bucket.key, values[index]); 379 } 380 else if (bucket.deleted) 381 tracef("D %s %s", bucket.key, values[index]); 382 } 383 tracef("totalUsed %s length %s capacity %s", totalUsed, length, capacity); 384 } 385 386 void toString()(scope void delegate(const(char)[]) sink) 387 { 388 import std.format : formattedWrite; 389 sink.formattedWrite("[",); 390 foreach(key, value; this) 391 { 392 sink.formattedWrite("%s:%s, ", key, value); 393 } 394 sink.formattedWrite("]"); 395 } 396 } 397 398 mixin template HashSetImpl() 399 { 400 void put(Key key) 401 { 402 if ((capacity >> 2) > occupiedBuckets) 403 { 404 putKey(key); 405 } 406 else 407 { 408 reserve(1); 409 putKey(key); 410 } 411 } 412 413 private void putKey(Key key) 414 { 415 size_t index = findInsertIndex(key); 416 if (keyBuckets[index].empty) { 417 ++occupiedBuckets; 418 ++length; 419 } 420 else if (keyBuckets[index].deleted) { 421 ++length; 422 } 423 else { 424 assert(keyBuckets[index].key == key); 425 assert(keyBuckets[index].used); 426 } 427 keyBuckets[index].assignKey(key); 428 version(DEBUG_INVARIANT) checkUsed(); 429 } 430 431 bool opBinaryRight(string op)(Key key) const if(op == "in") { 432 auto index = findIndex(key); 433 return index != size_t.max; 434 } 435 436 bool opIndex(Key key) inout 437 { 438 auto index = findIndex(key); 439 return index != size_t.max; 440 } 441 442 int opApply(scope int delegate(in Key) del) const { 443 foreach (ref bucket; keyBuckets[0..capacity]) 444 if (bucket.used) 445 if (auto ret = del(bucket.key)) 446 return ret; 447 return 0; 448 } 449 450 void toString()(scope void delegate(const(char)[]) sink) 451 { 452 import std.format : formattedWrite; 453 sink.formattedWrite("[",); 454 foreach(key; this) 455 { 456 sink.formattedWrite("%s, ", key); 457 } 458 sink.formattedWrite("]"); 459 } 460 461 void dumpBuckets() { 462 tracef("Buckets %s %s", cast(void*)keyBuckets, keyBuckets[0..capacity]); 463 } 464 465 void printBuckets() { 466 size_t totalUsed; 467 foreach (bucket; keyBuckets[0..capacity]) 468 { 469 if (bucket.empty) 470 tracef("E %s", bucket.key); 471 else if (bucket.used) { 472 ++totalUsed; 473 tracef("U %s", bucket.key); 474 } 475 else if (bucket.deleted) 476 tracef("D %s", bucket.key); 477 } 478 tracef("totalUsed %s length %s capacity %s", totalUsed, length, capacity); 479 } 480 }