1 /**
2     Internal hash map implementation.
3 
4     Copyright: © 2013 RejectedSoftware e.K.
5     License: Subject to the terms of the MIT license, as written in the included LICENSE file.
6     Authors: Sönke Ludwig
7 */
8 module memutils.hashmap;
9 
10 import std.conv : emplace, to;
11 import std.traits;
12 import std.algorithm : countUntil;
13 import memutils.refcounted;
14 import memutils.constants;
15 import memutils.helpers;
16 import memutils.allocators;
17 import memutils.utils;
18 
19 
20 alias HashMapRef(Key, Value, ALLOC = ThreadMem) = RefCounted!(HashMap!(Key, Value, ALLOC), ALLOC);
21 
22 struct HashMap(Key, Value, ALLOC = ThreadMem)
23 {
24 	@disable this(this);
25 
26 	alias Traits = DefaultHashMapTraits!Key;
27 	struct TableEntry {
28 		UnConst!Key key;
29 		Value value;
30 		
31 		this(Key key, Value value) { this.key = cast(UnConst!Key)key; this.value = value; }
32 	}
33 	private {
34 		TableEntry[] m_table; // NOTE: capacity is always POT
35 		size_t m_length;
36 		hash_t delegate(Key) m_hasher;
37 		bool m_resizing;
38 	}
39 	
40 	~this()
41 	{
42 		if (m_table) freeArray!(TableEntry, ALLOC)(m_table);
43 	}
44 
45 	@property bool empty() const { return length == 0; }
46 	@property size_t length() const { return m_length; }
47 	
48 	void remove(Key key)
49 	{
50 		static if (Key.stringof.countUntil("OIDImpl") != -1) logError("Removing: ", key.toString());
51 		auto idx = findIndex(key);
52 		assert (idx != size_t.max, "Removing non-existent element.");
53 		auto i = idx;
54 		while (true) {
55 			m_table[i].key = Traits.clearValue;
56 			m_table[i].value = Value.init;
57 			size_t j = i, r;
58 			do {
59 				if (++i >= m_table.length) i -= m_table.length;
60 				if (Traits.equals(m_table[i].key, Traits.clearValue)) {
61 					m_length--;
62 					return;
63 				}
64 				r = m_hasher(m_table[i].key) & (m_table.length-1);
65 
66 			} while ((j<r && r<=i) || (i<j && j<r) || (r<=i && i<j));
67 			m_table[j] = m_table[i];
68 		}
69 	}
70 	
71 	Value get(in Key key, lazy Value default_value = Value.init) const
72 	{
73 		auto idx = this.findIndex(key);
74 		if (idx == size_t.max) return default_value;
75 		const Value ret = m_table[idx].value;
76 		return *cast(Value*)&ret;
77 	}
78 	/*	
79 	static if (!is(typeof({ Value v; const(Value) vc; v = vc; }))) {
80 		const(Value) get(Key key, lazy const(Value) default_value = Value.init)
81 		{
82 			auto idx = findIndex(key);
83 			if (idx == size_t.max) return default_value;
84 			return m_table[idx].value;
85 		}
86 	}*/
87 	
88 	void clear()
89 	{
90 		foreach (i; 0 .. m_table.length)
91 		if (!Traits.equals(m_table[i].key, Traits.clearValue)) {
92 			m_table[i].key = Traits.clearValue;
93 			m_table[i].value = Value.init;
94 		}
95 		m_length = 0;
96 	}
97 	
98 	void set(Key key, Value value) {
99 		opIndexAssign(value, key);
100 	}
101 
102 	
103 	void opIndexAssign(inout Value value, in Key key)
104 	{
105 		//assert(!Traits.equals(key, Traits.clearValue), "Inserting clear value into hash map.");
106 		grow(1);
107 		auto i = findInsertIndex(key);
108 		if (!Traits.equals(m_table[i].key, key)) m_length++;
109 		m_table[i] = TableEntry(*cast(Key*) &key, *cast(Value*) &value);
110 	}
111 	
112 	ref inout(Value) opIndex(Key key) inout {
113 		auto idx = findIndex(key);
114 		assert (idx != size_t.max, "Accessing non-existent key type: " ~ Key.stringof ~ " value: " ~ key.to!string);
115 		return m_table[idx].value;
116 	}
117 	
118 	Value opIndex(Key key) const {
119 		auto idx = findIndex(key);
120 		assert (idx != size_t.max, "Accessing non-existent key type: " ~ Key.stringof ~ " value: " ~ key.to!string);
121 		const Value ret = m_table[idx].value;
122 		return *cast(Value*) &ret;
123 	}
124 	
125 	inout(Value)* opBinaryRight(string op)(Key key)
126 	inout if (op == "in") {
127 		auto idx = findIndex(key);
128 		if (idx == size_t.max) return null;
129 		return &m_table[idx].value;
130 	}
131 	
132 	int opApply(int delegate(ref Value) del)
133 	{
134 		foreach (i; 0 .. m_table.length)
135 			if (!Traits.equals(m_table[i].key, Traits.clearValue))
136 				if (auto ret = del(m_table[i].value))
137 					return ret;
138 		return 0;
139 	}
140 	
141 	int opApply(int delegate(in ref Value) del)
142 	const {
143 		foreach (i; 0 .. m_table.length)
144 			if (!Traits.equals(m_table[i].key, Traits.clearValue))
145 				if (auto ret = del(m_table[i].value))
146 					return ret;
147 		return 0;
148 	}
149 	
150 	int opApply(int delegate(const ref Key, ref Value) del)
151 	{
152 		foreach (i; 0 .. m_table.length)
153 			if (!Traits.equals(m_table[i].key, Traits.clearValue))
154 				if (auto ret = del(m_table[i].key, m_table[i].value))
155 					return ret;
156 		return 0;
157 	}
158 	
159 	int opApply(int delegate(in ref Key, in ref Value) del)
160 	const {
161 		foreach (i; 0 .. m_table.length)
162 			if (!Traits.equals(m_table[i].key, Traits.clearValue))
163 				if (auto ret = del(m_table[i].key, m_table[i].value))
164 					return ret;
165 		return 0;
166 	}
167 	
168 	private size_t findIndex(in Key key)
169 	const {
170 		if (m_length == 0) return size_t.max;
171 		size_t start = m_hasher(*cast(Key*) &key) & (m_table.length-1);
172 		auto i = start;
173 		while (!Traits.equals(m_table[i].key, key)) {
174 			if (Traits.equals(m_table[i].key, Traits.clearValue)) return size_t.max;
175 			if (++i >= m_table.length) i -= m_table.length;
176 			if (i == start) return size_t.max;
177 		}
178 		return i;
179 	}
180 	
181 	private size_t findInsertIndex(in Key key)
182 	const {
183 		auto hash = m_hasher(*cast(Key*) &key);
184 		size_t target = hash & (m_table.length-1);
185 		auto i = target;
186 		while (!Traits.equals(m_table[i].key, Traits.clearValue) && !Traits.equals(m_table[i].key, key)) {
187 			if (++i >= m_table.length) i -= m_table.length;
188 			assert (i != target, "No free bucket found, HashMap full!?");
189 		}
190 		return i;
191 	}
192 	
193 	private void grow(size_t amount)
194 	{
195 		auto newsize = m_length + amount;
196 		if (newsize < (m_table.length*2)/3) return;
197 		auto newcap = m_table.length ? m_table.length : 16;
198 		while (newsize >= (newcap*2)/3) newcap *= 2;
199 		resize(newcap);
200 	}
201 
202 	private void resize(size_t new_size)
203 	{
204 		assert(!m_resizing);
205 		m_resizing = true;
206 		scope(exit) m_resizing = false;
207 		
208 		if (!m_hasher) {
209 			setupHasher();
210 		}
211 
212 		// logTrace("Resizing ", Key.stringof, " : ", Value.stringof, " : ", cast(void*)&this, " from ", m_table.length, " to: ", new_size);
213 
214 		uint pot = 0;
215 		while (new_size > 1) pot++, new_size /= 2;
216 		new_size = 1 << pot;
217 		auto old_size = m_table.length;
218 		assert(new_size > old_size); // TODO: Allow hashmap to become smaller?
219 		auto oldtable = m_table;
220 		m_table = allocArray!(TableEntry, ALLOC)(new_size);
221 
222 		/// fixme: Use initializeAll ?
223 		/// 
224 		foreach (ref el; m_table) {
225 			static if (is(Key == struct)) {
226 				emplace(cast(UnConst!Key*)&el.key);
227 				static if (Traits.clearValue !is Key.init)
228 					el.key = cast(UnConst!Key)Traits.clearValue;
229 			} else el.key = cast(UnConst!Key)Traits.clearValue;
230 			emplace(&el.value);
231 		}
232 		foreach (ref el; oldtable)
233 		if (!Traits.equals(el.key, Traits.clearValue)) {
234 			auto idx = findInsertIndex(el.key);
235 			(cast(ubyte[])(&m_table[idx])[0 .. 1])[] = (cast(ubyte[])(&el)[0 .. 1])[];
236 		}
237 
238 		if (oldtable) freeArray!(TableEntry, ALLOC)(oldtable, 0);
239 		logTrace("Now have ", m_table.length);
240 	}
241 
242 	void setupHasher() {
243 
244 		
245 		static if ((__traits(hasMember, Key, "isRefCounted") && __traits(hasMember, typeof(*(Key())), "toArray") ) ||
246 			__traits(hasMember, Key, "toArray"))
247 		{
248 			m_hasher = (Key k) {
249 				import std.typecons : scoped;
250 				import memutils.vector : Array;
251 				Array!ubyte s = k.toArray();
252 				size_t hash = hashOf(s[], 0);
253 				return hash;
254 			};
255 		}
256 		else static if ((__traits(hasMember, Key, "isRefCounted") && __traits(hasMember, typeof(*(Key())), "toVector") ) ||
257 			__traits(hasMember, Key, "toVector"))
258 		{
259 			m_hasher = (Key k) {
260 				import std.typecons : scoped;
261 				import memutils.vector : Vector;
262 				Vector!char s = k.toVector();
263 				size_t hash = hashOf(s[], 0);
264 				return hash;
265 			};
266 		}
267 		else static if (( __traits(hasMember, Key, "isRefCounted") && __traits(hasMember, typeof(*(Key())), "toString") ) ||
268 			__traits(hasMember, Key, "toString"))
269 		{
270 			m_hasher = (Key k) {
271 				string s = k.toString();
272 				size_t hash = typeid(string).getHash(&s);
273 				// logTrace("string ", s, " hash:" , hash);
274 				return hash;
275 			};
276 		}
277 		
278 		else static if (__traits(compiles, (){ Key t; size_t hash = t.toHash(); }())) {
279 			static if (isPointer!Key || is(Unqual!Key == class)) m_hasher = k => k ? k.toHash() : 0;
280 			else m_hasher = k => k.toHash();
281 		} else static if (__traits(compiles, (){ Key t; size_t hash = t.toHashShared(); }())) {
282 			static if (isPointer!Key || is(Unqual!Key == class)) m_hasher = k => k ? k.toHashShared() : 0;
283 			else m_hasher = k => k.toHashShared();
284 		} 
285 		else static if (__traits(hasMember, Key, "isRefCounted")) {
286 			m_hasher = (k) { return typeid(typeof(*(Key()))).getHash(&k); };
287 		}
288 		else {
289 			m_hasher = (k) { return typeid(Key).getHash(&k); };
290 		}
291 	}
292 }
293 
294 
295 struct DefaultHashMapTraits(Key) {
296 	enum clearValue = Key.init;
297 	static bool equals(in Key a, in Key b)
298 	{
299 		static if (__traits(hasMember, Key, "isRefCounted") && 
300 			is (typeof(*(Key())) == class) && 
301 			__traits(compiles, "bool c = a.opEquals(*b);"))
302 		{
303 			if (!a && b) {
304 				return b.opEquals(*a);
305 			}
306 			else if (a && !b) {
307 				return a.opEquals(*b);
308 			}
309 			else if (a && b)
310 			{
311 				return a.opEquals(*b);
312 			}
313 			else {
314 				return true;
315 			}
316 		}
317 		else static if (__traits(hasMember, Key, "isRefCounted") && is (typeof(*(Key())) == class)) {
318 			return *a is *b;
319 		}
320 		else static if (isPointer!Key) {
321 			return a is b;
322 		}
323 		else {
324 			return a == b;
325 		}
326 	}
327 }