From b1394e6416773ac18892969aedc95cfcdc919ff1 Mon Sep 17 00:00:00 2001 From: doylet Date: Tue, 3 Jun 2025 16:26:40 +1000 Subject: [PATCH] Get latest changes from LPP bot --- Base/dn_base.cpp | 4 +- Base/dn_base.h | 51 +- Base/dn_base_containers.cpp | 48 +- Base/dn_base_containers.h | 15 + Base/dn_base_convert.cpp | 2 +- Base/dn_base_log.cpp | 5 +- Base/dn_base_mem.cpp | 11 +- Base/dn_base_string.cpp | 11 +- Base/dn_base_string.h | 38 +- Core/dn_core.cpp | 4 +- Core/dn_core_debug.cpp | 11 +- Core/dn_core_demo.cpp | 26 +- Extra/dn_async.cpp | 114 +++ Extra/dn_async.h | 52 + Extra/dn_helpers.h | 25 +- Extra/dn_json.cpp | 32 +- Extra/dn_tests.cpp | 70 +- OS/dn_os.cpp | 54 +- OS/dn_os.h | 84 +- OS/dn_os_posix.cpp | 271 +++-- OS/dn_os_posix.h | 45 + OS/dn_os_tls.cpp | 20 +- OS/dn_os_w32.cpp | 1843 +++++++++++++++++++++++++++++++++++ OS/dn_os_w32.h | 71 ++ OS/dn_os_windows.h | 12 + dn_base_inc.cpp | 2 + dn_core_inc.cpp | 2 + dn_os_inc.cpp | 4 +- dn_os_inc.h | 7 +- 29 files changed, 2644 insertions(+), 290 deletions(-) create mode 100644 Extra/dn_async.cpp create mode 100644 Extra/dn_async.h create mode 100644 OS/dn_os_w32.cpp create mode 100644 OS/dn_os_w32.h diff --git a/Base/dn_base.cpp b/Base/dn_base.cpp index c25d238..c90b0ee 100644 --- a/Base/dn_base.cpp +++ b/Base/dn_base.cpp @@ -36,11 +36,11 @@ DN_API DN_USize DN_CPU_HasFeatureArray(DN_CPUReport const *report, DN_CPUFeature { DN_USize result = 0; DN_USize const BITS = sizeof(report->features[0]) * 8; - DN_ForIndexU(feature_index, features_size) { + 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]; + DN_U64 chunk = report->features[chunk_index]; query->available = chunk & (1ULL << chunk_bit); result += DN_CAST(int) query->available; } diff --git a/Base/dn_base.h b/Base/dn_base.h index 9084b6d..e50c866 100644 --- a/Base/dn_base.h +++ b/Base/dn_base.h @@ -16,11 +16,11 @@ #include // exit() #endif -#define DN_ForIndexU(index, size) for (DN_USize index = 0; index < size; index++) -#define DN_ForIndexI(index, size) for (DN_ISize index = 0; index < size; index++) -#define DN_ForItSize(it, T, array, size) for (struct { DN_USize index; T *data; } it = {0, &(array)[0]}; it.index < (size); it.index++, it.data++) -#define DN_ForIt(it, T, array) for (struct { DN_USize index; T *data; } it = {0, &(array)->data[0]}; it.index < (array)->size; it.index++, it.data++) -#define DN_ForItCArray(it, T, array) for (struct { DN_USize index; T *data; } it = {0, &(array)[0]}; it.index < DN_ArrayCountU(array); it.index++, it.data++) +#define DN_ForIndexU(index, size) DN_USize index = 0; index < size; index++ +#define DN_ForIndexI(index, size) DN_ISize index = 0; index < size; index++ +#define DN_ForItSize(it, T, array, size) struct { DN_USize index; T *data; } it = {0, &(array)[0]}; it.index < (size); it.index++, it.data++ +#define DN_ForIt(it, T, array) struct { DN_USize index; T *data; } it = {0, &(array)->data[0]}; it.index < (array)->size; it.index++, it.data++ +#define DN_ForItCArray(it, T, array) struct { DN_USize index; T *data; } it = {0, &(array)[0]}; it.index < DN_ArrayCountU(array); it.index++, it.data++ #define DN_AlignUpPowerOfTwo(value, pot) (((uintptr_t)(value) + ((uintptr_t)(pot) - 1)) & ~((uintptr_t)(pot) - 1)) #define DN_AlignDownPowerOfTwo(value, pot) ((uintptr_t)(value) & ~((uintptr_t)(pot) - 1)) @@ -152,10 +152,10 @@ struct DN_DeferHelper #define DN_DEFER const auto DN_UniqueName(defer_lambda_) = DN_DeferHelper() + [&]() #endif // defined(__cplusplus) -#define DN_DEFER_LOOP(begin, end) \ - for (bool DN_UniqueName(once) = (begin, true); \ - DN_UniqueName(once); \ - end, DN_UniqueName(once) = false) +#define DN_DeferLoop(begin, end) \ + bool DN_UniqueName(once) = (begin, true); \ + DN_UniqueName(once); \ + end, DN_UniqueName(once) = false // NOTE: Types ///////////////////////////////////////////////////////////////////////////////////// typedef intptr_t DN_ISize; @@ -246,20 +246,18 @@ struct DN_CallSite #include #define DN_Atomic_CompareExchange64(dest, desired_val, prev_val) _InterlockedCompareExchange64((__int64 volatile *)dest, desired_val, prev_val) #define DN_Atomic_CompareExchange32(dest, desired_val, prev_val) _InterlockedCompareExchange((long volatile *)dest, desired_val, prev_val) + + #define DN_Atomic_LoadU64(target) *(target) + #define DN_Atomic_LoadU32(target) *(target) #define DN_Atomic_AddU32(target, value) _InterlockedExchangeAdd((long volatile *)target, value) #define DN_Atomic_AddU64(target, value) _InterlockedExchangeAdd64((__int64 volatile *)target, value) #define DN_Atomic_SubU32(target, value) DN_Atomic_AddU32(DN_CAST(long volatile *) target, (long)-value) #define DN_Atomic_SubU64(target, value) DN_Atomic_AddU64(target, (DN_U64) - value) - #define DN_CountLeadingZerosU64(value) __lzcnt64(value) - - #define DN_CPU_TSC() __rdtsc() - #define DN_CompilerReadBarrierAndCPUReadFence \ - _ReadBarrier(); \ - _mm_lfence() - #define DN_CompilerWriteBarrierAndCPUWriteFence \ - _WriteBarrier(); \ - _mm_sfence() + #define DN_CountLeadingZerosU64(value) __lzcnt64(value) + #define DN_CPU_TSC() __rdtsc() + #define DN_CompilerReadBarrierAndCPUReadFence _ReadBarrier(); _mm_lfence() + #define DN_CompilerWriteBarrierAndCPUWriteFence _WriteBarrier(); _mm_sfence() #elif defined(DN_COMPILER_GCC) || defined(DN_COMPILER_CLANG) #if defined(__ANDROID__) #elif defined(DN_PLATFORM_EMSCRIPTEN) @@ -268,17 +266,20 @@ struct DN_CallSite #include #endif - #define DN_Atomic_AddU32(target, value) __atomic_fetch_add(target, value, __ATOMIC_ACQ_REL) - #define DN_Atomic_AddU64(target, value) __atomic_fetch_add(target, value, __ATOMIC_ACQ_REL) - #define DN_Atomic_SubU32(target, value) __atomic_fetch_sub(target, value, __ATOMIC_ACQ_REL) - #define DN_Atomic_SubU64(target, value) __atomic_fetch_sub(target, value, __ATOMIC_ACQ_REL) + #define DN_Atomic_LoadU64(target) __atomic_load_n(x, __ATOMIC_SEQ_CST) + #define DN_Atomic_LoadU32(target) __atomic_load_n(x, __ATOMIC_SEQ_CST) + #define DN_Atomic_AddU32(target, value) __atomic_fetch_add(target, value, __ATOMIC_ACQ_REL) + #define DN_Atomic_AddU64(target, value) __atomic_fetch_add(target, value, __ATOMIC_ACQ_REL) + #define DN_Atomic_SubU32(target, value) __atomic_fetch_sub(target, value, __ATOMIC_ACQ_REL) + #define DN_Atomic_SubU64(target, value) __atomic_fetch_sub(target, value, __ATOMIC_ACQ_REL) - #define DN_CountLeadingZerosU64(value) __builtin_clzll(value) + #define DN_CountLeadingZerosU64(value) __builtin_clzll(value) #if defined(DN_COMPILER_GCC) #define DN_CPU_TSC() __rdtsc() #else #define DN_CPU_TSC() __builtin_readcyclecounter() #endif + #if defined(DN_PLATFORM_EMSCRIPTEN) #define DN_CompilerReadBarrierAndCPUReadFence #define DN_CompilerWriteBarrierAndCPUWriteFence @@ -406,7 +407,7 @@ struct DN_TicketMutex // NOTE: Intrinsics //////////////////////////////////////////////////////////////////////////////// DN_FORCE_INLINE DN_U64 DN_Atomic_SetValue64 (DN_U64 volatile *target, DN_U64 value); -DN_FORCE_INLINE long DN_Atomic_SetValue32 (long volatile *target, long value); +DN_FORCE_INLINE DN_U32 DN_Atomic_SetValue32 (DN_U32 volatile *target, DN_U32 value); #if !defined (DN_PLATFORM_ARM64) DN_API DN_CPUIDResult DN_CPU_ID (DN_CPUIDArgs args); @@ -515,7 +516,7 @@ DN_FORCE_INLINE DN_U64 DN_Atomic_SetValue64(DN_U64 volatile *target, DN_U64 valu #endif } -DN_FORCE_INLINE long DN_Atomic_SetValue32(long volatile *target, long value) +DN_FORCE_INLINE DN_U32 DN_Atomic_SetValue32(DN_U32 volatile *target, DN_U32 value) { #if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL) long result; diff --git a/Base/dn_base_containers.cpp b/Base/dn_base_containers.cpp index cd93ed6..a1f56de 100644 --- a/Base/dn_base_containers.cpp +++ b/Base/dn_base_containers.cpp @@ -1,5 +1,49 @@ #define DN_CONTAINERS_CPP +#include "../dn_base_inc.h" + +DN_API bool DN_Ring_HasSpace(DN_Ring const *ring, DN_U64 size) +{ + DN_U64 avail = ring->write_pos - ring->read_pos; + DN_U64 space = ring->size - avail; + bool result = space >= size; + return result; +} + +DN_API bool DN_Ring_HasData(DN_Ring const *ring, DN_U64 size) +{ + DN_U64 data = ring->write_pos - ring->read_pos; + bool result = data >= size; + return result; +} + +DN_API void DN_Ring_Write(DN_Ring *ring, void const *src, DN_U64 src_size) +{ + DN_Assert(src_size <= ring->size); + DN_U64 offset = ring->write_pos % ring->size; + DN_U64 bytes_before_split = ring->size - offset; + DN_U64 pre_split_bytes = DN_Min(bytes_before_split, src_size); + DN_U64 post_split_bytes = src_size - pre_split_bytes; + void const *pre_split_data = src; + void const *post_split_data = ((char *)src + pre_split_bytes); + DN_Memcpy(ring->base + offset, pre_split_data, pre_split_bytes); + DN_Memcpy(ring->base, post_split_data, post_split_bytes); + ring->write_pos += src_size; +} + +DN_API void DN_Ring_Read(DN_Ring *ring, void *dest, DN_U64 dest_size) +{ + DN_Assert(dest_size <= ring->size); + DN_U64 offset = ring->read_pos % ring->size; + DN_U64 bytes_before_split = ring->size - offset; + DN_U64 pre_split_bytes = DN_Min(bytes_before_split, dest_size); + DN_U64 post_split_bytes = dest_size - pre_split_bytes; + DN_Memcpy(dest, ring->base + offset, pre_split_bytes); + DN_Memcpy((char *)dest + pre_split_bytes, ring->base, post_split_bytes); + ring->read_pos += dest_size; +} +#define DN_Ring_WriteStruct(ring, item) DN_Ring_Write((ring), (item), sizeof(*(item))) + // NOTE: DN_CArray ///////////////////////////////////////////////////////////////////////////////// template DN_ArrayEraseResult DN_CArray_EraseRange(T *data, DN_USize *size, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase) @@ -936,7 +980,7 @@ template DN_List DN_List_InitCArray(DN_Arena *arena, DN_USize chunk_size, T const (&array)[N]) { DN_List result = DN_List_Init(arena, chunk_size); - DN_ForIndexU(index, N) + for (DN_ForIndexU(index, N)) DN_List_Add(&result, array[index]); return result; } @@ -945,7 +989,7 @@ template DN_List DN_List_InitSliceCopy(DN_Arena *arena, DN_USize chunk_size, DN_Slice slice) { DN_List result = DN_List_Init(arena, chunk_size); - DN_ForIndexU(index, slice.size) + for (DN_ForIndexU(index, slice.size)) DN_List_Add(&result, slice.data[index]); return result; } diff --git a/Base/dn_base_containers.h b/Base/dn_base_containers.h index 38d8b39..4f9e68f 100644 --- a/Base/dn_base_containers.h +++ b/Base/dn_base_containers.h @@ -1,6 +1,16 @@ #if !defined(DN_CONTAINERS_H) #define DN_CONTAINERS_H +#include "../dn_base_inc.h" + +struct DN_Ring +{ + DN_U64 size; + char *base; + DN_U64 write_pos; + DN_U64 read_pos; +}; + // NOTE: DN_CArray ///////////////////////////////////////////////////////////////////////////////// enum DN_ArrayErase { @@ -149,6 +159,11 @@ template struct DN_List }; #endif // !defined(DN_NO_LIST) +DN_API bool DN_Ring_HasSpace (DN_Ring const *ring, DN_U64 size); +DN_API bool DN_Ring_HasData (DN_Ring const *ring, DN_U64 size); +DN_API void DN_Ring_Write (DN_Ring *ring, void const *src, DN_U64 src_size); +DN_API void DN_Ring_Read (DN_Ring *ring, void *dest, DN_U64 dest_size); + template DN_ArrayEraseResult DN_CArray_EraseRange (T *data, DN_USize *size, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase); template T * DN_CArray_MakeArray (T *data, DN_USize *size, DN_USize max, DN_USize count, DN_ZeroMem zero_mem); template T * DN_CArray_InsertArray (T *data, DN_USize *size, DN_USize max, DN_USize index, T const *items, DN_USize count); diff --git a/Base/dn_base_convert.cpp b/Base/dn_base_convert.cpp index 425976e..7e04583 100644 --- a/Base/dn_base_convert.cpp +++ b/Base/dn_base_convert.cpp @@ -151,7 +151,7 @@ DN_API DN_Str8 DN_CVT_F64ToAge(DN_Arena *arena, DN_F64 age_s, DN_CVTU64AgeUnit u if (!arena) return result; - char buffer[128]; + char buffer[256]; DN_Arena stack_arena = DN_Arena_InitFromBuffer(buffer, sizeof(buffer), DN_ArenaFlags_NoPoison); DN_Str8Builder builder = DN_Str8Builder_Init(&stack_arena); DN_F64 remainder = age_s; diff --git a/Base/dn_base_log.cpp b/Base/dn_base_log.cpp index b573ac5..01369e3 100644 --- a/Base/dn_base_log.cpp +++ b/Base/dn_base_log.cpp @@ -60,12 +60,14 @@ DN_API DN_LOGPrefixSize DN_LOG_MakePrefix(DN_LOGStyle style, DN_LOGTypeParam typ DN_GCC_WARNING_PUSH DN_GCC_WARNING_DISABLE(-Wformat) DN_GCC_WARNING_DISABLE(-Wformat-extra-args) + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(4477) int size = DN_SNPrintF(dest, DN_CAST(int)dest_size, "%04u-%02u-%02uT%02u:%02u:%02u" // date "%S" // colour "%S" // bold - "%S" // type + " %S" // type "%.*s" // type padding "%S" // reset " %S" // file name @@ -85,6 +87,7 @@ DN_API DN_LOGPrefixSize DN_LOG_MakePrefix(DN_LOGStyle style, DN_LOGTypeParam typ reset_esc, // reset file_name, // file name call_site.line); // line number + DN_MSVC_WARNING_POP // '%S' requires an argument of type 'wchar_t *', but variadic argument 7 has type 'DN_Str8' DN_GCC_WARNING_POP static DN_USize max_header_length = 0; diff --git a/Base/dn_base_mem.cpp b/Base/dn_base_mem.cpp index 6460508..a0d1214 100644 --- a/Base/dn_base_mem.cpp +++ b/Base/dn_base_mem.cpp @@ -336,13 +336,12 @@ DN_API bool DN_Arena_OwnsPtr(DN_Arena const *arena, void *ptr) DN_API DN_ArenaStats DN_Arena_SumStatsArray(DN_ArenaStats const *array, DN_USize size) { DN_ArenaStats result = {}; - DN_ForItSize(it, DN_ArenaStats const, array, size) - { - DN_ArenaStats stats = *it.data; - result.info.used += stats.info.used; - result.info.commit += stats.info.commit; + for (DN_ForItSize(it, DN_ArenaStats const, array, size)) { + DN_ArenaStats stats = *it.data; + result.info.used += stats.info.used; + result.info.commit += stats.info.commit; result.info.reserve += stats.info.reserve; - result.info.blocks += stats.info.blocks; + result.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); diff --git a/Base/dn_base_string.cpp b/Base/dn_base_string.cpp index 6c3609b..b66a18e 100644 --- a/Base/dn_base_string.cpp +++ b/Base/dn_base_string.cpp @@ -272,7 +272,7 @@ DN_API DN_Str8 DN_Str8_Segment(DN_Arena *arena, DN_Str8 src, DN_USize segment_si DN_USize segment_counter = 0; DN_Str8 result = DN_Str8_Alloc(arena, src.size + segments, DN_ZeroMem_Yes); DN_USize write_index = 0; - DN_ForIndexU(src_index, src.size) { + 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; @@ -664,7 +664,7 @@ DN_API DN_Str8DotTruncateResult DN_Str8_DotTruncateMiddle(DN_Arena *arena, DN_St DN_API DN_Str8 DN_Str8_Lower(DN_Arena *arena, DN_Str8 string) { DN_Str8 result = DN_Str8_Copy(arena, string); - DN_ForIndexU(index, result.size) + for (DN_ForIndexU(index, result.size)) result.data[index] = DN_Char_ToLower(result.data[index]); return result; } @@ -672,7 +672,7 @@ DN_API DN_Str8 DN_Str8_Lower(DN_Arena *arena, DN_Str8 string) DN_API DN_Str8 DN_Str8_Upper(DN_Arena *arena, DN_Str8 string) { DN_Str8 result = DN_Str8_Copy(arena, string); - DN_ForIndexU(index, result.size) + for (DN_ForIndexU(index, result.size)) result.data[index] = DN_Char_ToUpper(result.data[index]); return result; } @@ -774,7 +774,7 @@ DN_API bool DN_Str8Builder_AddArrayRef(DN_Str8Builder *builder, DN_Str8 const *s return false; if (add == DN_Str8BuilderAdd_Append) { - DN_ForIndexU(index, size) { + for (DN_ForIndexU(index, size)) { DN_Str8 string = strings[index]; DN_Str8Link *link = links + index; @@ -821,8 +821,7 @@ DN_API bool DN_Str8Builder_AddArrayCopy(DN_Str8Builder *builder, DN_Str8 const * DN_ArenaTempMem tmp_mem = DN_Arena_TempMemBegin(builder->arena); bool result = true; DN_Str8 *strings_copy = DN_Arena_NewArray(builder->arena, DN_Str8, size, DN_ZeroMem_No); - DN_ForIndexU(index, size) - { + for (DN_ForIndexU(index, size)) { strings_copy[index] = DN_Str8_Copy(builder->arena, strings[index]); if (strings_copy[index].size != strings[index].size) { result = false; diff --git a/Base/dn_base_string.h b/Base/dn_base_string.h index a251545..6c80aba 100644 --- a/Base/dn_base_string.h +++ b/Base/dn_base_string.h @@ -1,30 +1,22 @@ #if !defined(DN_BASE_STRING_H) #define DN_BASE_STRING_H -#if defined(DN_USE_STD_PRINTF) - #include - #define DN_SPrintF(...) sprintf(__VA_ARGS__) - #define DN_SNPrintF(...) snprintf(__VA_ARGS__) - #define DN_VSPrintF(...) vsprintf(__VA_ARGS__) - #define DN_VSNPrintF(...) vsnprintf(__VA_ARGS__) -#else - #if !defined(DN_STB_SPRINTF_HEADER_ONLY) - #define STB_SPRINTF_IMPLEMENTATION - #define STB_SPRINTF_STATIC - #endif - DN_MSVC_WARNING_PUSH - DN_MSVC_WARNING_DISABLE(4505) // Unused function warning - DN_GCC_WARNING_PUSH - DN_GCC_WARNING_DISABLE(-Wunused-function) - #include "../External/stb_sprintf.h" - DN_GCC_WARNING_POP - DN_MSVC_WARNING_POP - - #define DN_SPrintF(...) STB_SPRINTF_DECORATE(sprintf)(__VA_ARGS__) - #define DN_SNPrintF(...) STB_SPRINTF_DECORATE(snprintf)(__VA_ARGS__) - #define DN_VSPrintF(...) STB_SPRINTF_DECORATE(vsprintf)(__VA_ARGS__) - #define DN_VSNPrintF(...) STB_SPRINTF_DECORATE(vsnprintf)(__VA_ARGS__) +#if !defined(DN_STB_SPRINTF_HEADER_ONLY) + #define STB_SPRINTF_IMPLEMENTATION + #define STB_SPRINTF_STATIC #endif +DN_MSVC_WARNING_PUSH +DN_MSVC_WARNING_DISABLE(4505) // Unused function warning +DN_GCC_WARNING_PUSH +DN_GCC_WARNING_DISABLE(-Wunused-function) +#include "../External/stb_sprintf.h" +DN_GCC_WARNING_POP +DN_MSVC_WARNING_POP + +#define DN_SPrintF(...) STB_SPRINTF_DECORATE(sprintf)(__VA_ARGS__) +#define DN_SNPrintF(...) STB_SPRINTF_DECORATE(snprintf)(__VA_ARGS__) +#define DN_VSPrintF(...) STB_SPRINTF_DECORATE(vsprintf)(__VA_ARGS__) +#define DN_VSNPrintF(...) STB_SPRINTF_DECORATE(vsnprintf)(__VA_ARGS__) /* //////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Core/dn_core.cpp b/Core/dn_core.cpp index dd4f4ab..20a1d22 100644 --- a/Core/dn_core.cpp +++ b/Core/dn_core.cpp @@ -55,12 +55,12 @@ DN_API void DN_Core_Init(DN_Core *core, DN_CoreOnInit on_init) DN_MSVC_WARNING_POP DN_USize longest_feature_name = 0; - DN_ForIndexU(feature_index, DN_CPUFeature_Count) { + 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); } - DN_ForIndexU(feature_index, DN_CPUFeature_Count) { + for (DN_ForIndexU(feature_index, DN_CPUFeature_Count)) { DN_CPUFeatureDecl feature_decl = g_dn_cpu_feature_decl[feature_index]; bool has_feature = DN_CPU_HasFeature(report, feature_decl.value); DN_Str8Builder_AppendF(&builder, diff --git a/Core/dn_core_debug.cpp b/Core/dn_core_debug.cpp index ba5c86c..6f447eb 100644 --- a/Core/dn_core_debug.cpp +++ b/Core/dn_core_debug.cpp @@ -13,12 +13,13 @@ DN_API DN_StackTraceWalkResult DN_StackTrace_Walk(DN_Arena *arena, uint16_t limi HANDLE thread = GetCurrentThread(); result.process = GetCurrentProcess(); - if (!g_dn_os_core_->win32_sym_initialised) { - g_dn_os_core_->win32_sym_initialised = true; + DN_W32Core *w32 = DN_OS_GetW32Core_(); + if (!w32->sym_initialised) { + w32->sym_initialised = true; SymSetOptions(SYMOPT_LOAD_LINES); if (!SymInitialize(result.process, nullptr /*UserSearchPath*/, true /*fInvadeProcess*/)) { DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); - DN_WinError error = DN_Win_LastError(tmem.arena); + DN_W32Error error = DN_W32_LastError(tmem.arena); DN_LOG_ErrorF("SymInitialize failed, stack trace can not be generated (%lu): %.*s\n", error.code, DN_STR_FMT(error.msg)); } } @@ -177,8 +178,8 @@ DN_API DN_StackTraceFrame DN_StackTrace_RawFrameToFrame(DN_Arena *arena, DN_Stac DN_StackTraceFrame result = {}; result.address = raw_frame.base_addr; result.line_number = line.LineNumber; - result.file_name = DN_Win_Str16ToStr8(arena, file_name16); - result.function_name = DN_Win_Str16ToStr8(arena, function_name16); + result.file_name = DN_W32_Str16ToStr8(arena, file_name16); + result.function_name = DN_W32_Str16ToStr8(arena, function_name16); if (!DN_Str8_HasData(result.function_name)) result.function_name = DN_STR8(""); diff --git a/Core/dn_core_demo.cpp b/Core/dn_core_demo.cpp index e09fe33..60a764d 100644 --- a/Core/dn_core_demo.cpp +++ b/Core/dn_core_demo.cpp @@ -403,10 +403,10 @@ void DN_Docs_Demo() } } - // NOTE: DN_LogProc /////////////////////////////////////////////////////////////////////////// + // 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 + // emitted using the DN_LOG_* family of functions are routed through this // routine. // NOTE: DN_FNV1A ///////////////////////////////////////////////////////////////////////////// @@ -660,7 +660,7 @@ void DN_Docs_Demo() // returned in add and multiply operations, and, the minimum is returned in // subtraction and division. - // NOTE: DN_Safe_SaturateCast* //////////////////////////////////////////////////////////////// + // 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 @@ -1175,31 +1175,31 @@ void DN_Docs_Demo() DN_VArray_Deinit(&array); } - // NOTE: DN_Win_LastError ///////////////////////////////////////////////////////////// - // NOTE: DN_Win_ErrorCodeToMsg ///////////////////////////////////////////////////////////// + // NOTE: DN_W32_LastError ///////////////////////////////////////////////////////////// + // NOTE: DN_W32_ErrorCodeToMsg ///////////////////////////////////////////////////////////// #if defined(DN_PLATFORM_WIN32) if (0) { // Generate the error string for the last Win32 API called that return // an error value. DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); - DN_WinError get_last_error = DN_Win_LastError(tmem.arena); + DN_W32Error get_last_error = DN_W32_LastError(tmem.arena); printf("Error (%lu): %.*s", get_last_error.code, DN_STR_FMT(get_last_error.msg)); // Alternatively, pass in the error code directly - DN_WinError error_msg_for_code = DN_Win_ErrorCodeToMsg(tmem.arena, /*error_code*/ 0); + DN_W32Error error_msg_for_code = DN_W32_ErrorCodeToMsg(tmem.arena, /*error_code*/ 0); printf("Error (%lu): %.*s", error_msg_for_code.code, DN_STR_FMT(error_msg_for_code.msg)); } - // NOTE: DN_Win_MakeProcessDPIAware /////////////////////////////////////////////////////////// + // 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_Win_Str8ToStr16 ///////////////////////////////////////////////////////////// - // NOTE: DN_Win_Str8ToStr16Buffer ///////////////////////////////////////////////////////////// - // NOTE: DN_Win_Str16ToStr8 ///////////////////////////////////////////////////////////// - // NOTE: DN_Win_Str16ToStr8Buffer ///////////////////////////////////////////////////////////// + // NOTE: DN_W32_Str8ToStr16 ///////////////////////////////////////////////////////////// + // NOTE: DN_W32_Str8ToStr16Buffer ///////////////////////////////////////////////////////////// + // NOTE: DN_W32_Str16ToStr8 ///////////////////////////////////////////////////////////// + // NOTE: DN_W32_Str16ToStr8Buffer ///////////////////////////////////////////////////////////// // // Convert a UTF8 <-> UTF16 string. // @@ -1211,7 +1211,7 @@ void DN_Docs_Demo() // // 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_Win_LastError' + // queried using 'DN_W32_LastError' #endif } diff --git a/Extra/dn_async.cpp b/Extra/dn_async.cpp new file mode 100644 index 0000000..c18cd50 --- /dev/null +++ b/Extra/dn_async.cpp @@ -0,0 +1,114 @@ +#define DN_ASYNC_CPP + +#include "../dn_base_inc.h" +#include "../dn_os_inc.h" +#include "dn_async.h" + +static DN_I32 DN_ASYNC_ThreadEntryPoint_(DN_OSThread *thread) +{ + DN_OS_ThreadSetName(DN_FStr8_ToStr8(&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_ASYNCJob job = {}; + for (DN_OS_MutexScope(&async->ring_mutex)) { + if (DN_Ring_HasData(ring, sizeof(job))) { + DN_Ring_Read(ring, &job, sizeof(job)); + break; + } + } + + if (job.work.func) { + DN_OS_ConditionVariableBroadcast(&async->ring_write_cv); // Resume any blocked ring write(s) + + DN_Atomic_AddU32(&async->busy_threads, 1); + job.work.func(job.work.input); + DN_Atomic_SubU32(&async->busy_threads, 1); + + if (job.completion_sem.handle != 0) + DN_OS_SemaphoreIncrement(&job.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; + thread->name = DN_FStr8_InitF<64>("ASYNC W%zu", index); + DN_OS_ThreadInit(thread, DN_ASYNC_ThreadEntryPoint_, async); + } +} + +DN_API void DN_ASYNC_Deinit(DN_ASYNCCore *async) +{ + DN_Assert(async); + DN_Atomic_SetValue32(&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_ThreadDeinit(it.data); +} + + +static bool DN_ASYNC_QueueJob_(DN_ASYNCCore *async, DN_ASYNCJob const *job, 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_Ring_HasSpace(&async->ring, sizeof(*job))) { + DN_Ring_WriteStruct(&async->ring, job); + 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_ASYNCJob job = {}; + job.work.func = func; + job.work.input = input; + bool result = DN_ASYNC_QueueJob_(async, &job, wait_time_ms); + return result; +} + +DN_API DN_OSSemaphore DN_ASYNC_QueueTask(DN_ASYNCCore *async, DN_ASYNCWorkFunc *func, void *input, DN_U64 wait_time_ms) +{ + DN_OSSemaphore result = DN_OS_SemaphoreInit(0); + DN_ASYNCJob job = {}; + job.work.func = func; + job.work.input = input; + job.completion_sem = result; + DN_ASYNC_QueueJob_(async, &job, wait_time_ms); + return result; +} + +DN_API void DN_ASYNC_WaitTask(DN_OSSemaphore *sem, DN_U32 timeout_ms) +{ + DN_OS_SemaphoreWait(sem, timeout_ms); + DN_OS_SemaphoreDeinit(sem); +} + diff --git a/Extra/dn_async.h b/Extra/dn_async.h new file mode 100644 index 0000000..061d22a --- /dev/null +++ b/Extra/dn_async.h @@ -0,0 +1,52 @@ +#if !defined(DN_ASYNC_H) +#define DN_ASYNC_H + +#include "../dn_base_inc.h" +#include "../dn_os_inc.h" + +enum DN_ASYNCPriority +{ + DN_ASYNCPriority_Low, + DN_ASYNCPriority_High, + DN_ASYNCPriority_Count, +}; + +struct DN_ASYNCCore +{ + DN_OSMutex ring_mutex; + DN_OSConditionVariable ring_write_cv; + DN_OSSemaphore worker_sem; + DN_Ring ring; + DN_OSThread *threads; + DN_U32 thread_count; + DN_U32 busy_threads; + DN_U32 join_threads; +}; + +typedef void(DN_ASYNCWorkFunc)(void *input); + +struct DN_ASYNCWork +{ + DN_ASYNCWorkFunc *func; + void *input; + void *output; +}; + +struct DN_ASYNCJob +{ + DN_ASYNCWork work; + DN_OSSemaphore completion_sem; +}; + +struct DN_ASYNCTask +{ + DN_ASYNCWork work; +}; + +DN_API void DN_ASYNC_Init (DN_ASYNCCore *async, char *base, DN_USize base_size, DN_OSThread *threads, DN_U32 threads_size); +DN_API void DN_ASYNC_Deinit (DN_ASYNCCore *async); +DN_API bool DN_ASYNC_QueueWork(DN_ASYNCCore *async, DN_ASYNCWorkFunc *func, void *input, DN_U64 wait_time_ms); +DN_API DN_OSSemaphore DN_ASYNC_QueueTask(DN_ASYNCCore *async, DN_ASYNCWorkFunc *func, void *input, DN_U64 wait_time_ms); +DN_API void DN_ASYNC_WaitTask (DN_OSSemaphore *sem, DN_U32 timeout_ms); + +#endif // DN_ASYNC_H diff --git a/Extra/dn_helpers.h b/Extra/dn_helpers.h index 45db015..244e3be 100644 --- a/Extra/dn_helpers.h +++ b/Extra/dn_helpers.h @@ -142,22 +142,21 @@ DN_API void DN_PCG32_Advance (DN_PCG32 *rng #if !defined(DN_NO_JSON_BUILDER) // NOTE: DN_JSONBuilder //////////////////////////////////////////////////////////////////////////// -#define DN_JSONBuilder_Object(builder) \ - DN_DEFER_LOOP(DN_JSONBuilder_ObjectBegin(builder), \ - DN_JSONBuilder_ObjectEnd(builder)) +#define DN_JSONBuilder_Object(builder) \ + DN_DeferLoop(DN_JSONBuilder_ObjectBegin(builder), \ + DN_JSONBuilder_ObjectEnd(builder)) -#define DN_JSONBuilder_ObjectNamed(builder, name) \ - DN_DEFER_LOOP(DN_JSONBuilder_ObjectBeginNamed(builder, name), \ - DN_JSONBuilder_ObjectEnd(builder)) +#define DN_JSONBuilder_ObjectNamed(builder, name) \ + DN_DeferLoop(DN_JSONBuilder_ObjectBeginNamed(builder, name), \ + DN_JSONBuilder_ObjectEnd(builder)) -#define DN_JSONBuilder_Array(builder) \ - DN_DEFER_LOOP(DN_JSONBuilder_ArrayBegin(builder), \ - DN_JSONBuilder_ArrayEnd(builder)) - -#define DN_JSONBuilder_ArrayNamed(builder, name) \ - DN_DEFER_LOOP(DN_JSONBuilder_ArrayBeginNamed(builder, name), \ - DN_JSONBuilder_ArrayEnd(builder)) +#define DN_JSONBuilder_Array(builder) \ + DN_DeferLoop(DN_JSONBuilder_ArrayBegin(builder), \ + DN_JSONBuilder_ArrayEnd(builder)) +#define DN_JSONBuilder_ArrayNamed(builder, name) \ + DN_DeferLoop(DN_JSONBuilder_ArrayBeginNamed(builder, name), \ + DN_JSONBuilder_ArrayEnd(builder)) DN_API DN_JSONBuilder DN_JSONBuilder_Init (DN_Arena *arena, int spaces_per_indent); DN_API DN_Str8 DN_JSONBuilder_Build (DN_JSONBuilder const *builder, DN_Arena *arena); diff --git a/Extra/dn_json.cpp b/Extra/dn_json.cpp index 0aa1d60..b0aebe8 100644 --- a/Extra/dn_json.cpp +++ b/Extra/dn_json.cpp @@ -407,22 +407,22 @@ void DN_JSON_ItErrorUnknownKeyValue_(DN_JSONIt *it, DN_CallSite call_site) json_string_s const *key = curr->name; if (it->flags & json_parse_flags_allow_location_information) { json_string_ex_s const *info = DN_CAST(json_string_ex_s const *)key; - DN_Log_TypeFCallSite(DN_LogType_Warning, - call_site, - "Unknown key-value pair in object [loc=%zu:%zu, key=%.*s, value=%.*s]", - info->line_no, - info->row_no, - DN_CAST(int)key->string_size, - key->string, - DN_CAST(int)value_type_size, - value_type); + DN_LOG_EmitFromType(DN_LOG_MakeU32LogTypeParam(DN_LOGType_Warning), + call_site, + "Unknown key-value pair in object [loc=%zu:%zu, key=%.*s, value=%.*s]", + info->line_no, + info->row_no, + DN_CAST(int) key->string_size, + key->string, + DN_CAST(int) value_type_size, + value_type); } else { - DN_Log_TypeFCallSite(DN_LogType_Warning, - call_site, - "Unknown key-value pair in object [key=%.*s, value=%.*s]", - DN_CAST(int)key->string_size, - key->string, - DN_CAST(int)value_type_size, - value_type); + DN_LOG_EmitFromType(DN_LOG_MakeU32LogTypeParam(DN_LOGType_Warning), + call_site, + "Unknown key-value pair in object [key=%.*s, value=%.*s]", + DN_CAST(int) key->string_size, + key->string, + DN_CAST(int) value_type_size, + value_type); } } diff --git a/Extra/dn_tests.cpp b/Extra/dn_tests.cpp index 577d2ec..e3e3ff4 100644 --- a/Extra/dn_tests.cpp +++ b/Extra/dn_tests.cpp @@ -1151,8 +1151,8 @@ static DN_UTCore DN_Tests_Intrinsics() DN_UT_Test(&result, "DN_Atomic_SetValue32") { - long a = 0; - long b = 111; + DN_U32 a = 0; + DN_U32 b = 111; DN_Atomic_SetValue32(&a, b); DN_UT_AssertF(&result, a == b, "a: %ld, b: %ld", a, b); } @@ -1574,6 +1574,8 @@ static DN_UTCore DN_Tests_M4() static DN_UTCore DN_Tests_OS() { DN_UTCore result = DN_UT_Init(); + + #if defined(DN_OS_INC_CPP) || 1 DN_UT_LogF(&result, "DN_OS\n"); { DN_UT_Test(&result, "Generate secure RNG bytes with nullptr") @@ -1690,6 +1692,58 @@ static DN_UTCore DN_Tests_OS() } } + DN_UT_LogF(&result, "\nSemaphore\n"); + { + DN_OSSemaphore sem = DN_OS_SemaphoreInit(0); + + 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 >= 100, "Expected to sleep for >= 100ms, slept %f ms", elapsed_ms); + } + + 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(); + 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(); + 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 >= 100, "Expected to sleep for >= 100ms, slept %f ms", elapsed_ms); + } + DN_OS_MutexDeinit(&mutex); + DN_OS_ConditionVariableDeinit(&cv); + } + #endif + return result; } @@ -2372,25 +2426,25 @@ static DN_UTCore DN_Tests_Win() DN_UT_Test(&result, "Str8 to Str16") { - DN_Str16 str_result = DN_Win_Str8ToStr16(tmem.arena, input8); + DN_Str16 str_result = DN_W32_Str8ToStr16(tmem.arena, input8); DN_UT_Assert(&result, str_result == input16); } DN_UT_Test(&result, "Str16 to Str8") { - DN_Str8 str_result = DN_Win_Str16ToStr8(tmem.arena, input16); + DN_Str8 str_result = DN_W32_Str16ToStr8(tmem.arena, input16); DN_UT_Assert(&result, str_result == input8); } DN_UT_Test(&result, "Str16 to Str8: Null terminates string") { - int size_required = DN_Win_Str16ToStr8Buffer(input16, nullptr, 0); + int size_required = DN_W32_Str16ToStr8Buffer(input16, nullptr, 0); char *string = DN_Arena_NewArray(tmem.arena, char, size_required + 1, DN_ZeroMem_No); // Fill the string with error sentinels DN_Memset(string, 'Z', size_required + 1); - int size_returned = DN_Win_Str16ToStr8Buffer(input16, string, size_required + 1); + int size_returned = DN_W32_Str16ToStr8Buffer(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); @@ -2400,8 +2454,8 @@ static DN_UTCore DN_Tests_Win() DN_UT_Test(&result, "Str16 to Str8: Arena null terminates string") { - DN_Str8 string8 = DN_Win_Str16ToStr8(tmem.arena, input16); - int size_returned = DN_Win_Str16ToStr8Buffer(input16, nullptr, 0); + DN_Str8 string8 = DN_W32_Str16ToStr8(tmem.arena, input16); + int size_returned = DN_W32_Str16ToStr8Buffer(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); diff --git a/OS/dn_os.cpp b/OS/dn_os.cpp index 10d6e4a..afebc16 100644 --- a/OS/dn_os.cpp +++ b/OS/dn_os.cpp @@ -1,5 +1,10 @@ #define DN_OS_CPP +#if defined(DN_PLATFORM_POSIX) +#include // get_nprocs +#include // getpagesize +#endif + static DN_OSCore *g_dn_os_core_; static void DN_OS_LOGEmitFromTypeTypeFV_(DN_LOGTypeParam type, void *user_data, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, va_list args) @@ -86,34 +91,46 @@ DN_API void DN_OS_Init(DN_OSCore *os, DN_OSInitArgs *args) #if defined(DN_PLATFORM_WIN32) SYSTEM_INFO system_info = {}; GetSystemInfo(&system_info); - os->page_size = system_info.dwPageSize; - os->alloc_granularity = system_info.dwAllocationGranularity; - QueryPerformanceFrequency(&os->win32_qpc_frequency); - HMODULE module = LoadLibraryA("kernel32.dll"); - os->win32_set_thread_description = DN_CAST(DN_WinSetThreadDescriptionFunc *) GetProcAddress(module, "SetThreadDescription"); - FreeLibrary(module); + os->logical_processor_count = system_info.dwNumberOfProcessors; + os->page_size = system_info.dwPageSize; + os->alloc_granularity = system_info.dwAllocationGranularity; #else - // TODO(doyle): Get the proper page size from the OS. - os->page_size = DN_Kilobytes(4); - os->alloc_granularity = DN_Kilobytes(64); + os->logical_processor_count = get_nprocs(); + os->page_size = getpagesize(); + os->alloc_granularity = os->page_size; #endif } // NOTE: Setup logging DN_OS_EmitLogsWithOSPrintFunctions(os); - #if defined(DN_PLATFORM_WIN32) - // NOTE: win32 bcrypt { + os->arena = DN_Arena_InitFromOSVMem(DN_Megabytes(1), DN_Kilobytes(4), DN_ArenaFlags_NoAllocTrack); + #if defined(DN_PLATFORM_WIN32) + os->platform_context = DN_Arena_New(&os->arena, DN_W32Core, DN_ZeroMem_Yes); + #elif defined(DN_PLATFORM_POSIX) + os->platform_context = DN_Arena_New(&os->arena, DN_POSIXCore, DN_ZeroMem_Yes); + #endif + + #if defined(DN_PLATFORM_WIN32) + DN_W32Core *w32 = DN_CAST(DN_W32Core *) os->platform_context; + InitializeCriticalSection(&w32->sync_primitive_free_list_mutex); + + QueryPerformanceFrequency(&w32->qpc_frequency); + HMODULE module = LoadLibraryA("kernel32.dll"); + w32->set_thread_description = DN_CAST(DN_W32SetThreadDescriptionFunc *) GetProcAddress(module, "SetThreadDescription"); + FreeLibrary(module); + + // NOTE: win32 bcrypt wchar_t const BCRYPT_ALGORITHM[] = L"RNG"; - long /*NTSTATUS*/ init_status = BCryptOpenAlgorithmProvider(&os->win32_bcrypt_rng_handle, BCRYPT_ALGORITHM, nullptr /*implementation*/, 0 /*flags*/); - if (os->win32_bcrypt_rng_handle && init_status == 0) - os->win32_bcrypt_init_success = true; + long /*NTSTATUS*/ init_status = BCryptOpenAlgorithmProvider(&w32->bcrypt_rng_handle, BCRYPT_ALGORITHM, nullptr /*implementation*/, 0 /*flags*/); + if (w32->bcrypt_rng_handle && init_status == 0) + w32->bcrypt_init_success = true; else DN_LOG_ErrorF("Failed to initialise Windows secure random number generator, error: %d", init_status); + #endif } - #endif // NOTE: Initialise tmem arenas which allocate memory and will be // recorded to the now initialised allocation table. The initialisation @@ -135,7 +152,6 @@ DN_API void DN_OS_Init(DN_OSCore *os, DN_OSInitArgs *args) #define DN_CPU_FEAT_XENTRY(label) g_dn_cpu_feature_decl[DN_CPUFeature_##label] = {DN_CPUFeature_##label, DN_STR8(#label)}; DN_CPU_FEAT_XMACRO #undef DN_CPU_FEAT_XENTRY - DN_Assert(g_dn_os_core_); } @@ -702,13 +718,13 @@ static void DN_OS_ThreadExecute_(void *user_context) DN_API void DN_OS_ThreadSetName(DN_Str8 name) { - DN_OSTLS *tls = DN_OS_TLSGet(); + DN_OSTLS *tls = DN_OS_TLSGet(); tls->name_size = DN_CAST(uint8_t) DN_Min(name.size, sizeof(tls->name) - 1); DN_Memcpy(tls->name, name.data, tls->name_size); tls->name[tls->name_size] = 0; #if defined(DN_PLATFORM_WIN32) - DN_Win_ThreadSetName(name); + DN_W32_ThreadSetName(name); #else DN_Posix_ThreadSetName(name); #endif @@ -717,7 +733,7 @@ DN_API void DN_OS_ThreadSetName(DN_Str8 name) // NOTE: DN_OSHttp ///////////////////////////////////////////////////////////////////////////////// DN_API void DN_OS_HttpRequestWait(DN_OSHttpResponse *response) { - if (response && DN_OS_SemaphoreIsValid(&response->on_complete_semaphore)) + if (response && response->on_complete_semaphore.handle != 0) DN_OS_SemaphoreWait(&response->on_complete_semaphore, DN_OS_SEMAPHORE_INFINITE_TIMEOUT); } diff --git a/OS/dn_os.h b/OS/dn_os.h index d6ed998..bcf0a8b 100644 --- a/OS/dn_os.h +++ b/OS/dn_os.h @@ -1,11 +1,13 @@ #if !defined(DN_OS_H) #define DN_OS_H +#include // operator new + #if defined(DN_PLATFORM_EMSCRIPTEN) || defined(DN_PLATFORM_POSIX) || defined(DN_PLATFORM_ARM64) #include "dn_os_posix.h" #elif defined(DN_PLATFORM_WIN32) #include "dn_os_windows.h" - #include "dn_os_win32.h" + #include "dn_os_w32.h" #else #error Please define a platform e.g. 'DN_PLATFORM_WIN32' to enable the correct implementation for platform APIs #endif @@ -38,10 +40,6 @@ #include // emscripten_fetch (for DN_OSHttpResponse) #endif -#if defined(DN_PLATFORM_WIN32) -typedef HRESULT DN_WinSetThreadDescriptionFunc(HANDLE hThread, PWSTR const lpThreadDescription); -#endif - // NOTE: DN_OSDate ///////////////////////////////////////////////////////////////////////////////// struct DN_OSDateTimeStr8 { @@ -178,11 +176,11 @@ struct DN_OSExecAsyncHandle struct DN_OSExecResult { - bool finished; - DN_Str8 stdout_text; - DN_Str8 stderr_text; - DN_U32 os_error_code; - DN_U32 exit_code; + bool finished; + DN_Str8 stdout_text; + DN_Str8 stderr_text; + DN_U32 os_error_code; + DN_U32 exit_code; }; struct DN_OSExecArgs @@ -192,18 +190,12 @@ struct DN_OSExecArgs DN_Slice environment; }; -#if !defined(DN_NO_SEMAPHORE) // NOTE: DN_OSSemaphore //////////////////////////////////////////////////////////////////////////// DN_U32 const DN_OS_SEMAPHORE_INFINITE_TIMEOUT = UINT32_MAX; struct DN_OSSemaphore { - #if defined(DN_OS_WIN32) && !defined(DN_OS_WIN32_USE_PTHREADS) - void *win32_handle; - #else - sem_t posix_handle; - bool posix_init; - #endif + DN_U64 handle; }; enum DN_OSSemaphoreWaitResult @@ -212,35 +204,31 @@ enum DN_OSSemaphoreWaitResult DN_OSSemaphoreWaitResult_Success, DN_OSSemaphoreWaitResult_Timeout, }; -#endif // !defined(DN_NO_SEMAPHORE) -// NOTE: DN_OSMutex //////////////////////////////////////////////////////////////////////////////// struct DN_OSMutex { -#if defined(DN_OS_WIN32) && !defined(DN_OS_WIN32_USE_PTHREADS) - char win32_handle[48]; -#else - pthread_mutex_t posix_handle; - pthread_mutexattr_t posix_attribs; -#endif + DN_U64 handle; +}; + +struct DN_OSConditionVariable +{ + DN_U64 handle; }; // NOTE: DN_OSThread /////////////////////////////////////////////////////////////////////////////// -#if !defined(DN_NO_THREAD) && !defined(DN_NO_SEMAPHORE) -typedef int32_t(DN_OSThreadFunc)(struct DN_OSThread *); +typedef DN_I32(DN_OSThreadFunc)(struct DN_OSThread *); struct DN_OSThread { DN_FStr8<64> name; - DN_OSTLS tls; - DN_OSTLSInitArgs tls_init_args; + DN_OSTLS tls; + DN_OSTLSInitArgs tls_init_args; void *handle; DN_U64 thread_id; void *user_context; DN_OSThreadFunc *func; DN_OSSemaphore init_semaphore; }; -#endif // !defined(DN_NO_THREAD) // NOTE: DN_OSHttp ///////////////////////////////////////////////////////////////////////////////// enum DN_OSHttpRequestSecure @@ -272,9 +260,9 @@ struct DN_OSHttpResponse #if defined(DN_PLATFORM_EMSCRIPTEN) emscripten_fetch_t *em_handle; #elif defined(DN_PLATFORM_WIN32) - HINTERNET win32_request_session; - HINTERNET win32_request_connection; - HINTERNET win32_request_handle; + HINTERNET w32_request_session; + HINTERNET w32_request_connection; + HINTERNET w32_request_handle; #endif }; @@ -300,6 +288,7 @@ struct DN_OSCore bool log_no_colour; // Disable colours in the logging output // NOTE: OS ////////////////////////////////////////////////////////////////////////////////////// + DN_U32 logical_processor_count; DN_U32 page_size; DN_U32 alloc_granularity; @@ -312,14 +301,8 @@ struct DN_OSCore DN_U64 mem_allocs_total; DN_U64 mem_allocs_frame; // Total OS heap allocs since the last 'DN_Core_FrameBegin' was invoked - // NOTE: Win32 ///////////////////////////////////////////////////////////////////////////////// - #if defined(DN_PLATFORM_WIN32) - DN_WinSetThreadDescriptionFunc *win32_set_thread_description; - LARGE_INTEGER win32_qpc_frequency; - void * win32_bcrypt_rng_handle; - bool win32_bcrypt_init_success; - bool win32_sym_initialised; - #endif + DN_Arena arena; + void *platform_context; }; struct DN_OSDiskSpace @@ -349,6 +332,8 @@ DN_API DN_OSDateTime DN_OS_DateLocalTimeNow (); DN_API DN_OSDateTimeStr8 DN_OS_DateLocalTimeStr8Now(char date_separator = '-', char hms_separator = ':'); DN_API DN_OSDateTimeStr8 DN_OS_DateLocalTimeStr8 (DN_OSDateTime time, char date_separator = '-', char hms_separator = ':'); DN_API DN_U64 DN_OS_DateUnixTimeNs (); +#define DN_OS_DateUnixTimeUs() (DN_OS_DateUnixTimeNs() / 1000) +#define DN_OS_DateUnixTimeMs() (DN_OS_DateUnixTimeNs() / 1000 * 1000) DN_API DN_U64 DN_OS_DateUnixTimeS (); DN_API DN_OSDateTime DN_OS_DateUnixTimeSToDate (DN_U64 time); DN_API DN_U64 DN_OS_DateLocalToUnixTimeS(DN_OSDateTime date); @@ -451,31 +436,30 @@ DN_API DN_OSExecResult DN_OS_Exec (DN_Slice c DN_API DN_OSExecResult DN_OS_ExecOrAbort (DN_Slice cmd_line, DN_OSExecArgs *args, DN_Arena *arena); #define DN_OS_ExecOrAbortFromTLS(...) DN_OS_ExecOrAbort(__VA_ARGS__, DN_OS_TLSTopArena()) -// NOTE: DN_OSSemaphore //////////////////////////////////////////////////////////////////////////// -#if !defined(DN_NO_SEMAPHORE) DN_API DN_OSSemaphore DN_OS_SemaphoreInit (DN_U32 initial_count); DN_API bool DN_OS_SemaphoreIsValid (DN_OSSemaphore *semaphore); DN_API void DN_OS_SemaphoreDeinit (DN_OSSemaphore *semaphore); DN_API void DN_OS_SemaphoreIncrement(DN_OSSemaphore *semaphore, DN_U32 amount); DN_API DN_OSSemaphoreWaitResult DN_OS_SemaphoreWait (DN_OSSemaphore *semaphore, DN_U32 timeout_ms); -#endif // !defined(DN_NO_SEMAPHORE) -// NOTE: DN_OSMutex //////////////////////////////////////////////////////////////////////////////// DN_API DN_OSMutex DN_OS_MutexInit (); DN_API void DN_OS_MutexDeinit(DN_OSMutex *mutex); DN_API void DN_OS_MutexLock (DN_OSMutex *mutex); DN_API void DN_OS_MutexUnlock(DN_OSMutex *mutex); -#define DN_OS_Mutex(mutex) DN_DEFER_LOOP(DN_OS_MutexLock(mutex), DN_OS_MutexUnlock(mutex)) +#define DN_OS_MutexScope(mutex) DN_DeferLoop(DN_OS_MutexLock(mutex), DN_OS_MutexUnlock(mutex)) + +DN_API DN_OSConditionVariable DN_OS_ConditionVariableInit (); +DN_API void DN_OS_ConditionVariableDeinit (DN_OSConditionVariable *cv); +DN_API bool DN_OS_ConditionVariableWait (DN_OSConditionVariable *cv, DN_OSMutex *mutex, DN_U64 sleep_ms); +DN_API bool DN_OS_ConditionVariableWaitUntil(DN_OSConditionVariable *cv, DN_OSMutex *mutex, DN_U64 end_ts_ms); +DN_API void DN_OS_ConditionVariableSignal (DN_OSConditionVariable *cv); +DN_API void DN_OS_ConditionVariableBroadcast(DN_OSConditionVariable *cv); -// NOTE: DN_OSThread /////////////////////////////////////////////////////////////////////////////// -#if !defined(DN_NO_THREAD) && !defined(DN_NO_SEMAPHORE) DN_API bool DN_OS_ThreadInit (DN_OSThread *thread, DN_OSThreadFunc *func, void *user_context); DN_API void DN_OS_ThreadDeinit(DN_OSThread *thread); DN_API DN_U32 DN_OS_ThreadID (); DN_API void DN_OS_ThreadSetName(DN_Str8 name); -#endif // !defined(DN_NO_THREAD) -// NOTE: DN_OSHttp ///////////////////////////////////////////////////////////////////////////////// DN_API void DN_OS_HttpRequestAsync(DN_OSHttpResponse *response, DN_Arena *arena, DN_Str8 host, DN_Str8 path, DN_OSHttpRequestSecure secure, DN_Str8 method, DN_Str8 body, DN_Str8 headers); DN_API void DN_OS_HttpRequestWait (DN_OSHttpResponse *response); DN_API void DN_OS_HttpRequestFree (DN_OSHttpResponse *response); diff --git a/OS/dn_os_posix.cpp b/OS/dn_os_posix.cpp index 8c23150..0c9fe89 100644 --- a/OS/dn_os_posix.cpp +++ b/OS/dn_os_posix.cpp @@ -4,11 +4,11 @@ #include // NOTE: DN_OSMem ////////////////////////////////////////////////////////////////////////////////// -static uint32_t DN_OS_MemConvertPageToOSFlags_(uint32_t protect) +static DN_U32 DN_OS_MemConvertPageToOSFlags_(DN_U32 protect) { DN_Assert((protect & ~DN_MemPage_All) == 0); DN_Assert(protect != 0); - uint32_t result = 0; + DN_U32 result = 0; if (protect & (DN_MemPage_NoAccess | DN_MemPage_Guard)) { result = PROT_NONE; @@ -21,7 +21,7 @@ static uint32_t DN_OS_MemConvertPageToOSFlags_(uint32_t protect) return result; } -DN_API void *DN_OS_MemReserve(DN_USize size, DN_MemCommit commit, uint32_t page_flags) +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); @@ -36,7 +36,7 @@ DN_API void *DN_OS_MemReserve(DN_USize size, DN_MemCommit commit, uint32_t page_ return result; } -DN_API bool DN_OS_MemCommit(void *ptr, DN_USize size, uint32_t page_flags) +DN_API bool DN_OS_MemCommit(void *ptr, DN_USize size, DN_U32 page_flags) { bool result = false; if (!ptr || size == 0) @@ -60,7 +60,7 @@ DN_API void DN_OS_MemRelease(void *ptr, DN_USize size) munmap(ptr, size); } -DN_API int DN_OS_MemProtect(void *ptr, DN_USize size, uint32_t page_flags) +DN_API int DN_OS_MemProtect(void *ptr, DN_USize size, DN_U32 page_flags) { if (!ptr || size == 0) return 0; @@ -162,7 +162,7 @@ DN_API DN_OSDateTime DN_OS_DateUnixTimeSToDate(uint64_t time) return result; } -DN_API bool DN_OS_SecureRNGBytes(void *buffer, uint32_t size) +DN_API bool DN_OS_SecureRNGBytes(void *buffer, DN_U32 size) { #if defined(DN_PLATFORM_EMSCRIPTEN) (void)buffer; @@ -182,7 +182,7 @@ DN_API bool DN_OS_SecureRNGBytes(void *buffer, uint32_t size) // TODO(doyle): // https://github.com/jedisct1/libsodium/blob/master/src/libsodium/randombytes/sysrandom/randombytes_sysrandom.c // TODO(doyle): https://man7.org/linux/man-pages/man2/getrandom.2.html - uint32_t read_bytes = 0; + DN_U32 read_bytes = 0; do { read_bytes = getrandom(buffer, size, 0); // NOTE: EINTR can not be triggered if size <= 32 bytes @@ -211,7 +211,7 @@ DN_API DN_OSDiskSpace DN_OS_DiskSpace(DN_Str8 path) return result; result.success = true; - result.free = info.f_bavail * info.f_frsize; + result.avail = info.f_bavail * info.f_frsize; result.size = info.f_blocks * info.f_frsize; return result; } @@ -275,7 +275,7 @@ DN_API void DN_OS_SleepMs(DN_UInt milliseconds) { struct timespec ts; ts.tv_sec = milliseconds / 1000; - ts.tv_nsec = (milliseconds % 1000) * 1000000; // Convert remaining milliseconds to nanoseconds + 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) ; @@ -916,8 +916,7 @@ DN_API DN_OSExecAsyncHandle DN_OS_ExecAsync(DN_Slice cmd_line, return result; } - DN_ForIndexU(arg_index, cmd_line.size) - { + for (DN_ForIndexU(arg_index, cmd_line.size)) { DN_Str8 arg = cmd_line.data[arg_index]; argv[arg_index] = DN_Str8_Copy(tmem.arena, arg).data; // NOTE: Copy string to guarantee it is null-terminated } @@ -990,7 +989,7 @@ DN_API DN_OSExecResult DN_OS_ExecPump(DN_OSExecAsyncHandle handle, size_t *stdout_size, char *stderr_buffer, size_t *stderr_size, - uint32_t timeout_ms, + DN_U32 timeout_ms, DN_OSErrSink *err) { DN_InvalidCodePath; @@ -998,75 +997,111 @@ DN_API DN_OSExecResult DN_OS_ExecPump(DN_OSExecAsyncHandle handle, return result; } -#if !defined(DN_NO_SEMAPHORE) -// NOTE: DN_OSSemaphore //////////////////////////////////////////////////////////////////////////// -DN_API DN_OSSemaphore DN_OS_SemaphoreInit(uint32_t initial_count) +static DN_POSIXCore *DN_OS_GetPOSIXCore_() { - DN_OSSemaphore result = {}; - int pshared = 0; // Share the semaphore across all threads in the process - if (sem_init(&result.posix_handle, pshared, initial_count) == 0) - result.posix_init = true; + DN_Assert(g_dn_os_core_ && g_dn_os_core_->platform_context); + DN_POSIXCore *result = DN_CAST(DN_POSIXCore *)g_dn_os_core_->platform_context; return result; } -DN_API bool DN_OS_SemaphoreIsValid(DN_OSSemaphore *semaphore) +static DN_POSIXSyncPrimitive *DN_OS_U64ToPOSIXSyncPrimitive_(DN_U64 u64) { - bool result = false; - if (semaphore) - result = semaphore->posix_init; + DN_POSIXSyncPrimitive *result = nullptr; + DN_Memcpy(&result, &u64, sizeof(u64)); + return result; +} + +static DN_U64 DN_POSIX_SyncPrimitiveToU64(DN_POSIXSyncPrimitive *primitive) +{ + DN_U64 result = 0; + static_assert(sizeof(result) == sizeof(primitive), "Pointer size mis-match"); + DN_Memcpy(&result, &primitive, sizeof(result)); + return result; +} + +static DN_POSIXSyncPrimitive *DN_POSIX_AllocSyncPrimitive_() +{ + DN_POSIXCore *posix = DN_OS_GetPOSIXCore_(); + DN_POSIXSyncPrimitive *result = nullptr; + pthread_mutex_lock(&posix->sync_primitive_free_list_mutex); + { + if (posix->sync_primitive_free_list) { + result = posix->sync_primitive_free_list; + posix->sync_primitive_free_list = posix->sync_primitive_free_list->next; + result->next = nullptr; + } else { + DN_OSCore *os = g_dn_os_core_; + result = DN_Arena_New(&os->arena, DN_POSIXSyncPrimitive, DN_ZeroMem_Yes); + } + } + pthread_mutex_unlock(&posix->sync_primitive_free_list_mutex); + return result; +} + +static void DN_POSIX_DeallocSyncPrimitive_(DN_POSIXSyncPrimitive *primitive) +{ + if (primitive) { + DN_POSIXCore *posix = DN_OS_GetPOSIXCore_(); + pthread_mutex_lock(&posix->sync_primitive_free_list_mutex); + primitive->next = posix->sync_primitive_free_list; + posix->sync_primitive_free_list = primitive; + pthread_mutex_unlock(&posix->sync_primitive_free_list_mutex); + } +} + +// NOTE: DN_OSSemaphore //////////////////////////////////////////////////////////////////////////// +DN_API DN_OSSemaphore DN_OS_SemaphoreInit(DN_U32 initial_count) +{ + DN_POSIXCore *posix = g_dn_os_core->posix_context; + DN_Assert(posix); + + DN_OSSemaphore result = {}; + DN_POSIXSyncPrimitive *primitive = DN_POSIX_AllocSyncPrimitive_(); + if (primitive) { + int pshared = 0; // Share the semaphore across all threads in the process + if (sem_init(&primitive->sem, pshared, initial_count) == 0) + result.handle = DN_POSIX_SyncPrimitiveToU64(primitive); + else + DN_POSIX_DeallocSyncPrimitive_(primitive); + } return result; } DN_API void DN_OS_SemaphoreDeinit(DN_OSSemaphore *semaphore) { - if (!DN_OS_SemaphoreIsValid(semaphore)) - return; - // TODO(doyle): Error handling? - if (semaphore->posix_init) - sem_destroy(&semaphore->posix_handle); - *semaphore = {}; + if (semaphore.handle != 0) { + DN_POSIXSyncPrimitive *primitive = DN_OS_U64ToPOSIXSyncPrimitive_(semaphore->handle); + sem_destroy(&primitive->sem); + DN_POSIX_DeallocSyncPrimitive_(posix_sem); + *semaphore = {}; + } } -// NOTE: These functions don't need semaphore to be passed by pointer, **BUT** -// the POSIX implementation disallows copies of sem_t. In particular: -// -// Source: The Open Group Base Specifications Issue 7, 2018 edition -// https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_09_09 -// -// 2.9.9 Synchronization Object Copies and Alternative Mappings -// -// For barriers, condition variables, mutexes, and read-write locks, [TSH] -// [Option Start] if the process-shared attribute is set to -// PTHREAD_PROCESS_PRIVATE, [Option End] only the synchronization object at the -// address used to initialize it can be used for performing synchronization. The -// effect of referring to another mapping of the same object when locking, -// unlocking, or destroying the object is undefined. [...] The effect of -// referring to a copy of the object when locking, unlocking, or destroying it -// is undefined. - -DN_API void DN_OS_SemaphoreIncrement(DN_OSSemaphore *semaphore, uint32_t amount) +DN_API void DN_OS_SemaphoreIncrement(DN_OSSemaphore *semaphore, DN_U32 amount) { - if (!DN_OS_SemaphoreIsValid(semaphore)) - return; - #if defined(DN_OS_WIN32) - sem_post_multiple(&semaphore->posix_handle, amount); // mingw extension - #else - DN_ForIndexU(index, amount) - sem_post(&semaphore->posix_handle); - #endif // !defined(DN_OS_WIN32) + if (semaphore.handle != 0) { + DN_POSIXSyncPrimitive *primitive = DN_OS_U64ToPOSIXSyncPrimitive_(semaphore->handle); + #if defined(DN_OS_WIN32) + sem_post_multiple(&primitive->sem, amount); // mingw extension + #else + for (DN_ForIndexU(index, amount)) + sem_post(&primitive->sem); + #endif // !defined(DN_OS_WIN32) + } } DN_API DN_OSSemaphoreWaitResult DN_OS_SemaphoreWait(DN_OSSemaphore *semaphore, - uint32_t timeout_ms) + DN_U32 timeout_ms) { DN_OSSemaphoreWaitResult result = {}; - if (!DN_OS_SemaphoreIsValid(semaphore)) + if (semaphore.handle == 0) return result; + DN_POSIXSyncPrimitive *primitive = DN_OS_U64ToPOSIXSyncPrimitive_(semaphore->handle); if (timeout_ms == DN_OS_SEMAPHORE_INFINITE_TIMEOUT) { int wait_result = 0; do { - wait_result = sem_wait(&semaphore->posix_handle); + wait_result = sem_wait(&primitive->sem); } while (wait_result == -1 && errno == EINTR); if (wait_result == 0) @@ -1075,47 +1110,115 @@ DN_API DN_OSSemaphoreWaitResult DN_OS_SemaphoreWait(DN_OSSemaphore *semaphore, struct timespec abs_timeout = {}; abs_timeout.tv_sec = timeout_ms / 1000; abs_timeout.tv_nsec = (timeout_ms % 1000) * 1'000'000; - if (sem_timedwait(&semaphore->posix_handle, &abs_timeout) == 0) + if (sem_timedwait(&primitive->sem) == 0) result = DN_OSSemaphoreWaitResult_Success; else if (errno == ETIMEDOUT) result = DN_OSSemaphoreWaitResult_Timeout; } return result; } -#endif // !defined(DN_NO_SEMAPHORE) -#if !defined(DN_NO_THREAD) // NOTE: DN_OSMutex //////////////////////////////////////////////////////////////////////////////// DN_API DN_OSMutex DN_OS_MutexInit() { - DN_OSMutex result = {}; - if (pthread_mutexattr_init(&result.posix_attribs) != 0) - return result; - if (pthread_mutex_init(&result.posix_handle, &result.posix_attribs) != 0) - return result; + DN_W32SyncPrimitive *primitive = DN_W32_AllocSyncPrimitive_(); + DN_OSMutex result = {}; + if (primitive) { + int pshared = 0; // Share the semaphore across all threads in the process + if (pthread_mutex_init(mutex, pshared, nullptr) == 0) + result.handle = DN_POSIX_SyncPrimitiveToU64(primitive); + else + DN_POSIX_DeallocSyncPrimitive_(primitive); + } return result; } DN_API void DN_OS_MutexDeinit(DN_OSMutex *mutex) { - if (!mutex) - return; - pthread_mutexattr_destroy(&mutex->posix_attribs); - pthread_mutex_destroy(&mutex->posix_handle); + if (mutex && mutex->handle != 0) { + DN_POSIXSyncPrimitive *primitive = DN_OS_U64ToPOSIXSyncPrimitive_(semaphore->handle); + pthread_mutex_destroy(&primitive->mutex); + DN_POSIX_DeallocSyncPrimitive_(primitive); + *mutex = {}; + } } DN_API void DN_OS_MutexLock(DN_OSMutex *mutex) { - if (!mutex) - return; - pthread_mutex_lock(&mutex->posix_handle); + if (mutex && mutex->handle != 0) { + DN_POSIXSyncPrimitive *primitive = DN_OS_U64ToPOSIXSyncPrimitive_(semaphore->handle); + pthread_mutex_lock(&primitive->mutex); + } } DN_API void DN_OS_MutexUnlock(DN_OSMutex *mutex) { - if (!mutex) - return; - pthread_mutex_unlock(&mutex->posix_handle); + if (mutex && mutex->handle != 0) { + DN_POSIXSyncPrimitive *primitive = DN_OS_U64ToPOSIXSyncPrimitive_(semaphore->handle); + pthread_mutex_unlock(&primitive->mutex); + } +} + +DN_API DN_OSConditionVariable DN_OS_ConditionVariableInit() +{ + DN_POSIXSyncPrimitive *primitive = DN_POSIX_AllocSyncPrimitive_(); + DN_OSConditionVariable result = {}; + if (primitive) { + if (pthread_cond_init(&primitive->cv) == 0) + result.handle = DN_POSIX_SyncPrimitiveToU64(primitive); + else + DN_POSIX_DeallocSyncPrimitive_(primitive); + } + return result; +} + +DN_API bool DN_OS_ConditionVariableDeinit(DN_OSConditionVariable *cv) +{ + if (cv && cv->handle != 0) { + DN_POSIXSyncPrimitive *primitive = DN_OS_U64ToPOSIXSyncPrimitive_(cv->handle); + pthread_cond_destroy(&primitive->cv); + DN_POSIX_DeallocSyncPrimitive_(primitive); + *cv = {}; + } +} + +DN_API bool DN_OS_ConditionVariableWaitUntil(DN_OSConditionVariable *cv, DN_OSMutex *mutex, DN_U64 end_ts_ms) +{ + bool result = false; + if (cv && mutex && mutex->handle != 0 && cv->handle != 0) { + DN_POSIXSyncPrimitive *cv_primitive = DN_OS_U64ToPOSIXSyncPrimitive_(cv->handle); + DN_POSIXSyncPrimitive *mutex_primitive = DN_OS_U64ToPOSIXSyncPrimitive_(mutex->handle); + + struct timespec time; + time.tv_sec = end_ts_ms / 1'000; + time.tv_nsec = 1'000'000 * (end_ts_ms - (end_ts_ms / 1'000) * 1'000); + int wait_result = pthread_cond_timedwait(&cv_primitive->cv, &mutex_primitive->mutex, &time); + result = (wait_result != ETIMEDOUT); + } + return result; +} + +DN_API bool DN_OS_ConditionVariableWait(DN_OSConditionVariable *cv, DN_OSMutex *mutex, DN_U64 sleep_ms) +{ + DN_U64 end_ts_ms = DN_OS_DateUnixTimeMs() + sleep_ms; + bool result = DN_OS_ConditionVariableWaitUntil(cv, mutex, end_ts_ms); + return result; +} + +DN_API void DN_OS_ConditionVariableSignal(DN_OSConditionVariable *cv) +{ + if (cv && cv->handle != 0) { + DN_POSIXSyncPrimitive *primitive = DN_OS_U64ToPOSIXSyncPrimitive_(cv->handle); + pthread_cond_signal(&primitive->cv); + } +} + +DN_API void DN_OS_ConditionVariableBroadcast(DN_OSConditionVariable *cv) +{ + if (cv && cv->handle != 0) { + DN_POSIXSyncPrimitive *primitive = DN_OS_U64ToPOSIXSyncPrimitive_(cv->handle); + pthread_cond_broadcast(&primitive->cv); + } } // NOTE: DN_OSThread /////////////////////////////////////////////////////////////////////////////// @@ -1181,11 +1284,11 @@ DN_API void DN_OS_ThreadDeinit(DN_OSThread *thread) thread->thread_id = {}; } -DN_API uint32_t DN_OS_ThreadID() +DN_API DN_U32 DN_OS_ThreadID() { pid_t result = gettid(); DN_Assert(gettid() >= 0); - return DN_CAST(uint32_t) result; + return DN_CAST(DN_U32) result; } DN_API void DN_Posix_ThreadSetName(DN_Str8 name) @@ -1195,7 +1298,6 @@ DN_API void DN_Posix_ThreadSetName(DN_Str8 name) pthread_t thread = pthread_self(); pthread_setname_np(thread, (char *)copy.data); } -#endif // !defined(DN_NO_THREAD) DN_API DN_POSIXProcSelfStatus DN_Posix_ProcSelfStatus() { @@ -1230,7 +1332,7 @@ DN_API DN_POSIXProcSelfStatus DN_Posix_ProcSelfStatus() DN_Str8 status_buf = DN_Str8Builder_BuildFromTLS(&builder); DN_Slice lines = DN_Str8_SplitAllocFromTLS(status_buf, DN_STR8("\n"), DN_Str8SplitIncludeEmptyStrings_No); - DN_ForIt(line_it, DN_Str8, &lines) { + for (DN_ForIt(line_it, DN_Str8, &lines)) { DN_Str8 line = DN_Str8_TrimWhitespaceAround(*line_it.data); if (DN_Str8_StartsWith(line, NAME, DN_Str8EqCase_Insensitive)) { DN_Str8 str8 = DN_Str8_TrimWhitespaceAround(DN_Str8_Slice(line, NAME.size, line.size)); @@ -1312,7 +1414,7 @@ static void DN_OS_HttpRequestEMFetchOnSuccessCallback(emscripten_fetch_t *fetch) if (!DN_Check(response)) return; - response->http_status = DN_CAST(uint32_t) fetch->status; + response->http_status = DN_CAST(DN_U32) fetch->status; response->body = DN_Str8_Alloc(response->arena, fetch->numBytes, DN_ZeroMem_No); if (response->body.data) DN_Memcpy(response->body.data, fetch->data, fetch->numBytes); @@ -1327,7 +1429,7 @@ static void DN_OS_HttpRequestEMFetchOnErrorCallback(emscripten_fetch_t *fetch) if (!DN_Check(response)) return; - response->http_status = DN_CAST(uint32_t) fetch->status; + response->http_status = DN_CAST(DN_U32) fetch->status; response->body = DN_Str8_Alloc(response->arena, fetch->numBytes, DN_ZeroMem_No); if (response->body.size) DN_Memcpy(response->body.data, fetch->data, fetch->numBytes); @@ -1372,7 +1474,7 @@ DN_API void DN_OS_HttpRequestAsync(DN_OSHttpResponse *response, DN_CheckF(method.size < sizeof(fetch_attribs.requestMethod), "%.*s", DN_STR_FMT(response->error_msg)); - response->error_code = DN_CAST(uint32_t) - 1; + response->error_code = DN_CAST(DN_U32) - 1; DN_Atomic_AddU32(&response->done, 1); return; } @@ -1409,7 +1511,6 @@ DN_API void DN_OS_HttpRequestFree(DN_OSHttpResponse *response) #endif // #elif defined(DN_OS_WIN32) DN_Arena_Deinit(&response->tmp_arena); - if (DN_OS_SemaphoreIsValid(&response->on_complete_semaphore)) - DN_OS_SemaphoreDeinit(&response->on_complete_semaphore); + DN_OS_SemaphoreDeinit(&response->on_complete_semaphore); *response = {}; } diff --git a/OS/dn_os_posix.h b/OS/dn_os_posix.h index c9bacf6..5ce3566 100644 --- a/OS/dn_os_posix.h +++ b/OS/dn_os_posix.h @@ -1,5 +1,8 @@ #pragma once +#include +#include + /* //////////////////////////////////////////////////////////////////////////////////////////////////// // @@ -26,5 +29,47 @@ struct DN_POSIXProcSelfStatus DN_U32 vm_size; }; +// NOTE: The POSIX implementation disallows copies of synchronisation objects in +// general hence we have to dynamically allocate these primitives to maintain a +// consistent address. +// +// +// Source: The Open Group Base Specifications Issue 7, 2018 edition +// https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_09_09 +// +// 2.9.9 Synchronization Object Copies and Alternative Mappings +// +// For barriers, condition variables, mutexes, and read-write locks, [TSH] +// [Option Start] if the process-shared attribute is set to +// PTHREAD_PROCESS_PRIVATE, [Option End] only the synchronization object at the +// address used to initialize it can be used for performing synchronization. The +// effect of referring to another mapping of the same object when locking, +// unlocking, or destroying the object is undefined. [...] The effect of +// referring to a copy of the object when locking, unlocking, or destroying it +// is undefined. + +enum DN_POSIXSyncPrimitiveType +{ + DN_OSPOSIXSyncPrimitiveType_Semaphore, + DN_OSPOSIXSyncPrimitiveType_Mutex, + DN_OSPOSIXSyncPrimitiveType_ConditionVariable, +}; + +struct DN_POSIXSyncPrimitive +{ + union + { + sem_t sem; + pthread_mutex_t mutex; + pthread_cond_t cv; + }; + DN_POSIXSyncPrimitive *next; +}; + +struct DN_POSIXCore +{ + DN_POSIXSyncPrimitive *sync_primitive_free_list; +}; + DN_API void DN_Posix_ThreadSetName(DN_Str8 name); DN_API DN_POSIXProcSelfStatus DN_Posix_ProcSelfStatus(); diff --git a/OS/dn_os_tls.cpp b/OS/dn_os_tls.cpp index 510f97d..969e420 100644 --- a/OS/dn_os_tls.cpp +++ b/OS/dn_os_tls.cpp @@ -35,9 +35,9 @@ DN_API void DN_OS_TLSInit(DN_OSTLS *tls, DN_OSTLSInitArgs args) // TODO: We shouldn't have the no alloc track flag here but the initial TLS // init on OS init happens before CORE init. CORE init is the one responsible // for setting up the alloc tracking data structures. - DN_ForIndexU(index, DN_OSTLSArena_Count) { - DN_Arena *arena = tls->arenas + index; - switch (DN_CAST(DN_OSTLSArena) index) { + for (DN_ForItCArray(it, DN_Arena, tls->arenas)) { + DN_Arena *arena = it.data; + switch (DN_CAST(DN_OSTLSArena) it.index) { default: *arena = DN_Arena_InitFromOSVMem(reserve, commit, DN_ArenaFlags_AllocCanLeak | DN_ArenaFlags_NoAllocTrack); break; case DN_OSTLSArena_ErrorSink: *arena = DN_Arena_InitFromOSVMem(err_sink_reserve, err_sink_commit, DN_ArenaFlags_AllocCanLeak | DN_ArenaFlags_NoAllocTrack); break; case DN_OSTLSArena_Count: DN_InvalidCodePath; break; @@ -54,10 +54,8 @@ DN_API void DN_OS_TLSDeinit(DN_OSTLS *tls) tls->init = false; tls->err_sink = {}; tls->arena_stack_index = {}; - DN_ForIndexU(index, DN_OSTLSArena_Count) { - DN_Arena *arena = tls->arenas + index; - DN_Arena_Deinit(arena); - } + for (DN_ForItCArray(it, DN_Arena, tls->arenas)) + DN_Arena_Deinit(it.data); } DN_THREAD_LOCAL DN_OSTLS *g_dn_curr_thread_tls; @@ -170,7 +168,7 @@ DN_API DN_OSErrSink *DN_OS_ErrSinkBegin_(DN_OSErrSinkMode mode, DN_CallSite call if (tls->err_sink.stack_size == DN_ArrayCountU(err->stack)) { DN_Str8Builder builder = DN_Str8Builder_InitFromTLS(); DN_USize counter = 0; - DN_ForItSize(it, DN_OSErrSinkNode, err->stack, err->stack_size) { + for (DN_ForItSize(it, DN_OSErrSinkNode, err->stack, err->stack_size)) { DN_MSVC_WARNING_PUSH DN_MSVC_WARNING_DISABLE(6284) // Object passed as _Param_(4) when a string is required in call to 'DN_Str8Builder_AppendF' Actual type: 'struct DN_Str8'. DN_Str8Builder_AppendF(&builder, " [%04zu] %S:%u %S\n", counter++, it.data->call_site.file, it.data->call_site.line, it.data->call_site.function); @@ -185,9 +183,9 @@ DN_API DN_OSErrSink *DN_OS_ErrSinkBegin_(DN_OSErrSinkMode mode, DN_CallSite call } DN_OSErrSinkNode *node = tls->err_sink.stack + tls->err_sink.stack_size++; - node->arena_pos = arena_pos; - node->mode = mode; - node->call_site = call_site; + node->arena_pos = arena_pos; + node->mode = mode; + node->call_site = call_site; DN_DLList_InitArena(node->msg_sentinel, DN_OSErrSinkMsg, result->arena); // NOTE: Handle allocation error diff --git a/OS/dn_os_w32.cpp b/OS/dn_os_w32.cpp new file mode 100644 index 0000000..aab8058 --- /dev/null +++ b/OS/dn_os_w32.cpp @@ -0,0 +1,1843 @@ +#define DN_OS_WIN32_CPP + +/* +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$$$$$\ $$$$$$\ $$\ $$\ $$$$$$\ $$\ $$\ $$$$$$\ $$$$$$\ +// $$ __$$\ $$ __$$\ $$ | $\ $$ |\_$$ _|$$$\ $$ |$$ ___$$\ $$ __$$\ +// $$ / $$ |$$ / \__| $$ |$$$\ $$ | $$ | $$$$\ $$ |\_/ $$ |\__/ $$ | +// $$ | $$ |\$$$$$$\ $$ $$ $$\$$ | $$ | $$ $$\$$ | $$$$$ / $$$$$$ | +// $$ | $$ | \____$$\ $$$$ _$$$$ | $$ | $$ \$$$$ | \___$$\ $$ ____/ +// $$ | $$ |$$\ $$ | $$$ / \$$$ | $$ | $$ |\$$$ |$$\ $$ |$$ | +// $$$$$$ |\$$$$$$ | $$ / \$$ |$$$$$$\ $$ | \$$ |\$$$$$$ |$$$$$$$$\ +// \______/ \______/ \__/ \__|\______|\__| \__| \______/ \________| +// +// dn_os_w32.cpp +// +//////////////////////////////////////////////////////////////////////////////////////////////////// +*/ + +// NOTE: DN_Mem /////////////////////////////////////////////////////////////////////////// +static DN_U32 DN_OS_MemConvertPageToOSFlags_(DN_U32 protect) +{ + DN_Assert((protect & ~DN_MemPage_All) == 0); + DN_Assert(protect != 0); + DN_U32 result = 0; + + if (protect & DN_MemPage_NoAccess) { + result = PAGE_NOACCESS; + } else if (protect & DN_MemPage_ReadWrite) { + result = PAGE_READWRITE; + } else if (protect & DN_MemPage_Read) { + result = PAGE_READONLY; + } else if (protect & DN_MemPage_Write) { + DN_LOG_WarningF("Windows does not support write-only pages, granting read+write access"); + result = PAGE_READWRITE; + } + + if (protect & DN_MemPage_Guard) + result |= PAGE_GUARD; + + DN_AssertF(result != PAGE_GUARD, "Page guard is a modifier, you must also specify a page permission like read or/and write"); + return result; +} + +DN_API void *DN_OS_MemReserve(DN_USize size, DN_MemCommit commit, DN_U32 page_flags) +{ + unsigned long os_page_flags = DN_OS_MemConvertPageToOSFlags_(page_flags); + unsigned long flags = MEM_RESERVE; + if (commit == DN_MemCommit_Yes) + flags |= MEM_COMMIT; + + void *result = VirtualAlloc(nullptr, size, flags, os_page_flags); + if (flags & MEM_COMMIT) { + DN_Assert(g_dn_os_core_); + DN_Atomic_AddU64(&g_dn_os_core_->vmem_allocs_total, 1); + DN_Atomic_AddU64(&g_dn_os_core_->vmem_allocs_frame, 1); + } + return result; +} + +DN_API bool DN_OS_MemCommit(void *ptr, DN_USize size, DN_U32 page_flags) +{ + bool result = false; + if (!ptr || size == 0) + return false; + unsigned long os_page_flags = DN_OS_MemConvertPageToOSFlags_(page_flags); + result = VirtualAlloc(ptr, size, MEM_COMMIT, os_page_flags) != nullptr; + DN_Assert(g_dn_os_core_); + DN_Atomic_AddU64(&g_dn_os_core_->vmem_allocs_total, 1); + DN_Atomic_AddU64(&g_dn_os_core_->vmem_allocs_frame, 1); + return result; +} + +DN_API void DN_OS_MemDecommit(void *ptr, DN_USize size) +{ + // NOTE: This is a decommit call, which is explicitly saying to free the + // pages but not the address space, you would use OS_MemRelease to release + // everything. + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(6250) // Calling 'VirtualFree' without the MEM_RELEASE flag might free memory but not address descriptors (VADs). This causes address space leaks. + VirtualFree(ptr, size, MEM_DECOMMIT); + DN_MSVC_WARNING_POP +} + +DN_API void DN_OS_MemRelease(void *ptr, DN_USize size) +{ + (void)size; + VirtualFree(ptr, 0, MEM_RELEASE); +} + +DN_API int DN_OS_MemProtect(void *ptr, DN_USize size, DN_U32 page_flags) +{ + if (!ptr || size == 0) + return 0; + + static DN_Str8 const ALIGNMENT_ERROR_MSG = + DN_STR8("Page protection requires pointers to be page aligned because we can only guard memory at a multiple of the page boundary."); + DN_AssertF(DN_IsPowerOfTwoAligned(DN_CAST(uintptr_t) ptr, g_dn_os_core_->page_size), "%s", ALIGNMENT_ERROR_MSG.data); + DN_AssertF(DN_IsPowerOfTwoAligned(size, g_dn_os_core_->page_size), "%s", ALIGNMENT_ERROR_MSG.data); + + unsigned long os_page_flags = DN_OS_MemConvertPageToOSFlags_(page_flags); + unsigned long prev_flags = 0; + int result = VirtualProtect(ptr, size, os_page_flags, &prev_flags); + + (void)prev_flags; + if (result == 0) + DN_AssertF(result, "VirtualProtect failed"); + return result; +} + +DN_API void *DN_OS_MemAlloc(DN_USize size, DN_ZeroMem zero_mem) +{ + DN_U32 flags = zero_mem == DN_ZeroMem_Yes ? HEAP_ZERO_MEMORY : 0; + DN_Assert(size <= DN_CAST(DWORD)(-1)); + void *result = HeapAlloc(GetProcessHeap(), flags, DN_CAST(DWORD) size); + DN_Assert(g_dn_os_core_); + DN_Atomic_AddU64(&g_dn_os_core_->mem_allocs_total, 1); + DN_Atomic_AddU64(&g_dn_os_core_->mem_allocs_frame, 1); + return result; +} + +DN_API void DN_OS_MemDealloc(void *ptr) +{ + HeapFree(GetProcessHeap(), 0, ptr); +} + +// NOTE: Date ////////////////////////////////////////////////////////////////////////////////////// +DN_API DN_OSDateTime DN_OS_DateLocalTimeNow() +{ + SYSTEMTIME sys_time; + GetLocalTime(&sys_time); + + DN_OSDateTime result = {}; + result.hour = DN_CAST(uint8_t) sys_time.wHour; + result.minutes = DN_CAST(uint8_t) sys_time.wMinute; + result.seconds = DN_CAST(uint8_t) sys_time.wSecond; + result.day = DN_CAST(uint8_t) sys_time.wDay; + result.month = DN_CAST(uint8_t) sys_time.wMonth; + result.year = DN_CAST(int16_t) sys_time.wYear; + return result; +} + +const DN_U64 DN_OS_WIN32_UNIX_TIME_START = 0x019DB1DED53E8000; // January 1, 1970 (start of Unix epoch) in "ticks" +const DN_U64 DN_OS_WIN32_FILE_TIME_TICKS_PER_SECOND = 10'000'000; // Filetime returned is in intervals of 100 nanoseconds + +DN_API DN_U64 DN_OS_DateUnixTimeNs() +{ + FILETIME file_time; + GetSystemTimeAsFileTime(&file_time); + + // NOTE: Filetime returned is in intervals of 100 nanoeseconds so we + // multiply by 100 to get nanoseconds. + LARGE_INTEGER date_time; + date_time.u.LowPart = file_time.dwLowDateTime; + date_time.u.HighPart = file_time.dwHighDateTime; + DN_U64 result = (date_time.QuadPart - DN_OS_WIN32_UNIX_TIME_START) * 100; + return result; +} + +static SYSTEMTIME DN_OS_DateToSystemTime_(DN_OSDateTime date) +{ + SYSTEMTIME result = {}; + result.wYear = date.year; + result.wMonth = date.month; + result.wDay = date.day; + result.wHour = date.hour; + result.wMinute = date.minutes; + result.wSecond = date.seconds; + return result; +} + +static DN_U64 DN_OS_SystemTimeToUnixTimeS_(SYSTEMTIME *sys_time) +{ + FILETIME file_time = {}; + SystemTimeToFileTime(sys_time, &file_time); + + LARGE_INTEGER date_time; + date_time.u.LowPart = file_time.dwLowDateTime; + date_time.u.HighPart = file_time.dwHighDateTime; + DN_U64 result = (date_time.QuadPart - DN_OS_WIN32_UNIX_TIME_START) / DN_OS_WIN32_FILE_TIME_TICKS_PER_SECOND; + return result; +} + +DN_API DN_U64 DN_OS_DateLocalToUnixTimeS(DN_OSDateTime date) +{ + SYSTEMTIME local_time = DN_OS_DateToSystemTime_(date); + SYSTEMTIME sys_time = {}; + TzSpecificLocalTimeToSystemTime(nullptr, &local_time, &sys_time); + DN_U64 result = DN_OS_SystemTimeToUnixTimeS_(&sys_time); + return result; +} + +DN_API DN_U64 DN_OS_DateToUnixTimeS(DN_OSDateTime date) +{ + DN_Assert(DN_OS_DateIsValid(date)); + + SYSTEMTIME sys_time = DN_OS_DateToSystemTime_(date); + DN_U64 result = DN_OS_SystemTimeToUnixTimeS_(&sys_time); + return result; +} + +DN_API DN_OSDateTime DN_OS_DateUnixTimeSToDate(DN_U64 time) +{ + // NOTE: Windows epoch time starts from Jan 1, 1601 and counts in + // 100-nanoseconds intervals. + // + // See: https://devblogs.microsoft.com/oldnewthing/20090306-00/?p=18913 + + DN_U64 w32_time = 116'444'736'000'000'000 + (time * 10'000'000); + SYSTEMTIME sys_time = {}; + FILETIME file_time = {}; + file_time.dwLowDateTime = (DWORD)w32_time; + file_time.dwHighDateTime = w32_time >> 32; + FileTimeToSystemTime(&file_time, &sys_time); + + DN_OSDateTime result = {}; + result.year = DN_CAST(uint16_t) sys_time.wYear; + result.month = DN_CAST(uint8_t) sys_time.wMonth; + result.day = DN_CAST(uint8_t) sys_time.wDay; + result.hour = DN_CAST(uint8_t) sys_time.wHour; + result.minutes = DN_CAST(uint8_t) sys_time.wMinute; + result.seconds = DN_CAST(uint8_t) sys_time.wSecond; + return result; +} + +DN_API bool DN_OS_SecureRNGBytes(void *buffer, DN_U32 size) +{ + DN_Assert(g_dn_os_core_); + DN_W32Core *w32 = DN_CAST(DN_W32Core *) g_dn_os_core_->platform_context; + + if (!buffer || size < 0 || !w32->bcrypt_init_success) + return false; + + if (size == 0) + return true; + + long gen_status = BCryptGenRandom(w32->bcrypt_rng_handle, DN_CAST(unsigned char *) buffer, size, 0 /*flags*/); + if (gen_status != 0) { + DN_LOG_ErrorF("Failed to generate random bytes: %d", gen_status); + return false; + } + + return true; +} + +DN_API DN_OSDiskSpace DN_OS_DiskSpace(DN_Str8 path) +{ + DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(nullptr); + DN_OSDiskSpace result = {}; + DN_Str16 path16 = DN_W32_Str8ToStr16(tmem.arena, path); + + ULARGE_INTEGER free_bytes_avail_to_caller; + ULARGE_INTEGER total_number_of_bytes; + ULARGE_INTEGER total_number_of_free_bytes; + if (!GetDiskFreeSpaceExW(path16.data, + &free_bytes_avail_to_caller, + &total_number_of_bytes, + &total_number_of_free_bytes)) + return result; + + result.success = true; + result.avail = free_bytes_avail_to_caller.QuadPart; + result.size = total_number_of_bytes.QuadPart; + return result; +} + +DN_API bool DN_OS_SetEnvVar(DN_Str8 name, DN_Str8 value) +{ + DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(nullptr); + DN_Str16 name16 = DN_W32_Str8ToStr16(tmem.arena, name); + DN_Str16 value16 = DN_W32_Str8ToStr16(tmem.arena, value); + bool result = SetEnvironmentVariableW(name16.data, value16.data) != 0; + return result; +} + +DN_API DN_Str8 DN_OS_EXEPath(DN_Arena *arena) +{ + DN_Str8 result = {}; + if (!arena) + return result; + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + DN_Str16 exe_dir16 = DN_W32_EXEPathW(tmem.arena); + result = DN_W32_Str16ToStr8(arena, exe_dir16); + return result; +} + +DN_API void DN_OS_SleepMs(DN_UInt milliseconds) +{ + Sleep(milliseconds); +} + +DN_API DN_U64 DN_OS_PerfCounterFrequency() +{ + DN_Assert(g_dn_os_core_); + DN_W32Core *w32 = DN_CAST(DN_W32Core *) g_dn_os_core_->platform_context; + DN_Assert(w32->qpc_frequency.QuadPart); + DN_U64 result = w32->qpc_frequency.QuadPart; + return result; +} + +DN_API DN_U64 DN_OS_PerfCounterNow() +{ + LARGE_INTEGER integer = {}; + QueryPerformanceCounter(&integer); + DN_U64 result = integer.QuadPart; + return result; +} + +#if !defined(DN_NO_OS_FILE_API) +static DN_U64 DN_W32_FileTimeToSeconds_(FILETIME const *time) +{ + ULARGE_INTEGER time_large_int = {}; + time_large_int.u.LowPart = time->dwLowDateTime; + time_large_int.u.HighPart = time->dwHighDateTime; + DN_U64 result = (time_large_int.QuadPart / 10000000ULL) - 11644473600ULL; + return result; +} + +DN_API DN_OSPathInfo DN_OS_PathInfo(DN_Str8 path) +{ + DN_OSPathInfo result = {}; + if (!DN_Str8_HasData(path)) + return result; + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str16 path16 = DN_W32_Str8ToStr16(tmem.arena, path); + + WIN32_FILE_ATTRIBUTE_DATA attrib_data = {}; + if (!GetFileAttributesExW(path16.data, GetFileExInfoStandard, &attrib_data)) + return result; + + result.exists = true; + result.create_time_in_s = DN_W32_FileTimeToSeconds_(&attrib_data.ftCreationTime); + result.last_access_time_in_s = DN_W32_FileTimeToSeconds_(&attrib_data.ftLastAccessTime); + result.last_write_time_in_s = DN_W32_FileTimeToSeconds_(&attrib_data.ftLastWriteTime); + + LARGE_INTEGER large_int = {}; + large_int.u.HighPart = DN_CAST(int32_t) attrib_data.nFileSizeHigh; + large_int.u.LowPart = attrib_data.nFileSizeLow; + result.size = (DN_U64)large_int.QuadPart; + + if (attrib_data.dwFileAttributes != INVALID_FILE_ATTRIBUTES) { + if (attrib_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + result.type = DN_OSPathInfoType_Directory; + else + result.type = DN_OSPathInfoType_File; + } + + return result; +} + +DN_API bool DN_OS_PathDelete(DN_Str8 path) +{ + bool result = false; + if (!DN_Str8_HasData(path)) + return result; + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str16 path16 = DN_W32_Str8ToStr16(tmem.arena, path); + if (path16.size) { + result = DeleteFileW(path16.data); + if (!result) + result = RemoveDirectoryW(path16.data); + } + return result; +} + +DN_API bool DN_OS_FileExists(DN_Str8 path) +{ + bool result = false; + if (!DN_Str8_HasData(path)) + return result; + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str16 path16 = DN_W32_Str8ToStr16(tmem.arena, path); + if (path16.size) { + WIN32_FILE_ATTRIBUTE_DATA attrib_data = {}; + if (GetFileAttributesExW(path16.data, GetFileExInfoStandard, &attrib_data)) + result = (attrib_data.dwFileAttributes != INVALID_FILE_ATTRIBUTES) && + !(attrib_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); + } + return result; +} + +DN_API bool DN_OS_CopyFile(DN_Str8 src, DN_Str8 dest, bool overwrite, DN_OSErrSink *err) +{ + bool result = false; + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str16 src16 = DN_W32_Str8ToStr16(tmem.arena, src); + DN_Str16 dest16 = DN_W32_Str8ToStr16(tmem.arena, dest); + + int fail_if_exists = overwrite == false; + result = CopyFileW(src16.data, dest16.data, fail_if_exists) != 0; + + if (!result) { + DN_W32Error win_error = DN_W32_LastError(tmem.arena); + DN_OS_ErrSinkAppendF(err, + win_error.code, + "Failed to copy file '%.*s' to '%.*s': (%u) %.*s", + DN_STR_FMT(src), + DN_STR_FMT(dest), + win_error.code, + DN_STR_FMT(win_error.msg)); + } + return result; +} + +DN_API bool DN_OS_MoveFile(DN_Str8 src, DN_Str8 dest, bool overwrite, DN_OSErrSink *err) +{ + bool result = false; + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str16 src16 = DN_W32_Str8ToStr16(tmem.arena, src); + DN_Str16 dest16 = DN_W32_Str8ToStr16(tmem.arena, dest); + + unsigned long flags = MOVEFILE_COPY_ALLOWED; + if (overwrite) + flags |= MOVEFILE_REPLACE_EXISTING; + + result = MoveFileExW(src16.data, dest16.data, flags) != 0; + if (!result) { + DN_W32Error win_error = DN_W32_LastError(tmem.arena); + DN_OS_ErrSinkAppendF(err, + win_error.code, + "Failed to move file '%.*s' to '%.*s': (%u) %.*s", + DN_STR_FMT(src), + DN_STR_FMT(dest), + win_error.code, + DN_STR_FMT(win_error.msg)); + } + return result; +} + +DN_API bool DN_OS_MakeDir(DN_Str8 path) +{ + bool result = true; + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str16 path16 = DN_W32_Str8ToStr16(tmem.arena, path); + + // NOTE: Go back from the end of the string to all the directories in the + // string, and try to create them. Since Win32 API cannot create + // intermediate directories that don't exist in a path we need to go back + // and record all the directories until we encounter one that exists. + // + // From that point onwards go forwards and make all the directories + // inbetween by null-terminating the string temporarily, creating the + // directory and so forth until we reach the end. + // + // If we find a file at some point in the path we fail out because the + // series of directories can not be made if a file exists with the same + // name. + for (DN_USize index = 0; index < path16.size; index++) { + bool first_char = index == (path16.size - 1); + wchar_t ch = path16.data[index]; + if (ch == '/' || ch == '\\' || first_char) { + wchar_t temp = path16.data[index]; + if (!first_char) + path16.data[index] = 0; // Temporarily null terminate it + + WIN32_FILE_ATTRIBUTE_DATA attrib_data = {}; + bool successful = GetFileAttributesExW(path16.data, GetFileExInfoStandard, &attrib_data); // Check + + if (successful) { + if (attrib_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + // NOTE: The directory exists, continue iterating the path + } else { + // NOTE: There's some kind of file that exists at the path + // but it's not a directory. This request to make a + // directory is invalid. + return false; + } + } else { + // NOTE: There's nothing that exists at this path, we can create + // a directory here + result |= (CreateDirectoryW(path16.data, nullptr) == 0); + } + + if (!first_char) + path16.data[index] = temp; // Undo null termination + } + } + return result; +} + +DN_API bool DN_OS_DirExists(DN_Str8 path) +{ + bool result = false; + if (!DN_Str8_HasData(path)) + return result; + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str16 path16 = DN_W32_Str8ToStr16(tmem.arena, path); + if (path16.size) { + WIN32_FILE_ATTRIBUTE_DATA attrib_data = {}; + if (GetFileAttributesExW(path16.data, GetFileExInfoStandard, &attrib_data)) + result = (attrib_data.dwFileAttributes != INVALID_FILE_ATTRIBUTES) && + (attrib_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); + } + + return result; +} + +DN_API bool DN_OS_DirIterate(DN_Str8 path, DN_OSDirIterator *it) +{ + if (!DN_Str8_HasData(path) || !it || path.size <= 0) + return false; + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_W32FolderIteratorW wide_it = {}; + DN_Str16 path16 = {}; + if (it->handle) { + wide_it.handle = it->handle; + } else { + bool needs_asterisks = DN_Str8_EndsWith(path, DN_STR8("\\")) || + DN_Str8_EndsWith(path, DN_STR8("/")); + bool has_glob = DN_Str8_EndsWith(path, DN_STR8("\\*")) || + DN_Str8_EndsWith(path, DN_STR8("/*")); + + DN_Str8 adjusted_path = path; + if (!has_glob) { + // NOTE: We are missing the glob for enumerating the files, we will + // add those characters in this branch, so overwrite the null + // character, add the glob and re-null terminate the buffer. + if (needs_asterisks) + adjusted_path = DN_OS_PathF(tmem.arena, "%.*s*", DN_STR_FMT(path)); + else + adjusted_path = DN_OS_PathF(tmem.arena, "%.*s/*", DN_STR_FMT(path)); + } + + path16 = DN_W32_Str8ToStr16(tmem.arena, adjusted_path); + if (path16.size <= 0) // Conversion error + return false; + } + + bool result = DN_W32_DirWIterate(path16, &wide_it); + it->handle = wide_it.handle; + if (result) { + int size = DN_W32_Str16ToStr8Buffer(wide_it.file_name, it->buffer, DN_ArrayCountU(it->buffer)); + it->file_name = DN_Str8_Init(it->buffer, size); + } + + return result; +} + +// NOTE: R/W Stream API //////////////////////////////////////////////////////////////////////////// +DN_API DN_OSFile DN_OS_FileOpen(DN_Str8 path, DN_OSFileOpen open_mode, DN_U32 access, DN_OSErrSink *err) +{ + DN_OSFile result = {}; + if (!DN_Str8_HasData(path) || path.size <= 0) + return result; + + if ((access & ~DN_OSFileAccess_All) || ((access & DN_OSFileAccess_All) == 0)) { + DN_InvalidCodePath; + return result; + } + + unsigned long create_flag = 0; + switch (open_mode) { + case DN_OSFileOpen_CreateAlways: create_flag = CREATE_ALWAYS; break; + case DN_OSFileOpen_OpenIfExist: create_flag = OPEN_EXISTING; break; + case DN_OSFileOpen_OpenAlways: create_flag = OPEN_ALWAYS; break; + default: DN_InvalidCodePath; return result; + } + + unsigned long access_mode = 0; + if (access & DN_OSFileAccess_AppendOnly) { + DN_AssertF((access & ~DN_OSFileAccess_AppendOnly) == 0, + "Append can only be applied exclusively to the file, other access modes not permitted"); + access_mode = FILE_APPEND_DATA; + } else { + if (access & DN_OSFileAccess_Read) + access_mode |= GENERIC_READ; + if (access & DN_OSFileAccess_Write) + access_mode |= GENERIC_WRITE; + if (access & DN_OSFileAccess_Execute) + access_mode |= GENERIC_EXECUTE; + } + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str16 path16 = DN_W32_Str8ToStr16(tmem.arena, path); + void *handle = CreateFileW(/*LPCWSTR lpFileName*/ path16.data, + /*DWORD dwDesiredAccess*/ access_mode, + /*DWORD dwShareMode*/ FILE_SHARE_READ | FILE_SHARE_WRITE, + /*LPSECURITY_ATTRIBUTES lpSecurityAttributes*/ nullptr, + /*DWORD dwCreationDisposition*/ create_flag, + /*DWORD dwFlagsAndAttributes*/ FILE_ATTRIBUTE_NORMAL, + /*HANDLE hTemplateFile*/ nullptr); + + if (handle == INVALID_HANDLE_VALUE) { + DN_W32Error win_error = DN_W32_LastError(tmem.arena); + result.error = true; + DN_OS_ErrSinkAppendF(err, win_error.code, "Failed to open file at '%.*s': '%.*s'", DN_STR_FMT(path), DN_STR_FMT(win_error.msg)); + return result; + } + + result.handle = handle; + return result; +} + +DN_API DN_OSFileRead DN_OS_FileRead(DN_OSFile *file, void *buffer, DN_USize size, DN_OSErrSink *err) +{ + DN_OSFileRead result = {}; + if (!file || !file->handle || file->error || !buffer || size <= 0) + return result; + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + if (!DN_Check(size <= (unsigned long)-1)) { + DN_Str8 buffer_size_str8 = DN_CVT_U64ToByteSizeStr8(tmem.arena, size, DN_CVTU64ByteSizeType_Auto); + DN_OS_ErrSinkAppendF( + err, + 1 /*error_code*/, + "Current implementation doesn't support reading >4GiB file (requested %.*s), implement Win32 overlapped IO", + DN_STR_FMT(buffer_size_str8)); + return result; + } + + unsigned long bytes_read = 0; + unsigned long read_result = ReadFile(/*HANDLE hFile*/ file->handle, + /*LPVOID lpBuffer*/ buffer, + /*DWORD nNumberOfBytesToRead*/ DN_CAST(unsigned long) size, + /*LPDWORD lpNumberOfByesRead*/ &bytes_read, + /*LPOVERLAPPED lpOverlapped*/ nullptr); + if (read_result == 0) { + DN_W32Error win_error = DN_W32_LastError(tmem.arena); + DN_OS_ErrSinkAppendF(err, win_error.code, "Failed to read data from file: (%u) %.*s", win_error.code, DN_STR_FMT(win_error.msg)); + return result; + } + + if (bytes_read != size) { + DN_W32Error win_error = DN_W32_LastError(tmem.arena); + DN_OS_ErrSinkAppendF( + err, + win_error.code, + "Failed to read the desired number of bytes from file, we read %uB but we expected %uB: (%u) %.*s", + bytes_read, + DN_CAST(unsigned long) size, + win_error.code, + DN_STR_FMT(win_error.msg)); + return result; + } + + result.bytes_read = bytes_read; + result.success = true; + return result; +} + +DN_API bool DN_OS_FileWritePtr(DN_OSFile *file, void const *buffer, DN_USize size, DN_OSErrSink *err) +{ + if (!file || !file->handle || file->error || !buffer || size <= 0) + return false; + + bool result = true; + char const *end = DN_CAST(char *) buffer + size; + for (char const *ptr = DN_CAST(char const *) buffer; result && ptr != end;) { + unsigned long write_size = DN_CAST(unsigned long) DN_Min((unsigned long)-1, end - ptr); + unsigned long bytes_written = 0; + result = WriteFile(file->handle, ptr, write_size, &bytes_written, nullptr /*lpOverlapped*/) != 0; + ptr += bytes_written; + } + + if (!result) { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_W32Error win_error = DN_W32_LastError(tmem.arena); + DN_Str8 buffer_size_str8 = DN_CVT_U64ToByteSizeStr8(tmem.arena, size, DN_CVTU64ByteSizeType_Auto); + DN_OS_ErrSinkAppendF(err, win_error.code, "Failed to write buffer (%.*s) to file handle: %.*s", DN_STR_FMT(buffer_size_str8), DN_STR_FMT(win_error.msg)); + } + return result; +} + +DN_API bool DN_OS_FileFlush(DN_OSFile *file, DN_OSErrSink *err) +{ + if (!file || !file->handle || file->error) + return false; + + BOOL result = FlushFileBuffers(DN_CAST(HANDLE) file->handle); + if (!result) { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_W32Error win_error = DN_W32_LastError(tmem.arena); + DN_OS_ErrSinkAppendF(err, win_error.code, "Failed to flush file buffer to disk: %.*s", DN_STR_FMT(win_error.msg)); + } + + return DN_CAST(bool) result; +} + +DN_API void DN_OS_FileClose(DN_OSFile *file) +{ + if (!file || !file->handle || file->error) + return; + CloseHandle(file->handle); + *file = {}; +} +#endif // !defined(DN_NO_OS_FILE_API) + +// NOTE: DN_OSExec ///////////////////////////////////////////////////////////////////////////////// +DN_API void DN_OS_Exit(int32_t exit_code) +{ + ExitProcess(DN_CAST(UINT) exit_code); +} + +DN_API DN_OSExecResult DN_OS_ExecPump(DN_OSExecAsyncHandle handle, + char *stdout_buffer, + size_t *stdout_size, + char *stderr_buffer, + size_t *stderr_size, + DN_U32 timeout_ms, + DN_OSErrSink *err) +{ + DN_OSExecResult result = {}; + size_t stdout_buffer_size = 0; + size_t stderr_buffer_size = 0; + if (stdout_size) { + stdout_buffer_size = *stdout_size; + *stdout_size = 0; + } + + if (stderr_size) { + stderr_buffer_size = *stderr_size; + *stderr_size = 0; + } + + if (!handle.process || handle.os_error_code || handle.exit_code) { + if (handle.os_error_code) + result.os_error_code = handle.os_error_code; + else + result.exit_code = handle.exit_code; + + DN_Assert(!handle.stdout_read); + DN_Assert(!handle.stdout_write); + DN_Assert(!handle.stderr_read); + DN_Assert(!handle.stderr_write); + DN_Assert(!handle.process); + return result; + } + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DWORD stdout_bytes_available = 0; + DWORD stderr_bytes_available = 0; + PeekNamedPipe(handle.stdout_read, nullptr, 0, nullptr, &stdout_bytes_available, nullptr); + PeekNamedPipe(handle.stderr_read, nullptr, 0, nullptr, &stderr_bytes_available, nullptr); + + DWORD exec_result = WAIT_TIMEOUT; + if (stdout_bytes_available == 0 && stderr_bytes_available == 0) + exec_result = WaitForSingleObject(handle.process, timeout_ms); + + if (exec_result == WAIT_FAILED) { + DN_W32Error win_error = DN_W32_LastError(tmem.arena); + result.os_error_code = win_error.code; + DN_OS_ErrSinkAppendF(err, result.os_error_code, "Executed command failed to terminate: %.*s", DN_STR_FMT(win_error.msg)); + } else if (DN_Check(exec_result == WAIT_TIMEOUT || exec_result == WAIT_OBJECT_0)) { + // NOTE: Read stdout from process ////////////////////////////////////////////////////// + // If the pipes are full, the process will block. We periodically + // flush the pipes to make sure this doesn't happen + char sink[DN_Kilobytes(8)]; + stdout_bytes_available = 0; + if (PeekNamedPipe(handle.stdout_read, nullptr, 0, nullptr, &stdout_bytes_available, nullptr)) { + if (stdout_bytes_available) { + DWORD bytes_read = 0; + char *dest_buffer = handle.stdout_write && stdout_buffer ? stdout_buffer : sink; + size_t dest_size = handle.stdout_write && stdout_buffer ? stdout_buffer_size : DN_ArrayCountU(sink); + BOOL success = ReadFile(handle.stdout_read, dest_buffer, DN_CAST(DWORD) dest_size, &bytes_read, NULL); + (void)success; // TODO: + if (stdout_size) + *stdout_size = bytes_read; + } + } + + // NOTE: Read stderr from process ////////////////////////////////////////////////////// + stderr_bytes_available = 0; + if (PeekNamedPipe(handle.stderr_read, nullptr, 0, nullptr, &stderr_bytes_available, nullptr)) { + if (stderr_bytes_available) { + char *dest_buffer = handle.stderr_write && stderr_buffer ? stderr_buffer : sink; + size_t dest_size = handle.stderr_write && stderr_buffer ? stderr_buffer_size : DN_ArrayCountU(sink); + DWORD bytes_read = 0; + BOOL success = ReadFile(handle.stderr_read, dest_buffer, DN_CAST(DWORD) dest_size, &bytes_read, NULL); + (void)success; // TODO: + if (stderr_size) + *stderr_size = bytes_read; + } + } + } + + result.finished = exec_result == WAIT_OBJECT_0 || exec_result == WAIT_FAILED; + if (exec_result == WAIT_OBJECT_0) { + DWORD exit_status; + if (GetExitCodeProcess(handle.process, &exit_status)) { + result.exit_code = exit_status; + } else { + DN_W32Error win_error = DN_W32_LastError(tmem.arena); + result.os_error_code = win_error.code; + DN_OS_ErrSinkAppendF(err, + result.os_error_code, + "Failed to retrieve command exit code: %.*s", + DN_STR_FMT(win_error.msg)); + } + + // NOTE: Cleanup /////////////////////////////////////////////////////////////////////////////// + CloseHandle(handle.stdout_write); + CloseHandle(handle.stderr_write); + CloseHandle(handle.stdout_read); + CloseHandle(handle.stderr_read); + CloseHandle(handle.process); + } + + result.stdout_text = DN_Str8_Init(stdout_buffer, stdout_size ? *stdout_size : 0); + result.stderr_text = DN_Str8_Init(stderr_buffer, stderr_size ? *stderr_size : 0); + return result; +} + +DN_API DN_OSExecResult DN_OS_ExecWait(DN_OSExecAsyncHandle handle, DN_Arena *arena, DN_OSErrSink *err) +{ + DN_OSExecResult result = {}; + if (!handle.process || handle.os_error_code || handle.exit_code) { + result.finished = true; + if (handle.os_error_code) + result.os_error_code = handle.os_error_code; + else + result.exit_code = handle.exit_code; + + DN_Assert(!handle.stdout_read); + DN_Assert(!handle.stdout_write); + DN_Assert(!handle.stderr_read); + DN_Assert(!handle.stderr_write); + DN_Assert(!handle.process); + return result; + } + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + DN_Str8Builder stdout_builder = {}; + DN_Str8Builder stderr_builder = {}; + if (arena) { + stdout_builder.arena = tmem.arena; + stderr_builder.arena = tmem.arena; + } + + DN_U32 const SLOW_WAIT_TIME_MS = 100; + DN_U32 const FAST_WAIT_TIME_MS = 20; + DN_U32 wait_ms = FAST_WAIT_TIME_MS; + while (!result.finished) { + size_t stdout_size = DN_Kilobytes(8); + size_t stderr_size = DN_Kilobytes(8); + char *stdout_buffer = DN_Arena_NewArray(tmem.arena, char, stdout_size, DN_ZeroMem_No); + char *stderr_buffer = DN_Arena_NewArray(tmem.arena, char, stderr_size, DN_ZeroMem_No); + result = DN_OS_ExecPump(handle, stdout_buffer, &stdout_size, stderr_buffer, &stderr_size, wait_ms, err); + DN_Str8Builder_AppendCopy(&stdout_builder, result.stdout_text); + DN_Str8Builder_AppendCopy(&stderr_builder, result.stderr_text); + wait_ms = (DN_Str8_HasData(result.stdout_text) || DN_Str8_HasData(result.stderr_text)) ? FAST_WAIT_TIME_MS : SLOW_WAIT_TIME_MS; + } + + // NOTE: Get stdout/stderr. If no arena is passed this is a no-op ////////////////////////////// + result.stdout_text = DN_Str8Builder_Build(&stdout_builder, arena); + result.stderr_text = DN_Str8Builder_Build(&stderr_builder, arena); + return result; +} + +DN_API DN_OSExecAsyncHandle DN_OS_ExecAsync(DN_Slice cmd_line, DN_OSExecArgs *args, DN_OSErrSink *err) +{ + // NOTE: Pre-amble ///////////////////////////////////////////////////////////////////////////// + DN_OSExecAsyncHandle result = {}; + if (cmd_line.size == 0) + return result; + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 cmd_rendered = DN_Slice_Str8Render(tmem.arena, cmd_line, DN_STR8(" ")); + DN_Str16 cmd16 = DN_W32_Str8ToStr16(tmem.arena, cmd_rendered); + DN_Str16 working_dir16 = DN_W32_Str8ToStr16(tmem.arena, args->working_dir); + + DN_Str8Builder env_builder = DN_Str8Builder_InitFromTLS(); + DN_Str8Builder_AppendArrayRef(&env_builder, args->environment.data, args->environment.size); + if (env_builder.string_size) + DN_Str8Builder_AppendRef(&env_builder, DN_STR8("\0")); + + DN_Str8 env_block8 = DN_Str8Builder_BuildDelimitedFromTLS(&env_builder, DN_STR8("\0")); + DN_Str16 env_block16 = {}; + if (env_block8.size) + env_block16 = DN_W32_Str8ToStr16(tmem.arena, env_block8); + + // NOTE: Stdout/err security attributes //////////////////////////////////////////////////////// + SECURITY_ATTRIBUTES save_std_security_attribs = {}; + save_std_security_attribs.nLength = sizeof(save_std_security_attribs); + save_std_security_attribs.bInheritHandle = true; + + // NOTE: Redirect stdout /////////////////////////////////////////////////////////////////////// + HANDLE stdout_read = {}; + HANDLE stdout_write = {}; + DN_DEFER + { + if (result.os_error_code || result.exit_code) { + CloseHandle(stdout_read); + CloseHandle(stdout_write); + } + }; + + if (DN_Bit_IsSet(args->flags, DN_OSExecFlags_SaveStdout)) { + if (!CreatePipe(&stdout_read, &stdout_write, &save_std_security_attribs, /*nSize*/ 0)) { + DN_W32Error win_error = DN_W32_LastError(tmem.arena); + result.os_error_code = win_error.code; + DN_OS_ErrSinkAppendF( + err, + result.os_error_code, + "Failed to create stdout pipe to redirect the output of the command '%.*s': %.*s", + DN_STR_FMT(cmd_rendered), + DN_STR_FMT(win_error.msg)); + return result; + } + + if (!SetHandleInformation(stdout_read, HANDLE_FLAG_INHERIT, 0)) { + DN_W32Error win_error = DN_W32_LastError(tmem.arena); + result.os_error_code = win_error.code; + DN_OS_ErrSinkAppendF(err, + result.os_error_code, + "Failed to make stdout 'read' pipe non-inheritable when trying to " + "execute command '%.*s': %.*s", + DN_STR_FMT(cmd_rendered), + DN_STR_FMT(win_error.msg)); + return result; + } + } + + // NOTE: Redirect stderr /////////////////////////////////////////////////////////////////////// + HANDLE stderr_read = {}; + HANDLE stderr_write = {}; + DN_DEFER + { + if (result.os_error_code || result.exit_code) { + CloseHandle(stderr_read); + CloseHandle(stderr_write); + } + }; + + if (DN_Bit_IsSet(args->flags, DN_OSExecFlags_SaveStderr)) { + if (DN_Bit_IsSet(args->flags, DN_OSExecFlags_MergeStderrToStdout)) { + stderr_read = stdout_read; + stderr_write = stdout_write; + } else { + if (!CreatePipe(&stderr_read, &stderr_write, &save_std_security_attribs, /*nSize*/ 0)) { + DN_W32Error win_error = DN_W32_LastError(tmem.arena); + result.os_error_code = win_error.code; + DN_OS_ErrSinkAppendF( + err, + result.os_error_code, + "Failed to create stderr pipe to redirect the output of the command '%.*s': %.*s", + DN_STR_FMT(cmd_rendered), + DN_STR_FMT(win_error.msg)); + return result; + } + + if (!SetHandleInformation(stderr_read, HANDLE_FLAG_INHERIT, 0)) { + DN_W32Error win_error = DN_W32_LastError(tmem.arena); + result.os_error_code = win_error.code; + DN_OS_ErrSinkAppendF(err, + result.os_error_code, + "Failed to make stderr 'read' pipe non-inheritable when trying to " + "execute command '%.*s': %.*s", + DN_STR_FMT(cmd_rendered), + DN_STR_FMT(win_error.msg)); + return result; + } + } + } + + // NOTE: Execute command /////////////////////////////////////////////////////////////////////// + PROCESS_INFORMATION proc_info = {}; + STARTUPINFOW startup_info = {}; + startup_info.cb = sizeof(STARTUPINFOW); + startup_info.hStdError = stderr_write ? stderr_write : GetStdHandle(STD_ERROR_HANDLE); + startup_info.hStdOutput = stdout_write ? stdout_write : GetStdHandle(STD_OUTPUT_HANDLE); + startup_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + startup_info.dwFlags |= STARTF_USESTDHANDLES; + BOOL create_result = CreateProcessW(nullptr, + cmd16.data, + nullptr, + nullptr, + true, + CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT, + env_block16.data, + working_dir16.data, + &startup_info, + &proc_info); + if (!create_result) { + DN_W32Error win_error = DN_W32_LastError(tmem.arena); + result.os_error_code = win_error.code; + DN_OS_ErrSinkAppendF(err, + result.os_error_code, + "Failed to execute command '%.*s': %.*s", + DN_STR_FMT(cmd_rendered), + DN_STR_FMT(win_error.msg)); + return result; + } + + // NOTE: Post-amble //////////////////////////////////////////////////////////////////////////// + CloseHandle(proc_info.hThread); + result.process = proc_info.hProcess; + result.stdout_read = stdout_read; + result.stdout_write = stdout_write; + if (DN_Bit_IsSet(args->flags, DN_OSExecFlags_SaveStderr) && DN_Bit_IsNotSet(args->flags, DN_OSExecFlags_MergeStderrToStdout)) { + result.stderr_read = stderr_read; + result.stderr_write = stderr_write; + } + result.exec_flags = args->flags; + return result; +} + +static DN_W32Core *DN_OS_GetW32Core_() +{ + DN_Assert(g_dn_os_core_ && g_dn_os_core_->platform_context); + DN_W32Core *result = DN_CAST(DN_W32Core *)g_dn_os_core_->platform_context; + return result; +} + +static DN_W32SyncPrimitive *DN_OS_U64ToW32SyncPrimitive_(DN_U64 u64) +{ + DN_W32SyncPrimitive *result = nullptr; + DN_Memcpy(&result, &u64, sizeof(u64)); + return result; +} + +static DN_U64 DN_W32_SyncPrimitiveToU64(DN_W32SyncPrimitive *primitive) +{ + DN_U64 result = 0; + static_assert(sizeof(result) == sizeof(primitive), "Pointer size mis-match"); + DN_Memcpy(&result, &primitive, sizeof(result)); + return result; +} + +static DN_W32SyncPrimitive *DN_W32_AllocSyncPrimitive_() +{ + DN_W32Core *w32 = DN_OS_GetW32Core_(); + DN_W32SyncPrimitive *result = nullptr; + EnterCriticalSection(&w32->sync_primitive_free_list_mutex); + { + if (w32->sync_primitive_free_list) { + result = w32->sync_primitive_free_list; + w32->sync_primitive_free_list = w32->sync_primitive_free_list->next; + result->next = nullptr; + } else { + DN_OSCore *os = g_dn_os_core_; + result = DN_Arena_New(&os->arena, DN_W32SyncPrimitive, DN_ZeroMem_Yes); + } + } + LeaveCriticalSection(&w32->sync_primitive_free_list_mutex); + return result; +} + +static void DN_W32_DeallocSyncPrimitive_(DN_W32SyncPrimitive *primitive) +{ + if (primitive) { + DN_W32Core *w32 = DN_OS_GetW32Core_(); + EnterCriticalSection(&w32->sync_primitive_free_list_mutex); + primitive->next = w32->sync_primitive_free_list; + w32->sync_primitive_free_list = primitive; + LeaveCriticalSection(&w32->sync_primitive_free_list_mutex); + } +} + +// NOTE: DN_OSSemaphore //////////////////////////////////////////////////////////////////////////// +DN_API DN_OSSemaphore DN_OS_SemaphoreInit(DN_U32 initial_count) +{ + DN_OSSemaphore result = {}; + DN_W32SyncPrimitive *primitive = DN_W32_AllocSyncPrimitive_(); + if (primitive) { + SECURITY_ATTRIBUTES security_attribs = {}; + primitive->sem = CreateSemaphoreA(&security_attribs, initial_count, INT32_MAX, nullptr /*name*/); + if (primitive->sem) + result.handle = DN_W32_SyncPrimitiveToU64(primitive); + if (!primitive->sem) + DN_W32_DeallocSyncPrimitive_(primitive); + } + return result; +} + +DN_API void DN_OS_SemaphoreDeinit(DN_OSSemaphore *semaphore) +{ + if (semaphore && semaphore->handle != 0) { + DN_W32SyncPrimitive *primitive = DN_OS_U64ToW32SyncPrimitive_(semaphore->handle); + CloseHandle(primitive->sem); + DN_W32_DeallocSyncPrimitive_(primitive); + *semaphore = {}; + } +} + +DN_API void DN_OS_SemaphoreIncrement(DN_OSSemaphore *semaphore, DN_U32 amount) +{ + if (semaphore && semaphore->handle != 0) { + DN_W32SyncPrimitive *primitive = DN_OS_U64ToW32SyncPrimitive_(semaphore->handle); + LONG prev_count = 0; + ReleaseSemaphore(primitive->sem, amount, &prev_count); + } +} + +DN_API DN_OSSemaphoreWaitResult DN_OS_SemaphoreWait(DN_OSSemaphore *semaphore, DN_U32 timeout_ms) +{ + DN_OSSemaphoreWaitResult result = {}; + if (semaphore && semaphore->handle != 0) { + DN_W32SyncPrimitive *primitive = DN_OS_U64ToW32SyncPrimitive_(semaphore->handle); + DWORD wait_result = WaitForSingleObject(primitive->sem, timeout_ms == DN_OS_SEMAPHORE_INFINITE_TIMEOUT ? INFINITE : timeout_ms); + if (wait_result == WAIT_TIMEOUT) + result = DN_OSSemaphoreWaitResult_Timeout; + else if (wait_result == WAIT_OBJECT_0) + result = DN_OSSemaphoreWaitResult_Success; + } + return result; +} + +// NOTE: DN_OSMutex //////////////////////////////////////////////////////////////////////////////// +DN_API DN_OSMutex DN_OS_MutexInit() +{ + DN_W32SyncPrimitive *primitive = DN_W32_AllocSyncPrimitive_(); + if (primitive) + InitializeCriticalSection(&primitive->mutex); + DN_OSMutex result = {}; + result.handle = DN_W32_SyncPrimitiveToU64(primitive); + return result; +} + +DN_API void DN_OS_MutexDeinit(DN_OSMutex *mutex) +{ + if (mutex && mutex->handle != 0) { + DN_W32SyncPrimitive *primitive = DN_OS_U64ToW32SyncPrimitive_(mutex->handle); + DeleteCriticalSection(&primitive->mutex); + DN_W32_DeallocSyncPrimitive_(primitive); + *mutex = {}; + } +} + +DN_API void DN_OS_MutexLock(DN_OSMutex *mutex) +{ + if (mutex && mutex->handle != 0) { + DN_W32SyncPrimitive *primitive = DN_OS_U64ToW32SyncPrimitive_(mutex->handle); + EnterCriticalSection(&primitive->mutex); + } +} + +DN_API void DN_OS_MutexUnlock(DN_OSMutex *mutex) +{ + if (mutex && mutex->handle != 0) { + DN_W32SyncPrimitive *primitive = DN_OS_U64ToW32SyncPrimitive_(mutex->handle); + LeaveCriticalSection(&primitive->mutex); + } +} + +// NOTE: DN_OSConditionVariable //////////////////////////////////////////////////////////////////// +DN_API DN_OSConditionVariable DN_OS_ConditionVariableInit() +{ + DN_W32SyncPrimitive *primitive = DN_W32_AllocSyncPrimitive_(); + if (primitive) + InitializeConditionVariable(&primitive->cv); + DN_OSConditionVariable result = {}; + result.handle = DN_W32_SyncPrimitiveToU64(primitive); + return result; +} + +DN_API void DN_OS_ConditionVariableDeinit(DN_OSConditionVariable *cv) +{ + if (cv && cv->handle != 0) { + DN_W32SyncPrimitive *primitive = DN_OS_U64ToW32SyncPrimitive_(cv->handle); + DN_W32_DeallocSyncPrimitive_(primitive); + *cv = {}; + } +} + +DN_API bool DN_OS_ConditionVariableWaitUntil(DN_OSConditionVariable *cv, DN_OSMutex *mutex, DN_U64 end_ts_ms) +{ + bool result = false; + DN_U64 now_ms = DN_OS_DateUnixTimeNs() / (1000 * 1000); + if (now_ms < end_ts_ms) { + DN_U64 sleep_ms = end_ts_ms - now_ms; + result = DN_OS_ConditionVariableWait(cv, mutex, sleep_ms); + } + return result; +} + +DN_API bool DN_OS_ConditionVariableWait(DN_OSConditionVariable *cv, DN_OSMutex *mutex, DN_U64 sleep_ms) +{ + bool result = false; + if (mutex && cv && mutex->handle != 0 && cv->handle != 0 && sleep_ms > 0) { + DN_W32SyncPrimitive *mutex_primitive = DN_OS_U64ToW32SyncPrimitive_(mutex->handle); + DN_W32SyncPrimitive *cv_primitive = DN_OS_U64ToW32SyncPrimitive_(cv->handle); + result = SleepConditionVariableCS(&cv_primitive->cv, &mutex_primitive->mutex, DN_CAST(DWORD) sleep_ms); + } + return result; +} + +DN_API void DN_OS_ConditionVariableSignal(DN_OSConditionVariable *cv) +{ + if (cv && cv->handle != 0) { + DN_W32SyncPrimitive *primitive = DN_OS_U64ToW32SyncPrimitive_(cv->handle); + WakeConditionVariable(&primitive->cv); + } +} + +DN_API void DN_OS_ConditionVariableBroadcast(DN_OSConditionVariable *cv) +{ + if (cv && cv->handle != 0) { + DN_W32SyncPrimitive *primitive = DN_OS_U64ToW32SyncPrimitive_(cv->handle); + WakeAllConditionVariable(&primitive->cv); + } +} + +// NOTE: DN_OSThread /////////////////////////////////////////////////////////////////////////////// +static DWORD __stdcall DN_OS_ThreadFunc_(void *user_context) +{ + DN_OS_ThreadExecute_(user_context); + return 0; +} + +DN_API bool DN_OS_ThreadInit(DN_OSThread *thread, DN_OSThreadFunc *func, void *user_context) +{ + bool result = false; + if (!thread) + return result; + + thread->func = func; + thread->user_context = user_context; + thread->init_semaphore = DN_OS_SemaphoreInit(0 /*initial_count*/); + + // TODO(doyle): Check if semaphore is valid + DWORD thread_id = 0; + SECURITY_ATTRIBUTES security_attribs = {}; + thread->handle = CreateThread(&security_attribs, + 0 /*stack_size*/, + DN_OS_ThreadFunc_, + thread, + 0 /*creation_flags*/, + &thread_id); + + result = thread->handle != INVALID_HANDLE_VALUE; + if (result) + thread->thread_id = thread_id; + + // NOTE: Ensure that thread_id is set before 'thread->func' is called. + if (result) { + DN_OS_SemaphoreIncrement(&thread->init_semaphore, 1); + } else { + DN_OS_SemaphoreDeinit(&thread->init_semaphore); + *thread = {}; + } + + return result; +} + +DN_API void DN_OS_ThreadDeinit(DN_OSThread *thread) +{ + if (!thread || !thread->handle) + return; + + WaitForSingleObject(thread->handle, INFINITE); + CloseHandle(thread->handle); + thread->handle = INVALID_HANDLE_VALUE; + thread->thread_id = {}; + DN_OS_TLSDeinit(&thread->tls); +} + +DN_API DN_U32 DN_OS_ThreadID() +{ + unsigned long result = GetCurrentThreadId(); + return result; +} + +DN_API void DN_W32_ThreadSetName(DN_Str8 name) +{ + DN_OSTLS *tls = DN_OS_TLSGet(); + DN_ArenaTempMem tmem = DN_Arena_TempMemBegin(tls->arenas + DN_OSTLSArena_TMem0); + + // NOTE: SetThreadDescription is only available in + // Windows Server 2016, Windows 10 LTSB 2016 and Windows 10 version 1607 + // + // See: https://learn.microsoft.com/en-us/windows/w32/api/processthreadsapi/nf-processthreadsapi-setthreaddescription + DN_W32Core *w32 = DN_OS_GetW32Core_(); + if (w32->set_thread_description) { + DN_Str16 name16 = DN_W32_Str8ToStr16(tmem.arena, name); + w32->set_thread_description(GetCurrentThread(), (WCHAR *)name16.data); + DN_Arena_TempMemEnd(tmem); + return; + } + + // NOTE: Fallback to throw-exception method to set thread name + #pragma pack(push, 8) + + struct DN_W32ThreadNameInfo + { + DN_U32 dwType; + char *szName; + DN_U32 dwThreadID; + DN_U32 dwFlags; + }; + + #pragma pack(pop) + + DN_Str8 copy = DN_Str8_Copy(tmem.arena, name); + DN_W32ThreadNameInfo info = {}; + info.dwType = 0x1000; + info.szName = (char *)copy.data; + info.dwThreadID = DN_OS_ThreadID(); + + // TODO: Review warning 6320 + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(6320) // Exception-filter expression is the constant EXCEPTION_EXECUTE_HANDLER. This might mask exceptions that were not intended to be handled + DN_MSVC_WARNING_DISABLE(6322) // Empty _except block + __try { + RaiseException(0x406D1388, 0, sizeof(info) / sizeof(void *), (const ULONG_PTR *)&info); + } __except (EXCEPTION_EXECUTE_HANDLER) { + } + DN_MSVC_WARNING_POP + + DN_Arena_TempMemEnd(tmem); +} + +// NOTE: DN_OSHttp ///////////////////////////////////////////////////////////////////////////////// +void DN_OS_HttpRequestWin32Callback(HINTERNET session, DWORD *dwContext, DWORD dwInternetStatus, VOID *lpvStatusInformation, DWORD dwStatusInformationLength) +{ + (void)session; + (void)dwStatusInformationLength; + + DN_OSHttpResponse *response = DN_CAST(DN_OSHttpResponse *) dwContext; + HINTERNET request = DN_CAST(HINTERNET) response->w32_request_handle; + DN_W32Error error = {}; + DWORD const READ_BUFFER_SIZE = DN_Megabytes(1); + + if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_RESOLVING_NAME) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_NAME_RESOLVED) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_SENDING_REQUEST) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_REQUEST_SENT) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_HANDLE_CREATED) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_DETECTING_PROXY) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_REDIRECT) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_INTERMEDIATE_RESPONSE) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_SECURE_FAILURE) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE) { + DWORD status = 0; + DWORD status_size = sizeof(status_size); + if (WinHttpQueryHeaders(request, + WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, + WINHTTP_HEADER_NAME_BY_INDEX, + &status, + &status_size, + WINHTTP_NO_HEADER_INDEX)) { + response->http_status = DN_CAST(uint16_t) status; + + // NOTE: You can normally call into WinHttpQueryDataAvailable which means the kernel + // will buffer the response into a single buffer and return us the full size of the + // request. + // + // or + // + // You may call WinHttpReadData directly to write the memory into our buffer directly. + // This is advantageous to avoid a copy from the kernel buffer into our buffer. If the + // end user application knows the typical payload size then they can optimise for this + // to prevent unnecessary allocation on the user side. + void *buffer = DN_Arena_Alloc(response->builder.arena, READ_BUFFER_SIZE, 1 /*align*/, DN_ZeroMem_No); + if (!WinHttpReadData(request, buffer, READ_BUFFER_SIZE, nullptr)) + error = DN_W32_LastError(&response->tmp_arena); + } else { + error = DN_W32_LastError(&response->tmp_arena); + } + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_READ_COMPLETE) { + DWORD bytes_read = dwStatusInformationLength; + if (bytes_read) { + DN_Str8 prev_buffer = DN_Str8_Init(DN_CAST(char *) lpvStatusInformation, bytes_read); + DN_Str8Builder_AppendRef(&response->builder, prev_buffer); + + void *buffer = DN_Arena_Alloc(response->builder.arena, READ_BUFFER_SIZE, 1 /*align*/, DN_ZeroMem_No); + if (!WinHttpReadData(request, buffer, READ_BUFFER_SIZE, nullptr)) + error = DN_W32_LastError(&response->tmp_arena); + } + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_REQUEST_ERROR) { + WINHTTP_ASYNC_RESULT *async_result = DN_CAST(WINHTTP_ASYNC_RESULT *) lpvStatusInformation; + error = DN_W32_ErrorCodeToMsg(&response->tmp_arena, DN_CAST(DN_U32) async_result->dwError); + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE) { + if (!WinHttpReceiveResponse(request, 0)) + error = DN_W32_LastError(&response->tmp_arena); + } + + // NOTE: If the request handle is missing, then, the response has been freed. + // MSDN says that this callback can still be called after closing the handle + // and trigger the WINHTTP_CALLBACK_STATUS_REQUEST_ERROR. + if (request) { + bool read_complete = dwInternetStatus == WINHTTP_CALLBACK_STATUS_READ_COMPLETE && dwStatusInformationLength == 0; + if (read_complete) + response->body = DN_Str8Builder_Build(&response->builder, response->arena); + + if (read_complete || dwInternetStatus == WINHTTP_CALLBACK_STATUS_REQUEST_ERROR || error.code) { + DN_OS_SemaphoreIncrement(&response->on_complete_semaphore, 1); + DN_Atomic_AddU32(&response->done, 1); + } + + if (error.code) { + response->error_code = error.code; + response->error_msg = error.msg; + } + } +} + +DN_API void DN_OS_HttpRequestAsync(DN_OSHttpResponse *response, + DN_Arena *arena, + DN_Str8 host, + DN_Str8 path, + DN_OSHttpRequestSecure secure, + DN_Str8 method, + DN_Str8 body, + DN_Str8 headers) +{ + if (!response || !arena) + return; + + response->arena = arena; + response->builder.arena = response->tmem_arena ? response->tmem_arena : &response->tmp_arena; + + DN_Arena *tmem_arena = response->tmem_arena; + DN_OSTLSTMem tmem_ = DN_OS_TLSTMem(arena); + if (!tmem_arena) + tmem_arena = tmem_.arena; + + DN_W32Error error = {}; + DN_DEFER + { + response->error_msg = error.msg; + response->error_code = error.code; + if (error.code) { + // NOTE: 'Wait' handles failures gracefully, skipping the wait and + // cleans up the request + DN_OS_HttpRequestWait(response); + DN_Atomic_AddU32(&response->done, 1); + } + }; + + response->w32_request_session = WinHttpOpen(nullptr /*user agent*/, WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, WINHTTP_FLAG_ASYNC); + if (!response->w32_request_session) { + error = DN_W32_LastError(&response->tmp_arena); + return; + } + + DWORD callback_flags = WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE | + WINHTTP_CALLBACK_STATUS_READ_COMPLETE | + WINHTTP_CALLBACK_STATUS_REQUEST_ERROR | + WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE; + if (WinHttpSetStatusCallback(response->w32_request_session, + DN_CAST(WINHTTP_STATUS_CALLBACK) DN_OS_HttpRequestWin32Callback, + callback_flags, + DN_CAST(DWORD_PTR) nullptr /*dwReserved*/) == WINHTTP_INVALID_STATUS_CALLBACK) { + error = DN_W32_LastError(&response->tmp_arena); + return; + } + + DN_Str16 host16 = DN_W32_Str8ToStr16(tmem_arena, host); + response->w32_request_connection = WinHttpConnect(response->w32_request_session, host16.data, secure ? INTERNET_DEFAULT_HTTPS_PORT : INTERNET_DEFAULT_HTTP_PORT, 0 /*reserved*/); + if (!response->w32_request_connection) { + error = DN_W32_LastError(&response->tmp_arena); + return; + } + + DN_Str16 method16 = DN_W32_Str8ToStr16(tmem_arena, method); + DN_Str16 path16 = DN_W32_Str8ToStr16(tmem_arena, path); + response->w32_request_handle = WinHttpOpenRequest(response->w32_request_connection, + method16.data, + path16.data, + nullptr /*version*/, + nullptr /*referrer*/, + nullptr /*accept types*/, + secure ? WINHTTP_FLAG_SECURE : 0); + if (!response->w32_request_handle) { + error = DN_W32_LastError(&response->tmp_arena); + return; + } + + DN_Str16 headers16 = DN_W32_Str8ToStr16(tmem_arena, headers); + response->on_complete_semaphore = DN_OS_SemaphoreInit(0); + if (!WinHttpSendRequest(response->w32_request_handle, + headers16.data, + DN_CAST(DWORD) headers16.size, + body.data /*optional data*/, + DN_CAST(DWORD) body.size /*optional length*/, + DN_CAST(DWORD) body.size /*total content length*/, + DN_CAST(DWORD_PTR) response)) { + error = DN_W32_LastError(&response->tmp_arena); + return; + } +} + +DN_API void DN_OS_HttpRequestFree(DN_OSHttpResponse *response) +{ + // NOTE: Cleanup + // NOTE: These calls are synchronous even when the HTTP request is async. + WinHttpCloseHandle(response->w32_request_handle); + WinHttpCloseHandle(response->w32_request_connection); + WinHttpCloseHandle(response->w32_request_session); + + response->w32_request_session = nullptr; + response->w32_request_connection = nullptr; + response->w32_request_handle = nullptr; + DN_Arena_Deinit(&response->tmp_arena); + DN_OS_SemaphoreDeinit(&response->on_complete_semaphore); + + *response = {}; +} + +// NOTE: DN_W32 //////////////////////////////////////////////////////////////////////////////////// +DN_API DN_Str16 DN_W32_ErrorCodeToMsg16Alloc(DN_U32 error_code) +{ + DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS; + void *module_to_get_errors_from = nullptr; + if (error_code >= 12000 && error_code <= 12175) { + flags |= FORMAT_MESSAGE_FROM_HMODULE; + module_to_get_errors_from = GetModuleHandleA("winhttp.dll"); + } + + wchar_t *result16 = nullptr; + DWORD size = FormatMessageW(/*DWORD dwFlags */ flags | FORMAT_MESSAGE_ALLOCATE_BUFFER, + /*LPCVOID lpSource */ module_to_get_errors_from, + /*DWORD dwMessageId */ error_code, + /*DWORD dwLanguageId*/ 0, + /*LPWSTR lpBuffer */ (LPWSTR)&result16, + /*DWORD nSize */ 0, + /*va_list *Arguments */ nullptr); + + DN_Str16 result = {result16, size}; + return result; +} + +DN_API DN_W32Error DN_W32_ErrorCodeToMsgAlloc(DN_U32 error_code) +{ + DN_W32Error result = {}; + result.code = error_code; + DN_Str16 error16 = DN_W32_ErrorCodeToMsg16Alloc(error_code); + if (error16.size) + result.msg = DN_W32_Str16ToStr8FromHeap(error16); + if (error16.data) + LocalFree(error16.data); + return result; +} + +DN_API DN_W32Error DN_W32_ErrorCodeToMsg(DN_Arena *arena, DN_U32 error_code) +{ + DN_W32Error result = {}; + result.code = error_code; + if (arena) { + DN_Str16 error16 = DN_W32_ErrorCodeToMsg16Alloc(error_code); + if (error16.size) + result.msg = DN_W32_Str16ToStr8(arena, error16); + if (error16.data) + LocalFree(error16.data); + } + return result; +} + +DN_API DN_W32Error DN_W32_LastError(DN_Arena *arena) +{ + DN_W32Error result = DN_W32_ErrorCodeToMsg(arena, GetLastError()); + return result; +} + +DN_API DN_W32Error DN_W32_LastErrorAlloc() +{ + DN_W32Error result = DN_W32_ErrorCodeToMsgAlloc(GetLastError()); + return result; +} + +DN_API void DN_W32_MakeProcessDPIAware() +{ + typedef bool SetProcessDpiAwareProc(void); + typedef bool SetProcessDpiAwarenessProc(DPI_AWARENESS); + typedef bool SetProcessDpiAwarenessContextProc(void * /*DPI_AWARENESS_CONTEXT*/); + + // NOTE(doyle): Taken from cmuratori/refterm snippet on DPI awareness. It + // appears we can make this robust by just loading user32.dll and using + // GetProcAddress on the DPI function. If it's not there, we're on an old + // version of windows, so we can call an older version of the API. + void *lib_handle = LoadLibraryA("user32.dll"); + if (!lib_handle) + return; + + if (auto *set_process_dpi_awareness_context = DN_CAST(SetProcessDpiAwarenessContextProc *) GetProcAddress(DN_CAST(HMODULE) lib_handle, "SetProcessDpiAwarenessContext")) + set_process_dpi_awareness_context(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + else if (auto *set_process_dpi_awareness = DN_CAST(SetProcessDpiAwarenessProc *) GetProcAddress(DN_CAST(HMODULE) lib_handle, "SetProcessDpiAwareness")) + set_process_dpi_awareness(DPI_AWARENESS_PER_MONITOR_AWARE); + else if (auto *set_process_dpi_aware = DN_CAST(SetProcessDpiAwareProc *) GetProcAddress(DN_CAST(HMODULE) lib_handle, "SetProcessDpiAware")) + set_process_dpi_aware(); +} + +// NOTE: Windows UTF8 to Str16 ////////////////////////////////////////////// +DN_API DN_Str16 DN_W32_Str8ToStr16(DN_Arena *arena, DN_Str8 src) +{ + DN_Str16 result = {}; + if (!arena || !DN_Str8_HasData(src)) + return result; + + int required_size = MultiByteToWideChar(CP_UTF8, 0 /*dwFlags*/, src.data, DN_CAST(int) src.size, nullptr /*dest*/, 0 /*dest size*/); + if (required_size <= 0) + return result; + + wchar_t *buffer = DN_Arena_NewArray(arena, wchar_t, required_size + 1, DN_ZeroMem_No); + if (!buffer) + return result; + + int chars_written = MultiByteToWideChar(CP_UTF8, 0 /*dwFlags*/, src.data, DN_CAST(int) src.size, buffer, required_size); + if (DN_Check(chars_written == required_size)) { + result.data = buffer; + result.size = chars_written; + result.data[result.size] = 0; + } + return result; +} + +DN_API int DN_W32_Str8ToStr16Buffer(DN_Str8 src, wchar_t *dest, int dest_size) +{ + int result = 0; + if (!DN_Str8_HasData(src)) + return result; + + result = MultiByteToWideChar(CP_UTF8, 0 /*dwFlags*/, src.data, DN_CAST(int) src.size, nullptr /*dest*/, 0 /*dest size*/); + if (result <= 0 || result > dest_size || !dest) + return result; + + result = MultiByteToWideChar(CP_UTF8, 0 /*dwFlags*/, src.data, DN_CAST(int) src.size, dest, DN_CAST(int) dest_size); + dest[DN_Min(result, dest_size - 1)] = 0; + return result; +} + +// NOTE: Windows Str16 To UTF8 ////////////////////////////////////////////////////////////////// +DN_API int DN_W32_Str16ToStr8Buffer(DN_Str16 src, char *dest, int dest_size) +{ + int result = 0; + if (!DN_Str16_HasData(src)) + return result; + + int src_size = DN_SaturateCastISizeToInt(src.size); + if (src_size <= 0) + return result; + + result = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.data, src_size, nullptr /*dest*/, 0 /*dest size*/, nullptr, nullptr); + if (result <= 0 || result > dest_size || !dest) + return result; + + result = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.data, src_size, dest, DN_CAST(int) dest_size, nullptr, nullptr); + dest[DN_Min(result, dest_size - 1)] = 0; + return result; +} + +DN_API DN_Str8 DN_W32_Str16ToStr8(DN_Arena *arena, DN_Str16 src) +{ + DN_Str8 result = {}; + if (!arena || !DN_Str16_HasData(src)) + return result; + + int src_size = DN_SaturateCastISizeToInt(src.size); + if (src_size <= 0) + return result; + + int required_size = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.data, src_size, nullptr /*dest*/, 0 /*dest size*/, nullptr, nullptr); + if (required_size <= 0) + return result; + + // NOTE: Str8 allocate ensures there's one extra byte for + // null-termination already so no-need to +1 the required size + DN_ArenaTempMemScope temp_mem = DN_ArenaTempMemScope(arena); + DN_Str8 buffer = DN_Str8_Alloc(arena, required_size, DN_ZeroMem_No); + if (!DN_Str8_HasData(buffer)) + return result; + + int chars_written = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.data, src_size, buffer.data, DN_CAST(int) buffer.size, nullptr, nullptr); + if (DN_Check(chars_written == required_size)) { + result = buffer; + result.data[result.size] = 0; + temp_mem.mem = {}; + } + + return result; +} + +DN_API DN_Str8 DN_W32_Str16ToStr8FromHeap(DN_Str16 src) +{ + DN_Str8 result = {}; + if (!DN_Str16_HasData(src)) + return result; + + int src_size = DN_SaturateCastISizeToInt(src.size); + if (src_size <= 0) + return result; + + int required_size = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.data, src_size, nullptr /*dest*/, 0 /*dest size*/, nullptr, nullptr); + if (required_size <= 0) + return result; + + // NOTE: Str8 allocate ensures there's one extra byte for + // null-termination already so no-need to +1 the required size + DN_Str8 buffer = DN_Str8_AllocFromOSHeap(required_size, DN_ZeroMem_No); + if (!DN_Str8_HasData(buffer)) + return result; + + int chars_written = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.data, src_size, buffer.data, DN_CAST(int) buffer.size, nullptr, nullptr); + if (DN_Check(chars_written == required_size)) { + result = buffer; + result.data[result.size] = 0; + } else { + DN_OS_MemDealloc(buffer.data); + buffer = {}; + } + + return result; +} + +// NOTE: Windows Executable Directory ////////////////////////////////////////// +DN_API DN_Str16 DN_W32_EXEPathW(DN_Arena *arena) +{ + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + DN_Str16 result = {}; + DN_USize module_size = 0; + wchar_t *module_path = nullptr; + do { + module_size += 256; + module_path = DN_Arena_NewArray(tmem.arena, wchar_t, module_size, DN_ZeroMem_No); + if (!module_path) + return result; + module_size = DN_CAST(DN_USize) GetModuleFileNameW(nullptr /*module*/, module_path, DN_CAST(int) module_size); + } while (GetLastError() == ERROR_INSUFFICIENT_BUFFER); + + DN_USize index_of_last_slash = 0; + for (DN_USize index = module_size - 1; !index_of_last_slash && index < module_size; index--) + index_of_last_slash = module_path[index] == '\\' ? index : 0; + + result.data = DN_Arena_NewArray(arena, wchar_t, module_size + 1, DN_ZeroMem_No); + result.size = module_size; + DN_Memcpy(result.data, module_path, sizeof(wchar_t) * result.size); + result.data[result.size] = 0; + return result; +} + +DN_API DN_Str16 DN_W32_EXEDirW(DN_Arena *arena) +{ + // TODO(doyle): Implement a DN_Str16_BinarySearchReverse + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + DN_Str16 result = {}; + DN_USize module_size = 0; + wchar_t *module_path = nullptr; + do { + module_size += 256; + module_path = DN_Arena_NewArray(tmem.arena, wchar_t, module_size, DN_ZeroMem_No); + if (!module_path) + return result; + module_size = DN_CAST(DN_USize) GetModuleFileNameW(nullptr /*module*/, module_path, DN_CAST(int) module_size); + } while (GetLastError() == ERROR_INSUFFICIENT_BUFFER); + + DN_USize index_of_last_slash = 0; + for (DN_USize index = module_size - 1; !index_of_last_slash && index < module_size; index--) + index_of_last_slash = module_path[index] == '\\' ? index : 0; + + result.data = DN_Arena_NewArray(arena, wchar_t, index_of_last_slash + 1, DN_ZeroMem_No); + result.size = index_of_last_slash; + DN_Memcpy(result.data, module_path, sizeof(wchar_t) * result.size); + result.data[result.size] = 0; + return result; +} + +DN_API DN_Str8 DN_W32_WorkingDir(DN_Arena *arena, DN_Str8 suffix) +{ + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + DN_Str16 suffix16 = DN_W32_Str8ToStr16(tmem.arena, suffix); + DN_Str16 dir16 = DN_W32_WorkingDirW(tmem.arena, suffix16); + DN_Str8 result = DN_W32_Str16ToStr8(arena, dir16); + return result; +} + +DN_API DN_Str16 DN_W32_WorkingDirW(DN_Arena *arena, DN_Str16 suffix) +{ + DN_Assert(suffix.size >= 0); + DN_Str16 result = {}; + + // NOTE: required_size is the size required *including* the null-terminator + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + unsigned long required_size = GetCurrentDirectoryW(0, nullptr); + unsigned long desired_size = required_size + DN_CAST(unsigned long) suffix.size; + + wchar_t *tmem_w_path = DN_Arena_NewArray(tmem.arena, wchar_t, desired_size, DN_ZeroMem_No); + if (!tmem_w_path) + return result; + + unsigned long bytes_written_wo_null_terminator = GetCurrentDirectoryW(desired_size, tmem_w_path); + if ((bytes_written_wo_null_terminator + 1) != required_size) { + // TODO(dn): Error + return result; + } + + wchar_t *w_path = DN_Arena_NewArray(arena, wchar_t, desired_size, DN_ZeroMem_No); + if (!w_path) + return result; + + if (suffix.size) { + DN_Memcpy(w_path, tmem_w_path, sizeof(*tmem_w_path) * bytes_written_wo_null_terminator); + DN_Memcpy(w_path + bytes_written_wo_null_terminator, suffix.data, sizeof(suffix.data[0]) * suffix.size); + w_path[desired_size] = 0; + } + + result = DN_Str16{w_path, DN_CAST(DN_USize)(desired_size - 1)}; + return result; +} + +DN_API bool DN_W32_DirWIterate(DN_Str16 path, DN_W32FolderIteratorW *it) +{ + WIN32_FIND_DATAW find_data = {}; + if (it->handle) { + if (FindNextFileW(it->handle, &find_data) == 0) { + FindClose(it->handle); + return false; + } + } else { + it->handle = FindFirstFileExW(path.data, /*LPCWSTR lpFileName,*/ + FindExInfoStandard, /*FINDEX_INFO_LEVELS fInfoLevelId,*/ + &find_data, /*LPVOID lpFindFileData,*/ + FindExSearchNameMatch, /*FINDEX_SEARCH_OPS fSearchOp,*/ + nullptr, /*LPVOID lpSearchFilter,*/ + FIND_FIRST_EX_LARGE_FETCH /*unsigned long dwAdditionalFlags)*/); + + if (it->handle == INVALID_HANDLE_VALUE) + return false; + } + + it->file_name_buf[0] = 0; + it->file_name = DN_Str16{it->file_name_buf, 0}; + + do { + if (find_data.cFileName[0] == '.' || (find_data.cFileName[0] == '.' && find_data.cFileName[1] == '.')) + continue; + + it->file_name.size = DN_CStr16_Size(find_data.cFileName); + DN_Assert(it->file_name.size < (DN_ArrayCountU(it->file_name_buf) - 1)); + DN_Memcpy(it->file_name.data, find_data.cFileName, it->file_name.size * sizeof(wchar_t)); + it->file_name_buf[it->file_name.size] = 0; + break; + } while (FindNextFileW(it->handle, &find_data) != 0); + + bool result = it->file_name.size > 0; + if (!result) + FindClose(it->handle); + return result; +} diff --git a/OS/dn_os_w32.h b/OS/dn_os_w32.h new file mode 100644 index 0000000..d3ef20c --- /dev/null +++ b/OS/dn_os_w32.h @@ -0,0 +1,71 @@ +#if !defined(DN_OS_WIN32_H) +#define DN_OS_WIN32_H + +struct DN_W32Error +{ + unsigned long code; + DN_Str8 msg; +}; + +struct DN_W32FolderIteratorW +{ + void *handle; + DN_Str16 file_name; + wchar_t file_name_buf[512]; +}; + +enum DN_W32SyncPrimitiveType +{ + DN_OSW32SyncPrimitiveType_Semaphore, + DN_OSW32SyncPrimitiveType_Mutex, + DN_OSW32SyncPrimitiveType_ConditionVariable, +}; + +struct DN_W32SyncPrimitive +{ + union + { + void *sem; + CRITICAL_SECTION mutex; + CONDITION_VARIABLE cv; + }; + + DN_W32SyncPrimitive *next; +}; + +typedef HRESULT DN_W32SetThreadDescriptionFunc(HANDLE hThread, PWSTR const lpThreadDescription); +struct DN_W32Core +{ + DN_W32SetThreadDescriptionFunc *set_thread_description; + LARGE_INTEGER qpc_frequency; + void *bcrypt_rng_handle; + bool bcrypt_init_success; + bool sym_initialised; + + CRITICAL_SECTION sync_primitive_free_list_mutex; + DN_W32SyncPrimitive *sync_primitive_free_list; +}; + +DN_API void DN_W32_ThreadSetName (DN_Str8 name); + +DN_API DN_Str16 DN_W32_ErrorCodeToMsg16Alloc(uint32_t error_code); +DN_API DN_W32Error DN_W32_ErrorCodeToMsg (DN_Arena *arena, uint32_t error_code); +DN_API DN_W32Error DN_W32_ErrorCodeToMsgAlloc (uint32_t error_code); +DN_API DN_W32Error DN_W32_LastError (DN_Arena *arena); +DN_API DN_W32Error DN_W32_LastErrorAlloc (); +DN_API void DN_W32_MakeProcessDPIAware (); + +// NOTE: Windows Str8 <-> Str16 //////////////////////////////////////////////////////////////////// +DN_API DN_Str16 DN_W32_Str8ToStr16 (DN_Arena *arena, DN_Str8 src); +DN_API int DN_W32_Str8ToStr16Buffer (DN_Str16 src, char *dest, int dest_size); +DN_API DN_Str8 DN_W32_Str16ToStr8 (DN_Arena *arena, DN_Str16 src); +DN_API int DN_W32_Str16ToStr8Buffer (DN_Str16 src, char *dest, int dest_size); +DN_API DN_Str8 DN_W32_Str16ToStr8FromHeap(DN_Str16 src); + +// NOTE: Path navigation /////////////////////////////////////////////////////////////////////////// +DN_API DN_Str16 DN_W32_EXEPathW (DN_Arena *arena); +DN_API DN_Str16 DN_W32_EXEDirW (DN_Arena *arena); +DN_API DN_Str8 DN_W32_WorkingDir (DN_Arena *arena, DN_Str8 suffix); +DN_API DN_Str16 DN_W32_WorkingDirW (DN_Arena *arena, DN_Str16 suffix); +DN_API bool DN_W32_DirWIterate (DN_Str16 path, DN_W32FolderIteratorW *it); +#endif // !defined(DN_OS_WIN32) diff --git a/OS/dn_os_windows.h b/OS/dn_os_windows.h index c73d7bc..3034058 100644 --- a/OS/dn_os_windows.h +++ b/OS/dn_os_windows.h @@ -296,6 +296,10 @@ WORD Identifier; } RTL_CRITICAL_SECTION_DEBUG, *PRTL_CRITICAL_SECTION_DEBUG, RTL_RESOURCE_DEBUG, *PRTL_RESOURCE_DEBUG; + typedef struct _RTL_CONDITION_VARIABLE { + PVOID Ptr; + } RTL_CONDITION_VARIABLE, *PRTL_CONDITION_VARIABLE; + #pragma pack(push, 8) typedef struct _RTL_CRITICAL_SECTION { PRTL_CRITICAL_SECTION_DEBUG DebugInfo; @@ -1065,8 +1069,15 @@ } // NOTE: um/synchapi.h ///////////////////////////////////////////////////////////////////////// + typedef RTL_CONDITION_VARIABLE CONDITION_VARIABLE, *PCONDITION_VARIABLE; + extern "C" { + __declspec(dllimport) VOID __stdcall InitializeConditionVariable (CONDITION_VARIABLE *ConditionVariable); + __declspec(dllimport) VOID __stdcall WakeConditionVariable (CONDITION_VARIABLE *ConditionVariable); + __declspec(dllimport) VOID __stdcall WakeAllConditionVariable (CONDITION_VARIABLE *ConditionVariable); + __declspec(dllimport) BOOL __stdcall SleepConditionVariableCS (CONDITION_VARIABLE *ConditionVariable, CRITICAL_SECTION *CriticalSection, DWORD dwMilliseconds); + __declspec(dllimport) VOID __stdcall InitializeCriticalSection (CRITICAL_SECTION *lpCriticalSection); __declspec(dllimport) VOID __stdcall EnterCriticalSection (CRITICAL_SECTION *lpCriticalSection); __declspec(dllimport) VOID __stdcall LeaveCriticalSection (CRITICAL_SECTION *lpCriticalSection); @@ -1075,6 +1086,7 @@ __declspec(dllimport) DWORD __stdcall SetCriticalSectionSpinCount (CRITICAL_SECTION *lpCriticalSection, DWORD dwSpinCount); __declspec(dllimport) BOOL __stdcall TryEnterCriticalSection (CRITICAL_SECTION *lpCriticalSection); __declspec(dllimport) VOID __stdcall DeleteCriticalSection (CRITICAL_SECTION *lpCriticalSection); + __declspec(dllimport) DWORD __stdcall WaitForSingleObject (HANDLE hHandle, DWORD dwMilliseconds); __declspec(dllimport) BOOL __stdcall ReleaseSemaphore (HANDLE hSemaphore, LONG lReleaseCount, LONG *lpPreviousCount); __declspec(dllimport) VOID __stdcall Sleep (DWORD dwMilliseconds); diff --git a/dn_base_inc.cpp b/dn_base_inc.cpp index 158e4aa..99ed25c 100644 --- a/dn_base_inc.cpp +++ b/dn_base_inc.cpp @@ -1,3 +1,5 @@ +#define DN_BASE_INC_CPP + #include "Base/dn_base.cpp" #include "Base/dn_base_containers.cpp" #include "Base/dn_base_convert.cpp" diff --git a/dn_core_inc.cpp b/dn_core_inc.cpp index c0944f2..f5b5e28 100644 --- a/dn_core_inc.cpp +++ b/dn_core_inc.cpp @@ -1,3 +1,5 @@ +#define DN_CORE_INC_CPP + #include "Core/dn_core.cpp" #include "Core/dn_core_debug.cpp" #include "Core/dn_core_demo.cpp" diff --git a/dn_os_inc.cpp b/dn_os_inc.cpp index 0e4b269..d3b111e 100644 --- a/dn_os_inc.cpp +++ b/dn_os_inc.cpp @@ -1,3 +1,5 @@ +#define DN_OS_INC_CPP + #include "OS/dn_os_tls.cpp" #include "OS/dn_os.cpp" #include "OS/dn_os_allocator.cpp" @@ -8,7 +10,7 @@ #if defined(DN_PLATFORM_POSIX) #include "OS/dn_os_posix.cpp" #elif defined(DN_PLATFORM_WIN32) - #include "OS/dn_os_win32.cpp" + #include "OS/dn_os_w32.cpp" #else #error Please define a platform e.g. 'DN_PLATFORM_WIN32' to enable the correct implementation for platform APIs #endif diff --git a/dn_os_inc.h b/dn_os_inc.h index 3df6507..bb2bd8b 100644 --- a/dn_os_inc.h +++ b/dn_os_inc.h @@ -1,3 +1,6 @@ +#if !defined(DN_OS_INC_H) +#define DN_OS_INC_H + #include "OS/dn_os_tls.h" #include "OS/dn_os.h" #include "OS/dn_os_allocator.h" @@ -7,7 +10,9 @@ #if defined(DN_PLATFORM_WIN32) #include "OS/dn_os_windows.h" - #include "OS/dn_os_win32.h" + #include "OS/dn_os_w32.h" #elif defined(DN_PLATFORM_POSIX) #include "OS/dn_os_posix.h" #endif + +#endif // DN_OS_INC_H