dqn: Reorganize the library

This commit is contained in:
doyle 2023-08-16 21:59:38 +10:00
parent 3c614946e0
commit a5a4485e29
27 changed files with 6735 additions and 5626 deletions

View File

@ -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<uint64_t> map = Dqn_DSMap_Init<uint64_t>(MAP_SIZE);
Dqn_DSMap<uint64_t> map = Dqn_DSMap_Init<uint64_t>(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<uint64_t> map = Dqn_DSMap_Init<uint64_t>(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<uint64_t>(&map, key));
DQN_UTEST_ASSERT(&test, !Dqn_DSMap_FindSlot<uint64_t>(&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<uint64_t>(&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<uint64_t>(&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

View File

@ -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 {

411
b_stacktrace.h Normal file
View File

@ -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 <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
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 <Windows.h>
#include <TlHelp32.h>
#include <DbgHelp.h>
#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 <execinfo.h>
#include <unistd.h>
#include <dlfcn.h>
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 <execinfo.h>
#include <ucontext.h>
#include <unistd.h>
#include <dlfcn.h>
#include <string.h>
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 */

343
dqn.h
View File

@ -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 <stdarg.h> // va_list
#include <stdio.h> // fprintf, FILE, stdout, stderr
#include <stdint.h> // [u]int_*, ...
#include <limits.h> // [U]INT_MAX, ...
// NOTE: Table of Contents =========================================================================
// Index | #define Label | Description
// NOTE: C Headers =================================================================================
#include <stdarg.h> // | | va_list
#include <stdio.h> // | | fprintf, FILE, stdout, stderr
#include <stdint.h> // | | [u]int_*, ...
#include <limits.h> // | | [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
// <strings.h>
//
// 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 <math.h>
//
// 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 <Windows.h> 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

View File

@ -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)

View File

@ -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 <string.h>
#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 <math.h>
#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 <signal.h>
#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 <stdio.h>
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 <x86intrin.h>
#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__}

View File

@ -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 <typename T> 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 <typename T> Dqn_List<T> Dqn_List_InitWithArena(Dqn_Arena *aren
DQN_API template <typename T> T * Dqn_List_At (Dqn_List<T> *list, Dqn_usize index, Dqn_ListChunk<T> *at_chunk);
DQN_API template <typename T> bool Dqn_List_Iterate (Dqn_List<T> *list, Dqn_ListIterator<T> *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 <typename T> T * Dqn_List_Make_ (DQN_LEAK_TRACE_FUNCTION Dqn_List<T> *list, Dqn_usize count);
DQN_API template <typename T> T * Dqn_List_Add_ (DQN_LEAK_TRACE_FUNCTION Dqn_List<T> *list, Dqn_usize count);
DQN_API template <typename T> T * Dqn_List_Make (Dqn_List<T> *list, Dqn_usize count);
DQN_API template <typename T> T * Dqn_List_Add (Dqn_List<T> *list, Dqn_usize count);
#endif // !defined(DQN_NO_LIST)
#if !defined(DQN_NO_VARRAY)
// NOTE: [$VARR] Dqn_VArray ========================================================================
DQN_API template <typename T> Dqn_VArray<T> 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<T> 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<T> 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 <typename T> Dqn_VArray<T> Dqn_VArray_Init(Dqn_Arena *arena, Dqn_usize max)
{
Dqn_VArray<T> result = Dqn_VArray_InitByteSize<T>(arena, max * sizeof(T));
DQN_ASSERT(result.max >= max);
return result;
}
@ -431,7 +417,7 @@ DQN_API template <typename T> T *Dqn_VArray_Make(Dqn_VArray<T> *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 <typename T> DQN_API Dqn_List<T> Dqn_List_InitWithArena(Dqn_Arena *aren
return result;
}
template <typename T> DQN_API T *Dqn_List_Make_(DQN_LEAK_TRACE_FUNCTION Dqn_List<T> *list, Dqn_usize count)
template <typename T> DQN_API T *Dqn_List_Make(Dqn_List<T> *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<T> * )Dqn_Arena_Allocate_(list->arena, sizeof(Dqn_ListChunk<T>), alignof(Dqn_ListChunk<T>), Dqn_ZeroMem_Yes DQN_LEAK_TRACE_ARG);
auto *tail = Dqn_Arena_New(list->arena, Dqn_ListChunk<T>, 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 <typename T> DQN_API T *Dqn_List_Make_(DQN_LEAK_TRACE_FUNCTION Dqn_List
return result;
}
template <typename T> DQN_API T *Dqn_List_Add_(DQN_LEAK_TRACE_FUNCTION Dqn_List<T> *list, T const &value)
template <typename T> DQN_API T *Dqn_List_Add(Dqn_List<T> *list, T const &value)
{
T *result = Dqn_List_Make_(list, 1);
*result = value;
T *result = Dqn_List_Make(list, 1);
*result = value;
return result;
}

320
dqn_debug.cpp Normal file
View File

@ -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<Dqn_AllocRecord> *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<Dqn_AllocRecord> *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<Dqn_AllocRecord> *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);
}

127
dqn_debug.h Normal file
View File

@ -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 <signal.h>
#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, ...);

1697
dqn_external.cpp Normal file

File diff suppressed because it is too large Load Diff

596
dqn_external.h Normal file
View File

@ -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 <Windows.h>
#include <shellscalingapi.h>
#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 <bcrypt.h> // Dqn_OS_SecureRNGBytes -> BCryptOpenAlgorithmProvider ... etc
#include <shellapi.h> // Dqn_Win_MakeProcessDPIAware -> SetProcessDpiAwareProc
#if !defined(DQN_NO_WINNET)
#include <wininet.h> // 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.h> // errno
#include <fcntl.h> // O_RDONLY ... etc
#include <linux/fs.h> // FICLONE
#include <sys/ioctl.h> // ioctl
#include <sys/types.h> // pid_t
#include <sys/random.h> // getrandom
#include <sys/stat.h> // stat
#include <sys/sendfile.h> // sendfile
#include <sys/mman.h> // mmap
#include <time.h> // clock_gettime, nanosleep
#include <unistd.h> // 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 <stdarg.h> // for va_arg(), va_list()
#include <stddef.h> // 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

View File

@ -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

989
dqn_helpers.cpp Normal file
View File

@ -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 "%.<decimal_places>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<Dqn_AllocRecord>(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)

655
dqn_helpers.h Normal file
View File

@ -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 '"<key>": <value>' (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 '"<key>": <value>'
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 <typename T>
using Dqn_BinarySearchLessThanProc = bool(T const &lhs, T const &rhs);
template <typename T>
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 <typename T>
Dqn_BinarySearchResult
Dqn_BinarySearch(T const *array,
Dqn_usize array_size,
T const &find,
Dqn_BinarySearchType type = Dqn_BinarySearchType_Match,
Dqn_BinarySearchLessThanProc<T> less_than = Dqn_BinarySearch_DefaultLessThan);
template <typename T> DQN_FORCE_INLINE bool Dqn_BinarySearch_DefaultLessThan(T const &lhs, T const &rhs)
{
bool result = lhs < rhs;
return result;
}
template <typename T>
Dqn_BinarySearchResult
Dqn_BinarySearch(T const *array,
Dqn_usize array_size,
T const &find,
Dqn_BinarySearchType type,
Dqn_BinarySearchLessThanProc<T> 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<Dqn_AllocRecord> 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);

View File

@ -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 =============================================================================

View File

@ -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 =============================================================================

View File

@ -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)
{

View File

@ -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

View File

@ -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<Dqn_LeakTrace>(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);
}

View File

@ -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<Dqn_LeakTrace> 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 <typename T>
using Dqn_BinarySearchLessThanProc = bool(T const &lhs, T const &rhs);
template <typename T>
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 <typename T>
Dqn_BinarySearchResult
Dqn_BinarySearch(T const *array,
Dqn_usize array_size,
T const &find,
Dqn_BinarySearchType type = Dqn_BinarySearchType_Match,
Dqn_BinarySearchLessThanProc<T> less_than = Dqn_BinarySearch_DefaultLessThan);
template <typename T> DQN_FORCE_INLINE bool Dqn_BinarySearch_DefaultLessThan(T const &lhs, T const &rhs)
{
bool result = lhs < rhs;
return result;
}
template <typename T>
Dqn_BinarySearchResult
Dqn_BinarySearch(T const *array,
Dqn_usize array_size,
T const &find,
Dqn_BinarySearchType type,
Dqn_BinarySearchLessThanProc<T> 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;
}

File diff suppressed because it is too large Load Diff

View File

@ -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);

View File

@ -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);
}

View File

@ -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, ...);

File diff suppressed because it is too large Load Diff

View File

@ -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 '"<key>": <value>' (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 '"<key>": <value>'
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 <stdarg.h> // for va_arg(), va_list()
#include <stddef.h> // 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_usize N> Dqn_FString8<N> Dqn_FString8_InitF(char const *fmt, ...)

100
dqn_tests_helpers.cpp Normal file
View File

@ -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<n; ++i)
typedef unsigned char u8;
typedef unsigned long long int u64;
typedef unsigned int ui;
void Keccak(ui r, ui c, const u8 *in, u64 inLen, u8 sfx, u8 *out, u64 outLen);
void FIPS202_SHAKE128(const u8 *in, u64 inLen, u8 *out, u64 outLen) { Keccak(1344, 256, in, inLen, 0x1F, out, outLen); }
void FIPS202_SHAKE256(const u8 *in, u64 inLen, u8 *out, u64 outLen) { Keccak(1088, 512, in, inLen, 0x1F, out, outLen); }
void FIPS202_SHA3_224(const u8 *in, u64 inLen, u8 *out) { Keccak(1152, 448, in, inLen, 0x06, out, 28); }
void FIPS202_SHA3_256(const u8 *in, u64 inLen, u8 *out) { Keccak(1088, 512, in, inLen, 0x06, out, 32); }
void FIPS202_SHA3_384(const u8 *in, u64 inLen, u8 *out) { Keccak(832, 768, in, inLen, 0x06, out, 48); }
void FIPS202_SHA3_512(const u8 *in, u64 inLen, u8 *out) { Keccak(576, 1024, in, inLen, 0x06, out, 64); }
int LFSR86540(u8 *R) { (*R)=((*R)<<1)^(((*R)&0x80)?0x71:0); return ((*R)&2)>>1; }
#define ROL(a,o) ((((u64)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<<j)-1));
}
}
void Keccak(ui r, ui c, const u8 *in, u64 inLen, u8 sfx, u8 *out, u64 outLen)
{
/*initialize*/ u8 s[200]; ui R=r/8; ui i,b=0; FOR(i,200) s[i]=0;
/*absorb*/ while(inLen>0) { b=(inLen<R)?inLen:R; FOR(i,b) s[i]^=in[i]; in+=b; inLen-=b; if (b==R) { KeccakF1600(s); b=0; } }
/*pad*/ s[b]^=sfx; if((sfx&0x80)&&(b==(R-1))) KeccakF1600(s); s[R-1]^=0x80; KeccakF1600(s);
/*squeeze*/ while(outLen>0) { b=(outLen<R)?outLen:R; FOR(i,b) out[i]=s[i]; out+=b; outLen-=b; if(outLen>0) 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