735 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			735 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #define DN_OS_CPP
 | |
| 
 | |
| static DN_OSCore *g_dn_os_core_;
 | |
| 
 | |
| static void DN_OS_LOGEmitFromTypeTypeFV_(DN_LOGTypeParam type, void *user_data, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, va_list args)
 | |
| {
 | |
|   DN_Assert(user_data);
 | |
|   DN_OSCore *core = DN_CAST(DN_OSCore *)user_data;
 | |
| 
 | |
|   // NOTE: Open log file for appending if requested ////////////////////////////////////////////////
 | |
|   DN_TicketMutex_Begin(&core->log_file_mutex);
 | |
|   if (core->log_to_file && !core->log_file.handle && !core->log_file.error) {
 | |
|     DN_OSTLSTMem tmem     = DN_OS_TLSTMem(nullptr);
 | |
|     DN_MSVC_WARNING_PUSH
 | |
|     DN_MSVC_WARNING_DISABLE(6284) // Object passed as _Param_(3) when a string is required in call to 'DN_OS_PathF' Actual type: 'struct DN_Str8'.
 | |
|     DN_Str8      log_path = DN_OS_PathF(tmem.arena, "%S/dn.log", DN_OS_EXEDir(tmem.arena));
 | |
|     DN_MSVC_WARNING_POP
 | |
|     core->log_file        = DN_OS_FileOpen(log_path, DN_OSFileOpen_CreateAlways, DN_OSFileAccess_AppendOnly, nullptr);
 | |
|   }
 | |
|   DN_TicketMutex_End(&core->log_file_mutex);
 | |
| 
 | |
|   DN_LOGStyle style = {};
 | |
|   if (!core->log_no_colour) {
 | |
|     style.colour = true;
 | |
|     style.bold   = DN_LOGBold_Yes;
 | |
|     if (type.is_u32_enum) {
 | |
|       switch (type.u32) {
 | |
|         case DN_LOGType_Debug: {
 | |
|           style.colour = false;
 | |
|           style.bold   = DN_LOGBold_No;
 | |
|         } break;
 | |
| 
 | |
|         case DN_LOGType_Info: {
 | |
|           style.g = 0x87;
 | |
|           style.b = 0xff;
 | |
|         } break;
 | |
| 
 | |
|         case DN_LOGType_Warning: {
 | |
|           style.r = 0xff;
 | |
|           style.g = 0xff;
 | |
|         } break;
 | |
| 
 | |
|         case DN_LOGType_Error: {
 | |
|           style.r = 0xff;
 | |
|         } break;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   DN_OSDateTime os_date  = DN_OS_DateLocalTimeNow();
 | |
|   DN_LOGDate    log_date = {};
 | |
|   log_date.year          = os_date.year;
 | |
|   log_date.month         = os_date.month;
 | |
|   log_date.day           = os_date.day;
 | |
|   log_date.hour          = os_date.hour;
 | |
|   log_date.minute        = os_date.minutes;
 | |
|   log_date.second        = os_date.seconds;
 | |
| 
 | |
|   char             prefix_buffer[128] = {};
 | |
|   DN_LOGPrefixSize prefix_size        = DN_LOG_MakePrefix(style, type, call_site, log_date, prefix_buffer, sizeof(prefix_buffer));
 | |
| 
 | |
|   va_list args_copy;
 | |
|   va_copy(args_copy, args);
 | |
|   DN_TicketMutex_Begin(&core->log_file_mutex);
 | |
|   {
 | |
|     DN_OS_FileWrite(&core->log_file, DN_Str8_Init(prefix_buffer, prefix_size.size), nullptr);
 | |
|     DN_OS_FileWriteF(&core->log_file, nullptr, "%*s ", DN_CAST(int)prefix_size.padding, "");
 | |
|     DN_OS_FileWriteFV(&core->log_file, nullptr, fmt, args_copy);
 | |
|     DN_OS_FileWrite(&core->log_file, DN_STR8("\n"), nullptr);
 | |
|   }
 | |
|   DN_TicketMutex_End(&core->log_file_mutex);
 | |
|   va_end(args_copy);
 | |
| 
 | |
|   DN_OSPrintDest dest = (type.is_u32_enum && type.u32 == DN_LOGType_Error) ? DN_OSPrintDest_Err : DN_OSPrintDest_Out;
 | |
|   DN_OS_Print(dest, DN_Str8_Init(prefix_buffer, prefix_size.size));
 | |
|   DN_OS_PrintF(dest, "%*s ", DN_CAST(int)prefix_size.padding, "");
 | |
|   DN_OS_PrintLnFV(dest, fmt, args);
 | |
| }
 | |
| 
 | |
| DN_API void DN_OS_Init(DN_OSCore *os, DN_OSInitArgs *args)
 | |
| {
 | |
|   g_dn_os_core_ = os;
 | |
| 
 | |
|   // NOTE: OS
 | |
|   {
 | |
|     #if defined(DN_PLATFORM_WIN32)
 | |
|     SYSTEM_INFO system_info = {};
 | |
|     GetSystemInfo(&system_info);
 | |
|     os->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);
 | |
|     #else
 | |
|     // TODO(doyle): Get the proper page size from the OS.
 | |
|     os->page_size         = DN_Kilobytes(4);
 | |
|     os->alloc_granularity = DN_Kilobytes(64);
 | |
|     #endif
 | |
|   }
 | |
| 
 | |
|   // NOTE: Setup logging
 | |
|   DN_OS_EmitLogsWithOSPrintFunctions(os);
 | |
| 
 | |
|   #if defined(DN_PLATFORM_WIN32)
 | |
|   // 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;
 | |
|     else
 | |
|       DN_LOG_ErrorF("Failed to initialise Windows secure random number generator, error: %d", init_status);
 | |
|   }
 | |
|   #endif
 | |
| 
 | |
|   // NOTE: Initialise tmem arenas which allocate memory and will be
 | |
|   // recorded to the now initialised allocation table. The initialisation
 | |
|   // of tmem memory may request tmem memory itself in leak tracing mode.
 | |
|   // This is supported as the tmem arenas defer allocation tracking until
 | |
|   // initialisation is done.
 | |
|   DN_OSTLSInitArgs tls_init_args = {};
 | |
|   if (args) {
 | |
|     tls_init_args.commit           = args->tls_commit;
 | |
|     tls_init_args.reserve          = args->tls_reserve;
 | |
|     tls_init_args.err_sink_reserve = args->tls_err_sink_reserve;
 | |
|     tls_init_args.err_sink_commit  = args->tls_err_sink_commit;
 | |
|   }
 | |
| 
 | |
|   DN_OS_TLSInit(&os->tls, tls_init_args);
 | |
|   DN_OS_TLSSetCurrentThreadTLS(&os->tls);
 | |
|   os->cpu_report = DN_CPU_Report();
 | |
| 
 | |
|   #define DN_CPU_FEAT_XENTRY(label) g_dn_cpu_feature_decl[DN_CPUFeature_##label] = {DN_CPUFeature_##label, DN_STR8(#label)};
 | |
|   DN_CPU_FEAT_XMACRO
 | |
|   #undef DN_CPU_FEAT_XENTRY
 | |
| 
 | |
|   DN_Assert(g_dn_os_core_);
 | |
| }
 | |
| 
 | |
| DN_API void DN_OS_EmitLogsWithOSPrintFunctions(DN_OSCore *os)
 | |
| {
 | |
|   DN_Assert(os);
 | |
|   DN_LOG_SetEmitFromTypeFVFunc(DN_OS_LOGEmitFromTypeTypeFV_, os);
 | |
| }
 | |
| 
 | |
| DN_API void DN_OS_DumpThreadContextArenaStat(DN_Str8 file_path)
 | |
| {
 | |
| #if defined(DN_DEBUG_THREAD_CONTEXT)
 | |
|   // NOTE: Open a file to write the arena stats to
 | |
|   FILE *file = nullptr;
 | |
|   fopen_s(&file, file_path.data, "a+b");
 | |
|   if (file) {
 | |
|     DN_LOG_ErrorF("Failed to dump thread context arenas [file=%.*s]", DN_STR_FMT(file_path));
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // NOTE: Copy the stats from library book-keeping
 | |
|   // NOTE: Extremely short critical section, copy the stats then do our
 | |
|   // work on it.
 | |
|   DN_ArenaStat stats[DN_CArray_CountI(g_dn_core->thread_context_arena_stats)];
 | |
|   int          stats_size = 0;
 | |
| 
 | |
|   DN_TicketMutex_Begin(&g_dn_core->thread_context_mutex);
 | |
|   stats_size = g_dn_core->thread_context_arena_stats_count;
 | |
|   DN_Memcpy(stats, g_dn_core->thread_context_arena_stats, sizeof(stats[0]) * stats_size);
 | |
|   DN_TicketMutex_End(&g_dn_core->thread_context_mutex);
 | |
| 
 | |
|   // NOTE: Print the cumulative stat
 | |
|   DN_DateHMSTimeStr now = DN_Date_HMSLocalTimeStrNow();
 | |
|   fprintf(file,
 | |
|           "Time=%.*s %.*s | Thread Context Arenas | Count=%d\n",
 | |
|           now.date_size,
 | |
|           now.date,
 | |
|           now.hms_size,
 | |
|           now.hms,
 | |
|           g_dn_core->thread_context_arena_stats_count);
 | |
| 
 | |
|   // NOTE: Write the cumulative thread arena data
 | |
|   {
 | |
|     DN_ArenaStat stat = {};
 | |
|     for (DN_USize index = 0; index < stats_size; index++) {
 | |
|       DN_ArenaStat const *current = stats + index;
 | |
|       stat.capacity += current->capacity;
 | |
|       stat.used += current->used;
 | |
|       stat.wasted += current->wasted;
 | |
|       stat.blocks += current->blocks;
 | |
| 
 | |
|       stat.capacity_hwm = DN_Max(stat.capacity_hwm, current->capacity_hwm);
 | |
|       stat.used_hwm     = DN_Max(stat.used_hwm, current->used_hwm);
 | |
|       stat.wasted_hwm   = DN_Max(stat.wasted_hwm, current->wasted_hwm);
 | |
|       stat.blocks_hwm   = DN_Max(stat.blocks_hwm, current->blocks_hwm);
 | |
|     }
 | |
| 
 | |
|     DN_ArenaStatStr stats_string = DN_Arena_StatStr(&stat);
 | |
|     fprintf(file, "  [ALL] CURR %.*s\n", stats_string.size, stats_string.data);
 | |
|   }
 | |
| 
 | |
|   // NOTE: Print individual thread arena data
 | |
|   for (DN_USize index = 0; index < stats_size; index++) {
 | |
|     DN_ArenaStat const *current        = stats + index;
 | |
|     DN_ArenaStatStr     current_string = DN_Arena_StatStr(current);
 | |
|     fprintf(file, "  [%03d] CURR %.*s\n", DN_CAST(int) index, current_string.size, current_string.data);
 | |
|   }
 | |
| 
 | |
|   fclose(file);
 | |
|   DN_LOG_InfoF("Dumped thread context arenas [file=%.*s]", DN_STR_FMT(file_path));
 | |
| #else
 | |
|   (void)file_path;
 | |
| #endif // #if defined(DN_DEBUG_THREAD_CONTEXT)
 | |
| }
 | |
| 
 | |
| // NOTE: Date //////////////////////////////////////////////////////////////////////////////////////
 | |
| DN_API DN_OSDateTimeStr8 DN_OS_DateLocalTimeStr8(DN_OSDateTime time, char date_separator, char hms_separator)
 | |
| {
 | |
|   DN_OSDateTimeStr8 result = {};
 | |
|   result.hms_size          = DN_CAST(uint8_t) DN_SNPrintF(result.hms,
 | |
|                                                  DN_ArrayCountI(result.hms),
 | |
|                                                  "%02hhu%c%02hhu%c%02hhu",
 | |
|                                                  time.hour,
 | |
|                                                  hms_separator,
 | |
|                                                  time.minutes,
 | |
|                                                  hms_separator,
 | |
|                                                  time.seconds);
 | |
| 
 | |
|   result.date_size = DN_CAST(uint8_t) DN_SNPrintF(result.date,
 | |
|                                                   DN_ArrayCountI(result.date),
 | |
|                                                   "%hu%c%02hhu%c%02hhu",
 | |
|                                                   time.year,
 | |
|                                                   date_separator,
 | |
|                                                   time.month,
 | |
|                                                   date_separator,
 | |
|                                                   time.day);
 | |
| 
 | |
|   DN_Assert(result.hms_size < DN_ArrayCountU(result.hms));
 | |
|   DN_Assert(result.date_size < DN_ArrayCountU(result.date));
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| DN_API DN_OSDateTimeStr8 DN_OS_DateLocalTimeStr8Now(char date_separator, char hms_separator)
 | |
| {
 | |
|   DN_OSDateTime     time   = DN_OS_DateLocalTimeNow();
 | |
|   DN_OSDateTimeStr8 result = DN_OS_DateLocalTimeStr8(time, date_separator, hms_separator);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| DN_API uint64_t DN_OS_DateUnixTimeS()
 | |
| {
 | |
|   uint64_t result = DN_OS_DateUnixTimeNs() / (1'000 /*us*/ * 1'000 /*ms*/ * 1'000 /*s*/);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| DN_API bool DN_OS_DateIsValid(DN_OSDateTime date)
 | |
| {
 | |
|   if (date.year < 1970)
 | |
|     return false;
 | |
|   if (date.month <= 0 || date.month >= 13)
 | |
|     return false;
 | |
|   if (date.day <= 0 || date.day >= 32)
 | |
|     return false;
 | |
|   if (date.hour >= 24)
 | |
|     return false;
 | |
|   if (date.minutes >= 60)
 | |
|     return false;
 | |
|   if (date.seconds >= 60)
 | |
|     return false;
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| // NOTE: Other /////////////////////////////////////////////////////////////////////////////////////
 | |
| DN_API DN_Str8 DN_OS_EXEDir(DN_Arena *arena)
 | |
| {
 | |
|   DN_Str8 result = {};
 | |
|   if (!arena)
 | |
|     return result;
 | |
|   DN_OSTLSTMem               tmem         = DN_OS_TLSTMem(arena);
 | |
|   DN_Str8                  exe_path     = DN_OS_EXEPath(tmem.arena);
 | |
|   DN_Str8                  separators[] = {DN_STR8("/"), DN_STR8("\\")};
 | |
|   DN_Str8BinarySplitResult split        = DN_Str8_BinarySplitLastArray(exe_path, separators, DN_ArrayCountU(separators));
 | |
|   result                                = DN_Str8_Copy(arena, split.lhs);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| // NOTE: Counters //////////////////////////////////////////////////////////////////////////////////
 | |
| DN_API DN_F64 DN_OS_PerfCounterS(uint64_t begin, uint64_t end)
 | |
| {
 | |
|   uint64_t frequency = DN_OS_PerfCounterFrequency();
 | |
|   uint64_t ticks     = end - begin;
 | |
|   DN_F64   result    = ticks / DN_CAST(DN_F64) frequency;
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| DN_API DN_F64 DN_OS_PerfCounterMs(uint64_t begin, uint64_t end)
 | |
| {
 | |
|   uint64_t frequency = DN_OS_PerfCounterFrequency();
 | |
|   uint64_t ticks     = end - begin;
 | |
|   DN_F64   result    = (ticks * 1'000) / DN_CAST(DN_F64) frequency;
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| DN_API DN_F64 DN_OS_PerfCounterUs(uint64_t begin, uint64_t end)
 | |
| {
 | |
|   uint64_t frequency = DN_OS_PerfCounterFrequency();
 | |
|   uint64_t ticks     = end - begin;
 | |
|   DN_F64   result    = (ticks * 1'000'000) / DN_CAST(DN_F64) frequency;
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| DN_API DN_F64 DN_OS_PerfCounterNs(uint64_t begin, uint64_t end)
 | |
| {
 | |
|   uint64_t frequency = DN_OS_PerfCounterFrequency();
 | |
|   uint64_t ticks     = end - begin;
 | |
|   DN_F64   result    = (ticks * 1'000'000'000) / DN_CAST(DN_F64) frequency;
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| DN_API DN_OSTimer DN_OS_TimerBegin()
 | |
| {
 | |
|   DN_OSTimer result = {};
 | |
|   result.start      = DN_OS_PerfCounterNow();
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| DN_API void DN_OS_TimerEnd(DN_OSTimer *timer)
 | |
| {
 | |
|   timer->end = DN_OS_PerfCounterNow();
 | |
| }
 | |
| 
 | |
| DN_API DN_F64 DN_OS_TimerS(DN_OSTimer timer)
 | |
| {
 | |
|   DN_F64 result = DN_OS_PerfCounterS(timer.start, timer.end);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| DN_API DN_F64 DN_OS_TimerMs(DN_OSTimer timer)
 | |
| {
 | |
|   DN_F64 result = DN_OS_PerfCounterMs(timer.start, timer.end);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| DN_API DN_F64 DN_OS_TimerUs(DN_OSTimer timer)
 | |
| {
 | |
|   DN_F64 result = DN_OS_PerfCounterUs(timer.start, timer.end);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| DN_API DN_F64 DN_OS_TimerNs(DN_OSTimer timer)
 | |
| {
 | |
|   DN_F64 result = DN_OS_PerfCounterNs(timer.start, timer.end);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| DN_API uint64_t DN_OS_EstimateTSCPerSecond(uint64_t duration_ms_to_gauge_tsc_frequency)
 | |
| {
 | |
|   uint64_t os_frequency      = DN_OS_PerfCounterFrequency();
 | |
|   uint64_t os_target_elapsed = duration_ms_to_gauge_tsc_frequency * os_frequency / 1000ULL;
 | |
|   uint64_t tsc_begin         = DN_CPU_TSC();
 | |
|   uint64_t result            = 0;
 | |
|   if (tsc_begin) {
 | |
|     uint64_t os_elapsed = 0;
 | |
|     for (uint64_t os_begin = DN_OS_PerfCounterNow(); os_elapsed < os_target_elapsed;)
 | |
|       os_elapsed = DN_OS_PerfCounterNow() - os_begin;
 | |
|     uint64_t tsc_end     = DN_CPU_TSC();
 | |
|     uint64_t tsc_elapsed = tsc_end - tsc_begin;
 | |
|     result               = tsc_elapsed / os_elapsed * os_frequency;
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| #if !defined(DN_NO_OS_FILE_API)
 | |
| // NOTE: DN_OSPathInfo/File ////////////////////////////////////////////////////////////////////////
 | |
| DN_API bool DN_OS_FileIsOlderThan(DN_Str8 file, DN_Str8 check_against)
 | |
| {
 | |
|   DN_OSPathInfo file_info          = DN_OS_PathInfo(file);
 | |
|   DN_OSPathInfo check_against_info = DN_OS_PathInfo(check_against);
 | |
|   bool          result             = !file_info.exists || file_info.last_write_time_in_s < check_against_info.last_write_time_in_s;
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| DN_API bool DN_OS_FileWrite(DN_OSFile *file, DN_Str8 buffer, DN_OSErrSink *error)
 | |
| {
 | |
|   bool result = DN_OS_FileWritePtr(file, buffer.data, buffer.size, error);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| struct DN_OSFileWriteChunker_
 | |
| {
 | |
|   DN_OSErrSink *err;
 | |
|   DN_OSFile  *file;
 | |
|   bool        success;
 | |
| };
 | |
| 
 | |
| static char *DN_OS_FileWriteChunker_(const char *buf, void *user, int len)
 | |
| {
 | |
|   DN_OSFileWriteChunker_ *chunker = DN_CAST(DN_OSFileWriteChunker_ *)user;
 | |
|   chunker->success                = DN_OS_FileWritePtr(chunker->file, buf, len, chunker->err);
 | |
|   char *result                    = chunker->success ? DN_CAST(char *) buf : nullptr;
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| DN_API bool DN_OS_FileWriteFV(DN_OSFile *file, DN_OSErrSink *error, DN_FMT_ATTRIB char const *fmt, va_list args)
 | |
| {
 | |
|   bool result = false;
 | |
|   if (!file || !fmt)
 | |
|     return result;
 | |
| 
 | |
|   DN_OSFileWriteChunker_ chunker = {};
 | |
|   chunker.err                    = error;
 | |
|   chunker.file                   = file;
 | |
|   char buffer[STB_SPRINTF_MIN];
 | |
|   STB_SPRINTF_DECORATE(vsprintfcb)(DN_OS_FileWriteChunker_, &chunker, buffer, fmt, args);
 | |
| 
 | |
|   result = chunker.success;
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| DN_API bool DN_OS_FileWriteF(DN_OSFile *file, DN_OSErrSink *error, DN_FMT_ATTRIB char const *fmt, ...)
 | |
| {
 | |
|   va_list args;
 | |
|   va_start(args, fmt);
 | |
|   bool result = DN_OS_FileWriteFV(file, error, fmt, args);
 | |
|   va_end(args);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| // NOTE: R/W Entire File ///////////////////////////////////////////////////////////////////////////
 | |
| DN_API DN_Str8 DN_OS_ReadAll(DN_Arena *arena, DN_Str8 path, DN_OSErrSink *error)
 | |
| {
 | |
|   DN_Str8 result = {};
 | |
|   if (!arena)
 | |
|     return result;
 | |
| 
 | |
|   // NOTE: Query file size + allocate buffer /////////////////////////////////////////////////////
 | |
|   DN_OSPathInfo path_info = DN_OS_PathInfo(path);
 | |
|   if (!path_info.exists) {
 | |
|     DN_OS_ErrSinkAppendF(error, 1, "File does not exist/could not be queried for reading '%.*s'", DN_STR_FMT(path));
 | |
|     return result;
 | |
|   }
 | |
| 
 | |
|   DN_ArenaTempMem temp_mem = DN_Arena_TempMemBegin(arena);
 | |
|   result                   = DN_Str8_Alloc(arena, path_info.size, DN_ZeroMem_No);
 | |
|   if (!DN_Str8_HasData(result)) {
 | |
|     DN_OSTLSTMem tmem             = DN_OS_TLSTMem(nullptr);
 | |
|     DN_Str8    buffer_size_str8 = DN_CVT_U64ToByteSizeStr8(tmem.arena, path_info.size, DN_CVTU64ByteSizeType_Auto);
 | |
|     DN_OS_ErrSinkAppendF(error, 1 /*error_code*/, "Failed to allocate %.*s for reading file '%.*s'", DN_STR_FMT(buffer_size_str8), DN_STR_FMT(path));
 | |
|     DN_Arena_TempMemEnd(temp_mem);
 | |
|     result = {};
 | |
|     return result;
 | |
|   }
 | |
| 
 | |
|   // NOTE: Read the file from disk ///////////////////////////////////////////////////////////////
 | |
|   DN_OSFile     file = DN_OS_FileOpen(path, DN_OSFileOpen_OpenIfExist, DN_OSFileAccess_Read, error);
 | |
|   DN_OSFileRead read = DN_OS_FileRead(&file, result.data, result.size, error);
 | |
|   if (file.error || !read.success) {
 | |
|     DN_Arena_TempMemEnd(temp_mem);
 | |
|     result = {};
 | |
|   }
 | |
|   DN_OS_FileClose(&file);
 | |
| 
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| DN_API bool DN_OS_WriteAll(DN_Str8 path, DN_Str8 buffer, DN_OSErrSink *error)
 | |
| {
 | |
|   DN_OSFile file   = DN_OS_FileOpen(path, DN_OSFileOpen_CreateAlways, DN_OSFileAccess_Write, error);
 | |
|   bool      result = DN_OS_FileWrite(&file, buffer, error);
 | |
|   DN_OS_FileClose(&file);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| DN_API bool DN_OS_WriteAllFV(DN_Str8 file_path, DN_OSErrSink *error, DN_FMT_ATTRIB char const *fmt, va_list args)
 | |
| {
 | |
|   DN_OSTLSTMem tmem   = DN_OS_TLSTMem(nullptr);
 | |
|   DN_Str8    buffer = DN_Str8_InitFV(tmem.arena, fmt, args);
 | |
|   bool       result = DN_OS_WriteAll(file_path, buffer, error);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| DN_API bool DN_OS_WriteAllF(DN_Str8 file_path, DN_OSErrSink *error, DN_FMT_ATTRIB char const *fmt, ...)
 | |
| {
 | |
|   va_list args;
 | |
|   va_start(args, fmt);
 | |
|   bool result = DN_OS_WriteAllFV(file_path, error, fmt, args);
 | |
|   va_end(args);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| DN_API bool DN_OS_WriteAllSafe(DN_Str8 path, DN_Str8 buffer, DN_OSErrSink *error)
 | |
| {
 | |
|   DN_OSTLSTMem tmem     = DN_OS_TLSTMem(nullptr);
 | |
|   DN_Str8    tmp_path = DN_Str8_InitF(tmem.arena, "%.*s.tmp", DN_STR_FMT(path));
 | |
|   if (!DN_OS_WriteAll(tmp_path, buffer, error))
 | |
|     return false;
 | |
|   if (!DN_OS_CopyFile(tmp_path, path, true /*overwrite*/, error))
 | |
|     return false;
 | |
|   if (!DN_OS_PathDelete(tmp_path))
 | |
|     return false;
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| DN_API bool DN_OS_WriteAllSafeFV(DN_Str8 path, DN_OSErrSink *error, DN_FMT_ATTRIB char const *fmt, va_list args)
 | |
| {
 | |
|   DN_OSTLSTMem tmem   = DN_OS_TLSTMem(nullptr);
 | |
|   DN_Str8    buffer = DN_Str8_InitFV(tmem.arena, fmt, args);
 | |
|   bool       result = DN_OS_WriteAllSafe(path, buffer, error);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| DN_API bool DN_OS_WriteAllSafeF(DN_Str8 path, DN_OSErrSink *error, DN_FMT_ATTRIB char const *fmt, ...)
 | |
| {
 | |
|   va_list args;
 | |
|   va_start(args, fmt);
 | |
|   bool result = DN_OS_WriteAllSafeFV(path, error, fmt, args);
 | |
|   return result;
 | |
| }
 | |
| #endif // !defined(DN_NO_OS_FILE_API)
 | |
| 
 | |
| // NOTE: DN_OSPath /////////////////////////////////////////////////////////////////////////////////
 | |
| DN_API bool DN_OS_PathAddRef(DN_Arena *arena, DN_OSPath *fs_path, DN_Str8 path)
 | |
| {
 | |
|   if (!arena || !fs_path || !DN_Str8_HasData(path))
 | |
|     return false;
 | |
| 
 | |
|   if (path.size <= 0)
 | |
|     return true;
 | |
| 
 | |
|   DN_Str8 const delimiter_array[] = {
 | |
|       DN_STR8("\\"),
 | |
|       DN_STR8("/")};
 | |
| 
 | |
|   if (fs_path->links_size == 0)
 | |
|     fs_path->has_prefix_path_separator = (path.data[0] == '/');
 | |
| 
 | |
|   for (;;) {
 | |
|     DN_Str8BinarySplitResult delimiter = DN_Str8_BinarySplitArray(path, delimiter_array, DN_ArrayCountU(delimiter_array));
 | |
|     for (; delimiter.lhs.data; delimiter = DN_Str8_BinarySplitArray(delimiter.rhs, delimiter_array, DN_ArrayCountU(delimiter_array))) {
 | |
|       if (delimiter.lhs.size <= 0)
 | |
|         continue;
 | |
| 
 | |
|       DN_OSPathLink *link = DN_Arena_New(arena, DN_OSPathLink, DN_ZeroMem_Yes);
 | |
|       if (!link)
 | |
|         return false;
 | |
| 
 | |
|       link->string = delimiter.lhs;
 | |
|       link->prev   = fs_path->tail;
 | |
|       if (fs_path->tail)
 | |
|         fs_path->tail->next = link;
 | |
|       else
 | |
|         fs_path->head = link;
 | |
|       fs_path->tail = link;
 | |
|       fs_path->links_size += 1;
 | |
|       fs_path->string_size += delimiter.lhs.size;
 | |
|     }
 | |
| 
 | |
|     if (!delimiter.lhs.data)
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| DN_API bool DN_OS_PathAdd(DN_Arena *arena, DN_OSPath *fs_path, DN_Str8 path)
 | |
| {
 | |
|   DN_Str8 copy   = DN_Str8_Copy(arena, path);
 | |
|   bool    result = DN_Str8_HasData(copy) ? true : DN_OS_PathAddRef(arena, fs_path, copy);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| DN_API bool DN_OS_PathAddF(DN_Arena *arena, DN_OSPath *fs_path, DN_FMT_ATTRIB char const *fmt, ...)
 | |
| {
 | |
|   va_list args;
 | |
|   va_start(args, fmt);
 | |
|   DN_Str8 path = DN_Str8_InitFV(arena, fmt, args);
 | |
|   va_end(args);
 | |
|   bool result = DN_OS_PathAddRef(arena, fs_path, path);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| DN_API bool DN_OS_PathPop(DN_OSPath *fs_path)
 | |
| {
 | |
|   if (!fs_path)
 | |
|     return false;
 | |
| 
 | |
|   if (fs_path->tail) {
 | |
|     DN_Assert(fs_path->head);
 | |
|     fs_path->links_size -= 1;
 | |
|     fs_path->string_size -= fs_path->tail->string.size;
 | |
|     fs_path->tail = fs_path->tail->prev;
 | |
|     if (fs_path->tail)
 | |
|       fs_path->tail->next = nullptr;
 | |
|     else
 | |
|       fs_path->head = nullptr;
 | |
|   } else {
 | |
|     DN_Assert(!fs_path->head);
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| DN_API DN_Str8 DN_OS_PathTo(DN_Arena *arena, DN_Str8 path, DN_Str8 path_separator)
 | |
| {
 | |
|   DN_OSPath fs_path = {};
 | |
|   DN_OS_PathAddRef(arena, &fs_path, path);
 | |
|   DN_Str8 result = DN_OS_PathBuildWithSeparator(arena, &fs_path, path_separator);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| DN_API DN_Str8 DN_OS_PathToF(DN_Arena *arena, DN_Str8 path_separator, DN_FMT_ATTRIB char const *fmt, ...)
 | |
| {
 | |
|   DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena);
 | |
|   va_list    args;
 | |
|   va_start(args, fmt);
 | |
|   DN_Str8 path = DN_Str8_InitFV(tmem.arena, fmt, args);
 | |
|   va_end(args);
 | |
|   DN_Str8 result = DN_OS_PathTo(arena, path, path_separator);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| DN_API DN_Str8 DN_OS_Path(DN_Arena *arena, DN_Str8 path)
 | |
| {
 | |
|   DN_Str8 result = DN_OS_PathTo(arena, path, DN_OSPathSeperatorString);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| DN_API DN_Str8 DN_OS_PathF(DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, ...)
 | |
| {
 | |
|   DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena);
 | |
|   va_list    args;
 | |
|   va_start(args, fmt);
 | |
|   DN_Str8 path = DN_Str8_InitFV(tmem.arena, fmt, args);
 | |
|   va_end(args);
 | |
|   DN_Str8 result = DN_OS_Path(arena, path);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| DN_API DN_Str8 DN_OS_PathBuildWithSeparator(DN_Arena *arena, DN_OSPath const *fs_path, DN_Str8 path_separator)
 | |
| {
 | |
|   DN_Str8 result = {};
 | |
|   if (!fs_path || fs_path->links_size <= 0)
 | |
|     return result;
 | |
| 
 | |
|   // NOTE: Each link except the last one needs the path separator appended to it, '/' or '\\'
 | |
|   DN_USize string_size = (fs_path->has_prefix_path_separator ? path_separator.size : 0) + fs_path->string_size + ((fs_path->links_size - 1) * path_separator.size);
 | |
|   result               = DN_Str8_Alloc(arena, string_size, DN_ZeroMem_No);
 | |
|   if (result.data) {
 | |
|     char *dest = result.data;
 | |
|     if (fs_path->has_prefix_path_separator) {
 | |
|       DN_Memcpy(dest, path_separator.data, path_separator.size);
 | |
|       dest += path_separator.size;
 | |
|     }
 | |
| 
 | |
|     for (DN_OSPathLink *link = fs_path->head; link; link = link->next) {
 | |
|       DN_Str8 string = link->string;
 | |
|       DN_Memcpy(dest, string.data, string.size);
 | |
|       dest += string.size;
 | |
| 
 | |
|       if (link != fs_path->tail) {
 | |
|         DN_Memcpy(dest, path_separator.data, path_separator.size);
 | |
|         dest += path_separator.size;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   result.data[string_size] = 0;
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| // NOTE: DN_OSExec /////////////////////////////////////////////////////////////////////////////////
 | |
| DN_API DN_OSExecResult DN_OS_Exec(DN_Slice<DN_Str8> cmd_line,
 | |
|                                   DN_OSExecArgs    *args,
 | |
|                                   DN_Arena         *arena,
 | |
|                                   DN_OSErrSink       *error)
 | |
| {
 | |
|   DN_OSExecAsyncHandle async_handle = DN_OS_ExecAsync(cmd_line, args, error);
 | |
|   DN_OSExecResult      result       = DN_OS_ExecWait(async_handle, arena, error);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| DN_API DN_OSExecResult DN_OS_ExecOrAbort(DN_Slice<DN_Str8> cmd_line, DN_OSExecArgs *args, DN_Arena *arena)
 | |
| {
 | |
|   DN_OSErrSink  *error  = DN_OS_ErrSinkBegin(DN_OSErrSinkMode_Nil);
 | |
|   DN_OSExecResult result = DN_OS_Exec(cmd_line, args, arena, error);
 | |
|   if (result.os_error_code)
 | |
|     DN_OS_ErrSinkEndAndExitIfErrorF(error, result.os_error_code, "OS failed to execute the requested command returning the error code %u", result.os_error_code);
 | |
| 
 | |
|   if (result.exit_code)
 | |
|     DN_OS_ErrSinkEndAndExitIfErrorF(error, result.exit_code, "OS executed command and returned non-zero exit code %u", result.exit_code);
 | |
|   DN_OS_ErrSinkEndAndIgnore(error);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| // NOTE: DN_OSThread ///////////////////////////////////////////////////////////////////////////////
 | |
| static void DN_OS_ThreadExecute_(void *user_context)
 | |
| {
 | |
|   DN_OSThread *thread = DN_CAST(DN_OSThread *) user_context;
 | |
|   DN_OS_TLSInit(&thread->tls, thread->tls_init_args);
 | |
|   DN_OS_TLSSetCurrentThreadTLS(&thread->tls);
 | |
|   DN_OS_SemaphoreWait(&thread->init_semaphore, DN_OS_SEMAPHORE_INFINITE_TIMEOUT);
 | |
|   thread->func(thread);
 | |
| }
 | |
| 
 | |
| DN_API void DN_OS_ThreadSetName(DN_Str8 name)
 | |
| {
 | |
|   DN_OSTLS *tls    = DN_OS_TLSGet();
 | |
|   tls->name_size = DN_CAST(uint8_t) DN_Min(name.size, sizeof(tls->name) - 1);
 | |
|   DN_Memcpy(tls->name, name.data, tls->name_size);
 | |
|   tls->name[tls->name_size] = 0;
 | |
| 
 | |
| #if defined(DN_PLATFORM_WIN32)
 | |
|   DN_Win_ThreadSetName(name);
 | |
| #else
 | |
|   DN_Posix_ThreadSetName(name);
 | |
| #endif
 | |
| }
 | |
| 
 | |
| // NOTE: DN_OSHttp /////////////////////////////////////////////////////////////////////////////////
 | |
| DN_API void DN_OS_HttpRequestWait(DN_OSHttpResponse *response)
 | |
| {
 | |
|   if (response && DN_OS_SemaphoreIsValid(&response->on_complete_semaphore))
 | |
|     DN_OS_SemaphoreWait(&response->on_complete_semaphore, DN_OS_SEMAPHORE_INFINITE_TIMEOUT);
 | |
| }
 | |
| 
 | |
| DN_API DN_OSHttpResponse DN_OS_HttpRequest(DN_Arena *arena, DN_Str8 host, DN_Str8 path, DN_OSHttpRequestSecure secure, DN_Str8 method, DN_Str8 body, DN_Str8 headers)
 | |
| {
 | |
|   // TODO(doyle): Revise the memory allocation and its lifetime
 | |
|   DN_OSHttpResponse result = {};
 | |
|   DN_OSTLSTMem        tmem   = DN_OS_TLSTMem(arena);
 | |
|   result.tmem_arena        = tmem.arena;
 | |
| 
 | |
|   DN_OS_HttpRequestAsync(&result, arena, host, path, secure, method, body, headers);
 | |
|   DN_OS_HttpRequestWait(&result);
 | |
|   return result;
 | |
| }
 |