From 76208f7155c77d8d72e3de54e54ec45b1263558d Mon Sep 17 00:00:00 2001 From: nazarii Date: Wed, 29 May 2024 17:08:54 +0300 Subject: [PATCH] Implement array based hash map --- core/templates/a_hash_map.cpp | 39 + core/templates/a_hash_map.h | 732 ++++++++++++++++++ core/templates/hashfuncs.h | 7 + scene/3d/skeleton_3d.h | 3 +- scene/animation/animation_mixer.cpp | 66 +- scene/animation/animation_mixer.h | 21 +- .../animation_node_state_machine.cpp | 2 +- scene/animation/animation_player.cpp | 4 +- scene/animation/animation_player.h | 4 +- scene/animation/animation_tree.cpp | 32 +- scene/animation/animation_tree.h | 47 +- tests/core/templates/test_a_hash_map.h | 295 +++++++ tests/test_main.cpp | 1 + 13 files changed, 1202 insertions(+), 51 deletions(-) create mode 100644 core/templates/a_hash_map.cpp create mode 100644 core/templates/a_hash_map.h create mode 100644 tests/core/templates/test_a_hash_map.h diff --git a/core/templates/a_hash_map.cpp b/core/templates/a_hash_map.cpp new file mode 100644 index 00000000000..04a14c261a2 --- /dev/null +++ b/core/templates/a_hash_map.cpp @@ -0,0 +1,39 @@ +/**************************************************************************/ +/* a_hash_map.cpp */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +#include "a_hash_map.h" +#include "core/variant/variant.h" + +// Explicit instantiation. +template class AHashMap; +template class AHashMap; +template class AHashMap; +template class AHashMap; +template class AHashMap; diff --git a/core/templates/a_hash_map.h b/core/templates/a_hash_map.h new file mode 100644 index 00000000000..29983ea2681 --- /dev/null +++ b/core/templates/a_hash_map.h @@ -0,0 +1,732 @@ +/**************************************************************************/ +/* a_hash_map.h */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +#ifndef A_HASH_MAP_H +#define A_HASH_MAP_H + +#include "core/templates/hash_map.h" + +struct HashMapData { + union { + struct + { + uint32_t hash; + uint32_t hash_to_key; + }; + uint64_t data; + }; +}; + +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)); + elements = reinterpret_cast(Memory::realloc_static(elements, sizeof(MapKeyValue) * (_get_resize_count(capacity) + 1))); + + memset(map_data, EMPTY_HASH, real_capacity * sizeof(HashMapData)); + + 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)); + elements = reinterpret_cast(Memory::alloc_static(sizeof(MapKeyValue) * (_get_resize_count(capacity) + 1))); + + memset(map_data, EMPTY_HASH, real_capacity * sizeof(HashMapData)); + } + + 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 < get_capacity(), "It is impossible to reserve less capacity than is currently available."); + if (elements == nullptr) { + capacity = MAX(4u, p_new_capacity); + capacity = next_power_of_2(capacity) - 1; + return; // Unallocated yet. + } + _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. + void insert_new(const TKey &p_key, const TValue &p_value) { + DEV_ASSERT(!has(p_key)); + uint32_t hash = _hash(p_key); + _insert_element(p_key, p_value, hash); + } + + /* 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(); + if (p_other.size() > get_capacity()) { + 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) { + } + + 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(); + } +}; + +extern template class AHashMap; +extern template class AHashMap; +extern template class AHashMap; +extern template class AHashMap; +extern template class AHashMap; + +#endif // A_HASH_MAP_H diff --git a/core/templates/hashfuncs.h b/core/templates/hashfuncs.h index 21eef10297a..7818ed07060 100644 --- a/core/templates/hashfuncs.h +++ b/core/templates/hashfuncs.h @@ -393,6 +393,13 @@ struct HashMapHasherDefault { } }; +struct HashHasher { + static _FORCE_INLINE_ uint32_t hash(const int32_t hash) { return hash; } + static _FORCE_INLINE_ uint32_t hash(const uint32_t hash) { return hash; } + static _FORCE_INLINE_ uint64_t hash(const int64_t hash) { return hash; } + static _FORCE_INLINE_ uint64_t hash(const uint64_t hash) { return hash; } +}; + // TODO: Fold this into HashMapHasherDefault once C++20 concepts are allowed template struct HashableHasher { diff --git a/scene/3d/skeleton_3d.h b/scene/3d/skeleton_3d.h index 0db12600c31..ecfe095f1de 100644 --- a/scene/3d/skeleton_3d.h +++ b/scene/3d/skeleton_3d.h @@ -31,6 +31,7 @@ #ifndef SKELETON_3D_H #define SKELETON_3D_H +#include "core/templates/a_hash_map.h" #include "scene/3d/node_3d.h" #include "scene/resources/3d/skin.h" @@ -159,7 +160,7 @@ private: bool process_order_dirty = false; Vector parentless_bones; - HashMap name_to_bone_index; + AHashMap name_to_bone_index; mutable StringName concatenated_bone_names = StringName(); void _update_bone_names() const; diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp index eb8bc8c3824..0fa6810d236 100644 --- a/scene/animation/animation_mixer.cpp +++ b/scene/animation/animation_mixer.cpp @@ -563,6 +563,7 @@ void AnimationMixer::_clear_caches() { memdelete(K.value); } track_cache.clear(); + animation_track_num_to_track_cashe.clear(); cache_valid = false; capture_cache.clear(); @@ -922,6 +923,27 @@ bool AnimationMixer::_update_caches() { idx++; } + for (KeyValue &K : track_cache) { + K.value->blend_idx = track_map[K.value->path]; + } + + animation_track_num_to_track_cashe.clear(); + LocalVector track_num_to_track_cashe; + for (const StringName &E : sname_list) { + Ref anim = get_animation(E); + const Vector tracks = anim->get_tracks(); + track_num_to_track_cashe.resize(tracks.size()); + for (int i = 0; i < tracks.size(); i++) { + TrackCache **track_ptr = track_cache.getptr(tracks[i]->thash); + if (track_ptr == nullptr) { + track_num_to_track_cashe[i] = nullptr; + } else { + track_num_to_track_cashe[i] = *track_ptr; + } + } + animation_track_num_to_track_cashe.insert(anim, track_num_to_track_cashe); + } + track_count = idx; cache_valid = true; @@ -946,7 +968,7 @@ void AnimationMixer::_process_animation(double p_delta, bool p_update_only) { clear_animation_instances(); } -Variant AnimationMixer::_post_process_key_value(const Ref &p_anim, int p_track, Variant p_value, ObjectID p_object_id, int p_object_sub_idx) { +Variant AnimationMixer::_post_process_key_value(const Ref &p_anim, int p_track, Variant &p_value, ObjectID p_object_id, int p_object_sub_idx) { #ifndef _3D_DISABLED switch (p_anim->track_get_type(p_track)) { case Animation::TYPE_POSITION_3D: { @@ -1033,7 +1055,7 @@ void AnimationMixer::_blend_init() { } } -bool AnimationMixer::_blend_pre_process(double p_delta, int p_track_count, const HashMap &p_track_map) { +bool AnimationMixer::_blend_pre_process(double p_delta, int p_track_count, const AHashMap &p_track_map) { return true; } @@ -1084,26 +1106,30 @@ void AnimationMixer::_blend_calc_total_weight() { real_t weight = ai.playback_info.weight; const real_t *track_weights_ptr = ai.playback_info.track_weights.ptr(); int track_weights_count = ai.playback_info.track_weights.size(); - static LocalVector processed_hashes; + ERR_CONTINUE_EDMSG(!animation_track_num_to_track_cashe.has(a), "No animation in cache."); + LocalVector &track_num_to_track_cashe = animation_track_num_to_track_cashe[a]; + thread_local HashSet processed_hashes; processed_hashes.clear(); const Vector tracks = a->get_tracks(); - for (const Animation::Track *animation_track : tracks) { + Animation::Track *const *tracks_ptr = tracks.ptr(); + int count = tracks.size(); + for (int i = 0; i < count; i++) { + Animation::Track *animation_track = tracks_ptr[i]; if (!animation_track->enabled) { continue; } Animation::TypeHash thash = animation_track->thash; - TrackCache **track_ptr = track_cache.getptr(thash); - if (track_ptr == nullptr || processed_hashes.has(thash)) { + TrackCache *track = track_num_to_track_cashe[i]; + if (track == nullptr || processed_hashes.has(thash)) { // No path, but avoid error spamming. // Or, there is the case different track type with same path; These can be distinguished by hash. So don't add the weight doubly. continue; } - TrackCache *track = *track_ptr; - int blend_idx = track_map[track->path]; + int blend_idx = track->blend_idx; ERR_CONTINUE(blend_idx < 0 || blend_idx >= track_count); real_t blend = blend_idx < track_weights_count ? track_weights_ptr[blend_idx] * weight : weight; track->total_weight += blend; - processed_hashes.push_back(thash); + processed_hashes.insert(thash); } } } @@ -1130,6 +1156,8 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { #ifndef _3D_DISABLED bool calc_root = !seeked || is_external_seeking; #endif // _3D_DISABLED + ERR_CONTINUE_EDMSG(!animation_track_num_to_track_cashe.has(a), "No animation in cache."); + LocalVector &track_num_to_track_cashe = animation_track_num_to_track_cashe[a]; const Vector tracks = a->get_tracks(); Animation::Track *const *tracks_ptr = tracks.ptr(); real_t a_length = a->get_length(); @@ -1139,15 +1167,11 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { if (!animation_track->enabled) { continue; } - Animation::TypeHash thash = animation_track->thash; - TrackCache **track_ptr = track_cache.getptr(thash); - if (track_ptr == nullptr) { + TrackCache *track = track_num_to_track_cashe[i]; + if (track == nullptr) { continue; // No path, but avoid error spamming. } - TrackCache *track = *track_ptr; - int *blend_idx_ptr = track_map.getptr(track->path); - ERR_CONTINUE(blend_idx_ptr == nullptr); - int blend_idx = *blend_idx_ptr; + int blend_idx = track->blend_idx; ERR_CONTINUE(blend_idx < 0 || blend_idx >= track_count); real_t blend = blend_idx < track_weights_count ? track_weights_ptr[blend_idx] * weight : weight; if (!deterministic) { @@ -1581,7 +1605,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { track_info.loop = a->get_loop_mode() != Animation::LOOP_NONE; track_info.backward = backward; track_info.use_blend = a->audio_track_is_use_blend(i); - HashMap &map = track_info.stream_info; + AHashMap &map = track_info.stream_info; // Main process to fire key is started from here. if (p_update_only) { @@ -1850,7 +1874,7 @@ void AnimationMixer::_blend_apply() { PlayingAudioTrackInfo &track_info = L.value; float db = Math::linear_to_db(track_info.use_blend ? track_info.volume : 1.0); LocalVector erase_streams; - HashMap &map = track_info.stream_info; + AHashMap &map = track_info.stream_info; for (const KeyValue &M : map) { PlayingAudioStreamInfo pasi = M.value; @@ -2134,7 +2158,7 @@ void AnimationMixer::restore(const Ref &p_backup) { ERR_FAIL_COND(p_backup.is_null()); track_cache = p_backup->get_data(); _blend_apply(); - track_cache = HashMap(); + track_cache = AHashMap(); cache_valid = false; } @@ -2370,7 +2394,7 @@ AnimationMixer::AnimationMixer() { AnimationMixer::~AnimationMixer() { } -void AnimatedValuesBackup::set_data(const HashMap p_data) { +void AnimatedValuesBackup::set_data(const AHashMap p_data) { clear_data(); for (const KeyValue &E : p_data) { @@ -2383,7 +2407,7 @@ void AnimatedValuesBackup::set_data(const HashMap AnimatedValuesBackup::get_data() const { +AHashMap AnimatedValuesBackup::get_data() const { HashMap ret; for (const KeyValue &E : data) { AnimationMixer::TrackCache *track = get_cache_copy(E.value); diff --git a/scene/animation/animation_mixer.h b/scene/animation/animation_mixer.h index 27c9a00a9c5..1906146c563 100644 --- a/scene/animation/animation_mixer.h +++ b/scene/animation/animation_mixer.h @@ -31,6 +31,7 @@ #ifndef ANIMATION_MIXER_H #define ANIMATION_MIXER_H +#include "core/templates/a_hash_map.h" #include "scene/animation/tween.h" #include "scene/main/node.h" #include "scene/resources/animation.h" @@ -102,7 +103,7 @@ public: protected: /* ---- Data lists ---- */ LocalVector animation_libraries; - HashMap animation_set; // HashMap + AHashMap animation_set; // HashMap TypedArray _get_animation_library_list() const; Vector _get_animation_list() const { @@ -148,6 +149,7 @@ protected: uint64_t setup_pass = 0; Animation::TrackType type = Animation::TrackType::TYPE_ANIMATION; NodePath path; + int blend_idx = -1; ObjectID object_id; real_t total_weight = 0.0; @@ -269,7 +271,7 @@ protected: // Audio track information for mixng and ending. struct PlayingAudioTrackInfo { - HashMap stream_info; + AHashMap stream_info; double length = 0.0; double time = 0.0; real_t volume = 0.0; @@ -308,7 +310,8 @@ protected: }; RootMotionCache root_motion_cache; - HashMap track_cache; + AHashMap track_cache; + AHashMap, LocalVector> animation_track_num_to_track_cashe; HashSet playing_caches; Vector playing_audio_stream_players; @@ -324,7 +327,7 @@ protected: /* ---- Blending processor ---- */ LocalVector animation_instances; - HashMap track_map; + AHashMap track_map; int track_count = 0; bool deterministic = false; @@ -359,12 +362,12 @@ protected: virtual void _process_animation(double p_delta, bool p_update_only = false); // For post process with retrieved key value during blending. - virtual Variant _post_process_key_value(const Ref &p_anim, int p_track, Variant p_value, ObjectID p_object_id, int p_object_sub_idx = -1); + virtual Variant _post_process_key_value(const Ref &p_anim, int p_track, Variant &p_value, ObjectID p_object_id, int p_object_sub_idx = -1); Variant post_process_key_value(const Ref &p_anim, int p_track, Variant p_value, ObjectID p_object_id, int p_object_sub_idx = -1); GDVIRTUAL5RC(Variant, _post_process_key_value, Ref, int, Variant, ObjectID, int); void _blend_init(); - virtual bool _blend_pre_process(double p_delta, int p_track_count, const HashMap &p_track_map); + virtual bool _blend_pre_process(double p_delta, int p_track_count, const AHashMap &p_track_map); virtual void _blend_capture(double p_delta); void _blend_calc_total_weight(); // For undeterministic blending. void _blend_process(double p_delta, bool p_update_only = false); @@ -485,11 +488,11 @@ public: class AnimatedValuesBackup : public RefCounted { GDCLASS(AnimatedValuesBackup, RefCounted); - HashMap data; + AHashMap data; public: - void set_data(const HashMap p_data); - HashMap get_data() const; + void set_data(const AHashMap p_data); + AHashMap get_data() const; void clear_data(); AnimationMixer::TrackCache *get_cache_copy(AnimationMixer::TrackCache *p_cache) const; diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp index c3c5399a6bc..5cc204100c0 100644 --- a/scene/animation/animation_node_state_machine.cpp +++ b/scene/animation/animation_node_state_machine.cpp @@ -1619,7 +1619,7 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachine::_process(const AnimationM playback_new = playback_new->duplicate(); // Don't process original when testing. } - return playback_new->process(node_state.base_path, this, p_playback_info, p_test_only); + return playback_new->process(node_state.get_base_path(), this, p_playback_info, p_test_only); } String AnimationNodeStateMachine::get_caption() const { diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp index 8a2ca479209..b3a75a75a04 100644 --- a/scene/animation/animation_player.cpp +++ b/scene/animation/animation_player.cpp @@ -133,7 +133,7 @@ void AnimationPlayer::_get_property_list(List *p_list) const { List anim_names; for (const KeyValue &E : animation_set) { - HashMap::ConstIterator F = animation_next_set.find(E.key); + AHashMap::ConstIterator F = animation_next_set.find(E.key); if (F && F->value != StringName()) { anim_names.push_back(PropertyInfo(Variant::STRING, "next/" + String(E.key), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); } @@ -299,7 +299,7 @@ void AnimationPlayer::_blend_playback_data(double p_delta, bool p_started) { } } -bool AnimationPlayer::_blend_pre_process(double p_delta, int p_track_count, const HashMap &p_track_map) { +bool AnimationPlayer::_blend_pre_process(double p_delta, int p_track_count, const AHashMap &p_track_map) { if (!playback.current.from) { _set_process(false); return false; diff --git a/scene/animation/animation_player.h b/scene/animation/animation_player.h index 3223e2522de..06b3eecb894 100644 --- a/scene/animation/animation_player.h +++ b/scene/animation/animation_player.h @@ -52,7 +52,7 @@ public: #endif // DISABLE_DEPRECATED private: - HashMap animation_next_set; // For auto advance. + AHashMap animation_next_set; // For auto advance. float speed_scale = 1.0; double default_blend_time = 0.0; @@ -138,7 +138,7 @@ protected: static void _bind_methods(); // Make animation instances. - virtual bool _blend_pre_process(double p_delta, int p_track_count, const HashMap &p_track_map) override; + virtual bool _blend_pre_process(double p_delta, int p_track_count, const AHashMap &p_track_map) override; virtual void _blend_capture(double p_delta) override; virtual void _blend_post_process() override; diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp index 19080e61dec..d676e2acf46 100644 --- a/scene/animation/animation_tree.cpp +++ b/scene/animation/animation_tree.cpp @@ -75,20 +75,34 @@ void AnimationNode::set_parameter(const StringName &p_name, const Variant &p_val if (process_state->is_testing) { return; } + + const AHashMap::Iterator it = property_cache.find(p_name); + if (it) { + process_state->tree->property_map.get_by_index(it->value).value.first = p_value; + return; + } + ERR_FAIL_COND(!process_state->tree->property_parent_map.has(node_state.base_path)); ERR_FAIL_COND(!process_state->tree->property_parent_map[node_state.base_path].has(p_name)); StringName path = process_state->tree->property_parent_map[node_state.base_path][p_name]; - - process_state->tree->property_map[path].first = p_value; + int idx = process_state->tree->property_map.get_index(path); + property_cache.insert_new(p_name, idx); + process_state->tree->property_map.get_by_index(idx).value.first = p_value; } Variant AnimationNode::get_parameter(const StringName &p_name) const { ERR_FAIL_NULL_V(process_state, Variant()); + const AHashMap::ConstIterator it = property_cache.find(p_name); + if (it) { + return process_state->tree->property_map.get_by_index(it->value).value.first; + } ERR_FAIL_COND_V(!process_state->tree->property_parent_map.has(node_state.base_path), Variant()); ERR_FAIL_COND_V(!process_state->tree->property_parent_map[node_state.base_path].has(p_name), Variant()); StringName path = process_state->tree->property_parent_map[node_state.base_path][p_name]; - return process_state->tree->property_map[path].first; + int idx = process_state->tree->property_map.get_index(path); + property_cache.insert_new(p_name, idx); + return process_state->tree->property_map.get_by_index(idx).value.first; } void AnimationNode::set_node_time_info(const NodeTimeInfo &p_node_time_info) { @@ -203,7 +217,7 @@ AnimationNode::NodeTimeInfo AnimationNode::_blend_node(Ref p_node } for (const KeyValue &E : filter) { - const HashMap &map = *process_state->track_map; + const AHashMap &map = *process_state->track_map; if (!map.has(E.key)) { continue; } @@ -292,7 +306,7 @@ AnimationNode::NodeTimeInfo AnimationNode::_blend_node(Ref p_node // This process, which depends on p_sync is needed to process sync correctly in the case of // that a synced AnimationNodeSync exists under the un-synced AnimationNodeSync. - p_node->node_state.base_path = new_path; + p_node->set_node_state_base_path(new_path); p_node->node_state.parent = new_parent; if (!p_playback_info.seeked && !p_sync && !any_valid) { p_playback_info.delta = 0.0; @@ -603,7 +617,7 @@ Ref AnimationTree::get_root_animation_node() const { return root_animation_node; } -bool AnimationTree::_blend_pre_process(double p_delta, int p_track_count, const HashMap &p_track_map) { +bool AnimationTree::_blend_pre_process(double p_delta, int p_track_count, const AHashMap &p_track_map) { _update_properties(); // If properties need updating, update them. if (!root_animation_node.is_valid()) { @@ -627,7 +641,7 @@ bool AnimationTree::_blend_pre_process(double p_delta, int p_track_count, const for (int i = 0; i < p_track_count; i++) { src_blendsw[i] = 1.0; // By default all go to 1 for the root input. } - root_animation_node->node_state.base_path = SNAME(Animation::PARAMETERS_BASE_PATH.ascii().get_data()); + root_animation_node->set_node_state_base_path(SNAME(Animation::PARAMETERS_BASE_PATH.ascii().get_data())); root_animation_node->node_state.parent = nullptr; } @@ -732,7 +746,7 @@ void AnimationTree::_animation_node_removed(const ObjectID &p_oid, const StringN void AnimationTree::_update_properties_for_node(const String &p_base_path, Ref p_node) { ERR_FAIL_COND(p_node.is_null()); if (!property_parent_map.has(p_base_path)) { - property_parent_map[p_base_path] = HashMap(); + property_parent_map[p_base_path] = AHashMap(); } if (!property_reference_map.has(p_node->get_instance_id())) { property_reference_map[p_node->get_instance_id()] = p_base_path; @@ -767,7 +781,7 @@ void AnimationTree::_update_properties_for_node(const String &p_base_path, Refmake_cache_dirty(); List children; p_node->get_child_nodes(&children); diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h index d4b7bf31c9d..8ee80f29ee2 100644 --- a/scene/animation/animation_tree.h +++ b/scene/animation/animation_tree.h @@ -60,7 +60,7 @@ public: bool closable = false; Vector inputs; - HashMap filter; + AHashMap filter; bool filter_enabled = false; // To propagate information from upstream for use in estimation of playback progress. @@ -97,22 +97,57 @@ public: // Temporary state for blending process which needs to be stored in each AnimationNodes. struct NodeState { + friend AnimationNode; + + private: StringName base_path; + + public: AnimationNode *parent = nullptr; Vector connections; Vector track_weights; + + const StringName get_base_path() const { + return base_path; + } + } node_state; // Temporary state for blending process which needs to be started in the AnimationTree, pass through the AnimationNodes, and then return to the AnimationTree. struct ProcessState { AnimationTree *tree = nullptr; - const HashMap *track_map; // TODO: Is there a better way to manage filter/tracks? + const AHashMap *track_map; // TODO: Is there a better way to manage filter/tracks? bool is_testing = false; bool valid = false; String invalid_reasons; uint64_t last_pass = 0; } *process_state = nullptr; +private: + mutable AHashMap property_cache; + +public: + void set_node_state_base_path(const StringName p_base_path) { + if (p_base_path != node_state.base_path) { + node_state.base_path = p_base_path; + make_cache_dirty(); + } + } + + void set_node_state_base_path(const String p_base_path) { + if (p_base_path != node_state.base_path) { + node_state.base_path = p_base_path; + make_cache_dirty(); + } + } + + const StringName get_node_state_base_path() const { + return node_state.get_base_path(); + } + + void make_cache_dirty() { + property_cache.clear(); + } Array _get_filters() const; void _set_filters(const Array &p_filters); friend class AnimationNodeBlendTree; @@ -250,9 +285,9 @@ private: friend class AnimationNode; List properties; - HashMap> property_parent_map; - HashMap property_reference_map; - HashMap> property_map; // Property value and read-only flag. + AHashMap> property_parent_map; + AHashMap property_reference_map; + AHashMap> property_map; // Property value and read-only flag. bool properties_dirty = true; @@ -286,7 +321,7 @@ private: virtual void _set_active(bool p_active) override; // Make animation instances. - virtual bool _blend_pre_process(double p_delta, int p_track_count, const HashMap &p_track_map) override; + virtual bool _blend_pre_process(double p_delta, int p_track_count, const AHashMap &p_track_map) override; #ifndef DISABLE_DEPRECATED void _set_process_callback_bind_compat_80813(AnimationProcessCallback p_mode); diff --git a/tests/core/templates/test_a_hash_map.h b/tests/core/templates/test_a_hash_map.h new file mode 100644 index 00000000000..e67ee7b4415 --- /dev/null +++ b/tests/core/templates/test_a_hash_map.h @@ -0,0 +1,295 @@ +/**************************************************************************/ +/* test_a_hash_map.h */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +#ifndef TEST_A_HASH_MAP_H +#define TEST_A_HASH_MAP_H + +#include "core/templates/a_hash_map.h" + +#include "tests/test_macros.h" + +namespace TestAHashMap { + +TEST_CASE("[AHashMap] Insert element") { + AHashMap map; + AHashMap::Iterator e = map.insert(42, 84); + + CHECK(e); + CHECK(e->key == 42); + CHECK(e->value == 84); + CHECK(map[42] == 84); + CHECK(map.has(42)); + CHECK(map.find(42)); +} + +TEST_CASE("[AHashMap] Overwrite element") { + AHashMap map; + map.insert(42, 84); + map.insert(42, 1234); + + CHECK(map[42] == 1234); +} + +TEST_CASE("[AHashMap] Erase via element") { + AHashMap map; + AHashMap::Iterator e = map.insert(42, 84); + map.remove(e); + CHECK(!map.has(42)); + CHECK(!map.find(42)); +} + +TEST_CASE("[AHashMap] Erase via key") { + AHashMap map; + map.insert(42, 84); + map.erase(42); + CHECK(!map.has(42)); + CHECK(!map.find(42)); +} + +TEST_CASE("[AHashMap] Size") { + AHashMap map; + map.insert(42, 84); + map.insert(123, 84); + map.insert(123, 84); + map.insert(0, 84); + map.insert(123485, 84); + + CHECK(map.size() == 4); +} + +TEST_CASE("[AHashMap] Iteration") { + AHashMap map; + + map.insert(42, 84); + map.insert(123, 12385); + map.insert(0, 12934); + map.insert(123485, 1238888); + map.insert(123, 111111); + + Vector> expected; + expected.push_back(Pair(42, 84)); + expected.push_back(Pair(123, 111111)); + expected.push_back(Pair(0, 12934)); + expected.push_back(Pair(123485, 1238888)); + + int idx = 0; + for (const KeyValue &E : map) { + CHECK(expected[idx] == Pair(E.key, E.value)); + idx++; + } + + idx--; + for (AHashMap::Iterator it = map.last(); it; --it) { + CHECK(expected[idx] == Pair(it->key, it->value)); + idx--; + } +} + +TEST_CASE("[AHashMap] Const iteration") { + AHashMap map; + map.insert(42, 84); + map.insert(123, 12385); + map.insert(0, 12934); + map.insert(123485, 1238888); + map.insert(123, 111111); + + const AHashMap const_map = map; + + Vector> expected; + expected.push_back(Pair(42, 84)); + expected.push_back(Pair(123, 111111)); + expected.push_back(Pair(0, 12934)); + expected.push_back(Pair(123485, 1238888)); + expected.push_back(Pair(123, 111111)); + + int idx = 0; + for (const KeyValue &E : const_map) { + CHECK(expected[idx] == Pair(E.key, E.value)); + idx++; + } + + idx--; + for (AHashMap::ConstIterator it = const_map.last(); it; --it) { + CHECK(expected[idx] == Pair(it->key, it->value)); + idx--; + } +} + +TEST_CASE("[AHashMap] Replace key") { + AHashMap map; + map.insert(42, 84); + map.insert(0, 12934); + CHECK(map.replace_key(0, 1)); + CHECK(map.has(1)); + CHECK(map[1] == 12934); +} + +TEST_CASE("[AHashMap] Clear") { + AHashMap map; + map.insert(42, 84); + map.insert(123, 12385); + map.insert(0, 12934); + + map.clear(); + CHECK(!map.has(42)); + CHECK(map.size() == 0); + CHECK(map.is_empty()); +} + +TEST_CASE("[AHashMap] Get") { + AHashMap map; + map.insert(42, 84); + map.insert(123, 12385); + map.insert(0, 12934); + + CHECK(map.get(123) == 12385); + map.get(123) = 10; + CHECK(map.get(123) == 10); + + CHECK(*map.getptr(0) == 12934); + *map.getptr(0) = 1; + CHECK(*map.getptr(0) == 1); + + CHECK(map.get(42) == 84); + CHECK(map.getptr(-10) == nullptr); +} + +TEST_CASE("[AHashMap] Insert, iterate and remove many elements") { + const int elem_max = 1234; + AHashMap map; + for (int i = 0; i < elem_max; i++) { + map.insert(i, i); + } + + //insert order should have been kept + int idx = 0; + for (auto &K : map) { + CHECK(idx == K.key); + CHECK(idx == K.value); + CHECK(map.has(idx)); + idx++; + } + + Vector elems_still_valid; + + for (int i = 0; i < elem_max; i++) { + if ((i % 5) == 0) { + map.erase(i); + } else { + elems_still_valid.push_back(i); + } + } + + CHECK(elems_still_valid.size() == map.size()); + + for (int i = 0; i < elems_still_valid.size(); i++) { + CHECK(map.has(elems_still_valid[i])); + } +} + +TEST_CASE("[AHashMap] Insert, iterate and remove many strings") { + const int elem_max = 432; + AHashMap map; + for (int i = 0; i < elem_max; i++) { + map.insert(itos(i), itos(i)); + } + + //insert order should have been kept + int idx = 0; + for (auto &K : map) { + CHECK(itos(idx) == K.key); + CHECK(itos(idx) == K.value); + CHECK(map.has(itos(idx))); + idx++; + } + + Vector elems_still_valid; + + for (int i = 0; i < elem_max; i++) { + if ((i % 5) == 0) { + map.erase(itos(i)); + } else { + elems_still_valid.push_back(itos(i)); + } + } + + CHECK(elems_still_valid.size() == map.size()); + + for (int i = 0; i < elems_still_valid.size(); i++) { + CHECK(map.has(elems_still_valid[i])); + } + + elems_still_valid.clear(); +} + +TEST_CASE("[AHashMap] Copy constructor") { + AHashMap map0; + const uint32_t count = 5; + for (uint32_t i = 0; i < count; i++) { + map0.insert(i, i); + } + AHashMap map1(map0); + CHECK(map0.size() == map1.size()); + CHECK(map0.get_capacity() == map1.get_capacity()); + CHECK(*map0.getptr(0) == *map1.getptr(0)); +} + +TEST_CASE("[AHashMap] Operator =") { + AHashMap map0; + AHashMap map1; + const uint32_t count = 5; + map1.insert(1234, 1234); + for (uint32_t i = 0; i < count; i++) { + map0.insert(i, i); + } + map1 = map0; + CHECK(map0.size() == map1.size()); + CHECK(map0.get_capacity() == map1.get_capacity()); + CHECK(*map0.getptr(0) == *map1.getptr(0)); +} + +TEST_CASE("[AHashMap] Array methods") { + AHashMap map; + for (int i = 0; i < 100; i++) { + map.insert(100 - i, i); + } + for (int i = 0; i < 100; i++) { + CHECK(map.get_by_index(i).value == i); + } + int index = map.get_index(1); + CHECK(map.get_by_index(index).value == 99); + CHECK(map.erase_by_index(index)); + CHECK(!map.erase_by_index(index)); + CHECK(map.get_index(1) == -1); +} + +} // namespace TestAHashMap + +#endif // TEST_A_HASH_MAP_H diff --git a/tests/test_main.cpp b/tests/test_main.cpp index 465484d6058..979aee80013 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -86,6 +86,7 @@ #include "tests/core/string/test_string.h" #include "tests/core/string/test_translation.h" #include "tests/core/string/test_translation_server.h" +#include "tests/core/templates/test_a_hash_map.h" #include "tests/core/templates/test_command_queue.h" #include "tests/core/templates/test_hash_map.h" #include "tests/core/templates/test_hash_set.h"