Browse Source

Replace unordered_map with AHashMap

dementive 4 weeks ago
parent
commit
2fd41b7e16
3 changed files with 776 additions and 52 deletions
  1. 12 22
      include/godot_cpp/core/class_db.hpp
  2. 734 0
      include/godot_cpp/templates/a_hash_map.hpp
  3. 30 30
      src/core/class_db.cpp

+ 12 - 22
include/godot_cpp/core/class_db.hpp

@@ -44,20 +44,10 @@
 // Needs to come after method_bind and object have been included.
 #include <godot_cpp/variant/callable_method_pointer.hpp>
 
-#include <godot_cpp/templates/local_vector.hpp>
-
+#include <godot_cpp/templates/a_hash_map.hpp>
 #include <list>
 #include <mutex>
 #include <set>
-#include <unordered_map>
-
-// Needed to use StringName as key in `std::unordered_map`
-template <>
-struct std::hash<godot::StringName> {
-	std::size_t operator()(godot::StringName const &s) const noexcept {
-		return s.hash();
-	}
-};
 
 namespace godot {
 
@@ -95,9 +85,9 @@ public:
 		StringName name;
 		StringName parent_name;
 		GDExtensionInitializationLevel level = GDEXTENSION_INITIALIZATION_SCENE;
-		std::unordered_map<StringName, MethodBind *> method_map;
+		AHashMap<StringName, MethodBind *> method_map;
 		std::set<StringName> signal_names;
-		std::unordered_map<StringName, VirtualMethod> virtual_methods;
+		AHashMap<StringName, VirtualMethod> virtual_methods;
 		std::set<StringName> property_names;
 		std::set<StringName> constant_names;
 		// Pointer to the parent custom class, if any. Will be null if the parent class is a Godot class.
@@ -106,11 +96,11 @@ public:
 
 private:
 	// This may only contain custom classes, not Godot classes
-	static std::unordered_map<StringName, ClassInfo> classes;
-	static std::unordered_map<StringName, const GDExtensionInstanceBindingCallbacks *> instance_binding_callbacks;
+	static AHashMap<StringName, ClassInfo> classes;
+	static AHashMap<StringName, const GDExtensionInstanceBindingCallbacks *> instance_binding_callbacks;
 	// Used to remember the custom class registration order.
 	static LocalVector<StringName> class_register_order;
-	static std::unordered_map<StringName, Object *> engine_singletons;
+	static AHashMap<StringName, Object *> engine_singletons;
 	static std::mutex engine_singletons_mutex;
 
 	static MethodBind *bind_methodfi(uint32_t p_flags, MethodBind *p_bind, const MethodDefinition &method_name, const void **p_defs, int p_defcount);
@@ -171,9 +161,9 @@ public:
 
 	static void _register_engine_singleton(const StringName &p_class_name, Object *p_singleton) {
 		std::lock_guard<std::mutex> lock(engine_singletons_mutex);
-		std::unordered_map<StringName, Object *>::const_iterator i = engine_singletons.find(p_class_name);
+		AHashMap<StringName, Object *>::ConstIterator i = engine_singletons.find(p_class_name);
 		if (i != engine_singletons.end()) {
-			ERR_FAIL_COND((*i).second != p_singleton);
+			ERR_FAIL_COND((*i).value != p_singleton);
 			return;
 		}
 		engine_singletons[p_class_name] = p_singleton;
@@ -243,10 +233,10 @@ void ClassDB::_register_class(bool p_virtual, bool p_exposed, bool p_runtime) {
 	cl.name = T::get_class_static();
 	cl.parent_name = T::get_parent_class_static();
 	cl.level = current_level;
-	std::unordered_map<StringName, ClassInfo>::iterator parent_it = classes.find(cl.parent_name);
+	AHashMap<StringName, ClassInfo>::Iterator parent_it = classes.find(cl.parent_name);
 	if (parent_it != classes.end()) {
 		// Assign parent if it is also a custom class
-		cl.parent_ptr = &parent_it->second;
+		cl.parent_ptr = &parent_it->value;
 	}
 	classes[cl.name] = cl;
 	class_register_order.push_back(cl.name);
@@ -340,13 +330,13 @@ MethodBind *ClassDB::bind_vararg_method(uint32_t p_flags, StringName p_name, M p
 
 	StringName instance_type = bind->get_instance_class();
 
-	std::unordered_map<StringName, ClassInfo>::iterator type_it = classes.find(instance_type);
+	AHashMap<StringName, ClassInfo>::Iterator type_it = classes.find(instance_type);
 	if (type_it == classes.end()) {
 		memdelete(bind);
 		ERR_FAIL_V_MSG(nullptr, String("Class '{0}' doesn't exist.").format(Array::make(instance_type)));
 	}
 
-	ClassInfo &type = type_it->second;
+	ClassInfo &type = type_it->value;
 
 	if (type.method_map.find(p_name) != type.method_map.end()) {
 		memdelete(bind);

+ 734 - 0
include/godot_cpp/templates/a_hash_map.hpp

@@ -0,0 +1,734 @@
+/**************************************************************************/
+/*  a_hash_map.hpp                                                        */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* 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.                 */
+/**************************************************************************/
+
+#pragma once
+
+#include <godot_cpp/templates/hash_map.hpp>
+
+namespace godot {
+
+struct HashMapData {
+	union {
+		uint64_t data;
+		struct
+		{
+			uint32_t hash;
+			uint32_t hash_to_key;
+		};
+	};
+};
+
+static_assert(sizeof(HashMapData) == 8);
+
+/**
+ * An array-based implementation of a hash map. It is very efficient in terms of performance and
+ * memory usage. Works like a dynamic array, adding elements to the end of the array, and
+ * allows you to access array elements by their index by using `get_by_index` method.
+ * Example:
+ * ```
+ *  AHashMap<int, Object *> map;
+ *
+ *  int get_object_id_by_number(int p_number) {
+ *		int id = map.get_index(p_number);
+ *		return id;
+ *  }
+ *
+ *  Object *get_object_by_id(int p_id) {
+ *		map.get_by_index(p_id).value;
+ *  }
+ * ```
+ * Still, don`t erase the elements because ID can break.
+ *
+ * When an element erase, its place is taken by the element from the end.
+ *
+ *        <-------------
+ *      |               |
+ *  6 8 X 9 32 -1 5 -10 7 X X X
+ *  6 8 7 9 32 -1 5 -10 X X X X
+ *
+ *
+ * Use RBMap if you need to iterate over sorted elements.
+ *
+ * Use HashMap if:
+ *   - You need to keep an iterator or const pointer to Key and you intend to add/remove elements in the meantime.
+ *   - You need to preserve the insertion order when using erase.
+ *
+ * It is recommended to use `HashMap` if `KeyValue` size is very large.
+ */
+template <typename TKey, typename TValue,
+		typename Hasher = HashMapHasherDefault,
+		typename Comparator = HashMapComparatorDefault<TKey>>
+class AHashMap {
+public:
+	// Must be a power of two.
+	static constexpr uint32_t INITIAL_CAPACITY = 16;
+	static constexpr uint32_t EMPTY_HASH = 0;
+	static_assert(EMPTY_HASH == 0, "EMPTY_HASH must always be 0 for the memcpy() optimization.");
+
+private:
+	typedef KeyValue<TKey, TValue> MapKeyValue;
+	MapKeyValue *elements = nullptr;
+	HashMapData *map_data = nullptr;
+
+	// Due to optimization, this is `capacity - 1`. Use + 1 to get normal capacity.
+	uint32_t capacity = 0;
+	uint32_t num_elements = 0;
+
+	uint32_t _hash(const TKey &p_key) const {
+		uint32_t hash = Hasher::hash(p_key);
+
+		if (unlikely(hash == EMPTY_HASH)) {
+			hash = EMPTY_HASH + 1;
+		}
+
+		return hash;
+	}
+
+	static _FORCE_INLINE_ uint32_t _get_resize_count(uint32_t p_capacity) {
+		return p_capacity ^ (p_capacity + 1) >> 2; // = get_capacity() * 0.75 - 1; Works only if p_capacity = 2^n - 1.
+	}
+
+	static _FORCE_INLINE_ uint32_t _get_probe_length(uint32_t p_pos, uint32_t p_hash, uint32_t p_local_capacity) {
+		const uint32_t original_pos = p_hash & p_local_capacity;
+		return (p_pos - original_pos + p_local_capacity + 1) & p_local_capacity;
+	}
+
+	bool _lookup_pos(const TKey &p_key, uint32_t &r_pos, uint32_t &r_hash_pos) const {
+		if (unlikely(elements == nullptr)) {
+			return false; // Failed lookups, no elements.
+		}
+		return _lookup_pos_with_hash(p_key, r_pos, r_hash_pos, _hash(p_key));
+	}
+
+	bool _lookup_pos_with_hash(const TKey &p_key, uint32_t &r_pos, uint32_t &r_hash_pos, uint32_t p_hash) const {
+		if (unlikely(elements == nullptr)) {
+			return false; // Failed lookups, no elements.
+		}
+
+		uint32_t pos = p_hash & capacity;
+		HashMapData data = map_data[pos];
+		if (data.hash == p_hash && Comparator::compare(elements[data.hash_to_key].key, p_key)) {
+			r_pos = data.hash_to_key;
+			r_hash_pos = pos;
+			return true;
+		}
+
+		if (data.data == EMPTY_HASH) {
+			return false;
+		}
+
+		// A collision occurred.
+		pos = (pos + 1) & capacity;
+		uint32_t distance = 1;
+		while (true) {
+			data = map_data[pos];
+			if (data.hash == p_hash && Comparator::compare(elements[data.hash_to_key].key, p_key)) {
+				r_pos = data.hash_to_key;
+				r_hash_pos = pos;
+				return true;
+			}
+
+			if (data.data == EMPTY_HASH) {
+				return false;
+			}
+
+			if (distance > _get_probe_length(pos, data.hash, capacity)) {
+				return false;
+			}
+
+			pos = (pos + 1) & capacity;
+			distance++;
+		}
+	}
+
+	uint32_t _insert_with_hash(uint32_t p_hash, uint32_t p_index) {
+		uint32_t pos = p_hash & capacity;
+
+		if (map_data[pos].data == EMPTY_HASH) {
+			uint64_t data = ((uint64_t)p_index << 32) | p_hash;
+			map_data[pos].data = data;
+			return pos;
+		}
+
+		uint32_t distance = 1;
+		pos = (pos + 1) & capacity;
+		HashMapData c_data;
+		c_data.hash = p_hash;
+		c_data.hash_to_key = p_index;
+
+		while (true) {
+			if (map_data[pos].data == EMPTY_HASH) {
+#ifdef DEV_ENABLED
+				if (unlikely(distance > 12)) {
+					WARN_PRINT("Excessive collision count (" +
+							itos(distance) + "), is the right hash function being used?");
+				}
+#endif
+				map_data[pos] = c_data;
+				return pos;
+			}
+
+			// Not an empty slot, let's check the probing length of the existing one.
+			uint32_t existing_probe_len = _get_probe_length(pos, map_data[pos].hash, capacity);
+			if (existing_probe_len < distance) {
+				SWAP(c_data, map_data[pos]);
+				distance = existing_probe_len;
+			}
+
+			pos = (pos + 1) & capacity;
+			distance++;
+		}
+	}
+
+	void _resize_and_rehash(uint32_t p_new_capacity) {
+		uint32_t real_old_capacity = capacity + 1;
+		// Capacity can't be 0 and must be 2^n - 1.
+		capacity = MAX(4u, p_new_capacity);
+		uint32_t real_capacity = next_power_of_2(capacity);
+		capacity = real_capacity - 1;
+
+		HashMapData *old_map_data = map_data;
+
+		map_data = reinterpret_cast<HashMapData *>(Memory::alloc_static(sizeof(HashMapData) * real_capacity));
+		memset(map_data, 0, sizeof(HashMapData) * real_capacity);
+		elements = reinterpret_cast<MapKeyValue *>(Memory::realloc_static(elements, sizeof(MapKeyValue) * (_get_resize_count(capacity) + 1)));
+
+		if (num_elements != 0) {
+			for (uint32_t i = 0; i < real_old_capacity; i++) {
+				HashMapData data = old_map_data[i];
+				if (data.data != EMPTY_HASH) {
+					_insert_with_hash(data.hash, data.hash_to_key);
+				}
+			}
+		}
+
+		Memory::free_static(old_map_data);
+	}
+
+	int32_t _insert_element(const TKey &p_key, const TValue &p_value, uint32_t p_hash) {
+		if (unlikely(elements == nullptr)) {
+			// Allocate on demand to save memory.
+
+			uint32_t real_capacity = capacity + 1;
+			map_data = reinterpret_cast<HashMapData *>(Memory::alloc_static(sizeof(HashMapData) * real_capacity));
+			memset(map_data, 0, sizeof(HashMapData) * real_capacity);
+			elements = reinterpret_cast<MapKeyValue *>(Memory::alloc_static(sizeof(MapKeyValue) * (_get_resize_count(capacity) + 1)));
+		}
+
+		if (unlikely(num_elements > _get_resize_count(capacity))) {
+			_resize_and_rehash(capacity * 2);
+		}
+
+		memnew_placement(&elements[num_elements], MapKeyValue(p_key, p_value));
+
+		_insert_with_hash(p_hash, num_elements);
+		num_elements++;
+		return num_elements - 1;
+	}
+
+	void _init_from(const AHashMap &p_other) {
+		capacity = p_other.capacity;
+		uint32_t real_capacity = capacity + 1;
+		num_elements = p_other.num_elements;
+
+		if (p_other.num_elements == 0) {
+			return;
+		}
+
+		map_data = reinterpret_cast<HashMapData *>(Memory::alloc_static(sizeof(HashMapData) * real_capacity));
+		elements = reinterpret_cast<MapKeyValue *>(Memory::alloc_static(sizeof(MapKeyValue) * (_get_resize_count(capacity) + 1)));
+
+		if constexpr (std::is_trivially_copyable_v<TKey> && std::is_trivially_copyable_v<TValue>) {
+			void *destination = elements;
+			const void *source = p_other.elements;
+			memcpy(destination, source, sizeof(MapKeyValue) * num_elements);
+		} else {
+			for (uint32_t i = 0; i < num_elements; i++) {
+				memnew_placement(&elements[i], MapKeyValue(p_other.elements[i]));
+			}
+		}
+
+		memcpy(map_data, p_other.map_data, sizeof(HashMapData) * real_capacity);
+	}
+
+public:
+	/* Standard Godot Container API */
+
+	_FORCE_INLINE_ uint32_t get_capacity() const { return capacity + 1; }
+	_FORCE_INLINE_ uint32_t size() const { return num_elements; }
+
+	_FORCE_INLINE_ bool is_empty() const {
+		return num_elements == 0;
+	}
+
+	void clear() {
+		if (elements == nullptr || num_elements == 0) {
+			return;
+		}
+
+		memset(map_data, EMPTY_HASH, (capacity + 1) * sizeof(HashMapData));
+		if constexpr (!(std::is_trivially_destructible_v<TKey> && std::is_trivially_destructible_v<TValue>)) {
+			for (uint32_t i = 0; i < num_elements; i++) {
+				elements[i].key.~TKey();
+				elements[i].value.~TValue();
+			}
+		}
+
+		num_elements = 0;
+	}
+
+	TValue &get(const TKey &p_key) {
+		uint32_t pos = 0;
+		uint32_t hash_pos = 0;
+		bool exists = _lookup_pos(p_key, pos, hash_pos);
+		CRASH_COND_MSG(!exists, "AHashMap key not found.");
+		return elements[pos].value;
+	}
+
+	const TValue &get(const TKey &p_key) const {
+		uint32_t pos = 0;
+		uint32_t hash_pos = 0;
+		bool exists = _lookup_pos(p_key, pos, hash_pos);
+		CRASH_COND_MSG(!exists, "AHashMap key not found.");
+		return elements[pos].value;
+	}
+
+	const TValue *getptr(const TKey &p_key) const {
+		uint32_t pos = 0;
+		uint32_t hash_pos = 0;
+		bool exists = _lookup_pos(p_key, pos, hash_pos);
+
+		if (exists) {
+			return &elements[pos].value;
+		}
+		return nullptr;
+	}
+
+	TValue *getptr(const TKey &p_key) {
+		uint32_t pos = 0;
+		uint32_t hash_pos = 0;
+		bool exists = _lookup_pos(p_key, pos, hash_pos);
+
+		if (exists) {
+			return &elements[pos].value;
+		}
+		return nullptr;
+	}
+
+	bool has(const TKey &p_key) const {
+		uint32_t _pos = 0;
+		uint32_t h_pos = 0;
+		return _lookup_pos(p_key, _pos, h_pos);
+	}
+
+	bool erase(const TKey &p_key) {
+		uint32_t pos = 0;
+		uint32_t element_pos = 0;
+		bool exists = _lookup_pos(p_key, element_pos, pos);
+
+		if (!exists) {
+			return false;
+		}
+
+		uint32_t next_pos = (pos + 1) & capacity;
+		while (map_data[next_pos].hash != EMPTY_HASH && _get_probe_length(next_pos, map_data[next_pos].hash, capacity) != 0) {
+			SWAP(map_data[next_pos], map_data[pos]);
+
+			pos = next_pos;
+			next_pos = (next_pos + 1) & capacity;
+		}
+
+		map_data[pos].data = EMPTY_HASH;
+		elements[element_pos].key.~TKey();
+		elements[element_pos].value.~TValue();
+		num_elements--;
+
+		if (element_pos < num_elements) {
+			void *destination = &elements[element_pos];
+			const void *source = &elements[num_elements];
+			memcpy(destination, source, sizeof(MapKeyValue));
+			uint32_t h_pos = 0;
+			_lookup_pos(elements[num_elements].key, pos, h_pos);
+			map_data[h_pos].hash_to_key = element_pos;
+		}
+
+		return true;
+	}
+
+	// Replace the key of an entry in-place, without invalidating iterators or changing the entries position during iteration.
+	// p_old_key must exist in the map and p_new_key must not, unless it is equal to p_old_key.
+	bool replace_key(const TKey &p_old_key, const TKey &p_new_key) {
+		if (p_old_key == p_new_key) {
+			return true;
+		}
+		uint32_t pos = 0;
+		uint32_t element_pos = 0;
+		ERR_FAIL_COND_V(_lookup_pos(p_new_key, element_pos, pos), false);
+		ERR_FAIL_COND_V(!_lookup_pos(p_old_key, element_pos, pos), false);
+		MapKeyValue &element = elements[element_pos];
+		const_cast<TKey &>(element.key) = p_new_key;
+
+		uint32_t next_pos = (pos + 1) & capacity;
+		while (map_data[next_pos].hash != EMPTY_HASH && _get_probe_length(next_pos, map_data[next_pos].hash, capacity) != 0) {
+			SWAP(map_data[next_pos], map_data[pos]);
+
+			pos = next_pos;
+			next_pos = (next_pos + 1) & capacity;
+		}
+
+		map_data[pos].data = EMPTY_HASH;
+
+		uint32_t hash = _hash(p_new_key);
+		_insert_with_hash(hash, element_pos);
+
+		return true;
+	}
+
+	// Reserves space for a number of elements, useful to avoid many resizes and rehashes.
+	// If adding a known (possibly large) number of elements at once, must be larger than old capacity.
+	void reserve(uint32_t p_new_capacity) {
+		ERR_FAIL_COND_MSG(p_new_capacity < size(), "reserve() called with a capacity smaller than the current size. This is likely a mistake.");
+		if (elements == nullptr) {
+			capacity = MAX(4u, p_new_capacity);
+			capacity = next_power_of_2(capacity) - 1;
+			return; // Unallocated yet.
+		}
+		if (p_new_capacity <= get_capacity()) {
+			return;
+		}
+		_resize_and_rehash(p_new_capacity);
+	}
+
+	/** Iterator API **/
+
+	struct ConstIterator {
+		_FORCE_INLINE_ const MapKeyValue &operator*() const {
+			return *pair;
+		}
+		_FORCE_INLINE_ const MapKeyValue *operator->() const {
+			return pair;
+		}
+		_FORCE_INLINE_ ConstIterator &operator++() {
+			pair++;
+			return *this;
+		}
+
+		_FORCE_INLINE_ ConstIterator &operator--() {
+			pair--;
+			if (pair < begin) {
+				pair = end;
+			}
+			return *this;
+		}
+
+		_FORCE_INLINE_ bool operator==(const ConstIterator &b) const { return pair == b.pair; }
+		_FORCE_INLINE_ bool operator!=(const ConstIterator &b) const { return pair != b.pair; }
+
+		_FORCE_INLINE_ explicit operator bool() const {
+			return pair != end;
+		}
+
+		_FORCE_INLINE_ ConstIterator(MapKeyValue *p_key, MapKeyValue *p_begin, MapKeyValue *p_end) {
+			pair = p_key;
+			begin = p_begin;
+			end = p_end;
+		}
+		_FORCE_INLINE_ ConstIterator() {}
+		_FORCE_INLINE_ ConstIterator(const ConstIterator &p_it) {
+			pair = p_it.pair;
+			begin = p_it.begin;
+			end = p_it.end;
+		}
+		_FORCE_INLINE_ void operator=(const ConstIterator &p_it) {
+			pair = p_it.pair;
+			begin = p_it.begin;
+			end = p_it.end;
+		}
+
+	private:
+		MapKeyValue *pair = nullptr;
+		MapKeyValue *begin = nullptr;
+		MapKeyValue *end = nullptr;
+	};
+
+	struct Iterator {
+		_FORCE_INLINE_ MapKeyValue &operator*() const {
+			return *pair;
+		}
+		_FORCE_INLINE_ MapKeyValue *operator->() const {
+			return pair;
+		}
+		_FORCE_INLINE_ Iterator &operator++() {
+			pair++;
+			return *this;
+		}
+		_FORCE_INLINE_ Iterator &operator--() {
+			pair--;
+			if (pair < begin) {
+				pair = end;
+			}
+			return *this;
+		}
+
+		_FORCE_INLINE_ bool operator==(const Iterator &b) const { return pair == b.pair; }
+		_FORCE_INLINE_ bool operator!=(const Iterator &b) const { return pair != b.pair; }
+
+		_FORCE_INLINE_ explicit operator bool() const {
+			return pair != end;
+		}
+
+		_FORCE_INLINE_ Iterator(MapKeyValue *p_key, MapKeyValue *p_begin, MapKeyValue *p_end) {
+			pair = p_key;
+			begin = p_begin;
+			end = p_end;
+		}
+		_FORCE_INLINE_ Iterator() {}
+		_FORCE_INLINE_ Iterator(const Iterator &p_it) {
+			pair = p_it.pair;
+			begin = p_it.begin;
+			end = p_it.end;
+		}
+		_FORCE_INLINE_ void operator=(const Iterator &p_it) {
+			pair = p_it.pair;
+			begin = p_it.begin;
+			end = p_it.end;
+		}
+
+		operator ConstIterator() const {
+			return ConstIterator(pair, begin, end);
+		}
+
+	private:
+		MapKeyValue *pair = nullptr;
+		MapKeyValue *begin = nullptr;
+		MapKeyValue *end = nullptr;
+	};
+
+	_FORCE_INLINE_ Iterator begin() {
+		return Iterator(elements, elements, elements + num_elements);
+	}
+	_FORCE_INLINE_ Iterator end() {
+		return Iterator(elements + num_elements, elements, elements + num_elements);
+	}
+	_FORCE_INLINE_ Iterator last() {
+		if (unlikely(num_elements == 0)) {
+			return Iterator(nullptr, nullptr, nullptr);
+		}
+		return Iterator(elements + num_elements - 1, elements, elements + num_elements);
+	}
+
+	Iterator find(const TKey &p_key) {
+		uint32_t pos = 0;
+		uint32_t h_pos = 0;
+		bool exists = _lookup_pos(p_key, pos, h_pos);
+		if (!exists) {
+			return end();
+		}
+		return Iterator(elements + pos, elements, elements + num_elements);
+	}
+
+	void remove(const Iterator &p_iter) {
+		if (p_iter) {
+			erase(p_iter->key);
+		}
+	}
+
+	_FORCE_INLINE_ ConstIterator begin() const {
+		return ConstIterator(elements, elements, elements + num_elements);
+	}
+	_FORCE_INLINE_ ConstIterator end() const {
+		return ConstIterator(elements + num_elements, elements, elements + num_elements);
+	}
+	_FORCE_INLINE_ ConstIterator last() const {
+		if (unlikely(num_elements == 0)) {
+			return ConstIterator(nullptr, nullptr, nullptr);
+		}
+		return ConstIterator(elements + num_elements - 1, elements, elements + num_elements);
+	}
+
+	ConstIterator find(const TKey &p_key) const {
+		uint32_t pos = 0;
+		uint32_t h_pos = 0;
+		bool exists = _lookup_pos(p_key, pos, h_pos);
+		if (!exists) {
+			return end();
+		}
+		return ConstIterator(elements + pos, elements, elements + num_elements);
+	}
+
+	/* Indexing */
+
+	const TValue &operator[](const TKey &p_key) const {
+		uint32_t pos = 0;
+		uint32_t h_pos = 0;
+		bool exists = _lookup_pos(p_key, pos, h_pos);
+		CRASH_COND(!exists);
+		return elements[pos].value;
+	}
+
+	TValue &operator[](const TKey &p_key) {
+		uint32_t pos = 0;
+		uint32_t h_pos = 0;
+		uint32_t hash = _hash(p_key);
+		bool exists = _lookup_pos_with_hash(p_key, pos, h_pos, hash);
+
+		if (exists) {
+			return elements[pos].value;
+		} else {
+			pos = _insert_element(p_key, TValue(), hash);
+			return elements[pos].value;
+		}
+	}
+
+	/* Insert */
+
+	Iterator insert(const TKey &p_key, const TValue &p_value) {
+		uint32_t pos = 0;
+		uint32_t h_pos = 0;
+		uint32_t hash = _hash(p_key);
+		bool exists = _lookup_pos_with_hash(p_key, pos, h_pos, hash);
+
+		if (!exists) {
+			pos = _insert_element(p_key, p_value, hash);
+		} else {
+			elements[pos].value = p_value;
+		}
+		return Iterator(elements + pos, elements, elements + num_elements);
+	}
+
+	// Inserts an element without checking if it already exists.
+	Iterator insert_new(const TKey &p_key, const TValue &p_value) {
+		DEV_ASSERT(!has(p_key));
+		uint32_t hash = _hash(p_key);
+		uint32_t pos = _insert_element(p_key, p_value, hash);
+		return Iterator(elements + pos, elements, elements + num_elements);
+	}
+
+	/* Array methods. */
+
+	// Unsafe. Changing keys and going outside the bounds of an array can lead to undefined behavior.
+	KeyValue<TKey, TValue> *get_elements_ptr() {
+		return elements;
+	}
+
+	// Returns the element index. If not found, returns -1.
+	int get_index(const TKey &p_key) {
+		uint32_t pos = 0;
+		uint32_t h_pos = 0;
+		bool exists = _lookup_pos(p_key, pos, h_pos);
+		if (!exists) {
+			return -1;
+		}
+		return pos;
+	}
+
+	KeyValue<TKey, TValue> &get_by_index(uint32_t p_index) {
+		CRASH_BAD_UNSIGNED_INDEX(p_index, num_elements);
+		return elements[p_index];
+	}
+
+	bool erase_by_index(uint32_t p_index) {
+		if (p_index >= size()) {
+			return false;
+		}
+		return erase(elements[p_index].key);
+	}
+
+	/* Constructors */
+
+	AHashMap(const AHashMap &p_other) {
+		_init_from(p_other);
+	}
+
+	AHashMap(const HashMap<TKey, TValue> &p_other) {
+		reserve(p_other.size());
+		for (const KeyValue<TKey, TValue> &E : p_other) {
+			uint32_t hash = _hash(E.key);
+			_insert_element(E.key, E.value, hash);
+		}
+	}
+
+	void operator=(const AHashMap &p_other) {
+		if (this == &p_other) {
+			return; // Ignore self assignment.
+		}
+
+		reset();
+
+		_init_from(p_other);
+	}
+
+	void operator=(const HashMap<TKey, TValue> &p_other) {
+		reset();
+		reserve(p_other.size());
+		for (const KeyValue<TKey, TValue> &E : p_other) {
+			uint32_t hash = _hash(E.key);
+			_insert_element(E.key, E.value, hash);
+		}
+	}
+
+	AHashMap(uint32_t p_initial_capacity) {
+		// Capacity can't be 0 and must be 2^n - 1.
+		capacity = MAX(4u, p_initial_capacity);
+		capacity = next_power_of_2(capacity) - 1;
+	}
+	AHashMap() :
+			capacity(INITIAL_CAPACITY - 1) {
+	}
+
+	AHashMap(std::initializer_list<KeyValue<TKey, TValue>> p_init) {
+		reserve(p_init.size());
+		for (const KeyValue<TKey, TValue> &E : p_init) {
+			insert(E.key, E.value);
+		}
+	}
+
+	void reset() {
+		if (elements != nullptr) {
+			if constexpr (!(std::is_trivially_destructible_v<TKey> && std::is_trivially_destructible_v<TValue>)) {
+				for (uint32_t i = 0; i < num_elements; i++) {
+					elements[i].key.~TKey();
+					elements[i].value.~TValue();
+				}
+			}
+			Memory::free_static(elements);
+			Memory::free_static(map_data);
+			elements = nullptr;
+		}
+		capacity = INITIAL_CAPACITY - 1;
+		num_elements = 0;
+	}
+
+	~AHashMap() {
+		reset();
+	}
+};
+
+} //namespace godot

+ 30 - 30
src/core/class_db.cpp

@@ -38,10 +38,10 @@
 
 namespace godot {
 
-std::unordered_map<StringName, ClassDB::ClassInfo> ClassDB::classes;
-std::unordered_map<StringName, const GDExtensionInstanceBindingCallbacks *> ClassDB::instance_binding_callbacks;
+AHashMap<StringName, ClassDB::ClassInfo> ClassDB::classes;
+AHashMap<StringName, const GDExtensionInstanceBindingCallbacks *> ClassDB::instance_binding_callbacks;
 LocalVector<StringName> ClassDB::class_register_order;
-std::unordered_map<StringName, Object *> ClassDB::engine_singletons;
+AHashMap<StringName, Object *> ClassDB::engine_singletons;
 std::mutex ClassDB::engine_singletons_mutex;
 GDExtensionInitializationLevel ClassDB::current_level = GDEXTENSION_INITIALIZATION_CORE;
 
@@ -114,9 +114,9 @@ MethodBind *ClassDB::get_method(const StringName &p_class, const StringName &p_m
 
 	ClassInfo *type = &classes[p_class];
 	while (type) {
-		std::unordered_map<StringName, MethodBind *>::iterator method = type->method_map.find(p_method);
+		AHashMap<StringName, MethodBind *>::Iterator method = type->method_map.find(p_method);
 		if (method != type->method_map.end()) {
-			return method->second;
+			return method->value;
 		}
 		type = type->parent_ptr;
 		continue;
@@ -128,13 +128,13 @@ MethodBind *ClassDB::get_method(const StringName &p_class, const StringName &p_m
 MethodBind *ClassDB::bind_methodfi(uint32_t p_flags, MethodBind *p_bind, const MethodDefinition &method_name, const void **p_defs, int p_defcount) {
 	StringName instance_type = p_bind->get_instance_class();
 
-	std::unordered_map<StringName, ClassInfo>::iterator type_it = classes.find(instance_type);
+	AHashMap<StringName, ClassInfo>::Iterator type_it = classes.find(instance_type);
 	if (type_it == classes.end()) {
 		memdelete(p_bind);
 		ERR_FAIL_V_MSG(nullptr, String("Class '{0}' doesn't exist.").format(Array::make(instance_type)));
 	}
 
-	ClassInfo &type = type_it->second;
+	ClassInfo &type = type_it->value;
 
 	if (type.method_map.find(method_name.name) != type.method_map.end()) {
 		memdelete(p_bind);
@@ -233,11 +233,11 @@ void ClassDB::bind_method_godot(const StringName &p_class_name, MethodBind *p_me
 }
 
 void ClassDB::add_signal(const StringName &p_class, const MethodInfo &p_signal) {
-	std::unordered_map<StringName, ClassInfo>::iterator type_it = classes.find(p_class);
+	AHashMap<StringName, ClassInfo>::Iterator type_it = classes.find(p_class);
 
 	ERR_FAIL_COND_MSG(type_it == classes.end(), String("Class '{0}' doesn't exist.").format(Array::make(p_class)));
 
-	ClassInfo &cl = type_it->second;
+	ClassInfo &cl = type_it->value;
 
 	// Check if this signal is already register
 	ClassInfo *check = &cl;
@@ -268,11 +268,11 @@ void ClassDB::add_signal(const StringName &p_class, const MethodInfo &p_signal)
 }
 
 void ClassDB::bind_integer_constant(const StringName &p_class_name, const StringName &p_enum_name, const StringName &p_constant_name, GDExtensionInt p_constant_value, bool p_is_bitfield) {
-	std::unordered_map<StringName, ClassInfo>::iterator type_it = classes.find(p_class_name);
+	AHashMap<StringName, ClassInfo>::Iterator type_it = classes.find(p_class_name);
 
 	ERR_FAIL_COND_MSG(type_it == classes.end(), String("Class '{0}' doesn't exist.").format(Array::make(p_class_name)));
 
-	ClassInfo &type = type_it->second;
+	ClassInfo &type = type_it->value;
 
 	// check if it already exists
 	ERR_FAIL_COND_MSG(type.constant_names.find(p_constant_name) != type.constant_names.end(), String("Constant '{0}::{1}' already registered.").format(Array::make(p_class_name, p_constant_name)));
@@ -290,17 +290,17 @@ GDExtensionClassCallVirtual ClassDB::get_virtual_func(void *p_userdata, GDExtens
 	const StringName *class_name = reinterpret_cast<const StringName *>(p_userdata);
 	const StringName *name = reinterpret_cast<const StringName *>(p_name);
 
-	std::unordered_map<StringName, ClassInfo>::iterator type_it = classes.find(*class_name);
+	AHashMap<StringName, ClassInfo>::Iterator type_it = classes.find(*class_name);
 	ERR_FAIL_COND_V_MSG(type_it == classes.end(), nullptr, String("Class '{0}' doesn't exist.").format(Array::make(*class_name)));
 
-	const ClassInfo *type = &type_it->second;
+	const ClassInfo *type = &type_it->value;
 
 	// Find method in current class, or any of its parent classes (Godot classes not included)
 	while (type != nullptr) {
-		std::unordered_map<StringName, ClassInfo::VirtualMethod>::const_iterator method_it = type->virtual_methods.find(*name);
+		AHashMap<StringName, ClassInfo::VirtualMethod>::ConstIterator method_it = type->virtual_methods.find(*name);
 
-		if (method_it != type->virtual_methods.end() && method_it->second.hash == p_hash) {
-			return method_it->second.func;
+		if (method_it != type->virtual_methods.end() && method_it->value.hash == p_hash) {
+			return method_it->value.func;
 		}
 
 		type = type->parent_ptr;
@@ -310,9 +310,9 @@ GDExtensionClassCallVirtual ClassDB::get_virtual_func(void *p_userdata, GDExtens
 }
 
 const GDExtensionInstanceBindingCallbacks *ClassDB::get_instance_binding_callbacks(const StringName &p_class) {
-	std::unordered_map<StringName, const GDExtensionInstanceBindingCallbacks *>::iterator callbacks_it = instance_binding_callbacks.find(p_class);
+	AHashMap<StringName, const GDExtensionInstanceBindingCallbacks *>::Iterator callbacks_it = instance_binding_callbacks.find(p_class);
 	if (likely(callbacks_it != instance_binding_callbacks.end())) {
-		return callbacks_it->second;
+		return callbacks_it->value;
 	}
 
 	// If we don't have an instance binding callback for the given class, find the closest parent where we do.
@@ -323,14 +323,14 @@ const GDExtensionInstanceBindingCallbacks *ClassDB::get_instance_binding_callbac
 		callbacks_it = instance_binding_callbacks.find(class_name);
 	} while (callbacks_it == instance_binding_callbacks.end());
 
-	return callbacks_it->second;
+	return callbacks_it->value;
 }
 
 void ClassDB::bind_virtual_method(const StringName &p_class, const StringName &p_method, GDExtensionClassCallVirtual p_call, uint32_t p_hash) {
-	std::unordered_map<StringName, ClassInfo>::iterator type_it = classes.find(p_class);
+	AHashMap<StringName, ClassInfo>::Iterator type_it = classes.find(p_class);
 	ERR_FAIL_COND_MSG(type_it == classes.end(), String("Class '{0}' doesn't exist.").format(Array::make(p_class)));
 
-	ClassInfo &type = type_it->second;
+	ClassInfo &type = type_it->value;
 
 	ERR_FAIL_COND_MSG(type.method_map.find(p_method) != type.method_map.end(), String("Method '{0}::{1}()' already registered as non-virtual.").format(Array::make(p_class, p_method)));
 	ERR_FAIL_COND_MSG(type.virtual_methods.find(p_method) != type.virtual_methods.end(), String("Virtual '{0}::{1}()' method already registered.").format(Array::make(p_class, p_method)));
@@ -342,7 +342,7 @@ void ClassDB::bind_virtual_method(const StringName &p_class, const StringName &p
 }
 
 void ClassDB::add_virtual_method(const StringName &p_class, const MethodInfo &p_method, const Vector<StringName> &p_arg_names) {
-	std::unordered_map<StringName, ClassInfo>::iterator type_it = classes.find(p_class);
+	AHashMap<StringName, ClassInfo>::Iterator type_it = classes.find(p_class);
 	ERR_FAIL_COND_MSG(type_it == classes.end(), String("Class '{0}' doesn't exist.").format(Array::make(p_class)));
 
 	GDExtensionClassVirtualMethodInfo mi;
@@ -390,8 +390,8 @@ void ClassDB::_editor_get_classes_used_callback(GDExtensionTypePtr p_packed_stri
 	PackedStringArray *arr = reinterpret_cast<PackedStringArray *>(p_packed_string_array);
 	arr->resize(instance_binding_callbacks.size());
 	int index = 0;
-	for (const std::pair<const StringName, const GDExtensionInstanceBindingCallbacks *> &pair : instance_binding_callbacks) {
-		(*arr)[index++] = pair.first;
+	for (const KeyValue<StringName, const GDExtensionInstanceBindingCallbacks *> &pair : instance_binding_callbacks) {
+		(*arr)[index++] = pair.key;
 	}
 }
 
@@ -399,8 +399,8 @@ void ClassDB::initialize_class(const ClassInfo &p_cl) {
 }
 
 void ClassDB::initialize(GDExtensionInitializationLevel p_level) {
-	for (const std::pair<const StringName, ClassInfo> &pair : classes) {
-		const ClassInfo &cl = pair.second;
+	for (const KeyValue<StringName, ClassInfo> &pair : classes) {
+		const ClassInfo &cl = pair.value;
 		if (cl.level != p_level) {
 			continue;
 		}
@@ -421,8 +421,8 @@ void ClassDB::deinitialize(GDExtensionInitializationLevel p_level) {
 
 		internal::gdextension_interface_classdb_unregister_extension_class(internal::library, name._native_ptr());
 
-		for (const std::pair<const StringName, MethodBind *> &method : cl.method_map) {
-			memdelete(method.second);
+		for (const KeyValue<StringName, MethodBind *> &method : cl.method_map) {
+			memdelete(method.value);
 		}
 
 		classes.erase(name);
@@ -442,8 +442,8 @@ void ClassDB::deinitialize(GDExtensionInitializationLevel p_level) {
 		{
 			std::lock_guard<std::mutex> lock(engine_singletons_mutex);
 			singleton_objects.reserve(engine_singletons.size());
-			for (const std::pair<const StringName, Object *> &pair : engine_singletons) {
-				singleton_objects.push_back(pair.second);
+			for (const KeyValue<StringName, Object *> &pair : engine_singletons) {
+				singleton_objects.push_back(pair.value);
 			}
 		}
 		for (const Object *i : singleton_objects) {