From aeaa497a7b74de0a4570983fd6ca31e2339efde9 Mon Sep 17 00:00:00 2001 From: doylet Date: Tue, 23 Jun 2026 23:17:16 +1000 Subject: [PATCH] Start merging things back into a mega header to simplify everything --- Single-Header/dn_single_header.cpp | 1868 +++- Single-Header/dn_single_header.h | 1749 +-- Source/Base/dn_base.cpp | 9078 --------------- Source/Base/dn_base.h | 3521 ------ Source/External/metadesk/md.c | 4450 -------- Source/External/metadesk/md.h | 1248 --- Source/External/metadesk/md_stb_sprintf.h | 1905 ---- Source/Extra/dn_net.cpp | 130 - Source/Extra/dn_net.h | 134 - Source/Extra/dn_net_curl.cpp | 708 -- Source/Extra/dn_net_curl.h | 39 - Source/Extra/dn_net_emscripten.cpp | 467 - Source/Extra/dn_net_emscripten.h | 17 - Source/Extra/dn_tests.cpp | 6 +- Source/Extra/dn_tests_main.cpp | 18 +- Source/OS/dn_os.cpp | 1414 --- Source/OS/dn_os.h | 575 - Source/dn.cpp | 11820 +++++++++++++++++++- Source/dn.h | 4363 +++++++- build.bat | 4 +- single_header_generator.cpp | 5 +- 21 files changed, 18542 insertions(+), 24977 deletions(-) delete mode 100644 Source/Base/dn_base.cpp delete mode 100644 Source/Base/dn_base.h delete mode 100644 Source/External/metadesk/md.c delete mode 100644 Source/External/metadesk/md.h delete mode 100644 Source/External/metadesk/md_stb_sprintf.h delete mode 100644 Source/Extra/dn_net.cpp delete mode 100644 Source/Extra/dn_net.h delete mode 100644 Source/Extra/dn_net_curl.cpp delete mode 100644 Source/Extra/dn_net_curl.h delete mode 100644 Source/Extra/dn_net_emscripten.cpp delete mode 100644 Source/Extra/dn_net_emscripten.h delete mode 100644 Source/OS/dn_os.cpp delete mode 100644 Source/OS/dn_os.h diff --git a/Single-Header/dn_single_header.cpp b/Single-Header/dn_single_header.cpp index 548da7d..3134307 100644 --- a/Single-Header/dn_single_header.cpp +++ b/Single-Header/dn_single_header.cpp @@ -1,17 +1,13 @@ -// Generated by the DN single header generator 2026-06-23 21:12:54 +// Generated by the DN single header generator 2026-06-23 23:16:54 -// DN: Single header generator commented out => #if defined(_CLANGD) -// #define DN_H_WITH_OS 1 -// #include "dn.h" -// #endif - -// DN: Single header generator commented out => #include "Base/dn_base.cpp" #define DN_BASE_CPP // DN: Single header generator commented out => #if defined(_CLANGD) // #define DN_ARENA_TEMP_MEM_UAF_GUARD 1 -// #define DN_H_WITH_OS 1 -// #include "../dn.h" +// #define DN_WITH_OS 1 +// #define DN_WITH_NET 1 +// #define DN_WITH_NET_CURL 1 +// #include "dn.h" // #endif #if DN_STR8_AVX512F @@ -24,6 +20,209 @@ enum DN_ArenaUAFCheckReportType_ DN_ArenaUAFCheckReportType_TempEndOutOfOrder, }; + +DN_Core *g_dn_; + +DN_API void DN_Init(DN_Core *dn, DN_InitFlags flags, DN_TCInitArgs args) +{ + DN_Set(dn); + dn->init_flags = flags; + + if (DN_BitIsSet(flags, DN_InitFlags_OS)) { + #if DN_WITH_OS + DN_OSCore *os = &dn->os; + dn->os_init = true; + DN_OS_SetLogPrintFuncToOS(); + + // NOTE: Query OS information + { + #if defined(DN_PLATFORM_WIN32) + SYSTEM_INFO system_info = {}; + GetSystemInfo(&system_info); + os->logical_processor_count = system_info.dwNumberOfProcessors; + os->page_size = system_info.dwPageSize; + os->alloc_granularity = system_info.dwAllocationGranularity; + #else + #if defined(DN_PLATFORM_EMSCRIPTEN) + os->logical_processor_count = 1; + #else + os->logical_processor_count = get_nprocs(); + #endif + os->page_size = getpagesize(); + os->alloc_granularity = os->page_size; + #endif + } + + { + os->mem = DN_MemListFromMemFuncs(DN_Megabytes(1), DN_Kilobytes(4), DN_MemFlags_NoAllocTrack, DN_MemFuncsDefault()); + os->arena = DN_ArenaFromMemList(&os->mem); + + #if defined(DN_PLATFORM_WIN32) + os->platform_context = DN_ArenaNew(&os->arena, DN_OSW32Core, DN_ZMem_Yes); + #elif defined(DN_PLATFORM_POSIX) || defined(DN_PLATFORM_EMSCRIPTEN) + os->platform_context = DN_ArenaNew(&os->arena, DN_OSPosixCore, DN_ZMem_Yes); + #endif + + #if defined(DN_PLATFORM_WIN32) + DN_OSW32Core *w32 = DN_Cast(DN_OSW32Core *) os->platform_context; + InitializeCriticalSection(&w32->sync_primitive_free_list_mutex); + + QueryPerformanceFrequency(&w32->qpc_frequency); + HMODULE module = LoadLibraryA("kernel32.dll"); + if (module) { + w32->set_thread_description = DN_Cast(DN_OSW32SetThreadDescriptionFunc *) GetProcAddress(module, "SetThreadDescription"); + FreeLibrary(module); + } + + // NOTE: win32 bcrypt + wchar_t const BCRYPT_ALGORITHM[] = L"RNG"; + long /*NTSTATUS*/ init_status = BCryptOpenAlgorithmProvider(&w32->bcrypt_rng_handle, BCRYPT_ALGORITHM, nullptr /*implementation*/, 0 /*flags*/); + if (w32->bcrypt_rng_handle && init_status == 0) + w32->bcrypt_init_success = true; + else + DN_LogErrorF("Failed to initialise Windows secure random number generator, error: %d", init_status); + #else + DN_OS_PosixInit(DN_Cast(DN_OSPosixCore *)os->platform_context); + #endif + } + + os->cpu_report = DN_CPUGetReport(); + + #define DN_CPU_FEAT_XENTRY(label) g_dn_cpu_feature_decl[DN_CPUFeature_##label] = {DN_CPUFeature_##label, DN_Str8Lit(#label)}; + DN_CPU_FEAT_XMACRO + #undef DN_CPU_FEAT_XENTRY + DN_Assert(g_dn_); + #endif + + // NOTE: Initialise thread context + DN_TCInitFromMemFuncs(&dn->main_tc, DN_OS_ThreadID(), args, DN_MemFuncsDefault()); + DN_TCEquip(&dn->main_tc); + } + + if (DN_BitIsSet(flags, DN_InitFlags_LeakTracker)) { + DN_Assert(dn->os_init); + #if DN_WITH_OS + // NOTE: Setup the allocation table with allocation tracking turned off on + // the arena we're using to initialise the table. + dn->leak.alloc_table_mem = DN_MemListFromMemFuncs(DN_Megabytes(1), DN_Kilobytes(512), DN_MemFlags_NoAllocTrack | DN_MemFlags_AllocCanLeak, DN_MemFuncsDefault()); + dn->leak.alloc_table_arena = DN_ArenaFromMemList(&dn->leak.alloc_table_mem); + dn->leak.alloc_table = DN_DSMapInit(&dn->leak.alloc_table_arena, 4096, DN_DSMapFlags_Nil); + #endif + } + + // NOTE: Print out init features + char buf[4096]; + DN_USize buf_size = 0; + if (DN_BitIsSet(flags, DN_InitFlags_LogLibFeatures)) { + DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), "DN initialised:\n"); + #if DN_WITH_OS + DN_F32 page_size_kib = dn->os.page_size / 1024.0f; + DN_F32 alloc_granularity_kib = dn->os.alloc_granularity / 1024.0f; + DN_FmtAppendTruncate(buf, + &buf_size, + sizeof(buf), + DN_Str8Lit("..."), + " OS Page/Granularity/Cores: %.0fKiB/%.0fKiB/%u\n", + page_size_kib, + alloc_granularity_kib, + dn->os.logical_processor_count); + #endif + + DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), " Thread Context: "); + if (DN_BitIsSet(flags, DN_InitFlags_OS)) { + DN_Arena *arena = dn->main_tc.main_arena; + DN_Str8 mem_funcs = DN_Str8Lit(""); + switch (arena->mem->funcs.type) { + case DN_MemFuncsType_Nil: break; + case DN_MemFuncsType_Heap: mem_funcs = DN_Str8Lit("Heap"); break; + case DN_MemFuncsType_Virtual: mem_funcs = DN_Str8Lit("Virtual"); break; + } + DN_Str8x32 main_commit = DN_Str8x32FromByteCountU64Auto(dn->main_tc.main_arena->mem->curr->commit); + DN_Str8x32 main_reserve = DN_Str8x32FromByteCountU64Auto(dn->main_tc.main_arena->mem->curr->reserve); + DN_Str8x32 err_commit = DN_Str8x32FromByteCountU64Auto(dn->main_tc.err_sink.arena->mem->curr->commit); + DN_Str8x32 err_reserve = DN_Str8x32FromByteCountU64Auto(dn->main_tc.err_sink.arena->mem->curr->reserve); + DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), "M %.*s/%.*s", DN_Str8PrintFmt(main_commit), DN_Str8PrintFmt(main_reserve)); + if (dn->main_tc.temp_arenas_count) { + DN_Arena *temp = dn->main_tc.temp_arenas[0]; + DN_Str8x32 temp_commit = DN_Str8x32FromByteCountU64Auto(temp->mem->curr->commit); + DN_Str8x32 temp_reserve = DN_Str8x32FromByteCountU64Auto(temp->mem->curr->reserve); + DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), " T(x%zu) %.*s/%.*s", dn->main_tc.temp_arenas_count, DN_Str8PrintFmt(temp_commit), DN_Str8PrintFmt(temp_reserve)); + } + DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), " E %.*s/%.*s (%.*s)\n", DN_Str8PrintFmt(err_commit), DN_Str8PrintFmt(err_reserve), DN_Str8PrintFmt(mem_funcs)); + } else { + DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), "N/A\n"); + } + + #if DN_HAS_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__) + if (DN_ASAN_POISON) { + DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), " ASAN manual poisoning%s\n", DN_ASAN_VET_POISON ? " (+vet sanity checks)" : ""); + DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), " ASAN poison guard size: %u\n", DN_ASAN_POISON_GUARD_SIZE); + } + #endif + + #if defined(DN_LEAK_TRACKING) + DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), " Allocation leak tracing\n"); + #endif + + #if defined(DN_PLATFORM_EMSCRIPTEN) || defined(DN_PLATFORM_POSIX) + DN_OSPosixCore *posix = DN_Cast(DN_OSPosixCore *)g_dn_->os.platform_context; + DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), " Clock GetTime: %S\n", posix->clock_monotonic_raw ? DN_Str8Lit("CLOCK_MONOTONIC_RAW") : DN_Str8Lit("CLOCK_MONOTONIC")); + #endif + + // TODO(doyle): Add stacktrace feature log + } + + if (DN_BitIsSet(flags, DN_InitFlags_LogCPUFeatures)) { + DN_Assert(dn->os_init); + #if DN_WITH_OS + DN_CPUReport const *report = &dn->os.cpu_report; + DN_Str8 brand = DN_Str8TrimWhitespaceAround(DN_Str8FromPtr(report->brand, sizeof(report->brand) - 1)); + DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), " CPU '%.*s' from '%s' detected:\n", DN_Str8PrintFmt(brand), report->vendor); + + DN_USize longest_feature_name = 0; + for (DN_ForIndexU(feature_index, DN_CPUFeature_Count)) { + DN_CPUFeatureDecl feature_decl = g_dn_cpu_feature_decl[feature_index]; + longest_feature_name = DN_Max(longest_feature_name, feature_decl.label.size); + } + + for (DN_ForIndexU(feature_index, DN_CPUFeature_Count)) { + DN_CPUFeatureDecl feature_decl = g_dn_cpu_feature_decl[feature_index]; + bool has_feature = DN_CPUHasFeature(report, feature_decl.value); + DN_FmtAppendTruncate(buf, + &buf_size, + sizeof(buf), + DN_Str8Lit("..."), + " %.*s:%*s%s\n", + DN_Str8PrintFmt(feature_decl.label), + DN_Cast(int)(longest_feature_name - feature_decl.label.size), + "", + has_feature ? "available" : "not available"); + } + #endif + } + + if (buf_size) + DN_LogDebugF("%.*s", DN_Cast(int)buf_size, buf); +} + +DN_API void DN_Set(DN_Core *dn) +{ + g_dn_ = dn; +} + +DN_API DN_Core *DN_Get() +{ + DN_Core *result = g_dn_; + return result; +} + +DN_API void DN_BeginFrame() +{ + #if DN_WITH_OS + DN_AtomicSetValue64(&g_dn_->os.mem_allocs_frame, 0); + #endif +} + DN_API bool DN_VerifyArgsF(DN_VerifyType type, bool expr, DN_CallSite call_site, DN_Str8 expr_str8, char const *fmt, ...) { bool result = expr; @@ -9085,18 +9284,7 @@ DN_API void DN_LeakDump_(DN_LeakTracker *leak) } } -DN_Core *g_dn_; - -#if DN_H_WITH_OS -// DN: Single header generator commented out => #include "OS/dn_os.cpp" -#define DN_OS_CPP - -// DN: Single header generator commented out => #if defined(_CLANGD) -// #define DN_H_WITH_OS 1 -// #define DN_H_WITH_CORE 1 -// #include "../dn.h" -// #endif - +#if DN_WITH_OS #if defined(DN_PLATFORM_POSIX) #include // get_nprocs #include // getpagesize @@ -10503,6 +10691,7 @@ DN_API void DN_StackTraceReloadSymbols() SymRefreshModuleList(process); #endif } + #if defined(DN_PLATFORM_POSIX) || defined(DN_PLATFORM_EMSCRIPTEN) // DN: Single header generator commented out => #include "OS/dn_os_posix.cpp" #define DN_OS_POSIX_CPP @@ -13641,353 +13830,13 @@ DN_API bool DN_OS_W32DirWIterate(DN_Str16 path, DN_OSW32FolderIteratorW *it) #else #error Please define a platform e.g. 'DN_PLATFORM_WIN32' to enable the correct implementation for platform APIs #endif -#endif +#endif // DN_WITH_OS -DN_API void DN_Init(DN_Core *dn, DN_InitFlags flags, DN_TCInitArgs args) -{ - DN_Set(dn); - dn->init_flags = flags; - - if (DN_BitIsSet(flags, DN_InitFlags_OS)) { - #if DN_H_WITH_OS - DN_OSCore *os = &dn->os; - dn->os_init = true; - DN_OS_SetLogPrintFuncToOS(); - - // NOTE: Query OS information - { - #if defined(DN_PLATFORM_WIN32) - SYSTEM_INFO system_info = {}; - GetSystemInfo(&system_info); - os->logical_processor_count = system_info.dwNumberOfProcessors; - os->page_size = system_info.dwPageSize; - os->alloc_granularity = system_info.dwAllocationGranularity; - #else - #if defined(DN_PLATFORM_EMSCRIPTEN) - os->logical_processor_count = 1; - #else - os->logical_processor_count = get_nprocs(); - #endif - os->page_size = getpagesize(); - os->alloc_granularity = os->page_size; - #endif - } - - { - os->mem = DN_MemListFromMemFuncs(DN_Megabytes(1), DN_Kilobytes(4), DN_MemFlags_NoAllocTrack, DN_MemFuncsDefault()); - os->arena = DN_ArenaFromMemList(&os->mem); - - #if defined(DN_PLATFORM_WIN32) - os->platform_context = DN_ArenaNew(&os->arena, DN_OSW32Core, DN_ZMem_Yes); - #elif defined(DN_PLATFORM_POSIX) || defined(DN_PLATFORM_EMSCRIPTEN) - os->platform_context = DN_ArenaNew(&os->arena, DN_OSPosixCore, DN_ZMem_Yes); - #endif - - #if defined(DN_PLATFORM_WIN32) - DN_OSW32Core *w32 = DN_Cast(DN_OSW32Core *) os->platform_context; - InitializeCriticalSection(&w32->sync_primitive_free_list_mutex); - - QueryPerformanceFrequency(&w32->qpc_frequency); - HMODULE module = LoadLibraryA("kernel32.dll"); - if (module) { - w32->set_thread_description = DN_Cast(DN_OSW32SetThreadDescriptionFunc *) GetProcAddress(module, "SetThreadDescription"); - FreeLibrary(module); - } - - // NOTE: win32 bcrypt - wchar_t const BCRYPT_ALGORITHM[] = L"RNG"; - long /*NTSTATUS*/ init_status = BCryptOpenAlgorithmProvider(&w32->bcrypt_rng_handle, BCRYPT_ALGORITHM, nullptr /*implementation*/, 0 /*flags*/); - if (w32->bcrypt_rng_handle && init_status == 0) - w32->bcrypt_init_success = true; - else - DN_LogErrorF("Failed to initialise Windows secure random number generator, error: %d", init_status); - #else - DN_OS_PosixInit(DN_Cast(DN_OSPosixCore *)os->platform_context); - #endif - } - - os->cpu_report = DN_CPUGetReport(); - - #define DN_CPU_FEAT_XENTRY(label) g_dn_cpu_feature_decl[DN_CPUFeature_##label] = {DN_CPUFeature_##label, DN_Str8Lit(#label)}; - DN_CPU_FEAT_XMACRO - #undef DN_CPU_FEAT_XENTRY - DN_Assert(g_dn_); - #endif - } - - if (DN_BitIsSet(flags, DN_InitFlags_LeakTracker)) { - DN_Assert(dn->os_init); - #if DN_H_WITH_OS - // NOTE: Setup the allocation table with allocation tracking turned off on - // the arena we're using to initialise the table. - dn->leak.alloc_table_mem = DN_MemListFromMemFuncs(DN_Megabytes(1), DN_Kilobytes(512), DN_MemFlags_NoAllocTrack | DN_MemFlags_AllocCanLeak, DN_MemFuncsDefault()); - dn->leak.alloc_table_arena = DN_ArenaFromMemList(&dn->leak.alloc_table_mem); - dn->leak.alloc_table = DN_DSMapInit(&dn->leak.alloc_table_arena, 4096, DN_DSMapFlags_Nil); - #endif - } - - if (DN_BitIsSet(flags, DN_InitFlags_ThreadContext)) { - DN_Assert(dn->os_init); - #if DN_H_WITH_OS - DN_TCInitFromMemFuncs(&dn->main_tc, DN_OS_ThreadID(), args, DN_MemFuncsDefault()); - DN_TCEquip(&dn->main_tc); - #endif - } - - // NOTE: Print out init features - char buf[4096]; - DN_USize buf_size = 0; - if (DN_BitIsSet(flags, DN_InitFlags_LogLibFeatures)) { - DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), "DN initialised:\n"); - #if DN_H_WITH_OS - DN_F32 page_size_kib = dn->os.page_size / 1024.0f; - DN_F32 alloc_granularity_kib = dn->os.alloc_granularity / 1024.0f; - DN_FmtAppendTruncate(buf, - &buf_size, - sizeof(buf), - DN_Str8Lit("..."), - " OS Page/Granularity/Cores: %.0fKiB/%.0fKiB/%u\n", - page_size_kib, - alloc_granularity_kib, - dn->os.logical_processor_count); - #endif - - DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), " Thread Context: "); - if (DN_BitIsSet(flags, DN_InitFlags_ThreadContext)) { - DN_Arena *arena = dn->main_tc.main_arena; - DN_Str8 mem_funcs = DN_Str8Lit(""); - switch (arena->mem->funcs.type) { - case DN_MemFuncsType_Nil: break; - case DN_MemFuncsType_Heap: mem_funcs = DN_Str8Lit("Heap"); break; - case DN_MemFuncsType_Virtual: mem_funcs = DN_Str8Lit("Virtual"); break; - } - DN_Str8x32 main_commit = DN_Str8x32FromByteCountU64Auto(dn->main_tc.main_arena->mem->curr->commit); - DN_Str8x32 main_reserve = DN_Str8x32FromByteCountU64Auto(dn->main_tc.main_arena->mem->curr->reserve); - DN_Str8x32 err_commit = DN_Str8x32FromByteCountU64Auto(dn->main_tc.err_sink.arena->mem->curr->commit); - DN_Str8x32 err_reserve = DN_Str8x32FromByteCountU64Auto(dn->main_tc.err_sink.arena->mem->curr->reserve); - DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), "M %.*s/%.*s", DN_Str8PrintFmt(main_commit), DN_Str8PrintFmt(main_reserve)); - if (dn->main_tc.temp_arenas_count) { - DN_Arena *temp = dn->main_tc.temp_arenas[0]; - DN_Str8x32 temp_commit = DN_Str8x32FromByteCountU64Auto(temp->mem->curr->commit); - DN_Str8x32 temp_reserve = DN_Str8x32FromByteCountU64Auto(temp->mem->curr->reserve); - DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), " T(x%zu) %.*s/%.*s", dn->main_tc.temp_arenas_count, DN_Str8PrintFmt(temp_commit), DN_Str8PrintFmt(temp_reserve)); - } - DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), " E %.*s/%.*s (%.*s)\n", DN_Str8PrintFmt(err_commit), DN_Str8PrintFmt(err_reserve), DN_Str8PrintFmt(mem_funcs)); - } else { - DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), "N/A\n"); - } - - #if DN_HAS_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__) - if (DN_ASAN_POISON) { - DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), " ASAN manual poisoning%s\n", DN_ASAN_VET_POISON ? " (+vet sanity checks)" : ""); - DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), " ASAN poison guard size: %u\n", DN_ASAN_POISON_GUARD_SIZE); - } - #endif - - #if defined(DN_LEAK_TRACKING) - DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), " Allocation leak tracing\n"); - #endif - - #if defined(DN_PLATFORM_EMSCRIPTEN) || defined(DN_PLATFORM_POSIX) - DN_OSPosixCore *posix = DN_Cast(DN_OSPosixCore *)g_dn_->os.platform_context; - DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), " Clock GetTime: %S\n", posix->clock_monotonic_raw ? DN_Str8Lit("CLOCK_MONOTONIC_RAW") : DN_Str8Lit("CLOCK_MONOTONIC")); - #endif - - // TODO(doyle): Add stacktrace feature log - } - - if (DN_BitIsSet(flags, DN_InitFlags_LogCPUFeatures)) { - DN_Assert(dn->os_init); - #if DN_H_WITH_OS - DN_CPUReport const *report = &dn->os.cpu_report; - DN_Str8 brand = DN_Str8TrimWhitespaceAround(DN_Str8FromPtr(report->brand, sizeof(report->brand) - 1)); - DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), " CPU '%.*s' from '%s' detected:\n", DN_Str8PrintFmt(brand), report->vendor); - - DN_USize longest_feature_name = 0; - for (DN_ForIndexU(feature_index, DN_CPUFeature_Count)) { - DN_CPUFeatureDecl feature_decl = g_dn_cpu_feature_decl[feature_index]; - longest_feature_name = DN_Max(longest_feature_name, feature_decl.label.size); - } - - for (DN_ForIndexU(feature_index, DN_CPUFeature_Count)) { - DN_CPUFeatureDecl feature_decl = g_dn_cpu_feature_decl[feature_index]; - bool has_feature = DN_CPUHasFeature(report, feature_decl.value); - DN_FmtAppendTruncate(buf, - &buf_size, - sizeof(buf), - DN_Str8Lit("..."), - " %.*s:%*s%s\n", - DN_Str8PrintFmt(feature_decl.label), - DN_Cast(int)(longest_feature_name - feature_decl.label.size), - "", - has_feature ? "available" : "not available"); - } - #endif - } - - if (buf_size) - DN_LogDebugF("%.*s", DN_Cast(int)buf_size, buf); -} - -DN_API void DN_Set(DN_Core *dn) -{ - g_dn_ = dn; -} - -DN_API DN_Core *DN_Get() -{ - DN_Core *result = g_dn_; - return result; -} - -DN_API void DN_BeginFrame() -{ - #if DN_H_WITH_OS - DN_AtomicSetValue64(&g_dn_->os.mem_allocs_frame, 0); - #endif -} - -#if DN_H_WITH_NET -// DN: Single header generator commented out => #include "Extra/dn_net.cpp" -#define DN_NET_CURL_CPP - -// DN: Single header generator commented out => #if defined(_CLANGD) -// #define DN_H_WITH_OS 1 -// #include "../dn.h" -// #include "dn_net.h" -// #endif - -DN_Str8 DN_NET_Str8FromResponseState(DN_NETResponseState state) -{ - DN_Str8 result = {}; - switch (state) { - case DN_NETResponseState_Nil: result = DN_Str8Lit("Nil"); break; - case DN_NETResponseState_Error: result = DN_Str8Lit("Error"); break; - case DN_NETResponseState_HTTP: result = DN_Str8Lit("HTTP"); break; - case DN_NETResponseState_WSOpen: result = DN_Str8Lit("WS Open"); break; - case DN_NETResponseState_WSText: result = DN_Str8Lit("WS Text"); break; - case DN_NETResponseState_WSBinary: result = DN_Str8Lit("WS Binary"); break; - case DN_NETResponseState_WSClose: result = DN_Str8Lit("WS Close"); break; - case DN_NETResponseState_WSPing: result = DN_Str8Lit("WS Ping"); break; - case DN_NETResponseState_WSPong: result = DN_Str8Lit("WS Pong"); break; - } - return result; -} - -DN_NETRequest *DN_NET_RequestFromHandle(DN_NETRequestHandle handle) -{ - DN_NETRequest *ptr = DN_Cast(DN_NETRequest *) handle.handle; - DN_NETRequest *result = nullptr; - if (ptr && ptr->gen == handle.gen) - result = ptr; - return result; -} - -DN_NETRequestHandle DN_NET_HandleFromRequest(DN_NETRequest *request) -{ - DN_NETRequestHandle result = {}; - if (request) { - result.handle = DN_Cast(DN_UPtr) request; - result.gen = request->gen; - } - return result; -} - -bool DN_NET_ResponseHasFailed(DN_NETResponse const* resp) -{ - bool result = false; - if (resp->type == DN_NETRequestType_HTTP) - result = resp->state == DN_NETResponseState_Error || resp->http_status >= 400; - else - result = resp->state == DN_NETResponseState_Error; - return result; -} - -DN_Str8 DN_NET_Str8DiagnosticFromResponse(DN_NETResponse const* resp, DN_Arena *arena) -{ - DN_TCScratch scratch = DN_TCScratchBeginArena(&arena, 1); - DN_Str8Builder builder = DN_Str8BuilderFromArena(&scratch.arena); - bool resp_failed = DN_NET_ResponseHasFailed(resp); - DN_Str8BuilderAppendF(&builder, "Request %s (%s", resp_failed ? "failed" : "succeeded", resp->type == DN_NETRequestType_HTTP ? "HTTP" : "WS"); - if (resp->type == DN_NETRequestType_HTTP) { - if (resp->http_status) - DN_Str8BuilderAppendF(&builder, " %u", resp->http_status); - } - DN_Str8BuilderAppendF(&builder, ")"); - if (resp->body.size || resp->error_str8.size) { - DN_Str8BuilderAppendRef(&builder, DN_Str8Lit(" with ")); - if (resp->body.size) - DN_Str8BuilderAppendF(&builder, "%.*s", DN_Str8PrintFmt(resp->body)); - if (resp->error_str8.size) - DN_Str8BuilderAppendF(&builder, "%s%.*s", resp->body.size ? ". " : "", DN_Str8PrintFmt(resp->error_str8)); - } - DN_Str8 result = DN_Str8FromStr8BuilderArena(&builder, arena); - DN_TCScratchEnd(&scratch); - return result; -} - -void DN_NET_BaseInit(DN_NETCore *net, char *base, DN_U64 base_size) -{ - net->base = base; - net->base_size = base_size; - net->mem = DN_MemListFromBuffer(net->base, net->base_size, DN_MemFlags_Nil); - net->arena = DN_ArenaFromMemList(&net->mem); - net->completion_sem = DN_OS_SemaphoreInit(0); -} - -DN_NETRequestHandle DN_NET_SetupRequest(DN_NETRequest *request, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args, DN_NETRequestType type) -{ - // NOTE: Setup request - DN_Assert(request); - if (request) { - if (!request->mem.curr) - request->mem = DN_MemListFromVMem(DN_Megabytes(1), DN_Kilobytes(1), DN_MemFlags_Nil); - request->arena = DN_ArenaTempBeginFromMemList(&request->mem); - request->type = type; - request->gen = DN_Max(request->gen + 1, 1); - request->url = DN_Str8FromStr8Arena(url, &request->arena); - request->method = DN_Str8FromStr8Arena(DN_Str8TrimWhitespaceAround(method), &request->arena); - - if (args) { - request->args.flags = args->flags; - request->args.username = DN_Str8FromStr8Arena(args->username, &request->arena); - request->args.password = DN_Str8FromStr8Arena(args->password, &request->arena); - if (type == DN_NETRequestType_HTTP) - request->args.payload = DN_Str8FromStr8Arena(args->payload, &request->arena); - - request->args.headers = DN_ArenaNewArray(&request->arena, DN_Str8, args->headers_size, DN_ZMem_No); - DN_Assert(request->args.headers); - if (request->args.headers) { - for (DN_ForItSize(it, DN_Str8, args->headers, args->headers_size)) - request->args.headers[it.index] = DN_Str8FromStr8Arena(*it.data, &request->arena); - request->args.headers_size = args->headers_size; - } - } - - request->completion_sem = DN_OS_SemaphoreInit(0); - request->start_response_arena = DN_ArenaTempBeginFromArena(&request->arena); - } - - DN_NETRequestHandle result = DN_NET_HandleFromRequest(request); - request->response.request = result; - request->response.type = request->type; - return result; -} - -void DN_NET_EndFinishedRequest(DN_NETRequest *request) -{ - // NOTE: Deallocate the memory used in the request and reset the string builder - DN_ArenaTempEnd(&request->start_response_arena, DN_ArenaReset_Yes); -} -#endif - -#if DN_CPP_WITH_TESTS +#if DN_WITH_TESTS // DN: Single header generator commented out => #include "Extra/dn_tests.cpp" // DN: Single header generator commented out => #if defined(_CLANGD) -// #define DN_H_WITH_OS 1 -// #include "../dn.h" -// #include "../Extra/dn_net.h" -// #include "../Extra/dn_net_curl.h" +// #define DN_WITH_OS 1 +// #include "dn.h" // #include "../Standalone/dn_utest.h" // // #define DN_UNIT_TESTS_WITH_KECCAK @@ -17118,4 +16967,1291 @@ DN_TSTResult DN_TST_RunSuite(DN_TSTPrint print) return result; } -#endif \ No newline at end of file +#endif + +#if DN_WITH_NET +DN_Str8 DN_NET_Str8FromResponseState(DN_NETResponseState state) +{ + DN_Str8 result = {}; + switch (state) { + case DN_NETResponseState_Nil: result = DN_Str8Lit("Nil"); break; + case DN_NETResponseState_Error: result = DN_Str8Lit("Error"); break; + case DN_NETResponseState_HTTP: result = DN_Str8Lit("HTTP"); break; + case DN_NETResponseState_WSOpen: result = DN_Str8Lit("WS Open"); break; + case DN_NETResponseState_WSText: result = DN_Str8Lit("WS Text"); break; + case DN_NETResponseState_WSBinary: result = DN_Str8Lit("WS Binary"); break; + case DN_NETResponseState_WSClose: result = DN_Str8Lit("WS Close"); break; + case DN_NETResponseState_WSPing: result = DN_Str8Lit("WS Ping"); break; + case DN_NETResponseState_WSPong: result = DN_Str8Lit("WS Pong"); break; + } + return result; +} + +DN_NETRequest *DN_NET_RequestFromHandle(DN_NETRequestHandle handle) +{ + DN_NETRequest *ptr = DN_Cast(DN_NETRequest *) handle.handle; + DN_NETRequest *result = nullptr; + if (ptr && ptr->gen == handle.gen) + result = ptr; + return result; +} + +DN_NETRequestHandle DN_NET_HandleFromRequest(DN_NETRequest *request) +{ + DN_NETRequestHandle result = {}; + if (request) { + result.handle = DN_Cast(DN_UPtr) request; + result.gen = request->gen; + } + return result; +} + +bool DN_NET_ResponseHasFailed(DN_NETResponse const* resp) +{ + bool result = false; + if (resp->type == DN_NETRequestType_HTTP) + result = resp->state == DN_NETResponseState_Error || resp->http_status >= 400; + else + result = resp->state == DN_NETResponseState_Error; + return result; +} + +DN_Str8 DN_NET_Str8DiagnosticFromResponse(DN_NETResponse const* resp, DN_Arena *arena) +{ + DN_TCScratch scratch = DN_TCScratchBeginArena(&arena, 1); + DN_Str8Builder builder = DN_Str8BuilderFromArena(&scratch.arena); + bool resp_failed = DN_NET_ResponseHasFailed(resp); + DN_Str8BuilderAppendF(&builder, "Request %s (%s", resp_failed ? "failed" : "succeeded", resp->type == DN_NETRequestType_HTTP ? "HTTP" : "WS"); + if (resp->type == DN_NETRequestType_HTTP) { + if (resp->http_status) + DN_Str8BuilderAppendF(&builder, " %u", resp->http_status); + } + DN_Str8BuilderAppendF(&builder, ")"); + if (resp->body.size || resp->error_str8.size) { + DN_Str8BuilderAppendRef(&builder, DN_Str8Lit(" with ")); + if (resp->body.size) + DN_Str8BuilderAppendF(&builder, "%.*s", DN_Str8PrintFmt(resp->body)); + if (resp->error_str8.size) + DN_Str8BuilderAppendF(&builder, "%s%.*s", resp->body.size ? ". " : "", DN_Str8PrintFmt(resp->error_str8)); + } + DN_Str8 result = DN_Str8FromStr8BuilderArena(&builder, arena); + DN_TCScratchEnd(&scratch); + return result; +} + +void DN_NET_BaseInit(DN_NETCore *net, char *base, DN_U64 base_size) +{ + net->base = base; + net->base_size = base_size; + net->mem = DN_MemListFromBuffer(net->base, net->base_size, DN_MemFlags_Nil); + net->arena = DN_ArenaFromMemList(&net->mem); + net->completion_sem = DN_OS_SemaphoreInit(0); +} + +DN_NETRequestHandle DN_NET_SetupRequest(DN_NETRequest *request, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args, DN_NETRequestType type) +{ + // NOTE: Setup request + DN_Assert(request); + if (request) { + if (!request->mem.curr) + request->mem = DN_MemListFromVMem(DN_Megabytes(1), DN_Kilobytes(1), DN_MemFlags_Nil); + request->arena = DN_ArenaTempBeginFromMemList(&request->mem); + request->type = type; + request->gen = DN_Max(request->gen + 1, 1); + request->url = DN_Str8FromStr8Arena(url, &request->arena); + request->method = DN_Str8FromStr8Arena(DN_Str8TrimWhitespaceAround(method), &request->arena); + + if (args) { + request->args.flags = args->flags; + request->args.username = DN_Str8FromStr8Arena(args->username, &request->arena); + request->args.password = DN_Str8FromStr8Arena(args->password, &request->arena); + if (type == DN_NETRequestType_HTTP) + request->args.payload = DN_Str8FromStr8Arena(args->payload, &request->arena); + + request->args.headers = DN_ArenaNewArray(&request->arena, DN_Str8, args->headers_size, DN_ZMem_No); + DN_Assert(request->args.headers); + if (request->args.headers) { + for (DN_ForItSize(it, DN_Str8, args->headers, args->headers_size)) + request->args.headers[it.index] = DN_Str8FromStr8Arena(*it.data, &request->arena); + request->args.headers_size = args->headers_size; + } + } + + request->completion_sem = DN_OS_SemaphoreInit(0); + request->start_response_arena = DN_ArenaTempBeginFromArena(&request->arena); + } + + DN_NETRequestHandle result = DN_NET_HandleFromRequest(request); + request->response.request = result; + request->response.type = request->type; + return result; +} + +void DN_NET_EndFinishedRequest(DN_NETRequest *request) +{ + // NOTE: Deallocate the memory used in the request and reset the string builder + DN_ArenaTempEnd(&request->start_response_arena, DN_ArenaReset_Yes); +} +#endif // #if DN_WITH_NET + +#if DN_WITH_NET_CURL +struct DN_NETCurlRequest +{ + void *handle; + struct curl_slist *slist; + char error[CURL_ERROR_SIZE]; + bool ws_has_more; + DN_Str8Builder str8_builder; +}; + +enum DN_NETCurlRingEventType +{ + DN_NETCurlRingEventType_Nil, + DN_NETCurlRingEventType_DoRequest, + DN_NETCurlRingEventType_SendWS, + DN_NETCurlRingEventType_ReceivedWSReceipt, + DN_NETCurlRingEventType_DeinitRequest, +}; + +struct DN_NETCurlRingEvent +{ + DN_NETCurlRingEventType type; + DN_NETRequestHandle request; + DN_USize ws_send_size; + DN_NETWSSend ws_send; +}; + +static DN_NETCurlRequest *DN_NET_CurlRequestFromRequest_(DN_NETRequest *req) +{ + DN_NETCurlRequest *result = req ? DN_Cast(DN_NETCurlRequest *) req->context[0] : 0; + return result; +} + +static DN_NETCore *DN_NET_CurlNetFromRequest(DN_NETRequest *req) +{ + DN_NETCore *result = req ? DN_Cast(DN_NETCore *) req->context[1] : 0; + return result; +} + +static bool DN_NET_CurlRequestIsInList(DN_NETRequest const *first, DN_NETRequest const *find) +{ + bool result = false; + for (DN_NETRequest const *it = first; !result && it; it = it->next) + result = find == it; + return result; +} + +static void DN_NET_CurlMarkRequestDone_(DN_NETCore *net, DN_NETRequest *request) +{ + DN_Assert(request); + DN_Assert(net); + // NOTE: The done list in CURL is also used as a place to put websocket requests after removing it + // from the 'ws_list'. By doing this we are stopping the CURL thread from receiving more data on + // the socket as that thread ticks the list of 'ws_list' sockets for data. + // + // Once the caller waited and has received the data from the websocket, the request is put back + // into the 'ws_list' which then lets the CURL thread start receiving more data for that socket. + // + // Since CURL uses a background thread, we do this behind a mutex + DN_NETCurlCore *curl = DN_Cast(DN_NETCurlCore *)net->context; + for (DN_OS_MutexScope(&curl->list_mutex)) { + DN_Assert(DN_NET_CurlRequestIsInList(curl->thread_request_list, request)); + DN_DoublyLLDetach(curl->thread_request_list, request); + DN_DoublyLLAppend(curl->response_list, request); + } + DN_OS_SemaphoreIncrement(&net->completion_sem, 1); + DN_OS_SemaphoreIncrement(&request->completion_sem, 1); +} + +static DN_USize DN_NET_CurlHTTPCallback_(char *payload, DN_USize size, DN_USize count, void *user_data) +{ + DN_NETRequest *req = DN_Cast(DN_NETRequest *) user_data; + DN_NETCurlRequest *curl_req = DN_NET_CurlRequestFromRequest_(req); + DN_USize result = 0; + DN_USize payload_size = size * count; + if (DN_Str8BuilderAppendBytesCopy(&curl_req->str8_builder, payload, payload_size)) + result = payload_size; + return result; +} + +static int32_t DN_NET_CurlThreadEntryPoint_(DN_OSThread *thread) +{ + DN_NETCore *net = DN_Cast(DN_NETCore *) thread->user_context; + DN_NETCurlCore *curl = DN_Cast(DN_NETCurlCore *) net->context; + DN_OS_ThreadSetNameFmt("%.*s", DN_Str8PrintFmt(curl->thread.name)); + + while (!curl->kill_thread) { + DN_TCScratch tmem = DN_TCScratchBeginArena(nullptr, 0); + + // NOTE: Handle events sitting in the ring queue + for (bool dequeue_ring = true; dequeue_ring;) { + DN_NETCurlRingEvent event = {}; + for (DN_OS_MutexScope(&curl->ring_mutex)) { + if (DN_RingHasData(&curl->ring, sizeof(event))) + DN_RingRead(&curl->ring, &event, sizeof(event)); + } + + DN_NETRequest *req = DN_NET_RequestFromHandle(event.request); + DN_NETCurlRequest *curl_req = DN_NET_CurlRequestFromRequest_(req); + switch (event.type) { + case DN_NETCurlRingEventType_Nil: dequeue_ring = false; break; + + case DN_NETCurlRingEventType_DoRequest: { + DN_Assert(req->response.state == DN_NETResponseState_Nil); + DN_Assert(req->type != DN_NETRequestType_Nil); + + // NOTE: Attach it to the CURL thread's request list + for (DN_OS_MutexScope(&curl->list_mutex)) { + DN_Assert(DN_NET_CurlRequestIsInList(curl->request_list, req)); + DN_DoublyLLDetach(curl->request_list, req); + } + DN_DoublyLLAppend(curl->thread_request_list, req); + + // NOTE: Add the connection to CURLM and start ticking it once we finish handling all the + // ring events + CURLMcode multi_add = curl_multi_add_handle(curl->thread_curlm, curl_req->handle); + DN_Assert(multi_add == CURLM_OK); + } break; + + case DN_NETCurlRingEventType_SendWS: { + DN_Str8 payload = {}; + for (DN_OS_MutexScope(&curl->ring_mutex)) { + DN_Assert(DN_RingHasData(&curl->ring, event.ws_send_size)); + payload = DN_Str8AllocArena(event.ws_send_size, DN_ZMem_No, &tmem.arena); + DN_RingRead(&curl->ring, payload.data, payload.size); + } + + DN_U32 curlws_flag = 0; + switch (event.ws_send) { + case DN_NETWSSend_Text: curlws_flag = CURLWS_TEXT; break; + case DN_NETWSSend_Binary: curlws_flag = CURLWS_BINARY; break; + case DN_NETWSSend_Close: curlws_flag = CURLWS_CLOSE; break; + case DN_NETWSSend_Ping: curlws_flag = CURLWS_PING; break; + case DN_NETWSSend_Pong: curlws_flag = CURLWS_PONG; break; + } + + DN_Assert(req->type == DN_NETRequestType_WS); + DN_Assert(req->response.state >= DN_NETResponseState_WSOpen && req->response.state <= DN_NETResponseState_WSPong); + + DN_USize sent = 0; + CURLcode send_result = curl_ws_send(curl_req->handle, payload.data, payload.size, &sent, 0, curlws_flag); + DN_AssertF(send_result == CURLE_OK, "Failed to send: %s", curl_easy_strerror(send_result)); + DN_AssertF(sent == payload.size, "Failed to send all bytes (%zu vs %zu)", sent, payload.size); + } break; + + case DN_NETCurlRingEventType_ReceivedWSReceipt: { + DN_Assert(req->type == DN_NETRequestType_WS); + DN_Assert(req->response.state >= DN_NETResponseState_WSOpen && req->response.state <= DN_NETResponseState_WSPong); + req->response.state = DN_NETResponseState_WSOpen; + + // NOTE: End the temp memory storing the WS data we just read and the user returned to us + // (we got their receipt back). Then restart the temp memory scope for the next websocket + // payload + DN_NET_EndFinishedRequest(req); + req->start_response_arena = DN_ArenaTempBeginFromArena(&req->arena); + curl_req->str8_builder = DN_Str8BuilderFromArena(&req->start_response_arena); + + for (DN_OS_MutexScope(&curl->list_mutex)) { + DN_Assert(DN_NET_CurlRequestIsInList(curl->request_list, req)); + DN_DoublyLLDetach(curl->request_list, req); + } + DN_DoublyLLAppend(curl->thread_request_list, req); + } break; + + case DN_NETCurlRingEventType_DeinitRequest: { + DN_Assert(event.request.handle != 0); + DN_NETRequest *request = DN_Cast(DN_NETRequest *) event.request.handle; + + // NOTE: Detach the request from the deinit list. This brings the request into this + // thread's provenance, no other threads modifying the deinit list will race with us. + for (DN_OS_MutexScope(&curl->list_mutex)) { + DN_Assert(DN_NET_CurlRequestIsInList(curl->deinit_list, request)); + DN_DoublyLLDetach(curl->deinit_list, request); + } + + // NOTE: Now we can modify the request, release resources + DN_NET_EndFinishedRequest(request); + DN_OS_SemaphoreDeinit(&request->completion_sem); + + curl_multi_remove_handle(curl->thread_curlm, curl_req->handle); + curl_slist_free_all(curl_req->slist); + curl_easy_reset(curl_req->handle); + + CURL *copy = curl_req->handle; + *curl_req = {}; + curl_req->handle = copy; + + // NOTE: Zero the struct preserving just the data we need to retain + DN_NETRequest resetter = {}; + resetter.arena = request->arena; + resetter.gen = request->gen; + DN_Memcpy(resetter.context, request->context, sizeof(resetter.context)); + *request = resetter; + + // NOTE: Add it to the free list + for (DN_OS_MutexScope(&curl->list_mutex)) + DN_DoublyLLAppend(curl->free_list, request); + } break; + } + } + + // NOTE: Pump handles + int running_handles = 0; + CURLMcode perform_result = curl_multi_perform(curl->thread_curlm, &running_handles); + if (perform_result != CURLM_OK) + DN_AssertInvalidCodePath; + + // NOTE: Check pump result + for (;;) { + int msgs_in_queue = 0; + CURLMsg *msg = curl_multi_info_read(curl->thread_curlm, &msgs_in_queue); + if (msg) { + // NOTE: Get request handle + DN_NETRequest *req = nullptr; + curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, DN_Cast(void **) & req); + DN_Assert(req); + DN_Assert(DN_NET_CurlRequestIsInList(curl->thread_request_list, req)); + + DN_NETCurlRequest *curl_req = DN_NET_CurlRequestFromRequest_(req); + DN_Assert(curl_req->handle == msg->easy_handle); + + if (msg->data.result == CURLE_OK) { + // NOTE: Get HTTP response code + CURLcode get_result = curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &req->response.http_status); + if (get_result == CURLE_OK) { + if (req->type == DN_NETRequestType_HTTP) { + req->response.state = DN_NETResponseState_HTTP; + } else { + DN_Assert(req->type == DN_NETRequestType_WS); + req->response.state = DN_NETResponseState_WSOpen; + } + } else { + req->response.error_str8 = DN_Str8FromFmtArena(&req->start_response_arena, "Failed to get HTTP response status (CURL %d): %s", msg->data.result, curl_easy_strerror(get_result)); + req->response.state = DN_NETResponseState_Error; + } + } else { + DN_USize curl_extended_error_size = DN_CStr8Size(curl_req->error); + req->response.state = DN_NETResponseState_Error; + req->response.error_str8 = DN_Str8FromFmtArena(&req->start_response_arena, + "HTTP request '%.*s' failed (CURL %d): %s%s%s%s", + DN_Str8PrintFmt(req->url), + msg->data.result, + curl_easy_strerror(msg->data.result), + curl_extended_error_size ? " (" : "", + curl_extended_error_size ? curl_req->error : "", + curl_extended_error_size ? ")" : ""); + } + + if (req->type == DN_NETRequestType_HTTP || req->response.state == DN_NETResponseState_Error) { + // NOTE: Remove the request from the multi handle if we're a HTTP request + // because it typically terminates the connection. In websockets the + // connection remains in the multi-handle to allow you to send and + // receive WS data from it. + // + // If there's an error (either websocket or HTTP) we will also remove the + // connection from the multi handle as it failed. One a connection has + // failed, curl will not poll that connection so there's no point keeping + // it attached to the multi handle. + curl_multi_remove_handle(curl->thread_curlm, msg->easy_handle); + } + + DN_NET_CurlMarkRequestDone_(net, req); + } + + if (msgs_in_queue == 0) + break; + } + + // NOTE: Check websockets + DN_USize ws_count = 0; + for (DN_NETRequest *req = curl->thread_request_list; req; req = req->next) { + DN_Assert(req->type == DN_NETRequestType_WS || req->type == DN_NETRequestType_HTTP); + if (req->type != DN_NETRequestType_WS || !(req->response.state >= DN_NETResponseState_WSOpen && req->response.state <= DN_NETResponseState_WSPong)) + continue; + ws_count++; + const curl_ws_frame *meta = nullptr; + DN_NETCurlRequest *curl_req = DN_NET_CurlRequestFromRequest_(req); + CURLcode receive_result = CURLE_OK; + while (receive_result == CURLE_OK) { + // NOTE: Determine WS payload size received. Note that since we pass in a null pointer CURL + // will set meta->len to 0 and say that there's meta->bytesleft in the next chunk. + DN_USize bytes_read = 0; + receive_result = curl_ws_recv(curl_req->handle, nullptr, 0, &bytes_read, &meta); + if (receive_result != CURLE_OK) + continue; + DN_Assert(meta->len == 0); + + if (meta->flags & CURLWS_TEXT) + req->response.state = DN_NETResponseState_WSText; + + if (meta->flags & CURLWS_BINARY) + req->response.state = DN_NETResponseState_WSBinary; + + if (meta->flags & CURLWS_PING) + req->response.state = DN_NETResponseState_WSPing; + + if (meta->flags & CURLWS_PONG) + req->response.state = DN_NETResponseState_WSPong; + + if (meta->flags & CURLWS_CLOSE) + req->response.state = DN_NETResponseState_WSClose; + + curl_req->ws_has_more = meta->flags & CURLWS_CONT; + if (curl_req->ws_has_more) { + bool is_text_or_binary = req->response.state == DN_NETResponseState_WSText || + req->response.state == DN_NETResponseState_WSBinary; + DN_Assert(is_text_or_binary); + } + + // NOTE: Allocate and read (we use meta->bytesleft as per comment from initial recv) + if (meta->bytesleft) { + DN_Str8 buffer = DN_Str8AllocArena(meta->bytesleft, DN_ZMem_No, &req->start_response_arena); + DN_Assert(buffer.size == DN_Cast(DN_USize)meta->bytesleft); + receive_result = curl_ws_recv(curl_req->handle, buffer.data, buffer.size, &buffer.size, &meta); + DN_Assert(buffer.size == DN_Cast(DN_USize)meta->len); + DN_Str8BuilderAppendRef(&curl_req->str8_builder, buffer); + } + + // NOTE: There are more bytes coming if meta->bytesleft is set, (e.g. the next chunk. We + // just read the current chunk). + // + // > If this is not a complete fragment, the bytesleft field informs about how many + // additional bytes are expected to arrive before this fragment is complete. + curl_req->ws_has_more |= meta && meta->bytesleft > 0; + if (!curl_req->ws_has_more) + break; + } + + // NOTE: curl_ws_recv returns CURLE_GOT_NOTHING if the associated connection is closed. + if (receive_result == CURLE_GOT_NOTHING) + curl_req->ws_has_more = false; + + // NOTE: We read all the possible bytes that CURL has received for this message, but, there are + // more bytes left that we will receive on subsequent calls. We will continue to the next + // request and return back to this one when PumpRequests is called again where hopefully that + // data has arrived. + if (curl_req->ws_has_more) + continue; + + // For CURLE_AGAIN + // + // > Instead of blocking, the function returns CURLE_AGAIN. The correct behavior is then to + // > wait for the socket to signal readability before calling this function again. + // + // In which case we continue ticking the other sockets and eventually exit once all ticked. + // Right after this we wait on the CURLM instance which will wake us up again when there's + // data to be read. + // + // if we received data, e.g. state was set to Text, Binary ... e.t.c we bypass this and + // report it to the user first. When the user waits for the response, they consume the data + // and then that will reinsert it into request list for CURL to read from the socket again. + bool received_data = (req->response.state >= DN_NETResponseState_WSText && req->response.state <= DN_NETResponseState_WSPong); + if (receive_result == CURLE_AGAIN && !received_data) + continue; + + if (!received_data) { + if (receive_result == CURLE_GOT_NOTHING) { + req->response.state = DN_NETResponseState_WSClose; + } else if (receive_result != CURLE_OK) { + DN_USize curl_extended_error_size = DN_CStr8Size(curl_req->error); + req->response.state = DN_NETResponseState_Error; + req->response.error_str8 = DN_Str8FromFmtArena(&req->start_response_arena, + "Websocket receive '%.*s' failed (CURL %d): %s%s%s%s", + DN_Str8PrintFmt(req->url), + receive_result, + curl_easy_strerror(receive_result), + curl_extended_error_size ? " (" : "", + curl_extended_error_size ? curl_req->error : "", + curl_extended_error_size ? ")" : ""); + } + } + + DN_NETRequest *request_copy = req; + req = req->prev; + DN_NET_CurlMarkRequestDone_(net, request_copy); + if (!req) + break; + } + + DN_I32 sleep_time_ms = ws_count > 0 ? 16 : INT32_MAX; + curl_multi_poll(curl->thread_curlm, nullptr, 0, sleep_time_ms, nullptr); + DN_TCScratchEnd(&tmem); + } + + return 0; +} + +DN_NETInterface DN_NET_CurlInterface() +{ + DN_NETInterface result = {}; + result.init = DN_NET_CurlInit; + result.deinit = DN_NET_CurlDeinit; + result.do_http = DN_NET_CurlDoHTTP; + result.do_ws = DN_NET_CurlDoWS; + result.do_ws_send = DN_NET_CurlDoWSSend; + result.wait_for_response = DN_NET_CurlWaitForResponse; + result.wait_for_any_response = DN_NET_CurlWaitForAnyResponse; + return result; +} + +void DN_NET_CurlInit(DN_NETCore *net, char *base, DN_U64 base_size) +{ + DN_NET_BaseInit(net, base, base_size); + DN_NETCurlCore *curl = DN_ArenaNew(&net->arena, DN_NETCurlCore, DN_ZMem_Yes); + net->context = curl; + net->api = DN_NET_CurlInterface(); + + DN_USize arena_bytes_avail = (net->arena.mem->curr->reserve - net->arena.mem->curr->used); + curl->ring.size = arena_bytes_avail / 2; + curl->ring.base = DN_Cast(char *) DN_ArenaAlloc(&net->arena, curl->ring.size, /*align*/ 1, DN_ZMem_Yes); + DN_Assert(curl->ring.base); + + curl->ring_mutex = DN_OS_MutexInit(); + curl->list_mutex = DN_OS_MutexInit(); + curl->thread_curlm = DN_Cast(CURLM *) curl_multi_init(); + + DN_FmtAppend(curl->thread.name.data, &curl->thread.name.size, sizeof(curl->thread.name.data), "NET (CURL)"); + DN_OS_ThreadInit(&curl->thread, DN_NET_CurlThreadEntryPoint_, nullptr, DN_TCInitArgsDefault(), net); +} + +void DN_NET_CurlDeinit(DN_NETCore *net) +{ + DN_NETCurlCore *curl = DN_Cast(DN_NETCurlCore *) net->context; + curl->kill_thread = true; + curl_multi_wakeup(curl->thread_curlm); + DN_OS_ThreadJoin(&curl->thread, DN_TCDeinitArenas_Yes); +} + +static DN_NETRequestHandle DN_NET_CurlDoRequest_(DN_NETCore *net, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args, DN_NETRequestType type) +{ + // NOTE: Allocate the request + DN_NETCurlCore *curl_core = DN_Cast(DN_NETCurlCore *) net->context; + DN_NETRequest *req = nullptr; + DN_NETRequestHandle result = {}; + { + // NOTE: The free list is modified by both the calling thread and the CURLM thread (which ticks + // all the requests in the background for us) + for (DN_OS_MutexScope(&curl_core->list_mutex)) { + req = curl_core->free_list; + DN_DoublyLLDetach(curl_core->free_list, req); + } + + // NOTE None in the free list so allocate one + if (!req) { + DN_OS_MutexLock(&curl_core->list_mutex); + DN_U64 arena_pos = DN_MemListPos(net->arena.mem); + req = DN_ArenaNewZ(&net->arena, DN_NETRequest); + DN_NETCurlRequest *curl_req = DN_ArenaNewZ(&net->arena, DN_NETCurlRequest); + if (!req || !curl_req) { + DN_MemListPopTo(net->arena.mem, arena_pos); + DN_OS_MutexUnlock(&curl_core->list_mutex); + return result; + } + DN_OS_MutexUnlock(&curl_core->list_mutex); + + curl_req->handle = DN_Cast(CURL *) curl_easy_init(); + req->context[0] = DN_Cast(DN_UPtr) curl_req; + } + } + + // NOTE: Setup the request + DN_NETCurlRequest *curl_req = DN_NET_CurlRequestFromRequest_(req); + { + result = DN_NET_SetupRequest(req, url, method, args, type); + req->context[1] = DN_Cast(DN_UPtr) net; + curl_req->str8_builder = DN_Str8BuilderFromArena(&req->start_response_arena); + } + + // NOTE: Setup the request for curl API + { + CURL *curl = curl_req->handle; + curl_easy_setopt(curl, CURLOPT_PRIVATE, req); + curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_req->error); + + // NOTE: Perform request and read all response headers before handing + // control back to app. + curl_easy_setopt(curl, CURLOPT_URL, req->url.data); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + + // NOTE: Setup response handler + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, DN_NET_CurlHTTPCallback_); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, req); + + // NOTE: Assign HTTP headers + for (DN_ForItSize(it, DN_Str8, req->args.headers, req->args.headers_size)) { + DN_Assert(it.data->data[it.data->size] == 0); + curl_req->slist = curl_slist_append(curl_req->slist, it.data->data); + } + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, curl_req->slist); + + // NOTE: Setup handle for protocol + switch (req->type) { + case DN_NETRequestType_Nil: DN_AssertInvalidCodePath; break; + + case DN_NETRequestType_WS: { + curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 2L); + } break; + + case DN_NETRequestType_HTTP: { + DN_Str8 const GET = DN_Str8Lit("GET"); + DN_Str8 const POST = DN_Str8Lit("POST"); + + if (DN_Str8EqInsensitive(req->method, GET)) { + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1); + } else if (DN_Str8EqInsensitive(req->method, POST)) { + curl_easy_setopt(curl, CURLOPT_POST, 1); + if (req->args.payload.size > DN_Gigabytes(2)) + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, req->args.payload.size); + else + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, req->args.payload.size); + curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, req->args.payload.data); + } else { + DN_AssertInvalidCodePathF("Unimplemented"); + } + } break; + } + + // NOTE: Handle basic auth + if (req->args.flags & DN_NETDoHTTPFlags_BasicAuth) { + if (req->args.username.size && req->args.password.size) { + DN_Assert(req->args.username.data[req->args.username.size] == 0); + DN_Assert(req->args.password.data[req->args.password.size] == 0); + curl_easy_setopt(curl, CURLOPT_USERNAME, req->args.username.data); + curl_easy_setopt(curl, CURLOPT_PASSWORD, req->args.password.data); + } + } + } + + // NOTE: Dispatch the request to the CURL thread + { + // NOTE: Immediately add the request to the request list so it happens "atomically" in the + // calling thread. If the calling thread deinitialises this layer before the CURL thread can be + // pre-empted, we can lose track of this request. + for (DN_OS_MutexScope(&curl_core->list_mutex)) + DN_DoublyLLAppend(curl_core->request_list, req); + + // NOTE: Enqueue request to go into CURL's ring queue. The CURL thread will sleep and wait for + // bytes to come in for the request and then dump the response into the done list to be consumed + // via wait for response + DN_NETCurlRingEvent event = {}; + event.type = DN_NETCurlRingEventType_DoRequest; + event.request = result; + for (DN_OS_MutexScope(&curl_core->ring_mutex)) + DN_RingWriteStruct(&curl_core->ring, &event); + + curl_multi_wakeup(curl_core->thread_curlm); + } + + return result; +} + +DN_NETRequestHandle DN_NET_CurlDoHTTP(DN_NETCore *net, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args) +{ + DN_NETRequestHandle result = DN_NET_CurlDoRequest_(net, url, method, args, DN_NETRequestType_HTTP); + return result; +} + +DN_NETRequestHandle DN_NET_CurlDoWSArgs(DN_NETCore *net, DN_Str8 url, DN_NETDoHTTPArgs const *args) +{ + DN_NETRequestHandle result = DN_NET_CurlDoRequest_(net, url, DN_Str8Lit(""), args, DN_NETRequestType_WS); + return result; +} + +DN_NETRequestHandle DN_NET_CurlDoWS(DN_NETCore *net, DN_Str8 url) +{ + DN_NETRequestHandle result = DN_NET_CurlDoWSArgs(net, url, nullptr); + return result; +} + +void DN_NET_CurlDoWSSend(DN_NETRequestHandle handle, DN_Str8 payload, DN_NETWSSend send) +{ + DN_NETRequest *req = DN_NET_RequestFromHandle(handle); + if (!req) + return; + + DN_NETCore *net = DN_NET_CurlNetFromRequest(req); + DN_NETCurlCore *curl = DN_Cast(DN_NETCurlCore *) net->context; + DN_Assert(curl); + + DN_NETCurlRingEvent event = {}; + event.type = DN_NETCurlRingEventType_SendWS; + event.request = handle; + event.ws_send_size = payload.size; + event.ws_send = send; + + for (DN_OS_MutexScope(&curl->ring_mutex)) { + DN_Assert(DN_RingHasSpace(&curl->ring, payload.size)); + DN_RingWriteStruct(&curl->ring, &event); + DN_RingWrite(&curl->ring, payload.data, payload.size); + } + curl_multi_wakeup(curl->thread_curlm); +} + +static DN_NETResponse DN_NET_CurlHandleFinishedRequest_(DN_NETCurlCore *curl, DN_NETRequest *req, DN_Arena *arena) +{ + // NOTE: Generate the response, copy out the strings into the user given memory + DN_NETResponse result = req->response; + DN_NETCurlRequest *curl_req = DN_NET_CurlRequestFromRequest_(req); + { + result.body = DN_Str8FromStr8BuilderArena(&curl_req->str8_builder, arena); + if (result.error_str8.size) + result.error_str8 = DN_Str8FromStr8Arena(result.error_str8, arena); + } + + bool continue_ws_request = false; + if (req->type == DN_NETRequestType_WS && + req->response.state != DN_NETResponseState_Error && + req->response.state != DN_NETResponseState_WSClose) { + continue_ws_request = true; + } + + // NOTE: Put the request into the requisite list + for (DN_OS_MutexScope(&curl->list_mutex)) { + // NOTE: Dequeue the request, it _must_ have been in the response list at this point for it to + // have ben waitable in the first place. + DN_AssertF(DN_NET_CurlRequestIsInList(curl->response_list, req), + "A completed response should only signal the completion semaphore when it's in the response list"); + DN_DoublyLLDetach(curl->response_list, req); + + // NOTE: A websocket that is continuing to get data should go back into the request list because + // there's more data to be received. All other requests need to go into the deinit list (so that + // we keep track of it in the time inbetween it takes for the CURL thread to be scheduled and + // release the CURL handle from CURLM and release resources e.t.c.) + if (continue_ws_request) + DN_DoublyLLAppend(curl->request_list, req); + else + DN_DoublyLLAppend(curl->deinit_list, req); + } + + + // NOTE: Submit the post-request event to the CURL thread + DN_NETCurlRingEvent event = {}; + event.request = DN_NET_HandleFromRequest(req); + if (continue_ws_request) { + event.type = DN_NETCurlRingEventType_ReceivedWSReceipt; + } else { + // NOTE: Deinit _has_ to be sent to the CURL thread because we need to remove the CURL handle + // from the CURLM instance and the CURL thread uses the CURLM instance (e.g. CURLM is not thread + // safe) + event.type = DN_NETCurlRingEventType_DeinitRequest; + } + + for (DN_OS_MutexScope(&curl->ring_mutex)) + DN_RingWriteStruct(&curl->ring, &event); + curl_multi_wakeup(curl->thread_curlm); + + return result; +} + +DN_NETResponse DN_NET_CurlWaitForResponse(DN_NETRequestHandle handle, DN_Arena *arena, DN_U32 timeout_ms) +{ + DN_NETResponse result = {}; + DN_NETRequest *req = DN_NET_RequestFromHandle(handle); + if (!req) + return result; + + DN_NETCore *net = DN_Cast(DN_NETCore *) req->context[1]; + DN_NETCurlCore *curl = DN_Cast(DN_NETCurlCore *) net->context; + DN_Assert(curl); + + DN_OSSemaphoreWaitResult wait = DN_OS_SemaphoreWait(&req->completion_sem, timeout_ms); + if (wait != DN_OSSemaphoreWaitResult_Success) + return result; + + // NOTE: Decrement the global 'request done' completion semaphore since the user consumed the + // request individually. + DN_OSSemaphoreWaitResult net_wait_result = DN_OS_SemaphoreWait(&net->completion_sem, 0 /*timeout_ms*/); + DN_AssertF(net_wait_result == DN_OSSemaphoreWaitResult_Success, "Wait result was: %zu", DN_Cast(DN_USize) net_wait_result); + + // NOTE: Finish handling the response + result = DN_NET_CurlHandleFinishedRequest_(curl, req, arena); + return result; +} + +DN_NETResponse DN_NET_CurlWaitForAnyResponse(DN_NETCore *net, DN_Arena *arena, DN_U32 timeout_ms) +{ + DN_NETCurlCore *curl = DN_Cast(DN_NETCurlCore *) net->context; + DN_Assert(curl); + + DN_NETResponse result = {}; + DN_OSSemaphoreWaitResult req_wait = DN_OS_SemaphoreWait(&net->completion_sem, timeout_ms); + if (req_wait != DN_OSSemaphoreWaitResult_Success) + return result; + + // NOTE: Just grab the handle, handle finished request will dequeue for us + DN_NETRequestHandle handle = {}; + for (DN_OS_MutexScope(&curl->list_mutex)) { + DN_Assert(curl->response_list); + handle = DN_NET_HandleFromRequest(curl->response_list); + } + + // NOTE: Decrement the request's completion semaphore since the user consumed the global semaphore + DN_NETRequest *req = DN_NET_RequestFromHandle(handle); + DN_OSSemaphoreWaitResult net_wait = DN_OS_SemaphoreWait(&req->completion_sem, 0 /*timeout_ms*/); + DN_AssertF(net_wait == DN_OSSemaphoreWaitResult_Success, "Wait result was: %zu", DN_Cast(DN_USize) net_wait); + + // NOTE: Finish handling the response + result = DN_NET_CurlHandleFinishedRequest_(curl, req, arena); + return result; +} +#endif // #if DN_WITH_NET_CURL + +#if DN_WITH_NET_EMSCRIPTEN +#include +#include +#include + +struct DN_NETEmcWSEvent +{ + DN_NETResponseState state; + DN_Str8 payload; + DN_NETEmcWSEvent *next; +}; + +struct DN_NETEmcCore +{ + DN_Pool pool; + DN_NETRequest *response_list; // Responses received that are to be deqeued via wait for response + DN_NETRequest *free_list; // Request pool that new requests will use before allocating +}; + +struct DN_NETEmcRequest +{ + int socket; + DN_NETEmcWSEvent *first_event; + DN_NETEmcWSEvent *last_event; +}; + +DN_NETInterface DN_NET_EmcInterface() +{ + DN_NETInterface result = {}; + result.init = DN_NET_EmcInit; + result.deinit = DN_NET_EmcDeinit; + result.do_http = DN_NET_EmcDoHTTP; + result.do_ws = DN_NET_EmcDoWS; + result.do_ws_send = DN_NET_EmcDoWSSend; + result.wait_for_response = DN_NET_EmcWaitForResponse; + result.wait_for_any_response = DN_NET_EmcWaitForAnyResponse; + return result; +} + +static DN_NETEmcWSEvent *DN_NET_EmcAllocWSEvent_(DN_NETRequest *request) +{ + // NOTE: Allocate the event and attach to the request + DN_NETEmcRequest *emc_request = DN_Cast(DN_NETEmcRequest *) request->context[1]; + DN_NETEmcWSEvent *result = DN_ArenaNew(&request->arena, DN_NETEmcWSEvent, DN_ZMem_Yes); + DN_Assert(result); + if (result) { + if (!emc_request->first_event) + emc_request->first_event = result; + if (emc_request->last_event) + emc_request->last_event->next = result; + emc_request->last_event = result; + } + return result; +} + +static void DN_NET_EmcOnRequestDone_(DN_NETCore *net, DN_NETRequest *request) +{ + // NOTE: This may be call multiple times on the same request if we get multiple responses when we + // yield to the javascript event loop, e.g. the application received multiple WS payloads before + // it waited and consequently consumed the response from the payload. + // + // So if the next pointer is already set, then it should be that the request is already enqueued. + DN_NETEmcCore *emc = DN_Cast(DN_NETEmcCore *) net->context; + if (!request->next && !request->prev && request != emc->response_list) { + request->prev = nullptr; + request->next = emc->response_list; + if (emc->response_list) + emc->response_list->prev = request; + emc->response_list = request; + } + DN_OS_SemaphoreIncrement(&net->completion_sem, 1); + DN_OS_SemaphoreIncrement(&request->completion_sem, 1); +} + +static bool DN_NET_EmcWSOnOpen(int eventType, EmscriptenWebSocketOpenEvent const *event, void *user_data) +{ + DN_NETRequest *req = DN_Cast(DN_NETRequest *) user_data; + DN_NETCore *net = DN_Cast(DN_NETCore *) req->context[0]; + DN_NETEmcWSEvent *net_event = DN_NET_EmcAllocWSEvent_(req); + net_event->state = DN_NETResponseState_WSOpen; + DN_NET_EmcOnRequestDone_(net, req); + return true; +} + +static bool DN_NET_EmcWSOnMessage(int eventType, const EmscriptenWebSocketMessageEvent *event, void *user_data) +{ + DN_NETRequest *req = DN_Cast(DN_NETRequest *) user_data; + DN_NETCore *net = DN_Cast(DN_NETCore *) req->context[0]; + DN_NETEmcWSEvent *net_event = DN_NET_EmcAllocWSEvent_(req); + net_event->state = event->isText ? DN_NETResponseState_WSText : DN_NETResponseState_WSBinary; + if (event->numBytes > 0) + net_event->payload = DN_Str8FromPtrArena(event->data, event->numBytes, &req->arena); + DN_NET_EmcOnRequestDone_(net, req); + return true; +} + +static bool DN_NET_EmcWSOnError(int eventType, EmscriptenWebSocketErrorEvent const *event, void *user_data) +{ + DN_NETRequest *req = DN_Cast(DN_NETRequest *) user_data; + DN_NETCore *net = DN_Cast(DN_NETCore *) req->context[0]; + DN_NETEmcWSEvent *net_event = DN_NET_EmcAllocWSEvent_(req); + net_event->state = DN_NETResponseState_Error; + DN_NET_EmcOnRequestDone_(net, req); + return true; +} + +static bool DN_NET_EmcWSOnClose(int eventType, EmscriptenWebSocketCloseEvent const *event, void *user_data) +{ + DN_NETRequest *req = DN_Cast(DN_NETRequest *) user_data; + DN_NETCore *net = DN_Cast(DN_NETCore *) req->context[0]; + DN_NETEmcWSEvent *net_event = DN_NET_EmcAllocWSEvent_(req); + net_event->state = DN_NETResponseState_WSClose; + net_event->payload = DN_Str8FromFmtArena(&req->arena, "Websocket closed '%.*s': (%u) %s (was %s close)", DN_Str8PrintFmt(req->url), event->code, event->reason, event->wasClean ? "clean" : "unclean"); + DN_NET_EmcOnRequestDone_(net, req); + return true; +} + +static void DN_NET_EmcHTTPSuccessCallback(emscripten_fetch_t *fetch) +{ + DN_NETRequest *req = DN_Cast(DN_NETRequest *) fetch->userData; + DN_NETCore *net = DN_Cast(DN_NETCore *) req->context[0]; + req->response.http_status = fetch->status; + req->response.state = DN_NETResponseState_HTTP; + req->response.body = DN_Str8FromStr8Arena(DN_Str8FromPtr(fetch->data, fetch->numBytes - 1), &req->arena); + DN_NET_EmcOnRequestDone_(net, req); +} + +static void DN_NET_EmcHTTPFailCallback(emscripten_fetch_t *fetch) +{ + DN_NETRequest *req = DN_Cast(DN_NETRequest *) fetch->userData; + DN_NETCore *net = DN_Cast(DN_NETCore *) req->context[0]; + req->response.http_status = fetch->status; + req->response.state = DN_NETResponseState_Error; + DN_NET_EmcOnRequestDone_(net, req); +} + +static void DN_NET_EmcHTTPProgressCallback(emscripten_fetch_t *fetch) +{ +} + +void DN_NET_EmcInit(DN_NETCore *net, char *base, DN_U64 base_size) +{ + DN_NET_BaseInit(net, base, base_size); + DN_NETEmcCore *emc = DN_ArenaNew(&net->arena, DN_NETEmcCore, DN_ZMem_Yes); + emc->pool = DN_PoolFromArena(&net->arena, 0); + net->context = emc; +} + +void DN_NET_EmcDeinit(DN_NETCore *net) +{ + (void)net; + // TODO: Track all the request handles and clean it up +} + +static DN_NETRequest *DN_NET_EmcAllocRequest_(DN_NETCore *net) +{ + // NOTE: Allocate request + DN_NETEmcCore *emc = DN_Cast(DN_NETEmcCore *) net->context; + DN_NETRequest *result = emc->free_list; + if (result) { + emc->free_list = emc->free_list->next; + result->next = nullptr; + DN_Assert(result->prev == nullptr); + if (emc->free_list) { + DN_Assert(emc->free_list->prev == nullptr); + } + } else { + // NOTE: Setup the request's arena here. WASM doesn't have the concept of virtual memory + // so we use malloc to initialise it. + result = DN_ArenaNew(&net->arena, DN_NETRequest, DN_ZMem_Yes); + if (result) { + result->arena = DN_ArenaFromMemList(&result->mem); + } + } + + // NOTE: Setup some emscripten specific data into our request context + if (result) { + result->context[0] = DN_Cast(DN_UPtr) net; + } + + return result; +} + +DN_NETRequestHandle DN_NET_EmcDoHTTP(DN_NETCore *net, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args) +{ + DN_NETRequest *req = DN_NET_EmcAllocRequest_(net); + DN_NETRequestHandle result = DN_NET_SetupRequest(req, url, method, args, DN_NETRequestType_HTTP); + + // NOTE: Setup the HTTP request via Emscripten + emscripten_fetch_attr_t fetch_attribs = {}; + { + DN_Assert(req->args.payload.data[req->args.payload.size] == 0); + DN_Assert(req->url.data[req->url.size] == 0); + + // NOTE: Setup request for emscripten + emscripten_fetch_attr_init(&fetch_attribs); + + fetch_attribs.requestData = req->args.payload.data; + fetch_attribs.requestDataSize = req->args.payload.size; + DN_Assert(req->method.size < DN_ArrayCountU(fetch_attribs.requestMethod)); + DN_Memcpy(fetch_attribs.requestMethod, req->method.data, req->method.size); + fetch_attribs.requestMethod[req->method.size] = 0; + + // NOTE: Assign HTTP headers + if (req->args.headers_size) { + char **headers = DN_ArenaNewArray(&req->start_response_arena, char *, req->args.headers_size + 1, DN_ZMem_Yes); + for (DN_ForItSize(it, DN_Str8, req->args.headers, req->args.headers_size)) { + DN_Assert(it.data->data[it.data->size] == 0); + headers[it.index] = it.data->data; + } + fetch_attribs.requestHeaders = headers; + } + + // NOTE: Handle basic auth + if (req->args.flags & DN_NETDoHTTPFlags_BasicAuth) { + if (req->args.username.size && req->args.password.size) { + DN_Assert(req->args.username.data[req->args.username.size] == 0); + DN_Assert(req->args.password.data[req->args.password.size] == 0); + fetch_attribs.withCredentials = true; + fetch_attribs.userName = req->args.username.data; + fetch_attribs.password = req->args.password.data; + } + } + + // NOTE: It would be nice to use EMSCRIPTEN_FETCH_STREAM_DATA however + // emscripten has this note on the current version I'm using that this is + // only supported in Firefox so this is a no-go. + // + // > If passed, the intermediate streamed bytes will be passed in to the + // > onprogress() handler. If not specified, the onprogress() handler will still + // > be called, but without data bytes. Note: Firefox only as it depends on + // > 'moz-chunked-arraybuffer'. + fetch_attribs.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY; + fetch_attribs.onsuccess = DN_NET_EmcHTTPSuccessCallback; + fetch_attribs.onerror = DN_NET_EmcHTTPFailCallback; + fetch_attribs.onprogress = DN_NET_EmcHTTPProgressCallback; + fetch_attribs.userData = req; + } + + // NOTE: Dispatch the asynchronous fetch + emscripten_fetch(&fetch_attribs, req->url.data); + return result; +} + +DN_NETRequestHandle DN_NET_EmcDoWS(DN_NETCore *net, DN_Str8 url) +{ + DN_Assert(emscripten_websocket_is_supported()); + DN_NETRequest *req = DN_NET_EmcAllocRequest_(net); + DN_NETRequestHandle result = DN_NET_SetupRequest(req, url, /*method=*/DN_Str8Lit(""), /*args=*/nullptr, DN_NETRequestType_WS); + if (!req) + return result; + + // NOTE: Setup some emscripten specific data into our request context + req->context[1] = DN_Cast(DN_UPtr) DN_ArenaNew(&req->start_response_arena, DN_NETEmcRequest, DN_ZMem_Yes); + + // NOTE: Create the websocket request and dispatch it via emscripten + EmscriptenWebSocketCreateAttributes attr; + emscripten_websocket_init_create_attributes(&attr); + attr.url = req->url.data; + + DN_NETEmcRequest *emc_request = DN_Cast(DN_NETEmcRequest *) req->context[1]; + emc_request->socket = emscripten_websocket_new(&attr); + DN_Assert(emc_request->socket > 0); + emscripten_websocket_set_onopen_callback(emc_request->socket, /*userData=*/req, DN_NET_EmcWSOnOpen); + emscripten_websocket_set_onmessage_callback(emc_request->socket, /*userData=*/req, DN_NET_EmcWSOnMessage); + emscripten_websocket_set_onerror_callback(emc_request->socket, /*userData=*/req, DN_NET_EmcWSOnError); + emscripten_websocket_set_onclose_callback(emc_request->socket, /*userData=*/req, DN_NET_EmcWSOnClose); + + return result; +} + +void DN_NET_EmcDoWSSend(DN_NETRequestHandle handle, DN_Str8 data, DN_NETWSSend send) +{ + DN_AssertF(send == DN_NETWSSend_Binary || send == DN_NETWSSend_Text || send == DN_NETWSSend_Close, + "Unimplemented, Emscripten only supports some of the available operations"); + int result = 0; + DN_NETRequest *request_ptr = DN_Cast(DN_NETRequest *) handle.handle; + if (request_ptr && request_ptr->gen == handle.gen) { + DN_Assert(request_ptr->type == DN_NETRequestType_WS); + DN_NETEmcRequest *emc_request = DN_Cast(DN_NETEmcRequest *) request_ptr->context[1]; + switch (send) { + default: DN_AssertInvalidCodePath; break; + case DN_NETWSSend_Text: { + DN_U64 pos = DN_MemListPos(request_ptr->start_response_arena.mem); + DN_Str8 data_null_terminated = DN_Str8FromStr8Arena(data, &request_ptr->start_response_arena); + result = emscripten_websocket_send_utf8_text(emc_request->socket, data_null_terminated.data); + DN_MemListPopTo(request_ptr->arena.mem, pos); + } break; + + case DN_NETWSSend_Binary: { + result = emscripten_websocket_send_binary(emc_request->socket, data.data, data.size); + } break; + + case DN_NETWSSend_Close: { + result = emscripten_websocket_close(emc_request->socket, 0, nullptr); + } break; + } + } + // TODO: Handle result, the header file doesn't really elucidate what this result value is + (void)result; +} + +static DN_NETResponse DN_NET_EmcHandleFinishedRequest_(DN_NETCore *net, DN_NETEmcCore *emc, DN_NETRequestHandle handle, DN_NETRequest *request, DN_Arena *arena) +{ + // NOTE: Generate the response, copy out the strings into the user given memory + DN_NETEmcRequest *emc_request = DN_Cast(DN_NETEmcRequest *) request->context[1]; + DN_NETResponse result = request->response; + bool end_request = true; + bool dequeue_request = true; + if (request->type == DN_NETRequestType_HTTP) { + result.body = DN_Str8FromStr8Arena(result.body, arena); + } else { + // NOTE: Get emscripten contexts + DN_NETEmcWSEvent *emc_event = emc_request->first_event; + DN_Assert(emc_event); + + DN_AssertF((emc_event->state >= DN_NETResponseState_WSOpen && emc_event->state <= DN_NETResponseState_WSPong) || emc_event->state == DN_NETResponseState_Error, + "emc_event=%p", emc_event); + + // NOTE: Build the result + result.state = emc_event->state; + result.request = handle; + result.body = DN_Str8FromStr8Arena(emc_event->payload, arena); + + // NOTE: Advance the event list + { + if (emc_request->first_event == emc_request->last_event) { + emc_request->last_event = emc_request->last_event->next; + DN_Assert(emc_request->first_event->next == emc_request->last_event); + } + emc_request->first_event = emc_event->next; + + // NOTE: If there's still an event on the request then we do not dequeue the request from the + // response list. The user can still "wait" for a response to read more data from it. + if (emc_request->first_event) + dequeue_request = false; + } + + if (result.state != DN_NETResponseState_WSClose) + end_request = false; + } + + // NOTE: Remove request from the response list which is doubly-linked + if (dequeue_request) { + if (request->prev) { + DN_AssertF(request->prev->next == request, "next=%p vs request=%p", request->prev->next, request); + request->prev->next = request->next; + } + + if (request->next) { + DN_AssertF(request->next->prev == request, "prev=%p vs request=%p", request->next->prev, request); + request->next->prev = request->prev; + } + + if (request == emc->response_list) + emc->response_list = emc->response_list->next; + + request->prev = nullptr; + request->next = nullptr; + DN_Assert(emc_request->first_event == nullptr); + DN_Assert(emc_request->last_event == nullptr); + + // NOTE: Deallocate the memory used in the request and reset the string builder (as all + // payload(s) have been read from the request). + if (!end_request) + DN_ArenaTempEnd(&request->start_response_arena, DN_ArenaReset_Yes); + } + + if (end_request) { + DN_NET_EndFinishedRequest(request); + emscripten_websocket_delete(emc_request->socket); + emc_request->socket = 0; + + DN_NETEmcCore *emc = DN_Cast(DN_NETEmcCore *) net->context; + request->next = emc->free_list; + request->prev = nullptr; + emc->free_list = request; + } + + return result; +} + +static DN_OSSemaphoreWaitResult DN_NET_EmcSemaphoreWait_(DN_OSSemaphore *sem, DN_U32 timeout_ms) +{ + // NOTE: In emscripten you can't just block on the semaphore with 'timeout_ms' because it needs + // to yield to the javascript's event loop otherwise the fetching step cannot progress. Instead + // we use a timeout of 0 to just immediately check if the semaphore has been signalled, if not, + // then we yield to the event loop by calling sleep. + // + // Once yielded, fetch will execute and eventually in the callback it will signal the semaphore + // where it'll return and we can break out of the simulated "timeout". + DN_OSSemaphoreWaitResult result = {}; + DN_U32 timeout_remaining_ms = timeout_ms; + DN_F64 begin_ms = emscripten_get_now(); + for (;;) { + result = DN_OS_SemaphoreWait(sem, 0); + if (result == DN_OSSemaphoreWaitResult_Success) + break; + if (timeout_remaining_ms <= 0) + break; + + emscripten_sleep(100 /*ms*/); + DN_F64 end_ms = emscripten_get_now(); + DN_USize duration_ms = DN_Cast(DN_USize)(end_ms - begin_ms); + timeout_remaining_ms = timeout_remaining_ms >= duration_ms ? timeout_remaining_ms - duration_ms : 0; + begin_ms = end_ms; + } + return result; +} + +DN_NETResponse DN_NET_EmcWaitForResponse(DN_NETRequestHandle handle, DN_Arena *arena, DN_U32 timeout_ms) +{ + DN_NETResponse result = {}; + DN_NETRequest *request_ptr = DN_Cast(DN_NETRequest *) handle.handle; + if (request_ptr && request_ptr->gen == handle.gen) { + DN_NETCore *net = DN_Cast(DN_NETCore *) request_ptr->context[0]; + DN_NETEmcCore *emc = DN_Cast(DN_NETEmcCore *) net->context; + DN_Assert(emc); + DN_OSSemaphoreWaitResult wait = DN_NET_EmcSemaphoreWait_(&request_ptr->completion_sem, timeout_ms); + if (wait != DN_OSSemaphoreWaitResult_Success) + return result; + + result = DN_NET_EmcHandleFinishedRequest_(net, emc, handle, request_ptr, arena); + + // NOTE: Decrement the global 'request done' completion semaphore since the user consumed the + // request individually. + DN_OSSemaphoreWaitResult net_wait_result = DN_OS_SemaphoreWait(&net->completion_sem, 0 /*timeout_ms*/); + DN_AssertF(net_wait_result == DN_OSSemaphoreWaitResult_Success, "Wait result was: %zu", DN_Cast(DN_USize) net_wait_result); + } + return result; +} + +DN_NETResponse DN_NET_EmcWaitForAnyResponse(DN_NETCore *net, DN_Arena *arena, DN_U32 timeout_ms) +{ + DN_NETEmcCore *emc = DN_Cast(DN_NETEmcCore *) net->context; + DN_Assert(emc); + + DN_NETResponse result = {}; + DN_OSSemaphoreWaitResult wait = DN_NET_EmcSemaphoreWait_(&net->completion_sem, timeout_ms); + if (wait != DN_OSSemaphoreWaitResult_Success) + return result; + + DN_AssertF(emc->response_list, + "This should be set otherwise we bumped the completion sem without queueing into the " + "done list or we forgot to wait on the global semaphore after a request finished"); + + // NOTE: Decrement the request's completion semaphore since the user consumed the global semaphore + DN_NETRequest *request_ptr = emc->response_list; + DN_OSSemaphoreWaitResult net_wait_result = DN_OS_SemaphoreWait(&request_ptr->completion_sem, 0 /*timeout_ms*/); + DN_AssertF(net_wait_result == DN_OSSemaphoreWaitResult_Success, "Wait result was: %zu", DN_Cast(DN_USize) net_wait_result); + + DN_NETRequestHandle request = {}; + request.handle = DN_Cast(DN_UPtr) request_ptr; + request.gen = request_ptr->gen; + result = DN_NET_EmcHandleFinishedRequest_(net, emc, request, request_ptr, arena); + + return result; +} +#endif // #if DN_WITH_NET_EMSCRIPTEN \ No newline at end of file diff --git a/Single-Header/dn_single_header.h b/Single-Header/dn_single_header.h index 059dd6c..e1b763e 100644 --- a/Single-Header/dn_single_header.h +++ b/Single-Header/dn_single_header.h @@ -1,4 +1,4 @@ -// Generated by the DN single header generator 2026-06-23 21:12:54 +// Generated by the DN single header generator 2026-06-23 23:16:53 #if !defined(DN_H) #define DN_H @@ -15,19 +15,22 @@ // // The following is a single translation unit example: /* - #define DN_H_WITH_OS 1 - #define DN_H_WITH_CORE 1 - #define DN_H_WITH_HELPERS 1 - #define DN_H_WITH_ASYNC 1 - #define DN_H_WITH_NET 1 - #include "dn.h" + #define DN_WITH_OS 1 + #define DN_WITH_NET 0 + #define DN_WITH_NET_CURL 0 + #define DN_WITH_NET_EMSCRIPTEN 0 + #include "dn.h" - #define DN_CPP_WITH_TESTS 1 - #define DN_CPP_WITH_DEMO 1 - #include "dn.cpp" + #define DN_CPP_WITH_TESTS 1 + #include "dn.cpp" + + int main() + { + DN_Core core = {}; + DN_Init(&core, DN_InitFlags_Nil, DN_TCInitArgsDefault()); + return 0; + } */ -// Then initialise the library at runtime by calling DN_Init(...). The library is laid out as: -// // - The base layer (dn_base.h) which provides primitives that do not require a host operating // system (e.g. freestanding) such as string manipulation, compiler intrinsics and containers. // This layer is unconditionallly always available by compiling with this library. @@ -39,6 +42,24 @@ // NOTE: Configuration +// NOTE: OS layer +// Enable the operating system layer which enables thread context, file system, threads, e.t.c: +// +// #define DN_WITH_OS 1 + +// NOTE: Networking layer +// Enable the networking layer (pre-requisite that the OS layer is enabled) that allows sending +// HTTP/Websocket requests either using CURL or Emscripten's networking APIs. +// +// #define DN_WITH_NET 1 +// #define DN_WITH_NET_CURL 1 +// #define DN_WITH_NET_EMSCRIPTEN 1 +// +// For CURL, ensure that you the target application links to a libcurl and that the +// #include is visible before this translation unit. +// +// For Emscripten ensure that this translation unit is compiled with `emcc` to have it enabled. + // NOTE: Platform Target // Define one of the following directives to configure this library to compile for that // platform. By default, the library will auto-detect the current host platform and select that @@ -65,7 +86,7 @@ // the functions in the library do not export an entry into the linking table. // translation units. -// NOTE: Disabling the in-built (if #define DN_H_WITH_OS 1) +// NOTE: Disabling the in-built (if #define DN_WITH_OS 1) // If you are building DN for the Windows platform, is a large legacy header that // applications have to include to use Windows APIs. By default this library uses a replacement // header for all the Windows functions that it uses in the OS layer removing the need to @@ -197,14 +218,10 @@ // // #define DN_STR8_AVX512F 1 -// DN: Single header generator commented out => #include "Base/dn_base.h" -#if !defined(DN_BASE_H) -#define DN_BASE_H - // DN: Single header generator commented out => #if defined(_CLANGD) +// #define DN_WITH_OS 1 // #define DN_STR8_AVX512F 1 // #define DN_PARANOIA_LEVEL 1 -// #include "../dn.h" // #endif // NOTE: Compiler identification @@ -1887,6 +1904,49 @@ template struct DN_DSMapResult T *value; }; +enum DN_LeakAllocFlag +{ + DN_LeakAllocFlag_Freed = 1 << 0, + DN_LeakAllocFlag_LeakPermitted = 1 << 1, +}; + +struct DN_LeakAlloc +{ + void *ptr; // 8 Pointer to the allocation being tracked + DN_USize size; // 16 Size of the allocation + DN_USize freed_size; // 24 Store the size of the allocation when it is freed + DN_Str8 stack_trace; // 40 Stack trace at the point of allocation + DN_Str8 freed_stack_trace; // 56 Stack trace of where the allocation was freed + DN_U16 flags; // 72 Bit flags from `DN_LeakAllocFlag` +}; + +// NOTE: We aim to keep the allocation record as light as possible as memory tracking can get +// expensive. Enforce that there is no unexpected padding. +DN_StaticAssert(sizeof(DN_LeakAlloc) == 64 || sizeof(DN_LeakAlloc) == 32); // NOTE: 64 bit vs 32 bit pointers respectively + +struct DN_LeakTracker +{ + DN_DSMap alloc_table; + DN_TicketMutex alloc_table_mutex; + DN_MemList alloc_table_mem; + DN_Arena alloc_table_arena; + DN_U64 alloc_table_bytes_allocated_for_stack_traces; +}; + +typedef DN_USize DN_InitFlags; +enum DN_InitFlags_ +{ + DN_InitFlags_Nil = 0, + + // NOTE: Query the OS for information and enable functionality that requires the OS (like virtual + // memory APIs, mutexes, secure RNG) as well as per-thread persistent and scratch allocators. + DN_InitFlags_OS = (1 << 0), + DN_InitFlags_LeakTracker = (1 << 1) | DN_InitFlags_OS, + DN_InitFlags_LogLibFeatures = (1 << 2), + DN_InitFlags_LogCPUFeatures = (1 << 3) | DN_InitFlags_OS, + DN_InitFlags_LogAllFeatures = DN_InitFlags_LogLibFeatures | DN_InitFlags_LogCPUFeatures, +}; + #if !defined(DN_STB_SPRINTF_HEADER_ONLY) #define STB_SPRINTF_IMPLEMENTATION #define STB_SPRINTF_STATIC @@ -1896,7 +1956,8 @@ DN_MSVC_WARNING_PUSH DN_MSVC_WARNING_DISABLE(4505) // Unused function warning DN_GCC_WARNING_PUSH DN_GCC_WARNING_DISABLE(-Wunused-function) -// DN: Single header generator commented out => #include "../External/stb_sprintf.h" +// NOTE: This depends on DN_Str8 because we've customised the library +// DN: Single header generator commented out => #include "External/stb_sprintf.h" // NOTE: Additional *unofficial* changes // - Adding STBSP__ASAN to STBSP__PUBLICDEC so that MSVC's ASAN does not trigger (https://github.com/nothings/stb/pull/1350) // - Adding __attribute__((no_sanitize("undefined"))) to STBSP__ASAN for CLANG so that UBSAN does not trigger (https://github.com/nothings/stb/pull/1477) @@ -3828,6 +3889,333 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. DN_GCC_WARNING_POP DN_MSVC_WARNING_POP +#if DN_WITH_OS +#include // operator new + +#if !defined(DN_OS_WIN32) || defined(DN_OS_WIN32_USE_PTHREADS) + #include + #include +#endif + +#if defined(DN_PLATFORM_POSIX) || defined(DN_PLATFORM_EMSCRIPTEN) + #include // errno + #include // O_RDONLY ... etc + #include // ioctl + #include // mmap + #include // getrandom + #include // stat + #include // pid_t + #include // waitpid + #include // clock_gettime, nanosleep + #include // access, gettid, write + + #if !defined(DN_PLATFORM_EMSCRIPTEN) + #include // FICLONE + #include // sendfile + #endif +#endif + +extern DN_CPUFeatureDecl g_dn_cpu_feature_decl[DN_CPUFeature_Count]; +struct DN_OSTimer /// Record time between two time-points using the OS's performance counter. +{ + DN_U64 start; + DN_U64 end; +}; + +// NOTE: DN_OSFile +enum DN_OSPathInfoType +{ + DN_OSPathInfoType_Unknown, + DN_OSPathInfoType_Directory, + DN_OSPathInfoType_File, +}; + +struct DN_OSPathInfo +{ + bool exists; + DN_OSPathInfoType type; + DN_U64 create_time_in_s; + DN_U64 last_write_time_in_s; + DN_U64 last_access_time_in_s; + DN_U64 size; +}; + +struct DN_OSDirIterator +{ + void *handle; + DN_Str8 file_name; + char buffer[512]; +}; + +// NOTE: R/W Stream API +struct DN_OSFileRead +{ + bool success; + DN_USize bytes_read; +}; + +struct DN_OSFile +{ + bool error; + void *handle; +}; + +enum DN_OSFileOpen +{ + DN_OSFileOpen_CreateAlways, // Create file if it does not exist, otherwise, zero out the file and open + DN_OSFileOpen_OpenIfExist, // Open file at path only if it exists + DN_OSFileOpen_OpenAlways, // Open file at path, create file if it does not exist +}; + +typedef DN_U32 DN_OSFileAccess; + +enum DN_OSFileAccess_ +{ + DN_OSFileAccess_Read = 1 << 0, + DN_OSFileAccess_Write = 1 << 1, + DN_OSFileAccess_Execute = 1 << 2, + DN_OSFileAccess_AppendOnly = 1 << 3, // This flag cannot be combined with any other access mode + DN_OSFileAccess_ReadWrite = DN_OSFileAccess_Read | DN_OSFileAccess_Write, + DN_OSFileAccess_All = DN_OSFileAccess_ReadWrite | DN_OSFileAccess_Execute | DN_OSFileAccess_AppendOnly, +}; + +// NOTE: DN_OSPath +#if !defined(DN_OSPathSeperator) + #if defined(DN_OS_WIN32) + #define DN_OSPathSeperator "\\" + #else + #define DN_OSPathSeperator "/" + #endif + #define DN_OSPathSeperatorString DN_Str8Lit(DN_OSPathSeperator) +#endif + +struct DN_OSPathLink +{ + DN_Str8 string; + DN_OSPathLink *next; + DN_OSPathLink *prev; +}; + +struct DN_OSPath +{ + bool has_prefix_path_separator; + DN_OSPathLink *head; + DN_OSPathLink *tail; + DN_USize string_size; + DN_U16 links_size; +}; + +// NOTE: DN_OSExec +typedef DN_U32 DN_OSExecFlags; + +enum DN_OSExecFlags_ +{ + DN_OSExecFlags_Nil = 0, + DN_OSExecFlags_SaveStdout = 1 << 0, + DN_OSExecFlags_SaveStderr = 1 << 1, + DN_OSExecFlags_SaveOutput = DN_OSExecFlags_SaveStdout | DN_OSExecFlags_SaveStderr, + DN_OSExecFlags_MergeStderrToStdout = 1 << 2 | DN_OSExecFlags_SaveOutput, +}; + +struct DN_OSExecAsyncHandle +{ + DN_OSExecFlags exec_flags; + DN_U32 os_error_code; + DN_U32 exit_code; + void *process; + void *stdout_read; + void *stdout_write; + void *stderr_read; + void *stderr_write; +}; + +struct DN_OSExecResult +{ + bool finished; + DN_Str8 stdout_text; + DN_Str8 stderr_text; + DN_U32 os_error_code; + DN_U32 exit_code; +}; + +struct DN_OSExecArgs +{ + DN_OSExecFlags flags; + DN_Str8 working_dir; + DN_Str8Slice environment; +}; + +// NOTE: DN_OSSemaphore +DN_U32 const DN_OS_SEMAPHORE_INFINITE_TIMEOUT = UINT32_MAX; + +struct DN_OSSemaphore +{ + DN_U64 handle; +}; + +struct DN_OSBarrier +{ + DN_U64 handle; +}; + +enum DN_OSSemaphoreWaitResult +{ + DN_OSSemaphoreWaitResult_Failed, + DN_OSSemaphoreWaitResult_Success, + DN_OSSemaphoreWaitResult_Timeout, +}; + +struct DN_OSMutex +{ + DN_U64 handle; +}; + +struct DN_OSConditionVariable +{ + DN_U64 handle; +}; + +// NOTE: DN_OSThread +typedef DN_I32(DN_OSThreadFunc)(struct DN_OSThread *); +struct DN_OSThreadLane +{ + DN_USize index; + DN_USize count; + DN_OSBarrier barrier; + void* shared_mem; +}; + +struct DN_OSThreadLaneway +{ + DN_OSThread* threads; + DN_USize threads_count; + DN_UPtr* shared_mem; + DN_OSBarrier barrier; +}; + +struct DN_OSThread +{ + DN_Str8x64 name; + DN_TCCore context; + DN_OSThreadLane lane; + bool is_lane_set; + void *handle; + DN_U64 thread_id; + void *user_context; + DN_OSThreadFunc *func; + DN_OSSemaphore init_semaphore; + DN_TCInitArgs tc_init_args; +}; + +struct DN_OSCore +{ + DN_CPUReport cpu_report; + + // NOTE: Logging + bool log_to_file; // Output logs to file as well as standard out + DN_OSFile log_file; // TODO(dn): Hmmm, how should we do this... ? + DN_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 + DN_TicketMutex log_mutex; + + // NOTE: OS + DN_U32 logical_processor_count; + DN_U32 page_size; + DN_U32 alloc_granularity; + + // NOTE: Memory + // Total OS mem allocs in lifetime of program (e.g. malloc, VirtualAlloc, HeapAlloc ...). This + // only includes allocations routed through the library such as the growing nature of arenas or + // using the memory allocation routines in the library like DN_OS_MemCommit and so forth. + DN_U64 vmem_allocs_total; + DN_U64 vmem_allocs_frame; // Total OS virtual memory allocs since the last 'DN_Core_FrameBegin' was invoked + DN_U64 mem_allocs_total; + DN_U64 mem_allocs_frame; // Total OS heap allocs since the last 'DN_Core_FrameBegin' was invoked + + DN_MemList mem; + DN_Arena arena; + void* platform_context; +}; + +struct DN_OSDiskSpace +{ + bool success; + DN_U64 avail; + DN_U64 size; +}; + +enum DN_OSAsyncPriority +{ + DN_OSAsyncPriority_Low, + DN_OSAsyncPriority_High, + DN_OSAsyncPriority_Count, +}; + +struct DN_OSAsyncCore +{ + DN_OSMutex ring_mutex; + DN_OSConditionVariable ring_write_cv; + DN_OSSemaphore worker_sem; + DN_Ring ring; + DN_OSThread *threads; + DN_U32 thread_count; + DN_U32 busy_threads; + DN_U32 join_threads; +}; + +struct DN_OSAsyncWorkArgs +{ + DN_OSThread *thread; + void *input; +}; + +typedef void(DN_OSAsyncWorkFunc)(DN_OSAsyncWorkArgs work_args); + +struct DN_OSAsyncWork +{ + DN_OSAsyncWorkFunc *func; + void *input; + void *output; +}; + +struct DN_OSAsyncTask +{ + bool queued; + DN_OSAsyncWork work; + DN_OSSemaphore completion_sem; +}; +#endif // #if DN_WITH_OS + +struct DN_Core +{ + DN_InitFlags init_flags; + DN_TCCore main_tc; + DN_USize mem_allocs_frame; + DN_LeakTracker leak; + + DN_LogType log_level_to_show_from; + DN_LogPrintFunc* print_func; + void* print_func_context; + + bool os_init; + #if DN_WITH_OS + DN_OSCore os; + #endif +}; + +// NOTE: Library initialisation. This must be called before using the library once to setup TLS and +// query the OS for information (such as page size) for tuning allocations and so forth. The caller +// should pass in a zero-initialised `DN_Core` that should persist for program lifetime. +// +// A reference to the core passed in is kept which can be queried with `DN_Get` and may be used +// internally by the library. If you have an application that has the concept of frames, you may +// optionally call `DN_BeginFrame` which resets some metrics that are counted for example it tracks +// the number of memory allocations for the current frame and that counter can be reset. +DN_API void DN_Init (DN_Core *dn, DN_InitFlags flags, DN_TCInitArgs args); +DN_API void DN_Set (DN_Core *dn); +DN_API DN_Core* DN_Get (); +DN_API void DN_BeginFrame (); + DN_API bool DN_VerifyArgsF (DN_VerifyType type, bool expr, DN_CallSite call_site, DN_Str8 expr_str8, char const *fmt, ...); DN_API bool DN_VerifyArgs (DN_VerifyType type, bool expr, DN_CallSite call_site, DN_Str8 expr_str8); @@ -3853,38 +4241,37 @@ DN_API DN_U64 DN_AlignUpPowerOfTwoU64 DN_API DN_U32 DN_AlignUpPowerOfTwoU32 (DN_U32 val); DN_API void DN_ByteSwapU64Ptr (DN_U8* dest, DN_U64 src); -#define DN_ByteSwap64(val) ( \ - (((((DN_U64)(val)) >> 56) & 0xFF) << 0) | \ - (((((DN_U64)(val)) >> 48) & 0xFF) << 8) | \ - (((((DN_U64)(val)) >> 40) & 0xFF) << 16) | \ - (((((DN_U64)(val)) >> 32) & 0xFF) << 24) | \ - (((((DN_U64)(val)) >> 24) & 0xFF) << 32) | \ - (((((DN_U64)(val)) >> 16) & 0xFF) << 40) | \ - (((((DN_U64)(val)) >> 8) & 0xFF) << 48) | \ - (((((DN_U64)(val)) >> 0) & 0xFF) << 56) \ - ) -#define DN_ByteSwap32(val) ( \ - (((((DN_U32)(val)) >> 24) & 0xFF) << 0) | \ - (((((DN_U32)(val)) >> 16) & 0xFF) << 8) | \ - (((((DN_U32)(val)) >> 8) & 0xFF) << 16) | \ - (((((DN_U32)(val)) >> 0) & 0xFF) << 24) \ - ) -#define DN_ByteSwap24(val) ( \ - (((((DN_U32)(val)) >> 16) & 0xFF) << 0) | \ - (((((DN_U32)(val)) >> 8) & 0xFF) << 8) | \ - (((((DN_U32)(val)) >> 0) & 0xFF) << 16) \ - ) -#define DN_ByteSwap16(val) ( \ - (((((DN_U16)(val)) >> 8) & 0xFF) << 0) | \ - (((((DN_U16)(val)) >> 0) & 0xFF) << 8) \ - ) +#define DN_ByteSwap64(val) ( \ + ((((DN_U64)(val) >> 56) & 0xFF) << 0) | \ + ((((DN_U64)(val) >> 48) & 0xFF) << 8) | \ + ((((DN_U64)(val) >> 40) & 0xFF) << 16) | \ + ((((DN_U64)(val) >> 32) & 0xFF) << 24) | \ + ((((DN_U64)(val) >> 24) & 0xFF) << 32) | \ + ((((DN_U64)(val) >> 16) & 0xFF) << 40) | \ + ((((DN_U64)(val) >> 8) & 0xFF) << 48) | \ + ((((DN_U64)(val) >> 0) & 0xFF) << 56) \ + ) +#define DN_ByteSwap32(val) ( \ + ((((DN_U32)(val) >> 24) & 0xFF) << 0) | \ + ((((DN_U32)(val) >> 16) & 0xFF) << 8) | \ + ((((DN_U32)(val) >> 8) & 0xFF) << 16) | \ + ((((DN_U32)(val) >> 0) & 0xFF) << 24) \ + ) +#define DN_ByteSwap24(val) ( \ + ((((DN_U32)(val) >> 16) & 0xFF) << 0) | \ + ((((DN_U32)(val) >> 8) & 0xFF) << 8) | \ + ((((DN_U32)(val) >> 0) & 0xFF) << 16) \ + ) +#define DN_ByteSwap16(val) ( \ + ((((DN_U16)(val) >> 8) & 0xFF) << 0) | \ + ((((DN_U16)(val) >> 0) & 0xFF) << 8) \ + ) #if defined(DN_64_BIT) - #define DN_ByteSwapUSize(val) DN_ByteSwap64(val) + #define DN_ByteSwapUSize(val) DN_ByteSwap64(val) #else - #define DN_ByteSwapUSize(val) DN_ByteSwap32(val) + #define DN_ByteSwapUSize(val) DN_ByteSwap32(val) #endif - DN_API DN_CPUIDResult DN_CPUID (DN_CPUIDArgs args); DN_API DN_USize DN_CPUHasFeatureArray (DN_CPUReport const *report, DN_CPUFeatureQuery *features, DN_USize features_size); DN_API bool DN_CPUHasFeature (DN_CPUReport const *report, DN_CPUFeature feature); @@ -4005,23 +4392,26 @@ DN_API void DN_MemListTempEnd #define DN_MemListNewCopy(arena, T, src) (T *)DN_MemListCopy(arena, (src), sizeof(T), alignof(T)) #define DN_MemListNewArrayCopy(arena, T, src, count) (T *)DN_MemListCopy(arena, (src), sizeof(T) * (count), alignof(T)) -// NOTE: `Arena`'s in this codebase are thin-layers over `MemList` but additionally provide -// use-after-free (UAF) protection when using temporary memory regions (e.g. thread context scratch -// `TCScratch` or `Temp[Begin|End]` family of functions). +// NOTE: Arena +// Overview +// `Arena`'s in this codebase are thin-layers over `MemList` but additionally provide +// use-after-free (UAF) protection when using temporary memory regions (e.g. thread context +// scratch `TCScratch` or `Temp[Begin|End]` family of functions). // -// These arenas associate themselves with the temporary memory region they begin in if it is -// constructed using the `Temp[Begin|End]` family of functions (TCScratch implicitly call these for -// you before handing you the arena). If you attempt to allocate from a different arena bound with a -// different temporary memory region than the active one an assertion is triggered. This protection -// is gated by the presence of the preprocessor definition `#define DN_ARENA_TEMP_MEM_UAF_GUARD 1`. +// These arenas associate themselves with the temporary memory region they begin in if it is +// constructed using the `Temp[Begin|End]` family of functions (TCScratch implicitly call these +// for you before handing you the arena). If you attempt to allocate from a different arena bound +// with a different temporary memory region than the active one an assertion is triggered. This +// protection is gated by the presence of the preprocessor definition +// `#define DN_ARENA_TEMP_MEM_UAF_GUARD 1`. // -// Without the preprocessor definition UAF protection is compiled out (e.g. no-op). UAF protection -// is also not enabled if you use `ArenaFromMemList` which simply sets up a plain arena that -// forwards all calls into the `MemList` API. +// Without the preprocessor definition UAF protection is compiled out (e.g. no-op). UAF protection +// is also not enabled if you use `ArenaFromMemList` which simply sets up a plain arena that +// forwards all calls into the `MemList` API. // -// To get UAF protection, all allocations _must_ go through the `Arena` API, using the `MemList` -// field directly in the `Arena` will bypass these checks and lead to unusual behaviour. If you want -// to forgo any of this infrastructure store and use the `MemList` directly in your codebase. +// To get UAF protection, all allocations _must_ go through the `Arena` API, using the `MemList` +// field directly in the `Arena` will bypass these checks and lead to unusual behaviour. If you +// want to forgo any of this infrastructure store and use the `MemList` directly in your codebase. // // UAF Example /* @@ -5468,35 +5858,6 @@ DN_BinarySearchResult DN_BinarySearch(T const *array, return result; } -enum DN_LeakAllocFlag -{ - DN_LeakAllocFlag_Freed = 1 << 0, - DN_LeakAllocFlag_LeakPermitted = 1 << 1, -}; - -struct DN_LeakAlloc -{ - void *ptr; // 8 Pointer to the allocation being tracked - DN_USize size; // 16 Size of the allocation - DN_USize freed_size; // 24 Store the size of the allocation when it is freed - DN_Str8 stack_trace; // 40 Stack trace at the point of allocation - DN_Str8 freed_stack_trace; // 56 Stack trace of where the allocation was freed - DN_U16 flags; // 72 Bit flags from `DN_LeakAllocFlag` -}; - -// NOTE: We aim to keep the allocation record as light as possible as memory tracking can get -// expensive. Enforce that there is no unexpected padding. -DN_StaticAssert(sizeof(DN_LeakAlloc) == 64 || sizeof(DN_LeakAlloc) == 32); // NOTE: 64 bit vs 32 bit pointers respectively - -struct DN_LeakTracker -{ - DN_DSMap alloc_table; - DN_TicketMutex alloc_table_mutex; - DN_MemList alloc_table_mem; - DN_Arena alloc_table_arena; - DN_U64 alloc_table_bytes_allocated_for_stack_traces; -}; - DN_API void DN_LeakTrackAlloc_ (DN_LeakTracker *leak, void *ptr, DN_USize size, bool alloc_can_leak); DN_API void DN_LeakTrackDealloc_(DN_LeakTracker *leak, void *ptr); DN_API void DN_LeakDump_ (DN_LeakTracker *leak); @@ -5511,6 +5872,279 @@ DN_API void DN_LeakDump_ (DN_LeakTracker *leak); #define DN_LeakDump(leak) do { } while (0) #endif +#if DN_WITH_OS +DN_API DN_MemFuncs DN_MemFuncsFromType (DN_MemFuncsType type); +DN_API DN_MemFuncs DN_MemFuncsDefault (); +DN_API DN_MemList DN_MemListFromHeap (DN_U64 size, DN_MemFlags flags); +DN_API DN_MemList DN_MemListFromVMem (DN_U64 reserve, DN_U64 commit, DN_MemFlags flags); +DN_API DN_Arena DN_ArenaFromHeap (DN_U64 wize, DN_MemFlags flags); +DN_API DN_Arena DN_ArenaFromVMem (DN_U64 reserve, DN_U64 commit, DN_MemFlags flags); + +DN_API DN_Str8 DN_Str8FromHeapF (DN_FMT_ATTRIB char const *fmt, ...); +DN_API DN_Str8 DN_Str8FromHeap (DN_USize size, DN_ZMem z_mem); +DN_API DN_Str8 DN_Str8BuilderBuildFromHeap (DN_Str8Builder const *builder); + +DN_API void DN_OS_LogPrint (DN_LogTypeParam type, void *user_data, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API void DN_OS_SetLogPrintFuncToOS (); + +DN_API void * DN_OS_MemReserve (DN_USize size, DN_MemCommit commit, DN_MemPage page_flags); +DN_API bool DN_OS_MemCommit (void *ptr, DN_USize size, DN_U32 page_flags); +DN_API void DN_OS_MemDecommit (void *ptr, DN_USize size); +DN_API void DN_OS_MemRelease (void *ptr, DN_USize size); +DN_API int DN_OS_MemProtect (void *ptr, DN_USize size, DN_U32 page_flags); + +DN_API void * DN_OS_MemAlloc (DN_USize size, DN_ZMem z_mem); +DN_API void DN_OS_MemDealloc (void *ptr); + +DN_API DN_Date DN_OS_DateLocalTimeNow (); +DN_API DN_Str8x32 DN_OS_DateLocalTimeStr8Now (char date_separator = '-', char hms_separator = ':'); +DN_API DN_Str8x32 DN_OS_DateLocalTimeStr8 (DN_Date time, char date_separator = '-', char hms_separator = ':'); +DN_API DN_U64 DN_OS_DateUnixTimeNs (); +#define DN_OS_DateUnixTimeUs() (DN_OS_DateUnixTimeNs() / 1000) +#define DN_OS_DateUnixTimeMs() (DN_OS_DateUnixTimeNs() / (1000 * 1000)) +#define DN_OS_DateUnixTimeS() (DN_OS_DateUnixTimeNs() / (1000 * 1000 * 1000)) +DN_API DN_U64 DN_OS_DateUnixTimeSFromLocalDate (DN_Date date); +DN_API DN_U64 DN_OS_DateLocalUnixTimeSFromUnixTimeS (DN_U64 unix_ts_s); + +DN_API void DN_OS_GenBytesSecure (void *buffer, DN_U32 size); +DN_API bool DN_OS_SetEnvVar (DN_Str8 name, DN_Str8 value); +DN_API DN_OSDiskSpace DN_OS_DiskSpace (DN_Str8 path); +DN_API DN_Str8 DN_OS_EXEPath (DN_Arena *arena); +DN_API DN_Str8 DN_OS_EXEDir (DN_Arena *arena); +DN_API void DN_OS_SleepMs (DN_UInt milliseconds); + +DN_API DN_U64 DN_OS_PerfCounterNow (); +DN_API DN_U64 DN_OS_PerfCounterFrequency (); +DN_API DN_F64 DN_OS_PerfCounterS (DN_U64 begin, uint64_t end); +DN_API DN_F64 DN_OS_PerfCounterMs (DN_U64 begin, uint64_t end); +DN_API DN_F64 DN_OS_PerfCounterUs (DN_U64 begin, uint64_t end); +DN_API DN_F64 DN_OS_PerfCounterNs (DN_U64 begin, uint64_t end); +DN_API DN_OSTimer DN_OS_TimerBegin (); +DN_API void DN_OS_TimerEnd (DN_OSTimer *timer); +DN_API DN_F64 DN_OS_TimerS (DN_OSTimer timer); +DN_API DN_F64 DN_OS_TimerMs (DN_OSTimer timer); +DN_API DN_F64 DN_OS_TimerUs (DN_OSTimer timer); +DN_API DN_F64 DN_OS_TimerNs (DN_OSTimer timer); +DN_API DN_U64 DN_OS_EstimateTSCPerSecond (uint64_t duration_ms_to_gauge_tsc_frequency); + +DN_API bool DN_OS_FileCopy (DN_Str8 src, DN_Str8 dest, bool overwrite, DN_ErrSink *err); +DN_API bool DN_OS_FileMove (DN_Str8 src, DN_Str8 dest, bool overwrite, DN_ErrSink *err); + +DN_API DN_OSFile DN_OS_FileOpen (DN_Str8 path, DN_OSFileOpen open_mode, DN_OSFileAccess access, DN_ErrSink *err); +DN_API DN_OSFileRead DN_OS_FileRead (DN_OSFile *file, void *buffer, DN_USize size, DN_ErrSink *err); +DN_API bool DN_OS_FileWritePtr (DN_OSFile *file, void const *data, DN_USize size, DN_ErrSink *err); +DN_API bool DN_OS_FileWrite (DN_OSFile *file, DN_Str8 buffer, DN_ErrSink *err); +DN_API bool DN_OS_FileWriteFV (DN_OSFile *file, DN_ErrSink *err, DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API bool DN_OS_FileWriteF (DN_OSFile *file, DN_ErrSink *err, DN_FMT_ATTRIB char const *fmt, ...); +DN_API bool DN_OS_FileFlush (DN_OSFile *file, DN_ErrSink *err); +DN_API void DN_OS_FileClose (DN_OSFile *file); + +DN_API DN_Str8 DN_OS_FileReadAll (DN_Allocator allocator, DN_Str8 path, DN_ErrSink *err); +DN_API DN_Str8 DN_OS_FileReadAllArena (DN_Arena *arena, DN_Str8 path, DN_ErrSink *err); +DN_API DN_Str8 DN_OS_FileReadAllPool (DN_Pool *pool, DN_Str8 path, DN_ErrSink *err); + +DN_API bool DN_OS_FileWriteAll (DN_Str8 path, DN_Str8 buffer, DN_ErrSink *err); +DN_API bool DN_OS_FileWriteAllFV (DN_Str8 path, DN_ErrSink *err, DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API bool DN_OS_FileWriteAllF (DN_Str8 path, DN_ErrSink *err, DN_FMT_ATTRIB char const *fmt, ...); +DN_API bool DN_OS_FileWriteAllSafe (DN_Str8 path, DN_Str8 buffer, DN_ErrSink *err); +DN_API bool DN_OS_FileWriteAllSafeFV (DN_Str8 path, DN_ErrSink *err, DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API bool DN_OS_FileWriteAllSafeF (DN_Str8 path, DN_ErrSink *err, DN_FMT_ATTRIB char const *fmt, ...); + +DN_API DN_Str8 DN_OS_Str8FromPathInfoType (DN_OSPathInfoType type); +DN_API DN_OSPathInfo DN_OS_PathInfo (DN_Str8 path); +DN_API bool DN_OS_PathIsOlderThan (DN_Str8 file, DN_Str8 check_against); +DN_API bool DN_OS_PathDelete (DN_Str8 path); +DN_API bool DN_OS_PathIsFile (DN_Str8 path); +DN_API bool DN_OS_PathIsDir (DN_Str8 path); +DN_API bool DN_OS_PathMakeDir (DN_Str8 path); +DN_API bool DN_OS_PathIterateDir (DN_Str8 path, DN_OSDirIterator *it); + +DN_API bool DN_OS_PathAddRef (DN_Arena *arena, DN_OSPath *fs_path, DN_Str8 path); +DN_API bool DN_OS_PathAdd (DN_Arena *arena, DN_OSPath *fs_path, DN_Str8 path); +DN_API bool DN_OS_PathAddF (DN_Arena *arena, DN_OSPath *fs_path, DN_FMT_ATTRIB char const *fmt, ...); +DN_API bool DN_OS_PathPop (DN_OSPath *fs_path); +DN_API DN_Str8 DN_OS_PathBuildWithSeparator (DN_Arena *arena, DN_OSPath const *fs_path, DN_Str8 path_separator); +DN_API DN_Str8 DN_OS_PathTo (DN_Arena *arena, DN_Str8 path, DN_Str8 path_separtor); +DN_API DN_Str8 DN_OS_PathToF (DN_Arena *arena, DN_Str8 path_separator, DN_FMT_ATTRIB char const *fmt, ...); +DN_API DN_Str8 DN_OS_Path (DN_Arena *arena, DN_Str8 path); +DN_API DN_Str8 DN_OS_PathF (DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, ...); + +#define DN_OS_PathBuildFwdSlash(allocator, fs_path) DN_OS_PathBuildWithSeparator(allocator, fs_path, DN_Str8Lit("/")) +#define DN_OS_PathBuildBackSlash(allocator, fs_path) DN_OS_PathBuildWithSeparator(allocator, fs_path, DN_Str8Lit("\\")) +#define DN_OS_PathBuild(allocator, fs_path) DN_OS_PathBuildWithSeparator(allocator, fs_path, DN_OSPathSeparatorString) + +DN_API void DN_OS_Exit (int32_t exit_code); +DN_API DN_OSExecResult DN_OS_ExecPump (DN_OSExecAsyncHandle handle, char *stdout_buffer, size_t *stdout_size, char *stderr_buffer, size_t *stderr_size, DN_U32 timeout_ms, DN_ErrSink *err); +DN_API DN_OSExecResult DN_OS_ExecWait (DN_OSExecAsyncHandle handle, DN_Arena *arena, DN_ErrSink *err); +DN_API DN_OSExecAsyncHandle DN_OS_ExecAsync (DN_Str8Slice cmd_line, DN_OSExecArgs *args, DN_ErrSink *err); +DN_API DN_OSExecResult DN_OS_Exec (DN_Str8Slice cmd_line, DN_OSExecArgs *args, DN_Arena *arena, DN_ErrSink *err); +DN_API DN_OSExecResult DN_OS_ExecOrAbort (DN_Str8Slice cmd_line, DN_OSExecArgs *args, DN_Arena *arena); + +DN_API DN_OSSemaphore DN_OS_SemaphoreInit (DN_U32 initial_count); +DN_API void DN_OS_SemaphoreDeinit (DN_OSSemaphore *semaphore); +DN_API void DN_OS_SemaphoreIncrement (DN_OSSemaphore *semaphore, DN_U32 amount); +DN_API DN_OSSemaphoreWaitResult DN_OS_SemaphoreWait (DN_OSSemaphore *semaphore, DN_U32 timeout_ms); + +DN_API DN_OSBarrier DN_OS_BarrierInit (DN_U32 thread_count); +DN_API void DN_OS_BarrierDeinit (DN_OSBarrier *barrier); +DN_API void DN_OS_BarrierWait (DN_OSBarrier *barrier); + +DN_API DN_OSMutex DN_OS_MutexInit (); +DN_API void DN_OS_MutexDeinit (DN_OSMutex *mutex); +DN_API void DN_OS_MutexLock (DN_OSMutex *mutex); +DN_API void DN_OS_MutexUnlock (DN_OSMutex *mutex); +#define DN_OS_MutexScope(mutex) DN_DeferLoop(DN_OS_MutexLock(mutex), DN_OS_MutexUnlock(mutex)) + +DN_API DN_OSConditionVariable DN_OS_ConditionVariableInit (); +DN_API void DN_OS_ConditionVariableDeinit (DN_OSConditionVariable *cv); +DN_API bool DN_OS_ConditionVariableWait (DN_OSConditionVariable *cv, DN_OSMutex *mutex, DN_U64 sleep_ms); +DN_API bool DN_OS_ConditionVariableWaitUntil (DN_OSConditionVariable *cv, DN_OSMutex *mutex, DN_U64 end_ts_ms); +DN_API void DN_OS_ConditionVariableSignal (DN_OSConditionVariable *cv); +DN_API void DN_OS_ConditionVariableBroadcast (DN_OSConditionVariable *cv); + +DN_API bool DN_OS_ThreadInit (DN_OSThread *thread, DN_OSThreadFunc *func, DN_OSThreadLane *lane, DN_TCInitArgs tc_init_args, void *user_context); +DN_API bool DN_OS_ThreadJoin (DN_OSThread *thread, DN_TCDeinitArenas deinit_arenas); +DN_API DN_U32 DN_OS_ThreadID (); +DN_API void DN_OS_ThreadSetNameFmt (char const *fmt, ...); + +// NOTE: Thread lanes provide an abstraction to represent the concept of programming a CPU like a +// GPU, e.g. SIMT (Single Instruction Multiple Threads). The lane terminology is popularised by Ryan +// Fleury. SIMT is formally defined as +// +// Threads are grouped into warps/wavefronts (typically 32 or 64 threads) that execute the same +// instruction in lockstep, but each thread operates on different data and maintains its own state +// +// The individual threads in a wavefront on the CPU side are colloquially dubbed "lanes" and a +// thread lane here contains the necessary state to facilitate this such as the current index in the +// wavefront and synchronisation primitives to coordinate the different lanes together. +// +// The idea is to write code in a single-threaded manner (linear execution) but across multiple +// threads so that the default is all execution paths are inherently multi-threaded by default. Opt +// out of parallelism instead of opt in. This optimises for the trend of core counts increasing +// whilst clock counts remain static. +// +// A laneway is a helper function to initialise the number of requested OS threads/lanes upfront and +// setup the required synchronisation primitives. It can then be dispatched all the threads which +// start executing the `entry_point` in parallel. +// +// API +// DN_OS_ThreadLaneSync +// A blocking call to synchronise the program-counter of all other lanes in the laneway to this +// function call invocation (using an OS barrier). Optionally pass in the pointer to a pointer +// `ptr_to_share` to broadcast the pointer from one lanes to the others. The lane that wishes +// to broadcast the pointer must have a non-null pointer, all other lanes must pass in a +// non-null pointer. A typical use case might look like: +/* + DN_OSThreadLane *lane = DN_OS_TCThreadLane(); // Get lane from current (t)hread (c)context + + // NOTE: Allocate buffer in lane 0 + DN_U8 *buffer = nullptr; + if (lane->index == 0) + buffer = DN_ArenaNewArray(DN_TCMainArena(), DN_U8, DN_Gigabytes(1), DN_ZMem_No); + + // NOTE: Lane 0 broadcasts the `buffer` pointer to lane 1..N + DN_OS_ThreadLaneSync(lane, &buffer); + + // NOTE: We use LaneRange to divide the buffer into equal sized chunks that each lane can + // write into without clobbering over each other. + DN_V2USize range = DN_OS_ThreadLaneRange(lane, DN_Gigabytes(1)); + for (DN_USize index = range.begin; index < range.end; index++) { buffer[index] = index; } +*/ +// In this example, lane 0 will allocate a 1GiB buffer pass in a `buffer` to +// DN_OS_ThreadLaneSync` that is non-null. Lanes 1->N will skip the branch (because their lanes +// indexes are 1..N) and invoke `DN_OS_ThreadLaneSync` with a nullptr `buffer`. After the +// blocking call is complete, lanes 0->N will now have synchronised the `buffer` pointer and all +// lanes point to the 1GiB range allocated in lane 0's allocator. +// +// Additionally we demonstrate `DN_OS_ThreadLaneRange` which does math behind the scenes to +// divide the buffer up and assign each lane their own indices in the buffer that they can work +// on in parallel without clobbering each others work. +// +// DN_OS_ThreadLaneRange +// Calculates the range of values the current lane in the laneway should execute. For example if +// you have 128 items and 16 threads each lane will receive the following `DN_V2USize` range: +// Lane 0 => [0, 8) +// Lane 1 => [8, 16) +// ... +// Lane 16 => [120, 128) +DN_API DN_OSThreadLane DN_OS_ThreadLaneInit (DN_USize index, DN_USize thread_count, DN_OSBarrier barrier, DN_UPtr *share_mem); +DN_API void DN_OS_ThreadLaneSync (DN_OSThreadLane *lane, void **ptr_to_share); +DN_API DN_V2USize DN_OS_ThreadLaneRange (DN_OSThreadLane const *lane, DN_USize values_count); + +DN_API DN_OSThreadLaneway DN_OS_ThreadLanewayFromArgs (DN_OSThread* threads, DN_USize threads_count, DN_UPtr* shared_mem); +DN_API DN_OSThreadLaneway DN_OS_ThreadLanewayFromArena (DN_USize threads_count, DN_Arena* arena); +DN_API void DN_OS_ThreadLanewayDispatch (DN_OSThreadLaneway *laneway, DN_OSThreadFunc *entry_point, DN_TCInitArgs tc_init_args, void *user_context); +DN_API void DN_OS_ThreadLanewayJoin (DN_OSThreadLaneway *laneway, DN_TCDeinitArenas deinit_arenas); + +DN_API DN_OSThreadLane* DN_OS_TCThreadLane (); +DN_API void DN_OS_TCThreadLaneSync (void **ptr_to_share); +DN_API DN_OSThreadLane DN_OS_TCThreadLaneEquip (DN_OSThreadLane lane); + + +DN_API void DN_OS_AsyncInit (DN_OSAsyncCore *async, char *base, DN_USize base_size, DN_OSThread *threads, DN_U32 threads_size); +DN_API void DN_OS_AsyncDeinit (DN_OSAsyncCore *async); +DN_API bool DN_OS_AsyncQueueWork(DN_OSAsyncCore *async, DN_OSAsyncWorkFunc *func, void *input, DN_U64 wait_time_ms); +DN_API DN_OSAsyncTask DN_OS_AsyncQueueTask(DN_OSAsyncCore *async, DN_OSAsyncWorkFunc *func, void *input, DN_U64 wait_time_ms); +DN_API bool DN_OS_AsyncWaitTask (DN_OSAsyncTask *task, DN_U32 timeout_ms); + +// NOTE: DN_OSPrint +enum DN_OSPrintDest +{ + DN_OSPrintDest_Out, + DN_OSPrintDest_Err, +}; + +// NOTE: Print Macros +#define DN_OS_PrintOut(string) DN_OS_Print(DN_OSPrintDest_Out, string) +#define DN_OS_PrintOutF(fmt, ...) DN_OS_PrintF(DN_OSPrintDest_Out, fmt, ##__VA_ARGS__) +#define DN_OS_PrintOutFV(fmt, args) DN_OS_PrintFV(DN_OSPrintDest_Out, fmt, args) + +#define DN_OS_PrintOutStyle(style, string) DN_OS_PrintStyle(DN_OSPrintDest_Out, style, string) +#define DN_OS_PrintOutFStyle(style, fmt, ...) DN_OS_PrintFStyle(DN_OSPrintDest_Out, style, fmt, ##__VA_ARGS__) +#define DN_OS_PrintOutFVStyle(style, fmt, args, ...) DN_OS_PrintFVStyle(DN_OSPrintDest_Out, style, fmt, args) + +#define DN_OS_PrintOutLn(string) DN_OS_PrintLn(DN_OSPrintDest_Out, string) +#define DN_OS_PrintOutLnF(fmt, ...) DN_OS_PrintLnF(DN_OSPrintDest_Out, fmt, ##__VA_ARGS__) +#define DN_OS_PrintOutLnFV(fmt, args) DN_OS_PrintLnFV(DN_OSPrintDest_Out, fmt, args) + +#define DN_OS_PrintOutLnStyle(style, string) DN_OS_PrintLnStyle(DN_OSPrintDest_Out, style, string); +#define DN_OS_PrintOutLnFStyle(style, fmt, ...) DN_OS_PrintLnFStyle(DN_OSPrintDest_Out, style, fmt, ##__VA_ARGS__) +#define DN_OS_PrintOutLnFVStyle(style, fmt, args) DN_OS_PrintLnFVStyle(DN_OSPrintDest_Out, style, fmt, args); + +#define DN_OS_PrintErr(string) DN_OS_Print(DN_OSPrintDest_Err, string) +#define DN_OS_PrintErrF(fmt, ...) DN_OS_PrintF(DN_OSPrintDest_Err, fmt, ##__VA_ARGS__) +#define DN_OS_PrintErrFV(fmt, args) DN_OS_PrintFV(DN_OSPrintDest_Err, fmt, args) + +#define DN_OS_PrintErrStyle(style, string) DN_OS_PrintStyle(DN_OSPrintDest_Err, style, string) +#define DN_OS_PrintErrFStyle(style, fmt, ...) DN_OS_PrintFStyle(DN_OSPrintDest_Err, style, fmt, ##__VA_ARGS__) +#define DN_OS_PrintErrFVStyle(style, fmt, args, ...) DN_OS_PrintFVStyle(DN_OSPrintDest_Err, style, fmt, args) + +#define DN_OS_PrintErrLn(string) DN_OS_PrintLn(DN_OSPrintDest_Err, string) +#define DN_OS_PrintErrLnF(fmt, ...) DN_OS_PrintLnF(DN_OSPrintDest_Err, fmt, ##__VA_ARGS__) +#define DN_OS_PrintErrLnFV(fmt, args) DN_OS_PrintLnFV(DN_OSPrintDest_Err, fmt, args) + +#define DN_OS_PrintErrLnStyle(style, string) DN_OS_PrintLnStyle(DN_OSPrintDest_Err, style, string); +#define DN_OS_PrintErrLnFStyle(style, fmt, ...) DN_OS_PrintLnFStyle(DN_OSPrintDest_Err, style, fmt, ##__VA_ARGS__) +#define DN_OS_PrintErrLnFVStyle(style, fmt, args) DN_OS_PrintLnFVStyle(DN_OSPrintDest_Err, style, fmt, args); + +// NOTE: Print +DN_API void DN_OS_Print (DN_OSPrintDest dest, DN_Str8 string); +DN_API void DN_OS_PrintF (DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, ...); +DN_API void DN_OS_PrintFV (DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, va_list args); + +DN_API void DN_OS_PrintStyle (DN_OSPrintDest dest, DN_LogStyle style, DN_Str8 string); +DN_API void DN_OS_PrintFStyle (DN_OSPrintDest dest, DN_LogStyle style, DN_FMT_ATTRIB char const *fmt, ...); +DN_API void DN_OS_PrintFVStyle (DN_OSPrintDest dest, DN_LogStyle style, DN_FMT_ATTRIB char const *fmt, va_list args); + +DN_API void DN_OS_PrintLn (DN_OSPrintDest dest, DN_Str8 string); +DN_API void DN_OS_PrintLnF (DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, ...); +DN_API void DN_OS_PrintLnFV (DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, va_list args); + +DN_API void DN_OS_PrintLnStyle (DN_OSPrintDest dest, DN_LogStyle style, DN_Str8 string); +DN_API void DN_OS_PrintLnFStyle (DN_OSPrintDest dest, DN_LogStyle style, DN_FMT_ATTRIB char const *fmt, ...); +DN_API void DN_OS_PrintLnFVStyle (DN_OSPrintDest dest, DN_LogStyle style, DN_FMT_ATTRIB char const *fmt, va_list args); +#endif // DN_WITH_OS + // NOTE: Template implementations #if defined(__cplusplus) template T *DN_MemCopyObjT(T *dest, T const *src, DN_USize count) @@ -5646,9 +6280,189 @@ bool DN_TArrayGrowIfNeededFromArena(T **data, DN_USize size, DN_USize *max, DN_A return result; } #endif // defined(__cplusplus) -#endif // !defined(DN_BASE_H) -#if DN_H_WITH_OS +#if DN_WITH_NET +enum DN_NETRequestType +{ + DN_NETRequestType_Nil, + DN_NETRequestType_HTTP, + DN_NETRequestType_WS, +}; + +enum DN_NETResponseState +{ + DN_NETResponseState_Nil, + DN_NETResponseState_Error, + DN_NETResponseState_HTTP, + DN_NETResponseState_WSOpen, + DN_NETResponseState_WSText, + DN_NETResponseState_WSBinary, + DN_NETResponseState_WSClose, + DN_NETResponseState_WSPing, + DN_NETResponseState_WSPong, +}; + +enum DN_NETWSSend +{ + DN_NETWSSend_Text, + DN_NETWSSend_Binary, + DN_NETWSSend_Close, + DN_NETWSSend_Ping, + DN_NETWSSend_Pong, +}; + +enum DN_NETDoHTTPFlags +{ + DN_NETDoHTTPFlags_Nil = 0, + DN_NETDoHTTPFlags_BasicAuth = 1 << 0, +}; + +struct DN_NETDoHTTPArgs +{ + // NOTE: WS and HTTP args + DN_NETDoHTTPFlags flags; + DN_Str8 username; + DN_Str8 password; + DN_Str8 *headers; + DN_U16 headers_size; + + // NOTE: HTTP args only + DN_Str8 payload; +}; + +struct DN_NETRequestHandle +{ + DN_UPtr handle; + DN_U64 gen; +}; + +struct DN_NETResponse +{ + // NOTE: Common to WS and HTTP responses + DN_NETRequestType type; + DN_NETResponseState state; + DN_NETRequestHandle request; + DN_Str8 error_str8; + DN_Str8 body; + + // NOTE: HTTP responses only + DN_U32 http_status; +}; + +struct DN_NETRequest +{ + DN_MemList mem; + DN_Arena arena; + DN_Arena start_response_arena; + DN_NETRequestType type; + DN_U64 gen; + DN_Str8 url; + DN_Str8 method; + DN_OSSemaphore completion_sem; + DN_NETDoHTTPArgs args; + DN_NETResponse response; + DN_NETRequest *next; + DN_NETRequest *prev; + DN_U64 context[2]; +}; + +typedef void (DN_NETInitFunc) (struct DN_NETCore *net, char *base, DN_U64 base_size); +typedef void (DN_NETDeinitFunc) (struct DN_NETCore *net); +typedef DN_NETRequestHandle(DN_NETDoHTTPFunc) (struct DN_NETCore *net, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args); +typedef DN_NETRequestHandle(DN_NETDoWSFunc) (struct DN_NETCore *net, DN_Str8 url); +typedef void (DN_NETDoWSSendFunc) (DN_NETRequestHandle handle, DN_Str8 data, DN_NETWSSend send); +typedef DN_NETResponse (DN_NETWaitForResponseFunc) (DN_NETRequestHandle handle, DN_Arena *arena, DN_U32 timeout_ms); +typedef DN_NETResponse (DN_NETWaitForAnyResponseFunc)(struct DN_NETCore *net, DN_Arena *arena, DN_U32 timeout_ms); + +struct DN_NETInterface +{ + DN_NETInitFunc* init; + DN_NETDeinitFunc* deinit; + DN_NETDoHTTPFunc* do_http; + DN_NETDoWSFunc* do_ws; + DN_NETDoWSSendFunc* do_ws_send; + DN_NETWaitForResponseFunc* wait_for_response; + DN_NETWaitForAnyResponseFunc* wait_for_any_response; +}; + +struct DN_NETCore +{ + char *base; + DN_U64 base_size; + DN_MemList mem; + DN_Arena arena; + DN_OSSemaphore completion_sem; + void *context; + DN_NETInterface api; +}; + +DN_Str8 DN_NET_Str8FromResponseState (DN_NETResponseState state); +DN_NETRequest * DN_NET_RequestFromHandle (DN_NETRequestHandle handle); +DN_NETRequestHandle DN_NET_HandleFromRequest (DN_NETRequest *request); +bool DN_NET_ResponseHasFailed (DN_NETResponse const* resp); +DN_Str8 DN_NET_Str8DiagnosticFromResponse(DN_NETResponse const* resp, DN_Arena *arena); + +// NOTE: Internal functions for different networking implementations to use +void DN_NET_BaseInit (DN_NETCore *net, char *base, DN_U64 base_size); +DN_NETRequestHandle DN_NET_SetupRequest (DN_NETRequest *request, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args, DN_NETRequestType type); +void DN_NET_EndFinishedRequest (DN_NETRequest *request); +#endif + +#if DN_WITH_NET_CURL +#if !DN_WITH_OS || !DN_WITH_NET + #error "NET API with CURL requires #define DN_WITH_NET 1 and #define DN_WITH_OS 1" +#endif +struct DN_NETCurlCore +{ + // NOTE: Shared w/ user and networking thread + DN_Ring ring; + DN_OSMutex ring_mutex; + bool kill_thread; + + DN_OSMutex list_mutex; // Lock for request, response, deinit, free list + DN_NETRequest *request_list; // Current requests submitted by the user thread awaiting to move into the thread request list + DN_NETRequest *response_list; // Finished requests that are to be deqeued by the user via wait for response + DN_NETRequest *deinit_list; // Requests that are finished and are awaiting to be de-initialised by the CURL thread + DN_NETRequest *free_list; // Request pool that new requests will use before allocating + + // NOTE: Networking thread only + DN_NETRequest *thread_request_list; // Current requests being executed by the CURL thread. + // This list is exclusively owned by the CURL thread so no locking is needed + DN_OSThread thread; + void *thread_curlm; +}; + +#define DN_NET_CurlCoreFromNet(net) ((net) ? (DN_Cast(DN_NETCurlCore *)(net)->context) : nullptr); +DN_NETInterface DN_NET_CurlInterface (); +void DN_NET_CurlInit (DN_NETCore *net, char *base, DN_U64 base_size); +void DN_NET_CurlDeinit (DN_NETCore *net); +DN_NETRequestHandle DN_NET_CurlDoHTTP (DN_NETCore *net, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args); +DN_NETRequestHandle DN_NET_CurlDoWSArgs (DN_NETCore *net, DN_Str8 url, DN_NETDoHTTPArgs const *args); +DN_NETRequestHandle DN_NET_CurlDoWS (DN_NETCore *net, DN_Str8 url); +void DN_NET_CurlDoWSSend (DN_NETRequestHandle handle, DN_Str8 payload, DN_NETWSSend send); +DN_NETResponse DN_NET_CurlWaitForResponse (DN_NETRequestHandle handle, DN_Arena *arena, DN_U32 timeout_ms); +DN_NETResponse DN_NET_CurlWaitForAnyResponse(DN_NETCore *net, DN_Arena *arena, DN_U32 timeout_ms); +#endif // #if DN_WITH_NET_CURL + +#if DN_WITH_NET_EMSCRIPTEN +#if !DN_WITH_OS || !DN_WITH_NET + #error "NET API with Emscripten requires #define DN_WITH_NET 1 and #define DN_WITH_OS 1" +#endif +#if !defined(__EMSCRIPTEN__) + #error "NET API with Esmcripten can only be compiled with emcc which defines __EMSCRIPTEN__" +#endif + +DN_NETInterface DN_NET_EmcInterface(); +void DN_NET_EmcInit (DN_NETCore *net, char *base, DN_U64 base_size); +void DN_NET_EmcDeinit (DN_NETCore *net); +DN_NETRequestHandle DN_NET_EmcDoHTTP (DN_NETCore *net, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args); +DN_NETRequestHandle DN_NET_EmcDoWS (DN_NETCore *net, DN_Str8 url); +void DN_NET_EmcDoWSSend (DN_NETRequestHandle handle, DN_Str8 data, DN_NETWSSend send); +DN_NETResponse DN_NET_EmcWaitForResponse (DN_NETRequestHandle handle, DN_Arena *arena, DN_U32 timeout_ms); +DN_NETResponse DN_NET_EmcWaitForAnyResponse(DN_NETCore *net, DN_Arena *arena, DN_U32 timeout_ms); +#endif // #if DN_WITH_NET_EMSCRIPTEN + +#if DN_WITH_OS #if defined(DN_PLATFORM_WIN32) // DN: Single header generator commented out => #include "OS/dn_os_windows.h" #if !defined(DN_OS_WINDOWS_H) @@ -7166,753 +7980,6 @@ DN_API DN_OSPosixProcSelfStatus DN_OS_PosixProcSelfStatus(); #else #error Please define a platform e.g. 'DN_PLATFORM_WIN32' to enable the correct implementation for platform APIs #endif -// DN: Single header generator commented out => #include "OS/dn_os.h" -#if !defined(DN_OS_H) -#define DN_OS_H +#endif // #if DN_WITH_OS -// DN: Single header generator commented out => #if defined(_CLANGD) -// #define DN_H_WITH_OS 1 -// #include "../dn.h" -// #endif - -#include // operator new - -#if !defined(DN_OS_WIN32) || defined(DN_OS_WIN32_USE_PTHREADS) - #include - #include -#endif - -#if defined(DN_PLATFORM_POSIX) || defined(DN_PLATFORM_EMSCRIPTEN) - #include // errno - #include // O_RDONLY ... etc - #include // ioctl - #include // mmap - #include // getrandom - #include // stat - #include // pid_t - #include // waitpid - #include // clock_gettime, nanosleep - #include // access, gettid, write - - #if !defined(DN_PLATFORM_EMSCRIPTEN) - #include // FICLONE - #include // sendfile - #endif -#endif - -extern DN_CPUFeatureDecl g_dn_cpu_feature_decl[DN_CPUFeature_Count]; - -struct DN_OSTimer /// Record time between two time-points using the OS's performance counter. -{ - DN_U64 start; - DN_U64 end; -}; - -// NOTE: DN_OSFile -enum DN_OSPathInfoType -{ - DN_OSPathInfoType_Unknown, - DN_OSPathInfoType_Directory, - DN_OSPathInfoType_File, -}; - -struct DN_OSPathInfo -{ - bool exists; - DN_OSPathInfoType type; - DN_U64 create_time_in_s; - DN_U64 last_write_time_in_s; - DN_U64 last_access_time_in_s; - DN_U64 size; -}; - -struct DN_OSDirIterator -{ - void *handle; - DN_Str8 file_name; - char buffer[512]; -}; - -// NOTE: R/W Stream API -struct DN_OSFileRead -{ - bool success; - DN_USize bytes_read; -}; - -struct DN_OSFile -{ - bool error; - void *handle; -}; - -enum DN_OSFileOpen -{ - DN_OSFileOpen_CreateAlways, // Create file if it does not exist, otherwise, zero out the file and open - DN_OSFileOpen_OpenIfExist, // Open file at path only if it exists - DN_OSFileOpen_OpenAlways, // Open file at path, create file if it does not exist -}; - -typedef DN_U32 DN_OSFileAccess; - -enum DN_OSFileAccess_ -{ - DN_OSFileAccess_Read = 1 << 0, - DN_OSFileAccess_Write = 1 << 1, - DN_OSFileAccess_Execute = 1 << 2, - DN_OSFileAccess_AppendOnly = 1 << 3, // This flag cannot be combined with any other access mode - DN_OSFileAccess_ReadWrite = DN_OSFileAccess_Read | DN_OSFileAccess_Write, - DN_OSFileAccess_All = DN_OSFileAccess_ReadWrite | DN_OSFileAccess_Execute | DN_OSFileAccess_AppendOnly, -}; - -// NOTE: DN_OSPath -#if !defined(DN_OSPathSeperator) - #if defined(DN_OS_WIN32) - #define DN_OSPathSeperator "\\" - #else - #define DN_OSPathSeperator "/" - #endif - #define DN_OSPathSeperatorString DN_Str8Lit(DN_OSPathSeperator) -#endif - -struct DN_OSPathLink -{ - DN_Str8 string; - DN_OSPathLink *next; - DN_OSPathLink *prev; -}; - -struct DN_OSPath -{ - bool has_prefix_path_separator; - DN_OSPathLink *head; - DN_OSPathLink *tail; - DN_USize string_size; - DN_U16 links_size; -}; - -// NOTE: DN_OSExec -typedef DN_U32 DN_OSExecFlags; - -enum DN_OSExecFlags_ -{ - DN_OSExecFlags_Nil = 0, - DN_OSExecFlags_SaveStdout = 1 << 0, - DN_OSExecFlags_SaveStderr = 1 << 1, - DN_OSExecFlags_SaveOutput = DN_OSExecFlags_SaveStdout | DN_OSExecFlags_SaveStderr, - DN_OSExecFlags_MergeStderrToStdout = 1 << 2 | DN_OSExecFlags_SaveOutput, -}; - -struct DN_OSExecAsyncHandle -{ - DN_OSExecFlags exec_flags; - DN_U32 os_error_code; - DN_U32 exit_code; - void *process; - void *stdout_read; - void *stdout_write; - void *stderr_read; - void *stderr_write; -}; - -struct DN_OSExecResult -{ - bool finished; - DN_Str8 stdout_text; - DN_Str8 stderr_text; - DN_U32 os_error_code; - DN_U32 exit_code; -}; - -struct DN_OSExecArgs -{ - DN_OSExecFlags flags; - DN_Str8 working_dir; - DN_Str8Slice environment; -}; - -// NOTE: DN_OSSemaphore -DN_U32 const DN_OS_SEMAPHORE_INFINITE_TIMEOUT = UINT32_MAX; - -struct DN_OSSemaphore -{ - DN_U64 handle; -}; - -struct DN_OSBarrier -{ - DN_U64 handle; -}; - -enum DN_OSSemaphoreWaitResult -{ - DN_OSSemaphoreWaitResult_Failed, - DN_OSSemaphoreWaitResult_Success, - DN_OSSemaphoreWaitResult_Timeout, -}; - -struct DN_OSMutex -{ - DN_U64 handle; -}; - -struct DN_OSConditionVariable -{ - DN_U64 handle; -}; - -// NOTE: DN_OSThread -typedef DN_I32(DN_OSThreadFunc)(struct DN_OSThread *); - -struct DN_OSThreadLane -{ - DN_USize index; - DN_USize count; - DN_OSBarrier barrier; - void* shared_mem; -}; - -struct DN_OSThreadLaneway -{ - DN_OSThread* threads; - DN_USize threads_count; - DN_UPtr* shared_mem; - DN_OSBarrier barrier; -}; - -struct DN_OSThread -{ - DN_Str8x64 name; - DN_TCCore context; - DN_OSThreadLane lane; - bool is_lane_set; - void *handle; - DN_U64 thread_id; - void *user_context; - DN_OSThreadFunc *func; - DN_OSSemaphore init_semaphore; - DN_TCInitArgs tc_init_args; -}; - -struct DN_OSCore -{ - DN_CPUReport cpu_report; - - // NOTE: Logging - bool log_to_file; // Output logs to file as well as standard out - DN_OSFile log_file; // TODO(dn): Hmmm, how should we do this... ? - DN_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 - DN_TicketMutex log_mutex; - - // NOTE: OS - DN_U32 logical_processor_count; - DN_U32 page_size; - DN_U32 alloc_granularity; - - // NOTE: Memory - // Total OS mem allocs in lifetime of program (e.g. malloc, VirtualAlloc, HeapAlloc ...). This - // only includes allocations routed through the library such as the growing nature of arenas or - // using the memory allocation routines in the library like DN_OS_MemCommit and so forth. - DN_U64 vmem_allocs_total; - DN_U64 vmem_allocs_frame; // Total OS virtual memory allocs since the last 'DN_Core_FrameBegin' was invoked - DN_U64 mem_allocs_total; - DN_U64 mem_allocs_frame; // Total OS heap allocs since the last 'DN_Core_FrameBegin' was invoked - - DN_MemList mem; - DN_Arena arena; - void *platform_context; -}; - -struct DN_OSDiskSpace -{ - bool success; - DN_U64 avail; - DN_U64 size; -}; - -DN_API DN_MemFuncs DN_MemFuncsFromType (DN_MemFuncsType type); -DN_API DN_MemFuncs DN_MemFuncsDefault (); -DN_API DN_MemList DN_MemListFromHeap (DN_U64 size, DN_MemFlags flags); -DN_API DN_MemList DN_MemListFromVMem (DN_U64 reserve, DN_U64 commit, DN_MemFlags flags); -DN_API DN_Arena DN_ArenaFromHeap (DN_U64 wize, DN_MemFlags flags); -DN_API DN_Arena DN_ArenaFromVMem (DN_U64 reserve, DN_U64 commit, DN_MemFlags flags); - -DN_API DN_Str8 DN_Str8FromHeapF (DN_FMT_ATTRIB char const *fmt, ...); -DN_API DN_Str8 DN_Str8FromHeap (DN_USize size, DN_ZMem z_mem); -DN_API DN_Str8 DN_Str8BuilderBuildFromHeap (DN_Str8Builder const *builder); - -DN_API void DN_OS_LogPrint (DN_LogTypeParam type, void *user_data, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, va_list args); -DN_API void DN_OS_SetLogPrintFuncToOS (); - -DN_API void * DN_OS_MemReserve (DN_USize size, DN_MemCommit commit, DN_MemPage page_flags); -DN_API bool DN_OS_MemCommit (void *ptr, DN_USize size, DN_U32 page_flags); -DN_API void DN_OS_MemDecommit (void *ptr, DN_USize size); -DN_API void DN_OS_MemRelease (void *ptr, DN_USize size); -DN_API int DN_OS_MemProtect (void *ptr, DN_USize size, DN_U32 page_flags); - -DN_API void * DN_OS_MemAlloc (DN_USize size, DN_ZMem z_mem); -DN_API void DN_OS_MemDealloc (void *ptr); - -DN_API DN_Date DN_OS_DateLocalTimeNow (); -DN_API DN_Str8x32 DN_OS_DateLocalTimeStr8Now (char date_separator = '-', char hms_separator = ':'); -DN_API DN_Str8x32 DN_OS_DateLocalTimeStr8 (DN_Date time, char date_separator = '-', char hms_separator = ':'); -DN_API DN_U64 DN_OS_DateUnixTimeNs (); -#define DN_OS_DateUnixTimeUs() (DN_OS_DateUnixTimeNs() / 1000) -#define DN_OS_DateUnixTimeMs() (DN_OS_DateUnixTimeNs() / (1000 * 1000)) -#define DN_OS_DateUnixTimeS() (DN_OS_DateUnixTimeNs() / (1000 * 1000 * 1000)) -DN_API DN_U64 DN_OS_DateUnixTimeSFromLocalDate (DN_Date date); -DN_API DN_U64 DN_OS_DateLocalUnixTimeSFromUnixTimeS (DN_U64 unix_ts_s); - -DN_API void DN_OS_GenBytesSecure (void *buffer, DN_U32 size); -DN_API bool DN_OS_SetEnvVar (DN_Str8 name, DN_Str8 value); -DN_API DN_OSDiskSpace DN_OS_DiskSpace (DN_Str8 path); -DN_API DN_Str8 DN_OS_EXEPath (DN_Arena *arena); -DN_API DN_Str8 DN_OS_EXEDir (DN_Arena *arena); -DN_API void DN_OS_SleepMs (DN_UInt milliseconds); - -DN_API DN_U64 DN_OS_PerfCounterNow (); -DN_API DN_U64 DN_OS_PerfCounterFrequency (); -DN_API DN_F64 DN_OS_PerfCounterS (DN_U64 begin, uint64_t end); -DN_API DN_F64 DN_OS_PerfCounterMs (DN_U64 begin, uint64_t end); -DN_API DN_F64 DN_OS_PerfCounterUs (DN_U64 begin, uint64_t end); -DN_API DN_F64 DN_OS_PerfCounterNs (DN_U64 begin, uint64_t end); -DN_API DN_OSTimer DN_OS_TimerBegin (); -DN_API void DN_OS_TimerEnd (DN_OSTimer *timer); -DN_API DN_F64 DN_OS_TimerS (DN_OSTimer timer); -DN_API DN_F64 DN_OS_TimerMs (DN_OSTimer timer); -DN_API DN_F64 DN_OS_TimerUs (DN_OSTimer timer); -DN_API DN_F64 DN_OS_TimerNs (DN_OSTimer timer); -DN_API DN_U64 DN_OS_EstimateTSCPerSecond (uint64_t duration_ms_to_gauge_tsc_frequency); - -DN_API bool DN_OS_FileCopy (DN_Str8 src, DN_Str8 dest, bool overwrite, DN_ErrSink *err); -DN_API bool DN_OS_FileMove (DN_Str8 src, DN_Str8 dest, bool overwrite, DN_ErrSink *err); - -DN_API DN_OSFile DN_OS_FileOpen (DN_Str8 path, DN_OSFileOpen open_mode, DN_OSFileAccess access, DN_ErrSink *err); -DN_API DN_OSFileRead DN_OS_FileRead (DN_OSFile *file, void *buffer, DN_USize size, DN_ErrSink *err); -DN_API bool DN_OS_FileWritePtr (DN_OSFile *file, void const *data, DN_USize size, DN_ErrSink *err); -DN_API bool DN_OS_FileWrite (DN_OSFile *file, DN_Str8 buffer, DN_ErrSink *err); -DN_API bool DN_OS_FileWriteFV (DN_OSFile *file, DN_ErrSink *err, DN_FMT_ATTRIB char const *fmt, va_list args); -DN_API bool DN_OS_FileWriteF (DN_OSFile *file, DN_ErrSink *err, DN_FMT_ATTRIB char const *fmt, ...); -DN_API bool DN_OS_FileFlush (DN_OSFile *file, DN_ErrSink *err); -DN_API void DN_OS_FileClose (DN_OSFile *file); - -DN_API DN_Str8 DN_OS_FileReadAll (DN_Allocator allocator, DN_Str8 path, DN_ErrSink *err); -DN_API DN_Str8 DN_OS_FileReadAllArena (DN_Arena *arena, DN_Str8 path, DN_ErrSink *err); -DN_API DN_Str8 DN_OS_FileReadAllPool (DN_Pool *pool, DN_Str8 path, DN_ErrSink *err); - -DN_API bool DN_OS_FileWriteAll (DN_Str8 path, DN_Str8 buffer, DN_ErrSink *err); -DN_API bool DN_OS_FileWriteAllFV (DN_Str8 path, DN_ErrSink *err, DN_FMT_ATTRIB char const *fmt, va_list args); -DN_API bool DN_OS_FileWriteAllF (DN_Str8 path, DN_ErrSink *err, DN_FMT_ATTRIB char const *fmt, ...); -DN_API bool DN_OS_FileWriteAllSafe (DN_Str8 path, DN_Str8 buffer, DN_ErrSink *err); -DN_API bool DN_OS_FileWriteAllSafeFV (DN_Str8 path, DN_ErrSink *err, DN_FMT_ATTRIB char const *fmt, va_list args); -DN_API bool DN_OS_FileWriteAllSafeF (DN_Str8 path, DN_ErrSink *err, DN_FMT_ATTRIB char const *fmt, ...); - -DN_API DN_Str8 DN_OS_Str8FromPathInfoType (DN_OSPathInfoType type); -DN_API DN_OSPathInfo DN_OS_PathInfo (DN_Str8 path); -DN_API bool DN_OS_PathIsOlderThan (DN_Str8 file, DN_Str8 check_against); -DN_API bool DN_OS_PathDelete (DN_Str8 path); -DN_API bool DN_OS_PathIsFile (DN_Str8 path); -DN_API bool DN_OS_PathIsDir (DN_Str8 path); -DN_API bool DN_OS_PathMakeDir (DN_Str8 path); -DN_API bool DN_OS_PathIterateDir (DN_Str8 path, DN_OSDirIterator *it); - -DN_API bool DN_OS_PathAddRef (DN_Arena *arena, DN_OSPath *fs_path, DN_Str8 path); -DN_API bool DN_OS_PathAdd (DN_Arena *arena, DN_OSPath *fs_path, DN_Str8 path); -DN_API bool DN_OS_PathAddF (DN_Arena *arena, DN_OSPath *fs_path, DN_FMT_ATTRIB char const *fmt, ...); -DN_API bool DN_OS_PathPop (DN_OSPath *fs_path); -DN_API DN_Str8 DN_OS_PathBuildWithSeparator (DN_Arena *arena, DN_OSPath const *fs_path, DN_Str8 path_separator); -DN_API DN_Str8 DN_OS_PathTo (DN_Arena *arena, DN_Str8 path, DN_Str8 path_separtor); -DN_API DN_Str8 DN_OS_PathToF (DN_Arena *arena, DN_Str8 path_separator, DN_FMT_ATTRIB char const *fmt, ...); -DN_API DN_Str8 DN_OS_Path (DN_Arena *arena, DN_Str8 path); -DN_API DN_Str8 DN_OS_PathF (DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, ...); - -#define DN_OS_PathBuildFwdSlash(allocator, fs_path) DN_OS_PathBuildWithSeparator(allocator, fs_path, DN_Str8Lit("/")) -#define DN_OS_PathBuildBackSlash(allocator, fs_path) DN_OS_PathBuildWithSeparator(allocator, fs_path, DN_Str8Lit("\\")) -#define DN_OS_PathBuild(allocator, fs_path) DN_OS_PathBuildWithSeparator(allocator, fs_path, DN_OSPathSeparatorString) - -DN_API void DN_OS_Exit (int32_t exit_code); -DN_API DN_OSExecResult DN_OS_ExecPump (DN_OSExecAsyncHandle handle, char *stdout_buffer, size_t *stdout_size, char *stderr_buffer, size_t *stderr_size, DN_U32 timeout_ms, DN_ErrSink *err); -DN_API DN_OSExecResult DN_OS_ExecWait (DN_OSExecAsyncHandle handle, DN_Arena *arena, DN_ErrSink *err); -DN_API DN_OSExecAsyncHandle DN_OS_ExecAsync (DN_Str8Slice cmd_line, DN_OSExecArgs *args, DN_ErrSink *err); -DN_API DN_OSExecResult DN_OS_Exec (DN_Str8Slice cmd_line, DN_OSExecArgs *args, DN_Arena *arena, DN_ErrSink *err); -DN_API DN_OSExecResult DN_OS_ExecOrAbort (DN_Str8Slice cmd_line, DN_OSExecArgs *args, DN_Arena *arena); - -DN_API DN_OSSemaphore DN_OS_SemaphoreInit (DN_U32 initial_count); -DN_API void DN_OS_SemaphoreDeinit (DN_OSSemaphore *semaphore); -DN_API void DN_OS_SemaphoreIncrement (DN_OSSemaphore *semaphore, DN_U32 amount); -DN_API DN_OSSemaphoreWaitResult DN_OS_SemaphoreWait (DN_OSSemaphore *semaphore, DN_U32 timeout_ms); - -DN_API DN_OSBarrier DN_OS_BarrierInit (DN_U32 thread_count); -DN_API void DN_OS_BarrierDeinit (DN_OSBarrier *barrier); -DN_API void DN_OS_BarrierWait (DN_OSBarrier *barrier); - -DN_API DN_OSMutex DN_OS_MutexInit (); -DN_API void DN_OS_MutexDeinit (DN_OSMutex *mutex); -DN_API void DN_OS_MutexLock (DN_OSMutex *mutex); -DN_API void DN_OS_MutexUnlock (DN_OSMutex *mutex); -#define DN_OS_MutexScope(mutex) DN_DeferLoop(DN_OS_MutexLock(mutex), DN_OS_MutexUnlock(mutex)) - -DN_API DN_OSConditionVariable DN_OS_ConditionVariableInit (); -DN_API void DN_OS_ConditionVariableDeinit (DN_OSConditionVariable *cv); -DN_API bool DN_OS_ConditionVariableWait (DN_OSConditionVariable *cv, DN_OSMutex *mutex, DN_U64 sleep_ms); -DN_API bool DN_OS_ConditionVariableWaitUntil (DN_OSConditionVariable *cv, DN_OSMutex *mutex, DN_U64 end_ts_ms); -DN_API void DN_OS_ConditionVariableSignal (DN_OSConditionVariable *cv); -DN_API void DN_OS_ConditionVariableBroadcast (DN_OSConditionVariable *cv); - -DN_API bool DN_OS_ThreadInit (DN_OSThread *thread, DN_OSThreadFunc *func, DN_OSThreadLane *lane, DN_TCInitArgs tc_init_args, void *user_context); -DN_API bool DN_OS_ThreadJoin (DN_OSThread *thread, DN_TCDeinitArenas deinit_arenas); -DN_API DN_U32 DN_OS_ThreadID (); -DN_API void DN_OS_ThreadSetNameFmt (char const *fmt, ...); - -// NOTE: Thread lanes provide an abstraction to represent the concept of programming a CPU like a -// GPU, e.g. SIMT (Single Instruction Multiple Threads). The lane terminology is popularised by Ryan -// Fleury. SIMT is formally defined as -// -// Threads are grouped into warps/wavefronts (typically 32 or 64 threads) that execute the same -// instruction in lockstep, but each thread operates on different data and maintains its own state -// -// The individual threads in a wavefront on the CPU side are colloquially dubbed "lanes" and a -// thread lane here contains the necessary state to facilitate this such as the current index in the -// wavefront and synchronisation primitives to coordinate the different lanes together. -// -// The idea is to write code in a single-threaded manner (linear execution) but across multiple -// threads so that the default is all execution paths are inherently multi-threaded by default. Opt -// out of parallelism instead of opt in. This optimises for the trend of core counts increasing -// whilst clock counts remain static. -// -// A laneway is a helper function to initialise the number of requested OS threads/lanes upfront and -// setup the required synchronisation primitives. It can then be dispatched all the threads which -// start executing the `entry_point` in parallel. -// -// API -// DN_OS_ThreadLaneSync -// A blocking call to synchronise the program-counter of all other lanes in the laneway to this -// function call invocation (using an OS barrier). Optionally pass in the pointer to a pointer -// `ptr_to_share` to broadcast the pointer from one lanes to the others. The lane that wishes -// to broadcast the pointer must have a non-null pointer, all other lanes must pass in a -// non-null pointer. A typical use case might look like: -/* - DN_OSThreadLane *lane = DN_OS_TCThreadLane(); // Get lane from current (t)hread (c)context - - // NOTE: Allocate buffer in lane 0 - DN_U8 *buffer = nullptr; - if (lane->index == 0) - buffer = DN_ArenaNewArray(DN_TCMainArena(), DN_U8, DN_Gigabytes(1), DN_ZMem_No); - - // NOTE: Lane 0 broadcasts the `buffer` pointer to lane 1..N - DN_OS_ThreadLaneSync(lane, &buffer); - - // NOTE: We use LaneRange to divide the buffer into equal sized chunks that each lane can - // write into without clobbering over each other. - DN_V2USize range = DN_OS_ThreadLaneRange(lane, DN_Gigabytes(1)); - for (DN_USize index = range.begin; index < range.end; index++) { buffer[index] = index; } -*/ -// In this example, lane 0 will allocate a 1GiB buffer pass in a `buffer` to -// DN_OS_ThreadLaneSync` that is non-null. Lanes 1->N will skip the branch (because their lanes -// indexes are 1..N) and invoke `DN_OS_ThreadLaneSync` with a nullptr `buffer`. After the -// blocking call is complete, lanes 0->N will now have synchronised the `buffer` pointer and all -// lanes point to the 1GiB range allocated in lane 0's allocator. -// -// Additionally we demonstrate `DN_OS_ThreadLaneRange` which does math behind the scenes to -// divide the buffer up and assign each lane their own indices in the buffer that they can work -// on in parallel without clobbering each others work. -// -// DN_OS_ThreadLaneRange -// Calculates the range of values the current lane in the laneway should execute. For example if -// you have 128 items and 16 threads each lane will receive the following `DN_V2USize` range: -// Lane 0 => [0, 8) -// Lane 1 => [8, 16) -// ... -// Lane 16 => [120, 128) -DN_API DN_OSThreadLane DN_OS_ThreadLaneInit (DN_USize index, DN_USize thread_count, DN_OSBarrier barrier, DN_UPtr *share_mem); -DN_API void DN_OS_ThreadLaneSync (DN_OSThreadLane *lane, void **ptr_to_share); -DN_API DN_V2USize DN_OS_ThreadLaneRange (DN_OSThreadLane const *lane, DN_USize values_count); - -DN_API DN_OSThreadLaneway DN_OS_ThreadLanewayFromArgs (DN_OSThread* threads, DN_USize threads_count, DN_UPtr* shared_mem); -DN_API DN_OSThreadLaneway DN_OS_ThreadLanewayFromArena (DN_USize threads_count, DN_Arena* arena); -DN_API void DN_OS_ThreadLanewayDispatch (DN_OSThreadLaneway *laneway, DN_OSThreadFunc *entry_point, DN_TCInitArgs tc_init_args, void *user_context); -DN_API void DN_OS_ThreadLanewayJoin (DN_OSThreadLaneway *laneway, DN_TCDeinitArenas deinit_arenas); - -DN_API DN_OSThreadLane* DN_OS_TCThreadLane (); -DN_API void DN_OS_TCThreadLaneSync (void **ptr_to_share); -DN_API DN_OSThreadLane DN_OS_TCThreadLaneEquip (DN_OSThreadLane lane); - -enum DN_OSAsyncPriority -{ - DN_OSAsyncPriority_Low, - DN_OSAsyncPriority_High, - DN_OSAsyncPriority_Count, -}; - -struct DN_OSAsyncCore -{ - DN_OSMutex ring_mutex; - DN_OSConditionVariable ring_write_cv; - DN_OSSemaphore worker_sem; - DN_Ring ring; - DN_OSThread *threads; - DN_U32 thread_count; - DN_U32 busy_threads; - DN_U32 join_threads; -}; - -struct DN_OSAsyncWorkArgs -{ - DN_OSThread *thread; - void *input; -}; - -typedef void(DN_OSAsyncWorkFunc)(DN_OSAsyncWorkArgs work_args); - -struct DN_OSAsyncWork -{ - DN_OSAsyncWorkFunc *func; - void *input; - void *output; -}; - -struct DN_OSAsyncTask -{ - bool queued; - DN_OSAsyncWork work; - DN_OSSemaphore completion_sem; -}; - -DN_API void DN_OS_AsyncInit (DN_OSAsyncCore *async, char *base, DN_USize base_size, DN_OSThread *threads, DN_U32 threads_size); -DN_API void DN_OS_AsyncDeinit (DN_OSAsyncCore *async); -DN_API bool DN_OS_AsyncQueueWork(DN_OSAsyncCore *async, DN_OSAsyncWorkFunc *func, void *input, DN_U64 wait_time_ms); -DN_API DN_OSAsyncTask DN_OS_AsyncQueueTask(DN_OSAsyncCore *async, DN_OSAsyncWorkFunc *func, void *input, DN_U64 wait_time_ms); -DN_API bool DN_OS_AsyncWaitTask (DN_OSAsyncTask *task, DN_U32 timeout_ms); - -// NOTE: DN_OSPrint -enum DN_OSPrintDest -{ - DN_OSPrintDest_Out, - DN_OSPrintDest_Err, -}; - -// NOTE: Print Macros -#define DN_OS_PrintOut(string) DN_OS_Print(DN_OSPrintDest_Out, string) -#define DN_OS_PrintOutF(fmt, ...) DN_OS_PrintF(DN_OSPrintDest_Out, fmt, ##__VA_ARGS__) -#define DN_OS_PrintOutFV(fmt, args) DN_OS_PrintFV(DN_OSPrintDest_Out, fmt, args) - -#define DN_OS_PrintOutStyle(style, string) DN_OS_PrintStyle(DN_OSPrintDest_Out, style, string) -#define DN_OS_PrintOutFStyle(style, fmt, ...) DN_OS_PrintFStyle(DN_OSPrintDest_Out, style, fmt, ##__VA_ARGS__) -#define DN_OS_PrintOutFVStyle(style, fmt, args, ...) DN_OS_PrintFVStyle(DN_OSPrintDest_Out, style, fmt, args) - -#define DN_OS_PrintOutLn(string) DN_OS_PrintLn(DN_OSPrintDest_Out, string) -#define DN_OS_PrintOutLnF(fmt, ...) DN_OS_PrintLnF(DN_OSPrintDest_Out, fmt, ##__VA_ARGS__) -#define DN_OS_PrintOutLnFV(fmt, args) DN_OS_PrintLnFV(DN_OSPrintDest_Out, fmt, args) - -#define DN_OS_PrintOutLnStyle(style, string) DN_OS_PrintLnStyle(DN_OSPrintDest_Out, style, string); -#define DN_OS_PrintOutLnFStyle(style, fmt, ...) DN_OS_PrintLnFStyle(DN_OSPrintDest_Out, style, fmt, ##__VA_ARGS__) -#define DN_OS_PrintOutLnFVStyle(style, fmt, args) DN_OS_PrintLnFVStyle(DN_OSPrintDest_Out, style, fmt, args); - -#define DN_OS_PrintErr(string) DN_OS_Print(DN_OSPrintDest_Err, string) -#define DN_OS_PrintErrF(fmt, ...) DN_OS_PrintF(DN_OSPrintDest_Err, fmt, ##__VA_ARGS__) -#define DN_OS_PrintErrFV(fmt, args) DN_OS_PrintFV(DN_OSPrintDest_Err, fmt, args) - -#define DN_OS_PrintErrStyle(style, string) DN_OS_PrintStyle(DN_OSPrintDest_Err, style, string) -#define DN_OS_PrintErrFStyle(style, fmt, ...) DN_OS_PrintFStyle(DN_OSPrintDest_Err, style, fmt, ##__VA_ARGS__) -#define DN_OS_PrintErrFVStyle(style, fmt, args, ...) DN_OS_PrintFVStyle(DN_OSPrintDest_Err, style, fmt, args) - -#define DN_OS_PrintErrLn(string) DN_OS_PrintLn(DN_OSPrintDest_Err, string) -#define DN_OS_PrintErrLnF(fmt, ...) DN_OS_PrintLnF(DN_OSPrintDest_Err, fmt, ##__VA_ARGS__) -#define DN_OS_PrintErrLnFV(fmt, args) DN_OS_PrintLnFV(DN_OSPrintDest_Err, fmt, args) - -#define DN_OS_PrintErrLnStyle(style, string) DN_OS_PrintLnStyle(DN_OSPrintDest_Err, style, string); -#define DN_OS_PrintErrLnFStyle(style, fmt, ...) DN_OS_PrintLnFStyle(DN_OSPrintDest_Err, style, fmt, ##__VA_ARGS__) -#define DN_OS_PrintErrLnFVStyle(style, fmt, args) DN_OS_PrintLnFVStyle(DN_OSPrintDest_Err, style, fmt, args); - -// NOTE: Print -DN_API void DN_OS_Print (DN_OSPrintDest dest, DN_Str8 string); -DN_API void DN_OS_PrintF (DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, ...); -DN_API void DN_OS_PrintFV (DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, va_list args); - -DN_API void DN_OS_PrintStyle (DN_OSPrintDest dest, DN_LogStyle style, DN_Str8 string); -DN_API void DN_OS_PrintFStyle (DN_OSPrintDest dest, DN_LogStyle style, DN_FMT_ATTRIB char const *fmt, ...); -DN_API void DN_OS_PrintFVStyle (DN_OSPrintDest dest, DN_LogStyle style, DN_FMT_ATTRIB char const *fmt, va_list args); - -DN_API void DN_OS_PrintLn (DN_OSPrintDest dest, DN_Str8 string); -DN_API void DN_OS_PrintLnF (DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, ...); -DN_API void DN_OS_PrintLnFV (DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, va_list args); - -DN_API void DN_OS_PrintLnStyle (DN_OSPrintDest dest, DN_LogStyle style, DN_Str8 string); -DN_API void DN_OS_PrintLnFStyle (DN_OSPrintDest dest, DN_LogStyle style, DN_FMT_ATTRIB char const *fmt, ...); -DN_API void DN_OS_PrintLnFVStyle (DN_OSPrintDest dest, DN_LogStyle style, DN_FMT_ATTRIB char const *fmt, va_list args); -#endif // !defined(DN_OS_H) -#endif - -typedef DN_USize DN_InitFlags; -enum DN_InitFlags_ -{ - DN_InitFlags_Nil = 0, - DN_InitFlags_OS = (1 << 0), - DN_InitFlags_ThreadContext = (1 << 1) | DN_InitFlags_OS, - DN_InitFlags_LeakTracker = (1 << 2) | DN_InitFlags_OS, - DN_InitFlags_LogLibFeatures = (1 << 3), - DN_InitFlags_LogCPUFeatures = (1 << 4) | DN_InitFlags_OS, - DN_InitFlags_LogAllFeatures = DN_InitFlags_LogLibFeatures | DN_InitFlags_LogCPUFeatures, -}; - -struct DN_Core -{ - DN_InitFlags init_flags; - DN_TCCore main_tc; - DN_USize mem_allocs_frame; - DN_LeakTracker leak; - - DN_LogType log_level_to_show_from; - DN_LogPrintFunc* print_func; - void* print_func_context; - - bool os_init; - #if defined(DN_OS_H) - DN_OSCore os; - #endif -}; - -DN_API void DN_Init (DN_Core *dn, DN_InitFlags flags, DN_TCInitArgs args); -DN_API void DN_Set (DN_Core *dn); -DN_API DN_Core *DN_Get (); -DN_API void DN_BeginFrame(); - -#if DN_H_WITH_NET -// DN: Single header generator commented out => #include "Extra/dn_net.h" -#if !defined(DN_NET_H) -#define DN_NET_H - -// DN: Single header generator commented out => #if defined(_CLANGD) -// #define DN_H_WITH_OS 1 -// #include "../dn.h" -// #endif - -enum DN_NETRequestType -{ - DN_NETRequestType_Nil, - DN_NETRequestType_HTTP, - DN_NETRequestType_WS, -}; - -enum DN_NETResponseState -{ - DN_NETResponseState_Nil, - DN_NETResponseState_Error, - DN_NETResponseState_HTTP, - DN_NETResponseState_WSOpen, - DN_NETResponseState_WSText, - DN_NETResponseState_WSBinary, - DN_NETResponseState_WSClose, - DN_NETResponseState_WSPing, - DN_NETResponseState_WSPong, -}; - -enum DN_NETWSSend -{ - DN_NETWSSend_Text, - DN_NETWSSend_Binary, - DN_NETWSSend_Close, - DN_NETWSSend_Ping, - DN_NETWSSend_Pong, -}; - -enum DN_NETDoHTTPFlags -{ - DN_NETDoHTTPFlags_Nil = 0, - DN_NETDoHTTPFlags_BasicAuth = 1 << 0, -}; - -struct DN_NETDoHTTPArgs -{ - // NOTE: WS and HTTP args - DN_NETDoHTTPFlags flags; - DN_Str8 username; - DN_Str8 password; - DN_Str8 *headers; - DN_U16 headers_size; - - // NOTE: HTTP args only - DN_Str8 payload; -}; - -struct DN_NETRequestHandle -{ - DN_UPtr handle; - DN_U64 gen; -}; - -struct DN_NETResponse -{ - // NOTE: Common to WS and HTTP responses - DN_NETRequestType type; - DN_NETResponseState state; - DN_NETRequestHandle request; - DN_Str8 error_str8; - DN_Str8 body; - - // NOTE: HTTP responses only - DN_U32 http_status; -}; - -struct DN_NETRequest -{ - DN_MemList mem; - DN_Arena arena; - DN_Arena start_response_arena; - DN_NETRequestType type; - DN_U64 gen; - DN_Str8 url; - DN_Str8 method; - DN_OSSemaphore completion_sem; - DN_NETDoHTTPArgs args; - DN_NETResponse response; - DN_NETRequest *next; - DN_NETRequest *prev; - DN_U64 context[2]; -}; - -typedef void (DN_NETInitFunc) (struct DN_NETCore *net, char *base, DN_U64 base_size); -typedef void (DN_NETDeinitFunc) (struct DN_NETCore *net); -typedef DN_NETRequestHandle(DN_NETDoHTTPFunc) (struct DN_NETCore *net, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args); -typedef DN_NETRequestHandle(DN_NETDoWSFunc) (struct DN_NETCore *net, DN_Str8 url); -typedef void (DN_NETDoWSSendFunc) (DN_NETRequestHandle handle, DN_Str8 data, DN_NETWSSend send); -typedef DN_NETResponse (DN_NETWaitForResponseFunc) (DN_NETRequestHandle handle, DN_Arena *arena, DN_U32 timeout_ms); -typedef DN_NETResponse (DN_NETWaitForAnyResponseFunc)(struct DN_NETCore *net, DN_Arena *arena, DN_U32 timeout_ms); - -struct DN_NETInterface -{ - DN_NETInitFunc* init; - DN_NETDeinitFunc* deinit; - DN_NETDoHTTPFunc* do_http; - DN_NETDoWSFunc* do_ws; - DN_NETDoWSSendFunc* do_ws_send; - DN_NETWaitForResponseFunc* wait_for_response; - DN_NETWaitForAnyResponseFunc* wait_for_any_response; -}; - -struct DN_NETCore -{ - char *base; - DN_U64 base_size; - DN_MemList mem; - DN_Arena arena; - DN_OSSemaphore completion_sem; - void *context; - DN_NETInterface api; -}; - -DN_Str8 DN_NET_Str8FromResponseState (DN_NETResponseState state); -DN_NETRequest * DN_NET_RequestFromHandle (DN_NETRequestHandle handle); -DN_NETRequestHandle DN_NET_HandleFromRequest (DN_NETRequest *request); -bool DN_NET_ResponseHasFailed (DN_NETResponse const* resp); -DN_Str8 DN_NET_Str8DiagnosticFromResponse(DN_NETResponse const* resp, DN_Arena *arena); - -// NOTE: Internal functions for different networking implementations to use -void DN_NET_BaseInit (DN_NETCore *net, char *base, DN_U64 base_size); -DN_NETRequestHandle DN_NET_SetupRequest (DN_NETRequest *request, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args, DN_NETRequestType type); -void DN_NET_EndFinishedRequest (DN_NETRequest *request); - -#endif // DN_NET_H -#endif -#endif // !defined(DN_H) \ No newline at end of file +#endif // #if !defined(DN_H) \ No newline at end of file diff --git a/Source/Base/dn_base.cpp b/Source/Base/dn_base.cpp deleted file mode 100644 index ba4d8ce..0000000 --- a/Source/Base/dn_base.cpp +++ /dev/null @@ -1,9078 +0,0 @@ -#define DN_BASE_CPP - -#if defined(_CLANGD) - #define DN_ARENA_TEMP_MEM_UAF_GUARD 1 - #define DN_H_WITH_OS 1 - #include "../dn.h" -#endif - -#if DN_STR8_AVX512F - #include -#endif - -enum DN_ArenaUAFCheckReportType_ -{ - DN_ArenaUAFCheckReportType_AllocViolation, - DN_ArenaUAFCheckReportType_TempEndOutOfOrder, -}; - -DN_API bool DN_VerifyArgsF(DN_VerifyType type, bool expr, DN_CallSite call_site, DN_Str8 expr_str8, char const *fmt, ...) -{ - bool result = expr; - if (result) - return result; - - DN_TCScratch scratch = DN_TCScratchBeginArena(nullptr, 0); - { - DN_Str8Builder builder = DN_Str8BuilderFromArena(&scratch.arena); - - // NOTE: Log message prefix - DN_Str8BuilderAppendF(&builder, "Verify [%.*s] failed%s", DN_Str8PrintFmt(expr_str8), fmt ? ". " : ""); - - // NOTE: Log user message - if (fmt) { - va_list args; - va_start(args, fmt); - DN_Str8BuilderAppendFV(&builder, fmt, args); - va_end(args); - } - - // NOTE: Log stack trace - if (DN_PARANOIA_LEVEL) { - if (type == DN_VerifyType_Nil) { - DN_Str8 trace = DN_Str8FromStackTraceNowArena(&scratch.arena, 128 /*limit*/, 4 /*skip*/); - DN_Str8BuilderAppendF(&builder, "\nTrace:\n "); - DN_Str8BuilderAppendRef(&builder, DN_Str8PadNewLinesArena(trace, DN_Str8Lit(" "), &scratch.arena)); - } - } - - DN_Str8 log = DN_Str8FromStr8BuilderArena(&builder, &scratch.arena); - DN_LogType log_type = type == DN_VerifyType_Nil ? DN_LogType_Error : DN_LogType_Warning; - DN_LogTypeParam log_type_param = DN_LogTypeParamFromType(log_type); - DN_LogPrintF(log_type_param, call_site, DN_LogFlags_Nil, "%.*s", DN_Str8PrintFmt(log)); - } - DN_TCScratchEnd(&scratch); - - if (type == DN_VerifyType_Nil && DN_PARANOIA_LEVEL) { - DN_DebugBreak; - } - return result; -} - -DN_API bool DN_VerifyArgs(DN_VerifyType type, bool expr, DN_CallSite call_site, DN_Str8 expr_str8) { - bool result = DN_VerifyArgsF(type, expr, call_site, expr_str8, /*fmt=*/ 0); - return result; -} - -DN_API bool DN_MemStartsWith(void const *lhs, DN_USize lhs_size, void const *rhs, DN_USize rhs_size) -{ - bool result = false; - if (lhs_size >= rhs_size) - result = DN_MemEqUnsafe(lhs, rhs, rhs_size); - return result; -} - -DN_API bool DN_MemEq(void const *lhs, DN_USize lhs_size, void const *rhs, DN_USize rhs_size) -{ - bool result = lhs_size == rhs_size && DN_Memcmp(lhs, rhs, rhs_size) == 0; - return result; -} - -DN_API bool DN_MemEqUnsafe(void const *lhs, void const *rhs, DN_USize size) -{ - bool result = DN_Memcmp(lhs, rhs, size) == 0; - return result; -} - -#if !defined(DN_PLATFORM_ARM64) && !defined(DN_PLATFORM_EMSCRIPTEN) - #define DN_SUPPORTS_CPU_ID -#endif - -#if defined(DN_SUPPORTS_CPU_ID) && (defined(DN_COMPILER_GCC) || defined(DN_COMPILER_CLANG)) - #include -#endif - -DN_CPUFeatureDecl g_dn_cpu_feature_decl[DN_CPUFeature_Count]; - -DN_API DN_U64 DN_AtomicSetValue64(DN_U64 volatile *target, DN_U64 value) -{ -#if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL) - __int64 result; - do { - result = *target; - } while (DN_AtomicCompareExchange64(target, value, result) != result); - return DN_Cast(DN_U64) result; -#elif defined(DN_COMPILER_GCC) || defined(DN_COMPILER_CLANG) - DN_U64 result = __sync_lock_test_and_set(target, value); - return result; -#else - #error Unsupported compiler -#endif -} - -DN_API DN_U32 DN_AtomicSetValue32(DN_U32 volatile *target, DN_U32 value) -{ -#if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL) - long result; - do { - result = *target; - } while (DN_AtomicCompareExchange32(target, value, result) != result); - return result; -#elif defined(DN_COMPILER_GCC) || defined(DN_COMPILER_CLANG) - long result = __sync_lock_test_and_set(target, value); - return result; -#else - #error Unsupported compiler -#endif -} - -DN_API DN_USize DN_AlignUpPowerOfTwoUSize(DN_USize val) -{ - DN_USize leading_zeros = DN_CountLeadingZerosUSize(val); - DN_USize bits = sizeof(DN_USize) * 8 - 1; - DN_USize result = leading_zeros == 0 ? SIZE_MAX : 1ULL << (bits - leading_zeros + 1); - return result; -} - -DN_API DN_U64 DN_AlignUpPowerOfTwoU64(DN_U64 val) -{ - DN_U64 leading_zeros = DN_CountLeadingZerosU64(val); - DN_U64 result = leading_zeros == 0 ? UINT64_MAX : 1ULL << (63 - leading_zeros + 1); - return result; -} - -DN_API DN_U32 DN_AlignUpPowerOfTwoU32(DN_U32 val) -{ - DN_U32 leading_zeros = DN_CountLeadingZerosU32(val); - DN_U32 result = leading_zeros == 0 ? UINT32_MAX : 1ULL << (31 - leading_zeros + 1); - return result; -} - -DN_API DN_CPUIDResult DN_CPUID(DN_CPUIDArgs args) -{ - DN_CPUIDResult result = {}; -#if defined(DN_SUPPORTS_CPU_ID) - __cpuidex(result.values, args.eax, args.ecx); -#endif - return result; -} - -DN_API DN_USize DN_CPUHasFeatureArray(DN_CPUReport const *report, DN_CPUFeatureQuery *features, DN_USize features_size) -{ - DN_USize result = 0; - DN_USize const BITS = sizeof(report->features[0]) * 8; - for (DN_ForIndexU(feature_index, features_size)) { - DN_CPUFeatureQuery *query = features + feature_index; - DN_USize chunk_index = query->feature / BITS; - DN_USize chunk_bit = query->feature % BITS; - DN_U64 chunk = report->features[chunk_index]; - query->available = chunk & (1ULL << chunk_bit); - result += DN_Cast(int) query->available; - } - - return result; -} - -DN_API bool DN_CPUHasFeature(DN_CPUReport const *report, DN_CPUFeature feature) -{ - DN_CPUFeatureQuery query = {}; - query.feature = feature; - bool result = DN_CPUHasFeatureArray(report, &query, 1) == 1; - return result; -} - -DN_API bool DN_CPUHasAllFeatures(DN_CPUReport const *report, DN_CPUFeature const *features, DN_USize features_size) -{ - bool result = true; - for (DN_USize index = 0; result && index < features_size; index++) - result &= DN_CPUHasFeature(report, features[index]); - return result; -} - -DN_API void DN_CPUSetFeature(DN_CPUReport *report, DN_CPUFeature feature) -{ - DN_Assert(feature < DN_CPUFeature_Count); - DN_USize const BITS = sizeof(report->features[0]) * 8; - DN_USize chunk_index = feature / BITS; - DN_USize chunk_bit = feature % BITS; - report->features[chunk_index] |= (1ULL << chunk_bit); -} - -DN_API DN_CPUReport DN_CPUGetReport() -{ - DN_CPUReport result = {}; -#if defined(DN_SUPPORTS_CPU_ID) - DN_CPUIDResult fn_0000_[500] = {}; - DN_CPUIDResult fn_8000_[500] = {}; - int const EXTENDED_FUNC_BASE_EAX = 0x8000'0000; - int const REGISTER_SIZE = sizeof(fn_0000_[0].reg.eax); - - // NOTE: Query standard/extended numbers - { - DN_CPUIDArgs args = {}; - - // NOTE: Query standard function (e.g. eax = 0x0) for function count + cpu vendor - args = {}; - fn_0000_[0] = DN_CPUID(args); - - // NOTE: Query extended function (e.g. eax = 0x8000'0000) for function count + cpu vendor - args = {}; - args.eax = DN_Cast(int) EXTENDED_FUNC_BASE_EAX; - fn_8000_[0] = DN_CPUID(args); - } - - // NOTE: Extract function count - int const STANDARD_FUNC_MAX_EAX = fn_0000_[0x0000].reg.eax; - int const EXTENDED_FUNC_MAX_EAX = fn_8000_[0x0000].reg.eax; - - // NOTE: Enumerate all CPUID results for the known function counts - { - DN_AssertF((STANDARD_FUNC_MAX_EAX + 1) <= DN_ArrayCountI(fn_0000_), - "Max standard count is %d", - STANDARD_FUNC_MAX_EAX + 1); - DN_AssertF((DN_Cast(DN_ISize) EXTENDED_FUNC_MAX_EAX - EXTENDED_FUNC_BASE_EAX + 1) <= DN_ArrayCountI(fn_8000_), - "Max extended count is %zu", - DN_Cast(DN_ISize) EXTENDED_FUNC_MAX_EAX - EXTENDED_FUNC_BASE_EAX + 1); - - for (int eax = 1; eax <= STANDARD_FUNC_MAX_EAX; eax++) { - DN_CPUIDArgs args = {}; - args.eax = eax; - fn_0000_[eax] = DN_CPUID(args); - } - - for (int eax = EXTENDED_FUNC_BASE_EAX + 1, index = 1; eax <= EXTENDED_FUNC_MAX_EAX; eax++, index++) { - DN_CPUIDArgs args = {}; - args.eax = eax; - fn_8000_[index] = DN_CPUID(args); - } - } - - // NOTE: Query CPU vendor - { - DN_Memcpy(result.vendor + 0, &fn_8000_[0x0000].reg.ebx, REGISTER_SIZE); - DN_Memcpy(result.vendor + 4, &fn_8000_[0x0000].reg.edx, REGISTER_SIZE); - DN_Memcpy(result.vendor + 8, &fn_8000_[0x0000].reg.ecx, REGISTER_SIZE); - } - - // NOTE: Query CPU brand - if (EXTENDED_FUNC_MAX_EAX >= (EXTENDED_FUNC_BASE_EAX + 4)) { - DN_Memcpy(result.brand + 0, &fn_8000_[0x0002].reg.eax, REGISTER_SIZE); - DN_Memcpy(result.brand + 4, &fn_8000_[0x0002].reg.ebx, REGISTER_SIZE); - DN_Memcpy(result.brand + 8, &fn_8000_[0x0002].reg.ecx, REGISTER_SIZE); - DN_Memcpy(result.brand + 12, &fn_8000_[0x0002].reg.edx, REGISTER_SIZE); - - DN_Memcpy(result.brand + 16, &fn_8000_[0x0003].reg.eax, REGISTER_SIZE); - DN_Memcpy(result.brand + 20, &fn_8000_[0x0003].reg.ebx, REGISTER_SIZE); - DN_Memcpy(result.brand + 24, &fn_8000_[0x0003].reg.ecx, REGISTER_SIZE); - DN_Memcpy(result.brand + 28, &fn_8000_[0x0003].reg.edx, REGISTER_SIZE); - - DN_Memcpy(result.brand + 32, &fn_8000_[0x0004].reg.eax, REGISTER_SIZE); - DN_Memcpy(result.brand + 36, &fn_8000_[0x0004].reg.ebx, REGISTER_SIZE); - DN_Memcpy(result.brand + 40, &fn_8000_[0x0004].reg.ecx, REGISTER_SIZE); - DN_Memcpy(result.brand + 44, &fn_8000_[0x0004].reg.edx, REGISTER_SIZE); - - DN_Assert(result.brand[sizeof(result.brand) - 1] == 0); - } - - // NOTE: Query CPU features - for (DN_USize ext_index = 0; ext_index < DN_CPUFeature_Count; ext_index++) { - bool available = false; - - // NOTE: Mask bits taken from various manuals - // - AMD64 Architecture Programmer's Manual, Volumes 1-5 - // - https://en.wikipedia.org/wiki/CPUID#Calling_CPUID - switch (DN_Cast(DN_CPUFeature) ext_index) { - case DN_CPUFeature_3DNow: available = (fn_8000_[0x0001].reg.edx & (1 << 31)); break; - case DN_CPUFeature_3DNowExt: available = (fn_8000_[0x0001].reg.edx & (1 << 30)); break; - case DN_CPUFeature_ABM: available = (fn_8000_[0x0001].reg.ecx & (1 << 5)); break; - case DN_CPUFeature_AES: available = (fn_0000_[0x0001].reg.ecx & (1 << 25)); break; - case DN_CPUFeature_AVX: available = (fn_0000_[0x0001].reg.ecx & (1 << 28)); break; - case DN_CPUFeature_AVX2: available = (fn_0000_[0x0007].reg.ebx & (1 << 0)); break; - case DN_CPUFeature_AVX512F: available = (fn_0000_[0x0007].reg.ebx & (1 << 16)); break; - case DN_CPUFeature_AVX512DQ: available = (fn_0000_[0x0007].reg.ebx & (1 << 17)); break; - case DN_CPUFeature_AVX512IFMA: available = (fn_0000_[0x0007].reg.ebx & (1 << 21)); break; - case DN_CPUFeature_AVX512PF: available = (fn_0000_[0x0007].reg.ebx & (1 << 26)); break; - case DN_CPUFeature_AVX512ER: available = (fn_0000_[0x0007].reg.ebx & (1 << 27)); break; - case DN_CPUFeature_AVX512CD: available = (fn_0000_[0x0007].reg.ebx & (1 << 28)); break; - case DN_CPUFeature_AVX512BW: available = (fn_0000_[0x0007].reg.ebx & (1 << 30)); break; - case DN_CPUFeature_AVX512VL: available = (fn_0000_[0x0007].reg.ebx & (1 << 31)); break; - case DN_CPUFeature_AVX512VBMI: available = (fn_0000_[0x0007].reg.ecx & (1 << 1)); break; - case DN_CPUFeature_AVX512VBMI2: available = (fn_0000_[0x0007].reg.ecx & (1 << 6)); break; - case DN_CPUFeature_AVX512VNNI: available = (fn_0000_[0x0007].reg.ecx & (1 << 11)); break; - case DN_CPUFeature_AVX512BITALG: available = (fn_0000_[0x0007].reg.ecx & (1 << 12)); break; - case DN_CPUFeature_AVX512VPOPCNTDQ: available = (fn_0000_[0x0007].reg.ecx & (1 << 14)); break; - case DN_CPUFeature_AVX5124VNNIW: available = (fn_0000_[0x0007].reg.edx & (1 << 2)); break; - case DN_CPUFeature_AVX5124FMAPS: available = (fn_0000_[0x0007].reg.edx & (1 << 3)); break; - case DN_CPUFeature_AVX512VP2INTERSECT: available = (fn_0000_[0x0007].reg.edx & (1 << 8)); break; - case DN_CPUFeature_AVX512FP16: available = (fn_0000_[0x0007].reg.edx & (1 << 23)); break; - case DN_CPUFeature_CLZERO: available = (fn_8000_[0x0008].reg.ebx & (1 << 0)); break; - case DN_CPUFeature_CMPXCHG8B: available = (fn_0000_[0x0001].reg.edx & (1 << 8)); break; - case DN_CPUFeature_CMPXCHG16B: available = (fn_0000_[0x0001].reg.ecx & (1 << 13)); break; - case DN_CPUFeature_F16C: available = (fn_0000_[0x0001].reg.ecx & (1 << 29)); break; - case DN_CPUFeature_FMA: available = (fn_0000_[0x0001].reg.ecx & (1 << 12)); break; - case DN_CPUFeature_FMA4: available = (fn_8000_[0x0001].reg.ecx & (1 << 16)); break; - case DN_CPUFeature_FP128: available = (fn_8000_[0x001A].reg.eax & (1 << 0)); break; - case DN_CPUFeature_FP256: available = (fn_8000_[0x001A].reg.eax & (1 << 2)); break; - case DN_CPUFeature_FPU: available = (fn_0000_[0x0001].reg.edx & (1 << 0)); break; - case DN_CPUFeature_MMX: available = (fn_0000_[0x0001].reg.edx & (1 << 23)); break; - case DN_CPUFeature_MONITOR: available = (fn_0000_[0x0001].reg.ecx & (1 << 3)); break; - case DN_CPUFeature_MOVBE: available = (fn_0000_[0x0001].reg.ecx & (1 << 22)); break; - case DN_CPUFeature_MOVU: available = (fn_8000_[0x001A].reg.eax & (1 << 1)); break; - case DN_CPUFeature_MmxExt: available = (fn_8000_[0x0001].reg.edx & (1 << 22)); break; - case DN_CPUFeature_PCLMULQDQ: available = (fn_0000_[0x0001].reg.ecx & (1 << 1)); break; - case DN_CPUFeature_POPCNT: available = (fn_0000_[0x0001].reg.ecx & (1 << 23)); break; - case DN_CPUFeature_RDRAND: available = (fn_0000_[0x0001].reg.ecx & (1 << 30)); break; - case DN_CPUFeature_RDSEED: available = (fn_0000_[0x0007].reg.ebx & (1 << 18)); break; - case DN_CPUFeature_RDTSCP: available = (fn_8000_[0x0001].reg.edx & (1 << 27)); break; - case DN_CPUFeature_SHA: available = (fn_0000_[0x0007].reg.ebx & (1 << 29)); break; - case DN_CPUFeature_SSE: available = (fn_0000_[0x0001].reg.edx & (1 << 25)); break; - case DN_CPUFeature_SSE2: available = (fn_0000_[0x0001].reg.edx & (1 << 26)); break; - case DN_CPUFeature_SSE3: available = (fn_0000_[0x0001].reg.ecx & (1 << 0)); break; - case DN_CPUFeature_SSE41: available = (fn_0000_[0x0001].reg.ecx & (1 << 19)); break; - case DN_CPUFeature_SSE42: available = (fn_0000_[0x0001].reg.ecx & (1 << 20)); break; - case DN_CPUFeature_SSE4A: available = (fn_8000_[0x0001].reg.ecx & (1 << 6)); break; - case DN_CPUFeature_SSSE3: available = (fn_0000_[0x0001].reg.ecx & (1 << 9)); break; - case DN_CPUFeature_TSC: available = (fn_0000_[0x0001].reg.edx & (1 << 4)); break; - case DN_CPUFeature_TscInvariant: available = (fn_8000_[0x0007].reg.edx & (1 << 8)); break; - case DN_CPUFeature_VAES: available = (fn_0000_[0x0007].reg.ecx & (1 << 9)); break; - case DN_CPUFeature_VPCMULQDQ: available = (fn_0000_[0x0007].reg.ecx & (1 << 10)); break; - case DN_CPUFeature_Count: DN_AssertInvalidCodePath; break; - } - - if (available) - DN_CPUSetFeature(&result, DN_Cast(DN_CPUFeature) ext_index); - } -#endif // DN_SUPPORTS_CPU_ID - return result; -} - -DN_API void DN_TicketMutex_Begin(DN_TicketMutex *mutex) -{ - DN_UInt ticket = DN_AtomicAddU32(&mutex->ticket, 1); - DN_TicketMutex_BeginTicket(mutex, ticket); -} - -DN_API void DN_TicketMutex_End(DN_TicketMutex *mutex) -{ - DN_AtomicAddU32(&mutex->serving, 1); -} - -DN_API DN_UInt DN_TicketMutex_MakeTicket(DN_TicketMutex *mutex) -{ - DN_UInt result = DN_AtomicAddU32(&mutex->ticket, 1); - return result; -} - -DN_API void DN_TicketMutex_BeginTicket(DN_TicketMutex const *mutex, DN_UInt ticket) -{ - DN_AssertF(mutex->serving <= ticket, - "Mutex skipped ticket? Was ticket generated by the correct mutex via MakeTicket? ticket = %u, " - "mutex->serving = %u", - ticket, - mutex->serving); - while (ticket != mutex->serving) { - // NOTE: Use spinlock intrinsic - _mm_pause(); - } -} - -DN_API bool DN_TicketMutex_CanLock(DN_TicketMutex const *mutex, DN_UInt ticket) -{ - bool result = (ticket == mutex->serving); - return result; -} - -#if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL) - #if !defined(DN_CRT_SECURE_NO_WARNINGS_PREVIOUSLY_DEFINED) - #undef _CRT_SECURE_NO_WARNINGS - #endif -#endif - -// NOTE: DN_Bit -DN_API void DN_BitUnsetInplace(DN_USize *flags, DN_USize bitfield) -{ - *flags = (*flags & ~bitfield); -} - -DN_API void DN_BitSetInplace(DN_USize *flags, DN_USize bitfield) -{ - *flags = (*flags | bitfield); -} - -DN_API bool DN_BitIsSet(DN_USize bits, DN_USize bits_to_set) -{ - bool result = DN_Cast(bool)((bits & bits_to_set) == bits_to_set); - return result; -} - -DN_API bool DN_BitIsAny(DN_USize bits, DN_USize bits_to_check) -{ - bool result = DN_Cast(bool)(bits & bits_to_check); - return result; -} - -DN_API bool DN_BitIsNotSet(DN_USize bits, DN_USize bits_to_check) -{ - auto result = !DN_BitIsSet(bits, bits_to_check); - return result; -} - -DN_API DN_I64 DN_SafeAddI64(DN_I64 a, DN_I64 b) -{ - DN_I64 result = a <= INT64_MAX - b ? (a + b) : INT64_MAX; - return result; -} - -DN_API DN_I64 DN_SafeMulI64(DN_I64 a, DN_I64 b) -{ - DN_I64 result = a <= INT64_MAX / b ? (a * b) : INT64_MAX; - return result; -} - -DN_API DN_U64 DN_SafeAddU64(DN_U64 a, DN_U64 b) -{ - DN_U64 result = a <= UINT64_MAX - b ? (a + b) : UINT64_MAX; - return result; -} - -DN_API DN_U64 DN_SafeSubU64(DN_U64 a, DN_U64 b) -{ - DN_U64 result = a >= b ? (a - b) : 0; - return result; -} - -DN_API DN_U64 DN_SafeMulU64(DN_U64 a, DN_U64 b) -{ - DN_U64 result = a <= UINT64_MAX / b ? (a * b) : UINT64_MAX; - return result; -} - -DN_API DN_U32 DN_SafeSubU32(DN_U32 a, DN_U32 b) -{ - DN_U32 result = a >= b ? (a - b) : 0; - return result; -} - -// NOTE: INT*_MAX literals will be promoted to the type of uintmax_t as uintmax_t is the highest -// possible rank (unsigned > signed). -DN_API int DN_SaturateCastUSizeToInt(DN_USize val) -{ - int result = DN_Cast(uintmax_t) val <= INT_MAX ? DN_Cast(int) val : INT_MAX; - return result; -} - -DN_API DN_I8 DN_SaturateCastUSizeToI8(DN_USize val) -{ - DN_I8 result = DN_Cast(uintmax_t) val <= INT8_MAX ? DN_Cast(DN_I8) val : INT8_MAX; - return result; -} - -DN_API DN_I16 DN_SaturateCastUSizeToI16(DN_USize val) -{ - DN_I16 result = DN_Cast(uintmax_t) val <= INT16_MAX ? DN_Cast(DN_I16) val : INT16_MAX; - return result; -} - -DN_API DN_I32 DN_SaturateCastUSizeToI32(DN_USize val) -{ - DN_I32 result = DN_Cast(uintmax_t) val <= INT32_MAX ? DN_Cast(DN_I32) val : INT32_MAX; - return result; -} - -DN_API DN_I64 DN_SaturateCastUSizeToI64(DN_USize val) -{ - DN_I64 result = DN_Cast(uintmax_t) val <= INT64_MAX ? DN_Cast(DN_I64) val : INT64_MAX; - return result; -} - -// NOTE: Both operands are unsigned and the lowest rank operand will be promoted to -// match the highest rank operand. -DN_API DN_U8 DN_SaturateCastUSizeToU8(DN_USize val) -{ - DN_U8 result = val <= UINT8_MAX ? DN_Cast(DN_U8) val : UINT8_MAX; - return result; -} - -DN_API DN_U16 DN_SaturateCastUSizeToU16(DN_USize val) -{ - DN_U16 result = val <= UINT16_MAX ? DN_Cast(DN_U16) val : UINT16_MAX; - return result; -} - -DN_API DN_U32 DN_SaturateCastUSizeToU32(DN_USize val) -{ - DN_U32 result = val <= UINT32_MAX ? DN_Cast(DN_U32) val : UINT32_MAX; - return result; -} - -DN_API DN_U64 DN_SaturateCastUSizeToU64(DN_USize val) -{ - DN_U64 result = DN_Cast(DN_U64) val <= UINT64_MAX ? DN_Cast(DN_U64) val : UINT64_MAX; - return result; -} - -// NOTE: DN_SaturateCastU64To* -DN_API int DN_SaturateCastU64ToInt(DN_U64 val) -{ - int result = val <= INT_MAX ? DN_Cast(int) val : INT_MAX; - return result; -} - -DN_API DN_I8 DN_SaturateCastU64ToI8(DN_U64 val) -{ - DN_I8 result = val <= INT8_MAX ? DN_Cast(DN_I8) val : INT8_MAX; - return result; -} - -DN_API DN_I16 DN_SaturateCastU64ToI16(DN_U64 val) -{ - DN_I16 result = val <= INT16_MAX ? DN_Cast(DN_I16) val : INT16_MAX; - return result; -} - -DN_API DN_I32 DN_SaturateCastU64ToI32(DN_U64 val) -{ - DN_I32 result = val <= INT32_MAX ? DN_Cast(DN_I32) val : INT32_MAX; - return result; -} - -DN_API DN_I64 DN_SaturateCastU64ToI64(DN_U64 val) -{ - DN_I64 result = val <= INT64_MAX ? DN_Cast(DN_I64) val : INT64_MAX; - return result; -} - -// NOTE: Both operands are unsigned and the lowest rank operand will be promoted to match the -// highest rank operand. -DN_API DN_UInt DN_SaturateCastU64ToUInt(DN_U64 val) -{ - DN_UInt result = val <= UINT8_MAX ? DN_Cast(DN_UInt) val : UINT_MAX; - return result; -} - -DN_API DN_U8 DN_SaturateCastU64ToU8(DN_U64 val) -{ - DN_U8 result = val <= UINT8_MAX ? DN_Cast(DN_U8) val : UINT8_MAX; - return result; -} - -DN_API DN_U16 DN_SaturateCastU64ToU16(DN_U64 val) -{ - DN_U16 result = val <= UINT16_MAX ? DN_Cast(DN_U16) val : UINT16_MAX; - return result; -} - -DN_API DN_U32 DN_SaturateCastU64ToU32(DN_U64 val) -{ - DN_U32 result = val <= UINT32_MAX ? DN_Cast(DN_U32) val : UINT32_MAX; - return result; -} - -// NOTE: Both operands are signed so the lowest rank operand will be promoted to match the highest -// rank operand. -DN_API int DN_SaturateCastISizeToInt(DN_ISize val) -{ - DN_Assert(val >= INT_MIN && val <= INT_MAX); - int result = DN_Cast(int) DN_Clamp(val, INT_MIN, INT_MAX); - return result; -} - -DN_API DN_I8 DN_SaturateCastISizeToI8(DN_ISize val) -{ - DN_Assert(val >= INT8_MIN && val <= INT8_MAX); - DN_I8 result = DN_Cast(DN_I8) DN_Clamp(val, INT8_MIN, INT8_MAX); - return result; -} - -DN_API DN_I16 DN_SaturateCastISizeToI16(DN_ISize val) -{ - DN_Assert(val >= INT16_MIN && val <= INT16_MAX); - DN_I16 result = DN_Cast(DN_I16) DN_Clamp(val, INT16_MIN, INT16_MAX); - return result; -} - -DN_API DN_I32 DN_SaturateCastISizeToI32(DN_ISize val) -{ - DN_Assert(val >= INT32_MIN && val <= INT32_MAX); - DN_I32 result = DN_Cast(DN_I32) DN_Clamp(val, INT32_MIN, INT32_MAX); - return result; -} - -DN_API DN_I64 DN_SaturateCastISizeToI64(DN_ISize val) -{ - DN_Assert(DN_Cast(DN_I64) val >= INT64_MIN && DN_Cast(DN_I64) val <= INT64_MAX); - DN_I64 result = DN_Cast(DN_I64) DN_Clamp(DN_Cast(DN_I64) val, INT64_MIN, INT64_MAX); - return result; -} - -// NOTE: 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. -DN_API DN_UInt DN_SaturateCastISizeToUInt(DN_ISize val) -{ - DN_UInt result = 0; - if (val >= DN_Cast(DN_ISize)0) { - if (DN_Cast(uintmax_t) val <= UINT_MAX) - result = DN_Cast(DN_UInt) val; - else - result = UINT_MAX; - } - return result; -} - -DN_API DN_U8 DN_SaturateCastISizeToU8(DN_ISize val) -{ - DN_U8 result = 0; - if (val >= DN_Cast(DN_ISize) 0) { - if (DN_Cast(uintmax_t) val <= UINT8_MAX) - result = DN_Cast(DN_U8) val; - else - result = UINT8_MAX; - } - return result; -} - -DN_API DN_U16 DN_SaturateCastISizeToU16(DN_ISize val) -{ - DN_U16 result = 0; - if (val >= DN_Cast(DN_ISize) 0) { - if (DN_Cast(uintmax_t) val <= UINT16_MAX) - result = DN_Cast(DN_U16) val; - else - result = UINT16_MAX; - } - return result; -} - -DN_API DN_U32 DN_SaturateCastISizeToU32(DN_ISize val) -{ - DN_U32 result = 0; - if (val >= DN_Cast(DN_ISize) 0) { - if (DN_Cast(uintmax_t) val <= UINT32_MAX) - result = DN_Cast(DN_U32) val; - else - result = UINT32_MAX; - } - return result; -} - -DN_API DN_U64 DN_SaturateCastISizeToU64(DN_ISize val) -{ - DN_U64 result = 0; - if (val >= DN_Cast(DN_ISize) 0) { - if (DN_Cast(uintmax_t) val <= UINT64_MAX) - result = DN_Cast(DN_U64) val; - else - result = UINT64_MAX; - } - return result; -} - -// NOTE: Both operands are signed so the lowest rank operand will be promoted to match the highest -// rank operand. -DN_API DN_ISize DN_SaturateCastI64ToISize(DN_I64 val) -{ - DN_ISize result = DN_Cast(DN_I64) DN_Clamp(val, DN_ISIZE_MIN, DN_ISIZE_MAX); - return result; -} - -DN_API DN_I8 DN_SaturateCastI64ToI8(DN_I64 val) -{ - DN_I8 result = DN_Cast(DN_I8) DN_Clamp(val, INT8_MIN, INT8_MAX); - return result; -} - -DN_API DN_I16 DN_SaturateCastI64ToI16(DN_I64 val) -{ - DN_I16 result = DN_Cast(DN_I16) DN_Clamp(val, INT16_MIN, INT16_MAX); - return result; -} - -DN_API DN_I32 DN_SaturateCastI64ToI32(DN_I64 val) -{ - DN_I32 result = DN_Cast(DN_I32) DN_Clamp(val, INT32_MIN, INT32_MAX); - return result; -} - -DN_API DN_UInt DN_SaturateCastI64ToUInt(DN_I64 val) -{ - DN_UInt result = 0; - if (val >= DN_Cast(DN_I64) 0) { - if (DN_Cast(uintmax_t) val <= UINT_MAX) - result = DN_Cast(DN_UInt) val; - else - result = UINT_MAX; - } - return result; -} - -DN_API DN_USize DN_SaturateCastI64ToUSize(DN_I64 val) -{ - DN_USize result = 0; - if (val >= DN_Cast(DN_I64) 0) { - if (DN_Cast(uintmax_t) val <= DN_USIZE_MAX) - result = DN_Cast(DN_USize) val; - else - result = DN_USIZE_MAX; - } - return result; -} - -DN_API DN_U8 DN_SaturateCastI64ToU8(DN_I64 val) -{ - DN_U8 result = 0; - if (val >= DN_Cast(DN_I64) 0) { - if (DN_Cast(uintmax_t) val <= UINT8_MAX) - result = DN_Cast(DN_U8) val; - else - result = UINT8_MAX; - } - return result; -} - -DN_API DN_U16 DN_SaturateCastI64ToU16(DN_I64 val) -{ - DN_U16 result = 0; - if (val >= DN_Cast(DN_I64) 0) { - if (DN_Cast(uintmax_t) val <= UINT16_MAX) - result = DN_Cast(DN_U16) val; - else - result = UINT16_MAX; - } - return result; -} - -DN_API DN_U32 DN_SaturateCastI64ToU32(DN_I64 val) -{ - DN_U32 result = 0; - if (val >= DN_Cast(DN_I64) 0) { - if (DN_Cast(uintmax_t) val <= UINT32_MAX) - result = DN_Cast(DN_U32) val; - else - result = UINT32_MAX; - } - return result; -} - -DN_API DN_U64 DN_SaturateCastI64ToU64(DN_I64 val) -{ - DN_U64 result = 0; - if (val >= DN_Cast(DN_I64) 0) { - if (DN_Cast(uintmax_t) val <= UINT64_MAX) - result = DN_Cast(DN_U64) val; - else - result = UINT64_MAX; - } - return result; -} - -DN_API DN_I8 DN_SaturateCastIntToI8(int val) -{ - DN_I8 result = DN_Cast(DN_I8) DN_Clamp(val, INT8_MIN, INT8_MAX); - return result; -} - -DN_API DN_I16 DN_SaturateCastIntToI16(int val) -{ - DN_I16 result = DN_Cast(DN_I16) DN_Clamp(val, INT16_MIN, INT16_MAX); - return result; -} - -DN_API DN_U8 DN_SaturateCastIntToU8(int val) -{ - DN_U8 result = 0; - if (val >= DN_Cast(DN_ISize) 0) { - if (DN_Cast(uintmax_t) val <= UINT8_MAX) - result = DN_Cast(DN_U8) val; - else - result = UINT8_MAX; - } - return result; -} - -DN_API DN_U16 DN_SaturateCastIntToU16(int val) -{ - DN_U16 result = 0; - if (val >= DN_Cast(DN_ISize) 0) { - if (DN_Cast(uintmax_t) val <= UINT16_MAX) - result = DN_Cast(DN_U16) val; - else - result = UINT16_MAX; - } - return result; -} - -DN_API DN_U32 DN_SaturateCastIntToU32(int val) -{ - DN_StaticAssert(sizeof(val) <= sizeof(DN_U32) && "Sanity check to allow simplifying of casting"); - DN_U32 result = 0; - if (val >= 0) - result = DN_Cast(DN_U32) val; - return result; -} - -DN_API DN_U64 DN_SaturateCastIntToU64(int val) -{ - DN_StaticAssert(sizeof(val) <= sizeof(DN_U64) && "Sanity check to allow simplifying of casting"); - DN_U64 result = 0; - if (val >= 0) - result = DN_Cast(DN_U64) val; - return result; -} - -// NOTE: DN_Asan -DN_StaticAssert(DN_IsPowerOfTwoAligned(DN_ASAN_POISON_GUARD_SIZE, DN_ASAN_POISON_ALIGNMENT) && - "ASAN poison guard size must be a power-of-two and aligned to ASAN's alignment" "requirement (8 bytes)"); -DN_API void DN_ASanPoisonMemoryRegion(void const volatile *ptr, DN_USize size) -{ - if (!ptr || !size) - return; - -#if DN_HAS_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__) - DN_AssertF(DN_IsPowerOfTwoAligned(ptr, 8), - "Poisoning requires the pointer to be aligned on an 8 byte boundary"); - - __asan_poison_memory_region(ptr, size); - if (DN_ASAN_VET_POISON) { - DN_AssertAlways(__asan_address_is_poisoned(ptr)); - DN_AssertAlways(__asan_address_is_poisoned((char *)ptr + (size - 1))); - } -#else - (void)ptr; - (void)size; -#endif -} - -DN_API void DN_ASanUnpoisonMemoryRegion(void const volatile *ptr, DN_USize size) -{ - if (!ptr || !size) - return; - -#if DN_HAS_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__) - __asan_unpoison_memory_region(ptr, size); - if (DN_ASAN_VET_POISON) - DN_AssertAlways(__asan_region_is_poisoned((void *)ptr, size) == 0); -#else - (void)ptr; - (void)size; -#endif -} - -DN_API DN_F32 DN_EpsilonClampF32(DN_F32 value, DN_F32 target, DN_F32 epsilon) -{ - DN_F32 delta = DN_Abs(target - value); - DN_F32 result = (delta < epsilon) ? target : value; - return result; -} - -static DN_MemBlock *DN_ArenaBlockFromMemFuncs_(DN_U64 reserve, DN_U64 commit, bool track_alloc, bool alloc_can_leak, DN_MemFuncs mem_funcs) -{ - DN_MemBlock *result = nullptr; - switch (mem_funcs.type) { - case DN_MemFuncsType_Nil: - break; - - case DN_MemFuncsType_Heap: { - DN_AssertF(reserve > DN_ARENA_HEADER_SIZE, "%I64u > %I64u", reserve, DN_ARENA_HEADER_SIZE); - result = DN_Cast(DN_MemBlock *) mem_funcs.heap_alloc(reserve); - if (!result) - return result; - - result->used = DN_ARENA_HEADER_SIZE; - result->commit = reserve; - result->reserve = reserve; - } break; - - case DN_MemFuncsType_Virtual: { - DN_AssertF(mem_funcs.virtual_page_size, "Page size must be set to a non-zero, power of two value"); - DN_Assert(DN_IsPowerOfTwo(mem_funcs.virtual_page_size)); - - DN_USize const page_size = mem_funcs.virtual_page_size; - DN_U64 real_reserve = reserve ? reserve : DN_ARENA_RESERVE_SIZE; - DN_U64 real_commit = commit ? commit : DN_ARENA_COMMIT_SIZE; - real_reserve = DN_AlignUpPowerOfTwo(real_reserve, page_size); - real_commit = DN_Min(DN_AlignUpPowerOfTwo(real_commit, page_size), real_reserve); - DN_AssertF(DN_ARENA_HEADER_SIZE < real_commit && real_commit <= real_reserve, "%I64u < %I64u <= %I64u", DN_ARENA_HEADER_SIZE, real_commit, real_reserve); - - DN_MemCommit mem_commit = real_reserve == real_commit ? DN_MemCommit_Yes : DN_MemCommit_No; - result = DN_Cast(DN_MemBlock *) mem_funcs.virtual_reserve(real_reserve, mem_commit, DN_MemPage_ReadWrite); - if (!result) - return result; - - if (mem_commit == DN_MemCommit_No && !mem_funcs.virtual_commit(result, real_commit, DN_MemPage_ReadWrite)) { - mem_funcs.virtual_release(result, real_reserve); - return result; - } - - result->used = DN_ARENA_HEADER_SIZE; - result->commit = real_commit; - result->reserve = real_reserve; - } break; - } - - if (track_alloc && result) - DN_LeakTrackAlloc(&g_dn_->leak, result, result->reserve, alloc_can_leak); - - return result; -} - -static bool DN_ArenaHasPoison_(DN_MemFlags flags) -{ - DN_MSVC_WARNING_PUSH - DN_MSVC_WARNING_DISABLE(6237) // warning C6237: ( && ) is always zero. is never evaluated and might have side effects. - bool result = DN_ASAN_POISON && DN_BitIsNotSet(flags, DN_MemFlags_NoPoison); - DN_MSVC_WARNING_POP - return result; -} - -static DN_MemBlock *DN_MemBlockFromMemFuncsFlags_(DN_U64 reserve, DN_U64 commit, DN_MemFlags flags, DN_MemFuncs mem_funcs) -{ - bool track_alloc = (flags & DN_MemFlags_NoAllocTrack) == 0; - bool alloc_can_leak = flags & DN_MemFlags_AllocCanLeak; - DN_MemBlock *result = DN_ArenaBlockFromMemFuncs_(reserve, commit, track_alloc, alloc_can_leak, mem_funcs); - if (result && DN_ArenaHasPoison_(flags)) - DN_ASanPoisonMemoryRegion(DN_Cast(char *) result + DN_ARENA_HEADER_SIZE, result->commit - DN_ARENA_HEADER_SIZE); - return result; -} - -static void DN_MemListOnNewBlock_(DN_MemList *mem, DN_MemBlock const *block) -{ - DN_Assert(mem); - if (block) { - mem->stats.info.used += block->used; - mem->stats.info.commit += block->commit; - mem->stats.info.reserve += block->reserve; - mem->stats.info.blocks += 1; - - mem->stats.hwm.used = DN_Max(mem->stats.hwm.used, mem->stats.info.used); - mem->stats.hwm.commit = DN_Max(mem->stats.hwm.commit, mem->stats.info.commit); - mem->stats.hwm.reserve = DN_Max(mem->stats.hwm.reserve, mem->stats.info.reserve); - mem->stats.hwm.blocks = DN_Max(mem->stats.hwm.blocks, mem->stats.info.blocks); - } -} - -DN_API DN_MemStats DN_MemStatsSum(DN_MemStats lhs, DN_MemStats rhs) -{ - DN_MemStats array[] = {lhs, rhs}; - DN_MemStats result = DN_MemStatsSumArray(array, DN_ArrayCountU(array)); - return result; -} - -DN_API DN_MemStats DN_MemStatsSumArray(DN_MemStats const *array, DN_USize size) -{ - DN_MemStats result = {}; - for (DN_ForItSize(it, DN_MemStats const, array, size)) { - DN_MemStats stats = *it.data; - result.info.used += stats.info.used; - result.info.commit += stats.info.commit; - result.info.reserve += stats.info.reserve; - result.info.blocks += stats.info.blocks; - - result.hwm.used = DN_Max(result.hwm.used, result.info.used); - result.hwm.commit = DN_Max(result.hwm.commit, result.info.commit); - result.hwm.reserve = DN_Max(result.hwm.reserve, result.info.reserve); - result.hwm.blocks = DN_Max(result.hwm.blocks, result.info.blocks); - } - return result; -} - -DN_API DN_MemList DN_MemListFromBuffer(void *buffer, DN_USize size, DN_MemFlags flags) -{ - DN_Assert(buffer); - DN_AssertF(DN_ARENA_HEADER_SIZE < size, "Buffer (%zu bytes) too small, need atleast %zu bytes to store arena metadata", size, DN_ARENA_HEADER_SIZE); - DN_AssertF(DN_IsPowerOfTwo(size), "Buffer (%zu bytes) must be a power-of-two", size); - - // NOTE: Init block - DN_MemBlock *block = DN_Cast(DN_MemBlock *) buffer; - block->commit = size; - block->reserve = size; - block->used = DN_ARENA_HEADER_SIZE; - if (block && DN_ArenaHasPoison_(flags)) - DN_ASanPoisonMemoryRegion(DN_Cast(char *) block + DN_ARENA_HEADER_SIZE, block->commit - DN_ARENA_HEADER_SIZE); - - DN_MemList result = {}; - result.flags = flags | DN_MemFlags_NoGrow | DN_MemFlags_NoAllocTrack | DN_MemFlags_AllocCanLeak | DN_MemFlags_UserBuffer; - result.curr = block; - DN_MemListOnNewBlock_(&result, result.curr); - return result; -} - -DN_API DN_MemList DN_MemListFromMemFuncs(DN_U64 reserve, DN_U64 commit, DN_MemFlags flags, DN_MemFuncs mem_funcs) -{ - DN_MemList result = {}; - result.funcs = mem_funcs; - result.flags |= flags | DN_MemFlags_MemFuncs; - result.curr = DN_MemBlockFromMemFuncsFlags_(reserve, commit, flags, mem_funcs); - DN_MemListOnNewBlock_(&result, result.curr); - return result; -} - -static void DN_MemBlockDeinit_(DN_MemList const *mem, DN_MemBlock *block) -{ - DN_USize release_size = block->reserve; - if (DN_BitIsNotSet(mem->flags, DN_MemFlags_NoAllocTrack)) - DN_LeakTrackDealloc(&g_dn_->leak, block); - - if (DN_ArenaHasPoison_(mem->flags)) - DN_ASanUnpoisonMemoryRegion(block, block->commit); - - if (mem->flags & DN_MemFlags_MemFuncs) { - if (mem->funcs.type == DN_MemFuncsType_Heap) - mem->funcs.heap_dealloc(block); - else - mem->funcs.virtual_release(block, release_size); - } -} - -DN_API void DN_MemListDeinit(DN_MemList *mem) -{ - bool mem_allocated_from_itself = DN_MemListOwnsPtr(mem, mem); - for (DN_MemBlock *block = mem ? mem->curr : nullptr; block;) { - DN_MemBlock *block_to_free = block; - block = block->prev; - DN_MemBlockDeinit_(mem, block_to_free); - } - if (mem && !mem_allocated_from_itself) - *mem = {}; -} - -DN_API bool DN_MemListCommitTo(DN_MemList *mem, DN_U64 pos) -{ - if (!mem || !mem->curr) - return false; - - // NOTE: Early out if the position to commit to is already committed - DN_MemBlock *curr = mem->curr; - if (pos <= curr->commit) - return true; - - // NOTE: Sanity check position is within the bounds of the memory block - DN_U64 real_pos = pos; - if (pos > curr->reserve) { - DN_Assert(pos <= curr->reserve); - real_pos = curr->reserve; - } - - // NOTE: Do the commit - DN_Assert(mem->funcs.virtual_page_size); - DN_USize end_commit = DN_AlignUpPowerOfTwo(real_pos, mem->funcs.virtual_page_size); - DN_USize commit_size = end_commit - curr->commit; - char *commit_ptr = DN_Cast(char *) curr + curr->commit; - if (!mem->funcs.virtual_commit(commit_ptr, commit_size, DN_MemPage_ReadWrite)) - return false; - - if (DN_ArenaHasPoison_(mem->flags)) - DN_ASanPoisonMemoryRegion(commit_ptr, commit_size); - - curr->commit = end_commit; - return true; -} - -DN_API bool DN_MemListCommit(DN_MemList *mem, DN_U64 size) -{ - if (!mem || !mem->curr) - return false; - DN_U64 pos = DN_Min(mem->curr->reserve, mem->curr->commit + size); - bool result = DN_MemListCommitTo(mem, pos); - return result; -} - -DN_API bool DN_MemListGrow(DN_MemList *mem, DN_U64 reserve, DN_U64 commit) -{ - if (mem->flags & (DN_MemFlags_NoGrow | DN_MemFlags_UserBuffer)) - return false; - - bool result = false; - DN_MemBlock *new_block = DN_MemBlockFromMemFuncsFlags_(reserve, commit, mem->flags, mem->funcs); - if (new_block) { - result = true; - new_block->prev = mem->curr; - mem->curr = new_block; - new_block->reserve_sum = new_block->prev->reserve_sum + new_block->prev->reserve; - DN_MemListOnNewBlock_(mem, mem->curr); - } - return result; -} - -DN_API void *DN_MemListAlloc(DN_MemList *mem, DN_U64 size, DN_U8 align, DN_ZMem z_mem) -{ - if (!mem) - return nullptr; - - if (!mem->curr) { - mem->curr = DN_MemBlockFromMemFuncsFlags_(DN_ARENA_RESERVE_SIZE, DN_ARENA_COMMIT_SIZE, mem->flags, mem->funcs); - DN_MemListOnNewBlock_(mem, mem->curr); - } - - if (!mem->curr) - return nullptr; - - try_alloc_again: - DN_MemBlock *curr = mem->curr; - bool poison = DN_ArenaHasPoison_(mem->flags); - DN_U8 real_align = poison ? DN_Max(align, DN_ASAN_POISON_ALIGNMENT) : align; - DN_U64 offset_pos = DN_AlignUpPowerOfTwo(curr->used, real_align) + (poison ? DN_ASAN_POISON_GUARD_SIZE : 0); - DN_U64 end_pos = offset_pos + size; - DN_U64 alloc_size = end_pos - curr->used; - - if (end_pos > curr->reserve) { - if (mem->flags & (DN_MemFlags_NoGrow | DN_MemFlags_UserBuffer)) - return nullptr; - DN_USize new_reserve = DN_Max(DN_ARENA_HEADER_SIZE + alloc_size, DN_ARENA_RESERVE_SIZE); - DN_USize new_commit = DN_Max(DN_ARENA_HEADER_SIZE + alloc_size, DN_ARENA_COMMIT_SIZE); - if (!DN_MemListGrow(mem, new_reserve, new_commit)) - return nullptr; - goto try_alloc_again; - } - - DN_USize prev_arena_commit = curr->commit; - if (end_pos > curr->commit) { - DN_Assert(mem->funcs.virtual_page_size); - DN_Assert(mem->funcs.type == DN_MemFuncsType_Virtual); - DN_Assert((mem->flags & DN_MemFlags_UserBuffer) == 0); - DN_USize end_commit = DN_AlignUpPowerOfTwo(end_pos, mem->funcs.virtual_page_size); - DN_USize commit_size = end_commit - curr->commit; - char *commit_ptr = DN_Cast(char *) curr + curr->commit; - if (!mem->funcs.virtual_commit(commit_ptr, commit_size, DN_MemPage_ReadWrite)) - return nullptr; - if (poison && DN_BitIsNotSet(mem->flags, DN_MemFlags_SimAlloc)) - DN_ASanPoisonMemoryRegion(commit_ptr, commit_size); - curr->commit = end_commit; - mem->stats.info.commit += commit_size; - mem->stats.hwm.commit = DN_Max(mem->stats.hwm.commit, mem->stats.info.commit); - } - - void *result = DN_Cast(char *) curr + offset_pos; - curr->used += alloc_size; - mem->stats.info.used += alloc_size; - mem->stats.hwm.used = DN_Max(mem->stats.hwm.used, mem->stats.info.used); - - if (poison && DN_BitIsNotSet(mem->flags, DN_MemFlags_SimAlloc)) - DN_ASanUnpoisonMemoryRegion(result, size); - - if (z_mem == DN_ZMem_Yes && DN_BitIsNotSet(mem->flags, DN_MemFlags_SimAlloc)) { - DN_USize reused_bytes = DN_Min(prev_arena_commit - offset_pos, size); - DN_Memset(result, 0, reused_bytes); - } - - DN_Assert(mem->stats.hwm.used >= mem->stats.info.used); - DN_Assert(mem->stats.hwm.commit >= mem->stats.info.commit); - DN_Assert(mem->stats.hwm.reserve >= mem->stats.info.reserve); - DN_Assert(mem->stats.hwm.blocks >= mem->stats.info.blocks); - return result; -} - -DN_API void *DN_MemListAllocContiguous(DN_MemList *mem, DN_U64 size, DN_U8 align, DN_ZMem z_mem) -{ - DN_MemFlags prev_flags = mem->flags; - mem->flags |= (DN_MemFlags_NoGrow | DN_MemFlags_NoPoison); - void *memory = DN_MemListAlloc(mem, size, align, z_mem); - mem->flags = prev_flags; - return memory; -} - -DN_API void *DN_MemListCopy(DN_MemList *mem, void const *data, DN_U64 size, DN_U8 align) -{ - if (!mem || !data || size == 0) - return nullptr; - void *result = DN_MemListAlloc(mem, size, align, DN_ZMem_No); - if (result) - DN_Memcpy(result, data, size); - return result; -} - -DN_API void DN_MemListPopTo(DN_MemList *mem, DN_U64 init_used) -{ - if (!mem || !mem->curr) - return; - - // NOTE: Free any memory blocks allocated additionally from the initial block to revert to - DN_U64 used = DN_Max(DN_ARENA_HEADER_SIZE, init_used); - DN_MemBlock *curr = mem->curr; - while (curr->reserve_sum >= used) { - DN_MemBlock *block_to_free = curr; - mem->stats.info.used -= block_to_free->used; - mem->stats.info.commit -= block_to_free->commit; - mem->stats.info.reserve -= block_to_free->reserve; - mem->stats.info.blocks -= 1; - if (mem->flags & DN_MemFlags_UserBuffer) - break; - curr = curr->prev; - DN_MemBlockDeinit_(mem, block_to_free); - } - - // NOTE: Revert the memory block we returned to - DN_U64 old_used = curr->used; - mem->stats.info.used = old_used; - mem->curr = curr; - curr->used = used - curr->reserve_sum; - - // NOTE: Scrub memory that we used previously in the block but no longer after reverting - DN_MSVC_WARNING_PUSH - DN_MSVC_WARNING_DISABLE(4127) // conditional expression is constant - if (DN_SCRUB_UNINIT_MEM_BYTE) { - if (old_used > curr->used) { - char *discarded = (char *)curr + curr->used; - DN_USize scrub_size = old_used - curr->used; - - // NOTE: If we allocated memory unaligned then the pointer given to the user was aligned up - // and unpoisoned. If the user snapped a memory list position before that allocation then - // attempts to revert it, scrubbing from the memory position (which is before alignment was - // applied!) will cause this code to accidentally scrub the our poison guard bytes. So we - // unpoison the region unconditionally to ensure that is cleaned up before scrubbing. Since - // scrubbing is a debug feature and, you have it turned on _with_ ASAN then we let that - // performance penalty slide. - if (DN_ArenaHasPoison_(mem->flags)) - DN_ASanUnpoisonMemoryRegion(discarded, scrub_size); - - DN_Memset(discarded, DN_SCRUB_UNINIT_MEM_BYTE, scrub_size); - } - } - DN_MSVC_WARNING_POP - - // NOTE: ASAN Poison - if (DN_ArenaHasPoison_(mem->flags)) { - char *poison_ptr = (char *)curr + DN_AlignUpPowerOfTwo(curr->used, DN_ASAN_POISON_ALIGNMENT); - DN_USize poison_size = ((char *)curr + curr->commit) - poison_ptr; - DN_ASanPoisonMemoryRegion(poison_ptr, poison_size); - } - mem->stats.info.used += curr->used; -} - -DN_API void DN_MemListPop(DN_MemList *mem, DN_U64 amount) -{ - DN_MemBlock *curr = mem->curr; - DN_USize used_sum = curr->reserve_sum + curr->used; - amount = DN_Min(amount, used_sum); - DN_USize pop_to = used_sum - amount; - DN_MemListPopTo(mem, pop_to); -} - -DN_API DN_U64 DN_MemListPos(DN_MemList const *mem) -{ - DN_U64 result = (mem && mem->curr) ? mem->curr->reserve_sum + mem->curr->used : 0; - return result; -} - -DN_API void DN_MemListClear(DN_MemList *mem) -{ - DN_MemListPopTo(mem, 0); -} - -DN_API bool DN_MemListOwnsPtr(DN_MemList const *mem, void *ptr) -{ - bool result = false; - uintptr_t uint_ptr = DN_Cast(uintptr_t) ptr; - for (DN_MemBlock const *block = mem ? mem->curr : nullptr; !result && block; block = block->prev) { - uintptr_t begin = DN_Cast(uintptr_t) block + DN_ARENA_HEADER_SIZE; - uintptr_t end = begin + block->reserve; - result = uint_ptr >= begin && uint_ptr <= end; - } - return result; -} - -DN_API DN_Str8x64 DN_MemListInfoStr8x64(DN_MemListInfo info) -{ - DN_Str8x64 result = {}; - DN_Str8x32 used = DN_Str8x32FromByteCountU64Auto(info.used); - DN_Str8x32 commit = DN_Str8x32FromByteCountU64Auto(info.commit); - DN_Str8x32 reserve = DN_Str8x32FromByteCountU64Auto(info.reserve); - // NOTE: Blocks, Used, Commit, Reserve - result = DN_Str8x64FromFmt("B=%u U=%.*s C=%.*s R=%.*s", DN_Cast(DN_U32)info.blocks, DN_Str8PrintFmt(used), DN_Str8PrintFmt(commit), DN_Str8PrintFmt(reserve)); - return result; -} - -DN_API DN_MemListTemp DN_MemListTempBegin(DN_MemList *mem) -{ - DN_MemListTemp result = {}; - if (mem) { - result.mem = mem; - result.used_sum = mem->curr ? mem->curr->reserve_sum + mem->curr->used : 0; - } - return result; -}; - -DN_API void DN_MemListTempEnd(DN_MemListTemp temp) -{ - DN_MemListPopTo(temp.mem, temp.used_sum); -}; - -DN_Str8 const DN_MEM_LIST_UAF_TRACING_DISABLED_MORE_INFO_STR8_ = DN_Str8Lit( - "\n\nSet `DN_MemFlags_TempMemUAFTrace` on the affected arenas or " - "`#define DN_ARENA_TEMP_MEM_UAF_TRACE_ON_BY_DEFAULT 1` for more information" -); - -#if defined(DN_ARENA_TEMP_MEM_UAF_GUARD) -static bool DN_MemListUAFTracingEnabled_(DN_MemList *mem) -{ - bool result = DN_ARENA_TEMP_MEM_UAF_TRACE_ON_BY_DEFAULT; - if (!result) - result = mem->flags & DN_MemFlags_TempMemUAFTrace; - if (mem->flags & DN_MemFlags_TempMemUAFTraceDisable) - result = false; - return result; -} -#endif - -static void DN_ArenaUAFCheck_(DN_Arena *arena, DN_ArenaUAFCheckReportType_ type) -{ - (void)arena; - (void)type; - #if DN_ARENA_TEMP_MEM_UAF_GUARD - DN_MemList *mem = arena->mem; - if (!arena || !mem) - return; - - if ((arena->uaf_guard_temp_mem || mem->uaf_guard_active_temp_mem) && !arena->uaf_guard_is_being_checked) { - // NOTE: The following functions below allocate memory which might trigger an additional UAF - // check which would cause infinite recursion so we set a flag here to prevent that. - arena->uaf_guard_is_being_checked = true; - if (mem->uaf_guard_active_id != arena->uaf_guard_id) { - // NOTE: We use the MemList on the arena directly to bypass any potential recursive UAF (if the - // current arena is triggering the UAF check then it's already violating so we use the - // underlying primitive to allocate memory). - DN_Allocator allocator = DN_AllocatorFromMemList(mem); - - // NOTE: MSVC does not recognise %'u which is a STB extension which causes a lot of incorrect - // format arguments warnings that we mute here. - DN_MSVC_WARNING_PUSH - DN_MSVC_WARNING_DISABLE(6271) // Extra argument passed to 'DN_Str8FromFmtArena' - DN_MSVC_WARNING_DISABLE(6067) // _Param_(10) in call to 'DN_LogPrint' must be the address of a string. Actual type: 'int'. - DN_MSVC_WARNING_DISABLE(6273) // Non-integer passed as _Param_(11) when an integer is required in call to 'DN_LogPrint' Actual type: 'char *'. - DN_Str8 error_msg = {}; - if (type == DN_ArenaUAFCheckReportType_AllocViolation) { - error_msg = DN_Str8FromFmtAllocator(allocator, - "Arena use-after-free (UAF) detected in temporary memory usage! This allocation (trace " - "shown above) is attempting to allocate memory inside the active temporary region (id: %'u) " - "but belongs to a different region (id: %'u). This means when the active temporary region is " - "released, this allocation will be released and scrubbed causing a potential UAF.\n\nEnsure " - "that scratch memory is deconflicting correctly, scratch and or temporary memory regions have " - "matching begin and end pairs and only the arena view with the active temporary memory region " - "is being allocated from.", - mem->uaf_guard_active_id, - arena->uaf_guard_id); - } else { - error_msg = DN_Str8Lit("The active temporary memory region recorded on the arena is " - "different from the current temporary memory region recorded on " - "the memory list allocator. This means that a temporary region " - "began but was not ended after the region was completed. Temporary " - "memory regions are enforced in a first-in-last-out manner (FILO) " - "to ensure the developer's intent of what the temporary region " - "spans is logically consistent and always strictly ends and begins " - "within a known lifetime."); - } - - DN_Str8 prefix = DN_Str8LineBreakAllocator(error_msg, 100, DN_Str8Lit("\n"), DN_Str8LineBreakMode_AtWord, allocator); - if (DN_MemListUAFTracingEnabled_(mem)) { - DN_Str8 curr_stack_trace = DN_Str8Lit(""); - if (arena->uaf_guard_temp_mem) - curr_stack_trace = DN_Str8FromStackTraceAllocator(allocator, &arena->uaf_guard_temp_mem->trace, 1); - curr_stack_trace = DN_Str8PadNewLinesAllocator(curr_stack_trace, DN_Str8Lit(" "), allocator); - - DN_Str8 active_stack_trace = DN_Str8Lit(""); - if (mem->uaf_guard_active_temp_mem) - active_stack_trace = DN_Str8FromStackTraceAllocator(allocator, &mem->uaf_guard_active_temp_mem->trace, 1); - active_stack_trace = DN_Str8PadNewLinesAllocator(active_stack_trace, DN_Str8Lit(" "), allocator); - - DN_AssertF(mem->uaf_guard_active_id == arena->uaf_guard_id, - "%.*s\n\nThe originating temporary memory region (id: %'u) was created at:" - "\n\n %.*s\n\nThe active temporary memory region (id: %'u) was created at:\n\n %.*s\n", - DN_Str8PrintFmt(prefix), - arena->uaf_guard_id, - DN_Str8PrintFmt(curr_stack_trace), - mem->uaf_guard_active_id, - DN_Str8PrintFmt(active_stack_trace)); - } else { - DN_Str8 suffix = DN_Str8LineBreakAllocator(DN_MEM_LIST_UAF_TRACING_DISABLED_MORE_INFO_STR8_, 100, DN_Str8Lit("\n"), DN_Str8LineBreakMode_AtWord, allocator); - DN_AssertF(mem->uaf_guard_active_id == arena->uaf_guard_id, "%.*s%.*s", DN_Str8PrintFmt(prefix), DN_Str8PrintFmt(suffix)); - } - DN_MSVC_WARNING_POP - } - arena->uaf_guard_is_being_checked = false; - } - #endif -} - -DN_API DN_Arena DN_ArenaFromMemList(DN_MemList *mem) -{ - DN_Arena result = {}; - result.mem = mem; - return result; -} - -DN_API DN_Arena DN_ArenaTempBeginFromMemList(DN_MemList* mem) -{ - DN_Arena result = DN_ArenaFromMemList(mem); - DN_MemListTemp temp_mem = DN_MemListTempBegin(mem); - -#if DN_ARENA_TEMP_MEM_UAF_GUARD - // NOTE: Below we use the `MemList` and bypass the UAF checks which could cause infinite recursion - // depending on how, say, stack-traces are implemented. - if (DN_MemListUAFTracingEnabled_(mem)) - temp_mem.trace = DN_StackTraceFromAllocator(DN_AllocatorFromMemList(mem), 256); - - // NOTE: Create persistent temp mem and set it on the mem list - result.uaf_guard_temp_mem = DN_MemListNewCopy(mem, DN_MemListTemp, &temp_mem); - result.uaf_guard_prev_temp_mem = mem->uaf_guard_active_temp_mem; - mem->uaf_guard_active_temp_mem = result.uaf_guard_temp_mem; - - // NOTE: Update IDs - result.uaf_guard_id = ++mem->uaf_guard_next_id; - result.uaf_guard_prev_id = mem->uaf_guard_active_id; - mem->uaf_guard_active_id = result.uaf_guard_id; -#else - result.temp_mem = temp_mem; -#endif - return result; -} - - -DN_API DN_Arena DN_ArenaTempBeginFromArena(DN_Arena *arena) -{ - DN_Arena result = DN_ArenaTempBeginFromMemList(arena->mem); - return result; -} - -DN_API void DN_ArenaTempEnd(DN_Arena *arena, DN_ArenaReset reset) -{ -#if DN_ARENA_TEMP_MEM_UAF_GUARD - DN_AssertF(arena->uaf_guard_temp_mem, "Arena was not created with temp memory"); - DN_ArenaUAFCheck_(arena, DN_ArenaUAFCheckReportType_TempEndOutOfOrder); -#else - DN_AssertF(arena->temp_mem.mem, "Arena was not created with temp memory"); -#endif - - if (reset == DN_ArenaReset_Yes) { -#if DN_ARENA_TEMP_MEM_UAF_GUARD - DN_MemListTempEnd(*arena->uaf_guard_temp_mem); -#else - DN_MemListTempEnd(arena->temp_mem); -#endif - } - -#if DN_ARENA_TEMP_MEM_UAF_GUARD - DN_MemList *mem = arena->mem; - mem->uaf_guard_active_id = arena->uaf_guard_prev_id; - mem->uaf_guard_active_temp_mem = arena->uaf_guard_prev_temp_mem; - - arena->uaf_guard_prev_temp_mem = nullptr; - arena->uaf_guard_prev_id = 0; - arena->uaf_guard_temp_mem = nullptr; -#endif -} - -DN_API void *DN_ArenaAlloc(DN_Arena *arena, DN_U64 size, DN_U8 align, DN_ZMem z_mem) -{ - DN_ArenaUAFCheck_(arena, DN_ArenaUAFCheckReportType_AllocViolation); - void *result = DN_MemListAlloc(arena->mem, size, align, z_mem); - return result; -} - -DN_API void *DN_ArenaAllocContiguous(DN_Arena *arena, DN_U64 size, DN_U8 align, DN_ZMem z_mem) -{ - DN_ArenaUAFCheck_(arena, DN_ArenaUAFCheckReportType_AllocViolation); - void *result = DN_MemListAllocContiguous(arena->mem, size, align, z_mem); - return result; -} - -DN_API void *DN_ArenaCopy(DN_Arena *arena, void const *data, DN_U64 size, DN_U8 align) -{ - DN_ArenaUAFCheck_(arena, DN_ArenaUAFCheckReportType_AllocViolation); - void *result = DN_MemListCopy(arena->mem, data, size, align); - return result; -} - -DN_API DN_Pool DN_PoolFromArena(DN_Arena *arena, DN_U8 align) -{ - DN_Pool result = {}; - if (arena) { - result.arena = arena; - result.align = align ? align : DN_POOL_DEFAULT_ALIGN; - } - return result; -} - -DN_API bool DN_PoolIsValid(DN_Pool const *pool) -{ - bool result = pool && pool->arena && pool->align; - return result; -} - -DN_API void *DN_PoolAlloc(DN_Pool *pool, DN_USize size) -{ - void *result = nullptr; - if (!DN_PoolIsValid(pool)) - return result; - - DN_USize const required_size = sizeof(DN_PoolSlot) + pool->align + size; - DN_USize const DN_USizeo_slot_offset = 5; // __lzcnt64(32) e.g. DN_PoolSlotSize_32B - DN_USize slot_index = 0; - if (required_size > 32) { - // NOTE: Round up if not PoT as the low bits are set. - DN_USize dist_to_next_msb = DN_CountLeadingZerosUSize(required_size) + 1; - dist_to_next_msb -= DN_Cast(DN_USize)(!DN_IsPowerOfTwo(required_size)); - - DN_USize const register_size = sizeof(DN_USize) * 8; - DN_AssertF(register_size >= (dist_to_next_msb - DN_USizeo_slot_offset), "lhs=%zu, rhs=%zu", register_size, (dist_to_next_msb - DN_USizeo_slot_offset)); - slot_index = register_size - dist_to_next_msb - DN_USizeo_slot_offset; - } - - if (slot_index >= DN_PoolSlotSize_Count) { - DN_AssertF(slot_index < DN_PoolSlotSize_Count, "Chunk pool does not support the requested allocation size"); - return result; - } - - DN_USize slot_size_in_bytes = 1ULL << (slot_index + DN_USizeo_slot_offset); - DN_AssertF(required_size <= (slot_size_in_bytes << 0), "slot_index=%zu, lhs=%zu, rhs=%zu", slot_index, required_size, (slot_size_in_bytes << 0)); - DN_AssertF(required_size >= (slot_size_in_bytes >> 1), "slot_index=%zu, lhs=%zu, rhs=%zu", slot_index, required_size, (slot_size_in_bytes >> 1)); - - DN_PoolSlot *slot = nullptr; - if (pool->slots[slot_index]) { - slot = pool->slots[slot_index]; - pool->slots[slot_index] = slot->next; - DN_Memset(slot->data, 0, size); - DN_Assert(DN_IsPowerOfTwoAligned(slot->data, pool->align)); - } else { - void *bytes = DN_ArenaAlloc(pool->arena, slot_size_in_bytes, alignof(DN_PoolSlot), DN_ZMem_Yes); - slot = DN_Cast(DN_PoolSlot *) bytes; - - // NOTE: The raw pointer is round up to the next 'pool->align'-ed - // address ensuring at least 1 byte of padding between the raw pointer - // and the pointer given to the user and that the user pointer is - // aligned to the pool's alignment. - // - // This allows us to smuggle 1 byte behind the user pointer that has - // the offset to the original pointer. - slot->data = DN_Cast(void *) DN_AlignDownPowerOfTwo(DN_Cast(uintptr_t) slot + sizeof(DN_PoolSlot) + pool->align, pool->align); - - uintptr_t offset_to_original_ptr = DN_Cast(uintptr_t) slot->data - DN_Cast(uintptr_t) bytes; - DN_Assert(slot->data > bytes); - DN_Assert(offset_to_original_ptr <= sizeof(DN_PoolSlot) + pool->align); - - // NOTE: Store the offset to the original pointer behind the user's - // pointer. - char *offset_to_original_storage = DN_Cast(char *) slot->data - 1; - DN_Memcpy(offset_to_original_storage, &offset_to_original_ptr, 1); - } - - // NOTE: Smuggle the slot type in the next pointer so that we know, when the - // pointer gets returned which free list to return the pointer to. - result = slot->data; - slot->next = DN_Cast(DN_PoolSlot *) slot_index; - return result; -} - -DN_API void DN_PoolDealloc(DN_Pool *pool, void *ptr) -{ - if (!DN_PoolIsValid(pool) || !ptr) - return; - - DN_Assert(DN_MemListOwnsPtr(pool->arena->mem, ptr)); - - char const *one_byte_behind_ptr = DN_Cast(char *) ptr - 1; - DN_USize offset_to_original_ptr = 0; - DN_Memcpy(&offset_to_original_ptr, one_byte_behind_ptr, 1); - DN_Assert(offset_to_original_ptr <= sizeof(DN_PoolSlot) + pool->align); - - char *original_ptr = DN_Cast(char *) ptr - offset_to_original_ptr; - DN_PoolSlot *slot = DN_Cast(DN_PoolSlot *) original_ptr; - DN_PoolSlotSize slot_index = DN_Cast(DN_PoolSlotSize)(DN_Cast(uintptr_t) slot->next); - DN_Assert(slot_index < DN_PoolSlotSize_Count); - - // NOTE: Scrub memory before returning to the pool - DN_MSVC_WARNING_PUSH - DN_MSVC_WARNING_DISABLE(4127) // conditional expression is constant - if (DN_SCRUB_UNINIT_MEM_BYTE) { - DN_USize slot_size_in_bytes = 1ULL << (slot_index + 5); - DN_USize data_offset = (char *)slot->data - (char *)slot; - DN_Memset(slot->data, DN_SCRUB_UNINIT_MEM_BYTE, slot_size_in_bytes - data_offset); - } - DN_MSVC_WARNING_POP - - slot->next = pool->slots[slot_index]; - pool->slots[slot_index] = slot; -} - -static void DN_ErrSinkCheck_(DN_ErrSink const *err) -{ - DN_Assert(err->arena->mem); - if (err->stack_size == 0) - return; - - DN_ErrSinkNode const *node = err->stack + (err->stack_size - 1); - DN_Assert(node->mode >= DN_ErrSinkMode_Nil && node->mode <= DN_ErrSinkMode_ExitOnError); - DN_Assert(node->msg_sentinel); - - // NOTE: Walk the list ensuring we eventually terminate at the sentinel (e.g. we have a - // well formed doubly-linked-list terminated by a sentinel, or otherwise we will hit the - // walk limit or dereference a null pointer and assert) - DN_USize WALK_LIMIT = 99'999; - DN_USize walk = 0; - for (DN_ErrSinkMsg *it = node->msg_sentinel->next; it != node->msg_sentinel; it = it->next, walk++) { - DN_AssertF(it, "Encountered null pointer which should not happen in a sentinel DLL"); - DN_Assert(walk < WALK_LIMIT); - } -} - -DN_API DN_ErrSink* DN_ErrSinkBegin_(DN_ErrSink *err, DN_ErrSinkMode mode, DN_CallSite call_site) -{ - // NOTE: OOM error - if (err->stack_size == DN_ArrayCountU(err->stack)) { - DN_Str8Builder builder = DN_Str8BuilderFromArena(err->arena); - for (DN_ForItSize(it, DN_ErrSinkNode, err->stack, err->stack_size)) - DN_Str8BuilderAppendF(&builder, " [%04zu] %.*s:%u %.*s\n", it.index, DN_Str8PrintFmt(it.data->call_site.file), it.data->call_site.line, DN_Str8PrintFmt(it.data->call_site.function)); - DN_Str8 msg = DN_Str8FromStr8BuilderArena(&builder, err->arena); - DN_AssertF(err->stack_size < DN_ArrayCountU(err->stack), "Error sink has run out of error scopes, potential leak. Scopes were\n%.*s", DN_Str8PrintFmt(msg)); - } - - // NOTE: Allocate the node - DN_ErrSinkNode *node = err->stack + err->stack_size++; - node->arena_pos = DN_MemListPos(err->arena->mem); - node->mode = mode; - node->call_site = call_site; - DN_SentinelDoublyLLInitArena(node->msg_sentinel, DN_ErrSinkMsg, err->arena); - - // NOTE: Handle allocation error - if (!node || !node->msg_sentinel) { - DN_MemListPopTo(err->arena->mem, node->arena_pos); - node->msg_sentinel = nullptr; - err->stack_size--; - } - - DN_ErrSink *result = err; - return result; -} - -DN_API bool DN_ErrSinkHasError(DN_ErrSink *err) -{ - bool result = false; - if (err && err->stack_size) { - DN_ErrSinkNode *node = err->stack + (err->stack_size - 1); - result = DN_SentinelDoublyLLHasItems(node->msg_sentinel); - } - return result; -} - -DN_API DN_ErrSinkMsg *DN_ErrSinkEnd(DN_Arena *arena, DN_ErrSink *err) -{ - DN_ErrSinkMsg *result = nullptr; - DN_ErrSinkCheck_(err); - DN_AssertF(arena != err->arena, "You are not allowed to reuse the arena for ending the error sink because the memory would get popped and lost"); - - // NOTE: Walk the list and allocate it onto the user's arena - DN_ErrSinkNode *node = err->stack + (err->stack_size - 1); - DN_ErrSinkMsg *prev = nullptr; - for (DN_ErrSinkMsg *it = node->msg_sentinel->next; it != node->msg_sentinel; it = it->next) { - DN_ErrSinkMsg *entry = DN_ArenaNew(arena, DN_ErrSinkMsg, DN_ZMem_Yes); - entry->msg = DN_Str8FromStr8Arena(it->msg, arena); - entry->call_site = it->call_site; - entry->error_code = it->error_code; - if (!result) - result = entry; // Assign first entry if we haven't yet - if (prev) - prev->next = entry; // Link the prev message to the current one - prev = entry; // Update prev to latest - } - - // NOTE: Deallocate all the memory for this scope - err->stack_size--; - DN_MemListPopTo(err->arena->mem, node->arena_pos); - return result; -} - -static void DN_ErrSinkAddMsgToStr8Builder_(DN_Str8Builder *builder, DN_ErrSinkMsg *msg, DN_ErrSinkMsg *end) -{ - if (msg == end) // NOTE: No error messages to add - return; - - if (msg->next == end) { - DN_ErrSinkMsg *it = msg; - DN_Str8 file_name = DN_Str8FileNameFromPath(it->call_site.file); - DN_Str8BuilderAppendF(builder, - "%.*s:%05I32u:%.*s %.*s", - DN_Str8PrintFmt(file_name), - it->call_site.line, - DN_Str8PrintFmt(it->call_site.function), - DN_Str8PrintFmt(it->msg)); - } else { - // NOTE: More than one message - for (DN_ErrSinkMsg *it = msg; it != end; it = it->next) { - DN_Str8 file_name = DN_Str8FileNameFromPath(it->call_site.file); - DN_Str8BuilderAppendF(builder, - "%s - %.*s:%05I32u:%.*s%s%.*s", - it == msg ? "" : "\n", - DN_Str8PrintFmt(file_name), - it->call_site.line, - DN_Str8PrintFmt(it->call_site.function), - it->msg.size ? " " : "", - DN_Str8PrintFmt(it->msg)); - } - } -} - -DN_API DN_Str8 DN_ErrSinkEndStr8(DN_Arena *arena, DN_ErrSink *err) -{ - DN_Str8 result = {}; - DN_ErrSinkCheck_(err); - if (err->stack_size == 0) - return result; - - DN_AssertF(arena != err->arena, "You are not allowed to reuse the arena for ending the error sink because the memory would get popped and lost"); - - // NOTE: Walk the list and allocate it onto the user's arena - DN_Str8Builder builder = DN_Str8BuilderFromArena(err->arena); - DN_ErrSinkNode *node = err->stack + (err->stack_size - 1); - DN_ErrSinkAddMsgToStr8Builder_(&builder, node->msg_sentinel->next, node->msg_sentinel); - - // NOTE: Deallocate all the memory for this scope - err->stack_size--; - DN_MemListPopTo(err->arena->mem, node->arena_pos); - - result = DN_Str8FromStr8BuilderArena(&builder, arena); - return result; -} - -DN_API void DN_ErrSinkEndIgnore(DN_ErrSink *err) -{ - DN_ErrSinkEnd(nullptr, err); -} - -DN_API bool DN_ErrSinkEndLogError_(DN_ErrSink *err, DN_CallSite call_site, DN_Str8 err_msg) -{ - DN_ErrSinkNode *node = err->stack + (err->stack_size - 1); - DN_AssertF(err->stack_size, "Begin must be called before calling end"); - DN_AssertF(node->msg_sentinel, "Begin must be called before calling end"); - err->stack_size--; - - bool result = false; - if (node->msg_sentinel != node->msg_sentinel->next) { - result = true; - // NOTE: Build the error string - DN_Str8Builder builder = DN_Str8BuilderFromArena(err->arena); - { - if (err_msg.size) { - DN_Str8BuilderAppendRef(&builder, err_msg); - DN_Str8BuilderAppendRef(&builder, DN_Str8Lit(":")); - } else { - DN_Str8BuilderAppendRef(&builder, DN_Str8Lit("Error(s) encountered:")); - } - if (node->msg_sentinel->next->next != node->msg_sentinel) // NOTE: More than 1 message - DN_Str8BuilderAppendRef(&builder, DN_Str8Lit("\n")); - DN_ErrSinkAddMsgToStr8Builder_(&builder, node->msg_sentinel->next, node->msg_sentinel); - } - - // NOTE: Log the error - DN_Str8 log = DN_Str8FromStr8BuilderArena(&builder, err->arena); - DN_LogPrintF(DN_LogTypeParamFromType(DN_LogType_Error), call_site, DN_LogFlags_Nil, "%.*s", DN_Str8PrintFmt(log)); - - if (node->mode == DN_ErrSinkMode_DebugBreakOnErrorLog) - DN_DebugBreak; - - // NOTE: Deallocate the error node's memory and pop it from the stack - DN_MemListPopTo(err->arena->mem, node->arena_pos); - } - return result; -} - -DN_API bool DN_ErrSinkEndLogErrorFV_(DN_ErrSink *err, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - DN_Str8 log = DN_Str8FromFmtVArena(err->arena, fmt, args); - bool result = DN_ErrSinkEndLogError_(err, call_site, log); - return result; -} - -DN_API bool DN_ErrSinkEndLogErrorF_(DN_ErrSink *err, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_Str8 log = DN_Str8FromFmtVArena(err->arena, fmt, args); - bool result = DN_ErrSinkEndLogError_(err, call_site, log); - va_end(args); - return result; -} - -DN_API void DN_ErrSinkEndExitIfErrorFV_(DN_ErrSink *err, DN_CallSite call_site, DN_U32 exit_val, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - if (DN_ErrSinkEndLogErrorFV_(err, call_site, fmt, args)) { - DN_DebugBreak; - DN_OS_Exit(exit_val); - } -} - -DN_API void DN_ErrSinkEndExitIfErrorF_(DN_ErrSink *err, DN_CallSite call_site, DN_U32 exit_val, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_ErrSinkEndExitIfErrorFV_(err, call_site, exit_val, fmt, args); - va_end(args); -} - -DN_API void DN_ErrSinkAppendFV_(DN_ErrSink *err, DN_U32 error_code, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - if (!err) - return; - - DN_Assert(err->stack_size); - DN_ErrSinkNode *node = err->stack + (err->stack_size - 1); - DN_AssertF(node, "Error sink must be begun by calling 'Begin' before using this function."); - - DN_ErrSinkMsg *msg = DN_ArenaNew(err->arena, DN_ErrSinkMsg, DN_ZMem_Yes); - DN_Assert(msg); - msg->msg = DN_Str8FromFmtVArena(err->arena, fmt, args); - msg->error_code = error_code; - msg->call_site = call_site; - DN_SentinelDoublyLLPrepend(node->msg_sentinel, msg); - if (node->mode == DN_ErrSinkMode_ExitOnError) - DN_ErrSinkEndExitIfErrorF_(err, msg->call_site, error_code, "Fatal error %u", error_code); -} - -DN_API void DN_ErrSinkAppendF_(DN_ErrSink *err, DN_U32 error_code, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_ErrSinkAppendFV_(err, error_code, call_site, fmt, args); - va_end(args); -} - -DN_THREAD_LOCAL DN_TCCore *g_dn_thread_context; - -DN_API void DN_TCInit(DN_TCCore *tc, DN_U64 thread_id, DN_Arena *main_arena, DN_Arena *temp_arenas, DN_USize temp_arenas_count, DN_Arena *err_sink_arena) -{ - tc->thread_id = thread_id; - tc->main_arena = main_arena; - tc->main_pool = DN_PoolFromArena(tc->main_arena, 0); - tc->err_sink.arena = err_sink_arena; - DN_Assert(temp_arenas_count < DN_ArrayCountU(tc->temp_arenas)); - for (DN_ForIndexU(index, temp_arenas_count)) - tc->temp_arenas[tc->temp_arenas_count++] = temp_arenas + index; -} - -DN_API DN_TCInitArgs DN_TCInitArgsDefault() -{ - DN_TCInitArgs result = {}; - result.main_reserve = DN_Kilobytes(64); - result.main_commit = DN_Kilobytes(4); - result.temp_reserve = DN_Kilobytes(64); - result.temp_commit = DN_Kilobytes(4); - result.temp_count = 2; - result.err_sink_reserve = DN_Kilobytes(64); - result.err_sink_commit = DN_Kilobytes(4); - return result; -} - -DN_API void DN_TCInitFromMemFuncs(DN_TCCore *tc, DN_U64 thread_id, DN_TCInitArgs args, DN_MemFuncs mem_funcs) -{ - DN_Assert(args.temp_count <= DN_ArrayCountU(tc->temp_arenas)); - tc->main_arena_mem_ = DN_MemListFromMemFuncs(args.main_reserve, args.main_commit, DN_MemFlags_AllocCanLeak | DN_MemFlags_NoAllocTrack, mem_funcs); - for (DN_ForIndexU(index, args.temp_count)) { - tc->temp_arena_mems_[index] = DN_MemListFromMemFuncs(args.temp_reserve, args.temp_commit, DN_MemFlags_AllocCanLeak | DN_MemFlags_NoAllocTrack, mem_funcs); - tc->temp_arenas_[index] = DN_ArenaFromMemList(&tc->temp_arena_mems_[index]); - } - tc->err_sink_arena_mem_ = DN_MemListFromMemFuncs(args.err_sink_reserve, args.err_sink_commit, DN_MemFlags_AllocCanLeak | DN_MemFlags_NoAllocTrack, mem_funcs); - tc->main_arena_ = DN_ArenaFromMemList(&tc->main_arena_mem_); - tc->err_sink_arena_ = DN_ArenaFromMemList(&tc->err_sink_arena_mem_); - DN_TCInit(tc, thread_id, &tc->main_arena_, tc->temp_arenas_, args.temp_count, &tc->err_sink_arena_); -} - -DN_API void DN_TCDeinit(DN_TCCore *tc, DN_TCDeinitArenas deinit_arenas) -{ - if (deinit_arenas == DN_TCDeinitArenas_Yes) { - DN_MemListDeinit(tc->main_arena->mem); - for (DN_ForIndexU(index, tc->temp_arenas_count)) { - DN_MemListDeinit(tc->temp_arenas[index]->mem); - DN_MemListDeinit(tc->temp_arenas[index]->mem); - } - DN_MemListDeinit(tc->err_sink.arena->mem); - } -} - -DN_API void DN_TCEquip(DN_TCCore *tc) -{ - g_dn_thread_context = tc; -} - -DN_API DN_TCCore *DN_TCGet() -{ - DN_AssertRaw(g_dn_thread_context && - "This thread's thread context has not been equipped yet. Ensure that DN_TCInit(...) " - "has been called to create a thread context and call DN_TCEquip(...) in the current " - "thread to make it retrievable via this function"); - return g_dn_thread_context; -} - -DN_API DN_Arena *DN_TCMainArena() -{ - DN_TCCore *tc = DN_TCGet(); - DN_Arena *result = tc->main_arena; - return result; -} - -DN_API DN_Pool *DN_TCMainPool() -{ - DN_TCCore *tc = DN_TCGet(); - DN_Pool *result = &tc->main_pool; - return result; -} - -DN_API DN_Arena DN_TCTempArenaAllocator(DN_Allocator *conflicts, DN_USize count) -{ - DN_MemList *conflict_mem_lists[8]; - DN_USize conflict_mem_lists_count = 0; - for (DN_ForItSize(it, DN_Allocator, conflicts, count)) { - DN_Allocator *allocator = it.data; - if (!allocator->context) - continue; - - DN_MemList *mem_list = nullptr; - switch (allocator->type) { - case DN_AllocatorType_MemList: mem_list = DN_Cast(DN_MemList *)allocator->context; break; - - case DN_AllocatorType_Arena: { - DN_Arena *arena = DN_Cast(DN_Arena *) allocator->context; - mem_list = arena->mem; - } break; - - case DN_AllocatorType_Pool: { - DN_Pool *pool = DN_Cast(DN_Pool *) allocator->context; - mem_list = pool->arena ? pool->arena->mem : nullptr; - } break; - } - - if (!mem_list) - continue; - - void *added = DN_LArrayAppend(conflict_mem_lists, &conflict_mem_lists_count, mem_list); - DN_Assert(added); - } - - DN_TCCore *tc = DN_TCGet(); - DN_Arena result = {}; - for (DN_ForItSize(it, DN_Arena *, tc->temp_arenas, tc->temp_arenas_count)) { - bool is_usable = true; - DN_Arena *rhs_arena = *it.data; - DN_MemList *rhs_mem = rhs_arena->mem; - for (DN_ForItSize(conflict_it, DN_MemList*, conflict_mem_lists, conflict_mem_lists_count)) { - DN_MemList *lhs_mem = *conflict_it.data; - if (lhs_mem == rhs_mem) { - is_usable = false; - break; - } - } - - if (is_usable) { - result = DN_ArenaTempBeginFromMemList(rhs_mem); - break; - } - } - - DN_AssertF(result.mem, "All temp arenas are being used, there are none left to return to the caller"); - return result; -} - -DN_API DN_Arena DN_TCTempArenaFromArena(DN_Arena **conflicts, DN_USize count) -{ - DN_TCCore *tc = DN_TCGet(); - DN_Arena result = {}; - for (DN_ForItSize(it, DN_Arena *, tc->temp_arenas, tc->temp_arenas_count)) { - bool is_usable = true; - DN_Arena *rhs_arena = *it.data; - DN_MemList *rhs_mem = rhs_arena->mem; - for (DN_ForItSize(conflict_it, DN_Arena *, conflicts, count)) { - DN_Arena *lhs_arena = *conflict_it.data; - DN_MemList *lhs_mem = lhs_arena->mem; - if (lhs_mem == rhs_mem) { - is_usable = false; - break; - } - } - - if (is_usable) { - result = DN_ArenaTempBeginFromMemList(rhs_mem); - break; - } - } - - DN_AssertF(result.mem, "All temp arenas are being used, there are none left to return to the caller"); - return result; -} - -#if defined(__cplusplus) -DN_TCScratchCpp::DN_TCScratchCpp(DN_Arena **conflicts, DN_USize count) -{ - this->data = DN_TCScratchBeginArena(conflicts, count); -} - -DN_TCScratchCpp::~DN_TCScratchCpp() -{ - DN_TCScratchEnd(&this->data); -} -#endif - -DN_API DN_TCScratch DN_TCScratchBeginAllocator(DN_Allocator *conflicts, DN_USize count) -{ - DN_TCScratch result = {}; - result.arena = DN_TCTempArenaAllocator(conflicts, count); - return result; -} - -DN_API DN_TCScratch DN_TCScratchBeginArena(DN_Arena **conflicts, DN_USize count) -{ - DN_TCScratch result = {}; - result.arena = DN_TCTempArenaFromArena(conflicts, count); - return result; -} - -DN_API void DN_TCScratchEnd(DN_TCScratch *scratch) -{ - DN_Assert(scratch->destructed == false); - DN_ArenaTempEnd(&scratch->arena, DN_ArenaReset_Yes); - *scratch = {}; - scratch->destructed = true; -} - -DN_API void DN_TCSetFrameArena(DN_Arena *arena) -{ - DN_TCCore *tc = DN_TCGet(); - tc->frame_arena = arena; -} - -DN_API DN_Arena *DN_TCFrameArena() -{ - DN_TCCore *tc = DN_TCGet(); - DN_Arena *result = tc->frame_arena; - return result; -} - -DN_API DN_ErrSink *DN_TCErrSink() -{ - DN_TCCore *tc = DN_TCGet(); - DN_ErrSink *result = &tc->err_sink; - return result; -} - -DN_API void *DN_PoolCopy(DN_Pool *pool, void const *data, DN_U64 size, DN_U8 align) -{ - if (!pool || !data || size == 0) - return nullptr; - - // TODO: Hmm should align be part of the alloc interface in general? I'm not going to worry - // about this until we crash because of misalignment. - DN_Assert(pool->align >= align); - - void *result = DN_PoolAlloc(pool, size); - if (result) - DN_Memcpy(result, data, size); - return result; -} - -DN_API bool DN_CharIsAlphabet(char ch) -{ - bool result = (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'); - return result; -} - -DN_API bool DN_CharIsDigit(char ch) -{ - bool result = (ch >= '0' && ch <= '9'); - return result; -} - -DN_API bool DN_CharIsAlphaNum(char ch) -{ - bool result = DN_CharIsAlphabet(ch) || DN_CharIsDigit(ch); - return result; -} - -DN_API bool DN_CharIsWhitespace(char ch) -{ - bool result = (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'); - return result; -} - -DN_API bool DN_CharIsHex(char ch) -{ - bool result = ((ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') || (ch >= '0' && ch <= '9')); - return result; -} - -DN_API char DN_CharToLower(char ch) -{ - char result = ch; - if (result >= 'A' && result <= 'Z') - result += 'a' - 'A'; - return result; -} - -DN_API char DN_CharToUpper(char ch) -{ - char result = ch; - if (result >= 'a' && result <= 'z') - result -= 'a' - 'A'; - return result; -} - -DN_API DN_U64FromResult DN_U64FromStr8(DN_Str8 string, char separator) -{ - // NOTE: Argument check - DN_U64FromResult result = {}; - if (string.size == 0) { - result.success = true; - return result; - } - - // NOTE: Sanitize input/output - DN_Str8 trim_string = DN_Str8TrimWhitespaceAround(string); - if (trim_string.size == 0) { - result.success = true; - return result; - } - - // NOTE: Handle prefix '+' - DN_USize start_index = 0; - if (!DN_CharIsDigit(trim_string.data[0])) { - if (trim_string.data[0] != '+') - return result; - start_index++; - } - - // NOTE: Convert the string number to the binary number - for (DN_USize index = start_index; index < trim_string.size; index++) { - char ch = trim_string.data[index]; - if (index) { - if (separator != 0 && ch == separator) - continue; - } - - if (!DN_CharIsDigit(ch)) - return result; - - result.value = DN_SafeMulU64(result.value, 10); - DN_U64 digit = ch - '0'; - result.value = DN_SafeAddU64(result.value, digit); - } - - result.success = true; - return result; -} - -DN_API DN_U64FromResult DN_U64FromPtr(void const *data, DN_USize size, char separator) -{ - DN_Str8 str8 = DN_Str8FromPtr((char *)data, size); - DN_U64FromResult result = DN_U64FromStr8(str8, separator); - return result; -} - -DN_API DN_U64 DN_U64FromPtrUnsafe(void const *data, DN_USize size, char separator) -{ - DN_U64FromResult from = DN_U64FromPtr(data, size, separator); - DN_U64 result = from.value; - DN_Assert(from.success); - return result; -} - -DN_API DN_U64FromResult DN_U64FromHexPtr(void const *hex, DN_USize hex_count) -{ - char *hex_ptr = DN_Cast(char *) hex; - if (hex_count >= 2 && hex_ptr[0] == '0' && (hex_ptr[1] == 'x' || hex_ptr[1] == 'X')) { - hex_ptr += 2; - hex_count -= 2; - } - - DN_U64FromResult result = {}; - DN_USize max_hex_count = sizeof(DN_U64) * 2; - DN_USize count = DN_Min(max_hex_count, hex_count); - DN_Assert(hex_count <= max_hex_count); - for (DN_USize index = 0; index < count; index++) { - char ch = hex_ptr[index]; - DN_U8 val = DN_U8FromHexNibble(ch); - if (val == 0xFF) - return result; - result.value = (result.value << 4) | val; - } - result.success = true; - return result; -} - -DN_API DN_U64 DN_U64FromHexPtrUnsafe(void const *hex, DN_USize hex_count) -{ - DN_U64FromResult from = DN_U64FromHexPtr(hex, hex_count); - DN_U64 result = from.value; - DN_Assert(from.success); - return result; -} - -DN_API DN_U64FromResult DN_U64FromHexStr8(DN_Str8 hex) -{ - DN_U64FromResult result = DN_U64FromHexPtr(hex.data, hex.size); - return result; -} - -DN_API DN_U64 DN_U64FromHexStr8Unsafe(DN_Str8 hex) -{ - DN_U64 result = DN_U64FromHexPtrUnsafe(hex.data, hex.size); - return result; -} - -DN_API DN_U64 DN_U64FromU8x32HiBEUnsafe(DN_U8x32 const *val) -{ - DN_U64 result_be = 0; // Last 8 bytes of 32-byte slot (big-endian) - DN_Memcpy(&result_be, val->data + sizeof(val->data) - sizeof(result_be), sizeof(result_be)); - DN_U64 result = DN_ByteSwap64(result_be); - return result; -} - -DN_API DN_U64FromResult DN_U64FromU8x32HiBE(DN_U8x32 const *val) -{ - DN_U64FromResult result = {}; - if (val) { - // NOTE: Check that the high bits are not set - DN_U8x32 zero_mask = {}; - bool high_bits_set = DN_Memcmp(val->data, zero_mask.data, sizeof(zero_mask.data) - sizeof(result)) != 0; - result.success = !high_bits_set; - result.value = DN_U64FromU8x32HiBEUnsafe(val); - } - return result; -} - -DN_API DN_USize DN_USizeFromU8x32HiBEUnsafe(DN_U8x32 const *val) -{ - DN_USize result_be = 0; - DN_Memcpy(&result_be, val->data + sizeof(val->data) - sizeof(result_be), sizeof(result_be)); - DN_USize result = DN_ByteSwapUSize(result_be); - return result; -} - -DN_API DN_USizeFromResult DN_USizeFromU8x32HiBE(DN_U8x32 const *val) -{ - DN_USizeFromResult result = {}; - if (val) { - // NOTE: Check that the high bits are not set - DN_U8x32 mask = {}; - DN_Memset(mask.data, 1, sizeof(mask.data) - sizeof(result)); - bool high_bits_set = DN_Memcmp(val->data, mask.data, 24) != 0; - result.success = !high_bits_set; - result.value = DN_USizeFromU8x32HiBEUnsafe(val); - } - return result; -} - -DN_API void DN_ByteSwapU64Ptr(DN_U8 *dest, DN_U64 src) -{ - dest[0] = DN_Cast(DN_U8)((src >> 56) & 0xFF); - dest[1] = DN_Cast(DN_U8)((src >> 48) & 0xFF); - dest[2] = DN_Cast(DN_U8)((src >> 40) & 0xFF); - dest[3] = DN_Cast(DN_U8)((src >> 32) & 0xFF); - dest[4] = DN_Cast(DN_U8)((src >> 24) & 0xFF); - dest[5] = DN_Cast(DN_U8)((src >> 16) & 0xFF); - dest[6] = DN_Cast(DN_U8)((src >> 8) & 0xFF); - dest[7] = DN_Cast(DN_U8)(src & 0xFF); -} - -DN_API DN_I64FromResult DN_I64FromStr8(DN_Str8 string, char separator) -{ - // NOTE: Argument check - DN_I64FromResult result = {}; - if (string.size == 0) { - result.success = true; - return result; - } - - // NOTE: Sanitize input/output - DN_Str8 trim_string = DN_Str8TrimWhitespaceAround(string); - if (trim_string.size == 0) { - result.success = true; - return result; - } - - bool negative = false; - DN_USize start_index = 0; - if (!DN_CharIsDigit(trim_string.data[0])) { - negative = (trim_string.data[start_index] == '-'); - if (!negative && trim_string.data[0] != '+') - return result; - start_index++; - } - - // NOTE: Convert the string number to the binary number - for (DN_USize index = start_index; index < trim_string.size; index++) { - char ch = trim_string.data[index]; - if (index) { - if (separator != 0 && ch == separator) - continue; - } - - if (!DN_CharIsDigit(ch)) - return result; - - result.value = DN_SafeMulU64(result.value, 10); - DN_U64 digit = ch - '0'; - result.value = DN_SafeAddU64(result.value, digit); - } - - if (negative) - result.value *= -1; - - result.success = true; - return result; -} - -DN_API DN_I64FromResult DN_I64FromPtr(void const *data, DN_USize size, char separator) -{ - DN_Str8 str8 = DN_Str8FromPtr((char *)data, size); - DN_I64FromResult result = DN_I64FromStr8(str8, separator); - return result; -} - -DN_API DN_I64 DN_I64FromPtrUnsafe(void const *data, DN_USize size, char separator) -{ - DN_I64FromResult from = DN_I64FromPtr(data, size, separator); - DN_I64 result = from.value; - DN_Assert(from.success); - return result; -} - -DN_API bool DN_U8x32Eq(DN_U8x32 const *lhs, DN_U8x32 const *rhs) -{ - bool result = DN_MemEqUnsafe(lhs->data, rhs->data, sizeof(lhs->data)); - return result; -} - -DN_API DN_U8x32 DN_U8x32FromBytesLeftPadZ(DN_U8 const *ptr, DN_USize count) -{ - DN_U8x32 result = {}; - DN_Assert(count <= sizeof(result.data)); - DN_Memcpy(result.data + sizeof(result.data) - count, ptr, count); - return result; -} - -DN_API DN_U8x32 DN_U8x32FromHexUnsafe(DN_Str8 hex_32b) -{ - DN_U8x32 result = {}; - hex_32b = DN_Str8TrimHexPrefix(hex_32b); - DN_Assert(hex_32b.size <= sizeof(result.data) * 2); - DN_BytesFromHexPtr(hex_32b.data, hex_32b.size, result.data, sizeof(result.data)); - return result; -} - -DN_API DN_U8x32FromResult DN_U8x32FromHex(DN_Str8 hex_32b) -{ - DN_U8x32FromResult result = {}; - DN_USize bytes_written = DN_BytesFromHexPtr(hex_32b.data, hex_32b.size, result.value.data, sizeof(result.value.data)); - if (bytes_written == sizeof(result.value.data)) - result.success = true; - return result; -} - -DN_API DN_U8x32FromResult DN_U8x32FromDecimalStr8(DN_Str8 decimal) -{ - DN_U8x32FromResult result = {}; - result.success = true; - for (DN_USize i = 0; i < decimal.size; i++) { - DN_U8 digit = decimal.data[i]; - if (!DN_CharIsDigit(digit)) { - result.success = false; - break; - } - - DN_U8 digit_val = digit - '0'; - - // NOTE: Goal is to do => (result = result * 10 + digit_val) - // Multiply current result by 10 - DN_U16 carry = 0; - for (int j = 31; j >= 0; j--) { - DN_U16 prod = DN_Cast(DN_U16)result.value.data[j] * 10 + carry; - result.value.data[j] = DN_Cast(DN_U8)(prod & 0xFF); - carry = prod >> 8; - } - - // Add the digit - carry = digit_val; - for (int j = 31; j >= 0 && carry > 0; j--) { - DN_U16 sum = DN_Cast(DN_U16)result.value.data[j] + carry; - result.value.data[j] = DN_Cast(DN_U8)(sum & 0xFF); - carry = sum >> 8; - } - } - - return result; -} - -DN_API DN_Allocator DN_AllocatorFromMemList(DN_MemList *mem) -{ - DN_Allocator result = {}; - result.type = DN_AllocatorType_MemList; - result.context = mem; - return result; -} - -DN_API DN_Allocator DN_AllocatorFromArena(DN_Arena *arena) -{ - DN_Allocator result = {}; - result.type = DN_AllocatorType_Arena; - result.context = arena; - return result; -} - -DN_API DN_Allocator DN_AllocatorFromPool(DN_Pool *pool) -{ - DN_Allocator result = {}; - result.type = DN_AllocatorType_Pool; - result.context = pool; - return result; -} - -DN_API void *DN_AllocatorAlloc(DN_Allocator allocator, DN_USize size, DN_U8 align, DN_ZMem z_mem) -{ - void *result = nullptr; - if (allocator.context) { - switch (allocator.type) { - case DN_AllocatorType_Arena: result = DN_ArenaAlloc (DN_Cast(DN_Arena *) allocator.context, size + 1, align, z_mem); break; - case DN_AllocatorType_Pool: result = DN_PoolAlloc (DN_Cast(DN_Pool *) allocator.context, size + 1); break; - case DN_AllocatorType_MemList: result = DN_MemListAlloc(DN_Cast(DN_MemList *) allocator.context, size + 1, align, z_mem); break; - } - } - return result; -} - -DN_API DN_FmtAppendResult DN_FmtVAppend(char *buf, DN_USize *buf_size, DN_USize buf_max, char const *fmt, va_list args) -{ - DN_FmtAppendResult result = {}; - DN_USize starting_size = *buf_size; - result.size_req = DN_VSNPrintF(buf + *buf_size, DN_Cast(int)(buf_max - *buf_size), fmt, args); - *buf_size += result.size_req; - if (*buf_size >= (buf_max - 1)) - *buf_size = buf_max - 1; - DN_Assert(*buf_size <= (buf_max - 1)); - result.str8 = DN_Str8FromPtr(buf, *buf_size); - result.truncated = result.str8.size != (starting_size + result.size_req); - return result; -} - -DN_API DN_FmtAppendResult DN_FmtAppend(char *buf, DN_USize *buf_size, DN_USize buf_max, char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_FmtAppendResult result = DN_FmtVAppend(buf, buf_size, buf_max - (*buf_size), fmt, args); - va_end(args); - return result; -} - -DN_API DN_FmtAppendResult DN_FmtAppendTruncate(char *buf, DN_USize *buf_size, DN_USize buf_max, DN_Str8 truncator, char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_FmtAppendResult result = DN_FmtVAppend(buf, buf_size, buf_max, fmt, args); - if (result.truncated) - DN_Memcpy(result.str8.data + result.str8.size - truncator.size, truncator.data, truncator.size); - va_end(args); - return result; -} - -DN_API DN_USize DN_FmtSize(DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_USize result = DN_VSNPrintF(nullptr, 0, fmt, args); - va_end(args); - return result; -} - -DN_API DN_USize DN_FmtVSize(DN_FMT_ATTRIB char const *fmt, va_list args) -{ - va_list args_copy; - va_copy(args_copy, args); - DN_USize result = DN_VSNPrintF(nullptr, 0, fmt, args_copy); - va_end(args_copy); - return result; -} - -DN_API DN_USize DN_CStr8Size(char const *src) -{ - DN_USize result = 0; - for (; src && src[0] != 0; src++, result++) - ; - return result; -} - -DN_API DN_USize DN_CStr16Size(wchar_t const *src) -{ - DN_USize result = 0; - for (; src && src[0] != 0; src++, result++) - ; - return result; -} - -DN_API DN_Str8 DN_Str8AllocAllocator(DN_USize size, DN_ZMem z_mem, DN_Allocator allocator) -{ - DN_Str8 result = {}; - result.data = DN_Cast(char *) DN_AllocatorAlloc(allocator, size + 1, alignof(char), z_mem); - if (result.data) { - result.size = size; - result.data[result.size] = 0; - } - return result; -} - -DN_API DN_Str8 DN_Str8AllocArena(DN_USize size, DN_ZMem z_mem, DN_Arena *arena) -{ - DN_Str8 result = DN_Str8AllocAllocator(size, z_mem, DN_AllocatorFromArena(arena)); - return result; -} - -DN_API DN_Str8 DN_Str8AllocPool(DN_USize size, DN_Pool *pool) -{ - DN_Str8 result = DN_Str8AllocAllocator(size, DN_ZMem_No, DN_AllocatorFromPool(pool)); - return result; -} - -DN_API DN_Str8 DN_Str8FromCStr8(char const *src) -{ - DN_USize size = DN_CStr8Size(src); - DN_Str8 result = DN_Str8FromPtr(src, size); - return result; -} - -DN_API DN_Str8 DN_Str8FromCStr8Arena(char const *src, DN_Arena *arena) -{ - DN_Str8 shallow = DN_Str8FromCStr8(src); - DN_Str8 result = DN_Str8FromStr8Arena(shallow, arena); - return result; -} - -DN_API DN_Str8 DN_Str8FromPtrArena(void const *data, DN_USize size, DN_Arena *arena) -{ - DN_Str8 result = DN_Str8AllocArena(size, DN_ZMem_No, arena); - if (result.size) - DN_Memcpy(result.data, data, size); - return result; -} - -DN_API DN_Str8 DN_Str8FromPtrPool(void const *data, DN_USize size, DN_Pool *pool) -{ - DN_Str8 result = DN_Str8AllocPool(size, pool); - if (result.size) - DN_Memcpy(result.data, data, size); - return result; -} - -DN_API DN_Str8 DN_Str8FromStr8Allocator(DN_Str8 string, DN_Allocator allocator) -{ - DN_Str8 result = {}; - result.data = DN_Cast(char *) DN_AllocatorAlloc(allocator, string.size + 1, alignof(char), DN_ZMem_No); - if (result.data) { - DN_Memcpy(result.data, string.data, string.size); - result.data[string.size] = 0; - result.size = string.size; - } - return result; -} - -DN_API DN_Str8 DN_Str8FromStr8Arena(DN_Str8 string, DN_Arena *arena) -{ - DN_Str8 result = DN_Str8FromStr8Allocator(string, DN_AllocatorFromArena(arena)); - return result; -} - -DN_API DN_Str8 DN_Str8FromStr8Pool(DN_Str8 string, DN_Pool *pool) -{ - DN_Str8 result = DN_Str8FromStr8Allocator(string, DN_AllocatorFromPool(pool)); - return result; -} - -DN_API DN_Str8 DN_Str8FromFmtVAllocator(DN_Allocator allocator, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - DN_USize size = DN_FmtVSize(fmt, args); - DN_Str8 result = DN_Str8AllocAllocator(size, DN_ZMem_No, allocator); - if (result.data) { - DN_USize written = 0; - DN_FmtVAppend(result.data, &written, result.size + 1, fmt, args); - DN_Assert(written == result.size); - } - return result; -} - -DN_API DN_Str8 DN_Str8FromFmtVArena(DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - DN_Str8 result = DN_Str8FromFmtVAllocator(DN_AllocatorFromArena(arena), fmt, args); - return result; -} - -DN_API DN_Str8 DN_Str8FromFmtAllocator(DN_Allocator allocator, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list va; - va_start(va, fmt); - DN_Str8 result = DN_Str8FromFmtVAllocator(allocator, fmt, va); - va_end(va); - return result; -} - -DN_API DN_Str8 DN_Str8FromFmtArena(DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list va; - va_start(va, fmt); - DN_Str8 result = DN_Str8FromFmtVArena(arena, fmt, va); - va_end(va); - return result; -} - -DN_API DN_Str8 DN_Str8FromFmtVPool(DN_Pool *pool, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - DN_Str8 result = DN_Str8FromFmtVAllocator(DN_AllocatorFromPool(pool), fmt, args); - return result; -} - -DN_API DN_Str8 DN_Str8FromFmtPool(DN_Pool *pool, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_Str8 result = DN_Str8FromFmtVPool(pool, fmt, args); - va_end(args); - return result; -} - -DN_API DN_Str8x16 DN_Str8x16FromFmt(DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_Str8x16 result = {}; - DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args); - va_end(args); - return result; -} - -DN_API DN_Str8x16 DN_Str8x16FromFmtVArena(DN_FMT_ATTRIB char const *fmt, va_list args) -{ - DN_Str8x16 result = {}; - DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args); - return result; -} - -DN_API DN_Str8x32 DN_Str8x32FromFmt(DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_Str8x32 result = {}; - DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args); - va_end(args); - return result; -} - -DN_API DN_Str8x32 DN_Str8x32FromFmtVArena(DN_FMT_ATTRIB char const *fmt, va_list args) -{ - DN_Str8x32 result = {}; - DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args); - return result; -} - -DN_API DN_Str8x64 DN_Str8x64FromFmt(DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_Str8x64 result = {}; - DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args); - va_end(args); - return result; -} - -DN_API DN_Str8x64 DN_Str8x64FromFmtV(DN_FMT_ATTRIB char const *fmt, va_list args) -{ - DN_Str8x64 result = {}; - DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args); - return result; -} - -DN_API DN_Str8x128 DN_Str8x128FromFmt(DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_Str8x128 result = {}; - DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args); - va_end(args); - return result; -} - -DN_API DN_Str8x128 DN_Str8x128FromFmtVArena(DN_FMT_ATTRIB char const *fmt, va_list args) -{ - DN_Str8x128 result = {}; - DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args); - return result; -} - -DN_API DN_Str8x256 DN_Str8x256FromFmt(DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_Str8x256 result = {}; - DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args); - va_end(args); - return result; -} - -DN_API DN_Str8x256 DN_Str8x256FromFmtVArena(DN_FMT_ATTRIB char const *fmt, va_list args) -{ - DN_Str8x256 result = {}; - DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args); - return result; -} - -DN_API DN_Str8x512 DN_Str8x512FromFmt(DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_Str8x512 result = {}; - DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args); - va_end(args); - return result; -} - -DN_API DN_Str8x512 DN_Str8x512FromFmtVArena(DN_FMT_ATTRIB char const *fmt, va_list args) -{ - DN_Str8x512 result = {}; - DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args); - return result; -} - -DN_API DN_Str8x1024 DN_Str8x1024FromFmt(DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_Str8x1024 result = {}; - DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args); - va_end(args); - return result; -} - -DN_API DN_Str8x1024 DN_Str8x1024FromFmtVArena(DN_FMT_ATTRIB char const *fmt, va_list args) -{ - DN_Str8x1024 result = {}; - DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args); - return result; -} - -DN_API void DN_Str8x16AppendFmt(DN_Str8x16 *str, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_Str8x16AppendFmtV(str, fmt, args); - va_end(args); -} - -DN_API void DN_Str8x16AppendFmtV(DN_Str8x16 *str, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - DN_FmtVAppend(str->data, &str->size, sizeof(str->data), fmt, args); -} - -DN_API void DN_Str8x32AppendFmt(DN_Str8x32 *str, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_Str8x32AppendFmtV(str, fmt, args); - va_end(args); -} - -DN_API void DN_Str8x32AppendFmtV(DN_Str8x32 *str, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - DN_FmtVAppend(str->data, &str->size, sizeof(str->data), fmt, args); -} - -DN_API void DN_Str8x64AppendFmt(DN_Str8x64 *str, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_Str8x64AppendFmtV(str, fmt, args); - va_end(args); -} - -DN_API void DN_Str8x64AppendFmtV(DN_Str8x64 *str, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - DN_FmtVAppend(str->data, &str->size, sizeof(str->data), fmt, args); -} - -DN_API void DN_Str8x128AppendFmt(DN_Str8x128 *str, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_Str8x128AppendFmtV(str, fmt, args); - va_end(args); -} - -DN_API void DN_Str8x128AppendFmtV(DN_Str8x128 *str, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - DN_FmtVAppend(str->data, &str->size, sizeof(str->data), fmt, args); -} - -DN_API void DN_Str8x256AppendFmt(DN_Str8x256 *str, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_Str8x256AppendFmtV(str, fmt, args); - va_end(args); -} - -DN_API void DN_Str8x256AppendFmtV(DN_Str8x256 *str, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - DN_FmtVAppend(str->data, &str->size, sizeof(str->data), fmt, args); -} - -DN_API void DN_Str8x512AppendFmt(DN_Str8x512 *str, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_Str8x512AppendFmtV(str, fmt, args); - va_end(args); -} - -DN_API void DN_Str8x512AppendFmtV(DN_Str8x512 *str, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - DN_FmtVAppend(str->data, &str->size, sizeof(str->data), fmt, args); -} - -DN_API void DN_Str8x1024AppendFmt(DN_Str8x1024 *str, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_Str8x1024AppendFmtV(str, fmt, args); - va_end(args); -} - -DN_API void DN_Str8x1024AppendFmtV(DN_Str8x1024 *str, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - DN_FmtVAppend(str->data, &str->size, sizeof(str->data), fmt, args); -} - -DN_API DN_Str8x32 DN_Str8x32FromU64(DN_U64 val, char separator) -{ - DN_Str8x32 result = {}; - DN_Str8x32 temp = DN_Str8x32FromFmt("%" PRIu64, val); - DN_USize temp_index = 0; - - // NOTE: Write the digits the first, up to [0, 2] digits that do not need a thousandth separator - DN_USize range_without_separator = temp.size % 3; - for (; temp_index < range_without_separator; temp_index++) - result.data[result.size++] = temp.data[temp_index]; - - // NOTE: Write the subsequent digits and every 3rd digit, add the seperator - DN_USize digit_counter = 0; - for (; temp_index < temp.size; temp_index++, digit_counter++) { - if (separator && temp_index && (digit_counter % 3 == 0)) - result.data[result.size++] = separator; - result.data[result.size++] = temp.data[temp_index]; - } - return result; -} - - -DN_API bool DN_Str8IsAll(DN_Str8 string, DN_Str8IsAllType is_all) -{ - bool result = string.size; - if (!result) - return result; - - switch (is_all) { - case DN_Str8IsAllType_Digits: { - for (DN_USize index = 0; result && index < string.size; index++) - result = string.data[index] >= '0' && string.data[index] <= '9'; - } break; - - case DN_Str8IsAllType_Hex: { - DN_Str8 trimmed = DN_Str8TrimPrefix(string, DN_Str8Lit("0x"), DN_Str8EqCase_Insensitive); - for (DN_USize index = 0; result && index < trimmed.size; index++) { - char ch = trimmed.data[index]; - result = (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'); - } - } break; - } - - return result; -} - -DN_API char *DN_Str8End(DN_Str8 string) -{ - char *result = string.data + string.size; - return result; -} - -DN_API DN_Str8 DN_Str8Subset(DN_Str8 string, DN_USize offset, DN_USize size) -{ - DN_Str8 result = DN_Str8FromPtr(string.data, 0); - if (string.size == 0) - return result; - - DN_USize capped_offset = DN_Min(offset, string.size); - DN_USize max_size = string.size - capped_offset; - DN_USize capped_size = DN_Min(size, max_size); - result = DN_Str8FromPtr(string.data + capped_offset, capped_size); - return result; -} - -DN_API DN_Str8 DN_Str8Advance(DN_Str8 string, DN_USize amount) -{ - DN_Str8 result = DN_Str8Subset(string, amount, DN_USIZE_MAX); - return result; -} - -DN_API DN_Str8 DN_Str8NextLine(DN_Str8 string) -{ - DN_Str8 result = DN_Str8BSplit(string, DN_Str8Lit("\n")).rhs; - return result; -} - -DN_API DN_Str8BSplitResult DN_Str8BSplitArray(DN_Str8 string, DN_Str8 const *find, DN_USize find_size) -{ - DN_Str8BSplitResult result = {}; - if (string.size == 0 || !find || find_size == 0) - return result; - - result.lhs = string; - for (DN_USize index = 0; !result.rhs.data && index < string.size; index++) { - for (DN_USize find_index = 0; find_index < find_size; find_index++) { - DN_Str8 find_item = find[find_index]; - DN_Str8 string_slice = DN_Str8Subset(string, index, find_item.size); - if (DN_Str8Eq(string_slice, find_item)) { - result.input_index = find_index; - result.lhs.size = index; - result.rhs.data = string_slice.data + find_item.size; - result.rhs.size = string.size - (index + find_item.size); - break; - } - } - } - - return result; -} - -DN_API DN_Str8BSplitResult DN_Str8BSplit(DN_Str8 string, DN_Str8 find) -{ - DN_Str8BSplitResult result = DN_Str8BSplitArray(string, &find, 1); - return result; -} - -DN_API DN_Str8BSplitResult DN_Str8BSplitLastArray(DN_Str8 string, DN_Str8 const *find, DN_USize find_size) -{ - DN_Str8BSplitResult result = {}; - if (string.size == 0 || !find || find_size == 0) - return result; - - result.lhs = string; - for (DN_USize index = string.size - 1; !result.rhs.data && index < string.size; index--) { - for (DN_USize find_index = 0; find_index < find_size; find_index++) { - DN_Str8 find_item = find[find_index]; - DN_Str8 string_slice = DN_Str8Subset(string, index, find_item.size); - if (DN_Str8Eq(string_slice, find_item)) { - result.lhs.size = index; - result.rhs.data = string_slice.data + find_item.size; - result.rhs.size = string.size - (index + find_item.size); - break; - } - } - } - - return result; -} - -DN_API DN_Str8BSplitResult DN_Str8BSplitLast(DN_Str8 string, DN_Str8 find) -{ - DN_Str8BSplitResult result = DN_Str8BSplitLastArray(string, &find, 1); - return result; -} - -DN_API DN_USize DN_Str8Split(DN_Str8 string, DN_Str8 delimiter, DN_Str8 *splits, DN_USize splits_count, DN_Str8SplitFlags flags) -{ - DN_USize result = 0; // The number of splits in the actual string. - if (string.size == 0 || delimiter.size == 0 || delimiter.size <= 0) - return result; - - DN_Str8 it = string; - bool allow_empty_strings = DN_BitIsNotSet(flags, DN_Str8SplitFlags_ExcludeEmptyStrings); - bool handle_quotes = DN_BitIsSet(flags, DN_Str8SplitFlags_HandleQuotedStrings); - do { - DN_Str8 item = {}; - if (handle_quotes && DN_Str8StartsWith(it, DN_Str8Lit("\""))) { - DN_Str8FindResult find = DN_Str8FindStr8(DN_Str8Advance(it, 1), DN_Str8Lit("\""), DN_Str8EqCase_Sensitive); - DN_Assert(find.found); - item = find.start_to_before_match; - it = DN_Str8BSplit(find.after_match_to_end_of_buffer, delimiter).rhs; - } else { - DN_Str8BSplitResult sub_split = DN_Str8BSplit(it, delimiter); - item = sub_split.lhs; - it = sub_split.rhs; - } - - if (item.size || allow_empty_strings) { - if (splits && result < splits_count) - splits[result] = item; - result++; - } - } while (it.size); - - return result; -} - -DN_API DN_Str8SplitResult DN_Str8SplitArena(DN_Str8 string, DN_Str8 delimiter, DN_Str8SplitFlags mode, DN_Arena *arena) -{ - DN_Str8SplitResult result = {}; - DN_USize count = DN_Str8Split(string, delimiter, /*splits*/ nullptr, /*count*/ 0, mode); - result.data = DN_ArenaNewArray(arena, DN_Str8, count, DN_ZMem_No); - if (result.data) { - result.count = DN_Str8Split(string, delimiter, result.data, count, mode); - DN_Assert(count == result.count); - } - return result; -} - -DN_API DN_Str8FindResult DN_Str8FindStr8Array(DN_Str8 string, DN_Str8 const *find, DN_USize find_size, DN_Str8EqCase eq_case) -{ - DN_Str8FindResult result = {}; - for (DN_USize index = 0; !result.found && index < string.size; index++) { - for (DN_USize find_index = 0; find_index < find_size; find_index++) { - DN_Str8 find_item = find[find_index]; - DN_Str8 string_slice = DN_Str8Subset(string, index, find_item.size); - if (DN_Str8Eq(string_slice, find_item, eq_case)) { - result.found = true; - result.index = index; - result.start_to_before_match = DN_Str8FromPtr(string.data, index); - result.match = DN_Str8FromPtr(string.data + index, find_item.size); - result.match_to_end_of_buffer = DN_Str8FromPtr(result.match.data, string.size - index); - result.after_match_to_end_of_buffer = DN_Str8Advance(result.match_to_end_of_buffer, find_item.size); - break; - } - } - } - return result; -} - -DN_API DN_Str8FindResult DN_Str8FindStr8(DN_Str8 string, DN_Str8 find, DN_Str8EqCase eq_case) -{ - DN_Str8FindResult result = DN_Str8FindStr8Array(string, &find, 1, eq_case); - return result; -} - -DN_API DN_Str8FindResult DN_Str8Find(DN_Str8 string, DN_Str8FindFlag flags) -{ - DN_Str8FindResult result = {}; - for (DN_USize index = 0; !result.found && index < string.size; index++) { - result.found |= ((flags & DN_Str8FindFlag_Digit) && DN_CharIsDigit(string.data[index])); - result.found |= ((flags & DN_Str8FindFlag_Alphabet) && DN_CharIsAlphabet(string.data[index])); - result.found |= ((flags & DN_Str8FindFlag_Whitespace) && DN_CharIsWhitespace(string.data[index])); - result.found |= ((flags & DN_Str8FindFlag_Plus) && string.data[index] == '+'); - result.found |= ((flags & DN_Str8FindFlag_Minus) && string.data[index] == '-'); - if (result.found) { - result.index = index; - result.match = DN_Str8FromPtr(string.data + index, 1); - result.match_to_end_of_buffer = DN_Str8FromPtr(result.match.data, string.size - index); - result.after_match_to_end_of_buffer = DN_Str8Advance(result.match_to_end_of_buffer, 1); - } - } - return result; -} - -DN_API DN_Str8 DN_Str8Segment(DN_Arena *arena, DN_Str8 src, DN_USize segment_size, char segment_char) -{ - if (!segment_size || src.size == 0) { - DN_Str8 result = DN_Str8FromStr8Arena(src, arena); - return result; - } - - DN_USize segments = src.size / segment_size; - if (src.size % segment_size == 0) - segments--; - - DN_USize segment_counter = 0; - DN_Str8 result = DN_Str8AllocArena(src.size + segments, DN_ZMem_Yes, arena); - DN_USize write_index = 0; - for (DN_ForIndexU(src_index, src.size)) { - result.data[write_index++] = src.data[src_index]; - if ((src_index + 1) % segment_size == 0 && segment_counter < segments) { - result.data[write_index++] = segment_char; - segment_counter++; - } - DN_AssertF(write_index <= result.size, "result.size=%zu, write_index=%zu", result.size, write_index); - } - - DN_AssertF(write_index == result.size, "result.size=%zu, write_index=%zu", result.size, write_index); - return result; -} - -DN_API DN_Str8 DN_Str8ReverseSegment(DN_Arena *arena, DN_Str8 src, DN_USize segment_size, char segment_char) -{ - if (!segment_size || src.size == 0) { - DN_Str8 result = DN_Str8FromStr8Arena(src, arena); - return result; - } - - DN_USize segments = src.size / segment_size; - if (src.size % segment_size == 0) - segments--; - - DN_USize write_counter = 0; - DN_USize segment_counter = 0; - DN_Str8 result = DN_Str8AllocArena(src.size + segments, DN_ZMem_Yes, arena); - DN_USize write_index = result.size - 1; - - DN_MSVC_WARNING_PUSH - DN_MSVC_WARNING_DISABLE(6293) // NOTE: Ill-defined loop - for (DN_USize src_index = src.size - 1; src_index < src.size; src_index--) { - DN_MSVC_WARNING_POP - result.data[write_index--] = src.data[src_index]; - if (++write_counter % segment_size == 0 && segment_counter < segments) { - result.data[write_index--] = segment_char; - segment_counter++; - } - } - - DN_Assert(write_index == SIZE_MAX); - return result; -} - -DN_API bool DN_Str8Eq(DN_Str8 lhs, DN_Str8 rhs, DN_Str8EqCase eq_case) -{ - if (lhs.size != rhs.size) - return false; - bool result = true; - switch (eq_case) { - case DN_Str8EqCase_Sensitive: { - result = (DN_Memcmp(lhs.data, rhs.data, lhs.size) == 0); - } break; - - case DN_Str8EqCase_Insensitive: { - for (DN_USize index = 0; index < lhs.size && result; index++) - result = (DN_CharToLower(lhs.data[index]) == DN_CharToLower(rhs.data[index])); - } break; - } - return result; -} - -DN_API bool DN_Str8EqInsensitive(DN_Str8 lhs, DN_Str8 rhs) -{ - bool result = DN_Str8Eq(lhs, rhs, DN_Str8EqCase_Insensitive); - return result; -} - -DN_API bool DN_Str8StartsWith(DN_Str8 string, DN_Str8 prefix, DN_Str8EqCase eq_case) -{ - DN_Str8 substring = {string.data, DN_Min(prefix.size, string.size)}; - bool result = DN_Str8Eq(substring, prefix, eq_case); - return result; -} - -DN_API bool DN_Str8StartsWithInsensitive(DN_Str8 string, DN_Str8 prefix) -{ - bool result = DN_Str8StartsWith(string, prefix, DN_Str8EqCase_Insensitive); - return result; -} - -DN_API bool DN_Str8EndsWith(DN_Str8 string, DN_Str8 suffix, DN_Str8EqCase eq_case) -{ - DN_Str8 substring = {string.data + string.size - suffix.size, DN_Min(string.size, suffix.size)}; - bool result = DN_Str8Eq(substring, suffix, eq_case); - return result; -} - -DN_API bool DN_Str8EndsWithInsensitive(DN_Str8 string, DN_Str8 suffix) -{ - bool result = DN_Str8EndsWith(string, suffix, DN_Str8EqCase_Insensitive); - return result; -} - -DN_API bool DN_Str8HasChar(DN_Str8 string, char ch) -{ - bool result = false; - for (DN_USize index = 0; !result && index < string.size; index++) - result = string.data[index] == ch; - return result; -} - -DN_API DN_Str8 DN_Str8TrimPrefix(DN_Str8 string, DN_Str8 prefix, DN_Str8EqCase eq_case) -{ - DN_Str8 result = string; - if (DN_Str8StartsWith(string, prefix, eq_case)) { - result.data += prefix.size; - result.size -= prefix.size; - } - return result; -} - -DN_API DN_Str8 DN_Str8TrimHexPrefix(DN_Str8 string) -{ - DN_Str8 result = DN_Str8TrimPrefix(string, DN_Str8Lit("0x"), DN_Str8EqCase_Insensitive); - return result; -} - -DN_API DN_Str8 DN_Str8TrimSuffix(DN_Str8 string, DN_Str8 suffix, DN_Str8EqCase eq_case) -{ - DN_Str8 result = string; - if (DN_Str8EndsWith(string, suffix, eq_case)) - result.size -= suffix.size; - return result; -} - -DN_API DN_Str8 DN_Str8TrimAround(DN_Str8 string, DN_Str8 trim_string) -{ - DN_Str8 result = DN_Str8TrimPrefix(string, trim_string); - result = DN_Str8TrimSuffix(result, trim_string); - return result; -} - -DN_API DN_Str8 DN_Str8TrimHeadWhitespace(DN_Str8 string) -{ - DN_Str8 result = string; - if (string.size == 0) - return result; - - char const *start = string.data; - char const *end = string.data + string.size; - while (start < end && DN_CharIsWhitespace(start[0])) - start++; - - result = DN_Str8FromPtr(start, end - start); - return result; -} - -DN_API DN_Str8 DN_Str8TrimTailWhitespace(DN_Str8 string) -{ - DN_Str8 result = string; - if (string.size == 0) - return result; - - char const *start = string.data; - char const *end = string.data + string.size; - while (end > start && DN_CharIsWhitespace(end[-1])) - end--; - - result = DN_Str8FromPtr(start, end - start); - return result; -} - -DN_API DN_Str8 DN_Str8TrimWhitespaceAround(DN_Str8 string) -{ - DN_Str8 result = DN_Str8TrimHeadWhitespace(string); - result = DN_Str8TrimTailWhitespace(result); - return result; -} - -DN_API DN_Str8 DN_Str8TrimByteOrderMark(DN_Str8 string) -{ - DN_Str8 result = string; - if (result.size == 0) - return result; - - // TODO(dn): This is little endian - DN_Str8 UTF8_BOM = DN_Str8Lit("\xEF\xBB\xBF"); - DN_Str8 UTF16_BOM_BE = DN_Str8Lit("\xEF\xFF"); - DN_Str8 UTF16_BOM_LE = DN_Str8Lit("\xFF\xEF"); - DN_Str8 UTF32_BOM_BE = DN_Str8Lit("\x00\x00\xFE\xFF"); - DN_Str8 UTF32_BOM_LE = DN_Str8Lit("\xFF\xFE\x00\x00"); - - result = DN_Str8TrimPrefix(result, UTF8_BOM, DN_Str8EqCase_Sensitive); - result = DN_Str8TrimPrefix(result, UTF16_BOM_BE, DN_Str8EqCase_Sensitive); - result = DN_Str8TrimPrefix(result, UTF16_BOM_LE, DN_Str8EqCase_Sensitive); - result = DN_Str8TrimPrefix(result, UTF32_BOM_BE, DN_Str8EqCase_Sensitive); - result = DN_Str8TrimPrefix(result, UTF32_BOM_LE, DN_Str8EqCase_Sensitive); - return result; -} - -DN_API DN_Str8 DN_Str8FileNameFromPath(DN_Str8 path) -{ - DN_Str8 separators[] = {DN_Str8Lit("/"), DN_Str8Lit("\\")}; - DN_Str8BSplitResult split = DN_Str8BSplitLastArray(path, separators, DN_ArrayCountU(separators)); - DN_Str8 result = split.rhs.size ? split.rhs : split.lhs; - return result; -} - -DN_API DN_Str8 DN_Str8FileNameNoExtension(DN_Str8 path) -{ - DN_Str8 file_name = DN_Str8FileNameFromPath(path); - DN_Str8 result = DN_Str8FilePathNoExtension(file_name); - return result; -} - -DN_API DN_Str8 DN_Str8FilePathNoExtension(DN_Str8 path) -{ - DN_Str8BSplitResult split = DN_Str8BSplitLast(path, DN_Str8Lit(".")); - DN_Str8 result = split.lhs; - return result; -} - -DN_API DN_Str8 DN_Str8FileExtension(DN_Str8 path) -{ - DN_Str8BSplitResult split = DN_Str8BSplitLast(path, DN_Str8Lit(".")); - DN_Str8 result = split.rhs; - return result; -} - -DN_API DN_Str8 DN_Str8FileDirectoryFromPath(DN_Str8 path) -{ - DN_Str8 separators[] = {DN_Str8Lit("/"), DN_Str8Lit("\\")}; - DN_Str8BSplitResult split = DN_Str8BSplitLastArray(path, separators, DN_ArrayCountU(separators)); - DN_Str8 result = split.lhs; - return result; -} - -DN_API DN_Str8 DN_Str8AppendF(DN_Arena *arena, DN_Str8 string, char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_Str8 result = DN_Str8AppendFV(arena, string, fmt, args); - va_end(args); - return result; -} - -DN_API DN_Str8 DN_Str8AppendFV(DN_Arena *arena, DN_Str8 string, char const *fmt, va_list args) -{ - // TODO: Calculate size and write into one buffer instead of 2 appends - DN_Str8 append = DN_Str8FromFmtVArena(arena, fmt, args); - DN_Str8 result = DN_Str8AllocArena(string.size + append.size, DN_ZMem_No, arena); - DN_Memcpy(result.data, string.data, string.size); - DN_Memcpy(result.data + string.size, append.data, append.size); - return result; -} - -DN_API DN_Str8 DN_Str8FillF(DN_Arena *arena, DN_USize count, char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_Str8 result = DN_Str8FillFV(arena, count, fmt, args); - va_end(args); - return result; -} - -DN_API DN_Str8 DN_Str8FillFV(DN_Arena *arena, DN_USize count, char const *fmt, va_list args) -{ - DN_Str8 fill = DN_Str8FromFmtVArena(arena, fmt, args); - DN_Str8 result = DN_Str8AllocArena(count * fill.size, DN_ZMem_No, arena); - for (DN_USize index = 0; index < count; index++) { - void *dest = result.data + (index * fill.size); - DN_Memcpy(dest, fill.data, fill.size); - } - return result; -} - -DN_API void DN_Str8Remove(DN_Str8 *string, DN_USize offset, DN_USize size) -{ - if (!string || string->size) - return; - - char *end = string->data + string->size; - char *dest = DN_Min(string->data + offset, end); - char *src = DN_Min(string->data + offset + size, end); - DN_USize bytes_to_move = end - src; - DN_Memmove(dest, src, bytes_to_move); - string->size -= bytes_to_move; -} - -DN_API DN_Str8TruncResult DN_Str8TruncMiddlePtr(DN_Str8 str8, DN_USize side_size, DN_Str8 truncator, char *dest, DN_USize dest_max) -{ - DN_Assert(side_size <= DN_USIZE_MAX / 2); - if (dest) { - // NOTE: If the user passes the dest buffer, we expect it to be sized correctly. - if ((side_size * 2) >= str8.size) { - DN_Assert(dest_max >= str8.size + 1 /*null*/); - } else { - DN_Assert(dest_max >= (2 * side_size + truncator.size) + 1 /*null*/); - } - } - - DN_Str8TruncResult result = {}; - if (str8.size <= (side_size * 2)) { - result.size_req = str8.size; - if (dest) { - DN_Memcpy(dest, str8.data, str8.size); - dest[str8.size] = 0; - result.str8 = DN_Str8FromPtr(dest, result.size_req); - } - return result; - } - - DN_Str8 head = DN_Str8Subset(str8, 0, side_size); - DN_Str8 tail = DN_Str8Subset(str8, str8.size - side_size, side_size); - DN_USize dest_size = 0; - if (dest) { - DN_FmtAppendResult append_result = DN_FmtAppend(dest, &dest_size, dest_max, "%.*s%.*s%.*s", DN_Str8PrintFmt(head), DN_Str8PrintFmt(truncator), DN_Str8PrintFmt(tail)); - result.str8 = append_result.str8; - result.truncated = true; - result.size_req = result.str8.size; - } else { - result.size_req = DN_FmtSize("%.*s%.*s%.*s", DN_Str8PrintFmt(head), DN_Str8PrintFmt(truncator), DN_Str8PrintFmt(tail)); - result.truncated = true; - } - - return result; -} - -DN_API DN_Str8TruncResult DN_Str8TruncMiddle(DN_Str8 str8, DN_USize side_size, DN_Str8 truncator, DN_Arena *arena) -{ - DN_Str8TruncResult trunc = DN_Str8TruncMiddlePtr(str8, side_size, truncator, nullptr, 0); - DN_Str8 dest = DN_Str8AllocArena(trunc.size_req, DN_ZMem_No, arena); - DN_Str8TruncResult result = DN_Str8TruncMiddlePtr(str8, side_size, truncator, dest.data, dest.size + 1); - return result; -} - -DN_API DN_Str8 DN_Str8Lower(DN_Str8 string, DN_Arena *arena) -{ - DN_Str8 result = DN_Str8FromStr8Arena(string, arena); - for (DN_ForIndexU(index, result.size)) - result.data[index] = DN_CharToLower(result.data[index]); - return result; -} - -DN_API DN_Str8 DN_Str8Upper(DN_Str8 string, DN_Arena *arena) -{ - DN_Str8 result = DN_Str8FromStr8Arena(string, arena); - for (DN_ForIndexU(index, result.size)) - result.data[index] = DN_CharToUpper(result.data[index]); - return result; -} - -DN_API DN_Str8 DN_Str8Replace(DN_Str8 string, - DN_Str8 find, - DN_Str8 replace, - DN_USize start_index, - DN_Arena *arena, - DN_Str8EqCase eq_case) -{ - DN_Str8 result = {}; - if (string.size == 0 || find.size == 0 || find.size > string.size || find.size == 0 || string.size == 0) { - result = DN_Str8FromStr8Arena(string, arena); - return result; - } - - DN_TCScratch scratch = DN_TCScratchBeginArena(&arena, 1); - DN_Str8Builder string_builder = DN_Str8BuilderFromArena(&scratch.arena); - DN_USize max = string.size - find.size; - DN_USize head = start_index; - - for (DN_USize tail = head; tail <= max; tail++) { - DN_Str8 check = DN_Str8Subset(string, tail, find.size); - if (!DN_Str8Eq(check, find, eq_case)) - continue; - - if (start_index > 0 && string_builder.string_size == 0) { - // User provided a hint in the string to start searching from, we - // need to add the string up to the hint. We only do this if there's - // a replacement action, otherwise we have a special case for no - // replacements, where the entire string gets copied. - DN_Str8 slice = DN_Str8FromPtr(string.data, head); - DN_Str8BuilderAppendRef(&string_builder, slice); - } - - DN_Str8 range = DN_Str8Subset(string, head, (tail - head)); - DN_Str8BuilderAppendRef(&string_builder, range); - DN_Str8BuilderAppendRef(&string_builder, replace); - head = tail + find.size; - tail += find.size - 1; // NOTE: -1 since the for loop will post increment us past the end of the find string - } - - if (string_builder.string_size == 0) { - // NOTE: No replacement possible, so we just do a full-copy - result = DN_Str8FromStr8Arena(string, arena); - } else { - DN_Str8 remainder = DN_Str8FromPtr(string.data + head, string.size - head); - DN_Str8BuilderAppendRef(&string_builder, remainder); - result = DN_Str8FromStr8BuilderArena(&string_builder, arena); - } - DN_TCScratchEnd(&scratch); - return result; -} - -DN_API DN_Str8 DN_Str8ReplaceSensitive(DN_Str8 string, DN_Str8 find, DN_Str8 replace, DN_USize start_index, DN_Arena *arena) -{ - DN_Str8 result = DN_Str8Replace(string, find, replace, start_index, arena, DN_Str8EqCase_Sensitive); - return result; -} - -DN_API DN_Str8 DN_Str8ReplaceInsensitive(DN_Str8 string, DN_Str8 find, DN_Str8 replace, DN_USize start_index, DN_Arena *arena) -{ - DN_Str8 result = DN_Str8Replace(string, find, replace, start_index, arena, DN_Str8EqCase_Insensitive); - return result; -} - -DN_API DN_Str8 DN_Str8PadNewLinesAllocator(DN_Str8 string, DN_Str8 pad_string, DN_Allocator allocator) -{ - DN_TCScratch scratch = DN_TCScratchBeginAllocator(&allocator, 1); - DN_Str8Builder builder = DN_Str8BuilderFromArena(&scratch.arena); - DN_Str8 it = string; - while (it.size) { - DN_Str8BSplitResult split = DN_Str8BSplit(it, DN_Str8Lit("\n")); - DN_Str8BuilderAppendRef(&builder, DN_Str8FromPtr(split.lhs.data, split.lhs.size + 1)); - it = split.rhs; - } - - DN_Str8 result = DN_Str8FromStr8BuilderDelimitAllocator(&builder, pad_string, allocator); - DN_TCScratchEnd(&scratch); - return result; -} - -DN_API DN_Str8 DN_Str8PadNewLinesArena(DN_Str8 string, DN_Str8 pad_string, DN_Arena *arena) -{ - DN_Str8 result = DN_Str8PadNewLinesAllocator(string, pad_string, DN_AllocatorFromArena(arena)); - return result; -} - -DN_API DN_USize DN_USizeCodepointCountFromUTF8(DN_Str8 str, DN_CodepointCountFlags flags) -{ - DN_USize result = 0; - - if (DN_BitIsNotSet(flags, DN_CodepointCountFlags_SkipANSICode)) { - DN_UTF8DecodeIterator it = {}; - while (DN_UTF8DecodeIterate(&it, str)) - ; - result = it.codepoint_index; - } else { - // NOTE: ANSI SGR (Select Graphic Rendition) sequence handling - // Format: ESC [ parameter_bytes intermediate_bytes final_byte - // Common examples: \x1b[31m (red), \x1b[1;31m (bold red), \x1b[0m (reset) - // Parameter bytes: 0x30-0x3F (digits and :;<=>?) - // Intermediate bytes: 0x20-0x2F (space and !"#$%&'()*+,-./) - // Final byte: 0x40-0x7E (@A-Z[\]^_`a-z{|}~) - char const *p = str.data; - char const *end = DN_Str8End(str); - while (p < end) { - if (*p == '\x1b' && p + 1 < end && *(p + 1) == '[') { // Detect CSI sequence: ESC [ - p += 2; - while (p < end && *p >= 0x30 && *p <= 0x3F) // Skip parameter bytes (0x30-0x3F) - p++; - while (p < end && *p >= 0x20 && *p <= 0x2F) // Skip intermediate bytes (0x20-0x2F) - p++; - if (p < end && *p >= 0x40 && *p <= 0x7E) // Skip final byte (0x40-0x7E) - p++; - continue; - } - - DN_UTF8DecodeResult decode = DN_UTF8Decode(DN_Str8FromPtr(p, end - p)); - if (!decode.success) - break; - p = decode.remaining.data; - result++; - } - } - - return result; -} - -DN_API DN_Str8 DN_Str8LineBreakAllocator(DN_Str8 src, DN_USize desired_width, DN_Str8 delimiter, DN_Str8LineBreakMode mode, DN_Allocator allocator) -{ - DN_TCScratch scratch = DN_TCScratchBeginAllocator(&allocator, 1); - DN_Str8Builder builder = DN_Str8BuilderFromArena(&scratch.arena); - - if (mode == DN_Str8LineBreakMode_AtWord) { - char* start = src.data; - char* end = src.data; - DN_Str8 it = src; - while (it.size) { - DN_Str8 splitters[] = {DN_Str8Lit(" "), DN_Str8Lit("\n")}; - DN_Str8BSplitResult split = DN_Str8BSplitArray(it, splitters, DN_ArrayCountU(splitters)); - DN_USize curr_line_length = end - start; - - // Handle explicit newlines in input - if (split.input_index == 1 /*the newline*/) { - if (curr_line_length == 0 && split.lhs.size) - start = split.lhs.data; - if (split.lhs.size) - end = DN_Str8End(split.lhs); - DN_Str8BuilderAppendRef(&builder, DN_Str8FromPtr(start, end - start)); - start = split.rhs.data; - end = split.rhs.data; - it = split.rhs; - continue; - } - - // Skip empty segments (multiple spaces, leading/trailing spaces) - if (split.lhs.size == 0) { - it = split.rhs; - continue; - } - - // First word on this line - if (curr_line_length == 0) { - start = split.lhs.data; - end = DN_Str8End(split.lhs); - it = split.rhs; - continue; - } - - // Check if adding this word (plus separator space) would overflow - DN_USize combined_length = curr_line_length + 1 + split.lhs.size; - if (combined_length > desired_width) { - // Commit current line, start new line with current word - DN_Str8BuilderAppendRef(&builder, DN_Str8FromPtr(start, end - start)); - start = split.lhs.data; - end = DN_Str8End(split.lhs); - it = split.rhs; - } else { - // Add word to current line - end = DN_Str8End(split.lhs); - it = split.rhs; - } - } - - // Append final line - if (end > start) - DN_Str8BuilderAppendRef(&builder, DN_Str8FromPtr(start, end - start)); - } else { - DN_Str8 it = src; - while (it.size) { - DN_Str8 chunk = DN_Str8Subset(it, 0, desired_width); - DN_Str8BuilderAppendRef(&builder, chunk); - it = DN_Str8Advance(it, desired_width); - } - } - - DN_Str8 result = DN_Str8FromStr8BuilderDelimitAllocator(&builder, delimiter, allocator); - DN_TCScratchEnd(&scratch); - return result; -} - -DN_API DN_Str8 DN_Str8LineBreakArena(DN_Str8 src, DN_USize desired_width, DN_Str8 delimiter, DN_Str8LineBreakMode mode, DN_Arena *arena) -{ - DN_Str8 result = DN_Str8LineBreakAllocator(src, desired_width, delimiter, mode, DN_AllocatorFromArena(arena)); - return result; -} - -DN_API DN_Str8 DN_Str8Table(DN_Str8 const *rows, DN_USize num_rows, DN_USize num_cols, DN_Str8TableFlags flags, DN_Arena *arena) -{ - DN_TCScratch scratch = DN_TCScratchBeginArena(&arena, 1); - DN_U16 col_widths[128] = {}; - for (DN_USize i = 0; i < num_cols; i++) { - for (DN_USize j = 0; j < num_rows; j++) { - DN_USize index = j * num_cols + i; - col_widths[i] = DN_Max(col_widths[i], (DN_U16)DN_USizeCodepointCountFromUTF8(rows[index], DN_CodepointCountFlags_SkipANSICode)); - } - } - - DN_Str8Builder builder = DN_Str8BuilderFromArena(&scratch.arena); - DN_Str8BuilderAppendF(&builder, "+"); - for (DN_USize i = 0; i < num_cols; i++) { - for (DN_USize j = 0; j < col_widths[i] + 2; j++) - DN_Str8BuilderAppendF(&builder, "-"); - DN_Str8BuilderAppendF(&builder, "+"); - } - DN_Str8BuilderAppendF(&builder, "\n"); - - for (DN_USize i = 0; i < num_rows; i++) { - DN_Str8BuilderAppendF(&builder, "|"); - for (DN_USize j = 0; j < num_cols; j++) { - DN_USize index = (i * num_cols) + j; - DN_Str8 item = rows[index]; - DN_Str8BuilderAppendF(&builder, " %.*s", DN_Str8PrintFmt(item)); - DN_USize item_width = DN_USizeCodepointCountFromUTF8(item, DN_CodepointCountFlags_SkipANSICode); - for (DN_USize k = 0; k < col_widths[j] - item_width; k++) - DN_Str8BuilderAppendF(&builder, " "); - DN_Str8BuilderAppendF(&builder, " |"); - } - DN_Str8BuilderAppendF(&builder, "\n"); - - bool print_row_line = i == 0 && DN_BitIsSet(flags, DN_Str8TableFlags_HasHeader); - if (!print_row_line) - print_row_line = DN_BitIsSet(flags, DN_Str8TableFlags_RowLines); - - if (print_row_line) { - DN_Str8BuilderAppendF(&builder, "+"); - for (DN_USize sub_i = 0; sub_i < num_cols; sub_i++) { - for (DN_USize sub_j = 0; sub_j < col_widths[sub_i] + 2; sub_j++) - DN_Str8BuilderAppendF(&builder, "-"); - DN_Str8BuilderAppendF(&builder, "+"); - } - DN_Str8BuilderAppendF(&builder, "\n"); - } - } - - DN_Str8BuilderAppendF(&builder, "+"); - for (DN_USize i = 0; i < num_cols; i++) { - for (DN_USize j = 0; j < col_widths[i] + 2; j++) - DN_Str8BuilderAppendF(&builder, "-"); - DN_Str8BuilderAppendF(&builder, "+"); - } - - DN_Str8 result = DN_Str8FromStr8BuilderArena(&builder, arena); - DN_TCScratchEnd(&scratch); - return result; -} - -#if DN_STR8_AVX512F -DN_API DN_Str8FindResult DN_Str8FindStr8AVX512F(DN_Str8 string, DN_Str8 find) -{ - // NOTE: Algorithm as described in http://0x80.pl/articles/simd-strfind.html - DN_Str8FindResult result = {}; - if (string.size == 0 || find.size == 0 || find.size > string.size) - return result; - - __m512i const find_first_ch = _mm512_set1_epi8(find.data[0]); - __m512i const find_last_ch = _mm512_set1_epi8(find.data[find.size - 1]); - - DN_USize const search_size = string.size - find.size; - DN_USize simd_iterations = search_size / sizeof(__m512i); - char const *ptr = string.data; - - while (simd_iterations--) { - __m512i find_first_ch_block = _mm512_loadu_si512(ptr); - __m512i find_last_ch_block = _mm512_loadu_si512(ptr + find.size - 1); - - // NOTE: AVX512F does not have a cmpeq so we use XOR to place a 0 bit - // where matches are found. - __m512i first_ch_matches = _mm512_xor_si512(find_first_ch_block, find_first_ch); - - // NOTE: We can combine the 2nd XOR and merge the 2 XOR results into one - // operation using the ternarylogic intrinsic. - // - // A = first_ch_matches (find_first_ch_block ^ find_first_ch) - // B = find_last_ch_block - // C = find_last_ch - // - // ternarylogic op => A | (B ^ C) => 0b1111'0110 => 0xf6 - // - // / A / B / C / B ^ C / A | (B ^ C) / - // | 0 | 0 | 0 | 0 | 0 | - // | 0 | 0 | 1 | 1 | 1 | - // | 0 | 1 | 0 | 1 | 1 | - // | 0 | 1 | 1 | 0 | 0 | - // | 1 | 0 | 0 | 0 | 1 | - // | 1 | 0 | 1 | 1 | 1 | - // | 1 | 1 | 0 | 1 | 1 | - // | 1 | 1 | 1 | 0 | 1 | - - __m512i ch_matches = _mm512_ternarylogic_epi32(first_ch_matches, find_last_ch_block, find_last_ch, 0xf6); - - // NOTE: Matches were XOR-ed and are hence indicated as zero so we mask - // out which 32 bit elements in the vector had zero bytes. This uses a - // bit twiddling trick - // https://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord - __mmask16 zero_byte_mask = {}; - { - const __m512i v01 = _mm512_set1_epi32(0x01010101u); - const __m512i v80 = _mm512_set1_epi32(0x80808080u); - const __m512i v1 = _mm512_sub_epi32(ch_matches, v01); - const __m512i tmp1 = _mm512_ternarylogic_epi32(v1, ch_matches, v80, 0x20); - zero_byte_mask = _mm512_test_epi32_mask(tmp1, tmp1); - } - - while (zero_byte_mask) { - uint64_t const lsb_zero_pos = _tzcnt_u64(zero_byte_mask); - char const *base_ptr = ptr + (4 * lsb_zero_pos); - - if (DN_Memcmp(base_ptr + 0, find.data, find.size) == 0) { - result.found = true; - result.index = base_ptr - string.data; - } else if (DN_Memcmp(base_ptr + 1, find.data, find.size) == 0) { - result.found = true; - result.index = base_ptr - string.data + 1; - } else if (DN_Memcmp(base_ptr + 2, find.data, find.size) == 0) { - result.found = true; - result.index = base_ptr - string.data + 2; - } else if (DN_Memcmp(base_ptr + 3, find.data, find.size) == 0) { - result.found = true; - result.index = base_ptr - string.data + 3; - } - - if (result.found) { - result.start_to_before_match = DN_Str8FromPtr(string.data, result.index); - result.match = DN_Str8FromPtr(string.data + result.index, find.size); - result.match_to_end_of_buffer = DN_Str8FromPtr(result.match.data, string.size - result.index); - result.after_match_to_end_of_buffer = DN_Str8Advance(result.match_to_end_of_buffer, find.size); - return result; - } - - zero_byte_mask = DN_BitClearNextLSB(zero_byte_mask); - } - - ptr += sizeof(__m512i); - } - - for (DN_USize index = ptr - string.data; index < string.size; index++) { - DN_Str8 string_slice = DN_Str8Subset(string, index, find.size); - if (DN_Str8Eq(string_slice, find)) { - result.found = true; - result.index = index; - result.start_to_before_match = DN_Str8FromPtr(string.data, index); - result.match = DN_Str8FromPtr(string.data + index, find.size); - result.match_to_end_of_buffer = DN_Str8FromPtr(result.match.data, string.size - index); - result.after_match_to_end_of_buffer = DN_Str8Advance(result.match_to_end_of_buffer, find.size); - return result; - } - } - - return result; -} - -DN_API DN_Str8FindResult DN_Str8FindLastStr8AVX512F(DN_Str8 string, DN_Str8 find) -{ - // NOTE: Algorithm as described in http://0x80.pl/articles/simd-strfind.html - DN_Str8FindResult result = {}; - if (string.size == 0 || find.size == 0 || find.size > string.size) - return result; - - __m512i const find_first_ch = _mm512_set1_epi8(find.data[0]); - __m512i const find_last_ch = _mm512_set1_epi8(find.data[find.size - 1]); - - DN_USize const search_size = string.size - find.size; - DN_USize simd_iterations = search_size / sizeof(__m512i); - char const *ptr = string.data + search_size + 1; - - while (simd_iterations--) { - ptr -= sizeof(__m512i); - __m512i find_first_ch_block = _mm512_loadu_si512(ptr); - __m512i find_last_ch_block = _mm512_loadu_si512(ptr + find.size - 1); - - // NOTE: AVX512F does not have a cmpeq so we use XOR to place a 0 bit - // where matches are found. - __m512i first_ch_matches = _mm512_xor_si512(find_first_ch_block, find_first_ch); - - // NOTE: We can combine the 2nd XOR and merge the 2 XOR results into one - // operation using the ternarylogic intrinsic. - // - // A = first_ch_matches (find_first_ch_block ^ find_first_ch) - // B = find_last_ch_block - // C = find_last_ch - // - // ternarylogic op => A | (B ^ C) => 0b1111'0110 => 0xf6 - // - // / A / B / C / B ^ C / A | (B ^ C) / - // | 0 | 0 | 0 | 0 | 0 | - // | 0 | 0 | 1 | 1 | 1 | - // | 0 | 1 | 0 | 1 | 1 | - // | 0 | 1 | 1 | 0 | 0 | - // | 1 | 0 | 0 | 0 | 1 | - // | 1 | 0 | 1 | 1 | 1 | - // | 1 | 1 | 0 | 1 | 1 | - // | 1 | 1 | 1 | 0 | 1 | - - __m512i ch_matches = _mm512_ternarylogic_epi32(first_ch_matches, find_last_ch_block, find_last_ch, 0xf6); - - // NOTE: Matches were XOR-ed and are hence indicated as zero so we mask - // out which 32 bit elements in the vector had zero bytes. This uses a - // bit twiddling trick - // https://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord - __mmask16 zero_byte_mask = {}; - { - const __m512i v01 = _mm512_set1_epi32(0x01010101u); - const __m512i v80 = _mm512_set1_epi32(0x80808080u); - const __m512i v1 = _mm512_sub_epi32(ch_matches, v01); - const __m512i tmp1 = _mm512_ternarylogic_epi32(v1, ch_matches, v80, 0x20); - zero_byte_mask = _mm512_test_epi32_mask(tmp1, tmp1); - } - - while (zero_byte_mask) { - uint64_t const lsb_zero_pos = _tzcnt_u64(zero_byte_mask); - char const *base_ptr = ptr + (4 * lsb_zero_pos); - - if (DN_Memcmp(base_ptr + 0, find.data, find.size) == 0) { - result.found = true; - result.index = base_ptr - string.data; - } else if (DN_Memcmp(base_ptr + 1, find.data, find.size) == 0) { - result.found = true; - result.index = base_ptr - string.data + 1; - } else if (DN_Memcmp(base_ptr + 2, find.data, find.size) == 0) { - result.found = true; - result.index = base_ptr - string.data + 2; - } else if (DN_Memcmp(base_ptr + 3, find.data, find.size) == 0) { - result.found = true; - result.index = base_ptr - string.data + 3; - } - - if (result.found) { - result.start_to_before_match = DN_Str8FromPtr(string.data, result.index); - result.match = DN_Str8FromPtr(string.data + result.index, find.size); - result.match_to_end_of_buffer = DN_Str8FromPtr(result.match.data, string.size - result.index); - return result; - } - - zero_byte_mask = DN_BitClearNextLSB(zero_byte_mask); - } - } - - for (DN_USize index = ptr - string.data - 1; index < string.size; index--) { - DN_Str8 string_slice = DN_Str8Subset(string, index, find.size); - if (DN_Str8Eq(string_slice, find)) { - result.found = true; - result.index = index; - result.start_to_before_match = DN_Str8FromPtr(string.data, index); - result.match = DN_Str8FromPtr(string.data + index, find.size); - result.match_to_end_of_buffer = DN_Str8FromPtr(result.match.data, string.size - index); - return result; - } - } - - return result; -} - -DN_API DN_Str8BSplitResult DN_Str8BSplitAVX512F(DN_Str8 string, DN_Str8 find) -{ - DN_Str8BSplitResult result = {}; - DN_Str8FindResult find_result = DN_Str8FindAVX512F(string, find); - if (find_result.found) { - result.lhs.data = string.data; - result.lhs.size = find_result.index; - result.rhs = DN_Str8Advance(find_result.match_to_end_of_buffer, find.size); - } else { - result.lhs = string; - } - - return result; -} - -DN_API DN_Str8BSplitResult DN_Str8BSplitLastAVX512F(DN_Str8 string, DN_Str8 find) -{ - DN_Str8BSplitResult result = {}; - DN_Str8FindResult find_result = DN_Str8FindLastAVX512F(string, find); - if (find_result.found) { - result.lhs.data = string.data; - result.lhs.size = find_result.index; - result.rhs = DN_Str8Advance(find_result.match_to_end_of_buffer, find.size); - } else { - result.lhs = string; - } - - return result; -} - -DN_API DN_USize DN_Str8SplitAVX512F(DN_Str8 string, DN_Str8 delimiter, DN_Str8 *splits, DN_USize splits_count, DN_Str8SplitFlags flags) -{ - DN_USize result = 0; // The number of splits in the actual string. - if (string.size == 0 || delimiter.size == 0 || delimiter.size <= 0) - return result; - - DN_Str8BSplitResult split = {}; - DN_Str8 first = string; - do { - split = DN_Str8BSplitAVX512F(first, delimiter); - if (split.lhs.size || DN_BitIsNotSet(flags, DN_Str8SplitFlags_ExcludeEmptyStrings)) { - if (splits && result < splits_count) - splits[result] = split.lhs; - result++; - } - first = split.rhs; - } while (first.size); - - return result; -} - -DN_API DN_Str8Slice DN_Str8SplitAllocAVX512F(DN_Arena *arena, DN_Str8 string, DN_Str8 delimiter, DN_Str8SplitFlags flags) -{ - DN_Str8Slice result = {}; - DN_USize splits_required = DN_Str8SplitAVX512F(string, delimiter, /*splits*/ nullptr, /*count*/ 0, flags); - result.data = DN_ArenaNewArray(arena, DN_Str8, splits_required, DN_ZMem_No); - if (result.data) { - result.count = DN_Str8SplitAVX512F(string, delimiter, result.data, splits_required, flags); - DN_Assert(splits_required == result.count); - } - return result; -} -#endif // DN_STR8_AVX512F - -DN_API DN_Str8 DN_Str8SliceRender(DN_Str8Slice slice, DN_Str8 separator, DN_Arena *arena) -{ - DN_Str8 result = {}; - if (!arena) - return result; - - DN_USize total_size = 0; - for (DN_USize index = 0; index < slice.count; index++) { - if (index) - total_size += separator.size; - DN_Str8 item = slice.data[index]; - total_size += item.size; - } - - result = DN_Str8AllocArena(total_size, DN_ZMem_No, arena); - if (result.data) { - DN_USize write_index = 0; - for (DN_USize index = 0; index < slice.count; index++) { - if (index) { - DN_Memcpy(result.data + write_index, separator.data, separator.size); - write_index += separator.size; - } - DN_Str8 item = slice.data[index]; - DN_Memcpy(result.data + write_index, item.data, item.size); - write_index += item.size; - } - } - - return result; -} - -DN_API DN_Str8 DN_Str8RenderSpaceSep(DN_Str8Slice slice, DN_Arena *arena) -{ - DN_Str8 result = DN_Str8SliceRender(slice, DN_Str8Lit(" "), arena); - return result; -} - -DN_API int DN_Str8CompareNatural(DN_Str8 lhs, DN_Str8 rhs, DN_Str8EqCase eq_case) -{ - const char *lhs_it = lhs.data; - const char *rhs_it = rhs.data; - const char *lhs_end = lhs.data + lhs.size; - const char *rhs_end = rhs.data + rhs.size; - - while (lhs_it < lhs_end && rhs_it < rhs_end) { - // NOTE: Skip leading spaces - while (lhs_it < lhs_end && DN_CharIsWhitespace(*lhs_it)) - lhs_it++; - while (rhs_it < rhs_end && DN_CharIsWhitespace(*rhs_it)) - rhs_it++; - - if (lhs_it >= lhs_end || rhs_it >= rhs_end) - break; - - // NOTE: Check if current positions are digits - if (DN_CharIsDigit(*lhs_it) && DN_CharIsDigit(*rhs_it)) { - // NOTE: Extract full number from lhs - DN_U64 lhs_num = 0; - while (lhs_it < lhs_end && DN_CharIsDigit(*lhs_it)) { - lhs_num = lhs_num * 10 + (*lhs_it - '0'); - lhs_it++; - } - - // NOTE: Extract full number from rhs - DN_U64 rhs_num = 0; - while (rhs_it < rhs_end && DN_CharIsDigit(*rhs_it)) { - rhs_num = rhs_num * 10 + (*rhs_it - '0'); - rhs_it++; - } - - if (lhs_num != rhs_num) - return (lhs_num < rhs_num) ? -1 : 1; - } else { - // NOTE: Compare non-digit characters - char lhs_ch = *lhs_it; - char rhs_ch = *rhs_it; - - if (eq_case == DN_Str8EqCase_Insensitive) { - if (DN_CharIsAlphabet(lhs_ch)) - lhs_ch = DN_CharToLower(lhs_ch); - if (DN_CharIsAlphabet(rhs_ch)) - rhs_ch = DN_CharToLower(rhs_ch); - } - - if (lhs_ch != rhs_ch) - return (lhs_ch < rhs_ch) ? -1 : 1; - lhs_it++; - rhs_it++; - } - } - - // NOTE: One string is prefix of other; shorter comes first - if (lhs_it < lhs_end) - return 1; - if (rhs_it < rhs_end) - return -1; - return 0; -} - -DN_API int DN_Str8CompareLexicographic(DN_Str8 lhs, DN_Str8 rhs, DN_Str8EqCase eq_case) -{ - const char *lhs_it = lhs.data; - const char *rhs_it = rhs.data; - const char *lhs_end = lhs.data + lhs.size; - const char *rhs_end = rhs.data + rhs.size; - - while (lhs_it < lhs_end && rhs_it < rhs_end) { - char lhs_ch = *lhs_it; - char rhs_ch = *rhs_it; - if (eq_case == DN_Str8EqCase_Insensitive) { - if (DN_CharIsAlphabet(lhs_ch)) - lhs_ch = DN_CharToLower(lhs_ch); - if (DN_CharIsAlphabet(rhs_ch)) - rhs_ch = DN_CharToLower(rhs_ch); - } - if (lhs_ch != rhs_ch) - return (lhs_ch < rhs_ch) ? -1 : 1; - lhs_it++; - rhs_it++; - } - - // NOTE: One string is prefix of other; shorter comes first - if (lhs.size < rhs.size) - return -1; - if (rhs.size < lhs.size) - return 1; - return 0; -} - - -DN_API bool DN_Str16Eq(DN_Str16 lhs, DN_Str16 rhs) -{ - if (lhs.size != rhs.size) - return false; - bool result = (DN_Memcmp(lhs.data, rhs.data, lhs.size) == 0); - return result; -} - - -DN_API DN_Str16 DN_Str16SliceRender(DN_Str16Slice slice, DN_Str16 separator, DN_Arena *arena) -{ - DN_Str16 result = {}; - if (!arena) - return result; - - DN_USize total_size = 0; - for (DN_USize index = 0; index < slice.count; index++) { - if (index) - total_size += separator.size; - DN_Str16 item = slice.data[index]; - total_size += item.size; - } - - result = {DN_ArenaNewArray(arena, wchar_t, total_size + 1, DN_ZMem_No), total_size}; - if (result.data) { - DN_USize write_index = 0; - for (DN_USize index = 0; index < slice.count; index++) { - if (index) { - DN_Memcpy(result.data + write_index, separator.data, separator.size * sizeof(result.data[0])); - write_index += separator.size; - } - DN_Str16 item = slice.data[index]; - DN_Memcpy(result.data + write_index, item.data, item.size * sizeof(result.data[0])); - write_index += item.size; - } - } - - result.data[total_size] = 0; - return result; -} - -DN_API DN_Str16 DN_Str16RenderSpaceSep(DN_Str16Slice slice, DN_Arena *arena) -{ - DN_Str16 result = DN_Str16SliceRender(slice, DN_Str16Lit(L" "), arena); - return result; -} - -DN_API DN_Str8Builder DN_Str8BuilderFromArena(DN_Arena *arena) -{ - DN_Str8Builder result = {}; - result.arena = arena; - return result; -} - -DN_API DN_Str8Builder DN_Str8BuilderFromStr8PtrRef(DN_Arena *arena, DN_Str8 const *strings, DN_USize size) -{ - DN_Str8Builder result = DN_Str8BuilderFromArena(arena); - DN_Str8BuilderAppendArrayRef(&result, strings, size); - return result; -} - -DN_API DN_Str8Builder DN_Str8BuilderFromStr8PtrCopy(DN_Arena *arena, DN_Str8 const *strings, DN_USize size) -{ - DN_Str8Builder result = DN_Str8BuilderFromArena(arena); - DN_Str8BuilderAppendArrayCopy(&result, strings, size); - return result; -} - -DN_API DN_Str8Builder DN_Str8BuilderFromBuilder(DN_Arena *arena, DN_Str8Builder const *builder) -{ - DN_Str8Builder result = DN_Str8BuilderFromArena(arena); - DN_Str8BuilderAppendBuilderCopy(&result, builder); - return result; -} - -DN_API bool DN_Str8BuilderAddArrayRef(DN_Str8Builder *builder, DN_Str8 const *strings, DN_USize size, DN_Str8BuilderAdd add) -{ - if (!builder) - return false; - - if (!strings || size <= 0) - return true; - - // NOTE: Allocate the links - DN_Str8Link *links = DN_ArenaNewArrayNoZ(builder->arena, DN_Str8Link, size); - if (!links) - return false; - - if (add == DN_Str8BuilderAdd_Append) { - for (DN_ForIndexU(index, size)) { - DN_Str8 string = strings[index]; - DN_Str8Link *link = links + index; - link->string = string; - link->next = NULL; - if (builder->head) - builder->tail->next = link; - else - builder->head = link; - builder->tail = link; - builder->count++; - builder->string_size += string.size; - } - } else { - DN_Assert(add == DN_Str8BuilderAdd_Prepend); - DN_MSVC_WARNING_PUSH - DN_MSVC_WARNING_DISABLE(6293) // NOTE: Ill-defined loop - for (DN_USize index = size - 1; index < size; index--) { - DN_MSVC_WARNING_POP - DN_Str8 string = strings[index]; - DN_Str8Link *link = links + index; - link->string = string; - link->next = builder->head; - builder->head = link; - if (!builder->tail) - builder->tail = link; - builder->count++; - builder->string_size += string.size; - } - } - return true; -} - -DN_API bool DN_Str8BuilderAddArrayCopy(DN_Str8Builder *builder, DN_Str8 const *strings, DN_USize size, DN_Str8BuilderAdd add) -{ - if (!builder) - return false; - - if (!strings || size <= 0) - return true; - - bool result = true; - DN_U64 arena_p = DN_MemListPos(builder->arena->mem); - DN_Str8 *strings_copy = DN_ArenaNewArrayNoZ(builder->arena, DN_Str8, size); - for (DN_ForIndexU(index, size)) { - strings_copy[index] = DN_Str8FromStr8Arena(strings[index], builder->arena); - if (strings_copy[index].size != strings[index].size) { - result = false; - break; - } - } - - if (result) - result = DN_Str8BuilderAddArrayRef(builder, strings_copy, size, add); - else - DN_MemListPopTo(builder->arena->mem, arena_p); - return result; -} - -DN_API bool DN_Str8BuilderAddFV(DN_Str8Builder *builder, DN_Str8BuilderAdd add, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - DN_Str8 string = DN_Str8FromFmtVArena(builder->arena, fmt, args); - DN_U64 arena_p = DN_MemListPos(builder->arena->mem); - bool result = DN_Str8BuilderAddArrayRef(builder, &string, 1, add); - if (!result) - DN_MemListPopTo(builder->arena->mem, arena_p); - return result; -} - -DN_API bool DN_Str8BuilderAppendRef(DN_Str8Builder *builder, DN_Str8 string) -{ - bool result = DN_Str8BuilderAddArrayRef(builder, &string, 1, DN_Str8BuilderAdd_Append); - return result; -} - -DN_API bool DN_Str8BuilderAppendCopy(DN_Str8Builder *builder, DN_Str8 string) -{ - bool result = DN_Str8BuilderAddArrayCopy(builder, &string, 1, DN_Str8BuilderAdd_Append); - return result; -} - -DN_API bool DN_Str8BuilderAppendF(DN_Str8Builder *builder, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - bool result = DN_Str8BuilderAppendFV(builder, fmt, args); - va_end(args); - return result; -} - -DN_API bool DN_Str8BuilderAppendBytesRef(DN_Str8Builder *builder, void const *ptr, DN_USize size) -{ - DN_Str8 input = DN_Str8FromPtr(ptr, size); - bool result = DN_Str8BuilderAppendRef(builder, input); - return result; -} - -DN_API bool DN_Str8BuilderAppendBytesCopy(DN_Str8Builder *builder, void const *ptr, DN_USize size) -{ - DN_Str8 input = DN_Str8FromPtr(ptr, size); - bool result = DN_Str8BuilderAppendCopy(builder, input); - return result; -} - -static bool DN_Str8BuilderAppendBuilder_(DN_Str8Builder *dest, DN_Str8Builder const *src, bool copy) -{ - if (!dest) - return false; - if (!src || src->string_size == 0) - return true; - - DN_Arena arena = DN_ArenaTempBeginFromArena(dest->arena); - DN_Str8Link *links = DN_ArenaNewArrayNoZ(&arena, DN_Str8Link, src->count); - bool result = true; - if (links) { - DN_Str8Link *first = nullptr; - DN_Str8Link *last = nullptr; - DN_USize link_index = 0; - for (DN_Str8Link const *it = src->head; it; it = it->next) { - DN_Str8Link *link = links + link_index++; - link->next = nullptr; - link->string = it->string; - - if (copy) { - link->string = DN_Str8FromStr8Arena(it->string, &arena); - if (link->string.size != it->string.size) { - result = false; - break; - } - } - - if (last) - last->next = link; - else - first = link; - last = link; - } - - if (result) { - if (dest->head) - dest->tail->next = first; - else - dest->head = first; - dest->tail = last; - dest->count += src->count; - dest->string_size += src->string_size; - } - } - DN_ArenaTempEnd(&arena, result ? DN_ArenaReset_No : DN_ArenaReset_Yes); - return result; -} - -DN_API bool DN_Str8BuilderAppendBuilderRef(DN_Str8Builder *dest, DN_Str8Builder const *src) -{ - bool result = DN_Str8BuilderAppendBuilder_(dest, src, false); - return result; -} - -DN_API bool DN_Str8BuilderAppendBuilderCopy(DN_Str8Builder *dest, DN_Str8Builder const *src) -{ - bool result = DN_Str8BuilderAppendBuilder_(dest, src, true); - return result; -} - -DN_API bool DN_Str8BuilderPrependRef(DN_Str8Builder *builder, DN_Str8 string) -{ - bool result = DN_Str8BuilderAddArrayRef(builder, &string, 1, DN_Str8BuilderAdd_Prepend); - return result; -} - -DN_API bool DN_Str8BuilderPrependCopy(DN_Str8Builder *builder, DN_Str8 string) -{ - bool result = DN_Str8BuilderAddArrayCopy(builder, &string, 1, DN_Str8BuilderAdd_Prepend); - return result; -} - -DN_API bool DN_Str8BuilderPrependF(DN_Str8Builder *builder, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - bool result = DN_Str8BuilderPrependFV(builder, fmt, args); - va_end(args); - return result; -} - -DN_API bool DN_Str8BuilderErase(DN_Str8Builder *builder, DN_Str8 string) -{ - for (DN_Str8Link **it = &builder->head; *it; it = &((*it)->next)) { - if (DN_Str8Eq((*it)->string, string)) { - *it = (*it)->next; - builder->string_size -= string.size; - builder->count -= 1; - return true; - } - } - return false; -} - -DN_API DN_Str8 DN_Str8FromStr8BuilderAllocator(DN_Str8Builder const *builder, DN_Allocator allocator) -{ - DN_Str8 result = DN_Str8FromStr8BuilderDelimitAllocator(builder, DN_Str8Lit(""), allocator); - return result; -} - -DN_API DN_Str8 DN_Str8FromStr8BuilderArena(DN_Str8Builder const *builder, DN_Arena *arena) -{ - DN_Str8 result = DN_Str8FromStr8BuilderAllocator(builder, DN_AllocatorFromArena(arena)); - return result; -} - -DN_API DN_Str8 DN_Str8FromStr8BuilderDelimitAllocator(DN_Str8Builder const *builder, DN_Str8 delimiter, DN_Allocator allocator) -{ - DN_Str8 result = {}; - if (!builder || builder->string_size <= 0 || builder->count <= 0) - return result; - - DN_USize size_for_delimiter = delimiter.size ? ((builder->count - 1) * delimiter.size) : 0; - result = DN_Str8AllocAllocator(builder->string_size + size_for_delimiter, DN_ZMem_No, allocator); - if (!result.data) - return result; - - DN_USize write_count = 0; - for (DN_Str8Link *link = builder->head; link; link = link->next) { - DN_Memcpy(result.data + write_count, link->string.data, link->string.size); - write_count += link->string.size; - if (link->next && delimiter.size) { - DN_Memcpy(result.data + write_count, delimiter.data, delimiter.size); - write_count += delimiter.size; - } - } - - result.data[write_count] = 0; - DN_Assert(write_count == builder->string_size + size_for_delimiter); - return result; -} - -DN_API DN_Str8 DN_Str8FromStr8BuilderDelimitArena(DN_Str8Builder const *builder, DN_Str8 delimiter, DN_Arena *arena) -{ - DN_Str8 result = DN_Str8FromStr8BuilderDelimitAllocator(builder, delimiter, DN_AllocatorFromArena(arena)); - return result; -} - -// NOTE: DN_UTF -DN_API int DN_UTF8Encode(DN_U8 utf8[4], DN_U32 codepoint) -{ - // NOTE: Table from https://www.reedbeta.com/blog/programmers-intro-to-unicode/ - // ----------------------------------------+----------------------------+--------------------+ - // UTF-8 (binary) | Code point (binary) | Range | - // ----------------------------------------+----------------------------+--------------------+ - // 0xxx'xxxx | xxx'xxxx | U+0000 - U+007F | - // 110x'xxxx 10yy'yyyy | xxx'xxyy'yyyy | U+0080 - U+07FF | - // 1110'xxxx 10yy'yyyy 10zz'zzzz | xxxx'yyyy'yyzz'zzzz | U+0800 - U+FFFF | - // 1111'0xxx 10yy'yyyy 10zz'zzzz 10ww'wwww | x'xxyy'yyyy'zzzz'zzww'wwww | U+10000 - U+10FFFF | - // ----------------------------------------+----------------------------+--------------------+ - - if (codepoint <= 0b0111'1111) { - utf8[0] = DN_Cast(DN_U8) codepoint; - return 1; - } - - if (codepoint <= 0b0111'1111'1111) { - utf8[0] = (0b1100'0000 | ((codepoint >> 6) & 0b01'1111)); // x - utf8[1] = (0b1000'0000 | ((codepoint >> 0) & 0b11'1111)); // y - return 2; - } - - if (codepoint <= 0b1111'1111'1111'1111) { - utf8[0] = (0b1110'0000 | ((codepoint >> 12) & 0b00'1111)); // x - utf8[1] = (0b1000'0000 | ((codepoint >> 6) & 0b11'1111)); // y - utf8[2] = (0b1000'0000 | ((codepoint >> 0) & 0b11'1111)); // z - return 3; - } - - if (codepoint <= 0b1'1111'1111'1111'1111'1111) { - utf8[0] = (0b1111'0000 | ((codepoint >> 18) & 0b00'0111)); // x - utf8[1] = (0b1000'0000 | ((codepoint >> 12) & 0b11'1111)); // y - utf8[2] = (0b1000'0000 | ((codepoint >> 6) & 0b11'1111)); // z - utf8[3] = (0b1000'0000 | ((codepoint >> 0) & 0b11'1111)); // w - return 4; - } - - return 0; -} - -DN_API DN_UTF8DecodeResult DN_UTF8Decode(DN_Str8 stream) -{ - DN_UTF8DecodeResult result = {}; - result.remaining = stream; - if (stream.size <= 0) - return result; - - DN_U8 b0 = DN_Cast(DN_U8)stream.data[0]; - DN_U8 b1 = DN_Cast(DN_U8)(stream.size >= 2 ? stream.data[1] : 0); - DN_U8 b2 = DN_Cast(DN_U8)(stream.size >= 3 ? stream.data[2] : 0); - DN_U8 b3 = DN_Cast(DN_U8)(stream.size >= 4 ? stream.data[3] : 0); - - if ((b0 & 0b1000'0000) == 0) { - result.codepoint = b0; - result.success = true; - result.remaining = DN_Str8FromPtr(stream.data + 1, stream.size - 1); - return result; - } - - if ((b0 & 0b1110'0000) == 0b1100'0000) { - if (stream.size < 2) - return result; - if ((b1 & 0b1100'0000) != 0b1000'0000) - return result; - DN_U32 cp = ((b0 & 0b0001'1111) << 6) | ((b1 & 0b0011'1111) << 0); - if (cp < 0x80) - return result; - result.codepoint = cp; - result.success = true; - result.remaining = DN_Str8FromPtr(stream.data + 2, stream.size - 2); - return result; - } - - if ((b0 & 0b1111'0000) == 0b1110'0000) { - if (stream.size < 3) - return result; - if ((b1 & 0b1100'0000) != 0b1000'0000) - return result; - if ((b2 & 0b1100'0000) != 0b1000'0000) - return result; - DN_U32 cp = ((b0 & 0b0000'1111) << 12) | ((b1 & 0b0011'1111) << 6) | ((b2 & 0b0011'1111) << 0); - if (cp < 0x800) - return result; - result.codepoint = cp; - result.success = true; - result.remaining = DN_Str8FromPtr(stream.data + 3, stream.size - 3); - return result; - } - - if ((b0 & 0b1111'1000) == 0b1111'0000) { - if (stream.size < 4) - return result; - if ((b1 & 0b1100'0000) != 0b1000'0000) - return result; - if ((b2 & 0b1100'0000) != 0b1000'0000) - return result; - if ((b3 & 0b1100'0000) != 0b1000'0000) - return result; - DN_U32 cp = ((b0 & 0b0000'0111) << 18) | - ((b1 & 0b0011'1111) << 12) | - ((b2 & 0b0011'1111) << 6) | - ((b3 & 0b0011'1111) << 0); - if (cp < 0x10000 || cp > 0x10FFFF) - return result; - result.codepoint = cp; - result.success = true; - result.remaining = DN_Str8FromPtr(stream.data + 4, stream.size - 4); - return result; - } - - return result; -} - -DN_API bool DN_UTF8DecodeIterate(DN_UTF8DecodeIterator *it, DN_Str8 utf8) -{ - if (it->init) { - it->codepoint_index++; - } else { - it->remaining = utf8; - it->init = true; - } - DN_UTF8DecodeResult decode = DN_UTF8Decode(it->remaining); - it->success = decode.success; - it->remaining = decode.remaining; - it->codepoint = decode.codepoint; - bool result = it->success; - return result; -} - -DN_API int DN_UTF16Encode(DN_U16 utf16[2], DN_U32 codepoint) -{ - // NOTE: Table from https://www.reedbeta.com/blog/programmers-intro-to-unicode/ - // ----------------------------------------+------------------------------------+------------------+ - // UTF-16 (binary) | Code point (binary) | Range | - // ----------------------------------------+------------------------------------+------------------+ - // xxxx'xxxx'xxxx'xxxx | xxxx'xxxx'xxxx'xxxx | U+0000???U+FFFF | - // 1101'10xx'xxxx'xxxx 1101'11yy'yyyy'yyyy | xxxx'xxxx'xxyy'yyyy'yyyy + 0x10000 | U+10000???U+10FFFF | - // ----------------------------------------+------------------------------------+------------------+ - - if (codepoint <= 0b1111'1111'1111'1111) { - utf16[0] = DN_Cast(DN_U16) codepoint; - return 1; - } - - if (codepoint <= 0b1111'1111'1111'1111'1111) { - DN_U32 surrogate_codepoint = codepoint + 0x10000; - utf16[0] = 0b1101'1000'0000'0000 | ((surrogate_codepoint >> 10) & 0b11'1111'1111); // x - utf16[1] = 0b1101'1100'0000'0000 | ((surrogate_codepoint >> 0) & 0b11'1111'1111); // y - return 2; - } - - return 0; -} - - -DN_API DN_U8 DN_U8FromHexNibble(char hex) -{ - bool digit = hex >= '0' && hex <= '9'; - bool upper = hex >= 'A' && hex <= 'F'; - bool lower = hex >= 'a' && hex <= 'f'; - DN_U8 result = 0xFF; - if (digit) - result = hex - '0'; - if (upper) - result = hex - 'A' + 10; - if (lower) - result = hex - 'a' + 10; - return result; -} - -DN_API DN_NibbleFromU8Result DN_NibbleFromU8(DN_U8 u8) -{ - static char const *table = "0123456789abcdef"; - DN_U8 lhs = (u8 >> 0) & 0xF; - DN_U8 rhs = (u8 >> 4) & 0xF; - DN_NibbleFromU8Result result = {}; - result.nibble0 = table[rhs]; - result.nibble1 = table[lhs]; - return result; -} - -DN_API DN_USize DN_BytesFromHex(DN_Str8 hex, void *dest, DN_USize dest_count) -{ - DN_Str8 hex_trimmed = DN_Str8TrimHexPrefix(hex); - DN_USize result = 0; - if (hex_trimmed.size > (dest_count * 2)) - return result; - - DN_U8 *ptr = DN_Cast(DN_U8 *) dest; - DN_USize index = 0; - - // NOTE: We are given an odd-sized hex string e.g.: 'F' instead of '0F', we 'left-pad' the parser - // and support reading the single nibble as 'F' - if (hex_trimmed.size % 2 != 0) { - DN_U8 nibble0 = 0; - DN_U8 nibble1 = DN_U8FromHexNibble(hex_trimmed.data[index++]); - if (nibble1 == 0xFF) - return result; - *ptr++ = nibble0 << 4 | nibble1 << 0; - result++; - } - - // NOTE: Parse the rest of the hex which is in byte pairs - for (; index < hex_trimmed.size; index += 2) { - DN_U8 nibble0 = DN_U8FromHexNibble(hex_trimmed.data[index + 0]); - DN_U8 nibble1 = DN_U8FromHexNibble(hex_trimmed.data[index + 1]); - if (nibble0 == 0xFF || nibble1 == 0xFF) - return result; - *ptr++ = nibble0 << 4 | nibble1 << 0; - result++; - } - return result; -} - -DN_API DN_Str8 DN_BytesFromHexArena(DN_Str8 hex, DN_Arena *arena) -{ - DN_Str8 result = DN_BytesFromHexPtrArena(hex.data, hex.size, arena); - return result; -} - -DN_API DN_USize DN_BytesFromHexPtr(char const *hex, DN_USize hex_count, void *dest, DN_USize dest_count) -{ - DN_USize result = DN_BytesFromHex(DN_Str8FromPtr(hex, hex_count), dest, dest_count); - return result; -} - -DN_API DN_Str8 DN_BytesFromHexPtrArena(char const *hex, DN_USize hex_count, DN_Arena *arena) -{ - DN_Str8 hex_trimmed = DN_Str8TrimHexPrefix(DN_Str8FromPtr(hex, hex_count)); - DN_Assert(hex_trimmed.size % 2 == 0); - DN_Str8 result = {}; - result.data = DN_ArenaNewArray(arena, char, hex_trimmed.size / 2, DN_ZMem_No); - if (result.data) - result.size = DN_BytesFromHex(hex_trimmed, result.data, hex_trimmed.size / 2); - return result; -} - -DN_API DN_Str8 DN_BytesFromHexPtrPool(char const *hex, DN_USize hex_count, DN_Pool *pool) -{ - DN_Str8 hex_trimmed = DN_Str8TrimHexPrefix(DN_Str8FromPtr(hex, hex_count)); - DN_Assert(hex_trimmed.size % 2 == 0); - DN_Str8 result = {}; - result.data = DN_PoolNewArray(pool, char, hex_trimmed.size / 2); - if (result.data) - result.size = DN_BytesFromHex(hex_trimmed, result.data, hex_trimmed.size / 2); - return result; -} - - -DN_API DN_U8x16 DN_BytesFromHex32Ptr(char const *hex, DN_USize hex_count) -{ - DN_U8x16 result = {}; - DN_Str8 hex_trimmed = DN_Str8TrimHexPrefix(DN_Str8FromPtr(hex, hex_count)); - DN_Assert(hex_trimmed.size / 2 == sizeof result.data); - DN_USize bytes_written = DN_BytesFromHex(hex_trimmed, result.data, sizeof result.data); - DN_Assert(bytes_written == sizeof result.data); - return result; -} - -DN_API DN_U8x32 DN_BytesFromHex64Ptr(char const *hex, DN_USize hex_count) -{ - DN_U8x32 result = {}; - DN_Str8 hex_trimmed = DN_Str8TrimHexPrefix(DN_Str8FromPtr(hex, hex_count)); - DN_Assert(hex_trimmed.size / 2 == sizeof result.data); - DN_USize bytes_written = DN_BytesFromHex(hex_trimmed, result.data, sizeof result.data); - DN_Assert(bytes_written == sizeof result.data); - return result; -} - -DN_API DN_HexU64 DN_HexFromU64(DN_U64 value, DN_HexFromU64Type type) -{ - DN_HexU64 result = {}; - DN_USize size = DN_HexFromPtrBytes(&value, sizeof(value), result.data, sizeof(result.data), DN_TrimLeadingZero_No); - result.size = DN_SaturateCastUSizeToU8(size); - if (type == DN_HexFromU64Type_Uppercase) { - for (DN_USize index = 0; index < result.size; index++) - result.data[index] = DN_CharToUpper(result.data[index]); - } - return result; -} - -DN_API DN_USize DN_HexFromPtrBytes(void const *bytes, DN_USize bytes_count, void *hex, DN_USize hex_count, DN_TrimLeadingZero trim_leading_z) -{ - DN_USize result = 0; - if ((bytes_count * 2) > hex_count) - return result; - DN_U8 const *src_u8 = DN_Cast(DN_U8 const *) bytes; - DN_U8 *ptr = DN_Cast(DN_U8 *) hex; - bool leading_zeros = true; - - for (DN_USize index = 0; index < bytes_count; index++) { - char ch = src_u8[index]; - if (leading_zeros) - leading_zeros = ch == 0; - - if (leading_zeros) { - if (trim_leading_z == DN_TrimLeadingZero_Yes && ch == 0) - continue; - } - - DN_NibbleFromU8Result to_nibbles = DN_NibbleFromU8(ch); - *ptr++ = to_nibbles.nibble0; - *ptr++ = to_nibbles.nibble1; - result += 2; - } - - if (result == 0) { - *ptr = '0'; - result++; - } - return result; -} - -DN_API DN_Str8 DN_HexFromPtrBytesArena(void const *bytes, DN_USize bytes_count, DN_Arena *arena, DN_TrimLeadingZero trim_leading_z) -{ - DN_Str8 result = {}; - if (bytes_count) { - result.data = DN_ArenaNewArray(arena, char, bytes_count * 2, DN_ZMem_No); - if (result.data) - result.size = DN_HexFromPtrBytes(bytes, bytes_count, result.data, bytes_count * 2, trim_leading_z); - } - return result; -} - -DN_API DN_USize DN_HexFromStr8Bytes(DN_Str8 bytes, void *hex, DN_USize hex_count, DN_TrimLeadingZero trim_leading_z) -{ - DN_USize result = DN_HexFromPtrBytes(bytes.data, bytes.size, hex, hex_count, trim_leading_z); - return result; -} - -DN_API DN_Str8 DN_HexFromStr8BytesArena(DN_Str8 bytes, DN_Arena *arena, DN_TrimLeadingZero trim_leading_z) -{ - DN_Str8 result = {}; - if (bytes.size) { - result.data = DN_ArenaNewArray(arena, char, bytes.size * 2, DN_ZMem_No); - if (result.data) - result.size = DN_HexFromStr8Bytes(bytes, result.data, bytes.size * 2, trim_leading_z); - } - return result; -} - -DN_API DN_Hex32 DN_Hex32FromPtr16b(void const *bytes, DN_USize bytes_count, DN_TrimLeadingZero trim_leading_z) -{ - DN_Hex32 result = {}; - DN_Assert(bytes_count * 2 == sizeof result.data - 1); - result.size = DN_HexFromPtrBytes(bytes, bytes_count, result.data, sizeof result.data, trim_leading_z); - DN_Assert(result.size <= sizeof result.data - 1); - return result; -} - -DN_API DN_Hex64 DN_Hex64FromPtr32b(void const *bytes, DN_USize bytes_count, DN_TrimLeadingZero trim_leading_z) -{ - DN_Hex64 result = {}; - DN_Assert(bytes_count * 2 == sizeof result.data - 1); - result.size = DN_HexFromPtrBytes(bytes, bytes_count, result.data, sizeof result.data, trim_leading_z); - DN_Assert(result.size <= sizeof result.data - 1); - return result; -} - -DN_API DN_Hex128 DN_Hex128FromPtr64b(void const *bytes, DN_USize bytes_count, DN_TrimLeadingZero trim_leading_z) -{ - DN_Hex128 result = {}; - DN_Assert(bytes_count * 2 == sizeof result.data - 1); - result.size = DN_HexFromPtrBytes(bytes, bytes_count, result.data, sizeof result.data, trim_leading_z); - DN_Assert(result.size <= sizeof result.data - 1); - return result; -} - -DN_API DN_Str8x128 DN_AgeStr8FromMsU64(DN_U64 duration_ms, DN_AgeUnit units) -{ - DN_Str8x128 result = {}; - DN_U64 remainder_ms = duration_ms; - if (units & DN_AgeUnit_FractionalSec) { - units |= DN_AgeUnit_Sec; - units &= ~DN_AgeUnit_Ms; - } - - DN_Str8 unit_suffix = {}; - if (units & DN_AgeUnit_Year) { - unit_suffix = DN_Str8Lit("y"); - DN_USize value_usize = remainder_ms / (DN_SecFromYears(1) * 1000); - remainder_ms -= DN_SecFromYears(value_usize) * 1000; - if (value_usize) - DN_FmtAppend(result.data, &result.size, sizeof(result.data), "%s%zu%.*s", result.size ? " " : "", value_usize, DN_Str8PrintFmt(unit_suffix)); - } - - if (units & DN_AgeUnit_Week) { - unit_suffix = DN_Str8Lit("w"); - DN_USize value_usize = remainder_ms / (DN_SecFromWeeks(1) * 1000); - remainder_ms -= DN_SecFromWeeks(value_usize) * 1000; - if (value_usize) - DN_FmtAppend(result.data, &result.size, sizeof(result.data), "%s%zu%.*s", result.size ? " " : "", value_usize, DN_Str8PrintFmt(unit_suffix)); - } - - if (units & DN_AgeUnit_Day) { - unit_suffix = DN_Str8Lit("d"); - DN_USize value_usize = remainder_ms / (DN_SecFromDays(1) * 1000); - remainder_ms -= DN_SecFromDays(value_usize) * 1000; - if (value_usize) - DN_FmtAppend(result.data, &result.size, sizeof(result.data), "%s%zu%.*s", result.size ? " " : "", value_usize, DN_Str8PrintFmt(unit_suffix)); - } - - if (units & DN_AgeUnit_Hr) { - unit_suffix = DN_Str8Lit("h"); - DN_USize value_usize = remainder_ms / (DN_SecFromHours(1) * 1000); - remainder_ms -= DN_SecFromHours(value_usize) * 1000; - if (value_usize) - DN_FmtAppend(result.data, &result.size, sizeof(result.data), "%s%zu%.*s", result.size ? " " : "", value_usize, DN_Str8PrintFmt(unit_suffix)); - } - - if (units & DN_AgeUnit_Min) { - unit_suffix = DN_Str8Lit("m"); - DN_USize value_usize = remainder_ms / (DN_SecFromMins(1) * 1000); - remainder_ms -= DN_SecFromMins(value_usize) * 1000; - if (value_usize) - DN_FmtAppend(result.data, &result.size, sizeof(result.data), "%s%zu%.*s", result.size ? " " : "", value_usize, DN_Str8PrintFmt(unit_suffix)); - } - - if (units & DN_AgeUnit_Sec) { - unit_suffix = DN_Str8Lit("s"); - if (units & DN_AgeUnit_FractionalSec) { - DN_F64 remainder_s = remainder_ms / 1000.0; - DN_FmtAppend(result.data, &result.size, sizeof(result.data), "%s%.3f%.*s", result.size ? " " : "", remainder_s, DN_Str8PrintFmt(unit_suffix)); - remainder_ms = 0; - } else { - DN_USize value_usize = remainder_ms / 1000; - remainder_ms -= DN_Cast(DN_USize)(value_usize * 1000); - if (value_usize) - DN_FmtAppend(result.data, &result.size, sizeof(result.data), "%s%zu%.*s", result.size ? " " : "", value_usize, DN_Str8PrintFmt(unit_suffix)); - } - } - - if (units & DN_AgeUnit_Ms) { - unit_suffix = DN_Str8Lit("ms"); - DN_Assert((units & DN_AgeUnit_FractionalSec) == 0); - DN_USize value_usize = remainder_ms; - remainder_ms -= value_usize; - if (value_usize || result.size == 0) - DN_FmtAppend(result.data, &result.size, sizeof(result.data), "%s%zu%.*s", result.size ? " " : "", value_usize, DN_Str8PrintFmt(unit_suffix)); - } - - if (result.size == 0) - DN_FmtAppend(result.data, &result.size, sizeof(result.data), "0%.*s", DN_Str8PrintFmt(unit_suffix)); - return result; -} - -DN_API DN_Str8x128 DN_AgeStr8FromSecU64(DN_U64 duration_s, DN_AgeUnit units) -{ - DN_U64 duration_ms = duration_s * 1000; - DN_Str8x128 result = DN_AgeStr8FromMsU64(duration_ms, units); - return result; -} - -DN_API DN_Str8x128 DN_AgeStr8FromSecF64(DN_F64 duration_s, DN_AgeUnit units) -{ - DN_U64 duration_ms = DN_Cast(DN_U64)(duration_s * 1000.0); - DN_Str8x128 result = DN_AgeStr8FromMsU64(duration_ms, units); - return result; -} - -DN_API int DN_IsLeapYear(int year) -{ - if (year % 4 != 0) - return 0; - if (year % 100 != 0) - return 1; - return (year % 400 == 0); -} - -DN_API bool DN_DateIsValid(DN_Date date) -{ - if (date.year < 1970) - return false; - if (date.month <= 0 || date.month >= 13) - return false; - if (date.day <= 0 || date.day >= 32) - return false; - if (date.hour >= 24) - return false; - if (date.minutes >= 60) - return false; - if (date.seconds >= 60) - return false; - return true; -} - -DN_API DN_Date DN_DateFromUnixTimeMs(DN_USize unix_ts_ms) -{ - DN_Date result = {}; - DN_USize ms = unix_ts_ms % 1000; - DN_USize total_seconds = unix_ts_ms / 1000; - result.milliseconds = (DN_U16)ms; - - DN_USize secs_in_day = total_seconds % 86400; - DN_USize days = total_seconds / 86400; - - result.hour = (DN_U8)(secs_in_day / 3600); - result.minutes = (DN_U8)((secs_in_day % 3600) / 60); - result.seconds = (DN_U8)(secs_in_day % 60); - - DN_U16 days_in_month[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - DN_USize days_left = days; - DN_U16 year = 1970; - - while (days_left >= (DN_IsLeapYear(year) ? 366 : 365)) { - DN_USize days_in_year = DN_IsLeapYear(year) ? 366 : 365; - days_left -= days_in_year; - year++; - } - - DN_U8 month = 1; - for (;;) { - DN_U16 day_count = days_in_month[month]; - if (month == 2 && DN_IsLeapYear(year)) - day_count = 29; - if (days_left < day_count) - break; - days_left -= day_count; - month++; - } - - result.year = year; - result.month = month; - result.day = (DN_U8)days_left + 1; - return result; -} - -DN_API DN_U64 DN_UnixTimeMsFromDate(DN_Date date) -{ - DN_Assert(DN_DateIsValid(date)); - - // Precomputed cumulative days before each month (non-leap year) - const DN_U16 days_before_month[13] = { - 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}; - - DN_U16 y = date.year; - DN_U8 m = date.month; - DN_U8 d = date.day; - - DN_U32 days = d - 1; // day of month starts at 0 internally - days += days_before_month[m - 1]; // Add days from previous months this year - - if (m > 2 && DN_IsLeapYear(y)) // Add February 29 if leap year and month > 2 - days += 1; - - // Add full years from 1970 to y-1 - for (DN_U16 year = 1970; year < y; ++year) - days += DN_IsLeapYear(year) ? 366 : 365; - - // Convert to seconds - DN_U64 seconds = DN_Cast(DN_U64)days * 86400ULL; - seconds += DN_Cast(DN_U64)date.hour * 3600ULL; - seconds += DN_Cast(DN_U64)date.minutes * 60ULL; - seconds += DN_Cast(DN_U64)date.seconds; - DN_U64 result = seconds * 1000ULL + date.milliseconds; - return result; -} - -DN_API DN_Str8 DN_Str8FromByteType(DN_ByteType type) -{ - DN_Str8 result = DN_Str8Lit(""); - switch (type) { - case DN_ByteType_B: result = DN_Str8Lit("B"); break; - case DN_ByteType_KiB: result = DN_Str8Lit("KiB"); break; - case DN_ByteType_MiB: result = DN_Str8Lit("MiB"); break; - case DN_ByteType_GiB: result = DN_Str8Lit("GiB"); break; - case DN_ByteType_TiB: result = DN_Str8Lit("TiB"); break; - case DN_ByteType_Count: result = DN_Str8Lit(""); break; - case DN_ByteType_Auto: result = DN_Str8Lit(""); break; - } - return result; -} - -DN_API DN_ByteCount DN_ByteCountFromU64(DN_U64 bytes, DN_ByteType type) -{ - DN_Assert(type != DN_ByteType_Count); - DN_ByteCount result = {}; - result.bytes = DN_Cast(DN_F64) bytes; - if (type == DN_ByteType_Auto) - for (; result.type < DN_ByteType_Count && result.bytes >= 1024.0; result.type = DN_Cast(DN_ByteType)(DN_Cast(DN_USize) result.type + 1)) - result.bytes /= 1024.0; - else - for (; result.type < type; result.type = DN_Cast(DN_ByteType)(DN_Cast(DN_USize) result.type + 1)) - result.bytes /= 1024.0; - result.suffix = DN_Str8FromByteType(result.type); - return result; -} - -DN_API DN_Str8x32 DN_Str8x32FromByteCountU64(DN_U64 bytes, DN_ByteType type) -{ - DN_ByteCount byte_count = DN_ByteCountFromU64(bytes, type); - DN_Str8x32 result = DN_Str8x32FromFmt("%.2f%.*s", byte_count.bytes, DN_Str8PrintFmt(byte_count.suffix)); - return result; -} - -DN_API DN_Profiler DN_ProfilerInit(DN_ProfilerAnchor *anchors, DN_USize count, DN_USize anchors_per_frame, DN_ProfilerTSCNowFunc *tsc_now, DN_U64 tsc_frequency) -{ - DN_Profiler result = {}; - result.anchors = anchors; - result.anchors_count = count; - result.anchors_per_frame = anchors_per_frame; - result.tsc_now = tsc_now; - result.tsc_frequency = tsc_frequency; - - DN_AssertF(result.tsc_frequency != 0, - "You must set this to the frequency of the timestamp counter function (TSC) (e.g. how " - "many ticks occur between timestamps). We use this to determine the duration between " - "each zone's recorded TSC. For example if the 'tsc_now' was set to Window's " - "QueryPerformanceCounter then 'tsc_frequency' would be set to the value of " - "QueryPerformanceFrequency which is typically 10mhz (e.g. The duration between two " - "consecutive TSC's is 10mhz)." - "" - "Hence frequency can't be zero otherwise it's a divide by 0. If you don't have a TSC " - "function and pass in null, the profiler defaults to rdtsc() and you must measure the " - "frequency of rdtsc yourself. The reason for this is that measuring rdtsc requires " - "having some alternate timing mechanism to measure the duration between the TSCs " - "provided by rdtsc and this profiler makes no assumption about what timing primitives " - "are available other than rdtsc which is a CPU builtin available on basically all " - "platforms or have an equivalent (e.g. __builtin_readcyclecounter)" - "" - "This codebase provides DN_OS_EstimateTSCPerSecond() as an example of how to that for " - "convenience and is available if compiling with the OS layer. Some platforms like " - "Emscripten don't support rdtsc() so you should use an alternative method like " - "emscripten_get_now() or clock_gettime with CLOCK_MONOTONIC."); - return result; -} - -DN_API DN_USize DN_ProfilerFrameCount(DN_Profiler const *profiler) -{ - DN_USize result = profiler ? profiler->anchors_count / profiler->anchors_per_frame : 0; - return result; -} - -DN_API DN_ProfilerAnchorArray DN_ProfilerFrameAnchorsFromIndex(DN_Profiler *profiler, DN_USize frame_index) -{ - DN_ProfilerAnchorArray result = {}; - DN_USize anchor_offset = frame_index * profiler->anchors_per_frame; - result.data = profiler->anchors + anchor_offset; - result.count = profiler->anchors_per_frame; - return result; -} - -DN_API DN_ProfilerAnchorArray DN_ProfilerFrameAnchors(DN_Profiler *profiler) -{ - DN_ProfilerAnchorArray result = DN_ProfilerFrameAnchorsFromIndex(profiler, profiler->frame_index); - return result; -} - -DN_API DN_ProfilerZone DN_ProfilerBeginZone(DN_Profiler *profiler, DN_Str8 name, DN_U16 anchor_index) -{ - DN_ProfilerZone result = {}; - if (!profiler || profiler->paused) - return result; - - if (anchor_index != 0) { - DN_AssertF(profiler->frame_zone.profiler, "DN_ProfilerNewFrame() must be called before calling BeginZone"); - } - DN_Assert(anchor_index < profiler->anchors_per_frame); - DN_ProfilerAnchor *anchor = DN_ProfilerFrameAnchors(profiler).data + anchor_index; - anchor->name = name; - - // TODO: We need per-thread-local-storage profiler so that we can use these apis - // across threads. For now, we let them overwrite each other but this is not tenable. - #if 0 - if (anchor->name.size && anchor->name != name) - DN_AssertF(name == anchor->name, "Potentially overwriting a zone by accident? Anchor is '%.*s', name is '%.*s'", DN_Str8PrintFmt(anchor->name), DN_Str8PrintFmt(name)); - #endif - - result.profiler = profiler; - result.begin_tsc = profiler->tsc_now ? profiler->tsc_now() : DN_CPUGetTSC(); - result.anchor_index = anchor_index; - result.parent_zone = profiler->parent_zone; - result.elapsed_tsc_at_zone_start = anchor->tsc_inclusive; - profiler->parent_zone = anchor_index; - return result; -} - -DN_API void DN_ProfilerEndZone(DN_ProfilerZone zone) -{ - DN_Profiler *profiler = zone.profiler; - if (!profiler || profiler->paused) - return; - - DN_Assert(zone.anchor_index < profiler->anchors_per_frame); - DN_Assert(zone.parent_zone < profiler->anchors_per_frame); - - DN_ProfilerAnchorArray array = DN_ProfilerFrameAnchors(profiler); - DN_ProfilerAnchor *anchor = array.data + zone.anchor_index; - DN_U64 tsc_now = profiler->tsc_now ? profiler->tsc_now() : DN_CPUGetTSC(); - DN_U64 elapsed_tsc = tsc_now - zone.begin_tsc; - - // NOTE: We snap the elapsed TSC at the zone start and overwrite every time we end zones. If we - // nest zones, the nested zones will clobber the inclusive timestamp with their values. - // This is fine, as long as all the zones and begun and ended correctly, when the top-most zone - // in the stack ends, it will overwrite the TSC with the elapsed time overall for just that top - // most function, unclobbering the elapsed time sitting in the anchor. - anchor->tsc_inclusive = zone.elapsed_tsc_at_zone_start + elapsed_tsc; - anchor->tsc_exclusive += elapsed_tsc; - anchor->hit_count++; - - if (zone.parent_zone != zone.anchor_index) { - DN_ProfilerAnchor *parent_anchor = array.data + zone.parent_zone; - parent_anchor->tsc_exclusive -= elapsed_tsc; - } - profiler->parent_zone = zone.parent_zone; -} - -DN_API void DN_ProfilerNewFrame(DN_Profiler *profiler) -{ - if (!profiler || profiler->paused) - return; - - // NOTE: End the frame's zone - DN_ProfilerEndZone(profiler->frame_zone); - DN_ProfilerAnchorArray old_frame_anchors = DN_ProfilerFrameAnchors(profiler); - DN_ProfilerAnchor old_frame_anchor = old_frame_anchors.data[0]; - profiler->frame_avg_tsc = (profiler->frame_avg_tsc + old_frame_anchor.tsc_inclusive) / 2.f; - - // NOTE: Bump to the next frame - DN_USize frame_count = profiler->anchors_count / profiler->anchors_per_frame; - profiler->frame_index = (profiler->frame_index + 1) % frame_count; - - // NOTE: Zero out the anchors - DN_ProfilerAnchorArray next_anchors = DN_ProfilerFrameAnchors(profiler); - DN_Memset(next_anchors.data, 0, sizeof(*profiler->anchors) * next_anchors.count); - - // NOTE: Start the frame's zone - profiler->frame_zone = DN_ProfilerBeginZone(profiler, DN_Str8Lit("Profiler Frame"), 0); -} - -DN_API DN_USize DN_ProfilerFmtAnchor(DN_ProfilerAnchor anchor, DN_U64 tsc_frequency, char *buffer, DN_USize count) -{ - DN_USize result = 0; - if (!anchor.hit_count) - return result; - - DN_U64 tsc_exclusive = anchor.tsc_exclusive; - DN_U64 tsc_inclusive = anchor.tsc_inclusive; - DN_F64 tsc_exclusive_milliseconds = tsc_exclusive * 1000 / DN_Cast(DN_F64) tsc_frequency; - if (tsc_exclusive == tsc_inclusive) { - DN_FmtAppend(buffer, &result, count, "%.*s[%u]: %.1fms", DN_Str8PrintFmt(anchor.name), anchor.hit_count, tsc_exclusive_milliseconds); - } else { - DN_F64 tsc_inclusive_milliseconds = tsc_inclusive * 1000 / DN_Cast(DN_F64) tsc_frequency; - DN_FmtAppend(buffer, &result, count, "%.*s[%u]: %.1f/%.1fms", DN_Str8PrintFmt(anchor.name), anchor.hit_count, tsc_exclusive_milliseconds, tsc_inclusive_milliseconds); - } - return result; -} - -DN_API DN_Str8 DN_ProfilerFmtAnchorStr8(DN_ProfilerAnchor anchor, DN_U64 tsc_frequency, DN_Arena *arena) -{ - DN_Str8 result = {}; - DN_USize size_req = DN_ProfilerFmtAnchor(anchor, tsc_frequency, nullptr, 0); - if (size_req) { - result = DN_Str8AllocArena(size_req, DN_ZMem_No, arena); - DN_ProfilerFmtAnchor(anchor, tsc_frequency, result.data, result.size + 1); - } - return result; -} - -DN_API void DN_ProfilerFmtToStdout(DN_Profiler *profiler) -{ - if (!profiler || profiler->frame_index == 0) - return; - - DN_USize frame_index = profiler->frame_index - 1; - DN_ProfilerAnchor *anchors = profiler->anchors + (frame_index * profiler->anchors_per_frame); - for (DN_USize index = 1; index < profiler->anchors_per_frame; index++) { - char buffer[2048]; - buffer[0] = 0; - DN_USize fmt_len = DN_ProfilerFmtAnchor(anchors[index], profiler->tsc_frequency, buffer, DN_ArrayCountU(buffer)); - DN_Str8 msg = DN_Str8FromPtr(buffer, fmt_len); - DN_OS_PrintOutLnF("%.*s", DN_Str8PrintFmt(msg)); - } -} - -DN_API DN_F64 DN_ProfilerSecFromTSC(DN_Profiler *profiler, DN_U64 duration_tsc) -{ - DN_F64 result = DN_Cast(DN_F64)duration_tsc / profiler->tsc_frequency; - return result; -} - -DN_API DN_F64 DN_ProfilerMsFromTSC(DN_Profiler *profiler, DN_U64 duration_tsc) -{ - DN_F64 result = DN_Cast(DN_F64)duration_tsc / profiler->tsc_frequency * 1000.0; - return result; -} - -static void DN_QSortSetElem_(void *array, DN_USize elem_size, DN_USize dest_index, DN_USize src_index) -{ - char *src = DN_Cast(char *) array + (src_index * elem_size); - char *dest = DN_Cast(char *) array + (dest_index * elem_size); - DN_Memcpy(dest, src, elem_size); -} - -static void DN_QSortSwapElems_(void *array, DN_USize elem_size, DN_USize lhs_index, DN_USize rhs_index) -{ - if (lhs_index == rhs_index) - return; - - char temp_buffer[512]; - bool use_buffer = elem_size <= DN_ArrayCountU(temp_buffer); - DN_TCScratch scratch = {}; - char *temp = {}; - if (use_buffer) { - temp = temp_buffer; - } else { - scratch = DN_TCScratchBeginArena(nullptr, 0); - temp = DN_ArenaNewArray(&scratch.arena, char, elem_size, DN_ZMem_No); - } - - char *lhs = DN_Cast(char *) array + (lhs_index * elem_size); - char *rhs = DN_Cast(char *) array + (rhs_index * elem_size); - DN_Memcpy(temp, lhs, elem_size); - DN_Memcpy(lhs, rhs, elem_size); - DN_Memcpy(rhs, temp, elem_size); - - if (!use_buffer) - DN_TCScratchEnd(&scratch); -} - -static void DN_QSortInsertion_(void *array, DN_USize array_size, DN_USize elem_size, void *user_context, DN_QSortCompareFunc *compare) -{ - char temp_buffer[512]; - bool use_buffer = elem_size <= DN_ArrayCountU(temp_buffer); - DN_TCScratch scratch = {}; - char *temp = {}; - if (use_buffer) { - temp = temp_buffer; - } else { - scratch = DN_TCScratchBeginArena(nullptr, 0); - temp = DN_ArenaNewArray(&scratch.arena, char, elem_size, DN_ZMem_No); - } - - DN_U8 *array_u8 = DN_Cast(DN_U8 *)array; - for (DN_USize item_to_insert_index = 1; item_to_insert_index < array_size; item_to_insert_index++) { - for (DN_USize index = 0; index < item_to_insert_index; index++) { - DN_U8 *lhs = array_u8 + (index * elem_size); - DN_U8 *rhs = array_u8 + (item_to_insert_index * elem_size); - if (compare(lhs, rhs, user_context)) - continue; - - DN_Memcpy(temp, rhs, elem_size); - for (DN_USize i = item_to_insert_index; i > index; i--) - DN_QSortSetElem_(array, elem_size, i, i - 1); - DN_Memcpy(lhs, temp, elem_size); - break; - } - } - - if (!use_buffer) - DN_TCScratchEnd(&scratch); -} - -DN_API void DN_QSort(void *array, DN_USize array_size, DN_USize elem_size, void *user_context, DN_QSortCompareFunc *compare) -{ - if (!array || array_size <= 1 || elem_size == 0 || !compare) - return; - - // NOTE: Insertion Sort, under 24->32 is an optimal amount - DN_U8 *array_u8 = DN_Cast(DN_U8 *)array; - DN_USize const QSORT_THRESHOLD = 24; - if (array_size < QSORT_THRESHOLD) { - DN_QSortInsertion_(array, array_size, elem_size, user_context, compare); - return; - } - - // NOTE: Quick sort, under 24->32 is an optimal amount - DN_USize last_index = array_size - 1; - DN_USize pivot_index = array_size / 2; - DN_USize partition_index = 0; - DN_USize start_index = 0; - - // Swap pivot with last index, so pivot is always at the end of the array. - // This makes logic much simpler. - DN_QSortSwapElems_(array, elem_size, last_index, pivot_index); - pivot_index = last_index; - - // 4^, 8, 7, 5, 2, 3, 6 - if (compare(array_u8 + (start_index * elem_size), array_u8 + (pivot_index * elem_size), user_context)) - partition_index++; - start_index++; - - // 4, |8, 7, 5^, 2, 3, 6* - // 4, 5, |7, 8, 2^, 3, 6* - // 4, 5, 2, |8, 7, ^3, 6* - // 4, 5, 2, 3, |7, 8, ^6* - for (DN_USize index = start_index; index < last_index; index++) { - if (compare(array_u8 + (index * elem_size), array_u8 + (pivot_index * elem_size), user_context)) { - DN_QSortSwapElems_(array, elem_size, partition_index, index); - partition_index++; - } - } - - // Move pivot to right of partition - // 4, 5, 2, 3, |6, 8, ^7* - DN_QSortSwapElems_(array, elem_size, partition_index, pivot_index); - DN_QSort(array_u8, partition_index, elem_size, user_context, compare); - - // Skip the value at partion index since that is guaranteed to be sorted. - // 4, 5, 2, 3, (x), 8, 7 - DN_USize one_after_partition_index = partition_index + 1; - DN_QSort(array_u8 + (one_after_partition_index * elem_size), (array_size - one_after_partition_index), elem_size, user_context, compare); -} - -DN_API bool DN_QSortCompareStr8NaturalAsc(void const* lhs, void const *rhs, void *user_context) -{ - DN_Str8EqCase eq_case = *DN_Cast(DN_Str8EqCase *) user_context; - DN_Str8 lhs_str8 = *DN_Cast(DN_Str8 *) lhs; - DN_Str8 rhs_str8 = *DN_Cast(DN_Str8 *) rhs; - bool result = DN_Str8CompareNatural(lhs_str8, rhs_str8, eq_case) < 0; - return result; -} - -DN_API bool DN_QSortCompareStr8NaturalDesc(void const* lhs, void const *rhs, void *user_context) -{ - DN_Str8EqCase eq_case = *DN_Cast(DN_Str8EqCase *) user_context; - DN_Str8 lhs_str8 = *DN_Cast(DN_Str8 *) lhs; - DN_Str8 rhs_str8 = *DN_Cast(DN_Str8 *) rhs; - bool result = DN_Str8CompareNatural(lhs_str8, rhs_str8, eq_case) > 0; - return result; -} - -DN_API bool DN_QSortCompareStr8LexicographicAsc(void const* lhs, void const *rhs, void *user_context) -{ - DN_Str8EqCase eq_case = *DN_Cast(DN_Str8EqCase *) user_context; - DN_Str8 lhs_str8 = *DN_Cast(DN_Str8 *) lhs; - DN_Str8 rhs_str8 = *DN_Cast(DN_Str8 *) rhs; - bool result = DN_Str8CompareLexicographic(lhs_str8, rhs_str8, eq_case) < 0; - return result; -} - -DN_API bool DN_QSortCompareStr8LexicographicDesc(void const* lhs, void const *rhs, void *user_context) -{ - DN_Str8EqCase eq_case = *DN_Cast(DN_Str8EqCase *) user_context; - DN_Str8 lhs_str8 = *DN_Cast(DN_Str8 *) lhs; - DN_Str8 rhs_str8 = *DN_Cast(DN_Str8 *) rhs; - bool result = DN_Str8CompareLexicographic(lhs_str8, rhs_str8, eq_case) > 0; - return result; -} - -DN_API bool DN_QSortCompareBytesLT(void const* lhs, void const *rhs, void *user_context) -{ - DN_USize elem_size = *DN_Cast(DN_USize *)user_context; - bool result = DN_Memcmp(lhs, rhs, elem_size) < 0; - return result; -} - -DN_API bool DN_QSortCompareBytesGT(void const* lhs, void const *rhs, void *user_context) -{ - DN_USize elem_size = *DN_Cast(DN_USize *)user_context; - bool result = DN_Memcmp(lhs, rhs, elem_size) > 0; - return result; -} - -DN_API void DN_QSortBytesLT(void *array, DN_USize array_size, DN_USize elem_size) -{ - DN_QSort(array, array_size, elem_size, &elem_size, DN_QSortCompareBytesLT); -} - -DN_API void DN_QSortBytesGT(void *array, DN_USize array_size, DN_USize elem_size) -{ - DN_QSort(array, array_size, elem_size, &elem_size, DN_QSortCompareBytesGT); -} - -DN_API void DN_QSortStr8NaturalAsc(DN_Str8 *array, DN_USize array_size, DN_Str8EqCase eq_case) -{ - DN_QSort(array, array_size, sizeof(*array), /*user_context=*/ &eq_case, DN_QSortCompareStr8NaturalAsc); -} - -DN_API void DN_QSortStr8NaturalDesc(DN_Str8 *array, DN_USize array_size, DN_Str8EqCase eq_case) -{ - DN_QSort(array, array_size, sizeof(*array), /*user_context=*/ &eq_case, DN_QSortCompareStr8NaturalDesc); -} - -DN_API void DN_QSortStr8LexicographicAsc(DN_Str8 *array, DN_USize array_size, DN_Str8EqCase eq_case) -{ - DN_QSort(array, array_size, sizeof(*array), /*user_context=*/ &eq_case, DN_QSortCompareStr8LexicographicAsc); -} - -DN_API void DN_QSortStr8LexicographicDesc(DN_Str8 *array, DN_USize array_size, DN_Str8EqCase eq_case) -{ - DN_QSort(array, array_size, sizeof(*array), /*user_context=*/ &eq_case, DN_QSortCompareStr8LexicographicDesc); -} - - -#define DN_PCG_DEFAULT_MULTIPLIER_64 6364136223846793005ULL -#define DN_PCG_DEFAULT_INCREMENT_64 1442695040888963407ULL -DN_API DN_PCG32 DN_PCG32Init(DN_U64 seed) -{ - DN_PCG32 result = {}; - DN_PCG32Next(&result); - result.state += seed; - DN_PCG32Next(&result); - return result; -} - -DN_API DN_U32 DN_PCG32Next(DN_PCG32 *rng) -{ - DN_U64 state = rng->state; - rng->state = state * DN_PCG_DEFAULT_MULTIPLIER_64 + DN_PCG_DEFAULT_INCREMENT_64; - - // XSH-RR - DN_U32 value = (DN_U32)((state ^ (state >> 18)) >> 27); - int rot = state >> 59; - return rot ? (value >> rot) | (value << (32 - rot)) : value; -} - -DN_API DN_U64 DN_PCG32Next64(DN_PCG32 *rng) -{ - DN_U64 value = DN_PCG32Next(rng); - value <<= 32; - value |= DN_PCG32Next(rng); - return value; -} - -DN_API DN_U32 DN_PCG32Range(DN_PCG32 *rng, DN_U32 low, DN_U32 high) -{ - DN_U32 bound = high - low; - DN_U32 threshold = -(DN_I32)bound % bound; - - for (;;) { - DN_U32 r = DN_PCG32Next(rng); - if (r >= threshold) - return low + (r % bound); - } -} - -DN_API DN_F32 DN_PCG32NextF32(DN_PCG32 *rng) -{ - DN_U32 x = DN_PCG32Next(rng); - return (DN_F32)(DN_I32)(x >> 8) * 0x1.0p-24f; -} - -DN_API DN_F64 DN_PCG32NextF64(DN_PCG32 *rng) -{ - DN_U64 x = DN_PCG32Next64(rng); - return (DN_F64)(DN_I64)(x >> 11) * 0x1.0p-53; -} - -DN_API void DN_PCG32Advance(DN_PCG32 *rng, DN_U64 delta) -{ - DN_U64 cur_mult = DN_PCG_DEFAULT_MULTIPLIER_64; - DN_U64 cur_plus = DN_PCG_DEFAULT_INCREMENT_64; - - DN_U64 acc_mult = 1; - DN_U64 acc_plus = 0; - - while (delta != 0) { - if (delta & 1) { - acc_mult *= cur_mult; - acc_plus = acc_plus * cur_mult + cur_plus; - } - cur_plus = (cur_mult + 1) * cur_plus; - cur_mult *= cur_mult; - delta >>= 1; - } - - rng->state = acc_mult * rng->state + acc_plus; -} - -// Default values recommended by: http://isthe.com/chongo/tech/comp/fnv/ -DN_API DN_U32 DN_FNV1AHashU32FromBytes(void const *bytes, DN_USize size, DN_U32 hash) -{ - auto buffer = DN_Cast(DN_U8 const *)bytes; - for (DN_USize i = 0; i < size; i++) - hash = (buffer[i] ^ hash) * 16777619 /*FNV Prime*/; - return hash; -} - -DN_API DN_U64 DN_FNV1AHashU64FromBytes(void const *bytes, DN_USize size, DN_U64 hash) -{ - auto buffer = DN_Cast(DN_U8 const *)bytes; - for (DN_USize i = 0; i < size; i++) - hash = (buffer[i] ^ hash) * 1099511628211 /*FNV Prime*/; - return hash; -} - -#if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL) - #define DN_MMH3_ROTL32(x, y) _rotl(x, y) - #define DN_MMH3_ROTL64(x, y) _rotl64(x, y) -#else - #define DN_MMH3_ROTL32(x, y) ((x) << (y)) | ((x) >> (32 - (y))) - #define DN_MMH3_ROTL64(x, y) ((x) << (y)) | ((x) >> (64 - (y))) -#endif - -//----------------------------------------------------------------------------- -// Block read - if your platform needs to do endian-swapping or can only -// handle aligned reads, do the conversion here -DN_FORCE_INLINE DN_U32 DN_MurmurHash3GetBlock32_(DN_U32 const *p, int i) -{ - return p[i]; -} - -DN_FORCE_INLINE DN_U64 DN_MurmurHash3GetBlock64_(DN_U64 const *p, int i) -{ - return p[i]; -} - -//----------------------------------------------------------------------------- -// Finalization mix - force all bits of a hash block to avalanche - -DN_FORCE_INLINE DN_U32 DN_MurmurHash3FMix32_(DN_U32 h) -{ - h ^= h >> 16; - h *= 0x85ebca6b; - h ^= h >> 13; - h *= 0xc2b2ae35; - h ^= h >> 16; - return h; -} - -DN_FORCE_INLINE DN_U64 DN_MurmurHash3FMix64_(DN_U64 k) -{ - k ^= k >> 33; - k *= 0xff51afd7ed558ccd; - k ^= k >> 33; - k *= 0xc4ceb9fe1a85ec53; - k ^= k >> 33; - return k; -} - -DN_API DN_U32 DN_MurmurHash3HashU128FromBytesX86(void const *bytes, int len, DN_U32 seed) -{ - const DN_U8 *data = (const DN_U8 *)bytes; - const int nblocks = len / 4; - - DN_U32 h1 = seed; - - const DN_U32 c1 = 0xcc9e2d51; - const DN_U32 c2 = 0x1b873593; - - //---------- - // body - - const DN_U32 *blocks = (const DN_U32 *)(data + nblocks * 4); - - for (int i = -nblocks; i; i++) - { - DN_U32 k1 = DN_MurmurHash3GetBlock32_(blocks, i); - - k1 *= c1; - k1 = DN_MMH3_ROTL32(k1, 15); - k1 *= c2; - - h1 ^= k1; - h1 = DN_MMH3_ROTL32(h1, 13); - h1 = h1 * 5 + 0xe6546b64; - } - - //---------- - // tail - - const DN_U8 *tail = (const DN_U8 *)(data + nblocks * 4); - - DN_U32 k1 = 0; - - switch (len & 3) - { - case 3: - k1 ^= tail[2] << 16; - case 2: - k1 ^= tail[1] << 8; - case 1: - k1 ^= tail[0]; - k1 *= c1; - k1 = DN_MMH3_ROTL32(k1, 15); - k1 *= c2; - h1 ^= k1; - }; - - //---------- - // finalization - - h1 ^= len; - - h1 = DN_MurmurHash3FMix32_(h1); - - return h1; -} - -DN_API DN_MurmurHash3 DN_MurmurHash3HashU128FromBytesX64(void const *bytes, int len, DN_U32 seed) -{ - const DN_U8 *data = (const DN_U8 *)bytes; - const int nblocks = len / 16; - - DN_U64 h1 = seed; - DN_U64 h2 = seed; - - const DN_U64 c1 = 0x87c37b91114253d5; - const DN_U64 c2 = 0x4cf5ad432745937f; - - //---------- - // body - - const DN_U64 *blocks = (const DN_U64 *)(data); - - for (int i = 0; i < nblocks; i++) - { - DN_U64 k1 = DN_MurmurHash3GetBlock64_(blocks, i * 2 + 0); - DN_U64 k2 = DN_MurmurHash3GetBlock64_(blocks, i * 2 + 1); - - k1 *= c1; - k1 = DN_MMH3_ROTL64(k1, 31); - k1 *= c2; - h1 ^= k1; - - h1 = DN_MMH3_ROTL64(h1, 27); - h1 += h2; - h1 = h1 * 5 + 0x52dce729; - - k2 *= c2; - k2 = DN_MMH3_ROTL64(k2, 33); - k2 *= c1; - h2 ^= k2; - - h2 = DN_MMH3_ROTL64(h2, 31); - h2 += h1; - h2 = h2 * 5 + 0x38495ab5; - } - - //---------- - // tail - - const DN_U8 *tail = (const DN_U8 *)(data + nblocks * 16); - - DN_U64 k1 = 0; - DN_U64 k2 = 0; - - switch (len & 15) - { - case 15: - k2 ^= ((DN_U64)tail[14]) << 48; - case 14: - k2 ^= ((DN_U64)tail[13]) << 40; - case 13: - k2 ^= ((DN_U64)tail[12]) << 32; - case 12: - k2 ^= ((DN_U64)tail[11]) << 24; - case 11: - k2 ^= ((DN_U64)tail[10]) << 16; - case 10: - k2 ^= ((DN_U64)tail[9]) << 8; - case 9: - k2 ^= ((DN_U64)tail[8]) << 0; - k2 *= c2; - k2 = DN_MMH3_ROTL64(k2, 33); - k2 *= c1; - h2 ^= k2; - - case 8: - k1 ^= ((DN_U64)tail[7]) << 56; - case 7: - k1 ^= ((DN_U64)tail[6]) << 48; - case 6: - k1 ^= ((DN_U64)tail[5]) << 40; - case 5: - k1 ^= ((DN_U64)tail[4]) << 32; - case 4: - k1 ^= ((DN_U64)tail[3]) << 24; - case 3: - k1 ^= ((DN_U64)tail[2]) << 16; - case 2: - k1 ^= ((DN_U64)tail[1]) << 8; - case 1: - k1 ^= ((DN_U64)tail[0]) << 0; - k1 *= c1; - k1 = DN_MMH3_ROTL64(k1, 31); - k1 *= c2; - h1 ^= k1; - }; - - //---------- - // finalization - - h1 ^= len; - h2 ^= len; - - h1 += h2; - h2 += h1; - - h1 = DN_MurmurHash3FMix64_(h1); - h2 = DN_MurmurHash3FMix64_(h2); - - h1 += h2; - h2 += h1; - - DN_MurmurHash3 result = {}; - result.e[0] = h1; - result.e[1] = h2; - return result; -} - -DN_API DN_U64 DN_MurmurHash3HashU64FromBytesX64(void const *bytes, int len, DN_U32 seed) -{ - DN_MurmurHash3 hash = DN_MurmurHash3HashU128FromBytesX64(bytes, len, seed); - DN_U64 result = hash.e[0]; - return result; -} - -DN_API DN_U32 DN_MurmurHash3HashU32FromBytesX64(void const *bytes, int len, DN_U32 seed) -{ - DN_MurmurHash3 hash = DN_MurmurHash3HashU128FromBytesX64(bytes, len, seed); - DN_U32 result = DN_Cast(DN_U32)hash.e[0]; - return result; -} - -DN_API DN_Str8x32 DN_Str8x32FromANSIColourCodeU8RGB(DN_ANSIColourMode mode, DN_U8 r, DN_U8 g, DN_U8 b) -{ - DN_Str8x32 result = DN_Str8x32FromFmt("\x1b[%d;2;%u;%u;%um", - mode == DN_ANSIColourMode_Fg ? 38 : 48, - r, - g, - b); - return result; -} - -DN_API DN_Str8x32 DN_Str8x32FromANSIColourCodeV3F32RGB255(DN_ANSIColourMode mode, DN_V3F32 rgb_255) -{ - DN_Str8x32 result = DN_Str8x32FromANSIColourCodeU8RGB(mode, DN_Cast(DN_U8)rgb_255.r, DN_Cast(DN_U8)rgb_255.g, DN_Cast(DN_U8)rgb_255.b); - return result; -} - -DN_API DN_Str8x32 DN_Str8x32FromANSIColourCodeU32RGB(DN_ANSIColourMode mode, DN_U32 value) -{ - DN_U8 r = DN_Cast(DN_U8)(value >> 24); - DN_U8 g = DN_Cast(DN_U8)(value >> 16); - DN_U8 b = DN_Cast(DN_U8)(value >> 8); - DN_Str8x32 result = DN_Str8x32FromANSIColourCodeU8RGB(mode, r, g, b); - return result; -} - -DN_API DN_Str8 DN_Str8FromStr8ANSIColourU8RGBArena(DN_ANSIColourMode mode, DN_Str8 str8, DN_U8 r, DN_U8 g, DN_U8 b, DN_Arena *arena) -{ - DN_Str8x32 ansi = DN_Str8x32FromANSIColourCodeU8RGB(mode, r, g, b); - DN_Str8 result = DN_Str8FromFmtArena(arena, "%.*s%.*s%s", DN_Str8PrintFmt(ansi), DN_Str8PrintFmt(str8), DN_ANSICodeResetLit); - return result; -} - -DN_API DN_Str8 DN_Str8FromStr8ANSIColourV3F32RGB255Arena(DN_ANSIColourMode mode, DN_Str8 str8, DN_V3F32 rgb_255, DN_Arena *arena) -{ - DN_Str8 result = DN_Str8FromStr8ANSIColourU8RGBArena(mode, str8, DN_Cast(DN_U8)rgb_255.r, DN_Cast(DN_U8)rgb_255.g, DN_Cast(DN_U8)rgb_255.b, arena); - return result; -} - -DN_API DN_Str8 DN_Str8ANSIColourU8RGBFromFmtVArena(DN_ANSIColourMode mode, DN_U8 r, DN_U8 g, DN_U8 b, DN_Arena *arena, char const *fmt, va_list args) -{ - DN_TCScratch scratch = DN_TCScratchBeginArena(&arena, 1); - DN_Str8 string = DN_Str8FromFmtVArena(&scratch.arena, fmt, args); - DN_Str8 result = DN_Str8FromStr8ANSIColourU8RGBArena(mode, string, r, g, b, arena); - DN_TCScratchEnd(&scratch); - return result; -} - -DN_API DN_Str8 DN_Str8FromFmtANSIColourU8RGBArena(DN_ANSIColourMode mode, DN_U8 r, DN_U8 g, DN_U8 b, DN_Arena *arena, char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_Str8 result = DN_Str8ANSIColourU8RGBFromFmtVArena(mode, r, g, b, arena, fmt, args); - va_end(args); - return result; -} - -DN_API DN_Str8 DN_Str8FromFmtANSIColourV3F32RGB255Arena(DN_ANSIColourMode mode, DN_V3F32 rgb_255, DN_Arena *arena, char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_Str8 result = DN_Str8ANSIColourU8RGBFromFmtVArena(mode, DN_Cast(DN_U8)rgb_255.r, DN_Cast(DN_U8)rgb_255.g, DN_Cast(DN_U8)rgb_255.b, arena, fmt, args); - va_end(args); - return result; -} - -DN_API DN_LogPrefixSize DN_LogMakePrefix(DN_LogStyle style, DN_LogTypeParam type, DN_CallSite call_site, DN_LogDate date, char *dest, DN_USize dest_size) -{ - DN_Str8 type_str8 = type.str8; - if (type.is_u32_enum) { - switch (type.u32) { - case DN_LogType_Debug: type_str8 = DN_Str8Lit("DEBUG"); break; - case DN_LogType_Info: type_str8 = DN_Str8Lit("INFO "); break; - case DN_LogType_Warning: type_str8 = DN_Str8Lit("WARN"); break; - case DN_LogType_Error: type_str8 = DN_Str8Lit("ERROR"); break; - case DN_LogType_Count: type_str8 = DN_Str8Lit("BADXX"); break; - } - } - - static DN_USize max_type_length = 0; - max_type_length = DN_Max(max_type_length, type_str8.size); - int type_padding = DN_Cast(int)(max_type_length - type_str8.size); - - DN_Str8x32 colour_esc = {}; - DN_Str8 bold_esc = {}; - DN_Str8 reset_esc = {}; - if (style.colour) { - bold_esc = DN_Str8Lit(DN_ANSICodeBoldLit); - reset_esc = DN_Str8Lit(DN_ANSICodeResetLit); - colour_esc = DN_Str8x32FromANSIColourCodeU8RGB(DN_ANSIColourMode_Fg, style.r, style.g, style.b); - } - - DN_Str8 file_name = DN_Str8FileNameFromPath(call_site.file); - int size = DN_SNPrintF(dest, - DN_Cast(int)dest_size, - "%04u-%02u-%02uT%02u:%02u:%02u" // date - "%.*s" // colour - "%.*s" // bold - " %.*s" // type - "%.*s" // type padding - "%.*s" // reset - " %.*s" // file name - ":%05u " // line number - , - date.year, - date.month, - date.day, - date.hour, - date.minute, - date.second, - DN_Str8PrintFmt(colour_esc), // colour - DN_Str8PrintFmt(bold_esc), // bold - DN_Str8PrintFmt(type_str8), // type - DN_Cast(int) type_padding, - "", // type padding - DN_Str8PrintFmt(reset_esc), // reset - DN_Str8PrintFmt(file_name), // file name - call_site.line); // line number - - static DN_USize max_header_length = 0; - DN_USize size_no_ansi_codes = size - colour_esc.size - reset_esc.size - bold_esc.size; - max_header_length = DN_Max(max_header_length, size_no_ansi_codes); - DN_USize header_padding = max_header_length - size_no_ansi_codes; - - DN_LogPrefixSize result = {}; - result.size = size; - result.padding = header_padding; - return result; -} - -DN_API void DN_LogSetPrintFunc(DN_LogPrintFunc *print_func, void *user_data) -{ - DN_Core *dn = DN_Get(); - dn->print_func = print_func; - dn->print_func_context = user_data; -} - -DN_API void DN_LogPrintFV(DN_LogTypeParam type, DN_CallSite call_site, DN_LogFlags flags, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - DN_Core *dn = DN_Get(); - if (type.is_u32_enum) { - DN_Assert(dn->log_level_to_show_from >= 0); - if (type.u32 < DN_Cast(DN_U32) dn->log_level_to_show_from) - return; - } - DN_LogPrintFunc *func = dn->print_func; - if (func) - func(type, dn->print_func_context, call_site, flags, fmt, args); -} - -DN_API void DN_LogPrintF(DN_LogTypeParam type, DN_CallSite call_site, DN_LogFlags flags, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_LogPrintFV(type, call_site, flags, fmt, args); - va_end(args); -} - -DN_API DN_LogTypeParam DN_LogTypeParamFromType(DN_LogType type) -{ - DN_LogTypeParam result = {}; - result.is_u32_enum = true; - result.u32 = type; - return result; -} - -DN_API DN_F32 DN_F32Lerp(DN_F32 a, DN_F32 t, DN_F32 b) -{ - DN_F32 result = a + ((b - a) * t); - return result; -} - -DN_API DN_F32 DN_F32Floor(DN_F32 val) -{ - DN_I32 val_i32 = DN_Cast(DN_I32) val; - if (val < 0 && val != DN_Cast(DN_F32) val_i32) - val_i32 -= 1; - DN_F32 result = DN_Cast(DN_F32)val_i32; - return result; -} - -DN_API DN_F32 DN_F32Ceil(DN_F32 val) -{ - DN_I32 val_i32 = DN_Cast(DN_I32)(val); - if (val > 0 && val != DN_Cast(DN_F32) val_i32) - val_i32 += 1; - DN_F32 result = DN_Cast(DN_F32) val_i32; - return result; -} - -DN_API DN_F32 DN_F32RoundHalfUp(DN_F32 val) -{ - DN_F32 result = val >= 0 ? DN_F32Floor(val + 0.5f) : DN_F32Ceil(val - 0.5f); - return result; -} - -DN_API bool operator==(DN_V2I32 lhs, DN_V2I32 rhs) -{ - bool result = (lhs.x == rhs.x) && (lhs.y == rhs.y); - return result; -} - -DN_API bool operator!=(DN_V2I32 lhs, DN_V2I32 rhs) -{ - bool result = !(lhs == rhs); - return result; -} - -DN_API bool operator>=(DN_V2I32 lhs, DN_V2I32 rhs) -{ - bool result = (lhs.x >= rhs.x) && (lhs.y >= rhs.y); - return result; -} - -DN_API bool operator<=(DN_V2I32 lhs, DN_V2I32 rhs) -{ - bool result = (lhs.x <= rhs.x) && (lhs.y <= rhs.y); - return result; -} - -DN_API bool operator<(DN_V2I32 lhs, DN_V2I32 rhs) -{ - bool result = (lhs.x < rhs.x) && (lhs.y < rhs.y); - return result; -} - -DN_API bool operator>(DN_V2I32 lhs, DN_V2I32 rhs) -{ - bool result = (lhs.x > rhs.x) && (lhs.y > rhs.y); - return result; -} - -DN_API DN_V2I32 operator-(DN_V2I32 lhs, DN_V2I32 rhs) -{ - DN_V2I32 result = DN_V2I32From2N(lhs.x - rhs.x, lhs.y - rhs.y); - return result; -} - -DN_API DN_V2I32 operator-(DN_V2I32 lhs) -{ - DN_V2I32 result = DN_V2I32From2N(-lhs.x, -lhs.y); - return result; -} - -DN_API DN_V2I32 operator+(DN_V2I32 lhs, DN_V2I32 rhs) -{ - DN_V2I32 result = DN_V2I32From2N(lhs.x + rhs.x, lhs.y + rhs.y); - return result; -} - -DN_API DN_V2I32 operator*(DN_V2I32 lhs, DN_V2I32 rhs) -{ - DN_V2I32 result = DN_V2I32From2N(lhs.x * rhs.x, lhs.y * rhs.y); - return result; -} - -DN_API DN_V2I32 operator*(DN_V2I32 lhs, DN_F32 rhs) -{ - DN_V2I32 result = DN_V2I32From2N(lhs.x * rhs, lhs.y * rhs); - return result; -} - -DN_API DN_V2I32 operator*(DN_V2I32 lhs, DN_I32 rhs) -{ - DN_V2I32 result = DN_V2I32From2N(lhs.x * rhs, lhs.y * rhs); - return result; -} - -DN_API DN_V2I32 operator/(DN_V2I32 lhs, DN_V2I32 rhs) -{ - DN_V2I32 result = DN_V2I32From2N(lhs.x / rhs.x, lhs.y / rhs.y); - return result; -} - -DN_API DN_V2I32 operator/(DN_V2I32 lhs, DN_F32 rhs) -{ - DN_V2I32 result = DN_V2I32From2N(lhs.x / rhs, lhs.y / rhs); - return result; -} - -DN_API DN_V2I32 operator/(DN_V2I32 lhs, DN_I32 rhs) -{ - DN_V2I32 result = DN_V2I32From2N(lhs.x / rhs, lhs.y / rhs); - return result; -} - -DN_API DN_V2I32 &operator*=(DN_V2I32 &lhs, DN_V2I32 rhs) -{ - lhs = lhs * rhs; - return lhs; -} - -DN_API DN_V2I32 &operator*=(DN_V2I32 &lhs, DN_F32 rhs) -{ - lhs = lhs * rhs; - return lhs; -} - -DN_API DN_V2I32 &operator*=(DN_V2I32 &lhs, DN_I32 rhs) -{ - lhs = lhs * rhs; - return lhs; -} - -DN_API DN_V2I32 &operator/=(DN_V2I32 &lhs, DN_V2I32 rhs) -{ - lhs = lhs / rhs; - return lhs; -} - -DN_API DN_V2I32 &operator/=(DN_V2I32 &lhs, DN_F32 rhs) -{ - lhs = lhs / rhs; - return lhs; -} - -DN_API DN_V2I32 &operator/=(DN_V2I32 &lhs, DN_I32 rhs) -{ - lhs = lhs / rhs; - return lhs; -} - -DN_API DN_V2I32 &operator-=(DN_V2I32 &lhs, DN_V2I32 rhs) -{ - lhs = lhs - rhs; - return lhs; -} - -DN_API DN_V2I32 &operator+=(DN_V2I32 &lhs, DN_V2I32 rhs) -{ - lhs = lhs + rhs; - return lhs; -} - -DN_API DN_V2I32 DN_V2I32Min(DN_V2I32 a, DN_V2I32 b) -{ - DN_V2I32 result = DN_V2I32From2N(DN_Min(a.x, b.x), DN_Min(a.y, b.y)); - return result; -} - -DN_API DN_V2I32 DN_V2I32Max(DN_V2I32 a, DN_V2I32 b) -{ - DN_V2I32 result = DN_V2I32From2N(DN_Max(a.x, b.x), DN_Max(a.y, b.y)); - return result; -} - -DN_API DN_V2I32 DN_V2I32Abs(DN_V2I32 a) -{ - DN_V2I32 result = DN_V2I32From2N(DN_Abs(a.x), DN_Abs(a.y)); - return result; -} - -DN_API bool operator!=(DN_V2U16 lhs, DN_V2U16 rhs) -{ - bool result = !(lhs == rhs); - return result; -} - -DN_API bool operator==(DN_V2U16 lhs, DN_V2U16 rhs) -{ - bool result = (lhs.x == rhs.x) && (lhs.y == rhs.y); - return result; -} - -DN_API bool operator>=(DN_V2U16 lhs, DN_V2U16 rhs) -{ - bool result = (lhs.x >= rhs.x) && (lhs.y >= rhs.y); - return result; -} - -DN_API bool operator<=(DN_V2U16 lhs, DN_V2U16 rhs) -{ - bool result = (lhs.x <= rhs.x) && (lhs.y <= rhs.y); - return result; -} - -DN_API bool operator<(DN_V2U16 lhs, DN_V2U16 rhs) -{ - bool result = (lhs.x < rhs.x) && (lhs.y < rhs.y); - return result; -} - -DN_API bool operator>(DN_V2U16 lhs, DN_V2U16 rhs) -{ - bool result = (lhs.x > rhs.x) && (lhs.y > rhs.y); - return result; -} - -DN_API DN_V2U16 operator-(DN_V2U16 lhs, DN_V2U16 rhs) -{ - DN_V2U16 result = DN_V2U16From2N(lhs.x - rhs.x, lhs.y - rhs.y); - return result; -} - -DN_API DN_V2U16 operator+(DN_V2U16 lhs, DN_V2U16 rhs) -{ - DN_V2U16 result = DN_V2U16From2N(lhs.x + rhs.x, lhs.y + rhs.y); - return result; -} - -DN_API DN_V2U16 operator*(DN_V2U16 lhs, DN_V2U16 rhs) -{ - DN_V2U16 result = DN_V2U16From2N(lhs.x * rhs.x, lhs.y * rhs.y); - return result; -} - -DN_API DN_V2U16 operator*(DN_V2U16 lhs, DN_F32 rhs) -{ - DN_V2U16 result = DN_V2U16From2N(lhs.x * rhs, lhs.y * rhs); - return result; -} - -DN_API DN_V2U16 operator*(DN_V2U16 lhs, DN_I32 rhs) -{ - DN_V2U16 result = DN_V2U16From2N(lhs.x * rhs, lhs.y * rhs); - return result; -} - -DN_API DN_V2U16 operator/(DN_V2U16 lhs, DN_V2U16 rhs) -{ - DN_V2U16 result = DN_V2U16From2N(lhs.x / rhs.x, lhs.y / rhs.y); - return result; -} - -DN_API DN_V2U16 operator/(DN_V2U16 lhs, DN_F32 rhs) -{ - DN_V2U16 result = DN_V2U16From2N(lhs.x / rhs, lhs.y / rhs); - return result; -} - -DN_API DN_V2U16 operator/(DN_V2U16 lhs, DN_I32 rhs) -{ - DN_V2U16 result = DN_V2U16From2N(lhs.x / rhs, lhs.y / rhs); - return result; -} - -DN_API DN_V2U16 &operator*=(DN_V2U16 &lhs, DN_V2U16 rhs) -{ - lhs = lhs * rhs; - return lhs; -} - -DN_API DN_V2U16 &operator*=(DN_V2U16 &lhs, DN_F32 rhs) -{ - lhs = lhs * rhs; - return lhs; -} - -DN_API DN_V2U16 &operator*=(DN_V2U16 &lhs, DN_I32 rhs) -{ - lhs = lhs * rhs; - return lhs; -} - -DN_API DN_V2U16 &operator/=(DN_V2U16 &lhs, DN_V2U16 rhs) -{ - lhs = lhs / rhs; - return lhs; -} - -DN_API DN_V2U16 &operator/=(DN_V2U16 &lhs, DN_F32 rhs) -{ - lhs = lhs / rhs; - return lhs; -} - -DN_API DN_V2U16 &operator/=(DN_V2U16 &lhs, DN_I32 rhs) -{ - lhs = lhs / rhs; - return lhs; -} - -DN_API DN_V2U16 &operator-=(DN_V2U16 &lhs, DN_V2U16 rhs) -{ - lhs = lhs - rhs; - return lhs; -} - -DN_API DN_V2U16 &operator+=(DN_V2U16 &lhs, DN_V2U16 rhs) -{ - lhs = lhs + rhs; - return lhs; -} - -DN_API DN_V2F32 DN_V2F32Lerp(DN_V2F32 a, DN_F32 t, DN_V2F32 b) -{ - DN_V2F32 result = {}; - result.x = a.x + ((b.x - a.x) * t); - result.y = a.y + ((b.y - a.y) * t); - return result; -} - -DN_API bool operator!=(DN_V2F32 lhs, DN_V2F32 rhs) -{ - bool result = !(lhs == rhs); - return result; -} - -DN_API bool operator==(DN_V2F32 lhs, DN_V2F32 rhs) -{ - bool result = (lhs.x == rhs.x) && (lhs.y == rhs.y); - return result; -} - -DN_API bool operator>=(DN_V2F32 lhs, DN_V2F32 rhs) -{ - bool result = (lhs.x >= rhs.x) && (lhs.y >= rhs.y); - return result; -} - -DN_API bool operator<=(DN_V2F32 lhs, DN_V2F32 rhs) -{ - bool result = (lhs.x <= rhs.x) && (lhs.y <= rhs.y); - return result; -} - -DN_API bool operator<(DN_V2F32 lhs, DN_V2F32 rhs) -{ - bool result = (lhs.x < rhs.x) && (lhs.y < rhs.y); - return result; -} - -DN_API bool operator>(DN_V2F32 lhs, DN_V2F32 rhs) -{ - bool result = (lhs.x > rhs.x) && (lhs.y > rhs.y); - return result; -} - -DN_API DN_V2F32 operator-(DN_V2F32 lhs) -{ - DN_V2F32 result = DN_V2F32From2N(-lhs.x, -lhs.y); - return result; -} - -DN_API DN_V2F32 operator-(DN_V2F32 lhs, DN_V2F32 rhs) -{ - DN_V2F32 result = DN_V2F32From2N(lhs.x - rhs.x, lhs.y - rhs.y); - return result; -} - -DN_API DN_V2F32 operator-(DN_V2F32 lhs, DN_V2I32 rhs) -{ - DN_V2F32 result = DN_V2F32From2N(lhs.x - rhs.x, lhs.y - rhs.y); - return result; -} - -DN_API DN_V2F32 operator-(DN_V2F32 lhs, DN_F32 rhs) -{ - DN_V2F32 result = DN_V2F32From2N(lhs.x - rhs, lhs.y - rhs); - return result; -} - -DN_API DN_V2F32 operator-(DN_V2F32 lhs, DN_I32 rhs) -{ - DN_V2F32 result = DN_V2F32From2N(lhs.x - rhs, lhs.y - rhs); - return result; -} - -DN_API DN_V2F32 operator+(DN_V2F32 lhs, DN_V2F32 rhs) -{ - DN_V2F32 result = DN_V2F32From2N(lhs.x + rhs.x, lhs.y + rhs.y); - return result; -} - -DN_API DN_V2F32 operator+(DN_V2F32 lhs, DN_V2I32 rhs) -{ - DN_V2F32 result = DN_V2F32From2N(lhs.x + rhs.x, lhs.y + rhs.y); - return result; -} - -DN_API DN_V2F32 operator+(DN_V2F32 lhs, DN_F32 rhs) -{ - DN_V2F32 result = DN_V2F32From2N(lhs.x + rhs, lhs.y + rhs); - return result; -} - -DN_API DN_V2F32 operator+(DN_V2F32 lhs, DN_I32 rhs) -{ - DN_V2F32 result = DN_V2F32From2N(lhs.x + rhs, lhs.y + rhs); - return result; -} - -DN_API DN_V2F32 operator*(DN_V2F32 lhs, DN_V2F32 rhs) -{ - DN_V2F32 result = DN_V2F32From2N(lhs.x * rhs.x, lhs.y * rhs.y); - return result; -} - -DN_API DN_V2F32 operator*(DN_V2F32 lhs, DN_V2I32 rhs) -{ - DN_V2F32 result = DN_V2F32From2N(lhs.x * rhs.x, lhs.y * rhs.y); - return result; -} - -DN_API DN_V2F32 operator*(DN_V2F32 lhs, DN_F32 rhs) -{ - DN_V2F32 result = DN_V2F32From2N(lhs.x * rhs, lhs.y * rhs); - return result; -} - -DN_API DN_V2F32 operator*(DN_V2F32 lhs, DN_I32 rhs) -{ - DN_V2F32 result = DN_V2F32From2N(lhs.x * rhs, lhs.y * rhs); - return result; -} - -DN_API DN_V2F32 operator/(DN_V2F32 lhs, DN_V2F32 rhs) -{ - DN_V2F32 result = DN_V2F32From2N(lhs.x / rhs.x, lhs.y / rhs.y); - return result; -} - -DN_API DN_V2F32 operator/(DN_V2F32 lhs, DN_V2I32 rhs) -{ - DN_V2F32 result = DN_V2F32From2N(lhs.x / rhs.x, lhs.y / rhs.y); - return result; -} - -DN_API DN_V2F32 operator/(DN_V2F32 lhs, DN_F32 rhs) -{ - DN_V2F32 result = DN_V2F32From2N(lhs.x / rhs, lhs.y / rhs); - return result; -} - -DN_API DN_V2F32 operator/(DN_V2F32 lhs, DN_I32 rhs) -{ - DN_V2F32 result = DN_V2F32From2N(lhs.x / rhs, lhs.y / rhs); - return result; -} - -DN_API DN_V2F32 &operator*=(DN_V2F32 &lhs, DN_V2F32 rhs) -{ - lhs = lhs * rhs; - return lhs; -} - -DN_API DN_V2F32 &operator*=(DN_V2F32 &lhs, DN_V2I32 rhs) -{ - lhs = lhs * rhs; - return lhs; -} - -DN_API DN_V2F32 &operator*=(DN_V2F32 &lhs, DN_F32 rhs) -{ - lhs = lhs * rhs; - return lhs; -} - -DN_API DN_V2F32 &operator*=(DN_V2F32 &lhs, DN_I32 rhs) -{ - lhs = lhs * rhs; - return lhs; -} - -DN_API DN_V2F32 &operator/=(DN_V2F32 &lhs, DN_V2F32 rhs) -{ - lhs = lhs / rhs; - return lhs; -} - -DN_API DN_V2F32 &operator/=(DN_V2F32 &lhs, DN_V2I32 rhs) -{ - lhs = lhs / rhs; - return lhs; -} - -DN_API DN_V2F32 &operator/=(DN_V2F32 &lhs, DN_F32 rhs) -{ - lhs = lhs / rhs; - return lhs; -} - -DN_API DN_V2F32 &operator/=(DN_V2F32 &lhs, DN_I32 rhs) -{ - lhs = lhs / rhs; - return lhs; -} - -DN_API DN_V2F32 &operator-=(DN_V2F32 &lhs, DN_V2F32 rhs) -{ - lhs = lhs - rhs; - return lhs; -} - -DN_API DN_V2F32 &operator-=(DN_V2F32 &lhs, DN_V2I32 rhs) -{ - lhs = lhs - rhs; - return lhs; -} - -DN_API DN_V2F32 &operator-=(DN_V2F32 &lhs, DN_F32 rhs) -{ - lhs = lhs - rhs; - return lhs; -} - -DN_API DN_V2F32 &operator-=(DN_V2F32 &lhs, DN_I32 rhs) -{ - lhs = lhs - rhs; - return lhs; -} - -DN_API DN_V2F32 &operator+=(DN_V2F32 &lhs, DN_V2F32 rhs) -{ - lhs = lhs + rhs; - return lhs; -} - -DN_API DN_V2F32 &operator+=(DN_V2F32 &lhs, DN_V2I32 rhs) -{ - lhs = lhs + rhs; - return lhs; -} - -DN_API DN_V2F32 &operator+=(DN_V2F32 &lhs, DN_F32 rhs) -{ - lhs = lhs + rhs; - return lhs; -} - -DN_API DN_V2F32 &operator+=(DN_V2F32 &lhs, DN_I32 rhs) -{ - lhs = lhs + rhs; - return lhs; -} - -DN_API DN_V2F32 DN_V2F32Min(DN_V2F32 a, DN_V2F32 b) -{ - DN_V2F32 result = DN_V2F32From2N(DN_Min(a.x, b.x), DN_Min(a.y, b.y)); - return result; -} - -DN_API DN_V2F32 DN_V2F32Max(DN_V2F32 a, DN_V2F32 b) -{ - DN_V2F32 result = DN_V2F32From2N(DN_Max(a.x, b.x), DN_Max(a.y, b.y)); - return result; -} - -DN_API DN_V2F32 DN_V2F32Abs(DN_V2F32 a) -{ - DN_V2F32 result = DN_V2F32From2N(DN_Abs(a.x), DN_Abs(a.y)); - return result; -} - -DN_API DN_F32 DN_V2F32Dot(DN_V2F32 a, DN_V2F32 b) -{ - // NOTE: Scalar projection of B onto A ///////////////////////////////////////////////////////// - // - // Scalar projection calculates the signed distance between `b` and `a` - // where `a` is a unit vector then, the dot product calculates the projection - // of `b` onto the infinite line that the direction of `a` represents. This - // calculation is the signed distance. - // - // signed_distance = dot_product(a, b) = (a.x * b.x) + (a.y * b.y) - // - // Y - // ^ b - // | /| - // | / | - // | / | - // | / | Projection - // | / | - // |/ V - // +--->--------> X - // . a . - // . . - // |------| <- Calculated signed distance - // - // The signed-ness of the result indicates the relationship: - // - // Distance <0 means `b` is behind `a` - // Distance >0 means `b` is in-front of `a` - // Distance ==0 means `b` is perpendicular to `a` - // - // If `a` is not normalized then the signed-ness of the result still holds - // however result no longer represents the actual distance between the - // 2 objects. One of the vectors must be normalised (e.g. turned into a unit - // vector). - // - // NOTE: DN_V projection ///////////////////////////////////////////////////////////////////// - // - // DN_V projection calculates the exact X,Y coordinates of where `b` meets - // `a` when it was projected. This is calculated by multipying the - // 'scalar projection' result by the unit vector of `a` - // - // vector_projection = a * signed_distance = a * dot_product(a, b) - - DN_F32 result = (a.x * b.x) + (a.y * b.y); - return result; -} - -DN_API DN_F32 DN_V2F32LengthSq2V2(DN_V2F32 lhs, DN_V2F32 rhs) -{ - // NOTE: Pythagoras's theorem (a^2 + b^2 = c^2) without the square root - DN_F32 a = rhs.x - lhs.x; - DN_F32 b = rhs.y - lhs.y; - DN_F32 c_squared = DN_Squared(a) + DN_Squared(b); - DN_F32 result = c_squared; - return result; -} - -DN_API bool DN_V2F32LengthSqIsWithin2V2(DN_V2F32 lhs, DN_V2F32 rhs, DN_F32 within_amount_sq) -{ - DN_F32 dist = DN_V2F32LengthSq2V2(lhs, rhs); - bool result = dist <= within_amount_sq; - return result; -} - -DN_API DN_F32 DN_V2F32Length2V2(DN_V2F32 lhs, DN_V2F32 rhs) -{ - DN_F32 result_squared = DN_V2F32LengthSq2V2(lhs, rhs); - DN_F32 result = DN_SqrtF32(result_squared); - return result; -} - -DN_API DN_F32 DN_V2F32LengthSq(DN_V2F32 lhs) -{ - // NOTE: Pythagoras's theorem without the square root - DN_F32 c_squared = DN_Squared(lhs.x) + DN_Squared(lhs.y); - DN_F32 result = c_squared; - return result; -} - -DN_API DN_F32 DN_V2F32Length(DN_V2F32 lhs) -{ - DN_F32 c_squared = DN_V2F32LengthSq(lhs); - DN_F32 result = DN_SqrtF32(c_squared); - return result; -} - -DN_API DN_V2F32 DN_V2F32Normalise(DN_V2F32 a) -{ - DN_F32 length = DN_V2F32Length(a); - DN_V2F32 result = a / length; - return result; -} - -DN_API DN_V2F32 DN_V2F32Perpendicular(DN_V2F32 a) -{ - // NOTE: Matrix form of a 2D vector can be defined as - // - // x' = x cos(t) - y sin(t) - // y' = x sin(t) + y cos(t) - // - // Calculate a line perpendicular to a vector means rotating the vector by - // 90 degrees - // - // x' = x cos(90) - y sin(90) - // y' = x sin(90) + y cos(90) - // - // Where `cos(90) = 0` and `sin(90) = 1` then, - // - // x' = -y - // y' = +x - - DN_V2F32 result = DN_V2F32From2N(-a.y, a.x); - return result; -} - -DN_API DN_V2F32 DN_V2F32Reflect(DN_V2F32 in, DN_V2F32 surface) -{ - DN_V2F32 normal = DN_V2F32Perpendicular(surface); - DN_V2F32 normal_norm = DN_V2F32Normalise(normal); - DN_F32 signed_dist = DN_V2F32Dot(in, normal_norm); - DN_V2F32 result = DN_V2F32From2N(in.x, in.y + (-signed_dist * 2.f)); - return result; -} - -DN_API DN_F32 DN_V2F32Area(DN_V2F32 a) -{ - DN_F32 result = a.w * a.h; - return result; -} - -DN_API bool operator!=(DN_V3F32 lhs, DN_V3F32 rhs) -{ - bool result = !(lhs == rhs); - return result; -} - -DN_API bool operator==(DN_V3F32 lhs, DN_V3F32 rhs) -{ - bool result = (lhs.x == rhs.x) && (lhs.y == rhs.y) && (lhs.z == rhs.z); - return result; -} - -DN_API bool operator>=(DN_V3F32 lhs, DN_V3F32 rhs) -{ - bool result = (lhs.x >= rhs.x) && (lhs.y >= rhs.y) && (lhs.z >= rhs.z); - return result; -} - -DN_API bool operator<=(DN_V3F32 lhs, DN_V3F32 rhs) -{ - bool result = (lhs.x <= rhs.x) && (lhs.y <= rhs.y) && (lhs.z <= rhs.z); - return result; -} - -DN_API bool operator<(DN_V3F32 lhs, DN_V3F32 rhs) -{ - bool result = (lhs.x < rhs.x) && (lhs.y < rhs.y) && (lhs.z < rhs.z); - return result; -} - -DN_API bool operator>(DN_V3F32 lhs, DN_V3F32 rhs) -{ - bool result = (lhs.x > rhs.x) && (lhs.y > rhs.y) && (lhs.z > rhs.z); - return result; -} - -DN_API DN_V3F32 operator-(DN_V3F32 lhs, DN_V3F32 rhs) -{ - DN_V3F32 result = DN_V3F32From3N(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z); - return result; -} - -DN_API DN_V3F32 operator-(DN_V3F32 lhs) -{ - DN_V3F32 result = DN_V3F32From3N(-lhs.x, -lhs.y, -lhs.z); - return result; -} - -DN_API DN_V3F32 operator+(DN_V3F32 lhs, DN_V3F32 rhs) -{ - DN_V3F32 result = DN_V3F32From3N(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z); - return result; -} - -DN_API DN_V3F32 operator*(DN_V3F32 lhs, DN_V3F32 rhs) -{ - DN_V3F32 result = DN_V3F32From3N(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z); - return result; -} - -DN_API DN_V3F32 operator*(DN_V3F32 lhs, DN_F32 rhs) -{ - DN_V3F32 result = DN_V3F32From3N(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs); - return result; -} - -DN_API DN_V3F32 operator*(DN_V3F32 lhs, DN_I32 rhs) -{ - DN_V3F32 result = DN_V3F32From3N(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs); - return result; -} - -DN_API DN_V3F32 operator/(DN_V3F32 lhs, DN_V3F32 rhs) -{ - DN_V3F32 result = DN_V3F32From3N(lhs.x / rhs.x, lhs.y / rhs.y, lhs.z / rhs.z); - return result; -} - -DN_API DN_V3F32 operator/(DN_V3F32 lhs, DN_F32 rhs) -{ - DN_V3F32 result = DN_V3F32From3N(lhs.x / rhs, lhs.y / rhs, lhs.z / rhs); - return result; -} - -DN_API DN_V3F32 operator/(DN_V3F32 lhs, DN_I32 rhs) -{ - DN_V3F32 result = DN_V3F32From3N(lhs.x / rhs, lhs.y / rhs, lhs.z / rhs); - return result; -} - -DN_API DN_V3F32 &operator*=(DN_V3F32 &lhs, DN_V3F32 rhs) -{ - lhs = lhs * rhs; - return lhs; -} - -DN_API DN_V3F32 &operator*=(DN_V3F32 &lhs, DN_F32 rhs) -{ - lhs = lhs * rhs; - return lhs; -} - -DN_API DN_V3F32 &operator*=(DN_V3F32 &lhs, DN_I32 rhs) -{ - lhs = lhs * rhs; - return lhs; -} - -DN_API DN_V3F32 &operator/=(DN_V3F32 &lhs, DN_V3F32 rhs) -{ - lhs = lhs / rhs; - return lhs; -} - -DN_API DN_V3F32 &operator/=(DN_V3F32 &lhs, DN_F32 rhs) -{ - lhs = lhs / rhs; - return lhs; -} - -DN_API DN_V3F32 &operator/=(DN_V3F32 &lhs, DN_I32 rhs) -{ - lhs = lhs / rhs; - return lhs; -} - -DN_API DN_V3F32 &operator-=(DN_V3F32 &lhs, DN_V3F32 rhs) -{ - lhs = lhs - rhs; - return lhs; -} - -DN_API DN_V3F32 &operator+=(DN_V3F32 &lhs, DN_V3F32 rhs) -{ - lhs = lhs + rhs; - return lhs; -} - -DN_API DN_V3F32 DN_V3F32Lerp(DN_V3F32 lhs, DN_F32 t01, DN_V3F32 rhs) -{ - DN_V3F32 result = {}; - result.x = lhs.x + ((rhs.x - lhs.x) * t01); - result.y = lhs.y + ((rhs.y - lhs.y) * t01); - result.z = lhs.z + ((rhs.z - lhs.z) * t01); - return result; -} - -DN_API DN_F32 DN_V3_LengthSq(DN_V3F32 a) -{ - DN_F32 result = DN_Squared(a.x) + DN_Squared(a.y) + DN_Squared(a.z); - return result; -} - -DN_API DN_F32 DN_V3_Length(DN_V3F32 a) -{ - DN_F32 length_sq = DN_Squared(a.x) + DN_Squared(a.y) + DN_Squared(a.z); - DN_F32 result = DN_SqrtF32(length_sq); - return result; -} - -DN_API DN_V3F32 DN_V3_Normalise(DN_V3F32 a) -{ - DN_F32 length = DN_V3_Length(a); - DN_V3F32 result = a / length; - return result; -} - -DN_API DN_V4F32 DN_V4F32Lerp(DN_V4F32 lhs, DN_F32 t01, DN_V4F32 rhs) -{ - DN_V4F32 result = {}; - result.x = lhs.x + (rhs.x - lhs.x) * t01; - result.y = lhs.y + (rhs.y - lhs.y) * t01; - result.z = lhs.z + (rhs.z - lhs.z) * t01; - result.w = lhs.w + (rhs.w - lhs.w) * t01; - return result; -} - -DN_API bool DN_V4F32RGBA01IsValid(DN_V4F32 rgba01) -{ - bool result = rgba01.r >= 0 && rgba01.r <= 1.f && - rgba01.g >= 0 && rgba01.g <= 1.f && - rgba01.b >= 0 && rgba01.b <= 1.f && - rgba01.a >= 0 && rgba01.a <= 1.f; - return result; -} - -DN_API DN_V4F32 DN_V4F32RGBA01FromRGBU32(DN_U32 u32) -{ - DN_U8 r = (DN_U8)((u32 & 0x00FF0000) >> 16); - DN_U8 g = (DN_U8)((u32 & 0x0000FF00) >> 8); - DN_U8 b = (DN_U8)((u32 & 0x000000FF) >> 0); - DN_V4F32 result = DN_V4F32RGBA01FromRGBU8(r, g, b); - return result; -} - -DN_API DN_V4F32 DN_V4F32RGBA01FromRGBAU32(DN_U32 u32) -{ - DN_U8 r = (DN_U8)((u32 & 0xFF000000) >> 24); - DN_U8 g = (DN_U8)((u32 & 0x00FF0000) >> 16); - DN_U8 b = (DN_U8)((u32 & 0x0000FF00) >> 8); - DN_U8 a = (DN_U8)((u32 & 0x000000FF) >> 0); - DN_V4F32 result = DN_V4F32RGBA01FromRGBAU8(r, g, b, a); - return result; -} - -#define DN_SRGB_COEFFICIENT_F32 2.2f -DN_API DN_V4F32 DN_V4F32Linear01FromSRGB01(DN_V4F32 srgb01) -{ - DN_Assert(srgb01.x >= 0.f && srgb01.x <= 1.f); - DN_Assert(srgb01.y >= 0.f && srgb01.y <= 1.f); - DN_Assert(srgb01.z >= 0.f && srgb01.z <= 1.f); - DN_Assert(srgb01.a >= 0.f && srgb01.a <= 1.f); - DN_V4F32 result = {}; - result.r = DN_PowF32(srgb01.r, DN_SRGB_COEFFICIENT_F32); - result.g = DN_PowF32(srgb01.g, DN_SRGB_COEFFICIENT_F32); - result.b = DN_PowF32(srgb01.b, DN_SRGB_COEFFICIENT_F32); - result.a = srgb01.a; - return result; -} - -DN_API DN_V4F32 DN_V4F32Linear01Desaturate(DN_V4F32 linear01, DN_F32 t01) -{ - DN_F32 luminance = (linear01.r * DN_V3F32_RGB_LUMINANCE.r) + (linear01.g * DN_V3F32_RGB_LUMINANCE.g) + (linear01.b * DN_V3F32_RGB_LUMINANCE.b); - DN_V4F32 result = linear01; - result.rgb = DN_V3F32Lerp(result.rgb, t01, DN_V3F32From1N(luminance)); - return result; -} - -DN_API DN_V4F32 DN_V4F32SRGB01FromLinear01(DN_V4F32 linear01) -{ - DN_Assert(linear01.x >= 0.f && linear01.x <= 1.f); - DN_Assert(linear01.y >= 0.f && linear01.y <= 1.f); - DN_Assert(linear01.z >= 0.f && linear01.z <= 1.f); - DN_Assert(linear01.a >= 0.f && linear01.a <= 1.f); - DN_V4F32 result = {}; - result.r = DN_PowF32(linear01.r, 1.f / DN_SRGB_COEFFICIENT_F32); - result.g = DN_PowF32(linear01.g, 1.f / DN_SRGB_COEFFICIENT_F32); - result.b = DN_PowF32(linear01.b, 1.f / DN_SRGB_COEFFICIENT_F32); - result.a = linear01.a; - return result; -} - -DN_API bool operator==(DN_V4F32 lhs, DN_V4F32 rhs) -{ - bool result = (lhs.x == rhs.x) && (lhs.y == rhs.y) && (lhs.z == rhs.z) && (lhs.w == rhs.w); - return result; -} - -DN_API bool operator!=(DN_V4F32 lhs, DN_V4F32 rhs) -{ - bool result = !(lhs == rhs); - return result; -} - -DN_API bool operator>=(DN_V4F32 lhs, DN_V4F32 rhs) -{ - bool result = (lhs.x >= rhs.x) && (lhs.y >= rhs.y) && (lhs.z >= rhs.z) && (lhs.w >= rhs.w); - return result; -} - -DN_API bool operator<=(DN_V4F32 lhs, DN_V4F32 rhs) -{ - bool result = (lhs.x <= rhs.x) && (lhs.y <= rhs.y) && (lhs.z <= rhs.z) && (lhs.w <= rhs.w); - return result; -} - -DN_API bool operator<(DN_V4F32 lhs, DN_V4F32 rhs) -{ - bool result = (lhs.x < rhs.x) && (lhs.y < rhs.y) && (lhs.z < rhs.z) && (lhs.w < rhs.w); - return result; -} - -DN_API bool operator>(DN_V4F32 lhs, DN_V4F32 rhs) -{ - bool result = (lhs.x > rhs.x) && (lhs.y > rhs.y) && (lhs.z > rhs.z) && (lhs.w > rhs.w); - return result; -} - -DN_API DN_V4F32 operator-(DN_V4F32 lhs, DN_V4F32 rhs) -{ - DN_V4F32 result = DN_V4F32From4N(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z, lhs.w - rhs.w); - return result; -} - -DN_API DN_V4F32 operator-(DN_V4F32 lhs) -{ - DN_V4F32 result = DN_V4F32From4N(-lhs.x, -lhs.y, -lhs.z, -lhs.w); - return result; -} - -DN_API DN_V4F32 operator+(DN_V4F32 lhs, DN_V4F32 rhs) -{ - DN_V4F32 result = DN_V4F32From4N(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z, lhs.w + rhs.w); - return result; -} - -DN_API DN_V4F32 operator*(DN_V4F32 lhs, DN_V4F32 rhs) -{ - DN_V4F32 result = DN_V4F32From4N(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z, lhs.w * rhs.w); - return result; -} - -DN_API DN_V4F32 operator*(DN_V4F32 lhs, DN_F32 rhs) -{ - DN_V4F32 result = DN_V4F32From4N(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs, lhs.w * rhs); - return result; -} - -DN_API DN_V4F32 operator*(DN_V4F32 lhs, DN_I32 rhs) -{ - DN_V4F32 result = DN_V4F32From4N(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs, lhs.w * rhs); - return result; -} - -DN_API DN_V4F32 operator/(DN_V4F32 lhs, DN_F32 rhs) -{ - DN_V4F32 result = DN_V4F32From4N(lhs.x / rhs, lhs.y / rhs, lhs.z / rhs, lhs.w / rhs); - return result; -} - -DN_API DN_V4F32 &operator*=(DN_V4F32 &lhs, DN_V4F32 rhs) -{ - lhs = lhs * rhs; - return lhs; -} - -DN_API DN_V4F32 &operator*=(DN_V4F32 &lhs, DN_F32 rhs) -{ - lhs = lhs * rhs; - return lhs; -} - -DN_API DN_V4F32 &operator*=(DN_V4F32 &lhs, DN_I32 rhs) -{ - lhs = lhs * rhs; - return lhs; -} - -DN_API DN_V4F32 &operator-=(DN_V4F32 &lhs, DN_V4F32 rhs) -{ - lhs = lhs - rhs; - return lhs; -} - -DN_API DN_V4F32 &operator+=(DN_V4F32 &lhs, DN_V4F32 rhs) -{ - lhs = lhs + rhs; - return lhs; -} - -DN_API DN_F32 DN_V4F32Dot(DN_V4F32 a, DN_V4F32 b) -{ - DN_F32 result = (a.x * b.x) + (a.y * b.y) + (a.z * b.z) + (a.w * b.w); - return result; -} - -DN_API DN_M4 DN_M4Identity() -{ - DN_M4 result = - { - { - {1, 0, 0, 0}, - {0, 1, 0, 0}, - {0, 0, 1, 0}, - {0, 0, 0, 1}, - } - }; - - return result; -} - -DN_API DN_M4 DN_M4ScaleF(DN_F32 x, DN_F32 y, DN_F32 z) -{ - DN_M4 result = - { - { - {x, 0, 0, 0}, - {0, y, 0, 0}, - {0, 0, z, 0}, - {0, 0, 0, 1}, - } - }; - - return result; -} - -DN_API DN_M4 DN_M4Scale(DN_V3F32 xyz) -{ - DN_M4 result = - { - { - {xyz.x, 0, 0, 0}, - {0, xyz.y, 0, 0}, - {0, 0, xyz.z, 0}, - {0, 0, 0, 1}, - } - }; - - return result; -} - -DN_API DN_M4 DN_M4TranslateF(DN_F32 x, DN_F32 y, DN_F32 z) -{ - DN_M4 result = - { - { - {1, 0, 0, 0}, - {0, 1, 0, 0}, - {0, 0, 1, 0}, - {x, y, z, 1}, - } - }; - - return result; -} - -DN_API DN_M4 DN_M4Translate(DN_V3F32 xyz) -{ - DN_M4 result = - { - { - {1, 0, 0, 0}, - {0, 1, 0, 0}, - {0, 0, 1, 0}, - {xyz.x, xyz.y, xyz.z, 1}, - } - }; - - return result; -} - -DN_API DN_M4 DN_M4Transpose(DN_M4 mat) -{ - DN_M4 result = {}; - for (int col = 0; col < 4; col++) - for (int row = 0; row < 4; row++) - result.columns[col][row] = mat.columns[row][col]; - return result; -} - -DN_API DN_M4 DN_M4Rotate(DN_V3F32 axis01, DN_F32 radians) -{ - DN_AssertF(DN_Abs(DN_V3_Length(axis01) - 1.f) <= 0.01f, - "Rotation axis must be normalised, length = %f", - DN_V3_Length(axis01)); - - DN_F32 sin = DN_SinF32(radians); - DN_F32 cos = DN_CosF32(radians); - DN_F32 one_minus_cos = 1.f - cos; - - DN_F32 x = axis01.x; - DN_F32 y = axis01.y; - DN_F32 z = axis01.z; - DN_F32 x2 = DN_Squared(x); - DN_F32 y2 = DN_Squared(y); - DN_F32 z2 = DN_Squared(z); - - DN_M4 result = - { - { - {cos + x2 * one_minus_cos, y * x * one_minus_cos + z * sin, z * x * one_minus_cos - y * sin, 0}, // Col 1 - {x * y * one_minus_cos - z * sin, cos + y2 * one_minus_cos, z * y * one_minus_cos + x * sin, 0}, // Col 2 - {x * z * one_minus_cos + y * sin, y * z * one_minus_cos - x * sin, cos + z2 * one_minus_cos, 0}, // Col 3 - {0, 0, 0, 1}, // Col 4 - } - }; - - return result; -} - -DN_API DN_M4 DN_M4Orthographic(DN_F32 left, DN_F32 right, DN_F32 bottom, DN_F32 top, DN_F32 z_near, DN_F32 z_far) -{ - // NOTE: Here is the matrix in column major for readability. Below it's - // transposed due to how you have to declare column major matrices in C/C++. - // - // m = [2/r-l, 0, 0, -1*(r+l)/(r-l)] - // [0, 2/t-b, 0, 1*(t+b)/(t-b)] - // [0, 0, -2/f-n, -1*(f+n)/(f-n)] - // [0, 0, 0, 1 ] - - DN_M4 result = - { - { - {2.f / (right - left), 0.f, 0.f, 0.f}, - {0.f, 2.f / (top - bottom), 0.f, 0.f}, - {0.f, 0.f, -2.f / (z_far - z_near), 0.f}, - {(-1.f * (right + left)) / (right - left), (-1.f * (top + bottom)) / (top - bottom), (-1.f * (z_far + z_near)) / (z_far - z_near), 1.f}, - } - }; - - return result; -} - -DN_API DN_M4 DN_M4Perspective(DN_F32 fov /*radians*/, DN_F32 aspect, DN_F32 z_near, DN_F32 z_far) -{ - DN_F32 tan_fov = DN_TanF32(fov / 2.f); - DN_M4 result = - { - { - {1.f / (aspect * tan_fov), 0.f, 0.f, 0.f}, - {0, 1.f / tan_fov, 0.f, 0.f}, - {0.f, 0.f, (z_near + z_far) / (z_near - z_far), -1.f}, - {0.f, 0.f, (2.f * z_near * z_far) / (z_near - z_far), 0.f}, - } - }; - - return result; -} - -DN_API DN_M4 DN_M4Add(DN_M4 lhs, DN_M4 rhs) -{ - DN_M4 result; - for (int col = 0; col < 4; col++) - for (int it = 0; it < 4; it++) - result.columns[col][it] = lhs.columns[col][it] + rhs.columns[col][it]; - return result; -} - -DN_API DN_M4 DN_M4Sub(DN_M4 lhs, DN_M4 rhs) -{ - DN_M4 result; - for (int col = 0; col < 4; col++) - for (int it = 0; it < 4; it++) - result.columns[col][it] = lhs.columns[col][it] - rhs.columns[col][it]; - return result; -} - -DN_API DN_M4 DN_M4Mul(DN_M4 lhs, DN_M4 rhs) -{ - DN_M4 result; - for (int col = 0; col < 4; col++) { - for (int row = 0; row < 4; row++) { - DN_F32 sum = 0; - for (int f32_it = 0; f32_it < 4; f32_it++) - sum += lhs.columns[f32_it][row] * rhs.columns[col][f32_it]; - - result.columns[col][row] = sum; - } - } - return result; -} - -DN_API DN_M4 DN_M4Div(DN_M4 lhs, DN_M4 rhs) -{ - DN_M4 result; - for (int col = 0; col < 4; col++) - for (int it = 0; it < 4; it++) - result.columns[col][it] = lhs.columns[col][it] / rhs.columns[col][it]; - return result; -} - -DN_API DN_M4 DN_M4AddF(DN_M4 lhs, DN_F32 rhs) -{ - DN_M4 result; - for (int col = 0; col < 4; col++) - for (int it = 0; it < 4; it++) - result.columns[col][it] = lhs.columns[col][it] + rhs; - return result; -} - -DN_API DN_M4 DN_M4SubF(DN_M4 lhs, DN_F32 rhs) -{ - DN_M4 result; - for (int col = 0; col < 4; col++) - for (int it = 0; it < 4; it++) - result.columns[col][it] = lhs.columns[col][it] - rhs; - return result; -} - -DN_API DN_M4 DN_M4MulF(DN_M4 lhs, DN_F32 rhs) -{ - DN_M4 result; - for (int col = 0; col < 4; col++) - for (int it = 0; it < 4; it++) - result.columns[col][it] = lhs.columns[col][it] * rhs; - return result; -} - -DN_API DN_M4 DN_M4DivF(DN_M4 lhs, DN_F32 rhs) -{ - DN_M4 result; - for (int col = 0; col < 4; col++) - for (int it = 0; it < 4; it++) - result.columns[col][it] = lhs.columns[col][it] / rhs; - return result; -} - -DN_API DN_Str8x256 DN_M4ColumnMajorString(DN_M4 mat) -{ - DN_Str8x256 result = {}; - for (int row = 0; row < 4; row++) { - for (int it = 0; it < 4; it++) { - if (it == 0) - DN_FmtAppend(result.data, &result.size, sizeof(result.data), "|"); - DN_FmtAppend(result.data, &result.size, sizeof(result.data), "%.5f", mat.columns[it][row]); - if (it != 3) - DN_FmtAppend(result.data, &result.size, sizeof(result.data), ", "); - else - DN_FmtAppend(result.data, &result.size, sizeof(result.data), "|\n"); - } - } - return result; -} - -DN_API bool DN_M2x3Eq(DN_M2x3 const *lhs, DN_M2x3 const *rhs) -{ - bool result = DN_Memcmp(lhs->e, rhs->e, sizeof(lhs->e[0]) * DN_ArrayCountU(lhs->e)) == 0; - return result; -} - -DN_API bool DN_M2x3NotEq(DN_M2x3 const *lhs, DN_M2x3 const *rhs) -{ - bool result = !DN_M2x3Eq(lhs, rhs); - return result; -} - -DN_API DN_M2x3 DN_M2x3Identity() -{ - DN_M2x3 result = { - { - 1, - 0, - 0, - 0, - 1, - 0, - } - }; - return result; -} - -DN_API DN_M2x3 DN_M2x3Translate(DN_V2F32 offset) -{ - DN_M2x3 result = { - { - 1, - 0, - offset.x, - 0, - 1, - offset.y, - } - }; - return result; -} - -DN_API DN_V2F32 DN_M2x3ScaleGet(DN_M2x3 m2x3) -{ - DN_V2F32 result = DN_V2F32From2N(m2x3.row[0][0], m2x3.row[1][1]); - return result; -} - -DN_API DN_M2x3 DN_M2x3Scale(DN_V2F32 scale) -{ - DN_M2x3 result = {{ - scale.x, 0, 0, - 0, scale.y, 0, - }}; - return result; -} - -DN_API DN_M2x3 DN_M2x3Rotate(DN_F32 radians) -{ - DN_M2x3 result = {{ - DN_CosF32(radians), DN_SinF32(radians), 0, - -DN_SinF32(radians), DN_CosF32(radians), 0, - }}; - return result; -} - -DN_API DN_M2x3 DN_M2x3ProjFromV2F32(DN_V2F32 size, DN_M2x3ProjOrigin origin) -{ - DN_M2x3 result = {}; - - // NOTE: Maps coordinates within a rectangle of `size` into NDC where (-1, +1) is top left, (+1, -1) is bot right - if (origin == DN_M2x3ProjOrigin_TopLeft) { - result = {{ - 2.f/size.w, 0, -1.f, - 0, -2.f/size.h, +1.f, - }}; - } else { - DN_Assert(origin == DN_M2x3ProjOrigin_Center); - result = {{ - 2.f/size.w, 0, 0.f, - 0, -2.f/size.h, 0.f, - }}; - } - return result; -} - -DN_API DN_M2x3XForm DN_M2x3XFormFromM2x3(DN_M2x3 forward, DN_M2x3 inverse) -{ - DN_M2x3XForm result = {}; - result.forward = forward; - result.inverse = inverse; - return result; -} - -DN_API DN_M2x3XForm DN_M2x3XFormFromTRS(DN_V2F32 pos, DN_V2F32 scale, DN_F32 rotate_rads, DN_V2F32 pivot_pos) -{ - DN_M2x3XForm result = {}; - result.forward = DN_M2x3Identity(); - result.inverse = DN_M2x3Identity(); - - if (scale.x == 0) - scale.x = 1; - if (scale.y == 0) - scale.y = 1; - - result.forward = DN_M2x3Mul(result.forward, DN_M2x3Translate(pivot_pos)); - result.forward = DN_M2x3Mul(result.forward, DN_M2x3Rotate(rotate_rads)); - result.forward = DN_M2x3Mul(result.forward, DN_M2x3Scale(scale)); - result.forward = DN_M2x3Mul(result.forward, DN_M2x3Translate(-pivot_pos)); - result.forward = DN_M2x3Mul(result.forward, DN_M2x3Translate(pos)); - - DN_V2F32 inverse_scale = DN_V2F32From1N(1) / scale; - result.inverse = DN_M2x3Mul(result.inverse, DN_M2x3Translate(-pos)); - result.inverse = DN_M2x3Mul(result.inverse, DN_M2x3Translate(pivot_pos)); - result.inverse = DN_M2x3Mul(result.inverse, DN_M2x3Scale(inverse_scale)); - result.inverse = DN_M2x3Mul(result.inverse, DN_M2x3Rotate(-rotate_rads)); - result.inverse = DN_M2x3Mul(result.inverse, DN_M2x3Translate(-pivot_pos)); - return result; -} - -DN_API DN_M2x3XForm DN_M2x3XFormIdentity() -{ - DN_M2x3XForm result = {}; - result.forward = DN_M2x3Identity(); - result.inverse = DN_M2x3Identity(); - return result; -} - -DN_API DN_M2x3XForm DN_M2x3XFormMul(DN_M2x3XForm m1, DN_M2x3XForm m2) -{ - DN_M2x3XForm result = {}; - result.forward = DN_M2x3Mul(m1.forward, m2.forward); - result.inverse = DN_M2x3Mul(m2.inverse, m1.inverse); - return result; -} - -DN_API DN_M2x3 DN_M2x3Mul(DN_M2x3 m1, DN_M2x3 m2) -{ - // NOTE: Ordinarily you can't multiply M2x3 with M2x3 because column count - // (3) != row count (2). We pretend we have two 3x3 matrices with the last - // row set to [0 0 1] and perform a 3x3 matrix multiply. - // - // | (0)a (1)b (2)c | | (0)g (1)h (2)i | - // | (3)d (4)e (5)f | x | (3)j (4)k (5)l | - // | (6)0 (7)0 (8)1 | | (6)0 (7)0 (8)1 | - - DN_M2x3 result = { - { - m1.e[0] * m2.e[0] + m1.e[1] * m2.e[3], // a*g + b*j + c*0[omitted], - m1.e[0] * m2.e[1] + m1.e[1] * m2.e[4], // a*h + b*k + c*0[omitted], - m1.e[0] * m2.e[2] + m1.e[1] * m2.e[5] + m1.e[2], // a*i + b*l + c*1, - - m1.e[3] * m2.e[0] + m1.e[4] * m2.e[3], // d*g + e*j + f*0[omitted], - m1.e[3] * m2.e[1] + m1.e[4] * m2.e[4], // d*h + e*k + f*0[omitted], - m1.e[3] * m2.e[2] + m1.e[4] * m2.e[5] + m1.e[5], // d*i + e*l + f*1, - } - }; - - return result; -} - -DN_API DN_V2F32 DN_M2x3Mul2F32(DN_M2x3 m1, DN_F32 x, DN_F32 y) -{ - // NOTE: Ordinarily you can't multiply M2x3 with V2 because column count (3) - // != row count (2). We pretend we have a V3 with `z` set to `1`. - // - // | (0)a (1)b (2)c | | x | - // | (3)d (4)e (5)f | x | y | - // | 1 | - - DN_V2F32 result = { - { - m1.e[0] * x + m1.e[1] * y + m1.e[2], // a*x + b*y + c*1 - m1.e[3] * x + m1.e[4] * y + m1.e[5], // d*x + e*y + f*1 - } - }; - return result; -} - -DN_API DN_V2F32 DN_M2x3MulV2F32(DN_M2x3 m1, DN_V2F32 v2) -{ - DN_V2F32 result = DN_M2x3Mul2F32(m1, v2.x, v2.y); - return result; -} - -DN_API DN_Rect DN_M2x3MulRect(DN_M2x3 m1, DN_Rect rect) -{ - DN_2V2F32 rect_range = DN_RectRange(rect); - DN_V2F32 m1_min = DN_M2x3MulV2F32(m1, rect_range.min); - DN_V2F32 m1_max = DN_M2x3MulV2F32(m1, rect_range.max); - - // NOTE: Re-establish AABB of the rectangle because it has gone through an arbitrary - // vertex transformation. - DN_2V2F32 result_range = {}; - result_range.min = DN_V2F32Min(m1_min, m1_max); - result_range.max = DN_V2F32Max(m1_min, m1_max); - - DN_Rect result = DN_RectFrom2V2(result_range.min, DN_V2F32Abs(result_range.max - result_range.min)); - return result; -} - -DN_API DN_V2F32 DN_RectCenter(DN_Rect rect) -{ - DN_V2F32 result = rect.pos + (rect.size * .5f); - return result; -} - -DN_API bool DN_RectContainsPoint(DN_Rect rect, DN_V2F32 p) -{ - DN_V2F32 min = rect.pos; - DN_V2F32 max = rect.pos + rect.size; - bool result = (p.x >= min.x && p.x <= max.x && p.y >= min.y && p.y <= max.y); - return result; -} - -DN_API bool DN_RectContainsRect(DN_Rect a, DN_Rect b) -{ - DN_V2F32 a_min = a.pos; - DN_V2F32 a_max = a.pos + a.size; - DN_V2F32 b_min = b.pos; - DN_V2F32 b_max = b.pos + b.size; - bool result = (b_min >= a_min && b_max <= a_max); - return result; -} - -DN_API DN_Rect DN_RectExpand(DN_Rect a, DN_F32 amount) -{ - DN_Rect result = a; - result.pos -= amount; - result.size += (amount * 2.f); - return result; -} - -DN_API DN_Rect DN_RectExpandV2(DN_Rect a, DN_V2F32 amount) -{ - DN_Rect result = a; - result.pos -= amount; - result.size += (amount * 2.f); - return result; -} - -DN_API bool DN_RectIntersects(DN_Rect a, DN_Rect b) -{ - DN_V2F32 a_min = a.pos; - DN_V2F32 a_max = a.pos + a.size; - DN_V2F32 b_min = b.pos; - DN_V2F32 b_max = b.pos + b.size; - bool has_size = a.size.x && a.size.y && b.size.x && b.size.y; - bool result = false; - if (has_size) - result = (a_min.x <= b_max.x && a_max.x >= b_min.x) && - (a_min.y <= b_max.y && a_max.y >= b_min.y); - return result; -} - -DN_API DN_Rect DN_RectIntersection(DN_Rect a, DN_Rect b) -{ - DN_Rect result = DN_RectFrom2V2(a.pos, DN_V2F32From1N(0)); - if (DN_RectIntersects(a, b)) { - DN_V2F32 a_min = a.pos; - DN_V2F32 a_max = a.pos + a.size; - DN_V2F32 b_min = b.pos; - DN_V2F32 b_max = b.pos + b.size; - - DN_V2F32 min = {}; - DN_V2F32 max = {}; - min.x = DN_Max(a_min.x, b_min.x); - min.y = DN_Max(a_min.y, b_min.y); - max.x = DN_Min(a_max.x, b_max.x); - max.y = DN_Min(a_max.y, b_max.y); - result = DN_RectFrom2V2(min, max - min); - } - return result; -} - -DN_API DN_Rect DN_RectUnion(DN_Rect a, DN_Rect b) -{ - DN_V2F32 a_min = a.pos; - DN_V2F32 a_max = a.pos + a.size; - DN_V2F32 b_min = b.pos; - DN_V2F32 b_max = b.pos + b.size; - - DN_V2F32 min, max; - min.x = DN_Min(a_min.x, b_min.x); - min.y = DN_Min(a_min.y, b_min.y); - max.x = DN_Max(a_max.x, b_max.x); - max.y = DN_Max(a_max.y, b_max.y); - DN_Rect result = DN_RectFrom2V2(min, max - min); - return result; -} - -DN_API DN_2V2F32 DN_RectRange(DN_Rect a) -{ - DN_2V2F32 result = {}; - result.min = a.pos; - result.max = a.pos + a.size; - return result; -} - -DN_API bool DN_RectEq(DN_Rect lhs, DN_Rect rhs) -{ - bool result = lhs.pos == rhs.pos && lhs.size == rhs.size; - return result; -} - -DN_API DN_F32 DN_RectArea(DN_Rect a) -{ - DN_F32 result = a.size.w * a.size.h; - return result; -} - -DN_API DN_Rect DN_RectCutLeftClip(DN_Rect *rect, DN_F32 amount, DN_RectCutClip clip) -{ - DN_F32 min_x = rect->pos.x; - DN_F32 max_x = rect->pos.x + rect->size.w; - DN_F32 result_max_x = min_x + amount; - if (clip) - result_max_x = DN_Min(result_max_x, max_x); - DN_Rect result = DN_RectFrom4N(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; -} - -DN_API DN_Rect DN_RectCutRightClip(DN_Rect *rect, DN_F32 amount, DN_RectCutClip clip) -{ - DN_F32 min_x = rect->pos.x; - DN_F32 max_x = rect->pos.x + rect->size.w; - DN_F32 result_min_x = max_x - amount; - if (clip) - result_min_x = DN_Max(result_min_x, 0); - DN_Rect result = DN_RectFrom4N(result_min_x, rect->pos.y, max_x - result_min_x, rect->size.h); - rect->size.w = result_min_x - min_x; - return result; -} - -DN_API DN_Rect DN_RectCutTopClip(DN_Rect *rect, DN_F32 amount, DN_RectCutClip clip) -{ - DN_F32 min_y = rect->pos.y; - DN_F32 max_y = rect->pos.y + rect->size.h; - DN_F32 result_max_y = min_y + amount; - if (clip) - result_max_y = DN_Min(result_max_y, max_y); - DN_Rect result = DN_RectFrom4N(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; -} - -DN_API DN_Rect DN_RectCutBottomClip(DN_Rect *rect, DN_F32 amount, DN_RectCutClip clip) -{ - DN_F32 min_y = rect->pos.y; - DN_F32 max_y = rect->pos.y + rect->size.h; - DN_F32 result_min_y = max_y - amount; - if (clip) - result_min_y = DN_Max(result_min_y, 0); - DN_Rect result = DN_RectFrom4N(rect->pos.x, result_min_y, rect->size.w, max_y - result_min_y); - rect->size.h = result_min_y - min_y; - return result; -} - -DN_API DN_Rect DN_RectCutCut(DN_RectCut rect_cut, DN_V2F32 size, DN_RectCutClip clip) -{ - DN_Rect result = {}; - if (rect_cut.rect) { - switch (rect_cut.side) { - case DN_RectCutSide_Left: result = DN_RectCutLeftClip(rect_cut.rect, size.w, clip); break; - case DN_RectCutSide_Right: result = DN_RectCutRightClip(rect_cut.rect, size.w, clip); break; - case DN_RectCutSide_Top: result = DN_RectCutTopClip(rect_cut.rect, size.h, clip); break; - case DN_RectCutSide_Bottom: result = DN_RectCutBottomClip(rect_cut.rect, size.h, clip); break; - } - } - return result; -} - -DN_API DN_V2F32 DN_RectInterpV2F32(DN_Rect rect, DN_V2F32 t01) -{ - DN_V2F32 result = DN_V2F32From2N(rect.pos.w + (rect.size.w * t01.x), - rect.pos.h + (rect.size.h * t01.y)); - return result; -} - -DN_API DN_V2F32 DN_RectTopLeft(DN_Rect rect) -{ - DN_V2F32 result = DN_RectInterpV2F32(rect, DN_V2F32From2N(0, 0)); - return result; -} - -DN_API DN_V2F32 DN_RectTopRight(DN_Rect rect) -{ - DN_V2F32 result = DN_RectInterpV2F32(rect, DN_V2F32From2N(1, 0)); - return result; -} - -DN_API DN_V2F32 DN_RectBottomLeft(DN_Rect rect) -{ - DN_V2F32 result = DN_RectInterpV2F32(rect, DN_V2F32From2N(0, 1)); - return result; -} - -DN_API DN_V2F32 DN_RectBottomRight(DN_Rect rect) -{ - DN_V2F32 result = DN_RectInterpV2F32(rect, DN_V2F32From2N(1, 1)); - return result; -} - -DN_API DN_RaycastV2 DN_RaycastLineIntersectV2(DN_V2F32 origin_a, DN_V2F32 dir_a, DN_V2F32 origin_b, DN_V2F32 dir_b) -{ - // NOTE: Parametric equation of a line - // - // p = o + (t*d) - // - // - o is the starting 2d point - // - d is the direction of the line - // - t is a scalar that scales along the direction of the point - // - // To determine if a ray intersections a ray, we want to solve - // - // (o_a + (t_a * d_a)) = (o_b + (t_b * d_b)) - // - // Where '_a' and '_b' represent the 1st and 2nd point's origin, direction - // and 't' components respectively. This is 2 equations with 2 unknowns - // (`t_a` and `t_b`) which we can solve for by expressing the equation in - // terms of `t_a` and `t_b`. - // - // Working that math out produces the formula below for 't'. - - DN_RaycastV2 result = {}; - DN_F32 denominator = ((dir_b.y * dir_a.x) - (dir_b.x * dir_a.y)); - if (denominator != 0.0f) { - result.t_a = (((origin_a.y - origin_b.y) * dir_b.x) + ((origin_b.x - origin_a.x) * dir_b.y)) / denominator; - result.t_b = (((origin_a.y - origin_b.y) * dir_a.x) + ((origin_b.x - origin_a.x) * dir_a.y)) / denominator; - result.hit = true; - } - return result; -} - -struct DN_ArrayFindEqMemcmpContext_ -{ - DN_USize elem_size; - void const *find; -}; - -DN_API void *DN_SliceAllocArena(void **data, DN_USize *slice_size_field, DN_USize size, DN_USize elem_size, DN_U8 align, DN_ZMem zmem, DN_Arena *arena) -{ - void *result = *data; - *data = DN_ArenaAlloc(arena, size * elem_size, align, zmem); - if (*data) - *slice_size_field = size; - return result; -} - -DN_API DN_ArrayFindResult DN_ArrayFind(void *data, DN_USize size, DN_USize elem_size, void const *find, DN_ArrayFindEqFunc *eq_func) -{ - DN_ArrayFindResult result = {}; - DN_Assert(data); - DN_Assert(elem_size); - if (find) { - for (DN_ForIndexU(index, size)) { - DN_U8 *it = DN_Cast(DN_U8 *) data + (index * elem_size); - if (eq_func(it, find)) { - result.index = index; - result.value = it; - result.success = true; - break; - } - } - } - return result; -} - -static bool DN_ArrayFindEqMemEqUnsafe_(void const *lhs, void const *find) -{ - DN_ArrayFindEqMemcmpContext_ *context = DN_Cast(DN_ArrayFindEqMemcmpContext_ *) find; - bool result = DN_MemEqUnsafe(lhs, context->find, context->elem_size); - return result; -} - -DN_API DN_ArrayFindResult DN_ArrayFindMemEq(void *data, DN_USize size, DN_USize elem_size, void const *find) -{ - DN_ArrayFindEqMemcmpContext_ context = {}; - context.elem_size = elem_size; - context.find = find; - DN_ArrayFindResult result = DN_ArrayFind(data, size, elem_size, &context, DN_ArrayFindEqMemEqUnsafe_); - return result; -} - -DN_API void *DN_ArrayInsertArray(void *data, DN_USize *size, DN_USize max, DN_USize elem_size, DN_USize index, void const *items, DN_USize count) -{ - void *result = nullptr; - if (!data || !size || !items || count <= 0 || ((*size + count) > max)) - return result; - - DN_USize clamped_index = DN_Min(index, *size); - if (clamped_index != *size) { - char const *src = DN_Cast(char *)data + (clamped_index * elem_size); - char const *dest = DN_Cast(char *)data + ((clamped_index + count) * elem_size); - char const *end = DN_Cast(char *)data + (size[0] * elem_size); - DN_USize bytes_to_move = end - src; - DN_Memmove(DN_Cast(void *) dest, src, bytes_to_move); - } - - result = DN_Cast(char *)data + (clamped_index * elem_size); - DN_Memcpy(result, items, elem_size * count); - *size += count; - return result; -} - -DN_API void *DN_ArrayPopFront(void *data, DN_USize *size, DN_USize elem_size, DN_USize count) -{ - if (!data || !size || *size == 0 || count == 0) - return nullptr; - - DN_USize pop_count = DN_Min(count, *size); - void *result = data; - - if (pop_count < *size) { - char *src = DN_Cast(char *)data + (pop_count * elem_size); - char *dest = DN_Cast(char *)data; - DN_USize bytes_to_move = (*size - pop_count) * elem_size; - DN_Memmove(dest, src, bytes_to_move); - } - - *size -= pop_count; - return result; -} - -DN_API void *DN_ArrayPopBack(void *data, DN_USize *size, DN_USize elem_size, DN_USize count) -{ - if (!data || !size || *size == 0 || count == 0) - return nullptr; - - DN_USize pop_count = DN_Min(count, *size); - *size -= pop_count; - - return DN_Cast(char *)data + (*size * elem_size); -} - -DN_API DN_ArrayEraseResult DN_ArrayEraseRange(void *data, DN_USize *size, DN_USize elem_size, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase) -{ - DN_ArrayEraseResult result = {}; - result.it_index = begin_index; - if (!data || !size || *size == 0 || count == 0) - return result; - - // Compute the range to erase - DN_USize start = 0, end = 0; - if (count < 0) { - // Erase backwards from begin_index, not inclusive of begin_index - // Range: [begin_index + count, begin_index) - // Which is: [begin_index - abs(count), begin_index) - DN_USize abs_count = DN_Abs(count); - start = (begin_index > abs_count) ? (begin_index - abs_count) : 0; - end = begin_index; - } else { - start = begin_index; - end = begin_index + count; - } - - // Clamp indices to valid bounds - start = DN_Min(start, *size); - end = DN_Min(end, *size); - - // Erase the range [start, end) - DN_USize erase_count = end > start ? end - start : 0; - if (erase_count) { - char *dest = (char *)data + (elem_size * start); - char *array_end = (char *)data + (elem_size * *size); - char *src = dest + (elem_size * erase_count); - if (erase == DN_ArrayErase_Stable) { - DN_USize move_size = array_end - src; - DN_Memmove(dest, src, move_size); - } else { - char *unstable_src = array_end - (elem_size * erase_count); - DN_USize move_size = array_end - unstable_src; - DN_Memcpy(dest, unstable_src, move_size); - } - *size -= erase_count; - } - - result.items_erased = erase_count; - // NOTE: If we are erasing from the current index of the iterator to the end of the array then - // there's no more elements in the array to iterate. So the returned index should b - // one-past-last index - if (begin_index == start && end >= *size) { - result.it_index = *size; - } else { - result.it_index = start ? start - 1 : 0; - } - return result; -} - -DN_API void *DN_ArrayMakeArray(void *data, DN_USize *size, DN_USize max, DN_USize elem_size, DN_USize make_count, DN_ZMem z_mem) -{ - void *result = nullptr; - DN_USize new_size = *size + make_count; - if (new_size <= max) { - result = DN_Cast(char *) data + (elem_size * size[0]); - *size = new_size; - if (z_mem == DN_ZMem_Yes) - DN_Memset(result, 0, elem_size * make_count); - } - return result; -} - -DN_API void *DN_ArrayMakeArrayAssert(void *data, DN_USize *size, DN_USize max, DN_USize elem_size, DN_USize make_count, DN_ZMem z_mem, DN_CallSite call_site) -{ - void *result = DN_ArrayMakeArray(data, size, max, elem_size, make_count, z_mem); - DN_AssertCallSiteF(result, call_site, "Array out of space, failed to add %zu items: array=%p size=%zu max=%zu", make_count, data, *size, max); - return result; -} - -DN_API void *DN_ArrayAddArray(void *data, DN_USize *size, DN_USize max, DN_USize elem_size, void const *elems, DN_USize elems_count, DN_ArrayAdd add) -{ - void *result = DN_ArrayMakeArray(data, size, max, elem_size, elems_count, DN_ZMem_No); - if (result) { - if (add == DN_ArrayAdd_Append) { - DN_Memcpy(result, elems, elems_count * elem_size); - } else { - char *move_dest = DN_Cast(char *)data + (elems_count * elem_size); // Shift elements forward - char *move_src = DN_Cast(char *)data; - DN_Memmove(move_dest, move_src, elem_size * size[0]); - DN_Memcpy(data, elems, elem_size * elems_count); - } - } - return result; -} - -DN_API void *DN_ArrayAddArrayAssert(void *data, DN_USize *size, DN_USize max, DN_USize elem_size, void const *elems, DN_USize elems_count, DN_ArrayAdd add, DN_CallSite call_site) -{ - void *result = DN_ArrayAddArray(data, size, max, elem_size, elems, elems_count, add); - DN_AssertCallSiteF(result, call_site, "Array out of space, failed to add %zu items: array=%p size=%zu max=%zu", elems_count, data, *size, max); - return result; -} - -DN_API bool DN_ArrayResizeFromArena(void **data, DN_USize *size, DN_USize *max, DN_USize elem_size, DN_Pool *pool, DN_USize new_max) -{ - bool result = true; - if (new_max != *max) { - DN_USize bytes_to_alloc = elem_size * new_max; - void *buffer = DN_PoolNewArray(pool, DN_U8, bytes_to_alloc); - if (buffer) { - DN_USize bytes_to_copy = elem_size * DN_Min(*size, new_max); - DN_Memcpy(buffer, *data, bytes_to_copy); - DN_PoolDealloc(pool, *data); - *data = buffer; - *max = new_max; - *size = DN_Min(*size, new_max); - } else { - result = false; - } - } - - return result; -} - -DN_API bool DN_ArrayResizeFromPool(void **data, DN_USize *size, DN_USize *max, DN_USize elem_size, DN_Pool *pool, DN_USize new_max) -{ - bool result = true; - if (new_max != *max) { - DN_USize bytes_to_alloc = elem_size * new_max; - void *buffer = DN_PoolNewArray(pool, DN_U8, bytes_to_alloc); - if (buffer) { - DN_USize bytes_to_copy = elem_size * DN_Min(*size, new_max); - DN_Memcpy(buffer, *data, bytes_to_copy); - DN_PoolDealloc(pool, *data); - *data = buffer; - *max = new_max; - *size = DN_Min(*size, new_max); - } else { - result = false; - } - } - - return result; -} - -DN_API bool DN_ArrayResizeFromArena(void **data, DN_USize *size, DN_USize *max, DN_USize elem_size, DN_Arena *arena, DN_USize new_max) -{ - bool result = true; - if (new_max != *max) { - DN_USize bytes_to_alloc = elem_size * new_max; - void *buffer = DN_ArenaNewArray(arena, DN_U8, bytes_to_alloc, DN_ZMem_No); - if (buffer) { - DN_USize bytes_to_copy = elem_size * DN_Min(*size, new_max); - DN_Memcpy(buffer, *data, bytes_to_copy); - *data = buffer; - *max = new_max; - *size = DN_Min(*size, new_max); - } else { - result = false; - } - } - - return result; -} - -DN_API bool DN_ArrayGrowFromPool(void **data, DN_USize size, DN_USize *max, DN_USize elem_size, DN_Pool *pool, DN_USize new_max) -{ - bool result = true; - if (new_max > *max) - result = DN_ArrayResizeFromPool(data, &size, max, elem_size, pool, new_max); - return result; -} - -DN_API bool DN_ArrayGrowFromArena(void **data, DN_USize size, DN_USize *max, DN_USize elem_size, DN_Arena *arena, DN_USize new_max) -{ - bool result = true; - if (new_max > *max) - result = DN_ArrayResizeFromArena(data, &size, max, elem_size, arena, new_max); - return result; -} - - -DN_API bool DN_ArrayGrowIfNeededFromPool(void **data, DN_USize size, DN_USize *max, DN_USize elem_size, DN_Pool *pool, DN_USize add_count) -{ - bool result = true; - DN_USize new_size = size + add_count; - if (new_size > *max) { - DN_USize new_max = DN_Max(DN_Max(*max * 2, new_size), 8); - result = DN_ArrayResizeFromPool(data, &size, max, elem_size, pool, new_max); - } - return result; -} - -DN_API bool DN_ArrayGrowIfNeededFromArena(void **data, DN_USize size, DN_USize *max, DN_USize elem_size, DN_Arena *arena, DN_USize add_count) -{ - bool result = true; - DN_USize new_size = size + add_count; - if (new_size > *max) { - DN_USize new_max = DN_Max(DN_Max(*max * 2, new_size), 8); - result = DN_ArrayResizeFromArena(data, &size, max, elem_size, arena, new_max); - } - return result; -} - -DN_API void *DN_SinglyLLDetach(void **link, void **next) -{ - void *result = *link; - if (*link) { - *link = *next; - *next = nullptr; - } - return result; -} - -DN_API bool DN_RingHasSpace(DN_Ring const *ring, DN_U64 size) -{ - DN_U64 avail = ring->write_pos - ring->read_pos; - DN_U64 space = ring->size - avail; - bool result = space >= size; - return result; -} - -DN_API bool DN_RingHasData(DN_Ring const *ring, DN_U64 size) -{ - DN_U64 data = ring->write_pos - ring->read_pos; - bool result = data >= size; - return result; -} - -DN_API void DN_RingWrite(DN_Ring *ring, void const *src, DN_U64 src_size) -{ - DN_Assert(src_size <= ring->size); - DN_U64 offset = ring->write_pos % ring->size; - DN_U64 bytes_before_split = ring->size - offset; - DN_U64 pre_split_bytes = DN_Min(bytes_before_split, src_size); - DN_U64 post_split_bytes = src_size - pre_split_bytes; - void const *pre_split_data = src; - void const *post_split_data = ((char *)src + pre_split_bytes); - DN_Memcpy(ring->base + offset, pre_split_data, pre_split_bytes); - DN_Memcpy(ring->base, post_split_data, post_split_bytes); - ring->write_pos += src_size; -} - -DN_API void DN_RingRead(DN_Ring *ring, void *dest, DN_U64 dest_size) -{ - DN_Assert(dest_size <= ring->size); - DN_U64 offset = ring->read_pos % ring->size; - DN_U64 bytes_before_split = ring->size - offset; - DN_U64 pre_split_bytes = DN_Min(bytes_before_split, dest_size); - DN_U64 post_split_bytes = dest_size - pre_split_bytes; - DN_Memcpy(dest, ring->base + offset, pre_split_bytes); - DN_Memcpy((char *)dest + pre_split_bytes, ring->base, post_split_bytes); - ring->read_pos += dest_size; -} - -#if defined(__cplusplus) -template -DN_DSMap DN_DSMapInit(DN_Arena *arena, DN_U32 size, DN_DSMapFlags flags) -{ - DN_AssertF(DN_IsPowerOfTwo(size), "Power-of-two size required, given size was '%u'", size); - DN_Assert(arena); - - DN_DSMap result = {}; - if (size <= 0) - return result; - if (!arena) - return result; - - result.arena = arena; - result.pool = DN_PoolFromArena(arena, DN_POOL_DEFAULT_ALIGN); - result.hash_to_slot = DN_ArenaNewArray(result.arena, DN_U32, size, DN_ZMem_Yes); - result.slots = DN_ArenaNewArray(result.arena, DN_DSMapSlot, size, DN_ZMem_Yes); - result.occupied = 1; // For sentinel - result.size = size; - result.initial_size = size; - result.flags = flags; - DN_AssertF(result.hash_to_slot && result.slots, "We pre-allocated a block of memory sufficient in size for the 2 arrays. Maybe the pointers needed extra space because of natural alignment?"); - return result; -} - -template -void DN_DSMapDeinit(DN_DSMap *map, DN_ZMem z_mem) -{ - if (!map) - return; - // TODO(doyle): Use z_mem - (void)z_mem; - DN_MemListDeinit(map->arena->mem); - *map = {}; -} - -template -bool DN_DSMapIsValid(DN_DSMap const *map) -{ - bool result = map && - map->arena && - map->hash_to_slot && // Hash to slot mapping array must be allocated - map->slots && // Slots array must be allocated - (map->size & (map->size - 1)) == 0 && // Must be power of two size - map->occupied >= 1; // DN_DS_MAP_SENTINEL_SLOT takes up one slot - return result; -} - -template -DN_U32 DN_DSMapHash(DN_DSMap const *map, DN_DSMapKey key) -{ - DN_U32 result = 0; - if (!map) - return result; - - if (key.type == DN_DSMapKeyType_U64NoHash) { - result = DN_Cast(DN_U32) key.u64; - return result; - } - - if (key.type == DN_DSMapKeyType_BufferAsU64NoHash) { - result = key.hash; - return result; - } - - DN_U32 seed = map->hash_seed ? map->hash_seed : DN_DS_MAP_DEFAULT_HASH_SEED; - if (map->hash_function) { - map->hash_function(key, seed); - } else { - // NOTE: Courtesy of Demetri Spanos (which this hash table was inspired - // from), the following is a hashing function snippet provided for - // reliable, quick and simple quality hashing functions for hash table - // use. - // Source: https://github.com/demetri/scribbles/blob/c475464756c104c91bab83ed4e14badefef12ab5/hashing/ub_aware_hash_functions.c - - char const *key_ptr = nullptr; - DN_U32 len = 0; - DN_U32 h = seed; - switch (key.type) { - case DN_DSMapKeyType_BufferAsU64NoHash: /*FALLTHRU*/ - case DN_DSMapKeyType_U64NoHash: DN_AssertInvalidCodePath; /*FALLTHRU*/ - case DN_DSMapKeyType_Invalid: break; - - case DN_DSMapKeyType_Buffer: - key_ptr = DN_Cast(char const *) key.buffer_data; - len = key.buffer_size; - break; - - case DN_DSMapKeyType_U64: - key_ptr = DN_Cast(char const *) & key.u64; - len = sizeof(key.u64); - break; - } - - // Murmur3 32-bit without UB unaligned accesses - // DN_U32 mur3_32_no_UB(const void *key, int len, DN_U32 h) - - // main body, work on 32-bit blocks at a time - for (DN_U32 i = 0; i < len / 4; i++) { - DN_U32 k; - memcpy(&k, &key_ptr[i * 4], sizeof(k)); - - k *= 0xcc9e2d51; - k = ((k << 15) | (k >> 17)) * 0x1b873593; - h = (((h ^ k) << 13) | ((h ^ k) >> 19)) * 5 + 0xe6546b64; - } - - // load/mix up to 3 remaining tail bytes into a tail block - DN_U32 t = 0; - uint8_t *tail = ((uint8_t *)key_ptr) + 4 * (len / 4); - switch (len & 3) { - case 3: t ^= tail[2] << 16; - case 2: t ^= tail[1] << 8; - case 1: { - t ^= tail[0] << 0; - h ^= ((0xcc9e2d51 * t << 15) | (0xcc9e2d51 * t >> 17)) * 0x1b873593; - } - } - - // finalization mix, including key length - h = ((h ^ len) ^ ((h ^ len) >> 16)) * 0x85ebca6b; - h = (h ^ (h >> 13)) * 0xc2b2ae35; - result = h ^ (h >> 16); - } - return result; -} - -template -DN_U32 DN_DSMapHashToSlotIndex(DN_DSMap const *map, DN_DSMapKey key) -{ - DN_Assert(key.type != DN_DSMapKeyType_Invalid); - DN_U32 result = DN_DS_MAP_SENTINEL_SLOT; - if (!DN_DSMapIsValid(map)) - return result; - - result = key.hash & (map->size - 1); - for (;;) { - if (result == DN_DS_MAP_SENTINEL_SLOT) // Sentinel is reserved - result++; - - if (map->hash_to_slot[result] == DN_DS_MAP_SENTINEL_SLOT) // Slot is vacant, can use - return result; - - DN_DSMapSlot *slot = map->slots + map->hash_to_slot[result]; - if (slot->key.type == DN_DSMapKeyType_Invalid || (slot->key.hash == key.hash && slot->key == key)) - return result; - - result = (result + 1) & (map->size - 1); - } -} - -template -DN_DSMapResult DN_DSMapFind(DN_DSMap const *map, DN_DSMapKey key) -{ - DN_DSMapResult result = {}; - if (DN_DSMapIsValid(map)) { - DN_U32 index = DN_DSMapHashToSlotIndex(map, key); - if (index != DN_DS_MAP_SENTINEL_SLOT && map->hash_to_slot[index] == DN_DS_MAP_SENTINEL_SLOT) { - result.slot = map->slots; // NOTE: Set to sentinel value - } else { - result.slot = map->slots + map->hash_to_slot[index]; - result.found = true; - } - result.value = &result.slot->value; - } - return result; -} - -template -DN_DSMapResult DN_DSMapMake(DN_DSMap *map, DN_DSMapKey key) -{ - DN_DSMapResult result = {}; - if (!DN_DSMapIsValid(map)) - return result; - - DN_U32 index = DN_DSMapHashToSlotIndex(map, key); - if (map->hash_to_slot[index] == DN_DS_MAP_SENTINEL_SLOT) { - // NOTE: Create the slot - if (index != DN_DS_MAP_SENTINEL_SLOT) - map->hash_to_slot[index] = map->occupied++; - - // NOTE: Check if resize is required - bool map_is_75pct_full = (map->occupied * 4) > (map->size * 3); - if (map_is_75pct_full) { - if (!DN_DSMapResize(map, map->size * 2)) - return result; - result = DN_DSMapMake(map, key); - } else { - result.slot = map->slots + map->hash_to_slot[index]; - result.slot->key = key; // NOTE: Assign key to new slot - if ((key.type == DN_DSMapKeyType_Buffer || - key.type == DN_DSMapKeyType_BufferAsU64NoHash) && - !key.no_copy_buffer) - result.slot->key.buffer_data = DN_PoolNewArrayCopy(&map->pool, char, key.buffer_data, key.buffer_size); - } - } else { - result.slot = map->slots + map->hash_to_slot[index]; - result.found = true; - } - - result.value = &result.slot->value; - DN_Assert(result.slot->key.type != DN_DSMapKeyType_Invalid); - return result; -} - -template -DN_DSMapResult DN_DSMapSet(DN_DSMap *map, DN_DSMapKey key, T const &value) -{ - DN_DSMapResult result = {}; - if (!DN_DSMapIsValid(map)) - return result; - - result = DN_DSMapMake(map, key); - result.slot->value = value; - return result; -} - -template -DN_DSMapResult DN_DSMapFindKeyU64(DN_DSMap const *map, DN_U64 key) -{ - DN_DSMapKey map_key = DN_DSMapKeyU64(map, key); - DN_DSMapResult result = DN_DSMapFind(map, map_key); - return result; -} - -template -DN_DSMapResult DN_DSMapMakeKeyU64(DN_DSMap *map, DN_U64 key) -{ - DN_DSMapKey map_key = DN_DSMapKeyU64(map, key); - DN_DSMapResult result = DN_DSMapMake(map, map_key); - return result; -} - -template -DN_DSMapResult DN_DSMapSetKeyU64(DN_DSMap *map, DN_U64 key, T const &value) -{ - DN_DSMapKey map_key = DN_DSMapKeyU64(map, key); - DN_DSMapResult result = DN_DSMapSet(map, map_key, value); - return result; -} - -template -DN_DSMapResult DN_DSMapFindKeyStr8(DN_DSMap const *map, DN_Str8 key) -{ - DN_DSMapKey map_key = DN_DSMapKeyStr8(map, key); - DN_DSMapResult result = DN_DSMapFind(map, map_key); - return result; -} - -template -DN_DSMapResult DN_DSMapMakeKeyStr8(DN_DSMap *map, DN_Str8 key) -{ - DN_DSMapKey map_key = DN_DSMapKeyStr8(map, key); - DN_DSMapResult result = DN_DSMapMake(map, map_key); - return result; -} - -template -DN_DSMapResult DN_DSMapSetKeyStr8(DN_DSMap *map, DN_Str8 key, T const &value) -{ - DN_DSMapKey map_key = DN_DSMapKeyStr8(map, key); - DN_DSMapResult result = DN_DSMapSet(map, map_key); - return result; -} - -template -bool DN_DSMapResize(DN_DSMap *map, DN_U32 size) -{ - if (!DN_DSMapIsValid(map) || size < map->occupied || size < map->initial_size) - return false; - - DN_Arena *prev_arena = map->arena; - DN_MemList *new_mem = prev_arena->mem; - DN_MemList prev_mem = *prev_arena->mem; - prev_arena->mem = &prev_mem; - - *new_mem = {}; - new_mem->funcs = prev_mem.funcs; - new_mem->flags = prev_mem.flags; - - DN_Arena new_arena = {}; - new_arena.mem = new_mem; - - DN_DSMap new_map = DN_DSMapInit(&new_arena, size, map->flags); - if (!DN_DSMapIsValid(&new_map)) - return false; - - new_map.initial_size = map->initial_size; - for (DN_U32 old_index = 1 /*Sentinel*/; old_index < map->occupied; old_index++) { - DN_DSMapSlot *old_slot = map->slots + old_index; - DN_DSMapKey old_key = old_slot->key; - if (old_key.type == DN_DSMapKeyType_Invalid) - continue; - DN_DSMapSet(&new_map, old_key, old_slot->value); - } - - if ((map->flags & DN_DSMapFlags_DontFreeArenaOnResize) == 0) - DN_DSMapDeinit(map, DN_ZMem_No); - *map = new_map; // Update the map inplace - map->arena = prev_arena; // Restore the previous arena pointer, it's been de-init-ed - *map->arena = new_arena; // Re-init the old arena with the new data - map->pool.arena = map->arena; - return true; -} - -template -bool DN_DSMapErase(DN_DSMap *map, DN_DSMapKey key) -{ - if (!DN_DSMapIsValid(map)) - return false; - - DN_U32 index = DN_DSMapHashToSlotIndex(map, key); - if (index == 0) - return true; - - DN_U32 slot_index = map->hash_to_slot[index]; - if (slot_index == DN_DS_MAP_SENTINEL_SLOT) - return false; - - // NOTE: Mark the slot as unoccupied - map->hash_to_slot[index] = DN_DS_MAP_SENTINEL_SLOT; - - DN_DSMapSlot *slot = map->slots + slot_index; - if (!slot->key.no_copy_buffer) - DN_PoolDealloc(&map->pool, DN_Cast(void *) slot->key.buffer_data); - *slot = {}; // TODO: Optional? - - if (map->occupied > 1 /*Sentinel*/) { - // NOTE: Repair the hash chain, e.g. rehash all the items after the removed - // element and reposition them if necessary. - for (DN_U32 probe_index = index;;) { - probe_index = (probe_index + 1) & (map->size - 1); - if (map->hash_to_slot[probe_index] == DN_DS_MAP_SENTINEL_SLOT) - break; - - DN_DSMapSlot *probe = map->slots + map->hash_to_slot[probe_index]; - DN_U32 new_index = probe->key.hash & (map->size - 1); - if (index <= probe_index) { - if (index < new_index && new_index <= probe_index) - continue; - } else { - if (index < new_index || new_index <= probe_index) - continue; - } - - map->hash_to_slot[index] = map->hash_to_slot[probe_index]; - map->hash_to_slot[probe_index] = DN_DS_MAP_SENTINEL_SLOT; - index = probe_index; - } - - // NOTE: We have erased a slot from the hash table, this leaves a gap - // in our contiguous array. After repairing the chain, the hash mapping - // is correct. - // We will now fill in the vacant spot that we erased using the last - // element in the slot list. - if (map->occupied >= 3 /*Ignoring sentinel, at least 2 other elements to unstable erase*/) { - DN_U32 last_index = map->occupied - 1; - if (last_index != slot_index) { - // NOTE: Copy in last slot to the erase slot - DN_DSMapSlot *last_slot = map->slots + last_index; - map->slots[slot_index] = *last_slot; - - // NOTE: Update the hash-to-slot mapping for the value that was copied in - DN_U32 hash_to_slot_index = DN_DSMapHashToSlotIndex(map, last_slot->key); - map->hash_to_slot[hash_to_slot_index] = slot_index; - *last_slot = {}; // TODO: Optional? - } - } - } - - map->occupied--; - bool map_is_below_25pct_full = (map->occupied * 4) < (map->size * 1); - if (map_is_below_25pct_full && (map->size / 2) >= map->initial_size) - DN_DSMapResize(map, map->size / 2); - - return true; -} - -template -bool DN_DSMapEraseKeyU64(DN_DSMap *map, DN_U64 key) -{ - DN_DSMapKey map_key = DN_DSMapKeyU64(map, key); - bool result = DN_DSMapErase(map, map_key); - return result; -} - -template -bool DN_DSMapEraseKeyStr8(DN_DSMap *map, DN_Str8 key) -{ - DN_DSMapKey map_key = DN_DSMapKeyStr8(map, key); - bool result = DN_DSMapErase(map, map_key); - return result; -} - -template -DN_DSMapKey DN_DSMapKeyBuffer(DN_DSMap const *map, void const *data, DN_USize size) -{ - DN_Assert(size > 0 && size <= UINT32_MAX); - DN_DSMapKey result = {}; - result.type = DN_DSMapKeyType_Buffer; - result.buffer_data = data; - result.buffer_size = DN_Cast(DN_U32) size; - result.hash = DN_DSMapHash(map, result); - return result; -} - -template -DN_DSMapKey DN_DSMapKeyBufferAsU64NoHash(DN_DSMap const *map, void const *data, DN_USize size) -{ - DN_DSMapKey result = {}; - result.type = DN_DSMapKeyType_BufferAsU64NoHash; - result.buffer_data = data; - result.buffer_size = DN_Cast(DN_U32) size; - DN_Assert(size >= sizeof(result.hash)); - DN_Memcpy(&result.hash, data, sizeof(result.hash)); - return result; -} - -template -DN_DSMapKey DN_DSMapKeyU64(DN_DSMap const *map, DN_U64 u64) -{ - DN_DSMapKey result = {}; - result.type = DN_DSMapKeyType_U64; - result.u64 = u64; - result.hash = DN_DSMapHash(map, result); - return result; -} - -template -DN_DSMapKey DN_DSMapKeyStr8(DN_DSMap const *map, DN_Str8 string) -{ - DN_DSMapKey result = DN_DSMapKeyBuffer(map, string.data, string.size); - return result; -} - -// NOTE: DN_DSMap -DN_API DN_DSMapKey DN_DSMapKeyU64NoHash(DN_U64 u64) -{ - DN_DSMapKey result = {}; - result.type = DN_DSMapKeyType_U64NoHash; - result.u64 = u64; - result.hash = DN_Cast(DN_U32) u64; - return result; -} - -DN_API bool DN_DSMapKeyEquals(DN_DSMapKey lhs, DN_DSMapKey rhs) -{ - bool result = false; - if (lhs.type == rhs.type && lhs.hash == rhs.hash) { - switch (lhs.type) { - case DN_DSMapKeyType_Invalid: result = true; break; - case DN_DSMapKeyType_U64NoHash: result = true; break; - case DN_DSMapKeyType_U64: result = lhs.u64 == rhs.u64; break; - - case DN_DSMapKeyType_BufferAsU64NoHash: /*FALLTHRU*/ - case DN_DSMapKeyType_Buffer: { - if (lhs.buffer_size == rhs.buffer_size) - result = DN_Memcmp(lhs.buffer_data, rhs.buffer_data, lhs.buffer_size) == 0; - } break; - } - } - return result; -} - -DN_API bool operator==(DN_DSMapKey lhs, DN_DSMapKey rhs) -{ - bool result = DN_DSMapKeyEquals(lhs, rhs); - return result; -} -#endif // defined(__cplusplus) - -DN_API void DN_BinPackU64(DN_BinPack *pack, DN_BinPackMode mode, DN_U64 *item) -{ - DN_U64 const VALUE_MASK = 0b0111'1111; - DN_U8 const CONTINUE_BIT = 0b1000'0000; - - if (mode == DN_BinPackMode_Serialise) { - DN_U64 it = *item; - do { - DN_U8 write_value = DN_Cast(DN_U8)(it & VALUE_MASK); - it >>= 7; - if (it) - write_value |= CONTINUE_BIT; - DN_Str8BuilderAppendBytesCopy(&pack->writer, &write_value, sizeof(write_value)); - } while (it); - } else { - *item = 0; - DN_USize bits_read = 0; - for (DN_U8 src = CONTINUE_BIT; (src & CONTINUE_BIT) && bits_read < 64; bits_read += 7) { - src = pack->read.data[pack->read_index++]; - DN_U8 masked_src = src & VALUE_MASK; - *item |= (DN_Cast(DN_U64) masked_src << bits_read); - } - } -} - -DN_API void DN_BinPackVarInt_(DN_BinPack *pack, DN_BinPackMode mode, void *item, DN_USize size) -{ - DN_U64 value = 0; - DN_AssertF(size <= sizeof(value), - "An item larger than 64 bits (%zu) is trying to be packed as a variable integer which is not supported", - size * 8); - - if (mode == DN_BinPackMode_Serialise) // Read `item` into U64 `value` - DN_Memcpy(&value, item, size); - - DN_BinPackU64(pack, mode, &value); - - if (mode == DN_BinPackMode_Deserialise) // Write U64 `value` into `item` - DN_Memcpy(item, &value, size); -} - -DN_API bool DN_BinPackIsEndOfReadStream(DN_BinPack const *pack) -{ - bool result = pack->read_index == pack->read.size; - return result; -} - -DN_API void DN_BinPackUSize(DN_BinPack *pack, DN_BinPackMode mode, DN_USize *item) -{ - DN_BinPackVarInt_(pack, mode, item, sizeof(*item)); -} - -DN_API void DN_BinPackU32(DN_BinPack *pack, DN_BinPackMode mode, DN_U32 *item) -{ - DN_BinPackVarInt_(pack, mode, item, sizeof(*item)); -} - -DN_API void DN_BinPackU16(DN_BinPack *pack, DN_BinPackMode mode, DN_U16 *item) -{ - DN_BinPackVarInt_(pack, mode, item, sizeof(*item)); -} - -DN_API void DN_BinPackU8(DN_BinPack *pack, DN_BinPackMode mode, DN_U8 *item) -{ - DN_BinPackVarInt_(pack, mode, item, sizeof(*item)); -} - -DN_API void DN_BinPackI64(DN_BinPack *pack, DN_BinPackMode mode, DN_I64 *item) -{ - DN_BinPackVarInt_(pack, mode, item, sizeof(*item)); -} - -DN_API void DN_BinPackI32(DN_BinPack *pack, DN_BinPackMode mode, DN_I32 *item) -{ - DN_BinPackVarInt_(pack, mode, item, sizeof(*item)); -} - -DN_API void DN_BinPackI16(DN_BinPack *pack, DN_BinPackMode mode, DN_I16 *item) -{ - DN_BinPackVarInt_(pack, mode, item, sizeof(*item)); -} - -DN_API void DN_BinPackI8(DN_BinPack *pack, DN_BinPackMode mode, DN_I8 *item) -{ - DN_BinPackVarInt_(pack, mode, item, sizeof(*item)); -} - -DN_API void DN_BinPackF64(DN_BinPack *pack, DN_BinPackMode mode, DN_F64 *item) -{ - DN_BinPackVarInt_(pack, mode, item, sizeof(*item)); -} - -DN_API void DN_BinPackF32(DN_BinPack *pack, DN_BinPackMode mode, DN_F32 *item) -{ - DN_BinPackVarInt_(pack, mode, item, sizeof(*item)); -} - -DN_API void DN_BinPackV2(DN_BinPack *pack, DN_BinPackMode mode, DN_V2F32 *item) -{ - DN_BinPackF32(pack, mode, &item->x); - DN_BinPackF32(pack, mode, &item->y); -} - -DN_API void DN_BinPackV4(DN_BinPack *pack, DN_BinPackMode mode, DN_V4F32 *item) -{ - DN_BinPackF32(pack, mode, &item->x); - DN_BinPackF32(pack, mode, &item->y); - DN_BinPackF32(pack, mode, &item->z); - DN_BinPackF32(pack, mode, &item->w); -} - -DN_API void DN_BinPackBool(DN_BinPack *pack, DN_BinPackMode mode, bool *item) -{ - DN_BinPackVarInt_(pack, mode, item, sizeof(*item)); -} - -DN_API void DN_BinPackStr8FromArena(DN_BinPack *pack, DN_Arena *arena, DN_BinPackMode mode, DN_Str8 *string) -{ - DN_BinPackVarInt_(pack, mode, &string->size, sizeof(string->size)); - if (mode == DN_BinPackMode_Serialise) { - DN_Str8BuilderAppendBytesCopy(&pack->writer, string->data, string->size); - } else { - DN_Str8 src = DN_Str8Subset(pack->read, pack->read_index, string->size); - *string = DN_Str8FromStr8Arena(src, arena); - pack->read_index += src.size; - } -} - -DN_API void DN_BinPackStr8FromPool(DN_BinPack *pack, DN_Pool *pool, DN_BinPackMode mode, DN_Str8 *string) -{ - DN_BinPackVarInt_(pack, mode, &string->size, sizeof(string->size)); - if (mode == DN_BinPackMode_Serialise) { - DN_Str8BuilderAppendBytesCopy(&pack->writer, string->data, string->size); - } else { - DN_Str8 src = DN_Str8Subset(pack->read, pack->read_index, string->size); - *string = DN_Str8FromStr8Pool(src, pool); - pack->read_index += src.size; - } -} - -DN_API DN_Str8 DN_BinPackStr8FromBuffer(DN_BinPack *pack, DN_BinPackMode mode, char *ptr, DN_USize *size, DN_USize max) -{ - DN_BinPackCBuffer(pack, mode, ptr, size, max); - DN_Str8 result = DN_Str8FromPtr(ptr, *size); - return result; -} - -DN_API void DN_BinPackBytesFromArena(DN_BinPack *pack, DN_Arena *arena, DN_BinPackMode mode, void **ptr, DN_USize *size) -{ - DN_Str8 string = DN_Str8FromPtr(*ptr, *size); - DN_BinPackStr8FromArena(pack, arena, mode, &string); - *ptr = string.data; - *size = string.size; -} - -DN_API void DN_BinPackBytesFromPool(DN_BinPack *pack, DN_Pool *pool, DN_BinPackMode mode, void **ptr, DN_USize *size) -{ - DN_Str8 string = DN_Str8FromPtr(*ptr, *size); - DN_BinPackStr8FromPool(pack, pool, mode, &string); - *ptr = string.data; - *size = string.size; -} - -DN_API void DN_BinPackCArray(DN_BinPack *pack, DN_BinPackMode mode, void *ptr, DN_USize size) -{ - DN_BinPackVarInt_(pack, mode, &size, sizeof(size)); - if (mode == DN_BinPackMode_Serialise) { - DN_Str8BuilderAppendBytesCopy(&pack->writer, ptr, size); - } else { - DN_Str8 src = DN_Str8Subset(pack->read, pack->read_index, size); - DN_Assert(src.size == size); - DN_Memcpy(ptr, src.data, DN_Min(src.size, size)); - pack->read_index += src.size; - } -} - -DN_API void DN_BinPackCBuffer(DN_BinPack *pack, DN_BinPackMode mode, char *ptr, DN_USize *size, DN_USize max) -{ - if (mode == DN_BinPackMode_Serialise) { - DN_BinPackUSize(pack, mode, size); - DN_Str8BuilderAppendBytesCopy(&pack->writer, ptr, *size); - } else { - DN_U64 size_u64 = 0; - DN_BinPackU64(pack, mode, &size_u64); - DN_Assert(size_u64 < DN_USIZE_MAX); - DN_Assert(size_u64 <= max); - - *size = DN_Min(size_u64, max); - DN_Memcpy(ptr, pack->read.data + pack->read_index, *size); - pack->read_index += size_u64; - } -} - -DN_API DN_Str8 DN_BinPackBuild(DN_BinPack const *pack, DN_Arena *arena) -{ - DN_Str8 result = DN_Str8FromStr8BuilderArena(&pack->writer, arena); - return result; -} - -DN_API DN_CSVTokeniser DN_CSVTokeniserInit(DN_Str8 string, char delimiter) -{ - DN_CSVTokeniser result = {}; - result.string = string; - result.delimiter = delimiter; - return result; -} - -DN_API bool DN_CSVTokeniserValid(DN_CSVTokeniser *tokeniser) -{ - bool result = tokeniser && !tokeniser->bad; - return result; -} - -static void DN_CSVTokeniserEatNewLines_(DN_CSVTokeniser *tokeniser) -{ - char const *end = tokeniser->string.data + tokeniser->string.size; - while (tokeniser->it[0] == '\n' || tokeniser->it[0] == '\r') - if (++tokeniser->it == end) - break; -} - -DN_API bool DN_CSVTokeniserNextRow(DN_CSVTokeniser *tokeniser) -{ - bool result = false; - if (DN_CSVTokeniserValid(tokeniser) && tokeniser->string.size) { - // NOTE: First time querying row iterator is nil, let tokeniser advance - if (tokeniser->it) { - // NOTE: Only advance the tokeniser if we're at the end of the line and - // there's more to tokenise. - char const *end = tokeniser->string.data + tokeniser->string.size; - if (tokeniser->it != end && tokeniser->end_of_line) { - tokeniser->end_of_line = false; - result = true; - } - } - } - - return result; -} - -DN_API DN_Str8 DN_CSVTokeniserNextField(DN_CSVTokeniser *tokeniser) -{ - DN_Str8 result = {}; - if (!DN_CSVTokeniserValid(tokeniser)) - return result; - - if (tokeniser->string.size == 0) { - tokeniser->bad = true; - return result; - } - - // NOTE: First time tokeniser is invoked with a string, set up initial state. - char const *string_end = tokeniser->string.data + tokeniser->string.size; - if (!tokeniser->it) { - tokeniser->it = tokeniser->string.data; - DN_CSVTokeniserEatNewLines_(tokeniser); // NOTE: Skip any leading new lines - } - - // NOTE: Tokeniser pointing at end, no more valid data to parse. - if (tokeniser->it == string_end) - return result; - - // NOTE: Scan forward until the next control character. - // 1. '"' Double quoted field, extract everything between the quotes. - // 2. tokeniser->delimiter End of the field, extract everything leading up to the delimiter. - // 3. '\n' Last field in record, extract everything leading up the the new line. - char const *begin = tokeniser->it; - while (tokeniser->it != string_end && (tokeniser->it[0] != '"' && - tokeniser->it[0] != tokeniser->delimiter && - tokeniser->it[0] != '\n')) - tokeniser->it++; - - bool quoted_field = (tokeniser->it != string_end) && tokeniser->it[0] == '"'; - if (quoted_field) { - begin = ++tokeniser->it; // Begin after the quote - - // NOTE: Scan forward until the next '"' which marks the end - // of the field unless it is escaped by another '"'. - find_next_quote: - while (tokeniser->it != string_end && tokeniser->it[0] != '"') - tokeniser->it++; - - // NOTE: If we encounter a '"' right after, the quotes were escaped - // and we need to skip to the next instance of a '"'. - if (tokeniser->it != string_end && tokeniser->it + 1 != string_end && tokeniser->it[1] == '"') { - tokeniser->it += 2; - goto find_next_quote; - } - } - - // NOTE: Mark the end of the field - char const *end = tokeniser->it; - tokeniser->end_of_line = tokeniser->it == string_end || end[0] == '\n'; - - // NOTE: In files with \r\n style new lines ensure that we don't include - // the \r byte in the CSV field we produce. - if (end != string_end && end[0] == '\n') { - DN_Assert((uintptr_t)(end - 1) > (uintptr_t)tokeniser->string.data && - "Internal error: The string iterator is pointing behind the start of the string we're reading"); - if (end[-1] == '\r') - end = end - 1; - } - - // NOTE: Quoted fields may have whitespace after the closing quote, we skip - // until we reach the field terminator. - if (quoted_field) - while (tokeniser->it != string_end && (tokeniser->it[0] != tokeniser->delimiter && tokeniser->it[0] != '\n')) - tokeniser->it++; - - // NOTE: Advance the tokeniser past the field terminator. - if (tokeniser->it != string_end) - tokeniser->it++; - - // NOTE: Generate the record - result.data = DN_Cast(char *) begin; - result.size = DN_Cast(int)(end - begin); - return result; -} - -DN_API DN_Str8 DN_CSVTokeniserNextColumn(DN_CSVTokeniser *tokeniser) -{ - DN_Str8 result = {}; - if (!DN_CSVTokeniserValid(tokeniser)) - return result; - - // NOTE: End of line, the user must explicitly advance to the next row - if (tokeniser->end_of_line) - return result; - - // NOTE: Advance tokeniser to the next field in the row - result = DN_CSVTokeniserNextField(tokeniser); - return result; -} - -DN_API void DN_CSVTokeniserSkipLine(DN_CSVTokeniser *tokeniser) -{ - while (DN_CSVTokeniserValid(tokeniser) && !tokeniser->end_of_line) - DN_CSVTokeniserNextColumn(tokeniser); - DN_CSVTokeniserNextRow(tokeniser); -} - -DN_API int DN_CSVTokeniserNextN(DN_CSVTokeniser *tokeniser, DN_Str8 *fields, int fields_size, bool column_iterator) -{ - if (!DN_CSVTokeniserValid(tokeniser) || !fields || fields_size <= 0) - return 0; - - int result = 0; - for (; result < fields_size; result++) { - fields[result] = column_iterator ? DN_CSVTokeniserNextColumn(tokeniser) : DN_CSVTokeniserNextField(tokeniser); - if (!DN_CSVTokeniserValid(tokeniser) || !fields[result].data) - break; - } - - return result; -} - -DN_API int DN_CSVTokeniserNextColumnN(DN_CSVTokeniser *tokeniser, DN_Str8 *fields, int fields_size) -{ - int result = DN_CSVTokeniserNextN(tokeniser, fields, fields_size, true /*column_iterator*/); - return result; -} - -DN_API int DN_CSVTokeniserNextFieldN(DN_CSVTokeniser *tokeniser, DN_Str8 *fields, int fields_size) -{ - int result = DN_CSVTokeniserNextN(tokeniser, fields, fields_size, false /*column_iterator*/); - return result; -} - -DN_API void DN_CSVTokeniserSkipLineN(DN_CSVTokeniser *tokeniser, int count) -{ - for (int i = 0; i < count && DN_CSVTokeniserValid(tokeniser); i++) - DN_CSVTokeniserSkipLine(tokeniser); -} - -DN_API void DN_CSVPackU64(DN_CSVPack *pack, DN_CSVSerialise serialise, DN_U64 *value) -{ - if (serialise == DN_CSVSerialise_Read) { - DN_Str8 csv_value = DN_CSVTokeniserNextColumn(&pack->read_tokeniser); - DN_U64FromResult to_u64 = DN_U64FromStr8(csv_value, 0); - DN_Assert(to_u64.success); - *value = to_u64.value; - } else { - DN_Str8BuilderAppendF(&pack->write_builder, "%s%I64u", pack->write_column++ ? "," : "", *value); - } -} - -DN_API void DN_CSVPackI64(DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I64 *value) -{ - if (serialise == DN_CSVSerialise_Read) { - DN_Str8 csv_value = DN_CSVTokeniserNextColumn(&pack->read_tokeniser); - DN_I64FromResult to_i64 = DN_I64FromStr8(csv_value, 0); - DN_Assert(to_i64.success); - *value = to_i64.value; - } else { - DN_Str8BuilderAppendF(&pack->write_builder, "%s%I64d", pack->write_column++ ? "," : "", *value); - } -} - -DN_API void DN_CSVPackI32(DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I32 *value) -{ - DN_I64 u64 = *value; - DN_CSVPackI64(pack, serialise, &u64); - if (serialise == DN_CSVSerialise_Read) - *value = DN_SaturateCastI64ToI32(u64); -} - -DN_API void DN_CSVPackI16(DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I16 *value) -{ - DN_I64 u64 = *value; - DN_CSVPackI64(pack, serialise, &u64); - if (serialise == DN_CSVSerialise_Read) - *value = DN_SaturateCastI64ToI16(u64); -} - -DN_API void DN_CSVPackI8(DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I8 *value) -{ - DN_I64 u64 = *value; - DN_CSVPackI64(pack, serialise, &u64); - if (serialise == DN_CSVSerialise_Read) - *value = DN_SaturateCastI64ToI8(u64); -} - -DN_API void DN_CSVPackU32(DN_CSVPack *pack, DN_CSVSerialise serialise, DN_U32 *value) -{ - DN_U64 u64 = *value; - DN_CSVPackU64(pack, serialise, &u64); - if (serialise == DN_CSVSerialise_Read) - *value = DN_SaturateCastU64ToU32(u64); -} - -DN_API void DN_CSVPackU16(DN_CSVPack *pack, DN_CSVSerialise serialise, DN_U16 *value) -{ - DN_U64 u64 = *value; - DN_CSVPackU64(pack, serialise, &u64); - if (serialise == DN_CSVSerialise_Read) - *value = DN_SaturateCastU64ToU16(u64); -} - -DN_API void DN_CSVPackBoolAsU64(DN_CSVPack *pack, DN_CSVSerialise serialise, bool *value) -{ - DN_U64 u64 = *value; - DN_CSVPackU64(pack, serialise, &u64); - if (serialise == DN_CSVSerialise_Read) - *value = u64 ? 1 : 0; -} - -DN_API void DN_CSVPackStr8(DN_CSVPack *pack, DN_CSVSerialise serialise, DN_Str8 *str8, DN_Arena *arena) -{ - if (serialise == DN_CSVSerialise_Read) { - DN_Str8 csv_value = DN_CSVTokeniserNextColumn(&pack->read_tokeniser); - *str8 = DN_Str8FromStr8Arena(csv_value, arena); - } else { - DN_Str8BuilderAppendF(&pack->write_builder, "%s%.*s", pack->write_column++ ? "," : "", DN_Str8PrintFmt(*str8)); - } -} - -DN_API void DN_CSVPackBuffer(DN_CSVPack *pack, DN_CSVSerialise serialise, void *dest, DN_USize *size) -{ - if (serialise == DN_CSVSerialise_Read) { - DN_Str8 csv_value = DN_CSVTokeniserNextColumn(&pack->read_tokeniser); - *size = DN_Min(*size, csv_value.size); - DN_Memcpy(dest, csv_value.data, *size); - } else { - DN_Str8BuilderAppendF(&pack->write_builder, "%s%.*s", pack->write_column++ ? "," : "", DN_Cast(int)(*size), DN_Cast(char *)dest); - } -} - -DN_API void DN_CSVPackBufferWithMax(DN_CSVPack *pack, DN_CSVSerialise serialise, void *dest, DN_USize *size, DN_USize max) -{ - if (serialise == DN_CSVSerialise_Read) - *size = max; - DN_CSVPackBuffer(pack, serialise, dest, size); -} - -DN_API bool DN_CSVPackNewLine(DN_CSVPack *pack, DN_CSVSerialise serialise) -{ - bool result = true; - if (serialise == DN_CSVSerialise_Read) { - result = DN_CSVTokeniserNextRow(&pack->read_tokeniser); - } else { - pack->write_column = 0; - result = DN_Str8BuilderAppendRef(&pack->write_builder, DN_Str8Lit("\n")); - } - return result; -} - -DN_API void DN_LeakTrackAlloc_(DN_LeakTracker *leak, void *ptr, DN_USize size, bool leak_permitted) -{ - if (!ptr) - return; - - DN_TicketMutex_Begin(&leak->alloc_table_mutex); - - DN_Str8 stack_trace = DN_Str8FromStackTraceNowHeap(128, 3 /*skip*/); - DN_DSMap *alloc_table = &leak->alloc_table; - DN_DSMapResult alloc_entry = DN_DSMapMakeKeyU64(alloc_table, DN_Cast(DN_U64) ptr); - DN_LeakAlloc *alloc = alloc_entry.value; - if (alloc_entry.found) { - if ((alloc->flags & DN_LeakAllocFlag_Freed) == 0) { - DN_Str8x32 alloc_size = DN_Str8x32FromByteCountU64Auto(alloc->size); - DN_Str8x32 new_alloc_size = DN_Str8x32FromByteCountU64Auto(size); - DN_AssertAlwaysF( - alloc->flags & DN_LeakAllocFlag_Freed, - "This 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 " - "DN_LeakTrackDealloc()\n" - "\n" - "The pointer (0x%p) originally allocated %.*s at:\n" - "\n" - "%.*s\n" - "\n" - "The pointer is allocating %.*s again at:\n" - "\n" - "%.*s\n", - ptr, - DN_Str8PrintFmt(alloc_size), - DN_Str8PrintFmt(alloc->stack_trace), - DN_Str8PrintFmt(new_alloc_size), - DN_Str8PrintFmt(stack_trace)); - } - - // NOTE: Pointer was reused, clean up the prior entry - leak->alloc_table_bytes_allocated_for_stack_traces -= alloc->stack_trace.size; - leak->alloc_table_bytes_allocated_for_stack_traces -= alloc->freed_stack_trace.size; - - DN_OS_MemDealloc(alloc->stack_trace.data); - DN_OS_MemDealloc(alloc->freed_stack_trace.data); - *alloc = {}; - } - - alloc->ptr = ptr; - alloc->size = size; - alloc->stack_trace = stack_trace; - alloc->flags |= leak_permitted ? DN_LeakAllocFlag_LeakPermitted : 0; - leak->alloc_table_bytes_allocated_for_stack_traces += alloc->stack_trace.size; - DN_TicketMutex_End(&leak->alloc_table_mutex); -} - -DN_API void DN_LeakTrackDealloc_(DN_LeakTracker *leak, void *ptr) -{ - if (!ptr) - return; - - DN_TicketMutex_Begin(&leak->alloc_table_mutex); - - DN_Str8 stack_trace = DN_Str8FromStackTraceNowHeap(128, 3 /*skip*/); - DN_DSMap *alloc_table = &leak->alloc_table; - DN_DSMapResult alloc_entry = DN_DSMapFindKeyU64(alloc_table, DN_Cast(uintptr_t) ptr); - DN_AssertAlwaysF(alloc_entry.found, - "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); - - DN_LeakAlloc *alloc = alloc_entry.value; - if (alloc->flags & DN_LeakAllocFlag_Freed) { - DN_Str8x32 freed_size = DN_Str8x32FromByteCountU64Auto(alloc->freed_size); - DN_AssertAlwaysF((alloc->flags & DN_LeakAllocFlag_Freed) == 0, - "Double 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 %.*s at:\n" - "\n" - "%.*s\n" - "\n" - "The pointer was freed at:\n" - "\n" - "%.*s\n" - "\n" - "The pointer is being freed again at:\n" - "\n" - "%.*s\n", - ptr, - DN_Str8PrintFmt(freed_size), - DN_Str8PrintFmt(alloc->stack_trace), - DN_Str8PrintFmt(alloc->freed_stack_trace), - DN_Str8PrintFmt(stack_trace)); - } - - DN_Assert(alloc->freed_stack_trace.size == 0); - alloc->flags |= DN_LeakAllocFlag_Freed; - alloc->freed_stack_trace = stack_trace; - leak->alloc_table_bytes_allocated_for_stack_traces += alloc->freed_stack_trace.size; - DN_TicketMutex_End(&leak->alloc_table_mutex); -} - -DN_API void DN_LeakDump_(DN_LeakTracker *leak) -{ - DN_U64 leak_count = 0; - DN_U64 leaked_bytes = 0; - for (DN_USize index = 1; index < leak->alloc_table.occupied; index++) { - DN_DSMapSlot *slot = leak->alloc_table.slots + index; - DN_LeakAlloc *alloc = &slot->value; - bool alloc_leaked = (alloc->flags & DN_LeakAllocFlag_Freed) == 0; - bool leak_permitted = (alloc->flags & DN_LeakAllocFlag_LeakPermitted); - if (alloc_leaked && !leak_permitted) { - leaked_bytes += alloc->size; - leak_count++; - DN_Str8x32 alloc_size = DN_Str8x32FromByteCountU64Auto(alloc->size); - DN_LogWarningF( - "Pointer (0x%p) leaked %.*s at:\n" - "%.*s", - alloc->ptr, - DN_Str8PrintFmt(alloc_size), - DN_Str8PrintFmt(alloc->stack_trace)); - } - } - - if (leak_count) { - DN_Str8x32 leak_size = DN_Str8x32FromByteCountU64Auto(leaked_bytes); - DN_LogWarningF("There were %I64u leaked allocations totalling %.*s", leak_count, DN_Str8PrintFmt(leak_size)); - } -} diff --git a/Source/Base/dn_base.h b/Source/Base/dn_base.h deleted file mode 100644 index bdbd67b..0000000 --- a/Source/Base/dn_base.h +++ /dev/null @@ -1,3521 +0,0 @@ -#if !defined(DN_BASE_H) -#define DN_BASE_H - -#if defined(_CLANGD) - #define DN_STR8_AVX512F 1 - #define DN_PARANOIA_LEVEL 1 - #include "../dn.h" -#endif - -// NOTE: Compiler identification -// NOTE: Warning! Order is important here, clang-cl on Windows defines _MSC_VER -#if defined(_MSC_VER) - #if defined(__clang__) - #define DN_COMPILER_CLANG_CL - #define DN_COMPILER_CLANG - #else - #define DN_COMPILER_MSVC - #endif -#elif defined(__clang__) - #define DN_COMPILER_CLANG -#elif defined(__GNUC__) - #define DN_COMPILER_GCC -#endif - -// NOTE: __has_feature -// NOTE: MSVC for example does not support the feature detection macro for instance so we compile it -// out -#if defined(__has_feature) - #define DN_HAS_FEATURE(expr) __has_feature(expr) -#else - #define DN_HAS_FEATURE(expr) 0 -#endif - -// NOTE: __has_builtin -// NOTE: MSVC for example does not support the feature detection macro for instance so we compile it out -#if defined(__has_builtin) - #define DN_HAS_BUILTIN(expr) __has_builtin(expr) -#else - #define DN_HAS_BUILTIN(expr) 0 -#endif - -// NOTE: Warning suppression macros -#if defined(DN_COMPILER_MSVC) - #define DN_MSVC_WARNING_PUSH __pragma(warning(push)) - #define DN_MSVC_WARNING_DISABLE(...) __pragma(warning(disable :##__VA_ARGS__)) - #define DN_MSVC_WARNING_ENABLE(...) __pragma(warning(default :##__VA_ARGS__)) - #define DN_MSVC_WARNING_POP __pragma(warning(pop)) -#else - #define DN_MSVC_WARNING_PUSH - #define DN_MSVC_WARNING_ENABLE(...) - #define DN_MSVC_WARNING_DISABLE(...) - #define DN_MSVC_WARNING_POP -#endif - -#if defined(DN_COMPILER_CLANG) || defined(DN_COMPILER_GCC) || defined(DN_COMPILER_CLANG_CL) - #define DN_GCC_WARNING_PUSH _Pragma("GCC diagnostic push") - #define DN_GCC_WARNING_DISABLE_HELPER_0(x) #x - #define DN_GCC_WARNING_DISABLE_HELPER_1(y) DN_GCC_WARNING_DISABLE_HELPER_0(GCC diagnostic ignored #y) - #define DN_GCC_WARNING_DISABLE(warning) _Pragma(DN_GCC_WARNING_DISABLE_HELPER_1(warning)) - #define DN_GCC_WARNING_POP _Pragma("GCC diagnostic pop") -#else - #define DN_GCC_WARNING_PUSH - #define DN_GCC_WARNING_DISABLE(...) - #define DN_GCC_WARNING_POP -#endif - -// NOTE: Host OS identification -#if defined(_WIN32) - #define DN_OS_WIN32 -#elif defined(__gnu_linux__) || defined(__linux__) - #define DN_OS_UNIX -#endif - -// NOTE: Platform identification -#if !defined(DN_PLATFORM_EMSCRIPTEN) && \ - !defined(DN_PLATFORM_POSIX) && \ - !defined(DN_PLATFORM_WIN32) - #if defined(__aarch64__) || defined(_M_ARM64) - #define DN_PLATFORM_ARM64 - #elif defined(__EMSCRIPTEN__) - #define DN_PLATFORM_EMSCRIPTEN - #elif defined(DN_OS_WIN32) - #define DN_PLATFORM_WIN32 - #else - #define DN_PLATFORM_POSIX - #endif -#endif - -// NOTE: Windows crap -#if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL) - #if defined(_CRT_SECURE_NO_WARNINGS) - #define DN_CRT_SECURE_NO_WARNINGS_PREVIOUSLY_DEFINED - #else - #define _CRT_SECURE_NO_WARNINGS - #endif -#endif - -// NOTE: Force Inline -#if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL) - #define DN_FORCE_INLINE __forceinline -#else - #define DN_FORCE_INLINE inline __attribute__((always_inline)) -#endif - -// NOTE: Function/Variable Annotations -#if defined(DN_STATIC_API) - #define DN_API static -#else - #define DN_API -#endif - -// NOTE: C/CPP Literals -// 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 = DN_LITERAL(Foo){32}; // Works on both C and C++ -#endif - -#if defined(__cplusplus) - #define DN_Literal(T) T -#else - #define DN_Literal(T) (T) -#endif - -// NOTE: Thread Locals -#if defined(__cplusplus) - #define DN_THREAD_LOCAL thread_local -#else - #define DN_THREAD_LOCAL _Thread_local -#endif - -// NOTE: C variadic argument annotations -// TODO: Other compilers -#if defined(DN_COMPILER_MSVC) - #define DN_FMT_ATTRIB _Printf_format_string_ -#else - #define DN_FMT_ATTRIB -#endif - -// NOTE: Type Cast -#define DN_Cast(val) (val) - -// NOTE: Zero initialisation macro -#if defined(__cplusplus) - #define DN_ZeroInit {} -#else - #define DN_ZeroInit {0} -#endif - -// NOTE: Macros -#define DN_Stringify(x) #x -#define DN_TokenCombine2(x, y) x ## y -#define DN_TokenCombine(x, y) DN_TokenCombine2(x, y) - -// NOTE: Error Checking/Validating -// Asserts are useful to verify invariants in the codebase, but there's sometimes the ambiguous -// question of what should be asserted, what happens when we should have triggered an assert -// in a release build (where they are canonically turned off), what alternative mechanisms should we -// use for error checking that should be visible to non-developers. -// -// The following is an excerpt from Tom Forsyth's assertion article which he references Chris -// Hargrove's guidelines on how asserts show be used. It is quite reasonable and we model our -// primitives after based on those concepts: -// -// Logging, asserts and unit tests (https://tomforsyth1000.github.io/blog.wiki.html -// -// Assert: Immediately fatal, and not ignorable. Fundamental assumption by an engineer has been -// disproven and needs immediate handling. Requires discipline on the part of the engineer to not -// add them in situations that are actually non-fatal (rule of thumb being that if a crash would -// be almost certain to happen anyway due to the same condition, then you’re no worse off making -// an assert). -// -// Errors: Probably fatal soon, but not necessarily immediately. Basically a marker for “you are -// now in a f*cked state, you might limp along a bit, but assume nothing”. Game continues, but an -// ugly red number gets displayed onscreen for how many of these have been encountered (so when -// people send you screenshots of bugs you can then point to the red error count and blame -// accordingly). Savegames are disabled from this point so as not to make the error effectively -// permanent; you should also deliberately violate a few other TCRs as soon as an error is -// encountered in order to ensure that all parties up and down the publisher/developer chain are -// aware of how bad things are. Errors are technically “ignorable” but everyone knows that it -// might only buy you a little bit of borrowed time; these are only a small step away from the -// immediately-blocking nature of an assert, but sometimes that small step can have a big impact -// on productivity. -// -// Warnings: Used for “you did something bad, but we caught it so it’s fine (the game state is -// still okay), however it might not be fine in the future so if you want to save yourself some -// headache you should fix this sooner rather than later”. Great for content problems. Also -// displayed onscreen as a yellow number (near the red error number). You can keep these around -// for a while and triage them when their utility is called into question. -// -// Crumbs: The meta-category for a large number of “verbose” informational breadcrumb categories -// that must be explicitly enabled so you don’t clutter everything up and obscure stuff that -// matters. Note that the occurrance of certain Errors should automatically enable relevant -// categories of crumbs so that more detailed information about the aforementioned f*cked state -// will be provided during the limp-along timeframe. -// -// In the excerpt, their domain (games programming) prioritises continuity over immediate failure -// on warning and error as this allows non-developer clientele to continue using the application -// despite error laden states. This is useful in general as not all failures are critical to the -// use case that the end user is dealing with. -// -// We model `Errors` and `Warnings` as `DN_Verify` and `DN_VerifyWarning` respectively. The verify -// variants check the expression to test, log and a message and allow the developer to branch on the -// result and "recover" where appropriate. Verify checks are never compiled out. We have traditional -// `Asserts` as `DN_Assert` which can be compiled out. -// -// The article also defines what it calls a paranoia level. We `#define DN_PARANOIA_LEVEL ` -// to customise the validation layers of the codebase. See DN_PARANOIA_LEVEL in the customisation -// section for more information. -// -// In summary use each of the primitives in these situation: -// -// `DN_Assert`: Fatal and immediately needs attention and can be compiled out -// -// `DN_Verify`: Fatal or eventually fatal but not necessarily immediately, program is or will -// degenerate into an incorrect state. Is always compiled in and is visible in non-debug -// environments. -// -// `DN_VerifyWarning`: Something bad happened, but we caught it and recovered from it. Program -// state remains consistent. It is always compiled in and is visible in non-debug environments. - -#if !defined(DN_PARANOIA_LEVEL) - #if defined(NDEBUG) - #define DN_PARANOIA_LEVEL 0 - #else - #define DN_PARANOIA_LEVEL 1 - #endif -#endif - -#if DN_HAS_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__) - #include -#endif - -#define DN_ASAN_POISON_ALIGNMENT 8 -#if !defined(DN_ASAN_VET_POISON) - #define DN_ASAN_VET_POISON 0 -#endif - -#if !defined(DN_ASAN_POISON) - #if DN_PARANOIA_LEVEL >= 1 - #define DN_ASAN_POISON 1 - #else - #define DN_ASAN_POISON 0 - #endif -#endif - -#if !defined(DN_ASAN_POISON_GUARD_SIZE) - #define DN_ASAN_POISON_GUARD_SIZE 128 -#endif - -#if !defined(DN_ARENA_TEMP_MEM_UAF_GUARD) - #if DN_PARANOIA_LEVEL >= 1 - #define DN_ARENA_TEMP_MEM_UAF_GUARD 1 - #else - #define DN_ARENA_TEMP_MEM_UAF_GUARD 0 - #endif -#endif - -#if !defined(DN_ARENA_TEMP_MEM_UAF_TRACE_ON_BY_DEFAULT) - #if DN_PARANOIA_LEVEL >= 2 - #define DN_ARENA_TEMP_MEM_UAF_TRACE_ON_BY_DEFAULT 1 - #else - #define DN_ARENA_TEMP_MEM_UAF_TRACE_ON_BY_DEFAULT 0 - #endif -#endif - -#if !defined(DN_SCRUB_UNINIT_MEM_BYTE) - #if DN_PARANOIA_LEVEL >= 1 - #define DN_SCRUB_UNINIT_MEM_BYTE 0xCD - #else - #define DN_SCRUB_UNINIT_MEM_BYTE 0x00 - #endif -#endif - -#define DN_AssertRaw(expr) do { if (!(expr)) DN_DebugBreak; } while (0) -#define DN_AssertAlwaysCallSiteF(expr, call_site, fmt, ...) \ - do { \ - if (!(expr)) { \ - DN_Str8 trace_ = DN_Str8FromStackTraceNowHeap(128 /*limit*/, 3 /*skip*/); \ - DN_LogTypeParam log_type_ = DN_LogTypeParamFromType(DN_LogType_Error); \ - DN_LogPrintF(log_type_, call_site, DN_LogFlags_Nil, "Assertion triggered [" #expr "]. " fmt "\nTrace:\n%.*s", ## __VA_ARGS__, DN_Str8PrintFmt(trace_)); \ - DN_DebugBreak; \ - } \ - } while (0) - -#define DN_AssertAlwaysF(expr, fmt, ...) DN_AssertAlwaysCallSiteF(expr, (DN_CallSiteNow), fmt, ##__VA_ARGS__) -#define DN_AssertAlways(expr) DN_AssertAlwaysF(expr, "") -#define DN_AssertInvalidCodePathF(fmt, ...) DN_AssertAlwaysF(0, fmt, ##__VA_ARGS__) -#define DN_AssertInvalidCodePath DN_AssertInvalidCodePathF("Invalid code path") -#if DN_PARANOIA_LEVEL >= 1 -#define DN_AssertCallSiteF(expr, call_site, fmt, ...) DN_AssertAlwaysCallSiteF(expr, call_site, fmt, ## __VA_ARGS__) -#define DN_AssertF(expr, fmt, ...) DN_AssertCallSiteF(expr, (DN_CallSiteNow), fmt, ## __VA_ARGS__) -#define DN_Assert(expr) DN_AssertAlways(expr) -#else -#define DN_AssertCallSiteF(expr, call_site, fmt, ...) (void)(expr); (void)call_site -#define DN_AssertF(expr, fmt, ...) (void)(expr) -#define DN_Assert(expr) (void)(expr) -#endif -#define DN_VerifyF(expr, fmt, ...) DN_VerifyArgsF(DN_VerifyType_Nil, expr, (DN_CallSiteNow), DN_Str8Lit(#expr), fmt, ##__VA_ARGS__) -#define DN_VerifyWarningF(expr, fmt, ...) DN_VerifyArgsF(DN_VerifyType_Warning, expr, (DN_CallSiteNow), DN_Str8Lit(#expr), fmt, ##__VA_ARGS__) -#define DN_Verify(expr) DN_VerifyF(expr, 0) -#define DN_VerifyWarning(expr) DN_VerifyWarningF(expr, 0) - -#define DN_StaticAssert(expr) \ - DN_GCC_WARNING_PUSH \ - DN_GCC_WARNING_DISABLE(-Wunused-local-typedefs) \ - typedef char DN_TokenCombine(static_assert_dummy__, __LINE__)[(expr) ? 1 : -1]; \ - DN_GCC_WARNING_POP - -#if defined(__aarch64__) || defined(_M_X64) || defined(__x86_64__) || defined(__x86_64) - #define DN_64_BIT -#else - #define DN_32_BIT -#endif - -#include // va_list -#include -#include -#include -#include // PRIu64... - -#if !defined(DN_OS_WIN32) -#include // exit() -#endif - -#define DN_ForIndexU(index, count) DN_USize index = 0; index < count; index++ -#define DN_ForIndexI(index, count) DN_ISize index = 0; index < count; index++ -#define DN_ForItSize(it, T, array, count) struct { DN_USize index; T *data; } it = {0, &(array)[0]}; it.index < (count); it.index++, it.data = (array) + it.index -#define DN_ForItSizeReverse(it, T, array, count) struct { DN_USize index; T *data; } it = {(count) - 1, &(array)[count - 1]}; it.index < (count); it.index--, it.data = (array) + it.index -#define DN_ForIt(it, T, array) struct { DN_USize index; T *data; } it = {0, &(array)->data[0]}; it.index < (array)->count; it.index++, it.data = ((array)->data) + it.index -#define DN_ForLinkedListIt(it, T, list) struct { DN_USize index; T *data; } it = {0, list}; it.data; it.index++, it.data = ((it).data->next) -#define DN_ForItCArray(it, T, array) struct { DN_USize index; T *data; } it = {0, &(array)[0]}; it.index < DN_ArrayCountU(array); it.index++, it.data = (array) + it.index - -#define DN_AlignUpPowerOfTwo(value, pot) (((uintptr_t)(value) + ((uintptr_t)(pot) - 1)) & ~((uintptr_t)(pot) - 1)) -#define DN_AlignDownPowerOfTwo(value, pot) ((uintptr_t)(value) & ~((uintptr_t)(pot) - 1)) -#define DN_IsPowerOfTwo(value) ((((uintptr_t)(value)) & (((uintptr_t)(value)) - 1)) == 0) -#define DN_IsPowerOfTwoAligned(value, pot) ((((uintptr_t)value) & (((uintptr_t)pot) - 1)) == 0) - -// NOTE: String.h Dependencies -#if !defined(DN_Memcpy) || !defined(DN_Memset) || !defined(DN_Memcmp) || !defined(DN_Memmove) - #include - #if !defined(DN_Memcpy) - #define DN_Memcpy(dest, src, count) memcpy((dest), (src), (count)) - #endif - #if !defined(DN_Memset) - #define DN_Memset(dest, value, count) memset((dest), (value), (count)) - #endif - #if !defined(DN_Memcmp) - #define DN_Memcmp(lhs, rhs, count) memcmp((lhs), (rhs), (count)) - #endif - #if !defined(DN_Memmove) - #define DN_Memmove(dest, src, count) memmove((dest), (src), (count)) - #endif -#endif - -// NOTE: Math.h Dependencies -#if !defined(DN_SqrtF32) || !defined(DN_SinF32) || !defined(DN_CosF32) || !defined(DN_TanF32) - #include - #if !defined(DN_SqrtF32) - #define DN_SqrtF32(val) sqrtf(val) - #endif - #if !defined(DN_SinF32) - #define DN_SinF32(val) sinf(val) - #endif - #if !defined(DN_CosF32) - #define DN_CosF32(val) cosf(val) - #endif - #if !defined(DN_TanF32) - #define DN_TanF32(val) tanf(val) - #endif - #if !defined(DN_PowF32) - #define DN_PowF32(val, exp) powf(val, exp) - #endif -#endif - -// NOTE: Math -#define DN_PiF32 3.14159265359f - -#define DN_DegreesToRadsF32(degrees) ((degrees) * (DN_PiF32 / 180.0f)) -#define DN_RadsToDegreesF32(radians) ((radians) * (180.f * DN_PiF32)) - -#define DN_Abs(val) (((val) < 0) ? (-(val)) : (val)) -#define DN_Max(a, b) (((a) > (b)) ? (a) : (b)) -#define DN_Min(a, b) (((a) < (b)) ? (a) : (b)) -#define DN_Clamp(val, lo, hi) DN_Max(DN_Min(val, hi), lo) -#define DN_Squared(val) ((val) * (val)) - -#define DN_Swap(a, b) \ - do { \ - auto temp = a; \ - a = b; \ - b = temp; \ - } while (0) - -// NOTE: Size -#define DN_SizeOfI(val) DN_Cast(ptrdiff_t)sizeof(val) -#define DN_ArrayCountU(array) (sizeof(array)/(sizeof((array)[0]))) -#define DN_ArrayCountI(array) (DN_ISize)DN_ArrayCountU(array) -#define DN_CharCountU(string) (sizeof(string) - 1) - -// NOTE: SI Byte -#define DN_Bytes(val) ((DN_U64)val) -#define DN_Kilobytes(val) ((DN_U64)1024 * DN_Bytes(val)) -#define DN_Megabytes(val) ((DN_U64)1024 * DN_Kilobytes(val)) -#define DN_Gigabytes(val) ((DN_U64)1024 * DN_Megabytes(val)) - -// NOTE: Time -#define DN_MsFromSec(val) ((val) * 1000ULL) -#define DN_SecFromMins(val) ((val) * 60ULL) -#define DN_SecFromHours(val) (DN_SecFromMins(val) * 60ULL) -#define DN_SecFromDays(val) (DN_SecFromHours(val) * 24ULL) -#define DN_SecFromWeeks(val) (DN_SecFromDays(val) * 7ULL) -#define DN_SecFromYears(val) (DN_SecFromWeeks(val) * 52ULL) - -// NOTE: Debug Break -#if !defined(DN_DebugBreak) - #if defined(NDEBUG) - #define DN_DebugBreak - #else - #if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL) - #define DN_DebugBreak __debugbreak() - #elif DN_HAS_BUILTIN(__builtin_debugtrap) - #define DN_DebugBreak __builtin_debugtrap() - #elif DN_HAS_BUILTIN(__builtin_trap) || defined(DN_COMPILER_GCC) - #define DN_DebugBreak __builtin_trap() - #else - #include - #if defined(SIGTRAP) - #define DN_DebugBreak raise(SIGTRAP) - #else - #define DN_DebugBreak raise(SIGABRT) - #endif - #endif - #endif -#endif - -// NOTE: Helper macros to declare an array data structure for a given `Type` -#define DN_DArrayStructDecl(Type) \ - struct Type##Array \ - { \ - Type* data; \ - DN_USize count; \ - DN_USize max; \ - } - -#define DN_FixedArrayStructDecl(Type, capacity) \ - struct Type##x##capacity##Array \ - { \ - Type data[capacity]; \ - DN_USize count; \ - DN_USize max; \ - } - -// NOTE: Types -typedef intptr_t DN_ISize; -typedef uintptr_t DN_USize; - -typedef int8_t DN_I8; -typedef int16_t DN_I16; -typedef int32_t DN_I32; -typedef int64_t DN_I64; - -typedef uint8_t DN_U8; -typedef uint16_t DN_U16; -typedef uint32_t DN_U32; -typedef uint64_t DN_U64; - -typedef uintptr_t DN_UPtr; -typedef float DN_F32; -typedef double DN_F64; -typedef unsigned int DN_UInt; -typedef DN_I32 DN_B32; - -#define DN_F32_MAX 3.402823466e+38F -#define DN_F32_MIN 1.175494351e-38F -#define DN_F64_MAX 1.7976931348623158e+308 -#define DN_F64_MIN 2.2250738585072014e-308 -#define DN_USIZE_MAX UINTPTR_MAX -#define DN_ISIZE_MAX INTPTR_MAX -#define DN_ISIZE_MIN INTPTR_MIN - -// NOTE: Intrinsics -// NOTE: DN_AtomicAdd/Exchange return the previous value store in the target -#if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL) - #include - #define DN_AtomicCompareExchange64(dest, desired_val, prev_val) _InterlockedCompareExchange64((__int64 volatile *)dest, desired_val, prev_val) - #define DN_AtomicCompareExchange32(dest, desired_val, prev_val) _InterlockedCompareExchange((long volatile *)dest, desired_val, prev_val) - - #define DN_AtomicLoadU64(target) *(target) - #define DN_AtomicLoadU32(target) *(target) - #define DN_AtomicAddU32(target, value) _InterlockedExchangeAdd((long volatile *)target, value) - #define DN_AtomicAddU64(target, value) _InterlockedExchangeAdd64((__int64 volatile *)target, value) - #define DN_AtomicSubU32(target, value) DN_AtomicAddU32(DN_Cast(long volatile *) target, (long)-value) - #define DN_AtomicSubU64(target, value) DN_AtomicAddU64(target, (DN_U64) - value) - - #define DN_CountLeadingZerosU64(value) __lzcnt64(value) - #define DN_CountLeadingZerosU32(value) __lzcnt(value) - #define DN_CPUGetTSC() __rdtsc() - #define DN_CompilerReadBarrierAndCPUReadFence _ReadBarrier(); _mm_lfence() - #define DN_CompilerWriteBarrierAndCPUWriteFence _WriteBarrier(); _mm_sfence() -#elif defined(DN_COMPILER_GCC) || defined(DN_COMPILER_CLANG) - #if defined(__ANDROID__) - #elif defined(DN_PLATFORM_EMSCRIPTEN) - #if !defined(__wasm_simd128__) - #error DN_Base requires -msse2 to be passed to Emscripten - #endif - #include - #else - #include - #endif - - #define DN_AtomicLoadU64(target) __atomic_load_n(x, __ATOMIC_SEQ_CST) - #define DN_AtomicLoadU32(target) __atomic_load_n(x, __ATOMIC_SEQ_CST) - #define DN_AtomicAddU32(target, value) __atomic_fetch_add(target, value, __ATOMIC_ACQ_REL) - #define DN_AtomicAddU64(target, value) __atomic_fetch_add(target, value, __ATOMIC_ACQ_REL) - #define DN_AtomicSubU32(target, value) __atomic_fetch_sub(target, value, __ATOMIC_ACQ_REL) - #define DN_AtomicSubU64(target, value) __atomic_fetch_sub(target, value, __ATOMIC_ACQ_REL) - - #define DN_CountLeadingZerosU64(value) __builtin_clzll(value) - #define DN_CountLeadingZerosU32(value) __builtin_clzl(value) - - #if defined(DN_COMPILER_GCC) - #define DN_CPUGetTSC() __rdtsc() - #else - #define DN_CPUGetTSC() __builtin_readcyclecounter() - #endif - - #if defined(DN_PLATFORM_EMSCRIPTEN) - #define DN_CompilerReadBarrierAndCPUReadFence - #define DN_CompilerWriteBarrierAndCPUWriteFence - #else - #define DN_CompilerReadBarrierAndCPUReadFence asm volatile("lfence" ::: "memory") - #define DN_CompilerWriteBarrierAndCPUWriteFence asm volatile("sfence" ::: "memory") - #endif -#else - #error "Compiler not supported" -#endif - -#if defined(DN_64_BIT) - #define DN_CountLeadingZerosUSize(value) DN_CountLeadingZerosU64(value) -#else - #define DN_CountLeadingZerosUSize(value) DN_CountLeadingZerosU32(value) -#endif - -enum DN_VerifyType -{ - DN_VerifyType_Nil, - DN_VerifyType_Warning, -}; - -enum DN_ZMem -{ - DN_ZMem_No, // Memory can be handed out without zero-ing it out - DN_ZMem_Yes, // Memory should be zero-ed out before giving to the callee -}; - -struct DN_Str8 -{ - char *data; // The bytes of the string - DN_USize size; // The number of bytes in the string -}; - -struct DN_Str8Slice -{ - DN_Str8 *data; - DN_USize count; -}; - -struct DN_Str8x16 { char data[16]; DN_USize size; }; -struct DN_Str8x32 { char data[32]; DN_USize size; }; -struct DN_Str8x64 { char data[64]; DN_USize size; }; -struct DN_Str8x128 { char data[128]; DN_USize size; }; -struct DN_Str8x256 { char data[256]; DN_USize size; }; -struct DN_Str8x512 { char data[512]; DN_USize size; }; -struct DN_Str8x1024 { char data[1024]; DN_USize size; }; - -struct DN_Str16 // A pointer and length style string that holds slices to UTF16 bytes. -{ - wchar_t *data; // The UTF16 bytes of the string - DN_USize size; // The number of characters in the string -}; - -struct DN_Str16Slice -{ - DN_Str16 *data; - DN_USize count; -}; - -struct DN_CPURegisters -{ - int eax; - int ebx; - int ecx; - int edx; -}; - -union DN_CPUIDResult -{ - DN_CPURegisters reg; - int values[4]; -}; - -struct DN_CPUIDArgs { int eax; int ecx; }; - -#define DN_CPU_FEAT_XMACRO \ - DN_CPU_FEAT_XENTRY(3DNow) \ - DN_CPU_FEAT_XENTRY(3DNowExt) \ - DN_CPU_FEAT_XENTRY(ABM) \ - DN_CPU_FEAT_XENTRY(AES) \ - DN_CPU_FEAT_XENTRY(AVX) \ - DN_CPU_FEAT_XENTRY(AVX2) \ - DN_CPU_FEAT_XENTRY(AVX512F) \ - DN_CPU_FEAT_XENTRY(AVX512DQ) \ - DN_CPU_FEAT_XENTRY(AVX512IFMA) \ - DN_CPU_FEAT_XENTRY(AVX512PF) \ - DN_CPU_FEAT_XENTRY(AVX512ER) \ - DN_CPU_FEAT_XENTRY(AVX512CD) \ - DN_CPU_FEAT_XENTRY(AVX512BW) \ - DN_CPU_FEAT_XENTRY(AVX512VL) \ - DN_CPU_FEAT_XENTRY(AVX512VBMI) \ - DN_CPU_FEAT_XENTRY(AVX512VBMI2) \ - DN_CPU_FEAT_XENTRY(AVX512VNNI) \ - DN_CPU_FEAT_XENTRY(AVX512BITALG) \ - DN_CPU_FEAT_XENTRY(AVX512VPOPCNTDQ) \ - DN_CPU_FEAT_XENTRY(AVX5124VNNIW) \ - DN_CPU_FEAT_XENTRY(AVX5124FMAPS) \ - DN_CPU_FEAT_XENTRY(AVX512VP2INTERSECT) \ - DN_CPU_FEAT_XENTRY(AVX512FP16) \ - DN_CPU_FEAT_XENTRY(CLZERO) \ - DN_CPU_FEAT_XENTRY(CMPXCHG8B) \ - DN_CPU_FEAT_XENTRY(CMPXCHG16B) \ - DN_CPU_FEAT_XENTRY(F16C) \ - DN_CPU_FEAT_XENTRY(FMA) \ - DN_CPU_FEAT_XENTRY(FMA4) \ - DN_CPU_FEAT_XENTRY(FP128) \ - DN_CPU_FEAT_XENTRY(FP256) \ - DN_CPU_FEAT_XENTRY(FPU) \ - DN_CPU_FEAT_XENTRY(MMX) \ - DN_CPU_FEAT_XENTRY(MONITOR) \ - DN_CPU_FEAT_XENTRY(MOVBE) \ - DN_CPU_FEAT_XENTRY(MOVU) \ - DN_CPU_FEAT_XENTRY(MmxExt) \ - DN_CPU_FEAT_XENTRY(PCLMULQDQ) \ - DN_CPU_FEAT_XENTRY(POPCNT) \ - DN_CPU_FEAT_XENTRY(RDRAND) \ - DN_CPU_FEAT_XENTRY(RDSEED) \ - DN_CPU_FEAT_XENTRY(RDTSCP) \ - DN_CPU_FEAT_XENTRY(SHA) \ - DN_CPU_FEAT_XENTRY(SSE) \ - DN_CPU_FEAT_XENTRY(SSE2) \ - DN_CPU_FEAT_XENTRY(SSE3) \ - DN_CPU_FEAT_XENTRY(SSE41) \ - DN_CPU_FEAT_XENTRY(SSE42) \ - DN_CPU_FEAT_XENTRY(SSE4A) \ - DN_CPU_FEAT_XENTRY(SSSE3) \ - DN_CPU_FEAT_XENTRY(TSC) \ - DN_CPU_FEAT_XENTRY(TscInvariant) \ - DN_CPU_FEAT_XENTRY(VAES) \ - DN_CPU_FEAT_XENTRY(VPCMULQDQ) - -enum DN_CPUFeature -{ - #define DN_CPU_FEAT_XENTRY(label) DN_CPUFeature_##label, - DN_CPU_FEAT_XMACRO - #undef DN_CPU_FEAT_XENTRY - DN_CPUFeature_Count, -}; - -struct DN_CPUFeatureDecl -{ - DN_CPUFeature value; - DN_Str8 label; -}; - -struct DN_CPUFeatureQuery -{ - DN_CPUFeature feature; - bool available; -}; - -struct DN_CPUReport -{ - char vendor[4 /*bytes*/ * 3 /*EDX, ECX, EBX*/ + 1 /*null*/]; - char brand[48]; - DN_U64 features[(DN_CPUFeature_Count / (sizeof(DN_U64) * 8)) + 1]; -}; - -struct DN_TicketMutex -{ - unsigned int volatile ticket; // The next ticket to give out to the thread taking the mutex - unsigned int volatile serving; // The ticket ID to block the mutex on until it is returned -}; - - -struct DN_Hex32 { char data[32 + 1]; DN_USize size; }; -struct DN_Hex64 { char data[64 + 1]; DN_USize size; }; -struct DN_Hex128 { char data[128 + 1]; DN_USize size; }; - -struct DN_HexU64 -{ - char data[(sizeof(DN_U64) * 2) + 1 /*null-terminator*/]; - DN_U8 size; -}; - -enum DN_HexFromU64Type -{ - DN_HexFromU64Type_Nil, - DN_HexFromU64Type_Uppercase, -}; - -enum DN_TrimLeadingZero -{ - DN_TrimLeadingZero_No, - DN_TrimLeadingZero_Yes, -}; - -struct DN_U8x16 { DN_U8 data[16]; }; -struct DN_U8x32 { DN_U8 data[32]; }; -struct DN_U8x64 { DN_U8 data[64]; }; - -DN_MSVC_WARNING_PUSH -DN_MSVC_WARNING_DISABLE(4201) // warning C4201: nonstandard extension used: nameless struct/union -union DN_V2USize -{ - struct { DN_USize x, y; }; - struct { DN_USize w, h; }; - struct { DN_USize min, max; }; - struct { DN_USize begin, end; }; - DN_USize data[2]; -}; - -union DN_V2U64 -{ - struct { DN_U64 x, y; }; - struct { DN_U64 w, h; }; - struct { DN_U64 min, max; }; - struct { DN_U64 begin, end; }; - DN_U64 data[2]; -}; -DN_MSVC_WARNING_POP - -struct DN_CallSite -{ - DN_Str8 file; - DN_Str8 function; - DN_U32 line; -}; - -#define DN_CallSiteNow DN_Literal(DN_CallSite){DN_Str8Lit(__FILE__), DN_Str8Lit(__func__), __LINE__ } - -#if defined(__cplusplus) -template -struct DN_Defer -{ - Procedure proc; - DN_Defer(Procedure p) : proc(p) {} - ~DN_Defer() { proc(); } -}; - -struct DN_DeferHelper -{ - template - DN_Defer operator+(Lambda lambda) { return DN_Defer(lambda); }; -}; - -#define DN_UniqueName(prefix) DN_TokenCombine(prefix, __LINE__) -#define DN_DEFER const auto DN_UniqueName(defer_lambda_) = DN_DeferHelper() + [&]() -#endif // defined(__cplusplus) - -#define DN_DeferLoop(begin, end) \ - bool DN_UniqueName(once) = (begin, true); \ - DN_UniqueName(once); \ - end, DN_UniqueName(once) = false - -struct DN_U64FromResult -{ - bool success; - DN_U64 value; -}; - -struct DN_USizeFromResult -{ - bool success; - DN_USize value; -}; - -struct DN_I64FromResult -{ - bool success; - DN_I64 value; -}; - -struct DN_U8x32FromResult -{ - bool success; - DN_U8x32 value; -}; - -struct DN_StackTraceFrame -{ - DN_U64 address; - DN_U64 line_number; - DN_Str8 file_name; - DN_Str8 function_name; -}; - -struct DN_StackTraceFrameSlice -{ - DN_StackTraceFrame *data; - DN_USize count; -}; - -struct DN_StackTraceRawFrame -{ - void *process; - DN_U64 base_addr; -}; - -struct DN_StackTrace -{ - void *process; // [Internal] Windows handle to the process - DN_U64 *base_addr; // The addresses of the functions in the stack trace - DN_U16 size; // The number of `base_addr`'s stored from the walk -}; - -struct DN_StackTraceIterator -{ - DN_StackTraceRawFrame raw_frame; - DN_U16 index; -}; - -enum DN_MemCommit -{ - DN_MemCommit_No, - DN_MemCommit_Yes, -}; - -typedef DN_U32 DN_MemPage; -enum DN_MemPage_ -{ - // Exception on read/write with a page. This flag overrides the read/write - // access. - DN_MemPage_NoAccess = 1 << 0, - - DN_MemPage_Read = 1 << 1, // Only read permitted on the page. - - // Only write permitted on the page. On Windows this is not supported and - // will be promoted to read+write permissions. - DN_MemPage_Write = 1 << 2, - - DN_MemPage_ReadWrite = DN_MemPage_Read | DN_MemPage_Write, - - // Modifier used in conjunction with previous flags. Raises exception on - // 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 DN_MemPage_NoAccess. - // This flag must only be used in DN_Mem_Protect - DN_MemPage_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. - DN_MemPage_AllocRecordLeakPermitted = 1 << 4, - - // If leak tracing is enabled this flag will prevent any allocation record - // from being created in the allocation table at all. If this flag is - // enabled, 'OSMemPage_AllocRecordLeakPermitted' has no effect since the - // record will never be created. - DN_MemPage_NoAllocRecordEntry = 1 << 5, - - // [INTERNAL] Do not use. All flags together do not constitute a correct - // configuration of pages. - DN_MemPage_All = DN_MemPage_NoAccess | - DN_MemPage_ReadWrite | - DN_MemPage_Guard | - DN_MemPage_AllocRecordLeakPermitted | - DN_MemPage_NoAllocRecordEntry, -}; - -#if !defined(DN_ARENA_RESERVE_SIZE) - #define DN_ARENA_RESERVE_SIZE DN_Megabytes(64) -#endif - -#if !defined(DN_ARENA_COMMIT_SIZE) - #define DN_ARENA_COMMIT_SIZE DN_Kilobytes(64) -#endif - -enum DN_MemFuncsType -{ - DN_MemFuncsType_Nil, - DN_MemFuncsType_Heap, - DN_MemFuncsType_Virtual, -}; - -typedef void *(DN_MemHeapAllocFunc)(DN_USize size); -typedef void (DN_MemHeapDeallocFunc)(void *ptr); -typedef void *(DN_MemVirtualReserveFunc)(DN_USize size, DN_MemCommit commit, DN_MemPage page_flags); -typedef bool (DN_MemVirtualCommitFunc)(void *ptr, DN_USize size, DN_U32 page_flags); -typedef void (DN_MemVirtualReleaseFunc)(void *ptr, DN_USize size); -struct DN_MemFuncs -{ - DN_MemFuncsType type; - DN_MemHeapAllocFunc *heap_alloc; - DN_MemHeapDeallocFunc *heap_dealloc; - - DN_U32 virtual_page_size; - DN_MemVirtualReserveFunc *virtual_reserve; - DN_MemVirtualCommitFunc *virtual_commit; - DN_MemVirtualReleaseFunc *virtual_release; -}; - -struct DN_MemBlock -{ - DN_MemBlock* prev; - DN_U64 used; - DN_U64 commit; - DN_U64 reserve; - DN_U64 reserve_sum; -}; - -struct DN_MemListInfo -{ - DN_U64 used; - DN_U64 commit; - DN_U64 reserve; - DN_U64 blocks; -}; - -struct DN_MemStats -{ - DN_MemListInfo info; - DN_MemListInfo hwm; -}; - -typedef DN_U32 DN_MemFlags; -enum DN_MemFlags_ -{ - DN_MemFlags_Nil = 0, - DN_MemFlags_NoGrow = 1 << 0, - DN_MemFlags_NoPoison = 1 << 1, - DN_MemFlags_NoAllocTrack = 1 << 2, - DN_MemFlags_AllocCanLeak = 1 << 3, - DN_MemFlags_SimAlloc = 1 << 4, - - // NOTE: Records stack traces of temp memory regions on construction to provide more diagnostics - // when UAF violation occurs in the use of a region (e.g. nested regions A and B, with A - // allocating whilst B is active would result in A's memory being wiped at the end of B). Tracing - // has a heavy performance penalty as each scratch/temp memory region triggers and stores the - // stack trace. - // - // Ignored if UAF guard is disabled at the preprocessor level - // (e.g.: #define DN_ARENA_TEMP_MEM_UAF_GUARD 0) - DN_MemFlags_TempMemUAFTrace = 1 << 5, - - // NOTE: Forcibly disables TempMemUAFTrace for the arena irrespective of global settings. Globally - // UAF tracing can be enabled across all arenas via the preprocessor which turns the tracing - // feature (e.g.: #define DN_ARENA_TEMP_MEM_UAF_TRACE_ON_BY_DEFAULT 1) into an opt-out situation - // where arenas have to specify this flag, specifically to not be traced. - // - // If both TempMemUAFTrace, TempMemUAFTraceDisable and or the global preprocessor flag is set - // disabling takes precedence, always if it is set. - DN_MemFlags_TempMemUAFTraceDisable = 1 << 6, - - // NOTE: Internal flags. Do not use - DN_MemFlags_UserBuffer = 1 << 7, - DN_MemFlags_MemFuncs = 1 << 8, -}; - -struct DN_MemList -{ - DN_MemBlock* curr; - DN_MemFlags flags; - DN_MemFuncs funcs; - DN_MemStats stats; - DN_Str8 label; - - #if DN_ARENA_TEMP_MEM_UAF_GUARD - DN_U32 uaf_guard_next_id; - DN_U32 uaf_guard_active_id; - struct DN_MemListTemp* uaf_guard_active_temp_mem; - #endif -}; - -struct DN_MemListTemp -{ - DN_MemList* mem; - DN_U64 used_sum; - #if DN_ARENA_TEMP_MEM_UAF_GUARD - DN_StackTrace trace; - #endif -}; - -enum DN_AllocatorType -{ - DN_AllocatorType_MemList, - DN_AllocatorType_Arena, - DN_AllocatorType_Pool, -}; - -struct DN_Allocator -{ - DN_AllocatorType type; - void* context; -}; - -enum DN_ArenaReset -{ - DN_ArenaReset_No, - DN_ArenaReset_Yes, -}; - -typedef DN_U32 DN_ArenaFlags; -enum DN_ArenaFlags_ -{ - DN_ArenaFlags_Nil = 0, - DN_ArenaFlags_OwnsMemList = 1 << 0, -}; - -struct DN_Arena -{ - DN_ArenaFlags flags; - DN_MemList* mem; - #if DN_ARENA_TEMP_MEM_UAF_GUARD - DN_U32 uaf_guard_id; - DN_MemListTemp* uaf_guard_temp_mem; - DN_U32 uaf_guard_prev_id; - DN_MemListTemp* uaf_guard_prev_temp_mem; - bool uaf_guard_is_being_checked; - #else - DN_MemListTemp temp_mem; - #endif -}; - -DN_USize const DN_ARENA_HEADER_SIZE = DN_AlignUpPowerOfTwo(sizeof(DN_Arena), 64); - -#if !defined(DN_POOL_DEFAULT_ALIGN) - #define DN_POOL_DEFAULT_ALIGN 16 -#endif - -struct DN_PoolSlot -{ - void *data; - DN_PoolSlot *next; -}; - -enum DN_PoolSlotSize -{ - DN_PoolSlotSize_32B, - DN_PoolSlotSize_64B, - DN_PoolSlotSize_128B, - DN_PoolSlotSize_256B, - DN_PoolSlotSize_512B, - DN_PoolSlotSize_1KiB, - DN_PoolSlotSize_2KiB, - DN_PoolSlotSize_4KiB, - DN_PoolSlotSize_8KiB, - DN_PoolSlotSize_16KiB, - DN_PoolSlotSize_32KiB, - DN_PoolSlotSize_64KiB, - DN_PoolSlotSize_128KiB, - DN_PoolSlotSize_256KiB, - DN_PoolSlotSize_512KiB, - DN_PoolSlotSize_1MiB, - DN_PoolSlotSize_2MiB, - DN_PoolSlotSize_4MiB, - DN_PoolSlotSize_8MiB, - DN_PoolSlotSize_16MiB, - DN_PoolSlotSize_32MiB, - DN_PoolSlotSize_64MiB, - DN_PoolSlotSize_128MiB, - DN_PoolSlotSize_256MiB, - DN_PoolSlotSize_512MiB, - DN_PoolSlotSize_1GiB, - DN_PoolSlotSize_2GiB, - DN_PoolSlotSize_4GiB, - DN_PoolSlotSize_8GiB, - DN_PoolSlotSize_16GiB, - DN_PoolSlotSize_32GiB, - DN_PoolSlotSize_Count, -}; - -struct DN_Pool -{ - DN_Arena *arena; - DN_PoolSlot *slots[DN_PoolSlotSize_Count]; - DN_U8 align; -}; - -struct DN_UTF8DecodeResult -{ - bool success; - DN_Str8 remaining; - DN_U32 codepoint; -}; - -struct DN_UTF8DecodeIterator -{ - bool init; - bool success; - DN_Str8 remaining; - DN_USize codepoint_index; - DN_U32 codepoint; -}; - -typedef DN_U32 DN_CodepointCountFlags; -enum DN_CodepointCountFlags_ -{ - DN_CodepointCountFlags_Nil = 0, - DN_CodepointCountFlags_SkipANSICode = 1 << 0, -}; - -struct DN_NibbleFromU8Result -{ - char nibble0; - char nibble1; -}; - -enum DN_Str8EqCase -{ - DN_Str8EqCase_Sensitive, - DN_Str8EqCase_Insensitive, -}; - -enum DN_Str8IsAllType -{ - DN_Str8IsAllType_Digits, - DN_Str8IsAllType_Hex, -}; - -struct DN_Str8BSplitResult -{ - // If there are multiple strings passed to split against, this is the index into that array of - // which the string was split on. If no array was passed this is always 0. - DN_USize input_index; - DN_Str8 lhs; - DN_Str8 rhs; -}; - -struct DN_Str8FindResult -{ - bool found; // True if string was found. If false, the subsequent fields below are not set. - DN_USize index; // Index in the buffer where the found string starts - DN_Str8 match; // Matching string in the buffer that was searched - DN_Str8 match_to_end_of_buffer; // Substring containing the found string to the end of the buffer - DN_Str8 after_match_to_end_of_buffer; // Substring starting after the found string to the end of the buffer - DN_Str8 start_to_before_match; // Substring from the start of the buffer up until the found string, not including it -}; - -typedef DN_USize DN_Str8FindFlag; -enum DN_Str8FindFlag_ -{ - DN_Str8FindFlag_Digit = 1 << 0, // 0-9 - DN_Str8FindFlag_Whitespace = 1 << 1, // '\r', '\t', '\n', ' ' - DN_Str8FindFlag_Alphabet = 1 << 2, // A-Z, a-z - DN_Str8FindFlag_Plus = 1 << 3, // + - DN_Str8FindFlag_Minus = 1 << 4, // - - DN_Str8FindFlag_AlphaNum = DN_Str8FindFlag_Alphabet | DN_Str8FindFlag_Digit, -}; - -typedef DN_USize DN_Str8SplitFlags; -enum DN_Str8SplitFlags_ -{ - DN_Str8SplitFlags_Nil = 0, - DN_Str8SplitFlags_ExcludeEmptyStrings = 1 << 0, - DN_Str8SplitFlags_HandleQuotedStrings = 1 << 1, -}; - -struct DN_Str8TruncResult -{ - bool truncated; - DN_Str8 str8; - DN_USize size_req; // Not including null-terminator -}; - -struct DN_Str8SplitResult -{ - DN_Str8 *data; - DN_USize count; -}; - -enum DN_Str8LineBreakMode -{ - DN_Str8LineBreakMode_AtWord, // Add delimiter to string at ' ' and '\n' boundaries - DN_Str8LineBreakMode_AtWidth, // Add delimiter to string at width intervals -}; - -typedef DN_USize DN_Str8TableFlags; -enum DN_Str8TableFlags_ -{ - DN_Str8TableFlags_None = 0, - DN_Str8TableFlags_HasHeader = 1 << 0, - DN_Str8TableFlags_RowLines = 1 << 1, -}; - -struct DN_Str8Link -{ - DN_Str8 string; // The string - DN_Str8Link *next; // The next string in the linked list - DN_Str8Link *prev; // The prev string in the linked list -}; - -struct DN_Str8Builder -{ - DN_Arena* arena; // Allocator to use to back the string list - DN_Str8Link* head; // First string in the linked list of strings - DN_Str8Link* tail; // Last string in the linked list of strings - DN_USize string_size; // The size in bytes necessary to construct the current string - DN_USize count; // The number of links in the linked list of strings -}; - -enum DN_Str8BuilderAdd -{ - DN_Str8BuilderAdd_Append, - DN_Str8BuilderAdd_Prepend, -}; - -typedef DN_U32 DN_AgeUnit; -enum DN_AgeUnit_ -{ - DN_AgeUnit_Ms = 1 << 0, - DN_AgeUnit_Sec = 1 << 1, - DN_AgeUnit_Min = 1 << 2, - DN_AgeUnit_Hr = 1 << 3, - DN_AgeUnit_Day = 1 << 4, - DN_AgeUnit_Week = 1 << 5, - DN_AgeUnit_Year = 1 << 6, - DN_AgeUnit_FractionalSec = 1 << 7, - DN_AgeUnit_HMS = DN_AgeUnit_Sec | DN_AgeUnit_Min | DN_AgeUnit_Hr, - DN_AgeUnit_All = DN_AgeUnit_Ms | DN_AgeUnit_HMS | DN_AgeUnit_Day | DN_AgeUnit_Week | DN_AgeUnit_Year, -}; - -enum DN_ByteType -{ - DN_ByteType_B, - DN_ByteType_KiB, - DN_ByteType_MiB, - DN_ByteType_GiB, - DN_ByteType_TiB, - DN_ByteType_Count, - DN_ByteType_Auto, -}; - -struct DN_ByteCount -{ - DN_ByteType type; - DN_Str8 suffix; // "KiB", "MiB", "GiB" .. e.t.c - DN_F64 bytes; -}; - -struct DN_Date -{ - DN_U8 day; - DN_U8 month; - DN_U16 year; - DN_U8 hour; - DN_U8 minutes; - DN_U8 seconds; - DN_U16 milliseconds; -}; - -struct DN_FmtAppendResult -{ - DN_USize size_req; - DN_Str8 str8; - bool truncated; -}; - -struct DN_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. - DN_U64 tsc_inclusive; - DN_U64 tsc_exclusive; - DN_U16 hit_count; - DN_Str8 name; -}; - -struct DN_ProfilerZone -{ - struct DN_Profiler *profiler; - DN_U16 anchor_index; - DN_U64 begin_tsc; - DN_U16 parent_zone; - DN_U64 elapsed_tsc_at_zone_start; -}; - -struct DN_ProfilerAnchorArray -{ - DN_ProfilerAnchor *data; - DN_USize count; -}; - -typedef DN_U64 (DN_ProfilerTSCNowFunc)(); -struct DN_Profiler -{ - DN_USize frame_index; - DN_ProfilerAnchor *anchors; - DN_USize anchors_count; - DN_USize anchors_per_frame; - DN_U16 parent_zone; - bool paused; - DN_ProfilerTSCNowFunc *tsc_now; - DN_U64 tsc_frequency; - DN_ProfilerZone frame_zone; - DN_F64 frame_avg_tsc; -}; - -typedef bool (DN_QSortCompareFunc)(void const *a, void const *b, void *user_context); -enum DN_ErrSinkMode -{ - DN_ErrSinkMode_Nil, // Default behaviour to accumulate errors into the sink - DN_ErrSinkMode_DebugBreakOnErrorLog, // Debug break (int3) when error is encountered and the sink is ended by the 'end and log' functions. - DN_ErrSinkMode_ExitOnError, // When an error is encountered, exit the program with the error code of the error that was caught. -}; - -struct DN_ErrSinkMsg -{ - DN_I32 error_code; - DN_Str8 msg; - DN_CallSite call_site; - DN_ErrSinkMsg *next; - DN_ErrSinkMsg *prev; -}; - -struct DN_ErrSinkNode -{ - DN_CallSite call_site; // Call site that the node was created - DN_ErrSinkMode mode; // Controls how the sink behaves when an error is registered onto the sink. - DN_ErrSinkMsg *msg_sentinel; // List of error messages accumulated for the current scope - DN_U64 arena_pos; // Position to reset the arena when the scope is ended -}; - -struct DN_ErrSink -{ - DN_Arena* arena; // Dedicated allocator from the thread's local storage - DN_ErrSinkNode stack[128]; // Each entry contains errors accumulated between a [begin, end] region of the active sink. - DN_USize stack_size; -}; - -struct DN_TCScratch -{ - DN_Arena arena; - DN_B32 destructed; -}; - -#if defined(__cplusplus) -struct DN_TCScratchCpp -{ - DN_TCScratchCpp(DN_Arena **conflicts, DN_USize count); - ~DN_TCScratchCpp(); - DN_TCScratch data; -}; -#endif - -struct DN_TCInitArgs -{ - DN_U64 main_reserve; - DN_U64 main_commit; - DN_U64 temp_reserve; - DN_U64 temp_commit; - DN_U64 temp_count; - DN_U64 err_sink_reserve; - DN_U64 err_sink_commit; -}; - -struct DN_TCCore // (T)hread (C)ontext sitting in thread-local storage -{ - DN_Str8x64 name; - DN_U64 thread_id; - DN_CallSite call_site; - char lane_opaque[sizeof(DN_U64) * 4]; - void* user_context; - - DN_MemList main_arena_mem_; - DN_MemList temp_arena_mems_[4]; - DN_MemList err_sink_arena_mem_; - - DN_Arena main_arena_; - DN_Arena temp_arenas_[4]; - DN_Arena err_sink_arena_; - - DN_Arena* main_arena; - DN_Pool main_pool; - DN_Arena* temp_arenas[4]; - DN_USize temp_arenas_count; - - DN_ErrSink err_sink; - - DN_Arena* frame_arena; -}; - -enum DN_TCDeinitArenas -{ - DN_TCDeinitArenas_No, - DN_TCDeinitArenas_Yes, -}; - -struct DN_PCG32 { DN_U64 state; }; -struct DN_MurmurHash3 { DN_U64 e[2]; }; - -enum DN_LogType -{ - DN_LogType_Debug, - DN_LogType_Info, - DN_LogType_Warning, - DN_LogType_Error, - DN_LogType_Count, -}; - -enum DN_LogBold -{ - DN_LogBold_No, - DN_LogBold_Yes, -}; - -struct DN_LogStyle -{ - DN_LogBold bold; - bool colour; - DN_U8 r, g, b; -}; - -struct DN_LogTypeParam -{ - bool is_u32_enum; - DN_U32 u32; - DN_Str8 str8; -}; - -enum DN_ANSIColourMode -{ - DN_ANSIColourMode_Fg, - DN_ANSIColourMode_Bg, -}; - -struct DN_LogDate -{ - DN_U16 year; - DN_U8 month; - DN_U8 day; - - DN_U8 hour; - DN_U8 minute; - DN_U8 second; -}; - -struct DN_LogPrefixSize -{ - DN_USize size; - DN_USize padding; -}; - -typedef DN_U32 DN_LogFlags; -enum DN_LogFlags_ -{ - DN_LogFlags_Nil = 0, - DN_LogFlags_NoNewLine = 1 << 0, - DN_LogFlags_NoPrefix = 1 << 1, -}; - -typedef void DN_LogPrintFunc(DN_LogTypeParam type, void *user_data, DN_CallSite call_site, DN_LogFlags flags, DN_FMT_ATTRIB char const *fmt, va_list args); - -DN_MSVC_WARNING_PUSH -DN_MSVC_WARNING_DISABLE(4201) // warning C4201: nonstandard extension used: nameless struct/union -union DN_V2I32 -{ - struct { DN_I32 x, y; }; - struct { DN_I32 w, h; }; - DN_I32 data[2]; -}; - -union DN_V2U16 -{ - struct { DN_U16 x, y; }; - struct { DN_U16 w, h; }; - DN_U16 data[2]; -}; - -union DN_V2U32 -{ - struct { DN_U32 x, y; }; - struct { DN_U32 w, h; }; - struct { DN_U32 min, max; }; - DN_U32 data[2]; -}; - -union DN_V2F32 -{ - struct { DN_F32 x, y; }; - struct { DN_F32 w, h; }; - DN_F32 data[2]; -}; - -struct DN_2V2F32 -{ - DN_V2F32 min; - DN_V2F32 max; -}; - -struct DN_V2F32Array -{ - DN_V2F32 *data; - DN_USize count; - DN_USize max; -}; - -union DN_V3F32 -{ - struct { DN_F32 x, y, z; }; - struct { DN_F32 r, g, b; }; - DN_V2F32 xy; - DN_F32 data[3]; -}; - -union DN_V4F32 -{ - struct { DN_F32 x, y, z, w; }; - struct { DN_F32 r, g, b, a; }; - DN_V3F32 rgb; - DN_V3F32 xyz; - DN_F32 data[4]; -}; - -struct DN_V4F32Array -{ - DN_V4F32* data; - DN_USize count; - DN_USize max; -}; -DN_MSVC_WARNING_POP - -struct DN_M4 -{ - DN_F32 columns[4][4]; // Column major matrix -}; - -union DN_M2x3 -{ - DN_F32 e[6]; - DN_F32 row[2][3]; -}; - -struct DN_M2x3XForm -{ - DN_M2x3 forward; - DN_M2x3 inverse; -}; - -enum DN_M2x3ProjOrigin -{ - DN_M2x3ProjOrigin_TopLeft, - DN_M2x3ProjOrigin_Center, -}; - -struct DN_Rect -{ - DN_V2F32 pos, size; -}; - -enum DN_RectCutClip -{ - DN_RectCutClip_No, - DN_RectCutClip_Yes, -}; - -enum DN_RectCutSide -{ - DN_RectCutSide_Left, - DN_RectCutSide_Right, - DN_RectCutSide_Top, - DN_RectCutSide_Bottom, -}; - -struct DN_RectCut -{ - DN_Rect* rect; - DN_RectCutSide side; -}; - -struct DN_RaycastV2 -{ - bool hit; // True if there was an intersection, false if the lines are parallel - DN_F32 t_a; // Distance along `dir_a` that the intersection occurred, e.g. `origin_a + (dir_a * t_a)` - DN_F32 t_b; // Distance along `dir_b` that the intersection occurred, e.g. `origin_b + (dir_b * t_b)` -}; - -struct DN_Ring -{ - DN_U64 size; - char *base; - DN_U64 write_pos; - DN_U64 read_pos; -}; - -enum DN_ArrayErase -{ - DN_ArrayErase_Unstable, - DN_ArrayErase_Stable, -}; - -enum DN_ArrayAdd -{ - DN_ArrayAdd_Append, - DN_ArrayAdd_Prepend, -}; - -struct DN_ArrayEraseResult -{ - // The next index your for-index should be set to such that you can continue - // to iterate the remainder of the array, e.g: - // - // for (DN_USize index = 0; index < array.size; index++) { - // if (erase) - // index = DN_FArray_EraseRange(&array, index, -3, DN_ArrayErase_Unstable); - // } - DN_USize it_index; - DN_USize items_erased; // The number of items erased -}; - -struct DN_ArrayFindResult -{ - bool success; - DN_USize index; - void *value; -}; -typedef bool (DN_ArrayFindEqFunc)(void const *lhs, void const *find); - -enum DN_DSMapKeyType -{ - // Key | Key Hash | Map Index - DN_DSMapKeyType_Invalid, - DN_DSMapKeyType_U64, // U64 | Hash(U64) | Hash(U64) % map_size - DN_DSMapKeyType_U64NoHash, // U64 | U64 | U64 % map_size - DN_DSMapKeyType_Buffer, // Buffer | Hash(buffer) | Hash(buffer) % map_size - DN_DSMapKeyType_BufferAsU64NoHash, // Buffer | U64(buffer[0:4]) | U64(buffer[0:4]) % map_size -}; - -struct DN_DSMapKey -{ - DN_DSMapKeyType type; - DN_U32 hash; // Hash to lookup in the map. If it equals, we check that the original key payload matches - void const *buffer_data; - DN_U32 buffer_size; - DN_U64 u64; - bool no_copy_buffer; -}; - -template -struct DN_DSMapSlot -{ - DN_DSMapKey key; // Hash table lookup key - T value; // Hash table value -}; - -typedef DN_U32 DN_DSMapFlags; -enum DN_DSMapFlags_ -{ - DN_DSMapFlags_Nil = 0, - DN_DSMapFlags_DontFreeArenaOnResize = 1 << 0, -}; - -using DN_DSMapHashFunction = DN_U32(DN_DSMapKey key, DN_U32 seed); -template struct DN_DSMap -{ - DN_U32 *hash_to_slot; // Mapping from hash to a index in the slots array - DN_DSMapSlot *slots; // Values of the array stored contiguously, non-sorted order - DN_U32 size; // Total capacity of the map and is a power of two - DN_U32 occupied; // Number of slots used in the hash table - DN_Arena *arena; // Backing arena for the hash table - DN_Pool pool; // Allocator for keys that are variable-sized buffers - DN_U32 initial_size; // Initial map size, map cannot shrink on erase below this size - DN_DSMapHashFunction *hash_function; // Custom hashing function to use if field is set - DN_U32 hash_seed; // Seed for the hashing function, when 0, DN_DS_MAP_DEFAULT_HASH_SEED is used - DN_DSMapFlags flags; -}; - -template struct DN_DSMapResult -{ - bool found; - DN_DSMapSlot *slot; - T *value; -}; - -#if !defined(DN_STB_SPRINTF_HEADER_ONLY) - #define STB_SPRINTF_IMPLEMENTATION - #define STB_SPRINTF_STATIC -#endif - -DN_MSVC_WARNING_PUSH -DN_MSVC_WARNING_DISABLE(4505) // Unused function warning -DN_GCC_WARNING_PUSH -DN_GCC_WARNING_DISABLE(-Wunused-function) -#include "../External/stb_sprintf.h" -DN_GCC_WARNING_POP -DN_MSVC_WARNING_POP - -DN_API bool DN_VerifyArgsF (DN_VerifyType type, bool expr, DN_CallSite call_site, DN_Str8 expr_str8, char const *fmt, ...); -DN_API bool DN_VerifyArgs (DN_VerifyType type, bool expr, DN_CallSite call_site, DN_Str8 expr_str8); - -#define DN_SPrintF(...) STB_SPRINTF_DECORATE(sprintf)(__VA_ARGS__) -#define DN_SNPrintF(...) STB_SPRINTF_DECORATE(snprintf)(__VA_ARGS__) -#define DN_VSPrintF(...) STB_SPRINTF_DECORATE(vsprintf)(__VA_ARGS__) -#define DN_VSNPrintF(...) STB_SPRINTF_DECORATE(vsnprintf)(__VA_ARGS__) - -DN_API bool DN_MemStartsWith (void const *lhs, DN_USize lhs_size, void const *rhs, DN_USize rhs_size); -DN_API bool DN_MemEq (void const *lhs, DN_USize lhs_size, void const *rhs, DN_USize rhs_size); -DN_API bool DN_MemEqUnsafe (void const *lhs, void const *rhs, DN_USize size); -#if defined(__cplusplus) -template T* DN_MemCopyObjT (T *dest, T const *src, DN_USize count); -#define DN_MemCopyObj(dest, src, count) DN_MemCopyObjT(dest, src, count) -#else -#define DN_MemCopyObj(dest, src, count) DN_Memcpy(dest, src, sizeof(*src) * count) -#endif - -DN_API DN_U64 DN_AtomicSetValue64 (DN_U64 volatile *target, DN_U64 value); -DN_API DN_U32 DN_AtomicSetValue32 (DN_U32 volatile *target, DN_U32 value); -DN_API DN_USize DN_AlignUpPowerOfTwoUSize (DN_USize val); -DN_API DN_U64 DN_AlignUpPowerOfTwoU64 (DN_U64 val); -DN_API DN_U32 DN_AlignUpPowerOfTwoU32 (DN_U32 val); - -DN_API void DN_ByteSwapU64Ptr (DN_U8* dest, DN_U64 src); -#define DN_ByteSwap64(val) ( \ - (((((DN_U64)(val)) >> 56) & 0xFF) << 0) | \ - (((((DN_U64)(val)) >> 48) & 0xFF) << 8) | \ - (((((DN_U64)(val)) >> 40) & 0xFF) << 16) | \ - (((((DN_U64)(val)) >> 32) & 0xFF) << 24) | \ - (((((DN_U64)(val)) >> 24) & 0xFF) << 32) | \ - (((((DN_U64)(val)) >> 16) & 0xFF) << 40) | \ - (((((DN_U64)(val)) >> 8) & 0xFF) << 48) | \ - (((((DN_U64)(val)) >> 0) & 0xFF) << 56) \ - ) -#define DN_ByteSwap32(val) ( \ - (((((DN_U32)(val)) >> 24) & 0xFF) << 0) | \ - (((((DN_U32)(val)) >> 16) & 0xFF) << 8) | \ - (((((DN_U32)(val)) >> 8) & 0xFF) << 16) | \ - (((((DN_U32)(val)) >> 0) & 0xFF) << 24) \ - ) -#define DN_ByteSwap24(val) ( \ - (((((DN_U32)(val)) >> 16) & 0xFF) << 0) | \ - (((((DN_U32)(val)) >> 8) & 0xFF) << 8) | \ - (((((DN_U32)(val)) >> 0) & 0xFF) << 16) \ - ) -#define DN_ByteSwap16(val) ( \ - (((((DN_U16)(val)) >> 8) & 0xFF) << 0) | \ - (((((DN_U16)(val)) >> 0) & 0xFF) << 8) \ - ) -#if defined(DN_64_BIT) - #define DN_ByteSwapUSize(val) DN_ByteSwap64(val) -#else - #define DN_ByteSwapUSize(val) DN_ByteSwap32(val) -#endif - - -DN_API DN_CPUIDResult DN_CPUID (DN_CPUIDArgs args); -DN_API DN_USize DN_CPUHasFeatureArray (DN_CPUReport const *report, DN_CPUFeatureQuery *features, DN_USize features_size); -DN_API bool DN_CPUHasFeature (DN_CPUReport const *report, DN_CPUFeature feature); -DN_API bool DN_CPUHasAllFeatures (DN_CPUReport const *report, DN_CPUFeature const *features, DN_USize features_size); -DN_API void DN_CPUSetFeature (DN_CPUReport *report, DN_CPUFeature feature); -DN_API DN_CPUReport DN_CPUGetReport (); - -DN_API void DN_TicketMutex_Begin (DN_TicketMutex *mutex); -DN_API void DN_TicketMutex_End (DN_TicketMutex *mutex); -DN_API DN_UInt DN_TicketMutex_MakeTicket (DN_TicketMutex *mutex); -DN_API void DN_TicketMutex_BeginTicket (DN_TicketMutex const *mutex, DN_UInt ticket); -DN_API bool DN_TicketMutex_CanLock (DN_TicketMutex const *mutex, DN_UInt ticket); - -DN_API void DN_BitUnsetInplace (DN_USize *flags, DN_USize bitfield); -DN_API void DN_BitSetInplace (DN_USize *flags, DN_USize bitfield); -DN_API bool DN_BitIsSet (DN_USize bits, DN_USize bits_to_set); -DN_API bool DN_BitIsNotSet (DN_USize bits, DN_USize bits_to_check); -DN_API bool DN_BitIsAny (DN_USize bits, DN_USize bits_to_check); -#define DN_BitClearNextLSB(value) (value) & ((value) - 1) - -DN_API DN_I64 DN_SafeAddI64 (DN_I64 a, DN_I64 b); -DN_API DN_I64 DN_SafeMulI64 (DN_I64 a, DN_I64 b); - -DN_API DN_U64 DN_SafeAddU64 (DN_U64 a, DN_U64 b); -DN_API DN_U64 DN_SafeMulU64 (DN_U64 a, DN_U64 b); - -DN_API DN_U64 DN_SafeSubU64 (DN_U64 a, DN_U64 b); -DN_API DN_U32 DN_SafeSubU32 (DN_U32 a, DN_U32 b); - -DN_API int DN_SaturateCastUSizeToInt (DN_USize val); -DN_API DN_I8 DN_SaturateCastUSizeToI8 (DN_USize val); -DN_API DN_I16 DN_SaturateCastUSizeToI16 (DN_USize val); -DN_API DN_I32 DN_SaturateCastUSizeToI32 (DN_USize val); -DN_API DN_I64 DN_SaturateCastUSizeToI64 (DN_USize val); - -DN_API int DN_SaturateCastU64ToInt (DN_U64 val); -DN_API DN_I8 DN_SaturateCastU8ToI8 (DN_U64 val); -DN_API DN_I16 DN_SaturateCastU16ToI16 (DN_U64 val); -DN_API DN_I32 DN_SaturateCastU32ToI32 (DN_U64 val); -DN_API DN_I64 DN_SaturateCastU64ToI64 (DN_U64 val); -DN_API DN_UInt DN_SaturateCastU64ToUInt (DN_U64 val); -DN_API DN_U8 DN_SaturateCastU64ToU8 (DN_U64 val); -DN_API DN_U16 DN_SaturateCastU64ToU16 (DN_U64 val); -DN_API DN_U32 DN_SaturateCastU64ToU32 (DN_U64 val); - -DN_API DN_U8 DN_SaturateCastUSizeToU8 (DN_USize val); -DN_API DN_U16 DN_SaturateCastUSizeToU16 (DN_USize val); -DN_API DN_U32 DN_SaturateCastUSizeToU32 (DN_USize val); -DN_API DN_U64 DN_SaturateCastUSizeToU64 (DN_USize val); - -DN_API int DN_SaturateCastISizeToInt (DN_ISize val); -DN_API DN_I8 DN_SaturateCastISizeToI8 (DN_ISize val); -DN_API DN_I16 DN_SaturateCastISizeToI16 (DN_ISize val); -DN_API DN_I32 DN_SaturateCastISizeToI32 (DN_ISize val); -DN_API DN_I64 DN_SaturateCastISizeToI64 (DN_ISize val); - -DN_API DN_UInt DN_SaturateCastISizeToUInt (DN_ISize val); -DN_API DN_U8 DN_SaturateCastISizeToU8 (DN_ISize val); -DN_API DN_U16 DN_SaturateCastISizeToU16 (DN_ISize val); -DN_API DN_U32 DN_SaturateCastISizeToU32 (DN_ISize val); -DN_API DN_U64 DN_SaturateCastISizeToU64 (DN_ISize val); - -DN_API DN_ISize DN_SaturateCastI64ToISize (DN_I64 val); -DN_API DN_I8 DN_SaturateCastI64ToI8 (DN_I64 val); -DN_API DN_I16 DN_SaturateCastI64ToI16 (DN_I64 val); -DN_API DN_I32 DN_SaturateCastI64ToI32 (DN_I64 val); - -DN_API DN_UInt DN_SaturateCastI64ToUInt (DN_I64 val); -DN_API DN_USize DN_SaturateCastI64ToUSize (DN_I64 val); -DN_API DN_U8 DN_SaturateCastI64ToU8 (DN_I64 val); -DN_API DN_U16 DN_SaturateCastI64ToU16 (DN_I64 val); -DN_API DN_U32 DN_SaturateCastI64ToU32 (DN_I64 val); -DN_API DN_U64 DN_SaturateCastI64ToU64 (DN_I64 val); - -DN_API DN_I8 DN_SaturateCastIntToI8 (int val); -DN_API DN_I16 DN_SaturateCastIntToI16 (int val); -DN_API DN_U8 DN_SaturateCastIntToU8 (int val); -DN_API DN_U16 DN_SaturateCastIntToU16 (int val); -DN_API DN_U32 DN_SaturateCastIntToU32 (int val); -DN_API DN_U64 DN_SaturateCastIntToU64 (int val); - -DN_API void DN_ASanPoisonMemoryRegion (void const volatile *ptr, DN_USize size); -DN_API void DN_ASanUnpoisonMemoryRegion (void const volatile *ptr, DN_USize size); - -DN_API DN_F32 DN_EpsilonClampF32 (DN_F32 value, DN_F32 target, DN_F32 epsilon); - -DN_API DN_MemStats DN_MemStatsSum (DN_MemStats lhs, DN_MemStats rhs); -DN_API DN_MemStats DN_MemStatsSumArray (DN_MemStats const *array, DN_USize size); - -// NOTE: `MemList` is an implementation of a classical `Arena` (e.g. bump allocator, can dynamically -// grow, frees by bumping pointer back, sub-divides a block of memory). The term `Arena` is reserved -// as a thin-layer over the functionality here to provide some use-after-free protection. See -// `Arena` for more info. -DN_API DN_MemList DN_MemListFromBuffer (void *buffer, DN_USize size, DN_MemFlags flags); -DN_API DN_MemList DN_MemListFromMemFuncs (DN_U64 reserve, DN_U64 commit, DN_MemFlags flags, DN_MemFuncs mem_funcs); -DN_API void DN_MemListDeinit (DN_MemList *mem); -DN_API bool DN_MemListCommit (DN_MemList *mem, DN_U64 size); -DN_API bool DN_MemListCommitTo (DN_MemList *mem, DN_U64 pos); -DN_API bool DN_MemListGrow (DN_MemList *mem, DN_U64 reserve, DN_U64 commit); -DN_API void * DN_MemListAlloc (DN_MemList *mem, DN_U64 size, uint8_t align, DN_ZMem zmem); -DN_API void * DN_MemListAllocContiguous (DN_MemList *mem, DN_U64 size, uint8_t align, DN_ZMem zmem); -DN_API void * DN_MemListCopy (DN_MemList *mem, void const *data, DN_U64 size, uint8_t align); -DN_API void DN_MemListPopTo (DN_MemList *mem, DN_U64 init_used); -DN_API void DN_MemListPop (DN_MemList *mem, DN_U64 amount); -DN_API DN_U64 DN_MemListPos (DN_MemList const *mem); -DN_API void DN_MemListClear (DN_MemList *mem); -DN_API bool DN_MemListOwnsPtr (DN_MemList const *mem, void *ptr); -DN_API DN_Str8x64 DN_MemListInfoStr8x64 (DN_MemListInfo info); -DN_API DN_MemListTemp DN_MemListTempBegin (DN_MemList *mem); -DN_API void DN_MemListTempEnd (DN_MemListTemp mem); -#define DN_MemListNew(arena, T, zmem) (T *)DN_MemListAlloc(arena, sizeof(T), alignof(T), zmem) -#define DN_MemListNewZ(arena, T) (T *)DN_MemListAlloc(arena, sizeof(T), alignof(T), DN_ZMem_Yes) -#define DN_MemListNewContiguous(arena, T, zmem) (T *)DN_MemListAllocContiguous(arena, sizeof(T), alignof(T), zmem) -#define DN_MemListNewContiguousZ(arena, T) (T *)DN_MemListAllocContiguous(arena, sizeof(T), alignof(T), DN_ZMem_Yes) -#define DN_MemListNewArray(arena, T, count, zmem) (T *)DN_MemListAlloc(arena, sizeof(T) * (count), alignof(T), zmem) -#define DN_MemListNewArrayZ(arena, T, count) (T *)DN_MemListAlloc(arena, sizeof(T) * (count), alignof(T), DN_ZMem_Yes) -#define DN_MemListNewArrayNoZ(arena, T, count) (T *)DN_MemListAlloc(arena, sizeof(T) * (count), alignof(T), DN_ZMem_No) -#define DN_MemListNewCopy(arena, T, src) (T *)DN_MemListCopy(arena, (src), sizeof(T), alignof(T)) -#define DN_MemListNewArrayCopy(arena, T, src, count) (T *)DN_MemListCopy(arena, (src), sizeof(T) * (count), alignof(T)) - -// NOTE: `Arena`'s in this codebase are thin-layers over `MemList` but additionally provide -// use-after-free (UAF) protection when using temporary memory regions (e.g. thread context scratch -// `TCScratch` or `Temp[Begin|End]` family of functions). -// -// These arenas associate themselves with the temporary memory region they begin in if it is -// constructed using the `Temp[Begin|End]` family of functions (TCScratch implicitly call these for -// you before handing you the arena). If you attempt to allocate from a different arena bound with a -// different temporary memory region than the active one an assertion is triggered. This protection -// is gated by the presence of the preprocessor definition `#define DN_ARENA_TEMP_MEM_UAF_GUARD 1`. -// -// Without the preprocessor definition UAF protection is compiled out (e.g. no-op). UAF protection -// is also not enabled if you use `ArenaFromMemList` which simply sets up a plain arena that -// forwards all calls into the `MemList` API. -// -// To get UAF protection, all allocations _must_ go through the `Arena` API, using the `MemList` -// field directly in the `Arena` will bypass these checks and lead to unusual behaviour. If you want -// to forgo any of this infrastructure store and use the `MemList` directly in your codebase. -// -// UAF Example -/* - DN_Arena arena = DN_ArenaFromHeap(DN_Megabytes(1), DN_MemFlags_Nil); - DN_Arena temp = DN_ArenaTempBeginFromArena(&arena); - { - // NOTE: You can also `TempBegin` with `&temp`, either is valid. They both have pointers to - // the same underlying memory block owned by `arena`. - DN_Arena nested_temp = DN_ArenaTempBeginFromArena(&arena); - - // NOTE: This allocation triggers the UAF guard and asserts! An allocation into `temp`'s memory - // region would be reset when we end `nested_temp`'s memory region since they are spawned from - // the same underlying memory block sitting in `arena`. - // - // But the intent here is that the caller is resetting `nested_temp`'s allocations and not - // `temp` hence the UAF protection triggers. - DN_U64 *u64 = DN_ArenaNewZ(&temp, DN_U64); - - DN_ArenaTempEnd(&nested_temp); - } - DN_ArenaTempEnd(&temp); - DN_ArenaDeinit(&arena); // Frees the memory - */ -DN_API DN_Arena DN_ArenaFromMemList (DN_MemList *mem); -DN_API DN_Arena DN_ArenaTempBeginFromMemList (DN_MemList *mem); -DN_API DN_Arena DN_ArenaTempBeginFromArena (DN_Arena *arena); -DN_API void DN_ArenaTempEnd (DN_Arena *arena, DN_ArenaReset reset); -DN_API void* DN_ArenaAlloc (DN_Arena *arena, DN_U64 size, uint8_t align, DN_ZMem z_mem); -DN_API void* DN_ArenaAllocContiguous (DN_Arena *arena, DN_U64 size, uint8_t align, DN_ZMem z_arena); -DN_API void* DN_ArenaCopy (DN_Arena *arena, void const *data, DN_U64 size, uint8_t align); -DN_API void DN_ArenaDeinit (DN_Arena *arena); - -#define DN_ArenaNew(arena, T, zmem) (T *)DN_ArenaAlloc(arena, sizeof(T), alignof(T), zmem) -#define DN_ArenaNewZ(arena, T) (T *)DN_ArenaAlloc(arena, sizeof(T), alignof(T), DN_ZMem_Yes) -#define DN_ArenaNewContiguous(arena, T, zmem) (T *)DN_ArenaAllocContiguous(arena, sizeof(T), alignof(T), zmem) -#define DN_ArenaNewContiguousZ(arena, T) (T *)DN_ArenaAllocContiguous(arena, sizeof(T), alignof(T), DN_ZMem_Yes) -#define DN_ArenaNewArray(arena, T, count, zmem) (T *)DN_ArenaAlloc(arena, sizeof(T) * (count), alignof(T), zmem) -#define DN_ArenaNewArrayZ(arena, T, count) (T *)DN_ArenaAlloc(arena, sizeof(T) * (count), alignof(T), DN_ZMem_Yes) -#define DN_ArenaNewArrayNoZ(arena, T, count) (T *)DN_ArenaAlloc(arena, sizeof(T) * (count), alignof(T), DN_ZMem_No) -#define DN_ArenaNewCopy(arena, T, src) (T *)DN_ArenaCopy(arena, (src), sizeof(T), alignof(T)) -#define DN_ArenaNewArrayCopy(arena, T, src, count) (T *)DN_ArenaCopy(arena, (src), sizeof(T) * (count), alignof(T)) - -DN_API DN_Pool DN_PoolFromArena (DN_Arena *arena, DN_U8 align); -DN_API bool DN_PoolIsValid (DN_Pool const *pool); -DN_API void * DN_PoolAlloc (DN_Pool *pool, DN_USize size); -DN_API void DN_PoolDealloc (DN_Pool *pool, void *ptr); -DN_API void * DN_PoolCopy (DN_Pool *pool, void const *data, DN_U64 size, uint8_t align); -#define DN_PoolNew(pool, T) (T *)DN_PoolAlloc(pool, sizeof(T)) -#define DN_PoolNewArray(pool, T, count) (T *)DN_PoolAlloc(pool, count * sizeof(T)) -#define DN_PoolNewCopy(pool, T, src) (T *)DN_PoolCopy (pool, (src), sizeof(T), alignof(T)) -#define DN_PoolNewArrayCopy(pool, T, src, count) (T *)DN_PoolCopy (pool, (src), sizeof(T) * (count), alignof(T)) - -DN_API DN_ErrSink* DN_ErrSinkBegin_ (DN_ErrSink *err, DN_ErrSinkMode mode, DN_CallSite call_site); -#define DN_ErrSinkBegin(err, mode) DN_ErrSinkBegin_(err, mode, DN_CallSiteNow) -#define DN_ErrSinkBeginDefault(err) DN_ErrSinkBegin(err, DN_ErrSinkMode_Nil) -DN_API bool DN_ErrSinkHasError (DN_ErrSink *err); -DN_API DN_ErrSinkMsg* DN_ErrSinkEnd (DN_Arena *arena, DN_ErrSink *err); -DN_API DN_Str8 DN_ErrSinkEndStr8 (DN_Arena *arena, DN_ErrSink *err); -DN_API void DN_ErrSinkEndIgnore (DN_ErrSink *err); -DN_API bool DN_ErrSinkEndLogError_ (DN_ErrSink *err, DN_CallSite call_site, DN_Str8 msg); -#define DN_ErrSinkEndLogError(err, err_msg) DN_ErrSinkEndLogError_(err, DN_CallSiteNow, err_msg) -DN_API bool DN_ErrSinkEndLogErrorFV_ (DN_ErrSink *err, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, va_list args); -#define DN_ErrSinkEndLogErrorFV(err, fmt, args) DN_ErrSinkEndLogErrorFV_(err, DN_CallSiteNow, fmt, args) -DN_API bool DN_ErrSinkEndLogErrorF_ (DN_ErrSink *err, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, ...); -#define DN_ErrSinkEndLogErrorF(err, fmt, ...) DN_ErrSinkEndLogErrorF_(err, DN_CallSiteNow, fmt, ##__VA_ARGS__) -DN_API void DN_ErrSinkEndExitIfErrorF_ (DN_ErrSink *err, DN_CallSite call_site, DN_U32 exit_val, DN_FMT_ATTRIB char const *fmt, ...); -#define DN_ErrSinkEndExitIfErrorF(err, exit_val, fmt, ...) DN_ErrSinkEndExitIfErrorF_(err, DN_CallSiteNow, exit_val, fmt, ##__VA_ARGS__) -DN_API void DN_ErrSinkEndExitIfErrorFV_ (DN_ErrSink *err, DN_CallSite call_site, DN_U32 exit_val, DN_FMT_ATTRIB char const *fmt, va_list args); -#define DN_ErrSinkEndExitIfErrorFV(err, exit_val, fmt, args) DN_ErrSinkEndExitIfErrorFV_(err, DN_CallSiteNow, exit_val, fmt, args) -DN_API void DN_ErrSinkAppendFV_ (DN_ErrSink *err, DN_U32 error_code, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, va_list args); -#define DN_ErrSinkAppendFV(error, error_code, fmt, args) DN_ErrSinkAppendFV_(error, error_code, DN_CallSiteNow, fmt, args) -DN_API void DN_ErrSinkAppendF_ (DN_ErrSink *err, DN_U32 error_code, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, ...); -#define DN_ErrSinkAppendF(error, error_code, fmt, ...) DN_ErrSinkAppendF_(error, error_code, DN_CallSiteNow, fmt, ##__VA_ARGS__) - -DN_API DN_TCInitArgs DN_TCInitArgsDefault (); -DN_API void DN_TCInit (DN_TCCore *tc, DN_U64 thread_id, DN_Arena *main_arena, DN_Arena *temp_arenas, DN_USize temp_arenas_count, DN_Arena *err_sink_arena); -DN_API void DN_TCInitFromMemFuncs (DN_TCCore *tc, DN_U64 thread_id, DN_TCInitArgs args, DN_MemFuncs mem_funcs); -DN_API void DN_TCDeinit (DN_TCCore *tc, DN_TCDeinitArenas deinit_arenas); -DN_API void DN_TCEquip (DN_TCCore *tc); -DN_API DN_TCCore* DN_TCGet (); -DN_API DN_Arena* DN_TCMainArena (); -DN_API DN_Pool* DN_TCMainPool (); -DN_API DN_Arena DN_TCTempArenaFromAllocator (DN_Allocator *conflicts, DN_USize count); -DN_API DN_Arena DN_TCTempArenaFromArena (DN_Arena **conflicts, DN_USize count); -DN_API DN_TCScratch DN_TCScratchBeginAllocator (DN_Allocator *conflicts, DN_USize count); -DN_API DN_TCScratch DN_TCScratchBeginArena (DN_Arena **conflicts, DN_USize count); -DN_API void DN_TCScratchEnd (DN_TCScratch *scratch); -DN_API void DN_TCSetFrameArena (DN_Arena *arena); -DN_API DN_Arena* DN_TCFrameArena (); -DN_API DN_ErrSink* DN_TCErrSink (); -#define DN_TCErrSinkBegin(mode) DN_ErrSinkBegin(DN_TCErrSink(), mode) -#define DN_TCErrSinkBeginDefault() DN_ErrSinkBeginDefault(DN_TCErrSink()) - -DN_API bool DN_CharIsAlphabet (char ch); -DN_API bool DN_CharIsDigit (char ch); -DN_API bool DN_CharIsAlphaNum (char ch); -DN_API bool DN_CharIsWhitespace (char ch); -DN_API bool DN_CharIsHex (char ch); -DN_API char DN_CharToLower (char ch); -DN_API char DN_CharToUpper (char ch); - -DN_API DN_U64FromResult DN_U64FromStr8 (DN_Str8 string, char separator); -DN_API DN_U64FromResult DN_U64FromPtr (void const *data, DN_USize size, char separator); -DN_API DN_U64 DN_U64FromPtrUnsafe (void const *data, DN_USize size, char separator); -DN_API DN_U64FromResult DN_U64FromHexPtr (void const *hex, DN_USize hex_count); -DN_API DN_U64 DN_U64FromHexPtrUnsafe (void const *hex, DN_USize hex_count); -DN_API DN_U64FromResult DN_U64FromHexStr8 (DN_Str8 hex); -DN_API DN_U64 DN_U64FromHexStr8Unsafe (DN_Str8 hex); -DN_API DN_U64 DN_U64FromU8x32HiBEUnsafe (DN_U8x32 const *val); // Get U64 stored in big-endian at the high bytes [24:32) -DN_API DN_U64FromResult DN_U64FromU8x32HiBE (DN_U8x32 const *val); // Checks [0:24) bytes aren't set before getting the U64 -DN_API DN_USize DN_USizeFromU8x32HiBEUnsafe (DN_U8x32 const *val); // Get USize stored in big-endian at the high bytes [32 - sizeof USize:32) -DN_API DN_USizeFromResult DN_USizeFromU8x32HiBE (DN_U8x32 const *val); // Checks [0:sizeof USize) bytes aren't set before getting the U64 -DN_API DN_I64FromResult DN_I64FromStr8 (DN_Str8 string, char separator); -DN_API DN_I64FromResult DN_I64FromPtr (void const *data, DN_USize size, char separator); -DN_API DN_I64 DN_I64FromPtrUnsafe (void const *data, DN_USize size, char separator); - -DN_API bool DN_U8x32Eq (DN_U8x32 const *lhs, DN_U8x32 const *rhs); -DN_API DN_U8x32 DN_U8x32FromBytesLeftPadZ (DN_U8 const *ptr, DN_USize count); -DN_API DN_U8x32 DN_U8x32FromHexUnsafe (DN_Str8 hex_32b); -DN_API DN_U8x32FromResult DN_U8x32FromHex (DN_Str8 hex_32b); -DN_API DN_U8x32FromResult DN_U8x32FromDecimalStr8 (DN_Str8 decimal); // Write decimal string (e.g. "12345") as big-endian 256-bit value - -DN_API DN_Allocator DN_AllocatorFromMemList (DN_MemList *mem); -DN_API DN_Allocator DN_AllocatorFromArena (DN_Arena *arena); -DN_API DN_Allocator DN_AllocatorFromPool (DN_Pool *pool); -DN_API void* DN_AllocatorAlloc (DN_Allocator allocator, DN_USize size, DN_U8 align, DN_ZMem z_mem); - -DN_API DN_USize DN_FmtVSize (DN_FMT_ATTRIB char const *fmt, va_list args); -DN_API DN_USize DN_FmtSize (DN_FMT_ATTRIB char const *fmt, ...); -DN_API DN_FmtAppendResult DN_FmtVAppend (char *buf, DN_USize *buf_size, DN_USize buf_max, char const *fmt, va_list args); -DN_API DN_FmtAppendResult DN_FmtAppend (char *buf, DN_USize *buf_size, DN_USize buf_max, char const *fmt, ...); -DN_API DN_FmtAppendResult DN_FmtAppendTruncate (char *buf, DN_USize *buf_size, DN_USize buf_max, DN_Str8 truncator, char const *fmt, ...); -DN_API DN_USize DN_CStr8Size (char const *src); -DN_API DN_USize DN_CStr16Size (wchar_t const *src); - -#define DN_Str16Lit(string) DN_Str16{(wchar_t *)(string), sizeof(string)/sizeof(string[0]) - 1} -#define DN_Str16FromPtr(data, size) DN_Literal(DN_Str16){(wchar_t *)(data), (DN_USize)(size)} - -#define DN_Str8Lit(c_str) DN_Literal(DN_Str8){(char *)(c_str), sizeof(c_str) - 1} -#define DN_Str8PrintFmt(string) (int)((string).size), (string).data - -#define DN_Str8FromPtr(data, size) DN_Literal(DN_Str8){(char *)(data), (DN_USize)(size)} -#define DN_Str8FromStruct(ptr) DN_Str8FromPtr((ptr)->data, (ptr)->size) -#define DN_Str8FromLitArray(c_array) DN_Str8FromPtr(c_array, DN_ArrayCountU(c_array)) -DN_API DN_Str8 DN_Str8AllocAllocator (DN_USize size, DN_ZMem z_mem, DN_Allocator allocator); -DN_API DN_Str8 DN_Str8AllocArena (DN_USize size, DN_ZMem z_mem, DN_Arena *arena); -DN_API DN_Str8 DN_Str8AllocPool (DN_USize size, DN_Pool *pool); - -DN_API DN_Str8 DN_Str8FromCStr8 (char const *src); -DN_API DN_Str8 DN_Str8FromCStr8Arena (char const *src, DN_Arena *arena); -DN_API DN_Str8 DN_Str8FromPtrArena (void const *data, DN_USize size, DN_Arena *arena); -DN_API DN_Str8 DN_Str8FromPtrPool (void const *data, DN_USize size, DN_Pool *pool); -DN_API DN_Str8 DN_Str8FromStr8Allocator (DN_Str8 string, DN_Allocator allocator); -DN_API DN_Str8 DN_Str8FromStr8Arena (DN_Str8 string, DN_Arena *arena); -DN_API DN_Str8 DN_Str8FromStr8Pool (DN_Str8 string, DN_Pool *pool); -DN_API DN_Str8 DN_Str8FromFmtVAllocator (DN_Allocator allocator, DN_FMT_ATTRIB char const *fmt, va_list args); -DN_API DN_Str8 DN_Str8FromFmtVArena (DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, va_list args); -DN_API DN_Str8 DN_Str8FromFmtAllocator (DN_Allocator allocator, DN_FMT_ATTRIB char const *fmt, ...); -DN_API DN_Str8 DN_Str8FromFmtArena (DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, ...); -DN_API DN_Str8 DN_Str8FromFmtVPool (DN_Pool *pool, DN_FMT_ATTRIB char const *fmt, va_list args); -DN_API DN_Str8 DN_Str8FromFmtPool (DN_Pool *pool, DN_FMT_ATTRIB char const *fmt, ...); - -DN_API DN_Str8x16 DN_Str8x16FromFmt (DN_FMT_ATTRIB char const *fmt, ...); -DN_API DN_Str8x16 DN_Str8x16FromFmtV (DN_FMT_ATTRIB char const *fmt, va_list args); -DN_API DN_Str8x32 DN_Str8x32FromFmt (DN_FMT_ATTRIB char const *fmt, ...); -DN_API DN_Str8x32 DN_Str8x32FromFmtV (DN_FMT_ATTRIB char const *fmt, va_list args); -DN_API DN_Str8x64 DN_Str8x64FromFmt (DN_FMT_ATTRIB char const *fmt, ...); -DN_API DN_Str8x64 DN_Str8x64FromFmtV (DN_FMT_ATTRIB char const *fmt, va_list args); -DN_API DN_Str8x128 DN_Str8x128FromFmt (DN_FMT_ATTRIB char const *fmt, ...); -DN_API DN_Str8x256 DN_Str8x256FromFmtV (DN_FMT_ATTRIB char const *fmt, va_list args); -DN_API DN_Str8x256 DN_Str8x256FromFmt (DN_FMT_ATTRIB char const *fmt, ...); -DN_API DN_Str8x256 DN_Str8x256FromFmtV (DN_FMT_ATTRIB char const *fmt, va_list args); -DN_API DN_Str8x512 DN_Str8x512FromFmt (DN_FMT_ATTRIB char const *fmt, ...); -DN_API DN_Str8x512 DN_Str8x512FromFmtV (DN_FMT_ATTRIB char const *fmt, va_list args); -DN_API DN_Str8x1024 DN_Str8x1024FromFmt (DN_FMT_ATTRIB char const *fmt, ...); -DN_API DN_Str8x1024 DN_Str8x1024FromFmtV (DN_FMT_ATTRIB char const *fmt, va_list args); -DN_API void DN_Str8x16AppendFmt (DN_Str8x16 *str, DN_FMT_ATTRIB char const *fmt, ...); -DN_API void DN_Str8x16AppendFmtV (DN_Str8x16 *str, DN_FMT_ATTRIB char const *fmt, va_list args); -DN_API void DN_Str8x32AppendFmt (DN_Str8x32 *str, DN_FMT_ATTRIB char const *fmt, ...); -DN_API void DN_Str8x32AppendFmtV (DN_Str8x32 *str, DN_FMT_ATTRIB char const *fmt, va_list args); -DN_API void DN_Str8x64AppendFmt (DN_Str8x64 *str, DN_FMT_ATTRIB char const *fmt, ...); -DN_API void DN_Str8x64AppendFmtV (DN_Str8x64 *str, DN_FMT_ATTRIB char const *fmt, va_list args); -DN_API void DN_Str8x128AppendFmt (DN_Str8x128 *str, DN_FMT_ATTRIB char const *fmt, ...); -DN_API void DN_Str8x128AppendFmtV (DN_Str8x128 *str, DN_FMT_ATTRIB char const *fmt, va_list args); -DN_API void DN_Str8x256AppendFmt (DN_Str8x256 *str, DN_FMT_ATTRIB char const *fmt, ...); -DN_API void DN_Str8x256AppendFmtV (DN_Str8x256 *str, DN_FMT_ATTRIB char const *fmt, va_list args); -DN_API void DN_Str8x512AppendFmt (DN_Str8x512 *str, DN_FMT_ATTRIB char const *fmt, ...); -DN_API void DN_Str8x512AppendFmtV (DN_Str8x512 *str, DN_FMT_ATTRIB char const *fmt, va_list args); -DN_API void DN_Str8x1024AppendFmt (DN_Str8x1024 *str, DN_FMT_ATTRIB char const *fmt, ...); -DN_API void DN_Str8x1024AppendFmtV (DN_Str8x1024 *str, DN_FMT_ATTRIB char const *fmt, va_list args); - -DN_API DN_Str8x32 DN_Str8x32FromU64 (DN_U64 val, char separator); -DN_API bool DN_Str8IsAll (DN_Str8 string, DN_Str8IsAllType is_all); -DN_API char * DN_Str8End (DN_Str8 string); -DN_API DN_Str8 DN_Str8Subset (DN_Str8 string, DN_USize offset, DN_USize size); -DN_API DN_Str8 DN_Str8Advance (DN_Str8 string, DN_USize amount); -DN_API DN_Str8 DN_Str8NextLine (DN_Str8 string); -DN_API DN_Str8BSplitResult DN_Str8BSplitArray (DN_Str8 string, DN_Str8 const *find, DN_USize find_size); -DN_API DN_Str8BSplitResult DN_Str8BSplit (DN_Str8 string, DN_Str8 find); -DN_API DN_Str8BSplitResult DN_Str8BSplitLastArray (DN_Str8 string, DN_Str8 const *find, DN_USize find_size); -DN_API DN_Str8BSplitResult DN_Str8BSplitLast (DN_Str8 string, DN_Str8 find); -DN_API DN_USize DN_Str8Split (DN_Str8 string, DN_Str8 delimiter, DN_Str8 *splits, DN_USize splits_count, DN_Str8SplitFlags mode); -DN_API DN_Str8SplitResult DN_Str8SplitArena (DN_Str8 string, DN_Str8 delimiter, DN_Str8SplitFlags mode, DN_Arena *arena); -DN_API DN_Str8FindResult DN_Str8FindStr8Array (DN_Str8 string, DN_Str8 const *find, DN_USize find_size, DN_Str8EqCase eq_case); -DN_API DN_Str8FindResult DN_Str8FindStr8 (DN_Str8 string, DN_Str8 find, DN_Str8EqCase eq_case); -DN_API DN_Str8FindResult DN_Str8Find (DN_Str8 string, DN_Str8FindFlag flags); -DN_API DN_Str8 DN_Str8Segment (DN_Arena *arena, DN_Str8 src, DN_USize segment_size, char segment_char); -DN_API DN_Str8 DN_Str8ReverseSegment (DN_Arena *arena, DN_Str8 src, DN_USize segment_size, char segment_char); -DN_API bool DN_Str8Eq (DN_Str8 lhs, DN_Str8 rhs, DN_Str8EqCase eq_case = DN_Str8EqCase_Sensitive); -DN_API bool DN_Str8EqInsensitive (DN_Str8 lhs, DN_Str8 rhs); -DN_API bool DN_Str8StartsWith (DN_Str8 string, DN_Str8 prefix, DN_Str8EqCase eq_case = DN_Str8EqCase_Sensitive); -DN_API bool DN_Str8StartsWithInsensitive (DN_Str8 string, DN_Str8 prefix); -DN_API bool DN_Str8EndsWith (DN_Str8 string, DN_Str8 prefix, DN_Str8EqCase eq_case = DN_Str8EqCase_Sensitive); -DN_API bool DN_Str8EndsWithInsensitive (DN_Str8 string, DN_Str8 prefix); -DN_API bool DN_Str8HasChar (DN_Str8 string, char ch); - -DN_API DN_Str8 DN_Str8TrimPrefix (DN_Str8 string, DN_Str8 prefix, DN_Str8EqCase eq_case = DN_Str8EqCase_Sensitive); -DN_API DN_Str8 DN_Str8TrimHexPrefix (DN_Str8 string); -DN_API DN_Str8 DN_Str8TrimSuffix (DN_Str8 string, DN_Str8 suffix, DN_Str8EqCase eq_case = DN_Str8EqCase_Sensitive); -DN_API DN_Str8 DN_Str8TrimAround (DN_Str8 string, DN_Str8 trim_string); -DN_API DN_Str8 DN_Str8TrimHeadWhitespace (DN_Str8 string); -DN_API DN_Str8 DN_Str8TrimTailWhitespace (DN_Str8 string); -DN_API DN_Str8 DN_Str8TrimWhitespaceAround (DN_Str8 string); -DN_API DN_Str8 DN_Str8TrimByteOrderMark (DN_Str8 string); - -DN_API DN_Str8 DN_Str8FileNameFromPath (DN_Str8 path); -DN_API DN_Str8 DN_Str8FileNameNoExtension (DN_Str8 path); -DN_API DN_Str8 DN_Str8FilePathNoExtension (DN_Str8 path); -DN_API DN_Str8 DN_Str8FileExtension (DN_Str8 path); -DN_API DN_Str8 DN_Str8FileDirectoryFromPath (DN_Str8 path); -DN_API DN_Str8 DN_Str8AppendF (DN_Arena *arena, DN_Str8 string, char const *fmt, ...); -DN_API DN_Str8 DN_Str8AppendFV (DN_Arena *arena, DN_Str8 string, char const *fmt, va_list args); -DN_API DN_Str8 DN_Str8FillF (DN_Arena *arena, DN_USize count, char const *fmt, ...); -DN_API DN_Str8 DN_Str8FillFV (DN_Arena *arena, DN_USize count, char const *fmt, va_list args); -DN_API void DN_Str8Remove (DN_Str8 *string, DN_USize offset, DN_USize size); -DN_API DN_Str8TruncResult DN_Str8TruncMiddlePtr (DN_Str8 str8, DN_USize side_size, DN_Str8 truncator, char *dest, DN_USize dest_max); -DN_API DN_Str8TruncResult DN_Str8TruncMiddle (DN_Str8 str8, DN_USize side_size, DN_Str8 truncator, DN_Arena *arena); -DN_API DN_Str8 DN_Str8Lower (DN_Str8 string, DN_Arena *arena); -DN_API DN_Str8 DN_Str8Upper (DN_Str8 string, DN_Arena *arena); -DN_API DN_Str8 DN_Str8Replace (DN_Str8 string, DN_Str8 find, DN_Str8 replace, DN_USize start_index, DN_Arena *arena, DN_Str8EqCase eq_case); -DN_API DN_Str8 DN_Str8ReplaceSensitive (DN_Str8 string, DN_Str8 find, DN_Str8 replace, DN_USize start_index, DN_Arena *arena); -DN_API DN_Str8 DN_Str8ReplaceInsensitive (DN_Str8 string, DN_Str8 find, DN_Str8 replace, DN_USize start_index, DN_Arena *arena); -DN_API DN_Str8 DN_Str8PadNewLinesAllocator (DN_Str8 string, DN_Str8 pad_string, DN_Allocator allocator); -DN_API DN_Str8 DN_Str8PadNewLinesArena (DN_Str8 string, DN_Str8 pad_string, DN_Arena *arena); -DN_API DN_Str8 DN_Str8LineBreakAllocator (DN_Str8 src, DN_USize desired_width, DN_Str8 delimiter, DN_Str8LineBreakMode mode, DN_Allocator allocator); -DN_API DN_Str8 DN_Str8LineBreakArena (DN_Str8 src, DN_USize desired_width, DN_Str8 delimiter, DN_Str8LineBreakMode mode, DN_Arena *arena); -DN_API DN_Str8 DN_Str8Table (DN_Str8 const* rows, DN_USize num_rows, DN_USize num_cols, DN_Str8TableFlags flags, DN_Arena *arena); - -#if DN_STR8_AVX512F -DN_API DN_Str8FindResult DN_Str8FindStr8AVX512F (DN_Str8 string, DN_Str8 find); -DN_API DN_Str8FindResult DN_Str8FindLastStr8AVX512F (DN_Str8 string, DN_Str8 find); -DN_API DN_Str8BSplitResult DN_Str8BSplitAVX512F (DN_Str8 string, DN_Str8 find); -DN_API DN_Str8BSplitResult DN_Str8BSplitLastAVX512F (DN_Str8 string, DN_Str8 find); -DN_API DN_USize DN_Str8SplitAVX512F (DN_Str8 string, DN_Str8 delimiter, DN_Str8 *splits, DN_USize splits_count, DN_Str8SplitFlags flags); -DN_API DN_Str8Slice DN_Str8SplitAllocAVX512F (DN_Arena *arena, DN_Str8 string, DN_Str8 delimiter, DN_Str8SplitFlags flags); -#endif - -DN_API DN_Str8 DN_Str8SliceRender (DN_Str8Slice array, DN_Str8 separator, DN_Arena *arena); -DN_API DN_Str8 DN_Str8RenderSpaceSep (DN_Str8Slice array, DN_Arena *arena); -DN_API int DN_Str8CompareNatural (DN_Str8 lhs, DN_Str8 rhs, DN_Str8EqCase eq_case); -DN_API int DN_Str8CompareLexicographic (DN_Str8 lhs, DN_Str8 rhs, DN_Str8EqCase eq_case); - -DN_API bool DN_Str16Eq (DN_Str16 lhs, DN_Str16 rhs); -DN_API DN_Str16 DN_Str16SliceRender (DN_Str16Slice array, DN_Str16 separator, DN_Arena *arena); -DN_API DN_Str16 DN_Str16RenderSpaceSep (DN_Str16Slice array, DN_Arena *arena); - -DN_API DN_Str8Builder DN_Str8BuilderFromArena (DN_Arena *arena); -DN_API DN_Str8Builder DN_Str8BuilderFromStr8PtrRef (DN_Arena *arena, DN_Str8 const *strings, DN_USize size); -DN_API DN_Str8Builder DN_Str8BuilderFromStr8PtrCopy (DN_Arena *arena, DN_Str8 const *strings, DN_USize size); -DN_API DN_Str8Builder DN_Str8BuilderFromBuilder (DN_Arena *arena, DN_Str8Builder const *builder); -DN_API bool DN_Str8BuilderAddArrayRef (DN_Str8Builder *builder, DN_Str8 const *strings, DN_USize size, DN_Str8BuilderAdd add); -DN_API bool DN_Str8BuilderAddArrayCopy (DN_Str8Builder *builder, DN_Str8 const *strings, DN_USize size, DN_Str8BuilderAdd add); -DN_API bool DN_Str8BuilderAddFV (DN_Str8Builder *builder, DN_Str8BuilderAdd add, DN_FMT_ATTRIB char const *fmt, va_list args); -#define DN_Str8BuilderAppendArrayRef(builder, strings, size) DN_Str8BuilderAddArrayRef(builder, strings, size, DN_Str8BuilderAdd_Append) -#define DN_Str8BuilderAppendArrayCopy(builder, strings, size) DN_Str8BuilderAddArrayCopy(builder, strings, size, DN_Str8BuilderAdd_Append) -#define DN_Str8BuilderAppendSliceRef(builder, slice) DN_Str8BuilderAddArrayRef(builder, slice.data, slice.size, DN_Str8BuilderAdd_Append) -#define DN_Str8BuilderAppendSliceCopy(builder, slice) DN_Str8BuilderAddArrayCopy(builder, slice.data, slice.size, DN_Str8BuilderAdd_Append) -DN_API bool DN_Str8BuilderAppendRef (DN_Str8Builder *builder, DN_Str8 string); -DN_API bool DN_Str8BuilderAppendCopy (DN_Str8Builder *builder, DN_Str8 string); -#define DN_Str8BuilderAppendFV(builder, fmt, args) DN_Str8BuilderAddFV(builder, DN_Str8BuilderAdd_Append, fmt, args) -DN_API bool DN_Str8BuilderAppendF (DN_Str8Builder *builder, DN_FMT_ATTRIB char const *fmt, ...); -DN_API bool DN_Str8BuilderAppendBytesRef (DN_Str8Builder *builder, void const *ptr, DN_USize size); -DN_API bool DN_Str8BuilderAppendBytesCopy (DN_Str8Builder *builder, void const *ptr, DN_USize size); -DN_API bool DN_Str8BuilderAppendBuilderRef (DN_Str8Builder *dest, DN_Str8Builder const *src); -DN_API bool DN_Str8BuilderAppendBuilderCopy (DN_Str8Builder *dest, DN_Str8Builder const *src); -#define DN_Str8BuilderPrependArrayRef(builder, strings, size) DN_Str8BuilderAddArrayRef(builder, strings, size, DN_Str8BuilderAdd_Prepend) -#define DN_Str8BuilderPrependArrayCopy(builder, strings, size) DN_Str8BuilderAddArrayCopy(builder, strings, size, DN_Str8BuilderAdd_Prepend) -#define DN_Str8BuilderPrependSliceRef(builder, slice) DN_Str8BuilderAddArrayRef(builder, slice.data, slice.size, DN_Str8BuilderAdd_Prepend) -#define DN_Str8BuilderPrependSliceCopy(builder, slice) DN_Str8BuilderAddArrayCopy(builder, slice.data, slice.size, DN_Str8BuilderAdd_Prepend) -DN_API bool DN_Str8BuilderPrependRef (DN_Str8Builder *builder, DN_Str8 string); -DN_API bool DN_Str8BuilderPrependCopy (DN_Str8Builder *builder, DN_Str8 string); -#define DN_Str8BuilderPrependFV(builder, fmt, args) DN_Str8BuilderAddFV(builder, DN_Str8BuilderAdd_Prepend, fmt, args) -DN_API bool DN_Str8BuilderPrependF (DN_Str8Builder *builder, DN_FMT_ATTRIB char const *fmt, ...); -DN_API bool DN_Str8BuilderErase (DN_Str8Builder *builder, DN_Str8 string); -DN_API DN_Str8 DN_Str8FromStr8BuilderAllocator (DN_Str8Builder const *builder, DN_Allocator allocator); -DN_API DN_Str8 DN_Str8FromStr8BuilderArena (DN_Str8Builder const *builder, DN_Arena *arena); -DN_API DN_Str8 DN_Str8FromStr8BuilderDelimitAllocator (DN_Str8Builder const *builder, DN_Str8 delimiter, DN_Allocator allocator); -DN_API DN_Str8 DN_Str8FromStr8BuilderDelimitArena (DN_Str8Builder const *builder, DN_Str8 delimiter, DN_Arena *arena); - -DN_API int DN_UTF8Encode (DN_U8 utf8[4], DN_U32 codepoint); -DN_API int DN_UTF16Encode (DN_U16 utf16[2], DN_U32 codepoint); -DN_API DN_UTF8DecodeResult DN_UTF8Decode (DN_Str8 stream); -DN_API bool DN_UTF8DecodeIterate (DN_UTF8DecodeIterator *it, DN_Str8 utf8); -DN_API DN_USize DN_USizeCodepointCountFromUTF8 (DN_Str8 str, DN_CodepointCountFlags flags); - -DN_API DN_U8 DN_U8FromHexNibble (char hex); -DN_API DN_NibbleFromU8Result DN_NibbleFromU8 (DN_U8 u8); - -DN_API DN_USize DN_BytesFromHex (DN_Str8 hex, void *dest, DN_USize dest_count); -DN_API DN_Str8 DN_BytesFromHexArena (DN_Str8 hex, DN_Arena *arena); -DN_API DN_USize DN_BytesFromHexPtr (char const *hex, DN_USize hex_count, void *dest, DN_USize dest_count); -DN_API DN_Str8 DN_BytesFromHexPtrArena (char const *hex, DN_USize hex_count, DN_Arena *arena); -DN_API DN_Str8 DN_BytesFromHexPtrPool (char const *hex, DN_USize hex_count, DN_Pool *pool); -DN_API DN_U8x16 DN_BytesFromHex32Ptr (char const *hex, DN_USize hex_count); -DN_API DN_U8x32 DN_BytesFromHex64Ptr (char const *hex, DN_USize hex_count); - -DN_API DN_HexU64 DN_HexFromU64 (DN_U64 value, DN_HexFromU64Type type); -DN_API DN_USize DN_HexFromPtrBytes (void const *bytes, DN_USize bytes_count, void *hex, DN_USize hex_count, DN_TrimLeadingZero trim_leading_z); -DN_API DN_Str8 DN_HexFromPtrBytesArena (void const *bytes, DN_USize bytes_count, DN_Arena *arena, DN_TrimLeadingZero trim_leading_z); -DN_API DN_USize DN_HexFromStr8Bytes (DN_Str8 bytes, void *hex, DN_USize hex_count, DN_TrimLeadingZero trim_leading_z); -DN_API DN_Str8 DN_HexFromStr8BytesArena (DN_Str8 bytes, DN_Arena *arena, DN_TrimLeadingZero trim_leading_z); -DN_API DN_Hex32 DN_Hex32FromPtr16b (void const *bytes, DN_USize bytes_count, DN_TrimLeadingZero trim_leading_z); -DN_API DN_Hex64 DN_Hex64FromPtr32b (void const *bytes, DN_USize bytes_count, DN_TrimLeadingZero trim_leading_z); -DN_API DN_Hex128 DN_Hex128FromPtr64b (void const *bytes, DN_USize bytes_count, DN_TrimLeadingZero trim_leading_z); - -DN_API DN_Str8x128 DN_AgeStr8FromMsU64 (DN_U64 duration_ms, DN_AgeUnit units); -DN_API DN_Str8x128 DN_AgeStr8FromSecU64 (DN_U64 duration_ms, DN_AgeUnit units); -DN_API DN_Str8x128 DN_AgeStr8FromSecF64 (DN_F64 sec, DN_AgeUnit units); - -DN_API int DN_IsLeapYear (int year); -DN_API bool DN_DateIsValid (DN_Date date); -DN_API DN_Date DN_DateFromUnixTimeMs (DN_USize unix_ts_ms); -DN_API DN_U64 DN_UnixTimeMsFromDate (DN_Date date); - -DN_API DN_Str8 DN_Str8FromByteType (DN_ByteType type); -DN_API DN_ByteCount DN_ByteCountFromU64 (DN_U64 byte_count, DN_ByteType type); -DN_API DN_Str8x32 DN_Str8x32FromByteCountU64 (DN_U64 byte_count, DN_ByteType type); -#define DN_Str8x32FromByteCountU64Auto(bytes) DN_Str8x32FromByteCountU64(bytes, DN_ByteType_Auto) - -// NOTE: Profiler -// Overview -// Basic profiler that tracks the duration of marked-up regions which are denoted by an -// opening and closing `anchor`. The profiler works in "frame" life-cycles which can by cycled by -// calling `DN_ProfilerNewFrame`. The number of frames that the profiler will persist is chosen by -// `anchors_per_frame`. -// -// For example if you pass a buffer of 1024 `anchors` and `anchors_per_frame = 128` then the -// profiler will hold onto 8 (1024 anchors / 128 anchors per frame) frames of profiling -// information. The profiler will cycle through the 8 frames of anchors and upon reaching the end -// begin overwriting the oldest frames worth of anchors. -// -// Once a frame has ended the just completed buffer of anchors that was just written to can be -// read by the caller and visualised to find the timings of each region, relative to the duration -// of the frame until it is eventually overwritten by calling `DN_ProfilerNewFrame` once the -// profiler has cycled through all the other frames. -// -// The caller must upfront determine the number of anchors and frames the profiler should have and -// pass it in. The profiler does not allocate any memory. -// -// When profiling functions that are invoked from different call-stacks the exclusive elapsed -// duration can exceed its inclusive duration. This is by design as the profiler makes no -// effort to distinguish between the different call-tree that a zone may have been triggered by. -// The profiler simpler always updates the same static anchor assigned for that zone. -// -// API -// DN_ProfilerInit -// You can set `tsc_now` to NULL to use the default timer mechanic which relies on -// DN_CPUGetTSC() which essentially uses __rdtsc. `tsc_frequency` must however always be -// provided, for, DN_CPUGetTSC() you can use `DN_OS_EstimateTSCPerSecond()` to calculate the -// frequency of `__rdtsc`. -// DN_ProfilerNewFrame -// Always call `DN_ProfilerNewFrame` at-least once using the `Zone` family of functions as it -// sets up -// DN_ProfilerBeginZone -// The zeroth anchor is reserved for profiling the begin and end of a profiler frame. If you are -// manually specifying `anchor_index` (e.g. using an enum) ensure that the first anchor index -// starts from `1`. Note that the `BeginZoneAuto` macro uses `__COUNTER__ + 1` to skip the 0th -// zone. -// DN_ProfilerFrameAnchors -// Returns the current frame's (e.g. the anchors that the profiler is currently writing to) -// worth of anchors -#define DN_ProfilerZoneLoop(prof, name, index) \ - DN_ProfilerZone DN_UniqueName(zone_) = DN_ProfilerBeginZone(prof, DN_Str8Lit(name), index), DN_UniqueName(dummy_) = {}; \ - DN_UniqueName(dummy_).begin_tsc == 0; \ - DN_ProfilerEndZone(DN_UniqueName(zone_)), DN_UniqueName(dummy_).begin_tsc = 1 - -#define DN_ProfilerZoneLoopAuto(prof, name) DN_ProfilerZoneLoop(prof, name, __COUNTER__ + 1) -DN_API DN_Profiler DN_ProfilerInit (DN_ProfilerAnchor *anchors, DN_USize count, DN_USize anchors_per_frame, DN_ProfilerTSCNowFunc *tsc_now, DN_U64 tsc_frequency); -DN_API DN_ProfilerZone DN_ProfilerBeginZone (DN_Profiler *profiler, DN_Str8 name, DN_U16 anchor_index); -#define DN_ProfilerBeginZoneAuto(prof, name) DN_ProfilerBeginZone(prof, DN_Str8Lit(name), __COUNTER__ + 1) -DN_API void DN_ProfilerEndZone (DN_ProfilerZone zone); -DN_API DN_USize DN_ProfilerFrameCount (DN_Profiler const *profiler); -DN_API DN_ProfilerAnchorArray DN_ProfilerFrameAnchorsFromIndex (DN_Profiler *profiler, DN_USize frame_index); -DN_API DN_ProfilerAnchorArray DN_ProfilerFrameAnchors (DN_Profiler *profiler); -DN_API void DN_ProfilerNewFrame (DN_Profiler *profiler); -DN_API DN_USize DN_ProfilerFmtAnchor (DN_ProfilerAnchor anchor, DN_U64 tsc_frequency, char *buffer, DN_USize count); -DN_API DN_Str8 DN_ProfilerFmtAnchorStr8 (DN_ProfilerAnchor anchor, DN_U64 tsc_frequency, DN_Arena *arena); -DN_API void DN_ProfilerFmtToStdout (DN_Profiler *profiler); -DN_API DN_F64 DN_ProfilerSecFromTSC (DN_Profiler *profiler, DN_U64 duration_tsc); -DN_API DN_F64 DN_ProfilerMsFromTSC (DN_Profiler *profiler, DN_U64 duration_tsc); - -DN_API void DN_QSort (void *array, DN_USize array_size, DN_USize elem_size, void *user_context, DN_QSortCompareFunc *compare); -DN_API bool DN_QSortCompareStr8NaturalAsc (void const* lhs, void const *rhs, void *user_context); -DN_API bool DN_QSortCompareStr8NaturalDesc (void const* lhs, void const *rhs, void *user_context); -DN_API bool DN_QSortCompareStr8LexicographicAsc (void const* lhs, void const *rhs, void *user_context); -DN_API bool DN_QSortCompareStr8LexicographicDesc (void const* lhs, void const *rhs, void *user_context); -DN_API bool DN_QSortCompareBytesLT (void const* lhs, void const *rhs, void *user_context); -DN_API bool DN_QSortCompareBytesGT (void const* lhs, void const *rhs, void *user_context); -DN_API void DN_QSortBytesLT (void *array, DN_USize array_size, DN_USize elem_size); -DN_API void DN_QSortBytesGT (void *array, DN_USize array_size, DN_USize elem_size); -DN_API void DN_QSortStr8NaturalAsc (DN_Str8 *array, DN_USize array_size, DN_Str8EqCase eq_case); -DN_API void DN_QSortStr8NaturalDesc (DN_Str8 *array, DN_USize array_size, DN_Str8EqCase eq_case); -DN_API void DN_QSortStr8LexicographicAsc (DN_Str8 *array, DN_USize array_size, DN_Str8EqCase eq_case); -DN_API void DN_QSortStr8LexicographicDesc (DN_Str8 *array, DN_USize array_size, DN_Str8EqCase eq_case); - -DN_API DN_PCG32 DN_PCG32Init (DN_U64 seed); -DN_API DN_U32 DN_PCG32Next (DN_PCG32 *rng); -DN_API DN_U64 DN_PCG32Next64 (DN_PCG32 *rng); -DN_API DN_U32 DN_PCG32Range (DN_PCG32 *rng, DN_U32 low, DN_U32 high); -DN_API DN_F32 DN_PCG32NextF32 (DN_PCG32 *rng); -DN_API DN_F64 DN_PCG32NextF64 (DN_PCG32 *rng); -DN_API void DN_PCG32Advance (DN_PCG32 *rng, DN_U64 delta); - -#if !defined(DN_FNV1A32_SEED) - #define DN_FNV1A32_SEED 2166136261U -#endif - -#if !defined(DN_FNV1A64_SEED) - #define DN_FNV1A64_SEED 14695981039346656037ULL -#endif - -DN_API DN_U32 DN_FNV1AHashU32FromBytes (void const *bytes, DN_USize size, DN_U32 seed); -DN_API DN_U64 DN_FNV1AHashU64FromBytes (void const *bytes, DN_USize size, DN_U64 seed); - -DN_API DN_U32 DN_MurmurHash3HashU32FromBytesX86 (void const *bytes, int len, DN_U32 seed); -DN_API DN_MurmurHash3 DN_MurmurHash3HashU128FromBytesX64 (void const *bytes, int len, DN_U32 seed); -DN_API DN_U64 DN_MurmurHash3HashU64FromBytesX64 (void const *bytes, int len, DN_U32 seed); -DN_API DN_U32 DN_MurmurHash3HashU32FromBytesX64 (void const *bytes, int len, DN_U32 seed); - -#if defined(DN_64_BIT) - #define DN_MurmurHash3HashU32FromBytes(bytes, len, seed) DN_MurmurHash3HashU32FromBytesX64(bytes, len, seed) -#else - #define DN_MurmurHash3HashU32FromBytes(bytes, len, seed) DN_MurmurHash3HashU32FromBytesX86(bytes, len, seed) -#endif - -#define DN_ANSICodeBoldLit "\x1b[1m" -#define DN_ANSICodeResetLit "\x1b[0m" -DN_API DN_Str8x32 DN_Str8x32FromANSIColourCodeU8RGB (DN_ANSIColourMode mode, DN_U8 r, DN_U8 g, DN_U8 b); -DN_API DN_Str8x32 DN_Str8x32FromANSIColourCodeV3F32RGB255 (DN_ANSIColourMode mode, DN_V3F32 rgb_255); -DN_API DN_Str8x32 DN_Str8x32FromANSIColourCodeU32RGB (DN_ANSIColourMode mode, DN_U32 value); -DN_API DN_Str8 DN_Str8FromStr8ANSIColourU8RGBArena (DN_ANSIColourMode mode, DN_Str8 str8, DN_U8 r, DN_U8 g, DN_U8 b, DN_Arena *arena); -DN_API DN_Str8 DN_Str8FromStr8ANSIColourV3F32RGB255Arena (DN_ANSIColourMode mode, DN_Str8 str8, DN_V3F32 rgb_255, DN_Arena *arena); -DN_API DN_Str8 DN_Str8FromFmtANSIColourU8RGBArena (DN_ANSIColourMode mode, DN_U8 r, DN_U8 g, DN_U8 b, DN_Arena *arena, char const *fmt, ...); -DN_API DN_Str8 DN_Str8FromFmtANSIColourV3F32RGB255Arena (DN_ANSIColourMode mode, DN_V3F32 rgb_255, DN_Arena *arena, char const *fmt, ...); - -// NOTE: Create log printable lines with a date prefix, severity and message. The platform -// implementation should call `SetPrintFunc` to intercept the log messages and output to the desired -// destination (log file, standard out, e.t.c.). When the library is initialised `DN_Init` to with -// OS functionality enabled, the log callback is by default set to outputting via standard out. -DN_API DN_LogPrefixSize DN_LogMakePrefix (DN_LogStyle style, DN_LogTypeParam type, DN_CallSite call_site, DN_LogDate date, char *dest, DN_USize dest_size); -DN_API void DN_LogSetPrintFunc (DN_LogPrintFunc *print_func, void *user_data); -DN_API void DN_LogPrintF (DN_LogTypeParam type, DN_CallSite call_site, DN_LogFlags flags, DN_FMT_ATTRIB char const *fmt, ...); -DN_API void DN_LogPrintFV (DN_LogTypeParam type, DN_CallSite call_site, DN_LogFlags flags, DN_FMT_ATTRIB char const *fmt, va_list args); -DN_API DN_LogTypeParam DN_LogTypeParamFromType (DN_LogType type); -#define DN_LogF(type, fmt, ...) DN_LogPrintF(DN_LogTypeParamFromType(type), DN_CallSiteNow, DN_LogFlags_Nil, fmt, ##__VA_ARGS__) - -#define DN_LogDebugF(fmt, ...) DN_LogPrintF(DN_LogTypeParamFromType(DN_LogType_Debug), DN_CallSiteNow, DN_LogFlags_Nil, fmt, ##__VA_ARGS__) -#define DN_LogInfoF(fmt, ...) DN_LogPrintF(DN_LogTypeParamFromType(DN_LogType_Info), DN_CallSiteNow, DN_LogFlags_Nil, fmt, ##__VA_ARGS__) -#define DN_LogWarningF(fmt, ...) DN_LogPrintF(DN_LogTypeParamFromType(DN_LogType_Warning), DN_CallSiteNow, DN_LogFlags_Nil, fmt, ##__VA_ARGS__) -#define DN_LogErrorF(fmt, ...) DN_LogPrintF(DN_LogTypeParamFromType(DN_LogType_Error), DN_CallSiteNow, DN_LogFlags_Nil, fmt, ##__VA_ARGS__) - -#define DN_LogFlagF(type, flags, fmt, ...) DN_LogPrintF(DN_LogTypeParamFromType(type), DN_CallSiteNow, flags, fmt, ##__VA_ARGS__) -#define DN_LogFlagDebugF(flags, fmt, ...) DN_LogPrintF(DN_LogTypeParamFromType(DN_LogType_Debug), DN_CallSiteNow, flags, fmt, ##__VA_ARGS__) -#define DN_LogFlagInfoF(flags, fmt, ...) DN_LogPrintF(DN_LogTypeParamFromType(DN_LogType_Info), DN_CallSiteNow, flags, fmt, ##__VA_ARGS__) -#define DN_LogFlagWarningF(flags, fmt, ...) DN_LogPrintF(DN_LogTypeParamFromType(DN_LogType_Warning), DN_CallSiteNow, flags, fmt, ##__VA_ARGS__) -#define DN_LogFlagErrorF(flags, fmt, ...) DN_LogPrintF(DN_LogTypeParamFromType(DN_LogType_Error), DN_CallSiteNow, flags, fmt, ##__VA_ARGS__) - - -// NOTE: OS primitives that the OS layer can provide for the base layer but is optional. -#if defined(DN_FREESTANDING) -#define DN_StackTraceFromArena(...) {} -#define DN_StackTraceFromAllocator(...) {} -#define DN_StackTraceIterate(...) false -#define DN_Str8FromStackTraceAllocator(...) DN_Str8Lit("N/A") -#define DN_Str8FromStackTraceArena(...) DN_Str8Lit("N/A") -#define DN_Str8FromStackTraceNowAllocator(...) DN_Str8Lit("N/A") -#define DN_Str8FromStackTraceNowArena(...) DN_Str8Lit("N/A") -#define DN_Str8FromStackTraceNowHeap(...) DN_Str8Lit("N/A") -#define DN_StackTraceGetFrames(...) -#define DN_StackTraceRawFrameToFrame(...) -#define DN_StackTracePrint(...) -#define DN_StackTraceReloadSymbols(...) -#else -DN_API DN_StackTrace DN_StackTraceFromArena (DN_Arena *arena, DN_U16 limit); -DN_API DN_StackTrace DN_StackTraceFromAllocator (DN_Allocator allocator, DN_U16 limit); -DN_API bool DN_StackTraceIterate (DN_StackTraceIterator *it, DN_StackTrace const *walk); -DN_API DN_Str8 DN_Str8FromStackTraceAllocator (DN_Allocator allocator, DN_StackTrace const *walk, DN_U16 skip); -DN_API DN_Str8 DN_Str8FromStackTraceArena (DN_Arena *arena, DN_StackTrace const *walk, DN_U16 skip); -DN_API DN_Str8 DN_Str8FromStackTraceNowAllocator (DN_Arena *arena, DN_U16 limit, DN_U16 skip); -DN_API DN_Str8 DN_Str8FromStackTraceNowArena (DN_Arena *arena, DN_U16 limit, DN_U16 skip); -DN_API DN_Str8 DN_Str8FromStackTraceNowHeap (DN_U16 limit, DN_U16 skip); -DN_API DN_StackTraceFrameSlice DN_StackTraceGetFrames (DN_Arena *arena, DN_U16 limit); -DN_API DN_StackTraceFrame DN_StackTraceRawFrameToFrame (DN_Arena *arena, DN_StackTraceRawFrame raw_frame); -DN_API void DN_StackTracePrint (DN_U16 limit); -DN_API void DN_StackTraceReloadSymbols (); -#endif - -DN_API DN_F32 DN_F32Lerp (DN_F32 a, DN_F32 t, DN_F32 b); -DN_API DN_F32 DN_F32Floor (DN_F32 val); -DN_API DN_F32 DN_F32Ceil (DN_F32 val); -DN_API DN_F32 DN_F32RoundHalfUp (DN_F32 val); - -#define DN_V2I32Zero DN_Literal(DN_V2I32){{(DN_I32)(0), (DN_I32)(0)}} -#define DN_V2I32One DN_Literal(DN_V2I32){{(DN_I32)(1), (DN_I32)(1)}} -#define DN_V2I32From1N(x) DN_Literal(DN_V2I32){{(DN_I32)(x), (DN_I32)(x)}} -#define DN_V2I32From2N(x, y) DN_Literal(DN_V2I32){{(DN_I32)(x), (DN_I32)(y)}} -#define DN_V2I32InitV2(xy) DN_Literal(DN_V2I32){{(DN_I32)(xy).x, (DN_I32)(xy).y}} - -DN_API bool operator!= (DN_V2I32 lhs, DN_V2I32 rhs); -DN_API bool operator== (DN_V2I32 lhs, DN_V2I32 rhs); -DN_API bool operator>= (DN_V2I32 lhs, DN_V2I32 rhs); -DN_API bool operator<= (DN_V2I32 lhs, DN_V2I32 rhs); -DN_API bool operator< (DN_V2I32 lhs, DN_V2I32 rhs); -DN_API bool operator> (DN_V2I32 lhs, DN_V2I32 rhs); -DN_API DN_V2I32 operator- (DN_V2I32 lhs, DN_V2I32 rhs); -DN_API DN_V2I32 operator- (DN_V2I32 lhs); -DN_API DN_V2I32 operator+ (DN_V2I32 lhs, DN_V2I32 rhs); -DN_API DN_V2I32 operator* (DN_V2I32 lhs, DN_V2I32 rhs); -DN_API DN_V2I32 operator* (DN_V2I32 lhs, DN_F32 rhs); -DN_API DN_V2I32 operator* (DN_V2I32 lhs, DN_I32 rhs); -DN_API DN_V2I32 operator/ (DN_V2I32 lhs, DN_V2I32 rhs); -DN_API DN_V2I32 operator/ (DN_V2I32 lhs, DN_F32 rhs); -DN_API DN_V2I32 operator/ (DN_V2I32 lhs, DN_I32 rhs); -DN_API DN_V2I32& operator*= (DN_V2I32& lhs, DN_V2I32 rhs); -DN_API DN_V2I32& operator*= (DN_V2I32& lhs, DN_F32 rhs); -DN_API DN_V2I32& operator*= (DN_V2I32& lhs, DN_I32 rhs); -DN_API DN_V2I32& operator/= (DN_V2I32& lhs, DN_V2I32 rhs); -DN_API DN_V2I32& operator/= (DN_V2I32& lhs, DN_F32 rhs); -DN_API DN_V2I32& operator/= (DN_V2I32& lhs, DN_I32 rhs); -DN_API DN_V2I32& operator-= (DN_V2I32& lhs, DN_V2I32 rhs); -DN_API DN_V2I32& operator+= (DN_V2I32& lhs, DN_V2I32 rhs); - -DN_API DN_V2I32 DN_V2I32Min (DN_V2I32 a, DN_V2I32 b); -DN_API DN_V2I32 DN_V2I32Max (DN_V2I32 a, DN_V2I32 b); -DN_API DN_V2I32 DN_V2I32Abs (DN_V2I32 a); - -#define DN_V2U16Zero DN_Literal(DN_V2U16){{(DN_U16)(0), (DN_U16)(0)}} -#define DN_V2U16One DN_Literal(DN_V2U16){{(DN_U16)(1), (DN_U16)(1)}} -#define DN_V2U16From1N(x) DN_Literal(DN_V2U16){{(DN_U16)(x), (DN_U16)(x)}} -#define DN_V2U16From2N(x, y) DN_Literal(DN_V2U16){{(DN_U16)(x), (DN_U16)(y)}} - -DN_API bool operator!= (DN_V2U16 lhs, DN_V2U16 rhs); -DN_API bool operator== (DN_V2U16 lhs, DN_V2U16 rhs); -DN_API bool operator>= (DN_V2U16 lhs, DN_V2U16 rhs); -DN_API bool operator<= (DN_V2U16 lhs, DN_V2U16 rhs); -DN_API bool operator< (DN_V2U16 lhs, DN_V2U16 rhs); -DN_API bool operator> (DN_V2U16 lhs, DN_V2U16 rhs); -DN_API DN_V2U16 operator- (DN_V2U16 lhs, DN_V2U16 rhs); -DN_API DN_V2U16 operator+ (DN_V2U16 lhs, DN_V2U16 rhs); -DN_API DN_V2U16 operator* (DN_V2U16 lhs, DN_V2U16 rhs); -DN_API DN_V2U16 operator* (DN_V2U16 lhs, DN_F32 rhs); -DN_API DN_V2U16 operator* (DN_V2U16 lhs, DN_I32 rhs); -DN_API DN_V2U16 operator/ (DN_V2U16 lhs, DN_V2U16 rhs); -DN_API DN_V2U16 operator/ (DN_V2U16 lhs, DN_F32 rhs); -DN_API DN_V2U16 operator/ (DN_V2U16 lhs, DN_I32 rhs); -DN_API DN_V2U16& operator*= (DN_V2U16& lhs, DN_V2U16 rhs); -DN_API DN_V2U16& operator*= (DN_V2U16& lhs, DN_F32 rhs); -DN_API DN_V2U16& operator*= (DN_V2U16& lhs, DN_I32 rhs); -DN_API DN_V2U16& operator/= (DN_V2U16& lhs, DN_V2U16 rhs); -DN_API DN_V2U16& operator/= (DN_V2U16& lhs, DN_F32 rhs); -DN_API DN_V2U16& operator/= (DN_V2U16& lhs, DN_I32 rhs); -DN_API DN_V2U16& operator-= (DN_V2U16& lhs, DN_V2U16 rhs); -DN_API DN_V2U16& operator+= (DN_V2U16& lhs, DN_V2U16 rhs); - -#define DN_V2U32Zero DN_Literal(DN_V2U32){{(DN_U32)(0), (DN_U32)(0)}} -#define DN_V2U32One DN_Literal(DN_V2U32){{(DN_U32)(1), (DN_U32)(1)}} -#define DN_V2U32From1N(x) DN_Literal(DN_V2U32){{(DN_U32)(x), (DN_U32)(x)}} -#define DN_V2U32From2N(x, y) DN_Literal(DN_V2U32){{(DN_U32)(x), (DN_U32)(y)}} - -#define DN_V2F32Zero DN_Literal(DN_V2F32){{(DN_F32)(0), (DN_F32)(0)}} -#define DN_V2F32One DN_Literal(DN_V2F32){{(DN_F32)(1), (DN_F32)(1)}} -#define DN_V2F32From1N(x) DN_Literal(DN_V2F32){{(DN_F32)(x), (DN_F32)(x)}} -#define DN_V2F32From2N(x, y) DN_Literal(DN_V2F32){{(DN_F32)(x), (DN_F32)(y)}} -#define DN_V2F32FromV2(xy) DN_Literal(DN_V2F32){{(DN_F32)(xy).x, (DN_F32)(xy).y}} - -DN_API DN_V2F32 DN_V2F32Lerp (DN_V2F32 a, DN_F32 t, DN_V2F32 b); - -DN_API bool operator!= (DN_V2F32 lhs, DN_V2F32 rhs); -DN_API bool operator== (DN_V2F32 lhs, DN_V2F32 rhs); -DN_API bool operator>= (DN_V2F32 lhs, DN_V2F32 rhs); -DN_API bool operator<= (DN_V2F32 lhs, DN_V2F32 rhs); -DN_API bool operator< (DN_V2F32 lhs, DN_V2F32 rhs); -DN_API bool operator> (DN_V2F32 lhs, DN_V2F32 rhs); - -DN_API DN_V2F32 operator- (DN_V2F32 lhs); -DN_API DN_V2F32 operator- (DN_V2F32 lhs, DN_V2F32 rhs); -DN_API DN_V2F32 operator- (DN_V2F32 lhs, DN_V2I32 rhs); -DN_API DN_V2F32 operator- (DN_V2F32 lhs, DN_F32 rhs); -DN_API DN_V2F32 operator- (DN_V2F32 lhs, DN_I32 rhs); - -DN_API DN_V2F32 operator+ (DN_V2F32 lhs, DN_V2F32 rhs); -DN_API DN_V2F32 operator+ (DN_V2F32 lhs, DN_V2I32 rhs); -DN_API DN_V2F32 operator+ (DN_V2F32 lhs, DN_F32 rhs); -DN_API DN_V2F32 operator+ (DN_V2F32 lhs, DN_I32 rhs); - -DN_API DN_V2F32 operator* (DN_V2F32 lhs, DN_V2F32 rhs); -DN_API DN_V2F32 operator* (DN_V2F32 lhs, DN_V2I32 rhs); -DN_API DN_V2F32 operator* (DN_V2F32 lhs, DN_F32 rhs); -DN_API DN_V2F32 operator* (DN_V2F32 lhs, DN_I32 rhs); - -DN_API DN_V2F32 operator/ (DN_V2F32 lhs, DN_V2F32 rhs); -DN_API DN_V2F32 operator/ (DN_V2F32 lhs, DN_V2I32 rhs); -DN_API DN_V2F32 operator/ (DN_V2F32 lhs, DN_F32 rhs); -DN_API DN_V2F32 operator/ (DN_V2F32 lhs, DN_I32 rhs); - -DN_API DN_V2F32& operator*= (DN_V2F32& lhs, DN_V2F32 rhs); -DN_API DN_V2F32& operator*= (DN_V2F32& lhs, DN_V2I32 rhs); -DN_API DN_V2F32& operator*= (DN_V2F32& lhs, DN_F32 rhs); -DN_API DN_V2F32& operator*= (DN_V2F32& lhs, DN_I32 rhs); - -DN_API DN_V2F32& operator/= (DN_V2F32& lhs, DN_V2F32 rhs); -DN_API DN_V2F32& operator/= (DN_V2F32& lhs, DN_V2I32 rhs); -DN_API DN_V2F32& operator/= (DN_V2F32& lhs, DN_F32 rhs); -DN_API DN_V2F32& operator/= (DN_V2F32& lhs, DN_I32 rhs); - -DN_API DN_V2F32& operator-= (DN_V2F32& lhs, DN_V2F32 rhs); -DN_API DN_V2F32& operator-= (DN_V2F32& lhs, DN_V2I32 rhs); -DN_API DN_V2F32& operator-= (DN_V2F32& lhs, DN_F32 rhs); -DN_API DN_V2F32& operator-= (DN_V2F32& lhs, DN_I32 rhs); - -DN_API DN_V2F32& operator+= (DN_V2F32& lhs, DN_V2F32 rhs); -DN_API DN_V2F32& operator+= (DN_V2F32& lhs, DN_V2I32 rhs); -DN_API DN_V2F32& operator+= (DN_V2F32& lhs, DN_F32 rhs); -DN_API DN_V2F32& operator+= (DN_V2F32& lhs, DN_I32 rhs); - -DN_API DN_V2F32 DN_V2F32Min (DN_V2F32 a, DN_V2F32 b); -DN_API DN_V2F32 DN_V2F32Max (DN_V2F32 a, DN_V2F32 b); -DN_API DN_V2F32 DN_V2F32Abs (DN_V2F32 a); -DN_API DN_F32 DN_V2F32Dot (DN_V2F32 a, DN_V2F32 b); -DN_API DN_F32 DN_V2F32LengthSq2V2 (DN_V2F32 lhs, DN_V2F32 rhs); -DN_API bool DN_V2F32LengthSqIsWithin2V2 (DN_V2F32 lhs, DN_V2F32 rhs, DN_F32 within_amount_sq); -DN_API DN_F32 DN_V2F32Length2V2 (DN_V2F32 lhs, DN_V2F32 rhs); -DN_API DN_F32 DN_V2F32LengthSq (DN_V2F32 lhs); -DN_API DN_F32 DN_V2F32Length (DN_V2F32 lhs); -DN_API DN_V2F32 DN_V2F32Normalise (DN_V2F32 a); -DN_API DN_V2F32 DN_V2F32Perpendicular (DN_V2F32 a); -DN_API DN_V2F32 DN_V2F32Reflect (DN_V2F32 in, DN_V2F32 surface); -DN_API DN_F32 DN_V2F32Area (DN_V2F32 a); - -#define DN_V3F32From1N(x) DN_Literal(DN_V3F32){{(DN_F32)(x), (DN_F32)(x), (DN_F32)(x)}} -#define DN_V3F32From3N(x, y, z) DN_Literal(DN_V3F32){{(DN_F32)(x), (DN_F32)(y), (DN_F32)(z)}} -#define DN_V3F32FromV2F32And1N(xy, z) DN_Literal(DN_V3F32){{(DN_F32)(xy.x), (DN_F32)(xy.y), (DN_F32)(z)}} - -// NOTE: Grayscale co-efficients from: -// https://github.com/EpicGames/UnrealEngine/blob/260bb2e1c5610b31c63a36206eedd289409c5f11/Engine/Source/Runtime/Core/Private/Math/Color.cpp#L304 -DN_V3F32 static const DN_V3F32_RGB_LUMINANCE = DN_V3F32From3N(0.3f, 0.59f, 0.11f); - -DN_API bool operator== (DN_V3F32 lhs, DN_V3F32 rhs); -DN_API bool operator!= (DN_V3F32 lhs, DN_V3F32 rhs); -DN_API bool operator>= (DN_V3F32 lhs, DN_V3F32 rhs); -DN_API bool operator<= (DN_V3F32 lhs, DN_V3F32 rhs); -DN_API bool operator< (DN_V3F32 lhs, DN_V3F32 rhs); -DN_API bool operator> (DN_V3F32 lhs, DN_V3F32 rhs); -DN_API DN_V3F32 operator- (DN_V3F32 lhs, DN_V3F32 rhs); -DN_API DN_V3F32 operator- (DN_V3F32 lhs); -DN_API DN_V3F32 operator+ (DN_V3F32 lhs, DN_V3F32 rhs); -DN_API DN_V3F32 operator* (DN_V3F32 lhs, DN_V3F32 rhs); -DN_API DN_V3F32 operator* (DN_V3F32 lhs, DN_F32 rhs); -DN_API DN_V3F32 operator* (DN_V3F32 lhs, DN_I32 rhs); -DN_API DN_V3F32 operator/ (DN_V3F32 lhs, DN_V3F32 rhs); -DN_API DN_V3F32 operator/ (DN_V3F32 lhs, DN_F32 rhs); -DN_API DN_V3F32 operator/ (DN_V3F32 lhs, DN_I32 rhs); -DN_API DN_V3F32& operator*= (DN_V3F32 &lhs, DN_V3F32 rhs); -DN_API DN_V3F32& operator*= (DN_V3F32 &lhs, DN_F32 rhs); -DN_API DN_V3F32& operator*= (DN_V3F32 &lhs, DN_I32 rhs); -DN_API DN_V3F32& operator/= (DN_V3F32 &lhs, DN_V3F32 rhs); -DN_API DN_V3F32& operator/= (DN_V3F32 &lhs, DN_F32 rhs); -DN_API DN_V3F32& operator/= (DN_V3F32 &lhs, DN_I32 rhs); -DN_API DN_V3F32& operator-= (DN_V3F32 &lhs, DN_V3F32 rhs); -DN_API DN_V3F32& operator+= (DN_V3F32 &lhs, DN_V3F32 rhs); -DN_API DN_V3F32 DN_V3F32Lerp (DN_V3F32 lhs, DN_F32 t01, DN_V3F32 rhs); -DN_API DN_F32 DN_V3F32LengthSq (DN_V3F32 a); -DN_API DN_F32 DN_V3F32Length (DN_V3F32 a); -DN_API DN_V3F32 DN_V3F32Normalise (DN_V3F32 a); - -#define DN_V4F32From1N(x) DN_Literal(DN_V4F32){{(DN_F32)(x), (DN_F32)(x), (DN_F32)(x), (DN_F32)(x)}} -#define DN_V4F32From4N(x, y, z, w) DN_Literal(DN_V4F32){{(DN_F32)(x), (DN_F32)(y), (DN_F32)(z), (DN_F32)(w)}} -#define DN_V4F32FromV3And1N(xyz, w) DN_Literal(DN_V4F32){{xyz.x, xyz.y, xyz.z, w}} -#define DN_V4F32RGBA01FromRGBAU8(r, g, b, a) DN_Literal(DN_V4F32){{r / 255.f, g / 255.f, b / 255.f, a / 255.f}} -#define DN_V4F32RGBA01FromRGBU8(r, g, b) DN_Literal(DN_V4F32){{r / 255.f, g / 255.f, b / 255.f, 1.f}} - -DN_API DN_V4F32 DN_V4F32Lerp (DN_V4F32 lhs, DN_F32 t01, DN_V4F32 rhs); -DN_API bool DN_V4F32RGBA01IsValid (DN_V4F32 rgba01); -DN_API DN_V4F32 DN_V4F32RGBA01FromRGBU32 (DN_U32 u32); -DN_API DN_V4F32 DN_V4F32RGBA01FromRGBAU32 (DN_U32 u32); -DN_API DN_V4F32 DN_V4F32Linear01FromSRGB01 (DN_V4F32 rgb01); -DN_API DN_V4F32 DN_V4F32Linear01Desaturate (DN_V4F32 linear01, DN_F32 t01); -DN_API DN_V4F32 DN_V4F32SRGB01FromLinear01 (DN_V4F32 linear01); - -#define DN_V4F32FromV4Alpha(v4, alpha) DN_V4F32FromV3And1N(v4.xyz, alpha) - -DN_API bool operator== (DN_V4F32 lhs, DN_V4F32 rhs); -DN_API bool operator!= (DN_V4F32 lhs, DN_V4F32 rhs); -DN_API bool operator<= (DN_V4F32 lhs, DN_V4F32 rhs); -DN_API bool operator< (DN_V4F32 lhs, DN_V4F32 rhs); -DN_API bool operator> (DN_V4F32 lhs, DN_V4F32 rhs); -DN_API DN_V4F32 operator- (DN_V4F32 lhs, DN_V4F32 rhs); -DN_API DN_V4F32 operator- (DN_V4F32 lhs); -DN_API DN_V4F32 operator+ (DN_V4F32 lhs, DN_V4F32 rhs); -DN_API DN_V4F32 operator* (DN_V4F32 lhs, DN_V4F32 rhs); -DN_API DN_V4F32 operator* (DN_V4F32 lhs, DN_F32 rhs); -DN_API DN_V4F32 operator* (DN_V4F32 lhs, DN_I32 rhs); -DN_API DN_V4F32 operator/ (DN_V4F32 lhs, DN_F32 rhs); -DN_API DN_V4F32& operator*= (DN_V4F32 &lhs, DN_V4F32 rhs); -DN_API DN_V4F32& operator*= (DN_V4F32 &lhs, DN_F32 rhs); -DN_API DN_V4F32& operator*= (DN_V4F32 &lhs, DN_I32 rhs); -DN_API DN_V4F32& operator-= (DN_V4F32 &lhs, DN_V4F32 rhs); -DN_API DN_V4F32& operator+= (DN_V4F32 &lhs, DN_V4F32 rhs); -DN_API DN_F32 DN_V4F32Dot (DN_V4F32 a, DN_V4F32 b); - -DN_API DN_M4 DN_M4Identity (); -DN_API DN_M4 DN_M4ScaleF (DN_F32 x, DN_F32 y, DN_F32 z); -DN_API DN_M4 DN_M4Scale (DN_V3F32 xyz); -DN_API DN_M4 DN_M4TranslateF (DN_F32 x, DN_F32 y, DN_F32 z); -DN_API DN_M4 DN_M4Translate (DN_V3F32 xyz); -DN_API DN_M4 DN_M4Transpose (DN_M4 mat); -DN_API DN_M4 DN_M4Rotate (DN_V3F32 axis, DN_F32 radians); -DN_API DN_M4 DN_M4Orthographic (DN_F32 left, DN_F32 right, DN_F32 bottom, DN_F32 top, DN_F32 z_near, DN_F32 z_far); -DN_API DN_M4 DN_M4Perspective (DN_F32 fov /*radians*/, DN_F32 aspect, DN_F32 z_near, DN_F32 z_far); -DN_API DN_M4 DN_M4Add (DN_M4 lhs, DN_M4 rhs); -DN_API DN_M4 DN_M4Sub (DN_M4 lhs, DN_M4 rhs); -DN_API DN_M4 DN_M4Mul (DN_M4 lhs, DN_M4 rhs); -DN_API DN_M4 DN_M4Div (DN_M4 lhs, DN_M4 rhs); -DN_API DN_M4 DN_M4AddF (DN_M4 lhs, DN_F32 rhs); -DN_API DN_M4 DN_M4SubF (DN_M4 lhs, DN_F32 rhs); -DN_API DN_M4 DN_M4MulF (DN_M4 lhs, DN_F32 rhs); -DN_API DN_M4 DN_M4DivF (DN_M4 lhs, DN_F32 rhs); -DN_API DN_Str8x256 DN_M4ColumnMajorString (DN_M4 mat); - -DN_API bool DN_M2x3Eq (DN_M2x3 const *lhs, DN_M2x3 const *rhs); -DN_API bool DN_M2x3NotEq (DN_M2x3 const *lhs, DN_M2x3 const *rhs); -DN_API DN_M2x3 DN_M2x3Identity (); -DN_API DN_M2x3 DN_M2x3Translate (DN_V2F32 offset); -DN_API DN_V2F32 DN_M2x3ScaleGet (DN_M2x3 m2x3); -DN_API DN_M2x3 DN_M2x3Scale (DN_V2F32 scale); -DN_API DN_M2x3 DN_M2x3Rotate (DN_F32 radians); -DN_API DN_M2x3 DN_M2x3ProjFromV2F32 (DN_V2F32 size, DN_M2x3ProjOrigin origin); -DN_API DN_M2x3XForm DN_M2x3XFormFrom2M2x3 (DN_M2x3 forward, DN_M2x3 inverse); -DN_API DN_M2x3XForm DN_M2x3XFormFromTRS (DN_V2F32 pos, DN_V2F32 scale, DN_F32 rotate_rads, DN_V2F32 pivot_pos); -DN_API DN_M2x3XForm DN_M2x3XFormIdentity (); -DN_API DN_M2x3XForm DN_M2x3XFormMul (DN_M2x3XForm m1, DN_M2x3XForm m2); -DN_API DN_M2x3 DN_M2x3Mul (DN_M2x3 m1, DN_M2x3 m2); -DN_API DN_V2F32 DN_M2x3Mul2F32 (DN_M2x3 m1, DN_F32 x, DN_F32 y); -DN_API DN_V2F32 DN_M2x3MulV2F32 (DN_M2x3 m1, DN_V2F32 v2); -DN_API DN_Rect DN_M2x3MulRect (DN_M2x3 m1, DN_Rect rect); - -#define DN_RectFrom2V2(pos, size) DN_Literal(DN_Rect){(pos), (size)} -#define DN_RectFrom4N(x, y, w, h) DN_Literal(DN_Rect){DN_Literal(DN_V2F32){{x, y}}, DN_Literal(DN_V2F32){{w, h}}} -#define DN_RectZero DN_RectFrom4N(0, 0, 0, 0) - -DN_API DN_V2F32 DN_RectCenter (DN_Rect rect); -DN_API bool DN_RectContainsPoint (DN_Rect rect, DN_V2F32 p); -DN_API bool DN_RectContainsRect (DN_Rect a, DN_Rect b); -DN_API DN_Rect DN_RectExpand (DN_Rect a, DN_F32 amount); -DN_API DN_Rect DN_RectExpandV2 (DN_Rect a, DN_V2F32 amount); -DN_API bool DN_RectIntersects (DN_Rect a, DN_Rect b); -DN_API DN_Rect DN_RectIntersection (DN_Rect a, DN_Rect b); -DN_API DN_Rect DN_RectUnion (DN_Rect a, DN_Rect b); -DN_API DN_2V2F32 DN_RectRange (DN_Rect a); -DN_API bool DN_RectEq (DN_Rect lhs, DN_Rect rhs); -DN_API DN_F32 DN_RectArea (DN_Rect a); -DN_API DN_V2F32 DN_RectInterpV2F32 (DN_Rect rect, DN_V2F32 t01); -DN_API DN_V2F32 DN_RectTopLeft (DN_Rect rect); -DN_API DN_V2F32 DN_RectTopRight (DN_Rect rect); -DN_API DN_V2F32 DN_RectBottomLeft (DN_Rect rect); -DN_API DN_V2F32 DN_RectBottomRight (DN_Rect rect); - -DN_API DN_Rect DN_RectCutLeftClip (DN_Rect *rect, DN_F32 amount, DN_RectCutClip clip); -DN_API DN_Rect DN_RectCutRightClip (DN_Rect *rect, DN_F32 amount, DN_RectCutClip clip); -DN_API DN_Rect DN_RectCutTopClip (DN_Rect *rect, DN_F32 amount, DN_RectCutClip clip); -DN_API DN_Rect DN_RectCutBottomClip (DN_Rect *rect, DN_F32 amount, DN_RectCutClip clip); - -#define DN_RectCutLeft(rect, amount) DN_RectCutLeftClip(rect, amount, DN_RectCutClip_Yes) -#define DN_RectCutRight(rect, amount) DN_RectCutRightClip(rect, amount, DN_RectCutClip_Yes) -#define DN_RectCutTop(rect, amount) DN_RectCutTopClip(rect, amount, DN_RectCutClip_Yes) -#define DN_RectCutBottom(rect, amount) DN_RectCutBottomClip(rect, amount, DN_RectCutClip_Yes) - -#define DN_RectCutLeftNoClip(rect, amount) DN_RectCutLeftClip(rect, amount, DN_RectCutClip_No) -#define DN_RectCutRightNoClip(rect, amount) DN_RectCutRightClip(rect, amount, DN_RectCutClip_No) -#define DN_RectCutTopNoClip(rect, amount) DN_RectCutTopClip(rect, amount, DN_RectCutClip_No) -#define DN_RectCutBottomNoClip(rect, amount) DN_RectCutBottomClip(rect, amount, DN_RectCutClip_No) - -DN_API DN_Rect DN_RectCutCut (DN_RectCut rect_cut, DN_V2F32 size, DN_RectCutClip clip); -#define DN_RectCutInit(rect, side) DN_Literal(DN_RectCut){rect, side} - -DN_API DN_RaycastV2 DN_RaycastLineIntersectV2 (DN_V2F32 origin_a, DN_V2F32 dir_a, DN_V2F32 origin_b, DN_V2F32 dir_b); - -// NOTE: Containers that are imlpemented using primarily macros for operating on data structures -// that are embedded into a C style struct or from a set of defined variables from the available -// scope. Keep it stupid simple, structs and functions. Minimal amount of container types with -// flexible construction leads to less duplicated container code and less template meta-programming. - -// NOTE: Intrusive Singly Linked List -// Define a struct with the members `next`: -// -// struct MyLinkItem { -// int data; -// MyLinkItem *next; -// } my_link = {}; -// -// MyLinkItem *first_item = DN_ISinglyLLDetach(&my_link, MyLinkItem); -#define DN_ISinglyLLDetach(list) (decltype(list))DN_SinglyLLDetach((void **)&(list), (void **)&(list)->next) - -// NOTE: Singly Linked List with Head and Tail pointer -/* - struct MyLinkItem { - int data; - MyLinkItem *next; - } my_list = {}; - - struct MyContainer { - MyLinkItem *head; - MyLinkItem *tail; - }; - - MyLinkItem item = {}; - MyContainer container = {}; - DN_ISinglyHeadTailLLAppend(container, item); - // ... or alternatively, DN_SinglyHeadTailLLAppend(container.head, container.tail, item); - - for (MyLinkItem *it = container.head; it; it = it->next) { } -*/ -#define DN_SinglyHeadTailLLAppend(head, tail, to_append) \ - do { \ - if (!head) \ - head = to_append; \ - if (tail) \ - tail->next = to_append; \ - tail = to_append; \ - } while (0) -#define DN_ISinglyHeadTailLLAppend(container_ptr, to_append) DN_SinglyHeadTailLLAppend((container_ptr)->head, (container_ptr)->tail, to_append) - -// NOTE: Sentinel Doubly Linked List -// Uses a sentinel/dummy node as the list head. The sentinel points to itself when empty. -// Define a struct with the members `next` and `prev`: -// -// struct MyLinkItem { -// int data; -// MyLinkItem *next; -// MyLinkItem *prev; -// } my_list = {}; -// -// DN_SentinelDoublyLLInit(&my_list); -// DN_SentinelDoublyLLAppend(&my_list, &new_item); -// DN_SentinelDoublyLLForEach(it, &my_list) { /* ... */ } -// -#define DN_SentinelDoublyLLInit(list) (list)->next = (list)->prev = (list) -#define DN_SentinelDoublyLLIsSentinel(list, item) ((list) == (item)) -#define DN_SentinelDoublyLLIsEmpty(list) (!(list) || ((list) == (list)->next)) -#define DN_SentinelDoublyLLIsInit(list) ((list)->next && (list)->prev) -#define DN_SentinelDoublyLLHasItems(list) ((list) && ((list) != (list)->next)) -#define DN_SentinelDoublyLLForEach(it, list) auto *it = (list)->next; (it) != (list); (it) = (it)->next - -#define DN_SentinelDoublyLLInitArena(list, T, ptr_arena) \ - do { \ - (list) = DN_ArenaNew(ptr_arena, T, DN_ZMem_Yes); \ - DN_SentinelDoublyLLInit(list); \ - } while (0) - -#define DN_SentinelDoublyLLInitPool(list, T, pool) \ - do { \ - (list) = DN_PoolNew(pool, T); \ - DN_SentinelDoublyLLInit(list); \ - } while (0) - -#define DN_SentinelDoublyLLDetach(item) \ - do { \ - if (item) { \ - (item)->prev->next = (item)->next; \ - (item)->next->prev = (item)->prev; \ - (item)->next = nullptr; \ - (item)->prev = nullptr; \ - } \ - } while (0) - -#define DN_SentinelDoublyLLDequeue(list, dest_ptr) \ - if (DN_SentinelDoublyLLHasItems(list)) { \ - dest_ptr = (list)->next; \ - DN_SentinelDoublyLLDetach(dest_ptr); \ - } - -#define DN_SentinelDoublyLLAppend(list, item) \ - do { \ - if (item) { \ - if ((item)->next) \ - DN_SentinelDoublyLLDetach(item); \ - (item)->next = (list)->next; \ - (item)->prev = (list); \ - (item)->next->prev = (item); \ - (item)->prev->next = (item); \ - } \ - } while (0) - -#define DN_SentinelDoublyLLPrepend(list, item) \ - do { \ - if (item) { \ - if ((item)->next) \ - DN_SentinelDoublyLLDetach(item); \ - (item)->next = (list); \ - (item)->prev = (list)->prev; \ - (item)->next->prev = (item); \ - (item)->prev->next = (item); \ - } \ - } while (0) - -// NOTE: Doubly Linked List -// Define a struct with the members `next` and `prev`. This list has null pointers for head->prev -// and tail->next. -// -// struct MyLinkItem { -// int data; -// MyLinkItem *next; -// MyLinkItem *prev; -// } my_link = {}; -// -// MyLinkItem first_item = {}, second_item = {}; -// DN_DoublyLLAppend(&first_item, &second_item); // first_item -> second_item -// -#define DN_DoublyLLDetach(head, ptr) \ - do { \ - if ((head) && (head) == (ptr)) \ - (head) = (head)->next; \ - if ((ptr)) { \ - if ((ptr)->next) \ - (ptr)->next->prev = (ptr)->prev; \ - if ((ptr)->prev) \ - (ptr)->prev->next = (ptr)->next; \ - (ptr)->prev = (ptr)->next = 0; \ - } \ - } while (0) - -#define DN_DoublyLLAppend(head, ptr) \ - do { \ - if ((ptr)) { \ - DN_AssertF((ptr)->prev == 0 && (ptr)->next == 0, "Detach the pointer first"); \ - (ptr)->prev = (head); \ - (ptr)->next = 0; \ - if ((head)) { \ - (ptr)->next = (head)->next; \ - (head)->next = (ptr); \ - if ((ptr)->next) \ - (ptr)->next->prev = (ptr); \ - } else { \ - (head) = (ptr); \ - } \ - } \ - } while (0) - -#define DN_DoublyLLPrepend(head, ptr) \ - do { \ - if ((ptr)) { \ - DN_AssertF((ptr)->prev == 0 && (ptr)->next == 0, "Detach the pointer first"); \ - (ptr)->prev = nullptr; \ - (ptr)->next = (head); \ - if ((head)) { \ - (ptr)->prev = (head)->prev; \ - (head)->prev = (ptr); \ - if ((ptr)->prev) \ - (ptr)->prev->next = (ptr); \ - } else { \ - (head) = (ptr); \ - } \ - } \ - } while (0) - -// NOTE: For C++ we need to cast the void* returned in these functions to the concrete type. In C, -// no cast is needed. -#if defined(__cplusplus) - #define DN_CppDeclType(x) decltype(x) -#else - #define DN_CppDeclType -#endif - -// NOTE: Arrays -// Data structures that have a `T *data`, `DN_USize count` and `DN_USize max` capacity that can be -// dynamically shrunk or expanded. -// -// API -// ResizeFrom: Resizes the array to `new_max` erase elements if resizing to a smaller size -// GrowFrom: Expands the capacity of the array if `new_max > array.max` otherwise no-op -// GrowIfNeeded: Expands the capacity of the array if `array.size + add_count > array.max` otherwise no-op -// -// Variants -// PArray => Pointer (to) Array -// LArray => Literal Array -// Define a C array and size. (P) array macros take a pointer to the aray, its size and its max -// capacity. The (L) array macros take the literal array and derives the max capacity -// automatically using DN_ArrayCountU(l_array). -// -// MyStruct buffer[TB_ASType_Count] = {}; -// DN_USize size = 0; -// MyStruct *item_0 = DN_PArrayMake(buffer, &size, DN_ArrayCountU(buffer), DN_ZMem_No); -// MyStruct *item_1 = DN_LArrayMake(buffer, &size, DN_ZMem_No); -// -// IArray => Intrusive Array -// Define a struct with the members `data`, `count` and `max`: -// -// struct MyArray { -// MyStruct *data; -// DN_USize count; -// DN_USize max; -// } my_array = {}; -// DN_Arena arena = {}; -// DN_IArrayResizeFromArena(&my_array, &arena, 256); -// MyStruct *item = DN_IArrayMake(&my_array, DN_ZMem_No); -// -#if defined(__cplusplus) - #define DN_PArrayFind(ptr, size, ptr_find, eq_func) DN_TArrayFind(ptr, size, ptr_find, eq_func) - #define DN_PArrayFindMemEq(ptr, size, ptr_find) DN_TArrayFindMemEq(ptr, size, ptr_find) - #define DN_PArrayResizeFromPool(ptr, ptr_size, ptr_max, pool, new_max) DN_TArrayResizeFromPool(&(ptr), ptr_size, ptr_max, pool, new_max) - #define DN_PArrayResizeFromArena(ptr, ptr_size, ptr_max, arena, new_max) DN_TArrayResizeFromArena(&(ptr), ptr_size, ptr_max, arena, new_max) - #define DN_PArrayGrowFromPool(ptr, size, ptr_max, pool, new_max) DN_TArrayGrowFromPool(&(ptr), size, ptr_max, pool, new_max) - #define DN_PArrayGrowFromArena(ptr, size, ptr_max, arena, new_max) DN_TArrayGrowFromArena(&(ptr), size, ptr_max, arena, new_max) - #define DN_PArrayGrowIfNeededFromPool(ptr, size, ptr_max, pool, add_count) DN_TArrayGrowIfNeededFromPool(ptr, size, ptr_max, pool, add_count) - #define DN_PArrayGrowIfNeededFromArena(ptr, size, ptr_max, arena, add_count) DN_TArrayGrowIfNeededFromArena(ptr, size, ptr_max, arena, add_count) - - #define DN_PArrayMakeArray(ptr, ptr_size, max, count, z_mem) DN_TArrayMakeArray(ptr, ptr_size, max, count, z_mem) - #define DN_PArrayMakeArrayZ(ptr, ptr_size, max, count) DN_TArrayMakeArray(ptr, ptr_size, max, count, DN_ZMem_Yes) - #define DN_PArrayMakeArrayNoZ(ptr, ptr_size, max, count) DN_TArrayMakeArray(ptr, ptr_size, max, count, DN_ZMem_No) - #define DN_PArrayMakeArrayAssert(ptr, ptr_size, max, count, z_mem) DN_TArrayMakeArrayAssert(ptr, ptr_size, max, count, z_mem, DN_CallSiteNow) - #define DN_PArrayMakeArrayAssertZ(ptr, ptr_size, max, count) DN_TArrayMakeArrayAssert(ptr, ptr_size, max, count, DN_ZMem_Yes, DN_CallSiteNow) - #define DN_PArrayMakeArrayAssertNoZ(ptr, ptr_size, max, count) DN_TArrayMakeArrayAssert(ptr, ptr_size, max, count, DN_ZMem_No, DN_CallSiteNow) - - #define DN_PArrayMake(ptr, ptr_size, max, z_mem) DN_TArrayMakeArray(ptr, ptr_size, max, 1, z_mem) - #define DN_PArrayMakeZ(ptr, ptr_size, max) DN_TArrayMakeArray(ptr, ptr_size, max, 1, DN_ZMem_Yes) - #define DN_PArrayMakeNoZ(ptr, ptr_size, max) DN_TArrayMakeArray(ptr, ptr_size, max, 1, DN_ZMem_No) - #define DN_PArrayMakeAssert(ptr, ptr_size, max, z_mem) DN_TArrayMakeArrayAssert(ptr, ptr_size, max, 1, z_mem, DN_CallSiteNow) - #define DN_PArrayMakeAssertZ(ptr, ptr_size, max) DN_TArrayMakeArrayAssert(ptr, ptr_size, max, 1, DN_ZMem_Yes, DN_CallSiteNow) - #define DN_PArrayMakeAssertNoZ(ptr, ptr_size, max) DN_TArrayMakeArrayAssert(ptr, ptr_size, max, 1, DN_ZMem_No, DN_CallSiteNow) - - #define DN_PArrayAddArray(ptr, ptr_size, max, items, count, add) DN_TArrayAddArray(ptr, ptr_size, max, items, count, add) - #define DN_PArrayAdd(ptr, ptr_size, max, item, add) DN_TArrayAddArray(ptr, ptr_size, max, &item, 1, add) - #define DN_PArrayAppendArray(ptr, ptr_size, max, items, count) DN_TArrayAddArray(ptr, ptr_size, max, items, count, DN_ArrayAdd_Append) - #define DN_PArrayAppend(ptr, ptr_size, max, item) DN_TArrayAddArray(ptr, ptr_size, max, &item, 1, DN_ArrayAdd_Append) - #define DN_PArrayAppendAssert(ptr, ptr_size, max, item) DN_TArrayAddArrayAssert(ptr, ptr_size, max, &item, 1, DN_ArrayAdd_Append, DN_CallSiteNow) - #define DN_PArrayPrependArray(ptr, ptr_size, max, items, count) DN_TArrayAddArray(ptr, ptr_size, max, items, count, DN_ArrayAdd_Prepend) - #define DN_PArrayPrepend(ptr, ptr_size, max, item) DN_TArrayAddArray(ptr, ptr_size, max, &item, 1, DN_ArrayAdd_Prepend) - - #define DN_PArrayEraseRange(ptr, ptr_size, begin_index, count, erase) DN_TArrayEraseRange(ptr, ptr_size, begin_index, count, erase) - #define DN_PArrayErase(ptr, ptr_size, index, erase) DN_TArrayEraseRange(ptr, ptr_size, index, 1, erase) - #define DN_PArrayInsertArray(ptr, ptr_size, max, index, items, count) DN_TArrayInsertArray(ptr, ptr_size, max, index, items, count) - #define DN_PArrayInsert(ptr, ptr_size, max, index, item) DN_TArrayInsertArray(ptr, ptr_size, max, index, &item, 1) - #define DN_PArrayPopFront(ptr, ptr_size, max, count) DN_TArrayPopFront(ptr, ptr_size, count) - #define DN_PArrayPopBack(ptr, ptr_size, max, count) DN_TArrayPopBack(ptr, ptr_size, count) -#else - #define DN_PArrayFind(ptr, size, ptr_find, eq_func) DN_ArrayFind(ptr, size, sizeof(*(ptr)), ptr_find, eq_func) - #define DN_PArrayFindMemEq(ptr, size, ptr_find) DN_ArrayFindMemEq(ptr, size, sizeof(*(ptr)), ptr_find) - #define DN_PArrayResizeFromPool(ptr, ptr_size, ptr_max, pool, new_max) DN_ArrayResizeFromPool((void **)&(ptr), ptr_size, ptr_max, sizeof((ptr)[0]), pool, new_max) - #define DN_PArrayResizeFromArena(ptr, ptr_size, ptr_max, arena, new_max) DN_ArrayResizeFromArena((void **)&(ptr), ptr_size, ptr_max, sizeof((ptr)[0]), arena, new_max) - #define DN_PArrayGrowFromPool(ptr, size, ptr_max, pool, new_max) DN_ArrayGrowFromPool((void **)&(ptr), size, ptr_max, sizeof((ptr)[0]), pool, new_max) - #define DN_PArrayGrowFromArena(ptr, size, ptr_max, arena, new_max) DN_ArrayGrowFromArena((void **)&(ptr), size, ptr_max, sizeof((ptr)[0]), arena, new_max) - #define DN_PArrayGrowIfNeededFromPool(ptr, size, ptr_max, pool, add_count) DN_ArrayGrowIfNeededFromPool((void **)(ptr), size, ptr_max, sizeof((*ptr)[0]), pool, add_count) - #define DN_PArrayGrowIfNeededFromArena(ptr, size, ptr_max, arena, add_count) DN_ArrayGrowIfNeededFromArena((void **)(ptr), size, ptr_max, sizeof((*ptr)[0]), arena, add_count) - - #define DN_PArrayMakeArray(ptr, ptr_size, max, count, z_mem) (DN_CppDeclType(&(ptr)[0]))DN_ArrayMakeArray(ptr, ptr_size, max, sizeof((ptr)[0]), count, z_mem) - #define DN_PArrayMakeArrayZ(ptr, ptr_size, max, count) (DN_CppDeclType(&(ptr)[0]))DN_ArrayMakeArray(ptr, ptr_size, max, sizeof((ptr)[0]), count, DN_ZMem_Yes) - #define DN_PArrayMakeArrayNoZ(ptr, ptr_size, max, count) (DN_CppDeclType(&(ptr)[0]))DN_ArrayMakeArray(ptr, ptr_size, max, sizeof((ptr)[0]), count, DN_ZMem_No) - #define DN_PArrayMakeArrayAssert(ptr, ptr_size, max, count, z_mem) (DN_CppDeclType(&(ptr)[0]))DN_ArrayMakeArrayAssert(ptr, ptr_size, max, sizeof((ptr)[0]), count, z_mem, DN_CallSiteNow) - #define DN_PArrayMakeArrayAssertZ(ptr, ptr_size, max, count) (DN_CppDeclType(&(ptr)[0]))DN_ArrayMakeArrayAssert(ptr, ptr_size, max, sizeof((ptr)[0]), count, DN_ZMem_Yes, DN_CallSiteNow) - #define DN_PArrayMakeArrayAssertNoZ(ptr, ptr_size, max, count) (DN_CppDeclType(&(ptr)[0]))DN_ArrayMakeArrayAssert(ptr, ptr_size, max, sizeof((ptr)[0]), count, DN_ZMem_No, DN_CallSiteNow) - - #define DN_PArrayMake(ptr, ptr_size, max, z_mem) (DN_CppDeclType(&(ptr)[0]))DN_ArrayMakeArray(ptr, ptr_size, max, sizeof((ptr)[0]), 1, z_mem) - #define DN_PArrayMakeZ(ptr, ptr_size, max) (DN_CppDeclType(&(ptr)[0]))DN_ArrayMakeArray(ptr, ptr_size, max, sizeof((ptr)[0]), 1, DN_ZMem_Yes) - #define DN_PArrayMakeNoZ(ptr, ptr_size, max) (DN_CppDeclType(&(ptr)[0]))DN_ArrayMakeArray(ptr, ptr_size, max, sizeof((ptr)[0]), 1, DN_ZMem_No) - #define DN_PArrayMakeAssert(ptr, ptr_size, max, z_mem) (DN_CppDeclType(&(ptr)[0]))DN_ArrayMakeArrayAssert(ptr, ptr_size, max, sizeof((ptr)[0]), 1, z_mem, DN_CallSiteNow) - #define DN_PArrayMakeAssertZ(ptr, ptr_size, max) (DN_CppDeclType(&(ptr)[0]))DN_ArrayMakeArrayAssert(ptr, ptr_size, max, sizeof((ptr)[0]), 1, DN_ZMem_Yes, DN_CallSiteNow) - #define DN_PArrayMakeAssertNoZ(ptr, ptr_size, max) (DN_CppDeclType(&(ptr)[0]))DN_ArrayMakeArrayAssert(ptr, ptr_size, max, sizeof((ptr)[0]), 1, DN_ZMem_No, DN_CallSiteNow) - - #define DN_PArrayAddArray(ptr, ptr_size, max, items, count, add) (DN_CppDeclType(&(ptr)[0]))DN_ArrayAddArray(ptr, ptr_size, max, sizeof((ptr)[0]), items, count, add) - #define DN_PArrayAdd(ptr, ptr_size, max, item, add) (DN_CppDeclType(&(ptr)[0]))DN_ArrayAddArray(ptr, ptr_size, max, sizeof((ptr)[0]), &item, 1, add) - #define DN_PArrayAppendArray(ptr, ptr_size, max, items, count) (DN_CppDeclType(&(ptr)[0]))DN_ArrayAddArray(ptr, ptr_size, max, sizeof((ptr)[0]), items, count, DN_ArrayAdd_Append) - #define DN_PArrayAppend(ptr, ptr_size, max, item) (DN_CppDeclType(&(ptr)[0]))DN_ArrayAddArray(ptr, ptr_size, max, sizeof((ptr)[0]), &item, 1, DN_ArrayAdd_Append) - #define DN_PArrayAppendAssert(ptr, ptr_size, max, item) (DN_CppDeclType(&(ptr)[0]))DN_ArrayAddArrayAssert(ptr, ptr_size, max, sizeof((ptr)[0]), &item, 1, DN_ArrayAdd_Append, DN_CallSiteNow) - #define DN_PArrayPrependArray(ptr, ptr_size, max, items, count) (DN_CppDeclType(&(ptr)[0]))DN_ArrayAddArray(ptr, ptr_size, max, sizeof((ptr)[0]), items, count, DN_ArrayAdd_Prepend) - #define DN_PArrayPrepend(ptr, ptr_size, max, item) (DN_CppDeclType(&(ptr)[0]))DN_ArrayAddArray(ptr, ptr_size, max, sizeof((ptr)[0]), &item, 1, DN_ArrayAdd_Prepend) - - #define DN_PArrayEraseRange(ptr, ptr_size, begin_index, count, erase) DN_ArrayEraseRange(ptr, ptr_size, sizeof((ptr)[0]), begin_index, count, erase) - #define DN_PArrayErase(ptr, ptr_size, index, erase) DN_ArrayEraseRange(ptr, ptr_size, sizeof((ptr)[0]), index, 1, erase) - #define DN_PArrayInsertArray(ptr, ptr_size, max, index, items, count) (DN_CppDeclType(&(ptr)[0]))DN_ArrayInsertArray(ptr, ptr_size, max, sizeof((ptr)[0]), index, items, count) - #define DN_PArrayInsert(ptr, ptr_size, max, index, item) (DN_CppDeclType(&(ptr)[0]))DN_ArrayInsertArray(ptr, ptr_size, max, sizeof((ptr)[0]), index, &item, 1) - #define DN_PArrayPopFront(ptr, ptr_size, max, count) (DN_CppDeclType(&(ptr)[0]))DN_ArrayPopFront(ptr, ptr_size, sizeof((ptr)[0]), count) - #define DN_PArrayPopBack(ptr, ptr_size, max, count) (DN_CppDeclType(&(ptr)[0]))DN_ArrayPopBack(ptr, ptr_size, sizeof((ptr)[0]), count) -#endif - -#define DN_LArrayFind(c_array, size, ptr_find, eq_func) DN_PArrayFind(c_array, size, ptr_find, eq_func) -#define DN_LArrayFindMemEq(c_array, size, ptr_find) DN_PArrayFindMemEq(c_array, size, ptr_find) -#define DN_LArrayResizeFromPool(c_array, size, pool, new_max) DN_PArrayResizeFromPool(c_array, size, DN_ArrayCountU(c_array), pool, new_max) -#define DN_LArrayResizeFromArena(c_array, size, arena, new_max) DN_PArrayResizeFromArena(c_array, size, DN_ArrayCountU(c_array), arena, new_max) -#define DN_LArrayGrowFromPool(c_array, size, pool, new_max) DN_PArrayGrowFromPool(c_array, size, DN_ArrayCountU(c_array), pool, new_max) -#define DN_LArrayGrowFromArena(c_array, size, arena, new_max) DN_PArrayGrowFromArena(c_array, size, DN_ArrayCountU(c_array), arena, new_max) -#define DN_LArrayGrowIfNeededFromPool(c_array, size, pool, add_count) DN_PArrayGrowIfNeededFromPool(c_array, size, DN_ArrayCountU(c_array), pool, add_count) -#define DN_LArrayGrowIfNeededFromArena(c_array, size, arena, add_count) DN_PArrayGrowIfNeededFromArena(c_array, size, DN_ArrayCountU(c_array), arena, add_count) - -#define DN_LArrayMakeArray(c_array, ptr_size, count, z_mem) DN_PArrayMakeArray(c_array, ptr_size, DN_ArrayCountU(c_array), count, z_mem) -#define DN_LArrayMakeArrayZ(c_array, ptr_size, count) DN_PArrayMakeArrayZ(c_array, ptr_size, DN_ArrayCountU(c_array), count) -#define DN_LArrayMakeArrayNoZ(c_array, ptr_size, count) DN_PArrayMakeArrayNoZ(c_array, ptr_size, DN_ArrayCountU(c_array), count) -#define DN_LArrayMakeArrayAssert(c_array, ptr_size, count, z_mem) DN_PArrayMakeArrayAssert(c_array, ptr_size, DN_ArrayCountU(c_array), count, z_mem) -#define DN_LArrayMakeArrayAssertZ(c_array, ptr_size, count) DN_PArrayMakeArrayAssertZ(c_array, ptr_size, DN_ArrayCountU(c_array), count) -#define DN_LArrayMakeArrayAssertNoZ(c_array, ptr_size, count) DN_PArrayMakeArrayAssertNoZ(c_array, ptr_size, DN_ArrayCountU(c_array), count) - -#define DN_LArrayMake(c_array, ptr_size, z_mem) DN_PArrayMake(c_array, ptr_size, DN_ArrayCountU(c_array), z_mem) -#define DN_LArrayMakeZ(c_array, ptr_size) DN_PArrayMakeZ(c_array, ptr_size, DN_ArrayCountU(c_array)) -#define DN_LArrayMakeNoZ(c_array, ptr_size) DN_PArrayMakeNoZ(c_array, ptr_size, DN_ArrayCountU(c_array)) -#define DN_LArrayMakeAssert(c_array, ptr_size, z_mem) DN_PArrayMakeAssert(c_array, ptr_size, DN_ArrayCountU(c_array), z_mem) -#define DN_LArrayMakeAssertZ(c_array, ptr_size) DN_PArrayMakeAssertZ(c_array, ptr_size, DN_ArrayCountU(c_array)) -#define DN_LArrayMakeAssertNoZ(c_array, ptr_size) DN_PArrayMakeAssertNoZ(c_array, ptr_size, DN_ArrayCountU(c_array)) - -#define DN_LArrayAddArray(c_array, ptr_size, items, count, add) DN_PArrayAddArray(c_array, ptr_size, DN_ArrayCountU(c_array), items, count, add) -#define DN_LArrayAdd(c_array, ptr_size, item, add) DN_PArrayAdd(c_array, ptr_size, DN_ArrayCountU(c_array), item, add) -#define DN_LArrayAppendArray(c_array, ptr_size, items, count) DN_PArrayAppendArray(c_array, ptr_size, DN_ArrayCountU(c_array), items, count) -#define DN_LArrayAppend(c_array, ptr_size, item) DN_PArrayAppend(c_array, ptr_size, DN_ArrayCountU(c_array), item) -#define DN_LArrayAppendAssert(c_array, ptr_size, item) DN_PArrayAppendAssert(c_array, ptr_size, DN_ArrayCountU(c_array), item) -#define DN_LArrayPrependArray(c_array, ptr_size, items, count) DN_PArrayPrependArray(c_array, ptr_size, DN_ArrayCountU(c_array), items, count) -#define DN_LArrayPrepend(c_array, ptr_size, item) DN_PArrayPrepend(c_array, ptr_size, DN_ArrayCountU(c_array), item) -#define DN_LArrayEraseRange(c_array, ptr_size, begin_index, count, erase) DN_PArrayEraseRange(c_array, ptr_size, begin_index, count, erase) -#define DN_LArrayErase(c_array, ptr_size, index, erase) DN_PArrayErase(c_array, ptr_size, index, erase) -#define DN_LArrayInsertArray(c_array, ptr_size, index, items, count) DN_PArrayInsertArray(c_array, ptr_size, DN_ArrayCountU(c_array), index, items, count) -#define DN_LArrayInsert(c_array, ptr_size, index, item) DN_PArrayInsert(c_array, ptr_size, DN_ArrayCountU(c_array), index, item) -#define DN_LArrayPopFront(c_array, ptr_size, count) DN_PArrayPopFront(c_array, ptr_size, DN_ArrayCountU(c_array), count) -#define DN_LArrayPopBack(c_array, ptr_size, count) DN_PArrayPopBack(c_array, ptr_size, DN_ArrayCountU(c_array), count) - -#define DN_IArrayFind(ptr_array, ptr_find, eq_func) DN_PArrayFind((ptr_array)->data, (ptr_array)->count, ptr_find, eq_func) -#define DN_IArrayFindMemEq(ptr_array, ptr_find) DN_PArrayFindMemEq((ptr_array)->data, (ptr_array)->count, ptr_find) -#define DN_IArrayResizeFromPool(ptr_array, pool, new_max) DN_PArrayResizeFromPool((ptr_array)->data, &(ptr_array)->count, &(ptr_array)->max, pool, new_max) -#define DN_IArrayResizeFromArena(ptr_array, arena, new_max) DN_PArrayResizeFromArena((ptr_array)->data, &(ptr_array)->count, &(ptr_array)->max, arena, new_max) -#define DN_IArrayGrowFromPool(ptr_array, pool, new_max) DN_PArrayGrowFromPool((ptr_array)->data, (ptr_array)->count, &(ptr_array)->max, pool, new_max) -#define DN_IArrayGrowFromArena(ptr_array, arena, new_max) DN_PArrayGrowFromArena((ptr_array)->data, (ptr_array)->count, &(ptr_array)->max, arena, new_max) -#define DN_IArrayGrowIfNeededFromPool(ptr_array, pool, add_count) DN_PArrayGrowIfNeededFromPool(&(ptr_array)->data, (ptr_array)->count, &(ptr_array)->max, pool, add_count) -#define DN_IArrayGrowIfNeededFromArena(ptr_array, arena, add_count) DN_PArrayGrowIfNeededFromArena(&(ptr_array)->data, (ptr_array)->count, &(ptr_array)->max, arena, add_count) - -#define DN_IArrayMakeArray(ptr_array, count, z_mem) DN_PArrayMakeArray((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count, z_mem) -#define DN_IArrayMakeArrayZ(ptr_array, count) DN_PArrayMakeArrayZ((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count) -#define DN_IArrayMakeArrayNoZ(ptr_array, count) DN_PArrayMakeArrayNoZ((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count) -#define DN_IArrayMakeArrayAssert(ptr_array, count, z_mem) DN_PArrayMakeArrayAssert((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count, z_mem) -#define DN_IArrayMakeArrayAssertZ(ptr_array, count) DN_PArrayMakeArrayAssertZ((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count) -#define DN_IArrayMakeArrayAssertNoZ(ptr_array, count) DN_PArrayMakeArrayAssertNoZ((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count) - -#define DN_IArrayMake(ptr_array, z_mem) DN_PArrayMake((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, z_mem) -#define DN_IArrayMakeZ(ptr_array) DN_PArrayMakeZ((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max) -#define DN_IArrayMakeNoZ(ptr_array) DN_PArrayMakeNoZ((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max) -#define DN_IArrayMakeAssert(ptr_array, z_mem) DN_PArrayMakeAssert((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, z_mem) -#define DN_IArrayMakeAssertZ(ptr_array) DN_PArrayMakeAssertZ((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max) -#define DN_IArrayMakeAssertNoZ(ptr_array) DN_PArrayMakeAssertNoZ((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max) - -#define DN_IArrayAddArray(ptr_array, items, count, add) DN_PArrayAddArray((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, items, count, add) -#define DN_IArrayAdd(ptr_array, item, add) DN_PArrayAdd((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, item, add) -#define DN_IArrayAppendArray(ptr_array, items, count) DN_PArrayAppendArray((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, items, count) -#define DN_IArrayAppend(ptr_array, item) DN_PArrayAppend((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, item) -#define DN_IArrayAppendAssert(ptr_array, item) DN_PArrayAppendAssert((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, item) -#define DN_IArrayPrependArray(ptr_array, items, count) DN_PArrayPrependArray((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, items, count) -#define DN_IArrayPrepend(ptr_array, item) DN_PArrayPrepend((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, item) -#define DN_IArrayEraseRange(ptr_array, begin_index, count, erase) DN_PArrayEraseRange((ptr_array)->data, &(ptr_array)->count, begin_index, count, erase) -#define DN_IArrayErase(ptr_array, index, erase) DN_PArrayErase((ptr_array)->data, &(ptr_array)->count, index, erase) -#define DN_IArrayInsertArray(ptr_array, index, items, count) DN_PArrayInsertArray((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, index, items, count) -#define DN_IArrayInsert(ptr_array, index, item) DN_PArrayInsert((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, index, item) -#define DN_IArrayPopFront(ptr_array, count) DN_PArrayPopFront((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count) -#define DN_IArrayPopBack(ptr_array, count) DN_PArrayPopBack((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count) - -// NOTE: Slices -// -// Fixed size container allocated up front that have a `T *data` and `DN_USize count` elements. -// -// API -// AllocArena: Allocates the container with the requested `count` elements -// -#define DN_ISliceAllocArena(slice_ptr, count_, zmem, arena) (DN_CppDeclType(&((slice_ptr)->data[0])))DN_SliceAllocArena((void **)&((slice_ptr)->data), &((slice_ptr)->count), count_, sizeof((slice_ptr)->data[0]), alignof(DN_CppDeclType((slice_ptr)->data[0])), zmem, arena) - - -DN_API void* DN_SliceAllocArena (void **data, DN_USize *slice_size_field, DN_USize size, DN_USize elem_size, DN_U8 align, DN_ZMem zmem, DN_Arena *arena); - -DN_API DN_ArrayFindResult DN_ArrayFind (void *data, DN_USize size, DN_USize elem_size, void const *find, DN_ArrayFindEqFunc *eq_func); -DN_API DN_ArrayFindResult DN_ArrayFindMemEq (void *data, DN_USize size, DN_USize elem_size, void const *find); -DN_API void* DN_ArrayInsertArray (void *data, DN_USize *size, DN_USize max, DN_USize elem_size, DN_USize index, void const *items, DN_USize count); -DN_API void* DN_ArrayPopFront (void *data, DN_USize *size, DN_USize elem_size, DN_USize count); -DN_API void* DN_ArrayPopBack (void *data, DN_USize *size, DN_USize elem_size, DN_USize count); -DN_API DN_ArrayEraseResult DN_ArrayEraseRange (void *data, DN_USize *size, DN_USize elem_size, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase); -DN_API void* DN_ArrayMakeArray (void *data, DN_USize *size, DN_USize max, DN_USize elem_size, DN_USize make_count, DN_ZMem z_mem); -DN_API void* DN_ArrayMakeArrayAssert (void *data, DN_USize *size, DN_USize max, DN_USize elem_size, DN_USize make_count, DN_ZMem z_mem, DN_CallSite call_site); -DN_API void* DN_ArrayAddArray (void *data, DN_USize *size, DN_USize max, DN_USize elem_size, void const *elems, DN_USize elems_count, DN_ArrayAdd add); -DN_API void* DN_ArrayAddArrayAssert (void *data, DN_USize *size, DN_USize max, DN_USize elem_size, void const *elems, DN_USize elems_count, DN_ArrayAdd add, DN_CallSite call_site); -DN_API bool DN_ArrayResizeFromPool (void **data, DN_USize *size, DN_USize *max, DN_USize elem_size, DN_Pool *pool, DN_USize new_max); -DN_API bool DN_ArrayResizeFromArena (void **data, DN_USize *size, DN_USize *max, DN_USize elem_size, DN_Arena *arena, DN_USize new_max); -DN_API bool DN_ArrayGrowFromPool (void **data, DN_USize size, DN_USize *max, DN_USize elem_size, DN_Pool *pool, DN_USize new_max); -DN_API bool DN_ArrayGrowFromArena (void **data, DN_USize size, DN_USize *max, DN_USize elem_size, DN_Arena *arena, DN_USize new_max); -DN_API bool DN_ArrayGrowIfNeededFromPool (void **data, DN_USize size, DN_USize *max, DN_USize elem_size, DN_Pool *pool, DN_USize add_count); -DN_API bool DN_ArrayGrowIfNeededFromArena (void **data, DN_USize size, DN_USize *max, DN_USize elem_size, DN_Arena *arena, DN_USize add_count); - -#if defined (__cplusplus) -template DN_ArrayFindResult DN_TArrayFind (T *data, DN_USize size, void const *find, DN_ArrayFindEqFunc *eq_func); -template DN_ArrayFindResult DN_TArrayFindMemEq (T *data, DN_USize size, void const *find, DN_ArrayFindEqFunc *eq_func); -template T* DN_TArrayInsertArray (T *data, DN_USize *size, DN_USize max, DN_USize index, void const *items, DN_USize count); -template T* DN_TArrayPopFront (T *data, DN_USize *size, DN_USize count); -template T* DN_TArrayPopBack (T *data, DN_USize *size, DN_USize count); -template DN_ArrayEraseResult DN_TArrayEraseRange (T *data, DN_USize *size, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase); -template T* DN_TArrayMakeArray (T *data, DN_USize *size, DN_USize max, DN_USize make_count, DN_ZMem z_mem); -template T* DN_TArrayMakeArrayAssert (T *data, DN_USize *size, DN_USize max, DN_USize make_count, DN_ZMem z_mem, DN_CallSite call_site); -template T* DN_TArrayMakeArrayAssertZ (T *data, DN_USize *size, DN_USize max, DN_USize make_count, DN_CallSite call_site); -template T* DN_TArrayMakeArrayAssertNoZ (T *data, DN_USize *size, DN_USize max, DN_USize make_count, DN_CallSite call_site); -template T* DN_TArrayAddArray (T *data, DN_USize *size, DN_USize max, T const *elems, DN_USize elems_count, DN_ArrayAdd add); -template T* DN_TArrayAddArrayAssert (T *data, DN_USize *size, DN_USize max, T const *elems, DN_USize elems_count, DN_ArrayAdd add, DN_CallSite call_site); -template bool DN_TArrayResizeFromPool (T **data, DN_USize *size, DN_USize *max, DN_Pool *pool, DN_USize new_max); -template bool DN_TArrayResizeFromArena (T **data, DN_USize *size, DN_USize *max, DN_Arena *arena, DN_USize new_max); -template bool DN_TArrayGrowFromPool (T **data, DN_USize size, DN_USize *max, DN_Pool *pool, DN_USize new_max); -template bool DN_TArrayGrowFromArena (T **data, DN_USize size, DN_USize *max, DN_Pool *pool, DN_USize new_max); -template bool DN_TArrayGrowIfNeededFromPool (T **data, DN_USize size, DN_USize *max, DN_Pool *pool, DN_USize add_count); -template bool DN_TArrayGrowIfNeededFromArena (T **data, DN_USize size, DN_USize *max, DN_Arena *pool, DN_USize add_count); -#endif - -DN_API void* DN_SinglyLLDetach (void **link, void **next); -DN_API bool DN_RingHasSpace (DN_Ring const *ring, DN_U64 size); -DN_API bool DN_RingHasData (DN_Ring const *ring, DN_U64 size); -DN_API void DN_RingWrite (DN_Ring *ring, void const *src, DN_U64 src_size); -#define DN_RingWriteStruct(ring, item) DN_RingWrite((ring), (item), sizeof(*(item))) -DN_API void DN_RingRead (DN_Ring *ring, void *dest, DN_U64 dest_size); -#define DN_RingReadStruct(ring, dest) DN_RingRead((ring), (dest), sizeof(*(dest))) - -// TODO: Replace with a C-style hash table -#if defined(__cplusplus) -DN_U32 const DN_DS_MAP_DEFAULT_HASH_SEED = 0x8a1ced49; -DN_U32 const DN_DS_MAP_SENTINEL_SLOT = 0; -template DN_DSMap DN_DSMapInit (DN_Arena *arena, DN_U32 size, DN_DSMapFlags flags); -template void DN_DSMapDeinit (DN_DSMap *map, DN_ZMem z_mem); -template bool DN_DSMapIsValid (DN_DSMap const *map); -template DN_U32 DN_DSMapHash (DN_DSMap const *map, DN_DSMapKey key); -template DN_U32 DN_DSMapHashToSlotIndex (DN_DSMap const *map, DN_DSMapKey key); -template DN_DSMapResult DN_DSMapFind (DN_DSMap const *map, DN_DSMapKey key); -template DN_DSMapResult DN_DSMapMake (DN_DSMap *map, DN_DSMapKey key); -template DN_DSMapResult DN_DSMapSet (DN_DSMap *map, DN_DSMapKey key, T const &value); -template DN_DSMapResult DN_DSMapFindKeyU64 (DN_DSMap const *map, DN_U64 key); -template DN_DSMapResult DN_DSMapMakeKeyU64 (DN_DSMap *map, DN_U64 key); -template DN_DSMapResult DN_DSMapSetKeyU64 (DN_DSMap *map, DN_U64 key, T const &value); -template DN_DSMapResult DN_DSMapFindKeyStr8 (DN_DSMap const *map, DN_Str8 key); -template DN_DSMapResult DN_DSMapMakeKeyStr8 (DN_DSMap *map, DN_Str8 key); -template DN_DSMapResult DN_DSMapSetKeyStr8 (DN_DSMap *map, DN_Str8 key, T const &value); -template bool DN_DSMapResize (DN_DSMap *map, DN_U32 size); -template bool DN_DSMapErase (DN_DSMap *map, DN_DSMapKey key); -template bool DN_DSMapEraseKeyU64 (DN_DSMap *map, DN_U64 key); -template bool DN_DSMapEraseKeyStr8 (DN_DSMap *map, DN_Str8 key); -template DN_DSMapKey DN_DSMapKeyBuffer (DN_DSMap const *map, void const *data, DN_USize size); -template DN_DSMapKey DN_DSMapKeyBufferAsU64NoHash (DN_DSMap const *map, void const *data, DN_USize size); -template DN_DSMapKey DN_DSMapKeyU64 (DN_DSMap const *map, DN_U64 u64); -template DN_DSMapKey DN_DSMapKeyStr8 (DN_DSMap const *map, DN_Str8 string); -#define DN_DSMapKeyCStr8(map, string) DN_DSMapKeyBuffer(map, string, sizeof((string))/sizeof((string)[0]) - 1) -DN_API DN_DSMapKey DN_DSMapKeyU64NoHash (DN_U64 u64); -DN_API bool DN_DSMapKeyEquals (DN_DSMapKey lhs, DN_DSMapKey rhs); -DN_API bool operator== (DN_DSMapKey lhs, DN_DSMapKey rhs); -#endif - -enum DN_BinPackMode -{ - DN_BinPackMode_Serialise, - DN_BinPackMode_Deserialise, -}; - -struct DN_BinPack -{ - DN_Str8Builder writer; - DN_Str8 read; - DN_USize read_index; -}; - -DN_API bool DN_BinPackIsEndOfReadStream(DN_BinPack const *pack); -DN_API void DN_BinPackUSize (DN_BinPack *pack, DN_BinPackMode mode, DN_USize *item); -DN_API void DN_BinPackU64 (DN_BinPack *pack, DN_BinPackMode mode, DN_U64 *item); -DN_API void DN_BinPackU32 (DN_BinPack *pack, DN_BinPackMode mode, DN_U32 *item); -DN_API void DN_BinPackU16 (DN_BinPack *pack, DN_BinPackMode mode, DN_U16 *item); -DN_API void DN_BinPackU8 (DN_BinPack *pack, DN_BinPackMode mode, DN_U8 *item); -DN_API void DN_BinPackI64 (DN_BinPack *pack, DN_BinPackMode mode, DN_I64 *item); -DN_API void DN_BinPackI32 (DN_BinPack *pack, DN_BinPackMode mode, DN_I32 *item); -DN_API void DN_BinPackI16 (DN_BinPack *pack, DN_BinPackMode mode, DN_I16 *item); -DN_API void DN_BinPackI8 (DN_BinPack *pack, DN_BinPackMode mode, DN_I8 *item); -DN_API void DN_BinPackF64 (DN_BinPack *pack, DN_BinPackMode mode, DN_F64 *item); -DN_API void DN_BinPackF32 (DN_BinPack *pack, DN_BinPackMode mode, DN_F32 *item); -DN_API void DN_BinPackV2 (DN_BinPack *pack, DN_BinPackMode mode, DN_V2F32 *item); -DN_API void DN_BinPackV4 (DN_BinPack *pack, DN_BinPackMode mode, DN_V4F32 *item); -DN_API void DN_BinPackBool (DN_BinPack *pack, DN_BinPackMode mode, bool *item); -DN_API void DN_BinPackStr8FromArena (DN_BinPack *pack, DN_Arena *arena, DN_BinPackMode mode, DN_Str8 *string); -DN_API void DN_BinPackStr8FromPool (DN_BinPack *pack, DN_Pool *pool, DN_BinPackMode mode, DN_Str8 *string); -DN_API DN_Str8 DN_BinPackStr8FromBuffer (DN_BinPack *pack, DN_BinPackMode mode, char *ptr, DN_USize *size, DN_USize max); -DN_API void DN_BinPackBytesFromArena (DN_BinPack *pack, DN_Arena *arena, DN_BinPackMode mode, void **ptr, DN_USize *size); -DN_API void DN_BinPackBytesFromPool (DN_BinPack *pack, DN_Pool *pool, DN_BinPackMode mode, void **ptr, DN_USize *size); -DN_API void DN_BinPackCArray (DN_BinPack *pack, DN_BinPackMode mode, void *ptr, DN_USize size); -DN_API void DN_BinPackCBuffer (DN_BinPack *pack, DN_BinPackMode mode, char *ptr, DN_USize *size, DN_USize max); -DN_API DN_Str8 DN_BinPackBuild (DN_BinPack const *pack, DN_Arena *arena); - -enum DN_CSVSerialise -{ - DN_CSVSerialise_Read, - DN_CSVSerialise_Write, -}; - -struct DN_CSVTokeniser -{ - bool bad; - DN_Str8 string; - char delimiter; - char const *it; - bool end_of_line; -}; - -struct DN_CSVPack -{ - DN_Str8Builder write_builder; - DN_USize write_column; - DN_CSVTokeniser read_tokeniser; -}; - -// NOTE: Data structures to create and parse CSV files, supports Python style escaped quotes (e.g. -// Using "" to escape quotes inside a quoted string). -// -// API -// DN_CSVTokeniserNextN: Reads the next N consecutive fields from the parser. If `column_iterator` -// is `false` then the read of the N consecutive fields does not proceed past the end of the -// current CSV row. If `true` then it reads the next N fields even if reading would progress onto -// the next row. -DN_API DN_CSVTokeniser DN_CSVTokeniserInit (DN_Str8 string, char delimiter); -DN_API bool DN_CSVTokeniserValid (DN_CSVTokeniser *tokeniser); -DN_API bool DN_CSVTokeniserNextRow (DN_CSVTokeniser *tokeniser); -DN_API DN_Str8 DN_CSVTokeniserNextField (DN_CSVTokeniser *tokeniser); -DN_API DN_Str8 DN_CSVTokeniserNextColumn (DN_CSVTokeniser *tokeniser); -DN_API void DN_CSVTokeniserSkipLine (DN_CSVTokeniser *tokeniser); -DN_API int DN_CSVTokeniserNextN (DN_CSVTokeniser *tokeniser, DN_Str8 *fields, int fields_size, bool column_iterator); -DN_API int DN_CSVTokeniserNextColumnN(DN_CSVTokeniser *tokeniser, DN_Str8 *fields, int fields_size); -DN_API int DN_CSVTokeniserNextFieldN (DN_CSVTokeniser *tokeniser, DN_Str8 *fields, int fields_size); -DN_API void DN_CSVTokeniserSkipLineN (DN_CSVTokeniser *tokeniser, int count); -DN_API void DN_CSVPackU64 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_U64 *value); -DN_API void DN_CSVPackI64 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I64 *value); -DN_API void DN_CSVPackI32 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I32 *value); -DN_API void DN_CSVPackI16 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I16 *value); -DN_API void DN_CSVPackI8 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I8 *value); -DN_API void DN_CSVPackU32 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_U32 *value); -DN_API void DN_CSVPackU16 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_U16 *value); -DN_API void DN_CSVPackBoolAsU64 (DN_CSVPack *pack, DN_CSVSerialise serialise, bool *value); -DN_API void DN_CSVPackStr8 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_Str8 *str8, DN_Arena *arena); -DN_API void DN_CSVPackBuffer (DN_CSVPack *pack, DN_CSVSerialise serialise, void *dest, size_t *size); -DN_API void DN_CSVPackBufferWithMax (DN_CSVPack *pack, DN_CSVSerialise serialise, void *dest, size_t *size, size_t max); -DN_API bool DN_CSVPackNewLine (DN_CSVPack *pack, DN_CSVSerialise serialise); - -// TODO: Replace with a C implementation -template -using DN_BinarySearchLessThanProc = bool(T const &lhs, T const &rhs); - -template -bool DN_BinarySearch_DefaultLessThan(T const &lhs, T const &rhs); - -enum DN_BinarySearchType -{ - // Index of the match. If no match is found, found is set to false and the - // index is set to the index where the match should be inserted/exist, if - // it were in the array - DN_BinarySearchType_Match, - - // Index of the first element in the array that is `element >= find`. If no such - // item is found or the array is empty, then, the index is set to the array - // size and found is set to `false`. - // - // For example: - // int array[] = {0, 1, 2, 3, 4, 5}; - // DN_BinarySearchResult result = DN_BinarySearch(array, DN_ArrayCountU(array), 4, DN_BinarySearchType_LowerBound); - // printf("%zu\n", result.index); // Prints index '4' - - DN_BinarySearchType_LowerBound, - - // Index of the first element in the array that is `element > find`. If no such - // item is found or the array is empty, then, the index is set to the array - // size and found is set to `false`. - // - // For example: - // int array[] = {0, 1, 2, 3, 4, 5}; - // DN_BinarySearchResult result = DN_BinarySearch(array, DN_ArrayCountU(array), 4, DN_BinarySearchType_UpperBound); - // printf("%zu\n", result.index); // Prints index '5' - - DN_BinarySearchType_UpperBound, -}; - -struct DN_BinarySearchResult -{ - bool found; - DN_USize index; -}; - -template bool DN_BinarySearch_DefaultLessThan(T const &lhs, T const &rhs); -template DN_BinarySearchResult DN_BinarySearch (T const *array, DN_USize array_size, T const &find, DN_BinarySearchType type = DN_BinarySearchType_Match, DN_BinarySearchLessThanProc less_than = DN_BinarySearch_DefaultLessThan); - -template -bool DN_BinarySearch_DefaultLessThan(T const &lhs, T const &rhs) -{ - bool result = lhs < rhs; - return result; -} - -template -DN_BinarySearchResult DN_BinarySearch(T const *array, - DN_USize array_size, - T const &find, - DN_BinarySearchType type, - DN_BinarySearchLessThanProc less_than) -{ - DN_BinarySearchResult result = {}; - if (!array || array_size <= 0 || !less_than) - return result; - - T const *end = array + array_size; - T const *first = array; - T const *last = end; - while (first != last) { - DN_USize count = last - first; - T const *it = first + (count / 2); - - bool advance_first = false; - if (type == DN_BinarySearchType_UpperBound) - advance_first = !less_than(find, it[0]); - else - advance_first = less_than(it[0], find); - - if (advance_first) - first = it + 1; - else - last = it; - } - - switch (type) { - case DN_BinarySearchType_Match: { - result.found = first != end && !less_than(find, *first); - } break; - - case DN_BinarySearchType_LowerBound: /*FALLTHRU*/ - case DN_BinarySearchType_UpperBound: { - result.found = first != end; - } break; - } - - result.index = first - array; - return result; -} - -enum DN_LeakAllocFlag -{ - DN_LeakAllocFlag_Freed = 1 << 0, - DN_LeakAllocFlag_LeakPermitted = 1 << 1, -}; - -struct DN_LeakAlloc -{ - void *ptr; // 8 Pointer to the allocation being tracked - DN_USize size; // 16 Size of the allocation - DN_USize freed_size; // 24 Store the size of the allocation when it is freed - DN_Str8 stack_trace; // 40 Stack trace at the point of allocation - DN_Str8 freed_stack_trace; // 56 Stack trace of where the allocation was freed - DN_U16 flags; // 72 Bit flags from `DN_LeakAllocFlag` -}; - -// NOTE: We aim to keep the allocation record as light as possible as memory tracking can get -// expensive. Enforce that there is no unexpected padding. -DN_StaticAssert(sizeof(DN_LeakAlloc) == 64 || sizeof(DN_LeakAlloc) == 32); // NOTE: 64 bit vs 32 bit pointers respectively - -struct DN_LeakTracker -{ - DN_DSMap alloc_table; - DN_TicketMutex alloc_table_mutex; - DN_MemList alloc_table_mem; - DN_Arena alloc_table_arena; - DN_U64 alloc_table_bytes_allocated_for_stack_traces; -}; - -DN_API void DN_LeakTrackAlloc_ (DN_LeakTracker *leak, void *ptr, DN_USize size, bool alloc_can_leak); -DN_API void DN_LeakTrackDealloc_(DN_LeakTracker *leak, void *ptr); -DN_API void DN_LeakDump_ (DN_LeakTracker *leak); - -#if defined(DN_LEAK_TRACKING) -#define DN_LeakTrackAlloc(leak, ptr, size, alloc_can_leak) DN_LeakTrackAlloc_(leak, ptr, size, alloc_can_leak) -#define DN_LeakTrackDealloc(leak, ptr) DN_LeakTrackDealloc_(leak, ptr) -#define DN_LeakDump(leak) DN_LeakDump_(leak) -#else -#define DN_LeakTrackAlloc(leak, ptr, size, alloc_can_leak) do { (void)ptr; (void)size; (void)alloc_can_leak; } while (0) -#define DN_LeakTrackDealloc(leak, ptr) do { (void)ptr; } while (0) -#define DN_LeakDump(leak) do { } while (0) -#endif - -// NOTE: Template implementations -#if defined(__cplusplus) -template T *DN_MemCopyObjT(T *dest, T const *src, DN_USize count) -{ - T* result = dest; - DN_Memcpy(dest, src, sizeof(T) * count); - return result; -} - -template -DN_ArrayFindResult DN_TArrayFind(T *data, DN_USize size, void const *find, DN_ArrayFindEqFunc *eq_func) -{ - DN_ArrayFindResult result = DN_ArrayFind(data, size, sizeof(*data), find, eq_func); - return result; -} - -template -DN_ArrayFindResult DN_TArrayFindMemEq(T *data, DN_USize size, void const *find) -{ - DN_ArrayFindResult result = DN_ArrayFindMemEq(data, size, sizeof(*data), find); - return result; -} - -template -T *DN_TArrayInsertArray(T *data, DN_USize *size, DN_USize max, DN_USize index, T const *items, DN_USize count) -{ - T *result = DN_Cast(T *)DN_ArrayInsertArray(data, size, max, sizeof(*data), index, items, count); - return result; -} - -template -T *DN_TArrayPopFront(T *data, DN_USize *size, DN_USize count) -{ - T *result = DN_Cast(T *)DN_ArrayPopFront(data, size, sizeof(*data), count); - return result; -} - -template -T *DN_TArrayPopBack(T *data, DN_USize *size, DN_USize count) -{ - T *result = DN_Cast(T *)DN_ArrayPopBack(data, size, sizeof(*data), count); - return result; -} - -template -DN_ArrayEraseResult DN_TArrayEraseRange(T *data, DN_USize *size, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase) -{ - DN_ArrayEraseResult result = DN_ArrayEraseRange(data, size, sizeof(*data), begin_index, count, erase); - return result; -} - -template -T *DN_TArrayMakeArray(T *data, DN_USize *size, DN_USize max, DN_USize make_count, DN_ZMem z_mem) -{ - T *result = DN_Cast(T *)DN_ArrayMakeArray(data, size, max, sizeof(*data), make_count, z_mem); - return result; -} - -template -T *DN_TArrayMakeArrayAssert(T *data, DN_USize *size, DN_USize max, DN_USize make_count, DN_ZMem z_mem, DN_CallSite call_site) -{ - T *result = DN_Cast(T *)DN_ArrayMakeArrayAssert(data, size, max, sizeof(*data), make_count, z_mem, call_site); - return result; -} - -template -T *DN_TArrayMakeArrayAssertZ(T *data, DN_USize *size, DN_USize max, DN_USize make_count, DN_CallSite call_site) -{ - T* result = DN_TArrayMakeArrayAssert(data, size, max, make_count, DN_ZMem_Yes, call_site); - return result; -} - -template -T *DN_TArrayMakeArrayAssertNoZ(T *data, DN_USize *size, DN_USize max, DN_USize make_count, DN_CallSite call_site) -{ - T* result = DN_TArrayMakeArrayAssert(data, size, max, make_count, DN_ZMem_No, call_site); - return result; -} - -template -T *DN_TArrayAddArray(T *data, DN_USize *size, DN_USize max, T const *elems, DN_USize elems_count, DN_ArrayAdd add) -{ - T* result = DN_Cast(T *)DN_ArrayAddArray(data, size, max, sizeof(*elems), elems, elems_count, add); - return result; -} - -template -T *DN_TArrayAddArrayAssert(T *data, DN_USize *size, DN_USize max, T const *elems, DN_USize elems_count, DN_ArrayAdd add, DN_CallSite call_site) -{ - T* result = DN_Cast(T *)DN_ArrayAddArrayAssert(data, size, max, sizeof(*elems), elems, elems_count, add, call_site); - return result; -} - -template -bool DN_TArrayResizeFromPool(T **data, DN_USize *size, DN_USize *max, DN_Pool *pool, DN_USize new_max) -{ - bool result = DN_ArrayResizeFromPool(DN_Cast(void **)data, size, max, sizeof(**data), pool, new_max); - return result; -} - -template -bool DN_TArrayResizeFromArena(T **data, DN_USize *size, DN_USize *max, DN_Arena *arena, DN_USize new_max) -{ - bool result = DN_ArrayResizeFromArena(DN_Cast(void **)data, size, max, sizeof(**data), arena, new_max); - return result; -} - -template -bool DN_TArrayGrowFromPool(T **data, DN_USize size, DN_USize *max, DN_Pool *pool, DN_USize new_max) -{ - bool result = DN_ArrayGrowFromPool(DN_Cast(void **)data, size, max, sizeof(**data), pool, new_max); - return result; -} - -template -bool DN_TArrayGrowFromArena(T **data, DN_USize size, DN_USize *max, DN_Arena *arena, DN_USize new_max) -{ - bool result = DN_ArrayGrowFromArena(DN_Cast(void **)data, size, max, sizeof(**data), arena, new_max); - return result; -} - -template -bool DN_TArrayGrowIfNeededFromPool(T **data, DN_USize size, DN_USize *max, DN_Pool *pool, DN_USize add_count) -{ - bool result = DN_ArrayGrowIfNeededFromPool(DN_Cast(void **)data, size, max, sizeof(**data), pool, add_count); - return result; -} - -template -bool DN_TArrayGrowIfNeededFromArena(T **data, DN_USize size, DN_USize *max, DN_Arena *arena, DN_USize add_count) -{ - bool result = DN_ArrayGrowIfNeededFromArena(DN_Cast(void **)data, size, max, sizeof(**data), arena, add_count); - return result; -} -#endif // defined(__cplusplus) -#endif // !defined(DN_BASE_H) diff --git a/Source/External/metadesk/md.c b/Source/External/metadesk/md.c deleted file mode 100644 index 6be1e69..0000000 --- a/Source/External/metadesk/md.c +++ /dev/null @@ -1,4450 +0,0 @@ -// LICENSE AT END OF FILE (MIT). - -/* -** Overrides & Options Macros -** -** Overridable -** "basic types" ** REQUIRED -** #define/typedef MD_i8, MD_i16, MD_i32, MD_i64 -** #define/typedef MD_u8, MD_u16, MD_u32, MD_u64 -** #define/typedef MD_f32, MD_f64 -** -** "memset" ** REQUIRED -** #define MD_IMPL_Memset (void*, int, uint64) -> void* -** #define MD_IMPL_Memmove (void*, void*, uint64) -> void* -** -** "file iteration" ** OPTIONAL (required for the metadesk FileIter helpers to work) -** #define MD_IMPL_FileIterBegin (MD_FileIter*, MD_String8) -> Boolean -** #define MD_IMPL_FileIterNext (MD_Arena*, MD_FileIter*) -> MD_FileInfo -** #define MD_IMPL_FileIterEnd (MD_FileIter*) -> void -** -** "file load" ** OPTIONAL (required for MD_ParseWholeFile to work) -** #define MD_IMPL_LoadEntireFile (MD_Arena*, MD_String8 filename) -> MD_String8 -** -** "low level memory" ** OPTIONAL (required when relying on the default arenas) -** #define MD_IMPL_Reserve (uint64) -> void* -** #define MD_IMPL_Commit (void*, uint64) -> MD_b32 -** #define MD_IMPL_Decommit (void*, uint64) -> void -** #define MD_IMPL_Release (void*, uint64) -> void -** -** -** "arena" ** REQUIRED -** #define MD_IMPL_Arena (must set before including md.h) -** #define MD_IMPL_ArenaMinPos uint64 -** #define MD_IMPL_ArenaAlloc () -> MD_IMPL_Arena* -** #define MD_IMPL_ArenaRelease (MD_IMPL_Arena*) -> void -** #define MD_IMPL_ArenaGetPos (MD_IMPL_Arena*) -> uint64 -** #define MD_IMPL_ArenaPush (MD_IMPL_Arena*, uint64) -> void* -** #define MD_IMPL_ArenaPopTo (MD_IMPL_Arena*, uint64) -> void -** #define MD_IMPL_ArenaSetAutoAlign (MD_IMPL_Arena*, uint64) -> void -** -** "scratch" ** REQUIRED -** #define MD_IMPL_GetScratch (MD_IMPL_Arena**, uint64) -> MD_IMPL_Arena* -** "scratch constants" ** OPTIONAL (required for default scratch) -** #define MD_IMPL_ScratchCount uint64 [default 2] -** -** "sprintf" ** REQUIRED -** #define MD_IMPL_Vsnprintf (char*, uint64, char const*, va_list) -> uint64 -** -** Static Parameters to the Default Arena Implementation -** #define MD_DEFAULT_ARENA_RES_SIZE uint64 [default 64 megabytes] -** #define MD_DEFAULT_ARENA_CMT_SIZE uint64 [default 64 kilabytes] -** -** Default Implementation Controls -** These controls default to '1' i.e. 'enabled' -** #define MD_DEFAULT_BASIC_TYPES -> construct "basic types" from stdint.h header -** #define MD_DEFAULT_MEMSET -> construct "memset" from CRT -** #define MD_DEFAULT_FILE_ITER -> construct "file iteration" from OS headers -** #define MD_DEFAULT_MEMORY -> construct "low level memory" from OS headers -** #define MD_DEFAULT_ARENA -> construct "arena" from "low level memory" -** #define MD_DEFAULT_SCRATCH -> construct "scratch" from "arena" -** #define MD_DEFAULT_SPRINTF -> construct "vsnprintf" from internal implementaion -** -*/ - -#if !defined(MD_C) -#define MD_C - -//~///////////////////////////////////////////////////////////////////////////// -/////////////////////////// CRT Implementation ///////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -#if MD_DEFAULT_MEMSET - -#include -#include - -#if !defined(MD_IMPL_Memset) -# define MD_IMPL_Memset MD_CRT_Memset -#endif -#if !defined(MD_IMPL_Memmove) -# define MD_IMPL_Memmove MD_CRT_Memmove -#endif - -#define MD_CRT_Memset memset -#define MD_CRT_Memmove memmove - -#endif - -#if MD_DEFAULT_FILE_LOAD - -#include - -#if !defined(MD_IMPL_LoadEntireFile) -# define MD_IMPL_LoadEntireFile MD_CRT_LoadEntireFile -#endif - -MD_FUNCTION MD_String8 -MD_CRT_LoadEntireFile(MD_Arena *arena, MD_String8 filename) -{ - MD_ArenaTemp scratch = MD_GetScratch(&arena, 1); - MD_String8 file_contents = MD_ZERO_STRUCT; - MD_String8 filename_copy = MD_S8Copy(scratch.arena, filename); - FILE *file = fopen((char*)filename_copy.str, "rb"); - if(file != 0) - { - fseek(file, 0, SEEK_END); - MD_u64 file_size = ftell(file); - fseek(file, 0, SEEK_SET); - file_contents.str = MD_PushArray(arena, MD_u8, file_size+1); - if(file_contents.str) - { - file_contents.size = file_size; - fread(file_contents.str, 1, file_size, file); - file_contents.str[file_contents.size] = 0; - } - fclose(file); - } - MD_ReleaseScratch(scratch); - return file_contents; -} - -#endif - - -//~///////////////////////////////////////////////////////////////////////////// -/////////////////////////// Win32 Implementation /////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -//- win32 header -#if (MD_DEFAULT_FILE_ITER || MD_2DEFAULT_MEMORY) && MD_OS_WINDOWS -# include -# pragma comment(lib, "User32.lib") -#endif - -//- win32 "file iteration" -#if MD_DEFAULT_FILE_ITER && MD_OS_WINDOWS - -#if !defined(MD_IMPL_FileIterBegin) -# define MD_IMPL_FileIterBegin MD_WIN32_FileIterBegin -#endif -#if !defined(MD_IMPL_FileIterNext) -# define MD_IMPL_FileIterNext MD_WIN32_FileIterNext -#endif -#if !defined(MD_IMPL_FileIterEnd) -# define MD_IMPL_FileIterEnd MD_WIN32_FileIterEnd -#endif - -typedef struct MD_WIN32_FileIter{ - HANDLE state; - MD_u64 first; - WIN32_FIND_DATAW find_data; -} MD_WIN32_FileIter; - -MD_StaticAssert(sizeof(MD_FileIter) >= sizeof(MD_WIN32_FileIter), file_iter_size_check); - -static MD_b32 -MD_WIN32_FileIterBegin(MD_FileIter *it, MD_String8 path) -{ - //- init search - MD_ArenaTemp scratch = MD_GetScratch(0, 0); - - MD_u8 c = path.str[path.size - 1]; - MD_b32 need_star = (c == '/' || c == '\\'); - MD_String8 cpath = need_star ? MD_S8Fmt(scratch.arena, "%.*s*", MD_S8VArg(path)) : path; - MD_String16 cpath16 = MD_S16FromS8(scratch.arena, cpath); - - WIN32_FIND_DATAW find_data = MD_ZERO_STRUCT; - HANDLE state = FindFirstFileW((WCHAR*)cpath16.str, &find_data); - - MD_ReleaseScratch(scratch); - - //- fill results - MD_b32 result = !!state; - if (result) - { - MD_WIN32_FileIter *win32_it = (MD_WIN32_FileIter*)it; - win32_it->state = state; - win32_it->first = 1; - MD_MemoryCopy(&win32_it->find_data, &find_data, sizeof(find_data)); - } - return(result); -} - -static MD_FileInfo -MD_WIN32_FileIterNext(MD_Arena *arena, MD_FileIter *it) -{ - //- get low-level file info for this step - MD_b32 good = 0; - - MD_WIN32_FileIter *win32_it = (MD_WIN32_FileIter*)it; - WIN32_FIND_DATAW *find_data = &win32_it->find_data; - if (win32_it->first) - { - win32_it->first = 0; - good = 1; - } - else - { - good = FindNextFileW(win32_it->state, find_data); - } - - //- convert to MD_FileInfo - MD_FileInfo result = {0}; - if (good) - { - if (find_data->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - { - result.flags |= MD_FileFlag_Directory; - } - MD_u16 *filename_base = (MD_u16*)find_data->cFileName; - MD_u16 *ptr = filename_base; - for (;*ptr != 0; ptr += 1); - MD_String16 filename16 = {0}; - filename16.str = filename_base; - filename16.size = (MD_u64)(ptr - filename_base); - result.filename = MD_S8FromS16(arena, filename16); - result.file_size = ((((MD_u64)find_data->nFileSizeHigh) << 32) | - ((MD_u64)find_data->nFileSizeLow)); - } - return(result); -} - -static void -MD_WIN32_FileIterEnd(MD_FileIter *it) -{ - MD_WIN32_FileIter *win32_it = (MD_WIN32_FileIter*)it; - FindClose(win32_it->state); -} - -#endif - -//- win32 "low level memory" -#if MD_DEFAULT_MEMORY && MD_OS_WINDOWS - -#if !defined(MD_IMPL_Reserve) -# define MD_IMPL_Reserve MD_WIN32_Reserve -#endif -#if !defined(MD_IMPL_Commit) -# define MD_IMPL_Commit MD_WIN32_Commit -#endif -#if !defined(MD_IMPL_Decommit) -# define MD_IMPL_Decommit MD_WIN32_Decommit -#endif -#if !defined(MD_IMPL_Release) -# define MD_IMPL_Release MD_WIN32_Release -#endif - -static void* -MD_WIN32_Reserve(MD_u64 size) -{ - void *result = VirtualAlloc(0, size, MEM_RESERVE, PAGE_READWRITE); - return(result); -} - -static MD_b32 -MD_WIN32_Commit(void *ptr, MD_u64 size) -{ - MD_b32 result = (VirtualAlloc(ptr, size, MEM_COMMIT, PAGE_READWRITE) != 0); - return(result); -} - -static void -MD_WIN32_Decommit(void *ptr, MD_u64 size) -{ - VirtualFree(ptr, size, MEM_DECOMMIT); -} - -static void -MD_WIN32_Release(void *ptr, MD_u64 size) -{ - (void)size; - VirtualFree(ptr, 0, MEM_RELEASE); -} - -#endif - -//~///////////////////////////////////////////////////////////////////////////// -////////////////////////// Linux Implementation //////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -//- linux headers -#if (MD_DEFAULT_FILE_ITER || MD_DEFAULT_MEMORY) && (MD_OS_LINUX || MD_OS_MAC) -# include -# include -# include -# include -# include -# include -// NOTE(mal): To get these constants I need to #define _GNU_SOURCE, -// which invites non-POSIX behavior I'd rather avoid -# ifndef O_PATH -# define O_PATH 010000000 -# endif -# ifndef AT_NO_AUTOMOUNT -# define AT_NO_AUTOMOUNT 0x800 -# endif -# ifndef AT_SYMLINK_NOFOLLOW -# define AT_SYMLINK_NOFOLLOW 0x100 -# endif -#endif - -//- linux "file iteration" -#if MD_DEFAULT_FILE_ITER && MD_OS_LINUX - -#if !defined(MD_IMPL_FileIterIncrement) -# define MD_IMPL_FileIterIncrement MD_LINUX_FileIterIncrement -#endif - -typedef struct MD_LINUX_FileIter MD_LINUX_FileIter; -struct MD_LINUX_FileIter -{ - int dir_fd; - DIR *dir; -}; -MD_StaticAssert(sizeof(MD_LINUX_FileIter) <= sizeof(MD_FileIter), file_iter_size_check); - -static MD_b32 -MD_LINUX_FileIterIncrement(MD_Arena *arena, MD_FileIter *opaque_it, MD_String8 path, - MD_FileInfo *out_info) -{ - MD_b32 result = 0; - - MD_LINUX_FileIter *it = (MD_LINUX_FileIter *)opaque_it; - if(it->dir == 0) - { - it->dir = opendir((char*)path.str); - it->dir_fd = open((char *)path.str, O_PATH|O_CLOEXEC); - } - - if(it->dir != 0 && it->dir_fd != -1) - { - struct dirent *dir_entry = readdir(it->dir); - if(dir_entry) - { - out_info->filename = MD_S8Fmt(arena, "%s", dir_entry->d_name); - out_info->flags = 0; - - struct stat st; - if(fstatat(it->dir_fd, dir_entry->d_name, &st, AT_NO_AUTOMOUNT|AT_SYMLINK_NOFOLLOW) == 0) - { - if((st.st_mode & S_IFMT) == S_IFDIR) - { - out_info->flags |= MD_FileFlag_Directory; - } - out_info->file_size = st.st_size; - } - result = 1; - } - } - - if(result == 0) - { - if(it->dir != 0) - { - closedir(it->dir); - it->dir = 0; - } - if(it->dir_fd != -1) - { - close(it->dir_fd); - it->dir_fd = -1; - } - } - - return result; -} - -#endif - -//- linux "low level memory" -#if MD_DEFAULT_MEMORY && (MD_OS_LINUX || MD_OS_MAC) - -#if !defined(MD_IMPL_Reserve) -# define MD_IMPL_Reserve MD_LINUX_Reserve -#endif -#if !defined(MD_IMPL_Commit) -# define MD_IMPL_Commit MD_LINUX_Commit -#endif -#if !defined(MD_IMPL_Decommit) -# define MD_IMPL_Decommit MD_LINUX_Decommit -#endif -#if !defined(MD_IMPL_Release) -# define MD_IMPL_Release MD_LINUX_Release -#endif - -static void* -MD_LINUX_Reserve(MD_u64 size) -{ - void *result = mmap(0, size, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, (off_t)0); - if(result == (void *)-1) - { - result = 0; - } - return(result); -} - -static MD_b32 -MD_LINUX_Commit(void *ptr, MD_u64 size) -{ - MD_b32 result = (mprotect(ptr, size, PROT_READ|PROT_WRITE) == 0); - return(result); -} - -static void -MD_LINUX_Decommit(void *ptr, MD_u64 size) -{ - mprotect(ptr, size, PROT_NONE); - madvise(ptr, size, MADV_DONTNEED); -} - -static void -MD_LINUX_Release(void *ptr, MD_u64 size) -{ - munmap(ptr, size); -} - -#endif - -//~///////////////////////////////////////////////////////////////////////////// -///////////// MD Arena From Reserve/Commit/Decommit/Release //////////////////// -//////////////////////////////////////////////////////////////////////////////// - -#if MD_DEFAULT_ARENA - -#if !defined(MD_DEFAULT_ARENA_RES_SIZE) -# define MD_DEFAULT_ARENA_RES_SIZE (64 << 20) -#endif -#if !defined(MD_DEFAULT_ARENA_CMT_SIZE) -# define MD_DEFAULT_ARENA_CMT_SIZE (64 << 10) -#endif - -#define MD_DEFAULT_ARENA_VERY_BIG (MD_DEFAULT_ARENA_RES_SIZE - MD_IMPL_ArenaMinPos)/2 - -//- "low level memory" implementation check -#if !defined(MD_IMPL_Reserve) -# error Missing implementation for MD_IMPL_Reserve -#endif -#if !defined(MD_IMPL_Commit) -# error Missing implementation for MD_IMPL_Commit -#endif -#if !defined(MD_IMPL_Decommit) -# error Missing implementation for MD_IMPL_Decommit -#endif -#if !defined(MD_IMPL_Release) -# error Missing implementation for MD_IMPL_Release -#endif - -#define MD_IMPL_ArenaMinPos 64 -MD_StaticAssert(sizeof(MD_ArenaDefault) <= MD_IMPL_ArenaMinPos, arena_def_size_check); - -#define MD_IMPL_ArenaAlloc MD_ArenaDefaultAlloc -#define MD_IMPL_ArenaRelease MD_ArenaDefaultRelease -#define MD_IMPL_ArenaGetPos MD_ArenaDefaultGetPos -#define MD_IMPL_ArenaPush MD_ArenaDefaultPush -#define MD_IMPL_ArenaPopTo MD_ArenaDefaultPopTo -#define MD_IMPL_ArenaSetAutoAlign MD_ArenaDefaultSetAutoAlign - -static MD_ArenaDefault* -MD_ArenaDefaultAlloc__Size(MD_u64 cmt, MD_u64 res) -{ - MD_Assert(MD_IMPL_ArenaMinPos < cmt && cmt <= res); - MD_u64 cmt_clamped = MD_ClampTop(cmt, res); - MD_ArenaDefault *result = 0; - void *mem = MD_IMPL_Reserve(res); - if (MD_IMPL_Commit(mem, cmt_clamped)) - { - result = (MD_ArenaDefault*)mem; - result->prev = 0; - result->current = result; - result->base_pos = 0; - result->pos = MD_IMPL_ArenaMinPos; - result->cmt = cmt_clamped; - result->cap = res; - result->align = 8; - } - return(result); -} - -static MD_ArenaDefault* -MD_ArenaDefaultAlloc(void) -{ - MD_ArenaDefault *result = MD_ArenaDefaultAlloc__Size(MD_DEFAULT_ARENA_CMT_SIZE, - MD_DEFAULT_ARENA_RES_SIZE); - return(result); -} - -static void -MD_ArenaDefaultRelease(MD_ArenaDefault *arena) -{ - for (MD_ArenaDefault *node = arena->current, *prev = 0; - node != 0; - node = prev) - { - prev = node->prev; - MD_IMPL_Release(node, node->cap); - } -} - -static MD_u64 -MD_ArenaDefaultGetPos(MD_ArenaDefault *arena) -{ - MD_ArenaDefault *current = arena->current; - MD_u64 result = current->base_pos + current->pos; - return(result); -} - -static void* -MD_ArenaDefaultPush(MD_ArenaDefault *arena, MD_u64 size) -{ - // try to be fast! - MD_ArenaDefault *current = arena->current; - MD_u64 align = arena->align; - MD_u64 pos = current->pos; - MD_u64 pos_aligned = MD_AlignPow2(pos, align); - MD_u64 new_pos = pos_aligned + size; - void *result = (MD_u8*)current + pos_aligned; - current->pos = new_pos; - - // if it's not going to work do the slow path - if (new_pos > current->cmt) - { - result = 0; - current->pos = pos; - - // new chunk if necessary - if (new_pos > current->cap) - { - MD_ArenaDefault *new_arena = 0; - if (size > MD_DEFAULT_ARENA_VERY_BIG) - { - MD_u64 big_size_unrounded = size + MD_IMPL_ArenaMinPos; - MD_u64 big_size = MD_AlignPow2(big_size_unrounded, (4 << 10)); - new_arena = MD_ArenaDefaultAlloc__Size(big_size, big_size); - } - else - { - new_arena = MD_ArenaDefaultAlloc(); - } - - // link in new chunk & recompute new_pos - if (new_arena != 0) - { - new_arena->base_pos = current->base_pos + current->cap; - new_arena->prev = current; - current = new_arena; - pos_aligned = current->pos; - new_pos = pos_aligned + size; - } - } - - // move ahead if the current chunk has enough reserve - if (new_pos <= current->cap) - { - - // extend commit if necessary - if (new_pos > current->cmt) - { - MD_u64 new_cmt_unclamped = MD_AlignPow2(new_pos, MD_DEFAULT_ARENA_CMT_SIZE); - MD_u64 new_cmt = MD_ClampTop(new_cmt_unclamped, current->cap); - MD_u64 cmt_size = new_cmt - current->cmt; - if (MD_IMPL_Commit((MD_u8*)current + current->cmt, cmt_size)) - { - current->cmt = new_cmt; - } - } - - // move ahead if the current chunk has enough commit - if (new_pos <= current->cmt) - { - result = (MD_u8*)current + current->pos; - current->pos = new_pos; - } - } - } - - return(result); -} - -static void -MD_ArenaDefaultPopTo(MD_ArenaDefault *arena, MD_u64 pos) -{ - // pop chunks in the chain - MD_u64 pos_clamped = MD_ClampBot(MD_IMPL_ArenaMinPos, pos); - (void)pos_clamped; - { - MD_ArenaDefault *node = arena->current; - for (MD_ArenaDefault *prev = 0; - node != 0 && node->base_pos >= pos; - node = prev) - { - prev = node->prev; - MD_IMPL_Release(node, node->cap); - } - arena->current = node; - } - - // reset the pos of the current - { - MD_ArenaDefault *current = arena->current; - MD_u64 local_pos_unclamped = pos - current->base_pos; - MD_u64 local_pos = MD_ClampBot(local_pos_unclamped, MD_IMPL_ArenaMinPos); - current->pos = local_pos; - } -} - -static void -MD_ArenaDefaultSetAutoAlign(MD_ArenaDefault *arena, MD_u64 align) -{ - arena->align = align; -} - -static void -MD_ArenaDefaultAbsorb(MD_ArenaDefault *arena, MD_ArenaDefault *sub_arena) -{ - MD_ArenaDefault *current = arena->current; - MD_u64 base_pos_shift = current->base_pos + current->cap; - for (MD_ArenaDefault *node = sub_arena->current; - node != 0; - node = node->prev) - { - node->base_pos += base_pos_shift; - } - sub_arena->prev = arena->current; - arena->current = sub_arena->current; -} - -#endif - -//- "arena" implementation checks -#if !defined(MD_IMPL_ArenaAlloc) -# error Missing implementation for MD_IMPL_ArenaAlloc -#endif -#if !defined(MD_IMPL_ArenaRelease) -# error Missing implementation for MD_IMPL_ArenaRelease -#endif -#if !defined(MD_IMPL_ArenaGetPos) -# error Missing implementation for MD_IMPL_ArenaGetPos -#endif -#if !defined(MD_IMPL_ArenaPush) -# error Missing implementation for MD_IMPL_ArenaPush -#endif -#if !defined(MD_IMPL_ArenaPopTo) -# error Missing implementation for MD_IMPL_ArenaPopTo -#endif -#if !defined(MD_IMPL_ArenaSetAutoAlign) -# error Missing implementation for MD_IMPL_ArenaSetAutoAlign -#endif -#if !defined(MD_IMPL_ArenaMinPos) -# error Missing implementation for MD_IMPL_ArenaMinPos -#endif - -//~///////////////////////////////////////////////////////////////////////////// -///////////////////////////// MD Scratch Pool ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -#if MD_DEFAULT_SCRATCH - -#if !defined(MD_IMPL_ScratchCount) -# define MD_IMPL_ScratchCount 2llu -#endif - -#if !defined(MD_IMPL_GetScratch) -# define MD_IMPL_GetScratch MD_GetScratchDefault -#endif - -MD_THREAD_LOCAL MD_Arena *md_thread_scratch_pool[MD_IMPL_ScratchCount] = {0, 0}; - -static MD_Arena* -MD_GetScratchDefault(MD_Arena **conflicts, MD_u64 count) -{ - MD_Arena **scratch_pool = md_thread_scratch_pool; - if (scratch_pool[0] == 0) - { - MD_Arena **arena_ptr = scratch_pool; - for (MD_u64 i = 0; i < MD_IMPL_ScratchCount; i += 1, arena_ptr += 1) - { - *arena_ptr = MD_ArenaAlloc(); - } - } - MD_Arena *result = 0; - MD_Arena **arena_ptr = scratch_pool; - for (MD_u64 i = 0; i < MD_IMPL_ScratchCount; i += 1, arena_ptr += 1) - { - MD_Arena *arena = *arena_ptr; - MD_Arena **conflict_ptr = conflicts; - for (MD_u32 j = 0; j < count; j += 1, conflict_ptr += 1) - { - if (arena == *conflict_ptr) - { - arena = 0; - break; - } - } - if (arena != 0) - { - result = arena; - break; - } - } - return(result); -} - -#endif - -//~///////////////////////////////////////////////////////////////////////////// -//////////////////////// MD Library Implementation ///////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -#if MD_DEFAULT_SPRINTF -#define STB_SPRINTF_IMPLEMENTATION -#define STB_SPRINTF_DECORATE(name) md_stbsp_##name -#include "md_stb_sprintf.h" -#endif - - -//~ Nil Node Definition - -static MD_Node _md_nil_node = -{ - &_md_nil_node, // next - &_md_nil_node, // prev - &_md_nil_node, // parent - &_md_nil_node, // first_child - &_md_nil_node, // last_child - &_md_nil_node, // first_tag - &_md_nil_node, // last_tag - MD_NodeKind_Nil, // kind - 0, // flags - MD_ZERO_STRUCT, // string - MD_ZERO_STRUCT, // raw_string - 0, // at - &_md_nil_node, // ref_target - MD_ZERO_STRUCT, // prev_comment - MD_ZERO_STRUCT, // next_comment -}; - -//~ Arena Functions - -MD_FUNCTION MD_Arena* -MD_ArenaAlloc(void) -{ - return(MD_IMPL_ArenaAlloc()); -} - -MD_FUNCTION void -MD_ArenaRelease(MD_Arena *arena) -{ - MD_IMPL_ArenaRelease(arena); -} - -MD_FUNCTION void* -MD_ArenaPush(MD_Arena *arena, MD_u64 size) -{ - void *result = MD_IMPL_ArenaPush(arena, size); - return(result); -} - -MD_FUNCTION void -MD_ArenaPutBack(MD_Arena *arena, MD_u64 size) -{ - MD_u64 pos = MD_IMPL_ArenaGetPos(arena); - MD_u64 new_pos = pos - size; - MD_u64 new_pos_clamped = MD_ClampBot(MD_IMPL_ArenaMinPos, new_pos); - MD_IMPL_ArenaPopTo(arena, new_pos_clamped); -} - -MD_FUNCTION void -MD_ArenaSetAlign(MD_Arena *arena, MD_u64 boundary) -{ - MD_IMPL_ArenaSetAutoAlign(arena, boundary); -} - -MD_FUNCTION void -MD_ArenaPushAlign(MD_Arena *arena, MD_u64 boundary) -{ - MD_u64 pos = MD_IMPL_ArenaGetPos(arena); - MD_u64 align_m1 = boundary - 1; - MD_u64 new_pos_aligned = (pos + align_m1)&(~align_m1); - if (new_pos_aligned > pos) - { - MD_u64 amt = new_pos_aligned - pos; - MD_MemoryZero(MD_IMPL_ArenaPush(arena, amt), amt); - } -} - -MD_FUNCTION void -MD_ArenaClear(MD_Arena *arena) -{ - MD_IMPL_ArenaPopTo(arena, MD_IMPL_ArenaMinPos); -} - -MD_FUNCTION MD_ArenaTemp -MD_ArenaBeginTemp(MD_Arena *arena) -{ - MD_ArenaTemp result; - result.arena = arena; - result.pos = MD_IMPL_ArenaGetPos(arena); - return(result); -} - -MD_FUNCTION void -MD_ArenaEndTemp(MD_ArenaTemp temp) -{ - MD_IMPL_ArenaPopTo(temp.arena, temp.pos); -} - -//~ Arena Scratch Pool - -MD_FUNCTION MD_ArenaTemp -MD_GetScratch(MD_Arena **conflicts, MD_u64 count) -{ - MD_Arena *arena = MD_IMPL_GetScratch(conflicts, count); - MD_ArenaTemp result = MD_ZERO_STRUCT; - if (arena != 0) - { - result = MD_ArenaBeginTemp(arena); - } - return(result); -} - -//~ Characters - -MD_FUNCTION MD_b32 -MD_CharIsAlpha(MD_u8 c) -{ - return MD_CharIsAlphaUpper(c) || MD_CharIsAlphaLower(c); -} - -MD_FUNCTION MD_b32 -MD_CharIsAlphaUpper(MD_u8 c) -{ - return c >= 'A' && c <= 'Z'; -} - -MD_FUNCTION MD_b32 -MD_CharIsAlphaLower(MD_u8 c) -{ - return c >= 'a' && c <= 'z'; -} - -MD_FUNCTION MD_b32 -MD_CharIsDigit(MD_u8 c) -{ - return (c >= '0' && c <= '9'); -} - -MD_FUNCTION MD_b32 -MD_CharIsUnreservedSymbol(MD_u8 c) -{ - return (c == '~' || c == '!' || c == '$' || c == '%' || c == '^' || - c == '&' || c == '*' || c == '-' || c == '=' || c == '+' || - c == '<' || c == '.' || c == '>' || c == '/' || c == '?' || - c == '|'); -} - -MD_FUNCTION MD_b32 -MD_CharIsReservedSymbol(MD_u8 c) -{ - return (c == '{' || c == '}' || c == '(' || c == ')' || c == '\\' || - c == '[' || c == ']' || c == '#' || c == ',' || c == ';' || - c == ':' || c == '@'); -} - -MD_FUNCTION MD_b32 -MD_CharIsSpace(MD_u8 c) -{ - return c == ' ' || c == '\r' || c == '\t' || c == '\f' || c == '\v' || c == '\n'; -} - -MD_FUNCTION MD_u8 -MD_CharToUpper(MD_u8 c) -{ - return (c >= 'a' && c <= 'z') ? ('A' + (c - 'a')) : c; -} - -MD_FUNCTION MD_u8 -MD_CharToLower(MD_u8 c) -{ - return (c >= 'A' && c <= 'Z') ? ('a' + (c - 'A')) : c; -} - -MD_FUNCTION MD_u8 -MD_CharToForwardSlash(MD_u8 c) -{ - return (c == '\\' ? '/' : c); -} - -//~ Strings - -MD_FUNCTION MD_u64 -MD_CalculateCStringLength(char *cstr) -{ - MD_u64 i = 0; - for(; cstr[i]; i += 1); - return i; -} - -MD_FUNCTION MD_String8 -MD_S8(MD_u8 *str, MD_u64 size) -{ - MD_String8 string; - string.str = str; - string.size = size; - return string; -} - -MD_FUNCTION MD_String8 -MD_S8Range(MD_u8 *first, MD_u8 *opl) -{ - MD_String8 string; - string.str = first; - string.size = (MD_u64)(opl - first); - return string; -} - -MD_FUNCTION MD_String8 -MD_S8Substring(MD_String8 str, MD_u64 min, MD_u64 max) -{ - if(max > str.size) - { - max = str.size; - } - if(min > str.size) - { - min = str.size; - } - if(min > max) - { - MD_u64 swap = min; - min = max; - max = swap; - } - str.size = max - min; - str.str += min; - return str; -} - -MD_FUNCTION MD_String8 -MD_S8Skip(MD_String8 str, MD_u64 min) -{ - return MD_S8Substring(str, min, str.size); -} - -MD_FUNCTION MD_String8 -MD_S8Chop(MD_String8 str, MD_u64 nmax) -{ - return MD_S8Substring(str, 0, str.size - nmax); -} - -MD_FUNCTION MD_String8 -MD_S8Prefix(MD_String8 str, MD_u64 size) -{ - return MD_S8Substring(str, 0, size); -} - -MD_FUNCTION MD_String8 -MD_S8Suffix(MD_String8 str, MD_u64 size) -{ - return MD_S8Substring(str, str.size - size, str.size); -} - -MD_FUNCTION MD_b32 -MD_S8Match(MD_String8 a, MD_String8 b, MD_MatchFlags flags) -{ - int result = 0; - if(a.size == b.size || flags & MD_StringMatchFlag_RightSideSloppy) - { - result = 1; - for(MD_u64 i = 0; i < a.size && i < b.size; i += 1) - { - MD_b32 match = (a.str[i] == b.str[i]); - if(flags & MD_StringMatchFlag_CaseInsensitive) - { - match |= (MD_CharToLower(a.str[i]) == MD_CharToLower(b.str[i])); - } - if(flags & MD_StringMatchFlag_SlashInsensitive) - { - match |= (MD_CharToForwardSlash(a.str[i]) == MD_CharToForwardSlash(b.str[i])); - } - if(match == 0) - { - result = 0; - break; - } - } - } - return result; -} - -MD_FUNCTION MD_u64 -MD_S8FindSubstring(MD_String8 str, MD_String8 substring, MD_u64 start_pos, MD_MatchFlags flags) -{ - MD_b32 found = 0; - MD_u64 found_idx = str.size; - for(MD_u64 i = start_pos; i < str.size; i += 1) - { - if(i + substring.size <= str.size) - { - MD_String8 substr_from_str = MD_S8Substring(str, i, i+substring.size); - if(MD_S8Match(substr_from_str, substring, flags)) - { - found_idx = i; - found = 1; - if(!(flags & MD_MatchFlag_FindLast)) - { - break; - } - } - } - } - return found_idx; -} - -MD_FUNCTION MD_String8 -MD_S8Copy(MD_Arena *arena, MD_String8 string) -{ - MD_String8 res; - res.size = string.size; - res.str = MD_PushArray(arena, MD_u8, string.size + 1); - MD_MemoryCopy(res.str, string.str, string.size); - res.str[string.size] = 0; - return(res); -} - -MD_FUNCTION MD_String8 -MD_S8FmtV(MD_Arena *arena, char *fmt, va_list args) -{ - MD_String8 result = MD_ZERO_STRUCT; - va_list args2; - va_copy(args2, args); - MD_u64 needed_bytes = MD_IMPL_Vsnprintf(0, 0, fmt, args)+1; - result.str = MD_PushArray(arena, MD_u8, needed_bytes); - result.size = needed_bytes - 1; - result.str[needed_bytes-1] = 0; - MD_IMPL_Vsnprintf((char*)result.str, (int)needed_bytes, fmt, args2); - return result; -} - -MD_FUNCTION MD_String8 -MD_S8Fmt(MD_Arena *arena, char *fmt, ...) -{ - va_list args; - va_start(args, fmt); - MD_String8 result = MD_S8FmtV(arena, fmt, args); - va_end(args); - return result; -} - -MD_FUNCTION void -MD_S8ListPush(MD_Arena *arena, MD_String8List *list, MD_String8 string) -{ - MD_String8Node *node = MD_PushArrayZero(arena, MD_String8Node, 1); - node->string = string; - - MD_QueuePush(list->first, list->last, node); - list->node_count += 1; - list->total_size += string.size; -} - -MD_FUNCTION void -MD_S8ListPushFmt(MD_Arena *arena, MD_String8List *list, char *fmt, ...) -{ - va_list args; - va_start(args, fmt); - MD_String8 string = MD_S8FmtV(arena, fmt, args); - va_end(args); - MD_S8ListPush(arena, list, string); -} - -MD_FUNCTION void -MD_S8ListConcat(MD_String8List *list, MD_String8List *to_push) -{ - if(to_push->first) - { - list->node_count += to_push->node_count; - list->total_size += to_push->total_size; - - if(list->last == 0) - { - *list = *to_push; - } - else - { - list->last->next = to_push->first; - list->last = to_push->last; - } - } - MD_MemoryZeroStruct(to_push); -} - -MD_FUNCTION MD_String8List -MD_S8Split(MD_Arena *arena, MD_String8 string, int splitter_count, - MD_String8 *splitters) -{ - MD_String8List list = MD_ZERO_STRUCT; - - MD_u64 split_start = 0; - for(MD_u64 i = 0; i < string.size; i += 1) - { - MD_b32 was_split = 0; - for(int split_idx = 0; split_idx < splitter_count; split_idx += 1) - { - MD_b32 match = 0; - if(i + splitters[split_idx].size <= string.size) - { - match = 1; - for(MD_u64 split_i = 0; split_i < splitters[split_idx].size && i + split_i < string.size; split_i += 1) - { - if(splitters[split_idx].str[split_i] != string.str[i + split_i]) - { - match = 0; - break; - } - } - } - if(match) - { - MD_String8 split_string = MD_S8(string.str + split_start, i - split_start); - MD_S8ListPush(arena, &list, split_string); - split_start = i + splitters[split_idx].size; - i += splitters[split_idx].size - 1; - was_split = 1; - break; - } - } - - if(was_split == 0 && i == string.size - 1) - { - MD_String8 split_string = MD_S8(string.str + split_start, i+1 - split_start); - MD_S8ListPush(arena, &list, split_string); - break; - } - } - - return list; -} - -MD_FUNCTION MD_String8 -MD_S8ListJoin(MD_Arena *arena, MD_String8List list, MD_StringJoin *join_ptr) -{ - // setup join parameters - MD_StringJoin join = MD_ZERO_STRUCT; - if (join_ptr != 0) - { - MD_MemoryCopy(&join, join_ptr, sizeof(join)); - } - - // calculate size & allocate - MD_u64 sep_count = 0; - if (list.node_count > 1) - { - sep_count = list.node_count - 1; - } - MD_String8 result = MD_ZERO_STRUCT; - result.size = (list.total_size + join.pre.size + - sep_count*join.mid.size + join.post.size); - result.str = MD_PushArrayZero(arena, MD_u8, result.size); - - // fill - MD_u8 *ptr = result.str; - MD_MemoryCopy(ptr, join.pre.str, join.pre.size); - ptr += join.pre.size; - for(MD_String8Node *node = list.first; node; node = node->next) - { - MD_MemoryCopy(ptr, node->string.str, node->string.size); - ptr += node->string.size; - if (node != list.last) - { - MD_MemoryCopy(ptr, join.mid.str, join.mid.size); - ptr += join.mid.size; - } - } - MD_MemoryCopy(ptr, join.post.str, join.post.size); - ptr += join.post.size; - - return(result); -} - -MD_FUNCTION MD_String8 -MD_S8ListJoinMid(MD_Arena *arena, MD_String8List list, - MD_String8 mid_separator) -{ - MD_StringJoin join = MD_ZERO_STRUCT; - join.pre = MD_S8Lit(""); - join.post = MD_S8Lit(""); - join.mid = mid_separator; - MD_String8 result = MD_S8ListJoin(arena, list, &join); - return result; -} - -MD_FUNCTION MD_String8 -MD_S8Stylize(MD_Arena *arena, MD_String8 string, MD_IdentifierStyle word_style, - MD_String8 separator) -{ - MD_String8 result = MD_ZERO_STRUCT; - - MD_String8List words = MD_ZERO_STRUCT; - - MD_b32 break_on_uppercase = 0; - { - break_on_uppercase = 1; - for(MD_u64 i = 0; i < string.size; i += 1) - { - if(!MD_CharIsAlpha(string.str[i]) && !MD_CharIsDigit(string.str[i])) - { - break_on_uppercase = 0; - break; - } - } - } - - MD_b32 making_word = 0; - MD_String8 word = MD_ZERO_STRUCT; - - for(MD_u64 i = 0; i < string.size;) - { - if(making_word) - { - if((break_on_uppercase && MD_CharIsAlphaUpper(string.str[i])) || - string.str[i] == '_' || MD_CharIsSpace(string.str[i]) || - i == string.size - 1) - { - if(i == string.size - 1) - { - word.size += 1; - } - making_word = 0; - MD_S8ListPush(arena, &words, word); - } - else - { - word.size += 1; - i += 1; - } - } - else - { - if(MD_CharIsAlpha(string.str[i])) - { - making_word = 1; - word.str = string.str + i; - word.size = 1; - } - i += 1; - } - } - - result.size = words.total_size; - if(words.node_count > 1) - { - result.size += separator.size*(words.node_count-1); - } - result.str = MD_PushArrayZero(arena, MD_u8, result.size); - - { - MD_u64 write_pos = 0; - for(MD_String8Node *node = words.first; node; node = node->next) - { - - // NOTE(rjf): Write word string to result. - { - MD_MemoryCopy(result.str + write_pos, node->string.str, node->string.size); - - // NOTE(rjf): Transform string based on word style. - switch(word_style) - { - case MD_IdentifierStyle_UpperCamelCase: - { - result.str[write_pos] = MD_CharToUpper(result.str[write_pos]); - for(MD_u64 i = write_pos+1; i < write_pos + node->string.size; i += 1) - { - result.str[i] = MD_CharToLower(result.str[i]); - } - }break; - - case MD_IdentifierStyle_LowerCamelCase: - { - MD_b32 is_first = (node == words.first); - result.str[write_pos] = (is_first ? - MD_CharToLower(result.str[write_pos]) : - MD_CharToUpper(result.str[write_pos])); - for(MD_u64 i = write_pos+1; i < write_pos + node->string.size; i += 1) - { - result.str[i] = MD_CharToLower(result.str[i]); - } - }break; - - case MD_IdentifierStyle_UpperCase: - { - for(MD_u64 i = write_pos; i < write_pos + node->string.size; i += 1) - { - result.str[i] = MD_CharToUpper(result.str[i]); - } - }break; - - case MD_IdentifierStyle_LowerCase: - { - for(MD_u64 i = write_pos; i < write_pos + node->string.size; i += 1) - { - result.str[i] = MD_CharToLower(result.str[i]); - } - }break; - - default: break; - } - - write_pos += node->string.size; - } - - if(node->next) - { - MD_MemoryCopy(result.str + write_pos, separator.str, separator.size); - write_pos += separator.size; - } - } - } - - return result; -} - -//~ Unicode Conversions - -MD_GLOBAL MD_u8 md_utf8_class[32] = { - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,2,2,2,2,3,3,4,5, -}; - -MD_FUNCTION MD_DecodedCodepoint -MD_DecodeCodepointFromUtf8(MD_u8 *str, MD_u64 max) -{ -#define MD_bitmask1 0x01 -#define MD_bitmask2 0x03 -#define MD_bitmask3 0x07 -#define MD_bitmask4 0x0F -#define MD_bitmask5 0x1F -#define MD_bitmask6 0x3F -#define MD_bitmask7 0x7F -#define MD_bitmask8 0xFF -#define MD_bitmask9 0x01FF -#define MD_bitmask10 0x03FF - - MD_DecodedCodepoint result = {~((MD_u32)0), 1}; - MD_u8 byte = str[0]; - MD_u8 byte_class = md_utf8_class[byte >> 3]; - switch (byte_class) - { - case 1: - { - result.codepoint = byte; - }break; - - case 2: - { - if (2 <= max) - { - MD_u8 cont_byte = str[1]; - if (md_utf8_class[cont_byte >> 3] == 0) - { - result.codepoint = (byte & MD_bitmask5) << 6; - result.codepoint |= (cont_byte & MD_bitmask6); - result.advance = 2; - } - } - }break; - - case 3: - { - if (3 <= max) - { - MD_u8 cont_byte[2] = {0}; - cont_byte[0] = str[1]; - cont_byte[1] = str[2]; - if (md_utf8_class[cont_byte[0] >> 3] == 0 && - md_utf8_class[cont_byte[1] >> 3] == 0) - { - result.codepoint = (byte & MD_bitmask4) << 12; - result.codepoint |= ((cont_byte[0] & MD_bitmask6) << 6); - result.codepoint |= (cont_byte[1] & MD_bitmask6); - result.advance = 3; - } - } - }break; - - case 4: - { - if (4 <= max) - { - MD_u8 cont_byte[3] = {0}; - cont_byte[0] = str[1]; - cont_byte[1] = str[2]; - cont_byte[2] = str[3]; - if (md_utf8_class[cont_byte[0] >> 3] == 0 && - md_utf8_class[cont_byte[1] >> 3] == 0 && - md_utf8_class[cont_byte[2] >> 3] == 0) - { - result.codepoint = (byte & MD_bitmask3) << 18; - result.codepoint |= ((cont_byte[0] & MD_bitmask6) << 12); - result.codepoint |= ((cont_byte[1] & MD_bitmask6) << 6); - result.codepoint |= (cont_byte[2] & MD_bitmask6); - result.advance = 4; - } - } - }break; - } - - return(result); -} - -MD_FUNCTION MD_DecodedCodepoint -MD_DecodeCodepointFromUtf16(MD_u16 *out, MD_u64 max) -{ - MD_DecodedCodepoint result = {~((MD_u32)0), 1}; - result.codepoint = out[0]; - result.advance = 1; - if (1 < max && 0xD800 <= out[0] && out[0] < 0xDC00 && 0xDC00 <= out[1] && out[1] < 0xE000) - { - result.codepoint = ((out[0] - 0xD800) << 10) | (out[1] - 0xDC00); - result.advance = 2; - } - return(result); -} - -MD_FUNCTION MD_u32 -MD_Utf8FromCodepoint(MD_u8 *out, MD_u32 codepoint) -{ -#define MD_bit8 0x80 - MD_u32 advance = 0; - if (codepoint <= 0x7F) - { - out[0] = (MD_u8)codepoint; - advance = 1; - } - else if (codepoint <= 0x7FF) - { - out[0] = (MD_bitmask2 << 6) | ((codepoint >> 6) & MD_bitmask5); - out[1] = MD_bit8 | (codepoint & MD_bitmask6); - advance = 2; - } - else if (codepoint <= 0xFFFF) - { - out[0] = (MD_bitmask3 << 5) | ((codepoint >> 12) & MD_bitmask4); - out[1] = MD_bit8 | ((codepoint >> 6) & MD_bitmask6); - out[2] = MD_bit8 | ( codepoint & MD_bitmask6); - advance = 3; - } - else if (codepoint <= 0x10FFFF) - { - out[0] = (MD_bitmask4 << 3) | ((codepoint >> 18) & MD_bitmask3); - out[1] = MD_bit8 | ((codepoint >> 12) & MD_bitmask6); - out[2] = MD_bit8 | ((codepoint >> 6) & MD_bitmask6); - out[3] = MD_bit8 | ( codepoint & MD_bitmask6); - advance = 4; - } - else - { - out[0] = '?'; - advance = 1; - } - return(advance); -} - -MD_FUNCTION MD_u32 -MD_Utf16FromCodepoint(MD_u16 *out, MD_u32 codepoint) -{ - MD_u32 advance = 1; - if (codepoint == ~((MD_u32)0)) - { - out[0] = (MD_u16)'?'; - } - else if (codepoint < 0x10000) - { - out[0] = (MD_u16)codepoint; - } - else - { - MD_u64 v = codepoint - 0x10000; - out[0] = (MD_u16)(0xD800 + (v >> 10)); - out[1] = 0xDC00 + (v & MD_bitmask10); - advance = 2; - } - return(advance); -} - -MD_FUNCTION MD_String8 -MD_S8FromS16(MD_Arena *arena, MD_String16 in) -{ - MD_u64 cap = in.size*3; - MD_u8 *str = MD_PushArrayZero(arena, MD_u8, cap + 1); - MD_u16 *ptr = in.str; - MD_u16 *opl = ptr + in.size; - MD_u64 size = 0; - MD_DecodedCodepoint consume; - for (;ptr < opl;) - { - consume = MD_DecodeCodepointFromUtf16(ptr, opl - ptr); - ptr += consume.advance; - size += MD_Utf8FromCodepoint(str + size, consume.codepoint); - } - str[size] = 0; - MD_ArenaPutBack(arena, cap - size); // := ((cap + 1) - (size + 1)) - return(MD_S8(str, size)); -} - -MD_FUNCTION MD_String16 -MD_S16FromS8(MD_Arena *arena, MD_String8 in) -{ - MD_u64 cap = in.size*2; - MD_u16 *str = MD_PushArrayZero(arena, MD_u16, cap + 1); - MD_u8 *ptr = in.str; - MD_u8 *opl = ptr + in.size; - MD_u64 size = 0; - MD_DecodedCodepoint consume; - for (;ptr < opl;) - { - consume = MD_DecodeCodepointFromUtf8(ptr, opl - ptr); - ptr += consume.advance; - size += MD_Utf16FromCodepoint(str + size, consume.codepoint); - } - str[size] = 0; - MD_ArenaPutBack(arena, 2*(cap - size)); // := 2*((cap + 1) - (size + 1)) - MD_String16 result = {0}; - result.str = str; - result.size = size; - return(result); -} - -MD_FUNCTION MD_String8 -MD_S8FromS32(MD_Arena *arena, MD_String32 in) -{ - MD_u64 cap = in.size*4; - MD_u8 *str = MD_PushArrayZero(arena, MD_u8, cap + 1); - MD_u32 *ptr = in.str; - MD_u32 *opl = ptr + in.size; - MD_u64 size = 0; - for (;ptr < opl; ptr += 1) - { - size += MD_Utf8FromCodepoint(str + size, *ptr); - } - str[size] = 0; - MD_ArenaPutBack(arena, cap - size); // := ((cap + 1) - (size + 1)) - return(MD_S8(str, size)); -} - -MD_FUNCTION MD_String32 -MD_S32FromS8(MD_Arena *arena, MD_String8 in) -{ - MD_u64 cap = in.size; - MD_u32 *str = MD_PushArrayZero(arena, MD_u32, cap + 1); - MD_u8 *ptr = in.str; - MD_u8 *opl = ptr + in.size; - MD_u64 size = 0; - MD_DecodedCodepoint consume; - for (;ptr < opl;) - { - consume = MD_DecodeCodepointFromUtf8(ptr, opl - ptr); - ptr += consume.advance; - str[size] = consume.codepoint; - size += 1; - } - str[size] = 0; - MD_ArenaPutBack(arena, 4*(cap - size)); // := 4*((cap + 1) - (size + 1)) - MD_String32 result = {0}; - result.str = str; - result.size = size; - return(result); -} - -//~ File Name Strings - -MD_FUNCTION MD_String8 -MD_PathChopLastPeriod(MD_String8 string) -{ - MD_u64 period_pos = MD_S8FindSubstring(string, MD_S8Lit("."), 0, MD_MatchFlag_FindLast); - if(period_pos < string.size) - { - string.size = period_pos; - } - return string; -} - -MD_FUNCTION MD_String8 -MD_PathSkipLastSlash(MD_String8 string) -{ - MD_u64 slash_pos = MD_S8FindSubstring(string, MD_S8Lit("/"), 0, - MD_StringMatchFlag_SlashInsensitive| - MD_MatchFlag_FindLast); - if(slash_pos < string.size) - { - string.str += slash_pos+1; - string.size -= slash_pos+1; - } - return string; -} - -MD_FUNCTION MD_String8 -MD_PathSkipLastPeriod(MD_String8 string) -{ - MD_u64 period_pos = MD_S8FindSubstring(string, MD_S8Lit("."), 0, MD_MatchFlag_FindLast); - if(period_pos < string.size) - { - string.str += period_pos+1; - string.size -= period_pos+1; - } - return string; -} - -MD_FUNCTION MD_String8 -MD_PathChopLastSlash(MD_String8 string) -{ - MD_u64 slash_pos = MD_S8FindSubstring(string, MD_S8Lit("/"), 0, - MD_StringMatchFlag_SlashInsensitive| - MD_MatchFlag_FindLast); - if(slash_pos < string.size) - { - string.size = slash_pos; - } - return string; -} - -MD_FUNCTION MD_String8 -MD_S8SkipWhitespace(MD_String8 string) -{ - for(MD_u64 i = 0; i < string.size; i += 1) - { - if(!MD_CharIsSpace(string.str[i])) - { - string = MD_S8Skip(string, i); - break; - } - } - return string; -} - -MD_FUNCTION MD_String8 -MD_S8ChopWhitespace(MD_String8 string) -{ - for(MD_u64 i = string.size-1; i < string.size; i -= 1) - { - if(!MD_CharIsSpace(string.str[i])) - { - string = MD_S8Prefix(string, i+1); - break; - } - } - return string; -} - -//~ Numeric Strings - -MD_GLOBAL MD_u8 md_char_to_value[] = { - 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07, - 0x08,0x09,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, - 0xFF,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0xFF, - 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, -}; - -MD_GLOBAL MD_u8 md_char_is_integer[] = { - 0,0,0,0,0,0,1,1, - 1,0,0,0,1,0,0,0, - 0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0, -}; - -MD_FUNCTION MD_b32 -MD_StringIsU64(MD_String8 string, MD_u32 radix) -{ - MD_b32 result = 0; - if (string.size > 0) - { - result = 1; - for (MD_u8 *ptr = string.str, *opl = string.str + string.size; - ptr < opl; - ptr += 1) - { - MD_u8 c = *ptr; - if (!md_char_is_integer[c >> 3]) - { - result = 0; - break; - } - if (md_char_to_value[(c - 0x30)&0x1F] >= radix) - { - result = 0; - break; - } - } - } - return(result); -} - -MD_FUNCTION MD_b32 -MD_StringIsCStyleInt(MD_String8 string) -{ - MD_u8 *ptr = string.str; - MD_u8 *opl = string.str + string.size; - - // consume sign - for (;ptr < opl && (*ptr == '+' || *ptr == '-'); ptr += 1); - - // radix from prefix - MD_u32 radix = 10; - if (ptr < opl) - { - MD_u8 c0 = *ptr; - if (c0 == '0') - { - ptr += 1; - radix = 8; - if (ptr < opl) - { - MD_u8 c1 = *ptr; - if (c1 == 'x') - { - ptr += 1; - radix = 0x10; - } - else if (c1 == 'b') - { - ptr += 1; - radix = 2; - } - } - } - } - - // check integer "digits" - MD_String8 digits_substr = MD_S8Range(ptr, opl); - MD_b32 result = MD_StringIsU64(digits_substr, radix); - - return(result); -} - -MD_FUNCTION MD_u64 -MD_U64FromString(MD_String8 string, MD_u32 radix) -{ - MD_Assert(2 <= radix && radix <= 16); - MD_u64 value = 0; - for (MD_u64 i = 0; i < string.size; i += 1) - { - value *= radix; - MD_u8 c = string.str[i]; - value += md_char_to_value[(c - 0x30)&0x1F]; - } - return(value); -} - -MD_FUNCTION MD_i64 -MD_CStyleIntFromString(MD_String8 string) -{ - MD_u64 p = 0; - - // consume sign - MD_i64 sign = +1; - if (p < string.size) - { - MD_u8 c = string.str[p]; - if (c == '-') - { - sign = -1; - p += 1; - } - else if (c == '+') - { - p += 1; - } - } - - // radix from prefix - MD_u32 radix = 10; - if (p < string.size) - { - MD_u8 c0 = string.str[p]; - if (c0 == '0') - { - p += 1; - radix = 8; - if (p < string.size) - { - MD_u8 c1 = string.str[p]; - if (c1 == 'x') - { - p += 1; - radix = 16; - } - else if (c1 == 'b') - { - p += 1; - radix = 2; - } - } - } - } - - // consume integer "digits" - MD_String8 digits_substr = MD_S8Skip(string, p); - MD_u64 n = MD_U64FromString(digits_substr, radix); - - // combine result - MD_i64 result = sign*n; - return(result); -} - -MD_FUNCTION MD_f64 -MD_F64FromString(MD_String8 string) -{ - char str[64]; - MD_u64 str_size = string.size; - if (str_size > sizeof(str) - 1) - { - str_size = sizeof(str) - 1; - } - MD_MemoryCopy(str, string.str, str_size); - str[str_size] = 0; - return(atof(str)); -} - - -MD_FUNCTION MD_String8 -MD_CStyleHexStringFromU64(MD_Arena *arena, MD_u64 x, MD_b32 caps) -{ - static char md_int_value_to_char[] = "0123456789abcdef"; - MD_u8 buffer[10]; - MD_u8 *opl = buffer + 10; - MD_u8 *ptr = opl; - if (x == 0) - { - ptr -= 1; - *ptr = '0'; - } - else - { - for (;;) - { - MD_u32 val = x%16; - x /= 16; - MD_u8 c = (MD_u8)md_int_value_to_char[val]; - if (caps) - { - c = MD_CharToUpper(c); - } - ptr -= 1; - *ptr = c; - if (x == 0) - { - break; - } - } - } - ptr -= 1; - *ptr = 'x'; - ptr -= 1; - *ptr = '0'; - - MD_String8 result = MD_ZERO_STRUCT; - result.size = (MD_u64)(ptr - buffer); - result.str = MD_PushArray(arena, MD_u8, result.size + 1); - MD_MemoryCopy(result.str, buffer, result.size); - result.str[result.size] =0; - return(result); -} - -//~ Enum/Flag Strings - -MD_FUNCTION MD_String8 -MD_StringFromNodeKind(MD_NodeKind kind) -{ - // NOTE(rjf): @maintenance Must be kept in sync with MD_NodeKind enum. - static char *cstrs[MD_NodeKind_COUNT] = - { - "Nil", - - "File", - "ErrorMarker", - - "Main", - "Tag", - - "List", - "Reference", - }; - return MD_S8CString(cstrs[kind]); -} - -MD_FUNCTION MD_String8List -MD_StringListFromNodeFlags(MD_Arena *arena, MD_NodeFlags flags) -{ - // NOTE(rjf): @maintenance Must be kept in sync with MD_NodeFlags enum. - static char *flag_cstrs[] = - { - "HasParenLeft", - "HasParenRight", - "HasBracketLeft", - "HasBracketRight", - "HasBraceLeft", - "HasBraceRight", - - "IsBeforeSemicolon", - "IsAfterSemicolon", - - "IsBeforeComma", - "IsAfterComma", - - "StringSingleQuote", - "StringDoubleQuote", - "StringTick", - "StringTriplet", - - "Numeric", - "Identifier", - "StringLiteral", - "Symbol", - }; - - MD_String8List list = MD_ZERO_STRUCT; - MD_u64 bits = sizeof(flags) * 8; - for(MD_u64 i = 0; i < bits && i < MD_ArrayCount(flag_cstrs); i += 1) - { - if(flags & (1ull << i)) - { - MD_S8ListPush(arena, &list, MD_S8CString(flag_cstrs[i])); - } - } - return list; -} - -//~ Map Table Data Structure - -MD_FUNCTION MD_u64 -MD_HashStr(MD_String8 string) -{ - MD_u64 result = 5381; - for(MD_u64 i = 0; i < string.size; i += 1) - { - result = ((result << 5) + result) + string.str[i]; - } - return result; -} - -// NOTE(mal): Generic 64-bit hash function (https://nullprogram.com/blog/2018/07/31/) -// Reversible, so no collisions. Assumes all bits of the pointer matter. -MD_FUNCTION MD_u64 -MD_HashPtr(void *p) -{ - MD_u64 h = (MD_u64)p; - h = (h ^ (h >> 30)) * 0xbf58476d1ce4e5b9; - h = (h ^ (h >> 27)) * 0x94d049bb133111eb; - h = h ^ (h >> 31); - return h; -} - -MD_FUNCTION MD_Map -MD_MapMakeBucketCount(MD_Arena *arena, MD_u64 bucket_count) -{ - MD_Map result = {0}; - result.bucket_count = bucket_count; - result.buckets = MD_PushArrayZero(arena, MD_MapBucket, bucket_count); - return(result); -} - -MD_FUNCTION MD_Map -MD_MapMake(MD_Arena *arena) -{ - MD_Map result = MD_MapMakeBucketCount(arena, 4093); - return(result); -} - -MD_FUNCTION MD_MapKey -MD_MapKeyStr(MD_String8 string) -{ - MD_MapKey result = {0}; - if (string.size != 0) - { - result.hash = MD_HashStr(string); - result.size = string.size; - if (string.size > 0) - { - result.ptr = string.str; - } - } - return(result); -} - -MD_FUNCTION MD_MapKey -MD_MapKeyPtr(void *ptr) -{ - MD_MapKey result = {0}; - if (ptr != 0) - { - result.hash = MD_HashPtr(ptr); - result.size = 0; - result.ptr = ptr; - } - return(result); -} - -MD_FUNCTION MD_MapSlot* -MD_MapLookup(MD_Map *map, MD_MapKey key) -{ - MD_MapSlot *result = 0; - if (map->bucket_count > 0) - { - MD_u64 index = key.hash%map->bucket_count; - result = MD_MapScan(map->buckets[index].first, key); - } - return(result); -} - -MD_FUNCTION MD_MapSlot* -MD_MapScan(MD_MapSlot *first_slot, MD_MapKey key) -{ - MD_MapSlot *result = 0; - if (first_slot != 0) - { - MD_b32 ptr_kind = (key.size == 0); - MD_String8 key_string = MD_S8((MD_u8*)key.ptr, key.size); - for (MD_MapSlot *slot = first_slot; - slot != 0; - slot = slot->next) - { - if (slot->key.hash == key.hash) - { - if (ptr_kind) - { - if (slot->key.size == 0 && slot->key.ptr == key.ptr) - { - result = slot; - break; - } - } - else - { - MD_String8 slot_string = MD_S8((MD_u8*)slot->key.ptr, slot->key.size); - if (MD_S8Match(slot_string, key_string, 0)) - { - result = slot; - break; - } - } - } - } - } - return(result); -} - -MD_FUNCTION MD_MapSlot* -MD_MapInsert(MD_Arena *arena, MD_Map *map, MD_MapKey key, void *val) -{ - MD_MapSlot *result = 0; - if (map->bucket_count > 0) - { - MD_u64 index = key.hash%map->bucket_count; - MD_MapSlot *slot = MD_PushArrayZero(arena, MD_MapSlot, 1); - MD_MapBucket *bucket = &map->buckets[index]; - MD_QueuePush(bucket->first, bucket->last, slot); - slot->key = key; - slot->val = val; - result = slot; - } - return(result); -} - -MD_FUNCTION MD_MapSlot* -MD_MapOverwrite(MD_Arena *arena, MD_Map *map, MD_MapKey key, void *val) -{ - MD_MapSlot *result = MD_MapLookup(map, key); - if (result != 0) - { - result->val = val; - } - else - { - result = MD_MapInsert(arena, map, key, val); - } - return(result); -} - -//~ Parsing - -MD_FUNCTION MD_Token -MD_TokenFromString(MD_String8 string) -{ - MD_Token token = MD_ZERO_STRUCT; - - MD_u8 *one_past_last = string.str + string.size; - MD_u8 *first = string.str; - - if(first < one_past_last) - { - MD_u8 *at = first; - MD_u32 skip_n = 0; - MD_u32 chop_n = 0; - -#define MD_TokenizerScan(cond) for (; at < one_past_last && (cond); at += 1) - - switch (*at) - { - // NOTE(allen): Whitespace parsing - case '\n': - { - token.kind = MD_TokenKind_Newline; - at += 1; - }break; - - case ' ': case '\r': case '\t': case '\f': case '\v': - { - token.kind = MD_TokenKind_Whitespace; - at += 1; - MD_TokenizerScan(*at == ' ' || *at == '\r' || *at == '\t' || *at == '\f' || *at == '\v'); - }break; - - // NOTE(allen): Comment parsing - case '/': - { - if (at + 1 < one_past_last) - { - if (at[1] == '/') - { - // trim off the first '//' - skip_n = 2; - at += 2; - token.kind = MD_TokenKind_Comment; - MD_TokenizerScan(*at != '\n' && *at != '\r'); - } - else if (at[1] == '*') - { - // trim off the first '/*' - skip_n = 2; - at += 2; - token.kind = MD_TokenKind_BrokenComment; - int counter = 1; - for (;at < one_past_last && counter > 0; at += 1) - { - if (at + 1 < one_past_last) - { - if (at[0] == '*' && at[1] == '/') - { - at += 1; - counter -= 1; - } - else if (at[0] == '/' && at[1] == '*') - { - at += 1; - counter += 1; - } - } - } - if(counter == 0) - { - token.kind = MD_TokenKind_Comment; - chop_n = 2; - } - } - } - if (token.kind == 0) goto symbol_lex; - }break; - - // NOTE(allen): Strings - case '"': - case '\'': - case '`': - { - token.kind = MD_TokenKind_BrokenStringLiteral; - - // determine delimiter setup - MD_u8 d = *at; - MD_b32 is_triplet = (at + 2 < one_past_last && at[1] == d && at[2] == d); - - // lex triple-delimiter string - if (is_triplet) - { - skip_n = 3; - at += 3; - MD_u32 consecutive_d = 0; - for (;;) - { - // fail condition - if (at >= one_past_last) - { - break; - } - - if(at[0] == d) - { - consecutive_d += 1; - at += 1; - // close condition - if (consecutive_d == 3) - { - chop_n = 3; - token.kind = MD_TokenKind_StringLiteral; - break; - } - } - else - { - consecutive_d = 0; - - // escaping rule - if(at[0] == '\\') - { - at += 1; - if(at < one_past_last && (at[0] == d || at[0] == '\\')) - { - at += 1; - } - } - else{ - at += 1; - } - } - } - } - - // lex single-delimiter string - if (!is_triplet) - { - skip_n = 1; - at += 1; - for (;at < one_past_last;) - { - // close condition - if (*at == d) - { - at += 1; - chop_n = 1; - token.kind = MD_TokenKind_StringLiteral; - break; - } - - // fail condition - if (*at == '\n') - { - break; - } - - // escaping rule - if (at[0] == '\\') - { - at += 1; - if (at < one_past_last && (at[0] == d || at[0] == '\\')) - { - at += 1; - } - } - else - { - at += 1; - } - } - } - - //- rjf: set relevant node flags on token - token.node_flags |= MD_NodeFlag_StringLiteral; - switch(d) - { - case '\'': token.node_flags |= MD_NodeFlag_StringSingleQuote; break; - case '"': token.node_flags |= MD_NodeFlag_StringDoubleQuote; break; - case '`': token.node_flags |= MD_NodeFlag_StringTick; break; - default: break; - } - if(is_triplet) - { - token.node_flags |= MD_NodeFlag_StringTriplet; - } - - }break; - - // NOTE(allen): Identifiers, Numbers, Symbols - default: - { - if (MD_CharIsAlpha(*at) || *at == '_') - { - token.node_flags |= MD_NodeFlag_Identifier; - token.kind = MD_TokenKind_Identifier; - at += 1; - MD_TokenizerScan(MD_CharIsAlpha(*at) || MD_CharIsDigit(*at) || *at == '_'); - } - - else if (MD_CharIsDigit(*at)) - { - token.node_flags |= MD_NodeFlag_Numeric; - token.kind = MD_TokenKind_Numeric; - at += 1; - - for (; at < one_past_last;) - { - MD_b32 good = 0; - if (*at == 'e' || *at == 'E') - { - good = 1; - at += 1; - if (at < one_past_last && (*at == '+' || *at == '-')) - { - at += 1; - } - } - else if (MD_CharIsAlpha(*at) || MD_CharIsDigit(*at) || *at == '.' || *at == '_') - { - good = 1; - at += 1; - } - if (!good) - { - break; - } - } - } - - else if (MD_CharIsUnreservedSymbol(*at)) - { - symbol_lex: - - token.node_flags |= MD_NodeFlag_Symbol; - token.kind = MD_TokenKind_Symbol; - at += 1; - MD_TokenizerScan(MD_CharIsUnreservedSymbol(*at)); - } - - else if (MD_CharIsReservedSymbol(*at)) - { - token.kind = MD_TokenKind_Reserved; - at += 1; - } - - else - { - token.kind = MD_TokenKind_BadCharacter; - at += 1; - } - }break; - } - - token.raw_string = MD_S8Range(first, at); - token.string = MD_S8Substring(token.raw_string, skip_n, token.raw_string.size - chop_n); - -#undef MD_TokenizerScan - - } - - return token; -} - -MD_FUNCTION MD_u64 -MD_LexAdvanceFromSkips(MD_String8 string, MD_TokenKind skip_kinds) -{ - MD_u64 result = string.size; - MD_u64 p = 0; - for (;;) - { - MD_Token token = MD_TokenFromString(MD_S8Skip(string, p)); - if ((skip_kinds & token.kind) == 0) - { - result = p; - break; - } - p += token.raw_string.size; - } - return(result); -} - -MD_FUNCTION MD_ParseResult -MD_ParseResultZero(void) -{ - MD_ParseResult result = MD_ZERO_STRUCT; - result.node = MD_NilNode(); - return result; -} - -MD_FUNCTION MD_ParseResult -MD_ParseNodeSet(MD_Arena *arena, MD_String8 string, MD_u64 offset, MD_Node *parent, - MD_ParseSetRule rule) -{ - MD_ParseResult result = MD_ParseResultZero(); - MD_u64 off = offset; - - //- rjf: fill data from set opener - MD_Token initial_token = MD_TokenFromString(MD_S8Skip(string, offset)); - MD_u8 set_opener = 0; - MD_NodeFlags set_opener_flags = 0; - MD_b32 close_with_brace = 0; - MD_b32 close_with_paren = 0; - MD_b32 close_with_separator = 0; - MD_b32 parse_all = 0; - switch(rule) - { - default: break; - - case MD_ParseSetRule_EndOnDelimiter: - { - MD_u64 opener_check_off = off; - opener_check_off += MD_LexAdvanceFromSkips(MD_S8Skip(string, opener_check_off), MD_TokenGroup_Irregular); - initial_token = MD_TokenFromString(MD_S8Skip(string, opener_check_off)); - if(initial_token.kind == MD_TokenKind_Reserved) - { - MD_u8 c = initial_token.raw_string.str[0]; - if(c == '{') - { - set_opener = '{'; - set_opener_flags |= MD_NodeFlag_HasBraceLeft; - opener_check_off += initial_token.raw_string.size; - off = opener_check_off; - close_with_brace = 1; - } - else if(c == '(') - { - set_opener = '('; - set_opener_flags |= MD_NodeFlag_HasParenLeft; - opener_check_off += initial_token.raw_string.size; - off = opener_check_off; - close_with_paren = 1; - } - else if(c == '[') - { - set_opener = '['; - set_opener_flags |= MD_NodeFlag_HasBracketLeft; - opener_check_off += initial_token.raw_string.size; - off = opener_check_off; - close_with_paren = 1; - } - else - { - close_with_separator = 1; - } - } - else - { - close_with_separator = 1; - } - }break; - - case MD_ParseSetRule_Global: - { - parse_all = 1; - }break; - } - - //- rjf: fill parent data from opener - parent->flags |= set_opener_flags; - - //- rjf: parse children - MD_b32 got_closer = 0; - MD_u64 parsed_child_count = 0; - if(set_opener != 0 || close_with_separator || parse_all) - { - MD_NodeFlags next_child_flags = 0; - for(;off < string.size;) - { - - //- rjf: check for separator closers - if(close_with_separator) - { - MD_u64 closer_check_off = off; - - //- rjf: check newlines - { - MD_Token potential_closer = MD_TokenFromString(MD_S8Skip(string, closer_check_off)); - if(potential_closer.kind == MD_TokenKind_Newline) - { - closer_check_off += potential_closer.raw_string.size; - // TODO(rjf): As far as I can tell, we can't actually do this, - // because higher-level unscoped sets may depend on this newline - // so they can be terminated. - // off = closer_check_off; - - // NOTE(rjf): always terminate with a newline if we have >0 children - if(parsed_child_count > 0) - { - // TODO(rjf): As far as I can tell, we can't actually do this, - // because higher-level unscoped sets may depend on this newline - // so they can be terminated. - // off = closer_check_off; - got_closer = 1; - break; - } - - // NOTE(rjf): terminate after double newline if we have 0 children - MD_Token next_closer = MD_TokenFromString(MD_S8Skip(string, closer_check_off)); - if(next_closer.kind == MD_TokenKind_Newline) - { - closer_check_off += next_closer.raw_string.size; - off = closer_check_off; - got_closer = 1; - break; - } - } - } - - //- rjf: check separators and possible braces from higher parents - { - closer_check_off += MD_LexAdvanceFromSkips(MD_S8Skip(string, off), MD_TokenGroup_Irregular); - MD_Token potential_closer = MD_TokenFromString(MD_S8Skip(string, closer_check_off)); - if(potential_closer.kind == MD_TokenKind_Reserved) - { - MD_u8 c = potential_closer.raw_string.str[0]; - if(c == ',' || c == ';') - { - off = closer_check_off; - closer_check_off += potential_closer.raw_string.size; - break; - } - else if(c == '}' || c == ']'|| c == ')') - { - goto end_parse; - } - } - } - - } - - //- rjf: check for non-separator closers - if(!close_with_separator && !parse_all) - { - MD_u64 closer_check_off = off; - closer_check_off += MD_LexAdvanceFromSkips(MD_S8Skip(string, off), MD_TokenGroup_Irregular); - MD_Token potential_closer = MD_TokenFromString(MD_S8Skip(string, closer_check_off)); - if(potential_closer.kind == MD_TokenKind_Reserved) - { - MD_u8 c = potential_closer.raw_string.str[0]; - if(close_with_brace && c == '}') - { - closer_check_off += potential_closer.raw_string.size; - off = closer_check_off; - parent->flags |= MD_NodeFlag_HasBraceRight; - got_closer = 1; - break; - } - else if(close_with_paren && c == ']') - { - closer_check_off += potential_closer.raw_string.size; - off = closer_check_off; - parent->flags |= MD_NodeFlag_HasBracketRight; - got_closer = 1; - break; - } - else if(close_with_paren && c == ')') - { - closer_check_off += potential_closer.raw_string.size; - off = closer_check_off; - parent->flags |= MD_NodeFlag_HasParenRight; - got_closer = 1; - break; - } - } - } - - //- rjf: parse next child - MD_ParseResult child_parse = MD_ParseOneNode(arena, string, off); - MD_MessageListConcat(&result.errors, &child_parse.errors); - off += child_parse.string_advance; - - //- rjf: hook child into parent - if(!MD_NodeIsNil(child_parse.node)) - { - // NOTE(rjf): @error No unnamed set children of implicitly-delimited sets - if(close_with_separator && - child_parse.node->string.size == 0 && - child_parse.node->flags & (MD_NodeFlag_HasParenLeft | - MD_NodeFlag_HasParenRight | - MD_NodeFlag_HasBracketLeft | - MD_NodeFlag_HasBracketRight | - MD_NodeFlag_HasBraceLeft | - MD_NodeFlag_HasBraceRight )) - { - MD_String8 error_str = MD_S8Lit("Unnamed set children of implicitly-delimited sets are not legal."); - MD_Message *error = MD_MakeNodeError(arena, child_parse.node, MD_MessageKind_Warning, - error_str); - MD_MessageListPush(&result.errors, error); - } - - MD_PushChild(parent, child_parse.node); - parsed_child_count += 1; - } - - //- rjf: check trailing separator - MD_NodeFlags trailing_separator_flags = 0; - if(!close_with_separator) - { - off += MD_LexAdvanceFromSkips(MD_S8Skip(string, off), MD_TokenGroup_Irregular); - MD_Token trailing_separator = MD_TokenFromString(MD_S8Skip(string, off)); - if (trailing_separator.kind == MD_TokenKind_Reserved) - { - MD_u8 c = trailing_separator.string.str[0]; - if(c == ',') - { - trailing_separator_flags |= MD_NodeFlag_IsBeforeComma; - off += trailing_separator.raw_string.size; - } - else if(c == ';') - { - trailing_separator_flags |= MD_NodeFlag_IsBeforeSemicolon; - off += trailing_separator.raw_string.size; - } - } - } - - //- rjf: fill child flags - child_parse.node->flags |= next_child_flags | trailing_separator_flags; - - //- rjf: setup next_child_flags - next_child_flags = MD_NodeFlag_AfterFromBefore(trailing_separator_flags); - } - } - end_parse:; - - //- rjf: push missing closer error, if we have one - if(set_opener != 0 && got_closer == 0) - { - // NOTE(rjf): @error We didn't get a closer for the set - MD_String8 error_str = MD_S8Fmt(arena, "Unbalanced \"%c\"", set_opener); - MD_Message *error = MD_MakeTokenError(arena, string, initial_token, - MD_MessageKind_FatalError, error_str); - MD_MessageListPush(&result.errors, error); - } - - //- rjf: push empty implicit set error, - if(close_with_separator && parsed_child_count == 0) - { - // NOTE(rjf): @error No empty implicitly-delimited sets - MD_Message *error = MD_MakeTokenError(arena, string, initial_token, MD_MessageKind_Error, - MD_S8Lit("Empty implicitly-delimited node list")); - MD_MessageListPush(&result.errors, error); - } - - //- rjf: fill result info - result.node = parent; - result.string_advance = off - offset; - - return result; -} - -MD_FUNCTION MD_ParseResult -MD_ParseOneNode(MD_Arena *arena, MD_String8 string, MD_u64 offset) -{ - MD_ParseResult result = MD_ParseResultZero(); - MD_u64 off = offset; - - //- rjf: parse pre-comment - MD_String8 prev_comment = MD_ZERO_STRUCT; - { - MD_Token comment_token = MD_ZERO_STRUCT; - for(;off < string.size;) - { - MD_Token token = MD_TokenFromString(MD_S8Skip(string, off)); - if(token.kind == MD_TokenKind_Comment) - { - off += token.raw_string.size; - comment_token = token; - } - else if(token.kind == MD_TokenKind_Newline) - { - off += token.raw_string.size; - MD_Token next_token = MD_TokenFromString(MD_S8Skip(string, off)); - if(next_token.kind == MD_TokenKind_Comment) - { - // NOTE(mal): If more than one comment, use the last comment - comment_token = next_token; - } - else if(next_token.kind == MD_TokenKind_Newline) - { - MD_MemoryZeroStruct(&comment_token); - } - } - else if((token.kind & MD_TokenGroup_Whitespace) != 0) - { - off += token.raw_string.size; - } - else - { - break; - } - prev_comment = comment_token.string; - } - } - - //- rjf: parse tag list - MD_Node *first_tag = MD_NilNode(); - MD_Node *last_tag = MD_NilNode(); - { - for(;off < string.size;) - { - //- rjf: parse @ symbol, signifying start of tag - off += MD_LexAdvanceFromSkips(MD_S8Skip(string, off), MD_TokenGroup_Irregular); - MD_Token next_token = MD_TokenFromString(MD_S8Skip(string, off)); - if(next_token.kind != MD_TokenKind_Reserved || - next_token.string.str[0] != '@') - { - break; - } - off += next_token.raw_string.size; - - //- rjf: parse string of tag node - MD_Token name = MD_TokenFromString(MD_S8Skip(string, off)); - MD_u64 name_off = off; - if((name.kind & MD_TokenGroup_Label) == 0) - { - // NOTE(rjf): @error Improper token for tag string - MD_String8 error_str = MD_S8Fmt(arena, "\"%.*s\" is not a proper tag label", - MD_S8VArg(name.raw_string)); - MD_Message *error = MD_MakeTokenError(arena, string, name, MD_MessageKind_Error, error_str); - MD_MessageListPush(&result.errors, error); - break; - } - off += name.raw_string.size; - - //- rjf: build tag - MD_Node *tag = MD_MakeNode(arena, MD_NodeKind_Tag, name.string, name.raw_string, name_off); - - //- rjf: parse tag arguments - MD_Token open_paren = MD_TokenFromString(MD_S8Skip(string, off)); - MD_ParseResult args_parse = MD_ParseResultZero(); - if(open_paren.kind == MD_TokenKind_Reserved && - open_paren.string.str[0] == '(') - { - args_parse = MD_ParseNodeSet(arena, string, off, tag, MD_ParseSetRule_EndOnDelimiter); - MD_MessageListConcat(&result.errors, &args_parse.errors); - } - off += args_parse.string_advance; - - //- rjf: push tag to result - MD_NodeDblPushBack(first_tag, last_tag, tag); - } - } - - //- rjf: parse node - MD_Node *parsed_node = MD_NilNode(); - MD_ParseResult children_parse = MD_ParseResultZero(); - retry:; - { - //- rjf: try to parse an unnamed set - off += MD_LexAdvanceFromSkips(MD_S8Skip(string, off), MD_TokenGroup_Irregular); - MD_Token unnamed_set_opener = MD_TokenFromString(MD_S8Skip(string, off)); - if(unnamed_set_opener.kind == MD_TokenKind_Reserved) - { - MD_u8 c = unnamed_set_opener.string.str[0]; - if (c == '(' || c == '{' || c == '[') - { - parsed_node = MD_MakeNode(arena, MD_NodeKind_Main, MD_S8Lit(""), MD_S8Lit(""), - unnamed_set_opener.raw_string.str - string.str); - children_parse = MD_ParseNodeSet(arena, string, off, parsed_node, - MD_ParseSetRule_EndOnDelimiter); - off += children_parse.string_advance; - MD_MessageListConcat(&result.errors, &children_parse.errors); - } - else if (c == ')' || c == '}' || c == ']') - { - // NOTE(rjf): @error Unexpected set closing symbol - MD_String8 error_str = MD_S8Fmt(arena, "Unbalanced \"%c\"", c); - MD_Message *error = MD_MakeTokenError(arena, string, unnamed_set_opener, - MD_MessageKind_FatalError, error_str); - MD_MessageListPush(&result.errors, error); - off += unnamed_set_opener.raw_string.size; - } - else - { - // NOTE(rjf): @error Unexpected reserved symbol - MD_String8 error_str = MD_S8Fmt(arena, "Unexpected reserved symbol \"%c\"", c); - MD_Message *error = MD_MakeTokenError(arena, string, unnamed_set_opener, - MD_MessageKind_Error, error_str); - MD_MessageListPush(&result.errors, error); - off += unnamed_set_opener.raw_string.size; - } - goto end_parse; - - } - - //- rjf: try to parse regular node, with/without children - off += MD_LexAdvanceFromSkips(MD_S8Skip(string, off), MD_TokenGroup_Irregular); - MD_Token label_name = MD_TokenFromString(MD_S8Skip(string, off)); - if((label_name.kind & MD_TokenGroup_Label) != 0) - { - off += label_name.raw_string.size; - parsed_node = MD_MakeNode(arena, MD_NodeKind_Main, label_name.string, label_name.raw_string, - label_name.raw_string.str - string.str); - parsed_node->flags |= label_name.node_flags; - - //- rjf: try to parse children for this node - MD_u64 colon_check_off = off; - colon_check_off += MD_LexAdvanceFromSkips(MD_S8Skip(string, colon_check_off), MD_TokenGroup_Irregular); - MD_Token colon = MD_TokenFromString(MD_S8Skip(string, colon_check_off)); - if(colon.kind == MD_TokenKind_Reserved && - colon.string.str[0] == ':') - { - colon_check_off += colon.raw_string.size; - off = colon_check_off; - - children_parse = MD_ParseNodeSet(arena, string, off, parsed_node, - MD_ParseSetRule_EndOnDelimiter); - off += children_parse.string_advance; - MD_MessageListConcat(&result.errors, &children_parse.errors); - } - goto end_parse; - } - - //- rjf: collect bad token - MD_Token bad_token = MD_TokenFromString(MD_S8Skip(string, off)); - if(bad_token.kind & MD_TokenGroup_Error) - { - off += bad_token.raw_string.size; - - switch (bad_token.kind) - { - case MD_TokenKind_BadCharacter: - { - MD_String8List bytes = {0}; - for(int i_byte = 0; i_byte < bad_token.raw_string.size; ++i_byte) - { - MD_u8 b = bad_token.raw_string.str[i_byte]; - MD_S8ListPush(arena, &bytes, MD_CStyleHexStringFromU64(arena, b, 1)); - } - - MD_StringJoin join = MD_ZERO_STRUCT; - join.mid = MD_S8Lit(" "); - MD_String8 byte_string = MD_S8ListJoin(arena, bytes, &join); - - // NOTE(rjf): @error Bad character - MD_String8 error_str = MD_S8Fmt(arena, "Non-ASCII character \"%.*s\"", - MD_S8VArg(byte_string)); - MD_Message *error = MD_MakeTokenError(arena, string, bad_token, MD_MessageKind_Error, - error_str); - MD_MessageListPush(&result.errors, error); - }break; - - case MD_TokenKind_BrokenComment: - { - // NOTE(rjf): @error Broken Comments - MD_Message *error = MD_MakeTokenError(arena, string, bad_token, MD_MessageKind_Error, - MD_S8Lit("Unterminated comment")); - MD_MessageListPush(&result.errors, error); - }break; - - case MD_TokenKind_BrokenStringLiteral: - { - // NOTE(rjf): @error Broken String Literals - MD_Message *error = MD_MakeTokenError(arena, string, bad_token, MD_MessageKind_Error, - MD_S8Lit("Unterminated string literal")); - MD_MessageListPush(&result.errors, error); - }break; - } - goto retry; - } - } - - end_parse:; - - //- rjf: parse comments after nodes. - MD_String8 next_comment = MD_ZERO_STRUCT; - { - MD_Token comment_token = MD_ZERO_STRUCT; - for(;;) - { - MD_Token token = MD_TokenFromString(MD_S8Skip(string, off)); - if(token.kind == MD_TokenKind_Comment) - { - comment_token = token; - off += token.raw_string.size; - break; - } - - else if(token.kind == MD_TokenKind_Newline) - { - break; - } - else if((token.kind & MD_TokenGroup_Whitespace) != 0) - { - off += token.raw_string.size; - } - else - { - break; - } - } - next_comment = comment_token.string; - } - - //- rjf: fill result - parsed_node->prev_comment = prev_comment; - parsed_node->next_comment = next_comment; - result.node = parsed_node; - if(!MD_NodeIsNil(result.node)) - { - result.node->first_tag = first_tag; - result.node->last_tag = last_tag; - for(MD_Node *tag = first_tag; !MD_NodeIsNil(tag); tag = tag->next) - { - tag->parent = result.node; - } - } - result.string_advance = off - offset; - - return result; -} - -MD_FUNCTION MD_ParseResult -MD_ParseWholeString(MD_Arena *arena, MD_String8 filename, MD_String8 contents) -{ - MD_Node *root = MD_MakeNode(arena, MD_NodeKind_File, filename, contents, 0); - MD_ParseResult result = MD_ParseNodeSet(arena, contents, 0, root, MD_ParseSetRule_Global); - result.node = root; - for(MD_Message *error = result.errors.first; error != 0; error = error->next) - { - if(MD_NodeIsNil(error->node->parent)) - { - error->node->parent = root; - } - } - return result; -} - -MD_FUNCTION MD_ParseResult -MD_ParseWholeFile(MD_Arena *arena, MD_String8 filename) -{ - MD_String8 file_contents = MD_LoadEntireFile(arena, filename); - MD_ParseResult parse = MD_ParseWholeString(arena, filename, file_contents); - if(file_contents.str == 0) - { - // NOTE(rjf): @error File failing to load - MD_String8 error_str = MD_S8Fmt(arena, "Could not read file \"%.*s\"", MD_S8VArg(filename)); - MD_Message *error = MD_MakeNodeError(arena, parse.node, MD_MessageKind_FatalError, - error_str); - MD_MessageListPush(&parse.errors, error); - } - return parse; -} - -//~ Messages (Errors/Warnings) - -MD_FUNCTION MD_Node* -MD_MakeErrorMarkerNode(MD_Arena *arena, MD_String8 parse_contents, MD_u64 offset) -{ - MD_Node *result = MD_MakeNode(arena, MD_NodeKind_ErrorMarker, MD_S8Lit(""), parse_contents, - offset); - return(result); -} - -MD_FUNCTION MD_Message* -MD_MakeNodeError(MD_Arena *arena, MD_Node *node, MD_MessageKind kind, MD_String8 str) -{ - MD_Message *error = MD_PushArrayZero(arena, MD_Message, 1); - error->node = node; - error->kind = kind; - error->string = str; - return error; -} - -MD_FUNCTION MD_Message * -MD_MakeTokenError(MD_Arena *arena, MD_String8 parse_contents, MD_Token token, - MD_MessageKind kind, MD_String8 str) -{ - MD_u64 offset = token.raw_string.str - parse_contents.str; - MD_Node *err_node = MD_MakeErrorMarkerNode(arena, parse_contents, offset); - return MD_MakeNodeError(arena, err_node, kind, str); -} - -MD_FUNCTION void -MD_MessageListPush(MD_MessageList *list, MD_Message *message) -{ - MD_QueuePush(list->first, list->last, message); - if(message->kind > list->max_message_kind) - { - list->max_message_kind = message->kind; - } - list->node_count += 1; -} - -MD_FUNCTION void -MD_MessageListConcat(MD_MessageList *list, MD_MessageList *to_push) -{ - if(to_push->node_count != 0) - { - if(list->last != 0) - { - list->last->next = to_push->first; - list->last = to_push->last; - list->node_count += to_push->node_count; - if(to_push->max_message_kind > list->max_message_kind) - { - list->max_message_kind = to_push->max_message_kind; - } - } - else - { - *list = *to_push; - } - MD_MemoryZeroStruct(to_push); - } -} - -//~ Location Conversions - -MD_FUNCTION MD_CodeLoc -MD_CodeLocFromFileOffset(MD_String8 filename, MD_u8 *base, MD_u64 offset) -{ - MD_CodeLoc loc; - loc.filename = filename; - loc.line = 1; - loc.column = 1; - if(base != 0) - { - MD_u8 *at = base + offset; - for(MD_u64 i = 0; base+i < at && base[i]; i += 1) - { - if(base[i] == '\n') - { - loc.line += 1; - loc.column = 1; - } - else - { - loc.column += 1; - } - } - } - return loc; -} - -MD_FUNCTION MD_CodeLoc -MD_CodeLocFromNode(MD_Node *node) -{ - MD_Node *file_root = MD_NilNode(); - for(MD_Node *parent = node->parent; !MD_NodeIsNil(parent); parent = parent->parent) - { - if(parent->kind == MD_NodeKind_File) - { - file_root = parent; - break; - } - } - MD_Node *first_tag = file_root->first_tag; - MD_CodeLoc loc = {0}; - if(MD_NodeIsNil(first_tag)) - { - loc = MD_CodeLocFromFileOffset(file_root->string, file_root->raw_string.str, node->offset); - } - else - { - loc = MD_CodeLocFromFileOffset(file_root->string, first_tag->raw_string.str, node->offset); - } - return loc; -} - -//~ Tree/List Building - -MD_FUNCTION MD_b32 -MD_NodeIsNil(MD_Node *node) -{ - return(node == 0 || node == &_md_nil_node || node->kind == MD_NodeKind_Nil); -} - -MD_FUNCTION MD_Node * -MD_NilNode(void) { return &_md_nil_node; } - -MD_FUNCTION MD_Node * -MD_MakeNode(MD_Arena *arena, MD_NodeKind kind, MD_String8 string, MD_String8 raw_string, - MD_u64 offset) -{ - MD_Node *node = MD_PushArrayZero(arena, MD_Node, 1); - node->kind = kind; - node->string = string; - node->raw_string = raw_string; - node->next = node->prev = node->parent = - node->first_child = node->last_child = - node->first_tag = node->last_tag = node->ref_target = MD_NilNode(); - node->offset = offset; - return node; -} - -MD_FUNCTION void -MD_PushChild(MD_Node *parent, MD_Node *new_child) -{ - if (!MD_NodeIsNil(new_child)) - { - MD_NodeDblPushBack(parent->first_child, parent->last_child, new_child); - new_child->parent = parent; - } -} - -MD_FUNCTION void -MD_PushTag(MD_Node *node, MD_Node *tag) -{ - if (!MD_NodeIsNil(tag)) - { - MD_NodeDblPushBack(node->first_tag, node->last_tag, tag); - tag->parent = node; - } -} - -MD_FUNCTION MD_Node* -MD_MakeList(MD_Arena *arena) -{ - MD_String8 empty = {0}; - MD_Node *result = MD_MakeNode(arena, MD_NodeKind_List, empty, empty, 0); - return(result); -} - -MD_FUNCTION void -MD_ListConcatInPlace(MD_Node *list, MD_Node *to_push) -{ - if (!MD_NodeIsNil(to_push->first_child)) - { - if (!MD_NodeIsNil(list->first_child)) - { - list->last_child->next = to_push->first_child; - list->last_child = to_push->last_child; - } - else - { - list->first_child = to_push->first_child; - list->last_child = to_push->last_child; - } - to_push->first_child = to_push->last_child = MD_NilNode(); - } -} - -MD_FUNCTION MD_Node* -MD_PushNewReference(MD_Arena *arena, MD_Node *list, MD_Node *target) -{ - MD_Node *n = MD_MakeNode(arena, MD_NodeKind_Reference, target->string, target->raw_string, - target->offset); - n->ref_target = target; - MD_PushChild(list, n); - return(n); -} - -//~ Introspection Helpers - -MD_FUNCTION MD_Node * -MD_FirstNodeWithString(MD_Node *first, MD_String8 string, MD_MatchFlags flags) -{ - MD_Node *result = MD_NilNode(); - for(MD_Node *node = first; !MD_NodeIsNil(node); node = node->next) - { - if(MD_S8Match(string, node->string, flags)) - { - result = node; - break; - } - } - return result; -} - -MD_FUNCTION MD_Node * -MD_NodeAtIndex(MD_Node *first, int n) -{ - MD_Node *result = MD_NilNode(); - if(n >= 0) - { - int idx = 0; - for(MD_Node *node = first; !MD_NodeIsNil(node); node = node->next, idx += 1) - { - if(idx == n) - { - result = node; - break; - } - } - } - return result; -} - -MD_FUNCTION MD_Node * -MD_FirstNodeWithFlags(MD_Node *first, MD_NodeFlags flags) -{ - MD_Node *result = MD_NilNode(); - for(MD_Node *n = first; !MD_NodeIsNil(n); n = n->next) - { - if(n->flags & flags) - { - result = n; - break; - } - } - return result; -} - -MD_FUNCTION int -MD_IndexFromNode(MD_Node *node) -{ - int idx = 0; - for(MD_Node *last = node->prev; !MD_NodeIsNil(last); last = last->prev, idx += 1); - return idx; -} - -MD_FUNCTION MD_Node * -MD_RootFromNode(MD_Node *node) -{ - MD_Node *parent = node; - for(MD_Node *p = parent; !MD_NodeIsNil(p); p = p->parent) - { - parent = p; - } - return parent; -} - -MD_FUNCTION MD_Node * -MD_ChildFromString(MD_Node *node, MD_String8 child_string, MD_MatchFlags flags) -{ - return MD_FirstNodeWithString(node->first_child, child_string, flags); -} - -MD_FUNCTION MD_Node * -MD_TagFromString(MD_Node *node, MD_String8 tag_string, MD_MatchFlags flags) -{ - return MD_FirstNodeWithString(node->first_tag, tag_string, flags); -} - -MD_FUNCTION MD_Node * -MD_ChildFromIndex(MD_Node *node, int n) -{ - return MD_NodeAtIndex(node->first_child, n); -} - -MD_FUNCTION MD_Node * -MD_TagFromIndex(MD_Node *node, int n) -{ - return MD_NodeAtIndex(node->first_tag, n); -} - -MD_FUNCTION MD_Node * -MD_TagArgFromIndex(MD_Node *node, MD_String8 tag_string, MD_MatchFlags flags, int n) -{ - MD_Node *tag = MD_TagFromString(node, tag_string, flags); - return MD_ChildFromIndex(tag, n); -} - -MD_FUNCTION MD_Node * -MD_TagArgFromString(MD_Node *node, MD_String8 tag_string, MD_MatchFlags tag_str_flags, MD_String8 arg_string, MD_MatchFlags arg_str_flags) -{ - MD_Node *tag = MD_TagFromString(node, tag_string, tag_str_flags); - MD_Node *arg = MD_ChildFromString(tag, arg_string, arg_str_flags); - return arg; -} - -MD_FUNCTION MD_b32 -MD_NodeHasChild(MD_Node *node, MD_String8 string, MD_MatchFlags flags) -{ - return !MD_NodeIsNil(MD_ChildFromString(node, string, flags)); -} - -MD_FUNCTION MD_b32 -MD_NodeHasTag(MD_Node *node, MD_String8 string, MD_MatchFlags flags) -{ - return !MD_NodeIsNil(MD_TagFromString(node, string, flags)); -} - -MD_FUNCTION MD_i64 -MD_ChildCountFromNode(MD_Node *node) -{ - MD_i64 result = 0; - for(MD_EachNode(child, node->first_child)) - { - result += 1; - } - return result; -} - -MD_FUNCTION MD_i64 -MD_TagCountFromNode(MD_Node *node) -{ - MD_i64 result = 0; - for(MD_EachNode(tag, node->first_tag)) - { - result += 1; - } - return result; -} - -MD_FUNCTION MD_Node * -MD_ResolveNodeFromReference(MD_Node *node) -{ - MD_u64 safety = 100; - for(; safety > 0 && node->kind == MD_NodeKind_Reference; - safety -= 1, node = node->ref_target); - MD_Node *result = node; - return(result); -} - -MD_FUNCTION MD_Node* -MD_NodeNextWithLimit(MD_Node *node, MD_Node *opl) -{ - node = node->next; - if (node == opl) - { - node = MD_NilNode(); - } - return(node); -} - -MD_FUNCTION MD_String8 -MD_PrevCommentFromNode(MD_Node *node) -{ - return(node->prev_comment); -} - -MD_FUNCTION MD_String8 -MD_NextCommentFromNode(MD_Node *node) -{ - return(node->next_comment); -} - -//~ Error/Warning Helpers - -MD_FUNCTION MD_String8 -MD_StringFromMessageKind(MD_MessageKind kind) -{ - MD_String8 result = MD_ZERO_STRUCT; - switch (kind) - { - default: break; - case MD_MessageKind_Note: result = MD_S8Lit("note"); break; - case MD_MessageKind_Warning: result = MD_S8Lit("warning"); break; - case MD_MessageKind_Error: result = MD_S8Lit("error"); break; - case MD_MessageKind_FatalError: result = MD_S8Lit("fatal error"); break; - } - return(result); -} - -MD_FUNCTION MD_String8 -MD_FormatMessage(MD_Arena *arena, MD_CodeLoc loc, MD_MessageKind kind, MD_String8 string) -{ - MD_String8 kind_string = MD_StringFromMessageKind(kind); - MD_String8 result = MD_S8Fmt(arena, "" MD_FmtCodeLoc " %.*s: %.*s\n", - MD_CodeLocVArg(loc), MD_S8VArg(kind_string), MD_S8VArg(string)); - return(result); -} - -#if !MD_DISABLE_PRINT_HELPERS - -MD_FUNCTION void -MD_PrintMessage(FILE *file, MD_CodeLoc code_loc, MD_MessageKind kind, MD_String8 string) -{ - MD_ArenaTemp scratch = MD_GetScratch(0, 0); - MD_String8 message = MD_FormatMessage(scratch.arena, code_loc, kind, string); - fwrite(message.str, message.size, 1, file); - MD_ReleaseScratch(scratch); -} - -MD_FUNCTION void -MD_PrintMessageFmt(FILE *file, MD_CodeLoc code_loc, MD_MessageKind kind, char *fmt, ...) -{ - MD_ArenaTemp scratch = MD_GetScratch(0, 0); - va_list args; - va_start(args, fmt); - MD_String8 string = MD_S8FmtV(scratch.arena, fmt, args); - va_end(args); - MD_String8 message = MD_FormatMessage(scratch.arena, code_loc, kind, string); - fwrite(message.str, message.size, 1, file); - MD_ReleaseScratch(scratch); -} - -#endif - -//~ Tree Comparison/Verification - -MD_FUNCTION MD_b32 -MD_NodeMatch(MD_Node *a, MD_Node *b, MD_MatchFlags flags) -{ - MD_b32 result = 0; - if(a->kind == b->kind && MD_S8Match(a->string, b->string, flags)) - { - result = 1; - if(result && flags & MD_NodeMatchFlag_NodeFlags) - { - result = result && a->flags == b->flags; - } - if(result && a->kind != MD_NodeKind_Tag && (flags & MD_NodeMatchFlag_Tags)) - { - for(MD_Node *a_tag = a->first_tag, *b_tag = b->first_tag; - !MD_NodeIsNil(a_tag) || !MD_NodeIsNil(b_tag); - a_tag = a_tag->next, b_tag = b_tag->next) - { - if(MD_NodeMatch(a_tag, b_tag, flags)) - { - if(flags & MD_NodeMatchFlag_TagArguments) - { - for(MD_Node *a_tag_arg = a_tag->first_child, *b_tag_arg = b_tag->first_child; - !MD_NodeIsNil(a_tag_arg) || !MD_NodeIsNil(b_tag_arg); - a_tag_arg = a_tag_arg->next, b_tag_arg = b_tag_arg->next) - { - if(!MD_NodeDeepMatch(a_tag_arg, b_tag_arg, flags)) - { - result = 0; - goto end; - } - } - } - } - else - { - result = 0; - goto end; - } - } - } - } - end:; - return result; -} - -MD_FUNCTION MD_b32 -MD_NodeDeepMatch(MD_Node *a, MD_Node *b, MD_MatchFlags flags) -{ - MD_b32 result = MD_NodeMatch(a, b, flags); - if(result) - { - for(MD_Node *a_child = a->first_child, *b_child = b->first_child; - !MD_NodeIsNil(a_child) || !MD_NodeIsNil(b_child); - a_child = a_child->next, b_child = b_child->next) - { - if(!MD_NodeDeepMatch(a_child, b_child, flags)) - { - result = 0; - goto end; - } - } - } - end:; - return result; -} - -//~ Expression Parsing - -MD_FUNCTION void -MD_ExprOprPush(MD_Arena *arena, MD_ExprOprList *list, - MD_ExprOprKind kind, MD_u32 precedence, MD_String8 string, - MD_u32 op_id, void *op_ptr) -{ - MD_ExprOpr *op = MD_PushArrayZero(arena, MD_ExprOpr, 1); - MD_QueuePush(list->first, list->last, op); - list->count += 1; - op->op_id = op_id; - op->kind = kind; - op->precedence = precedence; - op->string = string; - op->op_ptr = op_ptr; -} - -MD_GLOBAL MD_BakeOperatorErrorHandler md_bake_operator_error_handler = 0; - -MD_FUNCTION MD_BakeOperatorErrorHandler -MD_ExprSetBakeOperatorErrorHandler(MD_BakeOperatorErrorHandler handler){ - MD_BakeOperatorErrorHandler old_handler = md_bake_operator_error_handler; - md_bake_operator_error_handler = handler; - return old_handler; -} - -MD_FUNCTION MD_ExprOprTable -MD_ExprBakeOprTableFromList(MD_Arena *arena, MD_ExprOprList *list) -{ - MD_ExprOprTable result = MD_ZERO_STRUCT; - - // TODO(allen): @upgrade_potential(minor) - - for(MD_ExprOpr *op = list->first; - op != 0; - op = op->next) - { - MD_ExprOprKind op_kind = op->kind; - MD_String8 op_s = op->string; - - // error checking - MD_String8 error_str = MD_ZERO_STRUCT; - - MD_Token op_token = MD_TokenFromString(op_s); - MD_b32 is_setlike_op = - (op_s.size == 2 && - (MD_S8Match(op_s, MD_S8Lit("[]"), 0) || MD_S8Match(op_s, MD_S8Lit("()"), 0) || - MD_S8Match(op_s, MD_S8Lit("[)"), 0) || MD_S8Match(op_s, MD_S8Lit("(]"), 0) || - MD_S8Match(op_s, MD_S8Lit("{}"), 0))); - - if(op_kind != MD_ExprOprKind_Prefix && op_kind != MD_ExprOprKind_Postfix && - op_kind != MD_ExprOprKind_Binary && op_kind != MD_ExprOprKind_BinaryRightAssociative) - { - error_str = MD_S8Fmt(arena, "Ignored operator \"%.*s\" because its kind value (%d) does not match " - "any valid operator kind", MD_S8VArg(op_s), op_kind); - } - else if(is_setlike_op && op_kind != MD_ExprOprKind_Postfix) - { - error_str = - MD_S8Fmt(arena, "Ignored operator \"%.*s\". \"%.*s\" is only allowed as unary postfix", - MD_S8VArg(op_s), MD_S8VArg(op_s)); - } - else if(!is_setlike_op && - (op_token.kind != MD_TokenKind_Identifier && op_token.kind != MD_TokenKind_Symbol)) - { - error_str = MD_S8Fmt(arena, "Ignored operator \"%.*s\" because it is neither a symbol " - "nor an identifier token", MD_S8VArg(op_s)); - } - else if(!is_setlike_op && op_token.string.size < op_s.size) - { - error_str = MD_S8Fmt(arena, "Ignored operator \"%.*s\" because its prefix \"%.*s\" " - "constitutes a standalone operator", - MD_S8VArg(op_s), MD_S8VArg(op_token.string)); - } - else - { - for(MD_ExprOpr *op2 = list->first; - op2 != op; - op2 = op2->next) - { // NOTE(mal): O(n^2) - MD_ExprOprKind op2_kind = op2->kind; - MD_String8 op2_s = op2->string; - if(op->precedence == op2->precedence && - ((op_kind == MD_ExprOprKind_Binary && - op2_kind == MD_ExprOprKind_BinaryRightAssociative) || - (op_kind == MD_ExprOprKind_BinaryRightAssociative && - op2_kind == MD_ExprOprKind_Binary))) - { - error_str = - MD_S8Fmt(arena, "Ignored binary operator \"%.*s\" because another binary operator" - "has the same precedence and different associativity", MD_S8VArg(op_s)); - } - else if(MD_S8Match(op_s, op2_s, 0)) - { - if(op_kind == op2_kind) - { - error_str = MD_S8Fmt(arena, "Ignored repeat operator \"%.*s\"", MD_S8VArg(op_s)); - } - else if(op_kind != MD_ExprOprKind_Prefix && op2_kind != MD_ExprOprKind_Prefix) - { - error_str = - MD_S8Fmt(arena, "Ignored conflicting repeat operator \"%.*s\". There can't be" - "more than one posfix/binary operator associated to the same token", - MD_S8VArg(op_s)); - } - } - } - } - - // save error - if(error_str.size != 0 && md_bake_operator_error_handler) - { - md_bake_operator_error_handler(MD_MessageKind_Warning, error_str); - } - - // save list - else - { - MD_ExprOprList *saved_list = result.table + op_kind; - MD_ExprOpr *op_node_copy = MD_PushArray(arena, MD_ExprOpr, 1); - *op_node_copy = *op; - MD_QueuePush(saved_list->first, saved_list->last, op_node_copy); - saved_list->count += 1; - } - } - - return(result); -} - -MD_FUNCTION MD_ExprOpr* -MD_ExprOprFromKindString(MD_ExprOprTable *table, MD_ExprOprKind kind, MD_String8 s) -{ - // TODO(allen): @upgrade_potential - - // NOTE(mal): Look for operator on one or all (kind == MD_ExprOprKind_Null) tables - MD_ExprOpr *result = 0; - for(MD_ExprOprKind cur_kind = (MD_ExprOprKind)(MD_ExprOprKind_Null + 1); - cur_kind < MD_ExprOprKind_COUNT; - cur_kind = (MD_ExprOprKind)(cur_kind + 1)) - { - if(kind == MD_ExprOprKind_Null || kind == cur_kind) - { - MD_ExprOprList *op_list = table->table+cur_kind; - for(MD_ExprOpr *op = op_list->first; - op != 0; - op = op->next) - { - if(MD_S8Match(op->string, s, 0)) - { - result = op; - goto dbl_break; - } - } - } - } - dbl_break:; - return result; -} - -MD_FUNCTION MD_ExprParseResult -MD_ExprParse(MD_Arena *arena, MD_ExprOprTable *op_table, MD_Node *first, MD_Node *opl) -{ - // setup a context - MD_ExprParseCtx ctx = MD_ExprParse_MakeContext(op_table); - - // parse the top level - MD_Expr *expr = MD_ExprParse_TopLevel(arena, &ctx, first, opl); - - // fill result - MD_ExprParseResult result = {0}; - result.expr = expr; - result.errors = ctx.errors; - return(result); -} - -MD_FUNCTION MD_Expr* -MD_Expr_NewLeaf(MD_Arena *arena, MD_Node *node) -{ - MD_Expr *result = MD_PushArrayZero(arena, MD_Expr, 1); - result->md_node = node; - return(result); -} - -MD_FUNCTION MD_Expr* -MD_Expr_NewOpr(MD_Arena *arena, MD_ExprOpr *op, MD_Node *op_node, MD_Expr *l, MD_Expr *r) -{ - MD_Expr *result = MD_PushArrayZero(arena, MD_Expr, 1); - result->op = op; - result->md_node = op_node; - result->parent = 0; - result->left = l; - result->right = r; - if (l != 0) - { - MD_Assert(l->parent == 0); - l->parent = result; - } - if(r != 0) - { - MD_Assert(r->parent == 0); - r->parent = result; - } - return(result); -} - -MD_FUNCTION MD_ExprParseCtx -MD_ExprParse_MakeContext(MD_ExprOprTable *op_table) -{ - MD_ExprParseCtx result = MD_ZERO_STRUCT; - result.op_table = op_table; - - result.accel.postfix_set_ops[0] = MD_ExprOprFromKindString(op_table, MD_ExprOprKind_Postfix, MD_S8Lit("()")); - result.accel.postfix_set_flags[0] = MD_NodeFlag_HasParenLeft | MD_NodeFlag_HasParenRight; - - result.accel.postfix_set_ops[1] = MD_ExprOprFromKindString(op_table, MD_ExprOprKind_Postfix, MD_S8Lit("[]")); - result.accel.postfix_set_flags[1] = MD_NodeFlag_HasBracketLeft | MD_NodeFlag_HasBracketRight; - - result.accel.postfix_set_ops[2] = MD_ExprOprFromKindString(op_table, MD_ExprOprKind_Postfix, MD_S8Lit("{}")); - result.accel.postfix_set_flags[2] = MD_NodeFlag_HasBraceLeft | MD_NodeFlag_HasBraceRight; - - result.accel.postfix_set_ops[3] = MD_ExprOprFromKindString(op_table, MD_ExprOprKind_Postfix, MD_S8Lit("[)")); - result.accel.postfix_set_flags[3] = MD_NodeFlag_HasBracketLeft | MD_NodeFlag_HasParenRight; - - result.accel.postfix_set_ops[4] = MD_ExprOprFromKindString(op_table, MD_ExprOprKind_Postfix, MD_S8Lit("(]")); - result.accel.postfix_set_flags[4] = MD_NodeFlag_HasParenLeft | MD_NodeFlag_HasBracketRight; - - return(result); -} - -MD_FUNCTION MD_Expr* -MD_ExprParse_TopLevel(MD_Arena *arena, MD_ExprParseCtx *ctx, MD_Node *first, MD_Node *opl) -{ - // parse the node range - MD_Node *iter = first; - MD_Expr *expr = MD_ExprParse_MinPrecedence(arena, ctx, &iter, first, opl, 0); - - // check for failed-to-reach-end error - if(ctx->errors.max_message_kind == MD_MessageKind_Null) - { - MD_Node *stop_node = iter; - if(!MD_NodeIsNil(stop_node)) - { - MD_String8 error_str = MD_S8Lit("Expected binary or unary postfix operator."); - MD_Message *error = MD_MakeNodeError(arena,stop_node,MD_MessageKind_FatalError,error_str); - MD_MessageListPush(&ctx->errors, error); - } - } - - return(expr); -} - -MD_FUNCTION MD_b32 -MD_ExprParse_OprConsume(MD_ExprParseCtx *ctx, MD_Node **iter, MD_Node *opl, - MD_ExprOprKind kind, MD_u32 min_precedence, MD_ExprOpr **op_out) -{ - MD_b32 result = 0; - MD_Node *node = *iter; - if(!MD_NodeIsNil(node)) - { - MD_ExprOpr *op = MD_ExprOprFromKindString(ctx->op_table, kind, node->string); - if(op != 0 && op->precedence >= min_precedence) - { - result = 1; - *op_out = op; - *iter= MD_NodeNextWithLimit(*iter, opl); - } - } - return result; -} - -MD_FUNCTION MD_Expr* -MD_ExprParse_Atom(MD_Arena *arena, MD_ExprParseCtx *ctx, MD_Node **iter, - MD_Node *first, MD_Node *opl) -{ - // TODO(allen): nil - MD_Expr* result = 0; - - MD_Node *node = *iter; - MD_ExprOpr *op = 0; - - if(MD_NodeIsNil(node)) - { - MD_Node *last = first; - for (;last->next != opl; last = last->next); - - MD_Node *error_node = last->next; - if (MD_NodeIsNil(error_node)) - { - MD_Node *root = MD_RootFromNode(node); - MD_String8 parse_contents = root->raw_string; - MD_u64 offset = last->offset + last->raw_string.size; - error_node = MD_MakeErrorMarkerNode(arena, parse_contents, offset); - } - - MD_String8 error_str = MD_S8Lit("Unexpected end of expression."); - MD_Message *error = MD_MakeNodeError(arena, error_node, MD_MessageKind_FatalError, - error_str); - MD_MessageListPush(&ctx->errors, error); - } - else if((node->flags & MD_NodeFlag_HasParenLeft) && - (node->flags & MD_NodeFlag_HasParenRight)) - { // NOTE(mal): Parens - *iter = MD_NodeNextWithLimit(*iter, opl); - result = MD_ExprParse_TopLevel(arena, ctx, node->first_child, MD_NilNode()); - } - else if(((node->flags & MD_NodeFlag_HasBraceLeft) && (node->flags & MD_NodeFlag_HasBraceRight)) || - ((node->flags & MD_NodeFlag_HasBracketLeft) && (node->flags & MD_NodeFlag_HasBracketRight)) || - ((node->flags & MD_NodeFlag_HasBracketLeft) && (node->flags & MD_NodeFlag_HasParenRight)) || - ((node->flags & MD_NodeFlag_HasParenLeft) && (node->flags & MD_NodeFlag_HasBracketRight))) - { // NOTE(mal): Unparsed leaf sets ({...}, [...], [...), (...]) - *iter = MD_NodeNextWithLimit(*iter, opl); - result = MD_Expr_NewLeaf(arena, node); - } - else if(MD_ExprParse_OprConsume(ctx, iter, opl, MD_ExprOprKind_Prefix, 1, &op)) - { - MD_u32 min_precedence = op->precedence + 1; - MD_Expr *sub_expr = - MD_ExprParse_MinPrecedence(arena, ctx, iter, first, opl, min_precedence); - if(ctx->errors.max_message_kind == MD_MessageKind_Null) - { - result = MD_Expr_NewOpr(arena, op, node, sub_expr, 0); - } - } - else if(MD_ExprParse_OprConsume(ctx, iter, opl, MD_ExprOprKind_Null, 1, &op)) - { - MD_String8 error_str = MD_S8Fmt(arena, "Expected leaf. Got operator \"%.*s\".", MD_S8VArg(node->string)); - - MD_Message *error = MD_MakeNodeError(arena, node, MD_MessageKind_FatalError, error_str); - MD_MessageListPush(&ctx->errors, error); - } - else if(node->flags & - (MD_NodeFlag_HasParenLeft|MD_NodeFlag_HasParenRight|MD_NodeFlag_HasBracketLeft| - MD_NodeFlag_HasBracketRight|MD_NodeFlag_HasBraceLeft|MD_NodeFlag_HasBraceRight)) - { - MD_String8 error_str = MD_S8Fmt(arena, "Unexpected set.", MD_S8VArg(node->string)); - MD_Message *error = MD_MakeNodeError(arena, node, MD_MessageKind_FatalError, error_str); - MD_MessageListPush(&ctx->errors, error); - } - else{ // NOTE(mal): leaf - *iter = MD_NodeNextWithLimit(*iter, opl); - result = MD_Expr_NewLeaf(arena, node); - } - - return(result); -} - -MD_FUNCTION MD_Expr* -MD_ExprParse_MinPrecedence(MD_Arena *arena, MD_ExprParseCtx *ctx, - MD_Node **iter, MD_Node *first, MD_Node *opl, - MD_u32 min_precedence) -{ - // TODO(allen): nil - MD_Expr* result = 0; - - result = MD_ExprParse_Atom(arena, ctx, iter, first, opl); - if(ctx->errors.max_message_kind == MD_MessageKind_Null) - { - for (;!MD_NodeIsNil(*iter);) - { - MD_Node *node = *iter; - MD_ExprOpr *op = 0; - - if(MD_ExprParse_OprConsume(ctx, iter, opl, MD_ExprOprKind_Binary, - min_precedence, &op) || - MD_ExprParse_OprConsume(ctx, iter, opl, MD_ExprOprKind_BinaryRightAssociative, - min_precedence, &op)) - { - MD_u32 next_min_precedence = op->precedence + (op->kind == MD_ExprOprKind_Binary); - MD_Expr *sub_expr = - MD_ExprParse_MinPrecedence(arena, ctx, iter, first, opl, next_min_precedence); - if(ctx->errors.max_message_kind == MD_MessageKind_Null) - { - result = MD_Expr_NewOpr(arena, op, node, result, sub_expr); - } - else{ - break; - } - } - - else - { - MD_b32 found_postfix_setlike_operator = 0; - for(MD_u32 i_op = 0; - i_op < MD_ArrayCount(ctx->accel.postfix_set_ops); - ++i_op) - { - MD_ExprOpr *op2 = ctx->accel.postfix_set_ops[i_op]; - if(op2 && op2->precedence >= min_precedence && - node->flags == ctx->accel.postfix_set_flags[i_op]) - { - *iter = MD_NodeNextWithLimit(*iter, opl); - result = MD_Expr_NewOpr(arena, op2, node, result, 0); - found_postfix_setlike_operator = 1; - break; - } - } - - if(!found_postfix_setlike_operator) - { - if(MD_ExprParse_OprConsume(ctx, iter, opl, MD_ExprOprKind_Postfix, - min_precedence, &op)) - { - result = MD_Expr_NewOpr(arena, op, node, result, 0); - } - else - { - break; // NOTE: Due to lack of progress - } - } - - } - - } - } - - return(result); -} - - - - -//~ String Generation - -MD_FUNCTION void -MD_DebugDumpFromNode(MD_Arena *arena, MD_String8List *out, MD_Node *node, - int indent, MD_String8 indent_string, MD_GenerateFlags flags) -{ -#define MD_PrintIndent(_indent_level) do\ -{\ -for(int i = 0; i < (_indent_level); i += 1)\ -{\ -MD_S8ListPush(arena, out, indent_string);\ -}\ -}while(0) - - //- rjf: prev-comment - if(flags & MD_GenerateFlag_Comments && node->prev_comment.size != 0) - { - MD_PrintIndent(indent); - MD_S8ListPush(arena, out, MD_S8Lit("/*\n")); - MD_PrintIndent(indent); - MD_S8ListPush(arena, out, node->prev_comment); - MD_PrintIndent(indent); - MD_S8ListPush(arena, out, MD_S8Lit("\n")); - MD_PrintIndent(indent); - MD_S8ListPush(arena, out, MD_S8Lit("*/\n")); - } - - //- rjf: tags of node - if(flags & MD_GenerateFlag_Tags) - { - for(MD_EachNode(tag, node->first_tag)) - { - MD_PrintIndent(indent); - MD_S8ListPush(arena, out, MD_S8Lit("@")); - MD_S8ListPush(arena, out, tag->string); - if(flags & MD_GenerateFlag_TagArguments && !MD_NodeIsNil(tag->first_child)) - { - int tag_arg_indent = (int)(indent + 1 + tag->string.size + 1); - MD_S8ListPush(arena, out, MD_S8Lit("(")); - for(MD_EachNode(child, tag->first_child)) - { - int child_indent = tag_arg_indent; - if(MD_NodeIsNil(child->prev)) - { - child_indent = 0; - } - MD_DebugDumpFromNode(arena, out, child, child_indent, MD_S8Lit(" "), flags); - if(!MD_NodeIsNil(child->next)) - { - MD_S8ListPush(arena, out, MD_S8Lit(",\n")); - } - } - MD_S8ListPush(arena, out, MD_S8Lit(")\n")); - } - else - { - MD_S8ListPush(arena, out, MD_S8Lit("\n")); - } - } - } - - //- rjf: node kind - if(flags & MD_GenerateFlag_NodeKind) - { - MD_PrintIndent(indent); - MD_S8ListPush(arena, out, MD_S8Lit("// kind: \"")); - MD_S8ListPush(arena, out, MD_StringFromNodeKind(node->kind)); - MD_S8ListPush(arena, out, MD_S8Lit("\"\n")); - } - - //- rjf: node flags - if(flags & MD_GenerateFlag_NodeFlags) - { - MD_PrintIndent(indent); - MD_ArenaTemp scratch = MD_GetScratch(&arena, 1); - MD_String8List flag_strs = MD_StringListFromNodeFlags(scratch.arena, node->flags); - MD_StringJoin join = { MD_S8LitComp(""), MD_S8LitComp("|"), MD_S8LitComp("") }; - MD_String8 flag_str = MD_S8ListJoin(arena, flag_strs, &join); - MD_S8ListPush(arena, out, MD_S8Lit("// flags: \"")); - MD_S8ListPush(arena, out, flag_str); - MD_S8ListPush(arena, out, MD_S8Lit("\"\n")); - MD_ReleaseScratch(scratch); - } - - //- rjf: location - if(flags & MD_GenerateFlag_Location) - { - MD_PrintIndent(indent); - MD_CodeLoc loc = MD_CodeLocFromNode(node); - MD_String8 string = MD_S8Fmt(arena, "// location: %.*s:%i:%i\n", MD_S8VArg(loc.filename), (int)loc.line, (int)loc.column); - MD_S8ListPush(arena, out, string); - } - - //- rjf: name of node - if(node->string.size != 0) - { - MD_PrintIndent(indent); - if(node->kind == MD_NodeKind_File) - { - MD_S8ListPush(arena, out, MD_S8Lit("`")); - MD_S8ListPush(arena, out, node->string); - MD_S8ListPush(arena, out, MD_S8Lit("`")); - } - else - { - MD_S8ListPush(arena, out, node->raw_string); - } - } - - //- rjf: children list - if(flags & MD_GenerateFlag_Children && !MD_NodeIsNil(node->first_child)) - { - if(node->string.size != 0) - { - MD_S8ListPush(arena, out, MD_S8Lit(":\n")); - } - MD_PrintIndent(indent); - MD_S8ListPush(arena, out, MD_S8Lit("{\n")); - for(MD_EachNode(child, node->first_child)) - { - MD_DebugDumpFromNode(arena, out, child, indent+1, indent_string, flags); - MD_S8ListPush(arena, out, MD_S8Lit(",\n")); - } - MD_PrintIndent(indent); - MD_S8ListPush(arena, out, MD_S8Lit("}")); - } - - //- rjf: next-comment - if(flags & MD_GenerateFlag_Comments && node->next_comment.size != 0) - { - MD_PrintIndent(indent); - MD_S8ListPush(arena, out, MD_S8Lit("\n/*\n")); - MD_PrintIndent(indent); - MD_S8ListPush(arena, out, node->next_comment); - MD_PrintIndent(indent); - MD_S8ListPush(arena, out, MD_S8Lit("\n")); - MD_PrintIndent(indent); - MD_S8ListPush(arena, out, MD_S8Lit("*/\n")); - } - -#undef MD_PrintIndent -} - -MD_FUNCTION void -MD_ReconstructionFromNode(MD_Arena *arena, MD_String8List *out, MD_Node *node, - int indent, MD_String8 indent_string) -{ - MD_CodeLoc code_loc = MD_CodeLocFromNode(node); - -#define MD_PrintIndent(_indent_level) do\ -{\ -for(int i = 0; i < (_indent_level); i += 1)\ -{\ -MD_S8ListPush(arena, out, indent_string);\ -}\ -}while(0) - - //- rjf: prev-comment - if(node->prev_comment.size != 0) - { - MD_String8 comment = MD_S8SkipWhitespace(MD_S8ChopWhitespace(node->prev_comment)); - MD_b32 requires_multiline = MD_S8FindSubstring(comment, MD_S8Lit("\n"), 0, 0) < comment.size; - MD_PrintIndent(indent); - if(requires_multiline) - { - MD_S8ListPush(arena, out, MD_S8Lit("/*\n")); - } - else - { - MD_S8ListPush(arena, out, MD_S8Lit("// ")); - } - MD_S8ListPush(arena, out, comment); - if(requires_multiline) - { - MD_S8ListPush(arena, out, MD_S8Lit("\n*/\n")); - } - else - { - MD_S8ListPush(arena, out, MD_S8Lit("\n")); - } - } - - //- rjf: tags of node - MD_u32 tag_first_line = MD_CodeLocFromNode(node->first_tag).line; - MD_u32 tag_last_line = tag_first_line; - { - for(MD_EachNode(tag, node->first_tag)) - { - MD_u32 tag_line = MD_CodeLocFromNode(tag).line; - if(tag_line != tag_last_line) - { - MD_S8ListPush(arena, out, MD_S8Lit("\n")); - tag_last_line = tag_line; - } - else if(!MD_NodeIsNil(tag->prev)) - { - MD_S8ListPush(arena, out, MD_S8Lit(" ")); - } - - MD_PrintIndent(indent); - MD_S8ListPush(arena, out, MD_S8Lit("@")); - MD_S8ListPush(arena, out, tag->string); - if(!MD_NodeIsNil(tag->first_child)) - { - int tag_arg_indent = (int)(indent + 1 + tag->string.size + 1); - MD_S8ListPush(arena, out, MD_S8Lit("(")); - MD_u32 last_line = MD_CodeLocFromNode(tag).line; - for(MD_EachNode(child, tag->first_child)) - { - MD_CodeLoc child_loc = MD_CodeLocFromNode(child); - if(child_loc.line != last_line) - { - MD_S8ListPush(arena, out, MD_S8Lit("\n")); - MD_PrintIndent(indent); - } - last_line = child_loc.line; - - int child_indent = tag_arg_indent; - if(MD_NodeIsNil(child->prev)) - { - child_indent = 0; - } - MD_ReconstructionFromNode(arena, out, child, child_indent, MD_S8Lit(" ")); - if(!MD_NodeIsNil(child->next)) - { - MD_S8ListPush(arena, out, MD_S8Lit(",\n")); - } - } - MD_S8ListPush(arena, out, MD_S8Lit(")")); - } - } - } - - //- rjf: name of node - if(node->string.size != 0) - { - if(tag_first_line != tag_last_line) - { - MD_S8ListPush(arena, out, MD_S8Lit("\n")); - MD_PrintIndent(indent); - } - else if(!MD_NodeIsNil(node->first_tag) || !MD_NodeIsNil(node->prev)) - { - MD_S8ListPush(arena, out, MD_S8Lit(" ")); - } - if(node->kind == MD_NodeKind_File) - { - MD_S8ListPush(arena, out, MD_S8Lit("`")); - MD_S8ListPush(arena, out, node->string); - MD_S8ListPush(arena, out, MD_S8Lit("`")); - } - else - { - MD_S8ListPush(arena, out, node->raw_string); - } - } - - //- rjf: children list - if(!MD_NodeIsNil(node->first_child)) - { - if(node->string.size != 0) - { - MD_S8ListPush(arena, out, MD_S8Lit(":")); - } - - // rjf: figure out opener/closer symbols - MD_u8 opener_char = 0; - MD_u8 closer_char = 0; - if(node->flags & MD_NodeFlag_HasParenLeft) { opener_char = '('; } - else if(node->flags & MD_NodeFlag_HasBracketLeft) { opener_char = '['; } - else if(node->flags & MD_NodeFlag_HasBraceLeft) { opener_char = '{'; } - if(node->flags & MD_NodeFlag_HasParenRight) { closer_char = ')'; } - else if(node->flags & MD_NodeFlag_HasBracketRight){ closer_char = ']'; } - else if(node->flags & MD_NodeFlag_HasBraceRight) { closer_char = '}'; } - - MD_b32 multiline = 0; - for(MD_EachNode(child, node->first_child)) - { - MD_CodeLoc child_loc = MD_CodeLocFromNode(child); - if(child_loc.line != code_loc.line) - { - multiline = 1; - break; - } - } - - if(opener_char != 0) - { - if(multiline) - { - MD_S8ListPush(arena, out, MD_S8Lit("\n")); - MD_PrintIndent(indent); - } - else - { - MD_S8ListPush(arena, out, MD_S8Lit(" ")); - } - MD_S8ListPush(arena, out, MD_S8(&opener_char, 1)); - if(multiline) - { - MD_S8ListPush(arena, out, MD_S8Lit("\n")); - MD_PrintIndent(indent+1); - } - } - MD_u32 last_line = MD_CodeLocFromNode(node->first_child).line; - for(MD_EachNode(child, node->first_child)) - { - int child_indent = 0; - MD_CodeLoc child_loc = MD_CodeLocFromNode(child); - if(child_loc.line != last_line) - { - MD_S8ListPush(arena, out, MD_S8Lit("\n")); - MD_PrintIndent(indent); - child_indent = indent+1; - } - last_line = child_loc.line; - MD_ReconstructionFromNode(arena, out, child, child_indent, indent_string); - } - MD_PrintIndent(indent); - if(closer_char != 0) - { - if(last_line != code_loc.line) - { - MD_S8ListPush(arena, out, MD_S8Lit("\n")); - MD_PrintIndent(indent); - } - else - { - MD_S8ListPush(arena, out, MD_S8Lit(" ")); - } - MD_S8ListPush(arena, out, MD_S8(&closer_char, 1)); - } - } - - //- rjf: trailing separator symbols - if(node->flags & MD_NodeFlag_IsBeforeSemicolon) - { - MD_S8ListPush(arena, out, MD_S8Lit(";")); - } - else if(node->flags & MD_NodeFlag_IsBeforeComma) - { - MD_S8ListPush(arena, out, MD_S8Lit(",")); - } - - //- rjf: next-comment - // TODO(rjf): @node_comments - if(node->next_comment.size != 0) - { - MD_String8 comment = MD_S8SkipWhitespace(MD_S8ChopWhitespace(node->next_comment)); - MD_b32 requires_multiline = MD_S8FindSubstring(comment, MD_S8Lit("\n"), 0, 0) < comment.size; - MD_PrintIndent(indent); - if(requires_multiline) - { - MD_S8ListPush(arena, out, MD_S8Lit("/*\n")); - } - else - { - MD_S8ListPush(arena, out, MD_S8Lit("// ")); - } - MD_S8ListPush(arena, out, comment); - if(requires_multiline) - { - MD_S8ListPush(arena, out, MD_S8Lit("\n*/\n")); - } - else - { - MD_S8ListPush(arena, out, MD_S8Lit("\n")); - } - } - -#undef MD_PrintIndent -} - - -#if !MD_DISABLE_PRINT_HELPERS -MD_FUNCTION void -MD_PrintDebugDumpFromNode(FILE *file, MD_Node *node, MD_GenerateFlags flags) -{ - MD_ArenaTemp scratch = MD_GetScratch(0, 0); - MD_String8List list = {0}; - MD_DebugDumpFromNode(scratch.arena, &list, node, - 0, MD_S8Lit(" "), flags); - MD_String8 string = MD_S8ListJoin(scratch.arena, list, 0); - fwrite(string.str, string.size, 1, file); - MD_ReleaseScratch(scratch); -} -#endif - - -//~ Command Line Argument Helper - -MD_FUNCTION MD_String8List -MD_StringListFromArgCV(MD_Arena *arena, int argument_count, char **arguments) -{ - MD_String8List options = MD_ZERO_STRUCT; - for(int i = 1; i < argument_count; i += 1) - { - MD_S8ListPush(arena, &options, MD_S8CString(arguments[i])); - } - return options; -} - -MD_FUNCTION MD_CmdLine -MD_MakeCmdLineFromOptions(MD_Arena *arena, MD_String8List options) -{ - MD_CmdLine cmdln = MD_ZERO_STRUCT; - MD_b32 parsing_only_inputs = 0; - - for(MD_String8Node *n = options.first, *next = 0; - n; n = next) - { - next = n->next; - - //- rjf: figure out whether or not this is an option by checking for `-` or `--` - // from the beginning of the string - MD_String8 option_name = MD_ZERO_STRUCT; - if(MD_S8Match(n->string, MD_S8Lit("--"), 0)) - { - parsing_only_inputs = 1; - } - else if(MD_S8Match(MD_S8Prefix(n->string, 2), MD_S8Lit("--"), 0)) - { - option_name = MD_S8Skip(n->string, 2); - } - else if(MD_S8Match(MD_S8Prefix(n->string, 1), MD_S8Lit("-"), 0)) - { - option_name = MD_S8Skip(n->string, 1); - } - - //- rjf: trim off anything after a `:` or `=`, use that as the first value string - MD_String8 first_value = MD_ZERO_STRUCT; - MD_b32 has_many_values = 0; - if(option_name.size != 0) - { - MD_u64 colon_signifier_pos = MD_S8FindSubstring(option_name, MD_S8Lit(":"), 0, 0); - MD_u64 equal_signifier_pos = MD_S8FindSubstring(option_name, MD_S8Lit("="), 0, 0); - MD_u64 signifier_pos = MD_Min(colon_signifier_pos, equal_signifier_pos); - if(signifier_pos < option_name.size) - { - first_value = MD_S8Skip(option_name, signifier_pos+1); - option_name = MD_S8Prefix(option_name, signifier_pos); - if(MD_S8Match(MD_S8Suffix(first_value, 1), MD_S8Lit(","), 0)) - { - has_many_values = 1; - } - } - } - - //- rjf: gather arguments - if(option_name.size != 0 && !parsing_only_inputs) - { - MD_String8List option_values = MD_ZERO_STRUCT; - - //- rjf: push first value - if(first_value.size != 0) - { - MD_S8ListPush(arena, &option_values, first_value); - } - - //- rjf: scan next string values, add them to option values until we hit a lack - // of a ',' between values - if(has_many_values) - { - for(MD_String8Node *v = next; v; v = v->next, next = v) - { - MD_String8 value_str = v->string; - MD_b32 next_has_arguments = MD_S8Match(MD_S8Suffix(value_str, 1), MD_S8Lit(","), 0); - MD_b32 in_quotes = 0; - MD_u64 start = 0; - for(MD_u64 i = 0; i <= value_str.size; i += 1) - { - if(i == value_str.size || (value_str.str[i] == ',' && in_quotes == 0)) - { - if(start != i) - { - MD_S8ListPush(arena, &option_values, MD_S8Substring(value_str, start, i)); - } - start = i+1; - } - else if(value_str.str[i] == '"') - { - in_quotes = !in_quotes; - } - } - if(next_has_arguments == 0) - { - break; - } - } - } - - //- rjf: insert the fully parsed option - { - MD_CmdLineOption *opt = MD_PushArrayZero(arena, MD_CmdLineOption, 1); - opt->name = option_name; - opt->values = option_values; - if(cmdln.last_option == 0) - { - cmdln.first_option = cmdln.last_option = opt; - } - else - { - cmdln.last_option->next = opt; - cmdln.last_option = cmdln.last_option->next; - } - } - } - - //- rjf: this argument is not an option, push it to regular inputs list. - else - { - MD_S8ListPush(arena, &cmdln.inputs, n->string); - } - } - - return cmdln; -} - -MD_FUNCTION MD_String8List -MD_CmdLineValuesFromString(MD_CmdLine cmdln, MD_String8 name) -{ - MD_String8List values = MD_ZERO_STRUCT; - for(MD_CmdLineOption *opt = cmdln.first_option; opt; opt = opt->next) - { - if(MD_S8Match(opt->name, name, 0)) - { - values = opt->values; - break; - } - } - return values; -} - -MD_FUNCTION MD_b32 -MD_CmdLineB32FromString(MD_CmdLine cmdln, MD_String8 name) -{ - MD_b32 result = 0; - for(MD_CmdLineOption *opt = cmdln.first_option; opt; opt = opt->next) - { - if(MD_S8Match(opt->name, name, 0)) - { - result = 1; - break; - } - } - return result; -} - -MD_FUNCTION MD_i64 -MD_CmdLineI64FromString(MD_CmdLine cmdln, MD_String8 name) -{ - MD_String8List values = MD_CmdLineValuesFromString(cmdln, name); - MD_ArenaTemp scratch = MD_GetScratch(0, 0); - MD_String8 value_str = MD_S8ListJoin(scratch.arena, values, 0); - MD_i64 result = MD_CStyleIntFromString(value_str); - MD_ReleaseScratch(scratch); - return(result); -} - -//~ File System - -MD_FUNCTION MD_String8 -MD_LoadEntireFile(MD_Arena *arena, MD_String8 filename) -{ - MD_String8 result = MD_ZERO_STRUCT; -#if defined(MD_IMPL_LoadEntireFile) - result = MD_IMPL_LoadEntireFile(arena, filename); -#endif - return(result); -} - -MD_FUNCTION MD_b32 -MD_FileIterBegin(MD_FileIter *it, MD_String8 path) -{ -#if !defined(MD_IMPL_FileIterBegin) - return(0); -#else - return(MD_IMPL_FileIterBegin(it, path)); -#endif -} - -MD_FUNCTION MD_FileInfo -MD_FileIterNext(MD_Arena *arena, MD_FileIter *it) -{ -#if !defined(MD_IMPL_FileIterNext) - MD_FileInfo result = {0}; - return(result); -#else - return(MD_IMPL_FileIterNext(arena, it)); -#endif -} - -MD_FUNCTION void -MD_FileIterEnd(MD_FileIter *it) -{ -#if defined(MD_IMPL_FileIterEnd) - MD_IMPL_FileIterEnd(it); -#endif -} - -#endif // MD_C - -/* -Copyright 2021 Dion Systems LLC - -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. -*/ diff --git a/Source/External/metadesk/md.h b/Source/External/metadesk/md.h deleted file mode 100644 index ff44627..0000000 --- a/Source/External/metadesk/md.h +++ /dev/null @@ -1,1248 +0,0 @@ -// LICENSE AT END OF FILE (MIT). - -/* -** Welcome to Metadesk! -** -** Metadesk is a data description language designed to look like a programming -** language, and this is the accompanying parser library. While you are free to -** use it however you see fit, here are a couple of the uses we have intended -** to support: -** + quickly writing a C or C++ metaprogram from scratch -** + building "low budget" domain specific languages, such as marked-up -** webpage content, or asset metadata -** + creating robust and flexible config systems for applications -** -** If it's your first time with Metadesk, check out the "How to Build" section -** below, and consider looking at the examples included with the library. The -** examples_directory.txt will help you find your way from the intro examples -** through all the more advanced aspects of the library you might like to -** learn about. -** -** Direct issues, questions, suggestions, requests, etc to: -** https://github.com/Dion-Systems/metadesk -** -** -** How To Build: -** -** The library is set up as a direct source-include library, so if you have a -** single unit build you can just #include "md.h" and "md.c". If you have a -** multiple unit build you can #include "md.h" where necessary and add "md.c" -** as a separate compilation unit (extra care has to be taken if you intend to -** use overrides in a multiple unit build). -** -** See `bin/compile_flags.txt` for the flags to build with. -** -** The tests and examples can be built with the bash scripts in bin. There are -** a few things to know to use these scripts: -** 1. First you should run `bld_init.sh` which will initialize your copy of -** Metadesk's build system. -** 2. On Linux the shell scripts should work as written. On Windows you will -** need to use a bash interpreter specifically. Generally the `bash.exe` -** that comes with an install of git on Windows works well for this. -** Add it to your path or setup a batch script that calls it and then -** pass the bash scripts to the interpreter to build. -** 3. You should be able to run the scripts: -** `build_tests.sh` -** `build_examples.sh` -** `run_tests.sh` -** `run_examples.sh` -** `type_metadata_example.sh` -*/ - -#ifndef MD_H -#define MD_H - -#define MD_VERSION_MAJ 1 -#define MD_VERSION_MIN 0 - -//~ Set default values for controls -#if !defined(MD_DEFAULT_BASIC_TYPES) -# define MD_DEFAULT_BASIC_TYPES 1 -#endif -#if !defined(MD_DEFAULT_MEMSET) -# define MD_DEFAULT_MEMSET 1 -#endif -#if !defined(MD_DEFAULT_FILE_LOAD) -# define MD_DEFAULT_FILE_LOAD 1 -#endif -#if !defined(MD_DEFAULT_FILE_ITER) -# define MD_DEFAULT_FILE_ITER 1 -#endif -#if !defined(MD_DEFAULT_MEMORY) -# define MD_DEFAULT_MEMORY 1 -#endif -#if !defined(MD_DEFAULT_ARENA) -# define MD_DEFAULT_ARENA 1 -#endif -#if !defined(MD_DEFAULT_SCRATCH) -# define MD_DEFAULT_SCRATCH 1 -#endif -#if !defined(MD_DEFAULT_SPRINTF) -# define MD_DEFAULT_SPRINTF 1 -#endif - -#if !defined(MD_DISABLE_PRINT_HELPERS) -# define MD_DISABLE_PRINT_HELPERS 0 -#endif - - -//~///////////////////////////////////////////////////////////////////////////// -////////////////////////////// Context Cracking //////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -#if defined(__clang__) - -# define MD_COMPILER_CLANG 1 - -# if defined(__APPLE__) && defined(__MACH__) -# define MD_OS_MAC 1 -# elif defined(__gnu_linux__) -# define MD_OS_LINUX 1 -# elif defined(_WIN32) -# define MD_OS_WINDOWS 1 -# else -# error This compiler/platform combo is not supported yet -# endif - -# if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) -# define MD_ARCH_X64 1 -# elif defined(i386) || defined(__i386) || defined(__i386__) -# define MD_ARCH_X86 1 -# elif defined(__aarch64__) -# define MD_ARCH_ARM64 1 -# elif defined(__arm__) -# define MD_ARCH_ARM32 1 -# else -# error architecture not supported yet -# endif - -#elif defined(_MSC_VER) - -# define MD_COMPILER_CL 1 - -# if defined(_WIN32) -# define MD_OS_WINDOWS 1 -# else -# error This compiler/platform combo is not supported yet -# endif - -# if defined(_M_AMD64) -# define MD_ARCH_X64 1 -# elif defined(_M_IX86) -# define MD_ARCH_X86 1 -# elif defined(_M_ARM64) -# define MD_ARCH_ARM64 1 -# elif defined(_M_ARM) -# define MD_ARCH_ARM32 1 -# else -# error architecture not supported yet -# endif - -# if _MSC_VER >= 1920 -# define MD_COMPILER_CL_YEAR 2019 -# elif _MSC_VER >= 1910 -# define MD_COMPILER_CL_YEAR 2017 -# elif _MSC_VER >= 1900 -# define MD_COMPILER_CL_YEAR 2015 -# elif _MSC_VER >= 1800 -# define MD_COMPILER_CL_YEAR 2013 -# elif _MSC_VER >= 1700 -# define MD_COMPILER_CL_YEAR 2012 -# elif _MSC_VER >= 1600 -# define MD_COMPILER_CL_YEAR 2010 -# elif _MSC_VER >= 1500 -# define MD_COMPILER_CL_YEAR 2008 -# elif _MSC_VER >= 1400 -# define MD_COMPILER_CL_YEAR 2005 -# else -# define MD_COMPILER_CL_YEAR 0 -# endif - -#elif defined(__GNUC__) || defined(__GNUG__) - -# define MD_COMPILER_GCC 1 - -# if defined(__gnu_linux__) -# define MD_OS_LINUX 1 -# else -# error This compiler/platform combo is not supported yet -# endif - -# if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) -# define MD_ARCH_X64 1 -# elif defined(i386) || defined(__i386) || defined(__i386__) -# define MD_ARCH_X86 1 -# elif defined(__aarch64__) -# define MD_ARCH_ARM64 1 -# elif defined(__arm__) -# define MD_ARCH_ARM32 1 -# else -# error architecture not supported yet -# endif - -#else -# error This compiler is not supported yet -#endif - -#if defined(MD_ARCH_X64) -# define MD_ARCH_64BIT 1 -#elif defined(MD_ARCH_X86) -# define MD_ARCH_32BIT 1 -#endif - -#if defined(__cplusplus) -# define MD_LANG_CPP 1 - -// We can't get this 100% correct thanks to Microsoft's compiler. -// So this check lets us pre-define MD_CPP_VERSION if we have to. -# if !defined(MD_CPP_VERSION) -# if defined(MD_COMPILER_CL) -// CL is annoying and didn't update __cplusplus over time -// If it is available _MSVC_LANG serves the same role -# if defined(_MSVC_LANG) -# if _MSVC_LANG <= 199711L -# define MD_CPP_VERSION 98 -# elif _MSVC_LANG <= 201103L -# define MD_CPP_VERSION 11 -# elif _MSVC_LANG <= 201402L -# define MD_CPP_VERSION 14 -# elif _MSVC_LANG <= 201703L -# define MD_CPP_VERSION 17 -# elif _MSVC_LANG <= 202002L -# define MD_CPP_VERSION 20 -# else -# define MD_CPP_VERSION 23 -# endif -// If we don't have _MSVC_LANG we can guess from the compiler version -# else -# if MD_COMPILER_CL_YEAR <= 2010 -# define MD_CPP_VERSION 98 -# elif MD_COMPILER_CL_YEAR <= 2015 -# define MD_CPP_VERSION 11 -# else -# define MD_CPP_VERSION 17 -# endif -# endif -# else -// Other compilers use __cplusplus correctly -# if __cplusplus <= 199711L -# define MD_CPP_VERSION 98 -# elif __cplusplus <= 201103L -# define MD_CPP_VERSION 11 -# elif __cplusplus <= 201402L -# define MD_CPP_VERSION 14 -# elif __cplusplus <= 201703L -# define MD_CPP_VERSION 17 -# elif __cplusplus <= 202002L -# define MD_CPP_VERSION 20 -# else -# define MD_CPP_VERSION 23 -# endif -# endif -# endif - -#else -# define MD_LANG_C 1 -#endif - -// zeroify - -#if !defined(MD_ARCH_32BIT) -# define MD_ARCH_32BIT 0 -#endif -#if !defined(MD_ARCH_64BIT) -# define MD_ARCH_64BIT 0 -#endif -#if !defined(MD_ARCH_X64) -# define MD_ARCH_X64 0 -#endif -#if !defined(MD_ARCH_X86) -# define MD_ARCH_X86 0 -#endif -#if !defined(MD_ARCH_ARM64) -# define MD_ARCH_ARM64 0 -#endif -#if !defined(MD_ARCH_ARM32) -# define MD_ARCH_ARM32 0 -#endif -#if !defined(MD_COMPILER_CL) -# define MD_COMPILER_CL 0 -#endif -#if !defined(MD_COMPILER_GCC) -# define MD_COMPILER_GCC 0 -#endif -#if !defined(MD_COMPILER_CLANG) -# define MD_COMPILER_CLANG 0 -#endif -#if !defined(MD_OS_WINDOWS) -# define MD_OS_WINDOWS 0 -#endif -#if !defined(MD_OS_LINUX) -# define MD_OS_LINUX 0 -#endif -#if !defined(MD_OS_MAC) -# define MD_OS_MAC 0 -#endif -#if !defined(MD_LANG_C) -# define MD_LANG_C 0 -#endif -#if !defined(MD_LANG_CPP) -# define MD_LANG_CPP 0 -#endif -#if !defined(MD_CPP_VERSION) -# define MD_CPP_VERSION 0 -#endif - -#if MD_LANG_CPP -# define MD_ZERO_STRUCT {} -#else -# define MD_ZERO_STRUCT {0} -#endif - -#if MD_LANG_C -# define MD_C_LINKAGE_BEGIN -# define MD_C_LINKAGE_END -#else -# define MD_C_LINKAGE_BEGIN extern "C"{ -# define MD_C_LINKAGE_END } -#endif - -#if MD_COMPILER_CL -# define MD_THREAD_LOCAL __declspec(thread) -#elif MD_COMPILER_GCC || MD_COMPILER_CLANG -# define MD_THREAD_LOCAL __thread -#endif - -//~///////////////////////////////////////////////////////////////////////////// -///////////////////////////// Helpers, Macros, Etc ///////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -//~ Linkage Wrappers - -#if !defined(MD_FUNCTION) -# define MD_FUNCTION -#endif - -#if !defined(MD_GLOBAL) -# define MD_GLOBAL static -#endif - -//~ Basic Utilities - -#define MD_Assert(c) if (!(c)) { *(volatile MD_u64 *)0 = 0; } -#define MD_StaticAssert(c,label) MD_u8 MD_static_assert_##label[(c)?(1):(-1)] -#define MD_ArrayCount(a) (sizeof(a) / sizeof((a)[0])) - -#define MD_Min(a,b) (((a)<(b))?(a):(b)) -#define MD_Max(a,b) (((a)>(b))?(a):(b)) -#define MD_ClampBot(a,b) MD_Max(a,b) -#define MD_ClampTop(a,b) MD_Min(a,b) - -#define MD_AlignPow2(x,b) (((x)+((b)-1))&(~((b)-1))) - -//~ Linked List Macros - -// terminator modes -#define MD_CheckNull(p) ((p)==0) -#define MD_SetNull(p) ((p)=0) -#define MD_CheckNil(p) (MD_NodeIsNil(p)) -#define MD_SetNil(p) ((p)=MD_NilNode()) - -// implementations -#define MD_QueuePush_NZ(f,l,n,next,zchk,zset) (zchk(f)?\ -(f)=(l)=(n):\ -((l)->next=(n),(l)=(n),zset((n)->next))) -#define MD_QueuePop_NZ(f,l,next,zset) ((f)==(l)?\ -(zset(f),zset(l)):\ -((f)=(f)->next)) -#define MD_StackPush_N(f,n,next) ((n)->next=(f),(f)=(n)) -#define MD_StackPop_NZ(f,next,zchk) (zchk(f)?0:(f)=(f)->next) - -#define MD_DblPushBack_NPZ(f,l,n,next,prev,zchk,zset) \ -(zchk(f)?\ -((f)=(l)=(n),zset((n)->next),zset((n)->prev)):\ -((n)->prev=(l),(l)->next=(n),(l)=(n),zset((n)->next))) -#define MD_DblRemove_NPZ(f,l,n,next,prev,zset) (((f)==(n)?\ -((f)=(f)->next,zset((f)->prev)):\ -(l)==(n)?\ -((l)=(l)->prev,zset((l)->next)):\ -((n)->next->prev=(n)->prev,\ -(n)->prev->next=(n)->next))) - -// compositions -#define MD_QueuePush(f,l,n) MD_QueuePush_NZ(f,l,n,next,MD_CheckNull,MD_SetNull) -#define MD_QueuePop(f,l) MD_QueuePop_NZ(f,l,next,MD_SetNull) -#define MD_StackPush(f,n) MD_StackPush_N(f,n,next) -#define MD_StackPop(f) MD_StackPop_NZ(f,next,MD_CheckNull) -#define MD_DblPushBack(f,l,n) MD_DblPushBack_NPZ(f,l,n,next,prev,MD_CheckNull,MD_SetNull) -#define MD_DblPushFront(f,l,n) MD_DblPushBack_NPZ(l,f,n,prev,next,MD_CheckNull,MD_SetNull) -#define MD_DblRemove(f,l,n) MD_DblRemove_NPZ(f,l,n,next,prev,MD_SetNull) - -#define MD_NodeDblPushBack(f,l,n) MD_DblPushBack_NPZ(f,l,n,next,prev,MD_CheckNil,MD_SetNil) -#define MD_NodeDblPushFront(f,l,n) MD_DblPushBack_NPZ(l,f,n,prev,next,MD_CheckNil,MD_SetNil) -#define MD_NodeDblRemove(f,l,n) MD_DblRemove_NPZ(f,l,n,next,prev,MD_SetNil) - - -//~ Memory Operations - -#define MD_MemorySet(p,v,z) (MD_IMPL_Memset(p,v,z)) -#define MD_MemoryZero(p,z) (MD_IMPL_Memset(p,0,z)) -#define MD_MemoryZeroStruct(p) (MD_IMPL_Memset(p,0,sizeof(*(p)))) -#define MD_MemoryCopy(d,s,z) (MD_IMPL_Memmove(d,s,z)) - -//~ sprintf -#if MD_DEFAULT_SPRINTF -#define STB_SPRINTF_DECORATE(name) md_stbsp_##name -#define MD_IMPL_Vsnprintf md_stbsp_vsnprintf -#include "md_stb_sprintf.h" -#endif - -//~///////////////////////////////////////////////////////////////////////////// -//////////////////////////////////// Types ///////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -//~ Basic Types - -#include - -#if defined(MD_DEFAULT_BASIC_TYPES) - -#include -typedef int8_t MD_i8; -typedef int16_t MD_i16; -typedef int32_t MD_i32; -typedef int64_t MD_i64; -typedef uint8_t MD_u8; -typedef uint16_t MD_u16; -typedef uint32_t MD_u32; -typedef uint64_t MD_u64; -typedef float MD_f32; -typedef double MD_f64; - -#endif - -typedef MD_i8 MD_b8; -typedef MD_i16 MD_b16; -typedef MD_i32 MD_b32; -typedef MD_i64 MD_b64; - -//~ Default Arena - -#if MD_DEFAULT_ARENA - -typedef struct MD_ArenaDefault MD_ArenaDefault; -struct MD_ArenaDefault -{ - MD_ArenaDefault *prev; - MD_ArenaDefault *current; - MD_u64 base_pos; - MD_u64 pos; - MD_u64 cmt; - MD_u64 cap; - MD_u64 align; -}; -#define MD_IMPL_Arena MD_ArenaDefault - -#endif - -//~ Abstract Arena - -#if !defined(MD_IMPL_Arena) -# error Missing implementation for MD_IMPL_Arena -#endif - -typedef MD_IMPL_Arena MD_Arena; - -//~ Arena Helpers - -typedef struct MD_ArenaTemp MD_ArenaTemp; -struct MD_ArenaTemp -{ - MD_Arena *arena; - MD_u64 pos; -}; - -//~ Basic Unicode string types. - -typedef struct MD_String8 MD_String8; -struct MD_String8 -{ - MD_u8 *str; - MD_u64 size; -}; - -typedef struct MD_String16 MD_String16; -struct MD_String16 -{ - MD_u16 *str; - MD_u64 size; -}; - -typedef struct MD_String32 MD_String32; -struct MD_String32 -{ - MD_u32 *str; - MD_u64 size; -}; - -typedef struct MD_String8Node MD_String8Node; -struct MD_String8Node -{ - MD_String8Node *next; - MD_String8 string; -}; - -typedef struct MD_String8List MD_String8List; -struct MD_String8List -{ - MD_u64 node_count; - MD_u64 total_size; - MD_String8Node *first; - MD_String8Node *last; -}; - -typedef struct MD_StringJoin MD_StringJoin; -struct MD_StringJoin -{ - MD_String8 pre; - MD_String8 mid; - MD_String8 post; -}; - -// NOTE(rjf): @maintenance These three flag types must not overlap. -typedef MD_u32 MD_MatchFlags; -typedef MD_u32 MD_StringMatchFlags; -typedef MD_u32 MD_NodeMatchFlags; -enum -{ - MD_MatchFlag_FindLast = (1<<0), -}; -enum -{ - MD_StringMatchFlag_CaseInsensitive = (1<<4), - MD_StringMatchFlag_RightSideSloppy = (1<<5), - MD_StringMatchFlag_SlashInsensitive = (1<<6), -}; -enum -{ - MD_NodeMatchFlag_Tags = (1<<16), - MD_NodeMatchFlag_TagArguments = (1<<17), - MD_NodeMatchFlag_NodeFlags = (1<<18), -}; - -typedef struct MD_DecodedCodepoint MD_DecodedCodepoint; -struct MD_DecodedCodepoint -{ - MD_u32 codepoint; - MD_u32 advance; -}; - -typedef enum MD_IdentifierStyle -{ - MD_IdentifierStyle_UpperCamelCase, - MD_IdentifierStyle_LowerCamelCase, - MD_IdentifierStyle_UpperCase, - MD_IdentifierStyle_LowerCase, -} -MD_IdentifierStyle; - -//~ Node types that are used to build all ASTs. - -typedef enum MD_NodeKind -{ - // NOTE(rjf): @maintenance Must be kept in sync with MD_StringFromNodeKind. - - MD_NodeKind_Nil, - - // NOTE(rjf): Generated by parser - MD_NodeKind_File, - MD_NodeKind_ErrorMarker, - - // NOTE(rjf): Parsed from user Metadesk code - MD_NodeKind_Main, - MD_NodeKind_Tag, - - // NOTE(rjf): User-created data structures - MD_NodeKind_List, - MD_NodeKind_Reference, - - MD_NodeKind_COUNT, -} -MD_NodeKind; - -typedef MD_u64 MD_NodeFlags; -#define MD_NodeFlag_AfterFromBefore(f) ((f) << 1) -enum -{ - // NOTE(rjf): @maintenance Must be kept in sync with MD_StringListFromNodeFlags. - - // NOTE(rjf): @maintenance Because of MD_NodeFlag_AfterFromBefore, it is - // *required* that every single pair of "Before*" or "After*" flags be in - // the correct order which is that the Before* flag comes first, and the - // After* flag comes immediately after (After* being the more significant - // bit). - - MD_NodeFlag_HasParenLeft = (1<<0), - MD_NodeFlag_HasParenRight = (1<<1), - MD_NodeFlag_HasBracketLeft = (1<<2), - MD_NodeFlag_HasBracketRight = (1<<3), - MD_NodeFlag_HasBraceLeft = (1<<4), - MD_NodeFlag_HasBraceRight = (1<<5), - - MD_NodeFlag_MaskSetDelimiters = (0x3F<<0), - - MD_NodeFlag_IsBeforeSemicolon = (1<<6), - MD_NodeFlag_IsAfterSemicolon = (1<<7), - MD_NodeFlag_IsBeforeComma = (1<<8), - MD_NodeFlag_IsAfterComma = (1<<9), - - MD_NodeFlag_MaskSeperators = (0xF<<6), - - MD_NodeFlag_StringSingleQuote = (1<<10), - MD_NodeFlag_StringDoubleQuote = (1<<11), - MD_NodeFlag_StringTick = (1<<12), - MD_NodeFlag_StringTriplet = (1<<13), - - MD_NodeFlag_MaskStringDelimiters = (0xF<<10), - - MD_NodeFlag_Numeric = (1<<14), - MD_NodeFlag_Identifier = (1<<15), - MD_NodeFlag_StringLiteral = (1<<16), - MD_NodeFlag_Symbol = (1<<17), - - MD_NodeFlag_MaskLabelKind = (0xF<<14), -}; - -typedef struct MD_Node MD_Node; -struct MD_Node -{ - // Tree relationship data. - MD_Node *next; - MD_Node *prev; - MD_Node *parent; - MD_Node *first_child; - MD_Node *last_child; - - // Tag list. - MD_Node *first_tag; - MD_Node *last_tag; - - // Node info. - MD_NodeKind kind; - MD_NodeFlags flags; - MD_String8 string; - MD_String8 raw_string; - - // Source code location information. - MD_u64 offset; - - // Reference. - MD_Node *ref_target; - - // Comments. - // @usage prev_comment/next_comment should be considered "hidden". Rely on - // the functions MD_PrevCommentFromNode/MD_NextCommentFromNode to access - // these. Directly access to these is likely to break in a future version. - MD_String8 prev_comment; - MD_String8 next_comment; -}; - -//~ Code Location Info. - -typedef struct MD_CodeLoc MD_CodeLoc; -struct MD_CodeLoc -{ - MD_String8 filename; - MD_u32 line; - MD_u32 column; -}; - -//~ String-To-Ptr and Ptr-To-Ptr tables - -typedef struct MD_MapKey MD_MapKey; -struct MD_MapKey -{ - MD_u64 hash; - MD_u64 size; - void *ptr; -}; - -typedef struct MD_MapSlot MD_MapSlot; -struct MD_MapSlot -{ - MD_MapSlot *next; - MD_MapKey key; - void *val; -}; - -typedef struct MD_MapBucket MD_MapBucket; -struct MD_MapBucket -{ - MD_MapSlot *first; - MD_MapSlot *last; -}; - -typedef struct MD_Map MD_Map; -struct MD_Map -{ - MD_MapBucket *buckets; - MD_u64 bucket_count; -}; - -//~ Tokens - -typedef MD_u32 MD_TokenKind; -enum -{ - MD_TokenKind_Identifier = (1<<0), - MD_TokenKind_Numeric = (1<<1), - MD_TokenKind_StringLiteral = (1<<2), - MD_TokenKind_Symbol = (1<<3), - MD_TokenKind_Reserved = (1<<4), - MD_TokenKind_Comment = (1<<5), - MD_TokenKind_Whitespace = (1<<6), - MD_TokenKind_Newline = (1<<7), - MD_TokenKind_BrokenComment = (1<<8), - MD_TokenKind_BrokenStringLiteral = (1<<9), - MD_TokenKind_BadCharacter = (1<<10), -}; - -typedef MD_u32 MD_TokenGroups; -enum -{ - MD_TokenGroup_Comment = MD_TokenKind_Comment, - MD_TokenGroup_Whitespace = (MD_TokenKind_Whitespace| - MD_TokenKind_Newline), - MD_TokenGroup_Irregular = (MD_TokenGroup_Comment| - MD_TokenGroup_Whitespace), - MD_TokenGroup_Regular = ~MD_TokenGroup_Irregular, - MD_TokenGroup_Label = (MD_TokenKind_Identifier| - MD_TokenKind_Numeric| - MD_TokenKind_StringLiteral| - MD_TokenKind_Symbol), - MD_TokenGroup_Error = (MD_TokenKind_BrokenComment| - MD_TokenKind_BrokenStringLiteral| - MD_TokenKind_BadCharacter), -}; - -typedef struct MD_Token MD_Token; -struct MD_Token -{ - MD_TokenKind kind; - MD_NodeFlags node_flags; - MD_String8 string; - MD_String8 raw_string; -}; - -//~ Parsing State - -typedef enum MD_MessageKind -{ - // NOTE(rjf): @maintenance This enum needs to be sorted in order of - // severity. - MD_MessageKind_Null, - MD_MessageKind_Note, - MD_MessageKind_Warning, - MD_MessageKind_Error, - MD_MessageKind_FatalError, -} -MD_MessageKind; - -typedef struct MD_Message MD_Message; -struct MD_Message -{ - MD_Message *next; - MD_Node *node; - MD_MessageKind kind; - MD_String8 string; - void *user_ptr; -}; - -typedef struct MD_MessageList MD_MessageList; -struct MD_MessageList -{ - MD_MessageKind max_message_kind; - // TODO(allen): rename - MD_u64 node_count; - MD_Message *first; - MD_Message *last; -}; - -typedef enum MD_ParseSetRule -{ - MD_ParseSetRule_EndOnDelimiter, - MD_ParseSetRule_Global, -} MD_ParseSetRule; - -typedef struct MD_ParseResult MD_ParseResult; -struct MD_ParseResult -{ - MD_Node *node; - MD_u64 string_advance; - MD_MessageList errors; -}; - -//~ Expression Parsing - -typedef enum MD_ExprOprKind -{ - MD_ExprOprKind_Null, - MD_ExprOprKind_Prefix, - MD_ExprOprKind_Postfix, - MD_ExprOprKind_Binary, - MD_ExprOprKind_BinaryRightAssociative, - MD_ExprOprKind_COUNT, -} MD_ExprOprKind; - -typedef struct MD_ExprOpr MD_ExprOpr; -struct MD_ExprOpr -{ - struct MD_ExprOpr *next; - MD_u32 op_id; - MD_ExprOprKind kind; - MD_u32 precedence; - MD_String8 string; - void *op_ptr; -}; - -typedef struct MD_ExprOprList MD_ExprOprList; -struct MD_ExprOprList -{ - MD_ExprOpr *first; - MD_ExprOpr *last; - MD_u64 count; -}; - -typedef struct MD_ExprOprTable MD_ExprOprTable; -struct MD_ExprOprTable -{ - // TODO(mal): @upgrade_potential Hash? - MD_ExprOprList table[MD_ExprOprKind_COUNT]; -}; - -typedef struct MD_Expr MD_Expr; -struct MD_Expr -{ - struct MD_Expr *parent; - union - { - struct MD_Expr *left; - struct MD_Expr *unary_operand; - }; - struct MD_Expr *right; - MD_ExprOpr *op; - MD_Node *md_node; -}; - -typedef struct MD_ExprParseResult MD_ExprParseResult; -struct MD_ExprParseResult -{ - MD_Expr *expr; - MD_MessageList errors; -}; - -// TODO(allen): nil MD_Expr - -typedef struct MD_ExprParseCtx MD_ExprParseCtx; -struct MD_ExprParseCtx -{ - MD_ExprOprTable *op_table; - -#define MD_POSTFIX_SETLIKE_OP_COUNT 5 // (), [], {}, [), (] - struct - { - MD_ExprOpr *postfix_set_ops[MD_POSTFIX_SETLIKE_OP_COUNT]; - MD_NodeFlags postfix_set_flags[MD_POSTFIX_SETLIKE_OP_COUNT]; - } accel; -#undef MD_POSTFIX_SETLIKE_OP_COUNT - - MD_MessageList errors; -}; - -typedef void (*MD_BakeOperatorErrorHandler)(MD_MessageKind kind, MD_String8 s); - -//~ String Generation Types - -typedef MD_u32 MD_GenerateFlags; -enum -{ - MD_GenerateFlag_Tags = (1<<0), - MD_GenerateFlag_TagArguments = (1<<1), - MD_GenerateFlag_Children = (1<<2), - MD_GenerateFlag_Comments = (1<<3), - MD_GenerateFlag_NodeKind = (1<<4), - MD_GenerateFlag_NodeFlags = (1<<5), - MD_GenerateFlag_Location = (1<<6), - - MD_GenerateFlags_Tree = (MD_GenerateFlag_Tags | - MD_GenerateFlag_TagArguments | - MD_GenerateFlag_Children), - MD_GenerateFlags_All = 0xffffffff, -}; - -//~ Command line parsing helper types. - -typedef struct MD_CmdLineOption MD_CmdLineOption; -struct MD_CmdLineOption -{ - MD_CmdLineOption *next; - MD_String8 name; - MD_String8List values; -}; - -typedef struct MD_CmdLine MD_CmdLine; -struct MD_CmdLine -{ - MD_String8List inputs; - MD_CmdLineOption *first_option; - MD_CmdLineOption *last_option; -}; - -//~ File system access types. - -typedef MD_u32 MD_FileFlags; -enum -{ - MD_FileFlag_Directory = (1<<0), -}; - -typedef struct MD_FileInfo MD_FileInfo; -struct MD_FileInfo -{ - MD_FileFlags flags; - MD_String8 filename; - MD_u64 file_size; -}; - -typedef struct MD_FileIter MD_FileIter; -struct MD_FileIter -{ - // This is opaque state to store OS-specific file-system iteration data. - MD_u8 opaque[640]; -}; - -//~///////////////////////////////////////////////////////////////////////////// -////////////////////////////////// Functions /////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -//~ Arena - -MD_FUNCTION MD_Arena* MD_ArenaAlloc(void); -MD_FUNCTION void MD_ArenaRelease(MD_Arena *arena); - -MD_FUNCTION void* MD_ArenaPush(MD_Arena *arena, MD_u64 size); -MD_FUNCTION void MD_ArenaPutBack(MD_Arena *arena, MD_u64 size); -MD_FUNCTION void MD_ArenaSetAlign(MD_Arena *arena, MD_u64 boundary); -MD_FUNCTION void MD_ArenaPushAlign(MD_Arena *arena, MD_u64 boundary); -MD_FUNCTION void MD_ArenaClear(MD_Arena *arena); - -#define MD_PushArray(a,T,c) (T*)(MD_ArenaPush((a), sizeof(T)*(c))) -#define MD_PushArrayZero(a,T,c) (T*)(MD_MemoryZero(MD_PushArray(a,T,c), sizeof(T)*(c))) - -MD_FUNCTION MD_ArenaTemp MD_ArenaBeginTemp(MD_Arena *arena); -MD_FUNCTION void MD_ArenaEndTemp(MD_ArenaTemp temp); - -//~ Arena Scratch Pool - -MD_FUNCTION MD_ArenaTemp MD_GetScratch(MD_Arena **conflicts, MD_u64 count); - -#define MD_ReleaseScratch(scratch) MD_ArenaEndTemp(scratch) - -//~ Characters - -MD_FUNCTION MD_b32 MD_CharIsAlpha(MD_u8 c); -MD_FUNCTION MD_b32 MD_CharIsAlphaUpper(MD_u8 c); -MD_FUNCTION MD_b32 MD_CharIsAlphaLower(MD_u8 c); -MD_FUNCTION MD_b32 MD_CharIsDigit(MD_u8 c); -MD_FUNCTION MD_b32 MD_CharIsUnreservedSymbol(MD_u8 c); -MD_FUNCTION MD_b32 MD_CharIsReservedSymbol(MD_u8 c); -MD_FUNCTION MD_b32 MD_CharIsSpace(MD_u8 c); -MD_FUNCTION MD_u8 MD_CharToUpper(MD_u8 c); -MD_FUNCTION MD_u8 MD_CharToLower(MD_u8 c); -MD_FUNCTION MD_u8 MD_CharToForwardSlash(MD_u8 c); - -//~ Strings - -MD_FUNCTION MD_u64 MD_CalculateCStringLength(char *cstr); - -MD_FUNCTION MD_String8 MD_S8(MD_u8 *str, MD_u64 size); -#define MD_S8CString(s) MD_S8((MD_u8 *)(s), MD_CalculateCStringLength(s)) - -#if MD_LANG_C -# define MD_S8Lit(s) (MD_String8){(MD_u8 *)(s), sizeof(s)-1} -#elif MD_LANG_CPP -# define MD_S8Lit(s) MD_S8((MD_u8*)(s), sizeof(s) - 1) -#endif -#define MD_S8LitComp(s) {(MD_u8 *)(s), sizeof(s)-1} - -#if MD_CPP_VERSION != 0 && MD_CPP_VERSION != 98 // anything C++11 and up -static inline MD_String8 -operator "" _md(const char *s, size_t size) -{ - MD_String8 str = MD_S8((MD_u8 *)s, (MD_u64)size); - return str; -} -#endif - -MD_FUNCTION MD_String8 MD_S8Range(MD_u8 *first, MD_u8 *opl); - -MD_FUNCTION MD_String8 MD_S8Substring(MD_String8 str, MD_u64 min, MD_u64 max); -MD_FUNCTION MD_String8 MD_S8Skip(MD_String8 str, MD_u64 min); -MD_FUNCTION MD_String8 MD_S8Chop(MD_String8 str, MD_u64 nmax); -MD_FUNCTION MD_String8 MD_S8Prefix(MD_String8 str, MD_u64 size); -MD_FUNCTION MD_String8 MD_S8Suffix(MD_String8 str, MD_u64 size); - -MD_FUNCTION MD_b32 MD_S8Match(MD_String8 a, MD_String8 b, MD_MatchFlags flags); -MD_FUNCTION MD_u64 MD_S8FindSubstring(MD_String8 str, MD_String8 substring, - MD_u64 start_pos, MD_MatchFlags flags); - -MD_FUNCTION MD_String8 MD_S8Copy(MD_Arena *arena, MD_String8 string); -MD_FUNCTION MD_String8 MD_S8FmtV(MD_Arena *arena, char *fmt, va_list args); - -MD_FUNCTION MD_String8 MD_S8Fmt(MD_Arena *arena, char *fmt, ...); - -#define MD_S8VArg(s) (int)(s).size, (s).str - -MD_FUNCTION void MD_S8ListPush(MD_Arena *arena, MD_String8List *list, - MD_String8 string); -MD_FUNCTION void MD_S8ListPushFmt(MD_Arena *arena, MD_String8List *list, - char *fmt, ...); - -MD_FUNCTION void MD_S8ListConcat(MD_String8List *list, MD_String8List *to_push); -MD_FUNCTION MD_String8List MD_S8Split(MD_Arena *arena, MD_String8 string, int splitter_count, - MD_String8 *splitters); -MD_FUNCTION MD_String8 MD_S8ListJoin(MD_Arena *arena, MD_String8List list, - MD_StringJoin *join); -MD_FUNCTION MD_String8 MD_S8ListJoinMid(MD_Arena *arena, MD_String8List list, - MD_String8 mid_separator); - -MD_FUNCTION MD_String8 MD_S8Stylize(MD_Arena *arena, MD_String8 string, - MD_IdentifierStyle style, MD_String8 separator); - -//~ Unicode Conversions - -MD_FUNCTION MD_DecodedCodepoint MD_DecodeCodepointFromUtf8(MD_u8 *str, MD_u64 max); -MD_FUNCTION MD_DecodedCodepoint MD_DecodeCodepointFromUtf16(MD_u16 *str, MD_u64 max); -MD_FUNCTION MD_u32 MD_Utf8FromCodepoint(MD_u8 *out, MD_u32 codepoint); -MD_FUNCTION MD_u32 MD_Utf16FromCodepoint(MD_u16 *out, MD_u32 codepoint); -MD_FUNCTION MD_String8 MD_S8FromS16(MD_Arena *arena, MD_String16 str); -MD_FUNCTION MD_String16 MD_S16FromS8(MD_Arena *arena, MD_String8 str); -MD_FUNCTION MD_String8 MD_S8FromS32(MD_Arena *arena, MD_String32 str); -MD_FUNCTION MD_String32 MD_S32FromS8(MD_Arena *arena, MD_String8 str); - -//~ String Skipping/Chopping Helpers - -// This is intended for removing extensions. -MD_FUNCTION MD_String8 MD_PathChopLastPeriod(MD_String8 string); - -// This is intended for removing everything but the filename. -MD_FUNCTION MD_String8 MD_PathSkipLastSlash(MD_String8 string); - -// This is intended for getting an extension from a filename. -MD_FUNCTION MD_String8 MD_PathSkipLastPeriod(MD_String8 string); - -// This is intended for getting the folder string from a full path. -MD_FUNCTION MD_String8 MD_PathChopLastSlash(MD_String8 string); - -MD_FUNCTION MD_String8 MD_S8SkipWhitespace(MD_String8 string); -MD_FUNCTION MD_String8 MD_S8ChopWhitespace(MD_String8 string); - -//~ Numeric Strings - -MD_FUNCTION MD_b32 MD_StringIsU64(MD_String8 string, MD_u32 radix); -MD_FUNCTION MD_b32 MD_StringIsCStyleInt(MD_String8 string); - -MD_FUNCTION MD_u64 MD_U64FromString(MD_String8 string, MD_u32 radix); -MD_FUNCTION MD_i64 MD_CStyleIntFromString(MD_String8 string); -MD_FUNCTION MD_f64 MD_F64FromString(MD_String8 string); - -MD_FUNCTION MD_String8 MD_CStyleHexStringFromU64(MD_Arena *arena, MD_u64 x, MD_b32 caps); - -//~ Enum/Flag Strings - -MD_FUNCTION MD_String8 MD_StringFromNodeKind(MD_NodeKind kind); -MD_FUNCTION MD_String8List MD_StringListFromNodeFlags(MD_Arena *arena, MD_NodeFlags flags); - -//~ Map Table Data Structure - -MD_FUNCTION MD_u64 MD_HashStr(MD_String8 string); -MD_FUNCTION MD_u64 MD_HashPtr(void *p); - -MD_FUNCTION MD_Map MD_MapMakeBucketCount(MD_Arena *arena, MD_u64 bucket_count); -MD_FUNCTION MD_Map MD_MapMake(MD_Arena *arena); -MD_FUNCTION MD_MapKey MD_MapKeyStr(MD_String8 string); -MD_FUNCTION MD_MapKey MD_MapKeyPtr(void *ptr); -MD_FUNCTION MD_MapSlot* MD_MapLookup(MD_Map *map, MD_MapKey key); -MD_FUNCTION MD_MapSlot* MD_MapScan(MD_MapSlot *first_slot, MD_MapKey key); -MD_FUNCTION MD_MapSlot* MD_MapInsert(MD_Arena *arena, MD_Map *map, MD_MapKey key, void *val); -MD_FUNCTION MD_MapSlot* MD_MapOverwrite(MD_Arena *arena, MD_Map *map, MD_MapKey key, - void *val); - -//~ Parsing - -MD_FUNCTION MD_Token MD_TokenFromString(MD_String8 string); -MD_FUNCTION MD_u64 MD_LexAdvanceFromSkips(MD_String8 string, MD_TokenKind skip_kinds); -MD_FUNCTION MD_ParseResult MD_ParseResultZero(void); -MD_FUNCTION MD_ParseResult MD_ParseNodeSet(MD_Arena *arena, MD_String8 string, MD_u64 offset, MD_Node *parent, - MD_ParseSetRule rule); -MD_FUNCTION MD_ParseResult MD_ParseOneNode(MD_Arena *arena, MD_String8 string, MD_u64 offset); -MD_FUNCTION MD_ParseResult MD_ParseWholeString(MD_Arena *arena, MD_String8 filename, MD_String8 contents); - -MD_FUNCTION MD_ParseResult MD_ParseWholeFile(MD_Arena *arena, MD_String8 filename); - -//~ Messages (Errors/Warnings) - -MD_FUNCTION MD_Node* MD_MakeErrorMarkerNode(MD_Arena *arena, MD_String8 parse_contents, - MD_u64 offset); - -MD_FUNCTION MD_Message*MD_MakeNodeError(MD_Arena *arena, MD_Node *node, - MD_MessageKind kind, MD_String8 str); -MD_FUNCTION MD_Message*MD_MakeTokenError(MD_Arena *arena, MD_String8 parse_contents, - MD_Token token, MD_MessageKind kind, - MD_String8 str); - -MD_FUNCTION void MD_MessageListPush(MD_MessageList *list, MD_Message *message); -MD_FUNCTION void MD_MessageListConcat(MD_MessageList *list, MD_MessageList *to_push); - -//~ Location Conversion - -MD_FUNCTION MD_CodeLoc MD_CodeLocFromFileOffset(MD_String8 filename, MD_u8 *base, MD_u64 offset); -MD_FUNCTION MD_CodeLoc MD_CodeLocFromNode(MD_Node *node); - -//~ Tree/List Building - -MD_FUNCTION MD_b32 MD_NodeIsNil(MD_Node *node); -MD_FUNCTION MD_Node *MD_NilNode(void); -MD_FUNCTION MD_Node *MD_MakeNode(MD_Arena *arena, MD_NodeKind kind, MD_String8 string, - MD_String8 raw_string, MD_u64 offset); -MD_FUNCTION void MD_PushChild(MD_Node *parent, MD_Node *new_child); -MD_FUNCTION void MD_PushTag(MD_Node *node, MD_Node *tag); - -MD_FUNCTION MD_Node *MD_MakeList(MD_Arena *arena); -MD_FUNCTION void MD_ListConcatInPlace(MD_Node *list, MD_Node *to_push); -MD_FUNCTION MD_Node *MD_PushNewReference(MD_Arena *arena, MD_Node *list, MD_Node *target); - -//~ Introspection Helpers - -// These calls are for getting info from nodes, and introspecting -// on trees that are returned to you by the parser. - -MD_FUNCTION MD_Node * MD_FirstNodeWithString(MD_Node *first, MD_String8 string, MD_MatchFlags flags); -MD_FUNCTION MD_Node * MD_NodeAtIndex(MD_Node *first, int n); -MD_FUNCTION MD_Node * MD_FirstNodeWithFlags(MD_Node *first, MD_NodeFlags flags); -MD_FUNCTION int MD_IndexFromNode(MD_Node *node); -MD_FUNCTION MD_Node * MD_RootFromNode(MD_Node *node); -MD_FUNCTION MD_Node * MD_ChildFromString(MD_Node *node, MD_String8 child_string, MD_MatchFlags flags); -MD_FUNCTION MD_Node * MD_TagFromString(MD_Node *node, MD_String8 tag_string, MD_MatchFlags flags); -MD_FUNCTION MD_Node * MD_ChildFromIndex(MD_Node *node, int n); -MD_FUNCTION MD_Node * MD_TagFromIndex(MD_Node *node, int n); -MD_FUNCTION MD_Node * MD_TagArgFromIndex(MD_Node *node, MD_String8 tag_string, MD_MatchFlags flags, int n); -MD_FUNCTION MD_Node * MD_TagArgFromString(MD_Node *node, MD_String8 tag_string, MD_MatchFlags tag_str_flags, MD_String8 arg_string, MD_MatchFlags arg_str_flags); -MD_FUNCTION MD_b32 MD_NodeHasChild(MD_Node *node, MD_String8 string, MD_MatchFlags flags); -MD_FUNCTION MD_b32 MD_NodeHasTag(MD_Node *node, MD_String8 string, MD_MatchFlags flags); -MD_FUNCTION MD_i64 MD_ChildCountFromNode(MD_Node *node); -MD_FUNCTION MD_i64 MD_TagCountFromNode(MD_Node *node); -MD_FUNCTION MD_Node * MD_ResolveNodeFromReference(MD_Node *node); -MD_FUNCTION MD_Node* MD_NodeNextWithLimit(MD_Node *node, MD_Node *opl); - -MD_FUNCTION MD_String8 MD_PrevCommentFromNode(MD_Node *node); -MD_FUNCTION MD_String8 MD_NextCommentFromNode(MD_Node *node); - -// NOTE(rjf): For-Loop Helpers -#define MD_EachNode(it, first) MD_Node *it = (first); !MD_NodeIsNil(it); it = it->next - -//~ Error/Warning Helpers - -MD_FUNCTION MD_String8 MD_StringFromMessageKind(MD_MessageKind kind); - -#define MD_FmtCodeLoc "%.*s:%i:%i:" -#define MD_CodeLocVArg(loc) MD_S8VArg((loc).filename), (loc).line, (loc).column - -MD_FUNCTION MD_String8 MD_FormatMessage(MD_Arena *arena, MD_CodeLoc loc, MD_MessageKind kind, - MD_String8 string); - -#if !MD_DISABLE_PRINT_HELPERS -#include -MD_FUNCTION void MD_PrintMessage(FILE *file, MD_CodeLoc loc, MD_MessageKind kind, - MD_String8 string); -MD_FUNCTION void MD_PrintMessageFmt(FILE *file, MD_CodeLoc code_loc, MD_MessageKind kind, - char *fmt, ...); - -#define MD_PrintGenNoteCComment(f) fprintf((f), "// generated by %s:%d\n", __FILE__, __LINE__) -#endif - -//~ Tree Comparison/Verification - -MD_FUNCTION MD_b32 MD_NodeMatch(MD_Node *a, MD_Node *b, MD_MatchFlags flags); -MD_FUNCTION MD_b32 MD_NodeDeepMatch(MD_Node *a, MD_Node *b, MD_MatchFlags flags); - -//~ Expression Parsing - -MD_FUNCTION void MD_ExprOprPush(MD_Arena *arena, MD_ExprOprList *list, - MD_ExprOprKind kind, MD_u32 precedence, - MD_String8 op_string, - MD_u32 op_id, void *op_ptr); - -MD_FUNCTION MD_ExprOprTable MD_ExprBakeOprTableFromList(MD_Arena *arena, - MD_ExprOprList *list); -MD_FUNCTION MD_ExprOpr* MD_ExprOprFromKindString(MD_ExprOprTable *table, - MD_ExprOprKind kind, MD_String8 s); - -MD_FUNCTION MD_ExprParseResult MD_ExprParse(MD_Arena *arena, MD_ExprOprTable *op_table, - MD_Node *first, MD_Node *one_past_last); - -MD_FUNCTION MD_Expr* MD_Expr_NewLeaf(MD_Arena *arena, MD_Node *node); -MD_FUNCTION MD_Expr* MD_Expr_NewOpr(MD_Arena *arena, MD_ExprOpr *op, MD_Node *op_node, - MD_Expr *left, MD_Expr *right); - -MD_FUNCTION MD_ExprParseCtx MD_ExprParse_MakeContext(MD_ExprOprTable *table); - -MD_FUNCTION MD_Expr* MD_ExprParse_TopLevel(MD_Arena *arena, MD_ExprParseCtx *ctx, - MD_Node *first, MD_Node *opl); -MD_FUNCTION MD_b32 MD_ExprParse_OprConsume(MD_ExprParseCtx *ctx, - MD_Node **iter, MD_Node *opl, - MD_ExprOprKind kind, - MD_u32 min_precedence, - MD_ExprOpr **op_out); -MD_FUNCTION MD_Expr* MD_ExprParse_Atom(MD_Arena *arena, MD_ExprParseCtx *ctx, - MD_Node **iter, MD_Node *first, MD_Node *opl); -MD_FUNCTION MD_Expr* MD_ExprParse_MinPrecedence(MD_Arena *arena, MD_ExprParseCtx *ctx, - MD_Node **iter, MD_Node *first, MD_Node *opl, - MD_u32 min_precedence); - - -//~ String Generation - -MD_FUNCTION void MD_DebugDumpFromNode(MD_Arena *arena, MD_String8List *out, MD_Node *node, - int indent, MD_String8 indent_string, - MD_GenerateFlags flags); -MD_FUNCTION void MD_ReconstructionFromNode(MD_Arena *arena, MD_String8List *out, MD_Node *node, - int indent, MD_String8 indent_string); - -//~ Command Line Argument Helper - -MD_FUNCTION MD_String8List MD_StringListFromArgCV(MD_Arena *arena, int argument_count, - char **arguments); -MD_FUNCTION MD_CmdLine MD_MakeCmdLineFromOptions(MD_Arena *arena, MD_String8List options); -MD_FUNCTION MD_String8List MD_CmdLineValuesFromString(MD_CmdLine cmdln, MD_String8 name); -MD_FUNCTION MD_b32 MD_CmdLineB32FromString(MD_CmdLine cmdln, MD_String8 name); -MD_FUNCTION MD_i64 MD_CmdLineI64FromString(MD_CmdLine cmdln, MD_String8 name); - -//~ File System - -MD_FUNCTION MD_String8 MD_LoadEntireFile(MD_Arena *arena, MD_String8 filename); -MD_FUNCTION MD_b32 MD_FileIterBegin(MD_FileIter *it, MD_String8 path); -MD_FUNCTION MD_FileInfo MD_FileIterNext(MD_Arena *arena, MD_FileIter *it); -MD_FUNCTION void MD_FileIterEnd(MD_FileIter *it); - -#endif // MD_H - -/* -Copyright 2021 Dion Systems LLC - -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. -*/ diff --git a/Source/External/metadesk/md_stb_sprintf.h b/Source/External/metadesk/md_stb_sprintf.h deleted file mode 100644 index 4758435..0000000 --- a/Source/External/metadesk/md_stb_sprintf.h +++ /dev/null @@ -1,1905 +0,0 @@ -// NOTE(rjf): This library has been modified for Metadesk. - -// stb_sprintf - v1.09 - 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) -// -// 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 __GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) -#if __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 - -#include // for va_list() -#include // size_t, ptrdiff_t - -#ifndef STB_SPRINTF_MIN -#define STB_SPRINTF_MIN 512 // how many characters per callback -#endif -typedef char *STBSP_SPRINTFCB(const char *buf, void *user, int len); - -#ifndef STB_SPRINTF_DECORATE -#define STB_SPRINTF_DECORATE(name) stbsp_##name // define this before including if you want to change the names -#endif - -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va); -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsnprintf)(char *buf, int count, char const *fmt, va_list va); -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...); -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...); - -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va); -STBSP__PUBLICDEF void STB_SPRINTF_DECORATE(set_separators)(char comma, char period); - -#endif // STB_SPRINTF_H_INCLUDE - -#ifdef STB_SPRINTF_IMPLEMENTATION - -#include // for va_arg() - -#define stbsp__uint32 unsigned int -#define stbsp__int32 signed int - -#ifdef _MSC_VER -#define stbsp__uint64 unsigned __int64 -#define stbsp__int64 signed __int64 -#else -#define stbsp__uint64 unsigned long long -#define stbsp__int64 signed long long -#endif -#define stbsp__uint16 unsigned short - -#ifndef stbsp__uintptr -#if defined(__ppc64__) || defined(__powerpc64__) || defined(__aarch64__) || defined(_M_X64) || defined(__x86_64__) || defined(__x86_64) -#define stbsp__uintptr stbsp__uint64 -#else -#define stbsp__uintptr stbsp__uint32 -#endif -#endif - -#ifndef STB_SPRINTF_MSVC_MODE // used for MSVC2013 and earlier (MSVC2015 matches GCC) -#if defined(_MSC_VER) && (_MSC_VER < 1900) -#define STB_SPRINTF_MSVC_MODE -#endif -#endif - -#ifdef STB_SPRINTF_NOUNALIGNED // define this before inclusion to force stbsp_sprintf to always use aligned accesses -#define STBSP__UNALIGNED(code) -#else -#define STBSP__UNALIGNED(code) code -#endif - -#ifndef STB_SPRINTF_NOFLOAT -// internal float utility functions -static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, char *out, stbsp__int32 *decimal_pos, double value, stbsp__uint32 frac_digits); -static stbsp__int32 stbsp__real_to_parts(stbsp__int64 *bits, stbsp__int32 *expo, double value); -#define STBSP__SPECIAL 0x7000 -#endif - -static char stbsp__period = '.'; -static char stbsp__comma = ','; -static struct -{ - short temp; // force next field to be 2-byte aligned - char pair[201]; -} stbsp__digitpair = -{ - 0, - "00010203040506070809101112131415161718192021222324" - "25262728293031323334353637383940414243444546474849" - "50515253545556575859606162636465666768697071727374" - "75767778798081828384858687888990919293949596979899" -}; - -STBSP__PUBLICDEF void STB_SPRINTF_DECORATE(set_separators)(char pcomma, char pperiod) -{ - stbsp__period = pperiod; - stbsp__comma = pcomma; -} - -#define STBSP__LEFTJUST 1 -#define STBSP__LEADINGPLUS 2 -#define STBSP__LEADINGSPACE 4 -#define STBSP__LEADING_0X 8 -#define STBSP__LEADINGZERO 16 -#define STBSP__INTMAX 32 -#define STBSP__TRIPLET_COMMA 64 -#define STBSP__NEGATIVE 128 -#define STBSP__METRIC_SUFFIX 256 -#define STBSP__HALFWIDTH 512 -#define STBSP__METRIC_NOSPACE 1024 -#define STBSP__METRIC_1024 2048 -#define STBSP__METRIC_JEDEC 4096 - -static void stbsp__lead_sign(stbsp__uint32 fl, char *sign) -{ - sign[0] = 0; - if (fl & STBSP__NEGATIVE) { - sign[0] = 1; - sign[1] = '-'; - } else if (fl & STBSP__LEADINGSPACE) { - sign[0] = 1; - sign[1] = ' '; - } else if (fl & STBSP__LEADINGPLUS) { - sign[0] = 1; - sign[1] = '+'; - } -} - -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va) -{ - static char hex[] = "0123456789abcdefxp"; - static char hexu[] = "0123456789ABCDEFXP"; - char *bf; - char const *f; - int tlen = 0; - - bf = buf; - f = fmt; - for (;;) { - stbsp__int32 fw, pr, tz; - stbsp__uint32 fl; - - // macros for the callback buffer stuff -#define stbsp__chk_cb_bufL(bytes) \ -{ \ -int len = (int)(bf - buf); \ -if ((len + (bytes)) >= STB_SPRINTF_MIN) { \ -tlen += len; \ -if (0 == (bf = buf = callback(buf, user, len))) \ -goto done; \ -} \ -} -#define stbsp__chk_cb_buf(bytes) \ -{ \ -if (callback) { \ -stbsp__chk_cb_bufL(bytes); \ -} \ -} -#define stbsp__flush_cb() \ -{ \ -stbsp__chk_cb_bufL(STB_SPRINTF_MIN - 1); \ -} // flush if there is even one byte in the buffer -#define stbsp__cb_buf_clamp(cl, v) \ -cl = v; \ -if (callback) { \ -int lg = STB_SPRINTF_MIN - (int)(bf - buf); \ -if (cl > lg) \ -cl = lg; \ -} - - // fast copy everything up to the next % (or end of string) - for (;;) { - while (((stbsp__uintptr)f) & 3) { - schk1: - if (f[0] == '%') - goto scandd; - schk2: - if (f[0] == 0) - goto endfmt; - stbsp__chk_cb_buf(1); - *bf++ = f[0]; - ++f; - } - for (;;) { - // Check if the next 4 bytes contain %(0x25) or end of string. - // Using the 'hasless' trick: - // https://graphics.stanford.edu/~seander/bithacks.html#HasLessInWord - stbsp__uint32 v, c; - v = *(stbsp__uint32 *)f; - c = (~v) & 0x80808080; - if (((v ^ 0x25252525) - 0x01010101) & c) - goto schk1; - if ((v - 0x01010101) & c) - goto schk2; - if (callback) - if ((STB_SPRINTF_MIN - (int)(bf - buf)) < 4) - goto schk1; -#ifdef STB_SPRINTF_NOUNALIGNED - if(((stbsp__uintptr)bf) & 3) { - bf[0] = f[0]; - bf[1] = f[1]; - bf[2] = f[2]; - bf[3] = f[3]; - } else -#endif - { - *(stbsp__uint32 *)bf = v; - } - bf += 4; - f += 4; - } - } - scandd: - - ++f; - - // ok, we have a percent, read the modifiers first - fw = 0; - pr = -1; - fl = 0; - tz = 0; - - // flags - for (;;) { - switch (f[0]) { - // if we have left justify - case '-': - fl |= STBSP__LEFTJUST; - ++f; - continue; - // if we have leading plus - case '+': - fl |= STBSP__LEADINGPLUS; - ++f; - continue; - // if we have leading space - case ' ': - fl |= STBSP__LEADINGSPACE; - ++f; - continue; - // if we have leading 0x - case '#': - fl |= STBSP__LEADING_0X; - ++f; - continue; - // if we have thousand commas - case '\'': - fl |= STBSP__TRIPLET_COMMA; - ++f; - continue; - // if we have kilo marker (none->kilo->kibi->jedec) - case '$': - if (fl & STBSP__METRIC_SUFFIX) { - if (fl & STBSP__METRIC_1024) { - fl |= STBSP__METRIC_JEDEC; - } else { - fl |= STBSP__METRIC_1024; - } - } else { - fl |= STBSP__METRIC_SUFFIX; - } - ++f; - continue; - // if we don't want space between metric suffix and number - case '_': - fl |= STBSP__METRIC_NOSPACE; - ++f; - continue; - // if we have leading zero - case '0': - fl |= STBSP__LEADINGZERO; - ++f; - goto flags_done; - default: goto flags_done; - } - } - flags_done: - - // get the field width - if (f[0] == '*') { - fw = va_arg(va, stbsp__uint32); - ++f; - } else { - while ((f[0] >= '0') && (f[0] <= '9')) { - fw = fw * 10 + f[0] - '0'; - f++; - } - } - // get the precision - if (f[0] == '.') { - ++f; - if (f[0] == '*') { - pr = va_arg(va, stbsp__uint32); - ++f; - } else { - pr = 0; - while ((f[0] >= '0') && (f[0] <= '9')) { - pr = pr * 10 + f[0] - '0'; - f++; - } - } - } - - // handle integer size overrides - switch (f[0]) { - // are we halfwidth? - case 'h': - fl |= STBSP__HALFWIDTH; - ++f; - if (f[0] == 'h') - ++f; // QUARTERWIDTH - break; - // are we 64-bit (unix style) - case 'l': - fl |= ((sizeof(long) == 8) ? STBSP__INTMAX : 0); - ++f; - if (f[0] == 'l') { - fl |= STBSP__INTMAX; - ++f; - } - break; - // are we 64-bit on intmax? (c99) - case 'j': - fl |= (sizeof(size_t) == 8) ? STBSP__INTMAX : 0; - ++f; - break; - // are we 64-bit on size_t or ptrdiff_t? (c99) - case 'z': - fl |= (sizeof(ptrdiff_t) == 8) ? STBSP__INTMAX : 0; - ++f; - break; - case 't': - fl |= (sizeof(ptrdiff_t) == 8) ? STBSP__INTMAX : 0; - ++f; - break; - // are we 64-bit (msft style) - case 'I': - if ((f[1] == '6') && (f[2] == '4')) { - fl |= STBSP__INTMAX; - f += 3; - } else if ((f[1] == '3') && (f[2] == '2')) { - f += 3; - } else { - fl |= ((sizeof(void *) == 8) ? STBSP__INTMAX : 0); - ++f; - } - break; - default: break; - } - - // handle each replacement - switch (f[0]) { -#define STBSP__NUMSZ 512 // big enough for e308 (with commas) or e-307 - char num[STBSP__NUMSZ]; - char lead[8]; - char tail[8]; - char *s; - char const *h; - stbsp__uint32 l, n, cs; - stbsp__uint64 n64; -#ifndef STB_SPRINTF_NOFLOAT - double fv; -#endif - stbsp__int32 dp; - char const *sn; - - case 's': - // get the string - s = va_arg(va, char *); - if (s == 0) - s = (char *)"null"; - // get the length - sn = s; - for (;;) { - if ((((stbsp__uintptr)sn) & 3) == 0) - break; - lchk: - if (sn[0] == 0) - goto ld; - ++sn; - } - n = 0xffffffff; - if (pr >= 0) { - n = (stbsp__uint32)(sn - s); - if (n >= (stbsp__uint32)pr) - goto ld; - n = ((stbsp__uint32)(pr - n)) >> 2; - } - while (n) { - stbsp__uint32 v = *(stbsp__uint32 *)sn; - if ((v - 0x01010101) & (~v) & 0x80808080UL) - goto lchk; - sn += 4; - --n; - } - goto lchk; - ld: - - l = (stbsp__uint32)(sn - s); - // clamp to precision - if (l > (stbsp__uint32)pr) - l = pr; - lead[0] = 0; - tail[0] = 0; - pr = 0; - dp = 0; - cs = 0; - // copy the string in - goto scopy; - - //~ rjf: METADESK ADDITION: %S for MD_String8's - - case 'S': // MD_String8 - { - //- rjf: pull out string - MD_String8 str = va_arg(va, MD_String8); - - //- rjf: get string length - s = (char *)str.str; - sn = (const char *)(str.str + str.size); - l = (int)str.size; - - //- rjf: clamp to precision - lead[0] = 0; - tail[0] = 0; - pr = 0; - dp = 0; - cs = 0; - - goto scopy; - }break; - - //~ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - case 'c': // char - // get the character - s = num + STBSP__NUMSZ - 1; - *s = (char)va_arg(va, int); - l = 1; - lead[0] = 0; - tail[0] = 0; - pr = 0; - dp = 0; - cs = 0; - goto scopy; - - case 'n': // weird write-bytes specifier - { - int *d = va_arg(va, int *); - *d = tlen + (int)(bf - buf); - } break; - -#ifdef STB_SPRINTF_NOFLOAT - case 'A': // float - case 'a': // hex float - case 'G': // float - case 'g': // float - case 'E': // float - case 'e': // float - case 'f': // float - va_arg(va, double); // eat it - s = (char *)"No float"; - l = 8; - lead[0] = 0; - tail[0] = 0; - pr = 0; - dp = 0; - cs = 0; - goto scopy; -#else - case 'A': // hex float - case 'a': // hex float - h = (f[0] == 'A') ? hexu : hex; - fv = va_arg(va, double); - if (pr == -1) - pr = 6; // default is 6 - // read the double into a string - if (stbsp__real_to_parts((stbsp__int64 *)&n64, &dp, fv)) - fl |= STBSP__NEGATIVE; - - s = num + 64; - - stbsp__lead_sign(fl, lead); - - if (dp == -1023) - dp = (n64) ? -1022 : 0; - else - n64 |= (((stbsp__uint64)1) << 52); - n64 <<= (64 - 56); - if (pr < 15) - n64 += ((((stbsp__uint64)8) << 56) >> (pr * 4)); - // add leading chars - -#ifdef STB_SPRINTF_MSVC_MODE - *s++ = '0'; - *s++ = 'x'; -#else - lead[1 + lead[0]] = '0'; - lead[2 + lead[0]] = 'x'; - lead[0] += 2; -#endif - *s++ = h[(n64 >> 60) & 15]; - n64 <<= 4; - if (pr) - *s++ = stbsp__period; - sn = s; - - // print the bits - n = pr; - if (n > 13) - n = 13; - if (pr > (stbsp__int32)n) - tz = pr - n; - pr = 0; - while (n--) { - *s++ = h[(n64 >> 60) & 15]; - n64 <<= 4; - } - - // print the expo - tail[1] = h[17]; - if (dp < 0) { - tail[2] = '-'; - dp = -dp; - } else - tail[2] = '+'; - n = (dp >= 1000) ? 6 : ((dp >= 100) ? 5 : ((dp >= 10) ? 4 : 3)); - tail[0] = (char)n; - for (;;) { - tail[n] = '0' + dp % 10; - if (n <= 3) - break; - --n; - dp /= 10; - } - - dp = (int)(s - sn); - l = (int)(s - (num + 64)); - s = num + 64; - cs = 1 + (3 << 24); - goto scopy; - - case 'G': // float - case 'g': // float - h = (f[0] == 'G') ? hexu : hex; - fv = va_arg(va, double); - if (pr == -1) - pr = 6; - else if (pr == 0) - pr = 1; // default is 6 - // read the double into a string - if (stbsp__real_to_str(&sn, &l, num, &dp, fv, (pr - 1) | 0x80000000)) - fl |= STBSP__NEGATIVE; - - // clamp the precision and delete extra zeros after clamp - n = pr; - if (l > (stbsp__uint32)pr) - l = pr; - while ((l > 1) && (pr) && (sn[l - 1] == '0')) { - --pr; - --l; - } - - // should we use %e - if ((dp <= -4) || (dp > (stbsp__int32)n)) { - if (pr > (stbsp__int32)l) - pr = l - 1; - else if (pr) - --pr; // when using %e, there is one digit before the decimal - goto doexpfromg; - } - // this is the insane action to get the pr to match %g semantics for %f - if (dp > 0) { - pr = (dp < (stbsp__int32)l) ? l - dp : 0; - } else { - pr = -dp + ((pr > (stbsp__int32)l) ? (stbsp__int32) l : pr); - } - goto dofloatfromg; - - case 'E': // float - case 'e': // float - h = (f[0] == 'E') ? hexu : hex; - fv = va_arg(va, double); - if (pr == -1) - pr = 6; // default is 6 - // read the double into a string - if (stbsp__real_to_str(&sn, &l, num, &dp, fv, pr | 0x80000000)) - fl |= STBSP__NEGATIVE; - doexpfromg: - tail[0] = 0; - stbsp__lead_sign(fl, lead); - if (dp == STBSP__SPECIAL) { - s = (char *)sn; - cs = 0; - pr = 0; - goto scopy; - } - s = num + 64; - // handle leading chars - *s++ = sn[0]; - - if (pr) - *s++ = stbsp__period; - - // handle after decimal - if ((l - 1) > (stbsp__uint32)pr) - l = pr + 1; - for (n = 1; n < l; n++) - *s++ = sn[n]; - // trailing zeros - tz = pr - (l - 1); - pr = 0; - // dump expo - tail[1] = h[0xe]; - dp -= 1; - if (dp < 0) { - tail[2] = '-'; - dp = -dp; - } else - tail[2] = '+'; -#ifdef STB_SPRINTF_MSVC_MODE - n = 5; -#else - n = (dp >= 100) ? 5 : 4; -#endif - tail[0] = (char)n; - for (;;) { - tail[n] = '0' + dp % 10; - if (n <= 3) - break; - --n; - dp /= 10; - } - cs = 1 + (3 << 24); // how many tens - goto flt_lead; - - case 'f': // float - fv = va_arg(va, double); - doafloat: - // do kilos - if (fl & STBSP__METRIC_SUFFIX) { - double divisor; - divisor = 1000.0f; - if (fl & STBSP__METRIC_1024) - divisor = 1024.0; - while (fl < 0x4000000) { - if ((fv < divisor) && (fv > -divisor)) - break; - fv /= divisor; - fl += 0x1000000; - } - } - if (pr == -1) - pr = 6; // default is 6 - // read the double into a string - if (stbsp__real_to_str(&sn, &l, num, &dp, fv, pr)) - fl |= STBSP__NEGATIVE; - dofloatfromg: - tail[0] = 0; - stbsp__lead_sign(fl, lead); - if (dp == STBSP__SPECIAL) { - s = (char *)sn; - cs = 0; - pr = 0; - goto scopy; - } - s = num + 64; - - // handle the three decimal varieties - if (dp <= 0) { - stbsp__int32 i; - // handle 0.000*000xxxx - *s++ = '0'; - if (pr) - *s++ = stbsp__period; - n = -dp; - if ((stbsp__int32)n > pr) - n = pr; - i = n; - while (i) { - if ((((stbsp__uintptr)s) & 3) == 0) - break; - *s++ = '0'; - --i; - } - while (i >= 4) { - *(stbsp__uint32 *)s = 0x30303030; - s += 4; - i -= 4; - } - while (i) { - *s++ = '0'; - --i; - } - if ((stbsp__int32)(l + n) > pr) - l = pr - n; - i = l; - while (i) { - *s++ = *sn++; - --i; - } - tz = pr - (n + l); - cs = 1 + (3 << 24); // how many tens did we write (for commas below) - } else { - cs = (fl & STBSP__TRIPLET_COMMA) ? ((600 - (stbsp__uint32)dp) % 3) : 0; - if ((stbsp__uint32)dp >= l) { - // handle xxxx000*000.0 - n = 0; - for (;;) { - if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { - cs = 0; - *s++ = stbsp__comma; - } else { - *s++ = sn[n]; - ++n; - if (n >= l) - break; - } - } - if (n < (stbsp__uint32)dp) { - n = dp - n; - if ((fl & STBSP__TRIPLET_COMMA) == 0) { - while (n) { - if ((((stbsp__uintptr)s) & 3) == 0) - break; - *s++ = '0'; - --n; - } - while (n >= 4) { - *(stbsp__uint32 *)s = 0x30303030; - s += 4; - n -= 4; - } - } - while (n) { - if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { - cs = 0; - *s++ = stbsp__comma; - } else { - *s++ = '0'; - --n; - } - } - } - cs = (int)(s - (num + 64)) + (3 << 24); // cs is how many tens - if (pr) { - *s++ = stbsp__period; - tz = pr; - } - } else { - // handle xxxxx.xxxx000*000 - n = 0; - for (;;) { - if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { - cs = 0; - *s++ = stbsp__comma; - } else { - *s++ = sn[n]; - ++n; - if (n >= (stbsp__uint32)dp) - break; - } - } - cs = (int)(s - (num + 64)) + (3 << 24); // cs is how many tens - if (pr) - *s++ = stbsp__period; - if ((l - dp) > (stbsp__uint32)pr) - l = pr + dp; - while (n < l) { - *s++ = sn[n]; - ++n; - } - tz = pr - (l - dp); - } - } - pr = 0; - - // handle k,m,g,t - if (fl & STBSP__METRIC_SUFFIX) { - char idx; - idx = 1; - if (fl & STBSP__METRIC_NOSPACE) - idx = 0; - tail[0] = idx; - tail[1] = ' '; - { - if (fl >> 24) { // SI kilo is 'k', JEDEC and SI kibits are 'K'. - if (fl & STBSP__METRIC_1024) - tail[idx + 1] = "_KMGT"[fl >> 24]; - else - tail[idx + 1] = "_kMGT"[fl >> 24]; - idx++; - // If printing kibits and not in jedec, add the 'i'. - if (fl & STBSP__METRIC_1024 && !(fl & STBSP__METRIC_JEDEC)) { - tail[idx + 1] = 'i'; - idx++; - } - tail[0] = idx; - } - } - }; - - flt_lead: - // get the length that we copied - l = (stbsp__uint32)(s - (num + 64)); - s = num + 64; - goto scopy; -#endif - - case 'B': // upper binary - case 'b': // lower binary - h = (f[0] == 'B') ? hexu : hex; - lead[0] = 0; - if (fl & STBSP__LEADING_0X) { - lead[0] = 2; - lead[1] = '0'; - lead[2] = h[0xb]; - } - l = (8 << 4) | (1 << 8); - goto radixnum; - - case 'o': // octal - h = hexu; - lead[0] = 0; - if (fl & STBSP__LEADING_0X) { - lead[0] = 1; - lead[1] = '0'; - } - l = (3 << 4) | (3 << 8); - goto radixnum; - - case 'p': // pointer - fl |= (sizeof(void *) == 8) ? STBSP__INTMAX : 0; - pr = sizeof(void *) * 2; - fl &= ~STBSP__LEADINGZERO; // 'p' only prints the pointer with zeros - // fall through - to X - - case 'X': // upper hex - case 'x': // lower hex - h = (f[0] == 'X') ? hexu : hex; - l = (4 << 4) | (4 << 8); - lead[0] = 0; - if (fl & STBSP__LEADING_0X) { - lead[0] = 2; - lead[1] = '0'; - lead[2] = h[16]; - } - radixnum: - // get the number - if (fl & STBSP__INTMAX) - n64 = va_arg(va, stbsp__uint64); - else - n64 = va_arg(va, stbsp__uint32); - - s = num + STBSP__NUMSZ; - dp = 0; - // clear tail, and clear leading if value is zero - tail[0] = 0; - if (n64 == 0) { - lead[0] = 0; - if (pr == 0) { - l = 0; - cs = (((l >> 4) & 15)) << 24; - goto scopy; - } - } - // convert to string - for (;;) { - *--s = h[n64 & ((1 << (l >> 8)) - 1)]; - n64 >>= (l >> 8); - if (!((n64) || ((stbsp__int32)((num + STBSP__NUMSZ) - s) < pr))) - break; - if (fl & STBSP__TRIPLET_COMMA) { - ++l; - if ((l & 15) == ((l >> 4) & 15)) { - l &= ~15; - *--s = stbsp__comma; - } - } - }; - // get the tens and the comma pos - cs = (stbsp__uint32)((num + STBSP__NUMSZ) - s) + ((((l >> 4) & 15)) << 24); - // get the length that we copied - l = (stbsp__uint32)((num + STBSP__NUMSZ) - s); - // copy it - goto scopy; - - case 'u': // unsigned - case 'i': - case 'd': // integer - // get the integer and abs it - if (fl & STBSP__INTMAX) { - stbsp__int64 i64 = va_arg(va, stbsp__int64); - n64 = (stbsp__uint64)i64; - if ((f[0] != 'u') && (i64 < 0)) { - n64 = (stbsp__uint64)-i64; - fl |= STBSP__NEGATIVE; - } - } else { - stbsp__int32 i = va_arg(va, stbsp__int32); - n64 = (stbsp__uint32)i; - if ((f[0] != 'u') && (i < 0)) { - n64 = (stbsp__uint32)-i; - fl |= STBSP__NEGATIVE; - } - } - -#ifndef STB_SPRINTF_NOFLOAT - if (fl & STBSP__METRIC_SUFFIX) { - if (n64 < 1024) - pr = 0; - else if (pr == -1) - pr = 1; - fv = (double)(stbsp__int64)n64; - goto doafloat; - } -#endif - - // convert to string - s = num + STBSP__NUMSZ; - l = 0; - - for (;;) { - // do in 32-bit chunks (avoid lots of 64-bit divides even with constant denominators) - char *o = s - 8; - if (n64 >= 100000000) { - n = (stbsp__uint32)(n64 % 100000000); - n64 /= 100000000; - } else { - n = (stbsp__uint32)n64; - n64 = 0; - } - if ((fl & STBSP__TRIPLET_COMMA) == 0) { - do { - s -= 2; - *(stbsp__uint16 *)s = *(stbsp__uint16 *)&stbsp__digitpair.pair[(n % 100) * 2]; - n /= 100; - } while (n); - } - while (n) { - if ((fl & STBSP__TRIPLET_COMMA) && (l++ == 3)) { - l = 0; - *--s = stbsp__comma; - --o; - } else { - *--s = (char)(n % 10) + '0'; - n /= 10; - } - } - if (n64 == 0) { - if ((s[0] == '0') && (s != (num + STBSP__NUMSZ))) - ++s; - break; - } - while (s != o) - if ((fl & STBSP__TRIPLET_COMMA) && (l++ == 3)) { - l = 0; - *--s = stbsp__comma; - --o; - } else { - *--s = '0'; - } - } - - tail[0] = 0; - stbsp__lead_sign(fl, lead); - - // get the length that we copied - l = (stbsp__uint32)((num + STBSP__NUMSZ) - s); - if (l == 0) { - *--s = '0'; - l = 1; - } - cs = l + (3 << 24); - if (pr < 0) - pr = 0; - - scopy: - // get fw=leading/trailing space, pr=leading zeros - if (pr < (stbsp__int32)l) - pr = l; - n = pr + lead[0] + tail[0] + tz; - if (fw < (stbsp__int32)n) - fw = n; - fw -= n; - pr -= l; - - // handle right justify and leading zeros - if ((fl & STBSP__LEFTJUST) == 0) { - if (fl & STBSP__LEADINGZERO) // if leading zeros, everything is in pr - { - pr = (fw > pr) ? fw : pr; - fw = 0; - } else { - fl &= ~STBSP__TRIPLET_COMMA; // if no leading zeros, then no commas - } - } - - // copy the spaces and/or zeros - if (fw + pr) { - stbsp__int32 i; - stbsp__uint32 c; - - // copy leading spaces (or when doing %8.4d stuff) - if ((fl & STBSP__LEFTJUST) == 0) - while (fw > 0) { - stbsp__cb_buf_clamp(i, fw); - fw -= i; - while (i) { - if ((((stbsp__uintptr)bf) & 3) == 0) - break; - *bf++ = ' '; - --i; - } - while (i >= 4) { - *(stbsp__uint32 *)bf = 0x20202020; - bf += 4; - i -= 4; - } - while (i) { - *bf++ = ' '; - --i; - } - stbsp__chk_cb_buf(1); - } - - // copy leader - sn = lead + 1; - while (lead[0]) { - stbsp__cb_buf_clamp(i, lead[0]); - lead[0] -= (char)i; - while (i) { - *bf++ = *sn++; - --i; - } - stbsp__chk_cb_buf(1); - } - - // copy leading zeros - c = cs >> 24; - cs &= 0xffffff; - cs = (fl & STBSP__TRIPLET_COMMA) ? ((stbsp__uint32)(c - ((pr + cs) % (c + 1)))) : 0; - while (pr > 0) { - stbsp__cb_buf_clamp(i, pr); - pr -= i; - if ((fl & STBSP__TRIPLET_COMMA) == 0) { - while (i) { - if ((((stbsp__uintptr)bf) & 3) == 0) - break; - *bf++ = '0'; - --i; - } - while (i >= 4) { - *(stbsp__uint32 *)bf = 0x30303030; - bf += 4; - i -= 4; - } - } - while (i) { - if ((fl & STBSP__TRIPLET_COMMA) && (cs++ == c)) { - cs = 0; - *bf++ = stbsp__comma; - } else - *bf++ = '0'; - --i; - } - stbsp__chk_cb_buf(1); - } - } - - // copy leader if there is still one - sn = lead + 1; - while (lead[0]) { - stbsp__int32 i; - stbsp__cb_buf_clamp(i, lead[0]); - lead[0] -= (char)i; - while (i) { - *bf++ = *sn++; - --i; - } - stbsp__chk_cb_buf(1); - } - - // copy the string - n = l; - while (n) { - stbsp__int32 i; - stbsp__cb_buf_clamp(i, n); - n -= i; - STBSP__UNALIGNED(while (i >= 4) { - *(stbsp__uint32 volatile *)bf = *(stbsp__uint32 volatile *)s; - bf += 4; - s += 4; - i -= 4; - }) - while (i) { - *bf++ = *s++; - --i; - } - stbsp__chk_cb_buf(1); - } - - // copy trailing zeros - while (tz) { - stbsp__int32 i; - stbsp__cb_buf_clamp(i, tz); - tz -= i; - while (i) { - if ((((stbsp__uintptr)bf) & 3) == 0) - break; - *bf++ = '0'; - --i; - } - while (i >= 4) { - *(stbsp__uint32 *)bf = 0x30303030; - bf += 4; - i -= 4; - } - while (i) { - *bf++ = '0'; - --i; - } - stbsp__chk_cb_buf(1); - } - - // copy tail if there is one - sn = tail + 1; - while (tail[0]) { - stbsp__int32 i; - stbsp__cb_buf_clamp(i, tail[0]); - tail[0] -= (char)i; - while (i) { - *bf++ = *sn++; - --i; - } - stbsp__chk_cb_buf(1); - } - - // handle the left justify - if (fl & STBSP__LEFTJUST) - if (fw > 0) { - while (fw) { - stbsp__int32 i; - stbsp__cb_buf_clamp(i, fw); - fw -= i; - while (i) { - if ((((stbsp__uintptr)bf) & 3) == 0) - break; - *bf++ = ' '; - --i; - } - while (i >= 4) { - *(stbsp__uint32 *)bf = 0x20202020; - bf += 4; - i -= 4; - } - while (i--) - *bf++ = ' '; - stbsp__chk_cb_buf(1); - } - } - break; - - default: // unknown, just copy code - s = num + STBSP__NUMSZ - 1; - *s = f[0]; - l = 1; - fw = fl = 0; - lead[0] = 0; - tail[0] = 0; - pr = 0; - dp = 0; - cs = 0; - goto scopy; - } - ++f; - } - endfmt: - - if (!callback) - *bf = 0; - else - stbsp__flush_cb(); - - done: - return tlen + (int)(bf - buf); -} - -// cleanup -#undef STBSP__LEFTJUST -#undef STBSP__LEADINGPLUS -#undef STBSP__LEADINGSPACE -#undef STBSP__LEADING_0X -#undef STBSP__LEADINGZERO -#undef STBSP__INTMAX -#undef STBSP__TRIPLET_COMMA -#undef STBSP__NEGATIVE -#undef STBSP__METRIC_SUFFIX -#undef STBSP__NUMSZ -#undef stbsp__chk_cb_bufL -#undef stbsp__chk_cb_buf -#undef stbsp__flush_cb -#undef stbsp__cb_buf_clamp - -// ============================================================================ -// wrapper functions - -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...) -{ - int result; - va_list va; - va_start(va, fmt); - result = STB_SPRINTF_DECORATE(vsprintfcb)(0, 0, buf, fmt, va); - va_end(va); - return result; -} - -typedef struct stbsp__context { - char *buf; - int count; - int length; - char tmp[STB_SPRINTF_MIN]; -} stbsp__context; - -static char *stbsp__clamp_callback(const char *buf, void *user, int len) -{ - stbsp__context *c = (stbsp__context *)user; - c->length += len; - - if (len > c->count) - len = c->count; - - if (len) { - if (buf != c->buf) { - const char *s, *se; - char *d; - d = c->buf; - s = buf; - se = buf + len; - do { - *d++ = *s++; - } while (s < se); - } - c->buf += len; - c->count -= len; - } - - if (c->count <= 0) - return c->tmp; - return (c->count >= STB_SPRINTF_MIN) ? c->buf : c->tmp; // go direct into buffer if you can -} - -static char * stbsp__count_clamp_callback( const char * buf, void * user, int len ) -{ - stbsp__context * c = (stbsp__context*)user; - (void) sizeof(buf); - - c->length += len; - return c->tmp; // go direct into buffer if you can -} - -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE( vsnprintf )( char * buf, int count, char const * fmt, va_list va ) -{ - stbsp__context c; - - if ( (count == 0) && !buf ) - { - c.length = 0; - - STB_SPRINTF_DECORATE( vsprintfcb )( stbsp__count_clamp_callback, &c, c.tmp, fmt, va ); - } - else - { - int l; - - c.buf = buf; - c.count = count; - c.length = 0; - - STB_SPRINTF_DECORATE( vsprintfcb )( stbsp__clamp_callback, &c, stbsp__clamp_callback(0,&c,0), fmt, va ); - - // zero-terminate - l = (int)( c.buf - buf ); - if ( l >= count ) // should never be greater, only equal (or less) than count - l = count - 1; - buf[l] = 0; - } - - return c.length; -} - -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...) -{ - int result; - va_list va; - va_start(va, fmt); - - result = STB_SPRINTF_DECORATE(vsnprintf)(buf, count, fmt, va); - va_end(va); - - return result; -} - -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va) -{ - return STB_SPRINTF_DECORATE(vsprintfcb)(0, 0, buf, fmt, va); -} - -// ======================================================================= -// low level float utility functions - -#ifndef STB_SPRINTF_NOFLOAT - -// copies d to bits w/ strict aliasing (this compiles to nothing on /Ox) -#define STBSP__COPYFP(dest, src) \ -{ \ -int cn; \ -for (cn = 0; cn < 8; cn++) \ -((char *)&dest)[cn] = ((char *)&src)[cn]; \ -} - -// get float info -static stbsp__int32 stbsp__real_to_parts(stbsp__int64 *bits, stbsp__int32 *expo, double value) -{ - double d; - stbsp__int64 b = 0; - - // load value and round at the frac_digits - d = value; - - STBSP__COPYFP(b, d); - - *bits = b & ((((stbsp__uint64)1) << 52) - 1); - *expo = (stbsp__int32)(((b >> 52) & 2047) - 1023); - - return (stbsp__int32)((stbsp__uint64) b >> 63); -} - -static double const stbsp__bot[23] = { - 1e+000, 1e+001, 1e+002, 1e+003, 1e+004, 1e+005, 1e+006, 1e+007, 1e+008, 1e+009, 1e+010, 1e+011, - 1e+012, 1e+013, 1e+014, 1e+015, 1e+016, 1e+017, 1e+018, 1e+019, 1e+020, 1e+021, 1e+022 -}; -static double const stbsp__negbot[22] = { - 1e-001, 1e-002, 1e-003, 1e-004, 1e-005, 1e-006, 1e-007, 1e-008, 1e-009, 1e-010, 1e-011, - 1e-012, 1e-013, 1e-014, 1e-015, 1e-016, 1e-017, 1e-018, 1e-019, 1e-020, 1e-021, 1e-022 -}; -static double const stbsp__negboterr[22] = { - -5.551115123125783e-018, -2.0816681711721684e-019, -2.0816681711721686e-020, -4.7921736023859299e-021, -8.1803053914031305e-022, 4.5251888174113741e-023, - 4.5251888174113739e-024, -2.0922560830128471e-025, -6.2281591457779853e-026, -3.6432197315497743e-027, 6.0503030718060191e-028, 2.0113352370744385e-029, - -3.0373745563400371e-030, 1.1806906454401013e-032, -7.7705399876661076e-032, 2.0902213275965398e-033, -7.1542424054621921e-034, -7.1542424054621926e-035, - 2.4754073164739869e-036, 5.4846728545790429e-037, 9.2462547772103625e-038, -4.8596774326570872e-039 -}; -static double const stbsp__top[13] = { - 1e+023, 1e+046, 1e+069, 1e+092, 1e+115, 1e+138, 1e+161, 1e+184, 1e+207, 1e+230, 1e+253, 1e+276, 1e+299 -}; -static double const stbsp__negtop[13] = { - 1e-023, 1e-046, 1e-069, 1e-092, 1e-115, 1e-138, 1e-161, 1e-184, 1e-207, 1e-230, 1e-253, 1e-276, 1e-299 -}; -static double const stbsp__toperr[13] = { - 8388608, - 6.8601809640529717e+028, - -7.253143638152921e+052, - -4.3377296974619174e+075, - -1.5559416129466825e+098, - -3.2841562489204913e+121, - -3.7745893248228135e+144, - -1.7356668416969134e+167, - -3.8893577551088374e+190, - -9.9566444326005119e+213, - 6.3641293062232429e+236, - -5.2069140800249813e+259, - -5.2504760255204387e+282 -}; -static double const stbsp__negtoperr[13] = { - 3.9565301985100693e-040, -2.299904345391321e-063, 3.6506201437945798e-086, 1.1875228833981544e-109, - -5.0644902316928607e-132, -6.7156837247865426e-155, -2.812077463003139e-178, -5.7778912386589953e-201, - 7.4997100559334532e-224, -4.6439668915134491e-247, -6.3691100762962136e-270, -9.436808465446358e-293, - 8.0970921678014997e-317 -}; - -#if defined(_MSC_VER) && (_MSC_VER <= 1200) -static stbsp__uint64 const stbsp__powten[20] = { - 1, - 10, - 100, - 1000, - 10000, - 100000, - 1000000, - 10000000, - 100000000, - 1000000000, - 10000000000, - 100000000000, - 1000000000000, - 10000000000000, - 100000000000000, - 1000000000000000, - 10000000000000000, - 100000000000000000, - 1000000000000000000, - 10000000000000000000U -}; -#define stbsp__tento19th ((stbsp__uint64)1000000000000000000) -#else -static stbsp__uint64 const stbsp__powten[20] = { - 1, - 10, - 100, - 1000, - 10000, - 100000, - 1000000, - 10000000, - 100000000, - 1000000000, - 10000000000ULL, - 100000000000ULL, - 1000000000000ULL, - 10000000000000ULL, - 100000000000000ULL, - 1000000000000000ULL, - 10000000000000000ULL, - 100000000000000000ULL, - 1000000000000000000ULL, - 10000000000000000000ULL -}; -#define stbsp__tento19th (1000000000000000000ULL) -#endif - -#define stbsp__ddmulthi(oh, ol, xh, yh) \ -{ \ -double ahi = 0, alo, bhi = 0, blo; \ -stbsp__int64 bt; \ -oh = xh * yh; \ -STBSP__COPYFP(bt, xh); \ -bt &= ((~(stbsp__uint64)0) << 27); \ -STBSP__COPYFP(ahi, bt); \ -alo = xh - ahi; \ -STBSP__COPYFP(bt, yh); \ -bt &= ((~(stbsp__uint64)0) << 27); \ -STBSP__COPYFP(bhi, bt); \ -blo = yh - bhi; \ -ol = ((ahi * bhi - oh) + ahi * blo + alo * bhi) + alo * blo; \ -} - -#define stbsp__ddtoS64(ob, xh, xl) \ -{ \ -double ahi = 0, alo, vh, t; \ -ob = (stbsp__int64)ph; \ -vh = (double)ob; \ -ahi = (xh - vh); \ -t = (ahi - xh); \ -alo = (xh - (ahi - t)) - (vh + t); \ -ob += (stbsp__int64)(ahi + alo + xl); \ -} - -#define stbsp__ddrenorm(oh, ol) \ -{ \ -double s; \ -s = oh + ol; \ -ol = ol - (s - oh); \ -oh = s; \ -} - -#define stbsp__ddmultlo(oh, ol, xh, xl, yh, yl) ol = ol + (xh * yl + xl * yh); - -#define stbsp__ddmultlos(oh, ol, xh, yl) ol = ol + (xh * yl); - -static void stbsp__raise_to_power10(double *ohi, double *olo, double d, stbsp__int32 power) // power can be -323 to +350 -{ - double ph, pl; - if ((power >= 0) && (power <= 22)) { - stbsp__ddmulthi(ph, pl, d, stbsp__bot[power]); - } else { - stbsp__int32 e, et, eb; - double p2h, p2l; - - e = power; - if (power < 0) - e = -e; - et = (e * 0x2c9) >> 14; /* %23 */ - if (et > 13) - et = 13; - eb = e - (et * 23); - - ph = d; - pl = 0.0; - if (power < 0) { - if (eb) { - --eb; - stbsp__ddmulthi(ph, pl, d, stbsp__negbot[eb]); - stbsp__ddmultlos(ph, pl, d, stbsp__negboterr[eb]); - } - if (et) { - stbsp__ddrenorm(ph, pl); - --et; - stbsp__ddmulthi(p2h, p2l, ph, stbsp__negtop[et]); - stbsp__ddmultlo(p2h, p2l, ph, pl, stbsp__negtop[et], stbsp__negtoperr[et]); - ph = p2h; - pl = p2l; - } - } else { - if (eb) { - e = eb; - if (eb > 22) - eb = 22; - e -= eb; - stbsp__ddmulthi(ph, pl, d, stbsp__bot[eb]); - if (e) { - stbsp__ddrenorm(ph, pl); - stbsp__ddmulthi(p2h, p2l, ph, stbsp__bot[e]); - stbsp__ddmultlos(p2h, p2l, stbsp__bot[e], pl); - ph = p2h; - pl = p2l; - } - } - if (et) { - stbsp__ddrenorm(ph, pl); - --et; - stbsp__ddmulthi(p2h, p2l, ph, stbsp__top[et]); - stbsp__ddmultlo(p2h, p2l, ph, pl, stbsp__top[et], stbsp__toperr[et]); - ph = p2h; - pl = p2l; - } - } - } - stbsp__ddrenorm(ph, pl); - *ohi = ph; - *olo = pl; -} - -// given a float value, returns the significant bits in bits, and the position of the -// decimal point in decimal_pos. +/-INF and NAN are specified by special values -// returned in the decimal_pos parameter. -// frac_digits is absolute normally, but if you want from first significant digits (got %g and %e), or in 0x80000000 -static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, char *out, stbsp__int32 *decimal_pos, double value, stbsp__uint32 frac_digits) -{ - double d; - stbsp__int64 bits = 0; - stbsp__int32 expo, e, ng, tens; - - d = value; - STBSP__COPYFP(bits, d); - expo = (stbsp__int32)((bits >> 52) & 2047); - ng = (stbsp__int32)((stbsp__uint64) bits >> 63); - if (ng) - d = -d; - - if (expo == 2047) // is nan or inf? - { - *start = (bits & ((((stbsp__uint64)1) << 52) - 1)) ? "NaN" : "Inf"; - *decimal_pos = STBSP__SPECIAL; - *len = 3; - return ng; - } - - if (expo == 0) // is zero or denormal - { - if (((stbsp__uint64) bits << 1) == 0) // do zero - { - *decimal_pos = 1; - *start = out; - out[0] = '0'; - *len = 1; - return ng; - } - // find the right expo for denormals - { - stbsp__int64 v = ((stbsp__uint64)1) << 51; - while ((bits & v) == 0) { - --expo; - v >>= 1; - } - } - } - - // find the decimal exponent as well as the decimal bits of the value - { - double ph, pl; - - // log10 estimate - very specifically tweaked to hit or undershoot by no more than 1 of log10 of all expos 1..2046 - tens = expo - 1023; - tens = (tens < 0) ? ((tens * 617) / 2048) : (((tens * 1233) / 4096) + 1); - - // move the significant bits into position and stick them into an int - stbsp__raise_to_power10(&ph, &pl, d, 18 - tens); - - // get full as much precision from double-double as possible - stbsp__ddtoS64(bits, ph, pl); - - // check if we undershot - if (((stbsp__uint64)bits) >= stbsp__tento19th) - ++tens; - } - - // now do the rounding in integer land - frac_digits = (frac_digits & 0x80000000) ? ((frac_digits & 0x7ffffff) + 1) : (tens + frac_digits); - if ((frac_digits < 24)) { - stbsp__uint32 dg = 1; - if ((stbsp__uint64)bits >= stbsp__powten[9]) - dg = 10; - while ((stbsp__uint64)bits >= stbsp__powten[dg]) { - ++dg; - if (dg == 20) - goto noround; - } - if (frac_digits < dg) { - stbsp__uint64 r; - // add 0.5 at the right position and round - e = dg - frac_digits; - if ((stbsp__uint32)e >= 24) - goto noround; - r = stbsp__powten[e]; - bits = bits + (r / 2); - if ((stbsp__uint64)bits >= stbsp__powten[dg]) - ++tens; - bits /= r; - } - noround:; - } - - // kill long trailing runs of zeros - if (bits) { - stbsp__uint32 n; - for (;;) { - if (bits <= 0xffffffff) - break; - if (bits % 1000) - goto donez; - bits /= 1000; - } - n = (stbsp__uint32)bits; - while ((n % 1000) == 0) - n /= 1000; - bits = n; - donez:; - } - - // convert to string - out += 64; - e = 0; - for (;;) { - stbsp__uint32 n; - char *o = out - 8; - // do the conversion in chunks of U32s (avoid most 64-bit divides, worth it, constant denomiators be damned) - if (bits >= 100000000) { - n = (stbsp__uint32)(bits % 100000000); - bits /= 100000000; - } else { - n = (stbsp__uint32)bits; - bits = 0; - } - while (n) { - out -= 2; - *(stbsp__uint16 *)out = *(stbsp__uint16 *)&stbsp__digitpair.pair[(n % 100) * 2]; - n /= 100; - e += 2; - } - if (bits == 0) { - if ((e) && (out[0] == '0')) { - ++out; - --e; - } - break; - } - while (out != o) { - *--out = '0'; - ++e; - } - } - - *decimal_pos = tens; - *start = out; - *len = e; - return ng; -} - -#undef stbsp__ddmulthi -#undef stbsp__ddrenorm -#undef stbsp__ddmultlo -#undef stbsp__ddmultlos -#undef STBSP__SPECIAL -#undef STBSP__COPYFP - -#endif // STB_SPRINTF_NOFLOAT - -// clean up -#undef stbsp__uint16 -#undef stbsp__uint32 -#undef stbsp__int32 -#undef stbsp__uint64 -#undef stbsp__int64 -#undef STBSP__UNALIGNED - -#endif // STB_SPRINTF_IMPLEMENTATION - -/* ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright (c) 2017 Sean Barrett -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain (www.unlicense.org) -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------- -*/ diff --git a/Source/Extra/dn_net.cpp b/Source/Extra/dn_net.cpp deleted file mode 100644 index 9fed954..0000000 --- a/Source/Extra/dn_net.cpp +++ /dev/null @@ -1,130 +0,0 @@ -#define DN_NET_CURL_CPP - -#if defined(_CLANGD) - #define DN_H_WITH_OS 1 - #include "../dn.h" - #include "dn_net.h" -#endif - -DN_Str8 DN_NET_Str8FromResponseState(DN_NETResponseState state) -{ - DN_Str8 result = {}; - switch (state) { - case DN_NETResponseState_Nil: result = DN_Str8Lit("Nil"); break; - case DN_NETResponseState_Error: result = DN_Str8Lit("Error"); break; - case DN_NETResponseState_HTTP: result = DN_Str8Lit("HTTP"); break; - case DN_NETResponseState_WSOpen: result = DN_Str8Lit("WS Open"); break; - case DN_NETResponseState_WSText: result = DN_Str8Lit("WS Text"); break; - case DN_NETResponseState_WSBinary: result = DN_Str8Lit("WS Binary"); break; - case DN_NETResponseState_WSClose: result = DN_Str8Lit("WS Close"); break; - case DN_NETResponseState_WSPing: result = DN_Str8Lit("WS Ping"); break; - case DN_NETResponseState_WSPong: result = DN_Str8Lit("WS Pong"); break; - } - return result; -} - -DN_NETRequest *DN_NET_RequestFromHandle(DN_NETRequestHandle handle) -{ - DN_NETRequest *ptr = DN_Cast(DN_NETRequest *) handle.handle; - DN_NETRequest *result = nullptr; - if (ptr && ptr->gen == handle.gen) - result = ptr; - return result; -} - -DN_NETRequestHandle DN_NET_HandleFromRequest(DN_NETRequest *request) -{ - DN_NETRequestHandle result = {}; - if (request) { - result.handle = DN_Cast(DN_UPtr) request; - result.gen = request->gen; - } - return result; -} - -bool DN_NET_ResponseHasFailed(DN_NETResponse const* resp) -{ - bool result = false; - if (resp->type == DN_NETRequestType_HTTP) - result = resp->state == DN_NETResponseState_Error || resp->http_status >= 400; - else - result = resp->state == DN_NETResponseState_Error; - return result; -} - -DN_Str8 DN_NET_Str8DiagnosticFromResponse(DN_NETResponse const* resp, DN_Arena *arena) -{ - DN_TCScratch scratch = DN_TCScratchBeginArena(&arena, 1); - DN_Str8Builder builder = DN_Str8BuilderFromArena(&scratch.arena); - bool resp_failed = DN_NET_ResponseHasFailed(resp); - DN_Str8BuilderAppendF(&builder, "Request %s (%s", resp_failed ? "failed" : "succeeded", resp->type == DN_NETRequestType_HTTP ? "HTTP" : "WS"); - if (resp->type == DN_NETRequestType_HTTP) { - if (resp->http_status) - DN_Str8BuilderAppendF(&builder, " %u", resp->http_status); - } - DN_Str8BuilderAppendF(&builder, ")"); - if (resp->body.size || resp->error_str8.size) { - DN_Str8BuilderAppendRef(&builder, DN_Str8Lit(" with ")); - if (resp->body.size) - DN_Str8BuilderAppendF(&builder, "%.*s", DN_Str8PrintFmt(resp->body)); - if (resp->error_str8.size) - DN_Str8BuilderAppendF(&builder, "%s%.*s", resp->body.size ? ". " : "", DN_Str8PrintFmt(resp->error_str8)); - } - DN_Str8 result = DN_Str8FromStr8BuilderArena(&builder, arena); - DN_TCScratchEnd(&scratch); - return result; -} - -void DN_NET_BaseInit(DN_NETCore *net, char *base, DN_U64 base_size) -{ - net->base = base; - net->base_size = base_size; - net->mem = DN_MemListFromBuffer(net->base, net->base_size, DN_MemFlags_Nil); - net->arena = DN_ArenaFromMemList(&net->mem); - net->completion_sem = DN_OS_SemaphoreInit(0); -} - -DN_NETRequestHandle DN_NET_SetupRequest(DN_NETRequest *request, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args, DN_NETRequestType type) -{ - // NOTE: Setup request - DN_Assert(request); - if (request) { - if (!request->mem.curr) - request->mem = DN_MemListFromVMem(DN_Megabytes(1), DN_Kilobytes(1), DN_MemFlags_Nil); - request->arena = DN_ArenaTempBeginFromMemList(&request->mem); - request->type = type; - request->gen = DN_Max(request->gen + 1, 1); - request->url = DN_Str8FromStr8Arena(url, &request->arena); - request->method = DN_Str8FromStr8Arena(DN_Str8TrimWhitespaceAround(method), &request->arena); - - if (args) { - request->args.flags = args->flags; - request->args.username = DN_Str8FromStr8Arena(args->username, &request->arena); - request->args.password = DN_Str8FromStr8Arena(args->password, &request->arena); - if (type == DN_NETRequestType_HTTP) - request->args.payload = DN_Str8FromStr8Arena(args->payload, &request->arena); - - request->args.headers = DN_ArenaNewArray(&request->arena, DN_Str8, args->headers_size, DN_ZMem_No); - DN_Assert(request->args.headers); - if (request->args.headers) { - for (DN_ForItSize(it, DN_Str8, args->headers, args->headers_size)) - request->args.headers[it.index] = DN_Str8FromStr8Arena(*it.data, &request->arena); - request->args.headers_size = args->headers_size; - } - } - - request->completion_sem = DN_OS_SemaphoreInit(0); - request->start_response_arena = DN_ArenaTempBeginFromArena(&request->arena); - } - - DN_NETRequestHandle result = DN_NET_HandleFromRequest(request); - request->response.request = result; - request->response.type = request->type; - return result; -} - -void DN_NET_EndFinishedRequest(DN_NETRequest *request) -{ - // NOTE: Deallocate the memory used in the request and reset the string builder - DN_ArenaTempEnd(&request->start_response_arena, DN_ArenaReset_Yes); -} diff --git a/Source/Extra/dn_net.h b/Source/Extra/dn_net.h deleted file mode 100644 index 6706493..0000000 --- a/Source/Extra/dn_net.h +++ /dev/null @@ -1,134 +0,0 @@ -#if !defined(DN_NET_H) -#define DN_NET_H - -#if defined(_CLANGD) - #define DN_H_WITH_OS 1 - #include "../dn.h" -#endif - -enum DN_NETRequestType -{ - DN_NETRequestType_Nil, - DN_NETRequestType_HTTP, - DN_NETRequestType_WS, -}; - -enum DN_NETResponseState -{ - DN_NETResponseState_Nil, - DN_NETResponseState_Error, - DN_NETResponseState_HTTP, - DN_NETResponseState_WSOpen, - DN_NETResponseState_WSText, - DN_NETResponseState_WSBinary, - DN_NETResponseState_WSClose, - DN_NETResponseState_WSPing, - DN_NETResponseState_WSPong, -}; - -enum DN_NETWSSend -{ - DN_NETWSSend_Text, - DN_NETWSSend_Binary, - DN_NETWSSend_Close, - DN_NETWSSend_Ping, - DN_NETWSSend_Pong, -}; - -enum DN_NETDoHTTPFlags -{ - DN_NETDoHTTPFlags_Nil = 0, - DN_NETDoHTTPFlags_BasicAuth = 1 << 0, -}; - -struct DN_NETDoHTTPArgs -{ - // NOTE: WS and HTTP args - DN_NETDoHTTPFlags flags; - DN_Str8 username; - DN_Str8 password; - DN_Str8 *headers; - DN_U16 headers_size; - - // NOTE: HTTP args only - DN_Str8 payload; -}; - -struct DN_NETRequestHandle -{ - DN_UPtr handle; - DN_U64 gen; -}; - -struct DN_NETResponse -{ - // NOTE: Common to WS and HTTP responses - DN_NETRequestType type; - DN_NETResponseState state; - DN_NETRequestHandle request; - DN_Str8 error_str8; - DN_Str8 body; - - // NOTE: HTTP responses only - DN_U32 http_status; -}; - -struct DN_NETRequest -{ - DN_MemList mem; - DN_Arena arena; - DN_Arena start_response_arena; - DN_NETRequestType type; - DN_U64 gen; - DN_Str8 url; - DN_Str8 method; - DN_OSSemaphore completion_sem; - DN_NETDoHTTPArgs args; - DN_NETResponse response; - DN_NETRequest *next; - DN_NETRequest *prev; - DN_U64 context[2]; -}; - -typedef void (DN_NETInitFunc) (struct DN_NETCore *net, char *base, DN_U64 base_size); -typedef void (DN_NETDeinitFunc) (struct DN_NETCore *net); -typedef DN_NETRequestHandle(DN_NETDoHTTPFunc) (struct DN_NETCore *net, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args); -typedef DN_NETRequestHandle(DN_NETDoWSFunc) (struct DN_NETCore *net, DN_Str8 url); -typedef void (DN_NETDoWSSendFunc) (DN_NETRequestHandle handle, DN_Str8 data, DN_NETWSSend send); -typedef DN_NETResponse (DN_NETWaitForResponseFunc) (DN_NETRequestHandle handle, DN_Arena *arena, DN_U32 timeout_ms); -typedef DN_NETResponse (DN_NETWaitForAnyResponseFunc)(struct DN_NETCore *net, DN_Arena *arena, DN_U32 timeout_ms); - -struct DN_NETInterface -{ - DN_NETInitFunc* init; - DN_NETDeinitFunc* deinit; - DN_NETDoHTTPFunc* do_http; - DN_NETDoWSFunc* do_ws; - DN_NETDoWSSendFunc* do_ws_send; - DN_NETWaitForResponseFunc* wait_for_response; - DN_NETWaitForAnyResponseFunc* wait_for_any_response; -}; - -struct DN_NETCore -{ - char *base; - DN_U64 base_size; - DN_MemList mem; - DN_Arena arena; - DN_OSSemaphore completion_sem; - void *context; - DN_NETInterface api; -}; - -DN_Str8 DN_NET_Str8FromResponseState (DN_NETResponseState state); -DN_NETRequest * DN_NET_RequestFromHandle (DN_NETRequestHandle handle); -DN_NETRequestHandle DN_NET_HandleFromRequest (DN_NETRequest *request); -bool DN_NET_ResponseHasFailed (DN_NETResponse const* resp); -DN_Str8 DN_NET_Str8DiagnosticFromResponse(DN_NETResponse const* resp, DN_Arena *arena); - -// NOTE: Internal functions for different networking implementations to use -void DN_NET_BaseInit (DN_NETCore *net, char *base, DN_U64 base_size); -DN_NETRequestHandle DN_NET_SetupRequest (DN_NETRequest *request, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args, DN_NETRequestType type); -void DN_NET_EndFinishedRequest (DN_NETRequest *request); - -#endif // DN_NET_H diff --git a/Source/Extra/dn_net_curl.cpp b/Source/Extra/dn_net_curl.cpp deleted file mode 100644 index 1f7f9a3..0000000 --- a/Source/Extra/dn_net_curl.cpp +++ /dev/null @@ -1,708 +0,0 @@ -#define DN_NET_CURL_CPP - -#if defined(_CLANGD) - #define DN_H_WITH_OS 1 - #include "../dn.h" - #include "dn_net.h" - #include "dn_net_curl.h" -#endif - -struct DN_NETCurlRequest -{ - void *handle; - struct curl_slist *slist; - char error[CURL_ERROR_SIZE]; - bool ws_has_more; - DN_Str8Builder str8_builder; -}; - -enum DN_NETCurlRingEventType -{ - DN_NETCurlRingEventType_Nil, - DN_NETCurlRingEventType_DoRequest, - DN_NETCurlRingEventType_SendWS, - DN_NETCurlRingEventType_ReceivedWSReceipt, - DN_NETCurlRingEventType_DeinitRequest, -}; - -struct DN_NETCurlRingEvent -{ - DN_NETCurlRingEventType type; - DN_NETRequestHandle request; - DN_USize ws_send_size; - DN_NETWSSend ws_send; -}; - -static DN_NETCurlRequest *DN_NET_CurlRequestFromRequest_(DN_NETRequest *req) -{ - DN_NETCurlRequest *result = req ? DN_Cast(DN_NETCurlRequest *) req->context[0] : 0; - return result; -} - -static DN_NETCore *DN_NET_CurlNetFromRequest(DN_NETRequest *req) -{ - DN_NETCore *result = req ? DN_Cast(DN_NETCore *) req->context[1] : 0; - return result; -} - -static bool DN_NET_CurlRequestIsInList(DN_NETRequest const *first, DN_NETRequest const *find) -{ - bool result = false; - for (DN_NETRequest const *it = first; !result && it; it = it->next) - result = find == it; - return result; -} - -static void DN_NET_CurlMarkRequestDone_(DN_NETCore *net, DN_NETRequest *request) -{ - DN_Assert(request); - DN_Assert(net); - // NOTE: The done list in CURL is also used as a place to put websocket requests after removing it - // from the 'ws_list'. By doing this we are stopping the CURL thread from receiving more data on - // the socket as that thread ticks the list of 'ws_list' sockets for data. - // - // Once the caller waited and has received the data from the websocket, the request is put back - // into the 'ws_list' which then lets the CURL thread start receiving more data for that socket. - // - // Since CURL uses a background thread, we do this behind a mutex - DN_NETCurlCore *curl = DN_Cast(DN_NETCurlCore *)net->context; - for (DN_OS_MutexScope(&curl->list_mutex)) { - DN_Assert(DN_NET_CurlRequestIsInList(curl->thread_request_list, request)); - DN_DoublyLLDetach(curl->thread_request_list, request); - DN_DoublyLLAppend(curl->response_list, request); - } - DN_OS_SemaphoreIncrement(&net->completion_sem, 1); - DN_OS_SemaphoreIncrement(&request->completion_sem, 1); -} - -static DN_USize DN_NET_CurlHTTPCallback_(char *payload, DN_USize size, DN_USize count, void *user_data) -{ - DN_NETRequest *req = DN_Cast(DN_NETRequest *) user_data; - DN_NETCurlRequest *curl_req = DN_NET_CurlRequestFromRequest_(req); - DN_USize result = 0; - DN_USize payload_size = size * count; - if (DN_Str8BuilderAppendBytesCopy(&curl_req->str8_builder, payload, payload_size)) - result = payload_size; - return result; -} - -static int32_t DN_NET_CurlThreadEntryPoint_(DN_OSThread *thread) -{ - DN_NETCore *net = DN_Cast(DN_NETCore *) thread->user_context; - DN_NETCurlCore *curl = DN_Cast(DN_NETCurlCore *) net->context; - DN_OS_ThreadSetNameFmt("%.*s", DN_Str8PrintFmt(curl->thread.name)); - - while (!curl->kill_thread) { - DN_TCScratch tmem = DN_TCScratchBeginArena(nullptr, 0); - - // NOTE: Handle events sitting in the ring queue - for (bool dequeue_ring = true; dequeue_ring;) { - DN_NETCurlRingEvent event = {}; - for (DN_OS_MutexScope(&curl->ring_mutex)) { - if (DN_RingHasData(&curl->ring, sizeof(event))) - DN_RingRead(&curl->ring, &event, sizeof(event)); - } - - DN_NETRequest *req = DN_NET_RequestFromHandle(event.request); - DN_NETCurlRequest *curl_req = DN_NET_CurlRequestFromRequest_(req); - switch (event.type) { - case DN_NETCurlRingEventType_Nil: dequeue_ring = false; break; - - case DN_NETCurlRingEventType_DoRequest: { - DN_Assert(req->response.state == DN_NETResponseState_Nil); - DN_Assert(req->type != DN_NETRequestType_Nil); - - // NOTE: Attach it to the CURL thread's request list - for (DN_OS_MutexScope(&curl->list_mutex)) { - DN_Assert(DN_NET_CurlRequestIsInList(curl->request_list, req)); - DN_DoublyLLDetach(curl->request_list, req); - } - DN_DoublyLLAppend(curl->thread_request_list, req); - - // NOTE: Add the connection to CURLM and start ticking it once we finish handling all the - // ring events - CURLMcode multi_add = curl_multi_add_handle(curl->thread_curlm, curl_req->handle); - DN_Assert(multi_add == CURLM_OK); - } break; - - case DN_NETCurlRingEventType_SendWS: { - DN_Str8 payload = {}; - for (DN_OS_MutexScope(&curl->ring_mutex)) { - DN_Assert(DN_RingHasData(&curl->ring, event.ws_send_size)); - payload = DN_Str8AllocArena(event.ws_send_size, DN_ZMem_No, &tmem.arena); - DN_RingRead(&curl->ring, payload.data, payload.size); - } - - DN_U32 curlws_flag = 0; - switch (event.ws_send) { - case DN_NETWSSend_Text: curlws_flag = CURLWS_TEXT; break; - case DN_NETWSSend_Binary: curlws_flag = CURLWS_BINARY; break; - case DN_NETWSSend_Close: curlws_flag = CURLWS_CLOSE; break; - case DN_NETWSSend_Ping: curlws_flag = CURLWS_PING; break; - case DN_NETWSSend_Pong: curlws_flag = CURLWS_PONG; break; - } - - DN_Assert(req->type == DN_NETRequestType_WS); - DN_Assert(req->response.state >= DN_NETResponseState_WSOpen && req->response.state <= DN_NETResponseState_WSPong); - - DN_USize sent = 0; - CURLcode send_result = curl_ws_send(curl_req->handle, payload.data, payload.size, &sent, 0, curlws_flag); - DN_AssertF(send_result == CURLE_OK, "Failed to send: %s", curl_easy_strerror(send_result)); - DN_AssertF(sent == payload.size, "Failed to send all bytes (%zu vs %zu)", sent, payload.size); - } break; - - case DN_NETCurlRingEventType_ReceivedWSReceipt: { - DN_Assert(req->type == DN_NETRequestType_WS); - DN_Assert(req->response.state >= DN_NETResponseState_WSOpen && req->response.state <= DN_NETResponseState_WSPong); - req->response.state = DN_NETResponseState_WSOpen; - - // NOTE: End the temp memory storing the WS data we just read and the user returned to us - // (we got their receipt back). Then restart the temp memory scope for the next websocket - // payload - DN_NET_EndFinishedRequest(req); - req->start_response_arena = DN_ArenaTempBeginFromArena(&req->arena); - curl_req->str8_builder = DN_Str8BuilderFromArena(&req->start_response_arena); - - for (DN_OS_MutexScope(&curl->list_mutex)) { - DN_Assert(DN_NET_CurlRequestIsInList(curl->request_list, req)); - DN_DoublyLLDetach(curl->request_list, req); - } - DN_DoublyLLAppend(curl->thread_request_list, req); - } break; - - case DN_NETCurlRingEventType_DeinitRequest: { - DN_Assert(event.request.handle != 0); - DN_NETRequest *request = DN_Cast(DN_NETRequest *) event.request.handle; - - // NOTE: Detach the request from the deinit list. This brings the request into this - // thread's provenance, no other threads modifying the deinit list will race with us. - for (DN_OS_MutexScope(&curl->list_mutex)) { - DN_Assert(DN_NET_CurlRequestIsInList(curl->deinit_list, request)); - DN_DoublyLLDetach(curl->deinit_list, request); - } - - // NOTE: Now we can modify the request, release resources - DN_NET_EndFinishedRequest(request); - DN_OS_SemaphoreDeinit(&request->completion_sem); - - curl_multi_remove_handle(curl->thread_curlm, curl_req->handle); - curl_slist_free_all(curl_req->slist); - curl_easy_reset(curl_req->handle); - - CURL *copy = curl_req->handle; - *curl_req = {}; - curl_req->handle = copy; - - // NOTE: Zero the struct preserving just the data we need to retain - DN_NETRequest resetter = {}; - resetter.arena = request->arena; - resetter.gen = request->gen; - DN_Memcpy(resetter.context, request->context, sizeof(resetter.context)); - *request = resetter; - - // NOTE: Add it to the free list - for (DN_OS_MutexScope(&curl->list_mutex)) - DN_DoublyLLAppend(curl->free_list, request); - } break; - } - } - - // NOTE: Pump handles - int running_handles = 0; - CURLMcode perform_result = curl_multi_perform(curl->thread_curlm, &running_handles); - if (perform_result != CURLM_OK) - DN_AssertInvalidCodePath; - - // NOTE: Check pump result - for (;;) { - int msgs_in_queue = 0; - CURLMsg *msg = curl_multi_info_read(curl->thread_curlm, &msgs_in_queue); - if (msg) { - // NOTE: Get request handle - DN_NETRequest *req = nullptr; - curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, DN_Cast(void **) & req); - DN_Assert(req); - DN_Assert(DN_NET_CurlRequestIsInList(curl->thread_request_list, req)); - - DN_NETCurlRequest *curl_req = DN_NET_CurlRequestFromRequest_(req); - DN_Assert(curl_req->handle == msg->easy_handle); - - if (msg->data.result == CURLE_OK) { - // NOTE: Get HTTP response code - CURLcode get_result = curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &req->response.http_status); - if (get_result == CURLE_OK) { - if (req->type == DN_NETRequestType_HTTP) { - req->response.state = DN_NETResponseState_HTTP; - } else { - DN_Assert(req->type == DN_NETRequestType_WS); - req->response.state = DN_NETResponseState_WSOpen; - } - } else { - req->response.error_str8 = DN_Str8FromFmtArena(&req->start_response_arena, "Failed to get HTTP response status (CURL %d): %s", msg->data.result, curl_easy_strerror(get_result)); - req->response.state = DN_NETResponseState_Error; - } - } else { - DN_USize curl_extended_error_size = DN_CStr8Size(curl_req->error); - req->response.state = DN_NETResponseState_Error; - req->response.error_str8 = DN_Str8FromFmtArena(&req->start_response_arena, - "HTTP request '%.*s' failed (CURL %d): %s%s%s%s", - DN_Str8PrintFmt(req->url), - msg->data.result, - curl_easy_strerror(msg->data.result), - curl_extended_error_size ? " (" : "", - curl_extended_error_size ? curl_req->error : "", - curl_extended_error_size ? ")" : ""); - } - - if (req->type == DN_NETRequestType_HTTP || req->response.state == DN_NETResponseState_Error) { - // NOTE: Remove the request from the multi handle if we're a HTTP request - // because it typically terminates the connection. In websockets the - // connection remains in the multi-handle to allow you to send and - // receive WS data from it. - // - // If there's an error (either websocket or HTTP) we will also remove the - // connection from the multi handle as it failed. One a connection has - // failed, curl will not poll that connection so there's no point keeping - // it attached to the multi handle. - curl_multi_remove_handle(curl->thread_curlm, msg->easy_handle); - } - - DN_NET_CurlMarkRequestDone_(net, req); - } - - if (msgs_in_queue == 0) - break; - } - - // NOTE: Check websockets - DN_USize ws_count = 0; - for (DN_NETRequest *req = curl->thread_request_list; req; req = req->next) { - DN_Assert(req->type == DN_NETRequestType_WS || req->type == DN_NETRequestType_HTTP); - if (req->type != DN_NETRequestType_WS || !(req->response.state >= DN_NETResponseState_WSOpen && req->response.state <= DN_NETResponseState_WSPong)) - continue; - ws_count++; - const curl_ws_frame *meta = nullptr; - DN_NETCurlRequest *curl_req = DN_NET_CurlRequestFromRequest_(req); - CURLcode receive_result = CURLE_OK; - while (receive_result == CURLE_OK) { - // NOTE: Determine WS payload size received. Note that since we pass in a null pointer CURL - // will set meta->len to 0 and say that there's meta->bytesleft in the next chunk. - DN_USize bytes_read = 0; - receive_result = curl_ws_recv(curl_req->handle, nullptr, 0, &bytes_read, &meta); - if (receive_result != CURLE_OK) - continue; - DN_Assert(meta->len == 0); - - if (meta->flags & CURLWS_TEXT) - req->response.state = DN_NETResponseState_WSText; - - if (meta->flags & CURLWS_BINARY) - req->response.state = DN_NETResponseState_WSBinary; - - if (meta->flags & CURLWS_PING) - req->response.state = DN_NETResponseState_WSPing; - - if (meta->flags & CURLWS_PONG) - req->response.state = DN_NETResponseState_WSPong; - - if (meta->flags & CURLWS_CLOSE) - req->response.state = DN_NETResponseState_WSClose; - - curl_req->ws_has_more = meta->flags & CURLWS_CONT; - if (curl_req->ws_has_more) { - bool is_text_or_binary = req->response.state == DN_NETResponseState_WSText || - req->response.state == DN_NETResponseState_WSBinary; - DN_Assert(is_text_or_binary); - } - - // NOTE: Allocate and read (we use meta->bytesleft as per comment from initial recv) - if (meta->bytesleft) { - DN_Str8 buffer = DN_Str8AllocArena(meta->bytesleft, DN_ZMem_No, &req->start_response_arena); - DN_Assert(buffer.size == DN_Cast(DN_USize)meta->bytesleft); - receive_result = curl_ws_recv(curl_req->handle, buffer.data, buffer.size, &buffer.size, &meta); - DN_Assert(buffer.size == DN_Cast(DN_USize)meta->len); - DN_Str8BuilderAppendRef(&curl_req->str8_builder, buffer); - } - - // NOTE: There are more bytes coming if meta->bytesleft is set, (e.g. the next chunk. We - // just read the current chunk). - // - // > If this is not a complete fragment, the bytesleft field informs about how many - // additional bytes are expected to arrive before this fragment is complete. - curl_req->ws_has_more |= meta && meta->bytesleft > 0; - if (!curl_req->ws_has_more) - break; - } - - // NOTE: curl_ws_recv returns CURLE_GOT_NOTHING if the associated connection is closed. - if (receive_result == CURLE_GOT_NOTHING) - curl_req->ws_has_more = false; - - // NOTE: We read all the possible bytes that CURL has received for this message, but, there are - // more bytes left that we will receive on subsequent calls. We will continue to the next - // request and return back to this one when PumpRequests is called again where hopefully that - // data has arrived. - if (curl_req->ws_has_more) - continue; - - // For CURLE_AGAIN - // - // > Instead of blocking, the function returns CURLE_AGAIN. The correct behavior is then to - // > wait for the socket to signal readability before calling this function again. - // - // In which case we continue ticking the other sockets and eventually exit once all ticked. - // Right after this we wait on the CURLM instance which will wake us up again when there's - // data to be read. - // - // if we received data, e.g. state was set to Text, Binary ... e.t.c we bypass this and - // report it to the user first. When the user waits for the response, they consume the data - // and then that will reinsert it into request list for CURL to read from the socket again. - bool received_data = (req->response.state >= DN_NETResponseState_WSText && req->response.state <= DN_NETResponseState_WSPong); - if (receive_result == CURLE_AGAIN && !received_data) - continue; - - if (!received_data) { - if (receive_result == CURLE_GOT_NOTHING) { - req->response.state = DN_NETResponseState_WSClose; - } else if (receive_result != CURLE_OK) { - DN_USize curl_extended_error_size = DN_CStr8Size(curl_req->error); - req->response.state = DN_NETResponseState_Error; - req->response.error_str8 = DN_Str8FromFmtArena(&req->start_response_arena, - "Websocket receive '%.*s' failed (CURL %d): %s%s%s%s", - DN_Str8PrintFmt(req->url), - receive_result, - curl_easy_strerror(receive_result), - curl_extended_error_size ? " (" : "", - curl_extended_error_size ? curl_req->error : "", - curl_extended_error_size ? ")" : ""); - } - } - - DN_NETRequest *request_copy = req; - req = req->prev; - DN_NET_CurlMarkRequestDone_(net, request_copy); - if (!req) - break; - } - - DN_I32 sleep_time_ms = ws_count > 0 ? 16 : INT32_MAX; - curl_multi_poll(curl->thread_curlm, nullptr, 0, sleep_time_ms, nullptr); - DN_TCScratchEnd(&tmem); - } - - return 0; -} - -DN_NETInterface DN_NET_CurlInterface() -{ - DN_NETInterface result = {}; - result.init = DN_NET_CurlInit; - result.deinit = DN_NET_CurlDeinit; - result.do_http = DN_NET_CurlDoHTTP; - result.do_ws = DN_NET_CurlDoWS; - result.do_ws_send = DN_NET_CurlDoWSSend; - result.wait_for_response = DN_NET_CurlWaitForResponse; - result.wait_for_any_response = DN_NET_CurlWaitForAnyResponse; - return result; -} - -void DN_NET_CurlInit(DN_NETCore *net, char *base, DN_U64 base_size) -{ - DN_NET_BaseInit(net, base, base_size); - DN_NETCurlCore *curl = DN_ArenaNew(&net->arena, DN_NETCurlCore, DN_ZMem_Yes); - net->context = curl; - net->api = DN_NET_CurlInterface(); - - DN_USize arena_bytes_avail = (net->arena.mem->curr->reserve - net->arena.mem->curr->used); - curl->ring.size = arena_bytes_avail / 2; - curl->ring.base = DN_Cast(char *) DN_ArenaAlloc(&net->arena, curl->ring.size, /*align*/ 1, DN_ZMem_Yes); - DN_Assert(curl->ring.base); - - curl->ring_mutex = DN_OS_MutexInit(); - curl->list_mutex = DN_OS_MutexInit(); - curl->thread_curlm = DN_Cast(CURLM *) curl_multi_init(); - - DN_FmtAppend(curl->thread.name.data, &curl->thread.name.size, sizeof(curl->thread.name.data), "NET (CURL)"); - DN_OS_ThreadInit(&curl->thread, DN_NET_CurlThreadEntryPoint_, nullptr, DN_TCInitArgsDefault(), net); -} - -void DN_NET_CurlDeinit(DN_NETCore *net) -{ - DN_NETCurlCore *curl = DN_Cast(DN_NETCurlCore *) net->context; - curl->kill_thread = true; - curl_multi_wakeup(curl->thread_curlm); - DN_OS_ThreadJoin(&curl->thread, DN_TCDeinitArenas_Yes); -} - -static DN_NETRequestHandle DN_NET_CurlDoRequest_(DN_NETCore *net, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args, DN_NETRequestType type) -{ - // NOTE: Allocate the request - DN_NETCurlCore *curl_core = DN_Cast(DN_NETCurlCore *) net->context; - DN_NETRequest *req = nullptr; - DN_NETRequestHandle result = {}; - { - // NOTE: The free list is modified by both the calling thread and the CURLM thread (which ticks - // all the requests in the background for us) - for (DN_OS_MutexScope(&curl_core->list_mutex)) { - req = curl_core->free_list; - DN_DoublyLLDetach(curl_core->free_list, req); - } - - // NOTE None in the free list so allocate one - if (!req) { - DN_OS_MutexLock(&curl_core->list_mutex); - DN_U64 arena_pos = DN_MemListPos(net->arena.mem); - req = DN_ArenaNewZ(&net->arena, DN_NETRequest); - DN_NETCurlRequest *curl_req = DN_ArenaNewZ(&net->arena, DN_NETCurlRequest); - if (!req || !curl_req) { - DN_MemListPopTo(net->arena.mem, arena_pos); - DN_OS_MutexUnlock(&curl_core->list_mutex); - return result; - } - DN_OS_MutexUnlock(&curl_core->list_mutex); - - curl_req->handle = DN_Cast(CURL *) curl_easy_init(); - req->context[0] = DN_Cast(DN_UPtr) curl_req; - } - } - - // NOTE: Setup the request - DN_NETCurlRequest *curl_req = DN_NET_CurlRequestFromRequest_(req); - { - result = DN_NET_SetupRequest(req, url, method, args, type); - req->context[1] = DN_Cast(DN_UPtr) net; - curl_req->str8_builder = DN_Str8BuilderFromArena(&req->start_response_arena); - } - - // NOTE: Setup the request for curl API - { - CURL *curl = curl_req->handle; - curl_easy_setopt(curl, CURLOPT_PRIVATE, req); - curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_req->error); - - // NOTE: Perform request and read all response headers before handing - // control back to app. - curl_easy_setopt(curl, CURLOPT_URL, req->url.data); - curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); - - // NOTE: Setup response handler - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, DN_NET_CurlHTTPCallback_); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, req); - - // NOTE: Assign HTTP headers - for (DN_ForItSize(it, DN_Str8, req->args.headers, req->args.headers_size)) { - DN_Assert(it.data->data[it.data->size] == 0); - curl_req->slist = curl_slist_append(curl_req->slist, it.data->data); - } - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, curl_req->slist); - - // NOTE: Setup handle for protocol - switch (req->type) { - case DN_NETRequestType_Nil: DN_AssertInvalidCodePath; break; - - case DN_NETRequestType_WS: { - curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 2L); - } break; - - case DN_NETRequestType_HTTP: { - DN_Str8 const GET = DN_Str8Lit("GET"); - DN_Str8 const POST = DN_Str8Lit("POST"); - - if (DN_Str8EqInsensitive(req->method, GET)) { - curl_easy_setopt(curl, CURLOPT_HTTPGET, 1); - } else if (DN_Str8EqInsensitive(req->method, POST)) { - curl_easy_setopt(curl, CURLOPT_POST, 1); - if (req->args.payload.size > DN_Gigabytes(2)) - curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, req->args.payload.size); - else - curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, req->args.payload.size); - curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, req->args.payload.data); - } else { - DN_AssertInvalidCodePathF("Unimplemented"); - } - } break; - } - - // NOTE: Handle basic auth - if (req->args.flags & DN_NETDoHTTPFlags_BasicAuth) { - if (req->args.username.size && req->args.password.size) { - DN_Assert(req->args.username.data[req->args.username.size] == 0); - DN_Assert(req->args.password.data[req->args.password.size] == 0); - curl_easy_setopt(curl, CURLOPT_USERNAME, req->args.username.data); - curl_easy_setopt(curl, CURLOPT_PASSWORD, req->args.password.data); - } - } - } - - // NOTE: Dispatch the request to the CURL thread - { - // NOTE: Immediately add the request to the request list so it happens "atomically" in the - // calling thread. If the calling thread deinitialises this layer before the CURL thread can be - // pre-empted, we can lose track of this request. - for (DN_OS_MutexScope(&curl_core->list_mutex)) - DN_DoublyLLAppend(curl_core->request_list, req); - - // NOTE: Enqueue request to go into CURL's ring queue. The CURL thread will sleep and wait for - // bytes to come in for the request and then dump the response into the done list to be consumed - // via wait for response - DN_NETCurlRingEvent event = {}; - event.type = DN_NETCurlRingEventType_DoRequest; - event.request = result; - for (DN_OS_MutexScope(&curl_core->ring_mutex)) - DN_RingWriteStruct(&curl_core->ring, &event); - - curl_multi_wakeup(curl_core->thread_curlm); - } - - return result; -} - -DN_NETRequestHandle DN_NET_CurlDoHTTP(DN_NETCore *net, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args) -{ - DN_NETRequestHandle result = DN_NET_CurlDoRequest_(net, url, method, args, DN_NETRequestType_HTTP); - return result; -} - -DN_NETRequestHandle DN_NET_CurlDoWSArgs(DN_NETCore *net, DN_Str8 url, DN_NETDoHTTPArgs const *args) -{ - DN_NETRequestHandle result = DN_NET_CurlDoRequest_(net, url, DN_Str8Lit(""), args, DN_NETRequestType_WS); - return result; -} - -DN_NETRequestHandle DN_NET_CurlDoWS(DN_NETCore *net, DN_Str8 url) -{ - DN_NETRequestHandle result = DN_NET_CurlDoWSArgs(net, url, nullptr); - return result; -} - -void DN_NET_CurlDoWSSend(DN_NETRequestHandle handle, DN_Str8 payload, DN_NETWSSend send) -{ - DN_NETRequest *req = DN_NET_RequestFromHandle(handle); - if (!req) - return; - - DN_NETCore *net = DN_NET_CurlNetFromRequest(req); - DN_NETCurlCore *curl = DN_Cast(DN_NETCurlCore *) net->context; - DN_Assert(curl); - - DN_NETCurlRingEvent event = {}; - event.type = DN_NETCurlRingEventType_SendWS; - event.request = handle; - event.ws_send_size = payload.size; - event.ws_send = send; - - for (DN_OS_MutexScope(&curl->ring_mutex)) { - DN_Assert(DN_RingHasSpace(&curl->ring, payload.size)); - DN_RingWriteStruct(&curl->ring, &event); - DN_RingWrite(&curl->ring, payload.data, payload.size); - } - curl_multi_wakeup(curl->thread_curlm); -} - -static DN_NETResponse DN_NET_CurlHandleFinishedRequest_(DN_NETCurlCore *curl, DN_NETRequest *req, DN_Arena *arena) -{ - // NOTE: Generate the response, copy out the strings into the user given memory - DN_NETResponse result = req->response; - DN_NETCurlRequest *curl_req = DN_NET_CurlRequestFromRequest_(req); - { - result.body = DN_Str8FromStr8BuilderArena(&curl_req->str8_builder, arena); - if (result.error_str8.size) - result.error_str8 = DN_Str8FromStr8Arena(result.error_str8, arena); - } - - bool continue_ws_request = false; - if (req->type == DN_NETRequestType_WS && - req->response.state != DN_NETResponseState_Error && - req->response.state != DN_NETResponseState_WSClose) { - continue_ws_request = true; - } - - // NOTE: Put the request into the requisite list - for (DN_OS_MutexScope(&curl->list_mutex)) { - // NOTE: Dequeue the request, it _must_ have been in the response list at this point for it to - // have ben waitable in the first place. - DN_AssertF(DN_NET_CurlRequestIsInList(curl->response_list, req), - "A completed response should only signal the completion semaphore when it's in the response list"); - DN_DoublyLLDetach(curl->response_list, req); - - // NOTE: A websocket that is continuing to get data should go back into the request list because - // there's more data to be received. All other requests need to go into the deinit list (so that - // we keep track of it in the time inbetween it takes for the CURL thread to be scheduled and - // release the CURL handle from CURLM and release resources e.t.c.) - if (continue_ws_request) - DN_DoublyLLAppend(curl->request_list, req); - else - DN_DoublyLLAppend(curl->deinit_list, req); - } - - - // NOTE: Submit the post-request event to the CURL thread - DN_NETCurlRingEvent event = {}; - event.request = DN_NET_HandleFromRequest(req); - if (continue_ws_request) { - event.type = DN_NETCurlRingEventType_ReceivedWSReceipt; - } else { - // NOTE: Deinit _has_ to be sent to the CURL thread because we need to remove the CURL handle - // from the CURLM instance and the CURL thread uses the CURLM instance (e.g. CURLM is not thread - // safe) - event.type = DN_NETCurlRingEventType_DeinitRequest; - } - - for (DN_OS_MutexScope(&curl->ring_mutex)) - DN_RingWriteStruct(&curl->ring, &event); - curl_multi_wakeup(curl->thread_curlm); - - return result; -} - -DN_NETResponse DN_NET_CurlWaitForResponse(DN_NETRequestHandle handle, DN_Arena *arena, DN_U32 timeout_ms) -{ - DN_NETResponse result = {}; - DN_NETRequest *req = DN_NET_RequestFromHandle(handle); - if (!req) - return result; - - DN_NETCore *net = DN_Cast(DN_NETCore *) req->context[1]; - DN_NETCurlCore *curl = DN_Cast(DN_NETCurlCore *) net->context; - DN_Assert(curl); - - DN_OSSemaphoreWaitResult wait = DN_OS_SemaphoreWait(&req->completion_sem, timeout_ms); - if (wait != DN_OSSemaphoreWaitResult_Success) - return result; - - // NOTE: Decrement the global 'request done' completion semaphore since the user consumed the - // request individually. - DN_OSSemaphoreWaitResult net_wait_result = DN_OS_SemaphoreWait(&net->completion_sem, 0 /*timeout_ms*/); - DN_AssertF(net_wait_result == DN_OSSemaphoreWaitResult_Success, "Wait result was: %zu", DN_Cast(DN_USize) net_wait_result); - - // NOTE: Finish handling the response - result = DN_NET_CurlHandleFinishedRequest_(curl, req, arena); - return result; -} - -DN_NETResponse DN_NET_CurlWaitForAnyResponse(DN_NETCore *net, DN_Arena *arena, DN_U32 timeout_ms) -{ - DN_NETCurlCore *curl = DN_Cast(DN_NETCurlCore *) net->context; - DN_Assert(curl); - - DN_NETResponse result = {}; - DN_OSSemaphoreWaitResult req_wait = DN_OS_SemaphoreWait(&net->completion_sem, timeout_ms); - if (req_wait != DN_OSSemaphoreWaitResult_Success) - return result; - - // NOTE: Just grab the handle, handle finished request will dequeue for us - DN_NETRequestHandle handle = {}; - for (DN_OS_MutexScope(&curl->list_mutex)) { - DN_Assert(curl->response_list); - handle = DN_NET_HandleFromRequest(curl->response_list); - } - - // NOTE: Decrement the request's completion semaphore since the user consumed the global semaphore - DN_NETRequest *req = DN_NET_RequestFromHandle(handle); - DN_OSSemaphoreWaitResult net_wait = DN_OS_SemaphoreWait(&req->completion_sem, 0 /*timeout_ms*/); - DN_AssertF(net_wait == DN_OSSemaphoreWaitResult_Success, "Wait result was: %zu", DN_Cast(DN_USize) net_wait); - - // NOTE: Finish handling the response - result = DN_NET_CurlHandleFinishedRequest_(curl, req, arena); - return result; -} diff --git a/Source/Extra/dn_net_curl.h b/Source/Extra/dn_net_curl.h deleted file mode 100644 index 8711e61..0000000 --- a/Source/Extra/dn_net_curl.h +++ /dev/null @@ -1,39 +0,0 @@ -#if !defined(DN_NET_CURL_H) -#define DN_NET_CURL_H - -#if defined(_CLANGD) - #include "dn_net.h" -#endif - -struct DN_NETCurlCore -{ - // NOTE: Shared w/ user and networking thread - DN_Ring ring; - DN_OSMutex ring_mutex; - bool kill_thread; - - DN_OSMutex list_mutex; // Lock for request, response, deinit, free list - DN_NETRequest *request_list; // Current requests submitted by the user thread awaiting to move into the thread request list - DN_NETRequest *response_list; // Finished requests that are to be deqeued by the user via wait for response - DN_NETRequest *deinit_list; // Requests that are finished and are awaiting to be de-initialised by the CURL thread - DN_NETRequest *free_list; // Request pool that new requests will use before allocating - - // NOTE: Networking thread only - DN_NETRequest *thread_request_list; // Current requests being executed by the CURL thread. - // This list is exclusively owned by the CURL thread so no locking is needed - DN_OSThread thread; - void *thread_curlm; -}; - -#define DN_NET_CurlCoreFromNet(net) ((net) ? (DN_Cast(DN_NETCurlCore *)(net)->context) : nullptr); -DN_NETInterface DN_NET_CurlInterface (); -void DN_NET_CurlInit (DN_NETCore *net, char *base, DN_U64 base_size); -void DN_NET_CurlDeinit (DN_NETCore *net); -DN_NETRequestHandle DN_NET_CurlDoHTTP (DN_NETCore *net, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args); -DN_NETRequestHandle DN_NET_CurlDoWSArgs (DN_NETCore *net, DN_Str8 url, DN_NETDoHTTPArgs const *args); -DN_NETRequestHandle DN_NET_CurlDoWS (DN_NETCore *net, DN_Str8 url); -void DN_NET_CurlDoWSSend (DN_NETRequestHandle handle, DN_Str8 payload, DN_NETWSSend send); -DN_NETResponse DN_NET_CurlWaitForResponse (DN_NETRequestHandle handle, DN_Arena *arena, DN_U32 timeout_ms); -DN_NETResponse DN_NET_CurlWaitForAnyResponse(DN_NETCore *net, DN_Arena *arena, DN_U32 timeout_ms); - -#endif // !defined(DN_NET_CURL_H) diff --git a/Source/Extra/dn_net_emscripten.cpp b/Source/Extra/dn_net_emscripten.cpp deleted file mode 100644 index ac2f667..0000000 --- a/Source/Extra/dn_net_emscripten.cpp +++ /dev/null @@ -1,467 +0,0 @@ -#if !defined(__EMSCRIPTEN__) - #error "This file can only be compiled with Emscripten" -#endif - -#include -#include -#include - -#if defined(_CLANGD) - #include "dn_net.h" - #include "dn_net_emscripten.h" -#endif - -struct DN_NETEmcWSEvent -{ - DN_NETResponseState state; - DN_Str8 payload; - DN_NETEmcWSEvent *next; -}; - -struct DN_NETEmcCore -{ - DN_Pool pool; - DN_NETRequest *response_list; // Responses received that are to be deqeued via wait for response - DN_NETRequest *free_list; // Request pool that new requests will use before allocating -}; - -struct DN_NETEmcRequest -{ - int socket; - DN_NETEmcWSEvent *first_event; - DN_NETEmcWSEvent *last_event; -}; - -DN_NETInterface DN_NET_EmcInterface() -{ - DN_NETInterface result = {}; - result.init = DN_NET_EmcInit; - result.deinit = DN_NET_EmcDeinit; - result.do_http = DN_NET_EmcDoHTTP; - result.do_ws = DN_NET_EmcDoWS; - result.do_ws_send = DN_NET_EmcDoWSSend; - result.wait_for_response = DN_NET_EmcWaitForResponse; - result.wait_for_any_response = DN_NET_EmcWaitForAnyResponse; - return result; -} - -static DN_NETEmcWSEvent *DN_NET_EmcAllocWSEvent_(DN_NETRequest *request) -{ - // NOTE: Allocate the event and attach to the request - DN_NETEmcRequest *emc_request = DN_Cast(DN_NETEmcRequest *) request->context[1]; - DN_NETEmcWSEvent *result = DN_ArenaNew(&request->arena, DN_NETEmcWSEvent, DN_ZMem_Yes); - DN_Assert(result); - if (result) { - if (!emc_request->first_event) - emc_request->first_event = result; - if (emc_request->last_event) - emc_request->last_event->next = result; - emc_request->last_event = result; - } - return result; -} - -static void DN_NET_EmcOnRequestDone_(DN_NETCore *net, DN_NETRequest *request) -{ - // NOTE: This may be call multiple times on the same request if we get multiple responses when we - // yield to the javascript event loop, e.g. the application received multiple WS payloads before - // it waited and consequently consumed the response from the payload. - // - // So if the next pointer is already set, then it should be that the request is already enqueued. - DN_NETEmcCore *emc = DN_Cast(DN_NETEmcCore *) net->context; - if (!request->next && !request->prev && request != emc->response_list) { - request->prev = nullptr; - request->next = emc->response_list; - if (emc->response_list) - emc->response_list->prev = request; - emc->response_list = request; - } - DN_OS_SemaphoreIncrement(&net->completion_sem, 1); - DN_OS_SemaphoreIncrement(&request->completion_sem, 1); -} - -static bool DN_NET_EmcWSOnOpen(int eventType, EmscriptenWebSocketOpenEvent const *event, void *user_data) -{ - DN_NETRequest *req = DN_Cast(DN_NETRequest *) user_data; - DN_NETCore *net = DN_Cast(DN_NETCore *) req->context[0]; - DN_NETEmcWSEvent *net_event = DN_NET_EmcAllocWSEvent_(req); - net_event->state = DN_NETResponseState_WSOpen; - DN_NET_EmcOnRequestDone_(net, req); - return true; -} - -static bool DN_NET_EmcWSOnMessage(int eventType, const EmscriptenWebSocketMessageEvent *event, void *user_data) -{ - DN_NETRequest *req = DN_Cast(DN_NETRequest *) user_data; - DN_NETCore *net = DN_Cast(DN_NETCore *) req->context[0]; - DN_NETEmcWSEvent *net_event = DN_NET_EmcAllocWSEvent_(req); - net_event->state = event->isText ? DN_NETResponseState_WSText : DN_NETResponseState_WSBinary; - if (event->numBytes > 0) - net_event->payload = DN_Str8FromPtrArena(event->data, event->numBytes, &req->arena); - DN_NET_EmcOnRequestDone_(net, req); - return true; -} - -static bool DN_NET_EmcWSOnError(int eventType, EmscriptenWebSocketErrorEvent const *event, void *user_data) -{ - DN_NETRequest *req = DN_Cast(DN_NETRequest *) user_data; - DN_NETCore *net = DN_Cast(DN_NETCore *) req->context[0]; - DN_NETEmcWSEvent *net_event = DN_NET_EmcAllocWSEvent_(req); - net_event->state = DN_NETResponseState_Error; - DN_NET_EmcOnRequestDone_(net, req); - return true; -} - -static bool DN_NET_EmcWSOnClose(int eventType, EmscriptenWebSocketCloseEvent const *event, void *user_data) -{ - DN_NETRequest *req = DN_Cast(DN_NETRequest *) user_data; - DN_NETCore *net = DN_Cast(DN_NETCore *) req->context[0]; - DN_NETEmcWSEvent *net_event = DN_NET_EmcAllocWSEvent_(req); - net_event->state = DN_NETResponseState_WSClose; - net_event->payload = DN_Str8FromFmtArena(&req->arena, "Websocket closed '%.*s': (%u) %s (was %s close)", DN_Str8PrintFmt(req->url), event->code, event->reason, event->wasClean ? "clean" : "unclean"); - DN_NET_EmcOnRequestDone_(net, req); - return true; -} - -static void DN_NET_EmcHTTPSuccessCallback(emscripten_fetch_t *fetch) -{ - DN_NETRequest *req = DN_Cast(DN_NETRequest *) fetch->userData; - DN_NETCore *net = DN_Cast(DN_NETCore *) req->context[0]; - req->response.http_status = fetch->status; - req->response.state = DN_NETResponseState_HTTP; - req->response.body = DN_Str8FromStr8Arena(DN_Str8FromPtr(fetch->data, fetch->numBytes - 1), &req->arena); - DN_NET_EmcOnRequestDone_(net, req); -} - -static void DN_NET_EmcHTTPFailCallback(emscripten_fetch_t *fetch) -{ - DN_NETRequest *req = DN_Cast(DN_NETRequest *) fetch->userData; - DN_NETCore *net = DN_Cast(DN_NETCore *) req->context[0]; - req->response.http_status = fetch->status; - req->response.state = DN_NETResponseState_Error; - DN_NET_EmcOnRequestDone_(net, req); -} - -static void DN_NET_EmcHTTPProgressCallback(emscripten_fetch_t *fetch) -{ -} - -void DN_NET_EmcInit(DN_NETCore *net, char *base, DN_U64 base_size) -{ - DN_NET_BaseInit(net, base, base_size); - DN_NETEmcCore *emc = DN_ArenaNew(&net->arena, DN_NETEmcCore, DN_ZMem_Yes); - emc->pool = DN_PoolFromArena(&net->arena, 0); - net->context = emc; -} - -void DN_NET_EmcDeinit(DN_NETCore *net) -{ - (void)net; - // TODO: Track all the request handles and clean it up -} - -static DN_NETRequest *DN_NET_EmcAllocRequest_(DN_NETCore *net) -{ - // NOTE: Allocate request - DN_NETEmcCore *emc = DN_Cast(DN_NETEmcCore *) net->context; - DN_NETRequest *result = emc->free_list; - if (result) { - emc->free_list = emc->free_list->next; - result->next = nullptr; - DN_Assert(result->prev == nullptr); - if (emc->free_list) { - DN_Assert(emc->free_list->prev == nullptr); - } - } else { - // NOTE: Setup the request's arena here. WASM doesn't have the concept of virtual memory - // so we use malloc to initialise it. - result = DN_ArenaNew(&net->arena, DN_NETRequest, DN_ZMem_Yes); - if (result) { - result->arena = DN_ArenaFromMemList(&result->mem); - } - } - - // NOTE: Setup some emscripten specific data into our request context - if (result) { - result->context[0] = DN_Cast(DN_UPtr) net; - } - - return result; -} - -DN_NETRequestHandle DN_NET_EmcDoHTTP(DN_NETCore *net, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args) -{ - DN_NETRequest *req = DN_NET_EmcAllocRequest_(net); - DN_NETRequestHandle result = DN_NET_SetupRequest(req, url, method, args, DN_NETRequestType_HTTP); - - // NOTE: Setup the HTTP request via Emscripten - emscripten_fetch_attr_t fetch_attribs = {}; - { - DN_Assert(req->args.payload.data[req->args.payload.size] == 0); - DN_Assert(req->url.data[req->url.size] == 0); - - // NOTE: Setup request for emscripten - emscripten_fetch_attr_init(&fetch_attribs); - - fetch_attribs.requestData = req->args.payload.data; - fetch_attribs.requestDataSize = req->args.payload.size; - DN_Assert(req->method.size < DN_ArrayCountU(fetch_attribs.requestMethod)); - DN_Memcpy(fetch_attribs.requestMethod, req->method.data, req->method.size); - fetch_attribs.requestMethod[req->method.size] = 0; - - // NOTE: Assign HTTP headers - if (req->args.headers_size) { - char **headers = DN_ArenaNewArray(&req->start_response_arena, char *, req->args.headers_size + 1, DN_ZMem_Yes); - for (DN_ForItSize(it, DN_Str8, req->args.headers, req->args.headers_size)) { - DN_Assert(it.data->data[it.data->size] == 0); - headers[it.index] = it.data->data; - } - fetch_attribs.requestHeaders = headers; - } - - // NOTE: Handle basic auth - if (req->args.flags & DN_NETDoHTTPFlags_BasicAuth) { - if (req->args.username.size && req->args.password.size) { - DN_Assert(req->args.username.data[req->args.username.size] == 0); - DN_Assert(req->args.password.data[req->args.password.size] == 0); - fetch_attribs.withCredentials = true; - fetch_attribs.userName = req->args.username.data; - fetch_attribs.password = req->args.password.data; - } - } - - // NOTE: It would be nice to use EMSCRIPTEN_FETCH_STREAM_DATA however - // emscripten has this note on the current version I'm using that this is - // only supported in Firefox so this is a no-go. - // - // > If passed, the intermediate streamed bytes will be passed in to the - // > onprogress() handler. If not specified, the onprogress() handler will still - // > be called, but without data bytes. Note: Firefox only as it depends on - // > 'moz-chunked-arraybuffer'. - fetch_attribs.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY; - fetch_attribs.onsuccess = DN_NET_EmcHTTPSuccessCallback; - fetch_attribs.onerror = DN_NET_EmcHTTPFailCallback; - fetch_attribs.onprogress = DN_NET_EmcHTTPProgressCallback; - fetch_attribs.userData = req; - } - - // NOTE: Dispatch the asynchronous fetch - emscripten_fetch(&fetch_attribs, req->url.data); - return result; -} - -DN_NETRequestHandle DN_NET_EmcDoWS(DN_NETCore *net, DN_Str8 url) -{ - DN_Assert(emscripten_websocket_is_supported()); - DN_NETRequest *req = DN_NET_EmcAllocRequest_(net); - DN_NETRequestHandle result = DN_NET_SetupRequest(req, url, /*method=*/DN_Str8Lit(""), /*args=*/nullptr, DN_NETRequestType_WS); - if (!req) - return result; - - // NOTE: Setup some emscripten specific data into our request context - req->context[1] = DN_Cast(DN_UPtr) DN_ArenaNew(&req->start_response_arena, DN_NETEmcRequest, DN_ZMem_Yes); - - // NOTE: Create the websocket request and dispatch it via emscripten - EmscriptenWebSocketCreateAttributes attr; - emscripten_websocket_init_create_attributes(&attr); - attr.url = req->url.data; - - DN_NETEmcRequest *emc_request = DN_Cast(DN_NETEmcRequest *) req->context[1]; - emc_request->socket = emscripten_websocket_new(&attr); - DN_Assert(emc_request->socket > 0); - emscripten_websocket_set_onopen_callback(emc_request->socket, /*userData=*/req, DN_NET_EmcWSOnOpen); - emscripten_websocket_set_onmessage_callback(emc_request->socket, /*userData=*/req, DN_NET_EmcWSOnMessage); - emscripten_websocket_set_onerror_callback(emc_request->socket, /*userData=*/req, DN_NET_EmcWSOnError); - emscripten_websocket_set_onclose_callback(emc_request->socket, /*userData=*/req, DN_NET_EmcWSOnClose); - - return result; -} - -void DN_NET_EmcDoWSSend(DN_NETRequestHandle handle, DN_Str8 data, DN_NETWSSend send) -{ - DN_AssertF(send == DN_NETWSSend_Binary || send == DN_NETWSSend_Text || send == DN_NETWSSend_Close, - "Unimplemented, Emscripten only supports some of the available operations"); - int result = 0; - DN_NETRequest *request_ptr = DN_Cast(DN_NETRequest *) handle.handle; - if (request_ptr && request_ptr->gen == handle.gen) { - DN_Assert(request_ptr->type == DN_NETRequestType_WS); - DN_NETEmcRequest *emc_request = DN_Cast(DN_NETEmcRequest *) request_ptr->context[1]; - switch (send) { - default: DN_AssertInvalidCodePath; break; - case DN_NETWSSend_Text: { - DN_U64 pos = DN_MemListPos(request_ptr->start_response_arena.mem); - DN_Str8 data_null_terminated = DN_Str8FromStr8Arena(data, &request_ptr->start_response_arena); - result = emscripten_websocket_send_utf8_text(emc_request->socket, data_null_terminated.data); - DN_MemListPopTo(request_ptr->arena.mem, pos); - } break; - - case DN_NETWSSend_Binary: { - result = emscripten_websocket_send_binary(emc_request->socket, data.data, data.size); - } break; - - case DN_NETWSSend_Close: { - result = emscripten_websocket_close(emc_request->socket, 0, nullptr); - } break; - } - } - // TODO: Handle result, the header file doesn't really elucidate what this result value is - (void)result; -} - -static DN_NETResponse DN_NET_EmcHandleFinishedRequest_(DN_NETCore *net, DN_NETEmcCore *emc, DN_NETRequestHandle handle, DN_NETRequest *request, DN_Arena *arena) -{ - // NOTE: Generate the response, copy out the strings into the user given memory - DN_NETEmcRequest *emc_request = DN_Cast(DN_NETEmcRequest *) request->context[1]; - DN_NETResponse result = request->response; - bool end_request = true; - bool dequeue_request = true; - if (request->type == DN_NETRequestType_HTTP) { - result.body = DN_Str8FromStr8Arena(result.body, arena); - } else { - // NOTE: Get emscripten contexts - DN_NETEmcWSEvent *emc_event = emc_request->first_event; - DN_Assert(emc_event); - - DN_AssertF((emc_event->state >= DN_NETResponseState_WSOpen && emc_event->state <= DN_NETResponseState_WSPong) || emc_event->state == DN_NETResponseState_Error, - "emc_event=%p", emc_event); - - // NOTE: Build the result - result.state = emc_event->state; - result.request = handle; - result.body = DN_Str8FromStr8Arena(emc_event->payload, arena); - - // NOTE: Advance the event list - { - if (emc_request->first_event == emc_request->last_event) { - emc_request->last_event = emc_request->last_event->next; - DN_Assert(emc_request->first_event->next == emc_request->last_event); - } - emc_request->first_event = emc_event->next; - - // NOTE: If there's still an event on the request then we do not dequeue the request from the - // response list. The user can still "wait" for a response to read more data from it. - if (emc_request->first_event) - dequeue_request = false; - } - - if (result.state != DN_NETResponseState_WSClose) - end_request = false; - } - - // NOTE: Remove request from the response list which is doubly-linked - if (dequeue_request) { - if (request->prev) { - DN_AssertF(request->prev->next == request, "next=%p vs request=%p", request->prev->next, request); - request->prev->next = request->next; - } - - if (request->next) { - DN_AssertF(request->next->prev == request, "prev=%p vs request=%p", request->next->prev, request); - request->next->prev = request->prev; - } - - if (request == emc->response_list) - emc->response_list = emc->response_list->next; - - request->prev = nullptr; - request->next = nullptr; - DN_Assert(emc_request->first_event == nullptr); - DN_Assert(emc_request->last_event == nullptr); - - // NOTE: Deallocate the memory used in the request and reset the string builder (as all - // payload(s) have been read from the request). - if (!end_request) - DN_ArenaTempEnd(&request->start_response_arena, DN_ArenaReset_Yes); - } - - if (end_request) { - DN_NET_EndFinishedRequest(request); - emscripten_websocket_delete(emc_request->socket); - emc_request->socket = 0; - - DN_NETEmcCore *emc = DN_Cast(DN_NETEmcCore *) net->context; - request->next = emc->free_list; - request->prev = nullptr; - emc->free_list = request; - } - - return result; -} - -static DN_OSSemaphoreWaitResult DN_NET_EmcSemaphoreWait_(DN_OSSemaphore *sem, DN_U32 timeout_ms) -{ - // NOTE: In emscripten you can't just block on the semaphore with 'timeout_ms' because it needs - // to yield to the javascript's event loop otherwise the fetching step cannot progress. Instead - // we use a timeout of 0 to just immediately check if the semaphore has been signalled, if not, - // then we yield to the event loop by calling sleep. - // - // Once yielded, fetch will execute and eventually in the callback it will signal the semaphore - // where it'll return and we can break out of the simulated "timeout". - DN_OSSemaphoreWaitResult result = {}; - DN_U32 timeout_remaining_ms = timeout_ms; - DN_F64 begin_ms = emscripten_get_now(); - for (;;) { - result = DN_OS_SemaphoreWait(sem, 0); - if (result == DN_OSSemaphoreWaitResult_Success) - break; - if (timeout_remaining_ms <= 0) - break; - - emscripten_sleep(100 /*ms*/); - DN_F64 end_ms = emscripten_get_now(); - DN_USize duration_ms = DN_Cast(DN_USize)(end_ms - begin_ms); - timeout_remaining_ms = timeout_remaining_ms >= duration_ms ? timeout_remaining_ms - duration_ms : 0; - begin_ms = end_ms; - } - return result; -} - -DN_NETResponse DN_NET_EmcWaitForResponse(DN_NETRequestHandle handle, DN_Arena *arena, DN_U32 timeout_ms) -{ - DN_NETResponse result = {}; - DN_NETRequest *request_ptr = DN_Cast(DN_NETRequest *) handle.handle; - if (request_ptr && request_ptr->gen == handle.gen) { - DN_NETCore *net = DN_Cast(DN_NETCore *) request_ptr->context[0]; - DN_NETEmcCore *emc = DN_Cast(DN_NETEmcCore *) net->context; - DN_Assert(emc); - DN_OSSemaphoreWaitResult wait = DN_NET_EmcSemaphoreWait_(&request_ptr->completion_sem, timeout_ms); - if (wait != DN_OSSemaphoreWaitResult_Success) - return result; - - result = DN_NET_EmcHandleFinishedRequest_(net, emc, handle, request_ptr, arena); - - // NOTE: Decrement the global 'request done' completion semaphore since the user consumed the - // request individually. - DN_OSSemaphoreWaitResult net_wait_result = DN_OS_SemaphoreWait(&net->completion_sem, 0 /*timeout_ms*/); - DN_AssertF(net_wait_result == DN_OSSemaphoreWaitResult_Success, "Wait result was: %zu", DN_Cast(DN_USize) net_wait_result); - } - return result; -} - -DN_NETResponse DN_NET_EmcWaitForAnyResponse(DN_NETCore *net, DN_Arena *arena, DN_U32 timeout_ms) -{ - DN_NETEmcCore *emc = DN_Cast(DN_NETEmcCore *) net->context; - DN_Assert(emc); - - DN_NETResponse result = {}; - DN_OSSemaphoreWaitResult wait = DN_NET_EmcSemaphoreWait_(&net->completion_sem, timeout_ms); - if (wait != DN_OSSemaphoreWaitResult_Success) - return result; - - DN_AssertF(emc->response_list, - "This should be set otherwise we bumped the completion sem without queueing into the " - "done list or we forgot to wait on the global semaphore after a request finished"); - - // NOTE: Decrement the request's completion semaphore since the user consumed the global semaphore - DN_NETRequest *request_ptr = emc->response_list; - DN_OSSemaphoreWaitResult net_wait_result = DN_OS_SemaphoreWait(&request_ptr->completion_sem, 0 /*timeout_ms*/); - DN_AssertF(net_wait_result == DN_OSSemaphoreWaitResult_Success, "Wait result was: %zu", DN_Cast(DN_USize) net_wait_result); - - DN_NETRequestHandle request = {}; - request.handle = DN_Cast(DN_UPtr) request_ptr; - request.gen = request_ptr->gen; - result = DN_NET_EmcHandleFinishedRequest_(net, emc, request, request_ptr, arena); - - return result; -} - diff --git a/Source/Extra/dn_net_emscripten.h b/Source/Extra/dn_net_emscripten.h deleted file mode 100644 index 6949b73..0000000 --- a/Source/Extra/dn_net_emscripten.h +++ /dev/null @@ -1,17 +0,0 @@ -#if !defined(DN_NET_EMSCRIPTEN_H) -#define DN_NET_EMSCRIPTEN_H - -#if defined(_CLANGD) - #include "dn_net.h" -#endif - -DN_NETInterface DN_NET_EmcInterface(); -void DN_NET_EmcInit (DN_NETCore *net, char *base, DN_U64 base_size); -void DN_NET_EmcDeinit (DN_NETCore *net); -DN_NETRequestHandle DN_NET_EmcDoHTTP (DN_NETCore *net, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args); -DN_NETRequestHandle DN_NET_EmcDoWS (DN_NETCore *net, DN_Str8 url); -void DN_NET_EmcDoWSSend (DN_NETRequestHandle handle, DN_Str8 data, DN_NETWSSend send); -DN_NETResponse DN_NET_EmcWaitForResponse (DN_NETRequestHandle handle, DN_Arena *arena, DN_U32 timeout_ms); -DN_NETResponse DN_NET_EmcWaitForAnyResponse(DN_NETCore *net, DN_Arena *arena, DN_U32 timeout_ms); - -#endif // DN_NET_EMSCRIPTEN_H diff --git a/Source/Extra/dn_tests.cpp b/Source/Extra/dn_tests.cpp index affc480..657289c 100644 --- a/Source/Extra/dn_tests.cpp +++ b/Source/Extra/dn_tests.cpp @@ -1,8 +1,6 @@ #if defined(_CLANGD) - #define DN_H_WITH_OS 1 - #include "../dn.h" - #include "../Extra/dn_net.h" - #include "../Extra/dn_net_curl.h" + #define DN_WITH_OS 1 + #include "dn.h" #include "../Standalone/dn_utest.h" #define DN_UNIT_TESTS_WITH_KECCAK diff --git a/Source/Extra/dn_tests_main.cpp b/Source/Extra/dn_tests_main.cpp index 5dad454..aa572ac 100644 --- a/Source/Extra/dn_tests_main.cpp +++ b/Source/Extra/dn_tests_main.cpp @@ -1,24 +1,18 @@ -#if defined(DN_UNIT_TESTS_WITH_CURL) +#if DN_WITH_NET_CURL #define DN_NO_WINDOWS_H_REPLACEMENT_HEADER #endif #define DN_ARENA_TEMP_MEM_UAF_GUARD 1 #define DN_ARENA_TEMP_MEM_UAF_TRACE_ON_BY_DEFAULT 0 -#define DN_H_WITH_OS 1 -#define DN_H_WITH_CORE 1 -#define DN_H_WITH_HASH 1 -#define DN_H_WITH_HELPERS 1 -#define DN_H_WITH_ASYNC 1 -#define DN_H_WITH_NET 1 +#define DN_WITH_OS 1 +#define DN_WITH_NET 1 #include "../dn.h" -#include "../dn.cpp" -#if defined(DN_UNIT_TESTS_WITH_CURL) +#if DN_WITH_NET_CURL #define CURL_STATICLIB #include - #include "../Extra/dn_net_curl.h" - #include "../Extra/dn_net_curl.cpp" #endif +#include "../dn.cpp" #if defined(DN_PLATFORM_EMSCRIPTEN) #include @@ -36,7 +30,7 @@ DN_MSVC_WARNING_DISABLE(6262) // Function uses '29804' bytes of stack. Consider int main(int, char**) { DN_Core dn = {}; - DN_Init(&dn, DN_InitFlags_LogAllFeatures | DN_InitFlags_OS | DN_InitFlags_ThreadContext, DN_TCInitArgsDefault()); + DN_Init(&dn, DN_InitFlags_LogAllFeatures | DN_InitFlags_OS, DN_TCInitArgsDefault()); DN_TST_RunSuite(DN_TSTPrint_Yes); return 0; } diff --git a/Source/OS/dn_os.cpp b/Source/OS/dn_os.cpp deleted file mode 100644 index 2cfe3d2..0000000 --- a/Source/OS/dn_os.cpp +++ /dev/null @@ -1,1414 +0,0 @@ -#define DN_OS_CPP - -#if defined(_CLANGD) - #define DN_H_WITH_OS 1 - #define DN_H_WITH_CORE 1 - #include "../dn.h" -#endif - -#if defined(DN_PLATFORM_POSIX) -#include // get_nprocs -#include // getpagesize -#endif - -static void *DN_OS_MemFuncsHeapAllocShim_(DN_USize size) -{ - void *result = DN_OS_MemAlloc(size, DN_ZMem_Yes); - return result; -} - -DN_API DN_MemFuncs DN_MemFuncsFromType(DN_MemFuncsType type) -{ - DN_MemFuncs result = {}; - result.type = type; - switch (type) { - case DN_MemFuncsType_Nil: break; - case DN_MemFuncsType_Heap: { - result.heap_alloc = DN_OS_MemFuncsHeapAllocShim_; - result.heap_dealloc = DN_OS_MemDealloc; - } break; - - case DN_MemFuncsType_Virtual: { - DN_Core *dn = DN_Get(); - DN_Assert(dn->init_flags & DN_InitFlags_OS); - result.virtual_page_size = dn->os.page_size; - result.virtual_reserve = DN_OS_MemReserve; - result.virtual_commit = DN_OS_MemCommit; - result.virtual_release = DN_OS_MemRelease; - } break; - } - return result; -} - -DN_API DN_MemFuncs DN_MemFuncsDefault() -{ - DN_Core *dn = DN_Get(); - DN_MemFuncsType type = DN_MemFuncsType_Heap; - if (dn->os_init) { -#if !defined(DN_PLATFORM_EMSCRIPTEN) - type = DN_MemFuncsType_Virtual; -#endif - } - DN_MemFuncs result = DN_MemFuncsFromType(type); - return result; -} - -DN_API DN_MemList DN_MemListFromHeap(DN_U64 size, DN_MemFlags flags) -{ - DN_MemFuncs mem_funcs = DN_MemFuncsFromType(DN_MemFuncsType_Heap); - DN_MemList result = DN_MemListFromMemFuncs(size, size, flags, mem_funcs); - return result; -} - -DN_API DN_MemList DN_MemListFromVMem(DN_U64 reserve, DN_U64 commit, DN_MemFlags flags) -{ - DN_MemFuncs mem_funcs = DN_MemFuncsFromType(DN_MemFuncsType_Virtual); - DN_MemList result = DN_MemListFromMemFuncs(reserve, commit, flags, mem_funcs); - return result; -} - -DN_API DN_Arena DN_ArenaFromHeap(DN_U64 size, DN_MemFlags flags) -{ - DN_MemList mem = DN_MemListFromHeap(size, flags); - DN_Arena result = {}; - result.flags |= DN_ArenaFlags_OwnsMemList; - result.mem = DN_MemListNewCopy(&mem, DN_MemList, &mem); - return result; -} - -DN_API DN_Arena DN_ArenaFromVMem(DN_U64 reserve, DN_U64 commit, DN_MemFlags flags) -{ - DN_MemList mem = DN_MemListFromVMem(reserve, commit, flags); - DN_Arena result = {}; - result.flags |= DN_ArenaFlags_OwnsMemList; - result.mem = DN_MemListNewCopy(&mem, DN_MemList, &mem); - return result; -} - -DN_API void DN_ArenaDeinit(DN_Arena *arena) -{ - if (arena->flags & DN_ArenaFlags_OwnsMemList) - DN_MemListDeinit(arena->mem); -} - -DN_API DN_Str8 DN_Str8FromHeapF(DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_USize size = DN_FmtVSize(fmt, args); - DN_Str8 result = DN_Str8FromHeap(size, DN_ZMem_No); - DN_VSNPrintF(result.data, DN_Cast(int)(result.size + 1), fmt, args); - va_end(args); - return result; -} - -DN_API DN_Str8 DN_Str8FromHeap(DN_USize size, DN_ZMem z_mem) -{ - DN_Str8 result = {}; - result.data = DN_Cast(char *)DN_OS_MemAlloc(size + 1, z_mem); - if (result.data) { - result.size = size; - result.data[result.size] = 0; - } - return result; -} - -DN_API DN_Str8 DN_Str8PadNewLines(DN_Arena *arena, DN_Str8 src, DN_Str8 pad) -{ - // TODO: Implement this without requiring TLS so it can go into base strings - DN_TCScratch scratch = DN_TCScratchBeginArena(&arena, 1); - DN_Str8Builder builder = DN_Str8BuilderFromArena(&scratch.arena); - DN_Str8BSplitResult split = DN_Str8BSplit(src, DN_Str8Lit("\n")); - while (split.lhs.size) { - DN_Str8BuilderAppendRef(&builder, pad); - DN_Str8BuilderAppendRef(&builder, split.lhs); - split = DN_Str8BSplit(split.rhs, DN_Str8Lit("\n")); - if (split.lhs.size) - DN_Str8BuilderAppendRef(&builder, DN_Str8Lit("\n")); - } - DN_Str8 result = DN_Str8FromStr8BuilderArena(&builder, arena); - DN_TCScratchEnd(&scratch); - return result; -} - -DN_API DN_Str8 DN_Str8BuilderBuildFromHeap(DN_Str8Builder const *builder) -{ - DN_Str8 result = DN_ZeroInit; - if (!builder || builder->string_size <= 0 || builder->count <= 0) - return result; - - result.data = DN_Cast(char *) DN_OS_MemAlloc(builder->string_size + 1, DN_ZMem_No); - if (!result.data) - return result; - - for (DN_Str8Link *link = builder->head; link; link = link->next) { - DN_Memcpy(result.data + result.size, link->string.data, link->string.size); - result.size += link->string.size; - } - - result.data[result.size] = 0; - DN_Assert(result.size == builder->string_size); - return result; -} - -DN_API void DN_OS_LogPrint(DN_LogTypeParam type, void *user_data, DN_CallSite call_site, DN_LogFlags flags, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - DN_Assert(user_data); - DN_OSCore *os = DN_Cast(DN_OSCore *)user_data; - - // NOTE: Open log file for appending if requested - DN_TicketMutex_Begin(&os->log_file_mutex); - if (os->log_to_file && !os->log_file.handle && !os->log_file.error) { - DN_TCScratch scratch = DN_TCScratchBeginArena(nullptr, 0); - DN_Str8 exe_dir = DN_OS_EXEDir(&scratch.arena); - DN_Str8 log_path = DN_OS_PathF(&scratch.arena, "%.*s/dn.log", DN_Str8PrintFmt(exe_dir)); - os->log_file = DN_OS_FileOpen(log_path, DN_OSFileOpen_CreateAlways, DN_OSFileAccess_AppendOnly, nullptr); - DN_TCScratchEnd(&scratch); - } - DN_TicketMutex_End(&os->log_file_mutex); - - bool print_prefix = DN_BitIsNotSet(flags, DN_LogFlags_NoPrefix); - char prefix_buffer[128] = {}; - DN_LogPrefixSize prefix_size = {}; - if (print_prefix) { - DN_LogStyle style = {}; - if (!os->log_no_colour) { - style.colour = true; - style.bold = DN_LogBold_Yes; - if (type.is_u32_enum) { - switch (type.u32) { - case DN_LogType_Debug: { - style.colour = false; - style.bold = DN_LogBold_No; - } break; - - case DN_LogType_Info: { - style.g = 0x87; - style.b = 0xff; - } break; - - case DN_LogType_Warning: { - style.r = 0xff; - style.g = 0xff; - } break; - - case DN_LogType_Error: { - style.r = 0xff; - } break; - } - } - } - - DN_Date os_date = DN_OS_DateLocalTimeNow(); - DN_LogDate log_date = {}; - log_date.year = os_date.year; - log_date.month = os_date.month; - log_date.day = os_date.day; - log_date.hour = os_date.hour; - log_date.minute = os_date.minutes; - log_date.second = os_date.seconds; - prefix_size = DN_LogMakePrefix(style, type, call_site, log_date, prefix_buffer, sizeof(prefix_buffer)); - } - - va_list args_copy; - va_copy(args_copy, args); - DN_TicketMutex_Begin(&os->log_file_mutex); - { - if (print_prefix) { - DN_OS_FileWrite(&os->log_file, DN_Str8FromPtr(prefix_buffer, prefix_size.size), nullptr); - DN_OS_FileWriteF(&os->log_file, nullptr, "%*s ", DN_Cast(int) prefix_size.padding, ""); - } - DN_OS_FileWriteFV(&os->log_file, nullptr, fmt, args_copy); - if (!DN_BitIsSet(flags, DN_LogFlags_NoNewLine)) - DN_OS_FileWrite(&os->log_file, DN_Str8Lit("\n"), nullptr); - } - DN_TicketMutex_End(&os->log_file_mutex); - va_end(args_copy); - - DN_TicketMutex_Begin(&os->log_mutex); - { - if (print_prefix) - DN_OS_PrintF(DN_OSPrintDest_Err, "%.*s%*s ", DN_Cast(int) prefix_size.size, prefix_buffer, DN_Cast(int) prefix_size.padding, ""); - - if (DN_BitIsSet(flags, DN_LogFlags_NoNewLine)) - DN_OS_PrintFV(DN_OSPrintDest_Err, fmt, args); - else - DN_OS_PrintLnFV(DN_OSPrintDest_Err, fmt, args); - } - DN_TicketMutex_End(&os->log_mutex); -} - -DN_API void DN_OS_SetLogPrintFuncToOS() -{ - DN_Core *dn = DN_Get(); - DN_LogSetPrintFunc(DN_OS_LogPrint, &dn->os); -} - -// NOTE: Date -DN_API DN_Str8x32 DN_OS_DateLocalTimeStr8(DN_Date time, char date_separator, char hms_separator) -{ - DN_Str8x32 result = DN_Str8x32FromFmt("%hu%c%02hhu%c%02hhu %02hhu%c%02hhu%c%02hhu", - time.year, - date_separator, - time.month, - date_separator, - time.day, - time.hour, - hms_separator, - time.minutes, - hms_separator, - time.seconds); - return result; -} - -DN_API DN_Str8x32 DN_OS_DateLocalTimeStr8Now(char date_separator, char hms_separator) -{ - DN_Date time = DN_OS_DateLocalTimeNow(); - DN_Str8x32 result = DN_OS_DateLocalTimeStr8(time, date_separator, hms_separator); - return result; -} - -// NOTE: Other -DN_API DN_Str8 DN_OS_EXEDir(DN_Arena *arena) -{ - DN_Str8 result = {}; - if (!arena) - return result; - DN_TCScratch scratch = DN_TCScratchBeginArena(&arena, 1); - DN_Str8 exe_path = DN_OS_EXEPath(&scratch.arena); - DN_Str8 separators[] = {DN_Str8Lit("/"), DN_Str8Lit("\\")}; - DN_Str8BSplitResult split = DN_Str8BSplitLastArray(exe_path, separators, DN_ArrayCountU(separators)); - result = DN_Str8FromStr8Arena(split.lhs, arena); - DN_TCScratchEnd(&scratch); - return result; -} - -// NOTE: Counters -DN_API DN_F64 DN_OS_PerfCounterS(uint64_t begin, uint64_t end) -{ - uint64_t frequency = DN_OS_PerfCounterFrequency(); - uint64_t ticks = end - begin; - DN_F64 result = ticks / DN_Cast(DN_F64) frequency; - return result; -} - -DN_API DN_F64 DN_OS_PerfCounterMs(uint64_t begin, uint64_t end) -{ - uint64_t frequency = DN_OS_PerfCounterFrequency(); - uint64_t ticks = end - begin; - DN_F64 result = (ticks * 1'000) / DN_Cast(DN_F64) frequency; - return result; -} - -DN_API DN_F64 DN_OS_PerfCounterUs(uint64_t begin, uint64_t end) -{ - uint64_t frequency = DN_OS_PerfCounterFrequency(); - uint64_t ticks = end - begin; - DN_F64 result = (ticks * 1'000'000) / DN_Cast(DN_F64) frequency; - return result; -} - -DN_API DN_F64 DN_OS_PerfCounterNs(uint64_t begin, uint64_t end) -{ - uint64_t frequency = DN_OS_PerfCounterFrequency(); - uint64_t ticks = end - begin; - DN_F64 result = (ticks * 1'000'000'000) / DN_Cast(DN_F64) frequency; - return result; -} - -DN_API DN_OSTimer DN_OS_TimerBegin() -{ - DN_OSTimer result = {}; - result.start = DN_OS_PerfCounterNow(); - return result; -} - -DN_API void DN_OS_TimerEnd(DN_OSTimer *timer) -{ - timer->end = DN_OS_PerfCounterNow(); -} - -DN_API DN_F64 DN_OS_TimerS(DN_OSTimer timer) -{ - DN_F64 result = DN_OS_PerfCounterS(timer.start, timer.end); - return result; -} - -DN_API DN_F64 DN_OS_TimerMs(DN_OSTimer timer) -{ - DN_F64 result = DN_OS_PerfCounterMs(timer.start, timer.end); - return result; -} - -DN_API DN_F64 DN_OS_TimerUs(DN_OSTimer timer) -{ - DN_F64 result = DN_OS_PerfCounterUs(timer.start, timer.end); - return result; -} - -DN_API DN_F64 DN_OS_TimerNs(DN_OSTimer timer) -{ - DN_F64 result = DN_OS_PerfCounterNs(timer.start, timer.end); - return result; -} - -DN_API uint64_t DN_OS_EstimateTSCPerSecond(uint64_t duration_ms_to_gauge_tsc_frequency) -{ - uint64_t os_frequency = DN_OS_PerfCounterFrequency(); - uint64_t os_target_elapsed = duration_ms_to_gauge_tsc_frequency * os_frequency / 1000ULL; - uint64_t tsc_begin = DN_CPUGetTSC(); - uint64_t result = 0; - if (tsc_begin) { - uint64_t os_elapsed = 0; - for (uint64_t os_begin = DN_OS_PerfCounterNow(); os_elapsed < os_target_elapsed;) - os_elapsed = DN_OS_PerfCounterNow() - os_begin; - uint64_t tsc_end = DN_CPUGetTSC(); - uint64_t tsc_elapsed = tsc_end - tsc_begin; - result = tsc_elapsed / os_elapsed * os_frequency; - } - return result; -} - -DN_API bool DN_OS_PathIsOlderThan(DN_Str8 path, DN_Str8 check_against) -{ - DN_OSPathInfo file_info = DN_OS_PathInfo(path); - DN_OSPathInfo check_against_info = DN_OS_PathInfo(check_against); - bool result = !file_info.exists || file_info.last_write_time_in_s < check_against_info.last_write_time_in_s; - return result; -} - -DN_API bool DN_OS_FileWrite(DN_OSFile *file, DN_Str8 buffer, DN_ErrSink *error) -{ - bool result = DN_OS_FileWritePtr(file, buffer.data, buffer.size, error); - return result; -} - -struct DN_OSFileWriteChunker_ -{ - DN_ErrSink *err; - DN_OSFile *file; - bool success; -}; - -static char *DN_OS_FileWriteChunker_(const char *buf, void *user, int len) -{ - DN_OSFileWriteChunker_ *chunker = DN_Cast(DN_OSFileWriteChunker_ *)user; - chunker->success = DN_OS_FileWritePtr(chunker->file, buf, len, chunker->err); - char *result = chunker->success ? DN_Cast(char *) buf : nullptr; - return result; -} - -DN_API bool DN_OS_FileWriteFV(DN_OSFile *file, DN_ErrSink *error, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - bool result = false; - if (!file || !fmt) - return result; - - DN_OSFileWriteChunker_ chunker = {}; - chunker.err = error; - chunker.file = file; - char buffer[STB_SPRINTF_MIN]; - STB_SPRINTF_DECORATE(vsprintfcb)(DN_OS_FileWriteChunker_, &chunker, buffer, fmt, args); - - result = chunker.success; - return result; -} - -DN_API bool DN_OS_FileWriteF(DN_OSFile *file, DN_ErrSink *error, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - bool result = DN_OS_FileWriteFV(file, error, fmt, args); - va_end(args); - return result; -} - -DN_API DN_Str8 DN_OS_FileReadAll(DN_Allocator allocator, DN_Str8 path, DN_ErrSink *err) -{ - // NOTE: Query file size - DN_Str8 result = {}; - DN_OSPathInfo path_info = DN_OS_PathInfo(path); - if (!path_info.exists) { - DN_ErrSinkAppendF(err, 1, "File does not exist/could not be queried for reading '%.*s'", DN_Str8PrintFmt(path)); - return result; - } - - // NOTE: Allocate - DN_Arena temp_arena = {}; - if (allocator.type == DN_AllocatorType_Arena) { - DN_Arena *arena = DN_Cast(DN_Arena *) allocator.context; - temp_arena = DN_ArenaTempBeginFromArena(arena); - result = DN_Str8AllocArena(path_info.size, DN_ZMem_No, &temp_arena); - } else { - DN_Pool *pool = DN_Cast(DN_Pool *) allocator.context; - result = DN_Str8AllocPool(path_info.size, pool); - } - - if (!result.data) { - DN_Str8x32 bytes_str = DN_Str8x32FromByteCountU64Auto(path_info.size); - DN_ErrSinkAppendF(err, 1 /*err_code*/, "Failed to allocate %.*s for reading file '%.*s'", DN_Str8PrintFmt(bytes_str), DN_Str8PrintFmt(path)); - return result; - } - - // NOTE: Read all - DN_OSFile file = DN_OS_FileOpen(path, DN_OSFileOpen_OpenIfExist, DN_OSFileAccess_Read, err); - DN_OSFileRead read = DN_OS_FileRead(&file, result.data, result.size, err); - bool failed = file.error || !read.success; - - if (allocator.type == DN_AllocatorType_Arena) { - DN_ArenaTempEnd(&temp_arena, failed ? DN_ArenaReset_Yes : DN_ArenaReset_No); - } else { - if (failed) { - DN_Pool *pool = DN_Cast(DN_Pool *) allocator.context; - DN_PoolDealloc(pool, result.data); - } - } - - if (failed) - result = {}; - - DN_OS_FileClose(&file); - return result; -} - -DN_API DN_Str8 DN_OS_FileReadAllArena(DN_Arena *arena, DN_Str8 path, DN_ErrSink *err) -{ - DN_Allocator allocator = {}; - allocator.type = DN_AllocatorType_Arena; - allocator.context = arena; - DN_Str8 result = DN_OS_FileReadAll(allocator, path, err); - return result; -} - -DN_API DN_Str8 DN_OS_FileReadAllPool(DN_Pool *pool, DN_Str8 path, DN_ErrSink *err) -{ - DN_Allocator allocator = {}; - allocator.type = DN_AllocatorType_Pool; - allocator.context = pool; - DN_Str8 result = DN_OS_FileReadAll(allocator, path, err); - return result; -} - -DN_API bool DN_OS_FileWriteAll(DN_Str8 path, DN_Str8 buffer, DN_ErrSink *error) -{ - DN_OSFile file = DN_OS_FileOpen(path, DN_OSFileOpen_CreateAlways, DN_OSFileAccess_Write, error); - bool result = DN_OS_FileWrite(&file, buffer, error); - DN_OS_FileClose(&file); - return result; -} - -DN_API bool DN_OS_FileWriteAllFV(DN_Str8 file_path, DN_ErrSink *error, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - DN_TCScratch scratch = DN_TCScratchBeginArena(nullptr, 0); - DN_Str8 buffer = DN_Str8FromFmtVArena(&scratch.arena, fmt, args); - bool result = DN_OS_FileWriteAll(file_path, buffer, error); - DN_TCScratchEnd(&scratch); - return result; -} - -DN_API bool DN_OS_FileWriteAllF(DN_Str8 file_path, DN_ErrSink *error, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - bool result = DN_OS_FileWriteAllFV(file_path, error, fmt, args); - va_end(args); - return result; -} - -DN_API bool DN_OS_FileWriteAllSafe(DN_Str8 path, DN_Str8 buffer, DN_ErrSink *error) -{ - DN_TCScratch scratch = DN_TCScratchBeginArena(nullptr, 0); - DN_Str8 tmp_path = DN_Str8FromFmtArena(&scratch.arena, "%.*s.tmp", DN_Str8PrintFmt(path)); - if (!DN_OS_FileWriteAll(tmp_path, buffer, error)) { - DN_TCScratchEnd(&scratch); - return false; - } - if (!DN_OS_FileCopy(tmp_path, path, true /*overwrite*/, error)) { - DN_TCScratchEnd(&scratch); - return false; - } - if (!DN_OS_PathDelete(tmp_path)) { - DN_TCScratchEnd(&scratch); - return false; - } - DN_TCScratchEnd(&scratch); - return true; -} - -DN_API bool DN_OS_FileWriteAllSafeFV(DN_Str8 path, DN_ErrSink *error, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - DN_TCScratch scratch = DN_TCScratchBeginArena(nullptr, 0); - DN_Str8 buffer = DN_Str8FromFmtVArena(&scratch.arena, fmt, args); - bool result = DN_OS_FileWriteAllSafe(path, buffer, error); - DN_TCScratchEnd(&scratch); - return result; -} - -DN_API bool DN_OS_FileWriteAllSafeF(DN_Str8 path, DN_ErrSink *error, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - bool result = DN_OS_FileWriteAllSafeFV(path, error, fmt, args); - return result; -} - -DN_API DN_Str8 DN_OS_Str8FromPathInfoType(DN_OSPathInfoType type) -{ - DN_Str8 result = DN_Str8Lit("BAD PATH INFO TYPE"); - switch(type) { - case DN_OSPathInfoType_Unknown: result = DN_Str8Lit("Unknown"); break; - case DN_OSPathInfoType_Directory: result = DN_Str8Lit("Directory"); break; - case DN_OSPathInfoType_File: result = DN_Str8Lit("File"); break; - } - return result; -} - -DN_API bool DN_OS_PathAddRef(DN_Arena *arena, DN_OSPath *fs_path, DN_Str8 path) -{ - if (!arena || !fs_path || path.size == 0) - return false; - - if (path.size <= 0) - return true; - - DN_Str8 const delimiter_array[] = { - DN_Str8Lit("\\"), - DN_Str8Lit("/")}; - - if (fs_path->links_size == 0) - fs_path->has_prefix_path_separator = (path.data[0] == '/'); - - for (;;) { - DN_Str8BSplitResult delimiter = DN_Str8BSplitArray(path, delimiter_array, DN_ArrayCountU(delimiter_array)); - for (; delimiter.lhs.data; delimiter = DN_Str8BSplitArray(delimiter.rhs, delimiter_array, DN_ArrayCountU(delimiter_array))) { - if (delimiter.lhs.size <= 0) - continue; - - DN_OSPathLink *link = DN_ArenaNew(arena, DN_OSPathLink, DN_ZMem_Yes); - if (!link) - return false; - - link->string = delimiter.lhs; - link->prev = fs_path->tail; - if (fs_path->tail) - fs_path->tail->next = link; - else - fs_path->head = link; - fs_path->tail = link; - fs_path->links_size += 1; - fs_path->string_size += delimiter.lhs.size; - } - - if (!delimiter.lhs.data) - break; - } - - return true; -} - -DN_API bool DN_OS_PathAdd(DN_Arena *arena, DN_OSPath *fs_path, DN_Str8 path) -{ - DN_Str8 copy = DN_Str8FromStr8Arena(path, arena); - bool result = copy.size ? true : DN_OS_PathAddRef(arena, fs_path, copy); - return result; -} - -DN_API bool DN_OS_PathAddF(DN_Arena *arena, DN_OSPath *fs_path, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_Str8 path = DN_Str8FromFmtVArena(arena, fmt, args); - va_end(args); - bool result = DN_OS_PathAddRef(arena, fs_path, path); - return result; -} - -DN_API bool DN_OS_PathPop(DN_OSPath *fs_path) -{ - if (!fs_path) - return false; - - if (fs_path->tail) { - DN_Assert(fs_path->head); - fs_path->links_size -= 1; - fs_path->string_size -= fs_path->tail->string.size; - fs_path->tail = fs_path->tail->prev; - if (fs_path->tail) - fs_path->tail->next = nullptr; - else - fs_path->head = nullptr; - } else { - DN_Assert(!fs_path->head); - } - - return true; -} - -DN_API DN_Str8 DN_OS_PathTo(DN_Arena *arena, DN_Str8 path, DN_Str8 path_separator) -{ - DN_OSPath fs_path = {}; - DN_OS_PathAddRef(arena, &fs_path, path); - DN_Str8 result = DN_OS_PathBuildWithSeparator(arena, &fs_path, path_separator); - return result; -} - -DN_API DN_Str8 DN_OS_PathToF(DN_Arena *arena, DN_Str8 path_separator, DN_FMT_ATTRIB char const *fmt, ...) -{ - DN_TCScratch scratch = DN_TCScratchBeginArena(&arena, 1); - va_list args; - va_start(args, fmt); - DN_Str8 path = DN_Str8FromFmtVArena(&scratch.arena, fmt, args); - va_end(args); - DN_Str8 result = DN_OS_PathTo(arena, path, path_separator); - DN_TCScratchEnd(&scratch); - return result; -} - -DN_API DN_Str8 DN_OS_Path(DN_Arena *arena, DN_Str8 path) -{ - DN_Str8 result = DN_OS_PathTo(arena, path, DN_OSPathSeperatorString); - return result; -} - -DN_API DN_Str8 DN_OS_PathF(DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, ...) -{ - DN_TCScratch scratch = DN_TCScratchBeginArena(&arena, 1); - va_list args; - va_start(args, fmt); - DN_Str8 path = DN_Str8FromFmtVArena(&scratch.arena, fmt, args); - va_end(args); - DN_Str8 result = DN_OS_Path(arena, path); - DN_TCScratchEnd(&scratch); - return result; -} - -DN_API DN_Str8 DN_OS_PathBuildWithSeparator(DN_Arena *arena, DN_OSPath const *fs_path, DN_Str8 path_separator) -{ - DN_Str8 result = {}; - if (!fs_path || fs_path->links_size <= 0) - return result; - - // NOTE: Each link except the last one needs the path separator appended to it, '/' or '\\' - DN_USize string_size = (fs_path->has_prefix_path_separator ? path_separator.size : 0) + fs_path->string_size + ((fs_path->links_size - 1) * path_separator.size); - result = DN_Str8AllocArena(string_size, DN_ZMem_No, arena); - if (result.data) { - char *dest = result.data; - if (fs_path->has_prefix_path_separator) { - DN_Memcpy(dest, path_separator.data, path_separator.size); - dest += path_separator.size; - } - - for (DN_OSPathLink *link = fs_path->head; link; link = link->next) { - DN_Str8 string = link->string; - DN_Memcpy(dest, string.data, string.size); - dest += string.size; - - if (link != fs_path->tail) { - DN_Memcpy(dest, path_separator.data, path_separator.size); - dest += path_separator.size; - } - } - } - - result.data[string_size] = 0; - return result; -} - -// NOTE: DN_OSExec -DN_API DN_OSExecResult DN_OS_Exec(DN_Str8Slice cmd_line, DN_OSExecArgs *args, DN_Arena *arena, DN_ErrSink *error) -{ - DN_OSExecAsyncHandle async_handle = DN_OS_ExecAsync(cmd_line, args, error); - DN_OSExecResult result = DN_OS_ExecWait(async_handle, arena, error); - return result; -} - -DN_API DN_OSExecResult DN_OS_ExecOrAbort(DN_Str8Slice cmd_line, DN_OSExecArgs *args, DN_Arena *arena) -{ - DN_ErrSink *error = DN_TCErrSinkBegin(DN_ErrSinkMode_Nil); - DN_OSExecResult result = DN_OS_Exec(cmd_line, args, arena, error); - if (result.os_error_code) - DN_ErrSinkEndExitIfErrorF(error, result.os_error_code, "OS failed to execute the requested command returning the error code %u", result.os_error_code); - - if (result.exit_code) - DN_ErrSinkEndExitIfErrorF(error, result.exit_code, "OS executed command and returned non-zero exit code %u", result.exit_code); - DN_ErrSinkEndIgnore(error); - return result; -} - -// NOTE: DN_OSThread -static void DN_OS_ThreadExecute_(void *user_context) -{ - DN_OSThread *thread = DN_Cast(DN_OSThread *) user_context; - DN_TCInitFromMemFuncs(&thread->context, thread->thread_id, thread->tc_init_args, DN_MemFuncsDefault()); - DN_TCEquip(&thread->context); - if (thread->is_lane_set) { - DN_OS_TCThreadLaneEquip(thread->lane); - DN_OS_ThreadSetNameFmt("L%02zu/%02zu T%zu", thread->lane.index, thread->lane.count, thread->thread_id); - } else { - DN_OS_ThreadSetNameFmt("T%zu", thread->lane.index, thread->lane.count, thread->thread_id); - } - DN_OS_SemaphoreWait(&thread->init_semaphore, DN_OS_SEMAPHORE_INFINITE_TIMEOUT); - thread->func(thread); -} - -DN_API void DN_OS_ThreadSetNameFmt(char const *fmt, ...) -{ - DN_TCCore *tls = DN_TCGet(); - va_list args; - va_start(args, fmt); - tls->name = DN_Str8x64FromFmtV(fmt, args); - va_end(args); - - DN_Str8 name = DN_Str8FromPtr(tls->name.data, tls->name.size); -#if defined(DN_PLATFORM_WIN32) - DN_OS_W32ThreadSetName(name); -#else - DN_OS_PosixThreadSetName(name); -#endif -} - -DN_API DN_OSThreadLane DN_OS_ThreadLaneInit(DN_USize index, DN_USize thread_count, DN_OSBarrier barrier, DN_UPtr *shared_mem) -{ - DN_OSThreadLane result = {}; - result.index = index; - result.count = thread_count; - result.barrier = barrier; - result.shared_mem = shared_mem; - return result; -} - -DN_API void DN_OS_ThreadLaneSync(DN_OSThreadLane *lane, void **ptr_to_share) -{ - if (!lane) - return; - - // NOTE: Write the pointer into shared memory (if we're the lane producing the data) - bool sharing = false; - if (ptr_to_share && *ptr_to_share) { - DN_Memcpy(lane->shared_mem, ptr_to_share, sizeof(*ptr_to_share)); - sharing = true; - } - - DN_OS_BarrierWait(&lane->barrier); // NOTE: Ensure sharing lane has completed the write - - // NOTE: Read pointer from shared memory (if we're the other lanes that read the data) - if (ptr_to_share && !(*ptr_to_share)) { - sharing = true; - DN_Memcpy(ptr_to_share, lane->shared_mem, sizeof(*ptr_to_share)); - } - - if (sharing) - DN_OS_BarrierWait(&lane->barrier); // NOTE: Ensure the reading lanes have completed the read -} - -DN_API DN_V2USize DN_OS_ThreadLaneRange(DN_OSThreadLane const *lane, DN_USize values_count) -{ - DN_USize values_per_thread = values_count / lane->count; - DN_USize rem_values = values_count % lane->count; - bool thread_has_leftovers = lane->index < rem_values; - DN_USize leftovers_before_this_thread_index = 0; - - if (thread_has_leftovers) - leftovers_before_this_thread_index = lane->index; - else - leftovers_before_this_thread_index = rem_values; - - DN_USize thread_start_index = (values_per_thread * lane->index) + leftovers_before_this_thread_index; - DN_USize thread_values_count = values_per_thread + (thread_has_leftovers ? 1 : 0); - - DN_V2USize result = {}; - result.begin = thread_start_index; - result.end = result.begin + thread_values_count; - return result; -} - -DN_API DN_OSThreadLaneway DN_OS_ThreadLanewayFromArgs(DN_OSThread* threads, DN_USize threads_count, DN_UPtr* shared_mem) -{ - DN_OSThreadLaneway result = {}; - result.threads = threads; - result.threads_count = threads_count; - result.shared_mem = shared_mem; - result.barrier = DN_OS_BarrierInit(DN_Cast(DN_U32) result.threads_count); - return result; -} - -DN_API DN_OSThreadLaneway DN_OS_ThreadLanewayFromArena(DN_USize threads_count, DN_Arena* arena) -{ - DN_U64 mem_p = DN_MemListPos(arena->mem); - DN_OSThreadLaneway result = {}; - DN_OSThread* threads = DN_ArenaNewArray(arena, DN_OSThread, threads_count, DN_ZMem_No); - DN_UPtr* shared_mem = DN_ArenaNewZ(arena, DN_UPtr); - if (threads && shared_mem) - result = DN_OS_ThreadLanewayFromArgs(threads, threads_count, shared_mem); - else - DN_MemListPopTo(arena->mem, mem_p); - return result; -} - -DN_API void DN_OS_ThreadLanewayDispatch(DN_OSThreadLaneway *laneway, DN_OSThreadFunc *entry_point, DN_TCInitArgs tc_init_args, void *user_context) -{ - for (DN_ForItSize(it, DN_OSThread, laneway->threads, laneway->threads_count)) { - DN_OSThreadLane lane = DN_OS_ThreadLaneInit(it.index, laneway->threads_count, laneway->barrier, laneway->shared_mem); - DN_OS_ThreadInit(it.data, entry_point, &lane, tc_init_args, user_context); - } -} - -DN_API void DN_OS_ThreadLanewayJoin(DN_OSThreadLaneway *laneway, DN_TCDeinitArenas deinit_arenas) -{ - for (DN_ForItSize(it, DN_OSThread, laneway->threads, laneway->threads_count)) - DN_OS_ThreadJoin(it.data, deinit_arenas); - DN_OS_BarrierDeinit(&laneway->barrier); -} - -DN_API DN_OSThreadLane *DN_OS_TCThreadLane() -{ - DN_TCCore *tc = DN_TCGet(); - DN_OSThreadLane *result = tc ? DN_Cast(DN_OSThreadLane *) tc->lane_opaque : nullptr; - return result; -} - -DN_API void DN_OS_TCThreadLaneSync(void **ptr_to_share) -{ - DN_OSThreadLane *lane = DN_OS_TCThreadLane(); - DN_OS_ThreadLaneSync(lane, ptr_to_share); -} - -DN_API DN_OSThreadLane DN_OS_TCThreadLaneEquip(DN_OSThreadLane lane) -{ - DN_TCCore *tc = DN_TCGet(); - DN_OSThreadLane *curr = DN_Cast(DN_OSThreadLane *) tc->lane_opaque; - DN_StaticAssert(sizeof(tc->lane_opaque) >= sizeof(DN_OSThreadLane)); - DN_OSThreadLane result = *curr; - *curr = lane; - return result; -} - -static DN_I32 DN_OS_AsyncThreadEntryPoint_(DN_OSThread *thread) -{ - DN_OS_ThreadSetNameFmt("%.*s", DN_Str8PrintFmt(thread->name)); - DN_OSAsyncCore *async = DN_Cast(DN_OSAsyncCore *) thread->user_context; - DN_Ring *ring = &async->ring; - for (;;) { - DN_OS_SemaphoreWait(&async->worker_sem, UINT32_MAX); - if (async->join_threads) - break; - - DN_OSAsyncTask task = {}; - for (DN_OS_MutexScope(&async->ring_mutex)) { - if (DN_RingHasData(ring, sizeof(task))) - DN_RingRead(ring, &task, sizeof(task)); - } - - if (task.work.func) { - DN_OS_ConditionVariableBroadcast(&async->ring_write_cv); // Resume any blocked ring write(s) - - DN_OSAsyncWorkArgs args = {}; - args.input = task.work.input; - args.thread = thread; - - DN_AtomicAddU32(&async->busy_threads, 1); - task.work.func(args); - DN_AtomicSubU32(&async->busy_threads, 1); - - if (task.completion_sem.handle != 0) - DN_OS_SemaphoreIncrement(&task.completion_sem, 1); - } - } - - return 0; -} - -DN_API void DN_OS_AsyncInit(DN_OSAsyncCore *async, char *base, DN_USize base_size, DN_OSThread *threads, DN_U32 threads_size) -{ - DN_Assert(async); - async->ring.size = base_size; - async->ring.base = base; - async->ring_mutex = DN_OS_MutexInit(); - async->ring_write_cv = DN_OS_ConditionVariableInit(); - async->worker_sem = DN_OS_SemaphoreInit(0); - async->thread_count = threads_size; - async->threads = threads; - for (DN_ForIndexU(index, async->thread_count)) { - DN_OSThread *thread = async->threads + index; - DN_OS_ThreadInit(thread, DN_OS_AsyncThreadEntryPoint_, /*lane=*/ nullptr, DN_TCInitArgsDefault(), async); - } -} - -DN_API void DN_OS_AsyncDeinit(DN_OSAsyncCore *async) -{ - DN_Assert(async); - DN_AtomicSetValue32(&async->join_threads, true); - DN_OS_SemaphoreIncrement(&async->worker_sem, async->thread_count); - for (DN_ForItSize(it, DN_OSThread, async->threads, async->thread_count)) - DN_OS_ThreadJoin(it.data, DN_TCDeinitArenas_Yes); -} - -static bool DN_OS_AsyncQueueTask_(DN_OSAsyncCore *async, DN_OSAsyncTask const *task, DN_U64 wait_time_ms) { - DN_U64 end_time_ms = DN_OS_DateUnixTimeMs() + wait_time_ms; - bool result = false; - for (DN_OS_MutexScope(&async->ring_mutex)) { - for (;;) { - if (DN_RingHasSpace(&async->ring, sizeof(*task))) { - DN_RingWriteStruct(&async->ring, task); - result = true; - break; - } - DN_OS_ConditionVariableWaitUntil(&async->ring_write_cv, &async->ring_mutex, end_time_ms); - if (DN_OS_DateUnixTimeMs() >= end_time_ms) - break; - } - } - - if (result) - DN_OS_SemaphoreIncrement(&async->worker_sem, 1); // Flag that a job is available - - return result; -} - -DN_API bool DN_OS_AsyncQueueWork(DN_OSAsyncCore *async, DN_OSAsyncWorkFunc *func, void *input, DN_U64 wait_time_ms) -{ - DN_OSAsyncTask task = {}; - task.work.func = func; - task.work.input = input; - bool result = DN_OS_AsyncQueueTask_(async, &task, wait_time_ms); - return result; -} - -DN_API DN_OSAsyncTask DN_OS_AsyncQueueTask(DN_OSAsyncCore *async, DN_OSAsyncWorkFunc *func, void *input, DN_U64 wait_time_ms) -{ - DN_OSAsyncTask result = {}; - result.work.func = func; - result.work.input = input; - result.completion_sem = DN_OS_SemaphoreInit(0); - result.queued = DN_OS_AsyncQueueTask_(async, &result, wait_time_ms); - if (!result.queued) - DN_OS_SemaphoreDeinit(&result.completion_sem); - return result; -} - -DN_API bool DN_OS_AsyncWaitTask(DN_OSAsyncTask *task, DN_U32 timeout_ms) -{ - bool result = true; - if (!task->queued) - return result; - - DN_OSSemaphoreWaitResult wait = DN_OS_SemaphoreWait(&task->completion_sem, timeout_ms); - result = wait == DN_OSSemaphoreWaitResult_Success; - if (result) - DN_OS_SemaphoreDeinit(&task->completion_sem); - return result; -} - -DN_API DN_LogStyle DN_OS_PrintStyleColour(uint8_t r, uint8_t g, uint8_t b, DN_LogBold bold) -{ - DN_LogStyle result = {}; - result.bold = bold; - result.colour = true; - result.r = r; - result.g = g; - result.b = b; - return result; -} - -DN_API DN_LogStyle DN_OS_PrintStyleColourU32(uint32_t rgb, DN_LogBold bold) -{ - uint8_t r = (rgb >> 24) & 0xFF; - uint8_t g = (rgb >> 16) & 0xFF; - uint8_t b = (rgb >> 8) & 0xFF; - DN_LogStyle result = DN_OS_PrintStyleColour(r, g, b, bold); - return result; -} - -DN_API DN_LogStyle DN_OS_PrintStyleBold() -{ - DN_LogStyle result = {}; - result.bold = DN_LogBold_Yes; - return result; -} - -DN_API void DN_OS_Print(DN_OSPrintDest dest, DN_Str8 string) -{ - DN_Assert(dest == DN_OSPrintDest_Out || dest == DN_OSPrintDest_Err); - -#if defined(DN_PLATFORM_WIN32) - // NOTE: Get the output handles from kernel - DN_THREAD_LOCAL void *std_out_print_handle = nullptr; - DN_THREAD_LOCAL void *std_err_print_handle = nullptr; - DN_THREAD_LOCAL bool std_out_print_to_console = false; - DN_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 (dest == DN_OSPrintDest_Err) { - print_handle = std_err_print_handle; - print_to_console = std_err_print_to_console; - } - - // NOTE: Write the string - DN_Assert(string.size < DN_Cast(unsigned long) - 1); - unsigned long bytes_written = 0; - (void)bytes_written; - if (print_to_console) - WriteConsoleA(print_handle, string.data, DN_Cast(unsigned long) string.size, &bytes_written, nullptr); - else - WriteFile(print_handle, string.data, DN_Cast(unsigned long) string.size, &bytes_written, nullptr); -#else - fprintf(dest == DN_OSPrintDest_Out ? stdout : stderr, "%.*s", DN_Str8PrintFmt(string)); -#endif -} - -DN_API void DN_OS_PrintF(DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_OS_PrintFV(dest, fmt, args); - va_end(args); -} - -DN_API void DN_OS_PrintFStyle(DN_OSPrintDest dest, DN_LogStyle style, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_OS_PrintFVStyle(dest, style, fmt, args); - va_end(args); -} - -DN_API void DN_OS_PrintStyle(DN_OSPrintDest dest, DN_LogStyle style, DN_Str8 string) -{ - if (string.data && string.size) { - if (style.colour) { - DN_Str8x32 colour = DN_Str8x32FromANSIColourCodeU8RGB(DN_ANSIColourMode_Fg, style.r, style.g, style.b); - DN_OS_Print(dest, DN_Str8FromStruct(&colour)); - } - if (style.bold == DN_LogBold_Yes) - DN_OS_Print(dest, DN_Str8Lit(DN_ANSICodeBoldLit)); - DN_OS_Print(dest, string); - if (style.colour || style.bold == DN_LogBold_Yes) - DN_OS_Print(dest, DN_Str8Lit(DN_ANSICodeResetLit)); - } -} - -static char *DN_OS_PrintVSPrintfChunker_(const char *buf, void *user, int len) -{ - DN_Str8 string = {}; - string.data = DN_Cast(char *) buf; - string.size = len; - - DN_OSPrintDest dest = DN_Cast(DN_OSPrintDest) DN_Cast(uintptr_t) user; - DN_OS_Print(dest, string); - return (char *)buf; -} - -DN_API void DN_OS_PrintFV(DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - char buffer[STB_SPRINTF_MIN]; - STB_SPRINTF_DECORATE(vsprintfcb) - (DN_OS_PrintVSPrintfChunker_, DN_Cast(void *) DN_Cast(uintptr_t) dest, buffer, fmt, args); -} - -DN_API void DN_OS_PrintFVStyle(DN_OSPrintDest dest, DN_LogStyle style, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - if (fmt) { - if (style.colour) { - DN_Str8x32 colour = DN_Str8x32FromANSIColourCodeU8RGB(DN_ANSIColourMode_Fg, style.r, style.g, style.b); - DN_OS_Print(dest, DN_Str8FromStruct(&colour)); - } - if (style.bold == DN_LogBold_Yes) - DN_OS_Print(dest, DN_Str8Lit(DN_ANSICodeBoldLit)); - DN_OS_PrintFV(dest, fmt, args); - if (style.colour || style.bold == DN_LogBold_Yes) - DN_OS_Print(dest, DN_Str8Lit(DN_ANSICodeResetLit)); - } -} - -DN_API void DN_OS_PrintLn(DN_OSPrintDest dest, DN_Str8 string) -{ - DN_OS_Print(dest, string); - DN_OS_Print(dest, DN_Str8Lit("\n")); -} - -DN_API void DN_OS_PrintLnF(DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_OS_PrintLnFV(dest, fmt, args); - va_end(args); -} - -DN_API void DN_OS_PrintLnFV(DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - DN_OS_PrintFV(dest, fmt, args); - DN_OS_Print(dest, DN_Str8Lit("\n")); -} - -DN_API void DN_OS_PrintLnStyle(DN_OSPrintDest dest, DN_LogStyle style, DN_Str8 string) -{ - DN_OS_PrintStyle(dest, style, string); - DN_OS_Print(dest, DN_Str8Lit("\n")); -} - -DN_API void DN_OS_PrintLnFStyle(DN_OSPrintDest dest, DN_LogStyle style, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_OS_PrintLnFVStyle(dest, style, fmt, args); - va_end(args); -} - -DN_API void DN_OS_PrintLnFVStyle(DN_OSPrintDest dest, DN_LogStyle style, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - DN_OS_PrintFVStyle(dest, style, fmt, args); - DN_OS_Print(dest, DN_Str8Lit("\n")); -} - -DN_API DN_StackTrace DN_StackTraceFromAllocator(DN_Allocator allocator, DN_U16 limit) -{ - DN_StackTrace result = {}; -#if defined(DN_OS_WIN32) - if (!allocator.context) - return result; - - static DN_TicketMutex mutex = {}; - DN_TicketMutex_Begin(&mutex); - - HANDLE thread = GetCurrentThread(); - result.process = GetCurrentProcess(); - - DN_OSW32Core *w32 = DN_OS_W32GetCore(); - if (!w32->sym_initialised) { - w32->sym_initialised = true; - SymSetOptions(SYMOPT_LOAD_LINES); - if (!SymInitialize(result.process, nullptr /*UserSearchPath*/, true /*fInvadeProcess*/)) { - DN_TCScratch scratch = DN_TCScratchBeginAllocator(&allocator, 1); - DN_OSW32Error error = DN_OS_W32LastError(&scratch.arena); - DN_LogErrorF("SymInitialize failed, stack trace can not be generated (%lu): %.*s\n", error.code, DN_Str8PrintFmt(error.msg)); - DN_TCScratchEnd(&scratch); - } - } - - CONTEXT context; - RtlCaptureContext(&context); - - STACKFRAME64 frame = {}; - frame.AddrPC.Offset = context.Rip; - frame.AddrPC.Mode = AddrModeFlat; - frame.AddrFrame.Offset = context.Rbp; - frame.AddrFrame.Mode = AddrModeFlat; - frame.AddrStack.Offset = context.Rsp; - frame.AddrStack.Mode = AddrModeFlat; - - DN_U64 raw_frames[256] = {}; - DN_USize raw_frames_count = 0; - while (raw_frames_count < limit) { - if (!StackWalk64(IMAGE_FILE_MACHINE_AMD64, - result.process, - thread, - &frame, - &context, - nullptr /*ReadMemoryRoutine*/, - SymFunctionTableAccess64, - SymGetModuleBase64, - nullptr /*TranslateAddress*/)) - break; - - // NOTE: It might be useful one day to use frame.AddrReturn.Offset. - // If AddrPC.Offset == AddrReturn.Offset then we can detect recursion. - DN_LArrayAppend(raw_frames, &raw_frames_count, frame.AddrPC.Offset); - } - DN_TicketMutex_End(&mutex); - - result.base_addr = DN_Cast(DN_U64 *)DN_AllocatorAlloc(allocator, raw_frames_count * sizeof(DN_U64), alignof(DN_U64), DN_ZMem_No); - DN_Assert(result.base_addr); - - result.size = DN_Cast(DN_U16) raw_frames_count; - DN_Memcpy(result.base_addr, raw_frames, raw_frames_count * sizeof(raw_frames[0])); -#else - (void)limit; - (void)allocator; -#endif - return result; -} - - -DN_API DN_StackTrace DN_StackTraceFromArena(DN_Arena *arena, DN_U16 limit) -{ - DN_Allocator allocator = DN_AllocatorFromArena(arena); - DN_StackTrace result = DN_StackTraceFromAllocator(allocator, limit); - return result; -} - -static void DN_StackTraceAddToStr8Builder_(DN_StackTrace const *trace, DN_Str8Builder *builder, DN_USize skip) -{ - DN_StackTraceRawFrame raw_frame = {}; - raw_frame.process = trace->process; - for (DN_USize index = skip; index < trace->size; index++) { - raw_frame.base_addr = trace->base_addr[index]; - DN_StackTraceFrame frame = DN_StackTraceRawFrameToFrame(builder->arena, raw_frame); - DN_Str8BuilderAppendF(builder, "%.*s(%zu): %.*s%s", DN_Str8PrintFmt(frame.file_name), frame.line_number, DN_Str8PrintFmt(frame.function_name), (DN_Cast(int) index == trace->size - 1) ? "" : "\n"); - } -} - -DN_API bool DN_StackTraceIterate(DN_StackTraceIterator *it, DN_StackTrace const *trace) -{ - bool result = false; - if (!it || !trace || !trace->base_addr || !trace->process) - return result; - - if (it->index >= trace->size) - return false; - - result = true; - it->raw_frame.process = trace->process; - it->raw_frame.base_addr = trace->base_addr[it->index++]; - return result; -} - -DN_API DN_Str8 DN_Str8FromStackTraceAllocator(DN_Allocator allocator, DN_StackTrace const *trace, DN_U16 skip) -{ - DN_Str8 result = {}; - if (!trace) - return result; - - DN_TCScratch scratch = DN_TCScratchBeginAllocator(&allocator, 1); - DN_Str8Builder builder = DN_Str8BuilderFromArena(&scratch.arena); - DN_StackTraceAddToStr8Builder_(trace, &builder, skip); - result = DN_Str8FromStr8BuilderAllocator(&builder, allocator); - DN_TCScratchEnd(&scratch); - return result; -} - -DN_API DN_Str8 DN_Str8FromStackTraceArena(DN_Arena *arena, DN_StackTrace const *trace, DN_U16 skip) -{ - DN_Str8 result = {}; - if (!trace || !arena) - return result; - - DN_TCScratch scratch = DN_TCScratchBeginArena(&arena, 1); - DN_Str8Builder builder = DN_Str8BuilderFromArena(&scratch.arena); - DN_StackTraceAddToStr8Builder_(trace, &builder, skip); - result = DN_Str8FromStr8BuilderArena(&builder, arena); - DN_TCScratchEnd(&scratch); - return result; -} - -DN_API DN_Str8 DN_Str8FromStackTraceNowAllocator(DN_Allocator allocator, DN_U16 limit, DN_U16 skip) -{ - DN_TCScratch scratch = DN_TCScratchBeginArena(DN_Cast(DN_Arena **) & allocator.context, 1); - DN_StackTrace walk = DN_StackTraceFromArena(&scratch.arena, limit); - DN_Str8 result = DN_Str8FromStackTraceAllocator(allocator, &walk, skip); - DN_TCScratchEnd(&scratch); - return result; -} - -DN_API DN_Str8 DN_Str8FromStackTraceNowArena(DN_Arena *arena, DN_U16 limit, DN_U16 skip) -{ - DN_Str8 result = DN_Str8FromStackTraceNowAllocator(DN_AllocatorFromArena(arena), limit, skip); - return result; -} - -DN_API DN_Str8 DN_Str8FromStackTraceNowHeap(DN_U16 limit, DN_U16 skip) -{ - DN_Arena arena = DN_ArenaFromHeap(DN_Kilobytes(64), DN_MemFlags_NoAllocTrack); - DN_Str8Builder builder = DN_Str8BuilderFromArena(&arena); - DN_StackTrace walk = DN_StackTraceFromArena(&arena, limit); - DN_StackTraceAddToStr8Builder_(&walk, &builder, skip); - DN_Str8 result = DN_Str8BuilderBuildFromHeap(&builder); - DN_ArenaDeinit(&arena); - return result; -} - -DN_API DN_StackTraceFrameSlice DN_StackTraceGetFrames(DN_Arena *arena, DN_U16 limit) -{ - DN_StackTraceFrameSlice result = {}; - if (!arena) - return result; - - DN_TCScratch scratch = DN_TCScratchBeginArena(&arena, 1); - DN_StackTrace walk = DN_StackTraceFromArena(&scratch.arena, limit); - if (walk.size) { - if (DN_ISliceAllocArena(&result, walk.size, DN_ZMem_No, arena)) { - DN_USize slice_index = 0; - for (DN_StackTraceIterator it = {}; DN_StackTraceIterate(&it, &walk);) - result.data[slice_index++] = DN_StackTraceRawFrameToFrame(arena, it.raw_frame); - } - } - DN_TCScratchEnd(&scratch); - return result; -} - -DN_API DN_StackTraceFrame DN_StackTraceRawFrameToFrame(DN_Arena *arena, DN_StackTraceRawFrame raw_frame) -{ -#if defined(DN_OS_WIN32) - // NOTE: Get line+filename - - // TODO: Why does zero-initialising this with `line = {};` cause - // SymGetLineFromAddr64 function to fail once we are at - // __scrt_commain_main_seh and hit BaseThreadInitThunk frame? The - // line and file number are still valid in the result which we use, so, - // we silently ignore this error. - IMAGEHLP_LINEW64 line; - line.SizeOfStruct = sizeof(line); - DWORD line_displacement = 0; - if (!SymGetLineFromAddrW64(raw_frame.process, raw_frame.base_addr, &line_displacement, &line)) - line = {}; - - // NOTE: Get function name - - alignas(SYMBOL_INFOW) char buffer[sizeof(SYMBOL_INFOW) + (MAX_SYM_NAME * sizeof(wchar_t))] = {}; - SYMBOL_INFOW *symbol = DN_Cast(SYMBOL_INFOW *) buffer; - symbol->SizeOfStruct = sizeof(*symbol); - symbol->MaxNameLen = sizeof(buffer) - sizeof(*symbol); - - uint64_t symbol_displacement = 0; // Offset to the beginning of the symbol to the address - SymFromAddrW(raw_frame.process, raw_frame.base_addr, &symbol_displacement, symbol); - - // NOTE: Construct result - - DN_Str16 file_name16 = DN_Str16FromPtr(line.FileName, DN_CStr16Size(line.FileName)); - DN_Str16 function_name16 = DN_Str16FromPtr(symbol->Name, symbol->NameLen); - - DN_StackTraceFrame result = {}; - result.address = raw_frame.base_addr; - result.line_number = line.LineNumber; - result.file_name = DN_OS_W32Str16ToStr8(arena, file_name16); - result.function_name = DN_OS_W32Str16ToStr8(arena, function_name16); - - if (result.function_name.size == 0) - result.function_name = DN_Str8Lit(""); - if (result.file_name.size == 0) - result.file_name = DN_Str8Lit(""); -#else - DN_StackTraceFrame result = {}; -#endif - return result; -} - -DN_API void DN_StackTracePrint(DN_U16 limit) -{ - DN_TCScratch scratch = DN_TCScratchBeginArena(nullptr, 0); - DN_StackTraceFrameSlice stack_trace = DN_StackTraceGetFrames(&scratch.arena, limit); - for (DN_ForItSize(it, DN_StackTraceFrame, stack_trace.data, stack_trace.count)) { - DN_StackTraceFrame frame = *it.data; - DN_OS_PrintErrLnF("%.*s(%I64u): %.*s", DN_Str8PrintFmt(frame.file_name), frame.line_number, DN_Str8PrintFmt(frame.function_name)); - } - DN_TCScratchEnd(&scratch); -} - -DN_API void DN_StackTraceReloadSymbols() -{ -#if defined(DN_OS_WIN32) - HANDLE process = GetCurrentProcess(); - SymRefreshModuleList(process); -#endif -} diff --git a/Source/OS/dn_os.h b/Source/OS/dn_os.h deleted file mode 100644 index 2b49b4e..0000000 --- a/Source/OS/dn_os.h +++ /dev/null @@ -1,575 +0,0 @@ -#if !defined(DN_OS_H) -#define DN_OS_H - -#if defined(_CLANGD) - #define DN_H_WITH_OS 1 - #include "../dn.h" -#endif - -#include // operator new - -#if !defined(DN_OS_WIN32) || defined(DN_OS_WIN32_USE_PTHREADS) - #include - #include -#endif - -#if defined(DN_PLATFORM_POSIX) || defined(DN_PLATFORM_EMSCRIPTEN) - #include // errno - #include // O_RDONLY ... etc - #include // ioctl - #include // mmap - #include // getrandom - #include // stat - #include // pid_t - #include // waitpid - #include // clock_gettime, nanosleep - #include // access, gettid, write - - #if !defined(DN_PLATFORM_EMSCRIPTEN) - #include // FICLONE - #include // sendfile - #endif -#endif - -extern DN_CPUFeatureDecl g_dn_cpu_feature_decl[DN_CPUFeature_Count]; - -struct DN_OSTimer /// Record time between two time-points using the OS's performance counter. -{ - DN_U64 start; - DN_U64 end; -}; - -// NOTE: DN_OSFile -enum DN_OSPathInfoType -{ - DN_OSPathInfoType_Unknown, - DN_OSPathInfoType_Directory, - DN_OSPathInfoType_File, -}; - -struct DN_OSPathInfo -{ - bool exists; - DN_OSPathInfoType type; - DN_U64 create_time_in_s; - DN_U64 last_write_time_in_s; - DN_U64 last_access_time_in_s; - DN_U64 size; -}; - -struct DN_OSDirIterator -{ - void *handle; - DN_Str8 file_name; - char buffer[512]; -}; - -// NOTE: R/W Stream API -struct DN_OSFileRead -{ - bool success; - DN_USize bytes_read; -}; - -struct DN_OSFile -{ - bool error; - void *handle; -}; - -enum DN_OSFileOpen -{ - DN_OSFileOpen_CreateAlways, // Create file if it does not exist, otherwise, zero out the file and open - DN_OSFileOpen_OpenIfExist, // Open file at path only if it exists - DN_OSFileOpen_OpenAlways, // Open file at path, create file if it does not exist -}; - -typedef DN_U32 DN_OSFileAccess; - -enum DN_OSFileAccess_ -{ - DN_OSFileAccess_Read = 1 << 0, - DN_OSFileAccess_Write = 1 << 1, - DN_OSFileAccess_Execute = 1 << 2, - DN_OSFileAccess_AppendOnly = 1 << 3, // This flag cannot be combined with any other access mode - DN_OSFileAccess_ReadWrite = DN_OSFileAccess_Read | DN_OSFileAccess_Write, - DN_OSFileAccess_All = DN_OSFileAccess_ReadWrite | DN_OSFileAccess_Execute | DN_OSFileAccess_AppendOnly, -}; - -// NOTE: DN_OSPath -#if !defined(DN_OSPathSeperator) - #if defined(DN_OS_WIN32) - #define DN_OSPathSeperator "\\" - #else - #define DN_OSPathSeperator "/" - #endif - #define DN_OSPathSeperatorString DN_Str8Lit(DN_OSPathSeperator) -#endif - -struct DN_OSPathLink -{ - DN_Str8 string; - DN_OSPathLink *next; - DN_OSPathLink *prev; -}; - -struct DN_OSPath -{ - bool has_prefix_path_separator; - DN_OSPathLink *head; - DN_OSPathLink *tail; - DN_USize string_size; - DN_U16 links_size; -}; - -// NOTE: DN_OSExec -typedef DN_U32 DN_OSExecFlags; - -enum DN_OSExecFlags_ -{ - DN_OSExecFlags_Nil = 0, - DN_OSExecFlags_SaveStdout = 1 << 0, - DN_OSExecFlags_SaveStderr = 1 << 1, - DN_OSExecFlags_SaveOutput = DN_OSExecFlags_SaveStdout | DN_OSExecFlags_SaveStderr, - DN_OSExecFlags_MergeStderrToStdout = 1 << 2 | DN_OSExecFlags_SaveOutput, -}; - -struct DN_OSExecAsyncHandle -{ - DN_OSExecFlags exec_flags; - DN_U32 os_error_code; - DN_U32 exit_code; - void *process; - void *stdout_read; - void *stdout_write; - void *stderr_read; - void *stderr_write; -}; - -struct DN_OSExecResult -{ - bool finished; - DN_Str8 stdout_text; - DN_Str8 stderr_text; - DN_U32 os_error_code; - DN_U32 exit_code; -}; - -struct DN_OSExecArgs -{ - DN_OSExecFlags flags; - DN_Str8 working_dir; - DN_Str8Slice environment; -}; - -// NOTE: DN_OSSemaphore -DN_U32 const DN_OS_SEMAPHORE_INFINITE_TIMEOUT = UINT32_MAX; - -struct DN_OSSemaphore -{ - DN_U64 handle; -}; - -struct DN_OSBarrier -{ - DN_U64 handle; -}; - -enum DN_OSSemaphoreWaitResult -{ - DN_OSSemaphoreWaitResult_Failed, - DN_OSSemaphoreWaitResult_Success, - DN_OSSemaphoreWaitResult_Timeout, -}; - -struct DN_OSMutex -{ - DN_U64 handle; -}; - -struct DN_OSConditionVariable -{ - DN_U64 handle; -}; - -// NOTE: DN_OSThread -typedef DN_I32(DN_OSThreadFunc)(struct DN_OSThread *); - -struct DN_OSThreadLane -{ - DN_USize index; - DN_USize count; - DN_OSBarrier barrier; - void* shared_mem; -}; - -struct DN_OSThreadLaneway -{ - DN_OSThread* threads; - DN_USize threads_count; - DN_UPtr* shared_mem; - DN_OSBarrier barrier; -}; - -struct DN_OSThread -{ - DN_Str8x64 name; - DN_TCCore context; - DN_OSThreadLane lane; - bool is_lane_set; - void *handle; - DN_U64 thread_id; - void *user_context; - DN_OSThreadFunc *func; - DN_OSSemaphore init_semaphore; - DN_TCInitArgs tc_init_args; -}; - -struct DN_OSCore -{ - DN_CPUReport cpu_report; - - // NOTE: Logging - bool log_to_file; // Output logs to file as well as standard out - DN_OSFile log_file; // TODO(dn): Hmmm, how should we do this... ? - DN_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 - DN_TicketMutex log_mutex; - - // NOTE: OS - DN_U32 logical_processor_count; - DN_U32 page_size; - DN_U32 alloc_granularity; - - // NOTE: Memory - // Total OS mem allocs in lifetime of program (e.g. malloc, VirtualAlloc, HeapAlloc ...). This - // only includes allocations routed through the library such as the growing nature of arenas or - // using the memory allocation routines in the library like DN_OS_MemCommit and so forth. - DN_U64 vmem_allocs_total; - DN_U64 vmem_allocs_frame; // Total OS virtual memory allocs since the last 'DN_Core_FrameBegin' was invoked - DN_U64 mem_allocs_total; - DN_U64 mem_allocs_frame; // Total OS heap allocs since the last 'DN_Core_FrameBegin' was invoked - - DN_MemList mem; - DN_Arena arena; - void *platform_context; -}; - -struct DN_OSDiskSpace -{ - bool success; - DN_U64 avail; - DN_U64 size; -}; - -DN_API DN_MemFuncs DN_MemFuncsFromType (DN_MemFuncsType type); -DN_API DN_MemFuncs DN_MemFuncsDefault (); -DN_API DN_MemList DN_MemListFromHeap (DN_U64 size, DN_MemFlags flags); -DN_API DN_MemList DN_MemListFromVMem (DN_U64 reserve, DN_U64 commit, DN_MemFlags flags); -DN_API DN_Arena DN_ArenaFromHeap (DN_U64 wize, DN_MemFlags flags); -DN_API DN_Arena DN_ArenaFromVMem (DN_U64 reserve, DN_U64 commit, DN_MemFlags flags); - -DN_API DN_Str8 DN_Str8FromHeapF (DN_FMT_ATTRIB char const *fmt, ...); -DN_API DN_Str8 DN_Str8FromHeap (DN_USize size, DN_ZMem z_mem); -DN_API DN_Str8 DN_Str8BuilderBuildFromHeap (DN_Str8Builder const *builder); - -DN_API void DN_OS_LogPrint (DN_LogTypeParam type, void *user_data, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, va_list args); -DN_API void DN_OS_SetLogPrintFuncToOS (); - -DN_API void * DN_OS_MemReserve (DN_USize size, DN_MemCommit commit, DN_MemPage page_flags); -DN_API bool DN_OS_MemCommit (void *ptr, DN_USize size, DN_U32 page_flags); -DN_API void DN_OS_MemDecommit (void *ptr, DN_USize size); -DN_API void DN_OS_MemRelease (void *ptr, DN_USize size); -DN_API int DN_OS_MemProtect (void *ptr, DN_USize size, DN_U32 page_flags); - -DN_API void * DN_OS_MemAlloc (DN_USize size, DN_ZMem z_mem); -DN_API void DN_OS_MemDealloc (void *ptr); - -DN_API DN_Date DN_OS_DateLocalTimeNow (); -DN_API DN_Str8x32 DN_OS_DateLocalTimeStr8Now (char date_separator = '-', char hms_separator = ':'); -DN_API DN_Str8x32 DN_OS_DateLocalTimeStr8 (DN_Date time, char date_separator = '-', char hms_separator = ':'); -DN_API DN_U64 DN_OS_DateUnixTimeNs (); -#define DN_OS_DateUnixTimeUs() (DN_OS_DateUnixTimeNs() / 1000) -#define DN_OS_DateUnixTimeMs() (DN_OS_DateUnixTimeNs() / (1000 * 1000)) -#define DN_OS_DateUnixTimeS() (DN_OS_DateUnixTimeNs() / (1000 * 1000 * 1000)) -DN_API DN_U64 DN_OS_DateUnixTimeSFromLocalDate (DN_Date date); -DN_API DN_U64 DN_OS_DateLocalUnixTimeSFromUnixTimeS (DN_U64 unix_ts_s); - -DN_API void DN_OS_GenBytesSecure (void *buffer, DN_U32 size); -DN_API bool DN_OS_SetEnvVar (DN_Str8 name, DN_Str8 value); -DN_API DN_OSDiskSpace DN_OS_DiskSpace (DN_Str8 path); -DN_API DN_Str8 DN_OS_EXEPath (DN_Arena *arena); -DN_API DN_Str8 DN_OS_EXEDir (DN_Arena *arena); -DN_API void DN_OS_SleepMs (DN_UInt milliseconds); - -DN_API DN_U64 DN_OS_PerfCounterNow (); -DN_API DN_U64 DN_OS_PerfCounterFrequency (); -DN_API DN_F64 DN_OS_PerfCounterS (DN_U64 begin, uint64_t end); -DN_API DN_F64 DN_OS_PerfCounterMs (DN_U64 begin, uint64_t end); -DN_API DN_F64 DN_OS_PerfCounterUs (DN_U64 begin, uint64_t end); -DN_API DN_F64 DN_OS_PerfCounterNs (DN_U64 begin, uint64_t end); -DN_API DN_OSTimer DN_OS_TimerBegin (); -DN_API void DN_OS_TimerEnd (DN_OSTimer *timer); -DN_API DN_F64 DN_OS_TimerS (DN_OSTimer timer); -DN_API DN_F64 DN_OS_TimerMs (DN_OSTimer timer); -DN_API DN_F64 DN_OS_TimerUs (DN_OSTimer timer); -DN_API DN_F64 DN_OS_TimerNs (DN_OSTimer timer); -DN_API DN_U64 DN_OS_EstimateTSCPerSecond (uint64_t duration_ms_to_gauge_tsc_frequency); - -DN_API bool DN_OS_FileCopy (DN_Str8 src, DN_Str8 dest, bool overwrite, DN_ErrSink *err); -DN_API bool DN_OS_FileMove (DN_Str8 src, DN_Str8 dest, bool overwrite, DN_ErrSink *err); - -DN_API DN_OSFile DN_OS_FileOpen (DN_Str8 path, DN_OSFileOpen open_mode, DN_OSFileAccess access, DN_ErrSink *err); -DN_API DN_OSFileRead DN_OS_FileRead (DN_OSFile *file, void *buffer, DN_USize size, DN_ErrSink *err); -DN_API bool DN_OS_FileWritePtr (DN_OSFile *file, void const *data, DN_USize size, DN_ErrSink *err); -DN_API bool DN_OS_FileWrite (DN_OSFile *file, DN_Str8 buffer, DN_ErrSink *err); -DN_API bool DN_OS_FileWriteFV (DN_OSFile *file, DN_ErrSink *err, DN_FMT_ATTRIB char const *fmt, va_list args); -DN_API bool DN_OS_FileWriteF (DN_OSFile *file, DN_ErrSink *err, DN_FMT_ATTRIB char const *fmt, ...); -DN_API bool DN_OS_FileFlush (DN_OSFile *file, DN_ErrSink *err); -DN_API void DN_OS_FileClose (DN_OSFile *file); - -DN_API DN_Str8 DN_OS_FileReadAll (DN_Allocator allocator, DN_Str8 path, DN_ErrSink *err); -DN_API DN_Str8 DN_OS_FileReadAllArena (DN_Arena *arena, DN_Str8 path, DN_ErrSink *err); -DN_API DN_Str8 DN_OS_FileReadAllPool (DN_Pool *pool, DN_Str8 path, DN_ErrSink *err); - -DN_API bool DN_OS_FileWriteAll (DN_Str8 path, DN_Str8 buffer, DN_ErrSink *err); -DN_API bool DN_OS_FileWriteAllFV (DN_Str8 path, DN_ErrSink *err, DN_FMT_ATTRIB char const *fmt, va_list args); -DN_API bool DN_OS_FileWriteAllF (DN_Str8 path, DN_ErrSink *err, DN_FMT_ATTRIB char const *fmt, ...); -DN_API bool DN_OS_FileWriteAllSafe (DN_Str8 path, DN_Str8 buffer, DN_ErrSink *err); -DN_API bool DN_OS_FileWriteAllSafeFV (DN_Str8 path, DN_ErrSink *err, DN_FMT_ATTRIB char const *fmt, va_list args); -DN_API bool DN_OS_FileWriteAllSafeF (DN_Str8 path, DN_ErrSink *err, DN_FMT_ATTRIB char const *fmt, ...); - -DN_API DN_Str8 DN_OS_Str8FromPathInfoType (DN_OSPathInfoType type); -DN_API DN_OSPathInfo DN_OS_PathInfo (DN_Str8 path); -DN_API bool DN_OS_PathIsOlderThan (DN_Str8 file, DN_Str8 check_against); -DN_API bool DN_OS_PathDelete (DN_Str8 path); -DN_API bool DN_OS_PathIsFile (DN_Str8 path); -DN_API bool DN_OS_PathIsDir (DN_Str8 path); -DN_API bool DN_OS_PathMakeDir (DN_Str8 path); -DN_API bool DN_OS_PathIterateDir (DN_Str8 path, DN_OSDirIterator *it); - -DN_API bool DN_OS_PathAddRef (DN_Arena *arena, DN_OSPath *fs_path, DN_Str8 path); -DN_API bool DN_OS_PathAdd (DN_Arena *arena, DN_OSPath *fs_path, DN_Str8 path); -DN_API bool DN_OS_PathAddF (DN_Arena *arena, DN_OSPath *fs_path, DN_FMT_ATTRIB char const *fmt, ...); -DN_API bool DN_OS_PathPop (DN_OSPath *fs_path); -DN_API DN_Str8 DN_OS_PathBuildWithSeparator (DN_Arena *arena, DN_OSPath const *fs_path, DN_Str8 path_separator); -DN_API DN_Str8 DN_OS_PathTo (DN_Arena *arena, DN_Str8 path, DN_Str8 path_separtor); -DN_API DN_Str8 DN_OS_PathToF (DN_Arena *arena, DN_Str8 path_separator, DN_FMT_ATTRIB char const *fmt, ...); -DN_API DN_Str8 DN_OS_Path (DN_Arena *arena, DN_Str8 path); -DN_API DN_Str8 DN_OS_PathF (DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, ...); - -#define DN_OS_PathBuildFwdSlash(allocator, fs_path) DN_OS_PathBuildWithSeparator(allocator, fs_path, DN_Str8Lit("/")) -#define DN_OS_PathBuildBackSlash(allocator, fs_path) DN_OS_PathBuildWithSeparator(allocator, fs_path, DN_Str8Lit("\\")) -#define DN_OS_PathBuild(allocator, fs_path) DN_OS_PathBuildWithSeparator(allocator, fs_path, DN_OSPathSeparatorString) - -DN_API void DN_OS_Exit (int32_t exit_code); -DN_API DN_OSExecResult DN_OS_ExecPump (DN_OSExecAsyncHandle handle, char *stdout_buffer, size_t *stdout_size, char *stderr_buffer, size_t *stderr_size, DN_U32 timeout_ms, DN_ErrSink *err); -DN_API DN_OSExecResult DN_OS_ExecWait (DN_OSExecAsyncHandle handle, DN_Arena *arena, DN_ErrSink *err); -DN_API DN_OSExecAsyncHandle DN_OS_ExecAsync (DN_Str8Slice cmd_line, DN_OSExecArgs *args, DN_ErrSink *err); -DN_API DN_OSExecResult DN_OS_Exec (DN_Str8Slice cmd_line, DN_OSExecArgs *args, DN_Arena *arena, DN_ErrSink *err); -DN_API DN_OSExecResult DN_OS_ExecOrAbort (DN_Str8Slice cmd_line, DN_OSExecArgs *args, DN_Arena *arena); - -DN_API DN_OSSemaphore DN_OS_SemaphoreInit (DN_U32 initial_count); -DN_API void DN_OS_SemaphoreDeinit (DN_OSSemaphore *semaphore); -DN_API void DN_OS_SemaphoreIncrement (DN_OSSemaphore *semaphore, DN_U32 amount); -DN_API DN_OSSemaphoreWaitResult DN_OS_SemaphoreWait (DN_OSSemaphore *semaphore, DN_U32 timeout_ms); - -DN_API DN_OSBarrier DN_OS_BarrierInit (DN_U32 thread_count); -DN_API void DN_OS_BarrierDeinit (DN_OSBarrier *barrier); -DN_API void DN_OS_BarrierWait (DN_OSBarrier *barrier); - -DN_API DN_OSMutex DN_OS_MutexInit (); -DN_API void DN_OS_MutexDeinit (DN_OSMutex *mutex); -DN_API void DN_OS_MutexLock (DN_OSMutex *mutex); -DN_API void DN_OS_MutexUnlock (DN_OSMutex *mutex); -#define DN_OS_MutexScope(mutex) DN_DeferLoop(DN_OS_MutexLock(mutex), DN_OS_MutexUnlock(mutex)) - -DN_API DN_OSConditionVariable DN_OS_ConditionVariableInit (); -DN_API void DN_OS_ConditionVariableDeinit (DN_OSConditionVariable *cv); -DN_API bool DN_OS_ConditionVariableWait (DN_OSConditionVariable *cv, DN_OSMutex *mutex, DN_U64 sleep_ms); -DN_API bool DN_OS_ConditionVariableWaitUntil (DN_OSConditionVariable *cv, DN_OSMutex *mutex, DN_U64 end_ts_ms); -DN_API void DN_OS_ConditionVariableSignal (DN_OSConditionVariable *cv); -DN_API void DN_OS_ConditionVariableBroadcast (DN_OSConditionVariable *cv); - -DN_API bool DN_OS_ThreadInit (DN_OSThread *thread, DN_OSThreadFunc *func, DN_OSThreadLane *lane, DN_TCInitArgs tc_init_args, void *user_context); -DN_API bool DN_OS_ThreadJoin (DN_OSThread *thread, DN_TCDeinitArenas deinit_arenas); -DN_API DN_U32 DN_OS_ThreadID (); -DN_API void DN_OS_ThreadSetNameFmt (char const *fmt, ...); - -// NOTE: Thread lanes provide an abstraction to represent the concept of programming a CPU like a -// GPU, e.g. SIMT (Single Instruction Multiple Threads). The lane terminology is popularised by Ryan -// Fleury. SIMT is formally defined as -// -// Threads are grouped into warps/wavefronts (typically 32 or 64 threads) that execute the same -// instruction in lockstep, but each thread operates on different data and maintains its own state -// -// The individual threads in a wavefront on the CPU side are colloquially dubbed "lanes" and a -// thread lane here contains the necessary state to facilitate this such as the current index in the -// wavefront and synchronisation primitives to coordinate the different lanes together. -// -// The idea is to write code in a single-threaded manner (linear execution) but across multiple -// threads so that the default is all execution paths are inherently multi-threaded by default. Opt -// out of parallelism instead of opt in. This optimises for the trend of core counts increasing -// whilst clock counts remain static. -// -// A laneway is a helper function to initialise the number of requested OS threads/lanes upfront and -// setup the required synchronisation primitives. It can then be dispatched all the threads which -// start executing the `entry_point` in parallel. -// -// API -// DN_OS_ThreadLaneSync -// A blocking call to synchronise the program-counter of all other lanes in the laneway to this -// function call invocation (using an OS barrier). Optionally pass in the pointer to a pointer -// `ptr_to_share` to broadcast the pointer from one lanes to the others. The lane that wishes -// to broadcast the pointer must have a non-null pointer, all other lanes must pass in a -// non-null pointer. A typical use case might look like: -/* - DN_OSThreadLane *lane = DN_OS_TCThreadLane(); // Get lane from current (t)hread (c)context - - // NOTE: Allocate buffer in lane 0 - DN_U8 *buffer = nullptr; - if (lane->index == 0) - buffer = DN_ArenaNewArray(DN_TCMainArena(), DN_U8, DN_Gigabytes(1), DN_ZMem_No); - - // NOTE: Lane 0 broadcasts the `buffer` pointer to lane 1..N - DN_OS_ThreadLaneSync(lane, &buffer); - - // NOTE: We use LaneRange to divide the buffer into equal sized chunks that each lane can - // write into without clobbering over each other. - DN_V2USize range = DN_OS_ThreadLaneRange(lane, DN_Gigabytes(1)); - for (DN_USize index = range.begin; index < range.end; index++) { buffer[index] = index; } -*/ -// In this example, lane 0 will allocate a 1GiB buffer pass in a `buffer` to -// DN_OS_ThreadLaneSync` that is non-null. Lanes 1->N will skip the branch (because their lanes -// indexes are 1..N) and invoke `DN_OS_ThreadLaneSync` with a nullptr `buffer`. After the -// blocking call is complete, lanes 0->N will now have synchronised the `buffer` pointer and all -// lanes point to the 1GiB range allocated in lane 0's allocator. -// -// Additionally we demonstrate `DN_OS_ThreadLaneRange` which does math behind the scenes to -// divide the buffer up and assign each lane their own indices in the buffer that they can work -// on in parallel without clobbering each others work. -// -// DN_OS_ThreadLaneRange -// Calculates the range of values the current lane in the laneway should execute. For example if -// you have 128 items and 16 threads each lane will receive the following `DN_V2USize` range: -// Lane 0 => [0, 8) -// Lane 1 => [8, 16) -// ... -// Lane 16 => [120, 128) -DN_API DN_OSThreadLane DN_OS_ThreadLaneInit (DN_USize index, DN_USize thread_count, DN_OSBarrier barrier, DN_UPtr *share_mem); -DN_API void DN_OS_ThreadLaneSync (DN_OSThreadLane *lane, void **ptr_to_share); -DN_API DN_V2USize DN_OS_ThreadLaneRange (DN_OSThreadLane const *lane, DN_USize values_count); - -DN_API DN_OSThreadLaneway DN_OS_ThreadLanewayFromArgs (DN_OSThread* threads, DN_USize threads_count, DN_UPtr* shared_mem); -DN_API DN_OSThreadLaneway DN_OS_ThreadLanewayFromArena (DN_USize threads_count, DN_Arena* arena); -DN_API void DN_OS_ThreadLanewayDispatch (DN_OSThreadLaneway *laneway, DN_OSThreadFunc *entry_point, DN_TCInitArgs tc_init_args, void *user_context); -DN_API void DN_OS_ThreadLanewayJoin (DN_OSThreadLaneway *laneway, DN_TCDeinitArenas deinit_arenas); - -DN_API DN_OSThreadLane* DN_OS_TCThreadLane (); -DN_API void DN_OS_TCThreadLaneSync (void **ptr_to_share); -DN_API DN_OSThreadLane DN_OS_TCThreadLaneEquip (DN_OSThreadLane lane); - -enum DN_OSAsyncPriority -{ - DN_OSAsyncPriority_Low, - DN_OSAsyncPriority_High, - DN_OSAsyncPriority_Count, -}; - -struct DN_OSAsyncCore -{ - DN_OSMutex ring_mutex; - DN_OSConditionVariable ring_write_cv; - DN_OSSemaphore worker_sem; - DN_Ring ring; - DN_OSThread *threads; - DN_U32 thread_count; - DN_U32 busy_threads; - DN_U32 join_threads; -}; - -struct DN_OSAsyncWorkArgs -{ - DN_OSThread *thread; - void *input; -}; - -typedef void(DN_OSAsyncWorkFunc)(DN_OSAsyncWorkArgs work_args); - -struct DN_OSAsyncWork -{ - DN_OSAsyncWorkFunc *func; - void *input; - void *output; -}; - -struct DN_OSAsyncTask -{ - bool queued; - DN_OSAsyncWork work; - DN_OSSemaphore completion_sem; -}; - -DN_API void DN_OS_AsyncInit (DN_OSAsyncCore *async, char *base, DN_USize base_size, DN_OSThread *threads, DN_U32 threads_size); -DN_API void DN_OS_AsyncDeinit (DN_OSAsyncCore *async); -DN_API bool DN_OS_AsyncQueueWork(DN_OSAsyncCore *async, DN_OSAsyncWorkFunc *func, void *input, DN_U64 wait_time_ms); -DN_API DN_OSAsyncTask DN_OS_AsyncQueueTask(DN_OSAsyncCore *async, DN_OSAsyncWorkFunc *func, void *input, DN_U64 wait_time_ms); -DN_API bool DN_OS_AsyncWaitTask (DN_OSAsyncTask *task, DN_U32 timeout_ms); - -// NOTE: DN_OSPrint -enum DN_OSPrintDest -{ - DN_OSPrintDest_Out, - DN_OSPrintDest_Err, -}; - -// NOTE: Print Macros -#define DN_OS_PrintOut(string) DN_OS_Print(DN_OSPrintDest_Out, string) -#define DN_OS_PrintOutF(fmt, ...) DN_OS_PrintF(DN_OSPrintDest_Out, fmt, ##__VA_ARGS__) -#define DN_OS_PrintOutFV(fmt, args) DN_OS_PrintFV(DN_OSPrintDest_Out, fmt, args) - -#define DN_OS_PrintOutStyle(style, string) DN_OS_PrintStyle(DN_OSPrintDest_Out, style, string) -#define DN_OS_PrintOutFStyle(style, fmt, ...) DN_OS_PrintFStyle(DN_OSPrintDest_Out, style, fmt, ##__VA_ARGS__) -#define DN_OS_PrintOutFVStyle(style, fmt, args, ...) DN_OS_PrintFVStyle(DN_OSPrintDest_Out, style, fmt, args) - -#define DN_OS_PrintOutLn(string) DN_OS_PrintLn(DN_OSPrintDest_Out, string) -#define DN_OS_PrintOutLnF(fmt, ...) DN_OS_PrintLnF(DN_OSPrintDest_Out, fmt, ##__VA_ARGS__) -#define DN_OS_PrintOutLnFV(fmt, args) DN_OS_PrintLnFV(DN_OSPrintDest_Out, fmt, args) - -#define DN_OS_PrintOutLnStyle(style, string) DN_OS_PrintLnStyle(DN_OSPrintDest_Out, style, string); -#define DN_OS_PrintOutLnFStyle(style, fmt, ...) DN_OS_PrintLnFStyle(DN_OSPrintDest_Out, style, fmt, ##__VA_ARGS__) -#define DN_OS_PrintOutLnFVStyle(style, fmt, args) DN_OS_PrintLnFVStyle(DN_OSPrintDest_Out, style, fmt, args); - -#define DN_OS_PrintErr(string) DN_OS_Print(DN_OSPrintDest_Err, string) -#define DN_OS_PrintErrF(fmt, ...) DN_OS_PrintF(DN_OSPrintDest_Err, fmt, ##__VA_ARGS__) -#define DN_OS_PrintErrFV(fmt, args) DN_OS_PrintFV(DN_OSPrintDest_Err, fmt, args) - -#define DN_OS_PrintErrStyle(style, string) DN_OS_PrintStyle(DN_OSPrintDest_Err, style, string) -#define DN_OS_PrintErrFStyle(style, fmt, ...) DN_OS_PrintFStyle(DN_OSPrintDest_Err, style, fmt, ##__VA_ARGS__) -#define DN_OS_PrintErrFVStyle(style, fmt, args, ...) DN_OS_PrintFVStyle(DN_OSPrintDest_Err, style, fmt, args) - -#define DN_OS_PrintErrLn(string) DN_OS_PrintLn(DN_OSPrintDest_Err, string) -#define DN_OS_PrintErrLnF(fmt, ...) DN_OS_PrintLnF(DN_OSPrintDest_Err, fmt, ##__VA_ARGS__) -#define DN_OS_PrintErrLnFV(fmt, args) DN_OS_PrintLnFV(DN_OSPrintDest_Err, fmt, args) - -#define DN_OS_PrintErrLnStyle(style, string) DN_OS_PrintLnStyle(DN_OSPrintDest_Err, style, string); -#define DN_OS_PrintErrLnFStyle(style, fmt, ...) DN_OS_PrintLnFStyle(DN_OSPrintDest_Err, style, fmt, ##__VA_ARGS__) -#define DN_OS_PrintErrLnFVStyle(style, fmt, args) DN_OS_PrintLnFVStyle(DN_OSPrintDest_Err, style, fmt, args); - -// NOTE: Print -DN_API void DN_OS_Print (DN_OSPrintDest dest, DN_Str8 string); -DN_API void DN_OS_PrintF (DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, ...); -DN_API void DN_OS_PrintFV (DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, va_list args); - -DN_API void DN_OS_PrintStyle (DN_OSPrintDest dest, DN_LogStyle style, DN_Str8 string); -DN_API void DN_OS_PrintFStyle (DN_OSPrintDest dest, DN_LogStyle style, DN_FMT_ATTRIB char const *fmt, ...); -DN_API void DN_OS_PrintFVStyle (DN_OSPrintDest dest, DN_LogStyle style, DN_FMT_ATTRIB char const *fmt, va_list args); - -DN_API void DN_OS_PrintLn (DN_OSPrintDest dest, DN_Str8 string); -DN_API void DN_OS_PrintLnF (DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, ...); -DN_API void DN_OS_PrintLnFV (DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, va_list args); - -DN_API void DN_OS_PrintLnStyle (DN_OSPrintDest dest, DN_LogStyle style, DN_Str8 string); -DN_API void DN_OS_PrintLnFStyle (DN_OSPrintDest dest, DN_LogStyle style, DN_FMT_ATTRIB char const *fmt, ...); -DN_API void DN_OS_PrintLnFVStyle (DN_OSPrintDest dest, DN_LogStyle style, DN_FMT_ATTRIB char const *fmt, va_list args); -#endif // !defined(DN_OS_H) diff --git a/Source/dn.cpp b/Source/dn.cpp index f5dd7f7..bc9156d 100644 --- a/Source/dn.cpp +++ b/Source/dn.cpp @@ -1,30 +1,33 @@ +#define DN_BASE_CPP + #if defined(_CLANGD) - #define DN_H_WITH_OS 1 + #define DN_ARENA_TEMP_MEM_UAF_GUARD 1 + #define DN_WITH_OS 1 + #define DN_WITH_NET 1 + #define DN_WITH_NET_CURL 1 #include "dn.h" #endif -#include "Base/dn_base.cpp" +#if DN_STR8_AVX512F + #include +#endif + +enum DN_ArenaUAFCheckReportType_ +{ + DN_ArenaUAFCheckReportType_AllocViolation, + DN_ArenaUAFCheckReportType_TempEndOutOfOrder, +}; + DN_Core *g_dn_; -#if DN_H_WITH_OS -#include "OS/dn_os.cpp" -#if defined(DN_PLATFORM_POSIX) || defined(DN_PLATFORM_EMSCRIPTEN) - #include "OS/dn_os_posix.cpp" -#elif defined(DN_PLATFORM_WIN32) - #include "OS/dn_os_w32.cpp" -#else - #error Please define a platform e.g. 'DN_PLATFORM_WIN32' to enable the correct implementation for platform APIs -#endif -#endif - DN_API void DN_Init(DN_Core *dn, DN_InitFlags flags, DN_TCInitArgs args) { DN_Set(dn); dn->init_flags = flags; if (DN_BitIsSet(flags, DN_InitFlags_OS)) { - #if DN_H_WITH_OS + #if DN_WITH_OS DN_OSCore *os = &dn->os; dn->os_init = true; DN_OS_SetLogPrintFuncToOS(); @@ -88,11 +91,15 @@ DN_API void DN_Init(DN_Core *dn, DN_InitFlags flags, DN_TCInitArgs args) #undef DN_CPU_FEAT_XENTRY DN_Assert(g_dn_); #endif + + // NOTE: Initialise thread context + DN_TCInitFromMemFuncs(&dn->main_tc, DN_OS_ThreadID(), args, DN_MemFuncsDefault()); + DN_TCEquip(&dn->main_tc); } if (DN_BitIsSet(flags, DN_InitFlags_LeakTracker)) { DN_Assert(dn->os_init); - #if DN_H_WITH_OS + #if DN_WITH_OS // NOTE: Setup the allocation table with allocation tracking turned off on // the arena we're using to initialise the table. dn->leak.alloc_table_mem = DN_MemListFromMemFuncs(DN_Megabytes(1), DN_Kilobytes(512), DN_MemFlags_NoAllocTrack | DN_MemFlags_AllocCanLeak, DN_MemFuncsDefault()); @@ -101,20 +108,12 @@ DN_API void DN_Init(DN_Core *dn, DN_InitFlags flags, DN_TCInitArgs args) #endif } - if (DN_BitIsSet(flags, DN_InitFlags_ThreadContext)) { - DN_Assert(dn->os_init); - #if DN_H_WITH_OS - DN_TCInitFromMemFuncs(&dn->main_tc, DN_OS_ThreadID(), args, DN_MemFuncsDefault()); - DN_TCEquip(&dn->main_tc); - #endif - } - // NOTE: Print out init features char buf[4096]; DN_USize buf_size = 0; if (DN_BitIsSet(flags, DN_InitFlags_LogLibFeatures)) { DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), "DN initialised:\n"); - #if DN_H_WITH_OS + #if DN_WITH_OS DN_F32 page_size_kib = dn->os.page_size / 1024.0f; DN_F32 alloc_granularity_kib = dn->os.alloc_granularity / 1024.0f; DN_FmtAppendTruncate(buf, @@ -128,7 +127,7 @@ DN_API void DN_Init(DN_Core *dn, DN_InitFlags flags, DN_TCInitArgs args) #endif DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), " Thread Context: "); - if (DN_BitIsSet(flags, DN_InitFlags_ThreadContext)) { + if (DN_BitIsSet(flags, DN_InitFlags_OS)) { DN_Arena *arena = dn->main_tc.main_arena; DN_Str8 mem_funcs = DN_Str8Lit(""); switch (arena->mem->funcs.type) { @@ -173,7 +172,7 @@ DN_API void DN_Init(DN_Core *dn, DN_InitFlags flags, DN_TCInitArgs args) if (DN_BitIsSet(flags, DN_InitFlags_LogCPUFeatures)) { DN_Assert(dn->os_init); - #if DN_H_WITH_OS + #if DN_WITH_OS DN_CPUReport const *report = &dn->os.cpu_report; DN_Str8 brand = DN_Str8TrimWhitespaceAround(DN_Str8FromPtr(report->brand, sizeof(report->brand) - 1)); DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), " CPU '%.*s' from '%s' detected:\n", DN_Str8PrintFmt(brand), report->vendor); @@ -217,15 +216,11776 @@ DN_API DN_Core *DN_Get() DN_API void DN_BeginFrame() { - #if DN_H_WITH_OS + #if DN_WITH_OS DN_AtomicSetValue64(&g_dn_->os.mem_allocs_frame, 0); #endif } -#if DN_H_WITH_NET -#include "Extra/dn_net.cpp" +DN_API bool DN_VerifyArgsF(DN_VerifyType type, bool expr, DN_CallSite call_site, DN_Str8 expr_str8, char const *fmt, ...) +{ + bool result = expr; + if (result) + return result; + + DN_TCScratch scratch = DN_TCScratchBeginArena(nullptr, 0); + { + DN_Str8Builder builder = DN_Str8BuilderFromArena(&scratch.arena); + + // NOTE: Log message prefix + DN_Str8BuilderAppendF(&builder, "Verify [%.*s] failed%s", DN_Str8PrintFmt(expr_str8), fmt ? ". " : ""); + + // NOTE: Log user message + if (fmt) { + va_list args; + va_start(args, fmt); + DN_Str8BuilderAppendFV(&builder, fmt, args); + va_end(args); + } + + // NOTE: Log stack trace + if (DN_PARANOIA_LEVEL) { + if (type == DN_VerifyType_Nil) { + DN_Str8 trace = DN_Str8FromStackTraceNowArena(&scratch.arena, 128 /*limit*/, 4 /*skip*/); + DN_Str8BuilderAppendF(&builder, "\nTrace:\n "); + DN_Str8BuilderAppendRef(&builder, DN_Str8PadNewLinesArena(trace, DN_Str8Lit(" "), &scratch.arena)); + } + } + + DN_Str8 log = DN_Str8FromStr8BuilderArena(&builder, &scratch.arena); + DN_LogType log_type = type == DN_VerifyType_Nil ? DN_LogType_Error : DN_LogType_Warning; + DN_LogTypeParam log_type_param = DN_LogTypeParamFromType(log_type); + DN_LogPrintF(log_type_param, call_site, DN_LogFlags_Nil, "%.*s", DN_Str8PrintFmt(log)); + } + DN_TCScratchEnd(&scratch); + + if (type == DN_VerifyType_Nil && DN_PARANOIA_LEVEL) { + DN_DebugBreak; + } + return result; +} + +DN_API bool DN_VerifyArgs(DN_VerifyType type, bool expr, DN_CallSite call_site, DN_Str8 expr_str8) { + bool result = DN_VerifyArgsF(type, expr, call_site, expr_str8, /*fmt=*/ 0); + return result; +} + +DN_API bool DN_MemStartsWith(void const *lhs, DN_USize lhs_size, void const *rhs, DN_USize rhs_size) +{ + bool result = false; + if (lhs_size >= rhs_size) + result = DN_MemEqUnsafe(lhs, rhs, rhs_size); + return result; +} + +DN_API bool DN_MemEq(void const *lhs, DN_USize lhs_size, void const *rhs, DN_USize rhs_size) +{ + bool result = lhs_size == rhs_size && DN_Memcmp(lhs, rhs, rhs_size) == 0; + return result; +} + +DN_API bool DN_MemEqUnsafe(void const *lhs, void const *rhs, DN_USize size) +{ + bool result = DN_Memcmp(lhs, rhs, size) == 0; + return result; +} + +#if !defined(DN_PLATFORM_ARM64) && !defined(DN_PLATFORM_EMSCRIPTEN) + #define DN_SUPPORTS_CPU_ID #endif -#if DN_CPP_WITH_TESTS +#if defined(DN_SUPPORTS_CPU_ID) && (defined(DN_COMPILER_GCC) || defined(DN_COMPILER_CLANG)) + #include +#endif + +DN_CPUFeatureDecl g_dn_cpu_feature_decl[DN_CPUFeature_Count]; + +DN_API DN_U64 DN_AtomicSetValue64(DN_U64 volatile *target, DN_U64 value) +{ +#if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL) + __int64 result; + do { + result = *target; + } while (DN_AtomicCompareExchange64(target, value, result) != result); + return DN_Cast(DN_U64) result; +#elif defined(DN_COMPILER_GCC) || defined(DN_COMPILER_CLANG) + DN_U64 result = __sync_lock_test_and_set(target, value); + return result; +#else + #error Unsupported compiler +#endif +} + +DN_API DN_U32 DN_AtomicSetValue32(DN_U32 volatile *target, DN_U32 value) +{ +#if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL) + long result; + do { + result = *target; + } while (DN_AtomicCompareExchange32(target, value, result) != result); + return result; +#elif defined(DN_COMPILER_GCC) || defined(DN_COMPILER_CLANG) + long result = __sync_lock_test_and_set(target, value); + return result; +#else + #error Unsupported compiler +#endif +} + +DN_API DN_USize DN_AlignUpPowerOfTwoUSize(DN_USize val) +{ + DN_USize leading_zeros = DN_CountLeadingZerosUSize(val); + DN_USize bits = sizeof(DN_USize) * 8 - 1; + DN_USize result = leading_zeros == 0 ? SIZE_MAX : 1ULL << (bits - leading_zeros + 1); + return result; +} + +DN_API DN_U64 DN_AlignUpPowerOfTwoU64(DN_U64 val) +{ + DN_U64 leading_zeros = DN_CountLeadingZerosU64(val); + DN_U64 result = leading_zeros == 0 ? UINT64_MAX : 1ULL << (63 - leading_zeros + 1); + return result; +} + +DN_API DN_U32 DN_AlignUpPowerOfTwoU32(DN_U32 val) +{ + DN_U32 leading_zeros = DN_CountLeadingZerosU32(val); + DN_U32 result = leading_zeros == 0 ? UINT32_MAX : 1ULL << (31 - leading_zeros + 1); + return result; +} + +DN_API DN_CPUIDResult DN_CPUID(DN_CPUIDArgs args) +{ + DN_CPUIDResult result = {}; +#if defined(DN_SUPPORTS_CPU_ID) + __cpuidex(result.values, args.eax, args.ecx); +#endif + return result; +} + +DN_API DN_USize DN_CPUHasFeatureArray(DN_CPUReport const *report, DN_CPUFeatureQuery *features, DN_USize features_size) +{ + DN_USize result = 0; + DN_USize const BITS = sizeof(report->features[0]) * 8; + for (DN_ForIndexU(feature_index, features_size)) { + DN_CPUFeatureQuery *query = features + feature_index; + DN_USize chunk_index = query->feature / BITS; + DN_USize chunk_bit = query->feature % BITS; + DN_U64 chunk = report->features[chunk_index]; + query->available = chunk & (1ULL << chunk_bit); + result += DN_Cast(int) query->available; + } + + return result; +} + +DN_API bool DN_CPUHasFeature(DN_CPUReport const *report, DN_CPUFeature feature) +{ + DN_CPUFeatureQuery query = {}; + query.feature = feature; + bool result = DN_CPUHasFeatureArray(report, &query, 1) == 1; + return result; +} + +DN_API bool DN_CPUHasAllFeatures(DN_CPUReport const *report, DN_CPUFeature const *features, DN_USize features_size) +{ + bool result = true; + for (DN_USize index = 0; result && index < features_size; index++) + result &= DN_CPUHasFeature(report, features[index]); + return result; +} + +DN_API void DN_CPUSetFeature(DN_CPUReport *report, DN_CPUFeature feature) +{ + DN_Assert(feature < DN_CPUFeature_Count); + DN_USize const BITS = sizeof(report->features[0]) * 8; + DN_USize chunk_index = feature / BITS; + DN_USize chunk_bit = feature % BITS; + report->features[chunk_index] |= (1ULL << chunk_bit); +} + +DN_API DN_CPUReport DN_CPUGetReport() +{ + DN_CPUReport result = {}; +#if defined(DN_SUPPORTS_CPU_ID) + DN_CPUIDResult fn_0000_[500] = {}; + DN_CPUIDResult fn_8000_[500] = {}; + int const EXTENDED_FUNC_BASE_EAX = 0x8000'0000; + int const REGISTER_SIZE = sizeof(fn_0000_[0].reg.eax); + + // NOTE: Query standard/extended numbers + { + DN_CPUIDArgs args = {}; + + // NOTE: Query standard function (e.g. eax = 0x0) for function count + cpu vendor + args = {}; + fn_0000_[0] = DN_CPUID(args); + + // NOTE: Query extended function (e.g. eax = 0x8000'0000) for function count + cpu vendor + args = {}; + args.eax = DN_Cast(int) EXTENDED_FUNC_BASE_EAX; + fn_8000_[0] = DN_CPUID(args); + } + + // NOTE: Extract function count + int const STANDARD_FUNC_MAX_EAX = fn_0000_[0x0000].reg.eax; + int const EXTENDED_FUNC_MAX_EAX = fn_8000_[0x0000].reg.eax; + + // NOTE: Enumerate all CPUID results for the known function counts + { + DN_AssertF((STANDARD_FUNC_MAX_EAX + 1) <= DN_ArrayCountI(fn_0000_), + "Max standard count is %d", + STANDARD_FUNC_MAX_EAX + 1); + DN_AssertF((DN_Cast(DN_ISize) EXTENDED_FUNC_MAX_EAX - EXTENDED_FUNC_BASE_EAX + 1) <= DN_ArrayCountI(fn_8000_), + "Max extended count is %zu", + DN_Cast(DN_ISize) EXTENDED_FUNC_MAX_EAX - EXTENDED_FUNC_BASE_EAX + 1); + + for (int eax = 1; eax <= STANDARD_FUNC_MAX_EAX; eax++) { + DN_CPUIDArgs args = {}; + args.eax = eax; + fn_0000_[eax] = DN_CPUID(args); + } + + for (int eax = EXTENDED_FUNC_BASE_EAX + 1, index = 1; eax <= EXTENDED_FUNC_MAX_EAX; eax++, index++) { + DN_CPUIDArgs args = {}; + args.eax = eax; + fn_8000_[index] = DN_CPUID(args); + } + } + + // NOTE: Query CPU vendor + { + DN_Memcpy(result.vendor + 0, &fn_8000_[0x0000].reg.ebx, REGISTER_SIZE); + DN_Memcpy(result.vendor + 4, &fn_8000_[0x0000].reg.edx, REGISTER_SIZE); + DN_Memcpy(result.vendor + 8, &fn_8000_[0x0000].reg.ecx, REGISTER_SIZE); + } + + // NOTE: Query CPU brand + if (EXTENDED_FUNC_MAX_EAX >= (EXTENDED_FUNC_BASE_EAX + 4)) { + DN_Memcpy(result.brand + 0, &fn_8000_[0x0002].reg.eax, REGISTER_SIZE); + DN_Memcpy(result.brand + 4, &fn_8000_[0x0002].reg.ebx, REGISTER_SIZE); + DN_Memcpy(result.brand + 8, &fn_8000_[0x0002].reg.ecx, REGISTER_SIZE); + DN_Memcpy(result.brand + 12, &fn_8000_[0x0002].reg.edx, REGISTER_SIZE); + + DN_Memcpy(result.brand + 16, &fn_8000_[0x0003].reg.eax, REGISTER_SIZE); + DN_Memcpy(result.brand + 20, &fn_8000_[0x0003].reg.ebx, REGISTER_SIZE); + DN_Memcpy(result.brand + 24, &fn_8000_[0x0003].reg.ecx, REGISTER_SIZE); + DN_Memcpy(result.brand + 28, &fn_8000_[0x0003].reg.edx, REGISTER_SIZE); + + DN_Memcpy(result.brand + 32, &fn_8000_[0x0004].reg.eax, REGISTER_SIZE); + DN_Memcpy(result.brand + 36, &fn_8000_[0x0004].reg.ebx, REGISTER_SIZE); + DN_Memcpy(result.brand + 40, &fn_8000_[0x0004].reg.ecx, REGISTER_SIZE); + DN_Memcpy(result.brand + 44, &fn_8000_[0x0004].reg.edx, REGISTER_SIZE); + + DN_Assert(result.brand[sizeof(result.brand) - 1] == 0); + } + + // NOTE: Query CPU features + for (DN_USize ext_index = 0; ext_index < DN_CPUFeature_Count; ext_index++) { + bool available = false; + + // NOTE: Mask bits taken from various manuals + // - AMD64 Architecture Programmer's Manual, Volumes 1-5 + // - https://en.wikipedia.org/wiki/CPUID#Calling_CPUID + switch (DN_Cast(DN_CPUFeature) ext_index) { + case DN_CPUFeature_3DNow: available = (fn_8000_[0x0001].reg.edx & (1 << 31)); break; + case DN_CPUFeature_3DNowExt: available = (fn_8000_[0x0001].reg.edx & (1 << 30)); break; + case DN_CPUFeature_ABM: available = (fn_8000_[0x0001].reg.ecx & (1 << 5)); break; + case DN_CPUFeature_AES: available = (fn_0000_[0x0001].reg.ecx & (1 << 25)); break; + case DN_CPUFeature_AVX: available = (fn_0000_[0x0001].reg.ecx & (1 << 28)); break; + case DN_CPUFeature_AVX2: available = (fn_0000_[0x0007].reg.ebx & (1 << 0)); break; + case DN_CPUFeature_AVX512F: available = (fn_0000_[0x0007].reg.ebx & (1 << 16)); break; + case DN_CPUFeature_AVX512DQ: available = (fn_0000_[0x0007].reg.ebx & (1 << 17)); break; + case DN_CPUFeature_AVX512IFMA: available = (fn_0000_[0x0007].reg.ebx & (1 << 21)); break; + case DN_CPUFeature_AVX512PF: available = (fn_0000_[0x0007].reg.ebx & (1 << 26)); break; + case DN_CPUFeature_AVX512ER: available = (fn_0000_[0x0007].reg.ebx & (1 << 27)); break; + case DN_CPUFeature_AVX512CD: available = (fn_0000_[0x0007].reg.ebx & (1 << 28)); break; + case DN_CPUFeature_AVX512BW: available = (fn_0000_[0x0007].reg.ebx & (1 << 30)); break; + case DN_CPUFeature_AVX512VL: available = (fn_0000_[0x0007].reg.ebx & (1 << 31)); break; + case DN_CPUFeature_AVX512VBMI: available = (fn_0000_[0x0007].reg.ecx & (1 << 1)); break; + case DN_CPUFeature_AVX512VBMI2: available = (fn_0000_[0x0007].reg.ecx & (1 << 6)); break; + case DN_CPUFeature_AVX512VNNI: available = (fn_0000_[0x0007].reg.ecx & (1 << 11)); break; + case DN_CPUFeature_AVX512BITALG: available = (fn_0000_[0x0007].reg.ecx & (1 << 12)); break; + case DN_CPUFeature_AVX512VPOPCNTDQ: available = (fn_0000_[0x0007].reg.ecx & (1 << 14)); break; + case DN_CPUFeature_AVX5124VNNIW: available = (fn_0000_[0x0007].reg.edx & (1 << 2)); break; + case DN_CPUFeature_AVX5124FMAPS: available = (fn_0000_[0x0007].reg.edx & (1 << 3)); break; + case DN_CPUFeature_AVX512VP2INTERSECT: available = (fn_0000_[0x0007].reg.edx & (1 << 8)); break; + case DN_CPUFeature_AVX512FP16: available = (fn_0000_[0x0007].reg.edx & (1 << 23)); break; + case DN_CPUFeature_CLZERO: available = (fn_8000_[0x0008].reg.ebx & (1 << 0)); break; + case DN_CPUFeature_CMPXCHG8B: available = (fn_0000_[0x0001].reg.edx & (1 << 8)); break; + case DN_CPUFeature_CMPXCHG16B: available = (fn_0000_[0x0001].reg.ecx & (1 << 13)); break; + case DN_CPUFeature_F16C: available = (fn_0000_[0x0001].reg.ecx & (1 << 29)); break; + case DN_CPUFeature_FMA: available = (fn_0000_[0x0001].reg.ecx & (1 << 12)); break; + case DN_CPUFeature_FMA4: available = (fn_8000_[0x0001].reg.ecx & (1 << 16)); break; + case DN_CPUFeature_FP128: available = (fn_8000_[0x001A].reg.eax & (1 << 0)); break; + case DN_CPUFeature_FP256: available = (fn_8000_[0x001A].reg.eax & (1 << 2)); break; + case DN_CPUFeature_FPU: available = (fn_0000_[0x0001].reg.edx & (1 << 0)); break; + case DN_CPUFeature_MMX: available = (fn_0000_[0x0001].reg.edx & (1 << 23)); break; + case DN_CPUFeature_MONITOR: available = (fn_0000_[0x0001].reg.ecx & (1 << 3)); break; + case DN_CPUFeature_MOVBE: available = (fn_0000_[0x0001].reg.ecx & (1 << 22)); break; + case DN_CPUFeature_MOVU: available = (fn_8000_[0x001A].reg.eax & (1 << 1)); break; + case DN_CPUFeature_MmxExt: available = (fn_8000_[0x0001].reg.edx & (1 << 22)); break; + case DN_CPUFeature_PCLMULQDQ: available = (fn_0000_[0x0001].reg.ecx & (1 << 1)); break; + case DN_CPUFeature_POPCNT: available = (fn_0000_[0x0001].reg.ecx & (1 << 23)); break; + case DN_CPUFeature_RDRAND: available = (fn_0000_[0x0001].reg.ecx & (1 << 30)); break; + case DN_CPUFeature_RDSEED: available = (fn_0000_[0x0007].reg.ebx & (1 << 18)); break; + case DN_CPUFeature_RDTSCP: available = (fn_8000_[0x0001].reg.edx & (1 << 27)); break; + case DN_CPUFeature_SHA: available = (fn_0000_[0x0007].reg.ebx & (1 << 29)); break; + case DN_CPUFeature_SSE: available = (fn_0000_[0x0001].reg.edx & (1 << 25)); break; + case DN_CPUFeature_SSE2: available = (fn_0000_[0x0001].reg.edx & (1 << 26)); break; + case DN_CPUFeature_SSE3: available = (fn_0000_[0x0001].reg.ecx & (1 << 0)); break; + case DN_CPUFeature_SSE41: available = (fn_0000_[0x0001].reg.ecx & (1 << 19)); break; + case DN_CPUFeature_SSE42: available = (fn_0000_[0x0001].reg.ecx & (1 << 20)); break; + case DN_CPUFeature_SSE4A: available = (fn_8000_[0x0001].reg.ecx & (1 << 6)); break; + case DN_CPUFeature_SSSE3: available = (fn_0000_[0x0001].reg.ecx & (1 << 9)); break; + case DN_CPUFeature_TSC: available = (fn_0000_[0x0001].reg.edx & (1 << 4)); break; + case DN_CPUFeature_TscInvariant: available = (fn_8000_[0x0007].reg.edx & (1 << 8)); break; + case DN_CPUFeature_VAES: available = (fn_0000_[0x0007].reg.ecx & (1 << 9)); break; + case DN_CPUFeature_VPCMULQDQ: available = (fn_0000_[0x0007].reg.ecx & (1 << 10)); break; + case DN_CPUFeature_Count: DN_AssertInvalidCodePath; break; + } + + if (available) + DN_CPUSetFeature(&result, DN_Cast(DN_CPUFeature) ext_index); + } +#endif // DN_SUPPORTS_CPU_ID + return result; +} + +DN_API void DN_TicketMutex_Begin(DN_TicketMutex *mutex) +{ + DN_UInt ticket = DN_AtomicAddU32(&mutex->ticket, 1); + DN_TicketMutex_BeginTicket(mutex, ticket); +} + +DN_API void DN_TicketMutex_End(DN_TicketMutex *mutex) +{ + DN_AtomicAddU32(&mutex->serving, 1); +} + +DN_API DN_UInt DN_TicketMutex_MakeTicket(DN_TicketMutex *mutex) +{ + DN_UInt result = DN_AtomicAddU32(&mutex->ticket, 1); + return result; +} + +DN_API void DN_TicketMutex_BeginTicket(DN_TicketMutex const *mutex, DN_UInt ticket) +{ + DN_AssertF(mutex->serving <= ticket, + "Mutex skipped ticket? Was ticket generated by the correct mutex via MakeTicket? ticket = %u, " + "mutex->serving = %u", + ticket, + mutex->serving); + while (ticket != mutex->serving) { + // NOTE: Use spinlock intrinsic + _mm_pause(); + } +} + +DN_API bool DN_TicketMutex_CanLock(DN_TicketMutex const *mutex, DN_UInt ticket) +{ + bool result = (ticket == mutex->serving); + return result; +} + +#if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL) + #if !defined(DN_CRT_SECURE_NO_WARNINGS_PREVIOUSLY_DEFINED) + #undef _CRT_SECURE_NO_WARNINGS + #endif +#endif + +// NOTE: DN_Bit +DN_API void DN_BitUnsetInplace(DN_USize *flags, DN_USize bitfield) +{ + *flags = (*flags & ~bitfield); +} + +DN_API void DN_BitSetInplace(DN_USize *flags, DN_USize bitfield) +{ + *flags = (*flags | bitfield); +} + +DN_API bool DN_BitIsSet(DN_USize bits, DN_USize bits_to_set) +{ + bool result = DN_Cast(bool)((bits & bits_to_set) == bits_to_set); + return result; +} + +DN_API bool DN_BitIsAny(DN_USize bits, DN_USize bits_to_check) +{ + bool result = DN_Cast(bool)(bits & bits_to_check); + return result; +} + +DN_API bool DN_BitIsNotSet(DN_USize bits, DN_USize bits_to_check) +{ + auto result = !DN_BitIsSet(bits, bits_to_check); + return result; +} + +DN_API DN_I64 DN_SafeAddI64(DN_I64 a, DN_I64 b) +{ + DN_I64 result = a <= INT64_MAX - b ? (a + b) : INT64_MAX; + return result; +} + +DN_API DN_I64 DN_SafeMulI64(DN_I64 a, DN_I64 b) +{ + DN_I64 result = a <= INT64_MAX / b ? (a * b) : INT64_MAX; + return result; +} + +DN_API DN_U64 DN_SafeAddU64(DN_U64 a, DN_U64 b) +{ + DN_U64 result = a <= UINT64_MAX - b ? (a + b) : UINT64_MAX; + return result; +} + +DN_API DN_U64 DN_SafeSubU64(DN_U64 a, DN_U64 b) +{ + DN_U64 result = a >= b ? (a - b) : 0; + return result; +} + +DN_API DN_U64 DN_SafeMulU64(DN_U64 a, DN_U64 b) +{ + DN_U64 result = a <= UINT64_MAX / b ? (a * b) : UINT64_MAX; + return result; +} + +DN_API DN_U32 DN_SafeSubU32(DN_U32 a, DN_U32 b) +{ + DN_U32 result = a >= b ? (a - b) : 0; + return result; +} + +// NOTE: INT*_MAX literals will be promoted to the type of uintmax_t as uintmax_t is the highest +// possible rank (unsigned > signed). +DN_API int DN_SaturateCastUSizeToInt(DN_USize val) +{ + int result = DN_Cast(uintmax_t) val <= INT_MAX ? DN_Cast(int) val : INT_MAX; + return result; +} + +DN_API DN_I8 DN_SaturateCastUSizeToI8(DN_USize val) +{ + DN_I8 result = DN_Cast(uintmax_t) val <= INT8_MAX ? DN_Cast(DN_I8) val : INT8_MAX; + return result; +} + +DN_API DN_I16 DN_SaturateCastUSizeToI16(DN_USize val) +{ + DN_I16 result = DN_Cast(uintmax_t) val <= INT16_MAX ? DN_Cast(DN_I16) val : INT16_MAX; + return result; +} + +DN_API DN_I32 DN_SaturateCastUSizeToI32(DN_USize val) +{ + DN_I32 result = DN_Cast(uintmax_t) val <= INT32_MAX ? DN_Cast(DN_I32) val : INT32_MAX; + return result; +} + +DN_API DN_I64 DN_SaturateCastUSizeToI64(DN_USize val) +{ + DN_I64 result = DN_Cast(uintmax_t) val <= INT64_MAX ? DN_Cast(DN_I64) val : INT64_MAX; + return result; +} + +// NOTE: Both operands are unsigned and the lowest rank operand will be promoted to +// match the highest rank operand. +DN_API DN_U8 DN_SaturateCastUSizeToU8(DN_USize val) +{ + DN_U8 result = val <= UINT8_MAX ? DN_Cast(DN_U8) val : UINT8_MAX; + return result; +} + +DN_API DN_U16 DN_SaturateCastUSizeToU16(DN_USize val) +{ + DN_U16 result = val <= UINT16_MAX ? DN_Cast(DN_U16) val : UINT16_MAX; + return result; +} + +DN_API DN_U32 DN_SaturateCastUSizeToU32(DN_USize val) +{ + DN_U32 result = val <= UINT32_MAX ? DN_Cast(DN_U32) val : UINT32_MAX; + return result; +} + +DN_API DN_U64 DN_SaturateCastUSizeToU64(DN_USize val) +{ + DN_U64 result = DN_Cast(DN_U64) val <= UINT64_MAX ? DN_Cast(DN_U64) val : UINT64_MAX; + return result; +} + +// NOTE: DN_SaturateCastU64To* +DN_API int DN_SaturateCastU64ToInt(DN_U64 val) +{ + int result = val <= INT_MAX ? DN_Cast(int) val : INT_MAX; + return result; +} + +DN_API DN_I8 DN_SaturateCastU64ToI8(DN_U64 val) +{ + DN_I8 result = val <= INT8_MAX ? DN_Cast(DN_I8) val : INT8_MAX; + return result; +} + +DN_API DN_I16 DN_SaturateCastU64ToI16(DN_U64 val) +{ + DN_I16 result = val <= INT16_MAX ? DN_Cast(DN_I16) val : INT16_MAX; + return result; +} + +DN_API DN_I32 DN_SaturateCastU64ToI32(DN_U64 val) +{ + DN_I32 result = val <= INT32_MAX ? DN_Cast(DN_I32) val : INT32_MAX; + return result; +} + +DN_API DN_I64 DN_SaturateCastU64ToI64(DN_U64 val) +{ + DN_I64 result = val <= INT64_MAX ? DN_Cast(DN_I64) val : INT64_MAX; + return result; +} + +// NOTE: Both operands are unsigned and the lowest rank operand will be promoted to match the +// highest rank operand. +DN_API DN_UInt DN_SaturateCastU64ToUInt(DN_U64 val) +{ + DN_UInt result = val <= UINT8_MAX ? DN_Cast(DN_UInt) val : UINT_MAX; + return result; +} + +DN_API DN_U8 DN_SaturateCastU64ToU8(DN_U64 val) +{ + DN_U8 result = val <= UINT8_MAX ? DN_Cast(DN_U8) val : UINT8_MAX; + return result; +} + +DN_API DN_U16 DN_SaturateCastU64ToU16(DN_U64 val) +{ + DN_U16 result = val <= UINT16_MAX ? DN_Cast(DN_U16) val : UINT16_MAX; + return result; +} + +DN_API DN_U32 DN_SaturateCastU64ToU32(DN_U64 val) +{ + DN_U32 result = val <= UINT32_MAX ? DN_Cast(DN_U32) val : UINT32_MAX; + return result; +} + +// NOTE: Both operands are signed so the lowest rank operand will be promoted to match the highest +// rank operand. +DN_API int DN_SaturateCastISizeToInt(DN_ISize val) +{ + DN_Assert(val >= INT_MIN && val <= INT_MAX); + int result = DN_Cast(int) DN_Clamp(val, INT_MIN, INT_MAX); + return result; +} + +DN_API DN_I8 DN_SaturateCastISizeToI8(DN_ISize val) +{ + DN_Assert(val >= INT8_MIN && val <= INT8_MAX); + DN_I8 result = DN_Cast(DN_I8) DN_Clamp(val, INT8_MIN, INT8_MAX); + return result; +} + +DN_API DN_I16 DN_SaturateCastISizeToI16(DN_ISize val) +{ + DN_Assert(val >= INT16_MIN && val <= INT16_MAX); + DN_I16 result = DN_Cast(DN_I16) DN_Clamp(val, INT16_MIN, INT16_MAX); + return result; +} + +DN_API DN_I32 DN_SaturateCastISizeToI32(DN_ISize val) +{ + DN_Assert(val >= INT32_MIN && val <= INT32_MAX); + DN_I32 result = DN_Cast(DN_I32) DN_Clamp(val, INT32_MIN, INT32_MAX); + return result; +} + +DN_API DN_I64 DN_SaturateCastISizeToI64(DN_ISize val) +{ + DN_Assert(DN_Cast(DN_I64) val >= INT64_MIN && DN_Cast(DN_I64) val <= INT64_MAX); + DN_I64 result = DN_Cast(DN_I64) DN_Clamp(DN_Cast(DN_I64) val, INT64_MIN, INT64_MAX); + return result; +} + +// NOTE: 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. +DN_API DN_UInt DN_SaturateCastISizeToUInt(DN_ISize val) +{ + DN_UInt result = 0; + if (val >= DN_Cast(DN_ISize)0) { + if (DN_Cast(uintmax_t) val <= UINT_MAX) + result = DN_Cast(DN_UInt) val; + else + result = UINT_MAX; + } + return result; +} + +DN_API DN_U8 DN_SaturateCastISizeToU8(DN_ISize val) +{ + DN_U8 result = 0; + if (val >= DN_Cast(DN_ISize) 0) { + if (DN_Cast(uintmax_t) val <= UINT8_MAX) + result = DN_Cast(DN_U8) val; + else + result = UINT8_MAX; + } + return result; +} + +DN_API DN_U16 DN_SaturateCastISizeToU16(DN_ISize val) +{ + DN_U16 result = 0; + if (val >= DN_Cast(DN_ISize) 0) { + if (DN_Cast(uintmax_t) val <= UINT16_MAX) + result = DN_Cast(DN_U16) val; + else + result = UINT16_MAX; + } + return result; +} + +DN_API DN_U32 DN_SaturateCastISizeToU32(DN_ISize val) +{ + DN_U32 result = 0; + if (val >= DN_Cast(DN_ISize) 0) { + if (DN_Cast(uintmax_t) val <= UINT32_MAX) + result = DN_Cast(DN_U32) val; + else + result = UINT32_MAX; + } + return result; +} + +DN_API DN_U64 DN_SaturateCastISizeToU64(DN_ISize val) +{ + DN_U64 result = 0; + if (val >= DN_Cast(DN_ISize) 0) { + if (DN_Cast(uintmax_t) val <= UINT64_MAX) + result = DN_Cast(DN_U64) val; + else + result = UINT64_MAX; + } + return result; +} + +// NOTE: Both operands are signed so the lowest rank operand will be promoted to match the highest +// rank operand. +DN_API DN_ISize DN_SaturateCastI64ToISize(DN_I64 val) +{ + DN_ISize result = DN_Cast(DN_I64) DN_Clamp(val, DN_ISIZE_MIN, DN_ISIZE_MAX); + return result; +} + +DN_API DN_I8 DN_SaturateCastI64ToI8(DN_I64 val) +{ + DN_I8 result = DN_Cast(DN_I8) DN_Clamp(val, INT8_MIN, INT8_MAX); + return result; +} + +DN_API DN_I16 DN_SaturateCastI64ToI16(DN_I64 val) +{ + DN_I16 result = DN_Cast(DN_I16) DN_Clamp(val, INT16_MIN, INT16_MAX); + return result; +} + +DN_API DN_I32 DN_SaturateCastI64ToI32(DN_I64 val) +{ + DN_I32 result = DN_Cast(DN_I32) DN_Clamp(val, INT32_MIN, INT32_MAX); + return result; +} + +DN_API DN_UInt DN_SaturateCastI64ToUInt(DN_I64 val) +{ + DN_UInt result = 0; + if (val >= DN_Cast(DN_I64) 0) { + if (DN_Cast(uintmax_t) val <= UINT_MAX) + result = DN_Cast(DN_UInt) val; + else + result = UINT_MAX; + } + return result; +} + +DN_API DN_USize DN_SaturateCastI64ToUSize(DN_I64 val) +{ + DN_USize result = 0; + if (val >= DN_Cast(DN_I64) 0) { + if (DN_Cast(uintmax_t) val <= DN_USIZE_MAX) + result = DN_Cast(DN_USize) val; + else + result = DN_USIZE_MAX; + } + return result; +} + +DN_API DN_U8 DN_SaturateCastI64ToU8(DN_I64 val) +{ + DN_U8 result = 0; + if (val >= DN_Cast(DN_I64) 0) { + if (DN_Cast(uintmax_t) val <= UINT8_MAX) + result = DN_Cast(DN_U8) val; + else + result = UINT8_MAX; + } + return result; +} + +DN_API DN_U16 DN_SaturateCastI64ToU16(DN_I64 val) +{ + DN_U16 result = 0; + if (val >= DN_Cast(DN_I64) 0) { + if (DN_Cast(uintmax_t) val <= UINT16_MAX) + result = DN_Cast(DN_U16) val; + else + result = UINT16_MAX; + } + return result; +} + +DN_API DN_U32 DN_SaturateCastI64ToU32(DN_I64 val) +{ + DN_U32 result = 0; + if (val >= DN_Cast(DN_I64) 0) { + if (DN_Cast(uintmax_t) val <= UINT32_MAX) + result = DN_Cast(DN_U32) val; + else + result = UINT32_MAX; + } + return result; +} + +DN_API DN_U64 DN_SaturateCastI64ToU64(DN_I64 val) +{ + DN_U64 result = 0; + if (val >= DN_Cast(DN_I64) 0) { + if (DN_Cast(uintmax_t) val <= UINT64_MAX) + result = DN_Cast(DN_U64) val; + else + result = UINT64_MAX; + } + return result; +} + +DN_API DN_I8 DN_SaturateCastIntToI8(int val) +{ + DN_I8 result = DN_Cast(DN_I8) DN_Clamp(val, INT8_MIN, INT8_MAX); + return result; +} + +DN_API DN_I16 DN_SaturateCastIntToI16(int val) +{ + DN_I16 result = DN_Cast(DN_I16) DN_Clamp(val, INT16_MIN, INT16_MAX); + return result; +} + +DN_API DN_U8 DN_SaturateCastIntToU8(int val) +{ + DN_U8 result = 0; + if (val >= DN_Cast(DN_ISize) 0) { + if (DN_Cast(uintmax_t) val <= UINT8_MAX) + result = DN_Cast(DN_U8) val; + else + result = UINT8_MAX; + } + return result; +} + +DN_API DN_U16 DN_SaturateCastIntToU16(int val) +{ + DN_U16 result = 0; + if (val >= DN_Cast(DN_ISize) 0) { + if (DN_Cast(uintmax_t) val <= UINT16_MAX) + result = DN_Cast(DN_U16) val; + else + result = UINT16_MAX; + } + return result; +} + +DN_API DN_U32 DN_SaturateCastIntToU32(int val) +{ + DN_StaticAssert(sizeof(val) <= sizeof(DN_U32) && "Sanity check to allow simplifying of casting"); + DN_U32 result = 0; + if (val >= 0) + result = DN_Cast(DN_U32) val; + return result; +} + +DN_API DN_U64 DN_SaturateCastIntToU64(int val) +{ + DN_StaticAssert(sizeof(val) <= sizeof(DN_U64) && "Sanity check to allow simplifying of casting"); + DN_U64 result = 0; + if (val >= 0) + result = DN_Cast(DN_U64) val; + return result; +} + +// NOTE: DN_Asan +DN_StaticAssert(DN_IsPowerOfTwoAligned(DN_ASAN_POISON_GUARD_SIZE, DN_ASAN_POISON_ALIGNMENT) && + "ASAN poison guard size must be a power-of-two and aligned to ASAN's alignment" "requirement (8 bytes)"); +DN_API void DN_ASanPoisonMemoryRegion(void const volatile *ptr, DN_USize size) +{ + if (!ptr || !size) + return; + +#if DN_HAS_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__) + DN_AssertF(DN_IsPowerOfTwoAligned(ptr, 8), + "Poisoning requires the pointer to be aligned on an 8 byte boundary"); + + __asan_poison_memory_region(ptr, size); + if (DN_ASAN_VET_POISON) { + DN_AssertAlways(__asan_address_is_poisoned(ptr)); + DN_AssertAlways(__asan_address_is_poisoned((char *)ptr + (size - 1))); + } +#else + (void)ptr; + (void)size; +#endif +} + +DN_API void DN_ASanUnpoisonMemoryRegion(void const volatile *ptr, DN_USize size) +{ + if (!ptr || !size) + return; + +#if DN_HAS_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__) + __asan_unpoison_memory_region(ptr, size); + if (DN_ASAN_VET_POISON) + DN_AssertAlways(__asan_region_is_poisoned((void *)ptr, size) == 0); +#else + (void)ptr; + (void)size; +#endif +} + +DN_API DN_F32 DN_EpsilonClampF32(DN_F32 value, DN_F32 target, DN_F32 epsilon) +{ + DN_F32 delta = DN_Abs(target - value); + DN_F32 result = (delta < epsilon) ? target : value; + return result; +} + +static DN_MemBlock *DN_ArenaBlockFromMemFuncs_(DN_U64 reserve, DN_U64 commit, bool track_alloc, bool alloc_can_leak, DN_MemFuncs mem_funcs) +{ + DN_MemBlock *result = nullptr; + switch (mem_funcs.type) { + case DN_MemFuncsType_Nil: + break; + + case DN_MemFuncsType_Heap: { + DN_AssertF(reserve > DN_ARENA_HEADER_SIZE, "%I64u > %I64u", reserve, DN_ARENA_HEADER_SIZE); + result = DN_Cast(DN_MemBlock *) mem_funcs.heap_alloc(reserve); + if (!result) + return result; + + result->used = DN_ARENA_HEADER_SIZE; + result->commit = reserve; + result->reserve = reserve; + } break; + + case DN_MemFuncsType_Virtual: { + DN_AssertF(mem_funcs.virtual_page_size, "Page size must be set to a non-zero, power of two value"); + DN_Assert(DN_IsPowerOfTwo(mem_funcs.virtual_page_size)); + + DN_USize const page_size = mem_funcs.virtual_page_size; + DN_U64 real_reserve = reserve ? reserve : DN_ARENA_RESERVE_SIZE; + DN_U64 real_commit = commit ? commit : DN_ARENA_COMMIT_SIZE; + real_reserve = DN_AlignUpPowerOfTwo(real_reserve, page_size); + real_commit = DN_Min(DN_AlignUpPowerOfTwo(real_commit, page_size), real_reserve); + DN_AssertF(DN_ARENA_HEADER_SIZE < real_commit && real_commit <= real_reserve, "%I64u < %I64u <= %I64u", DN_ARENA_HEADER_SIZE, real_commit, real_reserve); + + DN_MemCommit mem_commit = real_reserve == real_commit ? DN_MemCommit_Yes : DN_MemCommit_No; + result = DN_Cast(DN_MemBlock *) mem_funcs.virtual_reserve(real_reserve, mem_commit, DN_MemPage_ReadWrite); + if (!result) + return result; + + if (mem_commit == DN_MemCommit_No && !mem_funcs.virtual_commit(result, real_commit, DN_MemPage_ReadWrite)) { + mem_funcs.virtual_release(result, real_reserve); + return result; + } + + result->used = DN_ARENA_HEADER_SIZE; + result->commit = real_commit; + result->reserve = real_reserve; + } break; + } + + if (track_alloc && result) + DN_LeakTrackAlloc(&g_dn_->leak, result, result->reserve, alloc_can_leak); + + return result; +} + +static bool DN_ArenaHasPoison_(DN_MemFlags flags) +{ + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(6237) // warning C6237: ( && ) is always zero. is never evaluated and might have side effects. + bool result = DN_ASAN_POISON && DN_BitIsNotSet(flags, DN_MemFlags_NoPoison); + DN_MSVC_WARNING_POP + return result; +} + +static DN_MemBlock *DN_MemBlockFromMemFuncsFlags_(DN_U64 reserve, DN_U64 commit, DN_MemFlags flags, DN_MemFuncs mem_funcs) +{ + bool track_alloc = (flags & DN_MemFlags_NoAllocTrack) == 0; + bool alloc_can_leak = flags & DN_MemFlags_AllocCanLeak; + DN_MemBlock *result = DN_ArenaBlockFromMemFuncs_(reserve, commit, track_alloc, alloc_can_leak, mem_funcs); + if (result && DN_ArenaHasPoison_(flags)) + DN_ASanPoisonMemoryRegion(DN_Cast(char *) result + DN_ARENA_HEADER_SIZE, result->commit - DN_ARENA_HEADER_SIZE); + return result; +} + +static void DN_MemListOnNewBlock_(DN_MemList *mem, DN_MemBlock const *block) +{ + DN_Assert(mem); + if (block) { + mem->stats.info.used += block->used; + mem->stats.info.commit += block->commit; + mem->stats.info.reserve += block->reserve; + mem->stats.info.blocks += 1; + + mem->stats.hwm.used = DN_Max(mem->stats.hwm.used, mem->stats.info.used); + mem->stats.hwm.commit = DN_Max(mem->stats.hwm.commit, mem->stats.info.commit); + mem->stats.hwm.reserve = DN_Max(mem->stats.hwm.reserve, mem->stats.info.reserve); + mem->stats.hwm.blocks = DN_Max(mem->stats.hwm.blocks, mem->stats.info.blocks); + } +} + +DN_API DN_MemStats DN_MemStatsSum(DN_MemStats lhs, DN_MemStats rhs) +{ + DN_MemStats array[] = {lhs, rhs}; + DN_MemStats result = DN_MemStatsSumArray(array, DN_ArrayCountU(array)); + return result; +} + +DN_API DN_MemStats DN_MemStatsSumArray(DN_MemStats const *array, DN_USize size) +{ + DN_MemStats result = {}; + for (DN_ForItSize(it, DN_MemStats const, array, size)) { + DN_MemStats stats = *it.data; + result.info.used += stats.info.used; + result.info.commit += stats.info.commit; + result.info.reserve += stats.info.reserve; + result.info.blocks += stats.info.blocks; + + result.hwm.used = DN_Max(result.hwm.used, result.info.used); + result.hwm.commit = DN_Max(result.hwm.commit, result.info.commit); + result.hwm.reserve = DN_Max(result.hwm.reserve, result.info.reserve); + result.hwm.blocks = DN_Max(result.hwm.blocks, result.info.blocks); + } + return result; +} + +DN_API DN_MemList DN_MemListFromBuffer(void *buffer, DN_USize size, DN_MemFlags flags) +{ + DN_Assert(buffer); + DN_AssertF(DN_ARENA_HEADER_SIZE < size, "Buffer (%zu bytes) too small, need atleast %zu bytes to store arena metadata", size, DN_ARENA_HEADER_SIZE); + DN_AssertF(DN_IsPowerOfTwo(size), "Buffer (%zu bytes) must be a power-of-two", size); + + // NOTE: Init block + DN_MemBlock *block = DN_Cast(DN_MemBlock *) buffer; + block->commit = size; + block->reserve = size; + block->used = DN_ARENA_HEADER_SIZE; + if (block && DN_ArenaHasPoison_(flags)) + DN_ASanPoisonMemoryRegion(DN_Cast(char *) block + DN_ARENA_HEADER_SIZE, block->commit - DN_ARENA_HEADER_SIZE); + + DN_MemList result = {}; + result.flags = flags | DN_MemFlags_NoGrow | DN_MemFlags_NoAllocTrack | DN_MemFlags_AllocCanLeak | DN_MemFlags_UserBuffer; + result.curr = block; + DN_MemListOnNewBlock_(&result, result.curr); + return result; +} + +DN_API DN_MemList DN_MemListFromMemFuncs(DN_U64 reserve, DN_U64 commit, DN_MemFlags flags, DN_MemFuncs mem_funcs) +{ + DN_MemList result = {}; + result.funcs = mem_funcs; + result.flags |= flags | DN_MemFlags_MemFuncs; + result.curr = DN_MemBlockFromMemFuncsFlags_(reserve, commit, flags, mem_funcs); + DN_MemListOnNewBlock_(&result, result.curr); + return result; +} + +static void DN_MemBlockDeinit_(DN_MemList const *mem, DN_MemBlock *block) +{ + DN_USize release_size = block->reserve; + if (DN_BitIsNotSet(mem->flags, DN_MemFlags_NoAllocTrack)) + DN_LeakTrackDealloc(&g_dn_->leak, block); + + if (DN_ArenaHasPoison_(mem->flags)) + DN_ASanUnpoisonMemoryRegion(block, block->commit); + + if (mem->flags & DN_MemFlags_MemFuncs) { + if (mem->funcs.type == DN_MemFuncsType_Heap) + mem->funcs.heap_dealloc(block); + else + mem->funcs.virtual_release(block, release_size); + } +} + +DN_API void DN_MemListDeinit(DN_MemList *mem) +{ + bool mem_allocated_from_itself = DN_MemListOwnsPtr(mem, mem); + for (DN_MemBlock *block = mem ? mem->curr : nullptr; block;) { + DN_MemBlock *block_to_free = block; + block = block->prev; + DN_MemBlockDeinit_(mem, block_to_free); + } + if (mem && !mem_allocated_from_itself) + *mem = {}; +} + +DN_API bool DN_MemListCommitTo(DN_MemList *mem, DN_U64 pos) +{ + if (!mem || !mem->curr) + return false; + + // NOTE: Early out if the position to commit to is already committed + DN_MemBlock *curr = mem->curr; + if (pos <= curr->commit) + return true; + + // NOTE: Sanity check position is within the bounds of the memory block + DN_U64 real_pos = pos; + if (pos > curr->reserve) { + DN_Assert(pos <= curr->reserve); + real_pos = curr->reserve; + } + + // NOTE: Do the commit + DN_Assert(mem->funcs.virtual_page_size); + DN_USize end_commit = DN_AlignUpPowerOfTwo(real_pos, mem->funcs.virtual_page_size); + DN_USize commit_size = end_commit - curr->commit; + char *commit_ptr = DN_Cast(char *) curr + curr->commit; + if (!mem->funcs.virtual_commit(commit_ptr, commit_size, DN_MemPage_ReadWrite)) + return false; + + if (DN_ArenaHasPoison_(mem->flags)) + DN_ASanPoisonMemoryRegion(commit_ptr, commit_size); + + curr->commit = end_commit; + return true; +} + +DN_API bool DN_MemListCommit(DN_MemList *mem, DN_U64 size) +{ + if (!mem || !mem->curr) + return false; + DN_U64 pos = DN_Min(mem->curr->reserve, mem->curr->commit + size); + bool result = DN_MemListCommitTo(mem, pos); + return result; +} + +DN_API bool DN_MemListGrow(DN_MemList *mem, DN_U64 reserve, DN_U64 commit) +{ + if (mem->flags & (DN_MemFlags_NoGrow | DN_MemFlags_UserBuffer)) + return false; + + bool result = false; + DN_MemBlock *new_block = DN_MemBlockFromMemFuncsFlags_(reserve, commit, mem->flags, mem->funcs); + if (new_block) { + result = true; + new_block->prev = mem->curr; + mem->curr = new_block; + new_block->reserve_sum = new_block->prev->reserve_sum + new_block->prev->reserve; + DN_MemListOnNewBlock_(mem, mem->curr); + } + return result; +} + +DN_API void *DN_MemListAlloc(DN_MemList *mem, DN_U64 size, DN_U8 align, DN_ZMem z_mem) +{ + if (!mem) + return nullptr; + + if (!mem->curr) { + mem->curr = DN_MemBlockFromMemFuncsFlags_(DN_ARENA_RESERVE_SIZE, DN_ARENA_COMMIT_SIZE, mem->flags, mem->funcs); + DN_MemListOnNewBlock_(mem, mem->curr); + } + + if (!mem->curr) + return nullptr; + + try_alloc_again: + DN_MemBlock *curr = mem->curr; + bool poison = DN_ArenaHasPoison_(mem->flags); + DN_U8 real_align = poison ? DN_Max(align, DN_ASAN_POISON_ALIGNMENT) : align; + DN_U64 offset_pos = DN_AlignUpPowerOfTwo(curr->used, real_align) + (poison ? DN_ASAN_POISON_GUARD_SIZE : 0); + DN_U64 end_pos = offset_pos + size; + DN_U64 alloc_size = end_pos - curr->used; + + if (end_pos > curr->reserve) { + if (mem->flags & (DN_MemFlags_NoGrow | DN_MemFlags_UserBuffer)) + return nullptr; + DN_USize new_reserve = DN_Max(DN_ARENA_HEADER_SIZE + alloc_size, DN_ARENA_RESERVE_SIZE); + DN_USize new_commit = DN_Max(DN_ARENA_HEADER_SIZE + alloc_size, DN_ARENA_COMMIT_SIZE); + if (!DN_MemListGrow(mem, new_reserve, new_commit)) + return nullptr; + goto try_alloc_again; + } + + DN_USize prev_arena_commit = curr->commit; + if (end_pos > curr->commit) { + DN_Assert(mem->funcs.virtual_page_size); + DN_Assert(mem->funcs.type == DN_MemFuncsType_Virtual); + DN_Assert((mem->flags & DN_MemFlags_UserBuffer) == 0); + DN_USize end_commit = DN_AlignUpPowerOfTwo(end_pos, mem->funcs.virtual_page_size); + DN_USize commit_size = end_commit - curr->commit; + char *commit_ptr = DN_Cast(char *) curr + curr->commit; + if (!mem->funcs.virtual_commit(commit_ptr, commit_size, DN_MemPage_ReadWrite)) + return nullptr; + if (poison && DN_BitIsNotSet(mem->flags, DN_MemFlags_SimAlloc)) + DN_ASanPoisonMemoryRegion(commit_ptr, commit_size); + curr->commit = end_commit; + mem->stats.info.commit += commit_size; + mem->stats.hwm.commit = DN_Max(mem->stats.hwm.commit, mem->stats.info.commit); + } + + void *result = DN_Cast(char *) curr + offset_pos; + curr->used += alloc_size; + mem->stats.info.used += alloc_size; + mem->stats.hwm.used = DN_Max(mem->stats.hwm.used, mem->stats.info.used); + + if (poison && DN_BitIsNotSet(mem->flags, DN_MemFlags_SimAlloc)) + DN_ASanUnpoisonMemoryRegion(result, size); + + if (z_mem == DN_ZMem_Yes && DN_BitIsNotSet(mem->flags, DN_MemFlags_SimAlloc)) { + DN_USize reused_bytes = DN_Min(prev_arena_commit - offset_pos, size); + DN_Memset(result, 0, reused_bytes); + } + + DN_Assert(mem->stats.hwm.used >= mem->stats.info.used); + DN_Assert(mem->stats.hwm.commit >= mem->stats.info.commit); + DN_Assert(mem->stats.hwm.reserve >= mem->stats.info.reserve); + DN_Assert(mem->stats.hwm.blocks >= mem->stats.info.blocks); + return result; +} + +DN_API void *DN_MemListAllocContiguous(DN_MemList *mem, DN_U64 size, DN_U8 align, DN_ZMem z_mem) +{ + DN_MemFlags prev_flags = mem->flags; + mem->flags |= (DN_MemFlags_NoGrow | DN_MemFlags_NoPoison); + void *memory = DN_MemListAlloc(mem, size, align, z_mem); + mem->flags = prev_flags; + return memory; +} + +DN_API void *DN_MemListCopy(DN_MemList *mem, void const *data, DN_U64 size, DN_U8 align) +{ + if (!mem || !data || size == 0) + return nullptr; + void *result = DN_MemListAlloc(mem, size, align, DN_ZMem_No); + if (result) + DN_Memcpy(result, data, size); + return result; +} + +DN_API void DN_MemListPopTo(DN_MemList *mem, DN_U64 init_used) +{ + if (!mem || !mem->curr) + return; + + // NOTE: Free any memory blocks allocated additionally from the initial block to revert to + DN_U64 used = DN_Max(DN_ARENA_HEADER_SIZE, init_used); + DN_MemBlock *curr = mem->curr; + while (curr->reserve_sum >= used) { + DN_MemBlock *block_to_free = curr; + mem->stats.info.used -= block_to_free->used; + mem->stats.info.commit -= block_to_free->commit; + mem->stats.info.reserve -= block_to_free->reserve; + mem->stats.info.blocks -= 1; + if (mem->flags & DN_MemFlags_UserBuffer) + break; + curr = curr->prev; + DN_MemBlockDeinit_(mem, block_to_free); + } + + // NOTE: Revert the memory block we returned to + DN_U64 old_used = curr->used; + mem->stats.info.used = old_used; + mem->curr = curr; + curr->used = used - curr->reserve_sum; + + // NOTE: Scrub memory that we used previously in the block but no longer after reverting + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(4127) // conditional expression is constant + if (DN_SCRUB_UNINIT_MEM_BYTE) { + if (old_used > curr->used) { + char *discarded = (char *)curr + curr->used; + DN_USize scrub_size = old_used - curr->used; + + // NOTE: If we allocated memory unaligned then the pointer given to the user was aligned up + // and unpoisoned. If the user snapped a memory list position before that allocation then + // attempts to revert it, scrubbing from the memory position (which is before alignment was + // applied!) will cause this code to accidentally scrub the our poison guard bytes. So we + // unpoison the region unconditionally to ensure that is cleaned up before scrubbing. Since + // scrubbing is a debug feature and, you have it turned on _with_ ASAN then we let that + // performance penalty slide. + if (DN_ArenaHasPoison_(mem->flags)) + DN_ASanUnpoisonMemoryRegion(discarded, scrub_size); + + DN_Memset(discarded, DN_SCRUB_UNINIT_MEM_BYTE, scrub_size); + } + } + DN_MSVC_WARNING_POP + + // NOTE: ASAN Poison + if (DN_ArenaHasPoison_(mem->flags)) { + char *poison_ptr = (char *)curr + DN_AlignUpPowerOfTwo(curr->used, DN_ASAN_POISON_ALIGNMENT); + DN_USize poison_size = ((char *)curr + curr->commit) - poison_ptr; + DN_ASanPoisonMemoryRegion(poison_ptr, poison_size); + } + mem->stats.info.used += curr->used; +} + +DN_API void DN_MemListPop(DN_MemList *mem, DN_U64 amount) +{ + DN_MemBlock *curr = mem->curr; + DN_USize used_sum = curr->reserve_sum + curr->used; + amount = DN_Min(amount, used_sum); + DN_USize pop_to = used_sum - amount; + DN_MemListPopTo(mem, pop_to); +} + +DN_API DN_U64 DN_MemListPos(DN_MemList const *mem) +{ + DN_U64 result = (mem && mem->curr) ? mem->curr->reserve_sum + mem->curr->used : 0; + return result; +} + +DN_API void DN_MemListClear(DN_MemList *mem) +{ + DN_MemListPopTo(mem, 0); +} + +DN_API bool DN_MemListOwnsPtr(DN_MemList const *mem, void *ptr) +{ + bool result = false; + uintptr_t uint_ptr = DN_Cast(uintptr_t) ptr; + for (DN_MemBlock const *block = mem ? mem->curr : nullptr; !result && block; block = block->prev) { + uintptr_t begin = DN_Cast(uintptr_t) block + DN_ARENA_HEADER_SIZE; + uintptr_t end = begin + block->reserve; + result = uint_ptr >= begin && uint_ptr <= end; + } + return result; +} + +DN_API DN_Str8x64 DN_MemListInfoStr8x64(DN_MemListInfo info) +{ + DN_Str8x64 result = {}; + DN_Str8x32 used = DN_Str8x32FromByteCountU64Auto(info.used); + DN_Str8x32 commit = DN_Str8x32FromByteCountU64Auto(info.commit); + DN_Str8x32 reserve = DN_Str8x32FromByteCountU64Auto(info.reserve); + // NOTE: Blocks, Used, Commit, Reserve + result = DN_Str8x64FromFmt("B=%u U=%.*s C=%.*s R=%.*s", DN_Cast(DN_U32)info.blocks, DN_Str8PrintFmt(used), DN_Str8PrintFmt(commit), DN_Str8PrintFmt(reserve)); + return result; +} + +DN_API DN_MemListTemp DN_MemListTempBegin(DN_MemList *mem) +{ + DN_MemListTemp result = {}; + if (mem) { + result.mem = mem; + result.used_sum = mem->curr ? mem->curr->reserve_sum + mem->curr->used : 0; + } + return result; +}; + +DN_API void DN_MemListTempEnd(DN_MemListTemp temp) +{ + DN_MemListPopTo(temp.mem, temp.used_sum); +}; + +DN_Str8 const DN_MEM_LIST_UAF_TRACING_DISABLED_MORE_INFO_STR8_ = DN_Str8Lit( + "\n\nSet `DN_MemFlags_TempMemUAFTrace` on the affected arenas or " + "`#define DN_ARENA_TEMP_MEM_UAF_TRACE_ON_BY_DEFAULT 1` for more information" +); + +#if defined(DN_ARENA_TEMP_MEM_UAF_GUARD) +static bool DN_MemListUAFTracingEnabled_(DN_MemList *mem) +{ + bool result = DN_ARENA_TEMP_MEM_UAF_TRACE_ON_BY_DEFAULT; + if (!result) + result = mem->flags & DN_MemFlags_TempMemUAFTrace; + if (mem->flags & DN_MemFlags_TempMemUAFTraceDisable) + result = false; + return result; +} +#endif + +static void DN_ArenaUAFCheck_(DN_Arena *arena, DN_ArenaUAFCheckReportType_ type) +{ + (void)arena; + (void)type; + #if DN_ARENA_TEMP_MEM_UAF_GUARD + DN_MemList *mem = arena->mem; + if (!arena || !mem) + return; + + if ((arena->uaf_guard_temp_mem || mem->uaf_guard_active_temp_mem) && !arena->uaf_guard_is_being_checked) { + // NOTE: The following functions below allocate memory which might trigger an additional UAF + // check which would cause infinite recursion so we set a flag here to prevent that. + arena->uaf_guard_is_being_checked = true; + if (mem->uaf_guard_active_id != arena->uaf_guard_id) { + // NOTE: We use the MemList on the arena directly to bypass any potential recursive UAF (if the + // current arena is triggering the UAF check then it's already violating so we use the + // underlying primitive to allocate memory). + DN_Allocator allocator = DN_AllocatorFromMemList(mem); + + // NOTE: MSVC does not recognise %'u which is a STB extension which causes a lot of incorrect + // format arguments warnings that we mute here. + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(6271) // Extra argument passed to 'DN_Str8FromFmtArena' + DN_MSVC_WARNING_DISABLE(6067) // _Param_(10) in call to 'DN_LogPrint' must be the address of a string. Actual type: 'int'. + DN_MSVC_WARNING_DISABLE(6273) // Non-integer passed as _Param_(11) when an integer is required in call to 'DN_LogPrint' Actual type: 'char *'. + DN_Str8 error_msg = {}; + if (type == DN_ArenaUAFCheckReportType_AllocViolation) { + error_msg = DN_Str8FromFmtAllocator(allocator, + "Arena use-after-free (UAF) detected in temporary memory usage! This allocation (trace " + "shown above) is attempting to allocate memory inside the active temporary region (id: %'u) " + "but belongs to a different region (id: %'u). This means when the active temporary region is " + "released, this allocation will be released and scrubbed causing a potential UAF.\n\nEnsure " + "that scratch memory is deconflicting correctly, scratch and or temporary memory regions have " + "matching begin and end pairs and only the arena view with the active temporary memory region " + "is being allocated from.", + mem->uaf_guard_active_id, + arena->uaf_guard_id); + } else { + error_msg = DN_Str8Lit("The active temporary memory region recorded on the arena is " + "different from the current temporary memory region recorded on " + "the memory list allocator. This means that a temporary region " + "began but was not ended after the region was completed. Temporary " + "memory regions are enforced in a first-in-last-out manner (FILO) " + "to ensure the developer's intent of what the temporary region " + "spans is logically consistent and always strictly ends and begins " + "within a known lifetime."); + } + + DN_Str8 prefix = DN_Str8LineBreakAllocator(error_msg, 100, DN_Str8Lit("\n"), DN_Str8LineBreakMode_AtWord, allocator); + if (DN_MemListUAFTracingEnabled_(mem)) { + DN_Str8 curr_stack_trace = DN_Str8Lit(""); + if (arena->uaf_guard_temp_mem) + curr_stack_trace = DN_Str8FromStackTraceAllocator(allocator, &arena->uaf_guard_temp_mem->trace, 1); + curr_stack_trace = DN_Str8PadNewLinesAllocator(curr_stack_trace, DN_Str8Lit(" "), allocator); + + DN_Str8 active_stack_trace = DN_Str8Lit(""); + if (mem->uaf_guard_active_temp_mem) + active_stack_trace = DN_Str8FromStackTraceAllocator(allocator, &mem->uaf_guard_active_temp_mem->trace, 1); + active_stack_trace = DN_Str8PadNewLinesAllocator(active_stack_trace, DN_Str8Lit(" "), allocator); + + DN_AssertF(mem->uaf_guard_active_id == arena->uaf_guard_id, + "%.*s\n\nThe originating temporary memory region (id: %'u) was created at:" + "\n\n %.*s\n\nThe active temporary memory region (id: %'u) was created at:\n\n %.*s\n", + DN_Str8PrintFmt(prefix), + arena->uaf_guard_id, + DN_Str8PrintFmt(curr_stack_trace), + mem->uaf_guard_active_id, + DN_Str8PrintFmt(active_stack_trace)); + } else { + DN_Str8 suffix = DN_Str8LineBreakAllocator(DN_MEM_LIST_UAF_TRACING_DISABLED_MORE_INFO_STR8_, 100, DN_Str8Lit("\n"), DN_Str8LineBreakMode_AtWord, allocator); + DN_AssertF(mem->uaf_guard_active_id == arena->uaf_guard_id, "%.*s%.*s", DN_Str8PrintFmt(prefix), DN_Str8PrintFmt(suffix)); + } + DN_MSVC_WARNING_POP + } + arena->uaf_guard_is_being_checked = false; + } + #endif +} + +DN_API DN_Arena DN_ArenaFromMemList(DN_MemList *mem) +{ + DN_Arena result = {}; + result.mem = mem; + return result; +} + +DN_API DN_Arena DN_ArenaTempBeginFromMemList(DN_MemList* mem) +{ + DN_Arena result = DN_ArenaFromMemList(mem); + DN_MemListTemp temp_mem = DN_MemListTempBegin(mem); + +#if DN_ARENA_TEMP_MEM_UAF_GUARD + // NOTE: Below we use the `MemList` and bypass the UAF checks which could cause infinite recursion + // depending on how, say, stack-traces are implemented. + if (DN_MemListUAFTracingEnabled_(mem)) + temp_mem.trace = DN_StackTraceFromAllocator(DN_AllocatorFromMemList(mem), 256); + + // NOTE: Create persistent temp mem and set it on the mem list + result.uaf_guard_temp_mem = DN_MemListNewCopy(mem, DN_MemListTemp, &temp_mem); + result.uaf_guard_prev_temp_mem = mem->uaf_guard_active_temp_mem; + mem->uaf_guard_active_temp_mem = result.uaf_guard_temp_mem; + + // NOTE: Update IDs + result.uaf_guard_id = ++mem->uaf_guard_next_id; + result.uaf_guard_prev_id = mem->uaf_guard_active_id; + mem->uaf_guard_active_id = result.uaf_guard_id; +#else + result.temp_mem = temp_mem; +#endif + return result; +} + + +DN_API DN_Arena DN_ArenaTempBeginFromArena(DN_Arena *arena) +{ + DN_Arena result = DN_ArenaTempBeginFromMemList(arena->mem); + return result; +} + +DN_API void DN_ArenaTempEnd(DN_Arena *arena, DN_ArenaReset reset) +{ +#if DN_ARENA_TEMP_MEM_UAF_GUARD + DN_AssertF(arena->uaf_guard_temp_mem, "Arena was not created with temp memory"); + DN_ArenaUAFCheck_(arena, DN_ArenaUAFCheckReportType_TempEndOutOfOrder); +#else + DN_AssertF(arena->temp_mem.mem, "Arena was not created with temp memory"); +#endif + + if (reset == DN_ArenaReset_Yes) { +#if DN_ARENA_TEMP_MEM_UAF_GUARD + DN_MemListTempEnd(*arena->uaf_guard_temp_mem); +#else + DN_MemListTempEnd(arena->temp_mem); +#endif + } + +#if DN_ARENA_TEMP_MEM_UAF_GUARD + DN_MemList *mem = arena->mem; + mem->uaf_guard_active_id = arena->uaf_guard_prev_id; + mem->uaf_guard_active_temp_mem = arena->uaf_guard_prev_temp_mem; + + arena->uaf_guard_prev_temp_mem = nullptr; + arena->uaf_guard_prev_id = 0; + arena->uaf_guard_temp_mem = nullptr; +#endif +} + +DN_API void *DN_ArenaAlloc(DN_Arena *arena, DN_U64 size, DN_U8 align, DN_ZMem z_mem) +{ + DN_ArenaUAFCheck_(arena, DN_ArenaUAFCheckReportType_AllocViolation); + void *result = DN_MemListAlloc(arena->mem, size, align, z_mem); + return result; +} + +DN_API void *DN_ArenaAllocContiguous(DN_Arena *arena, DN_U64 size, DN_U8 align, DN_ZMem z_mem) +{ + DN_ArenaUAFCheck_(arena, DN_ArenaUAFCheckReportType_AllocViolation); + void *result = DN_MemListAllocContiguous(arena->mem, size, align, z_mem); + return result; +} + +DN_API void *DN_ArenaCopy(DN_Arena *arena, void const *data, DN_U64 size, DN_U8 align) +{ + DN_ArenaUAFCheck_(arena, DN_ArenaUAFCheckReportType_AllocViolation); + void *result = DN_MemListCopy(arena->mem, data, size, align); + return result; +} + +DN_API DN_Pool DN_PoolFromArena(DN_Arena *arena, DN_U8 align) +{ + DN_Pool result = {}; + if (arena) { + result.arena = arena; + result.align = align ? align : DN_POOL_DEFAULT_ALIGN; + } + return result; +} + +DN_API bool DN_PoolIsValid(DN_Pool const *pool) +{ + bool result = pool && pool->arena && pool->align; + return result; +} + +DN_API void *DN_PoolAlloc(DN_Pool *pool, DN_USize size) +{ + void *result = nullptr; + if (!DN_PoolIsValid(pool)) + return result; + + DN_USize const required_size = sizeof(DN_PoolSlot) + pool->align + size; + DN_USize const DN_USizeo_slot_offset = 5; // __lzcnt64(32) e.g. DN_PoolSlotSize_32B + DN_USize slot_index = 0; + if (required_size > 32) { + // NOTE: Round up if not PoT as the low bits are set. + DN_USize dist_to_next_msb = DN_CountLeadingZerosUSize(required_size) + 1; + dist_to_next_msb -= DN_Cast(DN_USize)(!DN_IsPowerOfTwo(required_size)); + + DN_USize const register_size = sizeof(DN_USize) * 8; + DN_AssertF(register_size >= (dist_to_next_msb - DN_USizeo_slot_offset), "lhs=%zu, rhs=%zu", register_size, (dist_to_next_msb - DN_USizeo_slot_offset)); + slot_index = register_size - dist_to_next_msb - DN_USizeo_slot_offset; + } + + if (slot_index >= DN_PoolSlotSize_Count) { + DN_AssertF(slot_index < DN_PoolSlotSize_Count, "Chunk pool does not support the requested allocation size"); + return result; + } + + DN_USize slot_size_in_bytes = 1ULL << (slot_index + DN_USizeo_slot_offset); + DN_AssertF(required_size <= (slot_size_in_bytes << 0), "slot_index=%zu, lhs=%zu, rhs=%zu", slot_index, required_size, (slot_size_in_bytes << 0)); + DN_AssertF(required_size >= (slot_size_in_bytes >> 1), "slot_index=%zu, lhs=%zu, rhs=%zu", slot_index, required_size, (slot_size_in_bytes >> 1)); + + DN_PoolSlot *slot = nullptr; + if (pool->slots[slot_index]) { + slot = pool->slots[slot_index]; + pool->slots[slot_index] = slot->next; + DN_Memset(slot->data, 0, size); + DN_Assert(DN_IsPowerOfTwoAligned(slot->data, pool->align)); + } else { + void *bytes = DN_ArenaAlloc(pool->arena, slot_size_in_bytes, alignof(DN_PoolSlot), DN_ZMem_Yes); + slot = DN_Cast(DN_PoolSlot *) bytes; + + // NOTE: The raw pointer is round up to the next 'pool->align'-ed + // address ensuring at least 1 byte of padding between the raw pointer + // and the pointer given to the user and that the user pointer is + // aligned to the pool's alignment. + // + // This allows us to smuggle 1 byte behind the user pointer that has + // the offset to the original pointer. + slot->data = DN_Cast(void *) DN_AlignDownPowerOfTwo(DN_Cast(uintptr_t) slot + sizeof(DN_PoolSlot) + pool->align, pool->align); + + uintptr_t offset_to_original_ptr = DN_Cast(uintptr_t) slot->data - DN_Cast(uintptr_t) bytes; + DN_Assert(slot->data > bytes); + DN_Assert(offset_to_original_ptr <= sizeof(DN_PoolSlot) + pool->align); + + // NOTE: Store the offset to the original pointer behind the user's + // pointer. + char *offset_to_original_storage = DN_Cast(char *) slot->data - 1; + DN_Memcpy(offset_to_original_storage, &offset_to_original_ptr, 1); + } + + // NOTE: Smuggle the slot type in the next pointer so that we know, when the + // pointer gets returned which free list to return the pointer to. + result = slot->data; + slot->next = DN_Cast(DN_PoolSlot *) slot_index; + return result; +} + +DN_API void DN_PoolDealloc(DN_Pool *pool, void *ptr) +{ + if (!DN_PoolIsValid(pool) || !ptr) + return; + + DN_Assert(DN_MemListOwnsPtr(pool->arena->mem, ptr)); + + char const *one_byte_behind_ptr = DN_Cast(char *) ptr - 1; + DN_USize offset_to_original_ptr = 0; + DN_Memcpy(&offset_to_original_ptr, one_byte_behind_ptr, 1); + DN_Assert(offset_to_original_ptr <= sizeof(DN_PoolSlot) + pool->align); + + char *original_ptr = DN_Cast(char *) ptr - offset_to_original_ptr; + DN_PoolSlot *slot = DN_Cast(DN_PoolSlot *) original_ptr; + DN_PoolSlotSize slot_index = DN_Cast(DN_PoolSlotSize)(DN_Cast(uintptr_t) slot->next); + DN_Assert(slot_index < DN_PoolSlotSize_Count); + + // NOTE: Scrub memory before returning to the pool + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(4127) // conditional expression is constant + if (DN_SCRUB_UNINIT_MEM_BYTE) { + DN_USize slot_size_in_bytes = 1ULL << (slot_index + 5); + DN_USize data_offset = (char *)slot->data - (char *)slot; + DN_Memset(slot->data, DN_SCRUB_UNINIT_MEM_BYTE, slot_size_in_bytes - data_offset); + } + DN_MSVC_WARNING_POP + + slot->next = pool->slots[slot_index]; + pool->slots[slot_index] = slot; +} + +static void DN_ErrSinkCheck_(DN_ErrSink const *err) +{ + DN_Assert(err->arena->mem); + if (err->stack_size == 0) + return; + + DN_ErrSinkNode const *node = err->stack + (err->stack_size - 1); + DN_Assert(node->mode >= DN_ErrSinkMode_Nil && node->mode <= DN_ErrSinkMode_ExitOnError); + DN_Assert(node->msg_sentinel); + + // NOTE: Walk the list ensuring we eventually terminate at the sentinel (e.g. we have a + // well formed doubly-linked-list terminated by a sentinel, or otherwise we will hit the + // walk limit or dereference a null pointer and assert) + DN_USize WALK_LIMIT = 99'999; + DN_USize walk = 0; + for (DN_ErrSinkMsg *it = node->msg_sentinel->next; it != node->msg_sentinel; it = it->next, walk++) { + DN_AssertF(it, "Encountered null pointer which should not happen in a sentinel DLL"); + DN_Assert(walk < WALK_LIMIT); + } +} + +DN_API DN_ErrSink* DN_ErrSinkBegin_(DN_ErrSink *err, DN_ErrSinkMode mode, DN_CallSite call_site) +{ + // NOTE: OOM error + if (err->stack_size == DN_ArrayCountU(err->stack)) { + DN_Str8Builder builder = DN_Str8BuilderFromArena(err->arena); + for (DN_ForItSize(it, DN_ErrSinkNode, err->stack, err->stack_size)) + DN_Str8BuilderAppendF(&builder, " [%04zu] %.*s:%u %.*s\n", it.index, DN_Str8PrintFmt(it.data->call_site.file), it.data->call_site.line, DN_Str8PrintFmt(it.data->call_site.function)); + DN_Str8 msg = DN_Str8FromStr8BuilderArena(&builder, err->arena); + DN_AssertF(err->stack_size < DN_ArrayCountU(err->stack), "Error sink has run out of error scopes, potential leak. Scopes were\n%.*s", DN_Str8PrintFmt(msg)); + } + + // NOTE: Allocate the node + DN_ErrSinkNode *node = err->stack + err->stack_size++; + node->arena_pos = DN_MemListPos(err->arena->mem); + node->mode = mode; + node->call_site = call_site; + DN_SentinelDoublyLLInitArena(node->msg_sentinel, DN_ErrSinkMsg, err->arena); + + // NOTE: Handle allocation error + if (!node || !node->msg_sentinel) { + DN_MemListPopTo(err->arena->mem, node->arena_pos); + node->msg_sentinel = nullptr; + err->stack_size--; + } + + DN_ErrSink *result = err; + return result; +} + +DN_API bool DN_ErrSinkHasError(DN_ErrSink *err) +{ + bool result = false; + if (err && err->stack_size) { + DN_ErrSinkNode *node = err->stack + (err->stack_size - 1); + result = DN_SentinelDoublyLLHasItems(node->msg_sentinel); + } + return result; +} + +DN_API DN_ErrSinkMsg *DN_ErrSinkEnd(DN_Arena *arena, DN_ErrSink *err) +{ + DN_ErrSinkMsg *result = nullptr; + DN_ErrSinkCheck_(err); + DN_AssertF(arena != err->arena, "You are not allowed to reuse the arena for ending the error sink because the memory would get popped and lost"); + + // NOTE: Walk the list and allocate it onto the user's arena + DN_ErrSinkNode *node = err->stack + (err->stack_size - 1); + DN_ErrSinkMsg *prev = nullptr; + for (DN_ErrSinkMsg *it = node->msg_sentinel->next; it != node->msg_sentinel; it = it->next) { + DN_ErrSinkMsg *entry = DN_ArenaNew(arena, DN_ErrSinkMsg, DN_ZMem_Yes); + entry->msg = DN_Str8FromStr8Arena(it->msg, arena); + entry->call_site = it->call_site; + entry->error_code = it->error_code; + if (!result) + result = entry; // Assign first entry if we haven't yet + if (prev) + prev->next = entry; // Link the prev message to the current one + prev = entry; // Update prev to latest + } + + // NOTE: Deallocate all the memory for this scope + err->stack_size--; + DN_MemListPopTo(err->arena->mem, node->arena_pos); + return result; +} + +static void DN_ErrSinkAddMsgToStr8Builder_(DN_Str8Builder *builder, DN_ErrSinkMsg *msg, DN_ErrSinkMsg *end) +{ + if (msg == end) // NOTE: No error messages to add + return; + + if (msg->next == end) { + DN_ErrSinkMsg *it = msg; + DN_Str8 file_name = DN_Str8FileNameFromPath(it->call_site.file); + DN_Str8BuilderAppendF(builder, + "%.*s:%05I32u:%.*s %.*s", + DN_Str8PrintFmt(file_name), + it->call_site.line, + DN_Str8PrintFmt(it->call_site.function), + DN_Str8PrintFmt(it->msg)); + } else { + // NOTE: More than one message + for (DN_ErrSinkMsg *it = msg; it != end; it = it->next) { + DN_Str8 file_name = DN_Str8FileNameFromPath(it->call_site.file); + DN_Str8BuilderAppendF(builder, + "%s - %.*s:%05I32u:%.*s%s%.*s", + it == msg ? "" : "\n", + DN_Str8PrintFmt(file_name), + it->call_site.line, + DN_Str8PrintFmt(it->call_site.function), + it->msg.size ? " " : "", + DN_Str8PrintFmt(it->msg)); + } + } +} + +DN_API DN_Str8 DN_ErrSinkEndStr8(DN_Arena *arena, DN_ErrSink *err) +{ + DN_Str8 result = {}; + DN_ErrSinkCheck_(err); + if (err->stack_size == 0) + return result; + + DN_AssertF(arena != err->arena, "You are not allowed to reuse the arena for ending the error sink because the memory would get popped and lost"); + + // NOTE: Walk the list and allocate it onto the user's arena + DN_Str8Builder builder = DN_Str8BuilderFromArena(err->arena); + DN_ErrSinkNode *node = err->stack + (err->stack_size - 1); + DN_ErrSinkAddMsgToStr8Builder_(&builder, node->msg_sentinel->next, node->msg_sentinel); + + // NOTE: Deallocate all the memory for this scope + err->stack_size--; + DN_MemListPopTo(err->arena->mem, node->arena_pos); + + result = DN_Str8FromStr8BuilderArena(&builder, arena); + return result; +} + +DN_API void DN_ErrSinkEndIgnore(DN_ErrSink *err) +{ + DN_ErrSinkEnd(nullptr, err); +} + +DN_API bool DN_ErrSinkEndLogError_(DN_ErrSink *err, DN_CallSite call_site, DN_Str8 err_msg) +{ + DN_ErrSinkNode *node = err->stack + (err->stack_size - 1); + DN_AssertF(err->stack_size, "Begin must be called before calling end"); + DN_AssertF(node->msg_sentinel, "Begin must be called before calling end"); + err->stack_size--; + + bool result = false; + if (node->msg_sentinel != node->msg_sentinel->next) { + result = true; + // NOTE: Build the error string + DN_Str8Builder builder = DN_Str8BuilderFromArena(err->arena); + { + if (err_msg.size) { + DN_Str8BuilderAppendRef(&builder, err_msg); + DN_Str8BuilderAppendRef(&builder, DN_Str8Lit(":")); + } else { + DN_Str8BuilderAppendRef(&builder, DN_Str8Lit("Error(s) encountered:")); + } + if (node->msg_sentinel->next->next != node->msg_sentinel) // NOTE: More than 1 message + DN_Str8BuilderAppendRef(&builder, DN_Str8Lit("\n")); + DN_ErrSinkAddMsgToStr8Builder_(&builder, node->msg_sentinel->next, node->msg_sentinel); + } + + // NOTE: Log the error + DN_Str8 log = DN_Str8FromStr8BuilderArena(&builder, err->arena); + DN_LogPrintF(DN_LogTypeParamFromType(DN_LogType_Error), call_site, DN_LogFlags_Nil, "%.*s", DN_Str8PrintFmt(log)); + + if (node->mode == DN_ErrSinkMode_DebugBreakOnErrorLog) + DN_DebugBreak; + + // NOTE: Deallocate the error node's memory and pop it from the stack + DN_MemListPopTo(err->arena->mem, node->arena_pos); + } + return result; +} + +DN_API bool DN_ErrSinkEndLogErrorFV_(DN_ErrSink *err, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_Str8 log = DN_Str8FromFmtVArena(err->arena, fmt, args); + bool result = DN_ErrSinkEndLogError_(err, call_site, log); + return result; +} + +DN_API bool DN_ErrSinkEndLogErrorF_(DN_ErrSink *err, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8 log = DN_Str8FromFmtVArena(err->arena, fmt, args); + bool result = DN_ErrSinkEndLogError_(err, call_site, log); + va_end(args); + return result; +} + +DN_API void DN_ErrSinkEndExitIfErrorFV_(DN_ErrSink *err, DN_CallSite call_site, DN_U32 exit_val, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + if (DN_ErrSinkEndLogErrorFV_(err, call_site, fmt, args)) { + DN_DebugBreak; + DN_OS_Exit(exit_val); + } +} + +DN_API void DN_ErrSinkEndExitIfErrorF_(DN_ErrSink *err, DN_CallSite call_site, DN_U32 exit_val, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_ErrSinkEndExitIfErrorFV_(err, call_site, exit_val, fmt, args); + va_end(args); +} + +DN_API void DN_ErrSinkAppendFV_(DN_ErrSink *err, DN_U32 error_code, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + if (!err) + return; + + DN_Assert(err->stack_size); + DN_ErrSinkNode *node = err->stack + (err->stack_size - 1); + DN_AssertF(node, "Error sink must be begun by calling 'Begin' before using this function."); + + DN_ErrSinkMsg *msg = DN_ArenaNew(err->arena, DN_ErrSinkMsg, DN_ZMem_Yes); + DN_Assert(msg); + msg->msg = DN_Str8FromFmtVArena(err->arena, fmt, args); + msg->error_code = error_code; + msg->call_site = call_site; + DN_SentinelDoublyLLPrepend(node->msg_sentinel, msg); + if (node->mode == DN_ErrSinkMode_ExitOnError) + DN_ErrSinkEndExitIfErrorF_(err, msg->call_site, error_code, "Fatal error %u", error_code); +} + +DN_API void DN_ErrSinkAppendF_(DN_ErrSink *err, DN_U32 error_code, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_ErrSinkAppendFV_(err, error_code, call_site, fmt, args); + va_end(args); +} + +DN_THREAD_LOCAL DN_TCCore *g_dn_thread_context; + +DN_API void DN_TCInit(DN_TCCore *tc, DN_U64 thread_id, DN_Arena *main_arena, DN_Arena *temp_arenas, DN_USize temp_arenas_count, DN_Arena *err_sink_arena) +{ + tc->thread_id = thread_id; + tc->main_arena = main_arena; + tc->main_pool = DN_PoolFromArena(tc->main_arena, 0); + tc->err_sink.arena = err_sink_arena; + DN_Assert(temp_arenas_count < DN_ArrayCountU(tc->temp_arenas)); + for (DN_ForIndexU(index, temp_arenas_count)) + tc->temp_arenas[tc->temp_arenas_count++] = temp_arenas + index; +} + +DN_API DN_TCInitArgs DN_TCInitArgsDefault() +{ + DN_TCInitArgs result = {}; + result.main_reserve = DN_Kilobytes(64); + result.main_commit = DN_Kilobytes(4); + result.temp_reserve = DN_Kilobytes(64); + result.temp_commit = DN_Kilobytes(4); + result.temp_count = 2; + result.err_sink_reserve = DN_Kilobytes(64); + result.err_sink_commit = DN_Kilobytes(4); + return result; +} + +DN_API void DN_TCInitFromMemFuncs(DN_TCCore *tc, DN_U64 thread_id, DN_TCInitArgs args, DN_MemFuncs mem_funcs) +{ + DN_Assert(args.temp_count <= DN_ArrayCountU(tc->temp_arenas)); + tc->main_arena_mem_ = DN_MemListFromMemFuncs(args.main_reserve, args.main_commit, DN_MemFlags_AllocCanLeak | DN_MemFlags_NoAllocTrack, mem_funcs); + for (DN_ForIndexU(index, args.temp_count)) { + tc->temp_arena_mems_[index] = DN_MemListFromMemFuncs(args.temp_reserve, args.temp_commit, DN_MemFlags_AllocCanLeak | DN_MemFlags_NoAllocTrack, mem_funcs); + tc->temp_arenas_[index] = DN_ArenaFromMemList(&tc->temp_arena_mems_[index]); + } + tc->err_sink_arena_mem_ = DN_MemListFromMemFuncs(args.err_sink_reserve, args.err_sink_commit, DN_MemFlags_AllocCanLeak | DN_MemFlags_NoAllocTrack, mem_funcs); + tc->main_arena_ = DN_ArenaFromMemList(&tc->main_arena_mem_); + tc->err_sink_arena_ = DN_ArenaFromMemList(&tc->err_sink_arena_mem_); + DN_TCInit(tc, thread_id, &tc->main_arena_, tc->temp_arenas_, args.temp_count, &tc->err_sink_arena_); +} + +DN_API void DN_TCDeinit(DN_TCCore *tc, DN_TCDeinitArenas deinit_arenas) +{ + if (deinit_arenas == DN_TCDeinitArenas_Yes) { + DN_MemListDeinit(tc->main_arena->mem); + for (DN_ForIndexU(index, tc->temp_arenas_count)) { + DN_MemListDeinit(tc->temp_arenas[index]->mem); + DN_MemListDeinit(tc->temp_arenas[index]->mem); + } + DN_MemListDeinit(tc->err_sink.arena->mem); + } +} + +DN_API void DN_TCEquip(DN_TCCore *tc) +{ + g_dn_thread_context = tc; +} + +DN_API DN_TCCore *DN_TCGet() +{ + DN_AssertRaw(g_dn_thread_context && + "This thread's thread context has not been equipped yet. Ensure that DN_TCInit(...) " + "has been called to create a thread context and call DN_TCEquip(...) in the current " + "thread to make it retrievable via this function"); + return g_dn_thread_context; +} + +DN_API DN_Arena *DN_TCMainArena() +{ + DN_TCCore *tc = DN_TCGet(); + DN_Arena *result = tc->main_arena; + return result; +} + +DN_API DN_Pool *DN_TCMainPool() +{ + DN_TCCore *tc = DN_TCGet(); + DN_Pool *result = &tc->main_pool; + return result; +} + +DN_API DN_Arena DN_TCTempArenaAllocator(DN_Allocator *conflicts, DN_USize count) +{ + DN_MemList *conflict_mem_lists[8]; + DN_USize conflict_mem_lists_count = 0; + for (DN_ForItSize(it, DN_Allocator, conflicts, count)) { + DN_Allocator *allocator = it.data; + if (!allocator->context) + continue; + + DN_MemList *mem_list = nullptr; + switch (allocator->type) { + case DN_AllocatorType_MemList: mem_list = DN_Cast(DN_MemList *)allocator->context; break; + + case DN_AllocatorType_Arena: { + DN_Arena *arena = DN_Cast(DN_Arena *) allocator->context; + mem_list = arena->mem; + } break; + + case DN_AllocatorType_Pool: { + DN_Pool *pool = DN_Cast(DN_Pool *) allocator->context; + mem_list = pool->arena ? pool->arena->mem : nullptr; + } break; + } + + if (!mem_list) + continue; + + void *added = DN_LArrayAppend(conflict_mem_lists, &conflict_mem_lists_count, mem_list); + DN_Assert(added); + } + + DN_TCCore *tc = DN_TCGet(); + DN_Arena result = {}; + for (DN_ForItSize(it, DN_Arena *, tc->temp_arenas, tc->temp_arenas_count)) { + bool is_usable = true; + DN_Arena *rhs_arena = *it.data; + DN_MemList *rhs_mem = rhs_arena->mem; + for (DN_ForItSize(conflict_it, DN_MemList*, conflict_mem_lists, conflict_mem_lists_count)) { + DN_MemList *lhs_mem = *conflict_it.data; + if (lhs_mem == rhs_mem) { + is_usable = false; + break; + } + } + + if (is_usable) { + result = DN_ArenaTempBeginFromMemList(rhs_mem); + break; + } + } + + DN_AssertF(result.mem, "All temp arenas are being used, there are none left to return to the caller"); + return result; +} + +DN_API DN_Arena DN_TCTempArenaFromArena(DN_Arena **conflicts, DN_USize count) +{ + DN_TCCore *tc = DN_TCGet(); + DN_Arena result = {}; + for (DN_ForItSize(it, DN_Arena *, tc->temp_arenas, tc->temp_arenas_count)) { + bool is_usable = true; + DN_Arena *rhs_arena = *it.data; + DN_MemList *rhs_mem = rhs_arena->mem; + for (DN_ForItSize(conflict_it, DN_Arena *, conflicts, count)) { + DN_Arena *lhs_arena = *conflict_it.data; + DN_MemList *lhs_mem = lhs_arena->mem; + if (lhs_mem == rhs_mem) { + is_usable = false; + break; + } + } + + if (is_usable) { + result = DN_ArenaTempBeginFromMemList(rhs_mem); + break; + } + } + + DN_AssertF(result.mem, "All temp arenas are being used, there are none left to return to the caller"); + return result; +} + +#if defined(__cplusplus) +DN_TCScratchCpp::DN_TCScratchCpp(DN_Arena **conflicts, DN_USize count) +{ + this->data = DN_TCScratchBeginArena(conflicts, count); +} + +DN_TCScratchCpp::~DN_TCScratchCpp() +{ + DN_TCScratchEnd(&this->data); +} +#endif + +DN_API DN_TCScratch DN_TCScratchBeginAllocator(DN_Allocator *conflicts, DN_USize count) +{ + DN_TCScratch result = {}; + result.arena = DN_TCTempArenaAllocator(conflicts, count); + return result; +} + +DN_API DN_TCScratch DN_TCScratchBeginArena(DN_Arena **conflicts, DN_USize count) +{ + DN_TCScratch result = {}; + result.arena = DN_TCTempArenaFromArena(conflicts, count); + return result; +} + +DN_API void DN_TCScratchEnd(DN_TCScratch *scratch) +{ + DN_Assert(scratch->destructed == false); + DN_ArenaTempEnd(&scratch->arena, DN_ArenaReset_Yes); + *scratch = {}; + scratch->destructed = true; +} + +DN_API void DN_TCSetFrameArena(DN_Arena *arena) +{ + DN_TCCore *tc = DN_TCGet(); + tc->frame_arena = arena; +} + +DN_API DN_Arena *DN_TCFrameArena() +{ + DN_TCCore *tc = DN_TCGet(); + DN_Arena *result = tc->frame_arena; + return result; +} + +DN_API DN_ErrSink *DN_TCErrSink() +{ + DN_TCCore *tc = DN_TCGet(); + DN_ErrSink *result = &tc->err_sink; + return result; +} + +DN_API void *DN_PoolCopy(DN_Pool *pool, void const *data, DN_U64 size, DN_U8 align) +{ + if (!pool || !data || size == 0) + return nullptr; + + // TODO: Hmm should align be part of the alloc interface in general? I'm not going to worry + // about this until we crash because of misalignment. + DN_Assert(pool->align >= align); + + void *result = DN_PoolAlloc(pool, size); + if (result) + DN_Memcpy(result, data, size); + return result; +} + +DN_API bool DN_CharIsAlphabet(char ch) +{ + bool result = (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'); + return result; +} + +DN_API bool DN_CharIsDigit(char ch) +{ + bool result = (ch >= '0' && ch <= '9'); + return result; +} + +DN_API bool DN_CharIsAlphaNum(char ch) +{ + bool result = DN_CharIsAlphabet(ch) || DN_CharIsDigit(ch); + return result; +} + +DN_API bool DN_CharIsWhitespace(char ch) +{ + bool result = (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'); + return result; +} + +DN_API bool DN_CharIsHex(char ch) +{ + bool result = ((ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') || (ch >= '0' && ch <= '9')); + return result; +} + +DN_API char DN_CharToLower(char ch) +{ + char result = ch; + if (result >= 'A' && result <= 'Z') + result += 'a' - 'A'; + return result; +} + +DN_API char DN_CharToUpper(char ch) +{ + char result = ch; + if (result >= 'a' && result <= 'z') + result -= 'a' - 'A'; + return result; +} + +DN_API DN_U64FromResult DN_U64FromStr8(DN_Str8 string, char separator) +{ + // NOTE: Argument check + DN_U64FromResult result = {}; + if (string.size == 0) { + result.success = true; + return result; + } + + // NOTE: Sanitize input/output + DN_Str8 trim_string = DN_Str8TrimWhitespaceAround(string); + if (trim_string.size == 0) { + result.success = true; + return result; + } + + // NOTE: Handle prefix '+' + DN_USize start_index = 0; + if (!DN_CharIsDigit(trim_string.data[0])) { + if (trim_string.data[0] != '+') + return result; + start_index++; + } + + // NOTE: Convert the string number to the binary number + for (DN_USize index = start_index; index < trim_string.size; index++) { + char ch = trim_string.data[index]; + if (index) { + if (separator != 0 && ch == separator) + continue; + } + + if (!DN_CharIsDigit(ch)) + return result; + + result.value = DN_SafeMulU64(result.value, 10); + DN_U64 digit = ch - '0'; + result.value = DN_SafeAddU64(result.value, digit); + } + + result.success = true; + return result; +} + +DN_API DN_U64FromResult DN_U64FromPtr(void const *data, DN_USize size, char separator) +{ + DN_Str8 str8 = DN_Str8FromPtr((char *)data, size); + DN_U64FromResult result = DN_U64FromStr8(str8, separator); + return result; +} + +DN_API DN_U64 DN_U64FromPtrUnsafe(void const *data, DN_USize size, char separator) +{ + DN_U64FromResult from = DN_U64FromPtr(data, size, separator); + DN_U64 result = from.value; + DN_Assert(from.success); + return result; +} + +DN_API DN_U64FromResult DN_U64FromHexPtr(void const *hex, DN_USize hex_count) +{ + char *hex_ptr = DN_Cast(char *) hex; + if (hex_count >= 2 && hex_ptr[0] == '0' && (hex_ptr[1] == 'x' || hex_ptr[1] == 'X')) { + hex_ptr += 2; + hex_count -= 2; + } + + DN_U64FromResult result = {}; + DN_USize max_hex_count = sizeof(DN_U64) * 2; + DN_USize count = DN_Min(max_hex_count, hex_count); + DN_Assert(hex_count <= max_hex_count); + for (DN_USize index = 0; index < count; index++) { + char ch = hex_ptr[index]; + DN_U8 val = DN_U8FromHexNibble(ch); + if (val == 0xFF) + return result; + result.value = (result.value << 4) | val; + } + result.success = true; + return result; +} + +DN_API DN_U64 DN_U64FromHexPtrUnsafe(void const *hex, DN_USize hex_count) +{ + DN_U64FromResult from = DN_U64FromHexPtr(hex, hex_count); + DN_U64 result = from.value; + DN_Assert(from.success); + return result; +} + +DN_API DN_U64FromResult DN_U64FromHexStr8(DN_Str8 hex) +{ + DN_U64FromResult result = DN_U64FromHexPtr(hex.data, hex.size); + return result; +} + +DN_API DN_U64 DN_U64FromHexStr8Unsafe(DN_Str8 hex) +{ + DN_U64 result = DN_U64FromHexPtrUnsafe(hex.data, hex.size); + return result; +} + +DN_API DN_U64 DN_U64FromU8x32HiBEUnsafe(DN_U8x32 const *val) +{ + DN_U64 result_be = 0; // Last 8 bytes of 32-byte slot (big-endian) + DN_Memcpy(&result_be, val->data + sizeof(val->data) - sizeof(result_be), sizeof(result_be)); + DN_U64 result = DN_ByteSwap64(result_be); + return result; +} + +DN_API DN_U64FromResult DN_U64FromU8x32HiBE(DN_U8x32 const *val) +{ + DN_U64FromResult result = {}; + if (val) { + // NOTE: Check that the high bits are not set + DN_U8x32 zero_mask = {}; + bool high_bits_set = DN_Memcmp(val->data, zero_mask.data, sizeof(zero_mask.data) - sizeof(result)) != 0; + result.success = !high_bits_set; + result.value = DN_U64FromU8x32HiBEUnsafe(val); + } + return result; +} + +DN_API DN_USize DN_USizeFromU8x32HiBEUnsafe(DN_U8x32 const *val) +{ + DN_USize result_be = 0; + DN_Memcpy(&result_be, val->data + sizeof(val->data) - sizeof(result_be), sizeof(result_be)); + DN_USize result = DN_ByteSwapUSize(result_be); + return result; +} + +DN_API DN_USizeFromResult DN_USizeFromU8x32HiBE(DN_U8x32 const *val) +{ + DN_USizeFromResult result = {}; + if (val) { + // NOTE: Check that the high bits are not set + DN_U8x32 mask = {}; + DN_Memset(mask.data, 1, sizeof(mask.data) - sizeof(result)); + bool high_bits_set = DN_Memcmp(val->data, mask.data, 24) != 0; + result.success = !high_bits_set; + result.value = DN_USizeFromU8x32HiBEUnsafe(val); + } + return result; +} + +DN_API void DN_ByteSwapU64Ptr(DN_U8 *dest, DN_U64 src) +{ + dest[0] = DN_Cast(DN_U8)((src >> 56) & 0xFF); + dest[1] = DN_Cast(DN_U8)((src >> 48) & 0xFF); + dest[2] = DN_Cast(DN_U8)((src >> 40) & 0xFF); + dest[3] = DN_Cast(DN_U8)((src >> 32) & 0xFF); + dest[4] = DN_Cast(DN_U8)((src >> 24) & 0xFF); + dest[5] = DN_Cast(DN_U8)((src >> 16) & 0xFF); + dest[6] = DN_Cast(DN_U8)((src >> 8) & 0xFF); + dest[7] = DN_Cast(DN_U8)(src & 0xFF); +} + +DN_API DN_I64FromResult DN_I64FromStr8(DN_Str8 string, char separator) +{ + // NOTE: Argument check + DN_I64FromResult result = {}; + if (string.size == 0) { + result.success = true; + return result; + } + + // NOTE: Sanitize input/output + DN_Str8 trim_string = DN_Str8TrimWhitespaceAround(string); + if (trim_string.size == 0) { + result.success = true; + return result; + } + + bool negative = false; + DN_USize start_index = 0; + if (!DN_CharIsDigit(trim_string.data[0])) { + negative = (trim_string.data[start_index] == '-'); + if (!negative && trim_string.data[0] != '+') + return result; + start_index++; + } + + // NOTE: Convert the string number to the binary number + for (DN_USize index = start_index; index < trim_string.size; index++) { + char ch = trim_string.data[index]; + if (index) { + if (separator != 0 && ch == separator) + continue; + } + + if (!DN_CharIsDigit(ch)) + return result; + + result.value = DN_SafeMulU64(result.value, 10); + DN_U64 digit = ch - '0'; + result.value = DN_SafeAddU64(result.value, digit); + } + + if (negative) + result.value *= -1; + + result.success = true; + return result; +} + +DN_API DN_I64FromResult DN_I64FromPtr(void const *data, DN_USize size, char separator) +{ + DN_Str8 str8 = DN_Str8FromPtr((char *)data, size); + DN_I64FromResult result = DN_I64FromStr8(str8, separator); + return result; +} + +DN_API DN_I64 DN_I64FromPtrUnsafe(void const *data, DN_USize size, char separator) +{ + DN_I64FromResult from = DN_I64FromPtr(data, size, separator); + DN_I64 result = from.value; + DN_Assert(from.success); + return result; +} + +DN_API bool DN_U8x32Eq(DN_U8x32 const *lhs, DN_U8x32 const *rhs) +{ + bool result = DN_MemEqUnsafe(lhs->data, rhs->data, sizeof(lhs->data)); + return result; +} + +DN_API DN_U8x32 DN_U8x32FromBytesLeftPadZ(DN_U8 const *ptr, DN_USize count) +{ + DN_U8x32 result = {}; + DN_Assert(count <= sizeof(result.data)); + DN_Memcpy(result.data + sizeof(result.data) - count, ptr, count); + return result; +} + +DN_API DN_U8x32 DN_U8x32FromHexUnsafe(DN_Str8 hex_32b) +{ + DN_U8x32 result = {}; + hex_32b = DN_Str8TrimHexPrefix(hex_32b); + DN_Assert(hex_32b.size <= sizeof(result.data) * 2); + DN_BytesFromHexPtr(hex_32b.data, hex_32b.size, result.data, sizeof(result.data)); + return result; +} + +DN_API DN_U8x32FromResult DN_U8x32FromHex(DN_Str8 hex_32b) +{ + DN_U8x32FromResult result = {}; + DN_USize bytes_written = DN_BytesFromHexPtr(hex_32b.data, hex_32b.size, result.value.data, sizeof(result.value.data)); + if (bytes_written == sizeof(result.value.data)) + result.success = true; + return result; +} + +DN_API DN_U8x32FromResult DN_U8x32FromDecimalStr8(DN_Str8 decimal) +{ + DN_U8x32FromResult result = {}; + result.success = true; + for (DN_USize i = 0; i < decimal.size; i++) { + DN_U8 digit = decimal.data[i]; + if (!DN_CharIsDigit(digit)) { + result.success = false; + break; + } + + DN_U8 digit_val = digit - '0'; + + // NOTE: Goal is to do => (result = result * 10 + digit_val) + // Multiply current result by 10 + DN_U16 carry = 0; + for (int j = 31; j >= 0; j--) { + DN_U16 prod = DN_Cast(DN_U16)result.value.data[j] * 10 + carry; + result.value.data[j] = DN_Cast(DN_U8)(prod & 0xFF); + carry = prod >> 8; + } + + // Add the digit + carry = digit_val; + for (int j = 31; j >= 0 && carry > 0; j--) { + DN_U16 sum = DN_Cast(DN_U16)result.value.data[j] + carry; + result.value.data[j] = DN_Cast(DN_U8)(sum & 0xFF); + carry = sum >> 8; + } + } + + return result; +} + +DN_API DN_Allocator DN_AllocatorFromMemList(DN_MemList *mem) +{ + DN_Allocator result = {}; + result.type = DN_AllocatorType_MemList; + result.context = mem; + return result; +} + +DN_API DN_Allocator DN_AllocatorFromArena(DN_Arena *arena) +{ + DN_Allocator result = {}; + result.type = DN_AllocatorType_Arena; + result.context = arena; + return result; +} + +DN_API DN_Allocator DN_AllocatorFromPool(DN_Pool *pool) +{ + DN_Allocator result = {}; + result.type = DN_AllocatorType_Pool; + result.context = pool; + return result; +} + +DN_API void *DN_AllocatorAlloc(DN_Allocator allocator, DN_USize size, DN_U8 align, DN_ZMem z_mem) +{ + void *result = nullptr; + if (allocator.context) { + switch (allocator.type) { + case DN_AllocatorType_Arena: result = DN_ArenaAlloc (DN_Cast(DN_Arena *) allocator.context, size + 1, align, z_mem); break; + case DN_AllocatorType_Pool: result = DN_PoolAlloc (DN_Cast(DN_Pool *) allocator.context, size + 1); break; + case DN_AllocatorType_MemList: result = DN_MemListAlloc(DN_Cast(DN_MemList *) allocator.context, size + 1, align, z_mem); break; + } + } + return result; +} + +DN_API DN_FmtAppendResult DN_FmtVAppend(char *buf, DN_USize *buf_size, DN_USize buf_max, char const *fmt, va_list args) +{ + DN_FmtAppendResult result = {}; + DN_USize starting_size = *buf_size; + result.size_req = DN_VSNPrintF(buf + *buf_size, DN_Cast(int)(buf_max - *buf_size), fmt, args); + *buf_size += result.size_req; + if (*buf_size >= (buf_max - 1)) + *buf_size = buf_max - 1; + DN_Assert(*buf_size <= (buf_max - 1)); + result.str8 = DN_Str8FromPtr(buf, *buf_size); + result.truncated = result.str8.size != (starting_size + result.size_req); + return result; +} + +DN_API DN_FmtAppendResult DN_FmtAppend(char *buf, DN_USize *buf_size, DN_USize buf_max, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_FmtAppendResult result = DN_FmtVAppend(buf, buf_size, buf_max - (*buf_size), fmt, args); + va_end(args); + return result; +} + +DN_API DN_FmtAppendResult DN_FmtAppendTruncate(char *buf, DN_USize *buf_size, DN_USize buf_max, DN_Str8 truncator, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_FmtAppendResult result = DN_FmtVAppend(buf, buf_size, buf_max, fmt, args); + if (result.truncated) + DN_Memcpy(result.str8.data + result.str8.size - truncator.size, truncator.data, truncator.size); + va_end(args); + return result; +} + +DN_API DN_USize DN_FmtSize(DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_USize result = DN_VSNPrintF(nullptr, 0, fmt, args); + va_end(args); + return result; +} + +DN_API DN_USize DN_FmtVSize(DN_FMT_ATTRIB char const *fmt, va_list args) +{ + va_list args_copy; + va_copy(args_copy, args); + DN_USize result = DN_VSNPrintF(nullptr, 0, fmt, args_copy); + va_end(args_copy); + return result; +} + +DN_API DN_USize DN_CStr8Size(char const *src) +{ + DN_USize result = 0; + for (; src && src[0] != 0; src++, result++) + ; + return result; +} + +DN_API DN_USize DN_CStr16Size(wchar_t const *src) +{ + DN_USize result = 0; + for (; src && src[0] != 0; src++, result++) + ; + return result; +} + +DN_API DN_Str8 DN_Str8AllocAllocator(DN_USize size, DN_ZMem z_mem, DN_Allocator allocator) +{ + DN_Str8 result = {}; + result.data = DN_Cast(char *) DN_AllocatorAlloc(allocator, size + 1, alignof(char), z_mem); + if (result.data) { + result.size = size; + result.data[result.size] = 0; + } + return result; +} + +DN_API DN_Str8 DN_Str8AllocArena(DN_USize size, DN_ZMem z_mem, DN_Arena *arena) +{ + DN_Str8 result = DN_Str8AllocAllocator(size, z_mem, DN_AllocatorFromArena(arena)); + return result; +} + +DN_API DN_Str8 DN_Str8AllocPool(DN_USize size, DN_Pool *pool) +{ + DN_Str8 result = DN_Str8AllocAllocator(size, DN_ZMem_No, DN_AllocatorFromPool(pool)); + return result; +} + +DN_API DN_Str8 DN_Str8FromCStr8(char const *src) +{ + DN_USize size = DN_CStr8Size(src); + DN_Str8 result = DN_Str8FromPtr(src, size); + return result; +} + +DN_API DN_Str8 DN_Str8FromCStr8Arena(char const *src, DN_Arena *arena) +{ + DN_Str8 shallow = DN_Str8FromCStr8(src); + DN_Str8 result = DN_Str8FromStr8Arena(shallow, arena); + return result; +} + +DN_API DN_Str8 DN_Str8FromPtrArena(void const *data, DN_USize size, DN_Arena *arena) +{ + DN_Str8 result = DN_Str8AllocArena(size, DN_ZMem_No, arena); + if (result.size) + DN_Memcpy(result.data, data, size); + return result; +} + +DN_API DN_Str8 DN_Str8FromPtrPool(void const *data, DN_USize size, DN_Pool *pool) +{ + DN_Str8 result = DN_Str8AllocPool(size, pool); + if (result.size) + DN_Memcpy(result.data, data, size); + return result; +} + +DN_API DN_Str8 DN_Str8FromStr8Allocator(DN_Str8 string, DN_Allocator allocator) +{ + DN_Str8 result = {}; + result.data = DN_Cast(char *) DN_AllocatorAlloc(allocator, string.size + 1, alignof(char), DN_ZMem_No); + if (result.data) { + DN_Memcpy(result.data, string.data, string.size); + result.data[string.size] = 0; + result.size = string.size; + } + return result; +} + +DN_API DN_Str8 DN_Str8FromStr8Arena(DN_Str8 string, DN_Arena *arena) +{ + DN_Str8 result = DN_Str8FromStr8Allocator(string, DN_AllocatorFromArena(arena)); + return result; +} + +DN_API DN_Str8 DN_Str8FromStr8Pool(DN_Str8 string, DN_Pool *pool) +{ + DN_Str8 result = DN_Str8FromStr8Allocator(string, DN_AllocatorFromPool(pool)); + return result; +} + +DN_API DN_Str8 DN_Str8FromFmtVAllocator(DN_Allocator allocator, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_USize size = DN_FmtVSize(fmt, args); + DN_Str8 result = DN_Str8AllocAllocator(size, DN_ZMem_No, allocator); + if (result.data) { + DN_USize written = 0; + DN_FmtVAppend(result.data, &written, result.size + 1, fmt, args); + DN_Assert(written == result.size); + } + return result; +} + +DN_API DN_Str8 DN_Str8FromFmtVArena(DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_Str8 result = DN_Str8FromFmtVAllocator(DN_AllocatorFromArena(arena), fmt, args); + return result; +} + +DN_API DN_Str8 DN_Str8FromFmtAllocator(DN_Allocator allocator, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list va; + va_start(va, fmt); + DN_Str8 result = DN_Str8FromFmtVAllocator(allocator, fmt, va); + va_end(va); + return result; +} + +DN_API DN_Str8 DN_Str8FromFmtArena(DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list va; + va_start(va, fmt); + DN_Str8 result = DN_Str8FromFmtVArena(arena, fmt, va); + va_end(va); + return result; +} + +DN_API DN_Str8 DN_Str8FromFmtVPool(DN_Pool *pool, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_Str8 result = DN_Str8FromFmtVAllocator(DN_AllocatorFromPool(pool), fmt, args); + return result; +} + +DN_API DN_Str8 DN_Str8FromFmtPool(DN_Pool *pool, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8 result = DN_Str8FromFmtVPool(pool, fmt, args); + va_end(args); + return result; +} + +DN_API DN_Str8x16 DN_Str8x16FromFmt(DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8x16 result = {}; + DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args); + va_end(args); + return result; +} + +DN_API DN_Str8x16 DN_Str8x16FromFmtVArena(DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_Str8x16 result = {}; + DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args); + return result; +} + +DN_API DN_Str8x32 DN_Str8x32FromFmt(DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8x32 result = {}; + DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args); + va_end(args); + return result; +} + +DN_API DN_Str8x32 DN_Str8x32FromFmtVArena(DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_Str8x32 result = {}; + DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args); + return result; +} + +DN_API DN_Str8x64 DN_Str8x64FromFmt(DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8x64 result = {}; + DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args); + va_end(args); + return result; +} + +DN_API DN_Str8x64 DN_Str8x64FromFmtV(DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_Str8x64 result = {}; + DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args); + return result; +} + +DN_API DN_Str8x128 DN_Str8x128FromFmt(DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8x128 result = {}; + DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args); + va_end(args); + return result; +} + +DN_API DN_Str8x128 DN_Str8x128FromFmtVArena(DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_Str8x128 result = {}; + DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args); + return result; +} + +DN_API DN_Str8x256 DN_Str8x256FromFmt(DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8x256 result = {}; + DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args); + va_end(args); + return result; +} + +DN_API DN_Str8x256 DN_Str8x256FromFmtVArena(DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_Str8x256 result = {}; + DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args); + return result; +} + +DN_API DN_Str8x512 DN_Str8x512FromFmt(DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8x512 result = {}; + DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args); + va_end(args); + return result; +} + +DN_API DN_Str8x512 DN_Str8x512FromFmtVArena(DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_Str8x512 result = {}; + DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args); + return result; +} + +DN_API DN_Str8x1024 DN_Str8x1024FromFmt(DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8x1024 result = {}; + DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args); + va_end(args); + return result; +} + +DN_API DN_Str8x1024 DN_Str8x1024FromFmtVArena(DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_Str8x1024 result = {}; + DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args); + return result; +} + +DN_API void DN_Str8x16AppendFmt(DN_Str8x16 *str, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8x16AppendFmtV(str, fmt, args); + va_end(args); +} + +DN_API void DN_Str8x16AppendFmtV(DN_Str8x16 *str, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_FmtVAppend(str->data, &str->size, sizeof(str->data), fmt, args); +} + +DN_API void DN_Str8x32AppendFmt(DN_Str8x32 *str, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8x32AppendFmtV(str, fmt, args); + va_end(args); +} + +DN_API void DN_Str8x32AppendFmtV(DN_Str8x32 *str, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_FmtVAppend(str->data, &str->size, sizeof(str->data), fmt, args); +} + +DN_API void DN_Str8x64AppendFmt(DN_Str8x64 *str, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8x64AppendFmtV(str, fmt, args); + va_end(args); +} + +DN_API void DN_Str8x64AppendFmtV(DN_Str8x64 *str, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_FmtVAppend(str->data, &str->size, sizeof(str->data), fmt, args); +} + +DN_API void DN_Str8x128AppendFmt(DN_Str8x128 *str, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8x128AppendFmtV(str, fmt, args); + va_end(args); +} + +DN_API void DN_Str8x128AppendFmtV(DN_Str8x128 *str, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_FmtVAppend(str->data, &str->size, sizeof(str->data), fmt, args); +} + +DN_API void DN_Str8x256AppendFmt(DN_Str8x256 *str, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8x256AppendFmtV(str, fmt, args); + va_end(args); +} + +DN_API void DN_Str8x256AppendFmtV(DN_Str8x256 *str, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_FmtVAppend(str->data, &str->size, sizeof(str->data), fmt, args); +} + +DN_API void DN_Str8x512AppendFmt(DN_Str8x512 *str, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8x512AppendFmtV(str, fmt, args); + va_end(args); +} + +DN_API void DN_Str8x512AppendFmtV(DN_Str8x512 *str, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_FmtVAppend(str->data, &str->size, sizeof(str->data), fmt, args); +} + +DN_API void DN_Str8x1024AppendFmt(DN_Str8x1024 *str, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8x1024AppendFmtV(str, fmt, args); + va_end(args); +} + +DN_API void DN_Str8x1024AppendFmtV(DN_Str8x1024 *str, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_FmtVAppend(str->data, &str->size, sizeof(str->data), fmt, args); +} + +DN_API DN_Str8x32 DN_Str8x32FromU64(DN_U64 val, char separator) +{ + DN_Str8x32 result = {}; + DN_Str8x32 temp = DN_Str8x32FromFmt("%" PRIu64, val); + DN_USize temp_index = 0; + + // NOTE: Write the digits the first, up to [0, 2] digits that do not need a thousandth separator + DN_USize range_without_separator = temp.size % 3; + for (; temp_index < range_without_separator; temp_index++) + result.data[result.size++] = temp.data[temp_index]; + + // NOTE: Write the subsequent digits and every 3rd digit, add the seperator + DN_USize digit_counter = 0; + for (; temp_index < temp.size; temp_index++, digit_counter++) { + if (separator && temp_index && (digit_counter % 3 == 0)) + result.data[result.size++] = separator; + result.data[result.size++] = temp.data[temp_index]; + } + return result; +} + + +DN_API bool DN_Str8IsAll(DN_Str8 string, DN_Str8IsAllType is_all) +{ + bool result = string.size; + if (!result) + return result; + + switch (is_all) { + case DN_Str8IsAllType_Digits: { + for (DN_USize index = 0; result && index < string.size; index++) + result = string.data[index] >= '0' && string.data[index] <= '9'; + } break; + + case DN_Str8IsAllType_Hex: { + DN_Str8 trimmed = DN_Str8TrimPrefix(string, DN_Str8Lit("0x"), DN_Str8EqCase_Insensitive); + for (DN_USize index = 0; result && index < trimmed.size; index++) { + char ch = trimmed.data[index]; + result = (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'); + } + } break; + } + + return result; +} + +DN_API char *DN_Str8End(DN_Str8 string) +{ + char *result = string.data + string.size; + return result; +} + +DN_API DN_Str8 DN_Str8Subset(DN_Str8 string, DN_USize offset, DN_USize size) +{ + DN_Str8 result = DN_Str8FromPtr(string.data, 0); + if (string.size == 0) + return result; + + DN_USize capped_offset = DN_Min(offset, string.size); + DN_USize max_size = string.size - capped_offset; + DN_USize capped_size = DN_Min(size, max_size); + result = DN_Str8FromPtr(string.data + capped_offset, capped_size); + return result; +} + +DN_API DN_Str8 DN_Str8Advance(DN_Str8 string, DN_USize amount) +{ + DN_Str8 result = DN_Str8Subset(string, amount, DN_USIZE_MAX); + return result; +} + +DN_API DN_Str8 DN_Str8NextLine(DN_Str8 string) +{ + DN_Str8 result = DN_Str8BSplit(string, DN_Str8Lit("\n")).rhs; + return result; +} + +DN_API DN_Str8BSplitResult DN_Str8BSplitArray(DN_Str8 string, DN_Str8 const *find, DN_USize find_size) +{ + DN_Str8BSplitResult result = {}; + if (string.size == 0 || !find || find_size == 0) + return result; + + result.lhs = string; + for (DN_USize index = 0; !result.rhs.data && index < string.size; index++) { + for (DN_USize find_index = 0; find_index < find_size; find_index++) { + DN_Str8 find_item = find[find_index]; + DN_Str8 string_slice = DN_Str8Subset(string, index, find_item.size); + if (DN_Str8Eq(string_slice, find_item)) { + result.input_index = find_index; + result.lhs.size = index; + result.rhs.data = string_slice.data + find_item.size; + result.rhs.size = string.size - (index + find_item.size); + break; + } + } + } + + return result; +} + +DN_API DN_Str8BSplitResult DN_Str8BSplit(DN_Str8 string, DN_Str8 find) +{ + DN_Str8BSplitResult result = DN_Str8BSplitArray(string, &find, 1); + return result; +} + +DN_API DN_Str8BSplitResult DN_Str8BSplitLastArray(DN_Str8 string, DN_Str8 const *find, DN_USize find_size) +{ + DN_Str8BSplitResult result = {}; + if (string.size == 0 || !find || find_size == 0) + return result; + + result.lhs = string; + for (DN_USize index = string.size - 1; !result.rhs.data && index < string.size; index--) { + for (DN_USize find_index = 0; find_index < find_size; find_index++) { + DN_Str8 find_item = find[find_index]; + DN_Str8 string_slice = DN_Str8Subset(string, index, find_item.size); + if (DN_Str8Eq(string_slice, find_item)) { + result.lhs.size = index; + result.rhs.data = string_slice.data + find_item.size; + result.rhs.size = string.size - (index + find_item.size); + break; + } + } + } + + return result; +} + +DN_API DN_Str8BSplitResult DN_Str8BSplitLast(DN_Str8 string, DN_Str8 find) +{ + DN_Str8BSplitResult result = DN_Str8BSplitLastArray(string, &find, 1); + return result; +} + +DN_API DN_USize DN_Str8Split(DN_Str8 string, DN_Str8 delimiter, DN_Str8 *splits, DN_USize splits_count, DN_Str8SplitFlags flags) +{ + DN_USize result = 0; // The number of splits in the actual string. + if (string.size == 0 || delimiter.size == 0 || delimiter.size <= 0) + return result; + + DN_Str8 it = string; + bool allow_empty_strings = DN_BitIsNotSet(flags, DN_Str8SplitFlags_ExcludeEmptyStrings); + bool handle_quotes = DN_BitIsSet(flags, DN_Str8SplitFlags_HandleQuotedStrings); + do { + DN_Str8 item = {}; + if (handle_quotes && DN_Str8StartsWith(it, DN_Str8Lit("\""))) { + DN_Str8FindResult find = DN_Str8FindStr8(DN_Str8Advance(it, 1), DN_Str8Lit("\""), DN_Str8EqCase_Sensitive); + DN_Assert(find.found); + item = find.start_to_before_match; + it = DN_Str8BSplit(find.after_match_to_end_of_buffer, delimiter).rhs; + } else { + DN_Str8BSplitResult sub_split = DN_Str8BSplit(it, delimiter); + item = sub_split.lhs; + it = sub_split.rhs; + } + + if (item.size || allow_empty_strings) { + if (splits && result < splits_count) + splits[result] = item; + result++; + } + } while (it.size); + + return result; +} + +DN_API DN_Str8SplitResult DN_Str8SplitArena(DN_Str8 string, DN_Str8 delimiter, DN_Str8SplitFlags mode, DN_Arena *arena) +{ + DN_Str8SplitResult result = {}; + DN_USize count = DN_Str8Split(string, delimiter, /*splits*/ nullptr, /*count*/ 0, mode); + result.data = DN_ArenaNewArray(arena, DN_Str8, count, DN_ZMem_No); + if (result.data) { + result.count = DN_Str8Split(string, delimiter, result.data, count, mode); + DN_Assert(count == result.count); + } + return result; +} + +DN_API DN_Str8FindResult DN_Str8FindStr8Array(DN_Str8 string, DN_Str8 const *find, DN_USize find_size, DN_Str8EqCase eq_case) +{ + DN_Str8FindResult result = {}; + for (DN_USize index = 0; !result.found && index < string.size; index++) { + for (DN_USize find_index = 0; find_index < find_size; find_index++) { + DN_Str8 find_item = find[find_index]; + DN_Str8 string_slice = DN_Str8Subset(string, index, find_item.size); + if (DN_Str8Eq(string_slice, find_item, eq_case)) { + result.found = true; + result.index = index; + result.start_to_before_match = DN_Str8FromPtr(string.data, index); + result.match = DN_Str8FromPtr(string.data + index, find_item.size); + result.match_to_end_of_buffer = DN_Str8FromPtr(result.match.data, string.size - index); + result.after_match_to_end_of_buffer = DN_Str8Advance(result.match_to_end_of_buffer, find_item.size); + break; + } + } + } + return result; +} + +DN_API DN_Str8FindResult DN_Str8FindStr8(DN_Str8 string, DN_Str8 find, DN_Str8EqCase eq_case) +{ + DN_Str8FindResult result = DN_Str8FindStr8Array(string, &find, 1, eq_case); + return result; +} + +DN_API DN_Str8FindResult DN_Str8Find(DN_Str8 string, DN_Str8FindFlag flags) +{ + DN_Str8FindResult result = {}; + for (DN_USize index = 0; !result.found && index < string.size; index++) { + result.found |= ((flags & DN_Str8FindFlag_Digit) && DN_CharIsDigit(string.data[index])); + result.found |= ((flags & DN_Str8FindFlag_Alphabet) && DN_CharIsAlphabet(string.data[index])); + result.found |= ((flags & DN_Str8FindFlag_Whitespace) && DN_CharIsWhitespace(string.data[index])); + result.found |= ((flags & DN_Str8FindFlag_Plus) && string.data[index] == '+'); + result.found |= ((flags & DN_Str8FindFlag_Minus) && string.data[index] == '-'); + if (result.found) { + result.index = index; + result.match = DN_Str8FromPtr(string.data + index, 1); + result.match_to_end_of_buffer = DN_Str8FromPtr(result.match.data, string.size - index); + result.after_match_to_end_of_buffer = DN_Str8Advance(result.match_to_end_of_buffer, 1); + } + } + return result; +} + +DN_API DN_Str8 DN_Str8Segment(DN_Arena *arena, DN_Str8 src, DN_USize segment_size, char segment_char) +{ + if (!segment_size || src.size == 0) { + DN_Str8 result = DN_Str8FromStr8Arena(src, arena); + return result; + } + + DN_USize segments = src.size / segment_size; + if (src.size % segment_size == 0) + segments--; + + DN_USize segment_counter = 0; + DN_Str8 result = DN_Str8AllocArena(src.size + segments, DN_ZMem_Yes, arena); + DN_USize write_index = 0; + for (DN_ForIndexU(src_index, src.size)) { + result.data[write_index++] = src.data[src_index]; + if ((src_index + 1) % segment_size == 0 && segment_counter < segments) { + result.data[write_index++] = segment_char; + segment_counter++; + } + DN_AssertF(write_index <= result.size, "result.size=%zu, write_index=%zu", result.size, write_index); + } + + DN_AssertF(write_index == result.size, "result.size=%zu, write_index=%zu", result.size, write_index); + return result; +} + +DN_API DN_Str8 DN_Str8ReverseSegment(DN_Arena *arena, DN_Str8 src, DN_USize segment_size, char segment_char) +{ + if (!segment_size || src.size == 0) { + DN_Str8 result = DN_Str8FromStr8Arena(src, arena); + return result; + } + + DN_USize segments = src.size / segment_size; + if (src.size % segment_size == 0) + segments--; + + DN_USize write_counter = 0; + DN_USize segment_counter = 0; + DN_Str8 result = DN_Str8AllocArena(src.size + segments, DN_ZMem_Yes, arena); + DN_USize write_index = result.size - 1; + + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(6293) // NOTE: Ill-defined loop + for (DN_USize src_index = src.size - 1; src_index < src.size; src_index--) { + DN_MSVC_WARNING_POP + result.data[write_index--] = src.data[src_index]; + if (++write_counter % segment_size == 0 && segment_counter < segments) { + result.data[write_index--] = segment_char; + segment_counter++; + } + } + + DN_Assert(write_index == SIZE_MAX); + return result; +} + +DN_API bool DN_Str8Eq(DN_Str8 lhs, DN_Str8 rhs, DN_Str8EqCase eq_case) +{ + if (lhs.size != rhs.size) + return false; + bool result = true; + switch (eq_case) { + case DN_Str8EqCase_Sensitive: { + result = (DN_Memcmp(lhs.data, rhs.data, lhs.size) == 0); + } break; + + case DN_Str8EqCase_Insensitive: { + for (DN_USize index = 0; index < lhs.size && result; index++) + result = (DN_CharToLower(lhs.data[index]) == DN_CharToLower(rhs.data[index])); + } break; + } + return result; +} + +DN_API bool DN_Str8EqInsensitive(DN_Str8 lhs, DN_Str8 rhs) +{ + bool result = DN_Str8Eq(lhs, rhs, DN_Str8EqCase_Insensitive); + return result; +} + +DN_API bool DN_Str8StartsWith(DN_Str8 string, DN_Str8 prefix, DN_Str8EqCase eq_case) +{ + DN_Str8 substring = {string.data, DN_Min(prefix.size, string.size)}; + bool result = DN_Str8Eq(substring, prefix, eq_case); + return result; +} + +DN_API bool DN_Str8StartsWithInsensitive(DN_Str8 string, DN_Str8 prefix) +{ + bool result = DN_Str8StartsWith(string, prefix, DN_Str8EqCase_Insensitive); + return result; +} + +DN_API bool DN_Str8EndsWith(DN_Str8 string, DN_Str8 suffix, DN_Str8EqCase eq_case) +{ + DN_Str8 substring = {string.data + string.size - suffix.size, DN_Min(string.size, suffix.size)}; + bool result = DN_Str8Eq(substring, suffix, eq_case); + return result; +} + +DN_API bool DN_Str8EndsWithInsensitive(DN_Str8 string, DN_Str8 suffix) +{ + bool result = DN_Str8EndsWith(string, suffix, DN_Str8EqCase_Insensitive); + return result; +} + +DN_API bool DN_Str8HasChar(DN_Str8 string, char ch) +{ + bool result = false; + for (DN_USize index = 0; !result && index < string.size; index++) + result = string.data[index] == ch; + return result; +} + +DN_API DN_Str8 DN_Str8TrimPrefix(DN_Str8 string, DN_Str8 prefix, DN_Str8EqCase eq_case) +{ + DN_Str8 result = string; + if (DN_Str8StartsWith(string, prefix, eq_case)) { + result.data += prefix.size; + result.size -= prefix.size; + } + return result; +} + +DN_API DN_Str8 DN_Str8TrimHexPrefix(DN_Str8 string) +{ + DN_Str8 result = DN_Str8TrimPrefix(string, DN_Str8Lit("0x"), DN_Str8EqCase_Insensitive); + return result; +} + +DN_API DN_Str8 DN_Str8TrimSuffix(DN_Str8 string, DN_Str8 suffix, DN_Str8EqCase eq_case) +{ + DN_Str8 result = string; + if (DN_Str8EndsWith(string, suffix, eq_case)) + result.size -= suffix.size; + return result; +} + +DN_API DN_Str8 DN_Str8TrimAround(DN_Str8 string, DN_Str8 trim_string) +{ + DN_Str8 result = DN_Str8TrimPrefix(string, trim_string); + result = DN_Str8TrimSuffix(result, trim_string); + return result; +} + +DN_API DN_Str8 DN_Str8TrimHeadWhitespace(DN_Str8 string) +{ + DN_Str8 result = string; + if (string.size == 0) + return result; + + char const *start = string.data; + char const *end = string.data + string.size; + while (start < end && DN_CharIsWhitespace(start[0])) + start++; + + result = DN_Str8FromPtr(start, end - start); + return result; +} + +DN_API DN_Str8 DN_Str8TrimTailWhitespace(DN_Str8 string) +{ + DN_Str8 result = string; + if (string.size == 0) + return result; + + char const *start = string.data; + char const *end = string.data + string.size; + while (end > start && DN_CharIsWhitespace(end[-1])) + end--; + + result = DN_Str8FromPtr(start, end - start); + return result; +} + +DN_API DN_Str8 DN_Str8TrimWhitespaceAround(DN_Str8 string) +{ + DN_Str8 result = DN_Str8TrimHeadWhitespace(string); + result = DN_Str8TrimTailWhitespace(result); + return result; +} + +DN_API DN_Str8 DN_Str8TrimByteOrderMark(DN_Str8 string) +{ + DN_Str8 result = string; + if (result.size == 0) + return result; + + // TODO(dn): This is little endian + DN_Str8 UTF8_BOM = DN_Str8Lit("\xEF\xBB\xBF"); + DN_Str8 UTF16_BOM_BE = DN_Str8Lit("\xEF\xFF"); + DN_Str8 UTF16_BOM_LE = DN_Str8Lit("\xFF\xEF"); + DN_Str8 UTF32_BOM_BE = DN_Str8Lit("\x00\x00\xFE\xFF"); + DN_Str8 UTF32_BOM_LE = DN_Str8Lit("\xFF\xFE\x00\x00"); + + result = DN_Str8TrimPrefix(result, UTF8_BOM, DN_Str8EqCase_Sensitive); + result = DN_Str8TrimPrefix(result, UTF16_BOM_BE, DN_Str8EqCase_Sensitive); + result = DN_Str8TrimPrefix(result, UTF16_BOM_LE, DN_Str8EqCase_Sensitive); + result = DN_Str8TrimPrefix(result, UTF32_BOM_BE, DN_Str8EqCase_Sensitive); + result = DN_Str8TrimPrefix(result, UTF32_BOM_LE, DN_Str8EqCase_Sensitive); + return result; +} + +DN_API DN_Str8 DN_Str8FileNameFromPath(DN_Str8 path) +{ + DN_Str8 separators[] = {DN_Str8Lit("/"), DN_Str8Lit("\\")}; + DN_Str8BSplitResult split = DN_Str8BSplitLastArray(path, separators, DN_ArrayCountU(separators)); + DN_Str8 result = split.rhs.size ? split.rhs : split.lhs; + return result; +} + +DN_API DN_Str8 DN_Str8FileNameNoExtension(DN_Str8 path) +{ + DN_Str8 file_name = DN_Str8FileNameFromPath(path); + DN_Str8 result = DN_Str8FilePathNoExtension(file_name); + return result; +} + +DN_API DN_Str8 DN_Str8FilePathNoExtension(DN_Str8 path) +{ + DN_Str8BSplitResult split = DN_Str8BSplitLast(path, DN_Str8Lit(".")); + DN_Str8 result = split.lhs; + return result; +} + +DN_API DN_Str8 DN_Str8FileExtension(DN_Str8 path) +{ + DN_Str8BSplitResult split = DN_Str8BSplitLast(path, DN_Str8Lit(".")); + DN_Str8 result = split.rhs; + return result; +} + +DN_API DN_Str8 DN_Str8FileDirectoryFromPath(DN_Str8 path) +{ + DN_Str8 separators[] = {DN_Str8Lit("/"), DN_Str8Lit("\\")}; + DN_Str8BSplitResult split = DN_Str8BSplitLastArray(path, separators, DN_ArrayCountU(separators)); + DN_Str8 result = split.lhs; + return result; +} + +DN_API DN_Str8 DN_Str8AppendF(DN_Arena *arena, DN_Str8 string, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8 result = DN_Str8AppendFV(arena, string, fmt, args); + va_end(args); + return result; +} + +DN_API DN_Str8 DN_Str8AppendFV(DN_Arena *arena, DN_Str8 string, char const *fmt, va_list args) +{ + // TODO: Calculate size and write into one buffer instead of 2 appends + DN_Str8 append = DN_Str8FromFmtVArena(arena, fmt, args); + DN_Str8 result = DN_Str8AllocArena(string.size + append.size, DN_ZMem_No, arena); + DN_Memcpy(result.data, string.data, string.size); + DN_Memcpy(result.data + string.size, append.data, append.size); + return result; +} + +DN_API DN_Str8 DN_Str8FillF(DN_Arena *arena, DN_USize count, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8 result = DN_Str8FillFV(arena, count, fmt, args); + va_end(args); + return result; +} + +DN_API DN_Str8 DN_Str8FillFV(DN_Arena *arena, DN_USize count, char const *fmt, va_list args) +{ + DN_Str8 fill = DN_Str8FromFmtVArena(arena, fmt, args); + DN_Str8 result = DN_Str8AllocArena(count * fill.size, DN_ZMem_No, arena); + for (DN_USize index = 0; index < count; index++) { + void *dest = result.data + (index * fill.size); + DN_Memcpy(dest, fill.data, fill.size); + } + return result; +} + +DN_API void DN_Str8Remove(DN_Str8 *string, DN_USize offset, DN_USize size) +{ + if (!string || string->size) + return; + + char *end = string->data + string->size; + char *dest = DN_Min(string->data + offset, end); + char *src = DN_Min(string->data + offset + size, end); + DN_USize bytes_to_move = end - src; + DN_Memmove(dest, src, bytes_to_move); + string->size -= bytes_to_move; +} + +DN_API DN_Str8TruncResult DN_Str8TruncMiddlePtr(DN_Str8 str8, DN_USize side_size, DN_Str8 truncator, char *dest, DN_USize dest_max) +{ + DN_Assert(side_size <= DN_USIZE_MAX / 2); + if (dest) { + // NOTE: If the user passes the dest buffer, we expect it to be sized correctly. + if ((side_size * 2) >= str8.size) { + DN_Assert(dest_max >= str8.size + 1 /*null*/); + } else { + DN_Assert(dest_max >= (2 * side_size + truncator.size) + 1 /*null*/); + } + } + + DN_Str8TruncResult result = {}; + if (str8.size <= (side_size * 2)) { + result.size_req = str8.size; + if (dest) { + DN_Memcpy(dest, str8.data, str8.size); + dest[str8.size] = 0; + result.str8 = DN_Str8FromPtr(dest, result.size_req); + } + return result; + } + + DN_Str8 head = DN_Str8Subset(str8, 0, side_size); + DN_Str8 tail = DN_Str8Subset(str8, str8.size - side_size, side_size); + DN_USize dest_size = 0; + if (dest) { + DN_FmtAppendResult append_result = DN_FmtAppend(dest, &dest_size, dest_max, "%.*s%.*s%.*s", DN_Str8PrintFmt(head), DN_Str8PrintFmt(truncator), DN_Str8PrintFmt(tail)); + result.str8 = append_result.str8; + result.truncated = true; + result.size_req = result.str8.size; + } else { + result.size_req = DN_FmtSize("%.*s%.*s%.*s", DN_Str8PrintFmt(head), DN_Str8PrintFmt(truncator), DN_Str8PrintFmt(tail)); + result.truncated = true; + } + + return result; +} + +DN_API DN_Str8TruncResult DN_Str8TruncMiddle(DN_Str8 str8, DN_USize side_size, DN_Str8 truncator, DN_Arena *arena) +{ + DN_Str8TruncResult trunc = DN_Str8TruncMiddlePtr(str8, side_size, truncator, nullptr, 0); + DN_Str8 dest = DN_Str8AllocArena(trunc.size_req, DN_ZMem_No, arena); + DN_Str8TruncResult result = DN_Str8TruncMiddlePtr(str8, side_size, truncator, dest.data, dest.size + 1); + return result; +} + +DN_API DN_Str8 DN_Str8Lower(DN_Str8 string, DN_Arena *arena) +{ + DN_Str8 result = DN_Str8FromStr8Arena(string, arena); + for (DN_ForIndexU(index, result.size)) + result.data[index] = DN_CharToLower(result.data[index]); + return result; +} + +DN_API DN_Str8 DN_Str8Upper(DN_Str8 string, DN_Arena *arena) +{ + DN_Str8 result = DN_Str8FromStr8Arena(string, arena); + for (DN_ForIndexU(index, result.size)) + result.data[index] = DN_CharToUpper(result.data[index]); + return result; +} + +DN_API DN_Str8 DN_Str8Replace(DN_Str8 string, + DN_Str8 find, + DN_Str8 replace, + DN_USize start_index, + DN_Arena *arena, + DN_Str8EqCase eq_case) +{ + DN_Str8 result = {}; + if (string.size == 0 || find.size == 0 || find.size > string.size || find.size == 0 || string.size == 0) { + result = DN_Str8FromStr8Arena(string, arena); + return result; + } + + DN_TCScratch scratch = DN_TCScratchBeginArena(&arena, 1); + DN_Str8Builder string_builder = DN_Str8BuilderFromArena(&scratch.arena); + DN_USize max = string.size - find.size; + DN_USize head = start_index; + + for (DN_USize tail = head; tail <= max; tail++) { + DN_Str8 check = DN_Str8Subset(string, tail, find.size); + if (!DN_Str8Eq(check, find, eq_case)) + continue; + + if (start_index > 0 && string_builder.string_size == 0) { + // User provided a hint in the string to start searching from, we + // need to add the string up to the hint. We only do this if there's + // a replacement action, otherwise we have a special case for no + // replacements, where the entire string gets copied. + DN_Str8 slice = DN_Str8FromPtr(string.data, head); + DN_Str8BuilderAppendRef(&string_builder, slice); + } + + DN_Str8 range = DN_Str8Subset(string, head, (tail - head)); + DN_Str8BuilderAppendRef(&string_builder, range); + DN_Str8BuilderAppendRef(&string_builder, replace); + head = tail + find.size; + tail += find.size - 1; // NOTE: -1 since the for loop will post increment us past the end of the find string + } + + if (string_builder.string_size == 0) { + // NOTE: No replacement possible, so we just do a full-copy + result = DN_Str8FromStr8Arena(string, arena); + } else { + DN_Str8 remainder = DN_Str8FromPtr(string.data + head, string.size - head); + DN_Str8BuilderAppendRef(&string_builder, remainder); + result = DN_Str8FromStr8BuilderArena(&string_builder, arena); + } + DN_TCScratchEnd(&scratch); + return result; +} + +DN_API DN_Str8 DN_Str8ReplaceSensitive(DN_Str8 string, DN_Str8 find, DN_Str8 replace, DN_USize start_index, DN_Arena *arena) +{ + DN_Str8 result = DN_Str8Replace(string, find, replace, start_index, arena, DN_Str8EqCase_Sensitive); + return result; +} + +DN_API DN_Str8 DN_Str8ReplaceInsensitive(DN_Str8 string, DN_Str8 find, DN_Str8 replace, DN_USize start_index, DN_Arena *arena) +{ + DN_Str8 result = DN_Str8Replace(string, find, replace, start_index, arena, DN_Str8EqCase_Insensitive); + return result; +} + +DN_API DN_Str8 DN_Str8PadNewLinesAllocator(DN_Str8 string, DN_Str8 pad_string, DN_Allocator allocator) +{ + DN_TCScratch scratch = DN_TCScratchBeginAllocator(&allocator, 1); + DN_Str8Builder builder = DN_Str8BuilderFromArena(&scratch.arena); + DN_Str8 it = string; + while (it.size) { + DN_Str8BSplitResult split = DN_Str8BSplit(it, DN_Str8Lit("\n")); + DN_Str8BuilderAppendRef(&builder, DN_Str8FromPtr(split.lhs.data, split.lhs.size + 1)); + it = split.rhs; + } + + DN_Str8 result = DN_Str8FromStr8BuilderDelimitAllocator(&builder, pad_string, allocator); + DN_TCScratchEnd(&scratch); + return result; +} + +DN_API DN_Str8 DN_Str8PadNewLinesArena(DN_Str8 string, DN_Str8 pad_string, DN_Arena *arena) +{ + DN_Str8 result = DN_Str8PadNewLinesAllocator(string, pad_string, DN_AllocatorFromArena(arena)); + return result; +} + +DN_API DN_USize DN_USizeCodepointCountFromUTF8(DN_Str8 str, DN_CodepointCountFlags flags) +{ + DN_USize result = 0; + + if (DN_BitIsNotSet(flags, DN_CodepointCountFlags_SkipANSICode)) { + DN_UTF8DecodeIterator it = {}; + while (DN_UTF8DecodeIterate(&it, str)) + ; + result = it.codepoint_index; + } else { + // NOTE: ANSI SGR (Select Graphic Rendition) sequence handling + // Format: ESC [ parameter_bytes intermediate_bytes final_byte + // Common examples: \x1b[31m (red), \x1b[1;31m (bold red), \x1b[0m (reset) + // Parameter bytes: 0x30-0x3F (digits and :;<=>?) + // Intermediate bytes: 0x20-0x2F (space and !"#$%&'()*+,-./) + // Final byte: 0x40-0x7E (@A-Z[\]^_`a-z{|}~) + char const *p = str.data; + char const *end = DN_Str8End(str); + while (p < end) { + if (*p == '\x1b' && p + 1 < end && *(p + 1) == '[') { // Detect CSI sequence: ESC [ + p += 2; + while (p < end && *p >= 0x30 && *p <= 0x3F) // Skip parameter bytes (0x30-0x3F) + p++; + while (p < end && *p >= 0x20 && *p <= 0x2F) // Skip intermediate bytes (0x20-0x2F) + p++; + if (p < end && *p >= 0x40 && *p <= 0x7E) // Skip final byte (0x40-0x7E) + p++; + continue; + } + + DN_UTF8DecodeResult decode = DN_UTF8Decode(DN_Str8FromPtr(p, end - p)); + if (!decode.success) + break; + p = decode.remaining.data; + result++; + } + } + + return result; +} + +DN_API DN_Str8 DN_Str8LineBreakAllocator(DN_Str8 src, DN_USize desired_width, DN_Str8 delimiter, DN_Str8LineBreakMode mode, DN_Allocator allocator) +{ + DN_TCScratch scratch = DN_TCScratchBeginAllocator(&allocator, 1); + DN_Str8Builder builder = DN_Str8BuilderFromArena(&scratch.arena); + + if (mode == DN_Str8LineBreakMode_AtWord) { + char* start = src.data; + char* end = src.data; + DN_Str8 it = src; + while (it.size) { + DN_Str8 splitters[] = {DN_Str8Lit(" "), DN_Str8Lit("\n")}; + DN_Str8BSplitResult split = DN_Str8BSplitArray(it, splitters, DN_ArrayCountU(splitters)); + DN_USize curr_line_length = end - start; + + // Handle explicit newlines in input + if (split.input_index == 1 /*the newline*/) { + if (curr_line_length == 0 && split.lhs.size) + start = split.lhs.data; + if (split.lhs.size) + end = DN_Str8End(split.lhs); + DN_Str8BuilderAppendRef(&builder, DN_Str8FromPtr(start, end - start)); + start = split.rhs.data; + end = split.rhs.data; + it = split.rhs; + continue; + } + + // Skip empty segments (multiple spaces, leading/trailing spaces) + if (split.lhs.size == 0) { + it = split.rhs; + continue; + } + + // First word on this line + if (curr_line_length == 0) { + start = split.lhs.data; + end = DN_Str8End(split.lhs); + it = split.rhs; + continue; + } + + // Check if adding this word (plus separator space) would overflow + DN_USize combined_length = curr_line_length + 1 + split.lhs.size; + if (combined_length > desired_width) { + // Commit current line, start new line with current word + DN_Str8BuilderAppendRef(&builder, DN_Str8FromPtr(start, end - start)); + start = split.lhs.data; + end = DN_Str8End(split.lhs); + it = split.rhs; + } else { + // Add word to current line + end = DN_Str8End(split.lhs); + it = split.rhs; + } + } + + // Append final line + if (end > start) + DN_Str8BuilderAppendRef(&builder, DN_Str8FromPtr(start, end - start)); + } else { + DN_Str8 it = src; + while (it.size) { + DN_Str8 chunk = DN_Str8Subset(it, 0, desired_width); + DN_Str8BuilderAppendRef(&builder, chunk); + it = DN_Str8Advance(it, desired_width); + } + } + + DN_Str8 result = DN_Str8FromStr8BuilderDelimitAllocator(&builder, delimiter, allocator); + DN_TCScratchEnd(&scratch); + return result; +} + +DN_API DN_Str8 DN_Str8LineBreakArena(DN_Str8 src, DN_USize desired_width, DN_Str8 delimiter, DN_Str8LineBreakMode mode, DN_Arena *arena) +{ + DN_Str8 result = DN_Str8LineBreakAllocator(src, desired_width, delimiter, mode, DN_AllocatorFromArena(arena)); + return result; +} + +DN_API DN_Str8 DN_Str8Table(DN_Str8 const *rows, DN_USize num_rows, DN_USize num_cols, DN_Str8TableFlags flags, DN_Arena *arena) +{ + DN_TCScratch scratch = DN_TCScratchBeginArena(&arena, 1); + DN_U16 col_widths[128] = {}; + for (DN_USize i = 0; i < num_cols; i++) { + for (DN_USize j = 0; j < num_rows; j++) { + DN_USize index = j * num_cols + i; + col_widths[i] = DN_Max(col_widths[i], (DN_U16)DN_USizeCodepointCountFromUTF8(rows[index], DN_CodepointCountFlags_SkipANSICode)); + } + } + + DN_Str8Builder builder = DN_Str8BuilderFromArena(&scratch.arena); + DN_Str8BuilderAppendF(&builder, "+"); + for (DN_USize i = 0; i < num_cols; i++) { + for (DN_USize j = 0; j < col_widths[i] + 2; j++) + DN_Str8BuilderAppendF(&builder, "-"); + DN_Str8BuilderAppendF(&builder, "+"); + } + DN_Str8BuilderAppendF(&builder, "\n"); + + for (DN_USize i = 0; i < num_rows; i++) { + DN_Str8BuilderAppendF(&builder, "|"); + for (DN_USize j = 0; j < num_cols; j++) { + DN_USize index = (i * num_cols) + j; + DN_Str8 item = rows[index]; + DN_Str8BuilderAppendF(&builder, " %.*s", DN_Str8PrintFmt(item)); + DN_USize item_width = DN_USizeCodepointCountFromUTF8(item, DN_CodepointCountFlags_SkipANSICode); + for (DN_USize k = 0; k < col_widths[j] - item_width; k++) + DN_Str8BuilderAppendF(&builder, " "); + DN_Str8BuilderAppendF(&builder, " |"); + } + DN_Str8BuilderAppendF(&builder, "\n"); + + bool print_row_line = i == 0 && DN_BitIsSet(flags, DN_Str8TableFlags_HasHeader); + if (!print_row_line) + print_row_line = DN_BitIsSet(flags, DN_Str8TableFlags_RowLines); + + if (print_row_line) { + DN_Str8BuilderAppendF(&builder, "+"); + for (DN_USize sub_i = 0; sub_i < num_cols; sub_i++) { + for (DN_USize sub_j = 0; sub_j < col_widths[sub_i] + 2; sub_j++) + DN_Str8BuilderAppendF(&builder, "-"); + DN_Str8BuilderAppendF(&builder, "+"); + } + DN_Str8BuilderAppendF(&builder, "\n"); + } + } + + DN_Str8BuilderAppendF(&builder, "+"); + for (DN_USize i = 0; i < num_cols; i++) { + for (DN_USize j = 0; j < col_widths[i] + 2; j++) + DN_Str8BuilderAppendF(&builder, "-"); + DN_Str8BuilderAppendF(&builder, "+"); + } + + DN_Str8 result = DN_Str8FromStr8BuilderArena(&builder, arena); + DN_TCScratchEnd(&scratch); + return result; +} + +#if DN_STR8_AVX512F +DN_API DN_Str8FindResult DN_Str8FindStr8AVX512F(DN_Str8 string, DN_Str8 find) +{ + // NOTE: Algorithm as described in http://0x80.pl/articles/simd-strfind.html + DN_Str8FindResult result = {}; + if (string.size == 0 || find.size == 0 || find.size > string.size) + return result; + + __m512i const find_first_ch = _mm512_set1_epi8(find.data[0]); + __m512i const find_last_ch = _mm512_set1_epi8(find.data[find.size - 1]); + + DN_USize const search_size = string.size - find.size; + DN_USize simd_iterations = search_size / sizeof(__m512i); + char const *ptr = string.data; + + while (simd_iterations--) { + __m512i find_first_ch_block = _mm512_loadu_si512(ptr); + __m512i find_last_ch_block = _mm512_loadu_si512(ptr + find.size - 1); + + // NOTE: AVX512F does not have a cmpeq so we use XOR to place a 0 bit + // where matches are found. + __m512i first_ch_matches = _mm512_xor_si512(find_first_ch_block, find_first_ch); + + // NOTE: We can combine the 2nd XOR and merge the 2 XOR results into one + // operation using the ternarylogic intrinsic. + // + // A = first_ch_matches (find_first_ch_block ^ find_first_ch) + // B = find_last_ch_block + // C = find_last_ch + // + // ternarylogic op => A | (B ^ C) => 0b1111'0110 => 0xf6 + // + // / A / B / C / B ^ C / A | (B ^ C) / + // | 0 | 0 | 0 | 0 | 0 | + // | 0 | 0 | 1 | 1 | 1 | + // | 0 | 1 | 0 | 1 | 1 | + // | 0 | 1 | 1 | 0 | 0 | + // | 1 | 0 | 0 | 0 | 1 | + // | 1 | 0 | 1 | 1 | 1 | + // | 1 | 1 | 0 | 1 | 1 | + // | 1 | 1 | 1 | 0 | 1 | + + __m512i ch_matches = _mm512_ternarylogic_epi32(first_ch_matches, find_last_ch_block, find_last_ch, 0xf6); + + // NOTE: Matches were XOR-ed and are hence indicated as zero so we mask + // out which 32 bit elements in the vector had zero bytes. This uses a + // bit twiddling trick + // https://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord + __mmask16 zero_byte_mask = {}; + { + const __m512i v01 = _mm512_set1_epi32(0x01010101u); + const __m512i v80 = _mm512_set1_epi32(0x80808080u); + const __m512i v1 = _mm512_sub_epi32(ch_matches, v01); + const __m512i tmp1 = _mm512_ternarylogic_epi32(v1, ch_matches, v80, 0x20); + zero_byte_mask = _mm512_test_epi32_mask(tmp1, tmp1); + } + + while (zero_byte_mask) { + uint64_t const lsb_zero_pos = _tzcnt_u64(zero_byte_mask); + char const *base_ptr = ptr + (4 * lsb_zero_pos); + + if (DN_Memcmp(base_ptr + 0, find.data, find.size) == 0) { + result.found = true; + result.index = base_ptr - string.data; + } else if (DN_Memcmp(base_ptr + 1, find.data, find.size) == 0) { + result.found = true; + result.index = base_ptr - string.data + 1; + } else if (DN_Memcmp(base_ptr + 2, find.data, find.size) == 0) { + result.found = true; + result.index = base_ptr - string.data + 2; + } else if (DN_Memcmp(base_ptr + 3, find.data, find.size) == 0) { + result.found = true; + result.index = base_ptr - string.data + 3; + } + + if (result.found) { + result.start_to_before_match = DN_Str8FromPtr(string.data, result.index); + result.match = DN_Str8FromPtr(string.data + result.index, find.size); + result.match_to_end_of_buffer = DN_Str8FromPtr(result.match.data, string.size - result.index); + result.after_match_to_end_of_buffer = DN_Str8Advance(result.match_to_end_of_buffer, find.size); + return result; + } + + zero_byte_mask = DN_BitClearNextLSB(zero_byte_mask); + } + + ptr += sizeof(__m512i); + } + + for (DN_USize index = ptr - string.data; index < string.size; index++) { + DN_Str8 string_slice = DN_Str8Subset(string, index, find.size); + if (DN_Str8Eq(string_slice, find)) { + result.found = true; + result.index = index; + result.start_to_before_match = DN_Str8FromPtr(string.data, index); + result.match = DN_Str8FromPtr(string.data + index, find.size); + result.match_to_end_of_buffer = DN_Str8FromPtr(result.match.data, string.size - index); + result.after_match_to_end_of_buffer = DN_Str8Advance(result.match_to_end_of_buffer, find.size); + return result; + } + } + + return result; +} + +DN_API DN_Str8FindResult DN_Str8FindLastStr8AVX512F(DN_Str8 string, DN_Str8 find) +{ + // NOTE: Algorithm as described in http://0x80.pl/articles/simd-strfind.html + DN_Str8FindResult result = {}; + if (string.size == 0 || find.size == 0 || find.size > string.size) + return result; + + __m512i const find_first_ch = _mm512_set1_epi8(find.data[0]); + __m512i const find_last_ch = _mm512_set1_epi8(find.data[find.size - 1]); + + DN_USize const search_size = string.size - find.size; + DN_USize simd_iterations = search_size / sizeof(__m512i); + char const *ptr = string.data + search_size + 1; + + while (simd_iterations--) { + ptr -= sizeof(__m512i); + __m512i find_first_ch_block = _mm512_loadu_si512(ptr); + __m512i find_last_ch_block = _mm512_loadu_si512(ptr + find.size - 1); + + // NOTE: AVX512F does not have a cmpeq so we use XOR to place a 0 bit + // where matches are found. + __m512i first_ch_matches = _mm512_xor_si512(find_first_ch_block, find_first_ch); + + // NOTE: We can combine the 2nd XOR and merge the 2 XOR results into one + // operation using the ternarylogic intrinsic. + // + // A = first_ch_matches (find_first_ch_block ^ find_first_ch) + // B = find_last_ch_block + // C = find_last_ch + // + // ternarylogic op => A | (B ^ C) => 0b1111'0110 => 0xf6 + // + // / A / B / C / B ^ C / A | (B ^ C) / + // | 0 | 0 | 0 | 0 | 0 | + // | 0 | 0 | 1 | 1 | 1 | + // | 0 | 1 | 0 | 1 | 1 | + // | 0 | 1 | 1 | 0 | 0 | + // | 1 | 0 | 0 | 0 | 1 | + // | 1 | 0 | 1 | 1 | 1 | + // | 1 | 1 | 0 | 1 | 1 | + // | 1 | 1 | 1 | 0 | 1 | + + __m512i ch_matches = _mm512_ternarylogic_epi32(first_ch_matches, find_last_ch_block, find_last_ch, 0xf6); + + // NOTE: Matches were XOR-ed and are hence indicated as zero so we mask + // out which 32 bit elements in the vector had zero bytes. This uses a + // bit twiddling trick + // https://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord + __mmask16 zero_byte_mask = {}; + { + const __m512i v01 = _mm512_set1_epi32(0x01010101u); + const __m512i v80 = _mm512_set1_epi32(0x80808080u); + const __m512i v1 = _mm512_sub_epi32(ch_matches, v01); + const __m512i tmp1 = _mm512_ternarylogic_epi32(v1, ch_matches, v80, 0x20); + zero_byte_mask = _mm512_test_epi32_mask(tmp1, tmp1); + } + + while (zero_byte_mask) { + uint64_t const lsb_zero_pos = _tzcnt_u64(zero_byte_mask); + char const *base_ptr = ptr + (4 * lsb_zero_pos); + + if (DN_Memcmp(base_ptr + 0, find.data, find.size) == 0) { + result.found = true; + result.index = base_ptr - string.data; + } else if (DN_Memcmp(base_ptr + 1, find.data, find.size) == 0) { + result.found = true; + result.index = base_ptr - string.data + 1; + } else if (DN_Memcmp(base_ptr + 2, find.data, find.size) == 0) { + result.found = true; + result.index = base_ptr - string.data + 2; + } else if (DN_Memcmp(base_ptr + 3, find.data, find.size) == 0) { + result.found = true; + result.index = base_ptr - string.data + 3; + } + + if (result.found) { + result.start_to_before_match = DN_Str8FromPtr(string.data, result.index); + result.match = DN_Str8FromPtr(string.data + result.index, find.size); + result.match_to_end_of_buffer = DN_Str8FromPtr(result.match.data, string.size - result.index); + return result; + } + + zero_byte_mask = DN_BitClearNextLSB(zero_byte_mask); + } + } + + for (DN_USize index = ptr - string.data - 1; index < string.size; index--) { + DN_Str8 string_slice = DN_Str8Subset(string, index, find.size); + if (DN_Str8Eq(string_slice, find)) { + result.found = true; + result.index = index; + result.start_to_before_match = DN_Str8FromPtr(string.data, index); + result.match = DN_Str8FromPtr(string.data + index, find.size); + result.match_to_end_of_buffer = DN_Str8FromPtr(result.match.data, string.size - index); + return result; + } + } + + return result; +} + +DN_API DN_Str8BSplitResult DN_Str8BSplitAVX512F(DN_Str8 string, DN_Str8 find) +{ + DN_Str8BSplitResult result = {}; + DN_Str8FindResult find_result = DN_Str8FindAVX512F(string, find); + if (find_result.found) { + result.lhs.data = string.data; + result.lhs.size = find_result.index; + result.rhs = DN_Str8Advance(find_result.match_to_end_of_buffer, find.size); + } else { + result.lhs = string; + } + + return result; +} + +DN_API DN_Str8BSplitResult DN_Str8BSplitLastAVX512F(DN_Str8 string, DN_Str8 find) +{ + DN_Str8BSplitResult result = {}; + DN_Str8FindResult find_result = DN_Str8FindLastAVX512F(string, find); + if (find_result.found) { + result.lhs.data = string.data; + result.lhs.size = find_result.index; + result.rhs = DN_Str8Advance(find_result.match_to_end_of_buffer, find.size); + } else { + result.lhs = string; + } + + return result; +} + +DN_API DN_USize DN_Str8SplitAVX512F(DN_Str8 string, DN_Str8 delimiter, DN_Str8 *splits, DN_USize splits_count, DN_Str8SplitFlags flags) +{ + DN_USize result = 0; // The number of splits in the actual string. + if (string.size == 0 || delimiter.size == 0 || delimiter.size <= 0) + return result; + + DN_Str8BSplitResult split = {}; + DN_Str8 first = string; + do { + split = DN_Str8BSplitAVX512F(first, delimiter); + if (split.lhs.size || DN_BitIsNotSet(flags, DN_Str8SplitFlags_ExcludeEmptyStrings)) { + if (splits && result < splits_count) + splits[result] = split.lhs; + result++; + } + first = split.rhs; + } while (first.size); + + return result; +} + +DN_API DN_Str8Slice DN_Str8SplitAllocAVX512F(DN_Arena *arena, DN_Str8 string, DN_Str8 delimiter, DN_Str8SplitFlags flags) +{ + DN_Str8Slice result = {}; + DN_USize splits_required = DN_Str8SplitAVX512F(string, delimiter, /*splits*/ nullptr, /*count*/ 0, flags); + result.data = DN_ArenaNewArray(arena, DN_Str8, splits_required, DN_ZMem_No); + if (result.data) { + result.count = DN_Str8SplitAVX512F(string, delimiter, result.data, splits_required, flags); + DN_Assert(splits_required == result.count); + } + return result; +} +#endif // DN_STR8_AVX512F + +DN_API DN_Str8 DN_Str8SliceRender(DN_Str8Slice slice, DN_Str8 separator, DN_Arena *arena) +{ + DN_Str8 result = {}; + if (!arena) + return result; + + DN_USize total_size = 0; + for (DN_USize index = 0; index < slice.count; index++) { + if (index) + total_size += separator.size; + DN_Str8 item = slice.data[index]; + total_size += item.size; + } + + result = DN_Str8AllocArena(total_size, DN_ZMem_No, arena); + if (result.data) { + DN_USize write_index = 0; + for (DN_USize index = 0; index < slice.count; index++) { + if (index) { + DN_Memcpy(result.data + write_index, separator.data, separator.size); + write_index += separator.size; + } + DN_Str8 item = slice.data[index]; + DN_Memcpy(result.data + write_index, item.data, item.size); + write_index += item.size; + } + } + + return result; +} + +DN_API DN_Str8 DN_Str8RenderSpaceSep(DN_Str8Slice slice, DN_Arena *arena) +{ + DN_Str8 result = DN_Str8SliceRender(slice, DN_Str8Lit(" "), arena); + return result; +} + +DN_API int DN_Str8CompareNatural(DN_Str8 lhs, DN_Str8 rhs, DN_Str8EqCase eq_case) +{ + const char *lhs_it = lhs.data; + const char *rhs_it = rhs.data; + const char *lhs_end = lhs.data + lhs.size; + const char *rhs_end = rhs.data + rhs.size; + + while (lhs_it < lhs_end && rhs_it < rhs_end) { + // NOTE: Skip leading spaces + while (lhs_it < lhs_end && DN_CharIsWhitespace(*lhs_it)) + lhs_it++; + while (rhs_it < rhs_end && DN_CharIsWhitespace(*rhs_it)) + rhs_it++; + + if (lhs_it >= lhs_end || rhs_it >= rhs_end) + break; + + // NOTE: Check if current positions are digits + if (DN_CharIsDigit(*lhs_it) && DN_CharIsDigit(*rhs_it)) { + // NOTE: Extract full number from lhs + DN_U64 lhs_num = 0; + while (lhs_it < lhs_end && DN_CharIsDigit(*lhs_it)) { + lhs_num = lhs_num * 10 + (*lhs_it - '0'); + lhs_it++; + } + + // NOTE: Extract full number from rhs + DN_U64 rhs_num = 0; + while (rhs_it < rhs_end && DN_CharIsDigit(*rhs_it)) { + rhs_num = rhs_num * 10 + (*rhs_it - '0'); + rhs_it++; + } + + if (lhs_num != rhs_num) + return (lhs_num < rhs_num) ? -1 : 1; + } else { + // NOTE: Compare non-digit characters + char lhs_ch = *lhs_it; + char rhs_ch = *rhs_it; + + if (eq_case == DN_Str8EqCase_Insensitive) { + if (DN_CharIsAlphabet(lhs_ch)) + lhs_ch = DN_CharToLower(lhs_ch); + if (DN_CharIsAlphabet(rhs_ch)) + rhs_ch = DN_CharToLower(rhs_ch); + } + + if (lhs_ch != rhs_ch) + return (lhs_ch < rhs_ch) ? -1 : 1; + lhs_it++; + rhs_it++; + } + } + + // NOTE: One string is prefix of other; shorter comes first + if (lhs_it < lhs_end) + return 1; + if (rhs_it < rhs_end) + return -1; + return 0; +} + +DN_API int DN_Str8CompareLexicographic(DN_Str8 lhs, DN_Str8 rhs, DN_Str8EqCase eq_case) +{ + const char *lhs_it = lhs.data; + const char *rhs_it = rhs.data; + const char *lhs_end = lhs.data + lhs.size; + const char *rhs_end = rhs.data + rhs.size; + + while (lhs_it < lhs_end && rhs_it < rhs_end) { + char lhs_ch = *lhs_it; + char rhs_ch = *rhs_it; + if (eq_case == DN_Str8EqCase_Insensitive) { + if (DN_CharIsAlphabet(lhs_ch)) + lhs_ch = DN_CharToLower(lhs_ch); + if (DN_CharIsAlphabet(rhs_ch)) + rhs_ch = DN_CharToLower(rhs_ch); + } + if (lhs_ch != rhs_ch) + return (lhs_ch < rhs_ch) ? -1 : 1; + lhs_it++; + rhs_it++; + } + + // NOTE: One string is prefix of other; shorter comes first + if (lhs.size < rhs.size) + return -1; + if (rhs.size < lhs.size) + return 1; + return 0; +} + + +DN_API bool DN_Str16Eq(DN_Str16 lhs, DN_Str16 rhs) +{ + if (lhs.size != rhs.size) + return false; + bool result = (DN_Memcmp(lhs.data, rhs.data, lhs.size) == 0); + return result; +} + + +DN_API DN_Str16 DN_Str16SliceRender(DN_Str16Slice slice, DN_Str16 separator, DN_Arena *arena) +{ + DN_Str16 result = {}; + if (!arena) + return result; + + DN_USize total_size = 0; + for (DN_USize index = 0; index < slice.count; index++) { + if (index) + total_size += separator.size; + DN_Str16 item = slice.data[index]; + total_size += item.size; + } + + result = {DN_ArenaNewArray(arena, wchar_t, total_size + 1, DN_ZMem_No), total_size}; + if (result.data) { + DN_USize write_index = 0; + for (DN_USize index = 0; index < slice.count; index++) { + if (index) { + DN_Memcpy(result.data + write_index, separator.data, separator.size * sizeof(result.data[0])); + write_index += separator.size; + } + DN_Str16 item = slice.data[index]; + DN_Memcpy(result.data + write_index, item.data, item.size * sizeof(result.data[0])); + write_index += item.size; + } + } + + result.data[total_size] = 0; + return result; +} + +DN_API DN_Str16 DN_Str16RenderSpaceSep(DN_Str16Slice slice, DN_Arena *arena) +{ + DN_Str16 result = DN_Str16SliceRender(slice, DN_Str16Lit(L" "), arena); + return result; +} + +DN_API DN_Str8Builder DN_Str8BuilderFromArena(DN_Arena *arena) +{ + DN_Str8Builder result = {}; + result.arena = arena; + return result; +} + +DN_API DN_Str8Builder DN_Str8BuilderFromStr8PtrRef(DN_Arena *arena, DN_Str8 const *strings, DN_USize size) +{ + DN_Str8Builder result = DN_Str8BuilderFromArena(arena); + DN_Str8BuilderAppendArrayRef(&result, strings, size); + return result; +} + +DN_API DN_Str8Builder DN_Str8BuilderFromStr8PtrCopy(DN_Arena *arena, DN_Str8 const *strings, DN_USize size) +{ + DN_Str8Builder result = DN_Str8BuilderFromArena(arena); + DN_Str8BuilderAppendArrayCopy(&result, strings, size); + return result; +} + +DN_API DN_Str8Builder DN_Str8BuilderFromBuilder(DN_Arena *arena, DN_Str8Builder const *builder) +{ + DN_Str8Builder result = DN_Str8BuilderFromArena(arena); + DN_Str8BuilderAppendBuilderCopy(&result, builder); + return result; +} + +DN_API bool DN_Str8BuilderAddArrayRef(DN_Str8Builder *builder, DN_Str8 const *strings, DN_USize size, DN_Str8BuilderAdd add) +{ + if (!builder) + return false; + + if (!strings || size <= 0) + return true; + + // NOTE: Allocate the links + DN_Str8Link *links = DN_ArenaNewArrayNoZ(builder->arena, DN_Str8Link, size); + if (!links) + return false; + + if (add == DN_Str8BuilderAdd_Append) { + for (DN_ForIndexU(index, size)) { + DN_Str8 string = strings[index]; + DN_Str8Link *link = links + index; + link->string = string; + link->next = NULL; + if (builder->head) + builder->tail->next = link; + else + builder->head = link; + builder->tail = link; + builder->count++; + builder->string_size += string.size; + } + } else { + DN_Assert(add == DN_Str8BuilderAdd_Prepend); + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(6293) // NOTE: Ill-defined loop + for (DN_USize index = size - 1; index < size; index--) { + DN_MSVC_WARNING_POP + DN_Str8 string = strings[index]; + DN_Str8Link *link = links + index; + link->string = string; + link->next = builder->head; + builder->head = link; + if (!builder->tail) + builder->tail = link; + builder->count++; + builder->string_size += string.size; + } + } + return true; +} + +DN_API bool DN_Str8BuilderAddArrayCopy(DN_Str8Builder *builder, DN_Str8 const *strings, DN_USize size, DN_Str8BuilderAdd add) +{ + if (!builder) + return false; + + if (!strings || size <= 0) + return true; + + bool result = true; + DN_U64 arena_p = DN_MemListPos(builder->arena->mem); + DN_Str8 *strings_copy = DN_ArenaNewArrayNoZ(builder->arena, DN_Str8, size); + for (DN_ForIndexU(index, size)) { + strings_copy[index] = DN_Str8FromStr8Arena(strings[index], builder->arena); + if (strings_copy[index].size != strings[index].size) { + result = false; + break; + } + } + + if (result) + result = DN_Str8BuilderAddArrayRef(builder, strings_copy, size, add); + else + DN_MemListPopTo(builder->arena->mem, arena_p); + return result; +} + +DN_API bool DN_Str8BuilderAddFV(DN_Str8Builder *builder, DN_Str8BuilderAdd add, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_Str8 string = DN_Str8FromFmtVArena(builder->arena, fmt, args); + DN_U64 arena_p = DN_MemListPos(builder->arena->mem); + bool result = DN_Str8BuilderAddArrayRef(builder, &string, 1, add); + if (!result) + DN_MemListPopTo(builder->arena->mem, arena_p); + return result; +} + +DN_API bool DN_Str8BuilderAppendRef(DN_Str8Builder *builder, DN_Str8 string) +{ + bool result = DN_Str8BuilderAddArrayRef(builder, &string, 1, DN_Str8BuilderAdd_Append); + return result; +} + +DN_API bool DN_Str8BuilderAppendCopy(DN_Str8Builder *builder, DN_Str8 string) +{ + bool result = DN_Str8BuilderAddArrayCopy(builder, &string, 1, DN_Str8BuilderAdd_Append); + return result; +} + +DN_API bool DN_Str8BuilderAppendF(DN_Str8Builder *builder, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool result = DN_Str8BuilderAppendFV(builder, fmt, args); + va_end(args); + return result; +} + +DN_API bool DN_Str8BuilderAppendBytesRef(DN_Str8Builder *builder, void const *ptr, DN_USize size) +{ + DN_Str8 input = DN_Str8FromPtr(ptr, size); + bool result = DN_Str8BuilderAppendRef(builder, input); + return result; +} + +DN_API bool DN_Str8BuilderAppendBytesCopy(DN_Str8Builder *builder, void const *ptr, DN_USize size) +{ + DN_Str8 input = DN_Str8FromPtr(ptr, size); + bool result = DN_Str8BuilderAppendCopy(builder, input); + return result; +} + +static bool DN_Str8BuilderAppendBuilder_(DN_Str8Builder *dest, DN_Str8Builder const *src, bool copy) +{ + if (!dest) + return false; + if (!src || src->string_size == 0) + return true; + + DN_Arena arena = DN_ArenaTempBeginFromArena(dest->arena); + DN_Str8Link *links = DN_ArenaNewArrayNoZ(&arena, DN_Str8Link, src->count); + bool result = true; + if (links) { + DN_Str8Link *first = nullptr; + DN_Str8Link *last = nullptr; + DN_USize link_index = 0; + for (DN_Str8Link const *it = src->head; it; it = it->next) { + DN_Str8Link *link = links + link_index++; + link->next = nullptr; + link->string = it->string; + + if (copy) { + link->string = DN_Str8FromStr8Arena(it->string, &arena); + if (link->string.size != it->string.size) { + result = false; + break; + } + } + + if (last) + last->next = link; + else + first = link; + last = link; + } + + if (result) { + if (dest->head) + dest->tail->next = first; + else + dest->head = first; + dest->tail = last; + dest->count += src->count; + dest->string_size += src->string_size; + } + } + DN_ArenaTempEnd(&arena, result ? DN_ArenaReset_No : DN_ArenaReset_Yes); + return result; +} + +DN_API bool DN_Str8BuilderAppendBuilderRef(DN_Str8Builder *dest, DN_Str8Builder const *src) +{ + bool result = DN_Str8BuilderAppendBuilder_(dest, src, false); + return result; +} + +DN_API bool DN_Str8BuilderAppendBuilderCopy(DN_Str8Builder *dest, DN_Str8Builder const *src) +{ + bool result = DN_Str8BuilderAppendBuilder_(dest, src, true); + return result; +} + +DN_API bool DN_Str8BuilderPrependRef(DN_Str8Builder *builder, DN_Str8 string) +{ + bool result = DN_Str8BuilderAddArrayRef(builder, &string, 1, DN_Str8BuilderAdd_Prepend); + return result; +} + +DN_API bool DN_Str8BuilderPrependCopy(DN_Str8Builder *builder, DN_Str8 string) +{ + bool result = DN_Str8BuilderAddArrayCopy(builder, &string, 1, DN_Str8BuilderAdd_Prepend); + return result; +} + +DN_API bool DN_Str8BuilderPrependF(DN_Str8Builder *builder, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool result = DN_Str8BuilderPrependFV(builder, fmt, args); + va_end(args); + return result; +} + +DN_API bool DN_Str8BuilderErase(DN_Str8Builder *builder, DN_Str8 string) +{ + for (DN_Str8Link **it = &builder->head; *it; it = &((*it)->next)) { + if (DN_Str8Eq((*it)->string, string)) { + *it = (*it)->next; + builder->string_size -= string.size; + builder->count -= 1; + return true; + } + } + return false; +} + +DN_API DN_Str8 DN_Str8FromStr8BuilderAllocator(DN_Str8Builder const *builder, DN_Allocator allocator) +{ + DN_Str8 result = DN_Str8FromStr8BuilderDelimitAllocator(builder, DN_Str8Lit(""), allocator); + return result; +} + +DN_API DN_Str8 DN_Str8FromStr8BuilderArena(DN_Str8Builder const *builder, DN_Arena *arena) +{ + DN_Str8 result = DN_Str8FromStr8BuilderAllocator(builder, DN_AllocatorFromArena(arena)); + return result; +} + +DN_API DN_Str8 DN_Str8FromStr8BuilderDelimitAllocator(DN_Str8Builder const *builder, DN_Str8 delimiter, DN_Allocator allocator) +{ + DN_Str8 result = {}; + if (!builder || builder->string_size <= 0 || builder->count <= 0) + return result; + + DN_USize size_for_delimiter = delimiter.size ? ((builder->count - 1) * delimiter.size) : 0; + result = DN_Str8AllocAllocator(builder->string_size + size_for_delimiter, DN_ZMem_No, allocator); + if (!result.data) + return result; + + DN_USize write_count = 0; + for (DN_Str8Link *link = builder->head; link; link = link->next) { + DN_Memcpy(result.data + write_count, link->string.data, link->string.size); + write_count += link->string.size; + if (link->next && delimiter.size) { + DN_Memcpy(result.data + write_count, delimiter.data, delimiter.size); + write_count += delimiter.size; + } + } + + result.data[write_count] = 0; + DN_Assert(write_count == builder->string_size + size_for_delimiter); + return result; +} + +DN_API DN_Str8 DN_Str8FromStr8BuilderDelimitArena(DN_Str8Builder const *builder, DN_Str8 delimiter, DN_Arena *arena) +{ + DN_Str8 result = DN_Str8FromStr8BuilderDelimitAllocator(builder, delimiter, DN_AllocatorFromArena(arena)); + return result; +} + +// NOTE: DN_UTF +DN_API int DN_UTF8Encode(DN_U8 utf8[4], DN_U32 codepoint) +{ + // NOTE: Table from https://www.reedbeta.com/blog/programmers-intro-to-unicode/ + // ----------------------------------------+----------------------------+--------------------+ + // UTF-8 (binary) | Code point (binary) | Range | + // ----------------------------------------+----------------------------+--------------------+ + // 0xxx'xxxx | xxx'xxxx | U+0000 - U+007F | + // 110x'xxxx 10yy'yyyy | xxx'xxyy'yyyy | U+0080 - U+07FF | + // 1110'xxxx 10yy'yyyy 10zz'zzzz | xxxx'yyyy'yyzz'zzzz | U+0800 - U+FFFF | + // 1111'0xxx 10yy'yyyy 10zz'zzzz 10ww'wwww | x'xxyy'yyyy'zzzz'zzww'wwww | U+10000 - U+10FFFF | + // ----------------------------------------+----------------------------+--------------------+ + + if (codepoint <= 0b0111'1111) { + utf8[0] = DN_Cast(DN_U8) codepoint; + return 1; + } + + if (codepoint <= 0b0111'1111'1111) { + utf8[0] = (0b1100'0000 | ((codepoint >> 6) & 0b01'1111)); // x + utf8[1] = (0b1000'0000 | ((codepoint >> 0) & 0b11'1111)); // y + return 2; + } + + if (codepoint <= 0b1111'1111'1111'1111) { + utf8[0] = (0b1110'0000 | ((codepoint >> 12) & 0b00'1111)); // x + utf8[1] = (0b1000'0000 | ((codepoint >> 6) & 0b11'1111)); // y + utf8[2] = (0b1000'0000 | ((codepoint >> 0) & 0b11'1111)); // z + return 3; + } + + if (codepoint <= 0b1'1111'1111'1111'1111'1111) { + utf8[0] = (0b1111'0000 | ((codepoint >> 18) & 0b00'0111)); // x + utf8[1] = (0b1000'0000 | ((codepoint >> 12) & 0b11'1111)); // y + utf8[2] = (0b1000'0000 | ((codepoint >> 6) & 0b11'1111)); // z + utf8[3] = (0b1000'0000 | ((codepoint >> 0) & 0b11'1111)); // w + return 4; + } + + return 0; +} + +DN_API DN_UTF8DecodeResult DN_UTF8Decode(DN_Str8 stream) +{ + DN_UTF8DecodeResult result = {}; + result.remaining = stream; + if (stream.size <= 0) + return result; + + DN_U8 b0 = DN_Cast(DN_U8)stream.data[0]; + DN_U8 b1 = DN_Cast(DN_U8)(stream.size >= 2 ? stream.data[1] : 0); + DN_U8 b2 = DN_Cast(DN_U8)(stream.size >= 3 ? stream.data[2] : 0); + DN_U8 b3 = DN_Cast(DN_U8)(stream.size >= 4 ? stream.data[3] : 0); + + if ((b0 & 0b1000'0000) == 0) { + result.codepoint = b0; + result.success = true; + result.remaining = DN_Str8FromPtr(stream.data + 1, stream.size - 1); + return result; + } + + if ((b0 & 0b1110'0000) == 0b1100'0000) { + if (stream.size < 2) + return result; + if ((b1 & 0b1100'0000) != 0b1000'0000) + return result; + DN_U32 cp = ((b0 & 0b0001'1111) << 6) | ((b1 & 0b0011'1111) << 0); + if (cp < 0x80) + return result; + result.codepoint = cp; + result.success = true; + result.remaining = DN_Str8FromPtr(stream.data + 2, stream.size - 2); + return result; + } + + if ((b0 & 0b1111'0000) == 0b1110'0000) { + if (stream.size < 3) + return result; + if ((b1 & 0b1100'0000) != 0b1000'0000) + return result; + if ((b2 & 0b1100'0000) != 0b1000'0000) + return result; + DN_U32 cp = ((b0 & 0b0000'1111) << 12) | ((b1 & 0b0011'1111) << 6) | ((b2 & 0b0011'1111) << 0); + if (cp < 0x800) + return result; + result.codepoint = cp; + result.success = true; + result.remaining = DN_Str8FromPtr(stream.data + 3, stream.size - 3); + return result; + } + + if ((b0 & 0b1111'1000) == 0b1111'0000) { + if (stream.size < 4) + return result; + if ((b1 & 0b1100'0000) != 0b1000'0000) + return result; + if ((b2 & 0b1100'0000) != 0b1000'0000) + return result; + if ((b3 & 0b1100'0000) != 0b1000'0000) + return result; + DN_U32 cp = ((b0 & 0b0000'0111) << 18) | + ((b1 & 0b0011'1111) << 12) | + ((b2 & 0b0011'1111) << 6) | + ((b3 & 0b0011'1111) << 0); + if (cp < 0x10000 || cp > 0x10FFFF) + return result; + result.codepoint = cp; + result.success = true; + result.remaining = DN_Str8FromPtr(stream.data + 4, stream.size - 4); + return result; + } + + return result; +} + +DN_API bool DN_UTF8DecodeIterate(DN_UTF8DecodeIterator *it, DN_Str8 utf8) +{ + if (it->init) { + it->codepoint_index++; + } else { + it->remaining = utf8; + it->init = true; + } + DN_UTF8DecodeResult decode = DN_UTF8Decode(it->remaining); + it->success = decode.success; + it->remaining = decode.remaining; + it->codepoint = decode.codepoint; + bool result = it->success; + return result; +} + +DN_API int DN_UTF16Encode(DN_U16 utf16[2], DN_U32 codepoint) +{ + // NOTE: Table from https://www.reedbeta.com/blog/programmers-intro-to-unicode/ + // ----------------------------------------+------------------------------------+------------------+ + // UTF-16 (binary) | Code point (binary) | Range | + // ----------------------------------------+------------------------------------+------------------+ + // xxxx'xxxx'xxxx'xxxx | xxxx'xxxx'xxxx'xxxx | U+0000???U+FFFF | + // 1101'10xx'xxxx'xxxx 1101'11yy'yyyy'yyyy | xxxx'xxxx'xxyy'yyyy'yyyy + 0x10000 | U+10000???U+10FFFF | + // ----------------------------------------+------------------------------------+------------------+ + + if (codepoint <= 0b1111'1111'1111'1111) { + utf16[0] = DN_Cast(DN_U16) codepoint; + return 1; + } + + if (codepoint <= 0b1111'1111'1111'1111'1111) { + DN_U32 surrogate_codepoint = codepoint + 0x10000; + utf16[0] = 0b1101'1000'0000'0000 | ((surrogate_codepoint >> 10) & 0b11'1111'1111); // x + utf16[1] = 0b1101'1100'0000'0000 | ((surrogate_codepoint >> 0) & 0b11'1111'1111); // y + return 2; + } + + return 0; +} + + +DN_API DN_U8 DN_U8FromHexNibble(char hex) +{ + bool digit = hex >= '0' && hex <= '9'; + bool upper = hex >= 'A' && hex <= 'F'; + bool lower = hex >= 'a' && hex <= 'f'; + DN_U8 result = 0xFF; + if (digit) + result = hex - '0'; + if (upper) + result = hex - 'A' + 10; + if (lower) + result = hex - 'a' + 10; + return result; +} + +DN_API DN_NibbleFromU8Result DN_NibbleFromU8(DN_U8 u8) +{ + static char const *table = "0123456789abcdef"; + DN_U8 lhs = (u8 >> 0) & 0xF; + DN_U8 rhs = (u8 >> 4) & 0xF; + DN_NibbleFromU8Result result = {}; + result.nibble0 = table[rhs]; + result.nibble1 = table[lhs]; + return result; +} + +DN_API DN_USize DN_BytesFromHex(DN_Str8 hex, void *dest, DN_USize dest_count) +{ + DN_Str8 hex_trimmed = DN_Str8TrimHexPrefix(hex); + DN_USize result = 0; + if (hex_trimmed.size > (dest_count * 2)) + return result; + + DN_U8 *ptr = DN_Cast(DN_U8 *) dest; + DN_USize index = 0; + + // NOTE: We are given an odd-sized hex string e.g.: 'F' instead of '0F', we 'left-pad' the parser + // and support reading the single nibble as 'F' + if (hex_trimmed.size % 2 != 0) { + DN_U8 nibble0 = 0; + DN_U8 nibble1 = DN_U8FromHexNibble(hex_trimmed.data[index++]); + if (nibble1 == 0xFF) + return result; + *ptr++ = nibble0 << 4 | nibble1 << 0; + result++; + } + + // NOTE: Parse the rest of the hex which is in byte pairs + for (; index < hex_trimmed.size; index += 2) { + DN_U8 nibble0 = DN_U8FromHexNibble(hex_trimmed.data[index + 0]); + DN_U8 nibble1 = DN_U8FromHexNibble(hex_trimmed.data[index + 1]); + if (nibble0 == 0xFF || nibble1 == 0xFF) + return result; + *ptr++ = nibble0 << 4 | nibble1 << 0; + result++; + } + return result; +} + +DN_API DN_Str8 DN_BytesFromHexArena(DN_Str8 hex, DN_Arena *arena) +{ + DN_Str8 result = DN_BytesFromHexPtrArena(hex.data, hex.size, arena); + return result; +} + +DN_API DN_USize DN_BytesFromHexPtr(char const *hex, DN_USize hex_count, void *dest, DN_USize dest_count) +{ + DN_USize result = DN_BytesFromHex(DN_Str8FromPtr(hex, hex_count), dest, dest_count); + return result; +} + +DN_API DN_Str8 DN_BytesFromHexPtrArena(char const *hex, DN_USize hex_count, DN_Arena *arena) +{ + DN_Str8 hex_trimmed = DN_Str8TrimHexPrefix(DN_Str8FromPtr(hex, hex_count)); + DN_Assert(hex_trimmed.size % 2 == 0); + DN_Str8 result = {}; + result.data = DN_ArenaNewArray(arena, char, hex_trimmed.size / 2, DN_ZMem_No); + if (result.data) + result.size = DN_BytesFromHex(hex_trimmed, result.data, hex_trimmed.size / 2); + return result; +} + +DN_API DN_Str8 DN_BytesFromHexPtrPool(char const *hex, DN_USize hex_count, DN_Pool *pool) +{ + DN_Str8 hex_trimmed = DN_Str8TrimHexPrefix(DN_Str8FromPtr(hex, hex_count)); + DN_Assert(hex_trimmed.size % 2 == 0); + DN_Str8 result = {}; + result.data = DN_PoolNewArray(pool, char, hex_trimmed.size / 2); + if (result.data) + result.size = DN_BytesFromHex(hex_trimmed, result.data, hex_trimmed.size / 2); + return result; +} + + +DN_API DN_U8x16 DN_BytesFromHex32Ptr(char const *hex, DN_USize hex_count) +{ + DN_U8x16 result = {}; + DN_Str8 hex_trimmed = DN_Str8TrimHexPrefix(DN_Str8FromPtr(hex, hex_count)); + DN_Assert(hex_trimmed.size / 2 == sizeof result.data); + DN_USize bytes_written = DN_BytesFromHex(hex_trimmed, result.data, sizeof result.data); + DN_Assert(bytes_written == sizeof result.data); + return result; +} + +DN_API DN_U8x32 DN_BytesFromHex64Ptr(char const *hex, DN_USize hex_count) +{ + DN_U8x32 result = {}; + DN_Str8 hex_trimmed = DN_Str8TrimHexPrefix(DN_Str8FromPtr(hex, hex_count)); + DN_Assert(hex_trimmed.size / 2 == sizeof result.data); + DN_USize bytes_written = DN_BytesFromHex(hex_trimmed, result.data, sizeof result.data); + DN_Assert(bytes_written == sizeof result.data); + return result; +} + +DN_API DN_HexU64 DN_HexFromU64(DN_U64 value, DN_HexFromU64Type type) +{ + DN_HexU64 result = {}; + DN_USize size = DN_HexFromPtrBytes(&value, sizeof(value), result.data, sizeof(result.data), DN_TrimLeadingZero_No); + result.size = DN_SaturateCastUSizeToU8(size); + if (type == DN_HexFromU64Type_Uppercase) { + for (DN_USize index = 0; index < result.size; index++) + result.data[index] = DN_CharToUpper(result.data[index]); + } + return result; +} + +DN_API DN_USize DN_HexFromPtrBytes(void const *bytes, DN_USize bytes_count, void *hex, DN_USize hex_count, DN_TrimLeadingZero trim_leading_z) +{ + DN_USize result = 0; + if ((bytes_count * 2) > hex_count) + return result; + DN_U8 const *src_u8 = DN_Cast(DN_U8 const *) bytes; + DN_U8 *ptr = DN_Cast(DN_U8 *) hex; + bool leading_zeros = true; + + for (DN_USize index = 0; index < bytes_count; index++) { + char ch = src_u8[index]; + if (leading_zeros) + leading_zeros = ch == 0; + + if (leading_zeros) { + if (trim_leading_z == DN_TrimLeadingZero_Yes && ch == 0) + continue; + } + + DN_NibbleFromU8Result to_nibbles = DN_NibbleFromU8(ch); + *ptr++ = to_nibbles.nibble0; + *ptr++ = to_nibbles.nibble1; + result += 2; + } + + if (result == 0) { + *ptr = '0'; + result++; + } + return result; +} + +DN_API DN_Str8 DN_HexFromPtrBytesArena(void const *bytes, DN_USize bytes_count, DN_Arena *arena, DN_TrimLeadingZero trim_leading_z) +{ + DN_Str8 result = {}; + if (bytes_count) { + result.data = DN_ArenaNewArray(arena, char, bytes_count * 2, DN_ZMem_No); + if (result.data) + result.size = DN_HexFromPtrBytes(bytes, bytes_count, result.data, bytes_count * 2, trim_leading_z); + } + return result; +} + +DN_API DN_USize DN_HexFromStr8Bytes(DN_Str8 bytes, void *hex, DN_USize hex_count, DN_TrimLeadingZero trim_leading_z) +{ + DN_USize result = DN_HexFromPtrBytes(bytes.data, bytes.size, hex, hex_count, trim_leading_z); + return result; +} + +DN_API DN_Str8 DN_HexFromStr8BytesArena(DN_Str8 bytes, DN_Arena *arena, DN_TrimLeadingZero trim_leading_z) +{ + DN_Str8 result = {}; + if (bytes.size) { + result.data = DN_ArenaNewArray(arena, char, bytes.size * 2, DN_ZMem_No); + if (result.data) + result.size = DN_HexFromStr8Bytes(bytes, result.data, bytes.size * 2, trim_leading_z); + } + return result; +} + +DN_API DN_Hex32 DN_Hex32FromPtr16b(void const *bytes, DN_USize bytes_count, DN_TrimLeadingZero trim_leading_z) +{ + DN_Hex32 result = {}; + DN_Assert(bytes_count * 2 == sizeof result.data - 1); + result.size = DN_HexFromPtrBytes(bytes, bytes_count, result.data, sizeof result.data, trim_leading_z); + DN_Assert(result.size <= sizeof result.data - 1); + return result; +} + +DN_API DN_Hex64 DN_Hex64FromPtr32b(void const *bytes, DN_USize bytes_count, DN_TrimLeadingZero trim_leading_z) +{ + DN_Hex64 result = {}; + DN_Assert(bytes_count * 2 == sizeof result.data - 1); + result.size = DN_HexFromPtrBytes(bytes, bytes_count, result.data, sizeof result.data, trim_leading_z); + DN_Assert(result.size <= sizeof result.data - 1); + return result; +} + +DN_API DN_Hex128 DN_Hex128FromPtr64b(void const *bytes, DN_USize bytes_count, DN_TrimLeadingZero trim_leading_z) +{ + DN_Hex128 result = {}; + DN_Assert(bytes_count * 2 == sizeof result.data - 1); + result.size = DN_HexFromPtrBytes(bytes, bytes_count, result.data, sizeof result.data, trim_leading_z); + DN_Assert(result.size <= sizeof result.data - 1); + return result; +} + +DN_API DN_Str8x128 DN_AgeStr8FromMsU64(DN_U64 duration_ms, DN_AgeUnit units) +{ + DN_Str8x128 result = {}; + DN_U64 remainder_ms = duration_ms; + if (units & DN_AgeUnit_FractionalSec) { + units |= DN_AgeUnit_Sec; + units &= ~DN_AgeUnit_Ms; + } + + DN_Str8 unit_suffix = {}; + if (units & DN_AgeUnit_Year) { + unit_suffix = DN_Str8Lit("y"); + DN_USize value_usize = remainder_ms / (DN_SecFromYears(1) * 1000); + remainder_ms -= DN_SecFromYears(value_usize) * 1000; + if (value_usize) + DN_FmtAppend(result.data, &result.size, sizeof(result.data), "%s%zu%.*s", result.size ? " " : "", value_usize, DN_Str8PrintFmt(unit_suffix)); + } + + if (units & DN_AgeUnit_Week) { + unit_suffix = DN_Str8Lit("w"); + DN_USize value_usize = remainder_ms / (DN_SecFromWeeks(1) * 1000); + remainder_ms -= DN_SecFromWeeks(value_usize) * 1000; + if (value_usize) + DN_FmtAppend(result.data, &result.size, sizeof(result.data), "%s%zu%.*s", result.size ? " " : "", value_usize, DN_Str8PrintFmt(unit_suffix)); + } + + if (units & DN_AgeUnit_Day) { + unit_suffix = DN_Str8Lit("d"); + DN_USize value_usize = remainder_ms / (DN_SecFromDays(1) * 1000); + remainder_ms -= DN_SecFromDays(value_usize) * 1000; + if (value_usize) + DN_FmtAppend(result.data, &result.size, sizeof(result.data), "%s%zu%.*s", result.size ? " " : "", value_usize, DN_Str8PrintFmt(unit_suffix)); + } + + if (units & DN_AgeUnit_Hr) { + unit_suffix = DN_Str8Lit("h"); + DN_USize value_usize = remainder_ms / (DN_SecFromHours(1) * 1000); + remainder_ms -= DN_SecFromHours(value_usize) * 1000; + if (value_usize) + DN_FmtAppend(result.data, &result.size, sizeof(result.data), "%s%zu%.*s", result.size ? " " : "", value_usize, DN_Str8PrintFmt(unit_suffix)); + } + + if (units & DN_AgeUnit_Min) { + unit_suffix = DN_Str8Lit("m"); + DN_USize value_usize = remainder_ms / (DN_SecFromMins(1) * 1000); + remainder_ms -= DN_SecFromMins(value_usize) * 1000; + if (value_usize) + DN_FmtAppend(result.data, &result.size, sizeof(result.data), "%s%zu%.*s", result.size ? " " : "", value_usize, DN_Str8PrintFmt(unit_suffix)); + } + + if (units & DN_AgeUnit_Sec) { + unit_suffix = DN_Str8Lit("s"); + if (units & DN_AgeUnit_FractionalSec) { + DN_F64 remainder_s = remainder_ms / 1000.0; + DN_FmtAppend(result.data, &result.size, sizeof(result.data), "%s%.3f%.*s", result.size ? " " : "", remainder_s, DN_Str8PrintFmt(unit_suffix)); + remainder_ms = 0; + } else { + DN_USize value_usize = remainder_ms / 1000; + remainder_ms -= DN_Cast(DN_USize)(value_usize * 1000); + if (value_usize) + DN_FmtAppend(result.data, &result.size, sizeof(result.data), "%s%zu%.*s", result.size ? " " : "", value_usize, DN_Str8PrintFmt(unit_suffix)); + } + } + + if (units & DN_AgeUnit_Ms) { + unit_suffix = DN_Str8Lit("ms"); + DN_Assert((units & DN_AgeUnit_FractionalSec) == 0); + DN_USize value_usize = remainder_ms; + remainder_ms -= value_usize; + if (value_usize || result.size == 0) + DN_FmtAppend(result.data, &result.size, sizeof(result.data), "%s%zu%.*s", result.size ? " " : "", value_usize, DN_Str8PrintFmt(unit_suffix)); + } + + if (result.size == 0) + DN_FmtAppend(result.data, &result.size, sizeof(result.data), "0%.*s", DN_Str8PrintFmt(unit_suffix)); + return result; +} + +DN_API DN_Str8x128 DN_AgeStr8FromSecU64(DN_U64 duration_s, DN_AgeUnit units) +{ + DN_U64 duration_ms = duration_s * 1000; + DN_Str8x128 result = DN_AgeStr8FromMsU64(duration_ms, units); + return result; +} + +DN_API DN_Str8x128 DN_AgeStr8FromSecF64(DN_F64 duration_s, DN_AgeUnit units) +{ + DN_U64 duration_ms = DN_Cast(DN_U64)(duration_s * 1000.0); + DN_Str8x128 result = DN_AgeStr8FromMsU64(duration_ms, units); + return result; +} + +DN_API int DN_IsLeapYear(int year) +{ + if (year % 4 != 0) + return 0; + if (year % 100 != 0) + return 1; + return (year % 400 == 0); +} + +DN_API bool DN_DateIsValid(DN_Date date) +{ + if (date.year < 1970) + return false; + if (date.month <= 0 || date.month >= 13) + return false; + if (date.day <= 0 || date.day >= 32) + return false; + if (date.hour >= 24) + return false; + if (date.minutes >= 60) + return false; + if (date.seconds >= 60) + return false; + return true; +} + +DN_API DN_Date DN_DateFromUnixTimeMs(DN_USize unix_ts_ms) +{ + DN_Date result = {}; + DN_USize ms = unix_ts_ms % 1000; + DN_USize total_seconds = unix_ts_ms / 1000; + result.milliseconds = (DN_U16)ms; + + DN_USize secs_in_day = total_seconds % 86400; + DN_USize days = total_seconds / 86400; + + result.hour = (DN_U8)(secs_in_day / 3600); + result.minutes = (DN_U8)((secs_in_day % 3600) / 60); + result.seconds = (DN_U8)(secs_in_day % 60); + + DN_U16 days_in_month[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + DN_USize days_left = days; + DN_U16 year = 1970; + + while (days_left >= (DN_IsLeapYear(year) ? 366 : 365)) { + DN_USize days_in_year = DN_IsLeapYear(year) ? 366 : 365; + days_left -= days_in_year; + year++; + } + + DN_U8 month = 1; + for (;;) { + DN_U16 day_count = days_in_month[month]; + if (month == 2 && DN_IsLeapYear(year)) + day_count = 29; + if (days_left < day_count) + break; + days_left -= day_count; + month++; + } + + result.year = year; + result.month = month; + result.day = (DN_U8)days_left + 1; + return result; +} + +DN_API DN_U64 DN_UnixTimeMsFromDate(DN_Date date) +{ + DN_Assert(DN_DateIsValid(date)); + + // Precomputed cumulative days before each month (non-leap year) + const DN_U16 days_before_month[13] = { + 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}; + + DN_U16 y = date.year; + DN_U8 m = date.month; + DN_U8 d = date.day; + + DN_U32 days = d - 1; // day of month starts at 0 internally + days += days_before_month[m - 1]; // Add days from previous months this year + + if (m > 2 && DN_IsLeapYear(y)) // Add February 29 if leap year and month > 2 + days += 1; + + // Add full years from 1970 to y-1 + for (DN_U16 year = 1970; year < y; ++year) + days += DN_IsLeapYear(year) ? 366 : 365; + + // Convert to seconds + DN_U64 seconds = DN_Cast(DN_U64)days * 86400ULL; + seconds += DN_Cast(DN_U64)date.hour * 3600ULL; + seconds += DN_Cast(DN_U64)date.minutes * 60ULL; + seconds += DN_Cast(DN_U64)date.seconds; + DN_U64 result = seconds * 1000ULL + date.milliseconds; + return result; +} + +DN_API DN_Str8 DN_Str8FromByteType(DN_ByteType type) +{ + DN_Str8 result = DN_Str8Lit(""); + switch (type) { + case DN_ByteType_B: result = DN_Str8Lit("B"); break; + case DN_ByteType_KiB: result = DN_Str8Lit("KiB"); break; + case DN_ByteType_MiB: result = DN_Str8Lit("MiB"); break; + case DN_ByteType_GiB: result = DN_Str8Lit("GiB"); break; + case DN_ByteType_TiB: result = DN_Str8Lit("TiB"); break; + case DN_ByteType_Count: result = DN_Str8Lit(""); break; + case DN_ByteType_Auto: result = DN_Str8Lit(""); break; + } + return result; +} + +DN_API DN_ByteCount DN_ByteCountFromU64(DN_U64 bytes, DN_ByteType type) +{ + DN_Assert(type != DN_ByteType_Count); + DN_ByteCount result = {}; + result.bytes = DN_Cast(DN_F64) bytes; + if (type == DN_ByteType_Auto) + for (; result.type < DN_ByteType_Count && result.bytes >= 1024.0; result.type = DN_Cast(DN_ByteType)(DN_Cast(DN_USize) result.type + 1)) + result.bytes /= 1024.0; + else + for (; result.type < type; result.type = DN_Cast(DN_ByteType)(DN_Cast(DN_USize) result.type + 1)) + result.bytes /= 1024.0; + result.suffix = DN_Str8FromByteType(result.type); + return result; +} + +DN_API DN_Str8x32 DN_Str8x32FromByteCountU64(DN_U64 bytes, DN_ByteType type) +{ + DN_ByteCount byte_count = DN_ByteCountFromU64(bytes, type); + DN_Str8x32 result = DN_Str8x32FromFmt("%.2f%.*s", byte_count.bytes, DN_Str8PrintFmt(byte_count.suffix)); + return result; +} + +DN_API DN_Profiler DN_ProfilerInit(DN_ProfilerAnchor *anchors, DN_USize count, DN_USize anchors_per_frame, DN_ProfilerTSCNowFunc *tsc_now, DN_U64 tsc_frequency) +{ + DN_Profiler result = {}; + result.anchors = anchors; + result.anchors_count = count; + result.anchors_per_frame = anchors_per_frame; + result.tsc_now = tsc_now; + result.tsc_frequency = tsc_frequency; + + DN_AssertF(result.tsc_frequency != 0, + "You must set this to the frequency of the timestamp counter function (TSC) (e.g. how " + "many ticks occur between timestamps). We use this to determine the duration between " + "each zone's recorded TSC. For example if the 'tsc_now' was set to Window's " + "QueryPerformanceCounter then 'tsc_frequency' would be set to the value of " + "QueryPerformanceFrequency which is typically 10mhz (e.g. The duration between two " + "consecutive TSC's is 10mhz)." + "" + "Hence frequency can't be zero otherwise it's a divide by 0. If you don't have a TSC " + "function and pass in null, the profiler defaults to rdtsc() and you must measure the " + "frequency of rdtsc yourself. The reason for this is that measuring rdtsc requires " + "having some alternate timing mechanism to measure the duration between the TSCs " + "provided by rdtsc and this profiler makes no assumption about what timing primitives " + "are available other than rdtsc which is a CPU builtin available on basically all " + "platforms or have an equivalent (e.g. __builtin_readcyclecounter)" + "" + "This codebase provides DN_OS_EstimateTSCPerSecond() as an example of how to that for " + "convenience and is available if compiling with the OS layer. Some platforms like " + "Emscripten don't support rdtsc() so you should use an alternative method like " + "emscripten_get_now() or clock_gettime with CLOCK_MONOTONIC."); + return result; +} + +DN_API DN_USize DN_ProfilerFrameCount(DN_Profiler const *profiler) +{ + DN_USize result = profiler ? profiler->anchors_count / profiler->anchors_per_frame : 0; + return result; +} + +DN_API DN_ProfilerAnchorArray DN_ProfilerFrameAnchorsFromIndex(DN_Profiler *profiler, DN_USize frame_index) +{ + DN_ProfilerAnchorArray result = {}; + DN_USize anchor_offset = frame_index * profiler->anchors_per_frame; + result.data = profiler->anchors + anchor_offset; + result.count = profiler->anchors_per_frame; + return result; +} + +DN_API DN_ProfilerAnchorArray DN_ProfilerFrameAnchors(DN_Profiler *profiler) +{ + DN_ProfilerAnchorArray result = DN_ProfilerFrameAnchorsFromIndex(profiler, profiler->frame_index); + return result; +} + +DN_API DN_ProfilerZone DN_ProfilerBeginZone(DN_Profiler *profiler, DN_Str8 name, DN_U16 anchor_index) +{ + DN_ProfilerZone result = {}; + if (!profiler || profiler->paused) + return result; + + if (anchor_index != 0) { + DN_AssertF(profiler->frame_zone.profiler, "DN_ProfilerNewFrame() must be called before calling BeginZone"); + } + DN_Assert(anchor_index < profiler->anchors_per_frame); + DN_ProfilerAnchor *anchor = DN_ProfilerFrameAnchors(profiler).data + anchor_index; + anchor->name = name; + + // TODO: We need per-thread-local-storage profiler so that we can use these apis + // across threads. For now, we let them overwrite each other but this is not tenable. + #if 0 + if (anchor->name.size && anchor->name != name) + DN_AssertF(name == anchor->name, "Potentially overwriting a zone by accident? Anchor is '%.*s', name is '%.*s'", DN_Str8PrintFmt(anchor->name), DN_Str8PrintFmt(name)); + #endif + + result.profiler = profiler; + result.begin_tsc = profiler->tsc_now ? profiler->tsc_now() : DN_CPUGetTSC(); + result.anchor_index = anchor_index; + result.parent_zone = profiler->parent_zone; + result.elapsed_tsc_at_zone_start = anchor->tsc_inclusive; + profiler->parent_zone = anchor_index; + return result; +} + +DN_API void DN_ProfilerEndZone(DN_ProfilerZone zone) +{ + DN_Profiler *profiler = zone.profiler; + if (!profiler || profiler->paused) + return; + + DN_Assert(zone.anchor_index < profiler->anchors_per_frame); + DN_Assert(zone.parent_zone < profiler->anchors_per_frame); + + DN_ProfilerAnchorArray array = DN_ProfilerFrameAnchors(profiler); + DN_ProfilerAnchor *anchor = array.data + zone.anchor_index; + DN_U64 tsc_now = profiler->tsc_now ? profiler->tsc_now() : DN_CPUGetTSC(); + DN_U64 elapsed_tsc = tsc_now - zone.begin_tsc; + + // NOTE: We snap the elapsed TSC at the zone start and overwrite every time we end zones. If we + // nest zones, the nested zones will clobber the inclusive timestamp with their values. + // This is fine, as long as all the zones and begun and ended correctly, when the top-most zone + // in the stack ends, it will overwrite the TSC with the elapsed time overall for just that top + // most function, unclobbering the elapsed time sitting in the anchor. + anchor->tsc_inclusive = zone.elapsed_tsc_at_zone_start + elapsed_tsc; + anchor->tsc_exclusive += elapsed_tsc; + anchor->hit_count++; + + if (zone.parent_zone != zone.anchor_index) { + DN_ProfilerAnchor *parent_anchor = array.data + zone.parent_zone; + parent_anchor->tsc_exclusive -= elapsed_tsc; + } + profiler->parent_zone = zone.parent_zone; +} + +DN_API void DN_ProfilerNewFrame(DN_Profiler *profiler) +{ + if (!profiler || profiler->paused) + return; + + // NOTE: End the frame's zone + DN_ProfilerEndZone(profiler->frame_zone); + DN_ProfilerAnchorArray old_frame_anchors = DN_ProfilerFrameAnchors(profiler); + DN_ProfilerAnchor old_frame_anchor = old_frame_anchors.data[0]; + profiler->frame_avg_tsc = (profiler->frame_avg_tsc + old_frame_anchor.tsc_inclusive) / 2.f; + + // NOTE: Bump to the next frame + DN_USize frame_count = profiler->anchors_count / profiler->anchors_per_frame; + profiler->frame_index = (profiler->frame_index + 1) % frame_count; + + // NOTE: Zero out the anchors + DN_ProfilerAnchorArray next_anchors = DN_ProfilerFrameAnchors(profiler); + DN_Memset(next_anchors.data, 0, sizeof(*profiler->anchors) * next_anchors.count); + + // NOTE: Start the frame's zone + profiler->frame_zone = DN_ProfilerBeginZone(profiler, DN_Str8Lit("Profiler Frame"), 0); +} + +DN_API DN_USize DN_ProfilerFmtAnchor(DN_ProfilerAnchor anchor, DN_U64 tsc_frequency, char *buffer, DN_USize count) +{ + DN_USize result = 0; + if (!anchor.hit_count) + return result; + + DN_U64 tsc_exclusive = anchor.tsc_exclusive; + DN_U64 tsc_inclusive = anchor.tsc_inclusive; + DN_F64 tsc_exclusive_milliseconds = tsc_exclusive * 1000 / DN_Cast(DN_F64) tsc_frequency; + if (tsc_exclusive == tsc_inclusive) { + DN_FmtAppend(buffer, &result, count, "%.*s[%u]: %.1fms", DN_Str8PrintFmt(anchor.name), anchor.hit_count, tsc_exclusive_milliseconds); + } else { + DN_F64 tsc_inclusive_milliseconds = tsc_inclusive * 1000 / DN_Cast(DN_F64) tsc_frequency; + DN_FmtAppend(buffer, &result, count, "%.*s[%u]: %.1f/%.1fms", DN_Str8PrintFmt(anchor.name), anchor.hit_count, tsc_exclusive_milliseconds, tsc_inclusive_milliseconds); + } + return result; +} + +DN_API DN_Str8 DN_ProfilerFmtAnchorStr8(DN_ProfilerAnchor anchor, DN_U64 tsc_frequency, DN_Arena *arena) +{ + DN_Str8 result = {}; + DN_USize size_req = DN_ProfilerFmtAnchor(anchor, tsc_frequency, nullptr, 0); + if (size_req) { + result = DN_Str8AllocArena(size_req, DN_ZMem_No, arena); + DN_ProfilerFmtAnchor(anchor, tsc_frequency, result.data, result.size + 1); + } + return result; +} + +DN_API void DN_ProfilerFmtToStdout(DN_Profiler *profiler) +{ + if (!profiler || profiler->frame_index == 0) + return; + + DN_USize frame_index = profiler->frame_index - 1; + DN_ProfilerAnchor *anchors = profiler->anchors + (frame_index * profiler->anchors_per_frame); + for (DN_USize index = 1; index < profiler->anchors_per_frame; index++) { + char buffer[2048]; + buffer[0] = 0; + DN_USize fmt_len = DN_ProfilerFmtAnchor(anchors[index], profiler->tsc_frequency, buffer, DN_ArrayCountU(buffer)); + DN_Str8 msg = DN_Str8FromPtr(buffer, fmt_len); + DN_OS_PrintOutLnF("%.*s", DN_Str8PrintFmt(msg)); + } +} + +DN_API DN_F64 DN_ProfilerSecFromTSC(DN_Profiler *profiler, DN_U64 duration_tsc) +{ + DN_F64 result = DN_Cast(DN_F64)duration_tsc / profiler->tsc_frequency; + return result; +} + +DN_API DN_F64 DN_ProfilerMsFromTSC(DN_Profiler *profiler, DN_U64 duration_tsc) +{ + DN_F64 result = DN_Cast(DN_F64)duration_tsc / profiler->tsc_frequency * 1000.0; + return result; +} + +static void DN_QSortSetElem_(void *array, DN_USize elem_size, DN_USize dest_index, DN_USize src_index) +{ + char *src = DN_Cast(char *) array + (src_index * elem_size); + char *dest = DN_Cast(char *) array + (dest_index * elem_size); + DN_Memcpy(dest, src, elem_size); +} + +static void DN_QSortSwapElems_(void *array, DN_USize elem_size, DN_USize lhs_index, DN_USize rhs_index) +{ + if (lhs_index == rhs_index) + return; + + char temp_buffer[512]; + bool use_buffer = elem_size <= DN_ArrayCountU(temp_buffer); + DN_TCScratch scratch = {}; + char *temp = {}; + if (use_buffer) { + temp = temp_buffer; + } else { + scratch = DN_TCScratchBeginArena(nullptr, 0); + temp = DN_ArenaNewArray(&scratch.arena, char, elem_size, DN_ZMem_No); + } + + char *lhs = DN_Cast(char *) array + (lhs_index * elem_size); + char *rhs = DN_Cast(char *) array + (rhs_index * elem_size); + DN_Memcpy(temp, lhs, elem_size); + DN_Memcpy(lhs, rhs, elem_size); + DN_Memcpy(rhs, temp, elem_size); + + if (!use_buffer) + DN_TCScratchEnd(&scratch); +} + +static void DN_QSortInsertion_(void *array, DN_USize array_size, DN_USize elem_size, void *user_context, DN_QSortCompareFunc *compare) +{ + char temp_buffer[512]; + bool use_buffer = elem_size <= DN_ArrayCountU(temp_buffer); + DN_TCScratch scratch = {}; + char *temp = {}; + if (use_buffer) { + temp = temp_buffer; + } else { + scratch = DN_TCScratchBeginArena(nullptr, 0); + temp = DN_ArenaNewArray(&scratch.arena, char, elem_size, DN_ZMem_No); + } + + DN_U8 *array_u8 = DN_Cast(DN_U8 *)array; + for (DN_USize item_to_insert_index = 1; item_to_insert_index < array_size; item_to_insert_index++) { + for (DN_USize index = 0; index < item_to_insert_index; index++) { + DN_U8 *lhs = array_u8 + (index * elem_size); + DN_U8 *rhs = array_u8 + (item_to_insert_index * elem_size); + if (compare(lhs, rhs, user_context)) + continue; + + DN_Memcpy(temp, rhs, elem_size); + for (DN_USize i = item_to_insert_index; i > index; i--) + DN_QSortSetElem_(array, elem_size, i, i - 1); + DN_Memcpy(lhs, temp, elem_size); + break; + } + } + + if (!use_buffer) + DN_TCScratchEnd(&scratch); +} + +DN_API void DN_QSort(void *array, DN_USize array_size, DN_USize elem_size, void *user_context, DN_QSortCompareFunc *compare) +{ + if (!array || array_size <= 1 || elem_size == 0 || !compare) + return; + + // NOTE: Insertion Sort, under 24->32 is an optimal amount + DN_U8 *array_u8 = DN_Cast(DN_U8 *)array; + DN_USize const QSORT_THRESHOLD = 24; + if (array_size < QSORT_THRESHOLD) { + DN_QSortInsertion_(array, array_size, elem_size, user_context, compare); + return; + } + + // NOTE: Quick sort, under 24->32 is an optimal amount + DN_USize last_index = array_size - 1; + DN_USize pivot_index = array_size / 2; + DN_USize partition_index = 0; + DN_USize start_index = 0; + + // Swap pivot with last index, so pivot is always at the end of the array. + // This makes logic much simpler. + DN_QSortSwapElems_(array, elem_size, last_index, pivot_index); + pivot_index = last_index; + + // 4^, 8, 7, 5, 2, 3, 6 + if (compare(array_u8 + (start_index * elem_size), array_u8 + (pivot_index * elem_size), user_context)) + partition_index++; + start_index++; + + // 4, |8, 7, 5^, 2, 3, 6* + // 4, 5, |7, 8, 2^, 3, 6* + // 4, 5, 2, |8, 7, ^3, 6* + // 4, 5, 2, 3, |7, 8, ^6* + for (DN_USize index = start_index; index < last_index; index++) { + if (compare(array_u8 + (index * elem_size), array_u8 + (pivot_index * elem_size), user_context)) { + DN_QSortSwapElems_(array, elem_size, partition_index, index); + partition_index++; + } + } + + // Move pivot to right of partition + // 4, 5, 2, 3, |6, 8, ^7* + DN_QSortSwapElems_(array, elem_size, partition_index, pivot_index); + DN_QSort(array_u8, partition_index, elem_size, user_context, compare); + + // Skip the value at partion index since that is guaranteed to be sorted. + // 4, 5, 2, 3, (x), 8, 7 + DN_USize one_after_partition_index = partition_index + 1; + DN_QSort(array_u8 + (one_after_partition_index * elem_size), (array_size - one_after_partition_index), elem_size, user_context, compare); +} + +DN_API bool DN_QSortCompareStr8NaturalAsc(void const* lhs, void const *rhs, void *user_context) +{ + DN_Str8EqCase eq_case = *DN_Cast(DN_Str8EqCase *) user_context; + DN_Str8 lhs_str8 = *DN_Cast(DN_Str8 *) lhs; + DN_Str8 rhs_str8 = *DN_Cast(DN_Str8 *) rhs; + bool result = DN_Str8CompareNatural(lhs_str8, rhs_str8, eq_case) < 0; + return result; +} + +DN_API bool DN_QSortCompareStr8NaturalDesc(void const* lhs, void const *rhs, void *user_context) +{ + DN_Str8EqCase eq_case = *DN_Cast(DN_Str8EqCase *) user_context; + DN_Str8 lhs_str8 = *DN_Cast(DN_Str8 *) lhs; + DN_Str8 rhs_str8 = *DN_Cast(DN_Str8 *) rhs; + bool result = DN_Str8CompareNatural(lhs_str8, rhs_str8, eq_case) > 0; + return result; +} + +DN_API bool DN_QSortCompareStr8LexicographicAsc(void const* lhs, void const *rhs, void *user_context) +{ + DN_Str8EqCase eq_case = *DN_Cast(DN_Str8EqCase *) user_context; + DN_Str8 lhs_str8 = *DN_Cast(DN_Str8 *) lhs; + DN_Str8 rhs_str8 = *DN_Cast(DN_Str8 *) rhs; + bool result = DN_Str8CompareLexicographic(lhs_str8, rhs_str8, eq_case) < 0; + return result; +} + +DN_API bool DN_QSortCompareStr8LexicographicDesc(void const* lhs, void const *rhs, void *user_context) +{ + DN_Str8EqCase eq_case = *DN_Cast(DN_Str8EqCase *) user_context; + DN_Str8 lhs_str8 = *DN_Cast(DN_Str8 *) lhs; + DN_Str8 rhs_str8 = *DN_Cast(DN_Str8 *) rhs; + bool result = DN_Str8CompareLexicographic(lhs_str8, rhs_str8, eq_case) > 0; + return result; +} + +DN_API bool DN_QSortCompareBytesLT(void const* lhs, void const *rhs, void *user_context) +{ + DN_USize elem_size = *DN_Cast(DN_USize *)user_context; + bool result = DN_Memcmp(lhs, rhs, elem_size) < 0; + return result; +} + +DN_API bool DN_QSortCompareBytesGT(void const* lhs, void const *rhs, void *user_context) +{ + DN_USize elem_size = *DN_Cast(DN_USize *)user_context; + bool result = DN_Memcmp(lhs, rhs, elem_size) > 0; + return result; +} + +DN_API void DN_QSortBytesLT(void *array, DN_USize array_size, DN_USize elem_size) +{ + DN_QSort(array, array_size, elem_size, &elem_size, DN_QSortCompareBytesLT); +} + +DN_API void DN_QSortBytesGT(void *array, DN_USize array_size, DN_USize elem_size) +{ + DN_QSort(array, array_size, elem_size, &elem_size, DN_QSortCompareBytesGT); +} + +DN_API void DN_QSortStr8NaturalAsc(DN_Str8 *array, DN_USize array_size, DN_Str8EqCase eq_case) +{ + DN_QSort(array, array_size, sizeof(*array), /*user_context=*/ &eq_case, DN_QSortCompareStr8NaturalAsc); +} + +DN_API void DN_QSortStr8NaturalDesc(DN_Str8 *array, DN_USize array_size, DN_Str8EqCase eq_case) +{ + DN_QSort(array, array_size, sizeof(*array), /*user_context=*/ &eq_case, DN_QSortCompareStr8NaturalDesc); +} + +DN_API void DN_QSortStr8LexicographicAsc(DN_Str8 *array, DN_USize array_size, DN_Str8EqCase eq_case) +{ + DN_QSort(array, array_size, sizeof(*array), /*user_context=*/ &eq_case, DN_QSortCompareStr8LexicographicAsc); +} + +DN_API void DN_QSortStr8LexicographicDesc(DN_Str8 *array, DN_USize array_size, DN_Str8EqCase eq_case) +{ + DN_QSort(array, array_size, sizeof(*array), /*user_context=*/ &eq_case, DN_QSortCompareStr8LexicographicDesc); +} + + +#define DN_PCG_DEFAULT_MULTIPLIER_64 6364136223846793005ULL +#define DN_PCG_DEFAULT_INCREMENT_64 1442695040888963407ULL +DN_API DN_PCG32 DN_PCG32Init(DN_U64 seed) +{ + DN_PCG32 result = {}; + DN_PCG32Next(&result); + result.state += seed; + DN_PCG32Next(&result); + return result; +} + +DN_API DN_U32 DN_PCG32Next(DN_PCG32 *rng) +{ + DN_U64 state = rng->state; + rng->state = state * DN_PCG_DEFAULT_MULTIPLIER_64 + DN_PCG_DEFAULT_INCREMENT_64; + + // XSH-RR + DN_U32 value = (DN_U32)((state ^ (state >> 18)) >> 27); + int rot = state >> 59; + return rot ? (value >> rot) | (value << (32 - rot)) : value; +} + +DN_API DN_U64 DN_PCG32Next64(DN_PCG32 *rng) +{ + DN_U64 value = DN_PCG32Next(rng); + value <<= 32; + value |= DN_PCG32Next(rng); + return value; +} + +DN_API DN_U32 DN_PCG32Range(DN_PCG32 *rng, DN_U32 low, DN_U32 high) +{ + DN_U32 bound = high - low; + DN_U32 threshold = -(DN_I32)bound % bound; + + for (;;) { + DN_U32 r = DN_PCG32Next(rng); + if (r >= threshold) + return low + (r % bound); + } +} + +DN_API DN_F32 DN_PCG32NextF32(DN_PCG32 *rng) +{ + DN_U32 x = DN_PCG32Next(rng); + return (DN_F32)(DN_I32)(x >> 8) * 0x1.0p-24f; +} + +DN_API DN_F64 DN_PCG32NextF64(DN_PCG32 *rng) +{ + DN_U64 x = DN_PCG32Next64(rng); + return (DN_F64)(DN_I64)(x >> 11) * 0x1.0p-53; +} + +DN_API void DN_PCG32Advance(DN_PCG32 *rng, DN_U64 delta) +{ + DN_U64 cur_mult = DN_PCG_DEFAULT_MULTIPLIER_64; + DN_U64 cur_plus = DN_PCG_DEFAULT_INCREMENT_64; + + DN_U64 acc_mult = 1; + DN_U64 acc_plus = 0; + + while (delta != 0) { + if (delta & 1) { + acc_mult *= cur_mult; + acc_plus = acc_plus * cur_mult + cur_plus; + } + cur_plus = (cur_mult + 1) * cur_plus; + cur_mult *= cur_mult; + delta >>= 1; + } + + rng->state = acc_mult * rng->state + acc_plus; +} + +// Default values recommended by: http://isthe.com/chongo/tech/comp/fnv/ +DN_API DN_U32 DN_FNV1AHashU32FromBytes(void const *bytes, DN_USize size, DN_U32 hash) +{ + auto buffer = DN_Cast(DN_U8 const *)bytes; + for (DN_USize i = 0; i < size; i++) + hash = (buffer[i] ^ hash) * 16777619 /*FNV Prime*/; + return hash; +} + +DN_API DN_U64 DN_FNV1AHashU64FromBytes(void const *bytes, DN_USize size, DN_U64 hash) +{ + auto buffer = DN_Cast(DN_U8 const *)bytes; + for (DN_USize i = 0; i < size; i++) + hash = (buffer[i] ^ hash) * 1099511628211 /*FNV Prime*/; + return hash; +} + +#if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL) + #define DN_MMH3_ROTL32(x, y) _rotl(x, y) + #define DN_MMH3_ROTL64(x, y) _rotl64(x, y) +#else + #define DN_MMH3_ROTL32(x, y) ((x) << (y)) | ((x) >> (32 - (y))) + #define DN_MMH3_ROTL64(x, y) ((x) << (y)) | ((x) >> (64 - (y))) +#endif + +//----------------------------------------------------------------------------- +// Block read - if your platform needs to do endian-swapping or can only +// handle aligned reads, do the conversion here +DN_FORCE_INLINE DN_U32 DN_MurmurHash3GetBlock32_(DN_U32 const *p, int i) +{ + return p[i]; +} + +DN_FORCE_INLINE DN_U64 DN_MurmurHash3GetBlock64_(DN_U64 const *p, int i) +{ + return p[i]; +} + +//----------------------------------------------------------------------------- +// Finalization mix - force all bits of a hash block to avalanche + +DN_FORCE_INLINE DN_U32 DN_MurmurHash3FMix32_(DN_U32 h) +{ + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + h ^= h >> 16; + return h; +} + +DN_FORCE_INLINE DN_U64 DN_MurmurHash3FMix64_(DN_U64 k) +{ + k ^= k >> 33; + k *= 0xff51afd7ed558ccd; + k ^= k >> 33; + k *= 0xc4ceb9fe1a85ec53; + k ^= k >> 33; + return k; +} + +DN_API DN_U32 DN_MurmurHash3HashU128FromBytesX86(void const *bytes, int len, DN_U32 seed) +{ + const DN_U8 *data = (const DN_U8 *)bytes; + const int nblocks = len / 4; + + DN_U32 h1 = seed; + + const DN_U32 c1 = 0xcc9e2d51; + const DN_U32 c2 = 0x1b873593; + + //---------- + // body + + const DN_U32 *blocks = (const DN_U32 *)(data + nblocks * 4); + + for (int i = -nblocks; i; i++) + { + DN_U32 k1 = DN_MurmurHash3GetBlock32_(blocks, i); + + k1 *= c1; + k1 = DN_MMH3_ROTL32(k1, 15); + k1 *= c2; + + h1 ^= k1; + h1 = DN_MMH3_ROTL32(h1, 13); + h1 = h1 * 5 + 0xe6546b64; + } + + //---------- + // tail + + const DN_U8 *tail = (const DN_U8 *)(data + nblocks * 4); + + DN_U32 k1 = 0; + + switch (len & 3) + { + case 3: + k1 ^= tail[2] << 16; + case 2: + k1 ^= tail[1] << 8; + case 1: + k1 ^= tail[0]; + k1 *= c1; + k1 = DN_MMH3_ROTL32(k1, 15); + k1 *= c2; + h1 ^= k1; + }; + + //---------- + // finalization + + h1 ^= len; + + h1 = DN_MurmurHash3FMix32_(h1); + + return h1; +} + +DN_API DN_MurmurHash3 DN_MurmurHash3HashU128FromBytesX64(void const *bytes, int len, DN_U32 seed) +{ + const DN_U8 *data = (const DN_U8 *)bytes; + const int nblocks = len / 16; + + DN_U64 h1 = seed; + DN_U64 h2 = seed; + + const DN_U64 c1 = 0x87c37b91114253d5; + const DN_U64 c2 = 0x4cf5ad432745937f; + + //---------- + // body + + const DN_U64 *blocks = (const DN_U64 *)(data); + + for (int i = 0; i < nblocks; i++) + { + DN_U64 k1 = DN_MurmurHash3GetBlock64_(blocks, i * 2 + 0); + DN_U64 k2 = DN_MurmurHash3GetBlock64_(blocks, i * 2 + 1); + + k1 *= c1; + k1 = DN_MMH3_ROTL64(k1, 31); + k1 *= c2; + h1 ^= k1; + + h1 = DN_MMH3_ROTL64(h1, 27); + h1 += h2; + h1 = h1 * 5 + 0x52dce729; + + k2 *= c2; + k2 = DN_MMH3_ROTL64(k2, 33); + k2 *= c1; + h2 ^= k2; + + h2 = DN_MMH3_ROTL64(h2, 31); + h2 += h1; + h2 = h2 * 5 + 0x38495ab5; + } + + //---------- + // tail + + const DN_U8 *tail = (const DN_U8 *)(data + nblocks * 16); + + DN_U64 k1 = 0; + DN_U64 k2 = 0; + + switch (len & 15) + { + case 15: + k2 ^= ((DN_U64)tail[14]) << 48; + case 14: + k2 ^= ((DN_U64)tail[13]) << 40; + case 13: + k2 ^= ((DN_U64)tail[12]) << 32; + case 12: + k2 ^= ((DN_U64)tail[11]) << 24; + case 11: + k2 ^= ((DN_U64)tail[10]) << 16; + case 10: + k2 ^= ((DN_U64)tail[9]) << 8; + case 9: + k2 ^= ((DN_U64)tail[8]) << 0; + k2 *= c2; + k2 = DN_MMH3_ROTL64(k2, 33); + k2 *= c1; + h2 ^= k2; + + case 8: + k1 ^= ((DN_U64)tail[7]) << 56; + case 7: + k1 ^= ((DN_U64)tail[6]) << 48; + case 6: + k1 ^= ((DN_U64)tail[5]) << 40; + case 5: + k1 ^= ((DN_U64)tail[4]) << 32; + case 4: + k1 ^= ((DN_U64)tail[3]) << 24; + case 3: + k1 ^= ((DN_U64)tail[2]) << 16; + case 2: + k1 ^= ((DN_U64)tail[1]) << 8; + case 1: + k1 ^= ((DN_U64)tail[0]) << 0; + k1 *= c1; + k1 = DN_MMH3_ROTL64(k1, 31); + k1 *= c2; + h1 ^= k1; + }; + + //---------- + // finalization + + h1 ^= len; + h2 ^= len; + + h1 += h2; + h2 += h1; + + h1 = DN_MurmurHash3FMix64_(h1); + h2 = DN_MurmurHash3FMix64_(h2); + + h1 += h2; + h2 += h1; + + DN_MurmurHash3 result = {}; + result.e[0] = h1; + result.e[1] = h2; + return result; +} + +DN_API DN_U64 DN_MurmurHash3HashU64FromBytesX64(void const *bytes, int len, DN_U32 seed) +{ + DN_MurmurHash3 hash = DN_MurmurHash3HashU128FromBytesX64(bytes, len, seed); + DN_U64 result = hash.e[0]; + return result; +} + +DN_API DN_U32 DN_MurmurHash3HashU32FromBytesX64(void const *bytes, int len, DN_U32 seed) +{ + DN_MurmurHash3 hash = DN_MurmurHash3HashU128FromBytesX64(bytes, len, seed); + DN_U32 result = DN_Cast(DN_U32)hash.e[0]; + return result; +} + +DN_API DN_Str8x32 DN_Str8x32FromANSIColourCodeU8RGB(DN_ANSIColourMode mode, DN_U8 r, DN_U8 g, DN_U8 b) +{ + DN_Str8x32 result = DN_Str8x32FromFmt("\x1b[%d;2;%u;%u;%um", + mode == DN_ANSIColourMode_Fg ? 38 : 48, + r, + g, + b); + return result; +} + +DN_API DN_Str8x32 DN_Str8x32FromANSIColourCodeV3F32RGB255(DN_ANSIColourMode mode, DN_V3F32 rgb_255) +{ + DN_Str8x32 result = DN_Str8x32FromANSIColourCodeU8RGB(mode, DN_Cast(DN_U8)rgb_255.r, DN_Cast(DN_U8)rgb_255.g, DN_Cast(DN_U8)rgb_255.b); + return result; +} + +DN_API DN_Str8x32 DN_Str8x32FromANSIColourCodeU32RGB(DN_ANSIColourMode mode, DN_U32 value) +{ + DN_U8 r = DN_Cast(DN_U8)(value >> 24); + DN_U8 g = DN_Cast(DN_U8)(value >> 16); + DN_U8 b = DN_Cast(DN_U8)(value >> 8); + DN_Str8x32 result = DN_Str8x32FromANSIColourCodeU8RGB(mode, r, g, b); + return result; +} + +DN_API DN_Str8 DN_Str8FromStr8ANSIColourU8RGBArena(DN_ANSIColourMode mode, DN_Str8 str8, DN_U8 r, DN_U8 g, DN_U8 b, DN_Arena *arena) +{ + DN_Str8x32 ansi = DN_Str8x32FromANSIColourCodeU8RGB(mode, r, g, b); + DN_Str8 result = DN_Str8FromFmtArena(arena, "%.*s%.*s%s", DN_Str8PrintFmt(ansi), DN_Str8PrintFmt(str8), DN_ANSICodeResetLit); + return result; +} + +DN_API DN_Str8 DN_Str8FromStr8ANSIColourV3F32RGB255Arena(DN_ANSIColourMode mode, DN_Str8 str8, DN_V3F32 rgb_255, DN_Arena *arena) +{ + DN_Str8 result = DN_Str8FromStr8ANSIColourU8RGBArena(mode, str8, DN_Cast(DN_U8)rgb_255.r, DN_Cast(DN_U8)rgb_255.g, DN_Cast(DN_U8)rgb_255.b, arena); + return result; +} + +DN_API DN_Str8 DN_Str8ANSIColourU8RGBFromFmtVArena(DN_ANSIColourMode mode, DN_U8 r, DN_U8 g, DN_U8 b, DN_Arena *arena, char const *fmt, va_list args) +{ + DN_TCScratch scratch = DN_TCScratchBeginArena(&arena, 1); + DN_Str8 string = DN_Str8FromFmtVArena(&scratch.arena, fmt, args); + DN_Str8 result = DN_Str8FromStr8ANSIColourU8RGBArena(mode, string, r, g, b, arena); + DN_TCScratchEnd(&scratch); + return result; +} + +DN_API DN_Str8 DN_Str8FromFmtANSIColourU8RGBArena(DN_ANSIColourMode mode, DN_U8 r, DN_U8 g, DN_U8 b, DN_Arena *arena, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8 result = DN_Str8ANSIColourU8RGBFromFmtVArena(mode, r, g, b, arena, fmt, args); + va_end(args); + return result; +} + +DN_API DN_Str8 DN_Str8FromFmtANSIColourV3F32RGB255Arena(DN_ANSIColourMode mode, DN_V3F32 rgb_255, DN_Arena *arena, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8 result = DN_Str8ANSIColourU8RGBFromFmtVArena(mode, DN_Cast(DN_U8)rgb_255.r, DN_Cast(DN_U8)rgb_255.g, DN_Cast(DN_U8)rgb_255.b, arena, fmt, args); + va_end(args); + return result; +} + +DN_API DN_LogPrefixSize DN_LogMakePrefix(DN_LogStyle style, DN_LogTypeParam type, DN_CallSite call_site, DN_LogDate date, char *dest, DN_USize dest_size) +{ + DN_Str8 type_str8 = type.str8; + if (type.is_u32_enum) { + switch (type.u32) { + case DN_LogType_Debug: type_str8 = DN_Str8Lit("DEBUG"); break; + case DN_LogType_Info: type_str8 = DN_Str8Lit("INFO "); break; + case DN_LogType_Warning: type_str8 = DN_Str8Lit("WARN"); break; + case DN_LogType_Error: type_str8 = DN_Str8Lit("ERROR"); break; + case DN_LogType_Count: type_str8 = DN_Str8Lit("BADXX"); break; + } + } + + static DN_USize max_type_length = 0; + max_type_length = DN_Max(max_type_length, type_str8.size); + int type_padding = DN_Cast(int)(max_type_length - type_str8.size); + + DN_Str8x32 colour_esc = {}; + DN_Str8 bold_esc = {}; + DN_Str8 reset_esc = {}; + if (style.colour) { + bold_esc = DN_Str8Lit(DN_ANSICodeBoldLit); + reset_esc = DN_Str8Lit(DN_ANSICodeResetLit); + colour_esc = DN_Str8x32FromANSIColourCodeU8RGB(DN_ANSIColourMode_Fg, style.r, style.g, style.b); + } + + DN_Str8 file_name = DN_Str8FileNameFromPath(call_site.file); + int size = DN_SNPrintF(dest, + DN_Cast(int)dest_size, + "%04u-%02u-%02uT%02u:%02u:%02u" // date + "%.*s" // colour + "%.*s" // bold + " %.*s" // type + "%.*s" // type padding + "%.*s" // reset + " %.*s" // file name + ":%05u " // line number + , + date.year, + date.month, + date.day, + date.hour, + date.minute, + date.second, + DN_Str8PrintFmt(colour_esc), // colour + DN_Str8PrintFmt(bold_esc), // bold + DN_Str8PrintFmt(type_str8), // type + DN_Cast(int) type_padding, + "", // type padding + DN_Str8PrintFmt(reset_esc), // reset + DN_Str8PrintFmt(file_name), // file name + call_site.line); // line number + + static DN_USize max_header_length = 0; + DN_USize size_no_ansi_codes = size - colour_esc.size - reset_esc.size - bold_esc.size; + max_header_length = DN_Max(max_header_length, size_no_ansi_codes); + DN_USize header_padding = max_header_length - size_no_ansi_codes; + + DN_LogPrefixSize result = {}; + result.size = size; + result.padding = header_padding; + return result; +} + +DN_API void DN_LogSetPrintFunc(DN_LogPrintFunc *print_func, void *user_data) +{ + DN_Core *dn = DN_Get(); + dn->print_func = print_func; + dn->print_func_context = user_data; +} + +DN_API void DN_LogPrintFV(DN_LogTypeParam type, DN_CallSite call_site, DN_LogFlags flags, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_Core *dn = DN_Get(); + if (type.is_u32_enum) { + DN_Assert(dn->log_level_to_show_from >= 0); + if (type.u32 < DN_Cast(DN_U32) dn->log_level_to_show_from) + return; + } + DN_LogPrintFunc *func = dn->print_func; + if (func) + func(type, dn->print_func_context, call_site, flags, fmt, args); +} + +DN_API void DN_LogPrintF(DN_LogTypeParam type, DN_CallSite call_site, DN_LogFlags flags, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_LogPrintFV(type, call_site, flags, fmt, args); + va_end(args); +} + +DN_API DN_LogTypeParam DN_LogTypeParamFromType(DN_LogType type) +{ + DN_LogTypeParam result = {}; + result.is_u32_enum = true; + result.u32 = type; + return result; +} + +DN_API DN_F32 DN_F32Lerp(DN_F32 a, DN_F32 t, DN_F32 b) +{ + DN_F32 result = a + ((b - a) * t); + return result; +} + +DN_API DN_F32 DN_F32Floor(DN_F32 val) +{ + DN_I32 val_i32 = DN_Cast(DN_I32) val; + if (val < 0 && val != DN_Cast(DN_F32) val_i32) + val_i32 -= 1; + DN_F32 result = DN_Cast(DN_F32)val_i32; + return result; +} + +DN_API DN_F32 DN_F32Ceil(DN_F32 val) +{ + DN_I32 val_i32 = DN_Cast(DN_I32)(val); + if (val > 0 && val != DN_Cast(DN_F32) val_i32) + val_i32 += 1; + DN_F32 result = DN_Cast(DN_F32) val_i32; + return result; +} + +DN_API DN_F32 DN_F32RoundHalfUp(DN_F32 val) +{ + DN_F32 result = val >= 0 ? DN_F32Floor(val + 0.5f) : DN_F32Ceil(val - 0.5f); + return result; +} + +DN_API bool operator==(DN_V2I32 lhs, DN_V2I32 rhs) +{ + bool result = (lhs.x == rhs.x) && (lhs.y == rhs.y); + return result; +} + +DN_API bool operator!=(DN_V2I32 lhs, DN_V2I32 rhs) +{ + bool result = !(lhs == rhs); + return result; +} + +DN_API bool operator>=(DN_V2I32 lhs, DN_V2I32 rhs) +{ + bool result = (lhs.x >= rhs.x) && (lhs.y >= rhs.y); + return result; +} + +DN_API bool operator<=(DN_V2I32 lhs, DN_V2I32 rhs) +{ + bool result = (lhs.x <= rhs.x) && (lhs.y <= rhs.y); + return result; +} + +DN_API bool operator<(DN_V2I32 lhs, DN_V2I32 rhs) +{ + bool result = (lhs.x < rhs.x) && (lhs.y < rhs.y); + return result; +} + +DN_API bool operator>(DN_V2I32 lhs, DN_V2I32 rhs) +{ + bool result = (lhs.x > rhs.x) && (lhs.y > rhs.y); + return result; +} + +DN_API DN_V2I32 operator-(DN_V2I32 lhs, DN_V2I32 rhs) +{ + DN_V2I32 result = DN_V2I32From2N(lhs.x - rhs.x, lhs.y - rhs.y); + return result; +} + +DN_API DN_V2I32 operator-(DN_V2I32 lhs) +{ + DN_V2I32 result = DN_V2I32From2N(-lhs.x, -lhs.y); + return result; +} + +DN_API DN_V2I32 operator+(DN_V2I32 lhs, DN_V2I32 rhs) +{ + DN_V2I32 result = DN_V2I32From2N(lhs.x + rhs.x, lhs.y + rhs.y); + return result; +} + +DN_API DN_V2I32 operator*(DN_V2I32 lhs, DN_V2I32 rhs) +{ + DN_V2I32 result = DN_V2I32From2N(lhs.x * rhs.x, lhs.y * rhs.y); + return result; +} + +DN_API DN_V2I32 operator*(DN_V2I32 lhs, DN_F32 rhs) +{ + DN_V2I32 result = DN_V2I32From2N(lhs.x * rhs, lhs.y * rhs); + return result; +} + +DN_API DN_V2I32 operator*(DN_V2I32 lhs, DN_I32 rhs) +{ + DN_V2I32 result = DN_V2I32From2N(lhs.x * rhs, lhs.y * rhs); + return result; +} + +DN_API DN_V2I32 operator/(DN_V2I32 lhs, DN_V2I32 rhs) +{ + DN_V2I32 result = DN_V2I32From2N(lhs.x / rhs.x, lhs.y / rhs.y); + return result; +} + +DN_API DN_V2I32 operator/(DN_V2I32 lhs, DN_F32 rhs) +{ + DN_V2I32 result = DN_V2I32From2N(lhs.x / rhs, lhs.y / rhs); + return result; +} + +DN_API DN_V2I32 operator/(DN_V2I32 lhs, DN_I32 rhs) +{ + DN_V2I32 result = DN_V2I32From2N(lhs.x / rhs, lhs.y / rhs); + return result; +} + +DN_API DN_V2I32 &operator*=(DN_V2I32 &lhs, DN_V2I32 rhs) +{ + lhs = lhs * rhs; + return lhs; +} + +DN_API DN_V2I32 &operator*=(DN_V2I32 &lhs, DN_F32 rhs) +{ + lhs = lhs * rhs; + return lhs; +} + +DN_API DN_V2I32 &operator*=(DN_V2I32 &lhs, DN_I32 rhs) +{ + lhs = lhs * rhs; + return lhs; +} + +DN_API DN_V2I32 &operator/=(DN_V2I32 &lhs, DN_V2I32 rhs) +{ + lhs = lhs / rhs; + return lhs; +} + +DN_API DN_V2I32 &operator/=(DN_V2I32 &lhs, DN_F32 rhs) +{ + lhs = lhs / rhs; + return lhs; +} + +DN_API DN_V2I32 &operator/=(DN_V2I32 &lhs, DN_I32 rhs) +{ + lhs = lhs / rhs; + return lhs; +} + +DN_API DN_V2I32 &operator-=(DN_V2I32 &lhs, DN_V2I32 rhs) +{ + lhs = lhs - rhs; + return lhs; +} + +DN_API DN_V2I32 &operator+=(DN_V2I32 &lhs, DN_V2I32 rhs) +{ + lhs = lhs + rhs; + return lhs; +} + +DN_API DN_V2I32 DN_V2I32Min(DN_V2I32 a, DN_V2I32 b) +{ + DN_V2I32 result = DN_V2I32From2N(DN_Min(a.x, b.x), DN_Min(a.y, b.y)); + return result; +} + +DN_API DN_V2I32 DN_V2I32Max(DN_V2I32 a, DN_V2I32 b) +{ + DN_V2I32 result = DN_V2I32From2N(DN_Max(a.x, b.x), DN_Max(a.y, b.y)); + return result; +} + +DN_API DN_V2I32 DN_V2I32Abs(DN_V2I32 a) +{ + DN_V2I32 result = DN_V2I32From2N(DN_Abs(a.x), DN_Abs(a.y)); + return result; +} + +DN_API bool operator!=(DN_V2U16 lhs, DN_V2U16 rhs) +{ + bool result = !(lhs == rhs); + return result; +} + +DN_API bool operator==(DN_V2U16 lhs, DN_V2U16 rhs) +{ + bool result = (lhs.x == rhs.x) && (lhs.y == rhs.y); + return result; +} + +DN_API bool operator>=(DN_V2U16 lhs, DN_V2U16 rhs) +{ + bool result = (lhs.x >= rhs.x) && (lhs.y >= rhs.y); + return result; +} + +DN_API bool operator<=(DN_V2U16 lhs, DN_V2U16 rhs) +{ + bool result = (lhs.x <= rhs.x) && (lhs.y <= rhs.y); + return result; +} + +DN_API bool operator<(DN_V2U16 lhs, DN_V2U16 rhs) +{ + bool result = (lhs.x < rhs.x) && (lhs.y < rhs.y); + return result; +} + +DN_API bool operator>(DN_V2U16 lhs, DN_V2U16 rhs) +{ + bool result = (lhs.x > rhs.x) && (lhs.y > rhs.y); + return result; +} + +DN_API DN_V2U16 operator-(DN_V2U16 lhs, DN_V2U16 rhs) +{ + DN_V2U16 result = DN_V2U16From2N(lhs.x - rhs.x, lhs.y - rhs.y); + return result; +} + +DN_API DN_V2U16 operator+(DN_V2U16 lhs, DN_V2U16 rhs) +{ + DN_V2U16 result = DN_V2U16From2N(lhs.x + rhs.x, lhs.y + rhs.y); + return result; +} + +DN_API DN_V2U16 operator*(DN_V2U16 lhs, DN_V2U16 rhs) +{ + DN_V2U16 result = DN_V2U16From2N(lhs.x * rhs.x, lhs.y * rhs.y); + return result; +} + +DN_API DN_V2U16 operator*(DN_V2U16 lhs, DN_F32 rhs) +{ + DN_V2U16 result = DN_V2U16From2N(lhs.x * rhs, lhs.y * rhs); + return result; +} + +DN_API DN_V2U16 operator*(DN_V2U16 lhs, DN_I32 rhs) +{ + DN_V2U16 result = DN_V2U16From2N(lhs.x * rhs, lhs.y * rhs); + return result; +} + +DN_API DN_V2U16 operator/(DN_V2U16 lhs, DN_V2U16 rhs) +{ + DN_V2U16 result = DN_V2U16From2N(lhs.x / rhs.x, lhs.y / rhs.y); + return result; +} + +DN_API DN_V2U16 operator/(DN_V2U16 lhs, DN_F32 rhs) +{ + DN_V2U16 result = DN_V2U16From2N(lhs.x / rhs, lhs.y / rhs); + return result; +} + +DN_API DN_V2U16 operator/(DN_V2U16 lhs, DN_I32 rhs) +{ + DN_V2U16 result = DN_V2U16From2N(lhs.x / rhs, lhs.y / rhs); + return result; +} + +DN_API DN_V2U16 &operator*=(DN_V2U16 &lhs, DN_V2U16 rhs) +{ + lhs = lhs * rhs; + return lhs; +} + +DN_API DN_V2U16 &operator*=(DN_V2U16 &lhs, DN_F32 rhs) +{ + lhs = lhs * rhs; + return lhs; +} + +DN_API DN_V2U16 &operator*=(DN_V2U16 &lhs, DN_I32 rhs) +{ + lhs = lhs * rhs; + return lhs; +} + +DN_API DN_V2U16 &operator/=(DN_V2U16 &lhs, DN_V2U16 rhs) +{ + lhs = lhs / rhs; + return lhs; +} + +DN_API DN_V2U16 &operator/=(DN_V2U16 &lhs, DN_F32 rhs) +{ + lhs = lhs / rhs; + return lhs; +} + +DN_API DN_V2U16 &operator/=(DN_V2U16 &lhs, DN_I32 rhs) +{ + lhs = lhs / rhs; + return lhs; +} + +DN_API DN_V2U16 &operator-=(DN_V2U16 &lhs, DN_V2U16 rhs) +{ + lhs = lhs - rhs; + return lhs; +} + +DN_API DN_V2U16 &operator+=(DN_V2U16 &lhs, DN_V2U16 rhs) +{ + lhs = lhs + rhs; + return lhs; +} + +DN_API DN_V2F32 DN_V2F32Lerp(DN_V2F32 a, DN_F32 t, DN_V2F32 b) +{ + DN_V2F32 result = {}; + result.x = a.x + ((b.x - a.x) * t); + result.y = a.y + ((b.y - a.y) * t); + return result; +} + +DN_API bool operator!=(DN_V2F32 lhs, DN_V2F32 rhs) +{ + bool result = !(lhs == rhs); + return result; +} + +DN_API bool operator==(DN_V2F32 lhs, DN_V2F32 rhs) +{ + bool result = (lhs.x == rhs.x) && (lhs.y == rhs.y); + return result; +} + +DN_API bool operator>=(DN_V2F32 lhs, DN_V2F32 rhs) +{ + bool result = (lhs.x >= rhs.x) && (lhs.y >= rhs.y); + return result; +} + +DN_API bool operator<=(DN_V2F32 lhs, DN_V2F32 rhs) +{ + bool result = (lhs.x <= rhs.x) && (lhs.y <= rhs.y); + return result; +} + +DN_API bool operator<(DN_V2F32 lhs, DN_V2F32 rhs) +{ + bool result = (lhs.x < rhs.x) && (lhs.y < rhs.y); + return result; +} + +DN_API bool operator>(DN_V2F32 lhs, DN_V2F32 rhs) +{ + bool result = (lhs.x > rhs.x) && (lhs.y > rhs.y); + return result; +} + +DN_API DN_V2F32 operator-(DN_V2F32 lhs) +{ + DN_V2F32 result = DN_V2F32From2N(-lhs.x, -lhs.y); + return result; +} + +DN_API DN_V2F32 operator-(DN_V2F32 lhs, DN_V2F32 rhs) +{ + DN_V2F32 result = DN_V2F32From2N(lhs.x - rhs.x, lhs.y - rhs.y); + return result; +} + +DN_API DN_V2F32 operator-(DN_V2F32 lhs, DN_V2I32 rhs) +{ + DN_V2F32 result = DN_V2F32From2N(lhs.x - rhs.x, lhs.y - rhs.y); + return result; +} + +DN_API DN_V2F32 operator-(DN_V2F32 lhs, DN_F32 rhs) +{ + DN_V2F32 result = DN_V2F32From2N(lhs.x - rhs, lhs.y - rhs); + return result; +} + +DN_API DN_V2F32 operator-(DN_V2F32 lhs, DN_I32 rhs) +{ + DN_V2F32 result = DN_V2F32From2N(lhs.x - rhs, lhs.y - rhs); + return result; +} + +DN_API DN_V2F32 operator+(DN_V2F32 lhs, DN_V2F32 rhs) +{ + DN_V2F32 result = DN_V2F32From2N(lhs.x + rhs.x, lhs.y + rhs.y); + return result; +} + +DN_API DN_V2F32 operator+(DN_V2F32 lhs, DN_V2I32 rhs) +{ + DN_V2F32 result = DN_V2F32From2N(lhs.x + rhs.x, lhs.y + rhs.y); + return result; +} + +DN_API DN_V2F32 operator+(DN_V2F32 lhs, DN_F32 rhs) +{ + DN_V2F32 result = DN_V2F32From2N(lhs.x + rhs, lhs.y + rhs); + return result; +} + +DN_API DN_V2F32 operator+(DN_V2F32 lhs, DN_I32 rhs) +{ + DN_V2F32 result = DN_V2F32From2N(lhs.x + rhs, lhs.y + rhs); + return result; +} + +DN_API DN_V2F32 operator*(DN_V2F32 lhs, DN_V2F32 rhs) +{ + DN_V2F32 result = DN_V2F32From2N(lhs.x * rhs.x, lhs.y * rhs.y); + return result; +} + +DN_API DN_V2F32 operator*(DN_V2F32 lhs, DN_V2I32 rhs) +{ + DN_V2F32 result = DN_V2F32From2N(lhs.x * rhs.x, lhs.y * rhs.y); + return result; +} + +DN_API DN_V2F32 operator*(DN_V2F32 lhs, DN_F32 rhs) +{ + DN_V2F32 result = DN_V2F32From2N(lhs.x * rhs, lhs.y * rhs); + return result; +} + +DN_API DN_V2F32 operator*(DN_V2F32 lhs, DN_I32 rhs) +{ + DN_V2F32 result = DN_V2F32From2N(lhs.x * rhs, lhs.y * rhs); + return result; +} + +DN_API DN_V2F32 operator/(DN_V2F32 lhs, DN_V2F32 rhs) +{ + DN_V2F32 result = DN_V2F32From2N(lhs.x / rhs.x, lhs.y / rhs.y); + return result; +} + +DN_API DN_V2F32 operator/(DN_V2F32 lhs, DN_V2I32 rhs) +{ + DN_V2F32 result = DN_V2F32From2N(lhs.x / rhs.x, lhs.y / rhs.y); + return result; +} + +DN_API DN_V2F32 operator/(DN_V2F32 lhs, DN_F32 rhs) +{ + DN_V2F32 result = DN_V2F32From2N(lhs.x / rhs, lhs.y / rhs); + return result; +} + +DN_API DN_V2F32 operator/(DN_V2F32 lhs, DN_I32 rhs) +{ + DN_V2F32 result = DN_V2F32From2N(lhs.x / rhs, lhs.y / rhs); + return result; +} + +DN_API DN_V2F32 &operator*=(DN_V2F32 &lhs, DN_V2F32 rhs) +{ + lhs = lhs * rhs; + return lhs; +} + +DN_API DN_V2F32 &operator*=(DN_V2F32 &lhs, DN_V2I32 rhs) +{ + lhs = lhs * rhs; + return lhs; +} + +DN_API DN_V2F32 &operator*=(DN_V2F32 &lhs, DN_F32 rhs) +{ + lhs = lhs * rhs; + return lhs; +} + +DN_API DN_V2F32 &operator*=(DN_V2F32 &lhs, DN_I32 rhs) +{ + lhs = lhs * rhs; + return lhs; +} + +DN_API DN_V2F32 &operator/=(DN_V2F32 &lhs, DN_V2F32 rhs) +{ + lhs = lhs / rhs; + return lhs; +} + +DN_API DN_V2F32 &operator/=(DN_V2F32 &lhs, DN_V2I32 rhs) +{ + lhs = lhs / rhs; + return lhs; +} + +DN_API DN_V2F32 &operator/=(DN_V2F32 &lhs, DN_F32 rhs) +{ + lhs = lhs / rhs; + return lhs; +} + +DN_API DN_V2F32 &operator/=(DN_V2F32 &lhs, DN_I32 rhs) +{ + lhs = lhs / rhs; + return lhs; +} + +DN_API DN_V2F32 &operator-=(DN_V2F32 &lhs, DN_V2F32 rhs) +{ + lhs = lhs - rhs; + return lhs; +} + +DN_API DN_V2F32 &operator-=(DN_V2F32 &lhs, DN_V2I32 rhs) +{ + lhs = lhs - rhs; + return lhs; +} + +DN_API DN_V2F32 &operator-=(DN_V2F32 &lhs, DN_F32 rhs) +{ + lhs = lhs - rhs; + return lhs; +} + +DN_API DN_V2F32 &operator-=(DN_V2F32 &lhs, DN_I32 rhs) +{ + lhs = lhs - rhs; + return lhs; +} + +DN_API DN_V2F32 &operator+=(DN_V2F32 &lhs, DN_V2F32 rhs) +{ + lhs = lhs + rhs; + return lhs; +} + +DN_API DN_V2F32 &operator+=(DN_V2F32 &lhs, DN_V2I32 rhs) +{ + lhs = lhs + rhs; + return lhs; +} + +DN_API DN_V2F32 &operator+=(DN_V2F32 &lhs, DN_F32 rhs) +{ + lhs = lhs + rhs; + return lhs; +} + +DN_API DN_V2F32 &operator+=(DN_V2F32 &lhs, DN_I32 rhs) +{ + lhs = lhs + rhs; + return lhs; +} + +DN_API DN_V2F32 DN_V2F32Min(DN_V2F32 a, DN_V2F32 b) +{ + DN_V2F32 result = DN_V2F32From2N(DN_Min(a.x, b.x), DN_Min(a.y, b.y)); + return result; +} + +DN_API DN_V2F32 DN_V2F32Max(DN_V2F32 a, DN_V2F32 b) +{ + DN_V2F32 result = DN_V2F32From2N(DN_Max(a.x, b.x), DN_Max(a.y, b.y)); + return result; +} + +DN_API DN_V2F32 DN_V2F32Abs(DN_V2F32 a) +{ + DN_V2F32 result = DN_V2F32From2N(DN_Abs(a.x), DN_Abs(a.y)); + return result; +} + +DN_API DN_F32 DN_V2F32Dot(DN_V2F32 a, DN_V2F32 b) +{ + // NOTE: Scalar projection of B onto A ///////////////////////////////////////////////////////// + // + // Scalar projection calculates the signed distance between `b` and `a` + // where `a` is a unit vector then, the dot product calculates the projection + // of `b` onto the infinite line that the direction of `a` represents. This + // calculation is the signed distance. + // + // signed_distance = dot_product(a, b) = (a.x * b.x) + (a.y * b.y) + // + // Y + // ^ b + // | /| + // | / | + // | / | + // | / | Projection + // | / | + // |/ V + // +--->--------> X + // . a . + // . . + // |------| <- Calculated signed distance + // + // The signed-ness of the result indicates the relationship: + // + // Distance <0 means `b` is behind `a` + // Distance >0 means `b` is in-front of `a` + // Distance ==0 means `b` is perpendicular to `a` + // + // If `a` is not normalized then the signed-ness of the result still holds + // however result no longer represents the actual distance between the + // 2 objects. One of the vectors must be normalised (e.g. turned into a unit + // vector). + // + // NOTE: DN_V projection ///////////////////////////////////////////////////////////////////// + // + // DN_V projection calculates the exact X,Y coordinates of where `b` meets + // `a` when it was projected. This is calculated by multipying the + // 'scalar projection' result by the unit vector of `a` + // + // vector_projection = a * signed_distance = a * dot_product(a, b) + + DN_F32 result = (a.x * b.x) + (a.y * b.y); + return result; +} + +DN_API DN_F32 DN_V2F32LengthSq2V2(DN_V2F32 lhs, DN_V2F32 rhs) +{ + // NOTE: Pythagoras's theorem (a^2 + b^2 = c^2) without the square root + DN_F32 a = rhs.x - lhs.x; + DN_F32 b = rhs.y - lhs.y; + DN_F32 c_squared = DN_Squared(a) + DN_Squared(b); + DN_F32 result = c_squared; + return result; +} + +DN_API bool DN_V2F32LengthSqIsWithin2V2(DN_V2F32 lhs, DN_V2F32 rhs, DN_F32 within_amount_sq) +{ + DN_F32 dist = DN_V2F32LengthSq2V2(lhs, rhs); + bool result = dist <= within_amount_sq; + return result; +} + +DN_API DN_F32 DN_V2F32Length2V2(DN_V2F32 lhs, DN_V2F32 rhs) +{ + DN_F32 result_squared = DN_V2F32LengthSq2V2(lhs, rhs); + DN_F32 result = DN_SqrtF32(result_squared); + return result; +} + +DN_API DN_F32 DN_V2F32LengthSq(DN_V2F32 lhs) +{ + // NOTE: Pythagoras's theorem without the square root + DN_F32 c_squared = DN_Squared(lhs.x) + DN_Squared(lhs.y); + DN_F32 result = c_squared; + return result; +} + +DN_API DN_F32 DN_V2F32Length(DN_V2F32 lhs) +{ + DN_F32 c_squared = DN_V2F32LengthSq(lhs); + DN_F32 result = DN_SqrtF32(c_squared); + return result; +} + +DN_API DN_V2F32 DN_V2F32Normalise(DN_V2F32 a) +{ + DN_F32 length = DN_V2F32Length(a); + DN_V2F32 result = a / length; + return result; +} + +DN_API DN_V2F32 DN_V2F32Perpendicular(DN_V2F32 a) +{ + // NOTE: Matrix form of a 2D vector can be defined as + // + // x' = x cos(t) - y sin(t) + // y' = x sin(t) + y cos(t) + // + // Calculate a line perpendicular to a vector means rotating the vector by + // 90 degrees + // + // x' = x cos(90) - y sin(90) + // y' = x sin(90) + y cos(90) + // + // Where `cos(90) = 0` and `sin(90) = 1` then, + // + // x' = -y + // y' = +x + + DN_V2F32 result = DN_V2F32From2N(-a.y, a.x); + return result; +} + +DN_API DN_V2F32 DN_V2F32Reflect(DN_V2F32 in, DN_V2F32 surface) +{ + DN_V2F32 normal = DN_V2F32Perpendicular(surface); + DN_V2F32 normal_norm = DN_V2F32Normalise(normal); + DN_F32 signed_dist = DN_V2F32Dot(in, normal_norm); + DN_V2F32 result = DN_V2F32From2N(in.x, in.y + (-signed_dist * 2.f)); + return result; +} + +DN_API DN_F32 DN_V2F32Area(DN_V2F32 a) +{ + DN_F32 result = a.w * a.h; + return result; +} + +DN_API bool operator!=(DN_V3F32 lhs, DN_V3F32 rhs) +{ + bool result = !(lhs == rhs); + return result; +} + +DN_API bool operator==(DN_V3F32 lhs, DN_V3F32 rhs) +{ + bool result = (lhs.x == rhs.x) && (lhs.y == rhs.y) && (lhs.z == rhs.z); + return result; +} + +DN_API bool operator>=(DN_V3F32 lhs, DN_V3F32 rhs) +{ + bool result = (lhs.x >= rhs.x) && (lhs.y >= rhs.y) && (lhs.z >= rhs.z); + return result; +} + +DN_API bool operator<=(DN_V3F32 lhs, DN_V3F32 rhs) +{ + bool result = (lhs.x <= rhs.x) && (lhs.y <= rhs.y) && (lhs.z <= rhs.z); + return result; +} + +DN_API bool operator<(DN_V3F32 lhs, DN_V3F32 rhs) +{ + bool result = (lhs.x < rhs.x) && (lhs.y < rhs.y) && (lhs.z < rhs.z); + return result; +} + +DN_API bool operator>(DN_V3F32 lhs, DN_V3F32 rhs) +{ + bool result = (lhs.x > rhs.x) && (lhs.y > rhs.y) && (lhs.z > rhs.z); + return result; +} + +DN_API DN_V3F32 operator-(DN_V3F32 lhs, DN_V3F32 rhs) +{ + DN_V3F32 result = DN_V3F32From3N(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z); + return result; +} + +DN_API DN_V3F32 operator-(DN_V3F32 lhs) +{ + DN_V3F32 result = DN_V3F32From3N(-lhs.x, -lhs.y, -lhs.z); + return result; +} + +DN_API DN_V3F32 operator+(DN_V3F32 lhs, DN_V3F32 rhs) +{ + DN_V3F32 result = DN_V3F32From3N(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z); + return result; +} + +DN_API DN_V3F32 operator*(DN_V3F32 lhs, DN_V3F32 rhs) +{ + DN_V3F32 result = DN_V3F32From3N(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z); + return result; +} + +DN_API DN_V3F32 operator*(DN_V3F32 lhs, DN_F32 rhs) +{ + DN_V3F32 result = DN_V3F32From3N(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs); + return result; +} + +DN_API DN_V3F32 operator*(DN_V3F32 lhs, DN_I32 rhs) +{ + DN_V3F32 result = DN_V3F32From3N(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs); + return result; +} + +DN_API DN_V3F32 operator/(DN_V3F32 lhs, DN_V3F32 rhs) +{ + DN_V3F32 result = DN_V3F32From3N(lhs.x / rhs.x, lhs.y / rhs.y, lhs.z / rhs.z); + return result; +} + +DN_API DN_V3F32 operator/(DN_V3F32 lhs, DN_F32 rhs) +{ + DN_V3F32 result = DN_V3F32From3N(lhs.x / rhs, lhs.y / rhs, lhs.z / rhs); + return result; +} + +DN_API DN_V3F32 operator/(DN_V3F32 lhs, DN_I32 rhs) +{ + DN_V3F32 result = DN_V3F32From3N(lhs.x / rhs, lhs.y / rhs, lhs.z / rhs); + return result; +} + +DN_API DN_V3F32 &operator*=(DN_V3F32 &lhs, DN_V3F32 rhs) +{ + lhs = lhs * rhs; + return lhs; +} + +DN_API DN_V3F32 &operator*=(DN_V3F32 &lhs, DN_F32 rhs) +{ + lhs = lhs * rhs; + return lhs; +} + +DN_API DN_V3F32 &operator*=(DN_V3F32 &lhs, DN_I32 rhs) +{ + lhs = lhs * rhs; + return lhs; +} + +DN_API DN_V3F32 &operator/=(DN_V3F32 &lhs, DN_V3F32 rhs) +{ + lhs = lhs / rhs; + return lhs; +} + +DN_API DN_V3F32 &operator/=(DN_V3F32 &lhs, DN_F32 rhs) +{ + lhs = lhs / rhs; + return lhs; +} + +DN_API DN_V3F32 &operator/=(DN_V3F32 &lhs, DN_I32 rhs) +{ + lhs = lhs / rhs; + return lhs; +} + +DN_API DN_V3F32 &operator-=(DN_V3F32 &lhs, DN_V3F32 rhs) +{ + lhs = lhs - rhs; + return lhs; +} + +DN_API DN_V3F32 &operator+=(DN_V3F32 &lhs, DN_V3F32 rhs) +{ + lhs = lhs + rhs; + return lhs; +} + +DN_API DN_V3F32 DN_V3F32Lerp(DN_V3F32 lhs, DN_F32 t01, DN_V3F32 rhs) +{ + DN_V3F32 result = {}; + result.x = lhs.x + ((rhs.x - lhs.x) * t01); + result.y = lhs.y + ((rhs.y - lhs.y) * t01); + result.z = lhs.z + ((rhs.z - lhs.z) * t01); + return result; +} + +DN_API DN_F32 DN_V3_LengthSq(DN_V3F32 a) +{ + DN_F32 result = DN_Squared(a.x) + DN_Squared(a.y) + DN_Squared(a.z); + return result; +} + +DN_API DN_F32 DN_V3_Length(DN_V3F32 a) +{ + DN_F32 length_sq = DN_Squared(a.x) + DN_Squared(a.y) + DN_Squared(a.z); + DN_F32 result = DN_SqrtF32(length_sq); + return result; +} + +DN_API DN_V3F32 DN_V3_Normalise(DN_V3F32 a) +{ + DN_F32 length = DN_V3_Length(a); + DN_V3F32 result = a / length; + return result; +} + +DN_API DN_V4F32 DN_V4F32Lerp(DN_V4F32 lhs, DN_F32 t01, DN_V4F32 rhs) +{ + DN_V4F32 result = {}; + result.x = lhs.x + (rhs.x - lhs.x) * t01; + result.y = lhs.y + (rhs.y - lhs.y) * t01; + result.z = lhs.z + (rhs.z - lhs.z) * t01; + result.w = lhs.w + (rhs.w - lhs.w) * t01; + return result; +} + +DN_API bool DN_V4F32RGBA01IsValid(DN_V4F32 rgba01) +{ + bool result = rgba01.r >= 0 && rgba01.r <= 1.f && + rgba01.g >= 0 && rgba01.g <= 1.f && + rgba01.b >= 0 && rgba01.b <= 1.f && + rgba01.a >= 0 && rgba01.a <= 1.f; + return result; +} + +DN_API DN_V4F32 DN_V4F32RGBA01FromRGBU32(DN_U32 u32) +{ + DN_U8 r = (DN_U8)((u32 & 0x00FF0000) >> 16); + DN_U8 g = (DN_U8)((u32 & 0x0000FF00) >> 8); + DN_U8 b = (DN_U8)((u32 & 0x000000FF) >> 0); + DN_V4F32 result = DN_V4F32RGBA01FromRGBU8(r, g, b); + return result; +} + +DN_API DN_V4F32 DN_V4F32RGBA01FromRGBAU32(DN_U32 u32) +{ + DN_U8 r = (DN_U8)((u32 & 0xFF000000) >> 24); + DN_U8 g = (DN_U8)((u32 & 0x00FF0000) >> 16); + DN_U8 b = (DN_U8)((u32 & 0x0000FF00) >> 8); + DN_U8 a = (DN_U8)((u32 & 0x000000FF) >> 0); + DN_V4F32 result = DN_V4F32RGBA01FromRGBAU8(r, g, b, a); + return result; +} + +#define DN_SRGB_COEFFICIENT_F32 2.2f +DN_API DN_V4F32 DN_V4F32Linear01FromSRGB01(DN_V4F32 srgb01) +{ + DN_Assert(srgb01.x >= 0.f && srgb01.x <= 1.f); + DN_Assert(srgb01.y >= 0.f && srgb01.y <= 1.f); + DN_Assert(srgb01.z >= 0.f && srgb01.z <= 1.f); + DN_Assert(srgb01.a >= 0.f && srgb01.a <= 1.f); + DN_V4F32 result = {}; + result.r = DN_PowF32(srgb01.r, DN_SRGB_COEFFICIENT_F32); + result.g = DN_PowF32(srgb01.g, DN_SRGB_COEFFICIENT_F32); + result.b = DN_PowF32(srgb01.b, DN_SRGB_COEFFICIENT_F32); + result.a = srgb01.a; + return result; +} + +DN_API DN_V4F32 DN_V4F32Linear01Desaturate(DN_V4F32 linear01, DN_F32 t01) +{ + DN_F32 luminance = (linear01.r * DN_V3F32_RGB_LUMINANCE.r) + (linear01.g * DN_V3F32_RGB_LUMINANCE.g) + (linear01.b * DN_V3F32_RGB_LUMINANCE.b); + DN_V4F32 result = linear01; + result.rgb = DN_V3F32Lerp(result.rgb, t01, DN_V3F32From1N(luminance)); + return result; +} + +DN_API DN_V4F32 DN_V4F32SRGB01FromLinear01(DN_V4F32 linear01) +{ + DN_Assert(linear01.x >= 0.f && linear01.x <= 1.f); + DN_Assert(linear01.y >= 0.f && linear01.y <= 1.f); + DN_Assert(linear01.z >= 0.f && linear01.z <= 1.f); + DN_Assert(linear01.a >= 0.f && linear01.a <= 1.f); + DN_V4F32 result = {}; + result.r = DN_PowF32(linear01.r, 1.f / DN_SRGB_COEFFICIENT_F32); + result.g = DN_PowF32(linear01.g, 1.f / DN_SRGB_COEFFICIENT_F32); + result.b = DN_PowF32(linear01.b, 1.f / DN_SRGB_COEFFICIENT_F32); + result.a = linear01.a; + return result; +} + +DN_API bool operator==(DN_V4F32 lhs, DN_V4F32 rhs) +{ + bool result = (lhs.x == rhs.x) && (lhs.y == rhs.y) && (lhs.z == rhs.z) && (lhs.w == rhs.w); + return result; +} + +DN_API bool operator!=(DN_V4F32 lhs, DN_V4F32 rhs) +{ + bool result = !(lhs == rhs); + return result; +} + +DN_API bool operator>=(DN_V4F32 lhs, DN_V4F32 rhs) +{ + bool result = (lhs.x >= rhs.x) && (lhs.y >= rhs.y) && (lhs.z >= rhs.z) && (lhs.w >= rhs.w); + return result; +} + +DN_API bool operator<=(DN_V4F32 lhs, DN_V4F32 rhs) +{ + bool result = (lhs.x <= rhs.x) && (lhs.y <= rhs.y) && (lhs.z <= rhs.z) && (lhs.w <= rhs.w); + return result; +} + +DN_API bool operator<(DN_V4F32 lhs, DN_V4F32 rhs) +{ + bool result = (lhs.x < rhs.x) && (lhs.y < rhs.y) && (lhs.z < rhs.z) && (lhs.w < rhs.w); + return result; +} + +DN_API bool operator>(DN_V4F32 lhs, DN_V4F32 rhs) +{ + bool result = (lhs.x > rhs.x) && (lhs.y > rhs.y) && (lhs.z > rhs.z) && (lhs.w > rhs.w); + return result; +} + +DN_API DN_V4F32 operator-(DN_V4F32 lhs, DN_V4F32 rhs) +{ + DN_V4F32 result = DN_V4F32From4N(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z, lhs.w - rhs.w); + return result; +} + +DN_API DN_V4F32 operator-(DN_V4F32 lhs) +{ + DN_V4F32 result = DN_V4F32From4N(-lhs.x, -lhs.y, -lhs.z, -lhs.w); + return result; +} + +DN_API DN_V4F32 operator+(DN_V4F32 lhs, DN_V4F32 rhs) +{ + DN_V4F32 result = DN_V4F32From4N(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z, lhs.w + rhs.w); + return result; +} + +DN_API DN_V4F32 operator*(DN_V4F32 lhs, DN_V4F32 rhs) +{ + DN_V4F32 result = DN_V4F32From4N(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z, lhs.w * rhs.w); + return result; +} + +DN_API DN_V4F32 operator*(DN_V4F32 lhs, DN_F32 rhs) +{ + DN_V4F32 result = DN_V4F32From4N(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs, lhs.w * rhs); + return result; +} + +DN_API DN_V4F32 operator*(DN_V4F32 lhs, DN_I32 rhs) +{ + DN_V4F32 result = DN_V4F32From4N(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs, lhs.w * rhs); + return result; +} + +DN_API DN_V4F32 operator/(DN_V4F32 lhs, DN_F32 rhs) +{ + DN_V4F32 result = DN_V4F32From4N(lhs.x / rhs, lhs.y / rhs, lhs.z / rhs, lhs.w / rhs); + return result; +} + +DN_API DN_V4F32 &operator*=(DN_V4F32 &lhs, DN_V4F32 rhs) +{ + lhs = lhs * rhs; + return lhs; +} + +DN_API DN_V4F32 &operator*=(DN_V4F32 &lhs, DN_F32 rhs) +{ + lhs = lhs * rhs; + return lhs; +} + +DN_API DN_V4F32 &operator*=(DN_V4F32 &lhs, DN_I32 rhs) +{ + lhs = lhs * rhs; + return lhs; +} + +DN_API DN_V4F32 &operator-=(DN_V4F32 &lhs, DN_V4F32 rhs) +{ + lhs = lhs - rhs; + return lhs; +} + +DN_API DN_V4F32 &operator+=(DN_V4F32 &lhs, DN_V4F32 rhs) +{ + lhs = lhs + rhs; + return lhs; +} + +DN_API DN_F32 DN_V4F32Dot(DN_V4F32 a, DN_V4F32 b) +{ + DN_F32 result = (a.x * b.x) + (a.y * b.y) + (a.z * b.z) + (a.w * b.w); + return result; +} + +DN_API DN_M4 DN_M4Identity() +{ + DN_M4 result = + { + { + {1, 0, 0, 0}, + {0, 1, 0, 0}, + {0, 0, 1, 0}, + {0, 0, 0, 1}, + } + }; + + return result; +} + +DN_API DN_M4 DN_M4ScaleF(DN_F32 x, DN_F32 y, DN_F32 z) +{ + DN_M4 result = + { + { + {x, 0, 0, 0}, + {0, y, 0, 0}, + {0, 0, z, 0}, + {0, 0, 0, 1}, + } + }; + + return result; +} + +DN_API DN_M4 DN_M4Scale(DN_V3F32 xyz) +{ + DN_M4 result = + { + { + {xyz.x, 0, 0, 0}, + {0, xyz.y, 0, 0}, + {0, 0, xyz.z, 0}, + {0, 0, 0, 1}, + } + }; + + return result; +} + +DN_API DN_M4 DN_M4TranslateF(DN_F32 x, DN_F32 y, DN_F32 z) +{ + DN_M4 result = + { + { + {1, 0, 0, 0}, + {0, 1, 0, 0}, + {0, 0, 1, 0}, + {x, y, z, 1}, + } + }; + + return result; +} + +DN_API DN_M4 DN_M4Translate(DN_V3F32 xyz) +{ + DN_M4 result = + { + { + {1, 0, 0, 0}, + {0, 1, 0, 0}, + {0, 0, 1, 0}, + {xyz.x, xyz.y, xyz.z, 1}, + } + }; + + return result; +} + +DN_API DN_M4 DN_M4Transpose(DN_M4 mat) +{ + DN_M4 result = {}; + for (int col = 0; col < 4; col++) + for (int row = 0; row < 4; row++) + result.columns[col][row] = mat.columns[row][col]; + return result; +} + +DN_API DN_M4 DN_M4Rotate(DN_V3F32 axis01, DN_F32 radians) +{ + DN_AssertF(DN_Abs(DN_V3_Length(axis01) - 1.f) <= 0.01f, + "Rotation axis must be normalised, length = %f", + DN_V3_Length(axis01)); + + DN_F32 sin = DN_SinF32(radians); + DN_F32 cos = DN_CosF32(radians); + DN_F32 one_minus_cos = 1.f - cos; + + DN_F32 x = axis01.x; + DN_F32 y = axis01.y; + DN_F32 z = axis01.z; + DN_F32 x2 = DN_Squared(x); + DN_F32 y2 = DN_Squared(y); + DN_F32 z2 = DN_Squared(z); + + DN_M4 result = + { + { + {cos + x2 * one_minus_cos, y * x * one_minus_cos + z * sin, z * x * one_minus_cos - y * sin, 0}, // Col 1 + {x * y * one_minus_cos - z * sin, cos + y2 * one_minus_cos, z * y * one_minus_cos + x * sin, 0}, // Col 2 + {x * z * one_minus_cos + y * sin, y * z * one_minus_cos - x * sin, cos + z2 * one_minus_cos, 0}, // Col 3 + {0, 0, 0, 1}, // Col 4 + } + }; + + return result; +} + +DN_API DN_M4 DN_M4Orthographic(DN_F32 left, DN_F32 right, DN_F32 bottom, DN_F32 top, DN_F32 z_near, DN_F32 z_far) +{ + // NOTE: Here is the matrix in column major for readability. Below it's + // transposed due to how you have to declare column major matrices in C/C++. + // + // m = [2/r-l, 0, 0, -1*(r+l)/(r-l)] + // [0, 2/t-b, 0, 1*(t+b)/(t-b)] + // [0, 0, -2/f-n, -1*(f+n)/(f-n)] + // [0, 0, 0, 1 ] + + DN_M4 result = + { + { + {2.f / (right - left), 0.f, 0.f, 0.f}, + {0.f, 2.f / (top - bottom), 0.f, 0.f}, + {0.f, 0.f, -2.f / (z_far - z_near), 0.f}, + {(-1.f * (right + left)) / (right - left), (-1.f * (top + bottom)) / (top - bottom), (-1.f * (z_far + z_near)) / (z_far - z_near), 1.f}, + } + }; + + return result; +} + +DN_API DN_M4 DN_M4Perspective(DN_F32 fov /*radians*/, DN_F32 aspect, DN_F32 z_near, DN_F32 z_far) +{ + DN_F32 tan_fov = DN_TanF32(fov / 2.f); + DN_M4 result = + { + { + {1.f / (aspect * tan_fov), 0.f, 0.f, 0.f}, + {0, 1.f / tan_fov, 0.f, 0.f}, + {0.f, 0.f, (z_near + z_far) / (z_near - z_far), -1.f}, + {0.f, 0.f, (2.f * z_near * z_far) / (z_near - z_far), 0.f}, + } + }; + + return result; +} + +DN_API DN_M4 DN_M4Add(DN_M4 lhs, DN_M4 rhs) +{ + DN_M4 result; + for (int col = 0; col < 4; col++) + for (int it = 0; it < 4; it++) + result.columns[col][it] = lhs.columns[col][it] + rhs.columns[col][it]; + return result; +} + +DN_API DN_M4 DN_M4Sub(DN_M4 lhs, DN_M4 rhs) +{ + DN_M4 result; + for (int col = 0; col < 4; col++) + for (int it = 0; it < 4; it++) + result.columns[col][it] = lhs.columns[col][it] - rhs.columns[col][it]; + return result; +} + +DN_API DN_M4 DN_M4Mul(DN_M4 lhs, DN_M4 rhs) +{ + DN_M4 result; + for (int col = 0; col < 4; col++) { + for (int row = 0; row < 4; row++) { + DN_F32 sum = 0; + for (int f32_it = 0; f32_it < 4; f32_it++) + sum += lhs.columns[f32_it][row] * rhs.columns[col][f32_it]; + + result.columns[col][row] = sum; + } + } + return result; +} + +DN_API DN_M4 DN_M4Div(DN_M4 lhs, DN_M4 rhs) +{ + DN_M4 result; + for (int col = 0; col < 4; col++) + for (int it = 0; it < 4; it++) + result.columns[col][it] = lhs.columns[col][it] / rhs.columns[col][it]; + return result; +} + +DN_API DN_M4 DN_M4AddF(DN_M4 lhs, DN_F32 rhs) +{ + DN_M4 result; + for (int col = 0; col < 4; col++) + for (int it = 0; it < 4; it++) + result.columns[col][it] = lhs.columns[col][it] + rhs; + return result; +} + +DN_API DN_M4 DN_M4SubF(DN_M4 lhs, DN_F32 rhs) +{ + DN_M4 result; + for (int col = 0; col < 4; col++) + for (int it = 0; it < 4; it++) + result.columns[col][it] = lhs.columns[col][it] - rhs; + return result; +} + +DN_API DN_M4 DN_M4MulF(DN_M4 lhs, DN_F32 rhs) +{ + DN_M4 result; + for (int col = 0; col < 4; col++) + for (int it = 0; it < 4; it++) + result.columns[col][it] = lhs.columns[col][it] * rhs; + return result; +} + +DN_API DN_M4 DN_M4DivF(DN_M4 lhs, DN_F32 rhs) +{ + DN_M4 result; + for (int col = 0; col < 4; col++) + for (int it = 0; it < 4; it++) + result.columns[col][it] = lhs.columns[col][it] / rhs; + return result; +} + +DN_API DN_Str8x256 DN_M4ColumnMajorString(DN_M4 mat) +{ + DN_Str8x256 result = {}; + for (int row = 0; row < 4; row++) { + for (int it = 0; it < 4; it++) { + if (it == 0) + DN_FmtAppend(result.data, &result.size, sizeof(result.data), "|"); + DN_FmtAppend(result.data, &result.size, sizeof(result.data), "%.5f", mat.columns[it][row]); + if (it != 3) + DN_FmtAppend(result.data, &result.size, sizeof(result.data), ", "); + else + DN_FmtAppend(result.data, &result.size, sizeof(result.data), "|\n"); + } + } + return result; +} + +DN_API bool DN_M2x3Eq(DN_M2x3 const *lhs, DN_M2x3 const *rhs) +{ + bool result = DN_Memcmp(lhs->e, rhs->e, sizeof(lhs->e[0]) * DN_ArrayCountU(lhs->e)) == 0; + return result; +} + +DN_API bool DN_M2x3NotEq(DN_M2x3 const *lhs, DN_M2x3 const *rhs) +{ + bool result = !DN_M2x3Eq(lhs, rhs); + return result; +} + +DN_API DN_M2x3 DN_M2x3Identity() +{ + DN_M2x3 result = { + { + 1, + 0, + 0, + 0, + 1, + 0, + } + }; + return result; +} + +DN_API DN_M2x3 DN_M2x3Translate(DN_V2F32 offset) +{ + DN_M2x3 result = { + { + 1, + 0, + offset.x, + 0, + 1, + offset.y, + } + }; + return result; +} + +DN_API DN_V2F32 DN_M2x3ScaleGet(DN_M2x3 m2x3) +{ + DN_V2F32 result = DN_V2F32From2N(m2x3.row[0][0], m2x3.row[1][1]); + return result; +} + +DN_API DN_M2x3 DN_M2x3Scale(DN_V2F32 scale) +{ + DN_M2x3 result = {{ + scale.x, 0, 0, + 0, scale.y, 0, + }}; + return result; +} + +DN_API DN_M2x3 DN_M2x3Rotate(DN_F32 radians) +{ + DN_M2x3 result = {{ + DN_CosF32(radians), DN_SinF32(radians), 0, + -DN_SinF32(radians), DN_CosF32(radians), 0, + }}; + return result; +} + +DN_API DN_M2x3 DN_M2x3ProjFromV2F32(DN_V2F32 size, DN_M2x3ProjOrigin origin) +{ + DN_M2x3 result = {}; + + // NOTE: Maps coordinates within a rectangle of `size` into NDC where (-1, +1) is top left, (+1, -1) is bot right + if (origin == DN_M2x3ProjOrigin_TopLeft) { + result = {{ + 2.f/size.w, 0, -1.f, + 0, -2.f/size.h, +1.f, + }}; + } else { + DN_Assert(origin == DN_M2x3ProjOrigin_Center); + result = {{ + 2.f/size.w, 0, 0.f, + 0, -2.f/size.h, 0.f, + }}; + } + return result; +} + +DN_API DN_M2x3XForm DN_M2x3XFormFromM2x3(DN_M2x3 forward, DN_M2x3 inverse) +{ + DN_M2x3XForm result = {}; + result.forward = forward; + result.inverse = inverse; + return result; +} + +DN_API DN_M2x3XForm DN_M2x3XFormFromTRS(DN_V2F32 pos, DN_V2F32 scale, DN_F32 rotate_rads, DN_V2F32 pivot_pos) +{ + DN_M2x3XForm result = {}; + result.forward = DN_M2x3Identity(); + result.inverse = DN_M2x3Identity(); + + if (scale.x == 0) + scale.x = 1; + if (scale.y == 0) + scale.y = 1; + + result.forward = DN_M2x3Mul(result.forward, DN_M2x3Translate(pivot_pos)); + result.forward = DN_M2x3Mul(result.forward, DN_M2x3Rotate(rotate_rads)); + result.forward = DN_M2x3Mul(result.forward, DN_M2x3Scale(scale)); + result.forward = DN_M2x3Mul(result.forward, DN_M2x3Translate(-pivot_pos)); + result.forward = DN_M2x3Mul(result.forward, DN_M2x3Translate(pos)); + + DN_V2F32 inverse_scale = DN_V2F32From1N(1) / scale; + result.inverse = DN_M2x3Mul(result.inverse, DN_M2x3Translate(-pos)); + result.inverse = DN_M2x3Mul(result.inverse, DN_M2x3Translate(pivot_pos)); + result.inverse = DN_M2x3Mul(result.inverse, DN_M2x3Scale(inverse_scale)); + result.inverse = DN_M2x3Mul(result.inverse, DN_M2x3Rotate(-rotate_rads)); + result.inverse = DN_M2x3Mul(result.inverse, DN_M2x3Translate(-pivot_pos)); + return result; +} + +DN_API DN_M2x3XForm DN_M2x3XFormIdentity() +{ + DN_M2x3XForm result = {}; + result.forward = DN_M2x3Identity(); + result.inverse = DN_M2x3Identity(); + return result; +} + +DN_API DN_M2x3XForm DN_M2x3XFormMul(DN_M2x3XForm m1, DN_M2x3XForm m2) +{ + DN_M2x3XForm result = {}; + result.forward = DN_M2x3Mul(m1.forward, m2.forward); + result.inverse = DN_M2x3Mul(m2.inverse, m1.inverse); + return result; +} + +DN_API DN_M2x3 DN_M2x3Mul(DN_M2x3 m1, DN_M2x3 m2) +{ + // NOTE: Ordinarily you can't multiply M2x3 with M2x3 because column count + // (3) != row count (2). We pretend we have two 3x3 matrices with the last + // row set to [0 0 1] and perform a 3x3 matrix multiply. + // + // | (0)a (1)b (2)c | | (0)g (1)h (2)i | + // | (3)d (4)e (5)f | x | (3)j (4)k (5)l | + // | (6)0 (7)0 (8)1 | | (6)0 (7)0 (8)1 | + + DN_M2x3 result = { + { + m1.e[0] * m2.e[0] + m1.e[1] * m2.e[3], // a*g + b*j + c*0[omitted], + m1.e[0] * m2.e[1] + m1.e[1] * m2.e[4], // a*h + b*k + c*0[omitted], + m1.e[0] * m2.e[2] + m1.e[1] * m2.e[5] + m1.e[2], // a*i + b*l + c*1, + + m1.e[3] * m2.e[0] + m1.e[4] * m2.e[3], // d*g + e*j + f*0[omitted], + m1.e[3] * m2.e[1] + m1.e[4] * m2.e[4], // d*h + e*k + f*0[omitted], + m1.e[3] * m2.e[2] + m1.e[4] * m2.e[5] + m1.e[5], // d*i + e*l + f*1, + } + }; + + return result; +} + +DN_API DN_V2F32 DN_M2x3Mul2F32(DN_M2x3 m1, DN_F32 x, DN_F32 y) +{ + // NOTE: Ordinarily you can't multiply M2x3 with V2 because column count (3) + // != row count (2). We pretend we have a V3 with `z` set to `1`. + // + // | (0)a (1)b (2)c | | x | + // | (3)d (4)e (5)f | x | y | + // | 1 | + + DN_V2F32 result = { + { + m1.e[0] * x + m1.e[1] * y + m1.e[2], // a*x + b*y + c*1 + m1.e[3] * x + m1.e[4] * y + m1.e[5], // d*x + e*y + f*1 + } + }; + return result; +} + +DN_API DN_V2F32 DN_M2x3MulV2F32(DN_M2x3 m1, DN_V2F32 v2) +{ + DN_V2F32 result = DN_M2x3Mul2F32(m1, v2.x, v2.y); + return result; +} + +DN_API DN_Rect DN_M2x3MulRect(DN_M2x3 m1, DN_Rect rect) +{ + DN_2V2F32 rect_range = DN_RectRange(rect); + DN_V2F32 m1_min = DN_M2x3MulV2F32(m1, rect_range.min); + DN_V2F32 m1_max = DN_M2x3MulV2F32(m1, rect_range.max); + + // NOTE: Re-establish AABB of the rectangle because it has gone through an arbitrary + // vertex transformation. + DN_2V2F32 result_range = {}; + result_range.min = DN_V2F32Min(m1_min, m1_max); + result_range.max = DN_V2F32Max(m1_min, m1_max); + + DN_Rect result = DN_RectFrom2V2(result_range.min, DN_V2F32Abs(result_range.max - result_range.min)); + return result; +} + +DN_API DN_V2F32 DN_RectCenter(DN_Rect rect) +{ + DN_V2F32 result = rect.pos + (rect.size * .5f); + return result; +} + +DN_API bool DN_RectContainsPoint(DN_Rect rect, DN_V2F32 p) +{ + DN_V2F32 min = rect.pos; + DN_V2F32 max = rect.pos + rect.size; + bool result = (p.x >= min.x && p.x <= max.x && p.y >= min.y && p.y <= max.y); + return result; +} + +DN_API bool DN_RectContainsRect(DN_Rect a, DN_Rect b) +{ + DN_V2F32 a_min = a.pos; + DN_V2F32 a_max = a.pos + a.size; + DN_V2F32 b_min = b.pos; + DN_V2F32 b_max = b.pos + b.size; + bool result = (b_min >= a_min && b_max <= a_max); + return result; +} + +DN_API DN_Rect DN_RectExpand(DN_Rect a, DN_F32 amount) +{ + DN_Rect result = a; + result.pos -= amount; + result.size += (amount * 2.f); + return result; +} + +DN_API DN_Rect DN_RectExpandV2(DN_Rect a, DN_V2F32 amount) +{ + DN_Rect result = a; + result.pos -= amount; + result.size += (amount * 2.f); + return result; +} + +DN_API bool DN_RectIntersects(DN_Rect a, DN_Rect b) +{ + DN_V2F32 a_min = a.pos; + DN_V2F32 a_max = a.pos + a.size; + DN_V2F32 b_min = b.pos; + DN_V2F32 b_max = b.pos + b.size; + bool has_size = a.size.x && a.size.y && b.size.x && b.size.y; + bool result = false; + if (has_size) + result = (a_min.x <= b_max.x && a_max.x >= b_min.x) && + (a_min.y <= b_max.y && a_max.y >= b_min.y); + return result; +} + +DN_API DN_Rect DN_RectIntersection(DN_Rect a, DN_Rect b) +{ + DN_Rect result = DN_RectFrom2V2(a.pos, DN_V2F32From1N(0)); + if (DN_RectIntersects(a, b)) { + DN_V2F32 a_min = a.pos; + DN_V2F32 a_max = a.pos + a.size; + DN_V2F32 b_min = b.pos; + DN_V2F32 b_max = b.pos + b.size; + + DN_V2F32 min = {}; + DN_V2F32 max = {}; + min.x = DN_Max(a_min.x, b_min.x); + min.y = DN_Max(a_min.y, b_min.y); + max.x = DN_Min(a_max.x, b_max.x); + max.y = DN_Min(a_max.y, b_max.y); + result = DN_RectFrom2V2(min, max - min); + } + return result; +} + +DN_API DN_Rect DN_RectUnion(DN_Rect a, DN_Rect b) +{ + DN_V2F32 a_min = a.pos; + DN_V2F32 a_max = a.pos + a.size; + DN_V2F32 b_min = b.pos; + DN_V2F32 b_max = b.pos + b.size; + + DN_V2F32 min, max; + min.x = DN_Min(a_min.x, b_min.x); + min.y = DN_Min(a_min.y, b_min.y); + max.x = DN_Max(a_max.x, b_max.x); + max.y = DN_Max(a_max.y, b_max.y); + DN_Rect result = DN_RectFrom2V2(min, max - min); + return result; +} + +DN_API DN_2V2F32 DN_RectRange(DN_Rect a) +{ + DN_2V2F32 result = {}; + result.min = a.pos; + result.max = a.pos + a.size; + return result; +} + +DN_API bool DN_RectEq(DN_Rect lhs, DN_Rect rhs) +{ + bool result = lhs.pos == rhs.pos && lhs.size == rhs.size; + return result; +} + +DN_API DN_F32 DN_RectArea(DN_Rect a) +{ + DN_F32 result = a.size.w * a.size.h; + return result; +} + +DN_API DN_Rect DN_RectCutLeftClip(DN_Rect *rect, DN_F32 amount, DN_RectCutClip clip) +{ + DN_F32 min_x = rect->pos.x; + DN_F32 max_x = rect->pos.x + rect->size.w; + DN_F32 result_max_x = min_x + amount; + if (clip) + result_max_x = DN_Min(result_max_x, max_x); + DN_Rect result = DN_RectFrom4N(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; +} + +DN_API DN_Rect DN_RectCutRightClip(DN_Rect *rect, DN_F32 amount, DN_RectCutClip clip) +{ + DN_F32 min_x = rect->pos.x; + DN_F32 max_x = rect->pos.x + rect->size.w; + DN_F32 result_min_x = max_x - amount; + if (clip) + result_min_x = DN_Max(result_min_x, 0); + DN_Rect result = DN_RectFrom4N(result_min_x, rect->pos.y, max_x - result_min_x, rect->size.h); + rect->size.w = result_min_x - min_x; + return result; +} + +DN_API DN_Rect DN_RectCutTopClip(DN_Rect *rect, DN_F32 amount, DN_RectCutClip clip) +{ + DN_F32 min_y = rect->pos.y; + DN_F32 max_y = rect->pos.y + rect->size.h; + DN_F32 result_max_y = min_y + amount; + if (clip) + result_max_y = DN_Min(result_max_y, max_y); + DN_Rect result = DN_RectFrom4N(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; +} + +DN_API DN_Rect DN_RectCutBottomClip(DN_Rect *rect, DN_F32 amount, DN_RectCutClip clip) +{ + DN_F32 min_y = rect->pos.y; + DN_F32 max_y = rect->pos.y + rect->size.h; + DN_F32 result_min_y = max_y - amount; + if (clip) + result_min_y = DN_Max(result_min_y, 0); + DN_Rect result = DN_RectFrom4N(rect->pos.x, result_min_y, rect->size.w, max_y - result_min_y); + rect->size.h = result_min_y - min_y; + return result; +} + +DN_API DN_Rect DN_RectCutCut(DN_RectCut rect_cut, DN_V2F32 size, DN_RectCutClip clip) +{ + DN_Rect result = {}; + if (rect_cut.rect) { + switch (rect_cut.side) { + case DN_RectCutSide_Left: result = DN_RectCutLeftClip(rect_cut.rect, size.w, clip); break; + case DN_RectCutSide_Right: result = DN_RectCutRightClip(rect_cut.rect, size.w, clip); break; + case DN_RectCutSide_Top: result = DN_RectCutTopClip(rect_cut.rect, size.h, clip); break; + case DN_RectCutSide_Bottom: result = DN_RectCutBottomClip(rect_cut.rect, size.h, clip); break; + } + } + return result; +} + +DN_API DN_V2F32 DN_RectInterpV2F32(DN_Rect rect, DN_V2F32 t01) +{ + DN_V2F32 result = DN_V2F32From2N(rect.pos.w + (rect.size.w * t01.x), + rect.pos.h + (rect.size.h * t01.y)); + return result; +} + +DN_API DN_V2F32 DN_RectTopLeft(DN_Rect rect) +{ + DN_V2F32 result = DN_RectInterpV2F32(rect, DN_V2F32From2N(0, 0)); + return result; +} + +DN_API DN_V2F32 DN_RectTopRight(DN_Rect rect) +{ + DN_V2F32 result = DN_RectInterpV2F32(rect, DN_V2F32From2N(1, 0)); + return result; +} + +DN_API DN_V2F32 DN_RectBottomLeft(DN_Rect rect) +{ + DN_V2F32 result = DN_RectInterpV2F32(rect, DN_V2F32From2N(0, 1)); + return result; +} + +DN_API DN_V2F32 DN_RectBottomRight(DN_Rect rect) +{ + DN_V2F32 result = DN_RectInterpV2F32(rect, DN_V2F32From2N(1, 1)); + return result; +} + +DN_API DN_RaycastV2 DN_RaycastLineIntersectV2(DN_V2F32 origin_a, DN_V2F32 dir_a, DN_V2F32 origin_b, DN_V2F32 dir_b) +{ + // NOTE: Parametric equation of a line + // + // p = o + (t*d) + // + // - o is the starting 2d point + // - d is the direction of the line + // - t is a scalar that scales along the direction of the point + // + // To determine if a ray intersections a ray, we want to solve + // + // (o_a + (t_a * d_a)) = (o_b + (t_b * d_b)) + // + // Where '_a' and '_b' represent the 1st and 2nd point's origin, direction + // and 't' components respectively. This is 2 equations with 2 unknowns + // (`t_a` and `t_b`) which we can solve for by expressing the equation in + // terms of `t_a` and `t_b`. + // + // Working that math out produces the formula below for 't'. + + DN_RaycastV2 result = {}; + DN_F32 denominator = ((dir_b.y * dir_a.x) - (dir_b.x * dir_a.y)); + if (denominator != 0.0f) { + result.t_a = (((origin_a.y - origin_b.y) * dir_b.x) + ((origin_b.x - origin_a.x) * dir_b.y)) / denominator; + result.t_b = (((origin_a.y - origin_b.y) * dir_a.x) + ((origin_b.x - origin_a.x) * dir_a.y)) / denominator; + result.hit = true; + } + return result; +} + +struct DN_ArrayFindEqMemcmpContext_ +{ + DN_USize elem_size; + void const *find; +}; + +DN_API void *DN_SliceAllocArena(void **data, DN_USize *slice_size_field, DN_USize size, DN_USize elem_size, DN_U8 align, DN_ZMem zmem, DN_Arena *arena) +{ + void *result = *data; + *data = DN_ArenaAlloc(arena, size * elem_size, align, zmem); + if (*data) + *slice_size_field = size; + return result; +} + +DN_API DN_ArrayFindResult DN_ArrayFind(void *data, DN_USize size, DN_USize elem_size, void const *find, DN_ArrayFindEqFunc *eq_func) +{ + DN_ArrayFindResult result = {}; + DN_Assert(data); + DN_Assert(elem_size); + if (find) { + for (DN_ForIndexU(index, size)) { + DN_U8 *it = DN_Cast(DN_U8 *) data + (index * elem_size); + if (eq_func(it, find)) { + result.index = index; + result.value = it; + result.success = true; + break; + } + } + } + return result; +} + +static bool DN_ArrayFindEqMemEqUnsafe_(void const *lhs, void const *find) +{ + DN_ArrayFindEqMemcmpContext_ *context = DN_Cast(DN_ArrayFindEqMemcmpContext_ *) find; + bool result = DN_MemEqUnsafe(lhs, context->find, context->elem_size); + return result; +} + +DN_API DN_ArrayFindResult DN_ArrayFindMemEq(void *data, DN_USize size, DN_USize elem_size, void const *find) +{ + DN_ArrayFindEqMemcmpContext_ context = {}; + context.elem_size = elem_size; + context.find = find; + DN_ArrayFindResult result = DN_ArrayFind(data, size, elem_size, &context, DN_ArrayFindEqMemEqUnsafe_); + return result; +} + +DN_API void *DN_ArrayInsertArray(void *data, DN_USize *size, DN_USize max, DN_USize elem_size, DN_USize index, void const *items, DN_USize count) +{ + void *result = nullptr; + if (!data || !size || !items || count <= 0 || ((*size + count) > max)) + return result; + + DN_USize clamped_index = DN_Min(index, *size); + if (clamped_index != *size) { + char const *src = DN_Cast(char *)data + (clamped_index * elem_size); + char const *dest = DN_Cast(char *)data + ((clamped_index + count) * elem_size); + char const *end = DN_Cast(char *)data + (size[0] * elem_size); + DN_USize bytes_to_move = end - src; + DN_Memmove(DN_Cast(void *) dest, src, bytes_to_move); + } + + result = DN_Cast(char *)data + (clamped_index * elem_size); + DN_Memcpy(result, items, elem_size * count); + *size += count; + return result; +} + +DN_API void *DN_ArrayPopFront(void *data, DN_USize *size, DN_USize elem_size, DN_USize count) +{ + if (!data || !size || *size == 0 || count == 0) + return nullptr; + + DN_USize pop_count = DN_Min(count, *size); + void *result = data; + + if (pop_count < *size) { + char *src = DN_Cast(char *)data + (pop_count * elem_size); + char *dest = DN_Cast(char *)data; + DN_USize bytes_to_move = (*size - pop_count) * elem_size; + DN_Memmove(dest, src, bytes_to_move); + } + + *size -= pop_count; + return result; +} + +DN_API void *DN_ArrayPopBack(void *data, DN_USize *size, DN_USize elem_size, DN_USize count) +{ + if (!data || !size || *size == 0 || count == 0) + return nullptr; + + DN_USize pop_count = DN_Min(count, *size); + *size -= pop_count; + + return DN_Cast(char *)data + (*size * elem_size); +} + +DN_API DN_ArrayEraseResult DN_ArrayEraseRange(void *data, DN_USize *size, DN_USize elem_size, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase) +{ + DN_ArrayEraseResult result = {}; + result.it_index = begin_index; + if (!data || !size || *size == 0 || count == 0) + return result; + + // Compute the range to erase + DN_USize start = 0, end = 0; + if (count < 0) { + // Erase backwards from begin_index, not inclusive of begin_index + // Range: [begin_index + count, begin_index) + // Which is: [begin_index - abs(count), begin_index) + DN_USize abs_count = DN_Abs(count); + start = (begin_index > abs_count) ? (begin_index - abs_count) : 0; + end = begin_index; + } else { + start = begin_index; + end = begin_index + count; + } + + // Clamp indices to valid bounds + start = DN_Min(start, *size); + end = DN_Min(end, *size); + + // Erase the range [start, end) + DN_USize erase_count = end > start ? end - start : 0; + if (erase_count) { + char *dest = (char *)data + (elem_size * start); + char *array_end = (char *)data + (elem_size * *size); + char *src = dest + (elem_size * erase_count); + if (erase == DN_ArrayErase_Stable) { + DN_USize move_size = array_end - src; + DN_Memmove(dest, src, move_size); + } else { + char *unstable_src = array_end - (elem_size * erase_count); + DN_USize move_size = array_end - unstable_src; + DN_Memcpy(dest, unstable_src, move_size); + } + *size -= erase_count; + } + + result.items_erased = erase_count; + // NOTE: If we are erasing from the current index of the iterator to the end of the array then + // there's no more elements in the array to iterate. So the returned index should b + // one-past-last index + if (begin_index == start && end >= *size) { + result.it_index = *size; + } else { + result.it_index = start ? start - 1 : 0; + } + return result; +} + +DN_API void *DN_ArrayMakeArray(void *data, DN_USize *size, DN_USize max, DN_USize elem_size, DN_USize make_count, DN_ZMem z_mem) +{ + void *result = nullptr; + DN_USize new_size = *size + make_count; + if (new_size <= max) { + result = DN_Cast(char *) data + (elem_size * size[0]); + *size = new_size; + if (z_mem == DN_ZMem_Yes) + DN_Memset(result, 0, elem_size * make_count); + } + return result; +} + +DN_API void *DN_ArrayMakeArrayAssert(void *data, DN_USize *size, DN_USize max, DN_USize elem_size, DN_USize make_count, DN_ZMem z_mem, DN_CallSite call_site) +{ + void *result = DN_ArrayMakeArray(data, size, max, elem_size, make_count, z_mem); + DN_AssertCallSiteF(result, call_site, "Array out of space, failed to add %zu items: array=%p size=%zu max=%zu", make_count, data, *size, max); + return result; +} + +DN_API void *DN_ArrayAddArray(void *data, DN_USize *size, DN_USize max, DN_USize elem_size, void const *elems, DN_USize elems_count, DN_ArrayAdd add) +{ + void *result = DN_ArrayMakeArray(data, size, max, elem_size, elems_count, DN_ZMem_No); + if (result) { + if (add == DN_ArrayAdd_Append) { + DN_Memcpy(result, elems, elems_count * elem_size); + } else { + char *move_dest = DN_Cast(char *)data + (elems_count * elem_size); // Shift elements forward + char *move_src = DN_Cast(char *)data; + DN_Memmove(move_dest, move_src, elem_size * size[0]); + DN_Memcpy(data, elems, elem_size * elems_count); + } + } + return result; +} + +DN_API void *DN_ArrayAddArrayAssert(void *data, DN_USize *size, DN_USize max, DN_USize elem_size, void const *elems, DN_USize elems_count, DN_ArrayAdd add, DN_CallSite call_site) +{ + void *result = DN_ArrayAddArray(data, size, max, elem_size, elems, elems_count, add); + DN_AssertCallSiteF(result, call_site, "Array out of space, failed to add %zu items: array=%p size=%zu max=%zu", elems_count, data, *size, max); + return result; +} + +DN_API bool DN_ArrayResizeFromArena(void **data, DN_USize *size, DN_USize *max, DN_USize elem_size, DN_Pool *pool, DN_USize new_max) +{ + bool result = true; + if (new_max != *max) { + DN_USize bytes_to_alloc = elem_size * new_max; + void *buffer = DN_PoolNewArray(pool, DN_U8, bytes_to_alloc); + if (buffer) { + DN_USize bytes_to_copy = elem_size * DN_Min(*size, new_max); + DN_Memcpy(buffer, *data, bytes_to_copy); + DN_PoolDealloc(pool, *data); + *data = buffer; + *max = new_max; + *size = DN_Min(*size, new_max); + } else { + result = false; + } + } + + return result; +} + +DN_API bool DN_ArrayResizeFromPool(void **data, DN_USize *size, DN_USize *max, DN_USize elem_size, DN_Pool *pool, DN_USize new_max) +{ + bool result = true; + if (new_max != *max) { + DN_USize bytes_to_alloc = elem_size * new_max; + void *buffer = DN_PoolNewArray(pool, DN_U8, bytes_to_alloc); + if (buffer) { + DN_USize bytes_to_copy = elem_size * DN_Min(*size, new_max); + DN_Memcpy(buffer, *data, bytes_to_copy); + DN_PoolDealloc(pool, *data); + *data = buffer; + *max = new_max; + *size = DN_Min(*size, new_max); + } else { + result = false; + } + } + + return result; +} + +DN_API bool DN_ArrayResizeFromArena(void **data, DN_USize *size, DN_USize *max, DN_USize elem_size, DN_Arena *arena, DN_USize new_max) +{ + bool result = true; + if (new_max != *max) { + DN_USize bytes_to_alloc = elem_size * new_max; + void *buffer = DN_ArenaNewArray(arena, DN_U8, bytes_to_alloc, DN_ZMem_No); + if (buffer) { + DN_USize bytes_to_copy = elem_size * DN_Min(*size, new_max); + DN_Memcpy(buffer, *data, bytes_to_copy); + *data = buffer; + *max = new_max; + *size = DN_Min(*size, new_max); + } else { + result = false; + } + } + + return result; +} + +DN_API bool DN_ArrayGrowFromPool(void **data, DN_USize size, DN_USize *max, DN_USize elem_size, DN_Pool *pool, DN_USize new_max) +{ + bool result = true; + if (new_max > *max) + result = DN_ArrayResizeFromPool(data, &size, max, elem_size, pool, new_max); + return result; +} + +DN_API bool DN_ArrayGrowFromArena(void **data, DN_USize size, DN_USize *max, DN_USize elem_size, DN_Arena *arena, DN_USize new_max) +{ + bool result = true; + if (new_max > *max) + result = DN_ArrayResizeFromArena(data, &size, max, elem_size, arena, new_max); + return result; +} + + +DN_API bool DN_ArrayGrowIfNeededFromPool(void **data, DN_USize size, DN_USize *max, DN_USize elem_size, DN_Pool *pool, DN_USize add_count) +{ + bool result = true; + DN_USize new_size = size + add_count; + if (new_size > *max) { + DN_USize new_max = DN_Max(DN_Max(*max * 2, new_size), 8); + result = DN_ArrayResizeFromPool(data, &size, max, elem_size, pool, new_max); + } + return result; +} + +DN_API bool DN_ArrayGrowIfNeededFromArena(void **data, DN_USize size, DN_USize *max, DN_USize elem_size, DN_Arena *arena, DN_USize add_count) +{ + bool result = true; + DN_USize new_size = size + add_count; + if (new_size > *max) { + DN_USize new_max = DN_Max(DN_Max(*max * 2, new_size), 8); + result = DN_ArrayResizeFromArena(data, &size, max, elem_size, arena, new_max); + } + return result; +} + +DN_API void *DN_SinglyLLDetach(void **link, void **next) +{ + void *result = *link; + if (*link) { + *link = *next; + *next = nullptr; + } + return result; +} + +DN_API bool DN_RingHasSpace(DN_Ring const *ring, DN_U64 size) +{ + DN_U64 avail = ring->write_pos - ring->read_pos; + DN_U64 space = ring->size - avail; + bool result = space >= size; + return result; +} + +DN_API bool DN_RingHasData(DN_Ring const *ring, DN_U64 size) +{ + DN_U64 data = ring->write_pos - ring->read_pos; + bool result = data >= size; + return result; +} + +DN_API void DN_RingWrite(DN_Ring *ring, void const *src, DN_U64 src_size) +{ + DN_Assert(src_size <= ring->size); + DN_U64 offset = ring->write_pos % ring->size; + DN_U64 bytes_before_split = ring->size - offset; + DN_U64 pre_split_bytes = DN_Min(bytes_before_split, src_size); + DN_U64 post_split_bytes = src_size - pre_split_bytes; + void const *pre_split_data = src; + void const *post_split_data = ((char *)src + pre_split_bytes); + DN_Memcpy(ring->base + offset, pre_split_data, pre_split_bytes); + DN_Memcpy(ring->base, post_split_data, post_split_bytes); + ring->write_pos += src_size; +} + +DN_API void DN_RingRead(DN_Ring *ring, void *dest, DN_U64 dest_size) +{ + DN_Assert(dest_size <= ring->size); + DN_U64 offset = ring->read_pos % ring->size; + DN_U64 bytes_before_split = ring->size - offset; + DN_U64 pre_split_bytes = DN_Min(bytes_before_split, dest_size); + DN_U64 post_split_bytes = dest_size - pre_split_bytes; + DN_Memcpy(dest, ring->base + offset, pre_split_bytes); + DN_Memcpy((char *)dest + pre_split_bytes, ring->base, post_split_bytes); + ring->read_pos += dest_size; +} + +#if defined(__cplusplus) +template +DN_DSMap DN_DSMapInit(DN_Arena *arena, DN_U32 size, DN_DSMapFlags flags) +{ + DN_AssertF(DN_IsPowerOfTwo(size), "Power-of-two size required, given size was '%u'", size); + DN_Assert(arena); + + DN_DSMap result = {}; + if (size <= 0) + return result; + if (!arena) + return result; + + result.arena = arena; + result.pool = DN_PoolFromArena(arena, DN_POOL_DEFAULT_ALIGN); + result.hash_to_slot = DN_ArenaNewArray(result.arena, DN_U32, size, DN_ZMem_Yes); + result.slots = DN_ArenaNewArray(result.arena, DN_DSMapSlot, size, DN_ZMem_Yes); + result.occupied = 1; // For sentinel + result.size = size; + result.initial_size = size; + result.flags = flags; + DN_AssertF(result.hash_to_slot && result.slots, "We pre-allocated a block of memory sufficient in size for the 2 arrays. Maybe the pointers needed extra space because of natural alignment?"); + return result; +} + +template +void DN_DSMapDeinit(DN_DSMap *map, DN_ZMem z_mem) +{ + if (!map) + return; + // TODO(doyle): Use z_mem + (void)z_mem; + DN_MemListDeinit(map->arena->mem); + *map = {}; +} + +template +bool DN_DSMapIsValid(DN_DSMap const *map) +{ + bool result = map && + map->arena && + map->hash_to_slot && // Hash to slot mapping array must be allocated + map->slots && // Slots array must be allocated + (map->size & (map->size - 1)) == 0 && // Must be power of two size + map->occupied >= 1; // DN_DS_MAP_SENTINEL_SLOT takes up one slot + return result; +} + +template +DN_U32 DN_DSMapHash(DN_DSMap const *map, DN_DSMapKey key) +{ + DN_U32 result = 0; + if (!map) + return result; + + if (key.type == DN_DSMapKeyType_U64NoHash) { + result = DN_Cast(DN_U32) key.u64; + return result; + } + + if (key.type == DN_DSMapKeyType_BufferAsU64NoHash) { + result = key.hash; + return result; + } + + DN_U32 seed = map->hash_seed ? map->hash_seed : DN_DS_MAP_DEFAULT_HASH_SEED; + if (map->hash_function) { + map->hash_function(key, seed); + } else { + // NOTE: Courtesy of Demetri Spanos (which this hash table was inspired + // from), the following is a hashing function snippet provided for + // reliable, quick and simple quality hashing functions for hash table + // use. + // Source: https://github.com/demetri/scribbles/blob/c475464756c104c91bab83ed4e14badefef12ab5/hashing/ub_aware_hash_functions.c + + char const *key_ptr = nullptr; + DN_U32 len = 0; + DN_U32 h = seed; + switch (key.type) { + case DN_DSMapKeyType_BufferAsU64NoHash: /*FALLTHRU*/ + case DN_DSMapKeyType_U64NoHash: DN_AssertInvalidCodePath; /*FALLTHRU*/ + case DN_DSMapKeyType_Invalid: break; + + case DN_DSMapKeyType_Buffer: + key_ptr = DN_Cast(char const *) key.buffer_data; + len = key.buffer_size; + break; + + case DN_DSMapKeyType_U64: + key_ptr = DN_Cast(char const *) & key.u64; + len = sizeof(key.u64); + break; + } + + // Murmur3 32-bit without UB unaligned accesses + // DN_U32 mur3_32_no_UB(const void *key, int len, DN_U32 h) + + // main body, work on 32-bit blocks at a time + for (DN_U32 i = 0; i < len / 4; i++) { + DN_U32 k; + memcpy(&k, &key_ptr[i * 4], sizeof(k)); + + k *= 0xcc9e2d51; + k = ((k << 15) | (k >> 17)) * 0x1b873593; + h = (((h ^ k) << 13) | ((h ^ k) >> 19)) * 5 + 0xe6546b64; + } + + // load/mix up to 3 remaining tail bytes into a tail block + DN_U32 t = 0; + uint8_t *tail = ((uint8_t *)key_ptr) + 4 * (len / 4); + switch (len & 3) { + case 3: t ^= tail[2] << 16; + case 2: t ^= tail[1] << 8; + case 1: { + t ^= tail[0] << 0; + h ^= ((0xcc9e2d51 * t << 15) | (0xcc9e2d51 * t >> 17)) * 0x1b873593; + } + } + + // finalization mix, including key length + h = ((h ^ len) ^ ((h ^ len) >> 16)) * 0x85ebca6b; + h = (h ^ (h >> 13)) * 0xc2b2ae35; + result = h ^ (h >> 16); + } + return result; +} + +template +DN_U32 DN_DSMapHashToSlotIndex(DN_DSMap const *map, DN_DSMapKey key) +{ + DN_Assert(key.type != DN_DSMapKeyType_Invalid); + DN_U32 result = DN_DS_MAP_SENTINEL_SLOT; + if (!DN_DSMapIsValid(map)) + return result; + + result = key.hash & (map->size - 1); + for (;;) { + if (result == DN_DS_MAP_SENTINEL_SLOT) // Sentinel is reserved + result++; + + if (map->hash_to_slot[result] == DN_DS_MAP_SENTINEL_SLOT) // Slot is vacant, can use + return result; + + DN_DSMapSlot *slot = map->slots + map->hash_to_slot[result]; + if (slot->key.type == DN_DSMapKeyType_Invalid || (slot->key.hash == key.hash && slot->key == key)) + return result; + + result = (result + 1) & (map->size - 1); + } +} + +template +DN_DSMapResult DN_DSMapFind(DN_DSMap const *map, DN_DSMapKey key) +{ + DN_DSMapResult result = {}; + if (DN_DSMapIsValid(map)) { + DN_U32 index = DN_DSMapHashToSlotIndex(map, key); + if (index != DN_DS_MAP_SENTINEL_SLOT && map->hash_to_slot[index] == DN_DS_MAP_SENTINEL_SLOT) { + result.slot = map->slots; // NOTE: Set to sentinel value + } else { + result.slot = map->slots + map->hash_to_slot[index]; + result.found = true; + } + result.value = &result.slot->value; + } + return result; +} + +template +DN_DSMapResult DN_DSMapMake(DN_DSMap *map, DN_DSMapKey key) +{ + DN_DSMapResult result = {}; + if (!DN_DSMapIsValid(map)) + return result; + + DN_U32 index = DN_DSMapHashToSlotIndex(map, key); + if (map->hash_to_slot[index] == DN_DS_MAP_SENTINEL_SLOT) { + // NOTE: Create the slot + if (index != DN_DS_MAP_SENTINEL_SLOT) + map->hash_to_slot[index] = map->occupied++; + + // NOTE: Check if resize is required + bool map_is_75pct_full = (map->occupied * 4) > (map->size * 3); + if (map_is_75pct_full) { + if (!DN_DSMapResize(map, map->size * 2)) + return result; + result = DN_DSMapMake(map, key); + } else { + result.slot = map->slots + map->hash_to_slot[index]; + result.slot->key = key; // NOTE: Assign key to new slot + if ((key.type == DN_DSMapKeyType_Buffer || + key.type == DN_DSMapKeyType_BufferAsU64NoHash) && + !key.no_copy_buffer) + result.slot->key.buffer_data = DN_PoolNewArrayCopy(&map->pool, char, key.buffer_data, key.buffer_size); + } + } else { + result.slot = map->slots + map->hash_to_slot[index]; + result.found = true; + } + + result.value = &result.slot->value; + DN_Assert(result.slot->key.type != DN_DSMapKeyType_Invalid); + return result; +} + +template +DN_DSMapResult DN_DSMapSet(DN_DSMap *map, DN_DSMapKey key, T const &value) +{ + DN_DSMapResult result = {}; + if (!DN_DSMapIsValid(map)) + return result; + + result = DN_DSMapMake(map, key); + result.slot->value = value; + return result; +} + +template +DN_DSMapResult DN_DSMapFindKeyU64(DN_DSMap const *map, DN_U64 key) +{ + DN_DSMapKey map_key = DN_DSMapKeyU64(map, key); + DN_DSMapResult result = DN_DSMapFind(map, map_key); + return result; +} + +template +DN_DSMapResult DN_DSMapMakeKeyU64(DN_DSMap *map, DN_U64 key) +{ + DN_DSMapKey map_key = DN_DSMapKeyU64(map, key); + DN_DSMapResult result = DN_DSMapMake(map, map_key); + return result; +} + +template +DN_DSMapResult DN_DSMapSetKeyU64(DN_DSMap *map, DN_U64 key, T const &value) +{ + DN_DSMapKey map_key = DN_DSMapKeyU64(map, key); + DN_DSMapResult result = DN_DSMapSet(map, map_key, value); + return result; +} + +template +DN_DSMapResult DN_DSMapFindKeyStr8(DN_DSMap const *map, DN_Str8 key) +{ + DN_DSMapKey map_key = DN_DSMapKeyStr8(map, key); + DN_DSMapResult result = DN_DSMapFind(map, map_key); + return result; +} + +template +DN_DSMapResult DN_DSMapMakeKeyStr8(DN_DSMap *map, DN_Str8 key) +{ + DN_DSMapKey map_key = DN_DSMapKeyStr8(map, key); + DN_DSMapResult result = DN_DSMapMake(map, map_key); + return result; +} + +template +DN_DSMapResult DN_DSMapSetKeyStr8(DN_DSMap *map, DN_Str8 key, T const &value) +{ + DN_DSMapKey map_key = DN_DSMapKeyStr8(map, key); + DN_DSMapResult result = DN_DSMapSet(map, map_key); + return result; +} + +template +bool DN_DSMapResize(DN_DSMap *map, DN_U32 size) +{ + if (!DN_DSMapIsValid(map) || size < map->occupied || size < map->initial_size) + return false; + + DN_Arena *prev_arena = map->arena; + DN_MemList *new_mem = prev_arena->mem; + DN_MemList prev_mem = *prev_arena->mem; + prev_arena->mem = &prev_mem; + + *new_mem = {}; + new_mem->funcs = prev_mem.funcs; + new_mem->flags = prev_mem.flags; + + DN_Arena new_arena = {}; + new_arena.mem = new_mem; + + DN_DSMap new_map = DN_DSMapInit(&new_arena, size, map->flags); + if (!DN_DSMapIsValid(&new_map)) + return false; + + new_map.initial_size = map->initial_size; + for (DN_U32 old_index = 1 /*Sentinel*/; old_index < map->occupied; old_index++) { + DN_DSMapSlot *old_slot = map->slots + old_index; + DN_DSMapKey old_key = old_slot->key; + if (old_key.type == DN_DSMapKeyType_Invalid) + continue; + DN_DSMapSet(&new_map, old_key, old_slot->value); + } + + if ((map->flags & DN_DSMapFlags_DontFreeArenaOnResize) == 0) + DN_DSMapDeinit(map, DN_ZMem_No); + *map = new_map; // Update the map inplace + map->arena = prev_arena; // Restore the previous arena pointer, it's been de-init-ed + *map->arena = new_arena; // Re-init the old arena with the new data + map->pool.arena = map->arena; + return true; +} + +template +bool DN_DSMapErase(DN_DSMap *map, DN_DSMapKey key) +{ + if (!DN_DSMapIsValid(map)) + return false; + + DN_U32 index = DN_DSMapHashToSlotIndex(map, key); + if (index == 0) + return true; + + DN_U32 slot_index = map->hash_to_slot[index]; + if (slot_index == DN_DS_MAP_SENTINEL_SLOT) + return false; + + // NOTE: Mark the slot as unoccupied + map->hash_to_slot[index] = DN_DS_MAP_SENTINEL_SLOT; + + DN_DSMapSlot *slot = map->slots + slot_index; + if (!slot->key.no_copy_buffer) + DN_PoolDealloc(&map->pool, DN_Cast(void *) slot->key.buffer_data); + *slot = {}; // TODO: Optional? + + if (map->occupied > 1 /*Sentinel*/) { + // NOTE: Repair the hash chain, e.g. rehash all the items after the removed + // element and reposition them if necessary. + for (DN_U32 probe_index = index;;) { + probe_index = (probe_index + 1) & (map->size - 1); + if (map->hash_to_slot[probe_index] == DN_DS_MAP_SENTINEL_SLOT) + break; + + DN_DSMapSlot *probe = map->slots + map->hash_to_slot[probe_index]; + DN_U32 new_index = probe->key.hash & (map->size - 1); + if (index <= probe_index) { + if (index < new_index && new_index <= probe_index) + continue; + } else { + if (index < new_index || new_index <= probe_index) + continue; + } + + map->hash_to_slot[index] = map->hash_to_slot[probe_index]; + map->hash_to_slot[probe_index] = DN_DS_MAP_SENTINEL_SLOT; + index = probe_index; + } + + // NOTE: We have erased a slot from the hash table, this leaves a gap + // in our contiguous array. After repairing the chain, the hash mapping + // is correct. + // We will now fill in the vacant spot that we erased using the last + // element in the slot list. + if (map->occupied >= 3 /*Ignoring sentinel, at least 2 other elements to unstable erase*/) { + DN_U32 last_index = map->occupied - 1; + if (last_index != slot_index) { + // NOTE: Copy in last slot to the erase slot + DN_DSMapSlot *last_slot = map->slots + last_index; + map->slots[slot_index] = *last_slot; + + // NOTE: Update the hash-to-slot mapping for the value that was copied in + DN_U32 hash_to_slot_index = DN_DSMapHashToSlotIndex(map, last_slot->key); + map->hash_to_slot[hash_to_slot_index] = slot_index; + *last_slot = {}; // TODO: Optional? + } + } + } + + map->occupied--; + bool map_is_below_25pct_full = (map->occupied * 4) < (map->size * 1); + if (map_is_below_25pct_full && (map->size / 2) >= map->initial_size) + DN_DSMapResize(map, map->size / 2); + + return true; +} + +template +bool DN_DSMapEraseKeyU64(DN_DSMap *map, DN_U64 key) +{ + DN_DSMapKey map_key = DN_DSMapKeyU64(map, key); + bool result = DN_DSMapErase(map, map_key); + return result; +} + +template +bool DN_DSMapEraseKeyStr8(DN_DSMap *map, DN_Str8 key) +{ + DN_DSMapKey map_key = DN_DSMapKeyStr8(map, key); + bool result = DN_DSMapErase(map, map_key); + return result; +} + +template +DN_DSMapKey DN_DSMapKeyBuffer(DN_DSMap const *map, void const *data, DN_USize size) +{ + DN_Assert(size > 0 && size <= UINT32_MAX); + DN_DSMapKey result = {}; + result.type = DN_DSMapKeyType_Buffer; + result.buffer_data = data; + result.buffer_size = DN_Cast(DN_U32) size; + result.hash = DN_DSMapHash(map, result); + return result; +} + +template +DN_DSMapKey DN_DSMapKeyBufferAsU64NoHash(DN_DSMap const *map, void const *data, DN_USize size) +{ + DN_DSMapKey result = {}; + result.type = DN_DSMapKeyType_BufferAsU64NoHash; + result.buffer_data = data; + result.buffer_size = DN_Cast(DN_U32) size; + DN_Assert(size >= sizeof(result.hash)); + DN_Memcpy(&result.hash, data, sizeof(result.hash)); + return result; +} + +template +DN_DSMapKey DN_DSMapKeyU64(DN_DSMap const *map, DN_U64 u64) +{ + DN_DSMapKey result = {}; + result.type = DN_DSMapKeyType_U64; + result.u64 = u64; + result.hash = DN_DSMapHash(map, result); + return result; +} + +template +DN_DSMapKey DN_DSMapKeyStr8(DN_DSMap const *map, DN_Str8 string) +{ + DN_DSMapKey result = DN_DSMapKeyBuffer(map, string.data, string.size); + return result; +} + +// NOTE: DN_DSMap +DN_API DN_DSMapKey DN_DSMapKeyU64NoHash(DN_U64 u64) +{ + DN_DSMapKey result = {}; + result.type = DN_DSMapKeyType_U64NoHash; + result.u64 = u64; + result.hash = DN_Cast(DN_U32) u64; + return result; +} + +DN_API bool DN_DSMapKeyEquals(DN_DSMapKey lhs, DN_DSMapKey rhs) +{ + bool result = false; + if (lhs.type == rhs.type && lhs.hash == rhs.hash) { + switch (lhs.type) { + case DN_DSMapKeyType_Invalid: result = true; break; + case DN_DSMapKeyType_U64NoHash: result = true; break; + case DN_DSMapKeyType_U64: result = lhs.u64 == rhs.u64; break; + + case DN_DSMapKeyType_BufferAsU64NoHash: /*FALLTHRU*/ + case DN_DSMapKeyType_Buffer: { + if (lhs.buffer_size == rhs.buffer_size) + result = DN_Memcmp(lhs.buffer_data, rhs.buffer_data, lhs.buffer_size) == 0; + } break; + } + } + return result; +} + +DN_API bool operator==(DN_DSMapKey lhs, DN_DSMapKey rhs) +{ + bool result = DN_DSMapKeyEquals(lhs, rhs); + return result; +} +#endif // defined(__cplusplus) + +DN_API void DN_BinPackU64(DN_BinPack *pack, DN_BinPackMode mode, DN_U64 *item) +{ + DN_U64 const VALUE_MASK = 0b0111'1111; + DN_U8 const CONTINUE_BIT = 0b1000'0000; + + if (mode == DN_BinPackMode_Serialise) { + DN_U64 it = *item; + do { + DN_U8 write_value = DN_Cast(DN_U8)(it & VALUE_MASK); + it >>= 7; + if (it) + write_value |= CONTINUE_BIT; + DN_Str8BuilderAppendBytesCopy(&pack->writer, &write_value, sizeof(write_value)); + } while (it); + } else { + *item = 0; + DN_USize bits_read = 0; + for (DN_U8 src = CONTINUE_BIT; (src & CONTINUE_BIT) && bits_read < 64; bits_read += 7) { + src = pack->read.data[pack->read_index++]; + DN_U8 masked_src = src & VALUE_MASK; + *item |= (DN_Cast(DN_U64) masked_src << bits_read); + } + } +} + +DN_API void DN_BinPackVarInt_(DN_BinPack *pack, DN_BinPackMode mode, void *item, DN_USize size) +{ + DN_U64 value = 0; + DN_AssertF(size <= sizeof(value), + "An item larger than 64 bits (%zu) is trying to be packed as a variable integer which is not supported", + size * 8); + + if (mode == DN_BinPackMode_Serialise) // Read `item` into U64 `value` + DN_Memcpy(&value, item, size); + + DN_BinPackU64(pack, mode, &value); + + if (mode == DN_BinPackMode_Deserialise) // Write U64 `value` into `item` + DN_Memcpy(item, &value, size); +} + +DN_API bool DN_BinPackIsEndOfReadStream(DN_BinPack const *pack) +{ + bool result = pack->read_index == pack->read.size; + return result; +} + +DN_API void DN_BinPackUSize(DN_BinPack *pack, DN_BinPackMode mode, DN_USize *item) +{ + DN_BinPackVarInt_(pack, mode, item, sizeof(*item)); +} + +DN_API void DN_BinPackU32(DN_BinPack *pack, DN_BinPackMode mode, DN_U32 *item) +{ + DN_BinPackVarInt_(pack, mode, item, sizeof(*item)); +} + +DN_API void DN_BinPackU16(DN_BinPack *pack, DN_BinPackMode mode, DN_U16 *item) +{ + DN_BinPackVarInt_(pack, mode, item, sizeof(*item)); +} + +DN_API void DN_BinPackU8(DN_BinPack *pack, DN_BinPackMode mode, DN_U8 *item) +{ + DN_BinPackVarInt_(pack, mode, item, sizeof(*item)); +} + +DN_API void DN_BinPackI64(DN_BinPack *pack, DN_BinPackMode mode, DN_I64 *item) +{ + DN_BinPackVarInt_(pack, mode, item, sizeof(*item)); +} + +DN_API void DN_BinPackI32(DN_BinPack *pack, DN_BinPackMode mode, DN_I32 *item) +{ + DN_BinPackVarInt_(pack, mode, item, sizeof(*item)); +} + +DN_API void DN_BinPackI16(DN_BinPack *pack, DN_BinPackMode mode, DN_I16 *item) +{ + DN_BinPackVarInt_(pack, mode, item, sizeof(*item)); +} + +DN_API void DN_BinPackI8(DN_BinPack *pack, DN_BinPackMode mode, DN_I8 *item) +{ + DN_BinPackVarInt_(pack, mode, item, sizeof(*item)); +} + +DN_API void DN_BinPackF64(DN_BinPack *pack, DN_BinPackMode mode, DN_F64 *item) +{ + DN_BinPackVarInt_(pack, mode, item, sizeof(*item)); +} + +DN_API void DN_BinPackF32(DN_BinPack *pack, DN_BinPackMode mode, DN_F32 *item) +{ + DN_BinPackVarInt_(pack, mode, item, sizeof(*item)); +} + +DN_API void DN_BinPackV2(DN_BinPack *pack, DN_BinPackMode mode, DN_V2F32 *item) +{ + DN_BinPackF32(pack, mode, &item->x); + DN_BinPackF32(pack, mode, &item->y); +} + +DN_API void DN_BinPackV4(DN_BinPack *pack, DN_BinPackMode mode, DN_V4F32 *item) +{ + DN_BinPackF32(pack, mode, &item->x); + DN_BinPackF32(pack, mode, &item->y); + DN_BinPackF32(pack, mode, &item->z); + DN_BinPackF32(pack, mode, &item->w); +} + +DN_API void DN_BinPackBool(DN_BinPack *pack, DN_BinPackMode mode, bool *item) +{ + DN_BinPackVarInt_(pack, mode, item, sizeof(*item)); +} + +DN_API void DN_BinPackStr8FromArena(DN_BinPack *pack, DN_Arena *arena, DN_BinPackMode mode, DN_Str8 *string) +{ + DN_BinPackVarInt_(pack, mode, &string->size, sizeof(string->size)); + if (mode == DN_BinPackMode_Serialise) { + DN_Str8BuilderAppendBytesCopy(&pack->writer, string->data, string->size); + } else { + DN_Str8 src = DN_Str8Subset(pack->read, pack->read_index, string->size); + *string = DN_Str8FromStr8Arena(src, arena); + pack->read_index += src.size; + } +} + +DN_API void DN_BinPackStr8FromPool(DN_BinPack *pack, DN_Pool *pool, DN_BinPackMode mode, DN_Str8 *string) +{ + DN_BinPackVarInt_(pack, mode, &string->size, sizeof(string->size)); + if (mode == DN_BinPackMode_Serialise) { + DN_Str8BuilderAppendBytesCopy(&pack->writer, string->data, string->size); + } else { + DN_Str8 src = DN_Str8Subset(pack->read, pack->read_index, string->size); + *string = DN_Str8FromStr8Pool(src, pool); + pack->read_index += src.size; + } +} + +DN_API DN_Str8 DN_BinPackStr8FromBuffer(DN_BinPack *pack, DN_BinPackMode mode, char *ptr, DN_USize *size, DN_USize max) +{ + DN_BinPackCBuffer(pack, mode, ptr, size, max); + DN_Str8 result = DN_Str8FromPtr(ptr, *size); + return result; +} + +DN_API void DN_BinPackBytesFromArena(DN_BinPack *pack, DN_Arena *arena, DN_BinPackMode mode, void **ptr, DN_USize *size) +{ + DN_Str8 string = DN_Str8FromPtr(*ptr, *size); + DN_BinPackStr8FromArena(pack, arena, mode, &string); + *ptr = string.data; + *size = string.size; +} + +DN_API void DN_BinPackBytesFromPool(DN_BinPack *pack, DN_Pool *pool, DN_BinPackMode mode, void **ptr, DN_USize *size) +{ + DN_Str8 string = DN_Str8FromPtr(*ptr, *size); + DN_BinPackStr8FromPool(pack, pool, mode, &string); + *ptr = string.data; + *size = string.size; +} + +DN_API void DN_BinPackCArray(DN_BinPack *pack, DN_BinPackMode mode, void *ptr, DN_USize size) +{ + DN_BinPackVarInt_(pack, mode, &size, sizeof(size)); + if (mode == DN_BinPackMode_Serialise) { + DN_Str8BuilderAppendBytesCopy(&pack->writer, ptr, size); + } else { + DN_Str8 src = DN_Str8Subset(pack->read, pack->read_index, size); + DN_Assert(src.size == size); + DN_Memcpy(ptr, src.data, DN_Min(src.size, size)); + pack->read_index += src.size; + } +} + +DN_API void DN_BinPackCBuffer(DN_BinPack *pack, DN_BinPackMode mode, char *ptr, DN_USize *size, DN_USize max) +{ + if (mode == DN_BinPackMode_Serialise) { + DN_BinPackUSize(pack, mode, size); + DN_Str8BuilderAppendBytesCopy(&pack->writer, ptr, *size); + } else { + DN_U64 size_u64 = 0; + DN_BinPackU64(pack, mode, &size_u64); + DN_Assert(size_u64 < DN_USIZE_MAX); + DN_Assert(size_u64 <= max); + + *size = DN_Min(size_u64, max); + DN_Memcpy(ptr, pack->read.data + pack->read_index, *size); + pack->read_index += size_u64; + } +} + +DN_API DN_Str8 DN_BinPackBuild(DN_BinPack const *pack, DN_Arena *arena) +{ + DN_Str8 result = DN_Str8FromStr8BuilderArena(&pack->writer, arena); + return result; +} + +DN_API DN_CSVTokeniser DN_CSVTokeniserInit(DN_Str8 string, char delimiter) +{ + DN_CSVTokeniser result = {}; + result.string = string; + result.delimiter = delimiter; + return result; +} + +DN_API bool DN_CSVTokeniserValid(DN_CSVTokeniser *tokeniser) +{ + bool result = tokeniser && !tokeniser->bad; + return result; +} + +static void DN_CSVTokeniserEatNewLines_(DN_CSVTokeniser *tokeniser) +{ + char const *end = tokeniser->string.data + tokeniser->string.size; + while (tokeniser->it[0] == '\n' || tokeniser->it[0] == '\r') + if (++tokeniser->it == end) + break; +} + +DN_API bool DN_CSVTokeniserNextRow(DN_CSVTokeniser *tokeniser) +{ + bool result = false; + if (DN_CSVTokeniserValid(tokeniser) && tokeniser->string.size) { + // NOTE: First time querying row iterator is nil, let tokeniser advance + if (tokeniser->it) { + // NOTE: Only advance the tokeniser if we're at the end of the line and + // there's more to tokenise. + char const *end = tokeniser->string.data + tokeniser->string.size; + if (tokeniser->it != end && tokeniser->end_of_line) { + tokeniser->end_of_line = false; + result = true; + } + } + } + + return result; +} + +DN_API DN_Str8 DN_CSVTokeniserNextField(DN_CSVTokeniser *tokeniser) +{ + DN_Str8 result = {}; + if (!DN_CSVTokeniserValid(tokeniser)) + return result; + + if (tokeniser->string.size == 0) { + tokeniser->bad = true; + return result; + } + + // NOTE: First time tokeniser is invoked with a string, set up initial state. + char const *string_end = tokeniser->string.data + tokeniser->string.size; + if (!tokeniser->it) { + tokeniser->it = tokeniser->string.data; + DN_CSVTokeniserEatNewLines_(tokeniser); // NOTE: Skip any leading new lines + } + + // NOTE: Tokeniser pointing at end, no more valid data to parse. + if (tokeniser->it == string_end) + return result; + + // NOTE: Scan forward until the next control character. + // 1. '"' Double quoted field, extract everything between the quotes. + // 2. tokeniser->delimiter End of the field, extract everything leading up to the delimiter. + // 3. '\n' Last field in record, extract everything leading up the the new line. + char const *begin = tokeniser->it; + while (tokeniser->it != string_end && (tokeniser->it[0] != '"' && + tokeniser->it[0] != tokeniser->delimiter && + tokeniser->it[0] != '\n')) + tokeniser->it++; + + bool quoted_field = (tokeniser->it != string_end) && tokeniser->it[0] == '"'; + if (quoted_field) { + begin = ++tokeniser->it; // Begin after the quote + + // NOTE: Scan forward until the next '"' which marks the end + // of the field unless it is escaped by another '"'. + find_next_quote: + while (tokeniser->it != string_end && tokeniser->it[0] != '"') + tokeniser->it++; + + // NOTE: If we encounter a '"' right after, the quotes were escaped + // and we need to skip to the next instance of a '"'. + if (tokeniser->it != string_end && tokeniser->it + 1 != string_end && tokeniser->it[1] == '"') { + tokeniser->it += 2; + goto find_next_quote; + } + } + + // NOTE: Mark the end of the field + char const *end = tokeniser->it; + tokeniser->end_of_line = tokeniser->it == string_end || end[0] == '\n'; + + // NOTE: In files with \r\n style new lines ensure that we don't include + // the \r byte in the CSV field we produce. + if (end != string_end && end[0] == '\n') { + DN_Assert((uintptr_t)(end - 1) > (uintptr_t)tokeniser->string.data && + "Internal error: The string iterator is pointing behind the start of the string we're reading"); + if (end[-1] == '\r') + end = end - 1; + } + + // NOTE: Quoted fields may have whitespace after the closing quote, we skip + // until we reach the field terminator. + if (quoted_field) + while (tokeniser->it != string_end && (tokeniser->it[0] != tokeniser->delimiter && tokeniser->it[0] != '\n')) + tokeniser->it++; + + // NOTE: Advance the tokeniser past the field terminator. + if (tokeniser->it != string_end) + tokeniser->it++; + + // NOTE: Generate the record + result.data = DN_Cast(char *) begin; + result.size = DN_Cast(int)(end - begin); + return result; +} + +DN_API DN_Str8 DN_CSVTokeniserNextColumn(DN_CSVTokeniser *tokeniser) +{ + DN_Str8 result = {}; + if (!DN_CSVTokeniserValid(tokeniser)) + return result; + + // NOTE: End of line, the user must explicitly advance to the next row + if (tokeniser->end_of_line) + return result; + + // NOTE: Advance tokeniser to the next field in the row + result = DN_CSVTokeniserNextField(tokeniser); + return result; +} + +DN_API void DN_CSVTokeniserSkipLine(DN_CSVTokeniser *tokeniser) +{ + while (DN_CSVTokeniserValid(tokeniser) && !tokeniser->end_of_line) + DN_CSVTokeniserNextColumn(tokeniser); + DN_CSVTokeniserNextRow(tokeniser); +} + +DN_API int DN_CSVTokeniserNextN(DN_CSVTokeniser *tokeniser, DN_Str8 *fields, int fields_size, bool column_iterator) +{ + if (!DN_CSVTokeniserValid(tokeniser) || !fields || fields_size <= 0) + return 0; + + int result = 0; + for (; result < fields_size; result++) { + fields[result] = column_iterator ? DN_CSVTokeniserNextColumn(tokeniser) : DN_CSVTokeniserNextField(tokeniser); + if (!DN_CSVTokeniserValid(tokeniser) || !fields[result].data) + break; + } + + return result; +} + +DN_API int DN_CSVTokeniserNextColumnN(DN_CSVTokeniser *tokeniser, DN_Str8 *fields, int fields_size) +{ + int result = DN_CSVTokeniserNextN(tokeniser, fields, fields_size, true /*column_iterator*/); + return result; +} + +DN_API int DN_CSVTokeniserNextFieldN(DN_CSVTokeniser *tokeniser, DN_Str8 *fields, int fields_size) +{ + int result = DN_CSVTokeniserNextN(tokeniser, fields, fields_size, false /*column_iterator*/); + return result; +} + +DN_API void DN_CSVTokeniserSkipLineN(DN_CSVTokeniser *tokeniser, int count) +{ + for (int i = 0; i < count && DN_CSVTokeniserValid(tokeniser); i++) + DN_CSVTokeniserSkipLine(tokeniser); +} + +DN_API void DN_CSVPackU64(DN_CSVPack *pack, DN_CSVSerialise serialise, DN_U64 *value) +{ + if (serialise == DN_CSVSerialise_Read) { + DN_Str8 csv_value = DN_CSVTokeniserNextColumn(&pack->read_tokeniser); + DN_U64FromResult to_u64 = DN_U64FromStr8(csv_value, 0); + DN_Assert(to_u64.success); + *value = to_u64.value; + } else { + DN_Str8BuilderAppendF(&pack->write_builder, "%s%I64u", pack->write_column++ ? "," : "", *value); + } +} + +DN_API void DN_CSVPackI64(DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I64 *value) +{ + if (serialise == DN_CSVSerialise_Read) { + DN_Str8 csv_value = DN_CSVTokeniserNextColumn(&pack->read_tokeniser); + DN_I64FromResult to_i64 = DN_I64FromStr8(csv_value, 0); + DN_Assert(to_i64.success); + *value = to_i64.value; + } else { + DN_Str8BuilderAppendF(&pack->write_builder, "%s%I64d", pack->write_column++ ? "," : "", *value); + } +} + +DN_API void DN_CSVPackI32(DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I32 *value) +{ + DN_I64 u64 = *value; + DN_CSVPackI64(pack, serialise, &u64); + if (serialise == DN_CSVSerialise_Read) + *value = DN_SaturateCastI64ToI32(u64); +} + +DN_API void DN_CSVPackI16(DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I16 *value) +{ + DN_I64 u64 = *value; + DN_CSVPackI64(pack, serialise, &u64); + if (serialise == DN_CSVSerialise_Read) + *value = DN_SaturateCastI64ToI16(u64); +} + +DN_API void DN_CSVPackI8(DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I8 *value) +{ + DN_I64 u64 = *value; + DN_CSVPackI64(pack, serialise, &u64); + if (serialise == DN_CSVSerialise_Read) + *value = DN_SaturateCastI64ToI8(u64); +} + +DN_API void DN_CSVPackU32(DN_CSVPack *pack, DN_CSVSerialise serialise, DN_U32 *value) +{ + DN_U64 u64 = *value; + DN_CSVPackU64(pack, serialise, &u64); + if (serialise == DN_CSVSerialise_Read) + *value = DN_SaturateCastU64ToU32(u64); +} + +DN_API void DN_CSVPackU16(DN_CSVPack *pack, DN_CSVSerialise serialise, DN_U16 *value) +{ + DN_U64 u64 = *value; + DN_CSVPackU64(pack, serialise, &u64); + if (serialise == DN_CSVSerialise_Read) + *value = DN_SaturateCastU64ToU16(u64); +} + +DN_API void DN_CSVPackBoolAsU64(DN_CSVPack *pack, DN_CSVSerialise serialise, bool *value) +{ + DN_U64 u64 = *value; + DN_CSVPackU64(pack, serialise, &u64); + if (serialise == DN_CSVSerialise_Read) + *value = u64 ? 1 : 0; +} + +DN_API void DN_CSVPackStr8(DN_CSVPack *pack, DN_CSVSerialise serialise, DN_Str8 *str8, DN_Arena *arena) +{ + if (serialise == DN_CSVSerialise_Read) { + DN_Str8 csv_value = DN_CSVTokeniserNextColumn(&pack->read_tokeniser); + *str8 = DN_Str8FromStr8Arena(csv_value, arena); + } else { + DN_Str8BuilderAppendF(&pack->write_builder, "%s%.*s", pack->write_column++ ? "," : "", DN_Str8PrintFmt(*str8)); + } +} + +DN_API void DN_CSVPackBuffer(DN_CSVPack *pack, DN_CSVSerialise serialise, void *dest, DN_USize *size) +{ + if (serialise == DN_CSVSerialise_Read) { + DN_Str8 csv_value = DN_CSVTokeniserNextColumn(&pack->read_tokeniser); + *size = DN_Min(*size, csv_value.size); + DN_Memcpy(dest, csv_value.data, *size); + } else { + DN_Str8BuilderAppendF(&pack->write_builder, "%s%.*s", pack->write_column++ ? "," : "", DN_Cast(int)(*size), DN_Cast(char *)dest); + } +} + +DN_API void DN_CSVPackBufferWithMax(DN_CSVPack *pack, DN_CSVSerialise serialise, void *dest, DN_USize *size, DN_USize max) +{ + if (serialise == DN_CSVSerialise_Read) + *size = max; + DN_CSVPackBuffer(pack, serialise, dest, size); +} + +DN_API bool DN_CSVPackNewLine(DN_CSVPack *pack, DN_CSVSerialise serialise) +{ + bool result = true; + if (serialise == DN_CSVSerialise_Read) { + result = DN_CSVTokeniserNextRow(&pack->read_tokeniser); + } else { + pack->write_column = 0; + result = DN_Str8BuilderAppendRef(&pack->write_builder, DN_Str8Lit("\n")); + } + return result; +} + +DN_API void DN_LeakTrackAlloc_(DN_LeakTracker *leak, void *ptr, DN_USize size, bool leak_permitted) +{ + if (!ptr) + return; + + DN_TicketMutex_Begin(&leak->alloc_table_mutex); + + DN_Str8 stack_trace = DN_Str8FromStackTraceNowHeap(128, 3 /*skip*/); + DN_DSMap *alloc_table = &leak->alloc_table; + DN_DSMapResult alloc_entry = DN_DSMapMakeKeyU64(alloc_table, DN_Cast(DN_U64) ptr); + DN_LeakAlloc *alloc = alloc_entry.value; + if (alloc_entry.found) { + if ((alloc->flags & DN_LeakAllocFlag_Freed) == 0) { + DN_Str8x32 alloc_size = DN_Str8x32FromByteCountU64Auto(alloc->size); + DN_Str8x32 new_alloc_size = DN_Str8x32FromByteCountU64Auto(size); + DN_AssertAlwaysF( + alloc->flags & DN_LeakAllocFlag_Freed, + "This 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 " + "DN_LeakTrackDealloc()\n" + "\n" + "The pointer (0x%p) originally allocated %.*s at:\n" + "\n" + "%.*s\n" + "\n" + "The pointer is allocating %.*s again at:\n" + "\n" + "%.*s\n", + ptr, + DN_Str8PrintFmt(alloc_size), + DN_Str8PrintFmt(alloc->stack_trace), + DN_Str8PrintFmt(new_alloc_size), + DN_Str8PrintFmt(stack_trace)); + } + + // NOTE: Pointer was reused, clean up the prior entry + leak->alloc_table_bytes_allocated_for_stack_traces -= alloc->stack_trace.size; + leak->alloc_table_bytes_allocated_for_stack_traces -= alloc->freed_stack_trace.size; + + DN_OS_MemDealloc(alloc->stack_trace.data); + DN_OS_MemDealloc(alloc->freed_stack_trace.data); + *alloc = {}; + } + + alloc->ptr = ptr; + alloc->size = size; + alloc->stack_trace = stack_trace; + alloc->flags |= leak_permitted ? DN_LeakAllocFlag_LeakPermitted : 0; + leak->alloc_table_bytes_allocated_for_stack_traces += alloc->stack_trace.size; + DN_TicketMutex_End(&leak->alloc_table_mutex); +} + +DN_API void DN_LeakTrackDealloc_(DN_LeakTracker *leak, void *ptr) +{ + if (!ptr) + return; + + DN_TicketMutex_Begin(&leak->alloc_table_mutex); + + DN_Str8 stack_trace = DN_Str8FromStackTraceNowHeap(128, 3 /*skip*/); + DN_DSMap *alloc_table = &leak->alloc_table; + DN_DSMapResult alloc_entry = DN_DSMapFindKeyU64(alloc_table, DN_Cast(uintptr_t) ptr); + DN_AssertAlwaysF(alloc_entry.found, + "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); + + DN_LeakAlloc *alloc = alloc_entry.value; + if (alloc->flags & DN_LeakAllocFlag_Freed) { + DN_Str8x32 freed_size = DN_Str8x32FromByteCountU64Auto(alloc->freed_size); + DN_AssertAlwaysF((alloc->flags & DN_LeakAllocFlag_Freed) == 0, + "Double 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 %.*s at:\n" + "\n" + "%.*s\n" + "\n" + "The pointer was freed at:\n" + "\n" + "%.*s\n" + "\n" + "The pointer is being freed again at:\n" + "\n" + "%.*s\n", + ptr, + DN_Str8PrintFmt(freed_size), + DN_Str8PrintFmt(alloc->stack_trace), + DN_Str8PrintFmt(alloc->freed_stack_trace), + DN_Str8PrintFmt(stack_trace)); + } + + DN_Assert(alloc->freed_stack_trace.size == 0); + alloc->flags |= DN_LeakAllocFlag_Freed; + alloc->freed_stack_trace = stack_trace; + leak->alloc_table_bytes_allocated_for_stack_traces += alloc->freed_stack_trace.size; + DN_TicketMutex_End(&leak->alloc_table_mutex); +} + +DN_API void DN_LeakDump_(DN_LeakTracker *leak) +{ + DN_U64 leak_count = 0; + DN_U64 leaked_bytes = 0; + for (DN_USize index = 1; index < leak->alloc_table.occupied; index++) { + DN_DSMapSlot *slot = leak->alloc_table.slots + index; + DN_LeakAlloc *alloc = &slot->value; + bool alloc_leaked = (alloc->flags & DN_LeakAllocFlag_Freed) == 0; + bool leak_permitted = (alloc->flags & DN_LeakAllocFlag_LeakPermitted); + if (alloc_leaked && !leak_permitted) { + leaked_bytes += alloc->size; + leak_count++; + DN_Str8x32 alloc_size = DN_Str8x32FromByteCountU64Auto(alloc->size); + DN_LogWarningF( + "Pointer (0x%p) leaked %.*s at:\n" + "%.*s", + alloc->ptr, + DN_Str8PrintFmt(alloc_size), + DN_Str8PrintFmt(alloc->stack_trace)); + } + } + + if (leak_count) { + DN_Str8x32 leak_size = DN_Str8x32FromByteCountU64Auto(leaked_bytes); + DN_LogWarningF("There were %I64u leaked allocations totalling %.*s", leak_count, DN_Str8PrintFmt(leak_size)); + } +} + +#if DN_WITH_OS +#if defined(DN_PLATFORM_POSIX) +#include // get_nprocs +#include // getpagesize +#endif + +static void *DN_OS_MemFuncsHeapAllocShim_(DN_USize size) +{ + void *result = DN_OS_MemAlloc(size, DN_ZMem_Yes); + return result; +} + +DN_API DN_MemFuncs DN_MemFuncsFromType(DN_MemFuncsType type) +{ + DN_MemFuncs result = {}; + result.type = type; + switch (type) { + case DN_MemFuncsType_Nil: break; + case DN_MemFuncsType_Heap: { + result.heap_alloc = DN_OS_MemFuncsHeapAllocShim_; + result.heap_dealloc = DN_OS_MemDealloc; + } break; + + case DN_MemFuncsType_Virtual: { + DN_Core *dn = DN_Get(); + DN_Assert(dn->init_flags & DN_InitFlags_OS); + result.virtual_page_size = dn->os.page_size; + result.virtual_reserve = DN_OS_MemReserve; + result.virtual_commit = DN_OS_MemCommit; + result.virtual_release = DN_OS_MemRelease; + } break; + } + return result; +} + +DN_API DN_MemFuncs DN_MemFuncsDefault() +{ + DN_Core *dn = DN_Get(); + DN_MemFuncsType type = DN_MemFuncsType_Heap; + if (dn->os_init) { +#if !defined(DN_PLATFORM_EMSCRIPTEN) + type = DN_MemFuncsType_Virtual; +#endif + } + DN_MemFuncs result = DN_MemFuncsFromType(type); + return result; +} + +DN_API DN_MemList DN_MemListFromHeap(DN_U64 size, DN_MemFlags flags) +{ + DN_MemFuncs mem_funcs = DN_MemFuncsFromType(DN_MemFuncsType_Heap); + DN_MemList result = DN_MemListFromMemFuncs(size, size, flags, mem_funcs); + return result; +} + +DN_API DN_MemList DN_MemListFromVMem(DN_U64 reserve, DN_U64 commit, DN_MemFlags flags) +{ + DN_MemFuncs mem_funcs = DN_MemFuncsFromType(DN_MemFuncsType_Virtual); + DN_MemList result = DN_MemListFromMemFuncs(reserve, commit, flags, mem_funcs); + return result; +} + +DN_API DN_Arena DN_ArenaFromHeap(DN_U64 size, DN_MemFlags flags) +{ + DN_MemList mem = DN_MemListFromHeap(size, flags); + DN_Arena result = {}; + result.flags |= DN_ArenaFlags_OwnsMemList; + result.mem = DN_MemListNewCopy(&mem, DN_MemList, &mem); + return result; +} + +DN_API DN_Arena DN_ArenaFromVMem(DN_U64 reserve, DN_U64 commit, DN_MemFlags flags) +{ + DN_MemList mem = DN_MemListFromVMem(reserve, commit, flags); + DN_Arena result = {}; + result.flags |= DN_ArenaFlags_OwnsMemList; + result.mem = DN_MemListNewCopy(&mem, DN_MemList, &mem); + return result; +} + +DN_API void DN_ArenaDeinit(DN_Arena *arena) +{ + if (arena->flags & DN_ArenaFlags_OwnsMemList) + DN_MemListDeinit(arena->mem); +} + +DN_API DN_Str8 DN_Str8FromHeapF(DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_USize size = DN_FmtVSize(fmt, args); + DN_Str8 result = DN_Str8FromHeap(size, DN_ZMem_No); + DN_VSNPrintF(result.data, DN_Cast(int)(result.size + 1), fmt, args); + va_end(args); + return result; +} + +DN_API DN_Str8 DN_Str8FromHeap(DN_USize size, DN_ZMem z_mem) +{ + DN_Str8 result = {}; + result.data = DN_Cast(char *)DN_OS_MemAlloc(size + 1, z_mem); + if (result.data) { + result.size = size; + result.data[result.size] = 0; + } + return result; +} + +DN_API DN_Str8 DN_Str8PadNewLines(DN_Arena *arena, DN_Str8 src, DN_Str8 pad) +{ + // TODO: Implement this without requiring TLS so it can go into base strings + DN_TCScratch scratch = DN_TCScratchBeginArena(&arena, 1); + DN_Str8Builder builder = DN_Str8BuilderFromArena(&scratch.arena); + DN_Str8BSplitResult split = DN_Str8BSplit(src, DN_Str8Lit("\n")); + while (split.lhs.size) { + DN_Str8BuilderAppendRef(&builder, pad); + DN_Str8BuilderAppendRef(&builder, split.lhs); + split = DN_Str8BSplit(split.rhs, DN_Str8Lit("\n")); + if (split.lhs.size) + DN_Str8BuilderAppendRef(&builder, DN_Str8Lit("\n")); + } + DN_Str8 result = DN_Str8FromStr8BuilderArena(&builder, arena); + DN_TCScratchEnd(&scratch); + return result; +} + +DN_API DN_Str8 DN_Str8BuilderBuildFromHeap(DN_Str8Builder const *builder) +{ + DN_Str8 result = DN_ZeroInit; + if (!builder || builder->string_size <= 0 || builder->count <= 0) + return result; + + result.data = DN_Cast(char *) DN_OS_MemAlloc(builder->string_size + 1, DN_ZMem_No); + if (!result.data) + return result; + + for (DN_Str8Link *link = builder->head; link; link = link->next) { + DN_Memcpy(result.data + result.size, link->string.data, link->string.size); + result.size += link->string.size; + } + + result.data[result.size] = 0; + DN_Assert(result.size == builder->string_size); + return result; +} + +DN_API void DN_OS_LogPrint(DN_LogTypeParam type, void *user_data, DN_CallSite call_site, DN_LogFlags flags, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_Assert(user_data); + DN_OSCore *os = DN_Cast(DN_OSCore *)user_data; + + // NOTE: Open log file for appending if requested + DN_TicketMutex_Begin(&os->log_file_mutex); + if (os->log_to_file && !os->log_file.handle && !os->log_file.error) { + DN_TCScratch scratch = DN_TCScratchBeginArena(nullptr, 0); + DN_Str8 exe_dir = DN_OS_EXEDir(&scratch.arena); + DN_Str8 log_path = DN_OS_PathF(&scratch.arena, "%.*s/dn.log", DN_Str8PrintFmt(exe_dir)); + os->log_file = DN_OS_FileOpen(log_path, DN_OSFileOpen_CreateAlways, DN_OSFileAccess_AppendOnly, nullptr); + DN_TCScratchEnd(&scratch); + } + DN_TicketMutex_End(&os->log_file_mutex); + + bool print_prefix = DN_BitIsNotSet(flags, DN_LogFlags_NoPrefix); + char prefix_buffer[128] = {}; + DN_LogPrefixSize prefix_size = {}; + if (print_prefix) { + DN_LogStyle style = {}; + if (!os->log_no_colour) { + style.colour = true; + style.bold = DN_LogBold_Yes; + if (type.is_u32_enum) { + switch (type.u32) { + case DN_LogType_Debug: { + style.colour = false; + style.bold = DN_LogBold_No; + } break; + + case DN_LogType_Info: { + style.g = 0x87; + style.b = 0xff; + } break; + + case DN_LogType_Warning: { + style.r = 0xff; + style.g = 0xff; + } break; + + case DN_LogType_Error: { + style.r = 0xff; + } break; + } + } + } + + DN_Date os_date = DN_OS_DateLocalTimeNow(); + DN_LogDate log_date = {}; + log_date.year = os_date.year; + log_date.month = os_date.month; + log_date.day = os_date.day; + log_date.hour = os_date.hour; + log_date.minute = os_date.minutes; + log_date.second = os_date.seconds; + prefix_size = DN_LogMakePrefix(style, type, call_site, log_date, prefix_buffer, sizeof(prefix_buffer)); + } + + va_list args_copy; + va_copy(args_copy, args); + DN_TicketMutex_Begin(&os->log_file_mutex); + { + if (print_prefix) { + DN_OS_FileWrite(&os->log_file, DN_Str8FromPtr(prefix_buffer, prefix_size.size), nullptr); + DN_OS_FileWriteF(&os->log_file, nullptr, "%*s ", DN_Cast(int) prefix_size.padding, ""); + } + DN_OS_FileWriteFV(&os->log_file, nullptr, fmt, args_copy); + if (!DN_BitIsSet(flags, DN_LogFlags_NoNewLine)) + DN_OS_FileWrite(&os->log_file, DN_Str8Lit("\n"), nullptr); + } + DN_TicketMutex_End(&os->log_file_mutex); + va_end(args_copy); + + DN_TicketMutex_Begin(&os->log_mutex); + { + if (print_prefix) + DN_OS_PrintF(DN_OSPrintDest_Err, "%.*s%*s ", DN_Cast(int) prefix_size.size, prefix_buffer, DN_Cast(int) prefix_size.padding, ""); + + if (DN_BitIsSet(flags, DN_LogFlags_NoNewLine)) + DN_OS_PrintFV(DN_OSPrintDest_Err, fmt, args); + else + DN_OS_PrintLnFV(DN_OSPrintDest_Err, fmt, args); + } + DN_TicketMutex_End(&os->log_mutex); +} + +DN_API void DN_OS_SetLogPrintFuncToOS() +{ + DN_Core *dn = DN_Get(); + DN_LogSetPrintFunc(DN_OS_LogPrint, &dn->os); +} + +// NOTE: Date +DN_API DN_Str8x32 DN_OS_DateLocalTimeStr8(DN_Date time, char date_separator, char hms_separator) +{ + DN_Str8x32 result = DN_Str8x32FromFmt("%hu%c%02hhu%c%02hhu %02hhu%c%02hhu%c%02hhu", + time.year, + date_separator, + time.month, + date_separator, + time.day, + time.hour, + hms_separator, + time.minutes, + hms_separator, + time.seconds); + return result; +} + +DN_API DN_Str8x32 DN_OS_DateLocalTimeStr8Now(char date_separator, char hms_separator) +{ + DN_Date time = DN_OS_DateLocalTimeNow(); + DN_Str8x32 result = DN_OS_DateLocalTimeStr8(time, date_separator, hms_separator); + return result; +} + +// NOTE: Other +DN_API DN_Str8 DN_OS_EXEDir(DN_Arena *arena) +{ + DN_Str8 result = {}; + if (!arena) + return result; + DN_TCScratch scratch = DN_TCScratchBeginArena(&arena, 1); + DN_Str8 exe_path = DN_OS_EXEPath(&scratch.arena); + DN_Str8 separators[] = {DN_Str8Lit("/"), DN_Str8Lit("\\")}; + DN_Str8BSplitResult split = DN_Str8BSplitLastArray(exe_path, separators, DN_ArrayCountU(separators)); + result = DN_Str8FromStr8Arena(split.lhs, arena); + DN_TCScratchEnd(&scratch); + return result; +} + +// NOTE: Counters +DN_API DN_F64 DN_OS_PerfCounterS(uint64_t begin, uint64_t end) +{ + uint64_t frequency = DN_OS_PerfCounterFrequency(); + uint64_t ticks = end - begin; + DN_F64 result = ticks / DN_Cast(DN_F64) frequency; + return result; +} + +DN_API DN_F64 DN_OS_PerfCounterMs(uint64_t begin, uint64_t end) +{ + uint64_t frequency = DN_OS_PerfCounterFrequency(); + uint64_t ticks = end - begin; + DN_F64 result = (ticks * 1'000) / DN_Cast(DN_F64) frequency; + return result; +} + +DN_API DN_F64 DN_OS_PerfCounterUs(uint64_t begin, uint64_t end) +{ + uint64_t frequency = DN_OS_PerfCounterFrequency(); + uint64_t ticks = end - begin; + DN_F64 result = (ticks * 1'000'000) / DN_Cast(DN_F64) frequency; + return result; +} + +DN_API DN_F64 DN_OS_PerfCounterNs(uint64_t begin, uint64_t end) +{ + uint64_t frequency = DN_OS_PerfCounterFrequency(); + uint64_t ticks = end - begin; + DN_F64 result = (ticks * 1'000'000'000) / DN_Cast(DN_F64) frequency; + return result; +} + +DN_API DN_OSTimer DN_OS_TimerBegin() +{ + DN_OSTimer result = {}; + result.start = DN_OS_PerfCounterNow(); + return result; +} + +DN_API void DN_OS_TimerEnd(DN_OSTimer *timer) +{ + timer->end = DN_OS_PerfCounterNow(); +} + +DN_API DN_F64 DN_OS_TimerS(DN_OSTimer timer) +{ + DN_F64 result = DN_OS_PerfCounterS(timer.start, timer.end); + return result; +} + +DN_API DN_F64 DN_OS_TimerMs(DN_OSTimer timer) +{ + DN_F64 result = DN_OS_PerfCounterMs(timer.start, timer.end); + return result; +} + +DN_API DN_F64 DN_OS_TimerUs(DN_OSTimer timer) +{ + DN_F64 result = DN_OS_PerfCounterUs(timer.start, timer.end); + return result; +} + +DN_API DN_F64 DN_OS_TimerNs(DN_OSTimer timer) +{ + DN_F64 result = DN_OS_PerfCounterNs(timer.start, timer.end); + return result; +} + +DN_API uint64_t DN_OS_EstimateTSCPerSecond(uint64_t duration_ms_to_gauge_tsc_frequency) +{ + uint64_t os_frequency = DN_OS_PerfCounterFrequency(); + uint64_t os_target_elapsed = duration_ms_to_gauge_tsc_frequency * os_frequency / 1000ULL; + uint64_t tsc_begin = DN_CPUGetTSC(); + uint64_t result = 0; + if (tsc_begin) { + uint64_t os_elapsed = 0; + for (uint64_t os_begin = DN_OS_PerfCounterNow(); os_elapsed < os_target_elapsed;) + os_elapsed = DN_OS_PerfCounterNow() - os_begin; + uint64_t tsc_end = DN_CPUGetTSC(); + uint64_t tsc_elapsed = tsc_end - tsc_begin; + result = tsc_elapsed / os_elapsed * os_frequency; + } + return result; +} + +DN_API bool DN_OS_PathIsOlderThan(DN_Str8 path, DN_Str8 check_against) +{ + DN_OSPathInfo file_info = DN_OS_PathInfo(path); + DN_OSPathInfo check_against_info = DN_OS_PathInfo(check_against); + bool result = !file_info.exists || file_info.last_write_time_in_s < check_against_info.last_write_time_in_s; + return result; +} + +DN_API bool DN_OS_FileWrite(DN_OSFile *file, DN_Str8 buffer, DN_ErrSink *error) +{ + bool result = DN_OS_FileWritePtr(file, buffer.data, buffer.size, error); + return result; +} + +struct DN_OSFileWriteChunker_ +{ + DN_ErrSink *err; + DN_OSFile *file; + bool success; +}; + +static char *DN_OS_FileWriteChunker_(const char *buf, void *user, int len) +{ + DN_OSFileWriteChunker_ *chunker = DN_Cast(DN_OSFileWriteChunker_ *)user; + chunker->success = DN_OS_FileWritePtr(chunker->file, buf, len, chunker->err); + char *result = chunker->success ? DN_Cast(char *) buf : nullptr; + return result; +} + +DN_API bool DN_OS_FileWriteFV(DN_OSFile *file, DN_ErrSink *error, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + bool result = false; + if (!file || !fmt) + return result; + + DN_OSFileWriteChunker_ chunker = {}; + chunker.err = error; + chunker.file = file; + char buffer[STB_SPRINTF_MIN]; + STB_SPRINTF_DECORATE(vsprintfcb)(DN_OS_FileWriteChunker_, &chunker, buffer, fmt, args); + + result = chunker.success; + return result; +} + +DN_API bool DN_OS_FileWriteF(DN_OSFile *file, DN_ErrSink *error, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool result = DN_OS_FileWriteFV(file, error, fmt, args); + va_end(args); + return result; +} + +DN_API DN_Str8 DN_OS_FileReadAll(DN_Allocator allocator, DN_Str8 path, DN_ErrSink *err) +{ + // NOTE: Query file size + DN_Str8 result = {}; + DN_OSPathInfo path_info = DN_OS_PathInfo(path); + if (!path_info.exists) { + DN_ErrSinkAppendF(err, 1, "File does not exist/could not be queried for reading '%.*s'", DN_Str8PrintFmt(path)); + return result; + } + + // NOTE: Allocate + DN_Arena temp_arena = {}; + if (allocator.type == DN_AllocatorType_Arena) { + DN_Arena *arena = DN_Cast(DN_Arena *) allocator.context; + temp_arena = DN_ArenaTempBeginFromArena(arena); + result = DN_Str8AllocArena(path_info.size, DN_ZMem_No, &temp_arena); + } else { + DN_Pool *pool = DN_Cast(DN_Pool *) allocator.context; + result = DN_Str8AllocPool(path_info.size, pool); + } + + if (!result.data) { + DN_Str8x32 bytes_str = DN_Str8x32FromByteCountU64Auto(path_info.size); + DN_ErrSinkAppendF(err, 1 /*err_code*/, "Failed to allocate %.*s for reading file '%.*s'", DN_Str8PrintFmt(bytes_str), DN_Str8PrintFmt(path)); + return result; + } + + // NOTE: Read all + DN_OSFile file = DN_OS_FileOpen(path, DN_OSFileOpen_OpenIfExist, DN_OSFileAccess_Read, err); + DN_OSFileRead read = DN_OS_FileRead(&file, result.data, result.size, err); + bool failed = file.error || !read.success; + + if (allocator.type == DN_AllocatorType_Arena) { + DN_ArenaTempEnd(&temp_arena, failed ? DN_ArenaReset_Yes : DN_ArenaReset_No); + } else { + if (failed) { + DN_Pool *pool = DN_Cast(DN_Pool *) allocator.context; + DN_PoolDealloc(pool, result.data); + } + } + + if (failed) + result = {}; + + DN_OS_FileClose(&file); + return result; +} + +DN_API DN_Str8 DN_OS_FileReadAllArena(DN_Arena *arena, DN_Str8 path, DN_ErrSink *err) +{ + DN_Allocator allocator = {}; + allocator.type = DN_AllocatorType_Arena; + allocator.context = arena; + DN_Str8 result = DN_OS_FileReadAll(allocator, path, err); + return result; +} + +DN_API DN_Str8 DN_OS_FileReadAllPool(DN_Pool *pool, DN_Str8 path, DN_ErrSink *err) +{ + DN_Allocator allocator = {}; + allocator.type = DN_AllocatorType_Pool; + allocator.context = pool; + DN_Str8 result = DN_OS_FileReadAll(allocator, path, err); + return result; +} + +DN_API bool DN_OS_FileWriteAll(DN_Str8 path, DN_Str8 buffer, DN_ErrSink *error) +{ + DN_OSFile file = DN_OS_FileOpen(path, DN_OSFileOpen_CreateAlways, DN_OSFileAccess_Write, error); + bool result = DN_OS_FileWrite(&file, buffer, error); + DN_OS_FileClose(&file); + return result; +} + +DN_API bool DN_OS_FileWriteAllFV(DN_Str8 file_path, DN_ErrSink *error, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_TCScratch scratch = DN_TCScratchBeginArena(nullptr, 0); + DN_Str8 buffer = DN_Str8FromFmtVArena(&scratch.arena, fmt, args); + bool result = DN_OS_FileWriteAll(file_path, buffer, error); + DN_TCScratchEnd(&scratch); + return result; +} + +DN_API bool DN_OS_FileWriteAllF(DN_Str8 file_path, DN_ErrSink *error, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool result = DN_OS_FileWriteAllFV(file_path, error, fmt, args); + va_end(args); + return result; +} + +DN_API bool DN_OS_FileWriteAllSafe(DN_Str8 path, DN_Str8 buffer, DN_ErrSink *error) +{ + DN_TCScratch scratch = DN_TCScratchBeginArena(nullptr, 0); + DN_Str8 tmp_path = DN_Str8FromFmtArena(&scratch.arena, "%.*s.tmp", DN_Str8PrintFmt(path)); + if (!DN_OS_FileWriteAll(tmp_path, buffer, error)) { + DN_TCScratchEnd(&scratch); + return false; + } + if (!DN_OS_FileCopy(tmp_path, path, true /*overwrite*/, error)) { + DN_TCScratchEnd(&scratch); + return false; + } + if (!DN_OS_PathDelete(tmp_path)) { + DN_TCScratchEnd(&scratch); + return false; + } + DN_TCScratchEnd(&scratch); + return true; +} + +DN_API bool DN_OS_FileWriteAllSafeFV(DN_Str8 path, DN_ErrSink *error, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_TCScratch scratch = DN_TCScratchBeginArena(nullptr, 0); + DN_Str8 buffer = DN_Str8FromFmtVArena(&scratch.arena, fmt, args); + bool result = DN_OS_FileWriteAllSafe(path, buffer, error); + DN_TCScratchEnd(&scratch); + return result; +} + +DN_API bool DN_OS_FileWriteAllSafeF(DN_Str8 path, DN_ErrSink *error, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool result = DN_OS_FileWriteAllSafeFV(path, error, fmt, args); + return result; +} + +DN_API DN_Str8 DN_OS_Str8FromPathInfoType(DN_OSPathInfoType type) +{ + DN_Str8 result = DN_Str8Lit("BAD PATH INFO TYPE"); + switch(type) { + case DN_OSPathInfoType_Unknown: result = DN_Str8Lit("Unknown"); break; + case DN_OSPathInfoType_Directory: result = DN_Str8Lit("Directory"); break; + case DN_OSPathInfoType_File: result = DN_Str8Lit("File"); break; + } + return result; +} + +DN_API bool DN_OS_PathAddRef(DN_Arena *arena, DN_OSPath *fs_path, DN_Str8 path) +{ + if (!arena || !fs_path || path.size == 0) + return false; + + if (path.size <= 0) + return true; + + DN_Str8 const delimiter_array[] = { + DN_Str8Lit("\\"), + DN_Str8Lit("/")}; + + if (fs_path->links_size == 0) + fs_path->has_prefix_path_separator = (path.data[0] == '/'); + + for (;;) { + DN_Str8BSplitResult delimiter = DN_Str8BSplitArray(path, delimiter_array, DN_ArrayCountU(delimiter_array)); + for (; delimiter.lhs.data; delimiter = DN_Str8BSplitArray(delimiter.rhs, delimiter_array, DN_ArrayCountU(delimiter_array))) { + if (delimiter.lhs.size <= 0) + continue; + + DN_OSPathLink *link = DN_ArenaNew(arena, DN_OSPathLink, DN_ZMem_Yes); + if (!link) + return false; + + link->string = delimiter.lhs; + link->prev = fs_path->tail; + if (fs_path->tail) + fs_path->tail->next = link; + else + fs_path->head = link; + fs_path->tail = link; + fs_path->links_size += 1; + fs_path->string_size += delimiter.lhs.size; + } + + if (!delimiter.lhs.data) + break; + } + + return true; +} + +DN_API bool DN_OS_PathAdd(DN_Arena *arena, DN_OSPath *fs_path, DN_Str8 path) +{ + DN_Str8 copy = DN_Str8FromStr8Arena(path, arena); + bool result = copy.size ? true : DN_OS_PathAddRef(arena, fs_path, copy); + return result; +} + +DN_API bool DN_OS_PathAddF(DN_Arena *arena, DN_OSPath *fs_path, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8 path = DN_Str8FromFmtVArena(arena, fmt, args); + va_end(args); + bool result = DN_OS_PathAddRef(arena, fs_path, path); + return result; +} + +DN_API bool DN_OS_PathPop(DN_OSPath *fs_path) +{ + if (!fs_path) + return false; + + if (fs_path->tail) { + DN_Assert(fs_path->head); + fs_path->links_size -= 1; + fs_path->string_size -= fs_path->tail->string.size; + fs_path->tail = fs_path->tail->prev; + if (fs_path->tail) + fs_path->tail->next = nullptr; + else + fs_path->head = nullptr; + } else { + DN_Assert(!fs_path->head); + } + + return true; +} + +DN_API DN_Str8 DN_OS_PathTo(DN_Arena *arena, DN_Str8 path, DN_Str8 path_separator) +{ + DN_OSPath fs_path = {}; + DN_OS_PathAddRef(arena, &fs_path, path); + DN_Str8 result = DN_OS_PathBuildWithSeparator(arena, &fs_path, path_separator); + return result; +} + +DN_API DN_Str8 DN_OS_PathToF(DN_Arena *arena, DN_Str8 path_separator, DN_FMT_ATTRIB char const *fmt, ...) +{ + DN_TCScratch scratch = DN_TCScratchBeginArena(&arena, 1); + va_list args; + va_start(args, fmt); + DN_Str8 path = DN_Str8FromFmtVArena(&scratch.arena, fmt, args); + va_end(args); + DN_Str8 result = DN_OS_PathTo(arena, path, path_separator); + DN_TCScratchEnd(&scratch); + return result; +} + +DN_API DN_Str8 DN_OS_Path(DN_Arena *arena, DN_Str8 path) +{ + DN_Str8 result = DN_OS_PathTo(arena, path, DN_OSPathSeperatorString); + return result; +} + +DN_API DN_Str8 DN_OS_PathF(DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, ...) +{ + DN_TCScratch scratch = DN_TCScratchBeginArena(&arena, 1); + va_list args; + va_start(args, fmt); + DN_Str8 path = DN_Str8FromFmtVArena(&scratch.arena, fmt, args); + va_end(args); + DN_Str8 result = DN_OS_Path(arena, path); + DN_TCScratchEnd(&scratch); + return result; +} + +DN_API DN_Str8 DN_OS_PathBuildWithSeparator(DN_Arena *arena, DN_OSPath const *fs_path, DN_Str8 path_separator) +{ + DN_Str8 result = {}; + if (!fs_path || fs_path->links_size <= 0) + return result; + + // NOTE: Each link except the last one needs the path separator appended to it, '/' or '\\' + DN_USize string_size = (fs_path->has_prefix_path_separator ? path_separator.size : 0) + fs_path->string_size + ((fs_path->links_size - 1) * path_separator.size); + result = DN_Str8AllocArena(string_size, DN_ZMem_No, arena); + if (result.data) { + char *dest = result.data; + if (fs_path->has_prefix_path_separator) { + DN_Memcpy(dest, path_separator.data, path_separator.size); + dest += path_separator.size; + } + + for (DN_OSPathLink *link = fs_path->head; link; link = link->next) { + DN_Str8 string = link->string; + DN_Memcpy(dest, string.data, string.size); + dest += string.size; + + if (link != fs_path->tail) { + DN_Memcpy(dest, path_separator.data, path_separator.size); + dest += path_separator.size; + } + } + } + + result.data[string_size] = 0; + return result; +} + +// NOTE: DN_OSExec +DN_API DN_OSExecResult DN_OS_Exec(DN_Str8Slice cmd_line, DN_OSExecArgs *args, DN_Arena *arena, DN_ErrSink *error) +{ + DN_OSExecAsyncHandle async_handle = DN_OS_ExecAsync(cmd_line, args, error); + DN_OSExecResult result = DN_OS_ExecWait(async_handle, arena, error); + return result; +} + +DN_API DN_OSExecResult DN_OS_ExecOrAbort(DN_Str8Slice cmd_line, DN_OSExecArgs *args, DN_Arena *arena) +{ + DN_ErrSink *error = DN_TCErrSinkBegin(DN_ErrSinkMode_Nil); + DN_OSExecResult result = DN_OS_Exec(cmd_line, args, arena, error); + if (result.os_error_code) + DN_ErrSinkEndExitIfErrorF(error, result.os_error_code, "OS failed to execute the requested command returning the error code %u", result.os_error_code); + + if (result.exit_code) + DN_ErrSinkEndExitIfErrorF(error, result.exit_code, "OS executed command and returned non-zero exit code %u", result.exit_code); + DN_ErrSinkEndIgnore(error); + return result; +} + +// NOTE: DN_OSThread +static void DN_OS_ThreadExecute_(void *user_context) +{ + DN_OSThread *thread = DN_Cast(DN_OSThread *) user_context; + DN_TCInitFromMemFuncs(&thread->context, thread->thread_id, thread->tc_init_args, DN_MemFuncsDefault()); + DN_TCEquip(&thread->context); + if (thread->is_lane_set) { + DN_OS_TCThreadLaneEquip(thread->lane); + DN_OS_ThreadSetNameFmt("L%02zu/%02zu T%zu", thread->lane.index, thread->lane.count, thread->thread_id); + } else { + DN_OS_ThreadSetNameFmt("T%zu", thread->lane.index, thread->lane.count, thread->thread_id); + } + DN_OS_SemaphoreWait(&thread->init_semaphore, DN_OS_SEMAPHORE_INFINITE_TIMEOUT); + thread->func(thread); +} + +DN_API void DN_OS_ThreadSetNameFmt(char const *fmt, ...) +{ + DN_TCCore *tls = DN_TCGet(); + va_list args; + va_start(args, fmt); + tls->name = DN_Str8x64FromFmtV(fmt, args); + va_end(args); + + DN_Str8 name = DN_Str8FromPtr(tls->name.data, tls->name.size); +#if defined(DN_PLATFORM_WIN32) + DN_OS_W32ThreadSetName(name); +#else + DN_OS_PosixThreadSetName(name); +#endif +} + +DN_API DN_OSThreadLane DN_OS_ThreadLaneInit(DN_USize index, DN_USize thread_count, DN_OSBarrier barrier, DN_UPtr *shared_mem) +{ + DN_OSThreadLane result = {}; + result.index = index; + result.count = thread_count; + result.barrier = barrier; + result.shared_mem = shared_mem; + return result; +} + +DN_API void DN_OS_ThreadLaneSync(DN_OSThreadLane *lane, void **ptr_to_share) +{ + if (!lane) + return; + + // NOTE: Write the pointer into shared memory (if we're the lane producing the data) + bool sharing = false; + if (ptr_to_share && *ptr_to_share) { + DN_Memcpy(lane->shared_mem, ptr_to_share, sizeof(*ptr_to_share)); + sharing = true; + } + + DN_OS_BarrierWait(&lane->barrier); // NOTE: Ensure sharing lane has completed the write + + // NOTE: Read pointer from shared memory (if we're the other lanes that read the data) + if (ptr_to_share && !(*ptr_to_share)) { + sharing = true; + DN_Memcpy(ptr_to_share, lane->shared_mem, sizeof(*ptr_to_share)); + } + + if (sharing) + DN_OS_BarrierWait(&lane->barrier); // NOTE: Ensure the reading lanes have completed the read +} + +DN_API DN_V2USize DN_OS_ThreadLaneRange(DN_OSThreadLane const *lane, DN_USize values_count) +{ + DN_USize values_per_thread = values_count / lane->count; + DN_USize rem_values = values_count % lane->count; + bool thread_has_leftovers = lane->index < rem_values; + DN_USize leftovers_before_this_thread_index = 0; + + if (thread_has_leftovers) + leftovers_before_this_thread_index = lane->index; + else + leftovers_before_this_thread_index = rem_values; + + DN_USize thread_start_index = (values_per_thread * lane->index) + leftovers_before_this_thread_index; + DN_USize thread_values_count = values_per_thread + (thread_has_leftovers ? 1 : 0); + + DN_V2USize result = {}; + result.begin = thread_start_index; + result.end = result.begin + thread_values_count; + return result; +} + +DN_API DN_OSThreadLaneway DN_OS_ThreadLanewayFromArgs(DN_OSThread* threads, DN_USize threads_count, DN_UPtr* shared_mem) +{ + DN_OSThreadLaneway result = {}; + result.threads = threads; + result.threads_count = threads_count; + result.shared_mem = shared_mem; + result.barrier = DN_OS_BarrierInit(DN_Cast(DN_U32) result.threads_count); + return result; +} + +DN_API DN_OSThreadLaneway DN_OS_ThreadLanewayFromArena(DN_USize threads_count, DN_Arena* arena) +{ + DN_U64 mem_p = DN_MemListPos(arena->mem); + DN_OSThreadLaneway result = {}; + DN_OSThread* threads = DN_ArenaNewArray(arena, DN_OSThread, threads_count, DN_ZMem_No); + DN_UPtr* shared_mem = DN_ArenaNewZ(arena, DN_UPtr); + if (threads && shared_mem) + result = DN_OS_ThreadLanewayFromArgs(threads, threads_count, shared_mem); + else + DN_MemListPopTo(arena->mem, mem_p); + return result; +} + +DN_API void DN_OS_ThreadLanewayDispatch(DN_OSThreadLaneway *laneway, DN_OSThreadFunc *entry_point, DN_TCInitArgs tc_init_args, void *user_context) +{ + for (DN_ForItSize(it, DN_OSThread, laneway->threads, laneway->threads_count)) { + DN_OSThreadLane lane = DN_OS_ThreadLaneInit(it.index, laneway->threads_count, laneway->barrier, laneway->shared_mem); + DN_OS_ThreadInit(it.data, entry_point, &lane, tc_init_args, user_context); + } +} + +DN_API void DN_OS_ThreadLanewayJoin(DN_OSThreadLaneway *laneway, DN_TCDeinitArenas deinit_arenas) +{ + for (DN_ForItSize(it, DN_OSThread, laneway->threads, laneway->threads_count)) + DN_OS_ThreadJoin(it.data, deinit_arenas); + DN_OS_BarrierDeinit(&laneway->barrier); +} + +DN_API DN_OSThreadLane *DN_OS_TCThreadLane() +{ + DN_TCCore *tc = DN_TCGet(); + DN_OSThreadLane *result = tc ? DN_Cast(DN_OSThreadLane *) tc->lane_opaque : nullptr; + return result; +} + +DN_API void DN_OS_TCThreadLaneSync(void **ptr_to_share) +{ + DN_OSThreadLane *lane = DN_OS_TCThreadLane(); + DN_OS_ThreadLaneSync(lane, ptr_to_share); +} + +DN_API DN_OSThreadLane DN_OS_TCThreadLaneEquip(DN_OSThreadLane lane) +{ + DN_TCCore *tc = DN_TCGet(); + DN_OSThreadLane *curr = DN_Cast(DN_OSThreadLane *) tc->lane_opaque; + DN_StaticAssert(sizeof(tc->lane_opaque) >= sizeof(DN_OSThreadLane)); + DN_OSThreadLane result = *curr; + *curr = lane; + return result; +} + +static DN_I32 DN_OS_AsyncThreadEntryPoint_(DN_OSThread *thread) +{ + DN_OS_ThreadSetNameFmt("%.*s", DN_Str8PrintFmt(thread->name)); + DN_OSAsyncCore *async = DN_Cast(DN_OSAsyncCore *) thread->user_context; + DN_Ring *ring = &async->ring; + for (;;) { + DN_OS_SemaphoreWait(&async->worker_sem, UINT32_MAX); + if (async->join_threads) + break; + + DN_OSAsyncTask task = {}; + for (DN_OS_MutexScope(&async->ring_mutex)) { + if (DN_RingHasData(ring, sizeof(task))) + DN_RingRead(ring, &task, sizeof(task)); + } + + if (task.work.func) { + DN_OS_ConditionVariableBroadcast(&async->ring_write_cv); // Resume any blocked ring write(s) + + DN_OSAsyncWorkArgs args = {}; + args.input = task.work.input; + args.thread = thread; + + DN_AtomicAddU32(&async->busy_threads, 1); + task.work.func(args); + DN_AtomicSubU32(&async->busy_threads, 1); + + if (task.completion_sem.handle != 0) + DN_OS_SemaphoreIncrement(&task.completion_sem, 1); + } + } + + return 0; +} + +DN_API void DN_OS_AsyncInit(DN_OSAsyncCore *async, char *base, DN_USize base_size, DN_OSThread *threads, DN_U32 threads_size) +{ + DN_Assert(async); + async->ring.size = base_size; + async->ring.base = base; + async->ring_mutex = DN_OS_MutexInit(); + async->ring_write_cv = DN_OS_ConditionVariableInit(); + async->worker_sem = DN_OS_SemaphoreInit(0); + async->thread_count = threads_size; + async->threads = threads; + for (DN_ForIndexU(index, async->thread_count)) { + DN_OSThread *thread = async->threads + index; + DN_OS_ThreadInit(thread, DN_OS_AsyncThreadEntryPoint_, /*lane=*/ nullptr, DN_TCInitArgsDefault(), async); + } +} + +DN_API void DN_OS_AsyncDeinit(DN_OSAsyncCore *async) +{ + DN_Assert(async); + DN_AtomicSetValue32(&async->join_threads, true); + DN_OS_SemaphoreIncrement(&async->worker_sem, async->thread_count); + for (DN_ForItSize(it, DN_OSThread, async->threads, async->thread_count)) + DN_OS_ThreadJoin(it.data, DN_TCDeinitArenas_Yes); +} + +static bool DN_OS_AsyncQueueTask_(DN_OSAsyncCore *async, DN_OSAsyncTask const *task, DN_U64 wait_time_ms) { + DN_U64 end_time_ms = DN_OS_DateUnixTimeMs() + wait_time_ms; + bool result = false; + for (DN_OS_MutexScope(&async->ring_mutex)) { + for (;;) { + if (DN_RingHasSpace(&async->ring, sizeof(*task))) { + DN_RingWriteStruct(&async->ring, task); + result = true; + break; + } + DN_OS_ConditionVariableWaitUntil(&async->ring_write_cv, &async->ring_mutex, end_time_ms); + if (DN_OS_DateUnixTimeMs() >= end_time_ms) + break; + } + } + + if (result) + DN_OS_SemaphoreIncrement(&async->worker_sem, 1); // Flag that a job is available + + return result; +} + +DN_API bool DN_OS_AsyncQueueWork(DN_OSAsyncCore *async, DN_OSAsyncWorkFunc *func, void *input, DN_U64 wait_time_ms) +{ + DN_OSAsyncTask task = {}; + task.work.func = func; + task.work.input = input; + bool result = DN_OS_AsyncQueueTask_(async, &task, wait_time_ms); + return result; +} + +DN_API DN_OSAsyncTask DN_OS_AsyncQueueTask(DN_OSAsyncCore *async, DN_OSAsyncWorkFunc *func, void *input, DN_U64 wait_time_ms) +{ + DN_OSAsyncTask result = {}; + result.work.func = func; + result.work.input = input; + result.completion_sem = DN_OS_SemaphoreInit(0); + result.queued = DN_OS_AsyncQueueTask_(async, &result, wait_time_ms); + if (!result.queued) + DN_OS_SemaphoreDeinit(&result.completion_sem); + return result; +} + +DN_API bool DN_OS_AsyncWaitTask(DN_OSAsyncTask *task, DN_U32 timeout_ms) +{ + bool result = true; + if (!task->queued) + return result; + + DN_OSSemaphoreWaitResult wait = DN_OS_SemaphoreWait(&task->completion_sem, timeout_ms); + result = wait == DN_OSSemaphoreWaitResult_Success; + if (result) + DN_OS_SemaphoreDeinit(&task->completion_sem); + return result; +} + +DN_API DN_LogStyle DN_OS_PrintStyleColour(uint8_t r, uint8_t g, uint8_t b, DN_LogBold bold) +{ + DN_LogStyle result = {}; + result.bold = bold; + result.colour = true; + result.r = r; + result.g = g; + result.b = b; + return result; +} + +DN_API DN_LogStyle DN_OS_PrintStyleColourU32(uint32_t rgb, DN_LogBold bold) +{ + uint8_t r = (rgb >> 24) & 0xFF; + uint8_t g = (rgb >> 16) & 0xFF; + uint8_t b = (rgb >> 8) & 0xFF; + DN_LogStyle result = DN_OS_PrintStyleColour(r, g, b, bold); + return result; +} + +DN_API DN_LogStyle DN_OS_PrintStyleBold() +{ + DN_LogStyle result = {}; + result.bold = DN_LogBold_Yes; + return result; +} + +DN_API void DN_OS_Print(DN_OSPrintDest dest, DN_Str8 string) +{ + DN_Assert(dest == DN_OSPrintDest_Out || dest == DN_OSPrintDest_Err); + +#if defined(DN_PLATFORM_WIN32) + // NOTE: Get the output handles from kernel + DN_THREAD_LOCAL void *std_out_print_handle = nullptr; + DN_THREAD_LOCAL void *std_err_print_handle = nullptr; + DN_THREAD_LOCAL bool std_out_print_to_console = false; + DN_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 (dest == DN_OSPrintDest_Err) { + print_handle = std_err_print_handle; + print_to_console = std_err_print_to_console; + } + + // NOTE: Write the string + DN_Assert(string.size < DN_Cast(unsigned long) - 1); + unsigned long bytes_written = 0; + (void)bytes_written; + if (print_to_console) + WriteConsoleA(print_handle, string.data, DN_Cast(unsigned long) string.size, &bytes_written, nullptr); + else + WriteFile(print_handle, string.data, DN_Cast(unsigned long) string.size, &bytes_written, nullptr); +#else + fprintf(dest == DN_OSPrintDest_Out ? stdout : stderr, "%.*s", DN_Str8PrintFmt(string)); +#endif +} + +DN_API void DN_OS_PrintF(DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_OS_PrintFV(dest, fmt, args); + va_end(args); +} + +DN_API void DN_OS_PrintFStyle(DN_OSPrintDest dest, DN_LogStyle style, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_OS_PrintFVStyle(dest, style, fmt, args); + va_end(args); +} + +DN_API void DN_OS_PrintStyle(DN_OSPrintDest dest, DN_LogStyle style, DN_Str8 string) +{ + if (string.data && string.size) { + if (style.colour) { + DN_Str8x32 colour = DN_Str8x32FromANSIColourCodeU8RGB(DN_ANSIColourMode_Fg, style.r, style.g, style.b); + DN_OS_Print(dest, DN_Str8FromStruct(&colour)); + } + if (style.bold == DN_LogBold_Yes) + DN_OS_Print(dest, DN_Str8Lit(DN_ANSICodeBoldLit)); + DN_OS_Print(dest, string); + if (style.colour || style.bold == DN_LogBold_Yes) + DN_OS_Print(dest, DN_Str8Lit(DN_ANSICodeResetLit)); + } +} + +static char *DN_OS_PrintVSPrintfChunker_(const char *buf, void *user, int len) +{ + DN_Str8 string = {}; + string.data = DN_Cast(char *) buf; + string.size = len; + + DN_OSPrintDest dest = DN_Cast(DN_OSPrintDest) DN_Cast(uintptr_t) user; + DN_OS_Print(dest, string); + return (char *)buf; +} + +DN_API void DN_OS_PrintFV(DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + char buffer[STB_SPRINTF_MIN]; + STB_SPRINTF_DECORATE(vsprintfcb) + (DN_OS_PrintVSPrintfChunker_, DN_Cast(void *) DN_Cast(uintptr_t) dest, buffer, fmt, args); +} + +DN_API void DN_OS_PrintFVStyle(DN_OSPrintDest dest, DN_LogStyle style, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + if (fmt) { + if (style.colour) { + DN_Str8x32 colour = DN_Str8x32FromANSIColourCodeU8RGB(DN_ANSIColourMode_Fg, style.r, style.g, style.b); + DN_OS_Print(dest, DN_Str8FromStruct(&colour)); + } + if (style.bold == DN_LogBold_Yes) + DN_OS_Print(dest, DN_Str8Lit(DN_ANSICodeBoldLit)); + DN_OS_PrintFV(dest, fmt, args); + if (style.colour || style.bold == DN_LogBold_Yes) + DN_OS_Print(dest, DN_Str8Lit(DN_ANSICodeResetLit)); + } +} + +DN_API void DN_OS_PrintLn(DN_OSPrintDest dest, DN_Str8 string) +{ + DN_OS_Print(dest, string); + DN_OS_Print(dest, DN_Str8Lit("\n")); +} + +DN_API void DN_OS_PrintLnF(DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_OS_PrintLnFV(dest, fmt, args); + va_end(args); +} + +DN_API void DN_OS_PrintLnFV(DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_OS_PrintFV(dest, fmt, args); + DN_OS_Print(dest, DN_Str8Lit("\n")); +} + +DN_API void DN_OS_PrintLnStyle(DN_OSPrintDest dest, DN_LogStyle style, DN_Str8 string) +{ + DN_OS_PrintStyle(dest, style, string); + DN_OS_Print(dest, DN_Str8Lit("\n")); +} + +DN_API void DN_OS_PrintLnFStyle(DN_OSPrintDest dest, DN_LogStyle style, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_OS_PrintLnFVStyle(dest, style, fmt, args); + va_end(args); +} + +DN_API void DN_OS_PrintLnFVStyle(DN_OSPrintDest dest, DN_LogStyle style, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_OS_PrintFVStyle(dest, style, fmt, args); + DN_OS_Print(dest, DN_Str8Lit("\n")); +} + +DN_API DN_StackTrace DN_StackTraceFromAllocator(DN_Allocator allocator, DN_U16 limit) +{ + DN_StackTrace result = {}; +#if defined(DN_OS_WIN32) + if (!allocator.context) + return result; + + static DN_TicketMutex mutex = {}; + DN_TicketMutex_Begin(&mutex); + + HANDLE thread = GetCurrentThread(); + result.process = GetCurrentProcess(); + + DN_OSW32Core *w32 = DN_OS_W32GetCore(); + if (!w32->sym_initialised) { + w32->sym_initialised = true; + SymSetOptions(SYMOPT_LOAD_LINES); + if (!SymInitialize(result.process, nullptr /*UserSearchPath*/, true /*fInvadeProcess*/)) { + DN_TCScratch scratch = DN_TCScratchBeginAllocator(&allocator, 1); + DN_OSW32Error error = DN_OS_W32LastError(&scratch.arena); + DN_LogErrorF("SymInitialize failed, stack trace can not be generated (%lu): %.*s\n", error.code, DN_Str8PrintFmt(error.msg)); + DN_TCScratchEnd(&scratch); + } + } + + CONTEXT context; + RtlCaptureContext(&context); + + STACKFRAME64 frame = {}; + frame.AddrPC.Offset = context.Rip; + frame.AddrPC.Mode = AddrModeFlat; + frame.AddrFrame.Offset = context.Rbp; + frame.AddrFrame.Mode = AddrModeFlat; + frame.AddrStack.Offset = context.Rsp; + frame.AddrStack.Mode = AddrModeFlat; + + DN_U64 raw_frames[256] = {}; + DN_USize raw_frames_count = 0; + while (raw_frames_count < limit) { + if (!StackWalk64(IMAGE_FILE_MACHINE_AMD64, + result.process, + thread, + &frame, + &context, + nullptr /*ReadMemoryRoutine*/, + SymFunctionTableAccess64, + SymGetModuleBase64, + nullptr /*TranslateAddress*/)) + break; + + // NOTE: It might be useful one day to use frame.AddrReturn.Offset. + // If AddrPC.Offset == AddrReturn.Offset then we can detect recursion. + DN_LArrayAppend(raw_frames, &raw_frames_count, frame.AddrPC.Offset); + } + DN_TicketMutex_End(&mutex); + + result.base_addr = DN_Cast(DN_U64 *)DN_AllocatorAlloc(allocator, raw_frames_count * sizeof(DN_U64), alignof(DN_U64), DN_ZMem_No); + DN_Assert(result.base_addr); + + result.size = DN_Cast(DN_U16) raw_frames_count; + DN_Memcpy(result.base_addr, raw_frames, raw_frames_count * sizeof(raw_frames[0])); +#else + (void)limit; + (void)allocator; +#endif + return result; +} + + +DN_API DN_StackTrace DN_StackTraceFromArena(DN_Arena *arena, DN_U16 limit) +{ + DN_Allocator allocator = DN_AllocatorFromArena(arena); + DN_StackTrace result = DN_StackTraceFromAllocator(allocator, limit); + return result; +} + +static void DN_StackTraceAddToStr8Builder_(DN_StackTrace const *trace, DN_Str8Builder *builder, DN_USize skip) +{ + DN_StackTraceRawFrame raw_frame = {}; + raw_frame.process = trace->process; + for (DN_USize index = skip; index < trace->size; index++) { + raw_frame.base_addr = trace->base_addr[index]; + DN_StackTraceFrame frame = DN_StackTraceRawFrameToFrame(builder->arena, raw_frame); + DN_Str8BuilderAppendF(builder, "%.*s(%zu): %.*s%s", DN_Str8PrintFmt(frame.file_name), frame.line_number, DN_Str8PrintFmt(frame.function_name), (DN_Cast(int) index == trace->size - 1) ? "" : "\n"); + } +} + +DN_API bool DN_StackTraceIterate(DN_StackTraceIterator *it, DN_StackTrace const *trace) +{ + bool result = false; + if (!it || !trace || !trace->base_addr || !trace->process) + return result; + + if (it->index >= trace->size) + return false; + + result = true; + it->raw_frame.process = trace->process; + it->raw_frame.base_addr = trace->base_addr[it->index++]; + return result; +} + +DN_API DN_Str8 DN_Str8FromStackTraceAllocator(DN_Allocator allocator, DN_StackTrace const *trace, DN_U16 skip) +{ + DN_Str8 result = {}; + if (!trace) + return result; + + DN_TCScratch scratch = DN_TCScratchBeginAllocator(&allocator, 1); + DN_Str8Builder builder = DN_Str8BuilderFromArena(&scratch.arena); + DN_StackTraceAddToStr8Builder_(trace, &builder, skip); + result = DN_Str8FromStr8BuilderAllocator(&builder, allocator); + DN_TCScratchEnd(&scratch); + return result; +} + +DN_API DN_Str8 DN_Str8FromStackTraceArena(DN_Arena *arena, DN_StackTrace const *trace, DN_U16 skip) +{ + DN_Str8 result = {}; + if (!trace || !arena) + return result; + + DN_TCScratch scratch = DN_TCScratchBeginArena(&arena, 1); + DN_Str8Builder builder = DN_Str8BuilderFromArena(&scratch.arena); + DN_StackTraceAddToStr8Builder_(trace, &builder, skip); + result = DN_Str8FromStr8BuilderArena(&builder, arena); + DN_TCScratchEnd(&scratch); + return result; +} + +DN_API DN_Str8 DN_Str8FromStackTraceNowAllocator(DN_Allocator allocator, DN_U16 limit, DN_U16 skip) +{ + DN_TCScratch scratch = DN_TCScratchBeginArena(DN_Cast(DN_Arena **) & allocator.context, 1); + DN_StackTrace walk = DN_StackTraceFromArena(&scratch.arena, limit); + DN_Str8 result = DN_Str8FromStackTraceAllocator(allocator, &walk, skip); + DN_TCScratchEnd(&scratch); + return result; +} + +DN_API DN_Str8 DN_Str8FromStackTraceNowArena(DN_Arena *arena, DN_U16 limit, DN_U16 skip) +{ + DN_Str8 result = DN_Str8FromStackTraceNowAllocator(DN_AllocatorFromArena(arena), limit, skip); + return result; +} + +DN_API DN_Str8 DN_Str8FromStackTraceNowHeap(DN_U16 limit, DN_U16 skip) +{ + DN_Arena arena = DN_ArenaFromHeap(DN_Kilobytes(64), DN_MemFlags_NoAllocTrack); + DN_Str8Builder builder = DN_Str8BuilderFromArena(&arena); + DN_StackTrace walk = DN_StackTraceFromArena(&arena, limit); + DN_StackTraceAddToStr8Builder_(&walk, &builder, skip); + DN_Str8 result = DN_Str8BuilderBuildFromHeap(&builder); + DN_ArenaDeinit(&arena); + return result; +} + +DN_API DN_StackTraceFrameSlice DN_StackTraceGetFrames(DN_Arena *arena, DN_U16 limit) +{ + DN_StackTraceFrameSlice result = {}; + if (!arena) + return result; + + DN_TCScratch scratch = DN_TCScratchBeginArena(&arena, 1); + DN_StackTrace walk = DN_StackTraceFromArena(&scratch.arena, limit); + if (walk.size) { + if (DN_ISliceAllocArena(&result, walk.size, DN_ZMem_No, arena)) { + DN_USize slice_index = 0; + for (DN_StackTraceIterator it = {}; DN_StackTraceIterate(&it, &walk);) + result.data[slice_index++] = DN_StackTraceRawFrameToFrame(arena, it.raw_frame); + } + } + DN_TCScratchEnd(&scratch); + return result; +} + +DN_API DN_StackTraceFrame DN_StackTraceRawFrameToFrame(DN_Arena *arena, DN_StackTraceRawFrame raw_frame) +{ +#if defined(DN_OS_WIN32) + // NOTE: Get line+filename + + // TODO: Why does zero-initialising this with `line = {};` cause + // SymGetLineFromAddr64 function to fail once we are at + // __scrt_commain_main_seh and hit BaseThreadInitThunk frame? The + // line and file number are still valid in the result which we use, so, + // we silently ignore this error. + IMAGEHLP_LINEW64 line; + line.SizeOfStruct = sizeof(line); + DWORD line_displacement = 0; + if (!SymGetLineFromAddrW64(raw_frame.process, raw_frame.base_addr, &line_displacement, &line)) + line = {}; + + // NOTE: Get function name + + alignas(SYMBOL_INFOW) char buffer[sizeof(SYMBOL_INFOW) + (MAX_SYM_NAME * sizeof(wchar_t))] = {}; + SYMBOL_INFOW *symbol = DN_Cast(SYMBOL_INFOW *) buffer; + symbol->SizeOfStruct = sizeof(*symbol); + symbol->MaxNameLen = sizeof(buffer) - sizeof(*symbol); + + uint64_t symbol_displacement = 0; // Offset to the beginning of the symbol to the address + SymFromAddrW(raw_frame.process, raw_frame.base_addr, &symbol_displacement, symbol); + + // NOTE: Construct result + + DN_Str16 file_name16 = DN_Str16FromPtr(line.FileName, DN_CStr16Size(line.FileName)); + DN_Str16 function_name16 = DN_Str16FromPtr(symbol->Name, symbol->NameLen); + + DN_StackTraceFrame result = {}; + result.address = raw_frame.base_addr; + result.line_number = line.LineNumber; + result.file_name = DN_OS_W32Str16ToStr8(arena, file_name16); + result.function_name = DN_OS_W32Str16ToStr8(arena, function_name16); + + if (result.function_name.size == 0) + result.function_name = DN_Str8Lit(""); + if (result.file_name.size == 0) + result.file_name = DN_Str8Lit(""); +#else + DN_StackTraceFrame result = {}; +#endif + return result; +} + +DN_API void DN_StackTracePrint(DN_U16 limit) +{ + DN_TCScratch scratch = DN_TCScratchBeginArena(nullptr, 0); + DN_StackTraceFrameSlice stack_trace = DN_StackTraceGetFrames(&scratch.arena, limit); + for (DN_ForItSize(it, DN_StackTraceFrame, stack_trace.data, stack_trace.count)) { + DN_StackTraceFrame frame = *it.data; + DN_OS_PrintErrLnF("%.*s(%I64u): %.*s", DN_Str8PrintFmt(frame.file_name), frame.line_number, DN_Str8PrintFmt(frame.function_name)); + } + DN_TCScratchEnd(&scratch); +} + +DN_API void DN_StackTraceReloadSymbols() +{ +#if defined(DN_OS_WIN32) + HANDLE process = GetCurrentProcess(); + SymRefreshModuleList(process); +#endif +} + +#if defined(DN_PLATFORM_POSIX) || defined(DN_PLATFORM_EMSCRIPTEN) + #include "OS/dn_os_posix.cpp" +#elif defined(DN_PLATFORM_WIN32) + #include "OS/dn_os_w32.cpp" +#else + #error Please define a platform e.g. 'DN_PLATFORM_WIN32' to enable the correct implementation for platform APIs +#endif +#endif // DN_WITH_OS + +#if DN_WITH_TESTS #include "Extra/dn_tests.cpp" #endif + +#if DN_WITH_NET +DN_Str8 DN_NET_Str8FromResponseState(DN_NETResponseState state) +{ + DN_Str8 result = {}; + switch (state) { + case DN_NETResponseState_Nil: result = DN_Str8Lit("Nil"); break; + case DN_NETResponseState_Error: result = DN_Str8Lit("Error"); break; + case DN_NETResponseState_HTTP: result = DN_Str8Lit("HTTP"); break; + case DN_NETResponseState_WSOpen: result = DN_Str8Lit("WS Open"); break; + case DN_NETResponseState_WSText: result = DN_Str8Lit("WS Text"); break; + case DN_NETResponseState_WSBinary: result = DN_Str8Lit("WS Binary"); break; + case DN_NETResponseState_WSClose: result = DN_Str8Lit("WS Close"); break; + case DN_NETResponseState_WSPing: result = DN_Str8Lit("WS Ping"); break; + case DN_NETResponseState_WSPong: result = DN_Str8Lit("WS Pong"); break; + } + return result; +} + +DN_NETRequest *DN_NET_RequestFromHandle(DN_NETRequestHandle handle) +{ + DN_NETRequest *ptr = DN_Cast(DN_NETRequest *) handle.handle; + DN_NETRequest *result = nullptr; + if (ptr && ptr->gen == handle.gen) + result = ptr; + return result; +} + +DN_NETRequestHandle DN_NET_HandleFromRequest(DN_NETRequest *request) +{ + DN_NETRequestHandle result = {}; + if (request) { + result.handle = DN_Cast(DN_UPtr) request; + result.gen = request->gen; + } + return result; +} + +bool DN_NET_ResponseHasFailed(DN_NETResponse const* resp) +{ + bool result = false; + if (resp->type == DN_NETRequestType_HTTP) + result = resp->state == DN_NETResponseState_Error || resp->http_status >= 400; + else + result = resp->state == DN_NETResponseState_Error; + return result; +} + +DN_Str8 DN_NET_Str8DiagnosticFromResponse(DN_NETResponse const* resp, DN_Arena *arena) +{ + DN_TCScratch scratch = DN_TCScratchBeginArena(&arena, 1); + DN_Str8Builder builder = DN_Str8BuilderFromArena(&scratch.arena); + bool resp_failed = DN_NET_ResponseHasFailed(resp); + DN_Str8BuilderAppendF(&builder, "Request %s (%s", resp_failed ? "failed" : "succeeded", resp->type == DN_NETRequestType_HTTP ? "HTTP" : "WS"); + if (resp->type == DN_NETRequestType_HTTP) { + if (resp->http_status) + DN_Str8BuilderAppendF(&builder, " %u", resp->http_status); + } + DN_Str8BuilderAppendF(&builder, ")"); + if (resp->body.size || resp->error_str8.size) { + DN_Str8BuilderAppendRef(&builder, DN_Str8Lit(" with ")); + if (resp->body.size) + DN_Str8BuilderAppendF(&builder, "%.*s", DN_Str8PrintFmt(resp->body)); + if (resp->error_str8.size) + DN_Str8BuilderAppendF(&builder, "%s%.*s", resp->body.size ? ". " : "", DN_Str8PrintFmt(resp->error_str8)); + } + DN_Str8 result = DN_Str8FromStr8BuilderArena(&builder, arena); + DN_TCScratchEnd(&scratch); + return result; +} + +void DN_NET_BaseInit(DN_NETCore *net, char *base, DN_U64 base_size) +{ + net->base = base; + net->base_size = base_size; + net->mem = DN_MemListFromBuffer(net->base, net->base_size, DN_MemFlags_Nil); + net->arena = DN_ArenaFromMemList(&net->mem); + net->completion_sem = DN_OS_SemaphoreInit(0); +} + +DN_NETRequestHandle DN_NET_SetupRequest(DN_NETRequest *request, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args, DN_NETRequestType type) +{ + // NOTE: Setup request + DN_Assert(request); + if (request) { + if (!request->mem.curr) + request->mem = DN_MemListFromVMem(DN_Megabytes(1), DN_Kilobytes(1), DN_MemFlags_Nil); + request->arena = DN_ArenaTempBeginFromMemList(&request->mem); + request->type = type; + request->gen = DN_Max(request->gen + 1, 1); + request->url = DN_Str8FromStr8Arena(url, &request->arena); + request->method = DN_Str8FromStr8Arena(DN_Str8TrimWhitespaceAround(method), &request->arena); + + if (args) { + request->args.flags = args->flags; + request->args.username = DN_Str8FromStr8Arena(args->username, &request->arena); + request->args.password = DN_Str8FromStr8Arena(args->password, &request->arena); + if (type == DN_NETRequestType_HTTP) + request->args.payload = DN_Str8FromStr8Arena(args->payload, &request->arena); + + request->args.headers = DN_ArenaNewArray(&request->arena, DN_Str8, args->headers_size, DN_ZMem_No); + DN_Assert(request->args.headers); + if (request->args.headers) { + for (DN_ForItSize(it, DN_Str8, args->headers, args->headers_size)) + request->args.headers[it.index] = DN_Str8FromStr8Arena(*it.data, &request->arena); + request->args.headers_size = args->headers_size; + } + } + + request->completion_sem = DN_OS_SemaphoreInit(0); + request->start_response_arena = DN_ArenaTempBeginFromArena(&request->arena); + } + + DN_NETRequestHandle result = DN_NET_HandleFromRequest(request); + request->response.request = result; + request->response.type = request->type; + return result; +} + +void DN_NET_EndFinishedRequest(DN_NETRequest *request) +{ + // NOTE: Deallocate the memory used in the request and reset the string builder + DN_ArenaTempEnd(&request->start_response_arena, DN_ArenaReset_Yes); +} +#endif // #if DN_WITH_NET + +#if DN_WITH_NET_CURL +struct DN_NETCurlRequest +{ + void *handle; + struct curl_slist *slist; + char error[CURL_ERROR_SIZE]; + bool ws_has_more; + DN_Str8Builder str8_builder; +}; + +enum DN_NETCurlRingEventType +{ + DN_NETCurlRingEventType_Nil, + DN_NETCurlRingEventType_DoRequest, + DN_NETCurlRingEventType_SendWS, + DN_NETCurlRingEventType_ReceivedWSReceipt, + DN_NETCurlRingEventType_DeinitRequest, +}; + +struct DN_NETCurlRingEvent +{ + DN_NETCurlRingEventType type; + DN_NETRequestHandle request; + DN_USize ws_send_size; + DN_NETWSSend ws_send; +}; + +static DN_NETCurlRequest *DN_NET_CurlRequestFromRequest_(DN_NETRequest *req) +{ + DN_NETCurlRequest *result = req ? DN_Cast(DN_NETCurlRequest *) req->context[0] : 0; + return result; +} + +static DN_NETCore *DN_NET_CurlNetFromRequest(DN_NETRequest *req) +{ + DN_NETCore *result = req ? DN_Cast(DN_NETCore *) req->context[1] : 0; + return result; +} + +static bool DN_NET_CurlRequestIsInList(DN_NETRequest const *first, DN_NETRequest const *find) +{ + bool result = false; + for (DN_NETRequest const *it = first; !result && it; it = it->next) + result = find == it; + return result; +} + +static void DN_NET_CurlMarkRequestDone_(DN_NETCore *net, DN_NETRequest *request) +{ + DN_Assert(request); + DN_Assert(net); + // NOTE: The done list in CURL is also used as a place to put websocket requests after removing it + // from the 'ws_list'. By doing this we are stopping the CURL thread from receiving more data on + // the socket as that thread ticks the list of 'ws_list' sockets for data. + // + // Once the caller waited and has received the data from the websocket, the request is put back + // into the 'ws_list' which then lets the CURL thread start receiving more data for that socket. + // + // Since CURL uses a background thread, we do this behind a mutex + DN_NETCurlCore *curl = DN_Cast(DN_NETCurlCore *)net->context; + for (DN_OS_MutexScope(&curl->list_mutex)) { + DN_Assert(DN_NET_CurlRequestIsInList(curl->thread_request_list, request)); + DN_DoublyLLDetach(curl->thread_request_list, request); + DN_DoublyLLAppend(curl->response_list, request); + } + DN_OS_SemaphoreIncrement(&net->completion_sem, 1); + DN_OS_SemaphoreIncrement(&request->completion_sem, 1); +} + +static DN_USize DN_NET_CurlHTTPCallback_(char *payload, DN_USize size, DN_USize count, void *user_data) +{ + DN_NETRequest *req = DN_Cast(DN_NETRequest *) user_data; + DN_NETCurlRequest *curl_req = DN_NET_CurlRequestFromRequest_(req); + DN_USize result = 0; + DN_USize payload_size = size * count; + if (DN_Str8BuilderAppendBytesCopy(&curl_req->str8_builder, payload, payload_size)) + result = payload_size; + return result; +} + +static int32_t DN_NET_CurlThreadEntryPoint_(DN_OSThread *thread) +{ + DN_NETCore *net = DN_Cast(DN_NETCore *) thread->user_context; + DN_NETCurlCore *curl = DN_Cast(DN_NETCurlCore *) net->context; + DN_OS_ThreadSetNameFmt("%.*s", DN_Str8PrintFmt(curl->thread.name)); + + while (!curl->kill_thread) { + DN_TCScratch tmem = DN_TCScratchBeginArena(nullptr, 0); + + // NOTE: Handle events sitting in the ring queue + for (bool dequeue_ring = true; dequeue_ring;) { + DN_NETCurlRingEvent event = {}; + for (DN_OS_MutexScope(&curl->ring_mutex)) { + if (DN_RingHasData(&curl->ring, sizeof(event))) + DN_RingRead(&curl->ring, &event, sizeof(event)); + } + + DN_NETRequest *req = DN_NET_RequestFromHandle(event.request); + DN_NETCurlRequest *curl_req = DN_NET_CurlRequestFromRequest_(req); + switch (event.type) { + case DN_NETCurlRingEventType_Nil: dequeue_ring = false; break; + + case DN_NETCurlRingEventType_DoRequest: { + DN_Assert(req->response.state == DN_NETResponseState_Nil); + DN_Assert(req->type != DN_NETRequestType_Nil); + + // NOTE: Attach it to the CURL thread's request list + for (DN_OS_MutexScope(&curl->list_mutex)) { + DN_Assert(DN_NET_CurlRequestIsInList(curl->request_list, req)); + DN_DoublyLLDetach(curl->request_list, req); + } + DN_DoublyLLAppend(curl->thread_request_list, req); + + // NOTE: Add the connection to CURLM and start ticking it once we finish handling all the + // ring events + CURLMcode multi_add = curl_multi_add_handle(curl->thread_curlm, curl_req->handle); + DN_Assert(multi_add == CURLM_OK); + } break; + + case DN_NETCurlRingEventType_SendWS: { + DN_Str8 payload = {}; + for (DN_OS_MutexScope(&curl->ring_mutex)) { + DN_Assert(DN_RingHasData(&curl->ring, event.ws_send_size)); + payload = DN_Str8AllocArena(event.ws_send_size, DN_ZMem_No, &tmem.arena); + DN_RingRead(&curl->ring, payload.data, payload.size); + } + + DN_U32 curlws_flag = 0; + switch (event.ws_send) { + case DN_NETWSSend_Text: curlws_flag = CURLWS_TEXT; break; + case DN_NETWSSend_Binary: curlws_flag = CURLWS_BINARY; break; + case DN_NETWSSend_Close: curlws_flag = CURLWS_CLOSE; break; + case DN_NETWSSend_Ping: curlws_flag = CURLWS_PING; break; + case DN_NETWSSend_Pong: curlws_flag = CURLWS_PONG; break; + } + + DN_Assert(req->type == DN_NETRequestType_WS); + DN_Assert(req->response.state >= DN_NETResponseState_WSOpen && req->response.state <= DN_NETResponseState_WSPong); + + DN_USize sent = 0; + CURLcode send_result = curl_ws_send(curl_req->handle, payload.data, payload.size, &sent, 0, curlws_flag); + DN_AssertF(send_result == CURLE_OK, "Failed to send: %s", curl_easy_strerror(send_result)); + DN_AssertF(sent == payload.size, "Failed to send all bytes (%zu vs %zu)", sent, payload.size); + } break; + + case DN_NETCurlRingEventType_ReceivedWSReceipt: { + DN_Assert(req->type == DN_NETRequestType_WS); + DN_Assert(req->response.state >= DN_NETResponseState_WSOpen && req->response.state <= DN_NETResponseState_WSPong); + req->response.state = DN_NETResponseState_WSOpen; + + // NOTE: End the temp memory storing the WS data we just read and the user returned to us + // (we got their receipt back). Then restart the temp memory scope for the next websocket + // payload + DN_NET_EndFinishedRequest(req); + req->start_response_arena = DN_ArenaTempBeginFromArena(&req->arena); + curl_req->str8_builder = DN_Str8BuilderFromArena(&req->start_response_arena); + + for (DN_OS_MutexScope(&curl->list_mutex)) { + DN_Assert(DN_NET_CurlRequestIsInList(curl->request_list, req)); + DN_DoublyLLDetach(curl->request_list, req); + } + DN_DoublyLLAppend(curl->thread_request_list, req); + } break; + + case DN_NETCurlRingEventType_DeinitRequest: { + DN_Assert(event.request.handle != 0); + DN_NETRequest *request = DN_Cast(DN_NETRequest *) event.request.handle; + + // NOTE: Detach the request from the deinit list. This brings the request into this + // thread's provenance, no other threads modifying the deinit list will race with us. + for (DN_OS_MutexScope(&curl->list_mutex)) { + DN_Assert(DN_NET_CurlRequestIsInList(curl->deinit_list, request)); + DN_DoublyLLDetach(curl->deinit_list, request); + } + + // NOTE: Now we can modify the request, release resources + DN_NET_EndFinishedRequest(request); + DN_OS_SemaphoreDeinit(&request->completion_sem); + + curl_multi_remove_handle(curl->thread_curlm, curl_req->handle); + curl_slist_free_all(curl_req->slist); + curl_easy_reset(curl_req->handle); + + CURL *copy = curl_req->handle; + *curl_req = {}; + curl_req->handle = copy; + + // NOTE: Zero the struct preserving just the data we need to retain + DN_NETRequest resetter = {}; + resetter.arena = request->arena; + resetter.gen = request->gen; + DN_Memcpy(resetter.context, request->context, sizeof(resetter.context)); + *request = resetter; + + // NOTE: Add it to the free list + for (DN_OS_MutexScope(&curl->list_mutex)) + DN_DoublyLLAppend(curl->free_list, request); + } break; + } + } + + // NOTE: Pump handles + int running_handles = 0; + CURLMcode perform_result = curl_multi_perform(curl->thread_curlm, &running_handles); + if (perform_result != CURLM_OK) + DN_AssertInvalidCodePath; + + // NOTE: Check pump result + for (;;) { + int msgs_in_queue = 0; + CURLMsg *msg = curl_multi_info_read(curl->thread_curlm, &msgs_in_queue); + if (msg) { + // NOTE: Get request handle + DN_NETRequest *req = nullptr; + curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, DN_Cast(void **) & req); + DN_Assert(req); + DN_Assert(DN_NET_CurlRequestIsInList(curl->thread_request_list, req)); + + DN_NETCurlRequest *curl_req = DN_NET_CurlRequestFromRequest_(req); + DN_Assert(curl_req->handle == msg->easy_handle); + + if (msg->data.result == CURLE_OK) { + // NOTE: Get HTTP response code + CURLcode get_result = curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &req->response.http_status); + if (get_result == CURLE_OK) { + if (req->type == DN_NETRequestType_HTTP) { + req->response.state = DN_NETResponseState_HTTP; + } else { + DN_Assert(req->type == DN_NETRequestType_WS); + req->response.state = DN_NETResponseState_WSOpen; + } + } else { + req->response.error_str8 = DN_Str8FromFmtArena(&req->start_response_arena, "Failed to get HTTP response status (CURL %d): %s", msg->data.result, curl_easy_strerror(get_result)); + req->response.state = DN_NETResponseState_Error; + } + } else { + DN_USize curl_extended_error_size = DN_CStr8Size(curl_req->error); + req->response.state = DN_NETResponseState_Error; + req->response.error_str8 = DN_Str8FromFmtArena(&req->start_response_arena, + "HTTP request '%.*s' failed (CURL %d): %s%s%s%s", + DN_Str8PrintFmt(req->url), + msg->data.result, + curl_easy_strerror(msg->data.result), + curl_extended_error_size ? " (" : "", + curl_extended_error_size ? curl_req->error : "", + curl_extended_error_size ? ")" : ""); + } + + if (req->type == DN_NETRequestType_HTTP || req->response.state == DN_NETResponseState_Error) { + // NOTE: Remove the request from the multi handle if we're a HTTP request + // because it typically terminates the connection. In websockets the + // connection remains in the multi-handle to allow you to send and + // receive WS data from it. + // + // If there's an error (either websocket or HTTP) we will also remove the + // connection from the multi handle as it failed. One a connection has + // failed, curl will not poll that connection so there's no point keeping + // it attached to the multi handle. + curl_multi_remove_handle(curl->thread_curlm, msg->easy_handle); + } + + DN_NET_CurlMarkRequestDone_(net, req); + } + + if (msgs_in_queue == 0) + break; + } + + // NOTE: Check websockets + DN_USize ws_count = 0; + for (DN_NETRequest *req = curl->thread_request_list; req; req = req->next) { + DN_Assert(req->type == DN_NETRequestType_WS || req->type == DN_NETRequestType_HTTP); + if (req->type != DN_NETRequestType_WS || !(req->response.state >= DN_NETResponseState_WSOpen && req->response.state <= DN_NETResponseState_WSPong)) + continue; + ws_count++; + const curl_ws_frame *meta = nullptr; + DN_NETCurlRequest *curl_req = DN_NET_CurlRequestFromRequest_(req); + CURLcode receive_result = CURLE_OK; + while (receive_result == CURLE_OK) { + // NOTE: Determine WS payload size received. Note that since we pass in a null pointer CURL + // will set meta->len to 0 and say that there's meta->bytesleft in the next chunk. + DN_USize bytes_read = 0; + receive_result = curl_ws_recv(curl_req->handle, nullptr, 0, &bytes_read, &meta); + if (receive_result != CURLE_OK) + continue; + DN_Assert(meta->len == 0); + + if (meta->flags & CURLWS_TEXT) + req->response.state = DN_NETResponseState_WSText; + + if (meta->flags & CURLWS_BINARY) + req->response.state = DN_NETResponseState_WSBinary; + + if (meta->flags & CURLWS_PING) + req->response.state = DN_NETResponseState_WSPing; + + if (meta->flags & CURLWS_PONG) + req->response.state = DN_NETResponseState_WSPong; + + if (meta->flags & CURLWS_CLOSE) + req->response.state = DN_NETResponseState_WSClose; + + curl_req->ws_has_more = meta->flags & CURLWS_CONT; + if (curl_req->ws_has_more) { + bool is_text_or_binary = req->response.state == DN_NETResponseState_WSText || + req->response.state == DN_NETResponseState_WSBinary; + DN_Assert(is_text_or_binary); + } + + // NOTE: Allocate and read (we use meta->bytesleft as per comment from initial recv) + if (meta->bytesleft) { + DN_Str8 buffer = DN_Str8AllocArena(meta->bytesleft, DN_ZMem_No, &req->start_response_arena); + DN_Assert(buffer.size == DN_Cast(DN_USize)meta->bytesleft); + receive_result = curl_ws_recv(curl_req->handle, buffer.data, buffer.size, &buffer.size, &meta); + DN_Assert(buffer.size == DN_Cast(DN_USize)meta->len); + DN_Str8BuilderAppendRef(&curl_req->str8_builder, buffer); + } + + // NOTE: There are more bytes coming if meta->bytesleft is set, (e.g. the next chunk. We + // just read the current chunk). + // + // > If this is not a complete fragment, the bytesleft field informs about how many + // additional bytes are expected to arrive before this fragment is complete. + curl_req->ws_has_more |= meta && meta->bytesleft > 0; + if (!curl_req->ws_has_more) + break; + } + + // NOTE: curl_ws_recv returns CURLE_GOT_NOTHING if the associated connection is closed. + if (receive_result == CURLE_GOT_NOTHING) + curl_req->ws_has_more = false; + + // NOTE: We read all the possible bytes that CURL has received for this message, but, there are + // more bytes left that we will receive on subsequent calls. We will continue to the next + // request and return back to this one when PumpRequests is called again where hopefully that + // data has arrived. + if (curl_req->ws_has_more) + continue; + + // For CURLE_AGAIN + // + // > Instead of blocking, the function returns CURLE_AGAIN. The correct behavior is then to + // > wait for the socket to signal readability before calling this function again. + // + // In which case we continue ticking the other sockets and eventually exit once all ticked. + // Right after this we wait on the CURLM instance which will wake us up again when there's + // data to be read. + // + // if we received data, e.g. state was set to Text, Binary ... e.t.c we bypass this and + // report it to the user first. When the user waits for the response, they consume the data + // and then that will reinsert it into request list for CURL to read from the socket again. + bool received_data = (req->response.state >= DN_NETResponseState_WSText && req->response.state <= DN_NETResponseState_WSPong); + if (receive_result == CURLE_AGAIN && !received_data) + continue; + + if (!received_data) { + if (receive_result == CURLE_GOT_NOTHING) { + req->response.state = DN_NETResponseState_WSClose; + } else if (receive_result != CURLE_OK) { + DN_USize curl_extended_error_size = DN_CStr8Size(curl_req->error); + req->response.state = DN_NETResponseState_Error; + req->response.error_str8 = DN_Str8FromFmtArena(&req->start_response_arena, + "Websocket receive '%.*s' failed (CURL %d): %s%s%s%s", + DN_Str8PrintFmt(req->url), + receive_result, + curl_easy_strerror(receive_result), + curl_extended_error_size ? " (" : "", + curl_extended_error_size ? curl_req->error : "", + curl_extended_error_size ? ")" : ""); + } + } + + DN_NETRequest *request_copy = req; + req = req->prev; + DN_NET_CurlMarkRequestDone_(net, request_copy); + if (!req) + break; + } + + DN_I32 sleep_time_ms = ws_count > 0 ? 16 : INT32_MAX; + curl_multi_poll(curl->thread_curlm, nullptr, 0, sleep_time_ms, nullptr); + DN_TCScratchEnd(&tmem); + } + + return 0; +} + +DN_NETInterface DN_NET_CurlInterface() +{ + DN_NETInterface result = {}; + result.init = DN_NET_CurlInit; + result.deinit = DN_NET_CurlDeinit; + result.do_http = DN_NET_CurlDoHTTP; + result.do_ws = DN_NET_CurlDoWS; + result.do_ws_send = DN_NET_CurlDoWSSend; + result.wait_for_response = DN_NET_CurlWaitForResponse; + result.wait_for_any_response = DN_NET_CurlWaitForAnyResponse; + return result; +} + +void DN_NET_CurlInit(DN_NETCore *net, char *base, DN_U64 base_size) +{ + DN_NET_BaseInit(net, base, base_size); + DN_NETCurlCore *curl = DN_ArenaNew(&net->arena, DN_NETCurlCore, DN_ZMem_Yes); + net->context = curl; + net->api = DN_NET_CurlInterface(); + + DN_USize arena_bytes_avail = (net->arena.mem->curr->reserve - net->arena.mem->curr->used); + curl->ring.size = arena_bytes_avail / 2; + curl->ring.base = DN_Cast(char *) DN_ArenaAlloc(&net->arena, curl->ring.size, /*align*/ 1, DN_ZMem_Yes); + DN_Assert(curl->ring.base); + + curl->ring_mutex = DN_OS_MutexInit(); + curl->list_mutex = DN_OS_MutexInit(); + curl->thread_curlm = DN_Cast(CURLM *) curl_multi_init(); + + DN_FmtAppend(curl->thread.name.data, &curl->thread.name.size, sizeof(curl->thread.name.data), "NET (CURL)"); + DN_OS_ThreadInit(&curl->thread, DN_NET_CurlThreadEntryPoint_, nullptr, DN_TCInitArgsDefault(), net); +} + +void DN_NET_CurlDeinit(DN_NETCore *net) +{ + DN_NETCurlCore *curl = DN_Cast(DN_NETCurlCore *) net->context; + curl->kill_thread = true; + curl_multi_wakeup(curl->thread_curlm); + DN_OS_ThreadJoin(&curl->thread, DN_TCDeinitArenas_Yes); +} + +static DN_NETRequestHandle DN_NET_CurlDoRequest_(DN_NETCore *net, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args, DN_NETRequestType type) +{ + // NOTE: Allocate the request + DN_NETCurlCore *curl_core = DN_Cast(DN_NETCurlCore *) net->context; + DN_NETRequest *req = nullptr; + DN_NETRequestHandle result = {}; + { + // NOTE: The free list is modified by both the calling thread and the CURLM thread (which ticks + // all the requests in the background for us) + for (DN_OS_MutexScope(&curl_core->list_mutex)) { + req = curl_core->free_list; + DN_DoublyLLDetach(curl_core->free_list, req); + } + + // NOTE None in the free list so allocate one + if (!req) { + DN_OS_MutexLock(&curl_core->list_mutex); + DN_U64 arena_pos = DN_MemListPos(net->arena.mem); + req = DN_ArenaNewZ(&net->arena, DN_NETRequest); + DN_NETCurlRequest *curl_req = DN_ArenaNewZ(&net->arena, DN_NETCurlRequest); + if (!req || !curl_req) { + DN_MemListPopTo(net->arena.mem, arena_pos); + DN_OS_MutexUnlock(&curl_core->list_mutex); + return result; + } + DN_OS_MutexUnlock(&curl_core->list_mutex); + + curl_req->handle = DN_Cast(CURL *) curl_easy_init(); + req->context[0] = DN_Cast(DN_UPtr) curl_req; + } + } + + // NOTE: Setup the request + DN_NETCurlRequest *curl_req = DN_NET_CurlRequestFromRequest_(req); + { + result = DN_NET_SetupRequest(req, url, method, args, type); + req->context[1] = DN_Cast(DN_UPtr) net; + curl_req->str8_builder = DN_Str8BuilderFromArena(&req->start_response_arena); + } + + // NOTE: Setup the request for curl API + { + CURL *curl = curl_req->handle; + curl_easy_setopt(curl, CURLOPT_PRIVATE, req); + curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_req->error); + + // NOTE: Perform request and read all response headers before handing + // control back to app. + curl_easy_setopt(curl, CURLOPT_URL, req->url.data); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + + // NOTE: Setup response handler + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, DN_NET_CurlHTTPCallback_); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, req); + + // NOTE: Assign HTTP headers + for (DN_ForItSize(it, DN_Str8, req->args.headers, req->args.headers_size)) { + DN_Assert(it.data->data[it.data->size] == 0); + curl_req->slist = curl_slist_append(curl_req->slist, it.data->data); + } + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, curl_req->slist); + + // NOTE: Setup handle for protocol + switch (req->type) { + case DN_NETRequestType_Nil: DN_AssertInvalidCodePath; break; + + case DN_NETRequestType_WS: { + curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 2L); + } break; + + case DN_NETRequestType_HTTP: { + DN_Str8 const GET = DN_Str8Lit("GET"); + DN_Str8 const POST = DN_Str8Lit("POST"); + + if (DN_Str8EqInsensitive(req->method, GET)) { + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1); + } else if (DN_Str8EqInsensitive(req->method, POST)) { + curl_easy_setopt(curl, CURLOPT_POST, 1); + if (req->args.payload.size > DN_Gigabytes(2)) + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, req->args.payload.size); + else + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, req->args.payload.size); + curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, req->args.payload.data); + } else { + DN_AssertInvalidCodePathF("Unimplemented"); + } + } break; + } + + // NOTE: Handle basic auth + if (req->args.flags & DN_NETDoHTTPFlags_BasicAuth) { + if (req->args.username.size && req->args.password.size) { + DN_Assert(req->args.username.data[req->args.username.size] == 0); + DN_Assert(req->args.password.data[req->args.password.size] == 0); + curl_easy_setopt(curl, CURLOPT_USERNAME, req->args.username.data); + curl_easy_setopt(curl, CURLOPT_PASSWORD, req->args.password.data); + } + } + } + + // NOTE: Dispatch the request to the CURL thread + { + // NOTE: Immediately add the request to the request list so it happens "atomically" in the + // calling thread. If the calling thread deinitialises this layer before the CURL thread can be + // pre-empted, we can lose track of this request. + for (DN_OS_MutexScope(&curl_core->list_mutex)) + DN_DoublyLLAppend(curl_core->request_list, req); + + // NOTE: Enqueue request to go into CURL's ring queue. The CURL thread will sleep and wait for + // bytes to come in for the request and then dump the response into the done list to be consumed + // via wait for response + DN_NETCurlRingEvent event = {}; + event.type = DN_NETCurlRingEventType_DoRequest; + event.request = result; + for (DN_OS_MutexScope(&curl_core->ring_mutex)) + DN_RingWriteStruct(&curl_core->ring, &event); + + curl_multi_wakeup(curl_core->thread_curlm); + } + + return result; +} + +DN_NETRequestHandle DN_NET_CurlDoHTTP(DN_NETCore *net, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args) +{ + DN_NETRequestHandle result = DN_NET_CurlDoRequest_(net, url, method, args, DN_NETRequestType_HTTP); + return result; +} + +DN_NETRequestHandle DN_NET_CurlDoWSArgs(DN_NETCore *net, DN_Str8 url, DN_NETDoHTTPArgs const *args) +{ + DN_NETRequestHandle result = DN_NET_CurlDoRequest_(net, url, DN_Str8Lit(""), args, DN_NETRequestType_WS); + return result; +} + +DN_NETRequestHandle DN_NET_CurlDoWS(DN_NETCore *net, DN_Str8 url) +{ + DN_NETRequestHandle result = DN_NET_CurlDoWSArgs(net, url, nullptr); + return result; +} + +void DN_NET_CurlDoWSSend(DN_NETRequestHandle handle, DN_Str8 payload, DN_NETWSSend send) +{ + DN_NETRequest *req = DN_NET_RequestFromHandle(handle); + if (!req) + return; + + DN_NETCore *net = DN_NET_CurlNetFromRequest(req); + DN_NETCurlCore *curl = DN_Cast(DN_NETCurlCore *) net->context; + DN_Assert(curl); + + DN_NETCurlRingEvent event = {}; + event.type = DN_NETCurlRingEventType_SendWS; + event.request = handle; + event.ws_send_size = payload.size; + event.ws_send = send; + + for (DN_OS_MutexScope(&curl->ring_mutex)) { + DN_Assert(DN_RingHasSpace(&curl->ring, payload.size)); + DN_RingWriteStruct(&curl->ring, &event); + DN_RingWrite(&curl->ring, payload.data, payload.size); + } + curl_multi_wakeup(curl->thread_curlm); +} + +static DN_NETResponse DN_NET_CurlHandleFinishedRequest_(DN_NETCurlCore *curl, DN_NETRequest *req, DN_Arena *arena) +{ + // NOTE: Generate the response, copy out the strings into the user given memory + DN_NETResponse result = req->response; + DN_NETCurlRequest *curl_req = DN_NET_CurlRequestFromRequest_(req); + { + result.body = DN_Str8FromStr8BuilderArena(&curl_req->str8_builder, arena); + if (result.error_str8.size) + result.error_str8 = DN_Str8FromStr8Arena(result.error_str8, arena); + } + + bool continue_ws_request = false; + if (req->type == DN_NETRequestType_WS && + req->response.state != DN_NETResponseState_Error && + req->response.state != DN_NETResponseState_WSClose) { + continue_ws_request = true; + } + + // NOTE: Put the request into the requisite list + for (DN_OS_MutexScope(&curl->list_mutex)) { + // NOTE: Dequeue the request, it _must_ have been in the response list at this point for it to + // have ben waitable in the first place. + DN_AssertF(DN_NET_CurlRequestIsInList(curl->response_list, req), + "A completed response should only signal the completion semaphore when it's in the response list"); + DN_DoublyLLDetach(curl->response_list, req); + + // NOTE: A websocket that is continuing to get data should go back into the request list because + // there's more data to be received. All other requests need to go into the deinit list (so that + // we keep track of it in the time inbetween it takes for the CURL thread to be scheduled and + // release the CURL handle from CURLM and release resources e.t.c.) + if (continue_ws_request) + DN_DoublyLLAppend(curl->request_list, req); + else + DN_DoublyLLAppend(curl->deinit_list, req); + } + + + // NOTE: Submit the post-request event to the CURL thread + DN_NETCurlRingEvent event = {}; + event.request = DN_NET_HandleFromRequest(req); + if (continue_ws_request) { + event.type = DN_NETCurlRingEventType_ReceivedWSReceipt; + } else { + // NOTE: Deinit _has_ to be sent to the CURL thread because we need to remove the CURL handle + // from the CURLM instance and the CURL thread uses the CURLM instance (e.g. CURLM is not thread + // safe) + event.type = DN_NETCurlRingEventType_DeinitRequest; + } + + for (DN_OS_MutexScope(&curl->ring_mutex)) + DN_RingWriteStruct(&curl->ring, &event); + curl_multi_wakeup(curl->thread_curlm); + + return result; +} + +DN_NETResponse DN_NET_CurlWaitForResponse(DN_NETRequestHandle handle, DN_Arena *arena, DN_U32 timeout_ms) +{ + DN_NETResponse result = {}; + DN_NETRequest *req = DN_NET_RequestFromHandle(handle); + if (!req) + return result; + + DN_NETCore *net = DN_Cast(DN_NETCore *) req->context[1]; + DN_NETCurlCore *curl = DN_Cast(DN_NETCurlCore *) net->context; + DN_Assert(curl); + + DN_OSSemaphoreWaitResult wait = DN_OS_SemaphoreWait(&req->completion_sem, timeout_ms); + if (wait != DN_OSSemaphoreWaitResult_Success) + return result; + + // NOTE: Decrement the global 'request done' completion semaphore since the user consumed the + // request individually. + DN_OSSemaphoreWaitResult net_wait_result = DN_OS_SemaphoreWait(&net->completion_sem, 0 /*timeout_ms*/); + DN_AssertF(net_wait_result == DN_OSSemaphoreWaitResult_Success, "Wait result was: %zu", DN_Cast(DN_USize) net_wait_result); + + // NOTE: Finish handling the response + result = DN_NET_CurlHandleFinishedRequest_(curl, req, arena); + return result; +} + +DN_NETResponse DN_NET_CurlWaitForAnyResponse(DN_NETCore *net, DN_Arena *arena, DN_U32 timeout_ms) +{ + DN_NETCurlCore *curl = DN_Cast(DN_NETCurlCore *) net->context; + DN_Assert(curl); + + DN_NETResponse result = {}; + DN_OSSemaphoreWaitResult req_wait = DN_OS_SemaphoreWait(&net->completion_sem, timeout_ms); + if (req_wait != DN_OSSemaphoreWaitResult_Success) + return result; + + // NOTE: Just grab the handle, handle finished request will dequeue for us + DN_NETRequestHandle handle = {}; + for (DN_OS_MutexScope(&curl->list_mutex)) { + DN_Assert(curl->response_list); + handle = DN_NET_HandleFromRequest(curl->response_list); + } + + // NOTE: Decrement the request's completion semaphore since the user consumed the global semaphore + DN_NETRequest *req = DN_NET_RequestFromHandle(handle); + DN_OSSemaphoreWaitResult net_wait = DN_OS_SemaphoreWait(&req->completion_sem, 0 /*timeout_ms*/); + DN_AssertF(net_wait == DN_OSSemaphoreWaitResult_Success, "Wait result was: %zu", DN_Cast(DN_USize) net_wait); + + // NOTE: Finish handling the response + result = DN_NET_CurlHandleFinishedRequest_(curl, req, arena); + return result; +} +#endif // #if DN_WITH_NET_CURL + +#if DN_WITH_NET_EMSCRIPTEN +#include +#include +#include + +struct DN_NETEmcWSEvent +{ + DN_NETResponseState state; + DN_Str8 payload; + DN_NETEmcWSEvent *next; +}; + +struct DN_NETEmcCore +{ + DN_Pool pool; + DN_NETRequest *response_list; // Responses received that are to be deqeued via wait for response + DN_NETRequest *free_list; // Request pool that new requests will use before allocating +}; + +struct DN_NETEmcRequest +{ + int socket; + DN_NETEmcWSEvent *first_event; + DN_NETEmcWSEvent *last_event; +}; + +DN_NETInterface DN_NET_EmcInterface() +{ + DN_NETInterface result = {}; + result.init = DN_NET_EmcInit; + result.deinit = DN_NET_EmcDeinit; + result.do_http = DN_NET_EmcDoHTTP; + result.do_ws = DN_NET_EmcDoWS; + result.do_ws_send = DN_NET_EmcDoWSSend; + result.wait_for_response = DN_NET_EmcWaitForResponse; + result.wait_for_any_response = DN_NET_EmcWaitForAnyResponse; + return result; +} + +static DN_NETEmcWSEvent *DN_NET_EmcAllocWSEvent_(DN_NETRequest *request) +{ + // NOTE: Allocate the event and attach to the request + DN_NETEmcRequest *emc_request = DN_Cast(DN_NETEmcRequest *) request->context[1]; + DN_NETEmcWSEvent *result = DN_ArenaNew(&request->arena, DN_NETEmcWSEvent, DN_ZMem_Yes); + DN_Assert(result); + if (result) { + if (!emc_request->first_event) + emc_request->first_event = result; + if (emc_request->last_event) + emc_request->last_event->next = result; + emc_request->last_event = result; + } + return result; +} + +static void DN_NET_EmcOnRequestDone_(DN_NETCore *net, DN_NETRequest *request) +{ + // NOTE: This may be call multiple times on the same request if we get multiple responses when we + // yield to the javascript event loop, e.g. the application received multiple WS payloads before + // it waited and consequently consumed the response from the payload. + // + // So if the next pointer is already set, then it should be that the request is already enqueued. + DN_NETEmcCore *emc = DN_Cast(DN_NETEmcCore *) net->context; + if (!request->next && !request->prev && request != emc->response_list) { + request->prev = nullptr; + request->next = emc->response_list; + if (emc->response_list) + emc->response_list->prev = request; + emc->response_list = request; + } + DN_OS_SemaphoreIncrement(&net->completion_sem, 1); + DN_OS_SemaphoreIncrement(&request->completion_sem, 1); +} + +static bool DN_NET_EmcWSOnOpen(int eventType, EmscriptenWebSocketOpenEvent const *event, void *user_data) +{ + DN_NETRequest *req = DN_Cast(DN_NETRequest *) user_data; + DN_NETCore *net = DN_Cast(DN_NETCore *) req->context[0]; + DN_NETEmcWSEvent *net_event = DN_NET_EmcAllocWSEvent_(req); + net_event->state = DN_NETResponseState_WSOpen; + DN_NET_EmcOnRequestDone_(net, req); + return true; +} + +static bool DN_NET_EmcWSOnMessage(int eventType, const EmscriptenWebSocketMessageEvent *event, void *user_data) +{ + DN_NETRequest *req = DN_Cast(DN_NETRequest *) user_data; + DN_NETCore *net = DN_Cast(DN_NETCore *) req->context[0]; + DN_NETEmcWSEvent *net_event = DN_NET_EmcAllocWSEvent_(req); + net_event->state = event->isText ? DN_NETResponseState_WSText : DN_NETResponseState_WSBinary; + if (event->numBytes > 0) + net_event->payload = DN_Str8FromPtrArena(event->data, event->numBytes, &req->arena); + DN_NET_EmcOnRequestDone_(net, req); + return true; +} + +static bool DN_NET_EmcWSOnError(int eventType, EmscriptenWebSocketErrorEvent const *event, void *user_data) +{ + DN_NETRequest *req = DN_Cast(DN_NETRequest *) user_data; + DN_NETCore *net = DN_Cast(DN_NETCore *) req->context[0]; + DN_NETEmcWSEvent *net_event = DN_NET_EmcAllocWSEvent_(req); + net_event->state = DN_NETResponseState_Error; + DN_NET_EmcOnRequestDone_(net, req); + return true; +} + +static bool DN_NET_EmcWSOnClose(int eventType, EmscriptenWebSocketCloseEvent const *event, void *user_data) +{ + DN_NETRequest *req = DN_Cast(DN_NETRequest *) user_data; + DN_NETCore *net = DN_Cast(DN_NETCore *) req->context[0]; + DN_NETEmcWSEvent *net_event = DN_NET_EmcAllocWSEvent_(req); + net_event->state = DN_NETResponseState_WSClose; + net_event->payload = DN_Str8FromFmtArena(&req->arena, "Websocket closed '%.*s': (%u) %s (was %s close)", DN_Str8PrintFmt(req->url), event->code, event->reason, event->wasClean ? "clean" : "unclean"); + DN_NET_EmcOnRequestDone_(net, req); + return true; +} + +static void DN_NET_EmcHTTPSuccessCallback(emscripten_fetch_t *fetch) +{ + DN_NETRequest *req = DN_Cast(DN_NETRequest *) fetch->userData; + DN_NETCore *net = DN_Cast(DN_NETCore *) req->context[0]; + req->response.http_status = fetch->status; + req->response.state = DN_NETResponseState_HTTP; + req->response.body = DN_Str8FromStr8Arena(DN_Str8FromPtr(fetch->data, fetch->numBytes - 1), &req->arena); + DN_NET_EmcOnRequestDone_(net, req); +} + +static void DN_NET_EmcHTTPFailCallback(emscripten_fetch_t *fetch) +{ + DN_NETRequest *req = DN_Cast(DN_NETRequest *) fetch->userData; + DN_NETCore *net = DN_Cast(DN_NETCore *) req->context[0]; + req->response.http_status = fetch->status; + req->response.state = DN_NETResponseState_Error; + DN_NET_EmcOnRequestDone_(net, req); +} + +static void DN_NET_EmcHTTPProgressCallback(emscripten_fetch_t *fetch) +{ +} + +void DN_NET_EmcInit(DN_NETCore *net, char *base, DN_U64 base_size) +{ + DN_NET_BaseInit(net, base, base_size); + DN_NETEmcCore *emc = DN_ArenaNew(&net->arena, DN_NETEmcCore, DN_ZMem_Yes); + emc->pool = DN_PoolFromArena(&net->arena, 0); + net->context = emc; +} + +void DN_NET_EmcDeinit(DN_NETCore *net) +{ + (void)net; + // TODO: Track all the request handles and clean it up +} + +static DN_NETRequest *DN_NET_EmcAllocRequest_(DN_NETCore *net) +{ + // NOTE: Allocate request + DN_NETEmcCore *emc = DN_Cast(DN_NETEmcCore *) net->context; + DN_NETRequest *result = emc->free_list; + if (result) { + emc->free_list = emc->free_list->next; + result->next = nullptr; + DN_Assert(result->prev == nullptr); + if (emc->free_list) { + DN_Assert(emc->free_list->prev == nullptr); + } + } else { + // NOTE: Setup the request's arena here. WASM doesn't have the concept of virtual memory + // so we use malloc to initialise it. + result = DN_ArenaNew(&net->arena, DN_NETRequest, DN_ZMem_Yes); + if (result) { + result->arena = DN_ArenaFromMemList(&result->mem); + } + } + + // NOTE: Setup some emscripten specific data into our request context + if (result) { + result->context[0] = DN_Cast(DN_UPtr) net; + } + + return result; +} + +DN_NETRequestHandle DN_NET_EmcDoHTTP(DN_NETCore *net, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args) +{ + DN_NETRequest *req = DN_NET_EmcAllocRequest_(net); + DN_NETRequestHandle result = DN_NET_SetupRequest(req, url, method, args, DN_NETRequestType_HTTP); + + // NOTE: Setup the HTTP request via Emscripten + emscripten_fetch_attr_t fetch_attribs = {}; + { + DN_Assert(req->args.payload.data[req->args.payload.size] == 0); + DN_Assert(req->url.data[req->url.size] == 0); + + // NOTE: Setup request for emscripten + emscripten_fetch_attr_init(&fetch_attribs); + + fetch_attribs.requestData = req->args.payload.data; + fetch_attribs.requestDataSize = req->args.payload.size; + DN_Assert(req->method.size < DN_ArrayCountU(fetch_attribs.requestMethod)); + DN_Memcpy(fetch_attribs.requestMethod, req->method.data, req->method.size); + fetch_attribs.requestMethod[req->method.size] = 0; + + // NOTE: Assign HTTP headers + if (req->args.headers_size) { + char **headers = DN_ArenaNewArray(&req->start_response_arena, char *, req->args.headers_size + 1, DN_ZMem_Yes); + for (DN_ForItSize(it, DN_Str8, req->args.headers, req->args.headers_size)) { + DN_Assert(it.data->data[it.data->size] == 0); + headers[it.index] = it.data->data; + } + fetch_attribs.requestHeaders = headers; + } + + // NOTE: Handle basic auth + if (req->args.flags & DN_NETDoHTTPFlags_BasicAuth) { + if (req->args.username.size && req->args.password.size) { + DN_Assert(req->args.username.data[req->args.username.size] == 0); + DN_Assert(req->args.password.data[req->args.password.size] == 0); + fetch_attribs.withCredentials = true; + fetch_attribs.userName = req->args.username.data; + fetch_attribs.password = req->args.password.data; + } + } + + // NOTE: It would be nice to use EMSCRIPTEN_FETCH_STREAM_DATA however + // emscripten has this note on the current version I'm using that this is + // only supported in Firefox so this is a no-go. + // + // > If passed, the intermediate streamed bytes will be passed in to the + // > onprogress() handler. If not specified, the onprogress() handler will still + // > be called, but without data bytes. Note: Firefox only as it depends on + // > 'moz-chunked-arraybuffer'. + fetch_attribs.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY; + fetch_attribs.onsuccess = DN_NET_EmcHTTPSuccessCallback; + fetch_attribs.onerror = DN_NET_EmcHTTPFailCallback; + fetch_attribs.onprogress = DN_NET_EmcHTTPProgressCallback; + fetch_attribs.userData = req; + } + + // NOTE: Dispatch the asynchronous fetch + emscripten_fetch(&fetch_attribs, req->url.data); + return result; +} + +DN_NETRequestHandle DN_NET_EmcDoWS(DN_NETCore *net, DN_Str8 url) +{ + DN_Assert(emscripten_websocket_is_supported()); + DN_NETRequest *req = DN_NET_EmcAllocRequest_(net); + DN_NETRequestHandle result = DN_NET_SetupRequest(req, url, /*method=*/DN_Str8Lit(""), /*args=*/nullptr, DN_NETRequestType_WS); + if (!req) + return result; + + // NOTE: Setup some emscripten specific data into our request context + req->context[1] = DN_Cast(DN_UPtr) DN_ArenaNew(&req->start_response_arena, DN_NETEmcRequest, DN_ZMem_Yes); + + // NOTE: Create the websocket request and dispatch it via emscripten + EmscriptenWebSocketCreateAttributes attr; + emscripten_websocket_init_create_attributes(&attr); + attr.url = req->url.data; + + DN_NETEmcRequest *emc_request = DN_Cast(DN_NETEmcRequest *) req->context[1]; + emc_request->socket = emscripten_websocket_new(&attr); + DN_Assert(emc_request->socket > 0); + emscripten_websocket_set_onopen_callback(emc_request->socket, /*userData=*/req, DN_NET_EmcWSOnOpen); + emscripten_websocket_set_onmessage_callback(emc_request->socket, /*userData=*/req, DN_NET_EmcWSOnMessage); + emscripten_websocket_set_onerror_callback(emc_request->socket, /*userData=*/req, DN_NET_EmcWSOnError); + emscripten_websocket_set_onclose_callback(emc_request->socket, /*userData=*/req, DN_NET_EmcWSOnClose); + + return result; +} + +void DN_NET_EmcDoWSSend(DN_NETRequestHandle handle, DN_Str8 data, DN_NETWSSend send) +{ + DN_AssertF(send == DN_NETWSSend_Binary || send == DN_NETWSSend_Text || send == DN_NETWSSend_Close, + "Unimplemented, Emscripten only supports some of the available operations"); + int result = 0; + DN_NETRequest *request_ptr = DN_Cast(DN_NETRequest *) handle.handle; + if (request_ptr && request_ptr->gen == handle.gen) { + DN_Assert(request_ptr->type == DN_NETRequestType_WS); + DN_NETEmcRequest *emc_request = DN_Cast(DN_NETEmcRequest *) request_ptr->context[1]; + switch (send) { + default: DN_AssertInvalidCodePath; break; + case DN_NETWSSend_Text: { + DN_U64 pos = DN_MemListPos(request_ptr->start_response_arena.mem); + DN_Str8 data_null_terminated = DN_Str8FromStr8Arena(data, &request_ptr->start_response_arena); + result = emscripten_websocket_send_utf8_text(emc_request->socket, data_null_terminated.data); + DN_MemListPopTo(request_ptr->arena.mem, pos); + } break; + + case DN_NETWSSend_Binary: { + result = emscripten_websocket_send_binary(emc_request->socket, data.data, data.size); + } break; + + case DN_NETWSSend_Close: { + result = emscripten_websocket_close(emc_request->socket, 0, nullptr); + } break; + } + } + // TODO: Handle result, the header file doesn't really elucidate what this result value is + (void)result; +} + +static DN_NETResponse DN_NET_EmcHandleFinishedRequest_(DN_NETCore *net, DN_NETEmcCore *emc, DN_NETRequestHandle handle, DN_NETRequest *request, DN_Arena *arena) +{ + // NOTE: Generate the response, copy out the strings into the user given memory + DN_NETEmcRequest *emc_request = DN_Cast(DN_NETEmcRequest *) request->context[1]; + DN_NETResponse result = request->response; + bool end_request = true; + bool dequeue_request = true; + if (request->type == DN_NETRequestType_HTTP) { + result.body = DN_Str8FromStr8Arena(result.body, arena); + } else { + // NOTE: Get emscripten contexts + DN_NETEmcWSEvent *emc_event = emc_request->first_event; + DN_Assert(emc_event); + + DN_AssertF((emc_event->state >= DN_NETResponseState_WSOpen && emc_event->state <= DN_NETResponseState_WSPong) || emc_event->state == DN_NETResponseState_Error, + "emc_event=%p", emc_event); + + // NOTE: Build the result + result.state = emc_event->state; + result.request = handle; + result.body = DN_Str8FromStr8Arena(emc_event->payload, arena); + + // NOTE: Advance the event list + { + if (emc_request->first_event == emc_request->last_event) { + emc_request->last_event = emc_request->last_event->next; + DN_Assert(emc_request->first_event->next == emc_request->last_event); + } + emc_request->first_event = emc_event->next; + + // NOTE: If there's still an event on the request then we do not dequeue the request from the + // response list. The user can still "wait" for a response to read more data from it. + if (emc_request->first_event) + dequeue_request = false; + } + + if (result.state != DN_NETResponseState_WSClose) + end_request = false; + } + + // NOTE: Remove request from the response list which is doubly-linked + if (dequeue_request) { + if (request->prev) { + DN_AssertF(request->prev->next == request, "next=%p vs request=%p", request->prev->next, request); + request->prev->next = request->next; + } + + if (request->next) { + DN_AssertF(request->next->prev == request, "prev=%p vs request=%p", request->next->prev, request); + request->next->prev = request->prev; + } + + if (request == emc->response_list) + emc->response_list = emc->response_list->next; + + request->prev = nullptr; + request->next = nullptr; + DN_Assert(emc_request->first_event == nullptr); + DN_Assert(emc_request->last_event == nullptr); + + // NOTE: Deallocate the memory used in the request and reset the string builder (as all + // payload(s) have been read from the request). + if (!end_request) + DN_ArenaTempEnd(&request->start_response_arena, DN_ArenaReset_Yes); + } + + if (end_request) { + DN_NET_EndFinishedRequest(request); + emscripten_websocket_delete(emc_request->socket); + emc_request->socket = 0; + + DN_NETEmcCore *emc = DN_Cast(DN_NETEmcCore *) net->context; + request->next = emc->free_list; + request->prev = nullptr; + emc->free_list = request; + } + + return result; +} + +static DN_OSSemaphoreWaitResult DN_NET_EmcSemaphoreWait_(DN_OSSemaphore *sem, DN_U32 timeout_ms) +{ + // NOTE: In emscripten you can't just block on the semaphore with 'timeout_ms' because it needs + // to yield to the javascript's event loop otherwise the fetching step cannot progress. Instead + // we use a timeout of 0 to just immediately check if the semaphore has been signalled, if not, + // then we yield to the event loop by calling sleep. + // + // Once yielded, fetch will execute and eventually in the callback it will signal the semaphore + // where it'll return and we can break out of the simulated "timeout". + DN_OSSemaphoreWaitResult result = {}; + DN_U32 timeout_remaining_ms = timeout_ms; + DN_F64 begin_ms = emscripten_get_now(); + for (;;) { + result = DN_OS_SemaphoreWait(sem, 0); + if (result == DN_OSSemaphoreWaitResult_Success) + break; + if (timeout_remaining_ms <= 0) + break; + + emscripten_sleep(100 /*ms*/); + DN_F64 end_ms = emscripten_get_now(); + DN_USize duration_ms = DN_Cast(DN_USize)(end_ms - begin_ms); + timeout_remaining_ms = timeout_remaining_ms >= duration_ms ? timeout_remaining_ms - duration_ms : 0; + begin_ms = end_ms; + } + return result; +} + +DN_NETResponse DN_NET_EmcWaitForResponse(DN_NETRequestHandle handle, DN_Arena *arena, DN_U32 timeout_ms) +{ + DN_NETResponse result = {}; + DN_NETRequest *request_ptr = DN_Cast(DN_NETRequest *) handle.handle; + if (request_ptr && request_ptr->gen == handle.gen) { + DN_NETCore *net = DN_Cast(DN_NETCore *) request_ptr->context[0]; + DN_NETEmcCore *emc = DN_Cast(DN_NETEmcCore *) net->context; + DN_Assert(emc); + DN_OSSemaphoreWaitResult wait = DN_NET_EmcSemaphoreWait_(&request_ptr->completion_sem, timeout_ms); + if (wait != DN_OSSemaphoreWaitResult_Success) + return result; + + result = DN_NET_EmcHandleFinishedRequest_(net, emc, handle, request_ptr, arena); + + // NOTE: Decrement the global 'request done' completion semaphore since the user consumed the + // request individually. + DN_OSSemaphoreWaitResult net_wait_result = DN_OS_SemaphoreWait(&net->completion_sem, 0 /*timeout_ms*/); + DN_AssertF(net_wait_result == DN_OSSemaphoreWaitResult_Success, "Wait result was: %zu", DN_Cast(DN_USize) net_wait_result); + } + return result; +} + +DN_NETResponse DN_NET_EmcWaitForAnyResponse(DN_NETCore *net, DN_Arena *arena, DN_U32 timeout_ms) +{ + DN_NETEmcCore *emc = DN_Cast(DN_NETEmcCore *) net->context; + DN_Assert(emc); + + DN_NETResponse result = {}; + DN_OSSemaphoreWaitResult wait = DN_NET_EmcSemaphoreWait_(&net->completion_sem, timeout_ms); + if (wait != DN_OSSemaphoreWaitResult_Success) + return result; + + DN_AssertF(emc->response_list, + "This should be set otherwise we bumped the completion sem without queueing into the " + "done list or we forgot to wait on the global semaphore after a request finished"); + + // NOTE: Decrement the request's completion semaphore since the user consumed the global semaphore + DN_NETRequest *request_ptr = emc->response_list; + DN_OSSemaphoreWaitResult net_wait_result = DN_OS_SemaphoreWait(&request_ptr->completion_sem, 0 /*timeout_ms*/); + DN_AssertF(net_wait_result == DN_OSSemaphoreWaitResult_Success, "Wait result was: %zu", DN_Cast(DN_USize) net_wait_result); + + DN_NETRequestHandle request = {}; + request.handle = DN_Cast(DN_UPtr) request_ptr; + request.gen = request_ptr->gen; + result = DN_NET_EmcHandleFinishedRequest_(net, emc, request, request_ptr, arena); + + return result; +} +#endif // #if DN_WITH_NET_EMSCRIPTEN diff --git a/Source/dn.h b/Source/dn.h index 77dd839..d1ee711 100644 --- a/Source/dn.h +++ b/Source/dn.h @@ -13,19 +13,22 @@ // // The following is a single translation unit example: /* - #define DN_H_WITH_OS 1 - #define DN_H_WITH_CORE 1 - #define DN_H_WITH_HELPERS 1 - #define DN_H_WITH_ASYNC 1 - #define DN_H_WITH_NET 1 - #include "dn.h" + #define DN_WITH_OS 1 + #define DN_WITH_NET 0 + #define DN_WITH_NET_CURL 0 + #define DN_WITH_NET_EMSCRIPTEN 0 + #include "dn.h" - #define DN_CPP_WITH_TESTS 1 - #define DN_CPP_WITH_DEMO 1 - #include "dn.cpp" + #define DN_CPP_WITH_TESTS 1 + #include "dn.cpp" + + int main() + { + DN_Core core = {}; + DN_Init(&core, DN_InitFlags_Nil, DN_TCInitArgsDefault()); + return 0; + } */ -// Then initialise the library at runtime by calling DN_Init(...). The library is laid out as: -// // - The base layer (dn_base.h) which provides primitives that do not require a host operating // system (e.g. freestanding) such as string manipulation, compiler intrinsics and containers. // This layer is unconditionallly always available by compiling with this library. @@ -37,6 +40,24 @@ // NOTE: Configuration +// NOTE: OS layer +// Enable the operating system layer which enables thread context, file system, threads, e.t.c: +// +// #define DN_WITH_OS 1 + +// NOTE: Networking layer +// Enable the networking layer (pre-requisite that the OS layer is enabled) that allows sending +// HTTP/Websocket requests either using CURL or Emscripten's networking APIs. +// +// #define DN_WITH_NET 1 +// #define DN_WITH_NET_CURL 1 +// #define DN_WITH_NET_EMSCRIPTEN 1 +// +// For CURL, ensure that you the target application links to a libcurl and that the +// #include is visible before this translation unit. +// +// For Emscripten ensure that this translation unit is compiled with `emcc` to have it enabled. + // NOTE: Platform Target // Define one of the following directives to configure this library to compile for that // platform. By default, the library will auto-detect the current host platform and select that @@ -63,7 +84,7 @@ // the functions in the library do not export an entry into the linking table. // translation units. -// NOTE: Disabling the in-built (if #define DN_H_WITH_OS 1) +// NOTE: Disabling the in-built (if #define DN_WITH_OS 1) // If you are building DN for the Windows platform, is a large legacy header that // applications have to include to use Windows APIs. By default this library uses a replacement // header for all the Windows functions that it uses in the OS layer removing the need to @@ -195,32 +216,2046 @@ // // #define DN_STR8_AVX512F 1 -#include "Base/dn_base.h" +#if defined(_CLANGD) + #define DN_WITH_OS 1 + #define DN_STR8_AVX512F 1 + #define DN_PARANOIA_LEVEL 1 +#endif -#if DN_H_WITH_OS -#if defined(DN_PLATFORM_WIN32) - #include "OS/dn_os_windows.h" - #include "OS/dn_os_w32.h" -#elif defined(DN_PLATFORM_POSIX) || defined(DN_PLATFORM_EMSCRIPTEN) - #include "OS/dn_os_posix.h" +// NOTE: Compiler identification +// NOTE: Warning! Order is important here, clang-cl on Windows defines _MSC_VER +#if defined(_MSC_VER) + #if defined(__clang__) + #define DN_COMPILER_CLANG_CL + #define DN_COMPILER_CLANG + #else + #define DN_COMPILER_MSVC + #endif +#elif defined(__clang__) + #define DN_COMPILER_CLANG +#elif defined(__GNUC__) + #define DN_COMPILER_GCC +#endif + +// NOTE: __has_feature +// NOTE: MSVC for example does not support the feature detection macro for instance so we compile it +// out +#if defined(__has_feature) + #define DN_HAS_FEATURE(expr) __has_feature(expr) #else - #error Please define a platform e.g. 'DN_PLATFORM_WIN32' to enable the correct implementation for platform APIs + #define DN_HAS_FEATURE(expr) 0 #endif -#include "OS/dn_os.h" + +// NOTE: __has_builtin +// NOTE: MSVC for example does not support the feature detection macro for instance so we compile it out +#if defined(__has_builtin) + #define DN_HAS_BUILTIN(expr) __has_builtin(expr) +#else + #define DN_HAS_BUILTIN(expr) 0 #endif +// NOTE: Warning suppression macros +#if defined(DN_COMPILER_MSVC) + #define DN_MSVC_WARNING_PUSH __pragma(warning(push)) + #define DN_MSVC_WARNING_DISABLE(...) __pragma(warning(disable :##__VA_ARGS__)) + #define DN_MSVC_WARNING_ENABLE(...) __pragma(warning(default :##__VA_ARGS__)) + #define DN_MSVC_WARNING_POP __pragma(warning(pop)) +#else + #define DN_MSVC_WARNING_PUSH + #define DN_MSVC_WARNING_ENABLE(...) + #define DN_MSVC_WARNING_DISABLE(...) + #define DN_MSVC_WARNING_POP +#endif + +#if defined(DN_COMPILER_CLANG) || defined(DN_COMPILER_GCC) || defined(DN_COMPILER_CLANG_CL) + #define DN_GCC_WARNING_PUSH _Pragma("GCC diagnostic push") + #define DN_GCC_WARNING_DISABLE_HELPER_0(x) #x + #define DN_GCC_WARNING_DISABLE_HELPER_1(y) DN_GCC_WARNING_DISABLE_HELPER_0(GCC diagnostic ignored #y) + #define DN_GCC_WARNING_DISABLE(warning) _Pragma(DN_GCC_WARNING_DISABLE_HELPER_1(warning)) + #define DN_GCC_WARNING_POP _Pragma("GCC diagnostic pop") +#else + #define DN_GCC_WARNING_PUSH + #define DN_GCC_WARNING_DISABLE(...) + #define DN_GCC_WARNING_POP +#endif + +// NOTE: Host OS identification +#if defined(_WIN32) + #define DN_OS_WIN32 +#elif defined(__gnu_linux__) || defined(__linux__) + #define DN_OS_UNIX +#endif + +// NOTE: Platform identification +#if !defined(DN_PLATFORM_EMSCRIPTEN) && \ + !defined(DN_PLATFORM_POSIX) && \ + !defined(DN_PLATFORM_WIN32) + #if defined(__aarch64__) || defined(_M_ARM64) + #define DN_PLATFORM_ARM64 + #elif defined(__EMSCRIPTEN__) + #define DN_PLATFORM_EMSCRIPTEN + #elif defined(DN_OS_WIN32) + #define DN_PLATFORM_WIN32 + #else + #define DN_PLATFORM_POSIX + #endif +#endif + +// NOTE: Windows crap +#if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL) + #if defined(_CRT_SECURE_NO_WARNINGS) + #define DN_CRT_SECURE_NO_WARNINGS_PREVIOUSLY_DEFINED + #else + #define _CRT_SECURE_NO_WARNINGS + #endif +#endif + +// NOTE: Force Inline +#if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL) + #define DN_FORCE_INLINE __forceinline +#else + #define DN_FORCE_INLINE inline __attribute__((always_inline)) +#endif + +// NOTE: Function/Variable Annotations +#if defined(DN_STATIC_API) + #define DN_API static +#else + #define DN_API +#endif + +// NOTE: C/CPP Literals +// 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 = DN_LITERAL(Foo){32}; // Works on both C and C++ +#endif + +#if defined(__cplusplus) + #define DN_Literal(T) T +#else + #define DN_Literal(T) (T) +#endif + +// NOTE: Thread Locals +#if defined(__cplusplus) + #define DN_THREAD_LOCAL thread_local +#else + #define DN_THREAD_LOCAL _Thread_local +#endif + +// NOTE: C variadic argument annotations +// TODO: Other compilers +#if defined(DN_COMPILER_MSVC) + #define DN_FMT_ATTRIB _Printf_format_string_ +#else + #define DN_FMT_ATTRIB +#endif + +// NOTE: Type Cast +#define DN_Cast(val) (val) + +// NOTE: Zero initialisation macro +#if defined(__cplusplus) + #define DN_ZeroInit {} +#else + #define DN_ZeroInit {0} +#endif + +// NOTE: Macros +#define DN_Stringify(x) #x +#define DN_TokenCombine2(x, y) x ## y +#define DN_TokenCombine(x, y) DN_TokenCombine2(x, y) + +// NOTE: Error Checking/Validating +// Asserts are useful to verify invariants in the codebase, but there's sometimes the ambiguous +// question of what should be asserted, what happens when we should have triggered an assert +// in a release build (where they are canonically turned off), what alternative mechanisms should we +// use for error checking that should be visible to non-developers. +// +// The following is an excerpt from Tom Forsyth's assertion article which he references Chris +// Hargrove's guidelines on how asserts show be used. It is quite reasonable and we model our +// primitives after based on those concepts: +// +// Logging, asserts and unit tests (https://tomforsyth1000.github.io/blog.wiki.html +// +// Assert: Immediately fatal, and not ignorable. Fundamental assumption by an engineer has been +// disproven and needs immediate handling. Requires discipline on the part of the engineer to not +// add them in situations that are actually non-fatal (rule of thumb being that if a crash would +// be almost certain to happen anyway due to the same condition, then you’re no worse off making +// an assert). +// +// Errors: Probably fatal soon, but not necessarily immediately. Basically a marker for “you are +// now in a f*cked state, you might limp along a bit, but assume nothing”. Game continues, but an +// ugly red number gets displayed onscreen for how many of these have been encountered (so when +// people send you screenshots of bugs you can then point to the red error count and blame +// accordingly). Savegames are disabled from this point so as not to make the error effectively +// permanent; you should also deliberately violate a few other TCRs as soon as an error is +// encountered in order to ensure that all parties up and down the publisher/developer chain are +// aware of how bad things are. Errors are technically “ignorable” but everyone knows that it +// might only buy you a little bit of borrowed time; these are only a small step away from the +// immediately-blocking nature of an assert, but sometimes that small step can have a big impact +// on productivity. +// +// Warnings: Used for “you did something bad, but we caught it so it’s fine (the game state is +// still okay), however it might not be fine in the future so if you want to save yourself some +// headache you should fix this sooner rather than later”. Great for content problems. Also +// displayed onscreen as a yellow number (near the red error number). You can keep these around +// for a while and triage them when their utility is called into question. +// +// Crumbs: The meta-category for a large number of “verbose” informational breadcrumb categories +// that must be explicitly enabled so you don’t clutter everything up and obscure stuff that +// matters. Note that the occurrance of certain Errors should automatically enable relevant +// categories of crumbs so that more detailed information about the aforementioned f*cked state +// will be provided during the limp-along timeframe. +// +// In the excerpt, their domain (games programming) prioritises continuity over immediate failure +// on warning and error as this allows non-developer clientele to continue using the application +// despite error laden states. This is useful in general as not all failures are critical to the +// use case that the end user is dealing with. +// +// We model `Errors` and `Warnings` as `DN_Verify` and `DN_VerifyWarning` respectively. The verify +// variants check the expression to test, log and a message and allow the developer to branch on the +// result and "recover" where appropriate. Verify checks are never compiled out. We have traditional +// `Asserts` as `DN_Assert` which can be compiled out. +// +// The article also defines what it calls a paranoia level. We `#define DN_PARANOIA_LEVEL ` +// to customise the validation layers of the codebase. See DN_PARANOIA_LEVEL in the customisation +// section for more information. +// +// In summary use each of the primitives in these situation: +// +// `DN_Assert`: Fatal and immediately needs attention and can be compiled out +// +// `DN_Verify`: Fatal or eventually fatal but not necessarily immediately, program is or will +// degenerate into an incorrect state. Is always compiled in and is visible in non-debug +// environments. +// +// `DN_VerifyWarning`: Something bad happened, but we caught it and recovered from it. Program +// state remains consistent. It is always compiled in and is visible in non-debug environments. + +#if !defined(DN_PARANOIA_LEVEL) + #if defined(NDEBUG) + #define DN_PARANOIA_LEVEL 0 + #else + #define DN_PARANOIA_LEVEL 1 + #endif +#endif + +#if DN_HAS_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__) + #include +#endif + +#define DN_ASAN_POISON_ALIGNMENT 8 +#if !defined(DN_ASAN_VET_POISON) + #define DN_ASAN_VET_POISON 0 +#endif + +#if !defined(DN_ASAN_POISON) + #if DN_PARANOIA_LEVEL >= 1 + #define DN_ASAN_POISON 1 + #else + #define DN_ASAN_POISON 0 + #endif +#endif + +#if !defined(DN_ASAN_POISON_GUARD_SIZE) + #define DN_ASAN_POISON_GUARD_SIZE 128 +#endif + +#if !defined(DN_ARENA_TEMP_MEM_UAF_GUARD) + #if DN_PARANOIA_LEVEL >= 1 + #define DN_ARENA_TEMP_MEM_UAF_GUARD 1 + #else + #define DN_ARENA_TEMP_MEM_UAF_GUARD 0 + #endif +#endif + +#if !defined(DN_ARENA_TEMP_MEM_UAF_TRACE_ON_BY_DEFAULT) + #if DN_PARANOIA_LEVEL >= 2 + #define DN_ARENA_TEMP_MEM_UAF_TRACE_ON_BY_DEFAULT 1 + #else + #define DN_ARENA_TEMP_MEM_UAF_TRACE_ON_BY_DEFAULT 0 + #endif +#endif + +#if !defined(DN_SCRUB_UNINIT_MEM_BYTE) + #if DN_PARANOIA_LEVEL >= 1 + #define DN_SCRUB_UNINIT_MEM_BYTE 0xCD + #else + #define DN_SCRUB_UNINIT_MEM_BYTE 0x00 + #endif +#endif + +#define DN_AssertRaw(expr) do { if (!(expr)) DN_DebugBreak; } while (0) +#define DN_AssertAlwaysCallSiteF(expr, call_site, fmt, ...) \ + do { \ + if (!(expr)) { \ + DN_Str8 trace_ = DN_Str8FromStackTraceNowHeap(128 /*limit*/, 3 /*skip*/); \ + DN_LogTypeParam log_type_ = DN_LogTypeParamFromType(DN_LogType_Error); \ + DN_LogPrintF(log_type_, call_site, DN_LogFlags_Nil, "Assertion triggered [" #expr "]. " fmt "\nTrace:\n%.*s", ## __VA_ARGS__, DN_Str8PrintFmt(trace_)); \ + DN_DebugBreak; \ + } \ + } while (0) + +#define DN_AssertAlwaysF(expr, fmt, ...) DN_AssertAlwaysCallSiteF(expr, (DN_CallSiteNow), fmt, ##__VA_ARGS__) +#define DN_AssertAlways(expr) DN_AssertAlwaysF(expr, "") +#define DN_AssertInvalidCodePathF(fmt, ...) DN_AssertAlwaysF(0, fmt, ##__VA_ARGS__) +#define DN_AssertInvalidCodePath DN_AssertInvalidCodePathF("Invalid code path") +#if DN_PARANOIA_LEVEL >= 1 +#define DN_AssertCallSiteF(expr, call_site, fmt, ...) DN_AssertAlwaysCallSiteF(expr, call_site, fmt, ## __VA_ARGS__) +#define DN_AssertF(expr, fmt, ...) DN_AssertCallSiteF(expr, (DN_CallSiteNow), fmt, ## __VA_ARGS__) +#define DN_Assert(expr) DN_AssertAlways(expr) +#else +#define DN_AssertCallSiteF(expr, call_site, fmt, ...) (void)(expr); (void)call_site +#define DN_AssertF(expr, fmt, ...) (void)(expr) +#define DN_Assert(expr) (void)(expr) +#endif +#define DN_VerifyF(expr, fmt, ...) DN_VerifyArgsF(DN_VerifyType_Nil, expr, (DN_CallSiteNow), DN_Str8Lit(#expr), fmt, ##__VA_ARGS__) +#define DN_VerifyWarningF(expr, fmt, ...) DN_VerifyArgsF(DN_VerifyType_Warning, expr, (DN_CallSiteNow), DN_Str8Lit(#expr), fmt, ##__VA_ARGS__) +#define DN_Verify(expr) DN_VerifyF(expr, 0) +#define DN_VerifyWarning(expr) DN_VerifyWarningF(expr, 0) + +#define DN_StaticAssert(expr) \ + DN_GCC_WARNING_PUSH \ + DN_GCC_WARNING_DISABLE(-Wunused-local-typedefs) \ + typedef char DN_TokenCombine(static_assert_dummy__, __LINE__)[(expr) ? 1 : -1]; \ + DN_GCC_WARNING_POP + +#if defined(__aarch64__) || defined(_M_X64) || defined(__x86_64__) || defined(__x86_64) + #define DN_64_BIT +#else + #define DN_32_BIT +#endif + +#include // va_list +#include +#include +#include +#include // PRIu64... + +#if !defined(DN_OS_WIN32) +#include // exit() +#endif + +#define DN_ForIndexU(index, count) DN_USize index = 0; index < count; index++ +#define DN_ForIndexI(index, count) DN_ISize index = 0; index < count; index++ +#define DN_ForItSize(it, T, array, count) struct { DN_USize index; T *data; } it = {0, &(array)[0]}; it.index < (count); it.index++, it.data = (array) + it.index +#define DN_ForItSizeReverse(it, T, array, count) struct { DN_USize index; T *data; } it = {(count) - 1, &(array)[count - 1]}; it.index < (count); it.index--, it.data = (array) + it.index +#define DN_ForIt(it, T, array) struct { DN_USize index; T *data; } it = {0, &(array)->data[0]}; it.index < (array)->count; it.index++, it.data = ((array)->data) + it.index +#define DN_ForLinkedListIt(it, T, list) struct { DN_USize index; T *data; } it = {0, list}; it.data; it.index++, it.data = ((it).data->next) +#define DN_ForItCArray(it, T, array) struct { DN_USize index; T *data; } it = {0, &(array)[0]}; it.index < DN_ArrayCountU(array); it.index++, it.data = (array) + it.index + +#define DN_AlignUpPowerOfTwo(value, pot) (((uintptr_t)(value) + ((uintptr_t)(pot) - 1)) & ~((uintptr_t)(pot) - 1)) +#define DN_AlignDownPowerOfTwo(value, pot) ((uintptr_t)(value) & ~((uintptr_t)(pot) - 1)) +#define DN_IsPowerOfTwo(value) ((((uintptr_t)(value)) & (((uintptr_t)(value)) - 1)) == 0) +#define DN_IsPowerOfTwoAligned(value, pot) ((((uintptr_t)value) & (((uintptr_t)pot) - 1)) == 0) + +// NOTE: String.h Dependencies +#if !defined(DN_Memcpy) || !defined(DN_Memset) || !defined(DN_Memcmp) || !defined(DN_Memmove) + #include + #if !defined(DN_Memcpy) + #define DN_Memcpy(dest, src, count) memcpy((dest), (src), (count)) + #endif + #if !defined(DN_Memset) + #define DN_Memset(dest, value, count) memset((dest), (value), (count)) + #endif + #if !defined(DN_Memcmp) + #define DN_Memcmp(lhs, rhs, count) memcmp((lhs), (rhs), (count)) + #endif + #if !defined(DN_Memmove) + #define DN_Memmove(dest, src, count) memmove((dest), (src), (count)) + #endif +#endif + +// NOTE: Math.h Dependencies +#if !defined(DN_SqrtF32) || !defined(DN_SinF32) || !defined(DN_CosF32) || !defined(DN_TanF32) + #include + #if !defined(DN_SqrtF32) + #define DN_SqrtF32(val) sqrtf(val) + #endif + #if !defined(DN_SinF32) + #define DN_SinF32(val) sinf(val) + #endif + #if !defined(DN_CosF32) + #define DN_CosF32(val) cosf(val) + #endif + #if !defined(DN_TanF32) + #define DN_TanF32(val) tanf(val) + #endif + #if !defined(DN_PowF32) + #define DN_PowF32(val, exp) powf(val, exp) + #endif +#endif + +// NOTE: Math +#define DN_PiF32 3.14159265359f + +#define DN_DegreesToRadsF32(degrees) ((degrees) * (DN_PiF32 / 180.0f)) +#define DN_RadsToDegreesF32(radians) ((radians) * (180.f * DN_PiF32)) + +#define DN_Abs(val) (((val) < 0) ? (-(val)) : (val)) +#define DN_Max(a, b) (((a) > (b)) ? (a) : (b)) +#define DN_Min(a, b) (((a) < (b)) ? (a) : (b)) +#define DN_Clamp(val, lo, hi) DN_Max(DN_Min(val, hi), lo) +#define DN_Squared(val) ((val) * (val)) + +#define DN_Swap(a, b) \ + do { \ + auto temp = a; \ + a = b; \ + b = temp; \ + } while (0) + +// NOTE: Size +#define DN_SizeOfI(val) DN_Cast(ptrdiff_t)sizeof(val) +#define DN_ArrayCountU(array) (sizeof(array)/(sizeof((array)[0]))) +#define DN_ArrayCountI(array) (DN_ISize)DN_ArrayCountU(array) +#define DN_CharCountU(string) (sizeof(string) - 1) + +// NOTE: SI Byte +#define DN_Bytes(val) ((DN_U64)val) +#define DN_Kilobytes(val) ((DN_U64)1024 * DN_Bytes(val)) +#define DN_Megabytes(val) ((DN_U64)1024 * DN_Kilobytes(val)) +#define DN_Gigabytes(val) ((DN_U64)1024 * DN_Megabytes(val)) + +// NOTE: Time +#define DN_MsFromSec(val) ((val) * 1000ULL) +#define DN_SecFromMins(val) ((val) * 60ULL) +#define DN_SecFromHours(val) (DN_SecFromMins(val) * 60ULL) +#define DN_SecFromDays(val) (DN_SecFromHours(val) * 24ULL) +#define DN_SecFromWeeks(val) (DN_SecFromDays(val) * 7ULL) +#define DN_SecFromYears(val) (DN_SecFromWeeks(val) * 52ULL) + +// NOTE: Debug Break +#if !defined(DN_DebugBreak) + #if defined(NDEBUG) + #define DN_DebugBreak + #else + #if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL) + #define DN_DebugBreak __debugbreak() + #elif DN_HAS_BUILTIN(__builtin_debugtrap) + #define DN_DebugBreak __builtin_debugtrap() + #elif DN_HAS_BUILTIN(__builtin_trap) || defined(DN_COMPILER_GCC) + #define DN_DebugBreak __builtin_trap() + #else + #include + #if defined(SIGTRAP) + #define DN_DebugBreak raise(SIGTRAP) + #else + #define DN_DebugBreak raise(SIGABRT) + #endif + #endif + #endif +#endif + +// NOTE: Helper macros to declare an array data structure for a given `Type` +#define DN_DArrayStructDecl(Type) \ + struct Type##Array \ + { \ + Type* data; \ + DN_USize count; \ + DN_USize max; \ + } + +#define DN_FixedArrayStructDecl(Type, capacity) \ + struct Type##x##capacity##Array \ + { \ + Type data[capacity]; \ + DN_USize count; \ + DN_USize max; \ + } + +// NOTE: Types +typedef intptr_t DN_ISize; +typedef uintptr_t DN_USize; + +typedef int8_t DN_I8; +typedef int16_t DN_I16; +typedef int32_t DN_I32; +typedef int64_t DN_I64; + +typedef uint8_t DN_U8; +typedef uint16_t DN_U16; +typedef uint32_t DN_U32; +typedef uint64_t DN_U64; + +typedef uintptr_t DN_UPtr; +typedef float DN_F32; +typedef double DN_F64; +typedef unsigned int DN_UInt; +typedef DN_I32 DN_B32; + +#define DN_F32_MAX 3.402823466e+38F +#define DN_F32_MIN 1.175494351e-38F +#define DN_F64_MAX 1.7976931348623158e+308 +#define DN_F64_MIN 2.2250738585072014e-308 +#define DN_USIZE_MAX UINTPTR_MAX +#define DN_ISIZE_MAX INTPTR_MAX +#define DN_ISIZE_MIN INTPTR_MIN + +// NOTE: Intrinsics +// NOTE: DN_AtomicAdd/Exchange return the previous value store in the target +#if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL) + #include + #define DN_AtomicCompareExchange64(dest, desired_val, prev_val) _InterlockedCompareExchange64((__int64 volatile *)dest, desired_val, prev_val) + #define DN_AtomicCompareExchange32(dest, desired_val, prev_val) _InterlockedCompareExchange((long volatile *)dest, desired_val, prev_val) + + #define DN_AtomicLoadU64(target) *(target) + #define DN_AtomicLoadU32(target) *(target) + #define DN_AtomicAddU32(target, value) _InterlockedExchangeAdd((long volatile *)target, value) + #define DN_AtomicAddU64(target, value) _InterlockedExchangeAdd64((__int64 volatile *)target, value) + #define DN_AtomicSubU32(target, value) DN_AtomicAddU32(DN_Cast(long volatile *) target, (long)-value) + #define DN_AtomicSubU64(target, value) DN_AtomicAddU64(target, (DN_U64) - value) + + #define DN_CountLeadingZerosU64(value) __lzcnt64(value) + #define DN_CountLeadingZerosU32(value) __lzcnt(value) + #define DN_CPUGetTSC() __rdtsc() + #define DN_CompilerReadBarrierAndCPUReadFence _ReadBarrier(); _mm_lfence() + #define DN_CompilerWriteBarrierAndCPUWriteFence _WriteBarrier(); _mm_sfence() +#elif defined(DN_COMPILER_GCC) || defined(DN_COMPILER_CLANG) + #if defined(__ANDROID__) + #elif defined(DN_PLATFORM_EMSCRIPTEN) + #if !defined(__wasm_simd128__) + #error DN_Base requires -msse2 to be passed to Emscripten + #endif + #include + #else + #include + #endif + + #define DN_AtomicLoadU64(target) __atomic_load_n(x, __ATOMIC_SEQ_CST) + #define DN_AtomicLoadU32(target) __atomic_load_n(x, __ATOMIC_SEQ_CST) + #define DN_AtomicAddU32(target, value) __atomic_fetch_add(target, value, __ATOMIC_ACQ_REL) + #define DN_AtomicAddU64(target, value) __atomic_fetch_add(target, value, __ATOMIC_ACQ_REL) + #define DN_AtomicSubU32(target, value) __atomic_fetch_sub(target, value, __ATOMIC_ACQ_REL) + #define DN_AtomicSubU64(target, value) __atomic_fetch_sub(target, value, __ATOMIC_ACQ_REL) + + #define DN_CountLeadingZerosU64(value) __builtin_clzll(value) + #define DN_CountLeadingZerosU32(value) __builtin_clzl(value) + + #if defined(DN_COMPILER_GCC) + #define DN_CPUGetTSC() __rdtsc() + #else + #define DN_CPUGetTSC() __builtin_readcyclecounter() + #endif + + #if defined(DN_PLATFORM_EMSCRIPTEN) + #define DN_CompilerReadBarrierAndCPUReadFence + #define DN_CompilerWriteBarrierAndCPUWriteFence + #else + #define DN_CompilerReadBarrierAndCPUReadFence asm volatile("lfence" ::: "memory") + #define DN_CompilerWriteBarrierAndCPUWriteFence asm volatile("sfence" ::: "memory") + #endif +#else + #error "Compiler not supported" +#endif + +#if defined(DN_64_BIT) + #define DN_CountLeadingZerosUSize(value) DN_CountLeadingZerosU64(value) +#else + #define DN_CountLeadingZerosUSize(value) DN_CountLeadingZerosU32(value) +#endif + +enum DN_VerifyType +{ + DN_VerifyType_Nil, + DN_VerifyType_Warning, +}; + +enum DN_ZMem +{ + DN_ZMem_No, // Memory can be handed out without zero-ing it out + DN_ZMem_Yes, // Memory should be zero-ed out before giving to the callee +}; + +struct DN_Str8 +{ + char *data; // The bytes of the string + DN_USize size; // The number of bytes in the string +}; + +struct DN_Str8Slice +{ + DN_Str8 *data; + DN_USize count; +}; + +struct DN_Str8x16 { char data[16]; DN_USize size; }; +struct DN_Str8x32 { char data[32]; DN_USize size; }; +struct DN_Str8x64 { char data[64]; DN_USize size; }; +struct DN_Str8x128 { char data[128]; DN_USize size; }; +struct DN_Str8x256 { char data[256]; DN_USize size; }; +struct DN_Str8x512 { char data[512]; DN_USize size; }; +struct DN_Str8x1024 { char data[1024]; DN_USize size; }; + +struct DN_Str16 // A pointer and length style string that holds slices to UTF16 bytes. +{ + wchar_t *data; // The UTF16 bytes of the string + DN_USize size; // The number of characters in the string +}; + +struct DN_Str16Slice +{ + DN_Str16 *data; + DN_USize count; +}; + +struct DN_CPURegisters +{ + int eax; + int ebx; + int ecx; + int edx; +}; + +union DN_CPUIDResult +{ + DN_CPURegisters reg; + int values[4]; +}; + +struct DN_CPUIDArgs { int eax; int ecx; }; + +#define DN_CPU_FEAT_XMACRO \ + DN_CPU_FEAT_XENTRY(3DNow) \ + DN_CPU_FEAT_XENTRY(3DNowExt) \ + DN_CPU_FEAT_XENTRY(ABM) \ + DN_CPU_FEAT_XENTRY(AES) \ + DN_CPU_FEAT_XENTRY(AVX) \ + DN_CPU_FEAT_XENTRY(AVX2) \ + DN_CPU_FEAT_XENTRY(AVX512F) \ + DN_CPU_FEAT_XENTRY(AVX512DQ) \ + DN_CPU_FEAT_XENTRY(AVX512IFMA) \ + DN_CPU_FEAT_XENTRY(AVX512PF) \ + DN_CPU_FEAT_XENTRY(AVX512ER) \ + DN_CPU_FEAT_XENTRY(AVX512CD) \ + DN_CPU_FEAT_XENTRY(AVX512BW) \ + DN_CPU_FEAT_XENTRY(AVX512VL) \ + DN_CPU_FEAT_XENTRY(AVX512VBMI) \ + DN_CPU_FEAT_XENTRY(AVX512VBMI2) \ + DN_CPU_FEAT_XENTRY(AVX512VNNI) \ + DN_CPU_FEAT_XENTRY(AVX512BITALG) \ + DN_CPU_FEAT_XENTRY(AVX512VPOPCNTDQ) \ + DN_CPU_FEAT_XENTRY(AVX5124VNNIW) \ + DN_CPU_FEAT_XENTRY(AVX5124FMAPS) \ + DN_CPU_FEAT_XENTRY(AVX512VP2INTERSECT) \ + DN_CPU_FEAT_XENTRY(AVX512FP16) \ + DN_CPU_FEAT_XENTRY(CLZERO) \ + DN_CPU_FEAT_XENTRY(CMPXCHG8B) \ + DN_CPU_FEAT_XENTRY(CMPXCHG16B) \ + DN_CPU_FEAT_XENTRY(F16C) \ + DN_CPU_FEAT_XENTRY(FMA) \ + DN_CPU_FEAT_XENTRY(FMA4) \ + DN_CPU_FEAT_XENTRY(FP128) \ + DN_CPU_FEAT_XENTRY(FP256) \ + DN_CPU_FEAT_XENTRY(FPU) \ + DN_CPU_FEAT_XENTRY(MMX) \ + DN_CPU_FEAT_XENTRY(MONITOR) \ + DN_CPU_FEAT_XENTRY(MOVBE) \ + DN_CPU_FEAT_XENTRY(MOVU) \ + DN_CPU_FEAT_XENTRY(MmxExt) \ + DN_CPU_FEAT_XENTRY(PCLMULQDQ) \ + DN_CPU_FEAT_XENTRY(POPCNT) \ + DN_CPU_FEAT_XENTRY(RDRAND) \ + DN_CPU_FEAT_XENTRY(RDSEED) \ + DN_CPU_FEAT_XENTRY(RDTSCP) \ + DN_CPU_FEAT_XENTRY(SHA) \ + DN_CPU_FEAT_XENTRY(SSE) \ + DN_CPU_FEAT_XENTRY(SSE2) \ + DN_CPU_FEAT_XENTRY(SSE3) \ + DN_CPU_FEAT_XENTRY(SSE41) \ + DN_CPU_FEAT_XENTRY(SSE42) \ + DN_CPU_FEAT_XENTRY(SSE4A) \ + DN_CPU_FEAT_XENTRY(SSSE3) \ + DN_CPU_FEAT_XENTRY(TSC) \ + DN_CPU_FEAT_XENTRY(TscInvariant) \ + DN_CPU_FEAT_XENTRY(VAES) \ + DN_CPU_FEAT_XENTRY(VPCMULQDQ) + +enum DN_CPUFeature +{ + #define DN_CPU_FEAT_XENTRY(label) DN_CPUFeature_##label, + DN_CPU_FEAT_XMACRO + #undef DN_CPU_FEAT_XENTRY + DN_CPUFeature_Count, +}; + +struct DN_CPUFeatureDecl +{ + DN_CPUFeature value; + DN_Str8 label; +}; + +struct DN_CPUFeatureQuery +{ + DN_CPUFeature feature; + bool available; +}; + +struct DN_CPUReport +{ + char vendor[4 /*bytes*/ * 3 /*EDX, ECX, EBX*/ + 1 /*null*/]; + char brand[48]; + DN_U64 features[(DN_CPUFeature_Count / (sizeof(DN_U64) * 8)) + 1]; +}; + +struct DN_TicketMutex +{ + unsigned int volatile ticket; // The next ticket to give out to the thread taking the mutex + unsigned int volatile serving; // The ticket ID to block the mutex on until it is returned +}; + + +struct DN_Hex32 { char data[32 + 1]; DN_USize size; }; +struct DN_Hex64 { char data[64 + 1]; DN_USize size; }; +struct DN_Hex128 { char data[128 + 1]; DN_USize size; }; + +struct DN_HexU64 +{ + char data[(sizeof(DN_U64) * 2) + 1 /*null-terminator*/]; + DN_U8 size; +}; + +enum DN_HexFromU64Type +{ + DN_HexFromU64Type_Nil, + DN_HexFromU64Type_Uppercase, +}; + +enum DN_TrimLeadingZero +{ + DN_TrimLeadingZero_No, + DN_TrimLeadingZero_Yes, +}; + +struct DN_U8x16 { DN_U8 data[16]; }; +struct DN_U8x32 { DN_U8 data[32]; }; +struct DN_U8x64 { DN_U8 data[64]; }; + +DN_MSVC_WARNING_PUSH +DN_MSVC_WARNING_DISABLE(4201) // warning C4201: nonstandard extension used: nameless struct/union +union DN_V2USize +{ + struct { DN_USize x, y; }; + struct { DN_USize w, h; }; + struct { DN_USize min, max; }; + struct { DN_USize begin, end; }; + DN_USize data[2]; +}; + +union DN_V2U64 +{ + struct { DN_U64 x, y; }; + struct { DN_U64 w, h; }; + struct { DN_U64 min, max; }; + struct { DN_U64 begin, end; }; + DN_U64 data[2]; +}; +DN_MSVC_WARNING_POP + +struct DN_CallSite +{ + DN_Str8 file; + DN_Str8 function; + DN_U32 line; +}; + +#define DN_CallSiteNow DN_Literal(DN_CallSite){DN_Str8Lit(__FILE__), DN_Str8Lit(__func__), __LINE__ } + +#if defined(__cplusplus) +template +struct DN_Defer +{ + Procedure proc; + DN_Defer(Procedure p) : proc(p) {} + ~DN_Defer() { proc(); } +}; + +struct DN_DeferHelper +{ + template + DN_Defer operator+(Lambda lambda) { return DN_Defer(lambda); }; +}; + +#define DN_UniqueName(prefix) DN_TokenCombine(prefix, __LINE__) +#define DN_DEFER const auto DN_UniqueName(defer_lambda_) = DN_DeferHelper() + [&]() +#endif // defined(__cplusplus) + +#define DN_DeferLoop(begin, end) \ + bool DN_UniqueName(once) = (begin, true); \ + DN_UniqueName(once); \ + end, DN_UniqueName(once) = false + +struct DN_U64FromResult +{ + bool success; + DN_U64 value; +}; + +struct DN_USizeFromResult +{ + bool success; + DN_USize value; +}; + +struct DN_I64FromResult +{ + bool success; + DN_I64 value; +}; + +struct DN_U8x32FromResult +{ + bool success; + DN_U8x32 value; +}; + +struct DN_StackTraceFrame +{ + DN_U64 address; + DN_U64 line_number; + DN_Str8 file_name; + DN_Str8 function_name; +}; + +struct DN_StackTraceFrameSlice +{ + DN_StackTraceFrame *data; + DN_USize count; +}; + +struct DN_StackTraceRawFrame +{ + void *process; + DN_U64 base_addr; +}; + +struct DN_StackTrace +{ + void *process; // [Internal] Windows handle to the process + DN_U64 *base_addr; // The addresses of the functions in the stack trace + DN_U16 size; // The number of `base_addr`'s stored from the walk +}; + +struct DN_StackTraceIterator +{ + DN_StackTraceRawFrame raw_frame; + DN_U16 index; +}; + +enum DN_MemCommit +{ + DN_MemCommit_No, + DN_MemCommit_Yes, +}; + +typedef DN_U32 DN_MemPage; +enum DN_MemPage_ +{ + // Exception on read/write with a page. This flag overrides the read/write + // access. + DN_MemPage_NoAccess = 1 << 0, + + DN_MemPage_Read = 1 << 1, // Only read permitted on the page. + + // Only write permitted on the page. On Windows this is not supported and + // will be promoted to read+write permissions. + DN_MemPage_Write = 1 << 2, + + DN_MemPage_ReadWrite = DN_MemPage_Read | DN_MemPage_Write, + + // Modifier used in conjunction with previous flags. Raises exception on + // 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 DN_MemPage_NoAccess. + // This flag must only be used in DN_Mem_Protect + DN_MemPage_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. + DN_MemPage_AllocRecordLeakPermitted = 1 << 4, + + // If leak tracing is enabled this flag will prevent any allocation record + // from being created in the allocation table at all. If this flag is + // enabled, 'OSMemPage_AllocRecordLeakPermitted' has no effect since the + // record will never be created. + DN_MemPage_NoAllocRecordEntry = 1 << 5, + + // [INTERNAL] Do not use. All flags together do not constitute a correct + // configuration of pages. + DN_MemPage_All = DN_MemPage_NoAccess | + DN_MemPage_ReadWrite | + DN_MemPage_Guard | + DN_MemPage_AllocRecordLeakPermitted | + DN_MemPage_NoAllocRecordEntry, +}; + +#if !defined(DN_ARENA_RESERVE_SIZE) + #define DN_ARENA_RESERVE_SIZE DN_Megabytes(64) +#endif + +#if !defined(DN_ARENA_COMMIT_SIZE) + #define DN_ARENA_COMMIT_SIZE DN_Kilobytes(64) +#endif + +enum DN_MemFuncsType +{ + DN_MemFuncsType_Nil, + DN_MemFuncsType_Heap, + DN_MemFuncsType_Virtual, +}; + +typedef void *(DN_MemHeapAllocFunc)(DN_USize size); +typedef void (DN_MemHeapDeallocFunc)(void *ptr); +typedef void *(DN_MemVirtualReserveFunc)(DN_USize size, DN_MemCommit commit, DN_MemPage page_flags); +typedef bool (DN_MemVirtualCommitFunc)(void *ptr, DN_USize size, DN_U32 page_flags); +typedef void (DN_MemVirtualReleaseFunc)(void *ptr, DN_USize size); +struct DN_MemFuncs +{ + DN_MemFuncsType type; + DN_MemHeapAllocFunc *heap_alloc; + DN_MemHeapDeallocFunc *heap_dealloc; + + DN_U32 virtual_page_size; + DN_MemVirtualReserveFunc *virtual_reserve; + DN_MemVirtualCommitFunc *virtual_commit; + DN_MemVirtualReleaseFunc *virtual_release; +}; + +struct DN_MemBlock +{ + DN_MemBlock* prev; + DN_U64 used; + DN_U64 commit; + DN_U64 reserve; + DN_U64 reserve_sum; +}; + +struct DN_MemListInfo +{ + DN_U64 used; + DN_U64 commit; + DN_U64 reserve; + DN_U64 blocks; +}; + +struct DN_MemStats +{ + DN_MemListInfo info; + DN_MemListInfo hwm; +}; + +typedef DN_U32 DN_MemFlags; +enum DN_MemFlags_ +{ + DN_MemFlags_Nil = 0, + DN_MemFlags_NoGrow = 1 << 0, + DN_MemFlags_NoPoison = 1 << 1, + DN_MemFlags_NoAllocTrack = 1 << 2, + DN_MemFlags_AllocCanLeak = 1 << 3, + DN_MemFlags_SimAlloc = 1 << 4, + + // NOTE: Records stack traces of temp memory regions on construction to provide more diagnostics + // when UAF violation occurs in the use of a region (e.g. nested regions A and B, with A + // allocating whilst B is active would result in A's memory being wiped at the end of B). Tracing + // has a heavy performance penalty as each scratch/temp memory region triggers and stores the + // stack trace. + // + // Ignored if UAF guard is disabled at the preprocessor level + // (e.g.: #define DN_ARENA_TEMP_MEM_UAF_GUARD 0) + DN_MemFlags_TempMemUAFTrace = 1 << 5, + + // NOTE: Forcibly disables TempMemUAFTrace for the arena irrespective of global settings. Globally + // UAF tracing can be enabled across all arenas via the preprocessor which turns the tracing + // feature (e.g.: #define DN_ARENA_TEMP_MEM_UAF_TRACE_ON_BY_DEFAULT 1) into an opt-out situation + // where arenas have to specify this flag, specifically to not be traced. + // + // If both TempMemUAFTrace, TempMemUAFTraceDisable and or the global preprocessor flag is set + // disabling takes precedence, always if it is set. + DN_MemFlags_TempMemUAFTraceDisable = 1 << 6, + + // NOTE: Internal flags. Do not use + DN_MemFlags_UserBuffer = 1 << 7, + DN_MemFlags_MemFuncs = 1 << 8, +}; + +struct DN_MemList +{ + DN_MemBlock* curr; + DN_MemFlags flags; + DN_MemFuncs funcs; + DN_MemStats stats; + DN_Str8 label; + + #if DN_ARENA_TEMP_MEM_UAF_GUARD + DN_U32 uaf_guard_next_id; + DN_U32 uaf_guard_active_id; + struct DN_MemListTemp* uaf_guard_active_temp_mem; + #endif +}; + +struct DN_MemListTemp +{ + DN_MemList* mem; + DN_U64 used_sum; + #if DN_ARENA_TEMP_MEM_UAF_GUARD + DN_StackTrace trace; + #endif +}; + +enum DN_AllocatorType +{ + DN_AllocatorType_MemList, + DN_AllocatorType_Arena, + DN_AllocatorType_Pool, +}; + +struct DN_Allocator +{ + DN_AllocatorType type; + void* context; +}; + +enum DN_ArenaReset +{ + DN_ArenaReset_No, + DN_ArenaReset_Yes, +}; + +typedef DN_U32 DN_ArenaFlags; +enum DN_ArenaFlags_ +{ + DN_ArenaFlags_Nil = 0, + DN_ArenaFlags_OwnsMemList = 1 << 0, +}; + +struct DN_Arena +{ + DN_ArenaFlags flags; + DN_MemList* mem; + #if DN_ARENA_TEMP_MEM_UAF_GUARD + DN_U32 uaf_guard_id; + DN_MemListTemp* uaf_guard_temp_mem; + DN_U32 uaf_guard_prev_id; + DN_MemListTemp* uaf_guard_prev_temp_mem; + bool uaf_guard_is_being_checked; + #else + DN_MemListTemp temp_mem; + #endif +}; + +DN_USize const DN_ARENA_HEADER_SIZE = DN_AlignUpPowerOfTwo(sizeof(DN_Arena), 64); + +#if !defined(DN_POOL_DEFAULT_ALIGN) + #define DN_POOL_DEFAULT_ALIGN 16 +#endif + +struct DN_PoolSlot +{ + void *data; + DN_PoolSlot *next; +}; + +enum DN_PoolSlotSize +{ + DN_PoolSlotSize_32B, + DN_PoolSlotSize_64B, + DN_PoolSlotSize_128B, + DN_PoolSlotSize_256B, + DN_PoolSlotSize_512B, + DN_PoolSlotSize_1KiB, + DN_PoolSlotSize_2KiB, + DN_PoolSlotSize_4KiB, + DN_PoolSlotSize_8KiB, + DN_PoolSlotSize_16KiB, + DN_PoolSlotSize_32KiB, + DN_PoolSlotSize_64KiB, + DN_PoolSlotSize_128KiB, + DN_PoolSlotSize_256KiB, + DN_PoolSlotSize_512KiB, + DN_PoolSlotSize_1MiB, + DN_PoolSlotSize_2MiB, + DN_PoolSlotSize_4MiB, + DN_PoolSlotSize_8MiB, + DN_PoolSlotSize_16MiB, + DN_PoolSlotSize_32MiB, + DN_PoolSlotSize_64MiB, + DN_PoolSlotSize_128MiB, + DN_PoolSlotSize_256MiB, + DN_PoolSlotSize_512MiB, + DN_PoolSlotSize_1GiB, + DN_PoolSlotSize_2GiB, + DN_PoolSlotSize_4GiB, + DN_PoolSlotSize_8GiB, + DN_PoolSlotSize_16GiB, + DN_PoolSlotSize_32GiB, + DN_PoolSlotSize_Count, +}; + +struct DN_Pool +{ + DN_Arena *arena; + DN_PoolSlot *slots[DN_PoolSlotSize_Count]; + DN_U8 align; +}; + +struct DN_UTF8DecodeResult +{ + bool success; + DN_Str8 remaining; + DN_U32 codepoint; +}; + +struct DN_UTF8DecodeIterator +{ + bool init; + bool success; + DN_Str8 remaining; + DN_USize codepoint_index; + DN_U32 codepoint; +}; + +typedef DN_U32 DN_CodepointCountFlags; +enum DN_CodepointCountFlags_ +{ + DN_CodepointCountFlags_Nil = 0, + DN_CodepointCountFlags_SkipANSICode = 1 << 0, +}; + +struct DN_NibbleFromU8Result +{ + char nibble0; + char nibble1; +}; + +enum DN_Str8EqCase +{ + DN_Str8EqCase_Sensitive, + DN_Str8EqCase_Insensitive, +}; + +enum DN_Str8IsAllType +{ + DN_Str8IsAllType_Digits, + DN_Str8IsAllType_Hex, +}; + +struct DN_Str8BSplitResult +{ + // If there are multiple strings passed to split against, this is the index into that array of + // which the string was split on. If no array was passed this is always 0. + DN_USize input_index; + DN_Str8 lhs; + DN_Str8 rhs; +}; + +struct DN_Str8FindResult +{ + bool found; // True if string was found. If false, the subsequent fields below are not set. + DN_USize index; // Index in the buffer where the found string starts + DN_Str8 match; // Matching string in the buffer that was searched + DN_Str8 match_to_end_of_buffer; // Substring containing the found string to the end of the buffer + DN_Str8 after_match_to_end_of_buffer; // Substring starting after the found string to the end of the buffer + DN_Str8 start_to_before_match; // Substring from the start of the buffer up until the found string, not including it +}; + +typedef DN_USize DN_Str8FindFlag; +enum DN_Str8FindFlag_ +{ + DN_Str8FindFlag_Digit = 1 << 0, // 0-9 + DN_Str8FindFlag_Whitespace = 1 << 1, // '\r', '\t', '\n', ' ' + DN_Str8FindFlag_Alphabet = 1 << 2, // A-Z, a-z + DN_Str8FindFlag_Plus = 1 << 3, // + + DN_Str8FindFlag_Minus = 1 << 4, // - + DN_Str8FindFlag_AlphaNum = DN_Str8FindFlag_Alphabet | DN_Str8FindFlag_Digit, +}; + +typedef DN_USize DN_Str8SplitFlags; +enum DN_Str8SplitFlags_ +{ + DN_Str8SplitFlags_Nil = 0, + DN_Str8SplitFlags_ExcludeEmptyStrings = 1 << 0, + DN_Str8SplitFlags_HandleQuotedStrings = 1 << 1, +}; + +struct DN_Str8TruncResult +{ + bool truncated; + DN_Str8 str8; + DN_USize size_req; // Not including null-terminator +}; + +struct DN_Str8SplitResult +{ + DN_Str8 *data; + DN_USize count; +}; + +enum DN_Str8LineBreakMode +{ + DN_Str8LineBreakMode_AtWord, // Add delimiter to string at ' ' and '\n' boundaries + DN_Str8LineBreakMode_AtWidth, // Add delimiter to string at width intervals +}; + +typedef DN_USize DN_Str8TableFlags; +enum DN_Str8TableFlags_ +{ + DN_Str8TableFlags_None = 0, + DN_Str8TableFlags_HasHeader = 1 << 0, + DN_Str8TableFlags_RowLines = 1 << 1, +}; + +struct DN_Str8Link +{ + DN_Str8 string; // The string + DN_Str8Link *next; // The next string in the linked list + DN_Str8Link *prev; // The prev string in the linked list +}; + +struct DN_Str8Builder +{ + DN_Arena* arena; // Allocator to use to back the string list + DN_Str8Link* head; // First string in the linked list of strings + DN_Str8Link* tail; // Last string in the linked list of strings + DN_USize string_size; // The size in bytes necessary to construct the current string + DN_USize count; // The number of links in the linked list of strings +}; + +enum DN_Str8BuilderAdd +{ + DN_Str8BuilderAdd_Append, + DN_Str8BuilderAdd_Prepend, +}; + +typedef DN_U32 DN_AgeUnit; +enum DN_AgeUnit_ +{ + DN_AgeUnit_Ms = 1 << 0, + DN_AgeUnit_Sec = 1 << 1, + DN_AgeUnit_Min = 1 << 2, + DN_AgeUnit_Hr = 1 << 3, + DN_AgeUnit_Day = 1 << 4, + DN_AgeUnit_Week = 1 << 5, + DN_AgeUnit_Year = 1 << 6, + DN_AgeUnit_FractionalSec = 1 << 7, + DN_AgeUnit_HMS = DN_AgeUnit_Sec | DN_AgeUnit_Min | DN_AgeUnit_Hr, + DN_AgeUnit_All = DN_AgeUnit_Ms | DN_AgeUnit_HMS | DN_AgeUnit_Day | DN_AgeUnit_Week | DN_AgeUnit_Year, +}; + +enum DN_ByteType +{ + DN_ByteType_B, + DN_ByteType_KiB, + DN_ByteType_MiB, + DN_ByteType_GiB, + DN_ByteType_TiB, + DN_ByteType_Count, + DN_ByteType_Auto, +}; + +struct DN_ByteCount +{ + DN_ByteType type; + DN_Str8 suffix; // "KiB", "MiB", "GiB" .. e.t.c + DN_F64 bytes; +}; + +struct DN_Date +{ + DN_U8 day; + DN_U8 month; + DN_U16 year; + DN_U8 hour; + DN_U8 minutes; + DN_U8 seconds; + DN_U16 milliseconds; +}; + +struct DN_FmtAppendResult +{ + DN_USize size_req; + DN_Str8 str8; + bool truncated; +}; + +struct DN_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. + DN_U64 tsc_inclusive; + DN_U64 tsc_exclusive; + DN_U16 hit_count; + DN_Str8 name; +}; + +struct DN_ProfilerZone +{ + struct DN_Profiler *profiler; + DN_U16 anchor_index; + DN_U64 begin_tsc; + DN_U16 parent_zone; + DN_U64 elapsed_tsc_at_zone_start; +}; + +struct DN_ProfilerAnchorArray +{ + DN_ProfilerAnchor *data; + DN_USize count; +}; + +typedef DN_U64 (DN_ProfilerTSCNowFunc)(); +struct DN_Profiler +{ + DN_USize frame_index; + DN_ProfilerAnchor *anchors; + DN_USize anchors_count; + DN_USize anchors_per_frame; + DN_U16 parent_zone; + bool paused; + DN_ProfilerTSCNowFunc *tsc_now; + DN_U64 tsc_frequency; + DN_ProfilerZone frame_zone; + DN_F64 frame_avg_tsc; +}; + +typedef bool (DN_QSortCompareFunc)(void const *a, void const *b, void *user_context); +enum DN_ErrSinkMode +{ + DN_ErrSinkMode_Nil, // Default behaviour to accumulate errors into the sink + DN_ErrSinkMode_DebugBreakOnErrorLog, // Debug break (int3) when error is encountered and the sink is ended by the 'end and log' functions. + DN_ErrSinkMode_ExitOnError, // When an error is encountered, exit the program with the error code of the error that was caught. +}; + +struct DN_ErrSinkMsg +{ + DN_I32 error_code; + DN_Str8 msg; + DN_CallSite call_site; + DN_ErrSinkMsg *next; + DN_ErrSinkMsg *prev; +}; + +struct DN_ErrSinkNode +{ + DN_CallSite call_site; // Call site that the node was created + DN_ErrSinkMode mode; // Controls how the sink behaves when an error is registered onto the sink. + DN_ErrSinkMsg *msg_sentinel; // List of error messages accumulated for the current scope + DN_U64 arena_pos; // Position to reset the arena when the scope is ended +}; + +struct DN_ErrSink +{ + DN_Arena* arena; // Dedicated allocator from the thread's local storage + DN_ErrSinkNode stack[128]; // Each entry contains errors accumulated between a [begin, end] region of the active sink. + DN_USize stack_size; +}; + +struct DN_TCScratch +{ + DN_Arena arena; + DN_B32 destructed; +}; + +#if defined(__cplusplus) +struct DN_TCScratchCpp +{ + DN_TCScratchCpp(DN_Arena **conflicts, DN_USize count); + ~DN_TCScratchCpp(); + DN_TCScratch data; +}; +#endif + +struct DN_TCInitArgs +{ + DN_U64 main_reserve; + DN_U64 main_commit; + DN_U64 temp_reserve; + DN_U64 temp_commit; + DN_U64 temp_count; + DN_U64 err_sink_reserve; + DN_U64 err_sink_commit; +}; + +struct DN_TCCore // (T)hread (C)ontext sitting in thread-local storage +{ + DN_Str8x64 name; + DN_U64 thread_id; + DN_CallSite call_site; + char lane_opaque[sizeof(DN_U64) * 4]; + void* user_context; + + DN_MemList main_arena_mem_; + DN_MemList temp_arena_mems_[4]; + DN_MemList err_sink_arena_mem_; + + DN_Arena main_arena_; + DN_Arena temp_arenas_[4]; + DN_Arena err_sink_arena_; + + DN_Arena* main_arena; + DN_Pool main_pool; + DN_Arena* temp_arenas[4]; + DN_USize temp_arenas_count; + + DN_ErrSink err_sink; + + DN_Arena* frame_arena; +}; + +enum DN_TCDeinitArenas +{ + DN_TCDeinitArenas_No, + DN_TCDeinitArenas_Yes, +}; + +struct DN_PCG32 { DN_U64 state; }; +struct DN_MurmurHash3 { DN_U64 e[2]; }; + +enum DN_LogType +{ + DN_LogType_Debug, + DN_LogType_Info, + DN_LogType_Warning, + DN_LogType_Error, + DN_LogType_Count, +}; + +enum DN_LogBold +{ + DN_LogBold_No, + DN_LogBold_Yes, +}; + +struct DN_LogStyle +{ + DN_LogBold bold; + bool colour; + DN_U8 r, g, b; +}; + +struct DN_LogTypeParam +{ + bool is_u32_enum; + DN_U32 u32; + DN_Str8 str8; +}; + +enum DN_ANSIColourMode +{ + DN_ANSIColourMode_Fg, + DN_ANSIColourMode_Bg, +}; + +struct DN_LogDate +{ + DN_U16 year; + DN_U8 month; + DN_U8 day; + + DN_U8 hour; + DN_U8 minute; + DN_U8 second; +}; + +struct DN_LogPrefixSize +{ + DN_USize size; + DN_USize padding; +}; + +typedef DN_U32 DN_LogFlags; +enum DN_LogFlags_ +{ + DN_LogFlags_Nil = 0, + DN_LogFlags_NoNewLine = 1 << 0, + DN_LogFlags_NoPrefix = 1 << 1, +}; + +typedef void DN_LogPrintFunc(DN_LogTypeParam type, void *user_data, DN_CallSite call_site, DN_LogFlags flags, DN_FMT_ATTRIB char const *fmt, va_list args); + +DN_MSVC_WARNING_PUSH +DN_MSVC_WARNING_DISABLE(4201) // warning C4201: nonstandard extension used: nameless struct/union +union DN_V2I32 +{ + struct { DN_I32 x, y; }; + struct { DN_I32 w, h; }; + DN_I32 data[2]; +}; + +union DN_V2U16 +{ + struct { DN_U16 x, y; }; + struct { DN_U16 w, h; }; + DN_U16 data[2]; +}; + +union DN_V2U32 +{ + struct { DN_U32 x, y; }; + struct { DN_U32 w, h; }; + struct { DN_U32 min, max; }; + DN_U32 data[2]; +}; + +union DN_V2F32 +{ + struct { DN_F32 x, y; }; + struct { DN_F32 w, h; }; + DN_F32 data[2]; +}; + +struct DN_2V2F32 +{ + DN_V2F32 min; + DN_V2F32 max; +}; + +struct DN_V2F32Array +{ + DN_V2F32 *data; + DN_USize count; + DN_USize max; +}; + +union DN_V3F32 +{ + struct { DN_F32 x, y, z; }; + struct { DN_F32 r, g, b; }; + DN_V2F32 xy; + DN_F32 data[3]; +}; + +union DN_V4F32 +{ + struct { DN_F32 x, y, z, w; }; + struct { DN_F32 r, g, b, a; }; + DN_V3F32 rgb; + DN_V3F32 xyz; + DN_F32 data[4]; +}; + +struct DN_V4F32Array +{ + DN_V4F32* data; + DN_USize count; + DN_USize max; +}; +DN_MSVC_WARNING_POP + +struct DN_M4 +{ + DN_F32 columns[4][4]; // Column major matrix +}; + +union DN_M2x3 +{ + DN_F32 e[6]; + DN_F32 row[2][3]; +}; + +struct DN_M2x3XForm +{ + DN_M2x3 forward; + DN_M2x3 inverse; +}; + +enum DN_M2x3ProjOrigin +{ + DN_M2x3ProjOrigin_TopLeft, + DN_M2x3ProjOrigin_Center, +}; + +struct DN_Rect +{ + DN_V2F32 pos, size; +}; + +enum DN_RectCutClip +{ + DN_RectCutClip_No, + DN_RectCutClip_Yes, +}; + +enum DN_RectCutSide +{ + DN_RectCutSide_Left, + DN_RectCutSide_Right, + DN_RectCutSide_Top, + DN_RectCutSide_Bottom, +}; + +struct DN_RectCut +{ + DN_Rect* rect; + DN_RectCutSide side; +}; + +struct DN_RaycastV2 +{ + bool hit; // True if there was an intersection, false if the lines are parallel + DN_F32 t_a; // Distance along `dir_a` that the intersection occurred, e.g. `origin_a + (dir_a * t_a)` + DN_F32 t_b; // Distance along `dir_b` that the intersection occurred, e.g. `origin_b + (dir_b * t_b)` +}; + +struct DN_Ring +{ + DN_U64 size; + char *base; + DN_U64 write_pos; + DN_U64 read_pos; +}; + +enum DN_ArrayErase +{ + DN_ArrayErase_Unstable, + DN_ArrayErase_Stable, +}; + +enum DN_ArrayAdd +{ + DN_ArrayAdd_Append, + DN_ArrayAdd_Prepend, +}; + +struct DN_ArrayEraseResult +{ + // The next index your for-index should be set to such that you can continue + // to iterate the remainder of the array, e.g: + // + // for (DN_USize index = 0; index < array.size; index++) { + // if (erase) + // index = DN_FArray_EraseRange(&array, index, -3, DN_ArrayErase_Unstable); + // } + DN_USize it_index; + DN_USize items_erased; // The number of items erased +}; + +struct DN_ArrayFindResult +{ + bool success; + DN_USize index; + void *value; +}; +typedef bool (DN_ArrayFindEqFunc)(void const *lhs, void const *find); + +enum DN_DSMapKeyType +{ + // Key | Key Hash | Map Index + DN_DSMapKeyType_Invalid, + DN_DSMapKeyType_U64, // U64 | Hash(U64) | Hash(U64) % map_size + DN_DSMapKeyType_U64NoHash, // U64 | U64 | U64 % map_size + DN_DSMapKeyType_Buffer, // Buffer | Hash(buffer) | Hash(buffer) % map_size + DN_DSMapKeyType_BufferAsU64NoHash, // Buffer | U64(buffer[0:4]) | U64(buffer[0:4]) % map_size +}; + +struct DN_DSMapKey +{ + DN_DSMapKeyType type; + DN_U32 hash; // Hash to lookup in the map. If it equals, we check that the original key payload matches + void const *buffer_data; + DN_U32 buffer_size; + DN_U64 u64; + bool no_copy_buffer; +}; + +template +struct DN_DSMapSlot +{ + DN_DSMapKey key; // Hash table lookup key + T value; // Hash table value +}; + +typedef DN_U32 DN_DSMapFlags; +enum DN_DSMapFlags_ +{ + DN_DSMapFlags_Nil = 0, + DN_DSMapFlags_DontFreeArenaOnResize = 1 << 0, +}; + +using DN_DSMapHashFunction = DN_U32(DN_DSMapKey key, DN_U32 seed); +template struct DN_DSMap +{ + DN_U32 *hash_to_slot; // Mapping from hash to a index in the slots array + DN_DSMapSlot *slots; // Values of the array stored contiguously, non-sorted order + DN_U32 size; // Total capacity of the map and is a power of two + DN_U32 occupied; // Number of slots used in the hash table + DN_Arena *arena; // Backing arena for the hash table + DN_Pool pool; // Allocator for keys that are variable-sized buffers + DN_U32 initial_size; // Initial map size, map cannot shrink on erase below this size + DN_DSMapHashFunction *hash_function; // Custom hashing function to use if field is set + DN_U32 hash_seed; // Seed for the hashing function, when 0, DN_DS_MAP_DEFAULT_HASH_SEED is used + DN_DSMapFlags flags; +}; + +template struct DN_DSMapResult +{ + bool found; + DN_DSMapSlot *slot; + T *value; +}; + +enum DN_LeakAllocFlag +{ + DN_LeakAllocFlag_Freed = 1 << 0, + DN_LeakAllocFlag_LeakPermitted = 1 << 1, +}; + +struct DN_LeakAlloc +{ + void *ptr; // 8 Pointer to the allocation being tracked + DN_USize size; // 16 Size of the allocation + DN_USize freed_size; // 24 Store the size of the allocation when it is freed + DN_Str8 stack_trace; // 40 Stack trace at the point of allocation + DN_Str8 freed_stack_trace; // 56 Stack trace of where the allocation was freed + DN_U16 flags; // 72 Bit flags from `DN_LeakAllocFlag` +}; + +// NOTE: We aim to keep the allocation record as light as possible as memory tracking can get +// expensive. Enforce that there is no unexpected padding. +DN_StaticAssert(sizeof(DN_LeakAlloc) == 64 || sizeof(DN_LeakAlloc) == 32); // NOTE: 64 bit vs 32 bit pointers respectively + +struct DN_LeakTracker +{ + DN_DSMap alloc_table; + DN_TicketMutex alloc_table_mutex; + DN_MemList alloc_table_mem; + DN_Arena alloc_table_arena; + DN_U64 alloc_table_bytes_allocated_for_stack_traces; +}; + typedef DN_USize DN_InitFlags; enum DN_InitFlags_ { DN_InitFlags_Nil = 0, + + // NOTE: Query the OS for information and enable functionality that requires the OS (like virtual + // memory APIs, mutexes, secure RNG) as well as per-thread persistent and scratch allocators. DN_InitFlags_OS = (1 << 0), - DN_InitFlags_ThreadContext = (1 << 1) | DN_InitFlags_OS, - DN_InitFlags_LeakTracker = (1 << 2) | DN_InitFlags_OS, - DN_InitFlags_LogLibFeatures = (1 << 3), - DN_InitFlags_LogCPUFeatures = (1 << 4) | DN_InitFlags_OS, + DN_InitFlags_LeakTracker = (1 << 1) | DN_InitFlags_OS, + DN_InitFlags_LogLibFeatures = (1 << 2), + DN_InitFlags_LogCPUFeatures = (1 << 3) | DN_InitFlags_OS, DN_InitFlags_LogAllFeatures = DN_InitFlags_LogLibFeatures | DN_InitFlags_LogCPUFeatures, }; +#if !defined(DN_STB_SPRINTF_HEADER_ONLY) + #define STB_SPRINTF_IMPLEMENTATION + #define STB_SPRINTF_STATIC +#endif + +DN_MSVC_WARNING_PUSH +DN_MSVC_WARNING_DISABLE(4505) // Unused function warning +DN_GCC_WARNING_PUSH +DN_GCC_WARNING_DISABLE(-Wunused-function) +// NOTE: This depends on DN_Str8 because we've customised the library +#include "External/stb_sprintf.h" +DN_GCC_WARNING_POP +DN_MSVC_WARNING_POP + +#if DN_WITH_OS +#include // operator new + +#if !defined(DN_OS_WIN32) || defined(DN_OS_WIN32_USE_PTHREADS) + #include + #include +#endif + +#if defined(DN_PLATFORM_POSIX) || defined(DN_PLATFORM_EMSCRIPTEN) + #include // errno + #include // O_RDONLY ... etc + #include // ioctl + #include // mmap + #include // getrandom + #include // stat + #include // pid_t + #include // waitpid + #include // clock_gettime, nanosleep + #include // access, gettid, write + + #if !defined(DN_PLATFORM_EMSCRIPTEN) + #include // FICLONE + #include // sendfile + #endif +#endif + +extern DN_CPUFeatureDecl g_dn_cpu_feature_decl[DN_CPUFeature_Count]; +struct DN_OSTimer /// Record time between two time-points using the OS's performance counter. +{ + DN_U64 start; + DN_U64 end; +}; + +// NOTE: DN_OSFile +enum DN_OSPathInfoType +{ + DN_OSPathInfoType_Unknown, + DN_OSPathInfoType_Directory, + DN_OSPathInfoType_File, +}; + +struct DN_OSPathInfo +{ + bool exists; + DN_OSPathInfoType type; + DN_U64 create_time_in_s; + DN_U64 last_write_time_in_s; + DN_U64 last_access_time_in_s; + DN_U64 size; +}; + +struct DN_OSDirIterator +{ + void *handle; + DN_Str8 file_name; + char buffer[512]; +}; + +// NOTE: R/W Stream API +struct DN_OSFileRead +{ + bool success; + DN_USize bytes_read; +}; + +struct DN_OSFile +{ + bool error; + void *handle; +}; + +enum DN_OSFileOpen +{ + DN_OSFileOpen_CreateAlways, // Create file if it does not exist, otherwise, zero out the file and open + DN_OSFileOpen_OpenIfExist, // Open file at path only if it exists + DN_OSFileOpen_OpenAlways, // Open file at path, create file if it does not exist +}; + +typedef DN_U32 DN_OSFileAccess; + +enum DN_OSFileAccess_ +{ + DN_OSFileAccess_Read = 1 << 0, + DN_OSFileAccess_Write = 1 << 1, + DN_OSFileAccess_Execute = 1 << 2, + DN_OSFileAccess_AppendOnly = 1 << 3, // This flag cannot be combined with any other access mode + DN_OSFileAccess_ReadWrite = DN_OSFileAccess_Read | DN_OSFileAccess_Write, + DN_OSFileAccess_All = DN_OSFileAccess_ReadWrite | DN_OSFileAccess_Execute | DN_OSFileAccess_AppendOnly, +}; + +// NOTE: DN_OSPath +#if !defined(DN_OSPathSeperator) + #if defined(DN_OS_WIN32) + #define DN_OSPathSeperator "\\" + #else + #define DN_OSPathSeperator "/" + #endif + #define DN_OSPathSeperatorString DN_Str8Lit(DN_OSPathSeperator) +#endif + +struct DN_OSPathLink +{ + DN_Str8 string; + DN_OSPathLink *next; + DN_OSPathLink *prev; +}; + +struct DN_OSPath +{ + bool has_prefix_path_separator; + DN_OSPathLink *head; + DN_OSPathLink *tail; + DN_USize string_size; + DN_U16 links_size; +}; + +// NOTE: DN_OSExec +typedef DN_U32 DN_OSExecFlags; + +enum DN_OSExecFlags_ +{ + DN_OSExecFlags_Nil = 0, + DN_OSExecFlags_SaveStdout = 1 << 0, + DN_OSExecFlags_SaveStderr = 1 << 1, + DN_OSExecFlags_SaveOutput = DN_OSExecFlags_SaveStdout | DN_OSExecFlags_SaveStderr, + DN_OSExecFlags_MergeStderrToStdout = 1 << 2 | DN_OSExecFlags_SaveOutput, +}; + +struct DN_OSExecAsyncHandle +{ + DN_OSExecFlags exec_flags; + DN_U32 os_error_code; + DN_U32 exit_code; + void *process; + void *stdout_read; + void *stdout_write; + void *stderr_read; + void *stderr_write; +}; + +struct DN_OSExecResult +{ + bool finished; + DN_Str8 stdout_text; + DN_Str8 stderr_text; + DN_U32 os_error_code; + DN_U32 exit_code; +}; + +struct DN_OSExecArgs +{ + DN_OSExecFlags flags; + DN_Str8 working_dir; + DN_Str8Slice environment; +}; + +// NOTE: DN_OSSemaphore +DN_U32 const DN_OS_SEMAPHORE_INFINITE_TIMEOUT = UINT32_MAX; + +struct DN_OSSemaphore +{ + DN_U64 handle; +}; + +struct DN_OSBarrier +{ + DN_U64 handle; +}; + +enum DN_OSSemaphoreWaitResult +{ + DN_OSSemaphoreWaitResult_Failed, + DN_OSSemaphoreWaitResult_Success, + DN_OSSemaphoreWaitResult_Timeout, +}; + +struct DN_OSMutex +{ + DN_U64 handle; +}; + +struct DN_OSConditionVariable +{ + DN_U64 handle; +}; + +// NOTE: DN_OSThread +typedef DN_I32(DN_OSThreadFunc)(struct DN_OSThread *); +struct DN_OSThreadLane +{ + DN_USize index; + DN_USize count; + DN_OSBarrier barrier; + void* shared_mem; +}; + +struct DN_OSThreadLaneway +{ + DN_OSThread* threads; + DN_USize threads_count; + DN_UPtr* shared_mem; + DN_OSBarrier barrier; +}; + +struct DN_OSThread +{ + DN_Str8x64 name; + DN_TCCore context; + DN_OSThreadLane lane; + bool is_lane_set; + void *handle; + DN_U64 thread_id; + void *user_context; + DN_OSThreadFunc *func; + DN_OSSemaphore init_semaphore; + DN_TCInitArgs tc_init_args; +}; + +struct DN_OSCore +{ + DN_CPUReport cpu_report; + + // NOTE: Logging + bool log_to_file; // Output logs to file as well as standard out + DN_OSFile log_file; // TODO(dn): Hmmm, how should we do this... ? + DN_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 + DN_TicketMutex log_mutex; + + // NOTE: OS + DN_U32 logical_processor_count; + DN_U32 page_size; + DN_U32 alloc_granularity; + + // NOTE: Memory + // Total OS mem allocs in lifetime of program (e.g. malloc, VirtualAlloc, HeapAlloc ...). This + // only includes allocations routed through the library such as the growing nature of arenas or + // using the memory allocation routines in the library like DN_OS_MemCommit and so forth. + DN_U64 vmem_allocs_total; + DN_U64 vmem_allocs_frame; // Total OS virtual memory allocs since the last 'DN_Core_FrameBegin' was invoked + DN_U64 mem_allocs_total; + DN_U64 mem_allocs_frame; // Total OS heap allocs since the last 'DN_Core_FrameBegin' was invoked + + DN_MemList mem; + DN_Arena arena; + void* platform_context; +}; + +struct DN_OSDiskSpace +{ + bool success; + DN_U64 avail; + DN_U64 size; +}; + +enum DN_OSAsyncPriority +{ + DN_OSAsyncPriority_Low, + DN_OSAsyncPriority_High, + DN_OSAsyncPriority_Count, +}; + +struct DN_OSAsyncCore +{ + DN_OSMutex ring_mutex; + DN_OSConditionVariable ring_write_cv; + DN_OSSemaphore worker_sem; + DN_Ring ring; + DN_OSThread *threads; + DN_U32 thread_count; + DN_U32 busy_threads; + DN_U32 join_threads; +}; + +struct DN_OSAsyncWorkArgs +{ + DN_OSThread *thread; + void *input; +}; + +typedef void(DN_OSAsyncWorkFunc)(DN_OSAsyncWorkArgs work_args); + +struct DN_OSAsyncWork +{ + DN_OSAsyncWorkFunc *func; + void *input; + void *output; +}; + +struct DN_OSAsyncTask +{ + bool queued; + DN_OSAsyncWork work; + DN_OSSemaphore completion_sem; +}; +#endif // #if DN_WITH_OS + struct DN_Core { DN_InitFlags init_flags; @@ -233,17 +2268,2279 @@ struct DN_Core void* print_func_context; bool os_init; - #if defined(DN_OS_H) + #if DN_WITH_OS DN_OSCore os; #endif }; -DN_API void DN_Init (DN_Core *dn, DN_InitFlags flags, DN_TCInitArgs args); -DN_API void DN_Set (DN_Core *dn); -DN_API DN_Core *DN_Get (); -DN_API void DN_BeginFrame(); +// NOTE: Library initialisation. This must be called before using the library once to setup TLS and +// query the OS for information (such as page size) for tuning allocations and so forth. The caller +// should pass in a zero-initialised `DN_Core` that should persist for program lifetime. +// +// A reference to the core passed in is kept which can be queried with `DN_Get` and may be used +// internally by the library. If you have an application that has the concept of frames, you may +// optionally call `DN_BeginFrame` which resets some metrics that are counted for example it tracks +// the number of memory allocations for the current frame and that counter can be reset. +DN_API void DN_Init (DN_Core *dn, DN_InitFlags flags, DN_TCInitArgs args); +DN_API void DN_Set (DN_Core *dn); +DN_API DN_Core* DN_Get (); +DN_API void DN_BeginFrame (); -#if DN_H_WITH_NET -#include "Extra/dn_net.h" +DN_API bool DN_VerifyArgsF (DN_VerifyType type, bool expr, DN_CallSite call_site, DN_Str8 expr_str8, char const *fmt, ...); +DN_API bool DN_VerifyArgs (DN_VerifyType type, bool expr, DN_CallSite call_site, DN_Str8 expr_str8); + +#define DN_SPrintF(...) STB_SPRINTF_DECORATE(sprintf)(__VA_ARGS__) +#define DN_SNPrintF(...) STB_SPRINTF_DECORATE(snprintf)(__VA_ARGS__) +#define DN_VSPrintF(...) STB_SPRINTF_DECORATE(vsprintf)(__VA_ARGS__) +#define DN_VSNPrintF(...) STB_SPRINTF_DECORATE(vsnprintf)(__VA_ARGS__) + +DN_API bool DN_MemStartsWith (void const *lhs, DN_USize lhs_size, void const *rhs, DN_USize rhs_size); +DN_API bool DN_MemEq (void const *lhs, DN_USize lhs_size, void const *rhs, DN_USize rhs_size); +DN_API bool DN_MemEqUnsafe (void const *lhs, void const *rhs, DN_USize size); +#if defined(__cplusplus) +template T* DN_MemCopyObjT (T *dest, T const *src, DN_USize count); +#define DN_MemCopyObj(dest, src, count) DN_MemCopyObjT(dest, src, count) +#else +#define DN_MemCopyObj(dest, src, count) DN_Memcpy(dest, src, sizeof(*src) * count) #endif -#endif // !defined(DN_H) + +DN_API DN_U64 DN_AtomicSetValue64 (DN_U64 volatile *target, DN_U64 value); +DN_API DN_U32 DN_AtomicSetValue32 (DN_U32 volatile *target, DN_U32 value); +DN_API DN_USize DN_AlignUpPowerOfTwoUSize (DN_USize val); +DN_API DN_U64 DN_AlignUpPowerOfTwoU64 (DN_U64 val); +DN_API DN_U32 DN_AlignUpPowerOfTwoU32 (DN_U32 val); + +DN_API void DN_ByteSwapU64Ptr (DN_U8* dest, DN_U64 src); +#define DN_ByteSwap64(val) ( \ + ((((DN_U64)(val) >> 56) & 0xFF) << 0) | \ + ((((DN_U64)(val) >> 48) & 0xFF) << 8) | \ + ((((DN_U64)(val) >> 40) & 0xFF) << 16) | \ + ((((DN_U64)(val) >> 32) & 0xFF) << 24) | \ + ((((DN_U64)(val) >> 24) & 0xFF) << 32) | \ + ((((DN_U64)(val) >> 16) & 0xFF) << 40) | \ + ((((DN_U64)(val) >> 8) & 0xFF) << 48) | \ + ((((DN_U64)(val) >> 0) & 0xFF) << 56) \ + ) +#define DN_ByteSwap32(val) ( \ + ((((DN_U32)(val) >> 24) & 0xFF) << 0) | \ + ((((DN_U32)(val) >> 16) & 0xFF) << 8) | \ + ((((DN_U32)(val) >> 8) & 0xFF) << 16) | \ + ((((DN_U32)(val) >> 0) & 0xFF) << 24) \ + ) +#define DN_ByteSwap24(val) ( \ + ((((DN_U32)(val) >> 16) & 0xFF) << 0) | \ + ((((DN_U32)(val) >> 8) & 0xFF) << 8) | \ + ((((DN_U32)(val) >> 0) & 0xFF) << 16) \ + ) +#define DN_ByteSwap16(val) ( \ + ((((DN_U16)(val) >> 8) & 0xFF) << 0) | \ + ((((DN_U16)(val) >> 0) & 0xFF) << 8) \ + ) +#if defined(DN_64_BIT) + #define DN_ByteSwapUSize(val) DN_ByteSwap64(val) +#else + #define DN_ByteSwapUSize(val) DN_ByteSwap32(val) +#endif + +DN_API DN_CPUIDResult DN_CPUID (DN_CPUIDArgs args); +DN_API DN_USize DN_CPUHasFeatureArray (DN_CPUReport const *report, DN_CPUFeatureQuery *features, DN_USize features_size); +DN_API bool DN_CPUHasFeature (DN_CPUReport const *report, DN_CPUFeature feature); +DN_API bool DN_CPUHasAllFeatures (DN_CPUReport const *report, DN_CPUFeature const *features, DN_USize features_size); +DN_API void DN_CPUSetFeature (DN_CPUReport *report, DN_CPUFeature feature); +DN_API DN_CPUReport DN_CPUGetReport (); + +DN_API void DN_TicketMutex_Begin (DN_TicketMutex *mutex); +DN_API void DN_TicketMutex_End (DN_TicketMutex *mutex); +DN_API DN_UInt DN_TicketMutex_MakeTicket (DN_TicketMutex *mutex); +DN_API void DN_TicketMutex_BeginTicket (DN_TicketMutex const *mutex, DN_UInt ticket); +DN_API bool DN_TicketMutex_CanLock (DN_TicketMutex const *mutex, DN_UInt ticket); + +DN_API void DN_BitUnsetInplace (DN_USize *flags, DN_USize bitfield); +DN_API void DN_BitSetInplace (DN_USize *flags, DN_USize bitfield); +DN_API bool DN_BitIsSet (DN_USize bits, DN_USize bits_to_set); +DN_API bool DN_BitIsNotSet (DN_USize bits, DN_USize bits_to_check); +DN_API bool DN_BitIsAny (DN_USize bits, DN_USize bits_to_check); +#define DN_BitClearNextLSB(value) (value) & ((value) - 1) + +DN_API DN_I64 DN_SafeAddI64 (DN_I64 a, DN_I64 b); +DN_API DN_I64 DN_SafeMulI64 (DN_I64 a, DN_I64 b); + +DN_API DN_U64 DN_SafeAddU64 (DN_U64 a, DN_U64 b); +DN_API DN_U64 DN_SafeMulU64 (DN_U64 a, DN_U64 b); + +DN_API DN_U64 DN_SafeSubU64 (DN_U64 a, DN_U64 b); +DN_API DN_U32 DN_SafeSubU32 (DN_U32 a, DN_U32 b); + +DN_API int DN_SaturateCastUSizeToInt (DN_USize val); +DN_API DN_I8 DN_SaturateCastUSizeToI8 (DN_USize val); +DN_API DN_I16 DN_SaturateCastUSizeToI16 (DN_USize val); +DN_API DN_I32 DN_SaturateCastUSizeToI32 (DN_USize val); +DN_API DN_I64 DN_SaturateCastUSizeToI64 (DN_USize val); + +DN_API int DN_SaturateCastU64ToInt (DN_U64 val); +DN_API DN_I8 DN_SaturateCastU8ToI8 (DN_U64 val); +DN_API DN_I16 DN_SaturateCastU16ToI16 (DN_U64 val); +DN_API DN_I32 DN_SaturateCastU32ToI32 (DN_U64 val); +DN_API DN_I64 DN_SaturateCastU64ToI64 (DN_U64 val); +DN_API DN_UInt DN_SaturateCastU64ToUInt (DN_U64 val); +DN_API DN_U8 DN_SaturateCastU64ToU8 (DN_U64 val); +DN_API DN_U16 DN_SaturateCastU64ToU16 (DN_U64 val); +DN_API DN_U32 DN_SaturateCastU64ToU32 (DN_U64 val); + +DN_API DN_U8 DN_SaturateCastUSizeToU8 (DN_USize val); +DN_API DN_U16 DN_SaturateCastUSizeToU16 (DN_USize val); +DN_API DN_U32 DN_SaturateCastUSizeToU32 (DN_USize val); +DN_API DN_U64 DN_SaturateCastUSizeToU64 (DN_USize val); + +DN_API int DN_SaturateCastISizeToInt (DN_ISize val); +DN_API DN_I8 DN_SaturateCastISizeToI8 (DN_ISize val); +DN_API DN_I16 DN_SaturateCastISizeToI16 (DN_ISize val); +DN_API DN_I32 DN_SaturateCastISizeToI32 (DN_ISize val); +DN_API DN_I64 DN_SaturateCastISizeToI64 (DN_ISize val); + +DN_API DN_UInt DN_SaturateCastISizeToUInt (DN_ISize val); +DN_API DN_U8 DN_SaturateCastISizeToU8 (DN_ISize val); +DN_API DN_U16 DN_SaturateCastISizeToU16 (DN_ISize val); +DN_API DN_U32 DN_SaturateCastISizeToU32 (DN_ISize val); +DN_API DN_U64 DN_SaturateCastISizeToU64 (DN_ISize val); + +DN_API DN_ISize DN_SaturateCastI64ToISize (DN_I64 val); +DN_API DN_I8 DN_SaturateCastI64ToI8 (DN_I64 val); +DN_API DN_I16 DN_SaturateCastI64ToI16 (DN_I64 val); +DN_API DN_I32 DN_SaturateCastI64ToI32 (DN_I64 val); + +DN_API DN_UInt DN_SaturateCastI64ToUInt (DN_I64 val); +DN_API DN_USize DN_SaturateCastI64ToUSize (DN_I64 val); +DN_API DN_U8 DN_SaturateCastI64ToU8 (DN_I64 val); +DN_API DN_U16 DN_SaturateCastI64ToU16 (DN_I64 val); +DN_API DN_U32 DN_SaturateCastI64ToU32 (DN_I64 val); +DN_API DN_U64 DN_SaturateCastI64ToU64 (DN_I64 val); + +DN_API DN_I8 DN_SaturateCastIntToI8 (int val); +DN_API DN_I16 DN_SaturateCastIntToI16 (int val); +DN_API DN_U8 DN_SaturateCastIntToU8 (int val); +DN_API DN_U16 DN_SaturateCastIntToU16 (int val); +DN_API DN_U32 DN_SaturateCastIntToU32 (int val); +DN_API DN_U64 DN_SaturateCastIntToU64 (int val); + +DN_API void DN_ASanPoisonMemoryRegion (void const volatile *ptr, DN_USize size); +DN_API void DN_ASanUnpoisonMemoryRegion (void const volatile *ptr, DN_USize size); + +DN_API DN_F32 DN_EpsilonClampF32 (DN_F32 value, DN_F32 target, DN_F32 epsilon); + +DN_API DN_MemStats DN_MemStatsSum (DN_MemStats lhs, DN_MemStats rhs); +DN_API DN_MemStats DN_MemStatsSumArray (DN_MemStats const *array, DN_USize size); + +// NOTE: `MemList` is an implementation of a classical `Arena` (e.g. bump allocator, can dynamically +// grow, frees by bumping pointer back, sub-divides a block of memory). The term `Arena` is reserved +// as a thin-layer over the functionality here to provide some use-after-free protection. See +// `Arena` for more info. +DN_API DN_MemList DN_MemListFromBuffer (void *buffer, DN_USize size, DN_MemFlags flags); +DN_API DN_MemList DN_MemListFromMemFuncs (DN_U64 reserve, DN_U64 commit, DN_MemFlags flags, DN_MemFuncs mem_funcs); +DN_API void DN_MemListDeinit (DN_MemList *mem); +DN_API bool DN_MemListCommit (DN_MemList *mem, DN_U64 size); +DN_API bool DN_MemListCommitTo (DN_MemList *mem, DN_U64 pos); +DN_API bool DN_MemListGrow (DN_MemList *mem, DN_U64 reserve, DN_U64 commit); +DN_API void * DN_MemListAlloc (DN_MemList *mem, DN_U64 size, uint8_t align, DN_ZMem zmem); +DN_API void * DN_MemListAllocContiguous (DN_MemList *mem, DN_U64 size, uint8_t align, DN_ZMem zmem); +DN_API void * DN_MemListCopy (DN_MemList *mem, void const *data, DN_U64 size, uint8_t align); +DN_API void DN_MemListPopTo (DN_MemList *mem, DN_U64 init_used); +DN_API void DN_MemListPop (DN_MemList *mem, DN_U64 amount); +DN_API DN_U64 DN_MemListPos (DN_MemList const *mem); +DN_API void DN_MemListClear (DN_MemList *mem); +DN_API bool DN_MemListOwnsPtr (DN_MemList const *mem, void *ptr); +DN_API DN_Str8x64 DN_MemListInfoStr8x64 (DN_MemListInfo info); +DN_API DN_MemListTemp DN_MemListTempBegin (DN_MemList *mem); +DN_API void DN_MemListTempEnd (DN_MemListTemp mem); +#define DN_MemListNew(arena, T, zmem) (T *)DN_MemListAlloc(arena, sizeof(T), alignof(T), zmem) +#define DN_MemListNewZ(arena, T) (T *)DN_MemListAlloc(arena, sizeof(T), alignof(T), DN_ZMem_Yes) +#define DN_MemListNewContiguous(arena, T, zmem) (T *)DN_MemListAllocContiguous(arena, sizeof(T), alignof(T), zmem) +#define DN_MemListNewContiguousZ(arena, T) (T *)DN_MemListAllocContiguous(arena, sizeof(T), alignof(T), DN_ZMem_Yes) +#define DN_MemListNewArray(arena, T, count, zmem) (T *)DN_MemListAlloc(arena, sizeof(T) * (count), alignof(T), zmem) +#define DN_MemListNewArrayZ(arena, T, count) (T *)DN_MemListAlloc(arena, sizeof(T) * (count), alignof(T), DN_ZMem_Yes) +#define DN_MemListNewArrayNoZ(arena, T, count) (T *)DN_MemListAlloc(arena, sizeof(T) * (count), alignof(T), DN_ZMem_No) +#define DN_MemListNewCopy(arena, T, src) (T *)DN_MemListCopy(arena, (src), sizeof(T), alignof(T)) +#define DN_MemListNewArrayCopy(arena, T, src, count) (T *)DN_MemListCopy(arena, (src), sizeof(T) * (count), alignof(T)) + +// NOTE: Arena +// Overview +// `Arena`'s in this codebase are thin-layers over `MemList` but additionally provide +// use-after-free (UAF) protection when using temporary memory regions (e.g. thread context +// scratch `TCScratch` or `Temp[Begin|End]` family of functions). +// +// These arenas associate themselves with the temporary memory region they begin in if it is +// constructed using the `Temp[Begin|End]` family of functions (TCScratch implicitly call these +// for you before handing you the arena). If you attempt to allocate from a different arena bound +// with a different temporary memory region than the active one an assertion is triggered. This +// protection is gated by the presence of the preprocessor definition +// `#define DN_ARENA_TEMP_MEM_UAF_GUARD 1`. +// +// Without the preprocessor definition UAF protection is compiled out (e.g. no-op). UAF protection +// is also not enabled if you use `ArenaFromMemList` which simply sets up a plain arena that +// forwards all calls into the `MemList` API. +// +// To get UAF protection, all allocations _must_ go through the `Arena` API, using the `MemList` +// field directly in the `Arena` will bypass these checks and lead to unusual behaviour. If you +// want to forgo any of this infrastructure store and use the `MemList` directly in your codebase. +// +// UAF Example +/* + DN_Arena arena = DN_ArenaFromHeap(DN_Megabytes(1), DN_MemFlags_Nil); + DN_Arena temp = DN_ArenaTempBeginFromArena(&arena); + { + // NOTE: You can also `TempBegin` with `&temp`, either is valid. They both have pointers to + // the same underlying memory block owned by `arena`. + DN_Arena nested_temp = DN_ArenaTempBeginFromArena(&arena); + + // NOTE: This allocation triggers the UAF guard and asserts! An allocation into `temp`'s memory + // region would be reset when we end `nested_temp`'s memory region since they are spawned from + // the same underlying memory block sitting in `arena`. + // + // But the intent here is that the caller is resetting `nested_temp`'s allocations and not + // `temp` hence the UAF protection triggers. + DN_U64 *u64 = DN_ArenaNewZ(&temp, DN_U64); + + DN_ArenaTempEnd(&nested_temp); + } + DN_ArenaTempEnd(&temp); + DN_ArenaDeinit(&arena); // Frees the memory + */ +DN_API DN_Arena DN_ArenaFromMemList (DN_MemList *mem); +DN_API DN_Arena DN_ArenaTempBeginFromMemList (DN_MemList *mem); +DN_API DN_Arena DN_ArenaTempBeginFromArena (DN_Arena *arena); +DN_API void DN_ArenaTempEnd (DN_Arena *arena, DN_ArenaReset reset); +DN_API void* DN_ArenaAlloc (DN_Arena *arena, DN_U64 size, uint8_t align, DN_ZMem z_mem); +DN_API void* DN_ArenaAllocContiguous (DN_Arena *arena, DN_U64 size, uint8_t align, DN_ZMem z_arena); +DN_API void* DN_ArenaCopy (DN_Arena *arena, void const *data, DN_U64 size, uint8_t align); +DN_API void DN_ArenaDeinit (DN_Arena *arena); + +#define DN_ArenaNew(arena, T, zmem) (T *)DN_ArenaAlloc(arena, sizeof(T), alignof(T), zmem) +#define DN_ArenaNewZ(arena, T) (T *)DN_ArenaAlloc(arena, sizeof(T), alignof(T), DN_ZMem_Yes) +#define DN_ArenaNewContiguous(arena, T, zmem) (T *)DN_ArenaAllocContiguous(arena, sizeof(T), alignof(T), zmem) +#define DN_ArenaNewContiguousZ(arena, T) (T *)DN_ArenaAllocContiguous(arena, sizeof(T), alignof(T), DN_ZMem_Yes) +#define DN_ArenaNewArray(arena, T, count, zmem) (T *)DN_ArenaAlloc(arena, sizeof(T) * (count), alignof(T), zmem) +#define DN_ArenaNewArrayZ(arena, T, count) (T *)DN_ArenaAlloc(arena, sizeof(T) * (count), alignof(T), DN_ZMem_Yes) +#define DN_ArenaNewArrayNoZ(arena, T, count) (T *)DN_ArenaAlloc(arena, sizeof(T) * (count), alignof(T), DN_ZMem_No) +#define DN_ArenaNewCopy(arena, T, src) (T *)DN_ArenaCopy(arena, (src), sizeof(T), alignof(T)) +#define DN_ArenaNewArrayCopy(arena, T, src, count) (T *)DN_ArenaCopy(arena, (src), sizeof(T) * (count), alignof(T)) + +DN_API DN_Pool DN_PoolFromArena (DN_Arena *arena, DN_U8 align); +DN_API bool DN_PoolIsValid (DN_Pool const *pool); +DN_API void * DN_PoolAlloc (DN_Pool *pool, DN_USize size); +DN_API void DN_PoolDealloc (DN_Pool *pool, void *ptr); +DN_API void * DN_PoolCopy (DN_Pool *pool, void const *data, DN_U64 size, uint8_t align); +#define DN_PoolNew(pool, T) (T *)DN_PoolAlloc(pool, sizeof(T)) +#define DN_PoolNewArray(pool, T, count) (T *)DN_PoolAlloc(pool, count * sizeof(T)) +#define DN_PoolNewCopy(pool, T, src) (T *)DN_PoolCopy (pool, (src), sizeof(T), alignof(T)) +#define DN_PoolNewArrayCopy(pool, T, src, count) (T *)DN_PoolCopy (pool, (src), sizeof(T) * (count), alignof(T)) + +DN_API DN_ErrSink* DN_ErrSinkBegin_ (DN_ErrSink *err, DN_ErrSinkMode mode, DN_CallSite call_site); +#define DN_ErrSinkBegin(err, mode) DN_ErrSinkBegin_(err, mode, DN_CallSiteNow) +#define DN_ErrSinkBeginDefault(err) DN_ErrSinkBegin(err, DN_ErrSinkMode_Nil) +DN_API bool DN_ErrSinkHasError (DN_ErrSink *err); +DN_API DN_ErrSinkMsg* DN_ErrSinkEnd (DN_Arena *arena, DN_ErrSink *err); +DN_API DN_Str8 DN_ErrSinkEndStr8 (DN_Arena *arena, DN_ErrSink *err); +DN_API void DN_ErrSinkEndIgnore (DN_ErrSink *err); +DN_API bool DN_ErrSinkEndLogError_ (DN_ErrSink *err, DN_CallSite call_site, DN_Str8 msg); +#define DN_ErrSinkEndLogError(err, err_msg) DN_ErrSinkEndLogError_(err, DN_CallSiteNow, err_msg) +DN_API bool DN_ErrSinkEndLogErrorFV_ (DN_ErrSink *err, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, va_list args); +#define DN_ErrSinkEndLogErrorFV(err, fmt, args) DN_ErrSinkEndLogErrorFV_(err, DN_CallSiteNow, fmt, args) +DN_API bool DN_ErrSinkEndLogErrorF_ (DN_ErrSink *err, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, ...); +#define DN_ErrSinkEndLogErrorF(err, fmt, ...) DN_ErrSinkEndLogErrorF_(err, DN_CallSiteNow, fmt, ##__VA_ARGS__) +DN_API void DN_ErrSinkEndExitIfErrorF_ (DN_ErrSink *err, DN_CallSite call_site, DN_U32 exit_val, DN_FMT_ATTRIB char const *fmt, ...); +#define DN_ErrSinkEndExitIfErrorF(err, exit_val, fmt, ...) DN_ErrSinkEndExitIfErrorF_(err, DN_CallSiteNow, exit_val, fmt, ##__VA_ARGS__) +DN_API void DN_ErrSinkEndExitIfErrorFV_ (DN_ErrSink *err, DN_CallSite call_site, DN_U32 exit_val, DN_FMT_ATTRIB char const *fmt, va_list args); +#define DN_ErrSinkEndExitIfErrorFV(err, exit_val, fmt, args) DN_ErrSinkEndExitIfErrorFV_(err, DN_CallSiteNow, exit_val, fmt, args) +DN_API void DN_ErrSinkAppendFV_ (DN_ErrSink *err, DN_U32 error_code, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, va_list args); +#define DN_ErrSinkAppendFV(error, error_code, fmt, args) DN_ErrSinkAppendFV_(error, error_code, DN_CallSiteNow, fmt, args) +DN_API void DN_ErrSinkAppendF_ (DN_ErrSink *err, DN_U32 error_code, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, ...); +#define DN_ErrSinkAppendF(error, error_code, fmt, ...) DN_ErrSinkAppendF_(error, error_code, DN_CallSiteNow, fmt, ##__VA_ARGS__) + +DN_API DN_TCInitArgs DN_TCInitArgsDefault (); +DN_API void DN_TCInit (DN_TCCore *tc, DN_U64 thread_id, DN_Arena *main_arena, DN_Arena *temp_arenas, DN_USize temp_arenas_count, DN_Arena *err_sink_arena); +DN_API void DN_TCInitFromMemFuncs (DN_TCCore *tc, DN_U64 thread_id, DN_TCInitArgs args, DN_MemFuncs mem_funcs); +DN_API void DN_TCDeinit (DN_TCCore *tc, DN_TCDeinitArenas deinit_arenas); +DN_API void DN_TCEquip (DN_TCCore *tc); +DN_API DN_TCCore* DN_TCGet (); +DN_API DN_Arena* DN_TCMainArena (); +DN_API DN_Pool* DN_TCMainPool (); +DN_API DN_Arena DN_TCTempArenaFromAllocator (DN_Allocator *conflicts, DN_USize count); +DN_API DN_Arena DN_TCTempArenaFromArena (DN_Arena **conflicts, DN_USize count); +DN_API DN_TCScratch DN_TCScratchBeginAllocator (DN_Allocator *conflicts, DN_USize count); +DN_API DN_TCScratch DN_TCScratchBeginArena (DN_Arena **conflicts, DN_USize count); +DN_API void DN_TCScratchEnd (DN_TCScratch *scratch); +DN_API void DN_TCSetFrameArena (DN_Arena *arena); +DN_API DN_Arena* DN_TCFrameArena (); +DN_API DN_ErrSink* DN_TCErrSink (); +#define DN_TCErrSinkBegin(mode) DN_ErrSinkBegin(DN_TCErrSink(), mode) +#define DN_TCErrSinkBeginDefault() DN_ErrSinkBeginDefault(DN_TCErrSink()) + +DN_API bool DN_CharIsAlphabet (char ch); +DN_API bool DN_CharIsDigit (char ch); +DN_API bool DN_CharIsAlphaNum (char ch); +DN_API bool DN_CharIsWhitespace (char ch); +DN_API bool DN_CharIsHex (char ch); +DN_API char DN_CharToLower (char ch); +DN_API char DN_CharToUpper (char ch); + +DN_API DN_U64FromResult DN_U64FromStr8 (DN_Str8 string, char separator); +DN_API DN_U64FromResult DN_U64FromPtr (void const *data, DN_USize size, char separator); +DN_API DN_U64 DN_U64FromPtrUnsafe (void const *data, DN_USize size, char separator); +DN_API DN_U64FromResult DN_U64FromHexPtr (void const *hex, DN_USize hex_count); +DN_API DN_U64 DN_U64FromHexPtrUnsafe (void const *hex, DN_USize hex_count); +DN_API DN_U64FromResult DN_U64FromHexStr8 (DN_Str8 hex); +DN_API DN_U64 DN_U64FromHexStr8Unsafe (DN_Str8 hex); +DN_API DN_U64 DN_U64FromU8x32HiBEUnsafe (DN_U8x32 const *val); // Get U64 stored in big-endian at the high bytes [24:32) +DN_API DN_U64FromResult DN_U64FromU8x32HiBE (DN_U8x32 const *val); // Checks [0:24) bytes aren't set before getting the U64 +DN_API DN_USize DN_USizeFromU8x32HiBEUnsafe (DN_U8x32 const *val); // Get USize stored in big-endian at the high bytes [32 - sizeof USize:32) +DN_API DN_USizeFromResult DN_USizeFromU8x32HiBE (DN_U8x32 const *val); // Checks [0:sizeof USize) bytes aren't set before getting the U64 +DN_API DN_I64FromResult DN_I64FromStr8 (DN_Str8 string, char separator); +DN_API DN_I64FromResult DN_I64FromPtr (void const *data, DN_USize size, char separator); +DN_API DN_I64 DN_I64FromPtrUnsafe (void const *data, DN_USize size, char separator); + +DN_API bool DN_U8x32Eq (DN_U8x32 const *lhs, DN_U8x32 const *rhs); +DN_API DN_U8x32 DN_U8x32FromBytesLeftPadZ (DN_U8 const *ptr, DN_USize count); +DN_API DN_U8x32 DN_U8x32FromHexUnsafe (DN_Str8 hex_32b); +DN_API DN_U8x32FromResult DN_U8x32FromHex (DN_Str8 hex_32b); +DN_API DN_U8x32FromResult DN_U8x32FromDecimalStr8 (DN_Str8 decimal); // Write decimal string (e.g. "12345") as big-endian 256-bit value + +DN_API DN_Allocator DN_AllocatorFromMemList (DN_MemList *mem); +DN_API DN_Allocator DN_AllocatorFromArena (DN_Arena *arena); +DN_API DN_Allocator DN_AllocatorFromPool (DN_Pool *pool); +DN_API void* DN_AllocatorAlloc (DN_Allocator allocator, DN_USize size, DN_U8 align, DN_ZMem z_mem); + +DN_API DN_USize DN_FmtVSize (DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API DN_USize DN_FmtSize (DN_FMT_ATTRIB char const *fmt, ...); +DN_API DN_FmtAppendResult DN_FmtVAppend (char *buf, DN_USize *buf_size, DN_USize buf_max, char const *fmt, va_list args); +DN_API DN_FmtAppendResult DN_FmtAppend (char *buf, DN_USize *buf_size, DN_USize buf_max, char const *fmt, ...); +DN_API DN_FmtAppendResult DN_FmtAppendTruncate (char *buf, DN_USize *buf_size, DN_USize buf_max, DN_Str8 truncator, char const *fmt, ...); +DN_API DN_USize DN_CStr8Size (char const *src); +DN_API DN_USize DN_CStr16Size (wchar_t const *src); + +#define DN_Str16Lit(string) DN_Str16{(wchar_t *)(string), sizeof(string)/sizeof(string[0]) - 1} +#define DN_Str16FromPtr(data, size) DN_Literal(DN_Str16){(wchar_t *)(data), (DN_USize)(size)} + +#define DN_Str8Lit(c_str) DN_Literal(DN_Str8){(char *)(c_str), sizeof(c_str) - 1} +#define DN_Str8PrintFmt(string) (int)((string).size), (string).data + +#define DN_Str8FromPtr(data, size) DN_Literal(DN_Str8){(char *)(data), (DN_USize)(size)} +#define DN_Str8FromStruct(ptr) DN_Str8FromPtr((ptr)->data, (ptr)->size) +#define DN_Str8FromLitArray(c_array) DN_Str8FromPtr(c_array, DN_ArrayCountU(c_array)) +DN_API DN_Str8 DN_Str8AllocAllocator (DN_USize size, DN_ZMem z_mem, DN_Allocator allocator); +DN_API DN_Str8 DN_Str8AllocArena (DN_USize size, DN_ZMem z_mem, DN_Arena *arena); +DN_API DN_Str8 DN_Str8AllocPool (DN_USize size, DN_Pool *pool); + +DN_API DN_Str8 DN_Str8FromCStr8 (char const *src); +DN_API DN_Str8 DN_Str8FromCStr8Arena (char const *src, DN_Arena *arena); +DN_API DN_Str8 DN_Str8FromPtrArena (void const *data, DN_USize size, DN_Arena *arena); +DN_API DN_Str8 DN_Str8FromPtrPool (void const *data, DN_USize size, DN_Pool *pool); +DN_API DN_Str8 DN_Str8FromStr8Allocator (DN_Str8 string, DN_Allocator allocator); +DN_API DN_Str8 DN_Str8FromStr8Arena (DN_Str8 string, DN_Arena *arena); +DN_API DN_Str8 DN_Str8FromStr8Pool (DN_Str8 string, DN_Pool *pool); +DN_API DN_Str8 DN_Str8FromFmtVAllocator (DN_Allocator allocator, DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API DN_Str8 DN_Str8FromFmtVArena (DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API DN_Str8 DN_Str8FromFmtAllocator (DN_Allocator allocator, DN_FMT_ATTRIB char const *fmt, ...); +DN_API DN_Str8 DN_Str8FromFmtArena (DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, ...); +DN_API DN_Str8 DN_Str8FromFmtVPool (DN_Pool *pool, DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API DN_Str8 DN_Str8FromFmtPool (DN_Pool *pool, DN_FMT_ATTRIB char const *fmt, ...); + +DN_API DN_Str8x16 DN_Str8x16FromFmt (DN_FMT_ATTRIB char const *fmt, ...); +DN_API DN_Str8x16 DN_Str8x16FromFmtV (DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API DN_Str8x32 DN_Str8x32FromFmt (DN_FMT_ATTRIB char const *fmt, ...); +DN_API DN_Str8x32 DN_Str8x32FromFmtV (DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API DN_Str8x64 DN_Str8x64FromFmt (DN_FMT_ATTRIB char const *fmt, ...); +DN_API DN_Str8x64 DN_Str8x64FromFmtV (DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API DN_Str8x128 DN_Str8x128FromFmt (DN_FMT_ATTRIB char const *fmt, ...); +DN_API DN_Str8x256 DN_Str8x256FromFmtV (DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API DN_Str8x256 DN_Str8x256FromFmt (DN_FMT_ATTRIB char const *fmt, ...); +DN_API DN_Str8x256 DN_Str8x256FromFmtV (DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API DN_Str8x512 DN_Str8x512FromFmt (DN_FMT_ATTRIB char const *fmt, ...); +DN_API DN_Str8x512 DN_Str8x512FromFmtV (DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API DN_Str8x1024 DN_Str8x1024FromFmt (DN_FMT_ATTRIB char const *fmt, ...); +DN_API DN_Str8x1024 DN_Str8x1024FromFmtV (DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API void DN_Str8x16AppendFmt (DN_Str8x16 *str, DN_FMT_ATTRIB char const *fmt, ...); +DN_API void DN_Str8x16AppendFmtV (DN_Str8x16 *str, DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API void DN_Str8x32AppendFmt (DN_Str8x32 *str, DN_FMT_ATTRIB char const *fmt, ...); +DN_API void DN_Str8x32AppendFmtV (DN_Str8x32 *str, DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API void DN_Str8x64AppendFmt (DN_Str8x64 *str, DN_FMT_ATTRIB char const *fmt, ...); +DN_API void DN_Str8x64AppendFmtV (DN_Str8x64 *str, DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API void DN_Str8x128AppendFmt (DN_Str8x128 *str, DN_FMT_ATTRIB char const *fmt, ...); +DN_API void DN_Str8x128AppendFmtV (DN_Str8x128 *str, DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API void DN_Str8x256AppendFmt (DN_Str8x256 *str, DN_FMT_ATTRIB char const *fmt, ...); +DN_API void DN_Str8x256AppendFmtV (DN_Str8x256 *str, DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API void DN_Str8x512AppendFmt (DN_Str8x512 *str, DN_FMT_ATTRIB char const *fmt, ...); +DN_API void DN_Str8x512AppendFmtV (DN_Str8x512 *str, DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API void DN_Str8x1024AppendFmt (DN_Str8x1024 *str, DN_FMT_ATTRIB char const *fmt, ...); +DN_API void DN_Str8x1024AppendFmtV (DN_Str8x1024 *str, DN_FMT_ATTRIB char const *fmt, va_list args); + +DN_API DN_Str8x32 DN_Str8x32FromU64 (DN_U64 val, char separator); +DN_API bool DN_Str8IsAll (DN_Str8 string, DN_Str8IsAllType is_all); +DN_API char * DN_Str8End (DN_Str8 string); +DN_API DN_Str8 DN_Str8Subset (DN_Str8 string, DN_USize offset, DN_USize size); +DN_API DN_Str8 DN_Str8Advance (DN_Str8 string, DN_USize amount); +DN_API DN_Str8 DN_Str8NextLine (DN_Str8 string); +DN_API DN_Str8BSplitResult DN_Str8BSplitArray (DN_Str8 string, DN_Str8 const *find, DN_USize find_size); +DN_API DN_Str8BSplitResult DN_Str8BSplit (DN_Str8 string, DN_Str8 find); +DN_API DN_Str8BSplitResult DN_Str8BSplitLastArray (DN_Str8 string, DN_Str8 const *find, DN_USize find_size); +DN_API DN_Str8BSplitResult DN_Str8BSplitLast (DN_Str8 string, DN_Str8 find); +DN_API DN_USize DN_Str8Split (DN_Str8 string, DN_Str8 delimiter, DN_Str8 *splits, DN_USize splits_count, DN_Str8SplitFlags mode); +DN_API DN_Str8SplitResult DN_Str8SplitArena (DN_Str8 string, DN_Str8 delimiter, DN_Str8SplitFlags mode, DN_Arena *arena); +DN_API DN_Str8FindResult DN_Str8FindStr8Array (DN_Str8 string, DN_Str8 const *find, DN_USize find_size, DN_Str8EqCase eq_case); +DN_API DN_Str8FindResult DN_Str8FindStr8 (DN_Str8 string, DN_Str8 find, DN_Str8EqCase eq_case); +DN_API DN_Str8FindResult DN_Str8Find (DN_Str8 string, DN_Str8FindFlag flags); +DN_API DN_Str8 DN_Str8Segment (DN_Arena *arena, DN_Str8 src, DN_USize segment_size, char segment_char); +DN_API DN_Str8 DN_Str8ReverseSegment (DN_Arena *arena, DN_Str8 src, DN_USize segment_size, char segment_char); +DN_API bool DN_Str8Eq (DN_Str8 lhs, DN_Str8 rhs, DN_Str8EqCase eq_case = DN_Str8EqCase_Sensitive); +DN_API bool DN_Str8EqInsensitive (DN_Str8 lhs, DN_Str8 rhs); +DN_API bool DN_Str8StartsWith (DN_Str8 string, DN_Str8 prefix, DN_Str8EqCase eq_case = DN_Str8EqCase_Sensitive); +DN_API bool DN_Str8StartsWithInsensitive (DN_Str8 string, DN_Str8 prefix); +DN_API bool DN_Str8EndsWith (DN_Str8 string, DN_Str8 prefix, DN_Str8EqCase eq_case = DN_Str8EqCase_Sensitive); +DN_API bool DN_Str8EndsWithInsensitive (DN_Str8 string, DN_Str8 prefix); +DN_API bool DN_Str8HasChar (DN_Str8 string, char ch); + +DN_API DN_Str8 DN_Str8TrimPrefix (DN_Str8 string, DN_Str8 prefix, DN_Str8EqCase eq_case = DN_Str8EqCase_Sensitive); +DN_API DN_Str8 DN_Str8TrimHexPrefix (DN_Str8 string); +DN_API DN_Str8 DN_Str8TrimSuffix (DN_Str8 string, DN_Str8 suffix, DN_Str8EqCase eq_case = DN_Str8EqCase_Sensitive); +DN_API DN_Str8 DN_Str8TrimAround (DN_Str8 string, DN_Str8 trim_string); +DN_API DN_Str8 DN_Str8TrimHeadWhitespace (DN_Str8 string); +DN_API DN_Str8 DN_Str8TrimTailWhitespace (DN_Str8 string); +DN_API DN_Str8 DN_Str8TrimWhitespaceAround (DN_Str8 string); +DN_API DN_Str8 DN_Str8TrimByteOrderMark (DN_Str8 string); + +DN_API DN_Str8 DN_Str8FileNameFromPath (DN_Str8 path); +DN_API DN_Str8 DN_Str8FileNameNoExtension (DN_Str8 path); +DN_API DN_Str8 DN_Str8FilePathNoExtension (DN_Str8 path); +DN_API DN_Str8 DN_Str8FileExtension (DN_Str8 path); +DN_API DN_Str8 DN_Str8FileDirectoryFromPath (DN_Str8 path); +DN_API DN_Str8 DN_Str8AppendF (DN_Arena *arena, DN_Str8 string, char const *fmt, ...); +DN_API DN_Str8 DN_Str8AppendFV (DN_Arena *arena, DN_Str8 string, char const *fmt, va_list args); +DN_API DN_Str8 DN_Str8FillF (DN_Arena *arena, DN_USize count, char const *fmt, ...); +DN_API DN_Str8 DN_Str8FillFV (DN_Arena *arena, DN_USize count, char const *fmt, va_list args); +DN_API void DN_Str8Remove (DN_Str8 *string, DN_USize offset, DN_USize size); +DN_API DN_Str8TruncResult DN_Str8TruncMiddlePtr (DN_Str8 str8, DN_USize side_size, DN_Str8 truncator, char *dest, DN_USize dest_max); +DN_API DN_Str8TruncResult DN_Str8TruncMiddle (DN_Str8 str8, DN_USize side_size, DN_Str8 truncator, DN_Arena *arena); +DN_API DN_Str8 DN_Str8Lower (DN_Str8 string, DN_Arena *arena); +DN_API DN_Str8 DN_Str8Upper (DN_Str8 string, DN_Arena *arena); +DN_API DN_Str8 DN_Str8Replace (DN_Str8 string, DN_Str8 find, DN_Str8 replace, DN_USize start_index, DN_Arena *arena, DN_Str8EqCase eq_case); +DN_API DN_Str8 DN_Str8ReplaceSensitive (DN_Str8 string, DN_Str8 find, DN_Str8 replace, DN_USize start_index, DN_Arena *arena); +DN_API DN_Str8 DN_Str8ReplaceInsensitive (DN_Str8 string, DN_Str8 find, DN_Str8 replace, DN_USize start_index, DN_Arena *arena); +DN_API DN_Str8 DN_Str8PadNewLinesAllocator (DN_Str8 string, DN_Str8 pad_string, DN_Allocator allocator); +DN_API DN_Str8 DN_Str8PadNewLinesArena (DN_Str8 string, DN_Str8 pad_string, DN_Arena *arena); +DN_API DN_Str8 DN_Str8LineBreakAllocator (DN_Str8 src, DN_USize desired_width, DN_Str8 delimiter, DN_Str8LineBreakMode mode, DN_Allocator allocator); +DN_API DN_Str8 DN_Str8LineBreakArena (DN_Str8 src, DN_USize desired_width, DN_Str8 delimiter, DN_Str8LineBreakMode mode, DN_Arena *arena); +DN_API DN_Str8 DN_Str8Table (DN_Str8 const* rows, DN_USize num_rows, DN_USize num_cols, DN_Str8TableFlags flags, DN_Arena *arena); + +#if DN_STR8_AVX512F +DN_API DN_Str8FindResult DN_Str8FindStr8AVX512F (DN_Str8 string, DN_Str8 find); +DN_API DN_Str8FindResult DN_Str8FindLastStr8AVX512F (DN_Str8 string, DN_Str8 find); +DN_API DN_Str8BSplitResult DN_Str8BSplitAVX512F (DN_Str8 string, DN_Str8 find); +DN_API DN_Str8BSplitResult DN_Str8BSplitLastAVX512F (DN_Str8 string, DN_Str8 find); +DN_API DN_USize DN_Str8SplitAVX512F (DN_Str8 string, DN_Str8 delimiter, DN_Str8 *splits, DN_USize splits_count, DN_Str8SplitFlags flags); +DN_API DN_Str8Slice DN_Str8SplitAllocAVX512F (DN_Arena *arena, DN_Str8 string, DN_Str8 delimiter, DN_Str8SplitFlags flags); +#endif + +DN_API DN_Str8 DN_Str8SliceRender (DN_Str8Slice array, DN_Str8 separator, DN_Arena *arena); +DN_API DN_Str8 DN_Str8RenderSpaceSep (DN_Str8Slice array, DN_Arena *arena); +DN_API int DN_Str8CompareNatural (DN_Str8 lhs, DN_Str8 rhs, DN_Str8EqCase eq_case); +DN_API int DN_Str8CompareLexicographic (DN_Str8 lhs, DN_Str8 rhs, DN_Str8EqCase eq_case); + +DN_API bool DN_Str16Eq (DN_Str16 lhs, DN_Str16 rhs); +DN_API DN_Str16 DN_Str16SliceRender (DN_Str16Slice array, DN_Str16 separator, DN_Arena *arena); +DN_API DN_Str16 DN_Str16RenderSpaceSep (DN_Str16Slice array, DN_Arena *arena); + +DN_API DN_Str8Builder DN_Str8BuilderFromArena (DN_Arena *arena); +DN_API DN_Str8Builder DN_Str8BuilderFromStr8PtrRef (DN_Arena *arena, DN_Str8 const *strings, DN_USize size); +DN_API DN_Str8Builder DN_Str8BuilderFromStr8PtrCopy (DN_Arena *arena, DN_Str8 const *strings, DN_USize size); +DN_API DN_Str8Builder DN_Str8BuilderFromBuilder (DN_Arena *arena, DN_Str8Builder const *builder); +DN_API bool DN_Str8BuilderAddArrayRef (DN_Str8Builder *builder, DN_Str8 const *strings, DN_USize size, DN_Str8BuilderAdd add); +DN_API bool DN_Str8BuilderAddArrayCopy (DN_Str8Builder *builder, DN_Str8 const *strings, DN_USize size, DN_Str8BuilderAdd add); +DN_API bool DN_Str8BuilderAddFV (DN_Str8Builder *builder, DN_Str8BuilderAdd add, DN_FMT_ATTRIB char const *fmt, va_list args); +#define DN_Str8BuilderAppendArrayRef(builder, strings, size) DN_Str8BuilderAddArrayRef(builder, strings, size, DN_Str8BuilderAdd_Append) +#define DN_Str8BuilderAppendArrayCopy(builder, strings, size) DN_Str8BuilderAddArrayCopy(builder, strings, size, DN_Str8BuilderAdd_Append) +#define DN_Str8BuilderAppendSliceRef(builder, slice) DN_Str8BuilderAddArrayRef(builder, slice.data, slice.size, DN_Str8BuilderAdd_Append) +#define DN_Str8BuilderAppendSliceCopy(builder, slice) DN_Str8BuilderAddArrayCopy(builder, slice.data, slice.size, DN_Str8BuilderAdd_Append) +DN_API bool DN_Str8BuilderAppendRef (DN_Str8Builder *builder, DN_Str8 string); +DN_API bool DN_Str8BuilderAppendCopy (DN_Str8Builder *builder, DN_Str8 string); +#define DN_Str8BuilderAppendFV(builder, fmt, args) DN_Str8BuilderAddFV(builder, DN_Str8BuilderAdd_Append, fmt, args) +DN_API bool DN_Str8BuilderAppendF (DN_Str8Builder *builder, DN_FMT_ATTRIB char const *fmt, ...); +DN_API bool DN_Str8BuilderAppendBytesRef (DN_Str8Builder *builder, void const *ptr, DN_USize size); +DN_API bool DN_Str8BuilderAppendBytesCopy (DN_Str8Builder *builder, void const *ptr, DN_USize size); +DN_API bool DN_Str8BuilderAppendBuilderRef (DN_Str8Builder *dest, DN_Str8Builder const *src); +DN_API bool DN_Str8BuilderAppendBuilderCopy (DN_Str8Builder *dest, DN_Str8Builder const *src); +#define DN_Str8BuilderPrependArrayRef(builder, strings, size) DN_Str8BuilderAddArrayRef(builder, strings, size, DN_Str8BuilderAdd_Prepend) +#define DN_Str8BuilderPrependArrayCopy(builder, strings, size) DN_Str8BuilderAddArrayCopy(builder, strings, size, DN_Str8BuilderAdd_Prepend) +#define DN_Str8BuilderPrependSliceRef(builder, slice) DN_Str8BuilderAddArrayRef(builder, slice.data, slice.size, DN_Str8BuilderAdd_Prepend) +#define DN_Str8BuilderPrependSliceCopy(builder, slice) DN_Str8BuilderAddArrayCopy(builder, slice.data, slice.size, DN_Str8BuilderAdd_Prepend) +DN_API bool DN_Str8BuilderPrependRef (DN_Str8Builder *builder, DN_Str8 string); +DN_API bool DN_Str8BuilderPrependCopy (DN_Str8Builder *builder, DN_Str8 string); +#define DN_Str8BuilderPrependFV(builder, fmt, args) DN_Str8BuilderAddFV(builder, DN_Str8BuilderAdd_Prepend, fmt, args) +DN_API bool DN_Str8BuilderPrependF (DN_Str8Builder *builder, DN_FMT_ATTRIB char const *fmt, ...); +DN_API bool DN_Str8BuilderErase (DN_Str8Builder *builder, DN_Str8 string); +DN_API DN_Str8 DN_Str8FromStr8BuilderAllocator (DN_Str8Builder const *builder, DN_Allocator allocator); +DN_API DN_Str8 DN_Str8FromStr8BuilderArena (DN_Str8Builder const *builder, DN_Arena *arena); +DN_API DN_Str8 DN_Str8FromStr8BuilderDelimitAllocator (DN_Str8Builder const *builder, DN_Str8 delimiter, DN_Allocator allocator); +DN_API DN_Str8 DN_Str8FromStr8BuilderDelimitArena (DN_Str8Builder const *builder, DN_Str8 delimiter, DN_Arena *arena); + +DN_API int DN_UTF8Encode (DN_U8 utf8[4], DN_U32 codepoint); +DN_API int DN_UTF16Encode (DN_U16 utf16[2], DN_U32 codepoint); +DN_API DN_UTF8DecodeResult DN_UTF8Decode (DN_Str8 stream); +DN_API bool DN_UTF8DecodeIterate (DN_UTF8DecodeIterator *it, DN_Str8 utf8); +DN_API DN_USize DN_USizeCodepointCountFromUTF8 (DN_Str8 str, DN_CodepointCountFlags flags); + +DN_API DN_U8 DN_U8FromHexNibble (char hex); +DN_API DN_NibbleFromU8Result DN_NibbleFromU8 (DN_U8 u8); + +DN_API DN_USize DN_BytesFromHex (DN_Str8 hex, void *dest, DN_USize dest_count); +DN_API DN_Str8 DN_BytesFromHexArena (DN_Str8 hex, DN_Arena *arena); +DN_API DN_USize DN_BytesFromHexPtr (char const *hex, DN_USize hex_count, void *dest, DN_USize dest_count); +DN_API DN_Str8 DN_BytesFromHexPtrArena (char const *hex, DN_USize hex_count, DN_Arena *arena); +DN_API DN_Str8 DN_BytesFromHexPtrPool (char const *hex, DN_USize hex_count, DN_Pool *pool); +DN_API DN_U8x16 DN_BytesFromHex32Ptr (char const *hex, DN_USize hex_count); +DN_API DN_U8x32 DN_BytesFromHex64Ptr (char const *hex, DN_USize hex_count); + +DN_API DN_HexU64 DN_HexFromU64 (DN_U64 value, DN_HexFromU64Type type); +DN_API DN_USize DN_HexFromPtrBytes (void const *bytes, DN_USize bytes_count, void *hex, DN_USize hex_count, DN_TrimLeadingZero trim_leading_z); +DN_API DN_Str8 DN_HexFromPtrBytesArena (void const *bytes, DN_USize bytes_count, DN_Arena *arena, DN_TrimLeadingZero trim_leading_z); +DN_API DN_USize DN_HexFromStr8Bytes (DN_Str8 bytes, void *hex, DN_USize hex_count, DN_TrimLeadingZero trim_leading_z); +DN_API DN_Str8 DN_HexFromStr8BytesArena (DN_Str8 bytes, DN_Arena *arena, DN_TrimLeadingZero trim_leading_z); +DN_API DN_Hex32 DN_Hex32FromPtr16b (void const *bytes, DN_USize bytes_count, DN_TrimLeadingZero trim_leading_z); +DN_API DN_Hex64 DN_Hex64FromPtr32b (void const *bytes, DN_USize bytes_count, DN_TrimLeadingZero trim_leading_z); +DN_API DN_Hex128 DN_Hex128FromPtr64b (void const *bytes, DN_USize bytes_count, DN_TrimLeadingZero trim_leading_z); + +DN_API DN_Str8x128 DN_AgeStr8FromMsU64 (DN_U64 duration_ms, DN_AgeUnit units); +DN_API DN_Str8x128 DN_AgeStr8FromSecU64 (DN_U64 duration_ms, DN_AgeUnit units); +DN_API DN_Str8x128 DN_AgeStr8FromSecF64 (DN_F64 sec, DN_AgeUnit units); + +DN_API int DN_IsLeapYear (int year); +DN_API bool DN_DateIsValid (DN_Date date); +DN_API DN_Date DN_DateFromUnixTimeMs (DN_USize unix_ts_ms); +DN_API DN_U64 DN_UnixTimeMsFromDate (DN_Date date); + +DN_API DN_Str8 DN_Str8FromByteType (DN_ByteType type); +DN_API DN_ByteCount DN_ByteCountFromU64 (DN_U64 byte_count, DN_ByteType type); +DN_API DN_Str8x32 DN_Str8x32FromByteCountU64 (DN_U64 byte_count, DN_ByteType type); +#define DN_Str8x32FromByteCountU64Auto(bytes) DN_Str8x32FromByteCountU64(bytes, DN_ByteType_Auto) + +// NOTE: Profiler +// Overview +// Basic profiler that tracks the duration of marked-up regions which are denoted by an +// opening and closing `anchor`. The profiler works in "frame" life-cycles which can by cycled by +// calling `DN_ProfilerNewFrame`. The number of frames that the profiler will persist is chosen by +// `anchors_per_frame`. +// +// For example if you pass a buffer of 1024 `anchors` and `anchors_per_frame = 128` then the +// profiler will hold onto 8 (1024 anchors / 128 anchors per frame) frames of profiling +// information. The profiler will cycle through the 8 frames of anchors and upon reaching the end +// begin overwriting the oldest frames worth of anchors. +// +// Once a frame has ended the just completed buffer of anchors that was just written to can be +// read by the caller and visualised to find the timings of each region, relative to the duration +// of the frame until it is eventually overwritten by calling `DN_ProfilerNewFrame` once the +// profiler has cycled through all the other frames. +// +// The caller must upfront determine the number of anchors and frames the profiler should have and +// pass it in. The profiler does not allocate any memory. +// +// When profiling functions that are invoked from different call-stacks the exclusive elapsed +// duration can exceed its inclusive duration. This is by design as the profiler makes no +// effort to distinguish between the different call-tree that a zone may have been triggered by. +// The profiler simpler always updates the same static anchor assigned for that zone. +// +// API +// DN_ProfilerInit +// You can set `tsc_now` to NULL to use the default timer mechanic which relies on +// DN_CPUGetTSC() which essentially uses __rdtsc. `tsc_frequency` must however always be +// provided, for, DN_CPUGetTSC() you can use `DN_OS_EstimateTSCPerSecond()` to calculate the +// frequency of `__rdtsc`. +// DN_ProfilerNewFrame +// Always call `DN_ProfilerNewFrame` at-least once using the `Zone` family of functions as it +// sets up +// DN_ProfilerBeginZone +// The zeroth anchor is reserved for profiling the begin and end of a profiler frame. If you are +// manually specifying `anchor_index` (e.g. using an enum) ensure that the first anchor index +// starts from `1`. Note that the `BeginZoneAuto` macro uses `__COUNTER__ + 1` to skip the 0th +// zone. +// DN_ProfilerFrameAnchors +// Returns the current frame's (e.g. the anchors that the profiler is currently writing to) +// worth of anchors +#define DN_ProfilerZoneLoop(prof, name, index) \ + DN_ProfilerZone DN_UniqueName(zone_) = DN_ProfilerBeginZone(prof, DN_Str8Lit(name), index), DN_UniqueName(dummy_) = {}; \ + DN_UniqueName(dummy_).begin_tsc == 0; \ + DN_ProfilerEndZone(DN_UniqueName(zone_)), DN_UniqueName(dummy_).begin_tsc = 1 + +#define DN_ProfilerZoneLoopAuto(prof, name) DN_ProfilerZoneLoop(prof, name, __COUNTER__ + 1) +DN_API DN_Profiler DN_ProfilerInit (DN_ProfilerAnchor *anchors, DN_USize count, DN_USize anchors_per_frame, DN_ProfilerTSCNowFunc *tsc_now, DN_U64 tsc_frequency); +DN_API DN_ProfilerZone DN_ProfilerBeginZone (DN_Profiler *profiler, DN_Str8 name, DN_U16 anchor_index); +#define DN_ProfilerBeginZoneAuto(prof, name) DN_ProfilerBeginZone(prof, DN_Str8Lit(name), __COUNTER__ + 1) +DN_API void DN_ProfilerEndZone (DN_ProfilerZone zone); +DN_API DN_USize DN_ProfilerFrameCount (DN_Profiler const *profiler); +DN_API DN_ProfilerAnchorArray DN_ProfilerFrameAnchorsFromIndex (DN_Profiler *profiler, DN_USize frame_index); +DN_API DN_ProfilerAnchorArray DN_ProfilerFrameAnchors (DN_Profiler *profiler); +DN_API void DN_ProfilerNewFrame (DN_Profiler *profiler); +DN_API DN_USize DN_ProfilerFmtAnchor (DN_ProfilerAnchor anchor, DN_U64 tsc_frequency, char *buffer, DN_USize count); +DN_API DN_Str8 DN_ProfilerFmtAnchorStr8 (DN_ProfilerAnchor anchor, DN_U64 tsc_frequency, DN_Arena *arena); +DN_API void DN_ProfilerFmtToStdout (DN_Profiler *profiler); +DN_API DN_F64 DN_ProfilerSecFromTSC (DN_Profiler *profiler, DN_U64 duration_tsc); +DN_API DN_F64 DN_ProfilerMsFromTSC (DN_Profiler *profiler, DN_U64 duration_tsc); + +DN_API void DN_QSort (void *array, DN_USize array_size, DN_USize elem_size, void *user_context, DN_QSortCompareFunc *compare); +DN_API bool DN_QSortCompareStr8NaturalAsc (void const* lhs, void const *rhs, void *user_context); +DN_API bool DN_QSortCompareStr8NaturalDesc (void const* lhs, void const *rhs, void *user_context); +DN_API bool DN_QSortCompareStr8LexicographicAsc (void const* lhs, void const *rhs, void *user_context); +DN_API bool DN_QSortCompareStr8LexicographicDesc (void const* lhs, void const *rhs, void *user_context); +DN_API bool DN_QSortCompareBytesLT (void const* lhs, void const *rhs, void *user_context); +DN_API bool DN_QSortCompareBytesGT (void const* lhs, void const *rhs, void *user_context); +DN_API void DN_QSortBytesLT (void *array, DN_USize array_size, DN_USize elem_size); +DN_API void DN_QSortBytesGT (void *array, DN_USize array_size, DN_USize elem_size); +DN_API void DN_QSortStr8NaturalAsc (DN_Str8 *array, DN_USize array_size, DN_Str8EqCase eq_case); +DN_API void DN_QSortStr8NaturalDesc (DN_Str8 *array, DN_USize array_size, DN_Str8EqCase eq_case); +DN_API void DN_QSortStr8LexicographicAsc (DN_Str8 *array, DN_USize array_size, DN_Str8EqCase eq_case); +DN_API void DN_QSortStr8LexicographicDesc (DN_Str8 *array, DN_USize array_size, DN_Str8EqCase eq_case); + +DN_API DN_PCG32 DN_PCG32Init (DN_U64 seed); +DN_API DN_U32 DN_PCG32Next (DN_PCG32 *rng); +DN_API DN_U64 DN_PCG32Next64 (DN_PCG32 *rng); +DN_API DN_U32 DN_PCG32Range (DN_PCG32 *rng, DN_U32 low, DN_U32 high); +DN_API DN_F32 DN_PCG32NextF32 (DN_PCG32 *rng); +DN_API DN_F64 DN_PCG32NextF64 (DN_PCG32 *rng); +DN_API void DN_PCG32Advance (DN_PCG32 *rng, DN_U64 delta); + +#if !defined(DN_FNV1A32_SEED) + #define DN_FNV1A32_SEED 2166136261U +#endif + +#if !defined(DN_FNV1A64_SEED) + #define DN_FNV1A64_SEED 14695981039346656037ULL +#endif + +DN_API DN_U32 DN_FNV1AHashU32FromBytes (void const *bytes, DN_USize size, DN_U32 seed); +DN_API DN_U64 DN_FNV1AHashU64FromBytes (void const *bytes, DN_USize size, DN_U64 seed); + +DN_API DN_U32 DN_MurmurHash3HashU32FromBytesX86 (void const *bytes, int len, DN_U32 seed); +DN_API DN_MurmurHash3 DN_MurmurHash3HashU128FromBytesX64 (void const *bytes, int len, DN_U32 seed); +DN_API DN_U64 DN_MurmurHash3HashU64FromBytesX64 (void const *bytes, int len, DN_U32 seed); +DN_API DN_U32 DN_MurmurHash3HashU32FromBytesX64 (void const *bytes, int len, DN_U32 seed); + +#if defined(DN_64_BIT) + #define DN_MurmurHash3HashU32FromBytes(bytes, len, seed) DN_MurmurHash3HashU32FromBytesX64(bytes, len, seed) +#else + #define DN_MurmurHash3HashU32FromBytes(bytes, len, seed) DN_MurmurHash3HashU32FromBytesX86(bytes, len, seed) +#endif + +#define DN_ANSICodeBoldLit "\x1b[1m" +#define DN_ANSICodeResetLit "\x1b[0m" +DN_API DN_Str8x32 DN_Str8x32FromANSIColourCodeU8RGB (DN_ANSIColourMode mode, DN_U8 r, DN_U8 g, DN_U8 b); +DN_API DN_Str8x32 DN_Str8x32FromANSIColourCodeV3F32RGB255 (DN_ANSIColourMode mode, DN_V3F32 rgb_255); +DN_API DN_Str8x32 DN_Str8x32FromANSIColourCodeU32RGB (DN_ANSIColourMode mode, DN_U32 value); +DN_API DN_Str8 DN_Str8FromStr8ANSIColourU8RGBArena (DN_ANSIColourMode mode, DN_Str8 str8, DN_U8 r, DN_U8 g, DN_U8 b, DN_Arena *arena); +DN_API DN_Str8 DN_Str8FromStr8ANSIColourV3F32RGB255Arena (DN_ANSIColourMode mode, DN_Str8 str8, DN_V3F32 rgb_255, DN_Arena *arena); +DN_API DN_Str8 DN_Str8FromFmtANSIColourU8RGBArena (DN_ANSIColourMode mode, DN_U8 r, DN_U8 g, DN_U8 b, DN_Arena *arena, char const *fmt, ...); +DN_API DN_Str8 DN_Str8FromFmtANSIColourV3F32RGB255Arena (DN_ANSIColourMode mode, DN_V3F32 rgb_255, DN_Arena *arena, char const *fmt, ...); + +// NOTE: Create log printable lines with a date prefix, severity and message. The platform +// implementation should call `SetPrintFunc` to intercept the log messages and output to the desired +// destination (log file, standard out, e.t.c.). When the library is initialised `DN_Init` to with +// OS functionality enabled, the log callback is by default set to outputting via standard out. +DN_API DN_LogPrefixSize DN_LogMakePrefix (DN_LogStyle style, DN_LogTypeParam type, DN_CallSite call_site, DN_LogDate date, char *dest, DN_USize dest_size); +DN_API void DN_LogSetPrintFunc (DN_LogPrintFunc *print_func, void *user_data); +DN_API void DN_LogPrintF (DN_LogTypeParam type, DN_CallSite call_site, DN_LogFlags flags, DN_FMT_ATTRIB char const *fmt, ...); +DN_API void DN_LogPrintFV (DN_LogTypeParam type, DN_CallSite call_site, DN_LogFlags flags, DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API DN_LogTypeParam DN_LogTypeParamFromType (DN_LogType type); +#define DN_LogF(type, fmt, ...) DN_LogPrintF(DN_LogTypeParamFromType(type), DN_CallSiteNow, DN_LogFlags_Nil, fmt, ##__VA_ARGS__) + +#define DN_LogDebugF(fmt, ...) DN_LogPrintF(DN_LogTypeParamFromType(DN_LogType_Debug), DN_CallSiteNow, DN_LogFlags_Nil, fmt, ##__VA_ARGS__) +#define DN_LogInfoF(fmt, ...) DN_LogPrintF(DN_LogTypeParamFromType(DN_LogType_Info), DN_CallSiteNow, DN_LogFlags_Nil, fmt, ##__VA_ARGS__) +#define DN_LogWarningF(fmt, ...) DN_LogPrintF(DN_LogTypeParamFromType(DN_LogType_Warning), DN_CallSiteNow, DN_LogFlags_Nil, fmt, ##__VA_ARGS__) +#define DN_LogErrorF(fmt, ...) DN_LogPrintF(DN_LogTypeParamFromType(DN_LogType_Error), DN_CallSiteNow, DN_LogFlags_Nil, fmt, ##__VA_ARGS__) + +#define DN_LogFlagF(type, flags, fmt, ...) DN_LogPrintF(DN_LogTypeParamFromType(type), DN_CallSiteNow, flags, fmt, ##__VA_ARGS__) +#define DN_LogFlagDebugF(flags, fmt, ...) DN_LogPrintF(DN_LogTypeParamFromType(DN_LogType_Debug), DN_CallSiteNow, flags, fmt, ##__VA_ARGS__) +#define DN_LogFlagInfoF(flags, fmt, ...) DN_LogPrintF(DN_LogTypeParamFromType(DN_LogType_Info), DN_CallSiteNow, flags, fmt, ##__VA_ARGS__) +#define DN_LogFlagWarningF(flags, fmt, ...) DN_LogPrintF(DN_LogTypeParamFromType(DN_LogType_Warning), DN_CallSiteNow, flags, fmt, ##__VA_ARGS__) +#define DN_LogFlagErrorF(flags, fmt, ...) DN_LogPrintF(DN_LogTypeParamFromType(DN_LogType_Error), DN_CallSiteNow, flags, fmt, ##__VA_ARGS__) + + +// NOTE: OS primitives that the OS layer can provide for the base layer but is optional. +#if defined(DN_FREESTANDING) +#define DN_StackTraceFromArena(...) {} +#define DN_StackTraceFromAllocator(...) {} +#define DN_StackTraceIterate(...) false +#define DN_Str8FromStackTraceAllocator(...) DN_Str8Lit("N/A") +#define DN_Str8FromStackTraceArena(...) DN_Str8Lit("N/A") +#define DN_Str8FromStackTraceNowAllocator(...) DN_Str8Lit("N/A") +#define DN_Str8FromStackTraceNowArena(...) DN_Str8Lit("N/A") +#define DN_Str8FromStackTraceNowHeap(...) DN_Str8Lit("N/A") +#define DN_StackTraceGetFrames(...) +#define DN_StackTraceRawFrameToFrame(...) +#define DN_StackTracePrint(...) +#define DN_StackTraceReloadSymbols(...) +#else +DN_API DN_StackTrace DN_StackTraceFromArena (DN_Arena *arena, DN_U16 limit); +DN_API DN_StackTrace DN_StackTraceFromAllocator (DN_Allocator allocator, DN_U16 limit); +DN_API bool DN_StackTraceIterate (DN_StackTraceIterator *it, DN_StackTrace const *walk); +DN_API DN_Str8 DN_Str8FromStackTraceAllocator (DN_Allocator allocator, DN_StackTrace const *walk, DN_U16 skip); +DN_API DN_Str8 DN_Str8FromStackTraceArena (DN_Arena *arena, DN_StackTrace const *walk, DN_U16 skip); +DN_API DN_Str8 DN_Str8FromStackTraceNowAllocator (DN_Arena *arena, DN_U16 limit, DN_U16 skip); +DN_API DN_Str8 DN_Str8FromStackTraceNowArena (DN_Arena *arena, DN_U16 limit, DN_U16 skip); +DN_API DN_Str8 DN_Str8FromStackTraceNowHeap (DN_U16 limit, DN_U16 skip); +DN_API DN_StackTraceFrameSlice DN_StackTraceGetFrames (DN_Arena *arena, DN_U16 limit); +DN_API DN_StackTraceFrame DN_StackTraceRawFrameToFrame (DN_Arena *arena, DN_StackTraceRawFrame raw_frame); +DN_API void DN_StackTracePrint (DN_U16 limit); +DN_API void DN_StackTraceReloadSymbols (); +#endif + +DN_API DN_F32 DN_F32Lerp (DN_F32 a, DN_F32 t, DN_F32 b); +DN_API DN_F32 DN_F32Floor (DN_F32 val); +DN_API DN_F32 DN_F32Ceil (DN_F32 val); +DN_API DN_F32 DN_F32RoundHalfUp (DN_F32 val); + +#define DN_V2I32Zero DN_Literal(DN_V2I32){{(DN_I32)(0), (DN_I32)(0)}} +#define DN_V2I32One DN_Literal(DN_V2I32){{(DN_I32)(1), (DN_I32)(1)}} +#define DN_V2I32From1N(x) DN_Literal(DN_V2I32){{(DN_I32)(x), (DN_I32)(x)}} +#define DN_V2I32From2N(x, y) DN_Literal(DN_V2I32){{(DN_I32)(x), (DN_I32)(y)}} +#define DN_V2I32InitV2(xy) DN_Literal(DN_V2I32){{(DN_I32)(xy).x, (DN_I32)(xy).y}} + +DN_API bool operator!= (DN_V2I32 lhs, DN_V2I32 rhs); +DN_API bool operator== (DN_V2I32 lhs, DN_V2I32 rhs); +DN_API bool operator>= (DN_V2I32 lhs, DN_V2I32 rhs); +DN_API bool operator<= (DN_V2I32 lhs, DN_V2I32 rhs); +DN_API bool operator< (DN_V2I32 lhs, DN_V2I32 rhs); +DN_API bool operator> (DN_V2I32 lhs, DN_V2I32 rhs); +DN_API DN_V2I32 operator- (DN_V2I32 lhs, DN_V2I32 rhs); +DN_API DN_V2I32 operator- (DN_V2I32 lhs); +DN_API DN_V2I32 operator+ (DN_V2I32 lhs, DN_V2I32 rhs); +DN_API DN_V2I32 operator* (DN_V2I32 lhs, DN_V2I32 rhs); +DN_API DN_V2I32 operator* (DN_V2I32 lhs, DN_F32 rhs); +DN_API DN_V2I32 operator* (DN_V2I32 lhs, DN_I32 rhs); +DN_API DN_V2I32 operator/ (DN_V2I32 lhs, DN_V2I32 rhs); +DN_API DN_V2I32 operator/ (DN_V2I32 lhs, DN_F32 rhs); +DN_API DN_V2I32 operator/ (DN_V2I32 lhs, DN_I32 rhs); +DN_API DN_V2I32& operator*= (DN_V2I32& lhs, DN_V2I32 rhs); +DN_API DN_V2I32& operator*= (DN_V2I32& lhs, DN_F32 rhs); +DN_API DN_V2I32& operator*= (DN_V2I32& lhs, DN_I32 rhs); +DN_API DN_V2I32& operator/= (DN_V2I32& lhs, DN_V2I32 rhs); +DN_API DN_V2I32& operator/= (DN_V2I32& lhs, DN_F32 rhs); +DN_API DN_V2I32& operator/= (DN_V2I32& lhs, DN_I32 rhs); +DN_API DN_V2I32& operator-= (DN_V2I32& lhs, DN_V2I32 rhs); +DN_API DN_V2I32& operator+= (DN_V2I32& lhs, DN_V2I32 rhs); + +DN_API DN_V2I32 DN_V2I32Min (DN_V2I32 a, DN_V2I32 b); +DN_API DN_V2I32 DN_V2I32Max (DN_V2I32 a, DN_V2I32 b); +DN_API DN_V2I32 DN_V2I32Abs (DN_V2I32 a); + +#define DN_V2U16Zero DN_Literal(DN_V2U16){{(DN_U16)(0), (DN_U16)(0)}} +#define DN_V2U16One DN_Literal(DN_V2U16){{(DN_U16)(1), (DN_U16)(1)}} +#define DN_V2U16From1N(x) DN_Literal(DN_V2U16){{(DN_U16)(x), (DN_U16)(x)}} +#define DN_V2U16From2N(x, y) DN_Literal(DN_V2U16){{(DN_U16)(x), (DN_U16)(y)}} + +DN_API bool operator!= (DN_V2U16 lhs, DN_V2U16 rhs); +DN_API bool operator== (DN_V2U16 lhs, DN_V2U16 rhs); +DN_API bool operator>= (DN_V2U16 lhs, DN_V2U16 rhs); +DN_API bool operator<= (DN_V2U16 lhs, DN_V2U16 rhs); +DN_API bool operator< (DN_V2U16 lhs, DN_V2U16 rhs); +DN_API bool operator> (DN_V2U16 lhs, DN_V2U16 rhs); +DN_API DN_V2U16 operator- (DN_V2U16 lhs, DN_V2U16 rhs); +DN_API DN_V2U16 operator+ (DN_V2U16 lhs, DN_V2U16 rhs); +DN_API DN_V2U16 operator* (DN_V2U16 lhs, DN_V2U16 rhs); +DN_API DN_V2U16 operator* (DN_V2U16 lhs, DN_F32 rhs); +DN_API DN_V2U16 operator* (DN_V2U16 lhs, DN_I32 rhs); +DN_API DN_V2U16 operator/ (DN_V2U16 lhs, DN_V2U16 rhs); +DN_API DN_V2U16 operator/ (DN_V2U16 lhs, DN_F32 rhs); +DN_API DN_V2U16 operator/ (DN_V2U16 lhs, DN_I32 rhs); +DN_API DN_V2U16& operator*= (DN_V2U16& lhs, DN_V2U16 rhs); +DN_API DN_V2U16& operator*= (DN_V2U16& lhs, DN_F32 rhs); +DN_API DN_V2U16& operator*= (DN_V2U16& lhs, DN_I32 rhs); +DN_API DN_V2U16& operator/= (DN_V2U16& lhs, DN_V2U16 rhs); +DN_API DN_V2U16& operator/= (DN_V2U16& lhs, DN_F32 rhs); +DN_API DN_V2U16& operator/= (DN_V2U16& lhs, DN_I32 rhs); +DN_API DN_V2U16& operator-= (DN_V2U16& lhs, DN_V2U16 rhs); +DN_API DN_V2U16& operator+= (DN_V2U16& lhs, DN_V2U16 rhs); + +#define DN_V2U32Zero DN_Literal(DN_V2U32){{(DN_U32)(0), (DN_U32)(0)}} +#define DN_V2U32One DN_Literal(DN_V2U32){{(DN_U32)(1), (DN_U32)(1)}} +#define DN_V2U32From1N(x) DN_Literal(DN_V2U32){{(DN_U32)(x), (DN_U32)(x)}} +#define DN_V2U32From2N(x, y) DN_Literal(DN_V2U32){{(DN_U32)(x), (DN_U32)(y)}} + +#define DN_V2F32Zero DN_Literal(DN_V2F32){{(DN_F32)(0), (DN_F32)(0)}} +#define DN_V2F32One DN_Literal(DN_V2F32){{(DN_F32)(1), (DN_F32)(1)}} +#define DN_V2F32From1N(x) DN_Literal(DN_V2F32){{(DN_F32)(x), (DN_F32)(x)}} +#define DN_V2F32From2N(x, y) DN_Literal(DN_V2F32){{(DN_F32)(x), (DN_F32)(y)}} +#define DN_V2F32FromV2(xy) DN_Literal(DN_V2F32){{(DN_F32)(xy).x, (DN_F32)(xy).y}} + +DN_API DN_V2F32 DN_V2F32Lerp (DN_V2F32 a, DN_F32 t, DN_V2F32 b); + +DN_API bool operator!= (DN_V2F32 lhs, DN_V2F32 rhs); +DN_API bool operator== (DN_V2F32 lhs, DN_V2F32 rhs); +DN_API bool operator>= (DN_V2F32 lhs, DN_V2F32 rhs); +DN_API bool operator<= (DN_V2F32 lhs, DN_V2F32 rhs); +DN_API bool operator< (DN_V2F32 lhs, DN_V2F32 rhs); +DN_API bool operator> (DN_V2F32 lhs, DN_V2F32 rhs); + +DN_API DN_V2F32 operator- (DN_V2F32 lhs); +DN_API DN_V2F32 operator- (DN_V2F32 lhs, DN_V2F32 rhs); +DN_API DN_V2F32 operator- (DN_V2F32 lhs, DN_V2I32 rhs); +DN_API DN_V2F32 operator- (DN_V2F32 lhs, DN_F32 rhs); +DN_API DN_V2F32 operator- (DN_V2F32 lhs, DN_I32 rhs); + +DN_API DN_V2F32 operator+ (DN_V2F32 lhs, DN_V2F32 rhs); +DN_API DN_V2F32 operator+ (DN_V2F32 lhs, DN_V2I32 rhs); +DN_API DN_V2F32 operator+ (DN_V2F32 lhs, DN_F32 rhs); +DN_API DN_V2F32 operator+ (DN_V2F32 lhs, DN_I32 rhs); + +DN_API DN_V2F32 operator* (DN_V2F32 lhs, DN_V2F32 rhs); +DN_API DN_V2F32 operator* (DN_V2F32 lhs, DN_V2I32 rhs); +DN_API DN_V2F32 operator* (DN_V2F32 lhs, DN_F32 rhs); +DN_API DN_V2F32 operator* (DN_V2F32 lhs, DN_I32 rhs); + +DN_API DN_V2F32 operator/ (DN_V2F32 lhs, DN_V2F32 rhs); +DN_API DN_V2F32 operator/ (DN_V2F32 lhs, DN_V2I32 rhs); +DN_API DN_V2F32 operator/ (DN_V2F32 lhs, DN_F32 rhs); +DN_API DN_V2F32 operator/ (DN_V2F32 lhs, DN_I32 rhs); + +DN_API DN_V2F32& operator*= (DN_V2F32& lhs, DN_V2F32 rhs); +DN_API DN_V2F32& operator*= (DN_V2F32& lhs, DN_V2I32 rhs); +DN_API DN_V2F32& operator*= (DN_V2F32& lhs, DN_F32 rhs); +DN_API DN_V2F32& operator*= (DN_V2F32& lhs, DN_I32 rhs); + +DN_API DN_V2F32& operator/= (DN_V2F32& lhs, DN_V2F32 rhs); +DN_API DN_V2F32& operator/= (DN_V2F32& lhs, DN_V2I32 rhs); +DN_API DN_V2F32& operator/= (DN_V2F32& lhs, DN_F32 rhs); +DN_API DN_V2F32& operator/= (DN_V2F32& lhs, DN_I32 rhs); + +DN_API DN_V2F32& operator-= (DN_V2F32& lhs, DN_V2F32 rhs); +DN_API DN_V2F32& operator-= (DN_V2F32& lhs, DN_V2I32 rhs); +DN_API DN_V2F32& operator-= (DN_V2F32& lhs, DN_F32 rhs); +DN_API DN_V2F32& operator-= (DN_V2F32& lhs, DN_I32 rhs); + +DN_API DN_V2F32& operator+= (DN_V2F32& lhs, DN_V2F32 rhs); +DN_API DN_V2F32& operator+= (DN_V2F32& lhs, DN_V2I32 rhs); +DN_API DN_V2F32& operator+= (DN_V2F32& lhs, DN_F32 rhs); +DN_API DN_V2F32& operator+= (DN_V2F32& lhs, DN_I32 rhs); + +DN_API DN_V2F32 DN_V2F32Min (DN_V2F32 a, DN_V2F32 b); +DN_API DN_V2F32 DN_V2F32Max (DN_V2F32 a, DN_V2F32 b); +DN_API DN_V2F32 DN_V2F32Abs (DN_V2F32 a); +DN_API DN_F32 DN_V2F32Dot (DN_V2F32 a, DN_V2F32 b); +DN_API DN_F32 DN_V2F32LengthSq2V2 (DN_V2F32 lhs, DN_V2F32 rhs); +DN_API bool DN_V2F32LengthSqIsWithin2V2 (DN_V2F32 lhs, DN_V2F32 rhs, DN_F32 within_amount_sq); +DN_API DN_F32 DN_V2F32Length2V2 (DN_V2F32 lhs, DN_V2F32 rhs); +DN_API DN_F32 DN_V2F32LengthSq (DN_V2F32 lhs); +DN_API DN_F32 DN_V2F32Length (DN_V2F32 lhs); +DN_API DN_V2F32 DN_V2F32Normalise (DN_V2F32 a); +DN_API DN_V2F32 DN_V2F32Perpendicular (DN_V2F32 a); +DN_API DN_V2F32 DN_V2F32Reflect (DN_V2F32 in, DN_V2F32 surface); +DN_API DN_F32 DN_V2F32Area (DN_V2F32 a); + +#define DN_V3F32From1N(x) DN_Literal(DN_V3F32){{(DN_F32)(x), (DN_F32)(x), (DN_F32)(x)}} +#define DN_V3F32From3N(x, y, z) DN_Literal(DN_V3F32){{(DN_F32)(x), (DN_F32)(y), (DN_F32)(z)}} +#define DN_V3F32FromV2F32And1N(xy, z) DN_Literal(DN_V3F32){{(DN_F32)(xy.x), (DN_F32)(xy.y), (DN_F32)(z)}} + +// NOTE: Grayscale co-efficients from: +// https://github.com/EpicGames/UnrealEngine/blob/260bb2e1c5610b31c63a36206eedd289409c5f11/Engine/Source/Runtime/Core/Private/Math/Color.cpp#L304 +DN_V3F32 static const DN_V3F32_RGB_LUMINANCE = DN_V3F32From3N(0.3f, 0.59f, 0.11f); + +DN_API bool operator== (DN_V3F32 lhs, DN_V3F32 rhs); +DN_API bool operator!= (DN_V3F32 lhs, DN_V3F32 rhs); +DN_API bool operator>= (DN_V3F32 lhs, DN_V3F32 rhs); +DN_API bool operator<= (DN_V3F32 lhs, DN_V3F32 rhs); +DN_API bool operator< (DN_V3F32 lhs, DN_V3F32 rhs); +DN_API bool operator> (DN_V3F32 lhs, DN_V3F32 rhs); +DN_API DN_V3F32 operator- (DN_V3F32 lhs, DN_V3F32 rhs); +DN_API DN_V3F32 operator- (DN_V3F32 lhs); +DN_API DN_V3F32 operator+ (DN_V3F32 lhs, DN_V3F32 rhs); +DN_API DN_V3F32 operator* (DN_V3F32 lhs, DN_V3F32 rhs); +DN_API DN_V3F32 operator* (DN_V3F32 lhs, DN_F32 rhs); +DN_API DN_V3F32 operator* (DN_V3F32 lhs, DN_I32 rhs); +DN_API DN_V3F32 operator/ (DN_V3F32 lhs, DN_V3F32 rhs); +DN_API DN_V3F32 operator/ (DN_V3F32 lhs, DN_F32 rhs); +DN_API DN_V3F32 operator/ (DN_V3F32 lhs, DN_I32 rhs); +DN_API DN_V3F32& operator*= (DN_V3F32 &lhs, DN_V3F32 rhs); +DN_API DN_V3F32& operator*= (DN_V3F32 &lhs, DN_F32 rhs); +DN_API DN_V3F32& operator*= (DN_V3F32 &lhs, DN_I32 rhs); +DN_API DN_V3F32& operator/= (DN_V3F32 &lhs, DN_V3F32 rhs); +DN_API DN_V3F32& operator/= (DN_V3F32 &lhs, DN_F32 rhs); +DN_API DN_V3F32& operator/= (DN_V3F32 &lhs, DN_I32 rhs); +DN_API DN_V3F32& operator-= (DN_V3F32 &lhs, DN_V3F32 rhs); +DN_API DN_V3F32& operator+= (DN_V3F32 &lhs, DN_V3F32 rhs); +DN_API DN_V3F32 DN_V3F32Lerp (DN_V3F32 lhs, DN_F32 t01, DN_V3F32 rhs); +DN_API DN_F32 DN_V3F32LengthSq (DN_V3F32 a); +DN_API DN_F32 DN_V3F32Length (DN_V3F32 a); +DN_API DN_V3F32 DN_V3F32Normalise (DN_V3F32 a); + +#define DN_V4F32From1N(x) DN_Literal(DN_V4F32){{(DN_F32)(x), (DN_F32)(x), (DN_F32)(x), (DN_F32)(x)}} +#define DN_V4F32From4N(x, y, z, w) DN_Literal(DN_V4F32){{(DN_F32)(x), (DN_F32)(y), (DN_F32)(z), (DN_F32)(w)}} +#define DN_V4F32FromV3And1N(xyz, w) DN_Literal(DN_V4F32){{xyz.x, xyz.y, xyz.z, w}} +#define DN_V4F32RGBA01FromRGBAU8(r, g, b, a) DN_Literal(DN_V4F32){{r / 255.f, g / 255.f, b / 255.f, a / 255.f}} +#define DN_V4F32RGBA01FromRGBU8(r, g, b) DN_Literal(DN_V4F32){{r / 255.f, g / 255.f, b / 255.f, 1.f}} + +DN_API DN_V4F32 DN_V4F32Lerp (DN_V4F32 lhs, DN_F32 t01, DN_V4F32 rhs); +DN_API bool DN_V4F32RGBA01IsValid (DN_V4F32 rgba01); +DN_API DN_V4F32 DN_V4F32RGBA01FromRGBU32 (DN_U32 u32); +DN_API DN_V4F32 DN_V4F32RGBA01FromRGBAU32 (DN_U32 u32); +DN_API DN_V4F32 DN_V4F32Linear01FromSRGB01 (DN_V4F32 rgb01); +DN_API DN_V4F32 DN_V4F32Linear01Desaturate (DN_V4F32 linear01, DN_F32 t01); +DN_API DN_V4F32 DN_V4F32SRGB01FromLinear01 (DN_V4F32 linear01); + +#define DN_V4F32FromV4Alpha(v4, alpha) DN_V4F32FromV3And1N(v4.xyz, alpha) + +DN_API bool operator== (DN_V4F32 lhs, DN_V4F32 rhs); +DN_API bool operator!= (DN_V4F32 lhs, DN_V4F32 rhs); +DN_API bool operator<= (DN_V4F32 lhs, DN_V4F32 rhs); +DN_API bool operator< (DN_V4F32 lhs, DN_V4F32 rhs); +DN_API bool operator> (DN_V4F32 lhs, DN_V4F32 rhs); +DN_API DN_V4F32 operator- (DN_V4F32 lhs, DN_V4F32 rhs); +DN_API DN_V4F32 operator- (DN_V4F32 lhs); +DN_API DN_V4F32 operator+ (DN_V4F32 lhs, DN_V4F32 rhs); +DN_API DN_V4F32 operator* (DN_V4F32 lhs, DN_V4F32 rhs); +DN_API DN_V4F32 operator* (DN_V4F32 lhs, DN_F32 rhs); +DN_API DN_V4F32 operator* (DN_V4F32 lhs, DN_I32 rhs); +DN_API DN_V4F32 operator/ (DN_V4F32 lhs, DN_F32 rhs); +DN_API DN_V4F32& operator*= (DN_V4F32 &lhs, DN_V4F32 rhs); +DN_API DN_V4F32& operator*= (DN_V4F32 &lhs, DN_F32 rhs); +DN_API DN_V4F32& operator*= (DN_V4F32 &lhs, DN_I32 rhs); +DN_API DN_V4F32& operator-= (DN_V4F32 &lhs, DN_V4F32 rhs); +DN_API DN_V4F32& operator+= (DN_V4F32 &lhs, DN_V4F32 rhs); +DN_API DN_F32 DN_V4F32Dot (DN_V4F32 a, DN_V4F32 b); + +DN_API DN_M4 DN_M4Identity (); +DN_API DN_M4 DN_M4ScaleF (DN_F32 x, DN_F32 y, DN_F32 z); +DN_API DN_M4 DN_M4Scale (DN_V3F32 xyz); +DN_API DN_M4 DN_M4TranslateF (DN_F32 x, DN_F32 y, DN_F32 z); +DN_API DN_M4 DN_M4Translate (DN_V3F32 xyz); +DN_API DN_M4 DN_M4Transpose (DN_M4 mat); +DN_API DN_M4 DN_M4Rotate (DN_V3F32 axis, DN_F32 radians); +DN_API DN_M4 DN_M4Orthographic (DN_F32 left, DN_F32 right, DN_F32 bottom, DN_F32 top, DN_F32 z_near, DN_F32 z_far); +DN_API DN_M4 DN_M4Perspective (DN_F32 fov /*radians*/, DN_F32 aspect, DN_F32 z_near, DN_F32 z_far); +DN_API DN_M4 DN_M4Add (DN_M4 lhs, DN_M4 rhs); +DN_API DN_M4 DN_M4Sub (DN_M4 lhs, DN_M4 rhs); +DN_API DN_M4 DN_M4Mul (DN_M4 lhs, DN_M4 rhs); +DN_API DN_M4 DN_M4Div (DN_M4 lhs, DN_M4 rhs); +DN_API DN_M4 DN_M4AddF (DN_M4 lhs, DN_F32 rhs); +DN_API DN_M4 DN_M4SubF (DN_M4 lhs, DN_F32 rhs); +DN_API DN_M4 DN_M4MulF (DN_M4 lhs, DN_F32 rhs); +DN_API DN_M4 DN_M4DivF (DN_M4 lhs, DN_F32 rhs); +DN_API DN_Str8x256 DN_M4ColumnMajorString (DN_M4 mat); + +DN_API bool DN_M2x3Eq (DN_M2x3 const *lhs, DN_M2x3 const *rhs); +DN_API bool DN_M2x3NotEq (DN_M2x3 const *lhs, DN_M2x3 const *rhs); +DN_API DN_M2x3 DN_M2x3Identity (); +DN_API DN_M2x3 DN_M2x3Translate (DN_V2F32 offset); +DN_API DN_V2F32 DN_M2x3ScaleGet (DN_M2x3 m2x3); +DN_API DN_M2x3 DN_M2x3Scale (DN_V2F32 scale); +DN_API DN_M2x3 DN_M2x3Rotate (DN_F32 radians); +DN_API DN_M2x3 DN_M2x3ProjFromV2F32 (DN_V2F32 size, DN_M2x3ProjOrigin origin); +DN_API DN_M2x3XForm DN_M2x3XFormFrom2M2x3 (DN_M2x3 forward, DN_M2x3 inverse); +DN_API DN_M2x3XForm DN_M2x3XFormFromTRS (DN_V2F32 pos, DN_V2F32 scale, DN_F32 rotate_rads, DN_V2F32 pivot_pos); +DN_API DN_M2x3XForm DN_M2x3XFormIdentity (); +DN_API DN_M2x3XForm DN_M2x3XFormMul (DN_M2x3XForm m1, DN_M2x3XForm m2); +DN_API DN_M2x3 DN_M2x3Mul (DN_M2x3 m1, DN_M2x3 m2); +DN_API DN_V2F32 DN_M2x3Mul2F32 (DN_M2x3 m1, DN_F32 x, DN_F32 y); +DN_API DN_V2F32 DN_M2x3MulV2F32 (DN_M2x3 m1, DN_V2F32 v2); +DN_API DN_Rect DN_M2x3MulRect (DN_M2x3 m1, DN_Rect rect); + +#define DN_RectFrom2V2(pos, size) DN_Literal(DN_Rect){(pos), (size)} +#define DN_RectFrom4N(x, y, w, h) DN_Literal(DN_Rect){DN_Literal(DN_V2F32){{x, y}}, DN_Literal(DN_V2F32){{w, h}}} +#define DN_RectZero DN_RectFrom4N(0, 0, 0, 0) + +DN_API DN_V2F32 DN_RectCenter (DN_Rect rect); +DN_API bool DN_RectContainsPoint (DN_Rect rect, DN_V2F32 p); +DN_API bool DN_RectContainsRect (DN_Rect a, DN_Rect b); +DN_API DN_Rect DN_RectExpand (DN_Rect a, DN_F32 amount); +DN_API DN_Rect DN_RectExpandV2 (DN_Rect a, DN_V2F32 amount); +DN_API bool DN_RectIntersects (DN_Rect a, DN_Rect b); +DN_API DN_Rect DN_RectIntersection (DN_Rect a, DN_Rect b); +DN_API DN_Rect DN_RectUnion (DN_Rect a, DN_Rect b); +DN_API DN_2V2F32 DN_RectRange (DN_Rect a); +DN_API bool DN_RectEq (DN_Rect lhs, DN_Rect rhs); +DN_API DN_F32 DN_RectArea (DN_Rect a); +DN_API DN_V2F32 DN_RectInterpV2F32 (DN_Rect rect, DN_V2F32 t01); +DN_API DN_V2F32 DN_RectTopLeft (DN_Rect rect); +DN_API DN_V2F32 DN_RectTopRight (DN_Rect rect); +DN_API DN_V2F32 DN_RectBottomLeft (DN_Rect rect); +DN_API DN_V2F32 DN_RectBottomRight (DN_Rect rect); + +DN_API DN_Rect DN_RectCutLeftClip (DN_Rect *rect, DN_F32 amount, DN_RectCutClip clip); +DN_API DN_Rect DN_RectCutRightClip (DN_Rect *rect, DN_F32 amount, DN_RectCutClip clip); +DN_API DN_Rect DN_RectCutTopClip (DN_Rect *rect, DN_F32 amount, DN_RectCutClip clip); +DN_API DN_Rect DN_RectCutBottomClip (DN_Rect *rect, DN_F32 amount, DN_RectCutClip clip); + +#define DN_RectCutLeft(rect, amount) DN_RectCutLeftClip(rect, amount, DN_RectCutClip_Yes) +#define DN_RectCutRight(rect, amount) DN_RectCutRightClip(rect, amount, DN_RectCutClip_Yes) +#define DN_RectCutTop(rect, amount) DN_RectCutTopClip(rect, amount, DN_RectCutClip_Yes) +#define DN_RectCutBottom(rect, amount) DN_RectCutBottomClip(rect, amount, DN_RectCutClip_Yes) + +#define DN_RectCutLeftNoClip(rect, amount) DN_RectCutLeftClip(rect, amount, DN_RectCutClip_No) +#define DN_RectCutRightNoClip(rect, amount) DN_RectCutRightClip(rect, amount, DN_RectCutClip_No) +#define DN_RectCutTopNoClip(rect, amount) DN_RectCutTopClip(rect, amount, DN_RectCutClip_No) +#define DN_RectCutBottomNoClip(rect, amount) DN_RectCutBottomClip(rect, amount, DN_RectCutClip_No) + +DN_API DN_Rect DN_RectCutCut (DN_RectCut rect_cut, DN_V2F32 size, DN_RectCutClip clip); +#define DN_RectCutInit(rect, side) DN_Literal(DN_RectCut){rect, side} + +DN_API DN_RaycastV2 DN_RaycastLineIntersectV2 (DN_V2F32 origin_a, DN_V2F32 dir_a, DN_V2F32 origin_b, DN_V2F32 dir_b); + +// NOTE: Containers that are imlpemented using primarily macros for operating on data structures +// that are embedded into a C style struct or from a set of defined variables from the available +// scope. Keep it stupid simple, structs and functions. Minimal amount of container types with +// flexible construction leads to less duplicated container code and less template meta-programming. + +// NOTE: Intrusive Singly Linked List +// Define a struct with the members `next`: +// +// struct MyLinkItem { +// int data; +// MyLinkItem *next; +// } my_link = {}; +// +// MyLinkItem *first_item = DN_ISinglyLLDetach(&my_link, MyLinkItem); +#define DN_ISinglyLLDetach(list) (decltype(list))DN_SinglyLLDetach((void **)&(list), (void **)&(list)->next) + +// NOTE: Singly Linked List with Head and Tail pointer +/* + struct MyLinkItem { + int data; + MyLinkItem *next; + } my_list = {}; + + struct MyContainer { + MyLinkItem *head; + MyLinkItem *tail; + }; + + MyLinkItem item = {}; + MyContainer container = {}; + DN_ISinglyHeadTailLLAppend(container, item); + // ... or alternatively, DN_SinglyHeadTailLLAppend(container.head, container.tail, item); + + for (MyLinkItem *it = container.head; it; it = it->next) { } +*/ +#define DN_SinglyHeadTailLLAppend(head, tail, to_append) \ + do { \ + if (!head) \ + head = to_append; \ + if (tail) \ + tail->next = to_append; \ + tail = to_append; \ + } while (0) +#define DN_ISinglyHeadTailLLAppend(container_ptr, to_append) DN_SinglyHeadTailLLAppend((container_ptr)->head, (container_ptr)->tail, to_append) + +// NOTE: Sentinel Doubly Linked List +// Uses a sentinel/dummy node as the list head. The sentinel points to itself when empty. +// Define a struct with the members `next` and `prev`: +// +// struct MyLinkItem { +// int data; +// MyLinkItem *next; +// MyLinkItem *prev; +// } my_list = {}; +// +// DN_SentinelDoublyLLInit(&my_list); +// DN_SentinelDoublyLLAppend(&my_list, &new_item); +// DN_SentinelDoublyLLForEach(it, &my_list) { /* ... */ } +// +#define DN_SentinelDoublyLLInit(list) (list)->next = (list)->prev = (list) +#define DN_SentinelDoublyLLIsSentinel(list, item) ((list) == (item)) +#define DN_SentinelDoublyLLIsEmpty(list) (!(list) || ((list) == (list)->next)) +#define DN_SentinelDoublyLLIsInit(list) ((list)->next && (list)->prev) +#define DN_SentinelDoublyLLHasItems(list) ((list) && ((list) != (list)->next)) +#define DN_SentinelDoublyLLForEach(it, list) auto *it = (list)->next; (it) != (list); (it) = (it)->next + +#define DN_SentinelDoublyLLInitArena(list, T, ptr_arena) \ + do { \ + (list) = DN_ArenaNew(ptr_arena, T, DN_ZMem_Yes); \ + DN_SentinelDoublyLLInit(list); \ + } while (0) + +#define DN_SentinelDoublyLLInitPool(list, T, pool) \ + do { \ + (list) = DN_PoolNew(pool, T); \ + DN_SentinelDoublyLLInit(list); \ + } while (0) + +#define DN_SentinelDoublyLLDetach(item) \ + do { \ + if (item) { \ + (item)->prev->next = (item)->next; \ + (item)->next->prev = (item)->prev; \ + (item)->next = nullptr; \ + (item)->prev = nullptr; \ + } \ + } while (0) + +#define DN_SentinelDoublyLLDequeue(list, dest_ptr) \ + if (DN_SentinelDoublyLLHasItems(list)) { \ + dest_ptr = (list)->next; \ + DN_SentinelDoublyLLDetach(dest_ptr); \ + } + +#define DN_SentinelDoublyLLAppend(list, item) \ + do { \ + if (item) { \ + if ((item)->next) \ + DN_SentinelDoublyLLDetach(item); \ + (item)->next = (list)->next; \ + (item)->prev = (list); \ + (item)->next->prev = (item); \ + (item)->prev->next = (item); \ + } \ + } while (0) + +#define DN_SentinelDoublyLLPrepend(list, item) \ + do { \ + if (item) { \ + if ((item)->next) \ + DN_SentinelDoublyLLDetach(item); \ + (item)->next = (list); \ + (item)->prev = (list)->prev; \ + (item)->next->prev = (item); \ + (item)->prev->next = (item); \ + } \ + } while (0) + +// NOTE: Doubly Linked List +// Define a struct with the members `next` and `prev`. This list has null pointers for head->prev +// and tail->next. +// +// struct MyLinkItem { +// int data; +// MyLinkItem *next; +// MyLinkItem *prev; +// } my_link = {}; +// +// MyLinkItem first_item = {}, second_item = {}; +// DN_DoublyLLAppend(&first_item, &second_item); // first_item -> second_item +// +#define DN_DoublyLLDetach(head, ptr) \ + do { \ + if ((head) && (head) == (ptr)) \ + (head) = (head)->next; \ + if ((ptr)) { \ + if ((ptr)->next) \ + (ptr)->next->prev = (ptr)->prev; \ + if ((ptr)->prev) \ + (ptr)->prev->next = (ptr)->next; \ + (ptr)->prev = (ptr)->next = 0; \ + } \ + } while (0) + +#define DN_DoublyLLAppend(head, ptr) \ + do { \ + if ((ptr)) { \ + DN_AssertF((ptr)->prev == 0 && (ptr)->next == 0, "Detach the pointer first"); \ + (ptr)->prev = (head); \ + (ptr)->next = 0; \ + if ((head)) { \ + (ptr)->next = (head)->next; \ + (head)->next = (ptr); \ + if ((ptr)->next) \ + (ptr)->next->prev = (ptr); \ + } else { \ + (head) = (ptr); \ + } \ + } \ + } while (0) + +#define DN_DoublyLLPrepend(head, ptr) \ + do { \ + if ((ptr)) { \ + DN_AssertF((ptr)->prev == 0 && (ptr)->next == 0, "Detach the pointer first"); \ + (ptr)->prev = nullptr; \ + (ptr)->next = (head); \ + if ((head)) { \ + (ptr)->prev = (head)->prev; \ + (head)->prev = (ptr); \ + if ((ptr)->prev) \ + (ptr)->prev->next = (ptr); \ + } else { \ + (head) = (ptr); \ + } \ + } \ + } while (0) + +// NOTE: For C++ we need to cast the void* returned in these functions to the concrete type. In C, +// no cast is needed. +#if defined(__cplusplus) + #define DN_CppDeclType(x) decltype(x) +#else + #define DN_CppDeclType +#endif + +// NOTE: Arrays +// Data structures that have a `T *data`, `DN_USize count` and `DN_USize max` capacity that can be +// dynamically shrunk or expanded. +// +// API +// ResizeFrom: Resizes the array to `new_max` erase elements if resizing to a smaller size +// GrowFrom: Expands the capacity of the array if `new_max > array.max` otherwise no-op +// GrowIfNeeded: Expands the capacity of the array if `array.size + add_count > array.max` otherwise no-op +// +// Variants +// PArray => Pointer (to) Array +// LArray => Literal Array +// Define a C array and size. (P) array macros take a pointer to the aray, its size and its max +// capacity. The (L) array macros take the literal array and derives the max capacity +// automatically using DN_ArrayCountU(l_array). +// +// MyStruct buffer[TB_ASType_Count] = {}; +// DN_USize size = 0; +// MyStruct *item_0 = DN_PArrayMake(buffer, &size, DN_ArrayCountU(buffer), DN_ZMem_No); +// MyStruct *item_1 = DN_LArrayMake(buffer, &size, DN_ZMem_No); +// +// IArray => Intrusive Array +// Define a struct with the members `data`, `count` and `max`: +// +// struct MyArray { +// MyStruct *data; +// DN_USize count; +// DN_USize max; +// } my_array = {}; +// DN_Arena arena = {}; +// DN_IArrayResizeFromArena(&my_array, &arena, 256); +// MyStruct *item = DN_IArrayMake(&my_array, DN_ZMem_No); +// +#if defined(__cplusplus) + #define DN_PArrayFind(ptr, size, ptr_find, eq_func) DN_TArrayFind(ptr, size, ptr_find, eq_func) + #define DN_PArrayFindMemEq(ptr, size, ptr_find) DN_TArrayFindMemEq(ptr, size, ptr_find) + #define DN_PArrayResizeFromPool(ptr, ptr_size, ptr_max, pool, new_max) DN_TArrayResizeFromPool(&(ptr), ptr_size, ptr_max, pool, new_max) + #define DN_PArrayResizeFromArena(ptr, ptr_size, ptr_max, arena, new_max) DN_TArrayResizeFromArena(&(ptr), ptr_size, ptr_max, arena, new_max) + #define DN_PArrayGrowFromPool(ptr, size, ptr_max, pool, new_max) DN_TArrayGrowFromPool(&(ptr), size, ptr_max, pool, new_max) + #define DN_PArrayGrowFromArena(ptr, size, ptr_max, arena, new_max) DN_TArrayGrowFromArena(&(ptr), size, ptr_max, arena, new_max) + #define DN_PArrayGrowIfNeededFromPool(ptr, size, ptr_max, pool, add_count) DN_TArrayGrowIfNeededFromPool(ptr, size, ptr_max, pool, add_count) + #define DN_PArrayGrowIfNeededFromArena(ptr, size, ptr_max, arena, add_count) DN_TArrayGrowIfNeededFromArena(ptr, size, ptr_max, arena, add_count) + + #define DN_PArrayMakeArray(ptr, ptr_size, max, count, z_mem) DN_TArrayMakeArray(ptr, ptr_size, max, count, z_mem) + #define DN_PArrayMakeArrayZ(ptr, ptr_size, max, count) DN_TArrayMakeArray(ptr, ptr_size, max, count, DN_ZMem_Yes) + #define DN_PArrayMakeArrayNoZ(ptr, ptr_size, max, count) DN_TArrayMakeArray(ptr, ptr_size, max, count, DN_ZMem_No) + #define DN_PArrayMakeArrayAssert(ptr, ptr_size, max, count, z_mem) DN_TArrayMakeArrayAssert(ptr, ptr_size, max, count, z_mem, DN_CallSiteNow) + #define DN_PArrayMakeArrayAssertZ(ptr, ptr_size, max, count) DN_TArrayMakeArrayAssert(ptr, ptr_size, max, count, DN_ZMem_Yes, DN_CallSiteNow) + #define DN_PArrayMakeArrayAssertNoZ(ptr, ptr_size, max, count) DN_TArrayMakeArrayAssert(ptr, ptr_size, max, count, DN_ZMem_No, DN_CallSiteNow) + + #define DN_PArrayMake(ptr, ptr_size, max, z_mem) DN_TArrayMakeArray(ptr, ptr_size, max, 1, z_mem) + #define DN_PArrayMakeZ(ptr, ptr_size, max) DN_TArrayMakeArray(ptr, ptr_size, max, 1, DN_ZMem_Yes) + #define DN_PArrayMakeNoZ(ptr, ptr_size, max) DN_TArrayMakeArray(ptr, ptr_size, max, 1, DN_ZMem_No) + #define DN_PArrayMakeAssert(ptr, ptr_size, max, z_mem) DN_TArrayMakeArrayAssert(ptr, ptr_size, max, 1, z_mem, DN_CallSiteNow) + #define DN_PArrayMakeAssertZ(ptr, ptr_size, max) DN_TArrayMakeArrayAssert(ptr, ptr_size, max, 1, DN_ZMem_Yes, DN_CallSiteNow) + #define DN_PArrayMakeAssertNoZ(ptr, ptr_size, max) DN_TArrayMakeArrayAssert(ptr, ptr_size, max, 1, DN_ZMem_No, DN_CallSiteNow) + + #define DN_PArrayAddArray(ptr, ptr_size, max, items, count, add) DN_TArrayAddArray(ptr, ptr_size, max, items, count, add) + #define DN_PArrayAdd(ptr, ptr_size, max, item, add) DN_TArrayAddArray(ptr, ptr_size, max, &item, 1, add) + #define DN_PArrayAppendArray(ptr, ptr_size, max, items, count) DN_TArrayAddArray(ptr, ptr_size, max, items, count, DN_ArrayAdd_Append) + #define DN_PArrayAppend(ptr, ptr_size, max, item) DN_TArrayAddArray(ptr, ptr_size, max, &item, 1, DN_ArrayAdd_Append) + #define DN_PArrayAppendAssert(ptr, ptr_size, max, item) DN_TArrayAddArrayAssert(ptr, ptr_size, max, &item, 1, DN_ArrayAdd_Append, DN_CallSiteNow) + #define DN_PArrayPrependArray(ptr, ptr_size, max, items, count) DN_TArrayAddArray(ptr, ptr_size, max, items, count, DN_ArrayAdd_Prepend) + #define DN_PArrayPrepend(ptr, ptr_size, max, item) DN_TArrayAddArray(ptr, ptr_size, max, &item, 1, DN_ArrayAdd_Prepend) + + #define DN_PArrayEraseRange(ptr, ptr_size, begin_index, count, erase) DN_TArrayEraseRange(ptr, ptr_size, begin_index, count, erase) + #define DN_PArrayErase(ptr, ptr_size, index, erase) DN_TArrayEraseRange(ptr, ptr_size, index, 1, erase) + #define DN_PArrayInsertArray(ptr, ptr_size, max, index, items, count) DN_TArrayInsertArray(ptr, ptr_size, max, index, items, count) + #define DN_PArrayInsert(ptr, ptr_size, max, index, item) DN_TArrayInsertArray(ptr, ptr_size, max, index, &item, 1) + #define DN_PArrayPopFront(ptr, ptr_size, max, count) DN_TArrayPopFront(ptr, ptr_size, count) + #define DN_PArrayPopBack(ptr, ptr_size, max, count) DN_TArrayPopBack(ptr, ptr_size, count) +#else + #define DN_PArrayFind(ptr, size, ptr_find, eq_func) DN_ArrayFind(ptr, size, sizeof(*(ptr)), ptr_find, eq_func) + #define DN_PArrayFindMemEq(ptr, size, ptr_find) DN_ArrayFindMemEq(ptr, size, sizeof(*(ptr)), ptr_find) + #define DN_PArrayResizeFromPool(ptr, ptr_size, ptr_max, pool, new_max) DN_ArrayResizeFromPool((void **)&(ptr), ptr_size, ptr_max, sizeof((ptr)[0]), pool, new_max) + #define DN_PArrayResizeFromArena(ptr, ptr_size, ptr_max, arena, new_max) DN_ArrayResizeFromArena((void **)&(ptr), ptr_size, ptr_max, sizeof((ptr)[0]), arena, new_max) + #define DN_PArrayGrowFromPool(ptr, size, ptr_max, pool, new_max) DN_ArrayGrowFromPool((void **)&(ptr), size, ptr_max, sizeof((ptr)[0]), pool, new_max) + #define DN_PArrayGrowFromArena(ptr, size, ptr_max, arena, new_max) DN_ArrayGrowFromArena((void **)&(ptr), size, ptr_max, sizeof((ptr)[0]), arena, new_max) + #define DN_PArrayGrowIfNeededFromPool(ptr, size, ptr_max, pool, add_count) DN_ArrayGrowIfNeededFromPool((void **)(ptr), size, ptr_max, sizeof((*ptr)[0]), pool, add_count) + #define DN_PArrayGrowIfNeededFromArena(ptr, size, ptr_max, arena, add_count) DN_ArrayGrowIfNeededFromArena((void **)(ptr), size, ptr_max, sizeof((*ptr)[0]), arena, add_count) + + #define DN_PArrayMakeArray(ptr, ptr_size, max, count, z_mem) (DN_CppDeclType(&(ptr)[0]))DN_ArrayMakeArray(ptr, ptr_size, max, sizeof((ptr)[0]), count, z_mem) + #define DN_PArrayMakeArrayZ(ptr, ptr_size, max, count) (DN_CppDeclType(&(ptr)[0]))DN_ArrayMakeArray(ptr, ptr_size, max, sizeof((ptr)[0]), count, DN_ZMem_Yes) + #define DN_PArrayMakeArrayNoZ(ptr, ptr_size, max, count) (DN_CppDeclType(&(ptr)[0]))DN_ArrayMakeArray(ptr, ptr_size, max, sizeof((ptr)[0]), count, DN_ZMem_No) + #define DN_PArrayMakeArrayAssert(ptr, ptr_size, max, count, z_mem) (DN_CppDeclType(&(ptr)[0]))DN_ArrayMakeArrayAssert(ptr, ptr_size, max, sizeof((ptr)[0]), count, z_mem, DN_CallSiteNow) + #define DN_PArrayMakeArrayAssertZ(ptr, ptr_size, max, count) (DN_CppDeclType(&(ptr)[0]))DN_ArrayMakeArrayAssert(ptr, ptr_size, max, sizeof((ptr)[0]), count, DN_ZMem_Yes, DN_CallSiteNow) + #define DN_PArrayMakeArrayAssertNoZ(ptr, ptr_size, max, count) (DN_CppDeclType(&(ptr)[0]))DN_ArrayMakeArrayAssert(ptr, ptr_size, max, sizeof((ptr)[0]), count, DN_ZMem_No, DN_CallSiteNow) + + #define DN_PArrayMake(ptr, ptr_size, max, z_mem) (DN_CppDeclType(&(ptr)[0]))DN_ArrayMakeArray(ptr, ptr_size, max, sizeof((ptr)[0]), 1, z_mem) + #define DN_PArrayMakeZ(ptr, ptr_size, max) (DN_CppDeclType(&(ptr)[0]))DN_ArrayMakeArray(ptr, ptr_size, max, sizeof((ptr)[0]), 1, DN_ZMem_Yes) + #define DN_PArrayMakeNoZ(ptr, ptr_size, max) (DN_CppDeclType(&(ptr)[0]))DN_ArrayMakeArray(ptr, ptr_size, max, sizeof((ptr)[0]), 1, DN_ZMem_No) + #define DN_PArrayMakeAssert(ptr, ptr_size, max, z_mem) (DN_CppDeclType(&(ptr)[0]))DN_ArrayMakeArrayAssert(ptr, ptr_size, max, sizeof((ptr)[0]), 1, z_mem, DN_CallSiteNow) + #define DN_PArrayMakeAssertZ(ptr, ptr_size, max) (DN_CppDeclType(&(ptr)[0]))DN_ArrayMakeArrayAssert(ptr, ptr_size, max, sizeof((ptr)[0]), 1, DN_ZMem_Yes, DN_CallSiteNow) + #define DN_PArrayMakeAssertNoZ(ptr, ptr_size, max) (DN_CppDeclType(&(ptr)[0]))DN_ArrayMakeArrayAssert(ptr, ptr_size, max, sizeof((ptr)[0]), 1, DN_ZMem_No, DN_CallSiteNow) + + #define DN_PArrayAddArray(ptr, ptr_size, max, items, count, add) (DN_CppDeclType(&(ptr)[0]))DN_ArrayAddArray(ptr, ptr_size, max, sizeof((ptr)[0]), items, count, add) + #define DN_PArrayAdd(ptr, ptr_size, max, item, add) (DN_CppDeclType(&(ptr)[0]))DN_ArrayAddArray(ptr, ptr_size, max, sizeof((ptr)[0]), &item, 1, add) + #define DN_PArrayAppendArray(ptr, ptr_size, max, items, count) (DN_CppDeclType(&(ptr)[0]))DN_ArrayAddArray(ptr, ptr_size, max, sizeof((ptr)[0]), items, count, DN_ArrayAdd_Append) + #define DN_PArrayAppend(ptr, ptr_size, max, item) (DN_CppDeclType(&(ptr)[0]))DN_ArrayAddArray(ptr, ptr_size, max, sizeof((ptr)[0]), &item, 1, DN_ArrayAdd_Append) + #define DN_PArrayAppendAssert(ptr, ptr_size, max, item) (DN_CppDeclType(&(ptr)[0]))DN_ArrayAddArrayAssert(ptr, ptr_size, max, sizeof((ptr)[0]), &item, 1, DN_ArrayAdd_Append, DN_CallSiteNow) + #define DN_PArrayPrependArray(ptr, ptr_size, max, items, count) (DN_CppDeclType(&(ptr)[0]))DN_ArrayAddArray(ptr, ptr_size, max, sizeof((ptr)[0]), items, count, DN_ArrayAdd_Prepend) + #define DN_PArrayPrepend(ptr, ptr_size, max, item) (DN_CppDeclType(&(ptr)[0]))DN_ArrayAddArray(ptr, ptr_size, max, sizeof((ptr)[0]), &item, 1, DN_ArrayAdd_Prepend) + + #define DN_PArrayEraseRange(ptr, ptr_size, begin_index, count, erase) DN_ArrayEraseRange(ptr, ptr_size, sizeof((ptr)[0]), begin_index, count, erase) + #define DN_PArrayErase(ptr, ptr_size, index, erase) DN_ArrayEraseRange(ptr, ptr_size, sizeof((ptr)[0]), index, 1, erase) + #define DN_PArrayInsertArray(ptr, ptr_size, max, index, items, count) (DN_CppDeclType(&(ptr)[0]))DN_ArrayInsertArray(ptr, ptr_size, max, sizeof((ptr)[0]), index, items, count) + #define DN_PArrayInsert(ptr, ptr_size, max, index, item) (DN_CppDeclType(&(ptr)[0]))DN_ArrayInsertArray(ptr, ptr_size, max, sizeof((ptr)[0]), index, &item, 1) + #define DN_PArrayPopFront(ptr, ptr_size, max, count) (DN_CppDeclType(&(ptr)[0]))DN_ArrayPopFront(ptr, ptr_size, sizeof((ptr)[0]), count) + #define DN_PArrayPopBack(ptr, ptr_size, max, count) (DN_CppDeclType(&(ptr)[0]))DN_ArrayPopBack(ptr, ptr_size, sizeof((ptr)[0]), count) +#endif + +#define DN_LArrayFind(c_array, size, ptr_find, eq_func) DN_PArrayFind(c_array, size, ptr_find, eq_func) +#define DN_LArrayFindMemEq(c_array, size, ptr_find) DN_PArrayFindMemEq(c_array, size, ptr_find) +#define DN_LArrayResizeFromPool(c_array, size, pool, new_max) DN_PArrayResizeFromPool(c_array, size, DN_ArrayCountU(c_array), pool, new_max) +#define DN_LArrayResizeFromArena(c_array, size, arena, new_max) DN_PArrayResizeFromArena(c_array, size, DN_ArrayCountU(c_array), arena, new_max) +#define DN_LArrayGrowFromPool(c_array, size, pool, new_max) DN_PArrayGrowFromPool(c_array, size, DN_ArrayCountU(c_array), pool, new_max) +#define DN_LArrayGrowFromArena(c_array, size, arena, new_max) DN_PArrayGrowFromArena(c_array, size, DN_ArrayCountU(c_array), arena, new_max) +#define DN_LArrayGrowIfNeededFromPool(c_array, size, pool, add_count) DN_PArrayGrowIfNeededFromPool(c_array, size, DN_ArrayCountU(c_array), pool, add_count) +#define DN_LArrayGrowIfNeededFromArena(c_array, size, arena, add_count) DN_PArrayGrowIfNeededFromArena(c_array, size, DN_ArrayCountU(c_array), arena, add_count) + +#define DN_LArrayMakeArray(c_array, ptr_size, count, z_mem) DN_PArrayMakeArray(c_array, ptr_size, DN_ArrayCountU(c_array), count, z_mem) +#define DN_LArrayMakeArrayZ(c_array, ptr_size, count) DN_PArrayMakeArrayZ(c_array, ptr_size, DN_ArrayCountU(c_array), count) +#define DN_LArrayMakeArrayNoZ(c_array, ptr_size, count) DN_PArrayMakeArrayNoZ(c_array, ptr_size, DN_ArrayCountU(c_array), count) +#define DN_LArrayMakeArrayAssert(c_array, ptr_size, count, z_mem) DN_PArrayMakeArrayAssert(c_array, ptr_size, DN_ArrayCountU(c_array), count, z_mem) +#define DN_LArrayMakeArrayAssertZ(c_array, ptr_size, count) DN_PArrayMakeArrayAssertZ(c_array, ptr_size, DN_ArrayCountU(c_array), count) +#define DN_LArrayMakeArrayAssertNoZ(c_array, ptr_size, count) DN_PArrayMakeArrayAssertNoZ(c_array, ptr_size, DN_ArrayCountU(c_array), count) + +#define DN_LArrayMake(c_array, ptr_size, z_mem) DN_PArrayMake(c_array, ptr_size, DN_ArrayCountU(c_array), z_mem) +#define DN_LArrayMakeZ(c_array, ptr_size) DN_PArrayMakeZ(c_array, ptr_size, DN_ArrayCountU(c_array)) +#define DN_LArrayMakeNoZ(c_array, ptr_size) DN_PArrayMakeNoZ(c_array, ptr_size, DN_ArrayCountU(c_array)) +#define DN_LArrayMakeAssert(c_array, ptr_size, z_mem) DN_PArrayMakeAssert(c_array, ptr_size, DN_ArrayCountU(c_array), z_mem) +#define DN_LArrayMakeAssertZ(c_array, ptr_size) DN_PArrayMakeAssertZ(c_array, ptr_size, DN_ArrayCountU(c_array)) +#define DN_LArrayMakeAssertNoZ(c_array, ptr_size) DN_PArrayMakeAssertNoZ(c_array, ptr_size, DN_ArrayCountU(c_array)) + +#define DN_LArrayAddArray(c_array, ptr_size, items, count, add) DN_PArrayAddArray(c_array, ptr_size, DN_ArrayCountU(c_array), items, count, add) +#define DN_LArrayAdd(c_array, ptr_size, item, add) DN_PArrayAdd(c_array, ptr_size, DN_ArrayCountU(c_array), item, add) +#define DN_LArrayAppendArray(c_array, ptr_size, items, count) DN_PArrayAppendArray(c_array, ptr_size, DN_ArrayCountU(c_array), items, count) +#define DN_LArrayAppend(c_array, ptr_size, item) DN_PArrayAppend(c_array, ptr_size, DN_ArrayCountU(c_array), item) +#define DN_LArrayAppendAssert(c_array, ptr_size, item) DN_PArrayAppendAssert(c_array, ptr_size, DN_ArrayCountU(c_array), item) +#define DN_LArrayPrependArray(c_array, ptr_size, items, count) DN_PArrayPrependArray(c_array, ptr_size, DN_ArrayCountU(c_array), items, count) +#define DN_LArrayPrepend(c_array, ptr_size, item) DN_PArrayPrepend(c_array, ptr_size, DN_ArrayCountU(c_array), item) +#define DN_LArrayEraseRange(c_array, ptr_size, begin_index, count, erase) DN_PArrayEraseRange(c_array, ptr_size, begin_index, count, erase) +#define DN_LArrayErase(c_array, ptr_size, index, erase) DN_PArrayErase(c_array, ptr_size, index, erase) +#define DN_LArrayInsertArray(c_array, ptr_size, index, items, count) DN_PArrayInsertArray(c_array, ptr_size, DN_ArrayCountU(c_array), index, items, count) +#define DN_LArrayInsert(c_array, ptr_size, index, item) DN_PArrayInsert(c_array, ptr_size, DN_ArrayCountU(c_array), index, item) +#define DN_LArrayPopFront(c_array, ptr_size, count) DN_PArrayPopFront(c_array, ptr_size, DN_ArrayCountU(c_array), count) +#define DN_LArrayPopBack(c_array, ptr_size, count) DN_PArrayPopBack(c_array, ptr_size, DN_ArrayCountU(c_array), count) + +#define DN_IArrayFind(ptr_array, ptr_find, eq_func) DN_PArrayFind((ptr_array)->data, (ptr_array)->count, ptr_find, eq_func) +#define DN_IArrayFindMemEq(ptr_array, ptr_find) DN_PArrayFindMemEq((ptr_array)->data, (ptr_array)->count, ptr_find) +#define DN_IArrayResizeFromPool(ptr_array, pool, new_max) DN_PArrayResizeFromPool((ptr_array)->data, &(ptr_array)->count, &(ptr_array)->max, pool, new_max) +#define DN_IArrayResizeFromArena(ptr_array, arena, new_max) DN_PArrayResizeFromArena((ptr_array)->data, &(ptr_array)->count, &(ptr_array)->max, arena, new_max) +#define DN_IArrayGrowFromPool(ptr_array, pool, new_max) DN_PArrayGrowFromPool((ptr_array)->data, (ptr_array)->count, &(ptr_array)->max, pool, new_max) +#define DN_IArrayGrowFromArena(ptr_array, arena, new_max) DN_PArrayGrowFromArena((ptr_array)->data, (ptr_array)->count, &(ptr_array)->max, arena, new_max) +#define DN_IArrayGrowIfNeededFromPool(ptr_array, pool, add_count) DN_PArrayGrowIfNeededFromPool(&(ptr_array)->data, (ptr_array)->count, &(ptr_array)->max, pool, add_count) +#define DN_IArrayGrowIfNeededFromArena(ptr_array, arena, add_count) DN_PArrayGrowIfNeededFromArena(&(ptr_array)->data, (ptr_array)->count, &(ptr_array)->max, arena, add_count) + +#define DN_IArrayMakeArray(ptr_array, count, z_mem) DN_PArrayMakeArray((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count, z_mem) +#define DN_IArrayMakeArrayZ(ptr_array, count) DN_PArrayMakeArrayZ((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count) +#define DN_IArrayMakeArrayNoZ(ptr_array, count) DN_PArrayMakeArrayNoZ((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count) +#define DN_IArrayMakeArrayAssert(ptr_array, count, z_mem) DN_PArrayMakeArrayAssert((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count, z_mem) +#define DN_IArrayMakeArrayAssertZ(ptr_array, count) DN_PArrayMakeArrayAssertZ((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count) +#define DN_IArrayMakeArrayAssertNoZ(ptr_array, count) DN_PArrayMakeArrayAssertNoZ((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count) + +#define DN_IArrayMake(ptr_array, z_mem) DN_PArrayMake((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, z_mem) +#define DN_IArrayMakeZ(ptr_array) DN_PArrayMakeZ((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max) +#define DN_IArrayMakeNoZ(ptr_array) DN_PArrayMakeNoZ((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max) +#define DN_IArrayMakeAssert(ptr_array, z_mem) DN_PArrayMakeAssert((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, z_mem) +#define DN_IArrayMakeAssertZ(ptr_array) DN_PArrayMakeAssertZ((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max) +#define DN_IArrayMakeAssertNoZ(ptr_array) DN_PArrayMakeAssertNoZ((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max) + +#define DN_IArrayAddArray(ptr_array, items, count, add) DN_PArrayAddArray((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, items, count, add) +#define DN_IArrayAdd(ptr_array, item, add) DN_PArrayAdd((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, item, add) +#define DN_IArrayAppendArray(ptr_array, items, count) DN_PArrayAppendArray((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, items, count) +#define DN_IArrayAppend(ptr_array, item) DN_PArrayAppend((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, item) +#define DN_IArrayAppendAssert(ptr_array, item) DN_PArrayAppendAssert((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, item) +#define DN_IArrayPrependArray(ptr_array, items, count) DN_PArrayPrependArray((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, items, count) +#define DN_IArrayPrepend(ptr_array, item) DN_PArrayPrepend((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, item) +#define DN_IArrayEraseRange(ptr_array, begin_index, count, erase) DN_PArrayEraseRange((ptr_array)->data, &(ptr_array)->count, begin_index, count, erase) +#define DN_IArrayErase(ptr_array, index, erase) DN_PArrayErase((ptr_array)->data, &(ptr_array)->count, index, erase) +#define DN_IArrayInsertArray(ptr_array, index, items, count) DN_PArrayInsertArray((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, index, items, count) +#define DN_IArrayInsert(ptr_array, index, item) DN_PArrayInsert((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, index, item) +#define DN_IArrayPopFront(ptr_array, count) DN_PArrayPopFront((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count) +#define DN_IArrayPopBack(ptr_array, count) DN_PArrayPopBack((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count) + +// NOTE: Slices +// +// Fixed size container allocated up front that have a `T *data` and `DN_USize count` elements. +// +// API +// AllocArena: Allocates the container with the requested `count` elements +// +#define DN_ISliceAllocArena(slice_ptr, count_, zmem, arena) (DN_CppDeclType(&((slice_ptr)->data[0])))DN_SliceAllocArena((void **)&((slice_ptr)->data), &((slice_ptr)->count), count_, sizeof((slice_ptr)->data[0]), alignof(DN_CppDeclType((slice_ptr)->data[0])), zmem, arena) + + +DN_API void* DN_SliceAllocArena (void **data, DN_USize *slice_size_field, DN_USize size, DN_USize elem_size, DN_U8 align, DN_ZMem zmem, DN_Arena *arena); + +DN_API DN_ArrayFindResult DN_ArrayFind (void *data, DN_USize size, DN_USize elem_size, void const *find, DN_ArrayFindEqFunc *eq_func); +DN_API DN_ArrayFindResult DN_ArrayFindMemEq (void *data, DN_USize size, DN_USize elem_size, void const *find); +DN_API void* DN_ArrayInsertArray (void *data, DN_USize *size, DN_USize max, DN_USize elem_size, DN_USize index, void const *items, DN_USize count); +DN_API void* DN_ArrayPopFront (void *data, DN_USize *size, DN_USize elem_size, DN_USize count); +DN_API void* DN_ArrayPopBack (void *data, DN_USize *size, DN_USize elem_size, DN_USize count); +DN_API DN_ArrayEraseResult DN_ArrayEraseRange (void *data, DN_USize *size, DN_USize elem_size, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase); +DN_API void* DN_ArrayMakeArray (void *data, DN_USize *size, DN_USize max, DN_USize elem_size, DN_USize make_count, DN_ZMem z_mem); +DN_API void* DN_ArrayMakeArrayAssert (void *data, DN_USize *size, DN_USize max, DN_USize elem_size, DN_USize make_count, DN_ZMem z_mem, DN_CallSite call_site); +DN_API void* DN_ArrayAddArray (void *data, DN_USize *size, DN_USize max, DN_USize elem_size, void const *elems, DN_USize elems_count, DN_ArrayAdd add); +DN_API void* DN_ArrayAddArrayAssert (void *data, DN_USize *size, DN_USize max, DN_USize elem_size, void const *elems, DN_USize elems_count, DN_ArrayAdd add, DN_CallSite call_site); +DN_API bool DN_ArrayResizeFromPool (void **data, DN_USize *size, DN_USize *max, DN_USize elem_size, DN_Pool *pool, DN_USize new_max); +DN_API bool DN_ArrayResizeFromArena (void **data, DN_USize *size, DN_USize *max, DN_USize elem_size, DN_Arena *arena, DN_USize new_max); +DN_API bool DN_ArrayGrowFromPool (void **data, DN_USize size, DN_USize *max, DN_USize elem_size, DN_Pool *pool, DN_USize new_max); +DN_API bool DN_ArrayGrowFromArena (void **data, DN_USize size, DN_USize *max, DN_USize elem_size, DN_Arena *arena, DN_USize new_max); +DN_API bool DN_ArrayGrowIfNeededFromPool (void **data, DN_USize size, DN_USize *max, DN_USize elem_size, DN_Pool *pool, DN_USize add_count); +DN_API bool DN_ArrayGrowIfNeededFromArena (void **data, DN_USize size, DN_USize *max, DN_USize elem_size, DN_Arena *arena, DN_USize add_count); + +#if defined (__cplusplus) +template DN_ArrayFindResult DN_TArrayFind (T *data, DN_USize size, void const *find, DN_ArrayFindEqFunc *eq_func); +template DN_ArrayFindResult DN_TArrayFindMemEq (T *data, DN_USize size, void const *find, DN_ArrayFindEqFunc *eq_func); +template T* DN_TArrayInsertArray (T *data, DN_USize *size, DN_USize max, DN_USize index, void const *items, DN_USize count); +template T* DN_TArrayPopFront (T *data, DN_USize *size, DN_USize count); +template T* DN_TArrayPopBack (T *data, DN_USize *size, DN_USize count); +template DN_ArrayEraseResult DN_TArrayEraseRange (T *data, DN_USize *size, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase); +template T* DN_TArrayMakeArray (T *data, DN_USize *size, DN_USize max, DN_USize make_count, DN_ZMem z_mem); +template T* DN_TArrayMakeArrayAssert (T *data, DN_USize *size, DN_USize max, DN_USize make_count, DN_ZMem z_mem, DN_CallSite call_site); +template T* DN_TArrayMakeArrayAssertZ (T *data, DN_USize *size, DN_USize max, DN_USize make_count, DN_CallSite call_site); +template T* DN_TArrayMakeArrayAssertNoZ (T *data, DN_USize *size, DN_USize max, DN_USize make_count, DN_CallSite call_site); +template T* DN_TArrayAddArray (T *data, DN_USize *size, DN_USize max, T const *elems, DN_USize elems_count, DN_ArrayAdd add); +template T* DN_TArrayAddArrayAssert (T *data, DN_USize *size, DN_USize max, T const *elems, DN_USize elems_count, DN_ArrayAdd add, DN_CallSite call_site); +template bool DN_TArrayResizeFromPool (T **data, DN_USize *size, DN_USize *max, DN_Pool *pool, DN_USize new_max); +template bool DN_TArrayResizeFromArena (T **data, DN_USize *size, DN_USize *max, DN_Arena *arena, DN_USize new_max); +template bool DN_TArrayGrowFromPool (T **data, DN_USize size, DN_USize *max, DN_Pool *pool, DN_USize new_max); +template bool DN_TArrayGrowFromArena (T **data, DN_USize size, DN_USize *max, DN_Pool *pool, DN_USize new_max); +template bool DN_TArrayGrowIfNeededFromPool (T **data, DN_USize size, DN_USize *max, DN_Pool *pool, DN_USize add_count); +template bool DN_TArrayGrowIfNeededFromArena (T **data, DN_USize size, DN_USize *max, DN_Arena *pool, DN_USize add_count); +#endif + +DN_API void* DN_SinglyLLDetach (void **link, void **next); +DN_API bool DN_RingHasSpace (DN_Ring const *ring, DN_U64 size); +DN_API bool DN_RingHasData (DN_Ring const *ring, DN_U64 size); +DN_API void DN_RingWrite (DN_Ring *ring, void const *src, DN_U64 src_size); +#define DN_RingWriteStruct(ring, item) DN_RingWrite((ring), (item), sizeof(*(item))) +DN_API void DN_RingRead (DN_Ring *ring, void *dest, DN_U64 dest_size); +#define DN_RingReadStruct(ring, dest) DN_RingRead((ring), (dest), sizeof(*(dest))) + +// TODO: Replace with a C-style hash table +#if defined(__cplusplus) +DN_U32 const DN_DS_MAP_DEFAULT_HASH_SEED = 0x8a1ced49; +DN_U32 const DN_DS_MAP_SENTINEL_SLOT = 0; +template DN_DSMap DN_DSMapInit (DN_Arena *arena, DN_U32 size, DN_DSMapFlags flags); +template void DN_DSMapDeinit (DN_DSMap *map, DN_ZMem z_mem); +template bool DN_DSMapIsValid (DN_DSMap const *map); +template DN_U32 DN_DSMapHash (DN_DSMap const *map, DN_DSMapKey key); +template DN_U32 DN_DSMapHashToSlotIndex (DN_DSMap const *map, DN_DSMapKey key); +template DN_DSMapResult DN_DSMapFind (DN_DSMap const *map, DN_DSMapKey key); +template DN_DSMapResult DN_DSMapMake (DN_DSMap *map, DN_DSMapKey key); +template DN_DSMapResult DN_DSMapSet (DN_DSMap *map, DN_DSMapKey key, T const &value); +template DN_DSMapResult DN_DSMapFindKeyU64 (DN_DSMap const *map, DN_U64 key); +template DN_DSMapResult DN_DSMapMakeKeyU64 (DN_DSMap *map, DN_U64 key); +template DN_DSMapResult DN_DSMapSetKeyU64 (DN_DSMap *map, DN_U64 key, T const &value); +template DN_DSMapResult DN_DSMapFindKeyStr8 (DN_DSMap const *map, DN_Str8 key); +template DN_DSMapResult DN_DSMapMakeKeyStr8 (DN_DSMap *map, DN_Str8 key); +template DN_DSMapResult DN_DSMapSetKeyStr8 (DN_DSMap *map, DN_Str8 key, T const &value); +template bool DN_DSMapResize (DN_DSMap *map, DN_U32 size); +template bool DN_DSMapErase (DN_DSMap *map, DN_DSMapKey key); +template bool DN_DSMapEraseKeyU64 (DN_DSMap *map, DN_U64 key); +template bool DN_DSMapEraseKeyStr8 (DN_DSMap *map, DN_Str8 key); +template DN_DSMapKey DN_DSMapKeyBuffer (DN_DSMap const *map, void const *data, DN_USize size); +template DN_DSMapKey DN_DSMapKeyBufferAsU64NoHash (DN_DSMap const *map, void const *data, DN_USize size); +template DN_DSMapKey DN_DSMapKeyU64 (DN_DSMap const *map, DN_U64 u64); +template DN_DSMapKey DN_DSMapKeyStr8 (DN_DSMap const *map, DN_Str8 string); +#define DN_DSMapKeyCStr8(map, string) DN_DSMapKeyBuffer(map, string, sizeof((string))/sizeof((string)[0]) - 1) +DN_API DN_DSMapKey DN_DSMapKeyU64NoHash (DN_U64 u64); +DN_API bool DN_DSMapKeyEquals (DN_DSMapKey lhs, DN_DSMapKey rhs); +DN_API bool operator== (DN_DSMapKey lhs, DN_DSMapKey rhs); +#endif + +enum DN_BinPackMode +{ + DN_BinPackMode_Serialise, + DN_BinPackMode_Deserialise, +}; + +struct DN_BinPack +{ + DN_Str8Builder writer; + DN_Str8 read; + DN_USize read_index; +}; + +DN_API bool DN_BinPackIsEndOfReadStream(DN_BinPack const *pack); +DN_API void DN_BinPackUSize (DN_BinPack *pack, DN_BinPackMode mode, DN_USize *item); +DN_API void DN_BinPackU64 (DN_BinPack *pack, DN_BinPackMode mode, DN_U64 *item); +DN_API void DN_BinPackU32 (DN_BinPack *pack, DN_BinPackMode mode, DN_U32 *item); +DN_API void DN_BinPackU16 (DN_BinPack *pack, DN_BinPackMode mode, DN_U16 *item); +DN_API void DN_BinPackU8 (DN_BinPack *pack, DN_BinPackMode mode, DN_U8 *item); +DN_API void DN_BinPackI64 (DN_BinPack *pack, DN_BinPackMode mode, DN_I64 *item); +DN_API void DN_BinPackI32 (DN_BinPack *pack, DN_BinPackMode mode, DN_I32 *item); +DN_API void DN_BinPackI16 (DN_BinPack *pack, DN_BinPackMode mode, DN_I16 *item); +DN_API void DN_BinPackI8 (DN_BinPack *pack, DN_BinPackMode mode, DN_I8 *item); +DN_API void DN_BinPackF64 (DN_BinPack *pack, DN_BinPackMode mode, DN_F64 *item); +DN_API void DN_BinPackF32 (DN_BinPack *pack, DN_BinPackMode mode, DN_F32 *item); +DN_API void DN_BinPackV2 (DN_BinPack *pack, DN_BinPackMode mode, DN_V2F32 *item); +DN_API void DN_BinPackV4 (DN_BinPack *pack, DN_BinPackMode mode, DN_V4F32 *item); +DN_API void DN_BinPackBool (DN_BinPack *pack, DN_BinPackMode mode, bool *item); +DN_API void DN_BinPackStr8FromArena (DN_BinPack *pack, DN_Arena *arena, DN_BinPackMode mode, DN_Str8 *string); +DN_API void DN_BinPackStr8FromPool (DN_BinPack *pack, DN_Pool *pool, DN_BinPackMode mode, DN_Str8 *string); +DN_API DN_Str8 DN_BinPackStr8FromBuffer (DN_BinPack *pack, DN_BinPackMode mode, char *ptr, DN_USize *size, DN_USize max); +DN_API void DN_BinPackBytesFromArena (DN_BinPack *pack, DN_Arena *arena, DN_BinPackMode mode, void **ptr, DN_USize *size); +DN_API void DN_BinPackBytesFromPool (DN_BinPack *pack, DN_Pool *pool, DN_BinPackMode mode, void **ptr, DN_USize *size); +DN_API void DN_BinPackCArray (DN_BinPack *pack, DN_BinPackMode mode, void *ptr, DN_USize size); +DN_API void DN_BinPackCBuffer (DN_BinPack *pack, DN_BinPackMode mode, char *ptr, DN_USize *size, DN_USize max); +DN_API DN_Str8 DN_BinPackBuild (DN_BinPack const *pack, DN_Arena *arena); + +enum DN_CSVSerialise +{ + DN_CSVSerialise_Read, + DN_CSVSerialise_Write, +}; + +struct DN_CSVTokeniser +{ + bool bad; + DN_Str8 string; + char delimiter; + char const *it; + bool end_of_line; +}; + +struct DN_CSVPack +{ + DN_Str8Builder write_builder; + DN_USize write_column; + DN_CSVTokeniser read_tokeniser; +}; + +// NOTE: Data structures to create and parse CSV files, supports Python style escaped quotes (e.g. +// Using "" to escape quotes inside a quoted string). +// +// API +// DN_CSVTokeniserNextN: Reads the next N consecutive fields from the parser. If `column_iterator` +// is `false` then the read of the N consecutive fields does not proceed past the end of the +// current CSV row. If `true` then it reads the next N fields even if reading would progress onto +// the next row. +DN_API DN_CSVTokeniser DN_CSVTokeniserInit (DN_Str8 string, char delimiter); +DN_API bool DN_CSVTokeniserValid (DN_CSVTokeniser *tokeniser); +DN_API bool DN_CSVTokeniserNextRow (DN_CSVTokeniser *tokeniser); +DN_API DN_Str8 DN_CSVTokeniserNextField (DN_CSVTokeniser *tokeniser); +DN_API DN_Str8 DN_CSVTokeniserNextColumn (DN_CSVTokeniser *tokeniser); +DN_API void DN_CSVTokeniserSkipLine (DN_CSVTokeniser *tokeniser); +DN_API int DN_CSVTokeniserNextN (DN_CSVTokeniser *tokeniser, DN_Str8 *fields, int fields_size, bool column_iterator); +DN_API int DN_CSVTokeniserNextColumnN(DN_CSVTokeniser *tokeniser, DN_Str8 *fields, int fields_size); +DN_API int DN_CSVTokeniserNextFieldN (DN_CSVTokeniser *tokeniser, DN_Str8 *fields, int fields_size); +DN_API void DN_CSVTokeniserSkipLineN (DN_CSVTokeniser *tokeniser, int count); +DN_API void DN_CSVPackU64 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_U64 *value); +DN_API void DN_CSVPackI64 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I64 *value); +DN_API void DN_CSVPackI32 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I32 *value); +DN_API void DN_CSVPackI16 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I16 *value); +DN_API void DN_CSVPackI8 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I8 *value); +DN_API void DN_CSVPackU32 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_U32 *value); +DN_API void DN_CSVPackU16 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_U16 *value); +DN_API void DN_CSVPackBoolAsU64 (DN_CSVPack *pack, DN_CSVSerialise serialise, bool *value); +DN_API void DN_CSVPackStr8 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_Str8 *str8, DN_Arena *arena); +DN_API void DN_CSVPackBuffer (DN_CSVPack *pack, DN_CSVSerialise serialise, void *dest, size_t *size); +DN_API void DN_CSVPackBufferWithMax (DN_CSVPack *pack, DN_CSVSerialise serialise, void *dest, size_t *size, size_t max); +DN_API bool DN_CSVPackNewLine (DN_CSVPack *pack, DN_CSVSerialise serialise); + +// TODO: Replace with a C implementation +template +using DN_BinarySearchLessThanProc = bool(T const &lhs, T const &rhs); + +template +bool DN_BinarySearch_DefaultLessThan(T const &lhs, T const &rhs); + +enum DN_BinarySearchType +{ + // Index of the match. If no match is found, found is set to false and the + // index is set to the index where the match should be inserted/exist, if + // it were in the array + DN_BinarySearchType_Match, + + // Index of the first element in the array that is `element >= find`. If no such + // item is found or the array is empty, then, the index is set to the array + // size and found is set to `false`. + // + // For example: + // int array[] = {0, 1, 2, 3, 4, 5}; + // DN_BinarySearchResult result = DN_BinarySearch(array, DN_ArrayCountU(array), 4, DN_BinarySearchType_LowerBound); + // printf("%zu\n", result.index); // Prints index '4' + + DN_BinarySearchType_LowerBound, + + // Index of the first element in the array that is `element > find`. If no such + // item is found or the array is empty, then, the index is set to the array + // size and found is set to `false`. + // + // For example: + // int array[] = {0, 1, 2, 3, 4, 5}; + // DN_BinarySearchResult result = DN_BinarySearch(array, DN_ArrayCountU(array), 4, DN_BinarySearchType_UpperBound); + // printf("%zu\n", result.index); // Prints index '5' + + DN_BinarySearchType_UpperBound, +}; + +struct DN_BinarySearchResult +{ + bool found; + DN_USize index; +}; + +template bool DN_BinarySearch_DefaultLessThan(T const &lhs, T const &rhs); +template DN_BinarySearchResult DN_BinarySearch (T const *array, DN_USize array_size, T const &find, DN_BinarySearchType type = DN_BinarySearchType_Match, DN_BinarySearchLessThanProc less_than = DN_BinarySearch_DefaultLessThan); + +template +bool DN_BinarySearch_DefaultLessThan(T const &lhs, T const &rhs) +{ + bool result = lhs < rhs; + return result; +} + +template +DN_BinarySearchResult DN_BinarySearch(T const *array, + DN_USize array_size, + T const &find, + DN_BinarySearchType type, + DN_BinarySearchLessThanProc less_than) +{ + DN_BinarySearchResult result = {}; + if (!array || array_size <= 0 || !less_than) + return result; + + T const *end = array + array_size; + T const *first = array; + T const *last = end; + while (first != last) { + DN_USize count = last - first; + T const *it = first + (count / 2); + + bool advance_first = false; + if (type == DN_BinarySearchType_UpperBound) + advance_first = !less_than(find, it[0]); + else + advance_first = less_than(it[0], find); + + if (advance_first) + first = it + 1; + else + last = it; + } + + switch (type) { + case DN_BinarySearchType_Match: { + result.found = first != end && !less_than(find, *first); + } break; + + case DN_BinarySearchType_LowerBound: /*FALLTHRU*/ + case DN_BinarySearchType_UpperBound: { + result.found = first != end; + } break; + } + + result.index = first - array; + return result; +} + +DN_API void DN_LeakTrackAlloc_ (DN_LeakTracker *leak, void *ptr, DN_USize size, bool alloc_can_leak); +DN_API void DN_LeakTrackDealloc_(DN_LeakTracker *leak, void *ptr); +DN_API void DN_LeakDump_ (DN_LeakTracker *leak); + +#if defined(DN_LEAK_TRACKING) +#define DN_LeakTrackAlloc(leak, ptr, size, alloc_can_leak) DN_LeakTrackAlloc_(leak, ptr, size, alloc_can_leak) +#define DN_LeakTrackDealloc(leak, ptr) DN_LeakTrackDealloc_(leak, ptr) +#define DN_LeakDump(leak) DN_LeakDump_(leak) +#else +#define DN_LeakTrackAlloc(leak, ptr, size, alloc_can_leak) do { (void)ptr; (void)size; (void)alloc_can_leak; } while (0) +#define DN_LeakTrackDealloc(leak, ptr) do { (void)ptr; } while (0) +#define DN_LeakDump(leak) do { } while (0) +#endif + +#if DN_WITH_OS +DN_API DN_MemFuncs DN_MemFuncsFromType (DN_MemFuncsType type); +DN_API DN_MemFuncs DN_MemFuncsDefault (); +DN_API DN_MemList DN_MemListFromHeap (DN_U64 size, DN_MemFlags flags); +DN_API DN_MemList DN_MemListFromVMem (DN_U64 reserve, DN_U64 commit, DN_MemFlags flags); +DN_API DN_Arena DN_ArenaFromHeap (DN_U64 wize, DN_MemFlags flags); +DN_API DN_Arena DN_ArenaFromVMem (DN_U64 reserve, DN_U64 commit, DN_MemFlags flags); + +DN_API DN_Str8 DN_Str8FromHeapF (DN_FMT_ATTRIB char const *fmt, ...); +DN_API DN_Str8 DN_Str8FromHeap (DN_USize size, DN_ZMem z_mem); +DN_API DN_Str8 DN_Str8BuilderBuildFromHeap (DN_Str8Builder const *builder); + +DN_API void DN_OS_LogPrint (DN_LogTypeParam type, void *user_data, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API void DN_OS_SetLogPrintFuncToOS (); + +DN_API void * DN_OS_MemReserve (DN_USize size, DN_MemCommit commit, DN_MemPage page_flags); +DN_API bool DN_OS_MemCommit (void *ptr, DN_USize size, DN_U32 page_flags); +DN_API void DN_OS_MemDecommit (void *ptr, DN_USize size); +DN_API void DN_OS_MemRelease (void *ptr, DN_USize size); +DN_API int DN_OS_MemProtect (void *ptr, DN_USize size, DN_U32 page_flags); + +DN_API void * DN_OS_MemAlloc (DN_USize size, DN_ZMem z_mem); +DN_API void DN_OS_MemDealloc (void *ptr); + +DN_API DN_Date DN_OS_DateLocalTimeNow (); +DN_API DN_Str8x32 DN_OS_DateLocalTimeStr8Now (char date_separator = '-', char hms_separator = ':'); +DN_API DN_Str8x32 DN_OS_DateLocalTimeStr8 (DN_Date time, char date_separator = '-', char hms_separator = ':'); +DN_API DN_U64 DN_OS_DateUnixTimeNs (); +#define DN_OS_DateUnixTimeUs() (DN_OS_DateUnixTimeNs() / 1000) +#define DN_OS_DateUnixTimeMs() (DN_OS_DateUnixTimeNs() / (1000 * 1000)) +#define DN_OS_DateUnixTimeS() (DN_OS_DateUnixTimeNs() / (1000 * 1000 * 1000)) +DN_API DN_U64 DN_OS_DateUnixTimeSFromLocalDate (DN_Date date); +DN_API DN_U64 DN_OS_DateLocalUnixTimeSFromUnixTimeS (DN_U64 unix_ts_s); + +DN_API void DN_OS_GenBytesSecure (void *buffer, DN_U32 size); +DN_API bool DN_OS_SetEnvVar (DN_Str8 name, DN_Str8 value); +DN_API DN_OSDiskSpace DN_OS_DiskSpace (DN_Str8 path); +DN_API DN_Str8 DN_OS_EXEPath (DN_Arena *arena); +DN_API DN_Str8 DN_OS_EXEDir (DN_Arena *arena); +DN_API void DN_OS_SleepMs (DN_UInt milliseconds); + +DN_API DN_U64 DN_OS_PerfCounterNow (); +DN_API DN_U64 DN_OS_PerfCounterFrequency (); +DN_API DN_F64 DN_OS_PerfCounterS (DN_U64 begin, uint64_t end); +DN_API DN_F64 DN_OS_PerfCounterMs (DN_U64 begin, uint64_t end); +DN_API DN_F64 DN_OS_PerfCounterUs (DN_U64 begin, uint64_t end); +DN_API DN_F64 DN_OS_PerfCounterNs (DN_U64 begin, uint64_t end); +DN_API DN_OSTimer DN_OS_TimerBegin (); +DN_API void DN_OS_TimerEnd (DN_OSTimer *timer); +DN_API DN_F64 DN_OS_TimerS (DN_OSTimer timer); +DN_API DN_F64 DN_OS_TimerMs (DN_OSTimer timer); +DN_API DN_F64 DN_OS_TimerUs (DN_OSTimer timer); +DN_API DN_F64 DN_OS_TimerNs (DN_OSTimer timer); +DN_API DN_U64 DN_OS_EstimateTSCPerSecond (uint64_t duration_ms_to_gauge_tsc_frequency); + +DN_API bool DN_OS_FileCopy (DN_Str8 src, DN_Str8 dest, bool overwrite, DN_ErrSink *err); +DN_API bool DN_OS_FileMove (DN_Str8 src, DN_Str8 dest, bool overwrite, DN_ErrSink *err); + +DN_API DN_OSFile DN_OS_FileOpen (DN_Str8 path, DN_OSFileOpen open_mode, DN_OSFileAccess access, DN_ErrSink *err); +DN_API DN_OSFileRead DN_OS_FileRead (DN_OSFile *file, void *buffer, DN_USize size, DN_ErrSink *err); +DN_API bool DN_OS_FileWritePtr (DN_OSFile *file, void const *data, DN_USize size, DN_ErrSink *err); +DN_API bool DN_OS_FileWrite (DN_OSFile *file, DN_Str8 buffer, DN_ErrSink *err); +DN_API bool DN_OS_FileWriteFV (DN_OSFile *file, DN_ErrSink *err, DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API bool DN_OS_FileWriteF (DN_OSFile *file, DN_ErrSink *err, DN_FMT_ATTRIB char const *fmt, ...); +DN_API bool DN_OS_FileFlush (DN_OSFile *file, DN_ErrSink *err); +DN_API void DN_OS_FileClose (DN_OSFile *file); + +DN_API DN_Str8 DN_OS_FileReadAll (DN_Allocator allocator, DN_Str8 path, DN_ErrSink *err); +DN_API DN_Str8 DN_OS_FileReadAllArena (DN_Arena *arena, DN_Str8 path, DN_ErrSink *err); +DN_API DN_Str8 DN_OS_FileReadAllPool (DN_Pool *pool, DN_Str8 path, DN_ErrSink *err); + +DN_API bool DN_OS_FileWriteAll (DN_Str8 path, DN_Str8 buffer, DN_ErrSink *err); +DN_API bool DN_OS_FileWriteAllFV (DN_Str8 path, DN_ErrSink *err, DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API bool DN_OS_FileWriteAllF (DN_Str8 path, DN_ErrSink *err, DN_FMT_ATTRIB char const *fmt, ...); +DN_API bool DN_OS_FileWriteAllSafe (DN_Str8 path, DN_Str8 buffer, DN_ErrSink *err); +DN_API bool DN_OS_FileWriteAllSafeFV (DN_Str8 path, DN_ErrSink *err, DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API bool DN_OS_FileWriteAllSafeF (DN_Str8 path, DN_ErrSink *err, DN_FMT_ATTRIB char const *fmt, ...); + +DN_API DN_Str8 DN_OS_Str8FromPathInfoType (DN_OSPathInfoType type); +DN_API DN_OSPathInfo DN_OS_PathInfo (DN_Str8 path); +DN_API bool DN_OS_PathIsOlderThan (DN_Str8 file, DN_Str8 check_against); +DN_API bool DN_OS_PathDelete (DN_Str8 path); +DN_API bool DN_OS_PathIsFile (DN_Str8 path); +DN_API bool DN_OS_PathIsDir (DN_Str8 path); +DN_API bool DN_OS_PathMakeDir (DN_Str8 path); +DN_API bool DN_OS_PathIterateDir (DN_Str8 path, DN_OSDirIterator *it); + +DN_API bool DN_OS_PathAddRef (DN_Arena *arena, DN_OSPath *fs_path, DN_Str8 path); +DN_API bool DN_OS_PathAdd (DN_Arena *arena, DN_OSPath *fs_path, DN_Str8 path); +DN_API bool DN_OS_PathAddF (DN_Arena *arena, DN_OSPath *fs_path, DN_FMT_ATTRIB char const *fmt, ...); +DN_API bool DN_OS_PathPop (DN_OSPath *fs_path); +DN_API DN_Str8 DN_OS_PathBuildWithSeparator (DN_Arena *arena, DN_OSPath const *fs_path, DN_Str8 path_separator); +DN_API DN_Str8 DN_OS_PathTo (DN_Arena *arena, DN_Str8 path, DN_Str8 path_separtor); +DN_API DN_Str8 DN_OS_PathToF (DN_Arena *arena, DN_Str8 path_separator, DN_FMT_ATTRIB char const *fmt, ...); +DN_API DN_Str8 DN_OS_Path (DN_Arena *arena, DN_Str8 path); +DN_API DN_Str8 DN_OS_PathF (DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, ...); + +#define DN_OS_PathBuildFwdSlash(allocator, fs_path) DN_OS_PathBuildWithSeparator(allocator, fs_path, DN_Str8Lit("/")) +#define DN_OS_PathBuildBackSlash(allocator, fs_path) DN_OS_PathBuildWithSeparator(allocator, fs_path, DN_Str8Lit("\\")) +#define DN_OS_PathBuild(allocator, fs_path) DN_OS_PathBuildWithSeparator(allocator, fs_path, DN_OSPathSeparatorString) + +DN_API void DN_OS_Exit (int32_t exit_code); +DN_API DN_OSExecResult DN_OS_ExecPump (DN_OSExecAsyncHandle handle, char *stdout_buffer, size_t *stdout_size, char *stderr_buffer, size_t *stderr_size, DN_U32 timeout_ms, DN_ErrSink *err); +DN_API DN_OSExecResult DN_OS_ExecWait (DN_OSExecAsyncHandle handle, DN_Arena *arena, DN_ErrSink *err); +DN_API DN_OSExecAsyncHandle DN_OS_ExecAsync (DN_Str8Slice cmd_line, DN_OSExecArgs *args, DN_ErrSink *err); +DN_API DN_OSExecResult DN_OS_Exec (DN_Str8Slice cmd_line, DN_OSExecArgs *args, DN_Arena *arena, DN_ErrSink *err); +DN_API DN_OSExecResult DN_OS_ExecOrAbort (DN_Str8Slice cmd_line, DN_OSExecArgs *args, DN_Arena *arena); + +DN_API DN_OSSemaphore DN_OS_SemaphoreInit (DN_U32 initial_count); +DN_API void DN_OS_SemaphoreDeinit (DN_OSSemaphore *semaphore); +DN_API void DN_OS_SemaphoreIncrement (DN_OSSemaphore *semaphore, DN_U32 amount); +DN_API DN_OSSemaphoreWaitResult DN_OS_SemaphoreWait (DN_OSSemaphore *semaphore, DN_U32 timeout_ms); + +DN_API DN_OSBarrier DN_OS_BarrierInit (DN_U32 thread_count); +DN_API void DN_OS_BarrierDeinit (DN_OSBarrier *barrier); +DN_API void DN_OS_BarrierWait (DN_OSBarrier *barrier); + +DN_API DN_OSMutex DN_OS_MutexInit (); +DN_API void DN_OS_MutexDeinit (DN_OSMutex *mutex); +DN_API void DN_OS_MutexLock (DN_OSMutex *mutex); +DN_API void DN_OS_MutexUnlock (DN_OSMutex *mutex); +#define DN_OS_MutexScope(mutex) DN_DeferLoop(DN_OS_MutexLock(mutex), DN_OS_MutexUnlock(mutex)) + +DN_API DN_OSConditionVariable DN_OS_ConditionVariableInit (); +DN_API void DN_OS_ConditionVariableDeinit (DN_OSConditionVariable *cv); +DN_API bool DN_OS_ConditionVariableWait (DN_OSConditionVariable *cv, DN_OSMutex *mutex, DN_U64 sleep_ms); +DN_API bool DN_OS_ConditionVariableWaitUntil (DN_OSConditionVariable *cv, DN_OSMutex *mutex, DN_U64 end_ts_ms); +DN_API void DN_OS_ConditionVariableSignal (DN_OSConditionVariable *cv); +DN_API void DN_OS_ConditionVariableBroadcast (DN_OSConditionVariable *cv); + +DN_API bool DN_OS_ThreadInit (DN_OSThread *thread, DN_OSThreadFunc *func, DN_OSThreadLane *lane, DN_TCInitArgs tc_init_args, void *user_context); +DN_API bool DN_OS_ThreadJoin (DN_OSThread *thread, DN_TCDeinitArenas deinit_arenas); +DN_API DN_U32 DN_OS_ThreadID (); +DN_API void DN_OS_ThreadSetNameFmt (char const *fmt, ...); + +// NOTE: Thread lanes provide an abstraction to represent the concept of programming a CPU like a +// GPU, e.g. SIMT (Single Instruction Multiple Threads). The lane terminology is popularised by Ryan +// Fleury. SIMT is formally defined as +// +// Threads are grouped into warps/wavefronts (typically 32 or 64 threads) that execute the same +// instruction in lockstep, but each thread operates on different data and maintains its own state +// +// The individual threads in a wavefront on the CPU side are colloquially dubbed "lanes" and a +// thread lane here contains the necessary state to facilitate this such as the current index in the +// wavefront and synchronisation primitives to coordinate the different lanes together. +// +// The idea is to write code in a single-threaded manner (linear execution) but across multiple +// threads so that the default is all execution paths are inherently multi-threaded by default. Opt +// out of parallelism instead of opt in. This optimises for the trend of core counts increasing +// whilst clock counts remain static. +// +// A laneway is a helper function to initialise the number of requested OS threads/lanes upfront and +// setup the required synchronisation primitives. It can then be dispatched all the threads which +// start executing the `entry_point` in parallel. +// +// API +// DN_OS_ThreadLaneSync +// A blocking call to synchronise the program-counter of all other lanes in the laneway to this +// function call invocation (using an OS barrier). Optionally pass in the pointer to a pointer +// `ptr_to_share` to broadcast the pointer from one lanes to the others. The lane that wishes +// to broadcast the pointer must have a non-null pointer, all other lanes must pass in a +// non-null pointer. A typical use case might look like: +/* + DN_OSThreadLane *lane = DN_OS_TCThreadLane(); // Get lane from current (t)hread (c)context + + // NOTE: Allocate buffer in lane 0 + DN_U8 *buffer = nullptr; + if (lane->index == 0) + buffer = DN_ArenaNewArray(DN_TCMainArena(), DN_U8, DN_Gigabytes(1), DN_ZMem_No); + + // NOTE: Lane 0 broadcasts the `buffer` pointer to lane 1..N + DN_OS_ThreadLaneSync(lane, &buffer); + + // NOTE: We use LaneRange to divide the buffer into equal sized chunks that each lane can + // write into without clobbering over each other. + DN_V2USize range = DN_OS_ThreadLaneRange(lane, DN_Gigabytes(1)); + for (DN_USize index = range.begin; index < range.end; index++) { buffer[index] = index; } +*/ +// In this example, lane 0 will allocate a 1GiB buffer pass in a `buffer` to +// DN_OS_ThreadLaneSync` that is non-null. Lanes 1->N will skip the branch (because their lanes +// indexes are 1..N) and invoke `DN_OS_ThreadLaneSync` with a nullptr `buffer`. After the +// blocking call is complete, lanes 0->N will now have synchronised the `buffer` pointer and all +// lanes point to the 1GiB range allocated in lane 0's allocator. +// +// Additionally we demonstrate `DN_OS_ThreadLaneRange` which does math behind the scenes to +// divide the buffer up and assign each lane their own indices in the buffer that they can work +// on in parallel without clobbering each others work. +// +// DN_OS_ThreadLaneRange +// Calculates the range of values the current lane in the laneway should execute. For example if +// you have 128 items and 16 threads each lane will receive the following `DN_V2USize` range: +// Lane 0 => [0, 8) +// Lane 1 => [8, 16) +// ... +// Lane 16 => [120, 128) +DN_API DN_OSThreadLane DN_OS_ThreadLaneInit (DN_USize index, DN_USize thread_count, DN_OSBarrier barrier, DN_UPtr *share_mem); +DN_API void DN_OS_ThreadLaneSync (DN_OSThreadLane *lane, void **ptr_to_share); +DN_API DN_V2USize DN_OS_ThreadLaneRange (DN_OSThreadLane const *lane, DN_USize values_count); + +DN_API DN_OSThreadLaneway DN_OS_ThreadLanewayFromArgs (DN_OSThread* threads, DN_USize threads_count, DN_UPtr* shared_mem); +DN_API DN_OSThreadLaneway DN_OS_ThreadLanewayFromArena (DN_USize threads_count, DN_Arena* arena); +DN_API void DN_OS_ThreadLanewayDispatch (DN_OSThreadLaneway *laneway, DN_OSThreadFunc *entry_point, DN_TCInitArgs tc_init_args, void *user_context); +DN_API void DN_OS_ThreadLanewayJoin (DN_OSThreadLaneway *laneway, DN_TCDeinitArenas deinit_arenas); + +DN_API DN_OSThreadLane* DN_OS_TCThreadLane (); +DN_API void DN_OS_TCThreadLaneSync (void **ptr_to_share); +DN_API DN_OSThreadLane DN_OS_TCThreadLaneEquip (DN_OSThreadLane lane); + + +DN_API void DN_OS_AsyncInit (DN_OSAsyncCore *async, char *base, DN_USize base_size, DN_OSThread *threads, DN_U32 threads_size); +DN_API void DN_OS_AsyncDeinit (DN_OSAsyncCore *async); +DN_API bool DN_OS_AsyncQueueWork(DN_OSAsyncCore *async, DN_OSAsyncWorkFunc *func, void *input, DN_U64 wait_time_ms); +DN_API DN_OSAsyncTask DN_OS_AsyncQueueTask(DN_OSAsyncCore *async, DN_OSAsyncWorkFunc *func, void *input, DN_U64 wait_time_ms); +DN_API bool DN_OS_AsyncWaitTask (DN_OSAsyncTask *task, DN_U32 timeout_ms); + +// NOTE: DN_OSPrint +enum DN_OSPrintDest +{ + DN_OSPrintDest_Out, + DN_OSPrintDest_Err, +}; + +// NOTE: Print Macros +#define DN_OS_PrintOut(string) DN_OS_Print(DN_OSPrintDest_Out, string) +#define DN_OS_PrintOutF(fmt, ...) DN_OS_PrintF(DN_OSPrintDest_Out, fmt, ##__VA_ARGS__) +#define DN_OS_PrintOutFV(fmt, args) DN_OS_PrintFV(DN_OSPrintDest_Out, fmt, args) + +#define DN_OS_PrintOutStyle(style, string) DN_OS_PrintStyle(DN_OSPrintDest_Out, style, string) +#define DN_OS_PrintOutFStyle(style, fmt, ...) DN_OS_PrintFStyle(DN_OSPrintDest_Out, style, fmt, ##__VA_ARGS__) +#define DN_OS_PrintOutFVStyle(style, fmt, args, ...) DN_OS_PrintFVStyle(DN_OSPrintDest_Out, style, fmt, args) + +#define DN_OS_PrintOutLn(string) DN_OS_PrintLn(DN_OSPrintDest_Out, string) +#define DN_OS_PrintOutLnF(fmt, ...) DN_OS_PrintLnF(DN_OSPrintDest_Out, fmt, ##__VA_ARGS__) +#define DN_OS_PrintOutLnFV(fmt, args) DN_OS_PrintLnFV(DN_OSPrintDest_Out, fmt, args) + +#define DN_OS_PrintOutLnStyle(style, string) DN_OS_PrintLnStyle(DN_OSPrintDest_Out, style, string); +#define DN_OS_PrintOutLnFStyle(style, fmt, ...) DN_OS_PrintLnFStyle(DN_OSPrintDest_Out, style, fmt, ##__VA_ARGS__) +#define DN_OS_PrintOutLnFVStyle(style, fmt, args) DN_OS_PrintLnFVStyle(DN_OSPrintDest_Out, style, fmt, args); + +#define DN_OS_PrintErr(string) DN_OS_Print(DN_OSPrintDest_Err, string) +#define DN_OS_PrintErrF(fmt, ...) DN_OS_PrintF(DN_OSPrintDest_Err, fmt, ##__VA_ARGS__) +#define DN_OS_PrintErrFV(fmt, args) DN_OS_PrintFV(DN_OSPrintDest_Err, fmt, args) + +#define DN_OS_PrintErrStyle(style, string) DN_OS_PrintStyle(DN_OSPrintDest_Err, style, string) +#define DN_OS_PrintErrFStyle(style, fmt, ...) DN_OS_PrintFStyle(DN_OSPrintDest_Err, style, fmt, ##__VA_ARGS__) +#define DN_OS_PrintErrFVStyle(style, fmt, args, ...) DN_OS_PrintFVStyle(DN_OSPrintDest_Err, style, fmt, args) + +#define DN_OS_PrintErrLn(string) DN_OS_PrintLn(DN_OSPrintDest_Err, string) +#define DN_OS_PrintErrLnF(fmt, ...) DN_OS_PrintLnF(DN_OSPrintDest_Err, fmt, ##__VA_ARGS__) +#define DN_OS_PrintErrLnFV(fmt, args) DN_OS_PrintLnFV(DN_OSPrintDest_Err, fmt, args) + +#define DN_OS_PrintErrLnStyle(style, string) DN_OS_PrintLnStyle(DN_OSPrintDest_Err, style, string); +#define DN_OS_PrintErrLnFStyle(style, fmt, ...) DN_OS_PrintLnFStyle(DN_OSPrintDest_Err, style, fmt, ##__VA_ARGS__) +#define DN_OS_PrintErrLnFVStyle(style, fmt, args) DN_OS_PrintLnFVStyle(DN_OSPrintDest_Err, style, fmt, args); + +// NOTE: Print +DN_API void DN_OS_Print (DN_OSPrintDest dest, DN_Str8 string); +DN_API void DN_OS_PrintF (DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, ...); +DN_API void DN_OS_PrintFV (DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, va_list args); + +DN_API void DN_OS_PrintStyle (DN_OSPrintDest dest, DN_LogStyle style, DN_Str8 string); +DN_API void DN_OS_PrintFStyle (DN_OSPrintDest dest, DN_LogStyle style, DN_FMT_ATTRIB char const *fmt, ...); +DN_API void DN_OS_PrintFVStyle (DN_OSPrintDest dest, DN_LogStyle style, DN_FMT_ATTRIB char const *fmt, va_list args); + +DN_API void DN_OS_PrintLn (DN_OSPrintDest dest, DN_Str8 string); +DN_API void DN_OS_PrintLnF (DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, ...); +DN_API void DN_OS_PrintLnFV (DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, va_list args); + +DN_API void DN_OS_PrintLnStyle (DN_OSPrintDest dest, DN_LogStyle style, DN_Str8 string); +DN_API void DN_OS_PrintLnFStyle (DN_OSPrintDest dest, DN_LogStyle style, DN_FMT_ATTRIB char const *fmt, ...); +DN_API void DN_OS_PrintLnFVStyle (DN_OSPrintDest dest, DN_LogStyle style, DN_FMT_ATTRIB char const *fmt, va_list args); +#endif // DN_WITH_OS + +// NOTE: Template implementations +#if defined(__cplusplus) +template T *DN_MemCopyObjT(T *dest, T const *src, DN_USize count) +{ + T* result = dest; + DN_Memcpy(dest, src, sizeof(T) * count); + return result; +} + +template +DN_ArrayFindResult DN_TArrayFind(T *data, DN_USize size, void const *find, DN_ArrayFindEqFunc *eq_func) +{ + DN_ArrayFindResult result = DN_ArrayFind(data, size, sizeof(*data), find, eq_func); + return result; +} + +template +DN_ArrayFindResult DN_TArrayFindMemEq(T *data, DN_USize size, void const *find) +{ + DN_ArrayFindResult result = DN_ArrayFindMemEq(data, size, sizeof(*data), find); + return result; +} + +template +T *DN_TArrayInsertArray(T *data, DN_USize *size, DN_USize max, DN_USize index, T const *items, DN_USize count) +{ + T *result = DN_Cast(T *)DN_ArrayInsertArray(data, size, max, sizeof(*data), index, items, count); + return result; +} + +template +T *DN_TArrayPopFront(T *data, DN_USize *size, DN_USize count) +{ + T *result = DN_Cast(T *)DN_ArrayPopFront(data, size, sizeof(*data), count); + return result; +} + +template +T *DN_TArrayPopBack(T *data, DN_USize *size, DN_USize count) +{ + T *result = DN_Cast(T *)DN_ArrayPopBack(data, size, sizeof(*data), count); + return result; +} + +template +DN_ArrayEraseResult DN_TArrayEraseRange(T *data, DN_USize *size, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase) +{ + DN_ArrayEraseResult result = DN_ArrayEraseRange(data, size, sizeof(*data), begin_index, count, erase); + return result; +} + +template +T *DN_TArrayMakeArray(T *data, DN_USize *size, DN_USize max, DN_USize make_count, DN_ZMem z_mem) +{ + T *result = DN_Cast(T *)DN_ArrayMakeArray(data, size, max, sizeof(*data), make_count, z_mem); + return result; +} + +template +T *DN_TArrayMakeArrayAssert(T *data, DN_USize *size, DN_USize max, DN_USize make_count, DN_ZMem z_mem, DN_CallSite call_site) +{ + T *result = DN_Cast(T *)DN_ArrayMakeArrayAssert(data, size, max, sizeof(*data), make_count, z_mem, call_site); + return result; +} + +template +T *DN_TArrayMakeArrayAssertZ(T *data, DN_USize *size, DN_USize max, DN_USize make_count, DN_CallSite call_site) +{ + T* result = DN_TArrayMakeArrayAssert(data, size, max, make_count, DN_ZMem_Yes, call_site); + return result; +} + +template +T *DN_TArrayMakeArrayAssertNoZ(T *data, DN_USize *size, DN_USize max, DN_USize make_count, DN_CallSite call_site) +{ + T* result = DN_TArrayMakeArrayAssert(data, size, max, make_count, DN_ZMem_No, call_site); + return result; +} + +template +T *DN_TArrayAddArray(T *data, DN_USize *size, DN_USize max, T const *elems, DN_USize elems_count, DN_ArrayAdd add) +{ + T* result = DN_Cast(T *)DN_ArrayAddArray(data, size, max, sizeof(*elems), elems, elems_count, add); + return result; +} + +template +T *DN_TArrayAddArrayAssert(T *data, DN_USize *size, DN_USize max, T const *elems, DN_USize elems_count, DN_ArrayAdd add, DN_CallSite call_site) +{ + T* result = DN_Cast(T *)DN_ArrayAddArrayAssert(data, size, max, sizeof(*elems), elems, elems_count, add, call_site); + return result; +} + +template +bool DN_TArrayResizeFromPool(T **data, DN_USize *size, DN_USize *max, DN_Pool *pool, DN_USize new_max) +{ + bool result = DN_ArrayResizeFromPool(DN_Cast(void **)data, size, max, sizeof(**data), pool, new_max); + return result; +} + +template +bool DN_TArrayResizeFromArena(T **data, DN_USize *size, DN_USize *max, DN_Arena *arena, DN_USize new_max) +{ + bool result = DN_ArrayResizeFromArena(DN_Cast(void **)data, size, max, sizeof(**data), arena, new_max); + return result; +} + +template +bool DN_TArrayGrowFromPool(T **data, DN_USize size, DN_USize *max, DN_Pool *pool, DN_USize new_max) +{ + bool result = DN_ArrayGrowFromPool(DN_Cast(void **)data, size, max, sizeof(**data), pool, new_max); + return result; +} + +template +bool DN_TArrayGrowFromArena(T **data, DN_USize size, DN_USize *max, DN_Arena *arena, DN_USize new_max) +{ + bool result = DN_ArrayGrowFromArena(DN_Cast(void **)data, size, max, sizeof(**data), arena, new_max); + return result; +} + +template +bool DN_TArrayGrowIfNeededFromPool(T **data, DN_USize size, DN_USize *max, DN_Pool *pool, DN_USize add_count) +{ + bool result = DN_ArrayGrowIfNeededFromPool(DN_Cast(void **)data, size, max, sizeof(**data), pool, add_count); + return result; +} + +template +bool DN_TArrayGrowIfNeededFromArena(T **data, DN_USize size, DN_USize *max, DN_Arena *arena, DN_USize add_count) +{ + bool result = DN_ArrayGrowIfNeededFromArena(DN_Cast(void **)data, size, max, sizeof(**data), arena, add_count); + return result; +} +#endif // defined(__cplusplus) + +#if DN_WITH_NET +enum DN_NETRequestType +{ + DN_NETRequestType_Nil, + DN_NETRequestType_HTTP, + DN_NETRequestType_WS, +}; + +enum DN_NETResponseState +{ + DN_NETResponseState_Nil, + DN_NETResponseState_Error, + DN_NETResponseState_HTTP, + DN_NETResponseState_WSOpen, + DN_NETResponseState_WSText, + DN_NETResponseState_WSBinary, + DN_NETResponseState_WSClose, + DN_NETResponseState_WSPing, + DN_NETResponseState_WSPong, +}; + +enum DN_NETWSSend +{ + DN_NETWSSend_Text, + DN_NETWSSend_Binary, + DN_NETWSSend_Close, + DN_NETWSSend_Ping, + DN_NETWSSend_Pong, +}; + +enum DN_NETDoHTTPFlags +{ + DN_NETDoHTTPFlags_Nil = 0, + DN_NETDoHTTPFlags_BasicAuth = 1 << 0, +}; + +struct DN_NETDoHTTPArgs +{ + // NOTE: WS and HTTP args + DN_NETDoHTTPFlags flags; + DN_Str8 username; + DN_Str8 password; + DN_Str8 *headers; + DN_U16 headers_size; + + // NOTE: HTTP args only + DN_Str8 payload; +}; + +struct DN_NETRequestHandle +{ + DN_UPtr handle; + DN_U64 gen; +}; + +struct DN_NETResponse +{ + // NOTE: Common to WS and HTTP responses + DN_NETRequestType type; + DN_NETResponseState state; + DN_NETRequestHandle request; + DN_Str8 error_str8; + DN_Str8 body; + + // NOTE: HTTP responses only + DN_U32 http_status; +}; + +struct DN_NETRequest +{ + DN_MemList mem; + DN_Arena arena; + DN_Arena start_response_arena; + DN_NETRequestType type; + DN_U64 gen; + DN_Str8 url; + DN_Str8 method; + DN_OSSemaphore completion_sem; + DN_NETDoHTTPArgs args; + DN_NETResponse response; + DN_NETRequest *next; + DN_NETRequest *prev; + DN_U64 context[2]; +}; + +typedef void (DN_NETInitFunc) (struct DN_NETCore *net, char *base, DN_U64 base_size); +typedef void (DN_NETDeinitFunc) (struct DN_NETCore *net); +typedef DN_NETRequestHandle(DN_NETDoHTTPFunc) (struct DN_NETCore *net, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args); +typedef DN_NETRequestHandle(DN_NETDoWSFunc) (struct DN_NETCore *net, DN_Str8 url); +typedef void (DN_NETDoWSSendFunc) (DN_NETRequestHandle handle, DN_Str8 data, DN_NETWSSend send); +typedef DN_NETResponse (DN_NETWaitForResponseFunc) (DN_NETRequestHandle handle, DN_Arena *arena, DN_U32 timeout_ms); +typedef DN_NETResponse (DN_NETWaitForAnyResponseFunc)(struct DN_NETCore *net, DN_Arena *arena, DN_U32 timeout_ms); + +struct DN_NETInterface +{ + DN_NETInitFunc* init; + DN_NETDeinitFunc* deinit; + DN_NETDoHTTPFunc* do_http; + DN_NETDoWSFunc* do_ws; + DN_NETDoWSSendFunc* do_ws_send; + DN_NETWaitForResponseFunc* wait_for_response; + DN_NETWaitForAnyResponseFunc* wait_for_any_response; +}; + +struct DN_NETCore +{ + char *base; + DN_U64 base_size; + DN_MemList mem; + DN_Arena arena; + DN_OSSemaphore completion_sem; + void *context; + DN_NETInterface api; +}; + +DN_Str8 DN_NET_Str8FromResponseState (DN_NETResponseState state); +DN_NETRequest * DN_NET_RequestFromHandle (DN_NETRequestHandle handle); +DN_NETRequestHandle DN_NET_HandleFromRequest (DN_NETRequest *request); +bool DN_NET_ResponseHasFailed (DN_NETResponse const* resp); +DN_Str8 DN_NET_Str8DiagnosticFromResponse(DN_NETResponse const* resp, DN_Arena *arena); + +// NOTE: Internal functions for different networking implementations to use +void DN_NET_BaseInit (DN_NETCore *net, char *base, DN_U64 base_size); +DN_NETRequestHandle DN_NET_SetupRequest (DN_NETRequest *request, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args, DN_NETRequestType type); +void DN_NET_EndFinishedRequest (DN_NETRequest *request); +#endif + +#if DN_WITH_NET_CURL +#if !DN_WITH_OS || !DN_WITH_NET + #error "NET API with CURL requires #define DN_WITH_NET 1 and #define DN_WITH_OS 1" +#endif +struct DN_NETCurlCore +{ + // NOTE: Shared w/ user and networking thread + DN_Ring ring; + DN_OSMutex ring_mutex; + bool kill_thread; + + DN_OSMutex list_mutex; // Lock for request, response, deinit, free list + DN_NETRequest *request_list; // Current requests submitted by the user thread awaiting to move into the thread request list + DN_NETRequest *response_list; // Finished requests that are to be deqeued by the user via wait for response + DN_NETRequest *deinit_list; // Requests that are finished and are awaiting to be de-initialised by the CURL thread + DN_NETRequest *free_list; // Request pool that new requests will use before allocating + + // NOTE: Networking thread only + DN_NETRequest *thread_request_list; // Current requests being executed by the CURL thread. + // This list is exclusively owned by the CURL thread so no locking is needed + DN_OSThread thread; + void *thread_curlm; +}; + +#define DN_NET_CurlCoreFromNet(net) ((net) ? (DN_Cast(DN_NETCurlCore *)(net)->context) : nullptr); +DN_NETInterface DN_NET_CurlInterface (); +void DN_NET_CurlInit (DN_NETCore *net, char *base, DN_U64 base_size); +void DN_NET_CurlDeinit (DN_NETCore *net); +DN_NETRequestHandle DN_NET_CurlDoHTTP (DN_NETCore *net, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args); +DN_NETRequestHandle DN_NET_CurlDoWSArgs (DN_NETCore *net, DN_Str8 url, DN_NETDoHTTPArgs const *args); +DN_NETRequestHandle DN_NET_CurlDoWS (DN_NETCore *net, DN_Str8 url); +void DN_NET_CurlDoWSSend (DN_NETRequestHandle handle, DN_Str8 payload, DN_NETWSSend send); +DN_NETResponse DN_NET_CurlWaitForResponse (DN_NETRequestHandle handle, DN_Arena *arena, DN_U32 timeout_ms); +DN_NETResponse DN_NET_CurlWaitForAnyResponse(DN_NETCore *net, DN_Arena *arena, DN_U32 timeout_ms); +#endif // #if DN_WITH_NET_CURL + +#if DN_WITH_NET_EMSCRIPTEN +#if !DN_WITH_OS || !DN_WITH_NET + #error "NET API with Emscripten requires #define DN_WITH_NET 1 and #define DN_WITH_OS 1" +#endif +#if !defined(__EMSCRIPTEN__) + #error "NET API with Esmcripten can only be compiled with emcc which defines __EMSCRIPTEN__" +#endif + +DN_NETInterface DN_NET_EmcInterface(); +void DN_NET_EmcInit (DN_NETCore *net, char *base, DN_U64 base_size); +void DN_NET_EmcDeinit (DN_NETCore *net); +DN_NETRequestHandle DN_NET_EmcDoHTTP (DN_NETCore *net, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args); +DN_NETRequestHandle DN_NET_EmcDoWS (DN_NETCore *net, DN_Str8 url); +void DN_NET_EmcDoWSSend (DN_NETRequestHandle handle, DN_Str8 data, DN_NETWSSend send); +DN_NETResponse DN_NET_EmcWaitForResponse (DN_NETRequestHandle handle, DN_Arena *arena, DN_U32 timeout_ms); +DN_NETResponse DN_NET_EmcWaitForAnyResponse(DN_NETCore *net, DN_Arena *arena, DN_U32 timeout_ms); +#endif // #if DN_WITH_NET_EMSCRIPTEN + +#if DN_WITH_OS +#if defined(DN_PLATFORM_WIN32) + #include "OS/dn_os_windows.h" + #include "OS/dn_os_w32.h" +#elif defined(DN_PLATFORM_POSIX) || defined(DN_PLATFORM_EMSCRIPTEN) + #include "OS/dn_os_posix.h" +#else + #error Please define a platform e.g. 'DN_PLATFORM_WIN32' to enable the correct implementation for platform APIs +#endif +#endif // #if DN_WITH_OS + +#endif // #if !defined(DN_H) diff --git a/build.bat b/build.bat index 9abd1b4..d6dd741 100644 --- a/build.bat +++ b/build.bat @@ -34,7 +34,7 @@ pushd %build_dir% :: GR- Disable C RTTI :: Oi Use CPU Intrinsics :: Z7 Combine multi-debug files to one debug file - set flags=%flags% -D DN_UNIT_TESTS_WITH_KECCAK -D DN_UNIT_TESTS_WITH_NET %script_dir%\Source\Extra\dn_tests_main.cpp + set flags=%flags% -D DN_UNIT_TESTS_WITH_KECCAK %script_dir%\Source\Extra\dn_tests_main.cpp set msvc_driver_flags=-EHa -GR- -Od -Oi -Z7 -wd4201 -W4 -nologo %flags% -fsanitize=address where /q emcc && ( @@ -46,7 +46,7 @@ pushd %build_dir% echo [BUILD] MSVC cl detected, compiling ... set msvc_cmd=cl -MTd %msvc_driver_flags% -analyze -Fe:dn_unit_tests_msvc -Fo:dn_unit_tests_msvc if exist %build_dir%/Curl/Install/lib/libcurl-d.lib ( - set msvc_cmd=!msvc_cmd! -D DN_UNIT_TESTS_WITH_CURL -I %build_dir%/Curl/Install/include %build_dir%/Curl/Install/lib/libcurl-d.lib crypt32.lib ws2_32.lib advapi32.lib wldap32.lib iphlpapi.lib secur32.lib + set msvc_cmd=!msvc_cmd! -D DN_WITH_NET_CURL=1 -I %build_dir%/Curl/Install/include %build_dir%/Curl/Install/lib/libcurl-d.lib crypt32.lib ws2_32.lib advapi32.lib wldap32.lib iphlpapi.lib secur32.lib ) set msvc_cmd=!msvc_cmd! -link diff --git a/single_header_generator.cpp b/single_header_generator.cpp index 20727d0..bd94b75 100644 --- a/single_header_generator.cpp +++ b/single_header_generator.cpp @@ -1,5 +1,4 @@ -#define DN_H_WITH_OS 1 -#define DN_H_WITH_CORE 1 +#define DN_WITH_OS 1 #if defined(USE_SINGLE_HEADER) #include "Single-Header/dn_single_header.h" #else @@ -91,7 +90,7 @@ static void AppendCppFileLineByLine(DN_Str8Builder *dest, DN_Str8 cpp_path) int main(int argc, char **argv) { DN_Core dn = {}; - DN_Init(&dn, DN_InitFlags_OS | DN_InitFlags_ThreadContext, DN_TCInitArgsDefault()); + DN_Init(&dn, DN_InitFlags_OS, DN_TCInitArgsDefault()); if (argc != 3) { DN_OS_PrintErrF("USAGE: %s ", argv[0]);