Merge pull request #11405 from karroffel/new-hashmap
added OAHashMap type
This commit is contained in:
commit
67aa409f59
6 changed files with 770 additions and 33 deletions
|
@ -37,39 +37,6 @@
|
|||
#include "os/memory.h"
|
||||
#include "ustring.h"
|
||||
|
||||
struct HashMapHasherDefault {
|
||||
static _FORCE_INLINE_ uint32_t hash(const String &p_string) { return p_string.hash(); }
|
||||
static _FORCE_INLINE_ uint32_t hash(const char *p_cstr) { return hash_djb2(p_cstr); }
|
||||
static _FORCE_INLINE_ uint32_t hash(const uint64_t p_int) { return hash_one_uint64(p_int); }
|
||||
|
||||
static _FORCE_INLINE_ uint32_t hash(const int64_t p_int) { return hash(uint64_t(p_int)); }
|
||||
static _FORCE_INLINE_ uint32_t hash(const float p_float) { return hash_djb2_one_float(p_float); }
|
||||
static _FORCE_INLINE_ uint32_t hash(const double p_double) { return hash_djb2_one_float(p_double); }
|
||||
static _FORCE_INLINE_ uint32_t hash(const uint32_t p_int) { return p_int; }
|
||||
static _FORCE_INLINE_ uint32_t hash(const int32_t p_int) { return (uint32_t)p_int; }
|
||||
static _FORCE_INLINE_ uint32_t hash(const uint16_t p_int) { return p_int; }
|
||||
static _FORCE_INLINE_ uint32_t hash(const int16_t p_int) { return (uint32_t)p_int; }
|
||||
static _FORCE_INLINE_ uint32_t hash(const uint8_t p_int) { return p_int; }
|
||||
static _FORCE_INLINE_ uint32_t hash(const int8_t p_int) { return (uint32_t)p_int; }
|
||||
static _FORCE_INLINE_ uint32_t hash(const wchar_t p_wchar) { return (uint32_t)p_wchar; }
|
||||
//static _FORCE_INLINE_ uint32_t hash(const void* p_ptr) { return uint32_t(uint64_t(p_ptr))*(0x9e3779b1L); }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct HashMapComparatorDefault {
|
||||
static bool compare(const T &p_lhs, const T &p_rhs) {
|
||||
return p_lhs == p_rhs;
|
||||
}
|
||||
|
||||
bool compare(const float &p_lhs, const float &p_rhs) {
|
||||
return (p_lhs == p_rhs) || (Math::is_nan(p_lhs) && Math::is_nan(p_rhs));
|
||||
}
|
||||
|
||||
bool compare(const double &p_lhs, const double &p_rhs) {
|
||||
return (p_lhs == p_rhs) || (Math::is_nan(p_lhs) && Math::is_nan(p_rhs));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @class HashMap
|
||||
* @author Juan Linietsky <reduzio@gmail.com>
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
#include "math_defs.h"
|
||||
#include "math_funcs.h"
|
||||
#include "typedefs.h"
|
||||
#include "ustring.h"
|
||||
|
||||
/**
|
||||
* Hashing functions
|
||||
|
@ -128,4 +129,37 @@ static inline uint64_t make_uint64_t(T p_in) {
|
|||
return _u._u64;
|
||||
}
|
||||
|
||||
struct HashMapHasherDefault {
|
||||
static _FORCE_INLINE_ uint32_t hash(const String &p_string) { return p_string.hash(); }
|
||||
static _FORCE_INLINE_ uint32_t hash(const char *p_cstr) { return hash_djb2(p_cstr); }
|
||||
static _FORCE_INLINE_ uint32_t hash(const uint64_t p_int) { return hash_one_uint64(p_int); }
|
||||
|
||||
static _FORCE_INLINE_ uint32_t hash(const int64_t p_int) { return hash(uint64_t(p_int)); }
|
||||
static _FORCE_INLINE_ uint32_t hash(const float p_float) { return hash_djb2_one_float(p_float); }
|
||||
static _FORCE_INLINE_ uint32_t hash(const double p_double) { return hash_djb2_one_float(p_double); }
|
||||
static _FORCE_INLINE_ uint32_t hash(const uint32_t p_int) { return p_int; }
|
||||
static _FORCE_INLINE_ uint32_t hash(const int32_t p_int) { return (uint32_t)p_int; }
|
||||
static _FORCE_INLINE_ uint32_t hash(const uint16_t p_int) { return p_int; }
|
||||
static _FORCE_INLINE_ uint32_t hash(const int16_t p_int) { return (uint32_t)p_int; }
|
||||
static _FORCE_INLINE_ uint32_t hash(const uint8_t p_int) { return p_int; }
|
||||
static _FORCE_INLINE_ uint32_t hash(const int8_t p_int) { return (uint32_t)p_int; }
|
||||
static _FORCE_INLINE_ uint32_t hash(const wchar_t p_wchar) { return (uint32_t)p_wchar; }
|
||||
//static _FORCE_INLINE_ uint32_t hash(const void* p_ptr) { return uint32_t(uint64_t(p_ptr))*(0x9e3779b1L); }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct HashMapComparatorDefault {
|
||||
static bool compare(const T &p_lhs, const T &p_rhs) {
|
||||
return p_lhs == p_rhs;
|
||||
}
|
||||
|
||||
bool compare(const float &p_lhs, const float &p_rhs) {
|
||||
return (p_lhs == p_rhs) || (Math::is_nan(p_lhs) && Math::is_nan(p_rhs));
|
||||
}
|
||||
|
||||
bool compare(const double &p_lhs, const double &p_rhs) {
|
||||
return (p_lhs == p_rhs) || (Math::is_nan(p_lhs) && Math::is_nan(p_rhs));
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
593
core/oa_hash_map.h
Normal file
593
core/oa_hash_map.h
Normal file
|
@ -0,0 +1,593 @@
|
|||
/*************************************************************************/
|
||||
/* oa_hash_map.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
#ifndef OA_HASH_MAP_H
|
||||
#define OA_HASH_MAP_H
|
||||
|
||||
#include "hashfuncs.h"
|
||||
#include "math_funcs.h"
|
||||
#include "os/copymem.h"
|
||||
#include "os/memory.h"
|
||||
|
||||
// uncomment this to disable intial local storage.
|
||||
#define OA_HASH_MAP_INITIAL_LOCAL_STORAGE
|
||||
|
||||
/**
|
||||
* This class implements a hash map datastructure that uses open addressing with
|
||||
* local probing.
|
||||
*
|
||||
* It can give huge performance improvements over a chained HashMap because of
|
||||
* the increased data locality.
|
||||
*
|
||||
* Because of that locality property it's important to not use "large" value
|
||||
* types as the "TData" type. If TData values are too big it can cause more
|
||||
* cache misses then chaining. If larger values are needed then storing those
|
||||
* in a separate array and using pointers or indices to reference them is the
|
||||
* better solution.
|
||||
*
|
||||
* This hash map also implements real-time incremental rehashing.
|
||||
*
|
||||
*/
|
||||
template <class TKey, class TData,
|
||||
uint16_t INITIAL_NUM_ELEMENTS = 64,
|
||||
class Hasher = HashMapHasherDefault,
|
||||
class Comparator = HashMapComparatorDefault<TKey> >
|
||||
class OAHashMap {
|
||||
|
||||
private:
|
||||
#ifdef OA_HASH_MAP_INITIAL_LOCAL_STORAGE
|
||||
TData local_data[INITIAL_NUM_ELEMENTS];
|
||||
TKey local_keys[INITIAL_NUM_ELEMENTS];
|
||||
uint32_t local_hashes[INITIAL_NUM_ELEMENTS];
|
||||
uint8_t local_flags[INITIAL_NUM_ELEMENTS / 4 + (INITIAL_NUM_ELEMENTS % 4 != 0 ? 1 : 0)];
|
||||
#endif
|
||||
|
||||
struct {
|
||||
TData *data;
|
||||
TKey *keys;
|
||||
uint32_t *hashes;
|
||||
|
||||
// This is actually an array of bits, 4 bit pairs per octet.
|
||||
// | ba ba ba ba | ba ba ba ba | ....
|
||||
//
|
||||
// if a is set it means that there is an element present.
|
||||
// if b is set it means that an element was deleted. This is needed for
|
||||
// the local probing to work without relocating any succeeding and
|
||||
// colliding entries.
|
||||
uint8_t *flags;
|
||||
|
||||
uint32_t capacity;
|
||||
} table, old_table;
|
||||
|
||||
bool is_rehashing;
|
||||
uint32_t rehash_position;
|
||||
uint32_t rehash_amount;
|
||||
|
||||
uint32_t elements;
|
||||
|
||||
/* Methods */
|
||||
|
||||
// returns true if the value already existed, false if it's a new entry
|
||||
bool _raw_set_with_hash(uint32_t p_hash, const TKey &p_key, const TData &p_data) {
|
||||
for (int i = 0; i < table.capacity; i++) {
|
||||
|
||||
int pos = (p_hash + i) % table.capacity;
|
||||
|
||||
int flags_pos = pos / 4;
|
||||
int flags_pos_offset = pos % 4;
|
||||
|
||||
bool is_filled_flag = table.flags[flags_pos] & (1 << (2 * flags_pos_offset));
|
||||
bool is_deleted_flag = table.flags[flags_pos] & (1 << (2 * flags_pos_offset + 1));
|
||||
|
||||
if (is_filled_flag) {
|
||||
if (table.hashes[pos] == p_hash && Comparator::compare(table.keys[pos], p_key)) {
|
||||
table.data[pos] = p_data;
|
||||
return true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
table.keys[pos] = p_key;
|
||||
table.data[pos] = p_data;
|
||||
table.hashes[pos] = p_hash;
|
||||
|
||||
table.flags[flags_pos] |= (1 << (2 * flags_pos_offset));
|
||||
table.flags[flags_pos] &= ~(1 << (2 * flags_pos_offset + 1));
|
||||
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public:
|
||||
_FORCE_INLINE_ uint32_t get_capacity() const { return table.capacity; }
|
||||
_FORCE_INLINE_ uint32_t get_num_elements() const { return elements; }
|
||||
|
||||
void set(const TKey &p_key, const TData &p_data) {
|
||||
|
||||
uint32_t hash = Hasher::hash(p_key);
|
||||
|
||||
// We don't progress the rehashing if the table just got resized
|
||||
// to keep the cost of this function low.
|
||||
if (is_rehashing) {
|
||||
|
||||
// rehash progress
|
||||
|
||||
for (int i = 0; i <= rehash_amount && rehash_position < old_table.capacity; rehash_position++) {
|
||||
|
||||
int flags_pos = rehash_position / 4;
|
||||
int flags_pos_offset = rehash_position % 4;
|
||||
|
||||
bool is_filled_flag = (old_table.flags[flags_pos] & (1 << (2 * flags_pos_offset))) > 0;
|
||||
bool is_deleted_flag = (old_table.flags[flags_pos] & (1 << (2 * flags_pos_offset + 1))) > 0;
|
||||
|
||||
if (is_filled_flag) {
|
||||
_raw_set_with_hash(old_table.hashes[rehash_position], old_table.keys[rehash_position], old_table.data[rehash_position]);
|
||||
|
||||
old_table.keys[rehash_position].~TKey();
|
||||
old_table.data[rehash_position].~TData();
|
||||
|
||||
memnew_placement(&old_table.keys[rehash_position], TKey);
|
||||
memnew_placement(&old_table.data[rehash_position], TData);
|
||||
|
||||
old_table.flags[flags_pos] &= ~(1 << (2 * flags_pos_offset));
|
||||
old_table.flags[flags_pos] |= (1 << (2 * flags_pos_offset + 1));
|
||||
}
|
||||
}
|
||||
|
||||
if (rehash_position >= old_table.capacity) {
|
||||
|
||||
// wohooo, we can get rid of the old table.
|
||||
is_rehashing = false;
|
||||
|
||||
#ifdef OA_HASH_MAP_INITIAL_LOCAL_STORAGE
|
||||
if (old_table.data == local_data) {
|
||||
// Everything is local, so no cleanup :P
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
memdelete_arr(old_table.data);
|
||||
memdelete_arr(old_table.keys);
|
||||
memdelete_arr(old_table.hashes);
|
||||
memdelete_arr(old_table.flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Table is almost full, resize and start rehashing process.
|
||||
if (elements >= table.capacity * 0.7) {
|
||||
|
||||
old_table.capacity = table.capacity;
|
||||
old_table.data = table.data;
|
||||
old_table.flags = table.flags;
|
||||
old_table.hashes = table.hashes;
|
||||
old_table.keys = table.keys;
|
||||
|
||||
table.capacity = old_table.capacity * 2;
|
||||
|
||||
table.data = memnew_arr(TData, table.capacity);
|
||||
table.flags = memnew_arr(uint8_t, table.capacity / 4 + (table.capacity % 4 != 0 ? 1 : 0));
|
||||
table.hashes = memnew_arr(uint32_t, table.capacity);
|
||||
table.keys = memnew_arr(TKey, table.capacity);
|
||||
|
||||
zeromem(table.flags, table.capacity / 4 + (table.capacity % 4 != 0 ? 1 : 0));
|
||||
|
||||
is_rehashing = true;
|
||||
rehash_position = 0;
|
||||
rehash_amount = (elements * 2) / (table.capacity * 0.7 - old_table.capacity);
|
||||
}
|
||||
|
||||
if (!_raw_set_with_hash(hash, p_key, p_data))
|
||||
elements++;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns true if the value was found, false otherwise.
|
||||
*
|
||||
* if r_data is not NULL then the value will be written to the object
|
||||
* it points to.
|
||||
*/
|
||||
bool lookup(const TKey &p_key, TData *r_data) {
|
||||
|
||||
uint32_t hash = Hasher::hash(p_key);
|
||||
|
||||
bool check_old_table = is_rehashing;
|
||||
bool check_new_table = true;
|
||||
|
||||
// search for the key and return the value associated with it
|
||||
//
|
||||
// if we're rehashing we need to check both the old and the
|
||||
// current table. If we find a value in the old table we still
|
||||
// need to continue searching in the new table as it might have
|
||||
// been added after
|
||||
|
||||
TData *value = NULL;
|
||||
|
||||
for (int i = 0; i < table.capacity; i++) {
|
||||
|
||||
if (!check_new_table && !check_old_table) {
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// if we're rehashing check the old table
|
||||
if (check_old_table && i < old_table.capacity) {
|
||||
|
||||
int pos = (hash + i) % old_table.capacity;
|
||||
|
||||
int flags_pos = pos / 4;
|
||||
int flags_pos_offset = pos % 4;
|
||||
|
||||
bool is_filled_flag = (old_table.flags[flags_pos] & (1 << (2 * flags_pos_offset))) > 0;
|
||||
bool is_deleted_flag = (old_table.flags[flags_pos] & (1 << (2 * flags_pos_offset + 1))) > 0;
|
||||
|
||||
if (is_filled_flag) {
|
||||
// found our entry?
|
||||
if (old_table.hashes[pos] == hash && Comparator::compare(old_table.keys[pos], p_key)) {
|
||||
value = &old_table.data[pos];
|
||||
check_old_table = false;
|
||||
}
|
||||
} else if (!is_deleted_flag) {
|
||||
|
||||
// we hit an empty field here, we don't
|
||||
// need to further check this old table
|
||||
// because we know it's not in here.
|
||||
|
||||
check_old_table = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (check_new_table) {
|
||||
|
||||
int pos = (hash + i) % table.capacity;
|
||||
|
||||
int flags_pos = pos / 4;
|
||||
int flags_pos_offset = pos % 4;
|
||||
|
||||
bool is_filled_flag = (table.flags[flags_pos] & (1 << (2 * flags_pos_offset))) > 0;
|
||||
bool is_deleted_flag = (table.flags[flags_pos] & (1 << (2 * flags_pos_offset + 1))) > 0;
|
||||
|
||||
if (is_filled_flag) {
|
||||
// found our entry?
|
||||
if (table.hashes[pos] == hash && Comparator::compare(table.keys[pos], p_key)) {
|
||||
if (r_data != NULL)
|
||||
*r_data = table.data[pos];
|
||||
return true;
|
||||
}
|
||||
continue;
|
||||
} else if (is_deleted_flag) {
|
||||
continue;
|
||||
} else if (value != NULL) {
|
||||
|
||||
// We found a value in the old table
|
||||
if (r_data != NULL)
|
||||
*r_data = *value;
|
||||
return true;
|
||||
} else {
|
||||
check_new_table = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (value != NULL) {
|
||||
if (r_data != NULL)
|
||||
*r_data = *value;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ bool has(const TKey &p_key) {
|
||||
return lookup(p_key, NULL);
|
||||
}
|
||||
|
||||
void remove(const TKey &p_key) {
|
||||
uint32_t hash = Hasher::hash(p_key);
|
||||
|
||||
bool check_old_table = is_rehashing;
|
||||
bool check_new_table = true;
|
||||
|
||||
for (int i = 0; i < table.capacity; i++) {
|
||||
|
||||
if (!check_new_table && !check_old_table) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if we're rehashing check the old table
|
||||
if (check_old_table && i < old_table.capacity) {
|
||||
|
||||
int pos = (hash + i) % old_table.capacity;
|
||||
|
||||
int flags_pos = pos / 4;
|
||||
int flags_pos_offset = pos % 4;
|
||||
|
||||
bool is_filled_flag = (old_table.flags[flags_pos] & (1 << (2 * flags_pos_offset))) > 0;
|
||||
bool is_deleted_flag = (old_table.flags[flags_pos] & (1 << (2 * flags_pos_offset + 1))) > 0;
|
||||
|
||||
if (is_filled_flag) {
|
||||
// found our entry?
|
||||
if (old_table.hashes[pos] == hash && Comparator::compare(old_table.keys[pos], p_key)) {
|
||||
old_table.keys[pos].~TKey();
|
||||
old_table.data[pos].~TData();
|
||||
|
||||
memnew_placement(&old_table.keys[pos], TKey);
|
||||
memnew_placement(&old_table.data[pos], TData);
|
||||
|
||||
old_table.flags[flags_pos] &= ~(1 << (2 * flags_pos_offset));
|
||||
old_table.flags[flags_pos] |= (1 << (2 * flags_pos_offset + 1));
|
||||
|
||||
elements--;
|
||||
return;
|
||||
}
|
||||
} else if (!is_deleted_flag) {
|
||||
|
||||
// we hit an empty field here, we don't
|
||||
// need to further check this old table
|
||||
// because we know it's not in here.
|
||||
|
||||
check_old_table = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (check_new_table) {
|
||||
|
||||
int pos = (hash + i) % table.capacity;
|
||||
|
||||
int flags_pos = pos / 4;
|
||||
int flags_pos_offset = pos % 4;
|
||||
|
||||
bool is_filled_flag = (table.flags[flags_pos] & (1 << (2 * flags_pos_offset))) > 0;
|
||||
bool is_deleted_flag = (table.flags[flags_pos] & (1 << (2 * flags_pos_offset + 1))) > 0;
|
||||
|
||||
if (is_filled_flag) {
|
||||
// found our entry?
|
||||
if (table.hashes[pos] == hash && Comparator::compare(table.keys[pos], p_key)) {
|
||||
table.keys[pos].~TKey();
|
||||
table.data[pos].~TData();
|
||||
|
||||
memnew_placement(&table.keys[pos], TKey);
|
||||
memnew_placement(&table.data[pos], TData);
|
||||
|
||||
table.flags[flags_pos] &= ~(1 << (2 * flags_pos_offset));
|
||||
table.flags[flags_pos] |= (1 << (2 * flags_pos_offset + 1));
|
||||
|
||||
// don't return here, this value might still be in the old table
|
||||
// if it was already relocated.
|
||||
|
||||
elements--;
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
} else if (is_deleted_flag) {
|
||||
continue;
|
||||
} else {
|
||||
check_new_table = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Iterator {
|
||||
bool valid;
|
||||
|
||||
uint32_t hash;
|
||||
|
||||
const TKey *key;
|
||||
const TData *data;
|
||||
|
||||
private:
|
||||
friend class OAHashMap;
|
||||
bool was_from_old_table;
|
||||
};
|
||||
|
||||
Iterator iter() const {
|
||||
Iterator it;
|
||||
|
||||
it.valid = false;
|
||||
it.was_from_old_table = false;
|
||||
|
||||
bool check_old_table = is_rehashing;
|
||||
|
||||
for (int i = 0; i < table.capacity; i++) {
|
||||
|
||||
// if we're rehashing check the old table first
|
||||
if (check_old_table && i < old_table.capacity) {
|
||||
|
||||
int pos = i;
|
||||
|
||||
int flags_pos = pos / 4;
|
||||
int flags_pos_offset = pos % 4;
|
||||
|
||||
bool is_filled_flag = (old_table.flags[flags_pos] & (1 << (2 * flags_pos_offset))) > 0;
|
||||
|
||||
if (is_filled_flag) {
|
||||
it.valid = true;
|
||||
it.hash = old_table.hashes[pos];
|
||||
it.data = &old_table.data[pos];
|
||||
it.key = &old_table.keys[pos];
|
||||
|
||||
it.was_from_old_table = true;
|
||||
|
||||
return it;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
int pos = i;
|
||||
|
||||
int flags_pos = pos / 4;
|
||||
int flags_pos_offset = pos % 4;
|
||||
|
||||
bool is_filled_flag = (table.flags[flags_pos] & (1 << (2 * flags_pos_offset))) > 0;
|
||||
|
||||
if (is_filled_flag) {
|
||||
it.valid = true;
|
||||
it.hash = table.hashes[pos];
|
||||
it.data = &table.data[pos];
|
||||
it.key = &table.keys[pos];
|
||||
|
||||
return it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return it;
|
||||
}
|
||||
|
||||
Iterator next_iter(const Iterator &p_iter) const {
|
||||
if (!p_iter.valid) {
|
||||
return p_iter;
|
||||
}
|
||||
|
||||
Iterator it;
|
||||
|
||||
it.valid = false;
|
||||
it.was_from_old_table = false;
|
||||
|
||||
bool check_old_table = is_rehashing;
|
||||
|
||||
// we use this to skip the first check or not
|
||||
bool was_from_old_table = p_iter.was_from_old_table;
|
||||
|
||||
int prev_index = (p_iter.data - (p_iter.was_from_old_table ? old_table.data : table.data));
|
||||
|
||||
if (!was_from_old_table) {
|
||||
prev_index++;
|
||||
}
|
||||
|
||||
for (int i = prev_index; i < table.capacity; i++) {
|
||||
|
||||
// if we're rehashing check the old table first
|
||||
if (check_old_table && i < old_table.capacity && !was_from_old_table) {
|
||||
|
||||
int pos = i;
|
||||
|
||||
int flags_pos = pos / 4;
|
||||
int flags_pos_offset = pos % 4;
|
||||
|
||||
bool is_filled_flag = (old_table.flags[flags_pos] & (1 << (2 * flags_pos_offset))) > 0;
|
||||
|
||||
if (is_filled_flag) {
|
||||
it.valid = true;
|
||||
it.hash = old_table.hashes[pos];
|
||||
it.data = &old_table.data[pos];
|
||||
it.key = &old_table.keys[pos];
|
||||
|
||||
it.was_from_old_table = true;
|
||||
|
||||
return it;
|
||||
}
|
||||
}
|
||||
|
||||
was_from_old_table = false;
|
||||
|
||||
{
|
||||
int pos = i;
|
||||
|
||||
int flags_pos = pos / 4;
|
||||
int flags_pos_offset = pos % 4;
|
||||
|
||||
bool is_filled_flag = (table.flags[flags_pos] & (1 << (2 * flags_pos_offset))) > 0;
|
||||
|
||||
if (is_filled_flag) {
|
||||
it.valid = true;
|
||||
it.hash = table.hashes[pos];
|
||||
it.data = &table.data[pos];
|
||||
it.key = &table.keys[pos];
|
||||
|
||||
return it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return it;
|
||||
}
|
||||
|
||||
OAHashMap(uint32_t p_initial_capacity = INITIAL_NUM_ELEMENTS) {
|
||||
|
||||
#ifdef OA_HASH_MAP_INITIAL_LOCAL_STORAGE
|
||||
|
||||
if (p_initial_capacity <= INITIAL_NUM_ELEMENTS) {
|
||||
table.data = local_data;
|
||||
table.keys = local_keys;
|
||||
table.hashes = local_hashes;
|
||||
table.flags = local_flags;
|
||||
|
||||
zeromem(table.flags, INITIAL_NUM_ELEMENTS / 4 + (INITIAL_NUM_ELEMENTS % 4 != 0 ? 1 : 0));
|
||||
|
||||
table.capacity = INITIAL_NUM_ELEMENTS;
|
||||
elements = 0;
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
table.data = memnew_arr(TData, p_initial_capacity);
|
||||
table.keys = memnew_arr(TKey, p_initial_capacity);
|
||||
table.hashes = memnew_arr(uint32_t, p_initial_capacity);
|
||||
table.flags = memnew_arr(uint8_t, p_initial_capacity / 4 + (p_initial_capacity % 4 != 0 ? 1 : 0));
|
||||
|
||||
zeromem(table.flags, p_initial_capacity / 4 + (p_initial_capacity % 4 != 0 ? 1 : 0));
|
||||
|
||||
table.capacity = p_initial_capacity;
|
||||
elements = 0;
|
||||
}
|
||||
|
||||
is_rehashing = false;
|
||||
rehash_position = 0;
|
||||
}
|
||||
|
||||
~OAHashMap() {
|
||||
#ifdef OA_HASH_MAP_INITIAL_LOCAL_STORAGE
|
||||
if (table.capacity <= INITIAL_NUM_ELEMENTS) {
|
||||
return; // Everything is local, so no cleanup :P
|
||||
}
|
||||
#endif
|
||||
if (is_rehashing) {
|
||||
|
||||
#ifdef OA_HASH_MAP_INITIAL_LOCAL_STORAGE
|
||||
if (old_table.data == local_data) {
|
||||
// Everything is local, so no cleanup :P
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
memdelete_arr(old_table.data);
|
||||
memdelete_arr(old_table.keys);
|
||||
memdelete_arr(old_table.hashes);
|
||||
memdelete_arr(old_table.flags);
|
||||
}
|
||||
}
|
||||
|
||||
memdelete_arr(table.data);
|
||||
memdelete_arr(table.keys);
|
||||
memdelete_arr(table.hashes);
|
||||
memdelete_arr(table.flags);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
|
@ -37,6 +37,7 @@
|
|||
#include "test_image.h"
|
||||
#include "test_io.h"
|
||||
#include "test_math.h"
|
||||
#include "test_oa_hash_map.h"
|
||||
#include "test_ordered_hash_map.h"
|
||||
#include "test_physics.h"
|
||||
#include "test_physics_2d.h"
|
||||
|
@ -56,6 +57,7 @@ const char **tests_get_names() {
|
|||
"io",
|
||||
"shaderlang",
|
||||
"physics",
|
||||
"oa_hash_map",
|
||||
NULL
|
||||
};
|
||||
|
||||
|
@ -89,6 +91,11 @@ MainLoop *test_main(String p_test, const List<String> &p_args) {
|
|||
return TestRender::test();
|
||||
}
|
||||
|
||||
if (p_test == "oa_hash_map") {
|
||||
|
||||
return TestOAHashMap::test();
|
||||
}
|
||||
|
||||
#ifndef _3D_DISABLED
|
||||
if (p_test == "gui") {
|
||||
|
||||
|
|
97
main/tests/test_oa_hash_map.cpp
Normal file
97
main/tests/test_oa_hash_map.cpp
Normal file
|
@ -0,0 +1,97 @@
|
|||
/*************************************************************************/
|
||||
/* test_oa_hash_map.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "test_oa_hash_map.h"
|
||||
|
||||
#include "core/os/os.h"
|
||||
|
||||
#include "core/oa_hash_map.h"
|
||||
|
||||
namespace TestOAHashMap {
|
||||
|
||||
MainLoop *test() {
|
||||
|
||||
OS::get_singleton()->print("\n\n\nHello from test\n");
|
||||
|
||||
// test element tracking.
|
||||
{
|
||||
OAHashMap<int, int> map;
|
||||
|
||||
map.set(42, 1337);
|
||||
map.set(1337, 21);
|
||||
map.set(42, 11880);
|
||||
|
||||
int value;
|
||||
map.lookup(42, &value);
|
||||
|
||||
OS::get_singleton()->print("capacity %d\n", map.get_capacity());
|
||||
OS::get_singleton()->print("elements %d\n", map.get_num_elements());
|
||||
|
||||
OS::get_singleton()->print("map[42] = %d\n", value);
|
||||
}
|
||||
|
||||
// rehashing and deletion
|
||||
{
|
||||
OAHashMap<int, int> map;
|
||||
|
||||
for (int i = 0; i < 500; i++) {
|
||||
map.set(i, i * 2);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 500; i += 2) {
|
||||
map.remove(i);
|
||||
}
|
||||
|
||||
uint32_t num_elems = 0;
|
||||
for (int i = 0; i < 500; i++) {
|
||||
int tmp;
|
||||
if (map.lookup(i, &tmp))
|
||||
num_elems++;
|
||||
}
|
||||
|
||||
OS::get_singleton()->print("elements %d == %d.\n", map.get_num_elements(), num_elems);
|
||||
}
|
||||
|
||||
// iteration
|
||||
{
|
||||
OAHashMap<String, int> map;
|
||||
|
||||
map.set("Hello", 1);
|
||||
map.set("World", 2);
|
||||
map.set("Godot rocks", 42);
|
||||
|
||||
for (OAHashMap<String, int>::Iterator it = map.iter(); it.valid; it = map.next_iter(it)) {
|
||||
OS::get_singleton()->print("map[\"%s\"] = %d\n", it.key->utf8().get_data(), *it.data);
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
} // namespace TestOAHashMap
|
39
main/tests/test_oa_hash_map.h
Normal file
39
main/tests/test_oa_hash_map.h
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*************************************************************************/
|
||||
/* test_oa_hash_map.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
#ifndef TEST_OA_HASH_MAP_H
|
||||
#define TEST_OA_HASH_MAP_H
|
||||
|
||||
#include "os/main_loop.h"
|
||||
|
||||
namespace TestOAHashMap {
|
||||
|
||||
MainLoop *test();
|
||||
}
|
||||
#endif // TEST_OA_HASH_MAP_H
|
Loading…
Reference in a new issue