Explorar o código

added OAHashMap type

Karroffel %!s(int64=8) %!d(string=hai) anos
pai
achega
add040d381

+ 0 - 33
core/hash_map.h

@@ -37,39 +37,6 @@
 #include "os/memory.h"
 #include "ustring.h"
 
-struct HashMapHasherDefault {
-	static _FORCE_INLINE_ uint32_t hash(const String &p_string) { return p_string.hash(); }
-	static _FORCE_INLINE_ uint32_t hash(const char *p_cstr) { return hash_djb2(p_cstr); }
-	static _FORCE_INLINE_ uint32_t hash(const uint64_t p_int) { return hash_one_uint64(p_int); }
-
-	static _FORCE_INLINE_ uint32_t hash(const int64_t p_int) { return hash(uint64_t(p_int)); }
-	static _FORCE_INLINE_ uint32_t hash(const float p_float) { return hash_djb2_one_float(p_float); }
-	static _FORCE_INLINE_ uint32_t hash(const double p_double) { return hash_djb2_one_float(p_double); }
-	static _FORCE_INLINE_ uint32_t hash(const uint32_t p_int) { return p_int; }
-	static _FORCE_INLINE_ uint32_t hash(const int32_t p_int) { return (uint32_t)p_int; }
-	static _FORCE_INLINE_ uint32_t hash(const uint16_t p_int) { return p_int; }
-	static _FORCE_INLINE_ uint32_t hash(const int16_t p_int) { return (uint32_t)p_int; }
-	static _FORCE_INLINE_ uint32_t hash(const uint8_t p_int) { return p_int; }
-	static _FORCE_INLINE_ uint32_t hash(const int8_t p_int) { return (uint32_t)p_int; }
-	static _FORCE_INLINE_ uint32_t hash(const wchar_t p_wchar) { return (uint32_t)p_wchar; }
-	//static _FORCE_INLINE_ uint32_t hash(const void* p_ptr)  { return uint32_t(uint64_t(p_ptr))*(0x9e3779b1L); }
-};
-
-template <typename T>
-struct HashMapComparatorDefault {
-	static bool compare(const T &p_lhs, const T &p_rhs) {
-		return p_lhs == p_rhs;
-	}
-
-	bool compare(const float &p_lhs, const float &p_rhs) {
-		return (p_lhs == p_rhs) || (Math::is_nan(p_lhs) && Math::is_nan(p_rhs));
-	}
-
-	bool compare(const double &p_lhs, const double &p_rhs) {
-		return (p_lhs == p_rhs) || (Math::is_nan(p_lhs) && Math::is_nan(p_rhs));
-	}
-};
-
 /**
  * @class HashMap
  * @author Juan Linietsky <[email protected]>

+ 34 - 0
core/hashfuncs.h

@@ -33,6 +33,7 @@
 #include "math_defs.h"
 #include "math_funcs.h"
 #include "typedefs.h"
+#include "ustring.h"
 
 /**
  * Hashing functions
@@ -128,4 +129,37 @@ static inline uint64_t make_uint64_t(T p_in) {
 	return _u._u64;
 }
 
+struct HashMapHasherDefault {
+	static _FORCE_INLINE_ uint32_t hash(const String &p_string) { return p_string.hash(); }
+	static _FORCE_INLINE_ uint32_t hash(const char *p_cstr) { return hash_djb2(p_cstr); }
+	static _FORCE_INLINE_ uint32_t hash(const uint64_t p_int) { return hash_one_uint64(p_int); }
+
+	static _FORCE_INLINE_ uint32_t hash(const int64_t p_int) { return hash(uint64_t(p_int)); }
+	static _FORCE_INLINE_ uint32_t hash(const float p_float) { return hash_djb2_one_float(p_float); }
+	static _FORCE_INLINE_ uint32_t hash(const double p_double) { return hash_djb2_one_float(p_double); }
+	static _FORCE_INLINE_ uint32_t hash(const uint32_t p_int) { return p_int; }
+	static _FORCE_INLINE_ uint32_t hash(const int32_t p_int) { return (uint32_t)p_int; }
+	static _FORCE_INLINE_ uint32_t hash(const uint16_t p_int) { return p_int; }
+	static _FORCE_INLINE_ uint32_t hash(const int16_t p_int) { return (uint32_t)p_int; }
+	static _FORCE_INLINE_ uint32_t hash(const uint8_t p_int) { return p_int; }
+	static _FORCE_INLINE_ uint32_t hash(const int8_t p_int) { return (uint32_t)p_int; }
+	static _FORCE_INLINE_ uint32_t hash(const wchar_t p_wchar) { return (uint32_t)p_wchar; }
+	//static _FORCE_INLINE_ uint32_t hash(const void* p_ptr)  { return uint32_t(uint64_t(p_ptr))*(0x9e3779b1L); }
+};
+
+template <typename T>
+struct HashMapComparatorDefault {
+	static bool compare(const T &p_lhs, const T &p_rhs) {
+		return p_lhs == p_rhs;
+	}
+
+	bool compare(const float &p_lhs, const float &p_rhs) {
+		return (p_lhs == p_rhs) || (Math::is_nan(p_lhs) && Math::is_nan(p_rhs));
+	}
+
+	bool compare(const double &p_lhs, const double &p_rhs) {
+		return (p_lhs == p_rhs) || (Math::is_nan(p_lhs) && Math::is_nan(p_rhs));
+	}
+};
+
 #endif

+ 593 - 0
core/oa_hash_map.h

@@ -0,0 +1,593 @@
+/*************************************************************************/
+/*  oa_hash_map.h                                                        */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md)    */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+#ifndef OA_HASH_MAP_H
+#define OA_HASH_MAP_H
+
+#include "hashfuncs.h"
+#include "math_funcs.h"
+#include "os/copymem.h"
+#include "os/memory.h"
+
+// uncomment this to disable intial local storage.
+#define OA_HASH_MAP_INITIAL_LOCAL_STORAGE
+
+/**
+ * This class implements a hash map datastructure that uses open addressing with
+ * local probing.
+ *
+ * It can give huge performance improvements over a chained HashMap because of
+ * the increased data locality.
+ *
+ * Because of that locality property it's important to not use "large" value
+ * types as the "TData" type. If TData values are too big it can cause more
+ * cache misses then chaining. If larger values are needed then storing those
+ * in a separate array and using pointers or indices to reference them is the
+ * better solution.
+ *
+ * This hash map also implements real-time incremental rehashing.
+ *
+ */
+template <class TKey, class TData,
+		uint16_t INITIAL_NUM_ELEMENTS = 64,
+		class Hasher = HashMapHasherDefault,
+		class Comparator = HashMapComparatorDefault<TKey> >
+class OAHashMap {
+
+private:
+#ifdef OA_HASH_MAP_INITIAL_LOCAL_STORAGE
+	TData local_data[INITIAL_NUM_ELEMENTS];
+	TKey local_keys[INITIAL_NUM_ELEMENTS];
+	uint32_t local_hashes[INITIAL_NUM_ELEMENTS];
+	uint8_t local_flags[INITIAL_NUM_ELEMENTS / 4 + (INITIAL_NUM_ELEMENTS % 4 != 0 ? 1 : 0)];
+#endif
+
+	struct {
+		TData *data;
+		TKey *keys;
+		uint32_t *hashes;
+
+		// This is actually an array of bits, 4 bit pairs per octet.
+		// | ba ba ba ba | ba ba ba ba | ....
+		//
+		// if a is set it means that there is an element present.
+		// if b is set it means that an element was deleted. This is needed for
+		//   the local probing to work without relocating any succeeding and
+		//    colliding entries.
+		uint8_t *flags;
+
+		uint32_t capacity;
+	} table, old_table;
+
+	bool is_rehashing;
+	uint32_t rehash_position;
+	uint32_t rehash_amount;
+
+	uint32_t elements;
+
+	/* Methods */
+
+	// returns true if the value already existed, false if it's a new entry
+	bool _raw_set_with_hash(uint32_t p_hash, const TKey &p_key, const TData &p_data) {
+		for (int i = 0; i < table.capacity; i++) {
+
+			int pos = (p_hash + i) % table.capacity;
+
+			int flags_pos = pos / 4;
+			int flags_pos_offset = pos % 4;
+
+			bool is_filled_flag = table.flags[flags_pos] & (1 << (2 * flags_pos_offset));
+			bool is_deleted_flag = table.flags[flags_pos] & (1 << (2 * flags_pos_offset + 1));
+
+			if (is_filled_flag) {
+				if (table.hashes[pos] == p_hash && Comparator::compare(table.keys[pos], p_key)) {
+					table.data[pos] = p_data;
+					return true;
+				}
+				continue;
+			}
+
+			table.keys[pos] = p_key;
+			table.data[pos] = p_data;
+			table.hashes[pos] = p_hash;
+
+			table.flags[flags_pos] |= (1 << (2 * flags_pos_offset));
+			table.flags[flags_pos] &= ~(1 << (2 * flags_pos_offset + 1));
+
+			return false;
+		}
+		return false;
+	}
+
+public:
+	_FORCE_INLINE_ uint32_t get_capacity() const { return table.capacity; }
+	_FORCE_INLINE_ uint32_t get_num_elements() const { return elements; }
+
+	void set(const TKey &p_key, const TData &p_data) {
+
+		uint32_t hash = Hasher::hash(p_key);
+
+		// We don't progress the rehashing if the table just got resized
+		// to keep the cost of this function low.
+		if (is_rehashing) {
+
+			// rehash progress
+
+			for (int i = 0; i <= rehash_amount && rehash_position < old_table.capacity; rehash_position++) {
+
+				int flags_pos = rehash_position / 4;
+				int flags_pos_offset = rehash_position % 4;
+
+				bool is_filled_flag = (old_table.flags[flags_pos] & (1 << (2 * flags_pos_offset))) > 0;
+				bool is_deleted_flag = (old_table.flags[flags_pos] & (1 << (2 * flags_pos_offset + 1))) > 0;
+
+				if (is_filled_flag) {
+					_raw_set_with_hash(old_table.hashes[rehash_position], old_table.keys[rehash_position], old_table.data[rehash_position]);
+
+					old_table.keys[rehash_position].~TKey();
+					old_table.data[rehash_position].~TData();
+
+					memnew_placement(&old_table.keys[rehash_position], TKey);
+					memnew_placement(&old_table.data[rehash_position], TData);
+
+					old_table.flags[flags_pos] &= ~(1 << (2 * flags_pos_offset));
+					old_table.flags[flags_pos] |= (1 << (2 * flags_pos_offset + 1));
+				}
+			}
+
+			if (rehash_position >= old_table.capacity) {
+
+				// wohooo, we can get rid of the old table.
+				is_rehashing = false;
+
+#ifdef OA_HASH_MAP_INITIAL_LOCAL_STORAGE
+				if (old_table.data == local_data) {
+					// Everything is local, so no cleanup :P
+				} else
+#endif
+				{
+					memdelete_arr(old_table.data);
+					memdelete_arr(old_table.keys);
+					memdelete_arr(old_table.hashes);
+					memdelete_arr(old_table.flags);
+				}
+			}
+		}
+
+		// Table is almost full, resize and start rehashing process.
+		if (elements >= table.capacity * 0.7) {
+
+			old_table.capacity = table.capacity;
+			old_table.data = table.data;
+			old_table.flags = table.flags;
+			old_table.hashes = table.hashes;
+			old_table.keys = table.keys;
+
+			table.capacity = old_table.capacity * 2;
+
+			table.data = memnew_arr(TData, table.capacity);
+			table.flags = memnew_arr(uint8_t, table.capacity / 4 + (table.capacity % 4 != 0 ? 1 : 0));
+			table.hashes = memnew_arr(uint32_t, table.capacity);
+			table.keys = memnew_arr(TKey, table.capacity);
+
+			zeromem(table.flags, table.capacity / 4 + (table.capacity % 4 != 0 ? 1 : 0));
+
+			is_rehashing = true;
+			rehash_position = 0;
+			rehash_amount = (elements * 2) / (table.capacity * 0.7 - old_table.capacity);
+		}
+
+		if (!_raw_set_with_hash(hash, p_key, p_data))
+			elements++;
+	}
+
+	/**
+	 * returns true if the value was found, false otherwise.
+	 *
+	 * if r_data is not NULL then the value will be written to the object
+	 * it points to.
+	 */
+	bool lookup(const TKey &p_key, TData *r_data) {
+
+		uint32_t hash = Hasher::hash(p_key);
+
+		bool check_old_table = is_rehashing;
+		bool check_new_table = true;
+
+		// search for the key and return the value associated with it
+		//
+		// if we're rehashing we need to check both the old and the
+		// current table. If we find a value in the old table we still
+		// need to continue searching in the new table as it might have
+		// been added after
+
+		TData *value = NULL;
+
+		for (int i = 0; i < table.capacity; i++) {
+
+			if (!check_new_table && !check_old_table) {
+
+				break;
+			}
+
+			// if we're rehashing check the old table
+			if (check_old_table && i < old_table.capacity) {
+
+				int pos = (hash + i) % old_table.capacity;
+
+				int flags_pos = pos / 4;
+				int flags_pos_offset = pos % 4;
+
+				bool is_filled_flag = (old_table.flags[flags_pos] & (1 << (2 * flags_pos_offset))) > 0;
+				bool is_deleted_flag = (old_table.flags[flags_pos] & (1 << (2 * flags_pos_offset + 1))) > 0;
+
+				if (is_filled_flag) {
+					// found our entry?
+					if (old_table.hashes[pos] == hash && Comparator::compare(old_table.keys[pos], p_key)) {
+						value = &old_table.data[pos];
+						check_old_table = false;
+					}
+				} else if (!is_deleted_flag) {
+
+					// we hit an empty field here, we don't
+					// need to further check this old table
+					// because we know it's not in here.
+
+					check_old_table = false;
+				}
+			}
+
+			if (check_new_table) {
+
+				int pos = (hash + i) % table.capacity;
+
+				int flags_pos = pos / 4;
+				int flags_pos_offset = pos % 4;
+
+				bool is_filled_flag = (table.flags[flags_pos] & (1 << (2 * flags_pos_offset))) > 0;
+				bool is_deleted_flag = (table.flags[flags_pos] & (1 << (2 * flags_pos_offset + 1))) > 0;
+
+				if (is_filled_flag) {
+					// found our entry?
+					if (table.hashes[pos] == hash && Comparator::compare(table.keys[pos], p_key)) {
+						if (r_data != NULL)
+							*r_data = table.data[pos];
+						return true;
+					}
+					continue;
+				} else if (is_deleted_flag) {
+					continue;
+				} else if (value != NULL) {
+
+					// We found a value in the old table
+					if (r_data != NULL)
+						*r_data = *value;
+					return true;
+				} else {
+					check_new_table = false;
+				}
+			}
+		}
+
+		if (value != NULL) {
+			if (r_data != NULL)
+				*r_data = *value;
+			return true;
+		}
+		return false;
+	}
+
+	_FORCE_INLINE_ bool has(const TKey &p_key) {
+		return lookup(p_key, NULL);
+	}
+
+	void remove(const TKey &p_key) {
+		uint32_t hash = Hasher::hash(p_key);
+
+		bool check_old_table = is_rehashing;
+		bool check_new_table = true;
+
+		for (int i = 0; i < table.capacity; i++) {
+
+			if (!check_new_table && !check_old_table) {
+				return;
+			}
+
+			// if we're rehashing check the old table
+			if (check_old_table && i < old_table.capacity) {
+
+				int pos = (hash + i) % old_table.capacity;
+
+				int flags_pos = pos / 4;
+				int flags_pos_offset = pos % 4;
+
+				bool is_filled_flag = (old_table.flags[flags_pos] & (1 << (2 * flags_pos_offset))) > 0;
+				bool is_deleted_flag = (old_table.flags[flags_pos] & (1 << (2 * flags_pos_offset + 1))) > 0;
+
+				if (is_filled_flag) {
+					// found our entry?
+					if (old_table.hashes[pos] == hash && Comparator::compare(old_table.keys[pos], p_key)) {
+						old_table.keys[pos].~TKey();
+						old_table.data[pos].~TData();
+
+						memnew_placement(&old_table.keys[pos], TKey);
+						memnew_placement(&old_table.data[pos], TData);
+
+						old_table.flags[flags_pos] &= ~(1 << (2 * flags_pos_offset));
+						old_table.flags[flags_pos] |= (1 << (2 * flags_pos_offset + 1));
+
+						elements--;
+						return;
+					}
+				} else if (!is_deleted_flag) {
+
+					// we hit an empty field here, we don't
+					// need to further check this old table
+					// because we know it's not in here.
+
+					check_old_table = false;
+				}
+			}
+
+			if (check_new_table) {
+
+				int pos = (hash + i) % table.capacity;
+
+				int flags_pos = pos / 4;
+				int flags_pos_offset = pos % 4;
+
+				bool is_filled_flag = (table.flags[flags_pos] & (1 << (2 * flags_pos_offset))) > 0;
+				bool is_deleted_flag = (table.flags[flags_pos] & (1 << (2 * flags_pos_offset + 1))) > 0;
+
+				if (is_filled_flag) {
+					// found our entry?
+					if (table.hashes[pos] == hash && Comparator::compare(table.keys[pos], p_key)) {
+						table.keys[pos].~TKey();
+						table.data[pos].~TData();
+
+						memnew_placement(&table.keys[pos], TKey);
+						memnew_placement(&table.data[pos], TData);
+
+						table.flags[flags_pos] &= ~(1 << (2 * flags_pos_offset));
+						table.flags[flags_pos] |= (1 << (2 * flags_pos_offset + 1));
+
+						// don't return here, this value might still be in the old table
+						// if it was already relocated.
+
+						elements--;
+						return;
+					}
+					continue;
+				} else if (is_deleted_flag) {
+					continue;
+				} else {
+					check_new_table = false;
+				}
+			}
+		}
+	}
+
+	struct Iterator {
+		bool valid;
+
+		uint32_t hash;
+
+		const TKey *key;
+		const TData *data;
+
+	private:
+		friend class OAHashMap;
+		bool was_from_old_table;
+	};
+
+	Iterator iter() const {
+		Iterator it;
+
+		it.valid = false;
+		it.was_from_old_table = false;
+
+		bool check_old_table = is_rehashing;
+
+		for (int i = 0; i < table.capacity; i++) {
+
+			// if we're rehashing check the old table first
+			if (check_old_table && i < old_table.capacity) {
+
+				int pos = i;
+
+				int flags_pos = pos / 4;
+				int flags_pos_offset = pos % 4;
+
+				bool is_filled_flag = (old_table.flags[flags_pos] & (1 << (2 * flags_pos_offset))) > 0;
+
+				if (is_filled_flag) {
+					it.valid = true;
+					it.hash = old_table.hashes[pos];
+					it.data = &old_table.data[pos];
+					it.key = &old_table.keys[pos];
+
+					it.was_from_old_table = true;
+
+					return it;
+				}
+			}
+
+			{
+
+				int pos = i;
+
+				int flags_pos = pos / 4;
+				int flags_pos_offset = pos % 4;
+
+				bool is_filled_flag = (table.flags[flags_pos] & (1 << (2 * flags_pos_offset))) > 0;
+
+				if (is_filled_flag) {
+					it.valid = true;
+					it.hash = table.hashes[pos];
+					it.data = &table.data[pos];
+					it.key = &table.keys[pos];
+
+					return it;
+				}
+			}
+		}
+
+		return it;
+	}
+
+	Iterator next_iter(const Iterator &p_iter) const {
+		if (!p_iter.valid) {
+			return p_iter;
+		}
+
+		Iterator it;
+
+		it.valid = false;
+		it.was_from_old_table = false;
+
+		bool check_old_table = is_rehashing;
+
+		// we use this to skip the first check or not
+		bool was_from_old_table = p_iter.was_from_old_table;
+
+		int prev_index = (p_iter.data - (p_iter.was_from_old_table ? old_table.data : table.data));
+
+		if (!was_from_old_table) {
+			prev_index++;
+		}
+
+		for (int i = prev_index; i < table.capacity; i++) {
+
+			// if we're rehashing check the old table first
+			if (check_old_table && i < old_table.capacity && !was_from_old_table) {
+
+				int pos = i;
+
+				int flags_pos = pos / 4;
+				int flags_pos_offset = pos % 4;
+
+				bool is_filled_flag = (old_table.flags[flags_pos] & (1 << (2 * flags_pos_offset))) > 0;
+
+				if (is_filled_flag) {
+					it.valid = true;
+					it.hash = old_table.hashes[pos];
+					it.data = &old_table.data[pos];
+					it.key = &old_table.keys[pos];
+
+					it.was_from_old_table = true;
+
+					return it;
+				}
+			}
+
+			was_from_old_table = false;
+
+			{
+				int pos = i;
+
+				int flags_pos = pos / 4;
+				int flags_pos_offset = pos % 4;
+
+				bool is_filled_flag = (table.flags[flags_pos] & (1 << (2 * flags_pos_offset))) > 0;
+
+				if (is_filled_flag) {
+					it.valid = true;
+					it.hash = table.hashes[pos];
+					it.data = &table.data[pos];
+					it.key = &table.keys[pos];
+
+					return it;
+				}
+			}
+		}
+
+		return it;
+	}
+
+	OAHashMap(uint32_t p_initial_capacity = INITIAL_NUM_ELEMENTS) {
+
+#ifdef OA_HASH_MAP_INITIAL_LOCAL_STORAGE
+
+		if (p_initial_capacity <= INITIAL_NUM_ELEMENTS) {
+			table.data = local_data;
+			table.keys = local_keys;
+			table.hashes = local_hashes;
+			table.flags = local_flags;
+
+			zeromem(table.flags, INITIAL_NUM_ELEMENTS / 4 + (INITIAL_NUM_ELEMENTS % 4 != 0 ? 1 : 0));
+
+			table.capacity = INITIAL_NUM_ELEMENTS;
+			elements = 0;
+		} else
+#endif
+		{
+			table.data = memnew_arr(TData, p_initial_capacity);
+			table.keys = memnew_arr(TKey, p_initial_capacity);
+			table.hashes = memnew_arr(uint32_t, p_initial_capacity);
+			table.flags = memnew_arr(uint8_t, p_initial_capacity / 4 + (p_initial_capacity % 4 != 0 ? 1 : 0));
+
+			zeromem(table.flags, p_initial_capacity / 4 + (p_initial_capacity % 4 != 0 ? 1 : 0));
+
+			table.capacity = p_initial_capacity;
+			elements = 0;
+		}
+
+		is_rehashing = false;
+		rehash_position = 0;
+	}
+
+	~OAHashMap() {
+#ifdef OA_HASH_MAP_INITIAL_LOCAL_STORAGE
+		if (table.capacity <= INITIAL_NUM_ELEMENTS) {
+			return; // Everything is local, so no cleanup :P
+		}
+#endif
+		if (is_rehashing) {
+
+#ifdef OA_HASH_MAP_INITIAL_LOCAL_STORAGE
+			if (old_table.data == local_data) {
+				// Everything is local, so no cleanup :P
+			} else
+#endif
+			{
+				memdelete_arr(old_table.data);
+				memdelete_arr(old_table.keys);
+				memdelete_arr(old_table.hashes);
+				memdelete_arr(old_table.flags);
+			}
+		}
+
+		memdelete_arr(table.data);
+		memdelete_arr(table.keys);
+		memdelete_arr(table.hashes);
+		memdelete_arr(table.flags);
+	}
+};
+
+#endif

