Add Hash_Table implementation.
This commit is contained in:
parent
a0f95174b2
commit
737062b339
@ -12,8 +12,12 @@
|
||||
// #include "lib/third_party/dear-imgui/imgui_impl_win32.h"
|
||||
// #include "lib/third_party/dear-imgui/imgui_impl_dx11.h"
|
||||
|
||||
#pragma comment(lib, "d3d11")
|
||||
#pragma comment(lib, "d3dcompiler")
|
||||
// #pragma comment(lib, "d3d11")
|
||||
// #pragma comment(lib, "d3dcompiler")
|
||||
#define BASE_RUN_TESTS 1
|
||||
#if BASE_RUN_TESTS
|
||||
#include "lib/Base/run_tests.cpp"
|
||||
#endif
|
||||
|
||||
#include "src/Base_Entry_Point.cpp"
|
||||
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "Base.h"
|
||||
|
||||
#define ALLOCATOR_DEBUG_MODE 1
|
||||
#define ALLOCATOR_POISON_MEMORY_ON_ALLOCATION \
|
||||
(BUILD_DEBUG && ALLOCATOR_DEBUG_MODE)
|
||||
|
||||
@ -47,7 +47,7 @@ ArenaArray<T>* arena_array_new (s64 preallocate_count, Arena_Reserve reserve_siz
|
||||
}
|
||||
|
||||
template <typename T> T* array_start (ArenaArray<T>& array) {
|
||||
return (array.arena->memory_base + ARRAY_ARENA_START_OFFSET);
|
||||
return (T*)(array.arena->memory_base + ARRAY_ARENA_START_OFFSET);
|
||||
}
|
||||
|
||||
template <typename T> bool is_empty (ArenaArray<T>& array) {
|
||||
|
||||
283
lib/Base/Arena_Hash_Table.h
Normal file
283
lib/Base/Arena_Hash_Table.h
Normal file
@ -0,0 +1,283 @@
|
||||
// Should ArenaTable be bootstrapped like String_Builder?
|
||||
// see: new_string_builder
|
||||
template <typename T, typename U>
|
||||
struct ArenaTable {
|
||||
using KeyType = T; using ValueType = U;
|
||||
ArenaArray<Table_Entry<T, U>>* entries = {};
|
||||
s64 count = 0;
|
||||
s64 slots_filled = 0;
|
||||
|
||||
Hash_Function hash_function = nullptr;
|
||||
Hash_Compare_Function compare_function = nullptr;
|
||||
|
||||
s64 add_collisions = 0;
|
||||
s64 find_collisions = 0;
|
||||
|
||||
u32 load_factor_percent = 70;
|
||||
|
||||
bool refill_removed = true;
|
||||
bool count_collisions = false;
|
||||
};
|
||||
|
||||
template <typename T, typename U> bool table_is_valid (ArenaTable<T, U>* table) {
|
||||
if (table == nullptr) return false;
|
||||
if (table->entries == nullptr) return false;
|
||||
if (table->entries->allocated == 0) return false;
|
||||
|
||||
if (table->hash_function == nullptr) return false;
|
||||
if (table->compare_function == nullptr) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T, typename U> void table_init (ArenaTable<T, U>* table, s64 slots_to_allocate=64, Arena_Reserve new_reserve=Arena_Reserve::Size_64M) {
|
||||
s64 n = Next_Power_Of_Two(slots_to_allocate);
|
||||
|
||||
table->entries = arena_array_new<Table_Entry<T, U>> (n, new_reserve);
|
||||
array_resize(*table->entries, n, false); // don't init
|
||||
|
||||
for (s64 i = 0; i < n; i += 1) {
|
||||
(*table->entries)[i].hash = 0;
|
||||
}
|
||||
// default hash and compare functions:
|
||||
table->hash_function = table_hash_function_fnv1a;
|
||||
table->compare_function = keys_match_u64;
|
||||
}
|
||||
|
||||
// Adds given key value pair to the table, returns a pointer to the inserted value.
|
||||
template <typename T, typename U> U* table_add (ArenaTable<T, U>* table, T key, U value) {
|
||||
Assert(table_is_valid(table));
|
||||
Assert(table->load_factor_percent < 100);
|
||||
|
||||
if ( ((table->slots_filled + 1) * 100) >= (table->entries->allocated * table->load_factor_percent) ) {
|
||||
table_resize(table, Next_Power_Of_Two(table->entries->allocated + 64));
|
||||
}
|
||||
Assert(table->slots_filled < table->entries->allocated);
|
||||
|
||||
// #Walk_Table
|
||||
u32 mask = (u32)(table->entries->allocated - 1);
|
||||
|
||||
u32 hash = table->hash_function(&key, sizeof(T));
|
||||
|
||||
if (hash < HASH_TABLE_FIRST_VALID_HASH) {
|
||||
hash += HASH_TABLE_FIRST_VALID_HASH;
|
||||
}
|
||||
|
||||
u32 index = hash & mask;
|
||||
|
||||
u32 probe_increment = 1;
|
||||
u32 table_while_loop = (*table->entries)[index].hash;
|
||||
while (table_while_loop) {
|
||||
if (table->refill_removed) {
|
||||
if ((*table->entries)[index].hash == HASH_TABLE_REMOVED_HASH) {
|
||||
table->slots_filled -= 1; // 1 will get re-added below, for total increment 0.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (table->count_collisions) {
|
||||
table->add_collisions += 1;
|
||||
}
|
||||
|
||||
index = (index + probe_increment) & mask;
|
||||
probe_increment += 1;
|
||||
|
||||
table_while_loop = (*table->entries)[index].hash;
|
||||
}
|
||||
|
||||
// Walk_Table walked us to an unused entry, so add our new data into this slot:
|
||||
table->count += 1;
|
||||
table->slots_filled += 1;
|
||||
|
||||
Table_Entry<T, U>* entry = &(*table->entries)[index];
|
||||
entry->hash = hash;
|
||||
entry->key = key;
|
||||
entry->value = value;
|
||||
|
||||
return &entry->value;
|
||||
}
|
||||
|
||||
template <typename T, typename U> U* table_find_pointer (ArenaTable<T, U>* table, T key) {
|
||||
Assert(table_is_valid(table));
|
||||
if (!table_is_valid(table)) return nullptr;
|
||||
|
||||
// #Walk_Table
|
||||
u32 mask = (u32)(table->entries->allocated - 1);
|
||||
|
||||
u32 hash = table->hash_function(&key, sizeof(T));
|
||||
|
||||
if (hash < HASH_TABLE_FIRST_VALID_HASH) {
|
||||
hash += HASH_TABLE_FIRST_VALID_HASH;
|
||||
}
|
||||
|
||||
u32 index = hash & mask;
|
||||
|
||||
u32 probe_increment = 1;
|
||||
u32 table_while_loop = (*table->entries)[index].hash;
|
||||
while (table_while_loop) {
|
||||
Table_Entry<T, U>* entry = &(*table->entries)[index];
|
||||
if (entry->hash == hash) {
|
||||
if (table->compare_function(&entry->key, &key)) {
|
||||
return &entry->value;
|
||||
}
|
||||
}
|
||||
|
||||
if (table->count_collisions) { table->find_collisions += 1; }
|
||||
|
||||
index = (index + probe_increment) & mask;
|
||||
probe_increment += 1;
|
||||
|
||||
table_while_loop = (*table->entries)[index].hash;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename T, typename U> U* table_set (ArenaTable<T, U>* table, T key, U value) {
|
||||
U* value_ptr = table_find_pointer(table, key);
|
||||
if (value_ptr) {
|
||||
(*value_ptr) = value;
|
||||
return value_ptr;
|
||||
} else {
|
||||
return table_add(table, key, value);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename U> void table_resize (ArenaTable<T, U>* table, s64 slots_to_allocate) {
|
||||
s64 initial_count = table->entries->count;
|
||||
Assert(slots_to_allocate > initial_count);
|
||||
if (slots_to_allocate <= initial_count) {
|
||||
return;
|
||||
}
|
||||
|
||||
// #TODO: When you resize you need to reinsert all the values, so we
|
||||
// need a temporary copy of the original data:
|
||||
ArrayView<Table_Entry<T, U>> old_entries = array_copy(temp(), to_view(*table->entries));
|
||||
|
||||
s64 n = Next_Power_Of_Two(slots_to_allocate);
|
||||
|
||||
array_resize(*table->entries, n, false);
|
||||
|
||||
table->count = 0;
|
||||
table->slots_filled = 0;
|
||||
|
||||
// Initialize new values:
|
||||
for (s64 i = 0; i < old_entries.count; i += 1) {
|
||||
Table_Entry<T, U> entry = old_entries[i];
|
||||
// if entry is valid!
|
||||
if (entry.hash > HASH_TABLE_FIRST_VALID_HASH) {
|
||||
table_add(table, entry.key, entry.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename U> void table_reset (ArenaTable<T, U>* table, bool keep_memory=true) {
|
||||
table->count = 0;
|
||||
table->slots_filled = 0;
|
||||
|
||||
for (s64 i = 0; i < table->entries->count; i += 1) {
|
||||
(*table->entries)[i].hash = 0;
|
||||
}
|
||||
|
||||
if (!keep_memory) { array_reset(*table->entries); }
|
||||
}
|
||||
|
||||
template <typename T, typename U> void table_release (ArenaTable<T, U>* table) {
|
||||
arena_array_free(*table->entries);
|
||||
|
||||
#if BUILD_DEBUG
|
||||
poison_struct(table);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
template <typename T, typename U> bool table_contains (ArenaTable<T, U>* table, T key) {
|
||||
return (table_find_pointer(table, key) != nullptr);
|
||||
}
|
||||
|
||||
template <typename T, typename U> bool table_find (ArenaTable<T, U>* table, T key, U* value) {
|
||||
U* pointer = table_find_pointer(table, key);
|
||||
if (pointer) {
|
||||
(*value) = (*pointer);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename T, typename U> bool table_remove (ArenaTable<T, U>* table, T key, U* value) {
|
||||
Assert(table_is_valid(table));
|
||||
if (!table_is_valid(table)) return nullptr;
|
||||
|
||||
// #Walk_Table
|
||||
u32 mask = (u32)(table->entries->allocated - 1);
|
||||
|
||||
u32 hash = table->hash_function(&key, sizeof(T));
|
||||
|
||||
if (hash < HASH_TABLE_FIRST_VALID_HASH) {
|
||||
hash += HASH_TABLE_FIRST_VALID_HASH;
|
||||
}
|
||||
|
||||
u32 index = hash & mask;
|
||||
|
||||
u32 probe_increment = 1;
|
||||
u32 table_while_loop = (*table->entries)[index].hash;
|
||||
while (table_while_loop) {
|
||||
Table_Entry<T, U>* entry = &(*table->entries)[index];
|
||||
|
||||
if ((entry->hash == hash) && table->compare_function(&entry->key, &key)) {
|
||||
entry->hash = HASH_TABLE_REMOVED_HASH;
|
||||
table->count -= 1;
|
||||
(*value) = entry->value;
|
||||
return true;
|
||||
}
|
||||
|
||||
index = (index + probe_increment) & mask;
|
||||
probe_increment += 1;
|
||||
|
||||
table_while_loop = (*table->entries)[index].hash;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// #TODO: we need a for expansion iterator?
|
||||
// table_find_multiple (put results in Temp-backed Array<>, and return it as an ArrayView<T>) {
|
||||
template <typename T, typename U> ArrayView<U> table_remove (ArenaTable<T, U>* table, T key, U* value) {
|
||||
Array<U> results;
|
||||
results.allocator = temp();
|
||||
|
||||
// #Walk_Table
|
||||
u32 mask = (u32)(table->entries->allocated - 1);
|
||||
|
||||
u32 hash = table->hash_function(&key, sizeof(T));
|
||||
|
||||
if (hash < HASH_TABLE_FIRST_VALID_HASH) {
|
||||
hash += HASH_TABLE_FIRST_VALID_HASH;
|
||||
}
|
||||
|
||||
u32 index = hash & mask;
|
||||
|
||||
u32 probe_increment = 1;
|
||||
u32 table_while_loop = (*table->entries)[index].hash;
|
||||
while (table_while_loop) {
|
||||
Table_Entry<T, U>* entry = &(*table->entries)[index];
|
||||
|
||||
if (entry->hash == hash) {
|
||||
if (table->compare_function(&entry->key, &key)) {
|
||||
array_add(results, entry->value);
|
||||
} else {
|
||||
if (table->count_collisions) { table->find_collisions += 1; }
|
||||
}
|
||||
} else {
|
||||
if (table->count_collisions) { table->find_collisions += 1; }
|
||||
}
|
||||
|
||||
index = (index + probe_increment) & mask;
|
||||
probe_increment += 1;
|
||||
|
||||
table_while_loop = (*table->entries)[index].hash;
|
||||
}
|
||||
|
||||
return to_view(results);
|
||||
}
|
||||
@ -300,7 +300,7 @@ bool is_valid (ArrayView<T> src) {
|
||||
|
||||
// can also use ArrayView<T>(count, data) for initialization!
|
||||
template <typename T>
|
||||
ArrayView<T> array_view (Array<T> array) {
|
||||
ArrayView<T> to_view (Array<T> array) {
|
||||
ArrayView<T> av;
|
||||
av.count = array.count;
|
||||
av.data = array.data;
|
||||
@ -343,6 +343,12 @@ ArrayView<T> array_copy (const ArrayView<T>& src) {
|
||||
return ArrayView<T>(src.count, (T*)new_data);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
ArrayView<T> array_copy (Allocator allocator, const ArrayView<T>& src) {
|
||||
push_allocator(allocator);
|
||||
return array_copy(src);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void array_free (ArrayView<T>& src) {
|
||||
if (!src.data || src.count == 0) { return; }
|
||||
@ -380,4 +386,4 @@ ArrayView<T> array_view_from_values (ArgValues... args) {
|
||||
return array;
|
||||
}
|
||||
|
||||
MSVC_RUNTIME_CHECKS_RESTORE
|
||||
MSVC_RUNTIME_CHECKS_RESTORE
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
#include <string.h>
|
||||
|
||||
#if GP_ALLOCATOR_TRACK_ALLOCATIONS
|
||||
#include <mutex> // #TODO: Replace with Mutex (see OS_Win32.cpp)
|
||||
global General_Allocator gAllocator; // @Shared
|
||||
|
||||
70
lib/Base/Hash_Functions.h
Normal file
70
lib/Base/Hash_Functions.h
Normal file
@ -0,0 +1,70 @@
|
||||
constexpr u32 HASH_INIT = 5381;
|
||||
u32 sdbm_hash (void* data, s64 size) {
|
||||
u32 h = HASH_INIT;
|
||||
for (s64 i = 0; i < size; i += 1) {
|
||||
h = (h << 16) + (h << 6) - h + ((u8*)data)[i];
|
||||
}
|
||||
|
||||
return (u32)h;
|
||||
}
|
||||
|
||||
u64 knuth_hash (u64 x) {
|
||||
constexpr u64 KNUTH_GOLDEN_RATIO_64 = 11400714819323198485ULL;
|
||||
|
||||
return (KNUTH_GOLDEN_RATIO_64 * x);
|
||||
}
|
||||
|
||||
u32 knuth_hash_u32 (u64 x) {
|
||||
u32 h = HASH_INIT;
|
||||
|
||||
return (u32)((knuth_hash(x) ^ h) >> 32);
|
||||
}
|
||||
|
||||
constexpr u64 FNV_64_PRIME = 0x100000001b3ULL;
|
||||
constexpr u64 FNV_64_OFFSET_BIAS = 0xcbf29ce484222325ULL;
|
||||
|
||||
u64 fnv1a_hash (u64 x, u64 h = FNV_64_OFFSET_BIAS) {
|
||||
h ^= x;
|
||||
|
||||
return h * FNV_64_PRIME;
|
||||
}
|
||||
|
||||
// Good for hashing strings.
|
||||
u64 fnv1a_hash_any (void* data, s64 size, u64 h = FNV_64_OFFSET_BIAS) {
|
||||
for (s64 i = 0; i < size; i += 1) {
|
||||
h = fnv1a_hash( ((u8*)data)[i], h);
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
u32 table_hash_function_fnv1a (void* key, s64 size) {
|
||||
return (u32)fnv1a_hash_any(key, size);
|
||||
}
|
||||
|
||||
u32 table_hash_function_knuth (void* key, s64 size) {
|
||||
Assert(size == 8);
|
||||
|
||||
return knuth_hash_u32(*(u64*)key);
|
||||
}
|
||||
|
||||
u32 string_hash_function_fnv1a (void* key, s64 size) {
|
||||
Assert(size == sizeof(string));
|
||||
string key_as_string = *((string*)key);
|
||||
|
||||
return (u32)(fnv1a_hash_any(key_as_string.data, key_as_string.count));
|
||||
}
|
||||
|
||||
bool keys_match_u64 (void* key1, void* key2) {
|
||||
u64 key1_u64 = *(u64*)key1;
|
||||
u64 key2_u64 = *(u64*)key2;
|
||||
|
||||
return key1_u64 == key2_u64;
|
||||
}
|
||||
|
||||
bool string_keys_match_u64 (void* key1, void* key2) {
|
||||
string key1_s = *((string*)key1);
|
||||
string key2_s = *((string*)key2);
|
||||
|
||||
return strings_match(key1_s, key2_s);
|
||||
}
|
||||
320
lib/Base/Hash_Table.h
Normal file
320
lib/Base/Hash_Table.h
Normal file
@ -0,0 +1,320 @@
|
||||
// #NOTE: This Hash Table is borrowed from Jai's implementation! (With some tweaks)
|
||||
// I made my own version that's arena-backed, but the mechanisms are the same.
|
||||
|
||||
typedef u32 hash_result;
|
||||
|
||||
constexpr hash_result HASH_TABLE_FIRST_VALID_HASH = 2;
|
||||
constexpr hash_result HASH_TABLE_REMOVED_HASH = 1;
|
||||
|
||||
typedef hash_result (*Hash_Function)(void* key, s64 size);
|
||||
typedef bool (*Hash_Compare_Function)(void* key1, void* key2);
|
||||
|
||||
template <typename T, typename U>
|
||||
struct Table_Entry {
|
||||
using KeyType = T; using ValueType = U;
|
||||
T key;
|
||||
U value;
|
||||
hash_result hash;
|
||||
};
|
||||
|
||||
template <typename T, typename U>
|
||||
struct Table {
|
||||
using KeyType = T; using ValueType = U;
|
||||
Allocator allocator = {};
|
||||
ArrayView<Table_Entry<T, U>> entries = {};
|
||||
s64 allocated = 0;
|
||||
s64 count = 0;
|
||||
s64 slots_filled = 0;
|
||||
|
||||
Hash_Function hash_function = nullptr;
|
||||
Hash_Compare_Function compare_function = nullptr;
|
||||
|
||||
u32 load_factor_percent = 70;
|
||||
|
||||
s64 add_collisions = 0;
|
||||
s64 find_collisions = 0;
|
||||
|
||||
bool refill_removed = true;
|
||||
bool count_collisions = false;
|
||||
};
|
||||
|
||||
template <typename T, typename U> bool table_is_valid (Table<T, U>* table) {
|
||||
if (table == nullptr) return false;
|
||||
if (table->entries == nullptr) return false;
|
||||
if (table->entries->allocated == 0) return false;
|
||||
|
||||
if (table->hash_function == nullptr) return false;
|
||||
if (table->compare_function == nullptr) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T, typename U> void table_init (Table<T, U>* table, s64 slots_to_allocate=64, Arena_Reserve new_reserve=Arena_Reserve::Size_64M) {
|
||||
if (table->allocator->proc == nullptr) {
|
||||
table->allocator = context_allocator(); // #remember_allocator
|
||||
}
|
||||
push_allocator(table->allocator);
|
||||
|
||||
s64 n = Next_Power_Of_Two(slots_to_allocate);
|
||||
|
||||
table->entries = ArrayView<Table_Entry<T, U>>(n, false);
|
||||
table->allocated = n;
|
||||
|
||||
for (s64 i = 0; i < n; i += 1) {
|
||||
table->entries[i].hash = 0;
|
||||
}
|
||||
// default hash and compare functions:
|
||||
table->hash_function = table_hash_function_fnv1a;
|
||||
table->compare_function = keys_match_u64;
|
||||
}
|
||||
|
||||
// Adds given key value pair to the table, returns a pointer to the inserted value.
|
||||
template <typename T, typename U> U* table_add (Table<T, U>* table, T key, U value) {
|
||||
Assert(table_is_valid(table));
|
||||
Assert(table->load_factor_percent < 100);
|
||||
|
||||
if ( ((table->slots_filled + 1) * 100) >= (table->entries->allocated * table->load_factor_percent) ) {
|
||||
table_resize(table, Next_Power_Of_Two(table->entries->allocated + 64));
|
||||
}
|
||||
Assert(table->slots_filled < table->entries->allocated);
|
||||
|
||||
// #Walk_Table
|
||||
u32 mask = (u32)(table->entries->allocated - 1);
|
||||
|
||||
u32 hash = table->hash_function(&key, sizeof(T));
|
||||
|
||||
if (hash < HASH_TABLE_FIRST_VALID_HASH) {
|
||||
hash += HASH_TABLE_FIRST_VALID_HASH;
|
||||
}
|
||||
|
||||
u32 index = hash & mask;
|
||||
|
||||
u32 probe_increment = 1;
|
||||
u32 table_while_loop = table->entries[index].hash;
|
||||
while (table_while_loop) {
|
||||
if (table->refill_removed) {
|
||||
if (table->entries[index].hash == HASH_TABLE_REMOVED_HASH) {
|
||||
table->slots_filled -= 1; // 1 will get re-added below, for total increment 0.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (table->count_collisions) {
|
||||
table->add_collisions += 1;
|
||||
}
|
||||
|
||||
index = (index + probe_increment) & mask;
|
||||
probe_increment += 1;
|
||||
|
||||
table_while_loop = table->entries[index].hash;
|
||||
}
|
||||
|
||||
// Walk_Table walked us to an unused entry, so add our new data into this slot:
|
||||
table->count += 1;
|
||||
table->slots_filled += 1;
|
||||
|
||||
Table_Entry<T, U>* entry = &table->entries[index];
|
||||
entry->hash = hash;
|
||||
entry->key = key;
|
||||
entry->value = value;
|
||||
|
||||
return &entry->value;
|
||||
}
|
||||
|
||||
template <typename T, typename U> U* table_find_pointer (Table<T, U>* table, T key) {
|
||||
Assert(table_is_valid(table));
|
||||
if (!table_is_valid(table)) return nullptr;
|
||||
|
||||
// #Walk_Table
|
||||
u32 mask = (u32)(table->entries->allocated - 1);
|
||||
|
||||
u32 hash = table->hash_function(&key, sizeof(T));
|
||||
|
||||
if (hash < HASH_TABLE_FIRST_VALID_HASH) {
|
||||
hash += HASH_TABLE_FIRST_VALID_HASH;
|
||||
}
|
||||
|
||||
u32 index = hash & mask;
|
||||
|
||||
u32 probe_increment = 1;
|
||||
u32 table_while_loop = table->entries[index].hash;
|
||||
while (table_while_loop) {
|
||||
Table_Entry<T, U>* entry = &table->entries[index];
|
||||
if (entry->hash == hash) {
|
||||
if (table->compare_function(&entry->key, &key)) {
|
||||
return &entry.value
|
||||
}
|
||||
}
|
||||
|
||||
if (table->count_collisions) { table->find_collisions += 1; }
|
||||
|
||||
index = (index + probe_increment) & mask;
|
||||
probe_increment += 1;
|
||||
|
||||
table_while_loop = table->entries[index].hash;
|
||||
}
|
||||
|
||||
return nullptr
|
||||
}
|
||||
|
||||
template <typename T, typename U> U* table_set (Table<T, U>* table, T key, U value) {
|
||||
U* value_ptr = table_find_pointer(table, key);
|
||||
if (value_ptr) {
|
||||
(*value_ptr) = value;
|
||||
return value_ptr;
|
||||
} else {
|
||||
return table_add(table, key, value);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename U> void table_resize (Table<T, U>* table, s64 slots_to_allocate) {
|
||||
s64 initial_count = table->entries->count;
|
||||
Assert(slots_to_allocate > initial_count);
|
||||
if (slots_to_allocate <= initial_count) {
|
||||
return;
|
||||
}
|
||||
|
||||
// #TODO: When you resize you need to reinsert all the values, so we
|
||||
// need a temporary copy of the original data:
|
||||
ArrayView<Table_Entry<T, U>> old_entries = table->entries;
|
||||
|
||||
s64 n = Next_Power_Of_Two(slots_to_allocate);
|
||||
|
||||
table->entries = ArrayView<Table_Entry<T, U>>(n, false);
|
||||
table->allocated = n;
|
||||
|
||||
table->count = 0;
|
||||
table->slots_filled = 0;
|
||||
|
||||
// Initialize new values:
|
||||
for (s64 i = 0; i < old_entries.count; i += 1) {
|
||||
Table_Entry<T, U> entry = old_entries[i];
|
||||
// if entry is valid!
|
||||
if (entry.hash > HASH_TABLE_FIRST_VALID_HASH) {
|
||||
table_add(table, entry.key, entry.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename U> void table_reset (Table<T, U>* table, bool keep_memory=true) {
|
||||
table->count = 0;
|
||||
table->slots_filled = 0;
|
||||
|
||||
for (s64 i = 0; i < table->entries->count; i += 1) {
|
||||
table->entries[i].hash = 0;
|
||||
}
|
||||
|
||||
if (!keep_memory) { array_reset(table->entries); }
|
||||
}
|
||||
|
||||
template <typename T, typename U> void table_release (Table<T, U>* table) {
|
||||
arena_array_free(table->entries);
|
||||
|
||||
#if BUILD_DEBUG
|
||||
poison_struct(table);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
template <typename T, typename U> bool table_contains (Table<T, U>* table, T key) {
|
||||
return (table_find_pointer(table, key) != nullptr);
|
||||
}
|
||||
|
||||
template <typename T, typename U> bool table_find (Table<T, U>* table, T key, U* value) {
|
||||
U* pointer = table_find_pointer(table, key);
|
||||
if (pointer) {
|
||||
(*value) = (*pointer);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename T, typename U> bool table_remove (Table<T, U>* table, T key, U* value) {
|
||||
Assert(table_is_valid(table));
|
||||
if (!table_is_valid(table)) return nullptr;
|
||||
|
||||
// #Walk_Table
|
||||
u32 mask = (u32)(table->entries->allocated - 1);
|
||||
|
||||
u32 hash = table->hash_function(&key, sizeof(T));
|
||||
|
||||
if (hash < HASH_TABLE_FIRST_VALID_HASH) {
|
||||
hash += HASH_TABLE_FIRST_VALID_HASH;
|
||||
}
|
||||
|
||||
u32 index = hash & mask;
|
||||
|
||||
u32 probe_increment = 1;
|
||||
u32 table_while_loop = table->entries[index].hash;
|
||||
while (table_while_loop) {
|
||||
Table_Entry<T, U>* entry = &table->entries[index];
|
||||
|
||||
if ((entry->hash == hash) && table->compare_function(&entry->key, &key)) {
|
||||
entry->hash = HASH_TABLE_REMOVED_HASH;
|
||||
table->count -= 1;
|
||||
(*value) = entry->value;
|
||||
return true;
|
||||
}
|
||||
|
||||
index = (index + probe_increment) & mask;
|
||||
probe_increment += 1;
|
||||
|
||||
table_while_loop = table->entries[index].hash;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// #TODO: we need a for expansion iterator?
|
||||
// table_find_multiple (put results in Temp-backed Array<>, and return it as an ArrayView<T>) {
|
||||
template <typename T, typename U> ArrayView<U> table_remove (Table<T, U>* table, T key, U* value) {
|
||||
Array<U> results;
|
||||
results.allocator = temp();
|
||||
|
||||
// #Walk_Table
|
||||
u32 mask = (u32)(table->entries->allocated - 1);
|
||||
|
||||
u32 hash = table->hash_function(&key, sizeof(T));
|
||||
|
||||
if (hash < HASH_TABLE_FIRST_VALID_HASH) {
|
||||
hash += HASH_TABLE_FIRST_VALID_HASH;
|
||||
}
|
||||
|
||||
u32 index = hash & mask;
|
||||
|
||||
u32 probe_increment = 1;
|
||||
u32 table_while_loop = table->entries[index].hash;
|
||||
while (table_while_loop) {
|
||||
Table_Entry<T, U>* entry = &table->entries[index];
|
||||
|
||||
if (entry->hash == hash) {
|
||||
if (table->compare_function(&entry->key, &key)) {
|
||||
array_add(results, entry->value);
|
||||
} else {
|
||||
if (table->count_collisions) { table->find_collisions += 1; }
|
||||
}
|
||||
} else {
|
||||
if (table->count_collisions) { table->find_collisions += 1; }
|
||||
}
|
||||
|
||||
index = (index + probe_increment) & mask;
|
||||
probe_increment += 1;
|
||||
|
||||
table_while_loop = table->entries[index].hash;
|
||||
}
|
||||
|
||||
return to_view(results);
|
||||
}
|
||||
|
||||
// #TODO:
|
||||
// find_or_add is kind of like table_set, but used when you
|
||||
// just want a pointer to the value, which you can fill in.
|
||||
// find_or_add :: (table: *Table, key: table.Key_Type) -> (entry: *table.Value_Type, newly_added: bool) {
|
||||
// value := table_find_pointer(table, key);
|
||||
// if value return value, false;
|
||||
|
||||
// new_value: table.Value_Type;
|
||||
// value = table_add(table, key, new_value);
|
||||
// return value, true;
|
||||
// }
|
||||
74
lib/Base/run_tests.cpp
Normal file
74
lib/Base/run_tests.cpp
Normal file
@ -0,0 +1,74 @@
|
||||
void run_pre_setup_tests() {
|
||||
printf("Running pre-setup tests...\n");
|
||||
printf("\nFinished running pre-setup tests...\n");
|
||||
}
|
||||
|
||||
string string_literal_example = "Hello, I am a string literal.";
|
||||
void run_post_setup_tests() {
|
||||
printf("Running post-setup tests...\n");
|
||||
// See: main_thread_base_entry_point
|
||||
{ Timed_Block_Print("string_builder_testing");
|
||||
temp_reset();
|
||||
push_allocator(temp());
|
||||
// tip: use auto_reset or auto_release with `thread_context()->arena`
|
||||
|
||||
// String builder example:
|
||||
// OK. I can work with this.
|
||||
|
||||
auto sb = new_string_builder(Arena_Reserve::Size_64K);
|
||||
append(sb, "string_literal_example");
|
||||
append(sb, " ");
|
||||
print_to_builder(sb, "There are %d cats in the %s", 64, "house.\n");
|
||||
append(sb, " > ");
|
||||
print_to_builder(sb, "some size_t: %u", (u64)3982739867);
|
||||
append(sb, "\n");
|
||||
print_to_builder(sb, "the literal: %s", string_literal_example.data);
|
||||
|
||||
// string result = string_view(sb);
|
||||
string result = builder_to_string(sb); // also frees
|
||||
// print((char*)result.data);
|
||||
log("Hello.");
|
||||
log("log() should automatically append newlines to these prints.");
|
||||
log("If it doesn't, I will be very upset.");
|
||||
log(result);
|
||||
log("Hello. There are %s things here\n", string_literal_example.data);
|
||||
|
||||
print("Hello, I am just a printed message to stdout\n\n");
|
||||
|
||||
|
||||
// Testing file writes and reads:
|
||||
print("Writing some nonsense to a file.\n");
|
||||
bool success = write_entire_file("D:/TempLocal/junk.txt", to_view(result));
|
||||
log("Done. Success: %d\n", success);
|
||||
|
||||
// push_allocator(allocator(thread_context()->arena));
|
||||
push_allocator(GPAllocator());
|
||||
string file_path = "D:/Work/OpenBCI/ToolZ/prototyping-gui-main/modules/native-proto-lib/native-sdk-prototyping/src/SignalProcessing.cpp";
|
||||
ArrayView<u8> file_data = read_entire_file(file_path, true);
|
||||
log("file_data: \n");
|
||||
log("%s\n", file_data.data);
|
||||
}
|
||||
// ImGui_Application();
|
||||
{ // Test hashing:
|
||||
u64 start = 0x512585;
|
||||
u32 sdbm = sdbm_hash(&start, sizeof(u64));
|
||||
u64 kh = knuth_hash(start);
|
||||
u64 fnv = fnv1a_hash(start);
|
||||
|
||||
string some_data = "Hello there, my name is Musa";
|
||||
u64 result = fnv1a_hash_any(some_data.data, some_data.count);
|
||||
}
|
||||
|
||||
{
|
||||
ArenaTable<u64, u64> table;
|
||||
table_init(&table);
|
||||
table_resize(&table, 2048);
|
||||
table_add(&table, (u64)52975125, (u64)5);
|
||||
table_set(&table, (u64)52975125, (u64)99);
|
||||
|
||||
auto ptr = table_find_pointer(&table, (u64)52975125);
|
||||
printf("ptr.* = %llu\n", *ptr);
|
||||
}
|
||||
|
||||
printf("\nFinished running post-setup tests...\n");
|
||||
}
|
||||
@ -11,11 +11,12 @@
|
||||
#include "lib/Base/Allocator.h"
|
||||
#include "lib/Base/Array.h"
|
||||
#include "lib/Base/General_Purpose_Allocator.h"
|
||||
|
||||
|
||||
#include "lib/Base/Arena.h"
|
||||
#include "lib/Base/Arena_Array.h"
|
||||
#include "lib/Base/String.h"
|
||||
#include "lib/Base/Hash_Functions.h"
|
||||
#include "lib/Base/Hash_Table.h"
|
||||
#include "lib/Base/Arena_Hash_Table.h"
|
||||
#if OS_WINDOWS
|
||||
# include "lib/OS/OS_Win32.h"
|
||||
#endif
|
||||
@ -48,8 +49,6 @@
|
||||
|
||||
#include "lib/Base/Thread_Group.cpp"
|
||||
|
||||
// #include "imgui-docking.cpp"
|
||||
|
||||
// #if OS_LINUX..
|
||||
// #include "src/OS_Linux.cpp"
|
||||
// #if OS_MACOS..
|
||||
|
||||
@ -181,62 +181,27 @@ void ImGui_Application () {
|
||||
::UnregisterClassW(wc.lpszClassName, wc.hInstance);
|
||||
}*/
|
||||
|
||||
string string_literal_example = "Hello, I am a string literal.";
|
||||
|
||||
internal void Main_Entry_Point (int argc, WCHAR **argv) {
|
||||
set_cpu_base_frequency(3200); // REQUIRED FOR TIMING MODULE!
|
||||
set_cpu_base_frequency(3200); // REQUIRED FOR TIMING MODULE! will depend on CPU
|
||||
|
||||
#if BASE_RUN_TESTS
|
||||
run_pre_setup_tests(); // #no_context
|
||||
#endif
|
||||
// #NOTE: Be careful using a timing or auto-release macros
|
||||
// before setting up the thread context!
|
||||
Bootstrap_Main_Thread_Context();
|
||||
#if OS_WINDOWS
|
||||
Win32_Entry_Point(argc, argv); // This might be the problem.
|
||||
Win32_Entry_Point(argc, argv);
|
||||
#endif
|
||||
#if OS_LINUX
|
||||
// Linux_Entry_Point(argc, argv);
|
||||
#endif
|
||||
// See: main_thread_base_entry_point
|
||||
{ Timed_Block_Print("string_builder_testing");
|
||||
temp_reset();
|
||||
push_allocator(temp());
|
||||
// tip: use auto_reset or auto_release with `thread_context()->arena`
|
||||
|
||||
// String builder example:
|
||||
// OK. I can work with this.
|
||||
|
||||
auto sb = new_string_builder(Arena_Reserve::Size_64K);
|
||||
append(sb, "string_literal_example");
|
||||
append(sb, " ");
|
||||
print_to_builder(sb, "There are %d cats in the %s", 64, "house.\n");
|
||||
append(sb, " > ");
|
||||
print_to_builder(sb, "some size_t: %u", (u64)3982739867);
|
||||
append(sb, "\n");
|
||||
print_to_builder(sb, "the literal: %s", string_literal_example.data);
|
||||
|
||||
// string result = string_view(sb);
|
||||
string result = builder_to_string(sb); // also frees
|
||||
// print((char*)result.data);
|
||||
log("Hello.");
|
||||
log("log() should automatically append newlines to these prints.");
|
||||
log("If it doesn't, I will be very upset.");
|
||||
log(result);
|
||||
log("Hello. There are %s things here\n", string_literal_example.data);
|
||||
|
||||
print("Hello, I am just a printed message to stdout\n\n");
|
||||
|
||||
|
||||
// Testing file writes and reads:
|
||||
print("Writing some nonsense to a file.\n");
|
||||
bool success = write_entire_file("D:/TempLocal/junk.txt", to_view(result));
|
||||
log("Done. Success: %d\n", success);
|
||||
|
||||
// push_allocator(allocator(thread_context()->arena));
|
||||
push_allocator(GPAllocator());
|
||||
string file_path = "D:/Work/OpenBCI/ToolZ/prototyping-gui-main/modules/native-proto-lib/native-sdk-prototyping/src/SignalProcessing.cpp";
|
||||
ArrayView<u8> file_data = read_entire_file(file_path, true);
|
||||
log("file_data: \n");
|
||||
log("%s\n", file_data.data);
|
||||
}
|
||||
// ImGui_Application();
|
||||
#if BASE_RUN_TESTS
|
||||
run_post_setup_tests();
|
||||
#endif
|
||||
|
||||
// 2. Setup Window
|
||||
// 3. Initialize Graphics Backend (DX11 or OpenGL3)
|
||||
|
||||
// #TODO: #Main - `Main_Entry_Point`
|
||||
// [ ] Setup Mouse and Keyboard Inputs
|
||||
|
||||
Loading…
Reference in New Issue
Block a user