18168 lines
641 KiB
C++
18168 lines
641 KiB
C++
// Generated by the DN single header generator 2026-05-18 12:42:13
|
|
|
|
// DN: Single header generator commented out => #if defined(_CLANGD)
|
|
// #define DN_H_WITH_OS 1
|
|
// #include "dn.h"
|
|
// #endif
|
|
|
|
// DN: Single header generator commented out => #include "Base/dn_base.cpp"
|
|
#define DN_BASE_CPP
|
|
|
|
// DN: Single header generator commented out => #if defined(_CLANGD)
|
|
// #define DN_ARENA_TEMP_MEM_UAF_GUARD 1
|
|
// #define DN_H_WITH_OS 1
|
|
// #include "../dn.h"
|
|
// #endif
|
|
|
|
DN_API bool DN_MemStartsWith(void const *lhs, DN_USize lhs_size, void const *rhs, DN_USize rhs_size)
|
|
{
|
|
bool result = false;
|
|
if (lhs_size >= rhs_size)
|
|
result = DN_MemEqUnsafe(lhs, rhs, rhs_size);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_MemEq(void const *lhs, DN_USize lhs_size, void const *rhs, DN_USize rhs_size)
|
|
{
|
|
bool result = lhs_size == rhs_size && DN_Memcmp(lhs, rhs, rhs_size) == 0;
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_MemEqUnsafe(void const *lhs, void const *rhs, DN_USize size)
|
|
{
|
|
bool result = DN_Memcmp(lhs, rhs, size) == 0;
|
|
return result;
|
|
}
|
|
|
|
#if !defined(DN_PLATFORM_ARM64) && !defined(DN_PLATFORM_EMSCRIPTEN)
|
|
#define DN_SUPPORTS_CPU_ID
|
|
#endif
|
|
|
|
#if defined(DN_SUPPORTS_CPU_ID) && (defined(DN_COMPILER_GCC) || defined(DN_COMPILER_CLANG))
|
|
#include <cpuid.h>
|
|
#endif
|
|
|
|
DN_CPUFeatureDecl g_dn_cpu_feature_decl[DN_CPUFeature_Count];
|
|
|
|
DN_API DN_U64 DN_AtomicSetValue64(DN_U64 volatile *target, DN_U64 value)
|
|
{
|
|
#if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL)
|
|
__int64 result;
|
|
do {
|
|
result = *target;
|
|
} while (DN_AtomicCompareExchange64(target, value, result) != result);
|
|
return DN_Cast(DN_U64) result;
|
|
#elif defined(DN_COMPILER_GCC) || defined(DN_COMPILER_CLANG)
|
|
DN_U64 result = __sync_lock_test_and_set(target, value);
|
|
return result;
|
|
#else
|
|
#error Unsupported compiler
|
|
#endif
|
|
}
|
|
|
|
DN_API DN_U32 DN_AtomicSetValue32(DN_U32 volatile *target, DN_U32 value)
|
|
{
|
|
#if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL)
|
|
long result;
|
|
do {
|
|
result = *target;
|
|
} while (DN_AtomicCompareExchange32(target, value, result) != result);
|
|
return result;
|
|
#elif defined(DN_COMPILER_GCC) || defined(DN_COMPILER_CLANG)
|
|
long result = __sync_lock_test_and_set(target, value);
|
|
return result;
|
|
#else
|
|
#error Unsupported compiler
|
|
#endif
|
|
}
|
|
|
|
DN_API DN_USize DN_AlignUpPowerOfTwoUSize(DN_USize val)
|
|
{
|
|
DN_USize leading_zeros = DN_CountLeadingZerosUSize(val);
|
|
DN_USize bits = sizeof(DN_USize) * 8 - 1;
|
|
DN_USize result = leading_zeros == 0 ? SIZE_MAX : 1ULL << (bits - leading_zeros + 1);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_U64 DN_AlignUpPowerOfTwoU64(DN_U64 val)
|
|
{
|
|
DN_U64 leading_zeros = DN_CountLeadingZerosU64(val);
|
|
DN_U64 result = leading_zeros == 0 ? UINT64_MAX : 1ULL << (63 - leading_zeros + 1);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_U32 DN_AlignUpPowerOfTwoU32(DN_U32 val)
|
|
{
|
|
DN_U32 leading_zeros = DN_CountLeadingZerosU32(val);
|
|
DN_U32 result = leading_zeros == 0 ? UINT32_MAX : 1ULL << (31 - leading_zeros + 1);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_CPUIDResult DN_CPUID(DN_CPUIDArgs args)
|
|
{
|
|
DN_CPUIDResult result = {};
|
|
#if defined(DN_SUPPORTS_CPU_ID)
|
|
__cpuidex(result.values, args.eax, args.ecx);
|
|
#endif
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_USize DN_CPUHasFeatureArray(DN_CPUReport const *report, DN_CPUFeatureQuery *features, DN_USize features_size)
|
|
{
|
|
DN_USize result = 0;
|
|
DN_USize const BITS = sizeof(report->features[0]) * 8;
|
|
for (DN_ForIndexU(feature_index, features_size)) {
|
|
DN_CPUFeatureQuery *query = features + feature_index;
|
|
DN_USize chunk_index = query->feature / BITS;
|
|
DN_USize chunk_bit = query->feature % BITS;
|
|
DN_U64 chunk = report->features[chunk_index];
|
|
query->available = chunk & (1ULL << chunk_bit);
|
|
result += DN_Cast(int) query->available;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_CPUHasFeature(DN_CPUReport const *report, DN_CPUFeature feature)
|
|
{
|
|
DN_CPUFeatureQuery query = {};
|
|
query.feature = feature;
|
|
bool result = DN_CPUHasFeatureArray(report, &query, 1) == 1;
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_CPUHasAllFeatures(DN_CPUReport const *report, DN_CPUFeature const *features, DN_USize features_size)
|
|
{
|
|
bool result = true;
|
|
for (DN_USize index = 0; result && index < features_size; index++)
|
|
result &= DN_CPUHasFeature(report, features[index]);
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_CPUSetFeature(DN_CPUReport *report, DN_CPUFeature feature)
|
|
{
|
|
DN_Assert(feature < DN_CPUFeature_Count);
|
|
DN_USize const BITS = sizeof(report->features[0]) * 8;
|
|
DN_USize chunk_index = feature / BITS;
|
|
DN_USize chunk_bit = feature % BITS;
|
|
report->features[chunk_index] |= (1ULL << chunk_bit);
|
|
}
|
|
|
|
DN_API DN_CPUReport DN_CPUGetReport()
|
|
{
|
|
DN_CPUReport result = {};
|
|
#if defined(DN_SUPPORTS_CPU_ID)
|
|
DN_CPUIDResult fn_0000_[500] = {};
|
|
DN_CPUIDResult fn_8000_[500] = {};
|
|
int const EXTENDED_FUNC_BASE_EAX = 0x8000'0000;
|
|
int const REGISTER_SIZE = sizeof(fn_0000_[0].reg.eax);
|
|
|
|
// NOTE: Query standard/extended numbers ///////////////////////////////////////////////////////
|
|
{
|
|
DN_CPUIDArgs args = {};
|
|
|
|
// NOTE: Query standard function (e.g. eax = 0x0) for function count + cpu vendor
|
|
args = {};
|
|
fn_0000_[0] = DN_CPUID(args);
|
|
|
|
// NOTE: Query extended function (e.g. eax = 0x8000'0000) for function count + cpu vendor
|
|
args = {};
|
|
args.eax = DN_Cast(int) EXTENDED_FUNC_BASE_EAX;
|
|
fn_8000_[0] = DN_CPUID(args);
|
|
}
|
|
|
|
// NOTE: Extract function count ////////////////////////////////////////////////////////////////
|
|
int const STANDARD_FUNC_MAX_EAX = fn_0000_[0x0000].reg.eax;
|
|
int const EXTENDED_FUNC_MAX_EAX = fn_8000_[0x0000].reg.eax;
|
|
|
|
// NOTE: Enumerate all CPUID results for the known function counts /////////////////////////////
|
|
{
|
|
DN_AssertF((STANDARD_FUNC_MAX_EAX + 1) <= DN_ArrayCountI(fn_0000_),
|
|
"Max standard count is %d",
|
|
STANDARD_FUNC_MAX_EAX + 1);
|
|
DN_AssertF((DN_Cast(DN_ISize) EXTENDED_FUNC_MAX_EAX - EXTENDED_FUNC_BASE_EAX + 1) <= DN_ArrayCountI(fn_8000_),
|
|
"Max extended count is %zu",
|
|
DN_Cast(DN_ISize) EXTENDED_FUNC_MAX_EAX - EXTENDED_FUNC_BASE_EAX + 1);
|
|
|
|
for (int eax = 1; eax <= STANDARD_FUNC_MAX_EAX; eax++) {
|
|
DN_CPUIDArgs args = {};
|
|
args.eax = eax;
|
|
fn_0000_[eax] = DN_CPUID(args);
|
|
}
|
|
|
|
for (int eax = EXTENDED_FUNC_BASE_EAX + 1, index = 1; eax <= EXTENDED_FUNC_MAX_EAX; eax++, index++) {
|
|
DN_CPUIDArgs args = {};
|
|
args.eax = eax;
|
|
fn_8000_[index] = DN_CPUID(args);
|
|
}
|
|
}
|
|
|
|
// NOTE: Query CPU vendor //////////////////////////////////////////////////////////////////////
|
|
{
|
|
DN_Memcpy(result.vendor + 0, &fn_8000_[0x0000].reg.ebx, REGISTER_SIZE);
|
|
DN_Memcpy(result.vendor + 4, &fn_8000_[0x0000].reg.edx, REGISTER_SIZE);
|
|
DN_Memcpy(result.vendor + 8, &fn_8000_[0x0000].reg.ecx, REGISTER_SIZE);
|
|
}
|
|
|
|
// NOTE: Query CPU brand ///////////////////////////////////////////////////////////////////////
|
|
if (EXTENDED_FUNC_MAX_EAX >= (EXTENDED_FUNC_BASE_EAX + 4)) {
|
|
DN_Memcpy(result.brand + 0, &fn_8000_[0x0002].reg.eax, REGISTER_SIZE);
|
|
DN_Memcpy(result.brand + 4, &fn_8000_[0x0002].reg.ebx, REGISTER_SIZE);
|
|
DN_Memcpy(result.brand + 8, &fn_8000_[0x0002].reg.ecx, REGISTER_SIZE);
|
|
DN_Memcpy(result.brand + 12, &fn_8000_[0x0002].reg.edx, REGISTER_SIZE);
|
|
|
|
DN_Memcpy(result.brand + 16, &fn_8000_[0x0003].reg.eax, REGISTER_SIZE);
|
|
DN_Memcpy(result.brand + 20, &fn_8000_[0x0003].reg.ebx, REGISTER_SIZE);
|
|
DN_Memcpy(result.brand + 24, &fn_8000_[0x0003].reg.ecx, REGISTER_SIZE);
|
|
DN_Memcpy(result.brand + 28, &fn_8000_[0x0003].reg.edx, REGISTER_SIZE);
|
|
|
|
DN_Memcpy(result.brand + 32, &fn_8000_[0x0004].reg.eax, REGISTER_SIZE);
|
|
DN_Memcpy(result.brand + 36, &fn_8000_[0x0004].reg.ebx, REGISTER_SIZE);
|
|
DN_Memcpy(result.brand + 40, &fn_8000_[0x0004].reg.ecx, REGISTER_SIZE);
|
|
DN_Memcpy(result.brand + 44, &fn_8000_[0x0004].reg.edx, REGISTER_SIZE);
|
|
|
|
DN_Assert(result.brand[sizeof(result.brand) - 1] == 0);
|
|
}
|
|
|
|
// NOTE: Query CPU features //////////////////////////////////////////////////////////////////
|
|
for (DN_USize ext_index = 0; ext_index < DN_CPUFeature_Count; ext_index++) {
|
|
bool available = false;
|
|
|
|
// NOTE: Mask bits taken from various manuals
|
|
// - AMD64 Architecture Programmer's Manual, Volumes 1-5
|
|
// - https://en.wikipedia.org/wiki/CPUID#Calling_CPUID
|
|
switch (DN_Cast(DN_CPUFeature) ext_index) {
|
|
case DN_CPUFeature_3DNow: available = (fn_8000_[0x0001].reg.edx & (1 << 31)); break;
|
|
case DN_CPUFeature_3DNowExt: available = (fn_8000_[0x0001].reg.edx & (1 << 30)); break;
|
|
case DN_CPUFeature_ABM: available = (fn_8000_[0x0001].reg.ecx & (1 << 5)); break;
|
|
case DN_CPUFeature_AES: available = (fn_0000_[0x0001].reg.ecx & (1 << 25)); break;
|
|
case DN_CPUFeature_AVX: available = (fn_0000_[0x0001].reg.ecx & (1 << 28)); break;
|
|
case DN_CPUFeature_AVX2: available = (fn_0000_[0x0007].reg.ebx & (1 << 0)); break;
|
|
case DN_CPUFeature_AVX512F: available = (fn_0000_[0x0007].reg.ebx & (1 << 16)); break;
|
|
case DN_CPUFeature_AVX512DQ: available = (fn_0000_[0x0007].reg.ebx & (1 << 17)); break;
|
|
case DN_CPUFeature_AVX512IFMA: available = (fn_0000_[0x0007].reg.ebx & (1 << 21)); break;
|
|
case DN_CPUFeature_AVX512PF: available = (fn_0000_[0x0007].reg.ebx & (1 << 26)); break;
|
|
case DN_CPUFeature_AVX512ER: available = (fn_0000_[0x0007].reg.ebx & (1 << 27)); break;
|
|
case DN_CPUFeature_AVX512CD: available = (fn_0000_[0x0007].reg.ebx & (1 << 28)); break;
|
|
case DN_CPUFeature_AVX512BW: available = (fn_0000_[0x0007].reg.ebx & (1 << 30)); break;
|
|
case DN_CPUFeature_AVX512VL: available = (fn_0000_[0x0007].reg.ebx & (1 << 31)); break;
|
|
case DN_CPUFeature_AVX512VBMI: available = (fn_0000_[0x0007].reg.ecx & (1 << 1)); break;
|
|
case DN_CPUFeature_AVX512VBMI2: available = (fn_0000_[0x0007].reg.ecx & (1 << 6)); break;
|
|
case DN_CPUFeature_AVX512VNNI: available = (fn_0000_[0x0007].reg.ecx & (1 << 11)); break;
|
|
case DN_CPUFeature_AVX512BITALG: available = (fn_0000_[0x0007].reg.ecx & (1 << 12)); break;
|
|
case DN_CPUFeature_AVX512VPOPCNTDQ: available = (fn_0000_[0x0007].reg.ecx & (1 << 14)); break;
|
|
case DN_CPUFeature_AVX5124VNNIW: available = (fn_0000_[0x0007].reg.edx & (1 << 2)); break;
|
|
case DN_CPUFeature_AVX5124FMAPS: available = (fn_0000_[0x0007].reg.edx & (1 << 3)); break;
|
|
case DN_CPUFeature_AVX512VP2INTERSECT: available = (fn_0000_[0x0007].reg.edx & (1 << 8)); break;
|
|
case DN_CPUFeature_AVX512FP16: available = (fn_0000_[0x0007].reg.edx & (1 << 23)); break;
|
|
case DN_CPUFeature_CLZERO: available = (fn_8000_[0x0008].reg.ebx & (1 << 0)); break;
|
|
case DN_CPUFeature_CMPXCHG8B: available = (fn_0000_[0x0001].reg.edx & (1 << 8)); break;
|
|
case DN_CPUFeature_CMPXCHG16B: available = (fn_0000_[0x0001].reg.ecx & (1 << 13)); break;
|
|
case DN_CPUFeature_F16C: available = (fn_0000_[0x0001].reg.ecx & (1 << 29)); break;
|
|
case DN_CPUFeature_FMA: available = (fn_0000_[0x0001].reg.ecx & (1 << 12)); break;
|
|
case DN_CPUFeature_FMA4: available = (fn_8000_[0x0001].reg.ecx & (1 << 16)); break;
|
|
case DN_CPUFeature_FP128: available = (fn_8000_[0x001A].reg.eax & (1 << 0)); break;
|
|
case DN_CPUFeature_FP256: available = (fn_8000_[0x001A].reg.eax & (1 << 2)); break;
|
|
case DN_CPUFeature_FPU: available = (fn_0000_[0x0001].reg.edx & (1 << 0)); break;
|
|
case DN_CPUFeature_MMX: available = (fn_0000_[0x0001].reg.edx & (1 << 23)); break;
|
|
case DN_CPUFeature_MONITOR: available = (fn_0000_[0x0001].reg.ecx & (1 << 3)); break;
|
|
case DN_CPUFeature_MOVBE: available = (fn_0000_[0x0001].reg.ecx & (1 << 22)); break;
|
|
case DN_CPUFeature_MOVU: available = (fn_8000_[0x001A].reg.eax & (1 << 1)); break;
|
|
case DN_CPUFeature_MmxExt: available = (fn_8000_[0x0001].reg.edx & (1 << 22)); break;
|
|
case DN_CPUFeature_PCLMULQDQ: available = (fn_0000_[0x0001].reg.ecx & (1 << 1)); break;
|
|
case DN_CPUFeature_POPCNT: available = (fn_0000_[0x0001].reg.ecx & (1 << 23)); break;
|
|
case DN_CPUFeature_RDRAND: available = (fn_0000_[0x0001].reg.ecx & (1 << 30)); break;
|
|
case DN_CPUFeature_RDSEED: available = (fn_0000_[0x0007].reg.ebx & (1 << 18)); break;
|
|
case DN_CPUFeature_RDTSCP: available = (fn_8000_[0x0001].reg.edx & (1 << 27)); break;
|
|
case DN_CPUFeature_SHA: available = (fn_0000_[0x0007].reg.ebx & (1 << 29)); break;
|
|
case DN_CPUFeature_SSE: available = (fn_0000_[0x0001].reg.edx & (1 << 25)); break;
|
|
case DN_CPUFeature_SSE2: available = (fn_0000_[0x0001].reg.edx & (1 << 26)); break;
|
|
case DN_CPUFeature_SSE3: available = (fn_0000_[0x0001].reg.ecx & (1 << 0)); break;
|
|
case DN_CPUFeature_SSE41: available = (fn_0000_[0x0001].reg.ecx & (1 << 19)); break;
|
|
case DN_CPUFeature_SSE42: available = (fn_0000_[0x0001].reg.ecx & (1 << 20)); break;
|
|
case DN_CPUFeature_SSE4A: available = (fn_8000_[0x0001].reg.ecx & (1 << 6)); break;
|
|
case DN_CPUFeature_SSSE3: available = (fn_0000_[0x0001].reg.ecx & (1 << 9)); break;
|
|
case DN_CPUFeature_TSC: available = (fn_0000_[0x0001].reg.edx & (1 << 4)); break;
|
|
case DN_CPUFeature_TscInvariant: available = (fn_8000_[0x0007].reg.edx & (1 << 8)); break;
|
|
case DN_CPUFeature_VAES: available = (fn_0000_[0x0007].reg.ecx & (1 << 9)); break;
|
|
case DN_CPUFeature_VPCMULQDQ: available = (fn_0000_[0x0007].reg.ecx & (1 << 10)); break;
|
|
case DN_CPUFeature_Count: DN_InvalidCodePath; break;
|
|
}
|
|
|
|
if (available)
|
|
DN_CPUSetFeature(&result, DN_Cast(DN_CPUFeature) ext_index);
|
|
}
|
|
#endif // DN_SUPPORTS_CPU_ID
|
|
return result;
|
|
}
|
|
|
|
// NOTE: DN_TicketMutex ////////////////////////////////////////////////////////////////////////////
|
|
DN_API void DN_TicketMutex_Begin(DN_TicketMutex *mutex)
|
|
{
|
|
unsigned int ticket = DN_AtomicAddU32(&mutex->ticket, 1);
|
|
DN_TicketMutex_BeginTicket(mutex, ticket);
|
|
}
|
|
|
|
DN_API void DN_TicketMutex_End(DN_TicketMutex *mutex)
|
|
{
|
|
DN_AtomicAddU32(&mutex->serving, 1);
|
|
}
|
|
|
|
DN_API DN_UInt DN_TicketMutex_MakeTicket(DN_TicketMutex *mutex)
|
|
{
|
|
DN_UInt result = DN_AtomicAddU32(&mutex->ticket, 1);
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_TicketMutex_BeginTicket(DN_TicketMutex const *mutex, DN_UInt ticket)
|
|
{
|
|
DN_AssertF(mutex->serving <= ticket,
|
|
"Mutex skipped ticket? Was ticket generated by the correct mutex via MakeTicket? ticket = %u, "
|
|
"mutex->serving = %u",
|
|
ticket,
|
|
mutex->serving);
|
|
while (ticket != mutex->serving) {
|
|
// NOTE: Use spinlock intrinsic
|
|
_mm_pause();
|
|
}
|
|
}
|
|
|
|
DN_API bool DN_TicketMutex_CanLock(DN_TicketMutex const *mutex, DN_UInt ticket)
|
|
{
|
|
bool result = (ticket == mutex->serving);
|
|
return result;
|
|
}
|
|
|
|
#if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL)
|
|
#if !defined(DN_CRT_SECURE_NO_WARNINGS_PREVIOUSLY_DEFINED)
|
|
#undef _CRT_SECURE_NO_WARNINGS
|
|
#endif
|
|
#endif
|
|
|
|
// NOTE: DN_Bit ////////////////////////////////////////////////////////////////////////////////////
|
|
DN_API void DN_BitUnsetInplace(DN_USize *flags, DN_USize bitfield)
|
|
{
|
|
*flags = (*flags & ~bitfield);
|
|
}
|
|
|
|
DN_API void DN_BitSetInplace(DN_USize *flags, DN_USize bitfield)
|
|
{
|
|
*flags = (*flags | bitfield);
|
|
}
|
|
|
|
DN_API bool DN_BitIsSet(DN_USize bits, DN_USize bits_to_set)
|
|
{
|
|
auto result = DN_Cast(bool)((bits & bits_to_set) == bits_to_set);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_BitIsNotSet(DN_USize bits, DN_USize bits_to_check)
|
|
{
|
|
auto result = !DN_BitIsSet(bits, bits_to_check);
|
|
return result;
|
|
}
|
|
|
|
// NOTE: DN_Safe ///////////////////////////////////////////////////////////////////////////////////
|
|
DN_API DN_I64 DN_SafeAddI64(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_SafeMulI64(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_SafeAddU64(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_SafeSubU64(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_SafeMulU64(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_SafeSubU32(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_ASanPoisonMemoryRegion(void const volatile *ptr, DN_USize size)
|
|
{
|
|
if (!ptr || !size)
|
|
return;
|
|
|
|
#if DN_HAS_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__)
|
|
DN_AssertF(DN_IsPowerOfTwoAligned(ptr, 8),
|
|
"Poisoning requires the pointer to be aligned on an 8 byte boundary");
|
|
|
|
__asan_poison_memory_region(ptr, size);
|
|
if (DN_ASAN_VET_POISON) {
|
|
DN_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_ASanUnpoisonMemoryRegion(void const volatile *ptr, DN_USize size)
|
|
{
|
|
if (!ptr || !size)
|
|
return;
|
|
|
|
#if DN_HAS_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__)
|
|
__asan_unpoison_memory_region(ptr, size);
|
|
if (DN_ASAN_VET_POISON)
|
|
DN_HardAssert(__asan_region_is_poisoned((void *)ptr, size) == 0);
|
|
#else
|
|
(void)ptr;
|
|
(void)size;
|
|
#endif
|
|
}
|
|
|
|
DN_API DN_F32 DN_EpsilonClampF32(DN_F32 value, DN_F32 target, DN_F32 epsilon)
|
|
{
|
|
DN_F32 delta = DN_Abs(target - value);
|
|
DN_F32 result = (delta < epsilon) ? target : value;
|
|
return result;
|
|
}
|
|
|
|
static DN_MemBlock *DN_ArenaBlockFromMemFuncs_(DN_U64 reserve, DN_U64 commit, bool track_alloc, bool alloc_can_leak, DN_MemFuncs mem_funcs)
|
|
{
|
|
DN_MemBlock *result = nullptr;
|
|
switch (mem_funcs.type) {
|
|
case DN_MemFuncsType_Nil:
|
|
break;
|
|
|
|
case DN_MemFuncsType_Heap: {
|
|
DN_AssertF(reserve > DN_ARENA_HEADER_SIZE, "%I64u > %I64u", reserve, DN_ARENA_HEADER_SIZE);
|
|
result = DN_Cast(DN_MemBlock *) mem_funcs.heap_alloc(reserve);
|
|
if (!result)
|
|
return result;
|
|
|
|
result->used = DN_ARENA_HEADER_SIZE;
|
|
result->commit = reserve;
|
|
result->reserve = reserve;
|
|
} break;
|
|
|
|
case DN_MemFuncsType_Virtual: {
|
|
DN_AssertF(mem_funcs.virtual_page_size, "Page size must be set to a non-zero, power of two value");
|
|
DN_Assert(DN_IsPowerOfTwo(mem_funcs.virtual_page_size));
|
|
|
|
DN_USize const page_size = mem_funcs.virtual_page_size;
|
|
DN_U64 real_reserve = reserve ? reserve : DN_ARENA_RESERVE_SIZE;
|
|
DN_U64 real_commit = commit ? commit : DN_ARENA_COMMIT_SIZE;
|
|
real_reserve = DN_AlignUpPowerOfTwo(real_reserve, page_size);
|
|
real_commit = DN_Min(DN_AlignUpPowerOfTwo(real_commit, page_size), real_reserve);
|
|
DN_AssertF(DN_ARENA_HEADER_SIZE < real_commit && real_commit <= real_reserve, "%I64u < %I64u <= %I64u", DN_ARENA_HEADER_SIZE, real_commit, real_reserve);
|
|
|
|
DN_MemCommit mem_commit = real_reserve == real_commit ? DN_MemCommit_Yes : DN_MemCommit_No;
|
|
result = DN_Cast(DN_MemBlock *) mem_funcs.virtual_reserve(real_reserve, mem_commit, DN_MemPage_ReadWrite);
|
|
if (!result)
|
|
return result;
|
|
|
|
if (mem_commit == DN_MemCommit_No && !mem_funcs.virtual_commit(result, real_commit, DN_MemPage_ReadWrite)) {
|
|
mem_funcs.virtual_release(result, real_reserve);
|
|
return result;
|
|
}
|
|
|
|
result->used = DN_ARENA_HEADER_SIZE;
|
|
result->commit = real_commit;
|
|
result->reserve = real_reserve;
|
|
} break;
|
|
}
|
|
|
|
if (track_alloc && result)
|
|
DN_LeakTrackAlloc(&g_dn_->leak, result, result->reserve, alloc_can_leak);
|
|
|
|
return result;
|
|
}
|
|
|
|
static bool DN_ArenaHasPoison_(DN_MemFlags flags)
|
|
{
|
|
DN_MSVC_WARNING_PUSH
|
|
DN_MSVC_WARNING_DISABLE(6237) // warning C6237: (<zero> && <expression>) is always zero. <expression> is never evaluated and might have side effects.
|
|
bool result = DN_ASAN_POISON && DN_BitIsNotSet(flags, DN_MemFlags_NoPoison);
|
|
DN_MSVC_WARNING_POP
|
|
return result;
|
|
}
|
|
|
|
static DN_MemBlock *DN_MemBlockFromMemFuncsFlags_(DN_U64 reserve, DN_U64 commit, DN_MemFlags flags, DN_MemFuncs mem_funcs)
|
|
{
|
|
bool track_alloc = (flags & DN_MemFlags_NoAllocTrack) == 0;
|
|
bool alloc_can_leak = flags & DN_MemFlags_AllocCanLeak;
|
|
DN_MemBlock *result = DN_ArenaBlockFromMemFuncs_(reserve, commit, track_alloc, alloc_can_leak, mem_funcs);
|
|
if (result && DN_ArenaHasPoison_(flags))
|
|
DN_ASanPoisonMemoryRegion(DN_Cast(char *) result + DN_ARENA_HEADER_SIZE, result->commit - DN_ARENA_HEADER_SIZE);
|
|
return result;
|
|
}
|
|
|
|
static void DN_MemListOnNewBlock_(DN_MemList *mem, DN_MemBlock const *block)
|
|
{
|
|
DN_Assert(mem);
|
|
if (block) {
|
|
mem->stats.info.used += block->used;
|
|
mem->stats.info.commit += block->commit;
|
|
mem->stats.info.reserve += block->reserve;
|
|
mem->stats.info.blocks += 1;
|
|
|
|
mem->stats.hwm.used = DN_Max(mem->stats.hwm.used, mem->stats.info.used);
|
|
mem->stats.hwm.commit = DN_Max(mem->stats.hwm.commit, mem->stats.info.commit);
|
|
mem->stats.hwm.reserve = DN_Max(mem->stats.hwm.reserve, mem->stats.info.reserve);
|
|
mem->stats.hwm.blocks = DN_Max(mem->stats.hwm.blocks, mem->stats.info.blocks);
|
|
}
|
|
}
|
|
|
|
DN_API DN_MemStats DN_MemStatsSum(DN_MemStats lhs, DN_MemStats rhs)
|
|
{
|
|
DN_MemStats array[] = {lhs, rhs};
|
|
DN_MemStats result = DN_MemStatsSumArray(array, DN_ArrayCountU(array));
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_MemStats DN_MemStatsSumArray(DN_MemStats const *array, DN_USize size)
|
|
{
|
|
DN_MemStats result = {};
|
|
for (DN_ForItSize(it, DN_MemStats const, array, size)) {
|
|
DN_MemStats stats = *it.data;
|
|
result.info.used += stats.info.used;
|
|
result.info.commit += stats.info.commit;
|
|
result.info.reserve += stats.info.reserve;
|
|
result.info.blocks += stats.info.blocks;
|
|
|
|
result.hwm.used = DN_Max(result.hwm.used, result.info.used);
|
|
result.hwm.commit = DN_Max(result.hwm.commit, result.info.commit);
|
|
result.hwm.reserve = DN_Max(result.hwm.reserve, result.info.reserve);
|
|
result.hwm.blocks = DN_Max(result.hwm.blocks, result.info.blocks);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_MemList DN_MemListFromBuffer(void *buffer, DN_USize size, DN_MemFlags flags)
|
|
{
|
|
DN_Assert(buffer);
|
|
DN_AssertF(DN_ARENA_HEADER_SIZE < size, "Buffer (%zu bytes) too small, need atleast %zu bytes to store arena metadata", size, DN_ARENA_HEADER_SIZE);
|
|
DN_AssertF(DN_IsPowerOfTwo(size), "Buffer (%zu bytes) must be a power-of-two", size);
|
|
|
|
// NOTE: Init block
|
|
DN_MemBlock *block = DN_Cast(DN_MemBlock *) buffer;
|
|
block->commit = size;
|
|
block->reserve = size;
|
|
block->used = DN_ARENA_HEADER_SIZE;
|
|
if (block && DN_ArenaHasPoison_(flags))
|
|
DN_ASanPoisonMemoryRegion(DN_Cast(char *) block + DN_ARENA_HEADER_SIZE, block->commit - DN_ARENA_HEADER_SIZE);
|
|
|
|
DN_MemList result = {};
|
|
result.flags = flags | DN_MemFlags_NoGrow | DN_MemFlags_NoAllocTrack | DN_MemFlags_AllocCanLeak | DN_MemFlags_UserBuffer;
|
|
result.curr = block;
|
|
DN_MemListOnNewBlock_(&result, result.curr);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_MemList DN_MemListFromMemFuncs(DN_U64 reserve, DN_U64 commit, DN_MemFlags flags, DN_MemFuncs mem_funcs)
|
|
{
|
|
DN_MemList result = {};
|
|
result.funcs = mem_funcs;
|
|
result.flags |= flags | DN_MemFlags_MemFuncs;
|
|
result.curr = DN_MemBlockFromMemFuncsFlags_(reserve, commit, flags, mem_funcs);
|
|
DN_MemListOnNewBlock_(&result, result.curr);
|
|
return result;
|
|
}
|
|
|
|
static void DN_MemBlockDeinit_(DN_MemList const *mem, DN_MemBlock *block)
|
|
{
|
|
DN_USize release_size = block->reserve;
|
|
if (DN_BitIsNotSet(mem->flags, DN_MemFlags_NoAllocTrack))
|
|
DN_LeakTrackDealloc(&g_dn_->leak, block);
|
|
|
|
if (DN_ArenaHasPoison_(mem->flags))
|
|
DN_ASanUnpoisonMemoryRegion(block, block->commit);
|
|
|
|
if (mem->flags & DN_MemFlags_MemFuncs) {
|
|
if (mem->funcs.type == DN_MemFuncsType_Heap)
|
|
mem->funcs.heap_dealloc(block);
|
|
else
|
|
mem->funcs.virtual_release(block, release_size);
|
|
}
|
|
}
|
|
|
|
DN_API void DN_MemListDeinit(DN_MemList *mem)
|
|
{
|
|
for (DN_MemBlock *block = mem ? mem->curr : nullptr; block;) {
|
|
DN_MemBlock *block_to_free = block;
|
|
block = block->prev;
|
|
DN_MemBlockDeinit_(mem, block_to_free);
|
|
}
|
|
if (mem)
|
|
*mem = {};
|
|
}
|
|
|
|
DN_API bool DN_MemListCommitTo(DN_MemList *mem, DN_U64 pos)
|
|
{
|
|
if (!mem || !mem->curr)
|
|
return false;
|
|
|
|
DN_MemBlock *curr = mem->curr;
|
|
if (pos <= curr->commit)
|
|
return true;
|
|
|
|
DN_U64 real_pos = pos;
|
|
if (!DN_Check(pos <= curr->reserve))
|
|
real_pos = curr->reserve;
|
|
|
|
DN_Assert(mem->funcs.virtual_page_size);
|
|
DN_USize end_commit = DN_AlignUpPowerOfTwo(real_pos, mem->funcs.virtual_page_size);
|
|
DN_USize commit_size = end_commit - curr->commit;
|
|
char *commit_ptr = DN_Cast(char *) curr + curr->commit;
|
|
if (!mem->funcs.virtual_commit(commit_ptr, commit_size, DN_MemPage_ReadWrite))
|
|
return false;
|
|
|
|
if (DN_ArenaHasPoison_(mem->flags))
|
|
DN_ASanPoisonMemoryRegion(commit_ptr, commit_size);
|
|
|
|
curr->commit = end_commit;
|
|
return true;
|
|
}
|
|
|
|
DN_API bool DN_MemListCommit(DN_MemList *mem, DN_U64 size)
|
|
{
|
|
if (!mem || !mem->curr)
|
|
return false;
|
|
DN_U64 pos = DN_Min(mem->curr->reserve, mem->curr->commit + size);
|
|
bool result = DN_MemListCommitTo(mem, pos);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_MemListGrow(DN_MemList *mem, DN_U64 reserve, DN_U64 commit)
|
|
{
|
|
if (mem->flags & (DN_MemFlags_NoGrow | DN_MemFlags_UserBuffer))
|
|
return false;
|
|
|
|
bool result = false;
|
|
DN_MemBlock *new_block = DN_MemBlockFromMemFuncsFlags_(reserve, commit, mem->flags, mem->funcs);
|
|
if (new_block) {
|
|
result = true;
|
|
new_block->prev = mem->curr;
|
|
mem->curr = new_block;
|
|
new_block->reserve_sum = new_block->prev->reserve_sum + new_block->prev->reserve;
|
|
DN_MemListOnNewBlock_(mem, mem->curr);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API void *DN_MemListAlloc(DN_MemList *mem, DN_U64 size, uint8_t align, DN_ZMem z_mem)
|
|
{
|
|
if (!mem)
|
|
return nullptr;
|
|
|
|
if (!mem->curr) {
|
|
mem->curr = DN_MemBlockFromMemFuncsFlags_(DN_ARENA_RESERVE_SIZE, DN_ARENA_COMMIT_SIZE, mem->flags, mem->funcs);
|
|
DN_MemListOnNewBlock_(mem, mem->curr);
|
|
}
|
|
|
|
if (!mem->curr)
|
|
return nullptr;
|
|
|
|
try_alloc_again:
|
|
DN_MemBlock *curr = mem->curr;
|
|
bool poison = DN_ArenaHasPoison_(mem->flags);
|
|
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 (mem->flags & (DN_MemFlags_NoGrow | DN_MemFlags_UserBuffer))
|
|
return nullptr;
|
|
DN_USize new_reserve = DN_Max(DN_ARENA_HEADER_SIZE + alloc_size, DN_ARENA_RESERVE_SIZE);
|
|
DN_USize new_commit = DN_Max(DN_ARENA_HEADER_SIZE + alloc_size, DN_ARENA_COMMIT_SIZE);
|
|
if (!DN_MemListGrow(mem, new_reserve, new_commit))
|
|
return nullptr;
|
|
goto try_alloc_again;
|
|
}
|
|
|
|
DN_USize prev_arena_commit = curr->commit;
|
|
if (end_pos > curr->commit) {
|
|
DN_Assert(mem->funcs.virtual_page_size);
|
|
DN_Assert(mem->funcs.type == DN_MemFuncsType_Virtual);
|
|
DN_Assert((mem->flags & DN_MemFlags_UserBuffer) == 0);
|
|
DN_USize end_commit = DN_AlignUpPowerOfTwo(end_pos, mem->funcs.virtual_page_size);
|
|
DN_USize commit_size = end_commit - curr->commit;
|
|
char *commit_ptr = DN_Cast(char *) curr + curr->commit;
|
|
if (!mem->funcs.virtual_commit(commit_ptr, commit_size, DN_MemPage_ReadWrite))
|
|
return nullptr;
|
|
if (poison && DN_BitIsNotSet(mem->flags, DN_MemFlags_SimAlloc))
|
|
DN_ASanPoisonMemoryRegion(commit_ptr, commit_size);
|
|
curr->commit = end_commit;
|
|
mem->stats.info.commit += commit_size;
|
|
mem->stats.hwm.commit = DN_Max(mem->stats.hwm.commit, mem->stats.info.commit);
|
|
}
|
|
|
|
void *result = DN_Cast(char *) curr + offset_pos;
|
|
curr->used += alloc_size;
|
|
mem->stats.info.used += alloc_size;
|
|
mem->stats.hwm.used = DN_Max(mem->stats.hwm.used, mem->stats.info.used);
|
|
|
|
if (poison && DN_BitIsNotSet(mem->flags, DN_MemFlags_SimAlloc))
|
|
DN_ASanUnpoisonMemoryRegion(result, size);
|
|
|
|
if (z_mem == DN_ZMem_Yes && DN_BitIsNotSet(mem->flags, DN_MemFlags_SimAlloc)) {
|
|
DN_USize reused_bytes = DN_Min(prev_arena_commit - offset_pos, size);
|
|
DN_Memset(result, 0, reused_bytes);
|
|
}
|
|
|
|
DN_Assert(mem->stats.hwm.used >= mem->stats.info.used);
|
|
DN_Assert(mem->stats.hwm.commit >= mem->stats.info.commit);
|
|
DN_Assert(mem->stats.hwm.reserve >= mem->stats.info.reserve);
|
|
DN_Assert(mem->stats.hwm.blocks >= mem->stats.info.blocks);
|
|
return result;
|
|
}
|
|
|
|
DN_API void *DN_MemListAllocContiguous(DN_MemList *mem, DN_U64 size, uint8_t align, DN_ZMem z_mem)
|
|
{
|
|
DN_MemFlags prev_flags = mem->flags;
|
|
mem->flags |= (DN_MemFlags_NoGrow | DN_MemFlags_NoPoison);
|
|
void *memory = DN_MemListAlloc(mem, size, align, z_mem);
|
|
mem->flags = prev_flags;
|
|
return memory;
|
|
}
|
|
|
|
DN_API void *DN_MemListCopy(DN_MemList *mem, void const *data, DN_U64 size, uint8_t align)
|
|
{
|
|
if (!mem || !data || size == 0)
|
|
return nullptr;
|
|
void *result = DN_MemListAlloc(mem, size, align, DN_ZMem_No);
|
|
if (result)
|
|
DN_Memcpy(result, data, size);
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_MemListPopTo(DN_MemList *mem, DN_U64 init_used)
|
|
{
|
|
if (!mem || !mem->curr)
|
|
return;
|
|
|
|
// NOTE: Free any memory blocks allocated additionally from the initial block to revert to
|
|
DN_U64 used = DN_Max(DN_ARENA_HEADER_SIZE, init_used);
|
|
DN_MemBlock *curr = mem->curr;
|
|
while (curr->reserve_sum >= used) {
|
|
DN_MemBlock *block_to_free = curr;
|
|
mem->stats.info.used -= block_to_free->used;
|
|
mem->stats.info.commit -= block_to_free->commit;
|
|
mem->stats.info.reserve -= block_to_free->reserve;
|
|
mem->stats.info.blocks -= 1;
|
|
if (mem->flags & DN_MemFlags_UserBuffer)
|
|
break;
|
|
curr = curr->prev;
|
|
DN_MemBlockDeinit_(mem, block_to_free);
|
|
}
|
|
|
|
// NOTE: Revert the memory block we returned to
|
|
DN_U64 old_used = curr->used;
|
|
mem->stats.info.used = old_used;
|
|
mem->curr = curr;
|
|
curr->used = used - curr->reserve_sum;
|
|
|
|
// NOTE: Scrub memory that we used previously in the block but no longer after reverting
|
|
if (DN_SCRUB_UNINIT_MEM_BYTE) {
|
|
if (old_used > curr->used) {
|
|
char *discarded = (char *)curr + curr->used;
|
|
DN_Memset(discarded, DN_SCRUB_UNINIT_MEM_BYTE, old_used - curr->used);
|
|
}
|
|
}
|
|
|
|
// NOTE: ASAN Poison
|
|
if (DN_ArenaHasPoison_(mem->flags)) {
|
|
char *poison_ptr = (char *)curr + DN_AlignUpPowerOfTwo(curr->used, DN_ASAN_POISON_ALIGNMENT);
|
|
DN_USize poison_size = ((char *)curr + curr->commit) - poison_ptr;
|
|
DN_ASanPoisonMemoryRegion(poison_ptr, poison_size);
|
|
}
|
|
mem->stats.info.used += curr->used;
|
|
}
|
|
|
|
DN_API void DN_MemListPop(DN_MemList *mem, DN_U64 amount)
|
|
{
|
|
DN_MemBlock *curr = mem->curr;
|
|
DN_USize used_sum = curr->reserve_sum + curr->used;
|
|
if (!DN_Check(amount <= used_sum))
|
|
amount = used_sum;
|
|
DN_USize pop_to = used_sum - amount;
|
|
DN_MemListPopTo(mem, pop_to);
|
|
}
|
|
|
|
DN_API DN_U64 DN_MemListPos(DN_MemList const *mem)
|
|
{
|
|
DN_U64 result = (mem && mem->curr) ? mem->curr->reserve_sum + mem->curr->used : 0;
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_MemListClear(DN_MemList *mem)
|
|
{
|
|
DN_MemListPopTo(mem, 0);
|
|
}
|
|
|
|
DN_API bool DN_MemListOwnsPtr(DN_MemList const *mem, void *ptr)
|
|
{
|
|
bool result = false;
|
|
uintptr_t uint_ptr = DN_Cast(uintptr_t) ptr;
|
|
for (DN_MemBlock const *block = mem ? mem->curr : nullptr; !result && block; block = block->prev) {
|
|
uintptr_t begin = DN_Cast(uintptr_t) block + DN_ARENA_HEADER_SIZE;
|
|
uintptr_t end = begin + block->reserve;
|
|
result = uint_ptr >= begin && uint_ptr <= end;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8x64 DN_MemListInfoStr8x64(DN_MemListInfo info)
|
|
{
|
|
DN_Str8x64 result = {};
|
|
DN_Str8x32 used = DN_ByteCountStr8x32(info.used);
|
|
DN_Str8x32 commit = DN_ByteCountStr8x32(info.commit);
|
|
DN_Str8x32 reserve = DN_ByteCountStr8x32(info.reserve);
|
|
// NOTE: Blocks, Used, Commit, Reserve
|
|
result = DN_Str8x64FromFmt("B=%u U=%.*s C=%.*s R=%.*s", DN_Cast(DN_U32)info.blocks, DN_Str8PrintFmt(used), DN_Str8PrintFmt(commit), DN_Str8PrintFmt(reserve));
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_MemListTemp DN_MemListTempBegin(DN_MemList *mem)
|
|
{
|
|
DN_MemListTemp result = {};
|
|
if (mem) {
|
|
result.mem = mem;
|
|
result.used_sum = mem->curr ? mem->curr->reserve_sum + mem->curr->used : 0;
|
|
}
|
|
return result;
|
|
};
|
|
|
|
DN_API void DN_MemListTempEnd(DN_MemListTemp temp)
|
|
{
|
|
DN_MemListPopTo(temp.mem, temp.used_sum);
|
|
};
|
|
|
|
DN_Str8 const DN_MEM_LIST_UAF_TRACING_DISABLED_MORE_INFO_STR8_ = DN_Str8Lit(
|
|
"\n\nSet `DN_MemFlags_TempMemUAFTrace` on the affected arenas or "
|
|
"`#define DN_ARENA_TEMP_MEM_UAF_TRACE_ON_BY_DEFAULT 1` for more information"
|
|
);
|
|
|
|
#if defined(DN_ARENA_TEMP_MEM_UAF_GUARD)
|
|
static bool DN_MemListUAFTracingEnabled_(DN_MemList *mem)
|
|
{
|
|
bool result = DN_ARENA_TEMP_MEM_UAF_TRACE_ON_BY_DEFAULT;
|
|
if (!result)
|
|
result = mem->flags & DN_MemFlags_TempMemUAFTrace;
|
|
if (mem->flags & DN_MemFlags_TempMemUAFTraceDisable)
|
|
result = false;
|
|
return result;
|
|
}
|
|
#endif
|
|
|
|
DN_API void DN_ArenaUAFCheck(DN_Arena *arena)
|
|
{
|
|
(void)arena;
|
|
#if DN_ARENA_TEMP_MEM_UAF_GUARD
|
|
DN_MemList *mem = arena->mem;
|
|
if (!arena || !mem)
|
|
return;
|
|
|
|
if ((arena->uaf_guard_temp_mem || mem->uaf_guard_active_temp_mem) && !arena->uaf_guard_is_being_checked) {
|
|
// NOTE: The following functions below allocate memory which might trigger an additional UAF
|
|
// check which would cause infinite recursion so we set a flag here to prevent that.
|
|
arena->uaf_guard_is_being_checked = true;
|
|
if (mem->uaf_guard_active_id != arena->uaf_guard_id) {
|
|
// NOTE: MSVC does not recognise %'u which is a STB extension which causes a lot of incorrect
|
|
// format arguments warnings that we mute here.
|
|
DN_MSVC_WARNING_PUSH
|
|
DN_MSVC_WARNING_DISABLE(6271) // Extra argument passed to 'DN_Str8FromFmtArena'
|
|
DN_MSVC_WARNING_DISABLE(6067) // _Param_(10) in call to 'DN_LogPrint' must be the address of a string. Actual type: 'int'.
|
|
DN_MSVC_WARNING_DISABLE(6273) // Non-integer passed as _Param_(11) when an integer is required in call to 'DN_LogPrint' Actual type: 'char *'.
|
|
DN_Str8 prefix = DN_Str8LineBreakStr8(DN_Str8FromFmtArena(arena,
|
|
"Arena use-after-free (UAF) detected in temporary memory usage! This allocation (trace "
|
|
"shown above) is attempting to allocate memory inside the active temporary region (id: %'u) "
|
|
"but belongs to a different region (id: %'u). This means when the active temporary region is "
|
|
"released, this allocation will be released and scrubbed causing a potential UAF.\n\nEnsure "
|
|
"that scratch memory is deconflicting correctly, scratch and or temporary memory regions have "
|
|
"matching begin and end pairs and only the arena view with the active temporary memory region "
|
|
"is being allocated from.",
|
|
mem->uaf_guard_active_id,
|
|
arena->uaf_guard_id),
|
|
100,
|
|
arena);
|
|
|
|
if (DN_MemListUAFTracingEnabled_(mem)) {
|
|
DN_Str8 curr_stack_trace = DN_Str8Lit("<Unknown: Arena is not using temporary memory>");
|
|
if (arena->uaf_guard_temp_mem)
|
|
curr_stack_trace = DN_StackTraceWalkResultToStr8(arena, &arena->uaf_guard_temp_mem->trace, 1);
|
|
curr_stack_trace = DN_Str8PadNewLines(curr_stack_trace, DN_Str8Lit(" "), arena);
|
|
|
|
DN_Str8 active_stack_trace = DN_Str8PadNewLines(DN_StackTraceWalkResultToStr8(arena, &mem->uaf_guard_active_temp_mem->trace, 1), DN_Str8Lit(" "), arena);
|
|
DN_AssertF(mem->uaf_guard_active_id == arena->uaf_guard_id,
|
|
"%.*s\n\nThe originating temporary memory region (id: %'u) was created at:"
|
|
"\n\n %.*s\n\nThe active temporary memory region (id: %'u) was created at:\n\n %.*s",
|
|
DN_Str8PrintFmt(prefix),
|
|
arena->uaf_guard_id,
|
|
DN_Str8PrintFmt(curr_stack_trace),
|
|
mem->uaf_guard_active_id,
|
|
DN_Str8PrintFmt(active_stack_trace));
|
|
} else {
|
|
DN_Str8 suffix = DN_Str8LineBreakStr8(DN_MEM_LIST_UAF_TRACING_DISABLED_MORE_INFO_STR8_, 100, arena);
|
|
DN_AssertF(mem->uaf_guard_active_id == arena->uaf_guard_id, "%.*s%.*s", DN_Str8PrintFmt(prefix), DN_Str8PrintFmt(suffix));
|
|
}
|
|
DN_MSVC_WARNING_POP
|
|
}
|
|
arena->uaf_guard_is_being_checked = false;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
DN_API DN_Arena DN_ArenaFromMemList(DN_MemList *mem)
|
|
{
|
|
DN_Arena result = {};
|
|
result.mem = mem;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Arena DN_ArenaTempBeginFromMemList(DN_MemList* mem)
|
|
{
|
|
DN_Arena result = DN_ArenaFromMemList(mem);
|
|
DN_MemListTemp temp_mem = DN_MemListTempBegin(mem);
|
|
|
|
#if DN_ARENA_TEMP_MEM_UAF_GUARD
|
|
if (DN_MemListUAFTracingEnabled_(mem))
|
|
temp_mem.trace = DN_StackTraceWalk(&result, 256);
|
|
|
|
// NOTE: Create persistent temp mem and set it on the mem list
|
|
result.uaf_guard_temp_mem = DN_MemListNewCopy(mem, DN_MemListTemp, &temp_mem);
|
|
result.uaf_guard_prev_temp_mem = mem->uaf_guard_active_temp_mem;
|
|
mem->uaf_guard_active_temp_mem = result.uaf_guard_temp_mem;
|
|
|
|
// NOTE: Update IDs
|
|
result.uaf_guard_id = ++mem->uaf_guard_next_id;
|
|
result.uaf_guard_prev_id = mem->uaf_guard_active_id;
|
|
mem->uaf_guard_active_id = result.uaf_guard_id;
|
|
#else
|
|
result.temp_mem = temp_mem;
|
|
#endif
|
|
return result;
|
|
}
|
|
|
|
|
|
DN_API DN_Arena DN_ArenaTempBeginFromArena(DN_Arena *arena)
|
|
{
|
|
DN_Arena result = DN_ArenaTempBeginFromMemList(arena->mem);
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_ArenaTempEnd(DN_Arena *arena, DN_ArenaReset reset)
|
|
{
|
|
#if DN_ARENA_TEMP_MEM_UAF_GUARD
|
|
DN_AssertF(arena->uaf_guard_temp_mem, "Arena was not created with temp memory");
|
|
#else
|
|
DN_AssertF(arena->temp_mem.mem, "Arena was not created with temp memory");
|
|
#endif
|
|
|
|
#if DN_ARENA_TEMP_MEM_UAF_GUARD
|
|
DN_MemList *mem = arena->mem;
|
|
if (mem->uaf_guard_active_id != arena->uaf_guard_id) {
|
|
// NOTE: MSVC does not recognise %'u which is a STB extension which causes a lot of incorrect
|
|
// format arguments warnings that we mute here.
|
|
DN_MSVC_WARNING_PUSH
|
|
DN_MSVC_WARNING_DISABLE(6271) // Extra argument passed to 'DN_Str8FromFmtArena'
|
|
DN_MSVC_WARNING_DISABLE(6067) // _Param_(10) in call to 'DN_LogPrint' must be the address of a string. Actual type: 'int'.
|
|
DN_MSVC_WARNING_DISABLE(6273) // Non-integer passed as _Param_(11) when an integer is required in call to 'DN_LogPrint' Actual type: 'char *'.
|
|
|
|
// TODO: If this triggers, using the arena to format the error message is going to trigger the UAF check which is already failing.
|
|
DN_Str8 prefix = DN_Str8LineBreakStr8(DN_Str8Lit("The active temporary memory region recorded on the arena is "
|
|
"different from the current temporary memory region recorded on "
|
|
"the memory list allocator. This means that a temporary region "
|
|
"began but was not ended after the region was completed. Temporary "
|
|
"memory regions are enforced in a first-in-last-out manner (FILO) "
|
|
"to ensure the developer's intent of what the temporary region "
|
|
"spans is logically consistent and always strictly ends and begins "
|
|
"within a known lifetime."),
|
|
100,
|
|
arena);
|
|
|
|
if (DN_MemListUAFTracingEnabled_(mem)) {
|
|
DN_Str8 curr_stack_trace = DN_Str8PadNewLines(DN_StackTraceWalkResultToStr8(arena, &arena->uaf_guard_temp_mem->trace, 1), DN_Str8Lit(" "), arena);
|
|
DN_Str8 active_stack_trace = DN_Str8PadNewLines(DN_StackTraceWalkResultToStr8(arena, &mem->uaf_guard_active_temp_mem->trace, 1), DN_Str8Lit(" "), arena);
|
|
DN_AssertF(mem->uaf_guard_active_id == arena->uaf_guard_id,
|
|
"%.*s\n\nThe originating temporary memory region (id: %'u) was created at:"
|
|
"\n\n %.*s\n\nThe active temporary memory region (id: %'u) was created at:\n\n %.*s",
|
|
DN_Str8PrintFmt(prefix),
|
|
arena->uaf_guard_id,
|
|
DN_Str8PrintFmt(curr_stack_trace),
|
|
mem->uaf_guard_active_id,
|
|
DN_Str8PrintFmt(active_stack_trace));
|
|
} else {
|
|
DN_Str8 suffix = DN_Str8LineBreakStr8(DN_MEM_LIST_UAF_TRACING_DISABLED_MORE_INFO_STR8_, 100, arena);
|
|
DN_AssertF(mem->uaf_guard_active_id == arena->uaf_guard_id, "%.*s%.*s", DN_Str8PrintFmt(prefix), DN_Str8PrintFmt(suffix));
|
|
}
|
|
DN_MSVC_WARNING_POP
|
|
DN_Assert(arena->mem->uaf_guard_active_id == arena->uaf_guard_id);
|
|
}
|
|
#endif
|
|
|
|
if (reset == DN_ArenaReset_Yes) {
|
|
#if DN_ARENA_TEMP_MEM_UAF_GUARD
|
|
DN_MemListTempEnd(*arena->uaf_guard_temp_mem);
|
|
#else
|
|
DN_MemListTempEnd(arena->temp_mem);
|
|
#endif
|
|
}
|
|
|
|
#if DN_ARENA_TEMP_MEM_UAF_GUARD
|
|
mem->uaf_guard_active_id = arena->uaf_guard_prev_id;
|
|
mem->uaf_guard_active_temp_mem = arena->uaf_guard_prev_temp_mem;
|
|
|
|
arena->uaf_guard_prev_temp_mem = nullptr;
|
|
arena->uaf_guard_prev_id = 0;
|
|
arena->uaf_guard_temp_mem = nullptr;
|
|
#endif
|
|
}
|
|
|
|
DN_API void *DN_ArenaAlloc(DN_Arena *arena, DN_U64 size, uint8_t align, DN_ZMem z_mem)
|
|
{
|
|
DN_ArenaUAFCheck(arena);
|
|
void *result = DN_MemListAlloc(arena->mem, size, align, z_mem);
|
|
return result;
|
|
}
|
|
|
|
DN_API void *DN_ArenaAllocContiguous(DN_Arena *arena, DN_U64 size, uint8_t align, DN_ZMem z_mem)
|
|
{
|
|
DN_ArenaUAFCheck(arena);
|
|
void *result = DN_MemListAllocContiguous(arena->mem, size, align, z_mem);
|
|
return result;
|
|
}
|
|
|
|
DN_API void *DN_ArenaCopy(DN_Arena *arena, void const *data, DN_U64 size, uint8_t align)
|
|
{
|
|
DN_ArenaUAFCheck(arena);
|
|
void *result = DN_MemListCopy(arena->mem, data, size, align);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Pool DN_PoolFromArena(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_PoolIsValid(DN_Pool const *pool)
|
|
{
|
|
bool result = pool && pool->arena && pool->align;
|
|
return result;
|
|
}
|
|
|
|
DN_API void *DN_PoolAlloc(DN_Pool *pool, DN_USize size)
|
|
{
|
|
void *result = nullptr;
|
|
if (!DN_PoolIsValid(pool))
|
|
return result;
|
|
|
|
DN_USize const required_size = sizeof(DN_PoolSlot) + pool->align + size;
|
|
DN_USize const 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_CountLeadingZerosUSize(required_size) + 1;
|
|
dist_to_next_msb -= DN_Cast(DN_USize)(!DN_IsPowerOfTwo(required_size));
|
|
|
|
DN_USize const register_size = sizeof(DN_USize) * 8;
|
|
DN_AssertF(register_size >= (dist_to_next_msb - size_to_slot_offset), "lhs=%zu, rhs=%zu", 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_AssertF(required_size <= (slot_size_in_bytes << 0), "slot_index=%zu, lhs=%zu, rhs=%zu", slot_index, required_size, (slot_size_in_bytes << 0));
|
|
DN_AssertF(required_size >= (slot_size_in_bytes >> 1), "slot_index=%zu, lhs=%zu, rhs=%zu", slot_index, required_size, (slot_size_in_bytes >> 1));
|
|
|
|
DN_PoolSlot *slot = nullptr;
|
|
if (pool->slots[slot_index]) {
|
|
slot = pool->slots[slot_index];
|
|
pool->slots[slot_index] = slot->next;
|
|
DN_Memset(slot->data, 0, size);
|
|
DN_Assert(DN_IsPowerOfTwoAligned(slot->data, pool->align));
|
|
} else {
|
|
void *bytes = DN_ArenaAlloc(pool->arena, slot_size_in_bytes, alignof(DN_PoolSlot), DN_ZMem_Yes);
|
|
slot = DN_Cast(DN_PoolSlot *) bytes;
|
|
|
|
// NOTE: The raw pointer is round up to the next 'pool->align'-ed
|
|
// address ensuring at least 1 byte of padding between the raw pointer
|
|
// and the pointer given to the user and that the user pointer is
|
|
// aligned to the pool's alignment.
|
|
//
|
|
// This allows us to smuggle 1 byte behind the user pointer that has
|
|
// the offset to the original pointer.
|
|
slot->data = DN_Cast(void *) DN_AlignDownPowerOfTwo(DN_Cast(uintptr_t) slot + sizeof(DN_PoolSlot) + pool->align, pool->align);
|
|
|
|
uintptr_t offset_to_original_ptr = DN_Cast(uintptr_t) slot->data - DN_Cast(uintptr_t) bytes;
|
|
DN_Assert(slot->data > bytes);
|
|
DN_Assert(offset_to_original_ptr <= sizeof(DN_PoolSlot) + pool->align);
|
|
|
|
// NOTE: Store the offset to the original pointer behind the user's
|
|
// pointer.
|
|
char *offset_to_original_storage = DN_Cast(char *) slot->data - 1;
|
|
DN_Memcpy(offset_to_original_storage, &offset_to_original_ptr, 1);
|
|
}
|
|
|
|
// NOTE: Smuggle the slot type in the next pointer so that we know, when the
|
|
// pointer gets returned which free list to return the pointer to.
|
|
result = slot->data;
|
|
slot->next = DN_Cast(DN_PoolSlot *) slot_index;
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_PoolDealloc(DN_Pool *pool, void *ptr)
|
|
{
|
|
if (!DN_PoolIsValid(pool) || !ptr)
|
|
return;
|
|
|
|
DN_Assert(DN_MemListOwnsPtr(pool->arena->mem, ptr));
|
|
|
|
char const *one_byte_behind_ptr = DN_Cast(char *) ptr - 1;
|
|
DN_USize offset_to_original_ptr = 0;
|
|
DN_Memcpy(&offset_to_original_ptr, one_byte_behind_ptr, 1);
|
|
DN_Assert(offset_to_original_ptr <= sizeof(DN_PoolSlot) + pool->align);
|
|
|
|
char *original_ptr = DN_Cast(char *) ptr - offset_to_original_ptr;
|
|
DN_PoolSlot *slot = DN_Cast(DN_PoolSlot *) original_ptr;
|
|
DN_PoolSlotSize slot_index = DN_Cast(DN_PoolSlotSize)(DN_Cast(uintptr_t) slot->next);
|
|
DN_Assert(slot_index < DN_PoolSlotSize_Count);
|
|
|
|
// NOTE: Scrub memory before returning to the pool
|
|
if (DN_SCRUB_UNINIT_MEM_BYTE) {
|
|
DN_USize slot_size_in_bytes = 1ULL << (slot_index + 5);
|
|
DN_USize data_offset = (char *)slot->data - (char *)slot;
|
|
DN_Memset(slot->data, DN_SCRUB_UNINIT_MEM_BYTE, slot_size_in_bytes - data_offset);
|
|
}
|
|
|
|
slot->next = pool->slots[slot_index];
|
|
pool->slots[slot_index] = slot;
|
|
}
|
|
|
|
static void DN_ErrSinkCheck_(DN_ErrSink const *err)
|
|
{
|
|
DN_Assert(err->arena->mem);
|
|
if (err->stack_size == 0)
|
|
return;
|
|
|
|
DN_ErrSinkNode const *node = err->stack + (err->stack_size - 1);
|
|
DN_Assert(node->mode >= DN_ErrSinkMode_Nil && node->mode <= DN_ErrSinkMode_ExitOnError);
|
|
DN_Assert(node->msg_sentinel);
|
|
|
|
// NOTE: Walk the list ensuring we eventually terminate at the sentinel (e.g. we have a
|
|
// well formed doubly-linked-list terminated by a sentinel, or otherwise we will hit the
|
|
// walk limit or dereference a null pointer and assert)
|
|
size_t WALK_LIMIT = 99'999;
|
|
size_t walk = 0;
|
|
for (DN_ErrSinkMsg *it = node->msg_sentinel->next; it != node->msg_sentinel; it = it->next, walk++) {
|
|
DN_AssertF(it, "Encountered null pointer which should not happen in a sentinel DLL");
|
|
DN_Assert(walk < WALK_LIMIT);
|
|
}
|
|
}
|
|
|
|
DN_API DN_ErrSink* DN_ErrSinkBegin_(DN_ErrSink *err, DN_ErrSinkMode mode, DN_CallSite call_site)
|
|
{
|
|
// NOTE: OOM error
|
|
if (err->stack_size == DN_ArrayCountU(err->stack)) {
|
|
DN_Str8Builder builder = DN_Str8BuilderFromArena(err->arena);
|
|
for (DN_ForItSize(it, DN_ErrSinkNode, err->stack, err->stack_size))
|
|
DN_Str8BuilderAppendF(&builder, " [%04zu] %.*s:%u %.*s\n", it.index, DN_Str8PrintFmt(it.data->call_site.file), it.data->call_site.line, DN_Str8PrintFmt(it.data->call_site.function));
|
|
DN_Str8 msg = DN_Str8BuilderBuild(&builder, err->arena);
|
|
DN_AssertF(err->stack_size < DN_ArrayCountU(err->stack), "Error sink has run out of error scopes, potential leak. Scopes were\n%.*s", DN_Str8PrintFmt(msg));
|
|
}
|
|
|
|
// NOTE: Allocate the node
|
|
DN_ErrSinkNode *node = err->stack + err->stack_size++;
|
|
node->arena_pos = DN_MemListPos(err->arena->mem);
|
|
node->mode = mode;
|
|
node->call_site = call_site;
|
|
DN_SentinelDoublyLLInitArena(node->msg_sentinel, DN_ErrSinkMsg, err->arena);
|
|
|
|
// NOTE: Handle allocation error
|
|
if (!DN_Check(node && node->msg_sentinel)) {
|
|
DN_MemListPopTo(err->arena->mem, node->arena_pos);
|
|
node->msg_sentinel = nullptr;
|
|
err->stack_size--;
|
|
}
|
|
|
|
DN_ErrSink *result = err;
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_ErrSinkHasError(DN_ErrSink *err)
|
|
{
|
|
bool result = false;
|
|
if (err && err->stack_size) {
|
|
DN_ErrSinkNode *node = err->stack + (err->stack_size - 1);
|
|
result = DN_SentinelDoublyLLHasItems(node->msg_sentinel);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_ErrSinkMsg *DN_ErrSinkEnd(DN_Arena *arena, DN_ErrSink *err)
|
|
{
|
|
DN_ErrSinkMsg *result = nullptr;
|
|
DN_ErrSinkCheck_(err);
|
|
DN_AssertF(arena != err->arena, "You are not allowed to reuse the arena for ending the error sink because the memory would get popped and lost");
|
|
|
|
// NOTE: Walk the list and allocate it onto the user's arena
|
|
DN_ErrSinkNode *node = err->stack + (err->stack_size - 1);
|
|
DN_ErrSinkMsg *prev = nullptr;
|
|
for (DN_ErrSinkMsg *it = node->msg_sentinel->next; it != node->msg_sentinel; it = it->next) {
|
|
DN_ErrSinkMsg *entry = DN_ArenaNew(arena, DN_ErrSinkMsg, DN_ZMem_Yes);
|
|
entry->msg = DN_Str8FromStr8Arena(it->msg, arena);
|
|
entry->call_site = it->call_site;
|
|
entry->error_code = it->error_code;
|
|
if (!result)
|
|
result = entry; // Assign first entry if we haven't yet
|
|
if (prev)
|
|
prev->next = entry; // Link the prev message to the current one
|
|
prev = entry; // Update prev to latest
|
|
}
|
|
|
|
// NOTE: Deallocate all the memory for this scope
|
|
err->stack_size--;
|
|
DN_MemListPopTo(err->arena->mem, node->arena_pos);
|
|
return result;
|
|
}
|
|
|
|
static void DN_ErrSinkAddMsgToStr8Builder_(DN_Str8Builder *builder, DN_ErrSinkMsg *msg, DN_ErrSinkMsg *end)
|
|
{
|
|
if (msg == end) // NOTE: No error messages to add
|
|
return;
|
|
|
|
if (msg->next == end) {
|
|
DN_ErrSinkMsg *it = msg;
|
|
DN_Str8 file_name = DN_Str8FileNameFromPath(it->call_site.file);
|
|
DN_Str8BuilderAppendF(builder,
|
|
"%.*s:%05I32u:%.*s %.*s",
|
|
DN_Str8PrintFmt(file_name),
|
|
it->call_site.line,
|
|
DN_Str8PrintFmt(it->call_site.function),
|
|
DN_Str8PrintFmt(it->msg));
|
|
} else {
|
|
// NOTE: More than one message
|
|
for (DN_ErrSinkMsg *it = msg; it != end; it = it->next) {
|
|
DN_Str8 file_name = DN_Str8FileNameFromPath(it->call_site.file);
|
|
DN_Str8BuilderAppendF(builder,
|
|
"%s - %.*s:%05I32u:%.*s%s%.*s",
|
|
it == msg ? "" : "\n",
|
|
DN_Str8PrintFmt(file_name),
|
|
it->call_site.line,
|
|
DN_Str8PrintFmt(it->call_site.function),
|
|
it->msg.size ? " " : "",
|
|
DN_Str8PrintFmt(it->msg));
|
|
}
|
|
}
|
|
}
|
|
|
|
DN_API DN_Str8 DN_ErrSinkEndStr8(DN_Arena *arena, DN_ErrSink *err)
|
|
{
|
|
DN_Str8 result = {};
|
|
DN_ErrSinkCheck_(err);
|
|
if (err->stack_size == 0)
|
|
return result;
|
|
|
|
DN_AssertF(arena != err->arena, "You are not allowed to reuse the arena for ending the error sink because the memory would get popped and lost");
|
|
|
|
// NOTE: Walk the list and allocate it onto the user's arena
|
|
DN_Str8Builder builder = DN_Str8BuilderFromArena(err->arena);
|
|
DN_ErrSinkNode *node = err->stack + (err->stack_size - 1);
|
|
DN_ErrSinkAddMsgToStr8Builder_(&builder, node->msg_sentinel->next, node->msg_sentinel);
|
|
|
|
// NOTE: Deallocate all the memory for this scope
|
|
err->stack_size--;
|
|
DN_MemListPopTo(err->arena->mem, node->arena_pos);
|
|
|
|
result = DN_Str8BuilderBuild(&builder, arena);
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_ErrSinkEndIgnore(DN_ErrSink *err)
|
|
{
|
|
DN_ErrSinkEnd(nullptr, err);
|
|
}
|
|
|
|
DN_API bool DN_ErrSinkEndLogError_(DN_ErrSink *err, DN_CallSite call_site, DN_Str8 err_msg)
|
|
{
|
|
DN_ErrSinkNode *node = err->stack + (err->stack_size - 1);
|
|
DN_AssertF(err->stack_size, "Begin must be called before calling end");
|
|
DN_AssertF(node->msg_sentinel, "Begin must be called before calling end");
|
|
err->stack_size--;
|
|
|
|
bool result = false;
|
|
if (node->msg_sentinel != node->msg_sentinel->next) {
|
|
result = true;
|
|
// NOTE: Build the error string
|
|
DN_Str8Builder builder = DN_Str8BuilderFromArena(err->arena);
|
|
{
|
|
if (err_msg.size) {
|
|
DN_Str8BuilderAppendRef(&builder, err_msg);
|
|
DN_Str8BuilderAppendRef(&builder, DN_Str8Lit(":"));
|
|
} else {
|
|
DN_Str8BuilderAppendRef(&builder, DN_Str8Lit("Error(s) encountered:"));
|
|
}
|
|
if (node->msg_sentinel->next->next != node->msg_sentinel) // NOTE: More than 1 message
|
|
DN_Str8BuilderAppendRef(&builder, DN_Str8Lit("\n"));
|
|
DN_ErrSinkAddMsgToStr8Builder_(&builder, node->msg_sentinel->next, node->msg_sentinel);
|
|
}
|
|
|
|
// NOTE: Log the error
|
|
DN_Str8 log = DN_Str8BuilderBuild(&builder, err->arena);
|
|
DN_LogPrint(DN_LogTypeParamFromType(DN_LogType_Error), call_site, DN_LogFlags_Nil, "%.*s", DN_Str8PrintFmt(log));
|
|
|
|
if (node->mode == DN_ErrSinkMode_DebugBreakOnErrorLog)
|
|
DN_DebugBreak;
|
|
|
|
// NOTE: Deallocate the error node's memory and pop it from the stack
|
|
DN_MemListPopTo(err->arena->mem, node->arena_pos);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_ErrSinkEndLogErrorFV_(DN_ErrSink *err, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, va_list args)
|
|
{
|
|
DN_Str8 log = DN_Str8FromFmtVArena(err->arena, fmt, args);
|
|
bool result = DN_ErrSinkEndLogError_(err, call_site, log);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_ErrSinkEndLogErrorF_(DN_ErrSink *err, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
DN_Str8 log = DN_Str8FromFmtVArena(err->arena, fmt, args);
|
|
bool result = DN_ErrSinkEndLogError_(err, call_site, log);
|
|
va_end(args);
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_ErrSinkEndExitIfErrorFV_(DN_ErrSink *err, DN_CallSite call_site, DN_U32 exit_val, DN_FMT_ATTRIB char const *fmt, va_list args)
|
|
{
|
|
if (DN_ErrSinkEndLogErrorFV_(err, call_site, fmt, args)) {
|
|
DN_DebugBreak;
|
|
DN_OS_Exit(exit_val);
|
|
}
|
|
}
|
|
|
|
DN_API void DN_ErrSinkEndExitIfErrorF_(DN_ErrSink *err, DN_CallSite call_site, DN_U32 exit_val, DN_FMT_ATTRIB char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
DN_ErrSinkEndExitIfErrorFV_(err, call_site, exit_val, fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
DN_API void DN_ErrSinkAppendFV_(DN_ErrSink *err, DN_U32 error_code, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, va_list args)
|
|
{
|
|
if (!err)
|
|
return;
|
|
|
|
DN_Assert(err->stack_size);
|
|
DN_ErrSinkNode *node = err->stack + (err->stack_size - 1);
|
|
DN_AssertF(node, "Error sink must be begun by calling 'Begin' before using this function.");
|
|
|
|
DN_ErrSinkMsg *msg = DN_ArenaNew(err->arena, DN_ErrSinkMsg, DN_ZMem_Yes);
|
|
if (DN_Check(msg)) {
|
|
msg->msg = DN_Str8FromFmtVArena(err->arena, fmt, args);
|
|
msg->error_code = error_code;
|
|
msg->call_site = call_site;
|
|
DN_SentinelDoublyLLPrepend(node->msg_sentinel, msg);
|
|
if (node->mode == DN_ErrSinkMode_ExitOnError)
|
|
DN_ErrSinkEndExitIfErrorF_(err, msg->call_site, error_code, "Fatal error %u", error_code);
|
|
}
|
|
}
|
|
|
|
DN_API void DN_ErrSinkAppendF_(DN_ErrSink *err, DN_U32 error_code, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
DN_ErrSinkAppendFV_(err, error_code, call_site, fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
DN_THREAD_LOCAL DN_TCCore *g_dn_thread_context;
|
|
|
|
DN_API void DN_TCInit(DN_TCCore *tc, DN_U64 thread_id, DN_Arena *main_arena, DN_Arena *temp_a_arena, DN_Arena *temp_b_arena, DN_Arena *err_sink_arena)
|
|
{
|
|
tc->thread_id = thread_id;
|
|
tc->main_arena = main_arena;
|
|
tc->main_pool = DN_PoolFromArena(tc->main_arena, 0);
|
|
tc->temp_a_arena = temp_a_arena;
|
|
tc->temp_b_arena = temp_b_arena;
|
|
tc->err_sink.arena = err_sink_arena;
|
|
}
|
|
|
|
DN_API void DN_TCInitFromMemFuncs(DN_TCCore *tc, DN_U64 thread_id, DN_TCInitArgs *args, DN_MemFuncs mem_funcs)
|
|
{
|
|
DN_U64 main_reserve = (args && args->main_reserve) ? args->main_reserve : DN_Kilobytes(64);
|
|
DN_U64 main_commit = (args && args->main_commit) ? args->main_commit : DN_Kilobytes(4);
|
|
DN_U64 temp_reserve = (args && args->temp_reserve) ? args->temp_reserve : DN_Kilobytes(64);
|
|
DN_U64 temp_commit = (args && args->temp_commit) ? args->temp_commit : DN_Kilobytes(4);
|
|
DN_U64 err_sink_reserve = (args && args->err_sink_reserve) ? args->err_sink_reserve : DN_Kilobytes(64);
|
|
DN_U64 err_sink_commit = (args && args->err_sink_commit) ? args->err_sink_commit : DN_Kilobytes(4);
|
|
|
|
tc->main_arena_mem_ = DN_MemListFromMemFuncs(main_reserve, main_commit, DN_MemFlags_AllocCanLeak | DN_MemFlags_NoAllocTrack, mem_funcs);
|
|
tc->temp_a_arena_mem_ = DN_MemListFromMemFuncs(temp_reserve, temp_commit, DN_MemFlags_AllocCanLeak | DN_MemFlags_NoAllocTrack, mem_funcs);
|
|
tc->temp_b_arena_mem_ = DN_MemListFromMemFuncs(temp_reserve, temp_commit, DN_MemFlags_AllocCanLeak | DN_MemFlags_NoAllocTrack, mem_funcs);
|
|
tc->err_sink_arena_mem_ = DN_MemListFromMemFuncs(err_sink_reserve, err_sink_commit, DN_MemFlags_AllocCanLeak | DN_MemFlags_NoAllocTrack, mem_funcs);
|
|
tc->main_arena_ = DN_ArenaFromMemList(&tc->main_arena_mem_);
|
|
tc->temp_a_arena_ = DN_ArenaFromMemList(&tc->temp_a_arena_mem_);
|
|
tc->temp_b_arena_ = DN_ArenaFromMemList(&tc->temp_b_arena_mem_);
|
|
tc->err_sink_arena_ = DN_ArenaFromMemList(&tc->err_sink_arena_mem_);
|
|
|
|
DN_TCInit(tc, thread_id, &tc->main_arena_, &tc->temp_a_arena_, &tc->temp_b_arena_, &tc->err_sink_arena_);
|
|
}
|
|
|
|
DN_API void DN_TCDeinit(DN_TCCore *tc, DN_TCDeinitArenas deinit_arenas)
|
|
{
|
|
if (deinit_arenas == DN_TCDeinitArenas_Yes) {
|
|
DN_MemListDeinit(tc->main_arena->mem);
|
|
DN_MemListDeinit(tc->temp_a_arena->mem);
|
|
DN_MemListDeinit(tc->temp_b_arena->mem);
|
|
DN_MemListDeinit(tc->err_sink.arena->mem);
|
|
}
|
|
}
|
|
|
|
DN_API void DN_TCEquip(DN_TCCore *tc)
|
|
{
|
|
g_dn_thread_context = tc;
|
|
}
|
|
|
|
DN_API DN_TCCore *DN_TCGet()
|
|
{
|
|
DN_RawAssert(g_dn_thread_context &&
|
|
"This thread's thread context has not been equipped yet. Ensure that DN_TCInit(...) "
|
|
"has been called to create a thread context and call DN_TCEquip(...) in the current "
|
|
"thread to make it retrievable via this function");
|
|
return g_dn_thread_context;
|
|
}
|
|
|
|
DN_API DN_Arena *DN_TCMainArena()
|
|
{
|
|
DN_TCCore *tc = DN_TCGet();
|
|
DN_Arena *result = tc->main_arena;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Pool *DN_TCMainPool()
|
|
{
|
|
DN_TCCore *tc = DN_TCGet();
|
|
DN_Pool *result = &tc->main_pool;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Arena DN_TCTempArena(DN_Arena **conflicts, DN_USize count)
|
|
{
|
|
DN_TCCore *tc = DN_TCGet();
|
|
DN_MemList *candidates[] = {tc->temp_a_arena->mem, tc->temp_b_arena->mem};
|
|
DN_Arena result = {};
|
|
for (DN_ForItCArray(it, DN_MemList *, candidates)) {
|
|
bool is_usable = true;
|
|
DN_MemList *rhs_mem = *it.data;
|
|
for (DN_ForItSize(conflict_it, DN_Arena *, conflicts, count)) {
|
|
DN_Arena *lhs_arena = *conflict_it.data;
|
|
DN_MemList *lhs_mem = lhs_arena->mem;
|
|
if (lhs_mem == rhs_mem) {
|
|
is_usable = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (is_usable) {
|
|
result = DN_ArenaTempBeginFromMemList(rhs_mem);
|
|
break;
|
|
}
|
|
}
|
|
|
|
DN_AssertF(result.mem, "All temp arenas are being used, there are none left to return to the caller");
|
|
return result;
|
|
}
|
|
|
|
#if defined(__cplusplus)
|
|
DN_TCScratchCpp::DN_TCScratchCpp(DN_Arena **conflicts, DN_USize count)
|
|
{
|
|
this->data = DN_TCScratchBegin(conflicts, count);
|
|
}
|
|
|
|
DN_TCScratchCpp::~DN_TCScratchCpp()
|
|
{
|
|
DN_TCScratchEnd(&this->data);
|
|
}
|
|
#endif
|
|
|
|
DN_API DN_TCScratch DN_TCScratchBegin(DN_Arena **conflicts, DN_USize count)
|
|
{
|
|
DN_TCScratch result = {};
|
|
result.arena = DN_TCTempArena(conflicts, count);
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_TCScratchEnd(DN_TCScratch *scratch)
|
|
{
|
|
DN_Assert(scratch->destructed == false);
|
|
DN_ArenaTempEnd(&scratch->arena, DN_ArenaReset_Yes);
|
|
*scratch = {};
|
|
scratch->destructed = true;
|
|
}
|
|
|
|
DN_API void DN_TCSetFrameArena(DN_Arena *arena)
|
|
{
|
|
DN_TCCore *tc = DN_TCGet();
|
|
tc->frame_arena = arena;
|
|
}
|
|
|
|
DN_API DN_Arena *DN_TCFrameArena()
|
|
{
|
|
DN_TCCore *tc = DN_TCGet();
|
|
DN_Arena *result = tc->frame_arena;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_ErrSink *DN_TCErrSink()
|
|
{
|
|
DN_TCCore *tc = DN_TCGet();
|
|
DN_ErrSink *result = &tc->err_sink;
|
|
return result;
|
|
}
|
|
|
|
DN_API void *DN_PoolCopy(DN_Pool *pool, void const *data, DN_U64 size, 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_PoolAlloc(pool, size);
|
|
if (result)
|
|
DN_Memcpy(result, data, size);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_CharIsAlphabet(char ch)
|
|
{
|
|
bool result = (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z');
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_CharIsDigit(char ch)
|
|
{
|
|
bool result = (ch >= '0' && ch <= '9');
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_CharIsAlphaNum(char ch)
|
|
{
|
|
bool result = DN_CharIsAlphabet(ch) || DN_CharIsDigit(ch);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_CharIsWhitespace(char ch)
|
|
{
|
|
bool result = (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r');
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_CharIsHex(char ch)
|
|
{
|
|
bool result = ((ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') || (ch >= '0' && ch <= '9'));
|
|
return result;
|
|
}
|
|
|
|
DN_API char DN_CharToLower(char ch)
|
|
{
|
|
char result = ch;
|
|
if (result >= 'A' && result <= 'Z')
|
|
result += 'a' - 'A';
|
|
return result;
|
|
}
|
|
|
|
DN_API char DN_CharToUpper(char ch)
|
|
{
|
|
char result = ch;
|
|
if (result >= 'a' && result <= 'z')
|
|
result -= 'a' - 'A';
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_U64FromResult DN_U64FromStr8(DN_Str8 string, char separator)
|
|
{
|
|
// NOTE: Argument check
|
|
DN_U64FromResult result = {};
|
|
if (string.size == 0) {
|
|
result.success = true;
|
|
return result;
|
|
}
|
|
|
|
// NOTE: Sanitize input/output
|
|
DN_Str8 trim_string = DN_Str8TrimWhitespaceAround(string);
|
|
if (trim_string.size == 0) {
|
|
result.success = true;
|
|
return result;
|
|
}
|
|
|
|
// NOTE: Handle prefix '+'
|
|
DN_USize start_index = 0;
|
|
if (!DN_CharIsDigit(trim_string.data[0])) {
|
|
if (trim_string.data[0] != '+')
|
|
return result;
|
|
start_index++;
|
|
}
|
|
|
|
// NOTE: Convert the string number to the binary number
|
|
for (DN_USize index = start_index; index < trim_string.size; index++) {
|
|
char ch = trim_string.data[index];
|
|
if (index) {
|
|
if (separator != 0 && ch == separator)
|
|
continue;
|
|
}
|
|
|
|
if (!DN_CharIsDigit(ch))
|
|
return result;
|
|
|
|
result.value = DN_SafeMulU64(result.value, 10);
|
|
uint64_t digit = ch - '0';
|
|
result.value = DN_SafeAddU64(result.value, digit);
|
|
}
|
|
|
|
result.success = true;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_U64FromResult DN_U64FromPtr(void const *data, DN_USize size, char separator)
|
|
{
|
|
DN_Str8 str8 = DN_Str8FromPtr((char *)data, size);
|
|
DN_U64FromResult result = DN_U64FromStr8(str8, separator);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_U64 DN_U64FromPtrUnsafe(void const *data, DN_USize size, char separator)
|
|
{
|
|
DN_U64FromResult from = DN_U64FromPtr(data, size, separator);
|
|
DN_U64 result = from.value;
|
|
DN_Assert(from.success);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_U64FromResult DN_U64FromHexPtr(void const *hex, DN_USize hex_count)
|
|
{
|
|
char *hex_ptr = DN_Cast(char *) hex;
|
|
if (hex_count >= 2 && hex_ptr[0] == '0' && (hex_ptr[1] == 'x' || hex_ptr[1] == 'X')) {
|
|
hex_ptr += 2;
|
|
hex_count -= 2;
|
|
}
|
|
|
|
DN_U64FromResult result = {};
|
|
DN_USize max_hex_count = sizeof(DN_U64) * 2;
|
|
DN_USize count = DN_Min(max_hex_count, hex_count);
|
|
DN_Assert(hex_count <= max_hex_count);
|
|
for (DN_USize index = 0; index < count; index++) {
|
|
char ch = hex_ptr[index];
|
|
DN_U8 val = DN_U8FromHexNibble(ch);
|
|
if (val == 0xFF)
|
|
return result;
|
|
result.value = (result.value << 4) | val;
|
|
}
|
|
result.success = true;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_U64 DN_U64FromHexPtrUnsafe(void const *hex, DN_USize hex_count)
|
|
{
|
|
DN_U64FromResult from = DN_U64FromHexPtr(hex, hex_count);
|
|
DN_U64 result = from.value;
|
|
DN_Assert(from.success);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_U64FromResult DN_U64FromHexStr8(DN_Str8 hex)
|
|
{
|
|
DN_U64FromResult result = DN_U64FromHexPtr(hex.data, hex.size);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_U64 DN_U64FromHexStr8Unsafe(DN_Str8 hex)
|
|
{
|
|
DN_U64 result = DN_U64FromHexPtrUnsafe(hex.data, hex.size);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_U64 DN_U64FromU8x32HiBEUnsafe(DN_U8x32 const *val)
|
|
{
|
|
DN_U64 result_be = 0; // Last 8 bytes of 32-byte slot (big-endian)
|
|
DN_Memcpy(&result_be, val->data + sizeof(val->data) - sizeof(result_be), sizeof(result_be));
|
|
DN_U64 result = DN_ByteSwap64(result_be);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_U64FromResult DN_U64FromU8x32HiBE(DN_U8x32 const *val)
|
|
{
|
|
DN_U64FromResult result = {};
|
|
if (val) {
|
|
// NOTE: Check that the high bits are not set
|
|
DN_U8x32 zero_mask = {};
|
|
bool high_bits_set = DN_Memcmp(val->data, zero_mask.data, sizeof(zero_mask.data) - sizeof(result)) != 0;
|
|
result.success = !high_bits_set;
|
|
result.value = DN_U64FromU8x32HiBEUnsafe(val);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_USize DN_USizeFromU8x32HiBEUnsafe(DN_U8x32 const *val)
|
|
{
|
|
DN_USize result_be = 0;
|
|
DN_Memcpy(&result_be, val->data + sizeof(val->data) - sizeof(result_be), sizeof(result_be));
|
|
DN_USize result = DN_ByteSwapUSize(result_be);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_USizeFromResult DN_USizeFromU8x32HiBE(DN_U8x32 const *val)
|
|
{
|
|
DN_USizeFromResult result = {};
|
|
if (val) {
|
|
// NOTE: Check that the high bits are not set
|
|
DN_U8x32 mask = {};
|
|
DN_Memset(mask.data, 1, sizeof(mask.data) - sizeof(result));
|
|
bool high_bits_set = DN_Memcmp(val->data, mask.data, 24) != 0;
|
|
result.success = !high_bits_set;
|
|
result.value = DN_USizeFromU8x32HiBEUnsafe(val);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_ByteSwapU64Ptr(DN_U8 *dest, DN_U64 src)
|
|
{
|
|
dest[0] = DN_Cast(DN_U8)((src >> 56) & 0xFF);
|
|
dest[1] = DN_Cast(DN_U8)((src >> 48) & 0xFF);
|
|
dest[2] = DN_Cast(DN_U8)((src >> 40) & 0xFF);
|
|
dest[3] = DN_Cast(DN_U8)((src >> 32) & 0xFF);
|
|
dest[4] = DN_Cast(DN_U8)((src >> 24) & 0xFF);
|
|
dest[5] = DN_Cast(DN_U8)((src >> 16) & 0xFF);
|
|
dest[6] = DN_Cast(DN_U8)((src >> 8) & 0xFF);
|
|
dest[7] = DN_Cast(DN_U8)(src & 0xFF);
|
|
}
|
|
|
|
DN_API DN_I64FromResult DN_I64FromStr8(DN_Str8 string, char separator)
|
|
{
|
|
// NOTE: Argument check
|
|
DN_I64FromResult result = {};
|
|
if (string.size == 0) {
|
|
result.success = true;
|
|
return result;
|
|
}
|
|
|
|
// NOTE: Sanitize input/output
|
|
DN_Str8 trim_string = DN_Str8TrimWhitespaceAround(string);
|
|
if (trim_string.size == 0) {
|
|
result.success = true;
|
|
return result;
|
|
}
|
|
|
|
bool negative = false;
|
|
DN_USize start_index = 0;
|
|
if (!DN_CharIsDigit(trim_string.data[0])) {
|
|
negative = (trim_string.data[start_index] == '-');
|
|
if (!negative && trim_string.data[0] != '+')
|
|
return result;
|
|
start_index++;
|
|
}
|
|
|
|
// NOTE: Convert the string number to the binary number
|
|
for (DN_USize index = start_index; index < trim_string.size; index++) {
|
|
char ch = trim_string.data[index];
|
|
if (index) {
|
|
if (separator != 0 && ch == separator)
|
|
continue;
|
|
}
|
|
|
|
if (!DN_CharIsDigit(ch))
|
|
return result;
|
|
|
|
result.value = DN_SafeMulU64(result.value, 10);
|
|
uint64_t digit = ch - '0';
|
|
result.value = DN_SafeAddU64(result.value, digit);
|
|
}
|
|
|
|
if (negative)
|
|
result.value *= -1;
|
|
|
|
result.success = true;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_I64FromResult DN_I64FromPtr(void const *data, DN_USize size, char separator)
|
|
{
|
|
DN_Str8 str8 = DN_Str8FromPtr((char *)data, size);
|
|
DN_I64FromResult result = DN_I64FromStr8(str8, separator);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_I64 DN_I64FromPtrUnsafe(void const *data, DN_USize size, char separator)
|
|
{
|
|
DN_I64FromResult from = DN_I64FromPtr(data, size, separator);
|
|
DN_I64 result = from.value;
|
|
DN_Assert(from.success);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_U8x32Eq(DN_U8x32 const *lhs, DN_U8x32 const *rhs)
|
|
{
|
|
bool result = DN_MemEqUnsafe(lhs->data, rhs->data, sizeof(lhs->data));
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_U8x32 DN_U8x32FromBytesLeftPadZ(DN_U8 const *ptr, DN_USize count)
|
|
{
|
|
DN_U8x32 result = {};
|
|
DN_Assert(count <= sizeof(result.data));
|
|
DN_Memcpy(result.data + sizeof(result.data) - count, ptr, count);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_U8x32 DN_U8x32FromHexUnsafe(DN_Str8 hex_32b)
|
|
{
|
|
DN_U8x32 result = {};
|
|
hex_32b = DN_Str8TrimHexPrefix(hex_32b);
|
|
DN_Assert(hex_32b.size <= sizeof(result.data) * 2);
|
|
DN_BytesFromHexPtr(hex_32b.data, hex_32b.size, result.data, sizeof(result.data));
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_U8x32FromResult DN_U8x32FromHex(DN_Str8 hex_32b)
|
|
{
|
|
DN_U8x32FromResult result = {};
|
|
DN_USize bytes_written = DN_BytesFromHexPtr(hex_32b.data, hex_32b.size, result.value.data, sizeof(result.value.data));
|
|
if (bytes_written == sizeof(result.value.data))
|
|
result.success = true;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_U8x32FromResult DN_U8x32FromDecimalStr8(DN_Str8 decimal)
|
|
{
|
|
DN_U8x32FromResult result = {};
|
|
result.success = true;
|
|
for (DN_USize i = 0; i < decimal.size; i++) {
|
|
DN_U8 digit = decimal.data[i];
|
|
if (!DN_CharIsDigit(digit)) {
|
|
result.success = false;
|
|
break;
|
|
}
|
|
|
|
DN_U8 digit_val = digit - '0';
|
|
|
|
// NOTE: Goal is to do => (result = result * 10 + digit_val)
|
|
// Multiply current result by 10
|
|
DN_U16 carry = 0;
|
|
for (int j = 31; j >= 0; j--) {
|
|
DN_U16 prod = DN_Cast(DN_U16)result.value.data[j] * 10 + carry;
|
|
result.value.data[j] = DN_Cast(DN_U8)(prod & 0xFF);
|
|
carry = prod >> 8;
|
|
}
|
|
|
|
// Add the digit
|
|
carry = digit_val;
|
|
for (int j = 31; j >= 0 && carry > 0; j--) {
|
|
DN_U16 sum = DN_Cast(DN_U16)result.value.data[j] + carry;
|
|
result.value.data[j] = DN_Cast(DN_U8)(sum & 0xFF);
|
|
carry = sum >> 8;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_FmtAppendResult DN_FmtVAppend(char *buf, DN_USize *buf_size, DN_USize buf_max, char const *fmt, va_list args)
|
|
{
|
|
DN_FmtAppendResult result = {};
|
|
DN_USize starting_size = *buf_size;
|
|
result.size_req = DN_VSNPrintF(buf + *buf_size, DN_Cast(int)(buf_max - *buf_size), fmt, args);
|
|
*buf_size += result.size_req;
|
|
if (*buf_size >= (buf_max - 1))
|
|
*buf_size = buf_max - 1;
|
|
DN_Assert(*buf_size <= (buf_max - 1));
|
|
result.str8 = DN_Str8FromPtr(buf, *buf_size);
|
|
result.truncated = result.str8.size != (starting_size + result.size_req);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_FmtAppendResult DN_FmtAppend(char *buf, DN_USize *buf_size, DN_USize buf_max, char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
DN_FmtAppendResult result = DN_FmtVAppend(buf, buf_size, buf_max - (*buf_size), fmt, args);
|
|
va_end(args);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_FmtAppendResult DN_FmtAppendTruncate(char *buf, DN_USize *buf_size, DN_USize buf_max, DN_Str8 truncator, char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
DN_FmtAppendResult result = DN_FmtVAppend(buf, buf_size, buf_max, fmt, args);
|
|
if (result.truncated)
|
|
DN_Memcpy(result.str8.data + result.str8.size - truncator.size, truncator.data, truncator.size);
|
|
va_end(args);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_USize DN_FmtSize(DN_FMT_ATTRIB char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
DN_USize result = DN_VSNPrintF(nullptr, 0, fmt, args);
|
|
va_end(args);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_USize DN_FmtVSize(DN_FMT_ATTRIB char const *fmt, va_list args)
|
|
{
|
|
va_list args_copy;
|
|
va_copy(args_copy, args);
|
|
DN_USize result = DN_VSNPrintF(nullptr, 0, fmt, args_copy);
|
|
va_end(args_copy);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_USize DN_CStr8Size(char const *src)
|
|
{
|
|
DN_USize result = 0;
|
|
for (; src && src[0] != 0; src++, result++)
|
|
;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_USize DN_CStr16Size(wchar_t const *src)
|
|
{
|
|
DN_USize result = 0;
|
|
for (; src && src[0] != 0; src++, result++)
|
|
;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8AllocArena(DN_USize size, DN_ZMem z_mem, DN_Arena *arena)
|
|
{
|
|
DN_Str8 result = {};
|
|
result.data = DN_ArenaNewArray(arena, char, size + 1, z_mem);
|
|
if (result.data)
|
|
result.size = size;
|
|
result.data[result.size] = 0;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8AllocPool(DN_USize size, DN_Pool *pool)
|
|
{
|
|
DN_Str8 result = {};
|
|
result.data = DN_PoolNewArray(pool, char, size + 1);
|
|
if (result.data)
|
|
result.size = size;
|
|
result.data[result.size] = 0;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8FromCStr8(char const *src)
|
|
{
|
|
DN_USize size = DN_CStr8Size(src);
|
|
DN_Str8 result = DN_Str8FromPtr(src, size);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8FromCStr8Arena(char const *src, DN_Arena *arena)
|
|
{
|
|
DN_Str8 shallow = DN_Str8FromCStr8(src);
|
|
DN_Str8 result = DN_Str8FromStr8Arena(shallow, arena);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8FromPtrArena(void const *data, DN_USize size, DN_Arena *arena)
|
|
{
|
|
DN_Str8 result = DN_Str8AllocArena(size, DN_ZMem_No, arena);
|
|
if (result.size)
|
|
DN_Memcpy(result.data, data, size);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8FromPtrPool(void const *data, DN_USize size, DN_Pool *pool)
|
|
{
|
|
DN_Str8 result = DN_Str8AllocPool(size, pool);
|
|
if (result.size)
|
|
DN_Memcpy(result.data, data, size);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8FromStr8Arena(DN_Str8 string, DN_Arena *arena)
|
|
{
|
|
DN_Str8 result = {};
|
|
result.data = DN_Cast(char *) DN_ArenaAlloc(arena, string.size + 1, alignof(char), DN_ZMem_No);
|
|
if (result.data) {
|
|
DN_Memcpy(result.data, string.data, string.size);
|
|
result.data[string.size] = 0;
|
|
result.size = string.size;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8FromStr8Pool(DN_Str8 string, DN_Pool *pool)
|
|
{
|
|
DN_Str8 result = {};
|
|
result.data = DN_Cast(char *) DN_PoolAlloc(pool, string.size + 1);
|
|
if (result.data) {
|
|
DN_Memcpy(result.data, string.data, string.size);
|
|
result.data[string.size] = 0;
|
|
result.size = string.size;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8FromFmtVArena(DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, va_list args)
|
|
{
|
|
DN_USize size = DN_FmtVSize(fmt, args);
|
|
DN_Str8 result = DN_Str8AllocArena(size, DN_ZMem_No, arena);
|
|
if (result.data) {
|
|
DN_USize written = 0;
|
|
DN_FmtVAppend(result.data, &written, result.size + 1, fmt, args);
|
|
DN_Assert(written == result.size);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8FromFmtArena(DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, ...)
|
|
{
|
|
va_list va;
|
|
va_start(va, fmt);
|
|
DN_Str8 result = DN_Str8FromFmtVArena(arena, fmt, va);
|
|
va_end(va);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8FromFmtVPool(DN_Pool *pool, DN_FMT_ATTRIB char const *fmt, va_list args)
|
|
{
|
|
DN_USize size = DN_FmtVSize(fmt, args);
|
|
DN_Str8 result = DN_Str8AllocPool(size, pool);
|
|
if (result.data) {
|
|
DN_USize written = 0;
|
|
DN_FmtVAppend(result.data, &written, result.size + 1, fmt, args);
|
|
DN_Assert(written == result.size);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8FromFmtPool(DN_Pool *pool, DN_FMT_ATTRIB char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
DN_Str8 result = DN_Str8FromFmtVPool(pool, fmt, args);
|
|
va_end(args);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8x16 DN_Str8x16FromFmt(DN_FMT_ATTRIB char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
DN_Str8x16 result = {};
|
|
DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args);
|
|
va_end(args);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8x16 DN_Str8x16FromFmtVArena(DN_FMT_ATTRIB char const *fmt, va_list args)
|
|
{
|
|
DN_Str8x16 result = {};
|
|
DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8x32 DN_Str8x32FromFmt(DN_FMT_ATTRIB char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
DN_Str8x32 result = {};
|
|
DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args);
|
|
va_end(args);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8x32 DN_Str8x32FromFmtVArena(DN_FMT_ATTRIB char const *fmt, va_list args)
|
|
{
|
|
DN_Str8x32 result = {};
|
|
DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8x64 DN_Str8x64FromFmt(DN_FMT_ATTRIB char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
DN_Str8x64 result = {};
|
|
DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args);
|
|
va_end(args);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8x64 DN_Str8x64FromFmtV(DN_FMT_ATTRIB char const *fmt, va_list args)
|
|
{
|
|
DN_Str8x64 result = {};
|
|
DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8x128 DN_Str8x128FromFmt(DN_FMT_ATTRIB char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
DN_Str8x128 result = {};
|
|
DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args);
|
|
va_end(args);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8x128 DN_Str8x128FromFmtVArena(DN_FMT_ATTRIB char const *fmt, va_list args)
|
|
{
|
|
DN_Str8x128 result = {};
|
|
DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8x256 DN_Str8x256FromFmt(DN_FMT_ATTRIB char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
DN_Str8x256 result = {};
|
|
DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args);
|
|
va_end(args);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8x256 DN_Str8x256FromFmtVArena(DN_FMT_ATTRIB char const *fmt, va_list args)
|
|
{
|
|
DN_Str8x256 result = {};
|
|
DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8x512 DN_Str8x512FromFmt(DN_FMT_ATTRIB char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
DN_Str8x512 result = {};
|
|
DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args);
|
|
va_end(args);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8x512 DN_Str8x512FromFmtVArena(DN_FMT_ATTRIB char const *fmt, va_list args)
|
|
{
|
|
DN_Str8x512 result = {};
|
|
DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8x1024 DN_Str8x1024FromFmt(DN_FMT_ATTRIB char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
DN_Str8x1024 result = {};
|
|
DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args);
|
|
va_end(args);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8x1024 DN_Str8x1024FromFmtVArena(DN_FMT_ATTRIB char const *fmt, va_list args)
|
|
{
|
|
DN_Str8x1024 result = {};
|
|
DN_FmtVAppend(result.data, &result.size, sizeof(result.data), fmt, args);
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_Str8x16AppendFmt(DN_Str8x16 *str, DN_FMT_ATTRIB char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
DN_Str8x16AppendFmtV(str, fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
DN_API void DN_Str8x16AppendFmtV(DN_Str8x16 *str, DN_FMT_ATTRIB char const *fmt, va_list args)
|
|
{
|
|
DN_FmtVAppend(str->data, &str->size, sizeof(str->data), fmt, args);
|
|
}
|
|
|
|
DN_API void DN_Str8x32AppendFmt(DN_Str8x32 *str, DN_FMT_ATTRIB char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
DN_Str8x32AppendFmtV(str, fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
DN_API void DN_Str8x32AppendFmtV(DN_Str8x32 *str, DN_FMT_ATTRIB char const *fmt, va_list args)
|
|
{
|
|
DN_FmtVAppend(str->data, &str->size, sizeof(str->data), fmt, args);
|
|
}
|
|
|
|
DN_API void DN_Str8x64AppendFmt(DN_Str8x64 *str, DN_FMT_ATTRIB char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
DN_Str8x64AppendFmtV(str, fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
DN_API void DN_Str8x64AppendFmtV(DN_Str8x64 *str, DN_FMT_ATTRIB char const *fmt, va_list args)
|
|
{
|
|
DN_FmtVAppend(str->data, &str->size, sizeof(str->data), fmt, args);
|
|
}
|
|
|
|
DN_API void DN_Str8x128AppendFmt(DN_Str8x128 *str, DN_FMT_ATTRIB char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
DN_Str8x128AppendFmtV(str, fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
DN_API void DN_Str8x128AppendFmtV(DN_Str8x128 *str, DN_FMT_ATTRIB char const *fmt, va_list args)
|
|
{
|
|
DN_FmtVAppend(str->data, &str->size, sizeof(str->data), fmt, args);
|
|
}
|
|
|
|
DN_API void DN_Str8x256AppendFmt(DN_Str8x256 *str, DN_FMT_ATTRIB char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
DN_Str8x256AppendFmtV(str, fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
DN_API void DN_Str8x256AppendFmtV(DN_Str8x256 *str, DN_FMT_ATTRIB char const *fmt, va_list args)
|
|
{
|
|
DN_FmtVAppend(str->data, &str->size, sizeof(str->data), fmt, args);
|
|
}
|
|
|
|
DN_API void DN_Str8x512AppendFmt(DN_Str8x512 *str, DN_FMT_ATTRIB char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
DN_Str8x512AppendFmtV(str, fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
DN_API void DN_Str8x512AppendFmtV(DN_Str8x512 *str, DN_FMT_ATTRIB char const *fmt, va_list args)
|
|
{
|
|
DN_FmtVAppend(str->data, &str->size, sizeof(str->data), fmt, args);
|
|
}
|
|
|
|
DN_API void DN_Str8x1024AppendFmt(DN_Str8x1024 *str, DN_FMT_ATTRIB char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
DN_Str8x1024AppendFmtV(str, fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
DN_API void DN_Str8x1024AppendFmtV(DN_Str8x1024 *str, DN_FMT_ATTRIB char const *fmt, va_list args)
|
|
{
|
|
DN_FmtVAppend(str->data, &str->size, sizeof(str->data), fmt, args);
|
|
}
|
|
|
|
DN_API DN_Str8x32 DN_Str8x32FromU64(DN_U64 val, char separator)
|
|
{
|
|
DN_Str8x32 result = {};
|
|
DN_Str8x32 temp = DN_Str8x32FromFmt("%" PRIu64, val);
|
|
DN_USize temp_index = 0;
|
|
|
|
// NOTE: Write the digits the first, up to [0, 2] digits that do not need a thousandth separator
|
|
DN_USize range_without_separator = temp.size % 3;
|
|
for (; temp_index < range_without_separator; temp_index++)
|
|
result.data[result.size++] = temp.data[temp_index];
|
|
|
|
// NOTE: Write the subsequent digits and every 3rd digit, add the seperator
|
|
DN_USize digit_counter = 0;
|
|
for (; temp_index < temp.size; temp_index++, digit_counter++) {
|
|
if (separator && temp_index && (digit_counter % 3 == 0))
|
|
result.data[result.size++] = separator;
|
|
result.data[result.size++] = temp.data[temp_index];
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
DN_API bool DN_Str8IsAll(DN_Str8 string, DN_Str8IsAllType is_all)
|
|
{
|
|
bool result = string.size;
|
|
if (!result)
|
|
return result;
|
|
|
|
switch (is_all) {
|
|
case DN_Str8IsAllType_Digits: {
|
|
for (DN_USize index = 0; result && index < string.size; index++)
|
|
result = string.data[index] >= '0' && string.data[index] <= '9';
|
|
} break;
|
|
|
|
case DN_Str8IsAllType_Hex: {
|
|
DN_Str8 trimmed = DN_Str8TrimPrefix(string, DN_Str8Lit("0x"), DN_Str8EqCase_Insensitive);
|
|
for (DN_USize index = 0; result && index < trimmed.size; index++) {
|
|
char ch = trimmed.data[index];
|
|
result = (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F');
|
|
}
|
|
} break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
DN_API char *DN_Str8End(DN_Str8 string)
|
|
{
|
|
char *result = string.data + string.size;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8Subset(DN_Str8 string, DN_USize offset, DN_USize size)
|
|
{
|
|
DN_Str8 result = DN_Str8FromPtr(string.data, 0);
|
|
if (string.size == 0)
|
|
return result;
|
|
|
|
DN_USize capped_offset = DN_Min(offset, string.size);
|
|
DN_USize max_size = string.size - capped_offset;
|
|
DN_USize capped_size = DN_Min(size, max_size);
|
|
result = DN_Str8FromPtr(string.data + capped_offset, capped_size);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8Advance(DN_Str8 string, DN_USize amount)
|
|
{
|
|
DN_Str8 result = DN_Str8Subset(string, amount, DN_USIZE_MAX);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8NextLine(DN_Str8 string)
|
|
{
|
|
DN_Str8 result = DN_Str8BSplit(string, DN_Str8Lit("\n")).rhs;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8BSplitResult DN_Str8BSplitArray(DN_Str8 string, DN_Str8 const *find, DN_USize find_size)
|
|
{
|
|
DN_Str8BSplitResult result = {};
|
|
if (string.size == 0 || !find || find_size == 0)
|
|
return result;
|
|
|
|
result.lhs = string;
|
|
for (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_Str8Subset(string, index, find_item.size);
|
|
if (DN_Str8Eq(string_slice, find_item)) {
|
|
result.input_index = find_index;
|
|
result.lhs.size = index;
|
|
result.rhs.data = string_slice.data + find_item.size;
|
|
result.rhs.size = string.size - (index + find_item.size);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8BSplitResult DN_Str8BSplit(DN_Str8 string, DN_Str8 find)
|
|
{
|
|
DN_Str8BSplitResult result = DN_Str8BSplitArray(string, &find, 1);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8BSplitResult DN_Str8BSplitLastArray(DN_Str8 string, DN_Str8 const *find, DN_USize find_size)
|
|
{
|
|
DN_Str8BSplitResult result = {};
|
|
if (string.size == 0 || !find || find_size == 0)
|
|
return result;
|
|
|
|
result.lhs = string;
|
|
for (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_Str8Subset(string, index, find_item.size);
|
|
if (DN_Str8Eq(string_slice, find_item)) {
|
|
result.lhs.size = index;
|
|
result.rhs.data = string_slice.data + find_item.size;
|
|
result.rhs.size = string.size - (index + find_item.size);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8BSplitResult DN_Str8BSplitLast(DN_Str8 string, DN_Str8 find)
|
|
{
|
|
DN_Str8BSplitResult result = DN_Str8BSplitLastArray(string, &find, 1);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_USize DN_Str8Split(DN_Str8 string, DN_Str8 delimiter, DN_Str8 *splits, DN_USize splits_count, DN_Str8SplitFlags flags)
|
|
{
|
|
DN_USize result = 0; // The number of splits in the actual string.
|
|
if (string.size == 0 || delimiter.size == 0 || delimiter.size <= 0)
|
|
return result;
|
|
|
|
DN_Str8 it = string;
|
|
bool allow_empty_strings = DN_BitIsNotSet(flags, DN_Str8SplitFlags_ExcludeEmptyStrings);
|
|
bool handle_quotes = DN_BitIsSet(flags, DN_Str8SplitFlags_HandleQuotedStrings);
|
|
do {
|
|
DN_Str8 item = {};
|
|
if (handle_quotes && DN_Str8StartsWith(it, DN_Str8Lit("\""))) {
|
|
DN_Str8FindResult find = DN_Str8FindStr8(DN_Str8Advance(it, 1), DN_Str8Lit("\""), DN_Str8EqCase_Sensitive);
|
|
DN_Assert(find.found);
|
|
item = find.start_to_before_match;
|
|
it = DN_Str8BSplit(find.after_match_to_end_of_buffer, DN_Str8Lit(",")).rhs;
|
|
} else {
|
|
DN_Str8BSplitResult sub_split = DN_Str8BSplit(it, DN_Str8Lit(","));
|
|
item = sub_split.lhs;
|
|
it = sub_split.rhs;
|
|
}
|
|
|
|
if (item.size || allow_empty_strings) {
|
|
if (splits && result < splits_count)
|
|
splits[result] = item;
|
|
result++;
|
|
}
|
|
} while (it.size);
|
|
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8SplitResult DN_Str8SplitArena(DN_Arena *arena, DN_Str8 string, DN_Str8 delimiter, DN_Str8SplitFlags mode)
|
|
{
|
|
DN_Str8SplitResult result = {};
|
|
DN_USize count = DN_Str8Split(string, delimiter, /*splits*/ nullptr, /*count*/ 0, mode);
|
|
result.data = DN_ArenaNewArray(arena, DN_Str8, count, DN_ZMem_No);
|
|
if (result.data) {
|
|
result.count = DN_Str8Split(string, delimiter, result.data, count, mode);
|
|
DN_Assert(count == result.count);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8FindResult DN_Str8FindStr8Array(DN_Str8 string, DN_Str8 const *find, DN_USize find_size, DN_Str8EqCase eq_case)
|
|
{
|
|
DN_Str8FindResult result = {};
|
|
for (DN_USize index = 0; !result.found && index < string.size; index++) {
|
|
for (DN_USize find_index = 0; find_index < find_size; find_index++) {
|
|
DN_Str8 find_item = find[find_index];
|
|
DN_Str8 string_slice = DN_Str8Subset(string, index, find_item.size);
|
|
if (DN_Str8Eq(string_slice, find_item, eq_case)) {
|
|
result.found = true;
|
|
result.index = index;
|
|
result.start_to_before_match = DN_Str8FromPtr(string.data, index);
|
|
result.match = DN_Str8FromPtr(string.data + index, find_item.size);
|
|
result.match_to_end_of_buffer = DN_Str8FromPtr(result.match.data, string.size - index);
|
|
result.after_match_to_end_of_buffer = DN_Str8Advance(result.match_to_end_of_buffer, find_item.size);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8FindResult DN_Str8FindStr8(DN_Str8 string, DN_Str8 find, DN_Str8EqCase eq_case)
|
|
{
|
|
DN_Str8FindResult result = DN_Str8FindStr8Array(string, &find, 1, eq_case);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8FindResult DN_Str8Find(DN_Str8 string, DN_Str8FindFlag flags)
|
|
{
|
|
DN_Str8FindResult result = {};
|
|
for (size_t index = 0; !result.found && index < string.size; index++) {
|
|
result.found |= ((flags & DN_Str8FindFlag_Digit) && DN_CharIsDigit(string.data[index]));
|
|
result.found |= ((flags & DN_Str8FindFlag_Alphabet) && DN_CharIsAlphabet(string.data[index]));
|
|
result.found |= ((flags & DN_Str8FindFlag_Whitespace) && DN_CharIsWhitespace(string.data[index]));
|
|
result.found |= ((flags & DN_Str8FindFlag_Plus) && string.data[index] == '+');
|
|
result.found |= ((flags & DN_Str8FindFlag_Minus) && string.data[index] == '-');
|
|
if (result.found) {
|
|
result.index = index;
|
|
result.match = DN_Str8FromPtr(string.data + index, 1);
|
|
result.match_to_end_of_buffer = DN_Str8FromPtr(result.match.data, string.size - index);
|
|
result.after_match_to_end_of_buffer = DN_Str8Advance(result.match_to_end_of_buffer, 1);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8Segment(DN_Arena *arena, DN_Str8 src, DN_USize segment_size, char segment_char)
|
|
{
|
|
if (!segment_size || src.size == 0) {
|
|
DN_Str8 result = DN_Str8FromStr8Arena(src, arena);
|
|
return result;
|
|
}
|
|
|
|
DN_USize segments = src.size / segment_size;
|
|
if (src.size % segment_size == 0)
|
|
segments--;
|
|
|
|
DN_USize segment_counter = 0;
|
|
DN_Str8 result = DN_Str8AllocArena(src.size + segments, DN_ZMem_Yes, arena);
|
|
DN_USize write_index = 0;
|
|
for (DN_ForIndexU(src_index, src.size)) {
|
|
result.data[write_index++] = src.data[src_index];
|
|
if ((src_index + 1) % segment_size == 0 && segment_counter < segments) {
|
|
result.data[write_index++] = segment_char;
|
|
segment_counter++;
|
|
}
|
|
DN_AssertF(write_index <= result.size, "result.size=%zu, write_index=%zu", result.size, write_index);
|
|
}
|
|
|
|
DN_AssertF(write_index == result.size, "result.size=%zu, write_index=%zu", result.size, write_index);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8ReverseSegment(DN_Arena *arena, DN_Str8 src, DN_USize segment_size, char segment_char)
|
|
{
|
|
if (!segment_size || src.size == 0) {
|
|
DN_Str8 result = DN_Str8FromStr8Arena(src, arena);
|
|
return result;
|
|
}
|
|
|
|
DN_USize segments = src.size / segment_size;
|
|
if (src.size % segment_size == 0)
|
|
segments--;
|
|
|
|
DN_USize write_counter = 0;
|
|
DN_USize segment_counter = 0;
|
|
DN_Str8 result = DN_Str8AllocArena(src.size + segments, DN_ZMem_Yes, arena);
|
|
DN_USize write_index = result.size - 1;
|
|
|
|
DN_MSVC_WARNING_PUSH
|
|
DN_MSVC_WARNING_DISABLE(6293) // NOTE: Ill-defined loop
|
|
for (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_Str8Eq(DN_Str8 lhs, DN_Str8 rhs, DN_Str8EqCase eq_case)
|
|
{
|
|
if (lhs.size != rhs.size)
|
|
return false;
|
|
bool result = true;
|
|
switch (eq_case) {
|
|
case DN_Str8EqCase_Sensitive: {
|
|
result = (DN_Memcmp(lhs.data, rhs.data, lhs.size) == 0);
|
|
} break;
|
|
|
|
case DN_Str8EqCase_Insensitive: {
|
|
for (DN_USize index = 0; index < lhs.size && result; index++)
|
|
result = (DN_CharToLower(lhs.data[index]) == DN_CharToLower(rhs.data[index]));
|
|
} break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_Str8EqInsensitive(DN_Str8 lhs, DN_Str8 rhs)
|
|
{
|
|
bool result = DN_Str8Eq(lhs, rhs, DN_Str8EqCase_Insensitive);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_Str8StartsWith(DN_Str8 string, DN_Str8 prefix, DN_Str8EqCase eq_case)
|
|
{
|
|
DN_Str8 substring = {string.data, DN_Min(prefix.size, string.size)};
|
|
bool result = DN_Str8Eq(substring, prefix, eq_case);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_Str8StartsWithInsensitive(DN_Str8 string, DN_Str8 prefix)
|
|
{
|
|
bool result = DN_Str8StartsWith(string, prefix, DN_Str8EqCase_Insensitive);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_Str8EndsWith(DN_Str8 string, DN_Str8 suffix, DN_Str8EqCase eq_case)
|
|
{
|
|
DN_Str8 substring = {string.data + string.size - suffix.size, DN_Min(string.size, suffix.size)};
|
|
bool result = DN_Str8Eq(substring, suffix, eq_case);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_Str8EndsWithInsensitive(DN_Str8 string, DN_Str8 suffix)
|
|
{
|
|
bool result = DN_Str8EndsWith(string, suffix, DN_Str8EqCase_Insensitive);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_Str8HasChar(DN_Str8 string, char ch)
|
|
{
|
|
bool result = false;
|
|
for (DN_USize index = 0; !result && index < string.size; index++)
|
|
result = string.data[index] == ch;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8TrimPrefix(DN_Str8 string, DN_Str8 prefix, DN_Str8EqCase eq_case)
|
|
{
|
|
DN_Str8 result = string;
|
|
if (DN_Str8StartsWith(string, prefix, eq_case)) {
|
|
result.data += prefix.size;
|
|
result.size -= prefix.size;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8TrimHexPrefix(DN_Str8 string)
|
|
{
|
|
DN_Str8 result = DN_Str8TrimPrefix(string, DN_Str8Lit("0x"), DN_Str8EqCase_Insensitive);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8TrimSuffix(DN_Str8 string, DN_Str8 suffix, DN_Str8EqCase eq_case)
|
|
{
|
|
DN_Str8 result = string;
|
|
if (DN_Str8EndsWith(string, suffix, eq_case))
|
|
result.size -= suffix.size;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8TrimAround(DN_Str8 string, DN_Str8 trim_string)
|
|
{
|
|
DN_Str8 result = DN_Str8TrimPrefix(string, trim_string);
|
|
result = DN_Str8TrimSuffix(result, trim_string);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8TrimHeadWhitespace(DN_Str8 string)
|
|
{
|
|
DN_Str8 result = string;
|
|
if (string.size == 0)
|
|
return result;
|
|
|
|
char const *start = string.data;
|
|
char const *end = string.data + string.size;
|
|
while (start < end && DN_CharIsWhitespace(start[0]))
|
|
start++;
|
|
|
|
result = DN_Str8FromPtr(start, end - start);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8TrimTailWhitespace(DN_Str8 string)
|
|
{
|
|
DN_Str8 result = string;
|
|
if (string.size == 0)
|
|
return result;
|
|
|
|
char const *start = string.data;
|
|
char const *end = string.data + string.size;
|
|
while (end > start && DN_CharIsWhitespace(end[-1]))
|
|
end--;
|
|
|
|
result = DN_Str8FromPtr(start, end - start);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8TrimWhitespaceAround(DN_Str8 string)
|
|
{
|
|
DN_Str8 result = DN_Str8TrimHeadWhitespace(string);
|
|
result = DN_Str8TrimTailWhitespace(result);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8TrimByteOrderMark(DN_Str8 string)
|
|
{
|
|
DN_Str8 result = string;
|
|
if (result.size == 0)
|
|
return result;
|
|
|
|
// TODO(dn): This is little endian
|
|
DN_Str8 UTF8_BOM = DN_Str8Lit("\xEF\xBB\xBF");
|
|
DN_Str8 UTF16_BOM_BE = DN_Str8Lit("\xEF\xFF");
|
|
DN_Str8 UTF16_BOM_LE = DN_Str8Lit("\xFF\xEF");
|
|
DN_Str8 UTF32_BOM_BE = DN_Str8Lit("\x00\x00\xFE\xFF");
|
|
DN_Str8 UTF32_BOM_LE = DN_Str8Lit("\xFF\xFE\x00\x00");
|
|
|
|
result = DN_Str8TrimPrefix(result, UTF8_BOM, DN_Str8EqCase_Sensitive);
|
|
result = DN_Str8TrimPrefix(result, UTF16_BOM_BE, DN_Str8EqCase_Sensitive);
|
|
result = DN_Str8TrimPrefix(result, UTF16_BOM_LE, DN_Str8EqCase_Sensitive);
|
|
result = DN_Str8TrimPrefix(result, UTF32_BOM_BE, DN_Str8EqCase_Sensitive);
|
|
result = DN_Str8TrimPrefix(result, UTF32_BOM_LE, DN_Str8EqCase_Sensitive);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8FileNameFromPath(DN_Str8 path)
|
|
{
|
|
DN_Str8 separators[] = {DN_Str8Lit("/"), DN_Str8Lit("\\")};
|
|
DN_Str8BSplitResult split = DN_Str8BSplitLastArray(path, separators, DN_ArrayCountU(separators));
|
|
DN_Str8 result = split.rhs.size ? split.rhs : split.lhs;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8FileNameNoExtension(DN_Str8 path)
|
|
{
|
|
DN_Str8 file_name = DN_Str8FileNameFromPath(path);
|
|
DN_Str8 result = DN_Str8FilePathNoExtension(file_name);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8FilePathNoExtension(DN_Str8 path)
|
|
{
|
|
DN_Str8BSplitResult split = DN_Str8BSplitLast(path, DN_Str8Lit("."));
|
|
DN_Str8 result = split.lhs;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8FileExtension(DN_Str8 path)
|
|
{
|
|
DN_Str8BSplitResult split = DN_Str8BSplitLast(path, DN_Str8Lit("."));
|
|
DN_Str8 result = split.rhs;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8FileDirectoryFromPath(DN_Str8 path)
|
|
{
|
|
DN_Str8 separators[] = {DN_Str8Lit("/"), DN_Str8Lit("\\")};
|
|
DN_Str8BSplitResult split = DN_Str8BSplitLastArray(path, separators, DN_ArrayCountU(separators));
|
|
DN_Str8 result = split.lhs;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8AppendF(DN_Arena *arena, DN_Str8 string, char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
DN_Str8 result = DN_Str8AppendFV(arena, string, fmt, args);
|
|
va_end(args);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8AppendFV(DN_Arena *arena, DN_Str8 string, char const *fmt, va_list args)
|
|
{
|
|
// TODO: Calculate size and write into one buffer instead of 2 appends
|
|
DN_Str8 append = DN_Str8FromFmtVArena(arena, fmt, args);
|
|
DN_Str8 result = DN_Str8AllocArena(string.size + append.size, DN_ZMem_No, arena);
|
|
DN_Memcpy(result.data, string.data, string.size);
|
|
DN_Memcpy(result.data + string.size, append.data, append.size);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8FillF(DN_Arena *arena, DN_USize count, char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
DN_Str8 result = DN_Str8FillFV(arena, count, fmt, args);
|
|
va_end(args);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8FillFV(DN_Arena *arena, DN_USize count, char const *fmt, va_list args)
|
|
{
|
|
DN_Str8 fill = DN_Str8FromFmtVArena(arena, fmt, args);
|
|
DN_Str8 result = DN_Str8AllocArena(count * fill.size, DN_ZMem_No, arena);
|
|
for (DN_USize index = 0; index < count; index++) {
|
|
void *dest = result.data + (index * fill.size);
|
|
DN_Memcpy(dest, fill.data, fill.size);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_Str8Remove(DN_Str8 *string, DN_USize offset, DN_USize size)
|
|
{
|
|
if (!string || string->size)
|
|
return;
|
|
|
|
char *end = string->data + string->size;
|
|
char *dest = DN_Min(string->data + offset, end);
|
|
char *src = DN_Min(string->data + offset + size, end);
|
|
DN_USize bytes_to_move = end - src;
|
|
DN_Memmove(dest, src, bytes_to_move);
|
|
string->size -= bytes_to_move;
|
|
}
|
|
|
|
DN_API DN_Str8TruncResult DN_Str8TruncMiddlePtr(DN_Str8 str8, DN_USize side_size, DN_Str8 truncator, char *dest, DN_USize dest_max)
|
|
{
|
|
DN_Assert(side_size <= DN_USIZE_MAX / 2);
|
|
if (dest) {
|
|
// NOTE: If the user passes the dest buffer, we expect it to be sized correctly.
|
|
if ((side_size * 2) >= str8.size) {
|
|
DN_Assert(dest_max >= str8.size + 1 /*null*/);
|
|
} else {
|
|
DN_Assert(dest_max >= (2 * side_size + truncator.size) + 1 /*null*/);
|
|
}
|
|
}
|
|
|
|
DN_Str8TruncResult result = {};
|
|
if (str8.size <= (side_size * 2)) {
|
|
result.size_req = str8.size;
|
|
if (dest) {
|
|
DN_Memcpy(dest, str8.data, str8.size);
|
|
dest[str8.size] = 0;
|
|
result.str8 = DN_Str8FromPtr(dest, result.size_req);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_Str8 head = DN_Str8Subset(str8, 0, side_size);
|
|
DN_Str8 tail = DN_Str8Subset(str8, str8.size - side_size, side_size);
|
|
DN_USize dest_size = 0;
|
|
if (dest) {
|
|
DN_FmtAppendResult append_result = DN_FmtAppend(dest, &dest_size, dest_max, "%.*s%.*s%.*s", DN_Str8PrintFmt(head), DN_Str8PrintFmt(truncator), DN_Str8PrintFmt(tail));
|
|
result.str8 = append_result.str8;
|
|
result.truncated = true;
|
|
result.size_req = result.str8.size;
|
|
} else {
|
|
result.size_req = DN_FmtSize("%.*s%.*s%.*s", DN_Str8PrintFmt(head), DN_Str8PrintFmt(truncator), DN_Str8PrintFmt(tail));
|
|
result.truncated = true;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8TruncResult DN_Str8TruncMiddle(DN_Str8 str8, DN_USize side_size, DN_Str8 truncator, DN_Arena *arena)
|
|
{
|
|
DN_Str8TruncResult trunc = DN_Str8TruncMiddlePtr(str8, side_size, truncator, nullptr, 0);
|
|
DN_Str8 dest = DN_Str8AllocArena(trunc.size_req, DN_ZMem_No, arena);
|
|
DN_Str8TruncResult result = DN_Str8TruncMiddlePtr(str8, side_size, truncator, dest.data, dest.size + 1);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8Lower(DN_Str8 string, DN_Arena *arena)
|
|
{
|
|
DN_Str8 result = DN_Str8FromStr8Arena(string, arena);
|
|
for (DN_ForIndexU(index, result.size))
|
|
result.data[index] = DN_CharToLower(result.data[index]);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8Upper(DN_Str8 string, DN_Arena *arena)
|
|
{
|
|
DN_Str8 result = DN_Str8FromStr8Arena(string, arena);
|
|
for (DN_ForIndexU(index, result.size))
|
|
result.data[index] = DN_CharToUpper(result.data[index]);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8Replace(DN_Str8 string,
|
|
DN_Str8 find,
|
|
DN_Str8 replace,
|
|
DN_USize start_index,
|
|
DN_Arena *arena,
|
|
DN_Str8EqCase eq_case)
|
|
{
|
|
DN_Str8 result = {};
|
|
if (string.size == 0 || find.size == 0 || find.size > string.size || find.size == 0 || string.size == 0) {
|
|
result = DN_Str8FromStr8Arena(string, arena);
|
|
return result;
|
|
}
|
|
|
|
DN_TCScratch scratch = DN_TCScratchBegin(&arena, 1);
|
|
DN_Str8Builder string_builder = DN_Str8BuilderFromArena(&scratch.arena);
|
|
DN_USize max = string.size - find.size;
|
|
DN_USize head = start_index;
|
|
|
|
for (DN_USize tail = head; tail <= max; tail++) {
|
|
DN_Str8 check = DN_Str8Subset(string, tail, find.size);
|
|
if (!DN_Str8Eq(check, find, eq_case))
|
|
continue;
|
|
|
|
if (start_index > 0 && string_builder.string_size == 0) {
|
|
// User provided a hint in the string to start searching from, we
|
|
// need to add the string up to the hint. We only do this if there's
|
|
// a replacement action, otherwise we have a special case for no
|
|
// replacements, where the entire string gets copied.
|
|
DN_Str8 slice = DN_Str8FromPtr(string.data, head);
|
|
DN_Str8BuilderAppendRef(&string_builder, slice);
|
|
}
|
|
|
|
DN_Str8 range = DN_Str8Subset(string, head, (tail - head));
|
|
DN_Str8BuilderAppendRef(&string_builder, range);
|
|
DN_Str8BuilderAppendRef(&string_builder, replace);
|
|
head = tail + find.size;
|
|
tail += find.size - 1; // NOTE: -1 since the for loop will post increment us past the end of the find string
|
|
}
|
|
|
|
if (string_builder.string_size == 0) {
|
|
// NOTE: No replacement possible, so we just do a full-copy
|
|
result = DN_Str8FromStr8Arena(string, arena);
|
|
} else {
|
|
DN_Str8 remainder = DN_Str8FromPtr(string.data + head, string.size - head);
|
|
DN_Str8BuilderAppendRef(&string_builder, remainder);
|
|
result = DN_Str8BuilderBuild(&string_builder, arena);
|
|
}
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8ReplaceSensitive(DN_Str8 string, DN_Str8 find, DN_Str8 replace, DN_USize start_index, DN_Arena *arena)
|
|
{
|
|
DN_Str8 result = DN_Str8Replace(string, find, replace, start_index, arena, DN_Str8EqCase_Sensitive);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8ReplaceInsensitive(DN_Str8 string, DN_Str8 find, DN_Str8 replace, DN_USize start_index, DN_Arena *arena)
|
|
{
|
|
DN_Str8 result = DN_Str8Replace(string, find, replace, start_index, arena, DN_Str8EqCase_Insensitive);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8PadNewLines(DN_Str8 string, DN_Str8 pad_string, DN_Arena *arena)
|
|
{
|
|
DN_TCScratch scratch = DN_TCScratchBegin(&arena, 1);
|
|
DN_Str8Builder builder = DN_Str8BuilderFromArena(&scratch.arena);
|
|
DN_Str8 it = string;
|
|
while (it.size) {
|
|
DN_Str8BSplitResult split = DN_Str8BSplit(it, DN_Str8Lit("\n"));
|
|
DN_Str8BuilderAppendRef(&builder, DN_Str8FromPtr(split.lhs.data, split.lhs.size + 1));
|
|
it = split.rhs;
|
|
}
|
|
|
|
DN_Str8 result = DN_Str8BuilderBuildDelimited(&builder, pad_string, arena);
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_USize DN_USizeCodepointCountFromUTF8(DN_Str8 str, DN_CodepointCountFlags flags)
|
|
{
|
|
DN_USize result = 0;
|
|
|
|
if (DN_BitIsNotSet(flags, DN_CodepointCountFlags_SkipANSICode)) {
|
|
DN_UTF8DecodeIterator it = {};
|
|
while (DN_UTF8DecodeIterate(&it, str))
|
|
;
|
|
result = it.codepoint_index;
|
|
} else {
|
|
// NOTE: ANSI SGR (Select Graphic Rendition) sequence handling
|
|
// Format: ESC [ parameter_bytes intermediate_bytes final_byte
|
|
// Common examples: \x1b[31m (red), \x1b[1;31m (bold red), \x1b[0m (reset)
|
|
// Parameter bytes: 0x30-0x3F (digits and :;<=>?)
|
|
// Intermediate bytes: 0x20-0x2F (space and !"#$%&'()*+,-./)
|
|
// Final byte: 0x40-0x7E (@A-Z[\]^_`a-z{|}~)
|
|
char const *p = str.data;
|
|
char const *end = DN_Str8End(str);
|
|
while (p < end) {
|
|
if (*p == '\x1b' && p + 1 < end && *(p + 1) == '[') { // Detect CSI sequence: ESC [
|
|
p += 2;
|
|
while (p < end && *p >= 0x30 && *p <= 0x3F) // Skip parameter bytes (0x30-0x3F)
|
|
p++;
|
|
while (p < end && *p >= 0x20 && *p <= 0x2F) // Skip intermediate bytes (0x20-0x2F)
|
|
p++;
|
|
if (p < end && *p >= 0x40 && *p <= 0x7E) // Skip final byte (0x40-0x7E)
|
|
p++;
|
|
continue;
|
|
}
|
|
|
|
DN_UTF8DecodeResult decode = DN_UTF8Decode(DN_Str8FromPtr(p, end - p));
|
|
if (!decode.success)
|
|
break;
|
|
p = decode.remaining.data;
|
|
result++;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8LineBreakStr8(DN_Str8 src, DN_USize desired_width, DN_Arena *arena)
|
|
{
|
|
DN_TCScratch scratch = DN_TCScratchBegin(&arena, 1);
|
|
DN_Str8Builder builder = DN_Str8BuilderFromArena(&scratch.arena);
|
|
|
|
char* start = src.data;
|
|
char* end = src.data;
|
|
DN_Str8 it = src;
|
|
while (it.size) {
|
|
DN_Str8 splitters[] = {DN_Str8Lit(" "), DN_Str8Lit("\n")};
|
|
DN_Str8BSplitResult split = DN_Str8BSplitArray(it, splitters, DN_ArrayCountU(splitters));
|
|
DN_USize curr_line_length = end - start;
|
|
|
|
// Handle explicit newlines in input
|
|
if (split.input_index == 1 /*the newline*/) {
|
|
if (curr_line_length == 0 && split.lhs.size)
|
|
start = split.lhs.data;
|
|
if (split.lhs.size)
|
|
end = DN_Str8End(split.lhs);
|
|
DN_Str8BuilderAppendRef(&builder, DN_Str8FromPtr(start, end - start));
|
|
start = split.rhs.data;
|
|
end = split.rhs.data;
|
|
it = split.rhs;
|
|
continue;
|
|
}
|
|
|
|
// Skip empty segments (multiple spaces, leading/trailing spaces)
|
|
if (split.lhs.size == 0) {
|
|
it = split.rhs;
|
|
continue;
|
|
}
|
|
|
|
// First word on this line
|
|
if (curr_line_length == 0) {
|
|
start = split.lhs.data;
|
|
end = DN_Str8End(split.lhs);
|
|
it = split.rhs;
|
|
continue;
|
|
}
|
|
|
|
// Check if adding this word (plus separator space) would overflow
|
|
DN_USize combined_length = curr_line_length + 1 + split.lhs.size;
|
|
if (combined_length > desired_width) {
|
|
// Commit current line, start new line with current word
|
|
DN_Str8BuilderAppendRef(&builder, DN_Str8FromPtr(start, end - start));
|
|
start = split.lhs.data;
|
|
end = DN_Str8End(split.lhs);
|
|
it = split.rhs;
|
|
} else {
|
|
// Add word to current line
|
|
end = DN_Str8End(split.lhs);
|
|
it = split.rhs;
|
|
}
|
|
}
|
|
|
|
// Append final line
|
|
if (end > start)
|
|
DN_Str8BuilderAppendRef(&builder, DN_Str8FromPtr(start, end - start));
|
|
|
|
DN_Str8 result = DN_Str8BuilderBuildDelimited(&builder, DN_Str8Lit("\n"), arena);
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8Table(DN_Str8 const *rows, DN_USize num_rows, DN_USize num_cols, DN_Str8TableFlags flags, DN_Arena *arena)
|
|
{
|
|
DN_TCScratch scratch = DN_TCScratchBegin(&arena, 1);
|
|
DN_U16 col_widths[128] = {};
|
|
for (DN_USize i = 0; i < num_cols; i++) {
|
|
for (DN_USize j = 0; j < num_rows; j++) {
|
|
DN_USize index = j * num_cols + i;
|
|
col_widths[i] = DN_Max(col_widths[i], (DN_U16)DN_USizeCodepointCountFromUTF8(rows[index], DN_CodepointCountFlags_SkipANSICode));
|
|
}
|
|
}
|
|
|
|
DN_Str8Builder builder = DN_Str8BuilderFromArena(&scratch.arena);
|
|
DN_Str8BuilderAppendF(&builder, "+");
|
|
for (DN_USize i = 0; i < num_cols; i++) {
|
|
for (DN_USize j = 0; j < col_widths[i] + 2; j++)
|
|
DN_Str8BuilderAppendF(&builder, "-");
|
|
DN_Str8BuilderAppendF(&builder, "+");
|
|
}
|
|
DN_Str8BuilderAppendF(&builder, "\n");
|
|
|
|
for (DN_USize i = 0; i < num_rows; i++) {
|
|
DN_Str8BuilderAppendF(&builder, "|");
|
|
for (DN_USize j = 0; j < num_cols; j++) {
|
|
DN_USize index = (i * num_cols) + j;
|
|
DN_Str8 item = rows[index];
|
|
DN_Str8BuilderAppendF(&builder, " %.*s", DN_Str8PrintFmt(item));
|
|
DN_USize item_width = DN_USizeCodepointCountFromUTF8(item, DN_CodepointCountFlags_SkipANSICode);
|
|
for (DN_USize k = 0; k < col_widths[j] - item_width; k++)
|
|
DN_Str8BuilderAppendF(&builder, " ");
|
|
DN_Str8BuilderAppendF(&builder, " |");
|
|
}
|
|
DN_Str8BuilderAppendF(&builder, "\n");
|
|
|
|
bool print_row_line = i == 0 && DN_BitIsSet(flags, DN_Str8TableFlags_HasHeader);
|
|
if (!print_row_line)
|
|
print_row_line = DN_BitIsSet(flags, DN_Str8TableFlags_RowLines);
|
|
|
|
if (print_row_line) {
|
|
DN_Str8BuilderAppendF(&builder, "+");
|
|
for (DN_USize sub_i = 0; sub_i < num_cols; sub_i++) {
|
|
for (DN_USize sub_j = 0; sub_j < col_widths[sub_i] + 2; sub_j++)
|
|
DN_Str8BuilderAppendF(&builder, "-");
|
|
DN_Str8BuilderAppendF(&builder, "+");
|
|
}
|
|
DN_Str8BuilderAppendF(&builder, "\n");
|
|
}
|
|
}
|
|
|
|
DN_Str8BuilderAppendF(&builder, "+");
|
|
for (DN_USize i = 0; i < num_cols; i++) {
|
|
for (DN_USize j = 0; j < col_widths[i] + 2; j++)
|
|
DN_Str8BuilderAppendF(&builder, "-");
|
|
DN_Str8BuilderAppendF(&builder, "+");
|
|
}
|
|
|
|
DN_Str8 result = DN_Str8BuilderBuild(&builder, arena);
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8SliceRender(DN_Str8Slice slice, DN_Str8 separator, DN_Arena *arena)
|
|
{
|
|
DN_Str8 result = {};
|
|
if (!arena)
|
|
return result;
|
|
|
|
DN_USize total_size = 0;
|
|
for (DN_USize index = 0; index < slice.count; index++) {
|
|
if (index)
|
|
total_size += separator.size;
|
|
DN_Str8 item = slice.data[index];
|
|
total_size += item.size;
|
|
}
|
|
|
|
result = DN_Str8AllocArena(total_size, DN_ZMem_No, arena);
|
|
if (result.data) {
|
|
DN_USize write_index = 0;
|
|
for (DN_USize index = 0; index < slice.count; index++) {
|
|
if (index) {
|
|
DN_Memcpy(result.data + write_index, separator.data, separator.size);
|
|
write_index += separator.size;
|
|
}
|
|
DN_Str8 item = slice.data[index];
|
|
DN_Memcpy(result.data + write_index, item.data, item.size);
|
|
write_index += item.size;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8RenderSpaceSep(DN_Str8Slice slice, DN_Arena *arena)
|
|
{
|
|
DN_Str8 result = DN_Str8SliceRender(slice, DN_Str8Lit(" "), arena);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_Str16Eq(DN_Str16 lhs, DN_Str16 rhs)
|
|
{
|
|
if (lhs.size != rhs.size)
|
|
return false;
|
|
bool result = (DN_Memcmp(lhs.data, rhs.data, lhs.size) == 0);
|
|
return result;
|
|
}
|
|
|
|
|
|
DN_API DN_Str16 DN_Str16SliceRender(DN_Str16Slice slice, DN_Str16 separator, DN_Arena *arena)
|
|
{
|
|
DN_Str16 result = {};
|
|
if (!arena)
|
|
return result;
|
|
|
|
DN_USize total_size = 0;
|
|
for (DN_USize index = 0; index < slice.count; index++) {
|
|
if (index)
|
|
total_size += separator.size;
|
|
DN_Str16 item = slice.data[index];
|
|
total_size += item.size;
|
|
}
|
|
|
|
result = {DN_ArenaNewArray(arena, wchar_t, total_size + 1, DN_ZMem_No), total_size};
|
|
if (result.data) {
|
|
DN_USize write_index = 0;
|
|
for (DN_USize index = 0; index < slice.count; index++) {
|
|
if (index) {
|
|
DN_Memcpy(result.data + write_index, separator.data, separator.size * sizeof(result.data[0]));
|
|
write_index += separator.size;
|
|
}
|
|
DN_Str16 item = slice.data[index];
|
|
DN_Memcpy(result.data + write_index, item.data, item.size * sizeof(result.data[0]));
|
|
write_index += item.size;
|
|
}
|
|
}
|
|
|
|
result.data[total_size] = 0;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str16 DN_Str16RenderSpaceSep(DN_Str16Slice slice, DN_Arena *arena)
|
|
{
|
|
DN_Str16 result = DN_Str16SliceRender(slice, DN_Str16Lit(L" "), arena);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8Builder DN_Str8BuilderFromArena(DN_Arena *arena)
|
|
{
|
|
DN_Str8Builder result = {};
|
|
result.arena = arena;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8Builder DN_Str8BuilderFromStr8PtrRef(DN_Arena *arena, DN_Str8 const *strings, DN_USize size)
|
|
{
|
|
DN_Str8Builder result = DN_Str8BuilderFromArena(arena);
|
|
DN_Str8BuilderAppendArrayRef(&result, strings, size);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8Builder DN_Str8BuilderFromStr8PtrCopy(DN_Arena *arena, DN_Str8 const *strings, DN_USize size)
|
|
{
|
|
DN_Str8Builder result = DN_Str8BuilderFromArena(arena);
|
|
DN_Str8BuilderAppendArrayCopy(&result, strings, size);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8Builder DN_Str8BuilderFromBuilder(DN_Arena *arena, DN_Str8Builder const *builder)
|
|
{
|
|
DN_Str8Builder result = DN_Str8BuilderFromArena(arena);
|
|
DN_Str8BuilderAppendBuilderCopy(&result, builder);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_Str8BuilderAddArrayRef(DN_Str8Builder *builder, DN_Str8 const *strings, DN_USize size, DN_Str8BuilderAdd add)
|
|
{
|
|
if (!builder)
|
|
return false;
|
|
|
|
if (!strings || size <= 0)
|
|
return true;
|
|
|
|
// NOTE: Allocate the links
|
|
DN_Str8Link *links = DN_ArenaNewArrayNoZ(builder->arena, DN_Str8Link, size);
|
|
if (!links)
|
|
return false;
|
|
|
|
if (add == DN_Str8BuilderAdd_Append) {
|
|
for (DN_ForIndexU(index, size)) {
|
|
DN_Str8 string = strings[index];
|
|
DN_Str8Link *link = links + index;
|
|
link->string = string;
|
|
link->next = NULL;
|
|
if (builder->head)
|
|
builder->tail->next = link;
|
|
else
|
|
builder->head = link;
|
|
builder->tail = link;
|
|
builder->count++;
|
|
builder->string_size += string.size;
|
|
}
|
|
} else {
|
|
DN_Assert(add == DN_Str8BuilderAdd_Prepend);
|
|
DN_MSVC_WARNING_PUSH
|
|
DN_MSVC_WARNING_DISABLE(6293) // NOTE: Ill-defined loop
|
|
for (DN_USize index = size - 1; index < size; index--) {
|
|
DN_MSVC_WARNING_POP
|
|
DN_Str8 string = strings[index];
|
|
DN_Str8Link *link = links + index;
|
|
link->string = string;
|
|
link->next = builder->head;
|
|
builder->head = link;
|
|
if (!builder->tail)
|
|
builder->tail = link;
|
|
builder->count++;
|
|
builder->string_size += string.size;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
DN_API bool DN_Str8BuilderAddArrayCopy(DN_Str8Builder *builder, DN_Str8 const *strings, DN_USize size, DN_Str8BuilderAdd add)
|
|
{
|
|
if (!builder)
|
|
return false;
|
|
|
|
if (!strings || size <= 0)
|
|
return true;
|
|
|
|
bool result = true;
|
|
DN_U64 arena_p = DN_MemListPos(builder->arena->mem);
|
|
DN_Str8 *strings_copy = DN_ArenaNewArrayNoZ(builder->arena, DN_Str8, size);
|
|
for (DN_ForIndexU(index, size)) {
|
|
strings_copy[index] = DN_Str8FromStr8Arena(strings[index], builder->arena);
|
|
if (strings_copy[index].size != strings[index].size) {
|
|
result = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (result)
|
|
result = DN_Str8BuilderAddArrayRef(builder, strings_copy, size, add);
|
|
else
|
|
DN_MemListPopTo(builder->arena->mem, arena_p);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_Str8BuilderAddFV(DN_Str8Builder *builder, DN_Str8BuilderAdd add, DN_FMT_ATTRIB char const *fmt, va_list args)
|
|
{
|
|
DN_Str8 string = DN_Str8FromFmtVArena(builder->arena, fmt, args);
|
|
DN_U64 arena_p = DN_MemListPos(builder->arena->mem);
|
|
bool result = DN_Str8BuilderAddArrayRef(builder, &string, 1, add);
|
|
if (!result)
|
|
DN_MemListPopTo(builder->arena->mem, arena_p);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_Str8BuilderAppendRef(DN_Str8Builder *builder, DN_Str8 string)
|
|
{
|
|
bool result = DN_Str8BuilderAddArrayRef(builder, &string, 1, DN_Str8BuilderAdd_Append);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_Str8BuilderAppendCopy(DN_Str8Builder *builder, DN_Str8 string)
|
|
{
|
|
bool result = DN_Str8BuilderAddArrayCopy(builder, &string, 1, DN_Str8BuilderAdd_Append);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_Str8BuilderAppendF(DN_Str8Builder *builder, DN_FMT_ATTRIB char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
bool result = DN_Str8BuilderAppendFV(builder, fmt, args);
|
|
va_end(args);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_Str8BuilderAppendBytesRef(DN_Str8Builder *builder, void const *ptr, DN_USize size)
|
|
{
|
|
DN_Str8 input = DN_Str8FromPtr(ptr, size);
|
|
bool result = DN_Str8BuilderAppendRef(builder, input);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_Str8BuilderAppendBytesCopy(DN_Str8Builder *builder, void const *ptr, DN_USize size)
|
|
{
|
|
DN_Str8 input = DN_Str8FromPtr(ptr, size);
|
|
bool result = DN_Str8BuilderAppendCopy(builder, input);
|
|
return result;
|
|
}
|
|
|
|
static bool DN_Str8BuilderAppendBuilder_(DN_Str8Builder *dest, DN_Str8Builder const *src, bool copy)
|
|
{
|
|
if (!dest)
|
|
return false;
|
|
if (!src || src->string_size == 0)
|
|
return true;
|
|
|
|
DN_Arena arena = DN_ArenaTempBeginFromArena(dest->arena);
|
|
DN_Str8Link *links = DN_ArenaNewArrayNoZ(&arena, DN_Str8Link, src->count);
|
|
bool result = true;
|
|
if (links) {
|
|
DN_Str8Link *first = nullptr;
|
|
DN_Str8Link *last = nullptr;
|
|
DN_USize link_index = 0;
|
|
for (DN_Str8Link const *it = src->head; it; it = it->next) {
|
|
DN_Str8Link *link = links + link_index++;
|
|
link->next = nullptr;
|
|
link->string = it->string;
|
|
|
|
if (copy) {
|
|
link->string = DN_Str8FromStr8Arena(it->string, &arena);
|
|
if (link->string.size != it->string.size) {
|
|
result = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (last)
|
|
last->next = link;
|
|
else
|
|
first = link;
|
|
last = link;
|
|
}
|
|
|
|
if (result) {
|
|
if (dest->head)
|
|
dest->tail->next = first;
|
|
else
|
|
dest->head = first;
|
|
dest->tail = last;
|
|
dest->count += src->count;
|
|
dest->string_size += src->string_size;
|
|
}
|
|
}
|
|
DN_ArenaTempEnd(&arena, result ? DN_ArenaReset_No : DN_ArenaReset_Yes);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_Str8BuilderAppendBuilderRef(DN_Str8Builder *dest, DN_Str8Builder const *src)
|
|
{
|
|
bool result = DN_Str8BuilderAppendBuilder_(dest, src, false);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_Str8BuilderAppendBuilderCopy(DN_Str8Builder *dest, DN_Str8Builder const *src)
|
|
{
|
|
bool result = DN_Str8BuilderAppendBuilder_(dest, src, true);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_Str8BuilderPrependRef(DN_Str8Builder *builder, DN_Str8 string)
|
|
{
|
|
bool result = DN_Str8BuilderAddArrayRef(builder, &string, 1, DN_Str8BuilderAdd_Prepend);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_Str8BuilderPrependCopy(DN_Str8Builder *builder, DN_Str8 string)
|
|
{
|
|
bool result = DN_Str8BuilderAddArrayCopy(builder, &string, 1, DN_Str8BuilderAdd_Prepend);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_Str8BuilderPrependF(DN_Str8Builder *builder, DN_FMT_ATTRIB char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
bool result = DN_Str8BuilderPrependFV(builder, fmt, args);
|
|
va_end(args);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_Str8BuilderErase(DN_Str8Builder *builder, DN_Str8 string)
|
|
{
|
|
for (DN_Str8Link **it = &builder->head; *it; it = &((*it)->next)) {
|
|
if (DN_Str8Eq((*it)->string, string)) {
|
|
*it = (*it)->next;
|
|
builder->string_size -= string.size;
|
|
builder->count -= 1;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8BuilderBuild(DN_Str8Builder const *builder, DN_Arena *arena)
|
|
{
|
|
DN_Str8 result = DN_Str8BuilderBuildDelimited(builder, DN_Str8Lit(""), arena);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8BuilderBuildDelimited(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 = delimiter.size ? ((builder->count - 1) * delimiter.size) : 0;
|
|
result.data = DN_ArenaNewArray(arena,
|
|
char,
|
|
builder->string_size + size_for_delimiter + 1 /*null terminator*/,
|
|
DN_ZMem_No);
|
|
if (!result.data)
|
|
return result;
|
|
|
|
for (DN_Str8Link *link = builder->head; link; link = link->next) {
|
|
DN_Memcpy(result.data + result.size, link->string.data, link->string.size);
|
|
result.size += link->string.size;
|
|
if (link->next && delimiter.size) {
|
|
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;
|
|
}
|
|
|
|
// NOTE: DN_UTF
|
|
DN_API int DN_UTF8Encode(DN_U8 utf8[4], DN_U32 codepoint)
|
|
{
|
|
// NOTE: Table from https://www.reedbeta.com/blog/programmers-intro-to-unicode/
|
|
// ----------------------------------------+----------------------------+--------------------+
|
|
// UTF-8 (binary) | Code point (binary) | Range |
|
|
// ----------------------------------------+----------------------------+--------------------+
|
|
// 0xxx'xxxx | xxx'xxxx | U+0000 - U+007F |
|
|
// 110x'xxxx 10yy'yyyy | xxx'xxyy'yyyy | U+0080 - U+07FF |
|
|
// 1110'xxxx 10yy'yyyy 10zz'zzzz | xxxx'yyyy'yyzz'zzzz | U+0800 - U+FFFF |
|
|
// 1111'0xxx 10yy'yyyy 10zz'zzzz 10ww'wwww | x'xxyy'yyyy'zzzz'zzww'wwww | U+10000 - U+10FFFF |
|
|
// ----------------------------------------+----------------------------+--------------------+
|
|
|
|
if (codepoint <= 0b0111'1111) {
|
|
utf8[0] = DN_Cast(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 DN_UTF8DecodeResult DN_UTF8Decode(DN_Str8 stream)
|
|
{
|
|
DN_UTF8DecodeResult result = {};
|
|
result.remaining = stream;
|
|
if (stream.size <= 0)
|
|
return result;
|
|
|
|
DN_U8 b0 = DN_Cast(DN_U8)stream.data[0];
|
|
DN_U8 b1 = DN_Cast(DN_U8)(stream.size >= 2 ? stream.data[1] : 0);
|
|
DN_U8 b2 = DN_Cast(DN_U8)(stream.size >= 3 ? stream.data[2] : 0);
|
|
DN_U8 b3 = DN_Cast(DN_U8)(stream.size >= 4 ? stream.data[3] : 0);
|
|
|
|
if ((b0 & 0b1000'0000) == 0) {
|
|
result.codepoint = b0;
|
|
result.success = true;
|
|
result.remaining = DN_Str8FromPtr(stream.data + 1, stream.size - 1);
|
|
return result;
|
|
}
|
|
|
|
if ((b0 & 0b1110'0000) == 0b1100'0000) {
|
|
if (stream.size < 2)
|
|
return result;
|
|
if ((b1 & 0b1100'0000) != 0b1000'0000)
|
|
return result;
|
|
DN_U32 cp = ((b0 & 0b0001'1111) << 6) | ((b1 & 0b0011'1111) << 0);
|
|
if (cp < 0x80)
|
|
return result;
|
|
result.codepoint = cp;
|
|
result.success = true;
|
|
result.remaining = DN_Str8FromPtr(stream.data + 2, stream.size - 2);
|
|
return result;
|
|
}
|
|
|
|
if ((b0 & 0b1111'0000) == 0b1110'0000) {
|
|
if (stream.size < 3)
|
|
return result;
|
|
if ((b1 & 0b1100'0000) != 0b1000'0000)
|
|
return result;
|
|
if ((b2 & 0b1100'0000) != 0b1000'0000)
|
|
return result;
|
|
DN_U32 cp = ((b0 & 0b0000'1111) << 12) | ((b1 & 0b0011'1111) << 6) | ((b2 & 0b0011'1111) << 0);
|
|
if (cp < 0x800)
|
|
return result;
|
|
result.codepoint = cp;
|
|
result.success = true;
|
|
result.remaining = DN_Str8FromPtr(stream.data + 3, stream.size - 3);
|
|
return result;
|
|
}
|
|
|
|
if ((b0 & 0b1111'1000) == 0b1111'0000) {
|
|
if (stream.size < 4)
|
|
return result;
|
|
if ((b1 & 0b1100'0000) != 0b1000'0000)
|
|
return result;
|
|
if ((b2 & 0b1100'0000) != 0b1000'0000)
|
|
return result;
|
|
if ((b3 & 0b1100'0000) != 0b1000'0000)
|
|
return result;
|
|
DN_U32 cp = ((b0 & 0b0000'0111) << 18) |
|
|
((b1 & 0b0011'1111) << 12) |
|
|
((b2 & 0b0011'1111) << 6) |
|
|
((b3 & 0b0011'1111) << 0);
|
|
if (cp < 0x10000 || cp > 0x10FFFF)
|
|
return result;
|
|
result.codepoint = cp;
|
|
result.success = true;
|
|
result.remaining = DN_Str8FromPtr(stream.data + 4, stream.size - 4);
|
|
return result;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_UTF8DecodeIterate(DN_UTF8DecodeIterator *it, DN_Str8 utf8)
|
|
{
|
|
if (it->init) {
|
|
it->codepoint_index++;
|
|
} else {
|
|
it->remaining = utf8;
|
|
it->init = true;
|
|
}
|
|
DN_UTF8DecodeResult decode = DN_UTF8Decode(it->remaining);
|
|
it->success = decode.success;
|
|
it->remaining = decode.remaining;
|
|
it->codepoint = decode.codepoint;
|
|
bool result = it->success;
|
|
return result;
|
|
}
|
|
|
|
DN_API int DN_UTF16Encode(DN_U16 utf16[2], DN_U32 codepoint)
|
|
{
|
|
// NOTE: Table from https://www.reedbeta.com/blog/programmers-intro-to-unicode/
|
|
// ----------------------------------------+------------------------------------+------------------+
|
|
// UTF-16 (binary) | Code point (binary) | Range |
|
|
// ----------------------------------------+------------------------------------+------------------+
|
|
// xxxx'xxxx'xxxx'xxxx | xxxx'xxxx'xxxx'xxxx | U+0000???U+FFFF |
|
|
// 1101'10xx'xxxx'xxxx 1101'11yy'yyyy'yyyy | xxxx'xxxx'xxyy'yyyy'yyyy + 0x10000 | U+10000???U+10FFFF |
|
|
// ----------------------------------------+------------------------------------+------------------+
|
|
|
|
if (codepoint <= 0b1111'1111'1111'1111) {
|
|
utf16[0] = DN_Cast(DN_U16) codepoint;
|
|
return 1;
|
|
}
|
|
|
|
if (codepoint <= 0b1111'1111'1111'1111'1111) {
|
|
DN_U32 surrogate_codepoint = codepoint + 0x10000;
|
|
utf16[0] = 0b1101'1000'0000'0000 | ((surrogate_codepoint >> 10) & 0b11'1111'1111); // x
|
|
utf16[1] = 0b1101'1100'0000'0000 | ((surrogate_codepoint >> 0) & 0b11'1111'1111); // y
|
|
return 2;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
DN_API DN_U8 DN_U8FromHexNibble(char hex)
|
|
{
|
|
bool digit = hex >= '0' && hex <= '9';
|
|
bool upper = hex >= 'A' && hex <= 'F';
|
|
bool lower = hex >= 'a' && hex <= 'f';
|
|
DN_U8 result = 0xFF;
|
|
if (digit)
|
|
result = hex - '0';
|
|
if (upper)
|
|
result = hex - 'A' + 10;
|
|
if (lower)
|
|
result = hex - 'a' + 10;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_NibbleFromU8Result DN_NibbleFromU8(DN_U8 u8)
|
|
{
|
|
static char const *table = "0123456789abcdef";
|
|
DN_U8 lhs = (u8 >> 0) & 0xF;
|
|
DN_U8 rhs = (u8 >> 4) & 0xF;
|
|
DN_NibbleFromU8Result result = {};
|
|
result.nibble0 = table[rhs];
|
|
result.nibble1 = table[lhs];
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_USize DN_BytesFromHex(DN_Str8 hex, void *dest, DN_USize dest_count)
|
|
{
|
|
DN_Str8 hex_trimmed = DN_Str8TrimHexPrefix(hex);
|
|
DN_USize result = 0;
|
|
if (hex_trimmed.size > (dest_count * 2))
|
|
return result;
|
|
|
|
DN_U8 *ptr = DN_Cast(DN_U8 *) dest;
|
|
for (DN_USize index = 0; index < hex_trimmed.size; index += 2) {
|
|
DN_U8 nibble0 = DN_U8FromHexNibble(hex_trimmed.data[index + 0]);
|
|
DN_U8 nibble1 = DN_U8FromHexNibble(hex_trimmed.data[index + 1]);
|
|
if (nibble0 == 0xFF || nibble1 == 0xFF)
|
|
return result;
|
|
*ptr++ = nibble0 << 4 | nibble1 << 0;
|
|
result++;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_BytesFromHexArena(DN_Str8 hex, DN_Arena *arena)
|
|
{
|
|
DN_Str8 result = DN_BytesFromHexPtrArena(hex.data, hex.size, arena);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_USize DN_BytesFromHexPtr(char const *hex, DN_USize hex_count, void *dest, DN_USize dest_count)
|
|
{
|
|
DN_USize result = DN_BytesFromHex(DN_Str8FromPtr(hex, hex_count), dest, dest_count);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_BytesFromHexPtrArena(char const *hex, DN_USize hex_count, DN_Arena *arena)
|
|
{
|
|
DN_Str8 hex_trimmed = DN_Str8TrimHexPrefix(DN_Str8FromPtr(hex, hex_count));
|
|
DN_Assert(hex_trimmed.size % 2 == 0);
|
|
DN_Str8 result = {};
|
|
result.data = DN_ArenaNewArray(arena, char, hex_trimmed.size / 2, DN_ZMem_No);
|
|
if (result.data)
|
|
result.size = DN_BytesFromHex(hex_trimmed, result.data, hex_trimmed.size / 2);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_BytesFromHexPtrPool(char const *hex, DN_USize hex_count, DN_Pool *pool)
|
|
{
|
|
DN_Str8 hex_trimmed = DN_Str8TrimHexPrefix(DN_Str8FromPtr(hex, hex_count));
|
|
DN_Assert(hex_trimmed.size % 2 == 0);
|
|
DN_Str8 result = {};
|
|
result.data = DN_PoolNewArray(pool, char, hex_trimmed.size / 2);
|
|
if (result.data)
|
|
result.size = DN_BytesFromHex(hex_trimmed, result.data, hex_trimmed.size / 2);
|
|
return result;
|
|
}
|
|
|
|
|
|
DN_API DN_U8x16 DN_BytesFromHex32Ptr(char const *hex, DN_USize hex_count)
|
|
{
|
|
DN_U8x16 result = {};
|
|
DN_Str8 hex_trimmed = DN_Str8TrimHexPrefix(DN_Str8FromPtr(hex, hex_count));
|
|
DN_Assert(hex_trimmed.size / 2 == sizeof result.data);
|
|
DN_USize bytes_written = DN_BytesFromHex(hex_trimmed, result.data, sizeof result.data);
|
|
DN_Assert(bytes_written == sizeof result.data);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_U8x32 DN_BytesFromHex64Ptr(char const *hex, DN_USize hex_count)
|
|
{
|
|
DN_U8x32 result = {};
|
|
DN_Str8 hex_trimmed = DN_Str8TrimHexPrefix(DN_Str8FromPtr(hex, hex_count));
|
|
DN_Assert(hex_trimmed.size / 2 == sizeof result.data);
|
|
DN_USize bytes_written = DN_BytesFromHex(hex_trimmed, result.data, sizeof result.data);
|
|
DN_Assert(bytes_written == sizeof result.data);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_HexU64Str8 DN_HexFromU64(DN_U64 value, DN_HexFromU64Type type)
|
|
{
|
|
DN_HexU64Str8 result = {};
|
|
DN_HexFromPtrBytes(&value, sizeof(value), result.data, sizeof(result.data), DN_TrimLeadingZero_No);
|
|
if (type == DN_HexFromU64Type_Uppercase) {
|
|
for (DN_USize index = 0; index < result.size; index++)
|
|
result.data[index] = DN_CharToUpper(result.data[index]);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_USize DN_HexFromPtrBytes(void const *bytes, DN_USize bytes_count, void *hex, DN_USize hex_count, DN_TrimLeadingZero trim_leading_z)
|
|
{
|
|
DN_USize result = 0;
|
|
if ((bytes_count * 2) > hex_count)
|
|
return result;
|
|
DN_U8 const *src_u8 = DN_Cast(DN_U8 const *) bytes;
|
|
DN_U8 *ptr = DN_Cast(DN_U8 *) hex;
|
|
bool leading_zeros = true;
|
|
for (DN_USize index = 0; index < bytes_count; index++) {
|
|
char ch = src_u8[index];
|
|
if (leading_zeros)
|
|
leading_zeros = ch == 0;
|
|
|
|
if (leading_zeros) {
|
|
if (trim_leading_z == DN_TrimLeadingZero_Yes && ch == 0)
|
|
continue;
|
|
}
|
|
|
|
DN_NibbleFromU8Result to_nibbles = DN_NibbleFromU8(ch);
|
|
*ptr++ = to_nibbles.nibble0;
|
|
*ptr++ = to_nibbles.nibble1;
|
|
result += 2;
|
|
}
|
|
|
|
if (result == 0) {
|
|
*ptr = '0';
|
|
result++;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_HexFromPtrBytesArena(void const *bytes, DN_USize bytes_count, DN_Arena *arena, DN_TrimLeadingZero trim_leading_z)
|
|
{
|
|
DN_Str8 result = {};
|
|
if (bytes_count) {
|
|
result.data = DN_ArenaNewArray(arena, char, bytes_count * 2, DN_ZMem_No);
|
|
if (result.data)
|
|
result.size = DN_HexFromPtrBytes(bytes, bytes_count, result.data, bytes_count * 2, trim_leading_z);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_USize DN_HexFromStr8Bytes(DN_Str8 bytes, void *hex, DN_USize hex_count, DN_TrimLeadingZero trim_leading_z)
|
|
{
|
|
DN_USize result = DN_HexFromPtrBytes(bytes.data, bytes.size, hex, hex_count, trim_leading_z);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Hex32 DN_Hex32FromPtr16b(void const *bytes, DN_USize bytes_count, DN_TrimLeadingZero trim_leading_z)
|
|
{
|
|
DN_Hex32 result = {};
|
|
DN_Assert(bytes_count * 2 == sizeof result.data - 1);
|
|
result.size = DN_HexFromPtrBytes(bytes, bytes_count, result.data, sizeof result.data, trim_leading_z);
|
|
DN_Assert(result.size <= sizeof result.data - 1);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Hex64 DN_Hex64FromPtr32b(void const *bytes, DN_USize bytes_count, DN_TrimLeadingZero trim_leading_z)
|
|
{
|
|
DN_Hex64 result = {};
|
|
DN_Assert(bytes_count * 2 == sizeof result.data - 1);
|
|
result.size = DN_HexFromPtrBytes(bytes, bytes_count, result.data, sizeof result.data, trim_leading_z);
|
|
DN_Assert(result.size <= sizeof result.data - 1);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Hex128 DN_Hex128FromPtr64b(void const *bytes, DN_USize bytes_count, DN_TrimLeadingZero trim_leading_z)
|
|
{
|
|
DN_Hex128 result = {};
|
|
DN_Assert(bytes_count * 2 == sizeof result.data - 1);
|
|
result.size = DN_HexFromPtrBytes(bytes, bytes_count, result.data, sizeof result.data, trim_leading_z);
|
|
DN_Assert(result.size <= sizeof result.data - 1);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8x128 DN_AgeStr8FromMsU64(DN_U64 duration_ms, DN_AgeUnit units)
|
|
{
|
|
DN_Str8x128 result = {};
|
|
DN_U64 remainder_ms = duration_ms;
|
|
if (units & DN_AgeUnit_FractionalSec) {
|
|
units |= DN_AgeUnit_Sec;
|
|
units &= ~DN_AgeUnit_Ms;
|
|
}
|
|
|
|
DN_Str8 unit_suffix = {};
|
|
if (units & DN_AgeUnit_Year) {
|
|
unit_suffix = DN_Str8Lit("y");
|
|
DN_USize value_usize = remainder_ms / (DN_SecFromYears(1) * 1000);
|
|
remainder_ms -= DN_SecFromYears(value_usize) * 1000;
|
|
if (value_usize)
|
|
DN_FmtAppend(result.data, &result.size, sizeof(result.data), "%s%zu%.*s", result.size ? " " : "", value_usize, DN_Str8PrintFmt(unit_suffix));
|
|
}
|
|
|
|
if (units & DN_AgeUnit_Week) {
|
|
unit_suffix = DN_Str8Lit("w");
|
|
DN_USize value_usize = remainder_ms / (DN_SecFromWeeks(1) * 1000);
|
|
remainder_ms -= DN_SecFromWeeks(value_usize) * 1000;
|
|
if (value_usize)
|
|
DN_FmtAppend(result.data, &result.size, sizeof(result.data), "%s%zu%.*s", result.size ? " " : "", value_usize, DN_Str8PrintFmt(unit_suffix));
|
|
}
|
|
|
|
if (units & DN_AgeUnit_Day) {
|
|
unit_suffix = DN_Str8Lit("d");
|
|
DN_USize value_usize = remainder_ms / (DN_SecFromDays(1) * 1000);
|
|
remainder_ms -= DN_SecFromDays(value_usize) * 1000;
|
|
if (value_usize)
|
|
DN_FmtAppend(result.data, &result.size, sizeof(result.data), "%s%zu%.*s", result.size ? " " : "", value_usize, DN_Str8PrintFmt(unit_suffix));
|
|
}
|
|
|
|
if (units & DN_AgeUnit_Hr) {
|
|
unit_suffix = DN_Str8Lit("h");
|
|
DN_USize value_usize = remainder_ms / (DN_SecFromHours(1) * 1000);
|
|
remainder_ms -= DN_SecFromHours(value_usize) * 1000;
|
|
if (value_usize)
|
|
DN_FmtAppend(result.data, &result.size, sizeof(result.data), "%s%zu%.*s", result.size ? " " : "", value_usize, DN_Str8PrintFmt(unit_suffix));
|
|
}
|
|
|
|
if (units & DN_AgeUnit_Min) {
|
|
unit_suffix = DN_Str8Lit("m");
|
|
DN_USize value_usize = remainder_ms / (DN_SecFromMins(1) * 1000);
|
|
remainder_ms -= DN_SecFromMins(value_usize) * 1000;
|
|
if (value_usize)
|
|
DN_FmtAppend(result.data, &result.size, sizeof(result.data), "%s%zu%.*s", result.size ? " " : "", value_usize, DN_Str8PrintFmt(unit_suffix));
|
|
}
|
|
|
|
if (units & DN_AgeUnit_Sec) {
|
|
unit_suffix = DN_Str8Lit("s");
|
|
if (units & DN_AgeUnit_FractionalSec) {
|
|
DN_F64 remainder_s = remainder_ms / 1000.0;
|
|
DN_FmtAppend(result.data, &result.size, sizeof(result.data), "%s%.3f%.*s", result.size ? " " : "", remainder_s, DN_Str8PrintFmt(unit_suffix));
|
|
remainder_ms = 0;
|
|
} else {
|
|
DN_USize value_usize = remainder_ms / 1000;
|
|
remainder_ms -= DN_Cast(DN_USize)(value_usize * 1000);
|
|
if (value_usize)
|
|
DN_FmtAppend(result.data, &result.size, sizeof(result.data), "%s%zu%.*s", result.size ? " " : "", value_usize, DN_Str8PrintFmt(unit_suffix));
|
|
}
|
|
}
|
|
|
|
if (units & DN_AgeUnit_Ms) {
|
|
unit_suffix = DN_Str8Lit("ms");
|
|
DN_Assert((units & DN_AgeUnit_FractionalSec) == 0);
|
|
DN_USize value_usize = remainder_ms;
|
|
remainder_ms -= value_usize;
|
|
if (value_usize || result.size == 0)
|
|
DN_FmtAppend(result.data, &result.size, sizeof(result.data), "%s%zu%.*s", result.size ? " " : "", value_usize, DN_Str8PrintFmt(unit_suffix));
|
|
}
|
|
|
|
if (result.size == 0)
|
|
DN_FmtAppend(result.data, &result.size, sizeof(result.data), "0%.*s", DN_Str8PrintFmt(unit_suffix));
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8x128 DN_AgeStr8FromSecU64(DN_U64 duration_s, DN_AgeUnit units)
|
|
{
|
|
DN_U64 duration_ms = duration_s * 1000;
|
|
DN_Str8x128 result = DN_AgeStr8FromMsU64(duration_ms, units);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8x128 DN_AgeStr8FromSecF64(DN_F64 duration_s, DN_AgeUnit units)
|
|
{
|
|
DN_U64 duration_ms = DN_Cast(DN_U64)(duration_s * 1000.0);
|
|
DN_Str8x128 result = DN_AgeStr8FromMsU64(duration_ms, units);
|
|
return result;
|
|
}
|
|
|
|
DN_API int DN_IsLeapYear(int year)
|
|
{
|
|
if (year % 4 != 0)
|
|
return 0;
|
|
if (year % 100 != 0)
|
|
return 1;
|
|
return (year % 400 == 0);
|
|
}
|
|
|
|
DN_API bool DN_DateIsValid(DN_Date date)
|
|
{
|
|
if (date.year < 1970)
|
|
return false;
|
|
if (date.month <= 0 || date.month >= 13)
|
|
return false;
|
|
if (date.day <= 0 || date.day >= 32)
|
|
return false;
|
|
if (date.hour >= 24)
|
|
return false;
|
|
if (date.minutes >= 60)
|
|
return false;
|
|
if (date.seconds >= 60)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
DN_API DN_Date DN_DateFromUnixTimeMs(DN_USize unix_ts_ms)
|
|
{
|
|
DN_Date result = {};
|
|
DN_USize ms = unix_ts_ms % 1000;
|
|
DN_USize total_seconds = unix_ts_ms / 1000;
|
|
result.milliseconds = (DN_U16)ms;
|
|
|
|
DN_USize secs_in_day = total_seconds % 86400;
|
|
DN_USize days = total_seconds / 86400;
|
|
|
|
result.hour = (DN_U8)(secs_in_day / 3600);
|
|
result.minutes = (DN_U8)((secs_in_day % 3600) / 60);
|
|
result.seconds = (DN_U8)(secs_in_day % 60);
|
|
|
|
DN_U16 days_in_month[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
|
DN_USize days_left = days;
|
|
DN_U16 year = 1970;
|
|
|
|
while (days_left >= (DN_IsLeapYear(year) ? 366 : 365)) {
|
|
DN_USize days_in_year = DN_IsLeapYear(year) ? 366 : 365;
|
|
days_left -= days_in_year;
|
|
year++;
|
|
}
|
|
|
|
DN_U8 month = 1;
|
|
for (;;) {
|
|
DN_U16 day_count = days_in_month[month];
|
|
if (month == 2 && DN_IsLeapYear(year))
|
|
day_count = 29;
|
|
if (days_left < day_count)
|
|
break;
|
|
days_left -= day_count;
|
|
month++;
|
|
}
|
|
|
|
result.year = year;
|
|
result.month = month;
|
|
result.day = (DN_U8)days_left + 1;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_U64 DN_UnixTimeMsFromDate(DN_Date date)
|
|
{
|
|
DN_Assert(DN_DateIsValid(date));
|
|
|
|
// Precomputed cumulative days before each month (non-leap year)
|
|
const DN_U16 days_before_month[13] = {
|
|
0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365};
|
|
|
|
DN_U16 y = date.year;
|
|
DN_U8 m = date.month;
|
|
DN_U8 d = date.day;
|
|
|
|
DN_U32 days = d - 1; // day of month starts at 0 internally
|
|
days += days_before_month[m - 1]; // Add days from previous months this year
|
|
|
|
if (m > 2 && DN_IsLeapYear(y)) // Add February 29 if leap year and month > 2
|
|
days += 1;
|
|
|
|
// Add full years from 1970 to y-1
|
|
for (DN_U16 year = 1970; year < y; ++year)
|
|
days += DN_IsLeapYear(year) ? 366 : 365;
|
|
|
|
// Convert to seconds
|
|
DN_U64 seconds = DN_Cast(DN_U64)days * 86400ULL;
|
|
seconds += DN_Cast(DN_U64)date.hour * 3600ULL;
|
|
seconds += DN_Cast(DN_U64)date.minutes * 60ULL;
|
|
seconds += DN_Cast(DN_U64)date.seconds;
|
|
DN_U64 result = seconds * 1000ULL + date.milliseconds;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8FromByteCountType(DN_ByteCountType type)
|
|
{
|
|
DN_Str8 result = DN_Str8Lit("");
|
|
switch (type) {
|
|
case DN_ByteCountType_B: result = DN_Str8Lit("B"); break;
|
|
case DN_ByteCountType_KiB: result = DN_Str8Lit("KiB"); break;
|
|
case DN_ByteCountType_MiB: result = DN_Str8Lit("MiB"); break;
|
|
case DN_ByteCountType_GiB: result = DN_Str8Lit("GiB"); break;
|
|
case DN_ByteCountType_TiB: result = DN_Str8Lit("TiB"); break;
|
|
case DN_ByteCountType_Count: result = DN_Str8Lit(""); break;
|
|
case DN_ByteCountType_Auto: result = DN_Str8Lit(""); break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_ByteCountResult DN_ByteCountFromType(DN_U64 bytes, DN_ByteCountType type)
|
|
{
|
|
DN_Assert(type != DN_ByteCountType_Count);
|
|
DN_ByteCountResult result = {};
|
|
result.bytes = DN_Cast(DN_F64) bytes;
|
|
if (type == DN_ByteCountType_Auto)
|
|
for (; result.type < DN_ByteCountType_Count && result.bytes >= 1024.0; result.type = DN_Cast(DN_ByteCountType)(DN_Cast(DN_USize) result.type + 1))
|
|
result.bytes /= 1024.0;
|
|
else
|
|
for (; result.type < type; result.type = DN_Cast(DN_ByteCountType)(DN_Cast(DN_USize) result.type + 1))
|
|
result.bytes /= 1024.0;
|
|
result.suffix = DN_Str8FromByteCountType(result.type);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8x32 DN_ByteCountStr8x32FromType(DN_U64 bytes, DN_ByteCountType type)
|
|
{
|
|
DN_ByteCountResult byte_count = DN_ByteCountFromType(bytes, type);
|
|
DN_Str8x32 result = DN_Str8x32FromFmt("%.2f%.*s", byte_count.bytes, DN_Str8PrintFmt(byte_count.suffix));
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Profiler DN_ProfilerInit(DN_ProfilerAnchor *anchors, DN_USize count, DN_USize anchors_per_frame, DN_ProfilerTSCNowFunc *tsc_now, DN_U64 tsc_frequency)
|
|
{
|
|
DN_Profiler result = {};
|
|
result.anchors = anchors;
|
|
result.anchors_count = count;
|
|
result.anchors_per_frame = anchors_per_frame;
|
|
result.tsc_now = tsc_now;
|
|
result.tsc_frequency = tsc_frequency;
|
|
|
|
DN_AssertF(result.tsc_frequency != 0,
|
|
"You must set this to the frequency of the timestamp counter function (TSC) (e.g. how "
|
|
"many ticks occur between timestamps). We use this to determine the duration between "
|
|
"each zone's recorded TSC. For example if the 'tsc_now' was set to Window's "
|
|
"QueryPerformanceCounter then 'tsc_frequency' would be set to the value of "
|
|
"QueryPerformanceFrequency which is typically 10mhz (e.g. The duration between two "
|
|
"consecutive TSC's is 10mhz)."
|
|
""
|
|
"Hence frequency can't be zero otherwise it's a divide by 0. If you don't have a TSC "
|
|
"function and pass in null, the profiler defaults to rdtsc() and you must measure the "
|
|
"frequency of rdtsc yourself. The reason for this is that measuring rdtsc requires "
|
|
"having some alternate timing mechanism to measure the duration between the TSCs "
|
|
"provided by rdtsc and this profiler makes no assumption about what timing primitives "
|
|
"are available other than rdtsc which is a CPU builtin available on basically all "
|
|
"platforms or have an equivalent (e.g. __builtin_readcyclecounter)"
|
|
""
|
|
"This codebase provides DN_OS_EstimateTSCPerSecond() as an example of how to that for "
|
|
"convenience and is available if compiling with the OS layer. Some platforms like "
|
|
"Emscripten don't support rdtsc() so you should use an alternative method like "
|
|
"emscripten_get_now() or clock_gettime with CLOCK_MONOTONIC.");
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_USize DN_ProfilerFrameCount(DN_Profiler const *profiler)
|
|
{
|
|
DN_USize result = profiler ? profiler->anchors_count / profiler->anchors_per_frame : 0;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_ProfilerAnchorArray DN_ProfilerFrameAnchorsFromIndex(DN_Profiler *profiler, DN_USize frame_index)
|
|
{
|
|
DN_ProfilerAnchorArray result = {};
|
|
DN_USize anchor_offset = frame_index * profiler->anchors_per_frame;
|
|
result.data = profiler->anchors + anchor_offset;
|
|
result.count = profiler->anchors_per_frame;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_ProfilerAnchorArray DN_ProfilerFrameAnchors(DN_Profiler *profiler)
|
|
{
|
|
DN_ProfilerAnchorArray result = DN_ProfilerFrameAnchorsFromIndex(profiler, profiler->frame_index);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_ProfilerZone DN_ProfilerBeginZone(DN_Profiler *profiler, DN_Str8 name, DN_U16 anchor_index)
|
|
{
|
|
DN_ProfilerZone result = {};
|
|
if (!profiler || profiler->paused)
|
|
return result;
|
|
|
|
DN_Assert(anchor_index < profiler->anchors_per_frame);
|
|
DN_ProfilerAnchor *anchor = DN_ProfilerFrameAnchors(profiler).data + anchor_index;
|
|
anchor->name = name;
|
|
|
|
// TODO: We need per-thread-local-storage profiler so that we can use these apis
|
|
// across threads. For now, we let them overwrite each other but this is not tenable.
|
|
#if 0
|
|
if (anchor->name.size && anchor->name != name)
|
|
DN_AssertF(name == anchor->name, "Potentially overwriting a zone by accident? Anchor is '%.*s', name is '%.*s'", DN_Str8PrintFmt(anchor->name), DN_Str8PrintFmt(name));
|
|
#endif
|
|
|
|
result.begin_tsc = profiler->tsc_now ? profiler->tsc_now() : DN_CPUGetTSC();
|
|
result.anchor_index = anchor_index;
|
|
result.parent_zone = profiler->parent_zone;
|
|
result.elapsed_tsc_at_zone_start = anchor->tsc_inclusive;
|
|
profiler->parent_zone = anchor_index;
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_ProfilerEndZone(DN_Profiler *profiler, DN_ProfilerZone zone)
|
|
{
|
|
if (!profiler || profiler->paused)
|
|
return;
|
|
|
|
DN_Assert(zone.anchor_index < profiler->anchors_per_frame);
|
|
DN_Assert(zone.parent_zone < profiler->anchors_per_frame);
|
|
|
|
DN_ProfilerAnchorArray array = DN_ProfilerFrameAnchors(profiler);
|
|
DN_ProfilerAnchor *anchor = array.data + zone.anchor_index;
|
|
DN_U64 tsc_now = profiler->tsc_now ? profiler->tsc_now() : DN_CPUGetTSC();
|
|
DN_U64 elapsed_tsc = tsc_now - zone.begin_tsc;
|
|
|
|
anchor->hit_count++;
|
|
anchor->tsc_inclusive = zone.elapsed_tsc_at_zone_start + elapsed_tsc;
|
|
anchor->tsc_exclusive += elapsed_tsc;
|
|
|
|
if (zone.parent_zone != zone.anchor_index) {
|
|
DN_ProfilerAnchor *parent_anchor = array.data + zone.parent_zone;
|
|
parent_anchor->tsc_exclusive -= elapsed_tsc;
|
|
}
|
|
profiler->parent_zone = zone.parent_zone;
|
|
}
|
|
|
|
DN_API void DN_ProfilerNewFrame(DN_Profiler *profiler)
|
|
{
|
|
if (!profiler || profiler->paused)
|
|
return;
|
|
|
|
// NOTE: End the frame's zone
|
|
DN_ProfilerEndZone(profiler, profiler->frame_zone);
|
|
DN_ProfilerAnchorArray old_frame_anchors = DN_ProfilerFrameAnchors(profiler);
|
|
DN_ProfilerAnchor old_frame_anchor = old_frame_anchors.data[0];
|
|
profiler->frame_avg_tsc = (profiler->frame_avg_tsc + old_frame_anchor.tsc_inclusive) / 2.f;
|
|
|
|
// NOTE: Bump to the next frame
|
|
DN_USize frame_count = profiler->anchors_count / profiler->anchors_per_frame;
|
|
profiler->frame_index = (profiler->frame_index + 1) % frame_count;
|
|
|
|
// NOTE: Zero out the anchors
|
|
DN_ProfilerAnchorArray next_anchors = DN_ProfilerFrameAnchors(profiler);
|
|
DN_Memset(next_anchors.data, 0, sizeof(*profiler->anchors) * next_anchors.count);
|
|
|
|
// NOTE: Start the frame's zone
|
|
profiler->frame_zone = DN_ProfilerBeginZone(profiler, DN_Str8Lit("Profiler Frame"), 0);
|
|
}
|
|
|
|
DN_API void DN_ProfilerDump(DN_Profiler *profiler)
|
|
{
|
|
if (!profiler || profiler->frame_index == 0)
|
|
return;
|
|
|
|
DN_USize frame_index = profiler->frame_index - 1;
|
|
DN_Assert(profiler->frame_index < profiler->anchors_per_frame);
|
|
|
|
DN_ProfilerAnchor *anchors = profiler->anchors + (frame_index * profiler->anchors_per_frame);
|
|
for (DN_USize index = 1; index < profiler->anchors_per_frame; index++) {
|
|
DN_ProfilerAnchor const *anchor = anchors + index;
|
|
if (!anchor->hit_count)
|
|
continue;
|
|
|
|
DN_U64 tsc_exclusive = anchor->tsc_exclusive;
|
|
DN_U64 tsc_inclusive = anchor->tsc_inclusive;
|
|
DN_F64 tsc_exclusive_milliseconds = tsc_exclusive * 1000 / DN_Cast(DN_F64) profiler->tsc_frequency;
|
|
if (tsc_exclusive == tsc_inclusive) {
|
|
DN_OS_PrintOutLnF("%.*s[%u]: %.1fms", DN_Str8PrintFmt(anchor->name), anchor->hit_count, tsc_exclusive_milliseconds);
|
|
} else {
|
|
DN_F64 tsc_inclusive_milliseconds = tsc_inclusive * 1000 / DN_Cast(DN_F64) profiler->tsc_frequency;
|
|
DN_OS_PrintOutLnF("%.*s[%u]: %.1f/%.1fms",
|
|
DN_Str8PrintFmt(anchor->name),
|
|
anchor->hit_count,
|
|
tsc_exclusive_milliseconds,
|
|
tsc_inclusive_milliseconds);
|
|
}
|
|
}
|
|
}
|
|
|
|
DN_API DN_F64 DN_ProfilerSecFromTSC(DN_Profiler *profiler, DN_U64 duration_tsc)
|
|
{
|
|
DN_F64 result = DN_Cast(DN_F64)duration_tsc / profiler->tsc_frequency;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_F64 DN_ProfilerMsFromTSC(DN_Profiler *profiler, DN_U64 duration_tsc)
|
|
{
|
|
DN_F64 result = DN_Cast(DN_F64)duration_tsc / profiler->tsc_frequency * 1000.0;
|
|
return result;
|
|
}
|
|
|
|
#define DN_PCG_DEFAULT_MULTIPLIER_64 6364136223846793005ULL
|
|
#define DN_PCG_DEFAULT_INCREMENT_64 1442695040888963407ULL
|
|
DN_API DN_PCG32 DN_PCG32Init(DN_U64 seed)
|
|
{
|
|
DN_PCG32 result = {};
|
|
DN_PCG32Next(&result);
|
|
result.state += seed;
|
|
DN_PCG32Next(&result);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_U32 DN_PCG32Next(DN_PCG32 *rng)
|
|
{
|
|
DN_U64 state = rng->state;
|
|
rng->state = state * DN_PCG_DEFAULT_MULTIPLIER_64 + DN_PCG_DEFAULT_INCREMENT_64;
|
|
|
|
// XSH-RR
|
|
DN_U32 value = (DN_U32)((state ^ (state >> 18)) >> 27);
|
|
int rot = state >> 59;
|
|
return rot ? (value >> rot) | (value << (32 - rot)) : value;
|
|
}
|
|
|
|
DN_API DN_U64 DN_PCG32Next64(DN_PCG32 *rng)
|
|
{
|
|
DN_U64 value = DN_PCG32Next(rng);
|
|
value <<= 32;
|
|
value |= DN_PCG32Next(rng);
|
|
return value;
|
|
}
|
|
|
|
DN_API DN_U32 DN_PCG32Range(DN_PCG32 *rng, DN_U32 low, DN_U32 high)
|
|
{
|
|
DN_U32 bound = high - low;
|
|
DN_U32 threshold = -(DN_I32)bound % bound;
|
|
|
|
for (;;) {
|
|
DN_U32 r = DN_PCG32Next(rng);
|
|
if (r >= threshold)
|
|
return low + (r % bound);
|
|
}
|
|
}
|
|
|
|
DN_API DN_F32 DN_PCG32NextF32(DN_PCG32 *rng)
|
|
{
|
|
DN_U32 x = DN_PCG32Next(rng);
|
|
return (DN_F32)(DN_I32)(x >> 8) * 0x1.0p-24f;
|
|
}
|
|
|
|
DN_API DN_F64 DN_PCG32NextF64(DN_PCG32 *rng)
|
|
{
|
|
DN_U64 x = DN_PCG32Next64(rng);
|
|
return (DN_F64)(int64_t)(x >> 11) * 0x1.0p-53;
|
|
}
|
|
|
|
DN_API void DN_PCG32Advance(DN_PCG32 *rng, DN_U64 delta)
|
|
{
|
|
DN_U64 cur_mult = DN_PCG_DEFAULT_MULTIPLIER_64;
|
|
DN_U64 cur_plus = DN_PCG_DEFAULT_INCREMENT_64;
|
|
|
|
DN_U64 acc_mult = 1;
|
|
DN_U64 acc_plus = 0;
|
|
|
|
while (delta != 0) {
|
|
if (delta & 1) {
|
|
acc_mult *= cur_mult;
|
|
acc_plus = acc_plus * cur_mult + cur_plus;
|
|
}
|
|
cur_plus = (cur_mult + 1) * cur_plus;
|
|
cur_mult *= cur_mult;
|
|
delta >>= 1;
|
|
}
|
|
|
|
rng->state = acc_mult * rng->state + acc_plus;
|
|
}
|
|
|
|
// Default values recommended by: http://isthe.com/chongo/tech/comp/fnv/
|
|
DN_API DN_U32 DN_FNV1AHashU32FromBytes(void const *bytes, DN_USize size, DN_U32 hash)
|
|
{
|
|
auto buffer = DN_Cast(DN_U8 const *)bytes;
|
|
for (DN_USize i = 0; i < size; i++)
|
|
hash = (buffer[i] ^ hash) * 16777619 /*FNV Prime*/;
|
|
return hash;
|
|
}
|
|
|
|
DN_API DN_U64 DN_FNV1AHashU64FromBytes(void const *bytes, DN_USize size, DN_U64 hash)
|
|
{
|
|
auto buffer = DN_Cast(DN_U8 const *)bytes;
|
|
for (DN_USize i = 0; i < size; i++)
|
|
hash = (buffer[i] ^ hash) * 1099511628211 /*FNV Prime*/;
|
|
return hash;
|
|
}
|
|
|
|
#if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL)
|
|
#define DN_MMH3_ROTL32(x, y) _rotl(x, y)
|
|
#define DN_MMH3_ROTL64(x, y) _rotl64(x, y)
|
|
#else
|
|
#define DN_MMH3_ROTL32(x, y) ((x) << (y)) | ((x) >> (32 - (y)))
|
|
#define DN_MMH3_ROTL64(x, y) ((x) << (y)) | ((x) >> (64 - (y)))
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Block read - if your platform needs to do endian-swapping or can only
|
|
// handle aligned reads, do the conversion here
|
|
DN_FORCE_INLINE DN_U32 DN_MurmurHash3GetBlock32_(DN_U32 const *p, int i)
|
|
{
|
|
return p[i];
|
|
}
|
|
|
|
DN_FORCE_INLINE DN_U64 DN_MurmurHash3GetBlock64_(DN_U64 const *p, int i)
|
|
{
|
|
return p[i];
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Finalization mix - force all bits of a hash block to avalanche
|
|
|
|
DN_FORCE_INLINE DN_U32 DN_MurmurHash3FMix32_(DN_U32 h)
|
|
{
|
|
h ^= h >> 16;
|
|
h *= 0x85ebca6b;
|
|
h ^= h >> 13;
|
|
h *= 0xc2b2ae35;
|
|
h ^= h >> 16;
|
|
return h;
|
|
}
|
|
|
|
DN_FORCE_INLINE DN_U64 DN_MurmurHash3FMix64_(DN_U64 k)
|
|
{
|
|
k ^= k >> 33;
|
|
k *= 0xff51afd7ed558ccd;
|
|
k ^= k >> 33;
|
|
k *= 0xc4ceb9fe1a85ec53;
|
|
k ^= k >> 33;
|
|
return k;
|
|
}
|
|
|
|
DN_API DN_U32 DN_MurmurHash3HashU128FromBytesX86(void const *bytes, int len, DN_U32 seed)
|
|
{
|
|
const DN_U8 *data = (const DN_U8 *)bytes;
|
|
const int nblocks = len / 4;
|
|
|
|
DN_U32 h1 = seed;
|
|
|
|
const DN_U32 c1 = 0xcc9e2d51;
|
|
const DN_U32 c2 = 0x1b873593;
|
|
|
|
//----------
|
|
// body
|
|
|
|
const DN_U32 *blocks = (const DN_U32 *)(data + nblocks * 4);
|
|
|
|
for (int i = -nblocks; i; i++)
|
|
{
|
|
DN_U32 k1 = DN_MurmurHash3GetBlock32_(blocks, i);
|
|
|
|
k1 *= c1;
|
|
k1 = DN_MMH3_ROTL32(k1, 15);
|
|
k1 *= c2;
|
|
|
|
h1 ^= k1;
|
|
h1 = DN_MMH3_ROTL32(h1, 13);
|
|
h1 = h1 * 5 + 0xe6546b64;
|
|
}
|
|
|
|
//----------
|
|
// tail
|
|
|
|
const DN_U8 *tail = (const DN_U8 *)(data + nblocks * 4);
|
|
|
|
DN_U32 k1 = 0;
|
|
|
|
switch (len & 3)
|
|
{
|
|
case 3:
|
|
k1 ^= tail[2] << 16;
|
|
case 2:
|
|
k1 ^= tail[1] << 8;
|
|
case 1:
|
|
k1 ^= tail[0];
|
|
k1 *= c1;
|
|
k1 = DN_MMH3_ROTL32(k1, 15);
|
|
k1 *= c2;
|
|
h1 ^= k1;
|
|
};
|
|
|
|
//----------
|
|
// finalization
|
|
|
|
h1 ^= len;
|
|
|
|
h1 = DN_MurmurHash3FMix32_(h1);
|
|
|
|
return h1;
|
|
}
|
|
|
|
DN_API DN_MurmurHash3 DN_MurmurHash3HashU128FromBytesX64(void const *bytes, int len, DN_U32 seed)
|
|
{
|
|
const DN_U8 *data = (const DN_U8 *)bytes;
|
|
const int nblocks = len / 16;
|
|
|
|
DN_U64 h1 = seed;
|
|
DN_U64 h2 = seed;
|
|
|
|
const DN_U64 c1 = 0x87c37b91114253d5;
|
|
const DN_U64 c2 = 0x4cf5ad432745937f;
|
|
|
|
//----------
|
|
// body
|
|
|
|
const DN_U64 *blocks = (const DN_U64 *)(data);
|
|
|
|
for (int i = 0; i < nblocks; i++)
|
|
{
|
|
DN_U64 k1 = DN_MurmurHash3GetBlock64_(blocks, i * 2 + 0);
|
|
DN_U64 k2 = DN_MurmurHash3GetBlock64_(blocks, i * 2 + 1);
|
|
|
|
k1 *= c1;
|
|
k1 = DN_MMH3_ROTL64(k1, 31);
|
|
k1 *= c2;
|
|
h1 ^= k1;
|
|
|
|
h1 = DN_MMH3_ROTL64(h1, 27);
|
|
h1 += h2;
|
|
h1 = h1 * 5 + 0x52dce729;
|
|
|
|
k2 *= c2;
|
|
k2 = DN_MMH3_ROTL64(k2, 33);
|
|
k2 *= c1;
|
|
h2 ^= k2;
|
|
|
|
h2 = DN_MMH3_ROTL64(h2, 31);
|
|
h2 += h1;
|
|
h2 = h2 * 5 + 0x38495ab5;
|
|
}
|
|
|
|
//----------
|
|
// tail
|
|
|
|
const DN_U8 *tail = (const DN_U8 *)(data + nblocks * 16);
|
|
|
|
DN_U64 k1 = 0;
|
|
DN_U64 k2 = 0;
|
|
|
|
switch (len & 15)
|
|
{
|
|
case 15:
|
|
k2 ^= ((DN_U64)tail[14]) << 48;
|
|
case 14:
|
|
k2 ^= ((DN_U64)tail[13]) << 40;
|
|
case 13:
|
|
k2 ^= ((DN_U64)tail[12]) << 32;
|
|
case 12:
|
|
k2 ^= ((DN_U64)tail[11]) << 24;
|
|
case 11:
|
|
k2 ^= ((DN_U64)tail[10]) << 16;
|
|
case 10:
|
|
k2 ^= ((DN_U64)tail[9]) << 8;
|
|
case 9:
|
|
k2 ^= ((DN_U64)tail[8]) << 0;
|
|
k2 *= c2;
|
|
k2 = DN_MMH3_ROTL64(k2, 33);
|
|
k2 *= c1;
|
|
h2 ^= k2;
|
|
|
|
case 8:
|
|
k1 ^= ((DN_U64)tail[7]) << 56;
|
|
case 7:
|
|
k1 ^= ((DN_U64)tail[6]) << 48;
|
|
case 6:
|
|
k1 ^= ((DN_U64)tail[5]) << 40;
|
|
case 5:
|
|
k1 ^= ((DN_U64)tail[4]) << 32;
|
|
case 4:
|
|
k1 ^= ((DN_U64)tail[3]) << 24;
|
|
case 3:
|
|
k1 ^= ((DN_U64)tail[2]) << 16;
|
|
case 2:
|
|
k1 ^= ((DN_U64)tail[1]) << 8;
|
|
case 1:
|
|
k1 ^= ((DN_U64)tail[0]) << 0;
|
|
k1 *= c1;
|
|
k1 = DN_MMH3_ROTL64(k1, 31);
|
|
k1 *= c2;
|
|
h1 ^= k1;
|
|
};
|
|
|
|
//----------
|
|
// finalization
|
|
|
|
h1 ^= len;
|
|
h2 ^= len;
|
|
|
|
h1 += h2;
|
|
h2 += h1;
|
|
|
|
h1 = DN_MurmurHash3FMix64_(h1);
|
|
h2 = DN_MurmurHash3FMix64_(h2);
|
|
|
|
h1 += h2;
|
|
h2 += h1;
|
|
|
|
DN_MurmurHash3 result = {};
|
|
result.e[0] = h1;
|
|
result.e[1] = h2;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_U64 DN_MurmurHash3HashU64FromBytesX64(void const *bytes, int len, DN_U32 seed)
|
|
{
|
|
DN_MurmurHash3 hash = DN_MurmurHash3HashU128FromBytesX64(bytes, len, seed);
|
|
DN_U64 result = hash.e[0];
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_U32 DN_MurmurHash3HashU32FromBytesX64(void const *bytes, int len, DN_U32 seed)
|
|
{
|
|
DN_MurmurHash3 hash = DN_MurmurHash3HashU128FromBytesX64(bytes, len, seed);
|
|
DN_U32 result = DN_Cast(DN_U32)hash.e[0];
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8x32 DN_Str8x32FromANSIColourCodeU8RGB(DN_ANSIColourMode mode, DN_U8 r, DN_U8 g, DN_U8 b)
|
|
{
|
|
DN_Str8x32 result = DN_Str8x32FromFmt("\x1b[%d;2;%u;%u;%um",
|
|
mode == DN_ANSIColourMode_Fg ? 38 : 48,
|
|
r,
|
|
g,
|
|
b);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8x32 DN_Str8x32FromANSIColourCodeV3F32RGB255(DN_ANSIColourMode mode, DN_V3F32 rgb_255)
|
|
{
|
|
DN_Str8x32 result = DN_Str8x32FromANSIColourCodeU8RGB(mode, DN_Cast(DN_U8)rgb_255.r, DN_Cast(DN_U8)rgb_255.g, DN_Cast(DN_U8)rgb_255.b);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8x32 DN_Str8x32FromANSIColourCodeU32RGB(DN_ANSIColourMode mode, DN_U32 value)
|
|
{
|
|
DN_U8 r = DN_Cast(DN_U8)(value >> 24);
|
|
DN_U8 g = DN_Cast(DN_U8)(value >> 16);
|
|
DN_U8 b = DN_Cast(DN_U8)(value >> 8);
|
|
DN_Str8x32 result = DN_Str8x32FromANSIColourCodeU8RGB(mode, r, g, b);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8FromStr8ANSIColourU8RGBArena(DN_ANSIColourMode mode, DN_Str8 str8, DN_U8 r, DN_U8 g, DN_U8 b, DN_Arena *arena)
|
|
{
|
|
DN_Str8x32 ansi = DN_Str8x32FromANSIColourCodeU8RGB(mode, r, g, b);
|
|
DN_Str8 result = DN_Str8FromFmtArena(arena, "%.*s%.*s%s", DN_Str8PrintFmt(ansi), DN_Str8PrintFmt(str8), DN_ANSICodeResetLit);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8FromStr8ANSIColourV3F32RGB255Arena(DN_ANSIColourMode mode, DN_Str8 str8, DN_V3F32 rgb_255, DN_Arena *arena)
|
|
{
|
|
DN_Str8 result = DN_Str8FromStr8ANSIColourU8RGBArena(mode, str8, DN_Cast(DN_U8)rgb_255.r, DN_Cast(DN_U8)rgb_255.g, DN_Cast(DN_U8)rgb_255.b, arena);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8ANSIColourU8RGBFromFmtVArena(DN_ANSIColourMode mode, DN_U8 r, DN_U8 g, DN_U8 b, DN_Arena *arena, char const *fmt, va_list args)
|
|
{
|
|
DN_TCScratch scratch = DN_TCScratchBegin(&arena, 1);
|
|
DN_Str8 string = DN_Str8FromFmtVArena(&scratch.arena, fmt, args);
|
|
DN_Str8 result = DN_Str8FromStr8ANSIColourU8RGBArena(mode, string, r, g, b, arena);
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8FromFmtANSIColourU8RGBArena(DN_ANSIColourMode mode, DN_U8 r, DN_U8 g, DN_U8 b, DN_Arena *arena, char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
DN_Str8 result = DN_Str8ANSIColourU8RGBFromFmtVArena(mode, r, g, b, arena, fmt, args);
|
|
va_end(args);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8FromFmtANSIColourV3F32RGB255Arena(DN_ANSIColourMode mode, DN_V3F32 rgb_255, DN_Arena *arena, char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
DN_Str8 result = DN_Str8ANSIColourU8RGBFromFmtVArena(mode, DN_Cast(DN_U8)rgb_255.r, DN_Cast(DN_U8)rgb_255.g, DN_Cast(DN_U8)rgb_255.b, arena, fmt, args);
|
|
va_end(args);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_LogPrefixSize DN_LogMakePrefix(DN_LogStyle style, DN_LogTypeParam type, DN_CallSite call_site, DN_LogDate date, char *dest, DN_USize dest_size)
|
|
{
|
|
DN_Str8 type_str8 = type.str8;
|
|
if (type.is_u32_enum) {
|
|
switch (type.u32) {
|
|
case DN_LogType_Debug: type_str8 = DN_Str8Lit("DEBUG"); break;
|
|
case DN_LogType_Info: type_str8 = DN_Str8Lit("INFO "); break;
|
|
case DN_LogType_Warning: type_str8 = DN_Str8Lit("WARN"); break;
|
|
case DN_LogType_Error: type_str8 = DN_Str8Lit("ERROR"); break;
|
|
case DN_LogType_Count: type_str8 = DN_Str8Lit("BADXX"); break;
|
|
}
|
|
}
|
|
|
|
static DN_USize max_type_length = 0;
|
|
max_type_length = DN_Max(max_type_length, type_str8.size);
|
|
int type_padding = DN_Cast(int)(max_type_length - type_str8.size);
|
|
|
|
DN_Str8x32 colour_esc = {};
|
|
DN_Str8 bold_esc = {};
|
|
DN_Str8 reset_esc = {};
|
|
if (style.colour) {
|
|
bold_esc = DN_Str8Lit(DN_ANSICodeBoldLit);
|
|
reset_esc = DN_Str8Lit(DN_ANSICodeResetLit);
|
|
colour_esc = DN_Str8x32FromANSIColourCodeU8RGB(DN_ANSIColourMode_Fg, style.r, style.g, style.b);
|
|
}
|
|
|
|
DN_Str8 file_name = DN_Str8FileNameFromPath(call_site.file);
|
|
int size = DN_SNPrintF(dest,
|
|
DN_Cast(int)dest_size,
|
|
"%04u-%02u-%02uT%02u:%02u:%02u" // date
|
|
"%.*s" // colour
|
|
"%.*s" // bold
|
|
" %.*s" // type
|
|
"%.*s" // type padding
|
|
"%.*s" // reset
|
|
" %.*s" // file name
|
|
":%05u " // line number
|
|
,
|
|
date.year,
|
|
date.month,
|
|
date.day,
|
|
date.hour,
|
|
date.minute,
|
|
date.second,
|
|
DN_Str8PrintFmt(colour_esc), // colour
|
|
DN_Str8PrintFmt(bold_esc), // bold
|
|
DN_Str8PrintFmt(type_str8), // type
|
|
DN_Cast(int) type_padding,
|
|
"", // type padding
|
|
DN_Str8PrintFmt(reset_esc), // reset
|
|
DN_Str8PrintFmt(file_name), // file name
|
|
call_site.line); // line number
|
|
|
|
static DN_USize max_header_length = 0;
|
|
DN_USize size_no_ansi_codes = size - colour_esc.size - reset_esc.size - bold_esc.size;
|
|
max_header_length = DN_Max(max_header_length, size_no_ansi_codes);
|
|
DN_USize header_padding = max_header_length - size_no_ansi_codes;
|
|
|
|
DN_LogPrefixSize result = {};
|
|
result.size = size;
|
|
result.padding = header_padding;
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_LogSetPrintFunc(DN_LogPrintFunc *print_func, void *user_data)
|
|
{
|
|
DN_Core *dn = DN_Get();
|
|
dn->print_func = print_func;
|
|
dn->print_func_context = user_data;
|
|
}
|
|
|
|
DN_API void DN_LogPrint(DN_LogTypeParam type, DN_CallSite call_site, DN_LogFlags flags, DN_FMT_ATTRIB char const *fmt, ...)
|
|
{
|
|
DN_Core *dn = DN_Get();
|
|
if (type.is_u32_enum) {
|
|
if (type.u32 < dn->log_level_to_show_from)
|
|
return;
|
|
}
|
|
|
|
DN_LogPrintFunc *func = dn->print_func;
|
|
if (func) {
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
func(type, dn->print_func_context, call_site, flags, fmt, args);
|
|
va_end(args);
|
|
}
|
|
}
|
|
|
|
DN_API DN_LogTypeParam DN_LogTypeParamFromType(DN_LogType type)
|
|
{
|
|
DN_LogTypeParam result = {};
|
|
result.is_u32_enum = true;
|
|
result.u32 = type;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_F32 DN_F32Lerp(DN_F32 a, DN_F32 t, DN_F32 b)
|
|
{
|
|
DN_F32 result = a + ((b - a) * t);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_F32 DN_F32Floor(DN_F32 val)
|
|
{
|
|
DN_I32 val_i32 = DN_Cast(DN_I32) val;
|
|
if (val < 0 && val != DN_Cast(DN_F32) val_i32)
|
|
val_i32 -= 1;
|
|
DN_F32 result = DN_Cast(DN_F32)val_i32;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_F32 DN_F32Ceil(DN_F32 val)
|
|
{
|
|
DN_I32 val_i32 = DN_Cast(DN_I32)(val);
|
|
if (val > 0 && val != DN_Cast(DN_F32) val_i32)
|
|
val_i32 += 1;
|
|
DN_F32 result = DN_Cast(DN_F32) val_i32;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_F32 DN_F32RoundHalfUp(DN_F32 val)
|
|
{
|
|
DN_F32 result = val >= 0 ? DN_F32Floor(val + 0.5f) : DN_F32Ceil(val - 0.5f);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool operator==(DN_V2I32 lhs, DN_V2I32 rhs)
|
|
{
|
|
bool result = (lhs.x == rhs.x) && (lhs.y == rhs.y);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool operator!=(DN_V2I32 lhs, DN_V2I32 rhs)
|
|
{
|
|
bool result = !(lhs == rhs);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool operator>=(DN_V2I32 lhs, DN_V2I32 rhs)
|
|
{
|
|
bool result = (lhs.x >= rhs.x) && (lhs.y >= rhs.y);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool operator<=(DN_V2I32 lhs, DN_V2I32 rhs)
|
|
{
|
|
bool result = (lhs.x <= rhs.x) && (lhs.y <= rhs.y);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool operator<(DN_V2I32 lhs, DN_V2I32 rhs)
|
|
{
|
|
bool result = (lhs.x < rhs.x) && (lhs.y < rhs.y);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool operator>(DN_V2I32 lhs, DN_V2I32 rhs)
|
|
{
|
|
bool result = (lhs.x > rhs.x) && (lhs.y > rhs.y);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2I32 operator-(DN_V2I32 lhs, DN_V2I32 rhs)
|
|
{
|
|
DN_V2I32 result = DN_V2I32From2N(lhs.x - rhs.x, lhs.y - rhs.y);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2I32 operator-(DN_V2I32 lhs)
|
|
{
|
|
DN_V2I32 result = DN_V2I32From2N(-lhs.x, -lhs.y);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2I32 operator+(DN_V2I32 lhs, DN_V2I32 rhs)
|
|
{
|
|
DN_V2I32 result = DN_V2I32From2N(lhs.x + rhs.x, lhs.y + rhs.y);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2I32 operator*(DN_V2I32 lhs, DN_V2I32 rhs)
|
|
{
|
|
DN_V2I32 result = DN_V2I32From2N(lhs.x * rhs.x, lhs.y * rhs.y);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2I32 operator*(DN_V2I32 lhs, DN_F32 rhs)
|
|
{
|
|
DN_V2I32 result = DN_V2I32From2N(lhs.x * rhs, lhs.y * rhs);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2I32 operator*(DN_V2I32 lhs, DN_I32 rhs)
|
|
{
|
|
DN_V2I32 result = DN_V2I32From2N(lhs.x * rhs, lhs.y * rhs);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2I32 operator/(DN_V2I32 lhs, DN_V2I32 rhs)
|
|
{
|
|
DN_V2I32 result = DN_V2I32From2N(lhs.x / rhs.x, lhs.y / rhs.y);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2I32 operator/(DN_V2I32 lhs, DN_F32 rhs)
|
|
{
|
|
DN_V2I32 result = DN_V2I32From2N(lhs.x / rhs, lhs.y / rhs);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2I32 operator/(DN_V2I32 lhs, DN_I32 rhs)
|
|
{
|
|
DN_V2I32 result = DN_V2I32From2N(lhs.x / rhs, lhs.y / rhs);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2I32 &operator*=(DN_V2I32 &lhs, DN_V2I32 rhs)
|
|
{
|
|
lhs = lhs * rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V2I32 &operator*=(DN_V2I32 &lhs, DN_F32 rhs)
|
|
{
|
|
lhs = lhs * rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V2I32 &operator*=(DN_V2I32 &lhs, DN_I32 rhs)
|
|
{
|
|
lhs = lhs * rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V2I32 &operator/=(DN_V2I32 &lhs, DN_V2I32 rhs)
|
|
{
|
|
lhs = lhs / rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V2I32 &operator/=(DN_V2I32 &lhs, DN_F32 rhs)
|
|
{
|
|
lhs = lhs / rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V2I32 &operator/=(DN_V2I32 &lhs, DN_I32 rhs)
|
|
{
|
|
lhs = lhs / rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V2I32 &operator-=(DN_V2I32 &lhs, DN_V2I32 rhs)
|
|
{
|
|
lhs = lhs - rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V2I32 &operator+=(DN_V2I32 &lhs, DN_V2I32 rhs)
|
|
{
|
|
lhs = lhs + rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V2I32 DN_V2I32Min(DN_V2I32 a, DN_V2I32 b)
|
|
{
|
|
DN_V2I32 result = DN_V2I32From2N(DN_Min(a.x, b.x), DN_Min(a.y, b.y));
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2I32 DN_V2I32Max(DN_V2I32 a, DN_V2I32 b)
|
|
{
|
|
DN_V2I32 result = DN_V2I32From2N(DN_Max(a.x, b.x), DN_Max(a.y, b.y));
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2I32 DN_V2I32Abs(DN_V2I32 a)
|
|
{
|
|
DN_V2I32 result = DN_V2I32From2N(DN_Abs(a.x), DN_Abs(a.y));
|
|
return result;
|
|
}
|
|
|
|
DN_API bool operator!=(DN_V2U16 lhs, DN_V2U16 rhs)
|
|
{
|
|
bool result = !(lhs == rhs);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool operator==(DN_V2U16 lhs, DN_V2U16 rhs)
|
|
{
|
|
bool result = (lhs.x == rhs.x) && (lhs.y == rhs.y);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool operator>=(DN_V2U16 lhs, DN_V2U16 rhs)
|
|
{
|
|
bool result = (lhs.x >= rhs.x) && (lhs.y >= rhs.y);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool operator<=(DN_V2U16 lhs, DN_V2U16 rhs)
|
|
{
|
|
bool result = (lhs.x <= rhs.x) && (lhs.y <= rhs.y);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool operator<(DN_V2U16 lhs, DN_V2U16 rhs)
|
|
{
|
|
bool result = (lhs.x < rhs.x) && (lhs.y < rhs.y);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool operator>(DN_V2U16 lhs, DN_V2U16 rhs)
|
|
{
|
|
bool result = (lhs.x > rhs.x) && (lhs.y > rhs.y);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2U16 operator-(DN_V2U16 lhs, DN_V2U16 rhs)
|
|
{
|
|
DN_V2U16 result = DN_V2U16From2N(lhs.x - rhs.x, lhs.y - rhs.y);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2U16 operator+(DN_V2U16 lhs, DN_V2U16 rhs)
|
|
{
|
|
DN_V2U16 result = DN_V2U16From2N(lhs.x + rhs.x, lhs.y + rhs.y);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2U16 operator*(DN_V2U16 lhs, DN_V2U16 rhs)
|
|
{
|
|
DN_V2U16 result = DN_V2U16From2N(lhs.x * rhs.x, lhs.y * rhs.y);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2U16 operator*(DN_V2U16 lhs, DN_F32 rhs)
|
|
{
|
|
DN_V2U16 result = DN_V2U16From2N(lhs.x * rhs, lhs.y * rhs);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2U16 operator*(DN_V2U16 lhs, DN_I32 rhs)
|
|
{
|
|
DN_V2U16 result = DN_V2U16From2N(lhs.x * rhs, lhs.y * rhs);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2U16 operator/(DN_V2U16 lhs, DN_V2U16 rhs)
|
|
{
|
|
DN_V2U16 result = DN_V2U16From2N(lhs.x / rhs.x, lhs.y / rhs.y);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2U16 operator/(DN_V2U16 lhs, DN_F32 rhs)
|
|
{
|
|
DN_V2U16 result = DN_V2U16From2N(lhs.x / rhs, lhs.y / rhs);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2U16 operator/(DN_V2U16 lhs, DN_I32 rhs)
|
|
{
|
|
DN_V2U16 result = DN_V2U16From2N(lhs.x / rhs, lhs.y / rhs);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2U16 &operator*=(DN_V2U16 &lhs, DN_V2U16 rhs)
|
|
{
|
|
lhs = lhs * rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V2U16 &operator*=(DN_V2U16 &lhs, DN_F32 rhs)
|
|
{
|
|
lhs = lhs * rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V2U16 &operator*=(DN_V2U16 &lhs, DN_I32 rhs)
|
|
{
|
|
lhs = lhs * rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V2U16 &operator/=(DN_V2U16 &lhs, DN_V2U16 rhs)
|
|
{
|
|
lhs = lhs / rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V2U16 &operator/=(DN_V2U16 &lhs, DN_F32 rhs)
|
|
{
|
|
lhs = lhs / rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V2U16 &operator/=(DN_V2U16 &lhs, DN_I32 rhs)
|
|
{
|
|
lhs = lhs / rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V2U16 &operator-=(DN_V2U16 &lhs, DN_V2U16 rhs)
|
|
{
|
|
lhs = lhs - rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V2U16 &operator+=(DN_V2U16 &lhs, DN_V2U16 rhs)
|
|
{
|
|
lhs = lhs + rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V2F32 DN_V2F32Lerp(DN_V2F32 a, DN_F32 t, DN_V2F32 b)
|
|
{
|
|
DN_V2F32 result = {};
|
|
result.x = a.x + ((b.x - a.x) * t);
|
|
result.y = a.y + ((b.y - a.y) * t);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool operator!=(DN_V2F32 lhs, DN_V2F32 rhs)
|
|
{
|
|
bool result = !(lhs == rhs);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool operator==(DN_V2F32 lhs, DN_V2F32 rhs)
|
|
{
|
|
bool result = (lhs.x == rhs.x) && (lhs.y == rhs.y);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool operator>=(DN_V2F32 lhs, DN_V2F32 rhs)
|
|
{
|
|
bool result = (lhs.x >= rhs.x) && (lhs.y >= rhs.y);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool operator<=(DN_V2F32 lhs, DN_V2F32 rhs)
|
|
{
|
|
bool result = (lhs.x <= rhs.x) && (lhs.y <= rhs.y);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool operator<(DN_V2F32 lhs, DN_V2F32 rhs)
|
|
{
|
|
bool result = (lhs.x < rhs.x) && (lhs.y < rhs.y);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool operator>(DN_V2F32 lhs, DN_V2F32 rhs)
|
|
{
|
|
bool result = (lhs.x > rhs.x) && (lhs.y > rhs.y);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2F32 operator-(DN_V2F32 lhs)
|
|
{
|
|
DN_V2F32 result = DN_V2F32From2N(-lhs.x, -lhs.y);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2F32 operator-(DN_V2F32 lhs, DN_V2F32 rhs)
|
|
{
|
|
DN_V2F32 result = DN_V2F32From2N(lhs.x - rhs.x, lhs.y - rhs.y);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2F32 operator-(DN_V2F32 lhs, DN_V2I32 rhs)
|
|
{
|
|
DN_V2F32 result = DN_V2F32From2N(lhs.x - rhs.x, lhs.y - rhs.y);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2F32 operator-(DN_V2F32 lhs, DN_F32 rhs)
|
|
{
|
|
DN_V2F32 result = DN_V2F32From2N(lhs.x - rhs, lhs.y - rhs);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2F32 operator-(DN_V2F32 lhs, DN_I32 rhs)
|
|
{
|
|
DN_V2F32 result = DN_V2F32From2N(lhs.x - rhs, lhs.y - rhs);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2F32 operator+(DN_V2F32 lhs, DN_V2F32 rhs)
|
|
{
|
|
DN_V2F32 result = DN_V2F32From2N(lhs.x + rhs.x, lhs.y + rhs.y);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2F32 operator+(DN_V2F32 lhs, DN_V2I32 rhs)
|
|
{
|
|
DN_V2F32 result = DN_V2F32From2N(lhs.x + rhs.x, lhs.y + rhs.y);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2F32 operator+(DN_V2F32 lhs, DN_F32 rhs)
|
|
{
|
|
DN_V2F32 result = DN_V2F32From2N(lhs.x + rhs, lhs.y + rhs);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2F32 operator+(DN_V2F32 lhs, DN_I32 rhs)
|
|
{
|
|
DN_V2F32 result = DN_V2F32From2N(lhs.x + rhs, lhs.y + rhs);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2F32 operator*(DN_V2F32 lhs, DN_V2F32 rhs)
|
|
{
|
|
DN_V2F32 result = DN_V2F32From2N(lhs.x * rhs.x, lhs.y * rhs.y);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2F32 operator*(DN_V2F32 lhs, DN_V2I32 rhs)
|
|
{
|
|
DN_V2F32 result = DN_V2F32From2N(lhs.x * rhs.x, lhs.y * rhs.y);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2F32 operator*(DN_V2F32 lhs, DN_F32 rhs)
|
|
{
|
|
DN_V2F32 result = DN_V2F32From2N(lhs.x * rhs, lhs.y * rhs);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2F32 operator*(DN_V2F32 lhs, DN_I32 rhs)
|
|
{
|
|
DN_V2F32 result = DN_V2F32From2N(lhs.x * rhs, lhs.y * rhs);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2F32 operator/(DN_V2F32 lhs, DN_V2F32 rhs)
|
|
{
|
|
DN_V2F32 result = DN_V2F32From2N(lhs.x / rhs.x, lhs.y / rhs.y);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2F32 operator/(DN_V2F32 lhs, DN_V2I32 rhs)
|
|
{
|
|
DN_V2F32 result = DN_V2F32From2N(lhs.x / rhs.x, lhs.y / rhs.y);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2F32 operator/(DN_V2F32 lhs, DN_F32 rhs)
|
|
{
|
|
DN_V2F32 result = DN_V2F32From2N(lhs.x / rhs, lhs.y / rhs);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2F32 operator/(DN_V2F32 lhs, DN_I32 rhs)
|
|
{
|
|
DN_V2F32 result = DN_V2F32From2N(lhs.x / rhs, lhs.y / rhs);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2F32 &operator*=(DN_V2F32 &lhs, DN_V2F32 rhs)
|
|
{
|
|
lhs = lhs * rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V2F32 &operator*=(DN_V2F32 &lhs, DN_V2I32 rhs)
|
|
{
|
|
lhs = lhs * rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V2F32 &operator*=(DN_V2F32 &lhs, DN_F32 rhs)
|
|
{
|
|
lhs = lhs * rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V2F32 &operator*=(DN_V2F32 &lhs, DN_I32 rhs)
|
|
{
|
|
lhs = lhs * rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V2F32 &operator/=(DN_V2F32 &lhs, DN_V2F32 rhs)
|
|
{
|
|
lhs = lhs / rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V2F32 &operator/=(DN_V2F32 &lhs, DN_V2I32 rhs)
|
|
{
|
|
lhs = lhs / rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V2F32 &operator/=(DN_V2F32 &lhs, DN_F32 rhs)
|
|
{
|
|
lhs = lhs / rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V2F32 &operator/=(DN_V2F32 &lhs, DN_I32 rhs)
|
|
{
|
|
lhs = lhs / rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V2F32 &operator-=(DN_V2F32 &lhs, DN_V2F32 rhs)
|
|
{
|
|
lhs = lhs - rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V2F32 &operator-=(DN_V2F32 &lhs, DN_V2I32 rhs)
|
|
{
|
|
lhs = lhs - rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V2F32 &operator-=(DN_V2F32 &lhs, DN_F32 rhs)
|
|
{
|
|
lhs = lhs - rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V2F32 &operator-=(DN_V2F32 &lhs, DN_I32 rhs)
|
|
{
|
|
lhs = lhs - rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V2F32 &operator+=(DN_V2F32 &lhs, DN_V2F32 rhs)
|
|
{
|
|
lhs = lhs + rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V2F32 &operator+=(DN_V2F32 &lhs, DN_V2I32 rhs)
|
|
{
|
|
lhs = lhs + rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V2F32 &operator+=(DN_V2F32 &lhs, DN_F32 rhs)
|
|
{
|
|
lhs = lhs + rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V2F32 &operator+=(DN_V2F32 &lhs, DN_I32 rhs)
|
|
{
|
|
lhs = lhs + rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V2F32 DN_V2F32Min(DN_V2F32 a, DN_V2F32 b)
|
|
{
|
|
DN_V2F32 result = DN_V2F32From2N(DN_Min(a.x, b.x), DN_Min(a.y, b.y));
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2F32 DN_V2F32Max(DN_V2F32 a, DN_V2F32 b)
|
|
{
|
|
DN_V2F32 result = DN_V2F32From2N(DN_Max(a.x, b.x), DN_Max(a.y, b.y));
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2F32 DN_V2F32Abs(DN_V2F32 a)
|
|
{
|
|
DN_V2F32 result = DN_V2F32From2N(DN_Abs(a.x), DN_Abs(a.y));
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_F32 DN_V2F32Dot(DN_V2F32 a, DN_V2F32 b)
|
|
{
|
|
// NOTE: Scalar projection of B onto A /////////////////////////////////////////////////////////
|
|
//
|
|
// Scalar projection calculates the signed distance between `b` and `a`
|
|
// where `a` is a unit vector then, the dot product calculates the projection
|
|
// of `b` onto the infinite line that the direction of `a` represents. This
|
|
// calculation is the signed distance.
|
|
//
|
|
// signed_distance = dot_product(a, b) = (a.x * b.x) + (a.y * b.y)
|
|
//
|
|
// Y
|
|
// ^ b
|
|
// | /|
|
|
// | / |
|
|
// | / |
|
|
// | / | Projection
|
|
// | / |
|
|
// |/ V
|
|
// +--->--------> X
|
|
// . a .
|
|
// . .
|
|
// |------| <- Calculated signed distance
|
|
//
|
|
// The signed-ness of the result indicates the relationship:
|
|
//
|
|
// Distance <0 means `b` is behind `a`
|
|
// Distance >0 means `b` is in-front of `a`
|
|
// Distance ==0 means `b` is perpendicular to `a`
|
|
//
|
|
// If `a` is not normalized then the signed-ness of the result still holds
|
|
// however result no longer represents the actual distance between the
|
|
// 2 objects. One of the vectors must be normalised (e.g. turned into a unit
|
|
// vector).
|
|
//
|
|
// NOTE: DN_V projection /////////////////////////////////////////////////////////////////////
|
|
//
|
|
// DN_V projection calculates the exact X,Y coordinates of where `b` meets
|
|
// `a` when it was projected. This is calculated by multipying the
|
|
// 'scalar projection' result by the unit vector of `a`
|
|
//
|
|
// vector_projection = a * signed_distance = a * dot_product(a, b)
|
|
|
|
DN_F32 result = (a.x * b.x) + (a.y * b.y);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_F32 DN_V2F32LengthSq2V2(DN_V2F32 lhs, DN_V2F32 rhs)
|
|
{
|
|
// NOTE: Pythagoras's theorem (a^2 + b^2 = c^2) without the square root
|
|
DN_F32 a = rhs.x - lhs.x;
|
|
DN_F32 b = rhs.y - lhs.y;
|
|
DN_F32 c_squared = DN_Squared(a) + DN_Squared(b);
|
|
DN_F32 result = c_squared;
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_V2F32LengthSqIsWithin2V2(DN_V2F32 lhs, DN_V2F32 rhs, DN_F32 within_amount_sq)
|
|
{
|
|
DN_F32 dist = DN_V2F32LengthSq2V2(lhs, rhs);
|
|
bool result = dist <= within_amount_sq;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_F32 DN_V2F32Length2V2(DN_V2F32 lhs, DN_V2F32 rhs)
|
|
{
|
|
DN_F32 result_squared = DN_V2F32LengthSq2V2(lhs, rhs);
|
|
DN_F32 result = DN_SqrtF32(result_squared);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_F32 DN_V2F32LengthSq(DN_V2F32 lhs)
|
|
{
|
|
// NOTE: Pythagoras's theorem without the square root
|
|
DN_F32 c_squared = DN_Squared(lhs.x) + DN_Squared(lhs.y);
|
|
DN_F32 result = c_squared;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_F32 DN_V2F32Length(DN_V2F32 lhs)
|
|
{
|
|
DN_F32 c_squared = DN_V2F32LengthSq(lhs);
|
|
DN_F32 result = DN_SqrtF32(c_squared);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2F32 DN_V2F32Normalise(DN_V2F32 a)
|
|
{
|
|
DN_F32 length = DN_V2F32Length(a);
|
|
DN_V2F32 result = a / length;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2F32 DN_V2F32Perpendicular(DN_V2F32 a)
|
|
{
|
|
// NOTE: Matrix form of a 2D vector can be defined as
|
|
//
|
|
// x' = x cos(t) - y sin(t)
|
|
// y' = x sin(t) + y cos(t)
|
|
//
|
|
// Calculate a line perpendicular to a vector means rotating the vector by
|
|
// 90 degrees
|
|
//
|
|
// x' = x cos(90) - y sin(90)
|
|
// y' = x sin(90) + y cos(90)
|
|
//
|
|
// Where `cos(90) = 0` and `sin(90) = 1` then,
|
|
//
|
|
// x' = -y
|
|
// y' = +x
|
|
|
|
DN_V2F32 result = DN_V2F32From2N(-a.y, a.x);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2F32 DN_V2F32Reflect(DN_V2F32 in, DN_V2F32 surface)
|
|
{
|
|
DN_V2F32 normal = DN_V2F32Perpendicular(surface);
|
|
DN_V2F32 normal_norm = DN_V2F32Normalise(normal);
|
|
DN_F32 signed_dist = DN_V2F32Dot(in, normal_norm);
|
|
DN_V2F32 result = DN_V2F32From2N(in.x, in.y + (-signed_dist * 2.f));
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_F32 DN_V2F32Area(DN_V2F32 a)
|
|
{
|
|
DN_F32 result = a.w * a.h;
|
|
return result;
|
|
}
|
|
|
|
DN_API bool operator!=(DN_V3F32 lhs, DN_V3F32 rhs)
|
|
{
|
|
bool result = !(lhs == rhs);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool operator==(DN_V3F32 lhs, DN_V3F32 rhs)
|
|
{
|
|
bool result = (lhs.x == rhs.x) && (lhs.y == rhs.y) && (lhs.z == rhs.z);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool operator>=(DN_V3F32 lhs, DN_V3F32 rhs)
|
|
{
|
|
bool result = (lhs.x >= rhs.x) && (lhs.y >= rhs.y) && (lhs.z >= rhs.z);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool operator<=(DN_V3F32 lhs, DN_V3F32 rhs)
|
|
{
|
|
bool result = (lhs.x <= rhs.x) && (lhs.y <= rhs.y) && (lhs.z <= rhs.z);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool operator<(DN_V3F32 lhs, DN_V3F32 rhs)
|
|
{
|
|
bool result = (lhs.x < rhs.x) && (lhs.y < rhs.y) && (lhs.z < rhs.z);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool operator>(DN_V3F32 lhs, DN_V3F32 rhs)
|
|
{
|
|
bool result = (lhs.x > rhs.x) && (lhs.y > rhs.y) && (lhs.z > rhs.z);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V3F32 operator-(DN_V3F32 lhs, DN_V3F32 rhs)
|
|
{
|
|
DN_V3F32 result = DN_V3F32From3N(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V3F32 operator-(DN_V3F32 lhs)
|
|
{
|
|
DN_V3F32 result = DN_V3F32From3N(-lhs.x, -lhs.y, -lhs.z);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V3F32 operator+(DN_V3F32 lhs, DN_V3F32 rhs)
|
|
{
|
|
DN_V3F32 result = DN_V3F32From3N(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V3F32 operator*(DN_V3F32 lhs, DN_V3F32 rhs)
|
|
{
|
|
DN_V3F32 result = DN_V3F32From3N(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V3F32 operator*(DN_V3F32 lhs, DN_F32 rhs)
|
|
{
|
|
DN_V3F32 result = DN_V3F32From3N(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V3F32 operator*(DN_V3F32 lhs, DN_I32 rhs)
|
|
{
|
|
DN_V3F32 result = DN_V3F32From3N(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V3F32 operator/(DN_V3F32 lhs, DN_V3F32 rhs)
|
|
{
|
|
DN_V3F32 result = DN_V3F32From3N(lhs.x / rhs.x, lhs.y / rhs.y, lhs.z / rhs.z);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V3F32 operator/(DN_V3F32 lhs, DN_F32 rhs)
|
|
{
|
|
DN_V3F32 result = DN_V3F32From3N(lhs.x / rhs, lhs.y / rhs, lhs.z / rhs);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V3F32 operator/(DN_V3F32 lhs, DN_I32 rhs)
|
|
{
|
|
DN_V3F32 result = DN_V3F32From3N(lhs.x / rhs, lhs.y / rhs, lhs.z / rhs);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V3F32 &operator*=(DN_V3F32 &lhs, DN_V3F32 rhs)
|
|
{
|
|
lhs = lhs * rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V3F32 &operator*=(DN_V3F32 &lhs, DN_F32 rhs)
|
|
{
|
|
lhs = lhs * rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V3F32 &operator*=(DN_V3F32 &lhs, DN_I32 rhs)
|
|
{
|
|
lhs = lhs * rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V3F32 &operator/=(DN_V3F32 &lhs, DN_V3F32 rhs)
|
|
{
|
|
lhs = lhs / rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V3F32 &operator/=(DN_V3F32 &lhs, DN_F32 rhs)
|
|
{
|
|
lhs = lhs / rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V3F32 &operator/=(DN_V3F32 &lhs, DN_I32 rhs)
|
|
{
|
|
lhs = lhs / rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V3F32 &operator-=(DN_V3F32 &lhs, DN_V3F32 rhs)
|
|
{
|
|
lhs = lhs - rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V3F32 &operator+=(DN_V3F32 &lhs, DN_V3F32 rhs)
|
|
{
|
|
lhs = lhs + rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V3F32 DN_V3F32Lerp(DN_V3F32 lhs, DN_F32 t01, DN_V3F32 rhs)
|
|
{
|
|
DN_V3F32 result = {};
|
|
result.x = lhs.x + ((rhs.x - lhs.x) * t01);
|
|
result.y = lhs.y + ((rhs.y - lhs.y) * t01);
|
|
result.z = lhs.z + ((rhs.z - lhs.z) * t01);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_F32 DN_V3_LengthSq(DN_V3F32 a)
|
|
{
|
|
DN_F32 result = DN_Squared(a.x) + DN_Squared(a.y) + DN_Squared(a.z);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_F32 DN_V3_Length(DN_V3F32 a)
|
|
{
|
|
DN_F32 length_sq = DN_Squared(a.x) + DN_Squared(a.y) + DN_Squared(a.z);
|
|
DN_F32 result = DN_SqrtF32(length_sq);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V3F32 DN_V3_Normalise(DN_V3F32 a)
|
|
{
|
|
DN_F32 length = DN_V3_Length(a);
|
|
DN_V3F32 result = a / length;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V4F32 DN_V4F32Lerp(DN_V4F32 lhs, DN_F32 t01, DN_V4F32 rhs)
|
|
{
|
|
DN_V4F32 result = {};
|
|
result.x = lhs.x + (rhs.x - lhs.x) * t01;
|
|
result.y = lhs.y + (rhs.y - lhs.y) * t01;
|
|
result.z = lhs.z + (rhs.z - lhs.z) * t01;
|
|
result.w = lhs.w + (rhs.w - lhs.w) * t01;
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_V4F32RGBA01IsValid(DN_V4F32 rgba01)
|
|
{
|
|
bool result = rgba01.r >= 0 && rgba01.r <= 1.f &&
|
|
rgba01.g >= 0 && rgba01.g <= 1.f &&
|
|
rgba01.b >= 0 && rgba01.b <= 1.f &&
|
|
rgba01.a >= 0 && rgba01.a <= 1.f;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V4F32 DN_V4F32RGBA01FromRGBU32(DN_U32 u32)
|
|
{
|
|
DN_U8 r = (DN_U8)((u32 & 0x00FF0000) >> 16);
|
|
DN_U8 g = (DN_U8)((u32 & 0x0000FF00) >> 8);
|
|
DN_U8 b = (DN_U8)((u32 & 0x000000FF) >> 0);
|
|
DN_V4F32 result = DN_V4F32RGBA01FromRGBU8(r, g, b);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V4F32 DN_V4F32RGBA01FromRGBAU32(DN_U32 u32)
|
|
{
|
|
DN_U8 r = (DN_U8)((u32 & 0xFF000000) >> 24);
|
|
DN_U8 g = (DN_U8)((u32 & 0x00FF0000) >> 16);
|
|
DN_U8 b = (DN_U8)((u32 & 0x0000FF00) >> 8);
|
|
DN_U8 a = (DN_U8)((u32 & 0x000000FF) >> 0);
|
|
DN_V4F32 result = DN_V4F32RGBA01FromRGBAU8(r, g, b, a);
|
|
return result;
|
|
}
|
|
|
|
#define DN_SRGB_COEFFICIENT_F32 2.2f
|
|
DN_API DN_V4F32 DN_V4F32Linear01FromSRGB01(DN_V4F32 srgb01)
|
|
{
|
|
DN_Assert(srgb01.x >= 0.f && srgb01.x <= 1.f);
|
|
DN_Assert(srgb01.y >= 0.f && srgb01.y <= 1.f);
|
|
DN_Assert(srgb01.z >= 0.f && srgb01.z <= 1.f);
|
|
DN_Assert(srgb01.a >= 0.f && srgb01.a <= 1.f);
|
|
DN_V4F32 result = {};
|
|
result.r = DN_PowF32(srgb01.r, DN_SRGB_COEFFICIENT_F32);
|
|
result.g = DN_PowF32(srgb01.g, DN_SRGB_COEFFICIENT_F32);
|
|
result.b = DN_PowF32(srgb01.b, DN_SRGB_COEFFICIENT_F32);
|
|
result.a = srgb01.a;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V4F32 DN_V4F32Linear01Desaturate(DN_V4F32 linear01, DN_F32 t01)
|
|
{
|
|
DN_F32 luminance = (linear01.r * DN_V3F32_RGB_LUMINANCE.r) + (linear01.g * DN_V3F32_RGB_LUMINANCE.g) + (linear01.b * DN_V3F32_RGB_LUMINANCE.b);
|
|
DN_V4F32 result = linear01;
|
|
result.rgb = DN_V3F32Lerp(result.rgb, t01, DN_V3F32From1N(luminance));
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V4F32 DN_V4F32SRGB01FromLinear01(DN_V4F32 linear01)
|
|
{
|
|
DN_Assert(linear01.x >= 0.f && linear01.x <= 1.f);
|
|
DN_Assert(linear01.y >= 0.f && linear01.y <= 1.f);
|
|
DN_Assert(linear01.z >= 0.f && linear01.z <= 1.f);
|
|
DN_Assert(linear01.a >= 0.f && linear01.a <= 1.f);
|
|
DN_V4F32 result = {};
|
|
result.r = DN_PowF32(linear01.r, 1.f / DN_SRGB_COEFFICIENT_F32);
|
|
result.g = DN_PowF32(linear01.g, 1.f / DN_SRGB_COEFFICIENT_F32);
|
|
result.b = DN_PowF32(linear01.b, 1.f / DN_SRGB_COEFFICIENT_F32);
|
|
result.a = linear01.a;
|
|
return result;
|
|
}
|
|
|
|
DN_API bool operator==(DN_V4F32 lhs, DN_V4F32 rhs)
|
|
{
|
|
bool result = (lhs.x == rhs.x) && (lhs.y == rhs.y) && (lhs.z == rhs.z) && (lhs.w == rhs.w);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool operator!=(DN_V4F32 lhs, DN_V4F32 rhs)
|
|
{
|
|
bool result = !(lhs == rhs);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool operator>=(DN_V4F32 lhs, DN_V4F32 rhs)
|
|
{
|
|
bool result = (lhs.x >= rhs.x) && (lhs.y >= rhs.y) && (lhs.z >= rhs.z) && (lhs.w >= rhs.w);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool operator<=(DN_V4F32 lhs, DN_V4F32 rhs)
|
|
{
|
|
bool result = (lhs.x <= rhs.x) && (lhs.y <= rhs.y) && (lhs.z <= rhs.z) && (lhs.w <= rhs.w);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool operator<(DN_V4F32 lhs, DN_V4F32 rhs)
|
|
{
|
|
bool result = (lhs.x < rhs.x) && (lhs.y < rhs.y) && (lhs.z < rhs.z) && (lhs.w < rhs.w);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool operator>(DN_V4F32 lhs, DN_V4F32 rhs)
|
|
{
|
|
bool result = (lhs.x > rhs.x) && (lhs.y > rhs.y) && (lhs.z > rhs.z) && (lhs.w > rhs.w);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V4F32 operator-(DN_V4F32 lhs, DN_V4F32 rhs)
|
|
{
|
|
DN_V4F32 result = DN_V4F32From4N(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z, lhs.w - rhs.w);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V4F32 operator-(DN_V4F32 lhs)
|
|
{
|
|
DN_V4F32 result = DN_V4F32From4N(-lhs.x, -lhs.y, -lhs.z, -lhs.w);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V4F32 operator+(DN_V4F32 lhs, DN_V4F32 rhs)
|
|
{
|
|
DN_V4F32 result = DN_V4F32From4N(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z, lhs.w + rhs.w);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V4F32 operator*(DN_V4F32 lhs, DN_V4F32 rhs)
|
|
{
|
|
DN_V4F32 result = DN_V4F32From4N(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z, lhs.w * rhs.w);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V4F32 operator*(DN_V4F32 lhs, DN_F32 rhs)
|
|
{
|
|
DN_V4F32 result = DN_V4F32From4N(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs, lhs.w * rhs);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V4F32 operator*(DN_V4F32 lhs, DN_I32 rhs)
|
|
{
|
|
DN_V4F32 result = DN_V4F32From4N(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs, lhs.w * rhs);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V4F32 operator/(DN_V4F32 lhs, DN_F32 rhs)
|
|
{
|
|
DN_V4F32 result = DN_V4F32From4N(lhs.x / rhs, lhs.y / rhs, lhs.z / rhs, lhs.w / rhs);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V4F32 &operator*=(DN_V4F32 &lhs, DN_V4F32 rhs)
|
|
{
|
|
lhs = lhs * rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V4F32 &operator*=(DN_V4F32 &lhs, DN_F32 rhs)
|
|
{
|
|
lhs = lhs * rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V4F32 &operator*=(DN_V4F32 &lhs, DN_I32 rhs)
|
|
{
|
|
lhs = lhs * rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V4F32 &operator-=(DN_V4F32 &lhs, DN_V4F32 rhs)
|
|
{
|
|
lhs = lhs - rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_V4F32 &operator+=(DN_V4F32 &lhs, DN_V4F32 rhs)
|
|
{
|
|
lhs = lhs + rhs;
|
|
return lhs;
|
|
}
|
|
|
|
DN_API DN_F32 DN_V4F32Dot(DN_V4F32 a, DN_V4F32 b)
|
|
{
|
|
DN_F32 result = (a.x * b.x) + (a.y * b.y) + (a.z * b.z) + (a.w * b.w);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_M4 DN_M4Identity()
|
|
{
|
|
DN_M4 result =
|
|
{
|
|
{
|
|
{1, 0, 0, 0},
|
|
{0, 1, 0, 0},
|
|
{0, 0, 1, 0},
|
|
{0, 0, 0, 1},
|
|
}
|
|
};
|
|
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_M4 DN_M4ScaleF(DN_F32 x, DN_F32 y, DN_F32 z)
|
|
{
|
|
DN_M4 result =
|
|
{
|
|
{
|
|
{x, 0, 0, 0},
|
|
{0, y, 0, 0},
|
|
{0, 0, z, 0},
|
|
{0, 0, 0, 1},
|
|
}
|
|
};
|
|
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_M4 DN_M4Scale(DN_V3F32 xyz)
|
|
{
|
|
DN_M4 result =
|
|
{
|
|
{
|
|
{xyz.x, 0, 0, 0},
|
|
{0, xyz.y, 0, 0},
|
|
{0, 0, xyz.z, 0},
|
|
{0, 0, 0, 1},
|
|
}
|
|
};
|
|
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_M4 DN_M4TranslateF(DN_F32 x, DN_F32 y, DN_F32 z)
|
|
{
|
|
DN_M4 result =
|
|
{
|
|
{
|
|
{1, 0, 0, 0},
|
|
{0, 1, 0, 0},
|
|
{0, 0, 1, 0},
|
|
{x, y, z, 1},
|
|
}
|
|
};
|
|
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_M4 DN_M4Translate(DN_V3F32 xyz)
|
|
{
|
|
DN_M4 result =
|
|
{
|
|
{
|
|
{1, 0, 0, 0},
|
|
{0, 1, 0, 0},
|
|
{0, 0, 1, 0},
|
|
{xyz.x, xyz.y, xyz.z, 1},
|
|
}
|
|
};
|
|
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_M4 DN_M4Transpose(DN_M4 mat)
|
|
{
|
|
DN_M4 result = {};
|
|
for (int col = 0; col < 4; col++)
|
|
for (int row = 0; row < 4; row++)
|
|
result.columns[col][row] = mat.columns[row][col];
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_M4 DN_M4Rotate(DN_V3F32 axis01, DN_F32 radians)
|
|
{
|
|
DN_AssertF(DN_Abs(DN_V3_Length(axis01) - 1.f) <= 0.01f,
|
|
"Rotation axis must be normalised, length = %f",
|
|
DN_V3_Length(axis01));
|
|
|
|
DN_F32 sin = DN_SinF32(radians);
|
|
DN_F32 cos = DN_CosF32(radians);
|
|
DN_F32 one_minus_cos = 1.f - cos;
|
|
|
|
DN_F32 x = axis01.x;
|
|
DN_F32 y = axis01.y;
|
|
DN_F32 z = axis01.z;
|
|
DN_F32 x2 = DN_Squared(x);
|
|
DN_F32 y2 = DN_Squared(y);
|
|
DN_F32 z2 = DN_Squared(z);
|
|
|
|
DN_M4 result =
|
|
{
|
|
{
|
|
{cos + x2 * one_minus_cos, y * x * one_minus_cos + z * sin, z * x * one_minus_cos - y * sin, 0}, // Col 1
|
|
{x * y * one_minus_cos - z * sin, cos + y2 * one_minus_cos, z * y * one_minus_cos + x * sin, 0}, // Col 2
|
|
{x * z * one_minus_cos + y * sin, y * z * one_minus_cos - x * sin, cos + z2 * one_minus_cos, 0}, // Col 3
|
|
{0, 0, 0, 1}, // Col 4
|
|
}
|
|
};
|
|
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_M4 DN_M4Orthographic(DN_F32 left, DN_F32 right, DN_F32 bottom, DN_F32 top, DN_F32 z_near, DN_F32 z_far)
|
|
{
|
|
// NOTE: Here is the matrix in column major for readability. Below it's
|
|
// transposed due to how you have to declare column major matrices in C/C++.
|
|
//
|
|
// m = [2/r-l, 0, 0, -1*(r+l)/(r-l)]
|
|
// [0, 2/t-b, 0, 1*(t+b)/(t-b)]
|
|
// [0, 0, -2/f-n, -1*(f+n)/(f-n)]
|
|
// [0, 0, 0, 1 ]
|
|
|
|
DN_M4 result =
|
|
{
|
|
{
|
|
{2.f / (right - left), 0.f, 0.f, 0.f},
|
|
{0.f, 2.f / (top - bottom), 0.f, 0.f},
|
|
{0.f, 0.f, -2.f / (z_far - z_near), 0.f},
|
|
{(-1.f * (right + left)) / (right - left), (-1.f * (top + bottom)) / (top - bottom), (-1.f * (z_far + z_near)) / (z_far - z_near), 1.f},
|
|
}
|
|
};
|
|
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_M4 DN_M4Perspective(DN_F32 fov /*radians*/, DN_F32 aspect, DN_F32 z_near, DN_F32 z_far)
|
|
{
|
|
DN_F32 tan_fov = DN_TanF32(fov / 2.f);
|
|
DN_M4 result =
|
|
{
|
|
{
|
|
{1.f / (aspect * tan_fov), 0.f, 0.f, 0.f},
|
|
{0, 1.f / tan_fov, 0.f, 0.f},
|
|
{0.f, 0.f, (z_near + z_far) / (z_near - z_far), -1.f},
|
|
{0.f, 0.f, (2.f * z_near * z_far) / (z_near - z_far), 0.f},
|
|
}
|
|
};
|
|
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_M4 DN_M4Add(DN_M4 lhs, DN_M4 rhs)
|
|
{
|
|
DN_M4 result;
|
|
for (int col = 0; col < 4; col++)
|
|
for (int it = 0; it < 4; it++)
|
|
result.columns[col][it] = lhs.columns[col][it] + rhs.columns[col][it];
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_M4 DN_M4Sub(DN_M4 lhs, DN_M4 rhs)
|
|
{
|
|
DN_M4 result;
|
|
for (int col = 0; col < 4; col++)
|
|
for (int it = 0; it < 4; it++)
|
|
result.columns[col][it] = lhs.columns[col][it] - rhs.columns[col][it];
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_M4 DN_M4Mul(DN_M4 lhs, DN_M4 rhs)
|
|
{
|
|
DN_M4 result;
|
|
for (int col = 0; col < 4; col++) {
|
|
for (int row = 0; row < 4; row++) {
|
|
DN_F32 sum = 0;
|
|
for (int f32_it = 0; f32_it < 4; f32_it++)
|
|
sum += lhs.columns[f32_it][row] * rhs.columns[col][f32_it];
|
|
|
|
result.columns[col][row] = sum;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_M4 DN_M4Div(DN_M4 lhs, DN_M4 rhs)
|
|
{
|
|
DN_M4 result;
|
|
for (int col = 0; col < 4; col++)
|
|
for (int it = 0; it < 4; it++)
|
|
result.columns[col][it] = lhs.columns[col][it] / rhs.columns[col][it];
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_M4 DN_M4AddF(DN_M4 lhs, DN_F32 rhs)
|
|
{
|
|
DN_M4 result;
|
|
for (int col = 0; col < 4; col++)
|
|
for (int it = 0; it < 4; it++)
|
|
result.columns[col][it] = lhs.columns[col][it] + rhs;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_M4 DN_M4SubF(DN_M4 lhs, DN_F32 rhs)
|
|
{
|
|
DN_M4 result;
|
|
for (int col = 0; col < 4; col++)
|
|
for (int it = 0; it < 4; it++)
|
|
result.columns[col][it] = lhs.columns[col][it] - rhs;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_M4 DN_M4MulF(DN_M4 lhs, DN_F32 rhs)
|
|
{
|
|
DN_M4 result;
|
|
for (int col = 0; col < 4; col++)
|
|
for (int it = 0; it < 4; it++)
|
|
result.columns[col][it] = lhs.columns[col][it] * rhs;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_M4 DN_M4DivF(DN_M4 lhs, DN_F32 rhs)
|
|
{
|
|
DN_M4 result;
|
|
for (int col = 0; col < 4; col++)
|
|
for (int it = 0; it < 4; it++)
|
|
result.columns[col][it] = lhs.columns[col][it] / rhs;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8x256 DN_M4ColumnMajorString(DN_M4 mat)
|
|
{
|
|
DN_Str8x256 result = {};
|
|
for (int row = 0; row < 4; row++) {
|
|
for (int it = 0; it < 4; it++) {
|
|
if (it == 0)
|
|
DN_FmtAppend(result.data, &result.size, sizeof(result.data), "|");
|
|
DN_FmtAppend(result.data, &result.size, sizeof(result.data), "%.5f", mat.columns[it][row]);
|
|
if (it != 3)
|
|
DN_FmtAppend(result.data, &result.size, sizeof(result.data), ", ");
|
|
else
|
|
DN_FmtAppend(result.data, &result.size, sizeof(result.data), "|\n");
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API bool operator==(DN_M2x3 const &lhs, DN_M2x3 const &rhs)
|
|
{
|
|
bool result = DN_Memcmp(lhs.e, rhs.e, sizeof(lhs.e[0]) * DN_ArrayCountU(lhs.e)) == 0;
|
|
return result;
|
|
}
|
|
|
|
DN_API bool operator!=(DN_M2x3 const &lhs, DN_M2x3 const &rhs)
|
|
{
|
|
bool result = !(lhs == rhs);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_M2x3 DN_M2x3Identity()
|
|
{
|
|
DN_M2x3 result = {
|
|
{
|
|
1,
|
|
0,
|
|
0,
|
|
0,
|
|
1,
|
|
0,
|
|
}
|
|
};
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_M2x3 DN_M2x3Translate(DN_V2F32 offset)
|
|
{
|
|
DN_M2x3 result = {
|
|
{
|
|
1,
|
|
0,
|
|
offset.x,
|
|
0,
|
|
1,
|
|
offset.y,
|
|
}
|
|
};
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2F32 DN_M2x3ScaleGet(DN_M2x3 m2x3)
|
|
{
|
|
DN_V2F32 result = DN_V2F32From2N(m2x3.row[0][0], m2x3.row[1][1]);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_M2x3 DN_M2x3Scale(DN_V2F32 scale)
|
|
{
|
|
DN_M2x3 result = {{
|
|
scale.x, 0, 0,
|
|
0, scale.y, 0,
|
|
}};
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_M2x3 DN_M2x3Rotate(DN_F32 radians)
|
|
{
|
|
DN_M2x3 result = {{
|
|
DN_CosF32(radians), DN_SinF32(radians), 0,
|
|
-DN_SinF32(radians), DN_CosF32(radians), 0,
|
|
}};
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_M2x3 DN_M2x3ProjFromV2F32(DN_V2F32 size, DN_M2x3ProjOrigin origin)
|
|
{
|
|
DN_M2x3 result = {};
|
|
|
|
// NOTE: Maps coordinates within a rectangle of `size` into NDC where (-1, +1) is top left, (+1, -1) is bot right
|
|
if (origin == DN_M2x3ProjOrigin_TopLeft) {
|
|
result = {{
|
|
2.f/size.w, 0, -1.f,
|
|
0, -2.f/size.h, +1.f,
|
|
}};
|
|
} else {
|
|
DN_Assert(origin == DN_M2x3ProjOrigin_Center);
|
|
result = {{
|
|
2.f/size.w, 0, 0.f,
|
|
0, -2.f/size.h, 0.f,
|
|
}};
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_M2x3XForm DN_M2x3XFormFromM2x3(DN_M2x3 forward, DN_M2x3 inverse)
|
|
{
|
|
DN_M2x3XForm result = {};
|
|
result.forward = forward;
|
|
result.inverse = inverse;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_M2x3XForm DN_M2x3XFormFromTRS(DN_V2F32 pos, DN_V2F32 scale, DN_F32 rotate_rads, DN_V2F32 pivot_pos)
|
|
{
|
|
DN_M2x3XForm result = {};
|
|
result.forward = DN_M2x3Identity();
|
|
result.inverse = DN_M2x3Identity();
|
|
|
|
if (scale.x == 0)
|
|
scale.x = 1;
|
|
if (scale.y == 0)
|
|
scale.y = 1;
|
|
|
|
result.forward = DN_M2x3Mul(result.forward, DN_M2x3Translate(pivot_pos));
|
|
result.forward = DN_M2x3Mul(result.forward, DN_M2x3Rotate(rotate_rads));
|
|
result.forward = DN_M2x3Mul(result.forward, DN_M2x3Scale(scale));
|
|
result.forward = DN_M2x3Mul(result.forward, DN_M2x3Translate(-pivot_pos));
|
|
result.forward = DN_M2x3Mul(result.forward, DN_M2x3Translate(pos));
|
|
|
|
DN_V2F32 inverse_scale = DN_V2F32From1N(1) / scale;
|
|
result.inverse = DN_M2x3Mul(result.inverse, DN_M2x3Translate(-pos));
|
|
result.inverse = DN_M2x3Mul(result.inverse, DN_M2x3Translate(pivot_pos));
|
|
result.inverse = DN_M2x3Mul(result.inverse, DN_M2x3Scale(inverse_scale));
|
|
result.inverse = DN_M2x3Mul(result.inverse, DN_M2x3Rotate(-rotate_rads));
|
|
result.inverse = DN_M2x3Mul(result.inverse, DN_M2x3Translate(-pivot_pos));
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_M2x3XForm DN_M2x3XFormIdentity()
|
|
{
|
|
DN_M2x3XForm result = {};
|
|
result.forward = DN_M2x3Identity();
|
|
result.inverse = DN_M2x3Identity();
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_M2x3XForm DN_M2x3XFormMul(DN_M2x3XForm m1, DN_M2x3XForm m2)
|
|
{
|
|
DN_M2x3XForm result = {};
|
|
result.forward = DN_M2x3Mul(m1.forward, m2.forward);
|
|
result.inverse = DN_M2x3Mul(m2.inverse, m1.inverse);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_M2x3 DN_M2x3Mul(DN_M2x3 m1, DN_M2x3 m2)
|
|
{
|
|
// NOTE: Ordinarily you can't multiply M2x3 with M2x3 because column count
|
|
// (3) != row count (2). We pretend we have two 3x3 matrices with the last
|
|
// row set to [0 0 1] and perform a 3x3 matrix multiply.
|
|
//
|
|
// | (0)a (1)b (2)c | | (0)g (1)h (2)i |
|
|
// | (3)d (4)e (5)f | x | (3)j (4)k (5)l |
|
|
// | (6)0 (7)0 (8)1 | | (6)0 (7)0 (8)1 |
|
|
|
|
DN_M2x3 result = {
|
|
{
|
|
m1.e[0] * m2.e[0] + m1.e[1] * m2.e[3], // a*g + b*j + c*0[omitted],
|
|
m1.e[0] * m2.e[1] + m1.e[1] * m2.e[4], // a*h + b*k + c*0[omitted],
|
|
m1.e[0] * m2.e[2] + m1.e[1] * m2.e[5] + m1.e[2], // a*i + b*l + c*1,
|
|
|
|
m1.e[3] * m2.e[0] + m1.e[4] * m2.e[3], // d*g + e*j + f*0[omitted],
|
|
m1.e[3] * m2.e[1] + m1.e[4] * m2.e[4], // d*h + e*k + f*0[omitted],
|
|
m1.e[3] * m2.e[2] + m1.e[4] * m2.e[5] + m1.e[5], // d*i + e*l + f*1,
|
|
}
|
|
};
|
|
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2F32 DN_M2x3Mul2F32(DN_M2x3 m1, DN_F32 x, DN_F32 y)
|
|
{
|
|
// NOTE: Ordinarily you can't multiply M2x3 with V2 because column count (3)
|
|
// != row count (2). We pretend we have a V3 with `z` set to `1`.
|
|
//
|
|
// | (0)a (1)b (2)c | | x |
|
|
// | (3)d (4)e (5)f | x | y |
|
|
// | 1 |
|
|
|
|
DN_V2F32 result = {
|
|
{
|
|
m1.e[0] * x + m1.e[1] * y + m1.e[2], // a*x + b*y + c*1
|
|
m1.e[3] * x + m1.e[4] * y + m1.e[5], // d*x + e*y + f*1
|
|
}
|
|
};
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2F32 DN_M2x3MulV2F32(DN_M2x3 m1, DN_V2F32 v2)
|
|
{
|
|
DN_V2F32 result = DN_M2x3Mul2F32(m1, v2.x, v2.y);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Rect DN_M2x3MulRect(DN_M2x3 m1, DN_Rect rect)
|
|
{
|
|
DN_2V2F32 rect_range = DN_RectRange(rect);
|
|
DN_V2F32 m1_min = DN_M2x3MulV2F32(m1, rect_range.min);
|
|
DN_V2F32 m1_max = DN_M2x3MulV2F32(m1, rect_range.max);
|
|
|
|
// NOTE: Re-establish AABB of the rectangle because it has gone through an arbitrary
|
|
// vertex transformation.
|
|
DN_2V2F32 result_range = {};
|
|
result_range.min = DN_V2F32Min(m1_min, m1_max);
|
|
result_range.max = DN_V2F32Max(m1_min, m1_max);
|
|
|
|
DN_Rect result = DN_RectFrom2V2(result_range.min, DN_V2F32Abs(result_range.max - result_range.min));
|
|
return result;
|
|
}
|
|
|
|
DN_API bool operator==(const DN_Rect &lhs, const DN_Rect &rhs)
|
|
{
|
|
bool result = (lhs.pos == rhs.pos) && (lhs.size == rhs.size);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2F32 DN_RectCenter(DN_Rect rect)
|
|
{
|
|
DN_V2F32 result = rect.pos + (rect.size * .5f);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_RectContainsPoint(DN_Rect rect, DN_V2F32 p)
|
|
{
|
|
DN_V2F32 min = rect.pos;
|
|
DN_V2F32 max = rect.pos + rect.size;
|
|
bool result = (p.x >= min.x && p.x <= max.x && p.y >= min.y && p.y <= max.y);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_RectContainsRect(DN_Rect a, DN_Rect b)
|
|
{
|
|
DN_V2F32 a_min = a.pos;
|
|
DN_V2F32 a_max = a.pos + a.size;
|
|
DN_V2F32 b_min = b.pos;
|
|
DN_V2F32 b_max = b.pos + b.size;
|
|
bool result = (b_min >= a_min && b_max <= a_max);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Rect DN_RectExpand(DN_Rect a, DN_F32 amount)
|
|
{
|
|
DN_Rect result = a;
|
|
result.pos -= amount;
|
|
result.size += (amount * 2.f);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Rect DN_RectExpandV2(DN_Rect a, DN_V2F32 amount)
|
|
{
|
|
DN_Rect result = a;
|
|
result.pos -= amount;
|
|
result.size += (amount * 2.f);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_RectIntersects(DN_Rect a, DN_Rect b)
|
|
{
|
|
DN_V2F32 a_min = a.pos;
|
|
DN_V2F32 a_max = a.pos + a.size;
|
|
DN_V2F32 b_min = b.pos;
|
|
DN_V2F32 b_max = b.pos + b.size;
|
|
bool has_size = a.size.x && a.size.y && b.size.x && b.size.y;
|
|
bool result = false;
|
|
if (has_size)
|
|
result = (a_min.x <= b_max.x && a_max.x >= b_min.x) &&
|
|
(a_min.y <= b_max.y && a_max.y >= b_min.y);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Rect DN_RectIntersection(DN_Rect a, DN_Rect b)
|
|
{
|
|
DN_Rect result = DN_RectFrom2V2(a.pos, DN_V2F32From1N(0));
|
|
if (DN_RectIntersects(a, b)) {
|
|
DN_V2F32 a_min = a.pos;
|
|
DN_V2F32 a_max = a.pos + a.size;
|
|
DN_V2F32 b_min = b.pos;
|
|
DN_V2F32 b_max = b.pos + b.size;
|
|
|
|
DN_V2F32 min = {};
|
|
DN_V2F32 max = {};
|
|
min.x = DN_Max(a_min.x, b_min.x);
|
|
min.y = DN_Max(a_min.y, b_min.y);
|
|
max.x = DN_Min(a_max.x, b_max.x);
|
|
max.y = DN_Min(a_max.y, b_max.y);
|
|
result = DN_RectFrom2V2(min, max - min);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Rect DN_RectUnion(DN_Rect a, DN_Rect b)
|
|
{
|
|
DN_V2F32 a_min = a.pos;
|
|
DN_V2F32 a_max = a.pos + a.size;
|
|
DN_V2F32 b_min = b.pos;
|
|
DN_V2F32 b_max = b.pos + b.size;
|
|
|
|
DN_V2F32 min, max;
|
|
min.x = DN_Min(a_min.x, b_min.x);
|
|
min.y = DN_Min(a_min.y, b_min.y);
|
|
max.x = DN_Max(a_max.x, b_max.x);
|
|
max.y = DN_Max(a_max.y, b_max.y);
|
|
DN_Rect result = DN_RectFrom2V2(min, max - min);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_2V2F32 DN_RectRange(DN_Rect a)
|
|
{
|
|
DN_2V2F32 result = {};
|
|
result.min = a.pos;
|
|
result.max = a.pos + a.size;
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_RectEq(DN_Rect lhs, DN_Rect rhs)
|
|
{
|
|
bool result = lhs.pos == rhs.pos && lhs.size == rhs.size;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_F32 DN_RectArea(DN_Rect a)
|
|
{
|
|
DN_F32 result = a.size.w * a.size.h;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Rect DN_RectCutLeftClip(DN_Rect *rect, DN_F32 amount, DN_RectCutClip clip)
|
|
{
|
|
DN_F32 min_x = rect->pos.x;
|
|
DN_F32 max_x = rect->pos.x + rect->size.w;
|
|
DN_F32 result_max_x = min_x + amount;
|
|
if (clip)
|
|
result_max_x = DN_Min(result_max_x, max_x);
|
|
DN_Rect result = DN_RectFrom4N(min_x, rect->pos.y, result_max_x - min_x, rect->size.h);
|
|
rect->pos.x = result_max_x;
|
|
rect->size.w = max_x - result_max_x;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Rect DN_RectCutRightClip(DN_Rect *rect, DN_F32 amount, DN_RectCutClip clip)
|
|
{
|
|
DN_F32 min_x = rect->pos.x;
|
|
DN_F32 max_x = rect->pos.x + rect->size.w;
|
|
DN_F32 result_min_x = max_x - amount;
|
|
if (clip)
|
|
result_min_x = DN_Max(result_min_x, 0);
|
|
DN_Rect result = DN_RectFrom4N(result_min_x, rect->pos.y, max_x - result_min_x, rect->size.h);
|
|
rect->size.w = result_min_x - min_x;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Rect DN_RectCutTopClip(DN_Rect *rect, DN_F32 amount, DN_RectCutClip clip)
|
|
{
|
|
DN_F32 min_y = rect->pos.y;
|
|
DN_F32 max_y = rect->pos.y + rect->size.h;
|
|
DN_F32 result_max_y = min_y + amount;
|
|
if (clip)
|
|
result_max_y = DN_Min(result_max_y, max_y);
|
|
DN_Rect result = DN_RectFrom4N(rect->pos.x, min_y, rect->size.w, result_max_y - min_y);
|
|
rect->pos.y = result_max_y;
|
|
rect->size.h = max_y - result_max_y;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Rect DN_RectCutBottomClip(DN_Rect *rect, DN_F32 amount, DN_RectCutClip clip)
|
|
{
|
|
DN_F32 min_y = rect->pos.y;
|
|
DN_F32 max_y = rect->pos.y + rect->size.h;
|
|
DN_F32 result_min_y = max_y - amount;
|
|
if (clip)
|
|
result_min_y = DN_Max(result_min_y, 0);
|
|
DN_Rect result = DN_RectFrom4N(rect->pos.x, result_min_y, rect->size.w, max_y - result_min_y);
|
|
rect->size.h = result_min_y - min_y;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Rect DN_RectCutCut(DN_RectCut rect_cut, DN_V2F32 size, DN_RectCutClip clip)
|
|
{
|
|
DN_Rect result = {};
|
|
if (rect_cut.rect) {
|
|
switch (rect_cut.side) {
|
|
case DN_RectCutSide_Left: result = DN_RectCutLeftClip(rect_cut.rect, size.w, clip); break;
|
|
case DN_RectCutSide_Right: result = DN_RectCutRightClip(rect_cut.rect, size.w, clip); break;
|
|
case DN_RectCutSide_Top: result = DN_RectCutTopClip(rect_cut.rect, size.h, clip); break;
|
|
case DN_RectCutSide_Bottom: result = DN_RectCutBottomClip(rect_cut.rect, size.h, clip); break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2F32 DN_RectInterpV2F32(DN_Rect rect, DN_V2F32 t01)
|
|
{
|
|
DN_V2F32 result = DN_V2F32From2N(rect.pos.w + (rect.size.w * t01.x),
|
|
rect.pos.h + (rect.size.h * t01.y));
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2F32 DN_RectTopLeft(DN_Rect rect)
|
|
{
|
|
DN_V2F32 result = DN_RectInterpV2F32(rect, DN_V2F32From2N(0, 0));
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2F32 DN_RectTopRight(DN_Rect rect)
|
|
{
|
|
DN_V2F32 result = DN_RectInterpV2F32(rect, DN_V2F32From2N(1, 0));
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2F32 DN_RectBottomLeft(DN_Rect rect)
|
|
{
|
|
DN_V2F32 result = DN_RectInterpV2F32(rect, DN_V2F32From2N(0, 1));
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_V2F32 DN_RectBottomRight(DN_Rect rect)
|
|
{
|
|
DN_V2F32 result = DN_RectInterpV2F32(rect, DN_V2F32From2N(1, 1));
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_RaycastV2 DN_RaycastLineIntersectV2(DN_V2F32 origin_a, DN_V2F32 dir_a, DN_V2F32 origin_b, DN_V2F32 dir_b)
|
|
{
|
|
// NOTE: Parametric equation of a line
|
|
//
|
|
// p = o + (t*d)
|
|
//
|
|
// - o is the starting 2d point
|
|
// - d is the direction of the line
|
|
// - t is a scalar that scales along the direction of the point
|
|
//
|
|
// To determine if a ray intersections a ray, we want to solve
|
|
//
|
|
// (o_a + (t_a * d_a)) = (o_b + (t_b * d_b))
|
|
//
|
|
// Where '_a' and '_b' represent the 1st and 2nd point's origin, direction
|
|
// and 't' components respectively. This is 2 equations with 2 unknowns
|
|
// (`t_a` and `t_b`) which we can solve for by expressing the equation in
|
|
// terms of `t_a` and `t_b`.
|
|
//
|
|
// Working that math out produces the formula below for 't'.
|
|
|
|
DN_RaycastV2 result = {};
|
|
DN_F32 denominator = ((dir_b.y * dir_a.x) - (dir_b.x * dir_a.y));
|
|
if (denominator != 0.0f) {
|
|
result.t_a = (((origin_a.y - origin_b.y) * dir_b.x) + ((origin_b.x - origin_a.x) * dir_b.y)) / denominator;
|
|
result.t_b = (((origin_a.y - origin_b.y) * dir_a.x) + ((origin_b.x - origin_a.x) * dir_a.y)) / denominator;
|
|
result.hit = true;
|
|
}
|
|
return result;
|
|
}
|
|
// DN: Single header generator commented out => #include "Base/dn_base_containers.cpp"
|
|
// DN: Single header generator commented out => #if defined(_CLANGD)
|
|
// #include "../dn.h"
|
|
// #endif
|
|
|
|
DN_API void *DN_SliceAllocArena(void **data, DN_USize *slice_size_field, DN_USize size, DN_USize elem_size, DN_U8 align, DN_ZMem zmem, DN_Arena *arena)
|
|
{
|
|
void *result = *data;
|
|
*data = DN_ArenaAlloc(arena, size * elem_size, align, zmem);
|
|
if (*data)
|
|
*slice_size_field = size;
|
|
return result;
|
|
}
|
|
|
|
DN_API void *DN_CArrayInsertArray(void *data, DN_USize *size, DN_USize max, DN_USize elem_size, DN_USize index, void const *items, DN_USize count)
|
|
{
|
|
void *result = nullptr;
|
|
if (!data || !size || !items || count <= 0 || ((*size + count) > max))
|
|
return result;
|
|
|
|
DN_USize clamped_index = DN_Min(index, *size);
|
|
if (clamped_index != *size) {
|
|
char const *src = DN_Cast(char *)data + (clamped_index * elem_size);
|
|
char const *dest = DN_Cast(char *)data + ((clamped_index + count) * elem_size);
|
|
char const *end = DN_Cast(char *)data + (size[0] * elem_size);
|
|
DN_USize bytes_to_move = end - src;
|
|
DN_Memmove(DN_Cast(void *) dest, src, bytes_to_move);
|
|
}
|
|
|
|
result = DN_Cast(char *)data + (clamped_index * elem_size);
|
|
DN_Memcpy(result, items, elem_size * count);
|
|
*size += count;
|
|
return result;
|
|
}
|
|
|
|
DN_API void *DN_CArrayPopFront(void *data, DN_USize *size, DN_USize elem_size, DN_USize count)
|
|
{
|
|
if (!data || !size || *size == 0 || count == 0)
|
|
return nullptr;
|
|
|
|
DN_USize pop_count = DN_Min(count, *size);
|
|
void *result = data;
|
|
|
|
if (pop_count < *size) {
|
|
char *src = DN_Cast(char *)data + (pop_count * elem_size);
|
|
char *dest = DN_Cast(char *)data;
|
|
DN_USize bytes_to_move = (*size - pop_count) * elem_size;
|
|
DN_Memmove(dest, src, bytes_to_move);
|
|
}
|
|
|
|
*size -= pop_count;
|
|
return result;
|
|
}
|
|
|
|
DN_API void *DN_CArrayPopBack(void *data, DN_USize *size, DN_USize elem_size, DN_USize count)
|
|
{
|
|
if (!data || !size || *size == 0 || count == 0)
|
|
return nullptr;
|
|
|
|
DN_USize pop_count = DN_Min(count, *size);
|
|
*size -= pop_count;
|
|
|
|
return DN_Cast(char *)data + (*size * elem_size);
|
|
}
|
|
|
|
DN_API DN_ArrayEraseResult DN_CArrayEraseRange(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) {
|
|
// Erase backwards from begin_index, inclusive of begin_index
|
|
// Range: [begin_index + count + 1, begin_index + 1)
|
|
// Which is: [begin_index - abs(count) + 1, begin_index + 1)
|
|
DN_USize abs_count = DN_Abs(count);
|
|
start = (begin_index + 1 > abs_count) ? (begin_index + 1 - abs_count) : 0;
|
|
end = begin_index + 1;
|
|
} 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_CArrayMakeArray(void *data, DN_USize *size, DN_USize max, DN_USize data_size, DN_USize make_size, DN_ZMem z_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 (z_mem == DN_ZMem_Yes)
|
|
DN_Memset(result, 0, data_size * make_size);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
DN_API void *DN_CArrayAddArray(void *data, DN_USize *size, DN_USize max, DN_USize data_size, void const *elems, DN_USize elems_count, DN_ArrayAdd add)
|
|
{
|
|
void *result = DN_CArrayMakeArray(data, size, max, data_size, elems_count, DN_ZMem_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_CArrayResizeFromArena(void **data, DN_USize *size, DN_USize *max, DN_USize data_size, DN_Pool *pool, DN_USize new_max)
|
|
{
|
|
bool result = true;
|
|
if (new_max != *max) {
|
|
DN_USize bytes_to_alloc = data_size * new_max;
|
|
void *buffer = DN_PoolNewArray(pool, DN_U8, bytes_to_alloc);
|
|
if (buffer) {
|
|
DN_USize bytes_to_copy = data_size * DN_Min(*size, new_max);
|
|
DN_Memcpy(buffer, *data, bytes_to_copy);
|
|
DN_PoolDealloc(pool, *data);
|
|
*data = buffer;
|
|
*max = new_max;
|
|
*size = DN_Min(*size, new_max);
|
|
} else {
|
|
result = false;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_CArrayResizeFromPool(void **data, DN_USize *size, DN_USize *max, DN_USize data_size, DN_Pool *pool, DN_USize new_max)
|
|
{
|
|
bool result = true;
|
|
if (new_max != *max) {
|
|
DN_USize bytes_to_alloc = data_size * new_max;
|
|
void *buffer = DN_PoolNewArray(pool, DN_U8, bytes_to_alloc);
|
|
if (buffer) {
|
|
DN_USize bytes_to_copy = data_size * DN_Min(*size, new_max);
|
|
DN_Memcpy(buffer, *data, bytes_to_copy);
|
|
DN_PoolDealloc(pool, *data);
|
|
*data = buffer;
|
|
*max = new_max;
|
|
*size = DN_Min(*size, new_max);
|
|
} else {
|
|
result = false;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_CArrayResizeFromArena(void **data, DN_USize *size, DN_USize *max, DN_USize data_size, DN_Arena *arena, DN_USize new_max)
|
|
{
|
|
bool result = true;
|
|
if (new_max != *max) {
|
|
DN_USize bytes_to_alloc = data_size * new_max;
|
|
void *buffer = DN_ArenaNewArray(arena, DN_U8, bytes_to_alloc, DN_ZMem_No);
|
|
if (buffer) {
|
|
DN_USize bytes_to_copy = data_size * DN_Min(*size, new_max);
|
|
DN_Memcpy(buffer, *data, bytes_to_copy);
|
|
*data = buffer;
|
|
*max = new_max;
|
|
*size = DN_Min(*size, new_max);
|
|
} else {
|
|
result = false;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_CArrayGrowFromPool(void **data, DN_USize size, DN_USize *max, DN_USize data_size, DN_Pool *pool, DN_USize new_max)
|
|
{
|
|
bool result = true;
|
|
if (new_max > *max)
|
|
result = DN_CArrayResizeFromPool(data, &size, max, data_size, pool, new_max);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_CArrayGrowFromArena(void **data, DN_USize size, DN_USize *max, DN_USize data_size, DN_Arena *arena, DN_USize new_max)
|
|
{
|
|
bool result = true;
|
|
if (new_max > *max)
|
|
result = DN_CArrayResizeFromArena(data, &size, max, data_size, arena, new_max);
|
|
return result;
|
|
}
|
|
|
|
|
|
DN_API bool DN_CArrayGrowIfNeededFromPool(void **data, DN_USize size, DN_USize *max, DN_USize data_size, DN_Pool *pool, DN_USize add_count)
|
|
{
|
|
bool result = true;
|
|
DN_USize new_size = size + add_count;
|
|
if (new_size > *max) {
|
|
DN_USize new_max = DN_Max(DN_Max(*max * 2, new_size), 8);
|
|
result = DN_CArrayResizeFromPool(data, &size, max, data_size, pool, new_max);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_CArrayGrowIfNeededFromArena(void **data, DN_USize size, DN_USize *max, DN_USize data_size, DN_Arena *arena, DN_USize add_count)
|
|
{
|
|
bool result = true;
|
|
DN_USize new_size = size + add_count;
|
|
if (new_size > *max) {
|
|
DN_USize new_max = DN_Max(DN_Max(*max * 2, new_size), 8);
|
|
result = DN_CArrayResizeFromArena(data, &size, max, data_size, arena, new_max);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API void *DN_SinglyLLDetach(void **link, void **next)
|
|
{
|
|
void *result = *link;
|
|
if (*link) {
|
|
*link = *next;
|
|
*next = nullptr;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_RingHasSpace(DN_Ring const *ring, DN_U64 size)
|
|
{
|
|
DN_U64 avail = ring->write_pos - ring->read_pos;
|
|
DN_U64 space = ring->size - avail;
|
|
bool result = space >= size;
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_RingHasData(DN_Ring const *ring, DN_U64 size)
|
|
{
|
|
DN_U64 data = ring->write_pos - ring->read_pos;
|
|
bool result = data >= size;
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_RingWrite(DN_Ring *ring, void const *src, DN_U64 src_size)
|
|
{
|
|
DN_Assert(src_size <= ring->size);
|
|
DN_U64 offset = ring->write_pos % ring->size;
|
|
DN_U64 bytes_before_split = ring->size - offset;
|
|
DN_U64 pre_split_bytes = DN_Min(bytes_before_split, src_size);
|
|
DN_U64 post_split_bytes = src_size - pre_split_bytes;
|
|
void const *pre_split_data = src;
|
|
void const *post_split_data = ((char *)src + pre_split_bytes);
|
|
DN_Memcpy(ring->base + offset, pre_split_data, pre_split_bytes);
|
|
DN_Memcpy(ring->base, post_split_data, post_split_bytes);
|
|
ring->write_pos += src_size;
|
|
}
|
|
|
|
DN_API void DN_RingRead(DN_Ring *ring, void *dest, DN_U64 dest_size)
|
|
{
|
|
DN_Assert(dest_size <= ring->size);
|
|
DN_U64 offset = ring->read_pos % ring->size;
|
|
DN_U64 bytes_before_split = ring->size - offset;
|
|
DN_U64 pre_split_bytes = DN_Min(bytes_before_split, dest_size);
|
|
DN_U64 post_split_bytes = dest_size - pre_split_bytes;
|
|
DN_Memcpy(dest, ring->base + offset, pre_split_bytes);
|
|
DN_Memcpy((char *)dest + pre_split_bytes, ring->base, post_split_bytes);
|
|
ring->read_pos += dest_size;
|
|
}
|
|
|
|
template <typename T>
|
|
DN_DSMap<T> DN_DSMapInit(DN_Arena *arena, DN_U32 size, DN_DSMapFlags flags)
|
|
{
|
|
DN_DSMap<T> 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_PoolFromArena(arena, DN_POOL_DEFAULT_ALIGN);
|
|
result.hash_to_slot = DN_ArenaNewArray(result.arena, DN_U32, size, DN_ZMem_Yes);
|
|
result.slots = DN_ArenaNewArray(result.arena, DN_DSMapSlot<T>, size, DN_ZMem_Yes);
|
|
result.occupied = 1; // For sentinel
|
|
result.size = size;
|
|
result.initial_size = size;
|
|
result.flags = flags;
|
|
DN_AssertF(result.hash_to_slot && result.slots, "We pre-allocated a block of memory sufficient in size for the 2 arrays. Maybe the pointers needed extra space because of natural alignment?");
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
void DN_DSMapDeinit(DN_DSMap<T> *map, DN_ZMem z_mem)
|
|
{
|
|
if (!map)
|
|
return;
|
|
// TODO(doyle): Use z_mem
|
|
(void)z_mem;
|
|
DN_MemListDeinit(map->arena->mem);
|
|
*map = {};
|
|
}
|
|
|
|
template <typename T>
|
|
bool DN_DSMapIsValid(DN_DSMap<T> 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 <typename T>
|
|
DN_U32 DN_DSMapHash(DN_DSMap<T> 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 <typename T>
|
|
DN_U32 DN_DSMapHashToSlotIndex(DN_DSMap<T> const *map, DN_DSMapKey key)
|
|
{
|
|
DN_Assert(key.type != DN_DSMapKeyType_Invalid);
|
|
DN_U32 result = DN_DS_MAP_SENTINEL_SLOT;
|
|
if (!DN_DSMapIsValid(map))
|
|
return result;
|
|
|
|
result = key.hash & (map->size - 1);
|
|
for (;;) {
|
|
if (result == DN_DS_MAP_SENTINEL_SLOT) // Sentinel is reserved
|
|
result++;
|
|
|
|
if (map->hash_to_slot[result] == DN_DS_MAP_SENTINEL_SLOT) // Slot is vacant, can use
|
|
return result;
|
|
|
|
DN_DSMapSlot<T> *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 <typename T>
|
|
DN_DSMapResult<T> DN_DSMapFind(DN_DSMap<T> const *map, DN_DSMapKey key)
|
|
{
|
|
DN_DSMapResult<T> result = {};
|
|
if (DN_DSMapIsValid(map)) {
|
|
DN_U32 index = DN_DSMapHashToSlotIndex(map, key);
|
|
if (index != DN_DS_MAP_SENTINEL_SLOT && map->hash_to_slot[index] == DN_DS_MAP_SENTINEL_SLOT) {
|
|
result.slot = map->slots; // NOTE: Set to sentinel value
|
|
} else {
|
|
result.slot = map->slots + map->hash_to_slot[index];
|
|
result.found = true;
|
|
}
|
|
result.value = &result.slot->value;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
DN_DSMapResult<T> DN_DSMapMake(DN_DSMap<T> *map, DN_DSMapKey key)
|
|
{
|
|
DN_DSMapResult<T> result = {};
|
|
if (!DN_DSMapIsValid(map))
|
|
return result;
|
|
|
|
DN_U32 index = DN_DSMapHashToSlotIndex(map, key);
|
|
if (map->hash_to_slot[index] == DN_DS_MAP_SENTINEL_SLOT) {
|
|
// NOTE: Create the slot
|
|
if (index != DN_DS_MAP_SENTINEL_SLOT)
|
|
map->hash_to_slot[index] = map->occupied++;
|
|
|
|
// NOTE: Check if resize is required
|
|
bool map_is_75pct_full = (map->occupied * 4) > (map->size * 3);
|
|
if (map_is_75pct_full) {
|
|
if (!DN_DSMapResize(map, map->size * 2))
|
|
return result;
|
|
result = DN_DSMapMake(map, key);
|
|
} else {
|
|
result.slot = map->slots + map->hash_to_slot[index];
|
|
result.slot->key = key; // NOTE: Assign key to new slot
|
|
if ((key.type == DN_DSMapKeyType_Buffer ||
|
|
key.type == DN_DSMapKeyType_BufferAsU64NoHash) &&
|
|
!key.no_copy_buffer)
|
|
result.slot->key.buffer_data = DN_PoolNewArrayCopy(&map->pool, char, key.buffer_data, key.buffer_size);
|
|
}
|
|
} else {
|
|
result.slot = map->slots + map->hash_to_slot[index];
|
|
result.found = true;
|
|
}
|
|
|
|
result.value = &result.slot->value;
|
|
DN_Assert(result.slot->key.type != DN_DSMapKeyType_Invalid);
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
DN_DSMapResult<T> DN_DSMapSet(DN_DSMap<T> *map, DN_DSMapKey key, T const &value)
|
|
{
|
|
DN_DSMapResult<T> result = {};
|
|
if (!DN_DSMapIsValid(map))
|
|
return result;
|
|
|
|
result = DN_DSMapMake(map, key);
|
|
result.slot->value = value;
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
DN_DSMapResult<T> DN_DSMapFindKeyU64(DN_DSMap<T> const *map, DN_U64 key)
|
|
{
|
|
DN_DSMapKey map_key = DN_DSMapKeyU64(map, key);
|
|
DN_DSMapResult<T> result = DN_DSMapFind(map, map_key);
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
DN_DSMapResult<T> DN_DSMapMakeKeyU64(DN_DSMap<T> *map, DN_U64 key)
|
|
{
|
|
DN_DSMapKey map_key = DN_DSMapKeyU64(map, key);
|
|
DN_DSMapResult<T> result = DN_DSMapMake(map, map_key);
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
DN_DSMapResult<T> DN_DSMapSetKeyU64(DN_DSMap<T> *map, DN_U64 key, T const &value)
|
|
{
|
|
DN_DSMapKey map_key = DN_DSMapKeyU64(map, key);
|
|
DN_DSMapResult<T> result = DN_DSMapSet(map, map_key, value);
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
DN_DSMapResult<T> DN_DSMapFindKeyStr8(DN_DSMap<T> const *map, DN_Str8 key)
|
|
{
|
|
DN_DSMapKey map_key = DN_DSMapKeyStr8(map, key);
|
|
DN_DSMapResult<T> result = DN_DSMapFind(map, map_key);
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
DN_DSMapResult<T> DN_DSMapMakeKeyStr8(DN_DSMap<T> *map, DN_Str8 key)
|
|
{
|
|
DN_DSMapKey map_key = DN_DSMapKeyStr8(map, key);
|
|
DN_DSMapResult<T> result = DN_DSMapMake(map, map_key);
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
DN_DSMapResult<T> DN_DSMapSetKeyStr8(DN_DSMap<T> *map, DN_Str8 key, T const &value)
|
|
{
|
|
DN_DSMapKey map_key = DN_DSMapKeyStr8(map, key);
|
|
DN_DSMapResult<T> result = DN_DSMapSet(map, map_key);
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
bool DN_DSMapResize(DN_DSMap<T> *map, DN_U32 size)
|
|
{
|
|
if (!DN_DSMapIsValid(map) || size < map->occupied || size < map->initial_size)
|
|
return false;
|
|
|
|
DN_Arena *prev_arena = map->arena;
|
|
DN_MemList *new_mem = prev_arena->mem;
|
|
DN_MemList prev_mem = *prev_arena->mem;
|
|
prev_arena->mem = &prev_mem;
|
|
|
|
*new_mem = {};
|
|
new_mem->funcs = prev_mem.funcs;
|
|
new_mem->flags = prev_mem.flags;
|
|
|
|
DN_Arena new_arena = {};
|
|
new_arena.mem = new_mem;
|
|
|
|
DN_DSMap<T> new_map = DN_DSMapInit<T>(&new_arena, size, map->flags);
|
|
if (!DN_DSMapIsValid(&new_map))
|
|
return false;
|
|
|
|
new_map.initial_size = map->initial_size;
|
|
for (DN_U32 old_index = 1 /*Sentinel*/; old_index < map->occupied; old_index++) {
|
|
DN_DSMapSlot<T> *old_slot = map->slots + old_index;
|
|
DN_DSMapKey old_key = old_slot->key;
|
|
if (old_key.type == DN_DSMapKeyType_Invalid)
|
|
continue;
|
|
DN_DSMapSet(&new_map, old_key, old_slot->value);
|
|
}
|
|
|
|
if ((map->flags & DN_DSMapFlags_DontFreeArenaOnResize) == 0)
|
|
DN_DSMapDeinit(map, DN_ZMem_No);
|
|
*map = new_map; // Update the map inplace
|
|
map->arena = prev_arena; // Restore the previous arena pointer, it's been de-init-ed
|
|
*map->arena = new_arena; // Re-init the old arena with the new data
|
|
map->pool.arena = map->arena;
|
|
return true;
|
|
}
|
|
|
|
template <typename T>
|
|
bool DN_DSMapErase(DN_DSMap<T> *map, DN_DSMapKey key)
|
|
{
|
|
if (!DN_DSMapIsValid(map))
|
|
return false;
|
|
|
|
DN_U32 index = DN_DSMapHashToSlotIndex(map, key);
|
|
if (index == 0)
|
|
return true;
|
|
|
|
DN_U32 slot_index = map->hash_to_slot[index];
|
|
if (slot_index == DN_DS_MAP_SENTINEL_SLOT)
|
|
return false;
|
|
|
|
// NOTE: Mark the slot as unoccupied
|
|
map->hash_to_slot[index] = DN_DS_MAP_SENTINEL_SLOT;
|
|
|
|
DN_DSMapSlot<T> *slot = map->slots + slot_index;
|
|
if (!slot->key.no_copy_buffer)
|
|
DN_PoolDealloc(&map->pool, DN_Cast(void *) slot->key.buffer_data);
|
|
*slot = {}; // TODO: Optional?
|
|
|
|
if (map->occupied > 1 /*Sentinel*/) {
|
|
// NOTE: Repair the hash chain, e.g. rehash all the items after the removed
|
|
// element and reposition them if necessary.
|
|
for (DN_U32 probe_index = index;;) {
|
|
probe_index = (probe_index + 1) & (map->size - 1);
|
|
if (map->hash_to_slot[probe_index] == DN_DS_MAP_SENTINEL_SLOT)
|
|
break;
|
|
|
|
DN_DSMapSlot<T> *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<T> *last_slot = map->slots + last_index;
|
|
map->slots[slot_index] = *last_slot;
|
|
|
|
// NOTE: Update the hash-to-slot mapping for the value that was copied in
|
|
DN_U32 hash_to_slot_index = DN_DSMapHashToSlotIndex(map, last_slot->key);
|
|
map->hash_to_slot[hash_to_slot_index] = slot_index;
|
|
*last_slot = {}; // TODO: Optional?
|
|
}
|
|
}
|
|
}
|
|
|
|
map->occupied--;
|
|
bool map_is_below_25pct_full = (map->occupied * 4) < (map->size * 1);
|
|
if (map_is_below_25pct_full && (map->size / 2) >= map->initial_size)
|
|
DN_DSMapResize(map, map->size / 2);
|
|
|
|
return true;
|
|
}
|
|
|
|
template <typename T>
|
|
bool DN_DSMapEraseKeyU64(DN_DSMap<T> *map, DN_U64 key)
|
|
{
|
|
DN_DSMapKey map_key = DN_DSMapKeyU64(map, key);
|
|
bool result = DN_DSMapErase(map, map_key);
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
bool DN_DSMapEraseKeyStr8(DN_DSMap<T> *map, DN_Str8 key)
|
|
{
|
|
DN_DSMapKey map_key = DN_DSMapKeyStr8(map, key);
|
|
bool result = DN_DSMapErase(map, map_key);
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
DN_DSMapKey DN_DSMapKeyBuffer(DN_DSMap<T> const *map, void const *data, DN_USize size)
|
|
{
|
|
DN_Assert(size > 0 && size <= UINT32_MAX);
|
|
DN_DSMapKey result = {};
|
|
result.type = DN_DSMapKeyType_Buffer;
|
|
result.buffer_data = data;
|
|
result.buffer_size = DN_Cast(DN_U32) size;
|
|
result.hash = DN_DSMapHash(map, result);
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
DN_DSMapKey DN_DSMapKeyBufferAsU64NoHash(DN_DSMap<T> const *map, void const *data, DN_USize size)
|
|
{
|
|
DN_DSMapKey result = {};
|
|
result.type = DN_DSMapKeyType_BufferAsU64NoHash;
|
|
result.buffer_data = data;
|
|
result.buffer_size = DN_Cast(DN_U32) size;
|
|
DN_Assert(size >= sizeof(result.hash));
|
|
DN_Memcpy(&result.hash, data, sizeof(result.hash));
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
DN_DSMapKey DN_DSMapKeyU64(DN_DSMap<T> const *map, DN_U64 u64)
|
|
{
|
|
DN_DSMapKey result = {};
|
|
result.type = DN_DSMapKeyType_U64;
|
|
result.u64 = u64;
|
|
result.hash = DN_DSMapHash(map, result);
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
DN_DSMapKey DN_DSMapKeyStr8(DN_DSMap<T> const *map, DN_Str8 string)
|
|
{
|
|
DN_DSMapKey result = DN_DSMapKeyBuffer(map, string.data, string.size);
|
|
return result;
|
|
}
|
|
|
|
// NOTE: DN_DSMap
|
|
DN_API DN_DSMapKey DN_DSMapKeyU64NoHash(DN_U64 u64)
|
|
{
|
|
DN_DSMapKey result = {};
|
|
result.type = DN_DSMapKeyType_U64NoHash;
|
|
result.u64 = u64;
|
|
result.hash = DN_Cast(DN_U32) u64;
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_DSMapKeyEquals(DN_DSMapKey lhs, DN_DSMapKey rhs)
|
|
{
|
|
bool result = false;
|
|
if (lhs.type == rhs.type && lhs.hash == rhs.hash) {
|
|
switch (lhs.type) {
|
|
case DN_DSMapKeyType_Invalid: result = true; break;
|
|
case DN_DSMapKeyType_U64NoHash: result = true; break;
|
|
case DN_DSMapKeyType_U64: result = lhs.u64 == rhs.u64; break;
|
|
|
|
case DN_DSMapKeyType_BufferAsU64NoHash: /*FALLTHRU*/
|
|
case DN_DSMapKeyType_Buffer: {
|
|
if (lhs.buffer_size == rhs.buffer_size)
|
|
result = DN_Memcmp(lhs.buffer_data, rhs.buffer_data, lhs.buffer_size) == 0;
|
|
} break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API bool operator==(DN_DSMapKey lhs, DN_DSMapKey rhs)
|
|
{
|
|
bool result = DN_DSMapKeyEquals(lhs, rhs);
|
|
return result;
|
|
}
|
|
// DN: Single header generator commented out => #include "Base/dn_base_leak.cpp"
|
|
#define DN_BASE_LEAK_CPP
|
|
|
|
// DN: Single header generator commented out => #if defined(_CLANGD)
|
|
// #include "../dn.h"
|
|
// #endif
|
|
|
|
DN_API void DN_LeakTrackAlloc_(DN_LeakTracker *leak, void *ptr, DN_USize size, bool leak_permitted)
|
|
{
|
|
if (!ptr)
|
|
return;
|
|
|
|
DN_TicketMutex_Begin(&leak->alloc_table_mutex);
|
|
DN_DEFER
|
|
{
|
|
DN_TicketMutex_End(&leak->alloc_table_mutex);
|
|
};
|
|
|
|
DN_Str8 stack_trace = DN_StackTraceWalkStr8FromHeap(128, 3 /*skip*/);
|
|
DN_DSMap<DN_LeakAlloc> *alloc_table = &leak->alloc_table;
|
|
DN_DSMapResult<DN_LeakAlloc> alloc_entry = DN_DSMapMakeKeyU64(alloc_table, DN_Cast(DN_U64) ptr);
|
|
DN_LeakAlloc *alloc = alloc_entry.value;
|
|
if (alloc_entry.found) {
|
|
if ((alloc->flags & DN_LeakAllocFlag_Freed) == 0) {
|
|
DN_Str8x32 alloc_size = DN_ByteCountStr8x32(alloc->size);
|
|
DN_Str8x32 new_alloc_size = DN_ByteCountStr8x32(size);
|
|
DN_HardAssertF(
|
|
alloc->flags & DN_LeakAllocFlag_Freed,
|
|
"This pointer is already in the leak tracker, however it has not been freed yet. This "
|
|
"same pointer is being ask to be tracked twice in the allocation table, e.g. one if its "
|
|
"previous free calls has not being marked freed with an equivalent call to "
|
|
"DN_LeakTrackDealloc()\n"
|
|
"\n"
|
|
"The pointer (0x%p) originally allocated %.*s at:\n"
|
|
"\n"
|
|
"%.*s\n"
|
|
"\n"
|
|
"The pointer is allocating %.*s again at:\n"
|
|
"\n"
|
|
"%.*s\n",
|
|
ptr,
|
|
DN_Str8PrintFmt(alloc_size),
|
|
DN_Str8PrintFmt(alloc->stack_trace),
|
|
DN_Str8PrintFmt(new_alloc_size),
|
|
DN_Str8PrintFmt(stack_trace));
|
|
}
|
|
|
|
// NOTE: Pointer was reused, clean up the prior entry
|
|
leak->alloc_table_bytes_allocated_for_stack_traces -= alloc->stack_trace.size;
|
|
leak->alloc_table_bytes_allocated_for_stack_traces -= alloc->freed_stack_trace.size;
|
|
|
|
DN_OS_MemDealloc(alloc->stack_trace.data);
|
|
DN_OS_MemDealloc(alloc->freed_stack_trace.data);
|
|
*alloc = {};
|
|
}
|
|
|
|
alloc->ptr = ptr;
|
|
alloc->size = size;
|
|
alloc->stack_trace = stack_trace;
|
|
alloc->flags |= leak_permitted ? DN_LeakAllocFlag_LeakPermitted : 0;
|
|
leak->alloc_table_bytes_allocated_for_stack_traces += alloc->stack_trace.size;
|
|
}
|
|
|
|
DN_API void DN_LeakTrackDealloc_(DN_LeakTracker *leak, void *ptr)
|
|
{
|
|
if (!ptr)
|
|
return;
|
|
|
|
DN_TicketMutex_Begin(&leak->alloc_table_mutex);
|
|
DN_DEFER
|
|
{
|
|
DN_TicketMutex_End(&leak->alloc_table_mutex);
|
|
};
|
|
|
|
DN_Str8 stack_trace = DN_StackTraceWalkStr8FromHeap(128, 3 /*skip*/);
|
|
DN_DSMap<DN_LeakAlloc> *alloc_table = &leak->alloc_table;
|
|
DN_DSMapResult<DN_LeakAlloc> alloc_entry = DN_DSMapFindKeyU64(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_LeakAlloc *alloc = alloc_entry.value;
|
|
if (alloc->flags & DN_LeakAllocFlag_Freed) {
|
|
DN_Str8x32 freed_size = DN_ByteCountStr8x32(alloc->freed_size);
|
|
DN_HardAssertF((alloc->flags & DN_LeakAllocFlag_Freed) == 0,
|
|
"Double free detected, pointer to free was already marked "
|
|
"as freed. Either the pointer was reallocated but not "
|
|
"traced, or, the pointer was freed twice.\n"
|
|
"\n"
|
|
"The pointer (0x%p) originally allocated %.*s at:\n"
|
|
"\n"
|
|
"%.*s\n"
|
|
"\n"
|
|
"The pointer was freed at:\n"
|
|
"\n"
|
|
"%.*s\n"
|
|
"\n"
|
|
"The pointer is being freed again at:\n"
|
|
"\n"
|
|
"%.*s\n",
|
|
ptr,
|
|
DN_Str8PrintFmt(freed_size),
|
|
DN_Str8PrintFmt(alloc->stack_trace),
|
|
DN_Str8PrintFmt(alloc->freed_stack_trace),
|
|
DN_Str8PrintFmt(stack_trace));
|
|
}
|
|
|
|
DN_Assert(alloc->freed_stack_trace.size == 0);
|
|
alloc->flags |= DN_LeakAllocFlag_Freed;
|
|
alloc->freed_stack_trace = stack_trace;
|
|
leak->alloc_table_bytes_allocated_for_stack_traces += alloc->freed_stack_trace.size;
|
|
}
|
|
|
|
DN_API void DN_LeakDump_(DN_LeakTracker *leak)
|
|
{
|
|
DN_U64 leak_count = 0;
|
|
DN_U64 leaked_bytes = 0;
|
|
for (DN_USize index = 1; index < leak->alloc_table.occupied; index++) {
|
|
DN_DSMapSlot<DN_LeakAlloc> *slot = leak->alloc_table.slots + index;
|
|
DN_LeakAlloc *alloc = &slot->value;
|
|
bool alloc_leaked = (alloc->flags & DN_LeakAllocFlag_Freed) == 0;
|
|
bool leak_permitted = (alloc->flags & DN_LeakAllocFlag_LeakPermitted);
|
|
if (alloc_leaked && !leak_permitted) {
|
|
leaked_bytes += alloc->size;
|
|
leak_count++;
|
|
DN_Str8x32 alloc_size = DN_ByteCountStr8x32(alloc->size);
|
|
DN_LogWarningF(
|
|
"Pointer (0x%p) leaked %.*s at:\n"
|
|
"%.*s",
|
|
alloc->ptr,
|
|
DN_Str8PrintFmt(alloc_size),
|
|
DN_Str8PrintFmt(alloc->stack_trace));
|
|
}
|
|
}
|
|
|
|
if (leak_count) {
|
|
DN_Str8x32 leak_size = DN_ByteCountStr8x32(leaked_bytes);
|
|
DN_LogWarningF("There were %I64u leaked allocations totalling %.*s", leak_count, DN_Str8PrintFmt(leak_size));
|
|
}
|
|
}
|
|
|
|
DN_Core *g_dn_;
|
|
|
|
#if DN_H_WITH_OS
|
|
// DN: Single header generator commented out => #include "OS/dn_os.cpp"
|
|
#define DN_OS_CPP
|
|
|
|
// DN: Single header generator commented out => #if defined(_CLANGD)
|
|
// #define DN_H_WITH_OS 1
|
|
// #define DN_H_WITH_CORE 1
|
|
// #include "../dn.h"
|
|
// #endif
|
|
|
|
#if defined(DN_PLATFORM_POSIX)
|
|
#include <sys/sysinfo.h> // get_nprocs
|
|
#include <unistd.h> // getpagesize
|
|
#endif
|
|
|
|
static void *DN_OS_MemFuncsHeapAllocShim_(DN_USize size)
|
|
{
|
|
void *result = DN_OS_MemAlloc(size, DN_ZMem_Yes);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_MemFuncs DN_MemFuncsFromType(DN_MemFuncsType type)
|
|
{
|
|
DN_MemFuncs result = {};
|
|
result.type = type;
|
|
switch (type) {
|
|
case DN_MemFuncsType_Nil: break;
|
|
case DN_MemFuncsType_Heap: {
|
|
result.heap_alloc = DN_OS_MemFuncsHeapAllocShim_;
|
|
result.heap_dealloc = DN_OS_MemDealloc;
|
|
} break;
|
|
|
|
case DN_MemFuncsType_Virtual: {
|
|
DN_Core *dn = DN_Get();
|
|
DN_Assert(dn->init_flags & DN_InitFlags_OS);
|
|
result.virtual_page_size = dn->os.page_size;
|
|
result.virtual_reserve = DN_OS_MemReserve;
|
|
result.virtual_commit = DN_OS_MemCommit;
|
|
result.virtual_release = DN_OS_MemRelease;
|
|
} break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_MemFuncs DN_MemFuncsDefault()
|
|
{
|
|
DN_Core *dn = DN_Get();
|
|
DN_MemFuncsType type = DN_MemFuncsType_Heap;
|
|
if (dn->os_init) {
|
|
#if !defined(DN_PLATFORM_EMSCRIPTEN)
|
|
type = DN_MemFuncsType_Virtual;
|
|
#endif
|
|
}
|
|
DN_MemFuncs result = DN_MemFuncsFromType(type);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_MemList DN_MemListFromHeap(DN_U64 size, DN_MemFlags flags)
|
|
{
|
|
DN_MemFuncs mem_funcs = DN_MemFuncsFromType(DN_MemFuncsType_Heap);
|
|
DN_MemList result = DN_MemListFromMemFuncs(size, size, flags, mem_funcs);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_MemList DN_MemListFromVMem(DN_U64 reserve, DN_U64 commit, DN_MemFlags flags)
|
|
{
|
|
DN_MemFuncs mem_funcs = DN_MemFuncsFromType(DN_MemFuncsType_Virtual);
|
|
DN_MemList result = DN_MemListFromMemFuncs(reserve, commit, flags, mem_funcs);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8FromHeapF(DN_FMT_ATTRIB char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
DN_USize size = DN_FmtVSize(fmt, args);
|
|
DN_Str8 result = DN_Str8FromHeap(size, DN_ZMem_No);
|
|
DN_VSNPrintF(result.data, DN_Cast(int)(result.size + 1), fmt, args);
|
|
va_end(args);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8FromHeap(DN_USize size, DN_ZMem z_mem)
|
|
{
|
|
DN_Str8 result = {};
|
|
result.data = DN_Cast(char *)DN_OS_MemAlloc(size + 1, z_mem);
|
|
if (result.data) {
|
|
result.size = size;
|
|
result.data[result.size] = 0;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8PadNewLines(DN_Arena *arena, DN_Str8 src, DN_Str8 pad)
|
|
{
|
|
// TODO: Implement this without requiring TLS so it can go into base strings
|
|
DN_TCScratch scratch = DN_TCScratchBegin(&arena, 1);
|
|
DN_Str8Builder builder = DN_Str8BuilderFromArena(&scratch.arena);
|
|
DN_Str8BSplitResult split = DN_Str8BSplit(src, DN_Str8Lit("\n"));
|
|
while (split.lhs.size) {
|
|
DN_Str8BuilderAppendRef(&builder, pad);
|
|
DN_Str8BuilderAppendRef(&builder, split.lhs);
|
|
split = DN_Str8BSplit(split.rhs, DN_Str8Lit("\n"));
|
|
if (split.lhs.size)
|
|
DN_Str8BuilderAppendRef(&builder, DN_Str8Lit("\n"));
|
|
}
|
|
DN_Str8 result = DN_Str8BuilderBuild(&builder, arena);
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_Str8BuilderBuildFromHeap(DN_Str8Builder const *builder)
|
|
{
|
|
DN_Str8 result = DN_ZeroInit;
|
|
if (!builder || builder->string_size <= 0 || builder->count <= 0)
|
|
return result;
|
|
|
|
result.data = DN_Cast(char *) DN_OS_MemAlloc(builder->string_size + 1, DN_ZMem_No);
|
|
if (!result.data)
|
|
return result;
|
|
|
|
for (DN_Str8Link *link = builder->head; link; link = link->next) {
|
|
DN_Memcpy(result.data + result.size, link->string.data, link->string.size);
|
|
result.size += link->string.size;
|
|
}
|
|
|
|
result.data[result.size] = 0;
|
|
DN_Assert(result.size == builder->string_size);
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_OS_LogPrint(DN_LogTypeParam type, void *user_data, DN_CallSite call_site, DN_LogFlags flags, DN_FMT_ATTRIB char const *fmt, va_list args)
|
|
{
|
|
DN_Assert(user_data);
|
|
DN_OSCore *os = DN_Cast(DN_OSCore *)user_data;
|
|
|
|
// NOTE: Open log file for appending if requested
|
|
DN_TicketMutex_Begin(&os->log_file_mutex);
|
|
if (os->log_to_file && !os->log_file.handle && !os->log_file.error) {
|
|
DN_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_Str8 exe_dir = DN_OS_EXEDir(&scratch.arena);
|
|
DN_Str8 log_path = DN_OS_PathF(&scratch.arena, "%.*s/dn.log", DN_Str8PrintFmt(exe_dir));
|
|
os->log_file = DN_OS_FileOpen(log_path, DN_OSFileOpen_CreateAlways, DN_OSFileAccess_AppendOnly, nullptr);
|
|
DN_TCScratchEnd(&scratch);
|
|
}
|
|
DN_TicketMutex_End(&os->log_file_mutex);
|
|
|
|
bool print_prefix = DN_BitIsNotSet(flags, DN_LogFlags_NoPrefix);
|
|
char prefix_buffer[128] = {};
|
|
DN_LogPrefixSize prefix_size = {};
|
|
if (print_prefix) {
|
|
DN_LogStyle style = {};
|
|
if (!os->log_no_colour) {
|
|
style.colour = true;
|
|
style.bold = DN_LogBold_Yes;
|
|
if (type.is_u32_enum) {
|
|
switch (type.u32) {
|
|
case DN_LogType_Debug: {
|
|
style.colour = false;
|
|
style.bold = DN_LogBold_No;
|
|
} break;
|
|
|
|
case DN_LogType_Info: {
|
|
style.g = 0x87;
|
|
style.b = 0xff;
|
|
} break;
|
|
|
|
case DN_LogType_Warning: {
|
|
style.r = 0xff;
|
|
style.g = 0xff;
|
|
} break;
|
|
|
|
case DN_LogType_Error: {
|
|
style.r = 0xff;
|
|
} break;
|
|
}
|
|
}
|
|
}
|
|
|
|
DN_Date os_date = DN_OS_DateLocalTimeNow();
|
|
DN_LogDate log_date = {};
|
|
log_date.year = os_date.year;
|
|
log_date.month = os_date.month;
|
|
log_date.day = os_date.day;
|
|
log_date.hour = os_date.hour;
|
|
log_date.minute = os_date.minutes;
|
|
log_date.second = os_date.seconds;
|
|
prefix_size = DN_LogMakePrefix(style, type, call_site, log_date, prefix_buffer, sizeof(prefix_buffer));
|
|
}
|
|
|
|
va_list args_copy;
|
|
va_copy(args_copy, args);
|
|
DN_TicketMutex_Begin(&os->log_file_mutex);
|
|
{
|
|
if (print_prefix) {
|
|
DN_OS_FileWrite(&os->log_file, DN_Str8FromPtr(prefix_buffer, prefix_size.size), nullptr);
|
|
DN_OS_FileWriteF(&os->log_file, nullptr, "%*s ", DN_Cast(int) prefix_size.padding, "");
|
|
}
|
|
DN_OS_FileWriteFV(&os->log_file, nullptr, fmt, args_copy);
|
|
if (!DN_BitIsSet(flags, DN_LogFlags_NoNewLine))
|
|
DN_OS_FileWrite(&os->log_file, DN_Str8Lit("\n"), nullptr);
|
|
}
|
|
DN_TicketMutex_End(&os->log_file_mutex);
|
|
va_end(args_copy);
|
|
|
|
DN_TicketMutex_Begin(&os->log_mutex);
|
|
{
|
|
if (print_prefix)
|
|
DN_OS_PrintF(DN_OSPrintDest_Err, "%.*s%*s ", DN_Cast(int) prefix_size.size, prefix_buffer, DN_Cast(int) prefix_size.padding, "");
|
|
|
|
if (DN_BitIsSet(flags, DN_LogFlags_NoNewLine))
|
|
DN_OS_PrintFV(DN_OSPrintDest_Err, fmt, args);
|
|
else
|
|
DN_OS_PrintLnFV(DN_OSPrintDest_Err, fmt, args);
|
|
}
|
|
DN_TicketMutex_End(&os->log_mutex);
|
|
}
|
|
|
|
DN_API void DN_OS_SetLogPrintFuncToOS()
|
|
{
|
|
DN_Core *dn = DN_Get();
|
|
DN_LogSetPrintFunc(DN_OS_LogPrint, &dn->os);
|
|
}
|
|
|
|
// NOTE: Date
|
|
DN_API DN_Str8x32 DN_OS_DateLocalTimeStr8(DN_Date time, char date_separator, char hms_separator)
|
|
{
|
|
DN_Str8x32 result = DN_Str8x32FromFmt("%hu%c%02hhu%c%02hhu %02hhu%c%02hhu%c%02hhu",
|
|
time.year,
|
|
date_separator,
|
|
time.month,
|
|
date_separator,
|
|
time.day,
|
|
time.hour,
|
|
hms_separator,
|
|
time.minutes,
|
|
hms_separator,
|
|
time.seconds);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8x32 DN_OS_DateLocalTimeStr8Now(char date_separator, char hms_separator)
|
|
{
|
|
DN_Date time = DN_OS_DateLocalTimeNow();
|
|
DN_Str8x32 result = DN_OS_DateLocalTimeStr8(time, date_separator, hms_separator);
|
|
return result;
|
|
}
|
|
|
|
// NOTE: Other
|
|
DN_API DN_Str8 DN_OS_EXEDir(DN_Arena *arena)
|
|
{
|
|
DN_Str8 result = {};
|
|
if (!arena)
|
|
return result;
|
|
DN_TCScratch scratch = DN_TCScratchBegin(&arena, 1);
|
|
DN_Str8 exe_path = DN_OS_EXEPath(&scratch.arena);
|
|
DN_Str8 separators[] = {DN_Str8Lit("/"), DN_Str8Lit("\\")};
|
|
DN_Str8BSplitResult split = DN_Str8BSplitLastArray(exe_path, separators, DN_ArrayCountU(separators));
|
|
result = DN_Str8FromStr8Arena(split.lhs, arena);
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
// NOTE: Counters
|
|
DN_API DN_F64 DN_OS_PerfCounterS(uint64_t begin, uint64_t end)
|
|
{
|
|
uint64_t frequency = DN_OS_PerfCounterFrequency();
|
|
uint64_t ticks = end - begin;
|
|
DN_F64 result = ticks / DN_Cast(DN_F64) frequency;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_F64 DN_OS_PerfCounterMs(uint64_t begin, uint64_t end)
|
|
{
|
|
uint64_t frequency = DN_OS_PerfCounterFrequency();
|
|
uint64_t ticks = end - begin;
|
|
DN_F64 result = (ticks * 1'000) / DN_Cast(DN_F64) frequency;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_F64 DN_OS_PerfCounterUs(uint64_t begin, uint64_t end)
|
|
{
|
|
uint64_t frequency = DN_OS_PerfCounterFrequency();
|
|
uint64_t ticks = end - begin;
|
|
DN_F64 result = (ticks * 1'000'000) / DN_Cast(DN_F64) frequency;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_F64 DN_OS_PerfCounterNs(uint64_t begin, uint64_t end)
|
|
{
|
|
uint64_t frequency = DN_OS_PerfCounterFrequency();
|
|
uint64_t ticks = end - begin;
|
|
DN_F64 result = (ticks * 1'000'000'000) / DN_Cast(DN_F64) frequency;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_OSTimer DN_OS_TimerBegin()
|
|
{
|
|
DN_OSTimer result = {};
|
|
result.start = DN_OS_PerfCounterNow();
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_OS_TimerEnd(DN_OSTimer *timer)
|
|
{
|
|
timer->end = DN_OS_PerfCounterNow();
|
|
}
|
|
|
|
DN_API DN_F64 DN_OS_TimerS(DN_OSTimer timer)
|
|
{
|
|
DN_F64 result = DN_OS_PerfCounterS(timer.start, timer.end);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_F64 DN_OS_TimerMs(DN_OSTimer timer)
|
|
{
|
|
DN_F64 result = DN_OS_PerfCounterMs(timer.start, timer.end);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_F64 DN_OS_TimerUs(DN_OSTimer timer)
|
|
{
|
|
DN_F64 result = DN_OS_PerfCounterUs(timer.start, timer.end);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_F64 DN_OS_TimerNs(DN_OSTimer timer)
|
|
{
|
|
DN_F64 result = DN_OS_PerfCounterNs(timer.start, timer.end);
|
|
return result;
|
|
}
|
|
|
|
DN_API uint64_t DN_OS_EstimateTSCPerSecond(uint64_t duration_ms_to_gauge_tsc_frequency)
|
|
{
|
|
uint64_t os_frequency = DN_OS_PerfCounterFrequency();
|
|
uint64_t os_target_elapsed = duration_ms_to_gauge_tsc_frequency * os_frequency / 1000ULL;
|
|
uint64_t tsc_begin = DN_CPUGetTSC();
|
|
uint64_t result = 0;
|
|
if (tsc_begin) {
|
|
uint64_t os_elapsed = 0;
|
|
for (uint64_t os_begin = DN_OS_PerfCounterNow(); os_elapsed < os_target_elapsed;)
|
|
os_elapsed = DN_OS_PerfCounterNow() - os_begin;
|
|
uint64_t tsc_end = DN_CPUGetTSC();
|
|
uint64_t tsc_elapsed = tsc_end - tsc_begin;
|
|
result = tsc_elapsed / os_elapsed * os_frequency;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_OS_PathIsOlderThan(DN_Str8 path, DN_Str8 check_against)
|
|
{
|
|
DN_OSPathInfo file_info = DN_OS_PathInfo(path);
|
|
DN_OSPathInfo check_against_info = DN_OS_PathInfo(check_against);
|
|
bool result = !file_info.exists || file_info.last_write_time_in_s < check_against_info.last_write_time_in_s;
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_OS_FileWrite(DN_OSFile *file, DN_Str8 buffer, DN_ErrSink *error)
|
|
{
|
|
bool result = DN_OS_FileWritePtr(file, buffer.data, buffer.size, error);
|
|
return result;
|
|
}
|
|
|
|
struct DN_OSFileWriteChunker_
|
|
{
|
|
DN_ErrSink *err;
|
|
DN_OSFile *file;
|
|
bool success;
|
|
};
|
|
|
|
static char *DN_OS_FileWriteChunker_(const char *buf, void *user, int len)
|
|
{
|
|
DN_OSFileWriteChunker_ *chunker = DN_Cast(DN_OSFileWriteChunker_ *)user;
|
|
chunker->success = DN_OS_FileWritePtr(chunker->file, buf, len, chunker->err);
|
|
char *result = chunker->success ? DN_Cast(char *) buf : nullptr;
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_OS_FileWriteFV(DN_OSFile *file, DN_ErrSink *error, DN_FMT_ATTRIB char const *fmt, va_list args)
|
|
{
|
|
bool result = false;
|
|
if (!file || !fmt)
|
|
return result;
|
|
|
|
DN_OSFileWriteChunker_ chunker = {};
|
|
chunker.err = error;
|
|
chunker.file = file;
|
|
char buffer[STB_SPRINTF_MIN];
|
|
STB_SPRINTF_DECORATE(vsprintfcb)(DN_OS_FileWriteChunker_, &chunker, buffer, fmt, args);
|
|
|
|
result = chunker.success;
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_OS_FileWriteF(DN_OSFile *file, DN_ErrSink *error, DN_FMT_ATTRIB char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
bool result = DN_OS_FileWriteFV(file, error, fmt, args);
|
|
va_end(args);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_OS_FileReadAll(DN_Allocator allocator, DN_Str8 path, DN_ErrSink *err)
|
|
{
|
|
// NOTE: Query file size
|
|
DN_Str8 result = {};
|
|
DN_OSPathInfo path_info = DN_OS_PathInfo(path);
|
|
if (!path_info.exists) {
|
|
DN_ErrSinkAppendF(err, 1, "File does not exist/could not be queried for reading '%.*s'", DN_Str8PrintFmt(path));
|
|
return result;
|
|
}
|
|
|
|
// NOTE: Allocate
|
|
DN_Arena temp_arena = {};
|
|
if (allocator.type == DN_AllocatorType_Arena) {
|
|
DN_Arena *arena = DN_Cast(DN_Arena *) allocator.context;
|
|
temp_arena = DN_ArenaTempBeginFromArena(arena);
|
|
result = DN_Str8AllocArena(path_info.size, DN_ZMem_No, &temp_arena);
|
|
} else {
|
|
DN_Pool *pool = DN_Cast(DN_Pool *) allocator.context;
|
|
result = DN_Str8AllocPool(path_info.size, pool);
|
|
}
|
|
|
|
if (!result.data) {
|
|
DN_Str8x32 bytes_str = DN_ByteCountStr8x32(path_info.size);
|
|
DN_ErrSinkAppendF(err, 1 /*err_code*/, "Failed to allocate %.*s for reading file '%.*s'", DN_Str8PrintFmt(bytes_str), DN_Str8PrintFmt(path));
|
|
return result;
|
|
}
|
|
|
|
// NOTE: Read all
|
|
DN_OSFile file = DN_OS_FileOpen(path, DN_OSFileOpen_OpenIfExist, DN_OSFileAccess_Read, err);
|
|
DN_OSFileRead read = DN_OS_FileRead(&file, result.data, result.size, err);
|
|
bool failed = file.error || !read.success;
|
|
|
|
if (allocator.type == DN_AllocatorType_Arena) {
|
|
DN_ArenaTempEnd(&temp_arena, failed ? DN_ArenaReset_Yes : DN_ArenaReset_No);
|
|
} else {
|
|
if (failed) {
|
|
DN_Pool *pool = DN_Cast(DN_Pool *) allocator.context;
|
|
DN_PoolDealloc(pool, result.data);
|
|
}
|
|
}
|
|
|
|
if (failed)
|
|
result = {};
|
|
|
|
DN_OS_FileClose(&file);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_OS_FileReadAllArena(DN_Arena *arena, DN_Str8 path, DN_ErrSink *err)
|
|
{
|
|
DN_Allocator allocator = {};
|
|
allocator.type = DN_AllocatorType_Arena;
|
|
allocator.context = arena;
|
|
DN_Str8 result = DN_OS_FileReadAll(allocator, path, err);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_OS_FileReadAllPool(DN_Pool *pool, DN_Str8 path, DN_ErrSink *err)
|
|
{
|
|
DN_Allocator allocator = {};
|
|
allocator.type = DN_AllocatorType_Pool;
|
|
allocator.context = pool;
|
|
DN_Str8 result = DN_OS_FileReadAll(allocator, path, err);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_OS_FileWriteAll(DN_Str8 path, DN_Str8 buffer, DN_ErrSink *error)
|
|
{
|
|
DN_OSFile file = DN_OS_FileOpen(path, DN_OSFileOpen_CreateAlways, DN_OSFileAccess_Write, error);
|
|
bool result = DN_OS_FileWrite(&file, buffer, error);
|
|
DN_OS_FileClose(&file);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_OS_FileWriteAllFV(DN_Str8 file_path, DN_ErrSink *error, DN_FMT_ATTRIB char const *fmt, va_list args)
|
|
{
|
|
DN_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_Str8 buffer = DN_Str8FromFmtVArena(&scratch.arena, fmt, args);
|
|
bool result = DN_OS_FileWriteAll(file_path, buffer, error);
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_OS_FileWriteAllF(DN_Str8 file_path, DN_ErrSink *error, DN_FMT_ATTRIB char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
bool result = DN_OS_FileWriteAllFV(file_path, error, fmt, args);
|
|
va_end(args);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_OS_FileWriteAllSafe(DN_Str8 path, DN_Str8 buffer, DN_ErrSink *error)
|
|
{
|
|
DN_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_Str8 tmp_path = DN_Str8FromFmtArena(&scratch.arena, "%.*s.tmp", DN_Str8PrintFmt(path));
|
|
if (!DN_OS_FileWriteAll(tmp_path, buffer, error)) {
|
|
DN_TCScratchEnd(&scratch);
|
|
return false;
|
|
}
|
|
if (!DN_OS_FileCopy(tmp_path, path, true /*overwrite*/, error)) {
|
|
DN_TCScratchEnd(&scratch);
|
|
return false;
|
|
}
|
|
if (!DN_OS_PathDelete(tmp_path)) {
|
|
DN_TCScratchEnd(&scratch);
|
|
return false;
|
|
}
|
|
DN_TCScratchEnd(&scratch);
|
|
return true;
|
|
}
|
|
|
|
DN_API bool DN_OS_FileWriteAllSafeFV(DN_Str8 path, DN_ErrSink *error, DN_FMT_ATTRIB char const *fmt, va_list args)
|
|
{
|
|
DN_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_Str8 buffer = DN_Str8FromFmtVArena(&scratch.arena, fmt, args);
|
|
bool result = DN_OS_FileWriteAllSafe(path, buffer, error);
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_OS_FileWriteAllSafeF(DN_Str8 path, DN_ErrSink *error, DN_FMT_ATTRIB char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
bool result = DN_OS_FileWriteAllSafeFV(path, error, fmt, args);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_OS_Str8FromPathInfoType(DN_OSPathInfoType type)
|
|
{
|
|
DN_Str8 result = DN_Str8Lit("BAD PATH INFO TYPE");
|
|
switch(type) {
|
|
case DN_OSPathInfoType_Unknown: result = DN_Str8Lit("Unknown"); break;
|
|
case DN_OSPathInfoType_Directory: result = DN_Str8Lit("Directory"); break;
|
|
case DN_OSPathInfoType_File: result = DN_Str8Lit("File"); break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_OS_PathAddRef(DN_Arena *arena, DN_OSPath *fs_path, DN_Str8 path)
|
|
{
|
|
if (!arena || !fs_path || path.size == 0)
|
|
return false;
|
|
|
|
if (path.size <= 0)
|
|
return true;
|
|
|
|
DN_Str8 const delimiter_array[] = {
|
|
DN_Str8Lit("\\"),
|
|
DN_Str8Lit("/")};
|
|
|
|
if (fs_path->links_size == 0)
|
|
fs_path->has_prefix_path_separator = (path.data[0] == '/');
|
|
|
|
for (;;) {
|
|
DN_Str8BSplitResult delimiter = DN_Str8BSplitArray(path, delimiter_array, DN_ArrayCountU(delimiter_array));
|
|
for (; delimiter.lhs.data; delimiter = DN_Str8BSplitArray(delimiter.rhs, delimiter_array, DN_ArrayCountU(delimiter_array))) {
|
|
if (delimiter.lhs.size <= 0)
|
|
continue;
|
|
|
|
DN_OSPathLink *link = DN_ArenaNew(arena, DN_OSPathLink, DN_ZMem_Yes);
|
|
if (!link)
|
|
return false;
|
|
|
|
link->string = delimiter.lhs;
|
|
link->prev = fs_path->tail;
|
|
if (fs_path->tail)
|
|
fs_path->tail->next = link;
|
|
else
|
|
fs_path->head = link;
|
|
fs_path->tail = link;
|
|
fs_path->links_size += 1;
|
|
fs_path->string_size += delimiter.lhs.size;
|
|
}
|
|
|
|
if (!delimiter.lhs.data)
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
DN_API bool DN_OS_PathAdd(DN_Arena *arena, DN_OSPath *fs_path, DN_Str8 path)
|
|
{
|
|
DN_Str8 copy = DN_Str8FromStr8Arena(path, arena);
|
|
bool result = copy.size ? true : DN_OS_PathAddRef(arena, fs_path, copy);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_OS_PathAddF(DN_Arena *arena, DN_OSPath *fs_path, DN_FMT_ATTRIB char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
DN_Str8 path = DN_Str8FromFmtVArena(arena, fmt, args);
|
|
va_end(args);
|
|
bool result = DN_OS_PathAddRef(arena, fs_path, path);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_OS_PathPop(DN_OSPath *fs_path)
|
|
{
|
|
if (!fs_path)
|
|
return false;
|
|
|
|
if (fs_path->tail) {
|
|
DN_Assert(fs_path->head);
|
|
fs_path->links_size -= 1;
|
|
fs_path->string_size -= fs_path->tail->string.size;
|
|
fs_path->tail = fs_path->tail->prev;
|
|
if (fs_path->tail)
|
|
fs_path->tail->next = nullptr;
|
|
else
|
|
fs_path->head = nullptr;
|
|
} else {
|
|
DN_Assert(!fs_path->head);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_OS_PathTo(DN_Arena *arena, DN_Str8 path, DN_Str8 path_separator)
|
|
{
|
|
DN_OSPath fs_path = {};
|
|
DN_OS_PathAddRef(arena, &fs_path, path);
|
|
DN_Str8 result = DN_OS_PathBuildWithSeparator(arena, &fs_path, path_separator);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_OS_PathToF(DN_Arena *arena, DN_Str8 path_separator, DN_FMT_ATTRIB char const *fmt, ...)
|
|
{
|
|
DN_TCScratch scratch = DN_TCScratchBegin(&arena, 1);
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
DN_Str8 path = DN_Str8FromFmtVArena(&scratch.arena, fmt, args);
|
|
va_end(args);
|
|
DN_Str8 result = DN_OS_PathTo(arena, path, path_separator);
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_OS_Path(DN_Arena *arena, DN_Str8 path)
|
|
{
|
|
DN_Str8 result = DN_OS_PathTo(arena, path, DN_OSPathSeperatorString);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_OS_PathF(DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, ...)
|
|
{
|
|
DN_TCScratch scratch = DN_TCScratchBegin(&arena, 1);
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
DN_Str8 path = DN_Str8FromFmtVArena(&scratch.arena, fmt, args);
|
|
va_end(args);
|
|
DN_Str8 result = DN_OS_Path(arena, path);
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_OS_PathBuildWithSeparator(DN_Arena *arena, DN_OSPath const *fs_path, DN_Str8 path_separator)
|
|
{
|
|
DN_Str8 result = {};
|
|
if (!fs_path || fs_path->links_size <= 0)
|
|
return result;
|
|
|
|
// NOTE: Each link except the last one needs the path separator appended to it, '/' or '\\'
|
|
DN_USize string_size = (fs_path->has_prefix_path_separator ? path_separator.size : 0) + fs_path->string_size + ((fs_path->links_size - 1) * path_separator.size);
|
|
result = DN_Str8AllocArena(string_size, DN_ZMem_No, arena);
|
|
if (result.data) {
|
|
char *dest = result.data;
|
|
if (fs_path->has_prefix_path_separator) {
|
|
DN_Memcpy(dest, path_separator.data, path_separator.size);
|
|
dest += path_separator.size;
|
|
}
|
|
|
|
for (DN_OSPathLink *link = fs_path->head; link; link = link->next) {
|
|
DN_Str8 string = link->string;
|
|
DN_Memcpy(dest, string.data, string.size);
|
|
dest += string.size;
|
|
|
|
if (link != fs_path->tail) {
|
|
DN_Memcpy(dest, path_separator.data, path_separator.size);
|
|
dest += path_separator.size;
|
|
}
|
|
}
|
|
}
|
|
|
|
result.data[string_size] = 0;
|
|
return result;
|
|
}
|
|
|
|
// NOTE: DN_OSExec
|
|
DN_API DN_OSExecResult DN_OS_Exec(DN_Str8Slice cmd_line, DN_OSExecArgs *args, DN_Arena *arena, DN_ErrSink *error)
|
|
{
|
|
DN_OSExecAsyncHandle async_handle = DN_OS_ExecAsync(cmd_line, args, error);
|
|
DN_OSExecResult result = DN_OS_ExecWait(async_handle, arena, error);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_OSExecResult DN_OS_ExecOrAbort(DN_Str8Slice cmd_line, DN_OSExecArgs *args, DN_Arena *arena)
|
|
{
|
|
DN_ErrSink *error = DN_TCErrSinkBegin(DN_ErrSinkMode_Nil);
|
|
DN_OSExecResult result = DN_OS_Exec(cmd_line, args, arena, error);
|
|
if (result.os_error_code)
|
|
DN_ErrSinkEndExitIfErrorF(error, result.os_error_code, "OS failed to execute the requested command returning the error code %u", result.os_error_code);
|
|
|
|
if (result.exit_code)
|
|
DN_ErrSinkEndExitIfErrorF(error, result.exit_code, "OS executed command and returned non-zero exit code %u", result.exit_code);
|
|
DN_ErrSinkEndIgnore(error);
|
|
return result;
|
|
}
|
|
|
|
// NOTE: DN_OSThread
|
|
static void DN_OS_ThreadExecute_(void *user_context)
|
|
{
|
|
DN_OSThread *thread = DN_Cast(DN_OSThread *) user_context;
|
|
DN_MemFuncs mem_funcs = DN_MemFuncsDefault();
|
|
DN_TCInitFromMemFuncs(&thread->context, thread->thread_id, /*args=*/nullptr, mem_funcs);
|
|
DN_TCEquip(&thread->context);
|
|
if (thread->is_lane_set) {
|
|
DN_OS_TCThreadLaneEquip(thread->lane);
|
|
DN_OS_ThreadSetNameFmt("L%02zu/%02zu T%zu", thread->lane.index, thread->lane.count, thread->thread_id);
|
|
} else {
|
|
DN_OS_ThreadSetNameFmt("T%zu", thread->lane.index, thread->lane.count, thread->thread_id);
|
|
}
|
|
DN_OS_SemaphoreWait(&thread->init_semaphore, DN_OS_SEMAPHORE_INFINITE_TIMEOUT);
|
|
thread->func(thread);
|
|
}
|
|
|
|
DN_API void DN_OS_ThreadSetNameFmt(char const *fmt, ...)
|
|
{
|
|
DN_TCCore *tls = DN_TCGet();
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
tls->name = DN_Str8x64FromFmtV(fmt, args);
|
|
va_end(args);
|
|
|
|
DN_Str8 name = DN_Str8FromPtr(tls->name.data, tls->name.size);
|
|
#if defined(DN_PLATFORM_WIN32)
|
|
DN_OS_W32ThreadSetName(name);
|
|
#else
|
|
DN_OS_PosixThreadSetName(name);
|
|
#endif
|
|
}
|
|
|
|
DN_API DN_OSThreadLane DN_OS_ThreadLaneInit(DN_USize index, DN_USize thread_count, DN_OSBarrier barrier, DN_UPtr *shared_mem)
|
|
{
|
|
DN_OSThreadLane result = {};
|
|
result.index = index;
|
|
result.count = thread_count;
|
|
result.barrier = barrier;
|
|
result.shared_mem = shared_mem;
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_OS_ThreadLaneSync(DN_OSThreadLane *lane, void **ptr_to_share)
|
|
{
|
|
if (!lane)
|
|
return;
|
|
|
|
// NOTE: Write the pointer into shared memory (if we're the lane producing the data)
|
|
bool sharing = false;
|
|
if (ptr_to_share && *ptr_to_share) {
|
|
DN_Memcpy(lane->shared_mem, ptr_to_share, sizeof(*ptr_to_share));
|
|
sharing = true;
|
|
}
|
|
|
|
DN_OS_BarrierWait(&lane->barrier); // NOTE: Ensure sharing lane has completed the write
|
|
|
|
// NOTE: Read pointer from shared memory (if we're the other lanes that read the data)
|
|
if (ptr_to_share && !(*ptr_to_share)) {
|
|
sharing = true;
|
|
DN_Memcpy(ptr_to_share, lane->shared_mem, sizeof(*ptr_to_share));
|
|
}
|
|
|
|
if (sharing)
|
|
DN_OS_BarrierWait(&lane->barrier); // NOTE: Ensure the reading lanes have completed the read
|
|
}
|
|
|
|
DN_API DN_V2USize DN_OS_ThreadLaneRange(DN_OSThreadLane *lane, DN_USize values_count)
|
|
{
|
|
DN_USize values_per_thread = values_count / lane->count;
|
|
DN_USize rem_values = values_count % lane->count;
|
|
bool thread_has_leftovers = lane->index < rem_values;
|
|
DN_USize leftovers_before_this_thread_index = 0;
|
|
|
|
if (thread_has_leftovers)
|
|
leftovers_before_this_thread_index = lane->index;
|
|
else
|
|
leftovers_before_this_thread_index = rem_values;
|
|
|
|
DN_USize thread_start_index = (values_per_thread * lane->index) + leftovers_before_this_thread_index;
|
|
DN_USize thread_values_count = values_per_thread + (thread_has_leftovers ? 1 : 0);
|
|
|
|
DN_V2USize result = {};
|
|
result.begin = thread_start_index;
|
|
result.end = result.begin + thread_values_count;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_OSThreadLane *DN_OS_TCThreadLane()
|
|
{
|
|
DN_TCCore *tc = DN_TCGet();
|
|
DN_OSThreadLane *result = tc ? DN_Cast(DN_OSThreadLane *) tc->lane_opaque : nullptr;
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_OS_TCThreadLaneSync(void **ptr_to_share)
|
|
{
|
|
DN_OSThreadLane *lane = DN_OS_TCThreadLane();
|
|
DN_OS_ThreadLaneSync(lane, ptr_to_share);
|
|
}
|
|
|
|
DN_API DN_OSThreadLane DN_OS_TCThreadLaneEquip(DN_OSThreadLane lane)
|
|
{
|
|
DN_TCCore *tc = DN_TCGet();
|
|
DN_OSThreadLane *curr = DN_Cast(DN_OSThreadLane *) tc->lane_opaque;
|
|
DN_StaticAssert(sizeof(tc->lane_opaque) >= sizeof(DN_OSThreadLane));
|
|
DN_OSThreadLane result = *curr;
|
|
*curr = lane;
|
|
return result;
|
|
}
|
|
|
|
// 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_TCScratch scratch = DN_TCScratchBegin(&arena, 1);
|
|
result.scratch_arena = scratch.arena;
|
|
|
|
DN_OS_HttpRequestAsync(&result, arena, host, path, secure, method, body, headers);
|
|
DN_OS_HttpRequestWait(&result);
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
// NOTE: DN_OSPrint
|
|
DN_API DN_LogStyle DN_OS_PrintStyleColour(uint8_t r, uint8_t g, uint8_t b, DN_LogBold bold)
|
|
{
|
|
DN_LogStyle result = {};
|
|
result.bold = bold;
|
|
result.colour = true;
|
|
result.r = r;
|
|
result.g = g;
|
|
result.b = b;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_LogStyle DN_OS_PrintStyleColourU32(uint32_t rgb, DN_LogBold bold)
|
|
{
|
|
uint8_t r = (rgb >> 24) & 0xFF;
|
|
uint8_t g = (rgb >> 16) & 0xFF;
|
|
uint8_t b = (rgb >> 8) & 0xFF;
|
|
DN_LogStyle result = DN_OS_PrintStyleColour(r, g, b, bold);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_LogStyle DN_OS_PrintStyleBold()
|
|
{
|
|
DN_LogStyle result = {};
|
|
result.bold = DN_LogBold_Yes;
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_OS_Print(DN_OSPrintDest dest, DN_Str8 string)
|
|
{
|
|
DN_Assert(dest == DN_OSPrintDest_Out || dest == DN_OSPrintDest_Err);
|
|
|
|
#if defined(DN_PLATFORM_WIN32)
|
|
// NOTE: Get the output handles from kernel
|
|
DN_THREAD_LOCAL void *std_out_print_handle = nullptr;
|
|
DN_THREAD_LOCAL void *std_err_print_handle = nullptr;
|
|
DN_THREAD_LOCAL bool std_out_print_to_console = false;
|
|
DN_THREAD_LOCAL bool std_err_print_to_console = false;
|
|
|
|
if (!std_out_print_handle) {
|
|
unsigned long mode = 0;
|
|
(void)mode;
|
|
std_out_print_handle = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
std_out_print_to_console = GetConsoleMode(std_out_print_handle, &mode) != 0;
|
|
|
|
std_err_print_handle = GetStdHandle(STD_ERROR_HANDLE);
|
|
std_err_print_to_console = GetConsoleMode(std_err_print_handle, &mode) != 0;
|
|
}
|
|
|
|
// NOTE: Select the output handle
|
|
void *print_handle = std_out_print_handle;
|
|
bool print_to_console = std_out_print_to_console;
|
|
if (dest == DN_OSPrintDest_Err) {
|
|
print_handle = std_err_print_handle;
|
|
print_to_console = std_err_print_to_console;
|
|
}
|
|
|
|
// NOTE: Write the string
|
|
DN_Assert(string.size < DN_Cast(unsigned long) - 1);
|
|
unsigned long bytes_written = 0;
|
|
(void)bytes_written;
|
|
if (print_to_console)
|
|
WriteConsoleA(print_handle, string.data, DN_Cast(unsigned long) string.size, &bytes_written, nullptr);
|
|
else
|
|
WriteFile(print_handle, string.data, DN_Cast(unsigned long) string.size, &bytes_written, nullptr);
|
|
#else
|
|
fprintf(dest == DN_OSPrintDest_Out ? stdout : stderr, "%.*s", DN_Str8PrintFmt(string));
|
|
#endif
|
|
}
|
|
|
|
DN_API void DN_OS_PrintF(DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
DN_OS_PrintFV(dest, fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
DN_API void DN_OS_PrintFStyle(DN_OSPrintDest dest, DN_LogStyle style, DN_FMT_ATTRIB char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
DN_OS_PrintFVStyle(dest, style, fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
DN_API void DN_OS_PrintStyle(DN_OSPrintDest dest, DN_LogStyle style, DN_Str8 string)
|
|
{
|
|
if (string.data && string.size) {
|
|
if (style.colour) {
|
|
DN_Str8x32 colour = DN_Str8x32FromANSIColourCodeU8RGB(DN_ANSIColourMode_Fg, style.r, style.g, style.b);
|
|
DN_OS_Print(dest, DN_Str8FromStruct(&colour));
|
|
}
|
|
if (style.bold == DN_LogBold_Yes)
|
|
DN_OS_Print(dest, DN_Str8Lit(DN_ANSICodeBoldLit));
|
|
DN_OS_Print(dest, string);
|
|
if (style.colour || style.bold == DN_LogBold_Yes)
|
|
DN_OS_Print(dest, DN_Str8Lit(DN_ANSICodeResetLit));
|
|
}
|
|
}
|
|
|
|
static char *DN_OS_PrintVSPrintfChunker_(const char *buf, void *user, int len)
|
|
{
|
|
DN_Str8 string = {};
|
|
string.data = DN_Cast(char *) buf;
|
|
string.size = len;
|
|
|
|
DN_OSPrintDest dest = DN_Cast(DN_OSPrintDest) DN_Cast(uintptr_t) user;
|
|
DN_OS_Print(dest, string);
|
|
return (char *)buf;
|
|
}
|
|
|
|
DN_API void DN_OS_PrintFV(DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, va_list args)
|
|
{
|
|
char buffer[STB_SPRINTF_MIN];
|
|
STB_SPRINTF_DECORATE(vsprintfcb)
|
|
(DN_OS_PrintVSPrintfChunker_, DN_Cast(void *) DN_Cast(uintptr_t) dest, buffer, fmt, args);
|
|
}
|
|
|
|
DN_API void DN_OS_PrintFVStyle(DN_OSPrintDest dest, DN_LogStyle style, DN_FMT_ATTRIB char const *fmt, va_list args)
|
|
{
|
|
if (fmt) {
|
|
if (style.colour) {
|
|
DN_Str8x32 colour = DN_Str8x32FromANSIColourCodeU8RGB(DN_ANSIColourMode_Fg, style.r, style.g, style.b);
|
|
DN_OS_Print(dest, DN_Str8FromStruct(&colour));
|
|
}
|
|
if (style.bold == DN_LogBold_Yes)
|
|
DN_OS_Print(dest, DN_Str8Lit(DN_ANSICodeBoldLit));
|
|
DN_OS_PrintFV(dest, fmt, args);
|
|
if (style.colour || style.bold == DN_LogBold_Yes)
|
|
DN_OS_Print(dest, DN_Str8Lit(DN_ANSICodeResetLit));
|
|
}
|
|
}
|
|
|
|
DN_API void DN_OS_PrintLn(DN_OSPrintDest dest, DN_Str8 string)
|
|
{
|
|
DN_OS_Print(dest, string);
|
|
DN_OS_Print(dest, DN_Str8Lit("\n"));
|
|
}
|
|
|
|
DN_API void DN_OS_PrintLnF(DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
DN_OS_PrintLnFV(dest, fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
DN_API void DN_OS_PrintLnFV(DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, va_list args)
|
|
{
|
|
DN_OS_PrintFV(dest, fmt, args);
|
|
DN_OS_Print(dest, DN_Str8Lit("\n"));
|
|
}
|
|
|
|
DN_API void DN_OS_PrintLnStyle(DN_OSPrintDest dest, DN_LogStyle style, DN_Str8 string)
|
|
{
|
|
DN_OS_PrintStyle(dest, style, string);
|
|
DN_OS_Print(dest, DN_Str8Lit("\n"));
|
|
}
|
|
|
|
DN_API void DN_OS_PrintLnFStyle(DN_OSPrintDest dest, DN_LogStyle style, DN_FMT_ATTRIB char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
DN_OS_PrintLnFVStyle(dest, style, fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
DN_API void DN_OS_PrintLnFVStyle(DN_OSPrintDest dest, DN_LogStyle style, DN_FMT_ATTRIB char const *fmt, va_list args)
|
|
{
|
|
DN_OS_PrintFVStyle(dest, style, fmt, args);
|
|
DN_OS_Print(dest, DN_Str8Lit("\n"));
|
|
}
|
|
|
|
// NOTE: DN_VArray
|
|
template <typename T>
|
|
DN_VArray<T> DN_OS_VArrayInitByteSize(DN_USize byte_size)
|
|
{
|
|
DN_VArray<T> 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 <typename T>
|
|
DN_VArray<T> DN_OS_VArrayInit(DN_USize max)
|
|
{
|
|
DN_VArray<T> result = DN_OS_VArrayInitByteSize<T>(max * sizeof(T));
|
|
DN_Assert(result.max >= max);
|
|
return result;
|
|
}
|
|
|
|
template <typename T, DN_USize N>
|
|
DN_VArray<T> DN_OS_VArrayInitCArray(T const (&items)[N], DN_USize max)
|
|
{
|
|
DN_USize real_max = DN_Max(N, max);
|
|
DN_VArray<T> result = DN_OS_VArrayInit<T>(real_max);
|
|
if (DN_OS_VArrayIsValid(&result))
|
|
DN_OS_VArrayAddArray(&result, items, N);
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
void DN_OS_VArrayDeinit(DN_VArray<T> *array)
|
|
{
|
|
DN_OS_MemRelease(array->data, array->max * sizeof(T));
|
|
*array = {};
|
|
}
|
|
|
|
template <typename T>
|
|
bool DN_OS_VArrayIsValid(DN_VArray<T> const *array)
|
|
{
|
|
bool result = array->data && array->size <= array->max;
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
T *DN_OS_VArrayAddArray(DN_VArray<T> *array, T const *items, DN_USize count)
|
|
{
|
|
T *result = DN_OS_VArrayMakeArray(array, count, DN_ZMem_No);
|
|
if (result)
|
|
DN_Memcpy(result, items, count * sizeof(T));
|
|
return result;
|
|
}
|
|
|
|
template <typename T, DN_USize N>
|
|
T *DN_OS_VArrayAddCArray(DN_VArray<T> *array, T const (&items)[N])
|
|
{
|
|
T *result = DN_OS_VArrayAddArray(array, items, N);
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
T *DN_OS_VArrayAdd(DN_VArray<T> *array, T const &item)
|
|
{
|
|
T *result = DN_OS_VArrayAddArray(array, &item, 1);
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
T *DN_OS_VArrayMakeArray(DN_VArray<T> *array, DN_USize count, DN_ZMem z_mem)
|
|
{
|
|
if (!DN_OS_VArrayIsValid(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_OS_VArrayReserve(array, count))
|
|
return nullptr;
|
|
|
|
// TODO: Use placement new
|
|
T *result = array->data + array->size;
|
|
array->size += count;
|
|
if (z_mem == DN_ZMem_Yes)
|
|
DN_Memset(result, 0, count * sizeof(T));
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
T *DN_OS_VArrayMake(DN_VArray<T> *array, DN_ZMem z_mem)
|
|
{
|
|
T *result = DN_OS_VArrayMakeArray(array, 1, z_mem);
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
T *DN_OS_VArrayInsertArray(DN_VArray<T> *array, DN_USize index, T const *items, DN_USize count)
|
|
{
|
|
T *result = nullptr;
|
|
if (!DN_OS_VArrayIsValid(array))
|
|
return result;
|
|
if (DN_OS_VArrayReserve(array, array->size + count))
|
|
result = DN_CArrayInsertArray(array->data, &array->size, array->max, index, items, count);
|
|
return result;
|
|
}
|
|
|
|
template <typename T, DN_USize N>
|
|
T *DN_OS_VArrayInsertCArray(DN_VArray<T> *array, DN_USize index, T const (&items)[N])
|
|
{
|
|
T *result = DN_OS_VArrayInsertArray(array, index, items, N);
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
T *DN_OS_VArrayInsert(DN_VArray<T> *array, DN_USize index, T const &item)
|
|
{
|
|
T *result = DN_OS_VArrayInsertArray(array, index, &item, 1);
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
T *DN_OS_VArrayPopFront(DN_VArray<T> *array, DN_USize count)
|
|
{
|
|
T *result = DN_Cast(T *)DN_CArrayPopFront(array->data, &array->size, sizeof(T), count);
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
T *DN_OS_VArrayPopBack(DN_VArray<T> *array, DN_USize count)
|
|
{
|
|
T *result = DN_Cast(T *)DN_CArrayPopBack(array->data, &array->size, sizeof(T), count);
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
DN_ArrayEraseResult DN_OS_VArrayEraseRange(DN_VArray<T> *array, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase)
|
|
{
|
|
DN_ArrayEraseResult result = {};
|
|
if (!DN_OS_VArrayIsValid(array))
|
|
return result;
|
|
result = DN_CArrayEraseRange(array->data, &array->size, sizeof(T), begin_index, count, erase);
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
void DN_OS_VArrayClear(DN_VArray<T> *array, DN_ZMem z_mem)
|
|
{
|
|
if (array) {
|
|
if (z_mem == DN_ZMem_Yes)
|
|
DN_Memset(array->data, 0, array->size * sizeof(T));
|
|
array->size = 0;
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
bool DN_OS_VArrayReserve(DN_VArray<T> *array, DN_USize count)
|
|
{
|
|
if (!DN_OS_VArrayIsValid(array) || count == 0)
|
|
return false;
|
|
|
|
DN_USize real_commit = (array->size + count) * sizeof(T);
|
|
DN_USize aligned_commit = DN_AlignUpPowerOfTwo(real_commit, DN_Get()->os.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;
|
|
}
|
|
|
|
// NOTE: Stack Trace
|
|
DN_API DN_StackTraceWalkResult DN_StackTraceWalk(DN_Arena *arena, DN_U16 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_OSW32Core *w32 = DN_OS_W32GetCore();
|
|
if (!w32->sym_initialised) {
|
|
w32->sym_initialised = true;
|
|
SymSetOptions(SYMOPT_LOAD_LINES);
|
|
if (!SymInitialize(result.process, nullptr /*UserSearchPath*/, true /*fInvadeProcess*/)) {
|
|
DN_TCScratch scratch = DN_TCScratchBegin(&arena, 1);
|
|
DN_OSW32Error error = DN_OS_W32LastError(&scratch.arena);
|
|
DN_LogErrorF("SymInitialize failed, stack trace can not be generated (%lu): %.*s\n", error.code, DN_Str8PrintFmt(error.msg));
|
|
DN_TCScratchEnd(&scratch);
|
|
}
|
|
}
|
|
|
|
CONTEXT context;
|
|
RtlCaptureContext(&context);
|
|
|
|
STACKFRAME64 frame = {};
|
|
frame.AddrPC.Offset = context.Rip;
|
|
frame.AddrPC.Mode = AddrModeFlat;
|
|
frame.AddrFrame.Offset = context.Rbp;
|
|
frame.AddrFrame.Mode = AddrModeFlat;
|
|
frame.AddrStack.Offset = context.Rsp;
|
|
frame.AddrStack.Mode = AddrModeFlat;
|
|
|
|
DN_U64 raw_frames[256] = {};
|
|
DN_USize raw_frames_count = 0;
|
|
while (raw_frames_count < limit) {
|
|
if (!StackWalk64(IMAGE_FILE_MACHINE_AMD64,
|
|
result.process,
|
|
thread,
|
|
&frame,
|
|
&context,
|
|
nullptr /*ReadMemoryRoutine*/,
|
|
SymFunctionTableAccess64,
|
|
SymGetModuleBase64,
|
|
nullptr /*TranslateAddress*/))
|
|
break;
|
|
|
|
// NOTE: It might be useful one day to use frame.AddrReturn.Offset.
|
|
// If AddrPC.Offset == AddrReturn.Offset then we can detect recursion.
|
|
DN_LArrayAppend(raw_frames, &raw_frames_count, frame.AddrPC.Offset);
|
|
}
|
|
DN_TicketMutex_End(&mutex);
|
|
|
|
result.base_addr = DN_ArenaNewArray(arena, DN_U64, raw_frames_count, DN_ZMem_No);
|
|
result.size = DN_Cast(DN_U16) raw_frames_count;
|
|
DN_Memcpy(result.base_addr, raw_frames, raw_frames_count * sizeof(raw_frames[0]));
|
|
#else
|
|
(void)limit;
|
|
(void)arena;
|
|
#endif
|
|
return result;
|
|
}
|
|
|
|
static void DN_StackTraceAddWalkToStr8Builder(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_StackTraceRawFrameToFrame(builder->arena, raw_frame);
|
|
DN_Str8BuilderAppendF(builder, "%.*s(%zu): %.*s%s", DN_Str8PrintFmt(frame.file_name), frame.line_number, DN_Str8PrintFmt(frame.function_name), (DN_Cast(int) index == walk->size - 1) ? "" : "\n");
|
|
}
|
|
}
|
|
|
|
DN_API bool DN_StackTraceWalkResultIterate(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_StackTraceWalkResultToStr8(DN_Arena *arena, DN_StackTraceWalkResult const *walk, DN_U16 skip)
|
|
{
|
|
DN_Str8 result{};
|
|
if (!walk || !arena)
|
|
return result;
|
|
|
|
DN_TCScratch scratch = DN_TCScratchBegin(&arena, 1);
|
|
DN_Str8Builder builder = DN_Str8BuilderFromArena(&scratch.arena);
|
|
DN_StackTraceAddWalkToStr8Builder(walk, &builder, skip);
|
|
result = DN_Str8BuilderBuild(&builder, arena);
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_StackTraceWalkStr8(DN_Arena *arena, DN_U16 limit, DN_U16 skip)
|
|
{
|
|
DN_TCScratch scratch = DN_TCScratchBegin(&arena, 1);
|
|
DN_StackTraceWalkResult walk = DN_StackTraceWalk(&scratch.arena, limit);
|
|
DN_Str8 result = DN_StackTraceWalkResultToStr8(arena, &walk, skip);
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_StackTraceWalkStr8FromHeap(DN_U16 limit, DN_U16 skip)
|
|
{
|
|
// NOTE: We don't use WalkResultToStr8 because that uses the TLS arenas which
|
|
// does not use the OS heap.
|
|
DN_MemList mem = DN_MemListFromHeap(DN_Kilobytes(64), DN_MemFlags_NoAllocTrack);
|
|
DN_Arena arena = DN_ArenaFromMemList(&mem);
|
|
DN_Str8Builder builder = DN_Str8BuilderFromArena(&arena);
|
|
DN_StackTraceWalkResult walk = DN_StackTraceWalk(&arena, limit);
|
|
DN_StackTraceAddWalkToStr8Builder(&walk, &builder, skip);
|
|
DN_Str8 result = DN_Str8BuilderBuildFromHeap(&builder);
|
|
DN_MemListDeinit(&mem);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_StackTraceFrameSlice DN_StackTraceGetFrames(DN_Arena *arena, DN_U16 limit)
|
|
{
|
|
DN_StackTraceFrameSlice result = {};
|
|
if (!arena)
|
|
return result;
|
|
|
|
DN_TCScratch scratch = DN_TCScratchBegin(&arena, 1);
|
|
DN_StackTraceWalkResult walk = DN_StackTraceWalk(&scratch.arena, limit);
|
|
if (walk.size) {
|
|
if (DN_ISliceAllocArena(&result, walk.size, DN_ZMem_No, arena)) {
|
|
DN_USize slice_index = 0;
|
|
for (DN_StackTraceWalkResultIterator it = {}; DN_StackTraceWalkResultIterate(&it, &walk);)
|
|
result.data[slice_index++] = DN_StackTraceRawFrameToFrame(arena, it.raw_frame);
|
|
}
|
|
}
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_StackTraceFrame DN_StackTraceRawFrameToFrame(DN_Arena *arena, DN_StackTraceRawFrame raw_frame)
|
|
{
|
|
#if defined(DN_OS_WIN32)
|
|
// NOTE: Get line+filename
|
|
|
|
// TODO: Why does zero-initialising this with `line = {};` cause
|
|
// SymGetLineFromAddr64 function to fail once we are at
|
|
// __scrt_commain_main_seh and hit BaseThreadInitThunk frame? The
|
|
// line and file number are still valid in the result which we use, so,
|
|
// we silently ignore this error.
|
|
IMAGEHLP_LINEW64 line;
|
|
line.SizeOfStruct = sizeof(line);
|
|
DWORD line_displacement = 0;
|
|
if (!SymGetLineFromAddrW64(raw_frame.process, raw_frame.base_addr, &line_displacement, &line))
|
|
line = {};
|
|
|
|
// NOTE: Get function name
|
|
|
|
alignas(SYMBOL_INFOW) char buffer[sizeof(SYMBOL_INFOW) + (MAX_SYM_NAME * sizeof(wchar_t))] = {};
|
|
SYMBOL_INFOW *symbol = DN_Cast(SYMBOL_INFOW *) buffer;
|
|
symbol->SizeOfStruct = sizeof(*symbol);
|
|
symbol->MaxNameLen = sizeof(buffer) - sizeof(*symbol);
|
|
|
|
uint64_t symbol_displacement = 0; // Offset to the beginning of the symbol to the address
|
|
SymFromAddrW(raw_frame.process, raw_frame.base_addr, &symbol_displacement, symbol);
|
|
|
|
// NOTE: Construct result
|
|
|
|
DN_Str16 file_name16 = DN_Str16FromPtr(line.FileName, DN_CStr16Size(line.FileName));
|
|
DN_Str16 function_name16 = DN_Str16FromPtr(symbol->Name, symbol->NameLen);
|
|
|
|
DN_StackTraceFrame result = {};
|
|
result.address = raw_frame.base_addr;
|
|
result.line_number = line.LineNumber;
|
|
result.file_name = DN_OS_W32Str16ToStr8(arena, file_name16);
|
|
result.function_name = DN_OS_W32Str16ToStr8(arena, function_name16);
|
|
|
|
if (result.function_name.size == 0)
|
|
result.function_name = DN_Str8Lit("<unknown function>");
|
|
if (result.file_name.size == 0)
|
|
result.file_name = DN_Str8Lit("<unknown file>");
|
|
#else
|
|
DN_StackTraceFrame result = {};
|
|
#endif
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_StackTracePrint(DN_U16 limit)
|
|
{
|
|
DN_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_StackTraceFrameSlice stack_trace = DN_StackTraceGetFrames(&scratch.arena, limit);
|
|
for (DN_ForItSize(it, DN_StackTraceFrame, stack_trace.data, stack_trace.count)) {
|
|
DN_StackTraceFrame frame = *it.data;
|
|
DN_OS_PrintErrLnF("%.*s(%I64u): %.*s", DN_Str8PrintFmt(frame.file_name), frame.line_number, DN_Str8PrintFmt(frame.function_name));
|
|
}
|
|
DN_TCScratchEnd(&scratch);
|
|
}
|
|
|
|
DN_API void DN_StackTraceReloadSymbols()
|
|
{
|
|
#if defined(DN_OS_WIN32)
|
|
HANDLE process = GetCurrentProcess();
|
|
SymRefreshModuleList(process);
|
|
#endif
|
|
}
|
|
#if defined(DN_PLATFORM_POSIX) || defined(DN_PLATFORM_EMSCRIPTEN)
|
|
// DN: Single header generator commented out => #include "OS/dn_os_posix.cpp"
|
|
#define DN_OS_POSIX_CPP
|
|
|
|
// DN: Single header generator commented out => #if defined(_CLANGD)
|
|
// #define DN_H_WITH_OS 1
|
|
// #include "../dn.h"
|
|
// #include "dn_os_posix.h"
|
|
// #endif
|
|
|
|
#include <dirent.h> // readdir, opendir, closedir
|
|
#include <sys/statvfs.h>
|
|
#include <sys/mman.h>
|
|
|
|
// 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)
|
|
{
|
|
#if defined(DN_PLATFORM_EMSCRIPTEN)
|
|
DN_InvalidCodePathF("Emscripten does not support virtual memory, you should use DN_OS_MemAlloc");
|
|
#endif
|
|
|
|
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_AtomicAddU64(&g_dn_->os.mem_allocs_total, 1);
|
|
DN_AtomicAddU64(&g_dn_->os.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)
|
|
{
|
|
#if defined(DN_PLATFORM_EMSCRIPTEN)
|
|
DN_InvalidCodePathF("Emscripten does not support virtual memory");
|
|
#endif
|
|
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_AtomicAddU64(&g_dn_->os.mem_allocs_total, 1);
|
|
DN_AtomicAddU64(&g_dn_->os.mem_allocs_frame, 1);
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_OS_MemDecommit(void *ptr, DN_USize size)
|
|
{
|
|
#if defined(DN_PLATFORM_EMSCRIPTEN)
|
|
DN_InvalidCodePathF("Emscripten does not support virtual memory");
|
|
#endif
|
|
mprotect(ptr, size, PROT_NONE);
|
|
madvise(ptr, size, MADV_FREE);
|
|
}
|
|
|
|
DN_API void DN_OS_MemRelease(void *ptr, DN_USize size)
|
|
{
|
|
#if defined(DN_PLATFORM_EMSCRIPTEN)
|
|
DN_InvalidCodePathF("Emscripten does not support virtual memory");
|
|
#endif
|
|
munmap(ptr, size);
|
|
}
|
|
|
|
DN_API int DN_OS_MemProtect(void *ptr, DN_USize size, DN_U32 page_flags)
|
|
{
|
|
#if defined(DN_PLATFORM_EMSCRIPTEN)
|
|
DN_InvalidCodePathF("Emscripten does not support virtual memory");
|
|
#endif
|
|
if (!ptr || size == 0)
|
|
return 0;
|
|
|
|
static DN_Str8 const ALIGNMENT_ERROR_MSG = DN_Str8Lit(
|
|
"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.page_size),
|
|
"%s",
|
|
ALIGNMENT_ERROR_MSG.data);
|
|
DN_AssertF(
|
|
DN_IsPowerOfTwoAligned(size, g_dn_->os.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_ZMem z_mem)
|
|
{
|
|
void *result = z_mem == DN_ZMem_Yes ? calloc(1, size) : malloc(size);
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_OS_MemDealloc(void *ptr)
|
|
{
|
|
free(ptr);
|
|
}
|
|
|
|
// NOTE: Date
|
|
DN_API DN_Date DN_OS_DateLocalTimeNow()
|
|
{
|
|
DN_Date 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.milliseconds = ts.tv_nsec / (1000 * 1000); // TODO: Verify that getting the milliseconds like this is correct
|
|
|
|
result.day = DN_Cast(DN_U8) time.tm_mday;
|
|
result.month = DN_Cast(DN_U8) time.tm_mon + 1;
|
|
result.year = 1900 + DN_Cast(DN_U16) time.tm_year;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_U64 DN_OS_DateUnixTimeNs()
|
|
{
|
|
struct timespec ts = {};
|
|
clock_gettime(CLOCK_REALTIME, &ts);
|
|
DN_U64 result = (ts.tv_sec * 1000 /*ms*/ * 1000 /*us*/ * 1000 /*ns*/) + ts.tv_nsec;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_U64 DN_OS_DateUnixTimeSFromLocalDate(DN_Date date)
|
|
{
|
|
struct tm tm_time = {0};
|
|
tm_time.tm_year = (int)date.year - 1900;
|
|
tm_time.tm_mon = (int)date.month - 1; // month is 1-12 in your struct
|
|
tm_time.tm_mday = (int)date.day; // day of month 1-31
|
|
tm_time.tm_hour = (int)date.hour;
|
|
tm_time.tm_min = (int)date.minutes;
|
|
tm_time.tm_sec = (int)date.seconds;
|
|
tm_time.tm_isdst = -1; // tm_isdst = -1 lets mktime() determine whether DST is in effect
|
|
time_t unix_time = mktime(&tm_time);
|
|
DN_U64 result = DN_Cast(DN_U64) unix_time;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_U64 DN_OS_DateLocalUnixTimeSFromUnixTimeS(DN_U64 unix_ts_s)
|
|
{
|
|
struct tm tm_local;
|
|
time_t unix_ts = unix_ts_s;
|
|
void *ret = localtime_r(&unix_ts, &tm_local);
|
|
DN_Assert(ret);
|
|
|
|
long local_offset_seconds = tm_local.tm_gmtoff;
|
|
DN_U64 result = unix_ts_s;
|
|
if (local_offset_seconds > 0)
|
|
result += local_offset_seconds;
|
|
else
|
|
result -= local_offset_seconds;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Date DN_OS_DateUnixTimeSToDate(DN_U64 time)
|
|
{
|
|
time_t posix_time = DN_Cast(time_t) time;
|
|
struct tm posix_date = *gmtime(&posix_time);
|
|
DN_Date 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 void DN_OS_GenBytesSecure(void *buffer, DN_U32 size)
|
|
{
|
|
#if defined(DN_PLATFORM_EMSCRIPTEN)
|
|
DN_InvalidCodePath;
|
|
(void)buffer;
|
|
(void)size;
|
|
#else
|
|
DN_Assert(buffer && size);
|
|
DN_USize bytes_written = 0;
|
|
while (bytes_written < size) {
|
|
DN_USize bytes_remaining = size - bytes_written;
|
|
DN_USize need_amount = DN_Min(bytes_remaining, 32);
|
|
DN_USize bytes_read = 0;
|
|
do {
|
|
bytes_read = getrandom((DN_U8 *)buffer + bytes_written, need_amount, 0);
|
|
} while (bytes_read != need_amount || errno == EAGAIN || errno == EINTR);
|
|
bytes_written += bytes_read;
|
|
}
|
|
#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_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_OSDiskSpace result = {};
|
|
DN_Str8 path_z_terminated = DN_Str8FromStr8Arena(path, &scratch.arena);
|
|
struct statvfs info = {};
|
|
if (statvfs(path_z_terminated.data, &info) == 0) {
|
|
result.success = true;
|
|
result.avail = info.f_bavail * info.f_frsize;
|
|
result.size = info.f_blocks * info.f_frsize;
|
|
}
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_OS_EXEPath(DN_Arena *arena)
|
|
{
|
|
DN_Str8 result = {};
|
|
if (!arena)
|
|
return result;
|
|
|
|
DN_U64 mem_p = DN_MemListPos(arena->mem);
|
|
int required_size_wo_null_terminator = 0;
|
|
for (int try_size = 128;; try_size *= 2) {
|
|
char *try_buf = DN_ArenaNewArray(arena, char, try_size, DN_ZMem_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;
|
|
}
|
|
}
|
|
DN_MemListPopTo(arena->mem, mem_p);
|
|
|
|
if (required_size_wo_null_terminator) {
|
|
mem_p = DN_MemListPos(arena->mem);
|
|
char *exe_path = DN_ArenaNewArray(arena, char, required_size_wo_null_terminator + 1, DN_ZMem_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_MemListPopTo(arena->mem, mem_p);
|
|
} else {
|
|
result = DN_Str8FromPtr(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 DN_U64 DN_OS_PerfCounterFrequency()
|
|
{
|
|
// NOTE: On Linux we use clock_gettime(CLOCK_MONOTONIC_RAW) (or CLOCK_MONOTONIC) which
|
|
// increments at nanosecond granularity.
|
|
DN_U64 result = 1'000'000'000;
|
|
return result;
|
|
}
|
|
|
|
static DN_OSPosixCore *DN_OS_PosixGetCore()
|
|
{
|
|
DN_Core *dn = DN_Get();
|
|
DN_Assert(dn && dn->os_init);
|
|
DN_OSPosixCore *result = DN_Cast(DN_OSPosixCore *)dn->os.platform_context;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_U64 DN_OS_PerfCounterNow()
|
|
{
|
|
DN_OSPosixCore *posix = DN_OS_PosixGetCore();
|
|
struct timespec ts;
|
|
clock_gettime(posix->clock_monotonic_raw ? CLOCK_MONOTONIC_RAW : CLOCK_MONOTONIC, &ts);
|
|
DN_U64 result = DN_Cast(DN_U64) ts.tv_sec * 1'000'000'000 + DN_Cast(DN_U64) ts.tv_nsec;
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_OS_FileCopy(DN_Str8 src, DN_Str8 dest, bool overwrite, DN_ErrSink *error)
|
|
{
|
|
bool result = false;
|
|
#if defined(DN_PLATFORM_EMSCRIPTEN)
|
|
DN_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_ErrSinkAppendF(error,
|
|
error_code,
|
|
"Failed to open file '%.*s' for copying: (%d) %s",
|
|
DN_Str8PrintFmt(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_ErrSinkAppendF(error,
|
|
error_code,
|
|
"Failed to open file destination '%.*s' for copying to: (%d) %s",
|
|
DN_Str8PrintFmt(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_ErrSinkAppendF(error,
|
|
error_code,
|
|
"Failed to query file size of '%.*s' for copying: (%d) %s",
|
|
DN_Str8PrintFmt(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_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_Str8 file_size_str8 = DN_Str8FromByteCount(scratch.arena, stat_existing.st_size, DN_ByteCountType_Auto);
|
|
DN_Str8 bytes_written_str8 = DN_Str8FromByteCount(scratch.arena, bytes_written, DN_ByteCountType_Auto);
|
|
DN_rrSinkAppendF(error,
|
|
error_code,
|
|
"Failed to copy file '%.*s' to '%.*s', we copied %.*s but the file "
|
|
"size is %.*s: (%d) %s",
|
|
DN_Str8PrintFmt(src),
|
|
DN_Str8PrintFmt(dest),
|
|
DN_Str8PrintFmt(bytes_written_str8),
|
|
DN_Str8PrintFmt(file_size_str8),
|
|
error_code,
|
|
strerror(error_code));
|
|
DN_TCScratchEnd(&scratch);
|
|
}
|
|
|
|
#endif
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_OS_FileMove(DN_Str8 src, DN_Str8 dest, bool overwrite, DN_ErrSink *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_FileCopy(src, dest, overwrite, error);
|
|
}
|
|
|
|
if (file_moved) {
|
|
result = true;
|
|
int unlink_result = unlink(src.data);
|
|
if (unlink_result == -1) {
|
|
int error_code = errno;
|
|
DN_ErrSinkAppendF(
|
|
error,
|
|
error_code,
|
|
"File '%.*s' was moved but failed to be unlinked from old location: (%d) %s",
|
|
DN_Str8PrintFmt(src),
|
|
error_code,
|
|
strerror(error_code));
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_OSFile DN_OS_FileOpen(DN_Str8 path,
|
|
DN_OSFileOpen open_mode,
|
|
DN_OSFileAccess access,
|
|
DN_ErrSink *error)
|
|
{
|
|
DN_OSFile result = {};
|
|
if (path.size == 0 || 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_ErrSinkAppendF(
|
|
error,
|
|
1,
|
|
"Failed to open file '%.*s': File access flag 'execute' is not supported",
|
|
DN_Str8PrintFmt(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_ErrSinkAppendF(error,
|
|
1,
|
|
"Failed to open file '%.*s': File could not be opened in requested "
|
|
"mode 'DN_OSFileOpen' flag %d",
|
|
DN_Str8PrintFmt(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_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_ErrSink *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_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_Str8x32 buffer_size_str8 = DN_ByteCountStr8x32(size);
|
|
DN_ErrSinkAppendF(err, 1, "Failed to read %S from file", buffer_size_str8);
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
result.success = true;
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_OS_FileWritePtr(DN_OSFile *file, void const *buffer, DN_USize size, DN_ErrSink *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_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_Str8x32 buffer_size_str8 = DN_ByteCountStr8x32(size);
|
|
DN_ErrSinkAppendF(err, 1, "Failed to write buffer (%s) to file handle", DN_Str8PrintFmt(buffer_size_str8));
|
|
DN_TCScratchEnd(&scratch);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_OS_FileFlush(DN_OSFile *file, DN_ErrSink *err)
|
|
{
|
|
// TODO: errno is not thread safe
|
|
int fd = fileno(DN_Cast(FILE *) file->handle);
|
|
if (fd == -1) {
|
|
DN_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_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 = {};
|
|
}
|
|
|
|
DN_API DN_OSPathInfo DN_OS_PathInfo(DN_Str8 path)
|
|
{
|
|
DN_OSPathInfo result = {};
|
|
if (path.size == 0)
|
|
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 (path.size)
|
|
result = remove(path.data) == 0;
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_OS_PathIsFile(DN_Str8 path)
|
|
{
|
|
bool result = false;
|
|
if (path.size == 0)
|
|
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_PathIsDir(DN_Str8 path)
|
|
{
|
|
bool result = false;
|
|
if (path.size == 0)
|
|
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_PathMakeDir(DN_Str8 path)
|
|
{
|
|
DN_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
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_Str8FromStr8Arena(path, &scratch.arena);
|
|
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_PathIsFile(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.
|
|
DN_TCScratchEnd(&scratch);
|
|
return false;
|
|
} else if (DN_OS_PathIsDir(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--) {
|
|
DN_U16 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;
|
|
}
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_OS_PathIterateDir(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_CStr8Size(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_Str8FromPtr(it->buffer, clamped_size);
|
|
return true;
|
|
}
|
|
|
|
closedir(DN_Cast(DIR *) it->handle);
|
|
it->handle = NULL;
|
|
it->file_name = {};
|
|
it->buffer[0] = 0;
|
|
return false;
|
|
}
|
|
|
|
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_ErrSink *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_TCScratch scratch = DN_TCScratchBegin(&arena, 1);
|
|
if (arena && handle.stdout_read) {
|
|
char buffer[4096];
|
|
DN_Str8Builder builder = DN_Str8BuilderFromArena(&scratch.arena);
|
|
for (;;) {
|
|
ssize_t bytes_read =
|
|
read(stdout_pipe[DN_OSPipeType__Read], buffer, sizeof(buffer));
|
|
if (bytes_read <= 0)
|
|
break;
|
|
DN_Str8BuilderAppendF(&builder, "%.*s", bytes_read, buffer);
|
|
}
|
|
|
|
result.stdout_text = DN_Str8BuilderBuild(&builder, arena);
|
|
}
|
|
|
|
if (arena && handle.stderr_read) {
|
|
char buffer[4096];
|
|
DN_Str8Builder builder = DN_Str8BuilderFromArena(&scratch.arena);
|
|
for (;;) {
|
|
ssize_t bytes_read =
|
|
read(stderr_pipe[DN_OSPipeType__Read], buffer, sizeof(buffer));
|
|
if (bytes_read <= 0)
|
|
break;
|
|
DN_Str8BuilderAppendF(&builder, "%.*s", bytes_read, buffer);
|
|
}
|
|
|
|
result.stderr_text = DN_Str8BuilderBuild(&builder, arena);
|
|
}
|
|
DN_TCScratchEnd(&scratch);
|
|
}
|
|
|
|
close(stdout_pipe[DN_OSPipeType__Read]);
|
|
close(stderr_pipe[DN_OSPipeType__Read]);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_OSExecAsyncHandle DN_OS_ExecAsync(DN_Str8Slice cmd_line,
|
|
DN_OSExecArgs *args,
|
|
DN_ErrSink *error)
|
|
{
|
|
#if defined(DN_PLATFORM_EMSCRIPTEN)
|
|
DN_InvalidCodePathF("Unsupported operation");
|
|
#endif
|
|
DN_AssertFOnce(args->environment.count == 0, "Unimplemented in POSIX");
|
|
|
|
DN_OSExecAsyncHandle result = {};
|
|
if (cmd_line.count == 0)
|
|
return result;
|
|
|
|
DN_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_DEFER { DN_TCScratchEnd(&scratch); };
|
|
DN_Str8 cmd_rendered = DN_Str8SliceRender(cmd_line, DN_Str8Lit(" "), &scratch.arena);
|
|
int stdout_pipe[DN_OSPipeType__Count] = {};
|
|
int stderr_pipe[DN_OSPipeType__Count] = {};
|
|
|
|
// NOTE: Open stdout pipe
|
|
if (DN_BitIsSet(args->flags, DN_OSExecFlags_SaveStdout)) {
|
|
if (pipe(stdout_pipe) == -1) {
|
|
result.os_error_code = errno;
|
|
DN_ErrSinkAppendF(
|
|
error,
|
|
result.os_error_code,
|
|
"Failed to create stdout pipe to redirect the output of the command '%.*s': %s",
|
|
DN_Str8PrintFmt(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_BitIsSet(args->flags, DN_OSExecFlags_SaveStderr)) {
|
|
if (DN_BitIsSet(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_ErrSinkAppendF(
|
|
error,
|
|
result.os_error_code,
|
|
"Failed to create stderr pipe to redirect the output of the command '%.*s': %s",
|
|
DN_Str8PrintFmt(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_ErrSinkAppendF(
|
|
error,
|
|
result.os_error_code,
|
|
"Failed to fork process to execute the command '%.*s': %s",
|
|
DN_Str8PrintFmt(cmd_rendered),
|
|
strerror(result.os_error_code));
|
|
return result;
|
|
}
|
|
|
|
if (child_pid == 0) { // Child process
|
|
if (DN_BitIsSet(args->flags, DN_OSExecFlags_SaveStdout) &&
|
|
(dup2(stdout_pipe[DN_OSPipeType__Write], STDOUT_FILENO) == -1)) {
|
|
result.os_error_code = errno;
|
|
DN_ErrSinkAppendF(
|
|
error,
|
|
result.os_error_code,
|
|
"Failed to redirect stdout 'write' pipe for output of command '%.*s': %s",
|
|
DN_Str8PrintFmt(cmd_rendered),
|
|
strerror(result.os_error_code));
|
|
return result;
|
|
}
|
|
|
|
if (DN_BitIsSet(args->flags, DN_OSExecFlags_SaveStderr) &&
|
|
(dup2(stderr_pipe[DN_OSPipeType__Write], STDERR_FILENO) == -1)) {
|
|
result.os_error_code = errno;
|
|
DN_ErrSinkAppendF(
|
|
error,
|
|
result.os_error_code,
|
|
"Failed to redirect stderr 'read' pipe for output of command '%.*s': %s",
|
|
DN_Str8PrintFmt(cmd_rendered),
|
|
strerror(result.os_error_code));
|
|
return result;
|
|
}
|
|
|
|
// NOTE: Convert the command into something suitable for execvp
|
|
char **argv =
|
|
DN_ArenaNewArray(&scratch.arena, char *, cmd_line.count + 1 /*null*/, DN_ZMem_Yes);
|
|
if (!argv) {
|
|
result.exit_code = -1;
|
|
DN_ErrSinkAppendF(
|
|
error,
|
|
result.os_error_code,
|
|
"Failed to create argument values from command line '%.*s': Out of memory",
|
|
DN_Str8PrintFmt(cmd_rendered));
|
|
return result;
|
|
}
|
|
|
|
for (DN_ForIndexU(arg_index, cmd_line.count)) {
|
|
DN_Str8 arg = cmd_line.data[arg_index];
|
|
argv[arg_index] = DN_Str8FromStr8Arena(arg, &scratch.arena).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_Str8FromStr8Arena(args->working_dir, &scratch.arena);
|
|
if (chdir(working_dir.data) == -1) {
|
|
result.os_error_code = errno;
|
|
DN_ErrSinkAppendF(
|
|
error,
|
|
result.os_error_code,
|
|
"Failed to create argument values from command line '%.*s': %s",
|
|
DN_Str8PrintFmt(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_ErrSinkAppendF(
|
|
error,
|
|
result.os_error_code,
|
|
"Failed to execute command'%.*s': %s",
|
|
DN_Str8PrintFmt(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_BitIsSet(args->flags, DN_OSExecFlags_SaveStderr) && DN_BitIsNotSet(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_ErrSink *err)
|
|
{
|
|
DN_InvalidCodePath;
|
|
DN_OSExecResult result = {};
|
|
return result;
|
|
}
|
|
|
|
static DN_OSPosixSyncPrimitive *DN_OS_PosixU64ToSyncPrimitive_(DN_U64 u64)
|
|
{
|
|
DN_OSPosixSyncPrimitive *result = nullptr;
|
|
DN_Memcpy(&result, &u64, sizeof(result));
|
|
return result;
|
|
}
|
|
|
|
static DN_U64 DN_OS_PosixSyncPrimitiveToU64(DN_OSPosixSyncPrimitive *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_OSPosixSyncPrimitive *DN_POSIX_AllocSyncPrimitive_()
|
|
{
|
|
DN_OSPosixCore *posix = DN_OS_PosixGetCore();
|
|
DN_OSPosixSyncPrimitive *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;
|
|
result = DN_ArenaNew(&os->arena, DN_OSPosixSyncPrimitive, DN_ZMem_Yes);
|
|
}
|
|
}
|
|
pthread_mutex_unlock(&posix->sync_primitive_free_list_mutex);
|
|
return result;
|
|
}
|
|
|
|
static void DN_OS_PosixDeallocSyncPrimitive_(DN_OSPosixSyncPrimitive *primitive)
|
|
{
|
|
if (primitive) {
|
|
DN_OSPosixCore *posix = DN_OS_PosixGetCore();
|
|
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_OSPosixSyncPrimitive *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_OS_PosixSyncPrimitiveToU64(primitive);
|
|
else
|
|
DN_OS_PosixDeallocSyncPrimitive_(primitive);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_OS_SemaphoreDeinit(DN_OSSemaphore *semaphore)
|
|
{
|
|
if (semaphore && semaphore->handle != 0) {
|
|
DN_OSPosixSyncPrimitive *primitive = DN_OS_PosixU64ToSyncPrimitive_(semaphore->handle);
|
|
sem_destroy(&primitive->sem);
|
|
DN_OS_PosixDeallocSyncPrimitive_(primitive);
|
|
*semaphore = {};
|
|
}
|
|
}
|
|
|
|
DN_API void DN_OS_SemaphoreIncrement(DN_OSSemaphore *semaphore, DN_U32 amount)
|
|
{
|
|
if (semaphore && semaphore->handle != 0) {
|
|
DN_OSPosixSyncPrimitive *primitive = DN_OS_PosixU64ToSyncPrimitive_(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_OSPosixSyncPrimitive *primitive = DN_OS_PosixU64ToSyncPrimitive_(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;
|
|
}
|
|
|
|
DN_API DN_OSBarrier DN_OS_BarrierInit(DN_U32 thread_count)
|
|
{
|
|
DN_OSPosixSyncPrimitive *primitive = DN_POSIX_AllocSyncPrimitive_();
|
|
DN_OSBarrier result = {};
|
|
if (primitive) {
|
|
int init_result = pthread_barrier_init(&primitive->barrier, /*attr*/ NULL, thread_count);
|
|
if (init_result == 0) {
|
|
result.handle = DN_OS_PosixSyncPrimitiveToU64(primitive);
|
|
} else {
|
|
DN_OS_PosixDeallocSyncPrimitive_(primitive);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_OS_BarrierDeinit(DN_OSBarrier *barrier)
|
|
{
|
|
if (barrier && barrier->handle != 0) {
|
|
DN_OSPosixSyncPrimitive *primitive = DN_OS_PosixU64ToSyncPrimitive_(barrier->handle);
|
|
int del_result = pthread_barrier_destroy(&primitive->barrier);
|
|
DN_Assert(del_result == 0);
|
|
DN_OS_PosixDeallocSyncPrimitive_(primitive);
|
|
}
|
|
}
|
|
|
|
DN_API void DN_OS_BarrierWait(DN_OSBarrier *barrier)
|
|
{
|
|
if (barrier && barrier->handle != 0) {
|
|
DN_OSPosixSyncPrimitive *primitive = DN_OS_PosixU64ToSyncPrimitive_(barrier->handle);
|
|
pthread_barrier_wait(&primitive->barrier);
|
|
}
|
|
}
|
|
|
|
// NOTE: DN_OSMutex
|
|
DN_API DN_OSMutex DN_OS_MutexInit()
|
|
{
|
|
DN_OSPosixSyncPrimitive *primitive = DN_POSIX_AllocSyncPrimitive_();
|
|
DN_OSMutex result = {};
|
|
if (primitive) {
|
|
if (pthread_mutex_init(&primitive->mutex, nullptr) == 0)
|
|
result.handle = DN_OS_PosixSyncPrimitiveToU64(primitive);
|
|
else
|
|
DN_OS_PosixDeallocSyncPrimitive_(primitive);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_OS_MutexDeinit(DN_OSMutex *mutex)
|
|
{
|
|
if (mutex && mutex->handle != 0) {
|
|
DN_OSPosixSyncPrimitive *primitive = DN_OS_PosixU64ToSyncPrimitive_(mutex->handle);
|
|
pthread_mutex_destroy(&primitive->mutex);
|
|
DN_OS_PosixDeallocSyncPrimitive_(primitive);
|
|
*mutex = {};
|
|
}
|
|
}
|
|
|
|
DN_API void DN_OS_MutexLock(DN_OSMutex *mutex)
|
|
{
|
|
if (mutex && mutex->handle != 0) {
|
|
DN_OSPosixSyncPrimitive *primitive = DN_OS_PosixU64ToSyncPrimitive_(mutex->handle);
|
|
pthread_mutex_lock(&primitive->mutex);
|
|
}
|
|
}
|
|
|
|
DN_API void DN_OS_MutexUnlock(DN_OSMutex *mutex)
|
|
{
|
|
if (mutex && mutex->handle != 0) {
|
|
DN_OSPosixSyncPrimitive *primitive = DN_OS_PosixU64ToSyncPrimitive_(mutex->handle);
|
|
pthread_mutex_unlock(&primitive->mutex);
|
|
}
|
|
}
|
|
|
|
DN_API DN_OSConditionVariable DN_OS_ConditionVariableInit()
|
|
{
|
|
DN_OSPosixSyncPrimitive *primitive = DN_POSIX_AllocSyncPrimitive_();
|
|
DN_OSConditionVariable result = {};
|
|
if (primitive) {
|
|
if (pthread_cond_init(&primitive->cv, nullptr) == 0)
|
|
result.handle = DN_OS_PosixSyncPrimitiveToU64(primitive);
|
|
else
|
|
DN_OS_PosixDeallocSyncPrimitive_(primitive);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_OS_ConditionVariableDeinit(DN_OSConditionVariable *cv)
|
|
{
|
|
if (cv && cv->handle != 0) {
|
|
DN_OSPosixSyncPrimitive *primitive = DN_OS_PosixU64ToSyncPrimitive_(cv->handle);
|
|
pthread_cond_destroy(&primitive->cv);
|
|
DN_OS_PosixDeallocSyncPrimitive_(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_OSPosixSyncPrimitive *cv_primitive = DN_OS_PosixU64ToSyncPrimitive_(cv->handle);
|
|
DN_OSPosixSyncPrimitive *mutex_primitive = DN_OS_PosixU64ToSyncPrimitive_(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_OSPosixSyncPrimitive *primitive = DN_OS_PosixU64ToSyncPrimitive_(cv->handle);
|
|
pthread_cond_signal(&primitive->cv);
|
|
}
|
|
}
|
|
|
|
DN_API void DN_OS_ConditionVariableBroadcast(DN_OSConditionVariable *cv)
|
|
{
|
|
if (cv && cv->handle != 0) {
|
|
DN_OSPosixSyncPrimitive *primitive = DN_OS_PosixU64ToSyncPrimitive_(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, DN_OSThreadLane *lane, 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*/);
|
|
thread->lane = *lane;
|
|
|
|
// 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 bool DN_OS_ThreadJoin(DN_OSThread *thread, DN_TCDeinitArenas deinit_arenas)
|
|
{
|
|
bool result = false;
|
|
if (thread && thread->handle) {
|
|
pthread_t thread_id = {};
|
|
DN_Memcpy(&thread_id, &thread->thread_id, sizeof(thread_id));
|
|
|
|
void *return_val = nullptr;
|
|
result = pthread_join(thread_id, &return_val) == 0;
|
|
thread->handle = {};
|
|
thread->thread_id = {};
|
|
DN_TCDeinit(&thread->context, deinit_arenas);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
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_OS_PosixInit(DN_OSPosixCore *posix)
|
|
{
|
|
int mutex_init = pthread_mutex_init(&posix->sync_primitive_free_list_mutex, nullptr);
|
|
DN_Assert(mutex_init == 0);
|
|
|
|
struct timespec ts;
|
|
posix->clock_monotonic_raw = clock_gettime(CLOCK_MONOTONIC_RAW, &ts) != -1;
|
|
if (!posix->clock_monotonic_raw) {
|
|
int get_result = clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
DN_AssertF(get_result != -1, "CLOCK_MONOTONIC_RAW and CLOCK_MONOTONIC are not supported by this platform");
|
|
}
|
|
}
|
|
|
|
DN_API void DN_OS_PosixThreadSetName(DN_Str8 name)
|
|
{
|
|
#if defined(DN_PLATFORM_EMSCRIPTEN)
|
|
(void)name;
|
|
#else
|
|
DN_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_Str8 copy = DN_Str8FromStr8Arena(name, &scratch.arena);
|
|
pthread_t thread = pthread_self();
|
|
pthread_setname_np(thread, (char *)copy.data);
|
|
DN_TCScratchEnd(&scratch);
|
|
#endif
|
|
}
|
|
|
|
DN_API DN_OSPosixProcSelfStatus DN_OS_PosixProcSelfStatus()
|
|
{
|
|
DN_OSPosixProcSelfStatus 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_Str8Lit("/proc/self/status"), DN_OSFileOpen_OpenIfExist, DN_OSFileAccess_Read, nullptr);
|
|
|
|
if (!file.error) {
|
|
DN_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
char buf[256];
|
|
DN_Str8Builder builder = DN_Str8BuilderFromArena(&scratch.arena);
|
|
for (;;) {
|
|
DN_OSFileRead read = DN_OS_FileRead(&file, buf, sizeof(buf), nullptr);
|
|
if (!read.success || read.bytes_read == 0)
|
|
break;
|
|
DN_Str8BuilderAppendF(&builder, "%.*s", DN_Cast(int)read.bytes_read, buf);
|
|
}
|
|
|
|
DN_Str8 const NAME = DN_Str8Lit("Name:");
|
|
DN_Str8 const PID = DN_Str8Lit("Pid:");
|
|
DN_Str8 const VM_PEAK = DN_Str8Lit("VmPeak:");
|
|
DN_Str8 const VM_SIZE = DN_Str8Lit("VmSize:");
|
|
DN_Str8 status_buf = DN_Str8BuilderBuild(&builder, &scratch.arena);
|
|
DN_Str8SplitResult lines = DN_Str8SplitArena(status_buf, DN_Str8Lit("\n"), DN_Str8SplitFlags_ExcludeEmptyStrings, &scratch.arena);
|
|
|
|
for (DN_ForItSize(line_it, DN_Str8, lines.data, lines.count)) {
|
|
DN_Str8 line = DN_Str8TrimWhitespaceAround(*line_it.data);
|
|
if (DN_Str8StartsWith(line, NAME, DN_Str8EqCase_Insensitive)) {
|
|
DN_Str8 str8 = DN_Str8TrimWhitespaceAround(DN_Str8Subset(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_Str8StartsWith(line, PID, DN_Str8EqCase_Insensitive)) {
|
|
DN_Str8 str8 = DN_Str8TrimWhitespaceAround(DN_Str8Subset(line, PID.size, line.size));
|
|
DN_U64FromResult to_u64 = DN_U64FromStr8(str8, 0);
|
|
result.pid = to_u64.value;
|
|
DN_Assert(to_u64.success);
|
|
} else if (DN_Str8StartsWith(line, VM_SIZE, DN_Str8EqCase_Insensitive)) {
|
|
DN_Str8 size_with_kb = DN_Str8TrimWhitespaceAround(DN_Str8Subset(line, VM_SIZE.size, line.size));
|
|
DN_Assert(DN_Str8EndsWith(size_with_kb, DN_Str8Lit("kB")));
|
|
DN_Str8 vm_size = DN_Str8BSplit(size_with_kb, DN_Str8Lit(" ")).lhs;
|
|
DN_U64FromResult to_u64 = DN_U64FromStr8(vm_size, 0);
|
|
result.vm_size = DN_Kilobytes(to_u64.value);
|
|
DN_Assert(to_u64.success);
|
|
} else if (DN_Str8StartsWith(line, VM_PEAK, DN_Str8EqCase_Insensitive)) {
|
|
DN_Str8 size_with_kb = DN_Str8TrimWhitespaceAround(DN_Str8Subset(line, VM_PEAK.size, line.size));
|
|
DN_Assert(DN_Str8EndsWith(size_with_kb, DN_Str8Lit("kB")));
|
|
DN_Str8 vm_size = DN_Str8BSplit(size_with_kb, DN_Str8Lit(" ")).lhs;
|
|
DN_U64FromResult to_u64 = DN_U64FromStr8(vm_size, 0);
|
|
result.vm_peak = DN_Kilobytes(to_u64.value);
|
|
DN_Assert(to_u64.success);
|
|
}
|
|
}
|
|
DN_TCScratchEnd(&scratch);
|
|
}
|
|
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_LogInfoF("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_LogInfoF("Received: %.*s", event->numBytes, event->data);
|
|
} else {
|
|
DN_LogInfoF("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_Str8AllocArena(fetch->numBytes, DN_ZMem_No, response->arena);
|
|
if (response->body.data)
|
|
DN_Memcpy(response->body.data, fetch->data, fetch->numBytes);
|
|
|
|
DN_OS_SemaphoreIncrement(&response->on_complete_semaphore, 1);
|
|
DN_AtomicAddU32(&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_Str8AllocArena(fetch->numBytes, DN_ZMem_No, response->arena);
|
|
if (response->body.size)
|
|
DN_Memcpy(response->body.data, fetch->data, fetch->numBytes);
|
|
|
|
DN_OS_SemaphoreIncrement(&response->on_complete_semaphore, 1);
|
|
DN_AtomicAddU32(&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->scratch_arena.mem ? &response->scratch_arena : &response->tmp_arena;
|
|
|
|
DN_Arena *scratch = &response->scratch_arena;
|
|
DN_TCScratch scratch_ = DN_TCScratchBegin(&arena, 1);
|
|
DN_DEFER { DN_TCScratchEnd(&scratch_); };
|
|
if (!scratch)
|
|
scratch = &scratch_.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_Str8FromFmtArena(arena,
|
|
"Request method in EM has a size limit of 31 characters, method was "
|
|
"'%.*s' which is %zu characters long",
|
|
DN_Str8PrintFmt(method),
|
|
method.size);
|
|
DN_CheckF(method.size < sizeof(fetch_attribs.requestMethod),
|
|
"%.*s",
|
|
DN_Str8PrintFmt(response->error_msg));
|
|
response->error_code = DN_Cast(DN_U32) - 1;
|
|
DN_AtomicAddU32(&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_Str8FromFmtArena(scratch, "%.*s%.*s", DN_Str8PrintFmt(host), DN_Str8PrintFmt(path));
|
|
DN_LogInfoF("Initiating HTTP '%s' request to '%.*s' with payload '%.*s'",
|
|
fetch_attribs.requestMethod,
|
|
DN_Str8PrintFmt(url),
|
|
DN_Str8PrintFmt(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_MemListDeinit(response->tmp_arena.mem);
|
|
DN_OS_SemaphoreDeinit(&response->on_complete_semaphore);
|
|
*response = {};
|
|
}
|
|
#elif defined(DN_PLATFORM_WIN32)
|
|
// DN: Single header generator commented out => #include "OS/dn_os_w32.cpp"
|
|
#define DN_OS_W32_CPP
|
|
|
|
// DN: Single header generator commented out => #if defined(_CLANGD)
|
|
// #define DN_H_WITH_CORE 1
|
|
// #define DN_H_WITH_OS 1
|
|
// #include "../dn.h"
|
|
// #include "dn_os_w32.h"
|
|
// #endif
|
|
|
|
// 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_LogWarningF("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_Core *dn = DN_Get();
|
|
DN_AtomicAddU64(&dn->os.vmem_allocs_total, 1);
|
|
DN_AtomicAddU64(&dn->os.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_Core *dn = DN_Get();
|
|
DN_AtomicAddU64(&dn->os.vmem_allocs_total, 1);
|
|
DN_AtomicAddU64(&dn->os.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_Str8Lit("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, DN_Get()->os.page_size), "%s", ALIGNMENT_ERROR_MSG.data);
|
|
DN_AssertF(DN_IsPowerOfTwoAligned(size, DN_Get()->os.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_ZMem z_mem)
|
|
{
|
|
DN_Core *dn = DN_Get();
|
|
DN_RawAssert(dn->init_flags & DN_InitFlags_OS && "DN must be initialised with the OS flag");
|
|
DN_U32 flags = z_mem == DN_ZMem_Yes ? HEAP_ZERO_MEMORY : 0;
|
|
DN_Assert(size <= DN_Cast(DWORD)(-1));
|
|
void *result = HeapAlloc(GetProcessHeap(), flags, DN_Cast(DWORD) size);
|
|
DN_AtomicAddU64(&dn->os.mem_allocs_total, 1);
|
|
DN_AtomicAddU64(&dn->os.mem_allocs_frame, 1);
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_OS_MemDealloc(void *ptr)
|
|
{
|
|
HeapFree(GetProcessHeap(), 0, ptr);
|
|
}
|
|
|
|
// NOTE: Date
|
|
DN_API DN_Date DN_OS_DateLocalTimeNow()
|
|
{
|
|
SYSTEMTIME sys_time;
|
|
GetLocalTime(&sys_time);
|
|
|
|
DN_Date result = {};
|
|
result.hour = DN_Cast(DN_U8) sys_time.wHour;
|
|
result.milliseconds = DN_Cast(DN_U8) sys_time.wMilliseconds;
|
|
result.minutes = DN_Cast(DN_U8) sys_time.wMinute;
|
|
result.seconds = DN_Cast(DN_U8) sys_time.wSecond;
|
|
result.day = DN_Cast(DN_U8) sys_time.wDay;
|
|
result.month = DN_Cast(DN_U8) sys_time.wMonth;
|
|
result.year = DN_Cast(DN_U16) 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_Date 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;
|
|
result.wMilliseconds = date.milliseconds;
|
|
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_DateUnixTimeSFromLocalDate(DN_Date 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_DateLocalUnixTimeSFromUnixTimeS(DN_U64 unix_ts_s)
|
|
{
|
|
DN_U64 unix_time = DN_Cast(DN_U64) unix_ts_s * 10000000LL; // seconds -> 100ns units
|
|
DN_U64 filetime_utc = unix_time + 116444736000000000LL; // Unix epoch -> Windows epoch
|
|
FILETIME ft_utc = {DN_Cast(DWORD) filetime_utc, DN_Cast(DWORD)(filetime_utc >> 32)};
|
|
FILETIME ft_local;
|
|
bool converted = FileTimeToLocalFileTime(&ft_utc, &ft_local);
|
|
DN_Assert(converted);
|
|
|
|
DN_U64 filetime_local = (DN_Cast(DN_U64) ft_local.dwHighDateTime << 32) | ft_local.dwLowDateTime;
|
|
DN_U64 result = (filetime_local - 116444736000000000LL) / 10000000LL;
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_OS_GenBytesSecure(void *buffer, DN_U32 size)
|
|
{
|
|
DN_OSW32Core *w32 = DN_Cast(DN_OSW32Core *) DN_Get()->os.platform_context;
|
|
DN_Assert(w32->bcrypt_init_success);
|
|
|
|
long gen_status = BCryptGenRandom(w32->bcrypt_rng_handle, DN_Cast(unsigned char *) buffer, size, 0 /*flags*/);
|
|
// NOTE: This can only fail if the handle is invalid or one or more parameters are invalid. We
|
|
// validate our parameters so this shouldn't be the case.
|
|
DN_Assert(gen_status == 0);
|
|
}
|
|
|
|
DN_API DN_OSDiskSpace DN_OS_DiskSpace(DN_Str8 path)
|
|
{
|
|
DN_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_OSDiskSpace result = {};
|
|
DN_Str16 path16 = DN_OS_W32Str8ToStr16(&scratch.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)) {
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
result.success = true;
|
|
result.avail = free_bytes_avail_to_caller.QuadPart;
|
|
result.size = total_number_of_bytes.QuadPart;
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_OS_SetEnvVar(DN_Str8 name, DN_Str8 value)
|
|
{
|
|
DN_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_Str16 name16 = DN_OS_W32Str8ToStr16(&scratch.arena, name);
|
|
DN_Str16 value16 = DN_OS_W32Str8ToStr16(&scratch.arena, value);
|
|
bool result = SetEnvironmentVariableW(name16.data, value16.data) != 0;
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_OS_EXEPath(DN_Arena *arena)
|
|
{
|
|
DN_Str8 result = {};
|
|
if (!arena)
|
|
return result;
|
|
DN_TCScratch scratch = DN_TCScratchBegin(&arena, 1);
|
|
DN_Str16 exe_dir16 = DN_OS_W32EXEPathW(&scratch.arena);
|
|
result = DN_OS_W32Str16ToStr8(arena, exe_dir16);
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_OS_SleepMs(DN_UInt milliseconds)
|
|
{
|
|
Sleep(milliseconds);
|
|
}
|
|
|
|
DN_API DN_U64 DN_OS_PerfCounterFrequency()
|
|
{
|
|
DN_OSW32Core *w32 = DN_Cast(DN_OSW32Core *) DN_Get()->os.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;
|
|
}
|
|
|
|
static DN_U64 DN_OS_W32FileTimeToSeconds_(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 bool DN_OS_FileCopy(DN_Str8 src, DN_Str8 dest, bool overwrite, DN_ErrSink *err)
|
|
{
|
|
bool result = false;
|
|
DN_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_Str16 src16 = DN_OS_W32Str8ToStr16(&scratch.arena, src);
|
|
DN_Str16 dest16 = DN_OS_W32Str8ToStr16(&scratch.arena, dest);
|
|
|
|
int fail_if_exists = overwrite == false;
|
|
result = CopyFileW(src16.data, dest16.data, fail_if_exists) != 0;
|
|
|
|
if (!result) {
|
|
DN_OSW32Error win_error = DN_OS_W32LastError(&scratch.arena);
|
|
DN_ErrSinkAppendF(err,
|
|
win_error.code,
|
|
"Failed to copy file '%.*s' to '%.*s': (%u) %.*s",
|
|
DN_Str8PrintFmt(src),
|
|
DN_Str8PrintFmt(dest),
|
|
win_error.code,
|
|
DN_Str8PrintFmt(win_error.msg));
|
|
}
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_OS_FileMove(DN_Str8 src, DN_Str8 dest, bool overwrite, DN_ErrSink *err)
|
|
{
|
|
bool result = false;
|
|
DN_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_Str16 src16 = DN_OS_W32Str8ToStr16(&scratch.arena, src);
|
|
DN_Str16 dest16 = DN_OS_W32Str8ToStr16(&scratch.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_OSW32Error win_error = DN_OS_W32LastError(&scratch.arena);
|
|
DN_ErrSinkAppendF(err,
|
|
win_error.code,
|
|
"Failed to move file '%.*s' to '%.*s': (%u) %.*s",
|
|
DN_Str8PrintFmt(src),
|
|
DN_Str8PrintFmt(dest),
|
|
win_error.code,
|
|
DN_Str8PrintFmt(win_error.msg));
|
|
}
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_OSFile DN_OS_FileOpen(DN_Str8 path, DN_OSFileOpen open_mode, DN_OSFileAccess access, DN_ErrSink *err)
|
|
{
|
|
DN_OSFile result = {};
|
|
if (path.size == 0 || 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_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_Str16 path16 = DN_OS_W32Str8ToStr16(&scratch.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_OSW32Error win_error = DN_OS_W32LastError(&scratch.arena);
|
|
result.error = true;
|
|
DN_ErrSinkAppendF(err, win_error.code, "Failed to open file at '%.*s': '%.*s'", DN_Str8PrintFmt(path), DN_Str8PrintFmt(win_error.msg));
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
result.handle = handle;
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_OSFileRead DN_OS_FileRead(DN_OSFile *file, void *buffer, DN_USize size, DN_ErrSink *err)
|
|
{
|
|
DN_OSFileRead result = {};
|
|
if (!file || !file->handle || file->error || !buffer || size <= 0)
|
|
return result;
|
|
|
|
DN_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
if (!DN_Check(size <= (unsigned long)-1)) {
|
|
DN_Str8x32 buffer_size_str8 = DN_ByteCountStr8x32(size);
|
|
DN_ErrSinkAppendF(
|
|
err,
|
|
1 /*error_code*/,
|
|
"Current implementation doesn't support reading >4GiB file (requested %.*s), implement Win32 overlapped IO",
|
|
DN_Str8PrintFmt(buffer_size_str8));
|
|
DN_TCScratchEnd(&scratch);
|
|
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_OSW32Error win_error = DN_OS_W32LastError(&scratch.arena);
|
|
DN_ErrSinkAppendF(err, win_error.code, "Failed to read data from file: (%u) %.*s", win_error.code, DN_Str8PrintFmt(win_error.msg));
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
if (bytes_read != size) {
|
|
DN_OSW32Error win_error = DN_OS_W32LastError(&scratch.arena);
|
|
DN_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_Str8PrintFmt(win_error.msg));
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
result.bytes_read = bytes_read;
|
|
result.success = true;
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_OS_FileWritePtr(DN_OSFile *file, void const *buffer, DN_USize size, DN_ErrSink *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_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_OSW32Error win_error = DN_OS_W32LastError(&scratch.arena);
|
|
DN_Str8x32 buffer_size_str8 = DN_ByteCountStr8x32(size);
|
|
DN_ErrSinkAppendF(err, win_error.code, "Failed to write buffer (%.*s) to file handle: %.*s", DN_Str8PrintFmt(buffer_size_str8), DN_Str8PrintFmt(win_error.msg));
|
|
DN_TCScratchEnd(&scratch);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_OS_FileFlush(DN_OSFile *file, DN_ErrSink *err)
|
|
{
|
|
if (!file || !file->handle || file->error)
|
|
return false;
|
|
|
|
BOOL result = FlushFileBuffers(DN_Cast(HANDLE) file->handle);
|
|
if (!result) {
|
|
DN_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_OSW32Error win_error = DN_OS_W32LastError(&scratch.arena);
|
|
DN_ErrSinkAppendF(err, win_error.code, "Failed to flush file buffer to disk: %.*s", DN_Str8PrintFmt(win_error.msg));
|
|
DN_TCScratchEnd(&scratch);
|
|
}
|
|
|
|
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 = {};
|
|
}
|
|
|
|
DN_API DN_OSPathInfo DN_OS_PathInfo(DN_Str8 path)
|
|
{
|
|
DN_OSPathInfo result = {};
|
|
if (path.size == 0)
|
|
return result;
|
|
|
|
DN_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_Str16 path16 = DN_OS_W32Str8ToStr16(&scratch.arena, path);
|
|
|
|
WIN32_FILE_ATTRIBUTE_DATA attrib_data = {};
|
|
if (!GetFileAttributesExW(path16.data, GetFileExInfoStandard, &attrib_data)) {
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
result.exists = true;
|
|
result.create_time_in_s = DN_OS_W32FileTimeToSeconds_(&attrib_data.ftCreationTime);
|
|
result.last_access_time_in_s = DN_OS_W32FileTimeToSeconds_(&attrib_data.ftLastAccessTime);
|
|
result.last_write_time_in_s = DN_OS_W32FileTimeToSeconds_(&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;
|
|
}
|
|
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_OS_PathDelete(DN_Str8 path)
|
|
{
|
|
bool result = false;
|
|
if (path.size == 0)
|
|
return result;
|
|
|
|
DN_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_Str16 path16 = DN_OS_W32Str8ToStr16(&scratch.arena, path);
|
|
if (path16.size) {
|
|
result = DeleteFileW(path16.data);
|
|
if (!result)
|
|
result = RemoveDirectoryW(path16.data);
|
|
}
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_OS_PathIsFile(DN_Str8 path)
|
|
{
|
|
bool result = false;
|
|
if (path.size == 0)
|
|
return result;
|
|
|
|
DN_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_Str16 path16 = DN_OS_W32Str8ToStr16(&scratch.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);
|
|
}
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_OS_PathIsDir(DN_Str8 path)
|
|
{
|
|
bool result = false;
|
|
if (path.size == 0)
|
|
return result;
|
|
|
|
DN_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_Str16 path16 = DN_OS_W32Str8ToStr16(&scratch.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);
|
|
}
|
|
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_OS_PathMakeDir(DN_Str8 path)
|
|
{
|
|
bool result = true;
|
|
DN_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_Str16 path16 = DN_OS_W32Str8ToStr16(&scratch.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.
|
|
DN_TCScratchEnd(&scratch);
|
|
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
|
|
}
|
|
}
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_OS_PathIterateDir(DN_Str8 path, DN_OSDirIterator *it)
|
|
{
|
|
if (path.size == 0 || !it || path.size <= 0)
|
|
return false;
|
|
|
|
DN_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_OSW32FolderIteratorW wide_it = {};
|
|
DN_Str16 path16 = {};
|
|
if (it->handle) {
|
|
wide_it.handle = it->handle;
|
|
} else {
|
|
bool needs_asterisks = DN_Str8EndsWith(path, DN_Str8Lit("\\")) ||
|
|
DN_Str8EndsWith(path, DN_Str8Lit("/"));
|
|
bool has_glob = DN_Str8EndsWith(path, DN_Str8Lit("\\*")) ||
|
|
DN_Str8EndsWith(path, DN_Str8Lit("/*"));
|
|
|
|
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(&scratch.arena, "%.*s*", DN_Str8PrintFmt(path));
|
|
else
|
|
adjusted_path = DN_OS_PathF(&scratch.arena, "%.*s/*", DN_Str8PrintFmt(path));
|
|
}
|
|
|
|
path16 = DN_OS_W32Str8ToStr16(&scratch.arena, adjusted_path);
|
|
if (path16.size <= 0) { // Conversion error
|
|
DN_TCScratchEnd(&scratch);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool result = DN_OS_W32DirWIterate(path16, &wide_it);
|
|
it->handle = wide_it.handle;
|
|
if (result) {
|
|
int size = DN_OS_W32Str16ToStr8Buffer(wide_it.file_name, it->buffer, DN_ArrayCountU(it->buffer));
|
|
it->file_name = DN_Str8FromPtr(it->buffer, size);
|
|
}
|
|
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
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,
|
|
DN_USize *stdout_size,
|
|
char *stderr_buffer,
|
|
DN_USize *stderr_size,
|
|
DN_U32 timeout_ms,
|
|
DN_ErrSink *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_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
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_OSW32Error win_error = DN_OS_W32LastError(&scratch.arena);
|
|
result.os_error_code = win_error.code;
|
|
DN_ErrSinkAppendF(err, result.os_error_code, "Executed command failed to terminate: %.*s", DN_Str8PrintFmt(win_error.msg));
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
} 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;
|
|
DN_USize 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);
|
|
if (success) {
|
|
if (stdout_size)
|
|
*stdout_size = bytes_read;
|
|
} else {
|
|
DN_ErrSinkAppendF(err, 1, "Failed to read bytes from stdout");
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
if (success) {
|
|
if (stderr_size)
|
|
*stderr_size = bytes_read;
|
|
} else {
|
|
DN_ErrSinkAppendF(err, 1, "Failed to read bytes from stderr");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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_OSW32Error win_error = DN_OS_W32LastError(&scratch.arena);
|
|
result.os_error_code = win_error.code;
|
|
DN_ErrSinkAppendF(err,
|
|
result.os_error_code,
|
|
"Failed to retrieve command exit code: %.*s",
|
|
DN_Str8PrintFmt(win_error.msg));
|
|
}
|
|
|
|
// NOTE: Cleanup
|
|
if (handle.stdout_write)
|
|
CloseHandle(handle.stdout_write);
|
|
if (handle.stderr_write)
|
|
CloseHandle(handle.stderr_write);
|
|
if (handle.stdout_read)
|
|
CloseHandle(handle.stdout_read);
|
|
if (handle.stderr_read)
|
|
CloseHandle(handle.stderr_read);
|
|
if (handle.process)
|
|
CloseHandle(handle.process);
|
|
}
|
|
|
|
result.stdout_text = DN_Str8FromPtr(stdout_buffer, stdout_size ? *stdout_size : 0);
|
|
result.stderr_text = DN_Str8FromPtr(stderr_buffer, stderr_size ? *stderr_size : 0);
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_OSExecResult DN_OS_ExecWait(DN_OSExecAsyncHandle handle, DN_Arena *arena, DN_ErrSink *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_TCScratch scratch = DN_TCScratchBegin(&arena, 1);
|
|
DN_Str8Builder stdout_builder = {};
|
|
DN_Str8Builder stderr_builder = {};
|
|
if (arena) {
|
|
stdout_builder = DN_Str8BuilderFromArena(&scratch.arena);
|
|
stderr_builder = DN_Str8BuilderFromArena(&scratch.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_ArenaNewArray(&scratch.arena, char, stdout_size, DN_ZMem_No);
|
|
char *stderr_buffer = DN_ArenaNewArray(&scratch.arena, char, stderr_size, DN_ZMem_No);
|
|
result = DN_OS_ExecPump(handle, stdout_buffer, &stdout_size, stderr_buffer, &stderr_size, wait_ms, err);
|
|
DN_Str8BuilderAppendCopy(&stdout_builder, result.stdout_text);
|
|
DN_Str8BuilderAppendCopy(&stderr_builder, result.stderr_text);
|
|
wait_ms = (result.stdout_text.size || result.stderr_text.size) ? 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_Str8BuilderBuild(&stdout_builder, arena);
|
|
result.stderr_text = DN_Str8BuilderBuild(&stderr_builder, arena);
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_OSExecAsyncHandle DN_OS_ExecAsync(DN_Str8Slice cmd_line, DN_OSExecArgs *args, DN_ErrSink *err)
|
|
{
|
|
// NOTE: Pre-amble
|
|
DN_OSExecAsyncHandle result = {};
|
|
if (cmd_line.count == 0)
|
|
return result;
|
|
|
|
DN_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_Str8 cmd_rendered = DN_Str8SliceRender(cmd_line, DN_Str8Lit(" "), &scratch.arena);
|
|
DN_Str16 cmd16 = DN_OS_W32Str8ToStr16(&scratch.arena, cmd_rendered);
|
|
DN_Str16 working_dir16 = DN_OS_W32Str8ToStr16(&scratch.arena, args->working_dir);
|
|
|
|
DN_Str8Builder env_builder = DN_Str8BuilderFromArena(&scratch.arena);
|
|
DN_Str8BuilderAppendArrayRef(&env_builder, args->environment.data, args->environment.count);
|
|
if (env_builder.string_size)
|
|
DN_Str8BuilderAppendRef(&env_builder, DN_Str8Lit("\0"));
|
|
|
|
DN_Str8 env_block8 = DN_Str8BuilderBuildDelimited(&env_builder, DN_Str8Lit("\0"), &scratch.arena);
|
|
DN_Str16 env_block16 = {};
|
|
if (env_block8.size)
|
|
env_block16 = DN_OS_W32Str8ToStr16(&scratch.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_BitIsSet(args->flags, DN_OSExecFlags_SaveStdout)) {
|
|
if (!CreatePipe(&stdout_read, &stdout_write, &save_std_security_attribs, /*nSize*/ 0)) {
|
|
DN_OSW32Error win_error = DN_OS_W32LastError(&scratch.arena);
|
|
result.os_error_code = win_error.code;
|
|
DN_ErrSinkAppendF(
|
|
err,
|
|
result.os_error_code,
|
|
"Failed to create stdout pipe to redirect the output of the command '%.*s': %.*s",
|
|
DN_Str8PrintFmt(cmd_rendered),
|
|
DN_Str8PrintFmt(win_error.msg));
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
if (!SetHandleInformation(stdout_read, HANDLE_FLAG_INHERIT, 0)) {
|
|
DN_OSW32Error win_error = DN_OS_W32LastError(&scratch.arena);
|
|
result.os_error_code = win_error.code;
|
|
DN_ErrSinkAppendF(err,
|
|
result.os_error_code,
|
|
"Failed to make stdout 'read' pipe non-inheritable when trying to "
|
|
"execute command '%.*s': %.*s",
|
|
DN_Str8PrintFmt(cmd_rendered),
|
|
DN_Str8PrintFmt(win_error.msg));
|
|
DN_TCScratchEnd(&scratch);
|
|
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_BitIsSet(args->flags, DN_OSExecFlags_SaveStderr)) {
|
|
if (DN_BitIsSet(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_OSW32Error win_error = DN_OS_W32LastError(&scratch.arena);
|
|
result.os_error_code = win_error.code;
|
|
DN_ErrSinkAppendF(
|
|
err,
|
|
result.os_error_code,
|
|
"Failed to create stderr pipe to redirect the output of the command '%.*s': %.*s",
|
|
DN_Str8PrintFmt(cmd_rendered),
|
|
DN_Str8PrintFmt(win_error.msg));
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
if (!SetHandleInformation(stderr_read, HANDLE_FLAG_INHERIT, 0)) {
|
|
DN_OSW32Error win_error = DN_OS_W32LastError(&scratch.arena);
|
|
result.os_error_code = win_error.code;
|
|
DN_ErrSinkAppendF(err,
|
|
result.os_error_code,
|
|
"Failed to make stderr 'read' pipe non-inheritable when trying to "
|
|
"execute command '%.*s': %.*s",
|
|
DN_Str8PrintFmt(cmd_rendered),
|
|
DN_Str8PrintFmt(win_error.msg));
|
|
DN_TCScratchEnd(&scratch);
|
|
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_OSW32Error win_error = DN_OS_W32LastError(&scratch.arena);
|
|
result.os_error_code = win_error.code;
|
|
DN_ErrSinkAppendF(err, result.os_error_code, "Failed to execute command '%.*s': %.*s", DN_Str8PrintFmt(cmd_rendered), DN_Str8PrintFmt(win_error.msg));
|
|
DN_TCScratchEnd(&scratch);
|
|
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_BitIsSet(args->flags, DN_OSExecFlags_SaveStderr) && DN_BitIsNotSet(args->flags, DN_OSExecFlags_MergeStderrToStdout)) {
|
|
result.stderr_read = stderr_read;
|
|
result.stderr_write = stderr_write;
|
|
}
|
|
result.exec_flags = args->flags;
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_OSW32Core *DN_OS_W32GetCore()
|
|
{
|
|
DN_Core *dn = DN_Get();
|
|
DN_Assert(dn && dn->os_init);
|
|
DN_OSW32Core *result = DN_Cast(DN_OSW32Core *)dn->os.platform_context;
|
|
return result;
|
|
}
|
|
|
|
static DN_OSW32SyncPrimitive *DN_OS_U64ToW32SyncPrimitive_(DN_U64 u64)
|
|
{
|
|
DN_OSW32SyncPrimitive *result = nullptr;
|
|
DN_Memcpy(&result, &u64, sizeof(u64));
|
|
return result;
|
|
}
|
|
|
|
static DN_U64 DN_OS_W32SyncPrimitiveToU64(DN_OSW32SyncPrimitive *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_OSW32SyncPrimitive *DN_OS_W32AllocSyncPrimitive_()
|
|
{
|
|
DN_OSW32Core *w32 = DN_OS_W32GetCore();
|
|
DN_OSW32SyncPrimitive *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 = &DN_Get()->os;
|
|
result = DN_ArenaNew(&os->arena, DN_OSW32SyncPrimitive, DN_ZMem_Yes);
|
|
}
|
|
}
|
|
LeaveCriticalSection(&w32->sync_primitive_free_list_mutex);
|
|
return result;
|
|
}
|
|
|
|
static void DN_OS_W32DeallocSyncPrimitive_(DN_OSW32SyncPrimitive *primitive)
|
|
{
|
|
if (primitive) {
|
|
DN_OSW32Core *w32 = DN_OS_W32GetCore();
|
|
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_OSW32SyncPrimitive *primitive = DN_OS_W32AllocSyncPrimitive_();
|
|
if (primitive) {
|
|
SECURITY_ATTRIBUTES security_attribs = {};
|
|
primitive->sem = CreateSemaphoreA(&security_attribs, initial_count, INT32_MAX, nullptr /*name*/);
|
|
if (primitive->sem)
|
|
result.handle = DN_OS_W32SyncPrimitiveToU64(primitive);
|
|
if (!primitive->sem)
|
|
DN_OS_W32DeallocSyncPrimitive_(primitive);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_OS_SemaphoreDeinit(DN_OSSemaphore *semaphore)
|
|
{
|
|
if (semaphore && semaphore->handle != 0) {
|
|
DN_OSW32SyncPrimitive *primitive = DN_OS_U64ToW32SyncPrimitive_(semaphore->handle);
|
|
CloseHandle(primitive->sem);
|
|
DN_OS_W32DeallocSyncPrimitive_(primitive);
|
|
*semaphore = {};
|
|
}
|
|
}
|
|
|
|
DN_API void DN_OS_SemaphoreIncrement(DN_OSSemaphore *semaphore, DN_U32 amount)
|
|
{
|
|
if (semaphore && semaphore->handle != 0) {
|
|
DN_OSW32SyncPrimitive *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_OSW32SyncPrimitive *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_OSBarrier
|
|
DN_API DN_OSBarrier DN_OS_BarrierInit(DN_U32 thread_count)
|
|
{
|
|
DN_OSBarrier result = {};
|
|
DN_OSW32SyncPrimitive *primitive = DN_OS_W32AllocSyncPrimitive_();
|
|
if (primitive) {
|
|
BOOL init_result = InitializeSynchronizationBarrier(&primitive->barrier, thread_count, /*lSpinCount=*/-1);
|
|
if (init_result) {
|
|
result.handle = DN_OS_W32SyncPrimitiveToU64(primitive);
|
|
} else {
|
|
DN_OS_W32DeallocSyncPrimitive_(primitive);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_OS_BarrierDeinit(DN_OSBarrier *barrier)
|
|
{
|
|
if (barrier && barrier->handle != 0) {
|
|
DN_OSW32SyncPrimitive *primitive = DN_OS_U64ToW32SyncPrimitive_(barrier->handle);
|
|
bool result = DeleteSynchronizationBarrier(&primitive->barrier);
|
|
DN_Assert(result);
|
|
DN_OS_W32DeallocSyncPrimitive_(primitive);
|
|
}
|
|
}
|
|
|
|
DN_API void DN_OS_BarrierWait(DN_OSBarrier *barrier)
|
|
{
|
|
if (barrier && barrier->handle != 0) {
|
|
DN_OSW32SyncPrimitive *primitive = DN_OS_U64ToW32SyncPrimitive_(barrier->handle);
|
|
EnterSynchronizationBarrier(&primitive->barrier, /*dwFlags=*/ 0);
|
|
}
|
|
}
|
|
|
|
// NOTE: DN_OSMutex
|
|
DN_API DN_OSMutex DN_OS_MutexInit()
|
|
{
|
|
DN_OSW32SyncPrimitive *primitive = DN_OS_W32AllocSyncPrimitive_();
|
|
if (primitive)
|
|
InitializeCriticalSection(&primitive->mutex);
|
|
DN_OSMutex result = {};
|
|
result.handle = DN_OS_W32SyncPrimitiveToU64(primitive);
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_OS_MutexDeinit(DN_OSMutex *mutex)
|
|
{
|
|
if (mutex && mutex->handle != 0) {
|
|
DN_OSW32SyncPrimitive *primitive = DN_OS_U64ToW32SyncPrimitive_(mutex->handle);
|
|
DeleteCriticalSection(&primitive->mutex);
|
|
DN_OS_W32DeallocSyncPrimitive_(primitive);
|
|
*mutex = {};
|
|
}
|
|
}
|
|
|
|
DN_API void DN_OS_MutexLock(DN_OSMutex *mutex)
|
|
{
|
|
if (mutex && mutex->handle != 0) {
|
|
DN_OSW32SyncPrimitive *primitive = DN_OS_U64ToW32SyncPrimitive_(mutex->handle);
|
|
EnterCriticalSection(&primitive->mutex);
|
|
}
|
|
}
|
|
|
|
DN_API void DN_OS_MutexUnlock(DN_OSMutex *mutex)
|
|
{
|
|
if (mutex && mutex->handle != 0) {
|
|
DN_OSW32SyncPrimitive *primitive = DN_OS_U64ToW32SyncPrimitive_(mutex->handle);
|
|
LeaveCriticalSection(&primitive->mutex);
|
|
}
|
|
}
|
|
|
|
// NOTE: DN_OSConditionVariable ////////////////////////////////////////////////////////////////////
|
|
DN_API DN_OSConditionVariable DN_OS_ConditionVariableInit()
|
|
{
|
|
DN_OSW32SyncPrimitive *primitive = DN_OS_W32AllocSyncPrimitive_();
|
|
if (primitive)
|
|
InitializeConditionVariable(&primitive->cv);
|
|
DN_OSConditionVariable result = {};
|
|
result.handle = DN_OS_W32SyncPrimitiveToU64(primitive);
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_OS_ConditionVariableDeinit(DN_OSConditionVariable *cv)
|
|
{
|
|
if (cv && cv->handle != 0) {
|
|
DN_OSW32SyncPrimitive *primitive = DN_OS_U64ToW32SyncPrimitive_(cv->handle);
|
|
DN_OS_W32DeallocSyncPrimitive_(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_OSW32SyncPrimitive *mutex_primitive = DN_OS_U64ToW32SyncPrimitive_(mutex->handle);
|
|
DN_OSW32SyncPrimitive *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_OSW32SyncPrimitive *primitive = DN_OS_U64ToW32SyncPrimitive_(cv->handle);
|
|
WakeConditionVariable(&primitive->cv);
|
|
}
|
|
}
|
|
|
|
DN_API void DN_OS_ConditionVariableBroadcast(DN_OSConditionVariable *cv)
|
|
{
|
|
if (cv && cv->handle != 0) {
|
|
DN_OSW32SyncPrimitive *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, DN_OSThreadLane *lane, 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*/);
|
|
if (lane) {
|
|
thread->is_lane_set = true;
|
|
thread->lane = *lane;
|
|
}
|
|
|
|
// 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 bool DN_OS_ThreadJoin(DN_OSThread *thread, DN_TCDeinitArenas deinit_arenas)
|
|
{
|
|
bool result = false;
|
|
if (thread && thread->handle) {
|
|
DWORD wait_result = WaitForSingleObject(thread->handle, INFINITE);
|
|
result = wait_result == WAIT_OBJECT_0;
|
|
CloseHandle(thread->handle);
|
|
thread->handle = INVALID_HANDLE_VALUE;
|
|
thread->thread_id = {};
|
|
DN_TCDeinit(&thread->context, deinit_arenas);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_U32 DN_OS_ThreadID()
|
|
{
|
|
unsigned long result = GetCurrentThreadId();
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_OS_W32ThreadSetName(DN_Str8 name)
|
|
{
|
|
// 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_OSW32Core *w32 = DN_OS_W32GetCore();
|
|
DN_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
if (w32->set_thread_description) {
|
|
DN_Str16 name16 = DN_OS_W32Str8ToStr16(&scratch.arena, name);
|
|
w32->set_thread_description(GetCurrentThread(), (WCHAR *)name16.data);
|
|
} else {
|
|
// NOTE: Fallback to throw-exception method to set thread name
|
|
#pragma pack(push, 8)
|
|
struct DN_OSW32ThreadNameInfo
|
|
{
|
|
DN_U32 dwType;
|
|
char *szName;
|
|
DN_U32 dwThreadID;
|
|
DN_U32 dwFlags;
|
|
};
|
|
#pragma pack(pop)
|
|
|
|
DN_Str8 copy = DN_Str8FromStr8Arena(name, &scratch.arena);
|
|
DN_OSW32ThreadNameInfo 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_TCScratchEnd(&scratch);
|
|
}
|
|
|
|
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_OSW32Error 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_ArenaAlloc(response->builder.arena, READ_BUFFER_SIZE, 1 /*align*/, DN_ZMem_No);
|
|
if (!WinHttpReadData(request, buffer, READ_BUFFER_SIZE, nullptr))
|
|
error = DN_OS_W32LastError(&response->tmp_arena);
|
|
} else {
|
|
error = DN_OS_W32LastError(&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_Str8FromPtr(DN_Cast(char *) lpvStatusInformation, bytes_read);
|
|
DN_Str8BuilderAppendRef(&response->builder, prev_buffer);
|
|
|
|
void *buffer = DN_ArenaAlloc(response->builder.arena, READ_BUFFER_SIZE, 1 /*align*/, DN_ZMem_No);
|
|
if (!WinHttpReadData(request, buffer, READ_BUFFER_SIZE, nullptr))
|
|
error = DN_OS_W32LastError(&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_OS_W32ErrorCodeToMsg(&response->tmp_arena, DN_Cast(DN_U32) async_result->dwError);
|
|
} else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE) {
|
|
if (!WinHttpReceiveResponse(request, 0))
|
|
error = DN_OS_W32LastError(&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_Str8BuilderBuild(&response->builder, response->arena);
|
|
|
|
if (read_complete || dwInternetStatus == WINHTTP_CALLBACK_STATUS_REQUEST_ERROR || error.code) {
|
|
DN_OS_SemaphoreIncrement(&response->on_complete_semaphore, 1);
|
|
DN_AtomicAddU32(&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 = DN_Str8BuilderFromArena(response->scratch_arena.mem ? &response->scratch_arena : &response->tmp_arena);
|
|
|
|
DN_TCScratch scratch_ = DN_TCScratchBegin(&arena, 1);
|
|
if (!response->scratch_arena.mem)
|
|
response->scratch_arena = scratch_.arena;
|
|
|
|
DN_OSW32Error 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_AtomicAddU32(&response->done, 1);
|
|
}
|
|
DN_TCScratchEnd(&scratch_);
|
|
};
|
|
|
|
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_OS_W32LastError(&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_OS_W32LastError(&response->tmp_arena);
|
|
return;
|
|
}
|
|
|
|
DN_Str16 host16 = DN_OS_W32Str8ToStr16(&response->scratch_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_OS_W32LastError(&response->tmp_arena);
|
|
return;
|
|
}
|
|
|
|
DN_Str16 method16 = DN_OS_W32Str8ToStr16(&response->scratch_arena, method);
|
|
DN_Str16 path16 = DN_OS_W32Str8ToStr16(&response->scratch_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_OS_W32LastError(&response->tmp_arena);
|
|
return;
|
|
}
|
|
|
|
DN_Str16 headers16 = DN_OS_W32Str8ToStr16(&response->scratch_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_OS_W32LastError(&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_MemListDeinit(response->tmp_arena.mem);
|
|
DN_OS_SemaphoreDeinit(&response->on_complete_semaphore);
|
|
|
|
*response = {};
|
|
}
|
|
|
|
// NOTE: DN_OS_W32
|
|
DN_API DN_Str16 DN_OS_W32ErrorCodeToMsg16Alloc(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 = {};
|
|
result.data = result16;
|
|
result.size = size;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_OSW32Error DN_OS_W32ErrorCodeToMsgAlloc(DN_U32 error_code)
|
|
{
|
|
DN_OSW32Error result = {};
|
|
result.code = error_code;
|
|
DN_Str16 error16 = DN_OS_W32ErrorCodeToMsg16Alloc(error_code);
|
|
if (error16.size)
|
|
result.msg = DN_OS_W32Str16ToStr8FromHeap(error16);
|
|
if (error16.data)
|
|
LocalFree(error16.data);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_OSW32Error DN_OS_W32ErrorCodeToMsg(DN_Arena *arena, DN_U32 error_code)
|
|
{
|
|
DN_OSW32Error result = {};
|
|
result.code = error_code;
|
|
if (arena) {
|
|
DN_Str16 error16 = DN_OS_W32ErrorCodeToMsg16Alloc(error_code);
|
|
if (error16.size)
|
|
result.msg = DN_OS_W32Str16ToStr8(arena, error16);
|
|
if (error16.data)
|
|
LocalFree(error16.data);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_OSW32Error DN_OS_W32LastError(DN_Arena *arena)
|
|
{
|
|
DN_OSW32Error result = DN_OS_W32ErrorCodeToMsg(arena, GetLastError());
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_OSW32Error DN_OS_W32LastErrorAlloc()
|
|
{
|
|
DN_OSW32Error result = DN_OS_W32ErrorCodeToMsgAlloc(GetLastError());
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_OS_W32MakeProcessDPIAware()
|
|
{
|
|
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();
|
|
}
|
|
|
|
DN_API DN_Str16 DN_OS_W32Str8ToStr16(DN_Arena *arena, DN_Str8 src)
|
|
{
|
|
DN_Str16 result = {};
|
|
if (!arena || src.size == 0)
|
|
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_ArenaNewArray(arena, wchar_t, required_size + 1, DN_ZMem_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_OS_W32Str8ToStr16Buffer(DN_Str8 src, wchar_t *dest, int dest_size)
|
|
{
|
|
int result = 0;
|
|
if (src.size == 0)
|
|
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;
|
|
}
|
|
|
|
DN_API int DN_OS_W32Str16ToStr8Buffer(DN_Str16 src, char *dest, int dest_size)
|
|
{
|
|
int result = 0;
|
|
if (src.size == 0)
|
|
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_OS_W32Str16ToStr8(DN_Arena *arena, DN_Str16 src)
|
|
{
|
|
DN_Str8 result = {};
|
|
if (!arena || src.size == 0)
|
|
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_Arena temp = DN_ArenaTempBeginFromArena(arena);
|
|
DN_Str8 buffer = DN_Str8AllocArena(required_size, DN_ZMem_No, &temp);
|
|
if (buffer.size) {
|
|
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;
|
|
}
|
|
}
|
|
DN_ArenaTempEnd(&temp, result.size == DN_Cast(DN_USize)required_size ? DN_ArenaReset_No : DN_ArenaReset_Yes);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_OS_W32Str16ToStr8FromHeap(DN_Str16 src)
|
|
{
|
|
DN_Str8 result = {};
|
|
if (src.size == 0)
|
|
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_Str8FromHeap(required_size, DN_ZMem_No);
|
|
if (buffer.size == 0)
|
|
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_OS_W32EXEPathW(DN_Arena *arena)
|
|
{
|
|
DN_TCScratch scratch = DN_TCScratchBegin(&arena, 1);
|
|
DN_Str16 result = {};
|
|
DN_USize module_size = 0;
|
|
wchar_t *module_path = nullptr;
|
|
do {
|
|
module_size += 256;
|
|
module_path = DN_ArenaNewArray(&scratch.arena, wchar_t, module_size, DN_ZMem_No);
|
|
if (!module_path) {
|
|
DN_TCScratchEnd(&scratch);
|
|
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_ArenaNewArray(arena, wchar_t, module_size + 1, DN_ZMem_No);
|
|
result.size = module_size;
|
|
DN_Memcpy(result.data, module_path, sizeof(wchar_t) * result.size);
|
|
result.data[result.size] = 0;
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str16 DN_OS_W32EXEDirW(DN_Arena *arena)
|
|
{
|
|
// TODO(doyle): Implement a DN_Str16_BinarySearchReverse
|
|
DN_TCScratch scratch = DN_TCScratchBegin(&arena, 1);
|
|
DN_Str16 result = {};
|
|
DN_USize module_size = 0;
|
|
wchar_t *module_path = nullptr;
|
|
do {
|
|
module_size += 256;
|
|
module_path = DN_ArenaNewArray(&scratch.arena, wchar_t, module_size, DN_ZMem_No);
|
|
if (!module_path) {
|
|
DN_TCScratchEnd(&scratch);
|
|
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_ArenaNewArray(arena, wchar_t, index_of_last_slash + 1, DN_ZMem_No);
|
|
result.size = index_of_last_slash;
|
|
DN_Memcpy(result.data, module_path, sizeof(wchar_t) * result.size);
|
|
result.data[result.size] = 0;
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_OS_W32WorkingDir(DN_Arena *arena, DN_Str8 suffix)
|
|
{
|
|
DN_TCScratch scratch = DN_TCScratchBegin(&arena, 1);
|
|
DN_Str16 suffix16 = DN_OS_W32Str8ToStr16(&scratch.arena, suffix);
|
|
DN_Str16 dir16 = DN_OS_W32WorkingDirW(&scratch.arena, suffix16);
|
|
DN_Str8 result = DN_OS_W32Str16ToStr8(arena, dir16);
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str16 DN_OS_W32WorkingDirW(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_TCScratch scratch = DN_TCScratchBegin(&arena, 1);
|
|
unsigned long required_size = GetCurrentDirectoryW(0, nullptr);
|
|
unsigned long desired_size = required_size + DN_Cast(unsigned long) suffix.size;
|
|
|
|
wchar_t *scratch_w_path = DN_ArenaNewArray(&scratch.arena, wchar_t, desired_size, DN_ZMem_No);
|
|
if (!scratch_w_path) {
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
unsigned long bytes_written_wo_null_terminator = GetCurrentDirectoryW(desired_size, scratch_w_path);
|
|
if ((bytes_written_wo_null_terminator + 1) != required_size) {
|
|
// TODO(dn): Error
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
wchar_t *w_path = DN_ArenaNewArray(arena, wchar_t, desired_size, DN_ZMem_No);
|
|
if (!w_path) {
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
if (suffix.size) {
|
|
DN_Memcpy(w_path, scratch_w_path, sizeof(*scratch_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)};
|
|
DN_TCScratchEnd(&scratch);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_OS_W32DirWIterate(DN_Str16 path, DN_OSW32FolderIteratorW *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_CStr16Size(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
|
|
#endif
|
|
|
|
DN_API void DN_Init(DN_Core *dn, DN_InitFlags flags, DN_InitArgs *args)
|
|
{
|
|
DN_Set(dn);
|
|
dn->init_flags = flags;
|
|
|
|
if (DN_BitIsSet(flags, DN_InitFlags_OS)) {
|
|
#if DN_H_WITH_OS
|
|
DN_OSCore *os = &dn->os;
|
|
dn->os_init = true;
|
|
DN_OS_SetLogPrintFuncToOS();
|
|
|
|
// NOTE: Query OS information
|
|
{
|
|
#if defined(DN_PLATFORM_WIN32)
|
|
SYSTEM_INFO system_info = {};
|
|
GetSystemInfo(&system_info);
|
|
os->logical_processor_count = system_info.dwNumberOfProcessors;
|
|
os->page_size = system_info.dwPageSize;
|
|
os->alloc_granularity = system_info.dwAllocationGranularity;
|
|
#else
|
|
#if defined(DN_PLATFORM_EMSCRIPTEN)
|
|
os->logical_processor_count = 1;
|
|
#else
|
|
os->logical_processor_count = get_nprocs();
|
|
#endif
|
|
os->page_size = getpagesize();
|
|
os->alloc_granularity = os->page_size;
|
|
#endif
|
|
}
|
|
|
|
{
|
|
os->mem = DN_MemListFromMemFuncs(DN_Megabytes(1), DN_Kilobytes(4), DN_MemFlags_NoAllocTrack, DN_MemFuncsDefault());
|
|
os->arena = DN_ArenaFromMemList(&os->mem);
|
|
|
|
#if defined(DN_PLATFORM_WIN32)
|
|
os->platform_context = DN_ArenaNew(&os->arena, DN_OSW32Core, DN_ZMem_Yes);
|
|
#elif defined(DN_PLATFORM_POSIX) || defined(DN_PLATFORM_EMSCRIPTEN)
|
|
os->platform_context = DN_ArenaNew(&os->arena, DN_OSPosixCore, DN_ZMem_Yes);
|
|
#endif
|
|
|
|
#if defined(DN_PLATFORM_WIN32)
|
|
DN_OSW32Core *w32 = DN_Cast(DN_OSW32Core *) os->platform_context;
|
|
InitializeCriticalSection(&w32->sync_primitive_free_list_mutex);
|
|
|
|
QueryPerformanceFrequency(&w32->qpc_frequency);
|
|
HMODULE module = LoadLibraryA("kernel32.dll");
|
|
if (module) {
|
|
w32->set_thread_description = DN_Cast(DN_OSW32SetThreadDescriptionFunc *) GetProcAddress(module, "SetThreadDescription");
|
|
FreeLibrary(module);
|
|
}
|
|
|
|
// NOTE: win32 bcrypt
|
|
wchar_t const BCRYPT_ALGORITHM[] = L"RNG";
|
|
long /*NTSTATUS*/ init_status = BCryptOpenAlgorithmProvider(&w32->bcrypt_rng_handle, BCRYPT_ALGORITHM, nullptr /*implementation*/, 0 /*flags*/);
|
|
if (w32->bcrypt_rng_handle && init_status == 0)
|
|
w32->bcrypt_init_success = true;
|
|
else
|
|
DN_LogErrorF("Failed to initialise Windows secure random number generator, error: %d", init_status);
|
|
#else
|
|
DN_OS_PosixInit(DN_Cast(DN_OSPosixCore *)os->platform_context);
|
|
#endif
|
|
}
|
|
|
|
os->cpu_report = DN_CPUGetReport();
|
|
|
|
#define DN_CPU_FEAT_XENTRY(label) g_dn_cpu_feature_decl[DN_CPUFeature_##label] = {DN_CPUFeature_##label, DN_Str8Lit(#label)};
|
|
DN_CPU_FEAT_XMACRO
|
|
#undef DN_CPU_FEAT_XENTRY
|
|
DN_Assert(g_dn_);
|
|
#endif
|
|
}
|
|
|
|
if (DN_BitIsSet(flags, DN_InitFlags_LeakTracker)) {
|
|
DN_Assert(dn->os_init);
|
|
#if DN_H_WITH_OS
|
|
// NOTE: Setup the allocation table with allocation tracking turned off on
|
|
// the arena we're using to initialise the table.
|
|
dn->leak.alloc_table_mem = DN_MemListFromMemFuncs(DN_Megabytes(1), DN_Kilobytes(512), DN_MemFlags_NoAllocTrack | DN_MemFlags_AllocCanLeak, DN_MemFuncsDefault());
|
|
dn->leak.alloc_table_arena = DN_ArenaFromMemList(&dn->leak.alloc_table_mem);
|
|
dn->leak.alloc_table = DN_DSMapInit<DN_LeakAlloc>(&dn->leak.alloc_table_arena, 4096, DN_DSMapFlags_Nil);
|
|
#endif
|
|
}
|
|
|
|
if (DN_BitIsSet(flags, DN_InitFlags_ThreadContext)) {
|
|
DN_Assert(dn->os_init);
|
|
#if DN_H_WITH_OS
|
|
DN_TCInitArgs *tc_init_args = args ? &args->thread_context_init_args : nullptr;
|
|
DN_TCInitFromMemFuncs(&dn->main_tc, DN_OS_ThreadID(), tc_init_args, DN_MemFuncsDefault());
|
|
DN_TCEquip(&dn->main_tc);
|
|
#endif
|
|
}
|
|
|
|
// NOTE: Print out init features
|
|
char buf[4096];
|
|
DN_USize buf_size = 0;
|
|
if (DN_BitIsSet(flags, DN_InitFlags_LogLibFeatures)) {
|
|
DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), "DN initialised:\n");
|
|
#if DN_H_WITH_OS
|
|
DN_F32 page_size_kib = dn->os.page_size / 1024.0f;
|
|
DN_F32 alloc_granularity_kib = dn->os.alloc_granularity / 1024.0f;
|
|
DN_FmtAppendTruncate(buf,
|
|
&buf_size,
|
|
sizeof(buf),
|
|
DN_Str8Lit("..."),
|
|
" OS Page/Granularity/Cores: %.0fKiB/%.0fKiB/%u\n",
|
|
page_size_kib,
|
|
alloc_granularity_kib,
|
|
dn->os.logical_processor_count);
|
|
#endif
|
|
|
|
DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), " Thread Context: ");
|
|
if (DN_BitIsSet(flags, DN_InitFlags_ThreadContext)) {
|
|
DN_Arena *arena = dn->main_tc.main_arena;
|
|
DN_Str8 mem_funcs = DN_Str8Lit("");
|
|
switch (arena->mem->funcs.type) {
|
|
case DN_MemFuncsType_Nil: break;
|
|
case DN_MemFuncsType_Heap: mem_funcs = DN_Str8Lit("Heap"); break;
|
|
case DN_MemFuncsType_Virtual: mem_funcs = DN_Str8Lit("Virtual"); break;
|
|
}
|
|
DN_Str8x32 main_commit = DN_ByteCountStr8x32(dn->main_tc.main_arena->mem->curr->commit);
|
|
DN_Str8x32 main_reserve = DN_ByteCountStr8x32(dn->main_tc.main_arena->mem->curr->reserve);
|
|
DN_Str8x32 temp_commit = DN_ByteCountStr8x32(dn->main_tc.temp_a_arena->mem->curr->commit);
|
|
DN_Str8x32 temp_reserve = DN_ByteCountStr8x32(dn->main_tc.temp_a_arena->mem->curr->reserve);
|
|
DN_Str8x32 err_commit = DN_ByteCountStr8x32(dn->main_tc.err_sink.arena->mem->curr->commit);
|
|
DN_Str8x32 err_reserve = DN_ByteCountStr8x32(dn->main_tc.err_sink.arena->mem->curr->reserve);
|
|
DN_FmtAppendTruncate(buf,
|
|
&buf_size,
|
|
sizeof(buf),
|
|
DN_Str8Lit("..."),
|
|
"M %.*s/%.*s S(x2) %.*s/%.*s E %.*s/%.*s (%.*s)\n",
|
|
DN_Str8PrintFmt(main_commit),
|
|
DN_Str8PrintFmt(main_reserve),
|
|
DN_Str8PrintFmt(temp_commit),
|
|
DN_Str8PrintFmt(temp_reserve),
|
|
DN_Str8PrintFmt(err_commit),
|
|
DN_Str8PrintFmt(err_reserve),
|
|
DN_Str8PrintFmt(mem_funcs));
|
|
} else {
|
|
DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), "N/A\n");
|
|
}
|
|
|
|
#if DN_HAS_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__)
|
|
if (DN_ASAN_POISON) {
|
|
DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), " ASAN manual poisoning%s\n", DN_ASAN_VET_POISON ? " (+vet sanity checks)" : "");
|
|
DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), " ASAN poison guard size: %u\n", DN_ASAN_POISON_GUARD_SIZE);
|
|
}
|
|
#endif
|
|
|
|
#if defined(DN_LEAK_TRACKING)
|
|
DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), " Allocation leak tracing\n");
|
|
#endif
|
|
|
|
#if defined(DN_PLATFORM_EMSCRIPTEN) || defined(DN_PLATFORM_POSIX)
|
|
DN_OSPosixCore *posix = DN_Cast(DN_OSPosixCore *)g_dn_->os.platform_context;
|
|
DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), " Clock GetTime: %S\n", posix->clock_monotonic_raw ? DN_Str8Lit("CLOCK_MONOTONIC_RAW") : DN_Str8Lit("CLOCK_MONOTONIC"));
|
|
#endif
|
|
|
|
// TODO(doyle): Add stacktrace feature log
|
|
}
|
|
|
|
if (DN_BitIsSet(flags, DN_InitFlags_LogCPUFeatures)) {
|
|
DN_Assert(dn->os_init);
|
|
#if DN_H_WITH_OS
|
|
DN_CPUReport const *report = &dn->os.cpu_report;
|
|
DN_Str8 brand = DN_Str8TrimWhitespaceAround(DN_Str8FromPtr(report->brand, sizeof(report->brand) - 1));
|
|
DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), " CPU '%.*s' from '%s' detected:\n", DN_Str8PrintFmt(brand), report->vendor);
|
|
|
|
DN_USize longest_feature_name = 0;
|
|
for (DN_ForIndexU(feature_index, DN_CPUFeature_Count)) {
|
|
DN_CPUFeatureDecl feature_decl = g_dn_cpu_feature_decl[feature_index];
|
|
longest_feature_name = DN_Max(longest_feature_name, feature_decl.label.size);
|
|
}
|
|
|
|
for (DN_ForIndexU(feature_index, DN_CPUFeature_Count)) {
|
|
DN_CPUFeatureDecl feature_decl = g_dn_cpu_feature_decl[feature_index];
|
|
bool has_feature = DN_CPUHasFeature(report, feature_decl.value);
|
|
DN_FmtAppendTruncate(buf,
|
|
&buf_size,
|
|
sizeof(buf),
|
|
DN_Str8Lit("..."),
|
|
" %.*s:%*s%s\n",
|
|
DN_Str8PrintFmt(feature_decl.label),
|
|
DN_Cast(int)(longest_feature_name - feature_decl.label.size),
|
|
"",
|
|
has_feature ? "available" : "not available");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (buf_size)
|
|
DN_LogDebugF("%.*s", DN_Cast(int)buf_size, buf);
|
|
}
|
|
|
|
DN_API void DN_Set(DN_Core *dn)
|
|
{
|
|
g_dn_ = dn;
|
|
}
|
|
|
|
DN_API DN_Core *DN_Get()
|
|
{
|
|
DN_Core *result = g_dn_;
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_BeginFrame()
|
|
{
|
|
#if DN_H_WITH_OS
|
|
DN_AtomicSetValue64(&g_dn_->os.mem_allocs_frame, 0);
|
|
#endif
|
|
}
|
|
|
|
#if DN_H_WITH_HELPERS
|
|
// DN: Single header generator commented out => #include "Extra/dn_helpers.cpp"
|
|
#define DN_HELPERS_CPP
|
|
|
|
// DN: Single header generator commented out => #if defined(_CLANGD)
|
|
// #include "dn_helpers.h"
|
|
// #endif
|
|
|
|
DN_API DN_JSONBuilder DN_JSONBuilder_Init(DN_Arena *arena, int spaces_per_indent)
|
|
{
|
|
DN_JSONBuilder result = {};
|
|
result.spaces_per_indent = spaces_per_indent;
|
|
result.string_builder.arena = arena;
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_Str8 DN_JSONBuilder_Build(DN_JSONBuilder const *builder, DN_Arena *arena)
|
|
{
|
|
DN_Str8 result = DN_Str8BuilderBuild(&builder->string_builder, arena);
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_JSONBuilder_KeyValue(DN_JSONBuilder *builder, DN_Str8 key, DN_Str8 value)
|
|
{
|
|
if (key.size == 0 && value.size == 0)
|
|
return;
|
|
|
|
DN_JSONBuilderItem item = DN_JSONBuilderItem_KeyValue;
|
|
if (value.size >= 1) {
|
|
if (value.data[0] == '{' || value.data[0] == '[')
|
|
item = DN_JSONBuilderItem_OpenContainer;
|
|
else if (value.data[0] == '}' || value.data[0] == ']')
|
|
item = DN_JSONBuilderItem_CloseContainer;
|
|
}
|
|
|
|
bool adding_to_container_with_items =
|
|
item != DN_JSONBuilderItem_CloseContainer && (builder->last_item == DN_JSONBuilderItem_KeyValue ||
|
|
builder->last_item == DN_JSONBuilderItem_CloseContainer);
|
|
|
|
uint8_t prefix_size = 0;
|
|
char prefix[2] = {0};
|
|
if (adding_to_container_with_items)
|
|
prefix[prefix_size++] = ',';
|
|
|
|
if (builder->last_item != DN_JSONBuilderItem_Empty)
|
|
prefix[prefix_size++] = '\n';
|
|
|
|
if (item == DN_JSONBuilderItem_CloseContainer)
|
|
builder->indent_level--;
|
|
|
|
int spaces_per_indent = builder->spaces_per_indent ? builder->spaces_per_indent : 2;
|
|
int spaces = builder->indent_level * spaces_per_indent;
|
|
|
|
if (key.size)
|
|
DN_Str8BuilderAppendF(&builder->string_builder,
|
|
"%.*s%*c\"%.*s\": %.*s",
|
|
prefix_size,
|
|
prefix,
|
|
spaces,
|
|
' ',
|
|
DN_Str8PrintFmt(key),
|
|
DN_Str8PrintFmt(value));
|
|
else if (spaces == 0)
|
|
DN_Str8BuilderAppendF(&builder->string_builder, "%.*s%.*s", prefix_size, prefix, DN_Str8PrintFmt(value));
|
|
else
|
|
DN_Str8BuilderAppendF(&builder->string_builder, "%.*s%*c%.*s", prefix_size, prefix, spaces, ' ', DN_Str8PrintFmt(value));
|
|
|
|
if (item == DN_JSONBuilderItem_OpenContainer)
|
|
builder->indent_level++;
|
|
|
|
builder->last_item = item;
|
|
}
|
|
|
|
DN_API void DN_JSONBuilder_KeyValueFV(DN_JSONBuilder *builder, DN_Str8 key, char const *value_fmt, va_list args)
|
|
{
|
|
DN_TCScratch scratch = DN_TCScratchBegin(&builder->string_builder.arena, 1);
|
|
DN_Str8 value = DN_Str8FromFmtVArena(&scratch.arena, value_fmt, args);
|
|
DN_JSONBuilder_KeyValue(builder, key, value);
|
|
DN_TCScratchEnd(&scratch);
|
|
}
|
|
|
|
DN_API void DN_JSONBuilder_KeyValueF(DN_JSONBuilder *builder, DN_Str8 key, char const *value_fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, value_fmt);
|
|
DN_JSONBuilder_KeyValueFV(builder, key, value_fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
DN_API void DN_JSONBuilder_ObjectBeginNamed(DN_JSONBuilder *builder, DN_Str8 name)
|
|
{
|
|
DN_JSONBuilder_KeyValue(builder, name, DN_Str8Lit("{"));
|
|
}
|
|
|
|
DN_API void DN_JSONBuilder_ObjectEnd(DN_JSONBuilder *builder)
|
|
{
|
|
DN_JSONBuilder_KeyValue(builder, DN_Str8Lit(""), DN_Str8Lit("}"));
|
|
}
|
|
|
|
DN_API void DN_JSONBuilder_ArrayBeginNamed(DN_JSONBuilder *builder, DN_Str8 name)
|
|
{
|
|
DN_JSONBuilder_KeyValue(builder, name, DN_Str8Lit("["));
|
|
}
|
|
|
|
DN_API void DN_JSONBuilder_ArrayEnd(DN_JSONBuilder *builder)
|
|
{
|
|
DN_JSONBuilder_KeyValue(builder, DN_Str8Lit(""), DN_Str8Lit("]"));
|
|
}
|
|
|
|
DN_API void DN_JSONBuilder_Str8Named(DN_JSONBuilder *builder, DN_Str8 key, DN_Str8 value)
|
|
{
|
|
DN_JSONBuilder_KeyValueF(builder, key, "\"%.*s\"", value.size, value.data);
|
|
}
|
|
|
|
DN_API void DN_JSONBuilder_LiteralNamed(DN_JSONBuilder *builder, DN_Str8 key, DN_Str8 value)
|
|
{
|
|
DN_JSONBuilder_KeyValueF(builder, key, "%.*s", value.size, value.data);
|
|
}
|
|
|
|
DN_API void DN_JSONBuilder_U64Named(DN_JSONBuilder *builder, DN_Str8 key, uint64_t value)
|
|
{
|
|
DN_JSONBuilder_KeyValueF(builder, key, "%I64u", value);
|
|
}
|
|
|
|
DN_API void DN_JSONBuilder_I64Named(DN_JSONBuilder *builder, DN_Str8 key, int64_t value)
|
|
{
|
|
DN_JSONBuilder_KeyValueF(builder, key, "%I64d", value);
|
|
}
|
|
|
|
DN_API void DN_JSONBuilder_F64Named(DN_JSONBuilder *builder, DN_Str8 key, double value, int decimal_places)
|
|
{
|
|
if (!builder)
|
|
return;
|
|
|
|
if (decimal_places >= 16)
|
|
decimal_places = 16;
|
|
|
|
// NOTE: Generate the format string for the float, depending on how many
|
|
// decimals places it wants.
|
|
char float_fmt[16];
|
|
if (decimal_places > 0) {
|
|
// NOTE: Emit the format string "%.<decimal_places>f" i.e. %.1f
|
|
DN_SNPrintF(float_fmt, sizeof(float_fmt), "%%.%df", decimal_places);
|
|
} else {
|
|
// NOTE: Emit the format string "%f"
|
|
DN_SNPrintF(float_fmt, sizeof(float_fmt), "%%f");
|
|
}
|
|
DN_JSONBuilder_KeyValueF(builder, key, float_fmt, value);
|
|
}
|
|
|
|
DN_API void DN_JSONBuilder_BoolNamed(DN_JSONBuilder *builder, DN_Str8 key, bool value)
|
|
{
|
|
DN_Str8 value_string = value ? DN_Str8Lit("true") : DN_Str8Lit("false");
|
|
DN_JSONBuilder_KeyValueF(builder, key, "%.*s", value_string.size, value_string.data);
|
|
}
|
|
#endif
|
|
|
|
#if DN_H_WITH_ASYNC
|
|
// DN: Single header generator commented out => #include "Extra/dn_async.cpp"
|
|
#define DN_ASYNC_CPP
|
|
|
|
// DN: Single header generator commented out => #if defined(_CLANGD)
|
|
// #define DN_H_WITH_OS 1
|
|
// #include "../dn.h"
|
|
// #include "dn_async.h"
|
|
// #endif
|
|
|
|
static DN_I32 DN_ASYNC_ThreadEntryPoint_(DN_OSThread *thread)
|
|
{
|
|
DN_OS_ThreadSetNameFmt("%.*s", DN_Str8PrintFmt(thread->name));
|
|
DN_ASYNCCore *async = DN_Cast(DN_ASYNCCore *) thread->user_context;
|
|
DN_Ring *ring = &async->ring;
|
|
for (;;) {
|
|
DN_OS_SemaphoreWait(&async->worker_sem, UINT32_MAX);
|
|
if (async->join_threads)
|
|
break;
|
|
|
|
DN_ASYNCTask task = {};
|
|
for (DN_OS_MutexScope(&async->ring_mutex)) {
|
|
if (DN_RingHasData(ring, sizeof(task)))
|
|
DN_RingRead(ring, &task, sizeof(task));
|
|
}
|
|
|
|
if (task.work.func) {
|
|
DN_OS_ConditionVariableBroadcast(&async->ring_write_cv); // Resume any blocked ring write(s)
|
|
|
|
DN_ASYNCWorkArgs args = {};
|
|
args.input = task.work.input;
|
|
args.thread = thread;
|
|
|
|
DN_AtomicAddU32(&async->busy_threads, 1);
|
|
task.work.func(args);
|
|
DN_AtomicSubU32(&async->busy_threads, 1);
|
|
|
|
if (task.completion_sem.handle != 0)
|
|
DN_OS_SemaphoreIncrement(&task.completion_sem, 1);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
DN_API void DN_ASYNC_Init(DN_ASYNCCore *async, char *base, DN_USize base_size, DN_OSThread *threads, DN_U32 threads_size)
|
|
{
|
|
DN_Assert(async);
|
|
async->ring.size = base_size;
|
|
async->ring.base = base;
|
|
async->ring_mutex = DN_OS_MutexInit();
|
|
async->ring_write_cv = DN_OS_ConditionVariableInit();
|
|
async->worker_sem = DN_OS_SemaphoreInit(0);
|
|
async->thread_count = threads_size;
|
|
async->threads = threads;
|
|
for (DN_ForIndexU(index, async->thread_count)) {
|
|
DN_OSThread *thread = async->threads + index;
|
|
DN_OS_ThreadInit(thread, DN_ASYNC_ThreadEntryPoint_, nullptr, async);
|
|
}
|
|
}
|
|
|
|
DN_API void DN_ASYNC_Deinit(DN_ASYNCCore *async)
|
|
{
|
|
DN_Assert(async);
|
|
DN_AtomicSetValue32(&async->join_threads, true);
|
|
DN_OS_SemaphoreIncrement(&async->worker_sem, async->thread_count);
|
|
for (DN_ForItSize(it, DN_OSThread, async->threads, async->thread_count))
|
|
DN_OS_ThreadJoin(it.data, DN_TCDeinitArenas_Yes);
|
|
}
|
|
|
|
static bool DN_ASYNC_QueueTask_(DN_ASYNCCore *async, DN_ASYNCTask const *task, DN_U64 wait_time_ms) {
|
|
DN_U64 end_time_ms = DN_OS_DateUnixTimeMs() + wait_time_ms;
|
|
bool result = false;
|
|
for (DN_OS_MutexScope(&async->ring_mutex)) {
|
|
for (;;) {
|
|
if (DN_RingHasSpace(&async->ring, sizeof(*task))) {
|
|
DN_RingWriteStruct(&async->ring, task);
|
|
result = true;
|
|
break;
|
|
}
|
|
DN_OS_ConditionVariableWaitUntil(&async->ring_write_cv, &async->ring_mutex, end_time_ms);
|
|
if (DN_OS_DateUnixTimeMs() >= end_time_ms)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (result)
|
|
DN_OS_SemaphoreIncrement(&async->worker_sem, 1); // Flag that a job is available
|
|
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_ASYNC_QueueWork(DN_ASYNCCore *async, DN_ASYNCWorkFunc *func, void *input, DN_U64 wait_time_ms)
|
|
{
|
|
DN_ASYNCTask task = {};
|
|
task.work.func = func;
|
|
task.work.input = input;
|
|
bool result = DN_ASYNC_QueueTask_(async, &task, wait_time_ms);
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_ASYNCTask DN_ASYNC_QueueTask(DN_ASYNCCore *async, DN_ASYNCWorkFunc *func, void *input, DN_U64 wait_time_ms)
|
|
{
|
|
DN_ASYNCTask result = {};
|
|
result.work.func = func;
|
|
result.work.input = input;
|
|
result.completion_sem = DN_OS_SemaphoreInit(0);
|
|
result.queued = DN_ASYNC_QueueTask_(async, &result, wait_time_ms);
|
|
if (!result.queued)
|
|
DN_OS_SemaphoreDeinit(&result.completion_sem);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_ASYNC_WaitTask(DN_ASYNCTask *task, DN_U32 timeout_ms)
|
|
{
|
|
bool result = true;
|
|
if (!task->queued)
|
|
return result;
|
|
|
|
DN_OSSemaphoreWaitResult wait = DN_OS_SemaphoreWait(&task->completion_sem, timeout_ms);
|
|
result = wait == DN_OSSemaphoreWaitResult_Success;
|
|
if (result)
|
|
DN_OS_SemaphoreDeinit(&task->completion_sem);
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
|
|
#if DN_H_WITH_NET
|
|
// DN: Single header generator commented out => #include "Extra/dn_net.cpp"
|
|
#define DN_NET_CURL_CPP
|
|
|
|
// DN: Single header generator commented out => #if defined(_CLANGD)
|
|
// #define DN_H_WITH_OS 1
|
|
// #include "../dn.h"
|
|
// #include "dn_net.h"
|
|
// #endif
|
|
|
|
DN_Str8 DN_NET_Str8FromResponseState(DN_NETResponseState state)
|
|
{
|
|
DN_Str8 result = {};
|
|
switch (state) {
|
|
case DN_NETResponseState_Nil: result = DN_Str8Lit("Nil"); break;
|
|
case DN_NETResponseState_Error: result = DN_Str8Lit("Error"); break;
|
|
case DN_NETResponseState_HTTP: result = DN_Str8Lit("HTTP"); break;
|
|
case DN_NETResponseState_WSOpen: result = DN_Str8Lit("WS Open"); break;
|
|
case DN_NETResponseState_WSText: result = DN_Str8Lit("WS Text"); break;
|
|
case DN_NETResponseState_WSBinary: result = DN_Str8Lit("WS Binary"); break;
|
|
case DN_NETResponseState_WSClose: result = DN_Str8Lit("WS Close"); break;
|
|
case DN_NETResponseState_WSPing: result = DN_Str8Lit("WS Ping"); break;
|
|
case DN_NETResponseState_WSPong: result = DN_Str8Lit("WS Pong"); break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_NETRequest *DN_NET_RequestFromHandle(DN_NETRequestHandle handle)
|
|
{
|
|
DN_NETRequest *ptr = DN_Cast(DN_NETRequest *) handle.handle;
|
|
DN_NETRequest *result = nullptr;
|
|
if (ptr && ptr->gen == handle.gen)
|
|
result = ptr;
|
|
return result;
|
|
}
|
|
|
|
DN_NETRequestHandle DN_NET_HandleFromRequest(DN_NETRequest *request)
|
|
{
|
|
DN_NETRequestHandle result = {};
|
|
if (request) {
|
|
result.handle = DN_Cast(DN_UPtr) request;
|
|
result.gen = request->gen;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void DN_NET_EndFinishedRequest_(DN_NETRequest *request)
|
|
{
|
|
// NOTE: Deallocate the memory used in the request and reset the string builder
|
|
DN_ArenaTempEnd(&request->start_response_arena, DN_ArenaReset_Yes);
|
|
// NOTE: Check that the request is completely detached
|
|
DN_Assert(request->next == nullptr);
|
|
}
|
|
|
|
void DN_NET_BaseInit_(DN_NETCore *net, char *base, DN_U64 base_size)
|
|
{
|
|
net->base = base;
|
|
net->base_size = base_size;
|
|
net->mem = DN_MemListFromBuffer(net->base, net->base_size, DN_MemFlags_Nil);
|
|
net->arena = DN_ArenaFromMemList(&net->mem);
|
|
net->completion_sem = DN_OS_SemaphoreInit(0);
|
|
}
|
|
|
|
DN_NETRequestHandle DN_NET_SetupRequest_(DN_NETRequest *request, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args, DN_NETRequestType type)
|
|
{
|
|
// NOTE: Setup request
|
|
DN_Assert(request);
|
|
if (request) {
|
|
if (!request->mem.curr)
|
|
request->mem = DN_MemListFromVMem(DN_Megabytes(1), DN_Kilobytes(1), DN_MemFlags_Nil);
|
|
request->arena = DN_ArenaTempBeginFromMemList(&request->mem);
|
|
request->type = type;
|
|
request->gen = DN_Max(request->gen + 1, 1);
|
|
request->url = DN_Str8FromStr8Arena(url, &request->arena);
|
|
request->method = DN_Str8FromStr8Arena(DN_Str8TrimWhitespaceAround(method), &request->arena);
|
|
|
|
if (args) {
|
|
request->args.flags = args->flags;
|
|
request->args.username = DN_Str8FromStr8Arena(args->username, &request->arena);
|
|
request->args.password = DN_Str8FromStr8Arena(args->password, &request->arena);
|
|
if (type == DN_NETRequestType_HTTP)
|
|
request->args.payload = DN_Str8FromStr8Arena(args->payload, &request->arena);
|
|
|
|
request->args.headers = DN_ArenaNewArray(&request->arena, DN_Str8, args->headers_size, DN_ZMem_No);
|
|
DN_Assert(request->args.headers);
|
|
if (request->args.headers) {
|
|
for (DN_ForItSize(it, DN_Str8, args->headers, args->headers_size))
|
|
request->args.headers[it.index] = DN_Str8FromStr8Arena(*it.data, &request->arena);
|
|
request->args.headers_size = args->headers_size;
|
|
}
|
|
}
|
|
|
|
request->completion_sem = DN_OS_SemaphoreInit(0);
|
|
request->start_response_arena = DN_ArenaTempBeginFromArena(&request->arena);
|
|
}
|
|
|
|
DN_NETRequestHandle result = DN_NET_HandleFromRequest(request);
|
|
request->response.request = result;
|
|
return result;
|
|
}
|
|
#endif
|
|
|
|
#if DN_CPP_WITH_TESTS
|
|
// DN: Single header generator commented out => #include "Extra/dn_tests.cpp"
|
|
// DN: Single header generator commented out => #if defined(_CLANGD)
|
|
// #define DN_H_WITH_OS 1
|
|
// #include "../dn.h"
|
|
// #include "../Extra/dn_net.h"
|
|
// #include "../Extra/dn_net_curl.h"
|
|
// #include "../Standalone/dn_utest.h"
|
|
//
|
|
// #define DN_UNIT_TESTS_WITH_KECCAK
|
|
// #define DN_UNIT_TESTS_WITH_NET
|
|
// #define DN_UNIT_TESTS_WITH_CURL
|
|
// #define DN_SHA3_IMPLEMENTATION
|
|
// #endif
|
|
|
|
#if !defined(DN_UT_H)
|
|
#error dn_utest.h must be included before this
|
|
#endif
|
|
|
|
#if !defined(DN_UT_IMPLEMENTATION)
|
|
#error DN_UT_IMPLEMENTATION must be defined before dn_utest.h
|
|
#endif
|
|
|
|
#include <inttypes.h>
|
|
|
|
struct DN_TSTResult
|
|
{
|
|
bool passed;
|
|
int total_tests;
|
|
int total_good_tests;
|
|
};
|
|
|
|
enum DN_TSTPrint
|
|
{
|
|
DN_TSTPrint_No,
|
|
DN_TSTPrint_OnFailure,
|
|
DN_TSTPrint_Yes,
|
|
};
|
|
|
|
// NOTE: Taken from MSDN __cpuid example implementation
|
|
// https://learn.microsoft.com/en-us/cpp/intrinsics/cpuid-cpuidex?view=msvc-170
|
|
#if defined(DN_PLATFORM_WIN32) && defined(DN_COMPILER_MSVC)
|
|
struct DN_RefImplCPUReport {
|
|
unsigned int nIds_ = 0;
|
|
unsigned int nExIds_ = 0;
|
|
char vendor_[0x20] = {};
|
|
int vendorSize_ = 0;
|
|
char brand_[0x40] = {};
|
|
int brandSize_ = 0;
|
|
bool isIntel_ = false;
|
|
bool isAMD_ = false;
|
|
DN_U32 f_1_ECX_ = 0;
|
|
DN_U32 f_1_EDX_ = 0;
|
|
DN_U32 f_7_EBX_ = 0;
|
|
DN_U32 f_7_ECX_ = 0;
|
|
DN_U32 f_81_ECX_ = 0;
|
|
DN_U32 f_81_EDX_ = 0;
|
|
int data_[400][4] = {};
|
|
size_t dataSize_ = 0;
|
|
int extdata_[400][4] = {};
|
|
size_t extdataSize_ = 0;
|
|
|
|
bool SSE3(void) const { return f_1_ECX_ & (1 << 0); }
|
|
bool PCLMULQDQ(void) const { return f_1_ECX_ & (1 << 1); }
|
|
bool MONITOR(void) const { return f_1_ECX_ & (1 << 3); }
|
|
bool SSSE3(void) const { return f_1_ECX_ & (1 << 9); }
|
|
bool FMA(void) const { return f_1_ECX_ & (1 << 12); }
|
|
bool CMPXCHG16B(void) const { return f_1_ECX_ & (1 << 13); }
|
|
bool SSE41(void) const { return f_1_ECX_ & (1 << 19); }
|
|
bool SSE42(void) const { return f_1_ECX_ & (1 << 20); }
|
|
bool MOVBE(void) const { return f_1_ECX_ & (1 << 22); }
|
|
bool POPCNT(void) const { return f_1_ECX_ & (1 << 23); }
|
|
bool AES(void) const { return f_1_ECX_ & (1 << 25); }
|
|
bool XSAVE(void) const { return f_1_ECX_ & (1 << 26); }
|
|
bool OSXSAVE(void) const { return f_1_ECX_ & (1 << 27); }
|
|
bool AVX(void) const { return f_1_ECX_ & (1 << 28); }
|
|
bool F16C(void) const { return f_1_ECX_ & (1 << 29); }
|
|
bool RDRAND(void) const { return f_1_ECX_ & (1 << 30); }
|
|
|
|
bool MSR(void) const { return f_1_EDX_ & (1 << 5); }
|
|
bool CX8(void) const { return f_1_EDX_ & (1 << 8); }
|
|
bool SEP(void) const { return f_1_EDX_ & (1 << 11); }
|
|
bool CMOV(void) const { return f_1_EDX_ & (1 << 15); }
|
|
bool CLFSH(void) const { return f_1_EDX_ & (1 << 19); }
|
|
bool MMX(void) const { return f_1_EDX_ & (1 << 23); }
|
|
bool FXSR(void) const { return f_1_EDX_ & (1 << 24); }
|
|
bool SSE(void) const { return f_1_EDX_ & (1 << 25); }
|
|
bool SSE2(void) const { return f_1_EDX_ & (1 << 26); }
|
|
|
|
bool FSGSBASE(void) const { return f_7_EBX_ & (1 << 0); }
|
|
bool BMI1(void) const { return f_7_EBX_ & (1 << 3); }
|
|
bool HLE(void) const { return isIntel_ && f_7_EBX_ & (1 << 4); }
|
|
bool AVX2(void) const { return f_7_EBX_ & (1 << 5); }
|
|
bool BMI2(void) const { return f_7_EBX_ & (1 << 8); }
|
|
bool ERMS(void) const { return f_7_EBX_ & (1 << 9); }
|
|
bool INVPCID(void) const { return f_7_EBX_ & (1 << 10); }
|
|
bool RTM(void) const { return isIntel_ && f_7_EBX_ & (1 << 11); }
|
|
bool AVX512F(void) const { return f_7_EBX_ & (1 << 16); }
|
|
bool RDSEED(void) const { return f_7_EBX_ & (1 << 18); }
|
|
bool ADX(void) const { return f_7_EBX_ & (1 << 19); }
|
|
bool AVX512PF(void) const { return f_7_EBX_ & (1 << 26); }
|
|
bool AVX512ER(void) const { return f_7_EBX_ & (1 << 27); }
|
|
bool AVX512CD(void) const { return f_7_EBX_ & (1 << 28); }
|
|
bool SHA(void) const { return f_7_EBX_ & (1 << 29); }
|
|
|
|
bool PREFETCHWT1(void) const { return f_7_ECX_ & (1 << 0); }
|
|
|
|
bool LAHF(void) const { return f_81_ECX_ & (1 << 0); }
|
|
bool LZCNT(void) const { return isIntel_ && f_81_ECX_ & (1 << 5); }
|
|
bool ABM(void) const { return isAMD_ && f_81_ECX_ & (1 << 5); }
|
|
bool SSE4a(void) const { return isAMD_ && f_81_ECX_ & (1 << 6); }
|
|
bool XOP(void) const { return isAMD_ && f_81_ECX_ & (1 << 11); }
|
|
bool TBM(void) const { return isAMD_ && f_81_ECX_ & (1 << 21); }
|
|
|
|
bool SYSCALL(void) const { return isIntel_ && f_81_EDX_ & (1 << 11); }
|
|
bool MMXEXT(void) const { return isAMD_ && f_81_EDX_ & (1 << 22); }
|
|
bool RDTSCP(void) const { return f_81_EDX_ & (1 << 27); }
|
|
bool _3DNOWEXT(void) const { return isAMD_ && f_81_EDX_ & (1 << 30); }
|
|
bool _3DNOW(void) const { return isAMD_ && f_81_EDX_ & (1 << 31); }
|
|
};
|
|
|
|
static DN_RefImplCPUReport DN_RefImplCPUReport_Init()
|
|
{
|
|
DN_RefImplCPUReport result = {};
|
|
|
|
// int cpuInfo[4] = {-1};
|
|
int cpui[4];
|
|
|
|
// Calling __cpuid with 0x0 as the function_id argument
|
|
// gets the number of the highest valid function ID.
|
|
__cpuid(cpui, 0);
|
|
result.nIds_ = cpui[0];
|
|
|
|
for (unsigned int i = 0; i <= result.nIds_; ++i) {
|
|
__cpuidex(cpui, i, 0);
|
|
memcpy(result.data_[result.dataSize_++], cpui, sizeof(cpui));
|
|
}
|
|
|
|
// Capture vendor string
|
|
*reinterpret_cast<int *>(result.vendor_) = result.data_[0][1];
|
|
*reinterpret_cast<int *>(result.vendor_ + 4) = result.data_[0][3];
|
|
*reinterpret_cast<int *>(result.vendor_ + 8) = result.data_[0][2];
|
|
result.vendorSize_ = (int)strlen(result.vendor_);
|
|
|
|
if (strcmp(result.vendor_, "GenuineIntel") == 0)
|
|
result.isIntel_ = true;
|
|
else if (strcmp(result.vendor_, "AuthenticAMD") == 0)
|
|
result.isAMD_ = true;
|
|
|
|
// load bitset with flags for function 0x00000001
|
|
if (result.nIds_ >= 1) {
|
|
result.f_1_ECX_ = result.data_[1][2];
|
|
result.f_1_EDX_ = result.data_[1][3];
|
|
}
|
|
|
|
// load bitset with flags for function 0x00000007
|
|
if (result.nIds_ >= 7) {
|
|
result.f_7_EBX_ = result.data_[7][1];
|
|
result.f_7_ECX_ = result.data_[7][2];
|
|
}
|
|
|
|
// Calling __cpuid with 0x80000000 as the function_id argument
|
|
// gets the number of the highest valid extended ID.
|
|
__cpuid(cpui, 0x80000000);
|
|
result.nExIds_ = cpui[0];
|
|
|
|
for (unsigned int i = 0x80000000; i <= result.nExIds_; ++i) {
|
|
__cpuidex(cpui, i, 0);
|
|
memcpy(result.extdata_[result.extdataSize_++], cpui, sizeof(cpui));
|
|
}
|
|
|
|
// load bitset with flags for function 0x80000001
|
|
if (result.nExIds_ >= 0x80000001) {
|
|
result.f_81_ECX_ = result.extdata_[1][2];
|
|
result.f_81_EDX_ = result.extdata_[1][3];
|
|
}
|
|
|
|
// Interpret CPU brand string if reported
|
|
if (result.nExIds_ >= 0x80000004) {
|
|
memcpy(result.brand_, result.extdata_[2], sizeof(cpui));
|
|
memcpy(result.brand_ + 16, result.extdata_[3], sizeof(cpui));
|
|
memcpy(result.brand_ + 32, result.extdata_[4], sizeof(cpui));
|
|
result.brandSize_ = (int)strlen(result.brand_);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
#if 0
|
|
static void DN_RefImpl_CPUReportDump() // Print out supported instruction set features
|
|
{
|
|
auto support_message = [](std::string isa_feature, bool is_supported) {
|
|
printf("%s %s\n", isa_feature.c_str(), is_supported ? "supported" : "not supported");
|
|
};
|
|
|
|
printf("%s\n", DN_RefImplCPUReport::Vendor().c_str());
|
|
printf("%s\n", DN_RefImplCPUReport::Brand().c_str());
|
|
|
|
support_message("3DNOW", DN_RefImplCPUReport::_3DNOW());
|
|
support_message("3DNOWEXT", DN_RefImplCPUReport::_3DNOWEXT());
|
|
support_message("ABM", DN_RefImplCPUReport::ABM());
|
|
support_message("ADX", DN_RefImplCPUReport::ADX());
|
|
support_message("AES", DN_RefImplCPUReport::AES());
|
|
support_message("AVX", DN_RefImplCPUReport::AVX());
|
|
support_message("AVX2", DN_RefImplCPUReport::AVX2());
|
|
support_message("AVX512CD", DN_RefImplCPUReport::AVX512CD());
|
|
support_message("AVX512ER", DN_RefImplCPUReport::AVX512ER());
|
|
support_message("AVX512F", DN_RefImplCPUReport::AVX512F());
|
|
support_message("AVX512PF", DN_RefImplCPUReport::AVX512PF());
|
|
support_message("BMI1", DN_RefImplCPUReport::BMI1());
|
|
support_message("BMI2", DN_RefImplCPUReport::BMI2());
|
|
support_message("CLFSH", DN_RefImplCPUReport::CLFSH());
|
|
support_message("CMPXCHG16B", DN_RefImplCPUReport::CMPXCHG16B());
|
|
support_message("CX8", DN_RefImplCPUReport::CX8());
|
|
support_message("ERMS", DN_RefImplCPUReport::ERMS());
|
|
support_message("F16C", DN_RefImplCPUReport::F16C());
|
|
support_message("FMA", DN_RefImplCPUReport::FMA());
|
|
support_message("FSGSBASE", DN_RefImplCPUReport::FSGSBASE());
|
|
support_message("FXSR", DN_RefImplCPUReport::FXSR());
|
|
support_message("HLE", DN_RefImplCPUReport::HLE());
|
|
support_message("INVPCID", DN_RefImplCPUReport::INVPCID());
|
|
support_message("LAHF", DN_RefImplCPUReport::LAHF());
|
|
support_message("LZCNT", DN_RefImplCPUReport::LZCNT());
|
|
support_message("MMX", DN_RefImplCPUReport::MMX());
|
|
support_message("MMXEXT", DN_RefImplCPUReport::MMXEXT());
|
|
support_message("MONITOR", DN_RefImplCPUReport::MONITOR());
|
|
support_message("MOVBE", DN_RefImplCPUReport::MOVBE());
|
|
support_message("MSR", DN_RefImplCPUReport::MSR());
|
|
support_message("OSXSAVE", DN_RefImplCPUReport::OSXSAVE());
|
|
support_message("PCLMULQDQ", DN_RefImplCPUReport::PCLMULQDQ());
|
|
support_message("POPCNT", DN_RefImplCPUReport::POPCNT());
|
|
support_message("PREFETCHWT1", DN_RefImplCPUReport::PREFETCHWT1());
|
|
support_message("RDRAND", DN_RefImplCPUReport::RDRAND());
|
|
support_message("RDSEED", DN_RefImplCPUReport::RDSEED());
|
|
support_message("RDTSCP", DN_RefImplCPUReport::RDTSCP());
|
|
support_message("RTM", DN_RefImplCPUReport::RTM());
|
|
support_message("SEP", DN_RefImplCPUReport::SEP());
|
|
support_message("SHA", DN_RefImplCPUReport::SHA());
|
|
support_message("SSE", DN_RefImplCPUReport::SSE());
|
|
support_message("SSE2", DN_RefImplCPUReport::SSE2());
|
|
support_message("SSE3", DN_RefImplCPUReport::SSE3());
|
|
support_message("SSE4.1", DN_RefImplCPUReport::SSE41());
|
|
support_message("SSE4.2", DN_RefImplCPUReport::SSE42());
|
|
support_message("SSE4a", DN_RefImplCPUReport::SSE4a());
|
|
support_message("SSSE3", DN_RefImplCPUReport::SSSE3());
|
|
support_message("SYSCALL", DN_RefImplCPUReport::SYSCALL());
|
|
support_message("TBM", DN_RefImplCPUReport::TBM());
|
|
support_message("XOP", DN_RefImplCPUReport::XOP());
|
|
support_message("XSAVE", DN_RefImplCPUReport::XSAVE());
|
|
};
|
|
#endif
|
|
#endif // defined(DN_PLATFORM_WIN32) && defined(DN_COMPILER_MSVC)
|
|
|
|
static DN_UTCore DN_TST_Base()
|
|
{
|
|
DN_UTCore result = DN_UT_Init();
|
|
DN_UT_LogF(&result, "Base\n");
|
|
{
|
|
#if defined(DN_PLATFORM_WIN32) && defined(DN_COMPILER_MSVC)
|
|
DN_RefImplCPUReport ref_cpu_report = DN_RefImplCPUReport_Init();
|
|
for (DN_UT_Test(&result, "Query CPUID")) {
|
|
DN_CPUReport cpu_report = DN_CPUGetReport();
|
|
|
|
// NOTE: Sanity check our report against MSDN's example ////////////////////////////////////////
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_3DNow) == ref_cpu_report._3DNOW());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_3DNowExt) == ref_cpu_report._3DNOWEXT());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_ABM) == ref_cpu_report.ABM());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_AES) == ref_cpu_report.AES());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_AVX) == ref_cpu_report.AVX());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_AVX2) == ref_cpu_report.AVX2());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_AVX512CD) == ref_cpu_report.AVX512CD());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_AVX512ER) == ref_cpu_report.AVX512ER());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_AVX512F) == ref_cpu_report.AVX512F());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_AVX512PF) == ref_cpu_report.AVX512PF());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_CMPXCHG16B) == ref_cpu_report.CMPXCHG16B());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_F16C) == ref_cpu_report.F16C());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_FMA) == ref_cpu_report.FMA());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_MMX) == ref_cpu_report.MMX());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_MmxExt) == ref_cpu_report.MMXEXT());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_MONITOR) == ref_cpu_report.MONITOR());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_MOVBE) == ref_cpu_report.MOVBE());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_PCLMULQDQ) == ref_cpu_report.PCLMULQDQ());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_POPCNT) == ref_cpu_report.POPCNT());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_RDRAND) == ref_cpu_report.RDRAND());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_RDSEED) == ref_cpu_report.RDSEED());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_RDTSCP) == ref_cpu_report.RDTSCP());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SHA) == ref_cpu_report.SHA());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SSE) == ref_cpu_report.SSE());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SSE2) == ref_cpu_report.SSE2());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SSE3) == ref_cpu_report.SSE3());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SSE41) == ref_cpu_report.SSE41());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SSE42) == ref_cpu_report.SSE42());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SSE4A) == ref_cpu_report.SSE4a());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SSSE3) == ref_cpu_report.SSSE3());
|
|
|
|
// NOTE: Feature flags we haven't bothered detecting yet but are in MSDN's example /////////////
|
|
/*
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_ADX) == DN_RefImplCPUReport::ADX());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_BMI1) == DN_RefImplCPUReport::BMI1());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_BMI2) == DN_RefImplCPUReport::BMI2());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_CLFSH) == DN_RefImplCPUReport::CLFSH());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_CX8) == DN_RefImplCPUReport::CX8());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_ERMS) == DN_RefImplCPUReport::ERMS());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_FSGSBASE) == DN_RefImplCPUReport::FSGSBASE());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_FXSR) == DN_RefImplCPUReport::FXSR());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_HLE) == DN_RefImplCPUReport::HLE());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_INVPCID) == DN_RefImplCPUReport::INVPCID());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_LAHF) == DN_RefImplCPUReport::LAHF());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_LZCNT) == DN_RefImplCPUReport::LZCNT());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_MSR) == DN_RefImplCPUReport::MSR());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_OSXSAVE) == DN_RefImplCPUReport::OSXSAVE());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_PREFETCHWT1) == DN_RefImplCPUReport::PREFETCHWT1());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_RTM) == DN_RefImplCPUReport::RTM());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SEP) == DN_RefImplCPUReport::SEP());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SYSCALL) == DN_RefImplCPUReport::SYSCALL());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_TBM) == DN_RefImplCPUReport::TBM());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_XOP) == DN_RefImplCPUReport::XOP());
|
|
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_XSAVE) == DN_RefImplCPUReport::XSAVE());
|
|
*/
|
|
}
|
|
#endif // defined(DN_PLATFORM_WIN32) && defined(DN_COMPILER_MSVC)
|
|
|
|
for (DN_UT_Test(&result, "Age from U64: 1001 converts to 1s 1ms (seconds and ms)")) {
|
|
DN_Str8x128 str8 = DN_AgeStr8FromMsU64(1001, DN_AgeUnit_Sec | DN_AgeUnit_Ms);
|
|
DN_Str8 expect = DN_Str8Lit("1s 1ms");
|
|
DN_UT_AssertF(&result, DN_MemEq(str8.data, str8.size, expect.data, expect.size), "str8=%.*s, expect=%.*s", DN_Str8PrintFmt(str8), DN_Str8PrintFmt(expect));
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Age from U64: 1001 converts to 1.001s (fractional)")) {
|
|
DN_Str8x128 str8 = DN_AgeStr8FromMsU64(1001, DN_AgeUnit_FractionalSec);
|
|
DN_Str8 expect = DN_Str8Lit("1.001s");
|
|
DN_UT_AssertF(&result, DN_MemEq(str8.data, str8.size, expect.data, expect.size), "str8=%.*s, expect=%.*s", DN_Str8PrintFmt(str8), DN_Str8PrintFmt(expect));
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "FmtAppendTruncate: String truncates with 3 dots")) {
|
|
char buf[8] = {};
|
|
DN_USize buf_size = 0;
|
|
DN_FmtAppendResult buf_str8 = DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), "This string is longer than %d characters", DN_Cast(int)(sizeof(buf) - 1));
|
|
DN_Str8 expect = DN_Str8Lit("This..."); // 7 characters long, 1 byte reserved for null-terminator
|
|
DN_UT_Assert(&result, buf_str8.truncated);
|
|
DN_UT_AssertF(&result, DN_Str8Eq(buf_str8.str8, expect), "buf_str8=%.*s, expect=%.*s", DN_Str8PrintFmt(buf_str8.str8), DN_Str8PrintFmt(expect));
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "TicketMutex: Start and stop")) {
|
|
// TODO: We don't have a meaningful result but since atomics are
|
|
// implemented with a macro this ensures that we result that they are
|
|
// written correctly.
|
|
DN_TicketMutex mutex = {};
|
|
DN_TicketMutex_Begin(&mutex);
|
|
DN_TicketMutex_End(&mutex);
|
|
DN_UT_Assert(&result, mutex.ticket == mutex.serving);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "TicketMutex: Start and stop w/ advanced API")) {
|
|
DN_TicketMutex mutex = {};
|
|
unsigned int ticket_a = DN_TicketMutex_MakeTicket(&mutex);
|
|
unsigned int ticket_b = DN_TicketMutex_MakeTicket(&mutex);
|
|
DN_UT_Assert(&result, DN_Cast(bool) DN_TicketMutex_CanLock(&mutex, ticket_b) == false);
|
|
DN_UT_Assert(&result, DN_Cast(bool) DN_TicketMutex_CanLock(&mutex, ticket_a) == true);
|
|
|
|
DN_TicketMutex_BeginTicket(&mutex, ticket_a);
|
|
DN_TicketMutex_End(&mutex);
|
|
DN_TicketMutex_BeginTicket(&mutex, ticket_b);
|
|
DN_TicketMutex_End(&mutex);
|
|
|
|
DN_UT_Assert(&result, mutex.ticket == mutex.serving);
|
|
DN_UT_Assert(&result, mutex.ticket == ticket_b + 1);
|
|
}
|
|
|
|
// NOTE: MSVC SAL complains that we are using Interlocked functionality on
|
|
// variables it has detected as *not* being shared across threads. This is
|
|
// fine, we're just running some basic results, so permit it.
|
|
//
|
|
// Warning 28112 is a knock-on effect of this that it doesn't like us
|
|
// reading the value of the variable that has been used in an Interlocked
|
|
// function locally.
|
|
DN_MSVC_WARNING_PUSH
|
|
DN_MSVC_WARNING_DISABLE(28113) // Accessing a local variable val via an Interlocked function.
|
|
DN_MSVC_WARNING_DISABLE(28112) // A variable (val) which is accessed via an Interlocked function must always be accessed via an Interlocked function. See line 759.
|
|
{
|
|
// TODO(dn): We don't have meaningful results here, but since
|
|
// atomics/intrinsics are implemented using macros we ensure the macro was
|
|
// written properly with these results.
|
|
for (DN_UT_Test(&result, "AtomicAddU32")) {
|
|
DN_U32 val = 0;
|
|
DN_AtomicAddU32(&val, 1);
|
|
DN_UT_AssertF(&result, val == 1, "val: %u", val);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "AtomicAddU64")) {
|
|
uint64_t val = 0;
|
|
DN_AtomicAddU64(&val, 1);
|
|
DN_UT_AssertF(&result, val == 1, "val: %" PRIu64, val);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "AtomicSubU32")) {
|
|
DN_U32 val = 1;
|
|
DN_AtomicSubU32(&val, 1);
|
|
DN_UT_AssertF(&result, val == 0, "val: %u", val);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "AtomicSubU64")) {
|
|
uint64_t val = 1;
|
|
DN_AtomicSubU64(&val, 1);
|
|
DN_UT_AssertF(&result, val == 0, "val: %" PRIu64, val);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "AtomicSetValue32")) {
|
|
DN_U32 a = 0;
|
|
DN_U32 b = 111;
|
|
DN_AtomicSetValue32(&a, b);
|
|
DN_UT_AssertF(&result, a == b, "a: %ld, b: %ld", a, b);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "AtomicSetValue64")) {
|
|
int64_t a = 0;
|
|
int64_t b = 111;
|
|
DN_AtomicSetValue64(DN_Cast(uint64_t *) & a, b);
|
|
DN_UT_AssertF(&result, a == b, "a: %" PRId64 ", b: %" PRId64, a, b);
|
|
}
|
|
|
|
DN_UT_BeginF(&result, "CPUGetTSC: Compile check");
|
|
DN_CPUGetTSC();
|
|
DN_UT_End(&result);
|
|
|
|
DN_UT_BeginF(&result, "CompilerReadBarrierAndCPUReadFence: Compile check");
|
|
DN_CompilerReadBarrierAndCPUReadFence;
|
|
DN_UT_End(&result);
|
|
|
|
DN_UT_BeginF(&result, "CompilerWriteBarrierAndCPUWriteFence: Compile check");
|
|
DN_CompilerWriteBarrierAndCPUWriteFence;
|
|
DN_UT_End(&result);
|
|
}
|
|
DN_MSVC_WARNING_POP
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static DN_UTCore DN_TST_BaseArena()
|
|
{
|
|
DN_UTCore result = DN_UT_Init();
|
|
DN_UT_LogF(&result, "Arena\n");
|
|
{
|
|
for (DN_UT_Test(&result, "Reused memory is zeroed out")) {
|
|
uint8_t alignment = 1;
|
|
DN_USize alloc_size = DN_Kilobytes(128);
|
|
DN_MemList mem = DN_MemListFromVMem(0, 0, DN_MemFlags_Nil);
|
|
DN_Arena arena = DN_ArenaFromMemList(&mem);
|
|
DN_DEFER { DN_MemListDeinit(&mem); };
|
|
|
|
// NOTE: Allocate 128 kilobytes, fill it with garbage, then reset the arena
|
|
uintptr_t first_ptr_address = 0;
|
|
{
|
|
DN_U64 mem_p = DN_MemListPos(arena.mem);
|
|
void *ptr = DN_ArenaAlloc(&arena, alloc_size, alignment, DN_ZMem_Yes);
|
|
first_ptr_address = DN_Cast(uintptr_t) ptr;
|
|
DN_Memset(ptr, 'z', alloc_size);
|
|
DN_MemListPopTo(arena.mem, mem_p);
|
|
}
|
|
|
|
// NOTE: Reallocate 128 kilobytes
|
|
char *ptr = DN_Cast(char *) DN_ArenaAlloc(&arena, alloc_size, alignment, DN_ZMem_Yes);
|
|
|
|
// NOTE: Double check we got the same pointer
|
|
DN_UT_Assert(&result, first_ptr_address == DN_Cast(uintptr_t) ptr);
|
|
|
|
// NOTE: Check that the bytes are set to 0
|
|
for (DN_USize i = 0; i < alloc_size; i++)
|
|
DN_UT_Assert(&result, ptr[i] == 0);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Test arena grows naturally, 1mb + 4mb")) {
|
|
// NOTE: Allocate 1mb, then 4mb, this should force the arena to grow
|
|
DN_MemList mem = DN_MemListFromVMem(DN_Megabytes(2), DN_Megabytes(2), DN_MemFlags_Nil);
|
|
DN_Arena arena = DN_ArenaFromMemList(&mem);
|
|
DN_DEFER { DN_MemListDeinit(&mem); };
|
|
|
|
char *ptr_1mb = DN_ArenaNewArray(&arena, char, DN_Megabytes(1), DN_ZMem_Yes);
|
|
char *ptr_4mb = DN_ArenaNewArray(&arena, char, DN_Megabytes(4), DN_ZMem_Yes);
|
|
DN_UT_Assert(&result, ptr_1mb);
|
|
DN_UT_Assert(&result, ptr_4mb);
|
|
|
|
DN_MemBlock const *block_4mb_begin = arena.mem->curr;
|
|
char const *block_4mb_end = DN_Cast(char *) block_4mb_begin + block_4mb_begin->reserve;
|
|
|
|
DN_MemBlock const *block_1mb_begin = block_4mb_begin->prev;
|
|
DN_UT_AssertF(&result, block_1mb_begin, "New block should have been allocated");
|
|
char const *block_1mb_end = DN_Cast(char *) block_1mb_begin + block_1mb_begin->reserve;
|
|
|
|
DN_UT_AssertF(&result, block_1mb_begin != block_4mb_begin, "New block should have been allocated and linked");
|
|
DN_UT_AssertF(&result, ptr_1mb >= DN_Cast(char *) block_1mb_begin && ptr_1mb <= block_1mb_end, "Pointer was not allocated from correct memory block");
|
|
DN_UT_AssertF(&result, ptr_4mb >= DN_Cast(char *) block_4mb_begin && ptr_4mb <= block_4mb_end, "Pointer was not allocated from correct memory block");
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Test arena grows naturally, 1mb, temp memory 4mb")) {
|
|
DN_MemList mem = DN_MemListFromVMem(DN_Megabytes(2), DN_Megabytes(2), DN_MemFlags_Nil);
|
|
DN_Arena arena = DN_ArenaFromMemList(&mem);
|
|
DN_DEFER { DN_MemListDeinit(&mem); };
|
|
|
|
// NOTE: Allocate 1mb, then 4mb, this should force the arena to grow
|
|
char *ptr_1mb = DN_Cast(char *) DN_ArenaAlloc(&arena, DN_Megabytes(1), 1 /*align*/, DN_ZMem_Yes);
|
|
DN_UT_Assert(&result, ptr_1mb);
|
|
|
|
DN_Arena temp = DN_ArenaTempBeginFromArena(&arena);
|
|
{
|
|
char *ptr_4mb = DN_ArenaNewArray(&temp, char, DN_Megabytes(4), DN_ZMem_Yes);
|
|
DN_UT_Assert(&result, ptr_4mb);
|
|
|
|
DN_MemBlock const *block_4mb_begin = arena.mem->curr;
|
|
char const *block_4mb_end = DN_Cast(char *) block_4mb_begin + block_4mb_begin->reserve;
|
|
|
|
DN_MemBlock const *block_1mb_begin = block_4mb_begin->prev;
|
|
char const *block_1mb_end = DN_Cast(char *) block_1mb_begin + block_1mb_begin->reserve;
|
|
|
|
DN_UT_AssertF(&result, block_1mb_begin != block_4mb_begin, "New block should have been allocated and linked");
|
|
DN_UT_AssertF(&result, ptr_1mb >= DN_Cast(char *) block_1mb_begin && ptr_1mb <= block_1mb_end, "Pointer was not allocated from correct memory block");
|
|
DN_UT_AssertF(&result, ptr_4mb >= DN_Cast(char *) block_4mb_begin && ptr_4mb <= block_4mb_end, "Pointer was not allocated from correct memory block");
|
|
}
|
|
DN_ArenaTempEnd(&temp, DN_ArenaReset_Yes);
|
|
DN_UT_Assert(&result, arena.mem->curr->prev == nullptr);
|
|
DN_UT_AssertF(&result,
|
|
arena.mem->curr->reserve >= DN_Megabytes(1),
|
|
"size=%" PRIu64 "MiB (%" PRIu64 "B), expect=%" PRIu64 "B",
|
|
(arena.mem->curr->reserve / 1024 / 1024),
|
|
arena.mem->curr->reserve,
|
|
DN_Megabytes(1));
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static DN_UTCore DN_TST_BaseBytesHex()
|
|
{
|
|
DN_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_UTCore test = DN_UT_Init();
|
|
DN_UT_LogF(&test, "Bytes <-> Hex\n");
|
|
{
|
|
for (DN_UT_Test(&test, "Convert 0x123")) {
|
|
uint64_t result = DN_U64FromHexStr8Unsafe(DN_Str8Lit("0x123"));
|
|
DN_UT_AssertF(&test, result == 0x123, "result: %" PRIu64, result);
|
|
}
|
|
|
|
for (DN_UT_Test(&test, "Convert 0xFFFF")) {
|
|
uint64_t result = DN_U64FromHexStr8Unsafe(DN_Str8Lit("0xFFFF"));
|
|
DN_UT_AssertF(&test, result == 0xFFFF, "result: %" PRIu64, result);
|
|
}
|
|
|
|
for (DN_UT_Test(&test, "Convert FFFF")) {
|
|
uint64_t result = DN_U64FromHexStr8Unsafe(DN_Str8Lit("FFFF"));
|
|
DN_UT_AssertF(&test, result == 0xFFFF, "result: %" PRIu64, result);
|
|
}
|
|
|
|
for (DN_UT_Test(&test, "Convert abCD")) {
|
|
uint64_t result = DN_U64FromHexStr8Unsafe(DN_Str8Lit("abCD"));
|
|
DN_UT_AssertF(&test, result == 0xabCD, "result: %" PRIu64, result);
|
|
}
|
|
|
|
for (DN_UT_Test(&test, "Convert 0xabCD")) {
|
|
uint64_t result = DN_U64FromHexStr8Unsafe(DN_Str8Lit("0xabCD"));
|
|
DN_UT_AssertF(&test, result == 0xabCD, "result: %" PRIu64, result);
|
|
}
|
|
|
|
for (DN_UT_Test(&test, "Convert 0x")) {
|
|
uint64_t result = DN_U64FromHexStr8Unsafe(DN_Str8Lit("0x"));
|
|
DN_UT_AssertF(&test, result == 0x0, "result: %" PRIu64, result);
|
|
}
|
|
|
|
for (DN_UT_Test(&test, "Convert 0X")) {
|
|
uint64_t result = DN_U64FromHexStr8Unsafe(DN_Str8Lit("0X"));
|
|
DN_UT_AssertF(&test, result == 0x0, "result: %" PRIu64, result);
|
|
}
|
|
|
|
for (DN_UT_Test(&test, "Convert 3")) {
|
|
uint64_t result = DN_U64FromHexStr8Unsafe(DN_Str8Lit("3"));
|
|
DN_UT_AssertF(&test, result == 3, "result: %" PRIu64, result);
|
|
}
|
|
|
|
for (DN_UT_Test(&test, "Convert f")) {
|
|
DN_U64FromResult result = DN_U64FromHexStr8(DN_Str8Lit("f"));
|
|
DN_UT_Assert(&test, result.success);
|
|
DN_UT_Assert(&test, result.value == 0xf);
|
|
}
|
|
|
|
for (DN_UT_Test(&test, "Convert g")) {
|
|
DN_U64FromResult result = DN_U64FromHexStr8(DN_Str8Lit("g"));
|
|
DN_UT_Assert(&test, !result.success);
|
|
}
|
|
|
|
for (DN_UT_Test(&test, "Convert -0x3")) {
|
|
DN_U64FromResult result = DN_U64FromHexStr8(DN_Str8Lit("-0x3"));
|
|
DN_UT_Assert(&test, !result.success);
|
|
}
|
|
|
|
DN_U32 number = 0xd095f6;
|
|
for (DN_UT_Test(&test, "Convert %x to string", number)) {
|
|
DN_Str8 number_hex = DN_HexFromPtrBytesArena(&number, sizeof(number), &scratch.arena, DN_TrimLeadingZero_No);
|
|
DN_UT_AssertF(&test, DN_Str8Eq(number_hex, DN_Str8Lit("f695d000")), "number_hex=%.*s", DN_Str8PrintFmt(number_hex));
|
|
}
|
|
|
|
number = 0xf6ed00;
|
|
for (DN_UT_Test(&test, "Convert %x to string", number)) {
|
|
DN_Str8 number_hex = DN_HexFromPtrBytesArena(&number, sizeof(number), &scratch.arena, DN_TrimLeadingZero_No);
|
|
DN_UT_AssertF(&test, DN_Str8Eq(number_hex, DN_Str8Lit("00edf600")), "number_hex=%.*s", DN_Str8PrintFmt(number_hex));
|
|
}
|
|
|
|
DN_Str8 hex = DN_Str8Lit("0xf6ed00");
|
|
for (DN_UT_Test(&test, "Convert %.*s to bytes", DN_Str8PrintFmt(hex))) {
|
|
DN_Str8 bytes = DN_BytesFromHexArena(hex, &scratch.arena);
|
|
DN_UT_AssertF(&test,
|
|
DN_Str8Eq(bytes, DN_Str8Lit("\xf6\xed\x00")),
|
|
"number_hex=%.*s",
|
|
DN_Str8PrintFmt(DN_HexFromPtrBytesArena(bytes.data, bytes.size, &scratch.arena, DN_TrimLeadingZero_No)));
|
|
}
|
|
|
|
for (DN_UT_Test(&test, "Convert empty bytes to string", number)) {
|
|
DN_Str8 bytes = DN_Str8Lit("");
|
|
DN_Str8 as_hex = DN_HexFromPtrBytesArena(bytes.data, bytes.size, &scratch.arena, DN_TrimLeadingZero_No);
|
|
DN_UT_AssertF(&test, DN_Str8Eq(as_hex, DN_Str8Lit("")), "as_hex=%.*s", DN_Str8PrintFmt(as_hex));
|
|
}
|
|
}
|
|
DN_TCScratchEnd(&scratch);
|
|
return test;
|
|
}
|
|
|
|
#if DN_H_WITH_HELPERS
|
|
static DN_UTCore DN_TST_BinarySearch()
|
|
{
|
|
DN_UTCore result = DN_UT_Init();
|
|
DN_UT_LogF(&result, "DN_BinarySearch\n");
|
|
{
|
|
for (DN_UT_Test(&result, "Search array of 1 item")) {
|
|
DN_U32 array[] = {1};
|
|
DN_BinarySearchResult search = {};
|
|
|
|
// NOTE: Match =============================================================================
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 0U /*find*/, DN_BinarySearchType_Match);
|
|
DN_UT_Assert(&result, !search.found);
|
|
DN_UT_Assert(&result, search.index == 0);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 1U /*find*/, DN_BinarySearchType_Match);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 0);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 2U /*find*/, DN_BinarySearchType_Match);
|
|
DN_UT_Assert(&result, !search.found);
|
|
DN_UT_Assert(&result, search.index == 1);
|
|
|
|
// NOTE: Lower bound =======================================================================
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 0U /*find*/, DN_BinarySearchType_LowerBound);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 0);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 1U /*find*/, DN_BinarySearchType_LowerBound);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 0);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 2U /*find*/, DN_BinarySearchType_LowerBound);
|
|
DN_UT_Assert(&result, !search.found);
|
|
DN_UT_Assert(&result, search.index == 1);
|
|
|
|
// NOTE: Upper bound =======================================================================
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 0U /*find*/, DN_BinarySearchType_UpperBound);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 0);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 1U /*find*/, DN_BinarySearchType_UpperBound);
|
|
DN_UT_Assert(&result, !search.found);
|
|
DN_UT_Assert(&result, search.index == 1);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 2U /*find*/, DN_BinarySearchType_UpperBound);
|
|
DN_UT_Assert(&result, !search.found);
|
|
DN_UT_Assert(&result, search.index == 1);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Search array of 2 items")) {
|
|
DN_U32 array[] = {1};
|
|
DN_BinarySearchResult search = {};
|
|
|
|
// NOTE: Match =============================================================================
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 0U /*find*/, DN_BinarySearchType_Match);
|
|
DN_UT_Assert(&result, !search.found);
|
|
DN_UT_Assert(&result, search.index == 0);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 1U /*find*/, DN_BinarySearchType_Match);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 0);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 2U /*find*/, DN_BinarySearchType_Match);
|
|
DN_UT_Assert(&result, !search.found);
|
|
DN_UT_Assert(&result, search.index == 1);
|
|
|
|
// NOTE: Lower bound =======================================================================
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 0U /*find*/, DN_BinarySearchType_LowerBound);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 0);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 1U /*find*/, DN_BinarySearchType_LowerBound);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 0);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 2U /*find*/, DN_BinarySearchType_LowerBound);
|
|
DN_UT_Assert(&result, !search.found);
|
|
DN_UT_Assert(&result, search.index == 1);
|
|
|
|
// NOTE: Upper bound =======================================================================
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 0U /*find*/, DN_BinarySearchType_UpperBound);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 0);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 1U /*find*/, DN_BinarySearchType_UpperBound);
|
|
DN_UT_Assert(&result, !search.found);
|
|
DN_UT_Assert(&result, search.index == 1);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 2U /*find*/, DN_BinarySearchType_UpperBound);
|
|
DN_UT_Assert(&result, !search.found);
|
|
DN_UT_Assert(&result, search.index == 1);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Search array of 3 items")) {
|
|
DN_U32 array[] = {1, 2, 3};
|
|
DN_BinarySearchResult search = {};
|
|
|
|
// NOTE: Match =============================================================================
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 0U /*find*/, DN_BinarySearchType_Match);
|
|
DN_UT_Assert(&result, !search.found);
|
|
DN_UT_Assert(&result, search.index == 0);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 1U /*find*/, DN_BinarySearchType_Match);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 0);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 2U /*find*/, DN_BinarySearchType_Match);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 1);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 3U /*find*/, DN_BinarySearchType_Match);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 2);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 4U /*find*/, DN_BinarySearchType_Match);
|
|
DN_UT_Assert(&result, !search.found);
|
|
DN_UT_Assert(&result, search.index == 3);
|
|
|
|
// NOTE: Lower bound =======================================================================
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 0U /*find*/, DN_BinarySearchType_LowerBound);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 0);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 1U /*find*/, DN_BinarySearchType_LowerBound);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 0);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 2U /*find*/, DN_BinarySearchType_LowerBound);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 1);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 3U /*find*/, DN_BinarySearchType_LowerBound);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 2);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 4U /*find*/, DN_BinarySearchType_LowerBound);
|
|
DN_UT_Assert(&result, !search.found);
|
|
DN_UT_Assert(&result, search.index == 3);
|
|
|
|
// NOTE: Upper bound =======================================================================
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 0U /*find*/, DN_BinarySearchType_UpperBound);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 0);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 1U /*find*/, DN_BinarySearchType_UpperBound);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 1);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 2U /*find*/, DN_BinarySearchType_UpperBound);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 2);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 3U /*find*/, DN_BinarySearchType_UpperBound);
|
|
DN_UT_Assert(&result, !search.found);
|
|
DN_UT_Assert(&result, search.index == 3);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 4U /*find*/, DN_BinarySearchType_UpperBound);
|
|
DN_UT_Assert(&result, !search.found);
|
|
DN_UT_Assert(&result, search.index == 3);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Search array of 4 items")) {
|
|
DN_U32 array[] = {1, 2, 3, 4};
|
|
DN_BinarySearchResult search = {};
|
|
|
|
// NOTE: Match =============================================================================
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 0U /*find*/, DN_BinarySearchType_Match);
|
|
DN_UT_Assert(&result, !search.found);
|
|
DN_UT_Assert(&result, search.index == 0);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 1U /*find*/, DN_BinarySearchType_Match);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 0);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 2U /*find*/, DN_BinarySearchType_Match);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 1);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 3U /*find*/, DN_BinarySearchType_Match);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 2);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 4U /*find*/, DN_BinarySearchType_Match);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 3);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 5U /*find*/, DN_BinarySearchType_Match);
|
|
DN_UT_Assert(&result, !search.found);
|
|
DN_UT_Assert(&result, search.index == 4);
|
|
|
|
// NOTE: Lower bound =======================================================================
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 0U /*find*/, DN_BinarySearchType_LowerBound);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 0);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 1U /*find*/, DN_BinarySearchType_LowerBound);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 0);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 2U /*find*/, DN_BinarySearchType_LowerBound);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 1);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 3U /*find*/, DN_BinarySearchType_LowerBound);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 2);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 4U /*find*/, DN_BinarySearchType_LowerBound);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 3);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 5U /*find*/, DN_BinarySearchType_LowerBound);
|
|
DN_UT_Assert(&result, !search.found);
|
|
DN_UT_Assert(&result, search.index == 4);
|
|
|
|
// NOTE: Upper bound =======================================================================
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 0U /*find*/, DN_BinarySearchType_UpperBound);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 0);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 1U /*find*/, DN_BinarySearchType_UpperBound);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 1);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 2U /*find*/, DN_BinarySearchType_UpperBound);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 2);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 3U /*find*/, DN_BinarySearchType_UpperBound);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 3);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 4U /*find*/, DN_BinarySearchType_UpperBound);
|
|
DN_UT_Assert(&result, !search.found);
|
|
DN_UT_Assert(&result, search.index == 4);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 5U /*find*/, DN_BinarySearchType_UpperBound);
|
|
DN_UT_Assert(&result, !search.found);
|
|
DN_UT_Assert(&result, search.index == 4);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Search array with duplicate items")) {
|
|
DN_U32 array[] = {1, 1, 2, 2, 3};
|
|
DN_BinarySearchResult search = {};
|
|
|
|
// NOTE: Match =============================================================================
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 0U /*find*/, DN_BinarySearchType_Match);
|
|
DN_UT_Assert(&result, !search.found);
|
|
DN_UT_Assert(&result, search.index == 0);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 1U /*find*/, DN_BinarySearchType_Match);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 0);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 2U /*find*/, DN_BinarySearchType_Match);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 2);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 3U /*find*/, DN_BinarySearchType_Match);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 4);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 4U /*find*/, DN_BinarySearchType_Match);
|
|
DN_UT_Assert(&result, !search.found);
|
|
DN_UT_Assert(&result, search.index == 5);
|
|
|
|
// NOTE: Lower bound =======================================================================
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 0U /*find*/, DN_BinarySearchType_LowerBound);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 0);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 1U /*find*/, DN_BinarySearchType_LowerBound);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 0);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 2U /*find*/, DN_BinarySearchType_LowerBound);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 2);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 3U /*find*/, DN_BinarySearchType_LowerBound);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 4);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 4U /*find*/, DN_BinarySearchType_LowerBound);
|
|
DN_UT_Assert(&result, !search.found);
|
|
DN_UT_Assert(&result, search.index == 5);
|
|
|
|
// NOTE: Upper bound =======================================================================
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 0U /*find*/, DN_BinarySearchType_UpperBound);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 0);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 1U /*find*/, DN_BinarySearchType_UpperBound);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 2);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 2U /*find*/, DN_BinarySearchType_UpperBound);
|
|
DN_UT_Assert(&result, search.found);
|
|
DN_UT_Assert(&result, search.index == 4);
|
|
|
|
search = DN_BinarySearch<DN_U32>(array, DN_ArrayCountU(array), 3U /*find*/, DN_BinarySearchType_UpperBound);
|
|
DN_UT_Assert(&result, !search.found);
|
|
DN_UT_Assert(&result, search.index == 5);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
#endif // DN_H_WITH_HELPERS
|
|
|
|
static DN_UTCore DN_TST_BaseDSMap()
|
|
{
|
|
DN_UTCore result = DN_UT_Init();
|
|
DN_UT_LogF(&result, "DN_DSMap\n");
|
|
{
|
|
DN_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
{
|
|
DN_MemList mem = DN_MemListFromVMem(0, 0, DN_MemFlags_Nil);
|
|
DN_Arena arena = DN_ArenaFromMemList(&mem);
|
|
DN_U32 const MAP_SIZE = 64;
|
|
DN_DSMap<uint64_t> map = DN_DSMapInit<uint64_t>(&arena, MAP_SIZE, DN_DSMapFlags_Nil);
|
|
DN_DEFER
|
|
{
|
|
DN_DSMapDeinit(&map, DN_ZMem_Yes);
|
|
};
|
|
|
|
for (DN_UT_Test(&result, "Find non-existent value")) {
|
|
DN_DSMapResult<uint64_t> find = DN_DSMapFindKeyStr8(&map, DN_Str8Lit("Foo"));
|
|
DN_UT_Assert(&result, !find.found);
|
|
DN_UT_Assert(&result, map.size == MAP_SIZE);
|
|
DN_UT_Assert(&result, map.initial_size == MAP_SIZE);
|
|
DN_UT_Assert(&result, map.occupied == 1 /*Sentinel*/);
|
|
}
|
|
|
|
DN_DSMapKey key = DN_DSMapKeyCStr8(&map, "Bar");
|
|
for (DN_UT_Test(&result, "Insert value and lookup")) {
|
|
uint64_t desired_value = 0xF00BAA;
|
|
uint64_t *slot_value = DN_DSMapSet(&map, key, desired_value).value;
|
|
DN_UT_Assert(&result, slot_value);
|
|
DN_UT_Assert(&result, map.size == MAP_SIZE);
|
|
DN_UT_Assert(&result, map.initial_size == MAP_SIZE);
|
|
DN_UT_Assert(&result, map.occupied == 2);
|
|
|
|
uint64_t *value = DN_DSMapFind(&map, key).value;
|
|
DN_UT_Assert(&result, value);
|
|
DN_UT_Assert(&result, *value == desired_value);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Remove key")) {
|
|
DN_DSMapErase(&map, key);
|
|
DN_UT_Assert(&result, map.size == MAP_SIZE);
|
|
DN_UT_Assert(&result, map.initial_size == MAP_SIZE);
|
|
DN_UT_Assert(&result, map.occupied == 1 /*Sentinel*/);
|
|
}
|
|
}
|
|
|
|
enum DSMapTestType
|
|
{
|
|
DSMapTestType_Set,
|
|
DSMapTestType_MakeSlot,
|
|
DSMapTestType_Count
|
|
};
|
|
|
|
for (int result_type = 0; result_type < DSMapTestType_Count; result_type++) {
|
|
DN_Str8 prefix = {};
|
|
switch (result_type) {
|
|
case DSMapTestType_Set: prefix = DN_Str8Lit("Set"); break;
|
|
case DSMapTestType_MakeSlot: prefix = DN_Str8Lit("Make slot"); break;
|
|
}
|
|
|
|
DN_MemList mem = DN_MemListFromVMem(0, 0, DN_MemFlags_Nil);
|
|
DN_Arena arena = DN_ArenaFromMemList(&mem);
|
|
DN_U32 const MAP_SIZE = 64;
|
|
DN_DSMap<uint64_t> map = DN_DSMapInit<uint64_t>(&arena, MAP_SIZE, DN_DSMapFlags_Nil);
|
|
DN_DEFER
|
|
{
|
|
DN_DSMapDeinit(&map, DN_ZMem_Yes);
|
|
};
|
|
|
|
for (DN_UT_Test(&result, "%.*s: Test growing", DN_Str8PrintFmt(prefix))) {
|
|
uint64_t map_start_size = map.size;
|
|
uint64_t value = 0;
|
|
uint64_t grow_threshold = map_start_size * 3 / 4;
|
|
for (; map.occupied != grow_threshold; value++) {
|
|
DN_DSMapKey key = DN_DSMapKeyU64(&map, value);
|
|
DN_UT_Assert(&result, !DN_DSMapFind<uint64_t>(&map, key).found);
|
|
DN_DSMapResult<uint64_t> make_result = {};
|
|
if (result_type == DSMapTestType_Set)
|
|
make_result = DN_DSMapSet(&map, key, value);
|
|
else
|
|
make_result = DN_DSMapMake(&map, key);
|
|
DN_UT_Assert(&result, !make_result.found);
|
|
DN_UT_Assert(&result, DN_DSMapFind<uint64_t>(&map, key).value);
|
|
}
|
|
DN_UT_Assert(&result, map.initial_size == MAP_SIZE);
|
|
DN_UT_Assert(&result, map.size == map_start_size);
|
|
DN_UT_Assert(&result, map.occupied == 1 /*Sentinel*/ + value);
|
|
|
|
{ // NOTE: One more item should cause the table to grow by 2x
|
|
DN_DSMapKey key = DN_DSMapKeyU64(&map, value);
|
|
DN_DSMapResult<uint64_t> make_result = {};
|
|
if (result_type == DSMapTestType_Set)
|
|
make_result = DN_DSMapSet(&map, key, value);
|
|
else
|
|
make_result = DN_DSMapMake(&map, key);
|
|
|
|
value++;
|
|
DN_UT_Assert(&result, !make_result.found);
|
|
DN_UT_Assert(&result, map.size == map_start_size * 2);
|
|
DN_UT_Assert(&result, map.initial_size == MAP_SIZE);
|
|
DN_UT_Assert(&result, map.occupied == 1 /*Sentinel*/ + value);
|
|
}
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "%.*s: Check the sentinel is present", DN_Str8PrintFmt(prefix))) {
|
|
DN_DSMapSlot<uint64_t> NIL_SLOT = {};
|
|
DN_DSMapSlot<uint64_t> sentinel = map.slots[DN_DS_MAP_SENTINEL_SLOT];
|
|
DN_UT_Assert(&result, DN_Memcmp(&sentinel, &NIL_SLOT, sizeof(NIL_SLOT)) == 0);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "%.*s: Recheck all the hash tables values after growing", DN_Str8PrintFmt(prefix))) {
|
|
for (uint64_t index = 1 /*Sentinel*/; index < map.occupied; index++) {
|
|
DN_DSMapSlot<uint64_t> const *slot = map.slots + index;
|
|
|
|
// NOTE: Validate each slot value
|
|
uint64_t value_result = index - 1;
|
|
DN_DSMapKey key = DN_DSMapKeyU64(&map, value_result);
|
|
DN_UT_Assert(&result, DN_DSMapKeyEquals(slot->key, key));
|
|
if (result_type == DSMapTestType_Set)
|
|
DN_UT_Assert(&result, slot->value == value_result);
|
|
else
|
|
DN_UT_Assert(&result, slot->value == 0); // NOTE: Make slot does not set the key so should be 0
|
|
DN_UT_Assert(&result, slot->key.hash == DN_DSMapHash(&map, slot->key));
|
|
|
|
// NOTE: Check the reverse lookup is correct
|
|
DN_DSMapResult<uint64_t> check = DN_DSMapFind(&map, slot->key);
|
|
DN_UT_Assert(&result, slot->value == *check.value);
|
|
}
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "%.*s: Test shrinking", DN_Str8PrintFmt(prefix))) {
|
|
uint64_t start_map_size = map.size;
|
|
uint64_t start_map_occupied = map.occupied;
|
|
uint64_t value = 0;
|
|
uint64_t shrink_threshold = map.size * 1 / 4;
|
|
for (; map.occupied != shrink_threshold; value++) {
|
|
DN_DSMapKey key = DN_DSMapKeyU64(&map, value);
|
|
DN_UT_Assert(&result, DN_DSMapFind<uint64_t>(&map, key).found);
|
|
DN_DSMapErase(&map, key);
|
|
DN_UT_Assert(&result, !DN_DSMapFind<uint64_t>(&map, key).found);
|
|
}
|
|
DN_UT_Assert(&result, map.size == start_map_size);
|
|
DN_UT_Assert(&result, map.occupied == start_map_occupied - value);
|
|
|
|
{ // NOTE: One more item should cause the table to shrink by 2x
|
|
DN_DSMapKey key = DN_DSMapKeyU64(&map, value);
|
|
DN_DSMapErase(&map, key);
|
|
value++;
|
|
|
|
DN_UT_Assert(&result, map.size == start_map_size / 2);
|
|
DN_UT_Assert(&result, map.occupied == start_map_occupied - value);
|
|
}
|
|
|
|
{ // NOTE: Check the sentinel is present
|
|
DN_DSMapSlot<uint64_t> NIL_SLOT = {};
|
|
DN_DSMapSlot<uint64_t> sentinel = map.slots[DN_DS_MAP_SENTINEL_SLOT];
|
|
DN_UT_Assert(&result, DN_Memcmp(&sentinel, &NIL_SLOT, sizeof(NIL_SLOT)) == 0);
|
|
}
|
|
|
|
// NOTE: Recheck all the hash table values after shrinking
|
|
for (uint64_t index = 1 /*Sentinel*/; index < map.occupied; index++) {
|
|
// NOTE: Generate the key
|
|
uint64_t value_result = value + (index - 1);
|
|
DN_DSMapKey key = DN_DSMapKeyU64(&map, value_result);
|
|
|
|
// NOTE: Validate each slot value
|
|
DN_DSMapResult<uint64_t> find_result = DN_DSMapFind(&map, key);
|
|
DN_UT_Assert(&result, find_result.value);
|
|
DN_UT_Assert(&result, find_result.slot->key == key);
|
|
if (result_type == DSMapTestType_Set)
|
|
DN_UT_Assert(&result, *find_result.value == value_result);
|
|
else
|
|
DN_UT_Assert(&result, *find_result.value == 0); // NOTE: Make slot does not set the key so should be 0
|
|
DN_UT_Assert(&result, find_result.slot->key.hash == DN_DSMapHash(&map, find_result.slot->key));
|
|
|
|
// NOTE: Check the reverse lookup is correct
|
|
DN_DSMapResult<uint64_t> check = DN_DSMapFind(&map, find_result.slot->key);
|
|
DN_UT_Assert(&result, *find_result.value == *check.value);
|
|
}
|
|
|
|
for (; map.occupied != 1; value++) { // NOTE: Remove all items from the table
|
|
DN_DSMapKey key = DN_DSMapKeyU64(&map, value);
|
|
DN_UT_Assert(&result, DN_DSMapFind<uint64_t>(&map, key).found);
|
|
DN_DSMapErase(&map, key);
|
|
DN_UT_Assert(&result, !DN_DSMapFind<uint64_t>(&map, key).found);
|
|
}
|
|
DN_UT_Assert(&result, map.initial_size == MAP_SIZE);
|
|
DN_UT_Assert(&result, map.size == map.initial_size);
|
|
DN_UT_Assert(&result, map.occupied == 1 /*Sentinel*/);
|
|
}
|
|
}
|
|
DN_TCScratchEnd(&scratch);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static DN_UTCore DN_TST_BaseIArray()
|
|
{
|
|
DN_UTCore result = DN_UT_Init();
|
|
DN_UT_LogF(&result, "DN_IArray\n");
|
|
{
|
|
struct CustomArray
|
|
{
|
|
int *data;
|
|
DN_USize size;
|
|
DN_USize max;
|
|
};
|
|
|
|
int array_buffer[16];
|
|
CustomArray array = {};
|
|
array.data = array_buffer;
|
|
array.max = DN_ArrayCountU(array_buffer);
|
|
|
|
for (DN_UT_Test(&result, "Make item")) {
|
|
int *item = DN_IArrayMake(&array, DN_ZMem_Yes);
|
|
DN_UT_Assert(&result, item && array.size == 1);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static DN_UTCore DN_TST_BaseCArray2()
|
|
{
|
|
DN_UTCore result = DN_UT_Init();
|
|
DN_UT_LogF(&result, "DN_CArray2\n");
|
|
{
|
|
for (DN_UT_Test(&result, "Positive count, middle of array, stable erase")) {
|
|
int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
|
|
DN_USize size = 10;
|
|
DN_ArrayEraseResult erase = DN_CArrayEraseRange(arr, &size, sizeof(arr[0]), 3, 2, DN_ArrayErase_Stable);
|
|
int expected[] = {0, 1, 2, 5, 6, 7, 8, 9};
|
|
DN_UT_Assert(&result, erase.items_erased == 2);
|
|
DN_UT_Assert(&result, erase.it_index == 3);
|
|
DN_UT_Assert(&result, size == 8);
|
|
DN_UT_Assert(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Negative count, middle of array, stable erase")) {
|
|
int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
|
|
DN_USize size = 10;
|
|
DN_ArrayEraseResult erase = DN_CArrayEraseRange(arr, &size, sizeof(arr[0]), 5, -3, DN_ArrayErase_Stable);
|
|
int expected[] = {0, 1, 2, 6, 7, 8, 9};
|
|
DN_UT_Assert(&result, erase.items_erased == 3);
|
|
DN_UT_Assert(&result, erase.it_index == 3);
|
|
DN_UT_Assert(&result, size == 7);
|
|
DN_UT_Assert(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "count = -1, stable erase")) {
|
|
int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
|
|
DN_USize size = 10;
|
|
DN_ArrayEraseResult erase = DN_CArrayEraseRange(arr, &size, sizeof(arr[0]), 5, -1, DN_ArrayErase_Stable);
|
|
int expected[] = {0, 1, 2, 3, 4, 6, 7, 8, 9};
|
|
DN_UT_Assert(&result, erase.items_erased == 1);
|
|
DN_UT_Assert(&result, erase.it_index == 5);
|
|
DN_UT_Assert(&result, size == 9);
|
|
DN_UT_Assert(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Positive count, unstable erase")) {
|
|
int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
|
|
DN_USize size = 10;
|
|
DN_ArrayEraseResult erase = DN_CArrayEraseRange(arr, &size, sizeof(arr[0]), 3, 2, DN_ArrayErase_Unstable);
|
|
int expected[] = {0, 1, 2, 8, 9, 5, 6, 7};
|
|
DN_UT_Assert(&result, erase.items_erased == 2);
|
|
DN_UT_Assert(&result, erase.it_index == 3);
|
|
DN_UT_Assert(&result, size == 8);
|
|
DN_UT_Assert(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Negative count, unstable erase")) {
|
|
int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
|
|
DN_USize size = 10;
|
|
DN_ArrayEraseResult erase = DN_CArrayEraseRange(arr, &size, sizeof(arr[0]), 5, -3, DN_ArrayErase_Unstable);
|
|
int expected[] = {0, 1, 2, 7, 8, 9, 6};
|
|
DN_UT_Assert(&result, erase.items_erased == 3);
|
|
DN_UT_Assert(&result, erase.it_index == 3);
|
|
DN_UT_Assert(&result, size == 7);
|
|
DN_UT_Assert(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Edge case - begin_index at start, negative count")) {
|
|
int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
|
|
DN_USize size = 10;
|
|
DN_ArrayEraseResult erase = DN_CArrayEraseRange(arr, &size, sizeof(arr[0]), 0, -2, DN_ArrayErase_Stable);
|
|
int expected[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
|
|
DN_UT_Assert(&result, erase.items_erased == 1);
|
|
DN_UT_Assert(&result, erase.it_index == 0);
|
|
DN_UT_Assert(&result, size == 9);
|
|
DN_UT_Assert(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Edge case - begin_index at end, positive count")) {
|
|
int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
|
|
DN_USize size = 10;
|
|
DN_ArrayEraseResult erase = DN_CArrayEraseRange(arr, &size, sizeof(arr[0]), 9, 2, DN_ArrayErase_Stable);
|
|
int expected[] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
|
|
DN_UT_Assert(&result, erase.items_erased == 1);
|
|
DN_UT_Assert(&result, erase.it_index == 9);
|
|
DN_UT_Assert(&result, size == 9);
|
|
DN_UT_Assert(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Invalid input - count = 0")) {
|
|
int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
|
|
DN_USize size = 10;
|
|
DN_ArrayEraseResult erase = DN_CArrayEraseRange(arr, &size, sizeof(arr[0]), 5, 0, DN_ArrayErase_Stable);
|
|
int expected[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
|
|
DN_UT_Assert(&result, erase.items_erased == 0);
|
|
DN_UT_Assert(&result, erase.it_index == 0);
|
|
DN_UT_Assert(&result, size == 10);
|
|
DN_UT_Assert(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Invalid input - null data")) {
|
|
DN_USize size = 10;
|
|
DN_ArrayEraseResult erase = DN_CArrayEraseRange(nullptr, &size, sizeof(int), 5, 2, DN_ArrayErase_Stable);
|
|
DN_UT_Assert(&result, erase.items_erased == 0);
|
|
DN_UT_Assert(&result, erase.it_index == 0);
|
|
DN_UT_Assert(&result, size == 10);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Invalid input - null size")) {
|
|
int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
|
|
DN_ArrayEraseResult erase = DN_CArrayEraseRange(arr, NULL, sizeof(arr[0]), 5, 2, DN_ArrayErase_Stable);
|
|
DN_UT_Assert(&result, erase.items_erased == 0);
|
|
DN_UT_Assert(&result, erase.it_index == 0);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Invalid input - empty array")) {
|
|
int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
|
|
DN_USize size = 0;
|
|
DN_ArrayEraseResult erase = DN_CArrayEraseRange(arr, &size, sizeof(arr[0]), 5, 2, DN_ArrayErase_Stable);
|
|
DN_UT_Assert(&result, erase.items_erased == 0);
|
|
DN_UT_Assert(&result, erase.it_index == 0);
|
|
DN_UT_Assert(&result, size == 0);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Out-of-bounds begin_index")) {
|
|
int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
|
|
DN_USize size = 10;
|
|
DN_ArrayEraseResult erase = DN_CArrayEraseRange(arr, &size, sizeof(arr[0]), 15, 2, DN_ArrayErase_Stable);
|
|
int expected[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
|
|
DN_UT_Assert(&result, erase.items_erased == 0);
|
|
DN_UT_Assert(&result, erase.it_index == 10);
|
|
DN_UT_Assert(&result, size == 10);
|
|
DN_UT_Assert(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static DN_UTCore DN_TST_BaseVArray()
|
|
{
|
|
DN_UTCore result = DN_UT_Init();
|
|
DN_UT_LogF(&result, "DN_VArray\n");
|
|
{
|
|
{
|
|
DN_VArray<DN_U32> array = DN_OS_VArrayInitByteSize<DN_U32>(DN_Kilobytes(64));
|
|
DN_DEFER
|
|
{
|
|
DN_OS_VArrayDeinit(&array);
|
|
};
|
|
|
|
for (DN_UT_Test(&result, "Test adding an array of items to the array")) {
|
|
DN_U32 array_literal[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
|
|
DN_OS_VArrayAddArray<DN_U32>(&array, array_literal, DN_ArrayCountU(array_literal));
|
|
DN_UT_Assert(&result, array.size == DN_ArrayCountU(array_literal));
|
|
DN_UT_Assert(&result, DN_Memcmp(array.data, array_literal, DN_ArrayCountU(array_literal) * sizeof(array_literal[0])) == 0);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Test stable erase, 1 item, the '2' value from the array")) {
|
|
DN_OS_VArrayEraseRange(&array, 2 /*begin_index*/, 1 /*count*/, DN_ArrayErase_Stable);
|
|
DN_U32 array_literal[] = {0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
|
|
DN_UT_Assert(&result, array.size == DN_ArrayCountU(array_literal));
|
|
DN_UT_Assert(&result, DN_Memcmp(array.data, array_literal, DN_ArrayCountU(array_literal) * sizeof(array_literal[0])) == 0);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Test unstable erase, 1 item, the '1' value from the array")) {
|
|
DN_OS_VArrayEraseRange(&array, 1 /*begin_index*/, 1 /*count*/, DN_ArrayErase_Unstable);
|
|
DN_U32 array_literal[] = {0, 15, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14};
|
|
DN_UT_Assert(&result, array.size == DN_ArrayCountU(array_literal));
|
|
DN_UT_Assert(&result, DN_Memcmp(array.data, array_literal, DN_ArrayCountU(array_literal) * sizeof(array_literal[0])) == 0);
|
|
}
|
|
|
|
DN_ArrayErase erase_enums[] = {DN_ArrayErase_Stable, DN_ArrayErase_Unstable};
|
|
for (DN_UT_Test(&result, "Test un/stable erase, OOB")) {
|
|
for (DN_ArrayErase erase : erase_enums) {
|
|
DN_U32 array_literal[] = {0, 15, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14};
|
|
DN_OS_VArrayEraseRange(&array, DN_ArrayCountU(array_literal) /*begin_index*/, DN_ArrayCountU(array_literal) + 100 /*count*/, erase);
|
|
DN_UT_Assert(&result, array.size == DN_ArrayCountU(array_literal));
|
|
DN_UT_Assert(&result, DN_Memcmp(array.data, array_literal, DN_ArrayCountU(array_literal) * sizeof(array_literal[0])) == 0);
|
|
}
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Test flipped begin/end index stable erase, 2 items, the '15, 3' value from the array")) {
|
|
DN_OS_VArrayEraseRange(&array, 2 /*begin_index*/, -2 /*count*/, DN_ArrayErase_Stable);
|
|
DN_U32 array_literal[] = {0, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14};
|
|
DN_UT_Assert(&result, array.size == DN_ArrayCountU(array_literal));
|
|
DN_UT_Assert(&result, DN_Memcmp(array.data, array_literal, DN_ArrayCountU(array_literal) * sizeof(array_literal[0])) == 0);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Test flipped begin/end index unstable erase, 2 items, the '4, 5' value from the array")) {
|
|
DN_OS_VArrayEraseRange(&array, 2 /*begin_index*/, -2 /*count*/, DN_ArrayErase_Unstable);
|
|
DN_U32 array_literal[] = {0, 13, 14, 6, 7, 8, 9, 10, 11, 12};
|
|
DN_UT_Assert(&result, array.size == DN_ArrayCountU(array_literal));
|
|
DN_UT_Assert(&result, DN_Memcmp(array.data, array_literal, DN_ArrayCountU(array_literal) * sizeof(array_literal[0])) == 0);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Test stable erase range, 2+1 (oob) item, the '13, 14, +1 OOB' value from the array")) {
|
|
DN_OS_VArrayEraseRange(&array, 8 /*begin_index*/, 3 /*count*/, DN_ArrayErase_Stable);
|
|
DN_U32 array_literal[] = {0, 13, 14, 6, 7, 8, 9, 10};
|
|
DN_UT_Assert(&result, array.size == DN_ArrayCountU(array_literal));
|
|
DN_UT_Assert(&result, DN_Memcmp(array.data, array_literal, DN_ArrayCountU(array_literal) * sizeof(array_literal[0])) == 0);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Test unstable erase range, 3+1 (oob) item, the '11, 12, +1 OOB' value from the array")) {
|
|
DN_OS_VArrayEraseRange(&array, 6 /*begin_index*/, 3 /*count*/, DN_ArrayErase_Unstable);
|
|
DN_U32 array_literal[] = {0, 13, 14, 6, 7, 8};
|
|
DN_UT_Assert(&result, array.size == DN_ArrayCountU(array_literal));
|
|
DN_UT_Assert(&result, DN_Memcmp(array.data, array_literal, DN_ArrayCountU(array_literal) * sizeof(array_literal[0])) == 0);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Test stable erase -overflow OOB, erasing the '0, 13' value from the array")) {
|
|
DN_OS_VArrayEraseRange(&array, 1 /*begin_index*/, -DN_ISIZE_MAX /*count*/, DN_ArrayErase_Stable);
|
|
DN_U32 array_literal[] = {14, 6, 7, 8};
|
|
DN_UT_Assert(&result, array.size == DN_ArrayCountU(array_literal));
|
|
DN_UT_Assert(&result, DN_Memcmp(array.data, array_literal, DN_ArrayCountU(array_literal) * sizeof(array_literal[0])) == 0);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Test unstable erase +overflow OOB, erasing the '7, 8' value from the array")) {
|
|
DN_OS_VArrayEraseRange(&array, 2 /*begin_index*/, DN_ISIZE_MAX /*count*/, DN_ArrayErase_Unstable);
|
|
DN_U32 array_literal[] = {14, 6};
|
|
DN_UT_Assert(&result, array.size == DN_ArrayCountU(array_literal));
|
|
DN_UT_Assert(&result, DN_Memcmp(array.data, array_literal, DN_ArrayCountU(array_literal) * sizeof(array_literal[0])) == 0);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Test adding an array of items after erase")) {
|
|
DN_U32 array_literal[] = {0, 1, 2, 3};
|
|
DN_OS_VArrayAddArray<DN_U32>(&array, array_literal, DN_ArrayCountU(array_literal));
|
|
|
|
DN_U32 expected_literal[] = {14, 6, 0, 1, 2, 3};
|
|
DN_UT_Assert(&result, array.size == DN_ArrayCountU(expected_literal));
|
|
DN_UT_Assert(&result, DN_Memcmp(array.data, expected_literal, DN_ArrayCountU(expected_literal) * sizeof(expected_literal[0])) == 0);
|
|
}
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Array of unaligned objects are contiguously laid out in memory")) {
|
|
// NOTE: Since we allocate from a virtual memory block, each time
|
|
// we request memory from the block we can demand some alignment
|
|
// on the returned pointer from the memory block. If there's
|
|
// additional alignment done in that function then we can no
|
|
// longer access the items in the array contiguously leading to
|
|
// confusing memory "corruption" errors.
|
|
//
|
|
// This result makes sure that the unaligned objects are allocated
|
|
// from the memory block (and hence the array) contiguously
|
|
// when the size of the object is not aligned with the required
|
|
// alignment of the object.
|
|
DN_MSVC_WARNING_PUSH
|
|
DN_MSVC_WARNING_DISABLE(4324) // warning C4324: 'TestVArray::UnalignedObject': structure was padded due to alignment specifier
|
|
|
|
struct alignas(8) UnalignedObject
|
|
{
|
|
char data[511];
|
|
};
|
|
|
|
DN_MSVC_WARNING_POP
|
|
|
|
DN_VArray<UnalignedObject> array = DN_OS_VArrayInitByteSize<UnalignedObject>(DN_Kilobytes(64));
|
|
DN_DEFER
|
|
{
|
|
DN_OS_VArrayDeinit(&array);
|
|
};
|
|
|
|
// NOTE: Verify that the items returned from the data array are
|
|
// contiguous in memory.
|
|
UnalignedObject *make_item_a = DN_OS_VArrayMakeArray(&array, 1, DN_ZMem_Yes);
|
|
UnalignedObject *make_item_b = DN_OS_VArrayMakeArray(&array, 1, DN_ZMem_Yes);
|
|
DN_Memset(make_item_a->data, 'a', sizeof(make_item_a->data));
|
|
DN_Memset(make_item_b->data, 'b', sizeof(make_item_b->data));
|
|
DN_UT_Assert(&result, (uintptr_t)make_item_b == (uintptr_t)(make_item_a + 1));
|
|
|
|
// NOTE: Verify that accessing the items from the data array yield
|
|
// the same object.
|
|
DN_UT_Assert(&result, array.size == 2);
|
|
UnalignedObject *data_item_a = array.data + 0;
|
|
UnalignedObject *data_item_b = array.data + 1;
|
|
DN_UT_Assert(&result, (uintptr_t)data_item_b == (uintptr_t)(data_item_a + 1));
|
|
DN_UT_Assert(&result, (uintptr_t)data_item_b == (uintptr_t)(make_item_a + 1));
|
|
DN_UT_Assert(&result, (uintptr_t)data_item_b == (uintptr_t)make_item_b);
|
|
|
|
for (DN_USize i = 0; i < sizeof(data_item_a->data); i++)
|
|
DN_UT_Assert(&result, data_item_a->data[i] == 'a');
|
|
|
|
for (DN_USize i = 0; i < sizeof(data_item_b->data); i++)
|
|
DN_UT_Assert(&result, data_item_b->data[i] == 'b');
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#if defined(DN_UNIT_TESTS_WITH_KECCAK)
|
|
DN_GCC_WARNING_PUSH
|
|
DN_GCC_WARNING_DISABLE(-Wunused-parameter)
|
|
DN_GCC_WARNING_DISABLE(-Wsign-compare)
|
|
|
|
DN_MSVC_WARNING_PUSH
|
|
DN_MSVC_WARNING_DISABLE(4244)
|
|
DN_MSVC_WARNING_DISABLE(4100)
|
|
DN_MSVC_WARNING_DISABLE(6385)
|
|
// NOTE: Keccak Reference Implementation
|
|
// A very compact Keccak implementation taken from the reference implementation
|
|
// repository
|
|
// https://github.com/XKCP/XKCP/blob/master/Standalone/CompactFIPS202/C/Keccak-more-compact.c
|
|
#define FOR(i, n) for (i = 0; i < n; ++i)
|
|
void DN_RefImpl_Keccak_(int r, int c, const uint8_t *in, uint64_t inLen, uint8_t sfx, uint8_t *out, uint64_t outLen);
|
|
|
|
void DN_RefImpl_FIPS202_SHAKE128_(const uint8_t *in, uint64_t inLen, uint8_t *out, uint64_t outLen)
|
|
{
|
|
DN_RefImpl_Keccak_(1344, 256, in, inLen, 0x1F, out, outLen);
|
|
}
|
|
|
|
void DN_RefImpl_FIPS202_SHAKE256_(const uint8_t *in, uint64_t inLen, uint8_t *out, uint64_t outLen)
|
|
{
|
|
DN_RefImpl_Keccak_(1088, 512, in, inLen, 0x1F, out, outLen);
|
|
}
|
|
|
|
void DN_RefImpl_FIPS202_SHA3_224_(const uint8_t *in, uint64_t inLen, uint8_t *out)
|
|
{
|
|
DN_RefImpl_Keccak_(1152, 448, in, inLen, 0x06, out, 28);
|
|
}
|
|
|
|
void DN_RefImpl_FIPS202_SHA3_256_(const uint8_t *in, uint64_t inLen, uint8_t *out)
|
|
{
|
|
DN_RefImpl_Keccak_(1088, 512, in, inLen, 0x06, out, 32);
|
|
}
|
|
|
|
void DN_RefImpl_FIPS202_SHA3_384_(const uint8_t *in, uint64_t inLen, uint8_t *out)
|
|
{
|
|
DN_RefImpl_Keccak_(832, 768, in, inLen, 0x06, out, 48);
|
|
}
|
|
|
|
void DN_RefImpl_FIPS202_SHA3_512_(const uint8_t *in, uint64_t inLen, uint8_t *out)
|
|
{
|
|
DN_RefImpl_Keccak_(576, 1024, in, inLen, 0x06, out, 64);
|
|
}
|
|
|
|
int DN_RefImpl_LFSR86540_(uint8_t *R)
|
|
{
|
|
(*R) = ((*R) << 1) ^ (((*R) & 0x80) ? 0x71 : 0);
|
|
return ((*R) & 2) >> 1;
|
|
}
|
|
|
|
#define ROL(a, o) ((((uint64_t)a) << o) ^ (((uint64_t)a) >> (64 - o)))
|
|
|
|
static uint64_t DN_RefImpl_load64_(const uint8_t *x)
|
|
{
|
|
int i;
|
|
uint64_t u = 0;
|
|
FOR(i, 8)
|
|
{
|
|
u <<= 8;
|
|
u |= x[7 - i];
|
|
}
|
|
return u;
|
|
}
|
|
|
|
static void DN_RefImpl_store64_(uint8_t *x, uint64_t u)
|
|
{
|
|
int i;
|
|
FOR(i, 8)
|
|
{
|
|
x[i] = u;
|
|
u >>= 8;
|
|
}
|
|
}
|
|
|
|
static void DN_RefImpl_xor64_(uint8_t *x, uint64_t u)
|
|
{
|
|
int i;
|
|
FOR(i, 8)
|
|
{
|
|
x[i] ^= u;
|
|
u >>= 8;
|
|
}
|
|
}
|
|
|
|
#define rL(x, y) DN_RefImpl_load64_((uint8_t *)s + 8 * (x + 5 * y))
|
|
#define wL(x, y, l) DN_RefImpl_store64_((uint8_t *)s + 8 * (x + 5 * y), l)
|
|
#define XL(x, y, l) DN_RefImpl_xor64_((uint8_t *)s + 8 * (x + 5 * y), l)
|
|
|
|
void DN_RefImpl_Keccak_F1600(void *s)
|
|
{
|
|
int r, x, y, i, j, Y;
|
|
uint8_t R = 0x01;
|
|
uint64_t C[5], D;
|
|
for (i = 0; i < 24; i++) {
|
|
/*??*/ FOR(x, 5) C[x] = rL(x, 0) ^ rL(x, 1) ^ rL(x, 2) ^ rL(x, 3) ^ rL(x, 4);
|
|
FOR(x, 5)
|
|
{
|
|
D = C[(x + 4) % 5] ^ ROL(C[(x + 1) % 5], 1);
|
|
FOR(y, 5)
|
|
XL(x, y, D);
|
|
}
|
|
/*????*/ x = 1;
|
|
y = r = 0;
|
|
D = rL(x, y);
|
|
FOR(j, 24)
|
|
{
|
|
r += j + 1;
|
|
Y = (2 * x + 3 * y) % 5;
|
|
x = y;
|
|
y = Y;
|
|
C[0] = rL(x, y);
|
|
wL(x, y, ROL(D, r % 64));
|
|
D = C[0];
|
|
}
|
|
/*??*/ FOR(y, 5)
|
|
{
|
|
FOR(x, 5)
|
|
C[x] = rL(x, y);
|
|
FOR(x, 5)
|
|
wL(x, y, C[x] ^ ((~C[(x + 1) % 5]) & C[(x + 2) % 5]));
|
|
}
|
|
/*??*/ FOR(j, 7) if (DN_RefImpl_LFSR86540_(&R)) XL(0, 0, (uint64_t)1 << ((1 << j) - 1));
|
|
}
|
|
}
|
|
|
|
void DN_RefImpl_Keccak_(int r, int c, const uint8_t *in, uint64_t inLen, uint8_t sfx, uint8_t *out, uint64_t outLen)
|
|
{
|
|
/*initialize*/ uint8_t s[200];
|
|
int R = r / 8;
|
|
int i, b = 0;
|
|
FOR(i, 200)
|
|
s[i] = 0;
|
|
/*absorb*/ while (inLen > 0) {
|
|
b = (inLen < R) ? inLen : R;
|
|
FOR(i, b)
|
|
s[i] ^= in[i];
|
|
in += b;
|
|
inLen -= b;
|
|
if (b == R) {
|
|
DN_RefImpl_Keccak_F1600(s);
|
|
b = 0;
|
|
}
|
|
}
|
|
/*pad*/ s[b] ^= sfx;
|
|
if ((sfx & 0x80) && (b == (R - 1)))
|
|
DN_RefImpl_Keccak_F1600(s);
|
|
s[R - 1] ^= 0x80;
|
|
DN_RefImpl_Keccak_F1600(s);
|
|
/*squeeze*/ while (outLen > 0) {
|
|
b = (outLen < R) ? outLen : R;
|
|
FOR(i, b)
|
|
out[i] = s[i];
|
|
out += b;
|
|
outLen -= b;
|
|
if (outLen > 0)
|
|
DN_RefImpl_Keccak_F1600(s);
|
|
}
|
|
}
|
|
|
|
#undef XL
|
|
#undef wL
|
|
#undef rL
|
|
#undef ROL
|
|
#undef FOR
|
|
DN_MSVC_WARNING_POP
|
|
DN_GCC_WARNING_POP
|
|
|
|
#define DN_SHA3_IMPLEMENTATION
|
|
// DN: Single header generator commented out => #include "../Standalone/dn_sha3.h"
|
|
#if !defined(DN_SHA3_H)
|
|
#define DN_SHA3_H
|
|
|
|
// NOTE: DN_Sha3 -- FIPS202 SHA3 + non-finalized SHA3 (aka. Keccak) hashing algorithms
|
|
//
|
|
// Overview
|
|
// Single header file implementation of the Keccak hashing algorithms from the Keccak and SHA3
|
|
// families (including the FIPS202 published algorithms and the non-finalized ones, i.e. the ones
|
|
// used in Ethereum and Monero which adopted SHA3 before it was finalized. The only difference
|
|
// between the 2 is a different delimited suffix).
|
|
//
|
|
// Configuration
|
|
// Define this in one and only one C++ file to enable the implementation code of the header file.
|
|
//
|
|
// #define DN_SHA3_IMPLEMENTATION
|
|
//
|
|
// License
|
|
// MIT License
|
|
//
|
|
// Copyright (c) 2021 github.com/doy-lee
|
|
//
|
|
// 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.
|
|
|
|
#include <stdint.h>
|
|
#if !defined(DN_SHA3Memcpy)
|
|
#include <string.h>
|
|
#define DN_SHA3Memcpy(dest, src, count) memcpy(dest, src, count)
|
|
#endif
|
|
|
|
#if !defined(DN_SHA3Memcmp)
|
|
#include <string.h>
|
|
#define DN_SHA3Memcmp(dest, src, count) memcmp(dest, src, count)
|
|
#endif
|
|
|
|
#if !defined(DN_SHA3Memset)
|
|
#include <string.h>
|
|
#define DN_SHA3Memset(dest, byte, count) memset(dest, byte, count)
|
|
#endif
|
|
|
|
#if !defined(DN_SHA3Assert)
|
|
#if defined(NDEBUG)
|
|
#define DN_SHA3Assert(expr)
|
|
#else
|
|
#define DN_SHA3Assert(expr) \
|
|
do { \
|
|
if (!(expr)) { \
|
|
(*(volatile int *)0) = 0; \
|
|
} \
|
|
} while (0)
|
|
#endif
|
|
#endif
|
|
|
|
typedef struct DN_SHA3U8x28 { char data[28]; } DN_SHA3U8x28; // 224 bit
|
|
typedef struct DN_SHA3U8x32 { char data[32]; } DN_SHA3U8x32; // 256 bit
|
|
typedef struct DN_SHA3U8x48 { char data[48]; } DN_SHA3U8x48; // 384 bit
|
|
typedef struct DN_SHA3U8x64 { char data[64]; } DN_SHA3U8x64; // 512 bit
|
|
typedef struct DN_SHA3Str8x56 { char data[(sizeof(DN_SHA3U8x28) * 2) + 1]; } DN_SHA3Str8x56;
|
|
typedef struct DN_SHA3Str8x64 { char data[(sizeof(DN_SHA3U8x32) * 2) + 1]; } DN_SHA3Str8x64;
|
|
typedef struct DN_SHA3Str8x96 { char data[(sizeof(DN_SHA3U8x48) * 2) + 1]; } DN_SHA3Str8x96;
|
|
typedef struct DN_SHA3Str8x128 { char data[(sizeof(DN_SHA3U8x64) * 2) + 1]; } DN_SHA3Str8x128;
|
|
|
|
#define DN_SHA3_LANE_SIZE_U64 5
|
|
typedef struct DN_SHA3State {
|
|
size_t hash_size_bits; // The size of the hash the context was initialised for in bits
|
|
size_t state_size; // The number of bytes written to the state
|
|
size_t absorb_size; // The amount of bytes to absorb/sponge in/from the state
|
|
uint8_t state[DN_SHA3_LANE_SIZE_U64 * DN_SHA3_LANE_SIZE_U64 * sizeof(uint64_t)];
|
|
char delimited_suffix; // The delimited suffix of the current hash
|
|
} DN_SHA3State;
|
|
|
|
enum DN_SHA3Family
|
|
{
|
|
DN_SHA3Family_SHA3, // FIPS 202 SHA3 (delimited suffix is 0x6)
|
|
DN_SHA3Family_Keccak, // Non-finalized SHA3 (only difference is delimited suffix of 0x1 instead of 0x6)
|
|
};
|
|
|
|
// hash_size_bits: Number of bits to hash to. Available sizes are 224, 256, 384 and 512.
|
|
DN_SHA3State DN_SHA3FamilyInit (DN_SHA3Family type, size_t hash_size_bits);
|
|
DN_SHA3State DN_SHA3FamilyInitSHA3 (size_t hash_size_bits);
|
|
DN_SHA3State DN_SHA3FamilyInitKeccak(size_t hash_size_bits);
|
|
void DN_SHA3FamilyUpdate (DN_SHA3State *sha3, void const *data, size_t data_size);
|
|
void DN_SHA3FamilyFinish (DN_SHA3State *sha3, void *dest, size_t dest_size);
|
|
void DN_SHA3FamilyHash (DN_SHA3Family type, size_t hash_size_bits, void const *src, size_t src_size, void *dest, int dest_size);
|
|
|
|
void DN_SHA3Hash224bPtr (void const *src, size_t src_size, void *dest, size_t dest_size);
|
|
DN_SHA3U8x28 DN_SHA3Hash224b (void const *src, size_t src_size);
|
|
void DN_SHA3Hash256bPtr (void const *src, size_t src_size, void *dest, size_t dest_size);
|
|
DN_SHA3U8x32 DN_SHA3Hash256b (void const *src, size_t src_size);
|
|
void DN_SHA3Hash384bPtr (void const *src, size_t src_size, void *dest, size_t dest_size);
|
|
DN_SHA3U8x48 DN_SHA3Hash384b (void const *src, size_t src_size);
|
|
void DN_SHA3Hash512bPtr (void const *src, size_t src_size, void *dest, size_t dest_size);
|
|
DN_SHA3U8x64 DN_SHA3Hash512b (void const *src, size_t src_size);
|
|
|
|
void DN_KeccakHash224bPtr (void const *src, size_t src_size, void *dest, size_t dest_size);
|
|
DN_SHA3U8x28 DN_KeccakHash224b (void const *src, size_t src_size);
|
|
void DN_KeccakHash256bPtr (void const *src, size_t src_size, void *dest, size_t dest_size);
|
|
DN_SHA3U8x32 DN_KeccakHash256b (void const *src, size_t src_size);
|
|
void DN_KeccakHash384bPtr (void const *src, size_t src_size, void *dest, size_t dest_size);
|
|
DN_SHA3U8x48 DN_KeccakHash384b (void const *src, size_t src_size);
|
|
void DN_KeccakHash512bPtr (void const *src, size_t src_size, void *dest, size_t dest_size);
|
|
DN_SHA3U8x64 DN_KeccakHash512b (void const *src, size_t src_size);
|
|
|
|
void DN_SHA3HexFromBytes (void const *src, uint64_t src_size, char *dest, uint64_t dest_size);
|
|
DN_SHA3Str8x56 DN_SHA3HexFromU8x28 (DN_SHA3U8x28 const *bytes);
|
|
DN_SHA3Str8x64 DN_SHA3HexFromU8x32 (DN_SHA3U8x32 const *bytes);
|
|
DN_SHA3Str8x96 DN_SHA3HexFromU8x48 (DN_SHA3U8x48 const *bytes);
|
|
DN_SHA3Str8x128 DN_SHA3HexFromU8x64 (DN_SHA3U8x64 const *bytes);
|
|
bool DN_SHA3U8x28Eq (DN_SHA3U8x28 const *a, DN_SHA3U8x28 const *b);
|
|
bool DN_SHA3U8x32Eq (DN_SHA3U8x32 const *a, DN_SHA3U8x32 const *b);
|
|
bool DN_SHA3U8x48Eq (DN_SHA3U8x48 const *a, DN_SHA3U8x48 const *b);
|
|
bool DN_SHA3U8x64Eq (DN_SHA3U8x64 const *a, DN_SHA3U8x64 const *b);
|
|
#endif // DN_SHA3_H
|
|
|
|
#if defined(DN_SHA3_IMPLEMENTATION)
|
|
uint64_t const DN_SHA3_ROUNDS[] = {
|
|
0x0000000000000001, 0x0000000000008082, 0x800000000000808A, 0x8000000080008000, 0x000000000000808B,
|
|
0x0000000080000001, 0x8000000080008081, 0x8000000000008009, 0x000000000000008A, 0x0000000000000088,
|
|
0x0000000080008009, 0x000000008000000A, 0x000000008000808B, 0x800000000000008B, 0x8000000000008089,
|
|
0x8000000000008003, 0x8000000000008002, 0x8000000000000080, 0x000000000000800A, 0x800000008000000A,
|
|
0x8000000080008081, 0x8000000000008080, 0x0000000080000001, 0x8000000080008008,
|
|
};
|
|
|
|
uint64_t const DN_SHA3_ROTATIONS[][5] =
|
|
{
|
|
{0, 36, 3, 41, 18},
|
|
{1, 44, 10, 45, 2},
|
|
{62, 6, 43, 15, 61},
|
|
{28, 55, 25, 21, 56},
|
|
{27, 20, 39, 8, 14},
|
|
};
|
|
|
|
#define DN_SHA3_ROL64(val, rotate) (((val) << (rotate)) | (((val) >> (64 - (rotate)))))
|
|
static void DN_SHA3FamilyPermute_(void *state)
|
|
{
|
|
uint64_t *lanes_u64 = (uint64_t *)state;
|
|
for (int round_index = 0; round_index < 24; round_index++) {
|
|
#define DN_SHA3_LANE_INDEX(x, y) ((x) + ((y) * DN_SHA3_LANE_SIZE_U64))
|
|
// ?? step
|
|
#if 1
|
|
uint64_t c[DN_SHA3_LANE_SIZE_U64];
|
|
for (int x = 0; x < DN_SHA3_LANE_SIZE_U64; x++)
|
|
c[x] = lanes_u64[DN_SHA3_LANE_INDEX(x, 0)] ^
|
|
lanes_u64[DN_SHA3_LANE_INDEX(x, 1)] ^
|
|
lanes_u64[DN_SHA3_LANE_INDEX(x, 2)] ^
|
|
lanes_u64[DN_SHA3_LANE_INDEX(x, 3)] ^
|
|
lanes_u64[DN_SHA3_LANE_INDEX(x, 4)];
|
|
|
|
uint64_t d[DN_SHA3_LANE_SIZE_U64];
|
|
for (int x = 0; x < DN_SHA3_LANE_SIZE_U64; x++)
|
|
d[x] = c[(x + 4) % DN_SHA3_LANE_SIZE_U64] ^ DN_SHA3_ROL64(c[(x + 1) % DN_SHA3_LANE_SIZE_U64], 1);
|
|
|
|
for (int y = 0; y < DN_SHA3_LANE_SIZE_U64; y++)
|
|
for (int x = 0; x < DN_SHA3_LANE_SIZE_U64; x++)
|
|
lanes_u64[DN_SHA3_LANE_INDEX(x, y)] ^= d[x];
|
|
#else
|
|
uint64_t c[5], d[5];
|
|
c[0] = lanes_u64[0 * 5 + 0] ^ lanes_u64[1 * 5 + 0] ^ lanes_u64[2 * 5 + 0] ^ lanes_u64[3 * 5 + 0] ^ lanes_u64[4 * 5 + 0];
|
|
c[1] = lanes_u64[0 * 5 + 1] ^ lanes_u64[1 * 5 + 1] ^ lanes_u64[2 * 5 + 1] ^ lanes_u64[3 * 5 + 1] ^ lanes_u64[4 * 5 + 1];
|
|
c[2] = lanes_u64[0 * 5 + 2] ^ lanes_u64[1 * 5 + 2] ^ lanes_u64[2 * 5 + 2] ^ lanes_u64[3 * 5 + 2] ^ lanes_u64[4 * 5 + 2];
|
|
c[3] = lanes_u64[0 * 5 + 3] ^ lanes_u64[1 * 5 + 3] ^ lanes_u64[2 * 5 + 3] ^ lanes_u64[3 * 5 + 3] ^ lanes_u64[4 * 5 + 3];
|
|
c[4] = lanes_u64[0 * 5 + 4] ^ lanes_u64[1 * 5 + 4] ^ lanes_u64[2 * 5 + 4] ^ lanes_u64[3 * 5 + 4] ^ lanes_u64[4 * 5 + 4];
|
|
|
|
d[0] = c[4] ^ DN_SHA3_ROL64(c[1], 1);
|
|
d[1] = c[0] ^ DN_SHA3_ROL64(c[2], 1);
|
|
d[2] = c[1] ^ DN_SHA3_ROL64(c[3], 1);
|
|
d[3] = c[2] ^ DN_SHA3_ROL64(c[4], 1);
|
|
d[4] = c[3] ^ DN_SHA3_ROL64(c[0], 1);
|
|
#endif
|
|
|
|
// ?? and ?? steps
|
|
uint64_t b[DN_SHA3_LANE_SIZE_U64 * DN_SHA3_LANE_SIZE_U64];
|
|
for (int y = 0; y < DN_SHA3_LANE_SIZE_U64; y++) {
|
|
for (int x = 0; x < DN_SHA3_LANE_SIZE_U64; x++) {
|
|
uint64_t lane = lanes_u64[DN_SHA3_LANE_INDEX(x, y)];
|
|
uint64_t rotate_count = DN_SHA3_ROTATIONS[x][y];
|
|
b[DN_SHA3_LANE_INDEX(y, (2 * x + 3 * y) % 5)] = DN_SHA3_ROL64(lane, rotate_count);
|
|
}
|
|
}
|
|
|
|
// ?? step
|
|
for (int y = 0; y < DN_SHA3_LANE_SIZE_U64; y++) {
|
|
for (int x = 0; x < DN_SHA3_LANE_SIZE_U64; x++) {
|
|
uint64_t rhs = ~b[DN_SHA3_LANE_INDEX((x + 1) % 5, y)] & b[DN_SHA3_LANE_INDEX((x + 2) % 5, y)];
|
|
|
|
lanes_u64[DN_SHA3_LANE_INDEX(x, y)] = b[DN_SHA3_LANE_INDEX(x, y)] ^ rhs;
|
|
}
|
|
}
|
|
|
|
// ?? step
|
|
lanes_u64[DN_SHA3_LANE_INDEX(0, 0)] ^= DN_SHA3_ROUNDS[round_index];
|
|
#undef DN_SHA3_LANE_INDEX
|
|
#undef DN_SHA3_ROL64
|
|
}
|
|
}
|
|
|
|
DN_SHA3State DN_SHA3FamilyInit(DN_SHA3Family type, size_t hash_size_bits)
|
|
{
|
|
DN_SHA3Assert(hash_size_bits == 224 ||
|
|
hash_size_bits == 256 ||
|
|
hash_size_bits == 384 ||
|
|
hash_size_bits == 512);
|
|
|
|
char const SHA3_DELIMITED_SUFFIX = 0x06;
|
|
char const KECCAK_DELIMITED_SUFFIX = 0x01;
|
|
size_t const bitrate = 1600 - (hash_size_bits * 2);
|
|
|
|
#if defined(__cplusplus)
|
|
DN_SHA3State result = {};
|
|
#else
|
|
DN_SHA3State result = {0};
|
|
#endif
|
|
result.hash_size_bits = hash_size_bits;
|
|
result.absorb_size = bitrate / 8;
|
|
result.delimited_suffix = type == DN_SHA3Family_SHA3 ? SHA3_DELIMITED_SUFFIX : KECCAK_DELIMITED_SUFFIX;
|
|
DN_SHA3Assert(bitrate + (hash_size_bits * 2) /*capacity*/ == 1600);
|
|
return result;
|
|
}
|
|
|
|
DN_SHA3State DN_SHA3FamilyInitSHA3(size_t hash_size_bits)
|
|
{
|
|
DN_SHA3State result = DN_SHA3FamilyInit(DN_SHA3Family_SHA3, hash_size_bits);
|
|
return result;
|
|
}
|
|
|
|
DN_SHA3State DN_SHA3FamilyInitKeccak(size_t hash_size_bits)
|
|
{
|
|
DN_SHA3State result = DN_SHA3FamilyInit(DN_SHA3Family_Keccak, hash_size_bits);
|
|
return result;
|
|
}
|
|
|
|
void DN_SHA3FamilyUpdate(DN_SHA3State *sha3, void const *data, size_t data_size)
|
|
{
|
|
uint8_t *state = sha3->state;
|
|
uint8_t const *ptr = (uint8_t *)data;
|
|
size_t ptr_size = data_size;
|
|
while (ptr_size > 0) {
|
|
size_t space = sha3->absorb_size - sha3->state_size;
|
|
int bytes_to_absorb = (int)(space < ptr_size ? space : ptr_size);
|
|
|
|
for (int index = 0; index < bytes_to_absorb; index++)
|
|
state[sha3->state_size + index] ^= ptr[index];
|
|
|
|
ptr += bytes_to_absorb;
|
|
sha3->state_size += bytes_to_absorb;
|
|
ptr_size -= bytes_to_absorb;
|
|
|
|
if (sha3->state_size >= sha3->absorb_size) {
|
|
DN_SHA3Assert(sha3->state_size == sha3->absorb_size);
|
|
DN_SHA3FamilyPermute_(state);
|
|
sha3->state_size = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void DN_SHA3FamilyFinish(DN_SHA3State *sha3, void *dest, size_t dest_size)
|
|
{
|
|
DN_SHA3Assert(dest_size >= (size_t)(sha3->hash_size_bits / 8));
|
|
|
|
// Sponge Finalization Step: Final padding bit
|
|
size_t const INDEX_OF_0X80_BYTE = sha3->absorb_size - 1;
|
|
size_t const delimited_suffix_index = sha3->state_size;
|
|
DN_SHA3Assert(delimited_suffix_index < sha3->absorb_size);
|
|
|
|
uint8_t *state = sha3->state;
|
|
state[delimited_suffix_index] ^= sha3->delimited_suffix;
|
|
|
|
// NOTE: In the reference implementation, it checks that if the
|
|
// delimited suffix is set to the padding bit (0x80), then we need to
|
|
// permute twice. Once for the delimited suffix, and a second time for
|
|
// the "padding" permute.
|
|
//
|
|
// However all standard algorithms either specify a 0x01, or 0x06, 0x04
|
|
// delimited suffix and so forth- so this case is never hit. We can omit
|
|
// this from the implementation here.
|
|
|
|
state[INDEX_OF_0X80_BYTE] ^= 0x80;
|
|
DN_SHA3FamilyPermute_(state);
|
|
|
|
// Squeeze Step: Squeeze bytes from the state into our hash
|
|
uint8_t *dest_u8 = (uint8_t *)dest;
|
|
size_t const squeeze_count = dest_size / sha3->absorb_size;
|
|
size_t squeeze_index = 0;
|
|
for (; squeeze_index < squeeze_count; squeeze_index++) {
|
|
if (squeeze_index)
|
|
DN_SHA3FamilyPermute_(state);
|
|
DN_SHA3Memcpy(dest_u8, state, sha3->absorb_size);
|
|
dest_u8 += sha3->absorb_size;
|
|
}
|
|
|
|
// Squeeze Finalisation Step: Remainder bytes in hash
|
|
size_t const remainder = dest_size % sha3->absorb_size;
|
|
if (remainder) {
|
|
if (squeeze_index)
|
|
DN_SHA3FamilyPermute_(state);
|
|
DN_SHA3Memcpy(dest_u8, state, remainder);
|
|
}
|
|
}
|
|
|
|
void DN_SHA3FamilyHash(DN_SHA3Family type, size_t hash_size_bits, void const *src, size_t src_size, void *dest, size_t dest_size)
|
|
{
|
|
DN_SHA3State state = DN_SHA3FamilyInit(type, hash_size_bits);
|
|
DN_SHA3FamilyUpdate(&state, src, src_size);
|
|
DN_SHA3FamilyFinish(&state, dest, dest_size);
|
|
}
|
|
|
|
void DN_SHA3Hash224bPtr(void const *src, size_t src_size, void *dest, size_t dest_size)
|
|
{
|
|
DN_SHA3FamilyHash(DN_SHA3Family_SHA3, /*hash_size_bits=*/ 224, src, src_size, dest, dest_size);
|
|
}
|
|
|
|
DN_SHA3U8x28 DN_SHA3Hash224b(void const *src, size_t src_size)
|
|
{
|
|
DN_SHA3U8x28 result = {};
|
|
DN_SHA3Hash224bPtr(src, src_size, result.data, sizeof(result.data));
|
|
return result;
|
|
}
|
|
|
|
void DN_SHA3Hash256bPtr(void const *src, size_t src_size, void *dest, size_t dest_size)
|
|
{
|
|
DN_SHA3FamilyHash(DN_SHA3Family_SHA3, /*hash_size_bits=*/ 256, src, src_size, dest, dest_size);
|
|
}
|
|
|
|
DN_SHA3U8x32 DN_SHA3Hash256b(void const *src, size_t src_size)
|
|
{
|
|
DN_SHA3U8x32 result = {};
|
|
DN_SHA3Hash256bPtr(src, src_size, result.data, sizeof(result.data));
|
|
return result;
|
|
}
|
|
|
|
void DN_SHA3Hash384bPtr(void const *src, size_t src_size, void *dest, size_t dest_size)
|
|
{
|
|
DN_SHA3FamilyHash(DN_SHA3Family_SHA3, /*hash_size_bits=*/ 384, src, src_size, dest, dest_size);
|
|
}
|
|
|
|
DN_SHA3U8x48 DN_SHA3Hash384b(void const *src, size_t src_size)
|
|
{
|
|
DN_SHA3U8x48 result = {};
|
|
DN_SHA3Hash384bPtr(src, src_size, result.data, sizeof(result.data));
|
|
return result;
|
|
}
|
|
|
|
void DN_SHA3Hash512bPtr(void const *src, size_t src_size, void *dest, size_t dest_size)
|
|
{
|
|
DN_SHA3FamilyHash(DN_SHA3Family_SHA3, /*hash_size_bits=*/ 512, src, src_size, dest, dest_size);
|
|
}
|
|
|
|
DN_SHA3U8x64 DN_SHA3Hash512b(void const *src, size_t src_size)
|
|
{
|
|
DN_SHA3U8x64 result = {};
|
|
DN_SHA3Hash512bPtr(src, src_size, result.data, sizeof(result.data));
|
|
return result;
|
|
}
|
|
|
|
void DN_KeccakHash224bPtr(void const *src, size_t src_size, void *dest, size_t dest_size)
|
|
{
|
|
DN_SHA3FamilyHash(DN_SHA3Family_Keccak, /*hash_size_bits=*/ 224, src, src_size, dest, dest_size);
|
|
}
|
|
|
|
DN_SHA3U8x28 DN_KeccakHash224b(void const *src, size_t src_size)
|
|
{
|
|
DN_SHA3U8x28 result = {};
|
|
DN_KeccakHash224bPtr(src, src_size, result.data, sizeof(result.data));
|
|
return result;
|
|
}
|
|
|
|
void DN_KeccakHash256bPtr(void const *src, size_t src_size, void *dest, size_t dest_size)
|
|
{
|
|
DN_SHA3FamilyHash(DN_SHA3Family_Keccak, /*hash_size_bits=*/ 256, src, src_size, dest, dest_size);
|
|
}
|
|
|
|
DN_SHA3U8x32 DN_KeccakHash256b(void const *src, size_t src_size)
|
|
{
|
|
DN_SHA3U8x32 result = {};
|
|
DN_KeccakHash256bPtr(src, src_size, result.data, sizeof(result.data));
|
|
return result;
|
|
}
|
|
|
|
void DN_KeccakHash384bPtr(void const *src, size_t src_size, void *dest, size_t dest_size)
|
|
{
|
|
DN_SHA3FamilyHash(DN_SHA3Family_Keccak, /*hash_size_bits=*/ 384, src, src_size, dest, dest_size);
|
|
}
|
|
|
|
DN_SHA3U8x48 DN_KeccakHash384b(void const *src, size_t src_size)
|
|
{
|
|
DN_SHA3U8x48 result = {};
|
|
DN_KeccakHash384bPtr(src, src_size, result.data, sizeof(result.data));
|
|
return result;
|
|
}
|
|
|
|
void DN_KeccakHash512bPtr(void const *src, size_t src_size, void *dest, size_t dest_size)
|
|
{
|
|
DN_SHA3FamilyHash(DN_SHA3Family_Keccak, /*hash_size_bits=*/ 512, src, src_size, dest, dest_size);
|
|
}
|
|
|
|
DN_SHA3U8x64 DN_KeccakHash512b(void const *src, size_t src_size)
|
|
{
|
|
DN_SHA3U8x64 result = {};
|
|
DN_KeccakHash512bPtr(src, src_size, result.data, sizeof(result.data));
|
|
return result;
|
|
}
|
|
|
|
void DN_SHA3HexFromBytes(void const *src, size_t src_size, char *dest, size_t dest_size)
|
|
{
|
|
(void)src_size;
|
|
(void)dest_size;
|
|
DN_SHA3Assert(dest_size >= src_size * 2);
|
|
|
|
unsigned char *src_u8 = (unsigned char *)src;
|
|
for (size_t src_index = 0, dest_index = 0; src_index < src_size;
|
|
src_index += 1, dest_index += 2) {
|
|
char byte = src_u8[src_index];
|
|
char hex01 = (byte >> 4) & 0b1111;
|
|
char hex02 = (byte >> 0) & 0b1111;
|
|
dest[dest_index + 0] = hex01 < 10 ? (hex01 + '0') : (hex01 - 10) + 'a';
|
|
dest[dest_index + 1] = hex02 < 10 ? (hex02 + '0') : (hex02 - 10) + 'a';
|
|
}
|
|
}
|
|
|
|
DN_SHA3Str8x56 DN_SHA3HexFromU8x28(DN_SHA3U8x28 const *bytes)
|
|
{
|
|
DN_SHA3Str8x56 result;
|
|
DN_SHA3HexFromBytes(bytes->data, sizeof(bytes->data), result.data, sizeof(result.data));
|
|
result.data[sizeof(result.data) - 1] = 0;
|
|
return result;
|
|
}
|
|
|
|
DN_SHA3Str8x64 DN_SHA3HexFromU8x32(DN_SHA3U8x32 const *bytes)
|
|
{
|
|
DN_SHA3Str8x64 result;
|
|
DN_SHA3HexFromBytes(bytes->data, sizeof(bytes->data), result.data, sizeof(result.data));
|
|
result.data[sizeof(result.data) - 1] = 0;
|
|
return result;
|
|
}
|
|
|
|
DN_SHA3Str8x96 DN_SHA3HexFromU8x48(DN_SHA3U8x48 const *bytes)
|
|
{
|
|
DN_SHA3Str8x96 result;
|
|
DN_SHA3HexFromBytes(bytes->data, sizeof(bytes->data), result.data, sizeof(result.data));
|
|
result.data[sizeof(result.data) - 1] = 0;
|
|
return result;
|
|
}
|
|
|
|
DN_SHA3Str8x128 DN_SHA3HexFromU8x64(DN_SHA3U8x64 const *bytes)
|
|
{
|
|
DN_SHA3Str8x128 result;
|
|
DN_SHA3HexFromBytes(bytes->data, sizeof(bytes->data), result.data, sizeof(result.data));
|
|
result.data[sizeof(result.data) - 1] = 0;
|
|
return result;
|
|
}
|
|
|
|
bool DN_SHA3U8x32Eq(DN_SHA3U8x28 const *a, DN_SHA3U8x28 const *b)
|
|
{
|
|
int result = DN_SHA3Memcmp(a->data, b->data, sizeof(*a)) == 0;
|
|
return result;
|
|
}
|
|
|
|
bool DN_SHA3U8x32Eq(DN_SHA3U8x32 const *a, DN_SHA3U8x32 const *b)
|
|
{
|
|
int result = DN_SHA3Memcmp(a->data, b->data, sizeof(*a)) == 0;
|
|
return result;
|
|
}
|
|
|
|
bool DN_SHA3U8x48Eq(DN_SHA3U8x48 const *a, DN_SHA3U8x48 const *b)
|
|
{
|
|
int result = DN_SHA3Memcmp(a->data, b->data, sizeof(*a)) == 0;
|
|
return result;
|
|
}
|
|
|
|
bool DN_SHA3U8x64Eq(DN_SHA3U8x64 const *a, DN_SHA3U8x64 const *b)
|
|
{
|
|
int result = DN_SHA3Memcmp(a->data, b->data, sizeof(*a)) == 0;
|
|
return result;
|
|
}
|
|
#endif // DN_SHA3_IMPLEMENTATION
|
|
|
|
#define DN_UT_HASH_X_MACRO \
|
|
DN_UT_HASH_X_ENTRY(SHA3_224, "SHA3-224") \
|
|
DN_UT_HASH_X_ENTRY(SHA3_256, "SHA3-256") \
|
|
DN_UT_HASH_X_ENTRY(SHA3_384, "SHA3-384") \
|
|
DN_UT_HASH_X_ENTRY(SHA3_512, "SHA3-512") \
|
|
DN_UT_HASH_X_ENTRY(Keccak_224, "Keccak-224") \
|
|
DN_UT_HASH_X_ENTRY(Keccak_256, "Keccak-256") \
|
|
DN_UT_HASH_X_ENTRY(Keccak_384, "Keccak-384") \
|
|
DN_UT_HASH_X_ENTRY(Keccak_512, "Keccak-512") \
|
|
DN_UT_HASH_X_ENTRY(Count, "Keccak-512")
|
|
|
|
enum DN_TST__HashType
|
|
{
|
|
|
|
#define DN_UT_HASH_X_ENTRY(enum_val, string) Hash_##enum_val,
|
|
DN_UT_HASH_X_MACRO
|
|
#undef DN_UT_HASH_X_ENTRY
|
|
};
|
|
|
|
DN_Str8 const DN_UT_HASH_STRING_[] =
|
|
{
|
|
#define DN_UT_HASH_X_ENTRY(enum_val, string) DN_Str8Lit(string),
|
|
DN_UT_HASH_X_MACRO
|
|
#undef DN_UT_HASH_X_ENTRY
|
|
};
|
|
|
|
void DN_TST_KeccakDispatch_(DN_UTCore *test, int hash_type, DN_Str8 input)
|
|
{
|
|
DN_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_Str8 input_hex = DN_HexFromPtrBytesArena(input.data, input.size, &scratch.arena, DN_TrimLeadingZero_No);
|
|
|
|
switch (hash_type) {
|
|
case Hash_SHA3_224: {
|
|
DN_SHA3U8x28 hash = DN_SHA3Hash224b(input.data, input.size);
|
|
DN_SHA3U8x28 expect;
|
|
DN_RefImpl_FIPS202_SHA3_224_(DN_Cast(uint8_t *) input.data, input.size, (uint8_t *)expect.data);
|
|
|
|
DN_Str8 hash_hex = DN_HexFromPtrBytesArena(hash.data, DN_ArrayCountU(hash.data), &scratch.arena, DN_TrimLeadingZero_No);
|
|
DN_Str8 expect_hex = DN_HexFromPtrBytesArena(expect.data, DN_ArrayCountU(expect.data), &scratch.arena, DN_TrimLeadingZero_No);
|
|
DN_UT_AssertF(test,
|
|
DN_MemEq(hash.data, sizeof(hash.data), expect.data, sizeof(expect.data)),
|
|
"\ninput: %.*s"
|
|
"\nhash: %.*s"
|
|
"\nexpect: %.*s",
|
|
DN_Str8PrintFmt(input_hex),
|
|
DN_Str8PrintFmt(hash_hex),
|
|
DN_Str8PrintFmt(expect_hex));
|
|
} break;
|
|
|
|
case Hash_SHA3_256: {
|
|
DN_SHA3U8x32 hash = DN_SHA3Hash256b(input.data, input.size);
|
|
DN_SHA3U8x32 expect;
|
|
DN_RefImpl_FIPS202_SHA3_256_(DN_Cast(uint8_t *) input.data, input.size, (uint8_t *)expect.data);
|
|
|
|
DN_Str8 hash_hex = DN_HexFromPtrBytesArena(hash.data, DN_ArrayCountU(hash.data), &scratch.arena, DN_TrimLeadingZero_No);
|
|
DN_Str8 expect_hex = DN_HexFromPtrBytesArena(expect.data, DN_ArrayCountU(expect.data), &scratch.arena, DN_TrimLeadingZero_No);
|
|
DN_UT_AssertF(test,
|
|
DN_MemEq(hash.data, sizeof(hash.data), expect.data, sizeof(expect.data)),
|
|
"\ninput: %.*s"
|
|
"\nhash: %.*s"
|
|
"\nexpect: %.*s",
|
|
DN_Str8PrintFmt(input_hex),
|
|
DN_Str8PrintFmt(hash_hex),
|
|
DN_Str8PrintFmt(expect_hex));
|
|
} break;
|
|
|
|
case Hash_SHA3_384: {
|
|
DN_SHA3U8x48 hash = DN_SHA3Hash384b(input.data, input.size);
|
|
DN_SHA3U8x48 expect;
|
|
DN_RefImpl_FIPS202_SHA3_384_(DN_Cast(uint8_t *) input.data, input.size, (uint8_t *)expect.data);
|
|
|
|
DN_Str8 hash_hex = DN_HexFromPtrBytesArena(hash.data, DN_ArrayCountU(hash.data), &scratch.arena, DN_TrimLeadingZero_No);
|
|
DN_Str8 expect_hex = DN_HexFromPtrBytesArena(expect.data, DN_ArrayCountU(expect.data), &scratch.arena, DN_TrimLeadingZero_No);
|
|
DN_UT_AssertF(test,
|
|
DN_MemEq(hash.data, sizeof(hash.data), expect.data, sizeof(expect.data)),
|
|
"\ninput: %.*s"
|
|
"\nhash: %.*s"
|
|
"\nexpect: %.*s",
|
|
DN_Str8PrintFmt(input_hex),
|
|
DN_Str8PrintFmt(hash_hex),
|
|
DN_Str8PrintFmt(expect_hex));
|
|
} break;
|
|
|
|
case Hash_SHA3_512: {
|
|
DN_SHA3U8x64 hash = DN_SHA3Hash512b(input.data, input.size);
|
|
DN_SHA3U8x64 expect;
|
|
DN_RefImpl_FIPS202_SHA3_512_(DN_Cast(uint8_t *) input.data, input.size, (uint8_t *)expect.data);
|
|
|
|
DN_Str8 hash_hex = DN_HexFromPtrBytesArena(hash.data, DN_ArrayCountU(hash.data), &scratch.arena, DN_TrimLeadingZero_No);
|
|
DN_Str8 expect_hex = DN_HexFromPtrBytesArena(expect.data, DN_ArrayCountU(expect.data), &scratch.arena, DN_TrimLeadingZero_No);
|
|
DN_UT_AssertF(test,
|
|
DN_MemEq(hash.data, sizeof(hash.data), expect.data, sizeof(expect.data)),
|
|
"\ninput: %.*s"
|
|
"\nhash: %.*s"
|
|
"\nexpect: %.*s",
|
|
DN_Str8PrintFmt(input_hex),
|
|
DN_Str8PrintFmt(hash_hex),
|
|
DN_Str8PrintFmt(expect_hex));
|
|
} break;
|
|
|
|
case Hash_Keccak_224: {
|
|
DN_SHA3U8x28 hash = DN_KeccakHash224b(input.data, input.size);
|
|
DN_SHA3U8x28 expect;
|
|
DN_RefImpl_Keccak_(1152, 448, DN_Cast(uint8_t *) input.data, input.size, 0x01, (uint8_t *)expect.data, sizeof(expect));
|
|
|
|
DN_Str8 hash_hex = DN_HexFromPtrBytesArena(hash.data, DN_ArrayCountU(hash.data), &scratch.arena, DN_TrimLeadingZero_No);
|
|
DN_Str8 expect_hex = DN_HexFromPtrBytesArena(expect.data, DN_ArrayCountU(expect.data), &scratch.arena, DN_TrimLeadingZero_No);
|
|
DN_UT_AssertF(test,
|
|
DN_MemEq(hash.data, sizeof(hash.data), expect.data, sizeof(expect.data)),
|
|
"\ninput: %.*s"
|
|
"\nhash: %.*s"
|
|
"\nexpect: %.*s",
|
|
DN_Str8PrintFmt(input_hex),
|
|
DN_Str8PrintFmt(hash_hex),
|
|
DN_Str8PrintFmt(expect_hex));
|
|
} break;
|
|
|
|
case Hash_Keccak_256: {
|
|
DN_SHA3U8x32 hash = DN_KeccakHash256b(input.data, input.size);
|
|
DN_SHA3U8x32 expect;
|
|
DN_RefImpl_Keccak_(1088, 512, DN_Cast(uint8_t *) input.data, input.size, 0x01, (uint8_t *)expect.data, sizeof(expect));
|
|
|
|
DN_Str8 hash_hex = DN_HexFromPtrBytesArena(hash.data, DN_ArrayCountU(hash.data), &scratch.arena, DN_TrimLeadingZero_No);
|
|
DN_Str8 expect_hex = DN_HexFromPtrBytesArena(expect.data, DN_ArrayCountU(expect.data), &scratch.arena, DN_TrimLeadingZero_No);
|
|
DN_UT_AssertF(test,
|
|
DN_MemEq(hash.data, sizeof(hash.data), expect.data, sizeof(expect.data)),
|
|
"\ninput: %.*s"
|
|
"\nhash: %.*s"
|
|
"\nexpect: %.*s",
|
|
DN_Str8PrintFmt(input_hex),
|
|
DN_Str8PrintFmt(hash_hex),
|
|
DN_Str8PrintFmt(expect_hex));
|
|
} break;
|
|
|
|
case Hash_Keccak_384: {
|
|
DN_SHA3U8x48 hash = DN_KeccakHash384b(input.data, input.size);
|
|
DN_SHA3U8x48 expect;
|
|
DN_RefImpl_Keccak_(832, 768, DN_Cast(uint8_t *) input.data, input.size, 0x01, (uint8_t *)expect.data, sizeof(expect));
|
|
|
|
DN_Str8 hash_hex = DN_HexFromPtrBytesArena(hash.data, DN_ArrayCountU(hash.data), &scratch.arena, DN_TrimLeadingZero_No);
|
|
DN_Str8 expect_hex = DN_HexFromPtrBytesArena(expect.data, DN_ArrayCountU(expect.data), &scratch.arena, DN_TrimLeadingZero_No);
|
|
DN_UT_AssertF(test,
|
|
DN_MemEq(hash.data, sizeof(hash.data), expect.data, sizeof(expect.data)),
|
|
"\ninput: %.*s"
|
|
"\nhash: %.*s"
|
|
"\nexpect: %.*s",
|
|
DN_Str8PrintFmt(input_hex),
|
|
DN_Str8PrintFmt(hash_hex),
|
|
DN_Str8PrintFmt(expect_hex));
|
|
} break;
|
|
|
|
case Hash_Keccak_512: {
|
|
DN_SHA3U8x64 hash = DN_KeccakHash512b(input.data, input.size);
|
|
DN_SHA3U8x64 expect;
|
|
DN_RefImpl_Keccak_(576, 1024, DN_Cast(uint8_t *) input.data, input.size, 0x01, (uint8_t *)expect.data, sizeof(expect));
|
|
|
|
DN_Str8 hash_hex = DN_HexFromPtrBytesArena(hash.data, DN_ArrayCountU(hash.data), &scratch.arena, DN_TrimLeadingZero_No);
|
|
DN_Str8 expect_hex = DN_HexFromPtrBytesArena(expect.data, DN_ArrayCountU(expect.data), &scratch.arena, DN_TrimLeadingZero_No);
|
|
DN_UT_AssertF(test,
|
|
DN_MemEq(hash.data, sizeof(hash.data), expect.data, sizeof(expect.data)),
|
|
"\ninput: %.*s"
|
|
"\nhash: %.*s"
|
|
"\nexpect: %.*s",
|
|
DN_Str8PrintFmt(input_hex),
|
|
DN_Str8PrintFmt(hash_hex),
|
|
DN_Str8PrintFmt(expect_hex));
|
|
} break;
|
|
}
|
|
DN_TCScratchEnd(&scratch);
|
|
}
|
|
#endif // defined(DN_UNIT_TESTS_WITH_KECCAK)
|
|
|
|
DN_UTCore DN_TST_Keccak()
|
|
{
|
|
DN_UTCore result = DN_UT_Init();
|
|
#if defined(DN_UNIT_TESTS_WITH_KECCAK)
|
|
DN_Str8 const INPUTS[] = {
|
|
DN_Str8Lit("abc"),
|
|
DN_Str8Lit(""),
|
|
DN_Str8Lit("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"),
|
|
DN_Str8Lit("abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmno"
|
|
"pqrstnopqrstu"),
|
|
};
|
|
|
|
DN_UT_LogF(&result, "DN_KC\n");
|
|
{
|
|
for (int hash_type = 0; hash_type < Hash_Count; hash_type++) {
|
|
DN_PCG32 rng = DN_PCG32Init(0xd48e'be21'2af8'733d);
|
|
for (DN_Str8 input : INPUTS) {
|
|
DN_UT_BeginF(&result, "%.*s - Input: %.*s", DN_Str8PrintFmt(DN_UT_HASH_STRING_[hash_type]), DN_Cast(int) DN_Min(input.size, 54), input.data);
|
|
DN_TST_KeccakDispatch_(&result, hash_type, input);
|
|
DN_UT_End(&result);
|
|
}
|
|
|
|
DN_UT_BeginF(&result, "%.*s - Deterministic random inputs", DN_Str8PrintFmt(DN_UT_HASH_STRING_[hash_type]));
|
|
for (DN_USize index = 0; index < 128; index++) {
|
|
char src[4096] = {};
|
|
DN_U32 src_size = DN_PCG32Range(&rng, 0, sizeof(src));
|
|
|
|
for (DN_USize src_index = 0; src_index < src_size; src_index++)
|
|
src[src_index] = DN_Cast(char) DN_PCG32Range(&rng, 0, 255);
|
|
|
|
DN_Str8 input = DN_Str8FromPtr(src, src_size);
|
|
DN_TST_KeccakDispatch_(&result, hash_type, input);
|
|
}
|
|
DN_UT_End(&result);
|
|
}
|
|
}
|
|
#endif
|
|
return result;
|
|
}
|
|
|
|
static DN_UTCore DN_TST_M4()
|
|
{
|
|
DN_UTCore result = DN_UT_Init();
|
|
DN_UT_LogF(&result, "DN_M4\n");
|
|
{
|
|
for (DN_UT_Test(&result, "Simple translate and scale matrix")) {
|
|
DN_M4 translate = DN_M4TranslateF(1, 2, 3);
|
|
DN_M4 scale = DN_M4ScaleF(2, 2, 2);
|
|
DN_M4 mul_result = DN_M4Mul(translate, scale);
|
|
|
|
const DN_M4 EXPECT = {
|
|
{
|
|
{2, 0, 0, 0},
|
|
{0, 2, 0, 0},
|
|
{0, 0, 2, 0},
|
|
{1, 2, 3, 1},
|
|
}
|
|
};
|
|
|
|
DN_UT_AssertF(&result,
|
|
memcmp(mul_result.columns, EXPECT.columns, sizeof(EXPECT)) == 0,
|
|
"\nresult =\n%s\nexpected =\n%s",
|
|
DN_M4ColumnMajorString(mul_result).data,
|
|
DN_M4ColumnMajorString(EXPECT).data);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static DN_UTCore DN_TST_OS()
|
|
{
|
|
DN_UTCore result = DN_UT_Init();
|
|
|
|
#if defined(DN_OS_INC_CPP) || 1
|
|
DN_UT_LogF(&result, "DN_OS\n");
|
|
{
|
|
for (DN_UT_Test(&result, "Generate secure RNG 32 bytes")) {
|
|
char const ZERO[32] = {};
|
|
char buf[32] = {};
|
|
DN_OS_GenBytesSecure(buf, DN_ArrayCountU(buf));
|
|
DN_UT_Assert(&result, DN_Memcmp(buf, ZERO, DN_ArrayCountU(buf)) != 0);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Query executable directory")) {
|
|
DN_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_Str8 os_result = DN_OS_EXEDir(&scratch.arena);
|
|
DN_UT_Assert(&result, os_result.size);
|
|
DN_UT_AssertF(&result, DN_OS_PathIsDir(os_result), "result(%zu): %.*s", os_result.size, DN_Str8PrintFmt(os_result));
|
|
DN_TCScratchEnd(&scratch);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "DN_OS_PerfCounterNow")) {
|
|
uint64_t os_result = DN_OS_PerfCounterNow();
|
|
DN_UT_Assert(&result, os_result != 0);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Consecutive ticks are ordered")) {
|
|
uint64_t a = DN_OS_PerfCounterNow();
|
|
uint64_t b = DN_OS_PerfCounterNow();
|
|
DN_UT_AssertF(&result, b >= a, "a: %" PRIu64 ", b: %" PRIu64, a, b);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Ticks to time are a correct order of magnitude")) {
|
|
uint64_t a = DN_OS_PerfCounterNow();
|
|
uint64_t b = DN_OS_PerfCounterNow();
|
|
DN_F64 s = DN_OS_PerfCounterS(a, b);
|
|
DN_F64 ms = DN_OS_PerfCounterMs(a, b);
|
|
DN_F64 us = DN_OS_PerfCounterUs(a, b);
|
|
DN_F64 ns = DN_OS_PerfCounterNs(a, b);
|
|
DN_UT_AssertF(&result, s <= ms, "s: %f, ms: %f", s, ms);
|
|
DN_UT_AssertF(&result, ms <= us, "ms: %f, us: %f", ms, us);
|
|
DN_UT_AssertF(&result, us <= ns, "us: %f, ns: %f", us, ns);
|
|
}
|
|
}
|
|
|
|
DN_UT_LogF(&result, "\nDN_OS Filesystem\n");
|
|
{
|
|
for (DN_UT_Test(&result, "Make directory recursive \"abcd/efgh\"")) {
|
|
DN_UT_AssertF(&result, DN_OS_PathMakeDir(DN_Str8Lit("abcd/efgh")), "Failed to make directory");
|
|
DN_UT_AssertF(&result, DN_OS_PathIsDir(DN_Str8Lit("abcd")), "Directory was not made");
|
|
DN_UT_AssertF(&result, DN_OS_PathIsDir(DN_Str8Lit("abcd/efgh")), "Subdirectory was not made");
|
|
DN_UT_AssertF(&result, DN_OS_PathIsFile(DN_Str8Lit("abcd")) == false, "This function should only return true for files");
|
|
DN_UT_AssertF(&result, DN_OS_PathIsFile(DN_Str8Lit("abcd/efgh")) == false, "This function should only return true for files");
|
|
DN_UT_AssertF(&result, DN_OS_PathDelete(DN_Str8Lit("abcd/efgh")), "Failed to delete directory");
|
|
DN_UT_AssertF(&result, DN_OS_PathDelete(DN_Str8Lit("abcd")), "Failed to cleanup directory");
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "File write, read, copy, move and delete")) {
|
|
// NOTE: Write step
|
|
DN_Str8 const SRC_FILE = DN_Str8Lit("dn_result_file");
|
|
DN_B32 write_result = DN_OS_FileWriteAll(SRC_FILE, DN_Str8Lit("1234"), nullptr);
|
|
DN_UT_Assert(&result, write_result);
|
|
DN_UT_Assert(&result, DN_OS_PathIsFile(SRC_FILE));
|
|
|
|
// NOTE: Read step
|
|
DN_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_Str8 read_file = DN_OS_FileReadAllArena(&scratch.arena, SRC_FILE, nullptr);
|
|
DN_UT_AssertF(&result, read_file.size, "Failed to load file");
|
|
DN_UT_AssertF(&result, read_file.size == 4, "File read wrong amount of bytes (%zu)", read_file.size);
|
|
DN_UT_AssertF(&result, DN_Str8Eq(read_file, DN_Str8Lit("1234")), "Read %zu bytes instead of the expected 4: '%.*s'", read_file.size, DN_Str8PrintFmt(read_file));
|
|
|
|
// NOTE: Copy step
|
|
DN_Str8 const COPY_FILE = DN_Str8Lit("dn_result_file_copy");
|
|
DN_B32 copy_result = DN_OS_FileCopy(SRC_FILE, COPY_FILE, true /*overwrite*/, nullptr);
|
|
DN_UT_Assert(&result, copy_result);
|
|
DN_UT_Assert(&result, DN_OS_PathIsFile(COPY_FILE));
|
|
|
|
// NOTE: Move step
|
|
DN_Str8 const MOVE_FILE = DN_Str8Lit("dn_result_file_move");
|
|
DN_B32 move_result = DN_OS_FileMove(COPY_FILE, MOVE_FILE, true /*overwrite*/, nullptr);
|
|
DN_UT_Assert(&result, move_result);
|
|
DN_UT_Assert(&result, DN_OS_PathIsFile(MOVE_FILE));
|
|
DN_UT_AssertF(&result, DN_OS_PathIsFile(COPY_FILE) == false, "Moving a file should remove the original");
|
|
|
|
// NOTE: Delete step
|
|
DN_B32 delete_src_file = DN_OS_PathDelete(SRC_FILE);
|
|
DN_B32 delete_moved_file = DN_OS_PathDelete(MOVE_FILE);
|
|
DN_UT_Assert(&result, delete_src_file);
|
|
DN_UT_Assert(&result, delete_moved_file);
|
|
|
|
// NOTE: Deleting non-existent file fails
|
|
DN_B32 delete_non_existent_src_file = DN_OS_PathDelete(SRC_FILE);
|
|
DN_B32 delete_non_existent_moved_file = DN_OS_PathDelete(MOVE_FILE);
|
|
DN_UT_Assert(&result, delete_non_existent_moved_file == false);
|
|
DN_UT_Assert(&result, delete_non_existent_src_file == false);
|
|
DN_TCScratchEnd(&scratch);
|
|
}
|
|
}
|
|
|
|
DN_UT_LogF(&result, "\nSemaphore\n");
|
|
{
|
|
DN_OSSemaphore sem = DN_OS_SemaphoreInit(0);
|
|
|
|
for (DN_UT_Test(&result, "Wait timeout")) {
|
|
DN_U64 begin = DN_OS_PerfCounterNow();
|
|
DN_OSSemaphoreWaitResult wait_result = DN_OS_SemaphoreWait(&sem, 100 /*timeout_ms*/);
|
|
DN_U64 end = DN_OS_PerfCounterNow();
|
|
DN_UT_AssertF(&result, wait_result == DN_OSSemaphoreWaitResult_Timeout, "Received wait result %zu", wait_result);
|
|
DN_F64 elapsed_ms = DN_OS_PerfCounterMs(begin, end);
|
|
DN_UT_AssertF(&result, elapsed_ms >= 80 && elapsed_ms <= 120, "Expected to sleep for ~100ms, slept %f ms", elapsed_ms);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Wait success")) {
|
|
DN_OS_SemaphoreIncrement(&sem, 1);
|
|
DN_OSSemaphoreWaitResult wait_result = DN_OS_SemaphoreWait(&sem, 0 /*timeout_ms*/);
|
|
DN_UT_AssertF(&result, wait_result == DN_OSSemaphoreWaitResult_Success, "Received wait result %zu", wait_result);
|
|
}
|
|
|
|
DN_OS_SemaphoreDeinit(&sem);
|
|
}
|
|
|
|
DN_UT_LogF(&result, "\nMutex\n");
|
|
{
|
|
DN_OSMutex mutex = DN_OS_MutexInit();
|
|
for (DN_UT_Test(&result, "Lock")) {
|
|
DN_OS_MutexLock(&mutex);
|
|
DN_OS_MutexUnlock(&mutex);
|
|
}
|
|
DN_OS_MutexDeinit(&mutex);
|
|
}
|
|
|
|
DN_UT_LogF(&result, "\nCondition Variable\n");
|
|
{
|
|
DN_OSMutex mutex = DN_OS_MutexInit();
|
|
DN_OSConditionVariable cv = DN_OS_ConditionVariableInit();
|
|
for (DN_UT_Test(&result, "Lock and timeout")) {
|
|
DN_U64 begin = DN_OS_PerfCounterNow();
|
|
DN_OS_ConditionVariableWait(&cv, &mutex, 100 /*sleep_ms*/);
|
|
DN_U64 end = DN_OS_PerfCounterNow();
|
|
DN_F64 elapsed_ms = DN_OS_PerfCounterMs(begin, end);
|
|
DN_UT_AssertF(&result, elapsed_ms >= 99 && elapsed_ms <= 120, "Expected to sleep for ~100ms, slept %f ms", elapsed_ms);
|
|
}
|
|
DN_OS_MutexDeinit(&mutex);
|
|
DN_OS_ConditionVariableDeinit(&cv);
|
|
}
|
|
#endif
|
|
|
|
return result;
|
|
}
|
|
|
|
static DN_UTCore DN_TST_Rect()
|
|
{
|
|
DN_UTCore result = DN_UT_Init();
|
|
DN_UT_LogF(&result, "DN_Rect\n");
|
|
{
|
|
for (DN_UT_Test(&result, "No intersection")) {
|
|
DN_Rect a = DN_RectFrom2V2(DN_V2F32From1N(0), DN_V2F32From2N(100, 100));
|
|
DN_Rect b = DN_RectFrom2V2(DN_V2F32From2N(200, 0), DN_V2F32From2N(200, 200));
|
|
DN_Rect ab = DN_RectIntersection(a, b);
|
|
|
|
DN_V2F32 ab_max = ab.pos + ab.size;
|
|
DN_UT_AssertF(&result,
|
|
ab.pos.x == 0 && ab.pos.y == 0 && ab_max.x == 0 && ab_max.y == 0,
|
|
"ab = { min.x = %.2f, min.y = %.2f, max.x = %.2f. max.y = %.2f }",
|
|
ab.pos.x,
|
|
ab.pos.y,
|
|
ab_max.x,
|
|
ab_max.y);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "A's min intersects B")) {
|
|
DN_Rect a = DN_RectFrom2V2(DN_V2F32From2N(50, 50), DN_V2F32From2N(100, 100));
|
|
DN_Rect b = DN_RectFrom2V2(DN_V2F32From2N(0, 0), DN_V2F32From2N(100, 100));
|
|
DN_Rect ab = DN_RectIntersection(a, b);
|
|
|
|
DN_V2F32 ab_max = ab.pos + ab.size;
|
|
DN_UT_AssertF(&result,
|
|
ab.pos.x == 50 && ab.pos.y == 50 && ab_max.x == 100 && ab_max.y == 100,
|
|
"ab = { min.x = %.2f, min.y = %.2f, max.x = %.2f. max.y = %.2f }",
|
|
ab.pos.x,
|
|
ab.pos.y,
|
|
ab_max.x,
|
|
ab_max.y);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "B's min intersects A")) {
|
|
DN_Rect a = DN_RectFrom2V2(DN_V2F32From2N(0, 0), DN_V2F32From2N(100, 100));
|
|
DN_Rect b = DN_RectFrom2V2(DN_V2F32From2N(50, 50), DN_V2F32From2N(100, 100));
|
|
DN_Rect ab = DN_RectIntersection(a, b);
|
|
|
|
DN_V2F32 ab_max = ab.pos + ab.size;
|
|
DN_UT_AssertF(&result,
|
|
ab.pos.x == 50 && ab.pos.y == 50 && ab_max.x == 100 && ab_max.y == 100,
|
|
"ab = { min.x = %.2f, min.y = %.2f, max.x = %.2f. max.y = %.2f }",
|
|
ab.pos.x,
|
|
ab.pos.y,
|
|
ab_max.x,
|
|
ab_max.y);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "A's max intersects B")) {
|
|
DN_Rect a = DN_RectFrom2V2(DN_V2F32From2N(-50, -50), DN_V2F32From2N(100, 100));
|
|
DN_Rect b = DN_RectFrom2V2(DN_V2F32From2N(0, 0), DN_V2F32From2N(100, 100));
|
|
DN_Rect ab = DN_RectIntersection(a, b);
|
|
|
|
DN_V2F32 ab_max = ab.pos + ab.size;
|
|
DN_UT_AssertF(&result,
|
|
ab.pos.x == 0 && ab.pos.y == 0 && ab_max.x == 50 && ab_max.y == 50,
|
|
"ab = { min.x = %.2f, min.y = %.2f, max.x = %.2f. max.y = %.2f }",
|
|
ab.pos.x,
|
|
ab.pos.y,
|
|
ab_max.x,
|
|
ab_max.y);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "B's max intersects A")) {
|
|
DN_Rect a = DN_RectFrom2V2(DN_V2F32From2N(0, 0), DN_V2F32From2N(100, 100));
|
|
DN_Rect b = DN_RectFrom2V2(DN_V2F32From2N(-50, -50), DN_V2F32From2N(100, 100));
|
|
DN_Rect ab = DN_RectIntersection(a, b);
|
|
|
|
DN_V2F32 ab_max = ab.pos + ab.size;
|
|
DN_UT_AssertF(&result,
|
|
ab.pos.x == 0 && ab.pos.y == 0 && ab_max.x == 50 && ab_max.y == 50,
|
|
"ab = { min.x = %.2f, min.y = %.2f, max.x = %.2f. max.y = %.2f }",
|
|
ab.pos.x,
|
|
ab.pos.y,
|
|
ab_max.x,
|
|
ab_max.y);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "B contains A")) {
|
|
DN_Rect a = DN_RectFrom2V2(DN_V2F32From2N(25, 25), DN_V2F32From2N(25, 25));
|
|
DN_Rect b = DN_RectFrom2V2(DN_V2F32From2N(0, 0), DN_V2F32From2N(100, 100));
|
|
DN_Rect ab = DN_RectIntersection(a, b);
|
|
|
|
DN_V2F32 ab_max = ab.pos + ab.size;
|
|
DN_UT_AssertF(&result,
|
|
ab.pos.x == 25 && ab.pos.y == 25 && ab_max.x == 50 && ab_max.y == 50,
|
|
"ab = { min.x = %.2f, min.y = %.2f, max.x = %.2f. max.y = %.2f }",
|
|
ab.pos.x,
|
|
ab.pos.y,
|
|
ab_max.x,
|
|
ab_max.y);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "A contains B")) {
|
|
DN_Rect a = DN_RectFrom2V2(DN_V2F32From2N(0, 0), DN_V2F32From2N(100, 100));
|
|
DN_Rect b = DN_RectFrom2V2(DN_V2F32From2N(25, 25), DN_V2F32From2N(25, 25));
|
|
DN_Rect ab = DN_RectIntersection(a, b);
|
|
|
|
DN_V2F32 ab_max = ab.pos + ab.size;
|
|
DN_UT_AssertF(&result,
|
|
ab.pos.x == 25 && ab.pos.y == 25 && ab_max.x == 50 && ab_max.y == 50,
|
|
"ab = { min.x = %.2f, min.y = %.2f, max.x = %.2f. max.y = %.2f }",
|
|
ab.pos.x,
|
|
ab.pos.y,
|
|
ab_max.x,
|
|
ab_max.y);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "A equals B")) {
|
|
DN_Rect a = DN_RectFrom2V2(DN_V2F32From2N(0, 0), DN_V2F32From2N(100, 100));
|
|
DN_Rect b = a;
|
|
DN_Rect ab = DN_RectIntersection(a, b);
|
|
|
|
DN_V2F32 ab_max = ab.pos + ab.size;
|
|
DN_UT_AssertF(&result,
|
|
ab.pos.x == 0 && ab.pos.y == 0 && ab_max.x == 100 && ab_max.y == 100,
|
|
"ab = { min.x = %.2f, min.y = %.2f, max.x = %.2f. max.y = %.2f }",
|
|
ab.pos.x,
|
|
ab.pos.y,
|
|
ab_max.x,
|
|
ab_max.y);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static DN_UTCore DN_TST_BaseStrings()
|
|
{
|
|
DN_UTCore result = DN_UT_Init();
|
|
DN_UT_LogF(&result, "Strings\n");
|
|
{
|
|
for (DN_UT_Test(&result, "Str8 literal")) {
|
|
DN_Str8 string = DN_Str8Lit("AB");
|
|
DN_UT_AssertF(&result, string.size == 2, "size: %zu", string.size);
|
|
DN_UT_AssertF(&result, string.data[0] == 'A', "string[0]: %c", string.data[0]);
|
|
DN_UT_AssertF(&result, string.data[1] == 'B', "string[1]: %c", string.data[1]);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "C-string length")) {
|
|
DN_USize size = DN_CStr8Size("hello");
|
|
DN_UT_AssertF(&result, size == 5, "size=%zu", size);
|
|
}
|
|
|
|
char arena_base[512];
|
|
for (DN_UT_Test(&result, "Str8 format from arena")) {
|
|
DN_MemList mem = DN_MemListFromBuffer(arena_base, sizeof(arena_base), DN_MemFlags_Nil);
|
|
DN_Arena arena = DN_ArenaFromMemList(&mem);
|
|
DN_Str8 str8 = DN_Str8FromFmtArena(&arena, "Foo Bar %d", 5);
|
|
DN_Str8 expect = DN_Str8Lit("Foo Bar 5");
|
|
DN_UT_AssertF(&result, DN_Str8Eq(str8, expect), "str8=%.*s", DN_Str8PrintFmt(str8), DN_Str8PrintFmt(expect));
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Str8 format from pool")) {
|
|
DN_MemList mem = DN_MemListFromBuffer(arena_base, sizeof(arena_base), DN_MemFlags_Nil);
|
|
DN_Arena arena = DN_ArenaFromMemList(&mem);
|
|
DN_Pool pool = DN_PoolFromArena(&arena, 0);
|
|
DN_Str8 str8 = DN_Str8FromFmtPool(&pool, "Foo Bar %d", 5);
|
|
DN_Str8 expect = DN_Str8Lit("Foo Bar 5");
|
|
DN_UT_AssertF(&result, DN_Str8Eq(str8, expect), "str8=%.*s", DN_Str8PrintFmt(str8), DN_Str8PrintFmt(expect));
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Str8x32 from U64")) {
|
|
DN_Str8x32 str8 = DN_Str8x32FromU64(123456, ' ');
|
|
DN_Str8 expect = DN_Str8Lit("123 456");
|
|
DN_UT_AssertF(&result, DN_Str8Eq(DN_Str8FromStruct(&str8), expect), "buf_str8=%.*s, expect=%.*s", DN_Str8PrintFmt(str8), DN_Str8PrintFmt(expect));
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Initialise with format string")) {
|
|
DN_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_Str8 string = DN_Str8FromFmtArena(&scratch.arena, "%s", "AB");
|
|
DN_UT_AssertF(&result, string.size == 2, "size: %zu", string.size);
|
|
DN_UT_AssertF(&result, string.data[0] == 'A', "string[0]: %c", string.data[0]);
|
|
DN_UT_AssertF(&result, string.data[1] == 'B', "string[1]: %c", string.data[1]);
|
|
DN_UT_AssertF(&result, string.data[2] == 0, "string[2]: %c", string.data[2]);
|
|
DN_TCScratchEnd(&scratch);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Copy string")) {
|
|
DN_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_Str8 string = DN_Str8Lit("AB");
|
|
DN_Str8 copy = DN_Str8FromStr8Arena(string, &scratch.arena);
|
|
DN_UT_AssertF(&result, copy.size == 2, "size: %zu", copy.size);
|
|
DN_UT_AssertF(&result, copy.data[0] == 'A', "copy[0]: %c", copy.data[0]);
|
|
DN_UT_AssertF(&result, copy.data[1] == 'B', "copy[1]: %c", copy.data[1]);
|
|
DN_UT_AssertF(&result, copy.data[2] == 0, "copy[2]: %c", copy.data[2]);
|
|
DN_TCScratchEnd(&scratch);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Trim whitespace around string")) {
|
|
DN_Str8 string = DN_Str8TrimWhitespaceAround(DN_Str8Lit(" AB "));
|
|
DN_UT_AssertF(&result, DN_Str8Eq(string, DN_Str8Lit("AB")), "[string=%.*s]", DN_Str8PrintFmt(string));
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Allocate string from arena")) {
|
|
DN_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_Str8 string = DN_Str8AllocArena(2, DN_ZMem_No, &scratch.arena);
|
|
DN_UT_AssertF(&result, string.size == 2, "size: %zu", string.size);
|
|
DN_TCScratchEnd(&scratch);
|
|
}
|
|
|
|
// NOTE: TrimPrefix/Suffix /////////////////////////////////////////////////////////////////////
|
|
for (DN_UT_Test(&result, "Trim prefix with matching prefix")) {
|
|
DN_Str8 input = DN_Str8Lit("nft/abc");
|
|
DN_Str8 str_result = DN_Str8TrimPrefix(input, DN_Str8Lit("nft/"));
|
|
DN_UT_AssertF(&result, DN_Str8Eq(str_result, DN_Str8Lit("abc")), "%.*s", DN_Str8PrintFmt(str_result));
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Trim prefix with non matching prefix")) {
|
|
DN_Str8 input = DN_Str8Lit("nft/abc");
|
|
DN_Str8 str_result = DN_Str8TrimPrefix(input, DN_Str8Lit(" ft/"));
|
|
DN_UT_AssertF(&result, DN_Str8Eq(str_result, input), "%.*s", DN_Str8PrintFmt(str_result));
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Trim suffix with matching suffix")) {
|
|
DN_Str8 input = DN_Str8Lit("nft/abc");
|
|
DN_Str8 str_result = DN_Str8TrimSuffix(input, DN_Str8Lit("abc"));
|
|
DN_UT_AssertF(&result, DN_Str8Eq(str_result, DN_Str8Lit("nft/")), "%.*s", DN_Str8PrintFmt(str_result));
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Trim suffix with non matching suffix")) {
|
|
DN_Str8 input = DN_Str8Lit("nft/abc");
|
|
DN_Str8 str_result = DN_Str8TrimSuffix(input, DN_Str8Lit("ab"));
|
|
DN_UT_AssertF(&result, DN_Str8Eq(str_result, input), "%.*s", DN_Str8PrintFmt(str_result));
|
|
}
|
|
|
|
// NOTE: DN_Str8IsAllDigits //////////////////////////////////////////////////////////////
|
|
for (DN_UT_Test(&result, "Is all digits fails on non-digit string")) {
|
|
DN_B32 str_result = DN_Str8IsAll(DN_Str8Lit("@123string"), DN_Str8IsAllType_Digits);
|
|
DN_UT_Assert(&result, str_result == false);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Is all digits fails on nullptr")) {
|
|
DN_B32 str_result = DN_Str8IsAll(DN_Str8FromPtr(nullptr, 0), DN_Str8IsAllType_Digits);
|
|
DN_UT_Assert(&result, str_result == false);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Is all digits fails on string w/ 0 size")) {
|
|
char const buf[] = "@123string";
|
|
DN_B32 str_result = DN_Str8IsAll(DN_Str8FromPtr(buf, 0), DN_Str8IsAllType_Digits);
|
|
DN_UT_Assert(&result, !str_result);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Is all digits success")) {
|
|
DN_B32 str_result = DN_Str8IsAll(DN_Str8Lit("23"), DN_Str8IsAllType_Digits);
|
|
DN_UT_Assert(&result, DN_Cast(bool) str_result == true);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Is all digits fails on whitespace")) {
|
|
DN_B32 str_result = DN_Str8IsAll(DN_Str8Lit("23 "), DN_Str8IsAllType_Digits);
|
|
DN_UT_Assert(&result, DN_Cast(bool) str_result == false);
|
|
}
|
|
|
|
// NOTE: DN_Str8BSplit ///////////////////////////////////////////////////////////////////
|
|
{
|
|
{
|
|
char const *TEST_FMT = "Binary split \"%.*s\" with \"%.*s\"";
|
|
DN_Str8 delimiter = DN_Str8Lit("/");
|
|
DN_Str8 input = DN_Str8Lit("abcdef");
|
|
for (DN_UT_Test(&result, TEST_FMT, DN_Str8PrintFmt(input), DN_Str8PrintFmt(delimiter))) {
|
|
DN_Str8BSplitResult split = DN_Str8BSplit(input, delimiter);
|
|
DN_UT_AssertF(&result, DN_Str8Eq(split.lhs, DN_Str8Lit("abcdef")), "[lhs=%.*s]", DN_Str8PrintFmt(split.lhs));
|
|
DN_UT_AssertF(&result, DN_Str8Eq(split.rhs, DN_Str8Lit("")), "[rhs=%.*s]", DN_Str8PrintFmt(split.rhs));
|
|
}
|
|
|
|
input = DN_Str8Lit("abc/def");
|
|
for (DN_UT_Test(&result, TEST_FMT, DN_Str8PrintFmt(input), DN_Str8PrintFmt(delimiter))) {
|
|
DN_Str8BSplitResult split = DN_Str8BSplit(input, delimiter);
|
|
DN_UT_AssertF(&result, DN_Str8Eq(split.lhs, DN_Str8Lit("abc")), "[lhs=%.*s]", DN_Str8PrintFmt(split.lhs));
|
|
DN_UT_AssertF(&result, DN_Str8Eq(split.rhs, DN_Str8Lit("def")), "[rhs=%.*s]", DN_Str8PrintFmt(split.rhs));
|
|
}
|
|
|
|
input = DN_Str8Lit("/abcdef");
|
|
for (DN_UT_Test(&result, TEST_FMT, DN_Str8PrintFmt(input), DN_Str8PrintFmt(delimiter))) {
|
|
DN_Str8BSplitResult split = DN_Str8BSplit(input, delimiter);
|
|
DN_UT_AssertF(&result, DN_Str8Eq(split.lhs, DN_Str8Lit("")), "[lhs=%.*s]", DN_Str8PrintFmt(split.lhs));
|
|
DN_UT_AssertF(&result, DN_Str8Eq(split.rhs, DN_Str8Lit("abcdef")), "[rhs=%.*s]", DN_Str8PrintFmt(split.rhs));
|
|
}
|
|
}
|
|
|
|
{
|
|
DN_Str8 delimiter = DN_Str8Lit("-=-");
|
|
DN_Str8 input = DN_Str8Lit("123-=-456");
|
|
for (DN_UT_Test(&result, "Binary split \"%.*s\" with \"%.*s\"", DN_Str8PrintFmt(input), DN_Str8PrintFmt(delimiter))) {
|
|
DN_Str8BSplitResult split = DN_Str8BSplit(input, delimiter);
|
|
DN_UT_AssertF(&result, DN_Str8Eq(split.lhs, DN_Str8Lit("123")), "[lhs=%.*s]", DN_Str8PrintFmt(split.lhs));
|
|
DN_UT_AssertF(&result, DN_Str8Eq(split.rhs, DN_Str8Lit("456")), "[rhs=%.*s]", DN_Str8PrintFmt(split.rhs));
|
|
}
|
|
}
|
|
}
|
|
|
|
// NOTE: DN_I64FromStr8
|
|
for (DN_UT_Test(&result, "To I64: Convert empty string")) {
|
|
DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit(""), 0);
|
|
DN_UT_Assert(&result, str_result.success);
|
|
DN_UT_Assert(&result, str_result.value == 0);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "To I64: Convert \"1\"")) {
|
|
DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit("1"), 0);
|
|
DN_UT_Assert(&result, str_result.success);
|
|
DN_UT_Assert(&result, str_result.value == 1);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "To I64: Convert \"-0\"")) {
|
|
DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit("-0"), 0);
|
|
DN_UT_Assert(&result, str_result.success);
|
|
DN_UT_Assert(&result, str_result.value == 0);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "To I64: Convert \"-1\"")) {
|
|
DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit("-1"), 0);
|
|
DN_UT_Assert(&result, str_result.success);
|
|
DN_UT_Assert(&result, str_result.value == -1);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "To I64: Convert \"1.2\"")) {
|
|
DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit("1.2"), 0);
|
|
DN_UT_Assert(&result, !str_result.success);
|
|
DN_UT_Assert(&result, str_result.value == 1);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "To I64: Convert \"1,234\"")) {
|
|
DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit("1,234"), ',');
|
|
DN_UT_Assert(&result, str_result.success);
|
|
DN_UT_Assert(&result, str_result.value == 1234);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "To I64: Convert \"1,2\"")) {
|
|
DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit("1,2"), ',');
|
|
DN_UT_Assert(&result, str_result.success);
|
|
DN_UT_Assert(&result, str_result.value == 12);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "To I64: Convert \"12a3\"")) {
|
|
DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit("12a3"), 0);
|
|
DN_UT_Assert(&result, !str_result.success);
|
|
DN_UT_Assert(&result, str_result.value == 12);
|
|
}
|
|
|
|
// NOTE: DN_U64FromStr8
|
|
for (DN_UT_Test(&result, "To U64: Convert empty string")) {
|
|
DN_U64FromResult str_result = DN_U64FromStr8(DN_Str8Lit(""), 0);
|
|
DN_UT_Assert(&result, str_result.success);
|
|
DN_UT_AssertF(&result, str_result.value == 0, "result: %" PRIu64, str_result.value);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "To U64: Convert \"1\"")) {
|
|
DN_U64FromResult str_result = DN_U64FromStr8(DN_Str8Lit("1"), 0);
|
|
DN_UT_Assert(&result, str_result.success);
|
|
DN_UT_AssertF(&result, str_result.value == 1, "result: %" PRIu64, str_result.value);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "To U64: Convert \"-0\"")) {
|
|
DN_U64FromResult str_result = DN_U64FromStr8(DN_Str8Lit("-0"), 0);
|
|
DN_UT_Assert(&result, !str_result.success);
|
|
DN_UT_AssertF(&result, str_result.value == 0, "result: %" PRIu64, str_result.value);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "To U64: Convert \"-1\"")) {
|
|
DN_U64FromResult str_result = DN_U64FromStr8(DN_Str8Lit("-1"), 0);
|
|
DN_UT_Assert(&result, !str_result.success);
|
|
DN_UT_AssertF(&result, str_result.value == 0, "result: %" PRIu64, str_result.value);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "To U64: Convert \"1.2\"")) {
|
|
DN_U64FromResult str_result = DN_U64FromStr8(DN_Str8Lit("1.2"), 0);
|
|
DN_UT_Assert(&result, !str_result.success);
|
|
DN_UT_AssertF(&result, str_result.value == 1, "result: %" PRIu64, str_result.value);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "To U64: Convert \"1,234\"")) {
|
|
DN_U64FromResult str_result = DN_U64FromStr8(DN_Str8Lit("1,234"), ',');
|
|
DN_UT_Assert(&result, str_result.success);
|
|
DN_UT_AssertF(&result, str_result.value == 1234, "result: %" PRIu64, str_result.value);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "To U64: Convert \"1,2\"")) {
|
|
DN_U64FromResult str_result = DN_U64FromStr8(DN_Str8Lit("1,2"), ',');
|
|
DN_UT_Assert(&result, str_result.success);
|
|
DN_UT_AssertF(&result, str_result.value == 12, "result: %" PRIu64, str_result.value);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "To U64: Convert \"12a3\"")) {
|
|
DN_U64FromResult str_result = DN_U64FromStr8(DN_Str8Lit("12a3"), 0);
|
|
DN_UT_Assert(&result, !str_result.success);
|
|
DN_UT_AssertF(&result, str_result.value == 12, "result: %" PRIu64, str_result.value);
|
|
}
|
|
|
|
// NOTE: DN_Str8Find
|
|
for (DN_UT_Test(&result, "Find: String (char) is not in buffer")) {
|
|
DN_Str8 buf = DN_Str8Lit("836a35becd4e74b66a0d6844d51f1a63018c7ebc44cf7e109e8e4bba57eefb55");
|
|
DN_Str8 find = DN_Str8Lit("2");
|
|
DN_Str8FindResult str_result = DN_Str8FindStr8(buf, find, DN_Str8EqCase_Sensitive);
|
|
DN_UT_Assert(&result, !str_result.found);
|
|
DN_UT_Assert(&result, str_result.index == 0);
|
|
DN_UT_Assert(&result, str_result.match.data == nullptr);
|
|
DN_UT_Assert(&result, str_result.match.size == 0);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Find: String (char) is in buffer")) {
|
|
DN_Str8 buf = DN_Str8Lit("836a35becd4e74b66a0d6844d51f1a63018c7ebc44cf7e109e8e4bba57eefb55");
|
|
DN_Str8 find = DN_Str8Lit("6");
|
|
DN_Str8FindResult str_result = DN_Str8FindStr8(buf, find, DN_Str8EqCase_Sensitive);
|
|
DN_UT_Assert(&result, str_result.found);
|
|
DN_UT_Assert(&result, str_result.index == 2);
|
|
DN_UT_Assert(&result, str_result.match.data[0] == '6');
|
|
}
|
|
|
|
// NOTE: DN_Str8FileNameFromPath
|
|
for (DN_UT_Test(&result, "File name from Windows path")) {
|
|
DN_Str8 buf = DN_Str8Lit("C:\\ABC\\str_result.exe");
|
|
DN_Str8 str_result = DN_Str8FileNameFromPath(buf);
|
|
DN_UT_AssertF(&result, DN_Str8Eq(str_result, DN_Str8Lit("str_result.exe")), "%.*s", DN_Str8PrintFmt(str_result));
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "File name from Linux path")) {
|
|
DN_Str8 buf = DN_Str8Lit("/ABC/str_result.exe");
|
|
DN_Str8 str_result = DN_Str8FileNameFromPath(buf);
|
|
DN_UT_AssertF(&result, DN_Str8Eq(str_result, DN_Str8Lit("str_result.exe")), "%.*s", DN_Str8PrintFmt(str_result));
|
|
}
|
|
|
|
// NOTE: DN_Str8TrimPrefix
|
|
for (DN_UT_Test(&result, "Trim prefix")) {
|
|
DN_Str8 prefix = DN_Str8Lit("@123");
|
|
DN_Str8 buf = DN_Str8Lit("@123string");
|
|
DN_Str8 str_result = DN_Str8TrimPrefix(buf, prefix, DN_Str8EqCase_Sensitive);
|
|
DN_UT_Assert(&result, DN_Str8Eq(str_result, DN_Str8Lit("string")));
|
|
}
|
|
|
|
// NOTE: DN_Str8TruncMiddle
|
|
{
|
|
for (DN_UT_Test(&result, "TruncMiddlePtr: Short string is not truncated")) {
|
|
DN_Str8 str = DN_Str8Lit("Hello");
|
|
DN_Str8 trunc = DN_Str8Lit("...");
|
|
char dest[64] = {};
|
|
DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 5, trunc, dest, sizeof(dest));
|
|
DN_UT_Assert(&result, !res.truncated);
|
|
DN_UT_Assert(&result, res.size_req == 5);
|
|
DN_UT_AssertF(&result, DN_Str8Eq(res.str8, DN_Str8Lit("Hello")), "%.*s", DN_Str8PrintFmt(res.str8));
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "TruncMiddlePtr: Exact boundary (2*side_size) is not truncated")) {
|
|
DN_Str8 str = DN_Str8Lit("HelloWorld"); // 10 chars
|
|
DN_Str8 trunc = DN_Str8Lit("...");
|
|
char dest[64] = {};
|
|
DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 5, trunc, dest, sizeof(dest));
|
|
DN_UT_Assert(&result, !res.truncated);
|
|
DN_UT_Assert(&result, res.size_req == 10);
|
|
DN_UT_AssertF(&result, DN_Str8Eq(res.str8, DN_Str8Lit("HelloWorld")), "%.*s", DN_Str8PrintFmt(res.str8));
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "TruncMiddlePtr: Long string is truncated in the middle")) {
|
|
DN_Str8 str = DN_Str8Lit("HelloBeautifulWorld");
|
|
DN_Str8 trunc = DN_Str8Lit("...");
|
|
char dest[64] = {};
|
|
DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 5, trunc, dest, sizeof(dest));
|
|
DN_UT_Assert(&result, res.truncated);
|
|
DN_UT_Assert(&result, res.size_req == 13); // 5 + 3 + 5
|
|
DN_UT_AssertF(&result, DN_Str8Eq(res.str8, DN_Str8Lit("Hello...World")), "%.*s", DN_Str8PrintFmt(res.str8));
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "TruncMiddlePtr: Empty truncator concatenates head and tail")) {
|
|
DN_Str8 str = DN_Str8Lit("HelloBeautifulWorld");
|
|
DN_Str8 trunc = DN_Str8Lit("");
|
|
char dest[64] = {};
|
|
DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 5, trunc, dest, sizeof(dest));
|
|
DN_UT_Assert(&result, res.truncated);
|
|
DN_UT_Assert(&result, res.size_req == 10); // 5 + 0 + 5
|
|
DN_UT_AssertF(&result, DN_Str8Eq(res.str8, DN_Str8Lit("HelloWorld")), "%.*s", DN_Str8PrintFmt(res.str8));
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "TruncMiddlePtr: side_size of 0 returns just truncator")) {
|
|
DN_Str8 str = DN_Str8Lit("HelloWorld");
|
|
DN_Str8 trunc = DN_Str8Lit("...");
|
|
char dest[64] = {};
|
|
DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 0, trunc, dest, sizeof(dest));
|
|
DN_UT_Assert(&result, res.truncated);
|
|
DN_UT_Assert(&result, res.size_req == 3);
|
|
DN_UT_AssertF(&result, DN_Str8Eq(res.str8, DN_Str8Lit("...")), "%.*s", DN_Str8PrintFmt(res.str8));
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "TruncMiddlePtr: Null dest calculates size without writing")) {
|
|
DN_Str8 str = DN_Str8Lit("HelloBeautifulWorld");
|
|
DN_Str8 trunc = DN_Str8Lit("...");
|
|
DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 5, trunc, nullptr, 0);
|
|
DN_UT_Assert(&result, res.truncated);
|
|
DN_UT_Assert(&result, res.size_req == 13);
|
|
DN_UT_Assert(&result, res.str8.data == nullptr);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "TruncMiddlePtr: size_req is consistent between dry-run and actual")) {
|
|
DN_Str8 str = DN_Str8Lit("HelloBeautifulWorld");
|
|
DN_Str8 trunc = DN_Str8Lit("...");
|
|
DN_Str8TruncResult dry = DN_Str8TruncMiddlePtr(str, 5, trunc, nullptr, 0);
|
|
char dest[64] = {};
|
|
DN_Str8TruncResult actual = DN_Str8TruncMiddlePtr(str, 5, trunc, dest, sizeof(dest));
|
|
DN_UT_Assert(&result, dry.size_req == actual.size_req);
|
|
DN_UT_Assert(&result, dry.truncated == actual.truncated);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "TruncMiddlePtr: Minimum buffer size (2*side_size + trunc.size + 1) is sufficient")) {
|
|
DN_Str8 str = DN_Str8Lit("HelloBeautifulWorld");
|
|
DN_Str8 trunc = DN_Str8Lit("...");
|
|
char dest[14] = {}; // Exactly 2*5 + 3 + 1
|
|
DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 5, trunc, dest, sizeof(dest));
|
|
DN_UT_Assert(&result, res.truncated);
|
|
DN_UT_Assert(&result, res.size_req == 13);
|
|
DN_UT_AssertF(&result, DN_Str8Eq(res.str8, DN_Str8Lit("Hello...World")), "%.*s", DN_Str8PrintFmt(res.str8));
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "TruncMiddlePtr: Single character side size")) {
|
|
DN_Str8 str = DN_Str8Lit("HelloBeautifulWorld");
|
|
DN_Str8 trunc = DN_Str8Lit("...");
|
|
char dest[64] = {};
|
|
DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 1, trunc, dest, sizeof(dest));
|
|
DN_UT_Assert(&result, res.truncated);
|
|
DN_UT_Assert(&result, res.size_req == 5); // 1 + 3 + 1
|
|
DN_UT_AssertF(&result, DN_Str8Eq(res.str8, DN_Str8Lit("H...d")), "%.*s", DN_Str8PrintFmt(res.str8));
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "TruncMiddlePtr: Large side_size falls back to copy")) {
|
|
DN_Str8 str = DN_Str8Lit("Hello");
|
|
DN_Str8 trunc = DN_Str8Lit("...");
|
|
char dest[64] = {};
|
|
DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 100, trunc, dest, sizeof(dest));
|
|
DN_UT_Assert(&result, !res.truncated);
|
|
DN_UT_Assert(&result, res.size_req == 5);
|
|
DN_UT_AssertF(&result, DN_Str8Eq(res.str8, DN_Str8Lit("Hello")), "%.*s", DN_Str8PrintFmt(res.str8));
|
|
}
|
|
|
|
// NOTE: DN_Str8TruncMiddle (arena wrapper)
|
|
for (DN_UT_Test(&result, "TruncMiddle: Arena wrapper allocates and truncates correctly")) {
|
|
DN_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_Str8 str = DN_Str8Lit("HelloBeautifulWorld");
|
|
DN_Str8 trunc = DN_Str8Lit("...");
|
|
DN_Str8TruncResult res = DN_Str8TruncMiddle(str, 5, trunc, &scratch.arena);
|
|
DN_UT_Assert(&result, res.truncated);
|
|
DN_UT_Assert(&result, res.size_req == 13);
|
|
DN_UT_AssertF(&result, DN_Str8Eq(res.str8, DN_Str8Lit("Hello...World")), "%.*s", DN_Str8PrintFmt(res.str8));
|
|
DN_UT_Assert(&result, res.str8.data[res.str8.size] == '\0');
|
|
DN_TCScratchEnd(&scratch);
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static DN_UTCore DN_TST_Win()
|
|
{
|
|
DN_UTCore result = DN_UT_Init();
|
|
#if defined(DN_PLATFORM_WIN32)
|
|
DN_UT_LogF(&result, "OS Win32\n");
|
|
{
|
|
DN_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_Str8 input8 = DN_Str8Lit("String");
|
|
DN_Str16 input16 = DN_Str16{(wchar_t *)(L"String"), sizeof(L"String") / sizeof(L"String"[0]) - 1};
|
|
|
|
for (DN_UT_Test(&result, "Str8 to Str16")) {
|
|
DN_Str16 str_result = DN_OS_W32Str8ToStr16(&scratch.arena, input8);
|
|
DN_UT_Assert(&result, DN_Str16Eq(str_result, input16));
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Str16 to Str8")) {
|
|
DN_Str8 str_result = DN_OS_W32Str16ToStr8(&scratch.arena, input16);
|
|
DN_UT_Assert(&result, DN_Str8Eq(str_result, input8));
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Str16 to Str8: Null terminates string")) {
|
|
int size_required = DN_OS_W32Str16ToStr8Buffer(input16, nullptr, 0);
|
|
char *string = DN_ArenaNewArray(&scratch.arena, char, size_required + 1, DN_ZMem_No);
|
|
|
|
// Fill the string with error sentinels
|
|
DN_Memset(string, 'Z', size_required + 1);
|
|
|
|
int size_returned = DN_OS_W32Str16ToStr8Buffer(input16, string, size_required + 1);
|
|
char const EXPECTED[] = {'S', 't', 'r', 'i', 'n', 'g', 0};
|
|
|
|
DN_UT_AssertF(&result, size_required == size_returned, "string_size: %d, result: %d", size_required, size_returned);
|
|
DN_UT_AssertF(&result, size_returned == DN_ArrayCountU(EXPECTED) - 1, "string_size: %d, expected: %zu", size_returned, DN_ArrayCountU(EXPECTED) - 1);
|
|
DN_UT_Assert(&result, DN_Memcmp(EXPECTED, string, sizeof(EXPECTED)) == 0);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "Str16 to Str8: Arena null terminates string")) {
|
|
DN_Str8 string8 = DN_OS_W32Str16ToStr8(&scratch.arena, input16);
|
|
int size_returned = DN_OS_W32Str16ToStr8Buffer(input16, nullptr, 0);
|
|
char const EXPECTED[] = {'S', 't', 'r', 'i', 'n', 'g', 0};
|
|
|
|
DN_UT_AssertF(&result, DN_Cast(int) string8.size == size_returned, "string_size: %d, result: %d", DN_Cast(int) string8.size, size_returned);
|
|
DN_UT_AssertF(&result, DN_Cast(int) string8.size == DN_ArrayCountU(EXPECTED) - 1, "string_size: %d, expected: %zu", DN_Cast(int) string8.size, DN_ArrayCountU(EXPECTED) - 1);
|
|
DN_UT_Assert(&result, DN_Memcmp(EXPECTED, string8.data, sizeof(EXPECTED)) == 0);
|
|
}
|
|
DN_TCScratchEnd(&scratch);
|
|
}
|
|
#endif // DN_PLATFORM_WIN32
|
|
return result;
|
|
}
|
|
|
|
static DN_UTCore DN_TST_Net()
|
|
{
|
|
DN_UTCore result = DN_UT_Init();
|
|
#if defined(DN_UNIT_TESTS_WITH_NET)
|
|
DN_Str8 label = {};
|
|
DN_NETInterface net_interface = {};
|
|
#if defined(DN_PLATFORM_EMSCRIPTEN)
|
|
net_interface = DN_NET_EmcInterface();
|
|
label = DN_Str8Lit("Emscripten");
|
|
#elif defined(DN_UNIT_TESTS_WITH_CURL)
|
|
net_interface = DN_NET_CurlInterface();
|
|
label = DN_Str8Lit("CURL");
|
|
#endif
|
|
|
|
if (label.size) {
|
|
DN_UT_LogF(&result, "DN_NET\n");
|
|
|
|
DN_MemList mem = DN_MemListFromHeap(DN_Megabytes(4), DN_MemFlags_Nil);
|
|
DN_Arena arena = DN_ArenaFromMemList(&mem);
|
|
DN_Str8 remote_ws_server_url = DN_Str8Lit("wss://echo.websocket.org");
|
|
DN_Str8 remote_http_server_url = DN_Str8Lit("https://google.com");
|
|
|
|
DN_USize net_base_size = DN_Megabytes(1);
|
|
char *net_base = DN_ArenaNewArray(&arena, char, net_base_size, DN_ZMem_Yes);
|
|
DN_NETCore net = {};
|
|
net_interface.init(&net, net_base, net_base_size);
|
|
|
|
DN_U64 arena_reset_p = DN_MemListPos(arena.mem);
|
|
for (DN_UT_Test(&result, "%.*s WaitForResponse HTTP GET request", DN_Str8PrintFmt(label))) {
|
|
DN_NETRequestHandle request = net_interface.do_http(&net, remote_http_server_url, DN_Str8Lit("GET"), nullptr);
|
|
DN_NETResponse response = net_interface.wait_for_response(request, &arena, UINT32_MAX);
|
|
DN_UT_AssertF(&result, response.http_status == 200, "http_status=%u", response.http_status);
|
|
DN_UT_AssertF(&result, response.state == DN_NETResponseState_HTTP, "state=%u", response.state);
|
|
DN_UT_AssertF(&result, response.error_str8.size == 0, "%.*s", DN_Str8PrintFmt(response.error_str8));
|
|
DN_UT_Assert(&result, response.body.size);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "%.*s WaitForResponse HTTP POST request", DN_Str8PrintFmt(label))) {
|
|
net_interface.do_http(&net, remote_http_server_url, DN_Str8Lit("POST"), nullptr);
|
|
DN_NETResponse response = net_interface.wait_for_any_response(&net, &arena, UINT32_MAX);
|
|
DN_UT_AssertF(&result, response.http_status == 200, "http_status=%u", response.http_status);
|
|
DN_UT_AssertF(&result, response.state == DN_NETResponseState_HTTP, "state=%u", response.state);
|
|
DN_UT_AssertF(&result, response.error_str8.size == 0, "error=%.*s", DN_Str8PrintFmt(response.error_str8));
|
|
DN_UT_Assert(&result, response.body.size);
|
|
}
|
|
|
|
for (DN_UT_Test(&result, "%.*s WaitForResponse WS request", DN_Str8PrintFmt(label))) {
|
|
DN_NETRequestHandle request = net_interface.do_ws(&net, remote_ws_server_url);
|
|
DN_USize const WS_TIMEOUT_MS = 16;
|
|
|
|
// NOTE: Wait for WS connection to open
|
|
for (bool done = false; result.state != DN_UTState_TestFailed && !done; DN_MemListPopTo(arena.mem, arena_reset_p)) {
|
|
DN_NETResponse response = net_interface.wait_for_response(request, &arena, WS_TIMEOUT_MS);
|
|
if (response.state == DN_NETResponseState_Nil) // NOTE: Timeout
|
|
continue;
|
|
if (response.state == DN_NETResponseState_Error)
|
|
DN_UT_Log(&result, "ERROR: %.*s", DN_Str8PrintFmt(response.error_str8));
|
|
DN_UT_AssertF(&result, response.state == DN_NETResponseState_WSOpen, "state=%d", response.state);
|
|
done = true;
|
|
}
|
|
|
|
// NOTE: Receive the initial text from the echo server
|
|
for (bool done = false; result.state != DN_UTState_TestFailed && !done; DN_MemListPopTo(arena.mem, arena_reset_p)) {
|
|
DN_NETResponse response = net_interface.wait_for_response(request, &arena, WS_TIMEOUT_MS);
|
|
if (response.state == DN_NETResponseState_Nil) // NOTE: Timeout
|
|
continue;
|
|
if (response.state == DN_NETResponseState_Error)
|
|
DN_UT_Log(&result, "ERROR: %.*s", DN_Str8PrintFmt(response.error_str8));
|
|
DN_UT_AssertF(&result, response.state == DN_NETResponseState_WSText, "state=%d", response.state);
|
|
// NOTE: Send the close signal
|
|
net_interface.do_ws_send(request, DN_Str8Lit(""), DN_NETWSSend_Close);
|
|
done = true;
|
|
}
|
|
|
|
// NOTE: Expect to hear the close
|
|
for (bool done = false; result.state != DN_UTState_TestFailed && !done; DN_MemListPopTo(arena.mem, arena_reset_p)) {
|
|
DN_NETResponse response = net_interface.wait_for_response(request, &arena, WS_TIMEOUT_MS);
|
|
if (response.state == DN_NETResponseState_Nil) // NOTE: Timeout
|
|
continue;
|
|
if (response.state == DN_NETResponseState_Error)
|
|
DN_UT_Log(&result, "ERROR: %.*s", DN_Str8PrintFmt(response.error_str8));
|
|
DN_UT_AssertF(&result, response.state == DN_NETResponseState_WSClose, "state=%d");
|
|
done = true;
|
|
}
|
|
}
|
|
net_interface.deinit(&net);
|
|
DN_MemListDeinit(arena.mem);
|
|
}
|
|
#endif // defined(DN_UNIT_TESTS_WITH_NET)
|
|
return result;
|
|
}
|
|
|
|
DN_TSTResult DN_TST_RunSuite(DN_TSTPrint print)
|
|
{
|
|
DN_UTCore tests[] =
|
|
{
|
|
DN_TST_Base(),
|
|
DN_TST_BaseArena(),
|
|
DN_TST_BaseStrings(),
|
|
DN_TST_BaseBytesHex(),
|
|
#if DN_H_WITH_HELPERS
|
|
DN_TST_BinarySearch(),
|
|
#endif
|
|
DN_TST_BaseDSMap(),
|
|
DN_TST_BaseIArray(),
|
|
DN_TST_BaseCArray2(),
|
|
DN_TST_BaseVArray(),
|
|
DN_TST_Keccak(),
|
|
DN_TST_M4(),
|
|
DN_TST_OS(),
|
|
DN_TST_Rect(),
|
|
DN_TST_Win(),
|
|
DN_TST_Net(),
|
|
};
|
|
|
|
DN_TSTResult result = {};
|
|
for (const DN_UTCore &test : tests) {
|
|
result.total_tests += test.num_tests_in_group;
|
|
result.total_good_tests += test.num_tests_ok_in_group;
|
|
}
|
|
result.passed = result.total_tests == result.total_good_tests;
|
|
|
|
bool print_summary = false;
|
|
for (DN_UTCore &test : tests) {
|
|
if (test.num_tests_in_group <= 0)
|
|
continue;
|
|
bool do_print = print == DN_TSTPrint_Yes;
|
|
if (print == DN_TSTPrint_OnFailure && test.num_tests_ok_in_group != test.num_tests_in_group)
|
|
do_print = true;
|
|
|
|
if (do_print) {
|
|
print_summary = true;
|
|
DN_UT_PrintTests(&test);
|
|
}
|
|
DN_UT_Deinit(&test);
|
|
}
|
|
|
|
if (print_summary)
|
|
fprintf(stdout, "Summary: %d/%d tests succeeded\n", result.total_good_tests, result.total_tests);
|
|
|
|
return result;
|
|
}
|
|
#endif
|
|
|
|
#if DN_CPP_WITH_DEMO
|
|
// DN: Single header generator commented out => #include "Extra/dn_demo.cpp"
|
|
// DN: Single header generator commented out => #if defined(_CLANGD)
|
|
// #include "../dn.h"
|
|
// #endif
|
|
|
|
DN_MSVC_WARNING_PUSH
|
|
DN_MSVC_WARNING_DISABLE(4702) // unreachable code
|
|
void DN_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_AtomicSetValue64
|
|
// NOTE: DN_AtomicSetValue32
|
|
// 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_AtomicSetValue64(&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_HexFromBytes
|
|
{
|
|
DN_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
unsigned char bytes[2] = {0xFA, 0xCE};
|
|
DN_Str8 hex = DN_HexFromBytesPtrArena(bytes, sizeof(bytes), scratch.arena);
|
|
DN_Assert(DN_Str8Eq(hex, DN_Str8Lit("face"))); // NOTE: Guaranteed to be null-terminated
|
|
DN_TCScratchEnd(&scratch);
|
|
}
|
|
|
|
// NOTE: DN_BytesFromHex
|
|
{
|
|
unsigned char bytes[2];
|
|
DN_USize bytes_written = DN_BytesFromHex(DN_Str8Lit("0xFACE"), bytes, sizeof(bytes));
|
|
DN_Assert(bytes_written == 2);
|
|
DN_Assert(bytes[0] == 0xFA);
|
|
DN_Assert(bytes[1] == 0xCE);
|
|
}
|
|
|
|
// 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_DSMapInit
|
|
// NOTE: DN_DSMapDeinit
|
|
//
|
|
// 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_ArenaFromVMem(0, 0, DN_ArenaFlags_Nil);
|
|
DN_DSMap<int> map = DN_DSMapInit<int>(&arena, /*size*/ 1024, DN_DSMapFlags_Nil); // Size must be PoT!
|
|
DN_Assert(DN_DSMapIsValid(&map)); // Valid if no initialisation failure (e.g. mem alloc failure)
|
|
|
|
// NOTE: DN_DSMapKeyCStringLit
|
|
// NOTE: DN_DSMapKeyU64
|
|
// NOTE: DN_DSMapKeyU64NoHash
|
|
// NOTE: DN_DSMapKeyBuffer
|
|
// NOTE: DN_DSMapKeyStr8
|
|
// NOTE: DN_DSMapKeyStr8Copy
|
|
// 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_DSMapKeyStr8(&map, DN_Str8Lit("Sample Key"));
|
|
|
|
// NOTE: DN_DSMapFind
|
|
// NOTE: DN_DSMapMake
|
|
// NOTE: DN_DSMapSet
|
|
//
|
|
// 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<int> set_result = DN_DSMapSet(&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<int> *it = map.slots + index;
|
|
DN_DSMapKey it_key = it->key;
|
|
int *it_value = &it->value;
|
|
DN_Assert(*it_value == 0xCAFE);
|
|
|
|
DN_Assert(DN_Str8Eq(DN_Str8FromPtr(it_key.buffer_data, it_key.buffer_size), DN_Str8Lit("Sample Key")));
|
|
}
|
|
|
|
// NOTE: DN_DSMapErase
|
|
//
|
|
// 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_DSMapErase(&map, key);
|
|
DN_Assert(erased);
|
|
DN_Assert(map.occupied == 1); // Sentinel element
|
|
}
|
|
|
|
DN_DSMapDeinit(&map, DN_ZMem_Yes); // Deallocates the 'arena' for us!
|
|
}
|
|
|
|
// NOTE: DN_DSMapHash
|
|
//
|
|
// 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_DSMapHashToSlotIndex
|
|
//
|
|
// Calculate the index into the map's 'slots' array from the given hash.
|
|
|
|
// NOTE: DN_DSMapResize
|
|
//
|
|
// 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_ErrSink
|
|
//
|
|
// 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_ErrSink *error = DN_TCErrSinkBegin(DN_ErrSinkMode_Nil);
|
|
DN_OSFile file = DN_OS_FileOpen(DN_Str8Lit("/path/to/file"), DN_OSFileOpen_OpenIfExist, DN_OSFileAccess_ReadWrite, error);
|
|
DN_OS_FileWrite(&file, DN_Str8Lit("abc"), error);
|
|
DN_OS_FileClose(&file);
|
|
if (DN_ErrSinkEndLogErrorF(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_ErrSink *error = DN_TCErrSinkBegin(DN_ErrSinkMode_Nil);
|
|
DN_OSFile file = DN_OS_FileOpen(DN_Str8Lit("/path/to/file"), DN_OSFileOpen_OpenIfExist, DN_OSFileAccess_ReadWrite, error);
|
|
DN_OS_FileWrite(&file, DN_Str8Lit("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_TCErrSinkBegin(DN_ErrSinkMode_Nil);
|
|
DN_OS_FileWriteAll(DN_Str8Lit("/path/to/another/file"), DN_Str8Lit("123"), error);
|
|
DN_ErrSinkEndLogErrorF(error, "Failed to write to another file");
|
|
}
|
|
|
|
if (DN_ErrSinkEndLogErrorF(error, "Failed to write to file")) {
|
|
// Do error handling!
|
|
}
|
|
}
|
|
|
|
// 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 '"<key>": <value>' (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.
|
|
//
|
|
// "<name>": <value>
|
|
//
|
|
// And the non-named version emit just the 'value' portion
|
|
|
|
// 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_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_PathIterateDir(DN_Str8Lit("."), &it);) {
|
|
// printf("%.*s\n", DN_Str8PrintFmt(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_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_ErrSink *error = DN_TCErrSinkBegin(DN_ErrSinkMode_Nil);
|
|
DN_OS_FileWriteAllSafe(/*path*/ DN_Str8Lit("C:/Home/my.txt"), /*buffer*/ DN_Str8Lit("Hello world"), error);
|
|
DN_ErrSinkEndLogErrorF(error, "");
|
|
DN_TCScratchEnd(&scratch);
|
|
}
|
|
|
|
// 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
|
|
|
|
// 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.
|
|
#if defined(DN_OS_CPP)
|
|
{
|
|
enum DemoZone
|
|
{
|
|
DemoZone_MainLoop,
|
|
DemoZone_Count
|
|
};
|
|
|
|
#if defined(DN_PLATFORM_EMSCRIPTEN)
|
|
DN_ProfilerTSCNowFunc *tsc_now = DN_OS_PerfCounterNow;
|
|
DN_U64 tsc_frequency = DN_OS_PerfCounterFrequency();
|
|
#else
|
|
DN_ProfilerTSCNowFunc *tsc_now = nullptr;
|
|
DN_U64 tsc_frequency = DN_OS_EstimateTSCPerSecond(100);
|
|
#endif
|
|
|
|
DN_ProfilerAnchor anchors[4] = {};
|
|
DN_USize anchors_count = DN_ArrayCountU(anchors);
|
|
DN_USize anchors_per_frame = anchors_count / 2;
|
|
DN_Profiler profiler = DN_ProfilerInit(anchors, anchors_count, anchors_per_frame, tsc_now, tsc_frequency);
|
|
|
|
for (DN_USize it = 0; it < 1; it++) {
|
|
DN_ProfilerNewFrame(&profiler);
|
|
DN_ProfilerZone zone = DN_ProfilerBeginZone(&profiler, DN_Str8Lit("Main Loop"), DemoZone_MainLoop);
|
|
DN_OS_SleepMs(100);
|
|
DN_ProfilerEndZone(&profiler, zone);
|
|
DN_ProfilerDump(&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_OS_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_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
|
|
// NOTE: DN_OS_StackTraceWalk
|
|
//
|
|
// 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_StackTraceWalk(scratch.arena, /*depth limit*/ 128);
|
|
|
|
// Loop over the addresses produced in the stack trace
|
|
for (DN_StackTraceWalkResultIterator it = {}; DN_StackTraceWalkResultIterate(&it, &walk);) {
|
|
// NOTE: DN_StackTraceRawFrameToFrame
|
|
//
|
|
// Converts the base address into a human readable stack trace
|
|
// entry (e.g. address, line number, file and function name).
|
|
DN_StackTraceFrame frame = DN_StackTraceRawFrameToFrame(scratch.arena, it.raw_frame);
|
|
|
|
// You may then print out the frame like so
|
|
if (0)
|
|
printf("%.*s(%" PRIu64 "): %.*s\n", DN_Str8PrintFmt(frame.file_name), frame.line_number, DN_Str8PrintFmt(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_StackTraceReloadSymbols();
|
|
|
|
// NOTE: DN_OS_StackTraceGetFrames
|
|
//
|
|
// 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_StackTraceFrameSlice frames = DN_StackTraceGetFrames(scratch.arena, /*depth limit*/ 128);
|
|
(void)frames;
|
|
|
|
DN_TCScratchEnd(&scratch);
|
|
}
|
|
|
|
// NOTE: DN_Str8FromArena
|
|
//
|
|
// 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_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_Str8 string = DN_Str8AllocArena(scratch.arena, /*size*/ 1, DN_ZMem_Yes);
|
|
DN_Assert(string.size == 1);
|
|
DN_Assert(string.data[string.size] == 0); // It is null-terminated!
|
|
DN_TCScratchEnd(&scratch);
|
|
}
|
|
|
|
// NOTE: DN_Str8BSplit
|
|
//
|
|
// 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_Str8BSplitResult dot_split = DN_Str8BSplit(/*string*/ DN_Str8Lit("abc.def.ghi"), /*delimiter*/ DN_Str8Lit("."));
|
|
DN_Str8BSplitResult slash_split = DN_Str8BSplit(/*string*/ DN_Str8Lit("abc.def.ghi"), /*delimiter*/ DN_Str8Lit("/"));
|
|
DN_Assert(DN_Str8Eq(dot_split.lhs, DN_Str8Lit("abc")) && DN_Str8Eq(dot_split.rhs, DN_Str8Lit("def.ghi")));
|
|
DN_Assert(DN_Str8Eq(slash_split.lhs, DN_Str8Lit("abc.def.ghi")) && DN_Str8Eq(slash_split.rhs, DN_Str8Lit("")));
|
|
|
|
// Loop that walks the string and produces ("abc", "def", "ghi")
|
|
for (DN_Str8 it = DN_Str8Lit("abc.def.ghi"); it.size;) {
|
|
DN_Str8BSplitResult split = DN_Str8BSplit(it, DN_Str8Lit("."));
|
|
DN_Str8 chunk = split.lhs; // "abc", "def", ...
|
|
it = split.rhs;
|
|
(void)chunk;
|
|
}
|
|
}
|
|
|
|
// NOTE: DN_Str8FileNameFromPath
|
|
//
|
|
// 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_Str8FileNameFromPath(DN_Str8Lit("C:/Folder/item.txt"));
|
|
DN_Assert(DN_Str8Eq(string, DN_Str8Lit("item.txt")));
|
|
}
|
|
{
|
|
// TODO(doyle): Intuitively this seems incorrect. Empty string instead?
|
|
DN_Str8 string = DN_Str8FileNameFromPath(DN_Str8Lit("C:/Folder/"));
|
|
DN_Assert(DN_Str8Eq(string, DN_Str8Lit("C:/Folder")));
|
|
}
|
|
{
|
|
DN_Str8 string = DN_Str8FileNameFromPath(DN_Str8Lit("C:/Folder"));
|
|
DN_Assert(DN_Str8Eq(string, DN_Str8Lit("Folder")));
|
|
}
|
|
}
|
|
|
|
// NOTE: DN_Str8FilePathNoExtension
|
|
//
|
|
// 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_Str8FilePathNoExtension(DN_Str8Lit("C:/Folder/item.txt.bak"));
|
|
DN_Assert(DN_Str8Eq(string, DN_Str8Lit("C:/Folder/item.txt")));
|
|
}
|
|
|
|
// NOTE: DN_Str8FileNameNoExtension
|
|
//
|
|
// This function is the same as calling 'FileNameFromPath' followed by
|
|
// 'FilePathNoExtension'
|
|
{
|
|
DN_Str8 string = DN_Str8FileNameNoExtension(DN_Str8Lit("C:/Folder/item.txt.bak"));
|
|
DN_Assert(DN_Str8Eq(string, DN_Str8Lit("item.txt")));
|
|
}
|
|
|
|
// NOTE: DN_Str8Replace
|
|
// NOTE: DN_Str8ReplaceInsensitive
|
|
//
|
|
// 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_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_Str8 string = DN_Str8Replace(/*string*/ DN_Str8Lit("Foo Foo Bar"),
|
|
/*find*/ DN_Str8Lit("Foo"),
|
|
/*replace*/ DN_Str8Lit("Moo"),
|
|
/*start_index*/ 1,
|
|
/*arena*/ scratch.arena,
|
|
/*eq_case*/ DN_Str8EqCase_Sensitive);
|
|
DN_Assert(DN_Str8Eq(string, DN_Str8Lit("Foo Moo Bar")));
|
|
DN_TCScratchEnd(&scratch);
|
|
}
|
|
|
|
// NOTE: DN_Str8Segment
|
|
//
|
|
// 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_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_Str8 string = DN_Str8Segment(scratch.arena, /*string*/ DN_Str8Lit("123456789"), /*segment_size*/ 3, /*segment_char*/ ',');
|
|
DN_Assert(DN_Str8Eq(string, DN_Str8Lit("123,456,789")));
|
|
DN_TCScratchEnd(&scratch);
|
|
}
|
|
|
|
// NOTE: DN_Str8Split
|
|
{
|
|
// Splits the string at each delimiter into substrings occuring prior and
|
|
// after until the next delimiter.
|
|
DN_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
{
|
|
DN_Str8SplitResult splits = DN_Str8SplitArena(/*arena*/ scratch.arena,
|
|
/*string*/ DN_Str8Lit("192.168.8.1"),
|
|
/*delimiter*/ DN_Str8Lit("."),
|
|
/*mode*/ DN_Str8SplitIncludeEmptyStrings_No);
|
|
DN_Assert(splits.count == 4);
|
|
DN_Assert(DN_Str8Eq(splits.data[0], DN_Str8Lit("192")) &&
|
|
DN_Str8Eq(splits.data[1], DN_Str8Lit("168")) &&
|
|
DN_Str8Eq(splits.data[2], DN_Str8Lit("8")) &&
|
|
DN_Str8Eq(splits.data[3], DN_Str8Lit("1")));
|
|
}
|
|
|
|
// You can include empty strings that occur when splitting by setting
|
|
// the split mode to include empty strings.
|
|
{
|
|
DN_Str8SplitResult splits = DN_Str8SplitArena(/*arena*/ scratch.arena,
|
|
/*string*/ DN_Str8Lit("a--b"),
|
|
/*delimiter*/ DN_Str8Lit("-"),
|
|
/*mode*/ DN_Str8SplitIncludeEmptyStrings_Yes);
|
|
DN_Assert(splits.count == 3);
|
|
DN_Assert(DN_Str8Eq(splits.data[0], DN_Str8Lit("a")) &&
|
|
DN_Str8Eq(splits.data[1], DN_Str8Lit("")) &&
|
|
DN_Str8Eq(splits.data[2], DN_Str8Lit("b")));
|
|
}
|
|
|
|
DN_TCScratchEnd(&scratch);
|
|
}
|
|
|
|
// NOTE: DN_I64FromStr8, DN_U64FromStr8
|
|
//
|
|
// 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_I64FromResult result = DN_I64FromStr8(DN_Str8Lit("-1,234"), /*separator*/ ',');
|
|
DN_Assert(result.success && result.value == -1234);
|
|
}
|
|
{
|
|
DN_I64FromResult result = DN_I64FromStr8(DN_Str8Lit("-1,234"), /*separator*/ 0);
|
|
DN_Assert(!result.success && result.value == 1); // 1 because it's a greedy conversion
|
|
}
|
|
}
|
|
|
|
// NOTE: DN_Str8TrimByteOrderMark
|
|
//
|
|
// Removes a leading UTF8, UTF16 BE/LE, UTF32 BE/LE byte order mark from the
|
|
// string if it's present.
|
|
|
|
// NOTE: DN_Str8PrintFmt
|
|
//
|
|
// 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_Str8PrintFmt(DN_Str8Lit("Hello world")));
|
|
|
|
// NOTE: DN_Str8BuilderAppendF
|
|
// NOTE: DN_Str8BuilderAppendFV
|
|
// NOTE: DN_Str8BuilderAppendRef
|
|
// NOTE: DN_Str8BuilderAppendCopy
|
|
//
|
|
// - 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_Str8BuilderBuild
|
|
// NOTE: DN_Str8BuilderBuildCRT
|
|
//
|
|
// 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_Str8BuilderBuildSlice
|
|
//
|
|
// 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) scratch and
|
|
// permanent arena allocators. These can be used for allocations with a
|
|
// lifetime scoped to the lexical scope or for storing data permanently
|
|
// using the arena paradigm.
|
|
//
|
|
// TLS in this implementation is implemented using the `thread_local` C/C++
|
|
// keyword.
|
|
//
|
|
// 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 scratch
|
|
// 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 scratch arena requested in the function, we risk the scratch 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 scratch 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_TCScratch scratch_a = DN_TCScratchBegin(nullptr, 0);
|
|
|
|
// Now imagine we call a function where we pass scratch_a.arena down
|
|
// into it .. If we call scratch again, we need to pass in the arena
|
|
// to prevent aliasing.
|
|
DN_TCScratch scratch_b = DN_TCScratchBegin(&scratch_a.arena, 1);
|
|
DN_Assert(scratch_a.arena != scratch_b.arena);
|
|
|
|
DN_TCScratchEnd(&scratch_b);
|
|
DN_TCScratchEnd(&scratch_a);
|
|
}
|
|
|
|
// @proc DN_Thread_Getscratch
|
|
// @desc Retrieve the per-thread temporary arena allocator that is reset on scope
|
|
// exit.
|
|
|
|
// The scratch arena must be deconflicted with any existing arenas in the
|
|
// function to avoid trampling over each other's memory. Consider the situation
|
|
// where the scratch arena is passed into the function. Inside the function, if
|
|
// the same arena is reused then, if both arenas allocate, when the inner arena
|
|
// is reset, this will undo the passed in arena's allocations in the function.
|
|
|
|
// @param[in] conflict_arena A pointer to the arena currently being used in the
|
|
// function
|
|
|
|
// NOTE: DN_Str8x32FromFmt
|
|
{
|
|
DN_Str8x32 string = DN_Str8x32FromFmt("%d", 123123);
|
|
if (0) // Prints "123123"
|
|
printf("%.*s", DN_Str8PrintFmt(string));
|
|
}
|
|
|
|
// NOTE: DN_CVT_AgeFromU64
|
|
{
|
|
DN_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_Str8x128 string = DN_AgeStr8FromSecF64(DN_SecFromHours(2) + DN_SecFromMins(30), DN_AgeUnit_All);
|
|
DN_Assert(DN_Str8Eq(DN_Str8FromStruct(&string), DN_Str8Lit("2h 30m")));
|
|
DN_TCScratchEnd(&scratch);
|
|
}
|
|
|
|
// 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_OS_VArrayInit
|
|
// NOTE: DN_OS_VArrayInitByteSize
|
|
//
|
|
// 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<int> array = DN_OS_VArrayInit<int>(1024);
|
|
DN_Assert(array.size == 0 && array.max >= 1024);
|
|
|
|
// NOTE: DN_OS_VArrayMake
|
|
// NOTE: DN_OS_VArrayAdd
|
|
// NOTE: DN_OS_VArrayMakeArray
|
|
// NOTE: DN_OS_VArrayAddArray
|
|
//
|
|
// 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_OS_VArrayAdd(&array, 0xCAFE);
|
|
DN_Assert(*item == 0xCAFE && array.size == 1);
|
|
|
|
// NOTE: DN_OS_VArrayAddCArray
|
|
DN_OS_VArrayAddCArray(&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_OS_VArrayEraseRange
|
|
//
|
|
// 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_OS_VArrayEraseRange(&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_OS_VArrayReserve
|
|
//
|
|
// 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_OS_VArrayReserve(&array, /*count*/ 8);
|
|
|
|
DN_OS_VArrayDeinit(&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_TCScratch scratch = DN_TCScratchBegin(nullptr, 0);
|
|
DN_OSW32Error get_last_error = DN_OS_W32LastError(scratch.arena);
|
|
printf("Error (%lu): %.*s", get_last_error.code, DN_Str8PrintFmt(get_last_error.msg));
|
|
|
|
// Alternatively, pass in the error code directly
|
|
DN_OSW32Error error_msg_for_code = DN_OS_W32ErrorCodeToMsg(scratch.arena, /*error_code*/ 0);
|
|
printf("Error (%lu): %.*s", error_msg_for_code.code, DN_Str8PrintFmt(error_msg_for_code.msg));
|
|
DN_TCScratchEnd(&scratch);
|
|
}
|
|
|
|
// 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
|
|
#endif
|
|
|
|
#define DN_BIN_PACK_CPP
|
|
|
|
// DN: Single header generator commented out => #if defined(_CLANGD)
|
|
// #include "dn_bin_pack.h"
|
|
// #endif
|
|
|
|
DN_API void DN_BinPackU64(DN_BinPack *pack, DN_BinPackMode mode, DN_U64 *item)
|
|
{
|
|
DN_U64 const VALUE_MASK = 0b0111'1111;
|
|
DN_U8 const CONTINUE_BIT = 0b1000'0000;
|
|
|
|
if (mode == DN_BinPackMode_Serialise) {
|
|
DN_U64 it = *item;
|
|
do {
|
|
DN_U8 write_value = DN_Cast(DN_U8)(it & VALUE_MASK);
|
|
it >>= 7;
|
|
if (it)
|
|
write_value |= CONTINUE_BIT;
|
|
DN_Str8BuilderAppendBytesCopy(&pack->writer, &write_value, sizeof(write_value));
|
|
} while (it);
|
|
} else {
|
|
*item = 0;
|
|
DN_USize bits_read = 0;
|
|
for (DN_U8 src = CONTINUE_BIT; (src & CONTINUE_BIT) && bits_read < 64; bits_read += 7) {
|
|
src = pack->read.data[pack->read_index++];
|
|
DN_U8 masked_src = src & VALUE_MASK;
|
|
*item |= (DN_Cast(DN_U64) masked_src << bits_read);
|
|
}
|
|
}
|
|
}
|
|
|
|
DN_API void DN_BinPackVarInt_(DN_BinPack *pack, DN_BinPackMode mode, void *item, DN_USize size)
|
|
{
|
|
DN_U64 value = 0;
|
|
DN_AssertF(size <= sizeof(value),
|
|
"An item larger than 64 bits (%zu) is trying to be packed as a variable integer which is not supported",
|
|
size * 8);
|
|
|
|
if (mode == DN_BinPackMode_Serialise) // Read `item` into U64 `value`
|
|
DN_Memcpy(&value, item, size);
|
|
|
|
DN_BinPackU64(pack, mode, &value);
|
|
|
|
if (mode == DN_BinPackMode_Deserialise) // Write U64 `value` into `item`
|
|
DN_Memcpy(item, &value, size);
|
|
}
|
|
|
|
DN_API bool DN_BinPackIsEndOfReadStream(DN_BinPack const *pack)
|
|
{
|
|
bool result = pack->read_index == pack->read.size;
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_BinPackUSize(DN_BinPack *pack, DN_BinPackMode mode, DN_USize *item)
|
|
{
|
|
DN_BinPackVarInt_(pack, mode, item, sizeof(*item));
|
|
}
|
|
|
|
DN_API void DN_BinPackU32(DN_BinPack *pack, DN_BinPackMode mode, DN_U32 *item)
|
|
{
|
|
DN_BinPackVarInt_(pack, mode, item, sizeof(*item));
|
|
}
|
|
|
|
DN_API void DN_BinPackU16(DN_BinPack *pack, DN_BinPackMode mode, DN_U16 *item)
|
|
{
|
|
DN_BinPackVarInt_(pack, mode, item, sizeof(*item));
|
|
}
|
|
|
|
DN_API void DN_BinPackU8(DN_BinPack *pack, DN_BinPackMode mode, DN_U8 *item)
|
|
{
|
|
DN_BinPackVarInt_(pack, mode, item, sizeof(*item));
|
|
}
|
|
|
|
DN_API void DN_BinPackI64(DN_BinPack *pack, DN_BinPackMode mode, DN_I64 *item)
|
|
{
|
|
DN_BinPackVarInt_(pack, mode, item, sizeof(*item));
|
|
}
|
|
|
|
DN_API void DN_BinPackI32(DN_BinPack *pack, DN_BinPackMode mode, DN_I32 *item)
|
|
{
|
|
DN_BinPackVarInt_(pack, mode, item, sizeof(*item));
|
|
}
|
|
|
|
DN_API void DN_BinPackI16(DN_BinPack *pack, DN_BinPackMode mode, DN_I16 *item)
|
|
{
|
|
DN_BinPackVarInt_(pack, mode, item, sizeof(*item));
|
|
}
|
|
|
|
DN_API void DN_BinPackI8(DN_BinPack *pack, DN_BinPackMode mode, DN_I8 *item)
|
|
{
|
|
DN_BinPackVarInt_(pack, mode, item, sizeof(*item));
|
|
}
|
|
|
|
DN_API void DN_BinPackF64(DN_BinPack *pack, DN_BinPackMode mode, DN_F64 *item)
|
|
{
|
|
DN_BinPackVarInt_(pack, mode, item, sizeof(*item));
|
|
}
|
|
|
|
DN_API void DN_BinPackF32(DN_BinPack *pack, DN_BinPackMode mode, DN_F32 *item)
|
|
{
|
|
DN_BinPackVarInt_(pack, mode, item, sizeof(*item));
|
|
}
|
|
|
|
#if defined(DN_MATH_H)
|
|
DN_API void DN_BinPackV2(DN_BinPack *pack, DN_BinPackMode mode, DN_V2F32 *item)
|
|
{
|
|
DN_BinPackF32(pack, mode, &item->x);
|
|
DN_BinPackF32(pack, mode, &item->y);
|
|
}
|
|
|
|
DN_API void DN_BinPackV4(DN_BinPack *pack, DN_BinPackMode mode, DN_V4F32 *item)
|
|
{
|
|
DN_BinPackF32(pack, mode, &item->x);
|
|
DN_BinPackF32(pack, mode, &item->y);
|
|
DN_BinPackF32(pack, mode, &item->z);
|
|
DN_BinPackF32(pack, mode, &item->w);
|
|
}
|
|
#endif
|
|
|
|
DN_API void DN_BinPackBool(DN_BinPack *pack, DN_BinPackMode mode, bool *item)
|
|
{
|
|
DN_BinPackVarInt_(pack, mode, item, sizeof(*item));
|
|
}
|
|
|
|
DN_API void DN_BinPackStr8FromArena(DN_BinPack *pack, DN_Arena *arena, DN_BinPackMode mode, DN_Str8 *string)
|
|
{
|
|
DN_BinPackVarInt_(pack, mode, &string->size, sizeof(string->size));
|
|
if (mode == DN_BinPackMode_Serialise) {
|
|
DN_Str8BuilderAppendBytesCopy(&pack->writer, string->data, string->size);
|
|
} else {
|
|
DN_Str8 src = DN_Str8Subset(pack->read, pack->read_index, string->size);
|
|
*string = DN_Str8FromStr8Arena(src, arena);
|
|
pack->read_index += src.size;
|
|
}
|
|
}
|
|
|
|
DN_API void DN_BinPackStr8FromPool(DN_BinPack *pack, DN_Pool *pool, DN_BinPackMode mode, DN_Str8 *string)
|
|
{
|
|
DN_BinPackVarInt_(pack, mode, &string->size, sizeof(string->size));
|
|
if (mode == DN_BinPackMode_Serialise) {
|
|
DN_Str8BuilderAppendBytesCopy(&pack->writer, string->data, string->size);
|
|
} else {
|
|
DN_Str8 src = DN_Str8Subset(pack->read, pack->read_index, string->size);
|
|
*string = DN_Str8FromStr8Pool(src, pool);
|
|
pack->read_index += src.size;
|
|
}
|
|
}
|
|
|
|
DN_API DN_Str8 DN_BinPackStr8FromBuffer(DN_BinPack *pack, DN_BinPackMode mode, char *ptr, DN_USize *size, DN_USize max)
|
|
{
|
|
DN_BinPackCBuffer(pack, mode, ptr, size, max);
|
|
DN_Str8 result = DN_Str8FromPtr(ptr, *size);
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_BinPackBytesFromArena(DN_BinPack *pack, DN_Arena *arena, DN_BinPackMode mode, void **ptr, DN_USize *size)
|
|
{
|
|
DN_Str8 string = DN_Str8FromPtr(*ptr, *size);
|
|
DN_BinPackStr8FromArena(pack, arena, mode, &string);
|
|
*ptr = string.data;
|
|
*size = string.size;
|
|
}
|
|
|
|
DN_API void DN_BinPackBytesFromPool(DN_BinPack *pack, DN_Pool *pool, DN_BinPackMode mode, void **ptr, DN_USize *size)
|
|
{
|
|
DN_Str8 string = DN_Str8FromPtr(*ptr, *size);
|
|
DN_BinPackStr8FromPool(pack, pool, mode, &string);
|
|
*ptr = string.data;
|
|
*size = string.size;
|
|
}
|
|
|
|
DN_API void DN_BinPackCArray(DN_BinPack *pack, DN_BinPackMode mode, void *ptr, DN_USize size)
|
|
{
|
|
DN_BinPackVarInt_(pack, mode, &size, sizeof(size));
|
|
if (mode == DN_BinPackMode_Serialise) {
|
|
DN_Str8BuilderAppendBytesCopy(&pack->writer, ptr, size);
|
|
} else {
|
|
DN_Str8 src = DN_Str8Subset(pack->read, pack->read_index, size);
|
|
DN_Assert(src.size == size);
|
|
DN_Memcpy(ptr, src.data, DN_Min(src.size, size));
|
|
pack->read_index += src.size;
|
|
}
|
|
}
|
|
|
|
DN_API void DN_BinPackCBuffer(DN_BinPack *pack, DN_BinPackMode mode, char *ptr, DN_USize *size, DN_USize max)
|
|
{
|
|
if (mode == DN_BinPackMode_Serialise) {
|
|
DN_BinPackUSize(pack, mode, size);
|
|
DN_Str8BuilderAppendBytesCopy(&pack->writer, ptr, *size);
|
|
} else {
|
|
DN_U64 size_u64 = 0;
|
|
DN_BinPackU64(pack, mode, &size_u64);
|
|
DN_Assert(size_u64 < DN_USIZE_MAX);
|
|
DN_Assert(size_u64 <= max);
|
|
|
|
*size = DN_Min(size_u64, max);
|
|
DN_Memcpy(ptr, pack->read.data + pack->read_index, *size);
|
|
pack->read_index += size_u64;
|
|
}
|
|
}
|
|
|
|
DN_API DN_Str8 DN_BinPackBuild(DN_BinPack const *pack, DN_Arena *arena)
|
|
{
|
|
DN_Str8 result = DN_Str8BuilderBuild(&pack->writer, arena);
|
|
return result;
|
|
}
|
|
#define DN_CSV_CPP
|
|
|
|
// DN: Single header generator commented out => #include "dn_csv.h"
|
|
#if !defined(DN_CSV_H)
|
|
#define DN_CSV_H
|
|
|
|
// NOTE: Data structures to create and parse CSV files, supports Python style escaped quotes (e.g.
|
|
// Using "" to escape quotes inside a quoted string).
|
|
//
|
|
// API
|
|
// DN_CSV_TokeniserNextN: Reads the next N consecutive fields from the parser. If `column_iterator`
|
|
// is `false` then the read of the N consecutive fields does not proceed past the end of the
|
|
// current CSV row. If `true` then it reads the next N fields even if reading would progress onto
|
|
// the next row.
|
|
|
|
// DN: Single header generator commented out => #if defined(_CLANGD)
|
|
// #include "../dn.h"
|
|
// #endif
|
|
|
|
enum DN_CSVSerialise
|
|
{
|
|
DN_CSVSerialise_Read,
|
|
DN_CSVSerialise_Write,
|
|
};
|
|
|
|
struct DN_CSVTokeniser
|
|
{
|
|
bool bad;
|
|
DN_Str8 string;
|
|
char delimiter;
|
|
char const *it;
|
|
bool end_of_line;
|
|
};
|
|
|
|
struct DN_CSVPack
|
|
{
|
|
DN_Str8Builder write_builder;
|
|
DN_USize write_column;
|
|
DN_CSVTokeniser read_tokeniser;
|
|
};
|
|
|
|
DN_CSVTokeniser DN_CSV_TokeniserInit (DN_Str8 string, char delimiter);
|
|
bool DN_CSV_TokeniserValid (DN_CSVTokeniser *tokeniser);
|
|
bool DN_CSV_TokeniserNextRow (DN_CSVTokeniser *tokeniser);
|
|
DN_Str8 DN_CSV_TokeniserNextField (DN_CSVTokeniser *tokeniser);
|
|
DN_Str8 DN_CSV_TokeniserNextColumn (DN_CSVTokeniser *tokeniser);
|
|
void DN_CSV_TokeniserSkipLine (DN_CSVTokeniser *tokeniser);
|
|
int DN_CSV_TokeniserNextN (DN_CSVTokeniser *tokeniser, DN_Str8 *fields, int fields_size, bool column_iterator);
|
|
int DN_CSV_TokeniserNextColumnN(DN_CSVTokeniser *tokeniser, DN_Str8 *fields, int fields_size);
|
|
int DN_CSV_TokeniserNextFieldN (DN_CSVTokeniser *tokeniser, DN_Str8 *fields, int fields_size);
|
|
void DN_CSV_TokeniserSkipLineN (DN_CSVTokeniser *tokeniser, int count);
|
|
void DN_CSV_PackU64 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_U64 *value);
|
|
void DN_CSV_PackI64 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I64 *value);
|
|
void DN_CSV_PackI32 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I32 *value);
|
|
void DN_CSV_PackI16 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I16 *value);
|
|
void DN_CSV_PackI8 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I8 *value);
|
|
void DN_CSV_PackU32 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_U32 *value);
|
|
void DN_CSV_PackU16 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_U16 *value);
|
|
void DN_CSV_PackBoolAsU64 (DN_CSVPack *pack, DN_CSVSerialise serialise, bool *value);
|
|
void DN_CSV_PackStr8 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_Str8 *str8, DN_Arena *arena);
|
|
void DN_CSV_PackBuffer (DN_CSVPack *pack, DN_CSVSerialise serialise, void *dest, size_t *size);
|
|
void DN_CSV_PackBufferWithMax (DN_CSVPack *pack, DN_CSVSerialise serialise, void *dest, size_t *size, size_t max);
|
|
bool DN_CSV_PackNewLine (DN_CSVPack *pack, DN_CSVSerialise serialise);
|
|
|
|
#endif // !defined(DN_CSV_H)
|
|
|
|
DN_CSVTokeniser DN_CSV_TokeniserInit(DN_Str8 string, char delimiter)
|
|
{
|
|
DN_CSVTokeniser result = {};
|
|
result.string = string;
|
|
result.delimiter = delimiter;
|
|
return result;
|
|
}
|
|
|
|
bool DN_CSV_TokeniserValid(DN_CSVTokeniser *tokeniser)
|
|
{
|
|
bool result = tokeniser && !tokeniser->bad;
|
|
return result;
|
|
}
|
|
|
|
static void DN_CSV_TokeniserEatNewLines_(DN_CSVTokeniser *tokeniser)
|
|
{
|
|
char const *end = tokeniser->string.data + tokeniser->string.size;
|
|
while (tokeniser->it[0] == '\n' || tokeniser->it[0] == '\r')
|
|
if (++tokeniser->it == end)
|
|
break;
|
|
}
|
|
|
|
bool DN_CSV_TokeniserNextRow(DN_CSVTokeniser *tokeniser)
|
|
{
|
|
bool result = false;
|
|
if (DN_CSV_TokeniserValid(tokeniser) && tokeniser->string.size) {
|
|
// NOTE: First time querying row iterator is nil, let tokeniser advance
|
|
if (tokeniser->it) {
|
|
// NOTE: Only advance the tokeniser if we're at the end of the line and
|
|
// there's more to tokenise.
|
|
char const *end = tokeniser->string.data + tokeniser->string.size;
|
|
if (tokeniser->it != end && tokeniser->end_of_line) {
|
|
tokeniser->end_of_line = false;
|
|
result = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
DN_Str8 DN_CSV_TokeniserNextField(DN_CSVTokeniser *tokeniser)
|
|
{
|
|
DN_Str8 result = {};
|
|
if (!DN_CSV_TokeniserValid(tokeniser))
|
|
return result;
|
|
|
|
if (tokeniser->string.size == 0) {
|
|
tokeniser->bad = true;
|
|
return result;
|
|
}
|
|
|
|
// NOTE: First time tokeniser is invoked with a string, set up initial state.
|
|
char const *string_end = tokeniser->string.data + tokeniser->string.size;
|
|
if (!tokeniser->it) {
|
|
tokeniser->it = tokeniser->string.data;
|
|
DN_CSV_TokeniserEatNewLines_(tokeniser); // NOTE: Skip any leading new lines
|
|
}
|
|
|
|
// NOTE: Tokeniser pointing at end, no more valid data to parse.
|
|
if (tokeniser->it == string_end)
|
|
return result;
|
|
|
|
// NOTE: Scan forward until the next control character.
|
|
// 1. '"' Double quoted field, extract everything between the quotes.
|
|
// 2. tokeniser->delimiter End of the field, extract everything leading up to the delimiter.
|
|
// 3. '\n' Last field in record, extract everything leading up the the new line.
|
|
char const *begin = tokeniser->it;
|
|
while (tokeniser->it != string_end && (tokeniser->it[0] != '"' &&
|
|
tokeniser->it[0] != tokeniser->delimiter &&
|
|
tokeniser->it[0] != '\n'))
|
|
tokeniser->it++;
|
|
|
|
bool quoted_field = (tokeniser->it != string_end) && tokeniser->it[0] == '"';
|
|
if (quoted_field) {
|
|
begin = ++tokeniser->it; // Begin after the quote
|
|
|
|
// NOTE: Scan forward until the next '"' which marks the end
|
|
// of the field unless it is escaped by another '"'.
|
|
find_next_quote:
|
|
while (tokeniser->it != string_end && tokeniser->it[0] != '"')
|
|
tokeniser->it++;
|
|
|
|
// NOTE: If we encounter a '"' right after, the quotes were escaped
|
|
// and we need to skip to the next instance of a '"'.
|
|
if (tokeniser->it != string_end && tokeniser->it + 1 != string_end && tokeniser->it[1] == '"') {
|
|
tokeniser->it += 2;
|
|
goto find_next_quote;
|
|
}
|
|
}
|
|
|
|
// NOTE: Mark the end of the field
|
|
char const *end = tokeniser->it;
|
|
tokeniser->end_of_line = tokeniser->it == string_end || end[0] == '\n';
|
|
|
|
// NOTE: In files with \r\n style new lines ensure that we don't include
|
|
// the \r byte in the CSV field we produce.
|
|
if (end != string_end && end[0] == '\n') {
|
|
DN_Assert((uintptr_t)(end - 1) > (uintptr_t)tokeniser->string.data &&
|
|
"Internal error: The string iterator is pointing behind the start of the string we're reading");
|
|
if (end[-1] == '\r')
|
|
end = end - 1;
|
|
}
|
|
|
|
// NOTE: Quoted fields may have whitespace after the closing quote, we skip
|
|
// until we reach the field terminator.
|
|
if (quoted_field)
|
|
while (tokeniser->it != string_end && (tokeniser->it[0] != tokeniser->delimiter && tokeniser->it[0] != '\n'))
|
|
tokeniser->it++;
|
|
|
|
// NOTE: Advance the tokeniser past the field terminator.
|
|
if (tokeniser->it != string_end)
|
|
tokeniser->it++;
|
|
|
|
// NOTE: Generate the record
|
|
result.data = DN_Cast(char *) begin;
|
|
result.size = DN_Cast(int)(end - begin);
|
|
return result;
|
|
}
|
|
|
|
DN_Str8 DN_CSV_TokeniserNextColumn(DN_CSVTokeniser *tokeniser)
|
|
{
|
|
DN_Str8 result = {};
|
|
if (!DN_CSV_TokeniserValid(tokeniser))
|
|
return result;
|
|
|
|
// NOTE: End of line, the user must explicitly advance to the next row
|
|
if (tokeniser->end_of_line)
|
|
return result;
|
|
|
|
// NOTE: Advance tokeniser to the next field in the row
|
|
result = DN_CSV_TokeniserNextField(tokeniser);
|
|
return result;
|
|
}
|
|
|
|
void DN_CSV_TokeniserSkipLine(DN_CSVTokeniser *tokeniser)
|
|
{
|
|
while (DN_CSV_TokeniserValid(tokeniser) && !tokeniser->end_of_line)
|
|
DN_CSV_TokeniserNextColumn(tokeniser);
|
|
DN_CSV_TokeniserNextRow(tokeniser);
|
|
}
|
|
|
|
int DN_CSV_TokeniserNextN(DN_CSVTokeniser *tokeniser, DN_Str8 *fields, int fields_size, bool column_iterator)
|
|
{
|
|
if (!DN_CSV_TokeniserValid(tokeniser) || !fields || fields_size <= 0)
|
|
return 0;
|
|
|
|
int result = 0;
|
|
for (; result < fields_size; result++) {
|
|
fields[result] = column_iterator ? DN_CSV_TokeniserNextColumn(tokeniser) : DN_CSV_TokeniserNextField(tokeniser);
|
|
if (!DN_CSV_TokeniserValid(tokeniser) || !fields[result].data)
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
int DN_CSV_TokeniserNextColumnN(DN_CSVTokeniser *tokeniser, DN_Str8 *fields, int fields_size)
|
|
{
|
|
int result = DN_CSV_TokeniserNextN(tokeniser, fields, fields_size, true /*column_iterator*/);
|
|
return result;
|
|
}
|
|
|
|
int DN_CSV_TokeniserNextFieldN(DN_CSVTokeniser *tokeniser, DN_Str8 *fields, int fields_size)
|
|
{
|
|
int result = DN_CSV_TokeniserNextN(tokeniser, fields, fields_size, false /*column_iterator*/);
|
|
return result;
|
|
}
|
|
|
|
void DN_CSV_TokeniserSkipLineN(DN_CSVTokeniser *tokeniser, int count)
|
|
{
|
|
for (int i = 0; i < count && DN_CSV_TokeniserValid(tokeniser); i++)
|
|
DN_CSV_TokeniserSkipLine(tokeniser);
|
|
}
|
|
|
|
void DN_CSV_PackU64(DN_CSVPack *pack, DN_CSVSerialise serialise, DN_U64 *value)
|
|
{
|
|
if (serialise == DN_CSVSerialise_Read) {
|
|
DN_Str8 csv_value = DN_CSV_TokeniserNextColumn(&pack->read_tokeniser);
|
|
DN_U64FromResult to_u64 = DN_U64FromStr8(csv_value, 0);
|
|
DN_Assert(to_u64.success);
|
|
*value = to_u64.value;
|
|
} else {
|
|
DN_Str8BuilderAppendF(&pack->write_builder, "%s%" PRIu64, pack->write_column++ ? "," : "", *value);
|
|
}
|
|
}
|
|
|
|
void DN_CSV_PackI64(DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I64 *value)
|
|
{
|
|
if (serialise == DN_CSVSerialise_Read) {
|
|
DN_Str8 csv_value = DN_CSV_TokeniserNextColumn(&pack->read_tokeniser);
|
|
DN_I64FromResult to_i64 = DN_I64FromStr8(csv_value, 0);
|
|
DN_Assert(to_i64.success);
|
|
*value = to_i64.value;
|
|
} else {
|
|
DN_Str8BuilderAppendF(&pack->write_builder, "%s%" PRIu64, pack->write_column++ ? "," : "", *value);
|
|
}
|
|
}
|
|
|
|
void DN_CSV_PackI32(DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I32 *value)
|
|
{
|
|
DN_I64 u64 = *value;
|
|
DN_CSV_PackI64(pack, serialise, &u64);
|
|
if (serialise == DN_CSVSerialise_Read)
|
|
*value = DN_SaturateCastI64ToI32(u64);
|
|
}
|
|
|
|
void DN_CSV_PackI16(DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I16 *value)
|
|
{
|
|
DN_I64 u64 = *value;
|
|
DN_CSV_PackI64(pack, serialise, &u64);
|
|
if (serialise == DN_CSVSerialise_Read)
|
|
*value = DN_SaturateCastI64ToI16(u64);
|
|
}
|
|
|
|
void DN_CSV_PackI8(DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I8 *value)
|
|
{
|
|
DN_I64 u64 = *value;
|
|
DN_CSV_PackI64(pack, serialise, &u64);
|
|
if (serialise == DN_CSVSerialise_Read)
|
|
*value = DN_SaturateCastI64ToI8(u64);
|
|
}
|
|
|
|
void DN_CSV_PackU32(DN_CSVPack *pack, DN_CSVSerialise serialise, DN_U32 *value)
|
|
{
|
|
DN_U64 u64 = *value;
|
|
DN_CSV_PackU64(pack, serialise, &u64);
|
|
if (serialise == DN_CSVSerialise_Read)
|
|
*value = DN_SaturateCastU64ToU32(u64);
|
|
}
|
|
|
|
void DN_CSV_PackU16(DN_CSVPack *pack, DN_CSVSerialise serialise, DN_U16 *value)
|
|
{
|
|
DN_U64 u64 = *value;
|
|
DN_CSV_PackU64(pack, serialise, &u64);
|
|
if (serialise == DN_CSVSerialise_Read)
|
|
*value = DN_SaturateCastU64ToU16(u64);
|
|
}
|
|
|
|
void DN_CSV_PackBoolAsU64(DN_CSVPack *pack, DN_CSVSerialise serialise, bool *value)
|
|
{
|
|
DN_U64 u64 = *value;
|
|
DN_CSV_PackU64(pack, serialise, &u64);
|
|
if (serialise == DN_CSVSerialise_Read)
|
|
*value = u64 ? 1 : 0;
|
|
}
|
|
|
|
void DN_CSV_PackStr8(DN_CSVPack *pack, DN_CSVSerialise serialise, DN_Str8 *str8, DN_Arena *arena)
|
|
{
|
|
if (serialise == DN_CSVSerialise_Read) {
|
|
DN_Str8 csv_value = DN_CSV_TokeniserNextColumn(&pack->read_tokeniser);
|
|
*str8 = DN_Str8FromStr8Arena(csv_value, arena);
|
|
} else {
|
|
DN_Str8BuilderAppendF(&pack->write_builder, "%s%.*s", pack->write_column++ ? "," : "", DN_Str8PrintFmt(*str8));
|
|
}
|
|
}
|
|
|
|
void DN_CSV_PackBuffer(DN_CSVPack *pack, DN_CSVSerialise serialise, void *dest, size_t *size)
|
|
{
|
|
if (serialise == DN_CSVSerialise_Read) {
|
|
DN_Str8 csv_value = DN_CSV_TokeniserNextColumn(&pack->read_tokeniser);
|
|
*size = DN_Min(*size, csv_value.size);
|
|
DN_Memcpy(dest, csv_value.data, *size);
|
|
} else {
|
|
DN_Str8BuilderAppendF(&pack->write_builder, "%s%.*s", pack->write_column++ ? "," : "", DN_Cast(int)(*size), dest);
|
|
}
|
|
}
|
|
|
|
void DN_CSV_PackBufferWithMax(DN_CSVPack *pack, DN_CSVSerialise serialise, void *dest, size_t *size, size_t max)
|
|
{
|
|
if (serialise == DN_CSVSerialise_Read)
|
|
*size = max;
|
|
DN_CSV_PackBuffer(pack, serialise, dest, size);
|
|
}
|
|
|
|
bool DN_CSV_PackNewLine(DN_CSVPack *pack, DN_CSVSerialise serialise)
|
|
{
|
|
bool result = true;
|
|
if (serialise == DN_CSVSerialise_Read) {
|
|
result = DN_CSV_TokeniserNextRow(&pack->read_tokeniser);
|
|
} else {
|
|
pack->write_column = 0;
|
|
result = DN_Str8BuilderAppendRef(&pack->write_builder, DN_Str8Lit("\n"));
|
|
}
|
|
return result;
|
|
} |