+ 7 - 0
main/tests/test_main.cpp

@@ -37,6 +37,7 @@
 #include "test_image.h"
 #include "test_io.h"
 #include "test_math.h"
+#include "test_oa_hash_map.h"
 #include "test_ordered_hash_map.h"
 #include "test_physics.h"
 #include "test_physics_2d.h"
@@ -56,6 +57,7 @@ const char **tests_get_names() {
 		"io",
 		"shaderlang",
 		"physics",
+		"oa_hash_map",
 		NULL
 	};
 
@@ -89,6 +91,11 @@ MainLoop *test_main(String p_test, const List<String> &p_args) {
 		return TestRender::test();
 	}
 
+	if (p_test == "oa_hash_map") {
+
+		return TestOAHashMap::test();
+	}
+
 #ifndef _3D_DISABLED
 	if (p_test == "gui") {
 

+ 97 - 0
main/tests/test_oa_hash_map.cpp

@@ -0,0 +1,97 @@
+/*************************************************************************/
+/*  test_oa_hash_map.h                                                   */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md)    */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#include "test_oa_hash_map.h"
+
+#include "core/os/os.h"
+
+#include "core/oa_hash_map.h"
+
+namespace TestOAHashMap {
+
+MainLoop *test() {
+
+	OS::get_singleton()->print("\n\n\nHello from test\n");
+
+	// test element tracking.
+	{
+		OAHashMap<int, int> map;
+
+		map.set(42, 1337);
+		map.set(1337, 21);
+		map.set(42, 11880);
+
+		int value;
+		map.lookup(42, &value);
+
+		OS::get_singleton()->print("capacity  %d\n", map.get_capacity());
+		OS::get_singleton()->print("elements  %d\n", map.get_num_elements());
+
+		OS::get_singleton()->print("map[42] = %d\n", value);
+	}
+
+	// rehashing and deletion
+	{
+		OAHashMap<int, int> map;
+
+		for (int i = 0; i < 500; i++) {
+			map.set(i, i * 2);
+		}
+
+		for (int i = 0; i < 500; i += 2) {
+			map.remove(i);
+		}
+
+		uint32_t num_elems = 0;
+		for (int i = 0; i < 500; i++) {
+			int tmp;
+			if (map.lookup(i, &tmp))
+				num_elems++;
+		}
+
+		OS::get_singleton()->print("elements %d == %d.\n", map.get_num_elements(), num_elems);
+	}
+
+	// iteration
+	{
+		OAHashMap<String, int> map;
+
+		map.set("Hello", 1);
+		map.set("World", 2);
+		map.set("Godot rocks", 42);
+
+		for (OAHashMap<String, int>::Iterator it = map.iter(); it.valid; it = map.next_iter(it)) {
+			OS::get_singleton()->print("map[\"%s\"] = %d\n", it.key->utf8().get_data(), *it.data);
+		}
+	}
+
+	return NULL;
+}
+} // namespace TestOAHashMap

+ 39 - 0
main/tests/test_oa_hash_map.h

@@ -0,0 +1,39 @@
+/*************************************************************************/
+/*  test_oa_hash_map.h                                                   */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md)    */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+#ifndef TEST_OA_HASH_MAP_H
+#define TEST_OA_HASH_MAP_H
+
+#include "os/main_loop.h"
+
+namespace TestOAHashMap {
+
+MainLoop *test();
+}
+#endif // TEST_OA_HASH_MAP_H