diff --git a/build.c b/build.c index 30ad9e9..10ad5e2 100644 --- a/build.c +++ b/build.c @@ -3,7 +3,7 @@ /// // Build config stuff -#define RUN_TESTS 0 +#define RUN_TESTS 1 // This is only for people developing oogabooga! #define OOGABOOGA_DEV 1 @@ -11,7 +11,7 @@ #define ENABLE_PROFILING 0 // ENABLE_SIMD Requires CPU to support at least SSE1 but I will be very surprised if you find a system today which doesn't -#define ENABLE_SIMD 1 +#define ENABLE_SIMD 0 #define INITIAL_PROGRAM_MEMORY_SIZE MB(5) @@ -40,10 +40,10 @@ typedef struct Context_Extra { // #include "oogabooga/examples/minimal_game_loop.c" // An engine dev stress test for rendering -// #include "oogabooga/examples/renderer_stress_test.c" +#include "oogabooga/examples/renderer_stress_test.c" // Randy's example game that he's building out as a tutorial for using the engine -#include "entry_randygame.c" +// #include "entry_randygame.c" // This is where you swap in your own project! // #include "entry_yourepicgamename.c" diff --git a/oogabooga/base.c b/oogabooga/base.c index 712c1a8..294a5d0 100644 --- a/oogabooga/base.c +++ b/oogabooga/base.c @@ -118,3 +118,19 @@ void pop_context() { } + +u64 get_next_power_of_two(u64 x) { + if (x == 0) { + return 1; + } + + x--; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + x |= x >> 32; + + return x + 1; +} \ No newline at end of file diff --git a/oogabooga/examples/renderer_stress_test.c b/oogabooga/examples/renderer_stress_test.c index f98e217..25aaa01 100644 --- a/oogabooga/examples/renderer_stress_test.c +++ b/oogabooga/examples/renderer_stress_test.c @@ -76,7 +76,7 @@ int entry(int argc, char **argv) { draw_frame.view = camera_view; seed_for_random = 69; - for (u64 i = 0; i < 100000; i++) { + for (u64 i = 0; i < 10000; i++) { float32 aspect = (float32)window.width/(float32)window.height; float min_x = -aspect; float max_x = aspect; @@ -102,11 +102,13 @@ int entry(int argc, char **argv) { draw_image(bush_image, v2(0.65, 0.65), v2(0.2*sin(now), 0.2*sin(now)), COLOR_WHITE); + //draw_text(font, "I am text", v2(0.1, 0.6), COLOR_BLACK); + //draw_text(font, "I am text", v2(0.09, 0.61), COLOR_WHITE); + tm_scope_cycles("gfx_update") { gfx_update(); } - if (is_key_just_released('E')) { log("FPS: %.2f", 1.0 / delta); log("ms: %.2f", delta*1000.0); diff --git a/oogabooga/hash.c b/oogabooga/hash.c new file mode 100644 index 0000000..bbd06eb --- /dev/null +++ b/oogabooga/hash.c @@ -0,0 +1,84 @@ + + +#define PRIME64_1 11400714785074694791ULL +#define PRIME64_2 14029467366897019727ULL +#define PRIME64_3 1609587929392839161ULL +#define PRIME64_4 9650029242287828579ULL +#define PRIME64_5 2870177450012600261ULL + +static inline u64 xx_hash(u64 x) { + u64 h64 = PRIME64_5 + 8; + h64 += x * PRIME64_3; + h64 = ((h64 << 23) | (h64 >> (64 - 23))) * PRIME64_2 + PRIME64_4; + h64 ^= h64 >> 33; + h64 *= PRIME64_2; + h64 ^= h64 >> 29; + h64 *= PRIME64_3; + h64 ^= h64 >> 32; + return h64; +} + +static inline u64 city_hash(string s) { + const u64 k = 0x9ddfea08eb382d69ULL; + u64 a = s.count; + u64 b = s.count * 5; + u64 c = 9; + u64 d = b; + + if (s.count <= 16) { + memcpy(&a, s.data, sizeof(u64)); + memcpy(&b, s.data + s.count - 8, sizeof(u64)); + } else { + memcpy(&a, s.data, sizeof(u64)); + memcpy(&b, s.data + 8, sizeof(u64)); + memcpy(&c, s.data + s.count - 8, sizeof(u64)); + memcpy(&d, s.data + s.count - 16, sizeof(u64)); + } + + a += b; + a = (a << 43) | (a >> (64 - 43)); + a += c; + a = a * 5 + 0x52dce729; + d ^= a; + d = (d << 44) | (d >> (64 - 44)); + d += b; + + return d * k; +} + +u64 djb2_hash(string s) { + u64 hash = 5381; + for (u64 i = 0; i < s.count; i++) { + hash = ((hash << 5) + hash) + s.data[i]; + } + return hash; +} + +u64 string_get_hash(string s) { + if (s.count > 32) return djb2_hash(s); + return city_hash(s); +} +u64 pointer_get_hash(void *p) { + return xx_hash((u64)p); +} +u64 float64_get_hash(float64 x) { + return xx_hash(*(u64*)&x); +} +u64 float32_get_hash(float32 x) { + return float64_get_hash((float64)x); +} + +#define get_hash(x) _Generic((x), \ + string: string_get_hash, \ + s8: xx_hash, \ + u8: xx_hash, \ + s16: xx_hash, \ + u16: xx_hash, \ + s32: xx_hash, \ + u32: xx_hash, \ + s64: xx_hash, \ + u64: xx_hash, \ + f32: float32_get_hash, \ + f64: float64_get_hash, \ + default: pointer_get_hash \ + )(x) \ No newline at end of file diff --git a/oogabooga/hash_table.c b/oogabooga/hash_table.c new file mode 100644 index 0000000..004ffd9 --- /dev/null +++ b/oogabooga/hash_table.c @@ -0,0 +1,208 @@ + +// Very naive implementation for now but it should be cache efficient so not really a +// problem until very large (in which case you should probably write your own data structure +// anyways) + +/* + + Example Usage: + + + // Make a table with key type 'string' and value type 'int', allocated on the heap + Hash_Table table = make_hash_table(string, int, get_heap_allocator()); + + // Set key "Key string" to integer value 69. This returns whether or not key was newly added. + string key = STR("Key string"); + bool newly_added = hash_table_set(&table, key, 69); + + // Find value associated with given key. Returns pointer to that value. + string other_key = STR("Some other key"); + int* value = hash_table_find(&table, other_key); + + if (value) { + // Pointer is OK, item with key exists + } else { + // Pointer is null, item with key does NOT exist + } + + // Same as hash_table_find() != NULL + string another_key = STR("Another key"); + if (hash_table_contains(&table, another_key)) { + + } + + // Reset all entries (but keep allocated memory) + hash_table_reset(&table); + + // Free allocated entries in hash table + hash_table_destroy(&table); + + + Limitations: + - Key can only be a base type or pointer + - Key and value passed to the following function needs to be lvalues (we need to be able to take their addresses with '&'): + - hash_table_add + - hash_table_find + - hash_table_contains + - hash_table_set + + Example: + + hash_table_set(&table, my_key+5, my_value+3); // ERROR + + int key = my_key+5; + int value = my_value+3; + hash_table_set(&table, key, value); // OK + + +*/ + +typedef struct Hash_Table Hash_Table; + +// API: +#define make_hash_table_reserve(Key_Type, Value_Type, capacity_count, allocator) \ + make_hash_table_reserve(sizeof(Key_Type), sizeof(Value_Type), capacity_count, allocator) + +#define make_hash_table(Key_Type, Value_Type, allocator) \ + make_hash_table_raw(sizeof(Key_Type), sizeof(Value_Type), allocator) + +#define hash_table_add(table_ptr, key, value) \ + hash_table_add_raw((table_ptr), get_hash(key), &(key), &(value), sizeof(key), sizeof(value)) + +#define hash_table_find(table_ptr, key) \ + hash_table_find_raw((table_ptr), get_hash(key)) + +#define hash_table_contains(table_ptr, key) \ + hash_table_contains_raw((table_ptr), get_hash(key)) + +#define hash_table_set(table_ptr, key, value) \ + hash_table_set_raw((table_ptr), get_hash(key), &key, &value, sizeof(key), sizeof(value)) + +void hash_table_reserve(Hash_Table *t, u64 required_count); + + +typedef struct Hash_Table { + + // Each entry is hash-key-value + // Hash is sizeof(u64) bytes, key is _key_size bytes and value is _value_size bytes + void *entries; + + u64 count; // Number of valid entries + u64 capacity_count; // Number of allocated entries + + u64 _key_size; + u64 _value_size; + + Allocator allocator; +} Hash_Table; + +Hash_Table make_hash_table_reserve_raw(u64 key_size, u64 value_size, u64 capacity_count, Allocator allocator) { + + capacity_count = min(capacity_count, 8); + + Hash_Table t = ZERO(Hash_Table); + + t._key_size = key_size; + t._value_size = value_size; + t.allocator = allocator; + + u64 entry_size = value_size+sizeof(u64); + t.entries = alloc(t.allocator, entry_size*capacity_count); + memset(t.entries, 0, entry_size*capacity_count); + t.capacity_count = capacity_count; + + return t; +} +inline Hash_Table make_hash_table_raw(u64 key_size, u64 value_size, Allocator allocator) { + return make_hash_table_reserve_raw(key_size, value_size, 128, allocator); +} + +void hash_table_reset(Hash_Table *t) { + t->count = 0; +} +void hash_table_destroy(Hash_Table *t) { + dealloc(t->allocator, t->entries); + + t->entries = 0; + t->count = 0; + t->capacity_count = 0; +} + +void hash_table_reserve(Hash_Table *t, u64 required_count) { + u64 entry_size = t->_value_size+sizeof(u64); + + u64 required_size = required_count*entry_size; + + u64 current_size = t->capacity_count*entry_size; + + if (current_size >= required_size) return; + + u64 new_count = get_next_power_of_two(required_count); + u64 new_size = new_count*entry_size; + + void *new_entries = alloc(t->allocator, new_size); + memcpy(new_entries, t->entries, current_size); + + dealloc(t->allocator, t->entries); + + t->entries = new_entries; + t->capacity_count = new_count; +} + +// This can add multiple entries of same hash, beware! +void hash_table_add_raw(Hash_Table *t, u64 hash, void *k, void *v, u64 key_size, u64 value_size) { + + assert(t->_key_size == key_size, "Key type size does not match hash table initted key type size"); + assert(t->_value_size == value_size, "Value type size does not match hash table initted value type size"); + + hash_table_reserve(t, t->count+1); + + u64 entry_size = t->_value_size+sizeof(u64); + + u64 index = entry_size*t->count; + t->count += 1; + + u64 hash_offset = 0; + u64 value_offset = hash_offset + sizeof(u64); + + memcpy((u8*)t->entries+index+hash_offset, &hash, sizeof(u64)); + memcpy((u8*)t->entries+index+value_offset, v, value_size); +} + +void *hash_table_find_raw(Hash_Table *t, u64 hash) { + + // #Speed #Incomplete + // Do quadratic probe 'triangular numbers' + + u64 entry_size = t->_value_size+sizeof(u64); + u64 hash_offset = 0; + u64 value_offset = hash_offset + sizeof(u64); + + for (u64 i = 0; i < t->count; i += 1) { + u64 existing_hash = *(u64*)((u8*)t->entries+i*entry_size+hash_offset); + if (existing_hash == hash) { + void *value = ((u8*)t->entries+i*entry_size+value_offset); + return value; + } + } + return 0; +} + +bool hash_table_contains_raw(Hash_Table *t, u64 hash) { + return hash_table_find_raw(t, hash) != 0; +} + +// Returns true if key was newly added or false if it already existed +bool hash_table_set_raw(Hash_Table *t, u64 hash, void *k, void *v, u64 key_size, u64 value_size) { + bool newly_added = true; + + if (hash_table_contains_raw(t, hash)) newly_added = false; + + if (newly_added) { + hash_table_add_raw(t, hash, k, v, key_size, value_size); + } else { + memcpy(hash_table_find_raw(t, hash), v, value_size); + } + + return newly_added; +} \ No newline at end of file diff --git a/oogabooga/memory.c b/oogabooga/memory.c index 34c6037..78bd45f 100644 --- a/oogabooga/memory.c +++ b/oogabooga/memory.c @@ -197,7 +197,7 @@ Heap_Block *make_heap_block(Heap_Block *parent, u64 size) { // #Speed #Cleanup if (((u8*)block)+size >= ((u8*)program_memory)+program_memory_size) { u64 minimum_size = ((u8*)block+size) - (u8*)program_memory + 1; - u64 new_program_size = max((cast(u64)(minimum_size*2)), program_memory_size*2); + u64 new_program_size = get_next_power_of_two(minimum_size); assert(new_program_size >= minimum_size, "Bröd"); const u64 ATTEMPTS = 1000; for (u64 i = 0; i <= ATTEMPTS; i++) { diff --git a/oogabooga/oogabooga.c b/oogabooga/oogabooga.c index aa52de1..a54ac3f 100644 --- a/oogabooga/oogabooga.c +++ b/oogabooga/oogabooga.c @@ -253,9 +253,12 @@ typedef u8 bool; #include "string.c" #include "unicode.c" #include "string_format.c" +#include "hash.c" #include "path_utils.c" #include "linmath.c" +#include "hash_table.c" + #include "os_interface.c" #include "gfx_interface.c" @@ -291,6 +294,8 @@ typedef u8 bool; #include "tests.c" +#define malloc please_use_alloc_for_memory_allocations_instead_of_malloc +#define free please_use_dealloc_for_memory_deallocations_instead_of_free void oogabooga_init(u64 program_memory_size) { context.logger = default_logger; diff --git a/oogabooga/tests.c b/oogabooga/tests.c index 31a7b0d..9d4ad4e 100644 --- a/oogabooga/tests.c +++ b/oogabooga/tests.c @@ -36,6 +36,8 @@ void log_heap() { void test_allocator(bool do_log_heap) { + u64 h = get_hash((string*)69); + Allocator heap = get_heap_allocator(); // Basic allocation and free @@ -1018,6 +1020,51 @@ void test_linmath() { assert(floats_roughly_match(v3_dot, 38), "Failed: v3_dot_product"); assert(floats_roughly_match(v4_dot, 30), "Failed: v4_dot_product"); } +void test_hash_table() { + // Initialize a hash table with key type 'string' and value type 'int' + Hash_Table table = make_hash_table(string, int, get_heap_allocator()); + + // Test hash_table_set for adding new key-value pairs + string key1 = STR("Key string"); + int value1 = 69; + bool newly_added = hash_table_set(&table, key1, value1); + assert(newly_added == true, "Failed: Key should be newly added"); + + // Test hash_table_find for existing key + int* found_value = hash_table_find(&table, key1); + assert(found_value != NULL, "Failed: Key should exist in hash table"); + assert(*found_value == 69, "Failed: Value should be 69, got %i", *found_value); + + // Test hash_table_set for updating existing key + int new_value1 = 70; + newly_added = hash_table_set(&table, key1, new_value1); + assert(newly_added == false, "Failed: Key should not be newly added"); + + // Test hash_table_find for updated value + found_value = hash_table_find(&table, key1); + assert(found_value != NULL, "Failed: Key should exist in hash table"); + assert(*found_value == 70, "Failed: Value should be 70, got %i", *found_value); + + // Test hash_table_contains for existing key + bool contains = hash_table_contains(&table, key1); + assert(contains == true, "Failed: Hash table should contain key1"); + + // Test hash_table_contains for non-existing key + string key2 = STR("Non-existing key"); + contains = hash_table_contains(&table, key2); + assert(contains == false, "Failed: Hash table should not contain key2"); + + // Test hash_table_reset + hash_table_reset(&table); + found_value = hash_table_find(&table, key1); + assert(found_value == NULL, "Failed: Hash table should be empty after reset"); + + // Test hash_table_destroy + hash_table_destroy(&table); + assert(table.entries == NULL, "Failed: Hash table entries should be NULL after destroy"); + assert(table.count == 0, "Failed: Hash table count should be 0 after destroy"); + assert(table.capacity_count == 0, "Failed: Hash table capacity count should be 0 after destroy"); +} void oogabooga_run_tests() { @@ -1033,17 +1080,21 @@ void oogabooga_run_tests() { test_strings(); print("OK!\n"); - - //print("Thread bombing allocator... "); - //Thread* threads[100]; - //for (int i = 0; i < 100; i++) { - // threads[i] = os_make_thread(test_allocator_threaded, get_heap_allocator()); - // os_start_thread(threads[i]); - //} - //for (int i = 0; i < 100; i++) { - // os_join_thread(threads[i]); - //} - //print("OK!\n"); + + // #Temporary + // This makes entire os freeze in release lol +#if CONFIGURATION != RELEASE + print("Thread bombing allocator... "); + Thread* threads[100]; + for (int i = 0; i < 100; i++) { + threads[i] = os_make_thread(test_allocator_threaded, get_heap_allocator()); + os_start_thread(threads[i]); + } + for (int i = 0; i < 100; i++) { + os_join_thread(threads[i]); + } + print("OK!\n"); +#endif print("Testing file IO... "); test_file_io(); @@ -1057,4 +1108,8 @@ void oogabooga_run_tests() { test_simd(); print("OK!\n"); + print("Testing hash table... "); + test_hash_table(); + print("OK!\n"); + } \ No newline at end of file