godot/core/templates/oa_hash_map.h

399 lines
10 KiB
C++
Raw Permalink Normal View History

2017-09-19 02:40:13 +02:00
/*************************************************************************/
/* oa_hash_map.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
2017-09-19 02:40:13 +02:00
/* */
/* 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. */
/*************************************************************************/
2017-09-19 02:40:13 +02:00
#ifndef OA_HASH_MAP_H
#define OA_HASH_MAP_H
#include "core/math/math_funcs.h"
#include "core/os/memory.h"
#include "core/templates/hashfuncs.h"
2017-09-19 02:40:13 +02:00
/**
* A HashMap implementation that uses open addressing with Robin Hood hashing.
* Robin Hood hashing swaps out entries that have a smaller probing distance
* than the to-be-inserted entry, that evens out the average probing distance
* and enables faster lookups. Backward shift deletion is employed to further
* improve the performance and to avoid infinite loops in rare cases.
2017-09-19 02:40:13 +02:00
*
* The entries are stored inplace, so huge keys or values might fill cache lines
* a lot faster.
*
* Only used keys and values are constructed. For free positions there's space
* in the arrays for each, but that memory is kept uninitialized.
*
* The assignment operator copy the pairs from one map to the other.
2017-09-19 02:40:13 +02:00
*/
template <class TKey, class TValue,
2017-09-19 02:40:13 +02:00
class Hasher = HashMapHasherDefault,
class Comparator = HashMapComparatorDefault<TKey>>
2017-09-19 02:40:13 +02:00
class OAHashMap {
private:
TValue *values = nullptr;
TKey *keys = nullptr;
uint32_t *hashes = nullptr;
2017-09-19 02:40:13 +02:00
uint32_t capacity = 0;
2017-09-19 02:40:13 +02:00
uint32_t num_elements = 0;
2017-09-19 02:40:13 +02:00
static const uint32_t EMPTY_HASH = 0;
2017-09-19 02:40:13 +02:00
_FORCE_INLINE_ uint32_t _hash(const TKey &p_key) const {
uint32_t hash = Hasher::hash(p_key);
2017-09-19 02:40:13 +02:00
if (hash == EMPTY_HASH) {
hash = EMPTY_HASH + 1;
}
2017-09-19 02:40:13 +02:00
return hash;
}
2017-09-19 02:40:13 +02:00
_FORCE_INLINE_ uint32_t _get_probe_length(uint32_t p_pos, uint32_t p_hash) const {
uint32_t original_pos = p_hash % capacity;
return (p_pos - original_pos + capacity) % capacity;
}
2017-09-19 02:40:13 +02:00
_FORCE_INLINE_ void _construct(uint32_t p_pos, uint32_t p_hash, const TKey &p_key, const TValue &p_value) {
memnew_placement(&keys[p_pos], TKey(p_key));
memnew_placement(&values[p_pos], TValue(p_value));
hashes[p_pos] = p_hash;
2017-09-19 02:40:13 +02:00
num_elements++;
}
bool _lookup_pos(const TKey &p_key, uint32_t &r_pos) const {
uint32_t hash = _hash(p_key);
uint32_t pos = hash % capacity;
uint32_t distance = 0;
while (true) {
if (hashes[pos] == EMPTY_HASH) {
return false;
2017-09-19 02:40:13 +02:00
}
if (distance > _get_probe_length(pos, hashes[pos])) {
return false;
}
2017-09-19 02:40:13 +02:00
if (hashes[pos] == hash && Comparator::compare(keys[pos], p_key)) {
r_pos = pos;
return true;
}
2017-09-19 02:40:13 +02:00
pos = (pos + 1) % capacity;
distance++;
2017-09-19 02:40:13 +02:00
}
}
void _insert_with_hash(uint32_t p_hash, const TKey &p_key, const TValue &p_value) {
uint32_t hash = p_hash;
uint32_t distance = 0;
uint32_t pos = hash % capacity;
2017-09-19 02:40:13 +02:00
TKey key = p_key;
TValue value = p_value;
2017-09-19 02:40:13 +02:00
while (true) {
if (hashes[pos] == EMPTY_HASH) {
_construct(pos, hash, key, value);
2017-09-19 02:40:13 +02:00
return;
}
2017-09-19 02:40:13 +02:00
// not an empty slot, let's check the probing length of the existing one
uint32_t existing_probe_len = _get_probe_length(pos, hashes[pos]);
if (existing_probe_len < distance) {
SWAP(hash, hashes[pos]);
SWAP(key, keys[pos]);
SWAP(value, values[pos]);
distance = existing_probe_len;
}
2017-09-19 02:40:13 +02:00
pos = (pos + 1) % capacity;
distance++;
}
}
void _resize_and_rehash(uint32_t p_new_capacity) {
uint32_t old_capacity = capacity;
// Capacity can't be 0.
capacity = MAX(1, p_new_capacity);
2017-09-19 02:40:13 +02:00
TKey *old_keys = keys;
TValue *old_values = values;
uint32_t *old_hashes = hashes;
2017-09-19 02:40:13 +02:00
num_elements = 0;
keys = static_cast<TKey *>(Memory::alloc_static(sizeof(TKey) * capacity));
values = static_cast<TValue *>(Memory::alloc_static(sizeof(TValue) * capacity));
hashes = static_cast<uint32_t *>(Memory::alloc_static(sizeof(uint32_t) * capacity));
2017-09-19 02:40:13 +02:00
Fix warnings for comparison between signed and unsigned integers [-Wsign-compare] Also turn off -Wsign-compare warnings in the future, we do not consider them important. Fixes the following GCC 5 warnings: ``` core/node_path.cpp:279:24: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] core/oa_hash_map.h:169:21: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] core/oa_hash_map.h:314:21: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] drivers/gles2/shader_gles2.cpp:985:23: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] drivers/gles3/rasterizer_storage_gles3.cpp:1075:25: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] drivers/pulseaudio/audio_driver_pulseaudio.cpp:343:34: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] editor/editor_plugin.cpp:525:21: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] editor/editor_properties_array_dict.cpp:747:25: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] editor/plugins/spatial_editor_plugin.cpp:2078:20: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] editor/plugins/spatial_editor_plugin.cpp:4096:27: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] editor/plugins/sprite_editor_plugin.cpp:100:20: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] modules/cvtt/image_compress_cvtt.cpp:122:23: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] modules/cvtt/image_compress_cvtt.cpp:134:77: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] modules/cvtt/image_compress_cvtt.cpp:339:25: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] modules/etc/image_etc.cpp:222:34: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] modules/gdnative/register_types.cpp:242:21: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] modules/gdnative/register_types.cpp:258:21: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] modules/opensimplex/simplex_noise.cpp:200:13: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] modules/opensimplex/simplex_noise.cpp:222:13: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] modules/opensimplex/simplex_noise.cpp:246:13: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] platform/android/export/export.cpp:1085:21: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] platform/android/export/export.cpp:1489:23: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] platform/android/export/export.cpp:1623:22: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] platform/iphone/export/export.cpp:206:20: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] platform/iphone/export/export.cpp:356:20: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] platform/iphone/export/export.cpp:406:20: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] platform/iphone/export/export.cpp:493:21: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] scene/3d/audio_stream_player_3d.cpp:420:23: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] scene/resources/audio_stream_sample.cpp:565:22: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] scene/resources/audio_stream_sample.cpp:571:22: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] servers/audio/audio_rb_resampler.cpp:156:36: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] ``` The following warnings were not fixed, as they implied casting for no gain: ``` core/io/packet_peer.cpp:228:38: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] core/io/resource_format_binary.cpp:109:11: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] drivers/gles2/rasterizer_scene_gles2.cpp:144:57: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] drivers/unix/file_access_unix.cpp:249:46: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] scene/3d/voxel_light_baker.cpp:889:14: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] scene/3d/voxel_light_baker.cpp:1020:14: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] scene/3d/voxel_light_baker.cpp:1154:14: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] scene/3d/voxel_light_baker.cpp:2255:38: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] scene/resources/bit_mask.cpp:336:25: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] servers/audio/audio_stream.cpp:141:49: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] servers/audio/audio_stream.cpp:150:19: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] servers/audio/audio_stream.cpp:154:19: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] servers/audio_server.cpp:86:21: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] servers/audio_server.cpp:89:17: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] ```
2018-09-26 17:38:02 +02:00
for (uint32_t i = 0; i < capacity; i++) {
hashes[i] = 0;
2017-09-19 02:40:13 +02:00
}
if (old_capacity == 0) {
// Nothing to do.
return;
}
for (uint32_t i = 0; i < old_capacity; i++) {
if (old_hashes[i] == EMPTY_HASH) {
continue;
}
2017-09-19 02:40:13 +02:00
_insert_with_hash(old_hashes[i], old_keys[i], old_values[i]);
old_keys[i].~TKey();
old_values[i].~TValue();
}
2017-09-19 02:40:13 +02:00
Memory::free_static(old_keys);
Memory::free_static(old_values);
Memory::free_static(old_hashes);
}
2017-09-19 02:40:13 +02:00
void _resize_and_rehash() {
_resize_and_rehash(capacity * 2);
}
public:
_FORCE_INLINE_ uint32_t get_capacity() const { return capacity; }
_FORCE_INLINE_ uint32_t get_num_elements() const { return num_elements; }
2017-09-19 02:40:13 +02:00
2020-12-15 13:04:21 +01:00
bool is_empty() const {
return num_elements == 0;
}
void clear() {
for (uint32_t i = 0; i < capacity; i++) {
if (hashes[i] == EMPTY_HASH) {
continue;
}
hashes[i] = EMPTY_HASH;
values[i].~TValue();
keys[i].~TKey();
}
num_elements = 0;
}
void insert(const TKey &p_key, const TValue &p_value) {
if (num_elements + 1 > 0.9 * capacity) {
_resize_and_rehash();
2017-09-19 02:40:13 +02:00
}
uint32_t hash = _hash(p_key);
_insert_with_hash(hash, p_key, p_value);
}
void set(const TKey &p_key, const TValue &p_data) {
uint32_t pos = 0;
bool exists = _lookup_pos(p_key, pos);
if (exists) {
values[pos] = p_data;
} else {
insert(p_key, p_data);
}
2017-09-19 02:40:13 +02:00
}
/**
* returns true if the value was found, false otherwise.
*
* if r_data is not nullptr then the value will be written to the object
2017-09-19 02:40:13 +02:00
* it points to.
*/
bool lookup(const TKey &p_key, TValue &r_data) const {
uint32_t pos = 0;
bool exists = _lookup_pos(p_key, pos);
2017-09-19 02:40:13 +02:00
if (exists) {
r_data = values[pos];
2017-09-19 02:40:13 +02:00
return true;
}
2017-09-19 02:40:13 +02:00
return false;
}
/**
* returns true if the value was found, false otherwise.
*
* if r_data is not nullptr then the value will be written to the object
* it points to.
*/
TValue *lookup_ptr(const TKey &p_key) const {
uint32_t pos = 0;
bool exists = _lookup_pos(p_key, pos);
if (exists) {
return &values[pos];
}
2020-04-02 01:20:12 +02:00
return nullptr;
}
_FORCE_INLINE_ bool has(const TKey &p_key) const {
uint32_t _pos = 0;
return _lookup_pos(p_key, _pos);
2017-09-19 02:40:13 +02:00
}
void remove(const TKey &p_key) {
uint32_t pos = 0;
bool exists = _lookup_pos(p_key, pos);
2017-09-19 02:40:13 +02:00
if (!exists) {
return;
2017-09-19 02:40:13 +02:00
}
uint32_t next_pos = (pos + 1) % capacity;
while (hashes[next_pos] != EMPTY_HASH &&
_get_probe_length(next_pos, hashes[next_pos]) != 0) {
SWAP(hashes[next_pos], hashes[pos]);
SWAP(keys[next_pos], keys[pos]);
SWAP(values[next_pos], values[pos]);
pos = next_pos;
next_pos = (pos + 1) % capacity;
}
hashes[pos] = EMPTY_HASH;
values[pos].~TValue();
keys[pos].~TKey();
num_elements--;
2017-09-19 02:40:13 +02:00
}
/**
* 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(p_new_capacity < capacity);
_resize_and_rehash(p_new_capacity);
}
2017-09-19 02:40:13 +02:00
struct Iterator {
bool valid;
const TKey *key;
TValue *value;
2017-09-19 02:40:13 +02:00
private:
uint32_t pos;
2017-09-19 02:40:13 +02:00
friend class OAHashMap;
};
Iterator iter() const {
Iterator it;
it.valid = true;
it.pos = 0;
2017-09-19 02:40:13 +02:00
return next_iter(it);
2017-09-19 02:40:13 +02:00
}
Iterator next_iter(const Iterator &p_iter) const {
if (!p_iter.valid) {
return p_iter;
}
Iterator it;
it.valid = false;
it.pos = p_iter.pos;
2020-04-02 01:20:12 +02:00
it.key = nullptr;
it.value = nullptr;
2017-09-19 02:40:13 +02:00
for (uint32_t i = it.pos; i < capacity; i++) {
it.pos = i + 1;
2017-09-19 02:40:13 +02:00
if (hashes[i] == EMPTY_HASH) {
continue;
2017-09-19 02:40:13 +02:00
}
it.valid = true;
it.key = &keys[i];
it.value = &values[i];
return it;
2017-09-19 02:40:13 +02:00
}
return it;
}
OAHashMap(const OAHashMap &p_other) {
(*this) = p_other;
}
OAHashMap &operator=(const OAHashMap &p_other) {
if (capacity != 0) {
clear();
}
_resize_and_rehash(p_other.capacity);
for (Iterator it = p_other.iter(); it.valid; it = p_other.next_iter(it)) {
set(*it.key, *it.value);
}
return *this;
}
OAHashMap(uint32_t p_initial_capacity = 64) {
// Capacity can't be 0.
capacity = MAX(1, p_initial_capacity);
2017-09-19 02:40:13 +02:00
keys = static_cast<TKey *>(Memory::alloc_static(sizeof(TKey) * capacity));
values = static_cast<TValue *>(Memory::alloc_static(sizeof(TValue) * capacity));
hashes = static_cast<uint32_t *>(Memory::alloc_static(sizeof(uint32_t) * capacity));
2017-09-19 02:40:13 +02:00
for (uint32_t i = 0; i < capacity; i++) {
hashes[i] = EMPTY_HASH;
2017-09-19 02:40:13 +02:00
}
}
~OAHashMap() {
for (uint32_t i = 0; i < capacity; i++) {
if (hashes[i] == EMPTY_HASH) {
continue;
}
values[i].~TValue();
keys[i].~TKey();
}
Memory::free_static(keys);
Memory::free_static(values);
Memory::free_static(hashes);
2017-09-19 02:40:13 +02:00
}
};
#endif // OA_HASH_MAP_H