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 }