diff --git a/Single_Header/dn_single_header.cpp b/Single_Header/dn_single_header.cpp new file mode 100644 index 0000000..5935a58 --- /dev/null +++ b/Single_Header/dn_single_header.cpp @@ -0,0 +1,11391 @@ +// Generated by the DN single header generator 2025-06-26 22:13:43 + +#define DN_BASE_INC_CPP + +// DN: Single header generator inlined this file => #include "Base/dn_base.cpp" +#define DN_BASE_CPP + +// DN: Single header generator commented out this header => #include "../dn_clangd.h" + +// NOTE: [$INTR] Intrinsics //////////////////////////////////////////////////////////////////////// +#if !defined(DN_PLATFORM_ARM64) && !defined(DN_PLATFORM_EMSCRIPTEN) + #if defined(DN_COMPILER_GCC) || defined(DN_COMPILER_CLANG) + #include + #endif + +DN_CPUFeatureDecl g_dn_cpu_feature_decl[DN_CPUFeature_Count]; + +DN_API DN_CPUIDResult DN_CPU_ID(DN_CPUIDArgs args) +{ + DN_CPUIDResult result = {}; + __cpuidex(result.values, args.eax, args.ecx); + return result; +} + +DN_API DN_USize DN_CPU_HasFeatureArray(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_CPU_HasFeature(DN_CPUReport const *report, DN_CPUFeature feature) +{ + DN_CPUFeatureQuery query = {}; + query.feature = feature; + bool result = DN_CPU_HasFeatureArray(report, &query, 1) == 1; + return result; +} + +DN_API bool DN_CPU_HasAllFeatures(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_CPU_HasFeature(report, features[index]); + return result; +} + +DN_API void DN_CPU_SetFeature(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_CPU_Report() +{ + DN_CPUReport result = {}; + 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_CPU_ID(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_CPU_ID(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_CPU_ID(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_CPU_ID(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_InvalidCodePath; break; + } + + if (available) + DN_CPU_SetFeature(&result, DN_CAST(DN_CPUFeature) ext_index); + } + + return result; +} +#endif // !defined(DN_PLATFORM_ARM64) && !defined(DN_PLATFORM_EMSCRIPTEN) + +// NOTE: DN_TicketMutex //////////////////////////////////////////////////////////////////////////// +DN_API void DN_TicketMutex_Begin(DN_TicketMutex *mutex) +{ + unsigned int ticket = DN_Atomic_AddU32(&mutex->ticket, 1); + DN_TicketMutex_BeginTicket(mutex, ticket); +} + +DN_API void DN_TicketMutex_End(DN_TicketMutex *mutex) +{ + DN_Atomic_AddU32(&mutex->serving, 1); +} + +DN_API DN_UInt DN_TicketMutex_MakeTicket(DN_TicketMutex *mutex) +{ + DN_UInt result = DN_Atomic_AddU32(&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_Bit_UnsetInplace(DN_USize *flags, DN_USize bitfield) +{ + *flags = (*flags & ~bitfield); +} + +DN_API void DN_Bit_SetInplace(DN_USize *flags, DN_USize bitfield) +{ + *flags = (*flags | bitfield); +} + +DN_API bool DN_Bit_IsSet(DN_USize bits, DN_USize bits_to_set) +{ + auto result = DN_CAST(bool)((bits & bits_to_set) == bits_to_set); + return result; +} + +DN_API bool DN_Bit_IsNotSet(DN_USize bits, DN_USize bits_to_check) +{ + auto result = !DN_Bit_IsSet(bits, bits_to_check); + return result; +} + +// NOTE: DN_Safe /////////////////////////////////////////////////////////////////////////////////// +DN_API DN_I64 DN_Safe_AddI64(int64_t a, int64_t b) +{ + DN_I64 result = DN_CheckF(a <= INT64_MAX - b, "a=%zd, b=%zd", a, b) ? (a + b) : INT64_MAX; + return result; +} + +DN_API DN_I64 DN_Safe_MulI64(int64_t a, int64_t b) +{ + DN_I64 result = DN_CheckF(a <= INT64_MAX / b, "a=%zd, b=%zd", a, b) ? (a * b) : INT64_MAX; + return result; +} + +DN_API DN_U64 DN_Safe_AddU64(DN_U64 a, DN_U64 b) +{ + DN_U64 result = DN_CheckF(a <= UINT64_MAX - b, "a=%zu, b=%zu", a, b) ? (a + b) : UINT64_MAX; + return result; +} + +DN_API DN_U64 DN_Safe_SubU64(DN_U64 a, DN_U64 b) +{ + DN_U64 result = DN_CheckF(a >= b, "a=%zu, b=%zu", a, b) ? (a - b) : 0; + return result; +} + +DN_API DN_U64 DN_Safe_MulU64(DN_U64 a, DN_U64 b) +{ + DN_U64 result = DN_CheckF(a <= UINT64_MAX / b, "a=%zu, b=%zu", a, b) ? (a * b) : UINT64_MAX; + return result; +} + +DN_API DN_U32 DN_Safe_SubU32(DN_U32 a, DN_U32 b) +{ + DN_U32 result = DN_CheckF(a >= b, "a=%u, b=%u", a, b) ? (a - b) : 0; + return result; +} + +// NOTE: DN_SaturateCastUSizeToI* //////////////////////////////////////////////////////////// +// 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_Check(DN_CAST(uintmax_t) val <= INT_MAX) ? DN_CAST(int) val : INT_MAX; + return result; +} + +DN_API int8_t DN_SaturateCastUSizeToI8(DN_USize val) +{ + int8_t result = DN_Check(DN_CAST(uintmax_t) val <= INT8_MAX) ? DN_CAST(int8_t) val : INT8_MAX; + return result; +} + +DN_API DN_I16 DN_SaturateCastUSizeToI16(DN_USize val) +{ + DN_I16 result = DN_Check(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_Check(DN_CAST(uintmax_t) val <= INT32_MAX) ? DN_CAST(DN_I32) val : INT32_MAX; + return result; +} + +DN_API int64_t DN_SaturateCastUSizeToI64(DN_USize val) +{ + int64_t result = DN_Check(DN_CAST(uintmax_t) val <= INT64_MAX) ? DN_CAST(int64_t) val : INT64_MAX; + return result; +} + +// NOTE: DN_SaturateCastUSizeToU* //////////////////////////////////////////////////////////// +// 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 = DN_Check(val <= UINT8_MAX) ? DN_CAST(DN_U8) val : UINT8_MAX; + return result; +} + +DN_API DN_U16 DN_SaturateCastUSizeToU16(DN_USize val) +{ + DN_U16 result = DN_Check(val <= UINT16_MAX) ? DN_CAST(DN_U16) val : UINT16_MAX; + return result; +} + +DN_API DN_U32 DN_SaturateCastUSizeToU32(DN_USize val) +{ + DN_U32 result = DN_Check(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_Check(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 = DN_Check(val <= INT_MAX) ? DN_CAST(int) val : INT_MAX; + return result; +} + +DN_API int8_t DN_SaturateCastU64ToI8(DN_U64 val) +{ + int8_t result = DN_Check(val <= INT8_MAX) ? DN_CAST(int8_t) val : INT8_MAX; + return result; +} + +DN_API DN_I16 DN_SaturateCastU64ToI16(DN_U64 val) +{ + DN_I16 result = DN_Check(val <= INT16_MAX) ? DN_CAST(DN_I16) val : INT16_MAX; + return result; +} + +DN_API DN_I32 DN_SaturateCastU64ToI32(DN_U64 val) +{ + DN_I32 result = DN_Check(val <= INT32_MAX) ? DN_CAST(DN_I32) val : INT32_MAX; + return result; +} + +DN_API int64_t DN_SaturateCastU64ToI64(DN_U64 val) +{ + int64_t result = DN_Check(val <= INT64_MAX) ? DN_CAST(int64_t) val : INT64_MAX; + return result; +} + +// Both operands are unsigned and the lowest rank operand will be promoted to +// match the highest rank operand. +DN_API unsigned int DN_SaturateCastU64ToUInt(DN_U64 val) +{ + unsigned int result = DN_Check(val <= UINT8_MAX) ? DN_CAST(unsigned int) val : UINT_MAX; + return result; +} + +DN_API DN_U8 DN_SaturateCastU64ToU8(DN_U64 val) +{ + DN_U8 result = DN_Check(val <= UINT8_MAX) ? DN_CAST(DN_U8) val : UINT8_MAX; + return result; +} + +DN_API DN_U16 DN_SaturateCastU64ToU16(DN_U64 val) +{ + DN_U16 result = DN_Check(val <= UINT16_MAX) ? DN_CAST(DN_U16) val : UINT16_MAX; + return result; +} + +DN_API DN_U32 DN_SaturateCastU64ToU32(DN_U64 val) +{ + DN_U32 result = DN_Check(val <= UINT32_MAX) ? DN_CAST(DN_U32) val : UINT32_MAX; + return result; +} + +// NOTE: DN_SaturateCastISizeToI* //////////////////////////////////////////////////////////// +// 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 int8_t DN_SaturateCastISizeToI8(DN_ISize val) +{ + DN_Assert(val >= INT8_MIN && val <= INT8_MAX); + int8_t result = DN_CAST(int8_t) 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 int64_t DN_SaturateCastISizeToI64(DN_ISize val) +{ + DN_Assert(DN_CAST(int64_t) val >= INT64_MIN && DN_CAST(int64_t) val <= INT64_MAX); + int64_t result = DN_CAST(int64_t) DN_Clamp(DN_CAST(int64_t) val, INT64_MIN, INT64_MAX); + return result; +} + +// NOTE: DN_SaturateCastISizeToU* //////////////////////////////////////////////////////////// +// If the value is a negative integer, we clamp to 0. Otherwise, we know that +// the value is >=0, we can upcast safely to bounds check against the maximum +// allowed value. +DN_API unsigned int DN_SaturateCastISizeToUInt(DN_ISize val) +{ + unsigned int result = 0; + if (DN_Check(val >= DN_CAST(DN_ISize) 0)) { + if (DN_Check(DN_CAST(uintmax_t) val <= UINT_MAX)) + result = DN_CAST(unsigned int) val; + else + result = UINT_MAX; + } + return result; +} + +DN_API DN_U8 DN_SaturateCastISizeToU8(DN_ISize val) +{ + DN_U8 result = 0; + if (DN_Check(val >= DN_CAST(DN_ISize) 0)) { + if (DN_Check(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 (DN_Check(val >= DN_CAST(DN_ISize) 0)) { + if (DN_Check(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 (DN_Check(val >= DN_CAST(DN_ISize) 0)) { + if (DN_Check(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 (DN_Check(val >= DN_CAST(DN_ISize) 0)) { + if (DN_Check(DN_CAST(uintmax_t) val <= UINT64_MAX)) + result = DN_CAST(DN_U64) val; + else + result = UINT64_MAX; + } + return result; +} + +// NOTE: DN_SaturateCastI64To* /////////////////////////////////////////////////////////////// +// Both operands are signed so the lowest rank operand will be promoted to +// match the highest rank operand. +DN_API DN_ISize DN_SaturateCastI64ToISize(int64_t val) +{ + DN_Check(val >= DN_ISIZE_MIN && val <= DN_ISIZE_MAX); + DN_ISize result = DN_CAST(int64_t) DN_Clamp(val, DN_ISIZE_MIN, DN_ISIZE_MAX); + return result; +} + +DN_API int8_t DN_SaturateCastI64ToI8(int64_t val) +{ + DN_Check(val >= INT8_MIN && val <= INT8_MAX); + int8_t result = DN_CAST(int8_t) DN_Clamp(val, INT8_MIN, INT8_MAX); + return result; +} + +DN_API DN_I16 DN_SaturateCastI64ToI16(int64_t val) +{ + DN_Check(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_SaturateCastI64ToI32(int64_t val) +{ + DN_Check(val >= INT32_MIN && val <= INT32_MAX); + DN_I32 result = DN_CAST(DN_I32) DN_Clamp(val, INT32_MIN, INT32_MAX); + return result; +} + +DN_API unsigned int DN_SaturateCastI64ToUInt(int64_t val) +{ + unsigned int result = 0; + if (DN_Check(val >= DN_CAST(int64_t) 0)) { + if (DN_Check(DN_CAST(uintmax_t) val <= UINT_MAX)) + result = DN_CAST(unsigned int) val; + else + result = UINT_MAX; + } + return result; +} + +DN_API DN_ISize DN_SaturateCastI64ToUSize(int64_t val) +{ + DN_USize result = 0; + if (DN_Check(val >= DN_CAST(int64_t) 0)) { + if (DN_Check(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(int64_t val) +{ + DN_U8 result = 0; + if (DN_Check(val >= DN_CAST(int64_t) 0)) { + if (DN_Check(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(int64_t val) +{ + DN_U16 result = 0; + if (DN_Check(val >= DN_CAST(int64_t) 0)) { + if (DN_Check(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(int64_t val) +{ + DN_U32 result = 0; + if (DN_Check(val >= DN_CAST(int64_t) 0)) { + if (DN_Check(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(int64_t val) +{ + DN_U64 result = 0; + if (DN_Check(val >= DN_CAST(int64_t) 0)) { + if (DN_Check(DN_CAST(uintmax_t) val <= UINT64_MAX)) + result = DN_CAST(DN_U64) val; + else + result = UINT64_MAX; + } + return result; +} + +DN_API int8_t DN_SaturateCastIntToI8(int val) +{ + DN_Check(val >= INT8_MIN && val <= INT8_MAX); + int8_t result = DN_CAST(int8_t) DN_Clamp(val, INT8_MIN, INT8_MAX); + return result; +} + +DN_API DN_I16 DN_SaturateCastIntToI16(int val) +{ + DN_Check(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_U8 DN_SaturateCastIntToU8(int val) +{ + DN_U8 result = 0; + if (DN_Check(val >= DN_CAST(DN_ISize) 0)) { + if (DN_Check(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 (DN_Check(val >= DN_CAST(DN_ISize) 0)) { + if (DN_Check(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) +{ + static_assert(sizeof(val) <= sizeof(DN_U32), "Sanity check to allow simplifying of casting"); + DN_U32 result = 0; + if (DN_Check(val >= 0)) + result = DN_CAST(DN_U32) val; + return result; +} + +DN_API DN_U64 DN_SaturateCastIntToU64(int val) +{ + static_assert(sizeof(val) <= sizeof(DN_U64), "Sanity check to allow simplifying of casting"); + DN_U64 result = 0; + if (DN_Check(val >= 0)) + result = DN_CAST(DN_U64) val; + return result; +} + +// NOTE: DN_Asan ////////////////////////////////////////////////////////////////////////// //////// +static_assert(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_ASAN_PoisonMemoryRegion(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_HardAssert(__asan_address_is_poisoned(ptr)); + DN_HardAssert(__asan_address_is_poisoned((char *)ptr + (size - 1))); + } +#else + (void)ptr; + (void)size; +#endif +} + +DN_API void DN_ASAN_UnpoisonMemoryRegion(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_HardAssert(__asan_region_is_poisoned((void *)ptr, size) == 0); +#else + (void)ptr; + (void)size; +#endif +} +// DN: Single header generator inlined this file => #include "Base/dn_base_containers.cpp" +#define DN_CONTAINERS_CPP + +// DN: Single header generator commented out this header => #include "../dn_base_inc.h" + +DN_API void *DN_CArray2_MakeArray(void *data, DN_USize *size, DN_USize max, DN_USize data_size, DN_USize make_size, DN_ZeroMem zero_mem) +{ + void *result = nullptr; + DN_USize new_size = *size + make_size; + if (new_size <= max) { + result = DN_CAST(char *) data + (data_size * size[0]); + *size = new_size; + if (zero_mem == DN_ZeroMem_Yes) + DN_Memset(result, 0, data_size * make_size); + } + + return result; +} + +DN_API void *DN_CArray2_AddArray(void *data, DN_USize *size, DN_USize max, DN_USize data_size, void *elems, DN_USize elems_count, DN_ArrayAdd add) +{ + void *result = DN_CArray2_MakeArray(data, size, max, data_size, elems_count, DN_ZeroMem_No); + if (result) { + if (add == DN_ArrayAdd_Append) { + DN_Memcpy(result, elems, elems_count * data_size); + } else { + char *move_dest = DN_CAST(char *)data + (elems_count * data_size); // Shift elements forward + char *move_src = DN_CAST(char *)data; + DN_Memmove(move_dest, move_src, data_size * size[0]); + DN_Memcpy(data, elems, data_size * elems_count); + } + } + return result; +} + +DN_API bool DN_CArray2_GrowIfNeededFromPool(void **data, DN_USize size, DN_USize *max, DN_USize data_size, DN_Pool *pool) +{ + bool result = true; + if (size >= *max) { + DN_USize new_max = DN_Max(*max * 2, 8); + DN_USize bytes_to_alloc = data_size * new_max; + void *buffer = DN_Pool_NewArray(pool, DN_U8, bytes_to_alloc); + if (buffer) { + DN_USize bytes_to_copy = data_size * size; + DN_Memcpy(buffer, *data, bytes_to_copy); + DN_Pool_Dealloc(pool, *data); + *data = buffer; + *max = new_max; + } else { + result = false; + } + } + + return result; +} + +DN_API DN_ArrayEraseResult DN_CArray2_EraseRange(void *data, DN_USize *size, DN_USize elem_size, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase) +{ + DN_ArrayEraseResult result = {}; + if (!data || !size || *size == 0 || count == 0) + return result; + + // Compute the range to erase + DN_USize start = 0, end = 0; + if (count < 0) { + DN_USize abs_count = DN_Abs(count); + start = begin_index >= abs_count ? begin_index - abs_count + 1 : 0; + end = begin_index >= abs_count ? begin_index + 1 : 0; + } 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; + result.it_index = start; + return result; +} + +DN_API void *DN_CSLList_Detach(void **link, void **next) +{ + void *result = *link; + if (*link) { + *link = *next; + *next = nullptr; + } + return result; +} + +DN_API bool DN_Ring_HasSpace(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_Ring_HasData(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_Ring_Write(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_Ring_Read(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; +} + +// NOTE: DN_CArray ///////////////////////////////////////////////////////////////////////////////// +template +DN_ArrayEraseResult DN_CArray_EraseRange(T *data, DN_USize *size, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase) +{ + DN_ArrayEraseResult result = {}; + if (!data || !size || *size == 0 || count == 0) + return result; + + DN_AssertF(count != -1, "There's a bug with negative element erases, see the DN_VArray section in dn_docs.cpp"); + + // NOTE: Caculate the end index of the erase range + DN_ISize abs_count = DN_Abs(count); + DN_USize end_index = 0; + if (count < 0) { + end_index = begin_index - (abs_count - 1); + if (end_index > begin_index) + end_index = 0; + } else { + end_index = begin_index + (abs_count - 1); + if (end_index < begin_index) + end_index = (*size) - 1; + } + + // NOTE: Ensure begin_index < one_past_end_index + if (end_index < begin_index) { + DN_USize tmp = begin_index; + begin_index = end_index; + end_index = tmp; + } + + // NOTE: Ensure indexes are within valid bounds + begin_index = DN_Min(begin_index, *size); + end_index = DN_Min(end_index, *size - 1); + + // NOTE: Erase the items in the range [begin_index, one_past_end_index) + DN_USize one_past_end_index = end_index + 1; + DN_USize erase_count = one_past_end_index - begin_index; + if (erase_count) { + T *end = data + *size; + T *dest = data + begin_index; + if (erase == DN_ArrayErase_Stable) { + T *src = dest + erase_count; + DN_Memmove(dest, src, (end - src) * sizeof(T)); + } else { + T *src = end - erase_count; + DN_Memcpy(dest, src, (end - src) * sizeof(T)); + } + *size -= erase_count; + } + + result.items_erased = erase_count; + result.it_index = begin_index; + return result; +} + +template +T *DN_CArray_MakeArray(T *data, DN_USize *size, DN_USize max, DN_USize count, DN_ZeroMem zero_mem) +{ + if (!data || !size || count == 0) + return nullptr; + + if (!DN_CheckF((*size + count) <= max, "Array is out of space (user requested +%zu items, array has %zu/%zu items)", count, *size, max)) + return nullptr; + + // TODO: Use placement new? Why doesn't this work? + T *result = data + *size; + *size += count; + if (zero_mem == DN_ZeroMem_Yes) + DN_Memset(result, 0, sizeof(*result) * count); + return result; +} + +template +T *DN_CArray_InsertArray(T *data, DN_USize *size, DN_USize max, DN_USize index, T const *items, DN_USize count) +{ + T *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); + char const *dest = DN_CAST(char *)(data + (clamped_index + count)); + char const *end = DN_CAST(char *)(data + (*size)); + DN_USize bytes_to_move = end - src; + DN_Memmove(DN_CAST(void *) dest, src, bytes_to_move); + } + + result = data + clamped_index; + DN_Memcpy(result, items, sizeof(T) * count); + *size += count; + return result; +} + +template +T DN_CArray_PopFront(T *data, DN_USize *size, DN_USize count) +{ + T result = {}; + if (!data || !size || *size <= 0) + return result; + + result = data[0]; + DN_USize pop_count = DN_Min(count, *size); + DN_Memmove(data, data + pop_count, (*size - pop_count) * sizeof(T)); + *size -= pop_count; + return result; +} + +template +T DN_CArray_PopBack(T *data, DN_USize *size, DN_USize count) +{ + T result = {}; + if (!data || !size || *size <= 0) + return result; + + DN_USize pop_count = DN_Min(count, *size); + result = data[(*size - 1)]; + *size -= pop_count; + return result; +} + +template +DN_ArrayFindResult DN_CArray_Find(T *data, DN_USize size, T const &value) +{ + DN_ArrayFindResult result = {}; + if (!data || size <= 0) + return result; + + for (DN_USize index = 0; !result.data && index < size; index++) { + T *item = data + index; + if (*item == value) { + result.data = item; + result.index = index; + } + } + + return result; +} + +#if !defined(DN_NO_SARRAY) +// NOTE: DN_SArray ///////////////////////////////////////////////////////////////////////////////// +template +DN_SArray DN_SArray_Init(DN_Arena *arena, DN_USize size, DN_ZeroMem zero_mem) +{ + DN_SArray result = {}; + if (!arena || !size) + return result; + result.data = DN_Arena_NewArray(arena, T, size, zero_mem); + if (result.data) + result.max = size; + return result; +} + +template +DN_SArray DN_SArray_InitSlice(DN_Arena *arena, DN_Slice slice, DN_USize size, DN_ZeroMem zero_mem) +{ + DN_USize max = DN_Max(slice.size, size); + DN_SArray result = DN_SArray_Init(arena, max, DN_ZeroMem_No); + if (DN_SArray_IsValid(&result)) { + DN_SArray_AddArray(&result, slice.data, slice.size); + if (zero_mem == DN_ZeroMem_Yes) + DN_Memset(result.data + result.size, 0, (result.max - result.size) * sizeof(T)); + } + return result; +} + +template +DN_SArray DN_SArray_InitCArray(DN_Arena *arena, T const (&array)[N], DN_USize size, DN_ZeroMem zero_mem) +{ + DN_SArray result = DN_SArray_InitSlice(arena, DN_Slice_Init(DN_CAST(T *) array, N), size, zero_mem); + return result; +} + +template +DN_SArray DN_SArray_InitBuffer(T *buffer, DN_USize size) +{ + DN_SArray result = {}; + result.data = buffer; + result.max = size; + return result; +} + +template +bool DN_SArray_IsValid(DN_SArray const *array) +{ + bool result = array && array->data && array->size <= array->max; + return result; +} + +template +DN_Slice DN_SArray_Slice(DN_SArray const *array) +{ + DN_Slice result = {}; + if (array) + result = DN_Slice_Init(DN_CAST(T *) array->data, array->size); + return result; +} + +template +T *DN_SArray_MakeArray(DN_SArray *array, DN_USize count, DN_ZeroMem zero_mem) +{ + if (!DN_SArray_IsValid(array)) + return nullptr; + T *result = DN_CArray_MakeArray(array->data, &array->size, array->max, count, zero_mem); + return result; +} + +template +T *DN_SArray_Make(DN_SArray *array, DN_ZeroMem zero_mem) +{ + T *result = DN_SArray_MakeArray(array, 1, zero_mem); + return result; +} + +template +T *DN_SArray_AddArray(DN_SArray *array, T const *items, DN_USize count) +{ + T *result = DN_SArray_MakeArray(array, count, DN_ZeroMem_No); + if (result) + DN_Memcpy(result, items, count * sizeof(T)); + return result; +} + +template +T *DN_SArray_AddCArray(DN_SArray *array, T const (&items)[N]) +{ + T *result = DN_SArray_AddArray(array, items, N); + return result; +} + +template +T *DN_SArray_Add(DN_SArray *array, T const &item) +{ + T *result = DN_SArray_AddArray(array, &item, 1); + return result; +} + +template +T *DN_SArray_InsertArray(DN_SArray *array, DN_USize index, T const *items, DN_USize count) +{ + T *result = nullptr; + if (!DN_SArray_IsValid(array)) + return result; + result = DN_CArray_InsertArray(array->data, &array->size, array->max, index, items, count); + return result; +} + +template +T *DN_SArray_InsertCArray(DN_SArray *array, DN_USize index, T const (&items)[N]) +{ + T *result = DN_SArray_InsertArray(array, index, items, N); + return result; +} + +template +T *DN_SArray_Insert(DN_SArray *array, DN_USize index, T const &item) +{ + T *result = DN_SArray_InsertArray(array, index, &item, 1); + return result; +} + +template +T DN_SArray_PopFront(DN_SArray *array, DN_USize count) +{ + T result = DN_CArray_PopFront(array->data, &array->size, count); + return result; +} + +template +T DN_SArray_PopBack(DN_SArray *array, DN_USize count) +{ + T result = DN_CArray_PopBack(array->data, &array->size, count); + return result; +} + +template +DN_ArrayEraseResult DN_SArray_EraseRange(DN_SArray *array, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase) +{ + DN_ArrayEraseResult result = {}; + if (!DN_SArray_IsValid(array) || array->size == 0 || count == 0) + return result; + result = DN_CArray_EraseRange(array->data, &array->size, begin_index, count, erase); + return result; +} + +template +void DN_SArray_Clear(DN_SArray *array) +{ + if (array) + array->size = 0; +} +#endif // !defined(DN_NO_SARRAY) + +#if !defined(DN_NO_FARRAY) +// NOTE: DN_FArray ///////////////////////////////////////////////////////////////////////////////// +template +DN_FArray DN_FArray_Init(T const *array, DN_USize count) +{ + DN_FArray result = {}; + bool added = DN_FArray_AddArray(&result, array, count); + DN_Assert(added); + return result; +} + +template +DN_FArray DN_FArray_InitSlice(DN_Slice slice) +{ + DN_FArray result = DN_FArray_Init(slice.data, slice.size); + return result; +} + +template +DN_FArray DN_FArray_InitCArray(T const (&items)[K]) +{ + DN_FArray result = DN_FArray_Init(items, K); + return result; +} + +template +bool DN_FArray_IsValid(DN_FArray const *array) +{ + bool result = array && array->size <= DN_ArrayCountU(array->data); + return result; +} + +template +DN_Slice DN_FArray_Slice(DN_FArray const *array) +{ + DN_Slice result = {}; + if (array) + result = DN_Slice_Init(DN_CAST(T *) array->data, array->size); + return result; +} + +template +T *DN_FArray_AddArray(DN_FArray *array, T const *items, DN_USize count) +{ + T *result = DN_FArray_MakeArray(array, count, DN_ZeroMem_No); + if (result) + DN_Memcpy(result, items, count * sizeof(T)); + return result; +} + +template +T *DN_FArray_AddCArray(DN_FArray *array, T const (&items)[K]) +{ + T *result = DN_FArray_MakeArray(array, K, DN_ZeroMem_No); + if (result) + DN_Memcpy(result, items, K * sizeof(T)); + return result; +} + +template +T *DN_FArray_Add(DN_FArray *array, T const &item) +{ + T *result = DN_FArray_AddArray(array, &item, 1); + return result; +} + +template +T *DN_FArray_MakeArray(DN_FArray *array, DN_USize count, DN_ZeroMem zero_mem) +{ + if (!DN_FArray_IsValid(array)) + return nullptr; + T *result = DN_CArray_MakeArray(array->data, &array->size, N, count, zero_mem); + return result; +} + +template +T *DN_FArray_Make(DN_FArray *array, DN_ZeroMem zero_mem) +{ + T *result = DN_FArray_MakeArray(array, 1, zero_mem); + return result; +} + +template +T *DN_FArray_InsertArray(DN_FArray *array, DN_USize index, T const *items, DN_USize count) +{ + T *result = nullptr; + if (!DN_FArray_IsValid(array)) + return result; + result = DN_CArray_InsertArray(array->data, &array->size, N, index, items, count); + return result; +} + +template +T *DN_FArray_InsertCArray(DN_FArray *array, DN_USize index, T const (&items)[K]) +{ + T *result = DN_FArray_InsertArray(array, index, items, K); + return result; +} + +template +T *DN_FArray_Insert(DN_FArray *array, DN_USize index, T const &item) +{ + T *result = DN_FArray_InsertArray(array, index, &item, 1); + return result; +} + +template +T DN_FArray_PopFront(DN_FArray *array, DN_USize count) +{ + T result = DN_CArray_PopFront(array->data, &array->size, count); + return result; +} + +template +T DN_FArray_PopBack(DN_FArray *array, DN_USize count) +{ + T result = DN_CArray_PopBack(array->data, &array->size, count); + return result; +} + +template +DN_ArrayFindResult DN_FArray_Find(DN_FArray *array, T const &find) +{ + DN_ArrayFindResult result = DN_CArray_Find(array->data, array->size, find); + return result; +} + +template +DN_ArrayEraseResult DN_FArray_EraseRange(DN_FArray *array, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase) +{ + DN_ArrayEraseResult result = {}; + if (!DN_FArray_IsValid(array) || array->size == 0 || count == 0) + return result; + result = DN_CArray_EraseRange(array->data, &array->size, begin_index, count, erase); + return result; +} + +template +void DN_FArray_Clear(DN_FArray *array) +{ + if (array) + array->size = 0; +} +#endif // !defined(DN_NO_FARRAY) + +#if !defined(DN_NO_SLICE) +template +DN_Slice DN_Slice_Init(T *const data, DN_USize size) +{ + DN_Slice result = {}; + if (data) { + result.data = data; + result.size = size; + } + return result; +} + +template +DN_Slice DN_Slice_InitCArrayCopy(DN_Arena *arena, T const (&array)[N]) +{ + DN_Slice result = DN_Slice_Alloc(arena, N, DN_ZeroMem_No); + if (result.data) + DN_Memcpy(result.data, array, sizeof(T) * N); + return result; +} + +template +DN_Slice DN_Slice_CopyPtr(DN_Arena *arena, T *const data, DN_USize size) +{ + T *copy = DN_Arena_NewArrayCopy(arena, T, data, size); + DN_Slice result = DN_Slice_Init(copy, copy ? size : 0); + return result; +} + +template +DN_Slice DN_Slice_Copy(DN_Arena *arena, DN_Slice slice) +{ + DN_Slice result = DN_Slice_CopyPtr(arena, slice.data, slice.size); + return result; +} + +template +DN_Slice DN_Slice_Alloc(DN_Arena *arena, DN_USize size, DN_ZeroMem zero_mem) +{ + DN_Slice result = {}; + if (!arena || size == 0) + return result; + result.data = DN_Arena_NewArray(arena, T, size, zero_mem); + if (result.data) + result.size = size; + return result; +} + +#endif // !defined(DN_NO_SLICE) + +#if !defined(DN_NO_DSMAP) +// NOTE: DN_DSMap ////////////////////////////////////////////////////////////////////////////////// +DN_U32 const DN_DS_MAP_DEFAULT_HASH_SEED = 0x8a1ced49; +DN_U32 const DN_DS_MAP_SENTINEL_SLOT = 0; + +template +DN_DSMap DN_DSMap_Init(DN_Arena *arena, DN_U32 size, DN_DSMapFlags flags) +{ + DN_DSMap result = {}; + if (!DN_CheckF(DN_IsPowerOfTwo(size), "Power-of-two size required, given size was '%u'", size)) + return result; + if (size <= 0) + return result; + if (!DN_Check(arena)) + return result; + result.arena = arena; + result.pool = DN_Pool_Init(arena, DN_POOL_DEFAULT_ALIGN); + result.hash_to_slot = DN_Arena_NewArray(result.arena, DN_U32, size, DN_ZeroMem_Yes); + result.slots = DN_Arena_NewArray(result.arena, DN_DSMapSlot, size, DN_ZeroMem_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_DSMap_Deinit(DN_DSMap *map, DN_ZeroMem zero_mem) +{ + if (!map) + return; + // TODO(doyle): Use zero_mem + (void)zero_mem; + DN_Arena_Deinit(map->arena); + *map = {}; +} + +template +bool DN_DSMap_IsValid(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_DSMap_Hash(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_InvalidCodePath; /*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_DSMap_HashToSlotIndex(DN_DSMap const *map, DN_DSMapKey key) +{ + DN_Assert(key.type != DN_DSMapKeyType_Invalid); + DN_U32 result = DN_DS_MAP_SENTINEL_SLOT; + if (!DN_DSMap_IsValid(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_DSMap_Find(DN_DSMap const *map, DN_DSMapKey key) +{ + DN_DSMapResult result = {}; + if (DN_DSMap_IsValid(map)) { + DN_U32 index = DN_DSMap_HashToSlotIndex(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_DSMap_Make(DN_DSMap *map, DN_DSMapKey key) +{ + DN_DSMapResult result = {}; + if (!DN_DSMap_IsValid(map)) + return result; + + DN_U32 index = DN_DSMap_HashToSlotIndex(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_DSMap_Resize(map, map->size * 2)) + return result; + result = DN_DSMap_Make(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_Pool_NewArrayCopy(&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_DSMap_Set(DN_DSMap *map, DN_DSMapKey key, T const &value) +{ + DN_DSMapResult result = {}; + if (!DN_DSMap_IsValid(map)) + return result; + + result = DN_DSMap_Make(map, key); + result.slot->value = value; + return result; +} + +template +DN_DSMapResult DN_DSMap_FindKeyU64(DN_DSMap const *map, DN_U64 key) +{ + DN_DSMapKey map_key = DN_DSMap_KeyU64(map, key); + DN_DSMapResult result = DN_DSMap_Find(map, map_key); + return result; +} + +template +DN_DSMapResult DN_DSMap_MakeKeyU64(DN_DSMap *map, DN_U64 key) +{ + DN_DSMapKey map_key = DN_DSMap_KeyU64(map, key); + DN_DSMapResult result = DN_DSMap_Make(map, map_key); + return result; +} + +template +DN_DSMapResult DN_DSMap_SetKeyU64(DN_DSMap *map, DN_U64 key, T const &value) +{ + DN_DSMapKey map_key = DN_DSMap_KeyU64(map, key); + DN_DSMapResult result = DN_DSMap_Set(map, map_key, value); + return result; +} + +template +DN_DSMapResult DN_DSMap_FindKeyStr8(DN_DSMap const *map, DN_Str8 key) +{ + DN_DSMapKey map_key = DN_DSMap_KeyStr8(map, key); + DN_DSMapResult result = DN_DSMap_Find(map, map_key); + return result; +} + +template +DN_DSMapResult DN_DSMap_MakeKeyStr8(DN_DSMap *map, DN_Str8 key) +{ + DN_DSMapKey map_key = DN_DSMap_KeyStr8(map, key); + DN_DSMapResult result = DN_DSMap_Make(map, map_key); + return result; +} + +template +DN_DSMapResult DN_DSMap_SetKeyStr8(DN_DSMap *map, DN_Str8 key, T const &value) +{ + DN_DSMapKey map_key = DN_DSMap_KeyStr8(map, key); + DN_DSMapResult result = DN_DSMap_Set(map, map_key); + return result; +} + +template +bool DN_DSMap_Resize(DN_DSMap *map, DN_U32 size) +{ + if (!DN_DSMap_IsValid(map) || size < map->occupied || size < map->initial_size) + return false; + + DN_Arena *prev_arena = map->arena; + DN_Arena new_arena = {}; + new_arena.mem_funcs = prev_arena->mem_funcs; + new_arena.flags = prev_arena->flags; + new_arena.label = prev_arena->label; + new_arena.prev = prev_arena->prev; + new_arena.next = prev_arena->next; + + DN_DSMap new_map = DN_DSMap_Init(&new_arena, size, map->flags); + if (!DN_DSMap_IsValid(&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_DSMap_Set(&new_map, old_key, old_slot->value); + } + + if ((map->flags & DN_DSMapFlags_DontFreeArenaOnResize) == 0) + DN_DSMap_Deinit(map, DN_ZeroMem_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_DSMap_Erase(DN_DSMap *map, DN_DSMapKey key) +{ + if (!DN_DSMap_IsValid(map)) + return false; + + DN_U32 index = DN_DSMap_HashToSlotIndex(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_Pool_Dealloc(&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_DSMap_HashToSlotIndex(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_DSMap_Resize(map, map->size / 2); + + return true; +} + +template +bool DN_DSMap_EraseKeyU64(DN_DSMap *map, DN_U64 key) +{ + DN_DSMapKey map_key = DN_DSMap_KeyU64(map, key); + bool result = DN_DSMap_Erase(map, map_key); + return result; +} + +template +bool DN_DSMap_EraseKeyStr8(DN_DSMap *map, DN_Str8 key) +{ + DN_DSMapKey map_key = DN_DSMap_KeyStr8(map, key); + bool result = DN_DSMap_Erase(map, map_key); + return result; +} + +template +DN_DSMapKey DN_DSMap_KeyBuffer(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_DSMap_Hash(map, result); + return result; +} + +template +DN_DSMapKey DN_DSMap_KeyBufferAsU64NoHash(DN_DSMap const *map, void const *data, DN_U32 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_DSMap_KeyU64(DN_DSMap const *map, DN_U64 u64) +{ + DN_DSMapKey result = {}; + result.type = DN_DSMapKeyType_U64; + result.u64 = u64; + result.hash = DN_DSMap_Hash(map, result); + return result; +} + +template +DN_DSMapKey DN_DSMap_KeyStr8(DN_DSMap const *map, DN_Str8 string) +{ + DN_DSMapKey result = DN_DSMap_KeyBuffer(map, string.data, string.size); + return result; +} +#endif // !defined(DN_NO_DSMAP) + +#if !defined(DN_NO_LIST) +// NOTE: DN_List /////////////////////////////////////////////////////////////////////////////////// +template +DN_List DN_List_Init(DN_USize chunk_size) +{ + DN_List result = {}; + result.chunk_size = chunk_size; + return result; +} + +template +DN_List DN_List_InitCArray(DN_Arena *arena, DN_USize chunk_size, T const (&array)[N]) +{ + DN_List result = DN_List_Init(arena, chunk_size); + for (DN_ForIndexU(index, N)) + DN_List_Add(&result, array[index]); + return result; +} + +template +DN_List DN_List_InitSliceCopy(DN_Arena *arena, DN_USize chunk_size, DN_Slice slice) +{ + DN_List result = DN_List_Init(arena, chunk_size); + for (DN_ForIndexU(index, slice.size)) + DN_List_Add(&result, slice.data[index]); + return result; +} + +template +DN_API bool DN_List_AttachTail_(DN_List *list, DN_ListChunk *tail) +{ + if (!tail) + return false; + + if (list->tail) { + list->tail->next = tail; + tail->prev = list->tail; + } + + list->tail = tail; + + if (!list->head) + list->head = list->tail; + return true; +} + +template +DN_API DN_ListChunk *DN_List_AllocArena_(DN_List *list, DN_Arena *arena, DN_USize count) +{ + auto *result = DN_Arena_New(arena, DN_ListChunk, DN_ZeroMem_Yes); + DN_ArenaTempMem tmem = DN_Arena_TempMemBegin(arena); + if (!result) + return nullptr; + + DN_USize items = DN_Max(list->chunk_size, count); + result->data = DN_Arena_NewArray(arena, T, items, DN_ZeroMem_Yes); + result->size = items; + if (!result->data) { + DN_Arena_TempMemEnd(tmem); + result = nullptr; + } + + DN_List_AttachTail_(list, result); + return result; +} + +template +DN_API DN_ListChunk *DN_List_AllocPool_(DN_List *list, DN_Pool *pool, DN_USize count) +{ + auto *result = DN_Pool_New(pool, DN_ListChunk); + if (!result) + return nullptr; + + DN_USize items = DN_Max(list->chunk_size, count); + result->data = DN_Pool_NewArray(pool, T, items); + result->size = items; + if (!result->data) { + DN_Pool_Dealloc(result); + result = nullptr; + } + + DN_List_AttachTail_(list, result); + return result; +} + +template +DN_API T *DN_List_MakeArena(DN_List *list, DN_Arena *arena, DN_USize count) +{ + if (list->chunk_size == 0) + list->chunk_size = 128; + + if (!list->tail || (list->tail->count + count) > list->tail->size) { + if (!DN_List_AllocArena_(list, arena, count)) + return nullptr; + } + + T *result = list->tail->data + list->tail->count; + list->tail->count += count; + list->count += count; + return result; +} + +template +DN_API T *DN_List_MakePool(DN_List *list, DN_Pool *pool, DN_USize count) +{ + if (list->chunk_size == 0) + list->chunk_size = 128; + + if (!list->tail || (list->tail->count + count) > list->tail->size) { + if (!DN_List_AllocPool_(list, pool, count)) + return nullptr; + } + + T *result = list->tail->data + list->tail->count; + list->tail->count += count; + list->count += count; + return result; +} + +template +DN_API T *DN_List_AddArena(DN_List *list, DN_Arena *arena, T const &value) +{ + T *result = DN_List_MakeArena(list, arena, 1); + *result = value; + return result; +} + +template +DN_API T *DN_List_AddPool(DN_List *list, DN_Pool *pool, T const &value) +{ + T *result = DN_List_MakePool(list, pool, 1); + *result = value; + return result; +} + +template +DN_API bool DN_List_AddCArray(DN_List *list, T const (&array)[N]) +{ + if (!list) + return false; + for (T const &item : array) + if (!DN_List_Add(list, item)) + return false; + return true; +} + +template +DN_API void DN_List_AddListArena(DN_List *list, DN_Arena *arena, DN_List other) +{ + if (!list) + return; + // TODO(doyle): Copy chunk by chunk + for (DN_ListIterator it = {}; DN_List_Iterate(&other, &it, 0 /*start_index*/);) + DN_List_AddArena(list, arena, *it.data); +} + +template +DN_API void DN_List_AddListPool(DN_List *list, DN_Pool *pool, DN_List other) +{ + if (!list) + return; + // TODO(doyle): Copy chunk by chunk + for (DN_ListIterator it = {}; DN_List_Iterate(&other, &it, 0 /*start_index*/);) + DN_List_AddPool(list, pool, *it.data); +} + +template +void DN_List_Clear(DN_List *list) +{ + if (!list) + return; + list->head = list->tail = nullptr; + list->count = 0; +} + +template +DN_API bool DN_List_Iterate(DN_List *list, DN_ListIterator *it, DN_USize start_index) +{ + bool result = false; + if (!list || !it || list->chunk_size <= 0) + return result; + + if (it->init) { + it->index++; + } else { + *it = {}; + if (start_index == 0) { + it->chunk = list->head; + } else { + DN_List_At(list, start_index, &it->chunk); + if (list->chunk_size > 0) + it->chunk_data_index = start_index % list->chunk_size; + } + + it->init = true; + } + + if (it->chunk) { + if (it->chunk_data_index >= it->chunk->count) { + it->chunk = it->chunk->next; + it->chunk_data_index = 0; + } + + if (it->chunk) { + it->data = it->chunk->data + it->chunk_data_index++; + result = true; + } + } + + if (!it->chunk) + DN_Assert(result == false); + return result; +} + +template +DN_API T *DN_List_At(DN_List *list, DN_USize index, DN_ListChunk **at_chunk) +{ + if (!list || index >= list->count || !list->head) + return nullptr; + + // NOTE: Scan forwards to the chunk we need. We don't have random access to chunks + DN_ListChunk **chunk = &list->head; + DN_USize running_index = index; + for (; (*chunk) && running_index >= (*chunk)->size; chunk = &((*chunk)->next)) + running_index -= (*chunk)->size; + + T *result = nullptr; + if (*chunk) + result = (*chunk)->data + running_index; + + if (result && at_chunk) + *at_chunk = *chunk; + + return result; +} + +template +DN_Slice DN_List_ToSliceCopy(DN_List const *list, DN_Arena *arena) +{ + // TODO(doyle): Chunk memcopies is much faster + DN_Slice result = DN_Slice_Alloc(arena, list->count, DN_ZeroMem_No); + if (result.size) { + DN_USize slice_index = 0; + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(6011) // Dereferencing NULL pointer 'x' + for (DN_ListIterator it = {}; DN_List_Iterate(DN_CAST(DN_List *) list, &it, 0);) + result.data[slice_index++] = *it.data; + DN_MSVC_WARNING_POP + DN_Assert(slice_index == result.size); + } + return result; +} +#endif // !defined(DN_NO_LIST) + +// NOTE: DN_Slice ////////////////////////////////////////////////////////////////////////////////// +DN_API DN_Str8 DN_Slice_Str8Render(DN_Arena *arena, DN_Slice array, DN_Str8 separator) +{ + DN_Str8 result = {}; + if (!arena) + return result; + + DN_USize total_size = 0; + for (DN_USize index = 0; index < array.size; index++) { + if (index) + total_size += separator.size; + DN_Str8 item = array.data[index]; + total_size += item.size; + } + + result = DN_Str8_Alloc(arena, total_size, DN_ZeroMem_No); + if (result.data) { + DN_USize write_index = 0; + for (DN_USize index = 0; index < array.size; index++) { + if (index) { + DN_Memcpy(result.data + write_index, separator.data, separator.size); + write_index += separator.size; + } + DN_Str8 item = array.data[index]; + DN_Memcpy(result.data + write_index, item.data, item.size); + write_index += item.size; + } + } + + return result; +} + +DN_API DN_Str8 DN_Slice_Str8RenderSpaceSeparated(DN_Arena *arena, DN_Slice array) +{ + DN_Str8 result = DN_Slice_Str8Render(arena, array, DN_STR8(" ")); + return result; +} + +DN_API DN_Str16 DN_Slice_Str16Render(DN_Arena *arena, DN_Slice array, DN_Str16 separator) +{ + DN_Str16 result = {}; + if (!arena) + return result; + + DN_USize total_size = 0; + for (DN_USize index = 0; index < array.size; index++) { + if (index) + total_size += separator.size; + DN_Str16 item = array.data[index]; + total_size += item.size; + } + + result = {DN_Arena_NewArray(arena, wchar_t, total_size + 1, DN_ZeroMem_No), total_size}; + if (result.data) { + DN_USize write_index = 0; + for (DN_USize index = 0; index < array.size; index++) { + if (index) { + DN_Memcpy(result.data + write_index, separator.data, separator.size * sizeof(result.data[0])); + write_index += separator.size; + } + DN_Str16 item = array.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_Slice_Str16RenderSpaceSeparated(DN_Arena *arena, DN_Slice array) +{ + DN_Str16 result = DN_Slice_Str16Render(arena, array, DN_STR16(L" ")); + return result; +} + +#if !defined(DN_NO_DSMAP) +// NOTE: DN_DSMap ////////////////////////////////////////////////////////////////////////////////// +DN_API DN_DSMapKey DN_DSMap_KeyU64NoHash(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_DSMap_KeyEquals(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_DSMap_KeyEquals(lhs, rhs); + return result; +} +#endif // !defined(DN_NO_DSMAP) +// DN: Single header generator inlined this file => #include "Base/dn_base_convert.cpp" +#define DN_CONVERT_CPP + +DN_API int DN_CVT_FmtBuffer3DotTruncate(char *buffer, int size, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + int size_required = DN_VSNPrintF(buffer, size, fmt, args); + int result = DN_Max(DN_Min(size_required, size - 1), 0); + if (result == size - 1) { + buffer[size - 2] = '.'; + buffer[size - 3] = '.'; + } + va_end(args); + return result; +} + +DN_API DN_CVTU64Str8 DN_CVT_U64ToStr8(uint64_t val, char separator) +{ + DN_CVTU64Str8 result = {}; + if (val == 0) { + result.data[result.size++] = '0'; + } else { + // NOTE: The number is written in reverse because we form the string by + // dividing by 10, so we write it in, then reverse it out after all is + // done. + DN_CVTU64Str8 temp = {}; + for (DN_USize digit_count = 0; val > 0; digit_count++) { + if (separator && (digit_count != 0) && (digit_count % 3 == 0)) + temp.data[temp.size++] = separator; + + auto digit = DN_CAST(char)(val % 10); + temp.data[temp.size++] = '0' + digit; + val /= 10; + } + + // NOTE: Reverse the string + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(6293) // Ill-defined for-loop + DN_MSVC_WARNING_DISABLE(6385) // Reading invalid data from 'temp.data' unsigned overflow is valid for loop termination + for (DN_USize temp_index = temp.size - 1; temp_index < temp.size; temp_index--) { + char ch = temp.data[temp_index]; + result.data[result.size++] = ch; + } + DN_MSVC_WARNING_POP + } + + return result; +} + +DN_API DN_CVTU64ByteSize DN_CVT_U64ToByteSize(uint64_t bytes, DN_CVTU64ByteSizeType desired_type) +{ + DN_CVTU64ByteSize result = {}; + result.bytes = DN_CAST(DN_F64) bytes; + if (!DN_Check(desired_type != DN_CVTU64ByteSizeType_Count)) { + result.suffix = DN_CVT_U64ByteSizeTypeString(result.type); + return result; + } + + if (desired_type == DN_CVTU64ByteSizeType_Auto) + for (; result.type < DN_CVTU64ByteSizeType_Count && result.bytes >= 1024.0; result.type = DN_CAST(DN_CVTU64ByteSizeType)(DN_CAST(DN_USize) result.type + 1)) + result.bytes /= 1024.0; + else + for (; result.type < desired_type; result.type = DN_CAST(DN_CVTU64ByteSizeType)(DN_CAST(DN_USize) result.type + 1)) + result.bytes /= 1024.0; + + result.suffix = DN_CVT_U64ByteSizeTypeString(result.type); + return result; +} + +DN_API DN_Str8 DN_CVT_U64ToByteSizeStr8(DN_Arena *arena, uint64_t bytes, DN_CVTU64ByteSizeType desired_type) +{ + DN_CVTU64ByteSize byte_size = DN_CVT_U64ToByteSize(bytes, desired_type); + DN_Str8 result = DN_Str8_InitF(arena, "%.2f%.*s", byte_size.bytes, DN_STR_FMT(byte_size.suffix)); + return result; +} + +DN_API DN_Str8 DN_CVT_U64ByteSizeTypeString(DN_CVTU64ByteSizeType type) +{ + DN_Str8 result = DN_STR8(""); + switch (type) { + case DN_CVTU64ByteSizeType_B: result = DN_STR8("B"); break; + case DN_CVTU64ByteSizeType_KiB: result = DN_STR8("KiB"); break; + case DN_CVTU64ByteSizeType_MiB: result = DN_STR8("MiB"); break; + case DN_CVTU64ByteSizeType_GiB: result = DN_STR8("GiB"); break; + case DN_CVTU64ByteSizeType_TiB: result = DN_STR8("TiB"); break; + case DN_CVTU64ByteSizeType_Count: result = DN_STR8(""); break; + case DN_CVTU64ByteSizeType_Auto: result = DN_STR8(""); break; + } + return result; +} + +DN_API DN_Str8 DN_CVT_U64ToAge(DN_Arena *arena, DN_U64 age_s, DN_CVTU64AgeUnit unit) +{ + DN_Str8 result = {}; + if (!arena) + return result; + + char buffer[512]; + DN_Arena stack_arena = DN_Arena_InitFromBuffer(buffer, sizeof(buffer), DN_ArenaFlags_NoPoison); + DN_Str8Builder builder = DN_Str8Builder_Init(&stack_arena); + DN_U64 remainder = age_s; + + if (unit & DN_CVTU64AgeUnit_Year) { + DN_USize value = remainder / DN_YearsToSec(1); + remainder -= DN_YearsToSec(value); + if (value) + DN_Str8Builder_AppendF(&builder, "%s%zuyr", builder.string_size ? " " : "", value); + } + + if (unit & DN_CVTU64AgeUnit_Week) { + DN_USize value = remainder / DN_WeeksToSec(1); + remainder -= DN_WeeksToSec(value); + if (value) + DN_Str8Builder_AppendF(&builder, "%s%zuw", builder.string_size ? " " : "", value); + } + + if (unit & DN_CVTU64AgeUnit_Day) { + DN_USize value = remainder / DN_DaysToSec(1); + remainder -= DN_DaysToSec(value); + if (value) + DN_Str8Builder_AppendF(&builder, "%s%zud", builder.string_size ? " " : "", value); + } + + if (unit & DN_CVTU64AgeUnit_Hr) { + DN_USize value = remainder / DN_HoursToSec(1); + remainder -= DN_HoursToSec(value); + if (value) + DN_Str8Builder_AppendF(&builder, "%s%zuh", builder.string_size ? " " : "", value); + } + + if (unit & DN_CVTU64AgeUnit_Min) { + DN_USize value = remainder / DN_MinutesToSec(1); + remainder -= DN_MinutesToSec(value); + if (value) + DN_Str8Builder_AppendF(&builder, "%s%zum", builder.string_size ? " " : "", value); + } + + if (unit & DN_CVTU64AgeUnit_Sec) { + DN_USize value = remainder; + if (value || builder.string_size == 0) + DN_Str8Builder_AppendF(&builder, "%s%zus", builder.string_size ? " " : "", value); + } + + result = DN_Str8Builder_Build(&builder, arena); + return result; +} + +DN_API DN_Str8 DN_CVT_F64ToAge(DN_Arena *arena, DN_F64 age_s, DN_CVTU64AgeUnit unit) +{ + DN_Str8 result = {}; + if (!arena) + return result; + + char buffer[256]; + DN_Arena stack_arena = DN_Arena_InitFromBuffer(buffer, sizeof(buffer), DN_ArenaFlags_NoPoison); + DN_Str8Builder builder = DN_Str8Builder_Init(&stack_arena); + DN_F64 remainder = age_s; + + if (unit & DN_CVTU64AgeUnit_Year) { + DN_F64 value = remainder / DN_CAST(DN_F64) DN_YearsToSec(1); + if (value >= 1.0) { + remainder -= DN_YearsToSec(value); + DN_Str8Builder_AppendF(&builder, "%s%.1fyr", builder.string_size ? " " : "", value); + } + } + + if (unit & DN_CVTU64AgeUnit_Week) { + DN_F64 value = remainder / DN_CAST(DN_F64) DN_WeeksToSec(1); + if (value >= 1.0) { + remainder -= DN_WeeksToSec(value); + DN_Str8Builder_AppendF(&builder, "%s%.1fw", builder.string_size ? " " : "", value); + } + } + + if (unit & DN_CVTU64AgeUnit_Day) { + DN_F64 value = remainder / DN_CAST(DN_F64) DN_DaysToSec(1); + if (value >= 1.0) { + remainder -= DN_WeeksToSec(value); + DN_Str8Builder_AppendF(&builder, "%s%.1fd", builder.string_size ? " " : "", value); + } + } + + if (unit & DN_CVTU64AgeUnit_Hr) { + DN_F64 value = remainder / DN_CAST(DN_F64) DN_HoursToSec(1); + if (value >= 1.0) { + remainder -= DN_HoursToSec(value); + DN_Str8Builder_AppendF(&builder, "%s%.1fh", builder.string_size ? " " : "", value); + } + } + + if (unit & DN_CVTU64AgeUnit_Min) { + DN_F64 value = remainder / DN_CAST(DN_F64) DN_MinutesToSec(1); + if (value >= 1.0) { + remainder -= DN_MinutesToSec(value); + DN_Str8Builder_AppendF(&builder, "%s%.1fm", builder.string_size ? " " : "", value); + } + } + + if (unit & DN_CVTU64AgeUnit_Sec) { + DN_F64 value = remainder; + DN_Str8Builder_AppendF(&builder, "%s%.1fs", builder.string_size ? " " : "", value); + } + + result = DN_Str8Builder_Build(&builder, arena); + return result; +} + +DN_API uint64_t DN_CVT_HexToU64(DN_Str8 hex) +{ + DN_Str8 real_hex = DN_Str8_TrimPrefix(DN_Str8_TrimPrefix(hex, DN_STR8("0x")), DN_STR8("0X")); + DN_USize max_hex_size = sizeof(uint64_t) * 2 /*hex chars per byte*/; + DN_Assert(real_hex.size <= max_hex_size); + + DN_USize size = DN_Min(max_hex_size, real_hex.size); + uint64_t result = 0; + for (DN_USize index = 0; index < size; index++) { + char ch = real_hex.data[index]; + DN_CharHexToU8 val = DN_Char_HexToU8(ch); + if (!val.success) + break; + result = (result << 4) | val.value; + } + return result; +} + +DN_API DN_Str8 DN_CVT_U64ToHex(DN_Arena *arena, uint64_t number, uint32_t flags) +{ + DN_Str8 prefix = {}; + if ((flags & DN_CVTHexU64Str8Flags_0xPrefix)) + prefix = DN_STR8("0x"); + + char const *fmt = (flags & DN_CVTHexU64Str8Flags_UppercaseHex) ? "%I64X" : "%I64x"; + DN_USize required_size = DN_CStr8_FSize(fmt, number) + prefix.size; + DN_Str8 result = DN_Str8_Alloc(arena, required_size, DN_ZeroMem_No); + + if (DN_Str8_HasData(result)) { + DN_Memcpy(result.data, prefix.data, prefix.size); + int space = DN_CAST(int) DN_Max((result.size - prefix.size) + 1, 0); /*null-terminator*/ + DN_SNPrintF(result.data + prefix.size, space, fmt, number); + } + return result; +} + +DN_API DN_CVTU64HexStr8 DN_CVT_U64ToHexStr8(uint64_t number, DN_CVTHexU64Str8Flags flags) +{ + DN_Str8 prefix = {}; + if (flags & DN_CVTHexU64Str8Flags_0xPrefix) + prefix = DN_STR8("0x"); + + DN_CVTU64HexStr8 result = {}; + DN_Memcpy(result.data, prefix.data, prefix.size); + result.size += DN_CAST(int8_t) prefix.size; + + char const *fmt = (flags & DN_CVTHexU64Str8Flags_UppercaseHex) ? "%I64X" : "%I64x"; + int size = DN_SNPrintF(result.data + result.size, DN_ArrayCountU(result.data) - result.size, fmt, number); + result.size += DN_CAST(uint8_t) size; + DN_Assert(result.size < DN_ArrayCountU(result.data)); + + // NOTE: snprintf returns the required size of the format string + // irrespective of if there's space or not, but, always null terminates so + // the last byte is wasted. + result.size = DN_Min(result.size, DN_ArrayCountU(result.data) - 1); + return result; +} + +DN_API bool DN_CVT_BytesToHexPtr(void const *src, DN_USize src_size, char *dest, DN_USize dest_size) +{ + if (!src || !dest) + return false; + + if (!DN_Check(dest_size >= src_size * 2)) + return false; + + char const *HEX = "0123456789abcdef"; + unsigned char const *src_u8 = DN_CAST(unsigned char const *) src; + for (DN_USize src_index = 0, dest_index = 0; src_index < src_size; src_index++) { + char byte = src_u8[src_index]; + char hex01 = (byte >> 4) & 0b1111; + char hex02 = (byte >> 0) & 0b1111; + dest[dest_index++] = HEX[(int)hex01]; + dest[dest_index++] = HEX[(int)hex02]; + } + + return true; +} + +DN_API DN_Str8 DN_CVT_BytesToHex(DN_Arena *arena, void const *src, DN_USize size) +{ + DN_Str8 result = {}; + if (!src || size <= 0) + return result; + + result = DN_Str8_Alloc(arena, size * 2, DN_ZeroMem_No); + result.data[result.size] = 0; + bool converted = DN_CVT_BytesToHexPtr(src, size, result.data, result.size); + DN_Assert(converted); + return result; +} + +DN_API DN_USize DN_CVT_HexToBytesPtrUnchecked(DN_Str8 hex, void *dest, DN_USize dest_size) +{ + DN_USize result = 0; + unsigned char *dest_u8 = DN_CAST(unsigned char *) dest; + + for (DN_USize hex_index = 0; hex_index < hex.size; hex_index += 2, result += 1) { + char hex01 = hex.data[hex_index]; + char hex02 = (hex_index + 1 < hex.size) ? hex.data[hex_index + 1] : 0; + char bit4_01 = DN_Char_HexToU8(hex01).value; + char bit4_02 = DN_Char_HexToU8(hex02).value; + char byte = (bit4_01 << 4) | (bit4_02 << 0); + dest_u8[result] = byte; + } + + DN_Assert(result <= dest_size); + return result; +} + +DN_API DN_USize DN_CVT_HexToBytesPtr(DN_Str8 hex, void *dest, DN_USize dest_size) +{ + hex = DN_Str8_TrimPrefix(hex, DN_STR8("0x")); + hex = DN_Str8_TrimPrefix(hex, DN_STR8("0X")); + + DN_USize result = 0; + if (!DN_Str8_HasData(hex)) + return result; + + // NOTE: Trimmed hex can be "0xf" -> "f" or "0xAB" -> "AB" + // Either way, the size can be odd or even, hence we round up to the nearest + // multiple of two to ensure that we calculate the min buffer size orrectly. + DN_USize hex_size_rounded_up = hex.size + (hex.size % 2); + DN_USize min_buffer_size = hex_size_rounded_up / 2; + if (hex.size <= 0 || !DN_Check(dest_size >= min_buffer_size)) + return result; + + result = DN_CVT_HexToBytesPtrUnchecked(hex, dest, dest_size); + return result; +} + +DN_API DN_Str8 DN_CVT_HexToBytesUnchecked(DN_Arena *arena, DN_Str8 hex) +{ + DN_USize hex_size_rounded_up = hex.size + (hex.size % 2); + DN_Str8 result = DN_Str8_Alloc(arena, (hex_size_rounded_up / 2), DN_ZeroMem_No); + if (result.data) { + DN_USize bytes_written = DN_CVT_HexToBytesPtr(hex, result.data, result.size); + DN_Assert(bytes_written == result.size); + } + return result; +} + +DN_API DN_Str8 DN_CVT_HexToBytes(DN_Arena *arena, DN_Str8 hex) +{ + hex = DN_Str8_TrimPrefix(hex, DN_STR8("0x")); + hex = DN_Str8_TrimPrefix(hex, DN_STR8("0X")); + + DN_Str8 result = {}; + if (!DN_Str8_HasData(hex)) + return result; + + if (!DN_Check(DN_Str8_IsAll(hex, DN_Str8IsAll_Hex))) + return result; + + result = DN_CVT_HexToBytesUnchecked(arena, hex); + return result; +} +// DN: Single header generator inlined this file => #include "Base/dn_base_mem.cpp" +#define DN_BASE_MEM_CPP + +static DN_ArenaBlock *DN_Arena_BlockInitFromMemFuncs_(DN_U64 reserve, DN_U64 commit, bool track_alloc, bool alloc_can_leak, DN_ArenaMemFuncs mem_funcs) +{ + DN_ArenaBlock *result = nullptr; + switch (mem_funcs.type) { + case DN_ArenaMemFuncType_Nil: + break; + + case DN_ArenaMemFuncType_Basic: { + DN_AssertF(reserve > DN_ARENA_HEADER_SIZE, "%I64u > %I64u", reserve, DN_ARENA_HEADER_SIZE); + result = DN_CAST(DN_ArenaBlock *) mem_funcs.basic_alloc(reserve); + if (!result) + return result; + + result->used = DN_ARENA_HEADER_SIZE; + result->commit = reserve; + result->reserve = reserve; + } break; + + case DN_ArenaMemFuncType_VMem: { + DN_AssertF(mem_funcs.vmem_page_size, "Page size must be set to a non-zero, power of two value"); + DN_Assert(DN_IsPowerOfTwo(mem_funcs.vmem_page_size)); + + DN_USize const page_size = mem_funcs.vmem_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_ArenaBlock *) mem_funcs.vmem_reserve(real_reserve, mem_commit, DN_MemPage_ReadWrite); + if (!result) + return result; + + if (mem_commit == DN_MemCommit_No && !mem_funcs.vmem_commit(result, real_commit, DN_MemPage_ReadWrite)) { + mem_funcs.vmem_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_Debug_TrackAlloc(result, result->reserve, alloc_can_leak); + + return result; +} + +static DN_ArenaBlock *DN_Arena_BlockInitFlagsFromMemFuncs_(DN_U64 reserve, DN_U64 commit, DN_ArenaFlags flags, DN_ArenaMemFuncs mem_funcs) +{ + bool track_alloc = (flags & DN_ArenaFlags_NoAllocTrack) == 0; + bool alloc_can_leak = flags & DN_ArenaFlags_AllocCanLeak; + DN_ArenaBlock *result = DN_Arena_BlockInitFromMemFuncs_(reserve, commit, track_alloc, alloc_can_leak, mem_funcs); + if (result && ((flags & DN_ArenaFlags_NoPoison) == 0)) + DN_ASAN_PoisonMemoryRegion(DN_CAST(char *) result + DN_ARENA_HEADER_SIZE, result->commit - DN_ARENA_HEADER_SIZE); + return result; +} + +static void DN_Arena_UpdateStatsOnNewBlock_(DN_Arena *arena, DN_ArenaBlock const *block) +{ + DN_Assert(arena); + if (block) { + arena->stats.info.used += block->used; + arena->stats.info.commit += block->commit; + arena->stats.info.reserve += block->reserve; + arena->stats.info.blocks += 1; + + arena->stats.hwm.used = DN_Max(arena->stats.hwm.used, arena->stats.info.used); + arena->stats.hwm.commit = DN_Max(arena->stats.hwm.commit, arena->stats.info.commit); + arena->stats.hwm.reserve = DN_Max(arena->stats.hwm.reserve, arena->stats.info.reserve); + arena->stats.hwm.blocks = DN_Max(arena->stats.hwm.blocks, arena->stats.info.blocks); + } +} + +DN_API DN_Arena DN_Arena_InitFromBuffer(void *buffer, DN_USize size, DN_ArenaFlags 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_ArenaBlock *block = DN_CAST(DN_ArenaBlock *) buffer; + block->commit = size; + block->reserve = size; + block->used = DN_ARENA_HEADER_SIZE; + if (block && ((flags & DN_ArenaFlags_NoPoison) == 0)) + DN_ASAN_PoisonMemoryRegion(DN_CAST(char *) block + DN_ARENA_HEADER_SIZE, block->commit - DN_ARENA_HEADER_SIZE); + + DN_Arena result = {}; + result.flags = flags | DN_ArenaFlags_NoGrow | DN_ArenaFlags_NoAllocTrack | DN_ArenaFlags_AllocCanLeak | DN_ArenaFlags_UserBuffer; + result.curr = block; + DN_Arena_UpdateStatsOnNewBlock_(&result, result.curr); + return result; +} + +DN_API DN_Arena DN_Arena_InitFromMemFuncs(DN_U64 reserve, DN_U64 commit, DN_ArenaFlags flags, DN_ArenaMemFuncs mem_funcs) +{ + DN_Arena result = {}; + result.flags = flags; + result.mem_funcs = mem_funcs; + result.flags |= DN_ArenaFlags_MemFuncs; + result.curr = DN_Arena_BlockInitFlagsFromMemFuncs_(reserve, commit, flags, mem_funcs); + DN_Arena_UpdateStatsOnNewBlock_(&result, result.curr); + return result; +} + +static void DN_Arena_BlockDeinit_(DN_Arena const *arena, DN_ArenaBlock *block) +{ + DN_USize release_size = block->reserve; + if (DN_Bit_IsNotSet(arena->flags, DN_ArenaFlags_NoAllocTrack)) + DN_Debug_TrackDealloc(block); + DN_ASAN_UnpoisonMemoryRegion(block, block->commit); + if (arena->flags & DN_ArenaFlags_MemFuncs) { + if (arena->mem_funcs.type == DN_ArenaMemFuncType_Basic) + arena->mem_funcs.basic_dealloc(block); + else + arena->mem_funcs.vmem_release(block, release_size); + } +} + +DN_API void DN_Arena_Deinit(DN_Arena *arena) +{ + for (DN_ArenaBlock *block = arena ? arena->curr : nullptr; block;) { + DN_ArenaBlock *block_to_free = block; + block = block->prev; + DN_Arena_BlockDeinit_(arena, block_to_free); + } + if (arena) + *arena = {}; +} + +DN_API bool DN_Arena_CommitTo(DN_Arena *arena, DN_U64 pos) +{ + if (!arena || !arena->curr) + return false; + + DN_ArenaBlock *curr = arena->curr; + if (pos <= curr->commit) + return true; + + DN_U64 real_pos = pos; + if (!DN_Check(pos <= curr->reserve)) + real_pos = curr->reserve; + + DN_Assert(arena->mem_funcs.vmem_page_size); + DN_USize end_commit = DN_AlignUpPowerOfTwo(real_pos, arena->mem_funcs.vmem_page_size); + DN_USize commit_size = end_commit - curr->commit; + char *commit_ptr = DN_CAST(char *) curr + curr->commit; + if (!arena->mem_funcs.vmem_commit(commit_ptr, commit_size, DN_MemPage_ReadWrite)) + return false; + + bool poison = DN_ASAN_POISON && ((arena->flags & DN_ArenaFlags_NoPoison) == 0); + if (poison) + DN_ASAN_PoisonMemoryRegion(commit_ptr, commit_size); + + curr->commit = end_commit; + return true; +} + +DN_API bool DN_Arena_Commit(DN_Arena *arena, DN_U64 size) +{ + if (!arena || !arena->curr) + return false; + DN_U64 pos = DN_Min(arena->curr->reserve, arena->curr->commit + size); + bool result = DN_Arena_CommitTo(arena, pos); + return result; +} + +DN_API bool DN_Arena_Grow(DN_Arena *arena, DN_U64 reserve, DN_U64 commit) +{ + if (arena->flags & (DN_ArenaFlags_NoGrow | DN_ArenaFlags_UserBuffer)) + return false; + + bool result = false; + DN_ArenaBlock *new_block = DN_Arena_BlockInitFlagsFromMemFuncs_(reserve, commit, arena->flags, arena->mem_funcs); + if (new_block) { + result = true; + new_block->prev = arena->curr; + arena->curr = new_block; + new_block->reserve_sum = new_block->prev->reserve_sum + new_block->prev->reserve; + DN_Arena_UpdateStatsOnNewBlock_(arena, arena->curr); + } + return result; +} + +DN_API void *DN_Arena_Alloc(DN_Arena *arena, DN_U64 size, uint8_t align, DN_ZeroMem zero_mem) +{ + if (!arena) + return nullptr; + + if (!arena->curr) { + arena->curr = DN_Arena_BlockInitFlagsFromMemFuncs_(DN_ARENA_RESERVE_SIZE, DN_ARENA_COMMIT_SIZE, arena->flags, arena->mem_funcs); + DN_Arena_UpdateStatsOnNewBlock_(arena, arena->curr); + } + + if (!arena->curr) + return nullptr; + + try_alloc_again: + DN_ArenaBlock *curr = arena->curr; + bool poison = DN_ASAN_POISON && ((arena->flags & DN_ArenaFlags_NoPoison) == 0); + uint8_t 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 (arena->flags & (DN_ArenaFlags_NoGrow | DN_ArenaFlags_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_Arena_Grow(arena, new_reserve, new_commit)) + return nullptr; + goto try_alloc_again; + } + + DN_USize prev_arena_commit = curr->commit; + if (end_pos > curr->commit) { + DN_Assert(arena->mem_funcs.vmem_page_size); + DN_Assert(arena->mem_funcs.type == DN_ArenaMemFuncType_VMem); + DN_Assert((arena->flags & DN_ArenaFlags_UserBuffer) == 0); + DN_USize end_commit = DN_AlignUpPowerOfTwo(end_pos, arena->mem_funcs.vmem_page_size); + DN_USize commit_size = end_commit - curr->commit; + char *commit_ptr = DN_CAST(char *) curr + curr->commit; + if (!arena->mem_funcs.vmem_commit(commit_ptr, commit_size, DN_MemPage_ReadWrite)) + return nullptr; + if (poison) + DN_ASAN_PoisonMemoryRegion(commit_ptr, commit_size); + curr->commit = end_commit; + arena->stats.info.commit += commit_size; + arena->stats.hwm.commit = DN_Max(arena->stats.hwm.commit, arena->stats.info.commit); + } + + void *result = DN_CAST(char *) curr + offset_pos; + curr->used += alloc_size; + arena->stats.info.used += alloc_size; + arena->stats.hwm.used = DN_Max(arena->stats.hwm.used, arena->stats.info.used); + DN_ASAN_UnpoisonMemoryRegion(result, size); + + if (zero_mem == DN_ZeroMem_Yes) { + DN_USize reused_bytes = DN_Min(prev_arena_commit - offset_pos, size); + DN_Memset(result, 0, reused_bytes); + } + + DN_Assert(arena->stats.hwm.used >= arena->stats.info.used); + DN_Assert(arena->stats.hwm.commit >= arena->stats.info.commit); + DN_Assert(arena->stats.hwm.reserve >= arena->stats.info.reserve); + DN_Assert(arena->stats.hwm.blocks >= arena->stats.info.blocks); + return result; +} + +DN_API void *DN_Arena_AllocContiguous(DN_Arena *arena, DN_U64 size, uint8_t align, DN_ZeroMem zero_mem) +{ + DN_ArenaFlags prev_flags = arena->flags; + arena->flags |= (DN_ArenaFlags_NoGrow | DN_ArenaFlags_NoPoison); + void *memory = DN_Arena_Alloc(arena, size, align, zero_mem); + arena->flags = prev_flags; + return memory; +} + +DN_API void *DN_Arena_Copy(DN_Arena *arena, void const *data, DN_U64 size, uint8_t align) +{ + if (!arena || !data || size == 0) + return nullptr; + void *result = DN_Arena_Alloc(arena, size, align, DN_ZeroMem_No); + if (result) + DN_Memcpy(result, data, size); + return result; +} + +DN_API void DN_Arena_PopTo(DN_Arena *arena, DN_U64 init_used) +{ + if (!arena || !arena->curr) + return; + DN_U64 used = DN_Max(DN_ARENA_HEADER_SIZE, init_used); + DN_ArenaBlock *curr = arena->curr; + while (curr->reserve_sum >= used) { + DN_ArenaBlock *block_to_free = curr; + arena->stats.info.used -= block_to_free->used; + arena->stats.info.commit -= block_to_free->commit; + arena->stats.info.reserve -= block_to_free->reserve; + arena->stats.info.blocks -= 1; + if (arena->flags & DN_ArenaFlags_UserBuffer) + break; + curr = curr->prev; + DN_Arena_BlockDeinit_(arena, block_to_free); + } + + arena->stats.info.used -= curr->used; + arena->curr = curr; + curr->used = used - curr->reserve_sum; + char *poison_ptr = (char *)curr + DN_AlignUpPowerOfTwo(curr->used, DN_ASAN_POISON_ALIGNMENT); + DN_USize poison_size = ((char *)curr + curr->commit) - poison_ptr; + DN_ASAN_PoisonMemoryRegion(poison_ptr, poison_size); + arena->stats.info.used += curr->used; +} + +DN_API void DN_Arena_Pop(DN_Arena *arena, DN_U64 amount) +{ + DN_ArenaBlock *curr = arena->curr; + DN_USize used_sum = curr->reserve_sum + curr->used; + if (!DN_Check(amount <= used_sum)) + amount = used_sum; + DN_USize pop_to = used_sum - amount; + DN_Arena_PopTo(arena, pop_to); +} + +DN_API DN_U64 DN_Arena_Pos(DN_Arena const *arena) +{ + DN_U64 result = (arena && arena->curr) ? arena->curr->reserve_sum + arena->curr->used : 0; + return result; +} + +DN_API void DN_Arena_Clear(DN_Arena *arena) +{ + DN_Arena_PopTo(arena, 0); +} + +DN_API bool DN_Arena_OwnsPtr(DN_Arena const *arena, void *ptr) +{ + bool result = false; + uintptr_t uint_ptr = DN_CAST(uintptr_t) ptr; + for (DN_ArenaBlock const *block = arena ? arena->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_ArenaStats DN_Arena_SumStatsArray(DN_ArenaStats const *array, DN_USize size) +{ + DN_ArenaStats result = {}; + for (DN_ForItSize(it, DN_ArenaStats const, array, size)) { + DN_ArenaStats 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_ArenaStats DN_Arena_SumStats(DN_ArenaStats lhs, DN_ArenaStats rhs) +{ + DN_ArenaStats array[] = {lhs, rhs}; + DN_ArenaStats result = DN_Arena_SumStatsArray(array, DN_ArrayCountU(array)); + return result; +} + +DN_API DN_ArenaStats DN_Arena_SumArenaArrayToStats(DN_Arena const *array, DN_USize size) +{ + DN_ArenaStats result = {}; + for (DN_USize index = 0; index < size; index++) { + DN_Arena const *arena = array + index; + result = DN_Arena_SumStats(result, arena->stats); + } + return result; +} + +DN_API DN_ArenaTempMem DN_Arena_TempMemBegin(DN_Arena *arena) +{ + DN_ArenaTempMem result = {}; + if (arena) { + DN_ArenaBlock *curr = arena->curr; + result = {arena, curr ? curr->reserve_sum + curr->used : 0}; + } + return result; +}; + +DN_API void DN_Arena_TempMemEnd(DN_ArenaTempMem mem) +{ + DN_Arena_PopTo(mem.arena, mem.used_sum); +}; + +DN_ArenaTempMemScope::DN_ArenaTempMemScope(DN_Arena *arena) +{ + mem = DN_Arena_TempMemBegin(arena); +} + +DN_ArenaTempMemScope::~DN_ArenaTempMemScope() +{ + DN_Arena_TempMemEnd(mem); +} + +// NOTE: DN_Pool /////////////////////////////////////////////////////////////////////////////////// +DN_API DN_Pool DN_Pool_Init(DN_Arena *arena, uint8_t align) +{ + DN_Pool result = {}; + if (arena) { + result.arena = arena; + result.align = align ? align : DN_POOL_DEFAULT_ALIGN; + } + return result; +} + +DN_API bool DN_Pool_IsValid(DN_Pool const *pool) +{ + bool result = pool && pool->arena && pool->align; + return result; +} + +DN_API void *DN_Pool_Alloc(DN_Pool *pool, DN_USize size) +{ + void *result = nullptr; + if (!DN_Pool_IsValid(pool)) + return result; + + DN_USize const required_size = sizeof(DN_PoolSlot) + pool->align + size; + DN_USize const size_to_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_CountLeadingZerosU64(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_Assert(register_size >= dist_to_next_msb + size_to_slot_offset); + slot_index = register_size - dist_to_next_msb - size_to_slot_offset; + } + + if (!DN_CheckF(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 + size_to_slot_offset); + DN_Assert(required_size <= (slot_size_in_bytes << 0)); + DN_Assert(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_Arena_Alloc(pool->arena, slot_size_in_bytes, alignof(DN_PoolSlot), DN_ZeroMem_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 DN_Str8 DN_Pool_AllocStr8FV(DN_Pool *pool, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_Str8 result = {}; + if (!DN_Pool_IsValid(pool)) + return result; + + DN_USize size_required = DN_CStr8_FVSize(fmt, args); + result.data = DN_CAST(char *) DN_Pool_Alloc(pool, size_required + 1); + if (result.data) { + result.size = size_required; + DN_VSNPrintF(result.data, DN_CAST(int)(result.size + 1), fmt, args); + } + return result; +} + +DN_API DN_Str8 DN_Pool_AllocStr8F(DN_Pool *pool, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8 result = DN_Pool_AllocStr8FV(pool, fmt, args); + va_end(args); + return result; +} + +DN_API DN_Str8 DN_Pool_AllocStr8Copy(DN_Pool *pool, DN_Str8 string) +{ + DN_Str8 result = {}; + if (!DN_Pool_IsValid(pool)) + return result; + + if (!DN_Str8_HasData(string)) + return result; + + char *data = DN_CAST(char *) DN_Pool_Alloc(pool, string.size + 1); + if (!data) + return result; + + DN_Memcpy(data, string.data, string.size); + data[string.size] = 0; + result = DN_Str8_Init(data, string.size); + return result; +} + +DN_API void DN_Pool_Dealloc(DN_Pool *pool, void *ptr) +{ + if (!DN_Pool_IsValid(pool) || !ptr) + return; + + DN_Assert(DN_Arena_OwnsPtr(pool->arena, 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); + + slot->next = pool->slots[slot_index]; + pool->slots[slot_index] = slot; +} + +DN_API void *DN_Pool_Copy(DN_Pool *pool, void const *data, DN_U64 size, uint8_t 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_Pool_Alloc(pool, size); + if (result) + DN_Memcpy(result, data, size); + return result; +} +// DN: Single header generator inlined this file => #include "Base/dn_base_string.cpp" +#define DN_STRING_CPP + +// NOTE: DN_CStr8 ////////////////////////////////////////////////////////////////////////////////// +DN_API DN_USize DN_CStr8_FSize(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_CStr8_FVSize(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_CStr8_Size(char const *src) +{ + DN_USize result = 0; + while (src && src[0] != 0) { + src++; + result++; + } + return result; +} + +DN_API DN_USize DN_CStr16_Size(wchar_t const *src) +{ + DN_USize result = 0; + while (src && src[0] != 0) { + src++; + result++; + } + + return result; +} + +// NOTE: DN_Str16 ////////////////////////////////////////////////////////////////////////////////// +DN_API bool operator==(DN_Str16 const &lhs, DN_Str16 const &rhs) +{ + bool result = false; + if (lhs.size == rhs.size) + result = DN_Memcmp(lhs.data, rhs.data, lhs.size * sizeof(*lhs.data)) == 0; + return result; +} + +DN_API bool operator!=(DN_Str16 const &lhs, DN_Str16 const &rhs) +{ + bool result = !(lhs == rhs); + return result; +} + +// NOTE: DN_Str8 /////////////////////////////////////////////////////////////////////////////////// +DN_API DN_Str8 DN_Str8_InitCStr8(char const *src) +{ + DN_USize size = DN_CStr8_Size(src); + DN_Str8 result = DN_Str8_Init(src, size); + return result; +} + +DN_API bool DN_Str8_IsAll(DN_Str8 string, DN_Str8IsAll is_all) +{ + bool result = DN_Str8_HasData(string); + if (!result) + return result; + + switch (is_all) { + case DN_Str8IsAll_Digits: { + for (DN_USize index = 0; result && index < string.size; index++) + result = string.data[index] >= '0' && string.data[index] <= '9'; + } break; + + case DN_Str8IsAll_Hex: { + DN_Str8 trimmed = DN_Str8_TrimPrefix(string, DN_STR8("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_Str8_End(DN_Str8 string) +{ + char *result = string.data + string.size; + return result; +} + +DN_API DN_Str8 DN_Str8_Slice(DN_Str8 string, DN_USize offset, DN_USize size) +{ + DN_Str8 result = DN_Str8_Init(string.data, 0); + if (!DN_Str8_HasData(string)) + 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_Str8_Init(string.data + capped_offset, capped_size); + return result; +} + +DN_API DN_Str8 DN_Str8_Advance(DN_Str8 string, DN_USize amount) +{ + DN_Str8 result = DN_Str8_Slice(string, amount, DN_USIZE_MAX); + return result; +} + +DN_API DN_Str8 DN_Str8_NextLine(DN_Str8 string) +{ + DN_Str8 result = DN_Str8_BinarySplit(string, DN_STR8("\n")).rhs; + return result; +} + +DN_API DN_Str8BinarySplitResult DN_Str8_BinarySplitArray(DN_Str8 string, DN_Str8 const *find, DN_USize find_size) +{ + DN_Str8BinarySplitResult result = {}; + if (!DN_Str8_HasData(string) || !find || find_size == 0) + return result; + + result.lhs = string; + for (size_t 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_Str8_Slice(string, index, find_item.size); + if (DN_Str8_Eq(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_Str8BinarySplitResult DN_Str8_BinarySplit(DN_Str8 string, DN_Str8 find) +{ + DN_Str8BinarySplitResult result = DN_Str8_BinarySplitArray(string, &find, 1); + return result; +} + +DN_API DN_Str8BinarySplitResult DN_Str8_BinarySplitLastArray(DN_Str8 string, DN_Str8 const *find, DN_USize find_size) +{ + DN_Str8BinarySplitResult result = {}; + if (!DN_Str8_HasData(string) || !find || find_size == 0) + return result; + + result.lhs = string; + for (size_t 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_Str8_Slice(string, index, find_item.size); + if (DN_Str8_Eq(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_Str8BinarySplitResult DN_Str8_BinarySplitLast(DN_Str8 string, DN_Str8 find) +{ + DN_Str8BinarySplitResult result = DN_Str8_BinarySplitLastArray(string, &find, 1); + return result; +} + +DN_API DN_USize DN_Str8_Split(DN_Str8 string, DN_Str8 delimiter, DN_Str8 *splits, DN_USize splits_count, DN_Str8SplitIncludeEmptyStrings mode) +{ + DN_USize result = 0; // The number of splits in the actual string. + if (!DN_Str8_HasData(string) || !DN_Str8_HasData(delimiter) || delimiter.size <= 0) + return result; + + DN_Str8BinarySplitResult split = {}; + DN_Str8 first = string; + do { + split = DN_Str8_BinarySplit(first, delimiter); + if (split.lhs.size || mode == DN_Str8SplitIncludeEmptyStrings_Yes) { + if (splits && result < splits_count) + splits[result] = split.lhs; + result++; + } + first = split.rhs; + } while (first.size); + + return result; +} + +DN_API DN_Slice DN_Str8_SplitAlloc(DN_Arena *arena, DN_Str8 string, DN_Str8 delimiter, DN_Str8SplitIncludeEmptyStrings mode) +{ + DN_Slice result = {}; + DN_USize splits_required = DN_Str8_Split(string, delimiter, /*splits*/ nullptr, /*count*/ 0, mode); + result.data = DN_Arena_NewArray(arena, DN_Str8, splits_required, DN_ZeroMem_No); + if (result.data) { + result.size = DN_Str8_Split(string, delimiter, result.data, splits_required, mode); + DN_Assert(splits_required == result.size); + } + return result; +} + +DN_API DN_Str8FindResult DN_Str8_FindStr8Array(DN_Str8 string, DN_Str8 const *find, DN_USize find_size, DN_Str8EqCase eq_case) +{ + DN_Str8FindResult result = {}; + if (!DN_Str8_HasData(string) || !find || find_size == 0) + return 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_Str8_Slice(string, index, find_item.size); + if (DN_Str8_Eq(string_slice, find_item, eq_case)) { + result.found = true; + result.index = index; + result.start_to_before_match = DN_Str8_Init(string.data, index); + result.match = DN_Str8_Init(string.data + index, find_item.size); + result.match_to_end_of_buffer = DN_Str8_Init(result.match.data, string.size - index); + result.after_match_to_end_of_buffer = DN_Str8_Advance(result.match_to_end_of_buffer, find_item.size); + break; + } + } + } + return result; +} + +DN_API DN_Str8FindResult DN_Str8_FindStr8(DN_Str8 string, DN_Str8 find, DN_Str8EqCase eq_case) +{ + DN_Str8FindResult result = DN_Str8_FindStr8Array(string, &find, 1, eq_case); + return result; +} + +DN_API DN_Str8FindResult DN_Str8_Find(DN_Str8 string, uint32_t flags) +{ + DN_Str8FindResult result = {}; + for (size_t index = 0; !result.found && index < string.size; index++) { + result.found |= ((flags & DN_Str8FindFlag_Digit) && DN_Char_IsDigit(string.data[index])); + result.found |= ((flags & DN_Str8FindFlag_Alphabet) && DN_Char_IsAlphabet(string.data[index])); + result.found |= ((flags & DN_Str8FindFlag_Whitespace) && DN_Char_IsWhitespace(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_Str8_Init(string.data + index, 1); + result.match_to_end_of_buffer = DN_Str8_Init(result.match.data, string.size - index); + result.after_match_to_end_of_buffer = DN_Str8_Advance(result.match_to_end_of_buffer, 1); + } + } + return result; +} + +DN_API DN_Str8 DN_Str8_Segment(DN_Arena *arena, DN_Str8 src, DN_USize segment_size, char segment_char) +{ + if (!segment_size || !DN_Str8_HasData(src)) { + DN_Str8 result = DN_Str8_Copy(arena, src); + 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_Str8_Alloc(arena, src.size + segments, DN_ZeroMem_Yes); + 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_Str8_ReverseSegment(DN_Arena *arena, DN_Str8 src, DN_USize segment_size, char segment_char) +{ + if (!segment_size || !DN_Str8_HasData(src)) { + DN_Str8 result = DN_Str8_Copy(arena, src); + 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_Str8_Alloc(arena, src.size + segments, DN_ZeroMem_Yes); + DN_USize write_index = result.size - 1; + + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(6293) // NOTE: Ill-defined loop + for (size_t 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_Str8_Eq(DN_Str8 lhs, DN_Str8 rhs, DN_Str8EqCase eq_case) +{ + if (lhs.size != rhs.size) + return false; + + if (lhs.size == 0) + return true; + + if (!lhs.data || !rhs.data) + 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_Char_ToLower(lhs.data[index]) == DN_Char_ToLower(rhs.data[index])); + } break; + } + return result; +} + +DN_API bool DN_Str8_EqInsensitive(DN_Str8 lhs, DN_Str8 rhs) +{ + bool result = DN_Str8_Eq(lhs, rhs, DN_Str8EqCase_Insensitive); + return result; +} + +DN_API bool DN_Str8_StartsWith(DN_Str8 string, DN_Str8 prefix, DN_Str8EqCase eq_case) +{ + DN_Str8 substring = {string.data, DN_Min(prefix.size, string.size)}; + bool result = DN_Str8_Eq(substring, prefix, eq_case); + return result; +} + +DN_API bool DN_Str8_StartsWithInsensitive(DN_Str8 string, DN_Str8 prefix) +{ + bool result = DN_Str8_StartsWith(string, prefix, DN_Str8EqCase_Insensitive); + return result; +} + +DN_API bool DN_Str8_EndsWith(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_Str8_Eq(substring, suffix, eq_case); + return result; +} + +DN_API bool DN_Str8_EndsWithInsensitive(DN_Str8 string, DN_Str8 suffix) +{ + bool result = DN_Str8_EndsWith(string, suffix, DN_Str8EqCase_Insensitive); + return result; +} + +DN_API bool DN_Str8_HasChar(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_Str8_TrimPrefix(DN_Str8 string, DN_Str8 prefix, DN_Str8EqCase eq_case) +{ + DN_Str8 result = string; + if (DN_Str8_StartsWith(string, prefix, eq_case)) { + result.data += prefix.size; + result.size -= prefix.size; + } + return result; +} + +DN_API DN_Str8 DN_Str8_TrimHexPrefix(DN_Str8 string) +{ + DN_Str8 result = DN_Str8_TrimPrefix(string, DN_STR8("0x"), DN_Str8EqCase_Insensitive); + return result; +} + +DN_API DN_Str8 DN_Str8_TrimSuffix(DN_Str8 string, DN_Str8 suffix, DN_Str8EqCase eq_case) +{ + DN_Str8 result = string; + if (DN_Str8_EndsWith(string, suffix, eq_case)) + result.size -= suffix.size; + return result; +} + +DN_API DN_Str8 DN_Str8_TrimAround(DN_Str8 string, DN_Str8 trim_string) +{ + DN_Str8 result = DN_Str8_TrimPrefix(string, trim_string); + result = DN_Str8_TrimSuffix(result, trim_string); + return result; +} + +DN_API DN_Str8 DN_Str8_TrimHeadWhitespace(DN_Str8 string) +{ + DN_Str8 result = string; + if (!DN_Str8_HasData(string)) + return result; + + char const *start = string.data; + char const *end = string.data + string.size; + while (start < end && DN_Char_IsWhitespace(start[0])) + start++; + + result = DN_Str8_Init(start, end - start); + return result; +} + +DN_API DN_Str8 DN_Str8_TrimTailWhitespace(DN_Str8 string) +{ + DN_Str8 result = string; + if (!DN_Str8_HasData(string)) + return result; + + char const *start = string.data; + char const *end = string.data + string.size; + while (end > start && DN_Char_IsWhitespace(end[-1])) + end--; + + result = DN_Str8_Init(start, end - start); + return result; +} + +DN_API DN_Str8 DN_Str8_TrimWhitespaceAround(DN_Str8 string) +{ + DN_Str8 result = DN_Str8_TrimHeadWhitespace(string); + result = DN_Str8_TrimTailWhitespace(result); + return result; +} + +DN_API DN_Str8 DN_Str8_TrimByteOrderMark(DN_Str8 string) +{ + DN_Str8 result = string; + if (!DN_Str8_HasData(result)) + return result; + + // TODO(dn): This is little endian + DN_Str8 UTF8_BOM = DN_STR8("\xEF\xBB\xBF"); + DN_Str8 UTF16_BOM_BE = DN_STR8("\xEF\xFF"); + DN_Str8 UTF16_BOM_LE = DN_STR8("\xFF\xEF"); + DN_Str8 UTF32_BOM_BE = DN_STR8("\x00\x00\xFE\xFF"); + DN_Str8 UTF32_BOM_LE = DN_STR8("\xFF\xFE\x00\x00"); + + result = DN_Str8_TrimPrefix(result, UTF8_BOM, DN_Str8EqCase_Sensitive); + result = DN_Str8_TrimPrefix(result, UTF16_BOM_BE, DN_Str8EqCase_Sensitive); + result = DN_Str8_TrimPrefix(result, UTF16_BOM_LE, DN_Str8EqCase_Sensitive); + result = DN_Str8_TrimPrefix(result, UTF32_BOM_BE, DN_Str8EqCase_Sensitive); + result = DN_Str8_TrimPrefix(result, UTF32_BOM_LE, DN_Str8EqCase_Sensitive); + return result; +} + +DN_API DN_Str8 DN_Str8_FileNameFromPath(DN_Str8 path) +{ + DN_Str8 separators[] = {DN_STR8("/"), DN_STR8("\\")}; + DN_Str8BinarySplitResult split = DN_Str8_BinarySplitLastArray(path, separators, DN_ArrayCountU(separators)); + DN_Str8 result = DN_Str8_HasData(split.rhs) ? split.rhs : split.lhs; + return result; +} + +DN_API DN_Str8 DN_Str8_FileNameNoExtension(DN_Str8 path) +{ + DN_Str8 file_name = DN_Str8_FileNameFromPath(path); + DN_Str8 result = DN_Str8_FilePathNoExtension(file_name); + return result; +} + +DN_API DN_Str8 DN_Str8_FilePathNoExtension(DN_Str8 path) +{ + DN_Str8BinarySplitResult split = DN_Str8_BinarySplitLast(path, DN_STR8(".")); + DN_Str8 result = split.lhs; + return result; +} + +DN_API DN_Str8 DN_Str8_FileExtension(DN_Str8 path) +{ + DN_Str8BinarySplitResult split = DN_Str8_BinarySplitLast(path, DN_STR8(".")); + DN_Str8 result = split.rhs; + return result; +} + +DN_API DN_Str8 DN_Str8_FileDirectoryFromPath(DN_Str8 path) +{ + DN_Str8 separators[] = {DN_STR8("/"), DN_STR8("\\")}; + DN_Str8BinarySplitResult split = DN_Str8_BinarySplitLastArray(path, separators, DN_ArrayCountU(separators)); + DN_Str8 result = split.lhs; + return result; +} + +DN_API DN_Str8ToU64Result DN_Str8_ToU64(DN_Str8 string, char separator) +{ + // NOTE: Argument check + DN_Str8ToU64Result result = {}; + if (!DN_Str8_HasData(string)) { + result.success = true; + return result; + } + + // NOTE: Sanitize input/output + DN_Str8 trim_string = DN_Str8_TrimWhitespaceAround(string); + if (trim_string.size == 0) { + result.success = true; + return result; + } + + // NOTE: Handle prefix '+' + DN_USize start_index = 0; + if (!DN_Char_IsDigit(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_Char_IsDigit(ch)) + return result; + + result.value = DN_Safe_MulU64(result.value, 10); + uint64_t digit = ch - '0'; + result.value = DN_Safe_AddU64(result.value, digit); + } + + result.success = true; + return result; +} + +DN_API DN_Str8ToI64Result DN_Str8_ToI64(DN_Str8 string, char separator) +{ + // NOTE: Argument check + DN_Str8ToI64Result result = {}; + if (!DN_Str8_HasData(string)) { + result.success = true; + return result; + } + + // NOTE: Sanitize input/output + DN_Str8 trim_string = DN_Str8_TrimWhitespaceAround(string); + if (trim_string.size == 0) { + result.success = true; + return result; + } + + bool negative = false; + DN_USize start_index = 0; + if (!DN_Char_IsDigit(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_Char_IsDigit(ch)) + return result; + + result.value = DN_Safe_MulU64(result.value, 10); + uint64_t digit = ch - '0'; + result.value = DN_Safe_AddU64(result.value, digit); + } + + if (negative) + result.value *= -1; + + result.success = true; + return result; +} + +DN_API DN_Str8 DN_Str8_AppendF(DN_Arena *arena, DN_Str8 string, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8 result = DN_Str8_AppendFV(arena, string, fmt, args); + va_end(args); + return result; +} + +DN_API DN_Str8 DN_Str8_AppendFV(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_Str8_InitFV(arena, fmt, args); + DN_Str8 result = DN_Str8_Alloc(arena, string.size + append.size, DN_ZeroMem_No); + 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_Str8_FillF(DN_Arena *arena, DN_USize count, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8 result = DN_Str8_FillFV(arena, count, fmt, args); + va_end(args); + return result; +} + +DN_API DN_Str8 DN_Str8_FillFV(DN_Arena *arena, DN_USize count, char const *fmt, va_list args) +{ + DN_Str8 fill = DN_Str8_InitFV(arena, fmt, args); + DN_Str8 result = DN_Str8_Alloc(arena, count * fill.size, DN_ZeroMem_No); + 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_Str8_Remove(DN_Str8 *string, DN_USize offset, DN_USize size) +{ + if (!string || !DN_Str8_HasData(*string)) + 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_Str8DotTruncateResult DN_Str8_DotTruncateMiddle(DN_Arena *arena, DN_Str8 str8, uint32_t side_size, DN_Str8 truncator) +{ + DN_Str8DotTruncateResult result = {}; + if (str8.size <= (side_size * 2)) { + result.str8 = DN_Str8_Copy(arena, str8); + return result; + } + + DN_Str8 head = DN_Str8_Slice(str8, 0, side_size); + DN_Str8 tail = DN_Str8_Slice(str8, str8.size - side_size, side_size); + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(6284) // Object passed as _Param_(3) when a string is required in call to 'DN_Str8_InitF' Actual type: 'struct DN_Str8' + result.str8 = DN_Str8_InitF(arena, "%S%S%S", head, truncator, tail); + DN_MSVC_WARNING_POP + result.truncated = true; + return result; +} + +DN_API DN_Str8 DN_Str8_Lower(DN_Arena *arena, DN_Str8 string) +{ + DN_Str8 result = DN_Str8_Copy(arena, string); + for (DN_ForIndexU(index, result.size)) + result.data[index] = DN_Char_ToLower(result.data[index]); + return result; +} + +DN_API DN_Str8 DN_Str8_Upper(DN_Arena *arena, DN_Str8 string) +{ + DN_Str8 result = DN_Str8_Copy(arena, string); + for (DN_ForIndexU(index, result.size)) + result.data[index] = DN_Char_ToUpper(result.data[index]); + return result; +} + +#if defined(__cplusplus) +DN_API bool operator==(DN_Str8 const &lhs, DN_Str8 const &rhs) +{ + bool result = DN_Str8_Eq(lhs, rhs, DN_Str8EqCase_Sensitive); + return result; +} + +DN_API bool operator!=(DN_Str8 const &lhs, DN_Str8 const &rhs) +{ + bool result = !(lhs == rhs); + return result; +} +#endif + +DN_API DN_Str8 DN_Str8_InitF(DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list va; + va_start(va, fmt); + DN_Str8 result = DN_Str8_InitFV(arena, fmt, va); + va_end(va); + return result; +} + +DN_API DN_Str8 DN_Str8_InitFV(DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_Str8 result = {}; + if (!fmt) + return result; + + DN_USize size = DN_CStr8_FVSize(fmt, args); + if (size) { + result = DN_Str8_Alloc(arena, size, DN_ZeroMem_No); + if (DN_Str8_HasData(result)) + DN_VSNPrintF(result.data, DN_SaturateCastISizeToInt(size + 1 /*null-terminator*/), fmt, args); + } + return result; +} + +DN_API DN_Str8 DN_Str8_Alloc(DN_Arena *arena, DN_USize size, DN_ZeroMem zero_mem) +{ + DN_Str8 result = {}; + result.data = DN_Arena_NewArray(arena, char, size + 1, zero_mem); + if (result.data) + result.size = size; + result.data[result.size] = 0; + return result; +} + +DN_API DN_Str8 DN_Str8_Copy(DN_Arena *arena, DN_Str8 string) +{ + DN_Str8 result = DN_Str8_Alloc(arena, string.size, DN_ZeroMem_No); + if (DN_Str8_HasData(result)) { + DN_Memcpy(result.data, string.data, string.size); + result.data[string.size] = 0; + } + return result; +} + +// NOTE: DN_Str8Builder //////////////////////////////////////////////////////////////////////////// +DN_API DN_Str8Builder DN_Str8Builder_Init(DN_Arena *arena) +{ + DN_Str8Builder result = {}; + result.arena = arena; + return result; +} + +DN_API DN_Str8Builder DN_Str8Builder_InitArrayRef(DN_Arena *arena, + DN_Str8 const *strings, + DN_USize size) +{ + DN_Str8Builder result = DN_Str8Builder_Init(arena); + DN_Str8Builder_AppendArrayRef(&result, strings, size); + return result; +} + +DN_API DN_Str8Builder DN_Str8Builder_InitArrayCopy(DN_Arena *arena, + DN_Str8 const *strings, + DN_USize size) +{ + DN_Str8Builder result = DN_Str8Builder_Init(arena); + DN_Str8Builder_AppendArrayCopy(&result, strings, size); + return result; +} + +DN_API bool DN_Str8Builder_AddArrayRef(DN_Str8Builder *builder, DN_Str8 const *strings, DN_USize size, DN_Str8BuilderAdd add) +{ + if (!builder) + return false; + + if (!strings || size <= 0) + return true; + + DN_Str8Link *links = DN_Arena_NewArray(builder->arena, DN_Str8Link, size, DN_ZeroMem_No); + 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_Str8Builder_AddArrayCopy(DN_Str8Builder *builder, DN_Str8 const *strings, DN_USize size, DN_Str8BuilderAdd add) +{ + if (!builder) + return false; + + if (!strings || size <= 0) + return true; + + DN_ArenaTempMem tmp_mem = DN_Arena_TempMemBegin(builder->arena); + bool result = true; + DN_Str8 *strings_copy = DN_Arena_NewArray(builder->arena, DN_Str8, size, DN_ZeroMem_No); + for (DN_ForIndexU(index, size)) { + strings_copy[index] = DN_Str8_Copy(builder->arena, strings[index]); + if (strings_copy[index].size != strings[index].size) { + result = false; + break; + } + } + + if (result) + result = DN_Str8Builder_AddArrayRef(builder, strings_copy, size, add); + + if (!result) + DN_Arena_TempMemEnd(tmp_mem); + + return result; +} + +DN_API bool DN_Str8Builder_AddFV(DN_Str8Builder *builder, DN_Str8BuilderAdd add, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_Str8 string = DN_Str8_InitFV(builder->arena, fmt, args); + DN_ArenaTempMem temp_mem = DN_Arena_TempMemBegin(builder->arena); + bool result = DN_Str8Builder_AddArrayRef(builder, &string, 1, add); + if (!result) + DN_Arena_TempMemEnd(temp_mem); + return result; +} + +DN_API bool DN_Str8Builder_AppendRef(DN_Str8Builder *builder, DN_Str8 string) +{ + bool result = DN_Str8Builder_AddArrayRef(builder, &string, 1, DN_Str8BuilderAdd_Append); + return result; +} + +DN_API bool DN_Str8Builder_AppendCopy(DN_Str8Builder *builder, DN_Str8 string) +{ + bool result = DN_Str8Builder_AddArrayCopy(builder, &string, 1, DN_Str8BuilderAdd_Append); + return result; +} + +DN_API bool DN_Str8Builder_AppendF(DN_Str8Builder *builder, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool result = DN_Str8Builder_AppendFV(builder, fmt, args); + va_end(args); + return result; +} + +DN_API bool DN_Str8Builder_AppendBytesRef(DN_Str8Builder *builder, void const *ptr, DN_USize size) +{ + DN_Str8 input = DN_Str8_Init(ptr, size); + bool result = DN_Str8Builder_AppendRef(builder, input); + return result; +} + +DN_API bool DN_Str8Builder_AppendBytesCopy(DN_Str8Builder *builder, void const *ptr, DN_USize size) +{ + DN_Str8 input = DN_Str8_Init(ptr, size); + bool result = DN_Str8Builder_AppendCopy(builder, input); + return result; +} + +static bool DN_Str8Builder_AppendBuilder_(DN_Str8Builder *dest, DN_Str8Builder const *src, bool copy) +{ + if (!dest) + return false; + if (!src) + return true; + + DN_Arena_TempMemBegin(dest->arena); + DN_Str8Link *links = DN_Arena_NewArray(dest->arena, DN_Str8Link, src->count, DN_ZeroMem_No); + if (!links) + return false; + + DN_Str8Link *first = nullptr; + DN_Str8Link *last = nullptr; + DN_USize link_index = 0; + bool result = true; + 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_Str8_Copy(dest->arena, it->string); + 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; + } + return true; +} + +DN_API bool DN_Str8Builder_AppendBuilderRef(DN_Str8Builder *dest, DN_Str8Builder const *src) +{ + bool result = DN_Str8Builder_AppendBuilder_(dest, src, false); + return result; +} + +DN_API bool DN_Str8Builder_AppendBuilderCopy(DN_Str8Builder *dest, DN_Str8Builder const *src) +{ + bool result = DN_Str8Builder_AppendBuilder_(dest, src, true); + return result; +} + +DN_API bool DN_Str8Builder_PrependRef(DN_Str8Builder *builder, DN_Str8 string) +{ + bool result = DN_Str8Builder_AddArrayRef(builder, &string, 1, DN_Str8BuilderAdd_Prepend); + return result; +} + +DN_API bool DN_Str8Builder_PrependCopy(DN_Str8Builder *builder, DN_Str8 string) +{ + bool result = DN_Str8Builder_AddArrayCopy(builder, &string, 1, DN_Str8BuilderAdd_Prepend); + return result; +} + +DN_API bool DN_Str8Builder_PrependF(DN_Str8Builder *builder, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool result = DN_Str8Builder_PrependFV(builder, fmt, args); + va_end(args); + return result; +} + +DN_API bool DN_Str8Builder_Erase(DN_Str8Builder *builder, DN_Str8 string) +{ + for (DN_Str8Link **it = &builder->head; *it; it = &((*it)->next)) { + if ((*it)->string == string) { + *it = (*it)->next; + builder->string_size -= string.size; + builder->count -= 1; + return true; + } + } + return false; +} + +DN_API DN_Str8Builder DN_Str8Builder_Copy(DN_Arena *arena, DN_Str8Builder const *builder) +{ + DN_Str8Builder result = DN_Str8Builder_Init(arena); + DN_Str8Builder_AppendBuilderCopy(&result, builder); + return result; +} + +DN_API DN_Str8 DN_Str8Builder_Build(DN_Str8Builder const *builder, DN_Arena *arena) +{ + DN_Str8 result = DN_Str8Builder_BuildDelimited(builder, DN_STR8(""), arena); + return result; +} + +DN_API DN_Str8 DN_Str8Builder_BuildDelimited(DN_Str8Builder const *builder, DN_Str8 delimiter, DN_Arena *arena) +{ + DN_Str8 result = DN_ZeroInit; + if (!builder || builder->string_size <= 0 || builder->count <= 0) + return result; + + DN_USize size_for_delimiter = DN_Str8_HasData(delimiter) ? ((builder->count - 1) * delimiter.size) : 0; + result.data = DN_Arena_NewArray(arena, + char, + builder->string_size + size_for_delimiter + 1 /*null terminator*/, + DN_ZeroMem_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; + if (link->next && DN_Str8_HasData(delimiter)) { + DN_Memcpy(result.data + result.size, delimiter.data, delimiter.size); + result.size += delimiter.size; + } + } + + result.data[result.size] = 0; + DN_Assert(result.size == builder->string_size + size_for_delimiter); + return result; +} + +DN_API DN_Slice DN_Str8Builder_BuildSlice(DN_Str8Builder const *builder, DN_Arena *arena) +{ + DN_Slice result = DN_ZeroInit; + if (!builder || builder->string_size <= 0 || builder->count <= 0) + return result; + + result = DN_Slice_Alloc(arena, builder->count, DN_ZeroMem_No); + if (!result.data) + return result; + + DN_USize slice_index = 0; + for (DN_Str8Link *link = builder->head; link; link = link->next) + result.data[slice_index++] = DN_Str8_Copy(arena, link->string); + + DN_Assert(slice_index == builder->count); + return result; +} + +// NOTE: DN_Char /////////////////////////////////////////////////////////////////////////////////// +DN_API bool DN_Char_IsAlphabet(char ch) +{ + bool result = (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'); + return result; +} + +DN_API bool DN_Char_IsDigit(char ch) +{ + bool result = (ch >= '0' && ch <= '9'); + return result; +} + +DN_API bool DN_Char_IsAlphaNum(char ch) +{ + bool result = DN_Char_IsAlphabet(ch) || DN_Char_IsDigit(ch); + return result; +} + +DN_API bool DN_Char_IsWhitespace(char ch) +{ + bool result = (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'); + return result; +} + +DN_API bool DN_Char_IsHex(char ch) +{ + bool result = ((ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') || (ch >= '0' && ch <= '9')); + return result; +} + +DN_API DN_CharHexToU8 DN_Char_HexToU8(char ch) +{ + DN_CharHexToU8 result = {}; + result.success = true; + if (ch >= 'a' && ch <= 'f') + result.value = ch - 'a' + 10; + else if (ch >= 'A' && ch <= 'F') + result.value = ch - 'A' + 10; + else if (ch >= '0' && ch <= '9') + result.value = ch - '0'; + else + result.success = false; + return result; +} + +static char constexpr DN_HEX_LUT[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + +DN_API char DN_Char_ToHex(char ch) +{ + char result = DN_CAST(char) - 1; + if (ch < 16) + result = DN_HEX_LUT[DN_CAST(uint8_t) ch]; + return result; +} + +DN_API char DN_Char_ToHexUnchecked(char ch) +{ + char result = DN_HEX_LUT[DN_CAST(uint8_t) ch]; + return result; +} + +DN_API char DN_Char_ToLower(char ch) +{ + char result = ch; + if (result >= 'A' && result <= 'Z') + result += 'a' - 'A'; + return result; +} + +DN_API char DN_Char_ToUpper(char ch) +{ + char result = ch; + if (result >= 'a' && result <= 'z') + result -= 'a' - 'A'; + return result; +} + +// NOTE: DN_UTF //////////////////////////////////////////////////////////////////////////////////// +DN_API int DN_UTF8_EncodeCodepoint(uint8_t utf8[4], uint32_t 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(uint8_t) 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 int DN_UTF16_EncodeCodepoint(uint16_t utf16[2], uint32_t 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(uint16_t) codepoint; + return 1; + } + + if (codepoint <= 0b1111'1111'1111'1111'1111) { + uint32_t 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: Single header generator inlined this file => #include "Base/dn_base_log.cpp" +#define DN_BASE_LOG_CPP + +// DN: Single header generator commented out this header => #include "../dn_clangd.h" + +static DN_LOGEmitFromTypeFVFunc *g_dn_base_log_emit_from_type_fv_func_; +static void *g_dn_base_log_emit_from_type_fv_user_context_; + +DN_API DN_Str8 DN_LOG_ColourEscapeCodeStr8FromRGB(DN_LOGColourType colour, DN_U8 r, DN_U8 g, DN_U8 b) +{ + DN_THREAD_LOCAL char buffer[32]; + buffer[0] = 0; + DN_Str8 result = {}; + result.size = DN_SNPrintF(buffer, + DN_ArrayCountU(buffer), + "\x1b[%d;2;%u;%u;%um", + colour == DN_LOGColourType_Fg ? 38 : 48, + r, + g, + b); + result.data = buffer; + return result; +} + +DN_API DN_Str8 DN_LOG_ColourEscapeCodeStr8FromU32(DN_LOGColourType colour, 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_Str8 result = DN_LOG_ColourEscapeCodeStr8FromRGB(colour, r, g, b); + return result; +} + +DN_API DN_LOGPrefixSize DN_LOG_MakePrefix(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_STR8("DEBUG"); break; + case DN_LOGType_Info: type_str8 = DN_STR8("INFO "); break; + case DN_LOGType_Warning: type_str8 = DN_STR8("WARN"); break; + case DN_LOGType_Error: type_str8 = DN_STR8("ERROR"); break; + case DN_LOGType_Count: type_str8 = DN_STR8("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_Str8 colour_esc = {}; + DN_Str8 bold_esc = {}; + DN_Str8 reset_esc = {}; + if (style.colour) { + bold_esc = DN_STR8(DN_LOG_BoldEscapeCode); + reset_esc = DN_STR8(DN_LOG_ResetEscapeCode); + colour_esc = DN_LOG_ColourEscapeCodeStr8FromRGB(DN_LOGColourType_Fg, style.r, style.g, style.b); + } + + DN_Str8 file_name = DN_Str8_FileNameFromPath(call_site.file); + DN_GCC_WARNING_PUSH + DN_GCC_WARNING_DISABLE(-Wformat) + DN_GCC_WARNING_DISABLE(-Wformat-extra-args) + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(4477) + 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 + ":%05I32u " // line number + , + date.year, + date.month, + date.day, + date.hour, + date.minute, + date.second, + colour_esc, // colour + bold_esc, // bold + type_str8, // type + DN_CAST(int) type_padding, + "", // type padding + reset_esc, // reset + file_name, // file name + call_site.line); // line number + DN_MSVC_WARNING_POP // '%S' requires an argument of type 'wchar_t *', but variadic argument 7 has type 'DN_Str8' + DN_GCC_WARNING_POP + + 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_LOG_SetEmitFromTypeFVFunc(DN_LOGEmitFromTypeFVFunc *print_func, void *user_data) +{ + g_dn_base_log_emit_from_type_fv_func_ = print_func; + g_dn_base_log_emit_from_type_fv_user_context_ = user_data; +} + +DN_API void DN_LOG_EmitFromType(DN_LOGTypeParam type, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, ...) +{ + DN_LOGEmitFromTypeFVFunc *func = g_dn_base_log_emit_from_type_fv_func_; + void *user_context = g_dn_base_log_emit_from_type_fv_user_context_; + if (func) { + va_list args; + va_start(args, fmt); + func(type, user_context, call_site, fmt, args); + va_end(args); + } +} + +DN_API DN_LOGTypeParam DN_LOG_MakeU32LogTypeParam(DN_LOGType type) +{ + DN_LOGTypeParam result = {}; + result.is_u32_enum = true; + result.u32 = type; + return result; +} +#define DN_OS_INC_CPP + +// DN: Single header generator inlined this file => #include "OS/dn_os_tls.cpp" +#define DN_OS_TLSCPP + +// NOTE: DN_OSTLS //////////////////////////////////////////////////////////////////////////////////// +DN_OSTLSTMem::DN_OSTLSTMem(DN_OSTLS *tls, DN_U8 arena_index, DN_OSTLSPushTMem push_tmem) +{ + DN_Assert(arena_index == DN_OSTLSArena_TMem0 || arena_index == DN_OSTLSArena_TMem1); + arena = tls->arenas + arena_index; + temp_mem = DN_Arena_TempMemBegin(arena); + destructed = false; + push_arena = push_tmem; + if (push_arena) + DN_OS_TLSPushArena(arena); +} + +DN_OSTLSTMem::~DN_OSTLSTMem() +{ + DN_Assert(destructed == false); + DN_Arena_TempMemEnd(temp_mem); + destructed = true; + if (push_arena) + DN_OS_TLSPopArena(); +} + +DN_API void DN_OS_TLSInit(DN_OSTLS *tls, DN_OSTLSInitArgs args) +{ + DN_Check(tls); + if (tls->init) + return; + + DN_U64 reserve = args.reserve ? args.reserve : DN_Kilobytes(64); + DN_U64 commit = args.commit ? args.commit : DN_Kilobytes(4); + DN_U64 err_sink_reserve = args.err_sink_reserve ? args.err_sink_reserve : DN_Kilobytes(64); + DN_U64 err_sink_commit = args.err_sink_commit ? args.err_sink_commit : DN_Kilobytes(4); + + // TODO: We shouldn't have the no alloc track flag here but the initial TLS + // init on OS init happens before CORE init. CORE init is the one responsible + // for setting up the alloc tracking data structures. + for (DN_ForItCArray(it, DN_Arena, tls->arenas)) { + DN_Arena *arena = it.data; + switch (DN_CAST(DN_OSTLSArena) it.index) { + default: *arena = DN_Arena_InitFromOSVMem(reserve, commit, DN_ArenaFlags_AllocCanLeak | DN_ArenaFlags_NoAllocTrack); break; + case DN_OSTLSArena_ErrorSink: *arena = DN_Arena_InitFromOSVMem(err_sink_reserve, err_sink_commit, DN_ArenaFlags_AllocCanLeak | DN_ArenaFlags_NoAllocTrack); break; + case DN_OSTLSArena_Count: DN_InvalidCodePath; break; + } + } + + tls->thread_id = DN_OS_ThreadID(); + tls->err_sink.arena = tls->arenas + DN_OSTLSArena_ErrorSink; + tls->init = true; +} + +DN_API void DN_OS_TLSDeinit(DN_OSTLS *tls) +{ + tls->init = false; + tls->err_sink = {}; + tls->arena_stack_index = {}; + for (DN_ForItCArray(it, DN_Arena, tls->arenas)) + DN_Arena_Deinit(it.data); +} + +DN_THREAD_LOCAL DN_OSTLS *g_dn_curr_thread_tls; +DN_API void DN_OS_TLSSetCurrentThreadTLS(DN_OSTLS *tls) +{ + g_dn_curr_thread_tls = tls; +} + +DN_API DN_OSTLS *DN_OS_TLSGet() +{ + DN_Assert(g_dn_curr_thread_tls && + "DN must be initialised (via DN_Core_Init) before calling any functions depending on " + "TLS if this is the main thread, OR, the created thread has not called " + "SetCurrentThreadTLS yet so the TLS data structure hasn't been assigned yet"); + return g_dn_curr_thread_tls; +} + +DN_API DN_Arena *DN_OS_TLSArena() +{ + DN_OSTLS *tls = DN_OS_TLSGet(); + DN_Arena *result = tls->arenas + DN_OSTLSArena_Main; + return result; +} + +// TODO: Is there a way to handle conflict arenas without the user needing to +// manually pass it in? +DN_API DN_OSTLSTMem DN_OS_TLSGetTMem(void const *conflict_arena, DN_OSTLSPushTMem push_tmem) +{ + DN_OSTLS *tls = DN_OS_TLSGet(); + DN_U8 tls_index = (DN_U8)-1; + for (DN_U8 index = DN_OSTLSArena_TMem0; index <= DN_OSTLSArena_TMem1; index++) { + DN_Arena *arena = tls->arenas + index; + if (!conflict_arena || arena != conflict_arena) { + tls_index = index; + break; + } + } + + DN_Assert(tls_index != (DN_U8)-1); + return DN_OSTLSTMem(tls, tls_index, push_tmem); +} + +DN_API void DN_OS_TLSPushArena(DN_Arena *arena) +{ + DN_Assert(arena); + DN_OSTLS *tls = DN_OS_TLSGet(); + DN_Assert(tls->arena_stack_index < DN_ArrayCountU(tls->arena_stack)); + tls->arena_stack[tls->arena_stack_index++] = arena; +} + +DN_API void DN_OS_TLSPopArena() +{ + DN_OSTLS *tls = DN_OS_TLSGet(); + DN_Assert(tls->arena_stack_index > 0); + tls->arena_stack_index--; +} + +DN_API DN_Arena *DN_OS_TLSTopArena() +{ + DN_OSTLS *tls = DN_OS_TLSGet(); + DN_Arena *result = nullptr; + if (tls->arena_stack_index) + result = tls->arena_stack[tls->arena_stack_index - 1]; + return result; +} + +DN_API void DN_OS_TLSBeginFrame(DN_Arena *frame_arena) +{ + DN_OSTLS *tls = DN_OS_TLSGet(); + tls->frame_arena = frame_arena; +} + +DN_API DN_Arena *DN_OS_TLSFrameArena() +{ + DN_OSTLS *tls = DN_OS_TLSGet(); + DN_Arena *result = tls->frame_arena; + DN_AssertF(result, "Frame arena must be set by calling DN_OS_TLSBeginFrame at the beginning of the frame"); + return result; +} + +// NOTE: DN_OSErrSink //////////////////////////////////////////////////////////////////////////////// +static void DN_OS_ErrSinkCheck_(DN_OSErrSink const *err) +{ + DN_AssertF(err->arena, "Arena should be assigned in TLS init"); + if (err->stack_size == 0) + return; + + DN_OSErrSinkNode const *node = err->stack + (err->stack_size - 1); + DN_Assert(node->mode >= DN_OSErrSinkMode_Nil && node->mode <= DN_OSErrSinkMode_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) + size_t WALK_LIMIT = 99'999; + size_t walk = 0; + for (DN_OSErrSinkMsg *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_OSErrSink *DN_OS_ErrSinkBegin_(DN_OSErrSinkMode mode, DN_CallSite call_site) +{ + DN_OSTLS *tls = DN_OS_TLSGet(); + DN_OSErrSink *err = &tls->err_sink; + DN_OSErrSink *result = err; + DN_USize arena_pos = DN_Arena_Pos(result->arena); + + if (tls->err_sink.stack_size == DN_ArrayCountU(err->stack)) { + DN_Str8Builder builder = DN_Str8Builder_InitFromTLS(); + DN_USize counter = 0; + for (DN_ForItSize(it, DN_OSErrSinkNode, err->stack, err->stack_size)) { + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(6284) // Object passed as _Param_(4) when a string is required in call to 'DN_Str8Builder_AppendF' Actual type: 'struct DN_Str8'. + DN_Str8Builder_AppendF(&builder, " [%04zu] %S:%u %S\n", counter++, it.data->call_site.file, it.data->call_site.line, it.data->call_site.function); + DN_MSVC_WARNING_POP + } + + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(6284) // Object passed as _Param_(6) when a string is required in call to 'DN_LOG_EmitFromType' Actual type: 'struct DN_Str8'. + DN_AssertF(tls->err_sink.stack_size < DN_ArrayCountU(err->stack), + "Error sink has run out of error scopes, potential leak. Scopes were\n%S", DN_Str8Builder_BuildFromTLS(&builder)); + DN_MSVC_WARNING_POP + } + + DN_OSErrSinkNode *node = tls->err_sink.stack + tls->err_sink.stack_size++; + node->arena_pos = arena_pos; + node->mode = mode; + node->call_site = call_site; + DN_DLList_InitArena(node->msg_sentinel, DN_OSErrSinkMsg, result->arena); + + // NOTE: Handle allocation error + if (!DN_Check(node && node->msg_sentinel)) { + DN_Arena_PopTo(result->arena, arena_pos); + node->msg_sentinel = nullptr; + tls->err_sink.stack_size--; + } + + return result; +} + +DN_API bool DN_OS_ErrSinkHasError(DN_OSErrSink *err) +{ + bool result = false; + if (err && err->stack_size) { + DN_OSErrSinkNode *node = err->stack + (err->stack_size - 1); + result = DN_DLList_HasItems(node->msg_sentinel); + } + return result; +} + +DN_API DN_OSErrSinkMsg *DN_OS_ErrSinkEnd(DN_Arena *arena, DN_OSErrSink *err) +{ + DN_OSErrSinkMsg *result = nullptr; + DN_OS_ErrSinkCheck_(err); + if (!err || 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_OSErrSinkNode *node = err->stack + (err->stack_size - 1); + DN_OSErrSinkMsg *prev = nullptr; + for (DN_OSErrSinkMsg *it = node->msg_sentinel->next; it != node->msg_sentinel; it = it->next) { + DN_OSErrSinkMsg *entry = DN_Arena_New(arena, DN_OSErrSinkMsg, DN_ZeroMem_Yes); + entry->msg = DN_Str8_Copy(arena, it->msg); + 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_Arena_PopTo(err->arena, node->arena_pos); + return result; +} + +static void DN_OS_ErrSinkAddMsgToStr8Builder_(DN_Str8Builder *builder, DN_OSErrSinkMsg *msg, DN_OSErrSinkMsg *end) +{ + if (msg == end) // NOTE: No error messages to add + return; + + if (msg->next == end) { + DN_OSErrSinkMsg *it = msg; + DN_Str8 file_name = DN_Str8_FileNameFromPath(it->call_site.file); + DN_Str8Builder_AppendF(builder, + "%.*s:%05I32u:%.*s %.*s", + DN_STR_FMT(file_name), + it->call_site.line, + DN_STR_FMT(it->call_site.function), + DN_STR_FMT(it->msg)); + } else { + // NOTE: More than one message + for (DN_OSErrSinkMsg *it = msg; it != end; it = it->next) { + DN_Str8 file_name = DN_Str8_FileNameFromPath(it->call_site.file); + DN_Str8Builder_AppendF(builder, + "%s - %.*s:%05I32u:%.*s%s%.*s", + it == msg ? "" : "\n", + DN_STR_FMT(file_name), + it->call_site.line, + DN_STR_FMT(it->call_site.function), + DN_Str8_HasData(it->msg) ? " " : "", + DN_STR_FMT(it->msg)); + } + } +} + +DN_API DN_Str8 DN_OS_ErrSinkEndStr8(DN_Arena *arena, DN_OSErrSink *err) +{ + DN_Str8 result = {}; + DN_OS_ErrSinkCheck_(err); + if (!err || 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_OSTLSTMem tmem = DN_OS_TLSPushTMem(arena); + DN_Str8Builder builder = DN_Str8Builder_InitFromTLS(); + DN_OSErrSinkNode *node = err->stack + (err->stack_size - 1); + DN_OS_ErrSinkAddMsgToStr8Builder_(&builder, node->msg_sentinel->next, node->msg_sentinel); + + // NOTE: Deallocate all the memory for this scope + err->stack_size--; + DN_U64 arena_pos = node->arena_pos; + DN_Arena_PopTo(err->arena, arena_pos); + + result = DN_Str8Builder_Build(&builder, arena); + return result; +} + +DN_API void DN_OS_ErrSinkEndAndIgnore(DN_OSErrSink *err) +{ + DN_OS_ErrSinkEnd(nullptr, err); +} + +DN_API bool DN_OS_ErrSinkEndAndLogError_(DN_OSErrSink *err, DN_CallSite call_site, DN_Str8 err_msg) +{ + DN_AssertF(err->stack_size, "Begin must be called before calling end"); + DN_OSErrSinkNode *node = err->stack + (err->stack_size - 1); + DN_AssertF(node->msg_sentinel, "Begin must be called before calling end"); + + DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(nullptr); + DN_OSErrSinkMode mode = node->mode; + DN_OSErrSinkMsg *msg = DN_OS_ErrSinkEnd(tmem.arena, err); + if (!msg) + return false; + + DN_Str8Builder builder = DN_Str8Builder_InitFromTLS(); + if (DN_Str8_HasData(err_msg)) { + DN_Str8Builder_AppendRef(&builder, err_msg); + DN_Str8Builder_AppendRef(&builder, DN_STR8(":")); + } else { + DN_Str8Builder_AppendRef(&builder, DN_STR8("Error(s) encountered:")); + } + + if (msg->next) // NOTE: More than 1 message + DN_Str8Builder_AppendRef(&builder, DN_STR8("\n")); + DN_OS_ErrSinkAddMsgToStr8Builder_(&builder, msg, nullptr); + + DN_Str8 log = DN_Str8Builder_BuildFromTLS(&builder); + DN_LOG_EmitFromType(DN_LOG_MakeU32LogTypeParam(DN_LOGType_Error), call_site, "%.*s", DN_STR_FMT(log)); + + if (mode == DN_OSErrSinkMode_DebugBreakOnEndAndLog) + DN_DebugBreak; + return true; +} + +DN_API bool DN_OS_ErrSinkEndAndLogErrorFV_(DN_OSErrSink *err, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 log = DN_Str8_InitFV(tmem.arena, fmt, args); + bool result = DN_OS_ErrSinkEndAndLogError_(err, call_site, log); + return result; +} + +DN_API bool DN_OS_ErrSinkEndAndLogErrorF_(DN_OSErrSink *err, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 log = DN_Str8_InitFV(tmem.arena, fmt, args); + bool result = DN_OS_ErrSinkEndAndLogError_(err, call_site, log); + va_end(args); + return result; +} + +DN_API void DN_OS_ErrSinkEndAndExitIfErrorFV_(DN_OSErrSink *err, DN_CallSite call_site, DN_U32 exit_val, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + if (DN_OS_ErrSinkEndAndLogErrorFV_(err, call_site, fmt, args)) { + DN_DebugBreak; + DN_OS_Exit(exit_val); + } +} + +DN_API void DN_OS_ErrSinkEndAndExitIfErrorF_(DN_OSErrSink *err, DN_CallSite call_site, DN_U32 exit_val, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_OS_ErrSinkEndAndExitIfErrorFV_(err, call_site, exit_val, fmt, args); + va_end(args); +} + +DN_API void DN_OS_ErrSinkAppendFV_(DN_OSErrSink *err, DN_U32 error_code, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + if (!err) + return; + DN_Assert(err && err->stack_size); + DN_OSErrSinkNode *node = err->stack + (err->stack_size - 1); + DN_AssertF(node, "Error sink must be begun by calling 'Begin' before using this function."); + + DN_OSErrSinkMsg *msg = DN_Arena_New(err->arena, DN_OSErrSinkMsg, DN_ZeroMem_Yes); + if (DN_Check(msg)) { + msg->msg = DN_Str8_InitFV(err->arena, fmt, args); + msg->error_code = error_code; + msg->call_site = DN_OS_TLSGet()->call_site; + DN_DLList_Prepend(node->msg_sentinel, msg); + + if (node->mode == DN_OSErrSinkMode_ExitOnError) + DN_OS_ErrSinkEndAndExitIfErrorF_(err, msg->call_site, error_code, "Fatal error %u", error_code); + } +} + +DN_API void DN_OS_ErrSinkAppendF_(DN_OSErrSink *err, DN_U32 error_code, DN_FMT_ATTRIB char const *fmt, ...) +{ + if (!err) + return; + va_list args; + va_start(args, fmt); + DN_OS_ErrSinkAppendFV_(err, error_code, fmt, args); + va_end(args); +} + +// DN: Single header generator inlined this file => #include "OS/dn_os.cpp" +#define DN_OS_CPP + +#if defined(DN_PLATFORM_POSIX) +#include // get_nprocs +#include // getpagesize +#endif + +static DN_OSCore *g_dn_os_core_; + +static void DN_OS_LOGEmitFromTypeTypeFV_(DN_LOGTypeParam type, void *user_data, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_Assert(user_data); + DN_OSCore *core = DN_CAST(DN_OSCore *)user_data; + + // NOTE: Open log file for appending if requested //////////////////////////////////////////////// + DN_TicketMutex_Begin(&core->log_file_mutex); + if (core->log_to_file && !core->log_file.handle && !core->log_file.error) { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(6284) // Object passed as _Param_(3) when a string is required in call to 'DN_OS_PathF' Actual type: 'struct DN_Str8'. + DN_Str8 log_path = DN_OS_PathF(tmem.arena, "%S/dn.log", DN_OS_EXEDir(tmem.arena)); + DN_MSVC_WARNING_POP + core->log_file = DN_OS_FileOpen(log_path, DN_OSFileOpen_CreateAlways, DN_OSFileAccess_AppendOnly, nullptr); + } + DN_TicketMutex_End(&core->log_file_mutex); + + DN_LOGStyle style = {}; + if (!core->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_OSDateTime 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; + + char prefix_buffer[128] = {}; + DN_LOGPrefixSize prefix_size = DN_LOG_MakePrefix(style, type, call_site, log_date, prefix_buffer, sizeof(prefix_buffer)); + + va_list args_copy; + va_copy(args_copy, args); + DN_TicketMutex_Begin(&core->log_file_mutex); + { + DN_OS_FileWrite(&core->log_file, DN_Str8_Init(prefix_buffer, prefix_size.size), nullptr); + DN_OS_FileWriteF(&core->log_file, nullptr, "%*s ", DN_CAST(int)prefix_size.padding, ""); + DN_OS_FileWriteFV(&core->log_file, nullptr, fmt, args_copy); + DN_OS_FileWrite(&core->log_file, DN_STR8("\n"), nullptr); + } + DN_TicketMutex_End(&core->log_file_mutex); + va_end(args_copy); + + DN_OSPrintDest dest = (type.is_u32_enum && type.u32 == DN_LOGType_Error) ? DN_OSPrintDest_Err : DN_OSPrintDest_Out; + DN_OS_Print(dest, DN_Str8_Init(prefix_buffer, prefix_size.size)); + DN_OS_PrintF(dest, "%*s ", DN_CAST(int)prefix_size.padding, ""); + DN_OS_PrintLnFV(dest, fmt, args); +} + +DN_API void DN_OS_Init(DN_OSCore *os, DN_OSInitArgs *args) +{ + g_dn_os_core_ = os; + + // NOTE: OS + { + #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 + os->logical_processor_count = get_nprocs(); + os->page_size = getpagesize(); + os->alloc_granularity = os->page_size; + #endif + } + + // NOTE: Setup logging + DN_OS_EmitLogsWithOSPrintFunctions(os); + + { + os->arena = DN_Arena_InitFromOSVMem(DN_Megabytes(1), DN_Kilobytes(4), DN_ArenaFlags_NoAllocTrack); + #if defined(DN_PLATFORM_WIN32) + os->platform_context = DN_Arena_New(&os->arena, DN_W32Core, DN_ZeroMem_Yes); + #elif defined(DN_PLATFORM_POSIX) + os->platform_context = DN_Arena_New(&os->arena, DN_POSIXCore, DN_ZeroMem_Yes); + #endif + + #if defined(DN_PLATFORM_WIN32) + DN_W32Core *w32 = DN_CAST(DN_W32Core *) os->platform_context; + InitializeCriticalSection(&w32->sync_primitive_free_list_mutex); + + QueryPerformanceFrequency(&w32->qpc_frequency); + HMODULE module = LoadLibraryA("kernel32.dll"); + w32->set_thread_description = DN_CAST(DN_W32SetThreadDescriptionFunc *) 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_LOG_ErrorF("Failed to initialise Windows secure random number generator, error: %d", init_status); + #else + DN_POSIXCore *posix = DN_CAST(DN_POSIXCore *) os->platform_context; + int mutex_init = pthread_mutex_init(&posix->sync_primitive_free_list_mutex, nullptr); + DN_Assert(mutex_init == 0); + #endif + } + + // NOTE: Initialise tmem arenas which allocate memory and will be + // recorded to the now initialised allocation table. The initialisation + // of tmem memory may request tmem memory itself in leak tracing mode. + // This is supported as the tmem arenas defer allocation tracking until + // initialisation is done. + DN_OSTLSInitArgs tls_init_args = {}; + if (args) { + tls_init_args.commit = args->tls_commit; + tls_init_args.reserve = args->tls_reserve; + tls_init_args.err_sink_reserve = args->tls_err_sink_reserve; + tls_init_args.err_sink_commit = args->tls_err_sink_commit; + } + + DN_OS_TLSInit(&os->tls, tls_init_args); + DN_OS_TLSSetCurrentThreadTLS(&os->tls); + os->cpu_report = DN_CPU_Report(); + + #define DN_CPU_FEAT_XENTRY(label) g_dn_cpu_feature_decl[DN_CPUFeature_##label] = {DN_CPUFeature_##label, DN_STR8(#label)}; + DN_CPU_FEAT_XMACRO + #undef DN_CPU_FEAT_XENTRY + DN_Assert(g_dn_os_core_); +} + +DN_API void DN_OS_EmitLogsWithOSPrintFunctions(DN_OSCore *os) +{ + DN_Assert(os); + DN_LOG_SetEmitFromTypeFVFunc(DN_OS_LOGEmitFromTypeTypeFV_, os); +} + +DN_API void DN_OS_DumpThreadContextArenaStat(DN_Str8 file_path) +{ +#if defined(DN_DEBUG_THREAD_CONTEXT) + // NOTE: Open a file to write the arena stats to + FILE *file = nullptr; + fopen_s(&file, file_path.data, "a+b"); + if (file) { + DN_LOG_ErrorF("Failed to dump thread context arenas [file=%.*s]", DN_STR_FMT(file_path)); + return; + } + + // NOTE: Copy the stats from library book-keeping + // NOTE: Extremely short critical section, copy the stats then do our + // work on it. + DN_ArenaStat stats[DN_CArray_CountI(g_dn_core->thread_context_arena_stats)]; + int stats_size = 0; + + DN_TicketMutex_Begin(&g_dn_core->thread_context_mutex); + stats_size = g_dn_core->thread_context_arena_stats_count; + DN_Memcpy(stats, g_dn_core->thread_context_arena_stats, sizeof(stats[0]) * stats_size); + DN_TicketMutex_End(&g_dn_core->thread_context_mutex); + + // NOTE: Print the cumulative stat + DN_DateHMSTimeStr now = DN_Date_HMSLocalTimeStrNow(); + fprintf(file, + "Time=%.*s %.*s | Thread Context Arenas | Count=%d\n", + now.date_size, + now.date, + now.hms_size, + now.hms, + g_dn_core->thread_context_arena_stats_count); + + // NOTE: Write the cumulative thread arena data + { + DN_ArenaStat stat = {}; + for (DN_USize index = 0; index < stats_size; index++) { + DN_ArenaStat const *current = stats + index; + stat.capacity += current->capacity; + stat.used += current->used; + stat.wasted += current->wasted; + stat.blocks += current->blocks; + + stat.capacity_hwm = DN_Max(stat.capacity_hwm, current->capacity_hwm); + stat.used_hwm = DN_Max(stat.used_hwm, current->used_hwm); + stat.wasted_hwm = DN_Max(stat.wasted_hwm, current->wasted_hwm); + stat.blocks_hwm = DN_Max(stat.blocks_hwm, current->blocks_hwm); + } + + DN_ArenaStatStr stats_string = DN_Arena_StatStr(&stat); + fprintf(file, " [ALL] CURR %.*s\n", stats_string.size, stats_string.data); + } + + // NOTE: Print individual thread arena data + for (DN_USize index = 0; index < stats_size; index++) { + DN_ArenaStat const *current = stats + index; + DN_ArenaStatStr current_string = DN_Arena_StatStr(current); + fprintf(file, " [%03d] CURR %.*s\n", DN_CAST(int) index, current_string.size, current_string.data); + } + + fclose(file); + DN_LOG_InfoF("Dumped thread context arenas [file=%.*s]", DN_STR_FMT(file_path)); +#else + (void)file_path; +#endif // #if defined(DN_DEBUG_THREAD_CONTEXT) +} + +// NOTE: Date ////////////////////////////////////////////////////////////////////////////////////// +DN_API DN_OSDateTimeStr8 DN_OS_DateLocalTimeStr8(DN_OSDateTime time, char date_separator, char hms_separator) +{ + DN_OSDateTimeStr8 result = {}; + result.hms_size = DN_CAST(uint8_t) DN_SNPrintF(result.hms, + DN_ArrayCountI(result.hms), + "%02hhu%c%02hhu%c%02hhu", + time.hour, + hms_separator, + time.minutes, + hms_separator, + time.seconds); + + result.date_size = DN_CAST(uint8_t) DN_SNPrintF(result.date, + DN_ArrayCountI(result.date), + "%hu%c%02hhu%c%02hhu", + time.year, + date_separator, + time.month, + date_separator, + time.day); + + DN_Assert(result.hms_size < DN_ArrayCountU(result.hms)); + DN_Assert(result.date_size < DN_ArrayCountU(result.date)); + return result; +} + +DN_API DN_OSDateTimeStr8 DN_OS_DateLocalTimeStr8Now(char date_separator, char hms_separator) +{ + DN_OSDateTime time = DN_OS_DateLocalTimeNow(); + DN_OSDateTimeStr8 result = DN_OS_DateLocalTimeStr8(time, date_separator, hms_separator); + return result; +} + +DN_API bool DN_OS_DateIsValid(DN_OSDateTime 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; +} + +// NOTE: Other ///////////////////////////////////////////////////////////////////////////////////// +DN_API DN_Str8 DN_OS_EXEDir(DN_Arena *arena) +{ + DN_Str8 result = {}; + if (!arena) + return result; + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + DN_Str8 exe_path = DN_OS_EXEPath(tmem.arena); + DN_Str8 separators[] = {DN_STR8("/"), DN_STR8("\\")}; + DN_Str8BinarySplitResult split = DN_Str8_BinarySplitLastArray(exe_path, separators, DN_ArrayCountU(separators)); + result = DN_Str8_Copy(arena, split.lhs); + 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_CPU_TSC(); + 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_CPU_TSC(); + uint64_t tsc_elapsed = tsc_end - tsc_begin; + result = tsc_elapsed / os_elapsed * os_frequency; + } + return result; +} + +#if !defined(DN_NO_OS_FILE_API) +// NOTE: DN_OSPathInfo/File //////////////////////////////////////////////////////////////////////// +DN_API bool DN_OS_FileIsOlderThan(DN_Str8 file, DN_Str8 check_against) +{ + DN_OSPathInfo file_info = DN_OS_PathInfo(file); + 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_OSErrSink *error) +{ + bool result = DN_OS_FileWritePtr(file, buffer.data, buffer.size, error); + return result; +} + +struct DN_OSFileWriteChunker_ +{ + DN_OSErrSink *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_OSErrSink *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_OSErrSink *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; +} + +// NOTE: R/W Entire File /////////////////////////////////////////////////////////////////////////// +DN_API DN_Str8 DN_OS_ReadAll(DN_Arena *arena, DN_Str8 path, DN_OSErrSink *error) +{ + DN_Str8 result = {}; + if (!arena) + return result; + + // NOTE: Query file size + allocate buffer ///////////////////////////////////////////////////// + DN_OSPathInfo path_info = DN_OS_PathInfo(path); + if (!path_info.exists) { + DN_OS_ErrSinkAppendF(error, 1, "File does not exist/could not be queried for reading '%.*s'", DN_STR_FMT(path)); + return result; + } + + DN_ArenaTempMem temp_mem = DN_Arena_TempMemBegin(arena); + result = DN_Str8_Alloc(arena, path_info.size, DN_ZeroMem_No); + if (!DN_Str8_HasData(result)) { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 buffer_size_str8 = DN_CVT_U64ToByteSizeStr8(tmem.arena, path_info.size, DN_CVTU64ByteSizeType_Auto); + DN_OS_ErrSinkAppendF(error, 1 /*error_code*/, "Failed to allocate %.*s for reading file '%.*s'", DN_STR_FMT(buffer_size_str8), DN_STR_FMT(path)); + DN_Arena_TempMemEnd(temp_mem); + result = {}; + return result; + } + + // NOTE: Read the file from disk /////////////////////////////////////////////////////////////// + DN_OSFile file = DN_OS_FileOpen(path, DN_OSFileOpen_OpenIfExist, DN_OSFileAccess_Read, error); + DN_OSFileRead read = DN_OS_FileRead(&file, result.data, result.size, error); + if (file.error || !read.success) { + DN_Arena_TempMemEnd(temp_mem); + result = {}; + } + DN_OS_FileClose(&file); + + return result; +} + +DN_API bool DN_OS_WriteAll(DN_Str8 path, DN_Str8 buffer, DN_OSErrSink *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_WriteAllFV(DN_Str8 file_path, DN_OSErrSink *error, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 buffer = DN_Str8_InitFV(tmem.arena, fmt, args); + bool result = DN_OS_WriteAll(file_path, buffer, error); + return result; +} + +DN_API bool DN_OS_WriteAllF(DN_Str8 file_path, DN_OSErrSink *error, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool result = DN_OS_WriteAllFV(file_path, error, fmt, args); + va_end(args); + return result; +} + +DN_API bool DN_OS_WriteAllSafe(DN_Str8 path, DN_Str8 buffer, DN_OSErrSink *error) +{ + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 tmp_path = DN_Str8_InitF(tmem.arena, "%.*s.tmp", DN_STR_FMT(path)); + if (!DN_OS_WriteAll(tmp_path, buffer, error)) + return false; + if (!DN_OS_CopyFile(tmp_path, path, true /*overwrite*/, error)) + return false; + if (!DN_OS_PathDelete(tmp_path)) + return false; + return true; +} + +DN_API bool DN_OS_WriteAllSafeFV(DN_Str8 path, DN_OSErrSink *error, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 buffer = DN_Str8_InitFV(tmem.arena, fmt, args); + bool result = DN_OS_WriteAllSafe(path, buffer, error); + return result; +} + +DN_API bool DN_OS_WriteAllSafeF(DN_Str8 path, DN_OSErrSink *error, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool result = DN_OS_WriteAllSafeFV(path, error, fmt, args); + return result; +} +#endif // !defined(DN_NO_OS_FILE_API) + +// NOTE: DN_OSPath ///////////////////////////////////////////////////////////////////////////////// +DN_API bool DN_OS_PathAddRef(DN_Arena *arena, DN_OSPath *fs_path, DN_Str8 path) +{ + if (!arena || !fs_path || !DN_Str8_HasData(path)) + return false; + + if (path.size <= 0) + return true; + + DN_Str8 const delimiter_array[] = { + DN_STR8("\\"), + DN_STR8("/")}; + + if (fs_path->links_size == 0) + fs_path->has_prefix_path_separator = (path.data[0] == '/'); + + for (;;) { + DN_Str8BinarySplitResult delimiter = DN_Str8_BinarySplitArray(path, delimiter_array, DN_ArrayCountU(delimiter_array)); + for (; delimiter.lhs.data; delimiter = DN_Str8_BinarySplitArray(delimiter.rhs, delimiter_array, DN_ArrayCountU(delimiter_array))) { + if (delimiter.lhs.size <= 0) + continue; + + DN_OSPathLink *link = DN_Arena_New(arena, DN_OSPathLink, DN_ZeroMem_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_Str8_Copy(arena, path); + bool result = DN_Str8_HasData(copy) ? 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_Str8_InitFV(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_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + va_list args; + va_start(args, fmt); + DN_Str8 path = DN_Str8_InitFV(tmem.arena, fmt, args); + va_end(args); + DN_Str8 result = DN_OS_PathTo(arena, path, path_separator); + 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_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + va_list args; + va_start(args, fmt); + DN_Str8 path = DN_Str8_InitFV(tmem.arena, fmt, args); + va_end(args); + DN_Str8 result = DN_OS_Path(arena, path); + 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_Str8_Alloc(arena, string_size, DN_ZeroMem_No); + 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_Slice cmd_line, + DN_OSExecArgs *args, + DN_Arena *arena, + DN_OSErrSink *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_Slice cmd_line, DN_OSExecArgs *args, DN_Arena *arena) +{ + DN_OSErrSink *error = DN_OS_ErrSinkBegin(DN_OSErrSinkMode_Nil); + DN_OSExecResult result = DN_OS_Exec(cmd_line, args, arena, error); + if (result.os_error_code) + DN_OS_ErrSinkEndAndExitIfErrorF(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_OS_ErrSinkEndAndExitIfErrorF(error, result.exit_code, "OS executed command and returned non-zero exit code %u", result.exit_code); + DN_OS_ErrSinkEndAndIgnore(error); + return result; +} + +// NOTE: DN_OSThread /////////////////////////////////////////////////////////////////////////////// +static void DN_OS_ThreadExecute_(void *user_context) +{ + DN_OSThread *thread = DN_CAST(DN_OSThread *) user_context; + DN_OS_TLSInit(&thread->tls, thread->tls_init_args); + DN_OS_TLSSetCurrentThreadTLS(&thread->tls); + DN_OS_SemaphoreWait(&thread->init_semaphore, DN_OS_SEMAPHORE_INFINITE_TIMEOUT); + thread->func(thread); +} + +DN_API void DN_OS_ThreadSetName(DN_Str8 name) +{ + DN_OSTLS *tls = DN_OS_TLSGet(); + tls->name_size = DN_CAST(uint8_t) DN_Min(name.size, sizeof(tls->name) - 1); + DN_Memcpy(tls->name, name.data, tls->name_size); + tls->name[tls->name_size] = 0; + +#if defined(DN_PLATFORM_WIN32) + DN_W32_ThreadSetName(name); +#else + DN_Posix_ThreadSetName(name); +#endif +} + +// NOTE: DN_OSHttp ///////////////////////////////////////////////////////////////////////////////// +DN_API void DN_OS_HttpRequestWait(DN_OSHttpResponse *response) +{ + if (response && response->on_complete_semaphore.handle != 0) + DN_OS_SemaphoreWait(&response->on_complete_semaphore, DN_OS_SEMAPHORE_INFINITE_TIMEOUT); +} + +DN_API DN_OSHttpResponse DN_OS_HttpRequest(DN_Arena *arena, DN_Str8 host, DN_Str8 path, DN_OSHttpRequestSecure secure, DN_Str8 method, DN_Str8 body, DN_Str8 headers) +{ + // TODO(doyle): Revise the memory allocation and its lifetime + DN_OSHttpResponse result = {}; + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + result.tmem_arena = tmem.arena; + + DN_OS_HttpRequestAsync(&result, arena, host, path, secure, method, body, headers); + DN_OS_HttpRequestWait(&result); + return result; +} +// DN: Single header generator inlined this file => #include "OS/dn_os_allocator.cpp" +#define DN_OS_ALLOCATOR_CPP + +static void *DN_Arena_BasicAllocFromOSHeap(DN_USize size) +{ + void *result = DN_OS_MemAlloc(size, DN_ZeroMem_Yes); + return result; +} + +DN_API DN_Arena DN_Arena_InitFromOSHeap(DN_U64 size, DN_ArenaFlags flags) +{ + DN_ArenaMemFuncs mem_funcs = {}; + mem_funcs.type = DN_ArenaMemFuncType_Basic; + mem_funcs.basic_alloc = DN_Arena_BasicAllocFromOSHeap; + mem_funcs.basic_dealloc = DN_OS_MemDealloc; + DN_Arena result = DN_Arena_InitFromMemFuncs(size, size, flags, mem_funcs); + return result; +} + +DN_API DN_Arena DN_Arena_InitFromOSVMem(DN_U64 reserve, DN_U64 commit, DN_ArenaFlags flags) +{ + DN_ArenaMemFuncs mem_funcs = {}; + mem_funcs.type = DN_ArenaMemFuncType_VMem; + mem_funcs.vmem_page_size = g_dn_os_core_->page_size; + mem_funcs.vmem_reserve = DN_OS_MemReserve; + mem_funcs.vmem_commit = DN_OS_MemCommit; + mem_funcs.vmem_release = DN_OS_MemRelease; + DN_Arena result = DN_Arena_InitFromMemFuncs(reserve, commit, flags, mem_funcs); + return result; +} + +// DN: Single header generator inlined this file => #include "OS/dn_os_containers.cpp" +#define DN_OS_CONTAINERS_CPP + +/* +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$$$$$\ $$$$$$\ $$\ $$\ $$$$$$$$\ $$$$$$\ $$$$$$\ $$\ $$\ $$$$$$$$\ $$$$$$$\ $$$$$$\ +// $$ __$$\ $$ __$$\ $$$\ $$ |\__$$ __|$$ __$$\ \_$$ _|$$$\ $$ |$$ _____|$$ __$$\ $$ __$$\ +// $$ / \__|$$ / $$ |$$$$\ $$ | $$ | $$ / $$ | $$ | $$$$\ $$ |$$ | $$ | $$ |$$ / \__| +// $$ | $$ | $$ |$$ $$\$$ | $$ | $$$$$$$$ | $$ | $$ $$\$$ |$$$$$\ $$$$$$$ |\$$$$$$\ +// $$ | $$ | $$ |$$ \$$$$ | $$ | $$ __$$ | $$ | $$ \$$$$ |$$ __| $$ __$$< \____$$\ +// $$ | $$\ $$ | $$ |$$ |\$$$ | $$ | $$ | $$ | $$ | $$ |\$$$ |$$ | $$ | $$ |$$\ $$ | +// \$$$$$$ | $$$$$$ |$$ | \$$ | $$ | $$ | $$ |$$$$$$\ $$ | \$$ |$$$$$$$$\ $$ | $$ |\$$$$$$ | +// \______/ \______/ \__| \__| \__| \__| \__|\______|\__| \__|\________|\__| \__| \______/ +// +// dn_containers.cpp +// +//////////////////////////////////////////////////////////////////////////////////////////////////// +*/ + +// NOTE: DN_VArray ///////////////////////////////////////////////////////////////////////////////// +template +DN_VArray DN_VArray_InitByteSize(DN_USize byte_size) +{ + DN_VArray result = {}; + result.data = DN_CAST(T *) DN_OS_MemReserve(byte_size, DN_MemCommit_No, DN_MemPage_ReadWrite); + if (result.data) + result.max = byte_size / sizeof(T); + return result; +} + +template +DN_VArray DN_VArray_Init(DN_USize max) +{ + DN_VArray result = DN_VArray_InitByteSize(max * sizeof(T)); + DN_Assert(result.max >= max); + return result; +} + +template +DN_VArray DN_VArray_InitSlice(DN_Slice slice, DN_USize max) +{ + DN_USize real_max = DN_Max(slice.size, max); + DN_VArray result = DN_VArray_Init(real_max); + if (DN_VArray_IsValid(&result)) + DN_VArray_AddArray(&result, slice.data, slice.size); + return result; +} + +template +DN_VArray DN_VArray_InitCArray(T const (&items)[N], DN_USize max) +{ + DN_USize real_max = DN_Max(N, max); + DN_VArray result = DN_VArray_InitSlice(DN_Slice_Init(items, N), real_max); + return result; +} + +template +void DN_VArray_Deinit(DN_VArray *array) +{ + DN_OS_MemRelease(array->data, array->max * sizeof(T)); + *array = {}; +} + +template +bool DN_VArray_IsValid(DN_VArray const *array) +{ + bool result = array->data && array->size <= array->max; + return result; +} + +template +DN_Slice DN_VArray_Slice(DN_VArray const *array) +{ + DN_Slice result = {}; + if (array) + result = DN_Slice_Init(array->data, array->size); + return result; +} + +template +T *DN_VArray_AddArray(DN_VArray *array, T const *items, DN_USize count) +{ + T *result = DN_VArray_MakeArray(array, count, DN_ZeroMem_No); + if (result) + DN_Memcpy(result, items, count * sizeof(T)); + return result; +} + +template +T *DN_VArray_AddCArray(DN_VArray *array, T const (&items)[N]) +{ + T *result = DN_VArray_AddArray(array, items, N); + return result; +} + +template +T *DN_VArray_Add(DN_VArray *array, T const &item) +{ + T *result = DN_VArray_AddArray(array, &item, 1); + return result; +} + +template +T *DN_VArray_MakeArray(DN_VArray *array, DN_USize count, DN_ZeroMem zero_mem) +{ + if (!DN_VArray_IsValid(array)) + return nullptr; + + if (!DN_CheckF((array->size + count) < array->max, "Array is out of space (user requested +%zu items, array has %zu/%zu items)", count, array->size, array->max)) + return nullptr; + + if (!DN_VArray_Reserve(array, count)) + return nullptr; + + // TODO: Use placement new + T *result = array->data + array->size; + array->size += count; + if (zero_mem == DN_ZeroMem_Yes) + DN_Memset(result, 0, count * sizeof(T)); + return result; +} + +template +T *DN_VArray_Make(DN_VArray *array, DN_ZeroMem zero_mem) +{ + T *result = DN_VArray_MakeArray(array, 1, zero_mem); + return result; +} + +template +T *DN_VArray_InsertArray(DN_VArray *array, DN_USize index, T const *items, DN_USize count) +{ + T *result = nullptr; + if (!DN_VArray_IsValid(array)) + return result; + if (DN_VArray_Reserve(array, array->size + count)) + result = DN_CArray_InsertArray(array->data, &array->size, array->max, index, items, count); + return result; +} + +template +T *DN_VArray_InsertCArray(DN_VArray *array, DN_USize index, T const (&items)[N]) +{ + T *result = DN_VArray_InsertArray(array, index, items, N); + return result; +} + +template +T *DN_VArray_Insert(DN_VArray *array, DN_USize index, T const &item) +{ + T *result = DN_VArray_InsertArray(array, index, &item, 1); + return result; +} + +template +T *DN_VArray_PopFront(DN_VArray *array, DN_USize count) +{ + T *result = DN_CArray_PopFront(array->data, &array->size, count); + return result; +} + +template +T *DN_VArray_PopBack(DN_VArray *array, DN_USize count) +{ + T *result = DN_CArray_PopBack(array->data, &array->size, count); + return result; +} + +template +DN_ArrayEraseResult DN_VArray_EraseRange(DN_VArray *array, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase) +{ + DN_ArrayEraseResult result = {}; + if (!DN_VArray_IsValid(array)) + return result; + result = DN_CArray_EraseRange(array->data, &array->size, begin_index, count, erase); + return result; +} + +template +void DN_VArray_Clear(DN_VArray *array, DN_ZeroMem zero_mem) +{ + if (array) { + if (zero_mem == DN_ZeroMem_Yes) + DN_Memset(array->data, 0, array->size * sizeof(T)); + array->size = 0; + } +} + +template +bool DN_VArray_Reserve(DN_VArray *array, DN_USize count) +{ + if (!DN_VArray_IsValid(array) || count == 0) + return false; + + DN_USize real_commit = (array->size + count) * sizeof(T); + DN_USize aligned_commit = DN_AlignUpPowerOfTwo(real_commit, g_dn_os_core_->page_size); + if (array->commit >= aligned_commit) + return true; + bool result = DN_OS_MemCommit(array->data, aligned_commit, DN_MemPage_ReadWrite); + array->commit = aligned_commit; + return result; +} +// DN: Single header generator inlined this file => #include "OS/dn_os_print.cpp" +#define DN_OS_PRINT_CPP + +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_STR_FMT(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_OS_Print(dest, DN_LOG_ColourEscapeCodeStr8FromRGB(DN_LOGColourType_Fg, style.r, style.g, style.b)); + if (style.bold == DN_LOGBold_Yes) + DN_OS_Print(dest, DN_STR8(DN_LOG_BoldEscapeCode)); + DN_OS_Print(dest, string); + if (style.colour || style.bold == DN_LOGBold_Yes) + DN_OS_Print(dest, DN_STR8(DN_LOG_ResetEscapeCode)); + } +} + +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_OS_Print(dest, DN_LOG_ColourEscapeCodeStr8FromRGB(DN_LOGColourType_Fg, style.r, style.g, style.b)); + if (style.bold == DN_LOGBold_Yes) + DN_OS_Print(dest, DN_STR8(DN_LOG_BoldEscapeCode)); + DN_OS_PrintFV(dest, fmt, args); + if (style.colour || style.bold == DN_LOGBold_Yes) + DN_OS_Print(dest, DN_STR8(DN_LOG_ResetEscapeCode)); + } +} + +DN_API void DN_OS_PrintLn(DN_OSPrintDest dest, DN_Str8 string) +{ + DN_OS_Print(dest, string); + DN_OS_Print(dest, DN_STR8("\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_STR8("\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_STR8("\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_STR8("\n")); +} +// DN: Single header generator inlined this file => #include "OS/dn_os_string.cpp" +#define DN_OS_STRING_CPP + +// NOTE: DN_Str8 /////////////////////////////////////////////////////////////////////////////////// + +DN_API DN_Str8 DN_Str8_InitFFromFrame(DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8 result = DN_Str8_InitFV(DN_OS_TLSGet()->frame_arena, fmt, args); + va_end(args); + return result; +} + +DN_API DN_Str8 DN_Str8_InitFFromOSHeap(DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + + DN_Str8 result = {}; + DN_USize size = DN_CStr8_FVSize(fmt, args); + if (size) { + result = DN_Str8_AllocFromOSHeap(size, DN_ZeroMem_No); + if (DN_Str8_HasData(result)) + DN_VSNPrintF(result.data, DN_SaturateCastISizeToInt(size + 1 /*null-terminator*/), fmt, args); + } + + va_end(args); + return result; +} + +DN_API DN_Str8 DN_Str8_InitFFromTLS(DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8 result = DN_Str8_InitFV(DN_OS_TLSTopArena(), fmt, args); + va_end(args); + return result; +} + +DN_API DN_Str8 DN_Str8_InitFVFromFrame(DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_Str8 result = DN_Str8_InitFV(DN_OS_TLSGet()->frame_arena, fmt, args); + return result; +} + +DN_API DN_Str8 DN_Str8_InitFVFromTLS(DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_Str8 result = DN_Str8_InitFV(DN_OS_TLSTopArena(), fmt, args); + return result; +} + +DN_API DN_Str8 DN_Str8_AllocFromFrame(DN_USize size, DN_ZeroMem zero_mem) +{ + DN_Str8 result = DN_Str8_Alloc(DN_OS_TLSGet()->frame_arena, size, zero_mem); + return result; +} + +DN_API DN_Str8 DN_Str8_AllocFromOSHeap(DN_USize size, DN_ZeroMem zero_mem) +{ + DN_Str8 result = {}; + result.data = DN_CAST(char *)DN_OS_MemAlloc(size + 1, zero_mem); + if (result.data) + result.size = size; + result.data[result.size] = 0; + return result; +} + +DN_API DN_Str8 DN_Str8_AllocFromTLS(DN_USize size, DN_ZeroMem zero_mem) +{ + DN_Str8 result = DN_Str8_Alloc(DN_OS_TLSTopArena(), size, zero_mem); + return result; +} + +DN_API DN_Str8 DN_Str8_CopyFromFrame(DN_Str8 string) +{ + DN_Str8 result = DN_Str8_Copy(DN_OS_TLSGet()->frame_arena, string); + return result; +} + +DN_API DN_Str8 DN_Str8_CopyFromTLS(DN_Str8 string) +{ + DN_Str8 result = DN_Str8_Copy(DN_OS_TLSTopArena(), string); + return result; +} + +DN_API DN_Slice DN_Str8_SplitAllocFromFrame(DN_Str8 string, DN_Str8 delimiter, DN_Str8SplitIncludeEmptyStrings mode) +{ + DN_Slice result = DN_Str8_SplitAlloc(DN_OS_TLSGet()->frame_arena, string, delimiter, mode); + return result; +} + +DN_API DN_Slice DN_Str8_SplitAllocFromTLS(DN_Str8 string, DN_Str8 delimiter, DN_Str8SplitIncludeEmptyStrings mode) +{ + DN_Slice result = DN_Str8_SplitAlloc(DN_OS_TLSTopArena(), string, delimiter, mode); + return result; +} + +DN_API DN_Str8 DN_Str8_SegmentFromFrame(DN_Str8 src, DN_USize segment_size, char segment_char) +{ + DN_Str8 result = DN_Str8_Segment(DN_OS_TLSGet()->frame_arena, src, segment_size, segment_char); + return result; +} + +DN_API DN_Str8 DN_Str8_SegmentFromTLS(DN_Str8 src, DN_USize segment_size, char segment_char) +{ + DN_Str8 result = DN_Str8_Segment(DN_OS_TLSTopArena(), src, segment_size, segment_char); + return result; +} + +DN_API DN_Str8 DN_Str8_ReverseSegmentFromFrame(DN_Str8 src, DN_USize segment_size, char segment_char) +{ + DN_Str8 result = DN_Str8_ReverseSegment(DN_OS_TLSGet()->frame_arena, src, segment_size, segment_char); + return result; +} + +DN_API DN_Str8 DN_Str8_ReverseSegmentFromTLS(DN_Str8 src, DN_USize segment_size, char segment_char) +{ + DN_Str8 result = DN_Str8_ReverseSegment(DN_OS_TLSTopArena(), src, segment_size, segment_char); + return result; +} + +DN_API DN_Str8 DN_Str8_AppendFFromFrame(DN_Str8 string, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8 result = DN_Str8_AppendFV(DN_OS_TLSGet()->frame_arena, string, fmt, args); + va_end(args); + return result; +} + +DN_API DN_Str8 DN_Str8_AppendFFromTLS(DN_Str8 string, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8 result = DN_Str8_AppendFV(DN_OS_TLSTopArena(), string, fmt, args); + va_end(args); + return result; +} + +DN_API DN_Str8 DN_Str8_FillFFromFrame(DN_USize count, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8 result = DN_Str8_FillFV(DN_OS_TLSGet()->frame_arena, count, fmt, args); + va_end(args); + return result; +} + +DN_API DN_Str8 DN_Str8_FillFFromTLS(DN_USize count, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8 result = DN_Str8_FillFV(DN_OS_TLSTopArena(), count, fmt, args); + va_end(args); + return result; +} + +DN_API DN_Str8DotTruncateResult DN_Str8_DotTruncateMiddleFromFrame(DN_Str8 str8, uint32_t side_size, DN_Str8 truncator) +{ + DN_Str8DotTruncateResult result = DN_Str8_DotTruncateMiddle(DN_OS_TLSGet()->frame_arena, str8, side_size, truncator); + return result; +} + +DN_API DN_Str8DotTruncateResult DN_Str8_DotTruncateMiddleFromTLS(DN_Str8 str8, uint32_t side_size, DN_Str8 truncator) +{ + DN_Str8DotTruncateResult result = DN_Str8_DotTruncateMiddle(DN_OS_TLSTopArena(), str8, side_size, truncator); + return result; +} + + +DN_API DN_Str8 DN_Str8_PadNewLines(DN_Arena *arena, DN_Str8 src, DN_Str8 pad) +{ + // TODO: Implement this without requiring TLS so it can go into base strings + DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(arena); + DN_Str8Builder builder = DN_Str8Builder_InitFromTLS(); + + DN_Str8BinarySplitResult split = DN_Str8_BinarySplit(src, DN_STR8("\n")); + while (split.lhs.size) { + DN_Str8Builder_AppendRef(&builder, pad); + DN_Str8Builder_AppendRef(&builder, split.lhs); + split = DN_Str8_BinarySplit(split.rhs, DN_STR8("\n")); + if (split.lhs.size) + DN_Str8Builder_AppendRef(&builder, DN_STR8("\n")); + } + + DN_Str8 result = DN_Str8Builder_Build(&builder, arena); + return result; +} + +DN_API DN_Str8 DN_Str8_PadNewLinesFromFrame(DN_Str8 src, DN_Str8 pad) +{ + DN_Str8 result = DN_Str8_PadNewLines(DN_OS_TLSGet()->frame_arena, src, pad); + return result; +} + +DN_API DN_Str8 DN_Str8_PadNewLinesFromTLS(DN_Str8 src, DN_Str8 pad) +{ + DN_Str8 result = DN_Str8_PadNewLines(DN_OS_TLSTopArena(), src, pad); + return result; +} + +DN_API DN_Str8 DN_Str8_UpperFromFrame(DN_Str8 string) +{ + DN_Str8 result = DN_Str8_Upper(DN_OS_TLSGet()->frame_arena, string); + return result; +} + +DN_API DN_Str8 DN_Str8_UpperFromTLS(DN_Str8 string) +{ + DN_Str8 result = DN_Str8_Upper(DN_OS_TLSTopArena(), string); + return result; +} + +DN_API DN_Str8 DN_Str8_LowerFromFrame(DN_Str8 string) +{ + DN_Str8 result = DN_Str8_Lower(DN_OS_TLSGet()->frame_arena, string); + return result; +} + +DN_API DN_Str8 DN_Str8_LowerFromTLS(DN_Str8 string) +{ + DN_Str8 result = DN_Str8_Lower(DN_OS_TLSTopArena(), string); + return result; +} + +DN_API DN_Str8 DN_Str8_Replace(DN_Str8 string, + DN_Str8 find, + DN_Str8 replace, + DN_USize start_index, + DN_Arena *arena, + DN_Str8EqCase eq_case) +{ + // TODO: Implement this without requiring TLS so it can go into base strings + DN_Str8 result = {}; + if (!DN_Str8_HasData(string) || !DN_Str8_HasData(find) || find.size > string.size || find.size == 0 || string.size == 0) { + result = DN_Str8_Copy(arena, string); + return result; + } + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + DN_Str8Builder string_builder = DN_Str8Builder_Init(tmem.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_Str8_Slice(string, tail, find.size); + if (!DN_Str8_Eq(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_Str8_Init(string.data, head); + DN_Str8Builder_AppendRef(&string_builder, slice); + } + + DN_Str8 range = DN_Str8_Slice(string, head, (tail - head)); + DN_Str8Builder_AppendRef(&string_builder, range); + DN_Str8Builder_AppendRef(&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_Str8_Copy(arena, string); + } else { + DN_Str8 remainder = DN_Str8_Init(string.data + head, string.size - head); + DN_Str8Builder_AppendRef(&string_builder, remainder); + result = DN_Str8Builder_Build(&string_builder, arena); + } + + return result; +} + +DN_API DN_Str8 DN_Str8_ReplaceInsensitive(DN_Str8 string, DN_Str8 find, DN_Str8 replace, DN_USize start_index, DN_Arena *arena) +{ + DN_Str8 result = DN_Str8_Replace(string, find, replace, start_index, arena, DN_Str8EqCase_Insensitive); + return result; +} + +// NOTE: DN_Str8Builder //////////////////////////////////////////////////////////////////////////// + +DN_API DN_Str8 DN_Str8Builder_BuildFromOSHeap(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_ZeroMem_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; +} + +#if defined(DN_PLATFORM_POSIX) + // DN: Single header generator inlined this file => #include "OS/dn_os_posix.cpp" +#define DN_OS_POSIX_CPP + +#include // readdir, opendir, closedir +#include + +// NOTE: DN_OSMem ////////////////////////////////////////////////////////////////////////////////// +static DN_U32 DN_OS_MemConvertPageToOSFlags_(DN_U32 protect) +{ + DN_Assert((protect & ~DN_MemPage_All) == 0); + DN_Assert(protect != 0); + DN_U32 result = 0; + + if (protect & (DN_MemPage_NoAccess | DN_MemPage_Guard)) { + result = PROT_NONE; + } else { + if (protect & DN_MemPage_Read) + result = PROT_READ; + if (protect & DN_MemPage_Write) + result = PROT_WRITE; + } + return result; +} + +DN_API void *DN_OS_MemReserve(DN_USize size, DN_MemCommit commit, DN_U32 page_flags) +{ + unsigned long os_page_flags = DN_OS_MemConvertPageToOSFlags_(page_flags); + + if (commit == DN_MemCommit_Yes) + os_page_flags |= (PROT_READ | PROT_WRITE); + + void *result = mmap(nullptr, size, os_page_flags, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + DN_Atomic_AddU64(&g_dn_os_core_->mem_allocs_total, 1); + DN_Atomic_AddU64(&g_dn_os_core_->mem_allocs_frame, 1); + if (result == MAP_FAILED) + result = nullptr; + return result; +} + +DN_API bool DN_OS_MemCommit(void *ptr, DN_USize size, DN_U32 page_flags) +{ + bool result = false; + if (!ptr || size == 0) + return false; + + unsigned long os_page_flags = DN_OS_MemConvertPageToOSFlags_(page_flags); + result = mprotect(ptr, size, os_page_flags) == 0; + DN_Atomic_AddU64(&g_dn_os_core_->mem_allocs_total, 1); + DN_Atomic_AddU64(&g_dn_os_core_->mem_allocs_frame, 1); + return result; +} + +DN_API void DN_OS_MemDecommit(void *ptr, DN_USize size) +{ + mprotect(ptr, size, PROT_NONE); + madvise(ptr, size, MADV_FREE); +} + +DN_API void DN_OS_MemRelease(void *ptr, DN_USize size) +{ + munmap(ptr, size); +} + +DN_API int DN_OS_MemProtect(void *ptr, DN_USize size, DN_U32 page_flags) +{ + if (!ptr || size == 0) + return 0; + + static DN_Str8 const ALIGNMENT_ERROR_MSG = DN_STR8( + "Page protection requires pointers to be page aligned because we " + "can only guard memory at a multiple of the page boundary."); + DN_AssertF(DN_IsPowerOfTwoAligned(DN_CAST(uintptr_t) ptr, g_dn_os_core_->page_size), + "%s", + ALIGNMENT_ERROR_MSG.data); + DN_AssertF( + DN_IsPowerOfTwoAligned(size, g_dn_os_core_->page_size), "%s", ALIGNMENT_ERROR_MSG.data); + + unsigned long os_page_flags = DN_OS_MemConvertPageToOSFlags_(page_flags); + int result = mprotect(ptr, size, os_page_flags); + DN_AssertF(result == 0, "mprotect failed (%d)", errno); + return result; +} + +DN_API void *DN_OS_MemAlloc(DN_USize size, DN_ZeroMem zero_mem) +{ + void *result = zero_mem == DN_ZeroMem_Yes ? calloc(1, size) : malloc(size); + return result; +} + +DN_API void DN_OS_MemDealloc(void *ptr) +{ + free(ptr); +} + +// NOTE: Date ////////////////////////////////////////////////////////////////////////////////////// +DN_API DN_OSDateTime DN_OS_DateLocalTimeNow() +{ + DN_OSDateTime result = {}; + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + + // NOTE: localtime_r is used because it is thread safe + // See: https://linux.die.net/man/3/localtime + // According to POSIX.1-2004, localtime() is required to behave as though + // tzset(3) was called, while localtime_r() does not have this requirement. + // For portable code tzset(3) should be called before localtime_r(). + for (static bool once = true; once; once = false) + tzset(); + + struct tm time = {}; + localtime_r(&ts.tv_sec, &time); + + result.hour = time.tm_hour; + result.minutes = time.tm_min; + result.seconds = time.tm_sec; + + result.day = DN_CAST(uint8_t) time.tm_mday; + result.month = DN_CAST(uint8_t) time.tm_mon + 1; + result.year = 1900 + DN_CAST(int16_t) time.tm_year; + return result; +} + +DN_API uint64_t DN_OS_DateUnixTimeNs() +{ + struct timespec ts = {}; + clock_gettime(CLOCK_REALTIME, &ts); + uint64_t result = (ts.tv_sec * 1000 /*ms*/ * 1000 /*us*/ * 1000 /*ns*/) + ts.tv_nsec; + return result; +} + +DN_API uint64_t DN_OS_DateLocalToUnixTimeS(DN_OSDateTime) +{ + DN_AssertOnce(!"Unimplemented"); + uint64_t result = 0; + return result; +} + +DN_API uint64_t DN_OS_DateToUnixTimeS(DN_OSDateTime date) +{ + DN_Assert(DN_OS_DateIsValid(date)); + struct tm timeinfo = {}; + timeinfo.tm_year = date.year - 1900; + timeinfo.tm_mon = date.month - 1; + timeinfo.tm_mday = date.day; + timeinfo.tm_hour = date.hour; + timeinfo.tm_min = date.minutes; + timeinfo.tm_sec = date.seconds; + uint64_t result = mktime(&timeinfo); + return result; +} + +DN_API DN_OSDateTime DN_OS_DateUnixTimeSToDate(uint64_t time) +{ + time_t posix_time = DN_CAST(time_t) time; + struct tm posix_date = *gmtime(&posix_time); + DN_OSDateTime result = {}; + result.year = posix_date.tm_year + 1900; + result.month = posix_date.tm_mon + 1; + result.day = posix_date.tm_mday; + result.hour = posix_date.tm_hour; + result.minutes = posix_date.tm_min; + result.seconds = posix_date.tm_sec; + return result; +} + +DN_API bool DN_OS_SecureRNGBytes(void *buffer, DN_U32 size) +{ +#if defined(DN_PLATFORM_EMSCRIPTEN) + (void)buffer; + (void)size; + return false; +#else + if (!buffer || size < 0) + return false; + + if (size == 0) + return true; + + DN_AssertF(size <= 32, + "We can increase this by chunking the buffer and filling 32 bytes at a time. *Nix " + "guarantees 32 " + "bytes can always be fulfilled by this system at a time"); + // TODO(doyle): + // https://github.com/jedisct1/libsodium/blob/master/src/libsodium/randombytes/sysrandom/randombytes_sysrandom.c + // TODO(doyle): https://man7.org/linux/man-pages/man2/getrandom.2.html + DN_U32 read_bytes = 0; + do { + read_bytes = + getrandom(buffer, size, 0); // NOTE: EINTR can not be triggered if size <= 32 bytes + } while (read_bytes != size || errno == EAGAIN); + return true; +#endif +} + +DN_API bool DN_OS_SetEnvVar(DN_Str8 name, DN_Str8 value) +{ + DN_AssertFOnce(false, "Unimplemented"); + (void)name; + (void)value; + bool result = false; + return result; +} + +DN_API DN_OSDiskSpace DN_OS_DiskSpace(DN_Str8 path) +{ + DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(nullptr); + DN_OSDiskSpace result = {}; + DN_Str8 path_z_terminated = DN_Str8_Copy(tmem.arena, path); + + struct statvfs info = {}; + if (statvfs(path_z_terminated.data, &info) != 0) + return result; + + result.success = true; + result.avail = info.f_bavail * info.f_frsize; + result.size = info.f_blocks * info.f_frsize; + return result; +} + +DN_API DN_Str8 DN_OS_EXEPath(DN_Arena *arena) +{ + DN_Str8 result = {}; + if (!arena) + return result; + + int required_size_wo_null_terminator = 0; + for (int try_size = 128;; try_size *= 2) { + auto scoped_arena = DN_ArenaTempMemScope(arena); + char *try_buf = DN_Arena_NewArray(arena, char, try_size, DN_ZeroMem_No); + int bytes_written = readlink("/proc/self/exe", try_buf, try_size); + if (bytes_written == -1) { + // Failed, we're unable to determine the executable directory + break; + } else if (bytes_written == try_size) { + // Try again, if returned size was equal- we may of prematurely + // truncated according to the man pages + continue; + } else { + // readlink will give us the path to the executable. Once we + // determine the correct buffer size required to get the full file + // path, we do some post-processing on said string and extract just + // the directory. + + // TODO(dn): It'd be nice if there's some way of keeping this + // try_buf around, memcopy the byte and trash the try_buf from the + // arena. Instead we just get the size and redo the call one last + // time after this "calculate" step. + DN_AssertF(bytes_written < try_size, + "bytes_written can never be greater than the try size, function writes at " + "most try_size"); + required_size_wo_null_terminator = bytes_written; + break; + } + } + + if (required_size_wo_null_terminator) { + DN_ArenaTempMem temp_mem = DN_Arena_TempMemBegin(arena); + char *exe_path = + DN_Arena_NewArray(arena, char, required_size_wo_null_terminator + 1, DN_ZeroMem_No); + exe_path[required_size_wo_null_terminator] = 0; + + int bytes_written = readlink("/proc/self/exe", exe_path, required_size_wo_null_terminator); + if (bytes_written == -1) { + // Note that if read-link fails again can be because there's + // a potential race condition here, our exe or directory could have + // been deleted since the last call, so we need to be careful. + DN_Arena_TempMemEnd(temp_mem); + } else { + result = DN_Str8_Init(exe_path, required_size_wo_null_terminator); + } + } + return result; +} + +DN_API void DN_OS_SleepMs(DN_UInt milliseconds) +{ + struct timespec ts; + ts.tv_sec = milliseconds / 1000; + ts.tv_nsec = (milliseconds % 1000) * 1'000'000; // Convert remaining milliseconds to nanoseconds + // nanosleep can fail if interrupted by a signal, so we loop until the full sleep time has passed + while (nanosleep(&ts, &ts) == -1 && errno == EINTR) + ; +} + +DN_API uint64_t DN_OS_PerfCounterFrequency() +{ + // NOTE: On Linux we use clock_gettime(CLOCK_MONOTONIC_RAW) which + // increments at nanosecond granularity. + uint64_t result = 1'000'000'000; + return result; +} + +DN_API uint64_t DN_OS_PerfCounterNow() +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC_RAW, &ts); + uint64_t result = DN_CAST(uint64_t) ts.tv_sec * 1'000'000'000 + DN_CAST(uint64_t) ts.tv_nsec; + return result; +} + +#if !defined(DN_NO_OS_FILE_API) +DN_API DN_OSPathInfo DN_OS_PathInfo(DN_Str8 path) +{ + DN_OSPathInfo result = {}; + if (!DN_Str8_HasData(path)) + return result; + + struct stat file_stat; + if (lstat(path.data, &file_stat) != -1) { + result.exists = true; + result.size = file_stat.st_size; + result.last_access_time_in_s = file_stat.st_atime; + result.last_write_time_in_s = file_stat.st_mtime; + // TODO(dn): Seems linux does not support creation time via stat. We + // shoddily deal with this. + result.create_time_in_s = DN_Min(result.last_access_time_in_s, result.last_write_time_in_s); + + if (S_ISDIR(file_stat.st_mode)) + result.type = DN_OSPathInfoType_Directory; + else if (S_ISREG(file_stat.st_mode)) + result.type = DN_OSPathInfoType_File; + } + return result; +} + +DN_API bool DN_OS_PathDelete(DN_Str8 path) +{ + bool result = false; + if (DN_Str8_HasData(path)) + result = remove(path.data) == 0; + return result; +} + +DN_API bool DN_OS_FileExists(DN_Str8 path) +{ + bool result = false; + if (!DN_Str8_HasData(path)) + return result; + + struct stat stat_result; + if (lstat(path.data, &stat_result) != -1) + result = S_ISREG(stat_result.st_mode) || S_ISLNK(stat_result.st_mode); + return result; +} + +DN_API bool DN_OS_CopyFile(DN_Str8 src, DN_Str8 dest, bool overwrite, DN_OSErrSink *error) +{ + bool result = false; + #if defined(DN_PLATFORM_EMSCRIPTEN) + DN_OS_ErrSinkAppendF(error, 1, "Unsupported on Emscripten because of their VFS model"); + #else + int src_fd = open(src.data, O_RDONLY); + if (src_fd == -1) { + int error_code = errno; + DN_OS_ErrSinkAppendF(error, + error_code, + "Failed to open file '%.*s' for copying: (%d) %s", + DN_STR_FMT(src), + error_code, + strerror(error_code)); + return result; + } + DN_DEFER + { + close(src_fd); + }; + + // NOTE: File permission is set to read/write by owner, read by others + int dest_fd = open(dest.data, O_WRONLY | O_CREAT | (overwrite ? O_TRUNC : 0), 0644); + if (dest_fd == -1) { + int error_code = errno; + DN_OS_ErrSinkAppendF(error, + error_code, + "Failed to open file destination '%.*s' for copying to: (%d) %s", + DN_STR_FMT(src), + error_code, + strerror(error_code)); + return result; + } + DN_DEFER + { + close(dest_fd); + }; + + struct stat stat_existing; + int fstat_result = fstat(src_fd, &stat_existing); + if (fstat_result == -1) { + int error_code = errno; + DN_OS_ErrSinkAppendF(error, + error_code, + "Failed to query file size of '%.*s' for copying: (%d) %s", + DN_STR_FMT(src), + error_code, + strerror(error_code)); + return result; + } + + ssize_t bytes_written = sendfile64(dest_fd, src_fd, 0, stat_existing.st_size); + result = (bytes_written == stat_existing.st_size); + if (!result) { + int error_code = errno; + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 file_size_str8 = + DN_CVT_U64ToByteSizeStr8(tmem.arena, stat_existing.st_size, DN_CVTU64ByteSizeType_Auto); + DN_Str8 bytes_written_str8 = + DN_CVT_U64ToByteSizeStr8(tmem.arena, bytes_written, DN_CVTU64ByteSizeType_Auto); + DN_OS_ErrSinkAppendF(error, + error_code, + "Failed to copy file '%.*s' to '%.*s', we copied %.*s but the file " + "size is %.*s: (%d) %s", + DN_STR_FMT(src), + DN_STR_FMT(dest), + DN_STR_FMT(bytes_written_str8), + DN_STR_FMT(file_size_str8), + error_code, + strerror(error_code)); + } + + #endif + return result; +} + +DN_API bool DN_OS_MoveFile(DN_Str8 src, DN_Str8 dest, bool overwrite, DN_OSErrSink *error) +{ + // See: https://github.com/gingerBill/gb/blob/master/gb.h + bool result = false; + bool file_moved = true; + if (link(src.data, dest.data) == -1) { + // NOTE: Link can fail if we're trying to link across different volumes + // so we fall back to a binary directory. + file_moved |= DN_OS_CopyFile(src, dest, overwrite, error); + } + + if (file_moved) { + result = true; + int unlink_result = unlink(src.data); + if (unlink_result == -1) { + int error_code = errno; + DN_OS_ErrSinkAppendF( + error, + error_code, + "File '%.*s' was moved but failed to be unlinked from old location: (%d) %s", + DN_STR_FMT(src), + error_code, + strerror(error_code)); + } + } + return result; +} + +DN_API bool DN_OS_MakeDir(DN_Str8 path) +{ + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + bool result = true; + + // TODO(doyle): Implement this without using the path indexes, it's not + // necessary. See Windows implementation. + DN_USize path_indexes_size = 0; + uint16_t path_indexes[64] = {}; + + DN_Str8 copy = DN_Str8_Copy(tmem.arena, path); + for (DN_USize index = copy.size - 1; index < copy.size; index--) { + bool first_char = index == (copy.size - 1); + char ch = copy.data[index]; + if (ch == '/' || first_char) { + char temp = copy.data[index]; + + if (!first_char) + copy.data[index] = 0; // Temporarily null terminate it + + bool is_file = DN_OS_FileExists(copy); + + if (!first_char) + copy.data[index] = temp; // Undo null termination + + if (is_file) { + // NOTE: There's something that exists in at this path, but + // it's not a directory. This request to make a directory is + // invalid. + return false; + } else if (DN_OS_DirExists(copy)) { + // NOTE: We found a directory, we can stop here and start + // building up all the directories that didn't exist up to + // this point. + break; + } else { + // NOTE: There's nothing that exists at this path, we can + // create a directory here + path_indexes[path_indexes_size++] = DN_CAST(uint16_t) index; + } + } + } + + for (DN_USize index = path_indexes_size - 1; result && index < path_indexes_size; index--) { + uint16_t path_index = path_indexes[index]; + char temp = copy.data[path_index]; + + if (index != 0) + copy.data[path_index] = 0; + result |= mkdir(copy.data, 0774) == 0; + if (index != 0) + copy.data[path_index] = temp; + } + return result; +} + +DN_API bool DN_OS_DirExists(DN_Str8 path) +{ + bool result = false; + if (!DN_Str8_HasData(path)) + return result; + + struct stat stat_result; + if (lstat(path.data, &stat_result) != -1) + result = S_ISDIR(stat_result.st_mode); + return result; +} + +DN_API bool DN_OS_DirIterate(DN_Str8 path, DN_OSDirIterator *it) +{ + if (!it->handle) { + it->handle = opendir(path.data); + if (!it->handle) + return false; + } + + struct dirent *entry; + for (;;) { + entry = readdir(DN_CAST(DIR *) it->handle); + if (entry == NULL) + break; + + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) + continue; + + DN_USize name_size = DN_CStr8_Size(entry->d_name); + DN_USize clamped_size = DN_Min(sizeof(it->buffer) - 1, name_size); + DN_AssertF(name_size == clamped_size, "name: %s, name_size: %zu, clamped_size: %zu", entry->d_name, name_size, clamped_size); + DN_Memcpy(it->buffer, entry->d_name, clamped_size); + it->buffer[clamped_size] = 0; + it->file_name = DN_Str8_Init(it->buffer, clamped_size); + return true; + } + + closedir(DN_CAST(DIR *) it->handle); + it->handle = NULL; + it->file_name = {}; + it->buffer[0] = 0; + return false; +} + +// NOTE: R/W Stream API //////////////////////////////////////////////////////////////////////////// +DN_API DN_OSFile DN_OS_FileOpen(DN_Str8 path, + DN_OSFileOpen open_mode, + DN_OSFileAccess access, + DN_OSErrSink *error) +{ + DN_OSFile result = {}; + if (!DN_Str8_HasData(path) || path.size <= 0) + return result; + + if ((access & ~(DN_OSFileAccess_All) || ((access & DN_OSFileAccess_All) == 0))) { + DN_InvalidCodePath; + return result; + } + + if (access & DN_OSFileAccess_Execute) { + result.error = true; + DN_OS_ErrSinkAppendF( + error, + 1, + "Failed to open file '%.*s': File access flag 'execute' is not supported", + DN_STR_FMT(path)); + DN_InvalidCodePath; // TODO: Not supported via fopen + return result; + } + + // NOTE: fopen interface is not as expressive as the Win32 + // We will fopen the file beforehand to setup the state/check for validity + // before closing and reopening it with the correct request access + // permissions. + { + FILE *handle = nullptr; + switch (open_mode) { + case DN_OSFileOpen_CreateAlways: handle = fopen(path.data, "w"); break; + case DN_OSFileOpen_OpenIfExist: handle = fopen(path.data, "r"); break; + case DN_OSFileOpen_OpenAlways: handle = fopen(path.data, "a"); break; + default: DN_InvalidCodePath; break; + } + + if (!handle) { // TODO(doyle): FileOpen flag to string + result.error = true; + DN_OS_ErrSinkAppendF(error, + 1, + "Failed to open file '%.*s': File could not be opened in requested " + "mode 'DN_OSFileOpen' flag %d", + DN_STR_FMT(path), + open_mode); + return result; + } + fclose(handle); + } + + char const *fopen_mode = nullptr; + if (access & DN_OSFileAccess_AppendOnly) + fopen_mode = "a+"; + else if (access & DN_OSFileAccess_Write) + fopen_mode = "w+"; + else if (access & DN_OSFileAccess_Read) + fopen_mode = "r"; + + FILE *handle = fopen(path.data, fopen_mode); + if (!handle) { + result.error = true; + DN_OS_ErrSinkAppendF(error, + 1, + "Failed to open file '%S': File could not be opened with requested " + "access mode 'DN_OSFileAccess' %d", + path, + fopen_mode); + return result; + } + result.handle = handle; + return result; +} + +DN_API DN_OSFileRead DN_OS_FileRead(DN_OSFile *file, void *buffer, DN_USize size, DN_OSErrSink *err) +{ + DN_OSFileRead result = {}; + if (!file || !file->handle || file->error || !buffer || size <= 0) + return result; + + result.bytes_read = fread(buffer, 1, size, DN_CAST(FILE *) file->handle); + if (feof(DN_CAST(FILE*)file->handle)) { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 buffer_size_str8 = DN_CVT_U64ToByteSizeStr8(tmem.arena, size, DN_CVTU64ByteSizeType_Auto); + DN_OS_ErrSinkAppendF(err, 1, "Failed to read %S from file", buffer_size_str8); + return result; + } + + result.success = true; + return result; +} + +DN_API bool DN_OS_FileWritePtr(DN_OSFile *file, void const *buffer, DN_USize size, DN_OSErrSink *err) +{ + if (!file || !file->handle || file->error || !buffer || size <= 0) + return false; + bool result = + fwrite(buffer, DN_CAST(DN_USize) size, 1 /*count*/, DN_CAST(FILE *) file->handle) == + 1 /*count*/; + if (!result) { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 buffer_size_str8 = + DN_CVT_U64ToByteSizeStr8(tmem.arena, size, DN_CVTU64ByteSizeType_Auto); + DN_OS_ErrSinkAppendF( + err, 1, "Failed to write buffer (%s) to file handle", DN_STR_FMT(buffer_size_str8)); + } + return result; +} + +DN_API bool DN_OS_FileFlush(DN_OSFile *file, DN_OSErrSink *err) +{ + // TODO: errno is not thread safe + int fd = fileno(DN_CAST(FILE *) file->handle); + if (fd == -1) { + DN_OS_ErrSinkAppendF(err, errno, "Failed to flush file buffer to disk, file handle could not be converted to descriptor (%d): %s", fd, strerror(errno)); + return false; + } + + int fsync_result = fsync(fd); + if (fsync_result == -1) { + DN_OS_ErrSinkAppendF(err, errno, "Failed to flush file buffer to disk (%d): %s", fsync_result, strerror(errno)); + return false; + } + return true; +} + +DN_API void DN_OS_FileClose(DN_OSFile *file) +{ + if (!file || !file->handle || file->error) + return; + fclose(DN_CAST(FILE *) file->handle); + *file = {}; +} +#endif // !defined(DN_NO_OS_FILE_API) + +// NOTE: DN_OSExec ///////////////////////////////////////////////////////////////////////////////// +DN_API void DN_OS_Exit(int32_t exit_code) +{ + exit(DN_CAST(int) exit_code); +} + +enum DN_OSPipeType_ +{ + DN_OSPipeType__Read, + DN_OSPipeType__Write, + DN_OSPipeType__Count, +}; + +DN_API DN_OSExecResult DN_OS_ExecWait(DN_OSExecAsyncHandle handle, + DN_Arena *arena, + DN_OSErrSink *error) +{ + DN_OSExecResult result = {}; + if (!handle.process || handle.os_error_code || handle.exit_code) { + if (handle.os_error_code) + result.os_error_code = handle.os_error_code; + else + result.exit_code = handle.exit_code; + + DN_Assert(!handle.stdout_read); + DN_Assert(!handle.stdout_write); + DN_Assert(!handle.stderr_read); + DN_Assert(!handle.stderr_write); + return result; + } + +#if defined(DN_PLATFORM_EMSCRIPTEN) + DN_InvalidCodePathF("Unsupported operation"); +#endif + + static_assert(sizeof(pid_t) <= sizeof(handle.process), + "We store the PID opaquely in a register sized pointer"); + pid_t process = {}; + DN_Memcpy(&process, &handle.process, sizeof(process)); + for (;;) { + int status = 0; + if (waitpid(process, &status, 0) < 0) { + result.os_error_code = errno; + break; + } + + if (WIFEXITED(status)) { + result.exit_code = WEXITSTATUS(status); + break; + } + + if (WIFSIGNALED(status)) { + result.os_error_code = WTERMSIG(status); + break; + } + } + + int stdout_pipe[DN_OSPipeType__Count] = {}; + int stderr_pipe[DN_OSPipeType__Count] = {}; + DN_Memcpy(&stdout_pipe[DN_OSPipeType__Read], + &handle.stdout_read, + sizeof(stdout_pipe[DN_OSPipeType__Read])); + DN_Memcpy(&stdout_pipe[DN_OSPipeType__Write], + &handle.stdout_write, + sizeof(stdout_pipe[DN_OSPipeType__Write])); + DN_Memcpy(&stderr_pipe[DN_OSPipeType__Read], + &handle.stderr_read, + sizeof(stderr_pipe[DN_OSPipeType__Read])); + DN_Memcpy(&stderr_pipe[DN_OSPipeType__Write], + &handle.stderr_write, + sizeof(stderr_pipe[DN_OSPipeType__Write])); + + // NOTE: Process has finished, stop the write end of the pipe + close(stdout_pipe[DN_OSPipeType__Write]); + close(stderr_pipe[DN_OSPipeType__Write]); + + // NOTE: Read the data from the read end of the pipe + if (result.os_error_code == 0) { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + if (arena && handle.stdout_read) { + char buffer[4096]; + DN_Str8Builder builder = DN_Str8Builder_Init(tmem.arena); + for (;;) { + ssize_t bytes_read = + read(stdout_pipe[DN_OSPipeType__Read], buffer, sizeof(buffer)); + if (bytes_read <= 0) + break; + DN_Str8Builder_AppendF(&builder, "%.*s", bytes_read, buffer); + } + + result.stdout_text = DN_Str8Builder_Build(&builder, arena); + } + + if (arena && handle.stderr_read) { + char buffer[4096]; + DN_Str8Builder builder = DN_Str8Builder_Init(tmem.arena); + for (;;) { + ssize_t bytes_read = + read(stderr_pipe[DN_OSPipeType__Read], buffer, sizeof(buffer)); + if (bytes_read <= 0) + break; + DN_Str8Builder_AppendF(&builder, "%.*s", bytes_read, buffer); + } + + result.stderr_text = DN_Str8Builder_Build(&builder, arena); + } + } + + close(stdout_pipe[DN_OSPipeType__Read]); + close(stderr_pipe[DN_OSPipeType__Read]); + return result; +} + +DN_API DN_OSExecAsyncHandle DN_OS_ExecAsync(DN_Slice cmd_line, + DN_OSExecArgs *args, + DN_OSErrSink *error) +{ +#if defined(DN_PLATFORM_EMSCRIPTEN) + DN_InvalidCodePathF("Unsupported operation"); +#endif + DN_AssertFOnce(args->environment.size == 0, "Unimplemented in POSIX"); + + DN_OSExecAsyncHandle result = {}; + if (cmd_line.size == 0) + return result; + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 cmd_rendered = DN_Slice_Str8Render(tmem.arena, cmd_line, DN_STR8(" ")); + int stdout_pipe[DN_OSPipeType__Count] = {}; + int stderr_pipe[DN_OSPipeType__Count] = {}; + + // NOTE: Open stdout pipe ////////////////////////////////////////////////////////////////////// + if (DN_Bit_IsSet(args->flags, DN_OSExecFlags_SaveStdout)) { + if (pipe(stdout_pipe) == -1) { + result.os_error_code = errno; + DN_OS_ErrSinkAppendF( + error, + result.os_error_code, + "Failed to create stdout pipe to redirect the output of the command '%.*s': %s", + DN_STR_FMT(cmd_rendered), + strerror(result.os_error_code)); + return result; + } + DN_Assert(stdout_pipe[DN_OSPipeType__Read] != 0); + DN_Assert(stdout_pipe[DN_OSPipeType__Write] != 0); + } + + DN_DEFER + { + if (result.os_error_code == 0 && result.exit_code == 0) + return; + close(stdout_pipe[DN_OSPipeType__Read]); + close(stdout_pipe[DN_OSPipeType__Write]); + }; + + // NOTE: Open stderr pipe ////////////////////////////////////////////////////////////////////// + if (DN_Bit_IsSet(args->flags, DN_OSExecFlags_SaveStderr)) { + if (DN_Bit_IsSet(args->flags, DN_OSExecFlags_MergeStderrToStdout)) { + stderr_pipe[DN_OSPipeType__Read] = stdout_pipe[DN_OSPipeType__Read]; + stderr_pipe[DN_OSPipeType__Write] = stdout_pipe[DN_OSPipeType__Write]; + } else if (pipe(stderr_pipe) == -1) { + result.os_error_code = errno; + DN_OS_ErrSinkAppendF( + error, + result.os_error_code, + "Failed to create stderr pipe to redirect the output of the command '%.*s': %s", + DN_STR_FMT(cmd_rendered), + strerror(result.os_error_code)); + return result; + } + DN_Assert(stderr_pipe[DN_OSPipeType__Read] != 0); + DN_Assert(stderr_pipe[DN_OSPipeType__Write] != 0); + } + + DN_DEFER + { + if (result.os_error_code == 0 && result.exit_code == 0) + return; + close(stderr_pipe[DN_OSPipeType__Read]); + close(stderr_pipe[DN_OSPipeType__Write]); + }; + + pid_t child_pid = fork(); + if (child_pid < 0) { + result.os_error_code = errno; + DN_OS_ErrSinkAppendF( + error, + result.os_error_code, + "Failed to fork process to execute the command '%.*s': %s", + DN_STR_FMT(cmd_rendered), + strerror(result.os_error_code)); + return result; + } + + if (child_pid == 0) { // Child process + if (DN_Bit_IsSet(args->flags, DN_OSExecFlags_SaveStdout) && + (dup2(stdout_pipe[DN_OSPipeType__Write], STDOUT_FILENO) == -1)) { + result.os_error_code = errno; + DN_OS_ErrSinkAppendF( + error, + result.os_error_code, + "Failed to redirect stdout 'write' pipe for output of command '%.*s': %s", + DN_STR_FMT(cmd_rendered), + strerror(result.os_error_code)); + return result; + } + + if (DN_Bit_IsSet(args->flags, DN_OSExecFlags_SaveStderr) && + (dup2(stderr_pipe[DN_OSPipeType__Write], STDERR_FILENO) == -1)) { + result.os_error_code = errno; + DN_OS_ErrSinkAppendF( + error, + result.os_error_code, + "Failed to redirect stderr 'read' pipe for output of command '%.*s': %s", + DN_STR_FMT(cmd_rendered), + strerror(result.os_error_code)); + return result; + } + + // NOTE: Convert the command into something suitable for execvp + char **argv = + DN_Arena_NewArray(tmem.arena, char *, cmd_line.size + 1 /*null*/, DN_ZeroMem_Yes); + if (!argv) { + result.exit_code = -1; + DN_OS_ErrSinkAppendF( + error, + result.os_error_code, + "Failed to create argument values from command line '%.*s': Out of memory", + DN_STR_FMT(cmd_rendered)); + return result; + } + + for (DN_ForIndexU(arg_index, cmd_line.size)) { + DN_Str8 arg = cmd_line.data[arg_index]; + argv[arg_index] = DN_Str8_Copy(tmem.arena, arg).data; // NOTE: Copy string to guarantee it is null-terminated + } + + // NOTE: Change the working directory if there is one + char *prev_working_dir = nullptr; + DN_DEFER + { + if (!prev_working_dir) + return; + if (result.os_error_code == 0) { + int chdir_result = chdir(prev_working_dir); + (void)chdir_result; + } + free(prev_working_dir); + }; + + if (args->working_dir.size) { + prev_working_dir = get_current_dir_name(); + DN_Str8 working_dir = DN_Str8_Copy(tmem.arena, args->working_dir); + if (chdir(working_dir.data) == -1) { + result.os_error_code = errno; + DN_OS_ErrSinkAppendF( + error, + result.os_error_code, + "Failed to create argument values from command line '%.*s': %s", + DN_STR_FMT(cmd_rendered), + strerror(result.os_error_code)); + return result; + } + } + + // NOTE: Execute the command. We reuse argv because the first arg, the + // binary to execute is guaranteed to be null-terminated. + if (execvp(argv[0], argv) < 0) { + result.os_error_code = errno; + DN_OS_ErrSinkAppendF( + error, + result.os_error_code, + "Failed to execute command'%.*s': %s", + DN_STR_FMT(cmd_rendered), + strerror(result.os_error_code)); + return result; + } + } + + DN_Assert(result.os_error_code == 0); + DN_Memcpy(&result.stdout_read, + &stdout_pipe[DN_OSPipeType__Read], + sizeof(stdout_pipe[DN_OSPipeType__Read])); + DN_Memcpy(&result.stdout_write, + &stdout_pipe[DN_OSPipeType__Write], + sizeof(stdout_pipe[DN_OSPipeType__Write])); + + if (DN_Bit_IsSet(args->flags, DN_OSExecFlags_SaveStderr) && DN_Bit_IsNotSet(args->flags, DN_OSExecFlags_MergeStderrToStdout)) { + DN_Memcpy(&result.stderr_read, + &stderr_pipe[DN_OSPipeType__Read], + sizeof(stderr_pipe[DN_OSPipeType__Read])); + DN_Memcpy(&result.stderr_write, + &stderr_pipe[DN_OSPipeType__Write], + sizeof(stderr_pipe[DN_OSPipeType__Write])); + } + result.exec_flags = args->flags; + DN_Memcpy(&result.process, &child_pid, sizeof(child_pid)); + return result; +} + +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_OSErrSink *err) +{ + DN_InvalidCodePath; + DN_OSExecResult result = {}; + return result; +} + +static DN_POSIXCore *DN_OS_GetPOSIXCore_() +{ + DN_Assert(g_dn_os_core_ && g_dn_os_core_->platform_context); + DN_POSIXCore *result = DN_CAST(DN_POSIXCore *)g_dn_os_core_->platform_context; + return result; +} + +static DN_POSIXSyncPrimitive *DN_OS_U64ToPOSIXSyncPrimitive_(DN_U64 u64) +{ + DN_POSIXSyncPrimitive *result = nullptr; + DN_Memcpy(&result, &u64, sizeof(u64)); + return result; +} + +static DN_U64 DN_POSIX_SyncPrimitiveToU64(DN_POSIXSyncPrimitive *primitive) +{ + DN_U64 result = 0; + static_assert(sizeof(result) == sizeof(primitive), "Pointer size mis-match"); + DN_Memcpy(&result, &primitive, sizeof(result)); + return result; +} + +static DN_POSIXSyncPrimitive *DN_POSIX_AllocSyncPrimitive_() +{ + DN_POSIXCore *posix = DN_OS_GetPOSIXCore_(); + DN_POSIXSyncPrimitive *result = nullptr; + pthread_mutex_lock(&posix->sync_primitive_free_list_mutex); + { + if (posix->sync_primitive_free_list) { + result = posix->sync_primitive_free_list; + posix->sync_primitive_free_list = posix->sync_primitive_free_list->next; + result->next = nullptr; + } else { + DN_OSCore *os = g_dn_os_core_; + result = DN_Arena_New(&os->arena, DN_POSIXSyncPrimitive, DN_ZeroMem_Yes); + } + } + pthread_mutex_unlock(&posix->sync_primitive_free_list_mutex); + return result; +} + +static void DN_POSIX_DeallocSyncPrimitive_(DN_POSIXSyncPrimitive *primitive) +{ + if (primitive) { + DN_POSIXCore *posix = DN_OS_GetPOSIXCore_(); + pthread_mutex_lock(&posix->sync_primitive_free_list_mutex); + primitive->next = posix->sync_primitive_free_list; + posix->sync_primitive_free_list = primitive; + pthread_mutex_unlock(&posix->sync_primitive_free_list_mutex); + } +} + +// NOTE: DN_OSSemaphore //////////////////////////////////////////////////////////////////////////// +DN_API DN_OSSemaphore DN_OS_SemaphoreInit(DN_U32 initial_count) +{ + DN_OSSemaphore result = {}; + DN_POSIXSyncPrimitive *primitive = DN_POSIX_AllocSyncPrimitive_(); + if (primitive) { + int pshared = 0; // Share the semaphore across all threads in the process + if (sem_init(&primitive->sem, pshared, initial_count) == 0) + result.handle = DN_POSIX_SyncPrimitiveToU64(primitive); + else + DN_POSIX_DeallocSyncPrimitive_(primitive); + } + return result; +} + +DN_API void DN_OS_SemaphoreDeinit(DN_OSSemaphore *semaphore) +{ + if (semaphore && semaphore->handle != 0) { + DN_POSIXSyncPrimitive *primitive = DN_OS_U64ToPOSIXSyncPrimitive_(semaphore->handle); + sem_destroy(&primitive->sem); + DN_POSIX_DeallocSyncPrimitive_(primitive); + *semaphore = {}; + } +} + +DN_API void DN_OS_SemaphoreIncrement(DN_OSSemaphore *semaphore, DN_U32 amount) +{ + if (semaphore && semaphore->handle != 0) { + DN_POSIXSyncPrimitive *primitive = DN_OS_U64ToPOSIXSyncPrimitive_(semaphore->handle); + #if defined(DN_OS_WIN32) + sem_post_multiple(&primitive->sem, amount); // mingw extension + #else + for (DN_ForIndexU(index, amount)) + sem_post(&primitive->sem); + #endif // !defined(DN_OS_WIN32) + } +} + +DN_API DN_OSSemaphoreWaitResult DN_OS_SemaphoreWait(DN_OSSemaphore *semaphore, + DN_U32 timeout_ms) +{ + DN_OSSemaphoreWaitResult result = {}; + if (!semaphore || semaphore->handle == 0) + return result; + + DN_POSIXSyncPrimitive *primitive = DN_OS_U64ToPOSIXSyncPrimitive_(semaphore->handle); + if (timeout_ms == DN_OS_SEMAPHORE_INFINITE_TIMEOUT) { + int wait_result = 0; + do { + wait_result = sem_wait(&primitive->sem); + } while (wait_result == -1 && errno == EINTR); + + if (wait_result == 0) + result = DN_OSSemaphoreWaitResult_Success; + } else { + DN_U64 now_ms = DN_OS_DateUnixTimeMs(); + DN_U64 end_ts_ms = now_ms + timeout_ms; + + struct timespec abs_timeout = {}; + abs_timeout.tv_sec = end_ts_ms / 1'000; + abs_timeout.tv_nsec = 1'000'000 * (end_ts_ms - (end_ts_ms / 1'000) * 1'000); + if (sem_timedwait(&primitive->sem, &abs_timeout) == 0) + result = DN_OSSemaphoreWaitResult_Success; + else if (errno == ETIMEDOUT) + result = DN_OSSemaphoreWaitResult_Timeout; + } + return result; +} + +// NOTE: DN_OSMutex //////////////////////////////////////////////////////////////////////////////// +DN_API DN_OSMutex DN_OS_MutexInit() +{ + DN_POSIXSyncPrimitive *primitive = DN_POSIX_AllocSyncPrimitive_(); + DN_OSMutex result = {}; + if (primitive) { + if (pthread_mutex_init(&primitive->mutex, nullptr) == 0) + result.handle = DN_POSIX_SyncPrimitiveToU64(primitive); + else + DN_POSIX_DeallocSyncPrimitive_(primitive); + } + return result; +} + +DN_API void DN_OS_MutexDeinit(DN_OSMutex *mutex) +{ + if (mutex && mutex->handle != 0) { + DN_POSIXSyncPrimitive *primitive = DN_OS_U64ToPOSIXSyncPrimitive_(mutex->handle); + pthread_mutex_destroy(&primitive->mutex); + DN_POSIX_DeallocSyncPrimitive_(primitive); + *mutex = {}; + } +} + +DN_API void DN_OS_MutexLock(DN_OSMutex *mutex) +{ + if (mutex && mutex->handle != 0) { + DN_POSIXSyncPrimitive *primitive = DN_OS_U64ToPOSIXSyncPrimitive_(mutex->handle); + pthread_mutex_lock(&primitive->mutex); + } +} + +DN_API void DN_OS_MutexUnlock(DN_OSMutex *mutex) +{ + if (mutex && mutex->handle != 0) { + DN_POSIXSyncPrimitive *primitive = DN_OS_U64ToPOSIXSyncPrimitive_(mutex->handle); + pthread_mutex_unlock(&primitive->mutex); + } +} + +DN_API DN_OSConditionVariable DN_OS_ConditionVariableInit() +{ + DN_POSIXSyncPrimitive *primitive = DN_POSIX_AllocSyncPrimitive_(); + DN_OSConditionVariable result = {}; + if (primitive) { + if (pthread_cond_init(&primitive->cv, nullptr) == 0) + result.handle = DN_POSIX_SyncPrimitiveToU64(primitive); + else + DN_POSIX_DeallocSyncPrimitive_(primitive); + } + return result; +} + +DN_API void DN_OS_ConditionVariableDeinit(DN_OSConditionVariable *cv) +{ + if (cv && cv->handle != 0) { + DN_POSIXSyncPrimitive *primitive = DN_OS_U64ToPOSIXSyncPrimitive_(cv->handle); + pthread_cond_destroy(&primitive->cv); + DN_POSIX_DeallocSyncPrimitive_(primitive); + *cv = {}; + } +} + +DN_API bool DN_OS_ConditionVariableWaitUntil(DN_OSConditionVariable *cv, DN_OSMutex *mutex, DN_U64 end_ts_ms) +{ + bool result = false; + if (cv && mutex && mutex->handle != 0 && cv->handle != 0) { + DN_POSIXSyncPrimitive *cv_primitive = DN_OS_U64ToPOSIXSyncPrimitive_(cv->handle); + DN_POSIXSyncPrimitive *mutex_primitive = DN_OS_U64ToPOSIXSyncPrimitive_(mutex->handle); + + struct timespec time = {}; + time.tv_sec = end_ts_ms / 1'000; + time.tv_nsec = 1'000'000 * (end_ts_ms - (end_ts_ms / 1'000) * 1'000); + int wait_result = pthread_cond_timedwait(&cv_primitive->cv, &mutex_primitive->mutex, &time); + result = (wait_result != ETIMEDOUT); + } + return result; +} + +DN_API bool DN_OS_ConditionVariableWait(DN_OSConditionVariable *cv, DN_OSMutex *mutex, DN_U64 sleep_ms) +{ + DN_U64 end_ts_ms = DN_OS_DateUnixTimeMs() + sleep_ms; + bool result = DN_OS_ConditionVariableWaitUntil(cv, mutex, end_ts_ms); + return result; +} + +DN_API void DN_OS_ConditionVariableSignal(DN_OSConditionVariable *cv) +{ + if (cv && cv->handle != 0) { + DN_POSIXSyncPrimitive *primitive = DN_OS_U64ToPOSIXSyncPrimitive_(cv->handle); + pthread_cond_signal(&primitive->cv); + } +} + +DN_API void DN_OS_ConditionVariableBroadcast(DN_OSConditionVariable *cv) +{ + if (cv && cv->handle != 0) { + DN_POSIXSyncPrimitive *primitive = DN_OS_U64ToPOSIXSyncPrimitive_(cv->handle); + pthread_cond_broadcast(&primitive->cv); + } +} + +// NOTE: DN_OSThread /////////////////////////////////////////////////////////////////////////////// +static void *DN_OS_ThreadFunc_(void *user_context) +{ + DN_OS_ThreadExecute_(user_context); + return nullptr; +} + +DN_API bool DN_OS_ThreadInit(DN_OSThread *thread, DN_OSThreadFunc *func, void *user_context) +{ + bool result = false; + if (!thread) + return result; + + thread->func = func; + thread->user_context = user_context; + thread->init_semaphore = DN_OS_SemaphoreInit(0 /*initial_count*/); + + // TODO(doyle): Check if semaphore is valid + // NOTE: pthread_t is essentially the thread ID. In Windows, the handle and + // the ID are different things. For pthreads then we just duplicate the + // thread ID to both variables + pthread_t p_thread = {}; + static_assert(sizeof(p_thread) <= sizeof(thread->handle), + "We store the thread handle opaquely in our abstraction, " + "there must be enough bytes to store pthread's structure"); + static_assert(sizeof(p_thread) <= sizeof(thread->thread_id), + "We store the thread handle opaquely in our abstraction, " + "there must be enough bytes to store pthread's structure"); + + pthread_attr_t attribs = {}; + pthread_attr_init(&attribs); + result = pthread_create(&p_thread, &attribs, DN_OS_ThreadFunc_, thread) == 0; + pthread_attr_destroy(&attribs); + + if (result) { + DN_Memcpy(&thread->handle, &p_thread, sizeof(p_thread)); + DN_Memcpy(&thread->thread_id, &p_thread, sizeof(p_thread)); + } + + if (result) { + DN_OS_SemaphoreIncrement(&thread->init_semaphore, 1); + } else { + DN_OS_SemaphoreDeinit(&thread->init_semaphore); + *thread = {}; + } + + return result; +} + +DN_API void DN_OS_ThreadDeinit(DN_OSThread *thread) +{ + if (!thread || !thread->handle) + return; + + pthread_t thread_id = {}; + DN_Memcpy(&thread_id, &thread->thread_id, sizeof(thread_id)); + + void *return_val = nullptr; + pthread_join(thread_id, &return_val); + thread->handle = {}; + thread->thread_id = {}; +} + +DN_API DN_U32 DN_OS_ThreadID() +{ + pid_t result = gettid(); + DN_Assert(gettid() >= 0); + return DN_CAST(DN_U32) result; +} + +DN_API void DN_Posix_ThreadSetName(DN_Str8 name) +{ + DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(nullptr); + DN_Str8 copy = DN_Str8_Copy(tmem.arena, name); + pthread_t thread = pthread_self(); + pthread_setname_np(thread, (char *)copy.data); +} + +DN_API DN_POSIXProcSelfStatus DN_Posix_ProcSelfStatus() +{ + DN_POSIXProcSelfStatus result = {}; + + // NOTE: Example + // + // ... + // VmPeak: 3352 kB + // VmSize: 3352 kB + // VmLck: 0 kB + // ... + // + // VmSize is the total virtual memory used + DN_OSFile file = DN_OS_FileOpen(DN_STR8("/proc/self/status"), DN_OSFileOpen_OpenIfExist, DN_OSFileAccess_Read, nullptr); + DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(nullptr); + + if (!file.error) { + char buf[256]; + DN_Str8Builder builder = DN_Str8Builder_InitFromTLS(); + for (;;) { + DN_OSFileRead read = DN_OS_FileRead(&file, buf, sizeof(buf), nullptr); + if (!read.success || read.bytes_read == 0) + break; + DN_Str8Builder_AppendF(&builder, "%.*s", DN_CAST(int)read.bytes_read, buf); + } + + DN_Str8 const NAME = DN_STR8("Name:"); + DN_Str8 const PID = DN_STR8("Pid:"); + DN_Str8 const VM_PEAK = DN_STR8("VmPeak:"); + DN_Str8 const VM_SIZE = DN_STR8("VmSize:"); + DN_Str8 status_buf = DN_Str8Builder_BuildFromTLS(&builder); + DN_Slice lines = DN_Str8_SplitAllocFromTLS(status_buf, DN_STR8("\n"), DN_Str8SplitIncludeEmptyStrings_No); + + for (DN_ForIt(line_it, DN_Str8, &lines)) { + DN_Str8 line = DN_Str8_TrimWhitespaceAround(*line_it.data); + if (DN_Str8_StartsWith(line, NAME, DN_Str8EqCase_Insensitive)) { + DN_Str8 str8 = DN_Str8_TrimWhitespaceAround(DN_Str8_Slice(line, NAME.size, line.size)); + result.name_size = DN_Min(str8.size, sizeof(result.name)); + DN_Memcpy(result.name, str8.data, result.name_size); + } else if (DN_Str8_StartsWith(line, PID, DN_Str8EqCase_Insensitive)) { + DN_Str8 str8 = DN_Str8_TrimWhitespaceAround(DN_Str8_Slice(line, PID.size, line.size)); + DN_Str8ToU64Result to_u64 = DN_Str8_ToU64(str8, 0); + result.pid = to_u64.value; + DN_Assert(to_u64.success); + } else if (DN_Str8_StartsWith(line, VM_SIZE, DN_Str8EqCase_Insensitive)) { + DN_Str8 size_with_kb = DN_Str8_TrimWhitespaceAround(DN_Str8_Slice(line, VM_SIZE.size, line.size)); + DN_Assert(DN_Str8_EndsWith(size_with_kb, DN_STR8("kB"))); + DN_Str8 vm_size = DN_Str8_BinarySplit(size_with_kb, DN_STR8(" ")).lhs; + DN_Str8ToU64Result to_u64 = DN_Str8_ToU64(vm_size, 0); + result.vm_size = DN_Kilobytes(to_u64.value); + DN_Assert(to_u64.success); + } else if (DN_Str8_StartsWith(line, VM_PEAK, DN_Str8EqCase_Insensitive)) { + DN_Str8 size_with_kb = DN_Str8_TrimWhitespaceAround(DN_Str8_Slice(line, VM_PEAK.size, line.size)); + DN_Assert(DN_Str8_EndsWith(size_with_kb, DN_STR8("kB"))); + DN_Str8 vm_size = DN_Str8_BinarySplit(size_with_kb, DN_STR8(" ")).lhs; + DN_Str8ToU64Result to_u64 = DN_Str8_ToU64(vm_size, 0); + result.vm_peak = DN_Kilobytes(to_u64.value); + DN_Assert(to_u64.success); + } + } + } + DN_OS_FileClose(&file); + return result; +} + +// NOTE: DN_OSHttp ///////////////////////////////////////////////////////////////////////////////// +#if 0 // TODO(doyle): Implement websockets for Windows and Emscripten +static EM_BOOL EMWebSocketOnOpenCallback(int type, const EmscriptenWebSocketOpenEvent *event, void *user_context) +{ + (void)user_context; + (void)type; + (void)event; + // EMSCRIPTEN_RESULT result = emscripten_websocket_send_utf8_text(event->socket, R"({"jsonrpc":"2.0","id":1,"method": "eth_subscribe","params":["newHeads"]})"); + // if (result) + // DN_LOG_InfoF("Failed to emscripten_websocket_send_utf8_text(): %d\n", result); + return EM_TRUE; +} + +static EM_BOOL EMWebSocketOnMsgCallback(int type, const EmscriptenWebSocketMessageEvent *event __attribute__((nonnull)), void *user_context) +{ + (void)type; + (void)user_context; + (void)event; + if (event->isText) { + DN_LOG_InfoF("Received: %.*s", event->numBytes, event->data); + } else { + DN_LOG_InfoF("Received: %d bytes", event->numBytes); + } + return EM_TRUE; +} + +static EM_BOOL EMWebSocketOnErrorCallback(int type, const EmscriptenWebSocketErrorEvent *event, void *user_context) +{ + (void)user_context; + (void)type; + (void)event; + return EM_TRUE; +} + +static EM_BOOL EMWebSocketOnCloseCallback(int type, const EmscriptenWebSocketCloseEvent *event, void *user_context) +{ + (void)user_context; + (void)type; + (void)event; + return EM_TRUE; +} +#endif + +#if defined(DN_PLATFORM_EMSCRIPTEN) +static void DN_OS_HttpRequestEMFetchOnSuccessCallback(emscripten_fetch_t *fetch) +{ + DN_OSHttpResponse *response = DN_CAST(DN_OSHttpResponse *) fetch->userData; + if (!DN_Check(response)) + return; + + response->http_status = DN_CAST(DN_U32) fetch->status; + response->body = DN_Str8_Alloc(response->arena, fetch->numBytes, DN_ZeroMem_No); + if (response->body.data) + DN_Memcpy(response->body.data, fetch->data, fetch->numBytes); + + DN_OS_SemaphoreIncrement(&response->on_complete_semaphore, 1); + DN_Atomic_AddU32(&response->done, 1); +} + +static void DN_OS_HttpRequestEMFetchOnErrorCallback(emscripten_fetch_t *fetch) +{ + DN_OSHttpResponse *response = DN_CAST(DN_OSHttpResponse *) fetch->userData; + if (!DN_Check(response)) + return; + + response->http_status = DN_CAST(DN_U32) fetch->status; + response->body = DN_Str8_Alloc(response->arena, fetch->numBytes, DN_ZeroMem_No); + if (response->body.size) + DN_Memcpy(response->body.data, fetch->data, fetch->numBytes); + + DN_OS_SemaphoreIncrement(&response->on_complete_semaphore, 1); + DN_Atomic_AddU32(&response->done, 1); +} +#endif + +DN_API void DN_OS_HttpRequestAsync(DN_OSHttpResponse *response, + DN_Arena *arena, + DN_Str8 host, + DN_Str8 path, + DN_OSHttpRequestSecure secure, + DN_Str8 method, + DN_Str8 body, + DN_Str8 headers) +{ + if (!response || !arena) + return; + + response->arena = arena; + response->builder.arena = + response->tmem_arena ? response->tmem_arena : &response->tmp_arena; + + DN_Arena *tmem = response->tmem_arena; + DN_OSTLSTMem tmem_ = DN_OS_TLSTMem(arena); + if (!tmem) + tmem = tmem_.arena; + +#if defined(DN_PLATFORM_EMSCRIPTEN) + emscripten_fetch_attr_t fetch_attribs = {}; + emscripten_fetch_attr_init(&fetch_attribs); + + if (method.size >= sizeof(fetch_attribs.requestMethod)) { + response->error_msg = + DN_Str8_InitF(arena, + "Request method in EM has a size limit of 31 characters, method was " + "'%.*s' which is %zu characters long", + DN_STR_FMT(method), + method.size); + DN_CheckF(method.size < sizeof(fetch_attribs.requestMethod), + "%.*s", + DN_STR_FMT(response->error_msg)); + response->error_code = DN_CAST(DN_U32) - 1; + DN_Atomic_AddU32(&response->done, 1); + return; + } + + DN_Memcpy(fetch_attribs.requestMethod, method.data, method.size); + + fetch_attribs.requestData = body.data; + fetch_attribs.requestDataSize = body.size; + fetch_attribs.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY; + fetch_attribs.onsuccess = DN_OS_HttpRequestEMFetchOnSuccessCallback; + fetch_attribs.onerror = DN_OS_HttpRequestEMFetchOnErrorCallback; + fetch_attribs.userData = response; + + DN_Str8 url = DN_Str8_InitF(scratch_arena, "%.*s%.*s", DN_STR_FMT(host), DN_STR_FMT(path)); + DN_LOG_InfoF("Initiating HTTP '%s' request to '%.*s' with payload '%.*s'", + fetch_attribs.requestMethod, + DN_STR_FMT(url), + DN_STR_FMT(body)); + response->on_complete_semaphore = DN_OS_SemaphoreInit(0); + response->em_handle = emscripten_fetch(&fetch_attribs, url.data); +#else // #elif defined(DN_OS_WIN32) + DN_InvalidCodePathF("Unimplemented function"); +#endif +} + +DN_API void DN_OS_HttpRequestFree(DN_OSHttpResponse *response) +{ +// NOTE: Cleanup +#if defined(DN_PLATFORM_EMSCRIPTEN) + if (response->em_handle) { + emscripten_fetch_close(response->em_handle); + response->em_handle = nullptr; + } +#endif // #elif defined(DN_OS_WIN32) + + DN_Arena_Deinit(&response->tmp_arena); + DN_OS_SemaphoreDeinit(&response->on_complete_semaphore); + *response = {}; +} +#elif defined(DN_PLATFORM_WIN32) + // DN: Single header generator inlined this file => #include "OS/dn_os_w32.cpp" +#define DN_OS_WIN32_CPP + +/* +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$$$$$\ $$$$$$\ $$\ $$\ $$$$$$\ $$\ $$\ $$$$$$\ $$$$$$\ +// $$ __$$\ $$ __$$\ $$ | $\ $$ |\_$$ _|$$$\ $$ |$$ ___$$\ $$ __$$\ +// $$ / $$ |$$ / \__| $$ |$$$\ $$ | $$ | $$$$\ $$ |\_/ $$ |\__/ $$ | +// $$ | $$ |\$$$$$$\ $$ $$ $$\$$ | $$ | $$ $$\$$ | $$$$$ / $$$$$$ | +// $$ | $$ | \____$$\ $$$$ _$$$$ | $$ | $$ \$$$$ | \___$$\ $$ ____/ +// $$ | $$ |$$\ $$ | $$$ / \$$$ | $$ | $$ |\$$$ |$$\ $$ |$$ | +// $$$$$$ |\$$$$$$ | $$ / \$$ |$$$$$$\ $$ | \$$ |\$$$$$$ |$$$$$$$$\ +// \______/ \______/ \__/ \__|\______|\__| \__| \______/ \________| +// +// dn_os_w32.cpp +// +//////////////////////////////////////////////////////////////////////////////////////////////////// +*/ + +// NOTE: DN_Mem /////////////////////////////////////////////////////////////////////////// +static DN_U32 DN_OS_MemConvertPageToOSFlags_(DN_U32 protect) +{ + DN_Assert((protect & ~DN_MemPage_All) == 0); + DN_Assert(protect != 0); + DN_U32 result = 0; + + if (protect & DN_MemPage_NoAccess) { + result = PAGE_NOACCESS; + } else if (protect & DN_MemPage_ReadWrite) { + result = PAGE_READWRITE; + } else if (protect & DN_MemPage_Read) { + result = PAGE_READONLY; + } else if (protect & DN_MemPage_Write) { + DN_LOG_WarningF("Windows does not support write-only pages, granting read+write access"); + result = PAGE_READWRITE; + } + + if (protect & DN_MemPage_Guard) + result |= PAGE_GUARD; + + DN_AssertF(result != PAGE_GUARD, "Page guard is a modifier, you must also specify a page permission like read or/and write"); + return result; +} + +DN_API void *DN_OS_MemReserve(DN_USize size, DN_MemCommit commit, DN_U32 page_flags) +{ + unsigned long os_page_flags = DN_OS_MemConvertPageToOSFlags_(page_flags); + unsigned long flags = MEM_RESERVE; + if (commit == DN_MemCommit_Yes) + flags |= MEM_COMMIT; + + void *result = VirtualAlloc(nullptr, size, flags, os_page_flags); + if (flags & MEM_COMMIT) { + DN_Assert(g_dn_os_core_); + DN_Atomic_AddU64(&g_dn_os_core_->vmem_allocs_total, 1); + DN_Atomic_AddU64(&g_dn_os_core_->vmem_allocs_frame, 1); + } + return result; +} + +DN_API bool DN_OS_MemCommit(void *ptr, DN_USize size, DN_U32 page_flags) +{ + bool result = false; + if (!ptr || size == 0) + return false; + unsigned long os_page_flags = DN_OS_MemConvertPageToOSFlags_(page_flags); + result = VirtualAlloc(ptr, size, MEM_COMMIT, os_page_flags) != nullptr; + DN_Assert(g_dn_os_core_); + DN_Atomic_AddU64(&g_dn_os_core_->vmem_allocs_total, 1); + DN_Atomic_AddU64(&g_dn_os_core_->vmem_allocs_frame, 1); + return result; +} + +DN_API void DN_OS_MemDecommit(void *ptr, DN_USize size) +{ + // NOTE: This is a decommit call, which is explicitly saying to free the + // pages but not the address space, you would use OS_MemRelease to release + // everything. + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(6250) // Calling 'VirtualFree' without the MEM_RELEASE flag might free memory but not address descriptors (VADs). This causes address space leaks. + VirtualFree(ptr, size, MEM_DECOMMIT); + DN_MSVC_WARNING_POP +} + +DN_API void DN_OS_MemRelease(void *ptr, DN_USize size) +{ + (void)size; + VirtualFree(ptr, 0, MEM_RELEASE); +} + +DN_API int DN_OS_MemProtect(void *ptr, DN_USize size, DN_U32 page_flags) +{ + if (!ptr || size == 0) + return 0; + + static DN_Str8 const ALIGNMENT_ERROR_MSG = + DN_STR8("Page protection requires pointers to be page aligned because we can only guard memory at a multiple of the page boundary."); + DN_AssertF(DN_IsPowerOfTwoAligned(DN_CAST(uintptr_t) ptr, g_dn_os_core_->page_size), "%s", ALIGNMENT_ERROR_MSG.data); + DN_AssertF(DN_IsPowerOfTwoAligned(size, g_dn_os_core_->page_size), "%s", ALIGNMENT_ERROR_MSG.data); + + unsigned long os_page_flags = DN_OS_MemConvertPageToOSFlags_(page_flags); + unsigned long prev_flags = 0; + int result = VirtualProtect(ptr, size, os_page_flags, &prev_flags); + + (void)prev_flags; + if (result == 0) + DN_AssertF(result, "VirtualProtect failed"); + return result; +} + +DN_API void *DN_OS_MemAlloc(DN_USize size, DN_ZeroMem zero_mem) +{ + DN_U32 flags = zero_mem == DN_ZeroMem_Yes ? HEAP_ZERO_MEMORY : 0; + DN_Assert(size <= DN_CAST(DWORD)(-1)); + void *result = HeapAlloc(GetProcessHeap(), flags, DN_CAST(DWORD) size); + DN_Assert(g_dn_os_core_); + DN_Atomic_AddU64(&g_dn_os_core_->mem_allocs_total, 1); + DN_Atomic_AddU64(&g_dn_os_core_->mem_allocs_frame, 1); + return result; +} + +DN_API void DN_OS_MemDealloc(void *ptr) +{ + HeapFree(GetProcessHeap(), 0, ptr); +} + +// NOTE: Date ////////////////////////////////////////////////////////////////////////////////////// +DN_API DN_OSDateTime DN_OS_DateLocalTimeNow() +{ + SYSTEMTIME sys_time; + GetLocalTime(&sys_time); + + DN_OSDateTime result = {}; + result.hour = DN_CAST(uint8_t) sys_time.wHour; + result.minutes = DN_CAST(uint8_t) sys_time.wMinute; + result.seconds = DN_CAST(uint8_t) sys_time.wSecond; + result.day = DN_CAST(uint8_t) sys_time.wDay; + result.month = DN_CAST(uint8_t) sys_time.wMonth; + result.year = DN_CAST(int16_t) sys_time.wYear; + return result; +} + +const DN_U64 DN_OS_WIN32_UNIX_TIME_START = 0x019DB1DED53E8000; // January 1, 1970 (start of Unix epoch) in "ticks" +const DN_U64 DN_OS_WIN32_FILE_TIME_TICKS_PER_SECOND = 10'000'000; // Filetime returned is in intervals of 100 nanoseconds + +DN_API DN_U64 DN_OS_DateUnixTimeNs() +{ + FILETIME file_time; + GetSystemTimeAsFileTime(&file_time); + + // NOTE: Filetime returned is in intervals of 100 nanoeseconds so we + // multiply by 100 to get nanoseconds. + LARGE_INTEGER date_time; + date_time.u.LowPart = file_time.dwLowDateTime; + date_time.u.HighPart = file_time.dwHighDateTime; + DN_U64 result = (date_time.QuadPart - DN_OS_WIN32_UNIX_TIME_START) * 100; + return result; +} + +static SYSTEMTIME DN_OS_DateToSystemTime_(DN_OSDateTime date) +{ + SYSTEMTIME result = {}; + result.wYear = date.year; + result.wMonth = date.month; + result.wDay = date.day; + result.wHour = date.hour; + result.wMinute = date.minutes; + result.wSecond = date.seconds; + return result; +} + +static DN_U64 DN_OS_SystemTimeToUnixTimeS_(SYSTEMTIME *sys_time) +{ + FILETIME file_time = {}; + SystemTimeToFileTime(sys_time, &file_time); + + LARGE_INTEGER date_time; + date_time.u.LowPart = file_time.dwLowDateTime; + date_time.u.HighPart = file_time.dwHighDateTime; + DN_U64 result = (date_time.QuadPart - DN_OS_WIN32_UNIX_TIME_START) / DN_OS_WIN32_FILE_TIME_TICKS_PER_SECOND; + return result; +} + +DN_API DN_U64 DN_OS_DateLocalToUnixTimeS(DN_OSDateTime date) +{ + SYSTEMTIME local_time = DN_OS_DateToSystemTime_(date); + SYSTEMTIME sys_time = {}; + TzSpecificLocalTimeToSystemTime(nullptr, &local_time, &sys_time); + DN_U64 result = DN_OS_SystemTimeToUnixTimeS_(&sys_time); + return result; +} + +DN_API DN_U64 DN_OS_DateToUnixTimeS(DN_OSDateTime date) +{ + DN_Assert(DN_OS_DateIsValid(date)); + + SYSTEMTIME sys_time = DN_OS_DateToSystemTime_(date); + DN_U64 result = DN_OS_SystemTimeToUnixTimeS_(&sys_time); + return result; +} + +DN_API DN_OSDateTime DN_OS_DateUnixTimeSToDate(DN_U64 time) +{ + // NOTE: Windows epoch time starts from Jan 1, 1601 and counts in + // 100-nanoseconds intervals. + // + // See: https://devblogs.microsoft.com/oldnewthing/20090306-00/?p=18913 + + DN_U64 w32_time = 116'444'736'000'000'000 + (time * 10'000'000); + SYSTEMTIME sys_time = {}; + FILETIME file_time = {}; + file_time.dwLowDateTime = (DWORD)w32_time; + file_time.dwHighDateTime = w32_time >> 32; + FileTimeToSystemTime(&file_time, &sys_time); + + DN_OSDateTime result = {}; + result.year = DN_CAST(uint16_t) sys_time.wYear; + result.month = DN_CAST(uint8_t) sys_time.wMonth; + result.day = DN_CAST(uint8_t) sys_time.wDay; + result.hour = DN_CAST(uint8_t) sys_time.wHour; + result.minutes = DN_CAST(uint8_t) sys_time.wMinute; + result.seconds = DN_CAST(uint8_t) sys_time.wSecond; + return result; +} + +DN_API bool DN_OS_SecureRNGBytes(void *buffer, DN_U32 size) +{ + DN_Assert(g_dn_os_core_); + DN_W32Core *w32 = DN_CAST(DN_W32Core *) g_dn_os_core_->platform_context; + + if (!buffer || size < 0 || !w32->bcrypt_init_success) + return false; + + if (size == 0) + return true; + + long gen_status = BCryptGenRandom(w32->bcrypt_rng_handle, DN_CAST(unsigned char *) buffer, size, 0 /*flags*/); + if (gen_status != 0) { + DN_LOG_ErrorF("Failed to generate random bytes: %d", gen_status); + return false; + } + + return true; +} + +DN_API DN_OSDiskSpace DN_OS_DiskSpace(DN_Str8 path) +{ + DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(nullptr); + DN_OSDiskSpace result = {}; + DN_Str16 path16 = DN_W32_Str8ToStr16(tmem.arena, path); + + ULARGE_INTEGER free_bytes_avail_to_caller; + ULARGE_INTEGER total_number_of_bytes; + ULARGE_INTEGER total_number_of_free_bytes; + if (!GetDiskFreeSpaceExW(path16.data, + &free_bytes_avail_to_caller, + &total_number_of_bytes, + &total_number_of_free_bytes)) + return result; + + result.success = true; + result.avail = free_bytes_avail_to_caller.QuadPart; + result.size = total_number_of_bytes.QuadPart; + return result; +} + +DN_API bool DN_OS_SetEnvVar(DN_Str8 name, DN_Str8 value) +{ + DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(nullptr); + DN_Str16 name16 = DN_W32_Str8ToStr16(tmem.arena, name); + DN_Str16 value16 = DN_W32_Str8ToStr16(tmem.arena, value); + bool result = SetEnvironmentVariableW(name16.data, value16.data) != 0; + return result; +} + +DN_API DN_Str8 DN_OS_EXEPath(DN_Arena *arena) +{ + DN_Str8 result = {}; + if (!arena) + return result; + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + DN_Str16 exe_dir16 = DN_W32_EXEPathW(tmem.arena); + result = DN_W32_Str16ToStr8(arena, exe_dir16); + return result; +} + +DN_API void DN_OS_SleepMs(DN_UInt milliseconds) +{ + Sleep(milliseconds); +} + +DN_API DN_U64 DN_OS_PerfCounterFrequency() +{ + DN_Assert(g_dn_os_core_); + DN_W32Core *w32 = DN_CAST(DN_W32Core *) g_dn_os_core_->platform_context; + DN_Assert(w32->qpc_frequency.QuadPart); + DN_U64 result = w32->qpc_frequency.QuadPart; + return result; +} + +DN_API DN_U64 DN_OS_PerfCounterNow() +{ + LARGE_INTEGER integer = {}; + QueryPerformanceCounter(&integer); + DN_U64 result = integer.QuadPart; + return result; +} + +#if !defined(DN_NO_OS_FILE_API) +static DN_U64 DN_W32_FileTimeToSeconds_(FILETIME const *time) +{ + ULARGE_INTEGER time_large_int = {}; + time_large_int.u.LowPart = time->dwLowDateTime; + time_large_int.u.HighPart = time->dwHighDateTime; + DN_U64 result = (time_large_int.QuadPart / 10000000ULL) - 11644473600ULL; + return result; +} + +DN_API DN_OSPathInfo DN_OS_PathInfo(DN_Str8 path) +{ + DN_OSPathInfo result = {}; + if (!DN_Str8_HasData(path)) + return result; + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str16 path16 = DN_W32_Str8ToStr16(tmem.arena, path); + + WIN32_FILE_ATTRIBUTE_DATA attrib_data = {}; + if (!GetFileAttributesExW(path16.data, GetFileExInfoStandard, &attrib_data)) + return result; + + result.exists = true; + result.create_time_in_s = DN_W32_FileTimeToSeconds_(&attrib_data.ftCreationTime); + result.last_access_time_in_s = DN_W32_FileTimeToSeconds_(&attrib_data.ftLastAccessTime); + result.last_write_time_in_s = DN_W32_FileTimeToSeconds_(&attrib_data.ftLastWriteTime); + + LARGE_INTEGER large_int = {}; + large_int.u.HighPart = DN_CAST(int32_t) attrib_data.nFileSizeHigh; + large_int.u.LowPart = attrib_data.nFileSizeLow; + result.size = (DN_U64)large_int.QuadPart; + + if (attrib_data.dwFileAttributes != INVALID_FILE_ATTRIBUTES) { + if (attrib_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + result.type = DN_OSPathInfoType_Directory; + else + result.type = DN_OSPathInfoType_File; + } + + return result; +} + +DN_API bool DN_OS_PathDelete(DN_Str8 path) +{ + bool result = false; + if (!DN_Str8_HasData(path)) + return result; + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str16 path16 = DN_W32_Str8ToStr16(tmem.arena, path); + if (path16.size) { + result = DeleteFileW(path16.data); + if (!result) + result = RemoveDirectoryW(path16.data); + } + return result; +} + +DN_API bool DN_OS_FileExists(DN_Str8 path) +{ + bool result = false; + if (!DN_Str8_HasData(path)) + return result; + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str16 path16 = DN_W32_Str8ToStr16(tmem.arena, path); + if (path16.size) { + WIN32_FILE_ATTRIBUTE_DATA attrib_data = {}; + if (GetFileAttributesExW(path16.data, GetFileExInfoStandard, &attrib_data)) + result = (attrib_data.dwFileAttributes != INVALID_FILE_ATTRIBUTES) && + !(attrib_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); + } + return result; +} + +DN_API bool DN_OS_CopyFile(DN_Str8 src, DN_Str8 dest, bool overwrite, DN_OSErrSink *err) +{ + bool result = false; + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str16 src16 = DN_W32_Str8ToStr16(tmem.arena, src); + DN_Str16 dest16 = DN_W32_Str8ToStr16(tmem.arena, dest); + + int fail_if_exists = overwrite == false; + result = CopyFileW(src16.data, dest16.data, fail_if_exists) != 0; + + if (!result) { + DN_W32Error win_error = DN_W32_LastError(tmem.arena); + DN_OS_ErrSinkAppendF(err, + win_error.code, + "Failed to copy file '%.*s' to '%.*s': (%u) %.*s", + DN_STR_FMT(src), + DN_STR_FMT(dest), + win_error.code, + DN_STR_FMT(win_error.msg)); + } + return result; +} + +DN_API bool DN_OS_MoveFile(DN_Str8 src, DN_Str8 dest, bool overwrite, DN_OSErrSink *err) +{ + bool result = false; + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str16 src16 = DN_W32_Str8ToStr16(tmem.arena, src); + DN_Str16 dest16 = DN_W32_Str8ToStr16(tmem.arena, dest); + + unsigned long flags = MOVEFILE_COPY_ALLOWED; + if (overwrite) + flags |= MOVEFILE_REPLACE_EXISTING; + + result = MoveFileExW(src16.data, dest16.data, flags) != 0; + if (!result) { + DN_W32Error win_error = DN_W32_LastError(tmem.arena); + DN_OS_ErrSinkAppendF(err, + win_error.code, + "Failed to move file '%.*s' to '%.*s': (%u) %.*s", + DN_STR_FMT(src), + DN_STR_FMT(dest), + win_error.code, + DN_STR_FMT(win_error.msg)); + } + return result; +} + +DN_API bool DN_OS_MakeDir(DN_Str8 path) +{ + bool result = true; + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str16 path16 = DN_W32_Str8ToStr16(tmem.arena, path); + + // NOTE: Go back from the end of the string to all the directories in the + // string, and try to create them. Since Win32 API cannot create + // intermediate directories that don't exist in a path we need to go back + // and record all the directories until we encounter one that exists. + // + // From that point onwards go forwards and make all the directories + // inbetween by null-terminating the string temporarily, creating the + // directory and so forth until we reach the end. + // + // If we find a file at some point in the path we fail out because the + // series of directories can not be made if a file exists with the same + // name. + for (DN_USize index = 0; index < path16.size; index++) { + bool first_char = index == (path16.size - 1); + wchar_t ch = path16.data[index]; + if (ch == '/' || ch == '\\' || first_char) { + wchar_t temp = path16.data[index]; + if (!first_char) + path16.data[index] = 0; // Temporarily null terminate it + + WIN32_FILE_ATTRIBUTE_DATA attrib_data = {}; + bool successful = GetFileAttributesExW(path16.data, GetFileExInfoStandard, &attrib_data); // Check + + if (successful) { + if (attrib_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + // NOTE: The directory exists, continue iterating the path + } else { + // NOTE: There's some kind of file that exists at the path + // but it's not a directory. This request to make a + // directory is invalid. + return false; + } + } else { + // NOTE: There's nothing that exists at this path, we can create + // a directory here + result |= (CreateDirectoryW(path16.data, nullptr) == 0); + } + + if (!first_char) + path16.data[index] = temp; // Undo null termination + } + } + return result; +} + +DN_API bool DN_OS_DirExists(DN_Str8 path) +{ + bool result = false; + if (!DN_Str8_HasData(path)) + return result; + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str16 path16 = DN_W32_Str8ToStr16(tmem.arena, path); + if (path16.size) { + WIN32_FILE_ATTRIBUTE_DATA attrib_data = {}; + if (GetFileAttributesExW(path16.data, GetFileExInfoStandard, &attrib_data)) + result = (attrib_data.dwFileAttributes != INVALID_FILE_ATTRIBUTES) && + (attrib_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); + } + + return result; +} + +DN_API bool DN_OS_DirIterate(DN_Str8 path, DN_OSDirIterator *it) +{ + if (!DN_Str8_HasData(path) || !it || path.size <= 0) + return false; + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_W32FolderIteratorW wide_it = {}; + DN_Str16 path16 = {}; + if (it->handle) { + wide_it.handle = it->handle; + } else { + bool needs_asterisks = DN_Str8_EndsWith(path, DN_STR8("\\")) || + DN_Str8_EndsWith(path, DN_STR8("/")); + bool has_glob = DN_Str8_EndsWith(path, DN_STR8("\\*")) || + DN_Str8_EndsWith(path, DN_STR8("/*")); + + DN_Str8 adjusted_path = path; + if (!has_glob) { + // NOTE: We are missing the glob for enumerating the files, we will + // add those characters in this branch, so overwrite the null + // character, add the glob and re-null terminate the buffer. + if (needs_asterisks) + adjusted_path = DN_OS_PathF(tmem.arena, "%.*s*", DN_STR_FMT(path)); + else + adjusted_path = DN_OS_PathF(tmem.arena, "%.*s/*", DN_STR_FMT(path)); + } + + path16 = DN_W32_Str8ToStr16(tmem.arena, adjusted_path); + if (path16.size <= 0) // Conversion error + return false; + } + + bool result = DN_W32_DirWIterate(path16, &wide_it); + it->handle = wide_it.handle; + if (result) { + int size = DN_W32_Str16ToStr8Buffer(wide_it.file_name, it->buffer, DN_ArrayCountU(it->buffer)); + it->file_name = DN_Str8_Init(it->buffer, size); + } + + return result; +} + +// NOTE: R/W Stream API //////////////////////////////////////////////////////////////////////////// +DN_API DN_OSFile DN_OS_FileOpen(DN_Str8 path, DN_OSFileOpen open_mode, DN_U32 access, DN_OSErrSink *err) +{ + DN_OSFile result = {}; + if (!DN_Str8_HasData(path) || path.size <= 0) + return result; + + if ((access & ~DN_OSFileAccess_All) || ((access & DN_OSFileAccess_All) == 0)) { + DN_InvalidCodePath; + return result; + } + + unsigned long create_flag = 0; + switch (open_mode) { + case DN_OSFileOpen_CreateAlways: create_flag = CREATE_ALWAYS; break; + case DN_OSFileOpen_OpenIfExist: create_flag = OPEN_EXISTING; break; + case DN_OSFileOpen_OpenAlways: create_flag = OPEN_ALWAYS; break; + default: DN_InvalidCodePath; return result; + } + + unsigned long access_mode = 0; + if (access & DN_OSFileAccess_AppendOnly) { + DN_AssertF((access & ~DN_OSFileAccess_AppendOnly) == 0, + "Append can only be applied exclusively to the file, other access modes not permitted"); + access_mode = FILE_APPEND_DATA; + } else { + if (access & DN_OSFileAccess_Read) + access_mode |= GENERIC_READ; + if (access & DN_OSFileAccess_Write) + access_mode |= GENERIC_WRITE; + if (access & DN_OSFileAccess_Execute) + access_mode |= GENERIC_EXECUTE; + } + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str16 path16 = DN_W32_Str8ToStr16(tmem.arena, path); + void *handle = CreateFileW(/*LPCWSTR lpFileName*/ path16.data, + /*DWORD dwDesiredAccess*/ access_mode, + /*DWORD dwShareMode*/ FILE_SHARE_READ | FILE_SHARE_WRITE, + /*LPSECURITY_ATTRIBUTES lpSecurityAttributes*/ nullptr, + /*DWORD dwCreationDisposition*/ create_flag, + /*DWORD dwFlagsAndAttributes*/ FILE_ATTRIBUTE_NORMAL, + /*HANDLE hTemplateFile*/ nullptr); + + if (handle == INVALID_HANDLE_VALUE) { + DN_W32Error win_error = DN_W32_LastError(tmem.arena); + result.error = true; + DN_OS_ErrSinkAppendF(err, win_error.code, "Failed to open file at '%.*s': '%.*s'", DN_STR_FMT(path), DN_STR_FMT(win_error.msg)); + return result; + } + + result.handle = handle; + return result; +} + +DN_API DN_OSFileRead DN_OS_FileRead(DN_OSFile *file, void *buffer, DN_USize size, DN_OSErrSink *err) +{ + DN_OSFileRead result = {}; + if (!file || !file->handle || file->error || !buffer || size <= 0) + return result; + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + if (!DN_Check(size <= (unsigned long)-1)) { + DN_Str8 buffer_size_str8 = DN_CVT_U64ToByteSizeStr8(tmem.arena, size, DN_CVTU64ByteSizeType_Auto); + DN_OS_ErrSinkAppendF( + err, + 1 /*error_code*/, + "Current implementation doesn't support reading >4GiB file (requested %.*s), implement Win32 overlapped IO", + DN_STR_FMT(buffer_size_str8)); + return result; + } + + unsigned long bytes_read = 0; + unsigned long read_result = ReadFile(/*HANDLE hFile*/ file->handle, + /*LPVOID lpBuffer*/ buffer, + /*DWORD nNumberOfBytesToRead*/ DN_CAST(unsigned long) size, + /*LPDWORD lpNumberOfByesRead*/ &bytes_read, + /*LPOVERLAPPED lpOverlapped*/ nullptr); + if (read_result == 0) { + DN_W32Error win_error = DN_W32_LastError(tmem.arena); + DN_OS_ErrSinkAppendF(err, win_error.code, "Failed to read data from file: (%u) %.*s", win_error.code, DN_STR_FMT(win_error.msg)); + return result; + } + + if (bytes_read != size) { + DN_W32Error win_error = DN_W32_LastError(tmem.arena); + DN_OS_ErrSinkAppendF( + err, + win_error.code, + "Failed to read the desired number of bytes from file, we read %uB but we expected %uB: (%u) %.*s", + bytes_read, + DN_CAST(unsigned long) size, + win_error.code, + DN_STR_FMT(win_error.msg)); + return result; + } + + result.bytes_read = bytes_read; + result.success = true; + return result; +} + +DN_API bool DN_OS_FileWritePtr(DN_OSFile *file, void const *buffer, DN_USize size, DN_OSErrSink *err) +{ + if (!file || !file->handle || file->error || !buffer || size <= 0) + return false; + + bool result = true; + char const *end = DN_CAST(char *) buffer + size; + for (char const *ptr = DN_CAST(char const *) buffer; result && ptr != end;) { + unsigned long write_size = DN_CAST(unsigned long) DN_Min((unsigned long)-1, end - ptr); + unsigned long bytes_written = 0; + result = WriteFile(file->handle, ptr, write_size, &bytes_written, nullptr /*lpOverlapped*/) != 0; + ptr += bytes_written; + } + + if (!result) { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_W32Error win_error = DN_W32_LastError(tmem.arena); + DN_Str8 buffer_size_str8 = DN_CVT_U64ToByteSizeStr8(tmem.arena, size, DN_CVTU64ByteSizeType_Auto); + DN_OS_ErrSinkAppendF(err, win_error.code, "Failed to write buffer (%.*s) to file handle: %.*s", DN_STR_FMT(buffer_size_str8), DN_STR_FMT(win_error.msg)); + } + return result; +} + +DN_API bool DN_OS_FileFlush(DN_OSFile *file, DN_OSErrSink *err) +{ + if (!file || !file->handle || file->error) + return false; + + BOOL result = FlushFileBuffers(DN_CAST(HANDLE) file->handle); + if (!result) { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_W32Error win_error = DN_W32_LastError(tmem.arena); + DN_OS_ErrSinkAppendF(err, win_error.code, "Failed to flush file buffer to disk: %.*s", DN_STR_FMT(win_error.msg)); + } + + return DN_CAST(bool) result; +} + +DN_API void DN_OS_FileClose(DN_OSFile *file) +{ + if (!file || !file->handle || file->error) + return; + CloseHandle(file->handle); + *file = {}; +} +#endif // !defined(DN_NO_OS_FILE_API) + +// NOTE: DN_OSExec ///////////////////////////////////////////////////////////////////////////////// +DN_API void DN_OS_Exit(int32_t exit_code) +{ + ExitProcess(DN_CAST(UINT) 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_OSErrSink *err) +{ + DN_OSExecResult result = {}; + size_t stdout_buffer_size = 0; + size_t stderr_buffer_size = 0; + if (stdout_size) { + stdout_buffer_size = *stdout_size; + *stdout_size = 0; + } + + if (stderr_size) { + stderr_buffer_size = *stderr_size; + *stderr_size = 0; + } + + if (!handle.process || handle.os_error_code || handle.exit_code) { + if (handle.os_error_code) + result.os_error_code = handle.os_error_code; + else + result.exit_code = handle.exit_code; + + DN_Assert(!handle.stdout_read); + DN_Assert(!handle.stdout_write); + DN_Assert(!handle.stderr_read); + DN_Assert(!handle.stderr_write); + DN_Assert(!handle.process); + return result; + } + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DWORD stdout_bytes_available = 0; + DWORD stderr_bytes_available = 0; + PeekNamedPipe(handle.stdout_read, nullptr, 0, nullptr, &stdout_bytes_available, nullptr); + PeekNamedPipe(handle.stderr_read, nullptr, 0, nullptr, &stderr_bytes_available, nullptr); + + DWORD exec_result = WAIT_TIMEOUT; + if (stdout_bytes_available == 0 && stderr_bytes_available == 0) + exec_result = WaitForSingleObject(handle.process, timeout_ms); + + if (exec_result == WAIT_FAILED) { + DN_W32Error win_error = DN_W32_LastError(tmem.arena); + result.os_error_code = win_error.code; + DN_OS_ErrSinkAppendF(err, result.os_error_code, "Executed command failed to terminate: %.*s", DN_STR_FMT(win_error.msg)); + } else if (DN_Check(exec_result == WAIT_TIMEOUT || exec_result == WAIT_OBJECT_0)) { + // NOTE: Read stdout from process ////////////////////////////////////////////////////// + // If the pipes are full, the process will block. We periodically + // flush the pipes to make sure this doesn't happen + char sink[DN_Kilobytes(8)]; + stdout_bytes_available = 0; + if (PeekNamedPipe(handle.stdout_read, nullptr, 0, nullptr, &stdout_bytes_available, nullptr)) { + if (stdout_bytes_available) { + DWORD bytes_read = 0; + char *dest_buffer = handle.stdout_write && stdout_buffer ? stdout_buffer : sink; + size_t dest_size = handle.stdout_write && stdout_buffer ? stdout_buffer_size : DN_ArrayCountU(sink); + BOOL success = ReadFile(handle.stdout_read, dest_buffer, DN_CAST(DWORD) dest_size, &bytes_read, NULL); + (void)success; // TODO: + if (stdout_size) + *stdout_size = bytes_read; + } + } + + // NOTE: Read stderr from process ////////////////////////////////////////////////////// + stderr_bytes_available = 0; + if (PeekNamedPipe(handle.stderr_read, nullptr, 0, nullptr, &stderr_bytes_available, nullptr)) { + if (stderr_bytes_available) { + char *dest_buffer = handle.stderr_write && stderr_buffer ? stderr_buffer : sink; + size_t dest_size = handle.stderr_write && stderr_buffer ? stderr_buffer_size : DN_ArrayCountU(sink); + DWORD bytes_read = 0; + BOOL success = ReadFile(handle.stderr_read, dest_buffer, DN_CAST(DWORD) dest_size, &bytes_read, NULL); + (void)success; // TODO: + if (stderr_size) + *stderr_size = bytes_read; + } + } + } + + result.finished = exec_result == WAIT_OBJECT_0 || exec_result == WAIT_FAILED; + if (exec_result == WAIT_OBJECT_0) { + DWORD exit_status; + if (GetExitCodeProcess(handle.process, &exit_status)) { + result.exit_code = exit_status; + } else { + DN_W32Error win_error = DN_W32_LastError(tmem.arena); + result.os_error_code = win_error.code; + DN_OS_ErrSinkAppendF(err, + result.os_error_code, + "Failed to retrieve command exit code: %.*s", + DN_STR_FMT(win_error.msg)); + } + + // NOTE: Cleanup /////////////////////////////////////////////////////////////////////////////// + CloseHandle(handle.stdout_write); + CloseHandle(handle.stderr_write); + CloseHandle(handle.stdout_read); + CloseHandle(handle.stderr_read); + CloseHandle(handle.process); + } + + result.stdout_text = DN_Str8_Init(stdout_buffer, stdout_size ? *stdout_size : 0); + result.stderr_text = DN_Str8_Init(stderr_buffer, stderr_size ? *stderr_size : 0); + return result; +} + +DN_API DN_OSExecResult DN_OS_ExecWait(DN_OSExecAsyncHandle handle, DN_Arena *arena, DN_OSErrSink *err) +{ + DN_OSExecResult result = {}; + if (!handle.process || handle.os_error_code || handle.exit_code) { + result.finished = true; + if (handle.os_error_code) + result.os_error_code = handle.os_error_code; + else + result.exit_code = handle.exit_code; + + DN_Assert(!handle.stdout_read); + DN_Assert(!handle.stdout_write); + DN_Assert(!handle.stderr_read); + DN_Assert(!handle.stderr_write); + DN_Assert(!handle.process); + return result; + } + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + DN_Str8Builder stdout_builder = {}; + DN_Str8Builder stderr_builder = {}; + if (arena) { + stdout_builder.arena = tmem.arena; + stderr_builder.arena = tmem.arena; + } + + DN_U32 const SLOW_WAIT_TIME_MS = 100; + DN_U32 const FAST_WAIT_TIME_MS = 20; + DN_U32 wait_ms = FAST_WAIT_TIME_MS; + while (!result.finished) { + size_t stdout_size = DN_Kilobytes(8); + size_t stderr_size = DN_Kilobytes(8); + char *stdout_buffer = DN_Arena_NewArray(tmem.arena, char, stdout_size, DN_ZeroMem_No); + char *stderr_buffer = DN_Arena_NewArray(tmem.arena, char, stderr_size, DN_ZeroMem_No); + result = DN_OS_ExecPump(handle, stdout_buffer, &stdout_size, stderr_buffer, &stderr_size, wait_ms, err); + DN_Str8Builder_AppendCopy(&stdout_builder, result.stdout_text); + DN_Str8Builder_AppendCopy(&stderr_builder, result.stderr_text); + wait_ms = (DN_Str8_HasData(result.stdout_text) || DN_Str8_HasData(result.stderr_text)) ? FAST_WAIT_TIME_MS : SLOW_WAIT_TIME_MS; + } + + // NOTE: Get stdout/stderr. If no arena is passed this is a no-op ////////////////////////////// + result.stdout_text = DN_Str8Builder_Build(&stdout_builder, arena); + result.stderr_text = DN_Str8Builder_Build(&stderr_builder, arena); + return result; +} + +DN_API DN_OSExecAsyncHandle DN_OS_ExecAsync(DN_Slice cmd_line, DN_OSExecArgs *args, DN_OSErrSink *err) +{ + // NOTE: Pre-amble ///////////////////////////////////////////////////////////////////////////// + DN_OSExecAsyncHandle result = {}; + if (cmd_line.size == 0) + return result; + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 cmd_rendered = DN_Slice_Str8Render(tmem.arena, cmd_line, DN_STR8(" ")); + DN_Str16 cmd16 = DN_W32_Str8ToStr16(tmem.arena, cmd_rendered); + DN_Str16 working_dir16 = DN_W32_Str8ToStr16(tmem.arena, args->working_dir); + + DN_Str8Builder env_builder = DN_Str8Builder_InitFromTLS(); + DN_Str8Builder_AppendArrayRef(&env_builder, args->environment.data, args->environment.size); + if (env_builder.string_size) + DN_Str8Builder_AppendRef(&env_builder, DN_STR8("\0")); + + DN_Str8 env_block8 = DN_Str8Builder_BuildDelimitedFromTLS(&env_builder, DN_STR8("\0")); + DN_Str16 env_block16 = {}; + if (env_block8.size) + env_block16 = DN_W32_Str8ToStr16(tmem.arena, env_block8); + + // NOTE: Stdout/err security attributes //////////////////////////////////////////////////////// + SECURITY_ATTRIBUTES save_std_security_attribs = {}; + save_std_security_attribs.nLength = sizeof(save_std_security_attribs); + save_std_security_attribs.bInheritHandle = true; + + // NOTE: Redirect stdout /////////////////////////////////////////////////////////////////////// + HANDLE stdout_read = {}; + HANDLE stdout_write = {}; + DN_DEFER + { + if (result.os_error_code || result.exit_code) { + CloseHandle(stdout_read); + CloseHandle(stdout_write); + } + }; + + if (DN_Bit_IsSet(args->flags, DN_OSExecFlags_SaveStdout)) { + if (!CreatePipe(&stdout_read, &stdout_write, &save_std_security_attribs, /*nSize*/ 0)) { + DN_W32Error win_error = DN_W32_LastError(tmem.arena); + result.os_error_code = win_error.code; + DN_OS_ErrSinkAppendF( + err, + result.os_error_code, + "Failed to create stdout pipe to redirect the output of the command '%.*s': %.*s", + DN_STR_FMT(cmd_rendered), + DN_STR_FMT(win_error.msg)); + return result; + } + + if (!SetHandleInformation(stdout_read, HANDLE_FLAG_INHERIT, 0)) { + DN_W32Error win_error = DN_W32_LastError(tmem.arena); + result.os_error_code = win_error.code; + DN_OS_ErrSinkAppendF(err, + result.os_error_code, + "Failed to make stdout 'read' pipe non-inheritable when trying to " + "execute command '%.*s': %.*s", + DN_STR_FMT(cmd_rendered), + DN_STR_FMT(win_error.msg)); + return result; + } + } + + // NOTE: Redirect stderr /////////////////////////////////////////////////////////////////////// + HANDLE stderr_read = {}; + HANDLE stderr_write = {}; + DN_DEFER + { + if (result.os_error_code || result.exit_code) { + CloseHandle(stderr_read); + CloseHandle(stderr_write); + } + }; + + if (DN_Bit_IsSet(args->flags, DN_OSExecFlags_SaveStderr)) { + if (DN_Bit_IsSet(args->flags, DN_OSExecFlags_MergeStderrToStdout)) { + stderr_read = stdout_read; + stderr_write = stdout_write; + } else { + if (!CreatePipe(&stderr_read, &stderr_write, &save_std_security_attribs, /*nSize*/ 0)) { + DN_W32Error win_error = DN_W32_LastError(tmem.arena); + result.os_error_code = win_error.code; + DN_OS_ErrSinkAppendF( + err, + result.os_error_code, + "Failed to create stderr pipe to redirect the output of the command '%.*s': %.*s", + DN_STR_FMT(cmd_rendered), + DN_STR_FMT(win_error.msg)); + return result; + } + + if (!SetHandleInformation(stderr_read, HANDLE_FLAG_INHERIT, 0)) { + DN_W32Error win_error = DN_W32_LastError(tmem.arena); + result.os_error_code = win_error.code; + DN_OS_ErrSinkAppendF(err, + result.os_error_code, + "Failed to make stderr 'read' pipe non-inheritable when trying to " + "execute command '%.*s': %.*s", + DN_STR_FMT(cmd_rendered), + DN_STR_FMT(win_error.msg)); + return result; + } + } + } + + // NOTE: Execute command /////////////////////////////////////////////////////////////////////// + PROCESS_INFORMATION proc_info = {}; + STARTUPINFOW startup_info = {}; + startup_info.cb = sizeof(STARTUPINFOW); + startup_info.hStdError = stderr_write ? stderr_write : GetStdHandle(STD_ERROR_HANDLE); + startup_info.hStdOutput = stdout_write ? stdout_write : GetStdHandle(STD_OUTPUT_HANDLE); + startup_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + startup_info.dwFlags |= STARTF_USESTDHANDLES; + BOOL create_result = CreateProcessW(nullptr, + cmd16.data, + nullptr, + nullptr, + true, + CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT, + env_block16.data, + working_dir16.data, + &startup_info, + &proc_info); + if (!create_result) { + DN_W32Error win_error = DN_W32_LastError(tmem.arena); + result.os_error_code = win_error.code; + DN_OS_ErrSinkAppendF(err, + result.os_error_code, + "Failed to execute command '%.*s': %.*s", + DN_STR_FMT(cmd_rendered), + DN_STR_FMT(win_error.msg)); + return result; + } + + // NOTE: Post-amble //////////////////////////////////////////////////////////////////////////// + CloseHandle(proc_info.hThread); + result.process = proc_info.hProcess; + result.stdout_read = stdout_read; + result.stdout_write = stdout_write; + if (DN_Bit_IsSet(args->flags, DN_OSExecFlags_SaveStderr) && DN_Bit_IsNotSet(args->flags, DN_OSExecFlags_MergeStderrToStdout)) { + result.stderr_read = stderr_read; + result.stderr_write = stderr_write; + } + result.exec_flags = args->flags; + return result; +} + +static DN_W32Core *DN_OS_GetW32Core_() +{ + DN_Assert(g_dn_os_core_ && g_dn_os_core_->platform_context); + DN_W32Core *result = DN_CAST(DN_W32Core *)g_dn_os_core_->platform_context; + return result; +} + +static DN_W32SyncPrimitive *DN_OS_U64ToW32SyncPrimitive_(DN_U64 u64) +{ + DN_W32SyncPrimitive *result = nullptr; + DN_Memcpy(&result, &u64, sizeof(u64)); + return result; +} + +static DN_U64 DN_W32_SyncPrimitiveToU64(DN_W32SyncPrimitive *primitive) +{ + DN_U64 result = 0; + static_assert(sizeof(result) == sizeof(primitive), "Pointer size mis-match"); + DN_Memcpy(&result, &primitive, sizeof(result)); + return result; +} + +static DN_W32SyncPrimitive *DN_W32_AllocSyncPrimitive_() +{ + DN_W32Core *w32 = DN_OS_GetW32Core_(); + DN_W32SyncPrimitive *result = nullptr; + EnterCriticalSection(&w32->sync_primitive_free_list_mutex); + { + if (w32->sync_primitive_free_list) { + result = w32->sync_primitive_free_list; + w32->sync_primitive_free_list = w32->sync_primitive_free_list->next; + result->next = nullptr; + } else { + DN_OSCore *os = g_dn_os_core_; + result = DN_Arena_New(&os->arena, DN_W32SyncPrimitive, DN_ZeroMem_Yes); + } + } + LeaveCriticalSection(&w32->sync_primitive_free_list_mutex); + return result; +} + +static void DN_W32_DeallocSyncPrimitive_(DN_W32SyncPrimitive *primitive) +{ + if (primitive) { + DN_W32Core *w32 = DN_OS_GetW32Core_(); + EnterCriticalSection(&w32->sync_primitive_free_list_mutex); + primitive->next = w32->sync_primitive_free_list; + w32->sync_primitive_free_list = primitive; + LeaveCriticalSection(&w32->sync_primitive_free_list_mutex); + } +} + +// NOTE: DN_OSSemaphore //////////////////////////////////////////////////////////////////////////// +DN_API DN_OSSemaphore DN_OS_SemaphoreInit(DN_U32 initial_count) +{ + DN_OSSemaphore result = {}; + DN_W32SyncPrimitive *primitive = DN_W32_AllocSyncPrimitive_(); + if (primitive) { + SECURITY_ATTRIBUTES security_attribs = {}; + primitive->sem = CreateSemaphoreA(&security_attribs, initial_count, INT32_MAX, nullptr /*name*/); + if (primitive->sem) + result.handle = DN_W32_SyncPrimitiveToU64(primitive); + if (!primitive->sem) + DN_W32_DeallocSyncPrimitive_(primitive); + } + return result; +} + +DN_API void DN_OS_SemaphoreDeinit(DN_OSSemaphore *semaphore) +{ + if (semaphore && semaphore->handle != 0) { + DN_W32SyncPrimitive *primitive = DN_OS_U64ToW32SyncPrimitive_(semaphore->handle); + CloseHandle(primitive->sem); + DN_W32_DeallocSyncPrimitive_(primitive); + *semaphore = {}; + } +} + +DN_API void DN_OS_SemaphoreIncrement(DN_OSSemaphore *semaphore, DN_U32 amount) +{ + if (semaphore && semaphore->handle != 0) { + DN_W32SyncPrimitive *primitive = DN_OS_U64ToW32SyncPrimitive_(semaphore->handle); + LONG prev_count = 0; + ReleaseSemaphore(primitive->sem, amount, &prev_count); + } +} + +DN_API DN_OSSemaphoreWaitResult DN_OS_SemaphoreWait(DN_OSSemaphore *semaphore, DN_U32 timeout_ms) +{ + DN_OSSemaphoreWaitResult result = {}; + if (semaphore && semaphore->handle != 0) { + DN_W32SyncPrimitive *primitive = DN_OS_U64ToW32SyncPrimitive_(semaphore->handle); + DWORD wait_result = WaitForSingleObject(primitive->sem, timeout_ms == DN_OS_SEMAPHORE_INFINITE_TIMEOUT ? INFINITE : timeout_ms); + if (wait_result == WAIT_TIMEOUT) + result = DN_OSSemaphoreWaitResult_Timeout; + else if (wait_result == WAIT_OBJECT_0) + result = DN_OSSemaphoreWaitResult_Success; + } + return result; +} + +// NOTE: DN_OSMutex //////////////////////////////////////////////////////////////////////////////// +DN_API DN_OSMutex DN_OS_MutexInit() +{ + DN_W32SyncPrimitive *primitive = DN_W32_AllocSyncPrimitive_(); + if (primitive) + InitializeCriticalSection(&primitive->mutex); + DN_OSMutex result = {}; + result.handle = DN_W32_SyncPrimitiveToU64(primitive); + return result; +} + +DN_API void DN_OS_MutexDeinit(DN_OSMutex *mutex) +{ + if (mutex && mutex->handle != 0) { + DN_W32SyncPrimitive *primitive = DN_OS_U64ToW32SyncPrimitive_(mutex->handle); + DeleteCriticalSection(&primitive->mutex); + DN_W32_DeallocSyncPrimitive_(primitive); + *mutex = {}; + } +} + +DN_API void DN_OS_MutexLock(DN_OSMutex *mutex) +{ + if (mutex && mutex->handle != 0) { + DN_W32SyncPrimitive *primitive = DN_OS_U64ToW32SyncPrimitive_(mutex->handle); + EnterCriticalSection(&primitive->mutex); + } +} + +DN_API void DN_OS_MutexUnlock(DN_OSMutex *mutex) +{ + if (mutex && mutex->handle != 0) { + DN_W32SyncPrimitive *primitive = DN_OS_U64ToW32SyncPrimitive_(mutex->handle); + LeaveCriticalSection(&primitive->mutex); + } +} + +// NOTE: DN_OSConditionVariable //////////////////////////////////////////////////////////////////// +DN_API DN_OSConditionVariable DN_OS_ConditionVariableInit() +{ + DN_W32SyncPrimitive *primitive = DN_W32_AllocSyncPrimitive_(); + if (primitive) + InitializeConditionVariable(&primitive->cv); + DN_OSConditionVariable result = {}; + result.handle = DN_W32_SyncPrimitiveToU64(primitive); + return result; +} + +DN_API void DN_OS_ConditionVariableDeinit(DN_OSConditionVariable *cv) +{ + if (cv && cv->handle != 0) { + DN_W32SyncPrimitive *primitive = DN_OS_U64ToW32SyncPrimitive_(cv->handle); + DN_W32_DeallocSyncPrimitive_(primitive); + *cv = {}; + } +} + +DN_API bool DN_OS_ConditionVariableWaitUntil(DN_OSConditionVariable *cv, DN_OSMutex *mutex, DN_U64 end_ts_ms) +{ + bool result = false; + DN_U64 now_ms = DN_OS_DateUnixTimeNs() / (1000 * 1000); + if (now_ms < end_ts_ms) { + DN_U64 sleep_ms = end_ts_ms - now_ms; + result = DN_OS_ConditionVariableWait(cv, mutex, sleep_ms); + } + return result; +} + +DN_API bool DN_OS_ConditionVariableWait(DN_OSConditionVariable *cv, DN_OSMutex *mutex, DN_U64 sleep_ms) +{ + bool result = false; + if (mutex && cv && mutex->handle != 0 && cv->handle != 0 && sleep_ms > 0) { + DN_W32SyncPrimitive *mutex_primitive = DN_OS_U64ToW32SyncPrimitive_(mutex->handle); + DN_W32SyncPrimitive *cv_primitive = DN_OS_U64ToW32SyncPrimitive_(cv->handle); + result = SleepConditionVariableCS(&cv_primitive->cv, &mutex_primitive->mutex, DN_CAST(DWORD) sleep_ms); + } + return result; +} + +DN_API void DN_OS_ConditionVariableSignal(DN_OSConditionVariable *cv) +{ + if (cv && cv->handle != 0) { + DN_W32SyncPrimitive *primitive = DN_OS_U64ToW32SyncPrimitive_(cv->handle); + WakeConditionVariable(&primitive->cv); + } +} + +DN_API void DN_OS_ConditionVariableBroadcast(DN_OSConditionVariable *cv) +{ + if (cv && cv->handle != 0) { + DN_W32SyncPrimitive *primitive = DN_OS_U64ToW32SyncPrimitive_(cv->handle); + WakeAllConditionVariable(&primitive->cv); + } +} + +// NOTE: DN_OSThread /////////////////////////////////////////////////////////////////////////////// +static DWORD __stdcall DN_OS_ThreadFunc_(void *user_context) +{ + DN_OS_ThreadExecute_(user_context); + return 0; +} + +DN_API bool DN_OS_ThreadInit(DN_OSThread *thread, DN_OSThreadFunc *func, void *user_context) +{ + bool result = false; + if (!thread) + return result; + + thread->func = func; + thread->user_context = user_context; + thread->init_semaphore = DN_OS_SemaphoreInit(0 /*initial_count*/); + + // TODO(doyle): Check if semaphore is valid + DWORD thread_id = 0; + SECURITY_ATTRIBUTES security_attribs = {}; + thread->handle = CreateThread(&security_attribs, + 0 /*stack_size*/, + DN_OS_ThreadFunc_, + thread, + 0 /*creation_flags*/, + &thread_id); + + result = thread->handle != INVALID_HANDLE_VALUE; + if (result) + thread->thread_id = thread_id; + + // NOTE: Ensure that thread_id is set before 'thread->func' is called. + if (result) { + DN_OS_SemaphoreIncrement(&thread->init_semaphore, 1); + } else { + DN_OS_SemaphoreDeinit(&thread->init_semaphore); + *thread = {}; + } + + return result; +} + +DN_API void DN_OS_ThreadDeinit(DN_OSThread *thread) +{ + if (!thread || !thread->handle) + return; + + WaitForSingleObject(thread->handle, INFINITE); + CloseHandle(thread->handle); + thread->handle = INVALID_HANDLE_VALUE; + thread->thread_id = {}; + DN_OS_TLSDeinit(&thread->tls); +} + +DN_API DN_U32 DN_OS_ThreadID() +{ + unsigned long result = GetCurrentThreadId(); + return result; +} + +DN_API void DN_W32_ThreadSetName(DN_Str8 name) +{ + DN_OSTLS *tls = DN_OS_TLSGet(); + DN_ArenaTempMem tmem = DN_Arena_TempMemBegin(tls->arenas + DN_OSTLSArena_TMem0); + + // NOTE: SetThreadDescription is only available in + // Windows Server 2016, Windows 10 LTSB 2016 and Windows 10 version 1607 + // + // See: https://learn.microsoft.com/en-us/windows/w32/api/processthreadsapi/nf-processthreadsapi-setthreaddescription + DN_W32Core *w32 = DN_OS_GetW32Core_(); + if (w32->set_thread_description) { + DN_Str16 name16 = DN_W32_Str8ToStr16(tmem.arena, name); + w32->set_thread_description(GetCurrentThread(), (WCHAR *)name16.data); + DN_Arena_TempMemEnd(tmem); + return; + } + + // NOTE: Fallback to throw-exception method to set thread name + #pragma pack(push, 8) + + struct DN_W32ThreadNameInfo + { + DN_U32 dwType; + char *szName; + DN_U32 dwThreadID; + DN_U32 dwFlags; + }; + + #pragma pack(pop) + + DN_Str8 copy = DN_Str8_Copy(tmem.arena, name); + DN_W32ThreadNameInfo info = {}; + info.dwType = 0x1000; + info.szName = (char *)copy.data; + info.dwThreadID = DN_OS_ThreadID(); + + // TODO: Review warning 6320 + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(6320) // Exception-filter expression is the constant EXCEPTION_EXECUTE_HANDLER. This might mask exceptions that were not intended to be handled + DN_MSVC_WARNING_DISABLE(6322) // Empty _except block + __try { + RaiseException(0x406D1388, 0, sizeof(info) / sizeof(void *), (const ULONG_PTR *)&info); + } __except (EXCEPTION_EXECUTE_HANDLER) { + } + DN_MSVC_WARNING_POP + + DN_Arena_TempMemEnd(tmem); +} + +// NOTE: DN_OSHttp ///////////////////////////////////////////////////////////////////////////////// +void DN_OS_HttpRequestWin32Callback(HINTERNET session, DWORD *dwContext, DWORD dwInternetStatus, VOID *lpvStatusInformation, DWORD dwStatusInformationLength) +{ + (void)session; + (void)dwStatusInformationLength; + + DN_OSHttpResponse *response = DN_CAST(DN_OSHttpResponse *) dwContext; + HINTERNET request = DN_CAST(HINTERNET) response->w32_request_handle; + DN_W32Error error = {}; + DWORD const READ_BUFFER_SIZE = DN_Megabytes(1); + + if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_RESOLVING_NAME) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_NAME_RESOLVED) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_SENDING_REQUEST) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_REQUEST_SENT) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_HANDLE_CREATED) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_DETECTING_PROXY) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_REDIRECT) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_INTERMEDIATE_RESPONSE) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_SECURE_FAILURE) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE) { + DWORD status = 0; + DWORD status_size = sizeof(status_size); + if (WinHttpQueryHeaders(request, + WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, + WINHTTP_HEADER_NAME_BY_INDEX, + &status, + &status_size, + WINHTTP_NO_HEADER_INDEX)) { + response->http_status = DN_CAST(uint16_t) status; + + // NOTE: You can normally call into WinHttpQueryDataAvailable which means the kernel + // will buffer the response into a single buffer and return us the full size of the + // request. + // + // or + // + // You may call WinHttpReadData directly to write the memory into our buffer directly. + // This is advantageous to avoid a copy from the kernel buffer into our buffer. If the + // end user application knows the typical payload size then they can optimise for this + // to prevent unnecessary allocation on the user side. + void *buffer = DN_Arena_Alloc(response->builder.arena, READ_BUFFER_SIZE, 1 /*align*/, DN_ZeroMem_No); + if (!WinHttpReadData(request, buffer, READ_BUFFER_SIZE, nullptr)) + error = DN_W32_LastError(&response->tmp_arena); + } else { + error = DN_W32_LastError(&response->tmp_arena); + } + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_READ_COMPLETE) { + DWORD bytes_read = dwStatusInformationLength; + if (bytes_read) { + DN_Str8 prev_buffer = DN_Str8_Init(DN_CAST(char *) lpvStatusInformation, bytes_read); + DN_Str8Builder_AppendRef(&response->builder, prev_buffer); + + void *buffer = DN_Arena_Alloc(response->builder.arena, READ_BUFFER_SIZE, 1 /*align*/, DN_ZeroMem_No); + if (!WinHttpReadData(request, buffer, READ_BUFFER_SIZE, nullptr)) + error = DN_W32_LastError(&response->tmp_arena); + } + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_REQUEST_ERROR) { + WINHTTP_ASYNC_RESULT *async_result = DN_CAST(WINHTTP_ASYNC_RESULT *) lpvStatusInformation; + error = DN_W32_ErrorCodeToMsg(&response->tmp_arena, DN_CAST(DN_U32) async_result->dwError); + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE) { + if (!WinHttpReceiveResponse(request, 0)) + error = DN_W32_LastError(&response->tmp_arena); + } + + // NOTE: If the request handle is missing, then, the response has been freed. + // MSDN says that this callback can still be called after closing the handle + // and trigger the WINHTTP_CALLBACK_STATUS_REQUEST_ERROR. + if (request) { + bool read_complete = dwInternetStatus == WINHTTP_CALLBACK_STATUS_READ_COMPLETE && dwStatusInformationLength == 0; + if (read_complete) + response->body = DN_Str8Builder_Build(&response->builder, response->arena); + + if (read_complete || dwInternetStatus == WINHTTP_CALLBACK_STATUS_REQUEST_ERROR || error.code) { + DN_OS_SemaphoreIncrement(&response->on_complete_semaphore, 1); + DN_Atomic_AddU32(&response->done, 1); + } + + if (error.code) { + response->error_code = error.code; + response->error_msg = error.msg; + } + } +} + +DN_API void DN_OS_HttpRequestAsync(DN_OSHttpResponse *response, + DN_Arena *arena, + DN_Str8 host, + DN_Str8 path, + DN_OSHttpRequestSecure secure, + DN_Str8 method, + DN_Str8 body, + DN_Str8 headers) +{ + if (!response || !arena) + return; + + response->arena = arena; + response->builder.arena = response->tmem_arena ? response->tmem_arena : &response->tmp_arena; + + DN_Arena *tmem_arena = response->tmem_arena; + DN_OSTLSTMem tmem_ = DN_OS_TLSTMem(arena); + if (!tmem_arena) + tmem_arena = tmem_.arena; + + DN_W32Error error = {}; + DN_DEFER + { + response->error_msg = error.msg; + response->error_code = error.code; + if (error.code) { + // NOTE: 'Wait' handles failures gracefully, skipping the wait and + // cleans up the request + DN_OS_HttpRequestWait(response); + DN_Atomic_AddU32(&response->done, 1); + } + }; + + response->w32_request_session = WinHttpOpen(nullptr /*user agent*/, WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, WINHTTP_FLAG_ASYNC); + if (!response->w32_request_session) { + error = DN_W32_LastError(&response->tmp_arena); + return; + } + + DWORD callback_flags = WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE | + WINHTTP_CALLBACK_STATUS_READ_COMPLETE | + WINHTTP_CALLBACK_STATUS_REQUEST_ERROR | + WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE; + if (WinHttpSetStatusCallback(response->w32_request_session, + DN_CAST(WINHTTP_STATUS_CALLBACK) DN_OS_HttpRequestWin32Callback, + callback_flags, + DN_CAST(DWORD_PTR) nullptr /*dwReserved*/) == WINHTTP_INVALID_STATUS_CALLBACK) { + error = DN_W32_LastError(&response->tmp_arena); + return; + } + + DN_Str16 host16 = DN_W32_Str8ToStr16(tmem_arena, host); + response->w32_request_connection = WinHttpConnect(response->w32_request_session, host16.data, secure ? INTERNET_DEFAULT_HTTPS_PORT : INTERNET_DEFAULT_HTTP_PORT, 0 /*reserved*/); + if (!response->w32_request_connection) { + error = DN_W32_LastError(&response->tmp_arena); + return; + } + + DN_Str16 method16 = DN_W32_Str8ToStr16(tmem_arena, method); + DN_Str16 path16 = DN_W32_Str8ToStr16(tmem_arena, path); + response->w32_request_handle = WinHttpOpenRequest(response->w32_request_connection, + method16.data, + path16.data, + nullptr /*version*/, + nullptr /*referrer*/, + nullptr /*accept types*/, + secure ? WINHTTP_FLAG_SECURE : 0); + if (!response->w32_request_handle) { + error = DN_W32_LastError(&response->tmp_arena); + return; + } + + DN_Str16 headers16 = DN_W32_Str8ToStr16(tmem_arena, headers); + response->on_complete_semaphore = DN_OS_SemaphoreInit(0); + if (!WinHttpSendRequest(response->w32_request_handle, + headers16.data, + DN_CAST(DWORD) headers16.size, + body.data /*optional data*/, + DN_CAST(DWORD) body.size /*optional length*/, + DN_CAST(DWORD) body.size /*total content length*/, + DN_CAST(DWORD_PTR) response)) { + error = DN_W32_LastError(&response->tmp_arena); + return; + } +} + +DN_API void DN_OS_HttpRequestFree(DN_OSHttpResponse *response) +{ + // NOTE: Cleanup + // NOTE: These calls are synchronous even when the HTTP request is async. + WinHttpCloseHandle(response->w32_request_handle); + WinHttpCloseHandle(response->w32_request_connection); + WinHttpCloseHandle(response->w32_request_session); + + response->w32_request_session = nullptr; + response->w32_request_connection = nullptr; + response->w32_request_handle = nullptr; + DN_Arena_Deinit(&response->tmp_arena); + DN_OS_SemaphoreDeinit(&response->on_complete_semaphore); + + *response = {}; +} + +// NOTE: DN_W32 //////////////////////////////////////////////////////////////////////////////////// +DN_API DN_Str16 DN_W32_ErrorCodeToMsg16Alloc(DN_U32 error_code) +{ + DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS; + void *module_to_get_errors_from = nullptr; + if (error_code >= 12000 && error_code <= 12175) { + flags |= FORMAT_MESSAGE_FROM_HMODULE; + module_to_get_errors_from = GetModuleHandleA("winhttp.dll"); + } + + wchar_t *result16 = nullptr; + DWORD size = FormatMessageW(/*DWORD dwFlags */ flags | FORMAT_MESSAGE_ALLOCATE_BUFFER, + /*LPCVOID lpSource */ module_to_get_errors_from, + /*DWORD dwMessageId */ error_code, + /*DWORD dwLanguageId*/ 0, + /*LPWSTR lpBuffer */ (LPWSTR)&result16, + /*DWORD nSize */ 0, + /*va_list *Arguments */ nullptr); + + DN_Str16 result = {result16, size}; + return result; +} + +DN_API DN_W32Error DN_W32_ErrorCodeToMsgAlloc(DN_U32 error_code) +{ + DN_W32Error result = {}; + result.code = error_code; + DN_Str16 error16 = DN_W32_ErrorCodeToMsg16Alloc(error_code); + if (error16.size) + result.msg = DN_W32_Str16ToStr8FromHeap(error16); + if (error16.data) + LocalFree(error16.data); + return result; +} + +DN_API DN_W32Error DN_W32_ErrorCodeToMsg(DN_Arena *arena, DN_U32 error_code) +{ + DN_W32Error result = {}; + result.code = error_code; + if (arena) { + DN_Str16 error16 = DN_W32_ErrorCodeToMsg16Alloc(error_code); + if (error16.size) + result.msg = DN_W32_Str16ToStr8(arena, error16); + if (error16.data) + LocalFree(error16.data); + } + return result; +} + +DN_API DN_W32Error DN_W32_LastError(DN_Arena *arena) +{ + DN_W32Error result = DN_W32_ErrorCodeToMsg(arena, GetLastError()); + return result; +} + +DN_API DN_W32Error DN_W32_LastErrorAlloc() +{ + DN_W32Error result = DN_W32_ErrorCodeToMsgAlloc(GetLastError()); + return result; +} + +DN_API void DN_W32_MakeProcessDPIAware() +{ + typedef bool SetProcessDpiAwareProc(void); + typedef bool SetProcessDpiAwarenessProc(DPI_AWARENESS); + typedef bool SetProcessDpiAwarenessContextProc(void * /*DPI_AWARENESS_CONTEXT*/); + + // NOTE(doyle): Taken from cmuratori/refterm snippet on DPI awareness. It + // appears we can make this robust by just loading user32.dll and using + // GetProcAddress on the DPI function. If it's not there, we're on an old + // version of windows, so we can call an older version of the API. + void *lib_handle = LoadLibraryA("user32.dll"); + if (!lib_handle) + return; + + if (auto *set_process_dpi_awareness_context = DN_CAST(SetProcessDpiAwarenessContextProc *) GetProcAddress(DN_CAST(HMODULE) lib_handle, "SetProcessDpiAwarenessContext")) + set_process_dpi_awareness_context(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + else if (auto *set_process_dpi_awareness = DN_CAST(SetProcessDpiAwarenessProc *) GetProcAddress(DN_CAST(HMODULE) lib_handle, "SetProcessDpiAwareness")) + set_process_dpi_awareness(DPI_AWARENESS_PER_MONITOR_AWARE); + else if (auto *set_process_dpi_aware = DN_CAST(SetProcessDpiAwareProc *) GetProcAddress(DN_CAST(HMODULE) lib_handle, "SetProcessDpiAware")) + set_process_dpi_aware(); +} + +// NOTE: Windows UTF8 to Str16 ////////////////////////////////////////////// +DN_API DN_Str16 DN_W32_Str8ToStr16(DN_Arena *arena, DN_Str8 src) +{ + DN_Str16 result = {}; + if (!arena || !DN_Str8_HasData(src)) + return result; + + int required_size = MultiByteToWideChar(CP_UTF8, 0 /*dwFlags*/, src.data, DN_CAST(int) src.size, nullptr /*dest*/, 0 /*dest size*/); + if (required_size <= 0) + return result; + + wchar_t *buffer = DN_Arena_NewArray(arena, wchar_t, required_size + 1, DN_ZeroMem_No); + if (!buffer) + return result; + + int chars_written = MultiByteToWideChar(CP_UTF8, 0 /*dwFlags*/, src.data, DN_CAST(int) src.size, buffer, required_size); + if (DN_Check(chars_written == required_size)) { + result.data = buffer; + result.size = chars_written; + result.data[result.size] = 0; + } + return result; +} + +DN_API int DN_W32_Str8ToStr16Buffer(DN_Str8 src, wchar_t *dest, int dest_size) +{ + int result = 0; + if (!DN_Str8_HasData(src)) + return result; + + result = MultiByteToWideChar(CP_UTF8, 0 /*dwFlags*/, src.data, DN_CAST(int) src.size, nullptr /*dest*/, 0 /*dest size*/); + if (result <= 0 || result > dest_size || !dest) + return result; + + result = MultiByteToWideChar(CP_UTF8, 0 /*dwFlags*/, src.data, DN_CAST(int) src.size, dest, DN_CAST(int) dest_size); + dest[DN_Min(result, dest_size - 1)] = 0; + return result; +} + +// NOTE: Windows Str16 To UTF8 ////////////////////////////////////////////////////////////////// +DN_API int DN_W32_Str16ToStr8Buffer(DN_Str16 src, char *dest, int dest_size) +{ + int result = 0; + if (!DN_Str16_HasData(src)) + return result; + + int src_size = DN_SaturateCastISizeToInt(src.size); + if (src_size <= 0) + return result; + + result = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.data, src_size, nullptr /*dest*/, 0 /*dest size*/, nullptr, nullptr); + if (result <= 0 || result > dest_size || !dest) + return result; + + result = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.data, src_size, dest, DN_CAST(int) dest_size, nullptr, nullptr); + dest[DN_Min(result, dest_size - 1)] = 0; + return result; +} + +DN_API DN_Str8 DN_W32_Str16ToStr8(DN_Arena *arena, DN_Str16 src) +{ + DN_Str8 result = {}; + if (!arena || !DN_Str16_HasData(src)) + return result; + + int src_size = DN_SaturateCastISizeToInt(src.size); + if (src_size <= 0) + return result; + + int required_size = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.data, src_size, nullptr /*dest*/, 0 /*dest size*/, nullptr, nullptr); + if (required_size <= 0) + return result; + + // NOTE: Str8 allocate ensures there's one extra byte for + // null-termination already so no-need to +1 the required size + DN_ArenaTempMemScope temp_mem = DN_ArenaTempMemScope(arena); + DN_Str8 buffer = DN_Str8_Alloc(arena, required_size, DN_ZeroMem_No); + if (!DN_Str8_HasData(buffer)) + return result; + + int chars_written = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.data, src_size, buffer.data, DN_CAST(int) buffer.size, nullptr, nullptr); + if (DN_Check(chars_written == required_size)) { + result = buffer; + result.data[result.size] = 0; + temp_mem.mem = {}; + } + + return result; +} + +DN_API DN_Str8 DN_W32_Str16ToStr8FromHeap(DN_Str16 src) +{ + DN_Str8 result = {}; + if (!DN_Str16_HasData(src)) + return result; + + int src_size = DN_SaturateCastISizeToInt(src.size); + if (src_size <= 0) + return result; + + int required_size = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.data, src_size, nullptr /*dest*/, 0 /*dest size*/, nullptr, nullptr); + if (required_size <= 0) + return result; + + // NOTE: Str8 allocate ensures there's one extra byte for + // null-termination already so no-need to +1 the required size + DN_Str8 buffer = DN_Str8_AllocFromOSHeap(required_size, DN_ZeroMem_No); + if (!DN_Str8_HasData(buffer)) + return result; + + int chars_written = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.data, src_size, buffer.data, DN_CAST(int) buffer.size, nullptr, nullptr); + if (DN_Check(chars_written == required_size)) { + result = buffer; + result.data[result.size] = 0; + } else { + DN_OS_MemDealloc(buffer.data); + buffer = {}; + } + + return result; +} + +// NOTE: Windows Executable Directory ////////////////////////////////////////// +DN_API DN_Str16 DN_W32_EXEPathW(DN_Arena *arena) +{ + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + DN_Str16 result = {}; + DN_USize module_size = 0; + wchar_t *module_path = nullptr; + do { + module_size += 256; + module_path = DN_Arena_NewArray(tmem.arena, wchar_t, module_size, DN_ZeroMem_No); + if (!module_path) + return result; + module_size = DN_CAST(DN_USize) GetModuleFileNameW(nullptr /*module*/, module_path, DN_CAST(int) module_size); + } while (GetLastError() == ERROR_INSUFFICIENT_BUFFER); + + DN_USize index_of_last_slash = 0; + for (DN_USize index = module_size - 1; !index_of_last_slash && index < module_size; index--) + index_of_last_slash = module_path[index] == '\\' ? index : 0; + + result.data = DN_Arena_NewArray(arena, wchar_t, module_size + 1, DN_ZeroMem_No); + result.size = module_size; + DN_Memcpy(result.data, module_path, sizeof(wchar_t) * result.size); + result.data[result.size] = 0; + return result; +} + +DN_API DN_Str16 DN_W32_EXEDirW(DN_Arena *arena) +{ + // TODO(doyle): Implement a DN_Str16_BinarySearchReverse + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + DN_Str16 result = {}; + DN_USize module_size = 0; + wchar_t *module_path = nullptr; + do { + module_size += 256; + module_path = DN_Arena_NewArray(tmem.arena, wchar_t, module_size, DN_ZeroMem_No); + if (!module_path) + return result; + module_size = DN_CAST(DN_USize) GetModuleFileNameW(nullptr /*module*/, module_path, DN_CAST(int) module_size); + } while (GetLastError() == ERROR_INSUFFICIENT_BUFFER); + + DN_USize index_of_last_slash = 0; + for (DN_USize index = module_size - 1; !index_of_last_slash && index < module_size; index--) + index_of_last_slash = module_path[index] == '\\' ? index : 0; + + result.data = DN_Arena_NewArray(arena, wchar_t, index_of_last_slash + 1, DN_ZeroMem_No); + result.size = index_of_last_slash; + DN_Memcpy(result.data, module_path, sizeof(wchar_t) * result.size); + result.data[result.size] = 0; + return result; +} + +DN_API DN_Str8 DN_W32_WorkingDir(DN_Arena *arena, DN_Str8 suffix) +{ + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + DN_Str16 suffix16 = DN_W32_Str8ToStr16(tmem.arena, suffix); + DN_Str16 dir16 = DN_W32_WorkingDirW(tmem.arena, suffix16); + DN_Str8 result = DN_W32_Str16ToStr8(arena, dir16); + return result; +} + +DN_API DN_Str16 DN_W32_WorkingDirW(DN_Arena *arena, DN_Str16 suffix) +{ + DN_Assert(suffix.size >= 0); + DN_Str16 result = {}; + + // NOTE: required_size is the size required *including* the null-terminator + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + unsigned long required_size = GetCurrentDirectoryW(0, nullptr); + unsigned long desired_size = required_size + DN_CAST(unsigned long) suffix.size; + + wchar_t *tmem_w_path = DN_Arena_NewArray(tmem.arena, wchar_t, desired_size, DN_ZeroMem_No); + if (!tmem_w_path) + return result; + + unsigned long bytes_written_wo_null_terminator = GetCurrentDirectoryW(desired_size, tmem_w_path); + if ((bytes_written_wo_null_terminator + 1) != required_size) { + // TODO(dn): Error + return result; + } + + wchar_t *w_path = DN_Arena_NewArray(arena, wchar_t, desired_size, DN_ZeroMem_No); + if (!w_path) + return result; + + if (suffix.size) { + DN_Memcpy(w_path, tmem_w_path, sizeof(*tmem_w_path) * bytes_written_wo_null_terminator); + DN_Memcpy(w_path + bytes_written_wo_null_terminator, suffix.data, sizeof(suffix.data[0]) * suffix.size); + w_path[desired_size] = 0; + } + + result = DN_Str16{w_path, DN_CAST(DN_USize)(desired_size - 1)}; + return result; +} + +DN_API bool DN_W32_DirWIterate(DN_Str16 path, DN_W32FolderIteratorW *it) +{ + WIN32_FIND_DATAW find_data = {}; + if (it->handle) { + if (FindNextFileW(it->handle, &find_data) == 0) { + FindClose(it->handle); + return false; + } + } else { + it->handle = FindFirstFileExW(path.data, /*LPCWSTR lpFileName,*/ + FindExInfoStandard, /*FINDEX_INFO_LEVELS fInfoLevelId,*/ + &find_data, /*LPVOID lpFindFileData,*/ + FindExSearchNameMatch, /*FINDEX_SEARCH_OPS fSearchOp,*/ + nullptr, /*LPVOID lpSearchFilter,*/ + FIND_FIRST_EX_LARGE_FETCH /*unsigned long dwAdditionalFlags)*/); + + if (it->handle == INVALID_HANDLE_VALUE) + return false; + } + + it->file_name_buf[0] = 0; + it->file_name = DN_Str16{it->file_name_buf, 0}; + + do { + if (find_data.cFileName[0] == '.' || (find_data.cFileName[0] == '.' && find_data.cFileName[1] == '.')) + continue; + + it->file_name.size = DN_CStr16_Size(find_data.cFileName); + DN_Assert(it->file_name.size < (DN_ArrayCountU(it->file_name_buf) - 1)); + DN_Memcpy(it->file_name.data, find_data.cFileName, it->file_name.size * sizeof(wchar_t)); + it->file_name_buf[it->file_name.size] = 0; + break; + } while (FindNextFileW(it->handle, &find_data) != 0); + + bool result = it->file_name.size > 0; + if (!result) + FindClose(it->handle); + return result; +} +#else + #error Please define a platform e.g. 'DN_PLATFORM_WIN32' to enable the correct implementation for platform APIs +#endif +#define DN_CORE_INC_CPP + +// DN: Single header generator inlined this file => #include "Core/dn_core.cpp" +static DN_Core *g_dn_core; + +DN_API void DN_Core_Init(DN_Core *core, DN_CoreOnInit on_init) +{ + DN_Assert(g_dn_os_core_); + g_dn_core = core; + + // NOTE Initialise fields ////////////////////////////////////////////////////////////////////// + #if !defined(DN_NO_PROFILER) + core->profiler = &core->profiler_default_instance; + #endif + + #if defined(DN_LEAK_TRACKING) + // NOTE: Setup the allocation table with allocation tracking turned off on + // the arena we're using to initialise the table. + core->alloc_table_arena = DN_Arena_InitFromOSVMem(DN_Megabytes(1), DN_Kilobytes(512), DN_ArenaFlags_NoAllocTrack | DN_ArenaFlags_AllocCanLeak); + core->alloc_table = DN_DSMap_Init(&core->alloc_table_arena, 4096, DN_DSMapFlags_Nil); + #endif + + // NOTE: Print out init features /////////////////////////////////////////////////////////////// + DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(nullptr); + DN_Str8Builder builder = DN_Str8Builder_Init(tmem.arena); + if (on_init & DN_CoreOnInit_LogLibFeatures) { + DN_Str8Builder_AppendRef(&builder, DN_STR8("DN initialised:\n")); + + DN_F64 page_size_kib = g_dn_os_core_->page_size / 1024.0; + DN_F64 alloc_granularity_kib = g_dn_os_core_->alloc_granularity / 1024.0; + DN_Str8Builder_AppendF( + &builder, " OS Page Size/Alloc Granularity: %.1f/%.1fKiB\n", page_size_kib, alloc_granularity_kib); + + #if DN_HAS_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__) + if (DN_ASAN_POISON) { + DN_Str8Builder_AppendF( + &builder, " ASAN manual poisoning%s\n", DN_ASAN_VET_POISON ? " (+vet sanity checks)" : ""); + DN_Str8Builder_AppendF(&builder, " ASAN poison guard size: %u\n", DN_ASAN_POISON_GUARD_SIZE); + } + #endif + + #if defined(DN_LEAK_TRACKING) + DN_Str8Builder_AppendRef(&builder, DN_STR8(" Allocation leak tracing\n")); + #endif + + #if !defined(DN_NO_PROFILER) + DN_Str8Builder_AppendRef(&builder, DN_STR8(" TSC profiler available\n")); + #endif + // TODO(doyle): Add stacktrace feature log + } + + if (on_init & DN_CoreOnInit_LogCPUFeatures) { + DN_CPUReport const *report = &g_dn_os_core_->cpu_report; + DN_Str8 brand = DN_Str8_TrimWhitespaceAround(DN_Str8_Init(report->brand, sizeof(report->brand) - 1)); + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(6284) // Object passed as _Param_(3) when a string is required in call to 'DN_Str8Builder_AppendF' Actual type: 'struct DN_Str8'. + DN_Str8Builder_AppendF(&builder, " CPU '%S' from '%s' detected:\n", brand, report->vendor); + DN_MSVC_WARNING_POP + + 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_CPU_HasFeature(report, feature_decl.value); + DN_Str8Builder_AppendF(&builder, + " %.*s:%*s%s\n", + DN_STR_FMT(feature_decl.label), + DN_CAST(int)(longest_feature_name - feature_decl.label.size), + "", + has_feature ? "available" : "not available"); + } + } + + DN_Str8 info_log = DN_Str8Builder_Build(&builder, tmem.arena); + if (DN_Str8_HasData(info_log)) + DN_LOG_DebugF("%.*s", DN_STR_FMT(info_log)); +} + +DN_API void DN_Core_BeginFrame() +{ + DN_Atomic_SetValue64(&g_dn_os_core_->mem_allocs_frame, 0); +} + +#if !defined(DN_NO_PROFILER) +DN_API void DN_Core_SetProfiler(DN_Profiler *profiler) +{ + if (profiler) + g_dn_core->profiler = profiler; +} +#endif +// DN: Single header generator inlined this file => #include "Core/dn_core_debug.cpp" +#define DN_CORE_DEBUG_CPP + +DN_API DN_StackTraceWalkResult DN_StackTrace_Walk(DN_Arena *arena, uint16_t limit) +{ + DN_StackTraceWalkResult result = {}; +#if defined(DN_OS_WIN32) + if (!arena) + return result; + + static DN_TicketMutex mutex = {}; + DN_TicketMutex_Begin(&mutex); + + HANDLE thread = GetCurrentThread(); + result.process = GetCurrentProcess(); + + DN_W32Core *w32 = DN_OS_GetW32Core_(); + if (!w32->sym_initialised) { + w32->sym_initialised = true; + SymSetOptions(SYMOPT_LOAD_LINES); + if (!SymInitialize(result.process, nullptr /*UserSearchPath*/, true /*fInvadeProcess*/)) { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + DN_W32Error error = DN_W32_LastError(tmem.arena); + DN_LOG_ErrorF("SymInitialize failed, stack trace can not be generated (%lu): %.*s\n", error.code, DN_STR_FMT(error.msg)); + } + } + + 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_FArray raw_frames = {}; + while (raw_frames.size < 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_FArray_Add(&raw_frames, frame.AddrPC.Offset); + } + DN_TicketMutex_End(&mutex); + + result.base_addr = DN_Arena_NewArray(arena, uint64_t, raw_frames.size, DN_ZeroMem_No); + result.size = DN_CAST(uint16_t) raw_frames.size; + DN_Memcpy(result.base_addr, raw_frames.data, raw_frames.size * sizeof(raw_frames.data[0])); +#else + (void)limit; + (void)arena; +#endif + return result; +} + +static void DN_StackTrace_AddWalkToStr8Builder_(DN_StackTraceWalkResult const *walk, DN_Str8Builder *builder, DN_USize skip) +{ + DN_StackTraceRawFrame raw_frame = {}; + raw_frame.process = walk->process; + for (DN_USize index = skip; index < walk->size; index++) { + raw_frame.base_addr = walk->base_addr[index]; + DN_StackTraceFrame frame = DN_StackTrace_RawFrameToFrame(builder->arena, raw_frame); + DN_Str8Builder_AppendF(builder, "%.*s(%zu): %.*s%s", DN_STR_FMT(frame.file_name), frame.line_number, DN_STR_FMT(frame.function_name), (DN_CAST(int) index == walk->size - 1) ? "" : "\n"); + } +} + +DN_API bool DN_StackTrace_WalkResultIterate(DN_StackTraceWalkResultIterator *it, DN_StackTraceWalkResult const *walk) +{ + bool result = false; + if (!it || !walk || !walk->base_addr || !walk->process) + return result; + + if (it->index >= walk->size) + return false; + + result = true; + it->raw_frame.process = walk->process; + it->raw_frame.base_addr = walk->base_addr[it->index++]; + return result; +} + +DN_API DN_Str8 DN_StackTrace_WalkResultToStr8(DN_Arena *arena, DN_StackTraceWalkResult const *walk, uint16_t skip) +{ + DN_Str8 result{}; + if (!walk || !arena) + return result; + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + DN_Str8Builder builder = DN_Str8Builder_Init(tmem.arena); + DN_StackTrace_AddWalkToStr8Builder_(walk, &builder, skip); + result = DN_Str8Builder_Build(&builder, arena); + return result; +} + +DN_API DN_Str8 DN_StackTrace_WalkStr8(DN_Arena *arena, uint16_t limit, uint16_t skip) +{ + DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(arena); + DN_StackTraceWalkResult walk = DN_StackTrace_Walk(tmem.arena, limit); + DN_Str8 result = DN_StackTrace_WalkResultToStr8(arena, &walk, skip); + return result; +} + +DN_API DN_Str8 DN_StackTrace_WalkStr8FromHeap(uint16_t limit, uint16_t skip) +{ + // NOTE: We don't use WalkResultToStr8 because that uses the TLS arenas which + // does not use the OS heap. + DN_Arena arena = DN_Arena_InitFromOSHeap(DN_Kilobytes(64), DN_ArenaFlags_NoAllocTrack); + DN_Str8Builder builder = DN_Str8Builder_Init(&arena); + DN_StackTraceWalkResult walk = DN_StackTrace_Walk(&arena, limit); + DN_StackTrace_AddWalkToStr8Builder_(&walk, &builder, skip); + DN_Str8 result = DN_Str8Builder_BuildFromOSHeap(&builder); + DN_Arena_Deinit(&arena); + return result; +} + +DN_API DN_Slice DN_StackTrace_GetFrames(DN_Arena *arena, uint16_t limit) +{ + DN_Slice result = {}; + if (!arena) + return result; + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + DN_StackTraceWalkResult walk = DN_StackTrace_Walk(tmem.arena, limit); + if (!walk.size) + return result; + + DN_USize slice_index = 0; + result = DN_Slice_Alloc(arena, walk.size, DN_ZeroMem_No); + for (DN_StackTraceWalkResultIterator it = {}; DN_StackTrace_WalkResultIterate(&it, &walk); ) { + result.data[slice_index++] = DN_StackTrace_RawFrameToFrame(arena, it.raw_frame); + } + return result; +} + +DN_API DN_StackTraceFrame DN_StackTrace_RawFrameToFrame(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_Str16{line.FileName, DN_CStr16_Size(line.FileName)}; + DN_Str16 function_name16 = DN_Str16{symbol->Name, symbol->NameLen}; + + DN_StackTraceFrame result = {}; + result.address = raw_frame.base_addr; + result.line_number = line.LineNumber; + result.file_name = DN_W32_Str16ToStr8(arena, file_name16); + result.function_name = DN_W32_Str16ToStr8(arena, function_name16); + + if (!DN_Str8_HasData(result.function_name)) + result.function_name = DN_STR8(""); + if (!DN_Str8_HasData(result.file_name)) + result.file_name = DN_STR8(""); + #else + DN_StackTraceFrame result = {}; + #endif + return result; +} + +DN_API void DN_StackTrace_Print(uint16_t limit) +{ + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Slice stack_trace = DN_StackTrace_GetFrames(tmem.arena, limit); + for (DN_StackTraceFrame &frame : stack_trace) + DN_OS_PrintErrLnF("%.*s(%I64u): %.*s", DN_STR_FMT(frame.file_name), frame.line_number, DN_STR_FMT(frame.function_name)); +} + +DN_API void DN_StackTrace_ReloadSymbols() +{ + #if defined(DN_OS_WIN32) + HANDLE process = GetCurrentProcess(); + SymRefreshModuleList(process); + #endif +} + +// NOTE: DN_Debug ////////////////////////////////////////////////////////////////////////////////// +#if defined(DN_LEAK_TRACKING) +DN_API void DN_Debug_TrackAlloc(void *ptr, DN_USize size, bool leak_permitted) +{ + if (!ptr) + return; + + DN_TicketMutex_Begin(&g_dn_core->alloc_table_mutex); + DN_DEFER + { + DN_TicketMutex_End(&g_dn_core->alloc_table_mutex); + }; + + // NOTE: If the entry was not added, we are reusing a pointer that has been freed. + // TODO: Add API for always making the item but exposing a var to indicate if the item was newly created or it + // already existed. + DN_Str8 stack_trace = DN_StackTrace_WalkStr8FromHeap(128, 3 /*skip*/); + DN_DSMap *alloc_table = &g_dn_core->alloc_table; + DN_DSMapResult alloc_entry = DN_DSMap_MakeKeyU64(alloc_table, DN_CAST(uint64_t) ptr); + DN_DebugAlloc *alloc = alloc_entry.value; + if (alloc_entry.found) { + if ((alloc->flags & DN_DebugAllocFlag_Freed) == 0) { + DN_Str8 alloc_size = DN_CVT_U64ToByteSizeStr8(alloc_table->arena, alloc->size, DN_CVTU64ByteSizeType_Auto); + DN_Str8 new_alloc_size = DN_CVT_U64ToByteSizeStr8(alloc_table->arena, size, DN_CVTU64ByteSizeType_Auto); + DN_HardAssertF( + alloc->flags & DN_DebugAllocFlag_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_Debug_TrackDealloc()\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_STR_FMT(alloc_size), + DN_STR_FMT(alloc->stack_trace), + DN_STR_FMT(new_alloc_size), + DN_STR_FMT(stack_trace)); + } + + // NOTE: Pointer was reused, clean up the prior entry + g_dn_core->alloc_table_bytes_allocated_for_stack_traces -= alloc->stack_trace.size; + g_dn_core->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_DebugAllocFlag_LeakPermitted : 0; + g_dn_core->alloc_table_bytes_allocated_for_stack_traces += alloc->stack_trace.size; +} + +DN_API void DN_Debug_TrackDealloc(void *ptr) +{ + if (!ptr) + return; + + DN_TicketMutex_Begin(&g_dn_core->alloc_table_mutex); + DN_DEFER { DN_TicketMutex_End(&g_dn_core->alloc_table_mutex); }; + + DN_Str8 stack_trace = DN_StackTrace_WalkStr8FromHeap(128, 3 /*skip*/); + DN_DSMap *alloc_table = &g_dn_core->alloc_table; + DN_DSMapResult alloc_entry = DN_DSMap_FindKeyU64(alloc_table, DN_CAST(uintptr_t) ptr); + DN_HardAssertF(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_DebugAlloc *alloc = alloc_entry.value; + if (alloc->flags & DN_DebugAllocFlag_Freed) { + DN_Str8 freed_size = DN_CVT_U64ToByteSizeStr8(alloc_table->arena, alloc->freed_size, DN_CVTU64ByteSizeType_Auto); + DN_HardAssertF((alloc->flags & DN_DebugAllocFlag_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_STR_FMT(freed_size), + DN_STR_FMT(alloc->stack_trace), + DN_STR_FMT(alloc->freed_stack_trace), + DN_STR_FMT(stack_trace)); + } + + DN_Assert(!DN_Str8_HasData(alloc->freed_stack_trace)); + alloc->flags |= DN_DebugAllocFlag_Freed; + alloc->freed_stack_trace = stack_trace; + g_dn_core->alloc_table_bytes_allocated_for_stack_traces += alloc->freed_stack_trace.size; +} + +DN_API void DN_Debug_DumpLeaks() +{ + uint64_t leak_count = 0; + uint64_t leaked_bytes = 0; + for (DN_USize index = 1; index < g_dn_core->alloc_table.occupied; index++) { + DN_DSMapSlot *slot = g_dn_core->alloc_table.slots + index; + DN_DebugAlloc *alloc = &slot->value; + bool alloc_leaked = (alloc->flags & DN_DebugAllocFlag_Freed) == 0; + bool leak_permitted = (alloc->flags & DN_DebugAllocFlag_LeakPermitted); + if (alloc_leaked && !leak_permitted) { + leaked_bytes += alloc->size; + leak_count++; + DN_Str8 alloc_size = DN_CVT_U64ToByteSizeStr8(g_dn_core->alloc_table.arena, alloc->size, DN_CVTU64ByteSizeType_Auto); + DN_LOG_WarningF("Pointer (0x%p) leaked %.*s at:\n" + "%.*s", + alloc->ptr, DN_STR_FMT(alloc_size), + DN_STR_FMT(alloc->stack_trace)); + } + } + + if (leak_count) { + char buffer[512]; + DN_Arena arena = DN_Arena_InitFromBuffer(buffer, sizeof(buffer), DN_ArenaFlags_Nil); + DN_Str8 leak_size = DN_CVT_U64ToByteSizeStr8(&arena, leaked_bytes, DN_CVTU64ByteSizeType_Auto); + DN_LOG_WarningF("There were %I64u leaked allocations totalling %.*s", leak_count, DN_STR_FMT(leak_size)); + } +} +#endif // DN_LEAK_TRACKING + +#if !defined(DN_NO_PROFILER) +// NOTE: DN_Profiler /////////////////////////////////////////////////////////////////////////////// +DN_API DN_ProfilerZoneScope::DN_ProfilerZoneScope(DN_Str8 name, uint16_t anchor_index) +{ + zone = DN_Profiler_BeginZoneAtIndex(name, anchor_index); +} + +DN_API DN_ProfilerZoneScope::~DN_ProfilerZoneScope() +{ + DN_Profiler_EndZone(zone); +} + +DN_API DN_ProfilerAnchor *DN_Profiler_ReadBuffer() +{ + uint8_t mask = DN_ArrayCountU(g_dn_core->profiler->anchors) - 1; + DN_ProfilerAnchor *result = g_dn_core->profiler->anchors[(g_dn_core->profiler->active_anchor_buffer - 1) & mask]; + return result; +} + +DN_API DN_ProfilerAnchor *DN_Profiler_WriteBuffer() +{ + uint8_t mask = DN_ArrayCountU(g_dn_core->profiler->anchors) - 1; + DN_ProfilerAnchor *result = g_dn_core->profiler->anchors[(g_dn_core->profiler->active_anchor_buffer + 0) & mask]; + return result; +} + +DN_API DN_ProfilerZone DN_Profiler_BeginZoneAtIndex(DN_Str8 name, uint16_t anchor_index) +{ + DN_ProfilerAnchor *anchor = DN_Profiler_WriteBuffer() + anchor_index; + // 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 (DN_Str8_HasData(anchor->name) && anchor->name != name) + DN_AssertF(name == anchor->name, "Potentially overwriting a zone by accident? Anchor is '%.*s', name is '%.*s'", DN_STR_FMT(anchor->name), DN_STR_FMT(name)); + #endif + anchor->name = name; + DN_ProfilerZone result = {}; + result.begin_tsc = DN_CPU_TSC(); + result.anchor_index = anchor_index; + result.parent_zone = g_dn_core->profiler->parent_zone; + result.elapsed_tsc_at_zone_start = anchor->tsc_inclusive; + g_dn_core->profiler->parent_zone = anchor_index; + return result; +} + +DN_API void DN_Profiler_EndZone(DN_ProfilerZone zone) +{ + uint64_t elapsed_tsc = DN_CPU_TSC() - zone.begin_tsc; + DN_ProfilerAnchor *anchor_buffer = DN_Profiler_WriteBuffer(); + DN_ProfilerAnchor *anchor = anchor_buffer + zone.anchor_index; + + anchor->hit_count++; + anchor->tsc_inclusive = zone.elapsed_tsc_at_zone_start + elapsed_tsc; + anchor->tsc_exclusive += elapsed_tsc; + + DN_ProfilerAnchor *parent_anchor = anchor_buffer + zone.parent_zone; + parent_anchor->tsc_exclusive -= elapsed_tsc; + g_dn_core->profiler->parent_zone = zone.parent_zone; +} + +DN_API void DN_Profiler_SwapAnchorBuffer() +{ + g_dn_core->profiler->active_anchor_buffer++; + g_dn_core->profiler->parent_zone = 0; + DN_ProfilerAnchor *anchors = DN_Profiler_WriteBuffer(); + DN_Memset(anchors, + 0, + DN_ArrayCountU(g_dn_core->profiler->anchors[0]) * sizeof(g_dn_core->profiler->anchors[0][0])); +} + +DN_API void DN_Profiler_Dump(uint64_t tsc_per_second) +{ + DN_ProfilerAnchor *anchors = DN_Profiler_ReadBuffer(); + for (size_t anchor_index = 1; anchor_index < DN_PROFILER_ANCHOR_BUFFER_SIZE; anchor_index++) { + DN_ProfilerAnchor const *anchor = anchors + anchor_index; + if (!anchor->hit_count) + continue; + + uint64_t tsc_exclusive = anchor->tsc_exclusive; + uint64_t tsc_inclusive = anchor->tsc_inclusive; + DN_F64 tsc_exclusive_milliseconds = tsc_exclusive * 1000 / DN_CAST(DN_F64) tsc_per_second; + if (tsc_exclusive == tsc_inclusive) { + DN_OS_PrintOutLnF("%.*s[%u]: %.1fms", DN_STR_FMT(anchor->name), anchor->hit_count, tsc_exclusive_milliseconds); + } else { + DN_F64 tsc_inclusive_milliseconds = tsc_inclusive * 1000 / DN_CAST(DN_F64) tsc_per_second; + DN_OS_PrintOutLnF("%.*s[%u]: %.1f/%.1fms", + DN_STR_FMT(anchor->name), + anchor->hit_count, + tsc_exclusive_milliseconds, + tsc_inclusive_milliseconds); + } + } +} +#endif // !defined(DN_NO_PROFILER) + +// DN: Single header generator inlined this file => #include "Core/dn_core_demo.cpp" +/* +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$$$$$$\ $$$$$$\ $$$$$$\ $$$$$$\ +// $$ __$$\ $$ __$$\ $$ __$$\ $$ __$$\ +// $$ | $$ |$$ / $$ |$$ / \__|$$ / \__| +// $$ | $$ |$$ | $$ |$$ | \$$$$$$\ +// $$ | $$ |$$ | $$ |$$ | \____$$\ +// $$ | $$ |$$ | $$ |$$ | $$\ $$\ $$ | +// $$$$$$$ | $$$$$$ |\$$$$$$ |\$$$$$$ | +// \_______/ \______/ \______/ \______/ +// +// dn_docs.cpp -- Library documentation via real code examples +// +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Use this file for documentation and examples of the various APIs in this +// library. Normally docs are written as inline comments in header files, +// however, these quickly go out of date as APIs change. Instead, I provide +// some example code that compiles here that serves to also document the API. +// +// The library header files then become a very minimal reference of exactly the +// function prototypes and definitions instead of massive reams of inline +// comments that visually space out the functions and hinders discoverability +// and/or conciseness of being able to learn the breadth of the APIs. +// +//////////////////////////////////////////////////////////////////////////////////////////////////// +*/ + +DN_MSVC_WARNING_PUSH +DN_MSVC_WARNING_DISABLE(4702) // unreachable code + +void DN_Docs_Demo() +{ +// NOTE: Before using anything in the library, DN_Core_Init() must be +// called, for example: +#if 0 + DN_Core core = {}; + DN_Core_Init(&core, DN_CoreOnInit_Nil); +#endif + + // NOTE: DN_Atomic_SetValue64 ///////////////////////////////////////////////////////////////// + // NOTE: DN_Atomic_SetValue32 ///////////////////////////////////////////////////////////////// + // Atomically set the value into the target using an atomic compare and swap + // idiom. The return value of the function is the value that was last stored + // in the target. + { + uint64_t target = 8; + uint64_t value_to_set = 0xCAFE; + if (DN_Atomic_SetValue64(&target, value_to_set) == 8) { + // Atomic swap was successful, e.g. the last value that this thread + // observed was '8' which is the value we initialised with e.g. no + // other thread has modified the value. + } + } + + // NOTE: DN_CVT_BytesToHex //////////////////////////////////////////////////////////////////////// + { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + unsigned char bytes[2] = {0xFA, 0xCE}; + DN_Str8 hex = DN_CVT_BytesToHex(tmem.arena, bytes, sizeof(bytes)); + DN_Assert(hex == DN_STR8("face")); // NOTE: Guaranteed to be null-terminated + } + + // NOTE: DN_Check ///////////////////////////////////////////////////////////////////////////// + // + // Check the expression trapping in debug, whilst in release- trapping is + // removed and the expression is evaluated as if it were a normal 'if' branch. + // + // This allows handling of the condition gracefully when compiled out but + // traps to notify the developer in builds when it's compiled in. + { + bool flag = true; + if (DN_CheckF(flag, "Flag was false!")) { + /// This branch will execute! + } + } + + // NOTE: DN_CPUID ///////////////////////////////////////////////////////////////////////////// + // Execute the 'CPUID' instruction which lets you query the capabilities of + // the current CPU. + + // NOTE: DN_DEFER + // + // A macro that expands to a C++ lambda that executes arbitrary code on + // scope exit. + { + int x = 0; + DN_DEFER + { + x = 3; + }; + x = 1; + // On scope exit, DN_DEFER object executes and assigns x = 3 + } + + // NOTE: DN_DSMap ///////////////////////////////////////////////////////////////////////////// + // + // A hash table configured using the presets recommended by Demitri Spanos + // from the Handmade Network (HMN), + // + // - power of two capacity + // - grow by 2x on load >= 75% + // - open-addressing with linear probing + // - separate large values (esp. variable length values) into a separate table + // - use a well-known hash function: MurmurHash3 (or xxhash, city, spooky ...) + // - chain-repair on delete (rehash items in the probe chain after delete) + // - shrink by 1/2 on load < 25% (suggested by Martins Mmozeiko of HMN) + // + // Source: discord.com/channels/239737791225790464/600063880533770251/941835678424129597 + // + // This hash-table stores slots (values) separate from the hash mapping. + // Hashes are mapped to slots using the hash-to-slot array which is an array + // of slot indexes. This array intentionally only stores indexes to maximise + // usage of the cache line. Linear probing on collision will only cost a + // couple of cycles to fetch from L1 cache the next slot index to attempt. + // + // The slots array stores values contiguously, non-sorted allowing iteration + // of the map. On element erase, the last element is swapped into the + // deleted element causing the non-sorted property of this table. + // + // The 0th slot (DN_DS_MAP_SENTINEL_SLOT) in the slots array is reserved + // for a sentinel value, e.g. all zeros value. After map initialisation the + // 'occupied' value of the array will be set to 1 to exclude the sentinel + // from the capacity of the table. Skip the first value if you are iterating + // the hash table! + // + // This hash-table accept either a U64 or a buffer (ptr + len) as the key. + // In practice this covers a majority of use cases (with string, buffer and + // number keys). It also allows us to minimise our C++ templates to only + // require 1 variable which is the Value part of the hash-table simplifying + // interface complexity and cruft brought by C++. + // + // Keys are value-copied into the hash-table. If the key uses a pointer to a + // buffer, this buffer must be valid throughout the lifetime of the hash + // table! + { + // NOTE: DN_DSMap_Init ////////////////////////////////////////////////////////////////// + // NOTE: DN_DSMap_Deinit ////////////////////////////////////////////////////////////////// + // + // Initialise a hash table where the table size *must* be a + // power-of-two, otherwise an assert will be triggered. If + // initialisation fails (e.g. memory allocation failure) the table is + // returned zero-initialised where a call to 'IsValid' will return + // false. + // + // The map takes ownership of the arena. This means in practice that if the + // map needs to resize (e.g. because the load threshold of the table is + // exceeded), the arena associated with it will be released and the memory + // will be reallocated with the larger capacity and reassigned to the arena. + // + // In simple terms, when the map resizes it invalidates all memory that was + // previously allocated with the given arena! + // + // A 'Deinit' of the map will similarly deallocate the passed in arena (as + // the map takes ownership of the arena). + DN_Arena arena = DN_Arena_InitFromOSVMem(0, 0, DN_ArenaFlags_Nil); + DN_DSMap map = DN_DSMap_Init(&arena, /*size*/ 1024, DN_DSMapFlags_Nil); // Size must be PoT! + DN_Assert(DN_DSMap_IsValid(&map)); // Valid if no initialisation failure (e.g. mem alloc failure) + + // NOTE: DN_DSMap_KeyCStringLit /////////////////////////////////////////////////////////// + // NOTE: DN_DSMap_KeyU64 /////////////////////////////////////////////////////////// + // NOTE: DN_DSMap_KeyU64NoHash /////////////////////////////////////////////////////////// + // NOTE: DN_DSMap_KeyBuffer /////////////////////////////////////////////////////////// + // NOTE: DN_DSMap_KeyStr8 /////////////////////////////////////////////////////////// + // NOTE: DN_DSMap_KeyStr8Copy /////////////////////////////////////////////////////////// + // Create a hash-table key where: + // + // KeyCStringLit: Uses a Hash(cstring literal) + // KeyU64: Uses a Hash(U64) + // KeyU64NoHash: Uses a U64 (where it's truncated to 4 bytes) + // KeyBuffer: Uses a Hash(ptr+len) slice of bytes + // KeyStr8: Uses a Hash(string) + // KeyStr8Copy: Uses a Hash(string) that is copied first using the arena + // + // Buffer-based keys memory must persist throughout lifetime of the map. + // Keys are valued copied into the map, alternatively, copy the + // key/buffer before constructing the key. + // + // You *can't* use the map's arena to allocate keys because on resize it + // will deallocate then reallocate the entire arena. + // + // KeyU64NoHash may be useful if you have a source of data that is + // already sufficiently uniformly distributed already (e.g. using 8 + // bytes taken from a SHA256 hash as the key) and the first 4 bytes + // will be used verbatim. + DN_DSMapKey key = DN_DSMap_KeyStr8(&map, DN_STR8("Sample Key")); + + // NOTE: DN_DSMap_Find //////////////////////////////////////////////////////////////////// + // NOTE: DN_DSMap_Make //////////////////////////////////////////////////////////////////// + // NOTE: DN_DSMap_Set //////////////////////////////////////////////////////////////////// + // + // Query or commit key-value pair to the table, where: + // + // Find: does a key-lookup on the table and returns the hash table slot's value + // Make: assigns the key to the table and returns the hash table slot's value + // Set: assigns the key-value to the table and returns the hash table slot's value + // + // A find query will set 'found' to false if it does not exist. + // + // For 'Make' and 'Set', 'found' can be set to 'true' if the item already + // existed in the map prior to the call. If it's the first time the + // key-value pair is being inserted 'found' will be set to 'false'. + // + // If by adding the key-value pair to the table puts the table over 75% load, + // the table will be grown to 2x the current the size before insertion + // completes. + { + DN_DSMapResult set_result = DN_DSMap_Set(&map, key, 0xCAFE); + DN_Assert(!set_result.found); // First time we are setting the key-value pair, it wasn't previously in the table + DN_Assert(map.occupied == 2); // Sentinel + new element == 2 + } + + // Iterating elements in the array, note that index '0' is the sentinel + // slot! You typically don't care about it! + for (DN_USize index = 1; index < map.occupied; index++) { + DN_DSMapSlot *it = map.slots + index; + DN_DSMapKey it_key = it->key; + int *it_value = &it->value; + DN_Assert(*it_value == 0xCAFE); + + DN_Assert(DN_Str8_Init(it_key.buffer_data, it_key.buffer_size) == DN_STR8("Sample Key")); + } + + // NOTE: DN_DSMap_Erase /////////////////////////////////////////////////////////////////// + // + // Remove the key-value pair from the table. If by erasing the key-value + // pair from the table puts the table under 25% load, the table will be + // shrunk by 1/2 the current size after erasing. The table will not shrink + // below the initial size that the table was initialised as. + { + bool erased = DN_DSMap_Erase(&map, key); + DN_Assert(erased); + DN_Assert(map.occupied == 1); // Sentinel element + } + + DN_DSMap_Deinit(&map, DN_ZeroMem_Yes); // Deallocates the 'arena' for us! + } + +// NOTE: DN_DSMap_Hash //////////////////////////////////////////////////////////////////////// +// +// Hash the input key using the custom hash function if it's set on the map, +// otherwise uses the default hashing function (32bit Murmur3). + +// NOTE: DN_DSMap_HashToSlotIndex ///////////////////////////////////////////////////////////// +// +// Calculate the index into the map's 'slots' array from the given hash. + +// NOTE: DN_DSMap_Resize ////////////////////////////////////////////////////////////////////// +// +// Resize the table and move all elements to the new map, note that the new +// size must be a power of two. This function wil fail on memory allocation +// failure, or the requested size is smaller than the current number of +// elements in the map to resize. + +// NOTE: DN_OSErrSink ///////////////////////////////////////////////////////////////////////// +// +// Error sinks are a way of accumulating errors from API calls related or +// unrelated into 1 unified error handling pattern. The implemenation of a +// sink requires 2 fundamental design constraints on the APIs supporting +// this pattern. +// +// 1. Pipelining of errors +// Errors emitted over the course of several API calls are accumulated +// into a sink which save the error code and message of the first error +// encountered and can be checked later. +// +// 2. Error proof APIs +// Functions that produce errors must return objects/handles that are +// marked to trigger no-ops used in subsequent functions dependent on it. +// +// Consider the following example demonstrating a conventional error +// handling approach (error values by return/sentinel values) and error +// handling using error-proof and pipelining. + +// (A) Conventional error checking patterns using return/sentinel values +#if 0 + DN_OSFile *file = DN_OS_FileOpen("/path/to/file", ...); + if (file) { + if (!DN_OS_FileWrite(file, "abc")) { + // Error handling! + } + Dnq_OS_FileClose(file); + } else { + // Error handling! + } +#endif + + // (B) Error handling using pipelining and and error proof APIs. APIs that + // produce errors take in the error sink as a parameter. + if (0) { + DN_OSErrSink *error = DN_OS_ErrSinkBegin(DN_OSErrSinkMode_Nil); + DN_OSFile file = DN_OS_FileOpen(DN_STR8("/path/to/file"), DN_OSFileOpen_OpenIfExist, DN_OSFileAccess_ReadWrite, error); + DN_OS_FileWrite(&file, DN_STR8("abc"), error); + DN_OS_FileClose(&file); + if (DN_OS_ErrSinkEndAndLogErrorF(error, "Failed to write to file")) { + // Do error handling! + } + } + + // Pipeling and error-proof APIs lets you write sequence of instructions and + // defer error checking until it is convenient or necessary. Functions are + // *guaranteed* to return an object that is usable. There are no hidden + // exceptions to be thrown. Functions may opt to still return error values + // by way of return values thereby *not* precluding the ability to check + // every API call either. + // + // Ultimately, this error handling approach gives more flexibility on the + // manner in how errors are handled with less code. + // + // Error sinks can nest begin and end statements. This will open a new scope + // whereby the current captured error pushed onto a stack and the sink will + // be populated by the first error encountered in that scope. + + if (0) { + DN_OSErrSink *error = DN_OS_ErrSinkBegin(DN_OSErrSinkMode_Nil); + DN_OSFile file = DN_OS_FileOpen(DN_STR8("/path/to/file"), DN_OSFileOpen_OpenIfExist, DN_OSFileAccess_ReadWrite, error); + DN_OS_FileWrite(&file, DN_STR8("abc"), error); + DN_OS_FileClose(&file); + + { + // NOTE: My error sinks are thread-local, so the returned 'error' is + // the same as the 'error' value above. + DN_OS_ErrSinkBegin(DN_OSErrSinkMode_Nil); + DN_OS_WriteAll(DN_STR8("/path/to/another/file"), DN_STR8("123"), error); + DN_OS_ErrSinkEndAndLogErrorF(error, "Failed to write to another file"); + } + + if (DN_OS_ErrSinkEndAndLogErrorF(error, "Failed to write to file")) { + // Do error handling! + } + } + + // NOTE: DN_FStr8_Max ///////////////////////////////////////////////////////////////////////// + // + // Return the maximum capacity of the string, e.g. the 'N' template + // parameter of FStr8 + + // NOTE: DN_FStr8_ToStr8 ////////////////////////////////////////////////////////////////////// + // + // Create a slice of the string into a pointer and length string (DN_Str8). + // The lifetime of the slice is bound to the lifetime of the FStr8 and is + // invalidated when the FStr8 is. + + // NOTE: DN_CVT_HexToBytes //////////////////////////////////////////////////////////////////////// + { + unsigned char bytes[2]; + DN_USize bytes_written = DN_CVT_HexToBytesPtr(DN_STR8("0xFACE"), bytes, sizeof(bytes)); + DN_Assert(bytes_written == 2); + DN_Assert(bytes[0] == 0xFA); + DN_Assert(bytes[1] == 0xCE); + } + + // NOTE: DN_JSONBuilder_Build ///////////////////////////////////////////////////////////////// + // + // Convert the internal JSON buffer in the builder into a string. + + // NOTE: DN_JSONBuilder_KeyValue, DN_JSONBuilder_KeyValueF + // + // Add a JSON key value pair untyped. The value is emitted directly without + // checking the contents of value. + // + // All other functions internally call into this function which is the main + // workhorse of the builder. + + // NOTE: DN_JSON_Builder_ObjectEnd + // + // End a JSON object in the builder, generates internally a '}' string + + // NOTE: DN_JSON_Builder_ArrayEnd + // + // End a JSON array in the builder, generates internally a ']' string + + // NOTE: DN_JSONBuilder_LiteralNamed + // + // Add a named JSON key-value object whose value is directly written to + // the following '"": ' (e.g. useful for emitting the 'null' + // value) + + // NOTE: DN_JSONBuilder_U64 ///////////////////////////////////////////////////////////// + // NOTE: DN_JSONBuilder_U64Named ///////////////////////////////////////////////////////////// + // NOTE: DN_JSONBuilder_I64 ///////////////////////////////////////////////////////////// + // NOTE: DN_JSONBuilder_I64Named ///////////////////////////////////////////////////////////// + // NOTE: DN_JSONBuilder_F64 ///////////////////////////////////////////////////////////// + // NOTE: DN_JSONBuilder_F64Named ///////////////////////////////////////////////////////////// + // NOTE: DN_JSONBuilder_Bool ///////////////////////////////////////////////////////////// + // NOTE: DN_JSONBuilder_BoolNamed ///////////////////////////////////////////////////////////// + // + // Add the named JSON data type as a key-value object. The named variants + // generates internally the key-value pair, e.g. + // + // "": + // + // And the non-named version emit just the 'value' portion + + // NOTE: DN_List_Iterate ////////////////////////////////////////////////////////////////////// + { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_List list = DN_List_Init(/*chunk_size*/ 128); + for (DN_ListIterator it = {}; DN_List_Iterate(&list, &it, 0);) { + int *item = it.data; + (void)item; + } + } + + // NOTE: DN_LOGProc /////////////////////////////////////////////////////////////////////////// + // + // Function prototype of the logging interface exposed by this library. Logs + // emitted using the DN_LOG_* family of functions are routed through this + // routine. + + // NOTE: DN_FNV1A ///////////////////////////////////////////////////////////////////////////// +#if 0 + { + // Using the default hash as defined by DN_FNV1A32_SEED and + // DN_FNV1A64_SEED for 32/64bit hashes respectively + uint32_t buffer1 = 0xCAFE0000; + uint32_t buffer2 = 0xDEAD0000; + { + uint64_t hash = DN_FNV1A64_Hash(&buffer1, sizeof(buffer1)); + hash = DN_FNV1A64_Iterate(&buffer2, sizeof(buffer2), hash); // Chained hashing + (void)hash; + } + + // You can use a custom seed by skipping the 'Hash' call and instead + // calling 'Iterate' immediately. + { + uint64_t custom_seed = 0xABCDEF12; + uint64_t hash = DN_FNV1A64_Iterate(&buffer1, sizeof(buffer1), custom_seed); + hash = DN_FNV1A64_Iterate(&buffer2, sizeof(buffer2), hash); + (void)hash; + } + } +#endif + + // NOTE: DN_FmtBuffer3DotTruncate ////////////////////////////////////////////////////////////// + { + char buffer[8] = {}; + int buffer_chars_written = DN_CVT_FmtBuffer3DotTruncate(buffer, sizeof(buffer), "This string is longer than %d characters", DN_CAST(int)(sizeof(buffer) - 1)); + if (0) // Prints "This ..." which is exactly 8 characters long + printf("%.*s", buffer_chars_written, buffer); + } + + // NOTE: DN_MurmurHash3 /////////////////////////////////////////////////////////////////////// + // MurmurHash3 was written by Austin Appleby, and is placed in the public + // domain. The author (Austin Appleby) hereby disclaims copyright to this source + // code. + // + // Note - The x86 and x64 versions do _not_ produce the same results, as the + // algorithms are optimized for their respective platforms. You can still + // compile and run any of them on any platform, but your performance with the + // non-native version will be less than optimal. + + // NOTE: DN_OS_DateUnixTime + // + // Produce the time elapsed since the unix epoch + { + uint64_t now = DN_OS_DateUnixTimeS(); + (void)now; + } + + // NOTE: DN_OS_DirIterate ///////////////////////////////////////////////////////////////////// + // + // Iterate the files within the passed in folder + for (DN_OSDirIterator it = {}; DN_OS_DirIterate(DN_STR8("."), &it);) { + // printf("%.*s\n", DN_STR_FMT(it.file_name)); + } + + // NOTE: DN_OS_FileDelete + // + // This function can only delete files and it can *only* delete directories + // if it is empty otherwise this function fails. + + // NOTE: DN_OS_WriteAllSafe + // Writes the file at the path first by appending '.tmp' to the 'path' to + // write to. If the temporary file is written successfully then the file is + // copied into 'path', for example: + // + // path: C:/Home/my.txt + // tmp_path: C:/Home/my.txt.tmp + // + // If 'tmp_path' is written to successfuly, the file will be copied over into + // 'path'. + if (0) { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_OSErrSink *error = DN_OS_ErrSinkBegin(DN_OSErrSinkMode_Nil); + DN_OS_WriteAllSafe(/*path*/ DN_STR8("C:/Home/my.txt"), /*buffer*/ DN_STR8("Hello world"), error); + DN_OS_ErrSinkEndAndLogErrorF(error, ""); + } + + // NOTE: DN_OS_EstimateTSCPerSecond /////////////////////////////////////////////////////////// + // + // Estimate how many timestamp count's (TSC) there are per second. TSC + // is evaluated by calling __rdtsc() or the equivalent on the platform. This + // value can be used to convert TSC durations into seconds. + // + // The 'duration_ms_to_gauge_tsc_frequency' parameter specifies how many + // milliseconds to spend measuring the TSC rate of the current machine. + // 100ms is sufficient to produce a fairly accurate result with minimal + // blocking in applications if calculated on startup.. + // + // This may return 0 if querying the CPU timestamp counter is not supported + // on the platform (e.g. __rdtsc() or __builtin_readcyclecounter() returns 0). + + // NOTE: DN_OS_EXEDir ///////////////////////////////////////////////////////////////////////// + // + // Retrieve the executable directory without the trailing '/' or ('\' for + // windows). If this fails an empty string is returned. + + // NOTE: DN_OS_PerfCounterFrequency /////////////////////////////////////////////////////////// + // + // Get the number of ticks in the performance counter per second for the + // operating system you're running on. This value can be used to calculate + // duration from OS performance counter ticks. + + // NOTE: DN_OS_Path* ////////////////////////////////////////////////////////////////////////// + // Construct paths ensuring the native OS path separators are used in the + // string. In 99% of cases you can use 'PathConvertF' which converts the + // given path in one shot ensuring native path separators in the string. + // + // path: C:\Home/My/Folder + // converted: C:/Home/My/Folder (On Unix) + // C:\Home\My\Folder (On Windows) + // + // If you need to construct a path dynamically you can use the builder-esque + // interface to build a path's step-by-step using the 'OSPath' data structure. + // With this API you can append paths piece-meal to build the path after all + // pieces are appended. + // + // You may append a singular or nested path to the builder. In the builder, + // the string is scanned and separated into path separated chunks and stored + // in the builder, e.g. these are all valid to pass into 'PathAdd', + // 'PathAddRef' ... e.t.c + // + // "path/to/your/desired/folder" is valid + // "path" is valid + // "path/to\your/desired\folder" is valid + // + // 'PathPop' removes the last appended path from the current path stored in + // the 'OSPath': + // + // path: path/to/your/desired/folder + // popped_path: path/to/your/desired + + // NOTE: DN_OS_SecureRNGBytes ///////////////////////////////////////////////////////////////// + // + // Generate cryptographically secure bytes + +#if 0 + // NOTE: DN_PCG32 ///////////////////////////////////////////////////////////////////////////// + // + // Random number generator of the PCG family. Implementation taken from + // Martins Mmozeiko from Handmade Network. + // https://gist.github.com/mmozeiko/1561361cd4105749f80bb0b9223e9db8 + { + DN_PCG32 rng = DN_PCG32_Init(0xb917'a66c'1d9b'3bd8); + + // NOTE: DN_PCG32_Range /////////////////////////////////////////////////////////////////// + // + // Generate a value in the [low, high) interval + uint32_t u32_value = DN_PCG32_Range(&rng, 32, 64); + DN_Assert(u32_value >= 32 && u32_value < 64); + + // NOTE: DN_PCG32_NextF32 ///////////////////////////////////////////////////////////////// + // NOTE: DN_PCG32_NextF64 ///////////////////////////////////////////////////////////////// + // + // Generate a float/double in the [0, 1) interval + DN_F64 f64_value = DN_PCG32_NextF64(&rng); + DN_Assert(f64_value >= 0.f && f64_value < 1.f); + + // NOTE: DN_PCG32_Advance ///////////////////////////////////////////////////////////////// + // + // Step the random number generator by 'delta' steps + DN_PCG32_Advance(&rng, /*delta*/ 5); + } +#endif + +#if 0 +#if !defined(DN_NO_PROFILER) + // NOTE: DN_Profiler ///////////////////////////////////////////////////////////////////////////// + // + // A profiler based off Casey Muratori's Computer Enhance course, Performance + // Aware Programming. This profiler measures function elapsed time using the + // CPU's time stamp counter (e.g. rdtsc) providing a rough cycle count + // that can be converted into a duration. + // + // This profiler uses a double buffer scheme for storing profiling markers. + // After an application's typical update/frame cycle you can swap the + // profiler's buffer whereby the front buffer contains the previous frames + // profiling metrics and the back buffer will be populated with the new + // frame's profiling metrics. + { + enum Zone + { + Zone_MainLoop, + Zone_Count + }; + + DN_ProfilerZone profiler_zone_main_update = DN_Profiler_BeginZone(Zone_MainLoop); + + // NOTE: DN_Profiler_AnchorBuffer ///////////////////////////////////////////////////// + // + // Retrieve the requested buffer from the profiler for + // writing/reading profiling metrics. Pass in the enum to specify + // which buffer to grab from the profiler. + // + // The front buffer contains the previous frame's profiling metrics + // and the back buffer is where the profiler is currently writing + // to. + // + // For end user intents and purposes, you likely only need to read + // the front buffer which contain the metrics that you can visualise + // regarding the most profiling metrics recorded. + + // NOTE: DN_Profiler_ReadBuffer /////////////////////////////////////////////////////////// + // + // Retrieve the buffer of anchors of which there are + // `DN_PROFILER_ANCHOR_BUFFER_SIZE` anchors from the most recent run + // of the profiler after you have called `SwapAnchorBuffer` to trigger + // the double buffer + DN_ProfilerAnchor *read_anchors = DN_Profiler_ReadBuffer(); + for (DN_USize index = 0; index < DN_PROFILER_ANCHOR_BUFFER_SIZE; index++) { + DN_ProfilerAnchor *anchor = read_anchors + index; + if (DN_Str8_HasData(anchor->name)) { + // ... + } + } + + // NOTE: DN_Profiler_WriteBuffer ////////////////////////////////////////////////////////// + // + // Same as `ReadBuffer` however we return the buffer that the profiler + // is currently writing anchors into. + DN_ProfilerAnchor *write_anchors = DN_Profiler_WriteBuffer(); + for (DN_USize index = 0; index < DN_PROFILER_ANCHOR_BUFFER_SIZE; index++) { + DN_ProfilerAnchor *anchor = write_anchors + index; + if (DN_Str8_HasData(anchor->name)) { + // ... + } + } + + DN_Profiler_EndZone(profiler_zone_main_update); + DN_Profiler_SwapAnchorBuffer(); // Should occur after all profiling zones are ended! + DN_Memset(g_dn_core->profiler, 0, sizeof(*g_dn_core->profiler)); + } +#endif // !defined(DN_NO_PROFILER) +#endif + + // NOTE: DN_Raycast_LineIntersectV2 /////////////////////////////////////////////////////////// + // Calculate the intersection point of 2 rays returning a `t` value + // which is how much along the direction of the 'ray' did the intersection + // occur. + // + // The arguments passed in do not need to be normalised for the function to + // work. + + // NOTE: DN_Safe_* //////////////////////////////////////////////////////////////////////////// + // + // Performs the arithmetic operation and uses DN_Check on the operation to + // check if it overflows. If it overflows the MAX value of the integer is + // returned in add and multiply operations, and, the minimum is returned in + // subtraction and division. + + // NOTE: DN_SaturateCast* //////////////////////////////////////////////////////////////// + // + // Truncate the passed in value to the return type clamping the resulting + // value to the max value of the desired data type. It DN_Check's the + // truncation. + // + // The following sentinel values are returned when saturated, + // USize -> Int: INT_MAX + // USize -> I8: INT8_MAX + // USize -> I16: INT16_MAX + // USize -> I32: INT32_MAX + // USize -> I64: INT64_MAX + // + // U64 -> UInt: UINT_MAX + // U64 -> U8: UINT8_MAX + // U64 -> U16: UINT16_MAX + // U64 -> U32: UINT32_MAX + // + // USize -> U8: UINT8_MAX + // USize -> U16: UINT16_MAX + // USize -> U32: UINT32_MAX + // USize -> U64: UINT64_MAX + // + // ISize -> Int: INT_MIN or INT_MAX + // ISize -> I8: INT8_MIN or INT8_MAX + // ISize -> I16: INT16_MIN or INT16_MAX + // ISize -> I32: INT32_MIN or INT32_MAX + // ISize -> I64: INT64_MIN or INT64_MAX + // + // ISize -> UInt: 0 or UINT_MAX + // ISize -> U8: 0 or UINT8_MAX + // ISize -> U16: 0 or UINT16_MAX + // ISize -> U32: 0 or UINT32_MAX + // ISize -> U64: 0 or UINT64_MAX + // + // I64 -> ISize: DN_ISIZE_MIN or DN_ISIZE_MAX + // I64 -> I8: INT8_MIN or INT8_MAX + // I64 -> I16: INT16_MIN or INT16_MAX + // I64 -> I32: INT32_MIN or INT32_MAX + // + // Int -> I8: INT8_MIN or INT8_MAX + // Int -> I16: INT16_MIN or INT16_MAX + // Int -> U8: 0 or UINT8_MAX + // Int -> U16: 0 or UINT16_MAX + // Int -> U32: 0 or UINT32_MAX + // Int -> U64: 0 or UINT64_MAX + + // NOTE: DN_StackTrace //////////////////////////////////////////////////////////////////////// + // Emit stack traces at the calling site that these functions are invoked + // from. + // + // For some applications, it may be viable to generate raw stack traces and + // store just the base addresses of the call stack from the 'Walk' + // functions. This reduces the memory overhead and required to hold onto + // stack traces and resolve the addresses on-demand when required. + // + // However if your application is loading and/or unloading shared libraries, + // on Windows it may be impossible for the application to resolve raw base + // addresses if they become invalid over time. In these applications you + // must convert the raw stack traces before the unloading occurs, and when + // loading new shared libraries, 'ReloadSymbols' must be called to ensure + // the debug APIs are aware of how to resolve the new addresses imported + // into the address space. + { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + + // NOTE: DN_StackTrace_Walk /////////////////////////////////////////////////////////////// + // + // Generate a stack trace as a series of addresses to the base of the + // functions on the call-stack at the current instruction pointer. The + // addresses are stored in order from the current executing function + // first to the most ancestor function last in the walk. + DN_StackTraceWalkResult walk = DN_StackTrace_Walk(tmem.arena, /*depth limit*/ 128); + + // Loop over the addresses produced in the stack trace + for (DN_StackTraceWalkResultIterator it = {}; DN_StackTrace_WalkResultIterate(&it, &walk);) { + // NOTE: DN_StackTrace_RawFrameToFrame //////////////////////////////////////////////// + // + // Converts the base address into a human readable stack trace + // entry (e.g. address, line number, file and function name). + DN_StackTraceFrame frame = DN_StackTrace_RawFrameToFrame(tmem.arena, it.raw_frame); + + // You may then print out the frame like so + if (0) + printf("%.*s(%" PRIu64 "): %.*s\n", DN_STR_FMT(frame.file_name), frame.line_number, DN_STR_FMT(frame.function_name)); + } + + // If you load new shared-libraries into the address space it maybe + // necessary to call into 'ReloadSymbols' to ensure that the OS is able + // to resolve the new addresses. + DN_StackTrace_ReloadSymbols(); + + // NOTE: DN_StackTrace_GetFrames ////////////////////////////////////////////////////////// + // + // Helper function to create a stack trace and automatically convert the + // raw frames into human readable frames. This function effectively + // calls 'Walk' followed by 'RawFrameToFrame'. + DN_Slice frames = DN_StackTrace_GetFrames(tmem.arena, /*depth limit*/ 128); + (void)frames; + } + + // NOTE: DN_Str8_Alloc //////////////////////////////////////////////////////////////////////// + // + // Allocates a string with the requested 'size'. An additional byte is + // always requested from the allocator to null-terminate the buffer. This + // allows the string to be used with C-style string APIs. + // + // The returned string's 'size' member variable does *not* include this + // additional null-terminating byte. + { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 string = DN_Str8_Alloc(tmem.arena, /*size*/ 1, DN_ZeroMem_Yes); + DN_Assert(string.size == 1); + DN_Assert(string.data[string.size] == 0); // It is null-terminated! + } + + // NOTE: DN_Str8_BinarySplit ////////////////////////////////////////////////////////////////// + // + // Splits a string into 2 substrings occuring prior and after the first + // occurence of the delimiter. Neither strings include the matched + // delimiter. If no delimiter is found, the 'rhs' of the split will be + // empty. + { + DN_Str8BinarySplitResult dot_split = DN_Str8_BinarySplit(/*string*/ DN_STR8("abc.def.ghi"), /*delimiter*/ DN_STR8(".")); + DN_Str8BinarySplitResult slash_split = DN_Str8_BinarySplit(/*string*/ DN_STR8("abc.def.ghi"), /*delimiter*/ DN_STR8("/")); + DN_Assert(dot_split.lhs == DN_STR8("abc") && dot_split.rhs == DN_STR8("def.ghi")); + DN_Assert(slash_split.lhs == DN_STR8("abc.def.ghi") && slash_split.rhs == DN_STR8("")); + + // Loop that walks the string and produces ("abc", "def", "ghi") + for (DN_Str8 it = DN_STR8("abc.def.ghi"); it.size;) { + DN_Str8BinarySplitResult split = DN_Str8_BinarySplit(it, DN_STR8(".")); + DN_Str8 chunk = split.lhs; // "abc", "def", ... + it = split.rhs; + (void)chunk; + } + } + + // NOTE: DN_Str8_FileNameFromPath ///////////////////////////////////////////////////////////// + // + // Takes a slice to the file name from a file path. The file name is + // evaluated by searching from the end of the string backwards to the first + // occurring path separator '/' or '\'. If no path separator is found, the + // original string is returned. This function preserves the file extension + // if there were any. + { + { + DN_Str8 string = DN_Str8_FileNameFromPath(DN_STR8("C:/Folder/item.txt")); + DN_Assert(string == DN_STR8("item.txt")); + } + { + // TODO(doyle): Intuitively this seems incorrect. Empty string instead? + DN_Str8 string = DN_Str8_FileNameFromPath(DN_STR8("C:/Folder/")); + DN_Assert(string == DN_STR8("C:/Folder")); + } + { + DN_Str8 string = DN_Str8_FileNameFromPath(DN_STR8("C:/Folder")); + DN_Assert(string == DN_STR8("Folder")); + } + } + + // NOTE: DN_Str8_FilePathNoExtension ////////////////////////////////////////////////////////// + // + // This function preserves the original string if no extension was found. + // An extension is defined as the substring after the last '.' encountered + // in the string. + { + DN_Str8 string = DN_Str8_FilePathNoExtension(DN_STR8("C:/Folder/item.txt.bak")); + DN_Assert(string == DN_STR8("C:/Folder/item.txt")); + } + + // NOTE: DN_Str8_FileNameNoExtension ////////////////////////////////////////////////////////// + // + // This function is the same as calling 'FileNameFromPath' followed by + // 'FilePathNoExtension' + { + DN_Str8 string = DN_Str8_FileNameNoExtension(DN_STR8("C:/Folder/item.txt.bak")); + DN_Assert(string == DN_STR8("item.txt")); + } + + // NOTE: DN_Str8_Replace /////////////////////////////////////////////////////////// + // NOTE: DN_Str8_ReplaceInsensitive /////////////////////////////////////////////////////////// + // + // Replace any matching substring 'find' with 'replace' in the passed in + // 'string'. The 'start_index' may be specified to offset which index the + // string will start doing replacements from. + // + // String replacements are not done inline and the returned string will + // always be a newly allocated copy, irrespective of if any replacements + // were done or not. + { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 string = DN_Str8_Replace(/*string*/ DN_STR8("Foo Foo Bar"), + /*find*/ DN_STR8("Foo"), + /*replace*/ DN_STR8("Moo"), + /*start_index*/ 1, + /*arena*/ tmem.arena, + /*eq_case*/ DN_Str8EqCase_Sensitive); + DN_Assert(string == DN_STR8("Foo Moo Bar")); + } + + // NOTE: DN_Str8_Segment ////////////////////////////////////////////////////////////////////// + // + // Add a delimiting 'segment_char' every 'segment_size' number of characters + // in the string. + // + // Reverse segment delimits the string counting 'segment_size' from the back + // of the string. + { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 string = DN_Str8_Segment(tmem.arena, /*string*/ DN_STR8("123456789"), /*segment_size*/ 3, /*segment_char*/ ','); + DN_Assert(string == DN_STR8("123,456,789")); + } + + // NOTE: DN_Str8_Split //////////////////////////////////////////////////////////////////////// + { + // Splits the string at each delimiter into substrings occuring prior and + // after until the next delimiter. + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + { + DN_Slice splits = DN_Str8_SplitAlloc(/*arena*/ tmem.arena, + /*string*/ DN_STR8("192.168.8.1"), + /*delimiter*/ DN_STR8("."), + /*mode*/ DN_Str8SplitIncludeEmptyStrings_No); + DN_Assert(splits.size == 4); + DN_Assert(splits.data[0] == DN_STR8("192") && splits.data[1] == DN_STR8("168") && splits.data[2] == DN_STR8("8") && splits.data[3] == DN_STR8("1")); + } + + // You can include empty strings that occur when splitting by setting + // the split mode to include empty strings. + { + DN_Slice splits = DN_Str8_SplitAlloc(/*arena*/ tmem.arena, + /*string*/ DN_STR8("a--b"), + /*delimiter*/ DN_STR8("-"), + /*mode*/ DN_Str8SplitIncludeEmptyStrings_Yes); + DN_Assert(splits.size == 3); + DN_Assert(splits.data[0] == DN_STR8("a") && splits.data[1] == DN_STR8("") && splits.data[2] == DN_STR8("b")); + } + } + + // NOTE: DN_Str8_ToI64 //////////////////////////////////////////////////////////////////////// + // NOTE: DN_Str8_ToU64 //////////////////////////////////////////////////////////////////////// + // + // Convert a number represented as a string to a signed 64 bit number. + // + // The 'separator' is an optional digit separator for example, if + // 'separator' is set to ',' then '1,234' will successfully be parsed to + // '1234'. If no separator is desired, you may pass in '0' in which + // '1,234' will *not* be succesfully parsed. + // + // Real numbers are truncated. Both '-' and '+' prefixed strings are permitted, + // i.e. "+1234" -> 1234 and "-1234" -> -1234. Strings must consist entirely of + // digits, the seperator or the permitted prefixes as previously mentioned + // otherwise this function will return false, i.e. "1234 dog" will cause the + // function to return false, however, the output is greedily converted and + // will be evaluated to "1234". + // + // 'ToU64' only '+' prefix is permitted + // 'ToI64' either '+' or '-' prefix is permitted + { + { + DN_Str8ToI64Result result = DN_Str8_ToI64(DN_STR8("-1,234"), /*separator*/ ','); + DN_Assert(result.success && result.value == -1234); + } + { + DN_Str8ToI64Result result = DN_Str8_ToI64(DN_STR8("-1,234"), /*separator*/ 0); + DN_Assert(!result.success && result.value == 1); // 1 because it's a greedy conversion + } + } + + // NOTE: DN_Str8_TrimByteOrderMark //////////////////////////////////////////////////////////// + // + // Removes a leading UTF8, UTF16 BE/LE, UTF32 BE/LE byte order mark from the + // string if it's present. + + // NOTE: DN_STR_FMT /////////////////////////////////////////////////////////////////////////// + // + // Unpacks a string struct that has the fields {.data, .size} for printing a + // pointer and length style string using the printf format specifier "%.*s" + // + // printf("%.*s\n", DN_STR_FMT(DN_STR8("Hello world"))); + + // NOTE: DN_Str8Builder_AppendF //////////////////////////////////////////////////////////// + // NOTE: DN_Str8Builder_AppendFV //////////////////////////////////////////////////////////// + // NOTE: DN_Str8Builder_AppendRef //////////////////////////////////////////////////////////// + // NOTE: DN_Str8Builder_AppendCopy //////////////////////////////////////////////////////////// + // + // - Appends a string to the string builder as follows + // + // AppendRef: Stores the string slice by value + // AppendCopy: Stores the string slice by copy (with builder's arena) + // AppendF/V: Constructs a format string and calls 'AppendRef' + + // NOTE: DN_Str8Builder_Build /////////////////////////////////////////////////////////// + // NOTE: DN_Str8Builder_BuildCRT /////////////////////////////////////////////////////////// + // + // Constructs the final string by merging all the appended strings into + // one merged string. + // + // The CRT variant calls into 'malloc' and the string *must* be released + // using 'free'. + + // NOTE: DN_Str8Builder_BuildSlice /////////////////////////////////////////////////////////// + // + // Constructs the final string into an array of strings (e.g. a slice) + + // NOTE: DN_TicketMutex /////////////////////////////////////////////////////////////////////// + // + // A mutex implemented using an atomic compare and swap on tickets handed + // out for each critical section. + // + // This mutex serves ticket in order and will block all other threads until + // the tickets are returned in order. The thread with the oldest ticket that + // has not been returned has right of way to execute, all other threads will + // be blocked in an atomic compare and swap loop. block execution by going + // into an atomic + // + // When a thread is blocked by this mutex, a spinlock intrinsic '_mm_pause' is + // used to yield the CPU and reduce spinlock on the thread. This mutex is not + // ideal for long blocking operations. This mutex does not issue any syscalls + // and relies entirely on atomic instructions. + { + DN_TicketMutex mutex = {}; + DN_TicketMutex_Begin(&mutex); // Simple procedural mutual exclusion lock + DN_TicketMutex_End(&mutex); + + // NOTE: DN_TicketMutex_MakeTicket //////////////////////////////////////////////////////// + // + // Request the next available ticket for locking from the mutex. + DN_UInt ticket = DN_TicketMutex_MakeTicket(&mutex); + + if (DN_TicketMutex_CanLock(&mutex, ticket)) { + // NOTE: DN_TicketMutex_BeginTicket /////////////////////////////////////////////////// + // + // Locks the mutex using the given ticket if possible. If it's not + // the next ticket to be locked the executing thread will block + // until the mutex can lock the ticket, i.e. All prior tickets are + // returned, in sequence, to the mutex. + DN_TicketMutex_BeginTicket(&mutex, ticket); + DN_TicketMutex_End(&mutex); + } + } + + // NOTE: DN_ThreadContext ///////////////////////////////////////////////////////////////////// + // + // Each thread is assigned in their thread-local storage (TLS) tmem and + // permanent arena allocators. These can be used for allocations with a + // lifetime scoped to the lexical scope or for storing data permanently + // using the arena paradigm. + // + // TLS in this implementation is implemented using the `thread_local` C/C++ + // keyword. + // + // 99% of the time you will want DN_OS_TLSTMem(...) which returns you a + // temporary arena for function lifetime allocations. On scope exit, the + // arena is cleared out. + // + // This library's paradigm revolves heavily around arenas including tmem + // arenas into child functions for temporary calculations. If an arena is + // passed into a function, this poses a problem sometimes known as + // 'arena aliasing'. + // + // If an arena aliases another arena (e.g. the arena passed in) is the same + // as the tmem arena requested in the function, we risk the tmem arena + // on scope exit deallocating memory belonging to the caller. + // + // To avoid this we the 'DN_OS_TLSTMem(...)' API takes in a list of arenas + // to ensure that we provide a tmem arena that *won't* alias with the + // caller's arena. If arena aliasing occurs, with ASAN on, generally + // the library will trap and report use-after-poison once violated. + { + DN_OSTLSTMem tmem_a = DN_OS_TLSTMem(nullptr); + + // Now imagine we call a function where we pass tmem_a.arena down + // into it .. If we call tmem again, we need to pass in the arena + // to prevent aliasing. + DN_OSTLSTMem tmem_b = DN_OS_TLSTMem(tmem_a.arena); + DN_Assert(tmem_a.arena != tmem_b.arena); + } + + // @proc DN_Thread_GetTMem + // @desc Retrieve the per-thread temporary arena allocator that is reset on scope + // exit. + + // The tmem arena must be deconflicted with any existing arenas in the + // function to avoid trampling over each other's memory. Consider the situation + // where the tmem arena is passed into the function. Inside the function, if + // the same arena is reused then, if both arenas allocate, when the inner arena + // is reset, this will undo the passed in arena's allocations in the function. + + // @param[in] conflict_arena A pointer to the arena currently being used in the + // function + + // NOTE: DN_CVT_U64ToStr8 ///////////////////////////////////////////////////////////////////////// + { + DN_CVTU64Str8 string = DN_CVT_U64ToStr8(123123, ','); + if (0) // Prints "123,123" + printf("%.*s", DN_STR_FMT(string)); + } + + // NOTE: DN_CVT_U64ToAge ////////////////////////////////////////////////////////////////////////// + { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 string = DN_CVT_U64ToAge(tmem.arena, DN_HoursToSec(2) + DN_MinutesToSec(30), DN_CVTU64AgeUnit_All); + DN_Assert(DN_Str8_Eq(string, DN_STR8("2h 30m"))); + } + + // NOTE: DN_VArray //////////////////////////////////////////////////////////////////////////// + // + // An array that is backed by virtual memory by reserving addressing space + // and comitting pages as items are allocated in the array. This array never + // reallocs, instead you should reserve the upper bound of the memory you + // will possibly ever need (e.g. 16GB) and let the array commit physical + // pages on demand. + // + // On 64 bit operating systems you are given 48 bits of addressable space + // giving you 256 TB of reservable memory. This gives you practically + // an unlimited array capacity that avoids reallocs and only consumes memory + // that is actually occupied by the array. + // + // Each page that is committed into the array will be at page/allocation + // granularity which are always cache aligned. This array essentially retains + // all the benefits of normal arrays, + // + // - contiguous memory + // - O(1) random access + // - O(N) iterate + // + // In addition to no realloc on expansion or shrinking. + // + { + // NOTE: DN_VArray_Init /////////////////////////////////////////////////////////// + // NOTE: DN_VArray_InitByteSize /////////////////////////////////////////////////////////// + // + // Initialise an array with the requested byte size or item capacity + // respectively. The returned array may have a higher capacity than the + // requested amount since requested memory from the OS may have a certain + // alignment requirement (e.g. on Windows reserve/commit are 64k/4k + // aligned). + DN_VArray array = DN_VArray_Init(1024); + DN_Assert(array.size == 0 && array.max >= 1024); + + // NOTE: DN_VArray_Make ////////////////////////////////////////////////////////////// + // NOTE: DN_VArray_Add ////////////////////////////////////////////////////////////// + // NOTE: DN_VArray_MakeArray ////////////////////////////////////////////////////////////// + // NOTE: DN_VArray_AddArray ////////////////////////////////////////////////////////////// + // + // Allocate items from the array where: + // + // Make: creates a zero-init item from the array + // Add: creates a zero-init item and memcpy passed in data into the item + // + // If the array has run out of capacity or was never initialised, a null + // pointer is returned. + int *item = DN_VArray_Add(&array, 0xCAFE); + DN_Assert(*item == 0xCAFE && array.size == 1); + + // NOTE: DN_VArray_AddCArray ///////////////////////////////////////////////////////////// + DN_VArray_AddCArray(&array, {1, 2, 3}); + DN_Assert(array.size == 4); + +// TODO(doyle): There's a bug here with the negative erase! +// Loop over the array items and erase 1 item. +#if 0 + for (DN_USize index = 0; index < array.size; index++) { + if (index != 1) + continue; + + // NOTE: DN_VArray_EraseRange ///////////////////////////////////////////////////////// + // + // Erase the next 'count' items at 'begin_index' in the array. + // 'count' can be positive or negative which dictates the if we + // erase forward from the 'begin_index' or in reverse. + // + // This operation will invalidate all pointers to the array! + // + // A stable erase will shift all elements after the erase ranged + // into the range preserving the order of prior elements. Unstable + // erase will move the tail elements into the range being erased. + // + // Erase range returns a result that contains the next iterator + // index that can be used to update the your for loop index if you + // are trying to iterate over the array. + + // TODO(doyle): There's a bug here! This doesn't work. + // Erase index 0 with the negative count! + DN_ArrayEraseResult erase_result = DN_VArray_EraseRange(&array, + /*begin_index*/ index, + /*count*/ -1, + /*erase*/ DN_ArrayErase_Stable); + DN_Assert(erase_result.items_erased == 1); + + // Use the index returned to continue linearly iterating the array + index = erase_result.it_index; + DN_Assert(array.data[index + 1] == 2); // Next loop iteration will process item '2' + } + + DN_Assert(array.size == 3 && + array.data[0] == 1 && + array.data[1] == 2 && + array.data[2] == 3); +#endif + + // NOTE: DN_VArray_Reserve //////////////////////////////////////////////////////////////////// + // + // Ensure that the requested number of items are backed by physical pages + // from the OS. Calling this pre-emptively will minimise syscalls into the + // kernel to request memory. The requested items will be rounded up to the + // in bytes to the allocation granularity of OS allocation APIs hence the + // reserved space may be greater than the requested amount (e.g. this is 4k + // on Windows). + DN_VArray_Reserve(&array, /*count*/ 8); + + DN_VArray_Deinit(&array); + } + + // NOTE: DN_W32_LastError ///////////////////////////////////////////////////////////// + // NOTE: DN_W32_ErrorCodeToMsg ///////////////////////////////////////////////////////////// + #if defined(DN_PLATFORM_WIN32) + if (0) { + // Generate the error string for the last Win32 API called that return + // an error value. + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_W32Error get_last_error = DN_W32_LastError(tmem.arena); + printf("Error (%lu): %.*s", get_last_error.code, DN_STR_FMT(get_last_error.msg)); + + // Alternatively, pass in the error code directly + DN_W32Error error_msg_for_code = DN_W32_ErrorCodeToMsg(tmem.arena, /*error_code*/ 0); + printf("Error (%lu): %.*s", error_msg_for_code.code, DN_STR_FMT(error_msg_for_code.msg)); + } + + // NOTE: DN_W32_MakeProcessDPIAware /////////////////////////////////////////////////////////// + // + // Call once at application start-up to ensure that the application is DPI + // aware on Windows and ensure that application UI is scaled up + // appropriately for the monitor. + + // NOTE: DN_W32_Str8ToStr16 ///////////////////////////////////////////////////////////// + // NOTE: DN_W32_Str8ToStr16Buffer ///////////////////////////////////////////////////////////// + // NOTE: DN_W32_Str16ToStr8 ///////////////////////////////////////////////////////////// + // NOTE: DN_W32_Str16ToStr8Buffer ///////////////////////////////////////////////////////////// + // + // Convert a UTF8 <-> UTF16 string. + // + // The exact size buffer required for this function can be determined by + // calling this function with the 'dest' set to null and 'dest_size' set to + // 0, the return size is the size required for conversion not-including + // space for the null-terminator. This function *always* null-terminates the + // input buffer. + // + // Returns the number of u8's (for UTF16->8) OR u16's (for UTF8->16) + // written/required for conversion. 0 if there was a conversion error and can be + // queried using 'DN_W32_LastError' + #endif +} + +DN_MSVC_WARNING_POP \ No newline at end of file diff --git a/Single_Header/dn_single_header.h b/Single_Header/dn_single_header.h new file mode 100644 index 0000000..f0d6e52 --- /dev/null +++ b/Single_Header/dn_single_header.h @@ -0,0 +1,6622 @@ +// Generated by the DN single header generator 2025-06-26 22:13:42 + +#if !defined(DN_BASE_INC_H) +#define DN_BASE_INC_H + +// NOTE: DN configuration +// All the following configuration options are optional. If omitted, DN has default behaviours to +// handle the various options. +// +// 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 as the +// target platform. +// +// DN_PLATFORM_EMSCRIPTEN +// DN_PLATFORM_POSIX +// DN_PLATFORM_WIN32 +// +// For example +// +// #define DN_PLATFORM_WIN32 +// +// Will ensure that is included and the OS layer is implemented using Win32 +// primitives. +// +// Static functions +// All public functions in the DN library are prefixed with the macro '#define DN_API'. By +// default 'DN_API' is not defined to anything. Define +// +// DN_STATIC_API +// +// To replace all the prefixed with 'static' ensuring that the functions in the library do not +// export an entry into the linking table and thereby optimise compilation times as the linker +// will not try to resolve symbols from other translation units from the the unit including the +// DN library. +// +// Using the in-built replacement header for (requires dn_os_inc.h) +// If you are building DN for the Windows platform, is a large legacy header that +// applications have to link to use Windows syscalls. By default DN library uses a replacement +// header for all the Windows functions that it uses in the OS layer removing the need to include +// to improve compilation times. This can be disabled by defining: +// +// DN_NO_WINDOWS_H_REPLACEMENT_HEADER +// +// To instead use . DN automatically detects if is included in an earlier +// translation unit and will automatically disable the in-built replacement header in which case +// this does not need to be defined. + +// DN: Single header generator inlined this file => #include "Base/dn_base_compiler.h" +#if !defined(DN_BASE_COMPILER_H) +#define DN_BASE_COMPILER_H + +// NOTE: Compiler identification /////////////////////////////////////////////////////////////////// +// 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 ///////////////////////////////////////////////////////////////////////////// +// 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 ///////////////////////////////////////////////////////////////////////////// +// 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_POP __pragma(warning(pop)) +#else + #define DN_MSVC_WARNING_PUSH + #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: Address sanitizer ///////////////////////////////////////////////////////////////////////// +#if !defined(DN_ASAN_POISON) + #define DN_ASAN_POISON 0 +#endif + +#if !defined(DN_ASAN_VET_POISON) + #define DN_ASAN_VET_POISON 0 +#endif + +#define DN_ASAN_POISON_ALIGNMENT 8 + +#if !defined(DN_ASAN_POISON_GUARD_SIZE) + #define DN_ASAN_POISON_GUARD_SIZE 128 +#endif + +#if DN_HAS_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__) + #include +#endif +#endif // !defined(DN_BASE_COMPILER_H) +// DN: Single header generator inlined this file => #include "Base/dn_base.h" +#if !defined(DN_BASE_H) +#define DN_BASE_H + +// NOTE: Macros //////////////////////////////////////////////////////////////////////////////////// +#define DN_Stringify(x) #x +#define DN_TokenCombine2(x, y) x ## y +#define DN_TokenCombine(x, y) DN_TokenCombine2(x, y) + +#include // va_list +#include +#include +#include +#include // PRIu64... + +#if !defined(DN_OS_WIN32) +#include // exit() +#endif + +#define DN_ForIndexU(index, size) DN_USize index = 0; index < size; index++ +#define DN_ForIndexI(index, size) DN_ISize index = 0; index < size; index++ +#define DN_ForItSize(it, T, array, size) struct { DN_USize index; T *data; } it = {0, &(array)[0]}; it.index < (size); it.index++, it.data++ +#define DN_ForIt(it, T, array) struct { DN_USize index; T *data; } it = {0, &(array)->data[0]}; it.index < (array)->size; it.index++, it.data++ +#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++ + +#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 +#endif + +// NOTE: Math ////////////////////////////////////////////////////////////////////////////////////// +#define DN_PiF32 3.14159265359f + +#define DN_DegreesToRadians(degrees) ((degrees) * (DN_PI / 180.0f)) +#define DN_RadiansToDegrees(radians) ((radians) * (180.f * DN_PI)) + +#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_SecondsToMs(val) ((val) * 1000) +#define DN_MinutesToSec(val) ((val) * 60ULL) +#define DN_HoursToSec(val) (DN_MinutesToSec(val) * 60ULL) +#define DN_DaysToSec(val) (DN_HoursToSec(val) * 24ULL) +#define DN_WeeksToSec(val) (DN_DaysToSec(val) * 7ULL) +#define DN_YearsToSec(val) (DN_WeeksToSec(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: Byte swaps //////////////////////////////////////////////////////////////////////////////// +#define DN_ByteSwap64(u64) (((((u64) >> 56) & 0xFF) << 0) | \ + ((((u64) >> 48) & 0xFF) << 8) | \ + ((((u64) >> 40) & 0xFF) << 16) | \ + ((((u64) >> 32) & 0xFF) << 24) | \ + ((((u64) >> 24) & 0xFF) << 32) | \ + ((((u64) >> 16) & 0xFF) << 40) | \ + ((((u64) >> 8) & 0xFF) << 48) | \ + ((((u64) >> 0) & 0xFF) << 56)) + +// NOTE: Defer Macro /////////////////////////////////////////////////////////////////////////////// +#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 + +// 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 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 + +enum DN_ZeroMem +{ + DN_ZeroMem_No, // Memory can be handed out without zero-ing it out + DN_ZeroMem_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 + + char const *begin() const { return data; } + char const *end() const { return data + size; } + char *begin() { return data; } + char *end() { return data + 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 + + #if defined(__cplusplus) + wchar_t const *begin() const { return data; } // Const begin iterator for range-for loops + wchar_t const *end() const { return data + size; } // Const end iterator for range-for loops + wchar_t *begin() { return data; } // Begin iterator for range-for loops + wchar_t *end() { return data + size; } // End iterator for range-for loops + #endif +}; + +template +struct DN_Slice // A pointer and length container of data +{ + T *data; + DN_USize size; + + T *begin() { return data; } + T *end() { return data + size; } + T const *begin() const { return data; } + T const *end() const { return data + size; } +}; + +// NOTE: DN_CallSite /////////////////////////////////////////////////////////////////////////////// +struct DN_CallSite +{ + DN_Str8 file; + DN_Str8 function; + DN_U32 line; +}; + +#define DN_CALL_SITE \ + DN_CallSite \ + { \ + DN_STR8(__FILE__), DN_STR8(__func__), __LINE__ \ + } + +// NOTE: Intrinsics //////////////////////////////////////////////////////////////////////////////// +// NOTE: DN_Atomic_Add/Exchange return the previous value store in the target +#if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL) + #include + #define DN_Atomic_CompareExchange64(dest, desired_val, prev_val) _InterlockedCompareExchange64((__int64 volatile *)dest, desired_val, prev_val) + #define DN_Atomic_CompareExchange32(dest, desired_val, prev_val) _InterlockedCompareExchange((long volatile *)dest, desired_val, prev_val) + + #define DN_Atomic_LoadU64(target) *(target) + #define DN_Atomic_LoadU32(target) *(target) + #define DN_Atomic_AddU32(target, value) _InterlockedExchangeAdd((long volatile *)target, value) + #define DN_Atomic_AddU64(target, value) _InterlockedExchangeAdd64((__int64 volatile *)target, value) + #define DN_Atomic_SubU32(target, value) DN_Atomic_AddU32(DN_CAST(long volatile *) target, (long)-value) + #define DN_Atomic_SubU64(target, value) DN_Atomic_AddU64(target, (DN_U64) - value) + + #define DN_CountLeadingZerosU64(value) __lzcnt64(value) + #define DN_CPU_TSC() __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) + #include + #else + #include + #endif + + #define DN_Atomic_LoadU64(target) __atomic_load_n(x, __ATOMIC_SEQ_CST) + #define DN_Atomic_LoadU32(target) __atomic_load_n(x, __ATOMIC_SEQ_CST) + #define DN_Atomic_AddU32(target, value) __atomic_fetch_add(target, value, __ATOMIC_ACQ_REL) + #define DN_Atomic_AddU64(target, value) __atomic_fetch_add(target, value, __ATOMIC_ACQ_REL) + #define DN_Atomic_SubU32(target, value) __atomic_fetch_sub(target, value, __ATOMIC_ACQ_REL) + #define DN_Atomic_SubU64(target, value) __atomic_fetch_sub(target, value, __ATOMIC_ACQ_REL) + + #define DN_CountLeadingZerosU64(value) __builtin_clzll(value) + #if defined(DN_COMPILER_GCC) + #define DN_CPU_TSC() __rdtsc() + #else + #define DN_CPU_TSC() __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_PLATFORM_ARM64) +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]; +}; + +extern DN_CPUFeatureDecl g_dn_cpu_feature_decl[DN_CPUFeature_Count]; +#endif // DN_PLATFORM_ARM64 + +// NOTE: DN_TicketMutex //////////////////////////////////////////////////////////////////////////// +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 +}; + +// NOTE: Intrinsics //////////////////////////////////////////////////////////////////////////////// +DN_FORCE_INLINE DN_U64 DN_Atomic_SetValue64 (DN_U64 volatile *target, DN_U64 value); +DN_FORCE_INLINE DN_U32 DN_Atomic_SetValue32 (DN_U32 volatile *target, DN_U32 value); + +#if !defined (DN_PLATFORM_ARM64) +DN_API DN_CPUIDResult DN_CPU_ID (DN_CPUIDArgs args); +DN_API DN_USize DN_CPU_HasFeatureArray (DN_CPUReport const *report, DN_CPUFeatureQuery *features, DN_USize features_size); +DN_API bool DN_CPU_HasFeature (DN_CPUReport const *report, DN_CPUFeature feature); +DN_API bool DN_CPU_HasAllFeatures (DN_CPUReport const *report, DN_CPUFeature const *features, DN_USize features_size); +template +bool DN_CPU_HasAllFeaturesCArray(DN_CPUReport const *report, DN_CPUFeature const (&features)[N]); +DN_API void DN_CPU_SetFeature (DN_CPUReport *report, DN_CPUFeature feature); +DN_API DN_CPUReport DN_CPU_Report (); +#endif + +// NOTE: DN_TicketMutex //////////////////////////////////////////////////////////////////////////// +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); + +// NOTE: DN_DLList ///////////////////////////////////////////////////////////////////////////////// +#define DN_DLList_Init(list) \ + (list)->next = (list)->prev = (list) + +#define DN_DLList_IsSentinel(list, item) ((list) == (item)) + +#define DN_DLList_InitArena(list, T, arena) \ + do { \ + (list) = DN_Arena_New(arena, T, DN_ZeroMem_Yes); \ + DN_DLList_Init(list); \ + } while (0) + +#define DN_DLList_InitPool(list, T, pool) \ + do { \ + (list) = DN_Pool_New(pool, T); \ + DN_DLList_Init(list); \ + } while (0) + +#define DN_DLList_Detach(item) \ + do { \ + if (item) { \ + (item)->prev->next = (item)->next; \ + (item)->next->prev = (item)->prev; \ + (item)->next = nullptr; \ + (item)->prev = nullptr; \ + } \ + } while (0) + +#define DN_DLList_Dequeue(list, dest_ptr) \ + if (DN_DLList_HasItems(list)) { \ + dest_ptr = (list)->next; \ + DN_DLList_Detach(dest_ptr); \ + } + +#define DN_DLList_Append(list, item) \ + do { \ + if (item) { \ + if ((item)->next) \ + DN_DLList_Detach(item); \ + (item)->next = (list)->next; \ + (item)->prev = (list); \ + (item)->next->prev = (item); \ + (item)->prev->next = (item); \ + } \ + } while (0) + +#define DN_DLList_Prepend(list, item) \ + do { \ + if (item) { \ + if ((item)->next) \ + DN_DLList_Detach(item); \ + (item)->next = (list); \ + (item)->prev = (list)->prev; \ + (item)->next->prev = (item); \ + (item)->prev->next = (item); \ + } \ + } while (0) + +#define DN_DLList_IsEmpty(list) \ + (!(list) || ((list) == (list)->next)) + +#define DN_DLList_IsInit(list) \ + ((list)->next && (list)->prev) + +#define DN_DLList_HasItems(list) \ + ((list) && ((list) != (list)->next)) + +#define DN_DLList_ForEach(it, list) \ + auto *it = (list)->next; (it) != (list); (it) = (it)->next + +// NOTE: Intrinsics //////////////////////////////////////////////////////////////////////////////// +DN_FORCE_INLINE DN_U64 DN_Atomic_SetValue64(DN_U64 volatile *target, DN_U64 value) +{ +#if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL) + __int64 result; + do { + result = *target; + } while (DN_Atomic_CompareExchange64(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_FORCE_INLINE DN_U32 DN_Atomic_SetValue32(DN_U32 volatile *target, DN_U32 value) +{ +#if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL) + long result; + do { + result = *target; + } while (DN_Atomic_CompareExchange32(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 +} + +template +bool DN_CPU_HasAllFeaturesCArray(DN_CPUReport const *report, DN_CPUFeature const (&features)[N]) +{ + bool result = DN_CPU_HasAllFeatures(report, features, N); + return result; +} + +// NOTE: DN_Bit //////////////////////////////////////////////////////////////////////////////////// +DN_API void DN_Bit_UnsetInplace (DN_USize *flags, DN_USize bitfield); +DN_API void DN_Bit_SetInplace (DN_USize *flags, DN_USize bitfield); +DN_API bool DN_Bit_IsSet (DN_USize bits, DN_USize bits_to_set); +DN_API bool DN_Bit_IsNotSet (DN_USize bits, DN_USize bits_to_check); +#define DN_Bit_ClearNextLSB(value) (value) & ((value) - 1) + +// NOTE: DN_Safe /////////////////////////////////////////////////////////////////////////////////// +DN_API DN_I64 DN_Safe_AddI64 (DN_I64 a, DN_I64 b); +DN_API DN_I64 DN_Safe_MulI64 (DN_I64 a, DN_I64 b); + +DN_API DN_U64 DN_Safe_AddU64 (DN_U64 a, DN_U64 b); +DN_API DN_U64 DN_Safe_MulU64 (DN_U64 a, DN_U64 b); + +DN_API DN_U64 DN_Safe_SubU64 (DN_U64 a, DN_U64 b); +DN_API DN_U32 DN_Safe_SubU32 (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_ISize 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); + +// NOTE: DN_Asan /////////////////////////////////////////////////////////////////////////////////// +DN_API void DN_ASAN_PoisonMemoryRegion (void const volatile *ptr, DN_USize size); +DN_API void DN_ASAN_UnpoisonMemoryRegion (void const volatile *ptr, DN_USize size); +#endif // !defined(DN_BASE_H) +// DN: Single header generator inlined this file => #include "Base/dn_base_os.h" +#if !defined(DN_BASE_OS_H) +#define DN_BASE_OS_H + +// NOTE: OS primitives that the OS layer can provide for the base layer but is optional. + +struct DN_StackTraceFrame +{ + DN_U64 address; + DN_U64 line_number; + DN_Str8 file_name; + DN_Str8 function_name; +}; + +struct DN_StackTraceRawFrame +{ + void *process; + DN_U64 base_addr; +}; + +struct DN_StackTraceWalkResult +{ + 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_StackTraceWalkResultIterator +{ + DN_StackTraceRawFrame raw_frame; + DN_U16 index; +}; + +DN_API DN_Str8 DN_StackTrace_WalkStr8FromHeap (DN_U16 limit, DN_U16 skip); +DN_API DN_StackTraceWalkResult DN_StackTrace_Walk (struct DN_Arena *arena, DN_U16 limit); +DN_API bool DN_StackTrace_WalkResultIterate(DN_StackTraceWalkResultIterator *it, DN_StackTraceWalkResult const *walk); +DN_API DN_Str8 DN_StackTrace_WalkResultToStr8 (struct DN_Arena *arena, DN_StackTraceWalkResult const *walk, DN_U16 skip); +DN_API DN_Str8 DN_StackTrace_WalkStr8 (struct DN_Arena *arena, DN_U16 limit, DN_U16 skip); +DN_API DN_Str8 DN_StackTrace_WalkStr8FromHeap (DN_U16 limit, DN_U16 skip); +DN_API DN_Slice DN_StackTrace_GetFrames (struct DN_Arena *arena, DN_U16 limit); +DN_API DN_StackTraceFrame DN_StackTrace_RawFrameToFrame (struct DN_Arena *arena, DN_StackTraceRawFrame raw_frame); +DN_API void DN_StackTrace_Print (DN_U16 limit); +DN_API void DN_StackTrace_ReloadSymbols (); + + +#endif + +// DN: Single header generator inlined this file => #include "Base/dn_base_assert.h" +#if !defined(DN_BASE_ASSERT_H) +#define DN_BASE_ASSERT_H + +#define DN_HardAssert(expr) DN_HardAssertF(expr, "") + +#if defined(DN_FREESTANDING) + #define DN_HardAssertF(expr, fmt, ...) \ + do { \ + if (!(expr)) { \ + DN_DebugBreak; \ + (*(int *)0) = 0; \ + } \ + } while (0) +#else + #define DN_HardAssertF(expr, fmt, ...) \ + do { \ + if (!(expr)) { \ + DN_Str8 stack_trace_ = DN_StackTrace_WalkStr8FromHeap(128 /*limit*/, 2 /*skip*/); \ + DN_LOG_ErrorF("Hard assertion [" #expr "], stack trace was:\n\n%.*s\n\n" fmt, \ + DN_STR_FMT(stack_trace_), \ + ##__VA_ARGS__); \ + DN_DebugBreak; \ + } \ + } while (0) +#endif + +#if defined(DN_NO_ASSERT) + #define DN_Assert(...) + #define DN_AssertOnce(...) + #define DN_AssertF(...) + #define DN_AssertFOnce(...) +#else + #if defined(DN_FREESTANDING) + #define DN_AssertF(expr, fmt, ...) \ + do { \ + if (!(expr)) { \ + DN_DebugBreak; \ + (*(int *)0) = 0; \ + } \ + } while (0) + + #else + #define DN_AssertF(expr, fmt, ...) \ + do { \ + if (!(expr)) { \ + DN_Str8 stack_trace_ = DN_StackTrace_WalkStr8FromHeap(128 /*limit*/, 2 /*skip*/); \ + DN_LOG_ErrorF("Assertion [" #expr "], stack trace was:\n\n%.*s\n\n" fmt, \ + DN_STR_FMT(stack_trace_), \ + ##__VA_ARGS__); \ + DN_DebugBreak; \ + } \ + } while (0) + #endif + + #define DN_AssertFOnce(expr, fmt, ...) \ + do { \ + static bool once = true; \ + if (!(expr) && once) { \ + once = false; \ + DN_Str8 stack_trace_ = DN_StackTrace_WalkStr8FromHeap(128 /*limit*/, 2 /*skip*/); \ + DN_LOG_ErrorF("Assertion [" #expr "], stack trace was:\n\n%.*s\n\n" fmt, \ + DN_STR_FMT(stack_trace_), \ + ##__VA_ARGS__); \ + DN_DebugBreak; \ + } \ + } while (0) + + #define DN_Assert(expr) DN_AssertF((expr), "") + #define DN_AssertOnce(expr) DN_AssertFOnce((expr), "") +#endif + +#define DN_InvalidCodePathF(fmt, ...) DN_HardAssertF(0, fmt, ##__VA_ARGS__) +#define DN_InvalidCodePath DN_InvalidCodePathF("Invalid code path triggered") + +// NOTE: Check macro /////////////////////////////////////////////////////////////////////////////// +#define DN_Check(expr) DN_CheckF(expr, "") + +#if defined(DN_FREESTANDING) + #define DN_CheckF(expr, fmt, ...) (expr) +#else + #if defined(DN_NO_CHECK_BREAK) + #define DN_CheckF(expr, fmt, ...) \ + ((expr) ? true : (DN_LOG_WarningF(fmt, ##__VA_ARGS__), false)) + #else + #define DN_CheckF(expr, fmt, ...) \ + ((expr) ? true : (DN_LOG_ErrorF(fmt, ##__VA_ARGS__), DN_StackTrace_Print(128 /*limit*/), DN_DebugBreak, false)) + #endif +#endif + +#endif +// DN: Single header generator inlined this file => #include "Base/dn_base_mem.h" +#if !defined(DN_BASE_MEM_H) +#define DN_BASE_MEM_H + +// DN: Single header generator commented out this header => #include "../dn_clangd.h" + +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 + +struct DN_ArenaBlock +{ + DN_ArenaBlock *prev; + DN_U64 used; + DN_U64 commit; + DN_U64 reserve; + DN_U64 reserve_sum; +}; + +typedef uint32_t DN_ArenaFlags; + +enum DN_ArenaFlags_ +{ + DN_ArenaFlags_Nil = 0, + DN_ArenaFlags_NoGrow = 1 << 0, + DN_ArenaFlags_NoPoison = 1 << 1, + DN_ArenaFlags_NoAllocTrack = 1 << 2, + DN_ArenaFlags_AllocCanLeak = 1 << 3, + + // NOTE: Internal flags. Do not use + DN_ArenaFlags_UserBuffer = 1 << 4, + DN_ArenaFlags_MemFuncs = 1 << 5, +}; + +struct DN_ArenaInfo +{ + DN_U64 used; + DN_U64 commit; + DN_U64 reserve; + DN_U64 blocks; +}; + +struct DN_ArenaStats +{ + DN_ArenaInfo info; + DN_ArenaInfo hwm; +}; + +enum DN_ArenaMemFuncType +{ + DN_ArenaMemFuncType_Nil, + DN_ArenaMemFuncType_Basic, + DN_ArenaMemFuncType_VMem, +}; + +typedef void *(DN_ArenaMemBasicAllocFunc)(DN_USize size); +typedef void (DN_ArenaMemBasicDeallocFunc)(void *ptr); +typedef void *(DN_ArenaMemVMemReserveFunc)(DN_USize size, DN_MemCommit commit, DN_MemPage page_flags); +typedef bool (DN_ArenaMemVMemCommitFunc)(void *ptr, DN_USize size, DN_U32 page_flags); +typedef void (DN_ArenaMemVMemReleaseFunc)(void *ptr, DN_USize size); +struct DN_ArenaMemFuncs +{ + DN_ArenaMemFuncType type; + DN_ArenaMemBasicAllocFunc *basic_alloc; + DN_ArenaMemBasicDeallocFunc *basic_dealloc; + + DN_U32 vmem_page_size; + DN_ArenaMemVMemReserveFunc *vmem_reserve; + DN_ArenaMemVMemCommitFunc *vmem_commit; + DN_ArenaMemVMemReleaseFunc *vmem_release; +}; + +struct DN_Arena +{ + DN_ArenaMemFuncs mem_funcs; + DN_ArenaBlock *curr; + DN_ArenaStats stats; + DN_ArenaFlags flags; + DN_Str8 label; + DN_Arena *prev, *next; +}; + +struct DN_ArenaTempMem +{ + DN_Arena *arena; + DN_U64 used_sum; +}; + +struct DN_ArenaTempMemScope +{ + DN_ArenaTempMemScope(DN_Arena *arena); + ~DN_ArenaTempMemScope(); + DN_ArenaTempMem mem; +}; + +DN_USize const DN_ARENA_HEADER_SIZE = DN_AlignUpPowerOfTwo(sizeof(DN_Arena), 64); + +// NOTE: DN_Arena ////////////////////////////////////////////////////////////////////////////////// +DN_API DN_Arena DN_Arena_InitFromBuffer (void *buffer, DN_USize size, DN_ArenaFlags flags); +DN_API DN_Arena DN_Arena_InitFromMemFuncs (DN_U64 reserve, DN_U64 commit, DN_ArenaFlags flags, DN_ArenaMemFuncs mem_funcs); +DN_API void DN_Arena_Deinit (DN_Arena *arena); +DN_API bool DN_Arena_Commit (DN_Arena *arena, DN_U64 size); +DN_API bool DN_Arena_CommitTo (DN_Arena *arena, DN_U64 pos); +DN_API bool DN_Arena_Grow (DN_Arena *arena, DN_U64 reserve, DN_U64 commit); +DN_API void * DN_Arena_Alloc (DN_Arena *arena, DN_U64 size, uint8_t align, DN_ZeroMem zero_mem); +DN_API void * DN_Arena_AllocContiguous (DN_Arena *arena, DN_U64 size, uint8_t align, DN_ZeroMem zero_mem); +DN_API void * DN_Arena_Copy (DN_Arena *arena, void const *data, DN_U64 size, uint8_t align); +DN_API void DN_Arena_PopTo (DN_Arena *arena, DN_U64 init_used); +DN_API void DN_Arena_Pop (DN_Arena *arena, DN_U64 amount); +DN_API DN_U64 DN_Arena_Pos (DN_Arena const *arena); +DN_API void DN_Arena_Clear (DN_Arena *arena); +DN_API bool DN_Arena_OwnsPtr (DN_Arena const *arena, void *ptr); +DN_API DN_ArenaStats DN_Arena_SumStatsArray (DN_ArenaStats const *array, DN_USize size); +DN_API DN_ArenaStats DN_Arena_SumStats (DN_ArenaStats lhs, DN_ArenaStats rhs); +DN_API DN_ArenaStats DN_Arena_SumArenaArrayToStats (DN_Arena const *array, DN_USize size); +DN_API DN_ArenaTempMem DN_Arena_TempMemBegin (DN_Arena *arena); +DN_API void DN_Arena_TempMemEnd (DN_ArenaTempMem mem); +#define DN_Arena_New_Frame(T, zero_mem) (T *)DN_Arena_Alloc(DN_OS_TLSGet()->frame_arena, sizeof(T), alignof(T), zero_mem) +#define DN_Arena_New(arena, T, zero_mem) (T *)DN_Arena_Alloc(arena, sizeof(T), alignof(T), zero_mem) +#define DN_Arena_NewArray(arena, T, count, zero_mem) (T *)DN_Arena_Alloc(arena, sizeof(T) * (count), alignof(T), zero_mem) +#define DN_Arena_NewCopy(arena, T, src) (T *)DN_Arena_Copy (arena, (src), sizeof(T), alignof(T)) +#define DN_Arena_NewArrayCopy(arena, T, src, count) (T *)DN_Arena_Copy (arena, (src), sizeof(T) * (count), alignof(T)) + +#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]; + uint8_t align; +}; + +// NOTE: DN_Pool /////////////////////////////////////////////////////////////////////////////////// +DN_API DN_Pool DN_Pool_Init (DN_Arena *arena, uint8_t align); +DN_API bool DN_Pool_IsValid (DN_Pool const *pool); +DN_API void * DN_Pool_Alloc (DN_Pool *pool, DN_USize size); +DN_API DN_Str8 DN_Pool_AllocStr8FV (DN_Pool *pool, DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API DN_Str8 DN_Pool_AllocStr8F (DN_Pool *pool, DN_FMT_ATTRIB char const *fmt, ...); +DN_API DN_Str8 DN_Pool_AllocStr8Copy (DN_Pool *pool, DN_Str8 string); +DN_API void DN_Pool_Dealloc (DN_Pool *pool, void *ptr); +DN_API void * DN_Pool_Copy (DN_Pool *pool, void const *data, DN_U64 size, uint8_t align); + +#define DN_Pool_New(pool, T) (T *)DN_Pool_Alloc(pool, sizeof(T)) +#define DN_Pool_NewArray(pool, T, count) (T *)DN_Pool_Alloc(pool, count * sizeof(T)) +#define DN_Pool_NewCopy(arena, T, src) (T *)DN_Pool_Copy (arena, (src), sizeof(T), alignof(T)) +#define DN_Pool_NewArrayCopy(arena, T, src, count) (T *)DN_Pool_Copy (arena, (src), sizeof(T) * (count), alignof(T)) + +// NOTE: DN_Debug ////////////////////////////////////////////////////////////////////////////////// +#if defined(DN_LEAK_TRACKING) && !defined(DN_FREESTANDING) +DN_API void DN_Debug_TrackAlloc (void *ptr, DN_USize size, bool alloc_can_leak); +DN_API void DN_Debug_TrackDealloc(void *ptr); +DN_API void DN_Debug_DumpLeaks (); +#else +#define DN_Debug_TrackAlloc(ptr, size, alloc_can_leak) do { (void)ptr; (void)size; (void)alloc_can_leak; } while (0) +#define DN_Debug_TrackDealloc(ptr) do { (void)ptr; } while (0) +#define DN_Debug_DumpLeaks() do { } while (0) +#endif +#endif // !defined(DN_BASE_MEM_H) +// DN: Single header generator inlined this file => #include "Base/dn_base_log.h" +#if !defined(DN_BASE_LOG_H) +#define DN_BASE_LOG_H + +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_LOGColourType +{ + DN_LOGColourType_Fg, + DN_LOGColourType_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 void DN_LOGEmitFromTypeFVFunc(DN_LOGTypeParam type, void *user_data, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, va_list args); + +#define DN_LOG_ResetEscapeCode "\x1b[0m" +#define DN_LOG_BoldEscapeCode "\x1b[1m" +DN_API DN_Str8 DN_LOG_ColourEscapeCodeStr8FromRGB(DN_LOGColourType colour, DN_U8 r, DN_U8 g, DN_U8 b); +DN_API DN_Str8 DN_LOG_ColourEscapeCodeStr8FromU32(DN_LOGColourType colour, DN_U32 value); +DN_API DN_LOGPrefixSize DN_LOG_MakePrefix (DN_LOGStyle style, DN_LOGTypeParam type, DN_CallSite call_site, DN_LOGDate date, char *dest, DN_USize dest_size); +DN_API void DN_LOG_SetEmitFromTypeFVFunc (DN_LOGEmitFromTypeFVFunc *print_func, void *user_data); +DN_API void DN_LOG_EmitFromType (DN_LOGTypeParam type, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, ...); +DN_API DN_LOGTypeParam DN_LOG_MakeU32LogTypeParam (DN_LOGType type); +#define DN_LOG_DebugF(fmt, ...) DN_LOG_EmitFromType(DN_LOG_MakeU32LogTypeParam(DN_LOGType_Debug), DN_CALL_SITE, fmt, ##__VA_ARGS__) +#define DN_LOG_InfoF(fmt, ...) DN_LOG_EmitFromType(DN_LOG_MakeU32LogTypeParam(DN_LOGType_Info), DN_CALL_SITE, fmt, ##__VA_ARGS__) +#define DN_LOG_WarningF(fmt, ...) DN_LOG_EmitFromType(DN_LOG_MakeU32LogTypeParam(DN_LOGType_Warning), DN_CALL_SITE, fmt, ##__VA_ARGS__) +#define DN_LOG_ErrorF(fmt, ...) DN_LOG_EmitFromType(DN_LOG_MakeU32LogTypeParam(DN_LOGType_Error), DN_CALL_SITE, fmt, ##__VA_ARGS__) + + +#endif // !defined(DN_BASE_LOG_H) +// DN: Single header generator inlined this file => #include "Base/dn_base_string.h" +#if !defined(DN_BASE_STRING_H) +#define DN_BASE_STRING_H + +#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) +// DN: Single header generator commented out this header => #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) +// - Adding '%S' for DN_Str8 + +// stb_sprintf - v1.10 - public domain snprintf() implementation +// originally by Jeff Roberts / RAD Game Tools, 2015/10/20 +// http://github.com/nothings/stb +// +// allowed types: sc uidBboXx p AaGgEef n +// lengths : hh h ll j z t I64 I32 I +// +// Contributors: +// Fabian "ryg" Giesen (reformatting) +// github:aganm (attribute format) +// +// Contributors (bugfixes): +// github:d26435 +// github:trex78 +// github:account-login +// Jari Komppa (SI suffixes) +// Rohit Nirmal +// Marcin Wojdyr +// Leonard Ritter +// Stefano Zanotti +// Adam Allison +// Arvid Gerstmann +// Markus Kolb +// +// LICENSE: +// +// See end of file for license information. + +#ifndef STB_SPRINTF_H_INCLUDE +#define STB_SPRINTF_H_INCLUDE + +/* +Single file sprintf replacement. + +Originally written by Jeff Roberts at RAD Game Tools - 2015/10/20. +Hereby placed in public domain. + +This is a full sprintf replacement that supports everything that +the C runtime sprintfs support, including float/double, 64-bit integers, +hex floats, field parameters (%*.*d stuff), length reads backs, etc. + +Why would you need this if sprintf already exists? Well, first off, +it's *much* faster (see below). It's also much smaller than the CRT +versions code-space-wise. We've also added some simple improvements +that are super handy (commas in thousands, callbacks at buffer full, +for example). Finally, the format strings for MSVC and GCC differ +for 64-bit integers (among other small things), so this lets you use +the same format strings in cross platform code. + +It uses the standard single file trick of being both the header file +and the source itself. If you just include it normally, you just get +the header file function definitions. To get the code, you include +it from a C or C++ file and define STB_SPRINTF_IMPLEMENTATION first. + +It only uses va_args macros from the C runtime to do it's work. It +does cast doubles to S64s and shifts and divides U64s, which does +drag in CRT code on most platforms. + +It compiles to roughly 8K with float support, and 4K without. +As a comparison, when using MSVC static libs, calling sprintf drags +in 16K. + +API: +==== +int stbsp_sprintf( char * buf, char const * fmt, ... ) +int stbsp_snprintf( char * buf, int count, char const * fmt, ... ) + Convert an arg list into a buffer. stbsp_snprintf always returns + a zero-terminated string (unlike regular snprintf). + +int stbsp_vsprintf( char * buf, char const * fmt, va_list va ) +int stbsp_vsnprintf( char * buf, int count, char const * fmt, va_list va ) + Convert a va_list arg list into a buffer. stbsp_vsnprintf always returns + a zero-terminated string (unlike regular snprintf). + +int stbsp_vsprintfcb( STBSP_SPRINTFCB * callback, void * user, char * buf, char const * fmt, va_list va ) + typedef char * STBSP_SPRINTFCB( char const * buf, void * user, int len ); + Convert into a buffer, calling back every STB_SPRINTF_MIN chars. + Your callback can then copy the chars out, print them or whatever. + This function is actually the workhorse for everything else. + The buffer you pass in must hold at least STB_SPRINTF_MIN characters. + // you return the next buffer to use or 0 to stop converting + +void stbsp_set_separators( char comma, char period ) + Set the comma and period characters to use. + +FLOATS/DOUBLES: +=============== +This code uses a internal float->ascii conversion method that uses +doubles with error correction (double-doubles, for ~105 bits of +precision). This conversion is round-trip perfect - that is, an atof +of the values output here will give you the bit-exact double back. + +One difference is that our insignificant digits will be different than +with MSVC or GCC (but they don't match each other either). We also +don't attempt to find the minimum length matching float (pre-MSVC15 +doesn't either). + +If you don't need float or doubles at all, define STB_SPRINTF_NOFLOAT +and you'll save 4K of code space. + +64-BIT INTS: +============ +This library also supports 64-bit integers and you can use MSVC style or +GCC style indicators (%I64d or %lld). It supports the C99 specifiers +for size_t and ptr_diff_t (%jd %zd) as well. + +EXTRAS: +======= +Like some GCCs, for integers and floats, you can use a ' (single quote) +specifier and commas will be inserted on the thousands: "%'d" on 12345 +would print 12,345. + +For integers and floats, you can use a "$" specifier and the number +will be converted to float and then divided to get kilo, mega, giga or +tera and then printed, so "%$d" 1000 is "1.0 k", "%$.2d" 2536000 is +"2.53 M", etc. For byte values, use two $:s, like "%$$d" to turn +2536000 to "2.42 Mi". If you prefer JEDEC suffixes to SI ones, use three +$:s: "%$$$d" -> "2.42 M". To remove the space between the number and the +suffix, add "_" specifier: "%_$d" -> "2.53M". + +In addition to octal and hexadecimal conversions, you can print +integers in binary: "%b" for 256 would print 100. + +PERFORMANCE vs MSVC 2008 32-/64-bit (GCC is even slower than MSVC): +=================================================================== +"%d" across all 32-bit ints (4.8x/4.0x faster than 32-/64-bit MSVC) +"%24d" across all 32-bit ints (4.5x/4.2x faster) +"%x" across all 32-bit ints (4.5x/3.8x faster) +"%08x" across all 32-bit ints (4.3x/3.8x faster) +"%f" across e-10 to e+10 floats (7.3x/6.0x faster) +"%e" across e-10 to e+10 floats (8.1x/6.0x faster) +"%g" across e-10 to e+10 floats (10.0x/7.1x faster) +"%f" for values near e-300 (7.9x/6.5x faster) +"%f" for values near e+300 (10.0x/9.1x faster) +"%e" for values near e-300 (10.1x/7.0x faster) +"%e" for values near e+300 (9.2x/6.0x faster) +"%.320f" for values near e-300 (12.6x/11.2x faster) +"%a" for random values (8.6x/4.3x faster) +"%I64d" for 64-bits with 32-bit values (4.8x/3.4x faster) +"%I64d" for 64-bits > 32-bit values (4.9x/5.5x faster) +"%s%s%s" for 64 char strings (7.1x/7.3x faster) +"...512 char string..." ( 35.0x/32.5x faster!) +*/ + +#if defined(__clang__) + #if defined(__has_feature) && defined(__has_attribute) + #if __has_feature(address_sanitizer) + #if __has_attribute(__no_sanitize__) + #define STBSP__ASAN __attribute__((__no_sanitize__("address"))) + #elif __has_attribute(__no_sanitize_address__) + #define STBSP__ASAN __attribute__((__no_sanitize_address__)) + #elif __has_attribute(__no_address_safety_analysis__) + #define STBSP__ASAN __attribute__((__no_address_safety_analysis__)) + #endif + #endif + #endif +#elif defined(__GNUC__) && (__GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) + #if defined(__SANITIZE_ADDRESS__) && __SANITIZE_ADDRESS__ + #define STBSP__ASAN __attribute__((__no_sanitize_address__)) + #endif +#elif defined(_MSC_VER) + #if defined(__SANITIZE_ADDRESS__) + #define STBSP__ASAN __declspec(no_sanitize_address) + #endif +#endif + +#ifndef STBSP__ASAN +#define STBSP__ASAN +#endif + +#ifdef STB_SPRINTF_STATIC +#define STBSP__PUBLICDEC static STBSP__ASAN +#define STBSP__PUBLICDEF static STBSP__ASAN +#else +#ifdef __cplusplus +#define STBSP__PUBLICDEC extern "C" STBSP__ASAN +#define STBSP__PUBLICDEF extern "C" STBSP__ASAN +#else +#define STBSP__PUBLICDEC extern STBSP__ASAN +#define STBSP__PUBLICDEF STBSP__ASAN +#endif +#endif + +#if defined(__has_attribute) + #if __has_attribute(format) + #define STBSP__ATTRIBUTE_FORMAT(fmt,va) __attribute__((format(printf,fmt,va))) + #endif +#endif + +#ifndef STBSP__ATTRIBUTE_FORMAT +#define STBSP__ATTRIBUTE_FORMAT(fmt,va) +#endif + +#ifdef _MSC_VER +#define STBSP__NOTUSED(v) (void)(v) +#else +#define STBSP__NOTUSED(v) (void)sizeof(v) +#endif + +#include // for va_arg(), va_list() +#include // size_t, ptrdiff_t + +#ifndef STB_SPRINTF_MIN +#define STB_SPRINTF_MIN 512 // how many characters per callback +#endif +typedef char *STBSP_SPRINTFCB(const char *buf, void *user, int len); + +#ifndef STB_SPRINTF_DECORATE +#define STB_SPRINTF_DECORATE(name) stbsp_##name // define this before including if you want to change the names +#endif + +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va); +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsnprintf)(char *buf, int count, char const *fmt, va_list va); +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...) STBSP__ATTRIBUTE_FORMAT(2,3); +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...) STBSP__ATTRIBUTE_FORMAT(3,4); + +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va); +STBSP__PUBLICDEC void STB_SPRINTF_DECORATE(set_separators)(char comma, char period); + +#endif // STB_SPRINTF_H_INCLUDE + +#ifdef STB_SPRINTF_IMPLEMENTATION + +#define stbsp__uint32 unsigned int +#define stbsp__int32 signed int + +#ifdef _MSC_VER +#define stbsp__uint64 unsigned __int64 +#define stbsp__int64 signed __int64 +#else +#define stbsp__uint64 unsigned long long +#define stbsp__int64 signed long long +#endif +#define stbsp__uint16 unsigned short + +#ifndef stbsp__uintptr +#if defined(__ppc64__) || defined(__powerpc64__) || defined(__aarch64__) || defined(_M_X64) || defined(__x86_64__) || defined(__x86_64) || defined(__s390x__) +#define stbsp__uintptr stbsp__uint64 +#else +#define stbsp__uintptr stbsp__uint32 +#endif +#endif + +#ifndef STB_SPRINTF_MSVC_MODE // used for MSVC2013 and earlier (MSVC2015 matches GCC) +#if defined(_MSC_VER) && (_MSC_VER < 1900) +#define STB_SPRINTF_MSVC_MODE +#endif +#endif + +#ifdef STB_SPRINTF_NOUNALIGNED // define this before inclusion to force stbsp_sprintf to always use aligned accesses +#define STBSP__UNALIGNED(code) +#else +#define STBSP__UNALIGNED(code) code +#endif + +#ifndef STB_SPRINTF_NOFLOAT +// internal float utility functions +static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, char *out, stbsp__int32 *decimal_pos, double value, stbsp__uint32 frac_digits); +static stbsp__int32 stbsp__real_to_parts(stbsp__int64 *bits, stbsp__int32 *expo, double value); +#define STBSP__SPECIAL 0x7000 +#endif + +static char stbsp__period = '.'; +static char stbsp__comma = ','; +static struct +{ + short temp; // force next field to be 2-byte aligned + char pair[201]; +} stbsp__digitpair = +{ + 0, + "00010203040506070809101112131415161718192021222324" + "25262728293031323334353637383940414243444546474849" + "50515253545556575859606162636465666768697071727374" + "75767778798081828384858687888990919293949596979899" +}; + +STBSP__PUBLICDEF void STB_SPRINTF_DECORATE(set_separators)(char pcomma, char pperiod) +{ + stbsp__period = pperiod; + stbsp__comma = pcomma; +} + +#define STBSP__LEFTJUST 1 +#define STBSP__LEADINGPLUS 2 +#define STBSP__LEADINGSPACE 4 +#define STBSP__LEADING_0X 8 +#define STBSP__LEADINGZERO 16 +#define STBSP__INTMAX 32 +#define STBSP__TRIPLET_COMMA 64 +#define STBSP__NEGATIVE 128 +#define STBSP__METRIC_SUFFIX 256 +#define STBSP__HALFWIDTH 512 +#define STBSP__METRIC_NOSPACE 1024 +#define STBSP__METRIC_1024 2048 +#define STBSP__METRIC_JEDEC 4096 + +static void stbsp__lead_sign(stbsp__uint32 fl, char *sign) +{ + sign[0] = 0; + if (fl & STBSP__NEGATIVE) { + sign[0] = 1; + sign[1] = '-'; + } else if (fl & STBSP__LEADINGSPACE) { + sign[0] = 1; + sign[1] = ' '; + } else if (fl & STBSP__LEADINGPLUS) { + sign[0] = 1; + sign[1] = '+'; + } +} + +static STBSP__ASAN stbsp__uint32 stbsp__strlen_limited(char const *s, stbsp__uint32 limit) +{ + char const * sn = s; + + // get up to 4-byte alignment + for (;;) { + if (((stbsp__uintptr)sn & 3) == 0) + break; + + if (!limit || *sn == 0) + return (stbsp__uint32)(sn - s); + + ++sn; + --limit; + } + + // scan over 4 bytes at a time to find terminating 0 + // this will intentionally scan up to 3 bytes past the end of buffers, + // but becase it works 4B aligned, it will never cross page boundaries + // (hence the STBSP__ASAN markup; the over-read here is intentional + // and harmless) + while (limit >= 4) { + stbsp__uint32 v = *(stbsp__uint32 *)sn; + // bit hack to find if there's a 0 byte in there + if ((v - 0x01010101) & (~v) & 0x80808080UL) + break; + + sn += 4; + limit -= 4; + } + + // handle the last few characters to find actual size + while (limit && *sn) { + ++sn; + --limit; + } + + return (stbsp__uint32)(sn - s); +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va) +{ + static char hex[] = "0123456789abcdefxp"; + static char hexu[] = "0123456789ABCDEFXP"; + char *bf; + char const *f; + int tlen = 0; + + bf = buf; + f = fmt; + for (;;) { + stbsp__int32 fw, pr, tz; + stbsp__uint32 fl; + + // macros for the callback buffer stuff + #define stbsp__chk_cb_bufL(bytes) \ + { \ + int len = (int)(bf - buf); \ + if ((len + (bytes)) >= STB_SPRINTF_MIN) { \ + tlen += len; \ + if (0 == (bf = buf = callback(buf, user, len))) \ + goto done; \ + } \ + } + #define stbsp__chk_cb_buf(bytes) \ + { \ + if (callback) { \ + stbsp__chk_cb_bufL(bytes); \ + } \ + } + #define stbsp__flush_cb() \ + { \ + stbsp__chk_cb_bufL(STB_SPRINTF_MIN - 1); \ + } // flush if there is even one byte in the buffer + #define stbsp__cb_buf_clamp(cl, v) \ + cl = v; \ + if (callback) { \ + int lg = STB_SPRINTF_MIN - (int)(bf - buf); \ + if (cl > lg) \ + cl = lg; \ + } + + // fast copy everything up to the next % (or end of string) + for (;;) { + while (((stbsp__uintptr)f) & 3) { + schk1: + if (f[0] == '%') + goto scandd; + schk2: + if (f[0] == 0) + goto endfmt; + stbsp__chk_cb_buf(1); + *bf++ = f[0]; + ++f; + } + for (;;) { + // Check if the next 4 bytes contain %(0x25) or end of string. + // Using the 'hasless' trick: + // https://graphics.stanford.edu/~seander/bithacks.html#HasLessInWord + stbsp__uint32 v, c; + v = *(stbsp__uint32 *)f; + c = (~v) & 0x80808080; + if (((v ^ 0x25252525) - 0x01010101) & c) + goto schk1; + if ((v - 0x01010101) & c) + goto schk2; + if (callback) + if ((STB_SPRINTF_MIN - (int)(bf - buf)) < 4) + goto schk1; + #ifdef STB_SPRINTF_NOUNALIGNED + if(((stbsp__uintptr)bf) & 3) { + bf[0] = f[0]; + bf[1] = f[1]; + bf[2] = f[2]; + bf[3] = f[3]; + } else + #endif + { + *(stbsp__uint32 *)bf = v; + } + bf += 4; + f += 4; + } + } + scandd: + + ++f; + + // ok, we have a percent, read the modifiers first + fw = 0; + pr = -1; + fl = 0; + tz = 0; + + // flags + for (;;) { + switch (f[0]) { + // if we have left justify + case '-': + fl |= STBSP__LEFTJUST; + ++f; + continue; + // if we have leading plus + case '+': + fl |= STBSP__LEADINGPLUS; + ++f; + continue; + // if we have leading space + case ' ': + fl |= STBSP__LEADINGSPACE; + ++f; + continue; + // if we have leading 0x + case '#': + fl |= STBSP__LEADING_0X; + ++f; + continue; + // if we have thousand commas + case '\'': + fl |= STBSP__TRIPLET_COMMA; + ++f; + continue; + // if we have kilo marker (none->kilo->kibi->jedec) + case '$': + if (fl & STBSP__METRIC_SUFFIX) { + if (fl & STBSP__METRIC_1024) { + fl |= STBSP__METRIC_JEDEC; + } else { + fl |= STBSP__METRIC_1024; + } + } else { + fl |= STBSP__METRIC_SUFFIX; + } + ++f; + continue; + // if we don't want space between metric suffix and number + case '_': + fl |= STBSP__METRIC_NOSPACE; + ++f; + continue; + // if we have leading zero + case '0': + fl |= STBSP__LEADINGZERO; + ++f; + goto flags_done; + default: goto flags_done; + } + } + flags_done: + + // get the field width + if (f[0] == '*') { + fw = va_arg(va, stbsp__uint32); + ++f; + } else { + while ((f[0] >= '0') && (f[0] <= '9')) { + fw = fw * 10 + f[0] - '0'; + f++; + } + } + // get the precision + if (f[0] == '.') { + ++f; + if (f[0] == '*') { + pr = va_arg(va, stbsp__uint32); + ++f; + } else { + pr = 0; + while ((f[0] >= '0') && (f[0] <= '9')) { + pr = pr * 10 + f[0] - '0'; + f++; + } + } + } + + // handle integer size overrides + switch (f[0]) { + // are we halfwidth? + case 'h': + fl |= STBSP__HALFWIDTH; + ++f; + if (f[0] == 'h') + ++f; // QUARTERWIDTH + break; + // are we 64-bit (unix style) + case 'l': + fl |= ((sizeof(long) == 8) ? STBSP__INTMAX : 0); + ++f; + if (f[0] == 'l') { + fl |= STBSP__INTMAX; + ++f; + } + break; + // are we 64-bit on intmax? (c99) + case 'j': + fl |= (sizeof(size_t) == 8) ? STBSP__INTMAX : 0; + ++f; + break; + // are we 64-bit on size_t or ptrdiff_t? (c99) + case 'z': + fl |= (sizeof(ptrdiff_t) == 8) ? STBSP__INTMAX : 0; + ++f; + break; + case 't': + fl |= (sizeof(ptrdiff_t) == 8) ? STBSP__INTMAX : 0; + ++f; + break; + // are we 64-bit (msft style) + case 'I': + if ((f[1] == '6') && (f[2] == '4')) { + fl |= STBSP__INTMAX; + f += 3; + } else if ((f[1] == '3') && (f[2] == '2')) { + f += 3; + } else { + fl |= ((sizeof(void *) == 8) ? STBSP__INTMAX : 0); + ++f; + } + break; + default: break; + } + + // handle each replacement + switch (f[0]) { + #define STBSP__NUMSZ 512 // big enough for e308 (with commas) or e-307 + char num[STBSP__NUMSZ]; + char lead[8]; + char tail[8]; + char *s; + char const *h; + stbsp__uint32 l, n, cs; + stbsp__uint64 n64; +#ifndef STB_SPRINTF_NOFLOAT + double fv; +#endif + stbsp__int32 dp; + char const *sn; + + case 's': + // get the string + s = va_arg(va, char *); + if (s == 0) + s = (char *)"null"; + // get the length, limited to desired precision + // always limit to ~0u chars since our counts are 32b + l = stbsp__strlen_limited(s, (pr >= 0) ? pr : ~0u); + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + // copy the string in + goto scopy; + + case 'S': + { + DN_Str8 str8 = va_arg(va, DN_Str8); + s = (char *)str8.data; + l = (uint32_t)str8.size; + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + }goto scopy; + + case 'c': // char + // get the character + s = num + STBSP__NUMSZ - 1; + *s = (char)va_arg(va, int); + l = 1; + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + goto scopy; + + case 'n': // weird write-bytes specifier + { + int *d = va_arg(va, int *); + *d = tlen + (int)(bf - buf); + } break; + +#ifdef STB_SPRINTF_NOFLOAT + case 'A': // float + case 'a': // hex float + case 'G': // float + case 'g': // float + case 'E': // float + case 'e': // float + case 'f': // float + va_arg(va, double); // eat it + s = (char *)"No float"; + l = 8; + lead[0] = 0; + tail[0] = 0; + pr = 0; + cs = 0; + STBSP__NOTUSED(dp); + goto scopy; +#else + case 'A': // hex float + case 'a': // hex float + h = (f[0] == 'A') ? hexu : hex; + fv = va_arg(va, double); + if (pr == -1) + pr = 6; // default is 6 + // read the double into a string + if (stbsp__real_to_parts((stbsp__int64 *)&n64, &dp, fv)) + fl |= STBSP__NEGATIVE; + + s = num + 64; + + stbsp__lead_sign(fl, lead); + + if (dp == -1023) + dp = (n64) ? -1022 : 0; + else + n64 |= (((stbsp__uint64)1) << 52); + n64 <<= (64 - 56); + if (pr < 15) + n64 += ((((stbsp__uint64)8) << 56) >> (pr * 4)); +// add leading chars + +#ifdef STB_SPRINTF_MSVC_MODE + *s++ = '0'; + *s++ = 'x'; +#else + lead[1 + lead[0]] = '0'; + lead[2 + lead[0]] = 'x'; + lead[0] += 2; +#endif + *s++ = h[(n64 >> 60) & 15]; + n64 <<= 4; + if (pr) + *s++ = stbsp__period; + sn = s; + + // print the bits + n = pr; + if (n > 13) + n = 13; + if (pr > (stbsp__int32)n) + tz = pr - n; + pr = 0; + while (n--) { + *s++ = h[(n64 >> 60) & 15]; + n64 <<= 4; + } + + // print the expo + tail[1] = h[17]; + if (dp < 0) { + tail[2] = '-'; + dp = -dp; + } else + tail[2] = '+'; + n = (dp >= 1000) ? 6 : ((dp >= 100) ? 5 : ((dp >= 10) ? 4 : 3)); + tail[0] = (char)n; + for (;;) { + tail[n] = '0' + dp % 10; + if (n <= 3) + break; + --n; + dp /= 10; + } + + dp = (int)(s - sn); + l = (int)(s - (num + 64)); + s = num + 64; + cs = 1 + (3 << 24); + goto scopy; + + case 'G': // float + case 'g': // float + h = (f[0] == 'G') ? hexu : hex; + fv = va_arg(va, double); + if (pr == -1) + pr = 6; + else if (pr == 0) + pr = 1; // default is 6 + // read the double into a string + if (stbsp__real_to_str(&sn, &l, num, &dp, fv, (pr - 1) | 0x80000000)) + fl |= STBSP__NEGATIVE; + + // clamp the precision and delete extra zeros after clamp + n = pr; + if (l > (stbsp__uint32)pr) + l = pr; + while ((l > 1) && (pr) && (sn[l - 1] == '0')) { + --pr; + --l; + } + + // should we use %e + if ((dp <= -4) || (dp > (stbsp__int32)n)) { + if (pr > (stbsp__int32)l) + pr = l - 1; + else if (pr) + --pr; // when using %e, there is one digit before the decimal + goto doexpfromg; + } + // this is the insane action to get the pr to match %g semantics for %f + if (dp > 0) { + pr = (dp < (stbsp__int32)l) ? l - dp : 0; + } else { + pr = -dp + ((pr > (stbsp__int32)l) ? (stbsp__int32) l : pr); + } + goto dofloatfromg; + + case 'E': // float + case 'e': // float + h = (f[0] == 'E') ? hexu : hex; + fv = va_arg(va, double); + if (pr == -1) + pr = 6; // default is 6 + // read the double into a string + if (stbsp__real_to_str(&sn, &l, num, &dp, fv, pr | 0x80000000)) + fl |= STBSP__NEGATIVE; + doexpfromg: + tail[0] = 0; + stbsp__lead_sign(fl, lead); + if (dp == STBSP__SPECIAL) { + s = (char *)sn; + cs = 0; + pr = 0; + goto scopy; + } + s = num + 64; + // handle leading chars + *s++ = sn[0]; + + if (pr) + *s++ = stbsp__period; + + // handle after decimal + if ((l - 1) > (stbsp__uint32)pr) + l = pr + 1; + for (n = 1; n < l; n++) + *s++ = sn[n]; + // trailing zeros + tz = pr - (l - 1); + pr = 0; + // dump expo + tail[1] = h[0xe]; + dp -= 1; + if (dp < 0) { + tail[2] = '-'; + dp = -dp; + } else + tail[2] = '+'; +#ifdef STB_SPRINTF_MSVC_MODE + n = 5; +#else + n = (dp >= 100) ? 5 : 4; +#endif + tail[0] = (char)n; + for (;;) { + tail[n] = '0' + dp % 10; + if (n <= 3) + break; + --n; + dp /= 10; + } + cs = 1 + (3 << 24); // how many tens + goto flt_lead; + + case 'f': // float + fv = va_arg(va, double); + doafloat: + // do kilos + if (fl & STBSP__METRIC_SUFFIX) { + double divisor; + divisor = 1000.0f; + if (fl & STBSP__METRIC_1024) + divisor = 1024.0; + while (fl < 0x4000000) { + if ((fv < divisor) && (fv > -divisor)) + break; + fv /= divisor; + fl += 0x1000000; + } + } + if (pr == -1) + pr = 6; // default is 6 + // read the double into a string + if (stbsp__real_to_str(&sn, &l, num, &dp, fv, pr)) + fl |= STBSP__NEGATIVE; + dofloatfromg: + tail[0] = 0; + stbsp__lead_sign(fl, lead); + if (dp == STBSP__SPECIAL) { + s = (char *)sn; + cs = 0; + pr = 0; + goto scopy; + } + s = num + 64; + + // handle the three decimal varieties + if (dp <= 0) { + stbsp__int32 i; + // handle 0.000*000xxxx + *s++ = '0'; + if (pr) + *s++ = stbsp__period; + n = -dp; + if ((stbsp__int32)n > pr) + n = pr; + i = n; + while (i) { + if ((((stbsp__uintptr)s) & 3) == 0) + break; + *s++ = '0'; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)s = 0x30303030; + s += 4; + i -= 4; + } + while (i) { + *s++ = '0'; + --i; + } + if ((stbsp__int32)(l + n) > pr) + l = pr - n; + i = l; + while (i) { + *s++ = *sn++; + --i; + } + tz = pr - (n + l); + cs = 1 + (3 << 24); // how many tens did we write (for commas below) + } else { + cs = (fl & STBSP__TRIPLET_COMMA) ? ((600 - (stbsp__uint32)dp) % 3) : 0; + if ((stbsp__uint32)dp >= l) { + // handle xxxx000*000.0 + n = 0; + for (;;) { + if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { + cs = 0; + *s++ = stbsp__comma; + } else { + *s++ = sn[n]; + ++n; + if (n >= l) + break; + } + } + if (n < (stbsp__uint32)dp) { + n = dp - n; + if ((fl & STBSP__TRIPLET_COMMA) == 0) { + while (n) { + if ((((stbsp__uintptr)s) & 3) == 0) + break; + *s++ = '0'; + --n; + } + while (n >= 4) { + *(stbsp__uint32 *)s = 0x30303030; + s += 4; + n -= 4; + } + } + while (n) { + if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { + cs = 0; + *s++ = stbsp__comma; + } else { + *s++ = '0'; + --n; + } + } + } + cs = (int)(s - (num + 64)) + (3 << 24); // cs is how many tens + if (pr) { + *s++ = stbsp__period; + tz = pr; + } + } else { + // handle xxxxx.xxxx000*000 + n = 0; + for (;;) { + if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { + cs = 0; + *s++ = stbsp__comma; + } else { + *s++ = sn[n]; + ++n; + if (n >= (stbsp__uint32)dp) + break; + } + } + cs = (int)(s - (num + 64)) + (3 << 24); // cs is how many tens + if (pr) + *s++ = stbsp__period; + if ((l - dp) > (stbsp__uint32)pr) + l = pr + dp; + while (n < l) { + *s++ = sn[n]; + ++n; + } + tz = pr - (l - dp); + } + } + pr = 0; + + // handle k,m,g,t + if (fl & STBSP__METRIC_SUFFIX) { + char idx; + idx = 1; + if (fl & STBSP__METRIC_NOSPACE) + idx = 0; + tail[0] = idx; + tail[1] = ' '; + { + if (fl >> 24) { // SI kilo is 'k', JEDEC and SI kibits are 'K'. + if (fl & STBSP__METRIC_1024) + tail[idx + 1] = "_KMGT"[fl >> 24]; + else + tail[idx + 1] = "_kMGT"[fl >> 24]; + idx++; + // If printing kibits and not in jedec, add the 'i'. + if (fl & STBSP__METRIC_1024 && !(fl & STBSP__METRIC_JEDEC)) { + tail[idx + 1] = 'i'; + idx++; + } + tail[0] = idx; + } + } + }; + + flt_lead: + // get the length that we copied + l = (stbsp__uint32)(s - (num + 64)); + s = num + 64; + goto scopy; +#endif + + case 'B': // upper binary + case 'b': // lower binary + h = (f[0] == 'B') ? hexu : hex; + lead[0] = 0; + if (fl & STBSP__LEADING_0X) { + lead[0] = 2; + lead[1] = '0'; + lead[2] = h[0xb]; + } + l = (8 << 4) | (1 << 8); + goto radixnum; + + case 'o': // octal + h = hexu; + lead[0] = 0; + if (fl & STBSP__LEADING_0X) { + lead[0] = 1; + lead[1] = '0'; + } + l = (3 << 4) | (3 << 8); + goto radixnum; + + case 'p': // pointer + fl |= (sizeof(void *) == 8) ? STBSP__INTMAX : 0; + pr = sizeof(void *) * 2; + fl &= ~STBSP__LEADINGZERO; // 'p' only prints the pointer with zeros + // fall through - to X + + case 'X': // upper hex + case 'x': // lower hex + h = (f[0] == 'X') ? hexu : hex; + l = (4 << 4) | (4 << 8); + lead[0] = 0; + if (fl & STBSP__LEADING_0X) { + lead[0] = 2; + lead[1] = '0'; + lead[2] = h[16]; + } + radixnum: + // get the number + if (fl & STBSP__INTMAX) + n64 = va_arg(va, stbsp__uint64); + else + n64 = va_arg(va, stbsp__uint32); + + s = num + STBSP__NUMSZ; + dp = 0; + // clear tail, and clear leading if value is zero + tail[0] = 0; + if (n64 == 0) { + lead[0] = 0; + if (pr == 0) { + l = 0; + cs = 0; + goto scopy; + } + } + // convert to string + for (;;) { + *--s = h[n64 & ((1 << (l >> 8)) - 1)]; + n64 >>= (l >> 8); + if (!((n64) || ((stbsp__int32)((num + STBSP__NUMSZ) - s) < pr))) + break; + if (fl & STBSP__TRIPLET_COMMA) { + ++l; + if ((l & 15) == ((l >> 4) & 15)) { + l &= ~15; + *--s = stbsp__comma; + } + } + }; + // get the tens and the comma pos + cs = (stbsp__uint32)((num + STBSP__NUMSZ) - s) + ((((l >> 4) & 15)) << 24); + // get the length that we copied + l = (stbsp__uint32)((num + STBSP__NUMSZ) - s); + // copy it + goto scopy; + + case 'u': // unsigned + case 'i': + case 'd': // integer + // get the integer and abs it + if (fl & STBSP__INTMAX) { + stbsp__int64 i64 = va_arg(va, stbsp__int64); + n64 = (stbsp__uint64)i64; + if ((f[0] != 'u') && (i64 < 0)) { + n64 = (stbsp__uint64)-i64; + fl |= STBSP__NEGATIVE; + } + } else { + stbsp__int32 i = va_arg(va, stbsp__int32); + n64 = (stbsp__uint32)i; + if ((f[0] != 'u') && (i < 0)) { + n64 = (stbsp__uint32)-i; + fl |= STBSP__NEGATIVE; + } + } + +#ifndef STB_SPRINTF_NOFLOAT + if (fl & STBSP__METRIC_SUFFIX) { + if (n64 < 1024) + pr = 0; + else if (pr == -1) + pr = 1; + fv = (double)(stbsp__int64)n64; + goto doafloat; + } +#endif + + // convert to string + s = num + STBSP__NUMSZ; + l = 0; + + for (;;) { + // do in 32-bit chunks (avoid lots of 64-bit divides even with constant denominators) + char *o = s - 8; + if (n64 >= 100000000) { + n = (stbsp__uint32)(n64 % 100000000); + n64 /= 100000000; + } else { + n = (stbsp__uint32)n64; + n64 = 0; + } + if ((fl & STBSP__TRIPLET_COMMA) == 0) { + do { + s -= 2; + *(stbsp__uint16 *)s = *(stbsp__uint16 *)&stbsp__digitpair.pair[(n % 100) * 2]; + n /= 100; + } while (n); + } + while (n) { + if ((fl & STBSP__TRIPLET_COMMA) && (l++ == 3)) { + l = 0; + *--s = stbsp__comma; + --o; + } else { + *--s = (char)(n % 10) + '0'; + n /= 10; + } + } + if (n64 == 0) { + if ((s[0] == '0') && (s != (num + STBSP__NUMSZ))) + ++s; + break; + } + while (s != o) + if ((fl & STBSP__TRIPLET_COMMA) && (l++ == 3)) { + l = 0; + *--s = stbsp__comma; + --o; + } else { + *--s = '0'; + } + } + + tail[0] = 0; + stbsp__lead_sign(fl, lead); + + // get the length that we copied + l = (stbsp__uint32)((num + STBSP__NUMSZ) - s); + if (l == 0) { + *--s = '0'; + l = 1; + } + cs = l + (3 << 24); + if (pr < 0) + pr = 0; + + scopy: + // get fw=leading/trailing space, pr=leading zeros + if (pr < (stbsp__int32)l) + pr = l; + n = pr + lead[0] + tail[0] + tz; + if (fw < (stbsp__int32)n) + fw = n; + fw -= n; + pr -= l; + + // handle right justify and leading zeros + if ((fl & STBSP__LEFTJUST) == 0) { + if (fl & STBSP__LEADINGZERO) // if leading zeros, everything is in pr + { + pr = (fw > pr) ? fw : pr; + fw = 0; + } else { + fl &= ~STBSP__TRIPLET_COMMA; // if no leading zeros, then no commas + } + } + + // copy the spaces and/or zeros + if (fw + pr) { + stbsp__int32 i; + stbsp__uint32 c; + + // copy leading spaces (or when doing %8.4d stuff) + if ((fl & STBSP__LEFTJUST) == 0) + while (fw > 0) { + stbsp__cb_buf_clamp(i, fw); + fw -= i; + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = ' '; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x20202020; + bf += 4; + i -= 4; + } + while (i) { + *bf++ = ' '; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy leader + sn = lead + 1; + while (lead[0]) { + stbsp__cb_buf_clamp(i, lead[0]); + lead[0] -= (char)i; + while (i) { + *bf++ = *sn++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy leading zeros + c = cs >> 24; + cs &= 0xffffff; + cs = (fl & STBSP__TRIPLET_COMMA) ? ((stbsp__uint32)(c - ((pr + cs) % (c + 1)))) : 0; + while (pr > 0) { + stbsp__cb_buf_clamp(i, pr); + pr -= i; + if ((fl & STBSP__TRIPLET_COMMA) == 0) { + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = '0'; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x30303030; + bf += 4; + i -= 4; + } + } + while (i) { + if ((fl & STBSP__TRIPLET_COMMA) && (cs++ == c)) { + cs = 0; + *bf++ = stbsp__comma; + } else + *bf++ = '0'; + --i; + } + stbsp__chk_cb_buf(1); + } + } + + // copy leader if there is still one + sn = lead + 1; + while (lead[0]) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, lead[0]); + lead[0] -= (char)i; + while (i) { + *bf++ = *sn++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy the string + n = l; + while (n) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, n); + n -= i; + STBSP__UNALIGNED(while (i >= 4) { + *(stbsp__uint32 volatile *)bf = *(stbsp__uint32 volatile *)s; + bf += 4; + s += 4; + i -= 4; + }) + while (i) { + *bf++ = *s++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy trailing zeros + while (tz) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, tz); + tz -= i; + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = '0'; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x30303030; + bf += 4; + i -= 4; + } + while (i) { + *bf++ = '0'; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy tail if there is one + sn = tail + 1; + while (tail[0]) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, tail[0]); + tail[0] -= (char)i; + while (i) { + *bf++ = *sn++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // handle the left justify + if (fl & STBSP__LEFTJUST) + if (fw > 0) { + while (fw) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, fw); + fw -= i; + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = ' '; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x20202020; + bf += 4; + i -= 4; + } + while (i--) + *bf++ = ' '; + stbsp__chk_cb_buf(1); + } + } + break; + + default: // unknown, just copy code + s = num + STBSP__NUMSZ - 1; + *s = f[0]; + l = 1; + fw = fl = 0; + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + goto scopy; + } + ++f; + } +endfmt: + + if (!callback) + *bf = 0; + else + stbsp__flush_cb(); + +done: + return tlen + (int)(bf - buf); +} + +// cleanup +#undef STBSP__LEFTJUST +#undef STBSP__LEADINGPLUS +#undef STBSP__LEADINGSPACE +#undef STBSP__LEADING_0X +#undef STBSP__LEADINGZERO +#undef STBSP__INTMAX +#undef STBSP__TRIPLET_COMMA +#undef STBSP__NEGATIVE +#undef STBSP__METRIC_SUFFIX +#undef STBSP__NUMSZ +#undef stbsp__chk_cb_bufL +#undef stbsp__chk_cb_buf +#undef stbsp__flush_cb +#undef stbsp__cb_buf_clamp + +// ============================================================================ +// wrapper functions + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...) +{ + int result; + va_list va; + va_start(va, fmt); + result = STB_SPRINTF_DECORATE(vsprintfcb)(0, 0, buf, fmt, va); + va_end(va); + return result; +} + +typedef struct stbsp__context { + char *buf; + int count; + int length; + char tmp[STB_SPRINTF_MIN]; +} stbsp__context; + +static char *stbsp__clamp_callback(const char *buf, void *user, int len) +{ + stbsp__context *c = (stbsp__context *)user; + c->length += len; + + if (len > c->count) + len = c->count; + + if (len) { + if (buf != c->buf) { + const char *s, *se; + char *d; + d = c->buf; + s = buf; + se = buf + len; + do { + *d++ = *s++; + } while (s < se); + } + c->buf += len; + c->count -= len; + } + + if (c->count <= 0) + return c->tmp; + return (c->count >= STB_SPRINTF_MIN) ? c->buf : c->tmp; // go direct into buffer if you can +} + +static char * stbsp__count_clamp_callback( const char * buf, void * user, int len ) +{ + stbsp__context * c = (stbsp__context*)user; + (void) sizeof(buf); + + c->length += len; + return c->tmp; // go direct into buffer if you can +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE( vsnprintf )( char * buf, int count, char const * fmt, va_list va ) +{ + stbsp__context c; + + if ( (count == 0) && !buf ) + { + c.length = 0; + + STB_SPRINTF_DECORATE( vsprintfcb )( stbsp__count_clamp_callback, &c, c.tmp, fmt, va ); + } + else + { + int l; + + c.buf = buf; + c.count = count; + c.length = 0; + + STB_SPRINTF_DECORATE( vsprintfcb )( stbsp__clamp_callback, &c, stbsp__clamp_callback(0,&c,0), fmt, va ); + + // zero-terminate + l = (int)( c.buf - buf ); + if ( l >= count ) // should never be greater, only equal (or less) than count + l = count - 1; + buf[l] = 0; + } + + return c.length; +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...) +{ + int result; + va_list va; + va_start(va, fmt); + + result = STB_SPRINTF_DECORATE(vsnprintf)(buf, count, fmt, va); + va_end(va); + + return result; +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va) +{ + return STB_SPRINTF_DECORATE(vsprintfcb)(0, 0, buf, fmt, va); +} + +// ======================================================================= +// low level float utility functions + +#ifndef STB_SPRINTF_NOFLOAT + +// copies d to bits w/ strict aliasing (this compiles to nothing on /Ox) +#define STBSP__COPYFP(dest, src) \ + { \ + int cn; \ + for (cn = 0; cn < 8; cn++) \ + ((char *)&dest)[cn] = ((char *)&src)[cn]; \ + } + +// get float info +static stbsp__int32 stbsp__real_to_parts(stbsp__int64 *bits, stbsp__int32 *expo, double value) +{ + double d; + stbsp__int64 b = 0; + + // load value and round at the frac_digits + d = value; + + STBSP__COPYFP(b, d); + + *bits = b & ((((stbsp__uint64)1) << 52) - 1); + *expo = (stbsp__int32)(((b >> 52) & 2047) - 1023); + + return (stbsp__int32)((stbsp__uint64) b >> 63); +} + +static double const stbsp__bot[23] = { + 1e+000, 1e+001, 1e+002, 1e+003, 1e+004, 1e+005, 1e+006, 1e+007, 1e+008, 1e+009, 1e+010, 1e+011, + 1e+012, 1e+013, 1e+014, 1e+015, 1e+016, 1e+017, 1e+018, 1e+019, 1e+020, 1e+021, 1e+022 +}; +static double const stbsp__negbot[22] = { + 1e-001, 1e-002, 1e-003, 1e-004, 1e-005, 1e-006, 1e-007, 1e-008, 1e-009, 1e-010, 1e-011, + 1e-012, 1e-013, 1e-014, 1e-015, 1e-016, 1e-017, 1e-018, 1e-019, 1e-020, 1e-021, 1e-022 +}; +static double const stbsp__negboterr[22] = { + -5.551115123125783e-018, -2.0816681711721684e-019, -2.0816681711721686e-020, -4.7921736023859299e-021, -8.1803053914031305e-022, 4.5251888174113741e-023, + 4.5251888174113739e-024, -2.0922560830128471e-025, -6.2281591457779853e-026, -3.6432197315497743e-027, 6.0503030718060191e-028, 2.0113352370744385e-029, + -3.0373745563400371e-030, 1.1806906454401013e-032, -7.7705399876661076e-032, 2.0902213275965398e-033, -7.1542424054621921e-034, -7.1542424054621926e-035, + 2.4754073164739869e-036, 5.4846728545790429e-037, 9.2462547772103625e-038, -4.8596774326570872e-039 +}; +static double const stbsp__top[13] = { + 1e+023, 1e+046, 1e+069, 1e+092, 1e+115, 1e+138, 1e+161, 1e+184, 1e+207, 1e+230, 1e+253, 1e+276, 1e+299 +}; +static double const stbsp__negtop[13] = { + 1e-023, 1e-046, 1e-069, 1e-092, 1e-115, 1e-138, 1e-161, 1e-184, 1e-207, 1e-230, 1e-253, 1e-276, 1e-299 +}; +static double const stbsp__toperr[13] = { + 8388608, + 6.8601809640529717e+028, + -7.253143638152921e+052, + -4.3377296974619174e+075, + -1.5559416129466825e+098, + -3.2841562489204913e+121, + -3.7745893248228135e+144, + -1.7356668416969134e+167, + -3.8893577551088374e+190, + -9.9566444326005119e+213, + 6.3641293062232429e+236, + -5.2069140800249813e+259, + -5.2504760255204387e+282 +}; +static double const stbsp__negtoperr[13] = { + 3.9565301985100693e-040, -2.299904345391321e-063, 3.6506201437945798e-086, 1.1875228833981544e-109, + -5.0644902316928607e-132, -6.7156837247865426e-155, -2.812077463003139e-178, -5.7778912386589953e-201, + 7.4997100559334532e-224, -4.6439668915134491e-247, -6.3691100762962136e-270, -9.436808465446358e-293, + 8.0970921678014997e-317 +}; + +#if defined(_MSC_VER) && (_MSC_VER <= 1200) +static stbsp__uint64 const stbsp__powten[20] = { + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + 10000000000, + 100000000000, + 1000000000000, + 10000000000000, + 100000000000000, + 1000000000000000, + 10000000000000000, + 100000000000000000, + 1000000000000000000, + 10000000000000000000U +}; +#define stbsp__tento19th ((stbsp__uint64)1000000000000000000) +#else +static stbsp__uint64 const stbsp__powten[20] = { + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + 10000000000ULL, + 100000000000ULL, + 1000000000000ULL, + 10000000000000ULL, + 100000000000000ULL, + 1000000000000000ULL, + 10000000000000000ULL, + 100000000000000000ULL, + 1000000000000000000ULL, + 10000000000000000000ULL +}; +#define stbsp__tento19th (1000000000000000000ULL) +#endif + +#define stbsp__ddmulthi(oh, ol, xh, yh) \ + { \ + double ahi = 0, alo, bhi = 0, blo; \ + stbsp__int64 bt; \ + oh = xh * yh; \ + STBSP__COPYFP(bt, xh); \ + bt &= ((~(stbsp__uint64)0) << 27); \ + STBSP__COPYFP(ahi, bt); \ + alo = xh - ahi; \ + STBSP__COPYFP(bt, yh); \ + bt &= ((~(stbsp__uint64)0) << 27); \ + STBSP__COPYFP(bhi, bt); \ + blo = yh - bhi; \ + ol = ((ahi * bhi - oh) + ahi * blo + alo * bhi) + alo * blo; \ + } + +#define stbsp__ddtoS64(ob, xh, xl) \ + { \ + double ahi = 0, alo, vh, t; \ + ob = (stbsp__int64)xh; \ + vh = (double)ob; \ + ahi = (xh - vh); \ + t = (ahi - xh); \ + alo = (xh - (ahi - t)) - (vh + t); \ + ob += (stbsp__int64)(ahi + alo + xl); \ + } + +#define stbsp__ddrenorm(oh, ol) \ + { \ + double s; \ + s = oh + ol; \ + ol = ol - (s - oh); \ + oh = s; \ + } + +#define stbsp__ddmultlo(oh, ol, xh, xl, yh, yl) ol = ol + (xh * yl + xl * yh); + +#define stbsp__ddmultlos(oh, ol, xh, yl) ol = ol + (xh * yl); + +static void stbsp__raise_to_power10(double *ohi, double *olo, double d, stbsp__int32 power) // power can be -323 to +350 +{ + double ph, pl; + if ((power >= 0) && (power <= 22)) { + stbsp__ddmulthi(ph, pl, d, stbsp__bot[power]); + } else { + stbsp__int32 e, et, eb; + double p2h, p2l; + + e = power; + if (power < 0) + e = -e; + et = (e * 0x2c9) >> 14; /* %23 */ + if (et > 13) + et = 13; + eb = e - (et * 23); + + ph = d; + pl = 0.0; + if (power < 0) { + if (eb) { + --eb; + stbsp__ddmulthi(ph, pl, d, stbsp__negbot[eb]); + stbsp__ddmultlos(ph, pl, d, stbsp__negboterr[eb]); + } + if (et) { + stbsp__ddrenorm(ph, pl); + --et; + stbsp__ddmulthi(p2h, p2l, ph, stbsp__negtop[et]); + stbsp__ddmultlo(p2h, p2l, ph, pl, stbsp__negtop[et], stbsp__negtoperr[et]); + ph = p2h; + pl = p2l; + } + } else { + if (eb) { + e = eb; + if (eb > 22) + eb = 22; + e -= eb; + stbsp__ddmulthi(ph, pl, d, stbsp__bot[eb]); + if (e) { + stbsp__ddrenorm(ph, pl); + stbsp__ddmulthi(p2h, p2l, ph, stbsp__bot[e]); + stbsp__ddmultlos(p2h, p2l, stbsp__bot[e], pl); + ph = p2h; + pl = p2l; + } + } + if (et) { + stbsp__ddrenorm(ph, pl); + --et; + stbsp__ddmulthi(p2h, p2l, ph, stbsp__top[et]); + stbsp__ddmultlo(p2h, p2l, ph, pl, stbsp__top[et], stbsp__toperr[et]); + ph = p2h; + pl = p2l; + } + } + } + stbsp__ddrenorm(ph, pl); + *ohi = ph; + *olo = pl; +} + +// given a float value, returns the significant bits in bits, and the position of the +// decimal point in decimal_pos. +/-INF and NAN are specified by special values +// returned in the decimal_pos parameter. +// frac_digits is absolute normally, but if you want from first significant digits (got %g and %e), or in 0x80000000 +static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, char *out, stbsp__int32 *decimal_pos, double value, stbsp__uint32 frac_digits) +{ + double d; + stbsp__int64 bits = 0; + stbsp__int32 expo, e, ng, tens; + + d = value; + STBSP__COPYFP(bits, d); + expo = (stbsp__int32)((bits >> 52) & 2047); + ng = (stbsp__int32)((stbsp__uint64) bits >> 63); + if (ng) + d = -d; + + if (expo == 2047) // is nan or inf? + { + *start = (bits & ((((stbsp__uint64)1) << 52) - 1)) ? "NaN" : "Inf"; + *decimal_pos = STBSP__SPECIAL; + *len = 3; + return ng; + } + + if (expo == 0) // is zero or denormal + { + if (((stbsp__uint64) bits << 1) == 0) // do zero + { + *decimal_pos = 1; + *start = out; + out[0] = '0'; + *len = 1; + return ng; + } + // find the right expo for denormals + { + stbsp__int64 v = ((stbsp__uint64)1) << 51; + while ((bits & v) == 0) { + --expo; + v >>= 1; + } + } + } + + // find the decimal exponent as well as the decimal bits of the value + { + double ph, pl; + + // log10 estimate - very specifically tweaked to hit or undershoot by no more than 1 of log10 of all expos 1..2046 + tens = expo - 1023; + tens = (tens < 0) ? ((tens * 617) / 2048) : (((tens * 1233) / 4096) + 1); + + // move the significant bits into position and stick them into an int + stbsp__raise_to_power10(&ph, &pl, d, 18 - tens); + + // get full as much precision from double-double as possible + stbsp__ddtoS64(bits, ph, pl); + + // check if we undershot + if (((stbsp__uint64)bits) >= stbsp__tento19th) + ++tens; + } + + // now do the rounding in integer land + frac_digits = (frac_digits & 0x80000000) ? ((frac_digits & 0x7ffffff) + 1) : (tens + frac_digits); + if ((frac_digits < 24)) { + stbsp__uint32 dg = 1; + if ((stbsp__uint64)bits >= stbsp__powten[9]) + dg = 10; + while ((stbsp__uint64)bits >= stbsp__powten[dg]) { + ++dg; + if (dg == 20) + goto noround; + } + if (frac_digits < dg) { + stbsp__uint64 r; + // add 0.5 at the right position and round + e = dg - frac_digits; + if ((stbsp__uint32)e >= 24) + goto noround; + r = stbsp__powten[e]; + bits = bits + (r / 2); + if ((stbsp__uint64)bits >= stbsp__powten[dg]) + ++tens; + bits /= r; + } + noround:; + } + + // kill long trailing runs of zeros + if (bits) { + stbsp__uint32 n; + for (;;) { + if (bits <= 0xffffffff) + break; + if (bits % 1000) + goto donez; + bits /= 1000; + } + n = (stbsp__uint32)bits; + while ((n % 1000) == 0) + n /= 1000; + bits = n; + donez:; + } + + // convert to string + out += 64; + e = 0; + for (;;) { + stbsp__uint32 n; + char *o = out - 8; + // do the conversion in chunks of U32s (avoid most 64-bit divides, worth it, constant denomiators be damned) + if (bits >= 100000000) { + n = (stbsp__uint32)(bits % 100000000); + bits /= 100000000; + } else { + n = (stbsp__uint32)bits; + bits = 0; + } + while (n) { + out -= 2; + *(stbsp__uint16 *)out = *(stbsp__uint16 *)&stbsp__digitpair.pair[(n % 100) * 2]; + n /= 100; + e += 2; + } + if (bits == 0) { + if ((e) && (out[0] == '0')) { + ++out; + --e; + } + break; + } + while (out != o) { + *--out = '0'; + ++e; + } + } + + *decimal_pos = tens; + *start = out; + *len = e; + return ng; +} + +#undef stbsp__ddmulthi +#undef stbsp__ddrenorm +#undef stbsp__ddmultlo +#undef stbsp__ddmultlos +#undef STBSP__SPECIAL +#undef STBSP__COPYFP + +#endif // STB_SPRINTF_NOFLOAT + +// clean up +#undef stbsp__uint16 +#undef stbsp__uint32 +#undef stbsp__int32 +#undef stbsp__uint64 +#undef stbsp__int64 +#undef STBSP__UNALIGNED + +#endif // STB_SPRINTF_IMPLEMENTATION + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ +DN_GCC_WARNING_POP +DN_MSVC_WARNING_POP + +#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_base_string.h +// +//////////////////////////////////////////////////////////////////////////////////////////////////// +*/ + +// NOTE: DN_Str8 ////////////////////////////////////////////////////////////////////////////////// +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_Str8BinarySplitResult +{ + 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 +}; + +enum DN_Str8IsAll +{ + DN_Str8IsAll_Digits, + DN_Str8IsAll_Hex, +}; + +enum DN_Str8EqCase +{ + DN_Str8EqCase_Sensitive, + DN_Str8EqCase_Insensitive, +}; + +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, +}; + +enum DN_Str8SplitIncludeEmptyStrings +{ + DN_Str8SplitIncludeEmptyStrings_No, + DN_Str8SplitIncludeEmptyStrings_Yes, +}; + +struct DN_Str8ToU64Result +{ + bool success; + uint64_t value; +}; + +struct DN_Str8ToI64Result +{ + bool success; + int64_t value; +}; + +struct DN_Str8DotTruncateResult +{ + bool truncated; + DN_Str8 str8; +}; + +// NOTE: DN_FStr8 ////////////////////////////////////////////////////////////////////////////////// +#if !defined(DN_NO_FSTR8) +template +struct DN_FStr8 +{ + char data[N + 1]; + DN_USize size; + + char *begin() { return data; } + char *end() { return data + size; } + char const *begin() const { return data; } + char const *end() const { return data + size; } +}; +#endif // !defined(DN_NO_FSTR8) + +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, +}; + +// NOTE: DN_CStr8 ////////////////////////////////////////////////////////////////////////////////// +template constexpr DN_USize DN_CStr8_ArrayUCount (char const (&literal)[N]) { (void)literal; return N - 1; } +template constexpr DN_USize DN_CStr8_ArrayICount (char const (&literal)[N]) { (void)literal; return N - 1; } +DN_API DN_USize DN_CStr8_FSize (DN_FMT_ATTRIB char const *fmt, ...); +DN_API DN_USize DN_CStr8_FVSize (DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API DN_USize DN_CStr8_Size (char const *a); +DN_API DN_USize DN_CStr16_Size (wchar_t const *a); + +// NOTE: DN_Str16 ////////////////////////////////////////////////////////////////////////////////// +#define DN_STR16(string) DN_Str16{(wchar_t *)(string), sizeof(string)/sizeof(string[0]) - 1} +#define DN_Str16_HasData(string) ((string).data && (string).size) + +#if defined(__cplusplus) +DN_API bool operator== (DN_Str16 const &lhs, DN_Str16 const &rhs); +DN_API bool operator!= (DN_Str16 const &lhs, DN_Str16 const &rhs); +#endif + +// NOTE: DN_Str8 /////////////////////////////////////////////////////////////////////////////////// +#define DN_STR8(string) DN_Str8{(char *)(string), (sizeof(string) - 1)} +#define DN_STR_FMT(string) (int)((string).size), (string).data +#define DN_Str8_Init(data, size) DN_Str8{(char *)(data), (size_t)(size)} + +DN_API DN_Str8 DN_Str8_InitCStr8 (char const *src); +#define DN_Str8_HasData(string) ((string).data && (string).size) +DN_API bool DN_Str8_IsAll (DN_Str8 string, DN_Str8IsAll is_all); + +DN_API DN_Str8 DN_Str8_InitF (DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, ...); +DN_API DN_Str8 DN_Str8_InitFV (DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API DN_Str8 DN_Str8_Alloc (DN_Arena *arena, DN_USize size, DN_ZeroMem zero_mem); +DN_API DN_Str8 DN_Str8_Copy (DN_Arena *arena, DN_Str8 string); + +DN_API char * DN_Str8_End (DN_Str8 string); +DN_API DN_Str8 DN_Str8_Slice (DN_Str8 string, DN_USize offset, DN_USize size); +DN_API DN_Str8 DN_Str8_Advance (DN_Str8 string, DN_USize amount); +DN_API DN_Str8 DN_Str8_NextLine (DN_Str8 string); +DN_API DN_Str8BinarySplitResult DN_Str8_BinarySplitArray (DN_Str8 string, DN_Str8 const *find, DN_USize find_size); +DN_API DN_Str8BinarySplitResult DN_Str8_BinarySplit (DN_Str8 string, DN_Str8 find); +DN_API DN_Str8BinarySplitResult DN_Str8_BinarySplitLastArray (DN_Str8 string, DN_Str8 const *find, DN_USize find_size); +DN_API DN_Str8BinarySplitResult DN_Str8_BinarySplitLast (DN_Str8 string, DN_Str8 find); +DN_API DN_USize DN_Str8_Split (DN_Str8 string, DN_Str8 delimiter, DN_Str8 *splits, DN_USize splits_count, DN_Str8SplitIncludeEmptyStrings mode); +DN_API DN_Slice DN_Str8_SplitAlloc (DN_Arena *arena, DN_Str8 string, DN_Str8 delimiter, DN_Str8SplitIncludeEmptyStrings mode); + +DN_API DN_Str8FindResult DN_Str8_FindStr8Array (DN_Str8 string, DN_Str8 const *find, DN_USize find_size, DN_Str8EqCase eq_case); +DN_API DN_Str8FindResult DN_Str8_FindStr8 (DN_Str8 string, DN_Str8 find, DN_Str8EqCase eq_case); +DN_API DN_Str8FindResult DN_Str8_Find (DN_Str8 string, uint32_t flags); +DN_API DN_Str8 DN_Str8_Segment (DN_Arena *arena, DN_Str8 src, DN_USize segment_size, char segment_char); +DN_API DN_Str8 DN_Str8_ReverseSegment (DN_Arena *arena, DN_Str8 src, DN_USize segment_size, char segment_char); + +DN_API bool DN_Str8_Eq (DN_Str8 lhs, DN_Str8 rhs, DN_Str8EqCase eq_case = DN_Str8EqCase_Sensitive); +DN_API bool DN_Str8_EqInsensitive (DN_Str8 lhs, DN_Str8 rhs); +DN_API bool DN_Str8_StartsWith (DN_Str8 string, DN_Str8 prefix, DN_Str8EqCase eq_case = DN_Str8EqCase_Sensitive); +DN_API bool DN_Str8_StartsWithInsensitive (DN_Str8 string, DN_Str8 prefix); +DN_API bool DN_Str8_EndsWith (DN_Str8 string, DN_Str8 prefix, DN_Str8EqCase eq_case = DN_Str8EqCase_Sensitive); +DN_API bool DN_Str8_EndsWithInsensitive (DN_Str8 string, DN_Str8 prefix); +DN_API bool DN_Str8_HasChar (DN_Str8 string, char ch); + +DN_API DN_Str8 DN_Str8_TrimPrefix (DN_Str8 string, DN_Str8 prefix, DN_Str8EqCase eq_case = DN_Str8EqCase_Sensitive); +DN_API DN_Str8 DN_Str8_TrimHexPrefix (DN_Str8 string); +DN_API DN_Str8 DN_Str8_TrimSuffix (DN_Str8 string, DN_Str8 suffix, DN_Str8EqCase eq_case = DN_Str8EqCase_Sensitive); +DN_API DN_Str8 DN_Str8_TrimAround (DN_Str8 string, DN_Str8 trim_string); +DN_API DN_Str8 DN_Str8_TrimHeadWhitespace (DN_Str8 string); +DN_API DN_Str8 DN_Str8_TrimTailWhitespace (DN_Str8 string); +DN_API DN_Str8 DN_Str8_TrimWhitespaceAround (DN_Str8 string); +DN_API DN_Str8 DN_Str8_TrimByteOrderMark (DN_Str8 string); + +DN_API DN_Str8 DN_Str8_FileNameFromPath (DN_Str8 path); +DN_API DN_Str8 DN_Str8_FileNameNoExtension (DN_Str8 path); +DN_API DN_Str8 DN_Str8_FilePathNoExtension (DN_Str8 path); +DN_API DN_Str8 DN_Str8_FileExtension (DN_Str8 path); +DN_API DN_Str8 DN_Str8_FileDirectoryFromPath (DN_Str8 path); + +DN_API DN_Str8ToU64Result DN_Str8_ToU64 (DN_Str8 string, char separator); +DN_API DN_Str8ToI64Result DN_Str8_ToI64 (DN_Str8 string, char separator); + +DN_API DN_Str8 DN_Str8_AppendF (DN_Arena *arena, DN_Str8 string, char const *fmt, ...); +DN_API DN_Str8 DN_Str8_AppendFV (DN_Arena *arena, DN_Str8 string, char const *fmt, va_list args); + +DN_API DN_Str8 DN_Str8_FillF (DN_Arena *arena, DN_USize count, char const *fmt, ...); +DN_API DN_Str8 DN_Str8_FillFV (DN_Arena *arena, DN_USize count, char const *fmt, va_list args); + +DN_API void DN_Str8_Remove (DN_Str8 *string, DN_USize offset, DN_USize size); + +DN_API DN_Str8DotTruncateResult DN_Str8_DotTruncateMiddle (DN_Arena *arena, DN_Str8 str8, uint32_t side_size, DN_Str8 truncator); + +DN_API DN_Str8 DN_Str8_Lower (DN_Arena *arena, DN_Str8 string); +DN_API DN_Str8 DN_Str8_Upper (DN_Arena *arena, DN_Str8 string); + +#if defined(__cplusplus) +DN_API bool operator== (DN_Str8 const &lhs, DN_Str8 const &rhs); +DN_API bool operator!= (DN_Str8 const &lhs, DN_Str8 const &rhs); +#endif + +// NOTE: DN_Str8Builder //////////////////////////////////////////////////////////////////////////// +DN_API DN_Str8Builder DN_Str8Builder_Init (DN_Arena *arena); +DN_API DN_Str8Builder DN_Str8Builder_InitArrayRef (DN_Arena *arena, DN_Str8 const *strings, DN_USize size); +DN_API DN_Str8Builder DN_Str8Builder_InitArrayCopy (DN_Arena *arena, DN_Str8 const *strings, DN_USize size); +template DN_Str8Builder DN_Str8Builder_InitCArrayRef (DN_Arena *arena, DN_Str8 const (&array)[N]); +template DN_Str8Builder DN_Str8Builder_InitCArrayCopy (DN_Arena *arena, DN_Str8 const (&array)[N]); + +DN_API bool DN_Str8Builder_AddArrayRef (DN_Str8Builder *builder, DN_Str8 const *strings, DN_USize size, DN_Str8BuilderAdd add); +DN_API bool DN_Str8Builder_AddArrayCopy (DN_Str8Builder *builder, DN_Str8 const *strings, DN_USize size, DN_Str8BuilderAdd add); +DN_API bool DN_Str8Builder_AddFV (DN_Str8Builder *builder, DN_Str8BuilderAdd add, DN_FMT_ATTRIB char const *fmt, va_list args); + +#define DN_Str8Builder_AppendArrayRef(builder, strings, size) DN_Str8Builder_AddArrayRef(builder, strings, size, DN_Str8BuilderAdd_Append) +#define DN_Str8Builder_AppendArrayCopy(builder, strings, size) DN_Str8Builder_AddArrayCopy(builder, strings, size, DN_Str8BuilderAdd_Append) +#define DN_Str8Builder_AppendSliceRef(builder, slice) DN_Str8Builder_AddArrayRef(builder, slice.data, slice.size, DN_Str8BuilderAdd_Append) +#define DN_Str8Builder_AppendSliceCopy(builder, slice) DN_Str8Builder_AddArrayCopy(builder, slice.data, slice.size, DN_Str8BuilderAdd_Append) +DN_API bool DN_Str8Builder_AppendRef (DN_Str8Builder *builder, DN_Str8 string); +DN_API bool DN_Str8Builder_AppendCopy (DN_Str8Builder *builder, DN_Str8 string); +#define DN_Str8Builder_AppendFV(builder, fmt, args) DN_Str8Builder_AddFV(builder, DN_Str8BuilderAdd_Append, fmt, args) +DN_API bool DN_Str8Builder_AppendF (DN_Str8Builder *builder, DN_FMT_ATTRIB char const *fmt, ...); +DN_API bool DN_Str8Builder_AppendBytesRef (DN_Str8Builder *builder, void const *ptr, DN_USize size); +DN_API bool DN_Str8Builder_AppendBytesCopy (DN_Str8Builder *builder, void const *ptr, DN_USize size); +DN_API bool DN_Str8Builder_AppendBuilderRef (DN_Str8Builder *dest, DN_Str8Builder const *src); +DN_API bool DN_Str8Builder_AppendBuilderCopy (DN_Str8Builder *dest, DN_Str8Builder const *src); + +#define DN_Str8Builder_PrependArrayRef(builder, strings, size) DN_Str8Builder_AddArrayRef(builder, strings, size, DN_Str8BuilderAdd_Prepend) +#define DN_Str8Builder_PrependArrayCopy(builder, strings, size) DN_Str8Builder_AddArrayCopy(builder, strings, size, DN_Str8BuilderAdd_Prepend) +#define DN_Str8Builder_PrependSliceRef(builder, slice) DN_Str8Builder_AddArrayRef(builder, slice.data, slice.size, DN_Str8BuilderAdd_Prepend) +#define DN_Str8Builder_PrependSliceCopy(builder, slice) DN_Str8Builder_AddArrayCopy(builder, slice.data, slice.size, DN_Str8BuilderAdd_Prepend) +DN_API bool DN_Str8Builder_PrependRef (DN_Str8Builder *builder, DN_Str8 string); +DN_API bool DN_Str8Builder_PrependCopy (DN_Str8Builder *builder, DN_Str8 string); +#define DN_Str8Builder_PrependFV(builder, fmt, args) DN_Str8Builder_AddFV(builder, DN_Str8BuilderAdd_Prepend, fmt, args) +DN_API bool DN_Str8Builder_PrependF (DN_Str8Builder *builder, DN_FMT_ATTRIB char const *fmt, ...); + +DN_API bool DN_Str8Builder_Erase (DN_Str8Builder *builder, DN_Str8 string); +DN_API DN_Str8Builder DN_Str8Builder_Copy (DN_Arena *arena, DN_Str8Builder const *builder); +DN_API DN_Str8 DN_Str8Builder_Build (DN_Str8Builder const *builder, DN_Arena *arena); +DN_API DN_Str8 DN_Str8Builder_BuildDelimited (DN_Str8Builder const *builder, DN_Str8 delimiter, DN_Arena *arena); +DN_API DN_Slice DN_Str8Builder_BuildSlice (DN_Str8Builder const *builder, DN_Arena *arena); + +// NOTE: DN_FStr8 ////////////////////////////////////////////////////////////////////////////////// +#if !defined(DN_NO_FSTR8) +template DN_FStr8 DN_FStr8_InitF (DN_FMT_ATTRIB char const *fmt, ...); +template DN_FStr8 DN_FStr8_InitFV (char const *fmt, va_list args); +template DN_USize DN_FStr8_Max (DN_FStr8 const *string); +template void DN_FStr8_Clear (DN_FStr8 *string); +template bool DN_FStr8_AddFV (DN_FStr8 *string, DN_FMT_ATTRIB char const *fmt, va_list va); +template bool DN_FStr8_AddF (DN_FStr8 *string, DN_FMT_ATTRIB char const *fmt, ...); +template bool DN_FStr8_AddCStr8 (DN_FStr8 *string, char const *value, DN_USize size); +template bool DN_FStr8_Add (DN_FStr8 *string, DN_Str8 value); +template DN_Str8 DN_FStr8_ToStr8 (DN_FStr8 const *string); +template bool DN_FStr8_Eq (DN_FStr8 const *lhs, DN_FStr8 const *rhs, DN_Str8EqCase eq_case); +template bool DN_FStr8_EqStr8 (DN_FStr8 const *lhs, DN_Str8 rhs, DN_Str8EqCase eq_case); +template bool DN_FStr8_EqInsensitive (DN_FStr8 const *lhs, DN_FStr8 const *rhs); +template bool DN_FStr8_EqStr8Insensitive (DN_FStr8 const *lhs, DN_Str8 rhs); +template bool DN_FStr8_EqFStr8 (DN_FStr8 const *lhs, DN_FStr8 const *rhs, DN_Str8EqCase eq_case); +template bool DN_FStr8_EqFStr8Insensitive (DN_FStr8 const *lhs, DN_FStr8 const *rhs); +template bool operator== (DN_FStr8 const &lhs, DN_FStr8 const &rhs); +template bool operator!= (DN_FStr8 const &lhs, DN_FStr8 const &rhs); +template bool operator== (DN_FStr8 const &lhs, DN_Str8 const &rhs); +template bool operator!= (DN_FStr8 const &lhs, DN_Str8 const &rhs); +#endif // !defined(DN_NO_FSTR8) + +// NOTE: DN_Char /////////////////////////////////////////////////////////////////////////////////// +struct DN_CharHexToU8 +{ + bool success; + uint8_t value; +}; + +DN_API bool DN_Char_IsAlphabet (char ch); +DN_API bool DN_Char_IsDigit (char ch); +DN_API bool DN_Char_IsAlphaNum (char ch); +DN_API bool DN_Char_IsWhitespace (char ch); +DN_API bool DN_Char_IsHex (char ch); +DN_API DN_CharHexToU8 DN_Char_HexToU8 (char ch); +DN_API char DN_Char_ToHex (char ch); +DN_API char DN_Char_ToHexUnchecked (char ch); +DN_API char DN_Char_ToLower (char ch); +DN_API char DN_Char_ToUpper (char ch); + +// NOTE: DN_UTF //////////////////////////////////////////////////////////////////////////////////// +DN_API int DN_UTF8_EncodeCodepoint (uint8_t utf8[4], uint32_t codepoint); +DN_API int DN_UTF16_EncodeCodepoint (uint16_t utf16[2], uint32_t codepoint); + +// NOTE: DN_Str8Builder /////////////////////////////////////////////////////////////////////////// +template +DN_Str8Builder DN_Str8Builder_InitCArrayRef(DN_Arena *arena, DN_Str8 const (&array)[N]) +{ + DN_Str8Builder result = DN_Str8Builder_InitArrayRef(arena, array, N); + return result; +} + +template +DN_Str8Builder DN_Str8Builder_InitCArrayCopy(DN_Arena *arena, DN_Str8 const (&array)[N]) +{ + DN_Str8Builder result = DN_Str8Builder_InitArrayCopy(arena, array, N); + return result; +} + +template +bool DN_Str8Builder_AddCArrayRef(DN_Str8Builder *builder, DN_Str8 const (&array)[N], DN_Str8BuilderAdd add) +{ + bool result = DN_Str8Builder_AddArrayRef(builder, array, N, add); + return result; +} + +template +bool DN_Str8Builder_AddCArrayCopy(DN_Str8Builder *builder, DN_Str8 const (&array)[N], DN_Str8BuilderAdd add) +{ + bool result = DN_Str8Builder_AddArrayCopy(builder, array, N, add); + return result; +} + +#if !defined(DN_NO_FSTR8) +// NOTE: DN_FStr8 ////////////////////////////////////////////////////////////////////////////////// +template +DN_FStr8 DN_FStr8_InitF(DN_FMT_ATTRIB char const *fmt, ...) +{ + DN_FStr8 result = {}; + if (fmt) { + va_list args; + va_start(args, fmt); + DN_FStr8_AddFV(&result, fmt, args); + va_end(args); + } + return result; +} + +template +DN_FStr8 DN_FStr8_InitFV(char const *fmt, va_list args) +{ + DN_FStr8 result = {}; + DN_FStr8_AddFV(&result, fmt, args); + return result; +} + +template +DN_USize DN_FStr8_Max(DN_FStr8 const *) +{ + DN_USize result = N; + return result; +} + +template +void DN_FStr8_Clear(DN_FStr8 *string) +{ + *string = {}; +} + +template +bool DN_FStr8_AddFV(DN_FStr8 *string, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + bool result = false; + if (!string || !fmt) + return result; + + DN_USize require = DN_CStr8_FVSize(fmt, args) + 1 /*null_terminate*/; + DN_USize space = (N + 1) - string->size; + result = require <= space; + string->size += DN_VSNPrintF(string->data + string->size, DN_CAST(int) space, fmt, args); + + // NOTE: snprintf returns the required size of the format string + // irrespective of if there's space or not. + string->size = DN_Min(string->size, N); + return result; +} + +template +bool DN_FStr8_AddF(DN_FStr8 *string, DN_FMT_ATTRIB char const *fmt, ...) +{ + bool result = false; + if (!string || !fmt) + return result; + va_list args; + va_start(args, fmt); + result = DN_FStr8_AddFV(string, fmt, args); + va_end(args); + return result; +} + +template +bool DN_FStr8_AddCStr8(DN_FStr8 *string, char const *src, DN_USize size) +{ + DN_Assert(string->size <= N); + bool result = false; + if (!string || !src || size == 0 || string->size >= N) + return result; + + DN_USize space = N - string->size; + result = size <= space; + DN_Memcpy(string->data + string->size, src, DN_Min(space, size)); + string->size = DN_Min(string->size + size, N); + string->data[string->size] = 0; + return result; +} + +template +bool DN_FStr8_Add(DN_FStr8 *string, DN_Str8 src) +{ + bool result = DN_FStr8_AddCStr8(string, src.data, src.size); + return result; +} + +template +DN_Str8 DN_FStr8_ToStr8(DN_FStr8 const *string) +{ + DN_Str8 result = {}; + if (!string || string->size <= 0) + return result; + + result.data = DN_CAST(char *) string->data; + result.size = string->size; + return result; +} + +template +bool DN_FStr8_Eq(DN_FStr8 const *lhs, DN_FStr8 const *rhs, DN_Str8EqCase eq_case) +{ + DN_Str8 lhs_s8 = DN_FStr8_ToStr8(lhs); + DN_Str8 rhs_s8 = DN_FStr8_ToStr8(rhs); + bool result = DN_Str8_Eq(lhs_s8, rhs_s8, eq_case); + return result; +} + +template +bool DN_FStr8_EqStr8(DN_FStr8 const *lhs, DN_Str8 rhs, DN_Str8EqCase eq_case) +{ + DN_Str8 lhs_s8 = DN_FStr8_ToStr8(lhs); + bool result = DN_Str8_Eq(lhs_s8, rhs, eq_case); + return result; +} + +template +bool DN_FStr8_EqInsensitive(DN_FStr8 const *lhs, DN_FStr8 const *rhs) +{ + DN_Str8 lhs_s8 = DN_FStr8_ToStr8(lhs); + DN_Str8 rhs_s8 = DN_FStr8_ToStr8(rhs); + bool result = DN_Str8_Eq(lhs_s8, rhs_s8, DN_Str8EqCase_Insensitive); + return result; +} + +template +bool DN_FStr8_EqStr8Insensitive(DN_FStr8 const *lhs, DN_Str8 rhs) +{ + DN_Str8 lhs_s8 = DN_FStr8_ToStr8(lhs); + bool result = DN_Str8_Eq(lhs_s8, rhs, DN_Str8EqCase_Insensitive); + return result; +} + +template +bool DN_FStr8_EqFStr8(DN_FStr8 const *lhs, DN_FStr8 const *rhs, DN_Str8EqCase eq_case) +{ + DN_Str8 lhs_s8 = DN_FStr8_ToStr8(lhs); + DN_Str8 rhs_s8 = DN_FStr8_ToStr8(rhs); + bool result = DN_Str8_Eq(lhs_s8, rhs_s8, eq_case); + return result; +} + +template +bool DN_FStr8_EqFStr8Insensitive(DN_FStr8 const *lhs, DN_FStr8 const *rhs) +{ + DN_Str8 lhs_s8 = DN_FStr8_ToStr8(lhs); + DN_Str8 rhs_s8 = DN_FStr8_ToStr8(rhs); + bool result = DN_Str8_Eq(lhs_s8, rhs_s8, DN_Str8EqCase_Insensitive); + return result; +} + +template +bool operator==(DN_FStr8 const &lhs, DN_FStr8 const &rhs) +{ + bool result = DN_FStr8_Eq(&lhs, &rhs, DN_Str8EqCase_Sensitive); + return result; +} + +template +bool operator!=(DN_FStr8 const &lhs, DN_FStr8 const &rhs) +{ + bool result = !(lhs == rhs); + return result; +} + +template +bool operator==(DN_FStr8 const &lhs, DN_Str8 const &rhs) +{ + bool result = DN_Str8_Eq(DN_FStr8_ToStr8(&lhs), rhs, DN_Str8EqCase_Insensitive); + return result; +} + +template +bool operator!=(DN_FStr8 const &lhs, DN_Str8 const &rhs) +{ + bool result = !(lhs == rhs); + return result; +} +#endif // !defined(DN_NO_FSTR8) +#endif // !defined(DN_BASE_STRING_H) +// DN: Single header generator inlined this file => #include "Base/dn_base_containers.h" +#if !defined(DN_CONTAINERS_H) +#define DN_CONTAINERS_H + +// DN: Single header generator commented out this header => #include "../dn_base_inc.h" + +struct DN_Ring +{ + DN_U64 size; + char *base; + DN_U64 write_pos; + DN_U64 read_pos; +}; + +// NOTE: DN_CArray ///////////////////////////////////////////////////////////////////////////////// +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 +}; + +template struct DN_ArrayFindResult +{ + T *data; // Pointer to the value if a match is found, null pointer otherwise + DN_USize index; // Index to the value if a match is found, 0 otherwise +}; + +#if !defined(DN_NO_SARRAY) +// NOTE: DN_SArray ///////////////////////////////////////////////////////////////////////////////// +template struct DN_SArray +{ + T *data; // Pointer to the start of the array items in the block of memory + DN_USize size; // Number of items currently in the array + DN_USize max; // Maximum number of items this array can store + + T *begin() { return data; } + T *end () { return data + size; } + T const *begin() const { return data; } + T const *end () const { return data + size; } +}; +#endif // !defined(DN_NO_SARRAY) + +#if !defined(DN_NO_FARRAY) +// NOTE: DN_FArray ///////////////////////////////////////////////////////////////////////////////// +template struct DN_FArray +{ + T data[N]; // Pointer to the start of the array items in the block of memory + DN_USize size; // Number of items currently in the array + + T *begin() { return data; } + T *end () { return data + size; } + T const *begin() const { return data; } + T const *end () const { return data + size; } +}; + +template using DN_FArray8 = DN_FArray; +template using DN_FArray16 = DN_FArray; +template using DN_FArray32 = DN_FArray; +template using DN_FArray64 = DN_FArray; +#endif // !defined(DN_NO_FARRAY) + +#if !defined(DN_NO_DSMAP) +// NOTE: DN_DSMap ////////////////////////////////////////////////////////////////////////////////// +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; +}; +#endif // !defined(DN_NO_DSMAP) + +#if !defined(DN_NO_LIST) +// NOTE: DN_List /////////////////////////////////////////////////////////////////////////////////// +template struct DN_ListChunk +{ + T *data; + DN_USize size; + DN_USize count; + DN_ListChunk *next; + DN_ListChunk *prev; +}; + +template struct DN_ListIterator +{ + DN_B32 init; // True if DN_List_Iterate has been called at-least once on this iterator + DN_ListChunk *chunk; // The chunk that the iterator is reading from + DN_USize chunk_data_index; // The index in the chunk the iterator is referencing + DN_USize index; // The index of the item in the list as if it was flat array + T *data; // Pointer to the data the iterator is referencing. Nullptr if invalid. +}; + +template struct DN_List +{ + DN_USize count; // Cumulative count of all items made across all list chunks + DN_USize chunk_size; // When new ListChunk's are required, the minimum 'data' entries to allocate for that node. + DN_ListChunk *head; + DN_ListChunk *tail; +}; +#endif // !defined(DN_NO_LIST) + +// NOTE: 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. +// +// LArray => Literal Array +// Define a C array and size: +// +// ``` +// MyStruct buffer[TB_ASType_Count] = {}; +// DN_USize size = 0; +// MyStruct *item = DN_LArray_Make(buffer, size, DN_ArrayCountU(buffer), DN_ZeroMem_No); +// ``` +// +// IArray => Intrusive Array +// Define a struct with the members 'data', 'size' and 'max': +// +// ``` +// struct MyArray { +// MyStruct *data; +// DN_USize size; +// DN_USize max; +// } my_array = {}; +// +// MyStruct *item = DN_IArray_Make(&my_array, MyArray, DN_ZeroMem_No); +// ``` +// ISLList => Intrusive Singly Linked List +// Define a struct with the members 'next': +// +// ``` +// struct MyLinkItem { +// int data; +// MyLinkItem *next; +// } my_link = {}; +// +// MyLinkItem *first_item = DN_ISLList_Detach(&my_link, MyLinkItem); +// ``` + +#define DN_ISLList_Detach(list) (decltype(list)) DN_CSLList_Detach((void **)&(list), (void **)&(list)->next) + +#define DN_LArray_MakeArray(c_array, size, max, count, zero_mem) (decltype(&(c_array)[0])) DN_CArray2_MakeArray(c_array, size, max, sizeof((c_array)[0]), count, zero_mem) +#define DN_LArray_MakeArrayZ(c_array, size, max, count) (decltype(&(c_array)[0])) DN_CArray2_MakeArray(c_array, size, max, sizeof((c_array)[0]), count, DN_ZeroMem_Yes) +#define DN_LArray_Make(c_array, size, max, zero_mem) (decltype(&(c_array)[0])) DN_CArray2_MakeArray(c_array, size, max, sizeof((c_array)[0]), 1, zero_mem) +#define DN_LArray_MakeZ(c_array, size, max) (decltype(&(c_array)[0])) DN_CArray2_MakeArray(c_array, size, max, sizeof((c_array)[0]), 1, DN_ZeroMem_Yes) +#define DN_LArray_AddArray(c_array, size, max, items, count, add) (decltype(&(c_array)[0])) DN_CArray2_AddArray (c_array, size, max, sizeof((c_array)[0]), items, count, add) +#define DN_LArray_Add(c_array, size, max, item, add) (decltype(&(c_array)[0])) DN_CArray2_AddArray (c_array, size, max, sizeof((c_array)[0]), &item, 1, add) +#define DN_LArray_AppendArray(c_array, size, max, items, count) (decltype(&(c_array)[0])) DN_CArray2_AddArray (c_array, size, max, sizeof((c_array)[0]), items, count, DN_ArrayAdd_Append) +#define DN_LArray_Append(c_array, size, max, item) (decltype(&(c_array)[0])) DN_CArray2_AddArray (c_array, size, max, sizeof((c_array)[0]), &item, 1, DN_ArrayAdd_Append) +#define DN_LArray_PrependArray(c_array, size, max, items, count) (decltype(&(c_array)[0])) DN_CArray2_AddArray (c_array, size, max, sizeof((c_array)[0]), items, count, DN_ArrayAdd_Prepend) +#define DN_LArray_Prepend(c_array, size, max, item) (decltype(&(c_array)[0])) DN_CArray2_AddArray (c_array, size, max, sizeof((c_array)[0]), &item, 1, DN_ArrayAdd_Prepend) +#define DN_LArray_EraseRange(c_array, size, begin_index, count, erase) DN_CArray2_EraseRange(c_array, size, sizeof((c_array)[0]), begin_index, count, erase) + +#define DN_IArray_Front(array) (array)->data +#define DN_IArray_GrowIfNeededFromPool(array, pool) DN_CArray2_GrowIfNeededFromPool((void **)(&(array)->data), (array)->size, &(array)->max, sizeof((array)->data[0]), pool) +#define DN_IArray_MakeArray(array, count, zero_mem) (decltype(&((array)->data)[0])) DN_CArray2_MakeArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), count, zero_mem) +#define DN_IArray_MakeArrayZ(array, count) (decltype(&((array)->data)[0])) DN_CArray2_MakeArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), count, DN_ZeroMem_Yes) +#define DN_IArray_Make(array, zero_mem) (decltype(&((array)->data)[0])) DN_CArray2_MakeArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), 1, zero_mem) +#define DN_IArray_MakeZ(array) (decltype(&((array)->data)[0])) DN_CArray2_MakeArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), 1, DN_ZeroMem_Yes) +#define DN_IArray_AddArray(array, items, count, add) (decltype(&((array)->data)[0])) DN_CArray2_AddArray ((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), items, count, add) +#define DN_IArray_Add(array, item, add) (decltype(&((array)->data)[0])) DN_CArray2_AddArray ((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), &item, 1, add) +#define DN_IArray_AppendArray(array, items, count) (decltype(&((array)->data)[0])) DN_CArray2_AddArray ((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), items, count, DN_ArrayAdd_Append) +#define DN_IArray_Append(array, item) (decltype(&((array)->data)[0])) DN_CArray2_AddArray ((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), &item, 1, DN_ArrayAdd_Append) +#define DN_IArray_PrependArray(array, items, count) (decltype(&((array)->data)[0])) DN_CArray2_AddArray ((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), items, count, DN_ArrayAdd_Prepend) +#define DN_IArray_Prepend(array, item) (decltype(&((array)->data)[0])) DN_CArray2_AddArray ((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), &item, 1, DN_ArrayAdd_Prepend) +#define DN_IArray_EraseRange(array, size, begin_index, count, erase) DN_CArray2_EraseRange((array)->data, &(array)->size, sizeof(((array)->data)[0]), begin_index, count, erase) + +DN_API DN_ArrayEraseResult DN_CArray2_EraseRange (void *data, DN_USize *size, DN_USize elem_size, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase); +DN_API void *DN_CArray2_MakeArray (void *data, DN_USize *size, DN_USize max, DN_USize data_size, DN_USize make_size, DN_ZeroMem zero_mem); +DN_API void *DN_CArray2_AddArray (void *data, DN_USize *size, DN_USize max, DN_USize data_size, void *elems, DN_USize elems_count, DN_ArrayAdd add); +DN_API bool DN_CArray2_GrowIfNeededFromPool (void **data, DN_USize size, DN_USize *max, DN_USize data_size, DN_Pool *pool); +DN_API void *DN_CSLList_Detach (void **link, void **next); + +DN_API bool DN_Ring_HasSpace (DN_Ring const *ring, DN_U64 size); +DN_API bool DN_Ring_HasData (DN_Ring const *ring, DN_U64 size); +DN_API void DN_Ring_Write (DN_Ring *ring, void const *src, DN_U64 src_size); +#define DN_Ring_WriteStruct(ring, item) DN_Ring_Write((ring), (item), sizeof(*(item))) +DN_API void DN_Ring_Read (DN_Ring *ring, void *dest, DN_U64 dest_size); +#define DN_Ring_ReadStruct(ring, dest) DN_Ring_Read((ring), (dest), sizeof(*(dest))) + +template DN_ArrayEraseResult DN_CArray_EraseRange (T *data, DN_USize *size, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase); +template T * DN_CArray_MakeArray (T *data, DN_USize *size, DN_USize max, DN_USize count, DN_ZeroMem zero_mem); +template T * DN_CArray_InsertArray (T *data, DN_USize *size, DN_USize max, DN_USize index, T const *items, DN_USize count); +template T DN_CArray_PopFront (T *data, DN_USize *size, DN_USize count); +template T DN_CArray_PopBack (T *data, DN_USize *size, DN_USize count); +template DN_ArrayFindResult DN_CArray_Find (T *data, DN_USize size, T const &value); + +// NOTE: DN_SArray ///////////////////////////////////////////////////////////////////////////////// +#if !defined(DN_NO_SARRAY) +template DN_SArray DN_SArray_Init (DN_Arena *arena, DN_USize size, DN_ZeroMem zero_mem); +template DN_SArray DN_SArray_InitSlice (DN_Arena *arena, DN_Slice slice, DN_USize size, DN_ZeroMem zero_mem); +template DN_SArray DN_SArray_InitCArray (DN_Arena *arena, T const (&array)[N], DN_USize size, DN_ZeroMem); +template DN_SArray DN_SArray_InitBuffer (T* buffer, DN_USize size); +template bool DN_SArray_IsValid (DN_SArray const *array); +template DN_Slice DN_SArray_Slice (DN_SArray const *array); +template T * DN_SArray_AddArray (DN_SArray *array, T const *items, DN_USize count); +template T * DN_SArray_AddCArray (DN_SArray *array, T const (&items)[N]); +template T * DN_SArray_Add (DN_SArray *array, T const &item); +#define DN_SArray_AddArrayAssert(...) DN_HardAssert(DN_SArray_AddArray(__VA_ARGS__)) +#define DN_SArray_AddCArrayAssert(...) DN_HardAssert(DN_SArray_AddCArray(__VA_ARGS__)) +#define DN_SArray_AddAssert(...) DN_HardAssert(DN_SArray_Add(__VA_ARGS__)) +template T * DN_SArray_MakeArray (DN_SArray *array, DN_USize count, DN_ZeroMem zero_mem); +template T * DN_SArray_Make (DN_SArray *array, DN_ZeroMem zero_mem); +#define DN_SArray_MakeArrayAssert(...) DN_HardAssert(DN_SArray_MakeArray(__VA_ARGS__)) +#define DN_SArray_MakeAssert(...) DN_HardAssert(DN_SArray_Make(__VA_ARGS__)) +template T * DN_SArray_InsertArray (DN_SArray *array, DN_USize index, T const *items, DN_USize count); +template T * DN_SArray_InsertCArray (DN_SArray *array, DN_USize index, T const (&items)[N]); +template T * DN_SArray_Insert (DN_SArray *array, DN_USize index, T const &item); +#define DN_SArray_InsertArrayAssert(...) DN_HardAssert(DN_SArray_InsertArray(__VA_ARGS__)) +#define DN_SArray_InsertCArrayAssert(...) DN_HardAssert(DN_SArray_InsertCArray(__VA_ARGS__)) +#define DN_SArray_InsertAssert(...) DN_HardAssert(DN_SArray_Insert(__VA_ARGS__)) +template T DN_SArray_PopFront (DN_SArray *array, DN_USize count); +template T DN_SArray_PopBack (DN_SArray *array, DN_USize count); +template DN_ArrayEraseResult DN_SArray_EraseRange (DN_SArray *array, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase); +template void DN_SArray_Clear (DN_SArray *array); +#endif // !defined(DN_NO_SARRAY) + +#if !defined(DN_NO_FARRAY) +#define DN_FArray_ToSArray(array) DN_SArray_InitBuffer((array)->data, DN_ArrayCountU((array)->data)) +template DN_FArray DN_FArray_Init (T const *array, DN_USize count); +#define DN_FArray_HasData(array) ((array).data && (array).size) +template DN_FArray DN_FArray_InitSlice (DN_Slice slice); +template DN_FArray DN_FArray_InitCArray (T const (&items)[K]); +template bool DN_FArray_IsValid (DN_FArray const *array); +template DN_USize DN_FArray_Max (DN_FArray const *) { return N; } +template DN_Slice DN_FArray_Slice (DN_FArray const *array); +template T * DN_FArray_AddArray (DN_FArray *array, T const *items, DN_USize count); +template T * DN_FArray_AddCArray (DN_FArray *array, T const (&items)[K]); +template T * DN_FArray_Add (DN_FArray *array, T const &item); +#define DN_FArray_AddArrayAssert(...) DN_HardAssert(DN_FArray_AddArray(__VA_ARGS__)) +#define DN_FArray_AddCArrayAssert(...) DN_HardAssert(DN_FArray_AddCArray(__VA_ARGS__)) +#define DN_FArray_AddAssert(...) DN_HardAssert(DN_FArray_Add(__VA_ARGS__)) +template T * DN_FArray_MakeArray (DN_FArray *array, DN_USize count, DN_ZeroMem zero_mem); +template T * DN_FArray_Make (DN_FArray *array, DN_ZeroMem zero_mem); +#define DN_FArray_MakeArrayAssert(...) DN_HardAssert(DN_FArray_MakeArray(__VA_ARGS__)) +#define DN_FArray_MakeAssert(...) DN_HardAssert(DN_FArray_Make(__VA_ARGS__)) +template T * DN_FArray_InsertArray (DN_FArray *array, T const &item, DN_USize index); +template T * DN_FArray_InsertCArray (DN_FArray *array, DN_USize index, T const (&items)[K]); +template T * DN_FArray_Insert (DN_FArray *array, DN_USize index, T const &item); +#define DN_FArray_InsertArrayAssert(...) DN_HardAssert(DN_FArray_InsertArray(__VA_ARGS__)) +#define DN_FArray_InsertAssert(...) DN_HardAssert(DN_FArray_Insert(__VA_ARGS__)) +template T DN_FArray_PopFront (DN_FArray *array, DN_USize count); +template T DN_FArray_PopBack (DN_FArray *array, DN_USize count); +template DN_ArrayFindResult DN_FArray_Find (DN_FArray *array, T const &find); +template DN_ArrayEraseResult DN_FArray_EraseRange (DN_FArray *array, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase); +template void DN_FArray_Clear (DN_FArray *array); +#endif // !defined(DN_NO_FARRAY) + +#if !defined(DN_NO_SLICE) +#define DN_TO_SLICE(val) DN_Slice_Init((val)->data, (val)->size) +#define DN_Slice_InitCArray(array) DN_Slice_Init(array, DN_ArrayCountU(array)) +template DN_Slice DN_Slice_Init (T* const data, DN_USize size); +template DN_Slice DN_Slice_InitCArrayCopy (DN_Arena *arena, T const (&array)[N]); +template DN_Slice DN_Slice_Copy (DN_Arena *arena, DN_Slice slice); +template DN_Slice DN_Slice_CopyPtr (DN_Arena *arena, T* const data, DN_USize size); +template DN_Slice DN_Slice_Alloc (DN_Arena *arena, DN_USize size, DN_ZeroMem zero_mem); + DN_Str8 DN_Slice_Str8Render (DN_Arena *arena, DN_Slice array, DN_Str8 separator); + DN_Str8 DN_Slice_Str8RenderSpaceSeparated (DN_Arena *arena, DN_Slice array); + DN_Str16 DN_Slice_Str16Render (DN_Arena *arena, DN_Slice array, DN_Str16 separator); + DN_Str16 DN_Slice_Str16RenderSpaceSeparated(DN_Arena *arena, DN_Slice array); +#endif // !defined(DN_NO_SLICE) + +#if !defined(DN_NO_DSMAP) +template DN_DSMap DN_DSMap_Init (DN_Arena *arena, DN_U32 size, DN_DSMapFlags flags); +template void DN_DSMap_Deinit (DN_DSMap *map, DN_ZeroMem zero_mem); +template bool DN_DSMap_IsValid (DN_DSMap const *map); +template DN_U32 DN_DSMap_Hash (DN_DSMap const *map, DN_DSMapKey key); +template DN_U32 DN_DSMap_HashToSlotIndex (DN_DSMap const *map, DN_DSMapKey key); +template DN_DSMapResult DN_DSMap_Find (DN_DSMap const *map, DN_DSMapKey key); +template DN_DSMapResult DN_DSMap_Make (DN_DSMap *map, DN_DSMapKey key); +template DN_DSMapResult DN_DSMap_Set (DN_DSMap *map, DN_DSMapKey key, T const &value); +template DN_DSMapResult DN_DSMap_FindKeyU64 (DN_DSMap const *map, DN_U64 key); +template DN_DSMapResult DN_DSMap_MakeKeyU64 (DN_DSMap *map, DN_U64 key); +template DN_DSMapResult DN_DSMap_SetKeyU64 (DN_DSMap *map, DN_U64 key, T const &value); +template DN_DSMapResult DN_DSMap_FindKeyStr8 (DN_DSMap const *map, DN_Str8 key); +template DN_DSMapResult DN_DSMap_MakeKeyStr8 (DN_DSMap *map, DN_Str8 key); +template DN_DSMapResult DN_DSMap_SetKeyStr8 (DN_DSMap *map, DN_Str8 key, T const &value); +template bool DN_DSMap_Resize (DN_DSMap *map, DN_U32 size); +template bool DN_DSMap_Erase (DN_DSMap *map, DN_DSMapKey key); +template bool DN_DSMap_EraseKeyU64 (DN_DSMap *map, DN_U64 key); +template bool DN_DSMap_EraseKeyStr8 (DN_DSMap *map, DN_Str8 key); +template DN_DSMapKey DN_DSMap_KeyBuffer (DN_DSMap const *map, void const *data, DN_U32 size); +template DN_DSMapKey DN_DSMap_KeyBufferAsU64NoHash (DN_DSMap const *map, void const *data, DN_U32 size); +template DN_DSMapKey DN_DSMap_KeyU64 (DN_DSMap const *map, DN_U64 u64); +template DN_DSMapKey DN_DSMap_KeyStr8 (DN_DSMap const *map, DN_Str8 string); +#define DN_DSMap_KeyCStr8(map, string) DN_DSMap_KeyBuffer(map, string, sizeof((string))/sizeof((string)[0]) - 1) +DN_API DN_DSMapKey DN_DSMap_KeyU64NoHash (DN_U64 u64); +DN_API bool DN_DSMap_KeyEquals (DN_DSMapKey lhs, DN_DSMapKey rhs); +DN_API bool operator== (DN_DSMapKey lhs, DN_DSMapKey rhs); +#endif // !defined(DN_NO_DSMAP) + +#if !defined(DN_NO_LIST) +template DN_List DN_List_Init (DN_USize chunk_size); +template DN_List DN_List_InitCArray (DN_Arena *arena, DN_USize chunk_size, T const (&array)[N]); +template T * DN_List_At (DN_List *list, DN_USize index, DN_ListChunk **at_chunk); +template void DN_List_Clear (DN_List *list); +template bool DN_List_Iterate (DN_List *list, DN_ListIterator *it, DN_USize start_index); +template T * DN_List_MakeArena (DN_List *list, DN_Arena *arena, DN_USize count); +template T * DN_List_MakePool (DN_List *list, DN_Pool *pool, DN_USize count); +template T * DN_List_AddArena (DN_List *list, DN_Arena *arena, T const &value); +template T * DN_List_AddPool (DN_List *list, DN_Pool *pool, T const &value); +template void DN_List_AddListArena (DN_List *list, DN_Arena *arena, DN_List other); +template void DN_List_AddListArena (DN_List *list, DN_Pool *pool, DN_List other); +template DN_Slice DN_List_ToSliceCopy (DN_List const *list, DN_Arena* arena); +#endif // !defined(DN_NO_LIST) +#endif // !defined(DN_CONTAINER_H) +// DN: Single header generator inlined this file => #include "Base/dn_base_convert.h" +#if !defined(DN_BASE_CONVERT_H) +#define DN_BASE_CONVERT_H + +struct DN_CVTU64Str8 +{ + char data[27 + 1]; // NOTE(dn): 27 is the maximum size of DN_U64 including a separator + DN_U8 size; +}; + +enum DN_CVTU64ByteSizeType +{ + DN_CVTU64ByteSizeType_B, + DN_CVTU64ByteSizeType_KiB, + DN_CVTU64ByteSizeType_MiB, + DN_CVTU64ByteSizeType_GiB, + DN_CVTU64ByteSizeType_TiB, + DN_CVTU64ByteSizeType_Count, + DN_CVTU64ByteSizeType_Auto, +}; + +struct DN_CVTU64ByteSize +{ + DN_CVTU64ByteSizeType type; + DN_Str8 suffix; // "KiB", "MiB", "GiB" .. e.t.c + DN_F64 bytes; +}; + +struct DN_CVTU64HexStr8 +{ + char data[2 /*0x*/ + 16 /*hex*/ + 1 /*null-terminator*/]; + DN_U8 size; +}; + +typedef DN_U32 DN_CVTHexU64Str8Flags; +enum DN_CVTHexU64Str8Flags_ +{ + DN_CVTHexU64Str8Flags_Nil = 0, + DN_CVTHexU64Str8Flags_0xPrefix = 1 << 0, /// Add the '0x' prefix from the string + DN_CVTHexU64Str8Flags_UppercaseHex = 1 << 1, /// Use uppercase ascii characters for hex +}; + +typedef DN_U32 DN_CVTU64AgeUnit; +enum DN_CVTU64AgeUnit_ +{ + DN_CVTU64AgeUnit_Sec = 1 << 0, + DN_CVTU64AgeUnit_Min = 1 << 1, + DN_CVTU64AgeUnit_Hr = 1 << 2, + DN_CVTU64AgeUnit_Day = 1 << 3, + DN_CVTU64AgeUnit_Week = 1 << 4, + DN_CVTU64AgeUnit_Year = 1 << 5, + DN_CVTU64AgeUnit_HMS = DN_CVTU64AgeUnit_Sec | DN_CVTU64AgeUnit_Min | DN_CVTU64AgeUnit_Hr, + DN_CVTU64AgeUnit_All = DN_CVTU64AgeUnit_HMS | DN_CVTU64AgeUnit_Day | DN_CVTU64AgeUnit_Week | DN_CVTU64AgeUnit_Year, +}; + +DN_API int DN_CVT_FmtBuffer3DotTruncate (char *buffer, int size, DN_FMT_ATTRIB char const *fmt, ...); +DN_API DN_CVTU64Str8 DN_CVT_U64ToStr8 (DN_U64 val, char separator); +DN_API DN_CVTU64ByteSize DN_CVT_U64ToByteSize (DN_U64 bytes, DN_CVTU64ByteSizeType type); +DN_API DN_Str8 DN_CVT_U64ToByteSizeStr8 (DN_Arena *arena, DN_U64 bytes, DN_CVTU64ByteSizeType desired_type); +DN_API DN_Str8 DN_CVT_U64ByteSizeTypeString (DN_CVTU64ByteSizeType type); +DN_API DN_Str8 DN_CVT_U64ToAge (DN_Arena *arena, DN_U64 age_s, DN_CVTU64AgeUnit unit); +DN_API DN_Str8 DN_CVT_F64ToAge (DN_Arena *arena, DN_F64 age_s, DN_CVTU64AgeUnit unit); + +DN_API DN_U64 DN_CVT_HexToU64 (DN_Str8 hex); +DN_API DN_Str8 DN_CVT_U64ToHex (DN_Arena *arena, DN_U64 number, DN_CVTHexU64Str8Flags flags); +DN_API DN_CVTU64HexStr8 DN_CVT_U64ToHexStr8 (DN_U64 number, DN_U32 flags); + +DN_API bool DN_CVT_BytesToHexPtr (void const *src, DN_USize src_size, char *dest, DN_USize dest_size); +DN_API DN_Str8 DN_CVT_BytesToHex (DN_Arena *arena, void const *src, DN_USize size); +#define DN_CVT_BytesToHexFromTLS(...) DN_CVT_BytesToHex(DN_OS_TLSTopArena(), __VA_ARGS__) + +DN_API DN_USize DN_CVT_HexToBytesPtrUnchecked (DN_Str8 hex, void *dest, DN_USize dest_size); +DN_API DN_USize DN_CVT_HexToBytesPtr (DN_Str8 hex, void *dest, DN_USize dest_size); +DN_API DN_Str8 DN_CVT_HexToBytesUnchecked (DN_Arena *arena, DN_Str8 hex); +#define DN_CVT_HexToBytesUncheckedFromTLS(...) DN_CVT_HexToBytesUnchecked(DN_OS_TLSTopArena(), __VA_ARGS__) +DN_API DN_Str8 DN_CVT_HexToBytes (DN_Arena *arena, DN_Str8 hex); +#define DN_CVT_HexToBytesFromTLS(...) DN_CVT_HexToBytes(DN_OS_TLSTopArena(), __VA_ARGS__) +#endif // defined(DN_BASE_CONVERT_H) + +#endif // !defined(DN_BASE_INC_H) +#if !defined(DN_OS_INC_H) +#define DN_OS_INC_H + +#if defined(DN_PLATFORM_WIN32) + // DN: Single header generator inlined this file => #include "OS/dn_os_windows.h" +#if !defined(DN_OS_WINDOWS_H) +#define DN_OS_WINDOWS_H + +#if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL) + #pragma comment(lib, "bcrypt") + #pragma comment(lib, "winhttp") + #pragma comment(lib, "dbghelp") + #pragma comment(lib, "comdlg32") + #pragma comment(lib, "pathcch") + #pragma comment(lib, "Shell32") // ShellExecuteW + #pragma comment(lib, "shlwapi") +#endif + +#if defined(DN_NO_WINDOWS_H_REPLACEMENT_HEADER) || defined(_INC_WINDOWS) + #define WIN32_LEAN_AND_MEAN + #include // LONG + #include // DN_OS_SecureRNGBytes -> BCryptOpenAlgorithmProvider ... etc + #include // DN_Win_MakeProcessDPIAware -> SetProcessDpiAwareProc + #include // PathRelativePathTO + #include // PathCchCanonicalizeEx + #include // WinHttp* + #include // PROCESS_MEMORY_COUNTERS_EX2 + #include // OPENFILENAMEW + #include +#else + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(4201) // warning C4201: nonstandard extension used: nameless struct/union + + // NOTE: basetsd.h ///////////////////////////////////////////////////////////////////////////// + typedef unsigned __int64 ULONG_PTR, *PULONG_PTR; + typedef unsigned __int64 UINT_PTR, *PUINT_PTR; + typedef ULONG_PTR SIZE_T, *PSIZE_T; + typedef __int64 LONG_PTR, *PLONG_PTR; + typedef ULONG_PTR DWORD_PTR, *PDWORD_PTR; + typedef unsigned __int64 ULONG64, *PULONG64; + typedef unsigned __int64 DWORD64, *PDWORD64; + + // NOTE: shared/minwindef.h //////////////////////////////////////////////////////////////////// + struct HINSTANCE__ { + int unused; + }; + typedef struct HINSTANCE__ *HINSTANCE; + + typedef unsigned long DWORD; + typedef int BOOL; + typedef int INT; + typedef unsigned long ULONG; + typedef unsigned int UINT; + typedef unsigned short WORD; + typedef unsigned char BYTE; + typedef unsigned char UCHAR; + typedef HINSTANCE HMODULE; /* HMODULEs can be used in place of HINSTANCEs */ + typedef void * HANDLE; + typedef HANDLE HLOCAL; + + typedef unsigned __int64 WPARAM; + typedef LONG_PTR LPARAM; + typedef LONG_PTR LRESULT; + + #define MAX_PATH 260 + + typedef struct _FILETIME { + DWORD dwLowDateTime; + DWORD dwHighDateTime; + } FILETIME, *PFILETIME, *LPFILETIME; + + // NOTE: shared/winerror.h ///////////////////////////////////////////////////////////////////// + // NOTE: GetModuleFileNameW + #define ERROR_INSUFFICIENT_BUFFER 122L // dderror + + // NOTE: um/winnls.h /////////////////////////////////////////////////////////////////////////// + // NOTE: MultiByteToWideChar + #define CP_UTF8 65001 // UTF-8 translation + + // NOTE: um/winnt.h //////////////////////////////////////////////////////////////////////////// + typedef void VOID; + typedef __int64 LONGLONG; + typedef unsigned __int64 ULONGLONG; + typedef void * HANDLE; + typedef char CHAR; + typedef short SHORT; + typedef long LONG; + typedef wchar_t WCHAR; // wc, 16-bit UNICODE character + typedef CHAR * NPSTR, *LPSTR, *PSTR; + typedef WCHAR * NWPSTR, *LPWSTR, *PWSTR; + typedef long HRESULT; + + // NOTE: VirtualAlloc: Allocation Type + #define MEM_RESERVE 0x00002000 + #define MEM_COMMIT 0x00001000 + #define MEM_DECOMMIT 0x00004000 + #define MEM_RELEASE 0x00008000 + + // NOTE: VirtualAlloc: Page Permissions + #define PAGE_NOACCESS 0x01 + #define PAGE_READONLY 0x02 + #define PAGE_READWRITE 0x04 + #define PAGE_GUARD 0x100 + + // NOTE: HeapAlloc + #define HEAP_ZERO_MEMORY 0x00000008 + #define HEAP_NO_SERIALIZE 0x00000001 + #define HEAP_GROWABLE 0x00000002 + #define HEAP_GENERATE_EXCEPTIONS 0x00000004 + #define HEAP_ZERO_MEMORY 0x00000008 + #define HEAP_REALLOC_IN_PLACE_ONLY 0x00000010 + #define HEAP_TAIL_CHECKING_ENABLED 0x00000020 + #define HEAP_FREE_CHECKING_ENABLED 0x00000040 + #define HEAP_DISABLE_COALESCE_ON_FREE 0x00000080 + #define HEAP_CREATE_ALIGN_16 0x00010000 + #define HEAP_CREATE_ENABLE_TRACING 0x00020000 + #define HEAP_CREATE_ENABLE_EXECUTE 0x00040000 + #define HEAP_MAXIMUM_TAG 0x0FFF + #define HEAP_PSEUDO_TAG_FLAG 0x8000 + #define HEAP_TAG_SHIFT 18 + #define HEAP_CREATE_SEGMENT_HEAP 0x00000100 + #define HEAP_CREATE_HARDENED 0x00000200 + + // NOTE: FormatMessageA + #define MAKELANGID(p, s) ((((WORD )(s)) << 10) | (WORD )(p)) + #define LANG_NEUTRAL 0x00 + #define SUBLANG_DEFAULT 0x01 // user default + + // NOTE: CreateFile + #define GENERIC_READ (0x80000000L) + #define GENERIC_WRITE (0x40000000L) + #define GENERIC_EXECUTE (0x20000000L) + #define GENERIC_ALL (0x10000000L) + + #define FILE_APPEND_DATA (0x0004) // file + + // NOTE: CreateFile/FindFirstFile + #define FILE_SHARE_READ 0x00000001 + #define FILE_SHARE_WRITE 0x00000002 + #define FILE_SHARE_DELETE 0x00000004 + + #define FILE_ATTRIBUTE_READONLY 0x00000001 + #define FILE_ATTRIBUTE_HIDDEN 0x00000002 + #define FILE_ATTRIBUTE_SYSTEM 0x00000004 + #define FILE_ATTRIBUTE_DIRECTORY 0x00000010 + #define FILE_ATTRIBUTE_NORMAL 0x00000080 + + // NOTE: STACKFRAME64 + #define IMAGE_FILE_MACHINE_AMD64 0x8664 // AMD64 (K8) + + // NOTE: WaitForSingleObject + #define WAIT_TIMEOUT 258L // dderror + #define STATUS_WAIT_0 ((DWORD )0x00000000L) + #define STATUS_ABANDONED_WAIT_0 ((DWORD )0x00000080L) + + #define S_OK ((HRESULT)0L) + #define S_FALSE ((HRESULT)1L) + + typedef union _ULARGE_INTEGER { + struct { + DWORD LowPart; + DWORD HighPart; + } DUMMYSTRUCTNAME; + struct { + DWORD LowPart; + DWORD HighPart; + } u; + ULONGLONG QuadPart; + } ULARGE_INTEGER; + + typedef union _LARGE_INTEGER { + struct { + DWORD LowPart; + LONG HighPart; + } DUMMYSTRUCTNAME; + struct { + DWORD LowPart; + LONG HighPart; + } u; + LONGLONG QuadPart; + } LARGE_INTEGER; + + typedef struct __declspec(align(16)) _M128A { + ULONGLONG Low; + LONGLONG High; + } M128A, *PM128A; + + typedef struct __declspec(align(16)) _XSAVE_FORMAT { + WORD ControlWord; + WORD StatusWord; + BYTE TagWord; + BYTE Reserved1; + WORD ErrorOpcode; + DWORD ErrorOffset; + WORD ErrorSelector; + WORD Reserved2; + DWORD DataOffset; + WORD DataSelector; + WORD Reserved3; + DWORD MxCsr; + DWORD MxCsr_Mask; + M128A FloatRegisters[8]; + #if defined(_WIN64) + M128A XmmRegisters[16]; + BYTE Reserved4[96]; + #else + M128A XmmRegisters[8]; + BYTE Reserved4[224]; + #endif + } XSAVE_FORMAT, *PXSAVE_FORMAT; + typedef XSAVE_FORMAT XMM_SAVE_AREA32, *PXMM_SAVE_AREA32; + + typedef struct __declspec(align(16)) _CONTEXT { + DWORD64 P1Home; + DWORD64 P2Home; + DWORD64 P3Home; + DWORD64 P4Home; + DWORD64 P5Home; + DWORD64 P6Home; + DWORD ContextFlags; + DWORD MxCsr; + WORD SegCs; + WORD SegDs; + WORD SegEs; + WORD SegFs; + WORD SegGs; + WORD SegSs; + DWORD EFlags; + DWORD64 Dr0; + DWORD64 Dr1; + DWORD64 Dr2; + DWORD64 Dr3; + DWORD64 Dr6; + DWORD64 Dr7; + DWORD64 Rax; + DWORD64 Rcx; + DWORD64 Rdx; + DWORD64 Rbx; + DWORD64 Rsp; + DWORD64 Rbp; + DWORD64 Rsi; + DWORD64 Rdi; + DWORD64 R8; + DWORD64 R9; + DWORD64 R10; + DWORD64 R11; + DWORD64 R12; + DWORD64 R13; + DWORD64 R14; + DWORD64 R15; + DWORD64 Rip; + + union { + XMM_SAVE_AREA32 FltSave; + + struct { + M128A Header[2]; + M128A Legacy[8]; + M128A Xmm0; + M128A Xmm1; + M128A Xmm2; + M128A Xmm3; + M128A Xmm4; + M128A Xmm5; + M128A Xmm6; + M128A Xmm7; + M128A Xmm8; + M128A Xmm9; + M128A Xmm10; + M128A Xmm11; + M128A Xmm12; + M128A Xmm13; + M128A Xmm14; + M128A Xmm15; + } DUMMYSTRUCTNAME; + } DUMMYUNIONNAME; + + M128A VectorRegister[26]; + DWORD64 VectorControl; + DWORD64 DebugControl; + DWORD64 LastBranchToRip; + DWORD64 LastBranchFromRip; + DWORD64 LastExceptionToRip; + DWORD64 LastExceptionFromRip; + } CONTEXT; + + typedef struct _LIST_ENTRY { + struct _LIST_ENTRY *Flink; + struct _LIST_ENTRY *Blink; + } LIST_ENTRY, *PLIST_ENTRY, PRLIST_ENTRY; + + typedef struct _RTL_CRITICAL_SECTION_DEBUG { + WORD Type; + WORD CreatorBackTraceIndex; + struct _RTL_CRITICAL_SECTION *CriticalSection; + LIST_ENTRY ProcessLocksList; + DWORD EntryCount; + DWORD ContentionCount; + DWORD Flags; + WORD CreatorBackTraceIndexHigh; + WORD Identifier; + } RTL_CRITICAL_SECTION_DEBUG, *PRTL_CRITICAL_SECTION_DEBUG, RTL_RESOURCE_DEBUG, *PRTL_RESOURCE_DEBUG; + + typedef struct _RTL_CONDITION_VARIABLE { + VOID *Ptr; + } RTL_CONDITION_VARIABLE, *PRTL_CONDITION_VARIABLE; + + #pragma pack(push, 8) + typedef struct _RTL_CRITICAL_SECTION { + PRTL_CRITICAL_SECTION_DEBUG DebugInfo; + + // + // The following three fields control entering and exiting the critical + // section for the resource + // + + LONG LockCount; + LONG RecursionCount; + HANDLE OwningThread; // from the thread's ClientId->UniqueThread + HANDLE LockSemaphore; + ULONG_PTR SpinCount; // force size on 64-bit systems when packed + } RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION; + #pragma pack(pop) + + typedef struct _MODLOAD_DATA { + DWORD ssize; // size of this struct + DWORD ssig; // signature identifying the passed data + VOID *data; // pointer to passed data + DWORD size; // size of passed data + DWORD flags; // options + } MODLOAD_DATA, *PMODLOAD_DATA; + + #define SLMFLAG_VIRTUAL 0x1 + #define SLMFLAG_ALT_INDEX 0x2 + #define SLMFLAG_NO_SYMBOLS 0x4 + + extern "C" + { + __declspec(dllimport) VOID __stdcall RtlCaptureContext(CONTEXT *ContextRecord); + __declspec(dllimport) HANDLE __stdcall GetCurrentProcess(void); + __declspec(dllimport) HANDLE __stdcall GetCurrentThread(void); + __declspec(dllimport) DWORD __stdcall SymSetOptions(DWORD SymOptions); + __declspec(dllimport) BOOL __stdcall SymInitialize(HANDLE hProcess, const CHAR* UserSearchPath, BOOL fInvadeProcess); + __declspec(dllimport) DWORD64 __stdcall SymLoadModuleEx(HANDLE hProcess, HANDLE hFile, CHAR const *ImageName, CHAR const *ModuleName, DWORD64 BaseOfDll, DWORD DllSize, MODLOAD_DATA *Data, DWORD Flags); + __declspec(dllimport) BOOL __stdcall SymUnloadModule64(HANDLE hProcess, DWORD64 BaseOfDll); + } + + // NOTE: um/heapapi.h //////////////////////////////////////////////////////////////////// + extern "C" + { + __declspec(dllimport) HANDLE __stdcall HeapCreate(DWORD flOptions, SIZE_T dwInitialSize, SIZE_T dwMaximumSize); + __declspec(dllimport) BOOL __stdcall HeapDestroy(HANDLE hHeap); + __declspec(dllimport) VOID * __stdcall HeapAlloc(HANDLE hHeap, DWORD dwFlags,SIZE_T dwBytes); + __declspec(dllimport) VOID * __stdcall HeapReAlloc(HANDLE hHeap, DWORD dwFlags, VOID *lpMem, SIZE_T dwBytes); + __declspec(dllimport) BOOL __stdcall HeapFree(HANDLE hHeap, DWORD dwFlags, VOID *lpMem); + __declspec(dllimport) SIZE_T __stdcall HeapSize(HANDLE hHeap, DWORD dwFlags, VOID const *lpMem); + __declspec(dllimport) HANDLE __stdcall GetProcessHeap(VOID); + __declspec(dllimport) SIZE_T __stdcall HeapCompact(HANDLE hHeap, DWORD dwFlags); + } + + // NOTE: shared/windef.h //////////////////////////////////////////////////////////////////// + typedef struct tagPOINT + { + LONG x; + LONG y; + } POINT, *PPOINT, *NPPOINT, *LPPOINT; + + // NOTE: handleapi.h /////////////////////////////////////////////////////////////////////////// + #define INVALID_HANDLE_VALUE ((HANDLE)(LONG_PTR)-1) + + extern "C" + { + __declspec(dllimport) BOOL __stdcall CloseHandle(HANDLE hObject); + } + + // NOTE: consoleapi.h /////////////////////////////////////////////////////////////////////////// + extern "C" + { + __declspec(dllimport) BOOL __stdcall WriteConsoleA(HANDLE hConsoleOutput, const VOID* lpBuffer, DWORD nNumberOfCharsToWrite, DWORD *lpNumberOfCharsWritten, VOID *lpReserved); + __declspec(dllimport) BOOL __stdcall AllocConsole(VOID); + __declspec(dllimport) BOOL __stdcall FreeConsole(VOID); + __declspec(dllimport) BOOL __stdcall AttachConsole(DWORD dwProcessId); + __declspec(dllimport) BOOL __stdcall GetConsoleMode(HANDLE hConsoleHandle, DWORD *lpMode); + } + + // NOTE: um/minwinbase.h /////////////////////////////////////////////////////////////////////// + // NOTE: FindFirstFile + #define FIND_FIRST_EX_CASE_SENSITIVE 0x00000001 + #define FIND_FIRST_EX_LARGE_FETCH 0x00000002 + + // NOTE: WaitFor.. + #define WAIT_FAILED ((DWORD)0xFFFFFFFF) + #define WAIT_OBJECT_0 ((STATUS_WAIT_0 ) + 0 ) + #define WAIT_ABANDONED ((STATUS_ABANDONED_WAIT_0 ) + 0 ) + #define WAIT_ABANDONED_0 ((STATUS_ABANDONED_WAIT_0 ) + 0 ) + + // NOTE: CreateProcessW + #define CREATE_UNICODE_ENVIRONMENT 0x00000400 + #define CREATE_NO_WINDOW 0x08000000 + + typedef enum _GET_FILEEX_INFO_LEVELS { + GetFileExInfoStandard, + GetFileExMaxInfoLevel + } GET_FILEEX_INFO_LEVELS; + + typedef struct _SECURITY_ATTRIBUTES { + DWORD nLength; + VOID *lpSecurityDescriptor; + BOOL bInheritHandle; + } SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES; + + typedef enum _FINDEX_INFO_LEVELS { + FindExInfoStandard, + FindExInfoBasic, + FindExInfoMaxInfoLevel + } FINDEX_INFO_LEVELS; + + typedef enum _FINDEX_SEARCH_OPS { + FindExSearchNameMatch, + FindExSearchLimitToDirectories, + FindExSearchLimitToDevices, + FindExSearchMaxSearchOp + } FINDEX_SEARCH_OPS; + + typedef struct _WIN32_FIND_DATAW { + DWORD dwFileAttributes; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + DWORD nFileSizeHigh; + DWORD nFileSizeLow; + DWORD dwReserved0; + DWORD dwReserved1; + WCHAR cFileName[ MAX_PATH ]; + WCHAR cAlternateFileName[ 14 ]; + #ifdef _MAC + DWORD dwFileType; + DWORD dwCreatorType; + WORD wFinderFlags; + #endif + } WIN32_FIND_DATAW, *PWIN32_FIND_DATAW, *LPWIN32_FIND_DATAW; + + typedef struct _SYSTEMTIME { + WORD wYear; + WORD wMonth; + WORD wDayOfWeek; + WORD wDay; + WORD wHour; + WORD wMinute; + WORD wSecond; + WORD wMilliseconds; + } SYSTEMTIME, *PSYSTEMTIME, *LPSYSTEMTIME; + + typedef struct _OVERLAPPED { + ULONG_PTR Internal; + ULONG_PTR InternalHigh; + union { + struct { + DWORD Offset; + DWORD OffsetHigh; + } DUMMYSTRUCTNAME; + VOID *Pointer; + } DUMMYUNIONNAME; + + HANDLE hEvent; + } OVERLAPPED, *LPOVERLAPPED; + + typedef RTL_CRITICAL_SECTION CRITICAL_SECTION; + + #define WAIT_FAILED ((DWORD)0xFFFFFFFF) + #define WAIT_OBJECT_0 ((STATUS_WAIT_0 ) + 0 ) + + #define INFINITE 0xFFFFFFFF // Wait/Synchronisation: Infinite timeout + + #define STD_INPUT_HANDLE ((DWORD)-10) + #define STD_OUTPUT_HANDLE ((DWORD)-11) + #define STD_ERROR_HANDLE ((DWORD)-12) + + #define HANDLE_FLAG_INHERIT 0x00000001 + #define HANDLE_FLAG_PROTECT_FROM_CLOSE 0x00000002 + + // NOTE: MoveFile + #define MOVEFILE_REPLACE_EXISTING 0x00000001 + #define MOVEFILE_COPY_ALLOWED 0x00000002 + + // NOTE: FormatMessageA + #define FORMAT_MESSAGE_ALLOCATE_BUFFER 0x00000100 + #define FORMAT_MESSAGE_IGNORE_INSERTS 0x00000200 + #define FORMAT_MESSAGE_FROM_HMODULE 0x00000800 + #define FORMAT_MESSAGE_FROM_SYSTEM 0x00001000 + + // NOTE: CreateProcessW + #define STARTF_USESTDHANDLES 0x00000100 + + extern "C" + { + __declspec(dllimport) BOOL __stdcall MoveFileExW (const WCHAR *lpExistingFileName, const WCHAR *lpNewFileName, DWORD dwFlags); + __declspec(dllimport) BOOL __stdcall CopyFileW (const WCHAR *lpExistingFileName, const WCHAR *lpNewFileName, BOOL bFailIfExists); + __declspec(dllimport) HANDLE __stdcall CreateSemaphoreA(SECURITY_ATTRIBUTES *lpSemaphoreAttributes, LONG lInitialCount, LONG lMaximumCount, const CHAR *lpName); + __declspec(dllimport) DWORD __stdcall FormatMessageW (DWORD dwFlags, VOID const *lpSource, DWORD dwMessageId, DWORD dwLanguageId, LPWSTR lpBuffer, DWORD nSize, va_list *Arguments); + __declspec(dllimport) HLOCAL __stdcall LocalFree (HLOCAL hMem); + } + + // NOTE: um/stringapiset.h ///////////////////////////////////////////////////////////////////// + extern "C" + { + __declspec(dllimport) int __stdcall MultiByteToWideChar(UINT CodePage, DWORD dwFlags, const CHAR *lpMultiByteStr, int cbMultiByte, WCHAR *lpWideCharStr, int cchWideChar); + __declspec(dllimport) int __stdcall WideCharToMultiByte(UINT CodePage, DWORD dwFlags, const WCHAR *lpWideCharStr, int cchWideChar, LPSTR lpMultiByteStr, int cbMultiByte, const CHAR *lpDefaultChar, BOOL *lpUsedDefaultChar); + } + + // NOTE: um/fileapi.h ////////////////////////////////////////////////////////////////////////// + #define INVALID_FILE_SIZE ((DWORD)0xFFFFFFFF) + #define INVALID_FILE_ATTRIBUTES ((DWORD)-1) + + // NOTE: CreateFile + #define CREATE_NEW 1 + #define CREATE_ALWAYS 2 + #define OPEN_EXISTING 3 + #define OPEN_ALWAYS 4 + #define TRUNCATE_EXISTING 5 + + typedef struct _WIN32_FILE_ATTRIBUTE_DATA { + DWORD dwFileAttributes; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + DWORD nFileSizeHigh; + DWORD nFileSizeLow; + } WIN32_FILE_ATTRIBUTE_DATA, *LPWIN32_FILE_ATTRIBUTE_DATA; + + extern "C" + { + __declspec(dllimport) BOOL __stdcall FlushFileBuffers (HANDLE hFile); + __declspec(dllimport) BOOL __stdcall CreateDirectoryW (const WCHAR *lpPathName, SECURITY_ATTRIBUTES *lpSecurityAttributes); + __declspec(dllimport) BOOL __stdcall RemoveDirectoryW (const WCHAR *lpPathName); + __declspec(dllimport) BOOL __stdcall FindNextFileW (HANDLE hFindFile, WIN32_FIND_DATAW *lpFindFileData); + __declspec(dllimport) BOOL __stdcall FindClose (HANDLE hFindFile); + + __declspec(dllimport) HANDLE __stdcall FindFirstFileExW (const WCHAR *lpFileName, FINDEX_INFO_LEVELS fInfoLevelId, VOID *lpFindFileData, FINDEX_SEARCH_OPS fSearchOp, VOID *lpSearchFilter, DWORD dwAdditionalFlags); + __declspec(dllimport) BOOL __stdcall GetFileAttributesExW(const WCHAR *lpFileName, GET_FILEEX_INFO_LEVELS fInfoLevelId, VOID *lpFileInformation); + __declspec(dllimport) BOOL __stdcall GetFileSizeEx (HANDLE hFile, LARGE_INTEGER *lpFileSize); + __declspec(dllimport) BOOL __stdcall DeleteFileW (const WCHAR *lpFileName); + __declspec(dllimport) HANDLE __stdcall CreateFileW (const WCHAR *lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, SECURITY_ATTRIBUTES *lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile); + __declspec(dllimport) BOOL __stdcall ReadFile (HANDLE hFile, VOID *lpBuffer, DWORD nNumberOfBytesToRead, DWORD *lpNumberOfBytesRead, OVERLAPPED *lpOverlapped); + __declspec(dllimport) BOOL __stdcall WriteFile (HANDLE hFile, const VOID *lpBuffer, DWORD nNumberOfBytesToWrite, DWORD *lpNumberOfBytesWritten, OVERLAPPED *lpOverlapped); + __declspec(dllimport) BOOL __stdcall GetDiskFreeSpaceExW (WCHAR const *lpDirectoryName, ULARGE_INTEGER *lpFreeBytesAvailableToCaller, ULARGE_INTEGER *lpTotalNumberOfBytes, ULARGE_INTEGER *lpTotalNumberOfFreeBytes); + } + + // NOTE: um/processenv.h /////////////////////////////////////////////////////////////////////// + extern "C" + { + __declspec(dllimport) DWORD __stdcall GetCurrentDirectoryW (DWORD nBufferLength, WCHAR *lpBuffer); + __declspec(dllimport) HANDLE __stdcall GetStdHandle (DWORD nStdHandle); + __declspec(dllimport) WCHAR* __stdcall GetEnvironmentStringsW (); + __declspec(dllimport) BOOL __stdcall FreeEnvironmentStringsW(WCHAR *penv); + __declspec(dllimport) DWORD __stdcall GetEnvironmentVariableW(WCHAR const *lpName, WCHAR *lpBuffer, DWORD nSize); + __declspec(dllimport) BOOL __stdcall SetEnvironmentVariableW(WCHAR const *lpName, WCHAR const *lpValue); + } + + // NOTE: um/psapi.h //////////////////////////////////////////////////////////////////////////// + typedef struct _PROCESS_MEMORY_COUNTERS { + DWORD cb; + DWORD PageFaultCount; + SIZE_T PeakWorkingSetSize; + SIZE_T WorkingSetSize; + SIZE_T QuotaPeakPagedPoolUsage; + SIZE_T QuotaPagedPoolUsage; + SIZE_T QuotaPeakNonPagedPoolUsage; + SIZE_T QuotaNonPagedPoolUsage; + SIZE_T PagefileUsage; + SIZE_T PeakPagefileUsage; + } PROCESS_MEMORY_COUNTERS; + typedef PROCESS_MEMORY_COUNTERS *PPROCESS_MEMORY_COUNTERS; + + extern "C" + { + __declspec(dllimport) BOOL __stdcall GetProcessMemoryInfo(HANDLE Process, PPROCESS_MEMORY_COUNTERS ppsmemCounters, DWORD cb); + } + + // NOTE: um/sysinfoapi.h /////////////////////////////////////////////////////////////////////// + typedef struct _SYSTEM_INFO { + union { + DWORD dwOemId; // Obsolete field...do not use + struct { + WORD wProcessorArchitecture; + WORD wReserved; + } DUMMYSTRUCTNAME; + } DUMMYUNIONNAME; + DWORD dwPageSize; + VOID *lpMinimumApplicationAddress; + VOID *lpMaximumApplicationAddress; + DWORD_PTR dwActiveProcessorMask; + DWORD dwNumberOfProcessors; + DWORD dwProcessorType; + DWORD dwAllocationGranularity; + WORD wProcessorLevel; + WORD wProcessorRevision; + } SYSTEM_INFO, *LPSYSTEM_INFO; + + extern "C" + { + __declspec(dllimport) VOID __stdcall GetSystemInfo(SYSTEM_INFO *lpSystemInfo); + __declspec(dllimport) VOID __stdcall GetSystemTime(SYSTEMTIME *lpSystemTime); + __declspec(dllimport) VOID __stdcall GetSystemTimeAsFileTime(FILETIME *lpSystemTimeAsFileTime); + __declspec(dllimport) VOID __stdcall GetLocalTime(SYSTEMTIME *lpSystemTime); + } + + // NOTE: um/timezoneapi.h ////////////////////////////////////////////////////////////////////// + typedef struct _TIME_ZONE_INFORMATION { + LONG Bias; + WCHAR StandardName[32]; + SYSTEMTIME StandardDate; + LONG StandardBias; + WCHAR DaylightName[32]; + SYSTEMTIME DaylightDate; + LONG DaylightBias; + } TIME_ZONE_INFORMATION, *PTIME_ZONE_INFORMATION, *LPTIME_ZONE_INFORMATION; + + extern "C" + { + __declspec(dllimport) BOOL __stdcall FileTimeToSystemTime (const FILETIME* lpFileTime, SYSTEMTIME *lpSystemTime); + __declspec(dllimport) BOOL __stdcall SystemTimeToFileTime (const SYSTEMTIME* lpSystemTime, FILETIME *lpFileTime); + __declspec(dllimport) BOOL __stdcall TzSpecificLocalTimeToSystemTime(const TIME_ZONE_INFORMATION* lpTimeZoneInformation, const SYSTEMTIME* lpLocalTime, const LPSYSTEMTIME lpUniversalTime); + } + + // NOTE: shared/windef.h /////////////////////////////////////////////////////////////////////// + typedef struct tagRECT { + LONG left; + LONG top; + LONG right; + LONG bottom; + } RECT; + + struct HWND__ { + int unused; + }; + typedef struct HWND__ *HWND; + + struct DPI_AWARENESS_CONTEXT__ { + int unused; + }; + typedef struct DPI_AWARENESS_CONTEXT__ *DPI_AWARENESS_CONTEXT; + + typedef enum DPI_AWARENESS { + DPI_AWARENESS_INVALID = -1, + DPI_AWARENESS_UNAWARE = 0, + DPI_AWARENESS_SYSTEM_AWARE = 1, + DPI_AWARENESS_PER_MONITOR_AWARE = 2 + } DPI_AWARENESS; + + #define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT)-4) + + // NOTE: um/winuser.h ////////////////////////////////////////////////////////////////////////// + typedef struct tagWINDOWPLACEMENT { + UINT length; + UINT flags; + UINT showCmd; + POINT ptMinPosition; + POINT ptMaxPosition; + RECT rcNormalPosition; + #ifdef _MAC + RECT rcDevice; + #endif + } WINDOWPLACEMENT; + typedef WINDOWPLACEMENT *PWINDOWPLACEMENT, *LPWINDOWPLACEMENT; + + #define SW_HIDE 0 + #define SW_NORMAL 1 + #define SW_MAXIMIZE 3 + #define SW_SHOWNOACTIVATE 4 + #define SW_SHOW 5 + #define SW_FORCEMINIMIZE 11 + + extern "C" + { + __declspec(dllimport) BOOL __stdcall GetWindowRect (HWND hWnd, RECT *lpRect); + __declspec(dllimport) BOOL __stdcall SetWindowPos (HWND hWnd, HWND hWndInsertAfter, int X, int Y, int cx, int cy, UINT uFlags); + __declspec(dllimport) UINT __stdcall GetWindowModuleFileNameA(HWND hwnd, LPSTR pszFileName, UINT cchFileNameMax); + __declspec(dllimport) BOOL __stdcall ShowWindow (HWND hWnd, int nCmdShow); + __declspec(dllimport) BOOL __stdcall GetWindowPlacement (HWND hWnd, WINDOWPLACEMENT *lpwndpl); + + } + + // NOTE: um/wininet.h ////////////////////////////////////////////////////////////////////////// + typedef WORD INTERNET_PORT; + typedef VOID *HINTERNET; + + // NOTE: um/winhttp.h ////////////////////////////////////////////////////////////////////////// + #define WINHTTP_ACCESS_TYPE_DEFAULT_PROXY 0 + #define WINHTTP_ACCESS_TYPE_NO_PROXY 1 + #define WINHTTP_ACCESS_TYPE_NAMED_PROXY 3 + #define WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY 4 + + #define INTERNET_DEFAULT_PORT 0 // use the protocol-specific default + #define INTERNET_DEFAULT_HTTP_PORT 80 // " " HTTP " + #define INTERNET_DEFAULT_HTTPS_PORT 443 // " " HTTPS " + + // NOTE: WinHttpOpen + #define WINHTTP_FLAG_ASYNC 0x10000000 // this session is asynchronous (where supported) + #define WINHTTP_FLAG_SECURE_DEFAULTS 0x30000000 // note that this flag also forces async + + // NOTE: WinHttpOpenRequest + #define WINHTTP_FLAG_SECURE 0x00800000 // use SSL if applicable (HTTPS) + #define WINHTTP_FLAG_ESCAPE_PERCENT 0x00000004 // if escaping enabled, escape percent as well + #define WINHTTP_FLAG_NULL_CODEPAGE 0x00000008 // assume all symbols are ASCII, use fast convertion + #define WINHTTP_FLAG_ESCAPE_DISABLE 0x00000040 // disable escaping + #define WINHTTP_FLAG_ESCAPE_DISABLE_QUERY 0x00000080 // if escaping enabled escape path part, but do not escape query + #define WINHTTP_FLAG_BYPASS_PROXY_CACHE 0x00000100 // add "pragma: no-cache" request header + #define WINHTTP_FLAG_REFRESH WINHTTP_FLAG_BYPASS_PROXY_CACHE + #define WINHTTP_FLAG_AUTOMATIC_CHUNKING 0x00000200 // Send request without content-length header or chunked TE + + #define WINHTTP_NO_PROXY_NAME NULL + #define WINHTTP_NO_PROXY_BYPASS NULL + + // + // WINHTTP_QUERY_FLAG_NUMBER - if this bit is set in the dwInfoLevel parameter of + // HttpQueryHeader(), then the value of the header will be converted to a number + // before being returned to the caller, if applicable + // + #define WINHTTP_QUERY_FLAG_NUMBER 0x20000000 + + #define WINHTTP_QUERY_MIME_VERSION 0 + #define WINHTTP_QUERY_CONTENT_TYPE 1 + #define WINHTTP_QUERY_CONTENT_TRANSFER_ENCODING 2 + #define WINHTTP_QUERY_CONTENT_ID 3 + #define WINHTTP_QUERY_CONTENT_DESCRIPTION 4 + #define WINHTTP_QUERY_CONTENT_LENGTH 5 + #define WINHTTP_QUERY_CONTENT_LANGUAGE 6 + #define WINHTTP_QUERY_ALLOW 7 + #define WINHTTP_QUERY_PUBLIC 8 + #define WINHTTP_QUERY_DATE 9 + #define WINHTTP_QUERY_EXPIRES 10 + #define WINHTTP_QUERY_LAST_MODIFIED 11 + #define WINHTTP_QUERY_MESSAGE_ID 12 + #define WINHTTP_QUERY_URI 13 + #define WINHTTP_QUERY_DERIVED_FROM 14 + #define WINHTTP_QUERY_COST 15 + #define WINHTTP_QUERY_LINK 16 + #define WINHTTP_QUERY_PRAGMA 17 + #define WINHTTP_QUERY_VERSION 18 // special: part of status line + #define WINHTTP_QUERY_STATUS_CODE 19 // special: part of status line + #define WINHTTP_QUERY_STATUS_TEXT 20 // special: part of status line + #define WINHTTP_QUERY_RAW_HEADERS 21 // special: all headers as ASCIIZ + #define WINHTTP_QUERY_RAW_HEADERS_CRLF 22 // special: all headers + #define WINHTTP_QUERY_CONNECTION 23 + #define WINHTTP_QUERY_ACCEPT 24 + #define WINHTTP_QUERY_ACCEPT_CHARSET 25 + #define WINHTTP_QUERY_ACCEPT_ENCODING 26 + #define WINHTTP_QUERY_ACCEPT_LANGUAGE 27 + #define WINHTTP_QUERY_AUTHORIZATION 28 + #define WINHTTP_QUERY_CONTENT_ENCODING 29 + #define WINHTTP_QUERY_FORWARDED 30 + #define WINHTTP_QUERY_FROM 31 + #define WINHTTP_QUERY_IF_MODIFIED_SINCE 32 + #define WINHTTP_QUERY_LOCATION 33 + #define WINHTTP_QUERY_ORIG_URI 34 + #define WINHTTP_QUERY_REFERER 35 + #define WINHTTP_QUERY_RETRY_AFTER 36 + #define WINHTTP_QUERY_SERVER 37 + #define WINHTTP_QUERY_TITLE 38 + #define WINHTTP_QUERY_USER_AGENT 39 + #define WINHTTP_QUERY_WWW_AUTHENTICATE 40 + #define WINHTTP_QUERY_PROXY_AUTHENTICATE 41 + #define WINHTTP_QUERY_ACCEPT_RANGES 42 + #define WINHTTP_QUERY_SET_COOKIE 43 + #define WINHTTP_QUERY_COOKIE 44 + #define WINHTTP_QUERY_REQUEST_METHOD 45 // special: GET/POST etc. + #define WINHTTP_QUERY_REFRESH 46 + #define WINHTTP_QUERY_CONTENT_DISPOSITION 47 + + // NOTE: WinHttpQueryHeaders prettifiers for optional parameters. + #define WINHTTP_HEADER_NAME_BY_INDEX NULL + #define WINHTTP_NO_OUTPUT_BUFFER NULL + #define WINHTTP_NO_HEADER_INDEX NULL + + // NOTE: Http Response Status Codes + #define HTTP_STATUS_CONTINUE 100 // OK to continue with request + #define HTTP_STATUS_SWITCH_PROTOCOLS 101 // server has switched protocols in upgrade header + + #define HTTP_STATUS_OK 200 // request completed + #define HTTP_STATUS_CREATED 201 // object created, reason = new URI + #define HTTP_STATUS_ACCEPTED 202 // async completion (TBS) + #define HTTP_STATUS_PARTIAL 203 // partial completion + #define HTTP_STATUS_NO_CONTENT 204 // no info to return + #define HTTP_STATUS_RESET_CONTENT 205 // request completed, but clear form + #define HTTP_STATUS_PARTIAL_CONTENT 206 // partial GET fulfilled + #define HTTP_STATUS_WEBDAV_MULTI_STATUS 207 // WebDAV Multi-Status + + #define HTTP_STATUS_AMBIGUOUS 300 // server couldn't decide what to return + #define HTTP_STATUS_MOVED 301 // object permanently moved + #define HTTP_STATUS_REDIRECT 302 // object temporarily moved + #define HTTP_STATUS_REDIRECT_METHOD 303 // redirection w/ new access method + #define HTTP_STATUS_NOT_MODIFIED 304 // if-modified-since was not modified + #define HTTP_STATUS_USE_PROXY 305 // redirection to proxy, location header specifies proxy to use + #define HTTP_STATUS_REDIRECT_KEEP_VERB 307 // HTTP/1.1: keep same verb + #define HTTP_STATUS_PERMANENT_REDIRECT 308 // Object permanently moved keep verb + + #define HTTP_STATUS_BAD_REQUEST 400 // invalid syntax + #define HTTP_STATUS_DENIED 401 // access denied + #define HTTP_STATUS_PAYMENT_REQ 402 // payment required + #define HTTP_STATUS_FORBIDDEN 403 // request forbidden + #define HTTP_STATUS_NOT_FOUND 404 // object not found + #define HTTP_STATUS_BAD_METHOD 405 // method is not allowed + #define HTTP_STATUS_NONE_ACCEPTABLE 406 // no response acceptable to client found + #define HTTP_STATUS_PROXY_AUTH_REQ 407 // proxy authentication required + #define HTTP_STATUS_REQUEST_TIMEOUT 408 // server timed out waiting for request + #define HTTP_STATUS_CONFLICT 409 // user should resubmit with more info + #define HTTP_STATUS_GONE 410 // the resource is no longer available + #define HTTP_STATUS_LENGTH_REQUIRED 411 // the server refused to accept request w/o a length + #define HTTP_STATUS_PRECOND_FAILED 412 // precondition given in request failed + #define HTTP_STATUS_REQUEST_TOO_LARGE 413 // request entity was too large + #define HTTP_STATUS_URI_TOO_LONG 414 // request URI too long + #define HTTP_STATUS_UNSUPPORTED_MEDIA 415 // unsupported media type + #define HTTP_STATUS_RETRY_WITH 449 // retry after doing the appropriate action. + + #define HTTP_STATUS_SERVER_ERROR 500 // internal server error + #define HTTP_STATUS_NOT_SUPPORTED 501 // required not supported + #define HTTP_STATUS_BAD_GATEWAY 502 // error response received from gateway + #define HTTP_STATUS_SERVICE_UNAVAIL 503 // temporarily overloaded + #define HTTP_STATUS_GATEWAY_TIMEOUT 504 // timed out waiting for gateway + #define HTTP_STATUS_VERSION_NOT_SUP 505 // HTTP version not supported + + #define HTTP_STATUS_FIRST HTTP_STATUS_CONTINUE + #define HTTP_STATUS_LAST HTTP_STATUS_VERSION_NOT_SUP + + #define WINHTTP_CALLBACK_STATUS_RESOLVING_NAME 0x00000001 + #define WINHTTP_CALLBACK_STATUS_NAME_RESOLVED 0x00000002 + #define WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER 0x00000004 + #define WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER 0x00000008 + #define WINHTTP_CALLBACK_STATUS_SENDING_REQUEST 0x00000010 + #define WINHTTP_CALLBACK_STATUS_REQUEST_SENT 0x00000020 + #define WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE 0x00000040 + #define WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED 0x00000080 + #define WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION 0x00000100 + #define WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED 0x00000200 + #define WINHTTP_CALLBACK_STATUS_HANDLE_CREATED 0x00000400 + #define WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING 0x00000800 + #define WINHTTP_CALLBACK_STATUS_DETECTING_PROXY 0x00001000 + #define WINHTTP_CALLBACK_STATUS_REDIRECT 0x00004000 + #define WINHTTP_CALLBACK_STATUS_INTERMEDIATE_RESPONSE 0x00008000 + #define WINHTTP_CALLBACK_STATUS_SECURE_FAILURE 0x00010000 + #define WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE 0x00020000 + #define WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE 0x00040000 + #define WINHTTP_CALLBACK_STATUS_READ_COMPLETE 0x00080000 + #define WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE 0x00100000 + #define WINHTTP_CALLBACK_STATUS_REQUEST_ERROR 0x00200000 + #define WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE 0x00400000 + + #define WINHTTP_CALLBACK_STATUS_GETPROXYFORURL_COMPLETE 0x01000000 + #define WINHTTP_CALLBACK_STATUS_CLOSE_COMPLETE 0x02000000 + #define WINHTTP_CALLBACK_STATUS_SHUTDOWN_COMPLETE 0x04000000 + #define WINHTTP_CALLBACK_STATUS_SETTINGS_WRITE_COMPLETE 0x10000000 + #define WINHTTP_CALLBACK_STATUS_SETTINGS_READ_COMPLETE 0x20000000 + + #define WINHTTP_CALLBACK_FLAG_RESOLVE_NAME (WINHTTP_CALLBACK_STATUS_RESOLVING_NAME | WINHTTP_CALLBACK_STATUS_NAME_RESOLVED) + #define WINHTTP_CALLBACK_FLAG_CONNECT_TO_SERVER (WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER | WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER) + #define WINHTTP_CALLBACK_FLAG_SEND_REQUEST (WINHTTP_CALLBACK_STATUS_SENDING_REQUEST | WINHTTP_CALLBACK_STATUS_REQUEST_SENT) + #define WINHTTP_CALLBACK_FLAG_RECEIVE_RESPONSE (WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE | WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED) + #define WINHTTP_CALLBACK_FLAG_CLOSE_CONNECTION (WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION | WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED) + #define WINHTTP_CALLBACK_FLAG_HANDLES (WINHTTP_CALLBACK_STATUS_HANDLE_CREATED | WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING) + #define WINHTTP_CALLBACK_FLAG_DETECTING_PROXY WINHTTP_CALLBACK_STATUS_DETECTING_PROXY + #define WINHTTP_CALLBACK_FLAG_REDIRECT WINHTTP_CALLBACK_STATUS_REDIRECT + #define WINHTTP_CALLBACK_FLAG_INTERMEDIATE_RESPONSE WINHTTP_CALLBACK_STATUS_INTERMEDIATE_RESPONSE + #define WINHTTP_CALLBACK_FLAG_SECURE_FAILURE WINHTTP_CALLBACK_STATUS_SECURE_FAILURE + #define WINHTTP_CALLBACK_FLAG_SENDREQUEST_COMPLETE WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE + #define WINHTTP_CALLBACK_FLAG_HEADERS_AVAILABLE WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE + #define WINHTTP_CALLBACK_FLAG_DATA_AVAILABLE WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE + #define WINHTTP_CALLBACK_FLAG_READ_COMPLETE WINHTTP_CALLBACK_STATUS_READ_COMPLETE + #define WINHTTP_CALLBACK_FLAG_WRITE_COMPLETE WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE + #define WINHTTP_CALLBACK_FLAG_REQUEST_ERROR WINHTTP_CALLBACK_STATUS_REQUEST_ERROR + + #define WINHTTP_CALLBACK_FLAG_GETPROXYFORURL_COMPLETE WINHTTP_CALLBACK_STATUS_GETPROXYFORURL_COMPLETE + + #define WINHTTP_CALLBACK_FLAG_ALL_COMPLETIONS (WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE \ + | WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE \ + | WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE \ + | WINHTTP_CALLBACK_STATUS_READ_COMPLETE \ + | WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE \ + | WINHTTP_CALLBACK_STATUS_REQUEST_ERROR \ + | WINHTTP_CALLBACK_STATUS_GETPROXYFORURL_COMPLETE) + + #define WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS 0xffffffff + #define WINHTTP_INVALID_STATUS_CALLBACK ((WINHTTP_STATUS_CALLBACK)(-1L)) + + typedef struct _WINHTTP_EXTENDED_HEADER + { + union + { + CHAR const *pwszName; + WCHAR const *pszName; + }; + union + { + WCHAR const *pwszValue; + CHAR const *pszValue; + }; + } WINHTTP_EXTENDED_HEADER, *PWINHTTP_EXTENDED_HEADER; + + typedef struct _WINHTTP_ASYNC_RESULT + { + DWORD *dwResult; // indicates which async API has encountered an error + DWORD dwError; // the error code if the API failed + } WINHTTP_ASYNC_RESULT, *LPWINHTTP_ASYNC_RESULT, *PWINHTTP_ASYNC_RESULT; + + typedef + VOID + (*WINHTTP_STATUS_CALLBACK)( + HINTERNET hInternet, + DWORD *dwContext, + DWORD dwInternetStatus, + VOID *lpvStatusInformation, + DWORD dwStatusInformationLength + ); + + extern "C" + { + __declspec(dllimport) HINTERNET __stdcall WinHttpOpen(WCHAR const *pszAgentW, DWORD dwAccessType, WCHAR const *pszProxyW, WCHAR const *pszProxyBypassW, DWORD dwFlags); + __declspec(dllimport) BOOL __stdcall WinHttpCloseHandle(HINTERNET hInternet); + __declspec(dllimport) HINTERNET __stdcall WinHttpConnect(HINTERNET hSession, WCHAR const *pswzServerName, INTERNET_PORT nServerPort, DWORD dwReserved); + __declspec(dllimport) BOOL __stdcall WinHttpReadData(HINTERNET hRequest, VOID *lpBuffer, DWORD dwNumberOfBytesToRead, DWORD *lpdwNumberOfBytesRead); + __declspec(dllimport) HINTERNET __stdcall WinHttpOpenRequest(HINTERNET hConnect, WCHAR const *pwszVerb, WCHAR const *pwszObjectName, WCHAR const *pwszVersion, WCHAR const *pwszReferrer, WCHAR const *ppwszAcceptTypes, DWORD dwFlags); + __declspec(dllimport) BOOL __stdcall WinHttpSendRequest(HINTERNET hRequest, WCHAR const *lpszHeaders, DWORD dwHeadersLength, VOID *lpOptional, DWORD dwOptionalLength, DWORD dwTotalLength, DWORD_PTR dwContext); + __declspec(dllimport) DWORD __stdcall WinHttpAddRequestHeadersEx(HINTERNET hRequest, DWORD dwModifiers, ULONGLONG ullFlags, ULONGLONG ullExtra, DWORD cHeaders, WINHTTP_EXTENDED_HEADER *pHeaders); + __declspec(dllimport) BOOL __stdcall WinHttpSetCredentials(HINTERNET hRequest, // HINTERNET handle returned by WinHttpOpenRequest. + DWORD AuthTargets, // Only WINHTTP_AUTH_TARGET_SERVER and WINHTTP_AUTH_TARGET_PROXY are supported in this version and they are mutually exclusive + DWORD AuthScheme, // must be one of the supported Auth Schemes returned from WinHttpQueryAuthSchemes() + WCHAR * pwszUserName, // 1) NULL if default creds is to be used, in which case pszPassword will be ignored + WCHAR * pwszPassword, // 1) "" == Blank Password; 2)Parameter ignored if pszUserName is NULL; 3) Invalid to pass in NULL if pszUserName is not NULL + VOID * pAuthParams); + __declspec(dllimport) BOOL __stdcall WinHttpQueryHeaders(HINTERNET hRequest, DWORD dwInfoLevel, WCHAR const *pwszName, VOID *lpBuffer, DWORD *lpdwBufferLength, DWORD *lpdwIndex); + __declspec(dllimport) BOOL __stdcall WinHttpReceiveResponse(HINTERNET hRequest, VOID *lpReserved); + __declspec(dllimport) WINHTTP_STATUS_CALLBACK __stdcall WinHttpSetStatusCallback(HINTERNET hInternet, WINHTTP_STATUS_CALLBACK lpfnInternetCallback, DWORD dwNotificationFlags, DWORD_PTR dwReserved); + } + + // NOTE: um/DbgHelp.h ////////////////////////////////////////////////////////////////////////// + #define SYMOPT_CASE_INSENSITIVE 0x00000001 + #define SYMOPT_UNDNAME 0x00000002 + #define SYMOPT_DEFERRED_LOADS 0x00000004 + #define SYMOPT_NO_CPP 0x00000008 + #define SYMOPT_LOAD_LINES 0x00000010 + #define SYMOPT_OMAP_FIND_NEAREST 0x00000020 + #define SYMOPT_LOAD_ANYTHING 0x00000040 + #define SYMOPT_IGNORE_CVREC 0x00000080 + #define SYMOPT_NO_UNQUALIFIED_LOADS 0x00000100 + #define SYMOPT_FAIL_CRITICAL_ERRORS 0x00000200 + #define SYMOPT_EXACT_SYMBOLS 0x00000400 + #define SYMOPT_ALLOW_ABSOLUTE_SYMBOLS 0x00000800 + #define SYMOPT_IGNORE_NT_SYMPATH 0x00001000 + #define SYMOPT_INCLUDE_32BIT_MODULES 0x00002000 + #define SYMOPT_PUBLICS_ONLY 0x00004000 + #define SYMOPT_NO_PUBLICS 0x00008000 + #define SYMOPT_AUTO_PUBLICS 0x00010000 + #define SYMOPT_NO_IMAGE_SEARCH 0x00020000 + #define SYMOPT_SECURE 0x00040000 + #define SYMOPT_NO_PROMPTS 0x00080000 + #define SYMOPT_OVERWRITE 0x00100000 + #define SYMOPT_IGNORE_IMAGEDIR 0x00200000 + #define SYMOPT_FLAT_DIRECTORY 0x00400000 + #define SYMOPT_FAVOR_COMPRESSED 0x00800000 + #define SYMOPT_ALLOW_ZERO_ADDRESS 0x01000000 + #define SYMOPT_DISABLE_SYMSRV_AUTODETECT 0x02000000 + #define SYMOPT_READONLY_CACHE 0x04000000 + #define SYMOPT_SYMPATH_LAST 0x08000000 + #define SYMOPT_DISABLE_FAST_SYMBOLS 0x10000000 + #define SYMOPT_DISABLE_SYMSRV_TIMEOUT 0x20000000 + #define SYMOPT_DISABLE_SRVSTAR_ON_STARTUP 0x40000000 + #define SYMOPT_DEBUG 0x80000000 + + #define MAX_SYM_NAME 2000 + + typedef enum { + AddrMode1616, + AddrMode1632, + AddrModeReal, + AddrModeFlat + } ADDRESS_MODE; + + typedef struct _tagADDRESS64 { + DWORD64 Offset; + WORD Segment; + ADDRESS_MODE Mode; + } ADDRESS64, *LPADDRESS64; + + + typedef struct _KDHELP64 { + DWORD64 Thread; + DWORD ThCallbackStack; + DWORD ThCallbackBStore; + DWORD NextCallback; + DWORD FramePointer; + DWORD64 KiCallUserMode; + DWORD64 KeUserCallbackDispatcher; + DWORD64 SystemRangeStart; + DWORD64 KiUserExceptionDispatcher; + DWORD64 StackBase; + DWORD64 StackLimit; + DWORD BuildVersion; + DWORD RetpolineStubFunctionTableSize; + DWORD64 RetpolineStubFunctionTable; + DWORD RetpolineStubOffset; + DWORD RetpolineStubSize; + DWORD64 Reserved0[2]; + } KDHELP64, *PKDHELP64; + + typedef struct _tagSTACKFRAME64 { + ADDRESS64 AddrPC; // program counter + ADDRESS64 AddrReturn; // return address + ADDRESS64 AddrFrame; // frame pointer + ADDRESS64 AddrStack; // stack pointer + ADDRESS64 AddrBStore; // backing store pointer + VOID *FuncTableEntry; // pointer to pdata/fpo or NULL + DWORD64 Params[4]; // possible arguments to the function + BOOL Far; // WOW far call + BOOL Virtual; // is this a virtual frame? + DWORD64 Reserved[3]; + KDHELP64 KdHelp; + } STACKFRAME64; + + typedef struct _IMAGEHLP_LINEW64 { + DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_LINE64) + VOID *Key; // internal + DWORD LineNumber; // line number in file + WCHAR *FileName; // full filename + DWORD64 Address; // first instruction of line + } IMAGEHLP_LINEW64; + + typedef struct _SYMBOL_INFOW { + ULONG SizeOfStruct; + ULONG TypeIndex; // Type Index of symbol + ULONG64 Reserved[2]; + ULONG Index; + ULONG Size; + ULONG64 ModBase; // Base Address of module comtaining this symbol + ULONG Flags; + ULONG64 Value; // Value of symbol, ValuePresent should be 1 + ULONG64 Address; // Address of symbol including base address of module + ULONG Register; // register holding value or pointer to value + ULONG Scope; // scope of the symbol + ULONG Tag; // pdb classification + ULONG NameLen; // Actual length of name + ULONG MaxNameLen; + WCHAR Name[1]; // Name of symbol + } SYMBOL_INFOW; + + typedef BOOL (__stdcall READ_PROCESS_MEMORY_ROUTINE64)(HANDLE hProcess, DWORD64 qwBaseAddress, VOID *lpBuffer, DWORD nSize, DWORD *lpNumberOfBytesRead); + typedef VOID * (__stdcall FUNCTION_TABLE_ACCESS_ROUTINE64)(HANDLE ahProcess, DWORD64 AddrBase); + typedef DWORD64(__stdcall GET_MODULE_BASE_ROUTINE64)(HANDLE hProcess, DWORD64 Address); + typedef DWORD64(__stdcall TRANSLATE_ADDRESS_ROUTINE64)(HANDLE hProcess, HANDLE hThread, ADDRESS64 *lpaddr); + + extern "C" + { + __declspec(dllimport) BOOL __stdcall StackWalk64 (DWORD MachineType, HANDLE hProcess, HANDLE hThread, STACKFRAME64 *StackFrame, VOID *ContextRecord, READ_PROCESS_MEMORY_ROUTINE64 *ReadMemoryRoutine, FUNCTION_TABLE_ACCESS_ROUTINE64 *FunctionTableAccessRoutine, GET_MODULE_BASE_ROUTINE64 *GetModuleBaseRoutine, TRANSLATE_ADDRESS_ROUTINE64 *TranslateAddress); + __declspec(dllimport) BOOL __stdcall SymFromAddrW (HANDLE hProcess, DWORD64 Address, DWORD64 *Displacement, SYMBOL_INFOW *Symbol); + __declspec(dllimport) VOID * __stdcall SymFunctionTableAccess64(HANDLE hProcess, DWORD64 AddrBase); + __declspec(dllimport) BOOL __stdcall SymGetLineFromAddrW64 (HANDLE hProcess, DWORD64 dwAddr, DWORD *pdwDisplacement, IMAGEHLP_LINEW64 *Line); + __declspec(dllimport) DWORD64 __stdcall SymGetModuleBase64 (HANDLE hProcess, DWORD64 qwAddr); + __declspec(dllimport) BOOL __stdcall SymRefreshModuleList (HANDLE hProcess); + }; + + // NOTE: um/errhandlingapi.h /////////////////////////////////////////////////////////////////// + extern "C" + { + __declspec(dllimport) DWORD __stdcall GetLastError(VOID); + } + + // NOTE: um/libloaderapi.h ///////////////////////////////////////////////////////////////////// + extern "C" + { + __declspec(dllimport) HMODULE __stdcall LoadLibraryA (const CHAR *lpLibFileName); + __declspec(dllimport) BOOL __stdcall FreeLibrary (HMODULE hLibModule); + __declspec(dllimport) void * __stdcall GetProcAddress (HMODULE hModule, const CHAR *lpProcName); + __declspec(dllimport) HMODULE __stdcall GetModuleHandleA (const CHAR *lpModuleName); + __declspec(dllimport) DWORD __stdcall GetModuleFileNameW(HMODULE hModule, WCHAR *lpFilename, DWORD nSize); + } + + // NOTE: um/synchapi.h ///////////////////////////////////////////////////////////////////////// + typedef RTL_CONDITION_VARIABLE CONDITION_VARIABLE, *PCONDITION_VARIABLE; + + extern "C" + { + __declspec(dllimport) VOID __stdcall InitializeConditionVariable (CONDITION_VARIABLE *ConditionVariable); + __declspec(dllimport) VOID __stdcall WakeConditionVariable (CONDITION_VARIABLE *ConditionVariable); + __declspec(dllimport) VOID __stdcall WakeAllConditionVariable (CONDITION_VARIABLE *ConditionVariable); + __declspec(dllimport) BOOL __stdcall SleepConditionVariableCS (CONDITION_VARIABLE *ConditionVariable, CRITICAL_SECTION *CriticalSection, DWORD dwMilliseconds); + + __declspec(dllimport) VOID __stdcall InitializeCriticalSection (CRITICAL_SECTION *lpCriticalSection); + __declspec(dllimport) VOID __stdcall EnterCriticalSection (CRITICAL_SECTION *lpCriticalSection); + __declspec(dllimport) VOID __stdcall LeaveCriticalSection (CRITICAL_SECTION *lpCriticalSection); + __declspec(dllimport) BOOL __stdcall InitializeCriticalSectionAndSpinCount(CRITICAL_SECTION *lpCriticalSection, DWORD dwSpinCount); + __declspec(dllimport) BOOL __stdcall InitializeCriticalSectionEx (CRITICAL_SECTION *lpCriticalSection, DWORD dwSpinCount, DWORD Flags); + __declspec(dllimport) DWORD __stdcall SetCriticalSectionSpinCount (CRITICAL_SECTION *lpCriticalSection, DWORD dwSpinCount); + __declspec(dllimport) BOOL __stdcall TryEnterCriticalSection (CRITICAL_SECTION *lpCriticalSection); + __declspec(dllimport) VOID __stdcall DeleteCriticalSection (CRITICAL_SECTION *lpCriticalSection); + + __declspec(dllimport) DWORD __stdcall WaitForSingleObject (HANDLE hHandle, DWORD dwMilliseconds); + __declspec(dllimport) BOOL __stdcall ReleaseSemaphore (HANDLE hSemaphore, LONG lReleaseCount, LONG *lpPreviousCount); + __declspec(dllimport) VOID __stdcall Sleep (DWORD dwMilliseconds); + } + + // NOTE: um/profileapi.h /////////////////////////////////////////////////////////////////////// + extern "C" + { + __declspec(dllimport) BOOL __stdcall QueryPerformanceCounter (LARGE_INTEGER* lpPerformanceCount); + __declspec(dllimport) BOOL __stdcall QueryPerformanceFrequency(LARGE_INTEGER* lpFrequency); + } + + // NOTE: um/processthreadsapi.h //////////////////////////////////////////////////////////////// + typedef struct _PROCESS_INFORMATION { + HANDLE hProcess; + HANDLE hThread; + DWORD dwProcessId; + DWORD dwThreadId; + } PROCESS_INFORMATION, *PPROCESS_INFORMATION, *LPPROCESS_INFORMATION; + + typedef struct _STARTUPINFOW { + DWORD cb; + WCHAR *lpReserved; + WCHAR *lpDesktop; + WCHAR *lpTitle; + DWORD dwX; + DWORD dwY; + DWORD dwXSize; + DWORD dwYSize; + DWORD dwXCountChars; + DWORD dwYCountChars; + DWORD dwFillAttribute; + DWORD dwFlags; + WORD wShowWindow; + WORD cbReserved2; + BYTE *lpReserved2; + HANDLE hStdInput; + HANDLE hStdOutput; + HANDLE hStdError; + } STARTUPINFOW, *LPSTARTUPINFOW; + + typedef DWORD (__stdcall *PTHREAD_START_ROUTINE)( + VOID *lpThreadParameter + ); + typedef PTHREAD_START_ROUTINE LPTHREAD_START_ROUTINE; + + extern "C" + { + __declspec(dllimport) BOOL __stdcall CreateProcessW (WCHAR const *lpApplicationName, WCHAR *lpCommandLine, SECURITY_ATTRIBUTES *lpProcessAttributes, SECURITY_ATTRIBUTES *lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, VOID *lpEnvironment, WCHAR const *lpCurrentDirectory, STARTUPINFOW *lpStartupInfo, PROCESS_INFORMATION *lpProcessInformation); + __declspec(dllimport) HANDLE __stdcall CreateThread (SECURITY_ATTRIBUTES *lpThreadAttributes, SIZE_T dwStackSize, PTHREAD_START_ROUTINE lpStartAddress, VOID *lpParameter, DWORD dwCreationFlags, DWORD *lpThreadId); + __declspec(dllimport) DWORD __stdcall GetCurrentThreadId(VOID); + __declspec(dllimport) BOOL __stdcall GetExitCodeProcess(HANDLE hProcess, DWORD *lpExitCode); + __declspec(dllimport) void __stdcall ExitProcess (UINT uExitCode); + } + + // NOTE: um/memoryapi.h //////////////////////////////////////////////////////////////////////// + extern "C" + { + __declspec(dllimport) VOID * __stdcall VirtualAlloc (VOID *lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect); + __declspec(dllimport) BOOL __stdcall VirtualProtect(VOID *lpAddress, SIZE_T dwSize, DWORD flNewProtect, DWORD *lpflOldProtect); + __declspec(dllimport) BOOL __stdcall VirtualFree (VOID *lpAddress, SIZE_T dwSize, DWORD dwFreeType); + } + + // NOTE: shared/bcrypt.h /////////////////////////////////////////////////////////////////////// + typedef VOID *BCRYPT_ALG_HANDLE; + typedef LONG NTSTATUS; + + extern "C" + { + __declspec(dllimport) NTSTATUS __stdcall BCryptOpenAlgorithmProvider(BCRYPT_ALG_HANDLE *phAlgorithm, const WCHAR *pszAlgId, const WCHAR *pszImplementation, ULONG dwFlags); + __declspec(dllimport) NTSTATUS __stdcall BCryptGenRandom (BCRYPT_ALG_HANDLE hAlgorithm, UCHAR *pbBuffer, ULONG cbBuffer, ULONG dwFlags); + } + + // NOTE: um/shellapi.h ///////////////////////////////////////////////////////////////////////// + extern "C" + { + __declspec(dllimport) HINSTANCE __stdcall ShellExecuteA(HWND hwnd, CHAR const *lpOperation, CHAR const *lpFile, CHAR const *lpParameters, CHAR const *lpDirectory, INT nShowCmd); + __declspec(dllimport) HINSTANCE __stdcall ShellExecuteW(HWND hwnd, WCHAR const *lpOperation, WCHAR const *lpFile, WCHAR const *lpParameters, WCHAR const *lpDirectory, INT nShowCmd); + } + + // NOTE: um/debugapi.h ///////////////////////////////////////////////////////////////////////// + extern "C" + { + __declspec(dllimport) BOOL __stdcall IsDebuggerPresent(); + } + + // NOTE: um/namedpipeapi.h ///////////////////////////////////////////////////////////////////// + extern "C" + { + __declspec(dllimport) BOOL __stdcall CreatePipe (HANDLE *hReadPipe, HANDLE *hWritePipe, LPSECURITY_ATTRIBUTES lpPipeAttributes, DWORD nSize); + __declspec(dllimport) BOOL __stdcall PeekNamedPipe(HANDLE hNamedPipe, VOID *lpBuffer, DWORD nBufferSize, DWORD *lpBytesRead, DWORD *lpTotalBytesAvail, DWORD *lpBytesLeftThisMessage); + } + + // NOTE: um/handleapi.h //////////////////////////////////////////////////////////////////////// + extern "C" + { + __declspec(dllimport) BOOL __stdcall SetHandleInformation(HANDLE hObject, DWORD dwMask, DWORD dwFlags); + } + + // NOTE: um/commdlg.h ////////////////////////////////////////////////////////////////////////// + typedef UINT_PTR (__stdcall *LPOFNHOOKPROC)(HWND, UINT, WPARAM, LPARAM); + typedef struct tagOFNW { + DWORD lStructSize; + HWND hwndOwner; + HINSTANCE hInstance; + WCHAR const * lpstrFilter; + LPWSTR lpstrCustomFilter; + DWORD nMaxCustFilter; + DWORD nFilterIndex; + LPWSTR lpstrFile; + DWORD nMaxFile; + LPWSTR lpstrFileTitle; + DWORD nMaxFileTitle; + WCHAR const * lpstrInitialDir; + WCHAR const * lpstrTitle; + DWORD Flags; + WORD nFileOffset; + WORD nFileExtension; + WCHAR const * lpstrDefExt; + LPARAM lCustData; + LPOFNHOOKPROC lpfnHook; + WCHAR const * lpTemplateName; + #ifdef _MAC + LPEDITMENU lpEditInfo; + LPCSTR lpstrPrompt; + #endif + #if (_WIN32_WINNT >= 0x0500) + void * pvReserved; + DWORD dwReserved; + DWORD FlagsEx; + #endif // (_WIN32_WINNT >= 0x0500) + } OPENFILENAMEW, *LPOPENFILENAMEW; + + + #define OFN_READONLY 0x00000001 + #define OFN_OVERWRITEPROMPT 0x00000002 + #define OFN_HIDEREADONLY 0x00000004 + #define OFN_NOCHANGEDIR 0x00000008 + #define OFN_SHOWHELP 0x00000010 + #define OFN_ENABLEHOOK 0x00000020 + #define OFN_ENABLETEMPLATE 0x00000040 + #define OFN_ENABLETEMPLATEHANDLE 0x00000080 + #define OFN_NOVALIDATE 0x00000100 + #define OFN_ALLOWMULTISELECT 0x00000200 + #define OFN_EXTENSIONDIFFERENT 0x00000400 + #define OFN_PATHMUSTEXIST 0x00000800 + #define OFN_FILEMUSTEXIST 0x00001000 + #define OFN_CREATEPROMPT 0x00002000 + #define OFN_SHAREAWARE 0x00004000 + #define OFN_NOREADONLYRETURN 0x00008000 + #define OFN_NOTESTFILECREATE 0x00010000 + #define OFN_NONETWORKBUTTON 0x00020000 + #define OFN_NOLONGNAMES 0x00040000 // force no long names for 4.x modules + #if(WINVER >= 0x0400) + #define OFN_EXPLORER 0x00080000 // new look commdlg + #define OFN_NODEREFERENCELINKS 0x00100000 + #define OFN_LONGNAMES 0x00200000 // force long names for 3.x modules + // OFN_ENABLEINCLUDENOTIFY and OFN_ENABLESIZING require + // Windows 2000 or higher to have any effect. + #define OFN_ENABLEINCLUDENOTIFY 0x00400000 // send include message to callback + #define OFN_ENABLESIZING 0x00800000 + #endif /* WINVER >= 0x0400 */ + #if (_WIN32_WINNT >= 0x0500) + #define OFN_DONTADDTORECENT 0x02000000 + #define OFN_FORCESHOWHIDDEN 0x10000000 // Show All files including System and hidden files + #endif // (_WIN32_WINNT >= 0x0500) + + //FlagsEx Values + #if (_WIN32_WINNT >= 0x0500) + #define OFN_EX_NOPLACESBAR 0x00000001 + #endif // (_WIN32_WINNT >= 0x0500) + + extern "C" + { + __declspec(dllimport) BOOL __stdcall GetSaveFileNameW(LPOPENFILENAMEW); + __declspec(dllimport) BOOL __stdcall GetOpenFileNameW(LPOPENFILENAMEW); + } + + // NOTE: um/shlwapi.h ////////////////////////////////////////////////////////////////////////// + extern "C" + { + __declspec(dllimport) BOOL __stdcall PathRelativePathToW(WCHAR *pszPath, WCHAR const *pszFrom, DWORD dwAttrFrom, WCHAR const *pszTo, DWORD dwAttrTo); + __declspec(dllimport) BOOL __stdcall PathIsRelativeW(WCHAR *pszPath); + } + + // NOTE: um/pathcch.h ////////////////////////////////////////////////////////////////////////// + typedef enum PATHCCH_OPTIONS + { + PATHCCH_NONE = 0x0, + + // This option allows applications to gain access to long paths. It has two + // different behaviors. For process configured to enable long paths it will allow + // the returned path to be longer than the max path limit that is normally imposed. + // For process that are not this option will convert long paths into the extended + // length DOS device form (with \\?\ prefix) when the path is longer than the limit. + // This form is not length limited by the Win32 file system API on all versions of Windows. + // This second behavior is the same behavior for OSes that don't have the long path feature. + // This can not be specified with PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH. + PATHCCH_ALLOW_LONG_PATHS = 0x01, + + // Can only be used when PATHCCH_ALLOW_LONG_PATHS is specified. This + // Forces the API to treat the caller as long path enabled, independent of the + // process's long name enabled state. Cannot be used with PATHCCH_FORCE_DISABLE_LONG_NAME_PROCESS. + PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS = 0x02, + + // Can only be used when PATHCCH_ALLOW_LONG_PATHS is specified. This + // Forces the API to treat the caller as long path disabled, independent of the + // process's long name enabled state. Cannot be used with PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS. + PATHCCH_FORCE_DISABLE_LONG_NAME_PROCESS = 0x04, + + // Disable the normalization of path segments that includes removing trailing dots and spaces. + // This enables access to paths that win32 path normalization will block. + PATHCCH_DO_NOT_NORMALIZE_SEGMENTS = 0x08, + + // Convert the input path into the extended length DOS device path form (with the \\?\ prefix) + // if not already in that form. This enables access to paths that are otherwise not addressable + // due to Win32 normalization rules (that can strip trailing dots and spaces) and path + // length limitations. This option implies the same behavior of PATHCCH_DO_NOT_NORMALIZE_SEGMENTS. + // This can not be specified with PATHCCH_ALLOW_LONG_PATHS. + PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH = 0x10, + + // When combining or normalizing a path ensure there is a trailing backslash. + PATHCCH_ENSURE_TRAILING_SLASH = 0x020, + + // Convert forward slashes to back slashes and collapse multiple slashes. + // This is needed to to support sub-path or identity comparisons. + PATHCCH_CANONICALIZE_SLASHES = 0x040, + } PATHCCH_OPTIONS; + + extern "C" + { + __declspec(dllimport) HRESULT __stdcall PathCchCanonicalizeEx(PWSTR pszPathOut, size_t cchPathOut, WCHAR const *pszPathIn, ULONG dwFlags); + }; + + // NOTE: um/errhandlingapi.h /////////////////////////////////////////////////////////////////// + extern "C" + { + __declspec(dllimport) VOID __stdcall RaiseException(DWORD dwExceptionCode, DWORD dwExceptionFlags, DWORD nNumberOfArguments, const ULONG_PTR* lpArguments); + }; + + // NOTE: include/excpt.h /////////////////////////////////////////////////////////////////// + #define EXCEPTION_EXECUTE_HANDLER 1 + #define EXCEPTION_CONTINUE_SEARCH 0 + #define EXCEPTION_CONTINUE_EXECUTION (-1) + + DN_MSVC_WARNING_POP +#endif // !defined(_INC_WINDOWS) +#endif // !defined(DN_OS_WINDOWS_H) + // DN: Single header generator inlined this file => #include "OS/dn_os_w32.h" +#if !defined(DN_OS_WIN32_H) +#define DN_OS_WIN32_H + +struct DN_W32Error +{ + unsigned long code; + DN_Str8 msg; +}; + +struct DN_W32FolderIteratorW +{ + void *handle; + DN_Str16 file_name; + wchar_t file_name_buf[512]; +}; + +enum DN_W32SyncPrimitiveType +{ + DN_OSW32SyncPrimitiveType_Semaphore, + DN_OSW32SyncPrimitiveType_Mutex, + DN_OSW32SyncPrimitiveType_ConditionVariable, +}; + +struct DN_W32SyncPrimitive +{ + union + { + void *sem; + CRITICAL_SECTION mutex; + CONDITION_VARIABLE cv; + }; + + DN_W32SyncPrimitive *next; +}; + +typedef HRESULT DN_W32SetThreadDescriptionFunc(HANDLE hThread, PWSTR const lpThreadDescription); +struct DN_W32Core +{ + DN_W32SetThreadDescriptionFunc *set_thread_description; + LARGE_INTEGER qpc_frequency; + void *bcrypt_rng_handle; + bool bcrypt_init_success; + bool sym_initialised; + + CRITICAL_SECTION sync_primitive_free_list_mutex; + DN_W32SyncPrimitive *sync_primitive_free_list; +}; + +DN_API void DN_W32_ThreadSetName (DN_Str8 name); + +DN_API DN_Str16 DN_W32_ErrorCodeToMsg16Alloc(uint32_t error_code); +DN_API DN_W32Error DN_W32_ErrorCodeToMsg (DN_Arena *arena, uint32_t error_code); +DN_API DN_W32Error DN_W32_ErrorCodeToMsgAlloc (uint32_t error_code); +DN_API DN_W32Error DN_W32_LastError (DN_Arena *arena); +DN_API DN_W32Error DN_W32_LastErrorAlloc (); +DN_API void DN_W32_MakeProcessDPIAware (); + +// NOTE: Windows Str8 <-> Str16 //////////////////////////////////////////////////////////////////// +DN_API DN_Str16 DN_W32_Str8ToStr16 (DN_Arena *arena, DN_Str8 src); +DN_API int DN_W32_Str8ToStr16Buffer (DN_Str16 src, char *dest, int dest_size); +DN_API DN_Str8 DN_W32_Str16ToStr8 (DN_Arena *arena, DN_Str16 src); +DN_API int DN_W32_Str16ToStr8Buffer (DN_Str16 src, char *dest, int dest_size); +DN_API DN_Str8 DN_W32_Str16ToStr8FromHeap(DN_Str16 src); + +// NOTE: Path navigation /////////////////////////////////////////////////////////////////////////// +DN_API DN_Str16 DN_W32_EXEPathW (DN_Arena *arena); +DN_API DN_Str16 DN_W32_EXEDirW (DN_Arena *arena); +DN_API DN_Str8 DN_W32_WorkingDir (DN_Arena *arena, DN_Str8 suffix); +DN_API DN_Str16 DN_W32_WorkingDirW (DN_Arena *arena, DN_Str16 suffix); +DN_API bool DN_W32_DirWIterate (DN_Str16 path, DN_W32FolderIteratorW *it); +#endif // !defined(DN_OS_WIN32) +#elif defined(DN_PLATFORM_POSIX) + // DN: Single header generator inlined this file => #include "OS/dn_os_posix.h" +#pragma once + +#include +#include + +/* +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$$$$$\ $$$$$$\ $$$$$$$\ $$$$$$\ $$$$$$\ $$$$$$\ $$\ $$\ +// $$ __$$\ $$ __$$\ $$ __$$\ $$ __$$\ $$ __$$\ \_$$ _|$$ | $$ | +// $$ / $$ |$$ / \__| $$ | $$ |$$ / $$ |$$ / \__| $$ | \$$\ $$ | +// $$ | $$ |\$$$$$$\ $$$$$$$ |$$ | $$ |\$$$$$$\ $$ | \$$$$ / +// $$ | $$ | \____$$\ $$ ____/ $$ | $$ | \____$$\ $$ | $$ $$< +// $$ | $$ |$$\ $$ | $$ | $$ | $$ |$$\ $$ | $$ | $$ /\$$\ +// $$$$$$ |\$$$$$$ | $$ | $$$$$$ |\$$$$$$ |$$$$$$\ $$ / $$ | +// \______/ \______/ \__| \______/ \______/ \______|\__| \__| +// +// dn_os_posix.h +// +//////////////////////////////////////////////////////////////////////////////////////////////////// +*/ + +struct DN_POSIXProcSelfStatus +{ + char name[64]; + DN_U8 name_size; + DN_U32 pid; + DN_U64 vm_peak; + DN_U32 vm_size; +}; + +// NOTE: The POSIX implementation disallows copies of synchronisation objects in +// general hence we have to dynamically allocate these primitives to maintain a +// consistent address. +// +// +// Source: The Open Group Base Specifications Issue 7, 2018 edition +// https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_09_09 +// +// 2.9.9 Synchronization Object Copies and Alternative Mappings +// +// For barriers, condition variables, mutexes, and read-write locks, [TSH] +// [Option Start] if the process-shared attribute is set to +// PTHREAD_PROCESS_PRIVATE, [Option End] only the synchronization object at the +// address used to initialize it can be used for performing synchronization. The +// effect of referring to another mapping of the same object when locking, +// unlocking, or destroying the object is undefined. [...] The effect of +// referring to a copy of the object when locking, unlocking, or destroying it +// is undefined. + +enum DN_POSIXSyncPrimitiveType +{ + DN_OSPOSIXSyncPrimitiveType_Semaphore, + DN_OSPOSIXSyncPrimitiveType_Mutex, + DN_OSPOSIXSyncPrimitiveType_ConditionVariable, +}; + +struct DN_POSIXSyncPrimitive +{ + union + { + sem_t sem; + pthread_mutex_t mutex; + pthread_cond_t cv; + }; + DN_POSIXSyncPrimitive *next; +}; + +struct DN_POSIXCore +{ + DN_POSIXSyncPrimitive *sync_primitive_free_list; + pthread_mutex_t sync_primitive_free_list_mutex; +}; + +DN_API void DN_Posix_ThreadSetName(DN_Str8 name); +DN_API DN_POSIXProcSelfStatus DN_Posix_ProcSelfStatus(); +#else + #error Please define a platform e.g. 'DN_PLATFORM_WIN32' to enable the correct implementation for platform APIs +#endif + +// DN: Single header generator inlined this file => #include "OS/dn_os_tls.h" +#if !defined(DN_OS_TLS_H) +#define DN_OS_TLS_H + +// NOTE: DN_OSErrSink ///////////////////////////////////////////////////////////////////////////// +enum DN_OSErrSinkMode +{ + DN_OSErrSinkMode_Nil, // Default behaviour to accumulate errors into the sink + DN_OSErrSinkMode_DebugBreakOnEndAndLog, // Debug break (int3) when error is encountered and the sink is ended by the 'end and log' functions. + DN_OSErrSinkMode_ExitOnError, // When an error is encountered, exit the program with the error code of the error that was caught. +}; + +struct DN_OSErrSinkMsg +{ + DN_I32 error_code; + DN_Str8 msg; + DN_CallSite call_site; + DN_OSErrSinkMsg *next; + DN_OSErrSinkMsg *prev; +}; + +struct DN_OSErrSinkNode +{ + DN_CallSite call_site; // Call site that the node was created + DN_OSErrSinkMode mode; // Controls how the sink behaves when an error is registered onto the sink. + DN_OSErrSinkMsg *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_OSErrSink +{ + DN_Arena * arena; // Dedicated allocator from the thread's local storage + DN_OSErrSinkNode stack[128]; // Each entry contains errors accumulated between a [begin, end] region of the active sink. + DN_USize stack_size; +}; + +enum DN_OSTLSArena +{ + DN_OSTLSArena_Main, // NOTE: Arena for permanent allocations + DN_OSTLSArena_ErrorSink, // NOTE: Arena for logging error information for this thread + + // NOTE: Per-thread scratch arenas (2 to prevent aliasing) + DN_OSTLSArena_TMem0, + DN_OSTLSArena_TMem1, + + DN_OSTLSArena_Count, +}; + +struct DN_OSTLS +{ + DN_B32 init; // Flag to track if TLS has been initialised + DN_U64 thread_id; + DN_CallSite call_site; // Stores call-site information when requested by thread + DN_OSErrSink err_sink; // Error handling state + DN_Arena arenas[DN_OSTLSArena_Count]; // Default arenas that the thread has access to implicitly + DN_Arena * arena_stack[8]; // Active stack of arenas push/popped arenas on into the TLS + DN_USize arena_stack_index; + + DN_Arena * frame_arena; + char name[64]; + DN_U8 name_size; +}; + +// Push the temporary memory arena when retrieved, popped when the arena goes +// out of scope. Pushed arenas are used automatically as the allocator in TLS +// suffixed function. +enum DN_OSTLSPushTMem +{ + DN_OSTLSPushTMem_No, + DN_OSTLSPushTMem_Yes, +}; + +struct DN_OSTLSTMem +{ + DN_OSTLSTMem(DN_OSTLS *context, uint8_t context_index, DN_OSTLSPushTMem push_scratch); + ~DN_OSTLSTMem(); + DN_Arena *arena; + DN_B32 destructed; + DN_OSTLSPushTMem push_arena; + DN_ArenaTempMem temp_mem; +}; + +struct DN_OSTLSInitArgs +{ + DN_U64 reserve; + DN_U64 commit; + DN_U64 err_sink_reserve; + DN_U64 err_sink_commit; +}; + +// NOTE: DN_OSTLS //////////////////////////////////////////////////////////////////////////////////// +DN_API void DN_OS_TLSInit (DN_OSTLS *tls, DN_OSTLSInitArgs args); +DN_API void DN_OS_TLSDeinit (DN_OSTLS *tls); +DN_API DN_OSTLS * DN_OS_TLSGet (); +DN_API void DN_OS_TLSSetCurrentThreadTLS (DN_OSTLS *tls); +DN_API DN_Arena * DN_OS_TLSArena (); +DN_API DN_OSTLSTMem DN_OS_TLSGetTMem (void const *conflict_arena, DN_OSTLSPushTMem push_tmp_mem); +DN_API void DN_OS_TLSPushArena (DN_Arena *arena); +DN_API void DN_OS_TLSPopArena (); +DN_API DN_Arena * DN_OS_TLSTopArena (); +DN_API void DN_OS_TLSBeginFrame (DN_Arena *frame_arena); +DN_API DN_Arena * DN_OS_TLSFrameArena (); +#define DN_OS_TLSSaveCallSite do { DN_OS_TLSGet()->call_site = DN_CALL_SITE; } while (0) +#define DN_OS_TLSTMem(...) DN_OS_TLSGetTMem(__VA_ARGS__, DN_OSTLSPushTMem_No) +#define DN_OS_TLSPushTMem(...) DN_OS_TLSGetTMem(__VA_ARGS__, DN_OSTLSPushTMem_Yes) + +// NOTE: DN_OS_ErrSink //////////////////////////////////////////////////////////////////////////// +DN_API DN_OSErrSink * DN_OS_ErrSinkBegin_ (DN_OSErrSinkMode mode, DN_CallSite call_site); +#define DN_OS_ErrSinkBegin(mode) DN_OS_ErrSinkBegin_(mode, DN_CALL_SITE) +#define DN_OS_ErrSinkBeginDefault() DN_OS_ErrSinkBegin(DN_OSErrSinkMode_Nil) +DN_API bool DN_OS_ErrSinkHasError (DN_OSErrSink *err); +DN_API DN_OSErrSinkMsg *DN_OS_ErrSinkEnd (DN_Arena *arena, DN_OSErrSink *err); +DN_API DN_Str8 DN_OS_ErrSinkEndStr8 (DN_Arena *arena, DN_OSErrSink *err); +DN_API void DN_OS_ErrSinkEndAndIgnore (DN_OSErrSink *err); +DN_API bool DN_OS_ErrSinkEndAndLogError_ (DN_OSErrSink *err, DN_CallSite call_site, DN_Str8 msg); +DN_API bool DN_OS_ErrSinkEndAndLogErrorFV_ (DN_OSErrSink *err, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API bool DN_OS_ErrSinkEndAndLogErrorF_ (DN_OSErrSink *err, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, ...); +DN_API void DN_OS_ErrSinkEndAndExitIfErrorF_ (DN_OSErrSink *err, DN_CallSite call_site, DN_U32 exit_val, DN_FMT_ATTRIB char const *fmt, ...); +DN_API void DN_OS_ErrSinkEndAndExitIfErrorFV_ (DN_OSErrSink *err, DN_CallSite call_site, DN_U32 exit_val, DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API void DN_OS_ErrSinkAppendFV_ (DN_OSErrSink *err, DN_U32 error_code, DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API void DN_OS_ErrSinkAppendF_ (DN_OSErrSink *err, DN_U32 error_code, DN_FMT_ATTRIB char const *fmt, ...); +#define DN_OS_ErrSinkEndAndLogError(err, err_msg) DN_OS_ErrSinkEndAndLogError_(err, DN_CALL_SITE, err_msg) +#define DN_OS_ErrSinkEndAndLogErrorFV(err, fmt, args) DN_OS_ErrSinkEndAndLogErrorFV_(err, DN_CALL_SITE, fmt, args) +#define DN_OS_ErrSinkEndAndLogErrorF(err, fmt, ...) DN_OS_ErrSinkEndAndLogErrorF_(err, DN_CALL_SITE, fmt, ##__VA_ARGS__) +#define DN_OS_ErrSinkEndAndExitIfErrorFV(err, exit_val, fmt, args) DN_OS_ErrSinkEndAndExitIfErrorFV_(err, DN_CALL_SITE, exit_val, fmt, args) +#define DN_OS_ErrSinkEndAndExitIfErrorF(err, exit_val, fmt, ...) DN_OS_ErrSinkEndAndExitIfErrorF_(err, DN_CALL_SITE, exit_val, fmt, ##__VA_ARGS__) + +#define DN_OS_ErrSinkAppendFV(error, error_code, fmt, args) \ + do { \ + DN_OS_TLSSaveCallSite; \ + DN_OS_ErrSinkAppendFV_(error, error_code, fmt, args); \ + } while (0) + +#define DN_OS_ErrSinkAppendF(error, error_code, fmt, ...) \ + do { \ + DN_OS_TLSSaveCallSite; \ + DN_OS_ErrSinkAppendF_(error, error_code, fmt, ##__VA_ARGS__); \ + } while (0) + +#endif // defined(DN_OS_TLS_H) +// DN: Single header generator inlined this file => #include "OS/dn_os.h" +#if !defined(DN_OS_H) +#define DN_OS_H + +#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 + +#if defined(DN_PLATFORM_EMSCRIPTEN) + #include // emscripten_fetch (for DN_OSHttpResponse) +#endif + +// NOTE: DN_OSDate ///////////////////////////////////////////////////////////////////////////////// +struct DN_OSDateTimeStr8 +{ + char date[DN_ArrayCountU("YYYY-MM-SS")]; + DN_U8 date_size; + char hms[DN_ArrayCountU("HH:MM:SS")]; + DN_U8 hms_size; +}; + +struct DN_OSDateTime +{ + DN_U8 day; + DN_U8 month; + DN_U16 year; + DN_U8 hour; + DN_U8 minutes; + DN_U8 seconds; +}; + +struct DN_OSTimer /// Record time between two time-points using the OS's performance counter. +{ + DN_U64 start; + DN_U64 end; +}; + +#if !defined(DN_NO_OS_FILE_API) +// 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, +}; +#endif // DN_NO_OS_FILE_API + +// NOTE: DN_OSPath //////////////////////////////////////////////////////////////////////////////// +#if !defined(DN_OSPathSeperator) + #if defined(DN_OS_WIN32) + #define DN_OSPathSeperator "\\" + #else + #define DN_OSPathSeperator "/" + #endif + #define DN_OSPathSeperatorString DN_STR8(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_Slice environment; +}; + +// NOTE: DN_OSSemaphore //////////////////////////////////////////////////////////////////////////// +DN_U32 const DN_OS_SEMAPHORE_INFINITE_TIMEOUT = UINT32_MAX; + +struct DN_OSSemaphore +{ + 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_OSThread +{ + DN_FStr8<64> name; + DN_OSTLS tls; + DN_OSTLSInitArgs tls_init_args; + void *handle; + DN_U64 thread_id; + void *user_context; + DN_OSThreadFunc *func; + DN_OSSemaphore init_semaphore; +}; + +// NOTE: DN_OSHttp ///////////////////////////////////////////////////////////////////////////////// +enum DN_OSHttpRequestSecure +{ + DN_OSHttpRequestSecure_No, + DN_OSHttpRequestSecure_Yes, +}; + +struct DN_OSHttpResponse +{ + // NOTE: Response data + DN_U32 error_code; + DN_Str8 error_msg; + DN_U16 http_status; + DN_Str8 body; + DN_B32 done; + + // NOTE: Book-keeping + DN_Arena *arena; // Allocates memory for the response + + // NOTE: Async book-keeping + // Synchronous HTTP response uses the TLS scratch arena whereas async + // calls use their own dedicated arena. + DN_Arena tmp_arena; + DN_Arena *tmem_arena; + DN_Str8Builder builder; + DN_OSSemaphore on_complete_semaphore; + + #if defined(DN_PLATFORM_EMSCRIPTEN) + emscripten_fetch_t *em_handle; + #elif defined(DN_PLATFORM_WIN32) + HINTERNET w32_request_session; + HINTERNET w32_request_connection; + HINTERNET w32_request_handle; + #endif +}; + +struct DN_OSInitArgs +{ + DN_U64 tls_reserve; + DN_U64 tls_commit; + DN_U64 tls_err_sink_reserve; + DN_U64 tls_err_sink_commit; +}; + +struct DN_OSCore +{ + DN_CPUReport cpu_report; + DN_OSTLS tls; // Thread local storage state for the main thread. + + // NOTE: Logging /////////////////////////////////////////////////////////////////////////////// + DN_LOGEmitFromTypeFVFunc * log_callback; // Set this pointer to override the logging routine + void * log_user_data; // User pointer passed into 'log_callback' + 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 + + // 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_Arena arena; + void *platform_context; +}; + +struct DN_OSDiskSpace +{ + bool success; + DN_U64 avail; + DN_U64 size; +}; + +DN_API void DN_OS_Init (DN_OSCore *os, DN_OSInitArgs *args); +DN_API void DN_OS_EmitLogsWithOSPrintFunctions(DN_OSCore *os); +DN_API void DN_OS_DumpThreadContextArenaStat (DN_Str8 file_path); + +// NOTE: Memory //////////////////////////////////////////////////////////////////////////////////// +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); + +// NOTE: Heap +DN_API void * DN_OS_MemAlloc (DN_USize size, DN_ZeroMem zero_mem); +DN_API void DN_OS_MemDealloc (void *ptr); + +// NOTE: DN_OSDate ///////////////////////////////////////////////////////////////////////////////// +DN_API DN_OSDateTime DN_OS_DateLocalTimeNow (); +DN_API DN_OSDateTimeStr8 DN_OS_DateLocalTimeStr8Now(char date_separator = '-', char hms_separator = ':'); +DN_API DN_OSDateTimeStr8 DN_OS_DateLocalTimeStr8 (DN_OSDateTime 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_OSDateTime DN_OS_DateUnixTimeSToDate (DN_U64 time); +DN_API DN_U64 DN_OS_DateLocalToUnixTimeS(DN_OSDateTime date); +DN_API DN_U64 DN_OS_DateToUnixTimeS (DN_OSDateTime date); +DN_API bool DN_OS_DateIsValid (DN_OSDateTime date); + +// NOTE: Other ///////////////////////////////////////////////////////////////////////////////////// +DN_API bool DN_OS_SecureRNGBytes (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); +#define DN_OS_EXEDirFromTLS() DN_OS_EXEDir(DN_OS_TLSTopArena()) +DN_API void DN_OS_SleepMs (DN_UInt milliseconds); + +// NOTE: Counters ////////////////////////////////////////////////////////////////////////////////// +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); +#if !defined(DN_NO_OS_FILE_API) +// NOTE: File system paths ///////////////////////////////////////////////////////////////////////// +DN_API DN_OSPathInfo DN_OS_PathInfo (DN_Str8 path); +DN_API bool DN_OS_FileIsOlderThan(DN_Str8 file, DN_Str8 check_against); +DN_API bool DN_OS_PathDelete (DN_Str8 path); +DN_API bool DN_OS_FileExists (DN_Str8 path); +DN_API bool DN_OS_CopyFile (DN_Str8 src, DN_Str8 dest, bool overwrite, DN_OSErrSink *err); +DN_API bool DN_OS_MoveFile (DN_Str8 src, DN_Str8 dest, bool overwrite, DN_OSErrSink *err); +DN_API bool DN_OS_MakeDir (DN_Str8 path); +DN_API bool DN_OS_DirExists (DN_Str8 path); +DN_API bool DN_OS_DirIterate (DN_Str8 path, DN_OSDirIterator *it); + +// NOTE: R/W Stream API //////////////////////////////////////////////////////////////////////////// +DN_API DN_OSFile DN_OS_FileOpen (DN_Str8 path, DN_OSFileOpen open_mode, DN_OSFileAccess access, DN_OSErrSink *err); +DN_API DN_OSFileRead DN_OS_FileRead (DN_OSFile *file, void *buffer, DN_USize size, DN_OSErrSink *err); +DN_API bool DN_OS_FileWritePtr(DN_OSFile *file, void const *data, DN_USize size, DN_OSErrSink *err); +DN_API bool DN_OS_FileWrite (DN_OSFile *file, DN_Str8 buffer, DN_OSErrSink *err); +DN_API bool DN_OS_FileWriteFV (DN_OSFile *file, DN_OSErrSink *err, DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API bool DN_OS_FileWriteF (DN_OSFile *file, DN_OSErrSink *err, DN_FMT_ATTRIB char const *fmt, ...); +DN_API bool DN_OS_FileFlush (DN_OSFile *file, DN_OSErrSink *err); +DN_API void DN_OS_FileClose (DN_OSFile *file); + +// NOTE: R/W Entire File /////////////////////////////////////////////////////////////////////////// +DN_API DN_Str8 DN_OS_ReadAll (DN_Arena *arena, DN_Str8 path, DN_OSErrSink *err); +#define DN_OS_ReadAllFromTLS(...) DN_OS_ReadAll(DN_OS_TLSTopArena(), ##__VA_ARGS__) +DN_API bool DN_OS_WriteAll (DN_Str8 path, DN_Str8 buffer, DN_OSErrSink *err); +DN_API bool DN_OS_WriteAllFV (DN_Str8 path, DN_OSErrSink *err, DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API bool DN_OS_WriteAllF (DN_Str8 path, DN_OSErrSink *err, DN_FMT_ATTRIB char const *fmt, ...); +DN_API bool DN_OS_WriteAllSafe (DN_Str8 path, DN_Str8 buffer, DN_OSErrSink *err); +DN_API bool DN_OS_WriteAllSafeFV (DN_Str8 path, DN_OSErrSink *err, DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API bool DN_OS_WriteAllSafeF (DN_Str8 path, DN_OSErrSink *err, DN_FMT_ATTRIB char const *fmt, ...); +#endif // !defined(DN_NO_OS_FILE_API) + +// NOTE: File system paths ///////////////////////////////////////////////////////////////////////// +DN_API bool DN_OS_PathAddRef (DN_Arena *arena, DN_OSPath *fs_path, DN_Str8 path); +#define DN_OS_PathAddRefFromTLS(...) DN_OS_PathAddRef(DN_OS_TLSTopArena(), ##__VA_ARGS__) +#define DN_OS_PathAddRefFromFrame(...) DN_OS_PathAddRef(DN_OS_TLSFrameArena(), ##__VA_ARGS__) +DN_API bool DN_OS_PathAdd (DN_Arena *arena, DN_OSPath *fs_path, DN_Str8 path); +#define DN_OS_PathAddFromTLS(...) DN_OS_PathAdd(DN_OS_TLSTopArena(), ##__VA_ARGS__) +#define DN_OS_PathAddFromFrame(...) DN_OS_PathAdd(DN_OS_TLSFrameArena(), ##__VA_ARGS__) +DN_API bool DN_OS_PathAddF (DN_Arena *arena, DN_OSPath *fs_path, DN_FMT_ATTRIB char const *fmt, ...); +#define DN_OS_PathAddFFromTLS(...) DN_OS_PathAddF(DN_OS_TLSTopArena(), ##__VA_ARGS__) +#define DN_OS_PathAddFFromFrame(...) DN_OS_PathAddF(DN_OS_TLSFrameArena(), ##__VA_ARGS__) +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); +#define DN_OS_PathBuildWithSeperatorFromTLS(...) DN_OS_PathBuildWithSeperator(DN_OS_TLSTopArena(), ##__VA_ARGS__) +#define DN_OS_PathBuildWithSeperatorFromFrame(...) DN_OS_PathBuildWithSeperator(DN_OS_TLSFrameArena(), ##__VA_ARGS__) +DN_API DN_Str8 DN_OS_PathTo (DN_Arena *arena, DN_Str8 path, DN_Str8 path_separtor); +#define DN_OS_PathToFromTLS(...) DN_OS_PathTo(DN_OS_TLSTopArena(), ##__VA_ARGS__) +#define DN_OS_PathToFromFrame(...) DN_OS_PathTo(DN_OS_TLSFrameArena(), ##__VA_ARGS__) +DN_API DN_Str8 DN_OS_PathToF (DN_Arena *arena, DN_Str8 path_separator, DN_FMT_ATTRIB char const *fmt, ...); +#define DN_OS_PathToFFromTLS(...) DN_OS_PathToF(DN_OS_TLSTopArena(), ##__VA_ARGS__) +#define DN_OS_PathToFFromFrame(...) DN_OS_PathToF(DN_OS_TLSFrameArena(), ##__VA_ARGS__) +DN_API DN_Str8 DN_OS_Path (DN_Arena *arena, DN_Str8 path); +#define DN_OS_PathFromTLS(...) DN_OS_Path(DN_OS_TLSTopArena(), ##__VA_ARGS__) +#define DN_OS_PathFromFrame(...) DN_OS_Path(DN_OS_TLSFrameArena(), ##__VA_ARGS__) +DN_API DN_Str8 DN_OS_PathF (DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, ...); +#define DN_OS_PathFFromTLS(...) DN_OS_PathF(DN_OS_TLSTopArena(), ##__VA_ARGS__) +#define DN_OS_PathFFromFrame(...) DN_OS_PathF(DN_OS_TLSFrameArena(), ##__VA_ARGS__) + +#define DN_OS_PathBuildFwdSlash(allocator, fs_path) DN_OS_PathBuildWithSeparator(allocator, fs_path, DN_STR8("/")) +#define DN_OS_PathBuildBackSlash(allocator, fs_path) DN_OS_PathBuildWithSeparator(allocator, fs_path, DN_STR8("\\")) +#define DN_OS_PathBuild(allocator, fs_path) DN_OS_PathBuildWithSeparator(allocator, fs_path, DN_OSPathSeparatorString) + +// NOTE: DN_OSExec ///////////////////////////////////////////////////////////////////////////////// +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_OSErrSink *err); +DN_API DN_OSExecResult DN_OS_ExecWait (DN_OSExecAsyncHandle handle, DN_Arena *arena, DN_OSErrSink *err); +DN_API DN_OSExecAsyncHandle DN_OS_ExecAsync (DN_Slice cmd_line, DN_OSExecArgs *args, DN_OSErrSink *err); +DN_API DN_OSExecResult DN_OS_Exec (DN_Slice cmd_line, DN_OSExecArgs *args, DN_Arena *arena, DN_OSErrSink *err); +DN_API DN_OSExecResult DN_OS_ExecOrAbort (DN_Slice cmd_line, DN_OSExecArgs *args, DN_Arena *arena); +#define DN_OS_ExecOrAbortFromTLS(...) DN_OS_ExecOrAbort(__VA_ARGS__, DN_OS_TLSTopArena()) + +DN_API DN_OSSemaphore DN_OS_SemaphoreInit (DN_U32 initial_count); +DN_API bool DN_OS_SemaphoreIsValid (DN_OSSemaphore *semaphore); +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_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, void *user_context); +DN_API void DN_OS_ThreadDeinit(DN_OSThread *thread); +DN_API DN_U32 DN_OS_ThreadID (); +DN_API void DN_OS_ThreadSetName(DN_Str8 name); + +DN_API void DN_OS_HttpRequestAsync(DN_OSHttpResponse *response, DN_Arena *arena, DN_Str8 host, DN_Str8 path, DN_OSHttpRequestSecure secure, DN_Str8 method, DN_Str8 body, DN_Str8 headers); +DN_API void DN_OS_HttpRequestWait (DN_OSHttpResponse *response); +DN_API void DN_OS_HttpRequestFree (DN_OSHttpResponse *response); +DN_API DN_OSHttpResponse DN_OS_HttpRequest (DN_Arena *arena, DN_Str8 host, DN_Str8 path, DN_OSHttpRequestSecure secure, DN_Str8 method, DN_Str8 body, DN_Str8 headers); +#endif // !defined(DN_OS_H) +// DN: Single header generator inlined this file => #include "OS/dn_os_allocator.h" +#if !defined(DN_OS_ALLOCATOR_H) +#define DN_OS_ALLOCATOR_H + +DN_API DN_Arena DN_Arena_InitFromOSHeap(DN_U64 size, DN_ArenaFlags flags); +DN_API DN_Arena DN_Arena_InitFromOSVMem(DN_U64 reserve, DN_U64 commit, DN_ArenaFlags flags); + +#endif // !defined(DN_OS_ALLOCATOR_H) +// DN: Single header generator inlined this file => #include "OS/dn_os_containers.h" +#if !defined(DN_OS_CONTAINERS_H) +#define DN_OS_CONTAINERS_H + +// NOTE: DN_VArray ///////////////////////////////////////////////////////////////////////////////// +// TODO(doyle): Add an API for shrinking the array by decomitting pages back to the OS. +template struct DN_VArray +{ + T *data; // Pointer to the start of the array items in the block of memory + DN_USize size; // Number of items currently in the array + DN_USize max; // Maximum number of items this array can store + DN_USize commit; // Bytes committed + + T *begin() { return data; } + T *end () { return data + size; } + T const *begin() const { return data; } + T const *end () const { return data + size; } +}; + +template DN_VArray DN_VArray_InitByteSize (DN_USize byte_size); +template DN_VArray DN_VArray_Init (DN_USize max); +template DN_VArray DN_VArray_InitSlice (DN_Slice slice, DN_USize max); +template DN_VArray DN_VArray_InitCArray (T const (&items)[N], DN_USize max); +template void DN_VArray_Deinit (DN_VArray *array); +template bool DN_VArray_IsValid (DN_VArray const *array); +template DN_Slice DN_VArray_Slice (DN_VArray const *array); +template bool DN_VArray_Reserve (DN_VArray *array, DN_USize count); +template T * DN_VArray_AddArray (DN_VArray *array, T const *items, DN_USize count); +template T * DN_VArray_AddCArray (DN_VArray *array, T const (&items)[N]); +template T * DN_VArray_Add (DN_VArray *array, T const &item); +#define DN_VArray_AddArrayAssert(...) DN_HardAssert(DN_VArray_AddArray(__VA_ARGS__)) +#define DN_VArray_AddCArrayAssert(...) DN_HardAssert(DN_VArray_AddCArray(__VA_ARGS__)) +#define DN_VArray_AddAssert(...) DN_HardAssert(DN_VArray_Add(__VA_ARGS__)) +template T * DN_VArray_MakeArray (DN_VArray *array, DN_USize count, DN_ZeroMem zero_mem); +template T * DN_VArray_Make (DN_VArray *array, DN_ZeroMem zero_mem); +#define DN_VArray_MakeArrayAssert(...) DN_HardAssert(DN_VArray_MakeArray(__VA_ARGS__)) +#define DN_VArray_MakeAssert(...) DN_HardAssert(DN_VArray_Make(__VA_ARGS__)) +template T * DN_VArray_InsertArray (DN_VArray *array, DN_USize index, T const *items, DN_USize count); +template T * DN_VArray_InsertCArray (DN_VArray *array, DN_USize index, T const (&items)[N]); +template T * DN_VArray_Insert (DN_VArray *array, DN_USize index, T const &item); +#define DN_VArray_InsertArrayAssert(...) DN_HardAssert(DN_VArray_InsertArray(__VA_ARGS__)) +#define DN_VArray_InsertCArrayAssert(...) DN_HardAssert(DN_VArray_InsertCArray(__VA_ARGS__)) +#define DN_VArray_InsertAssert(...) DN_HardAssert(DN_VArray_Insert(__VA_ARGS__)) +template T DN_VArray_PopFront (DN_VArray *array, DN_USize count); +template T DN_VArray_PopBack (DN_VArray *array, DN_USize count); +template DN_ArrayEraseResult DN_VArray_EraseRange (DN_VArray *array, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase); +template void DN_VArray_Clear (DN_VArray *array, DN_ZeroMem zero_mem); +#endif // !defined(DN_OS_CONTAINERS_H) +// DN: Single header generator inlined this file => #include "OS/dn_os_print.h" +#if !defined(DN_OS_PRINT_H) +#define DN_OS_PRINT_H + +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_PRINT_H) +// DN: Single header generator inlined this file => #include "OS/dn_os_string.h" +#if !defined(DN_OS_STRING_H) +#define DN_OS_STRING_H + +// NOTE: DN_Str8 /////////////////////////////////////////////////////////////////////////////////// + +DN_API DN_Str8 DN_Str8_InitFFromFrame (DN_FMT_ATTRIB char const *fmt, ...); +DN_API DN_Str8 DN_Str8_InitFFromOSHeap (DN_FMT_ATTRIB char const *fmt, ...); +DN_API DN_Str8 DN_Str8_InitFFromTLS (DN_FMT_ATTRIB char const *fmt, ...); + +DN_API DN_Str8 DN_Str8_InitFVFromFrame (DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API DN_Str8 DN_Str8_InitFVFromTLS (DN_FMT_ATTRIB char const *fmt, va_list args); + +DN_API DN_Str8 DN_Str8_AllocFromFrame (DN_USize size, DN_ZeroMem zero_mem); +DN_API DN_Str8 DN_Str8_AllocFromOSHeap (DN_USize size, DN_ZeroMem zero_mem); +DN_API DN_Str8 DN_Str8_AllocFromTLS (DN_USize size, DN_ZeroMem zero_mem); + +DN_API DN_Str8 DN_Str8_CopyFromFrame (DN_Arena *arena, DN_Str8 string); +DN_API DN_Str8 DN_Str8_CopyFromTLS (DN_Arena *arena, DN_Str8 string); + +DN_API DN_Slice DN_Str8_SplitAllocFromFrame (DN_Str8 string, DN_Str8 delimiter, DN_Str8SplitIncludeEmptyStrings mode); +DN_API DN_Slice DN_Str8_SplitAllocFromTLS (DN_Str8 string, DN_Str8 delimiter, DN_Str8SplitIncludeEmptyStrings mode); + +DN_API DN_Str8 DN_Str8_SegmentFromFrame (DN_Str8 src, DN_USize segment_size, char segment_char); +DN_API DN_Str8 DN_Str8_SegmentFromTLS (DN_Str8 src, DN_USize segment_size, char segment_char); + +DN_API DN_Str8 DN_Str8_ReverseSegmentFromFrame (DN_Str8 src, DN_USize segment_size, char segment_char); +DN_API DN_Str8 DN_Str8_ReverseSegmentFromTLS (DN_Str8 src, DN_USize segment_size, char segment_char); + +DN_API DN_Str8 DN_Str8_AppendFFromFrame (DN_Str8 string, char const *fmt, ...); +DN_API DN_Str8 DN_Str8_AppendFFromTLS (DN_Str8 string, char const *fmt, ...); + +DN_API DN_Str8 DN_Str8_FillFFromFrame (DN_Str8 string, char const *fmt, ...); +DN_API DN_Str8 DN_Str8_FillFFromTLS (DN_Str8 string, char const *fmt, ...); + +DN_API DN_Str8DotTruncateResult DN_Str8_DotTruncateMiddleFromFrame (DN_Str8 str8, uint32_t side_size, DN_Str8 truncator); +DN_API DN_Str8DotTruncateResult DN_Str8_DotTruncateMiddleFromTLS (DN_Str8 str8, uint32_t side_size, DN_Str8 truncator); + +DN_API DN_Str8 DN_Str8_PadNewLines (DN_Arena *arena, DN_Str8 src, DN_Str8 pad); +DN_API DN_Str8 DN_Str8_PadNewLinesFromFrame (DN_Str8 src, DN_Str8 pad); +DN_API DN_Str8 DN_Str8_PadNewLinesFromTLS (DN_Str8 src, DN_Str8 pad); + +DN_API DN_Str8 DN_Str8_UpperFromFrame (DN_Str8 string); +DN_API DN_Str8 DN_Str8_UpperFromTLS (DN_Str8 string); + +DN_API DN_Str8 DN_Str8_LowerFromFrame (DN_Str8 string); +DN_API DN_Str8 DN_Str8_LowerFromTLS (DN_Str8 string); + +DN_API DN_Str8 DN_Str8_Replace (DN_Str8 string, DN_Str8 find, DN_Str8 replace, DN_USize start_index, DN_Arena *arena, DN_Str8EqCase eq_case = DN_Str8EqCase_Sensitive); +DN_API DN_Str8 DN_Str8_ReplaceInsensitive (DN_Str8 string, DN_Str8 find, DN_Str8 replace, DN_USize start_index, DN_Arena *arena); + +// NOTE: DN_Str8Builder //////////////////////////////////////////////////////////////////////////// + +DN_API DN_Str8Builder DN_Str8Builder_InitFromFrame () { return DN_Str8Builder_Init(DN_OS_TLSGet()->frame_arena); } +DN_API DN_Str8Builder DN_Str8Builder_InitFromTLS () { return DN_Str8Builder_Init(DN_OS_TLSTopArena()); } + +DN_API DN_Str8Builder DN_Str8Builder_InitArrayRefFromFrame (DN_Str8 const *strings, DN_USize size) { return DN_Str8Builder_InitArrayRef(DN_OS_TLSGet()->frame_arena, strings, size); } +DN_API DN_Str8Builder DN_Str8Builder_InitArrayRefFromTLS (DN_Str8 const *strings, DN_USize size) { return DN_Str8Builder_InitArrayRef(DN_OS_TLSTopArena(), strings, size); } +DN_API DN_Str8Builder DN_Str8Builder_InitArrayCopyFromFrame (DN_Str8 const *strings, DN_USize size) { return DN_Str8Builder_InitArrayCopy(DN_OS_TLSGet()->frame_arena, strings, size); } +DN_API DN_Str8Builder DN_Str8Builder_InitArrayCopyFromTLS (DN_Str8 const *strings, DN_USize size) { return DN_Str8Builder_InitArrayCopy(DN_OS_TLSTopArena(), strings, size); } + +DN_API DN_Str8Builder DN_Str8Builder_CopyFromFrame (DN_Str8Builder const *builder) { return DN_Str8Builder_Copy(DN_OS_TLSGet()->frame_arena, builder); } +DN_API DN_Str8Builder DN_Str8Builder_CopyFromTLS (DN_Str8Builder const *builder) { return DN_Str8Builder_Copy(DN_OS_TLSTopArena(), builder); } + +DN_API DN_Str8 DN_Str8Builder_BuildFromFrame (DN_Str8Builder const *builder) { return DN_Str8Builder_Build(builder, DN_OS_TLSGet()->frame_arena); } +DN_API DN_Slice DN_Str8Builder_BuildFromOSHeap (DN_Str8Builder const *builder, DN_Arena *arena); +DN_API DN_Str8 DN_Str8Builder_BuildFromTLS (DN_Str8Builder const *builder) { return DN_Str8Builder_Build(builder, DN_OS_TLSTopArena()); } + +DN_API DN_Str8 DN_Str8Builder_BuildDelimitedFromFrame(DN_Str8Builder const *builder, DN_Str8 delimiter) { return DN_Str8Builder_BuildDelimited(builder, delimiter, DN_OS_TLSGet()->frame_arena); } +DN_API DN_Str8 DN_Str8Builder_BuildDelimitedFromTLS (DN_Str8Builder const *builder, DN_Str8 delimiter) { return DN_Str8Builder_BuildDelimited(builder, delimiter, DN_OS_TLSTopArena()); } + +DN_API DN_Slice DN_Str8Builder_BuildSliceFromFrame (DN_Str8Builder const *builder) { return DN_Str8Builder_BuildSlice(builder, DN_OS_TLSGet()->frame_arena); } +DN_API DN_Slice DN_Str8Builder_BuildSliceFromTLS (DN_Str8Builder const *builder) { return DN_Str8Builder_BuildSlice(builder, DN_OS_TLSTopArena()); } + +#endif // !defined(DN_OS_STRING_H) + +#endif // DN_OS_INC_H +#if !defined(DN_CORE_INC_H) +#define DN_CORE_INC_H + +// DN: Single header generator inlined this file => #include "Core/dn_core_debug.h" +#if !defined(DN_CORE_DEBUG_H) +#define DN_CORE_DEBUG_H + +// NOTE: DN_StackTrace ///////////////////////////////////////////////////////////////////////////// +// NOTE: DN_Debug ////////////////////////////////////////////////////////////////////////////////// +enum DN_DebugAllocFlag +{ + DN_DebugAllocFlag_Freed = 1 << 0, + DN_DebugAllocFlag_LeakPermitted = 1 << 1, +}; + +struct DN_DebugAlloc +{ + 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_DebugAllocFlag` +}; + +static_assert(sizeof(DN_DebugAlloc) == 64 || sizeof(DN_DebugAlloc) == 32, // NOTE: 64 bit vs 32 bit pointers respectively + "We aim to keep the allocation record as light as possible as " + "memory tracking can get expensive. Enforce that there is no " + "unexpected padding."); + +// NOTE: DN_Profiler /////////////////////////////////////////////////////////////////////////////// +#if !defined(DN_NO_PROFILER) +#if !defined(DN_PROFILER_ANCHOR_BUFFER_SIZE) + #define DN_PROFILER_ANCHOR_BUFFER_SIZE 256 +#endif + +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 +{ + DN_U16 anchor_index; + DN_U64 begin_tsc; + DN_U16 parent_zone; + DN_U64 elapsed_tsc_at_zone_start; +}; + +#if defined(__cplusplus) +struct DN_ProfilerZoneScope +{ + DN_ProfilerZoneScope(DN_Str8 name, DN_U16 anchor_index); + ~DN_ProfilerZoneScope(); + DN_ProfilerZone zone; +}; + +#define DN_Profiler_ZoneScopeAtIndex(name, anchor_index) auto DN_UniqueName(profile_zone_) = DN_ProfilerZoneScope(DN_STR8(name), anchor_index) +#define DN_Profiler_ZoneScope(name) DN_Profiler_ZoneScopeAtIndex(name, __COUNTER__ + 1) +#endif + +#define DN_Profiler_ZoneBlockIndex(name, index) \ + for (DN_ProfilerZone DN_UniqueName(profile_zone__) = DN_Profiler_BeginZoneAtIndex(name, index), DN_UniqueName(dummy__) = {}; \ + DN_UniqueName(dummy__).begin_tsc == 0; \ + DN_Profiler_EndZone(DN_UniqueName(profile_zone__)), DN_UniqueName(dummy__).begin_tsc = 1) + +#define DN_Profiler_ZoneBlock(name) DN_Profiler_ZoneBlockIndex(DN_STR8(name), __COUNTER__ + 1) + +enum DN_ProfilerAnchorBuffer +{ + DN_ProfilerAnchorBuffer_Back, + DN_ProfilerAnchorBuffer_Front, +}; + +struct DN_Profiler +{ + DN_ProfilerAnchor anchors[2][DN_PROFILER_ANCHOR_BUFFER_SIZE]; + DN_U8 active_anchor_buffer; + DN_U16 parent_zone; +}; + +DN_API DN_ProfilerAnchor * DN_Profiler_ReadBuffer (); +DN_API DN_ProfilerAnchor * DN_Profiler_WriteBuffer (); +#define DN_Profiler_BeginZone(name) DN_Profiler_BeginZoneAtIndex(DN_STR8(name), __COUNTER__ + 1) +DN_API DN_ProfilerZone DN_Profiler_BeginZoneAtIndex (DN_Str8 name, DN_U16 anchor_index); +DN_API void DN_Profiler_EndZone (DN_ProfilerZone zone); +DN_API DN_ProfilerAnchor * DN_Profiler_AnchorBuffer (DN_ProfilerAnchorBuffer buffer); +DN_API void DN_Profiler_SwapAnchorBuffer (); +DN_API void DN_Profiler_Dump (DN_U64 tsc_per_second); + +#endif // !defined(DN_NO_PROFILER) +#endif // DN_CORE_DEBUG_H +// DN: Single header generator inlined this file => #include "Core/dn_core.h" +#if !defined(DN_CORE_H) +#define DN_CORE_H + +// NOTE: DN_Core /////////////////////////////////////////////////////////////////////////////////// +// Book-keeping data for the library and allow customisation of certain features +// provided. +struct DN_Core +{ + // NOTE: Leak Tracing ////////////////////////////////////////////////////////////////////////// + #if defined(DN_LEAK_TRACKING) + DN_DSMap alloc_table; + DN_TicketMutex alloc_table_mutex; + DN_Arena alloc_table_arena; + #endif + DN_U64 alloc_table_bytes_allocated_for_stack_traces; + + // NOTE: Profiler ////////////////////////////////////////////////////////////////////////////// + #if !defined(DN_NO_PROFILER) + DN_Profiler * profiler; + DN_Profiler profiler_default_instance; + #endif +}; + +enum DN_CoreOnInit +{ + DN_CoreOnInit_Nil = 0, + DN_CoreOnInit_LogLibFeatures = 1 << 0, + DN_CoreOnInit_LogCPUFeatures = 1 << 1, + DN_CoreOnInit_LogAllFeatures = DN_CoreOnInit_LogLibFeatures | DN_CoreOnInit_LogCPUFeatures, +}; + +DN_API void DN_Core_Init (DN_Core *core, DN_CoreOnInit on_init); +DN_API void DN_Core_BeginFrame (); +#if !defined(DN_NO_PROFILER) +DN_API void DN_Core_SetProfiler (DN_Profiler *profiler); +#endif +#endif // !defined(DN_CORE_H) + +#endif // !defined(DN_CORE_INC_H) \ No newline at end of file diff --git a/Base/dn_base.cpp b/Source/Base/dn_base.cpp similarity index 98% rename from Base/dn_base.cpp rename to Source/Base/dn_base.cpp index c90b0ee..6deb114 100644 --- a/Base/dn_base.cpp +++ b/Source/Base/dn_base.cpp @@ -2,21 +2,6 @@ #include "../dn_clangd.h" -/* -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// $$$$$$$\ -// $$ __$$\ -// $$ | $$ | $$$$$$\ $$$$$$$\ $$$$$$\ -// $$$$$$$\ | \____$$\ $$ _____|$$ __$$\ -// $$ __$$\ $$$$$$$ |\$$$$$$\ $$$$$$$$ | -// $$ | $$ |$$ __$$ | \____$$\ $$ ____| -// $$$$$$$ |\$$$$$$$ |$$$$$$$ |\$$$$$$$\ -// \_______/ \_______|\_______/ \_______| -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -*/ - // NOTE: [$INTR] Intrinsics //////////////////////////////////////////////////////////////////////// #if !defined(DN_PLATFORM_ARM64) && !defined(DN_PLATFORM_EMSCRIPTEN) #if defined(DN_COMPILER_GCC) || defined(DN_COMPILER_CLANG) diff --git a/Base/dn_base.h b/Source/Base/dn_base.h similarity index 100% rename from Base/dn_base.h rename to Source/Base/dn_base.h diff --git a/Base/dn_base_assert.h b/Source/Base/dn_base_assert.h similarity index 100% rename from Base/dn_base_assert.h rename to Source/Base/dn_base_assert.h diff --git a/Base/dn_base_compiler.h b/Source/Base/dn_base_compiler.h similarity index 100% rename from Base/dn_base_compiler.h rename to Source/Base/dn_base_compiler.h diff --git a/Base/dn_base_containers.cpp b/Source/Base/dn_base_containers.cpp similarity index 100% rename from Base/dn_base_containers.cpp rename to Source/Base/dn_base_containers.cpp diff --git a/Base/dn_base_containers.h b/Source/Base/dn_base_containers.h similarity index 100% rename from Base/dn_base_containers.h rename to Source/Base/dn_base_containers.h diff --git a/Base/dn_base_convert.cpp b/Source/Base/dn_base_convert.cpp similarity index 100% rename from Base/dn_base_convert.cpp rename to Source/Base/dn_base_convert.cpp diff --git a/Base/dn_base_convert.h b/Source/Base/dn_base_convert.h similarity index 100% rename from Base/dn_base_convert.h rename to Source/Base/dn_base_convert.h diff --git a/Base/dn_base_log.cpp b/Source/Base/dn_base_log.cpp similarity index 100% rename from Base/dn_base_log.cpp rename to Source/Base/dn_base_log.cpp diff --git a/Base/dn_base_log.h b/Source/Base/dn_base_log.h similarity index 100% rename from Base/dn_base_log.h rename to Source/Base/dn_base_log.h diff --git a/Base/dn_base_mem.cpp b/Source/Base/dn_base_mem.cpp similarity index 100% rename from Base/dn_base_mem.cpp rename to Source/Base/dn_base_mem.cpp diff --git a/Base/dn_base_mem.h b/Source/Base/dn_base_mem.h similarity index 100% rename from Base/dn_base_mem.h rename to Source/Base/dn_base_mem.h diff --git a/Base/dn_base_os.h b/Source/Base/dn_base_os.h similarity index 100% rename from Base/dn_base_os.h rename to Source/Base/dn_base_os.h diff --git a/Base/dn_base_string.cpp b/Source/Base/dn_base_string.cpp similarity index 99% rename from Base/dn_base_string.cpp rename to Source/Base/dn_base_string.cpp index b66a18e..b071dd5 100644 --- a/Base/dn_base_string.cpp +++ b/Source/Base/dn_base_string.cpp @@ -499,6 +499,14 @@ DN_API DN_Str8 DN_Str8_FileExtension(DN_Str8 path) return result; } +DN_API DN_Str8 DN_Str8_FileDirectoryFromPath(DN_Str8 path) +{ + DN_Str8 separators[] = {DN_STR8("/"), DN_STR8("\\")}; + DN_Str8BinarySplitResult split = DN_Str8_BinarySplitLastArray(path, separators, DN_ArrayCountU(separators)); + DN_Str8 result = split.lhs; + return result; +} + DN_API DN_Str8ToU64Result DN_Str8_ToU64(DN_Str8 string, char separator) { // NOTE: Argument check diff --git a/Base/dn_base_string.h b/Source/Base/dn_base_string.h similarity index 99% rename from Base/dn_base_string.h rename to Source/Base/dn_base_string.h index 6c80aba..e18cb7d 100644 --- a/Base/dn_base_string.h +++ b/Source/Base/dn_base_string.h @@ -204,6 +204,7 @@ DN_API DN_Str8 DN_Str8_FileNameFrom DN_API DN_Str8 DN_Str8_FileNameNoExtension (DN_Str8 path); DN_API DN_Str8 DN_Str8_FilePathNoExtension (DN_Str8 path); DN_API DN_Str8 DN_Str8_FileExtension (DN_Str8 path); +DN_API DN_Str8 DN_Str8_FileDirectoryFromPath (DN_Str8 path); DN_API DN_Str8ToU64Result DN_Str8_ToU64 (DN_Str8 string, char separator); DN_API DN_Str8ToI64Result DN_Str8_ToI64 (DN_Str8 string, char separator); diff --git a/Core/dn_core.cpp b/Source/Core/dn_core.cpp similarity index 100% rename from Core/dn_core.cpp rename to Source/Core/dn_core.cpp diff --git a/Core/dn_core.h b/Source/Core/dn_core.h similarity index 100% rename from Core/dn_core.h rename to Source/Core/dn_core.h diff --git a/Core/dn_core_debug.cpp b/Source/Core/dn_core_debug.cpp similarity index 100% rename from Core/dn_core_debug.cpp rename to Source/Core/dn_core_debug.cpp diff --git a/Core/dn_core_debug.h b/Source/Core/dn_core_debug.h similarity index 100% rename from Core/dn_core_debug.h rename to Source/Core/dn_core_debug.h diff --git a/Core/dn_core_demo.cpp b/Source/Core/dn_core_demo.cpp similarity index 100% rename from Core/dn_core_demo.cpp rename to Source/Core/dn_core_demo.cpp diff --git a/External/b_stacktrace.h b/Source/External/b_stacktrace.h similarity index 100% rename from External/b_stacktrace.h rename to Source/External/b_stacktrace.h diff --git a/External/json.h b/Source/External/json.h similarity index 100% rename from External/json.h rename to Source/External/json.h diff --git a/External/metadesk/md.c b/Source/External/metadesk/md.c similarity index 100% rename from External/metadesk/md.c rename to Source/External/metadesk/md.c diff --git a/External/metadesk/md.h b/Source/External/metadesk/md.h similarity index 100% rename from External/metadesk/md.h rename to Source/External/metadesk/md.h diff --git a/External/metadesk/md_stb_sprintf.h b/Source/External/metadesk/md_stb_sprintf.h similarity index 100% rename from External/metadesk/md_stb_sprintf.h rename to Source/External/metadesk/md_stb_sprintf.h diff --git a/External/stb_sprintf.h b/Source/External/stb_sprintf.h similarity index 100% rename from External/stb_sprintf.h rename to Source/External/stb_sprintf.h diff --git a/Extra/dn_async.cpp b/Source/Extra/dn_async.cpp similarity index 100% rename from Extra/dn_async.cpp rename to Source/Extra/dn_async.cpp diff --git a/Extra/dn_async.h b/Source/Extra/dn_async.h similarity index 100% rename from Extra/dn_async.h rename to Source/Extra/dn_async.h diff --git a/Extra/dn_bin_pack.cpp b/Source/Extra/dn_bin_pack.cpp similarity index 100% rename from Extra/dn_bin_pack.cpp rename to Source/Extra/dn_bin_pack.cpp diff --git a/Extra/dn_bin_pack.h b/Source/Extra/dn_bin_pack.h similarity index 100% rename from Extra/dn_bin_pack.h rename to Source/Extra/dn_bin_pack.h diff --git a/Extra/dn_cgen.cpp b/Source/Extra/dn_cgen.cpp similarity index 100% rename from Extra/dn_cgen.cpp rename to Source/Extra/dn_cgen.cpp diff --git a/Extra/dn_cgen.h b/Source/Extra/dn_cgen.h similarity index 100% rename from Extra/dn_cgen.h rename to Source/Extra/dn_cgen.h diff --git a/Extra/dn_csv.cpp b/Source/Extra/dn_csv.cpp similarity index 100% rename from Extra/dn_csv.cpp rename to Source/Extra/dn_csv.cpp diff --git a/Extra/dn_csv.h b/Source/Extra/dn_csv.h similarity index 100% rename from Extra/dn_csv.h rename to Source/Extra/dn_csv.h diff --git a/Extra/dn_hash.cpp b/Source/Extra/dn_hash.cpp similarity index 100% rename from Extra/dn_hash.cpp rename to Source/Extra/dn_hash.cpp diff --git a/Extra/dn_hash.h b/Source/Extra/dn_hash.h similarity index 100% rename from Extra/dn_hash.h rename to Source/Extra/dn_hash.h diff --git a/Extra/dn_helpers.cpp b/Source/Extra/dn_helpers.cpp similarity index 100% rename from Extra/dn_helpers.cpp rename to Source/Extra/dn_helpers.cpp diff --git a/Extra/dn_helpers.h b/Source/Extra/dn_helpers.h similarity index 100% rename from Extra/dn_helpers.h rename to Source/Extra/dn_helpers.h diff --git a/Extra/dn_json.cpp b/Source/Extra/dn_json.cpp similarity index 100% rename from Extra/dn_json.cpp rename to Source/Extra/dn_json.cpp diff --git a/Extra/dn_json.h b/Source/Extra/dn_json.h similarity index 100% rename from Extra/dn_json.h rename to Source/Extra/dn_json.h diff --git a/Extra/dn_math.cpp b/Source/Extra/dn_math.cpp similarity index 100% rename from Extra/dn_math.cpp rename to Source/Extra/dn_math.cpp diff --git a/Extra/dn_math.h b/Source/Extra/dn_math.h similarity index 100% rename from Extra/dn_math.h rename to Source/Extra/dn_math.h diff --git a/Extra/dn_tests.cpp b/Source/Extra/dn_tests.cpp similarity index 100% rename from Extra/dn_tests.cpp rename to Source/Extra/dn_tests.cpp diff --git a/Extra/dn_tests_main.cpp b/Source/Extra/dn_tests_main.cpp similarity index 100% rename from Extra/dn_tests_main.cpp rename to Source/Extra/dn_tests_main.cpp diff --git a/Extra/dn_type_info.cpp b/Source/Extra/dn_type_info.cpp similarity index 100% rename from Extra/dn_type_info.cpp rename to Source/Extra/dn_type_info.cpp diff --git a/Extra/dn_type_info.h b/Source/Extra/dn_type_info.h similarity index 100% rename from Extra/dn_type_info.h rename to Source/Extra/dn_type_info.h diff --git a/OS/dn_os.cpp b/Source/OS/dn_os.cpp similarity index 100% rename from OS/dn_os.cpp rename to Source/OS/dn_os.cpp diff --git a/OS/dn_os.h b/Source/OS/dn_os.h similarity index 98% rename from OS/dn_os.h rename to Source/OS/dn_os.h index 6fce49b..0fa0218 100644 --- a/OS/dn_os.h +++ b/Source/OS/dn_os.h @@ -3,16 +3,6 @@ #include // operator new -#if defined(DN_PLATFORM_EMSCRIPTEN) || defined(DN_PLATFORM_POSIX) || defined(DN_PLATFORM_ARM64) - #include "dn_os_posix.h" -#elif defined(DN_PLATFORM_WIN32) - #include "dn_os_windows.h" - #include "dn_os_w32.h" -#else - #error Please define a platform e.g. 'DN_PLATFORM_WIN32' to enable the correct implementation for platform APIs -#endif - - #if !defined(DN_OS_WIN32) || defined(DN_OS_WIN32_USE_PTHREADS) #include #include diff --git a/OS/dn_os_allocator.cpp b/Source/OS/dn_os_allocator.cpp similarity index 100% rename from OS/dn_os_allocator.cpp rename to Source/OS/dn_os_allocator.cpp diff --git a/OS/dn_os_allocator.h b/Source/OS/dn_os_allocator.h similarity index 100% rename from OS/dn_os_allocator.h rename to Source/OS/dn_os_allocator.h diff --git a/OS/dn_os_containers.cpp b/Source/OS/dn_os_containers.cpp similarity index 100% rename from OS/dn_os_containers.cpp rename to Source/OS/dn_os_containers.cpp diff --git a/OS/dn_os_containers.h b/Source/OS/dn_os_containers.h similarity index 100% rename from OS/dn_os_containers.h rename to Source/OS/dn_os_containers.h diff --git a/OS/dn_os_posix.cpp b/Source/OS/dn_os_posix.cpp similarity index 100% rename from OS/dn_os_posix.cpp rename to Source/OS/dn_os_posix.cpp diff --git a/OS/dn_os_posix.h b/Source/OS/dn_os_posix.h similarity index 100% rename from OS/dn_os_posix.h rename to Source/OS/dn_os_posix.h diff --git a/OS/dn_os_print.cpp b/Source/OS/dn_os_print.cpp similarity index 100% rename from OS/dn_os_print.cpp rename to Source/OS/dn_os_print.cpp diff --git a/OS/dn_os_print.h b/Source/OS/dn_os_print.h similarity index 100% rename from OS/dn_os_print.h rename to Source/OS/dn_os_print.h diff --git a/OS/dn_os_string.cpp b/Source/OS/dn_os_string.cpp similarity index 100% rename from OS/dn_os_string.cpp rename to Source/OS/dn_os_string.cpp diff --git a/OS/dn_os_string.h b/Source/OS/dn_os_string.h similarity index 100% rename from OS/dn_os_string.h rename to Source/OS/dn_os_string.h diff --git a/OS/dn_os_tls.cpp b/Source/OS/dn_os_tls.cpp similarity index 100% rename from OS/dn_os_tls.cpp rename to Source/OS/dn_os_tls.cpp diff --git a/OS/dn_os_tls.h b/Source/OS/dn_os_tls.h similarity index 100% rename from OS/dn_os_tls.h rename to Source/OS/dn_os_tls.h diff --git a/OS/dn_os_w32.cpp b/Source/OS/dn_os_w32.cpp similarity index 100% rename from OS/dn_os_w32.cpp rename to Source/OS/dn_os_w32.cpp diff --git a/OS/dn_os_w32.h b/Source/OS/dn_os_w32.h similarity index 100% rename from OS/dn_os_w32.h rename to Source/OS/dn_os_w32.h diff --git a/OS/dn_os_windows.h b/Source/OS/dn_os_windows.h similarity index 100% rename from OS/dn_os_windows.h rename to Source/OS/dn_os_windows.h diff --git a/SIMD/dn_simd_avx512f.cpp b/Source/SIMD/dn_simd_avx512f.cpp similarity index 100% rename from SIMD/dn_simd_avx512f.cpp rename to Source/SIMD/dn_simd_avx512f.cpp diff --git a/SIMD/dn_simd_avx512f.h b/Source/SIMD/dn_simd_avx512f.h similarity index 100% rename from SIMD/dn_simd_avx512f.h rename to Source/SIMD/dn_simd_avx512f.h diff --git a/Standalone/dn_cpp_file.h b/Source/Standalone/dn_cpp_file.h similarity index 100% rename from Standalone/dn_cpp_file.h rename to Source/Standalone/dn_cpp_file.h diff --git a/Standalone/dn_keccak.h b/Source/Standalone/dn_keccak.h similarity index 100% rename from Standalone/dn_keccak.h rename to Source/Standalone/dn_keccak.h diff --git a/Standalone/dn_utest.h b/Source/Standalone/dn_utest.h similarity index 100% rename from Standalone/dn_utest.h rename to Source/Standalone/dn_utest.h diff --git a/dn_base_inc.cpp b/Source/dn_base_inc.cpp similarity index 100% rename from dn_base_inc.cpp rename to Source/dn_base_inc.cpp diff --git a/Source/dn_base_inc.h b/Source/dn_base_inc.h new file mode 100644 index 0000000..4d93974 --- /dev/null +++ b/Source/dn_base_inc.h @@ -0,0 +1,57 @@ +#if !defined(DN_BASE_INC_H) +#define DN_BASE_INC_H + +// NOTE: DN configuration +// All the following configuration options are optional. If omitted, DN has default behaviours to +// handle the various options. +// +// 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 as the +// target platform. +// +// DN_PLATFORM_EMSCRIPTEN +// DN_PLATFORM_POSIX +// DN_PLATFORM_WIN32 +// +// For example +// +// #define DN_PLATFORM_WIN32 +// +// Will ensure that is included and the OS layer is implemented using Win32 +// primitives. +// +// Static functions +// All public functions in the DN library are prefixed with the macro '#define DN_API'. By +// default 'DN_API' is not defined to anything. Define +// +// DN_STATIC_API +// +// To replace all the prefixed with 'static' ensuring that the functions in the library do not +// export an entry into the linking table and thereby optimise compilation times as the linker +// will not try to resolve symbols from other translation units from the the unit including the +// DN library. +// +// Using the in-built replacement header for (requires dn_os_inc.h) +// If you are building DN for the Windows platform, is a large legacy header that +// applications have to link to use Windows syscalls. By default DN library uses a replacement +// header for all the Windows functions that it uses in the OS layer removing the need to include +// to improve compilation times. This can be disabled by defining: +// +// DN_NO_WINDOWS_H_REPLACEMENT_HEADER +// +// To instead use . DN automatically detects if is included in an earlier +// translation unit and will automatically disable the in-built replacement header in which case +// this does not need to be defined. + +#include "Base/dn_base_compiler.h" +#include "Base/dn_base.h" +#include "Base/dn_base_os.h" +#include "Base/dn_base_assert.h" +#include "Base/dn_base_mem.h" +#include "Base/dn_base_log.h" +#include "Base/dn_base_string.h" +#include "Base/dn_base_containers.h" +#include "Base/dn_base_convert.h" + +#endif // !defined(DN_BASE_INC_H) diff --git a/dn_clangd.h b/Source/dn_clangd.h similarity index 100% rename from dn_clangd.h rename to Source/dn_clangd.h diff --git a/dn_core_inc.cpp b/Source/dn_core_inc.cpp similarity index 100% rename from dn_core_inc.cpp rename to Source/dn_core_inc.cpp diff --git a/dn_core_inc.h b/Source/dn_core_inc.h similarity index 100% rename from dn_core_inc.h rename to Source/dn_core_inc.h diff --git a/dn_os_inc.cpp b/Source/dn_os_inc.cpp similarity index 100% rename from dn_os_inc.cpp rename to Source/dn_os_inc.cpp diff --git a/dn_os_inc.h b/Source/dn_os_inc.h similarity index 77% rename from dn_os_inc.h rename to Source/dn_os_inc.h index bb2bd8b..bfd7e8c 100644 --- a/dn_os_inc.h +++ b/Source/dn_os_inc.h @@ -1,6 +1,15 @@ #if !defined(DN_OS_INC_H) #define DN_OS_INC_H +#if defined(DN_PLATFORM_WIN32) + #include "OS/dn_os_windows.h" + #include "OS/dn_os_w32.h" +#elif defined(DN_PLATFORM_POSIX) + #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 + #include "OS/dn_os_tls.h" #include "OS/dn_os.h" #include "OS/dn_os_allocator.h" @@ -8,11 +17,4 @@ #include "OS/dn_os_print.h" #include "OS/dn_os_string.h" -#if defined(DN_PLATFORM_WIN32) - #include "OS/dn_os_windows.h" - #include "OS/dn_os_w32.h" -#elif defined(DN_PLATFORM_POSIX) - #include "OS/dn_os_posix.h" -#endif - #endif // DN_OS_INC_H diff --git a/build.bat b/build.bat index f83c426..49242e1 100644 --- a/build.bat +++ b/build.bat @@ -13,7 +13,7 @@ pushd Build REM O2 Optimisation Level 2 REM Oi Use CPU Intrinsics REM Z7 Combine multi-debug files to one debug file - set common_flags=-D DN_UNIT_TESTS_WITH_KECCAK %script_dir%\Extra\dn_tests_main.cpp + set common_flags=-D DN_UNIT_TESTS_WITH_KECCAK %script_dir%\Source\Extra\dn_tests_main.cpp set msvc_driver_flags=%common_flags% -MT -EHa -GR- -Od -Oi -Z7 -wd4201 -W4 -nologo @@ -33,8 +33,12 @@ pushd Build echo [BUILD] MSVC's cl detected, compiling ... set msvc_cmd=cl %msvc_compile_flags% %msvc_link_flags% powershell -Command "$time = Measure-Command { !msvc_cmd! | Out-Default }; Write-Host '[BUILD] msvc:'$time.TotalSeconds's'; exit $LASTEXITCODE" || exit /b 1 + + call cl %script_dir%\single_header_generator.cpp -nologo -link + call single_header_generator.exe %script_dir%\Source %script_dir%\Single_Header ) + REM REM clang-cl =================================================================================== set has_clang_cl=1 where /q clang-cl || set has_clang_cl=0 diff --git a/dn_base_inc.h b/dn_base_inc.h deleted file mode 100644 index 3d8f9c6..0000000 --- a/dn_base_inc.h +++ /dev/null @@ -1,14 +0,0 @@ -#if !defined(DN_BASE_INC_H) -#define DN_BASE_INC_H - -#include "Base/dn_base_compiler.h" -#include "Base/dn_base.h" -#include "Base/dn_base_os.h" -#include "Base/dn_base_assert.h" -#include "Base/dn_base_mem.h" -#include "Base/dn_base_log.h" -#include "Base/dn_base_string.h" -#include "Base/dn_base_containers.h" -#include "Base/dn_base_convert.h" - -#endif // !defined(DN_BASE_INC_H) diff --git a/single_header_generator.cpp b/single_header_generator.cpp new file mode 100644 index 0000000..07030f1 --- /dev/null +++ b/single_header_generator.cpp @@ -0,0 +1,166 @@ +#define USE_SINGLE_HEADER 1 + +#if USE_SINGLE_HEADER +#include "Single_Header/dn_single_header.h" +#else +#include "Source/dn_base_inc.h" +#include "Source/dn_os_inc.h" +#include "Source/dn_core_inc.h" +#endif + +#if USE_SINGLE_HEADER +#include "Single_Header/dn_single_header.cpp" +#else +#include "Source/dn_base_inc.cpp" +#include "Source/dn_os_inc.cpp" +#include "Source/dn_core_inc.cpp" +#endif + +enum FileType +{ + FileType_Header, + FileType_Impl, + FileType_Count +}; + +struct File +{ + FileType type; + DN_Str8 file_name; +}; + +static void AppendCppFileLineByLine(DN_Str8Builder *dest, DN_Str8 cpp_path) +{ + DN_OSErrSink *err = DN_OS_ErrSinkBeginDefault(); + DN_Str8 buffer = DN_OS_ReadAllFromTLS(cpp_path, err); + DN_OS_ErrSinkEndAndExitIfErrorF(err, -1, "Failed to load file from '%S' for appending", cpp_path); + + for (DN_Str8 inc_walker = buffer;;) { + DN_Str8BinarySplitResult split = DN_Str8_BinarySplit(inc_walker, DN_STR8("\n")); + if (!DN_Str8_HasData(split.lhs)) + break; + inc_walker = split.rhs; + + // NOTE: Trim the whitespace, mainly for windows, the file we read will have \r\n whereas we just want to emit \n + DN_Str8 line = DN_Str8_TrimTailWhitespace(split.lhs); + + // NOTE: Comment out any #include "../dn_.*" matches if we encounter one + DN_Str8FindResult find = DN_Str8_FindStr8(line, DN_STR8("#include \"../dn_"), DN_Str8EqCase_Sensitive); + { + if (find.found) { + line = DN_Str8_InitFFromTLS("%S// DN: Single header generator commented out this header => %S", find.start_to_before_match, DN_Str8_TrimWhitespaceAround(find.match_to_end_of_buffer)); + + // The only time we use '../dn_.*' is for LSP purposes, so we + // don't care about inlining it, hence we don't set 'include_file' + } + } + + // NOTE: Inline any other relative includes if we encounter one + // (Right now DN only includes stb_sprintf with a relative path) + DN_Str8 extra_include_path = {}; + if (!find.found) { + find = DN_Str8_FindStr8(line, DN_STR8("#include \""), DN_Str8EqCase_Sensitive); + if (find.found) { + line = DN_Str8_InitFFromTLS("%S// DN: Single header generator commented out this header => %S", find.start_to_before_match, DN_Str8_TrimWhitespaceAround(find.match_to_end_of_buffer)); + DN_Str8 rel_include_path = DN_Str8_TrimWhitespaceAround(find.after_match_to_end_of_buffer); + DN_Str8 root_dir = DN_Str8_FileDirectoryFromPath(cpp_path); + extra_include_path = DN_OS_PathFFromTLS("%S/%S", root_dir, DN_Str8_TrimSuffix(rel_include_path, DN_STR8("\""))); + } + } + + DN_Str8Builder_AppendRef(dest, line); + DN_Str8Builder_AppendRef(dest, DN_STR8("\n")); + + if (extra_include_path.size) + AppendCppFileLineByLine(dest, extra_include_path); + } +} + +int main(int argc, char **argv) +{ + DN_Core dn = {}; + DN_OSCore os = {}; + DN_OS_Init(&os, nullptr); + DN_Core_Init(&dn, DN_CoreOnInit_Nil); + + if (argc != 3) { + DN_OS_PrintErrF("USAGE: %s ", argv[0]); + return -1; + } + + DN_Str8 dn_root_dir = DN_Str8_InitCStr8(argv[1]); + DN_Str8 output_dir = DN_Str8_InitCStr8(argv[2]); + if (!DN_OS_MakeDir(output_dir)) { + DN_OS_PrintErrF("Failed to make requested output directory: %S", output_dir); + return -1; + } + + File const FILES[] = { + {FileType_Header, DN_STR8("dn_base_inc.h")}, + {FileType_Header, DN_STR8("dn_os_inc.h")}, + {FileType_Header, DN_STR8("dn_core_inc.h")}, + {FileType_Impl, DN_STR8("dn_base_inc.cpp")}, + {FileType_Impl, DN_STR8("dn_os_inc.cpp")}, + {FileType_Impl, DN_STR8("dn_core_inc.cpp")}, + }; + + for (DN_ForIndexU(type, FileType_Count)) { + DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(nullptr); + DN_Str8Builder builder = DN_Str8Builder_InitFromTLS(); + for (DN_ForItCArray(it, File const, FILES)) { + if (it.data->type != type) + continue; + + // NOTE: Parse the include files in the *_inc.[h|cpp] files + DN_Str8 path = DN_OS_PathFFromTLS("%S/%S", dn_root_dir, it.data->file_name); + { + DN_OSErrSink *err = DN_OS_ErrSinkBeginDefault(); + DN_Str8 file_buffer = DN_OS_ReadAllFromTLS(path, err); + DN_OS_ErrSinkEndAndExitIfErrorF(err, -1, "Failed to load file"); + + // NOTE: Walk the top-level dn_*_inc.[h|cpp] files + for (DN_Str8 walker = file_buffer;;) { + DN_Str8BinarySplitResult split = DN_Str8_BinarySplit(walker, DN_STR8("\n")); + if (!DN_Str8_HasData(split.lhs)) + break; + + // NOTE: Parse the line, if it was a #include, extract it into this string + DN_Str8 include_file = {}; + { + walker = split.rhs; + DN_Str8 line = DN_Str8_TrimTailWhitespace(split.lhs); + + // NOTE: Comment out any #include "dn_.*" matches if we encounter one + DN_Str8FindResult find = DN_Str8_FindStr8(line, DN_STR8("#include \""), DN_Str8EqCase_Sensitive); + { + if (find.found && DN_Str8_FindStr8(line, DN_STR8("dn_"), DN_Str8EqCase_Sensitive).found) { + line = DN_Str8_InitFFromTLS("%S// DN: Single header generator inlined this file => %S", find.start_to_before_match, DN_Str8_TrimWhitespaceAround(find.match_to_end_of_buffer)); + include_file = DN_Str8_BinarySplit(find.after_match_to_end_of_buffer, DN_STR8("\"")).lhs; + DN_Assert(include_file.size); + } + } + + // NOTE: Record the line + DN_Str8Builder_AppendRef(&builder, line); + DN_Str8Builder_AppendRef(&builder, DN_STR8("\n")); + } + + if (include_file.size) { // NOTE: If the line was a include file, we will inline the included file + DN_Str8 include_path = DN_OS_PathFFromTLS("%S/%S", dn_root_dir, include_file); + AppendCppFileLineByLine(&builder, include_path); + } + } + } + } + + DN_OSDateTime date = DN_OS_DateLocalTimeNow(); + DN_Str8Builder_PrependF(&builder, "// Generated by the DN single header generator %04u-%02u-%02u %02u:%02u:%02u\n\n", date.year, date.month, date.day, date.hour, date.minutes, date.seconds); + + DN_Str8 suffix = type == FileType_Header ? DN_STR8("h") : DN_STR8("cpp"); + DN_Str8 buffer = DN_Str8_TrimWhitespaceAround(DN_Str8Builder_BuildFromTLS(&builder)); + DN_Str8 single_header_path = DN_OS_PathFFromTLS("%S/dn_single_header.%S", output_dir, suffix); + DN_OSErrSink *err = DN_OS_ErrSinkBeginDefault(); + DN_OS_WriteAllSafe(single_header_path, buffer, err); + DN_OS_ErrSinkEndAndExitIfErrorF(err, -1, "Failed to write Single header file '%S'", single_header_path); + } +}