From 2fd41b7e16b4f3693adf95c72658b06b103f4596 Mon Sep 17 00:00:00 2001 From: dementive <87823030+dementive@users.noreply.github.com> Date: Mon, 8 Sep 2025 17:46:51 -0500 Subject: [PATCH] Replace unordered_map with AHashMap --- include/godot_cpp/core/class_db.hpp | 34 +- include/godot_cpp/templates/a_hash_map.hpp | 734 +++++++++++++++++++++ src/core/class_db.cpp | 60 +- 3 files changed, 776 insertions(+), 52 deletions(-) create mode 100644 include/godot_cpp/templates/a_hash_map.hpp diff --git a/include/godot_cpp/core/class_db.hpp b/include/godot_cpp/core/class_db.hpp index 311109217..b14f597a2 100644 --- a/include/godot_cpp/core/class_db.hpp +++ b/include/godot_cpp/core/class_db.hpp @@ -44,20 +44,10 @@ // Needs to come after method_bind and object have been included. #include -#include - +#include #include #include #include -#include - -// Needed to use StringName as key in `std::unordered_map` -template <> -struct std::hash { - std::size_t operator()(godot::StringName const &s) const noexcept { - return s.hash(); - } -}; namespace godot { @@ -95,9 +85,9 @@ class ClassDB { StringName name; StringName parent_name; GDExtensionInitializationLevel level = GDEXTENSION_INITIALIZATION_SCENE; - std::unordered_map method_map; + AHashMap method_map; std::set signal_names; - std::unordered_map virtual_methods; + AHashMap virtual_methods; std::set property_names; std::set 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 @@ class ClassDB { private: // This may only contain custom classes, not Godot classes - static std::unordered_map classes; - static std::unordered_map instance_binding_callbacks; + static AHashMap classes; + static AHashMap instance_binding_callbacks; // Used to remember the custom class registration order. static LocalVector class_register_order; - static std::unordered_map engine_singletons; + static AHashMap 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 @@ class ClassDB { static void _register_engine_singleton(const StringName &p_class_name, Object *p_singleton) { std::lock_guard lock(engine_singletons_mutex); - std::unordered_map::const_iterator i = engine_singletons.find(p_class_name); + AHashMap::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::iterator parent_it = classes.find(cl.parent_name); + AHashMap::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::iterator type_it = classes.find(instance_type); + AHashMap::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); diff --git a/include/godot_cpp/templates/a_hash_map.hpp b/include/godot_cpp/templates/a_hash_map.hpp new file mode 100644 index 000000000..18a03df22 --- /dev/null +++ b/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 + +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 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 > +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 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(Memory::alloc_static(sizeof(HashMapData) * real_capacity)); + memset(map_data, 0, sizeof(HashMapData) * real_capacity); + elements = reinterpret_cast(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(Memory::alloc_static(sizeof(HashMapData) * real_capacity)); + memset(map_data, 0, sizeof(HashMapData) * real_capacity); + elements = reinterpret_cast(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(Memory::alloc_static(sizeof(HashMapData) * real_capacity)); + elements = reinterpret_cast(Memory::alloc_static(sizeof(MapKeyValue) * (_get_resize_count(capacity) + 1))); + + if constexpr (std::is_trivially_copyable_v && std::is_trivially_copyable_v) { + 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 && std::is_trivially_destructible_v)) { + 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(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 *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 &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 &p_other) { + reserve(p_other.size()); + for (const KeyValue &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 &p_other) { + reset(); + reserve(p_other.size()); + for (const KeyValue &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> p_init) { + reserve(p_init.size()); + for (const KeyValue &E : p_init) { + insert(E.key, E.value); + } + } + + void reset() { + if (elements != nullptr) { + if constexpr (!(std::is_trivially_destructible_v && std::is_trivially_destructible_v)) { + 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 diff --git a/src/core/class_db.cpp b/src/core/class_db.cpp index 4846281b1..11f7b0778 100644 --- a/src/core/class_db.cpp +++ b/src/core/class_db.cpp @@ -38,10 +38,10 @@ namespace godot { -std::unordered_map ClassDB::classes; -std::unordered_map ClassDB::instance_binding_callbacks; +AHashMap ClassDB::classes; +AHashMap ClassDB::instance_binding_callbacks; LocalVector ClassDB::class_register_order; -std::unordered_map ClassDB::engine_singletons; +AHashMap 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::iterator method = type->method_map.find(p_method); + AHashMap::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::iterator type_it = classes.find(instance_type); + AHashMap::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::iterator type_it = classes.find(p_class); + AHashMap::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::iterator type_it = classes.find(p_class_name); + AHashMap::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(p_userdata); const StringName *name = reinterpret_cast(p_name); - std::unordered_map::iterator type_it = classes.find(*class_name); + AHashMap::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::const_iterator method_it = type->virtual_methods.find(*name); + AHashMap::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::iterator callbacks_it = instance_binding_callbacks.find(p_class); + AHashMap::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::iterator type_it = classes.find(p_class); + AHashMap::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 &p_arg_names) { - std::unordered_map::iterator type_it = classes.find(p_class); + AHashMap::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(p_packed_string_array); arr->resize(instance_binding_callbacks.size()); int index = 0; - for (const std::pair &pair : instance_binding_callbacks) { - (*arr)[index++] = pair.first; + for (const KeyValue &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 &pair : classes) { - const ClassInfo &cl = pair.second; + for (const KeyValue &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 &method : cl.method_map) { - memdelete(method.second); + for (const KeyValue &method : cl.method_map) { + memdelete(method.value); } classes.erase(name); @@ -442,8 +442,8 @@ void ClassDB::deinitialize(GDExtensionInitializationLevel p_level) { { std::lock_guard lock(engine_singletons_mutex); singleton_objects.reserve(engine_singletons.size()); - for (const std::pair &pair : engine_singletons) { - singleton_objects.push_back(pair.second); + for (const KeyValue &pair : engine_singletons) { + singleton_objects.push_back(pair.value); } } for (const Object *i : singleton_objects) {