From a5a4485e29492188d31edcfe56f84ba280d6b610 Mon Sep 17 00:00:00 2001 From: doyle Date: Wed, 16 Aug 2023 21:59:38 +1000 Subject: [PATCH] dqn: Reorganize the library --- Misc/dqn_unit_tests.cpp | 500 ++++---- Misc/dqn_utest.h | 4 +- b_stacktrace.h | 411 +++++++ dqn.h | 343 +++++- dqn_core.cpp => dqn_base.cpp | 8 +- dqn_core.h => dqn_base.h | 121 +- dqn_containers.h | 50 +- dqn_debug.cpp | 320 +++++ dqn_debug.h | 127 ++ dqn_external.cpp | 1697 ++++++++++++++++++++++++++ dqn_external.h | 596 +++++++++ dqn_hash.h | 7 - dqn_helpers.cpp | 989 +++++++++++++++ dqn_helpers.h | 655 ++++++++++ dqn_math.cpp | 211 +++- dqn_math.h | 139 ++- dqn_memory.cpp | 519 +++----- dqn_memory.h | 205 ++-- dqn_misc.cpp | 587 --------- dqn_misc.h | 386 ------ dqn_platform.cpp | 837 ++++++------- dqn_platform.h | 315 +++-- dqn_print.cpp | 341 ------ dqn_print.h | 138 --- dqn_strings.cpp | 2215 +--------------------------------- dqn_strings.h | 540 +-------- dqn_tests_helpers.cpp | 100 ++ 27 files changed, 6735 insertions(+), 5626 deletions(-) create mode 100644 b_stacktrace.h rename dqn_core.cpp => dqn_base.cpp (92%) rename dqn_core.h => dqn_base.h (77%) create mode 100644 dqn_debug.cpp create mode 100644 dqn_debug.h create mode 100644 dqn_external.cpp create mode 100644 dqn_external.h create mode 100644 dqn_helpers.cpp create mode 100644 dqn_helpers.h delete mode 100644 dqn_misc.cpp delete mode 100644 dqn_misc.h delete mode 100644 dqn_print.cpp delete mode 100644 dqn_print.h create mode 100644 dqn_tests_helpers.cpp diff --git a/Misc/dqn_unit_tests.cpp b/Misc/dqn_unit_tests.cpp index b95e90f..75a5d06 100644 --- a/Misc/dqn_unit_tests.cpp +++ b/Misc/dqn_unit_tests.cpp @@ -32,189 +32,134 @@ #define DQN_UTEST_IMPLEMENTATION #include "dqn_utest.h" -enum Guard { - Guard_None, - Guard_UseAfterFree, - Guard_Count, -}; - -static Dqn_String8 ArenaGuardTestSuffix(uint32_t guard) -{ - Dqn_String8 result = {}; - switch (guard) { - case Guard_None: result = DQN_STRING8(" "); break; - case Guard_UseAfterFree: result = DQN_STRING8(" [UAF]"); break; - } - return result; -} - Dqn_UTest TestArena() { Dqn_UTest test = {}; DQN_UTEST_GROUP(test, "Dqn_Arena") { - for (Dqn_usize guard = 0; guard < Guard_Count; guard++) { - Dqn_String8 test_suffix = ArenaGuardTestSuffix(guard); - DQN_UTEST_TEST("Reused memory is zeroed out%.*s", DQN_STRING_FMT(test_suffix)) { - Dqn_Arena arena = {}; - arena.use_after_free_guard = guard == Guard_UseAfterFree; - - // NOTE: Allocate 128 kilobytes, fill it with garbage, then reset the arena - Dqn_usize size = DQN_KILOBYTES(128); - uintptr_t first_ptr_address = 0; - { - Dqn_ArenaTempMemory temp_mem = Dqn_Arena_BeginTempMemory(&arena); - void *ptr = Dqn_Arena_Allocate(&arena, size, 1, Dqn_ZeroMem_Yes); - first_ptr_address = DQN_CAST(uintptr_t)ptr; - DQN_MEMSET(ptr, 'z', size); - Dqn_Arena_EndTempMemory(temp_mem); - } - - // NOTE: Reallocate 128 kilobytes - char *ptr = DQN_CAST(char *)Dqn_Arena_Allocate(&arena, size, 1, Dqn_ZeroMem_Yes); - - // NOTE: Double check we got the same pointer - DQN_UTEST_ASSERT(&test, first_ptr_address == DQN_CAST(uintptr_t)ptr); - - // NOTE: Check that the bytes are set to 0 - for (Dqn_usize i = 0; i < size; i++) - DQN_UTEST_ASSERT(&test, ptr[i] == 0); - Dqn_Arena_Free(&arena, Dqn_ZeroMem_No); - } + DQN_UTEST_TEST("Grow arena, reserve 4k, commit 1k (e.g. different sizes)") { + Dqn_Arena arena = {}; + Dqn_Arena_Grow(&arena, DQN_KILOBYTES(4), DQN_KILOBYTES(1), /*flags*/ 0); + Dqn_Arena_Free(&arena); } - for (Dqn_usize guard = 0; guard < Guard_Count; guard++) { - Dqn_String8 test_suffix = ArenaGuardTestSuffix(guard); - DQN_UTEST_TEST("Test arena grows naturally, 1mb + 4mb%.*s", DQN_STRING_FMT(test_suffix)) { - Dqn_Arena arena = {}; - arena.use_after_free_guard = guard == Guard_UseAfterFree; + DQN_UTEST_TEST("Reused memory is zeroed out") { - // NOTE: Allocate 1mb, then 4mb, this should force the arena to grow - char *ptr_1mb = DQN_CAST(char *)Dqn_Arena_Allocate(&arena, DQN_MEGABYTES(1), 1 /*align*/, Dqn_ZeroMem_Yes); - char *ptr_4mb = DQN_CAST(char *)Dqn_Arena_Allocate(&arena, DQN_MEGABYTES(4), 1 /*align*/, Dqn_ZeroMem_Yes); - DQN_UTEST_ASSERT(&test, ptr_1mb); + Dqn_usize size = DQN_KILOBYTES(128); + Dqn_Arena arena = {}; + Dqn_Arena_Grow(&arena, size, /*commit*/ size, /*flags*/ 0); + + // NOTE: Allocate 128 kilobytes, fill it with garbage, then reset the arena + uintptr_t first_ptr_address = 0; + { + Dqn_ArenaTempMemory temp_mem = Dqn_Arena_BeginTempMemory(&arena); + void *ptr = Dqn_Arena_Alloc(&arena, size, 1, Dqn_ZeroMem_Yes); + first_ptr_address = DQN_CAST(uintptr_t)ptr; + DQN_MEMSET(ptr, 'z', size); + Dqn_Arena_EndTempMemory(temp_mem, false /*cancel*/); + } + + // NOTE: Reallocate 128 kilobytes + char *ptr = DQN_CAST(char *)Dqn_Arena_Alloc(&arena, size, 1, Dqn_ZeroMem_Yes); + + // NOTE: Double check we got the same pointer + DQN_UTEST_ASSERT(&test, first_ptr_address == DQN_CAST(uintptr_t)ptr); + + // NOTE: Check that the bytes are set to 0 + for (Dqn_usize i = 0; i < size; i++) + DQN_UTEST_ASSERT(&test, ptr[i] == 0); + Dqn_Arena_Free(&arena); + } + + DQN_UTEST_TEST("Test arena grows naturally, 1mb + 4mb") { + Dqn_Arena arena = {}; + + // NOTE: Allocate 1mb, then 4mb, this should force the arena to grow + char *ptr_1mb = DQN_CAST(char *)Dqn_Arena_Alloc(&arena, DQN_MEGABYTES(1), 1 /*align*/, Dqn_ZeroMem_Yes); + char *ptr_4mb = DQN_CAST(char *)Dqn_Arena_Alloc(&arena, DQN_MEGABYTES(4), 1 /*align*/, Dqn_ZeroMem_Yes); + DQN_UTEST_ASSERT(&test, ptr_1mb); + DQN_UTEST_ASSERT(&test, ptr_4mb); + + Dqn_MemBlock const *block_1mb = arena.head; + char const *block_1mb_begin = DQN_CAST(char *)block_1mb->data; + char const *block_1mb_end = DQN_CAST(char *)block_1mb->data + block_1mb->size; + + Dqn_MemBlock const *block_4mb = arena.curr; + char const *block_4mb_begin = DQN_CAST(char *)block_4mb->data; + char const *block_4mb_end = DQN_CAST(char *)block_4mb->data + block_4mb->size; + + DQN_UTEST_ASSERTF(&test, block_1mb != block_4mb, "New block should have been allocated and linked"); + DQN_UTEST_ASSERTF(&test, ptr_1mb >= block_1mb_begin && ptr_1mb <= block_1mb_end, "Pointer was not allocated from correct memory block"); + DQN_UTEST_ASSERTF(&test, ptr_4mb >= block_4mb_begin && ptr_4mb <= block_4mb_end, "Pointer was not allocated from correct memory block"); + DQN_UTEST_ASSERT (&test, arena.curr == arena.tail); + DQN_UTEST_ASSERT (&test, arena.curr != arena.head); + + Dqn_Arena_Free(&arena); + } + + DQN_UTEST_TEST("Test arena grows naturally, 1mb, temp memory 4mb") { + Dqn_Arena arena = {}; + + // NOTE: Allocate 1mb, then 4mb, this should force the arena to grow + char *ptr_1mb = DQN_CAST(char *)Dqn_Arena_Alloc(&arena, DQN_MEGABYTES(1), 1 /*align*/, Dqn_ZeroMem_Yes); + DQN_UTEST_ASSERT(&test, ptr_1mb); + + Dqn_ArenaTempMemory temp_memory = Dqn_Arena_BeginTempMemory(&arena); + { + char *ptr_4mb = DQN_CAST(char *)Dqn_Arena_Alloc(&arena, DQN_MEGABYTES(4), 1 /*align*/, Dqn_ZeroMem_Yes); DQN_UTEST_ASSERT(&test, ptr_4mb); - Dqn_ArenaBlock const *block_1mb = arena.head; - char const *block_1mb_begin = DQN_CAST(char *)block_1mb->memory; - char const *block_1mb_end = DQN_CAST(char *)block_1mb->memory + block_1mb->size; + Dqn_MemBlock const *block_1mb = arena.head; + char const *block_1mb_begin = DQN_CAST(char *)block_1mb->data; + char const *block_1mb_end = DQN_CAST(char *)block_1mb->data + block_1mb->size; - Dqn_ArenaBlock const *block_4mb = arena.curr; - char const *block_4mb_begin = DQN_CAST(char *)block_4mb->memory; - char const *block_4mb_end = DQN_CAST(char *)block_4mb->memory + block_4mb->size; + Dqn_MemBlock const *block_4mb = arena.curr; + char const *block_4mb_begin = DQN_CAST(char *)block_4mb->data; + char const *block_4mb_end = DQN_CAST(char *)block_4mb->data + block_4mb->size; DQN_UTEST_ASSERTF(&test, block_1mb != block_4mb, "New block should have been allocated and linked"); DQN_UTEST_ASSERTF(&test, ptr_1mb >= block_1mb_begin && ptr_1mb <= block_1mb_end, "Pointer was not allocated from correct memory block"); DQN_UTEST_ASSERTF(&test, ptr_4mb >= block_4mb_begin && ptr_4mb <= block_4mb_end, "Pointer was not allocated from correct memory block"); DQN_UTEST_ASSERT (&test, arena.curr == arena.tail); DQN_UTEST_ASSERT (&test, arena.curr != arena.head); - - Dqn_Arena_Free(&arena, Dqn_ZeroMem_No); } + Dqn_Arena_EndTempMemory(temp_memory, false /*cancel*/); + + DQN_UTEST_ASSERT (&test, arena.curr == arena.head); + DQN_UTEST_ASSERT (&test, arena.curr == arena.tail); + DQN_UTEST_ASSERT (&test, arena.curr->next == nullptr); + DQN_UTEST_ASSERTF(&test, arena.curr->size >= DQN_MEGABYTES(1), + "size=%zuMiB (%zuB), expect=%zuB", (arena.curr->size / 1024 / 1024), arena.curr->size, DQN_MEGABYTES(1)); + + Dqn_Arena_Free(&arena); } - for (Dqn_usize guard = 0; guard < Guard_Count; guard++) { - Dqn_String8 test_suffix = ArenaGuardTestSuffix(guard); - DQN_UTEST_TEST("Test arena grows naturally, 1mb, temp memory 4mb%.*s", DQN_STRING_FMT(test_suffix)) { - Dqn_Arena arena = {}; - arena.use_after_free_guard = guard == Guard_UseAfterFree; + DQN_UTEST_TEST("Init arena, temp region then free inside regions") { + Dqn_Arena arena = {}; + Dqn_Arena_Grow(&arena, DQN_KILOBYTES(1), 0, Dqn_ZeroMem_No); - // NOTE: Allocate 1mb, then 4mb, this should force the arena to grow - char *ptr_1mb = DQN_CAST(char *)Dqn_Arena_Allocate(&arena, DQN_MEGABYTES(1), 1 /*align*/, Dqn_ZeroMem_Yes); - DQN_UTEST_ASSERT(&test, ptr_1mb); - - Dqn_ArenaTempMemory temp_memory = Dqn_Arena_BeginTempMemory(&arena); - { - char *ptr_4mb = DQN_CAST(char *)Dqn_Arena_Allocate(&arena, DQN_MEGABYTES(4), 1 /*align*/, Dqn_ZeroMem_Yes); - DQN_UTEST_ASSERT(&test, ptr_4mb); - - Dqn_ArenaBlock const *block_1mb = arena.head; - char const *block_1mb_begin = DQN_CAST(char *)block_1mb->memory; - char const *block_1mb_end = DQN_CAST(char *)block_1mb->memory + block_1mb->size; - - Dqn_ArenaBlock const *block_4mb = arena.curr; - char const *block_4mb_begin = DQN_CAST(char *)block_4mb->memory; - char const *block_4mb_end = DQN_CAST(char *)block_4mb->memory + block_4mb->size; - - DQN_UTEST_ASSERTF(&test, block_1mb != block_4mb, "New block should have been allocated and linked"); - DQN_UTEST_ASSERTF(&test, ptr_1mb >= block_1mb_begin && ptr_1mb <= block_1mb_end, "Pointer was not allocated from correct memory block"); - DQN_UTEST_ASSERTF(&test, ptr_4mb >= block_4mb_begin && ptr_4mb <= block_4mb_end, "Pointer was not allocated from correct memory block"); - DQN_UTEST_ASSERT (&test, arena.curr == arena.tail); - DQN_UTEST_ASSERT (&test, arena.curr != arena.head); - } - Dqn_Arena_EndTempMemory(temp_memory); - - DQN_UTEST_ASSERT (&test, arena.curr == arena.head); - DQN_UTEST_ASSERT (&test, arena.curr == arena.tail); - DQN_UTEST_ASSERT (&test, arena.curr->next == nullptr); - DQN_UTEST_ASSERTF(&test, arena.curr->size >= DQN_MEGABYTES(1), - "size=%zuMiB (%zuB), expect=%zuB", (arena.curr->size / 1024 / 1024), arena.curr->size, DQN_MEGABYTES(1)); - - Dqn_Arena_Free(&arena, Dqn_ZeroMem_No); + Dqn_ArenaTempMemory temp_memory = Dqn_Arena_BeginTempMemory(&arena); + { + char *ptr = DQN_CAST(char *)Dqn_Arena_Alloc(&arena, DQN_MEGABYTES(1), 1 /*align*/, Dqn_ZeroMem_Yes); + DQN_UTEST_ASSERT(&test, ptr); + Dqn_Arena_Free(&arena); } + Dqn_Arena_EndTempMemory(temp_memory, false /*cancel*/); + Dqn_Arena_Free(&arena); } - for (Dqn_usize guard = 0; guard < Guard_Count; guard++) { - Dqn_String8 test_suffix = ArenaGuardTestSuffix(guard); - DQN_UTEST_TEST("Zero init arena, temp region then free inside region%.*s", DQN_STRING_FMT(test_suffix)) { - Dqn_Arena arena = {}; - arena.use_after_free_guard = guard == Guard_UseAfterFree; - Dqn_ArenaTempMemory temp_memory = Dqn_Arena_BeginTempMemory(&arena); - { - char *ptr = DQN_CAST(char *)Dqn_Arena_Allocate(&arena, DQN_MEGABYTES(1), 1 /*align*/, Dqn_ZeroMem_Yes); - DQN_UTEST_ASSERT(&test, ptr); - Dqn_Arena_Free(&arena, Dqn_ZeroMem_No); - } - Dqn_Arena_EndTempMemory(temp_memory); - Dqn_Arena_Free(&arena, Dqn_ZeroMem_No); - } - } - - for (Dqn_usize guard = 0; guard < Guard_Count; guard++) { - Dqn_String8 test_suffix = ArenaGuardTestSuffix(guard); - DQN_UTEST_TEST("Zero init arena, allocate, temp region then free inside region%.*s", DQN_STRING_FMT(test_suffix)) { - Dqn_Arena arena = {}; - arena.use_after_free_guard = guard == Guard_UseAfterFree; - - char *outside = DQN_CAST(char *)Dqn_Arena_Allocate(&arena, DQN_MEGABYTES(1), 1 /*align*/, Dqn_ZeroMem_Yes); - DQN_UTEST_ASSERT(&test, outside); - - Dqn_ArenaTempMemory temp_memory = Dqn_Arena_BeginTempMemory(&arena); - { - char *inside = DQN_CAST(char *)Dqn_Arena_Allocate(&arena, DQN_MEGABYTES(2), 1 /*align*/, Dqn_ZeroMem_Yes); - DQN_UTEST_ASSERT(&test, inside); - Dqn_Arena_Free(&arena, Dqn_ZeroMem_No); - } - Dqn_Arena_EndTempMemory(temp_memory); - Dqn_Arena_Free(&arena, Dqn_ZeroMem_No); - } - } - - Dqn_usize sizes[] = {DQN_KILOBYTES(1), DQN_KILOBYTES(4), DQN_KILOBYTES(5)}; - for (Dqn_usize size : sizes) { - DQN_UTEST_TEST("Use-after-free guard on %.1f KiB allocation", size / 1024.0) { - Dqn_Arena arena = {}; - arena.use_after_free_guard = true; - Dqn_Arena_Grow(&arena, size, false /*commit*/, 0 /*flags*/); - - // NOTE: Wrap in temp memory, allocate and write - Dqn_ArenaTempMemory temp_mem = Dqn_Arena_BeginTempMemory(&arena); - uintptr_t first_ptr_address = 0; - void *ptr = Dqn_Arena_Allocate(&arena, size, 1, Dqn_ZeroMem_Yes); - DQN_MEMSET(ptr, 'z', size); - Dqn_Arena_EndTempMemory(temp_mem); - - // NOTE: Temp memory is ended, try and write the pointer - // we should trigger the use-after-free guard. - bool caught = false; - __try { - DQN_MEMSET(ptr, 'a', size); - } __except (1) { - caught = true; - } - - DQN_UTEST_ASSERTF(&test, caught, "Exception was not triggered, was page protected properly?"); - Dqn_Arena_Free(&arena, Dqn_ZeroMem_No); + DQN_UTEST_TEST("Init arena, allocate, temp region then free inside region") { + Dqn_Arena arena = {}; + char *outside = DQN_CAST(char *)Dqn_Arena_Alloc(&arena, DQN_MEGABYTES(1), 1 /*align*/, Dqn_ZeroMem_Yes); + DQN_UTEST_ASSERT(&test, outside); + + Dqn_ArenaTempMemory temp_memory = Dqn_Arena_BeginTempMemory(&arena); + { + char *inside = DQN_CAST(char *)Dqn_Arena_Alloc(&arena, DQN_MEGABYTES(2), 1 /*align*/, Dqn_ZeroMem_Yes); + DQN_UTEST_ASSERT(&test, inside); + Dqn_Arena_Free(&arena); } + Dqn_Arena_EndTempMemory(temp_memory, false /*cancel*/); + Dqn_Arena_Free(&arena); } } return test; @@ -483,7 +428,7 @@ Dqn_UTest TestDSMap() Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); { uint32_t const MAP_SIZE = 64; - Dqn_DSMap map = Dqn_DSMap_Init(MAP_SIZE); + Dqn_DSMap map = Dqn_DSMap_Init(MAP_SIZE); DQN_DEFER { Dqn_DSMap_Deinit(&map); }; DQN_UTEST_TEST("Find non-existent value") { @@ -524,7 +469,7 @@ Dqn_UTest TestDSMap() case DSMapTestType_MakeSlot: prefix = DQN_STRING8("Make slot"); break; } - DQN_ARENA_TEMP_MEMORY_SCOPE(scratch.arena); + Dqn_Arena_TempMemoryScope(scratch.arena); uint32_t const MAP_SIZE = 64; Dqn_DSMap map = Dqn_DSMap_Init(MAP_SIZE); DQN_DEFER { Dqn_DSMap_Deinit(&map); }; @@ -534,7 +479,7 @@ Dqn_UTest TestDSMap() uint64_t value = 0; uint64_t grow_threshold = map_start_size * 3 / 4; for (; map.occupied != grow_threshold; value++) { - uint64_t *val_copy = Dqn_Arena_Copy(scratch.arena, uint64_t, &value, 1); + uint64_t *val_copy = Dqn_Arena_NewCopy(scratch.arena, uint64_t, &value, 1); Dqn_DSMapKey key = Dqn_DSMap_KeyBuffer(&map, (char *)val_copy, sizeof(*val_copy)); DQN_UTEST_ASSERT(&test, !Dqn_DSMap_Find(&map, key)); DQN_UTEST_ASSERT(&test, !Dqn_DSMap_FindSlot(&map, key)); @@ -553,7 +498,7 @@ Dqn_UTest TestDSMap() DQN_UTEST_ASSERT(&test, map.occupied == 1 /*Sentinel*/ + value); { // NOTE: One more item should cause the table to grow by 2x - uint64_t *val_copy = Dqn_Arena_Copy(scratch.arena, uint64_t, &value, 1); + uint64_t *val_copy = Dqn_Arena_NewCopy(scratch.arena, uint64_t, &value, 1); Dqn_DSMapKey key = Dqn_DSMap_KeyBuffer(&map, (char *)val_copy, sizeof(*val_copy)); bool found = false; if (test_type == DSMapTestType_Set) { @@ -603,7 +548,7 @@ Dqn_UTest TestDSMap() uint64_t value = 0; uint64_t shrink_threshold = map.size * 1 / 4; for (; map.occupied != shrink_threshold; value++) { - uint64_t *val_copy = Dqn_Arena_Copy(scratch.arena, uint64_t, &value, 1); + uint64_t *val_copy = Dqn_Arena_NewCopy(scratch.arena, uint64_t, &value, 1); Dqn_DSMapKey key = Dqn_DSMap_KeyBuffer(&map, (char *)val_copy, sizeof(*val_copy)); DQN_UTEST_ASSERT(&test, Dqn_DSMap_Find(&map, key)); @@ -616,7 +561,7 @@ Dqn_UTest TestDSMap() DQN_UTEST_ASSERT(&test, map.occupied == start_map_occupied - value); { // NOTE: One more item should cause the table to grow by 2x - uint64_t *val_copy = Dqn_Arena_Copy(scratch.arena, uint64_t, &value, 1); + uint64_t *val_copy = Dqn_Arena_NewCopy(scratch.arena, uint64_t, &value, 1); Dqn_DSMapKey key = Dqn_DSMap_KeyBuffer(&map, (char *)val_copy, sizeof(*val_copy)); Dqn_DSMap_Erase(&map, key); value++; @@ -655,7 +600,7 @@ Dqn_UTest TestDSMap() } for (; map.occupied != 1; value++) { // NOTE: Remove all items from the table - uint64_t *val_copy = Dqn_Arena_Copy(scratch.arena, uint64_t, &value, 1); + uint64_t *val_copy = Dqn_Arena_NewCopy(scratch.arena, uint64_t, &value, 1); Dqn_DSMapKey key = Dqn_DSMap_KeyBuffer(&map, (char *)val_copy, sizeof(*val_copy)); DQN_UTEST_ASSERT(&test, Dqn_DSMap_Find(&map, key)); Dqn_DSMap_Erase(&map, key); @@ -710,7 +655,7 @@ Dqn_UTest TestFs() // NOTE: Read step Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - Dqn_String8 read_file = Dqn_Fs_ReadString8(SRC_FILE, scratch.allocator); + Dqn_String8 read_file = Dqn_Fs_Read(SRC_FILE, scratch.allocator); DQN_UTEST_ASSERTF(&test, Dqn_String8_IsValid(read_file), "Failed to load file"); DQN_UTEST_ASSERTF(&test, read_file.size == 4, "File read wrong amount of bytes"); DQN_UTEST_ASSERTF(&test, Dqn_String8_Eq(read_file, DQN_STRING8("test")), "read(%zu): %.*s", read_file.size, DQN_STRING_FMT(read_file)); @@ -840,8 +785,8 @@ Dqn_UTest TestIntrinsics() DQN_UTEST_ASSERTF(&test, a == b, "a: %I64i, b: %I64i", a, b); } - Dqn_UTest_Begin(&test, "Dqn_CPUClockCycle"); - Dqn_CPUClockCycle(); + Dqn_UTest_Begin(&test, "Dqn_CPU_TSC"); + Dqn_CPU_TSC(); Dqn_UTest_End(&test); Dqn_UTest_Begin(&test, "Dqn_CompilerReadBarrierAndCPUReadFence"); @@ -1159,116 +1104,124 @@ Dqn_UTest TestRect() Dqn_UTest test = {}; DQN_UTEST_GROUP(test, "Dqn_Rect") { DQN_UTEST_TEST("No intersection") { - Dqn_Rect a = Dqn_Rect_InitFromPosAndSize(Dqn_V2(0, 0), Dqn_V2(100, 100)); - Dqn_Rect b = Dqn_Rect_InitFromPosAndSize(Dqn_V2(200, 0), Dqn_V2(200, 200)); + Dqn_Rect a = Dqn_Rect_InitV2x2(Dqn_V2_InitNx1(0), Dqn_V2_InitNx2(100, 100)); + Dqn_Rect b = Dqn_Rect_InitV2x2(Dqn_V2_InitNx2(200, 0), Dqn_V2_InitNx2(200, 200)); Dqn_Rect ab = Dqn_Rect_Intersection(a, b); + Dqn_V2 ab_max = ab.pos + ab.size; DQN_UTEST_ASSERTF(&test, - ab.min.x == 0 && ab.min.y == 0 && ab.max.x == 0 && ab.max.y == 0, - "ab = { min.x = %.2f, min.y = %.2f, max.x = %.2f. max.y = %.2f }", - ab.min.x, - ab.min.y, - ab.max.x, - ab.max.y); + ab.pos.x == 0 && ab.pos.y == 0 && ab_max.x == 0 && ab_max.y == 0, + "ab = { min.x = %.2f, min.y = %.2f, max.x = %.2f. max.y = %.2f }", + ab.pos.x, + ab.pos.y, + ab_max.x, + ab_max.y); } DQN_UTEST_TEST("A's min intersects B") { - Dqn_Rect a = Dqn_Rect_InitFromPosAndSize(Dqn_V2(50, 50), Dqn_V2(100, 100)); - Dqn_Rect b = Dqn_Rect_InitFromPosAndSize(Dqn_V2( 0, 0), Dqn_V2(100, 100)); + Dqn_Rect a = Dqn_Rect_InitV2x2(Dqn_V2_InitNx2(50, 50), Dqn_V2_InitNx2(100, 100)); + Dqn_Rect b = Dqn_Rect_InitV2x2(Dqn_V2_InitNx2( 0, 0), Dqn_V2_InitNx2(100, 100)); Dqn_Rect ab = Dqn_Rect_Intersection(a, b); + Dqn_V2 ab_max = ab.pos + ab.size; DQN_UTEST_ASSERTF(&test, - ab.min.x == 50 && ab.min.y == 50 && ab.max.x == 100 && ab.max.y == 100, + ab.pos.x == 50 && ab.pos.y == 50 && ab_max.x == 100 && ab_max.y == 100, "ab = { min.x = %.2f, min.y = %.2f, max.x = %.2f. max.y = %.2f }", - ab.min.x, - ab.min.y, - ab.max.x, - ab.max.y); + ab.pos.x, + ab.pos.y, + ab_max.x, + ab_max.y); } DQN_UTEST_TEST("B's min intersects A") { - Dqn_Rect a = Dqn_Rect_InitFromPosAndSize(Dqn_V2( 0, 0), Dqn_V2(100, 100)); - Dqn_Rect b = Dqn_Rect_InitFromPosAndSize(Dqn_V2(50, 50), Dqn_V2(100, 100)); + Dqn_Rect a = Dqn_Rect_InitV2x2(Dqn_V2_InitNx2( 0, 0), Dqn_V2_InitNx2(100, 100)); + Dqn_Rect b = Dqn_Rect_InitV2x2(Dqn_V2_InitNx2(50, 50), Dqn_V2_InitNx2(100, 100)); Dqn_Rect ab = Dqn_Rect_Intersection(a, b); + Dqn_V2 ab_max = ab.pos + ab.size; DQN_UTEST_ASSERTF(&test, - ab.min.x == 50 && ab.min.y == 50 && ab.max.x == 100 && ab.max.y == 100, + ab.pos.x == 50 && ab.pos.y == 50 && ab_max.x == 100 && ab_max.y == 100, "ab = { min.x = %.2f, min.y = %.2f, max.x = %.2f. max.y = %.2f }", - ab.min.x, - ab.min.y, - ab.max.x, - ab.max.y); + ab.pos.x, + ab.pos.y, + ab_max.x, + ab_max.y); } DQN_UTEST_TEST("A's max intersects B") { - Dqn_Rect a = Dqn_Rect_InitFromPosAndSize(Dqn_V2(-50, -50), Dqn_V2(100, 100)); - Dqn_Rect b = Dqn_Rect_InitFromPosAndSize(Dqn_V2( 0, 0), Dqn_V2(100, 100)); + Dqn_Rect a = Dqn_Rect_InitV2x2(Dqn_V2_InitNx2(-50, -50), Dqn_V2_InitNx2(100, 100)); + Dqn_Rect b = Dqn_Rect_InitV2x2(Dqn_V2_InitNx2( 0, 0), Dqn_V2_InitNx2(100, 100)); Dqn_Rect ab = Dqn_Rect_Intersection(a, b); + Dqn_V2 ab_max = ab.pos + ab.size; DQN_UTEST_ASSERTF(&test, - ab.min.x == 0 && ab.min.y == 0 && ab.max.x == 50 && ab.max.y == 50, + ab.pos.x == 0 && ab.pos.y == 0 && ab_max.x == 50 && ab_max.y == 50, "ab = { min.x = %.2f, min.y = %.2f, max.x = %.2f. max.y = %.2f }", - ab.min.x, - ab.min.y, - ab.max.x, - ab.max.y); + ab.pos.x, + ab.pos.y, + ab_max.x, + ab_max.y); } DQN_UTEST_TEST("B's max intersects A") { - Dqn_Rect a = Dqn_Rect_InitFromPosAndSize(Dqn_V2( 0, 0), Dqn_V2(100, 100)); - Dqn_Rect b = Dqn_Rect_InitFromPosAndSize(Dqn_V2(-50, -50), Dqn_V2(100, 100)); + Dqn_Rect a = Dqn_Rect_InitV2x2(Dqn_V2_InitNx2( 0, 0), Dqn_V2_InitNx2(100, 100)); + Dqn_Rect b = Dqn_Rect_InitV2x2(Dqn_V2_InitNx2(-50, -50), Dqn_V2_InitNx2(100, 100)); Dqn_Rect ab = Dqn_Rect_Intersection(a, b); + Dqn_V2 ab_max = ab.pos + ab.size; DQN_UTEST_ASSERTF(&test, - ab.min.x == 0 && ab.min.y == 0 && ab.max.x == 50 && ab.max.y == 50, + ab.pos.x == 0 && ab.pos.y == 0 && ab_max.x == 50 && ab_max.y == 50, "ab = { min.x = %.2f, min.y = %.2f, max.x = %.2f. max.y = %.2f }", - ab.min.x, - ab.min.y, - ab.max.x, - ab.max.y); + ab.pos.x, + ab.pos.y, + ab_max.x, + ab_max.y); } DQN_UTEST_TEST("B contains A") { - Dqn_Rect a = Dqn_Rect_InitFromPosAndSize(Dqn_V2(25, 25), Dqn_V2( 25, 25)); - Dqn_Rect b = Dqn_Rect_InitFromPosAndSize(Dqn_V2( 0, 0), Dqn_V2(100, 100)); + Dqn_Rect a = Dqn_Rect_InitV2x2(Dqn_V2_InitNx2(25, 25), Dqn_V2_InitNx2( 25, 25)); + Dqn_Rect b = Dqn_Rect_InitV2x2(Dqn_V2_InitNx2( 0, 0), Dqn_V2_InitNx2(100, 100)); Dqn_Rect ab = Dqn_Rect_Intersection(a, b); + Dqn_V2 ab_max = ab.pos + ab.size; DQN_UTEST_ASSERTF(&test, - ab.min.x == 25 && ab.min.y == 25 && ab.max.x == 50 && ab.max.y == 50, + ab.pos.x == 25 && ab.pos.y == 25 && ab_max.x == 50 && ab_max.y == 50, "ab = { min.x = %.2f, min.y = %.2f, max.x = %.2f. max.y = %.2f }", - ab.min.x, - ab.min.y, - ab.max.x, - ab.max.y); + ab.pos.x, + ab.pos.y, + ab_max.x, + ab_max.y); } DQN_UTEST_TEST("A contains B") { - Dqn_Rect a = Dqn_Rect_InitFromPosAndSize(Dqn_V2( 0, 0), Dqn_V2(100, 100)); - Dqn_Rect b = Dqn_Rect_InitFromPosAndSize(Dqn_V2(25, 25), Dqn_V2( 25, 25)); + Dqn_Rect a = Dqn_Rect_InitV2x2(Dqn_V2_InitNx2( 0, 0), Dqn_V2_InitNx2(100, 100)); + Dqn_Rect b = Dqn_Rect_InitV2x2(Dqn_V2_InitNx2(25, 25), Dqn_V2_InitNx2( 25, 25)); Dqn_Rect ab = Dqn_Rect_Intersection(a, b); + Dqn_V2 ab_max = ab.pos + ab.size; DQN_UTEST_ASSERTF(&test, - ab.min.x == 25 && ab.min.y == 25 && ab.max.x == 50 && ab.max.y == 50, + ab.pos.x == 25 && ab.pos.y == 25 && ab_max.x == 50 && ab_max.y == 50, "ab = { min.x = %.2f, min.y = %.2f, max.x = %.2f. max.y = %.2f }", - ab.min.x, - ab.min.y, - ab.max.x, - ab.max.y); + ab.pos.x, + ab.pos.y, + ab_max.x, + ab_max.y); } DQN_UTEST_TEST("A equals B") { - Dqn_Rect a = Dqn_Rect_InitFromPosAndSize(Dqn_V2(0, 0), Dqn_V2(100, 100)); + Dqn_Rect a = Dqn_Rect_InitV2x2(Dqn_V2_InitNx2(0, 0), Dqn_V2_InitNx2(100, 100)); Dqn_Rect b = a; Dqn_Rect ab = Dqn_Rect_Intersection(a, b); + Dqn_V2 ab_max = ab.pos + ab.size; DQN_UTEST_ASSERTF(&test, - ab.min.x == 0 && ab.min.y == 0 && ab.max.x == 100 && ab.max.y == 100, + ab.pos.x == 0 && ab.pos.y == 0 && ab_max.x == 100 && ab_max.y == 100, "ab = { min.x = %.2f, min.y = %.2f, max.x = %.2f. max.y = %.2f }", - ab.min.x, - ab.min.y, - ab.max.x, - ab.max.y); + ab.pos.x, + ab.pos.y, + ab_max.x, + ab_max.y); } } return test; @@ -1416,95 +1369,113 @@ Dqn_UTest TestString8() // NOTE: Dqn_String8_ToI64 // ========================================================================================= DQN_UTEST_TEST("To I64: Convert null string") { - int64_t result = Dqn_String8_ToI64(Dqn_String8_Init(nullptr, 5), 0); - DQN_UTEST_ASSERT(&test, result == 0); + Dqn_String8ToI64Result result = Dqn_String8_ToI64(Dqn_String8_Init(nullptr, 5), 0); + DQN_UTEST_ASSERT(&test, !result.success); + DQN_UTEST_ASSERT(&test, result.value == 0); } DQN_UTEST_TEST("To I64: Convert empty string") { - int64_t result = Dqn_String8_ToI64(DQN_STRING8(""), 0); - DQN_UTEST_ASSERT(&test, result == 0); + Dqn_String8ToI64Result result = Dqn_String8_ToI64(DQN_STRING8(""), 0); + DQN_UTEST_ASSERT(&test, !result.success); + DQN_UTEST_ASSERT(&test, result.value == 0); } DQN_UTEST_TEST("To I64: Convert \"1\"") { - int64_t result = Dqn_String8_ToI64(DQN_STRING8("1"), 0); - DQN_UTEST_ASSERT(&test, result == 1); + Dqn_String8ToI64Result result = Dqn_String8_ToI64(DQN_STRING8("1"), 0); + DQN_UTEST_ASSERT(&test, result.success); + DQN_UTEST_ASSERT(&test, result.value == 1); } DQN_UTEST_TEST("To I64: Convert \"-0\"") { - int64_t result = Dqn_String8_ToI64(DQN_STRING8("-0"), 0); - DQN_UTEST_ASSERT(&test, result == 0); + Dqn_String8ToI64Result result = Dqn_String8_ToI64(DQN_STRING8("-0"), 0); + DQN_UTEST_ASSERT(&test, result.success); + DQN_UTEST_ASSERT(&test, result.value == 0); } DQN_UTEST_TEST("To I64: Convert \"-1\"") { - int64_t result = Dqn_String8_ToI64(DQN_STRING8("-1"), 0); - DQN_UTEST_ASSERT(&test, result == -1); + Dqn_String8ToI64Result result = Dqn_String8_ToI64(DQN_STRING8("-1"), 0); + DQN_UTEST_ASSERT(&test, result.success); + DQN_UTEST_ASSERT(&test, result.value == -1); } DQN_UTEST_TEST("To I64: Convert \"1.2\"") { - int64_t result = Dqn_String8_ToI64(DQN_STRING8("1.2"), 0); - DQN_UTEST_ASSERT(&test, result == 1); + Dqn_String8ToI64Result result = Dqn_String8_ToI64(DQN_STRING8("1.2"), 0); + DQN_UTEST_ASSERT(&test, !result.success); + DQN_UTEST_ASSERT(&test, result.value == 1); } DQN_UTEST_TEST("To I64: Convert \"1,234\"") { - int64_t result = Dqn_String8_ToI64(DQN_STRING8("1,234"), ','); - DQN_UTEST_ASSERT(&test, result == 1234); + Dqn_String8ToI64Result result = Dqn_String8_ToI64(DQN_STRING8("1,234"), ','); + DQN_UTEST_ASSERT(&test, result.success); + DQN_UTEST_ASSERT(&test, result.value == 1234); } DQN_UTEST_TEST("To I64: Convert \"1,2\"") { - int64_t result = Dqn_String8_ToI64(DQN_STRING8("1,2"), ','); - DQN_UTEST_ASSERT(&test, result == 12); + Dqn_String8ToI64Result result = Dqn_String8_ToI64(DQN_STRING8("1,2"), ','); + DQN_UTEST_ASSERT(&test, result.success); + DQN_UTEST_ASSERT(&test, result.value == 12); } DQN_UTEST_TEST("To I64: Convert \"12a3\"") { - int64_t result = Dqn_String8_ToI64(DQN_STRING8("12a3"), 0); - DQN_UTEST_ASSERT(&test, result == 12); + Dqn_String8ToI64Result result = Dqn_String8_ToI64(DQN_STRING8("12a3"), 0); + DQN_UTEST_ASSERT(&test, !result.success); + DQN_UTEST_ASSERT(&test, result.value == 12); } // NOTE: Dqn_String8_ToU64 // --------------------------------------------------------------------------------------------- DQN_UTEST_TEST("To U64: Convert nullptr") { - uint64_t result = Dqn_String8_ToU64(Dqn_String8_Init(nullptr, 5), 0); - DQN_UTEST_ASSERTF(&test, result == 0, "result: %zu", result); + Dqn_String8ToU64Result result = Dqn_String8_ToU64(Dqn_String8_Init(nullptr, 5), 0); + DQN_UTEST_ASSERT(&test, !result.success); + DQN_UTEST_ASSERTF(&test, result.value == 0, "result: %I64u", result.value); } DQN_UTEST_TEST("To U64: Convert empty string") { - uint64_t result = Dqn_String8_ToU64(DQN_STRING8(""), 0); - DQN_UTEST_ASSERTF(&test, result == 0, "result: %zu", result); + Dqn_String8ToU64Result result = Dqn_String8_ToU64(DQN_STRING8(""), 0); + DQN_UTEST_ASSERT(&test, !result.success); + DQN_UTEST_ASSERTF(&test, result.value == 0, "result: %I64u", result.value); } DQN_UTEST_TEST("To U64: Convert \"1\"") { - uint64_t result = Dqn_String8_ToU64(DQN_STRING8("1"), 0); - DQN_UTEST_ASSERTF(&test, result == 1, "result: %zu", result); + Dqn_String8ToU64Result result = Dqn_String8_ToU64(DQN_STRING8("1"), 0); + DQN_UTEST_ASSERT(&test, result.success); + DQN_UTEST_ASSERTF(&test, result.value == 1, "result: %I64u", result.value); } DQN_UTEST_TEST("To U64: Convert \"-0\"") { - uint64_t result = Dqn_String8_ToU64(DQN_STRING8("-0"), 0); - DQN_UTEST_ASSERTF(&test, result == 0, "result: %zu", result); + Dqn_String8ToU64Result result = Dqn_String8_ToU64(DQN_STRING8("-0"), 0); + DQN_UTEST_ASSERT(&test, !result.success); + DQN_UTEST_ASSERTF(&test, result.value == 0, "result: %I64u", result.value); } DQN_UTEST_TEST("To U64: Convert \"-1\"") { - uint64_t result = Dqn_String8_ToU64(DQN_STRING8("-1"), 0); - DQN_UTEST_ASSERTF(&test, result == 0, "result: %zu", result); + Dqn_String8ToU64Result result = Dqn_String8_ToU64(DQN_STRING8("-1"), 0); + DQN_UTEST_ASSERT(&test, !result.success); + DQN_UTEST_ASSERTF(&test, result.value == 0, "result: %I64u", result.value); } DQN_UTEST_TEST("To U64: Convert \"1.2\"") { - uint64_t result = Dqn_String8_ToU64(DQN_STRING8("1.2"), 0); - DQN_UTEST_ASSERTF(&test, result == 1, "result: %zu", result); + Dqn_String8ToU64Result result = Dqn_String8_ToU64(DQN_STRING8("1.2"), 0); + DQN_UTEST_ASSERT(&test, !result.success); + DQN_UTEST_ASSERTF(&test, result.value == 1, "result: %I64u", result.value); } DQN_UTEST_TEST("To U64: Convert \"1,234\"") { - uint64_t result = Dqn_String8_ToU64(DQN_STRING8("1,234"), ','); - DQN_UTEST_ASSERTF(&test, result == 1234, "result: %zu", result); + Dqn_String8ToU64Result result = Dqn_String8_ToU64(DQN_STRING8("1,234"), ','); + DQN_UTEST_ASSERT(&test, result.success); + DQN_UTEST_ASSERTF(&test, result.value == 1234, "result: %I64u", result.value); } DQN_UTEST_TEST("To U64: Convert \"1,2\"") { - uint64_t result = Dqn_String8_ToU64(DQN_STRING8("1,2"), ','); - DQN_UTEST_ASSERTF(&test, result == 12, "result: %zu", result); + Dqn_String8ToU64Result result = Dqn_String8_ToU64(DQN_STRING8("1,2"), ','); + DQN_UTEST_ASSERT(&test, result.success); + DQN_UTEST_ASSERTF(&test, result.value == 12, "result: %I64u", result.value); } DQN_UTEST_TEST("To U64: Convert \"12a3\"") { - uint64_t result = Dqn_String8_ToU64(DQN_STRING8("12a3"), 0); - DQN_UTEST_ASSERTF(&test, result == 12, "result: %zu", result); + Dqn_String8ToU64Result result = Dqn_String8_ToU64(DQN_STRING8("12a3"), 0); + DQN_UTEST_ASSERT(&test, !result.success); + DQN_UTEST_ASSERTF(&test, result.value == 12, "result: %I64u", result.value); } // NOTE: Dqn_String8_Find @@ -1786,14 +1757,15 @@ Dqn_UTest TestWin() void CustomLogProc(Dqn_String8 type, int log_type, void *user_data, Dqn_CallSite call_site, char const *fmt, va_list args) { + (void)user_data; Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); Dqn_String8 log = Dqn_Log_MakeString(scratch.allocator, true /*colour*/, type, log_type, call_site, fmt, args); DQN_UTEST_LOG("%.*s", DQN_STRING_FMT(log)); } -void TestRunSuite() +void Dqn_TestRunSuite() { - Dqn_Library *dqn_library = Dqn_Library_Init(nullptr); + Dqn_Library *dqn_library = Dqn_Library_Init(); dqn_library->log_callback = CustomLogProc; Dqn_UTest tests[] = @@ -1832,7 +1804,7 @@ void TestRunSuite() int main(int argc, char *argv[]) { (void)argv; (void)argc; - TestRunSuite(); + Dqn_TestRunSuite(); return 0; } #endif diff --git a/Misc/dqn_utest.h b/Misc/dqn_utest.h index 138f82d..2525333 100644 --- a/Misc/dqn_utest.h +++ b/Misc/dqn_utest.h @@ -194,14 +194,14 @@ void Dqn_UTest_Begin(Dqn_UTest *test, char const *fmt, ...) void Dqn_UTest_End(Dqn_UTest *test) { assert(test->state != Dqn_UTestState_Nil && "Test was marked as ended but a test was never commenced using Dqn_UTest_Begin"); - int pad_size = DQN_UTEST_RESULT_LPAD - (DQN_UTEST_SPACING + test->name_size); + size_t pad_size = DQN_UTEST_RESULT_LPAD - (DQN_UTEST_SPACING + test->name_size); if (pad_size < 0) pad_size = 0; char pad_buffer[DQN_UTEST_RESULT_LPAD] = {}; memset(pad_buffer, DQN_UTEST_RESULT_PAD_CHAR, pad_size); - printf("%*s%.*s%.*s", DQN_UTEST_SPACING, "", (int)test->name_size, test->name, pad_size, pad_buffer); + printf("%*s%.*s%.*s", DQN_UTEST_SPACING, "", (int)test->name_size, test->name, (int)pad_size, pad_buffer); if (test->state == Dqn_UTestState_TestFailed) { printf(DQN_UTEST_BAD_COLOR " FAILED"); } else { diff --git a/b_stacktrace.h b/b_stacktrace.h new file mode 100644 index 0000000..a5e9c49 --- /dev/null +++ b/b_stacktrace.h @@ -0,0 +1,411 @@ +#if !defined(B_STACKTRACE_INCLUDED) +#define B_STACKTRACE_INCLUDED (1) +/* +b_stacktrace v0.21 -- a cross-platform stack-trace generator +SPDX-License-Identifier: MIT +URL: https://github.com/iboB/b_stacktrace + +Usage +===== + +#define B_STACKTRACE_IMPL before including b_stacktrace.h in *one* C or C++ +file to create the implementation + +#include "b_stacktrace.h" to get access to the following functions: + +char* b_stacktrace_get_string(); + Returns a human-readable stack-trace string from the point of view of the + caller. + The string is allocated with `malloc` and needs to be freed with `free` + +b_stacktrace_handle b_stacktrace_get(); + Returns a stack-trace handle from the point of view of the caller which + can be expanded to a string via b_stacktrace_to_string. + The handle is allocated with `malloc` and needs to be freed with `free` + +b_stacktrace_to_string(b_stacktrace_handle stacktrace); + Converts a stack-trace handle to a human-readable string. + The string is allocated with `malloc` and needs to be freed with `free` + + +Config +====== + +#define B_STACKTRACE_API to custom export symbols to export the library +functions from a shared lib + +Revision History +================ + +* 0.21 (2022-12-20) Fixed typo +* 0.20 (2022-12-18) Beta. + Expanded interface + Minor fixes +* 0.10 (2020-12-07) Initial public release. Alpha version + +MIT License +=========== + +Copyright (c) 2020-2023 Borislav Stanimirov + +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. +*/ + +#if !defined(B_STACKTRACE_API) +#define B_STACKTRACE_API extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* + A stacktrace handle +*/ +typedef struct b_stacktrace_tag* b_stacktrace_handle; + +/* + Returns a stack-trace handle from the point of view of the caller which + can be expanded to a string via b_stacktrace_to_string. + The handle is allocated with `malloc` and needs to be freed with `free` +*/ +B_STACKTRACE_API b_stacktrace_handle b_stacktrace_get(); + +/* + Converts a stack-trace handle to a human-readable string. + The string is allocated with `malloc` and needs to be freed with `free` +*/ +B_STACKTRACE_API char* b_stacktrace_to_string(b_stacktrace_handle stacktrace); + +/* + Returns a human-readable stack-trace string from the point of view of the + caller. + The string is allocated with `malloc` and needs to be freed with `free` +*/ +B_STACKTRACE_API char* b_stacktrace_get_string(void); + +/* version */ +#define B_STACKTRACE_VER_MAJOR 0 +#define B_STACKTRACE_VER_MINOR 20 + +#ifdef __cplusplus +} +#endif +#endif /* B_STACKTRACE_INCLUDED */ + +#if defined(B_STACKTRACE_IMPL) + +#if defined(__linux__) && !defined(_GNU_SOURCE) +#define _GNU_SOURCE +#endif + +#include +#include +#include + +typedef struct print_buf { + char* buf; + int pos; + int size; +} print_buf; + +static print_buf buf_init(void) { + print_buf ret = { + (char*) malloc(1024), + 0, + 1024 + }; + return ret; +} + +static void buf_printf(print_buf* b, const char* fmt, ...) { + va_list args; + va_start(args, fmt); + const int len = vsnprintf(NULL, 0, fmt, args) + 1; + va_end(args); + + const int new_end = b->pos + len; + + if (new_end > b->size) { + while (new_end > b->size) b->size *= 2; + b->buf = (char*)realloc(b->buf, b->size); + } + + va_start(args, fmt); + b->pos += vsnprintf(b->buf + b->pos, len, fmt, args); + va_end(args); +} + +char* b_stacktrace_get_string(void) { + b_stacktrace_handle h = b_stacktrace_get(); + char* ret = b_stacktrace_to_string(h); + free(h); + return ret; +} + +#define B_STACKTRACE_MAX_DEPTH 1024 + +#if defined(_WIN32) + +#define WIN32_LEAN_AND_MEAN +// #include +#include +#include + +#pragma comment(lib, "DbgHelp.lib") + +#define B_STACKTRACE_ERROR_FLAG ((DWORD64)1 << 63) + +typedef struct b_stacktrace_entry { + DWORD64 AddrPC_Offset; + DWORD64 AddrReturn_Offset; +} b_stacktrace_entry; + +static int SymInitialize_called = 0; + +b_stacktrace_handle b_stacktrace_get(void) { + HANDLE process = GetCurrentProcess(); + HANDLE thread = GetCurrentThread(); + CONTEXT context; + STACKFRAME64 frame; /* in/out stackframe */ + DWORD imageType; + b_stacktrace_entry* ret = (b_stacktrace_entry*)malloc(B_STACKTRACE_MAX_DEPTH * sizeof(b_stacktrace_entry)); + int i = 0; + + if (!SymInitialize_called) { + SymInitialize(process, NULL, TRUE); + SymInitialize_called = 1; + } + + RtlCaptureContext(&context); + + memset(&frame, 0, sizeof(frame)); +#ifdef _M_IX86 + imageType = IMAGE_FILE_MACHINE_I386; + frame.AddrPC.Offset = context.Eip; + frame.AddrPC.Mode = AddrModeFlat; + frame.AddrFrame.Offset = context.Ebp; + frame.AddrFrame.Mode = AddrModeFlat; + frame.AddrStack.Offset = context.Esp; + frame.AddrStack.Mode = AddrModeFlat; +#elif _M_X64 + imageType = IMAGE_FILE_MACHINE_AMD64; + frame.AddrPC.Offset = context.Rip; + frame.AddrPC.Mode = AddrModeFlat; + frame.AddrFrame.Offset = context.Rsp; + frame.AddrFrame.Mode = AddrModeFlat; + frame.AddrStack.Offset = context.Rsp; + frame.AddrStack.Mode = AddrModeFlat; +#elif _M_IA64 + imageType = IMAGE_FILE_MACHINE_IA64; + frame.AddrPC.Offset = context.StIIP; + frame.AddrPC.Mode = AddrModeFlat; + frame.AddrFrame.Offset = context.IntSp; + frame.AddrFrame.Mode = AddrModeFlat; + frame.AddrBStore.Offset = context.RsBSP; + frame.AddrBStore.Mode = AddrModeFlat; + frame.AddrStack.Offset = context.IntSp; + frame.AddrStack.Mode = AddrModeFlat; +#else + #error "Platform not supported!" +#endif + + while (1) { + b_stacktrace_entry* cur = ret + i++; + if (i == B_STACKTRACE_MAX_DEPTH) { + cur->AddrPC_Offset = 0; + cur->AddrReturn_Offset = 0; + break; + } + + if (!StackWalk64(imageType, process, thread, &frame, &context, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL)) { + cur->AddrPC_Offset = frame.AddrPC.Offset; + cur->AddrReturn_Offset = B_STACKTRACE_ERROR_FLAG; /* mark error */ + cur->AddrReturn_Offset |= GetLastError(); + break; + } + + cur->AddrPC_Offset = frame.AddrPC.Offset; + cur->AddrReturn_Offset = frame.AddrReturn.Offset; + + if (frame.AddrReturn.Offset == 0) { + break; + } + } + + return (b_stacktrace_handle)(ret); +} + +char* b_stacktrace_to_string(b_stacktrace_handle h) { + const b_stacktrace_entry* entries = (b_stacktrace_entry*)h; + int i = 0; + HANDLE process = GetCurrentProcess(); + print_buf out = buf_init(); + IMAGEHLP_SYMBOL64* symbol = (IMAGEHLP_SYMBOL64*)malloc(sizeof(IMAGEHLP_SYMBOL64) + 1024); + symbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); + symbol->MaxNameLength = 1024; + + while (1) { + IMAGEHLP_LINE64 lineData; + DWORD lineOffset = 0; + DWORD64 symOffset = 0; + const b_stacktrace_entry* cur = entries + i++; + + if (cur->AddrReturn_Offset & B_STACKTRACE_ERROR_FLAG) { + DWORD error = cur->AddrReturn_Offset & 0xFFFFFFFF; + buf_printf(&out, "StackWalk64 error: %d @ %p\n", error, cur->AddrPC_Offset); + break; + } + + if (cur->AddrPC_Offset == cur->AddrReturn_Offset) { + buf_printf(&out, "Stack overflow @ %p\n", cur->AddrPC_Offset); + break; + } + + SymGetLineFromAddr64(process, cur->AddrPC_Offset, &lineOffset, &lineData); + buf_printf(&out, "%s(%d): ", lineData.FileName, lineData.LineNumber); + + if (SymGetSymFromAddr64(process, cur->AddrPC_Offset, &symOffset, symbol)) { + buf_printf(&out, "%s\n", symbol->Name); + } + else { + buf_printf(&out, " Unkown symbol @ %p\n", cur->AddrPC_Offset); + } + + if (cur->AddrReturn_Offset == 0) { + break; + } + } + + free(symbol); + return out.buf; +} + +#elif defined(__APPLE__) + +#include +#include +#include + +typedef struct b_stacktrace { + void* trace[B_STACKTRACE_MAX_DEPTH]; + int trace_size; +} b_stacktrace; + +b_stacktrace_handle b_stacktrace_get(void) { + b_stacktrace* ret = (b_stacktrace*)malloc(sizeof(b_stacktrace)); + ret->trace_size = backtrace(ret->trace, B_STACKTRACE_MAX_DEPTH); + return (b_stacktrace_handle)(ret); +} + +char* b_stacktrace_to_string(b_stacktrace_handle h) { + const b_stacktrace* stacktrace = (b_stacktrace*)h; + char** messages = backtrace_symbols(stacktrace->trace, stacktrace->trace_size); + print_buf out = buf_init(); + *out.buf = 0; + + for (int i = 0; i < stacktrace->trace_size; ++i) { + buf_printf(&out, "%s\n", messages[i]); + } + + free(messages); + return out.buf; +} + +#elif defined(__linux__) + +#include +#include +#include +#include +#include + +typedef struct b_stacktrace { + void* trace[B_STACKTRACE_MAX_DEPTH]; + int trace_size; +} b_stacktrace; + +b_stacktrace_handle b_stacktrace_get(void) { + b_stacktrace* ret = (b_stacktrace*)malloc(sizeof(b_stacktrace)); + ret->trace_size = backtrace(ret->trace, B_STACKTRACE_MAX_DEPTH); + return (b_stacktrace_handle)(ret); +} + +char* b_stacktrace_to_string(b_stacktrace_handle h) { + const b_stacktrace* stacktrace = (b_stacktrace*)h; + char** messages = backtrace_symbols(stacktrace->trace, stacktrace->trace_size); + print_buf out = buf_init(); + + for (int i = 0; i < stacktrace->trace_size; ++i) { + void* tracei = stacktrace->trace[i]; + char* msg = messages[i]; + + /* calculate load offset */ + Dl_info info; + dladdr(tracei, &info); + if (info.dli_fbase == (void*)0x400000) { + /* address from executable, so don't offset */ + info.dli_fbase = NULL; + } + + while (*msg && *msg != '(') ++msg; + *msg = 0; + + { + char cmd[1024]; + char line[2048]; + + FILE* fp; + snprintf(cmd, 1024, "addr2line -e %s -f -C -p %p 2>/dev/null", messages[i], (void*)((char*)tracei - (char*)info.dli_fbase)); + + fp = popen(cmd, "r"); + if (!fp) { + buf_printf(&out, "Failed to generate trace further...\n"); + break; + } + + while (fgets(line, sizeof(line), fp)) { + buf_printf(&out, "%s: ", messages[i]); + if (strstr(line, "?? ")) { + /* just output address if nothing can be found */ + buf_printf(&out, "%p\n", tracei); + } + else { + buf_printf(&out, "%s", line); + } + } + + pclose(fp); + } + } + + free(messages); + return out.buf; +} + +#else +/* noop implementation */ +char* b_stacktrace_get_string(void) { + print_buf out = buf_init(); + buf_printf("b_stacktrace: unsupported platform\n"); + return out.buf; +} +#endif /* platform */ + +#endif /* B_STACKTRACE_IMPL */ diff --git a/dqn.h b/dqn.h index 57fcaab..c7a248b 100644 --- a/dqn.h +++ b/dqn.h @@ -1,75 +1,300 @@ -// NOTE: [$CFGM] Config macros ===================================================================== +// dqn.h "Personal standard library" | MIT licensed | git.doylet.dev/dqn // -// #define DQN_IMPLEMENTATION -// Define this in one and only one C++ file to enable the implementation -// code of the header file +// This library is a single-header file-esque library with inspiration taken +// from STB libraries for ease of integration and use. It defines a bunch of +// primitives and standard library functions that are missing and or more +// appropriate for development in modern day computing (e.g. cache friendly +// memory management, 64bit MMU, non-pessimized APIs that aren't constrained by +// the language specification and operate closer to the OS). // -// #define DQN_NO_ASSERT -// Turn all assertion macros to no-ops +// Define DQN_IMPLEMENTATION macro in one and only one translation unit to +// enable the implementation of this library, for example: // -// #define DQN_NO_CHECK_BREAK -// Disable debug break when a check macro's expression fails. Instead only -// the error will be logged. +// #define DQN_IMEPLEMENTATION +// #include "dqn.h" // -// #define DQN_NO_WIN32_MIN_HEADER -// Define this to stop this library from defining a minimal subset of Win32 -// prototypes and definitions in this file. Useful for stopping redefinition -// of symbols if another library includes "Windows.h" +// Additionally, this library supports including/excluding specific sections +// of the library by using #define on the name of the section. These names are +// documented in the section table of contents at the #define column, for +// example: // -// #define DQN_STATIC_API -// Apply static to all function definitions and disable external linkage to -// other translation units. +// #define DQN_ONLY_VARRAY +// #define DQN_ONLY_WIN // -// #define DQN_STB_SPRINTF_HEADER_ONLY -// Define this to stop this library from defining -// STB_SPRINTF_IMPLEMENTATION. Useful if another library uses and includes -// "stb_sprintf.h" +// Compiles the library with all optional APIs turned off except virtual arrays +// and the Win32 helpers. Alternatively: // -// #define DQN_MEMSET_BYTE 0 -// Change the byte that DQN_MEMSET will clear memory with. By default this -// is set to 0. Some of this library API accepts are clear memory parameter -// to scrub memory after certain operations. +// #define DQN_NO_VARRAY +// #define DQN_NO_WIN // -// #define DQN_LEAK_TRACING -// When defined to some allocating calls in the library will automatically -// get passed in the file name, function name, line number and an optional -// leak_msg. +// Compiles the library with all optional APIs turned on except the previously +// mentioned APIs. +// +// Below is a table of contents that describes what you can find in each header +// of this library and additional macros that can be defined to customise the +// behaviour of this library. -#if defined(DQN_LEAK_TRACING) -#error Leak tracing not supported because we enter an infinite leak tracing loop tracing our own allocations made to tracks leaks in the internal leak table. +#if !defined(DQN_H) +#if defined(DQN_ONLY_VARRAY) || \ + defined(DQN_ONLY_FARRAY) || \ + defined(DQN_ONLY_DSMAP) || \ + defined(DQN_ONLY_LIST) || \ + defined(DQN_ONLY_FSTRING8) || \ + defined(DQN_ONLY_FS) || \ + defined(DQN_ONLY_WINNET) || \ + defined(DQN_ONLY_WIN) || \ + defined(DQN_ONLY_V2) || \ + defined(DQN_ONLY_V3) || \ + defined(DQN_ONLY_V4) || \ + defined(DQN_ONLY_M4) || \ + defined(DQN_ONLY_RECT) || \ + defined(DQN_ONLY_JSON_BUILDER) || \ + defined(DQN_ONLY_BIN) + + #if !defined(DQN_ONLY_VARRAY) + #define DQN_NO_VARRAY + #endif + #if !defined(DQN_ONLY_FARRAY) + #define DQN_NO_FARRAY + #endif + #if !defined(DQN_ONLY_DSMAP) + #define DQN_NO_DSMAP + #endif + #if !defined(DQN_ONLY_LIST) + #define DQN_NO_LIST + #endif + #if !defined(DQN_ONLY_FSTRING8) + #define DQN_NO_FSTRING8 + #endif + #if !defined(DQN_ONLY_FS) + #define DQN_NO_FS + #endif + #if !defined(DQN_ONLY_WINNET) + #define DQN_NO_WINNET + #endif + #if !defined(DQN_ONLY_WIN) + #define DQN_NO_WIN + #endif + #if !defined(DQN_ONLY_V2) + #define DQN_NO_V2 + #endif + #if !defined(DQN_ONLY_V3) + #define DQN_NO_V3 + #endif + #if !defined(DQN_ONLY_V4) + #define DQN_NO_V4 + #endif + #if !defined(DQN_ONLY_M4) + #define DQN_NO_M4 + #endif + #if !defined(DQN_ONLY_RECT) + #define DQN_NO_RECT + #endif + #if !defined(DQN_ONLY_JSON_BUILDER) + #define DQN_NO_JSON_BUILDER + #endif + #if !defined(DQN_ONLY_BIN) + #define DQN_NO_BIN + #endif + #if !defined(DQN_ONLY_PROFILER) + #define DQN_NO_PROFILER + #endif #endif -// -// #define DQN_DEBUG_THREAD_CONTEXT -// Define this macro to record allocation stats for arenas used in the -// thread context. The thread context arena stats can be printed by using -// Dqn_Library_DumpThreadContextArenaStat. -#if !defined(DQN_H) - #define DQN_H - #include // va_list - #include // fprintf, FILE, stdout, stderr - #include // [u]int_*, ... - #include // [U]INT_MAX, ... +// NOTE: Table of Contents ========================================================================= +// Index | #define Label | Description +// NOTE: C Headers ================================================================================= +#include // | | va_list +#include // | | fprintf, FILE, stdout, stderr +#include // | | [u]int_*, ... +#include // | | [U]INT_MAX, ... - #include "dqn_core.h" - #include "dqn_memory.h" - #include "dqn_print.h" - #include "dqn_strings.h" - #include "dqn_platform.h" - #include "dqn_containers.h" - #include "dqn_math.h" - #include "dqn_misc.h" - #include "dqn_hash.h" +// NOTE: Dqn_Base ================================================================================== +// [$CMAC] Compiler macros | | Macros for the compiler +// [$MACR] Macros | | Define macros used in the library +// [$TYPE] Types | | Basic types and typedefs +// [$INTR] Intrinsics | | Atomics, cpuid, ticket mutex +// [$TMUT] Dqn_TicketMutex | | Userland mutex via spinlocking atomics + +// NOTE: Additional Configuration +// - Override the default heap-allocation routine that is called when the +// default Dqn_Allocator is used by #define-ing. By default we use the OS's +// virtual memory allocators (e.g. VirtualAlloc on Windows and mmap on Linux). +// +// DQN_ALLOC(size) +// DQN_DEALLOC(ptr, size) +// +// - Override the byte-manipulation routines by #define-ing. By default we use +// +// +// DQN_MEMCPY(dest, src, count) +// DQN_MEMSET(dest, value, count) +// DQN_MEMCMP(lhs, rhs, count) +// DQN_MEMMOVE(dest, src, count) +// +// - Override these math functions. By default we use +// +// DQN_SQRTF(val) +// DQN_SINF(val) +// DQN_COSF(val) +// DQN_TANF(val) +// +// - Change the prefix to all declared functions in the library by #define-ing. +// +// DQN_API +// +// - Apply static to all function definitions and disable external linkage to +// other translation units by #define-ing. This macro is only used if DQN_API +// is not overriden. +// +// DQN_STATIC_API +// +// - Turn all assertion macros to no-ops except for hard asserts (which are +// always enabled and represent unrecoverable errors in the library). +// +// DQN_NO_ASSERT +// +// - Augment DQN_CHECK(expr) macro's behaviour. By default it will trigger a +// debugger break when when the expression evalutes false otherwise by +// #define-ing this macro it will evaluate to false and DQN_CHECK is usually +// used in a if branch to recover gracefully from the failed condition. +// +// DQN_NO_CHECK_BREAK + +#include "dqn_base.h" + +// NOTE: Dqn_External ============================================================================== +// [$BSTK] b_stacktrace | | Generating call stacktraces +// [$OS_H] OS Headers | | Headers from the operating system +// [$STBS] stb_sprintf | | Portable sprintf +#include "dqn_external.h" + +// NOTE: Additional Configuration +// - Define this to stop this library from defining a minimal subset of Win32 +// prototypes and definitions in this file. You should use this macro if you +// intend to #include yourself to avoid symbol conflicts with +// the redefined declarations in this library. +// +// DQN_NO_WIN32_MIN_HEADER +// +// - Define this to stop this library from defining STB_SPRINTF_IMPLEMENTATION. +// Useful if another library uses and includes "stb_sprintf.h" +// +// DQN_STB_SPRINTF_HEADER_ONLY + +// NOTE: Dqn_Memory ================================================================================ +// [$ALLO] Dqn_Allocator | | Generic allocator interface +// [$VMEM] Dqn_VMem | | Virtual memory allocation +// [$MEMB] Dqn_MemBlock | | Virtual memory blocks +// [$AREN] Dqn_Arena | | Growing bump allocator +// [$ACAT] Dqn_ArenaCatalog | | Collate, create & manage arenas in a catalog +#include "dqn_memory.h" + +// NOTE: Dqn_Debug ================================================================================= +// [$DEBM] Debug Macros | | +// [$CALL] Dqn_CallSite | | Source code location/tracing +// [$DEBG] Dqn_Debug | | Debugging tools/helpers +// [$LLOG] Dqn_Log | | Console logging macros +#include "dqn_debug.h" + +// NOTE: Additional Configuration +// - Override the default break into the active debugger function. By default +// we use __debugbreak() on Windows and raise(SIGTRAP) on other platforms. +// +// DQN_DEBUG_BREAK +// +// - Change the byte that DQN_MEMSET will clear memory with. By default this +// is set to 0. Some of the API's in this library accept a Dqn_ZeroMem enum +// which scrubs memory with this #define-d value. +// +// DQN_MEMSET_BYTE +// +// - Override the stack trace dump functionality on invocation of this macro. +// Currently this is only used in asserts. +// +// DQN_DUMP_STACK_TRACE +// +// - Enable memory leak tracking when requesting memory from the OS via this +// library. For example calls to Dqn_VMem_Reserve or DQN_ALLOC are recorded. +// Allocations are stored in a global hash-table and their respective stack +// traces for the allocation location. Memory leaks can be dumped at the end +// of the program or some epoch by calling Dqn_Library_DumpLeaks() +// +// You may mark sections of your program as allowed to leak memory by setting +// the arena's or Dqn_Library's runtime struct `allocs_are_allowed_to_leak` +// flag. +// +// DQN_LEAK_TRACING + +// NOTE: Dqn_Strings =============================================================================== +// [$CSTR] Dqn_CString8 | | C-string helpers +// [$STR8] Dqn_String8 | | Pointer and length strings +// [$FSTR] Dqn_FString8 | DQN_FSTRING8 | Fixed-size strings +// [$STRB] Dqn_String8Builder | | +// [$CHAR] Dqn_Char | | Character ascii/digit.. helpers +// [$UTFX] Dqn_UTF | | Unicode helpers +#include "dqn_strings.h" + +// NOTE: Dqn_Containers ============================================================================ +// [$VARR] Dqn_VArray | DQN_VARRAY | Array backed by virtual memory arena +// [$FARR] Dqn_FArray | DQN_FARRAY | Fixed-size arrays +// [$DMAP] Dqn_DSMap | DQN_DSMAP | Hashtable, 70% max load, PoT size, linear probe, chain repair +// [$LIST] Dqn_List | DQN_LIST | Chunked linked lists, append only +#include "dqn_containers.h" + +// NOTE: Dqn_Platform ============================================================================== +// [$PRIN] Dqn_Print | | Console printing +// [$FSYS] Dqn_Fs | DQN_FS | Filesystem helpers +// [$DATE] Dqn_Date | | Date-time helpers +// [$WIND] Dqn_Win | | Windows OS helpers +// [$WINN] Dqn_WinNet | DQN_WINNET | Windows internet download/query helpers +// [$OSYS] Dqn_OS | DQN_WIN | Operating-system APIs +// [$TCTX] Dqn_ThreadContext | | Per-thread data structure e.g. temp arenas + +// NOTE: Additional Configuration +// - Define this macro to record allocation stats for arenas used in the +// thread context. The thread context arena stats can be printed by using +// Dqn_Library_DumpThreadContextArenaStat. +// +// DQN_DEBUG_THREAD_CONTEXT + +#include "dqn_platform.h" + +// NOTE: Dqn_Math ================================================================================== +// [$VEC2] Dqn_V2, V2i | DQN_V2 | +// [$VEC3] Dqn_V3, V3i | DQN_V3 | +// [$VEC4] Dqn_V4, V4i | DQN_V4 | +// [$MAT4] Dqn_M4 | DQN_M4 | +// [$RECT] Dqn_Rect | DQN_RECT | +// [$MATH] Other | | +#include "dqn_math.h" + +// NOTE: Dqn_Hash ================================================================================== +// [$FNV1] Dqn_FNV1A | | Hash(x) -> 32/64bit via FNV1a +// [$MMUR] Dqn_MurmurHash3 | | Hash(x) -> 32/128bit via MurmurHash3 +#include "dqn_hash.h" + +// NOTE: Dqn_Helpers =============================================================================== +// [$JSON] Dqn_JSONBuilder | DQN_JSON_BUILDER | Construct json output +// [$BHEX] Dqn_Bin | DQN_BIN | Binary <-> hex helpers +// [$BSEA] Dqn_BinarySearch | | Binary search +// [$BITS] Dqn_Bit | | Bitset manipulation +// [$SAFE] Dqn_Safe | | Safe arithmetic, casts, asserts +// [$MISC] Misc | | Uncategorised helper functions +// [$DLIB] Dqn_Library | | Globally shared runtime data for this library +// [$PROF] Dqn_Profiler | DQN_PROFILER | Profiler that measures using a timestamp counter +#include "dqn_helpers.h" #endif // DQN_H #if defined(DQN_IMPLEMENTATION) - #include "dqn_platform.cpp" - #include "dqn_memory.cpp" - #include "dqn_print.cpp" - #include "dqn_strings.cpp" - #include "dqn_containers.cpp" - #include "dqn_math.cpp" - #include "dqn_misc.cpp" - #include "dqn_hash.cpp" - #include "dqn_core.cpp" +#include "dqn_base.cpp" +#include "dqn_external.cpp" +#include "dqn_memory.cpp" +#include "dqn_debug.cpp" +#include "dqn_strings.cpp" +#include "dqn_containers.cpp" +#include "dqn_platform.cpp" +#include "dqn_math.cpp" +#include "dqn_hash.cpp" +#include "dqn_helpers.cpp" #endif // DQN_IMPLEMENTATION diff --git a/dqn_core.cpp b/dqn_base.cpp similarity index 92% rename from dqn_core.cpp rename to dqn_base.cpp index 45f1917..3006331 100644 --- a/dqn_core.cpp +++ b/dqn_base.cpp @@ -7,13 +7,13 @@ Dqn_CPUIDRegisters Dqn_CPUID(int function_id) { Dqn_CPUIDRegisters result = {}; -#if defined(DQN_COMPILER_W32_MSVC) || defined(DQN_COMPILER_W32_CLANG) + #if defined(DQN_COMPILER_W32_MSVC) __cpuid(DQN_CAST(int *)result.array, function_id); -#elif defined(DQN_COMPILER_GCC) || defined(DQN_COMPILER_CLANG) + #elif defined(DQN_COMPILER_GCC) || defined(DQN_COMPILER_CLANG) __get_cpuid(function_id, &result.array[0] /*eax*/, &result.array[1] /*ebx*/, &result.array[2] /*ecx*/ , &result.array[3] /*edx*/); -#else + #else #error "Compiler not supported" -#endif + #endif return result; } #endif // !defined(DQN_OS_ARM64) diff --git a/dqn_core.h b/dqn_base.h similarity index 77% rename from dqn_core.h rename to dqn_base.h index dceec84..2b150a3 100644 --- a/dqn_core.h +++ b/dqn_base.h @@ -1,20 +1,9 @@ -// NOTE: Table Of Contents ========================================================================= -// Index | Disable #define | Description -// ================================================================================================= -// [$CMAC] Compiler macros | | Macros for the compiler -// [$MACR] Macros | | Define macros used in the library -// [$TYPE] Typedefs | | Typedefs used in the library -// [$GSTR] Global Structs | | Forward declare useful structs -// [$INTR] Intrinsics | | Atomics, cpuid, ticket mutex -// [$TMUT] Dqn_TicketMutex | | Userland mutex via spinlocking atomics -// [$CALL] Dqn_CallSite | | Source code location/tracing -// ===================+=================+=========================================================== - // NOTE: [$CMAC] Compiler macros =================================================================== // NOTE: Warning! Order is important here, clang-cl on Windows defines _MSC_VER #if defined(_MSC_VER) #if defined(__clang__) #define DQN_COMPILER_W32_CLANG + #define DQN_COMPILER_CLANG #else #define DQN_COMPILER_W32_MSVC #endif @@ -24,8 +13,8 @@ #define DQN_COMPILER_GCC #endif -// Declare struct literals that work in both C and C++ because the syntax is -// different between languages. +// NOTE: Declare struct literals that work in both C and C++ because the syntax +// is different between languages. #if 0 struct Foo { int a; } struct Foo foo = DQN_LITERAL(Foo){32}; // Works on both C and C++ @@ -63,9 +52,10 @@ #define DQN_FOR_UINDEX(index, size) for (Dqn_usize index = 0; index < size; index++) #define DQN_FOR_IINDEX(index, size) for (Dqn_isize index = 0; index < size; index++) -#define Dqn_PowerOfTwoAlign(value, power_of_two) (((value) + ((power_of_two) - 1)) & ~((power_of_two) - 1)) -#define Dqn_IsPowerOfTwo(value) (((value) & (value - 1)) == 0) -#define Dqn_IsPowerOfTwoAligned(value, power_of_two) (((value) & (power_of_two - 1)) == 0) +#define Dqn_AlignUpPowerOfTwo(value, pot) (((uintptr_t)(value) + ((uintptr_t)(pot) - 1)) & ~((uintptr_t)(pot) - 1)) +#define Dqn_AlignDownPowerOfTwo(value, pot) ((uintptr_t)(value) & ((uintptr_t)(pot) - 1)) +#define Dqn_IsPowerOfTwo(value) ((((uintptr_t)(value)) & (((uintptr_t)(value)) - 1)) == 0) +#define Dqn_IsPowerOfTwoAligned(value, pot) ((((uintptr_t)value) & (((uintptr_t)pot) - 1)) == 0) // NOTE: Alloc Macros ============================================================================== #if !defined(DQN_ALLOC) @@ -76,7 +66,7 @@ #define DQN_DEALLOC(ptr, size) Dqn_VMem_Release(ptr, size) #endif -// NOTE: String.h Dependnecies ===================================================================== +// NOTE: String.h Dependencies ===================================================================== #if !defined(DQN_MEMCPY) || !defined(DQN_MEMSET) || !defined(DQN_MEMCMP) || !defined(DQN_MEMMOVE) #include #if !defined(DQN_MEMCPY) @@ -86,14 +76,14 @@ #define DQN_MEMSET(dest, value, count) memset(dest, value, count) #endif #if !defined(DQN_MEMCMP) - #define DQN_MEMCMP(ptr1, ptr2, num) memcmp(ptr1, ptr2, num) + #define DQN_MEMCMP(lhs, rhs, count) memcmp(lhs, rhs, count) #endif #if !defined(DQN_MEMMOVE) - #define DQN_MEMMOVE(dest, src, num) memmove(dest, src, num) + #define DQN_MEMMOVE(dest, src, count) memmove(dest, src, count) #endif #endif -// NOTE: Math.h Dependnecies ======================================================================= +// NOTE: Math.h Dependencies ======================================================================= #if !defined(DQN_SQRTF) || !defined(DQN_SINF) || !defined(DQN_COSF) || !defined(DQN_TANF) #include #define DQN_SQRTF(val) sqrtf(val) @@ -115,8 +105,8 @@ #define DQN_RADIAN_TO_DEGREE(radians) ((radians) * (180.f * DQN_PI)) #define DQN_ABS(val) (((val) < 0) ? (-(val)) : (val)) -#define DQN_MAX(a, b) ((a > b) ? (a) : (b)) -#define DQN_MIN(a, b) ((a < b) ? (a) : (b)) +#define DQN_MAX(a, b) (((a) > (b)) ? (a) : (b)) +#define DQN_MIN(a, b) (((a) < (b)) ? (a) : (b)) #define DQN_CLAMP(val, lo, hi) DQN_MAX(DQN_MIN(val, hi), lo) #define DQN_SQUARED(val) ((val) * (val)) @@ -168,31 +158,12 @@ #define DQN_DAYS_TO_S(val) (DQN_HOURS_TO_S(val) * 24ULL) #define DQN_YEARS_TO_S(val) (DQN_DAYS_TO_S(val) * 365ULL) -// NOTE: Debug Macros ============================================================================== -#if !defined(DQN_DEBUG_BREAK) - #if defined(NDEBUG) - #define DQN_DEBUG_BREAK - #else - #if defined(DQN_COMPILER_W32_MSVC) || defined(DQN_COMPILER_W32_CLANG) - #define DQN_DEBUG_BREAK __debugbreak() - #elif defined(DQN_COMPILER_CLANG) || defined(DQN_COMPILER_GCC) - #include - #define DQN_DEBUG_BREAK raise(SIGTRAP) - #elif - #error "Unhandled compiler" - #endif - #endif -#endif - -#if !defined(DQN_MEMSET_BYTE) - #define DQN_MEMSET_BYTE 0 -#endif - // NOTE: Assert Macros ============================================================================= #define DQN_HARD_ASSERT(expr) DQN_HARD_ASSERTF(expr, "") #define DQN_HARD_ASSERTF(expr, fmt, ...) \ if (!(expr)) { \ Dqn_Log_ErrorF("Hard assert triggered " #expr ". " fmt, ##__VA_ARGS__); \ + DQN_DUMP_STACK_TRACE; \ DQN_DEBUG_BREAK; \ } @@ -204,6 +175,7 @@ #define DQN_ASSERTF(expr, fmt, ...) \ if (!(expr)) { \ Dqn_Log_ErrorF("Assert triggered " #expr ". " fmt, ##__VA_ARGS__); \ + DQN_DUMP_STACK_TRACE; \ DQN_DEBUG_BREAK; \ } #endif @@ -243,6 +215,7 @@ #endif // NOTE: Defer Macro =============================================================================== +#if defined(__cplusplus) #if 0 #include int main() @@ -273,13 +246,14 @@ struct Dqn_DeferHelper #define DQN_UNIQUE_NAME(prefix) DQN_TOKEN_COMBINE(prefix, __LINE__) #define DQN_DEFER const auto DQN_UNIQUE_NAME(defer_lambda_) = Dqn_DeferHelper() + [&]() +#endif // defined(__cplusplus) #define DQN_DEFER_LOOP(begin, end) \ for (bool DQN_UNIQUE_NAME(once) = (begin, true); \ DQN_UNIQUE_NAME(once); \ end, DQN_UNIQUE_NAME(once) = false) -// NOTE: [$TYPE] Typedefs ========================================================================== +// NOTE: [$TYPE] Types ============================================================================= typedef intptr_t Dqn_isize; typedef uintptr_t Dqn_usize; typedef intptr_t Dqn_isize; @@ -292,25 +266,40 @@ typedef int32_t Dqn_b32; #define DQN_ISIZE_MAX INTPTR_MAX #define DQN_ISIZE_MIN INTPTR_MIN -// NOTE [$GSTR] Global Structs ===================================================================== +typedef enum Dqn_ZeroMem +{ + Dqn_ZeroMem_No, // Memory can be handed out without zero-ing it out + Dqn_ZeroMem_Yes, // Memory should be zero-ed out before giving to the callee +} +Dqn_ZeroMem; + struct Dqn_String8 { char *data; // The bytes of the string Dqn_usize size; // The number of bytes in the string #if defined(__cplusplus) - char const *begin() const { return data; } // Const begin iterator for range-for loops - char const *end () const { return data + size; } // Const end iterator for range-for loops - char *begin() { return data; } // Begin iterator for range-for loops - char *end () { return data + size; } // End iterator for range-for loops + char const *begin() const { return data; } + char const *end () const { return data + size; } + char *begin() { return data; } + char *end () { return data + size; } #endif }; // NOTE: [$INTR] Intrinsics ======================================================================== -typedef enum Dqn_ZeroMem { - Dqn_ZeroMem_No, ///< Memory can be handed out without zero-ing it out - Dqn_ZeroMem_Yes, ///< Memory should be zero-ed out before giving to the callee -} Dqn_ZeroMem; +// Platform agnostic functions for CPU level instructions like atomics, barriers +// and timestamp counters. +// +// NOTE: API +// @proc Dqn_Atomic_SetValue64, Dqn_Atomic_SetValue32 +// @desc Atomically set the value into the target using an atomic compare and +// swap. +// @param[in,out] target The target pointer to set atomically +// @param[in] value The value to set atomically into the target +// @return The value that was last stored in the target + +// @proc Dqn_CPUID +// Execute 'CPUID' instruction to query the capabilities of the current CPU. // NOTE: Dqn_Atomic_Add/Exchange return the previous value store in the target #if defined(DQN_COMPILER_W32_MSVC) || defined(DQN_COMPILER_W32_CLANG) @@ -321,24 +310,22 @@ typedef enum Dqn_ZeroMem { #define Dqn_Atomic_AddU64(target, value) _InterlockedExchangeAdd64((__int64 volatile *)target, value) #define Dqn_Atomic_SubU32(target, value) Dqn_Atomic_AddU32(DQN_CAST(long volatile *)target, (long)-value) #define Dqn_Atomic_SubU64(target, value) Dqn_Atomic_AddU64(target, (uint64_t)-value) - #define Dqn_CPUClockCycle() __rdtsc() + #define Dqn_CPU_TSC() __rdtsc() #define Dqn_CompilerReadBarrierAndCPUReadFence _ReadBarrier(); _mm_lfence() #define Dqn_CompilerWriteBarrierAndCPUWriteFence _WriteBarrier(); _mm_sfence() #elif defined(DQN_COMPILER_GCC) || defined(DQN_COMPILER_CLANG) #if defined(__ANDROID__) - #else #include #endif - #define Dqn_Atomic_AddU32(target, value) __atomic_fetch_add(target, value, __ATOMIC_ACQ_REL) #define Dqn_Atomic_AddU64(target, value) __atomic_fetch_add(target, value, __ATOMIC_ACQ_REL) #define Dqn_Atomic_SubU32(target, value) __atomic_fetch_sub(target, value, __ATOMIC_ACQ_REL) #define Dqn_Atomic_SubU64(target, value) __atomic_fetch_sub(target, value, __ATOMIC_ACQ_REL) #if defined(DQN_COMPILER_GCC) - #define Dqn_CPUClockCycle() __rdtsc() + #define Dqn_CPU_TSC() __rdtsc() #else - #define Dqn_CPUClockCycle() __builtin_readcyclecounter() + #define Dqn_CPU_TSC() __builtin_readcyclecounter() #endif #define Dqn_CompilerReadBarrierAndCPUReadFence asm volatile("lfence" ::: "memory") #define Dqn_CompilerWriteBarrierAndCPUWriteFence asm volatile("sfence" ::: "memory") @@ -346,10 +333,6 @@ typedef enum Dqn_ZeroMem { #error "Compiler not supported" #endif -/// Atomically set the value into the target using an atomic compare and swap. -/// @param[in,out] target The target pointer to set atomically -/// @param[in] value The value to set atomically into the target -/// @return The value that was last stored in the target DQN_FORCE_INLINE uint64_t Dqn_Atomic_SetValue64(uint64_t volatile *target, uint64_t value) { #if defined(DQN_COMPILER_W32_MSVC) || defined(DQN_COMPILER_W32_CLANG) @@ -366,10 +349,6 @@ DQN_FORCE_INLINE uint64_t Dqn_Atomic_SetValue64(uint64_t volatile *target, uint6 #endif } -/// Atomically set the value into the target using an atomic compare and swap. -/// @param[in,out] target The target pointer to set atomically -/// @param[in] value The value to set atomically into the target -/// @return The value that was last stored in the target DQN_FORCE_INLINE long Dqn_Atomic_SetValue32(long volatile *target, long value) { #if defined(DQN_COMPILER_W32_MSVC) || defined(DQN_COMPILER_W32_CLANG) @@ -392,7 +371,6 @@ struct Dqn_CPUIDRegisters Dqn_uint array[4]; ///< Values from 'CPUID' instruction for each register (EAX, EBX, ECX, EDX) }; -/// Execute 'CPUID' instruction to query the capabilities of the current CPU. Dqn_CPUIDRegisters Dqn_CPUID(int function_id); #endif // DQN_OS_ARM64 @@ -446,12 +424,3 @@ void Dqn_TicketMutex_End (Dqn_TicketMutex *mutex); Dqn_uint Dqn_TicketMutex_MakeTicket (Dqn_TicketMutex *mutex); void Dqn_TicketMutex_BeginTicket(Dqn_TicketMutex const *mutex, Dqn_uint ticket); bool Dqn_TicketMutex_CanLock (Dqn_TicketMutex const *mutex, Dqn_uint ticket); - -// NOTE: [$CALL] Dqn_CallSite ====================================================================== -struct Dqn_CallSite -{ - Dqn_String8 file; - Dqn_String8 function; - unsigned int line; -}; -#define DQN_CALL_SITE Dqn_CallSite{DQN_STRING8(__FILE__), DQN_STRING8(__func__), __LINE__} diff --git a/dqn_containers.h b/dqn_containers.h index 34af4cc..ca94ce9 100644 --- a/dqn_containers.h +++ b/dqn_containers.h @@ -1,12 +1,3 @@ -// NOTE: Table Of Contents ========================================================================= -// Index | Disable #define | Description -// ================================================================================================= -// [$VARR] Dqn_VArray | DQN_NO_VARRAY | Array backed by virtual memory arena -// [$FARR] Dqn_FArray | DQN_NO_FARRAY | Fixed-size arrays -// [$DMAP] Dqn_DSMap | DQN_NO_DSMAP | Hashtable, 70% max load, PoT size, linear probe, chain repair -// [$LIST] Dqn_List | DQN_NO_LIST | Chunked linked lists, append only -// ================================================================================================= - #if !defined(DQN_NO_VARRAY) // NOTE: [$VARR] Dqn_VArray ======================================================================== // An array that is backed by virtual memory by reserving addressing space and @@ -72,10 +63,10 @@ template struct Dqn_VArray { - Dqn_ArenaBlock *block; // Block of memory from the allocator for this array - T *data; // Pointer to the start of the array items in the block of memory - Dqn_usize size; // Number of items currently in the array - Dqn_usize max; // Maximum number of items this array can store + Dqn_MemBlock *block; // Block of memory from the allocator for this array + T *data; // Pointer to the start of the array items in the block of memory + Dqn_usize size; // Number of items currently in the array + Dqn_usize max; // Maximum number of items this array can store T *begin() { return data; } T *end () { return data + size; } @@ -389,30 +380,25 @@ DQN_API template Dqn_List Dqn_List_InitWithArena(Dqn_Arena *aren DQN_API template T * Dqn_List_At (Dqn_List *list, Dqn_usize index, Dqn_ListChunk *at_chunk); DQN_API template bool Dqn_List_Iterate (Dqn_List *list, Dqn_ListIterator *it, Dqn_usize start_index); -// NOTE: Macros ==================================================================================== -#define Dqn_List_Make(list, count) Dqn_List_Make_(DQN_LEAK_TRACE list, count) -#define Dqn_List_Add(list, count) Dqn_List_Add_(DQN_LEAK_TRACE list, count) - -// NOTE: Internal ================================================================================== -DQN_API template T * Dqn_List_Make_ (DQN_LEAK_TRACE_FUNCTION Dqn_List *list, Dqn_usize count); -DQN_API template T * Dqn_List_Add_ (DQN_LEAK_TRACE_FUNCTION Dqn_List *list, Dqn_usize count); +DQN_API template T * Dqn_List_Make (Dqn_List *list, Dqn_usize count); +DQN_API template T * Dqn_List_Add (Dqn_List *list, Dqn_usize count); #endif // !defined(DQN_NO_LIST) #if !defined(DQN_NO_VARRAY) // NOTE: [$VARR] Dqn_VArray ======================================================================== DQN_API template Dqn_VArray Dqn_VArray_InitByteSize(Dqn_Arena *arena, Dqn_usize byte_size) { - Dqn_usize byte_size_64k_aligned = Dqn_PowerOfTwoAlign(byte_size, DQN_VMEM_RESERVE_GRANULARITY); - Dqn_VArray result = {}; - result.block = Dqn_Arena_Grow(arena, byte_size_64k_aligned, 0 /*commit*/, Dqn_ArenaBlockFlags_Private); - result.max = result.block->size / sizeof(T); - result.data = DQN_CAST(T *)Dqn_PowerOfTwoAlign((uintptr_t)result.block->memory, alignof(T)); + Dqn_VArray result = {}; + result.block = Dqn_Arena_Grow(arena, byte_size, 0 /*commit*/, Dqn_MemBlockFlag_ArenaPrivate); + result.data = DQN_CAST(T *)Dqn_MemBlock_Alloc(result.block, /*size*/ 0, alignof(T), Dqn_ZeroMem_No); + result.max = (result.block->size - result.block->used) / sizeof(T); return result; } DQN_API template Dqn_VArray Dqn_VArray_Init(Dqn_Arena *arena, Dqn_usize max) { Dqn_VArray result = Dqn_VArray_InitByteSize(arena, max * sizeof(T)); + DQN_ASSERT(result.max >= max); return result; } @@ -431,7 +417,7 @@ DQN_API template T *Dqn_VArray_Make(Dqn_VArray *array, Dqn_usize return nullptr; // TODO: Use placement new? Why doesn't this work? - T *result = Dqn_Arena_NewArrayWithBlock(array->block, T, count, zero_mem); + T *result = Dqn_MemBlock_NewArray(array->block, T, count, zero_mem); if (result) array->size += count; return result; @@ -960,17 +946,17 @@ template DQN_API Dqn_List Dqn_List_InitWithArena(Dqn_Arena *aren return result; } -template DQN_API T *Dqn_List_Make_(DQN_LEAK_TRACE_FUNCTION Dqn_List *list, Dqn_usize count) +template DQN_API T *Dqn_List_Make(Dqn_List *list, Dqn_usize count) { if (list->chunk_size == 0) list->chunk_size = 128; if (!list->tail || (list->tail->count + count) > list->tail->size) { - auto *tail = (Dqn_ListChunk * )Dqn_Arena_Allocate_(list->arena, sizeof(Dqn_ListChunk), alignof(Dqn_ListChunk), Dqn_ZeroMem_Yes DQN_LEAK_TRACE_ARG); + auto *tail = Dqn_Arena_New(list->arena, Dqn_ListChunk, Dqn_ZeroMem_Yes); if (!tail) return nullptr; Dqn_usize items = DQN_MAX(list->chunk_size, count); - tail->data = DQN_CAST(T * )Dqn_Arena_Allocate_(list->arena, sizeof(T) * items, alignof(T), Dqn_ZeroMem_Yes DQN_LEAK_TRACE_ARG); + tail->data = Dqn_Arena_NewArray(list->arena, T, items, Dqn_ZeroMem_Yes); tail->size = items; if (!tail->data) @@ -993,10 +979,10 @@ template DQN_API T *Dqn_List_Make_(DQN_LEAK_TRACE_FUNCTION Dqn_List return result; } -template DQN_API T *Dqn_List_Add_(DQN_LEAK_TRACE_FUNCTION Dqn_List *list, T const &value) +template DQN_API T *Dqn_List_Add(Dqn_List *list, T const &value) { - T *result = Dqn_List_Make_(list, 1); - *result = value; + T *result = Dqn_List_Make(list, 1); + *result = value; return result; } diff --git a/dqn_debug.cpp b/dqn_debug.cpp new file mode 100644 index 0000000..27a2556 --- /dev/null +++ b/dqn_debug.cpp @@ -0,0 +1,320 @@ +DQN_API Dqn_String8 Dqn_Debug_CleanStackTrace(Dqn_String8 stack_trace) +{ + // NOTE: Remove the stacktrace's library invocations from the stack trace + // which just adds noise to the actual trace we want to look at, e.g: + // + // dqn\b_stacktrace.h(198): b_stacktrace_get + // dqn\b_stacktrace.h(156): b_stacktrace_get_string + + Dqn_String8BinarySplitResult split_line0 = Dqn_String8_BinarySplit(stack_trace, DQN_STRING8("\n")); + Dqn_String8BinarySplitResult split_line1 = Dqn_String8_BinarySplit(split_line0.rhs, DQN_STRING8("\n")); + + Dqn_String8 result = split_line1.rhs; + return result; +} + +#if defined(DQN_LEAK_TRACING) +DQN_API void Dqn_Debug_TrackAlloc_(Dqn_String8 stack_trace, void *ptr, Dqn_usize size, bool leak_permitted) +{ + if (!ptr) + return; + + if (g_dqn_library->alloc_tracking_disabled) + return; + + // NOTE: In this function we can create alloc records and hence it's + // possible for the alloc table to resize. This can cause a nested call + // into the tracking alloc function and dead-lock. We don't + // care about tracking these alloc records for the alloc table itself so we + // disable alloc tracking for the duration of this function. + // TODO(doyle): @robust This is not thread safe. + Dqn_TicketMutex_Begin(&g_dqn_library->alloc_table_mutex); + g_dqn_library->alloc_tracking_disabled = true; + + DQN_DEFER { + g_dqn_library->alloc_tracking_disabled = false; + Dqn_TicketMutex_End(&g_dqn_library->alloc_table_mutex); + }; + + // NOTE: If the entry was not added, we are reusing a pointer that has been freed. + // TODO: Add API for always making the item but exposing a var to indicate if the item was newly created or it already existed. + Dqn_DSMap *alloc_table = &g_dqn_library->alloc_table; + Dqn_DSMapKey key = Dqn_DSMap_KeyU64(alloc_table, DQN_CAST(uintptr_t)ptr); + Dqn_AllocRecord *alloc = Dqn_DSMap_Find(alloc_table, key); + if (alloc) { + if ((alloc->flags & Dqn_AllocRecordFlag_Freed) == 0) { + Dqn_String8 alloc_stack_trace = Dqn_String8_Init(alloc->stack_trace, alloc->stack_trace_size); + Dqn_String8 alloc_clean_stack_trace = Dqn_String8_Slice(alloc_stack_trace, g_dqn_library->stack_trace_offset_to_our_call_stack, alloc_stack_trace.size); + Dqn_String8 clean_stack_trace = Dqn_String8_Slice(stack_trace, g_dqn_library->stack_trace_offset_to_our_call_stack, stack_trace.size); + DQN_HARD_ASSERTF( + alloc->flags & Dqn_AllocRecordFlag_Freed, + "\n\nThis pointer is already in the leak tracker, however it has not " + "been freed yet. This same pointer is being ask to be tracked " + "twice in the allocation table, e.g. one if its previous free " + "calls has not being marked freed with an equivalent call to " + "Dqn_Debug_TrackDealloc()\n" + "\n" + "The pointer (0x%p) originally allocated %_$$zu at:\n" + "\n" + "%.*s" + "\n" + "The pointer is being allocated again at:\n" + "%.*s" + , + ptr, alloc->size, + DQN_STRING_FMT(alloc_clean_stack_trace), + DQN_STRING_FMT(clean_stack_trace)); + } + + // NOTE: Pointer was reused, clean up the prior entry + free(alloc->stack_trace); + free(alloc->freed_stack_trace); + *alloc = {}; + } else { + alloc = Dqn_DSMap_Make(alloc_table, key, /*found*/ nullptr); + } + + alloc->ptr = ptr; + alloc->size = size; + alloc->stack_trace = stack_trace.data; + alloc->stack_trace_size = DQN_CAST(uint16_t)stack_trace.size; + + // TODO(doyle): @robust The global flag is not multi-thread safe + if (leak_permitted || g_dqn_library->alloc_is_allowed_to_leak) + alloc->flags |= Dqn_AllocRecordFlag_LeakPermitted; +} + +DQN_API void Dqn_Debug_TrackDealloc_(Dqn_String8 stack_trace, void *ptr) +{ + if (!ptr || g_dqn_library->alloc_tracking_disabled) + return; + + Dqn_TicketMutex_Begin(&g_dqn_library->alloc_table_mutex); + DQN_DEFER { Dqn_TicketMutex_End(&g_dqn_library->alloc_table_mutex); }; + + Dqn_DSMap *alloc_table = &g_dqn_library->alloc_table; + Dqn_DSMapKey key = Dqn_DSMap_KeyU64(alloc_table, DQN_CAST(uintptr_t)ptr); + Dqn_AllocRecord *alloc = Dqn_DSMap_Find(alloc_table, key); + + DQN_HARD_ASSERTF(alloc, "Allocated pointer can not be removed as it does not exist in the " + "allocation table. When this memory was allocated, the pointer was " + "not added to the allocation table [ptr=%p]", + ptr); + + if (alloc->flags & Dqn_AllocRecordFlag_Freed) { + Dqn_String8 alloc_stack_trace = Dqn_String8_Init(alloc->stack_trace, alloc->stack_trace_size); + Dqn_String8 alloc_clean_stack_trace = Dqn_String8_Slice(alloc_stack_trace, g_dqn_library->stack_trace_offset_to_our_call_stack, alloc_stack_trace.size); + + Dqn_String8 alloc_freed_stack_trace = Dqn_String8_Init(alloc->freed_stack_trace, alloc->freed_stack_trace_size); + Dqn_String8 alloc_freed_clean_stack_trace = Dqn_String8_Slice(alloc_freed_stack_trace, g_dqn_library->stack_trace_offset_to_our_call_stack, alloc_freed_stack_trace.size); + + Dqn_String8 dealloc_stack_trace = Dqn_String8_Slice(stack_trace, g_dqn_library->stack_trace_offset_to_our_call_stack, stack_trace.size); + + DQN_HARD_ASSERTF((alloc->flags & Dqn_AllocRecordFlag_Freed) == 0, + "\n\nDouble free detected, pointer to free was already marked " + " as freed. Either the pointer was reallocated but not" + " traced, or, the pointer was freed twice.\n" + "\n" + "The pointer (0x%p) originally allocated %_$$zu at:\n" + "\n" + "%.*s" + "\n" + "The pointer was freed at:\n" + "\n" + "%.*s" + "\n" + "The pointer is being freed again at:\n" + "%.*s" + , + ptr, alloc->freed_size, + DQN_STRING_FMT(alloc_clean_stack_trace), + DQN_STRING_FMT(alloc_freed_clean_stack_trace), + DQN_STRING_FMT(dealloc_stack_trace)); + } + + alloc->flags |= Dqn_AllocRecordFlag_Freed; + alloc->freed_size = alloc->size; + alloc->freed_stack_trace = stack_trace.data; + alloc->freed_stack_trace_size = DQN_CAST(uint16_t)stack_trace.size; +} + +DQN_API void Dqn_Debug_DumpLeaks() +{ + uint64_t leak_count = 0; + uint64_t leaked_bytes = 0; + for (Dqn_usize index = 1; index < g_dqn_library->alloc_table.occupied; index++) { + Dqn_DSMapSlot *slot = g_dqn_library->alloc_table.slots + index; + Dqn_AllocRecord *alloc = &slot->value; + bool alloc_leaked = (alloc->flags & Dqn_AllocRecordFlag_Freed) == 0; + bool leak_permitted = (alloc->flags & Dqn_AllocRecordFlag_LeakPermitted); + if (alloc_leaked && !leak_permitted) { + leaked_bytes += alloc->size; + leak_count++; + + Dqn_String8 stack_trace = Dqn_String8_Init(alloc->stack_trace, alloc->stack_trace_size); + Dqn_String8 clean_stack_trace = Dqn_String8_Slice(stack_trace, g_dqn_library->stack_trace_offset_to_our_call_stack, stack_trace.size); + Dqn_Log_WarningF("Pointer (0x%p) leaked %_$$zu at:\n" + "%.*s", + alloc->ptr, alloc->size, + DQN_STRING_FMT(clean_stack_trace)); + } + } + + if (leak_count) { + Dqn_Log_WarningF("There were %I64u leaked allocations totalling %_$$I64u", leak_count, leaked_bytes); + } +} +#endif /// defined(DQN_LEAK_TRACING) + +// NOTE: [$LLOG] Dqn_Log ========================================================================== +DQN_API Dqn_String8 Dqn_Log_MakeString(Dqn_Allocator allocator, + bool colour, + Dqn_String8 type, + int log_type, + Dqn_CallSite call_site, + char const *fmt, + va_list args) +{ + Dqn_usize header_size_no_ansi_codes = 0; + Dqn_String8 header = {}; + { + DQN_LOCAL_PERSIST Dqn_usize max_type_length = 0; + max_type_length = DQN_MAX(max_type_length, type.size); + int type_padding = DQN_CAST(int)(max_type_length - type.size); + + Dqn_String8 colour_esc = {}; + Dqn_String8 bold_esc = {}; + Dqn_String8 reset_esc = {}; + if (colour) { + bold_esc = Dqn_Print_ESCBoldString; + reset_esc = Dqn_Print_ESCResetString; + switch (log_type) { + case Dqn_LogType_Debug: break; + case Dqn_LogType_Info: colour_esc = Dqn_Print_ESCColourFgU32String(Dqn_LogTypeColourU32_Info); break; + case Dqn_LogType_Warning: colour_esc = Dqn_Print_ESCColourFgU32String(Dqn_LogTypeColourU32_Warning); break; + case Dqn_LogType_Error: colour_esc = Dqn_Print_ESCColourFgU32String(Dqn_LogTypeColourU32_Error); break; + } + } + + Dqn_String8 file_name = Dqn_String8_FileNameFromPath(call_site.file); + Dqn_DateHMSTimeString const time = Dqn_Date_HMSLocalTimeStringNow(); + header = Dqn_String8_InitF(allocator, + "%.*s " // date + "%.*s " // hms + "%.*s" // colour + "%.*s" // bold + "%.*s" // type + "%*s" // type padding + "%.*s" // reset + " %.*s" // file name + ":%05u ", // line number + time.date_size - 2, time.date + 2, + time.hms_size, time.hms, + colour_esc.size, colour_esc.data, + bold_esc.size, bold_esc.data, + type.size, type.data, + type_padding, "", + reset_esc.size, reset_esc.data, + file_name.size, file_name.data, + call_site.line); + header_size_no_ansi_codes = header.size - colour_esc.size - Dqn_Print_ESCResetString.size; + } + + // NOTE: Header padding + // ========================================================================= + Dqn_usize header_padding = 0; + { + DQN_LOCAL_PERSIST Dqn_usize max_header_length = 0; + max_header_length = DQN_MAX(max_header_length, header_size_no_ansi_codes); + header_padding = max_header_length - header_size_no_ansi_codes; + } + + // NOTE: Construct final log + // ========================================================================= + Dqn_String8 user_msg = Dqn_String8_InitFV(allocator, fmt, args); + Dqn_String8 result = Dqn_String8_Allocate(allocator, header.size + header_padding + user_msg.size, Dqn_ZeroMem_No); + DQN_MEMCPY(result.data, header.data, header.size); + DQN_MEMSET(result.data + header.size, ' ', header_padding); + DQN_MEMCPY(result.data + header.size + header_padding, user_msg.data, user_msg.size); + return result; +} + +DQN_FILE_SCOPE void Dqn_Log_FVDefault_(Dqn_String8 type, int log_type, void *user_data, Dqn_CallSite call_site, char const *fmt, va_list args) +{ + (void)log_type; + (void)user_data; + + // NOTE: Open log file for appending if requested + // ========================================================================= + Dqn_TicketMutex_Begin(&g_dqn_library->log_file_mutex); + if (g_dqn_library->log_to_file && !g_dqn_library->log_file) { + Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); + #if (defined(DQN_OS_WIN32) && !defined(DQN_NO_WIN)) || !defined(DQN_OS_WIN32) + Dqn_String8 exe_dir = Dqn_OS_EXEDir(scratch.allocator); + #else + Dqn_String8 exe_dir = DQN_STRING8("."); + #endif + Dqn_String8 log_file = Dqn_String8_InitF(scratch.allocator, "%.*s/dqn.log", DQN_STRING_FMT(exe_dir)); + fopen_s(DQN_CAST(FILE **)&g_dqn_library->log_file, log_file.data, "a"); + } + Dqn_TicketMutex_End(&g_dqn_library->log_file_mutex); + + // NOTE: Generate the log header + // ========================================================================= + Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); + Dqn_String8 log_line = Dqn_Log_MakeString(scratch.allocator, + !g_dqn_library->log_no_colour, + type, + log_type, + call_site, + fmt, + args); + + // NOTE: Print log + // ========================================================================= + Dqn_Print_StdLn(Dqn_PrintStd_Out, log_line); + + Dqn_TicketMutex_Begin(&g_dqn_library->log_file_mutex); + if (g_dqn_library->log_to_file) { + fprintf(DQN_CAST(FILE *)g_dqn_library->log_file, "%.*s\n", DQN_STRING_FMT(log_line)); + } + Dqn_TicketMutex_End(&g_dqn_library->log_file_mutex); +} + +DQN_API void Dqn_Log_FVCallSite(Dqn_String8 type, Dqn_CallSite call_site, char const *fmt, va_list args) +{ + Dqn_LogProc *logging_function = g_dqn_library->log_callback ? g_dqn_library->log_callback : Dqn_Log_FVDefault_; + logging_function(type, -1 /*log_type*/, g_dqn_library->log_user_data, call_site, fmt, args); +} + +DQN_API void Dqn_Log_FCallSite(Dqn_String8 type, Dqn_CallSite call_site, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + Dqn_Log_FVCallSite(type, call_site, fmt, args); + va_end(args); +} + +DQN_API void Dqn_Log_TypeFVCallSite(Dqn_LogType type, Dqn_CallSite call_site, char const *fmt, va_list args) +{ + Dqn_String8 type_string = DQN_STRING8("DQN-BAD-LOG-TYPE"); + switch (type) { + case Dqn_LogType_Error: type_string = DQN_STRING8("ERROR"); break; + case Dqn_LogType_Info: type_string = DQN_STRING8("INFO"); break; + case Dqn_LogType_Warning: type_string = DQN_STRING8("WARN"); break; + case Dqn_LogType_Debug: type_string = DQN_STRING8("DEBUG"); break; + case Dqn_LogType_Count: type_string = DQN_STRING8("BADXX"); break; + } + + Dqn_LogProc *logging_function = g_dqn_library->log_callback ? g_dqn_library->log_callback : Dqn_Log_FVDefault_; + logging_function(type_string, type /*log_type*/, g_dqn_library->log_user_data, call_site, fmt, args); +} + +DQN_API void Dqn_Log_TypeFCallSite(Dqn_LogType type, Dqn_CallSite call_site, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + Dqn_Log_TypeFVCallSite(type, call_site, fmt, args); + va_end(args); +} + diff --git a/dqn_debug.h b/dqn_debug.h new file mode 100644 index 0000000..0705029 --- /dev/null +++ b/dqn_debug.h @@ -0,0 +1,127 @@ +// NOTE: Debug Macros ============================================================================== +#if !defined(DQN_DEBUG_BREAK) + #if defined(NDEBUG) + #define DQN_DEBUG_BREAK + #else + #if defined(DQN_COMPILER_W32_MSVC) || defined(DQN_COMPILER_W32_CLANG) + #define DQN_DEBUG_BREAK __debugbreak() + #elif defined(DQN_COMPILER_CLANG) || defined(DQN_COMPILER_GCC) + #include + #define DQN_DEBUG_BREAK raise(SIGTRAP) + #elif + #error "Unhandled compiler" + #endif + #endif +#endif + +#if !defined(DQN_MEMSET_BYTE) + #define DQN_MEMSET_BYTE 0 +#endif + +#if !defined(DQN_DUMP_STACK_TRACE) + #define DQN_DUMP_STACK_TRACE \ + do { \ + Dqn_String8 stack_trace_ = Dqn_String8_InitCString8(b_stacktrace_get_string()); \ + Dqn_String8 clean_stack_trace_ = Dqn_Debug_CleanStackTrace(stack_trace_); \ + Dqn_Print_StdF(Dqn_PrintStd_Err, "%.*s", DQN_STRING_FMT(clean_stack_trace_)); \ + free(stack_trace_.data); \ + } while (0) +#endif + +// NOTE: [$CALL] Dqn_CallSite ====================================================================== +struct Dqn_CallSite +{ + Dqn_String8 file; + Dqn_String8 function; + unsigned int line; +}; +#define DQN_CALL_SITE Dqn_CallSite{DQN_STRING8(__FILE__), DQN_STRING8(__func__), __LINE__} + +// NOTE: [$DEBG] Dqn_Debug ========================================================================= +enum Dqn_AllocRecordFlag +{ + Dqn_AllocRecordFlag_Freed = 1 << 0, + Dqn_AllocRecordFlag_LeakPermitted = 1 << 1, +}; + +struct Dqn_AllocRecord +{ + void *ptr; // Pointer to the allocation being tracked + Dqn_usize size; // Size of the allocation + Dqn_usize freed_size; // Store the size of the allocation when it is freed + char *stack_trace; // Stack trace at the point of allocation + char *freed_stack_trace; // Stack trace of where the allocation was freed + uint16_t stack_trace_size; // Size of the `stack_trace` + uint16_t freed_stack_trace_size; // Size of `freed_stack_trace` + uint16_t flags; // Bit flags from `Dqn_AllocRecordFlag` + char padding[2]; +}; +static_assert(sizeof(Dqn_AllocRecord) == 48, + "We aim to keep the allocation record as light as possible as " + "memory tracking can get expensive. Enforce that there is no " + "unexpected padding."); + +DQN_API Dqn_String8 Dqn_Debug_CleanStackTrace(Dqn_String8 stack_trace); + +#if defined(DQN_LEAK_TRACING) +#define Dqn_Debug_TrackAlloc(ptr, size, leak_permitted) Dqn_Debug_TrackAlloc_ (Dqn_String8_InitCString8(b_stacktrace_get_string()), ptr, size, leak_permitted) +#define Dqn_Debug_TrackDealloc(ptr) Dqn_Debug_TrackDealloc_(Dqn_String8_InitCString8(b_stacktrace_get_string()), ptr) + +DQN_API void Dqn_Debug_TrackAlloc_(Dqn_String8 stack_trace, void *ptr, Dqn_usize size, bool leak_permitted); +DQN_API void Dqn_Debug_TrackDealloc_(Dqn_String8 stack_trace, void *ptr); +DQN_API void Dqn_Debug_DumpLeaks(); +#else +#define Dqn_Debug_TrackAlloc(...) +#define Dqn_Debug_TrackDealloc(...) +#define Dqn_Debug_DumpLeaks(...) +#endif + +// NOTE: [$LLOG] Dqn_Log ========================================================================== +// NOTE: API +// @proc Dqn_LogProc +// @desc The logging procedure of the library. Users can override the default +// logging function by setting the logging function pointer in Dqn_Library. +// This function will be invoked every time a log is recorded using the +// following functions. +// +// @param[in] log_type This value is one of the Dqn_LogType values if the log +// was generated from one of the default categories. -1 if the log is not from +// one of the default categories. + +enum Dqn_LogType +{ + Dqn_LogType_Debug, + Dqn_LogType_Info, + Dqn_LogType_Warning, + Dqn_LogType_Error, + Dqn_LogType_Count, +}; + +/// RGBA +#define Dqn_LogTypeColourU32_Info 0x00'87'ff'ff // Blue +#define Dqn_LogTypeColourU32_Warning 0xff'ff'00'ff // Yellow +#define Dqn_LogTypeColourU32_Error 0xff'00'00'ff // Red + +typedef void Dqn_LogProc(Dqn_String8 type, int log_type, void *user_data, Dqn_CallSite call_site, char const *fmt, va_list va); + +#define Dqn_Log_DebugF(fmt, ...) Dqn_Log_TypeFCallSite(Dqn_LogType_Debug, DQN_CALL_SITE, fmt, ## __VA_ARGS__) +#define Dqn_Log_InfoF(fmt, ...) Dqn_Log_TypeFCallSite(Dqn_LogType_Info, DQN_CALL_SITE, fmt, ## __VA_ARGS__) +#define Dqn_Log_WarningF(fmt, ...) Dqn_Log_TypeFCallSite(Dqn_LogType_Warning, DQN_CALL_SITE, fmt, ## __VA_ARGS__) +#define Dqn_Log_ErrorF(fmt, ...) Dqn_Log_TypeFCallSite(Dqn_LogType_Error, DQN_CALL_SITE, fmt, ## __VA_ARGS__) + +#define Dqn_Log_DebugFV(fmt, args) Dqn_Log_TypeFVCallSite(Dqn_LogType_Debug, DQN_CALL_SITE, fmt, args) +#define Dqn_Log_InfoFV(fmt, args) Dqn_Log_TypeFVCallSite(Dqn_LogType_Info, DQN_CALL_SITE, fmt, args) +#define Dqn_Log_WarningFV(fmt, args) Dqn_Log_TypeFVCallSite(Dqn_LogType_Warning, DQN_CALL_SITE, fmt, args) +#define Dqn_Log_ErrorFV(fmt, args) Dqn_Log_TypeFVCallSite(Dqn_LogType_Error, DQN_CALL_SITE, fmt, args) + +#define Dqn_Log_TypeFV(type, fmt, args) Dqn_Log_TypeFVCallSite(type, DQN_CALL_SITE, fmt, args) +#define Dqn_Log_TypeF(type, fmt, ...) Dqn_Log_TypeFCallSite(type, DQN_CALL_SITE, fmt, ## __VA_ARGS__) + +#define Dqn_Log_FV(type, fmt, args) Dqn_Log_FVCallSite(type, DQN_CALL_SITE, fmt, args) +#define Dqn_Log_F(type, fmt, ...) Dqn_Log_FCallSite(type, DQN_CALL_SITE, fmt, ## __VA_ARGS__) + +DQN_API Dqn_String8 Dqn_Log_MakeString (Dqn_Allocator allocator, bool colour, Dqn_String8 type, int log_type, Dqn_CallSite call_site, char const *fmt, va_list args); +DQN_API void Dqn_Log_TypeFVCallSite(Dqn_LogType type, Dqn_CallSite call_site, char const *fmt, va_list va); +DQN_API void Dqn_Log_TypeFCallSite (Dqn_LogType type, Dqn_CallSite call_site, char const *fmt, ...); +DQN_API void Dqn_Log_FVCallSite (Dqn_String8 type, Dqn_CallSite call_site, char const *fmt, va_list va); +DQN_API void Dqn_Log_FCallSite (Dqn_String8 type, Dqn_CallSite call_site, char const *fmt, ...); diff --git a/dqn_external.cpp b/dqn_external.cpp new file mode 100644 index 0000000..9dea54e --- /dev/null +++ b/dqn_external.cpp @@ -0,0 +1,1697 @@ +// NOTE: [$BSTK] b_stacktrace ====================================================================== +#define B_STACKTRACE_IMPL +#include "b_stacktrace.h" + +// NOTE: [$STBS] stb_sprintf ======================================================================= +#if !defined(DQN_STB_SPRINTF_HEADER_ONLY) +#define STB_SPRINTF_IMPLEMENTATION +#ifdef STB_SPRINTF_IMPLEMENTATION + +#define stbsp__uint32 unsigned int +#define stbsp__int32 signed int + +#ifdef _MSC_VER +#define stbsp__uint64 unsigned __int64 +#define stbsp__int64 signed __int64 +#else +#define stbsp__uint64 unsigned long long +#define stbsp__int64 signed long long +#endif +#define stbsp__uint16 unsigned short + +#ifndef stbsp__uintptr +#if defined(__ppc64__) || defined(__powerpc64__) || defined(__aarch64__) || defined(_M_X64) || defined(__x86_64__) || defined(__x86_64) || defined(__s390x__) +#define stbsp__uintptr stbsp__uint64 +#else +#define stbsp__uintptr stbsp__uint32 +#endif +#endif + +#ifndef STB_SPRINTF_MSVC_MODE // used for MSVC2013 and earlier (MSVC2015 matches GCC) +#if defined(_MSC_VER) && (_MSC_VER < 1900) +#define STB_SPRINTF_MSVC_MODE +#endif +#endif + +#ifdef STB_SPRINTF_NOUNALIGNED // define this before inclusion to force stbsp_sprintf to always use aligned accesses +#define STBSP__UNALIGNED(code) +#else +#define STBSP__UNALIGNED(code) code +#endif + +#ifndef STB_SPRINTF_NOFLOAT +// internal float utility functions +static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, char *out, stbsp__int32 *decimal_pos, double value, stbsp__uint32 frac_digits); +static stbsp__int32 stbsp__real_to_parts(stbsp__int64 *bits, stbsp__int32 *expo, double value); +#define STBSP__SPECIAL 0x7000 +#endif + +static char stbsp__period = '.'; +static char stbsp__comma = ','; +static struct +{ + short temp; // force next field to be 2-byte aligned + char pair[201]; +} stbsp__digitpair = +{ + 0, + "00010203040506070809101112131415161718192021222324" + "25262728293031323334353637383940414243444546474849" + "50515253545556575859606162636465666768697071727374" + "75767778798081828384858687888990919293949596979899" +}; + +STBSP__PUBLICDEF void STB_SPRINTF_DECORATE(set_separators)(char pcomma, char pperiod) +{ + stbsp__period = pperiod; + stbsp__comma = pcomma; +} + +#define STBSP__LEFTJUST 1 +#define STBSP__LEADINGPLUS 2 +#define STBSP__LEADINGSPACE 4 +#define STBSP__LEADING_0X 8 +#define STBSP__LEADINGZERO 16 +#define STBSP__INTMAX 32 +#define STBSP__TRIPLET_COMMA 64 +#define STBSP__NEGATIVE 128 +#define STBSP__METRIC_SUFFIX 256 +#define STBSP__HALFWIDTH 512 +#define STBSP__METRIC_NOSPACE 1024 +#define STBSP__METRIC_1024 2048 +#define STBSP__METRIC_JEDEC 4096 + +static void stbsp__lead_sign(stbsp__uint32 fl, char *sign) +{ + sign[0] = 0; + if (fl & STBSP__NEGATIVE) { + sign[0] = 1; + sign[1] = '-'; + } else if (fl & STBSP__LEADINGSPACE) { + sign[0] = 1; + sign[1] = ' '; + } else if (fl & STBSP__LEADINGPLUS) { + sign[0] = 1; + sign[1] = '+'; + } +} + +static STBSP__ASAN stbsp__uint32 stbsp__strlen_limited(char const *s, stbsp__uint32 limit) +{ + char const * sn = s; + + // get up to 4-byte alignment + for (;;) { + if (((stbsp__uintptr)sn & 3) == 0) + break; + + if (!limit || *sn == 0) + return (stbsp__uint32)(sn - s); + + ++sn; + --limit; + } + + // scan over 4 bytes at a time to find terminating 0 + // this will intentionally scan up to 3 bytes past the end of buffers, + // but becase it works 4B aligned, it will never cross page boundaries + // (hence the STBSP__ASAN markup; the over-read here is intentional + // and harmless) + while (limit >= 4) { + stbsp__uint32 v = *(stbsp__uint32 *)sn; + // bit hack to find if there's a 0 byte in there + if ((v - 0x01010101) & (~v) & 0x80808080UL) + break; + + sn += 4; + limit -= 4; + } + + // handle the last few characters to find actual size + while (limit && *sn) { + ++sn; + --limit; + } + + return (stbsp__uint32)(sn - s); +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va) +{ + static char hex[] = "0123456789abcdefxp"; + static char hexu[] = "0123456789ABCDEFXP"; + char *bf; + char const *f; + int tlen = 0; + + bf = buf; + f = fmt; + for (;;) { + stbsp__int32 fw, pr, tz; + stbsp__uint32 fl; + + // macros for the callback buffer stuff + #define stbsp__chk_cb_bufL(bytes) \ + { \ + int len = (int)(bf - buf); \ + if ((len + (bytes)) >= STB_SPRINTF_MIN) { \ + tlen += len; \ + if (0 == (bf = buf = callback(buf, user, len))) \ + goto done; \ + } \ + } + #define stbsp__chk_cb_buf(bytes) \ + { \ + if (callback) { \ + stbsp__chk_cb_bufL(bytes); \ + } \ + } + #define stbsp__flush_cb() \ + { \ + stbsp__chk_cb_bufL(STB_SPRINTF_MIN - 1); \ + } // flush if there is even one byte in the buffer + #define stbsp__cb_buf_clamp(cl, v) \ + cl = v; \ + if (callback) { \ + int lg = STB_SPRINTF_MIN - (int)(bf - buf); \ + if (cl > lg) \ + cl = lg; \ + } + + // fast copy everything up to the next % (or end of string) + for (;;) { + while (((stbsp__uintptr)f) & 3) { + schk1: + if (f[0] == '%') + goto scandd; + schk2: + if (f[0] == 0) + goto endfmt; + stbsp__chk_cb_buf(1); + *bf++ = f[0]; + ++f; + } + for (;;) { + // Check if the next 4 bytes contain %(0x25) or end of string. + // Using the 'hasless' trick: + // https://graphics.stanford.edu/~seander/bithacks.html#HasLessInWord + stbsp__uint32 v, c; + v = *(stbsp__uint32 *)f; + c = (~v) & 0x80808080; + if (((v ^ 0x25252525) - 0x01010101) & c) + goto schk1; + if ((v - 0x01010101) & c) + goto schk2; + if (callback) + if ((STB_SPRINTF_MIN - (int)(bf - buf)) < 4) + goto schk1; + #ifdef STB_SPRINTF_NOUNALIGNED + if(((stbsp__uintptr)bf) & 3) { + bf[0] = f[0]; + bf[1] = f[1]; + bf[2] = f[2]; + bf[3] = f[3]; + } else + #endif + { + *(stbsp__uint32 *)bf = v; + } + bf += 4; + f += 4; + } + } + scandd: + + ++f; + + // ok, we have a percent, read the modifiers first + fw = 0; + pr = -1; + fl = 0; + tz = 0; + + // flags + for (;;) { + switch (f[0]) { + // if we have left justify + case '-': + fl |= STBSP__LEFTJUST; + ++f; + continue; + // if we have leading plus + case '+': + fl |= STBSP__LEADINGPLUS; + ++f; + continue; + // if we have leading space + case ' ': + fl |= STBSP__LEADINGSPACE; + ++f; + continue; + // if we have leading 0x + case '#': + fl |= STBSP__LEADING_0X; + ++f; + continue; + // if we have thousand commas + case '\'': + fl |= STBSP__TRIPLET_COMMA; + ++f; + continue; + // if we have kilo marker (none->kilo->kibi->jedec) + case '$': + if (fl & STBSP__METRIC_SUFFIX) { + if (fl & STBSP__METRIC_1024) { + fl |= STBSP__METRIC_JEDEC; + } else { + fl |= STBSP__METRIC_1024; + } + } else { + fl |= STBSP__METRIC_SUFFIX; + } + ++f; + continue; + // if we don't want space between metric suffix and number + case '_': + fl |= STBSP__METRIC_NOSPACE; + ++f; + continue; + // if we have leading zero + case '0': + fl |= STBSP__LEADINGZERO; + ++f; + goto flags_done; + default: goto flags_done; + } + } + flags_done: + + // get the field width + if (f[0] == '*') { + fw = va_arg(va, stbsp__uint32); + ++f; + } else { + while ((f[0] >= '0') && (f[0] <= '9')) { + fw = fw * 10 + f[0] - '0'; + f++; + } + } + // get the precision + if (f[0] == '.') { + ++f; + if (f[0] == '*') { + pr = va_arg(va, stbsp__uint32); + ++f; + } else { + pr = 0; + while ((f[0] >= '0') && (f[0] <= '9')) { + pr = pr * 10 + f[0] - '0'; + f++; + } + } + } + + // handle integer size overrides + switch (f[0]) { + // are we halfwidth? + case 'h': + fl |= STBSP__HALFWIDTH; + ++f; + if (f[0] == 'h') + ++f; // QUARTERWIDTH + break; + // are we 64-bit (unix style) + case 'l': + fl |= ((sizeof(long) == 8) ? STBSP__INTMAX : 0); + ++f; + if (f[0] == 'l') { + fl |= STBSP__INTMAX; + ++f; + } + break; + // are we 64-bit on intmax? (c99) + case 'j': + fl |= (sizeof(size_t) == 8) ? STBSP__INTMAX : 0; + ++f; + break; + // are we 64-bit on size_t or ptrdiff_t? (c99) + case 'z': + fl |= (sizeof(ptrdiff_t) == 8) ? STBSP__INTMAX : 0; + ++f; + break; + case 't': + fl |= (sizeof(ptrdiff_t) == 8) ? STBSP__INTMAX : 0; + ++f; + break; + // are we 64-bit (msft style) + case 'I': + if ((f[1] == '6') && (f[2] == '4')) { + fl |= STBSP__INTMAX; + f += 3; + } else if ((f[1] == '3') && (f[2] == '2')) { + f += 3; + } else { + fl |= ((sizeof(void *) == 8) ? STBSP__INTMAX : 0); + ++f; + } + break; + default: break; + } + + // handle each replacement + switch (f[0]) { + #define STBSP__NUMSZ 512 // big enough for e308 (with commas) or e-307 + char num[STBSP__NUMSZ]; + char lead[8]; + char tail[8]; + char *s; + char const *h; + stbsp__uint32 l, n, cs; + stbsp__uint64 n64; +#ifndef STB_SPRINTF_NOFLOAT + double fv; +#endif + stbsp__int32 dp; + char const *sn; + + case 's': + // get the string + s = va_arg(va, char *); + if (s == 0) + s = (char *)"null"; + // get the length, limited to desired precision + // always limit to ~0u chars since our counts are 32b + l = stbsp__strlen_limited(s, (pr >= 0) ? pr : ~0u); + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + // copy the string in + goto scopy; + + case 'c': // char + // get the character + s = num + STBSP__NUMSZ - 1; + *s = (char)va_arg(va, int); + l = 1; + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + goto scopy; + + case 'n': // weird write-bytes specifier + { + int *d = va_arg(va, int *); + *d = tlen + (int)(bf - buf); + } break; + +#ifdef STB_SPRINTF_NOFLOAT + case 'A': // float + case 'a': // hex float + case 'G': // float + case 'g': // float + case 'E': // float + case 'e': // float + case 'f': // float + va_arg(va, double); // eat it + s = (char *)"No float"; + l = 8; + lead[0] = 0; + tail[0] = 0; + pr = 0; + cs = 0; + STBSP__NOTUSED(dp); + goto scopy; +#else + case 'A': // hex float + case 'a': // hex float + h = (f[0] == 'A') ? hexu : hex; + fv = va_arg(va, double); + if (pr == -1) + pr = 6; // default is 6 + // read the double into a string + if (stbsp__real_to_parts((stbsp__int64 *)&n64, &dp, fv)) + fl |= STBSP__NEGATIVE; + + s = num + 64; + + stbsp__lead_sign(fl, lead); + + if (dp == -1023) + dp = (n64) ? -1022 : 0; + else + n64 |= (((stbsp__uint64)1) << 52); + n64 <<= (64 - 56); + if (pr < 15) + n64 += ((((stbsp__uint64)8) << 56) >> (pr * 4)); +// add leading chars + +#ifdef STB_SPRINTF_MSVC_MODE + *s++ = '0'; + *s++ = 'x'; +#else + lead[1 + lead[0]] = '0'; + lead[2 + lead[0]] = 'x'; + lead[0] += 2; +#endif + *s++ = h[(n64 >> 60) & 15]; + n64 <<= 4; + if (pr) + *s++ = stbsp__period; + sn = s; + + // print the bits + n = pr; + if (n > 13) + n = 13; + if (pr > (stbsp__int32)n) + tz = pr - n; + pr = 0; + while (n--) { + *s++ = h[(n64 >> 60) & 15]; + n64 <<= 4; + } + + // print the expo + tail[1] = h[17]; + if (dp < 0) { + tail[2] = '-'; + dp = -dp; + } else + tail[2] = '+'; + n = (dp >= 1000) ? 6 : ((dp >= 100) ? 5 : ((dp >= 10) ? 4 : 3)); + tail[0] = (char)n; + for (;;) { + tail[n] = '0' + dp % 10; + if (n <= 3) + break; + --n; + dp /= 10; + } + + dp = (int)(s - sn); + l = (int)(s - (num + 64)); + s = num + 64; + cs = 1 + (3 << 24); + goto scopy; + + case 'G': // float + case 'g': // float + h = (f[0] == 'G') ? hexu : hex; + fv = va_arg(va, double); + if (pr == -1) + pr = 6; + else if (pr == 0) + pr = 1; // default is 6 + // read the double into a string + if (stbsp__real_to_str(&sn, &l, num, &dp, fv, (pr - 1) | 0x80000000)) + fl |= STBSP__NEGATIVE; + + // clamp the precision and delete extra zeros after clamp + n = pr; + if (l > (stbsp__uint32)pr) + l = pr; + while ((l > 1) && (pr) && (sn[l - 1] == '0')) { + --pr; + --l; + } + + // should we use %e + if ((dp <= -4) || (dp > (stbsp__int32)n)) { + if (pr > (stbsp__int32)l) + pr = l - 1; + else if (pr) + --pr; // when using %e, there is one digit before the decimal + goto doexpfromg; + } + // this is the insane action to get the pr to match %g semantics for %f + if (dp > 0) { + pr = (dp < (stbsp__int32)l) ? l - dp : 0; + } else { + pr = -dp + ((pr > (stbsp__int32)l) ? (stbsp__int32) l : pr); + } + goto dofloatfromg; + + case 'E': // float + case 'e': // float + h = (f[0] == 'E') ? hexu : hex; + fv = va_arg(va, double); + if (pr == -1) + pr = 6; // default is 6 + // read the double into a string + if (stbsp__real_to_str(&sn, &l, num, &dp, fv, pr | 0x80000000)) + fl |= STBSP__NEGATIVE; + doexpfromg: + tail[0] = 0; + stbsp__lead_sign(fl, lead); + if (dp == STBSP__SPECIAL) { + s = (char *)sn; + cs = 0; + pr = 0; + goto scopy; + } + s = num + 64; + // handle leading chars + *s++ = sn[0]; + + if (pr) + *s++ = stbsp__period; + + // handle after decimal + if ((l - 1) > (stbsp__uint32)pr) + l = pr + 1; + for (n = 1; n < l; n++) + *s++ = sn[n]; + // trailing zeros + tz = pr - (l - 1); + pr = 0; + // dump expo + tail[1] = h[0xe]; + dp -= 1; + if (dp < 0) { + tail[2] = '-'; + dp = -dp; + } else + tail[2] = '+'; +#ifdef STB_SPRINTF_MSVC_MODE + n = 5; +#else + n = (dp >= 100) ? 5 : 4; +#endif + tail[0] = (char)n; + for (;;) { + tail[n] = '0' + dp % 10; + if (n <= 3) + break; + --n; + dp /= 10; + } + cs = 1 + (3 << 24); // how many tens + goto flt_lead; + + case 'f': // float + fv = va_arg(va, double); + doafloat: + // do kilos + if (fl & STBSP__METRIC_SUFFIX) { + double divisor; + divisor = 1000.0f; + if (fl & STBSP__METRIC_1024) + divisor = 1024.0; + while (fl < 0x4000000) { + if ((fv < divisor) && (fv > -divisor)) + break; + fv /= divisor; + fl += 0x1000000; + } + } + if (pr == -1) + pr = 6; // default is 6 + // read the double into a string + if (stbsp__real_to_str(&sn, &l, num, &dp, fv, pr)) + fl |= STBSP__NEGATIVE; + dofloatfromg: + tail[0] = 0; + stbsp__lead_sign(fl, lead); + if (dp == STBSP__SPECIAL) { + s = (char *)sn; + cs = 0; + pr = 0; + goto scopy; + } + s = num + 64; + + // handle the three decimal varieties + if (dp <= 0) { + stbsp__int32 i; + // handle 0.000*000xxxx + *s++ = '0'; + if (pr) + *s++ = stbsp__period; + n = -dp; + if ((stbsp__int32)n > pr) + n = pr; + i = n; + while (i) { + if ((((stbsp__uintptr)s) & 3) == 0) + break; + *s++ = '0'; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)s = 0x30303030; + s += 4; + i -= 4; + } + while (i) { + *s++ = '0'; + --i; + } + if ((stbsp__int32)(l + n) > pr) + l = pr - n; + i = l; + while (i) { + *s++ = *sn++; + --i; + } + tz = pr - (n + l); + cs = 1 + (3 << 24); // how many tens did we write (for commas below) + } else { + cs = (fl & STBSP__TRIPLET_COMMA) ? ((600 - (stbsp__uint32)dp) % 3) : 0; + if ((stbsp__uint32)dp >= l) { + // handle xxxx000*000.0 + n = 0; + for (;;) { + if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { + cs = 0; + *s++ = stbsp__comma; + } else { + *s++ = sn[n]; + ++n; + if (n >= l) + break; + } + } + if (n < (stbsp__uint32)dp) { + n = dp - n; + if ((fl & STBSP__TRIPLET_COMMA) == 0) { + while (n) { + if ((((stbsp__uintptr)s) & 3) == 0) + break; + *s++ = '0'; + --n; + } + while (n >= 4) { + *(stbsp__uint32 *)s = 0x30303030; + s += 4; + n -= 4; + } + } + while (n) { + if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { + cs = 0; + *s++ = stbsp__comma; + } else { + *s++ = '0'; + --n; + } + } + } + cs = (int)(s - (num + 64)) + (3 << 24); // cs is how many tens + if (pr) { + *s++ = stbsp__period; + tz = pr; + } + } else { + // handle xxxxx.xxxx000*000 + n = 0; + for (;;) { + if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { + cs = 0; + *s++ = stbsp__comma; + } else { + *s++ = sn[n]; + ++n; + if (n >= (stbsp__uint32)dp) + break; + } + } + cs = (int)(s - (num + 64)) + (3 << 24); // cs is how many tens + if (pr) + *s++ = stbsp__period; + if ((l - dp) > (stbsp__uint32)pr) + l = pr + dp; + while (n < l) { + *s++ = sn[n]; + ++n; + } + tz = pr - (l - dp); + } + } + pr = 0; + + // handle k,m,g,t + if (fl & STBSP__METRIC_SUFFIX) { + char idx; + idx = 1; + if (fl & STBSP__METRIC_NOSPACE) + idx = 0; + tail[0] = idx; + tail[1] = ' '; + { + if (fl >> 24) { // SI kilo is 'k', JEDEC and SI kibits are 'K'. + if (fl & STBSP__METRIC_1024) + tail[idx + 1] = "_KMGT"[fl >> 24]; + else + tail[idx + 1] = "_kMGT"[fl >> 24]; + idx++; + // If printing kibits and not in jedec, add the 'i'. + if (fl & STBSP__METRIC_1024 && !(fl & STBSP__METRIC_JEDEC)) { + tail[idx + 1] = 'i'; + idx++; + } + tail[0] = idx; + } + } + }; + + flt_lead: + // get the length that we copied + l = (stbsp__uint32)(s - (num + 64)); + s = num + 64; + goto scopy; +#endif + + case 'B': // upper binary + case 'b': // lower binary + h = (f[0] == 'B') ? hexu : hex; + lead[0] = 0; + if (fl & STBSP__LEADING_0X) { + lead[0] = 2; + lead[1] = '0'; + lead[2] = h[0xb]; + } + l = (8 << 4) | (1 << 8); + goto radixnum; + + case 'o': // octal + h = hexu; + lead[0] = 0; + if (fl & STBSP__LEADING_0X) { + lead[0] = 1; + lead[1] = '0'; + } + l = (3 << 4) | (3 << 8); + goto radixnum; + + case 'p': // pointer + fl |= (sizeof(void *) == 8) ? STBSP__INTMAX : 0; + pr = sizeof(void *) * 2; + fl &= ~STBSP__LEADINGZERO; // 'p' only prints the pointer with zeros + // fall through - to X + + case 'X': // upper hex + case 'x': // lower hex + h = (f[0] == 'X') ? hexu : hex; + l = (4 << 4) | (4 << 8); + lead[0] = 0; + if (fl & STBSP__LEADING_0X) { + lead[0] = 2; + lead[1] = '0'; + lead[2] = h[16]; + } + radixnum: + // get the number + if (fl & STBSP__INTMAX) + n64 = va_arg(va, stbsp__uint64); + else + n64 = va_arg(va, stbsp__uint32); + + s = num + STBSP__NUMSZ; + dp = 0; + // clear tail, and clear leading if value is zero + tail[0] = 0; + if (n64 == 0) { + lead[0] = 0; + if (pr == 0) { + l = 0; + cs = 0; + goto scopy; + } + } + // convert to string + for (;;) { + *--s = h[n64 & ((1 << (l >> 8)) - 1)]; + n64 >>= (l >> 8); + if (!((n64) || ((stbsp__int32)((num + STBSP__NUMSZ) - s) < pr))) + break; + if (fl & STBSP__TRIPLET_COMMA) { + ++l; + if ((l & 15) == ((l >> 4) & 15)) { + l &= ~15; + *--s = stbsp__comma; + } + } + }; + // get the tens and the comma pos + cs = (stbsp__uint32)((num + STBSP__NUMSZ) - s) + ((((l >> 4) & 15)) << 24); + // get the length that we copied + l = (stbsp__uint32)((num + STBSP__NUMSZ) - s); + // copy it + goto scopy; + + case 'u': // unsigned + case 'i': + case 'd': // integer + // get the integer and abs it + if (fl & STBSP__INTMAX) { + stbsp__int64 i64 = va_arg(va, stbsp__int64); + n64 = (stbsp__uint64)i64; + if ((f[0] != 'u') && (i64 < 0)) { + n64 = (stbsp__uint64)-i64; + fl |= STBSP__NEGATIVE; + } + } else { + stbsp__int32 i = va_arg(va, stbsp__int32); + n64 = (stbsp__uint32)i; + if ((f[0] != 'u') && (i < 0)) { + n64 = (stbsp__uint32)-i; + fl |= STBSP__NEGATIVE; + } + } + +#ifndef STB_SPRINTF_NOFLOAT + if (fl & STBSP__METRIC_SUFFIX) { + if (n64 < 1024) + pr = 0; + else if (pr == -1) + pr = 1; + fv = (double)(stbsp__int64)n64; + goto doafloat; + } +#endif + + // convert to string + s = num + STBSP__NUMSZ; + l = 0; + + for (;;) { + // do in 32-bit chunks (avoid lots of 64-bit divides even with constant denominators) + char *o = s - 8; + if (n64 >= 100000000) { + n = (stbsp__uint32)(n64 % 100000000); + n64 /= 100000000; + } else { + n = (stbsp__uint32)n64; + n64 = 0; + } + if ((fl & STBSP__TRIPLET_COMMA) == 0) { + do { + s -= 2; + *(stbsp__uint16 *)s = *(stbsp__uint16 *)&stbsp__digitpair.pair[(n % 100) * 2]; + n /= 100; + } while (n); + } + while (n) { + if ((fl & STBSP__TRIPLET_COMMA) && (l++ == 3)) { + l = 0; + *--s = stbsp__comma; + --o; + } else { + *--s = (char)(n % 10) + '0'; + n /= 10; + } + } + if (n64 == 0) { + if ((s[0] == '0') && (s != (num + STBSP__NUMSZ))) + ++s; + break; + } + while (s != o) + if ((fl & STBSP__TRIPLET_COMMA) && (l++ == 3)) { + l = 0; + *--s = stbsp__comma; + --o; + } else { + *--s = '0'; + } + } + + tail[0] = 0; + stbsp__lead_sign(fl, lead); + + // get the length that we copied + l = (stbsp__uint32)((num + STBSP__NUMSZ) - s); + if (l == 0) { + *--s = '0'; + l = 1; + } + cs = l + (3 << 24); + if (pr < 0) + pr = 0; + + scopy: + // get fw=leading/trailing space, pr=leading zeros + if (pr < (stbsp__int32)l) + pr = l; + n = pr + lead[0] + tail[0] + tz; + if (fw < (stbsp__int32)n) + fw = n; + fw -= n; + pr -= l; + + // handle right justify and leading zeros + if ((fl & STBSP__LEFTJUST) == 0) { + if (fl & STBSP__LEADINGZERO) // if leading zeros, everything is in pr + { + pr = (fw > pr) ? fw : pr; + fw = 0; + } else { + fl &= ~STBSP__TRIPLET_COMMA; // if no leading zeros, then no commas + } + } + + // copy the spaces and/or zeros + if (fw + pr) { + stbsp__int32 i; + stbsp__uint32 c; + + // copy leading spaces (or when doing %8.4d stuff) + if ((fl & STBSP__LEFTJUST) == 0) + while (fw > 0) { + stbsp__cb_buf_clamp(i, fw); + fw -= i; + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = ' '; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x20202020; + bf += 4; + i -= 4; + } + while (i) { + *bf++ = ' '; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy leader + sn = lead + 1; + while (lead[0]) { + stbsp__cb_buf_clamp(i, lead[0]); + lead[0] -= (char)i; + while (i) { + *bf++ = *sn++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy leading zeros + c = cs >> 24; + cs &= 0xffffff; + cs = (fl & STBSP__TRIPLET_COMMA) ? ((stbsp__uint32)(c - ((pr + cs) % (c + 1)))) : 0; + while (pr > 0) { + stbsp__cb_buf_clamp(i, pr); + pr -= i; + if ((fl & STBSP__TRIPLET_COMMA) == 0) { + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = '0'; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x30303030; + bf += 4; + i -= 4; + } + } + while (i) { + if ((fl & STBSP__TRIPLET_COMMA) && (cs++ == c)) { + cs = 0; + *bf++ = stbsp__comma; + } else + *bf++ = '0'; + --i; + } + stbsp__chk_cb_buf(1); + } + } + + // copy leader if there is still one + sn = lead + 1; + while (lead[0]) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, lead[0]); + lead[0] -= (char)i; + while (i) { + *bf++ = *sn++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy the string + n = l; + while (n) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, n); + n -= i; + STBSP__UNALIGNED(while (i >= 4) { + *(stbsp__uint32 volatile *)bf = *(stbsp__uint32 volatile *)s; + bf += 4; + s += 4; + i -= 4; + }) + while (i) { + *bf++ = *s++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy trailing zeros + while (tz) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, tz); + tz -= i; + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = '0'; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x30303030; + bf += 4; + i -= 4; + } + while (i) { + *bf++ = '0'; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy tail if there is one + sn = tail + 1; + while (tail[0]) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, tail[0]); + tail[0] -= (char)i; + while (i) { + *bf++ = *sn++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // handle the left justify + if (fl & STBSP__LEFTJUST) + if (fw > 0) { + while (fw) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, fw); + fw -= i; + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = ' '; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x20202020; + bf += 4; + i -= 4; + } + while (i--) + *bf++ = ' '; + stbsp__chk_cb_buf(1); + } + } + break; + + default: // unknown, just copy code + s = num + STBSP__NUMSZ - 1; + *s = f[0]; + l = 1; + fw = fl = 0; + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + goto scopy; + } + ++f; + } +endfmt: + + if (!callback) + *bf = 0; + else + stbsp__flush_cb(); + +done: + return tlen + (int)(bf - buf); +} + +// cleanup +#undef STBSP__LEFTJUST +#undef STBSP__LEADINGPLUS +#undef STBSP__LEADINGSPACE +#undef STBSP__LEADING_0X +#undef STBSP__LEADINGZERO +#undef STBSP__INTMAX +#undef STBSP__TRIPLET_COMMA +#undef STBSP__NEGATIVE +#undef STBSP__METRIC_SUFFIX +#undef STBSP__NUMSZ +#undef stbsp__chk_cb_bufL +#undef stbsp__chk_cb_buf +#undef stbsp__flush_cb +#undef stbsp__cb_buf_clamp + +// ============================================================================ +// wrapper functions + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...) +{ + int result; + va_list va; + va_start(va, fmt); + result = STB_SPRINTF_DECORATE(vsprintfcb)(0, 0, buf, fmt, va); + va_end(va); + return result; +} + +typedef struct stbsp__context { + char *buf; + int count; + int length; + char tmp[STB_SPRINTF_MIN]; +} stbsp__context; + +static char *stbsp__clamp_callback(const char *buf, void *user, int len) +{ + stbsp__context *c = (stbsp__context *)user; + c->length += len; + + if (len > c->count) + len = c->count; + + if (len) { + if (buf != c->buf) { + const char *s, *se; + char *d; + d = c->buf; + s = buf; + se = buf + len; + do { + *d++ = *s++; + } while (s < se); + } + c->buf += len; + c->count -= len; + } + + if (c->count <= 0) + return c->tmp; + return (c->count >= STB_SPRINTF_MIN) ? c->buf : c->tmp; // go direct into buffer if you can +} + +static char * stbsp__count_clamp_callback( const char * buf, void * user, int len ) +{ + stbsp__context * c = (stbsp__context*)user; + (void) sizeof(buf); + + c->length += len; + return c->tmp; // go direct into buffer if you can +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE( vsnprintf )( char * buf, int count, char const * fmt, va_list va ) +{ + stbsp__context c; + + if ( (count == 0) && !buf ) + { + c.length = 0; + + STB_SPRINTF_DECORATE( vsprintfcb )( stbsp__count_clamp_callback, &c, c.tmp, fmt, va ); + } + else + { + int l; + + c.buf = buf; + c.count = count; + c.length = 0; + + STB_SPRINTF_DECORATE( vsprintfcb )( stbsp__clamp_callback, &c, stbsp__clamp_callback(0,&c,0), fmt, va ); + + // zero-terminate + l = (int)( c.buf - buf ); + if ( l >= count ) // should never be greater, only equal (or less) than count + l = count - 1; + buf[l] = 0; + } + + return c.length; +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...) +{ + int result; + va_list va; + va_start(va, fmt); + + result = STB_SPRINTF_DECORATE(vsnprintf)(buf, count, fmt, va); + va_end(va); + + return result; +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va) +{ + return STB_SPRINTF_DECORATE(vsprintfcb)(0, 0, buf, fmt, va); +} + +// ======================================================================= +// low level float utility functions + +#ifndef STB_SPRINTF_NOFLOAT + +// copies d to bits w/ strict aliasing (this compiles to nothing on /Ox) +#define STBSP__COPYFP(dest, src) \ + { \ + int cn; \ + for (cn = 0; cn < 8; cn++) \ + ((char *)&dest)[cn] = ((char *)&src)[cn]; \ + } + +// get float info +static stbsp__int32 stbsp__real_to_parts(stbsp__int64 *bits, stbsp__int32 *expo, double value) +{ + double d; + stbsp__int64 b = 0; + + // load value and round at the frac_digits + d = value; + + STBSP__COPYFP(b, d); + + *bits = b & ((((stbsp__uint64)1) << 52) - 1); + *expo = (stbsp__int32)(((b >> 52) & 2047) - 1023); + + return (stbsp__int32)((stbsp__uint64) b >> 63); +} + +static double const stbsp__bot[23] = { + 1e+000, 1e+001, 1e+002, 1e+003, 1e+004, 1e+005, 1e+006, 1e+007, 1e+008, 1e+009, 1e+010, 1e+011, + 1e+012, 1e+013, 1e+014, 1e+015, 1e+016, 1e+017, 1e+018, 1e+019, 1e+020, 1e+021, 1e+022 +}; +static double const stbsp__negbot[22] = { + 1e-001, 1e-002, 1e-003, 1e-004, 1e-005, 1e-006, 1e-007, 1e-008, 1e-009, 1e-010, 1e-011, + 1e-012, 1e-013, 1e-014, 1e-015, 1e-016, 1e-017, 1e-018, 1e-019, 1e-020, 1e-021, 1e-022 +}; +static double const stbsp__negboterr[22] = { + -5.551115123125783e-018, -2.0816681711721684e-019, -2.0816681711721686e-020, -4.7921736023859299e-021, -8.1803053914031305e-022, 4.5251888174113741e-023, + 4.5251888174113739e-024, -2.0922560830128471e-025, -6.2281591457779853e-026, -3.6432197315497743e-027, 6.0503030718060191e-028, 2.0113352370744385e-029, + -3.0373745563400371e-030, 1.1806906454401013e-032, -7.7705399876661076e-032, 2.0902213275965398e-033, -7.1542424054621921e-034, -7.1542424054621926e-035, + 2.4754073164739869e-036, 5.4846728545790429e-037, 9.2462547772103625e-038, -4.8596774326570872e-039 +}; +static double const stbsp__top[13] = { + 1e+023, 1e+046, 1e+069, 1e+092, 1e+115, 1e+138, 1e+161, 1e+184, 1e+207, 1e+230, 1e+253, 1e+276, 1e+299 +}; +static double const stbsp__negtop[13] = { + 1e-023, 1e-046, 1e-069, 1e-092, 1e-115, 1e-138, 1e-161, 1e-184, 1e-207, 1e-230, 1e-253, 1e-276, 1e-299 +}; +static double const stbsp__toperr[13] = { + 8388608, + 6.8601809640529717e+028, + -7.253143638152921e+052, + -4.3377296974619174e+075, + -1.5559416129466825e+098, + -3.2841562489204913e+121, + -3.7745893248228135e+144, + -1.7356668416969134e+167, + -3.8893577551088374e+190, + -9.9566444326005119e+213, + 6.3641293062232429e+236, + -5.2069140800249813e+259, + -5.2504760255204387e+282 +}; +static double const stbsp__negtoperr[13] = { + 3.9565301985100693e-040, -2.299904345391321e-063, 3.6506201437945798e-086, 1.1875228833981544e-109, + -5.0644902316928607e-132, -6.7156837247865426e-155, -2.812077463003139e-178, -5.7778912386589953e-201, + 7.4997100559334532e-224, -4.6439668915134491e-247, -6.3691100762962136e-270, -9.436808465446358e-293, + 8.0970921678014997e-317 +}; + +#if defined(_MSC_VER) && (_MSC_VER <= 1200) +static stbsp__uint64 const stbsp__powten[20] = { + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + 10000000000, + 100000000000, + 1000000000000, + 10000000000000, + 100000000000000, + 1000000000000000, + 10000000000000000, + 100000000000000000, + 1000000000000000000, + 10000000000000000000U +}; +#define stbsp__tento19th ((stbsp__uint64)1000000000000000000) +#else +static stbsp__uint64 const stbsp__powten[20] = { + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + 10000000000ULL, + 100000000000ULL, + 1000000000000ULL, + 10000000000000ULL, + 100000000000000ULL, + 1000000000000000ULL, + 10000000000000000ULL, + 100000000000000000ULL, + 1000000000000000000ULL, + 10000000000000000000ULL +}; +#define stbsp__tento19th (1000000000000000000ULL) +#endif + +#define stbsp__ddmulthi(oh, ol, xh, yh) \ + { \ + double ahi = 0, alo, bhi = 0, blo; \ + stbsp__int64 bt; \ + oh = xh * yh; \ + STBSP__COPYFP(bt, xh); \ + bt &= ((~(stbsp__uint64)0) << 27); \ + STBSP__COPYFP(ahi, bt); \ + alo = xh - ahi; \ + STBSP__COPYFP(bt, yh); \ + bt &= ((~(stbsp__uint64)0) << 27); \ + STBSP__COPYFP(bhi, bt); \ + blo = yh - bhi; \ + ol = ((ahi * bhi - oh) + ahi * blo + alo * bhi) + alo * blo; \ + } + +#define stbsp__ddtoS64(ob, xh, xl) \ + { \ + double ahi = 0, alo, vh, t; \ + ob = (stbsp__int64)xh; \ + vh = (double)ob; \ + ahi = (xh - vh); \ + t = (ahi - xh); \ + alo = (xh - (ahi - t)) - (vh + t); \ + ob += (stbsp__int64)(ahi + alo + xl); \ + } + +#define stbsp__ddrenorm(oh, ol) \ + { \ + double s; \ + s = oh + ol; \ + ol = ol - (s - oh); \ + oh = s; \ + } + +#define stbsp__ddmultlo(oh, ol, xh, xl, yh, yl) ol = ol + (xh * yl + xl * yh); + +#define stbsp__ddmultlos(oh, ol, xh, yl) ol = ol + (xh * yl); + +static void stbsp__raise_to_power10(double *ohi, double *olo, double d, stbsp__int32 power) // power can be -323 to +350 +{ + double ph, pl; + if ((power >= 0) && (power <= 22)) { + stbsp__ddmulthi(ph, pl, d, stbsp__bot[power]); + } else { + stbsp__int32 e, et, eb; + double p2h, p2l; + + e = power; + if (power < 0) + e = -e; + et = (e * 0x2c9) >> 14; /* %23 */ + if (et > 13) + et = 13; + eb = e - (et * 23); + + ph = d; + pl = 0.0; + if (power < 0) { + if (eb) { + --eb; + stbsp__ddmulthi(ph, pl, d, stbsp__negbot[eb]); + stbsp__ddmultlos(ph, pl, d, stbsp__negboterr[eb]); + } + if (et) { + stbsp__ddrenorm(ph, pl); + --et; + stbsp__ddmulthi(p2h, p2l, ph, stbsp__negtop[et]); + stbsp__ddmultlo(p2h, p2l, ph, pl, stbsp__negtop[et], stbsp__negtoperr[et]); + ph = p2h; + pl = p2l; + } + } else { + if (eb) { + e = eb; + if (eb > 22) + eb = 22; + e -= eb; + stbsp__ddmulthi(ph, pl, d, stbsp__bot[eb]); + if (e) { + stbsp__ddrenorm(ph, pl); + stbsp__ddmulthi(p2h, p2l, ph, stbsp__bot[e]); + stbsp__ddmultlos(p2h, p2l, stbsp__bot[e], pl); + ph = p2h; + pl = p2l; + } + } + if (et) { + stbsp__ddrenorm(ph, pl); + --et; + stbsp__ddmulthi(p2h, p2l, ph, stbsp__top[et]); + stbsp__ddmultlo(p2h, p2l, ph, pl, stbsp__top[et], stbsp__toperr[et]); + ph = p2h; + pl = p2l; + } + } + } + stbsp__ddrenorm(ph, pl); + *ohi = ph; + *olo = pl; +} + +// given a float value, returns the significant bits in bits, and the position of the +// decimal point in decimal_pos. +/-INF and NAN are specified by special values +// returned in the decimal_pos parameter. +// frac_digits is absolute normally, but if you want from first significant digits (got %g and %e), or in 0x80000000 +static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, char *out, stbsp__int32 *decimal_pos, double value, stbsp__uint32 frac_digits) +{ + double d; + stbsp__int64 bits = 0; + stbsp__int32 expo, e, ng, tens; + + d = value; + STBSP__COPYFP(bits, d); + expo = (stbsp__int32)((bits >> 52) & 2047); + ng = (stbsp__int32)((stbsp__uint64) bits >> 63); + if (ng) + d = -d; + + if (expo == 2047) // is nan or inf? + { + *start = (bits & ((((stbsp__uint64)1) << 52) - 1)) ? "NaN" : "Inf"; + *decimal_pos = STBSP__SPECIAL; + *len = 3; + return ng; + } + + if (expo == 0) // is zero or denormal + { + if (((stbsp__uint64) bits << 1) == 0) // do zero + { + *decimal_pos = 1; + *start = out; + out[0] = '0'; + *len = 1; + return ng; + } + // find the right expo for denormals + { + stbsp__int64 v = ((stbsp__uint64)1) << 51; + while ((bits & v) == 0) { + --expo; + v >>= 1; + } + } + } + + // find the decimal exponent as well as the decimal bits of the value + { + double ph, pl; + + // log10 estimate - very specifically tweaked to hit or undershoot by no more than 1 of log10 of all expos 1..2046 + tens = expo - 1023; + tens = (tens < 0) ? ((tens * 617) / 2048) : (((tens * 1233) / 4096) + 1); + + // move the significant bits into position and stick them into an int + stbsp__raise_to_power10(&ph, &pl, d, 18 - tens); + + // get full as much precision from double-double as possible + stbsp__ddtoS64(bits, ph, pl); + + // check if we undershot + if (((stbsp__uint64)bits) >= stbsp__tento19th) + ++tens; + } + + // now do the rounding in integer land + frac_digits = (frac_digits & 0x80000000) ? ((frac_digits & 0x7ffffff) + 1) : (tens + frac_digits); + if ((frac_digits < 24)) { + stbsp__uint32 dg = 1; + if ((stbsp__uint64)bits >= stbsp__powten[9]) + dg = 10; + while ((stbsp__uint64)bits >= stbsp__powten[dg]) { + ++dg; + if (dg == 20) + goto noround; + } + if (frac_digits < dg) { + stbsp__uint64 r; + // add 0.5 at the right position and round + e = dg - frac_digits; + if ((stbsp__uint32)e >= 24) + goto noround; + r = stbsp__powten[e]; + bits = bits + (r / 2); + if ((stbsp__uint64)bits >= stbsp__powten[dg]) + ++tens; + bits /= r; + } + noround:; + } + + // kill long trailing runs of zeros + if (bits) { + stbsp__uint32 n; + for (;;) { + if (bits <= 0xffffffff) + break; + if (bits % 1000) + goto donez; + bits /= 1000; + } + n = (stbsp__uint32)bits; + while ((n % 1000) == 0) + n /= 1000; + bits = n; + donez:; + } + + // convert to string + out += 64; + e = 0; + for (;;) { + stbsp__uint32 n; + char *o = out - 8; + // do the conversion in chunks of U32s (avoid most 64-bit divides, worth it, constant denomiators be damned) + if (bits >= 100000000) { + n = (stbsp__uint32)(bits % 100000000); + bits /= 100000000; + } else { + n = (stbsp__uint32)bits; + bits = 0; + } + while (n) { + out -= 2; + *(stbsp__uint16 *)out = *(stbsp__uint16 *)&stbsp__digitpair.pair[(n % 100) * 2]; + n /= 100; + e += 2; + } + if (bits == 0) { + if ((e) && (out[0] == '0')) { + ++out; + --e; + } + break; + } + while (out != o) { + *--out = '0'; + ++e; + } + } + + *decimal_pos = tens; + *start = out; + *len = e; + return ng; +} + +#undef stbsp__ddmulthi +#undef stbsp__ddrenorm +#undef stbsp__ddmultlo +#undef stbsp__ddmultlos +#undef STBSP__SPECIAL +#undef STBSP__COPYFP + +#endif // STB_SPRINTF_NOFLOAT + +// clean up +#undef stbsp__uint16 +#undef stbsp__uint32 +#undef stbsp__int32 +#undef stbsp__uint64 +#undef stbsp__int64 +#undef STBSP__UNALIGNED + +#endif // STB_SPRINTF_IMPLEMENTATION + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +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. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +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 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. +------------------------------------------------------------------------------ +*/ +#endif // DQN_STB_SPRINTF_HEADER_ONLY diff --git a/dqn_external.h b/dqn_external.h new file mode 100644 index 0000000..3e6bfba --- /dev/null +++ b/dqn_external.h @@ -0,0 +1,596 @@ +// NOTE: [$BSTK] b_stacktrace ====================================================================== +#if defined(DQN_OS_WIN32) + #define DQN_NO_WIN32_MIN_HEADER + #define WIN32_MEAN_AND_LEAN + #include + #include +#endif +#define B_STACKTRACE_API static +#include "b_stacktrace.h" + +// NOTE: [$OS_H] OS Headers ======================================================================== +#if defined(DQN_OS_WIN32) + #pragma comment(lib, "bcrypt") + #pragma comment(lib, "wininet") + + #if defined(DQN_NO_WIN32_MIN_HEADER) + #include // Dqn_OS_SecureRNGBytes -> BCryptOpenAlgorithmProvider ... etc + #include // Dqn_Win_MakeProcessDPIAware -> SetProcessDpiAwareProc + #if !defined(DQN_NO_WINNET) + #include // Dqn_Win_Net -> InternetConnect ... etc + #endif // DQN_NO_WINNET + #elif !defined(_INC_WINDOWS) + #if defined(DQN_COMPILER_W32_MSVC) + #pragma warning(push) + #pragma warning(disable: 4201) // warning C4201: nonstandard extension used: nameless struct/union + #endif + #define MAX_PATH 260 + + // NOTE: Wait/Synchronization + #define INFINITE 0xFFFFFFFF // Infinite timeout + + // NOTE: FormatMessageA + #define FORMAT_MESSAGE_FROM_SYSTEM 0x00001000 + #define FORMAT_MESSAGE_IGNORE_INSERTS 0x00000200 + #define FORMAT_MESSAGE_FROM_HMODULE 0x00000800 + #define MAKELANGID(p, s) ((((unsigned short )(s)) << 10) | (unsigned short )(p)) + #define SUBLANG_DEFAULT 0x01 // user default + #define LANG_NEUTRAL 0x00 + + // NOTE: MultiByteToWideChar + #define CP_UTF8 65001 // UTF-8 translation + + // NOTE: VirtualAlloc + // NOTE: Allocation Type + #define MEM_RESERVE 0x00002000 + #define MEM_COMMIT 0x00001000 + #define MEM_DECOMMIT 0x00004000 + #define MEM_RELEASE 0x00008000 + + // NOTE: Protect + #define PAGE_NOACCESS 0x01 + #define PAGE_READONLY 0x02 + #define PAGE_READWRITE 0x04 + #define PAGE_GUARD 0x100 + + // NOTE: FindFirstFile + #define INVALID_HANDLE_VALUE ((void *)(long *)-1) + #define INVALID_FILE_ATTRIBUTES ((unsigned long)-1) + #define FILE_ATTRIBUTE_NORMAL 0x00000080 + #define FIND_FIRST_EX_LARGE_FETCH 0x00000002 + #define FILE_ATTRIBUTE_DIRECTORY 0x00000010 + #define FILE_ATTRIBUTE_READONLY 0x00000001 + #define FILE_ATTRIBUTE_HIDDEN 0x00000002 + + // NOTE: GetModuleFileNameW + #define ERROR_INSUFFICIENT_BUFFER 122L + + // NOTE: MoveFile + #define MOVEFILE_REPLACE_EXISTING 0x00000001 + #define MOVEFILE_COPY_ALLOWED 0x00000002 + + // NOTE: Wininet + typedef unsigned short INTERNET_PORT; + #define INTERNET_OPEN_TYPE_PRECONFIG 0 // use registry configuration + #define INTERNET_DEFAULT_HTTPS_PORT 443 // HTTPS + #define INTERNET_SERVICE_HTTP 3 + #define INTERNET_OPTION_USER_AGENT 41 + #define INTERNET_FLAG_NO_AUTH 0x00040000 // no automatic authentication handling + #define INTERNET_FLAG_SECURE 0x00800000 // use PCT/SSL if applicable (HTTP) + + // NOTE: CreateFile + #define GENERIC_READ (0x80000000L) + #define GENERIC_WRITE (0x40000000L) + #define GENERIC_EXECUTE (0x20000000L) + #define GENERIC_ALL (0x10000000L) + #define FILE_ATTRIBUTE_NORMAL 0x00000080 + #define FILE_APPEND_DATA 4 + + #define CREATE_NEW 1 + #define CREATE_ALWAYS 2 + #define OPEN_EXISTING 3 + #define OPEN_ALWAYS 4 + #define TRUNCATE_EXISTING 5 + + #define STD_INPUT_HANDLE ((unsigned long)-10) + #define STD_OUTPUT_HANDLE ((unsigned long)-11) + #define STD_ERROR_HANDLE ((unsigned long)-12) + + #define INVALID_FILE_SIZE ((unsigned long)0xFFFFFFFF) + + #define HTTP_QUERY_RAW_HEADERS 21 + #define HTTP_QUERY_RAW_HEADERS_CRLF 22 + + // NOTE: HttpAddRequestHeadersA + #define HTTP_ADDREQ_FLAG_ADD_IF_NEW 0x10000000 + #define HTTP_ADDREQ_FLAG_ADD 0x20000000 + #define HTTP_ADDREQ_FLAG_COALESCE_WITH_COMMA 0x40000000 + #define HTTP_ADDREQ_FLAG_COALESCE_WITH_SEMICOLON 0x01000000 + #define HTTP_ADDREQ_FLAG_COALESCE HTTP_ADDREQ_FLAG_COALESCE_WITH_COMMA + #define HTTP_ADDREQ_FLAG_REPLACE 0x80000000 + + #define SW_MAXIMIZED 3 + #define SW_SHOW 5 + + typedef enum PROCESS_DPI_AWARENESS { + PROCESS_DPI_UNAWARE = 0, + PROCESS_SYSTEM_DPI_AWARE = 1, + PROCESS_PER_MONITOR_DPI_AWARE = 2 + } PROCESS_DPI_AWARENESS; + + #define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((void *)-4) + + typedef union { + struct { + unsigned long LowPart; + unsigned long HighPart; + } DUMMYSTRUCTNAME; + struct { + unsigned long LowPart; + unsigned long HighPart; + } u; + uint64_t QuadPart; + } ULARGE_INTEGER; + + typedef struct + { + unsigned long dwLowDateTime; + unsigned long dwHighDateTime; + } FILETIME; + + typedef struct + { + unsigned long dwFileAttributes; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + unsigned long nFileSizeHigh; + unsigned long nFileSizeLow; + } WIN32_FILE_ATTRIBUTE_DATA; + + typedef enum + { + GetFileExInfoStandard, + GetFileExMaxInfoLevel + } GET_FILEEX_INFO_LEVELS; + + typedef struct { + unsigned long nLength; + void *lpSecurityDescriptor; + bool bInheritHandle; + } SECURITY_ATTRIBUTES; + + typedef struct { + long left; + long top; + long right; + long bottom; + } RECT, *PRECT, *NPRECT, *LPRECT; + + typedef struct { + union { + unsigned long dwOemId; // Obsolete field...do not use + struct { + uint16_t wProcessorArchitecture; + uint16_t wReserved; + } DUMMYSTRUCTNAME; + } DUMMYUNIONNAME; + unsigned long dwPageSize; + void *lpMinimumApplicationAddress; + void *lpMaximumApplicationAddress; + unsigned long *dwActiveProcessorMask; + unsigned long dwNumberOfProcessors; + unsigned long dwProcessorType; + unsigned long dwAllocationGranularity; + uint16_t wProcessorLevel; + uint16_t wProcessorRevision; + } SYSTEM_INFO; + + typedef struct { + unsigned short wYear; + unsigned short wMonth; + unsigned short wDayOfWeek; + unsigned short wDay; + unsigned short wHour; + unsigned short wMinute; + unsigned short wSecond; + unsigned short wMilliseconds; + } SYSTEMTIME; + + typedef struct { + unsigned long dwFileAttributes; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + unsigned long nFileSizeHigh; + unsigned long nFileSizeLow; + unsigned long dwReserved0; + unsigned long dwReserved1; + wchar_t cFileName[MAX_PATH]; + wchar_t cAlternateFileName[14]; + #ifdef _MAC + unsigned long dwFileType; + unsigned long dwCreatorType; + unsigned short wFinderFlags; + #endif + } WIN32_FIND_DATAW; + + typedef enum { + FindExInfoStandard, + FindExInfoBasic, + FindExInfoMaxInfoLevel, + } FINDEX_INFO_LEVELS; + + typedef enum { + FindExSearchNameMatch, + FindExSearchLimitToDirectories, + FindExSearchLimitToDevices, + FindExSearchMaxSearchOp + } FINDEX_SEARCH_OPS; + + typedef enum { + INTERNET_SCHEME_PARTIAL = -2, + INTERNET_SCHEME_UNKNOWN = -1, + INTERNET_SCHEME_DEFAULT = 0, + INTERNET_SCHEME_FTP, + INTERNET_SCHEME_GOPHER, + INTERNET_SCHEME_HTTP, + INTERNET_SCHEME_HTTPS, + INTERNET_SCHEME_FILE, + INTERNET_SCHEME_NEWS, + INTERNET_SCHEME_MAILTO, + INTERNET_SCHEME_SOCKS, + INTERNET_SCHEME_JAVASCRIPT, + INTERNET_SCHEME_VBSCRIPT, + INTERNET_SCHEME_RES, + INTERNET_SCHEME_FIRST = INTERNET_SCHEME_FTP, + INTERNET_SCHEME_LAST = INTERNET_SCHEME_RES + } INTERNET_SCHEME; + + typedef struct { + unsigned long dwStructSize; // size of this structure. Used in version check + char *lpszScheme; // pointer to scheme name + unsigned long dwSchemeLength; // length of scheme name + INTERNET_SCHEME nScheme; // enumerated scheme type (if known) + char *lpszHostName; // pointer to host name + unsigned long dwHostNameLength; // length of host name + INTERNET_PORT nPort; // converted port number + char *lpszUserName; // pointer to user name + unsigned long dwUserNameLength; // length of user name + char *lpszPassword; // pointer to password + unsigned long dwPasswordLength; // length of password + char *lpszUrlPath; // pointer to URL-path + unsigned long dwUrlPathLength; // length of URL-path + char *lpszExtraInfo; // pointer to extra information (e.g. ?foo or #foo) + unsigned long dwExtraInfoLength; // length of extra information + } URL_COMPONENTSA; + + typedef void * HMODULE; + typedef union { + struct { + unsigned long LowPart; + long HighPart; + }; + struct { + unsigned long LowPart; + long HighPart; + } u; + uint64_t QuadPart; + } LARGE_INTEGER; + + extern "C" + { + /*BOOL*/ int __stdcall CreateDirectoryW (wchar_t const *lpPathName, SECURITY_ATTRIBUTES *lpSecurityAttributes); + /*BOOL*/ int __stdcall RemoveDirectoryW (wchar_t const *lpPathName); + /*DWORD*/ unsigned long __stdcall GetCurrentDirectoryW (unsigned long nBufferLength, wchar_t *lpBuffer); + + /*BOOL*/ int __stdcall FindNextFileW (void *hFindFile, WIN32_FIND_DATAW *lpFindFileData); + /*HANDLE*/ void * __stdcall FindFirstFileExW (wchar_t const *lpFileName, FINDEX_INFO_LEVELS fInfoLevelId, void *lpFindFileData, FINDEX_SEARCH_OPS fSearchOp, void *lpSearchFilter, unsigned long dwAdditionalFlags); + /*DWORD*/ unsigned long __stdcall GetFileAttributesExW (wchar_t const *lpFileName, GET_FILEEX_INFO_LEVELS fInfoLevelId, WIN32_FILE_ATTRIBUTE_DATA *lpFileInformation); + /*BOOL*/ int __stdcall GetFileSizeEx (void *hFile, LARGE_INTEGER *lpFileSize); + + /*BOOL*/ int __stdcall MoveFileExW (wchar_t const *lpExistingFileName, wchar_t const *lpNewFileName, unsigned long dwFlags); + /*BOOL*/ int __stdcall CopyFileW (wchar_t const *lpExistingFileName, wchar_t const *lpNewFileName, int bFailIfExists); + /*BOOL*/ int __stdcall DeleteFileW (wchar_t const *lpExistingFileName); + /*HANDLE*/ void * __stdcall CreateFileW (wchar_t const *lpFileName, unsigned long dwDesiredAccess, unsigned long dwShareMode, SECURITY_ATTRIBUTES *lpSecurityAttributes, unsigned long dwCreationDisposition, unsigned long dwFlagsAndAttributes, void *hTemplateFile); + /*BOOL*/ int __stdcall ReadFile (void *hFile, void *lpBuffer, unsigned long nNumberOfBytesToRead, unsigned long *lpNumberOfBytesRead, struct OVERLAPPED *lpOverlapped); + /*BOOL*/ int __stdcall WriteFile (void *hFile, void const *lpBuffer, unsigned long nNumberOfBytesToWrite, unsigned long *lpNumberOfBytesWritten, struct OVERLAPPED *lpOverlapped); + /*BOOL*/ int __stdcall CloseHandle (void *hObject); + + /*BOOL*/ int __stdcall WriteConsoleA (void *hConsoleOutput, const char *lpBuffer, unsigned long nNumberOfCharsToWrite, unsigned long *lpNumberOfCharsWritten, void *lpReserved); + /*BOOL*/ int __stdcall AllocConsole (); + /*BOOL*/ int __stdcall FreeConsole (); + /*BOOL*/ int __stdcall AttachConsole (unsigned long dwProcessId); + /*HANDLE*/ void * __stdcall GetStdHandle (unsigned long nStdHandle); + /*BOOL*/ int __stdcall GetConsoleMode (void *hConsoleHandle, unsigned long *lpMode); + + /*HMODULE*/ void * __stdcall LoadLibraryA (char const *lpFileName); + /*BOOL*/ int __stdcall FreeLibrary (void *hModule); + /*FARPROC*/ void * __stdcall GetProcAddress (void *hModule, char const *lpProcName); + + /*BOOL*/ int __stdcall GetWindowRect (void *hWnd, RECT *lpRect); + /*BOOL*/ int __stdcall SetWindowPos (void *hWnd, void *hWndInsertAfter, int X, int Y, int cx, int cy, unsigned int uFlags); + + /*DWORD*/ unsigned long __stdcall GetWindowModuleFileNameA (void *hwnd, char *pszFileName, unsigned int cchFileNameMax); + /*HMODULE*/ void * __stdcall GetModuleHandleA (char const *lpModuleName); + /*DWORD*/ unsigned long __stdcall GetModuleFileNameW (void *hModule, wchar_t *lpFilename, unsigned long nSize); + + /*DWORD*/ unsigned long __stdcall WaitForSingleObject (void *hHandle, unsigned long dwMilliseconds); + + /*BOOL*/ int __stdcall QueryPerformanceCounter (LARGE_INTEGER *lpPerformanceCount); + /*BOOL*/ int __stdcall QueryPerformanceFrequency (LARGE_INTEGER *lpFrequency); + + /*HANDLE*/ void * __stdcall CreateThread (SECURITY_ATTRIBUTES *lpThreadAttributes, size_t dwStackSize, unsigned long (*lpStartAddress)(void *), void *lpParameter, unsigned long dwCreationFlags, unsigned long *lpThreadId); + /*HANDLE*/ void * __stdcall CreateSemaphoreA (SECURITY_ATTRIBUTES *lpSecurityAttributes, long lInitialCount, long lMaxCount, char *lpName); + /*BOOL*/ int __stdcall ReleaseSemaphore (void *semaphore, long lReleaseCount, long *lpPreviousCount); + void __stdcall Sleep (unsigned long dwMilliseconds); + /*DWORD*/ unsigned long __stdcall GetCurrentThreadId (); + + void * __stdcall VirtualAlloc (void *lpAddress, size_t dwSize, unsigned long flAllocationType, unsigned long flProtect); + /*BOOL*/ int __stdcall VirtualProtect (void *lpAddress, size_t dwSize, unsigned long flNewProtect, unsigned long *lpflOldProtect); + /*BOOL*/ int __stdcall VirtualFree (void *lpAddress, size_t dwSize, unsigned long dwFreeType); + + void __stdcall GetSystemInfo (SYSTEM_INFO *system_info); + void __stdcall GetSystemTime (SYSTEMTIME *lpSystemTime); + void __stdcall GetSystemTimeAsFileTime (FILETIME *lpFileTime); + void __stdcall GetLocalTime (SYSTEMTIME *lpSystemTime); + + /*DWORD*/ unsigned long __stdcall FormatMessageA (unsigned long dwFlags, void *lpSource, unsigned long dwMessageId, unsigned long dwLanguageId, char *lpBuffer, unsigned long nSize, va_list *Arguments); + /*DWORD*/ unsigned long __stdcall GetLastError (); + + int __stdcall MultiByteToWideChar (unsigned int CodePage, unsigned long dwFlags, char const *lpMultiByteStr, int cbMultiByte, wchar_t *lpWideCharStr, int cchWideChar); + int __stdcall WideCharToMultiByte (unsigned int CodePage, unsigned long dwFlags, wchar_t const *lpWideCharStr, int cchWideChar, char *lpMultiByteStr, int cbMultiByte, char const *lpDefaultChar, bool *lpUsedDefaultChar); + + /*NTSTATUS*/ long __stdcall BCryptOpenAlgorithmProvider(void *phAlgorithm, wchar_t const *pszAlgId, wchar_t const *pszImplementation, unsigned long dwFlags); + /*NTSTATUS*/ long __stdcall BCryptGenRandom (void *hAlgorithm, unsigned char *pbBuffer, unsigned long cbBuffer, unsigned long dwFlags); + + /*BOOLAPI*/ int __stdcall InternetCrackUrlA (char const *lpszUrl, unsigned long dwUrlLength, unsigned long dwFlags, URL_COMPONENTSA *lpUrlComponents); + /*HANDLE*/ void * __stdcall InternetOpenA (char const *lpszAgent, unsigned long dwAccessType, char const *lpszProxy, char const *lpszProxyBypass, unsigned long dwFlags); + /*HANDLE*/ void * __stdcall InternetConnectA (void *hInternet, char const *lpszServerName, INTERNET_PORT nServerPort, char const *lpszUserName, char const *lpszPassword, unsigned long dwService, unsigned long dwFlags, unsigned long *dwContext); + /*BOOLAPI*/ int __stdcall InternetSetOptionA (void *hInternet, unsigned long dwOption, void *lpBuffer, unsigned long dwBufferLength); + /*BOOLAPI*/ int __stdcall InternetReadFile (void *hFile, void *lpBuffer, unsigned long dwNumberOfBytesToRead, unsigned long *lpdwNumberOfBytesRead); + /*BOOLAPI*/ int __stdcall InternetCloseHandle (void *hInternet); + /*HANDLE*/ void * __stdcall HttpOpenRequestA (void *hConnect, char const *lpszVerb, char const *lpszObjectName, char const *lpszVersion, char const *lpszReferrer, char const **lplpszAcceptTypes, unsigned long dwFlags, unsigned long *dwContext); + /*BOOLAPI*/ int __stdcall HttpSendRequestA (void *hRequest, char const *lpszHeaders, unsigned long dwHeadersLength, void *lpOptional, unsigned long dwOptionalLength); + /*BOOLAPI*/ int __stdcall HttpAddRequestHeadersA (void *hRequest, char const *lpszHeaders, unsigned long dwHeadersLength, unsigned long dwModifiers); + /*BOOL*/ int __stdcall HttpQueryInfoA (void *hRequest, unsigned long dwInfoLevel, void *lpBuffer, unsigned long *lpdwBufferLength, unsigned long *lpdwIndex); + + /*HINSTANCE*/ void * __stdcall ShellExecuteA (void *hwnd, char const *lpOperation, char const *lpFile, char const *lpParameters, char const *lpDirectory, int nShowCmd); + /*BOOL*/ int __stdcall ShowWindow (void *hWnd, int nCmdShow); + } + #if defined(DQN_COMPILER_W32_MSVC) + #pragma warning(pop) + #endif + #endif // !defined(_INC_WINDOWS) +#elif defined(DQN_OS_UNIX) + #include // errno + #include // O_RDONLY ... etc + #include // FICLONE + #include // ioctl + #include // pid_t + #include // getrandom + #include // stat + #include // sendfile + #include // mmap + #include // clock_gettime, nanosleep + #include // access, gettid +#endif + +// NOTE: [$STBS] stb_sprintf ======================================================================= +// stb_sprintf - v1.10 - public domain snprintf() implementation +// originally by Jeff Roberts / RAD Game Tools, 2015/10/20 +// http://github.com/nothings/stb +// +// allowed types: sc uidBboXx p AaGgEef n +// lengths : hh h ll j z t I64 I32 I +// +// Contributors: +// Fabian "ryg" Giesen (reformatting) +// github:aganm (attribute format) +// +// Contributors (bugfixes): +// github:d26435 +// github:trex78 +// github:account-login +// Jari Komppa (SI suffixes) +// Rohit Nirmal +// Marcin Wojdyr +// Leonard Ritter +// Stefano Zanotti +// Adam Allison +// Arvid Gerstmann +// Markus Kolb +// +// LICENSE: +// +// See end of file for license information. + +#ifndef STB_SPRINTF_H_INCLUDE +#define STB_SPRINTF_H_INCLUDE + +/* +Single file sprintf replacement. + +Originally written by Jeff Roberts at RAD Game Tools - 2015/10/20. +Hereby placed in public domain. + +This is a full sprintf replacement that supports everything that +the C runtime sprintfs support, including float/double, 64-bit integers, +hex floats, field parameters (%*.*d stuff), length reads backs, etc. + +Why would you need this if sprintf already exists? Well, first off, +it's *much* faster (see below). It's also much smaller than the CRT +versions code-space-wise. We've also added some simple improvements +that are super handy (commas in thousands, callbacks at buffer full, +for example). Finally, the format strings for MSVC and GCC differ +for 64-bit integers (among other small things), so this lets you use +the same format strings in cross platform code. + +It uses the standard single file trick of being both the header file +and the source itself. If you just include it normally, you just get +the header file function definitions. To get the code, you include +it from a C or C++ file and define STB_SPRINTF_IMPLEMENTATION first. + +It only uses va_args macros from the C runtime to do it's work. It +does cast doubles to S64s and shifts and divides U64s, which does +drag in CRT code on most platforms. + +It compiles to roughly 8K with float support, and 4K without. +As a comparison, when using MSVC static libs, calling sprintf drags +in 16K. + +API: +==== +int stbsp_sprintf( char * buf, char const * fmt, ... ) +int stbsp_snprintf( char * buf, int count, char const * fmt, ... ) + Convert an arg list into a buffer. stbsp_snprintf always returns + a zero-terminated string (unlike regular snprintf). + +int stbsp_vsprintf( char * buf, char const * fmt, va_list va ) +int stbsp_vsnprintf( char * buf, int count, char const * fmt, va_list va ) + Convert a va_list arg list into a buffer. stbsp_vsnprintf always returns + a zero-terminated string (unlike regular snprintf). + +int stbsp_vsprintfcb( STBSP_SPRINTFCB * callback, void * user, char * buf, char const * fmt, va_list va ) + typedef char * STBSP_SPRINTFCB( char const * buf, void * user, int len ); + Convert into a buffer, calling back every STB_SPRINTF_MIN chars. + Your callback can then copy the chars out, print them or whatever. + This function is actually the workhorse for everything else. + The buffer you pass in must hold at least STB_SPRINTF_MIN characters. + // you return the next buffer to use or 0 to stop converting + +void stbsp_set_separators( char comma, char period ) + Set the comma and period characters to use. + +FLOATS/DOUBLES: +=============== +This code uses a internal float->ascii conversion method that uses +doubles with error correction (double-doubles, for ~105 bits of +precision). This conversion is round-trip perfect - that is, an atof +of the values output here will give you the bit-exact double back. + +One difference is that our insignificant digits will be different than +with MSVC or GCC (but they don't match each other either). We also +don't attempt to find the minimum length matching float (pre-MSVC15 +doesn't either). + +If you don't need float or doubles at all, define STB_SPRINTF_NOFLOAT +and you'll save 4K of code space. + +64-BIT INTS: +============ +This library also supports 64-bit integers and you can use MSVC style or +GCC style indicators (%I64d or %lld). It supports the C99 specifiers +for size_t and ptr_diff_t (%jd %zd) as well. + +EXTRAS: +======= +Like some GCCs, for integers and floats, you can use a ' (single quote) +specifier and commas will be inserted on the thousands: "%'d" on 12345 +would print 12,345. + +For integers and floats, you can use a "$" specifier and the number +will be converted to float and then divided to get kilo, mega, giga or +tera and then printed, so "%$d" 1000 is "1.0 k", "%$.2d" 2536000 is +"2.53 M", etc. For byte values, use two $:s, like "%$$d" to turn +2536000 to "2.42 Mi". If you prefer JEDEC suffixes to SI ones, use three +$:s: "%$$$d" -> "2.42 M". To remove the space between the number and the +suffix, add "_" specifier: "%_$d" -> "2.53M". + +In addition to octal and hexadecimal conversions, you can print +integers in binary: "%b" for 256 would print 100. + +PERFORMANCE vs MSVC 2008 32-/64-bit (GCC is even slower than MSVC): +=================================================================== +"%d" across all 32-bit ints (4.8x/4.0x faster than 32-/64-bit MSVC) +"%24d" across all 32-bit ints (4.5x/4.2x faster) +"%x" across all 32-bit ints (4.5x/3.8x faster) +"%08x" across all 32-bit ints (4.3x/3.8x faster) +"%f" across e-10 to e+10 floats (7.3x/6.0x faster) +"%e" across e-10 to e+10 floats (8.1x/6.0x faster) +"%g" across e-10 to e+10 floats (10.0x/7.1x faster) +"%f" for values near e-300 (7.9x/6.5x faster) +"%f" for values near e+300 (10.0x/9.1x faster) +"%e" for values near e-300 (10.1x/7.0x faster) +"%e" for values near e+300 (9.2x/6.0x faster) +"%.320f" for values near e-300 (12.6x/11.2x faster) +"%a" for random values (8.6x/4.3x faster) +"%I64d" for 64-bits with 32-bit values (4.8x/3.4x faster) +"%I64d" for 64-bits > 32-bit values (4.9x/5.5x faster) +"%s%s%s" for 64 char strings (7.1x/7.3x faster) +"...512 char string..." ( 35.0x/32.5x faster!) +*/ + +#if defined(__clang__) + #if defined(__has_feature) && defined(__has_attribute) + #if __has_feature(address_sanitizer) + #if __has_attribute(__no_sanitize__) + #define STBSP__ASAN __attribute__((__no_sanitize__("address"))) + #elif __has_attribute(__no_sanitize_address__) + #define STBSP__ASAN __attribute__((__no_sanitize_address__)) + #elif __has_attribute(__no_address_safety_analysis__) + #define STBSP__ASAN __attribute__((__no_address_safety_analysis__)) + #endif + #endif + #endif +#elif defined(__GNUC__) && (__GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) + #if defined(__SANITIZE_ADDRESS__) && __SANITIZE_ADDRESS__ + #define STBSP__ASAN __attribute__((__no_sanitize_address__)) + #endif +#endif + +#ifndef STBSP__ASAN +#define STBSP__ASAN +#endif + +#ifdef STB_SPRINTF_STATIC +#define STBSP__PUBLICDEC static +#define STBSP__PUBLICDEF static STBSP__ASAN +#else +#ifdef __cplusplus +#define STBSP__PUBLICDEC extern "C" +#define STBSP__PUBLICDEF extern "C" STBSP__ASAN +#else +#define STBSP__PUBLICDEC extern +#define STBSP__PUBLICDEF STBSP__ASAN +#endif +#endif + +#if defined(__has_attribute) + #if __has_attribute(format) + #define STBSP__ATTRIBUTE_FORMAT(fmt,va) __attribute__((format(printf,fmt,va))) + #endif +#endif + +#ifndef STBSP__ATTRIBUTE_FORMAT +#define STBSP__ATTRIBUTE_FORMAT(fmt,va) +#endif + +#ifdef _MSC_VER +#define STBSP__NOTUSED(v) (void)(v) +#else +#define STBSP__NOTUSED(v) (void)sizeof(v) +#endif + +#include // for va_arg(), va_list() +#include // size_t, ptrdiff_t + +#ifndef STB_SPRINTF_MIN +#define STB_SPRINTF_MIN 512 // how many characters per callback +#endif +typedef char *STBSP_SPRINTFCB(const char *buf, void *user, int len); + +#ifndef STB_SPRINTF_DECORATE +#define STB_SPRINTF_DECORATE(name) stbsp_##name // define this before including if you want to change the names +#endif + +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va); +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsnprintf)(char *buf, int count, char const *fmt, va_list va); +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...) STBSP__ATTRIBUTE_FORMAT(2,3); +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...) STBSP__ATTRIBUTE_FORMAT(3,4); + +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va); +STBSP__PUBLICDEC void STB_SPRINTF_DECORATE(set_separators)(char comma, char period); +#endif // STB_SPRINTF_H_INCLUDE + + diff --git a/dqn_hash.h b/dqn_hash.h index 3cbd720..621bfa6 100644 --- a/dqn_hash.h +++ b/dqn_hash.h @@ -1,10 +1,3 @@ -// NOTE: Table Of Contents ========================================================================= -// Index | Disable #define | Description -// ================================================================================================= -// [$FNV1] Dqn_FNV1A | | Hash(x) -> 32/64bit via FNV1a -// [$MMUR] Dqn_MurmurHash3 | | Hash(x) -> 32/128bit via MurmurHash3 -// ================================================================================================= - // NOTE: [$FNV1] Dqn_FNV1A ========================================================================= // NOTE: API ======================================================================================= #if 0 diff --git a/dqn_helpers.cpp b/dqn_helpers.cpp new file mode 100644 index 0000000..c3b1b5a --- /dev/null +++ b/dqn_helpers.cpp @@ -0,0 +1,989 @@ +#if !defined(DQN_NO_JSON_BUILDER) +// NOTE: [$JSON] Dqn_JSONBuilder =================================================================== +DQN_API Dqn_JSONBuilder Dqn_JSONBuilder_Init(Dqn_Allocator allocator, int spaces_per_indent) +{ + Dqn_JSONBuilder result = {}; + result.spaces_per_indent = spaces_per_indent; + result.string_builder.allocator = allocator; + return result; +} + +DQN_API Dqn_String8 Dqn_JSONBuilder_Build(Dqn_JSONBuilder const *builder, Dqn_Allocator allocator) +{ + Dqn_String8 result = Dqn_String8Builder_Build(&builder->string_builder, allocator); + return result; +} + +DQN_API void Dqn_JSONBuilder_KeyValue(Dqn_JSONBuilder *builder, Dqn_String8 key, Dqn_String8 value) +{ + if (key.size == 0 && value.size == 0) + return; + + Dqn_JSONBuilderItem item = Dqn_JSONBuilderItem_KeyValue; + if (value.size == 1) { + if (value.data[0] == '{' || value.data[0] == '[') { + item = Dqn_JSONBuilderItem_OpenContainer; + } else if (value.data[0] == '}' || value.data[0] == ']') { + item = Dqn_JSONBuilderItem_CloseContainer; + } + } + + bool adding_to_container_with_items = item != Dqn_JSONBuilderItem_CloseContainer && + (builder->last_item == Dqn_JSONBuilderItem_KeyValue || + builder->last_item == Dqn_JSONBuilderItem_CloseContainer); + + uint8_t prefix_size = 0; + char prefix[2] = {0}; + if (adding_to_container_with_items) + prefix[prefix_size++] = ','; + + if (builder->last_item != Dqn_JSONBuilderItem_Empty) + prefix[prefix_size++] = '\n'; + + if (item == Dqn_JSONBuilderItem_CloseContainer) + builder->indent_level--; + + int spaces_per_indent = builder->spaces_per_indent ? builder->spaces_per_indent : 2; + int spaces = builder->indent_level * spaces_per_indent; + + if (key.size) { + Dqn_String8Builder_AppendF(&builder->string_builder, + "%.*s%*c\"%.*s\": %.*s", + prefix_size, prefix, + spaces, ' ', + DQN_STRING_FMT(key), + DQN_STRING_FMT(value)); + } else { + Dqn_String8Builder_AppendF(&builder->string_builder, + "%.*s%*c%.*s", + prefix_size, prefix, + spaces, ' ', + DQN_STRING_FMT(value)); + } + + if (item == Dqn_JSONBuilderItem_OpenContainer) + builder->indent_level++; + + builder->last_item = item; +} + +DQN_API void Dqn_JSONBuilder_KeyValueFV(Dqn_JSONBuilder *builder, Dqn_String8 key, char const *value_fmt, va_list args) +{ + Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(builder->string_builder.allocator.user_context); + Dqn_String8 value = Dqn_String8_InitFV(scratch.allocator, value_fmt, args); + Dqn_JSONBuilder_KeyValue(builder, key, value); +} + +DQN_API void Dqn_JSONBuilder_KeyValueF(Dqn_JSONBuilder *builder, Dqn_String8 key, char const *value_fmt, ...) +{ + va_list args; + va_start(args, value_fmt); + Dqn_JSONBuilder_KeyValueFV(builder, key, value_fmt, args); + va_end(args); +} + +DQN_API void Dqn_JSONBuilder_ObjectBeginNamed(Dqn_JSONBuilder *builder, Dqn_String8 name) +{ + Dqn_JSONBuilder_KeyValue(builder, name, DQN_STRING8("{")); +} + +DQN_API void Dqn_JSONBuilder_ObjectEnd(Dqn_JSONBuilder *builder) +{ + Dqn_JSONBuilder_KeyValue(builder, DQN_STRING8(""), DQN_STRING8("}")); +} + +DQN_API void Dqn_JSONBuilder_ArrayBeginNamed(Dqn_JSONBuilder *builder, Dqn_String8 name) +{ + Dqn_JSONBuilder_KeyValue(builder, name, DQN_STRING8("[")); +} + +DQN_API void Dqn_JSONBuilder_ArrayEnd(Dqn_JSONBuilder *builder) +{ + Dqn_JSONBuilder_KeyValue(builder, DQN_STRING8(""), DQN_STRING8("]")); +} + +DQN_API void Dqn_JSONBuilder_StringNamed(Dqn_JSONBuilder *builder, Dqn_String8 key, Dqn_String8 value) +{ + Dqn_JSONBuilder_KeyValueF(builder, key, "\"%.*s\"", value.size, value.data); +} + +DQN_API void Dqn_JSONBuilder_LiteralNamed(Dqn_JSONBuilder *builder, Dqn_String8 key, Dqn_String8 value) +{ + Dqn_JSONBuilder_KeyValueF(builder, key, "%.*s", value.size, value.data); +} + +DQN_API void Dqn_JSONBuilder_U64Named(Dqn_JSONBuilder *builder, Dqn_String8 key, uint64_t value) +{ + Dqn_JSONBuilder_KeyValueF(builder, key, "%I64u", value); +} + +DQN_API void Dqn_JSONBuilder_I64Named(Dqn_JSONBuilder *builder, Dqn_String8 key, int64_t value) +{ + Dqn_JSONBuilder_KeyValueF(builder, key, "%I64d", value); +} + +DQN_API void Dqn_JSONBuilder_F64Named(Dqn_JSONBuilder *builder, Dqn_String8 key, double value, int decimal_places) +{ + if (!builder) + return; + + if (decimal_places >= 16) + decimal_places = 16; + + // NOTE: Generate the format string for the float, depending on how many + // decimals places it wants. + char float_fmt[16]; + if (decimal_places > 0) { + // NOTE: Emit the format string "%.f" i.e. %.1f + STB_SPRINTF_DECORATE(snprintf)(float_fmt, sizeof(float_fmt), "%%.%df", decimal_places); + } else { + // NOTE: Emit the format string "%f" + STB_SPRINTF_DECORATE(snprintf)(float_fmt, sizeof(float_fmt), "%%f"); + } + + char fmt[32]; + if (key.size) + STB_SPRINTF_DECORATE(snprintf)(fmt, sizeof(fmt), "\"%%.*s\": %s", float_fmt); + else + STB_SPRINTF_DECORATE(snprintf)(fmt, sizeof(fmt), "%s", float_fmt); + + Dqn_JSONBuilder_KeyValueF(builder, key, fmt, value); +} + +DQN_API void Dqn_JSONBuilder_BoolNamed(Dqn_JSONBuilder *builder, Dqn_String8 key, bool value) +{ + Dqn_String8 value_string = value ? DQN_STRING8("true") : DQN_STRING8("false"); + Dqn_JSONBuilder_KeyValueF(builder, key, "%.*s", value_string.size, value_string.data); +} +#endif // !defined(DQN_NO_JSON_BUILDER) + +#if !defined(DQN_NO_BIN) +// NOTE: [$BHEX] Dqn_Bin =========================================================================== +DQN_API char const *Dqn_Bin_HexBufferTrim0x(char const *hex, Dqn_usize size, Dqn_usize *real_size) +{ + Dqn_String8 result = Dqn_String8_TrimWhitespaceAround(Dqn_String8_Init(hex, size)); + result = Dqn_String8_TrimPrefix(result, DQN_STRING8("0x"), Dqn_String8EqCase_Insensitive); + if (real_size) + *real_size = result.size; + return result.data; +} + +DQN_API Dqn_String8 Dqn_Bin_HexTrim0x(Dqn_String8 string) +{ + Dqn_usize trimmed_size = 0; + char const *trimmed = Dqn_Bin_HexBufferTrim0x(string.data, string.size, &trimmed_size); + Dqn_String8 result = Dqn_String8_Init(trimmed, trimmed_size); + return result; +} + +DQN_API Dqn_BinHexU64String Dqn_Bin_U64ToHexU64String(uint64_t number, uint32_t flags) +{ + Dqn_String8 prefix = {}; + if (!(flags & Dqn_BinHexU64StringFlags_No0xPrefix)) + prefix = DQN_STRING8("0x"); + + Dqn_BinHexU64String result = {}; + DQN_MEMCPY(result.data, prefix.data, prefix.size); + result.size += DQN_CAST(int8_t)prefix.size; + + char const *fmt = (flags & Dqn_BinHexU64StringFlags_UppercaseHex) ? "%I64X" : "%I64x"; + int size = STB_SPRINTF_DECORATE(snprintf)(result.data + result.size, DQN_ARRAY_UCOUNT(result.data) - result.size, fmt, number); + result.size += DQN_CAST(uint8_t)size; + DQN_ASSERT(result.size < DQN_ARRAY_UCOUNT(result.data)); + + // NOTE: snprintf returns the required size of the format string + // irrespective of if there's space or not, but, always null terminates so + // the last byte is wasted. + result.size = DQN_MIN(result.size, DQN_ARRAY_UCOUNT(result.data) - 1); + return result; +} + +DQN_API Dqn_String8 Dqn_Bin_U64ToHex(Dqn_Allocator allocator, uint64_t number, uint32_t flags) +{ + Dqn_String8 prefix = {}; + if (!(flags & Dqn_BinHexU64StringFlags_No0xPrefix)) + prefix = DQN_STRING8("0x"); + + char const *fmt = (flags & Dqn_BinHexU64StringFlags_UppercaseHex) ? "%I64X" : "%I64x"; + Dqn_usize required_size = Dqn_CString8_FSize(fmt, number) + prefix.size; + Dqn_String8 result = Dqn_String8_Allocate(allocator, required_size, Dqn_ZeroMem_No); + + if (Dqn_String8_IsValid(result)) { + DQN_MEMCPY(result.data, prefix.data, prefix.size); + int space = DQN_CAST(int)DQN_MAX((result.size - prefix.size) + 1, 0); /*null-terminator*/ + STB_SPRINTF_DECORATE(snprintf)(result.data + prefix.size, space, fmt, number); + } + return result; +} + +DQN_API uint64_t Dqn_Bin_HexBufferToU64(char const *hex, Dqn_usize size) +{ + Dqn_usize trim_size = size; + char const *trim_hex = hex; + if (trim_size >= 2) { + if (trim_hex[0] == '0' && (trim_hex[1] == 'x' || trim_hex[1] == 'X')) { + trim_size -= 2; + trim_hex += 2; + } + } + + DQN_ASSERT(DQN_CAST(Dqn_usize)(trim_size * 4 / 8) /*maximum amount of bytes represented in the hex string*/ <= sizeof(uint64_t)); + + uint64_t result = 0; + Dqn_usize bits_written = 0; + Dqn_usize max_size = DQN_MIN(size, 8 /*bytes*/ * 2 /*hex chars per byte*/); + for (Dqn_usize hex_index = 0; hex_index < max_size; hex_index++, bits_written += 4) { + char ch = trim_hex[hex_index]; + if (!Dqn_Char_IsHex(ch)) + break; + uint8_t val = Dqn_Char_HexToU8(ch); + Dqn_usize bit_shift = 60 - bits_written; + result |= (DQN_CAST(uint64_t)val << bit_shift); + } + + result >>= (64 - bits_written); // Shift the remainder digits to the end. + return result; +} + +DQN_API uint64_t Dqn_Bin_HexToU64(Dqn_String8 hex) +{ + uint64_t result = Dqn_Bin_HexBufferToU64(hex.data, hex.size); + return result; +} + +DQN_API bool Dqn_Bin_BytesToHexBuffer(void const *src, Dqn_usize src_size, char *dest, Dqn_usize dest_size) +{ + if (!src || !dest) + return false; + + if (!DQN_CHECK(dest_size >= src_size * 2)) + return false; + + char const *HEX = "0123456789abcdef"; + unsigned char const *src_u8 = DQN_CAST(unsigned char const *)src; + for (Dqn_usize src_index = 0, dest_index = 0; src_index < src_size; src_index++) { + char byte = src_u8[src_index]; + char hex01 = (byte >> 4) & 0b1111; + char hex02 = (byte >> 0) & 0b1111; + dest[dest_index++] = HEX[(int)hex01]; + dest[dest_index++] = HEX[(int)hex02]; + } + + return true; +} + +DQN_API char *Dqn_Bin_BytesToHexBufferArena(Dqn_Arena *arena, void const *src, Dqn_usize size) +{ + char *result = size > 0 ? Dqn_Arena_NewArray(arena, char, (size * 2) + 1 /*null terminate*/, Dqn_ZeroMem_No) : nullptr; + if (result) { + bool converted = Dqn_Bin_BytesToHexBuffer(src, size, result, size * 2); + DQN_ASSERT(converted); + result[size * 2] = 0; + } + return result; +} + +DQN_API Dqn_String8 Dqn_Bin_BytesToHexArena(Dqn_Arena *arena, void const *src, Dqn_usize size) +{ + Dqn_String8 result = {}; + result.data = Dqn_Bin_BytesToHexBufferArena(arena, src, size); + if (result.data) + result.size = size * 2; + return result; +} + +DQN_API Dqn_usize Dqn_Bin_HexBufferToBytes(char const *hex, Dqn_usize hex_size, void *dest, Dqn_usize dest_size) +{ + Dqn_usize result = 0; + if (!hex || hex_size <= 0) + return result; + + Dqn_usize trim_size = 0; + char const *trim_hex = Dqn_Bin_HexBufferTrim0x(hex, + hex_size, + &trim_size); + + // NOTE: Trimmed hex can be "0xf" -> "f" or "0xAB" -> "AB" + // Either way, the size can be odd or even, hence we round up to the nearest + // multiple of two to ensure that we calculate the min buffer size orrectly. + Dqn_usize trim_size_rounded_up = trim_size + (trim_size % 2); + Dqn_usize min_buffer_size = trim_size_rounded_up / 2; + if (dest_size < min_buffer_size || trim_size <= 0) { + DQN_ASSERTF(dest_size >= min_buffer_size, "Insufficient buffer size for converting hex to binary"); + return result; + } + + result = Dqn_Bin_HexBufferToBytesUnchecked(trim_hex, + trim_size, + dest, + dest_size); + return result; +} + +DQN_API Dqn_usize Dqn_Bin_HexBufferToBytesUnchecked(char const *hex, Dqn_usize hex_size, void *dest, Dqn_usize dest_size) +{ + Dqn_usize result = 0; + unsigned char *dest_u8 = DQN_CAST(unsigned char *)dest; + + for (Dqn_usize hex_index = 0; + hex_index < hex_size; + hex_index += 2, result += 1) + { + char hex01 = hex[hex_index]; + char hex02 = (hex_index + 1 < hex_size) ? hex[hex_index + 1] : 0; + + char bit4_01 = (hex01 >= '0' && hex01 <= '9') ? 0 + (hex01 - '0') + : (hex01 >= 'a' && hex01 <= 'f') ? 10 + (hex01 - 'a') + : (hex01 >= 'A' && hex01 <= 'F') ? 10 + (hex01 - 'A') + : 0; + + char bit4_02 = (hex02 >= '0' && hex02 <= '9') ? 0 + (hex02 - '0') + : (hex02 >= 'a' && hex02 <= 'f') ? 10 + (hex02 - 'a') + : (hex02 >= 'A' && hex02 <= 'F') ? 10 + (hex02 - 'A') + : 0; + + char byte = (bit4_01 << 4) | (bit4_02 << 0); + dest_u8[result] = byte; + } + + DQN_ASSERT(result <= dest_size); + return result; +} + +DQN_API Dqn_usize Dqn_Bin_HexToBytesUnchecked(Dqn_String8 hex, void *dest, Dqn_usize dest_size) +{ + Dqn_usize result = Dqn_Bin_HexBufferToBytesUnchecked(hex.data, hex.size, dest, dest_size); + return result; +} + +DQN_API Dqn_usize Dqn_Bin_HexToBytes(Dqn_String8 hex, void *dest, Dqn_usize dest_size) +{ + Dqn_usize result = Dqn_Bin_HexBufferToBytes(hex.data, hex.size, dest, dest_size); + return result; +} + +DQN_API char *Dqn_Bin_HexBufferToBytesArena(Dqn_Arena *arena, char const *hex, Dqn_usize size, Dqn_usize *real_size) +{ + char *result = nullptr; + if (!arena || !hex || size <= 0) + return result; + + Dqn_usize trim_size = 0; + char const *trim_hex = Dqn_Bin_HexBufferTrim0x(hex, + size, + &trim_size); + + Dqn_usize binary_size = trim_size / 2; + result = Dqn_Arena_NewArray(arena, char, binary_size, Dqn_ZeroMem_No); + if (result) { + Dqn_usize convert_size = Dqn_Bin_HexBufferToBytesUnchecked(trim_hex, trim_size, result, binary_size); + if (real_size) + *real_size = convert_size; + } + return result; +} + +DQN_API Dqn_String8 Dqn_Bin_HexToBytesArena(Dqn_Arena *arena, Dqn_String8 hex) +{ + Dqn_String8 result = {}; + result.data = Dqn_Bin_HexBufferToBytesArena(arena, hex.data, hex.size, &result.size); + return result; +} +#endif // !defined(DQN_NO_BIN) + +// NOTE: [$BITS] Dqn_Bit =========================================================================== +DQN_API void Dqn_Bit_UnsetInplace(uint64_t *flags, uint64_t bitfield) +{ + *flags = (*flags & ~bitfield); +} + +DQN_API void Dqn_Bit_SetInplace(uint64_t *flags, uint64_t bitfield) +{ + *flags = (*flags | bitfield); +} + +DQN_API bool Dqn_Bit_IsSet(uint64_t bits, uint64_t bits_to_set) +{ + auto result = DQN_CAST(bool)((bits & bits_to_set) == bits_to_set); + return result; +} + +DQN_API bool Dqn_Bit_IsNotSet(uint64_t bits, uint64_t bits_to_check) +{ + auto result = !Dqn_Bit_IsSet(bits, bits_to_check); + return result; +} + +// NOTE: [$SAFE] Dqn_Safe ========================================================================== +DQN_API int64_t Dqn_Safe_AddI64(int64_t a, int64_t b) +{ + int64_t result = DQN_CHECKF(a <= INT64_MAX - b, "a=%zd, b=%zd", a, b) ? (a + b) : INT64_MAX; + return result; +} + +DQN_API int64_t Dqn_Safe_MulI64(int64_t a, int64_t b) +{ + int64_t result = DQN_CHECKF(a <= INT64_MAX / b, "a=%zd, b=%zd", a, b) ? (a * b) : INT64_MAX; + return result; +} + +DQN_API uint64_t Dqn_Safe_AddU64(uint64_t a, uint64_t b) +{ + uint64_t result = DQN_CHECKF(a <= UINT64_MAX - b, "a=%zu, b=%zu", a, b) ? (a + b) : UINT64_MAX; + return result; +} + +DQN_API uint64_t Dqn_Safe_SubU64(uint64_t a, uint64_t b) +{ + uint64_t result = DQN_CHECKF(a >= b, "a=%zu, b=%zu", a, b) ? (a - b) : 0; + return result; +} + +DQN_API uint64_t Dqn_Safe_MulU64(uint64_t a, uint64_t b) +{ + uint64_t result = DQN_CHECKF(a <= UINT64_MAX / b, "a=%zu, b=%zu", a, b) ? (a * b) : UINT64_MAX; + return result; +} + +DQN_API uint32_t Dqn_Safe_SubU32(uint32_t a, uint32_t b) +{ + uint32_t result = DQN_CHECKF(a >= b, "a=%u, b=%u", a, b) ? (a - b) : 0; + return result; +} + +// NOTE: Dqn_Safe_SaturateCastUSizeToI* +// ----------------------------------------------------------------------------- +// INT*_MAX literals will be promoted to the type of uintmax_t as uintmax_t is +// the highest possible rank (unsigned > signed). +DQN_API int Dqn_Safe_SaturateCastUSizeToInt(Dqn_usize val) +{ + int result = DQN_CHECK(DQN_CAST(uintmax_t)val <= INT_MAX) ? DQN_CAST(int)val : INT_MAX; + return result; +} + +DQN_API int8_t Dqn_Safe_SaturateCastUSizeToI8(Dqn_usize val) +{ + int8_t result = DQN_CHECK(DQN_CAST(uintmax_t)val <= INT8_MAX) ? DQN_CAST(int8_t)val : INT8_MAX; + return result; +} + +DQN_API int16_t Dqn_Safe_SaturateCastUSizeToI16(Dqn_usize val) +{ + int16_t result = DQN_CHECK(DQN_CAST(uintmax_t)val <= INT16_MAX) ? DQN_CAST(int16_t)val : INT16_MAX; + return result; +} + +DQN_API int32_t Dqn_Safe_SaturateCastUSizeToI32(Dqn_usize val) +{ + int32_t result = DQN_CHECK(DQN_CAST(uintmax_t)val <= INT32_MAX) ? DQN_CAST(int32_t)val : INT32_MAX; + return result; +} + +DQN_API int64_t Dqn_Safe_SaturateCastUSizeToI64(Dqn_usize val) +{ + int64_t result = DQN_CHECK(DQN_CAST(uintmax_t)val <= INT64_MAX) ? DQN_CAST(int64_t)val : INT64_MAX; + return result; +} + +// NOTE: Dqn_Safe_SaturateCastUSizeToU* +// ----------------------------------------------------------------------------- +// Both operands are unsigned and the lowest rank operand will be promoted to +// match the highest rank operand. +DQN_API uint8_t Dqn_Safe_SaturateCastUSizeToU8(Dqn_usize val) +{ + uint8_t result = DQN_CHECK(val <= UINT8_MAX) ? DQN_CAST(uint8_t)val : UINT8_MAX; + return result; +} + +DQN_API uint16_t Dqn_Safe_SaturateCastUSizeToU16(Dqn_usize val) +{ + uint16_t result = DQN_CHECK(val <= UINT16_MAX) ? DQN_CAST(uint16_t)val : UINT16_MAX; + return result; +} + +DQN_API uint32_t Dqn_Safe_SaturateCastUSizeToU32(Dqn_usize val) +{ + uint32_t result = DQN_CHECK(val <= UINT32_MAX) ? DQN_CAST(uint32_t)val : UINT32_MAX; + return result; +} + +DQN_API uint64_t Dqn_Safe_SaturateCastUSizeToU64(Dqn_usize val) +{ + uint64_t result = DQN_CHECK(val <= UINT64_MAX) ? DQN_CAST(uint64_t)val : UINT64_MAX; + return result; +} + +// NOTE: Dqn_Safe_SaturateCastU64ToU* +// ----------------------------------------------------------------------------- +// Both operands are unsigned and the lowest rank operand will be promoted to +// match the highest rank operand. +DQN_API unsigned int Dqn_Safe_SaturateCastU64ToUInt(uint64_t val) +{ + unsigned int result = DQN_CHECK(val <= UINT8_MAX) ? DQN_CAST(unsigned int)val : UINT_MAX; + return result; +} + +DQN_API uint8_t Dqn_Safe_SaturateCastU64ToU8(uint64_t val) +{ + uint8_t result = DQN_CHECK(val <= UINT8_MAX) ? DQN_CAST(uint8_t)val : UINT8_MAX; + return result; +} + +DQN_API uint16_t Dqn_Safe_SaturateCastU64ToU16(uint64_t val) +{ + uint16_t result = DQN_CHECK(val <= UINT16_MAX) ? DQN_CAST(uint16_t)val : UINT16_MAX; + return result; +} + +DQN_API uint32_t Dqn_Safe_SaturateCastU64ToU32(uint64_t val) +{ + uint32_t result = DQN_CHECK(val <= UINT32_MAX) ? DQN_CAST(uint32_t)val : UINT32_MAX; + return result; +} + + +// NOTE: Dqn_Safe_SaturateCastISizeToI* +// ----------------------------------------------------------------------------- +// Both operands are signed so the lowest rank operand will be promoted to +// match the highest rank operand. +DQN_API int Dqn_Safe_SaturateCastISizeToInt(Dqn_isize val) +{ + DQN_ASSERT(val >= INT_MIN && val <= INT_MAX); + int result = DQN_CAST(int)DQN_CLAMP(val, INT_MIN, INT_MAX); + return result; +} + +DQN_API int8_t Dqn_Safe_SaturateCastISizeToI8(Dqn_isize val) +{ + DQN_ASSERT(val >= INT8_MIN && val <= INT8_MAX); + int8_t result = DQN_CAST(int8_t)DQN_CLAMP(val, INT8_MIN, INT8_MAX); + return result; +} + +DQN_API int16_t Dqn_Safe_SaturateCastISizeToI16(Dqn_isize val) +{ + DQN_ASSERT(val >= INT16_MIN && val <= INT16_MAX); + int16_t result = DQN_CAST(int16_t)DQN_CLAMP(val, INT16_MIN, INT16_MAX); + return result; +} + +DQN_API int32_t Dqn_Safe_SaturateCastISizeToI32(Dqn_isize val) +{ + DQN_ASSERT(val >= INT32_MIN && val <= INT32_MAX); + int32_t result = DQN_CAST(int32_t)DQN_CLAMP(val, INT32_MIN, INT32_MAX); + return result; +} + +DQN_API int64_t Dqn_Safe_SaturateCastISizeToI64(Dqn_isize val) +{ + DQN_ASSERT(val >= INT64_MIN && val <= INT64_MAX); + int64_t result = DQN_CAST(int64_t)DQN_CLAMP(val, INT64_MIN, INT64_MAX); + return result; +} + +// NOTE: Dqn_Safe_SaturateCastISizeToU* +// ----------------------------------------------------------------------------- +// If the value is a negative integer, we clamp to 0. Otherwise, we know that +// the value is >=0, we can upcast safely to bounds check against the maximum +// allowed value. +DQN_API unsigned int Dqn_Safe_SaturateCastISizeToUInt(Dqn_isize val) +{ + unsigned int result = 0; + if (DQN_CHECK(val >= DQN_CAST(Dqn_isize)0)) { + if (DQN_CHECK(DQN_CAST(uintmax_t)val <= UINT_MAX)) + result = DQN_CAST(unsigned int)val; + else + result = UINT_MAX; + } + return result; +} + +DQN_API uint8_t Dqn_Safe_SaturateCastISizeToU8(Dqn_isize val) +{ + uint8_t result = 0; + if (DQN_CHECK(val >= DQN_CAST(Dqn_isize)0)) { + if (DQN_CHECK(DQN_CAST(uintmax_t)val <= UINT8_MAX)) + result = DQN_CAST(uint8_t)val; + else + result = UINT8_MAX; + } + return result; +} + +DQN_API uint16_t Dqn_Safe_SaturateCastISizeToU16(Dqn_isize val) +{ + uint16_t result = 0; + if (DQN_CHECK(val >= DQN_CAST(Dqn_isize)0)) { + if (DQN_CHECK(DQN_CAST(uintmax_t)val <= UINT16_MAX)) + result = DQN_CAST(uint16_t)val; + else + result = UINT16_MAX; + } + return result; +} + +DQN_API uint32_t Dqn_Safe_SaturateCastISizeToU32(Dqn_isize val) +{ + uint32_t result = 0; + if (DQN_CHECK(val >= DQN_CAST(Dqn_isize)0)) { + if (DQN_CHECK(DQN_CAST(uintmax_t)val <= UINT32_MAX)) + result = DQN_CAST(uint32_t)val; + else + result = UINT32_MAX; + } + return result; +} + +DQN_API uint64_t Dqn_Safe_SaturateCastISizeToU64(Dqn_isize val) +{ + uint64_t result = 0; + if (DQN_CHECK(val >= DQN_CAST(Dqn_isize)0)) { + if (DQN_CHECK(DQN_CAST(uintmax_t)val <= UINT64_MAX)) + result = DQN_CAST(uint64_t)val; + else + result = UINT64_MAX; + } + return result; +} + +// NOTE: Dqn_Safe_SaturateCastI64To* +// ----------------------------------------------------------------------------- +// Both operands are signed so the lowest rank operand will be promoted to +// match the highest rank operand. +DQN_API Dqn_isize Dqn_Safe_SaturateCastI64ToISize(int64_t val) +{ + DQN_CHECK(val >= DQN_ISIZE_MIN && val <= DQN_ISIZE_MAX); + Dqn_isize result = DQN_CAST(int64_t)DQN_CLAMP(val, DQN_ISIZE_MIN, DQN_ISIZE_MAX); + return result; +} + +DQN_API int8_t Dqn_Safe_SaturateCastI64ToI8(int64_t val) +{ + DQN_CHECK(val >= INT8_MIN && val <= INT8_MAX); + int8_t result = DQN_CAST(int8_t)DQN_CLAMP(val, INT8_MIN, INT8_MAX); + return result; +} + +DQN_API int16_t Dqn_Safe_SaturateCastI64ToI16(int64_t val) +{ + DQN_CHECK(val >= INT16_MIN && val <= INT16_MAX); + int16_t result = DQN_CAST(int16_t)DQN_CLAMP(val, INT16_MIN, INT16_MAX); + return result; +} + +DQN_API int32_t Dqn_Safe_SaturateCastI64ToI32(int64_t val) +{ + DQN_CHECK(val >= INT32_MIN && val <= INT32_MAX); + int32_t result = DQN_CAST(int32_t)DQN_CLAMP(val, INT32_MIN, INT32_MAX); + return result; +} + +// NOTE: Dqn_Safe_SaturateCastIntTo* +// ----------------------------------------------------------------------------- +DQN_API int8_t Dqn_Safe_SaturateCastIntToI8(int val) +{ + DQN_CHECK(val >= INT8_MIN && val <= INT8_MAX); + int8_t result = DQN_CAST(int8_t)DQN_CLAMP(val, INT8_MIN, INT8_MAX); + return result; +} + +DQN_API int16_t Dqn_Safe_SaturateCastIntToI16(int val) +{ + DQN_CHECK(val >= INT16_MIN && val <= INT16_MAX); + int16_t result = DQN_CAST(int16_t)DQN_CLAMP(val, INT16_MIN, INT16_MAX); + return result; +} + +DQN_API uint8_t Dqn_Safe_SaturateCastIntToU8(int val) +{ + uint8_t result = 0; + if (DQN_CHECK(val >= DQN_CAST(Dqn_isize)0)) { + if (DQN_CHECK(DQN_CAST(uintmax_t)val <= UINT8_MAX)) + result = DQN_CAST(uint8_t)val; + else + result = UINT8_MAX; + } + return result; +} + +DQN_API uint16_t Dqn_Safe_SaturateCastIntToU16(int val) +{ + uint16_t result = 0; + if (DQN_CHECK(val >= DQN_CAST(Dqn_isize)0)) { + if (DQN_CHECK(DQN_CAST(uintmax_t)val <= UINT16_MAX)) + result = DQN_CAST(uint16_t)val; + else + result = UINT16_MAX; + } + return result; +} + +DQN_API uint32_t Dqn_Safe_SaturateCastIntToU32(int val) +{ + static_assert(sizeof(val) <= sizeof(uint32_t), "Sanity check to allow simplifying of casting"); + uint32_t result = 0; + if (DQN_CHECK(val >= 0)) + result = DQN_CAST(uint32_t)val; + return result; +} + +DQN_API uint64_t Dqn_Safe_SaturateCastIntToU64(int val) +{ + static_assert(sizeof(val) <= sizeof(uint64_t), "Sanity check to allow simplifying of casting"); + uint64_t result = 0; + if (DQN_CHECK(val >= 0)) + result = DQN_CAST(uint64_t)val; + return result; +} + +// NOTE: [$MISC] Misc ============================================================================== +DQN_API int Dqn_SNPrintFDotTruncate(char *buffer, int size, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + int size_required = STB_SPRINTF_DECORATE(vsnprintf)(buffer, size, fmt, args); + int result = DQN_MAX(DQN_MIN(size_required, size - 1), 0); + if (result == size - 1) { + buffer[size - 2] = '.'; + buffer[size - 3] = '.'; + } + va_end(args); + return result; +} + +DQN_API Dqn_U64String Dqn_U64ToString(uint64_t val, char separator) +{ + Dqn_U64String result = {}; + if (val == 0) { + result.data[result.size++] = '0'; + } else { + // NOTE: The number is written in reverse because we form the string by + // dividing by 10, so we write it in, then reverse it out after all is + // done. + Dqn_U64String temp = {}; + for (size_t digit_count = 0; val > 0; digit_count++) { + if (separator && (digit_count != 0) && (digit_count % 3 == 0)) { + temp.data[temp.size++] = separator; + } + + auto digit = DQN_CAST(char)(val % 10); + temp.data[temp.size++] = '0' + digit; + val /= 10; + } + + // NOTE: Reverse the string + for (size_t temp_index = temp.size - 1; temp_index < temp.size; temp_index--) { + char ch = temp.data[temp_index]; + result.data[result.size++] = ch; + } + } + + return result; +} + +// NOTE: [$DLIB] Dqn_Library ======================================================================= +Dqn_Library *g_dqn_library; + +DQN_API Dqn_Library *Dqn_Library_Init() +{ + if (!g_dqn_library) { + static Dqn_Library default_instance = {}; + g_dqn_library = &default_instance; + } + + Dqn_Library *result = g_dqn_library; + Dqn_TicketMutex_Begin(&result->lib_mutex); + DQN_DEFER { Dqn_TicketMutex_End(&result->lib_mutex); }; + if (result->lib_init) + return result; + + // ============================================================================================= + + Dqn_ArenaCatalog_Init(&result->arena_catalog, &result->arena_catalog_backup_arena); + result->lib_init = true; + + #if !defined(DQN_NO_PROFILER) + result->profiler = &result->profiler_default_instance; + #endif + + // ============================================================================================= + + { // NOTE: Query OS page size + SYSTEM_INFO system_info = {}; + #if defined(DQN_OS_WIN32) + GetSystemInfo(&system_info); + result->os_page_size = system_info.dwPageSize; + result->os_alloc_granularity = system_info.dwAllocationGranularity; + #else + // TODO(doyle): Get the proper page size from the OS. + result->os_page_size = DQN_KILOBYTES(4); + result->os_alloc_granularity = DQN_KILOBYTES(64); + #endif + } + + // ============================================================================================= + + #if defined(DQN_LEAK_TRACING) // NOTE: Initialise the allocation leak tracker + { + result->alloc_tracking_disabled = true; // TODO(doyle): @robust Does this need to be atomic? + + Dqn_String8 sample_backtrace = Dqn_String8_InitCString8(b_stacktrace_get_string()); + Dqn_String8 clean_backtrace = Dqn_Debug_CleanStackTrace(sample_backtrace); + result->stack_trace_offset_to_our_call_stack = DQN_CAST(uint16_t)(sample_backtrace.size - clean_backtrace.size); + free(sample_backtrace.data); + + result->alloc_table = Dqn_DSMap_Init(4096); + result->alloc_tracking_disabled = false; + } + #endif + + return result; +} + +DQN_API void Dqn_Library_SetPointer(Dqn_Library *library) +{ + if (library) + g_dqn_library = library; +} + +#if !defined(DQN_NO_PROFILER) +DQN_API void Dqn_Library_SetProfiler(Dqn_Profiler *profiler) +{ + if (profiler) + g_dqn_library->profiler = profiler; +} +#endif + +DQN_API void Dqn_Library_SetLogCallback(Dqn_LogProc *proc, void *user_data) +{ + g_dqn_library->log_callback = proc; + g_dqn_library->log_user_data = user_data; +} + +DQN_API void Dqn_Library_SetLogFile(FILE *file) +{ + Dqn_TicketMutex_Begin(&g_dqn_library->log_file_mutex); + g_dqn_library->log_file = file; + g_dqn_library->log_to_file = file ? true : false; + Dqn_TicketMutex_End(&g_dqn_library->log_file_mutex); +} + +DQN_API void Dqn_Library_DumpThreadContextArenaStat(Dqn_String8 file_path) +{ + #if defined(DQN_DEBUG_THREAD_CONTEXT) + // NOTE: Open a file to write the arena stats to + FILE *file = nullptr; + fopen_s(&file, file_path.data, "a+b"); + if (file) { + Dqn_Log_ErrorF("Failed to dump thread context arenas [file=%.*s]", DQN_STRING_FMT(file_path)); + return; + } + + // NOTE: Copy the stats from library book-keeping + // NOTE: Extremely short critical section, copy the stats then do our + // work on it. + Dqn_ArenaStat stats[Dqn_CArray_CountI(g_dqn_library->thread_context_arena_stats)]; + int stats_size = 0; + + Dqn_TicketMutex_Begin(&g_dqn_library->thread_context_mutex); + stats_size = g_dqn_library->thread_context_arena_stats_count; + DQN_MEMCPY(stats, g_dqn_library->thread_context_arena_stats, sizeof(stats[0]) * stats_size); + Dqn_TicketMutex_End(&g_dqn_library->thread_context_mutex); + + // NOTE: Print the cumulative stat + Dqn_DateHMSTimeString now = Dqn_Date_HMSLocalTimeStringNow(); + fprintf(file, + "Time=%.*s %.*s | Thread Context Arenas | Count=%d\n", + now.date_size, now.date, + now.hms_size, now.hms, + g_dqn_library->thread_context_arena_stats_count); + + // NOTE: Write the cumulative thread arena data + { + Dqn_ArenaStat stat = {}; + for (Dqn_usize index = 0; index < stats_size; index++) { + Dqn_ArenaStat const *current = stats + index; + stat.capacity += current->capacity; + stat.used += current->used; + stat.wasted += current->wasted; + stat.blocks += current->blocks; + + stat.capacity_hwm = DQN_MAX(stat.capacity_hwm, current->capacity_hwm); + stat.used_hwm = DQN_MAX(stat.used_hwm, current->used_hwm); + stat.wasted_hwm = DQN_MAX(stat.wasted_hwm, current->wasted_hwm); + stat.blocks_hwm = DQN_MAX(stat.blocks_hwm, current->blocks_hwm); + } + + Dqn_ArenaStatString stats_string = Dqn_Arena_StatString(&stat); + fprintf(file, " [ALL] CURR %.*s\n", stats_string.size, stats_string.data); + } + + // NOTE: Print individual thread arena data + for (Dqn_usize index = 0; index < stats_size; index++) { + Dqn_ArenaStat const *current = stats + index; + Dqn_ArenaStatString current_string = Dqn_Arena_StatString(current); + fprintf(file, " [%03d] CURR %.*s\n", DQN_CAST(int)index, current_string.size, current_string.data); + } + + fclose(file); + Dqn_Log_InfoF("Dumped thread context arenas [file=%.*s]", DQN_STRING_FMT(file_path)); + #else + (void)file_path; + #endif // #if defined(DQN_DEBUG_THREAD_CONTEXT) +} + + +#if !defined(DQN_NO_PROFILER) +// NOTE: [$PROF] Dqn_Profiler ====================================================================== +Dqn_ProfilerZoneScope::Dqn_ProfilerZoneScope(uint16_t label) +{ + zone = Dqn_Profiler_BeginZone(label); +} + +Dqn_ProfilerZoneScope::~Dqn_ProfilerZoneScope() +{ + Dqn_Profiler_EndZone(zone); +} + +Dqn_ProfilerZone Dqn_Profiler_BeginZone(uint16_t label) +{ + Dqn_ProfilerAnchor *anchor = Dqn_Profiler_AnchorBuffer(Dqn_ProfilerAnchorBuffer_Back) + label; + Dqn_ProfilerZone result = {}; + result.begin_tsc = Dqn_CPU_TSC(); + result.label = label; + result.parent_zone = g_dqn_library->profiler->parent_zone; + result.elapsed_tsc_at_zone_start = anchor->tsc_inclusive; + g_dqn_library->profiler->parent_zone = label; + return result; +} + +void Dqn_Profiler_EndZone(Dqn_ProfilerZone zone) +{ + uint64_t elapsed_tsc = Dqn_CPU_TSC() - zone.begin_tsc; + Dqn_ProfilerAnchor *anchor_buffer = Dqn_Profiler_AnchorBuffer(Dqn_ProfilerAnchorBuffer_Back); + Dqn_ProfilerAnchor *anchor = anchor_buffer + zone.label; + + anchor->hit_count++; + anchor->tsc_inclusive = zone.elapsed_tsc_at_zone_start + elapsed_tsc; + anchor->tsc_exclusive += elapsed_tsc; + + Dqn_ProfilerAnchor *parent_anchor = anchor_buffer + zone.parent_zone; + parent_anchor->tsc_exclusive -= elapsed_tsc; + g_dqn_library->profiler->parent_zone = zone.parent_zone; +} + +Dqn_ProfilerAnchor *Dqn_Profiler_AnchorBuffer(Dqn_ProfilerAnchorBuffer buffer) +{ + uint8_t offset = buffer == Dqn_ProfilerAnchorBuffer_Back ? 0 : 1; + uint8_t anchor_buffer = (g_dqn_library->profiler->active_anchor_buffer + offset) % DQN_ARRAY_UCOUNT(g_dqn_library->profiler->anchors); + Dqn_ProfilerAnchor *result = g_dqn_library->profiler->anchors[anchor_buffer]; + return result; +} + +void Dqn_Profiler_SwapAnchorBuffer(uint32_t anchor_count) +{ + g_dqn_library->profiler->active_anchor_buffer++; + Dqn_ProfilerAnchor *anchors = Dqn_Profiler_AnchorBuffer(Dqn_ProfilerAnchorBuffer_Back); + DQN_MEMSET(anchors, 0, anchor_count * sizeof(g_dqn_library->profiler->anchors[0][0])); +} +#endif // !defined(DQN_NO_PROFILER) diff --git a/dqn_helpers.h b/dqn_helpers.h new file mode 100644 index 0000000..e938c7e --- /dev/null +++ b/dqn_helpers.h @@ -0,0 +1,655 @@ +#if !defined(DQN_NO_JSON_BUILDER) +// NOTE: [$JSON] Dqn_JSONBuilder =================================================================== +// Basic helper class to construct JSON output to a string +// TODO(dqn): We need to write tests for this +// +// NOTE: API ======================================================================================= +// @proc Dqn_JSONBuilder_Build +// @desc Convert the internal JSON buffer in the builder into a string. +// @param[in] arena The allocator to use to build the string + +// @proc Dqn_JSONBuilder_KeyValue, Dqn_JSONBuilder_KeyValueF +// @desc Add a JSON key value pair untyped. The value is emitted directly +// without checking the contents of value. +// +// All other functions internally call into this function which is the main +// workhorse of the builder. + +// @proc Dqn_JSON_Builder_ObjectEnd +// @desc End a JSON object in the builder, generates internally a '}' string + +// @proc Dqn_JSON_Builder_ArrayEnd +// @desc End a JSON array in the builder, generates internally a ']' string + +// @proc Dqn_JSONBuilder_LiteralNamed +// @desc Add a named JSON key-value object whose value is directly written to +// the following '"": ' (e.g. useful for emitting the 'null' +// value) + +// @proc Dqn_JSONBuilder_U64Named, Dqn_JSONBuilder_U64, +// Dqn_JSONBuilder_I64Named, Dqn_JSONBuilder_I64, +// Dqn_JSONBuilder_F64Named, Dqn_JSONBuilder_F64, +// Dqn_JSONBuilder_BoolNamed, Dqn_JSONBuilder_Bool, +// @desc Add the named JSON data type as a key-value object. Generates +// internally the string '"": ' + +enum Dqn_JSONBuilderItem +{ + Dqn_JSONBuilderItem_Empty, + Dqn_JSONBuilderItem_OpenContainer, + Dqn_JSONBuilderItem_CloseContainer, + Dqn_JSONBuilderItem_KeyValue, +}; + +struct Dqn_JSONBuilder +{ + bool use_stdout; // When set, ignore the string builder and dump immediately to stdout + Dqn_String8Builder string_builder; // (Internal) + int indent_level; // (Internal) + int spaces_per_indent; // The number of spaces per indent level + Dqn_JSONBuilderItem last_item; +}; + +#define Dqn_JSONBuilder_Object(builder) \ + DQN_DEFER_LOOP(Dqn_JSONBuilder_ObjectBegin(builder), \ + Dqn_JSONBuilder_ObjectEnd(builder)) + +#define Dqn_JSONBuilder_ObjectNamed(builder, name) \ + DQN_DEFER_LOOP(Dqn_JSONBuilder_ObjectBeginNamed(builder, name), \ + Dqn_JSONBuilder_ObjectEnd(builder)) + +#define Dqn_JSONBuilder_Array(builder) \ + DQN_DEFER_LOOP(Dqn_JSONBuilder_ArrayBegin(builder), \ + Dqn_JSONBuilder_ArrayEnd(builder)) + +#define Dqn_JSONBuilder_ArrayNamed(builder, name) \ + DQN_DEFER_LOOP(Dqn_JSONBuilder_ArrayBeginNamed(builder, name), \ + Dqn_JSONBuilder_ArrayEnd(builder)) + + +DQN_API Dqn_JSONBuilder Dqn_JSONBuilder_Init (Dqn_Allocator allocator, int spaces_per_indent); +DQN_API Dqn_String8 Dqn_JSONBuilder_Build (Dqn_JSONBuilder const *builder, Dqn_Allocator allocator); +DQN_API void Dqn_JSONBuilder_KeyValue (Dqn_JSONBuilder *builder, Dqn_String8 key, Dqn_String8 value); +DQN_API void Dqn_JSONBuilder_KeyValueF (Dqn_JSONBuilder *builder, Dqn_String8 key, char const *value_fmt, ...); +DQN_API void Dqn_JSONBuilder_ObjectBeginNamed(Dqn_JSONBuilder *builder, Dqn_String8 name); +DQN_API void Dqn_JSONBuilder_ObjectEnd (Dqn_JSONBuilder *builder); +DQN_API void Dqn_JSONBuilder_ArrayBeginNamed (Dqn_JSONBuilder *builder, Dqn_String8 name); +DQN_API void Dqn_JSONBuilder_ArrayEnd (Dqn_JSONBuilder *builder); +DQN_API void Dqn_JSONBuilder_StringNamed (Dqn_JSONBuilder *builder, Dqn_String8 key, Dqn_String8 value); +DQN_API void Dqn_JSONBuilder_LiteralNamed (Dqn_JSONBuilder *builder, Dqn_String8 key, Dqn_String8 value); +DQN_API void Dqn_JSONBuilder_U64Named (Dqn_JSONBuilder *builder, Dqn_String8 key, uint64_t value); +DQN_API void Dqn_JSONBuilder_I64Named (Dqn_JSONBuilder *builder, Dqn_String8 key, int64_t value); +DQN_API void Dqn_JSONBuilder_F64Named (Dqn_JSONBuilder *builder, Dqn_String8 key, double value, int decimal_places); +DQN_API void Dqn_JSONBuilder_BoolNamed (Dqn_JSONBuilder *builder, Dqn_String8 key, bool value); + +#define Dqn_JSONBuilder_ObjectBegin(builder) Dqn_JSONBuilder_ObjectBeginNamed(builder, DQN_STRING8("")) +#define Dqn_JSONBuilder_ArrayBegin(builder) Dqn_JSONBuilder_ArrayBeginNamed(builder, DQN_STRING8("")) +#define Dqn_JSONBuilder_String(builder, value) Dqn_JSONBuilder_StringNamed(builder, DQN_STRING8(""), value) +#define Dqn_JSONBuilder_Literal(builder, value) Dqn_JSONBuilder_LiteralNamed(builder, DQN_STRING8(""), value) +#define Dqn_JSONBuilder_U64(builder, value) Dqn_JSONBuilder_U64Named(builder, DQN_STRING8(""), value) +#define Dqn_JSONBuilder_I64(builder, value) Dqn_JSONBuilder_I64Named(builder, DQN_STRING8(""), value) +#define Dqn_JSONBuilder_F64(builder, value) Dqn_JSONBuilder_F64Named(builder, DQN_STRING8(""), value) +#define Dqn_JSONBuilder_Bool(builder, value) Dqn_JSONBuilder_BoolNamed(builder, DQN_STRING8(""), value) +#endif // !defined(DQN_NO_JSON_BUIDLER) + +#if !defined(DQN_NO_BIN) +// NOTE: [$BHEX] Dqn_Bin =========================================================================== +// NOTE: API ======================================================================================= +// @proc Dqn_Bin_U64ToHexU64String +// @desc Convert a 64 bit number to a hex string +// @param[in] number Number to convert to hexadecimal representation +// @param[in] flags Bit flags from Dqn_BinHexU64StringFlags to customise the +// output of the hexadecimal string. +// @return The hexadecimal representation of the number. This string is always +// null-terminated. + +// @proc Dqn_Bin_U64ToHex +// @copybrief Dqn_Bin_U64ToHexU64String + +// @param[in] allocator The memory allocator to use for the memory of the +// hexadecimal string. +// @copyparams Dqn_Bin_U64ToHexU64String + +// @proc Dqn_Bin_HexBufferToU64 +// @desc Convert a hexadecimal string a 64 bit value. +// Asserts if the hex string is too big to be converted into a 64 bit number. + +// @proc Dqn_Bin_HexToU64 +// @copydoc Dqn_Bin_HexToU64 + +// @proc Dqn_Bin_BytesToHexBuffer +// @desc Convert a binary buffer into its hex representation into dest. +// +// The dest buffer must be large enough to contain the hex representation, i.e. +// atleast (src_size * 2). +// +// @return True if the conversion into the dest buffer was successful, false +// otherwise (e.g. invalid arguments). + +// @proc Dqn_Bin_BytesToHexBufferArena +// @desc Convert a series of bytes into a string +// @return A null-terminated hex string, null pointer if allocation failed + +// @proc Dqn_Bin_BytesToHexArena +// @copydoc Dqn_Bin_BytesToHexBufferArena +// @return A hex string, the string is invalid if conversion failed. + +// @proc Dqn_Bin_HexBufferToBytes +// @desc Convert a hex string into binary at `dest`. +// +// The dest buffer must be large enough to contain the binary representation, +// i.e. atleast ceil(hex_size / 2). This function will strip whitespace, +// leading 0x/0X prefix from the string before conversion. +// +// @param[in] hex The hexadecimal string to convert +// @param[in] hex_size Size of the hex buffer. This function can handle an odd +// size hex string i.e. "fff" produces 0xfff0. +// @param[out] dest Buffer to write the bytes to +// @param[out] dest_size Maximum number of bytes to write to dest +// +// @return The number of bytes written to `dest_size`, this value will *never* +// be greater than `dest_size`. + +// @proc Dqn_Bin_HexToBytes +// @desc String8 variant of @see Dqn_Bin_HexBufferToBytes + +// @proc Dqn_Bin_StringHexBufferToBytesUnchecked +// @desc Unchecked variant of @see Dqn_Bin_HexBufferToBytes +// +// This function skips some string checks, it assumes the hex is a valid hex +// stream and that the arguments are valid e.g. no trimming or 0x prefix +// stripping is performed + +// @proc Dqn_Bin_String +// @desc String8 variant of @see Dqn_Bin_HexBufferToBytesUnchecked + +// @proc Dqn_Bin_HexBufferToBytesArena +// Dynamic allocating variant of @see Dqn_Bin_HexBufferToBytesUnchecked +// +// @param[in] arena The arena to allocate the bytes from +// @param[in] hex Hex string to convert into bytes +// @param[in] size Size of the hex string +// @param[out] real_size The size of the buffer returned by the function +// +// @return The byte representation of the hex string. + +// @proc Dqn_Bin_HexToBytesArena +// @copybrief Dqn_Bin_HexBufferToBytesArena +// +// @param[in] arena The arena to allocate the bytes from +// @param[in] hex Hex string to convert into bytes +// +// @return The byte representation of the hex string. + +struct Dqn_BinHexU64String +{ + char data[2 /*0x*/ + 16 /*hex*/ + 1 /*null-terminator*/]; + uint8_t size; +}; + +enum Dqn_BinHexU64StringFlags +{ + Dqn_BinHexU64StringFlags_No0xPrefix = 1 << 0, /// Remove the 0x prefix from the string + Dqn_BinHexU64StringFlags_UppercaseHex = 1 << 1, /// Use uppercase ascii characters for hex +}; + +DQN_API char const * Dqn_Bin_HexBufferTrim0x (char const *hex, Dqn_usize size, Dqn_usize *real_size); +DQN_API Dqn_String8 Dqn_Bin_HexTrim0x (Dqn_String8 string); + +DQN_API Dqn_BinHexU64String Dqn_Bin_U64ToHexU64String (uint64_t number, uint32_t flags); +DQN_API Dqn_String8 Dqn_Bin_U64ToHex (Dqn_Allocator allocator, uint64_t number, uint32_t flags); + +DQN_API uint64_t Dqn_Bin_HexBufferToU64 (char const *hex, Dqn_usize size); +DQN_API uint64_t Dqn_Bin_HexToU64 (Dqn_String8 hex); + +DQN_API Dqn_String8 Dqn_Bin_BytesToHexArena (Dqn_Arena *arena, void const *src, Dqn_usize size); +DQN_API char * Dqn_Bin_BytesToHexBufferArena (Dqn_Arena *arena, void const *src, Dqn_usize size); +DQN_API bool Dqn_Bin_BytesToHexBuffer (void const *src, Dqn_usize src_size, char *dest, Dqn_usize dest_size); + +DQN_API Dqn_usize Dqn_Bin_HexBufferToBytesUnchecked(char const *hex, Dqn_usize hex_size, void *dest, Dqn_usize dest_size); +DQN_API Dqn_usize Dqn_Bin_HexBufferToBytes (char const *hex, Dqn_usize hex_size, void *dest, Dqn_usize dest_size); +DQN_API char * Dqn_Bin_HexBufferToBytesArena (Dqn_Arena *arena, char const *hex, Dqn_usize hex_size, Dqn_usize *real_size); + +DQN_API Dqn_usize Dqn_Bin_HexToBytesUnchecked (Dqn_String8 hex, void *dest, Dqn_usize dest_size); +DQN_API Dqn_usize Dqn_Bin_HexToBytes (Dqn_String8 hex, void *dest, Dqn_usize dest_size); +DQN_API Dqn_String8 Dqn_Bin_HexToBytesArena (Dqn_Arena *arena, Dqn_String8 hex); +#endif // !defined(DQN_NO_BIN) + +// NOTE: [$BSEA] Dqn_BinarySearch ================================================================== +template +using Dqn_BinarySearchLessThanProc = bool(T const &lhs, T const &rhs); + +template +DQN_FORCE_INLINE bool Dqn_BinarySearch_DefaultLessThan(T const &lhs, T const &rhs); + +enum Dqn_BinarySearchType +{ + /// Index of the match. If no match is found, found is set to false and the + /// index is set to 0 + Dqn_BinarySearchType_Match, + + /// Index after the match. If no match is found, found is set to false and + /// the index is set to one past the closest match. + Dqn_BinarySearchType_OnePastMatch, +}; + +struct Dqn_BinarySearchResult +{ + bool found; + Dqn_usize index; +}; + +template +Dqn_BinarySearchResult +Dqn_BinarySearch(T const *array, + Dqn_usize array_size, + T const &find, + Dqn_BinarySearchType type = Dqn_BinarySearchType_Match, + Dqn_BinarySearchLessThanProc less_than = Dqn_BinarySearch_DefaultLessThan); + + +template DQN_FORCE_INLINE bool Dqn_BinarySearch_DefaultLessThan(T const &lhs, T const &rhs) +{ + bool result = lhs < rhs; + return result; +} + +template +Dqn_BinarySearchResult +Dqn_BinarySearch(T const *array, + Dqn_usize array_size, + T const &find, + Dqn_BinarySearchType type, + Dqn_BinarySearchLessThanProc less_than) +{ + Dqn_BinarySearchResult result = {}; + Dqn_usize head = 0; + Dqn_usize tail = array_size - 1; + if (array && array_size > 0) { + while (!result.found && head <= tail) { + Dqn_usize mid = (head + tail) / 2; + T const &value = array[mid]; + if (less_than(find, value)) { + tail = mid - 1; + if (mid == 0) + break; + } else if (less_than(value, find)) { + head = mid + 1; + } else { + result.found = true; + result.index = mid; + } + } + } + + if (type == Dqn_BinarySearchType_OnePastMatch) + result.index = result.found ? result.index + 1 : tail + 1; + else + DQN_ASSERT(type == Dqn_BinarySearchType_Match); + + return result; +} + +// NOTE: [$BITS] Dqn_Bit =========================================================================== +DQN_API void Dqn_Bit_UnsetInplace(uint32_t *flags, uint32_t bitfield); +DQN_API void Dqn_Bit_SetInplace (uint32_t *flags, uint32_t bitfield); +DQN_API bool Dqn_Bit_IsSet (uint32_t bits, uint32_t bits_to_set); +DQN_API bool Dqn_Bit_IsNotSet (uint32_t bits, uint32_t bits_to_check); + +// NOTE: [$SAFE] Dqn_Safe ========================================================================== +// @proc Dqn_Safe_AddI64, Dqn_Safe_MulI64 +// @desc Add/multiply 2 I64's together, safe asserting that the operation does +// not overflow. +// @return The result of operation, INT64_MAX if it overflowed. + +// @proc Dqn_Safe_AddU64, Dqn_Safe_MulU64 +// @desc Add/multiply 2 U64's together, safe asserting that the operation does +// not overflow. +// @return The result of operation, UINT64_MAX if it overflowed. + +// @proc Dqn_Safe_SubU64, Dqn_Safe_SubU32 +// @desc Subtract 2xU64 or 2xU32's together, safe asserting that the operation +// does not overflow. +// @return The result of operation, 0 if it overflowed. + +// @proc Dqn_Safe_SaturateCastUSizeToInt, +// Dqn_Safe_SaturateCastUSizeToI8, +// Dqn_Safe_SaturateCastUSizeToI16, +// Dqn_Safe_SaturateCastUSizeToI32, +// Dqn_Safe_SaturateCastUSizeToI64 +// +// Dqn_Safe_SaturateCastU64ToUInt, +// Dqn_Safe_SaturateCastU64ToU8, +// Dqn_Safe_SaturateCastU64ToU16, +// Dqn_Safe_SaturateCastU64ToU32, +// +// Dqn_Safe_SaturateCastUSizeToU8, +// Dqn_Safe_SaturateCastUSizeToU16, +// Dqn_Safe_SaturateCastUSizeToU32, +// Dqn_Safe_SaturateCastUSizeToU64 +// +// Dqn_Safe_SaturateCastISizeToInt, +// Dqn_Safe_SaturateCastISizeToI8, +// Dqn_Safe_SaturateCastISizeToI16, +// Dqn_Safe_SaturateCastISizeToI32, +// Dqn_Safe_SaturateCastISizeToI64, +// +// int Dqn_Safe_SaturateCastISizeToUInt, +// Dqn_Safe_SaturateCastISizeToU8, +// Dqn_Safe_SaturateCastISizeToU16, +// Dqn_Safe_SaturateCastISizeToU32, +// Dqn_Safe_SaturateCastISizeToU64, +// +// Dqn_Safe_SaturateCastI64ToISize, +// Dqn_Safe_SaturateCastI64ToI8, +// Dqn_Safe_SaturateCastI64ToI16, +// Dqn_Safe_SaturateCastI64ToI32, +// +// Dqn_Safe_SaturateCastIntToI8, +// Dqn_Safe_SaturateCastIntToU8, +// Dqn_Safe_SaturateCastIntToU16, +// Dqn_Safe_SaturateCastIntToU32, +// Dqn_Safe_SaturateCastIntToU64, +// +// @desc Truncate the lhs operand to the right clamping the result to the max +// value of the desired data type. Safe asserts if clamping occurs. +// +// The following sentinel values are returned when saturated, +// USize -> Int: INT_MAX +// USize -> I8: INT8_MAX +// USize -> I16: INT16_MAX +// USize -> I32: INT32_MAX +// USize -> I64: INT64_MAX +// +// U64 -> UInt: UINT_MAX +// U64 -> U8: UINT8_MAX +// U64 -> U16: UINT16_MAX +// U64 -> U32: UINT32_MAX +// +// USize -> U8: UINT8_MAX +// USize -> U16: UINT16_MAX +// USize -> U32: UINT32_MAX +// USize -> U64: UINT64_MAX +// +// ISize -> Int: INT_MIN or INT_MAX +// ISize -> I8: INT8_MIN or INT8_MAX +// ISize -> I16: INT16_MIN or INT16_MAX +// ISize -> I32: INT32_MIN or INT32_MAX +// ISize -> I64: INT64_MIN or INT64_MAX +// +// ISize -> UInt: 0 or UINT_MAX +// ISize -> U8: 0 or UINT8_MAX +// ISize -> U16: 0 or UINT16_MAX +// ISize -> U32: 0 or UINT32_MAX +// ISize -> U64: 0 or UINT64_MAX +// +// I64 -> ISize: DQN_ISIZE_MIN or DQN_ISIZE_MAX +// I64 -> I8: INT8_MIN or INT8_MAX +// I64 -> I16: INT16_MIN or INT16_MAX +// I64 -> I32: INT32_MIN or INT32_MAX +// +// Int -> I8: INT8_MIN or INT8_MAX +// Int -> I16: INT16_MIN or INT16_MAX +// Int -> U8: 0 or UINT8_MAX +// Int -> U16: 0 or UINT16_MAX +// Int -> U32: 0 or UINT32_MAX +// Int -> U64: 0 or UINT64_MAX + +DQN_API int64_t Dqn_Safe_AddI64 (int64_t a, int64_t b); +DQN_API int64_t Dqn_Safe_MulI64 (int64_t a, int64_t b); + +DQN_API uint64_t Dqn_Safe_AddU64 (uint64_t a, uint64_t b); +DQN_API uint64_t Dqn_Safe_MulU64 (uint64_t a, uint64_t b); + +DQN_API uint64_t Dqn_Safe_SubU64 (uint64_t a, uint64_t b); +DQN_API uint32_t Dqn_Safe_SubU32 (uint32_t a, uint32_t b); + +DQN_API int Dqn_Safe_SaturateCastUSizeToInt (Dqn_usize val); +DQN_API int8_t Dqn_Safe_SaturateCastUSizeToI8 (Dqn_usize val); +DQN_API int16_t Dqn_Safe_SaturateCastUSizeToI16 (Dqn_usize val); +DQN_API int32_t Dqn_Safe_SaturateCastUSizeToI32 (Dqn_usize val); +DQN_API int64_t Dqn_Safe_SaturateCastUSizeToI64 (Dqn_usize val); + +DQN_API unsigned int Dqn_Safe_SaturateCastU64ToUInt (uint64_t val); +DQN_API uint8_t Dqn_Safe_SaturateCastU64ToU8 (uint64_t val); +DQN_API uint16_t Dqn_Safe_SaturateCastU64ToU16 (uint64_t val); +DQN_API uint32_t Dqn_Safe_SaturateCastU64ToU32 (uint64_t val); + +DQN_API uint8_t Dqn_Safe_SaturateCastUSizeToU8 (Dqn_usize val); +DQN_API uint16_t Dqn_Safe_SaturateCastUSizeToU16 (Dqn_usize val); +DQN_API uint32_t Dqn_Safe_SaturateCastUSizeToU32 (Dqn_usize val); +DQN_API uint64_t Dqn_Safe_SaturateCastUSizeToU64 (Dqn_usize val); + +DQN_API int Dqn_Safe_SaturateCastISizeToInt (Dqn_isize val); +DQN_API int8_t Dqn_Safe_SaturateCastISizeToI8 (Dqn_isize val); +DQN_API int16_t Dqn_Safe_SaturateCastISizeToI16 (Dqn_isize val); +DQN_API int32_t Dqn_Safe_SaturateCastISizeToI32 (Dqn_isize val); +DQN_API int64_t Dqn_Safe_SaturateCastISizeToI64 (Dqn_isize val); + +DQN_API unsigned int Dqn_Safe_SaturateCastISizeToUInt(Dqn_isize val); +DQN_API uint8_t Dqn_Safe_SaturateCastISizeToU8 (Dqn_isize val); +DQN_API uint16_t Dqn_Safe_SaturateCastISizeToU16 (Dqn_isize val); +DQN_API uint32_t Dqn_Safe_SaturateCastISizeToU32 (Dqn_isize val); +DQN_API uint64_t Dqn_Safe_SaturateCastISizeToU64 (Dqn_isize val); + +DQN_API Dqn_isize Dqn_Safe_SaturateCastI64ToISize (int64_t val); +DQN_API int8_t Dqn_Safe_SaturateCastI64ToI8 (int64_t val); +DQN_API int16_t Dqn_Safe_SaturateCastI64ToI16 (int64_t val); +DQN_API int32_t Dqn_Safe_SaturateCastI64ToI32 (int64_t val); + +DQN_API int8_t Dqn_Safe_SaturateCastIntToI8 (int val); +DQN_API int16_t Dqn_Safe_SaturateCastIntToI16 (int val); +DQN_API uint8_t Dqn_Safe_SaturateCastIntToU8 (int val); +DQN_API uint16_t Dqn_Safe_SaturateCastIntToU16 (int val); +DQN_API uint32_t Dqn_Safe_SaturateCastIntToU32 (int val); +DQN_API uint64_t Dqn_Safe_SaturateCastIntToU64 (int val); + +// NOTE: [$MISC] Misc ============================================================================== +// NOTE: API ======================================================================================= +// @proc Dqn_SNPrintFDotTruncate +// @desc Write the format string to the buffer truncating with a trailing ".." +// if there is insufficient space in the buffer followed by null-terminating +// the buffer (uses stb_sprintf underneath). +// @return The size of the string written to the buffer *not* including the +// null-terminator. +// +// @proc Dqn_U64ToString +// @desc Convert a 64 bit unsigned value to its string representation. +// @param[in] val Value to convert into a string +// @param[in] separator The separator to insert every 3 digits. Set this to +// 0 if no separator is desired. + +struct Dqn_U64String +{ + char data[27+1]; // NOTE(dqn): 27 is the maximum size of uint64_t including a separtor + uint8_t size; +}; + +DQN_API int Dqn_SNPrintFDotTruncate(char *buffer, int size, char const *fmt, ...); +DQN_API Dqn_U64String Dqn_U64ToString (uint64_t val, char separator); + +#if !defined(DQN_NO_PROFILER) +// NOTE: [$PROF] Dqn_Profiler ====================================================================== +// A profiler based off Casey Muratori's Computer Enhance course, Performance +// Aware Programming. This profiler measures function elapsed time using the +// CPU's time stamp counter (e.g. rdtsc) providing a rough cycle count +// that can be converted into a duration. +// +// This profiler uses a double buffer scheme for storing profiling markers. +// After an application's typical update/frame cycle you can swap the profiler's +// buffer whereby the front buffer contains the previous frames profiling +// metrics and the back buffer will be populated with the new frame's profiling +// metrics. +#if 0 + uint64_t tsc_per_seconds = Dqn_EstimateTSCPerSecond(/*duration_ms_to_gauge_tsc_frequency*/ 100); + for (;;) { // Main update loop + enum Zone { Zone_MainLoop, Zone_Count }; + Dqn_ProfilerZone profiler_zone_main_update = Dqn_Profiler_BeginZone(Zone_MainLoop); + Dqn_ProfilerAnchor *anchors = Dqn_ProfilerAnchorBuffer(Dqn_ProfilerAnchorBuffer_Front); + for (size_t index = 0; index < Zone_Count; index++) { + Dqn_ProfilerAnchor *anchor = anchors + index; + printf("%.*s[%u] %zu cycles (%.1fms)\n", + DQN_STRING_FMT(anchor->label), + anchor->hit_count, + anchor->tsc_inclusive, + anchor->tsc_inclusive * tsc_per_seconds * 1000.f); + } + Dqn_Profiler_EndZone(&profiler_zone_main_update); + Dqn_Profiler_SwapAnchorBuffer(Zone_Count); // Should occur after all profiling zones are ended! + } +#endif + +// @proc Dqn_Profiler_AnchorBuffer +// Retrieve the requested buffer from the profiler for writing/reading +// profiling metrics. +// +// @param buffer Enum that lets you specify which buffer to grab from the +// profiler. The front buffer contains the previous frame's profiling metrics +// and the back buffer is where the profiler is current writing to. +// +// For end user intents and purposes, you likely only need to read the front +// buffer which contain the metrics that you can visualise regarding the most +// profiling metrics recorded. + +#if !defined(DQN_PROFILER_ANCHOR_BUFFER_SIZE) + #define DQN_PROFILER_ANCHOR_BUFFER_SIZE 128 +#endif + +struct Dqn_ProfilerAnchor +{ + // Inclusive refers to the time spent to complete the function call + // including all children functions. + // + // Exclusive refers to the time spent in the function, not including any + // time spent in children functions that we call that are also being + // profiled. If we recursively call into ourselves, the time we spent in + // our function is accumulated. + uint64_t tsc_inclusive; + uint64_t tsc_exclusive; + uint16_t hit_count; +}; + +struct Dqn_ProfilerZone +{ + uint16_t label; + uint64_t begin_tsc; + uint16_t parent_zone; + uint64_t elapsed_tsc_at_zone_start; +}; + +#if defined(__cplusplus) +struct Dqn_ProfilerZoneScope +{ + Dqn_ProfilerZoneScope(uint16_t label); + ~Dqn_ProfilerZoneScope(); + Dqn_ProfilerZone zone; +}; +#define Dqn_Profiler_ZoneScope(label) auto DQN_UNIQUE_NAME(profile_zone_##label_) = Dqn_ProfilerZoneScope(label) +#endif + +enum Dqn_ProfilerAnchorBuffer +{ + Dqn_ProfilerAnchorBuffer_Back, + Dqn_ProfilerAnchorBuffer_Front, +}; + +struct Dqn_Profiler +{ + Dqn_ProfilerAnchor anchors[2][DQN_PROFILER_ANCHOR_BUFFER_SIZE]; + uint8_t active_anchor_buffer; + uint16_t parent_zone; +}; + +Dqn_ProfilerZone Dqn_Profiler_BeginZone (uint16_t label); +void Dqn_Profiler_EndZone (Dqn_ProfilerZone zone); +Dqn_ProfilerAnchor *Dqn_Profiler_AnchorBuffer (Dqn_ProfilerAnchorBuffer buffer); +void Dqn_Profiler_SwapAnchorBuffer (uint32_t anchor_count); +#endif // !defined(DQN_NO_PROFILER) + +// NOTE: [$DLIB] Dqn_Library ======================================================================= +// Book-keeping data for the library and allow customisation of certain features +// provided. +// +// NOTE: API ======================================================================================= +// @proc Dqn_Library_SetLogCallback +// @desc Update the default logging function, all logging functions will run through +// this callback +// @param[in] proc The new logging function, set to nullptr to revert back to +// the default logger. +// @param[in] user_data A user defined parameter to pass to the callback + +// @proc Dqn_Library_SetLogFile +// @param[in] file Pass in nullptr to turn off writing logs to disk, otherwise +// point it to the FILE that you wish to write to. + +// @proc Dqn_Library_DumpThreadContextArenaStat +// @desc Dump the per-thread arena statistics to the specified file + +struct Dqn_Library +{ + bool lib_init; + Dqn_TicketMutex lib_mutex; + + Dqn_LogProc *log_callback; ///< Set this pointer to override the logging routine + void * log_user_data; + bool log_to_file; ///< Output logs to file as well as standard out + void * log_file; ///< TODO(dqn): Hmmm, how should we do this... ? + Dqn_TicketMutex log_file_mutex; ///< Is locked when instantiating the log_file for the first time + bool log_no_colour; ///< Disable colours in the logging output + + /// The backup arena to use if no arena is passed into Dqn_Library_Init + Dqn_Arena arena_catalog_backup_arena; + Dqn_ArenaCatalog arena_catalog; + + // NOTE: Leak Tracing ========================================================================== + + bool allocs_are_allowed_to_leak; + bool alloc_tracking_disabled; + #if defined(DQN_LEAK_TRACING) + #if defined(DQN_NO_DSMAP) + #error "DSMap is required for allocation tracing" + #endif + + // TODO(doyle): @robust Setting some of these flags is not multi-thread safe + uint16_t stack_trace_offset_to_our_call_stack; + Dqn_TicketMutex alloc_table_mutex; + Dqn_DSMap alloc_table; + #endif + + // NOTE: OS ==================================================================================== + + #if defined(DQN_OS_WIN32) + LARGE_INTEGER win32_qpc_frequency; + Dqn_TicketMutex win32_bcrypt_rng_mutex; + void *win32_bcrypt_rng_handle; + #endif + + uint32_t os_page_size; + uint32_t os_alloc_granularity; + + // NOTE: Thread Context ======================================================================== + + #if defined(DQN_DEBUG_THREAD_CONTEXT) + Dqn_TicketMutex thread_context_mutex; + Dqn_ArenaStat thread_context_arena_stats[256]; + uint8_t thread_context_arena_stats_count; + #endif + + // NOTE: Profiler ============================================================================== + + #if !defined(DQN_NO_PROFILER) + Dqn_Profiler *profiler; + Dqn_Profiler profiler_default_instance; + #endif +} extern *g_dqn_library; + +// NOTE: API ======================================================================================= +DQN_API Dqn_Library *Dqn_Library_Init (); +DQN_API void Dqn_Library_SetPointer (Dqn_Library *library); +#if !defined(DQN_NO_PROFILER) +DQN_API void Dqn_Library_SetProfiler (Dqn_Profiler *profiler); +#endif +DQN_API void Dqn_Library_SetLogCallback (Dqn_LogProc *proc, void *user_data); +DQN_API void Dqn_Library_SetLogFile (void *file); +DQN_API void Dqn_Library_DumpThreadContextArenaStat(Dqn_String8 file_path); + diff --git a/dqn_math.cpp b/dqn_math.cpp index eedebbe..62d9d0d 100644 --- a/dqn_math.cpp +++ b/dqn_math.cpp @@ -1,5 +1,6 @@ #if !defined(DQN_NO_V2) // NOTE: [$VEC2] Vector2 =========================================================================== +// NOTE: Dqn_V2I DQN_API bool operator!=(Dqn_V2I lhs, Dqn_V2I rhs) { bool result = !(lhs == rhs); @@ -132,7 +133,140 @@ DQN_API Dqn_V2I &operator+=(Dqn_V2I &lhs, Dqn_V2I rhs) return lhs; } -// NOTE: Vector2 +// NOTE: Dqn_V2U16 +DQN_API bool operator!=(Dqn_V2U16 lhs, Dqn_V2U16 rhs) +{ + bool result = !(lhs == rhs); + return result; +} + +DQN_API bool operator==(Dqn_V2U16 lhs, Dqn_V2U16 rhs) +{ + bool result = (lhs.x == rhs.x) && (lhs.y == rhs.y); + return result; +} + +DQN_API bool operator>=(Dqn_V2U16 lhs, Dqn_V2U16 rhs) +{ + bool result = (lhs.x >= rhs.x) && (lhs.y >= rhs.y); + return result; +} + +DQN_API bool operator<=(Dqn_V2U16 lhs, Dqn_V2U16 rhs) +{ + bool result = (lhs.x <= rhs.x) && (lhs.y <= rhs.y); + return result; +} + +DQN_API bool operator<(Dqn_V2U16 lhs, Dqn_V2U16 rhs) +{ + bool result = (lhs.x < rhs.x) && (lhs.y < rhs.y); + return result; +} + +DQN_API bool operator>(Dqn_V2U16 lhs, Dqn_V2U16 rhs) +{ + bool result = (lhs.x > rhs.x) && (lhs.y > rhs.y); + return result; +} + +DQN_API Dqn_V2U16 operator-(Dqn_V2U16 lhs, Dqn_V2U16 rhs) +{ + Dqn_V2U16 result = Dqn_V2U16_InitNx2(lhs.x - rhs.x, lhs.y - rhs.y); + return result; +} + +DQN_API Dqn_V2U16 operator+(Dqn_V2U16 lhs, Dqn_V2U16 rhs) +{ + Dqn_V2U16 result = Dqn_V2U16_InitNx2(lhs.x + rhs.x, lhs.y + rhs.y); + return result; +} + +DQN_API Dqn_V2U16 operator*(Dqn_V2U16 lhs, Dqn_V2U16 rhs) +{ + Dqn_V2U16 result = Dqn_V2U16_InitNx2(lhs.x * rhs.x, lhs.y * rhs.y); + return result; +} + +DQN_API Dqn_V2U16 operator*(Dqn_V2U16 lhs, Dqn_f32 rhs) +{ + Dqn_V2U16 result = Dqn_V2U16_InitNx2(lhs.x * rhs, lhs.y * rhs); + return result; +} + +DQN_API Dqn_V2U16 operator*(Dqn_V2U16 lhs, int32_t rhs) +{ + Dqn_V2U16 result = Dqn_V2U16_InitNx2(lhs.x * rhs, lhs.y * rhs); + return result; +} + +DQN_API Dqn_V2U16 operator/(Dqn_V2U16 lhs, Dqn_V2U16 rhs) +{ + Dqn_V2U16 result = Dqn_V2U16_InitNx2(lhs.x / rhs.x, lhs.y / rhs.y); + return result; +} + +DQN_API Dqn_V2U16 operator/(Dqn_V2U16 lhs, Dqn_f32 rhs) +{ + Dqn_V2U16 result = Dqn_V2U16_InitNx2(lhs.x / rhs, lhs.y / rhs); + return result; +} + +DQN_API Dqn_V2U16 operator/(Dqn_V2U16 lhs, int32_t rhs) +{ + Dqn_V2U16 result = Dqn_V2U16_InitNx2(lhs.x / rhs, lhs.y / rhs); + return result; +} + +DQN_API Dqn_V2U16 &operator*=(Dqn_V2U16 &lhs, Dqn_V2U16 rhs) +{ + lhs = lhs * rhs; + return lhs; +} + +DQN_API Dqn_V2U16 &operator*=(Dqn_V2U16 &lhs, Dqn_f32 rhs) +{ + lhs = lhs * rhs; + return lhs; +} + +DQN_API Dqn_V2U16 &operator*=(Dqn_V2U16 &lhs, int32_t rhs) +{ + lhs = lhs * rhs; + return lhs; +} + +DQN_API Dqn_V2U16 &operator/=(Dqn_V2U16 &lhs, Dqn_V2U16 rhs) +{ + lhs = lhs / rhs; + return lhs; +} + +DQN_API Dqn_V2U16 &operator/=(Dqn_V2U16 &lhs, Dqn_f32 rhs) +{ + lhs = lhs / rhs; + return lhs; +} + +DQN_API Dqn_V2U16 &operator/=(Dqn_V2U16 &lhs, int32_t rhs) +{ + lhs = lhs / rhs; + return lhs; +} + +DQN_API Dqn_V2U16 &operator-=(Dqn_V2U16 &lhs, Dqn_V2U16 rhs) +{ + lhs = lhs - rhs; + return lhs; +} + +DQN_API Dqn_V2U16 &operator+=(Dqn_V2U16 &lhs, Dqn_V2U16 rhs) +{ + lhs = lhs + rhs; + return lhs; +} + +// NOTE: Dqn_V2 DQN_API bool operator!=(Dqn_V2 lhs, Dqn_V2 rhs) { bool result = !(lhs == rhs); @@ -874,7 +1008,7 @@ DQN_API bool Dqn_Rect_Intersects(Dqn_Rect a, Dqn_Rect b) DQN_API Dqn_Rect Dqn_Rect_Intersection(Dqn_Rect a, Dqn_Rect b) { - Dqn_Rect result = {}; + Dqn_Rect result = Dqn_Rect_InitV2x2(a.pos, Dqn_V2_InitNx1(0)); if (Dqn_Rect_Intersects(a, b)) { Dqn_V2 a_min = a.pos; Dqn_V2 a_max = a.pos + a.size; @@ -907,6 +1041,79 @@ DQN_API Dqn_Rect Dqn_Rect_Union(Dqn_Rect a, Dqn_Rect b) Dqn_Rect result = Dqn_Rect_InitV2x2(min, max - min); return result; } + +DQN_API Dqn_RectMinMax Dqn_Rect_MinMax(Dqn_Rect a) +{ + Dqn_RectMinMax result = {}; + result.min = a.pos; + result.max = a.pos + a.size; + return result; +} + +DQN_API Dqn_Rect Dqn_Rect_CutLeftClip(Dqn_Rect *rect, Dqn_f32 amount, Dqn_RectCutClip clip) +{ + Dqn_f32 min_x = rect->pos.x; + Dqn_f32 max_x = rect->pos.x + rect->size.w; + Dqn_f32 result_max_x = min_x + amount; + if (clip) + result_max_x = DQN_MIN(result_max_x, max_x); + Dqn_Rect result = Dqn_Rect_InitNx4(min_x, rect->pos.y, result_max_x - min_x, rect->size.h); + rect->pos.x = result_max_x; + rect->size.w = max_x - result_max_x; + return result; +} + +DQN_API Dqn_Rect Dqn_Rect_CutRightClip(Dqn_Rect *rect, Dqn_f32 amount, Dqn_RectCutClip clip) +{ + Dqn_f32 min_x = rect->pos.x; + Dqn_f32 max_x = rect->pos.x + rect->size.w; + Dqn_f32 result_min_x = max_x - amount; + if (clip) + result_min_x = DQN_MAX(result_min_x, 0); + Dqn_Rect result = Dqn_Rect_InitNx4(result_min_x, rect->pos.y, max_x - result_min_x, rect->size.h); + rect->size.w = result_min_x - min_x; + return result; +} + +DQN_API Dqn_Rect Dqn_Rect_CutTopClip(Dqn_Rect *rect, Dqn_f32 amount, Dqn_RectCutClip clip) +{ + Dqn_f32 min_y = rect->pos.y; + Dqn_f32 max_y = rect->pos.y + rect->size.h; + Dqn_f32 result_max_y = min_y + amount; + if (clip) + result_max_y = DQN_MIN(result_max_y, max_y); + Dqn_Rect result = Dqn_Rect_InitNx4(rect->pos.x, min_y, rect->size.w, result_max_y - min_y); + rect->pos.y = result_max_y; + rect->size.h = max_y - result_max_y; + return result; +} + +DQN_API Dqn_Rect Dqn_Rect_CutBottomClip(Dqn_Rect *rect, Dqn_f32 amount, Dqn_RectCutClip clip) +{ + Dqn_f32 min_y = rect->pos.y; + Dqn_f32 max_y = rect->pos.y + rect->size.h; + Dqn_f32 result_min_y = max_y - amount; + if (clip) + result_min_y = DQN_MAX(result_min_y, 0); + Dqn_Rect result = Dqn_Rect_InitNx4(rect->pos.x, result_min_y, rect->size.w, max_y - result_min_y); + rect->size.h = result_min_y - min_y; + return result; +} + +DQN_API Dqn_Rect Dqn_RectCut_Cut(Dqn_RectCut rect_cut, Dqn_V2 size, Dqn_RectCutClip clip) +{ + Dqn_Rect result = {}; + if (rect_cut.rect) { + switch (rect_cut.side) { + case Dqn_RectCutSide_Left: result = Dqn_Rect_CutLeftClip(rect_cut.rect, size.w, clip); break; + case Dqn_RectCutSide_Right: result = Dqn_Rect_CutRightClip(rect_cut.rect, size.w, clip); break; + case Dqn_RectCutSide_Top: result = Dqn_Rect_CutTopClip(rect_cut.rect, size.h, clip); break; + case Dqn_RectCutSide_Bottom: result = Dqn_Rect_CutBottomClip(rect_cut.rect, size.h, clip); break; + } + } + return result; +} + #endif // !defined(DQN_NO_RECT) // NOTE: [$MATH] Other ============================================================================= diff --git a/dqn_math.h b/dqn_math.h index a0d00dc..95cab24 100644 --- a/dqn_math.h +++ b/dqn_math.h @@ -1,13 +1,3 @@ -// NOTE: Table Of Contents ========================================================================= -// Index | Disable #define | Description -// ================================================================================================= -// [$VEC2] Dqn_V2, V2i | DQN_NO_V2 | -// [$VEC3] Dqn_V3, V3i | DQN_NO_V3 | -// [$VEC4] Dqn_V4, V4i | DQN_NO_V4 | -// [$MAT4] Dqn_M4 | DQN_NO_M4 | -// [$RECT] Dqn_Rect | DQN_NO_RECT | -// [$MATH] Other | | -// ================================================================================================= #if defined(DQN_COMPILER_W32_MSVC) #pragma warning(push) #pragma warning(disable: 4201) // warning C4201: nonstandard extension used: nameless struct/union @@ -22,8 +12,8 @@ union Dqn_V2I int32_t data[2]; }; -#define Dqn_V2I_InitNx1(x) DQN_LITERAL(Dqn_V2I){(int32_t)(x), (int32_t)(x)} -#define Dqn_V2I_InitNx2(x, y) DQN_LITERAL(Dqn_V2I){(int32_t)(x), (int32_t)(y)} +#define Dqn_V2I_InitNx1(x) DQN_LITERAL(Dqn_V2I){{(int32_t)(x), (int32_t)(x)}} +#define Dqn_V2I_InitNx2(x, y) DQN_LITERAL(Dqn_V2I){{(int32_t)(x), (int32_t)(y)}} DQN_API bool operator!=(Dqn_V2I lhs, Dqn_V2I rhs); DQN_API bool operator==(Dqn_V2I lhs, Dqn_V2I rhs); @@ -48,6 +38,39 @@ DQN_API Dqn_V2I &operator/=(Dqn_V2I& lhs, int32_t rhs); DQN_API Dqn_V2I &operator-=(Dqn_V2I& lhs, Dqn_V2I rhs); DQN_API Dqn_V2I &operator+=(Dqn_V2I& lhs, Dqn_V2I rhs); +union Dqn_V2U16 +{ + struct { uint16_t x, y; }; + struct { uint16_t w, h; }; + uint16_t data[2]; +}; + +#define Dqn_V2U16_InitNx1(x) DQN_LITERAL(Dqn_V2U16){{(uint16_t)(x), (uint16_t)(x)}} +#define Dqn_V2U16_InitNx2(x, y) DQN_LITERAL(Dqn_V2U16){{(uint16_t)(x), (uint16_t)(y)}} + +DQN_API bool operator!=(Dqn_V2U16 lhs, Dqn_V2U16 rhs); +DQN_API bool operator==(Dqn_V2U16 lhs, Dqn_V2U16 rhs); +DQN_API bool operator>=(Dqn_V2U16 lhs, Dqn_V2U16 rhs); +DQN_API bool operator<=(Dqn_V2U16 lhs, Dqn_V2U16 rhs); +DQN_API bool operator< (Dqn_V2U16 lhs, Dqn_V2U16 rhs); +DQN_API bool operator> (Dqn_V2U16 lhs, Dqn_V2U16 rhs); +DQN_API Dqn_V2U16 operator- (Dqn_V2U16 lhs, Dqn_V2U16 rhs); +DQN_API Dqn_V2U16 operator+ (Dqn_V2U16 lhs, Dqn_V2U16 rhs); +DQN_API Dqn_V2U16 operator* (Dqn_V2U16 lhs, Dqn_V2U16 rhs); +DQN_API Dqn_V2U16 operator* (Dqn_V2U16 lhs, Dqn_f32 rhs); +DQN_API Dqn_V2U16 operator* (Dqn_V2U16 lhs, int32_t rhs); +DQN_API Dqn_V2U16 operator/ (Dqn_V2U16 lhs, Dqn_V2U16 rhs); +DQN_API Dqn_V2U16 operator/ (Dqn_V2U16 lhs, Dqn_f32 rhs); +DQN_API Dqn_V2U16 operator/ (Dqn_V2U16 lhs, int32_t rhs); +DQN_API Dqn_V2U16 &operator*=(Dqn_V2U16& lhs, Dqn_V2U16 rhs); +DQN_API Dqn_V2U16 &operator*=(Dqn_V2U16& lhs, Dqn_f32 rhs); +DQN_API Dqn_V2U16 &operator*=(Dqn_V2U16& lhs, int32_t rhs); +DQN_API Dqn_V2U16 &operator/=(Dqn_V2U16& lhs, Dqn_V2U16 rhs); +DQN_API Dqn_V2U16 &operator/=(Dqn_V2U16& lhs, Dqn_f32 rhs); +DQN_API Dqn_V2U16 &operator/=(Dqn_V2U16& lhs, int32_t rhs); +DQN_API Dqn_V2U16 &operator-=(Dqn_V2U16& lhs, Dqn_V2U16 rhs); +DQN_API Dqn_V2U16 &operator+=(Dqn_V2U16& lhs, Dqn_V2U16 rhs); + union Dqn_V2 { struct { Dqn_f32 x, y; }; @@ -55,8 +78,8 @@ union Dqn_V2 Dqn_f32 data[2]; }; -#define Dqn_V2_InitNx1(x) DQN_LITERAL(Dqn_V2){(Dqn_f32)(x), (Dqn_f32)(x)} -#define Dqn_V2_InitNx2(x, y) DQN_LITERAL(Dqn_V2){(Dqn_f32)(x), (Dqn_f32)(y)} +#define Dqn_V2_InitNx1(x) DQN_LITERAL(Dqn_V2){{(Dqn_f32)(x), (Dqn_f32)(x)}} +#define Dqn_V2_InitNx2(x, y) DQN_LITERAL(Dqn_V2){{(Dqn_f32)(x), (Dqn_f32)(y)}} DQN_API bool operator!=(Dqn_V2 lhs, Dqn_V2 rhs); DQN_API bool operator==(Dqn_V2 lhs, Dqn_V2 rhs); @@ -100,9 +123,9 @@ union Dqn_V3 Dqn_f32 data[3]; }; -#define Dqn_V3_InitNx1(x) DQN_LITERAL(Dqn_V3){(Dqn_f32)(x), (Dqn_f32)(x), (Dqn_f32)(x)} -#define Dqn_V3_InitNx3(x, y, z) DQN_LITERAL(Dqn_V3){(Dqn_f32)(x), (Dqn_f32)(y), (Dqn_f32)(z)} -#define Dqn_V3_InitV2x1_Nx1(xy, z) DQN_LITERAL(Dqn_V3){(Dqn_f32)(xy.x), (Dqn_f32)(xy.y), (Dqn_f32)(z)} +#define Dqn_V3_InitNx1(x) DQN_LITERAL(Dqn_V3){{(Dqn_f32)(x), (Dqn_f32)(x), (Dqn_f32)(x)}} +#define Dqn_V3_InitNx3(x, y, z) DQN_LITERAL(Dqn_V3){{(Dqn_f32)(x), (Dqn_f32)(y), (Dqn_f32)(z)}} +#define Dqn_V3_InitV2x1_Nx1(xy, z) DQN_LITERAL(Dqn_V3){{(Dqn_f32)(xy.x), (Dqn_f32)(xy.y), (Dqn_f32)(z)}} DQN_API bool operator!=(Dqn_V3 lhs, Dqn_V3 rhs); DQN_API bool operator==(Dqn_V3 lhs, Dqn_V3 rhs); @@ -135,10 +158,6 @@ DQN_API Dqn_V3 Dqn_V3_Normalise(Dqn_V3 a); #if !defined(DQN_NO_V4) // NOTE: [$VEC4] Vector4 =========================================================================== -#if defined(DQN_NO_V3) - #error "Dqn_Rect requires Dqn_V3 hence DQN_NO_V3 must *not* be defined" -#endif - #if defined(DQN_COMPILER_W32_MSVC) #pragma warning(push) #pragma warning(disable: 4201) // warning C4201: nonstandard extension used: nameless struct/union @@ -147,12 +166,14 @@ union Dqn_V4 { struct { Dqn_f32 x, y, z, w; }; struct { Dqn_f32 r, g, b, a; }; + #if !defined(DQN_NO_V3) Dqn_V3 rgb; + #endif Dqn_f32 data[4]; }; -#define Dqn_V4_InitNx1(x) DQN_LITERAL(Dqn_V4){(Dqn_f32)(x), (Dqn_f32)(x), (Dqn_f32)(x), (Dqn_f32)(x)} -#define Dqn_V4_InitNx4(x, y, z, w) DQN_LITERAL(Dqn_V4){(Dqn_f32)(x), (Dqn_f32)(y), (Dqn_f32)(z), (Dqn_f32)(w)} +#define Dqn_V4_InitNx1(x) DQN_LITERAL(Dqn_V4){{(Dqn_f32)(x), (Dqn_f32)(x), (Dqn_f32)(x), (Dqn_f32)(x)}} +#define Dqn_V4_InitNx4(x, y, z, w) DQN_LITERAL(Dqn_V4){{(Dqn_f32)(x), (Dqn_f32)(y), (Dqn_f32)(z), (Dqn_f32)(w)}} bool operator!=(Dqn_V4 lhs, Dqn_V4 rhs); bool operator==(Dqn_V4 lhs, Dqn_V4 rhs); @@ -213,7 +234,7 @@ DQN_API Dqn_FString8<256> Dqn_M4_ColumnMajorString(Dqn_M4 mat); // NOTE: [$RECT] Dqn_Rect ========================================================================== #if !defined(DQN_NO_RECT) #if defined(DQN_NO_V2) - #error "Dqn_Rect requires Dqn_V2 hence DQN_NO_V2 must *not* be defined" + #error "Rectangles requires V2, DQN_NO_V2 must not be defined" #endif struct Dqn_Rect @@ -221,19 +242,65 @@ struct Dqn_Rect Dqn_V2 pos, size; }; -#if defined(__cplusplus) -#define Dqn_Rect_InitV2x2(pos, size) Dqn_Rect{pos, size} -#else -#define Dqn_Rect_InitV2x2(pos, size) (Dqn_Rect){pos, size} -#endif +struct Dqn_RectMinMax +{ + Dqn_V2 min, max; +}; -DQN_API bool operator== (const Dqn_Rect& lhs, const Dqn_Rect& rhs); -DQN_API Dqn_V2 Dqn_Rect_Center (Dqn_Rect rect); -DQN_API bool Dqn_Rect_ContainsPoint(Dqn_Rect rect, Dqn_V2 p); -DQN_API bool Dqn_Rect_ContainsRect (Dqn_Rect a, Dqn_Rect b); -DQN_API bool Dqn_Rect_Intersects (Dqn_Rect a, Dqn_Rect b); -DQN_API Dqn_Rect Dqn_Rect_Intersection (Dqn_Rect a, Dqn_Rect b); -DQN_API Dqn_Rect Dqn_Rect_Union (Dqn_Rect a, Dqn_Rect b); +#define Dqn_Rect_InitV2x2(pos, size) DQN_LITERAL(Dqn_Rect){(pos), (size)} +#define Dqn_Rect_InitNx4(pos_x, pos_y, size_w, size_h) DQN_LITERAL(Dqn_Rect){DQN_LITERAL(Dqn_V2){{pos_x, pos_y}}, DQN_LITERAL(Dqn_V2){{size_w, size_h}}} + +DQN_API bool operator== (const Dqn_Rect& lhs, const Dqn_Rect& rhs); +DQN_API Dqn_V2 Dqn_Rect_Center (Dqn_Rect rect); +DQN_API bool Dqn_Rect_ContainsPoint(Dqn_Rect rect, Dqn_V2 p); +DQN_API bool Dqn_Rect_ContainsRect (Dqn_Rect a, Dqn_Rect b); +DQN_API bool Dqn_Rect_Intersects (Dqn_Rect a, Dqn_Rect b); +DQN_API Dqn_Rect Dqn_Rect_Intersection (Dqn_Rect a, Dqn_Rect b); +DQN_API Dqn_Rect Dqn_Rect_Union (Dqn_Rect a, Dqn_Rect b); +DQN_API Dqn_RectMinMax Dqn_Rect_MinMax (Dqn_Rect a); + +enum Dqn_RectCutClip +{ + Dqn_RectCutClip_No, + Dqn_RectCutClip_Yes, +}; + +DQN_API Dqn_Rect Dqn_Rect_CutLeftClip(Dqn_Rect *rect, Dqn_f32 amount, Dqn_RectCutClip clip); +DQN_API Dqn_Rect Dqn_Rect_CutRightClip(Dqn_Rect *rect, Dqn_f32 amount, Dqn_RectCutClip clip); +DQN_API Dqn_Rect Dqn_Rect_CutTopClip(Dqn_Rect *rect, Dqn_f32 amount, Dqn_RectCutClip clip); +DQN_API Dqn_Rect Dqn_Rect_CutBottomClip(Dqn_Rect *rect, Dqn_f32 amount, Dqn_RectCutClip clip); + +#define Dqn_Rect_CutLeft(rect, amount) Dqn_Rect_CutLeftClip(rect, amount, Dqn_RectCutClip_Yes) +#define Dqn_Rect_CutRight(rect, amount) Dqn_Rect_CutRightClip(rect, amount, Dqn_RectCutClip_Yes) +#define Dqn_Rect_CutTop(rect, amount) Dqn_Rect_CutTopClip(rect, amount, Dqn_RectCutClip_Yes) +#define Dqn_Rect_CutBottom(rect, amount) Dqn_Rect_CutBottomClip(rect, amount, Dqn_RectCutClip_Yes) + +#define Dqn_Rect_CutLeftNoClip(rect, amount) Dqn_Rect_CutLeftClip(rect, amount, Dqn_RectCutClip_No) +#define Dqn_Rect_CutRightNoClip(rect, amount) Dqn_Rect_CutRightClip(rect, amount, Dqn_RectCutClip_No) +#define Dqn_Rect_CutTopNoClip(rect, amount) Dqn_Rect_CutTopClip(rect, amount, Dqn_RectCutClip_No) +#define Dqn_Rect_CutBottomNoClip(rect, amount) Dqn_Rect_CutBottomClip(rect, amount, Dqn_RectCutClip_No) + +enum Dqn_RectCutSide +{ + Dqn_RectCutSide_Left, + Dqn_RectCutSide_Right, + Dqn_RectCutSide_Top, + Dqn_RectCutSide_Bottom, +}; + +struct Dqn_RectCut +{ + Dqn_Rect* rect; + Dqn_RectCutSide side; +}; + +#define Dqn_RectCut_Init(rect, side) DQN_LITERAL(Dqn_RectCut){rect, side} +#define Dqn_RectCut_Left(rect) DQN_LITERAL(Dqn_RectCut){rect, Dqn_RectCutSide_Left} +#define Dqn_RectCut_Right(rect) DQN_LITERAL(Dqn_RectCut){rect, Dqn_RectCutSide_Right} +#define Dqn_RectCut_Top(rect) DQN_LITERAL(Dqn_RectCut){rect, Dqn_RectCutSide_Top} +#define Dqn_RectCut_Bottom(rect) DQN_LITERAL(Dqn_RectCut){rect, Dqn_RectCutSide_Bottom} + +DQN_API Dqn_Rect Dqn_RectCut_Cut(Dqn_RectCut rect_cut, Dqn_V2 size, Dqn_RectCutClip clip); #endif // !defined(DQN_NO_RECT) // NOTE: [$MATH] Other ============================================================================= diff --git a/dqn_memory.cpp b/dqn_memory.cpp index 83ac763..1a5c8ec 100644 --- a/dqn_memory.cpp +++ b/dqn_memory.cpp @@ -1,23 +1,21 @@ // NOTE: [$ALLO] Dqn_Allocator ===================================================================== -DQN_API void *Dqn_Allocator_Alloc_(DQN_LEAK_TRACE_FUNCTION Dqn_Allocator allocator, size_t size, uint8_t align, Dqn_ZeroMem zero_mem) +DQN_API void *Dqn_Allocator_Alloc(Dqn_Allocator allocator, size_t size, uint8_t align, Dqn_ZeroMem zero_mem) { void *result = NULL; if (allocator.alloc) { - result = allocator.alloc(DQN_LEAK_TRACE_ARG size, align, zero_mem, allocator.user_context); + result = allocator.alloc(size, align, zero_mem, allocator.user_context); } else { result = DQN_ALLOC(size); - Dqn_Library_LeakTraceAdd(DQN_LEAK_TRACE_ARG result, size); } return result; } -DQN_API void Dqn_Allocator_Dealloc_(DQN_LEAK_TRACE_FUNCTION Dqn_Allocator allocator, void *ptr, size_t size) +DQN_API void Dqn_Allocator_Dealloc(Dqn_Allocator allocator, void *ptr, size_t size) { if (allocator.dealloc) { - allocator.dealloc(DQN_LEAK_TRACE_ARG ptr, size, allocator.user_context); + allocator.dealloc(ptr, size, allocator.user_context); } else { DQN_DEALLOC(ptr, size); - Dqn_Library_LeakTraceMarkFree(DQN_LEAK_TRACE_ARG ptr); } } @@ -79,16 +77,22 @@ DQN_API void *Dqn_VMem_Reserve(Dqn_usize size, Dqn_VMemCommit commit, uint32_t p #else #error "Missing implementation for Dqn_VMem_Reserve" #endif + + Dqn_Debug_TrackAlloc(result, size, (page_flags & Dqn_VMemPage_AllocRecordLeakPermitted)); return result; } DQN_API bool Dqn_VMem_Commit(void *ptr, Dqn_usize size, uint32_t page_flags) { + bool result = false; + if (!ptr || size == 0) + return false; + unsigned long os_page_flags = Dqn_VMem_ConvertPageToOSFlags_(page_flags); #if defined(DQN_OS_WIN32) - bool result = VirtualAlloc(ptr, size, MEM_COMMIT, os_page_flags) != nullptr; + result = VirtualAlloc(ptr, size, MEM_COMMIT, os_page_flags) != nullptr; #elif defined(DQN_OS_UNIX) - bool result = mprotect(ptr, size, os_page_flags) == 0; + result = mprotect(ptr, size, os_page_flags) == 0; #else #error "Missing implementation for Dqn_VMem_Commit" #endif @@ -117,6 +121,7 @@ DQN_API void Dqn_VMem_Release(void *ptr, Dqn_usize size) #else #error "Missing implementation for Dqn_VMem_Release" #endif + Dqn_Debug_TrackDealloc(ptr); } DQN_API int Dqn_VMem_Protect(void *ptr, Dqn_usize size, uint32_t page_flags) @@ -127,8 +132,8 @@ DQN_API int Dqn_VMem_Protect(void *ptr, Dqn_usize size, uint32_t page_flags) static Dqn_String8 const ALIGNMENT_ERROR_MSG = DQN_STRING8("Page protection requires pointers to be page aligned because we " "can only guard memory at a multiple of the page boundary."); - DQN_ASSERTF(Dqn_IsPowerOfTwoAligned(DQN_CAST(uintptr_t)ptr, DQN_VMEM_PAGE_GRANULARITY), "%s", ALIGNMENT_ERROR_MSG.data); - DQN_ASSERTF(Dqn_IsPowerOfTwoAligned(size, DQN_VMEM_PAGE_GRANULARITY), "%s", ALIGNMENT_ERROR_MSG.data); + DQN_ASSERTF(Dqn_IsPowerOfTwoAligned(DQN_CAST(uintptr_t)ptr, g_dqn_library->os_page_size), "%s", ALIGNMENT_ERROR_MSG.data); + DQN_ASSERTF(Dqn_IsPowerOfTwoAligned(size, g_dqn_library->os_page_size), "%s", ALIGNMENT_ERROR_MSG.data); unsigned long os_page_flags = Dqn_VMem_ConvertPageToOSFlags_(page_flags); #if defined(DQN_OS_WIN32) @@ -136,8 +141,12 @@ DQN_API int Dqn_VMem_Protect(void *ptr, Dqn_usize size, uint32_t page_flags) int result = VirtualProtect(ptr, size, os_page_flags, &prev_flags); (void)prev_flags; if (result == 0) { + #if defined(DQN_NO_WIN) + DQN_ASSERTF(result, "VirtualProtect failed"); + #else Dqn_WinErrorMsg error = Dqn_Win_LastError(); DQN_ASSERTF(result, "VirtualProtect failed (%d): %.*s", error.code, error.size, error.data); + #endif } #else int result = mprotect(result->memory, result->size, os_page_flags); @@ -147,112 +156,107 @@ DQN_API int Dqn_VMem_Protect(void *ptr, Dqn_usize size, uint32_t page_flags) return result; } -// NOTE: [$AREN] Dqn_Arena ========================================================================= -DQN_API void Dqn_Arena_CommitFromBlock(Dqn_ArenaBlock *block, Dqn_usize size, Dqn_ArenaCommit commit) +// NOTE: [$MEMB] Dqn_MemBlock ====================================================================== +DQN_API Dqn_usize Dqn_MemBlock_MetadataSize(uint8_t flags) { - Dqn_usize commit_size = 0; - switch (commit) { - case Dqn_ArenaCommit_GetNewPages: { - commit_size = Dqn_PowerOfTwoAlign(size, DQN_VMEM_COMMIT_GRANULARITY); - } break; - - case Dqn_ArenaCommit_EnsureSpace: { - DQN_ASSERT(block->commit >= block->used); - Dqn_usize const unused_commit_space = block->commit - block->used; - if (unused_commit_space < size) - commit_size = Dqn_PowerOfTwoAlign(size - unused_commit_space, DQN_VMEM_COMMIT_GRANULARITY); - } break; - } - - if (commit_size) { - uint32_t page_flags = Dqn_VMemPage_ReadWrite; - char *commit_ptr = DQN_CAST(char *)block->memory + block->commit; - Dqn_VMem_Commit(commit_ptr, commit_size, page_flags); - block->commit += commit_size; - - DQN_ASSERTF(block->commit < block->size, - "Block size should be PoT aligned and its alignment should be greater than the commit granularity [block_size=%_$$d, block_commit=%_$$d]", - block->size, block->commit); - - // NOTE: Guard new pages being allocated from the block - if (block->arena->use_after_free_guard) - Dqn_VMem_Protect(commit_ptr, commit_size, page_flags | Dqn_VMemPage_Guard); - } + Dqn_usize result = sizeof(Dqn_MemBlock); + if (flags & Dqn_MemBlockFlag_PageGuard) + result = g_dqn_library->os_page_size; + return result; } -DQN_API void *Dqn_Arena_AllocateFromBlock(Dqn_ArenaBlock *block, Dqn_usize size, uint8_t align, Dqn_ZeroMem zero_mem) +DQN_API Dqn_MemBlock *Dqn_MemBlock_Init(Dqn_usize reserve, Dqn_usize commit, uint8_t flags) { + DQN_ASSERTF(g_dqn_library->os_page_size, "Library needs to be initialised by called Dqn_Library_Init()"); + DQN_ASSERTF(Dqn_IsPowerOfTwo(g_dqn_library->os_page_size), "Invalid page size"); + DQN_ASSERTF((flags & ~Dqn_MemBlockFlag_All) == 0, "Invalid flag combination, must adhere to Dqn_MemBlockFlags"); + + if (reserve == 0) + return nullptr; + + Dqn_usize metadata_size = Dqn_MemBlock_MetadataSize(flags); + Dqn_usize reserve_aligned = Dqn_AlignUpPowerOfTwo(reserve + metadata_size, g_dqn_library->os_page_size); + Dqn_usize commit_aligned = Dqn_AlignUpPowerOfTwo(commit + metadata_size, g_dqn_library->os_page_size); + commit_aligned = DQN_MIN(commit_aligned, reserve_aligned); + + // NOTE: Avoid 1 syscall by committing on reserve if amounts are equal + Dqn_VMemCommit commit_on_reserve = commit_aligned == reserve_aligned ? Dqn_VMemCommit_Yes : Dqn_VMemCommit_No; + auto *result = DQN_CAST(Dqn_MemBlock *)Dqn_VMem_Reserve(reserve_aligned, commit_on_reserve, Dqn_VMemPage_ReadWrite); + if (result) { + // NOTE: Commit pages if we did not commit on the initial range. + if (!commit_on_reserve) + Dqn_VMem_Commit(result, commit_aligned, Dqn_VMemPage_ReadWrite); + + result->data = DQN_CAST(uint8_t *)result + metadata_size; + result->size = reserve_aligned - metadata_size; + result->commit = commit_aligned - metadata_size; + result->flags = flags; + + // NOTE: Guard pages + if (flags & Dqn_MemBlockFlag_PageGuard) + Dqn_VMem_Protect(result->data, commit_aligned, Dqn_VMemPage_ReadWrite | Dqn_VMemPage_Guard); + } + return result; +} + +DQN_API void *Dqn_MemBlock_Alloc(Dqn_MemBlock *block, Dqn_usize size, uint8_t alignment, Dqn_ZeroMem zero_mem) +{ + void *result = nullptr; if (!block) - return nullptr; + return result; - DQN_ASSERTF((align & (align - 1)) == 0, "Power of two alignment required"); - align = DQN_MAX(align, 1); + DQN_ASSERT(Dqn_IsPowerOfTwo(alignment)); + Dqn_usize aligned_used = Dqn_AlignUpPowerOfTwo(block->used, alignment); + Dqn_usize new_used = aligned_used + size; + if (new_used > block->size) + return result; - DQN_ASSERT(block->hwm_used <= block->commit); - DQN_ASSERT(block->hwm_used >= block->used); - DQN_ASSERTF(block->commit >= block->used, - "Internal error: Committed size must always be greater than the used size [commit=%_$$zd, used=%_$$zd]", - block->commit, block->used); + result = DQN_CAST(char *)block->data + aligned_used; + block->used = new_used; - // NOTE: Calculate how much we need to pad the next pointer to divvy out - // (only if it is unaligned) - uintptr_t next_ptr = DQN_CAST(uintptr_t)block->memory + block->used; - Dqn_usize align_offset = 0; - if (next_ptr & (align - 1)) - align_offset = (align - (next_ptr & (align - 1))); - - Dqn_usize allocation_size = size + align_offset; - if ((block->used + allocation_size) > block->size) - return nullptr; - - void *result = DQN_CAST(char *)next_ptr + align_offset; if (zero_mem == Dqn_ZeroMem_Yes) { - // NOTE: Newly commit pages are always 0-ed out, we only need to - // memset the memory that are being reused. - Dqn_usize const reused_bytes = DQN_MIN(block->hwm_used - block->used, allocation_size); - DQN_MEMSET(DQN_CAST(void *)next_ptr, DQN_MEMSET_BYTE, reused_bytes); + Dqn_usize reused_bytes = DQN_MIN(block->commit - aligned_used, size); + DQN_MEMSET(result, DQN_MEMSET_BYTE, reused_bytes); } - // NOTE: Ensure requested bytes are backed by physical pages from the OS - Dqn_Arena_CommitFromBlock(block, allocation_size, Dqn_ArenaCommit_EnsureSpace); + if (block->commit < block->used) { + Dqn_usize commit_size = Dqn_AlignUpPowerOfTwo(block->used - block->commit, g_dqn_library->os_page_size); + void *commit_ptr = (void *)Dqn_AlignUpPowerOfTwo((char *)block->data + block->commit, g_dqn_library->os_page_size); + block->commit += commit_size - Dqn_MemBlock_MetadataSize(block->flags); + Dqn_VMem_Commit(commit_ptr, commit_size, Dqn_VMemPage_ReadWrite); + DQN_ASSERT(block->commit <= block->size); + } - // NOTE: Unlock the pages requested - if (block->arena->use_after_free_guard) - Dqn_VMem_Protect(result, allocation_size, Dqn_VMemPage_ReadWrite); - - // NOTE: Update arena - Dqn_Arena *arena = block->arena; - arena->stats.used += allocation_size; - arena->stats.used_hwm = DQN_MAX(arena->stats.used_hwm, arena->stats.used); - block->used += allocation_size; - block->hwm_used = DQN_MAX(block->hwm_used, block->used); - - DQN_ASSERTF(block->used <= block->commit, "Internal error: Committed size must be greater than used size [used=%_$$zd, commit=%_$$zd]", block->used, block->commit); - DQN_ASSERTF(block->commit <= block->size, "Internal error: Allocation exceeded block capacity [commit=%_$$zd, size=%_$$zd]", block->commit, block->size); - DQN_ASSERTF(((DQN_CAST(uintptr_t)result) & (align - 1)) == 0, "Internal error: Pointer alignment failed [address=%p, align=%x]", result, align); + if (block->flags & Dqn_MemBlockFlag_PageGuard) + Dqn_VMem_Protect(result, size, Dqn_VMemPage_ReadWrite); return result; } -DQN_FILE_SCOPE void *Dqn_Arena_AllocatorAlloc_(DQN_LEAK_TRACE_FUNCTION size_t size, uint8_t align, Dqn_ZeroMem zero_mem, void *user_context) +DQN_API void Dqn_MemBlock_Free(Dqn_MemBlock *block) +{ + if (block) { + Dqn_usize release_size = block->size + Dqn_MemBlock_MetadataSize(block->flags); + Dqn_VMem_Release(block, release_size); + } +} + +// NOTE: [$AREN] Dqn_Arena ========================================================================= +DQN_FILE_SCOPE void *Dqn_Arena_AllocatorAlloc(size_t size, uint8_t align, Dqn_ZeroMem zero_mem, void *user_context) { void *result = NULL; if (!user_context) return result; Dqn_Arena *arena = DQN_CAST(Dqn_Arena *)user_context; - result = Dqn_Arena_Allocate_(DQN_LEAK_TRACE_ARG arena, size, align, zero_mem); + result = Dqn_Arena_Alloc(arena, size, align, zero_mem); return result; } -DQN_FILE_SCOPE void Dqn_Arena_AllocatorDealloc_(DQN_LEAK_TRACE_FUNCTION void *ptr, size_t size, void *user_context) +DQN_FILE_SCOPE void Dqn_Arena_AllocatorDealloc(void *, size_t, void *) { // NOTE: No-op, arenas batch allocate and batch deallocate. Call free on the // underlying arena, since we can't free individual pointers. - DQN_LEAK_TRACE_UNUSED; - (void)ptr; - (void)size; - (void)user_context; } DQN_API Dqn_Allocator Dqn_Arena_Allocator(Dqn_Arena *arena) @@ -260,151 +264,155 @@ DQN_API Dqn_Allocator Dqn_Arena_Allocator(Dqn_Arena *arena) Dqn_Allocator result = {}; if (arena) { result.user_context = arena; - result.alloc = Dqn_Arena_AllocatorAlloc_; - result.dealloc = Dqn_Arena_AllocatorDealloc_; + result.alloc = Dqn_Arena_AllocatorAlloc; + result.dealloc = Dqn_Arena_AllocatorDealloc; } return result; } -struct Dqn_ArenaBlockResetInfo_ +DQN_API Dqn_MemBlock *Dqn_Arena_Grow(Dqn_Arena *arena, Dqn_usize reserve, Dqn_usize commit, uint8_t flags) { - bool free_memory; - Dqn_usize used_value; -}; + if (!arena) + return nullptr; -// Calculate the size in bytes required to allocate the memory for the block -// (*not* including the memory required for the user in the block!) -#define Dqn_Arena_BlockMetadataSize_(arena) ((arena)->use_after_free_guard ? (Dqn_PowerOfTwoAlign(sizeof(Dqn_ArenaBlock), DQN_VMEM_PAGE_GRANULARITY)) : sizeof(Dqn_ArenaBlock)) + uint8_t mem_block_flags = flags; + if (arena->allocs_are_allowed_to_leak) + mem_block_flags |= Dqn_MemBlockFlag_AllocRecordLeakPermitted; -DQN_API void Dqn_Arena_BlockReset_(DQN_LEAK_TRACE_FUNCTION Dqn_ArenaBlock *block, Dqn_ZeroMem zero_mem, Dqn_ArenaBlockResetInfo_ reset_info) -{ - if (!block) - return; + Dqn_MemBlock *result = Dqn_MemBlock_Init(reserve, commit, mem_block_flags); + if (result) { + if (!arena->head) + arena->head = result; - if (zero_mem == Dqn_ZeroMem_Yes) - DQN_MEMSET(block->memory, DQN_MEMSET_BYTE, block->commit); + if (arena->tail) + arena->tail->next = result; - if (reset_info.free_memory) { - Dqn_usize block_metadata_size = Dqn_Arena_BlockMetadataSize_(block->arena); - Dqn_VMem_Release(block, block_metadata_size + block->size); - Dqn_Library_LeakTraceMarkFree(DQN_LEAK_TRACE_ARG block); - } else { - if (block->arena->use_after_free_guard) { - DQN_ASSERTF( - block->flags & Dqn_ArenaBlockFlags_UseAfterFreeGuard, - "Arena has set use-after-free guard but the memory it uses was not initialised " - "with use-after-free semantics. You might have set the arena use-after-free " - "flag after it has already done an allocation which is not valid. Make sure " - "you set the flag first before the arena is used."); - } else { - DQN_ASSERTF( - (block->flags & Dqn_ArenaBlockFlags_UseAfterFreeGuard) == 0, - "The arena's memory block has the use-after-free guard set but the arena does " - "not have the use-after-free flag set. This is not valid, a block that has " - "use-after-free semantics was allocated from an arena with the equivalent flag set " - "and for the lifetime of the block the owning arena must also have the same flag " - "set for correct behaviour. Make sure you do not unset the flag on the arena " - "whilst it still has memory blocks that it owns."); - } + if (!arena->curr) + arena->curr = result; - block->used = reset_info.used_value; - // NOTE: Guard all the committed pages again - if (block->arena->use_after_free_guard) - Dqn_VMem_Protect(block->memory, block->commit, Dqn_VMemPage_ReadWrite | Dqn_VMemPage_Guard); + result->prev = arena->tail; + arena->tail = result; + arena->blocks += 1; } + return result; } -DQN_API void Dqn_Arena_Reset(Dqn_Arena *arena, Dqn_ZeroMem zero_mem) +DQN_API void *Dqn_Arena_Alloc(Dqn_Arena *arena, Dqn_usize size, uint8_t align, Dqn_ZeroMem zero_mem) +{ + DQN_ASSERT(Dqn_IsPowerOfTwo(align)); + + void *result = nullptr; + if (!arena || size == 0 || align == 0) + return result; + + for (;;) { + while (arena->curr && (arena->curr->flags & Dqn_MemBlockFlag_ArenaPrivate)) + arena->curr = arena->curr->next; + + if (!arena->curr) { + if (!Dqn_Arena_Grow(arena, size, size /*commit*/, 0 /*flags*/)) + return result; + } + + result = Dqn_MemBlock_Alloc(arena->curr, size, align, zero_mem); + if (result) + break; + + arena->curr = arena->curr->next; + } + + return result; +} + +DQN_API void *Dqn_Arena_Copy(Dqn_Arena *arena, void *src, Dqn_usize size, uint8_t align) +{ + void *result = Dqn_Arena_Alloc(arena, size, align, Dqn_ZeroMem_No); + DQN_MEMCPY(result, src, size); + return result; +} + +DQN_API void *Dqn_Arena_CopyZ(Dqn_Arena *arena, void *src, Dqn_usize size, uint8_t align) +{ + void *result = Dqn_Arena_Alloc(arena, size + 1, align, Dqn_ZeroMem_No); + DQN_MEMCPY(result, src, size); + (DQN_CAST(char *)result)[size] = 0; + return result; +} + +DQN_API void Dqn_Arena_Free(Dqn_Arena *arena) { if (!arena) return; - // NOTE: Zero all the blocks until we reach the first block in the list - for (Dqn_ArenaBlock *block = arena->head; block; block = block->next) { - Dqn_ArenaBlockResetInfo_ reset_info = {}; - Dqn_Arena_BlockReset_(DQN_LEAK_TRACE block, zero_mem, reset_info); + for (Dqn_MemBlock *block = arena->head; block; ) { + Dqn_MemBlock *next = block->next; + Dqn_MemBlock_Free(block); + block = next; } - arena->curr = arena->head; - arena->stats.used = 0; - arena->stats.wasted = 0; + arena->curr = arena->head = arena->tail = nullptr; + arena->blocks = 0; } DQN_API Dqn_ArenaTempMemory Dqn_Arena_BeginTempMemory(Dqn_Arena *arena) { Dqn_ArenaTempMemory result = {}; if (arena) { - arena->temp_memory_count++; result.arena = arena; result.head = arena->head; result.curr = arena->curr; result.tail = arena->tail; result.curr_used = (arena->curr) ? arena->curr->used : 0; - result.stats = arena->stats; + result.blocks = arena->blocks; } return result; } -DQN_API void Dqn_Arena_EndTempMemory_(DQN_LEAK_TRACE_FUNCTION Dqn_ArenaTempMemory scope) +DQN_API void Dqn_Arena_EndTempMemory(Dqn_ArenaTempMemory temp_memory, bool cancel) { - if (!scope.arena) + if (cancel || !temp_memory.arena) return; - Dqn_Arena *arena = scope.arena; - if (!DQN_CHECKF(arena->temp_memory_count > 0, "End temp memory has been called without a matching begin pair on the arena")) + // NOTE: The arena has been freed or invalidly manipulated (e.g. freed) + // since the temp memory started as the head cannot change once it is + // captured in the temp memory. + Dqn_Arena *arena = temp_memory.arena; + if (arena->head != temp_memory.head) return; - // NOTE: Revert arena stats - arena->temp_memory_count--; - arena->stats.capacity = scope.stats.capacity; - arena->stats.used = scope.stats.used; - arena->stats.wasted = scope.stats.wasted; - arena->stats.blocks = scope.stats.blocks; - - // NOTE: Revert the current block to the scope's current block - arena->head = scope.head; - arena->curr = scope.curr; + // NOTE: Revert the current block to the temp_memory's current block + arena->blocks = temp_memory.blocks; + arena->head = temp_memory.head; + arena->curr = temp_memory.curr; if (arena->curr) { - Dqn_ArenaBlock *curr = arena->curr; - Dqn_ArenaBlockResetInfo_ reset_info = {}; - reset_info.used_value = scope.curr_used; - Dqn_Arena_BlockReset_(DQN_LEAK_TRACE_ARG curr, Dqn_ZeroMem_No, reset_info); + Dqn_MemBlock *curr = arena->curr; + curr->used = temp_memory.curr_used; } - // NOTE: Free the tail blocks until we reach the scope's tail block - while (arena->tail != scope.tail) { - Dqn_ArenaBlock *tail = arena->tail; - arena->tail = tail->prev; - Dqn_ArenaBlockResetInfo_ reset_info = {}; - reset_info.free_memory = true; - Dqn_Arena_BlockReset_(DQN_LEAK_TRACE_ARG tail, Dqn_ZeroMem_No, reset_info); + // NOTE: Free the tail blocks until we reach the temp_memory's tail block + while (arena->tail != temp_memory.tail) { + Dqn_MemBlock *tail = arena->tail; + arena->tail = tail->prev; + Dqn_MemBlock_Free(tail); } + // NOTE: Chop the restored tail link + if (arena->tail) + arena->tail->next = nullptr; + // NOTE: Reset the usage of all the blocks between the tail and current block's - if (arena->tail) { - arena->tail->next = nullptr; - for (Dqn_ArenaBlock *block = arena->tail; block && block != arena->curr; block = block->prev) { - Dqn_ArenaBlockResetInfo_ reset_info = {}; - Dqn_Arena_BlockReset_(DQN_LEAK_TRACE_ARG block, Dqn_ZeroMem_No, reset_info); - } - } + for (Dqn_MemBlock *block = arena->tail; block != arena->curr; block = block->prev) + block->used = 0; } -Dqn_ArenaTempMemoryScope_::Dqn_ArenaTempMemoryScope_(DQN_LEAK_TRACE_FUNCTION Dqn_Arena *arena) +Dqn_ArenaTempMemoryScope::Dqn_ArenaTempMemoryScope(Dqn_Arena *arena) { temp_memory = Dqn_Arena_BeginTempMemory(arena); - #if defined(DQN_LEAK_TRACING) - leak_site__ = DQN_LEAK_TRACE_ARG_NO_COMMA; - #endif } -Dqn_ArenaTempMemoryScope_::~Dqn_ArenaTempMemoryScope_() +Dqn_ArenaTempMemoryScope::~Dqn_ArenaTempMemoryScope() { - #if defined(DQN_LEAK_TRACING) - Dqn_Arena_EndTempMemory_(leak_site__, temp_memory); - #else - Dqn_Arena_EndTempMemory_(temp_memory); - #endif + Dqn_Arena_EndTempMemory(temp_memory, cancel); } DQN_API Dqn_ArenaStatString Dqn_Arena_StatString(Dqn_ArenaStat const *stat) @@ -447,155 +455,6 @@ DQN_API Dqn_ArenaStatString Dqn_Arena_StatString(Dqn_ArenaStat const *stat) return result; } -DQN_API void Dqn_Arena_LogStats(Dqn_Arena const *arena) -{ - Dqn_ArenaStatString string = Dqn_Arena_StatString(&arena->stats); - Dqn_Log_InfoF("%.*s\n", DQN_STRING_FMT(string)); -} - -DQN_API Dqn_ArenaBlock *Dqn_Arena_Grow_(DQN_LEAK_TRACE_FUNCTION Dqn_Arena *arena, Dqn_usize size, Dqn_usize commit, uint8_t flags) -{ - DQN_ASSERT(commit <= size); - if (!arena || size == 0) - return nullptr; - - Dqn_usize block_metadata_size = Dqn_Arena_BlockMetadataSize_(arena); - commit = DQN_MIN(commit, size); - Dqn_usize reserve_aligned = Dqn_PowerOfTwoAlign(size + block_metadata_size, DQN_VMEM_RESERVE_GRANULARITY); - Dqn_usize commit_aligned = Dqn_PowerOfTwoAlign(commit + block_metadata_size, DQN_VMEM_COMMIT_GRANULARITY); - DQN_ASSERT(commit_aligned < reserve_aligned); - - // NOTE: If the commit amount is the same as reserve size we can save one - // syscall by asking the OS to reserve+commit in the same call. - Dqn_VMemCommit commit_on_reserve = reserve_aligned == commit_aligned ? Dqn_VMemCommit_Yes : Dqn_VMemCommit_No; - uint32_t page_flags = Dqn_VMemPage_ReadWrite; - auto *result = DQN_CAST(Dqn_ArenaBlock *)Dqn_VMem_Reserve(reserve_aligned, commit_on_reserve, page_flags); - if (result) { - // NOTE: Commit the amount requested by the user if we did not commit - // on reserve the initial range. - if (commit_on_reserve == Dqn_VMemCommit_No) { - Dqn_VMem_Commit(result, commit_aligned, page_flags); - } else { - DQN_ASSERT(commit_aligned == reserve_aligned); - } - - // NOTE: Sanity check memory is zero-ed out - DQN_ASSERT(result->used == 0); - DQN_ASSERT(result->next == nullptr); - DQN_ASSERT(result->prev == nullptr); - - // NOTE: Setup the block - result->memory = DQN_CAST(uint8_t *)result + block_metadata_size; - result->size = reserve_aligned - block_metadata_size; - result->commit = commit_aligned - block_metadata_size; - result->flags = flags; - result->arena = arena; - - if (arena->use_after_free_guard) - result->flags |= Dqn_ArenaBlockFlags_UseAfterFreeGuard; - - // NOTE: Reset the block (this will guard the memory pages if required, otherwise no-op). - Dqn_ArenaBlockResetInfo_ reset_info = {}; - Dqn_Arena_BlockReset_(DQN_LEAK_TRACE_ARG result, Dqn_ZeroMem_No, reset_info); - - // NOTE: Attach the block to the arena - if (arena->tail) { - arena->tail->next = result; - result->prev = arena->tail; - } else { - DQN_ASSERT(!arena->curr); - arena->curr = result; - arena->head = result; - } - arena->tail = result; - - // NOTE: Update stats - arena->stats.syscalls += (commit_on_reserve ? 1 : 2); - arena->stats.blocks += 1; - arena->stats.capacity += arena->curr->size; - - arena->stats.blocks_hwm = DQN_MAX(arena->stats.blocks_hwm, arena->stats.blocks); - arena->stats.capacity_hwm = DQN_MAX(arena->stats.capacity_hwm, arena->stats.capacity); - - Dqn_Library_LeakTraceAdd(DQN_LEAK_TRACE_ARG result, size); - } - - return result; -} - -DQN_API void *Dqn_Arena_Allocate_(DQN_LEAK_TRACE_FUNCTION Dqn_Arena *arena, Dqn_usize size, uint8_t align, Dqn_ZeroMem zero_mem) -{ - DQN_ASSERTF((align & (align - 1)) == 0, "Power of two alignment required"); - align = DQN_MAX(align, 1); - - if (arena->use_after_free_guard) { - size = Dqn_PowerOfTwoAlign(size, DQN_VMEM_PAGE_GRANULARITY); - align = 1; - } - - void *result = nullptr; - for (; !result;) { - while (arena->curr && (arena->curr->flags & Dqn_ArenaBlockFlags_Private)) - arena->curr = arena->curr->next; - - result = Dqn_Arena_AllocateFromBlock(arena->curr, size, align, zero_mem); - if (!result) { - if (!arena->curr || arena->curr == arena->tail) { - Dqn_usize allocation_size = size + (align - 1); - if (!Dqn_Arena_Grow(DQN_LEAK_TRACE_ARG arena, allocation_size, allocation_size /*commit*/, 0 /*flags*/)) { - break; - } - } - - if (arena->curr != arena->tail) - arena->curr = arena->curr->next; - } - } - - if (result) - DQN_ASSERT((arena->curr->flags & Dqn_ArenaBlockFlags_Private) == 0); - - return result; -} - -DQN_API void *Dqn_Arena_Copy_(DQN_LEAK_TRACE_FUNCTION Dqn_Arena *arena, void *src, Dqn_usize size, uint8_t align) -{ - void *result = Dqn_Arena_Allocate_(DQN_LEAK_TRACE_ARG arena, size, align, Dqn_ZeroMem_No); - DQN_MEMCPY(result, src, size); - return result; -} - -DQN_API void *Dqn_Arena_CopyZ_(DQN_LEAK_TRACE_FUNCTION Dqn_Arena *arena, void *src, Dqn_usize size, uint8_t align) -{ - void *result = Dqn_Arena_Allocate_(DQN_LEAK_TRACE_ARG arena, size + 1, align, Dqn_ZeroMem_No); - DQN_MEMCPY(result, src, size); - (DQN_CAST(char *)result)[size] = 0; - return result; -} - -DQN_API void Dqn_Arena_Free_(DQN_LEAK_TRACE_FUNCTION Dqn_Arena *arena, Dqn_ZeroMem zero_mem) -{ - if (!arena) - return; - - if (!DQN_CHECKF(arena->temp_memory_count == 0, "You cannot free an arena whilst in an temp memory region")) - return; - - while (arena->tail) { - Dqn_ArenaBlock *block = arena->tail; - arena->tail = block->prev; - Dqn_ArenaBlockResetInfo_ reset_info = {}; - reset_info.free_memory = true; - Dqn_Arena_BlockReset_(DQN_LEAK_TRACE_ARG block, zero_mem, reset_info); - } - - arena->curr = arena->tail = nullptr; - arena->stats.capacity = 0; - arena->stats.used = 0; - arena->stats.wasted = 0; - arena->stats.blocks = 0; -} - // NOTE: [$ACAT] Dqn_ArenaCatalog ================================================================== DQN_API void Dqn_ArenaCatalog_Init(Dqn_ArenaCatalog *catalog, Dqn_Arena *arena) { diff --git a/dqn_memory.h b/dqn_memory.h index 86816ce..57e7e31 100644 --- a/dqn_memory.h +++ b/dqn_memory.h @@ -1,46 +1,6 @@ -// NOTE: Table Of Contents ========================================================================= -// Index | Disable #define | Description -// ================================================================================================= -// [$ALLO] Dqn_Allocator | | Generic allocator interface -// [$VMEM] Dqn_VMem | | Virtual memory allocation -// [$AREN] Dqn_Arena | | Growing bump allocator -// [$ACAT] Dqn_ArenaCatalog | | Collate, create & manage arenas in a catalog -// ================================================================================================= - // NOTE: [$ALLO] Dqn_Allocator ===================================================================== -#if defined(DQN_LEAK_TRACING) - #if defined(DQN_NO_DSMAP) - #error "DSMap is required for allocation tracing" - #endif - #define DQN_LEAK_TRACE DQN_CALL_SITE, - #define DQN_LEAK_TRACE_NO_COMMA DQN_CALL_SITE - #define DQN_LEAK_TRACE_FUNCTION Dqn_CallSite leak_site_, - #define DQN_LEAK_TRACE_FUNCTION_NO_COMMA Dqn_CallSite leak_site_ - #define DQN_LEAK_TRACE_ARG leak_site_, - #define DQN_LEAK_TRACE_ARG_NO_COMMA leak_site_ - #define DQN_LEAK_TRACE_UNUSED (void)leak_site_; -#else - #define DQN_LEAK_TRACE - #define DQN_LEAK_TRACE_NO_COMMA - #define DQN_LEAK_TRACE_FUNCTION - #define DQN_LEAK_TRACE_FUNCTION_NO_COMMA - #define DQN_LEAK_TRACE_ARG - #define DQN_LEAK_TRACE_ARG_NO_COMMA - #define DQN_LEAK_TRACE_UNUSED -#endif - -struct Dqn_LeakTrace -{ - void *ptr; // The pointer we are tracking - Dqn_usize size; // Size of the allocation - Dqn_CallSite call_site; // Call site where the allocation was allocated - bool freed; // True if this pointer has been freed - Dqn_usize freed_size; // Size of the allocation that has been freed - Dqn_CallSite freed_call_site; // Call site where the allocation was freed -}; - -typedef void *Dqn_Allocator_AllocProc(DQN_LEAK_TRACE_FUNCTION size_t size, uint8_t align, Dqn_ZeroMem zero_mem, void *user_context); -typedef void Dqn_Allocator_DeallocProc(DQN_LEAK_TRACE_FUNCTION void *ptr, size_t size, void *user_context); +typedef void *Dqn_Allocator_AllocProc(size_t size, uint8_t align, Dqn_ZeroMem zero_mem, void *user_context); +typedef void Dqn_Allocator_DeallocProc(void *ptr, size_t size, void *user_context); struct Dqn_Allocator { @@ -49,15 +9,13 @@ struct Dqn_Allocator Dqn_Allocator_DeallocProc *dealloc; // Memory deallocating routine }; -// NOTE: Macros ================================================================================== -#define Dqn_Allocator_Alloc(allocator, size, align, zero_mem) Dqn_Allocator_Alloc_(DQN_LEAK_TRACE allocator, size, align, zero_mem) -#define Dqn_Allocator_Dealloc(allocator, ptr, size) Dqn_Allocator_Dealloc_(DQN_LEAK_TRACE allocator, ptr, size) -#define Dqn_Allocator_NewArray(allocator, Type, count, zero_mem) (Type *)Dqn_Allocator_Alloc_(DQN_LEAK_TRACE allocator, sizeof(Type) * count, alignof(Type), zero_mem) -#define Dqn_Allocator_New(allocator, Type, zero_mem) (Type *)Dqn_Allocator_Alloc_(DQN_LEAK_TRACE allocator, sizeof(Type), alignof(Type), zero_mem) +// NOTE: Macros ==================================================================================== +#define Dqn_Allocator_NewArray(allocator, Type, count, zero_mem) (Type *)Dqn_Allocator_Alloc(allocator, sizeof(Type) * count, alignof(Type), zero_mem) +#define Dqn_Allocator_New(allocator, Type, zero_mem) (Type *)Dqn_Allocator_Alloc(allocator, sizeof(Type), alignof(Type), zero_mem) -// NOTE: Internal ================================================================================== -void *Dqn_Allocator_Alloc_ (DQN_LEAK_TRACE_FUNCTION Dqn_Allocator allocator, size_t size, uint8_t align, Dqn_ZeroMem zero_mem); -void Dqn_Allocator_Dealloc_(DQN_LEAK_TRACE_FUNCTION Dqn_Allocator allocator, void *ptr, size_t size); +// NOTE: API ======================================================================================= +void *Dqn_Allocator_Alloc (Dqn_Allocator allocator, size_t size, uint8_t align, Dqn_ZeroMem zero_mem); +void Dqn_Allocator_Dealloc(Dqn_Allocator allocator, void *ptr, size_t size); // NOTE: [$VMEM] Dqn_VMem ========================================================================== enum Dqn_VMemCommit @@ -85,34 +43,55 @@ enum Dqn_VMemPage // first access to the page, then, the underlying protection flags are // active. This is supported on Windows, on other OS's using this flag will // set the OS equivalent of Dqn_VMemPage_NoAccess. + // This flag must only be used in Dqn_VMem_Protect Dqn_VMemPage_Guard = 1 << 3, + + // If leak tracing is enabled, this flag will allow the allocation recorded + // from the reserve call to be leaked, e.g. not printed when leaks are + // dumped to the console. + Dqn_VMemPage_AllocRecordLeakPermitted = 1 << 2, }; -#if !defined(DQN_VMEM_PAGE_GRANULARITY) - #define DQN_VMEM_PAGE_GRANULARITY DQN_KILOBYTES(4) -#endif - -#if !defined(DQN_VMEM_RESERVE_GRANULARITY) - #define DQN_VMEM_RESERVE_GRANULARITY DQN_KILOBYTES(64) -#endif - -#if !defined(DQN_VMEM_COMMIT_GRANULARITY) - #define DQN_VMEM_COMMIT_GRANULARITY DQN_VMEM_PAGE_GRANULARITY -#endif - -static_assert(Dqn_IsPowerOfTwo(DQN_VMEM_RESERVE_GRANULARITY), - "This library assumes that the memory allocation routines from the OS has PoT allocation granularity"); -static_assert(Dqn_IsPowerOfTwo(DQN_VMEM_COMMIT_GRANULARITY), - "This library assumes that the memory allocation routines from the OS has PoT allocation granularity"); -static_assert(DQN_VMEM_COMMIT_GRANULARITY < DQN_VMEM_RESERVE_GRANULARITY, - "Minimum commit size must be lower than the reserve size to avoid OOB math on pointers in this library"); - DQN_API void *Dqn_VMem_Reserve (Dqn_usize size, Dqn_VMemCommit commit, uint32_t page_flags); DQN_API bool Dqn_VMem_Commit (void *ptr, Dqn_usize size, uint32_t page_flags); DQN_API void Dqn_VMem_Decommit(void *ptr, Dqn_usize size); DQN_API void Dqn_VMem_Release (void *ptr, Dqn_usize size); DQN_API int Dqn_VMem_Protect (void *ptr, Dqn_usize size, uint32_t page_flags); +// NOTE: [$MEMB] Dqn_MemBlock ====================================================================== +enum Dqn_MemBlockFlag +{ + Dqn_MemBlockFlag_PageGuard = 1 << 0, + Dqn_MemBlockFlag_ArenaPrivate = 1 << 1, + + // If leak tracing is enabled, this flag will allow the allocation recorded + // from the reserve call to be leaked, e.g. not printed when leaks are + // dumped to the console. + Dqn_MemBlockFlag_AllocRecordLeakPermitted = 1 << 2, + Dqn_MemBlockFlag_All = Dqn_MemBlockFlag_PageGuard | + Dqn_MemBlockFlag_ArenaPrivate | + Dqn_MemBlockFlag_AllocRecordLeakPermitted, +}; + +struct Dqn_MemBlock +{ + void *data; + Dqn_usize used; + Dqn_usize size; + Dqn_usize commit; + Dqn_MemBlock *next; + Dqn_MemBlock *prev; + uint8_t flags; +}; + +DQN_API Dqn_usize Dqn_MemBlock_MetadataSize(uint8_t flags); +DQN_API Dqn_MemBlock *Dqn_MemBlock_Init (Dqn_usize reserve, Dqn_usize commit, uint8_t flags); +DQN_API void *Dqn_MemBlock_Alloc (Dqn_MemBlock *block, Dqn_usize size, uint8_t alignment, Dqn_ZeroMem zero_mem); +DQN_API void Dqn_MemBlock_Free (Dqn_MemBlock *block); + +#define Dqn_MemBlock_New(block, Type, zero_mem) (Type *)Dqn_MemBlock_Alloc(block, sizeof(Type), alignof(Type), zero_mem) +#define Dqn_MemBlock_NewArray(block, Type, count, zero_mem) (Type *)Dqn_MemBlock_Alloc(block, sizeof(Type) * count, alignof(Type), zero_mem) + // NOTE: [$AREN] Dqn_Arena ========================================================================= // A bump-allocator that can grow dynamically by chaining blocks of memory // together. The arena's memory is backed by virtual memory allowing the @@ -144,17 +123,17 @@ DQN_API int Dqn_VMem_Protect (void *ptr, Dqn_usize size, uint32_t page_flags); // @param flags[in] Bit flags from 'Dqn_ArenaBlockFlags', set to 0 if none // @return The block of memory that -// @proc Dqn_Arena_Allocate, Dqn_Arena_New, Dqn_Arena_NewArray, +// @proc Dqn_Arena_Alloc, Dqn_Arena_New, Dqn_Arena_NewArray, // Dqn_Arena_NewArrayWithBlock, -// @desc Allocate byte/objects -// `Allocate` allocates bytes +// @desc Alloc byte/objects +// `Alloc` allocates bytes // `New` allocates an object // `NewArray` allocates an array of objects // `NewArrayWithBlock` allocates an array of objects from the given memory 'block' // @return A pointer to the allocated bytes/object. Null pointer on failure // @proc Dqn_Arena_Copy, Dqn_Arena_CopyZ -// @desc Allocate a copy of an object's bytes. The 'Z' variant adds +// @desc Alloc a copy of an object's bytes. The 'Z' variant adds // a null-terminating byte at the end of the stream. // @return A pointer to the allocated object. Null pointer on failure. @@ -186,13 +165,6 @@ DQN_API int Dqn_VMem_Protect (void *ptr, Dqn_usize size, uint32_t page_flags); // @proc Dqn_Arena_LogStats // @desc Dump the stats of the given arena to the memory log-stream. // @param[in] arena The arena to dump stats for - -enum Dqn_ArenaBlockFlags -{ - Dqn_ArenaBlockFlags_Private = 1 << 0, // Private blocks can only allocate its memory when used in the 'FromBlock' API variants - Dqn_ArenaBlockFlags_UseAfterFreeGuard = 1 << 1, // Block was allocated with use-after-free guard semantics -}; - struct Dqn_ArenaStat { Dqn_usize capacity; // Total allocating capacity of the arena in bytes @@ -228,36 +200,35 @@ struct Dqn_ArenaStatString struct Dqn_Arena { - bool use_after_free_guard; - uint32_t temp_memory_count; + bool allocs_are_allowed_to_leak; Dqn_String8 label; // Optional label to describe the arena - Dqn_ArenaBlock *head; // Active block the arena is allocating from - Dqn_ArenaBlock *curr; // Active block the arena is allocating from - Dqn_ArenaBlock *tail; // Last block in the linked list of blocks - Dqn_ArenaStat stats; // Current arena stats, reset when reset usage is invoked. + Dqn_MemBlock *head; // Active block the arena is allocating from + Dqn_MemBlock *curr; // Active block the arena is allocating from + Dqn_MemBlock *tail; // Last block in the linked list of blocks + uint64_t blocks; }; struct Dqn_ArenaTempMemory { - Dqn_Arena *arena; // Arena the scope is for - Dqn_ArenaBlock *head; // Head block of the arena at the beginning of the scope - Dqn_ArenaBlock *curr; // Current block of the arena at the beginning of the scope - Dqn_ArenaBlock *tail; // Tail block of the arena at the beginning of the scope - Dqn_usize curr_used; // Current used amount of the current block - Dqn_ArenaStat stats; // Stats of the arena at the beginning of the scope + Dqn_Arena *arena; // Arena the scope is for + Dqn_MemBlock *head; // Head block of the arena at the beginning of the scope + Dqn_MemBlock *curr; // Current block of the arena at the beginning of the scope + Dqn_MemBlock *tail; // Tail block of the arena at the beginning of the scope + Dqn_usize blocks; + Dqn_usize curr_used; // Current used amount of the current block }; // Automatically begin and end a temporary memory scope on object construction // and destruction respectively. -#define DQN_ARENA_TEMP_MEMORY_SCOPE(arena) Dqn_ArenaTempMemoryScope_ DQN_UNIQUE_NAME(temp_memory_) = Dqn_ArenaTempMemoryScope_(DQN_LEAK_TRACE arena) -#define Dqn_ArenaTempMemoryScope(arena) Dqn_ArenaTempMemoryScope_(DQN_LEAK_TRACE arena) -struct Dqn_ArenaTempMemoryScope_ +#define Dqn_Arena_TempMemoryScope(arena) Dqn_ArenaTempMemoryScope DQN_UNIQUE_NAME(temp_memory_) = Dqn_ArenaTempMemoryScope(arena) +struct Dqn_ArenaTempMemoryScope { - Dqn_ArenaTempMemoryScope_(DQN_LEAK_TRACE_FUNCTION Dqn_Arena *arena); - ~Dqn_ArenaTempMemoryScope_(); + Dqn_ArenaTempMemoryScope(Dqn_Arena *arena); + ~Dqn_ArenaTempMemoryScope(); Dqn_ArenaTempMemory temp_memory; + bool cancel = false; #if defined(DQN_LEAK_TRACING) - Dqn_CallSite leak_site__; + Dqn_CallSite leak_site_; #endif }; @@ -270,35 +241,25 @@ enum Dqn_ArenaCommit }; // NOTE: Allocation ================================================================================ -#define Dqn_Arena_Grow(arena, size, commit, flags) Dqn_Arena_Grow_(DQN_LEAK_TRACE arena, size, commit, flags) -#define Dqn_Arena_Allocate(arena, size, align, zero_mem) Dqn_Arena_Allocate_(DQN_LEAK_TRACE arena, size, align, zero_mem) -#define Dqn_Arena_New(arena, Type, zero_mem) (Type *)Dqn_Arena_Allocate_(DQN_LEAK_TRACE arena, sizeof(Type), alignof(Type), zero_mem) -#define Dqn_Arena_NewArray(arena, Type, count, zero_mem) (Type *)Dqn_Arena_Allocate_(DQN_LEAK_TRACE arena, sizeof(Type) * count, alignof(Type), zero_mem) -#define Dqn_Arena_NewArrayWithBlock(block, Type, count, zero_mem) (Type *)Dqn_Arena_AllocateFromBlock(block, sizeof(Type) * count, alignof(Type), zero_mem) -#define Dqn_Arena_Copy(arena, Type, src, count) (Type *)Dqn_Arena_Copy_(DQN_LEAK_TRACE arena, src, sizeof(*src) * count, alignof(Type)) -#define Dqn_Arena_CopyZ(arena, Type, src, count) (Type *)Dqn_Arena_CopyZ_(DQN_LEAK_TRACE arena, src, sizeof(*src) * count, alignof(Type)) -#define Dqn_Arena_Free(arena, zero_mem) Dqn_Arena_Free_(DQN_LEAK_TRACE arena, zero_mem) +#define Dqn_Arena_New(arena, Type, zero_mem) (Type *)Dqn_Arena_Alloc(arena, sizeof(Type), alignof(Type), zero_mem) +#define Dqn_Arena_NewArray(arena, Type, count, zero_mem) (Type *)Dqn_Arena_Alloc(arena, sizeof(Type) * count, alignof(Type), zero_mem) +#define Dqn_Arena_NewCopy(arena, Type, src, count) (Type *)Dqn_Arena_Copy(arena, src, sizeof(*src) * count, alignof(Type)) +#define Dqn_Arena_NewCopyZ(arena, Type, src, count) (Type *)Dqn_Arena_CopyZ(arena, src, sizeof(*src) * count, alignof(Type)) -DQN_API void Dqn_Arena_CommitFromBlock (Dqn_ArenaBlock *block, Dqn_usize size, Dqn_ArenaCommit commit); -DQN_API void * Dqn_Arena_AllocateFromBlock(Dqn_ArenaBlock *block, Dqn_usize size, uint8_t align, Dqn_ZeroMem zero_mem); -DQN_API Dqn_Allocator Dqn_Arena_Allocator (Dqn_Arena *arena); -DQN_API void Dqn_Arena_Reset (Dqn_Arena *arena, Dqn_ZeroMem zero_mem); +DQN_API Dqn_Allocator Dqn_Arena_Allocator (Dqn_Arena *arena); +DQN_API Dqn_MemBlock * Dqn_Arena_Grow (Dqn_Arena *arena, Dqn_usize size, Dqn_usize commit, uint8_t flags); +DQN_API void * Dqn_Arena_Alloc (Dqn_Arena *arena, Dqn_usize size, uint8_t align, Dqn_ZeroMem zero_mem); +DQN_API void * Dqn_Arena_Copy (Dqn_Arena *arena, void *src, Dqn_usize size, uint8_t alignment); +DQN_API void * Dqn_Arena_CopyZ (Dqn_Arena *arena, void *src, Dqn_usize size, uint8_t alignment); +DQN_API void Dqn_Arena_Free (Dqn_Arena *arena, Dqn_ZeroMem zero_mem); // NOTE: Temp Memory =============================================================================== -DQN_API Dqn_ArenaTempMemory Dqn_Arena_BeginTempMemory (Dqn_Arena *arena); -#define Dqn_Arena_EndTempMemory(arena_temp_memory) Dqn_Arena_EndTempMemory_(DQN_LEAK_TRACE arena_temp_memory) +DQN_API Dqn_ArenaTempMemory Dqn_Arena_BeginTempMemory(Dqn_Arena *arena); +DQN_API void Dqn_Arena_EndTempMemory (Dqn_ArenaTempMemory temp_memory, bool cancel); // NOTE: Arena Stats =============================================================================== -DQN_API Dqn_ArenaStatString Dqn_Arena_StatString (Dqn_ArenaStat const *stat); -DQN_API void Dqn_Arena_LogStats (Dqn_Arena const *arena); - -// NOTE: Internal ================================================================================== -DQN_API Dqn_ArenaBlock * Dqn_Arena_Grow_ (DQN_LEAK_TRACE_FUNCTION Dqn_Arena *arena, Dqn_usize size, Dqn_usize commit, uint8_t flags); -DQN_API void * Dqn_Arena_Allocate_ (DQN_LEAK_TRACE_FUNCTION Dqn_Arena *arena, Dqn_usize size, uint8_t align, Dqn_ZeroMem zero_mem); -DQN_API void * Dqn_Arena_Copy_ (DQN_LEAK_TRACE_FUNCTION Dqn_Arena *arena, void *src, Dqn_usize size, uint8_t alignment); -DQN_API void Dqn_Arena_Free_ (DQN_LEAK_TRACE_FUNCTION Dqn_Arena *arena, Dqn_ZeroMem zero_mem); -DQN_API void * Dqn_Arena_CopyZ_ (DQN_LEAK_TRACE_FUNCTION Dqn_Arena *arena, void *src, Dqn_usize size, uint8_t alignment); -DQN_API void Dqn_Arena_EndTempMemory_ (DQN_LEAK_TRACE_FUNCTION Dqn_ArenaTempMemory arena_temp_memory); +DQN_API Dqn_ArenaStatString Dqn_Arena_StatString (Dqn_ArenaStat const *stat); +DQN_API void Dqn_Arena_LogStats (Dqn_Arena const *arena); // NOTE: [$ACAT] Dqn_ArenaCatalog ================================================================== struct Dqn_ArenaCatalogItem diff --git a/dqn_misc.cpp b/dqn_misc.cpp deleted file mode 100644 index 3d7811a..0000000 --- a/dqn_misc.cpp +++ /dev/null @@ -1,587 +0,0 @@ -// NOTE: [$DLIB] Dqn_Library ======================================================================= -Dqn_Library dqn_library; -DQN_API Dqn_Library *Dqn_Library_Init(Dqn_Arena *arena) -{ - Dqn_Library *result = &dqn_library; - Dqn_TicketMutex_Begin(&result->lib_mutex); - if (!result->lib_init) { - Dqn_ArenaCatalog_Init(&result->arena_catalog, arena ? arena : &result->arena_catalog_backup_arena); - result->lib_init = true; - } - Dqn_TicketMutex_End(&result->lib_mutex); - return result; -} - -DQN_API void Dqn_Library_SetLogCallback(Dqn_LogProc *proc, void *user_data) -{ - dqn_library.log_callback = proc; - dqn_library.log_user_data = user_data; -} - -DQN_API void Dqn_Library_SetLogFile(FILE *file) -{ - Dqn_TicketMutex_Begin(&dqn_library.log_file_mutex); - dqn_library.log_file = file; - dqn_library.log_to_file = file ? true : false; - Dqn_TicketMutex_End(&dqn_library.log_file_mutex); -} - -DQN_API void Dqn_Library_DumpThreadContextArenaStat(Dqn_String8 file_path) -{ - #if defined(DQN_DEBUG_THREAD_CONTEXT) - // NOTE: Open a file to write the arena stats to - FILE *file = nullptr; - fopen_s(&file, file_path.data, "a+b"); - if (file) { - Dqn_Log_ErrorF("Failed to dump thread context arenas [file=%.*s]", DQN_STRING_FMT(file_path)); - return; - } - - // NOTE: Copy the stats from library book-keeping - // NOTE: Extremely short critical section, copy the stats then do our - // work on it. - Dqn_ArenaStat stats[Dqn_CArray_CountI(dqn_library.thread_context_arena_stats)]; - int stats_size = 0; - - Dqn_TicketMutex_Begin(&dqn_library.thread_context_mutex); - stats_size = dqn_library.thread_context_arena_stats_count; - DQN_MEMCPY(stats, dqn_library.thread_context_arena_stats, sizeof(stats[0]) * stats_size); - Dqn_TicketMutex_End(&dqn_library.thread_context_mutex); - - // NOTE: Print the cumulative stat - Dqn_DateHMSTimeString now = Dqn_Date_HMSLocalTimeStringNow(); - fprintf(file, - "Time=%.*s %.*s | Thread Context Arenas | Count=%d\n", - now.date_size, now.date, - now.hms_size, now.hms, - dqn_library.thread_context_arena_stats_count); - - // NOTE: Write the cumulative thread arena data - { - Dqn_ArenaStat stat = {}; - for (Dqn_usize index = 0; index < stats_size; index++) { - Dqn_ArenaStat const *current = stats + index; - stat.capacity += current->capacity; - stat.used += current->used; - stat.wasted += current->wasted; - stat.blocks += current->blocks; - - stat.capacity_hwm = DQN_MAX(stat.capacity_hwm, current->capacity_hwm); - stat.used_hwm = DQN_MAX(stat.used_hwm, current->used_hwm); - stat.wasted_hwm = DQN_MAX(stat.wasted_hwm, current->wasted_hwm); - stat.blocks_hwm = DQN_MAX(stat.blocks_hwm, current->blocks_hwm); - } - - Dqn_ArenaStatString stats_string = Dqn_Arena_StatString(&stat); - fprintf(file, " [ALL] CURR %.*s\n", stats_string.size, stats_string.data); - } - - // NOTE: Print individual thread arena data - for (Dqn_usize index = 0; index < stats_size; index++) { - Dqn_ArenaStat const *current = stats + index; - Dqn_ArenaStatString current_string = Dqn_Arena_StatString(current); - fprintf(file, " [%03d] CURR %.*s\n", DQN_CAST(int)index, current_string.size, current_string.data); - } - - fclose(file); - Dqn_Log_InfoF("Dumped thread context arenas [file=%.*s]", DQN_STRING_FMT(file_path)); - #else - (void)file_path; - #endif // #if defined(DQN_DEBUG_THREAD_CONTEXT) -} - -#if defined(DQN_LEAK_TRACING) -DQN_API void Dqn_Library_LeakTraceAdd(Dqn_CallSite call_site, void *ptr, Dqn_usize size) -{ - if (!ptr) - return; - - Dqn_TicketMutex_Begin(&dqn_library.alloc_table_mutex); - if (!Dqn_DSMap_IsValid(&dqn_library.alloc_table)) - dqn_library.alloc_table = Dqn_DSMap_Init(4096); - - // NOTE: If the entry was not added, we are reusing a pointer that has been freed. - // TODO: Add API for always making the item but exposing a var to indicate if the item was newly created or it already existed. - Dqn_LeakTrace *trace = Dqn_DSMap_Find(&dqn_library.alloc_table, Dqn_DSMap_KeyU64(DQN_CAST(uintptr_t)ptr)); - if (trace) { - DQN_HARD_ASSERTF(trace->freed, "This pointer is already in the leak tracker, however it" - " has not been freed yet. Somehow this pointer has been" - " given to the allocation table and has not being marked" - " freed with an equivalent call to LeakTraceMarkFree()" - " [ptr=%p, size=%_$$d, file=\"%.*s:%u\"," - " function=\"%.*s\"]", - ptr, - size, - DQN_STRING_FMT(trace->call_site.file), - trace->call_site.line, - DQN_STRING_FMT(trace->call_site.function)); - } else { - trace = Dqn_DSMap_Make(&dqn_library.alloc_table, Dqn_DSMap_KeyU64(DQN_CAST(uintptr_t)ptr)); - } - - trace->ptr = ptr; - trace->size = size; - trace->call_site = call_site; - Dqn_TicketMutex_End(&dqn_library.alloc_table_mutex); -} - -DQN_API void Dqn_Library_LeakTraceMarkFree(Dqn_CallSite call_site, void *ptr) -{ - if (!ptr) - return; - - Dqn_TicketMutex_Begin(&dqn_library.alloc_table_mutex); - - Dqn_LeakTrace *trace = Dqn_DSMap_Find(&dqn_library.alloc_table, Dqn_DSMap_KeyU64(DQN_CAST(uintptr_t)ptr)); - DQN_HARD_ASSERTF(trace, "Allocated pointer can not be removed as it does not exist in the" - " allocation table. When this memory was allocated, the pointer was" - " not added to the allocation table [ptr=%p]", - ptr); - - DQN_HARD_ASSERTF(!trace->freed, - "Double free detected, pointer was previously allocated at [ptr=%p, %_$$d, file=\"%.*s:%u\", function=\"%.*s\"]", - ptr, - trace->size, - DQN_STRING_FMT(trace->call_site.file), - trace->call_site.line, - DQN_STRING_FMT(trace->call_site.function)); - - trace->freed = true; - trace->freed_size = trace->size; - trace->freed_call_site = call_site; - Dqn_TicketMutex_End(&dqn_library.alloc_table_mutex); -} -#endif /// defined(DQN_LEAK_TRACING) - -// NOTE: [$BITS] Dqn_Bit =========================================================================== -DQN_API void Dqn_Bit_UnsetInplace(uint64_t *flags, uint64_t bitfield) -{ - *flags = (*flags & ~bitfield); -} - -DQN_API void Dqn_Bit_SetInplace(uint64_t *flags, uint64_t bitfield) -{ - *flags = (*flags | bitfield); -} - -DQN_API bool Dqn_Bit_IsSet(uint64_t bits, uint64_t bits_to_set) -{ - auto result = DQN_CAST(bool)((bits & bits_to_set) == bits_to_set); - return result; -} - -DQN_API bool Dqn_Bit_IsNotSet(uint64_t bits, uint64_t bits_to_check) -{ - auto result = !Dqn_Bit_IsSet(bits, bits_to_check); - return result; -} - -// NOTE: [$SAFE] Dqn_Safe ========================================================================== -DQN_API int64_t Dqn_Safe_AddI64(int64_t a, int64_t b) -{ - int64_t result = DQN_CHECKF(a <= INT64_MAX - b, "a=%zd, b=%zd", a, b) ? (a + b) : INT64_MAX; - return result; -} - -DQN_API int64_t Dqn_Safe_MulI64(int64_t a, int64_t b) -{ - int64_t result = DQN_CHECKF(a <= INT64_MAX / b, "a=%zd, b=%zd", a, b) ? (a * b) : INT64_MAX; - return result; -} - -DQN_API uint64_t Dqn_Safe_AddU64(uint64_t a, uint64_t b) -{ - uint64_t result = DQN_CHECKF(a <= UINT64_MAX - b, "a=%zu, b=%zu", a, b) ? (a + b) : UINT64_MAX; - return result; -} - -DQN_API uint64_t Dqn_Safe_SubU64(uint64_t a, uint64_t b) -{ - uint64_t result = DQN_CHECKF(a >= b, "a=%zu, b=%zu", a, b) ? (a - b) : 0; - return result; -} - -DQN_API uint64_t Dqn_Safe_MulU64(uint64_t a, uint64_t b) -{ - uint64_t result = DQN_CHECKF(a <= UINT64_MAX / b, "a=%zu, b=%zu", a, b) ? (a * b) : UINT64_MAX; - return result; -} - -DQN_API uint32_t Dqn_Safe_SubU32(uint32_t a, uint32_t b) -{ - uint32_t result = DQN_CHECKF(a >= b, "a=%u, b=%u", a, b) ? (a - b) : 0; - return result; -} - -// NOTE: Dqn_Safe_SaturateCastUSizeToI* -// ----------------------------------------------------------------------------- -// INT*_MAX literals will be promoted to the type of uintmax_t as uintmax_t is -// the highest possible rank (unsigned > signed). -DQN_API int Dqn_Safe_SaturateCastUSizeToInt(Dqn_usize val) -{ - int result = DQN_CHECK(DQN_CAST(uintmax_t)val <= INT_MAX) ? DQN_CAST(int)val : INT_MAX; - return result; -} - -DQN_API int8_t Dqn_Safe_SaturateCastUSizeToI8(Dqn_usize val) -{ - int8_t result = DQN_CHECK(DQN_CAST(uintmax_t)val <= INT8_MAX) ? DQN_CAST(int8_t)val : INT8_MAX; - return result; -} - -DQN_API int16_t Dqn_Safe_SaturateCastUSizeToI16(Dqn_usize val) -{ - int16_t result = DQN_CHECK(DQN_CAST(uintmax_t)val <= INT16_MAX) ? DQN_CAST(int16_t)val : INT16_MAX; - return result; -} - -DQN_API int32_t Dqn_Safe_SaturateCastUSizeToI32(Dqn_usize val) -{ - int32_t result = DQN_CHECK(DQN_CAST(uintmax_t)val <= INT32_MAX) ? DQN_CAST(int32_t)val : INT32_MAX; - return result; -} - -DQN_API int64_t Dqn_Safe_SaturateCastUSizeToI64(Dqn_usize val) -{ - int64_t result = DQN_CHECK(DQN_CAST(uintmax_t)val <= INT64_MAX) ? DQN_CAST(int64_t)val : INT64_MAX; - return result; -} - -// NOTE: Dqn_Safe_SaturateCastUSizeToU* -// ----------------------------------------------------------------------------- -// Both operands are unsigned and the lowest rank operand will be promoted to -// match the highest rank operand. -DQN_API uint8_t Dqn_Safe_SaturateCastUSizeToU8(Dqn_usize val) -{ - uint8_t result = DQN_CHECK(val <= UINT8_MAX) ? DQN_CAST(uint8_t)val : UINT8_MAX; - return result; -} - -DQN_API uint16_t Dqn_Safe_SaturateCastUSizeToU16(Dqn_usize val) -{ - uint16_t result = DQN_CHECK(val <= UINT16_MAX) ? DQN_CAST(uint16_t)val : UINT16_MAX; - return result; -} - -DQN_API uint32_t Dqn_Safe_SaturateCastUSizeToU32(Dqn_usize val) -{ - uint32_t result = DQN_CHECK(val <= UINT32_MAX) ? DQN_CAST(uint32_t)val : UINT32_MAX; - return result; -} - -DQN_API uint64_t Dqn_Safe_SaturateCastUSizeToU64(Dqn_usize val) -{ - uint64_t result = DQN_CHECK(val <= UINT64_MAX) ? DQN_CAST(uint64_t)val : UINT64_MAX; - return result; -} - -// NOTE: Dqn_Safe_SaturateCastU64ToU* -// ----------------------------------------------------------------------------- -// Both operands are unsigned and the lowest rank operand will be promoted to -// match the highest rank operand. -DQN_API unsigned int Dqn_Safe_SaturateCastU64ToUInt(uint64_t val) -{ - unsigned int result = DQN_CHECK(val <= UINT8_MAX) ? DQN_CAST(unsigned int)val : UINT_MAX; - return result; -} - -DQN_API uint8_t Dqn_Safe_SaturateCastU64ToU8(uint64_t val) -{ - uint8_t result = DQN_CHECK(val <= UINT8_MAX) ? DQN_CAST(uint8_t)val : UINT8_MAX; - return result; -} - -DQN_API uint16_t Dqn_Safe_SaturateCastU64ToU16(uint64_t val) -{ - uint16_t result = DQN_CHECK(val <= UINT16_MAX) ? DQN_CAST(uint16_t)val : UINT16_MAX; - return result; -} - -DQN_API uint32_t Dqn_Safe_SaturateCastU64ToU32(uint64_t val) -{ - uint32_t result = DQN_CHECK(val <= UINT32_MAX) ? DQN_CAST(uint32_t)val : UINT32_MAX; - return result; -} - - -// NOTE: Dqn_Safe_SaturateCastISizeToI* -// ----------------------------------------------------------------------------- -// Both operands are signed so the lowest rank operand will be promoted to -// match the highest rank operand. -DQN_API int Dqn_Safe_SaturateCastISizeToInt(Dqn_isize val) -{ - DQN_ASSERT(val >= INT_MIN && val <= INT_MAX); - int result = DQN_CAST(int)DQN_CLAMP(val, INT_MIN, INT_MAX); - return result; -} - -DQN_API int8_t Dqn_Safe_SaturateCastISizeToI8(Dqn_isize val) -{ - DQN_ASSERT(val >= INT8_MIN && val <= INT8_MAX); - int8_t result = DQN_CAST(int8_t)DQN_CLAMP(val, INT8_MIN, INT8_MAX); - return result; -} - -DQN_API int16_t Dqn_Safe_SaturateCastISizeToI16(Dqn_isize val) -{ - DQN_ASSERT(val >= INT16_MIN && val <= INT16_MAX); - int16_t result = DQN_CAST(int16_t)DQN_CLAMP(val, INT16_MIN, INT16_MAX); - return result; -} - -DQN_API int32_t Dqn_Safe_SaturateCastISizeToI32(Dqn_isize val) -{ - DQN_ASSERT(val >= INT32_MIN && val <= INT32_MAX); - int32_t result = DQN_CAST(int32_t)DQN_CLAMP(val, INT32_MIN, INT32_MAX); - return result; -} - -DQN_API int64_t Dqn_Safe_SaturateCastISizeToI64(Dqn_isize val) -{ - DQN_ASSERT(val >= INT64_MIN && val <= INT64_MAX); - int64_t result = DQN_CAST(int64_t)DQN_CLAMP(val, INT64_MIN, INT64_MAX); - return result; -} - -// NOTE: Dqn_Safe_SaturateCastISizeToU* -// ----------------------------------------------------------------------------- -// If the value is a negative integer, we clamp to 0. Otherwise, we know that -// the value is >=0, we can upcast safely to bounds check against the maximum -// allowed value. -DQN_API unsigned int Dqn_Safe_SaturateCastISizeToUInt(Dqn_isize val) -{ - unsigned int result = 0; - if (DQN_CHECK(val >= DQN_CAST(Dqn_isize)0)) { - if (DQN_CHECK(DQN_CAST(uintmax_t)val <= UINT_MAX)) - result = DQN_CAST(unsigned int)val; - else - result = UINT_MAX; - } - return result; -} - -DQN_API uint8_t Dqn_Safe_SaturateCastISizeToU8(Dqn_isize val) -{ - uint8_t result = 0; - if (DQN_CHECK(val >= DQN_CAST(Dqn_isize)0)) { - if (DQN_CHECK(DQN_CAST(uintmax_t)val <= UINT8_MAX)) - result = DQN_CAST(uint8_t)val; - else - result = UINT8_MAX; - } - return result; -} - -DQN_API uint16_t Dqn_Safe_SaturateCastISizeToU16(Dqn_isize val) -{ - uint16_t result = 0; - if (DQN_CHECK(val >= DQN_CAST(Dqn_isize)0)) { - if (DQN_CHECK(DQN_CAST(uintmax_t)val <= UINT16_MAX)) - result = DQN_CAST(uint16_t)val; - else - result = UINT16_MAX; - } - return result; -} - -DQN_API uint32_t Dqn_Safe_SaturateCastISizeToU32(Dqn_isize val) -{ - uint32_t result = 0; - if (DQN_CHECK(val >= DQN_CAST(Dqn_isize)0)) { - if (DQN_CHECK(DQN_CAST(uintmax_t)val <= UINT32_MAX)) - result = DQN_CAST(uint32_t)val; - else - result = UINT32_MAX; - } - return result; -} - -DQN_API uint64_t Dqn_Safe_SaturateCastISizeToU64(Dqn_isize val) -{ - uint64_t result = 0; - if (DQN_CHECK(val >= DQN_CAST(Dqn_isize)0)) { - if (DQN_CHECK(DQN_CAST(uintmax_t)val <= UINT64_MAX)) - result = DQN_CAST(uint64_t)val; - else - result = UINT64_MAX; - } - return result; -} - -// NOTE: Dqn_Safe_SaturateCastI64To* -// ----------------------------------------------------------------------------- -// Both operands are signed so the lowest rank operand will be promoted to -// match the highest rank operand. -DQN_API Dqn_isize Dqn_Safe_SaturateCastI64ToISize(int64_t val) -{ - DQN_CHECK(val >= DQN_ISIZE_MIN && val <= DQN_ISIZE_MAX); - Dqn_isize result = DQN_CAST(int64_t)DQN_CLAMP(val, DQN_ISIZE_MIN, DQN_ISIZE_MAX); - return result; -} - -DQN_API int8_t Dqn_Safe_SaturateCastI64ToI8(int64_t val) -{ - DQN_CHECK(val >= INT8_MIN && val <= INT8_MAX); - int8_t result = DQN_CAST(int8_t)DQN_CLAMP(val, INT8_MIN, INT8_MAX); - return result; -} - -DQN_API int16_t Dqn_Safe_SaturateCastI64ToI16(int64_t val) -{ - DQN_CHECK(val >= INT16_MIN && val <= INT16_MAX); - int16_t result = DQN_CAST(int16_t)DQN_CLAMP(val, INT16_MIN, INT16_MAX); - return result; -} - -DQN_API int32_t Dqn_Safe_SaturateCastI64ToI32(int64_t val) -{ - DQN_CHECK(val >= INT32_MIN && val <= INT32_MAX); - int32_t result = DQN_CAST(int32_t)DQN_CLAMP(val, INT32_MIN, INT32_MAX); - return result; -} - -// NOTE: Dqn_Safe_SaturateCastIntTo* -// ----------------------------------------------------------------------------- -DQN_API int8_t Dqn_Safe_SaturateCastIntToI8(int val) -{ - DQN_CHECK(val >= INT8_MIN && val <= INT8_MAX); - int8_t result = DQN_CAST(int8_t)DQN_CLAMP(val, INT8_MIN, INT8_MAX); - return result; -} - -DQN_API int16_t Dqn_Safe_SaturateCastIntToI16(int val) -{ - DQN_CHECK(val >= INT16_MIN && val <= INT16_MAX); - int16_t result = DQN_CAST(int16_t)DQN_CLAMP(val, INT16_MIN, INT16_MAX); - return result; -} - -DQN_API uint8_t Dqn_Safe_SaturateCastIntToU8(int val) -{ - uint8_t result = 0; - if (DQN_CHECK(val >= DQN_CAST(Dqn_isize)0)) { - if (DQN_CHECK(DQN_CAST(uintmax_t)val <= UINT8_MAX)) - result = DQN_CAST(uint8_t)val; - else - result = UINT8_MAX; - } - return result; -} - -DQN_API uint16_t Dqn_Safe_SaturateCastIntToU16(int val) -{ - uint16_t result = 0; - if (DQN_CHECK(val >= DQN_CAST(Dqn_isize)0)) { - if (DQN_CHECK(DQN_CAST(uintmax_t)val <= UINT16_MAX)) - result = DQN_CAST(uint16_t)val; - else - result = UINT16_MAX; - } - return result; -} - -DQN_API uint32_t Dqn_Safe_SaturateCastIntToU32(int val) -{ - static_assert(sizeof(val) <= sizeof(uint32_t), "Sanity check to allow simplifying of casting"); - uint32_t result = 0; - if (DQN_CHECK(val >= 0)) - result = DQN_CAST(uint32_t)val; - return result; -} - -DQN_API uint64_t Dqn_Safe_SaturateCastIntToU64(int val) -{ - static_assert(sizeof(val) <= sizeof(uint64_t), "Sanity check to allow simplifying of casting"); - uint64_t result = 0; - if (DQN_CHECK(val >= 0)) - result = DQN_CAST(uint64_t)val; - return result; -} - -// NOTE: [$TCTX] Dqn_ThreadContext ================================================================= -Dqn_ThreadScratch::Dqn_ThreadScratch(DQN_LEAK_TRACE_FUNCTION Dqn_ThreadContext *context, uint8_t context_index) -{ - index = context_index; - allocator = context->temp_allocators[index]; - arena = context->temp_arenas[index]; - temp_memory = Dqn_Arena_BeginTempMemory(arena); - #if defined(DQN_LEAK_TRACING) - leak_site__ = DQN_LEAK_TRACE_ARG_NO_COMMA; - #endif -} - -Dqn_ThreadScratch::~Dqn_ThreadScratch() -{ - #if defined(DQN_DEBUG_THREAD_CONTEXT) - temp_arenas_stat[index] = arena->stats; - #endif - DQN_ASSERT(destructed == false); - #if defined(DQN_LEAK_TRACING) - Dqn_Arena_EndTempMemory_(leak_site__, temp_memory); - #else - Dqn_Arena_EndTempMemory_(temp_memory); - #endif - destructed = true; -} - -DQN_API uint32_t Dqn_Thread_GetID() -{ - #if defined(DQN_OS_WIN32) - unsigned long result = GetCurrentThreadId(); - #else - pid_t result = gettid(); - assert(gettid() >= 0); - #endif - return (uint32_t)result; -} - -DQN_API Dqn_ThreadContext *Dqn_Thread_GetContext_(DQN_LEAK_TRACE_FUNCTION_NO_COMMA) -{ - - DQN_THREAD_LOCAL Dqn_ThreadContext result = {}; - if (!result.init) { - result.init = true; - DQN_ASSERTF(dqn_library.lib_init, "Library must be initialised by calling Dqn_Library_Init(nullptr)"); - - // NOTE: Setup permanent arena - Dqn_ArenaCatalog *catalog = &dqn_library.arena_catalog; - result.allocator = Dqn_Arena_Allocator(result.arena); - result.arena = Dqn_ArenaCatalog_AllocF(catalog, - DQN_GIGABYTES(1) /*size*/, - DQN_KILOBYTES(64) /*commit*/, - "Thread %u Arena", - Dqn_Thread_GetID()); - - // NOTE: Setup temporary arenas - for (uint8_t index = 0; index < DQN_THREAD_CONTEXT_ARENAS; index++) { - result.temp_arenas[index] = Dqn_ArenaCatalog_AllocF(catalog, - DQN_GIGABYTES(1) /*size*/, - DQN_KILOBYTES(64) /*commit*/, - "Thread %u Temp Arena %u", - Dqn_Thread_GetID(), - index); - result.temp_allocators[index] = Dqn_Arena_Allocator(result.temp_arenas[index]); - } - } - return &result; -} - -// TODO: Is there a way to handle conflict arenas without the user needing to -// manually pass it in? -DQN_API Dqn_ThreadScratch Dqn_Thread_GetScratch_(DQN_LEAK_TRACE_FUNCTION void const *conflict_arena) -{ - static_assert(DQN_THREAD_CONTEXT_ARENAS < (uint8_t)-1, "We use UINT8_MAX as a sentinel value"); - Dqn_ThreadContext *context = Dqn_Thread_GetContext_(DQN_LEAK_TRACE_ARG_NO_COMMA); - uint8_t context_index = (uint8_t)-1; - for (uint8_t index = 0; index < DQN_THREAD_CONTEXT_ARENAS; index++) { - Dqn_Arena *arena = context->temp_arenas[index]; - if (!conflict_arena || arena != conflict_arena) { - context_index = index; - break; - } - } - - DQN_ASSERT(context_index != (uint8_t)-1); - return Dqn_ThreadScratch(DQN_LEAK_TRACE_ARG context, context_index); -} - diff --git a/dqn_misc.h b/dqn_misc.h deleted file mode 100644 index 49ee967..0000000 --- a/dqn_misc.h +++ /dev/null @@ -1,386 +0,0 @@ -// NOTE: Table Of Contents ========================================================================= -// Index | Disable #define | Description -// ================================================================================================= -// [$DLIB] Dqn_Library | | Library run-time behaviour configuration -// [$BITS] Dqn_Bit | | Bitset manipulation -// [$SAFE] Dqn_Safe | | Safe arithmetic, casts, asserts -// [$TCTX] Dqn_ThreadContext | | Per-thread data structure e.g. temp arenas -// [$BSEA] Dqn_BinarySearch | | Binary search -// ================================================================================================= - -// NOTE: [$DLIB] Dqn_Library ======================================================================= -// Book-keeping data for the library and allow customisation of certain features -// provided. -// -// NOTE: API ======================================================================================= -// @proc Dqn_Library_SetLogCallback -// @desc Update the default logging function, all logging functions will run through -// this callback -// @param[in] proc The new logging function, set to nullptr to revert back to -// the default logger. -// @param[in] user_data A user defined parameter to pass to the callback -// -// @proc Dqn_Library_SetLogFile -// @param[in] file Pass in nullptr to turn off writing logs to disk, otherwise -// point it to the FILE that you wish to write to. -// -// @proc Dqn_Library_DumpThreadContextArenaStat -// @desc Dump the per-thread arena statistics to the specified file - -struct Dqn_Library -{ - bool lib_init; - Dqn_TicketMutex lib_mutex; - - Dqn_LogProc *log_callback; ///< Set this pointer to override the logging routine - void * log_user_data; - bool log_to_file; ///< Output logs to file as well as standard out - void * log_file; ///< TODO(dqn): Hmmm, how should we do this... ? - Dqn_TicketMutex log_file_mutex; ///< Is locked when instantiating the log_file for the first time - bool log_no_colour; ///< Disable colours in the logging output - - /// The backup arena to use if no arena is passed into Dqn_Library_Init - Dqn_Arena arena_catalog_backup_arena; - Dqn_ArenaCatalog arena_catalog; - - #if defined(DQN_LEAK_TRACING) - Dqn_TicketMutex alloc_table_mutex; - Dqn_DSMap alloc_table; - #endif - - #if defined(DQN_OS_WIN32) - LARGE_INTEGER win32_qpc_frequency; - Dqn_TicketMutex win32_bcrypt_rng_mutex; - void *win32_bcrypt_rng_handle; - #endif - - #if defined(DQN_DEBUG_THREAD_CONTEXT) - Dqn_TicketMutex thread_context_mutex; - Dqn_ArenaStat thread_context_arena_stats[256]; - uint8_t thread_context_arena_stats_count; - #endif -} extern dqn_library; - -// NOTE: Properties ================================================================================ -DQN_API Dqn_Library *Dqn_Library_Init (Dqn_Arena *arena); -DQN_API void Dqn_Library_SetLogCallback (Dqn_LogProc *proc, void *user_data); -DQN_API void Dqn_Library_SetLogFile (void *file); -DQN_API void Dqn_Library_DumpThreadContextArenaStat(Dqn_String8 file_path); - -// NOTE: Leak Trace ================================================================================ -#if defined(DQN_LEAK_TRACING) -DQN_API void Dqn_Library_LeakTraceAdd (Dqn_CallSite call_site, void *ptr, Dqn_usize size); -DQN_API void Dqn_Library_LeakTraceMarkFree (Dqn_CallSite call_site, void *ptr); -#else -#define Dqn_Library_LeakTraceAdd(...) -#define Dqn_Library_LeakTraceMarkFree(...) -#endif - -// NOTE: [$BITS] Dqn_Bit =========================================================================== -DQN_API void Dqn_Bit_UnsetInplace(uint32_t *flags, uint32_t bitfield); -DQN_API void Dqn_Bit_SetInplace (uint32_t *flags, uint32_t bitfield); -DQN_API bool Dqn_Bit_IsSet (uint32_t bits, uint32_t bits_to_set); -DQN_API bool Dqn_Bit_IsNotSet (uint32_t bits, uint32_t bits_to_check); - -// NOTE: [$SAFE] Dqn_Safe ========================================================================== -// @proc Dqn_Safe_AddI64, Dqn_Safe_MulI64 -// @desc Add/multiply 2 I64's together, safe asserting that the operation does -// not overflow. -// @return The result of operation, INT64_MAX if it overflowed. - -// @proc Dqn_Safe_AddU64, Dqn_Safe_MulU64 -// @desc Add/multiply 2 U64's together, safe asserting that the operation does -// not overflow. -// @return The result of operation, UINT64_MAX if it overflowed. - -// @proc Dqn_Safe_SubU64, Dqn_Safe_SubU32 -// @desc Subtract 2xU64 or 2xU32's together, safe asserting that the operation -// does not overflow. -// @return The result of operation, 0 if it overflowed. - -// @proc Dqn_Safe_SaturateCastUSizeToInt, -// Dqn_Safe_SaturateCastUSizeToI8, -// Dqn_Safe_SaturateCastUSizeToI16, -// Dqn_Safe_SaturateCastUSizeToI32, -// Dqn_Safe_SaturateCastUSizeToI64 -// -// Dqn_Safe_SaturateCastU64ToUInt, -// Dqn_Safe_SaturateCastU64ToU8, -// Dqn_Safe_SaturateCastU64ToU16, -// Dqn_Safe_SaturateCastU64ToU32, -// -// Dqn_Safe_SaturateCastUSizeToU8, -// Dqn_Safe_SaturateCastUSizeToU16, -// Dqn_Safe_SaturateCastUSizeToU32, -// Dqn_Safe_SaturateCastUSizeToU64 -// -// Dqn_Safe_SaturateCastISizeToInt, -// Dqn_Safe_SaturateCastISizeToI8, -// Dqn_Safe_SaturateCastISizeToI16, -// Dqn_Safe_SaturateCastISizeToI32, -// Dqn_Safe_SaturateCastISizeToI64, -// -// int Dqn_Safe_SaturateCastISizeToUInt, -// Dqn_Safe_SaturateCastISizeToU8, -// Dqn_Safe_SaturateCastISizeToU16, -// Dqn_Safe_SaturateCastISizeToU32, -// Dqn_Safe_SaturateCastISizeToU64, -// -// Dqn_Safe_SaturateCastI64ToISize, -// Dqn_Safe_SaturateCastI64ToI8, -// Dqn_Safe_SaturateCastI64ToI16, -// Dqn_Safe_SaturateCastI64ToI32, -// -// Dqn_Safe_SaturateCastIntToI8, -// Dqn_Safe_SaturateCastIntToU8, -// Dqn_Safe_SaturateCastIntToU16, -// Dqn_Safe_SaturateCastIntToU32, -// Dqn_Safe_SaturateCastIntToU64, -// -// @desc Truncate the lhs operand to the right clamping the result to the max -// value of the desired data type. Safe asserts if clamping occurs. -// -// The following sentinel values are returned when saturated, -// USize -> Int: INT_MAX -// USize -> I8: INT8_MAX -// USize -> I16: INT16_MAX -// USize -> I32: INT32_MAX -// USize -> I64: INT64_MAX -// -// U64 -> UInt: UINT_MAX -// U64 -> U8: UINT8_MAX -// U64 -> U16: UINT16_MAX -// U64 -> U32: UINT32_MAX -// -// USize -> U8: UINT8_MAX -// USize -> U16: UINT16_MAX -// USize -> U32: UINT32_MAX -// USize -> U64: UINT64_MAX -// -// ISize -> Int: INT_MIN or INT_MAX -// ISize -> I8: INT8_MIN or INT8_MAX -// ISize -> I16: INT16_MIN or INT16_MAX -// ISize -> I32: INT32_MIN or INT32_MAX -// ISize -> I64: INT64_MIN or INT64_MAX -// -// ISize -> UInt: 0 or UINT_MAX -// ISize -> U8: 0 or UINT8_MAX -// ISize -> U16: 0 or UINT16_MAX -// ISize -> U32: 0 or UINT32_MAX -// ISize -> U64: 0 or UINT64_MAX -// -// I64 -> ISize: DQN_ISIZE_MIN or DQN_ISIZE_MAX -// I64 -> I8: INT8_MIN or INT8_MAX -// I64 -> I16: INT16_MIN or INT16_MAX -// I64 -> I32: INT32_MIN or INT32_MAX -// -// Int -> I8: INT8_MIN or INT8_MAX -// Int -> I16: INT16_MIN or INT16_MAX -// Int -> U8: 0 or UINT8_MAX -// Int -> U16: 0 or UINT16_MAX -// Int -> U32: 0 or UINT32_MAX -// Int -> U64: 0 or UINT64_MAX - -DQN_API int64_t Dqn_Safe_AddI64 (int64_t a, int64_t b); -DQN_API int64_t Dqn_Safe_MulI64 (int64_t a, int64_t b); - -DQN_API uint64_t Dqn_Safe_AddU64 (uint64_t a, uint64_t b); -DQN_API uint64_t Dqn_Safe_MulU64 (uint64_t a, uint64_t b); - -DQN_API uint64_t Dqn_Safe_SubU64 (uint64_t a, uint64_t b); -DQN_API uint32_t Dqn_Safe_SubU32 (uint32_t a, uint32_t b); - -DQN_API int Dqn_Safe_SaturateCastUSizeToInt (Dqn_usize val); -DQN_API int8_t Dqn_Safe_SaturateCastUSizeToI8 (Dqn_usize val); -DQN_API int16_t Dqn_Safe_SaturateCastUSizeToI16 (Dqn_usize val); -DQN_API int32_t Dqn_Safe_SaturateCastUSizeToI32 (Dqn_usize val); -DQN_API int64_t Dqn_Safe_SaturateCastUSizeToI64 (Dqn_usize val); - -DQN_API unsigned int Dqn_Safe_SaturateCastU64ToUInt (uint64_t val); -DQN_API uint8_t Dqn_Safe_SaturateCastU64ToU8 (uint64_t val); -DQN_API uint16_t Dqn_Safe_SaturateCastU64ToU16 (uint64_t val); -DQN_API uint32_t Dqn_Safe_SaturateCastU64ToU32 (uint64_t val); - -DQN_API uint8_t Dqn_Safe_SaturateCastUSizeToU8 (Dqn_usize val); -DQN_API uint16_t Dqn_Safe_SaturateCastUSizeToU16 (Dqn_usize val); -DQN_API uint32_t Dqn_Safe_SaturateCastUSizeToU32 (Dqn_usize val); -DQN_API uint64_t Dqn_Safe_SaturateCastUSizeToU64 (Dqn_usize val); - -DQN_API int Dqn_Safe_SaturateCastISizeToInt (Dqn_isize val); -DQN_API int8_t Dqn_Safe_SaturateCastISizeToI8 (Dqn_isize val); -DQN_API int16_t Dqn_Safe_SaturateCastISizeToI16 (Dqn_isize val); -DQN_API int32_t Dqn_Safe_SaturateCastISizeToI32 (Dqn_isize val); -DQN_API int64_t Dqn_Safe_SaturateCastISizeToI64 (Dqn_isize val); - -DQN_API unsigned int Dqn_Safe_SaturateCastISizeToUInt(Dqn_isize val); -DQN_API uint8_t Dqn_Safe_SaturateCastISizeToU8 (Dqn_isize val); -DQN_API uint16_t Dqn_Safe_SaturateCastISizeToU16 (Dqn_isize val); -DQN_API uint32_t Dqn_Safe_SaturateCastISizeToU32 (Dqn_isize val); -DQN_API uint64_t Dqn_Safe_SaturateCastISizeToU64 (Dqn_isize val); - -DQN_API Dqn_isize Dqn_Safe_SaturateCastI64ToISize (int64_t val); -DQN_API int8_t Dqn_Safe_SaturateCastI64ToI8 (int64_t val); -DQN_API int16_t Dqn_Safe_SaturateCastI64ToI16 (int64_t val); -DQN_API int32_t Dqn_Safe_SaturateCastI64ToI32 (int64_t val); - -DQN_API int8_t Dqn_Safe_SaturateCastIntToI8 (int val); -DQN_API int16_t Dqn_Safe_SaturateCastIntToI16 (int val); -DQN_API uint8_t Dqn_Safe_SaturateCastIntToU8 (int val); -DQN_API uint16_t Dqn_Safe_SaturateCastIntToU16 (int val); -DQN_API uint32_t Dqn_Safe_SaturateCastIntToU32 (int val); -DQN_API uint64_t Dqn_Safe_SaturateCastIntToU64 (int val); - -// NOTE: [$TCTX] Dqn_ThreadContext ================================================================= -// Each thread is assigned in their thread-local storage (TLS) scratch and -// permanent arena allocators. These can be used for allocations with a lifetime -// scoped to the lexical scope or for storing data permanently using the arena -// paradigm. -// -// TLS in this implementation is implemented using the `thread_local` C/C++ -// keyword. -// -// NOTE: API -// -// @proc Dqn_Thread_GetContext -// @desc Get the current thread's context- this contains all the metadata for managing -// the thread scratch data. In general you probably want Dqn_Thread_GetScratch() -// which ensures you get a usable scratch arena for temporary allocations -// without having to worry about selecting the right arena from the state. -// -// @proc Dqn_Thread_GetScratch -// @desc Retrieve the per-thread temporary arena allocator that is reset on scope -// exit. -// -// The scratch arena must be deconflicted with any existing arenas in the -// function to avoid trampling over each other's memory. Consider the situation -// where the scratch arena is passed into the function. Inside the function, if -// the same arena is reused then, if both arenas allocate, when the inner arena -// is reset, this will undo the passed in arena's allocations in the function. -// -// @param[in] conflict_arena A pointer to the arena currently being used in the -// function - -#if !defined(DQN_THREAD_CONTEXT_ARENAS) - #define DQN_THREAD_CONTEXT_ARENAS 2 -#endif - -struct Dqn_ThreadContext -{ - Dqn_b32 init; - - Dqn_Arena *arena; ///< Per thread arena - Dqn_Allocator allocator; ///< Allocator that uses the arena - - /// Temp memory arena's for the calling thread - Dqn_Arena *temp_arenas[DQN_THREAD_CONTEXT_ARENAS]; - - /// Allocators that use the corresponding arena from the thread context. - /// Provided for convenience when interfacing with allocator interfaces. - Dqn_Allocator temp_allocators[DQN_THREAD_CONTEXT_ARENAS]; - - #if defined(DQN_DEBUG_THREAD_CONTEXT) - Dqn_ArenaStat temp_arenas_stat[DQN_THREAD_CONTEXT_ARENAS]; - #endif -}; - -struct Dqn_ThreadScratch -{ - Dqn_ThreadScratch(DQN_LEAK_TRACE_FUNCTION Dqn_ThreadContext *context, uint8_t context_index); - ~Dqn_ThreadScratch(); - - /// Index into the arena/allocator/stat array in the thread context - /// specifying what arena was assigned. - uint8_t index; - Dqn_Allocator allocator; - Dqn_Arena *arena; - Dqn_b32 destructed = false; /// Detect copies of the scratch - Dqn_ArenaTempMemory temp_memory; - #if defined(DQN_LEAK_TRACING) - Dqn_CallSite leak_site__; - #endif -}; - -// NOTE: Context =================================================================================== -#define Dqn_Thread_GetContext() Dqn_Thread_GetContext_(DQN_LEAK_TRACE_NO_COMMA) -#define Dqn_Thread_GetScratch(conflict_arena) Dqn_Thread_GetScratch_(DQN_LEAK_TRACE conflict_arena) -DQN_API uint32_t Dqn_Thread_GetID(); - -// NOTE: Internal ================================================================================== -DQN_API Dqn_ThreadContext *Dqn_Thread_GetContext_(DQN_LEAK_TRACE_FUNCTION_NO_COMMA); -DQN_API Dqn_ThreadScratch Dqn_Thread_GetScratch_(DQN_LEAK_TRACE_FUNCTION void const *conflict_arena); - -// NOTE: [$BSEA] Dqn_BinarySearch ================================================================== -template -using Dqn_BinarySearchLessThanProc = bool(T const &lhs, T const &rhs); - -template -DQN_FORCE_INLINE bool Dqn_BinarySearch_DefaultLessThan(T const &lhs, T const &rhs); - -enum Dqn_BinarySearchType -{ - /// Index of the match. If no match is found, found is set to false and the - /// index is set to 0 - Dqn_BinarySearchType_Match, - - /// Index after the match. If no match is found, found is set to false and - /// the index is set to one past the closest match. - Dqn_BinarySearchType_OnePastMatch, -}; - -struct Dqn_BinarySearchResult -{ - bool found; - Dqn_usize index; -}; - -template -Dqn_BinarySearchResult -Dqn_BinarySearch(T const *array, - Dqn_usize array_size, - T const &find, - Dqn_BinarySearchType type = Dqn_BinarySearchType_Match, - Dqn_BinarySearchLessThanProc less_than = Dqn_BinarySearch_DefaultLessThan); - - -template DQN_FORCE_INLINE bool Dqn_BinarySearch_DefaultLessThan(T const &lhs, T const &rhs) -{ - bool result = lhs < rhs; - return result; -} - -template -Dqn_BinarySearchResult -Dqn_BinarySearch(T const *array, - Dqn_usize array_size, - T const &find, - Dqn_BinarySearchType type, - Dqn_BinarySearchLessThanProc less_than) -{ - Dqn_BinarySearchResult result = {}; - Dqn_usize head = 0; - Dqn_usize tail = array_size - 1; - if (array && array_size > 0) { - while (!result.found && head <= tail) { - Dqn_usize mid = (head + tail) / 2; - T const &value = array[mid]; - if (less_than(find, value)) { - tail = mid - 1; - if (mid == 0) - break; - } else if (less_than(value, find)) { - head = mid + 1; - } else { - result.found = true; - result.index = mid; - } - } - } - - if (type == Dqn_BinarySearchType_OnePastMatch) - result.index = result.found ? result.index + 1 : tail + 1; - else - DQN_ASSERT(type == Dqn_BinarySearchType_Match); - - return result; -} - diff --git a/dqn_platform.cpp b/dqn_platform.cpp index 3de79eb..f892005 100644 --- a/dqn_platform.cpp +++ b/dqn_platform.cpp @@ -1,356 +1,206 @@ -#if defined(DQN_OS_WIN32) -// NOTE: [$W32H] Win32 Min Header ================================================================== - #pragma comment(lib, "bcrypt") - #pragma comment(lib, "wininet") +// NOTE: [$PRIN] Dqn_Print ========================================================================= +DQN_API Dqn_PrintStyle Dqn_Print_StyleColour(uint8_t r, uint8_t g, uint8_t b, Dqn_PrintBold bold) +{ + Dqn_PrintStyle result = {}; + result.bold = bold; + result.colour = true; + result.r = r; + result.g = g; + result.b = b; + return result; +} - #if defined(DQN_NO_WIN32_MIN_HEADER) - #include // Dqn_OS_SecureRNGBytes -> BCryptOpenAlgorithmProvider ... etc - #include // Dqn_Win_MakeProcessDPIAware -> SetProcessDpiAwareProc - #if !defined(DQN_NO_WINNET) - #include // Dqn_Win_Net -> InternetConnect ... etc - #endif // DQN_NO_WINNET +DQN_API Dqn_PrintStyle Dqn_Print_StyleColourU32(uint32_t rgb, Dqn_PrintBold bold) +{ + uint8_t r = (rgb >> 24) & 0xFF; + uint8_t g = (rgb >> 16) & 0xFF; + uint8_t b = (rgb >> 8) & 0xFF; + Dqn_PrintStyle result = Dqn_Print_StyleColour(r, g, b, bold); + return result; +} + +DQN_API Dqn_PrintStyle Dqn_Print_StyleBold() +{ + Dqn_PrintStyle result = {}; + result.bold = Dqn_PrintBold_Yes; + return result; +} + +DQN_API void Dqn_Print_Std(Dqn_PrintStd std_handle, Dqn_String8 string) +{ + DQN_ASSERT(std_handle == Dqn_PrintStd_Out || std_handle == Dqn_PrintStd_Err); + + #if defined(DQN_OS_WIN32) + // NOTE: Get the output handles from kernel + // ========================================================================= + DQN_THREAD_LOCAL void *std_out_print_handle = nullptr; + DQN_THREAD_LOCAL void *std_err_print_handle = nullptr; + DQN_THREAD_LOCAL bool std_out_print_to_console = false; + DQN_THREAD_LOCAL bool std_err_print_to_console = false; + + if (!std_out_print_handle) { + unsigned long mode = 0; (void)mode; + std_out_print_handle = GetStdHandle(STD_OUTPUT_HANDLE); + std_out_print_to_console = GetConsoleMode(std_out_print_handle, &mode) != 0; + + std_err_print_handle = GetStdHandle(STD_ERROR_HANDLE); + std_err_print_to_console = GetConsoleMode(std_err_print_handle, &mode) != 0; + } + + // NOTE: Select the output handle + // ========================================================================= + void *print_handle = std_out_print_handle; + bool print_to_console = std_out_print_to_console; + if (std_handle == Dqn_PrintStd_Err) { + print_handle = std_err_print_handle; + print_to_console = std_err_print_to_console; + } + + // NOTE: Write the string + // ========================================================================= + DQN_ASSERT(string.size < DQN_CAST(unsigned long)-1); + unsigned long bytes_written = 0; (void)bytes_written; + if (print_to_console) { + WriteConsoleA(print_handle, string.data, DQN_CAST(unsigned long)string.size, &bytes_written, nullptr); + } else { + WriteFile(print_handle, string.data, DQN_CAST(unsigned long)string.size, &bytes_written, nullptr); + } #else - // Taken from Windows.h - // Defines - // --------------------------------------------------------------------- - #define MAX_PATH 260 + fprintf(std_handle == Dqn_PrintStd_Out ? stdout : stderr, "%.*s", DQN_STRING_FMT(string)); + #endif +} - // NOTE: Wait/Synchronization - #define INFINITE 0xFFFFFFFF // Infinite timeout +DQN_API void Dqn_Print_StdStyle(Dqn_PrintStd std_handle, Dqn_PrintStyle style, Dqn_String8 string) +{ + if (string.data && string.size) { + if (style.colour) + Dqn_Print_Std(std_handle, Dqn_Print_ESCColourFgString(style.r, style.g, style.b)); + if (style.bold == Dqn_PrintBold_Yes) + Dqn_Print_Std(std_handle, Dqn_Print_ESCBoldString); + Dqn_Print_Std(std_handle, string); + if (style.colour || style.bold == Dqn_PrintBold_Yes) + Dqn_Print_Std(std_handle, Dqn_Print_ESCResetString); + } +} - // NOTE: FormatMessageA - #define FORMAT_MESSAGE_FROM_SYSTEM 0x00001000 - #define FORMAT_MESSAGE_IGNORE_INSERTS 0x00000200 - #define FORMAT_MESSAGE_FROM_HMODULE 0x00000800 - #define MAKELANGID(p, s) ((((unsigned short )(s)) << 10) | (unsigned short )(p)) - #define SUBLANG_DEFAULT 0x01 // user default - #define LANG_NEUTRAL 0x00 +DQN_FILE_SCOPE char *Dqn_Print_VSPrintfChunker_(const char *buf, void *user, int len) +{ + Dqn_String8 string = {}; + string.data = DQN_CAST(char *)buf; + string.size = len; - // NOTE: MultiByteToWideChar - #define CP_UTF8 65001 // UTF-8 translation + Dqn_PrintStd std_handle = DQN_CAST(Dqn_PrintStd)DQN_CAST(uintptr_t)user; + Dqn_Print_Std(std_handle, string); + return (char *)buf; +} - // NOTE: VirtualAlloc - // NOTE: Allocation Type - #define MEM_RESERVE 0x00002000 - #define MEM_COMMIT 0x00001000 - #define MEM_DECOMMIT 0x00004000 - #define MEM_RELEASE 0x00008000 +DQN_API void Dqn_Print_StdF(Dqn_PrintStd std_handle, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + Dqn_Print_StdFV(std_handle, fmt, args); + va_end(args); +} - // NOTE: Protect - #define PAGE_NOACCESS 0x01 - #define PAGE_READONLY 0x02 - #define PAGE_READWRITE 0x04 - #define PAGE_GUARD 0x100 +DQN_API void Dqn_Print_StdFStyle(Dqn_PrintStd std_handle, Dqn_PrintStyle style, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + Dqn_Print_StdFVStyle(std_handle, style, fmt, args); + va_end(args); +} - // NOTE: FindFirstFile - #define INVALID_HANDLE_VALUE ((void *)(long *)-1) - #define INVALID_FILE_ATTRIBUTES ((unsigned long)-1) - #define FILE_ATTRIBUTE_NORMAL 0x00000080 - #define FIND_FIRST_EX_LARGE_FETCH 0x00000002 - #define FILE_ATTRIBUTE_DIRECTORY 0x00000010 - #define FILE_ATTRIBUTE_READONLY 0x00000001 - #define FILE_ATTRIBUTE_HIDDEN 0x00000002 +DQN_API void Dqn_Print_StdFV(Dqn_PrintStd std_handle, char const *fmt, va_list args) +{ + char buffer[STB_SPRINTF_MIN]; + STB_SPRINTF_DECORATE(vsprintfcb)(Dqn_Print_VSPrintfChunker_, DQN_CAST(void *)DQN_CAST(uintptr_t)std_handle, buffer, fmt, args); +} - // NOTE: GetModuleFileNameW - #define ERROR_INSUFFICIENT_BUFFER 122L +DQN_API void Dqn_Print_StdFVStyle(Dqn_PrintStd std_handle, Dqn_PrintStyle style, char const *fmt, va_list args) +{ + if (fmt) { + if (style.colour) + Dqn_Print_Std(std_handle, Dqn_Print_ESCColourFgString(style.r, style.g, style.b)); + if (style.bold == Dqn_PrintBold_Yes) + Dqn_Print_Std(std_handle, Dqn_Print_ESCBoldString); + Dqn_Print_StdFV(std_handle, fmt, args); + if (style.colour || style.bold == Dqn_PrintBold_Yes) + Dqn_Print_Std(std_handle, Dqn_Print_ESCResetString); + } +} - // NOTE: MoveFile - #define MOVEFILE_REPLACE_EXISTING 0x00000001 - #define MOVEFILE_COPY_ALLOWED 0x00000002 +DQN_API void Dqn_Print_StdLn(Dqn_PrintStd std_handle, Dqn_String8 string) +{ + Dqn_Print_Std(std_handle, string); + Dqn_Print_Std(std_handle, DQN_STRING8("\n")); +} - // NOTE: Wininet - typedef unsigned short INTERNET_PORT; - #define INTERNET_OPEN_TYPE_PRECONFIG 0 // use registry configuration - #define INTERNET_DEFAULT_HTTPS_PORT 443 // HTTPS - #define INTERNET_SERVICE_HTTP 3 - #define INTERNET_OPTION_USER_AGENT 41 - #define INTERNET_FLAG_NO_AUTH 0x00040000 // no automatic authentication handling - #define INTERNET_FLAG_SECURE 0x00800000 // use PCT/SSL if applicable (HTTP) +DQN_API void Dqn_Print_StdLnF(Dqn_PrintStd std_handle, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + Dqn_Print_StdLnFV(std_handle, fmt, args); + va_end(args); +} - // NOTE: CreateFile - #define GENERIC_READ (0x80000000L) - #define GENERIC_WRITE (0x40000000L) - #define GENERIC_EXECUTE (0x20000000L) - #define GENERIC_ALL (0x10000000L) - #define FILE_ATTRIBUTE_NORMAL 0x00000080 - #define FILE_APPEND_DATA 4 +DQN_API void Dqn_Print_StdLnFV(Dqn_PrintStd std_handle, char const *fmt, va_list args) +{ + Dqn_Print_StdFV(std_handle, fmt, args); + Dqn_Print_Std(std_handle, DQN_STRING8("\n")); +} - #define CREATE_NEW 1 - #define CREATE_ALWAYS 2 - #define OPEN_EXISTING 3 - #define OPEN_ALWAYS 4 - #define TRUNCATE_EXISTING 5 +DQN_API void Dqn_Print_StdLnStyle(Dqn_PrintStd std_handle, Dqn_PrintStyle style, Dqn_String8 string) +{ + Dqn_Print_StdStyle(std_handle, style, string); + Dqn_Print_Std(std_handle, DQN_STRING8("\n")); +} - #define STD_INPUT_HANDLE ((unsigned long)-10) - #define STD_OUTPUT_HANDLE ((unsigned long)-11) - #define STD_ERROR_HANDLE ((unsigned long)-12) +DQN_API void Dqn_Print_StdLnFStyle(Dqn_PrintStd std_handle, Dqn_PrintStyle style, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + Dqn_Print_StdLnFVStyle(std_handle, style, fmt, args); + va_end(args); +} - #define INVALID_FILE_SIZE ((unsigned long)0xFFFFFFFF) +DQN_API void Dqn_Print_StdLnFVStyle(Dqn_PrintStd std_handle, Dqn_PrintStyle style, char const *fmt, va_list args) +{ + Dqn_Print_StdFVStyle(std_handle, style, fmt, args); + Dqn_Print_Std(std_handle, DQN_STRING8("\n")); +} - #define HTTP_QUERY_RAW_HEADERS 21 - #define HTTP_QUERY_RAW_HEADERS_CRLF 22 +DQN_API Dqn_String8 Dqn_Print_ESCColourString(Dqn_PrintESCColour colour, uint8_t r, uint8_t g, uint8_t b) +{ + DQN_THREAD_LOCAL char buffer[32]; + buffer[0] = 0; + Dqn_String8 result = {}; + result.size = STB_SPRINTF_DECORATE(snprintf)(buffer, + DQN_ARRAY_UCOUNT(buffer), + "\x1b[%d;2;%u;%u;%um", + colour == Dqn_PrintESCColour_Fg ? 38 : 48, + r, g, b); + result.data = buffer; + return result; +} - // NOTE: HttpAddRequestHeadersA - #define HTTP_ADDREQ_FLAG_ADD_IF_NEW 0x10000000 - #define HTTP_ADDREQ_FLAG_ADD 0x20000000 - #define HTTP_ADDREQ_FLAG_COALESCE_WITH_COMMA 0x40000000 - #define HTTP_ADDREQ_FLAG_COALESCE_WITH_SEMICOLON 0x01000000 - #define HTTP_ADDREQ_FLAG_COALESCE HTTP_ADDREQ_FLAG_COALESCE_WITH_COMMA - #define HTTP_ADDREQ_FLAG_REPLACE 0x80000000 +DQN_API Dqn_String8 Dqn_Print_ESCColourU32String(Dqn_PrintESCColour colour, uint32_t value) +{ + uint8_t r = DQN_CAST(uint8_t)(value >> 24); + uint8_t g = DQN_CAST(uint8_t)(value >> 16); + uint8_t b = DQN_CAST(uint8_t)(value >> 8); + Dqn_String8 result = Dqn_Print_ESCColourString(colour, r, g, b); + return result; +} - #define SW_MAXIMIZED 3 - #define SW_SHOW 5 - - typedef enum PROCESS_DPI_AWARENESS { - PROCESS_DPI_UNAWARE = 0, - PROCESS_SYSTEM_DPI_AWARE = 1, - PROCESS_PER_MONITOR_DPI_AWARE = 2 - } PROCESS_DPI_AWARENESS; - - #define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((void *)-4) - - typedef union { - struct { - unsigned long LowPart; - unsigned long HighPart; - } DUMMYSTRUCTNAME; - struct { - unsigned long LowPart; - unsigned long HighPart; - } u; - uint64_t QuadPart; - } ULARGE_INTEGER; - - typedef struct - { - unsigned long dwLowDateTime; - unsigned long dwHighDateTime; - } FILETIME; - - typedef struct - { - unsigned long dwFileAttributes; - FILETIME ftCreationTime; - FILETIME ftLastAccessTime; - FILETIME ftLastWriteTime; - unsigned long nFileSizeHigh; - unsigned long nFileSizeLow; - } WIN32_FILE_ATTRIBUTE_DATA; - - typedef enum - { - GetFileExInfoStandard, - GetFileExMaxInfoLevel - } GET_FILEEX_INFO_LEVELS; - - typedef struct { - unsigned long nLength; - void *lpSecurityDescriptor; - bool bInheritHandle; - } SECURITY_ATTRIBUTES; - - typedef struct { - long left; - long top; - long right; - long bottom; - } RECT, *PRECT, *NPRECT, *LPRECT; - - typedef struct { - union { - unsigned long dwOemId; // Obsolete field...do not use - struct { - uint16_t wProcessorArchitecture; - uint16_t wReserved; - } DUMMYSTRUCTNAME; - } DUMMYUNIONNAME; - unsigned long dwPageSize; - void *lpMinimumApplicationAddress; - void *lpMaximumApplicationAddress; - unsigned long *dwActiveProcessorMask; - unsigned long dwNumberOfProcessors; - unsigned long dwProcessorType; - unsigned long dwAllocationGranularity; - uint16_t wProcessorLevel; - uint16_t wProcessorRevision; - } SYSTEM_INFO; - - typedef struct { - unsigned short wYear; - unsigned short wMonth; - unsigned short wDayOfWeek; - unsigned short wDay; - unsigned short wHour; - unsigned short wMinute; - unsigned short wSecond; - unsigned short wMilliseconds; - } SYSTEMTIME; - - typedef struct { - unsigned long dwFileAttributes; - FILETIME ftCreationTime; - FILETIME ftLastAccessTime; - FILETIME ftLastWriteTime; - unsigned long nFileSizeHigh; - unsigned long nFileSizeLow; - unsigned long dwReserved0; - unsigned long dwReserved1; - wchar_t cFileName[MAX_PATH]; - wchar_t cAlternateFileName[14]; - #ifdef _MAC - unsigned long dwFileType; - unsigned long dwCreatorType; - unsigned short wFinderFlags; - #endif - } WIN32_FIND_DATAW; - - typedef enum { - FindExInfoStandard, - FindExInfoBasic, - FindExInfoMaxInfoLevel, - } FINDEX_INFO_LEVELS; - - typedef enum { - FindExSearchNameMatch, - FindExSearchLimitToDirectories, - FindExSearchLimitToDevices, - FindExSearchMaxSearchOp - } FINDEX_SEARCH_OPS; - - typedef enum { - INTERNET_SCHEME_PARTIAL = -2, - INTERNET_SCHEME_UNKNOWN = -1, - INTERNET_SCHEME_DEFAULT = 0, - INTERNET_SCHEME_FTP, - INTERNET_SCHEME_GOPHER, - INTERNET_SCHEME_HTTP, - INTERNET_SCHEME_HTTPS, - INTERNET_SCHEME_FILE, - INTERNET_SCHEME_NEWS, - INTERNET_SCHEME_MAILTO, - INTERNET_SCHEME_SOCKS, - INTERNET_SCHEME_JAVASCRIPT, - INTERNET_SCHEME_VBSCRIPT, - INTERNET_SCHEME_RES, - INTERNET_SCHEME_FIRST = INTERNET_SCHEME_FTP, - INTERNET_SCHEME_LAST = INTERNET_SCHEME_RES - } INTERNET_SCHEME; - - typedef struct { - unsigned long dwStructSize; // size of this structure. Used in version check - char *lpszScheme; // pointer to scheme name - unsigned long dwSchemeLength; // length of scheme name - INTERNET_SCHEME nScheme; // enumerated scheme type (if known) - char *lpszHostName; // pointer to host name - unsigned long dwHostNameLength; // length of host name - INTERNET_PORT nPort; // converted port number - char *lpszUserName; // pointer to user name - unsigned long dwUserNameLength; // length of user name - char *lpszPassword; // pointer to password - unsigned long dwPasswordLength; // length of password - char *lpszUrlPath; // pointer to URL-path - unsigned long dwUrlPathLength; // length of URL-path - char *lpszExtraInfo; // pointer to extra information (e.g. ?foo or #foo) - unsigned long dwExtraInfoLength; // length of extra information - } URL_COMPONENTSA; - - // Functions - // --------------------------------------------------------------------- - extern "C" - { - /*BOOL*/ int __stdcall CreateDirectoryW (wchar_t const *lpPathName, SECURITY_ATTRIBUTES *lpSecurityAttributes); - /*BOOL*/ int __stdcall RemoveDirectoryW (wchar_t const *lpPathName); - /*DWORD*/ unsigned long __stdcall GetCurrentDirectoryW (unsigned long nBufferLength, wchar_t *lpBuffer); - - /*BOOL*/ int __stdcall FindNextFileW (void *hFindFile, WIN32_FIND_DATAW *lpFindFileData); - /*HANDLE*/ void * __stdcall FindFirstFileExW (wchar_t const *lpFileName, FINDEX_INFO_LEVELS fInfoLevelId, void *lpFindFileData, FINDEX_SEARCH_OPS fSearchOp, void *lpSearchFilter, unsigned long dwAdditionalFlags); - /*DWORD*/ unsigned long __stdcall GetFileAttributesExW (wchar_t const *lpFileName, GET_FILEEX_INFO_LEVELS fInfoLevelId, WIN32_FILE_ATTRIBUTE_DATA *lpFileInformation); - /*BOOL*/ int __stdcall GetFileSizeEx (void *hFile, LARGE_INTEGER *lpFileSize); - - /*BOOL*/ int __stdcall MoveFileExW (wchar_t const *lpExistingFileName, wchar_t const *lpNewFileName, unsigned long dwFlags); - /*BOOL*/ int __stdcall CopyFileW (wchar_t const *lpExistingFileName, wchar_t const *lpNewFileName, int bFailIfExists); - /*BOOL*/ int __stdcall DeleteFileW (wchar_t const *lpExistingFileName); - /*HANDLE*/ void * __stdcall CreateFileW (wchar_t const *lpFileName, unsigned long dwDesiredAccess, unsigned long dwShareMode, SECURITY_ATTRIBUTES *lpSecurityAttributes, unsigned long dwCreationDisposition, unsigned long dwFlagsAndAttributes, void *hTemplateFile); - /*BOOL*/ int __stdcall ReadFile (void *hFile, void *lpBuffer, unsigned long nNumberOfBytesToRead, unsigned long *lpNumberOfBytesRead, struct OVERLAPPED *lpOverlapped); - /*BOOL*/ int __stdcall WriteFile (void *hFile, void const *lpBuffer, unsigned long nNumberOfBytesToWrite, unsigned long *lpNumberOfBytesWritten, struct OVERLAPPED *lpOverlapped); - /*BOOL*/ int __stdcall CloseHandle (void *hObject); - - /*BOOL*/ int __stdcall WriteConsoleA (void *hConsoleOutput, const char *lpBuffer, unsigned long nNumberOfCharsToWrite, unsigned long *lpNumberOfCharsWritten, void *lpReserved); - /*BOOL*/ int __stdcall AllocConsole (); - /*BOOL*/ int __stdcall FreeConsole (); - /*BOOL*/ int __stdcall AttachConsole (unsigned long dwProcessId); - /*HANDLE*/ void * __stdcall GetStdHandle (unsigned long nStdHandle); - /*BOOL*/ int __stdcall GetConsoleMode (void *hConsoleHandle, unsigned long *lpMode); - - /*HMODULE*/ void * __stdcall LoadLibraryA (char const *lpFileName); - /*BOOL*/ int __stdcall FreeLibrary (void *hModule); - /*FARPROC*/ void * __stdcall GetProcAddress (void *hModule, char const *lpProcName); - - /*BOOL*/ int __stdcall GetWindowRect (void *hWnd, RECT *lpRect); - /*BOOL*/ int __stdcall SetWindowPos (void *hWnd, void *hWndInsertAfter, int X, int Y, int cx, int cy, unsigned int uFlags); - - /*DWORD*/ unsigned long __stdcall GetWindowModuleFileNameA (void *hwnd, char *pszFileName, unsigned int cchFileNameMax); - /*HMODULE*/ void * __stdcall GetModuleHandleA (char const *lpModuleName); - /*DWORD*/ unsigned long __stdcall GetModuleFileNameW (void *hModule, wchar_t *lpFilename, unsigned long nSize); - - /*DWORD*/ unsigned long __stdcall WaitForSingleObject (void *hHandle, unsigned long dwMilliseconds); - - /*BOOL*/ int __stdcall QueryPerformanceCounter (LARGE_INTEGER *lpPerformanceCount); - /*BOOL*/ int __stdcall QueryPerformanceFrequency (LARGE_INTEGER *lpFrequency); - - /*HANDLE*/ void * __stdcall CreateThread (SECURITY_ATTRIBUTES *lpThreadAttributes, size_t dwStackSize, unsigned long (*lpStartAddress)(void *), void *lpParameter, unsigned long dwCreationFlags, unsigned long *lpThreadId); - /*HANDLE*/ void * __stdcall CreateSemaphoreA (SECURITY_ATTRIBUTES *lpSecurityAttributes, long lInitialCount, long lMaxCount, char *lpName); - /*BOOL*/ int __stdcall ReleaseSemaphore (void *semaphore, long lReleaseCount, long *lpPreviousCount); - void __stdcall Sleep (unsigned long dwMilliseconds); - /*DWORD*/ unsigned long __stdcall GetCurrentThreadId (); - - void * __stdcall VirtualAlloc (void *lpAddress, size_t dwSize, unsigned long flAllocationType, unsigned long flProtect); - /*BOOL*/ int __stdcall VirtualProtect (void *lpAddress, size_t dwSize, unsigned long flNewProtect, unsigned long *lpflOldProtect); - /*BOOL*/ int __stdcall VirtualFree (void *lpAddress, size_t dwSize, unsigned long dwFreeType); - - void __stdcall GetSystemInfo (SYSTEM_INFO *system_info); - void __stdcall GetSystemTime (SYSTEMTIME *lpSystemTime); - void __stdcall GetSystemTimeAsFileTime (FILETIME *lpFileTime); - void __stdcall GetLocalTime (SYSTEMTIME *lpSystemTime); - - /*DWORD*/ unsigned long __stdcall FormatMessageA (unsigned long dwFlags, void *lpSource, unsigned long dwMessageId, unsigned long dwLanguageId, char *lpBuffer, unsigned long nSize, va_list *Arguments); - /*DWORD*/ unsigned long __stdcall GetLastError (); - - int __stdcall MultiByteToWideChar (unsigned int CodePage, unsigned long dwFlags, char const *lpMultiByteStr, int cbMultiByte, wchar_t *lpWideCharStr, int cchWideChar); - int __stdcall WideCharToMultiByte (unsigned int CodePage, unsigned long dwFlags, wchar_t const *lpWideCharStr, int cchWideChar, char *lpMultiByteStr, int cbMultiByte, char const *lpDefaultChar, bool *lpUsedDefaultChar); - - /*NTSTATUS*/ long __stdcall BCryptOpenAlgorithmProvider(void *phAlgorithm, wchar_t const *pszAlgId, wchar_t const *pszImplementation, unsigned long dwFlags); - /*NTSTATUS*/ long __stdcall BCryptGenRandom (void *hAlgorithm, unsigned char *pbBuffer, unsigned long cbBuffer, unsigned long dwFlags); - - /*BOOLAPI*/ int __stdcall InternetCrackUrlA (char const *lpszUrl, unsigned long dwUrlLength, unsigned long dwFlags, URL_COMPONENTSA *lpUrlComponents); - /*HANDLE*/ void * __stdcall InternetOpenA (char const *lpszAgent, unsigned long dwAccessType, char const *lpszProxy, char const *lpszProxyBypass, unsigned long dwFlags); - /*HANDLE*/ void * __stdcall InternetConnectA (void *hInternet, char const *lpszServerName, INTERNET_PORT nServerPort, char const *lpszUserName, char const *lpszPassword, unsigned long dwService, unsigned long dwFlags, unsigned long *dwContext); - /*BOOLAPI*/ int __stdcall InternetSetOptionA (void *hInternet, unsigned long dwOption, void *lpBuffer, unsigned long dwBufferLength); - /*BOOLAPI*/ int __stdcall InternetReadFile (void *hFile, void *lpBuffer, unsigned long dwNumberOfBytesToRead, unsigned long *lpdwNumberOfBytesRead); - /*BOOLAPI*/ int __stdcall InternetCloseHandle (void *hInternet); - /*HANDLE*/ void * __stdcall HttpOpenRequestA (void *hConnect, char const *lpszVerb, char const *lpszObjectName, char const *lpszVersion, char const *lpszReferrer, char const **lplpszAcceptTypes, unsigned long dwFlags, unsigned long *dwContext); - /*BOOLAPI*/ int __stdcall HttpSendRequestA (void *hRequest, char const *lpszHeaders, unsigned long dwHeadersLength, void *lpOptional, unsigned long dwOptionalLength); - /*BOOLAPI*/ int __stdcall HttpAddRequestHeadersA (void *hRequest, char const *lpszHeaders, unsigned long dwHeadersLength, unsigned long dwModifiers); - /*BOOL*/ int __stdcall HttpQueryInfoA (void *hRequest, unsigned long dwInfoLevel, void *lpBuffer, unsigned long *lpdwBufferLength, unsigned long *lpdwIndex); - - /*HINSTANCE*/ void * __stdcall ShellExecuteA (void *hwnd, char const *lpOperation, char const *lpFile, char const *lpParameters, char const *lpDirectory, int nShowCmd); - /*BOOL*/ int __stdcall ShowWindow (void *hWnd, int nCmdShow); - } - #endif // !defined(DQN_NO_WIN32_MINIMAL_HEADER) && !defined(_INC_WINDOWS) -#elif defined(DQN_OS_UNIX) - #include // errno - #include // O_RDONLY ... etc - #include // FICLONE - #include // ioctl - #include // pid_t - #include // getrandom - #include // stat - #include // sendfile - #include // mmap - #include // clock_gettime, nanosleep - #include // access, gettid +// TODO(doyle): Use our temp scratch arenas instead of a massive array on the +// stack. +// NOTE: Max size from MSDN, using \\? syntax, but the ? bit can be expanded +// even more so the max size is kind of not well defined. +#if defined(DQN_OS_WIN32) && !defined(DQN_OS_WIN32_MAX_PATH) + #define DQN_OS_WIN32_MAX_PATH 32767 + 128 /*fudge*/ #endif +#if !defined(DQN_NO_FS) // NOTE: [$FSYS] Dqn_Fs ============================================================================ #if defined(DQN_OS_WIN32) DQN_API uint64_t Dqn__WinFileTimeToSeconds(FILETIME const *time) @@ -363,16 +213,10 @@ DQN_API uint64_t Dqn__WinFileTimeToSeconds(FILETIME const *time) } #endif -// NOTE: Max size from MSDN, using \\? syntax, but the ? bit can be expanded -// even more so the max size is kind of not well defined. -#if defined(DQN_OS_WIN32) && !defined(DQN_OS_WIN32_MAX_PATH) - #define DQN_OS_WIN32_MAX_PATH 32767 + 128 /*fudge*/ -#endif - DQN_API bool Dqn_Fs_Exists(Dqn_String8 path) { bool result = false; -#if defined(DQN_OS_WIN32) + #if defined(DQN_OS_WIN32) wchar_t path16[DQN_OS_WIN32_MAX_PATH]; int path16_size = Dqn_Win_String8ToCString16(path, path16, DQN_ARRAY_ICOUNT(path16)); if (path16_size) { @@ -382,16 +226,13 @@ DQN_API bool Dqn_Fs_Exists(Dqn_String8 path) !(attrib_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); } } - -#elif defined(DQN_OS_UNIX) + #elif defined(DQN_OS_UNIX) struct stat stat_result; if (lstat(path.data, &stat_result) != -1) result = S_ISREG(stat_result.st_mode) || S_ISLNK(stat_result.st_mode); - -#else + #else #error Unimplemented - -#endif + #endif return result; } @@ -399,7 +240,7 @@ DQN_API bool Dqn_Fs_Exists(Dqn_String8 path) DQN_API bool Dqn_Fs_DirExists(Dqn_String8 path) { bool result = false; -#if defined(DQN_OS_WIN32) + #if defined(DQN_OS_WIN32) wchar_t path16[DQN_OS_WIN32_MAX_PATH]; int path16_size = Dqn_Win_String8ToCString16(path, path16, DQN_ARRAY_ICOUNT(path16)); if (path16_size) { @@ -410,14 +251,13 @@ DQN_API bool Dqn_Fs_DirExists(Dqn_String8 path) } } -#elif defined(DQN_OS_UNIX) + #elif defined(DQN_OS_UNIX) struct stat stat_result; if (lstat(path.data, &stat_result) != -1) result = S_ISDIR(stat_result.st_mode); -#else + #else #error Unimplemented -#endif - + #endif return result; } @@ -472,7 +312,7 @@ DQN_API Dqn_FsInfo Dqn_Fs_GetInfo(Dqn_String8 path) DQN_API bool Dqn_Fs_Copy(Dqn_String8 src, Dqn_String8 dest, bool overwrite) { bool result = false; -#if defined(DQN_OS_WIN32) + #if defined(DQN_OS_WIN32) Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); Dqn_String16 src16 = Dqn_Win_String8ToString16Allocator(src, Dqn_Arena_Allocator(scratch.arena)); Dqn_String16 dest16 = Dqn_Win_String8ToString16Allocator(dest, Dqn_Arena_Allocator(scratch.arena)); @@ -488,7 +328,7 @@ DQN_API bool Dqn_Fs_Copy(Dqn_String8 src, Dqn_String8 dest, bool overwrite) DQN_STRING_FMT(error)); } -#elif defined(DQN_OS_UNIX) + #elif defined(DQN_OS_UNIX) int src_fd = open(src.data, O_RDONLY); int dest_fd = open(dest.data, O_WRONLY | O_CREAT | (overwrite ? O_TRUNC : 0)); @@ -504,9 +344,9 @@ DQN_API bool Dqn_Fs_Copy(Dqn_String8 src, Dqn_String8 dest, bool overwrite) if (dest_fd != -1) close(dest_fd); -#else + #else #error Unimplemented -#endif + #endif return result; } @@ -518,7 +358,7 @@ DQN_API bool Dqn_Fs_MakeDir(Dqn_String8 path) Dqn_usize path_indexes_size = 0; uint16_t path_indexes[64] = {}; -#if defined(DQN_OS_WIN32) + #if defined(DQN_OS_WIN32) Dqn_String16 path16 = Dqn_Win_String8ToString16Allocator(path, Dqn_Arena_Allocator(scratch.arena)); // NOTE: Go back from the end of the string to all the directories in the @@ -573,7 +413,7 @@ DQN_API bool Dqn_Fs_MakeDir(Dqn_String8 path) if (index != 0) path16.data[path_index] = temp; } -#elif defined(DQN_OS_UNIX) + #elif defined(DQN_OS_UNIX) Dqn_String8 copy = Dqn_String8_Copy(scratch.arena, path); for (Dqn_usize index = copy.size - 1; index < copy.size; index--) { bool first_char = index == (copy.size - 1); @@ -613,9 +453,9 @@ DQN_API bool Dqn_Fs_MakeDir(Dqn_String8 path) if (index != 0) copy.data[path_index] = temp; } -#else + #else #error Unimplemented -#endif + #endif return result; } @@ -624,7 +464,7 @@ DQN_API bool Dqn_Fs_Move(Dqn_String8 src, Dqn_String8 dest, bool overwrite) { bool result = false; -#if defined(DQN_OS_WIN32) + #if defined(DQN_OS_WIN32) Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); Dqn_String16 src16 = Dqn_Win_String8ToString16Allocator(src, Dqn_Arena_Allocator(scratch.arena)); Dqn_String16 dest16 = Dqn_Win_String8ToString16Allocator(dest, Dqn_Arena_Allocator(scratch.arena)); @@ -642,7 +482,7 @@ DQN_API bool Dqn_Fs_Move(Dqn_String8 src, Dqn_String8 dest, bool overwrite) DQN_STRING_FMT(Dqn_Win_LastError())); } -#elif defined(DQN_OS_UNIX) + #elif defined(DQN_OS_UNIX) // See: https://github.com/gingerBill/gb/blob/master/gb.h bool file_moved = true; if (link(src.data, dest.data) == -1) @@ -655,17 +495,16 @@ DQN_API bool Dqn_Fs_Move(Dqn_String8 src, Dqn_String8 dest, bool overwrite) if (file_moved) result = (unlink(src.data) != -1); // Remove original file -#else + #else #error Unimplemented - -#endif + #endif return result; } DQN_API bool Dqn_Fs_Delete(Dqn_String8 path) { bool result = false; -#if defined(DQN_OS_WIN32) + #if defined(DQN_OS_WIN32) wchar_t path16[DQN_OS_WIN32_MAX_PATH]; int path16_size = Dqn_Win_String8ToCString16(path, path16, DQN_ARRAY_ICOUNT(path16)); if (path16_size) { @@ -673,18 +512,16 @@ DQN_API bool Dqn_Fs_Delete(Dqn_String8 path) if (!result) result = RemoveDirectoryW(path16); } -#elif defined(DQN_OS_UNIX) + #elif defined(DQN_OS_UNIX) result = remove(path.data) == 0; -#else + #else #error Unimplemented -#endif - + #endif return result; } -// NOTE: Read/Write Entire File API -// ============================================================================= -DQN_API char *Dqn_Fs_ReadCString8_(DQN_LEAK_TRACE_FUNCTION char const *path, Dqn_usize path_size, Dqn_usize *file_size, Dqn_Allocator allocator) +// NOTE: R/W Entire File =========================================================================== +DQN_API char *Dqn_Fs_ReadCString8(char const *path, Dqn_usize path_size, Dqn_usize *file_size, Dqn_Allocator allocator) { char *result = nullptr; if (!path) @@ -696,7 +533,7 @@ DQN_API char *Dqn_Fs_ReadCString8_(DQN_LEAK_TRACE_FUNCTION char const *path, Dqn (void)allocator; (void)file_size; -#if defined(DQN_OS_WIN32) + #if defined(DQN_OS_WIN32) // NOTE: Convert to UTF16 // ------------------------------------------------------------------------- Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(allocator.user_context); @@ -737,10 +574,10 @@ DQN_API char *Dqn_Fs_ReadCString8_(DQN_LEAK_TRACE_FUNCTION char const *path, Dqn // NOTE: Read the file from disk // ------------------------------------------------------------------------- - result = DQN_CAST(char *)Dqn_Allocator_Alloc_(DQN_LEAK_TRACE_ARG allocator, - bytes_desired, - alignof(char), - Dqn_ZeroMem_No); + result = DQN_CAST(char *)Dqn_Allocator_Alloc(allocator, + bytes_desired, + alignof(char), + Dqn_ZeroMem_No); unsigned long bytes_read = 0; unsigned long read_result = ReadFile(/*HANDLE hFile*/ file_handle, /*LPVOID lpBuffer*/ result, @@ -769,7 +606,7 @@ DQN_API char *Dqn_Fs_ReadCString8_(DQN_LEAK_TRACE_FUNCTION char const *path, Dqn if (file_size) { *file_size = Dqn_Safe_SaturateCastI64ToISize(bytes_read); } -#else + #else Dqn_usize file_size_ = 0; if (!file_size) file_size = &file_size_; @@ -790,7 +627,7 @@ DQN_API char *Dqn_Fs_ReadCString8_(DQN_LEAK_TRACE_FUNCTION char const *path, Dqn } rewind(file_handle); - result = DQN_CAST(char *)Dqn_Allocator_Alloc(DQN_LEAK_TRACE_ARG allocator, + result = DQN_CAST(char *)Dqn_Allocator_Alloc(allocator, *file_size, alignof(char), Dqn_ZeroMem_No); @@ -805,14 +642,14 @@ DQN_API char *Dqn_Fs_ReadCString8_(DQN_LEAK_TRACE_FUNCTION char const *path, Dqn Dqn_Log_ErrorF("Failed to read %td bytes into buffer from '%s'\n", *file_size, file); return result; } -#endif + #endif return result; } -DQN_API Dqn_String8 Dqn_Fs_ReadString8_(DQN_LEAK_TRACE_FUNCTION Dqn_String8 path, Dqn_Allocator allocator) +DQN_API Dqn_String8 Dqn_Fs_Read(Dqn_String8 path, Dqn_Allocator allocator) { Dqn_usize file_size = 0; - char * string = Dqn_Fs_ReadCString8_(DQN_LEAK_TRACE_ARG path.data, path.size, &file_size, allocator); + char * string = Dqn_Fs_ReadCString8(path.data, path.size, &file_size, allocator); Dqn_String8 result = Dqn_String8_Init(string, file_size); return result; } @@ -823,7 +660,7 @@ DQN_API bool Dqn_Fs_WriteCString8(char const *path, Dqn_usize path_size, char co if (!path || !buffer || buffer_size <= 0) return result; -#if defined(DQN_OS_WIN32) + #if defined(DQN_OS_WIN32) if (path_size <= 0) path_size = Dqn_CString8_Size(path); @@ -849,7 +686,7 @@ DQN_API bool Dqn_Fs_WriteCString8(char const *path, Dqn_usize path_size, char co result = WriteFile(file_handle, buffer, DQN_CAST(unsigned long)buffer_size, &bytes_written, nullptr /*lpOverlapped*/); DQN_ASSERT(bytes_written == buffer_size); return result; -#else + #else // TODO(dqn): Use OS apis (void)path_size; @@ -866,17 +703,16 @@ DQN_API bool Dqn_Fs_WriteCString8(char const *path, Dqn_usize path_size, char co Dqn_Log_ErrorF("Failed to 'fwrite' memory to file [file=%s]", path); return result; -#endif + #endif } -DQN_API bool Dqn_Fs_WriteString8(Dqn_String8 file_path, Dqn_String8 buffer) +DQN_API bool Dqn_Fs_Write(Dqn_String8 file_path, Dqn_String8 buffer) { bool result = Dqn_Fs_WriteCString8(file_path.data, file_path.size, buffer.data, buffer.size); return result; } -// NOTE: Read/Write File Stream API -// ============================================================================= +// NOTE: R/W Stream API ============================================================================ DQN_API Dqn_FsFile Dqn_Fs_OpenFile(Dqn_String8 path, Dqn_FsFileOpen open_mode, uint32_t access) { Dqn_FsFile result = {}; @@ -1031,6 +867,7 @@ DQN_API void Dqn_Fs_CloseFile(Dqn_FsFile *file) #endif *file = {}; } +#endif // !defined(DQN_NO_FS) DQN_API bool Dqn_FsPath_AddRef(Dqn_Arena *arena, Dqn_FsPath *fs_path, Dqn_String8 path) { @@ -1046,7 +883,7 @@ DQN_API bool Dqn_FsPath_AddRef(Dqn_Arena *arena, Dqn_FsPath *fs_path, Dqn_String }; for (;;) { Dqn_String8BinarySplitResult delimiter = Dqn_String8_BinarySplitArray(path, delimiter_array, DQN_ARRAY_UCOUNT(delimiter_array)); - for (; delimiter.lhs.data; delimiter = Dqn_String8_BinarySplitArray(delimiter.rhs, delimiter_array, DQN_ARRAY_UCOUNT(delimiter_array))) { + for (; delimiter.lhs.data; delimiter = Dqn_String8_BinarySplitArray(delimiter.rhs, delimiter_array, DQN_ARRAY_UCOUNT(delimiter_array))) { if (delimiter.lhs.size <= 0) continue; @@ -1080,6 +917,16 @@ DQN_API bool Dqn_FsPath_Add(Dqn_Arena *arena, Dqn_FsPath *fs_path, Dqn_String8 p return result; } +DQN_API bool Dqn_FsPath_AddF(Dqn_Arena *arena, Dqn_FsPath *fs_path, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + Dqn_String8 path = Dqn_String8_InitFV(Dqn_Arena_Allocator(arena), fmt, args); + va_end(args); + bool result = Dqn_FsPath_AddRef(arena, fs_path, path); + return result; +} + DQN_API bool Dqn_FsPath_Pop(Dqn_FsPath *fs_path) { if (!fs_path) @@ -1102,7 +949,7 @@ DQN_API bool Dqn_FsPath_Pop(Dqn_FsPath *fs_path) return true; } -DQN_API Dqn_String8 Dqn_FsPath_ConvertString8(Dqn_Arena *arena, Dqn_String8 path) +DQN_API Dqn_String8 Dqn_FsPath_Convert(Dqn_Arena *arena, Dqn_String8 path) { Dqn_FsPath fs_path = {}; Dqn_FsPath_AddRef(arena, &fs_path, path); @@ -1110,6 +957,17 @@ DQN_API Dqn_String8 Dqn_FsPath_ConvertString8(Dqn_Arena *arena, Dqn_String8 path return result; } +DQN_API Dqn_String8 Dqn_FsPath_ConvertF(Dqn_Arena *arena, char const *fmt, ...) +{ + Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(arena); + va_list args; + va_start(args, fmt); + Dqn_String8 path = Dqn_String8_InitFV(scratch.allocator, fmt, args); + va_end(args); + Dqn_String8 result = Dqn_FsPath_Convert(arena, path); + return result; +} + DQN_API Dqn_String8 Dqn_FsPath_BuildWithSeparator(Dqn_Arena *arena, Dqn_FsPath const *fs_path, Dqn_String8 path_separator) { Dqn_String8 result = {}; @@ -1235,6 +1093,7 @@ DQN_API uint64_t Dqn_Date_EpochTime() } #if defined(DQN_OS_WIN32) +#if !defined(DQN_NO_WIN) // NOTE: [$WIND] Dqn_Win =========================================================================== DQN_API void Dqn_Win_LastErrorToBuffer(Dqn_WinErrorMsg *msg) { @@ -1416,7 +1275,7 @@ DQN_API Dqn_String16 Dqn_Win_EXEDirWArena(Dqn_Arena *arena) Dqn_String16 result = {}; if (dir_size > 0) { - result.data = Dqn_Arena_CopyZ(arena, wchar_t, dir, dir_size); + result.data = Dqn_Arena_NewCopyZ(arena, wchar_t, dir, dir_size); if (result.data) { result.size = dir_size; } @@ -1554,8 +1413,9 @@ DQN_API bool Dqn_Win_FolderIterate(Dqn_String8 path, Dqn_Win_FolderIterator *it) return result; } +#endif // !defined(DQN_NO_WIN) -#if !defined(DQN_NO_WIN_NET) +#if !defined(DQN_NO_WINNET) // NOTE: [$WINN] Dqn_WinNet ======================================================================== DQN_API Dqn_WinNetHandle Dqn_Win_NetHandleInitCString(char const *url, int url_size) { @@ -1792,7 +1652,7 @@ DQN_API Dqn_WinNetHandleResponse Dqn_Win_NetHandleSendRequest(Dqn_WinNetHandle * DQN_ASSERT(!found_content_length); if (!found_content_length) { found_content_length = true; - result.content_length = Dqn_String8_ToU64(value, 0 /*separator*/); + result.content_length = Dqn_String8_ToU64(value, 0 /*separator*/).value; } } @@ -1962,23 +1822,23 @@ DQN_API bool Dqn_OS_SecureRNGBytes(void *buffer, uint32_t size) #if defined(DQN_OS_WIN32) bool init = true; - Dqn_TicketMutex_Begin(&dqn_library.win32_bcrypt_rng_mutex); - if (!dqn_library.win32_bcrypt_rng_handle) + Dqn_TicketMutex_Begin(&g_dqn_library->win32_bcrypt_rng_mutex); + if (!g_dqn_library->win32_bcrypt_rng_handle) { wchar_t const BCRYPT_ALGORITHM[] = L"RNG"; - long /*NTSTATUS*/ init_status = BCryptOpenAlgorithmProvider(&dqn_library.win32_bcrypt_rng_handle, BCRYPT_ALGORITHM, nullptr /*implementation*/, 0 /*flags*/); - if (!dqn_library.win32_bcrypt_rng_handle || init_status != 0) + long /*NTSTATUS*/ init_status = BCryptOpenAlgorithmProvider(&g_dqn_library->win32_bcrypt_rng_handle, BCRYPT_ALGORITHM, nullptr /*implementation*/, 0 /*flags*/); + if (!g_dqn_library->win32_bcrypt_rng_handle || init_status != 0) { Dqn_Log_ErrorF("Failed to initialise random number generator, error: %d", init_status); init = false; } } - Dqn_TicketMutex_End(&dqn_library.win32_bcrypt_rng_mutex); + Dqn_TicketMutex_End(&g_dqn_library->win32_bcrypt_rng_mutex); if (!init) return false; - long gen_status = BCryptGenRandom(dqn_library.win32_bcrypt_rng_handle, DQN_CAST(unsigned char *)buffer, size, 0 /*flags*/); + long gen_status = BCryptGenRandom(g_dqn_library->win32_bcrypt_rng_handle, DQN_CAST(unsigned char *)buffer, size, 0 /*flags*/); if (gen_status != 0) { Dqn_Log_ErrorF("Failed to generate random bytes: %d", gen_status); @@ -2003,17 +1863,16 @@ DQN_API bool Dqn_OS_SecureRNGBytes(void *buffer, uint32_t size) return true; } +#if (defined(DQN_OS_WIN32) && !defined(DQN_NO_WIN)) || !defined(DQN_OS_WIN32) DQN_API Dqn_String8 Dqn_OS_EXEDir(Dqn_Allocator allocator) { Dqn_String8 result = {}; -#if defined(DQN_OS_WIN32) + #if defined(DQN_OS_WIN32) wchar_t exe_dir[DQN_OS_WIN32_MAX_PATH]; Dqn_usize exe_dir_size = Dqn_Win_EXEDirW(exe_dir, DQN_ARRAY_ICOUNT(exe_dir)); result = Dqn_Win_CString16ToString8Allocator(exe_dir, DQN_CAST(int)exe_dir_size, allocator); - -#elif defined(DQN_OS_UNIX) - + #elif defined(DQN_OS_UNIX) int required_size_wo_null_terminator = 0; for (int try_size = 128; ; @@ -2084,43 +1943,43 @@ DQN_API Dqn_String8 Dqn_OS_EXEDir(Dqn_Allocator allocator) result = Dqn_String8_Init(exe_path, required_size_wo_null_terminator); } } - -#else + #else #error Unimplemented -#endif - + #endif return result; } +#endif DQN_API void Dqn_OS_SleepMs(Dqn_uint milliseconds) { -#if defined(DQN_OS_WIN32) + #if defined(DQN_OS_WIN32) Sleep(milliseconds); -#else + #else struct timespec ts; ts.tv_sec = milliseconds / 1000; ts.tv_nsec = (milliseconds % 1000) * 1'000'000; nanosleep(&ts, nullptr); -#endif + #endif } DQN_FILE_SCOPE void Dqn_OS_PerfCounter_Init() { -#if defined(DQN_OS_WIN32) - if (dqn_library.win32_qpc_frequency.QuadPart == 0) - QueryPerformanceFrequency(&dqn_library.win32_qpc_frequency); -#endif + // TODO(doyle): Move this to Dqn_Library_Init + #if defined(DQN_OS_WIN32) + if (g_dqn_library->win32_qpc_frequency.QuadPart == 0) + QueryPerformanceFrequency(&g_dqn_library->win32_qpc_frequency); + #endif } DQN_API Dqn_f64 Dqn_OS_PerfCounterS(uint64_t begin, uint64_t end) { Dqn_OS_PerfCounter_Init(); uint64_t ticks = end - begin; -#if defined(DQN_OS_WIN32) - Dqn_f64 result = ticks / DQN_CAST(Dqn_f64)dqn_library.win32_qpc_frequency.QuadPart; -#else + #if defined(DQN_OS_WIN32) + Dqn_f64 result = ticks / DQN_CAST(Dqn_f64)g_dqn_library->win32_qpc_frequency.QuadPart; + #else Dqn_f64 result = ticks / 1'000'000'000; -#endif + #endif return result; } @@ -2128,11 +1987,11 @@ DQN_API Dqn_f64 Dqn_OS_PerfCounterMs(uint64_t begin, uint64_t end) { Dqn_OS_PerfCounter_Init(); uint64_t ticks = end - begin; -#if defined(DQN_OS_WIN32) - Dqn_f64 result = (ticks * 1'000) / DQN_CAST(Dqn_f64)dqn_library.win32_qpc_frequency.QuadPart; -#else + #if defined(DQN_OS_WIN32) + Dqn_f64 result = (ticks * 1'000) / DQN_CAST(Dqn_f64)g_dqn_library->win32_qpc_frequency.QuadPart; + #else Dqn_f64 result = ticks / DQN_CAST(Dqn_f64)1'000'000; -#endif + #endif return result; } @@ -2140,11 +1999,11 @@ DQN_API Dqn_f64 Dqn_OS_PerfCounterMicroS(uint64_t begin, uint64_t end) { Dqn_OS_PerfCounter_Init(); uint64_t ticks = end - begin; -#if defined(DQN_OS_WIN32) - Dqn_f64 result = (ticks * 1'000'000) / DQN_CAST(Dqn_f64)dqn_library.win32_qpc_frequency.QuadPart; -#else + #if defined(DQN_OS_WIN32) + Dqn_f64 result = (ticks * 1'000'000) / DQN_CAST(Dqn_f64)g_dqn_library->win32_qpc_frequency.QuadPart; + #else Dqn_f64 result = ticks / DQN_CAST(Dqn_f64)1'000; -#endif + #endif return result; } @@ -2152,28 +2011,41 @@ DQN_API Dqn_f64 Dqn_OS_PerfCounterNs(uint64_t begin, uint64_t end) { Dqn_OS_PerfCounter_Init(); uint64_t ticks = end - begin; -#if defined(DQN_OS_WIN32) - Dqn_f64 result = (ticks * 1'000'000'000) / DQN_CAST(Dqn_f64)dqn_library.win32_qpc_frequency.QuadPart; -#else + #if defined(DQN_OS_WIN32) + Dqn_f64 result = (ticks * 1'000'000'000) / DQN_CAST(Dqn_f64)g_dqn_library->win32_qpc_frequency.QuadPart; + #else Dqn_f64 result = ticks; -#endif + #endif + return result; +} + +DQN_API uint64_t Dqn_OS_PerfCounterFrequency() +{ + uint64_t result = 0; + #if defined(DQN_OS_WIN32) + LARGE_INTEGER integer = {}; + QueryPerformanceFrequency(&integer); + result = integer.QuadPart; + #else + // NOTE: On Linux we use clock_gettime(CLOCK_MONOTONIC_RAW) which + // increments at nanosecond granularity. + result = 1'000'000'000; + #endif return result; } DQN_API uint64_t Dqn_OS_PerfCounterNow() { uint64_t result = 0; -#if defined(DQN_OS_WIN32) + #if defined(DQN_OS_WIN32) LARGE_INTEGER integer = {}; - int qpc_result = QueryPerformanceCounter(&integer); - (void)qpc_result; - DQN_ASSERTF(qpc_result, "MSDN says this can only fail when running on a version older than Windows XP"); + QueryPerformanceCounter(&integer); result = integer.QuadPart; -#else + #else struct timespec ts; clock_gettime(CLOCK_MONOTONIC_RAW, &ts); result = DQN_CAST(uint64_t)ts.tv_sec * 1'000'000'000 + DQN_CAST(uint64_t)ts.tv_nsec; -#endif + #endif return result; } @@ -2214,3 +2086,96 @@ DQN_API Dqn_f64 Dqn_OS_TimerNs(Dqn_OSTimer timer) return result; } +DQN_API uint64_t Dqn_OS_EstimateTSCPerSecond(uint64_t duration_ms_to_gauge_tsc_frequency) +{ + uint64_t os_frequency = Dqn_OS_PerfCounterFrequency(); + uint64_t os_target_elapsed = duration_ms_to_gauge_tsc_frequency * os_frequency / 1000ULL; + uint64_t tsc_begin = Dqn_CPU_TSC(); + uint64_t os_elapsed = 0; + for (uint64_t os_begin = Dqn_OS_PerfCounterNow(); os_elapsed < os_target_elapsed; ) + os_elapsed = Dqn_OS_PerfCounterNow() - os_begin; + uint64_t tsc_end = Dqn_CPU_TSC(); + uint64_t tsc_elapsed = tsc_end - tsc_begin; + uint64_t result = tsc_elapsed / os_elapsed * os_frequency; + return result; +} + +// NOTE: [$TCTX] Dqn_ThreadContext ================================================================= +Dqn_ThreadScratch::Dqn_ThreadScratch(Dqn_ThreadContext *context, uint8_t context_index) +{ + index = context_index; + allocator = context->temp_allocators[index]; + arena = context->temp_arenas[index]; + temp_memory = Dqn_Arena_BeginTempMemory(arena); +} + +Dqn_ThreadScratch::~Dqn_ThreadScratch() +{ + #if defined(DQN_DEBUG_THREAD_CONTEXT) + temp_arenas_stat[index] = arena->stats; + #endif + DQN_ASSERT(destructed == false); + Dqn_Arena_EndTempMemory(temp_memory, /*cancel*/ false); + destructed = true; +} + +DQN_API uint32_t Dqn_Thread_GetID() +{ + #if defined(DQN_OS_WIN32) + unsigned long result = GetCurrentThreadId(); + #else + pid_t result = gettid(); + assert(gettid() >= 0); + #endif + return (uint32_t)result; +} + +DQN_API Dqn_ThreadContext *Dqn_Thread_GetContext() +{ + DQN_THREAD_LOCAL Dqn_ThreadContext result = {}; + if (!result.init) { + result.init = true; + DQN_ASSERTF(g_dqn_library->lib_init, "Library must be initialised by calling Dqn_Library_Init(nullptr)"); + + // NOTE: Setup permanent arena + Dqn_ArenaCatalog *catalog = &g_dqn_library->arena_catalog; + result.allocator = Dqn_Arena_Allocator(result.arena); + result.arena = Dqn_ArenaCatalog_AllocF(catalog, + DQN_GIGABYTES(1) /*size*/, + DQN_KILOBYTES(64) /*commit*/, + "Thread %u Arena", + Dqn_Thread_GetID()); + + // NOTE: Setup temporary arenas + for (uint8_t index = 0; index < DQN_THREAD_CONTEXT_ARENAS; index++) { + result.temp_arenas[index] = Dqn_ArenaCatalog_AllocF(catalog, + DQN_GIGABYTES(1) /*size*/, + DQN_KILOBYTES(64) /*commit*/, + "Thread %u Temp Arena %u", + Dqn_Thread_GetID(), + index); + result.temp_allocators[index] = Dqn_Arena_Allocator(result.temp_arenas[index]); + } + } + return &result; +} + +// TODO: Is there a way to handle conflict arenas without the user needing to +// manually pass it in? +DQN_API Dqn_ThreadScratch Dqn_Thread_GetScratch(void const *conflict_arena) +{ + static_assert(DQN_THREAD_CONTEXT_ARENAS < (uint8_t)-1, "We use UINT8_MAX as a sentinel value"); + Dqn_ThreadContext *context = Dqn_Thread_GetContext(); + uint8_t context_index = (uint8_t)-1; + for (uint8_t index = 0; index < DQN_THREAD_CONTEXT_ARENAS; index++) { + Dqn_Arena *arena = context->temp_arenas[index]; + if (!conflict_arena || arena != conflict_arena) { + context_index = index; + break; + } + } + + DQN_ASSERT(context_index != (uint8_t)-1); + return Dqn_ThreadScratch(context, context_index); +} + diff --git a/dqn_platform.h b/dqn_platform.h index a74bbc0..ba94cd4 100644 --- a/dqn_platform.h +++ b/dqn_platform.h @@ -1,14 +1,91 @@ -// NOTE: Table Of Contents ========================================================================= -// Index | Disable #define | Description -// ================================================================================================= -// [$FSYS] Dqn_Fs | | Filesystem helpers -// [$DATE] Dqn_Date | | Date-time helpers -// [$W32H] Win32 Min Header | DQN_NO_WIN32_MIN_HEADER | Minimal windows.h subset -// [$WIND] Dqn_Win | | Windows OS helpers -// [$WINN] Dqn_WinNet | DQN_NO_WINNET | Windows internet download/query helpers -// [$OSYS] Dqn_OS | DQN_NO_WIN | Operating-system APIs -// ================================================================================================= +// NOTE: [$PRIN] Dqn_Print ========================================================================= +enum Dqn_PrintStd +{ + Dqn_PrintStd_Out, + Dqn_PrintStd_Err, +}; +enum Dqn_PrintBold +{ + Dqn_PrintBold_No, + Dqn_PrintBold_Yes, +}; + +struct Dqn_PrintStyle +{ + Dqn_PrintBold bold; + bool colour; + uint8_t r, g, b; +}; + +enum Dqn_PrintESCColour +{ + Dqn_PrintESCColour_Fg, + Dqn_PrintESCColour_Bg, +}; + +// NOTE: Print Style =============================================================================== +DQN_API Dqn_PrintStyle Dqn_Print_StyleColour (uint8_t r, uint8_t g, uint8_t b, Dqn_PrintBold bold); +DQN_API Dqn_PrintStyle Dqn_Print_StyleColourU32 (uint32_t rgb, Dqn_PrintBold bold); +DQN_API Dqn_PrintStyle Dqn_Print_StyleBold (); + +// NOTE: Print Standard Out ======================================================================== +#define Dqn_Print(string) Dqn_Print_Std(Dqn_PrintStd_Out, string) +#define Dqn_Print_F(fmt, ...) Dqn_Print_StdF(Dqn_PrintStd_Out, fmt, ## __VA_ARGS__) +#define Dqn_Print_FV(fmt, args) Dqn_Print_StdFV(Dqn_PrintStd_Out, fmt, args) + +#define Dqn_Print_Style(style, string) Dqn_Print_StdStyle(Dqn_PrintStd_Out, style, string) +#define Dqn_Print_FStyle(style, fmt, ...) Dqn_Print_StdFStyle(Dqn_PrintStd_Out, style, fmt, ## __VA_ARGS__) +#define Dqn_Print_FVStyle(style, fmt, args, ...) Dqn_Print_StdFVStyle(Dqn_PrintStd_Out, style, fmt, args) + +#define Dqn_Print_Ln(string) Dqn_Print_StdLn(Dqn_PrintStd_Out, string) +#define Dqn_Print_LnF(fmt, ...) Dqn_Print_StdLnF(Dqn_PrintStd_Out, fmt, ## __VA_ARGS__) +#define Dqn_Print_LnFV(fmt, args) Dqn_Print_StdLnFV(Dqn_PrintStd_Out, fmt, args) + +#define Dqn_Print_LnStyle(style, string) Dqn_Print_StdLnStyle(Dqn_PrintStd_Out, style, string); +#define Dqn_Print_LnFStyle(style, fmt, ...) Dqn_Print_StdLnFStyle(Dqn_PrintStd_Out, style, fmt, ## __VA_ARGS__); +#define Dqn_Print_LnFVStyle(style, fmt, args) Dqn_Print_StdLnFVStyle(Dqn_PrintStd_Out, style, fmt, args); + +// NOTE: Print ===================================================================================== +DQN_API void Dqn_Print_Std (Dqn_PrintStd std_handle, Dqn_String8 string); +DQN_API void Dqn_Print_StdF (Dqn_PrintStd std_handle, char const *fmt, ...); +DQN_API void Dqn_Print_StdFV (Dqn_PrintStd std_handle, char const *fmt, va_list args); + +DQN_API void Dqn_Print_StdStyle (Dqn_PrintStd std_handle, Dqn_PrintStyle style, Dqn_String8 string); +DQN_API void Dqn_Print_StdFStyle (Dqn_PrintStd std_handle, Dqn_PrintStyle style, char const *fmt, ...); +DQN_API void Dqn_Print_StdFVStyle (Dqn_PrintStd std_handle, Dqn_PrintStyle style, char const *fmt, va_list args); + +DQN_API void Dqn_Print_StdLn (Dqn_PrintStd std_handle, Dqn_String8 string); +DQN_API void Dqn_Print_StdLnF (Dqn_PrintStd std_handle, char const *fmt, ...); +DQN_API void Dqn_Print_StdLnFV (Dqn_PrintStd std_handle, char const *fmt, va_list args); + +DQN_API void Dqn_Print_StdLnStyle (Dqn_PrintStd std_handle, Dqn_PrintStyle style, Dqn_String8 string); +DQN_API void Dqn_Print_StdLnFStyle (Dqn_PrintStd std_handle, Dqn_PrintStyle style, char const *fmt, ...); +DQN_API void Dqn_Print_StdLnFVStyle (Dqn_PrintStd std_handle, Dqn_PrintStyle style, char const *fmt, va_list args); + +// NOTE: ANSI Formatting Codes ===================================================================== +Dqn_String8 Dqn_Print_ESCColourString (Dqn_PrintESCColour colour, uint8_t r, uint8_t g, uint8_t b); +Dqn_String8 Dqn_Print_ESCColourU32String(Dqn_PrintESCColour colour, uint32_t value); + +#define Dqn_Print_ESCColourFgString(r, g, b) Dqn_Print_ESCColourString(Dqn_PrintESCColour_Fg, r, g, b) +#define Dqn_Print_ESCColourBgString(r, g, b) Dqn_Print_ESCColourString(Dqn_PrintESCColour_Bg, r, g, b) +#define Dqn_Print_ESCColourFg(r, g, b) Dqn_Print_ESCColourString(Dqn_PrintESCColour_Fg, r, g, b).data +#define Dqn_Print_ESCColourBg(r, g, b) Dqn_Print_ESCColourString(Dqn_PrintESCColour_Bg, r, g, b).data + +#define Dqn_Print_ESCColourFgU32String(value) Dqn_Print_ESCColourU32String(Dqn_PrintESCColour_Fg, value) +#define Dqn_Print_ESCColourBgU32String(value) Dqn_Print_ESCColourU32String(Dqn_PrintESCColour_Bg, value) +#define Dqn_Print_ESCColourFgU32(value) Dqn_Print_ESCColourU32String(Dqn_PrintESCColour_Fg, value).data +#define Dqn_Print_ESCColourBgU32(value) Dqn_Print_ESCColourU32String(Dqn_PrintESCColour_Bg, value).data + +#define Dqn_Print_ESCReset "\x1b[0m" +#define Dqn_Print_ESCBold "\x1b[1m" +#define Dqn_Print_ESCResetString DQN_STRING8(Dqn_Print_ESCReset) +#define Dqn_Print_ESCBoldString DQN_STRING8(Dqn_Print_ESCBold) + +#if !defined(DQN_NO_FS) +#if defined(DQN_OS_WIN32) && defined(DQN_NO_WIN) + #error "Filesystem APIs requires Windows API, DQN_NO_WIN must not be defined" +#endif // NOTE: [$FSYS] Dqn_Fs ============================================================================ // NOTE: FS Manipulation ======================================================= // TODO(dqn): We should have a Dqn_String8 interface and a CString interface @@ -52,15 +129,10 @@ DQN_API bool Dqn_Fs_Delete (Dqn_String8 path); // @proc Dqn_Fs_ReadString8, Dqn_Fs_ReadCString8 // @desc Read the file at the path to a string. -#define Dqn_Fs_ReadCString8(path, path_size, file_size, allocator) Dqn_Fs_ReadCString8_(DQN_LEAK_TRACE path, path_size, file_size, allocator) -#define Dqn_Fs_ReadString8(path, allocator) Dqn_Fs_ReadString8_(DQN_LEAK_TRACE path, allocator) - DQN_API bool Dqn_Fs_WriteCString8(char const *file_path, Dqn_usize file_path_size, char const *buffer, Dqn_usize buffer_size); -DQN_API bool Dqn_Fs_WriteString8 (Dqn_String8 file_path, Dqn_String8 buffer); - -// NOTE: Internal ================================================================================== -DQN_API char *Dqn_Fs_ReadCString8_(DQN_LEAK_TRACE_FUNCTION char const *path, Dqn_usize path_size, Dqn_usize *file_size, Dqn_Allocator allocator); -DQN_API Dqn_String8 Dqn_Fs_ReadString8_ (DQN_LEAK_TRACE_FUNCTION Dqn_String8 path, Dqn_Allocator allocator); +DQN_API bool Dqn_Fs_Write (Dqn_String8 file_path, Dqn_String8 buffer); +DQN_API char *Dqn_Fs_ReadCString8 (char const *path, Dqn_usize path_size, Dqn_usize *file_size, Dqn_Allocator allocator); +DQN_API Dqn_String8 Dqn_Fs_Read (Dqn_String8 path, Dqn_Allocator allocator); // NOTE: R/W Stream API ============================================================================ // NOTE: API ======================================================================================= @@ -100,6 +172,7 @@ enum Dqn_FsFileAccess DQN_API Dqn_FsFile Dqn_Fs_OpenFile (Dqn_String8 path, Dqn_FsFileOpen open_mode, uint32_t access); DQN_API bool Dqn_Fs_WriteFile(Dqn_FsFile *file, char const *buffer, Dqn_usize size); DQN_API void Dqn_Fs_CloseFile(Dqn_FsFile *file); +#endif // !defined(DQN_NO_FS) // NOTE: File system paths ========================================================================= // Helper data structure for building paths suitable for OS consumption. @@ -122,8 +195,8 @@ DQN_API void Dqn_Fs_CloseFile(Dqn_FsFile *file); // For example "path/to/your/desired/folder" popped produces // "path/to/your/desired" -// @proc Dqn_FsPath_ConvertString8 -// @desc Convert the path specified in the string to the OS native separated +// @proc Dqn_FsPath_Convert +// @desc Convert the path specified in the string to the OS native separated // path. #if !defined(Dqn_FsPathOSSeperator) @@ -152,9 +225,11 @@ struct Dqn_FsPath DQN_API bool Dqn_FsPath_AddRef (Dqn_Arena *arena, Dqn_FsPath *fs_path, Dqn_String8 path); DQN_API bool Dqn_FsPath_Add (Dqn_Arena *arena, Dqn_FsPath *fs_path, Dqn_String8 path); +DQN_API bool Dqn_FsPath_AddF (Dqn_Arena *arena, Dqn_FsPath *fs_path, char const *fmt, ...); DQN_API bool Dqn_FsPath_Pop (Dqn_FsPath *fs_path); DQN_API Dqn_String8 Dqn_FsPath_BuildWithSeparator(Dqn_Arena *arena, Dqn_FsPath const *fs_path, Dqn_String8 path_separator); -DQN_API Dqn_String8 Dqn_FsPath_ConvertString8 (Dqn_Arena *arena, Dqn_String8 path); +DQN_API Dqn_String8 Dqn_FsPath_Convert (Dqn_Arena *arena, Dqn_String8 path); +DQN_API Dqn_String8 Dqn_FsPath_ConvertF (Dqn_Arena *arena, char const *fmt, ...); #define Dqn_FsPath_BuildFwdSlash(arena, fs_path) Dqn_FsPath_BuildWithSeparator(arena, fs_path, DQN_STRING8("/")) #define Dqn_FsPath_BuildBackSlash(arena, fs_path) Dqn_FsPath_BuildWithSeparator(arena, fs_path, DQN_STRING8("\\")) @@ -197,39 +272,8 @@ DQN_API Dqn_DateHMSTimeString Dqn_Date_HMSLocalTimeStringNow(char date_separator DQN_API Dqn_DateHMSTimeString Dqn_Date_HMSLocalTimeString (Dqn_DateHMSTime time, char date_separator = '-', char hms_separator = ':'); DQN_API uint64_t Dqn_Date_EpochTime (); -// NOTE: [$W32H] Win32 Min Header ================================================================== #if defined(DQN_OS_WIN32) - -#if !defined(DQN_NO_WIN32_MIN_HEADER) && !defined(_INC_WINDOWS) - #if defined(DQN_COMPILER_W32_MSVC) - #pragma warning(push) - #pragma warning(disable: 4201) // warning C4201: nonstandard extension used: nameless struct/union - #endif - // Taken from Windows.h - // typedef unsigned long DWORD; - // typedef unsigned short WORD; - // typedef int BOOL; - // typedef void * HWND; - // typedef void * HANDLE; - // typedef long NTSTATUS; - - typedef void * HMODULE; - typedef union { - struct { - unsigned long LowPart; - long HighPart; - }; - struct { - unsigned long LowPart; - long HighPart; - } u; - uint64_t QuadPart; - } LARGE_INTEGER; - #if defined(DQN_COMPILER_W32_MSVC) - #pragma warning(pop) - #endif -#endif // !defined(DQN_NO_WIN32_MIN_HEADER) && !defined(_INC_WINDOWS) - +#if !defined(DQN_NO_WIN) // NOTE: [$WIND] Dqn_Win =========================================================================== // NOTE: API ======================================================================================= // @proc Dqn_Win_LastErrorToBuffer, Dqn_Win_LastError @@ -316,6 +360,7 @@ DQN_API Dqn_String8 Dqn_Win_WorkingDir (Dqn_Allocator allocator, Dqn_Stri DQN_API Dqn_String16 Dqn_Win_WorkingDirW (Dqn_Allocator allocator, Dqn_String16 suffix); DQN_API bool Dqn_Win_FolderIterate (Dqn_String8 path, Dqn_Win_FolderIterator *it); DQN_API bool Dqn_Win_FolderWIterate(Dqn_String16 path, Dqn_Win_FolderIteratorW *it); +#endif // !defined(DQN_NO_WIN) #if !defined(DQN_NO_WINNET) // NOTE: [$WINN] Dqn_WinNet ======================================================================== @@ -435,6 +480,20 @@ DQN_API Dqn_String8 Dqn_Win_NetHandlePumpToAllocString (Dqn_W // @desc Retrieve the executable directory without the trailing '/' or // ('\' for windows). If this fails an empty string is returned. +// @proc Dqn_OS_PerfCounterFrequency +// @desc Get the number of ticks in the performance counter per second for the +// operating system you're running on. This value can be used to calculate +// duration from OS performance counter ticks. + +// @proc Dqn_OS_EstimateTSCPerSecond +// @desc Estimate how many timestamp count's (TSC) there are per second. TSC +// is evaluated by calling __rdtsc() or the equivalent on the platform. This +// value can be used to convert TSC durations into seconds. +// +// @param duration_ms_to_gauge_tsc_frequency How many milliseconds to spend +// measuring the TSC rate of the current machine. 100ms is sufficient to +// produce a fairly accurate result with minimal blocking in applications. + /// Record time between two time-points using the OS's performance counter. struct Dqn_OSTimer { @@ -442,74 +501,96 @@ struct Dqn_OSTimer uint64_t end; }; -DQN_API bool Dqn_OS_SecureRNGBytes (void *buffer, uint32_t size); -DQN_API Dqn_String8 Dqn_OS_EXEDir (Dqn_Allocator allocator); -DQN_API void Dqn_OS_SleepMs (Dqn_uint milliseconds); -DQN_API uint64_t Dqn_OS_PerfCounterNow (); -DQN_API Dqn_f64 Dqn_OS_PerfCounterS (uint64_t begin, uint64_t end); -DQN_API Dqn_f64 Dqn_OS_PerfCounterMs (uint64_t begin, uint64_t end); -DQN_API Dqn_f64 Dqn_OS_PerfCounterMicroS(uint64_t begin, uint64_t end); -DQN_API Dqn_f64 Dqn_OS_PerfCounterNs (uint64_t begin, uint64_t end); -DQN_API Dqn_OSTimer Dqn_OS_TimerBegin (); -DQN_API void Dqn_OS_TimerEnd (Dqn_OSTimer *timer); -DQN_API Dqn_f64 Dqn_OS_TimerS (Dqn_OSTimer timer); -DQN_API Dqn_f64 Dqn_OS_TimerMs (Dqn_OSTimer timer); -DQN_API Dqn_f64 Dqn_OS_TimerMicroS (Dqn_OSTimer timer); -DQN_API Dqn_f64 Dqn_OS_TimerNs (Dqn_OSTimer timer); +DQN_API bool Dqn_OS_SecureRNGBytes (void *buffer, uint32_t size); +#if (defined(DQN_OS_WIN32) && !defined(DQN_NO_WIN)) || !defined(DQN_OS_WIN32) +DQN_API Dqn_String8 Dqn_OS_EXEDir (Dqn_Allocator allocator); +#endif +DQN_API void Dqn_OS_SleepMs (Dqn_uint milliseconds); +DQN_API uint64_t Dqn_OS_PerfCounterNow (); +DQN_API uint64_t Dqn_OS_PerfCounterFrequency(); +DQN_API Dqn_f64 Dqn_OS_PerfCounterS (uint64_t begin, uint64_t end); +DQN_API Dqn_f64 Dqn_OS_PerfCounterMs (uint64_t begin, uint64_t end); +DQN_API Dqn_f64 Dqn_OS_PerfCounterMicroS (uint64_t begin, uint64_t end); +DQN_API Dqn_f64 Dqn_OS_PerfCounterNs (uint64_t begin, uint64_t end); +DQN_API Dqn_OSTimer Dqn_OS_TimerBegin (); +DQN_API void Dqn_OS_TimerEnd (Dqn_OSTimer *timer); +DQN_API Dqn_f64 Dqn_OS_TimerS (Dqn_OSTimer timer); +DQN_API Dqn_f64 Dqn_OS_TimerMs (Dqn_OSTimer timer); +DQN_API Dqn_f64 Dqn_OS_TimerMicroS (Dqn_OSTimer timer); +DQN_API Dqn_f64 Dqn_OS_TimerNs (Dqn_OSTimer timer); +DQN_API uint64_t Dqn_OS_EstimateTSCPerSecond(uint64_t duration_ms_to_gauge_tsc_frequency); -// OS_TimedBlock provides a extremely primitive way of measuring the duration of -// code blocks, by sprinkling DQN_OS_TIMED_BLOCK_RECORD("record label"), you can -// measure the time between the macro and the next record call. +// NOTE: [$TCTX] Dqn_ThreadContext ================================================================= +// Each thread is assigned in their thread-local storage (TLS) scratch and +// permanent arena allocators. These can be used for allocations with a lifetime +// scoped to the lexical scope or for storing data permanently using the arena +// paradigm. // -// Example: Record the duration of the for-loop below and print it at the end. -/* - int main() - { - DQN_OS_TIMED_BLOCK_INIT("Profiling Region", 32); // name, records to allocate - DQN_OS_TIMED_BLOCK_RECORD("a"); - for (int unused1_ = 0; unused1_ < 1000000; unused1_++) - { - for (int unused2_ = 0; unused2_ < 1000000; unused2_++) - { - (void)unused1_; - (void)unused2_; - } - } - DQN_OS_TIMED_BLOCK_RECORD("b"); - DQN_OS_TIMED_BLOCK_DUMP; - return 0; - } -*/ -struct Dqn_OSTimedBlock +// TLS in this implementation is implemented using the `thread_local` C/C++ +// keyword. +// +// NOTE: API +// +// @proc Dqn_Thread_GetContext +// @desc Get the current thread's context- this contains all the metadata for managing +// the thread scratch data. In general you probably want Dqn_Thread_GetScratch() +// which ensures you get a usable scratch arena for temporary allocations +// without having to worry about selecting the right arena from the state. +// +// @proc Dqn_Thread_GetScratch +// @desc Retrieve the per-thread temporary arena allocator that is reset on scope +// exit. +// +// The scratch arena must be deconflicted with any existing arenas in the +// function to avoid trampling over each other's memory. Consider the situation +// where the scratch arena is passed into the function. Inside the function, if +// the same arena is reused then, if both arenas allocate, when the inner arena +// is reset, this will undo the passed in arena's allocations in the function. +// +// @param[in] conflict_arena A pointer to the arena currently being used in the +// function + +#if !defined(DQN_THREAD_CONTEXT_ARENAS) + #define DQN_THREAD_CONTEXT_ARENAS 2 +#endif + +struct Dqn_ThreadContext { - char const *label; - uint64_t tick; + Dqn_b32 init; + + Dqn_Arena *arena; ///< Per thread arena + Dqn_Allocator allocator; ///< Allocator that uses the arena + + /// Temp memory arena's for the calling thread + Dqn_Arena *temp_arenas[DQN_THREAD_CONTEXT_ARENAS]; + + /// Allocators that use the corresponding arena from the thread context. + /// Provided for convenience when interfacing with allocator interfaces. + Dqn_Allocator temp_allocators[DQN_THREAD_CONTEXT_ARENAS]; + + #if defined(DQN_DEBUG_THREAD_CONTEXT) + Dqn_ArenaStat temp_arenas_stat[DQN_THREAD_CONTEXT_ARENAS]; + #endif }; -// Initialise a timing block region, -#define DQN_OS_TIMED_BLOCK_INIT(label, size) \ - Dqn_OSTimedBlock timings_[size]; \ - Dqn_usize timings_size_ = 0; \ - DQN_OS_TIMED_BLOCK_RECORD(label) +struct Dqn_ThreadScratch +{ + Dqn_ThreadScratch(Dqn_ThreadContext *context, uint8_t context_index); + ~Dqn_ThreadScratch(); -// Add a timing record at the current location this macro is invoked. -// DQN_OS_TIMED_BLOCK_INIT must have been called in a scope visible to the macro -// prior. -// label: The label to give to the timing record -#define DQN_OS_TIMED_BLOCK_RECORD(label) timings_[timings_size_++] = {label, Dqn_OS_PerfCounterNow()} + /// Index into the arena/allocator/stat array in the thread context + /// specifying what arena was assigned. + uint8_t index; + Dqn_Allocator allocator; + Dqn_Arena *arena; + Dqn_b32 destructed = false; /// Detect copies of the scratch + Dqn_ArenaTempMemory temp_memory; + #if defined(DQN_LEAK_TRACING) + Dqn_CallSite leak_site__; + #endif +}; -// Dump the timing block via Dqn_Log -#define DQN_OS_TIMED_BLOCK_DUMP \ - DQN_ASSERTF(timings_size_ < sizeof(timings_) / sizeof(timings_[0]), \ - "Timings array indexed out-of-bounds, use a bigger size"); \ - for (int timings_index_ = 0; timings_index_ < (timings_size_ - 1); timings_index_++) { \ - Dqn_OSTimedBlock t1 = timings_[timings_index_ + 0]; \ - Dqn_OSTimedBlock t2 = timings_[timings_index_ + 1]; \ - DQN_LOG_D("%s -> %s: %fms", t1.label, t2.label, Dqn_OS_PerfCounterMs(t1.tick, t2.tick)); \ - } \ - \ - if (timings_size_ >= 1) { \ - Dqn_OSTimedBlock t1 = timings_[0]; \ - Dqn_OSTimedBlock t2 = timings_[timings_size_ - 1]; \ - DQN_LOG_D("%s -> %s (total): %fms", t1.label, t2.label, Dqn_OS_PerfCounterMs(t1.tick, t2.tick));\ - } +// NOTE: Context =================================================================================== +DQN_API uint32_t Dqn_Thread_GetID(); +DQN_API Dqn_ThreadContext *Dqn_Thread_GetContext(); +DQN_API Dqn_ThreadScratch Dqn_Thread_GetScratch(void const *conflict_arena); diff --git a/dqn_print.cpp b/dqn_print.cpp deleted file mode 100644 index 997a936..0000000 --- a/dqn_print.cpp +++ /dev/null @@ -1,341 +0,0 @@ -// NOTE: [$PRIN] Dqn_Print ========================================================================= -DQN_API Dqn_PrintStyle Dqn_Print_StyleColour(uint8_t r, uint8_t g, uint8_t b, Dqn_PrintBold bold) -{ - Dqn_PrintStyle result = {}; - result.bold = bold; - result.colour = true; - result.r = r; - result.g = g; - result.b = b; - return result; -} - -DQN_API Dqn_PrintStyle Dqn_Print_StyleColourU32(uint32_t rgb, Dqn_PrintBold bold) -{ - uint8_t r = (rgb >> 24) & 0xFF; - uint8_t g = (rgb >> 16) & 0xFF; - uint8_t b = (rgb >> 8) & 0xFF; - Dqn_PrintStyle result = Dqn_Print_StyleColour(r, g, b, bold); - return result; -} - -DQN_API Dqn_PrintStyle Dqn_Print_StyleBold() -{ - Dqn_PrintStyle result = {}; - result.bold = Dqn_PrintBold_Yes; - return result; -} - -DQN_API void Dqn_Print_Std(Dqn_PrintStd std_handle, Dqn_String8 string) -{ - DQN_ASSERT(std_handle == Dqn_PrintStd_Out || std_handle == Dqn_PrintStd_Err); - - #if defined(DQN_OS_WIN32) - // NOTE: Get the output handles from kernel - // ========================================================================= - DQN_THREAD_LOCAL void *std_out_print_handle = nullptr; - DQN_THREAD_LOCAL void *std_err_print_handle = nullptr; - DQN_THREAD_LOCAL bool std_out_print_to_console = false; - DQN_THREAD_LOCAL bool std_err_print_to_console = false; - - if (!std_out_print_handle) { - unsigned long mode = 0; (void)mode; - std_out_print_handle = GetStdHandle(STD_OUTPUT_HANDLE); - std_out_print_to_console = GetConsoleMode(std_out_print_handle, &mode) != 0; - - std_err_print_handle = GetStdHandle(STD_ERROR_HANDLE); - std_err_print_to_console = GetConsoleMode(std_err_print_handle, &mode) != 0; - } - - // NOTE: Select the output handle - // ========================================================================= - void *print_handle = std_out_print_handle; - bool print_to_console = std_out_print_to_console; - if (std_handle == Dqn_PrintStd_Err) { - print_handle = std_err_print_handle; - print_to_console = std_err_print_to_console; - } - - // NOTE: Write the string - // ========================================================================= - DQN_ASSERT(string.size < DQN_CAST(unsigned long)-1); - unsigned long bytes_written = 0; (void)bytes_written; - if (print_to_console) { - WriteConsoleA(print_handle, string.data, DQN_CAST(unsigned long)string.size, &bytes_written, nullptr); - } else { - WriteFile(print_handle, string.data, DQN_CAST(unsigned long)string.size, &bytes_written, nullptr); - } - #else - fprintf(std_handle == Dqn_PrintStd_Out ? stdout : stderr, "%.*s", DQN_STRING_FMT(string)); - #endif -} - -DQN_API void Dqn_Print_StdStyle(Dqn_PrintStd std_handle, Dqn_PrintStyle style, Dqn_String8 string) -{ - if (string.data && string.size) { - if (style.colour) - Dqn_Print_Std(std_handle, Dqn_Print_ESCColourFgString(style.r, style.g, style.b)); - if (style.bold == Dqn_PrintBold_Yes) - Dqn_Print_Std(std_handle, Dqn_Print_ESCBoldString); - Dqn_Print_Std(std_handle, string); - if (style.colour || style.bold == Dqn_PrintBold_Yes) - Dqn_Print_Std(std_handle, Dqn_Print_ESCResetString); - } -} - -DQN_FILE_SCOPE char *Dqn_Print_VSPrintfChunker_(const char *buf, void *user, int len) -{ - Dqn_String8 string = {}; - string.data = DQN_CAST(char *)buf; - string.size = len; - - Dqn_PrintStd std_handle = DQN_CAST(Dqn_PrintStd)DQN_CAST(uintptr_t)user; - Dqn_Print_Std(std_handle, string); - return (char *)buf; -} - -DQN_API void Dqn_Print_StdF(Dqn_PrintStd std_handle, char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - Dqn_Print_StdFV(std_handle, fmt, args); - va_end(args); -} - -DQN_API void Dqn_Print_StdFStyle(Dqn_PrintStd std_handle, Dqn_PrintStyle style, char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - Dqn_Print_StdFVStyle(std_handle, style, fmt, args); - va_end(args); -} - -DQN_API void Dqn_Print_StdFV(Dqn_PrintStd std_handle, char const *fmt, va_list args) -{ - char buffer[STB_SPRINTF_MIN]; - STB_SPRINTF_DECORATE(vsprintfcb)(Dqn_Print_VSPrintfChunker_, DQN_CAST(void *)DQN_CAST(uintptr_t)std_handle, buffer, fmt, args); -} - -DQN_API void Dqn_Print_StdFVStyle(Dqn_PrintStd std_handle, Dqn_PrintStyle style, char const *fmt, va_list args) -{ - if (fmt) { - if (style.colour) - Dqn_Print_Std(std_handle, Dqn_Print_ESCColourFgString(style.r, style.g, style.b)); - if (style.bold == Dqn_PrintBold_Yes) - Dqn_Print_Std(std_handle, Dqn_Print_ESCBoldString); - Dqn_Print_StdFV(std_handle, fmt, args); - if (style.colour || style.bold == Dqn_PrintBold_Yes) - Dqn_Print_Std(std_handle, Dqn_Print_ESCResetString); - } -} - -DQN_API void Dqn_Print_StdLn(Dqn_PrintStd std_handle, Dqn_String8 string) -{ - Dqn_Print_Std(std_handle, string); - Dqn_Print_Std(std_handle, DQN_STRING8("\n")); -} - -DQN_API void Dqn_Print_StdLnF(Dqn_PrintStd std_handle, char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - Dqn_Print_StdLnFV(std_handle, fmt, args); - va_end(args); -} - -DQN_API void Dqn_Print_StdLnFV(Dqn_PrintStd std_handle, char const *fmt, va_list args) -{ - Dqn_Print_StdFV(std_handle, fmt, args); - Dqn_Print_Std(std_handle, DQN_STRING8("\n")); -} - -DQN_API void Dqn_Print_StdLnStyle(Dqn_PrintStd std_handle, Dqn_PrintStyle style, Dqn_String8 string) -{ - Dqn_Print_StdStyle(std_handle, style, string); - Dqn_Print_Std(std_handle, DQN_STRING8("\n")); -} - -DQN_API void Dqn_Print_StdLnFStyle(Dqn_PrintStd std_handle, Dqn_PrintStyle style, char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - Dqn_Print_StdLnFVStyle(std_handle, style, fmt, args); - va_end(args); -} - -DQN_API void Dqn_Print_StdLnFVStyle(Dqn_PrintStd std_handle, Dqn_PrintStyle style, char const *fmt, va_list args) -{ - Dqn_Print_StdFVStyle(std_handle, style, fmt, args); - Dqn_Print_Std(std_handle, DQN_STRING8("\n")); -} - -DQN_API Dqn_String8 Dqn_Print_ESCColourString(Dqn_PrintESCColour colour, uint8_t r, uint8_t g, uint8_t b) -{ - DQN_THREAD_LOCAL char buffer[32]; - buffer[0] = 0; - Dqn_String8 result = {}; - result.size = STB_SPRINTF_DECORATE(snprintf)(buffer, - DQN_ARRAY_UCOUNT(buffer), - "\x1b[%d;2;%u;%u;%um", - colour == Dqn_PrintESCColour_Fg ? 38 : 48, - r, g, b); - result.data = buffer; - return result; -} - -DQN_API Dqn_String8 Dqn_Print_ESCColourU32String(Dqn_PrintESCColour colour, uint32_t value) -{ - uint8_t r = DQN_CAST(uint8_t)(value >> 24); - uint8_t g = DQN_CAST(uint8_t)(value >> 16); - uint8_t b = DQN_CAST(uint8_t)(value >> 8); - Dqn_String8 result = Dqn_Print_ESCColourString(colour, r, g, b); - return result; -} - -// NOTE: [$LLOG] Dqn_Log ========================================================================== -DQN_API Dqn_String8 Dqn_Log_MakeString(Dqn_Allocator allocator, - bool colour, - Dqn_String8 type, - int log_type, - Dqn_CallSite call_site, - char const *fmt, - va_list args) -{ - Dqn_usize header_size_no_ansi_codes = 0; - Dqn_String8 header = {}; - { - DQN_LOCAL_PERSIST Dqn_usize max_type_length = 0; - max_type_length = DQN_MAX(max_type_length, type.size); - int type_padding = DQN_CAST(int)(max_type_length - type.size); - - Dqn_String8 colour_esc = {}; - Dqn_String8 bold_esc = {}; - Dqn_String8 reset_esc = {}; - if (colour) { - bold_esc = Dqn_Print_ESCBoldString; - reset_esc = Dqn_Print_ESCResetString; - switch (log_type) { - case Dqn_LogType_Debug: break; - case Dqn_LogType_Info: colour_esc = Dqn_Print_ESCColourFgU32String(Dqn_LogTypeColourU32_Info); break; - case Dqn_LogType_Warning: colour_esc = Dqn_Print_ESCColourFgU32String(Dqn_LogTypeColourU32_Warning); break; - case Dqn_LogType_Error: colour_esc = Dqn_Print_ESCColourFgU32String(Dqn_LogTypeColourU32_Error); break; - } - } - - Dqn_String8 file_name = Dqn_String8_FileNameFromPath(call_site.file); - Dqn_DateHMSTimeString const time = Dqn_Date_HMSLocalTimeStringNow(); - header = Dqn_String8_InitF(allocator, - "%.*s " // date - "%.*s " // hms - "%.*s" // colour - "%.*s" // bold - "%.*s" // type - "%*s" // type padding - "%.*s" // reset - " %.*s" // file name - ":%05u ", // line number - time.date_size - 2, time.date + 2, - time.hms_size, time.hms, - colour_esc.size, colour_esc.data, - bold_esc.size, bold_esc.data, - type.size, type.data, - type_padding, "", - reset_esc.size, reset_esc.data, - file_name.size, file_name.data, - call_site.line); - header_size_no_ansi_codes = header.size - colour_esc.size - Dqn_Print_ESCResetString.size; - } - - // NOTE: Header padding - // ========================================================================= - Dqn_usize header_padding = 0; - { - DQN_LOCAL_PERSIST Dqn_usize max_header_length = 0; - max_header_length = DQN_MAX(max_header_length, header_size_no_ansi_codes); - header_padding = max_header_length - header_size_no_ansi_codes; - } - - // NOTE: Construct final log - // ========================================================================= - Dqn_String8 user_msg = Dqn_String8_InitFV(allocator, fmt, args); - Dqn_String8 result = Dqn_String8_Allocate(allocator, header.size + header_padding + user_msg.size, Dqn_ZeroMem_No); - DQN_MEMCPY(result.data, header.data, header.size); - DQN_MEMSET(result.data + header.size, ' ', header_padding); - DQN_MEMCPY(result.data + header.size + header_padding, user_msg.data, user_msg.size); - return result; -} - -DQN_FILE_SCOPE void Dqn_Log_FVDefault_(Dqn_String8 type, int log_type, void *user_data, Dqn_CallSite call_site, char const *fmt, va_list args) -{ - (void)log_type; - (void)user_data; - - // NOTE: Open log file for appending if requested - // ========================================================================= - Dqn_TicketMutex_Begin(&dqn_library.log_file_mutex); - if (dqn_library.log_to_file && !dqn_library.log_file) { - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - Dqn_String8 exe_dir = Dqn_OS_EXEDir(scratch.allocator); - Dqn_String8 log_file = Dqn_String8_InitF(scratch.allocator, "%.*s/dqn.log", DQN_STRING_FMT(exe_dir)); - fopen_s(DQN_CAST(FILE **)&dqn_library.log_file, log_file.data, "a"); - } - Dqn_TicketMutex_End(&dqn_library.log_file_mutex); - - // NOTE: Generate the log header - // ========================================================================= - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - Dqn_String8 log_line = Dqn_Log_MakeString(scratch.allocator, - !dqn_library.log_no_colour, - type, - log_type, - call_site, - fmt, - args); - - // NOTE: Print log - // ========================================================================= - Dqn_Print_StdLn(Dqn_PrintStd_Out, log_line); - - Dqn_TicketMutex_Begin(&dqn_library.log_file_mutex); - if (dqn_library.log_to_file) { - fprintf(DQN_CAST(FILE *)dqn_library.log_file, "%.*s\n", DQN_STRING_FMT(log_line)); - } - Dqn_TicketMutex_End(&dqn_library.log_file_mutex); -} - -DQN_API void Dqn_Log_FVCallSite(Dqn_String8 type, Dqn_CallSite call_site, char const *fmt, va_list args) -{ - Dqn_LogProc *logging_function = dqn_library.log_callback ? dqn_library.log_callback : Dqn_Log_FVDefault_; - logging_function(type, -1 /*log_type*/, dqn_library.log_user_data, call_site, fmt, args); -} - -DQN_API void Dqn_Log_FCallSite(Dqn_String8 type, Dqn_CallSite call_site, char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - Dqn_Log_FVCallSite(type, call_site, fmt, args); - va_end(args); -} - -DQN_API void Dqn_Log_TypeFVCallSite(Dqn_LogType type, Dqn_CallSite call_site, char const *fmt, va_list args) -{ - Dqn_String8 type_string = DQN_STRING8("DQN-BAD-LOG-TYPE"); - switch (type) { - case Dqn_LogType_Error: type_string = DQN_STRING8("ERROR"); break; - case Dqn_LogType_Info: type_string = DQN_STRING8("INFO"); break; - case Dqn_LogType_Warning: type_string = DQN_STRING8("WARN"); break; - case Dqn_LogType_Debug: type_string = DQN_STRING8("DEBUG"); break; - case Dqn_LogType_Count: type_string = DQN_STRING8("BADXX"); break; - } - - Dqn_LogProc *logging_function = dqn_library.log_callback ? dqn_library.log_callback : Dqn_Log_FVDefault_; - logging_function(type_string, type /*log_type*/, dqn_library.log_user_data, call_site, fmt, args); -} - -DQN_API void Dqn_Log_TypeFCallSite(Dqn_LogType type, Dqn_CallSite call_site, char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - Dqn_Log_TypeFVCallSite(type, call_site, fmt, args); - va_end(args); -} diff --git a/dqn_print.h b/dqn_print.h deleted file mode 100644 index aa75584..0000000 --- a/dqn_print.h +++ /dev/null @@ -1,138 +0,0 @@ -// NOTE: Table Of Contents ========================================================================= -// Index | Disable #define | Description -// ================================================================================================= -// [$PRIN] Dqn_Print | | Console printing -// [$LLOG] Dqn_Log | | Library logging -// ================================================================================================= - -// NOTE: [$PRIN] Dqn_Print ========================================================================= -enum Dqn_PrintStd -{ - Dqn_PrintStd_Out, - Dqn_PrintStd_Err, -}; - -enum Dqn_PrintBold -{ - Dqn_PrintBold_No, - Dqn_PrintBold_Yes, -}; - -struct Dqn_PrintStyle -{ - Dqn_PrintBold bold; - bool colour; - uint8_t r, g, b; -}; - -enum Dqn_PrintESCColour -{ - Dqn_PrintESCColour_Fg, - Dqn_PrintESCColour_Bg, -}; - -// NOTE: Print Style =============================================================================== -DQN_API Dqn_PrintStyle Dqn_Print_StyleColour (uint8_t r, uint8_t g, uint8_t b, Dqn_PrintBold bold); -DQN_API Dqn_PrintStyle Dqn_Print_StyleColourU32 (uint32_t rgb, Dqn_PrintBold bold); -DQN_API Dqn_PrintStyle Dqn_Print_StyleBold (); - -// NOTE: Print Standard Out ======================================================================== -#define Dqn_Print(string) Dqn_Print_Std(Dqn_PrintStd_Out, string) -#define Dqn_Print_F(fmt, ...) Dqn_Print_StdF(Dqn_PrintStd_Out, fmt, ## __VA_ARGS__) -#define Dqn_Print_FV(fmt, args) Dqn_Print_StdFV(Dqn_PrintStd_Out, fmt, args) - -#define Dqn_Print_Style(style, string) Dqn_Print_StdStyle(Dqn_PrintStd_Out, style, string) -#define Dqn_Print_FStyle(style, fmt, ...) Dqn_Print_StdFStyle(Dqn_PrintStd_Out, style, fmt, ## __VA_ARGS__) -#define Dqn_Print_FVStyle(style, fmt, args, ...) Dqn_Print_StdFVStyle(Dqn_PrintStd_Out, style, fmt, args) - -#define Dqn_Print_Ln(string) Dqn_Print_StdLn(Dqn_PrintStd_Out, string) -#define Dqn_Print_LnF(fmt, ...) Dqn_Print_StdLnF(Dqn_PrintStd_Out, fmt, ## __VA_ARGS__) -#define Dqn_Print_LnFV(fmt, args) Dqn_Print_StdLnFV(Dqn_PrintStd_Out, fmt, args) - -#define Dqn_Print_LnStyle(style, string) Dqn_Print_StdLnStyle(Dqn_PrintStd_Out, style, string); -#define Dqn_Print_LnFStyle(style, fmt, ...) Dqn_Print_StdLnFStyle(Dqn_PrintStd_Out, style, fmt, ## __VA_ARGS__); -#define Dqn_Print_LnFVStyle(style, fmt, args) Dqn_Print_StdLnFVStyle(Dqn_PrintStd_Out, style, fmt, args); - -// NOTE: Print ===================================================================================== -DQN_API void Dqn_Print_Std (Dqn_PrintStd std_handle, Dqn_String8 string); -DQN_API void Dqn_Print_StdF (Dqn_PrintStd std_handle, char const *fmt, ...); -DQN_API void Dqn_Print_StdFV (Dqn_PrintStd std_handle, char const *fmt, va_list args); - -DQN_API void Dqn_Print_StdStyle (Dqn_PrintStd std_handle, Dqn_PrintStyle style, Dqn_String8 string); -DQN_API void Dqn_Print_StdFStyle (Dqn_PrintStd std_handle, Dqn_PrintStyle style, char const *fmt, ...); -DQN_API void Dqn_Print_StdFVStyle (Dqn_PrintStd std_handle, Dqn_PrintStyle style, char const *fmt, va_list args); - -DQN_API void Dqn_Print_StdLn (Dqn_PrintStd std_handle, Dqn_String8 string); -DQN_API void Dqn_Print_StdLnF (Dqn_PrintStd std_handle, char const *fmt, ...); -DQN_API void Dqn_Print_StdLnFV (Dqn_PrintStd std_handle, char const *fmt, va_list args); - -DQN_API void Dqn_Print_StdLnStyle (Dqn_PrintStd std_handle, Dqn_PrintStyle style, Dqn_String8 string); -DQN_API void Dqn_Print_StdLnFStyle (Dqn_PrintStd std_handle, Dqn_PrintStyle style, char const *fmt, ...); -DQN_API void Dqn_Print_StdLnFVStyle (Dqn_PrintStd std_handle, Dqn_PrintStyle style, char const *fmt, va_list args); - -// NOTE: ANSI Formatting Codes ===================================================================== -Dqn_String8 Dqn_Print_ESCColourString (Dqn_PrintESCColour colour, uint8_t r, uint8_t g, uint8_t b); -Dqn_String8 Dqn_Print_ESCColourU32String(Dqn_PrintESCColour colour, uint32_t value); - -#define Dqn_Print_ESCColourFgString(r, g, b) Dqn_Print_ESCColourString(Dqn_PrintESCColour_Fg, r, g, b) -#define Dqn_Print_ESCColourBgString(r, g, b) Dqn_Print_ESCColourString(Dqn_PrintESCColour_Bg, r, g, b) -#define Dqn_Print_ESCColourFg(r, g, b) Dqn_Print_ESCColourString(Dqn_PrintESCColour_Fg, r, g, b).data -#define Dqn_Print_ESCColourBg(r, g, b) Dqn_Print_ESCColourString(Dqn_PrintESCColour_Bg, r, g, b).data - -#define Dqn_Print_ESCColourFgU32String(value) Dqn_Print_ESCColourU32String(Dqn_PrintESCColour_Fg, value) -#define Dqn_Print_ESCColourBgU32String(value) Dqn_Print_ESCColourU32String(Dqn_PrintESCColour_Bg, value) -#define Dqn_Print_ESCColourFgU32(value) Dqn_Print_ESCColourU32String(Dqn_PrintESCColour_Fg, value).data -#define Dqn_Print_ESCColourBgU32(value) Dqn_Print_ESCColourU32String(Dqn_PrintESCColour_Bg, value).data - -#define Dqn_Print_ESCReset "\x1b[0m" -#define Dqn_Print_ESCBold "\x1b[1m" -#define Dqn_Print_ESCResetString DQN_STRING8(Dqn_Print_ESCReset) -#define Dqn_Print_ESCBoldString DQN_STRING8(Dqn_Print_ESCBold) - -// NOTE: [$LLOG] Dqn_Log ========================================================================== -enum Dqn_LogType -{ - Dqn_LogType_Debug, - Dqn_LogType_Info, - Dqn_LogType_Warning, - Dqn_LogType_Error, - Dqn_LogType_Count, -}; - -/// RGBA -#define Dqn_LogTypeColourU32_Info 0x00'87'ff'ff // Blue -#define Dqn_LogTypeColourU32_Warning 0xff'ff'00'ff // Yellow -#define Dqn_LogTypeColourU32_Error 0xff'00'00'ff // Red - -/// The logging procedure of the library. Users can override the default logging -/// function by setting the logging function pointer in Dqn_Library. This -/// function will be invoked every time a log is recorded using the following -/// functions. -/// -/// @param[in] log_type This value is one of the Dqn_LogType values if the log -/// was generated from one of the default categories. -1 if the log is not from -/// one of the default categories. -typedef void Dqn_LogProc(Dqn_String8 type, int log_type, void *user_data, Dqn_CallSite call_site, char const *fmt, va_list va); - -#define Dqn_Log_DebugF(fmt, ...) Dqn_Log_TypeFCallSite(Dqn_LogType_Debug, DQN_CALL_SITE, fmt, ## __VA_ARGS__) -#define Dqn_Log_InfoF(fmt, ...) Dqn_Log_TypeFCallSite(Dqn_LogType_Info, DQN_CALL_SITE, fmt, ## __VA_ARGS__) -#define Dqn_Log_WarningF(fmt, ...) Dqn_Log_TypeFCallSite(Dqn_LogType_Warning, DQN_CALL_SITE, fmt, ## __VA_ARGS__) -#define Dqn_Log_ErrorF(fmt, ...) Dqn_Log_TypeFCallSite(Dqn_LogType_Error, DQN_CALL_SITE, fmt, ## __VA_ARGS__) - -#define Dqn_Log_DebugFV(fmt, args) Dqn_Log_TypeFVCallSite(Dqn_LogType_Debug, DQN_CALL_SITE, fmt, args) -#define Dqn_Log_InfoFV(fmt, args) Dqn_Log_TypeFVCallSite(Dqn_LogType_Info, DQN_CALL_SITE, fmt, args) -#define Dqn_Log_WarningFV(fmt, args) Dqn_Log_TypeFVCallSite(Dqn_LogType_Warning, DQN_CALL_SITE, fmt, args) -#define Dqn_Log_ErrorFV(fmt, args) Dqn_Log_TypeFVCallSite(Dqn_LogType_Error, DQN_CALL_SITE, fmt, args) - -#define Dqn_Log_TypeFV(type, fmt, args) Dqn_Log_TypeFVCallSite(type, DQN_CALL_SITE, fmt, args) -#define Dqn_Log_TypeF(type, fmt, ...) Dqn_Log_TypeFCallSite(type, DQN_CALL_SITE, fmt, ## __VA_ARGS__) - -#define Dqn_Log_FV(type, fmt, args) Dqn_Log_FVCallSite(type, DQN_CALL_SITE, fmt, args) -#define Dqn_Log_F(type, fmt, ...) Dqn_Log_FCallSite(type, DQN_CALL_SITE, fmt, ## __VA_ARGS__) - -DQN_API Dqn_String8 Dqn_Log_MakeString (Dqn_Allocator allocator, bool colour, Dqn_String8 type, int log_type, Dqn_CallSite call_site, char const *fmt, va_list args); -DQN_API void Dqn_Log_TypeFVCallSite(Dqn_LogType type, Dqn_CallSite call_site, char const *fmt, va_list va); -DQN_API void Dqn_Log_TypeFCallSite (Dqn_LogType type, Dqn_CallSite call_site, char const *fmt, ...); -DQN_API void Dqn_Log_FVCallSite (Dqn_String8 type, Dqn_CallSite call_site, char const *fmt, va_list va); -DQN_API void Dqn_Log_FCallSite (Dqn_String8 type, Dqn_CallSite call_site, char const *fmt, ...); - diff --git a/dqn_strings.cpp b/dqn_strings.cpp index a0c7d9c..8c54bc0 100644 --- a/dqn_strings.cpp +++ b/dqn_strings.cpp @@ -97,7 +97,7 @@ DQN_API Dqn_String8BinarySplitResult Dqn_String8_BinarySplitArray(Dqn_String8 st return result; result.lhs = string; - for (size_t index = 0; index <= string.size; index++) { + for (size_t index = 0; !result.rhs.data && index <= string.size; index++) { for (Dqn_usize find_index = 0; find_index < find_size; find_index++) { Dqn_String8 find_item = find[find_index]; Dqn_String8 string_slice = Dqn_String8_Slice(string, index, find_item.size); @@ -354,23 +354,25 @@ DQN_API Dqn_String8 Dqn_String8_FileNameFromPath(Dqn_String8 path) return result; } -DQN_API bool Dqn_String8_ToU64Checked(Dqn_String8 string, char separator, uint64_t *output) +DQN_API Dqn_String8ToU64Result Dqn_String8_ToU64(Dqn_String8 string, char separator) { // NOTE: Argument check + Dqn_String8ToU64Result result = {}; if (!Dqn_String8_IsValid(string)) - return false; + return result; // NOTE: Sanitize input/output - *output = 0; Dqn_String8 trim_string = Dqn_String8_TrimWhitespaceAround(string); - if (trim_string.size == 0) - return true; + if (trim_string.size == 0) { + result.success = false; + return result; + } // NOTE: Handle prefix '+' Dqn_usize start_index = 0; if (!Dqn_Char_IsDigit(trim_string.data[0])) { if (trim_string.data[0] != '+') - return false; + return result; start_index++; } @@ -383,41 +385,37 @@ DQN_API bool Dqn_String8_ToU64Checked(Dqn_String8 string, char separator, uint64 } if (!Dqn_Char_IsDigit(ch)) - return false; + return result; - *output = Dqn_Safe_MulU64(*output, 10); + result.value = Dqn_Safe_MulU64(result.value, 10); uint64_t digit = ch - '0'; - *output = Dqn_Safe_AddU64(*output, digit); + result.value = Dqn_Safe_AddU64(result.value, digit); } - return true; -} - -DQN_API uint64_t Dqn_String8_ToU64(Dqn_String8 string, char separator) -{ - uint64_t result = 0; - Dqn_String8_ToU64Checked(string, separator, &result); + result.success = true; return result; } -DQN_API bool Dqn_String8_ToI64Checked(Dqn_String8 string, char separator, int64_t *output) +DQN_API Dqn_String8ToI64Result Dqn_String8_ToI64(Dqn_String8 string, char separator) { // NOTE: Argument check + Dqn_String8ToI64Result result = {}; if (!Dqn_String8_IsValid(string)) - return false; + return result; // NOTE: Sanitize input/output - *output = 0; Dqn_String8 trim_string = Dqn_String8_TrimWhitespaceAround(string); - if (trim_string.size == 0) - return true; + if (trim_string.size == 0) { + result.success = false; + return result; + } bool negative = false; Dqn_usize start_index = 0; if (!Dqn_Char_IsDigit(trim_string.data[0])) { negative = (trim_string.data[start_index] == '-'); if (!negative && trim_string.data[0] != '+') - return false; + return result; start_index++; } @@ -430,23 +428,17 @@ DQN_API bool Dqn_String8_ToI64Checked(Dqn_String8 string, char separator, int64_ } if (!Dqn_Char_IsDigit(ch)) - return false; + return result; - *output = Dqn_Safe_MulU64(*output, 10); + result.value = Dqn_Safe_MulU64(result.value, 10); uint64_t digit = ch - '0'; - *output = Dqn_Safe_AddU64(*output, digit); + result.value = Dqn_Safe_AddU64(result.value, digit); } if (negative) - *output *= -1; + result.value *= -1; - return true; -} - -DQN_API int64_t Dqn_String8_ToI64(Dqn_String8 string, char separator) -{ - int64_t result = 0; - Dqn_String8_ToI64Checked(string, separator, &result); + result.success = true; return result; } @@ -535,16 +527,16 @@ DQN_API bool operator!=(Dqn_String8 const &lhs, Dqn_String8 const &rhs) } #endif -DQN_API Dqn_String8 Dqn_String8_InitF_(DQN_LEAK_TRACE_FUNCTION Dqn_Allocator allocator, char const *fmt, ...) +DQN_API Dqn_String8 Dqn_String8_InitF(Dqn_Allocator allocator, char const *fmt, ...) { va_list va; va_start(va, fmt); - Dqn_String8 result = Dqn_String8_InitFV_(DQN_LEAK_TRACE_ARG allocator, fmt, va); + Dqn_String8 result = Dqn_String8_InitFV(allocator, fmt, va); va_end(va); return result; } -DQN_API Dqn_String8 Dqn_String8_InitFV_(DQN_LEAK_TRACE_FUNCTION Dqn_Allocator allocator, char const *fmt, va_list args) +DQN_API Dqn_String8 Dqn_String8_InitFV(Dqn_Allocator allocator, char const *fmt, va_list args) { Dqn_String8 result = {}; if (!fmt) @@ -552,29 +544,29 @@ DQN_API Dqn_String8 Dqn_String8_InitFV_(DQN_LEAK_TRACE_FUNCTION Dqn_Allocator al Dqn_usize size = Dqn_CString8_FVSize(fmt, args); if (size) { - result = Dqn_String8_Allocate_(DQN_LEAK_TRACE_ARG allocator, size, Dqn_ZeroMem_No); + result = Dqn_String8_Allocate(allocator, size, Dqn_ZeroMem_No); if (Dqn_String8_IsValid(result)) STB_SPRINTF_DECORATE(vsnprintf)(result.data, Dqn_Safe_SaturateCastISizeToInt(size + 1 /*null-terminator*/), fmt, args); } return result; } -DQN_API Dqn_String8 Dqn_String8_Allocate_(DQN_LEAK_TRACE_FUNCTION Dqn_Allocator allocator, Dqn_usize size, Dqn_ZeroMem zero_mem) +DQN_API Dqn_String8 Dqn_String8_Allocate(Dqn_Allocator allocator, Dqn_usize size, Dqn_ZeroMem zero_mem) { Dqn_String8 result = {}; - result.data = (char *)Dqn_Allocator_Alloc_(DQN_LEAK_TRACE_ARG allocator, size + 1, alignof(char), zero_mem); + result.data = (char *)Dqn_Allocator_Alloc(allocator, size + 1, alignof(char), zero_mem); if (result.data) result.size = size; return result; } -DQN_API Dqn_String8 Dqn_String8_CopyCString_(DQN_LEAK_TRACE_FUNCTION Dqn_Allocator allocator, char const *string, Dqn_usize size) +DQN_API Dqn_String8 Dqn_String8_CopyCString(Dqn_Allocator allocator, char const *string, Dqn_usize size) { Dqn_String8 result = {}; if (!string) return result; - result = Dqn_String8_Allocate_(DQN_LEAK_TRACE_ARG allocator, size, Dqn_ZeroMem_No); + result = Dqn_String8_Allocate(allocator, size, Dqn_ZeroMem_No); if (Dqn_String8_IsValid(result)) { DQN_MEMCPY(result.data, string, size); result.data[size] = 0; @@ -582,9 +574,9 @@ DQN_API Dqn_String8 Dqn_String8_CopyCString_(DQN_LEAK_TRACE_FUNCTION Dqn_Allocat return result; } -DQN_API Dqn_String8 Dqn_String8_Copy_(DQN_LEAK_TRACE_FUNCTION Dqn_Allocator allocator, Dqn_String8 string) +DQN_API Dqn_String8 Dqn_String8_Copy(Dqn_Allocator allocator, Dqn_String8 string) { - Dqn_String8 result = Dqn_String8_CopyCString_(DQN_LEAK_TRACE_ARG allocator, string.data, string.size); + Dqn_String8 result = Dqn_String8_CopyCString(allocator, string.data, string.size); return result; } @@ -619,7 +611,7 @@ DQN_API bool Dqn_String8Builder_AppendCopy(Dqn_String8Builder *builder, Dqn_Stri return result; } -DQN_API bool Dqn_String8Builder_AppendFV_(DQN_LEAK_TRACE_FUNCTION Dqn_String8Builder *builder, char const *fmt, va_list args) +DQN_API bool Dqn_String8Builder_AppendFV(Dqn_String8Builder *builder, char const *fmt, va_list args) { Dqn_String8 string = Dqn_String8_InitFV(builder->allocator, fmt, args); if (string.size == 0) @@ -627,7 +619,7 @@ DQN_API bool Dqn_String8Builder_AppendFV_(DQN_LEAK_TRACE_FUNCTION Dqn_String8Bui bool result = Dqn_String8Builder_AppendRef(builder, string); if (!result) - Dqn_Allocator_Dealloc_(DQN_LEAK_TRACE_ARG builder->allocator, string.data, string.size + 1); + Dqn_Allocator_Dealloc(builder->allocator, string.data, string.size + 1); return result; } @@ -660,165 +652,6 @@ DQN_API Dqn_String8 Dqn_String8Builder_Build(Dqn_String8Builder const *builder, return result; } -#if !defined(DQN_NO_JSON_BUILDER) -// NOTE: [$JSON] Dqn_JSONBuilder =================================================================== -DQN_API Dqn_JSONBuilder Dqn_JSONBuilder_Init(Dqn_Allocator allocator, int spaces_per_indent) -{ - Dqn_JSONBuilder result = {}; - result.spaces_per_indent = spaces_per_indent; - result.string_builder.allocator = allocator; - return result; -} - -DQN_API Dqn_String8 Dqn_JSONBuilder_Build(Dqn_JSONBuilder const *builder, Dqn_Allocator allocator) -{ - Dqn_String8 result = Dqn_String8Builder_Build(&builder->string_builder, allocator); - return result; -} - -DQN_API void Dqn_JSONBuilder_KeyValue(Dqn_JSONBuilder *builder, Dqn_String8 key, Dqn_String8 value) -{ - if (key.size == 0 && value.size == 0) - return; - - Dqn_JSONBuilderItem item = Dqn_JSONBuilderItem_KeyValue; - if (value.size == 1) { - if (value.data[0] == '{' || value.data[0] == '[') { - item = Dqn_JSONBuilderItem_OpenContainer; - } else if (value.data[0] == '}' || value.data[0] == ']') { - item = Dqn_JSONBuilderItem_CloseContainer; - } - } - - bool adding_to_container_with_items = item != Dqn_JSONBuilderItem_CloseContainer && - (builder->last_item == Dqn_JSONBuilderItem_KeyValue || - builder->last_item == Dqn_JSONBuilderItem_CloseContainer); - - uint8_t prefix_size = 0; - char prefix[2] = {0}; - if (adding_to_container_with_items) - prefix[prefix_size++] = ','; - - if (builder->last_item != Dqn_JSONBuilderItem_Empty) - prefix[prefix_size++] = '\n'; - - if (item == Dqn_JSONBuilderItem_CloseContainer) - builder->indent_level--; - - int spaces_per_indent = builder->spaces_per_indent ? builder->spaces_per_indent : 2; - int spaces = builder->indent_level * spaces_per_indent; - - if (key.size) { - Dqn_String8Builder_AppendF(&builder->string_builder, - "%.*s%*c\"%.*s\": %.*s", - prefix_size, prefix, - spaces, ' ', - DQN_STRING_FMT(key), - DQN_STRING_FMT(value)); - } else { - Dqn_String8Builder_AppendF(&builder->string_builder, - "%.*s%*c%.*s", - prefix_size, prefix, - spaces, ' ', - DQN_STRING_FMT(value)); - } - - if (item == Dqn_JSONBuilderItem_OpenContainer) - builder->indent_level++; - - builder->last_item = item; -} - -DQN_API void Dqn_JSONBuilder_KeyValueFV(Dqn_JSONBuilder *builder, Dqn_String8 key, char const *value_fmt, va_list args) -{ - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(builder->string_builder.allocator.user_context); - Dqn_String8 value = Dqn_String8_InitFV(scratch.allocator, value_fmt, args); - Dqn_JSONBuilder_KeyValue(builder, key, value); -} - -DQN_API void Dqn_JSONBuilder_KeyValueF(Dqn_JSONBuilder *builder, Dqn_String8 key, char const *value_fmt, ...) -{ - va_list args; - va_start(args, value_fmt); - Dqn_JSONBuilder_KeyValueFV(builder, key, value_fmt, args); - va_end(args); -} - -DQN_API void Dqn_JSONBuilder_ObjectBeginNamed(Dqn_JSONBuilder *builder, Dqn_String8 name) -{ - Dqn_JSONBuilder_KeyValue(builder, name, DQN_STRING8("{")); -} - -DQN_API void Dqn_JSONBuilder_ObjectEnd(Dqn_JSONBuilder *builder) -{ - Dqn_JSONBuilder_KeyValue(builder, DQN_STRING8(""), DQN_STRING8("}")); -} - -DQN_API void Dqn_JSONBuilder_ArrayBeginNamed(Dqn_JSONBuilder *builder, Dqn_String8 name) -{ - Dqn_JSONBuilder_KeyValue(builder, name, DQN_STRING8("[")); -} - -DQN_API void Dqn_JSONBuilder_ArrayEnd(Dqn_JSONBuilder *builder) -{ - Dqn_JSONBuilder_KeyValue(builder, DQN_STRING8(""), DQN_STRING8("]")); -} - -DQN_API void Dqn_JSONBuilder_StringNamed(Dqn_JSONBuilder *builder, Dqn_String8 key, Dqn_String8 value) -{ - Dqn_JSONBuilder_KeyValueF(builder, key, "\"%.*s\"", value.size, value.data); -} - -DQN_API void Dqn_JSONBuilder_LiteralNamed(Dqn_JSONBuilder *builder, Dqn_String8 key, Dqn_String8 value) -{ - Dqn_JSONBuilder_KeyValueF(builder, key, "%.*s", value.size, value.data); -} - -DQN_API void Dqn_JSONBuilder_U64Named(Dqn_JSONBuilder *builder, Dqn_String8 key, uint64_t value) -{ - Dqn_JSONBuilder_KeyValueF(builder, key, "%I64u", value); -} - -DQN_API void Dqn_JSONBuilder_I64Named(Dqn_JSONBuilder *builder, Dqn_String8 key, int64_t value) -{ - Dqn_JSONBuilder_KeyValueF(builder, key, "%I64d", value); -} - -DQN_API void Dqn_JSONBuilder_F64Named(Dqn_JSONBuilder *builder, Dqn_String8 key, double value, int decimal_places) -{ - if (!builder) - return; - - if (decimal_places >= 16) - decimal_places = 16; - - // NOTE: Generate the format string for the float, depending on how many - // decimals places it wants. - char float_fmt[16]; - if (decimal_places > 0) { - // NOTE: Emit the format string "%.f" i.e. %.1f - STB_SPRINTF_DECORATE(snprintf)(float_fmt, sizeof(float_fmt), "%%.%df", decimal_places); - } else { - // NOTE: Emit the format string "%f" - STB_SPRINTF_DECORATE(snprintf)(float_fmt, sizeof(float_fmt), "%%f"); - } - - char fmt[32]; - if (key.size) - STB_SPRINTF_DECORATE(snprintf)(fmt, sizeof(fmt), "\"%%.*s\": %s", float_fmt); - else - STB_SPRINTF_DECORATE(snprintf)(fmt, sizeof(fmt), "%s", float_fmt); - - Dqn_JSONBuilder_KeyValueF(builder, key, fmt, value); -} - -DQN_API void Dqn_JSONBuilder_BoolNamed(Dqn_JSONBuilder *builder, Dqn_String8 key, bool value) -{ - Dqn_String8 value_string = value ? DQN_STRING8("true") : DQN_STRING8("false"); - Dqn_JSONBuilder_KeyValueF(builder, key, "%.*s", value_string.size, value_string.data); -} -#endif // !defined(DQN_NO_JSON_BUILDER) - // NOTE: [$CHAR] Dqn_Char ========================================================================== DQN_API bool Dqn_Char_IsAlphabet(char ch) { @@ -959,1975 +792,3 @@ DQN_API int Dqn_UTF16_EncodeCodepoint(uint16_t utf16[2], uint32_t codepoint) return 0; } -#if !defined(DQN_NO_HEX) -// NOTE: [$BHEX] Dqn_Bin =========================================================================== -DQN_API char const *Dqn_Bin_HexBufferTrim0x(char const *hex, Dqn_usize size, Dqn_usize *real_size) -{ - Dqn_String8 result = Dqn_String8_TrimWhitespaceAround(Dqn_String8_Init(hex, size)); - result = Dqn_String8_TrimPrefix(result, DQN_STRING8("0x"), Dqn_String8EqCase_Insensitive); - if (real_size) - *real_size = result.size; - return result.data; -} - -DQN_API Dqn_String8 Dqn_Bin_HexTrim0x(Dqn_String8 string) -{ - Dqn_usize trimmed_size = 0; - char const *trimmed = Dqn_Bin_HexBufferTrim0x(string.data, string.size, &trimmed_size); - Dqn_String8 result = Dqn_String8_Init(trimmed, trimmed_size); - return result; -} - -DQN_API Dqn_BinHexU64String Dqn_Bin_U64ToHexU64String(uint64_t number, uint32_t flags) -{ - Dqn_String8 prefix = {}; - if (!(flags & Dqn_BinHexU64StringFlags_No0xPrefix)) - prefix = DQN_STRING8("0x"); - - Dqn_BinHexU64String result = {}; - DQN_MEMCPY(result.data, prefix.data, prefix.size); - result.size += DQN_CAST(int8_t)prefix.size; - - char const *fmt = (flags & Dqn_BinHexU64StringFlags_UppercaseHex) ? "%I64X" : "%I64x"; - int size = STB_SPRINTF_DECORATE(snprintf)(result.data + result.size, DQN_ARRAY_UCOUNT(result.data) - result.size, fmt, number); - result.size += DQN_CAST(uint8_t)size; - DQN_ASSERT(result.size < DQN_ARRAY_UCOUNT(result.data)); - - // NOTE: snprintf returns the required size of the format string - // irrespective of if there's space or not, but, always null terminates so - // the last byte is wasted. - result.size = DQN_MIN(result.size, DQN_ARRAY_UCOUNT(result.data) - 1); - return result; -} - -DQN_API Dqn_String8 Dqn_Bin_U64ToHex(Dqn_Allocator allocator, uint64_t number, uint32_t flags) -{ - Dqn_String8 prefix = {}; - if (!(flags & Dqn_BinHexU64StringFlags_No0xPrefix)) - prefix = DQN_STRING8("0x"); - - char const *fmt = (flags & Dqn_BinHexU64StringFlags_UppercaseHex) ? "%I64X" : "%I64x"; - Dqn_usize required_size = Dqn_CString8_FSize(fmt, number) + prefix.size; - Dqn_String8 result = Dqn_String8_Allocate(allocator, required_size, Dqn_ZeroMem_No); - - if (Dqn_String8_IsValid(result)) { - DQN_MEMCPY(result.data, prefix.data, prefix.size); - int space = DQN_CAST(int)DQN_MAX((result.size - prefix.size) + 1, 0); /*null-terminator*/ - STB_SPRINTF_DECORATE(snprintf)(result.data + prefix.size, space, fmt, number); - } - return result; -} - -DQN_API uint64_t Dqn_Bin_HexBufferToU64(char const *hex, Dqn_usize size) -{ - Dqn_usize trim_size = size; - char const *trim_hex = hex; - if (trim_size >= 2) { - if (trim_hex[0] == '0' && (trim_hex[1] == 'x' || trim_hex[1] == 'X')) { - trim_size -= 2; - trim_hex += 2; - } - } - - DQN_ASSERT(DQN_CAST(Dqn_usize)(trim_size * 4 / 8) /*maximum amount of bytes represented in the hex string*/ <= sizeof(uint64_t)); - - uint64_t result = 0; - Dqn_usize bits_written = 0; - Dqn_usize max_size = DQN_MIN(size, 8 /*bytes*/ * 2 /*hex chars per byte*/); - for (Dqn_usize hex_index = 0; hex_index < max_size; hex_index++, bits_written += 4) { - char ch = trim_hex[hex_index]; - if (!Dqn_Char_IsHex(ch)) - break; - uint8_t val = Dqn_Char_HexToU8(ch); - Dqn_usize bit_shift = 60 - bits_written; - result |= (DQN_CAST(uint64_t)val << bit_shift); - } - - result >>= (64 - bits_written); // Shift the remainder digits to the end. - return result; -} - -DQN_API uint64_t Dqn_Bin_HexToU64(Dqn_String8 hex) -{ - uint64_t result = Dqn_Bin_HexBufferToU64(hex.data, hex.size); - return result; -} - -DQN_API bool Dqn_Bin_BytesToHexBuffer(void const *src, Dqn_usize src_size, char *dest, Dqn_usize dest_size) -{ - if (!src || !dest) - return false; - - if (!DQN_CHECK(dest_size >= src_size * 2)) - return false; - - char const *HEX = "0123456789abcdef"; - unsigned char const *src_u8 = DQN_CAST(unsigned char const *)src; - for (Dqn_usize src_index = 0, dest_index = 0; src_index < src_size; src_index++) { - char byte = src_u8[src_index]; - char hex01 = (byte >> 4) & 0b1111; - char hex02 = (byte >> 0) & 0b1111; - dest[dest_index++] = HEX[(int)hex01]; - dest[dest_index++] = HEX[(int)hex02]; - } - - return true; -} - -DQN_API char *Dqn_Bin_BytesToHexBufferArena(Dqn_Arena *arena, void const *src, Dqn_usize size) -{ - char *result = size > 0 ? Dqn_Arena_NewArray(arena, char, (size * 2) + 1 /*null terminate*/, Dqn_ZeroMem_No) : nullptr; - if (result) { - bool converted = Dqn_Bin_BytesToHexBuffer(src, size, result, size * 2); - DQN_ASSERT(converted); - result[size * 2] = 0; - } - return result; -} - -DQN_API Dqn_String8 Dqn_Bin_BytesToHexArena(Dqn_Arena *arena, void const *src, Dqn_usize size) -{ - Dqn_String8 result = {}; - result.data = Dqn_Bin_BytesToHexBufferArena(arena, src, size); - if (result.data) - result.size = size * 2; - return result; -} - -DQN_API Dqn_usize Dqn_Bin_HexBufferToBytes(char const *hex, Dqn_usize hex_size, void *dest, Dqn_usize dest_size) -{ - Dqn_usize result = 0; - if (!hex || hex_size <= 0) - return result; - - Dqn_usize trim_size = 0; - char const *trim_hex = Dqn_Bin_HexBufferTrim0x(hex, - hex_size, - &trim_size); - - // NOTE: Trimmed hex can be "0xf" -> "f" or "0xAB" -> "AB" - // Either way, the size can be odd or even, hence we round up to the nearest - // multiple of two to ensure that we calculate the min buffer size orrectly. - Dqn_usize trim_size_rounded_up = trim_size + (trim_size % 2); - Dqn_usize min_buffer_size = trim_size_rounded_up / 2; - if (dest_size < min_buffer_size || trim_size <= 0) { - DQN_ASSERTF(dest_size >= min_buffer_size, "Insufficient buffer size for converting hex to binary"); - return result; - } - - result = Dqn_Bin_HexBufferToBytesUnchecked(trim_hex, - trim_size, - dest, - dest_size); - return result; -} - -DQN_API Dqn_usize Dqn_Bin_HexBufferToBytesUnchecked(char const *hex, Dqn_usize hex_size, void *dest, Dqn_usize dest_size) -{ - Dqn_usize result = 0; - unsigned char *dest_u8 = DQN_CAST(unsigned char *)dest; - - for (Dqn_usize hex_index = 0; - hex_index < hex_size; - hex_index += 2, result += 1) - { - char hex01 = hex[hex_index]; - char hex02 = (hex_index + 1 < hex_size) ? hex[hex_index + 1] : 0; - - char bit4_01 = (hex01 >= '0' && hex01 <= '9') ? 0 + (hex01 - '0') - : (hex01 >= 'a' && hex01 <= 'f') ? 10 + (hex01 - 'a') - : (hex01 >= 'A' && hex01 <= 'F') ? 10 + (hex01 - 'A') - : 0; - - char bit4_02 = (hex02 >= '0' && hex02 <= '9') ? 0 + (hex02 - '0') - : (hex02 >= 'a' && hex02 <= 'f') ? 10 + (hex02 - 'a') - : (hex02 >= 'A' && hex02 <= 'F') ? 10 + (hex02 - 'A') - : 0; - - char byte = (bit4_01 << 4) | (bit4_02 << 0); - dest_u8[result] = byte; - } - - DQN_ASSERT(result <= dest_size); - return result; -} - -DQN_API Dqn_usize Dqn_Bin_HexToBytesUnchecked(Dqn_String8 hex, void *dest, Dqn_usize dest_size) -{ - Dqn_usize result = Dqn_Bin_HexBufferToBytesUnchecked(hex.data, hex.size, dest, dest_size); - return result; -} - -DQN_API Dqn_usize Dqn_Bin_HexToBytes(Dqn_String8 hex, void *dest, Dqn_usize dest_size) -{ - Dqn_usize result = Dqn_Bin_HexBufferToBytes(hex.data, hex.size, dest, dest_size); - return result; -} - -DQN_API char *Dqn_Bin_HexBufferToBytesArena(Dqn_Arena *arena, char const *hex, Dqn_usize size, Dqn_usize *real_size) -{ - char *result = nullptr; - if (!arena || !hex || size <= 0) - return result; - - Dqn_usize trim_size = 0; - char const *trim_hex = Dqn_Bin_HexBufferTrim0x(hex, - size, - &trim_size); - - Dqn_usize binary_size = trim_size / 2; - result = Dqn_Arena_NewArray(arena, char, binary_size, Dqn_ZeroMem_No); - if (result) { - Dqn_usize convert_size = Dqn_Bin_HexBufferToBytesUnchecked(trim_hex, trim_size, result, binary_size); - if (real_size) - *real_size = convert_size; - } - return result; -} - -DQN_API Dqn_String8 Dqn_Bin_HexToBytesArena(Dqn_Arena *arena, Dqn_String8 hex) -{ - Dqn_String8 result = {}; - result.data = Dqn_Bin_HexBufferToBytesArena(arena, hex.data, hex.size, &result.size); - return result; -} -#endif // !defined(DQN_NO_HEX) - -// NOTE: Other ===================================================================================== -DQN_API int Dqn_SNPrintFDotTruncate(char *buffer, int size, char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - int size_required = STB_SPRINTF_DECORATE(vsnprintf)(buffer, size, fmt, args); - int result = DQN_MAX(DQN_MIN(size_required, size - 1), 0); - if (result == size - 1) { - buffer[size - 2] = '.'; - buffer[size - 3] = '.'; - } - va_end(args); - return result; -} - -DQN_API Dqn_U64String Dqn_U64ToString(uint64_t val, char separator) -{ - Dqn_U64String result = {}; - if (val == 0) { - result.data[result.size++] = '0'; - } else { - // NOTE: The number is written in reverse because we form the string by - // dividing by 10, so we write it in, then reverse it out after all is - // done. - Dqn_U64String temp = {}; - for (size_t digit_count = 0; val > 0; digit_count++) { - if (separator && (digit_count != 0) && (digit_count % 3 == 0)) { - temp.data[temp.size++] = separator; - } - - auto digit = DQN_CAST(char)(val % 10); - temp.data[temp.size++] = '0' + digit; - val /= 10; - } - - // NOTE: Reverse the string - for (size_t temp_index = temp.size - 1; temp_index < temp.size; temp_index--) { - char ch = temp.data[temp_index]; - result.data[result.size++] = ch; - } - } - - return result; -} - -// NOTE: [$STBS] stb_sprintf ======================================================================= -#if !defined(DQN_STB_SPRINTF_HEADER_ONLY) -#define STB_SPRINTF_IMPLEMENTATION -#ifdef STB_SPRINTF_IMPLEMENTATION - -#define stbsp__uint32 unsigned int -#define stbsp__int32 signed int - -#ifdef _MSC_VER -#define stbsp__uint64 unsigned __int64 -#define stbsp__int64 signed __int64 -#else -#define stbsp__uint64 unsigned long long -#define stbsp__int64 signed long long -#endif -#define stbsp__uint16 unsigned short - -#ifndef stbsp__uintptr -#if defined(__ppc64__) || defined(__powerpc64__) || defined(__aarch64__) || defined(_M_X64) || defined(__x86_64__) || defined(__x86_64) || defined(__s390x__) -#define stbsp__uintptr stbsp__uint64 -#else -#define stbsp__uintptr stbsp__uint32 -#endif -#endif - -#ifndef STB_SPRINTF_MSVC_MODE // used for MSVC2013 and earlier (MSVC2015 matches GCC) -#if defined(_MSC_VER) && (_MSC_VER < 1900) -#define STB_SPRINTF_MSVC_MODE -#endif -#endif - -#ifdef STB_SPRINTF_NOUNALIGNED // define this before inclusion to force stbsp_sprintf to always use aligned accesses -#define STBSP__UNALIGNED(code) -#else -#define STBSP__UNALIGNED(code) code -#endif - -#ifndef STB_SPRINTF_NOFLOAT -// internal float utility functions -static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, char *out, stbsp__int32 *decimal_pos, double value, stbsp__uint32 frac_digits); -static stbsp__int32 stbsp__real_to_parts(stbsp__int64 *bits, stbsp__int32 *expo, double value); -#define STBSP__SPECIAL 0x7000 -#endif - -static char stbsp__period = '.'; -static char stbsp__comma = ','; -static struct -{ - short temp; // force next field to be 2-byte aligned - char pair[201]; -} stbsp__digitpair = -{ - 0, - "00010203040506070809101112131415161718192021222324" - "25262728293031323334353637383940414243444546474849" - "50515253545556575859606162636465666768697071727374" - "75767778798081828384858687888990919293949596979899" -}; - -STBSP__PUBLICDEF void STB_SPRINTF_DECORATE(set_separators)(char pcomma, char pperiod) -{ - stbsp__period = pperiod; - stbsp__comma = pcomma; -} - -#define STBSP__LEFTJUST 1 -#define STBSP__LEADINGPLUS 2 -#define STBSP__LEADINGSPACE 4 -#define STBSP__LEADING_0X 8 -#define STBSP__LEADINGZERO 16 -#define STBSP__INTMAX 32 -#define STBSP__TRIPLET_COMMA 64 -#define STBSP__NEGATIVE 128 -#define STBSP__METRIC_SUFFIX 256 -#define STBSP__HALFWIDTH 512 -#define STBSP__METRIC_NOSPACE 1024 -#define STBSP__METRIC_1024 2048 -#define STBSP__METRIC_JEDEC 4096 - -static void stbsp__lead_sign(stbsp__uint32 fl, char *sign) -{ - sign[0] = 0; - if (fl & STBSP__NEGATIVE) { - sign[0] = 1; - sign[1] = '-'; - } else if (fl & STBSP__LEADINGSPACE) { - sign[0] = 1; - sign[1] = ' '; - } else if (fl & STBSP__LEADINGPLUS) { - sign[0] = 1; - sign[1] = '+'; - } -} - -static STBSP__ASAN stbsp__uint32 stbsp__strlen_limited(char const *s, stbsp__uint32 limit) -{ - char const * sn = s; - - // get up to 4-byte alignment - for (;;) { - if (((stbsp__uintptr)sn & 3) == 0) - break; - - if (!limit || *sn == 0) - return (stbsp__uint32)(sn - s); - - ++sn; - --limit; - } - - // scan over 4 bytes at a time to find terminating 0 - // this will intentionally scan up to 3 bytes past the end of buffers, - // but becase it works 4B aligned, it will never cross page boundaries - // (hence the STBSP__ASAN markup; the over-read here is intentional - // and harmless) - while (limit >= 4) { - stbsp__uint32 v = *(stbsp__uint32 *)sn; - // bit hack to find if there's a 0 byte in there - if ((v - 0x01010101) & (~v) & 0x80808080UL) - break; - - sn += 4; - limit -= 4; - } - - // handle the last few characters to find actual size - while (limit && *sn) { - ++sn; - --limit; - } - - return (stbsp__uint32)(sn - s); -} - -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va) -{ - static char hex[] = "0123456789abcdefxp"; - static char hexu[] = "0123456789ABCDEFXP"; - char *bf; - char const *f; - int tlen = 0; - - bf = buf; - f = fmt; - for (;;) { - stbsp__int32 fw, pr, tz; - stbsp__uint32 fl; - - // macros for the callback buffer stuff - #define stbsp__chk_cb_bufL(bytes) \ - { \ - int len = (int)(bf - buf); \ - if ((len + (bytes)) >= STB_SPRINTF_MIN) { \ - tlen += len; \ - if (0 == (bf = buf = callback(buf, user, len))) \ - goto done; \ - } \ - } - #define stbsp__chk_cb_buf(bytes) \ - { \ - if (callback) { \ - stbsp__chk_cb_bufL(bytes); \ - } \ - } - #define stbsp__flush_cb() \ - { \ - stbsp__chk_cb_bufL(STB_SPRINTF_MIN - 1); \ - } // flush if there is even one byte in the buffer - #define stbsp__cb_buf_clamp(cl, v) \ - cl = v; \ - if (callback) { \ - int lg = STB_SPRINTF_MIN - (int)(bf - buf); \ - if (cl > lg) \ - cl = lg; \ - } - - // fast copy everything up to the next % (or end of string) - for (;;) { - while (((stbsp__uintptr)f) & 3) { - schk1: - if (f[0] == '%') - goto scandd; - schk2: - if (f[0] == 0) - goto endfmt; - stbsp__chk_cb_buf(1); - *bf++ = f[0]; - ++f; - } - for (;;) { - // Check if the next 4 bytes contain %(0x25) or end of string. - // Using the 'hasless' trick: - // https://graphics.stanford.edu/~seander/bithacks.html#HasLessInWord - stbsp__uint32 v, c; - v = *(stbsp__uint32 *)f; - c = (~v) & 0x80808080; - if (((v ^ 0x25252525) - 0x01010101) & c) - goto schk1; - if ((v - 0x01010101) & c) - goto schk2; - if (callback) - if ((STB_SPRINTF_MIN - (int)(bf - buf)) < 4) - goto schk1; - #ifdef STB_SPRINTF_NOUNALIGNED - if(((stbsp__uintptr)bf) & 3) { - bf[0] = f[0]; - bf[1] = f[1]; - bf[2] = f[2]; - bf[3] = f[3]; - } else - #endif - { - *(stbsp__uint32 *)bf = v; - } - bf += 4; - f += 4; - } - } - scandd: - - ++f; - - // ok, we have a percent, read the modifiers first - fw = 0; - pr = -1; - fl = 0; - tz = 0; - - // flags - for (;;) { - switch (f[0]) { - // if we have left justify - case '-': - fl |= STBSP__LEFTJUST; - ++f; - continue; - // if we have leading plus - case '+': - fl |= STBSP__LEADINGPLUS; - ++f; - continue; - // if we have leading space - case ' ': - fl |= STBSP__LEADINGSPACE; - ++f; - continue; - // if we have leading 0x - case '#': - fl |= STBSP__LEADING_0X; - ++f; - continue; - // if we have thousand commas - case '\'': - fl |= STBSP__TRIPLET_COMMA; - ++f; - continue; - // if we have kilo marker (none->kilo->kibi->jedec) - case '$': - if (fl & STBSP__METRIC_SUFFIX) { - if (fl & STBSP__METRIC_1024) { - fl |= STBSP__METRIC_JEDEC; - } else { - fl |= STBSP__METRIC_1024; - } - } else { - fl |= STBSP__METRIC_SUFFIX; - } - ++f; - continue; - // if we don't want space between metric suffix and number - case '_': - fl |= STBSP__METRIC_NOSPACE; - ++f; - continue; - // if we have leading zero - case '0': - fl |= STBSP__LEADINGZERO; - ++f; - goto flags_done; - default: goto flags_done; - } - } - flags_done: - - // get the field width - if (f[0] == '*') { - fw = va_arg(va, stbsp__uint32); - ++f; - } else { - while ((f[0] >= '0') && (f[0] <= '9')) { - fw = fw * 10 + f[0] - '0'; - f++; - } - } - // get the precision - if (f[0] == '.') { - ++f; - if (f[0] == '*') { - pr = va_arg(va, stbsp__uint32); - ++f; - } else { - pr = 0; - while ((f[0] >= '0') && (f[0] <= '9')) { - pr = pr * 10 + f[0] - '0'; - f++; - } - } - } - - // handle integer size overrides - switch (f[0]) { - // are we halfwidth? - case 'h': - fl |= STBSP__HALFWIDTH; - ++f; - if (f[0] == 'h') - ++f; // QUARTERWIDTH - break; - // are we 64-bit (unix style) - case 'l': - fl |= ((sizeof(long) == 8) ? STBSP__INTMAX : 0); - ++f; - if (f[0] == 'l') { - fl |= STBSP__INTMAX; - ++f; - } - break; - // are we 64-bit on intmax? (c99) - case 'j': - fl |= (sizeof(size_t) == 8) ? STBSP__INTMAX : 0; - ++f; - break; - // are we 64-bit on size_t or ptrdiff_t? (c99) - case 'z': - fl |= (sizeof(ptrdiff_t) == 8) ? STBSP__INTMAX : 0; - ++f; - break; - case 't': - fl |= (sizeof(ptrdiff_t) == 8) ? STBSP__INTMAX : 0; - ++f; - break; - // are we 64-bit (msft style) - case 'I': - if ((f[1] == '6') && (f[2] == '4')) { - fl |= STBSP__INTMAX; - f += 3; - } else if ((f[1] == '3') && (f[2] == '2')) { - f += 3; - } else { - fl |= ((sizeof(void *) == 8) ? STBSP__INTMAX : 0); - ++f; - } - break; - default: break; - } - - // handle each replacement - switch (f[0]) { - #define STBSP__NUMSZ 512 // big enough for e308 (with commas) or e-307 - char num[STBSP__NUMSZ]; - char lead[8]; - char tail[8]; - char *s; - char const *h; - stbsp__uint32 l, n, cs; - stbsp__uint64 n64; -#ifndef STB_SPRINTF_NOFLOAT - double fv; -#endif - stbsp__int32 dp; - char const *sn; - - case 's': - // get the string - s = va_arg(va, char *); - if (s == 0) - s = (char *)"null"; - // get the length, limited to desired precision - // always limit to ~0u chars since our counts are 32b - l = stbsp__strlen_limited(s, (pr >= 0) ? pr : ~0u); - lead[0] = 0; - tail[0] = 0; - pr = 0; - dp = 0; - cs = 0; - // copy the string in - goto scopy; - - case 'c': // char - // get the character - s = num + STBSP__NUMSZ - 1; - *s = (char)va_arg(va, int); - l = 1; - lead[0] = 0; - tail[0] = 0; - pr = 0; - dp = 0; - cs = 0; - goto scopy; - - case 'n': // weird write-bytes specifier - { - int *d = va_arg(va, int *); - *d = tlen + (int)(bf - buf); - } break; - -#ifdef STB_SPRINTF_NOFLOAT - case 'A': // float - case 'a': // hex float - case 'G': // float - case 'g': // float - case 'E': // float - case 'e': // float - case 'f': // float - va_arg(va, double); // eat it - s = (char *)"No float"; - l = 8; - lead[0] = 0; - tail[0] = 0; - pr = 0; - cs = 0; - STBSP__NOTUSED(dp); - goto scopy; -#else - case 'A': // hex float - case 'a': // hex float - h = (f[0] == 'A') ? hexu : hex; - fv = va_arg(va, double); - if (pr == -1) - pr = 6; // default is 6 - // read the double into a string - if (stbsp__real_to_parts((stbsp__int64 *)&n64, &dp, fv)) - fl |= STBSP__NEGATIVE; - - s = num + 64; - - stbsp__lead_sign(fl, lead); - - if (dp == -1023) - dp = (n64) ? -1022 : 0; - else - n64 |= (((stbsp__uint64)1) << 52); - n64 <<= (64 - 56); - if (pr < 15) - n64 += ((((stbsp__uint64)8) << 56) >> (pr * 4)); -// add leading chars - -#ifdef STB_SPRINTF_MSVC_MODE - *s++ = '0'; - *s++ = 'x'; -#else - lead[1 + lead[0]] = '0'; - lead[2 + lead[0]] = 'x'; - lead[0] += 2; -#endif - *s++ = h[(n64 >> 60) & 15]; - n64 <<= 4; - if (pr) - *s++ = stbsp__period; - sn = s; - - // print the bits - n = pr; - if (n > 13) - n = 13; - if (pr > (stbsp__int32)n) - tz = pr - n; - pr = 0; - while (n--) { - *s++ = h[(n64 >> 60) & 15]; - n64 <<= 4; - } - - // print the expo - tail[1] = h[17]; - if (dp < 0) { - tail[2] = '-'; - dp = -dp; - } else - tail[2] = '+'; - n = (dp >= 1000) ? 6 : ((dp >= 100) ? 5 : ((dp >= 10) ? 4 : 3)); - tail[0] = (char)n; - for (;;) { - tail[n] = '0' + dp % 10; - if (n <= 3) - break; - --n; - dp /= 10; - } - - dp = (int)(s - sn); - l = (int)(s - (num + 64)); - s = num + 64; - cs = 1 + (3 << 24); - goto scopy; - - case 'G': // float - case 'g': // float - h = (f[0] == 'G') ? hexu : hex; - fv = va_arg(va, double); - if (pr == -1) - pr = 6; - else if (pr == 0) - pr = 1; // default is 6 - // read the double into a string - if (stbsp__real_to_str(&sn, &l, num, &dp, fv, (pr - 1) | 0x80000000)) - fl |= STBSP__NEGATIVE; - - // clamp the precision and delete extra zeros after clamp - n = pr; - if (l > (stbsp__uint32)pr) - l = pr; - while ((l > 1) && (pr) && (sn[l - 1] == '0')) { - --pr; - --l; - } - - // should we use %e - if ((dp <= -4) || (dp > (stbsp__int32)n)) { - if (pr > (stbsp__int32)l) - pr = l - 1; - else if (pr) - --pr; // when using %e, there is one digit before the decimal - goto doexpfromg; - } - // this is the insane action to get the pr to match %g semantics for %f - if (dp > 0) { - pr = (dp < (stbsp__int32)l) ? l - dp : 0; - } else { - pr = -dp + ((pr > (stbsp__int32)l) ? (stbsp__int32) l : pr); - } - goto dofloatfromg; - - case 'E': // float - case 'e': // float - h = (f[0] == 'E') ? hexu : hex; - fv = va_arg(va, double); - if (pr == -1) - pr = 6; // default is 6 - // read the double into a string - if (stbsp__real_to_str(&sn, &l, num, &dp, fv, pr | 0x80000000)) - fl |= STBSP__NEGATIVE; - doexpfromg: - tail[0] = 0; - stbsp__lead_sign(fl, lead); - if (dp == STBSP__SPECIAL) { - s = (char *)sn; - cs = 0; - pr = 0; - goto scopy; - } - s = num + 64; - // handle leading chars - *s++ = sn[0]; - - if (pr) - *s++ = stbsp__period; - - // handle after decimal - if ((l - 1) > (stbsp__uint32)pr) - l = pr + 1; - for (n = 1; n < l; n++) - *s++ = sn[n]; - // trailing zeros - tz = pr - (l - 1); - pr = 0; - // dump expo - tail[1] = h[0xe]; - dp -= 1; - if (dp < 0) { - tail[2] = '-'; - dp = -dp; - } else - tail[2] = '+'; -#ifdef STB_SPRINTF_MSVC_MODE - n = 5; -#else - n = (dp >= 100) ? 5 : 4; -#endif - tail[0] = (char)n; - for (;;) { - tail[n] = '0' + dp % 10; - if (n <= 3) - break; - --n; - dp /= 10; - } - cs = 1 + (3 << 24); // how many tens - goto flt_lead; - - case 'f': // float - fv = va_arg(va, double); - doafloat: - // do kilos - if (fl & STBSP__METRIC_SUFFIX) { - double divisor; - divisor = 1000.0f; - if (fl & STBSP__METRIC_1024) - divisor = 1024.0; - while (fl < 0x4000000) { - if ((fv < divisor) && (fv > -divisor)) - break; - fv /= divisor; - fl += 0x1000000; - } - } - if (pr == -1) - pr = 6; // default is 6 - // read the double into a string - if (stbsp__real_to_str(&sn, &l, num, &dp, fv, pr)) - fl |= STBSP__NEGATIVE; - dofloatfromg: - tail[0] = 0; - stbsp__lead_sign(fl, lead); - if (dp == STBSP__SPECIAL) { - s = (char *)sn; - cs = 0; - pr = 0; - goto scopy; - } - s = num + 64; - - // handle the three decimal varieties - if (dp <= 0) { - stbsp__int32 i; - // handle 0.000*000xxxx - *s++ = '0'; - if (pr) - *s++ = stbsp__period; - n = -dp; - if ((stbsp__int32)n > pr) - n = pr; - i = n; - while (i) { - if ((((stbsp__uintptr)s) & 3) == 0) - break; - *s++ = '0'; - --i; - } - while (i >= 4) { - *(stbsp__uint32 *)s = 0x30303030; - s += 4; - i -= 4; - } - while (i) { - *s++ = '0'; - --i; - } - if ((stbsp__int32)(l + n) > pr) - l = pr - n; - i = l; - while (i) { - *s++ = *sn++; - --i; - } - tz = pr - (n + l); - cs = 1 + (3 << 24); // how many tens did we write (for commas below) - } else { - cs = (fl & STBSP__TRIPLET_COMMA) ? ((600 - (stbsp__uint32)dp) % 3) : 0; - if ((stbsp__uint32)dp >= l) { - // handle xxxx000*000.0 - n = 0; - for (;;) { - if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { - cs = 0; - *s++ = stbsp__comma; - } else { - *s++ = sn[n]; - ++n; - if (n >= l) - break; - } - } - if (n < (stbsp__uint32)dp) { - n = dp - n; - if ((fl & STBSP__TRIPLET_COMMA) == 0) { - while (n) { - if ((((stbsp__uintptr)s) & 3) == 0) - break; - *s++ = '0'; - --n; - } - while (n >= 4) { - *(stbsp__uint32 *)s = 0x30303030; - s += 4; - n -= 4; - } - } - while (n) { - if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { - cs = 0; - *s++ = stbsp__comma; - } else { - *s++ = '0'; - --n; - } - } - } - cs = (int)(s - (num + 64)) + (3 << 24); // cs is how many tens - if (pr) { - *s++ = stbsp__period; - tz = pr; - } - } else { - // handle xxxxx.xxxx000*000 - n = 0; - for (;;) { - if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { - cs = 0; - *s++ = stbsp__comma; - } else { - *s++ = sn[n]; - ++n; - if (n >= (stbsp__uint32)dp) - break; - } - } - cs = (int)(s - (num + 64)) + (3 << 24); // cs is how many tens - if (pr) - *s++ = stbsp__period; - if ((l - dp) > (stbsp__uint32)pr) - l = pr + dp; - while (n < l) { - *s++ = sn[n]; - ++n; - } - tz = pr - (l - dp); - } - } - pr = 0; - - // handle k,m,g,t - if (fl & STBSP__METRIC_SUFFIX) { - char idx; - idx = 1; - if (fl & STBSP__METRIC_NOSPACE) - idx = 0; - tail[0] = idx; - tail[1] = ' '; - { - if (fl >> 24) { // SI kilo is 'k', JEDEC and SI kibits are 'K'. - if (fl & STBSP__METRIC_1024) - tail[idx + 1] = "_KMGT"[fl >> 24]; - else - tail[idx + 1] = "_kMGT"[fl >> 24]; - idx++; - // If printing kibits and not in jedec, add the 'i'. - if (fl & STBSP__METRIC_1024 && !(fl & STBSP__METRIC_JEDEC)) { - tail[idx + 1] = 'i'; - idx++; - } - tail[0] = idx; - } - } - }; - - flt_lead: - // get the length that we copied - l = (stbsp__uint32)(s - (num + 64)); - s = num + 64; - goto scopy; -#endif - - case 'B': // upper binary - case 'b': // lower binary - h = (f[0] == 'B') ? hexu : hex; - lead[0] = 0; - if (fl & STBSP__LEADING_0X) { - lead[0] = 2; - lead[1] = '0'; - lead[2] = h[0xb]; - } - l = (8 << 4) | (1 << 8); - goto radixnum; - - case 'o': // octal - h = hexu; - lead[0] = 0; - if (fl & STBSP__LEADING_0X) { - lead[0] = 1; - lead[1] = '0'; - } - l = (3 << 4) | (3 << 8); - goto radixnum; - - case 'p': // pointer - fl |= (sizeof(void *) == 8) ? STBSP__INTMAX : 0; - pr = sizeof(void *) * 2; - fl &= ~STBSP__LEADINGZERO; // 'p' only prints the pointer with zeros - // fall through - to X - - case 'X': // upper hex - case 'x': // lower hex - h = (f[0] == 'X') ? hexu : hex; - l = (4 << 4) | (4 << 8); - lead[0] = 0; - if (fl & STBSP__LEADING_0X) { - lead[0] = 2; - lead[1] = '0'; - lead[2] = h[16]; - } - radixnum: - // get the number - if (fl & STBSP__INTMAX) - n64 = va_arg(va, stbsp__uint64); - else - n64 = va_arg(va, stbsp__uint32); - - s = num + STBSP__NUMSZ; - dp = 0; - // clear tail, and clear leading if value is zero - tail[0] = 0; - if (n64 == 0) { - lead[0] = 0; - if (pr == 0) { - l = 0; - cs = 0; - goto scopy; - } - } - // convert to string - for (;;) { - *--s = h[n64 & ((1 << (l >> 8)) - 1)]; - n64 >>= (l >> 8); - if (!((n64) || ((stbsp__int32)((num + STBSP__NUMSZ) - s) < pr))) - break; - if (fl & STBSP__TRIPLET_COMMA) { - ++l; - if ((l & 15) == ((l >> 4) & 15)) { - l &= ~15; - *--s = stbsp__comma; - } - } - }; - // get the tens and the comma pos - cs = (stbsp__uint32)((num + STBSP__NUMSZ) - s) + ((((l >> 4) & 15)) << 24); - // get the length that we copied - l = (stbsp__uint32)((num + STBSP__NUMSZ) - s); - // copy it - goto scopy; - - case 'u': // unsigned - case 'i': - case 'd': // integer - // get the integer and abs it - if (fl & STBSP__INTMAX) { - stbsp__int64 i64 = va_arg(va, stbsp__int64); - n64 = (stbsp__uint64)i64; - if ((f[0] != 'u') && (i64 < 0)) { - n64 = (stbsp__uint64)-i64; - fl |= STBSP__NEGATIVE; - } - } else { - stbsp__int32 i = va_arg(va, stbsp__int32); - n64 = (stbsp__uint32)i; - if ((f[0] != 'u') && (i < 0)) { - n64 = (stbsp__uint32)-i; - fl |= STBSP__NEGATIVE; - } - } - -#ifndef STB_SPRINTF_NOFLOAT - if (fl & STBSP__METRIC_SUFFIX) { - if (n64 < 1024) - pr = 0; - else if (pr == -1) - pr = 1; - fv = (double)(stbsp__int64)n64; - goto doafloat; - } -#endif - - // convert to string - s = num + STBSP__NUMSZ; - l = 0; - - for (;;) { - // do in 32-bit chunks (avoid lots of 64-bit divides even with constant denominators) - char *o = s - 8; - if (n64 >= 100000000) { - n = (stbsp__uint32)(n64 % 100000000); - n64 /= 100000000; - } else { - n = (stbsp__uint32)n64; - n64 = 0; - } - if ((fl & STBSP__TRIPLET_COMMA) == 0) { - do { - s -= 2; - *(stbsp__uint16 *)s = *(stbsp__uint16 *)&stbsp__digitpair.pair[(n % 100) * 2]; - n /= 100; - } while (n); - } - while (n) { - if ((fl & STBSP__TRIPLET_COMMA) && (l++ == 3)) { - l = 0; - *--s = stbsp__comma; - --o; - } else { - *--s = (char)(n % 10) + '0'; - n /= 10; - } - } - if (n64 == 0) { - if ((s[0] == '0') && (s != (num + STBSP__NUMSZ))) - ++s; - break; - } - while (s != o) - if ((fl & STBSP__TRIPLET_COMMA) && (l++ == 3)) { - l = 0; - *--s = stbsp__comma; - --o; - } else { - *--s = '0'; - } - } - - tail[0] = 0; - stbsp__lead_sign(fl, lead); - - // get the length that we copied - l = (stbsp__uint32)((num + STBSP__NUMSZ) - s); - if (l == 0) { - *--s = '0'; - l = 1; - } - cs = l + (3 << 24); - if (pr < 0) - pr = 0; - - scopy: - // get fw=leading/trailing space, pr=leading zeros - if (pr < (stbsp__int32)l) - pr = l; - n = pr + lead[0] + tail[0] + tz; - if (fw < (stbsp__int32)n) - fw = n; - fw -= n; - pr -= l; - - // handle right justify and leading zeros - if ((fl & STBSP__LEFTJUST) == 0) { - if (fl & STBSP__LEADINGZERO) // if leading zeros, everything is in pr - { - pr = (fw > pr) ? fw : pr; - fw = 0; - } else { - fl &= ~STBSP__TRIPLET_COMMA; // if no leading zeros, then no commas - } - } - - // copy the spaces and/or zeros - if (fw + pr) { - stbsp__int32 i; - stbsp__uint32 c; - - // copy leading spaces (or when doing %8.4d stuff) - if ((fl & STBSP__LEFTJUST) == 0) - while (fw > 0) { - stbsp__cb_buf_clamp(i, fw); - fw -= i; - while (i) { - if ((((stbsp__uintptr)bf) & 3) == 0) - break; - *bf++ = ' '; - --i; - } - while (i >= 4) { - *(stbsp__uint32 *)bf = 0x20202020; - bf += 4; - i -= 4; - } - while (i) { - *bf++ = ' '; - --i; - } - stbsp__chk_cb_buf(1); - } - - // copy leader - sn = lead + 1; - while (lead[0]) { - stbsp__cb_buf_clamp(i, lead[0]); - lead[0] -= (char)i; - while (i) { - *bf++ = *sn++; - --i; - } - stbsp__chk_cb_buf(1); - } - - // copy leading zeros - c = cs >> 24; - cs &= 0xffffff; - cs = (fl & STBSP__TRIPLET_COMMA) ? ((stbsp__uint32)(c - ((pr + cs) % (c + 1)))) : 0; - while (pr > 0) { - stbsp__cb_buf_clamp(i, pr); - pr -= i; - if ((fl & STBSP__TRIPLET_COMMA) == 0) { - while (i) { - if ((((stbsp__uintptr)bf) & 3) == 0) - break; - *bf++ = '0'; - --i; - } - while (i >= 4) { - *(stbsp__uint32 *)bf = 0x30303030; - bf += 4; - i -= 4; - } - } - while (i) { - if ((fl & STBSP__TRIPLET_COMMA) && (cs++ == c)) { - cs = 0; - *bf++ = stbsp__comma; - } else - *bf++ = '0'; - --i; - } - stbsp__chk_cb_buf(1); - } - } - - // copy leader if there is still one - sn = lead + 1; - while (lead[0]) { - stbsp__int32 i; - stbsp__cb_buf_clamp(i, lead[0]); - lead[0] -= (char)i; - while (i) { - *bf++ = *sn++; - --i; - } - stbsp__chk_cb_buf(1); - } - - // copy the string - n = l; - while (n) { - stbsp__int32 i; - stbsp__cb_buf_clamp(i, n); - n -= i; - STBSP__UNALIGNED(while (i >= 4) { - *(stbsp__uint32 volatile *)bf = *(stbsp__uint32 volatile *)s; - bf += 4; - s += 4; - i -= 4; - }) - while (i) { - *bf++ = *s++; - --i; - } - stbsp__chk_cb_buf(1); - } - - // copy trailing zeros - while (tz) { - stbsp__int32 i; - stbsp__cb_buf_clamp(i, tz); - tz -= i; - while (i) { - if ((((stbsp__uintptr)bf) & 3) == 0) - break; - *bf++ = '0'; - --i; - } - while (i >= 4) { - *(stbsp__uint32 *)bf = 0x30303030; - bf += 4; - i -= 4; - } - while (i) { - *bf++ = '0'; - --i; - } - stbsp__chk_cb_buf(1); - } - - // copy tail if there is one - sn = tail + 1; - while (tail[0]) { - stbsp__int32 i; - stbsp__cb_buf_clamp(i, tail[0]); - tail[0] -= (char)i; - while (i) { - *bf++ = *sn++; - --i; - } - stbsp__chk_cb_buf(1); - } - - // handle the left justify - if (fl & STBSP__LEFTJUST) - if (fw > 0) { - while (fw) { - stbsp__int32 i; - stbsp__cb_buf_clamp(i, fw); - fw -= i; - while (i) { - if ((((stbsp__uintptr)bf) & 3) == 0) - break; - *bf++ = ' '; - --i; - } - while (i >= 4) { - *(stbsp__uint32 *)bf = 0x20202020; - bf += 4; - i -= 4; - } - while (i--) - *bf++ = ' '; - stbsp__chk_cb_buf(1); - } - } - break; - - default: // unknown, just copy code - s = num + STBSP__NUMSZ - 1; - *s = f[0]; - l = 1; - fw = fl = 0; - lead[0] = 0; - tail[0] = 0; - pr = 0; - dp = 0; - cs = 0; - goto scopy; - } - ++f; - } -endfmt: - - if (!callback) - *bf = 0; - else - stbsp__flush_cb(); - -done: - return tlen + (int)(bf - buf); -} - -// cleanup -#undef STBSP__LEFTJUST -#undef STBSP__LEADINGPLUS -#undef STBSP__LEADINGSPACE -#undef STBSP__LEADING_0X -#undef STBSP__LEADINGZERO -#undef STBSP__INTMAX -#undef STBSP__TRIPLET_COMMA -#undef STBSP__NEGATIVE -#undef STBSP__METRIC_SUFFIX -#undef STBSP__NUMSZ -#undef stbsp__chk_cb_bufL -#undef stbsp__chk_cb_buf -#undef stbsp__flush_cb -#undef stbsp__cb_buf_clamp - -// ============================================================================ -// wrapper functions - -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...) -{ - int result; - va_list va; - va_start(va, fmt); - result = STB_SPRINTF_DECORATE(vsprintfcb)(0, 0, buf, fmt, va); - va_end(va); - return result; -} - -typedef struct stbsp__context { - char *buf; - int count; - int length; - char tmp[STB_SPRINTF_MIN]; -} stbsp__context; - -static char *stbsp__clamp_callback(const char *buf, void *user, int len) -{ - stbsp__context *c = (stbsp__context *)user; - c->length += len; - - if (len > c->count) - len = c->count; - - if (len) { - if (buf != c->buf) { - const char *s, *se; - char *d; - d = c->buf; - s = buf; - se = buf + len; - do { - *d++ = *s++; - } while (s < se); - } - c->buf += len; - c->count -= len; - } - - if (c->count <= 0) - return c->tmp; - return (c->count >= STB_SPRINTF_MIN) ? c->buf : c->tmp; // go direct into buffer if you can -} - -static char * stbsp__count_clamp_callback( const char * buf, void * user, int len ) -{ - stbsp__context * c = (stbsp__context*)user; - (void) sizeof(buf); - - c->length += len; - return c->tmp; // go direct into buffer if you can -} - -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE( vsnprintf )( char * buf, int count, char const * fmt, va_list va ) -{ - stbsp__context c; - - if ( (count == 0) && !buf ) - { - c.length = 0; - - STB_SPRINTF_DECORATE( vsprintfcb )( stbsp__count_clamp_callback, &c, c.tmp, fmt, va ); - } - else - { - int l; - - c.buf = buf; - c.count = count; - c.length = 0; - - STB_SPRINTF_DECORATE( vsprintfcb )( stbsp__clamp_callback, &c, stbsp__clamp_callback(0,&c,0), fmt, va ); - - // zero-terminate - l = (int)( c.buf - buf ); - if ( l >= count ) // should never be greater, only equal (or less) than count - l = count - 1; - buf[l] = 0; - } - - return c.length; -} - -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...) -{ - int result; - va_list va; - va_start(va, fmt); - - result = STB_SPRINTF_DECORATE(vsnprintf)(buf, count, fmt, va); - va_end(va); - - return result; -} - -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va) -{ - return STB_SPRINTF_DECORATE(vsprintfcb)(0, 0, buf, fmt, va); -} - -// ======================================================================= -// low level float utility functions - -#ifndef STB_SPRINTF_NOFLOAT - -// copies d to bits w/ strict aliasing (this compiles to nothing on /Ox) -#define STBSP__COPYFP(dest, src) \ - { \ - int cn; \ - for (cn = 0; cn < 8; cn++) \ - ((char *)&dest)[cn] = ((char *)&src)[cn]; \ - } - -// get float info -static stbsp__int32 stbsp__real_to_parts(stbsp__int64 *bits, stbsp__int32 *expo, double value) -{ - double d; - stbsp__int64 b = 0; - - // load value and round at the frac_digits - d = value; - - STBSP__COPYFP(b, d); - - *bits = b & ((((stbsp__uint64)1) << 52) - 1); - *expo = (stbsp__int32)(((b >> 52) & 2047) - 1023); - - return (stbsp__int32)((stbsp__uint64) b >> 63); -} - -static double const stbsp__bot[23] = { - 1e+000, 1e+001, 1e+002, 1e+003, 1e+004, 1e+005, 1e+006, 1e+007, 1e+008, 1e+009, 1e+010, 1e+011, - 1e+012, 1e+013, 1e+014, 1e+015, 1e+016, 1e+017, 1e+018, 1e+019, 1e+020, 1e+021, 1e+022 -}; -static double const stbsp__negbot[22] = { - 1e-001, 1e-002, 1e-003, 1e-004, 1e-005, 1e-006, 1e-007, 1e-008, 1e-009, 1e-010, 1e-011, - 1e-012, 1e-013, 1e-014, 1e-015, 1e-016, 1e-017, 1e-018, 1e-019, 1e-020, 1e-021, 1e-022 -}; -static double const stbsp__negboterr[22] = { - -5.551115123125783e-018, -2.0816681711721684e-019, -2.0816681711721686e-020, -4.7921736023859299e-021, -8.1803053914031305e-022, 4.5251888174113741e-023, - 4.5251888174113739e-024, -2.0922560830128471e-025, -6.2281591457779853e-026, -3.6432197315497743e-027, 6.0503030718060191e-028, 2.0113352370744385e-029, - -3.0373745563400371e-030, 1.1806906454401013e-032, -7.7705399876661076e-032, 2.0902213275965398e-033, -7.1542424054621921e-034, -7.1542424054621926e-035, - 2.4754073164739869e-036, 5.4846728545790429e-037, 9.2462547772103625e-038, -4.8596774326570872e-039 -}; -static double const stbsp__top[13] = { - 1e+023, 1e+046, 1e+069, 1e+092, 1e+115, 1e+138, 1e+161, 1e+184, 1e+207, 1e+230, 1e+253, 1e+276, 1e+299 -}; -static double const stbsp__negtop[13] = { - 1e-023, 1e-046, 1e-069, 1e-092, 1e-115, 1e-138, 1e-161, 1e-184, 1e-207, 1e-230, 1e-253, 1e-276, 1e-299 -}; -static double const stbsp__toperr[13] = { - 8388608, - 6.8601809640529717e+028, - -7.253143638152921e+052, - -4.3377296974619174e+075, - -1.5559416129466825e+098, - -3.2841562489204913e+121, - -3.7745893248228135e+144, - -1.7356668416969134e+167, - -3.8893577551088374e+190, - -9.9566444326005119e+213, - 6.3641293062232429e+236, - -5.2069140800249813e+259, - -5.2504760255204387e+282 -}; -static double const stbsp__negtoperr[13] = { - 3.9565301985100693e-040, -2.299904345391321e-063, 3.6506201437945798e-086, 1.1875228833981544e-109, - -5.0644902316928607e-132, -6.7156837247865426e-155, -2.812077463003139e-178, -5.7778912386589953e-201, - 7.4997100559334532e-224, -4.6439668915134491e-247, -6.3691100762962136e-270, -9.436808465446358e-293, - 8.0970921678014997e-317 -}; - -#if defined(_MSC_VER) && (_MSC_VER <= 1200) -static stbsp__uint64 const stbsp__powten[20] = { - 1, - 10, - 100, - 1000, - 10000, - 100000, - 1000000, - 10000000, - 100000000, - 1000000000, - 10000000000, - 100000000000, - 1000000000000, - 10000000000000, - 100000000000000, - 1000000000000000, - 10000000000000000, - 100000000000000000, - 1000000000000000000, - 10000000000000000000U -}; -#define stbsp__tento19th ((stbsp__uint64)1000000000000000000) -#else -static stbsp__uint64 const stbsp__powten[20] = { - 1, - 10, - 100, - 1000, - 10000, - 100000, - 1000000, - 10000000, - 100000000, - 1000000000, - 10000000000ULL, - 100000000000ULL, - 1000000000000ULL, - 10000000000000ULL, - 100000000000000ULL, - 1000000000000000ULL, - 10000000000000000ULL, - 100000000000000000ULL, - 1000000000000000000ULL, - 10000000000000000000ULL -}; -#define stbsp__tento19th (1000000000000000000ULL) -#endif - -#define stbsp__ddmulthi(oh, ol, xh, yh) \ - { \ - double ahi = 0, alo, bhi = 0, blo; \ - stbsp__int64 bt; \ - oh = xh * yh; \ - STBSP__COPYFP(bt, xh); \ - bt &= ((~(stbsp__uint64)0) << 27); \ - STBSP__COPYFP(ahi, bt); \ - alo = xh - ahi; \ - STBSP__COPYFP(bt, yh); \ - bt &= ((~(stbsp__uint64)0) << 27); \ - STBSP__COPYFP(bhi, bt); \ - blo = yh - bhi; \ - ol = ((ahi * bhi - oh) + ahi * blo + alo * bhi) + alo * blo; \ - } - -#define stbsp__ddtoS64(ob, xh, xl) \ - { \ - double ahi = 0, alo, vh, t; \ - ob = (stbsp__int64)xh; \ - vh = (double)ob; \ - ahi = (xh - vh); \ - t = (ahi - xh); \ - alo = (xh - (ahi - t)) - (vh + t); \ - ob += (stbsp__int64)(ahi + alo + xl); \ - } - -#define stbsp__ddrenorm(oh, ol) \ - { \ - double s; \ - s = oh + ol; \ - ol = ol - (s - oh); \ - oh = s; \ - } - -#define stbsp__ddmultlo(oh, ol, xh, xl, yh, yl) ol = ol + (xh * yl + xl * yh); - -#define stbsp__ddmultlos(oh, ol, xh, yl) ol = ol + (xh * yl); - -static void stbsp__raise_to_power10(double *ohi, double *olo, double d, stbsp__int32 power) // power can be -323 to +350 -{ - double ph, pl; - if ((power >= 0) && (power <= 22)) { - stbsp__ddmulthi(ph, pl, d, stbsp__bot[power]); - } else { - stbsp__int32 e, et, eb; - double p2h, p2l; - - e = power; - if (power < 0) - e = -e; - et = (e * 0x2c9) >> 14; /* %23 */ - if (et > 13) - et = 13; - eb = e - (et * 23); - - ph = d; - pl = 0.0; - if (power < 0) { - if (eb) { - --eb; - stbsp__ddmulthi(ph, pl, d, stbsp__negbot[eb]); - stbsp__ddmultlos(ph, pl, d, stbsp__negboterr[eb]); - } - if (et) { - stbsp__ddrenorm(ph, pl); - --et; - stbsp__ddmulthi(p2h, p2l, ph, stbsp__negtop[et]); - stbsp__ddmultlo(p2h, p2l, ph, pl, stbsp__negtop[et], stbsp__negtoperr[et]); - ph = p2h; - pl = p2l; - } - } else { - if (eb) { - e = eb; - if (eb > 22) - eb = 22; - e -= eb; - stbsp__ddmulthi(ph, pl, d, stbsp__bot[eb]); - if (e) { - stbsp__ddrenorm(ph, pl); - stbsp__ddmulthi(p2h, p2l, ph, stbsp__bot[e]); - stbsp__ddmultlos(p2h, p2l, stbsp__bot[e], pl); - ph = p2h; - pl = p2l; - } - } - if (et) { - stbsp__ddrenorm(ph, pl); - --et; - stbsp__ddmulthi(p2h, p2l, ph, stbsp__top[et]); - stbsp__ddmultlo(p2h, p2l, ph, pl, stbsp__top[et], stbsp__toperr[et]); - ph = p2h; - pl = p2l; - } - } - } - stbsp__ddrenorm(ph, pl); - *ohi = ph; - *olo = pl; -} - -// given a float value, returns the significant bits in bits, and the position of the -// decimal point in decimal_pos. +/-INF and NAN are specified by special values -// returned in the decimal_pos parameter. -// frac_digits is absolute normally, but if you want from first significant digits (got %g and %e), or in 0x80000000 -static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, char *out, stbsp__int32 *decimal_pos, double value, stbsp__uint32 frac_digits) -{ - double d; - stbsp__int64 bits = 0; - stbsp__int32 expo, e, ng, tens; - - d = value; - STBSP__COPYFP(bits, d); - expo = (stbsp__int32)((bits >> 52) & 2047); - ng = (stbsp__int32)((stbsp__uint64) bits >> 63); - if (ng) - d = -d; - - if (expo == 2047) // is nan or inf? - { - *start = (bits & ((((stbsp__uint64)1) << 52) - 1)) ? "NaN" : "Inf"; - *decimal_pos = STBSP__SPECIAL; - *len = 3; - return ng; - } - - if (expo == 0) // is zero or denormal - { - if (((stbsp__uint64) bits << 1) == 0) // do zero - { - *decimal_pos = 1; - *start = out; - out[0] = '0'; - *len = 1; - return ng; - } - // find the right expo for denormals - { - stbsp__int64 v = ((stbsp__uint64)1) << 51; - while ((bits & v) == 0) { - --expo; - v >>= 1; - } - } - } - - // find the decimal exponent as well as the decimal bits of the value - { - double ph, pl; - - // log10 estimate - very specifically tweaked to hit or undershoot by no more than 1 of log10 of all expos 1..2046 - tens = expo - 1023; - tens = (tens < 0) ? ((tens * 617) / 2048) : (((tens * 1233) / 4096) + 1); - - // move the significant bits into position and stick them into an int - stbsp__raise_to_power10(&ph, &pl, d, 18 - tens); - - // get full as much precision from double-double as possible - stbsp__ddtoS64(bits, ph, pl); - - // check if we undershot - if (((stbsp__uint64)bits) >= stbsp__tento19th) - ++tens; - } - - // now do the rounding in integer land - frac_digits = (frac_digits & 0x80000000) ? ((frac_digits & 0x7ffffff) + 1) : (tens + frac_digits); - if ((frac_digits < 24)) { - stbsp__uint32 dg = 1; - if ((stbsp__uint64)bits >= stbsp__powten[9]) - dg = 10; - while ((stbsp__uint64)bits >= stbsp__powten[dg]) { - ++dg; - if (dg == 20) - goto noround; - } - if (frac_digits < dg) { - stbsp__uint64 r; - // add 0.5 at the right position and round - e = dg - frac_digits; - if ((stbsp__uint32)e >= 24) - goto noround; - r = stbsp__powten[e]; - bits = bits + (r / 2); - if ((stbsp__uint64)bits >= stbsp__powten[dg]) - ++tens; - bits /= r; - } - noround:; - } - - // kill long trailing runs of zeros - if (bits) { - stbsp__uint32 n; - for (;;) { - if (bits <= 0xffffffff) - break; - if (bits % 1000) - goto donez; - bits /= 1000; - } - n = (stbsp__uint32)bits; - while ((n % 1000) == 0) - n /= 1000; - bits = n; - donez:; - } - - // convert to string - out += 64; - e = 0; - for (;;) { - stbsp__uint32 n; - char *o = out - 8; - // do the conversion in chunks of U32s (avoid most 64-bit divides, worth it, constant denomiators be damned) - if (bits >= 100000000) { - n = (stbsp__uint32)(bits % 100000000); - bits /= 100000000; - } else { - n = (stbsp__uint32)bits; - bits = 0; - } - while (n) { - out -= 2; - *(stbsp__uint16 *)out = *(stbsp__uint16 *)&stbsp__digitpair.pair[(n % 100) * 2]; - n /= 100; - e += 2; - } - if (bits == 0) { - if ((e) && (out[0] == '0')) { - ++out; - --e; - } - break; - } - while (out != o) { - *--out = '0'; - ++e; - } - } - - *decimal_pos = tens; - *start = out; - *len = e; - return ng; -} - -#undef stbsp__ddmulthi -#undef stbsp__ddrenorm -#undef stbsp__ddmultlo -#undef stbsp__ddmultlos -#undef STBSP__SPECIAL -#undef STBSP__COPYFP - -#endif // STB_SPRINTF_NOFLOAT - -// clean up -#undef stbsp__uint16 -#undef stbsp__uint32 -#undef stbsp__int32 -#undef stbsp__uint64 -#undef stbsp__int64 -#undef STBSP__UNALIGNED - -#endif // STB_SPRINTF_IMPLEMENTATION - -/* ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright (c) 2017 Sean Barrett -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. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain (www.unlicense.org) -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. -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 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. ------------------------------------------------------------------------------- -*/ -#endif // DQN_STB_SPRINTF_HEADER_ONLY diff --git a/dqn_strings.h b/dqn_strings.h index 3de6ff1..7d6b662 100644 --- a/dqn_strings.h +++ b/dqn_strings.h @@ -1,17 +1,3 @@ -// NOTE: Table Of Contents ========================================================================= -// Index | Disable #define | Description -// ================================================================================================= -// [$CSTR] Dqn_CString8 | | C-string helpers -// [$STR8] Dqn_String8 | | Pointer and length strings -// [$FSTR] Dqn_FString8 | DQN_NO_FSTRING8 | Fixed-size strings -// [$STRB] Dqn_String8Builder | | -// [$JSON] Dqn_JSONBuilder | DQN_NO_JSON_BUILDER | Construct json output -// [$CHAR] Dqn_Char | | Character ascii/digit.. helpers -// [$UTFX] Dqn_UTF | | Unicode helpers -// [$BHEX] Dqn_Bin | DQN_NO_HEX | Binary <-> hex helpers -// [$STBS] stb_sprintf | DQN_STB_SPRINTF_HEADER_ONLY | Portable sprintf -// ================================================================================================= - // NOTE: [$CSTR] Dqn_CString8 ====================================================================== // @proc Dqn_CString8_ArrayCount // @desc Calculate the size of a cstring literal/array at compile time @@ -192,8 +178,7 @@ DQN_API Dqn_usize Dqn_CString16_Size (wcha // @return The file name in the file path, if none is found, the original path // string is returned. Null pointer if arguments are null or invalid. -// @proc Dqn_String8_ToI64Checked, Dqn_String8_ToI64, -// Dqn_String8_ToU64Checked, Dqn_String8_ToU64 +// @proc Dqn_String8_ToI64, Dqn_String8_ToU64 // @desc Convert a number represented as a string to a signed 64 bit number. // // The `separator` is an optional digit separator for example, if `separator` @@ -203,23 +188,14 @@ DQN_API Dqn_usize Dqn_CString16_Size (wcha // i.e. "+1234" -> 1234 and "-1234" -> -1234. Strings must consist entirely of // digits, the seperator or the permitted prefixes as previously mentioned // otherwise this function will return false, i.e. "1234 dog" will cause the -// function to return false, however, the output is greedily converted and will -// be evaluated to "1234". +// function to return false, however, the output is greedily converted and +// will be evaluated to "1234". // -// `ToU64Checked` and `ToU64` only '+' prefix is permitted -// `ToI64Checked` and `ToI64` both '+' and '-' prefix is permitted +// `ToU64` only '+' prefix is permitted +// `ToI64` either '+' or '-' prefix is permitted // -// @param[in] buf The string to convert -// @param[in] size The size of the string, pass '-1' to calculate the -// null-terminated string length in the function. // @param[in] separator The character used to separate the digits, if any. Set // this to 0, if no separators are permitted. -// @param[out] output The number to write the parsed value to -// -// @return The checked variants return false if there were invalid characters -// in the string true otherwise. -// The non-checked variant returns the number that could optimistically be -// parsed from the string e.g. "1234 dog" will return 1234. // @proc Dqn_String8_Replace, Dqn_String8_ReplaceInsensitive // @desc TODO(doyle): Write description @@ -247,14 +223,14 @@ DQN_API Dqn_usize Dqn_CString16_Size (wcha struct Dqn_String8Link { - Dqn_String8 string; ///< The string - Dqn_String8Link *next; ///< The next string in the linked list + Dqn_String8 string; // The string + Dqn_String8Link *next; // The next string in the linked list }; struct Dqn_String16 /// A pointer and length style string that holds slices to UTF16 bytes. { - wchar_t *data; ///< The UTF16 bytes of the string - Dqn_usize size; ///< The number of characters in the string + wchar_t *data; // The UTF16 bytes of the string + Dqn_usize size; // The number of characters in the string #if defined(__cplusplus) wchar_t const *begin() const { return data; } ///< Const begin iterator for range-for loops @@ -289,12 +265,6 @@ struct Dqn_String8FindResult #define Dqn_String8_Init(data, size) (Dqn_String8){(data), (size)} #endif -#define Dqn_String8_InitF(allocator, fmt, ...) Dqn_String8_InitF_(DQN_LEAK_TRACE allocator, fmt, ## __VA_ARGS__) -#define Dqn_String8_InitFV(allocator, fmt, args) Dqn_String8_InitFV_(DQN_LEAK_TRACE allocator, fmt, args) -#define Dqn_String8_Allocate(allocator, size, zero_mem) Dqn_String8_Allocate_(DQN_LEAK_TRACE allocator, size, zero_mem) -#define Dqn_String8_CopyCString(allocator, string, size) Dqn_String8_CopyCString_(DQN_LEAK_TRACE allocator, string, size) -#define Dqn_String8_Copy(allocator, string) Dqn_String8_Copy_(DQN_LEAK_TRACE allocator, string) - // NOTE: API ======================================================================================= enum Dqn_String8IsAll { @@ -322,6 +292,12 @@ DQN_API Dqn_String8 Dqn_String8_InitCString8 (char con DQN_API bool Dqn_String8_IsValid (Dqn_String8 string); DQN_API bool Dqn_String8_IsAll (Dqn_String8 string, Dqn_String8IsAll is_all); +DQN_API Dqn_String8 Dqn_String8_InitF (Dqn_Allocator allocator, char const *fmt, ...); +DQN_API Dqn_String8 Dqn_String8_InitFV (Dqn_Allocator allocator, char const *fmt, va_list args); +DQN_API Dqn_String8 Dqn_String8_Allocate (Dqn_Allocator allocator, Dqn_usize size, Dqn_ZeroMem zero_mem); +DQN_API Dqn_String8 Dqn_String8_CopyCString (Dqn_Allocator allocator, char const *string, Dqn_usize size); +DQN_API Dqn_String8 Dqn_String8_Copy (Dqn_Allocator allocator, Dqn_String8 string); + DQN_API Dqn_String8 Dqn_String8_Slice (Dqn_String8 string, Dqn_usize offset, Dqn_usize size); DQN_API Dqn_String8BinarySplitResult Dqn_String8_BinarySplitArray (Dqn_String8 string, Dqn_String8 const *find, Dqn_usize find_size); DQN_API Dqn_String8BinarySplitResult Dqn_String8_BinarySplit (Dqn_String8 string, Dqn_String8 find); @@ -346,10 +322,20 @@ DQN_API Dqn_String8 Dqn_String8_TrimByteOrderMark (Dqn_Stri DQN_API Dqn_String8 Dqn_String8_FileNameFromPath (Dqn_String8 path); -DQN_API bool Dqn_String8_ToU64Checked (Dqn_String8 string, char separator, uint64_t *output); -DQN_API uint64_t Dqn_String8_ToU64 (Dqn_String8 string, char separator); -DQN_API bool Dqn_String8_ToI64Checked (Dqn_String8 string, char separator, int64_t *output); -DQN_API int64_t Dqn_String8_ToI64 (Dqn_String8 string, char separator); +struct Dqn_String8ToU64Result +{ + bool success; + uint64_t value; +}; + +struct Dqn_String8ToI64Result +{ + bool success; + int64_t value; +}; + +DQN_API Dqn_String8ToU64Result Dqn_String8_ToU64 (Dqn_String8 string, char separator); +DQN_API Dqn_String8ToI64Result Dqn_String8_ToI64 (Dqn_String8 string, char separator); DQN_API Dqn_String8 Dqn_String8_Replace (Dqn_String8 string, Dqn_String8 find, Dqn_String8 replace, Dqn_usize start_index, Dqn_Allocator allocator, Dqn_String8EqCase eq_case = Dqn_String8EqCase_Sensitive); DQN_API Dqn_String8 Dqn_String8_ReplaceInsensitive (Dqn_String8 string, Dqn_String8 find, Dqn_String8 replace, Dqn_usize start_index, Dqn_Allocator allocator); @@ -360,13 +346,6 @@ DQN_API bool operator== (Dqn_Stri DQN_API bool operator!= (Dqn_String8 const &lhs, Dqn_String8 const &rhs); #endif -// NOTE: Internal ================================================================================== -DQN_API Dqn_String8 Dqn_String8_InitF_ (DQN_LEAK_TRACE_FUNCTION Dqn_Allocator allocator, char const *fmt, ...); -DQN_API Dqn_String8 Dqn_String8_InitFV_ (DQN_LEAK_TRACE_FUNCTION Dqn_Allocator allocator, char const *fmt, va_list args); -DQN_API Dqn_String8 Dqn_String8_Allocate_ (DQN_LEAK_TRACE_FUNCTION Dqn_Allocator allocator, Dqn_usize size, Dqn_ZeroMem zero_mem); -DQN_API Dqn_String8 Dqn_String8_CopyCString_ (DQN_LEAK_TRACE_FUNCTION Dqn_Allocator allocator, char const *string, Dqn_usize size); -DQN_API Dqn_String8 Dqn_String8_Copy_ (DQN_LEAK_TRACE_FUNCTION Dqn_Allocator allocator, Dqn_String8 string); - #if !defined(DQN_NO_FSTRING8) // NOTE: [$FSTR] Dqn_FString8 ====================================================================== // NOTE: API ======================================================================================= @@ -500,107 +479,12 @@ struct Dqn_String8Builder Dqn_usize count; ///< The number of links in the linked list of strings }; -#define Dqn_String8Builder_AppendFV(builder, fmt, args) Dqn_String8Builder_AppendFV_(DQN_LEAK_TRACE builder, fmt, args) DQN_API bool Dqn_String8Builder_AppendF (Dqn_String8Builder *builder, char const *fmt, ...); +DQN_API bool Dqn_String8Builder_AppendFV (Dqn_String8Builder *builder, char const *fmt, va_list args); DQN_API bool Dqn_String8Builder_AppendRef (Dqn_String8Builder *builder, Dqn_String8 string); DQN_API bool Dqn_String8Builder_AppendCopy(Dqn_String8Builder *builder, Dqn_String8 string); DQN_API Dqn_String8 Dqn_String8Builder_Build (Dqn_String8Builder const *builder, Dqn_Allocator allocator); -// NOTE: Internal ================================================================================== -DQN_API bool Dqn_String8Builder_AppendFV_ (DQN_LEAK_TRACE_FUNCTION Dqn_String8Builder *builder, char const *fmt, va_list args); - -#if !defined(DQN_NO_JSON_BUILDER) -// NOTE: [$JSON] Dqn_JSONBuilder =================================================================== -// Basic helper class to construct JSON output to a string -// TODO(dqn): We need to write tests for this -// -// NOTE: API ======================================================================================= -// @proc Dqn_JSONBuilder_Build -// @desc Convert the internal JSON buffer in the builder into a string. -// @param[in] arena The allocator to use to build the string - -// @proc Dqn_JSONBuilder_KeyValue, Dqn_JSONBuilder_KeyValueF -// @desc Add a JSON key value pair untyped. The value is emitted directly -// without checking the contents of value. -// -// All other functions internally call into this function which is the main -// workhorse of the builder. - -// @proc Dqn_JSON_Builder_ObjectEnd -// @desc End a JSON object in the builder, generates internally a '}' string - -// @proc Dqn_JSON_Builder_ArrayEnd -// @desc End a JSON array in the builder, generates internally a ']' string - -// @proc Dqn_JSONBuilder_LiteralNamed -// @desc Add a named JSON key-value object whose value is directly written to -// the following '"": ' (e.g. useful for emitting the 'null' -// value) - -// @proc Dqn_JSONBuilder_U64Named, Dqn_JSONBuilder_U64, -// Dqn_JSONBuilder_I64Named, Dqn_JSONBuilder_I64, -// Dqn_JSONBuilder_F64Named, Dqn_JSONBuilder_F64, -// Dqn_JSONBuilder_BoolNamed, Dqn_JSONBuilder_Bool, -// @desc Add the named JSON data type as a key-value object. Generates -// internally the string '"": ' - -enum Dqn_JSONBuilderItem { - Dqn_JSONBuilderItem_Empty, - Dqn_JSONBuilderItem_OpenContainer, - Dqn_JSONBuilderItem_CloseContainer, - Dqn_JSONBuilderItem_KeyValue, -}; - -struct Dqn_JSONBuilder { - bool use_stdout; ///< When set, ignore the string builder and dump immediately to stdout - Dqn_String8Builder string_builder; ///< (Internal) - int indent_level; ///< (Internal) - int spaces_per_indent; ///< The number of spaces per indent level - Dqn_JSONBuilderItem last_item; -}; - -#define Dqn_JSONBuilder_Object(builder) \ - DQN_DEFER_LOOP(Dqn_JSONBuilder_ObjectBegin(builder), \ - Dqn_JSONBuilder_ObjectEnd(builder)) - -#define Dqn_JSONBuilder_ObjectNamed(builder, name) \ - DQN_DEFER_LOOP(Dqn_JSONBuilder_ObjectBeginNamed(builder, name), \ - Dqn_JSONBuilder_ObjectEnd(builder)) - -#define Dqn_JSONBuilder_Array(builder) \ - DQN_DEFER_LOOP(Dqn_JSONBuilder_ArrayBegin(builder), \ - Dqn_JSONBuilder_ArrayEnd(builder)) - -#define Dqn_JSONBuilder_ArrayNamed(builder, name) \ - DQN_DEFER_LOOP(Dqn_JSONBuilder_ArrayBeginNamed(builder, name), \ - Dqn_JSONBuilder_ArrayEnd(builder)) - - -DQN_API Dqn_JSONBuilder Dqn_JSONBuilder_Init (Dqn_Allocator allocator, int spaces_per_indent); -DQN_API Dqn_String8 Dqn_JSONBuilder_Build (Dqn_JSONBuilder const *builder, Dqn_Allocator allocator); -DQN_API void Dqn_JSONBuilder_KeyValue (Dqn_JSONBuilder *builder, Dqn_String8 key, Dqn_String8 value); -DQN_API void Dqn_JSONBuilder_KeyValueF (Dqn_JSONBuilder *builder, Dqn_String8 key, char const *value_fmt, ...); -DQN_API void Dqn_JSONBuilder_ObjectBeginNamed(Dqn_JSONBuilder *builder, Dqn_String8 name); -DQN_API void Dqn_JSONBuilder_ObjectEnd (Dqn_JSONBuilder *builder); -DQN_API void Dqn_JSONBuilder_ArrayBeginNamed (Dqn_JSONBuilder *builder, Dqn_String8 name); -DQN_API void Dqn_JSONBuilder_ArrayEnd (Dqn_JSONBuilder *builder); -DQN_API void Dqn_JSONBuilder_StringNamed (Dqn_JSONBuilder *builder, Dqn_String8 key, Dqn_String8 value); -DQN_API void Dqn_JSONBuilder_LiteralNamed (Dqn_JSONBuilder *builder, Dqn_String8 key, Dqn_String8 value); -DQN_API void Dqn_JSONBuilder_U64Named (Dqn_JSONBuilder *builder, Dqn_String8 key, uint64_t value); -DQN_API void Dqn_JSONBuilder_I64Named (Dqn_JSONBuilder *builder, Dqn_String8 key, int64_t value); -DQN_API void Dqn_JSONBuilder_F64Named (Dqn_JSONBuilder *builder, Dqn_String8 key, double value, int decimal_places); -DQN_API void Dqn_JSONBuilder_BoolNamed (Dqn_JSONBuilder *builder, Dqn_String8 key, bool value); - -#define Dqn_JSONBuilder_ObjectBegin(builder) Dqn_JSONBuilder_ObjectBeginNamed(builder, DQN_STRING8("")) -#define Dqn_JSONBuilder_ArrayBegin(builder) Dqn_JSONBuilder_ArrayBeginNamed(builder, DQN_STRING8("")) -#define Dqn_JSONBuilder_String(builder, value) Dqn_JSONBuilder_StringNamed(builder, DQN_STRING8(""), value) -#define Dqn_JSONBuilder_Literal(builder, value) Dqn_JSONBuilder_LiteralNamed(builder, DQN_STRING8(""), value) -#define Dqn_JSONBuilder_U64(builder, value) Dqn_JSONBuilder_U64Named(builder, DQN_STRING8(""), value) -#define Dqn_JSONBuilder_I64(builder, value) Dqn_JSONBuilder_I64Named(builder, DQN_STRING8(""), value) -#define Dqn_JSONBuilder_F64(builder, value) Dqn_JSONBuilder_F64Named(builder, DQN_STRING8(""), value) -#define Dqn_JSONBuilder_Bool(builder, value) Dqn_JSONBuilder_BoolNamed(builder, DQN_STRING8(""), value) -#endif // !defined(DQN_NO_JSON_BUIDLER) - // NOTE: [$CHAR] Dqn_Char ========================================================================== DQN_API bool Dqn_Char_IsAlphabet (char ch); DQN_API bool Dqn_Char_IsDigit (char ch); @@ -616,370 +500,6 @@ DQN_API char Dqn_Char_ToLower (char ch); DQN_API int Dqn_UTF8_EncodeCodepoint(uint8_t utf8[4], uint32_t codepoint); DQN_API int Dqn_UTF16_EncodeCodepoint(uint16_t utf16[2], uint32_t codepoint); -#if !defined(DQN_NO_HEX) -// NOTE: [$BHEX] Dqn_Bin =========================================================================== -// NOTE: API ======================================================================================= -// @proc Dqn_Bin_U64ToHexU64String -// @desc Convert a 64 bit number to a hex string -// @param[in] number Number to convert to hexadecimal representation -// @param[in] flags Bit flags from Dqn_BinHexU64StringFlags to customise the -// output of the hexadecimal string. -// @return The hexadecimal representation of the number. This string is always -// null-terminated. - -// @proc Dqn_Bin_U64ToHex -// @copybrief Dqn_Bin_U64ToHexU64String - -// @param[in] allocator The memory allocator to use for the memory of the -// hexadecimal string. -// @copyparams Dqn_Bin_U64ToHexU64String - -// @proc Dqn_Bin_HexBufferToU64 -// @desc Convert a hexadecimal string a 64 bit value. -// Asserts if the hex string is too big to be converted into a 64 bit number. - -// @proc Dqn_Bin_HexToU64 -// @copydoc Dqn_Bin_HexToU64 - -// @proc Dqn_Bin_BytesToHexBuffer -// @desc Convert a binary buffer into its hex representation into dest. -// -// The dest buffer must be large enough to contain the hex representation, i.e. -// atleast (src_size * 2). -// -// @return True if the conversion into the dest buffer was successful, false -// otherwise (e.g. invalid arguments). - -// @proc Dqn_Bin_BytesToHexBufferArena -// @desc Convert a series of bytes into a string -// @return A null-terminated hex string, null pointer if allocation failed - -// @proc Dqn_Bin_BytesToHexArena -// @copydoc Dqn_Bin_BytesToHexBufferArena -// @return A hex string, the string is invalid if conversion failed. - -// @proc Dqn_Bin_HexBufferToBytes -// @desc Convert a hex string into binary at `dest`. -// -// The dest buffer must be large enough to contain the binary representation, -// i.e. atleast ceil(hex_size / 2). This function will strip whitespace, -// leading 0x/0X prefix from the string before conversion. -// -// @param[in] hex The hexadecimal string to convert -// @param[in] hex_size Size of the hex buffer. This function can handle an odd -// size hex string i.e. "fff" produces 0xfff0. -// @param[out] dest Buffer to write the bytes to -// @param[out] dest_size Maximum number of bytes to write to dest -// -// @return The number of bytes written to `dest_size`, this value will *never* -// be greater than `dest_size`. - -// @proc Dqn_Bin_HexToBytes -// @desc String8 variant of @see Dqn_Bin_HexBufferToBytes - -// @proc Dqn_Bin_StringHexBufferToBytesUnchecked -// @desc Unchecked variant of @see Dqn_Bin_HexBufferToBytes -// -// This function skips some string checks, it assumes the hex is a valid hex -// stream and that the arguments are valid e.g. no trimming or 0x prefix -// stripping is performed - -// @proc Dqn_Bin_String -// @desc String8 variant of @see Dqn_Bin_HexBufferToBytesUnchecked - -// @proc Dqn_Bin_HexBufferToBytesArena -// Dynamic allocating variant of @see Dqn_Bin_HexBufferToBytesUnchecked -// -// @param[in] arena The arena to allocate the bytes from -// @param[in] hex Hex string to convert into bytes -// @param[in] size Size of the hex string -// @param[out] real_size The size of the buffer returned by the function -// -// @return The byte representation of the hex string. - -// @proc Dqn_Bin_HexToBytesArena -// @copybrief Dqn_Bin_HexBufferToBytesArena -// -// @param[in] arena The arena to allocate the bytes from -// @param[in] hex Hex string to convert into bytes -// -// @return The byte representation of the hex string. - -struct Dqn_BinHexU64String -{ - char data[2 /*0x*/ + 16 /*hex*/ + 1 /*null-terminator*/]; - uint8_t size; -}; - -enum Dqn_BinHexU64StringFlags -{ - Dqn_BinHexU64StringFlags_No0xPrefix = 1 << 0, /// Remove the 0x prefix from the string - Dqn_BinHexU64StringFlags_UppercaseHex = 1 << 1, /// Use uppercase ascii characters for hex -}; - -DQN_API char const * Dqn_Bin_HexBufferTrim0x (char const *hex, Dqn_usize size, Dqn_usize *real_size); -DQN_API Dqn_String8 Dqn_Bin_HexTrim0x (Dqn_String8 string); - -DQN_API Dqn_BinHexU64String Dqn_Bin_U64ToHexU64String (uint64_t number, uint32_t flags); -DQN_API Dqn_String8 Dqn_Bin_U64ToHex (Dqn_Allocator allocator, uint64_t number, uint32_t flags); - -DQN_API uint64_t Dqn_Bin_HexBufferToU64 (char const *hex, Dqn_usize size); -DQN_API uint64_t Dqn_Bin_HexToU64 (Dqn_String8 hex); - -DQN_API Dqn_String8 Dqn_Bin_BytesToHexArena (Dqn_Arena *arena, void const *src, Dqn_usize size); -DQN_API char * Dqn_Bin_BytesToHexBufferArena (Dqn_Arena *arena, void const *src, Dqn_usize size); -DQN_API bool Dqn_Bin_BytesToHexBuffer (void const *src, Dqn_usize src_size, char *dest, Dqn_usize dest_size); - -DQN_API Dqn_usize Dqn_Bin_HexBufferToBytesUnchecked(char const *hex, Dqn_usize hex_size, void *dest, Dqn_usize dest_size); -DQN_API Dqn_usize Dqn_Bin_HexBufferToBytes (char const *hex, Dqn_usize hex_size, void *dest, Dqn_usize dest_size); -DQN_API char * Dqn_Bin_HexBufferToBytesArena (Dqn_Arena *arena, char const *hex, Dqn_usize hex_size, Dqn_usize *real_size); - -DQN_API Dqn_usize Dqn_Bin_HexToBytesUnchecked (Dqn_String8 hex, void *dest, Dqn_usize dest_size); -DQN_API Dqn_usize Dqn_Bin_HexToBytes (Dqn_String8 hex, void *dest, Dqn_usize dest_size); -DQN_API Dqn_String8 Dqn_Bin_HexToBytesArena (Dqn_Arena *arena, Dqn_String8 hex); -#endif // !defined(DQN_NO_HEX) - -// NOTE: Other ===================================================================================== -// NOTE: API ======================================================================================= -// @proc Dqn_SNPrintFDotTruncate -// @desc Write the format string to the buffer truncating with a trailing ".." -// if there is insufficient space in the buffer followed by null-terminating -// the buffer (uses stb_sprintf underneath). -// @return The size of the string written to the buffer *not* including the -// null-terminator. -// -// @proc Dqn_U64ToString -// @desc Convert a 64 bit unsigned value to its string representation. -// @param[in] val Value to convert into a string -// @param[in] separator The separator to insert every 3 digits. Set this to -// 0 if no separator is desired. - -struct Dqn_U64String -{ - char data[27+1]; // NOTE(dqn): 27 is the maximum size of uint64_t including a separtor - uint8_t size; -}; - -DQN_API int Dqn_SNPrintFDotTruncate(char *buffer, int size, char const *fmt, ...); -DQN_API Dqn_U64String Dqn_U64ToString (uint64_t val, char separator); - -// NOTE: [$STBS] stb_sprintf ======================================================================= -// stb_sprintf - v1.10 - public domain snprintf() implementation -// originally by Jeff Roberts / RAD Game Tools, 2015/10/20 -// http://github.com/nothings/stb -// -// allowed types: sc uidBboXx p AaGgEef n -// lengths : hh h ll j z t I64 I32 I -// -// Contributors: -// Fabian "ryg" Giesen (reformatting) -// github:aganm (attribute format) -// -// Contributors (bugfixes): -// github:d26435 -// github:trex78 -// github:account-login -// Jari Komppa (SI suffixes) -// Rohit Nirmal -// Marcin Wojdyr -// Leonard Ritter -// Stefano Zanotti -// Adam Allison -// Arvid Gerstmann -// Markus Kolb -// -// LICENSE: -// -// See end of file for license information. - -#ifndef STB_SPRINTF_H_INCLUDE -#define STB_SPRINTF_H_INCLUDE - -/* -Single file sprintf replacement. - -Originally written by Jeff Roberts at RAD Game Tools - 2015/10/20. -Hereby placed in public domain. - -This is a full sprintf replacement that supports everything that -the C runtime sprintfs support, including float/double, 64-bit integers, -hex floats, field parameters (%*.*d stuff), length reads backs, etc. - -Why would you need this if sprintf already exists? Well, first off, -it's *much* faster (see below). It's also much smaller than the CRT -versions code-space-wise. We've also added some simple improvements -that are super handy (commas in thousands, callbacks at buffer full, -for example). Finally, the format strings for MSVC and GCC differ -for 64-bit integers (among other small things), so this lets you use -the same format strings in cross platform code. - -It uses the standard single file trick of being both the header file -and the source itself. If you just include it normally, you just get -the header file function definitions. To get the code, you include -it from a C or C++ file and define STB_SPRINTF_IMPLEMENTATION first. - -It only uses va_args macros from the C runtime to do it's work. It -does cast doubles to S64s and shifts and divides U64s, which does -drag in CRT code on most platforms. - -It compiles to roughly 8K with float support, and 4K without. -As a comparison, when using MSVC static libs, calling sprintf drags -in 16K. - -API: -==== -int stbsp_sprintf( char * buf, char const * fmt, ... ) -int stbsp_snprintf( char * buf, int count, char const * fmt, ... ) - Convert an arg list into a buffer. stbsp_snprintf always returns - a zero-terminated string (unlike regular snprintf). - -int stbsp_vsprintf( char * buf, char const * fmt, va_list va ) -int stbsp_vsnprintf( char * buf, int count, char const * fmt, va_list va ) - Convert a va_list arg list into a buffer. stbsp_vsnprintf always returns - a zero-terminated string (unlike regular snprintf). - -int stbsp_vsprintfcb( STBSP_SPRINTFCB * callback, void * user, char * buf, char const * fmt, va_list va ) - typedef char * STBSP_SPRINTFCB( char const * buf, void * user, int len ); - Convert into a buffer, calling back every STB_SPRINTF_MIN chars. - Your callback can then copy the chars out, print them or whatever. - This function is actually the workhorse for everything else. - The buffer you pass in must hold at least STB_SPRINTF_MIN characters. - // you return the next buffer to use or 0 to stop converting - -void stbsp_set_separators( char comma, char period ) - Set the comma and period characters to use. - -FLOATS/DOUBLES: -=============== -This code uses a internal float->ascii conversion method that uses -doubles with error correction (double-doubles, for ~105 bits of -precision). This conversion is round-trip perfect - that is, an atof -of the values output here will give you the bit-exact double back. - -One difference is that our insignificant digits will be different than -with MSVC or GCC (but they don't match each other either). We also -don't attempt to find the minimum length matching float (pre-MSVC15 -doesn't either). - -If you don't need float or doubles at all, define STB_SPRINTF_NOFLOAT -and you'll save 4K of code space. - -64-BIT INTS: -============ -This library also supports 64-bit integers and you can use MSVC style or -GCC style indicators (%I64d or %lld). It supports the C99 specifiers -for size_t and ptr_diff_t (%jd %zd) as well. - -EXTRAS: -======= -Like some GCCs, for integers and floats, you can use a ' (single quote) -specifier and commas will be inserted on the thousands: "%'d" on 12345 -would print 12,345. - -For integers and floats, you can use a "$" specifier and the number -will be converted to float and then divided to get kilo, mega, giga or -tera and then printed, so "%$d" 1000 is "1.0 k", "%$.2d" 2536000 is -"2.53 M", etc. For byte values, use two $:s, like "%$$d" to turn -2536000 to "2.42 Mi". If you prefer JEDEC suffixes to SI ones, use three -$:s: "%$$$d" -> "2.42 M". To remove the space between the number and the -suffix, add "_" specifier: "%_$d" -> "2.53M". - -In addition to octal and hexadecimal conversions, you can print -integers in binary: "%b" for 256 would print 100. - -PERFORMANCE vs MSVC 2008 32-/64-bit (GCC is even slower than MSVC): -=================================================================== -"%d" across all 32-bit ints (4.8x/4.0x faster than 32-/64-bit MSVC) -"%24d" across all 32-bit ints (4.5x/4.2x faster) -"%x" across all 32-bit ints (4.5x/3.8x faster) -"%08x" across all 32-bit ints (4.3x/3.8x faster) -"%f" across e-10 to e+10 floats (7.3x/6.0x faster) -"%e" across e-10 to e+10 floats (8.1x/6.0x faster) -"%g" across e-10 to e+10 floats (10.0x/7.1x faster) -"%f" for values near e-300 (7.9x/6.5x faster) -"%f" for values near e+300 (10.0x/9.1x faster) -"%e" for values near e-300 (10.1x/7.0x faster) -"%e" for values near e+300 (9.2x/6.0x faster) -"%.320f" for values near e-300 (12.6x/11.2x faster) -"%a" for random values (8.6x/4.3x faster) -"%I64d" for 64-bits with 32-bit values (4.8x/3.4x faster) -"%I64d" for 64-bits > 32-bit values (4.9x/5.5x faster) -"%s%s%s" for 64 char strings (7.1x/7.3x faster) -"...512 char string..." ( 35.0x/32.5x faster!) -*/ - -#if defined(__clang__) - #if defined(__has_feature) && defined(__has_attribute) - #if __has_feature(address_sanitizer) - #if __has_attribute(__no_sanitize__) - #define STBSP__ASAN __attribute__((__no_sanitize__("address"))) - #elif __has_attribute(__no_sanitize_address__) - #define STBSP__ASAN __attribute__((__no_sanitize_address__)) - #elif __has_attribute(__no_address_safety_analysis__) - #define STBSP__ASAN __attribute__((__no_address_safety_analysis__)) - #endif - #endif - #endif -#elif defined(__GNUC__) && (__GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) - #if defined(__SANITIZE_ADDRESS__) && __SANITIZE_ADDRESS__ - #define STBSP__ASAN __attribute__((__no_sanitize_address__)) - #endif -#endif - -#ifndef STBSP__ASAN -#define STBSP__ASAN -#endif - -#ifdef STB_SPRINTF_STATIC -#define STBSP__PUBLICDEC static -#define STBSP__PUBLICDEF static STBSP__ASAN -#else -#ifdef __cplusplus -#define STBSP__PUBLICDEC extern "C" -#define STBSP__PUBLICDEF extern "C" STBSP__ASAN -#else -#define STBSP__PUBLICDEC extern -#define STBSP__PUBLICDEF STBSP__ASAN -#endif -#endif - -#if defined(__has_attribute) - #if __has_attribute(format) - #define STBSP__ATTRIBUTE_FORMAT(fmt,va) __attribute__((format(printf,fmt,va))) - #endif -#endif - -#ifndef STBSP__ATTRIBUTE_FORMAT -#define STBSP__ATTRIBUTE_FORMAT(fmt,va) -#endif - -#ifdef _MSC_VER -#define STBSP__NOTUSED(v) (void)(v) -#else -#define STBSP__NOTUSED(v) (void)sizeof(v) -#endif - -#include // for va_arg(), va_list() -#include // size_t, ptrdiff_t - -#ifndef STB_SPRINTF_MIN -#define STB_SPRINTF_MIN 512 // how many characters per callback -#endif -typedef char *STBSP_SPRINTFCB(const char *buf, void *user, int len); - -#ifndef STB_SPRINTF_DECORATE -#define STB_SPRINTF_DECORATE(name) stbsp_##name // define this before including if you want to change the names -#endif - -STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va); -STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsnprintf)(char *buf, int count, char const *fmt, va_list va); -STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...) STBSP__ATTRIBUTE_FORMAT(2,3); -STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...) STBSP__ATTRIBUTE_FORMAT(3,4); - -STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va); -STBSP__PUBLICDEC void STB_SPRINTF_DECORATE(set_separators)(char comma, char period); -#endif // STB_SPRINTF_H_INCLUDE - #if !defined(DQN_NO_FSTRING8) // NOTE: [$FSTR] Dqn_FString8 ====================================================================== template Dqn_FString8 Dqn_FString8_InitF(char const *fmt, ...) diff --git a/dqn_tests_helpers.cpp b/dqn_tests_helpers.cpp new file mode 100644 index 0000000..93f94f9 --- /dev/null +++ b/dqn_tests_helpers.cpp @@ -0,0 +1,100 @@ +#if defined(DQN_KECCAK_H) +// ----------------------------------------------------------------------------- +// Dqn_Keccak Reference Implementation +// ----------------------------------------------------------------------------- +// A very compact Keccak implementation taken from the reference implementation +// repository +// +// https://github.com/XKCP/XKCP/blob/master/Standalone/CompactFIPS202/C/Keccak-more-compact.c +// + +#define FOR(i,n) for(i=0; i>1; } +#define ROL(a,o) ((((u64)a)<>(64-o))) +static u64 load64(const u8 *x) { ui i; u64 u=0; FOR(i,8) { u<<=8; u|=x[7-i]; } return u; } +static void store64(u8 *x, u64 u) { ui i; FOR(i,8) { x[i]=u; u>>=8; } } +static void xor64(u8 *x, u64 u) { ui i; FOR(i,8) { x[i]^=u; u>>=8; } } +#define rL(x,y) load64((u8*)s+8*(x+5*y)) +#define wL(x,y,l) store64((u8*)s+8*(x+5*y),l) +#define XL(x,y,l) xor64((u8*)s+8*(x+5*y),l) +void KeccakF1600(void *s) +{ + ui r,x,y,i,j,Y; u8 R=0x01; u64 C[5],D; + for(i=0; i<24; i++) { + /*θ*/ FOR(x,5) C[x]=rL(x,0)^rL(x,1)^rL(x,2)^rL(x,3)^rL(x,4); FOR(x,5) { D=C[(x+4)%5]^ROL(C[(x+1)%5],1); FOR(y,5) XL(x,y,D); } + /*ρπ*/ x=1; y=r=0; D=rL(x,y); FOR(j,24) { r+=j+1; Y=(2*x+3*y)%5; x=y; y=Y; C[0]=rL(x,y); wL(x,y,ROL(D,r%64)); D=C[0]; } + /*χ*/ FOR(y,5) { FOR(x,5) C[x]=rL(x,y); FOR(x,5) wL(x,y,C[x]^((~C[(x+1)%5])&C[(x+2)%5])); } + /*ι*/ FOR(j,7) if (LFSR86540(&R)) XL(0,0,(u64)1<<((1<0) { b=(inLen0) { b=(outLen0) KeccakF1600(s); } +} + + +// PCG32 Random Number Generator +// ----------------------------------------------------------------------------- +// NOTE: https://github.com/imneme/pcg-c-basic + +struct pcg_state_setseq_64 +{ // Internals are *Private*. + uint64_t state; // RNG state. All values are possible. + uint64_t inc; // Controls which RNG sequence (stream) is + // selected. Must *always* be odd. +}; +typedef struct pcg_state_setseq_64 pcg32_random_t; + +// pcg32_random_r(rng) +// Generate a uniformly distributed 32-bit random number + +uint32_t pcg32_random_r(pcg32_random_t* rng) +{ + uint64_t oldstate = rng->state; + rng->state = oldstate * 6364136223846793005ULL + rng->inc; + uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u; + uint32_t rot = oldstate >> 59u; + return (xorshifted >> rot) | (xorshifted << ((-rot) & 31)); +} + +// pcg32_srandom_r(rng, initstate, initseq): +// Seed the rng. Specified in two parts, state initializer and a +// sequence selection constant (a.k.a. stream id) + +void pcg32_srandom_r(pcg32_random_t* rng, uint64_t initstate, uint64_t initseq) +{ + rng->state = 0U; + rng->inc = (initseq << 1u) | 1u; + pcg32_random_r(rng); + rng->state += initstate; + pcg32_random_r(rng); +} + +// pcg32_boundedrand_r(rng, bound): +// Generate a uniformly distributed number, r, where 0 <= r < bound + +uint32_t pcg32_boundedrand_r(pcg32_random_t* rng, uint32_t bound) +{ + uint32_t threshold = -bound % bound; + for (;;) { + uint32_t r = pcg32_random_r(rng); + if (r >= threshold) + return r % bound; + } +} +#endif // DQN_KECCAK_H