power_play/src/base/base_win32/base_win32.c
2025-12-12 14:10:10 -06:00

545 lines
15 KiB
C

W32_Ctx W32 = ZI;
////////////////////////////////////////////////////////////
//~ Win32 embedded data
BOOL W32_FindEmbeddedRcData(HMODULE module, LPCWSTR type, LPWSTR wstr_entry_name, LONG_PTR udata)
{
W32_FindEmbeddedDataCtx *ctx = (W32_FindEmbeddedDataCtx *)udata;
TempArena scratch = BeginScratchNoConflict();
String entry_name = StringFromWstrNoLimit(scratch.arena, (LPWSTR)wstr_entry_name);
String embedded_data_prefix = Lit(Stringize(W32_EmbeddedDataPrefix));
if (StringBeginsWith(entry_name, embedded_data_prefix))
{
HRSRC hres = FindResourceW(module, wstr_entry_name, type);
if (hres)
{
HGLOBAL hg = LoadResource(module, hres);
if (hg)
{
if (ctx->embedded_strings_count < countof(ctx->embedded_strings))
{
String embedded = ZI;
embedded.len = SizeofResource(module, hres);
embedded.text = LockResource(hg);
ctx->embedded_strings[ctx->embedded_strings_count++] = embedded;
}
else
{
Panic(Lit("Maximum number of embedded resource entries exceeded"));
}
}
}
}
EndScratch(scratch);
return 1;
}
////////////////////////////////////////////////////////////
//~ @hookimpl Core api
StringList GetRawCommandline(void)
{
return W32.raw_command_line;
}
void Echo(String msg)
{
HANDLE console_handle = GetStdHandle(STD_OUTPUT_HANDLE);
if (console_handle && console_handle != INVALID_HANDLE_VALUE)
{
WriteFile(console_handle, msg.text, msg.len, 0, 0);
}
else if (IsRunningInDebugger())
{
char msg_cstr[16384];
u64 len = MinU64(countof(msg_cstr) - 1, msg.len);
CopyBytes(msg_cstr, msg.text, len);
msg_cstr[len] = 0;
OutputDebugStringA(msg_cstr);
}
}
b32 Panic(String msg)
{
LogPanic(msg);
char msg_cstr[4096];
CstrFromStringToBuff(StringFromArray(msg_cstr), msg);
{
u32 mb_flags = MB_SETFOREGROUND | MB_ICONERROR;
MessageBoxExA(0, msg_cstr, "Fatal error", mb_flags, 0);
}
HANDLE console_handle = GetStdHandle(STD_ERROR_HANDLE);
if (console_handle != INVALID_HANDLE_VALUE)
{
WriteFile(console_handle, msg.text, msg.len, 0, 0);
}
if (IsRunningInDebugger())
{
Assert(0);
}
else
{
ExitProcess(1);
}
return 0;
}
b32 IsRunningInDebugger(void)
{
return IsDebuggerPresent();
}
i64 TimeNs(void)
{
LARGE_INTEGER qpc;
QueryPerformanceCounter(&qpc);
i64 result = (qpc.QuadPart - W32.timer_start_qpc) * W32.ns_per_qpc;
return result;
}
void TrueRand(String buffer)
{
BCryptGenRandom(BCRYPT_RNG_ALG_HANDLE, (u8 *)buffer.text, buffer.len, 0);
}
CpuTopologyInfo GetCpuTopologyInfo(void)
{
TempArena scratch = BeginScratchNoConflict();
CpuTopologyInfo res = ZI;
{
DWORD infos_buff_size = 0;
u8 *infos_buff = 0;
b32 ok = 0;
{
GetLogicalProcessorInformationEx(RelationProcessorCore, 0, &infos_buff_size);
infos_buff = PushStructsNoZero(scratch.arena, u8, infos_buff_size);
ok = GetLogicalProcessorInformationEx(RelationProcessorCore, (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *)infos_buff, &infos_buff_size);
}
if (ok)
{
/* Determine max efficiency class */
i32 max_efficiency_class = 0;
{
DWORD pos = 0;
while (pos < infos_buff_size)
{
SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *info = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *)&infos_buff[pos];
max_efficiency_class = MaxI32(max_efficiency_class, info->Processor.EfficiencyClass);
pos += info->Size;
}
}
/* Generate physical core info */
{
DWORD pos = 0;
while (pos < infos_buff_size)
{
SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *info = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *)&infos_buff[pos];
++res.num_physical_cores;
++res.num_logical_cores;
if (info->Processor.Flags & LTP_PC_SMT)
{
/* Core has SMT sibling */
++res.num_logical_cores;
}
if (info->Processor.EfficiencyClass == max_efficiency_class)
{
/* Core is P-core */
++res.num_physical_performance_cores;
}
else
{
/* Core is not a P-core */
++res.num_physical_non_performance_cores;
}
pos += info->Size;
}
}
}
}
res.num_logical_cores = MaxI32(res.num_logical_cores, 1);
res.num_physical_cores = MaxI32(res.num_physical_cores, 1);
res.num_physical_performance_cores = MaxI32(res.num_physical_performance_cores, 1);
EndScratch(scratch);
return res;
}
void SleepSeconds(f64 seconds)
{
Sleep(seconds / 1000.0);
}
////////////////////////////////////////////////////////////
//~ @hookimpl Swap
b32 IsSwappedIn(void)
{
return IsHotSwappingEnabled;
}
b32 IsSwappingOut(void)
{
return IsHotSwappingEnabled;
}
String SwappedStateFromName(Arena *arena, String name)
{
TempArena scratch = BeginScratch(arena);
String result = ZI;
String path = StringF(scratch.arena, "ppswap/%F.swp", FmtString(name));
wchar_t *path_wstr = WstrFromString(scratch.arena, path);
HANDLE handle = CreateFileW(path_wstr, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (handle != INVALID_HANDLE_VALUE)
{
u32 chunk_size = Kibi(64);
result.text = ArenaNext(arena, u8);
for (;;)
{
u8 *chunk = PushStructsNoZero(arena, u8, chunk_size);
u32 chunk_bytes_read = 0;
ReadFile(handle, chunk, chunk_size, (LPDWORD)&chunk_bytes_read, 0);
result.len += chunk_bytes_read;
if (chunk_bytes_read < chunk_size)
{
PopStructsNoCopy(arena, u8, chunk_size - chunk_bytes_read);
break;
}
}
}
CloseHandle(handle);
EndScratch(scratch);
return result;
}
void WriteSwappedState(String name, String data)
{
TempArena scratch = BeginScratchNoConflict();
/* TODO: Use directory non-relative to executable */
CreateDirectoryW(L"ppswap", 0);
String result = ZI;
String path = StringF(scratch.arena, "ppswap/%F.swp", FmtString(name));
wchar_t *path_wstr = WstrFromString(scratch.arena, path);
HANDLE handle = CreateFileW(path_wstr, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
if (handle != INVALID_HANDLE_VALUE)
{
SetFilePointer(handle, 0, 0, FILE_BEGIN);
SetEndOfFile(handle);
WriteFile(handle, data.text, data.len, 0, 0);
}
CloseHandle(handle);
EndScratch(scratch);
}
////////////////////////////////////////////////////////////
//~ @hookimpl Exit
void OnExit(ExitFunc *func)
{
i32 index = Atomic32FetchAdd(&W32.num_exit_funcs, 1);
if (index >= countof(W32.exit_funcs))
{
Panic(Lit("Maximum on exit functions registered"));
}
W32.exit_funcs[index] = func;
}
void SignalExit(i32 code)
{
Atomic32Set(&W32.exit_code, code);
SetEvent(W32.exit_event);
}
void ExitNow(i32 code)
{
ExitProcess(code);
}
////////////////////////////////////////////////////////////
//~ Log
void W32_BootstrapLogs(String logfile_path)
{
W32.logs_arena = AcquireArena(Gibi(64));
W32.log_msgs_arena = AcquireArena(Gibi(64));
W32.readable_log_events = ArenaNext(W32.logs_arena, LogEvent);
if (logfile_path.len > 0)
{
TempArena scratch = BeginScratchNoConflict();
{
wchar_t *path_wstr = WstrFromString(scratch.arena, logfile_path);
W32.logfile = CreateFileW(path_wstr,
FILE_APPEND_DATA,
FILE_SHARE_READ | FILE_SHARE_WRITE,
0,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
0);
}
EndScratch(scratch);
}
Atomic32Set(&W32.logs_initialized, 1);
}
void W32_Log(i32 level, String msg)
{
TempArena scratch = BeginScratchNoConflict();
if (Atomic32Fetch(&W32.logs_initialized))
{
LogLevelSettings settings = log_settings[level];
if (level < 0 || level >= LogLevel_Count)
{
Panic(Lit("Invalid log level"));
}
DateTime datetime = LocalDateTime();
i64 now_ns = TimeNs();
i32 thread_id = GetCurrentThreadId();
//- Log message to file
/* TODO: Log asynchronously */
{
String shorthand = settings.shorthand;
String msg_formatted = StringF(
scratch.arena,
"[%F:%F:%F.%F] <%F> [%F] %F\n",
/* Time */
FmtUint(datetime.hour, .z = 2),
FmtUint(datetime.minute, .z = 2),
FmtUint(datetime.second, .z = 2),
FmtUint(datetime.milliseconds, .z = 3),
/* Thread id */
FmtUint(thread_id, .z = 5),
/* Level */
FmtString(shorthand),
/* Message */
FmtString(msg)
);
WriteFile(W32.logfile, msg_formatted.text, msg_formatted.len, 0, 0);
}
//- Log message to queue
/* TODO: Log asynchronously */
LockTicketMutex(&W32.logs_tm);
{
/* Get staged data */
LogEvent *ev = PushStruct(W32.logs_arena, LogEvent);
ev->msg = PushString(W32.log_msgs_arena, msg);
ev->datetime = datetime;
ev->time_ns = now_ns;
ev->level = level;
ev->thread_id = thread_id;
ev->id = W32.logs_count++;
ev->level_id = W32.log_level_counts[level]++;
Atomic64Set(&W32.readable_logs_count, W32.logs_count);
}
UnlockTicketMutex(&W32.logs_tm);
}
EndScratch(scratch);
}
////////////////////////////////////////////////////////////
//~ @hookimpl Log
/* Panic log function is separate to enforce zero side effects other than
* immediately writing to log file. */
void LogPanic(String msg)
{
if (Atomic32Fetch(&W32.logs_initialized))
{
String beg = Lit("******** PANICKING ********\n");
String end = Lit("\n***************************\n");
WriteFile(W32.logfile, beg.text, beg.len, 0, 0);
WriteFile(W32.logfile, msg.text, msg.len, 0, 0);
WriteFile(W32.logfile, end.text, end.len, 0, 0);
}
}
void Log_(i32 level, String msg)
{
W32_Log(level, msg);
}
void LogF_(i32 level, String fmt, ...)
{
if (Atomic32Fetch(&W32.logs_initialized))
{
TempArena scratch = BeginScratchNoConflict();
va_list args;
va_start(args, fmt);
{
String msg = FormatString(scratch.arena, fmt, FmtArgsFromVaList(scratch.arena, args));
W32_Log(level, msg);
}
va_end(args);
EndScratch(scratch);
}
}
LogEventsArray GetLogEvents(void)
{
LogEventsArray result = ZI;
result.count = Atomic64Fetch(&W32.readable_logs_count);
if (result.count > 0)
{
result.logs = W32.readable_log_events;
}
return result;
}
////////////////////////////////////////////////////////////
//~ Main
i32 W32_Main(void)
{
/* Init time */
{
LARGE_INTEGER qpf;
QueryPerformanceFrequency(&qpf);
W32.ns_per_qpc = 1000000000 / qpf.QuadPart;
}
{
LARGE_INTEGER qpc;
QueryPerformanceCounter(&qpc);
W32.timer_start_qpc = qpc.QuadPart;
}
/* Setup events */
W32.panic_event = CreateEventW(0, 1, 0, 0);
W32.exit_event = CreateEventW(0, 1, 0, 0);
W32.main_thread_id = GetCurrentThreadId();
SetThreadDescription(GetCurrentThread(), L"Main thread");
/* Query system info */
GetSystemInfo(&W32.info);
/* Init main thread */
W32_InitCurrentThread(Lit("Main"));
/* Get raw args from command line */
{
Arena *perm = PermArena();
StringList args_list = ZI;
{
LPCWSTR cmdline_wstr = GetCommandLineW();
i32 argc = 0;
LPWSTR *argv = CommandLineToArgvW(cmdline_wstr, &argc);
for (i32 i = 0; i < argc; ++i)
{
wchar_t *arg_wstr = argv[i];
String arg = StringFromWstrNoLimit(perm, arg_wstr);
PushStringToList(perm, &args_list, arg);
}
}
W32.raw_command_line = args_list;
}
//////////////////////////////
//- Bootstrap
/* Bootstrap command line */
BootstrapCmdline();
/* Bootstrap log system */
/* FIXME: Remove hardcoded log path */
W32_BootstrapLogs(Lit("log.log"));
LogInfoF("Main thread ID: %F", FmtUint(ThreadId()));
/* Bootstrap resource system */
{
W32_FindEmbeddedDataCtx ctx = ZI;
EnumResourceNamesW(0, RT_RCDATA, &W32_FindEmbeddedRcData, (LONG_PTR)&ctx);
BootstrapResources(ctx.embedded_strings_count, ctx.embedded_strings);
}
/* Bootstrap async */
BootstrapAsync();
/* Bootstrap layers */
if (!Atomic32Fetch(&W32.panicking))
{
BootstrapLayers();
}
//////////////////////////////
//- Wait for exit signal
/* Wait for exit start or panic */
if (!Atomic32Fetch(&W32.panicking))
{
HANDLE handles[] = {
W32.exit_event,
W32.panic_event,
};
DWORD wake = WaitForMultipleObjects(countof(handles), handles, 0, INFINITE);
}
//////////////////////////////
//- Shutdown
/* Run exit callbacks */
if (!Atomic32Fetch(&W32.panicking))
{
i32 num_funcs = Atomic32Fetch(&W32.num_exit_funcs);
for (i32 idx = num_funcs - 1; idx >= 0; --idx)
{
ExitFunc *func = W32.exit_funcs[idx];
func();
}
}
/* Exit */
if (Atomic32Fetch(&W32.panicking))
{
WaitForSingleObject(W32.panic_event, INFINITE);
MessageBoxExW(0, W32.panic_wstr, L"Fatal error", MB_ICONSTOP | MB_SETFOREGROUND | MB_TOPMOST, 0);
Atomic32FetchTestSet(&W32.exit_code, 0, 1);
}
return Atomic32Fetch(&W32.exit_code);
}
////////////////////////////////////////////////////////////
//~ Crt main
#if IsCrtlibEnabled
# if IsConsoleApp
int main(char **argc, int argv)
{
return W32_Main();
}
# else
int CALLBACK wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev_instance, _In_ LPWSTR cmdline_wstr, _In_ int show_code)
{
return W32_Main();
}
# endif /* IsConsoleApp */
#endif /* IsCrtlibEnabled */
////////////////////////////////////////////////////////////
//~ Crt stub
#if !IsCrtlibEnabled
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wmissing-variable-declarations"
#pragma clang diagnostic ignored "-Wmissing-prototypes"
/* Enable floating point */
__attribute((used))
int _fltused;
__attribute((used))
void __stdcall wWinMainCRTStartup(void)
{
i32 result = W32_Main();
ExitProcess(result);
}
#pragma clang diagnostic pop
#endif /* !IsCrtlibEnabled */