From 514c2a64964a4ae7fad6fb3d595484aa3a44005b Mon Sep 17 00:00:00 2001 From: jacob Date: Thu, 3 Jul 2025 19:53:59 -0500 Subject: [PATCH] fibers wip --- build.c | 1 - profile.bat | 63 - src/app.c | 9 +- src/common.h | 9 + src/config.h | 16 - src/gp_dx12.c | 2 +- src/host.c | 2 +- src/job.c | 2 +- src/prof_tracy.h | 18 +- src/resource.c | 4 +- src/sprite.c | 2 +- src/sys.h | 42 +- src/sys_win32-old.c | 2902 ------------------------------------------- src/sys_win32.c | 480 ++++++- src/ttf_dwrite.cpp | 2 - src/user.c | 43 +- 16 files changed, 521 insertions(+), 3076 deletions(-) delete mode 100644 profile.bat delete mode 100644 src/sys_win32-old.c diff --git a/build.c b/build.c index 1fa87313..9ccf87c5 100644 --- a/build.c +++ b/build.c @@ -843,7 +843,6 @@ void OnBuild(StringList cli_args) StringBeginsWith(name, Lit("ttf_"))) { if (PlatformWindows) { ignore = !(StringEqual(name, Lit("sys_win32.c")) || - StringEqual(name, Lit("sys_win32-old.c")) || StringEqual(name, Lit("sock_win32.c")) || StringEqual(name, Lit("gp_dx12.c")) || StringEqual(name, Lit("playback_wasapi.c")) || diff --git a/profile.bat b/profile.bat deleted file mode 100644 index 873aebd2..00000000 --- a/profile.bat +++ /dev/null @@ -1,63 +0,0 @@ -@echo off -setlocal enabledelayedexpansion - -:: Unpack arguments -for %%a in (%*) do set "%%a=1" - -set app_path=%1 - -if NOT "%escalate%" == "1" goto skipEscalate -:: This enables tracy sampling by running the executable in administrator mode. -:: BatchGotAdmin -:: https://stackoverflow.com/a/10052222 -:------------------------------------- -:: --> Check for permissions -if "%PROCESSOR_ARCHITECTURE%" EQU "amd64" ( - >nul 2>&1 "%SYSTEMROOT%\SysWOW64\cacls.exe" "%SYSTEMROOT%\SysWOW64\config\system" -) else ( - >nul 2>&1 "%SYSTEMROOT%\system32\cacls.exe" "%SYSTEMROOT%\system32\config\system" -) - -:: --> If error flag set, we do not have admin. -if '%errorlevel%' NEQ '0' ( - echo Requesting administrative privileges... - goto UACPrompt -) else ( goto gotAdmin ) - -:UACPrompt - echo Set UAC = CreateObject^("Shell.Application"^) > "%temp%\getadmin.vbs" - set params= %* - echo UAC.ShellExecute "cmd.exe", "/c ""%~s0"" %params:"=""%", "", "runas", 1 >> "%temp%\getadmin.vbs" - - "%temp%\getadmin.vbs" - del "%temp%\getadmin.vbs" - exit /B - -:gotAdmin - pushd "%CD%" - CD /D "%~dp0" -:-------------------------------------- -:skipEscalate - -:: `ping` is being used in place of `TIMEOUT` -:: https://www.ibm.com/support/pages/timeout-command-run-batch-job-exits-immediately-and-returns-error-input-redirection-not-supported-exiting-process-immediately - -taskkill /im tracy-profiler.exe /f 2> nul - -start tracy-capture.exe -o .tracy -f - -echo Launching app "%app_path%"... -%app_path% - -if NOT %errorlevel% == 0 ( - echo. - echo Program failed - pause - exit /b 1 -) - -:: Give time for trace file to finish before opening tracy -ping -n 2 127.0.0.1 >NUL - -echo Launching tracy... -start "" "tracy-profiler.exe" ".tracy" diff --git a/src/app.c b/src/app.c index 623b7d69..da5123ba 100644 --- a/src/app.c +++ b/src/app.c @@ -247,17 +247,16 @@ void sys_app_entry(struct string args_str) struct string *worker_names = arena_push_array(scratch.arena, struct string, worker_count); for (i32 i = 0; i < worker_count; ++i) { - struct string prefix = string_from_int(scratch.arena, worker_count - i, 10, 2); /* For profiler sorting order */ struct string id = string_from_int(scratch.arena, i, 10, 2); struct string *name = &worker_names[i]; if (i == APP_DEDICATED_WORKER_ID_USER) { - *name = string_format(scratch.arena, LIT("[W%F] Worker #%F (User)"), FMT_STR(prefix), FMT_STR(id)); + *name = string_format(scratch.arena, LIT("Worker #%F (User)"), FMT_STR(id)); } else if (i == APP_DEDICATED_WORKER_ID_SIM) { - *name = string_format(scratch.arena, LIT("[W%F] Worker #%F (Sim)"), FMT_STR(prefix), FMT_STR(id)); + *name = string_format(scratch.arena, LIT("Worker #%F (Sim)"), FMT_STR(id)); } else if (i == APP_DEDICATED_WORKER_ID_AUDIO) { - *name = string_format(scratch.arena, LIT("[W%F] Worker #%F (Audio)"), FMT_STR(prefix), FMT_STR(id)); + *name = string_format(scratch.arena, LIT("Worker #%F (Audio)"), FMT_STR(id)); } else { - *name = string_format(scratch.arena, LIT("[W%F] Worker #%F"), FMT_STR(prefix), FMT_STR(id)); + *name = string_format(scratch.arena, LIT("Worker #%F"), FMT_STR(id)); } } diff --git a/src/common.h b/src/common.h index 26155e3e..22aeeb2e 100644 --- a/src/common.h +++ b/src/common.h @@ -656,6 +656,15 @@ INLINE f64 clamp_f64(f64 v, f64 min, f64 max) { return v < min ? min : v > max ? #include "prof_tracy.h" +#define PROF_THREAD_GROUP_RUNNERS -8000 +#define PROF_THREAD_GROUP_FIBERS -7000 +#define PROF_THREAD_GROUP_WORKERS -6000 +#define PROF_THREAD_GROUP_IO -5 +#define PROF_THREAD_GROUP_WINDOW -4 +#define PROF_THREAD_GROUP_EVICTORS -3 +#define PROF_THREAD_GROUP_APP -2 +#define PROF_THREAD_GROUP_MAIN -1 + #ifdef __cplusplus } #endif diff --git a/src/config.h b/src/config.h index ced28b19..e3bee223 100644 --- a/src/config.h +++ b/src/config.h @@ -76,22 +76,6 @@ /* If enabled, things like network writes & memory allocations will be tracked in a global statistics struct */ #define GSTAT_ENABLED 1 - - - - - - - -#define FIBERS_TEST 1 - - - - - - - - /* ========================== * * Settings * ========================== */ diff --git a/src/gp_dx12.c b/src/gp_dx12.c index 2a5674dd..3f9aaaea 100644 --- a/src/gp_dx12.c +++ b/src/gp_dx12.c @@ -390,7 +390,7 @@ struct gp_startup_receipt gp_startup(void) /* Start evictor thread */ G.evictor_thread_wake_event = CreateEvent(NULL, false, false, NULL); - G.evictor_thread = sys_thread_alloc(evictor_thread_entry_point, NULL, LIT("[P2] GPU evictor thread")); + G.evictor_thread = sys_thread_alloc(evictor_thread_entry_point, NULL, LIT("GPU resource evictor thread"), PROF_THREAD_GROUP_EVICTORS); struct gp_startup_receipt res = ZI; return res; diff --git a/src/host.c b/src/host.c index e7e80626..37af1dca 100644 --- a/src/host.c +++ b/src/host.c @@ -199,7 +199,7 @@ struct host *host_alloc(u16 listen_port) host->sock = sock_alloc(listen_port, MEGABYTE(2), MEGABYTE(2)); host->rcv_buffer_write_mutex = sys_mutex_alloc(); - host->receiver_thread = sys_thread_alloc(&host_receiver_thread_entry_point, host, LIT("[P5] Host receiver")); + host->receiver_thread = sys_thread_alloc(&host_receiver_thread_entry_point, host, LIT("Host receiver"), PROF_THREAD_GROUP_IO); return host; } diff --git a/src/job.c b/src/job.c index ae87e7ed..3d6f1181 100644 --- a/src/job.c +++ b/src/job.c @@ -95,7 +95,7 @@ void job_startup(i32 num_workers, struct string *worker_names) G.num_worker_threads = num_workers; for (i32 i = 0; i < G.num_worker_threads; ++i) { struct string name = worker_names[i]; - G.worker_threads[i] = sys_thread_alloc(worker_thread_entry_point, (void *)(i64)i, name); + G.worker_threads[i] = sys_thread_alloc(worker_thread_entry_point, (void *)(i64)i, name, PROF_THREAD_GROUP_WORKERS + i); } atomic_i32_fetch_set(&G.num_idle_worker_threads, num_workers); diff --git a/src/prof_tracy.h b/src/prof_tracy.h index c48a8fc4..6be530cb 100644 --- a/src/prof_tracy.h +++ b/src/prof_tracy.h @@ -35,11 +35,11 @@ #define __profscope(name) static const struct ___tracy_source_location_data CAT(__tracy_source_location,__LINE__) = { #name, __func__, __FILE__, (uint32_t)__LINE__, 0 }; __attribute((cleanup(__prof_zone_cleanup_func))) TracyCZoneCtx __tracy_zone_ctx = ___tracy_emit_zone_begin( &CAT(__tracy_source_location,__LINE__), true ) INLINE void __prof_zone_cleanup_func(TracyCZoneCtx *ctx) { TracyCZoneEnd(*ctx) } -#define __profalloc(ptr, size) TracyCAlloc((ptr), (size)) -#define __proffree(ptr) TracyCFree((ptr)) -#define __profmsg(txt, len, col) TracyCMessageC((txt), (len), BGR32(col)) -#define __profframe(name) TracyCFrameMarkNamed((name)) -#define __profthread(name) TracyCSetThreadName((name)) +#define __profalloc(ptr, size) TracyCAlloc((ptr), (size)) +#define __proffree(ptr) TracyCFree((ptr)) +#define __profmsg(txt, len, col) TracyCMessageC((txt), (len), BGR32(col)) +#define __profframe(name) TracyCFrameMarkNamed((name)) +#define __profthread(name, group_hint) TracyCSetThreadNameWithHint((name), (group_hint)) enum __prof_plot_type { __prof_plot_type_number = TracyPlotFormatNumber, @@ -64,7 +64,7 @@ enum __prof_plot_type { #define __proffree(ptr) #define __profmsg(txt, len, col) #define __profframe(name) -#define __profthread(name) +#define __profthread(name, group_hint) #define __prof_plot_init(name, type, step, fill, color) #define __prof_plot(name, val) #define __prof_plot_i(name, val) @@ -136,10 +136,10 @@ INLINE void __prof_dx12_zone_cleanup_func(TracyCD3D12ZoneCtx *ctx) { ___tracy_d3 #endif /* PROFILING_CAPTURE_FRAME_IMAGE */ #ifdef TRACY_FIBERS -# define __prof_fiber_enter(fiber_name) ___tracy_fiber_enter(fiber_name) -# define __prof_fiber_leave ___tracy_fiber_leave() +# define __prof_fiber_enter(fiber_name, profiler_group) TracyCFiberEnterWithHint(fiber_name, profiler_group) +# define __prof_fiber_leave TracyCFiberLeave #else -# define __prof_fiber_enter(fiber_name) +# define __prof_fiber_enter(fiber_name, profiler_group) # define __prof_fiber_leave #endif diff --git a/src/resource.c b/src/resource.c index 101f444f..4093e257 100644 --- a/src/resource.c +++ b/src/resource.c @@ -77,8 +77,8 @@ struct resource_startup_receipt resource_startup(void) G.watch_dispatcher_cv = sys_condition_variable_alloc(); app_register_exit_callback(&resource_shutdown); - G.resource_watch_monitor_thread = sys_thread_alloc(resource_watch_monitor_thread_entry_point, NULL, LIT("[P2] Resource watch monitor")); - G.resource_watch_dispatch_thread = sys_thread_alloc(resource_watch_dispatcher_thread_entry_point, NULL, LIT("[P2] Resource watch dispatcher")); + G.resource_watch_monitor_thread = sys_thread_alloc(resource_watch_monitor_thread_entry_point, NULL, LIT("Resource watch monitor"), PROF_THREAD_GROUP_IO); + G.resource_watch_dispatch_thread = sys_thread_alloc(resource_watch_dispatcher_thread_entry_point, NULL, LIT("Resource watch dispatcher"), PROF_THREAD_GROUP_IO); #endif diff --git a/src/sprite.c b/src/sprite.c index 7bc162ba..b28e594d 100644 --- a/src/sprite.c +++ b/src/sprite.c @@ -257,7 +257,7 @@ struct sprite_startup_receipt sprite_startup(struct gp_startup_receipt *gp_sr, G.evictor_scheduler_mutex = sys_mutex_alloc(); G.evictor_scheduler_shutdown_cv = sys_condition_variable_alloc(); - G.evictor_scheduler_thread = sys_thread_alloc(sprite_evictor_scheduler_thread_entry_point, NULL, LIT("[P2] Sprite evictor scheduler")); + G.evictor_scheduler_thread = sys_thread_alloc(sprite_evictor_scheduler_thread_entry_point, NULL, LIT("Sprite evictor scheduler"), PROF_THREAD_GROUP_EVICTORS); app_register_exit_callback(&sprite_shutdown); resource_register_watch_callback(&sprite_resource_watch_callback); diff --git a/src/sys.h b/src/sys.h index 7dd4f3c5..043c494f 100644 --- a/src/sys.h +++ b/src/sys.h @@ -396,8 +396,6 @@ void sys_condition_variable_broadcast(struct sys_condition_variable *sys_cv); * Threads * ========================== */ -#define SYS_THREAD_STACK_SIZE MEGABYTE(4) - #define SYS_THREAD_DEF(name, arg_name) void name(void *arg_name) typedef SYS_THREAD_DEF(sys_thread_func, data); @@ -405,7 +403,8 @@ typedef SYS_THREAD_DEF(sys_thread_func, data); struct sys_thread *sys_thread_alloc( sys_thread_func *entry_point, void *thread_data, /* Passed as arg to `entry_point` */ - struct string thread_name + struct string thread_name, + i32 profiler_group ); void sys_thread_wait_release(struct sys_thread *thread); @@ -489,6 +488,30 @@ b32 sys_run_command(struct string cmd); i32 sys_current_fiber_id(void); +void sys_yield(void); + +/* ========================== * + * Job + * ========================== */ + +enum sys_job_priority { + SYS_JOB_PRIORITY_HIGH = 0, + SYS_JOB_PRIORITY_NORMAL = 1, + SYS_JOB_PRIORITY_BACKGROUND = 2, + + NUM_SYS_JOB_PRIORITIES +}; + +struct sys_job_data { + i32 id; + void *sig; +}; + +#define SYS_JOB_DEF(job_name, arg_name) void job_name(struct sys_job_data arg_name) +typedef SYS_JOB_DEF(sys_job_func, job_data); + +void sys_run(i32 count, sys_job_func *func, enum sys_job_priority priority); + /* ========================== * * Scratch context * ========================== */ @@ -501,19 +524,6 @@ struct sys_scratch_ctx { struct sys_scratch_ctx *sys_scratch_ctx_from_fiber_id(i32 fiber_id); -/* ========================== * - * Job - * ========================== */ - -struct sys_job_data { - i32 id; - void *sig; -}; - -#define SYS_JOB_DEF(job_name, arg_name) void job_name(struct sys_job_data arg_name) -typedef SYS_JOB_DEF(sys_job_func, job_data); - - /* ========================== * * App entry point * ========================== */ diff --git a/src/sys_win32-old.c b/src/sys_win32-old.c deleted file mode 100644 index 3c21a0dd..00000000 --- a/src/sys_win32-old.c +++ /dev/null @@ -1,2902 +0,0 @@ -#if !FIBERS_TEST - -#include "sys.h" -#include "memory.h" -#include "app.h" -#include "string.h" -#include "arena.h" -#include "scratch.h" -#include "atomic.h" -#include "log.h" -#include "math.h" -#include "util.h" -#include "thread_local.h" -#include "uni.h" - -#pragma warning(push, 0) -# define UNICODE -# include -# include -# include -# include -# include -# include -#pragma warning(pop) - -#pragma comment(lib, "kernel32") -#pragma comment(lib, "user32") -#pragma comment(lib, "shell32") -#pragma comment(lib, "ole32") -#pragma comment(lib, "winmm") -#pragma comment(lib, "dwmapi") -#pragma comment(lib, "bcrypt") - -#define SYS_WINDOW_EVENT_LISTENERS_MAX 512 -#define WINDOW_CLASS_NAME L"power_play_window_class" - -struct win32_mutex { - SRWLOCK srwlock; - struct win32_mutex *next_free; - -#if PROFILING_LOCKS - __proflock_ctx(profiling_ctx); -#endif - -#if RTC - u64 owner_tid; - struct atomic_i64 count; -#endif -}; - -struct win32_condition_variable { - CONDITION_VARIABLE condition_variable; - struct win32_condition_variable *next_free; -#if RTC - struct atomic_i64 num_waiters; -#endif -}; - -struct win32_thread { - sys_thread_func *entry_point; - void *thread_data; - char thread_name_cstr[256]; - wchar_t thread_name_wstr[256]; - - struct win32_thread *next; - struct win32_thread *prev; - - HANDLE handle; -}; - -enum win32_window_cursor_set_flag { - WIN32_WINDOW_CURSOR_SET_FLAG_NONE = 0x0, - WIN32_WINDOW_CURSOR_SET_FLAG_POSITION = 0x1, - WIN32_WINDOW_CURSOR_SET_FLAG_HIDE = 0x2, - WIN32_WINDOW_CURSOR_SET_FLAG_SHOW = 0x4, - WIN32_WINDOW_CURSOR_SET_FLAG_ENABLE_CLIP = 0X8, - WIN32_WINDOW_CURSOR_SET_FLAG_DISABLE_CLIP = 0X10 -}; - -struct win32_window { - u32 flags; - - HWND hwnd; - u32 tid; - struct sync_flag ready_sf; - - u16 utf16_high_surrogate_last_input; - - struct sys_mutex *settings_mutex; - struct sys_window_settings settings; - - i32 monitor_width; - i32 monitor_height; - /* NOTE: width & height are unaffected by window minimization (they retain - * their pre-minimized values) */ - i32 x, y, width, height; - - /* FIXME: Use a cmd buffer for updating cursor (and maybe also settings). - * Current setup means cursor_set calls can be applied out of order */ - u32 cursor_set_flags; - struct v2 cursor_set_position; - struct rect cursor_clip_bounds; - - struct atomic_i32 event_thread_shutdown; - struct sys_thread *event_thread; - - struct sys_mutex *event_callbacks_mutex; - sys_window_event_callback_func *event_callbacks[SYS_WINDOW_EVENT_LISTENERS_MAX]; - u64 event_callbacks_count; - - struct win32_window *next_free; -}; - -/* ========================== * - * Global state - * ========================== */ - -GLOBAL struct { - SYSTEM_INFO info; - i64 timer_start_qpc; - i64 qpc_per_second; - i64 ns_per_qpc; - i32 scheduler_period_ms; - DWORD thread_tls_index; - u32 main_thread_id; - - wchar_t cmdline_args_wstr[8192]; - - /* Panic */ - struct atomic_i32 panicking; - wchar_t panic_wstr[4096]; - HANDLE panic_event; - - /* Lookup tables */ - enum sys_btn vk_btn_table[256]; - - /* Mutexes pool */ - struct sys_mutex *mutexes_mutex; - struct arena *mutexes_arena; - struct win32_mutex *first_free_mutex; - - /* Condition variables pool */ - struct sys_mutex *condition_variables_mutex; - struct arena *condition_variables_arena; - struct win32_condition_variable *first_free_condition_variable; - - /* Threads pool */ - struct sys_mutex *threads_mutex; - struct arena *threads_arena; - struct win32_thread *threads_first; - struct win32_thread *threads_last; - struct win32_thread *threads_first_free; - - /* Watches pool */ - struct sys_mutex *watches_mutex; - struct arena *watches_arena; - struct win32_watch *watches_first_free; - - /* Windows pool */ - WNDCLASSEXW window_class; - struct sys_mutex *windows_mutex; - struct arena *windows_arena; - struct win32_window *first_free_window; -} G = ZI, DEBUG_ALIAS(G, G_sys_win32); - -/* ========================== * - * Events - * ========================== */ - -INTERNAL void win32_init_vk_btn_table(void) -{ - MEMZERO_ARRAY(G.vk_btn_table); - - for (u32 i = 'A', j = SYS_BTN_A; i <= 'Z'; ++i, ++j) { - G.vk_btn_table[i] = (enum sys_btn)j; - } - for (u32 i = '0', j = SYS_BTN_0; i <= '9'; ++i, ++j) { - G.vk_btn_table[i] = (enum sys_btn)j; - } - for (u32 i = VK_F1, j = SYS_BTN_F1; i <= VK_F24; ++i, ++j) { - G.vk_btn_table[i] = (enum sys_btn)j; - } - - G.vk_btn_table[VK_ESCAPE] = SYS_BTN_ESC; - G.vk_btn_table[VK_OEM_3] = SYS_BTN_GRAVE_ACCENT; - G.vk_btn_table[VK_OEM_MINUS] = SYS_BTN_MINUS; - G.vk_btn_table[VK_OEM_PLUS] = SYS_BTN_EQUAL; - G.vk_btn_table[VK_BACK] = SYS_BTN_BACKSPACE; - G.vk_btn_table[VK_TAB] = SYS_BTN_TAB; - G.vk_btn_table[VK_SPACE] = SYS_BTN_SPACE; - G.vk_btn_table[VK_RETURN] = SYS_BTN_ENTER; - G.vk_btn_table[VK_CONTROL] = SYS_BTN_CTRL; - G.vk_btn_table[VK_SHIFT] = SYS_BTN_SHIFT; - G.vk_btn_table[VK_MENU] = SYS_BTN_ALT; - G.vk_btn_table[VK_UP] = SYS_BTN_UP; - G.vk_btn_table[VK_LEFT] = SYS_BTN_LEFT; - G.vk_btn_table[VK_DOWN] = SYS_BTN_DOWN; - G.vk_btn_table[VK_RIGHT] = SYS_BTN_RIGHT; - G.vk_btn_table[VK_DELETE] = SYS_BTN_DELETE; - G.vk_btn_table[VK_PRIOR] = SYS_BTN_PAGE_UP; - G.vk_btn_table[VK_NEXT] = SYS_BTN_PAGE_DOWN; - G.vk_btn_table[VK_HOME] = SYS_BTN_HOME; - G.vk_btn_table[VK_END] = SYS_BTN_END; - G.vk_btn_table[VK_OEM_2] = SYS_BTN_FORWARD_SLASH; - G.vk_btn_table[VK_OEM_PERIOD] = SYS_BTN_PERIOD; - G.vk_btn_table[VK_OEM_COMMA] = SYS_BTN_COMMA; - G.vk_btn_table[VK_OEM_7] = SYS_BTN_QUOTE; - G.vk_btn_table[VK_OEM_4] = SYS_BTN_LEFT_BRACKET; - G.vk_btn_table[VK_OEM_6] = SYS_BTN_RIGHT_BRACKET; - G.vk_btn_table[VK_INSERT] = SYS_BTN_INSERT; - G.vk_btn_table[VK_OEM_1] = SYS_BTN_SEMICOLON; -} - -/* ========================== * - * Memory - * ========================== */ - -void *sys_memory_reserve(u64 size) -{ - void *ptr = VirtualAlloc(NULL, size, MEM_RESERVE, PAGE_NOACCESS); - return ptr; -} - -void sys_memory_release(void *address) -{ - VirtualFree(address, 0, MEM_RELEASE); -} - -void *sys_memory_commit(void *address, u64 size) -{ - void *ptr = VirtualAlloc(address, size, MEM_COMMIT, PAGE_READWRITE); - return ptr; -} - -void sys_memory_decommit(void *address, u64 size) -{ - VirtualFree(address, size, MEM_DECOMMIT); -} - -void sys_memory_set_committed_readonly(void *address, u64 size) -{ - DWORD old; - VirtualProtect(address, size, PAGE_READONLY, &old); -} - -void sys_memory_set_committed_readwrite(void *address, u64 size) -{ - DWORD old; - VirtualProtect(address, size, PAGE_READWRITE, &old); -} - -/* ========================== * - * Time - * ========================== */ - -INTERNAL struct sys_datetime win32_time_to_sys_time(SYSTEMTIME st) -{ - return (struct sys_datetime) - { - .year = st.wYear, - .month = st.wMonth, - .day_of_week = st.wDayOfWeek, - .day = st.wDay, - .hour = st.wHour, - .minute = st.wMinute, - .second = st.wSecond, - .milliseconds = st.wMilliseconds - }; -} - -struct sys_datetime sys_local_time(void) -{ - SYSTEMTIME lt; - GetLocalTime(<); - return win32_time_to_sys_time(lt); -} - -i64 sys_time_ns(void) -{ - LARGE_INTEGER qpc; - QueryPerformanceCounter(&qpc); - i64 res = (qpc.QuadPart - G.timer_start_qpc) * G.ns_per_qpc; - return res; -} - -/* ========================== * - * File system - * ========================== */ - -INTERNAL struct string string_from_win32_path(struct arena *arena, wchar_t *src) -{ - struct string res = { - .len = 0, - .text = arena_push_dry(arena, u8) - }; - - while (*src) { - struct string16 decode_str = { .len = *(src + 1) ? 2 : 1, .text = src }; - struct uni_decode_utf16_result decoded = uni_decode_utf16(decode_str); - struct uni_encode_utf8_result encoded = uni_encode_utf8(decoded.codepoint); - u8 *dest = arena_push_array_no_zero(arena, u8, encoded.count8); - for (u32 i = 0; i < encoded.count8; ++i) { - u8 byte = encoded.chars8[i]; - if (byte == '\\') { - byte = '/'; - } - dest[i] = byte; - } - res.len += encoded.count8; - src += decoded.advance16; - } - - return res; -} - -struct string sys_get_write_path(struct arena *arena) -{ - u16 *p = NULL; - /* TODO: cache this? */ - HRESULT res = SHGetKnownFolderPath( - &FOLDERID_LocalAppData, - 0, - NULL, - &p - ); - struct string path = ZI; - if (res == S_OK) { - path = string_from_win32_path(arena, p); - } - CoTaskMemFree(p); - return path; -} - -b32 sys_is_file(struct string path) -{ - __prof; - struct arena_temp scratch = scratch_begin_no_conflict(); - wchar_t *path_wstr = wstr_from_string(scratch.arena, path); - DWORD attributes = GetFileAttributesW(path_wstr); - scratch_end(scratch); - return attributes != INVALID_FILE_ATTRIBUTES && !(attributes & FILE_ATTRIBUTE_DIRECTORY); -} - -b32 sys_is_dir(struct string path) -{ - struct arena_temp scratch = scratch_begin_no_conflict(); - wchar_t *path_wstr = wstr_from_string(scratch.arena, path); - DWORD attributes = GetFileAttributesW(path_wstr); - scratch_end(scratch); - return attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY); -} - -void sys_mkdir(struct string path) -{ - __prof; - struct arena_temp scratch = scratch_begin_no_conflict(); - wchar_t *path_wstr = wstr_from_string(scratch.arena, path); - int err_code = SHCreateDirectory(NULL, path_wstr); - struct string err = ZI; - switch (err_code) { - case ERROR_BAD_PATHNAME: { - err = LIT("Bad path name"); - } break; - - case ERROR_FILENAME_EXCED_RANGE: { - err = LIT("Path name too long"); - } break; - - case ERROR_FILE_EXISTS: { - err = LIT("A file already exists at this location"); - } break; - - case ERROR_CANCELLED: { - err = LIT("User canceled the operation"); - } break; - - default: break; - } - if (err.len > 0) { - struct string msg = string_format(scratch.arena, - LIT("Failed to create directory \"%F\": %F"), - FMT_STR(path), - FMT_STR(err)); - sys_panic(msg); - } - scratch_end(scratch); -} - -struct sys_file sys_file_open_read(struct string path) -{ - __prof; - struct arena_temp scratch = scratch_begin_no_conflict(); - struct sys_file file = ZI; - - wchar_t *path_wstr = wstr_from_string(scratch.arena, path); - HANDLE handle = CreateFileW( - path_wstr, - GENERIC_READ, - FILE_SHARE_READ, - NULL, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, - NULL - ); - file.handle = (u64)handle; - file.valid = handle != INVALID_HANDLE_VALUE; - - scratch_end(scratch); - return file; -} - -struct sys_file sys_file_open_read_wait(struct string path) -{ - __prof; - struct arena_temp scratch = scratch_begin_no_conflict(); - struct sys_file file = ZI; - - wchar_t *path_wstr = wstr_from_string(scratch.arena, path); - i32 delay_ms = 1; - HANDLE handle; - while ((handle = CreateFileW(path_wstr, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE) { - if (GetLastError() == ERROR_SHARING_VIOLATION) { - __profscope(File share conflict delay); - Sleep(delay_ms); - if (delay_ms < 1024) { - delay_ms *= 2; - } - } else { - break; - } - } - file.handle = (u64)handle; - file.valid = handle != INVALID_HANDLE_VALUE; - - scratch_end(scratch); - return file; -} - -struct sys_file sys_file_open_write(struct string path) -{ - __prof; - struct arena_temp scratch = scratch_begin_no_conflict(); - struct sys_file file = ZI; - - wchar_t *path_wstr = wstr_from_string(scratch.arena, path); - HANDLE handle = CreateFileW( - path_wstr, - GENERIC_WRITE, - 0, - NULL, - CREATE_ALWAYS, - FILE_ATTRIBUTE_NORMAL, - NULL - ); - file.handle = (u64)handle; - file.valid = handle != INVALID_HANDLE_VALUE; - - scratch_end(scratch); - return file; -} - -struct sys_file sys_file_open_append(struct string path) -{ - __prof; - struct arena_temp scratch = scratch_begin_no_conflict(); - struct sys_file file = ZI; - - wchar_t *path_wstr = wstr_from_string(scratch.arena, path); - HANDLE handle = CreateFileW( - path_wstr, - FILE_APPEND_DATA, - FILE_SHARE_READ, - NULL, - OPEN_ALWAYS, - FILE_ATTRIBUTE_NORMAL, - NULL - ); - file.handle = (u64)handle; - file.valid = handle != INVALID_HANDLE_VALUE; - - scratch_end(scratch); - return file; -} - -void sys_file_close(struct sys_file file) -{ - __prof; - if (file.handle) { - CloseHandle((HANDLE)file.handle); - } -} - -struct string sys_file_read_all(struct arena *arena, struct sys_file file) -{ - __prof; - i64 size = 0; - GetFileSizeEx((HANDLE)file.handle, (PLARGE_INTEGER)&size); - - struct string s = { - .len = size, - .text = NULL - }; - - if (size > 0) { - /* ReadFile returns non-zero on success */ - /* TODO: error checking */ - arena_align(arena, 16); - s.text = arena_push_array_no_zero(arena, u8, size); - (UNUSED)ReadFile( - (HANDLE)file.handle, - s.text, - (DWORD)s.len, - NULL, /* lpNumberOfBytesRead */ - NULL - ); - } - - return s; -} - -void sys_file_write(struct sys_file file, struct string data) -{ - __prof; - /* TODO: Check what the real data limit is and chunk sequentially based on - * that (rather than failing) */ - if (data.len >= 0x7FFF) { - struct arena_temp scratch = scratch_begin_no_conflict(); - sys_panic(string_format(scratch.arena, - LIT("Tried to write too many bytes to disk (%F)"), - FMT_UINT(data.len))); - scratch_end(scratch); - } - - /* WriteFile returns TRUE on success */ - (UNUSED)WriteFile( - (HANDLE)file.handle, - data.text, - (DWORD)data.len, - NULL, /* lpNumberOfBytesWritten */ - NULL - ); -} - -u64 sys_file_get_size(struct sys_file file) -{ - LARGE_INTEGER li_file_size; - GetFileSizeEx((HANDLE)file.handle, &li_file_size); - return (u64)(li_file_size.QuadPart > 0 ? li_file_size.QuadPart : 0); -} - -struct sys_file_time sys_file_get_time(struct sys_file file) -{ - __prof; - - /* Get file times */ - FILETIME ft_created; - FILETIME ft_accessed; - FILETIME ft_modified; - b32 success = !!GetFileTime((HANDLE)file.handle, &ft_created, &ft_accessed, &ft_modified); - if (success) { - /* Convert file times to local file time */ - FileTimeToLocalFileTime(&ft_created, &ft_created); - FileTimeToLocalFileTime(&ft_accessed, &ft_accessed); - FileTimeToLocalFileTime(&ft_modified, &ft_modified); - - /* Convert local file times to system times */ - SYSTEMTIME st_created; - SYSTEMTIME st_accessed; - SYSTEMTIME st_modified; - FileTimeToSystemTime(&ft_created, &st_created); - FileTimeToSystemTime(&ft_accessed, &st_accessed); - FileTimeToSystemTime(&ft_modified, &st_modified); - - return (struct sys_file_time) { - .created = win32_time_to_sys_time(st_created), - .accessed = win32_time_to_sys_time(st_accessed), - .modified = win32_time_to_sys_time(st_modified) - }; - } else { - return (struct sys_file_time) { 0 }; - } -} - -/* ========================== * - * File map - * ========================== */ - -struct sys_file_map sys_file_map_open_read(struct sys_file file) -{ - __prof; - struct sys_file_map map = ZI; - - u64 size = sys_file_get_size(file); - u8 *base_ptr = NULL; - HANDLE map_handle = 0; - - if (size > 0) { - map_handle = CreateFileMappingW( - (HANDLE)file.handle, - NULL, - PAGE_READONLY, - 0, - 0, - NULL - ); - if (map_handle != INVALID_HANDLE_VALUE) { - base_ptr = MapViewOfFile( - map_handle, - FILE_MAP_READ, - 0, - 0, - 0 - ); - if (base_ptr == NULL) { - /* Failed to create view */ - CloseHandle(map_handle); - map_handle = INVALID_HANDLE_VALUE; - } - } - } - if (map_handle == INVALID_HANDLE_VALUE) { - size = 0; - } - map.handle = (u64)map_handle; - map.mapped_memory = STRING(size, base_ptr); - map.valid = map_handle != INVALID_HANDLE_VALUE && base_ptr != NULL; - - - return map; -} - -void sys_file_map_close(struct sys_file_map map) -{ - if (map.mapped_memory.text) { - UnmapViewOfFile(map.mapped_memory.text); - } - if (map.handle) { - CloseHandle((HANDLE)map.handle); - } -} - -struct string sys_file_map_data(struct sys_file_map map) -{ - return map.mapped_memory; -} - -/* ========================== * - * File iter - * ========================== */ - -struct win32_file_filter { - HANDLE find_handle; - wchar_t *filter_wstr; -}; - -struct sys_file_filter sys_file_filter_begin(struct arena *arena, struct string pattern) -{ - struct sys_file_filter filter = ZI; - struct win32_file_filter *filter_internal = arena_push(arena, struct win32_file_filter); - filter_internal->filter_wstr = wstr_from_string(arena, pattern); - filter.handle = (u64)filter_internal; - return filter; -} - -b32 sys_file_filter_next(struct arena *arena, struct sys_file_filter *filter) -{ - struct win32_file_filter *filter_internal = (struct win32_file_filter *)filter->handle; - - WIN32_FIND_DATAW find_file_data = ZI; - b32 found = false; - - if (filter_internal->find_handle) { - found = FindNextFileW(filter_internal->find_handle, &find_file_data); - } else { - filter_internal->find_handle = FindFirstFileExW(filter_internal->filter_wstr, FindExInfoStandard, &find_file_data, FindExSearchNameMatch, NULL, FIND_FIRST_EX_CASE_SENSITIVE | FIND_FIRST_EX_LARGE_FETCH); - found = filter_internal->find_handle != INVALID_HANDLE_VALUE; - } - - if (found) { - struct string file_name = string_from_wstr_no_limit(arena, find_file_data.cFileName); - if (string_eq(file_name, LIT(".")) || string_eq(file_name, LIT(".."))) { - /* Skip initial '.' and '..' matches */ - found = sys_file_filter_next(arena, filter); - } else { - filter->info.is_dir = find_file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY; - filter->info.filename = string_copy(arena, file_name); - } - } - - return found; -} - -void sys_file_filter_end(struct sys_file_filter *filter) -{ - struct win32_file_filter *filter_internal = (struct win32_file_filter *)filter->handle; - if (filter_internal->find_handle) { - FindClose(filter_internal->find_handle); - } -} - -/* ========================== * - * Watch - * ========================== */ - -struct win32_watch { - HANDLE dir_handle; - HANDLE wake_handle; - struct win32_watch *next_free; - u8 results_buff[KILOBYTE(64)]; -}; - -struct sys_watch *sys_watch_alloc(struct string dir_path) -{ - struct arena_temp scratch = scratch_begin_no_conflict(); - - struct win32_watch *w32_watch = NULL; - { - struct sys_lock lock = sys_mutex_lock_e(G.watches_mutex); - { - if (G.watches_first_free) { - w32_watch = G.watches_first_free; - G.watches_first_free = w32_watch->next_free; - } else { - w32_watch = arena_push_no_zero(G.watches_arena, struct win32_watch); - } - } - sys_mutex_unlock(&lock); - } - MEMZERO_STRUCT(w32_watch); - - wchar_t *dir_path_wstr = wstr_from_string(scratch.arena, dir_path); - w32_watch->dir_handle = CreateFileW( - dir_path_wstr, - FILE_LIST_DIRECTORY, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - NULL, - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, - NULL - ); - - w32_watch->wake_handle = CreateEventW(NULL, FALSE, FALSE, NULL); - - scratch_end(scratch); - return (struct sys_watch *)w32_watch; -} - -void sys_watch_release(struct sys_watch *dw) -{ - struct win32_watch *w32_watch = (struct win32_watch *)dw; - CloseHandle(w32_watch->dir_handle); - CloseHandle(w32_watch->wake_handle); - - struct sys_lock lock = sys_mutex_lock_e(G.watches_mutex); - { - w32_watch->next_free = G.watches_first_free; - G.watches_first_free = w32_watch; - } - sys_mutex_unlock(&lock); -} - -struct sys_watch_info_list sys_watch_wait(struct arena *arena, struct sys_watch *dw) -{ - __prof; - struct win32_watch *w32_watch = (struct win32_watch *)dw; - struct sys_watch_info_list list = ZI; - - DWORD filter = FILE_NOTIFY_CHANGE_FILE_NAME | - FILE_NOTIFY_CHANGE_DIR_NAME | - FILE_NOTIFY_CHANGE_LAST_WRITE | - FILE_NOTIFY_CHANGE_CREATION; - - b32 done = false; - while (!done) { - OVERLAPPED ov = ZI; - ov.hEvent = CreateEventW(NULL, FALSE, FALSE, NULL); - ASSERT(ov.hEvent); - - BOOL success = ReadDirectoryChangesW(w32_watch->dir_handle, - w32_watch->results_buff, - countof(w32_watch->results_buff), - true, - filter, - NULL, - &ov, - NULL); - (UNUSED)success; - ASSERT(success); - - HANDLE handles[] = { - ov.hEvent, - w32_watch->wake_handle - }; - DWORD wait_res = WaitForMultipleObjects(2, handles, FALSE, INFINITE); - - if (wait_res == WAIT_OBJECT_0) { - i64 offset = 0; - while (!done) { - FILE_NOTIFY_INFORMATION *res = (FILE_NOTIFY_INFORMATION *)(w32_watch->results_buff + offset); - - struct sys_watch_info *info = arena_push(arena, struct sys_watch_info); - if (list.last) { - list.last->next = info; - info->prev = list.last; - } else { - list.first = info; - } - list.last = info; - ++list.count; - - struct string16 name16 = ZI; - name16.text = res->FileName; - name16.len = res->FileNameLength / sizeof(wchar_t); - - info->name = string_from_string16(arena, name16); - for (u64 i = 0; i < info->name.len; ++i) { - if (info->name.text[i] == '\\') { - info->name.text[i] = '/'; - } - } - - switch (res->Action) { - case FILE_ACTION_ADDED: - { - info->kind = SYS_WATCH_INFO_KIND_ADDED; - } break; - - case FILE_ACTION_REMOVED: - { - info->kind = SYS_WATCH_INFO_KIND_REMOVED; - } break; - - case FILE_ACTION_MODIFIED: - { - info->kind = SYS_WATCH_INFO_KIND_MODIFIED; - } break; - - case FILE_ACTION_RENAMED_OLD_NAME: - { - info->kind = SYS_WATCH_INFO_KIND_REMOVED; - } break; - - case FILE_ACTION_RENAMED_NEW_NAME: - { - info->kind = SYS_WATCH_INFO_KIND_RENAMED_OLD; - } break; - - default: - { - info->kind = SYS_WATCH_INFO_KIND_RENAMED_NEW; - } break; - } - - if (res->NextEntryOffset == 0) { - done = true; - } else { - offset += res->NextEntryOffset; - } - } - } else if (wait_res == WAIT_OBJECT_0 + 1) { - ResetEvent(w32_watch->wake_handle); - done = true; - } else { - ASSERT(false); - } - } - - return list; -} - -void sys_watch_wake(struct sys_watch *dw) -{ - struct win32_watch *w32_watch = (struct win32_watch *)dw; - SetEvent(w32_watch->wake_handle); -} - -struct sys_watch_info_list sys_watch_info_copy(struct arena *arena, struct sys_watch_info_list src_list) -{ - struct sys_watch_info_list dst_list = ZI; - for (struct sys_watch_info *src = src_list.first; src; src = src->next) { - struct sys_watch_info *dst = arena_push(arena, struct sys_watch_info); - dst->kind = src->kind; - dst->name = string_copy(arena, src->name); - if (dst_list.last) { - dst_list.last->next = dst; - dst->prev = dst_list.last; - dst_list.last = dst; - } else { - dst_list.first = dst; - dst_list.last = dst; - } - dst_list.count = src_list.count; - } - return dst_list; -} - -/* ========================== * - * Window - * ========================== */ - -INTERNAL void win32_update_window_from_system(struct win32_window *window); -INTERNAL void win32_window_wake(struct win32_window *window); - -INTERNAL void win32_window_process_event(struct win32_window *window, struct sys_event event) -{ - __prof; - struct sys_lock lock = sys_mutex_lock_e(window->event_callbacks_mutex); - for (u64 i = 0; i < window->event_callbacks_count; ++i) { - window->event_callbacks[i](event); - } - sys_mutex_unlock(&lock); -} - -INTERNAL HWND win32_create_window(struct win32_window *window) -{ - /* - * From martins (https://gist.github.com/mmozeiko/5e727f845db182d468a34d524508ad5f#file-win32_d3d11-c-L66-L70): - * WS_EX_NOREDIRECTIONBITMAP flag here is needed to fix ugly bug with Windows 10 - * when window is resized and DXGI swap chain uses FLIP presentation model - * DO NOT use it if you choose to use non-FLIP presentation model - * read about the bug here: https://stackoverflow.com/q/63096226 and here: https://stackoverflow.com/q/53000291 - */ - DWORD exstyle = WS_EX_APPWINDOW | WS_EX_NOREDIRECTIONBITMAP; - - /* TODO: Check for hwnd success */ - HWND hwnd = CreateWindowExW( - exstyle, - G.window_class.lpszClassName, - L"", - WS_OVERLAPPEDWINDOW, - CW_USEDEFAULT, - CW_USEDEFAULT, - CW_USEDEFAULT, - CW_USEDEFAULT, - NULL, - NULL, - G.window_class.hInstance, - NULL - ); - - /* Dark mode */ - BOOL dark_mode = true; - DwmSetWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, (LPCVOID)&dark_mode, sizeof(dark_mode)); - - /* Set window as userdata */ - SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR)window); - - return hwnd; -} - -INTERNAL SYS_THREAD_DEF(window_thread_entry_point, arg) -{ - struct win32_window *window = (struct win32_window *)arg; - - /* Win32 limitation: Window must be initialized on same thread that processes events */ - window->hwnd = win32_create_window(window); - window->tid = sys_thread_id(); - win32_update_window_from_system(window); - BringWindowToTop(window->hwnd); - - sync_flag_set(&window->ready_sf); - - while (!atomic_i32_fetch(&window->event_thread_shutdown)) { - MSG msg = ZI; - { - GetMessageW(&msg, 0, 0, 0); - } - { - __profscope(Process window message); - if (atomic_i32_fetch(&window->event_thread_shutdown)) { - break; - } - - /* Update cursor */ - if (GetFocus() == window->hwnd) { - u32 cursor_flags = window->cursor_set_flags; - - /* Hide cursor */ - if (cursor_flags & WIN32_WINDOW_CURSOR_SET_FLAG_HIDE) { - while(ShowCursor(false) >= 0); - } - - /* Show cursor */ - if (cursor_flags & WIN32_WINDOW_CURSOR_SET_FLAG_SHOW) { - while(ShowCursor(true) < 0); - } - - /* Update position */ - if (cursor_flags & WIN32_WINDOW_CURSOR_SET_FLAG_POSITION) { - struct v2 window_space_pos = window->cursor_set_position; - POINT p = { window_space_pos.x, window_space_pos.y }; - ClientToScreen(window->hwnd, &p); - SetCursorPos(p.x, p.y); - } - - /* Stop clipping cursor */ - if (cursor_flags & WIN32_WINDOW_CURSOR_SET_FLAG_DISABLE_CLIP) { - ClipCursor(NULL); - } - - /* Clip cursor in window window */ - if (cursor_flags & WIN32_WINDOW_CURSOR_SET_FLAG_ENABLE_CLIP) { - i32 left = window->x + math_round_to_int(window->cursor_clip_bounds.x); - i32 right = left + math_round_to_int(window->cursor_clip_bounds.width); - i32 top = window->y + math_round_to_int(window->cursor_clip_bounds.y); - i32 bottom = top + math_round_to_int(window->cursor_clip_bounds.height); - RECT clip = { - .left = clamp_i32(left, window->x, window->x + window->width), - .right = clamp_i32(right, window->x, window->x + window->width), - .top = clamp_i32(top, window->y, window->y + window->height), - .bottom = clamp_i32(bottom, window->y, window->y + window->height) - }; - ClipCursor(&clip); - } - - window->cursor_set_flags = 0; - } - - /* Process message */ - switch (msg.message) { - case WM_QUIT: { - win32_window_process_event(window, (struct sys_event) { .kind = SYS_EVENT_KIND_QUIT }); - } break; - - default: { - TranslateMessage(&msg); - DispatchMessageW(&msg); - } break; - } - } - } - - /* Destroy window hwnd */ - DestroyWindow(window->hwnd); -} - -INTERNAL struct win32_window *win32_window_alloc(void) -{ - struct win32_window *window = NULL; - { - struct sys_lock lock = sys_mutex_lock_e(G.windows_mutex); - if (G.first_free_window) { - window = G.first_free_window; - G.first_free_window = window->next_free; - } else { - window = arena_push_no_zero(G.windows_arena, struct win32_window); - } - sys_mutex_unlock(&lock); - } - MEMZERO_STRUCT(window); - - /* Allocate sync flag */ - window->ready_sf = sync_flag_alloc(); - - /* Allocate mutexes */ - window->settings_mutex = sys_mutex_alloc(); - window->event_callbacks_mutex = sys_mutex_alloc(); - - /* Start window thread for processing events */ - window->event_thread = sys_thread_alloc(&window_thread_entry_point, window, LIT("[P4] Window thread")); - - /* Wait for event thread to create actual window */ - sync_flag_wait(&window->ready_sf); - - return window; -} - -INTERNAL void win32_window_release(struct win32_window *window) -{ - struct sys_lock lock = sys_mutex_lock_e(G.windows_mutex); - - window->next_free = G.first_free_window; - G.first_free_window = window; - - /* Stop window thread */ - atomic_i32_fetch_set(&window->event_thread_shutdown, 1); - win32_window_wake(window); - sys_thread_wait_release(window->event_thread); - - /* Release mutexes */ - sys_mutex_release(window->event_callbacks_mutex); - sys_mutex_release(window->settings_mutex); - - /* Release sync flag */ - sync_flag_release(&window->ready_sf); - - sys_mutex_unlock(&lock); -} - -INTERNAL void win32_update_window_from_system(struct win32_window *window) -{ - HWND hwnd = window->hwnd; - - RECT window_rect = ZI; - GetWindowRect(hwnd, &window_rect); - - RECT client_rect = ZI; - GetClientRect(hwnd, (LPRECT)&client_rect); - ClientToScreen(hwnd, (LPPOINT)&client_rect.left); - ClientToScreen(hwnd, (LPPOINT)&client_rect.right); - - /* TODO: Error if we can't get monitor info */ - /* Screen dimensions */ - MONITORINFO monitor_info = { .cbSize = sizeof(monitor_info) }; - GetMonitorInfo(MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY), &monitor_info); - RECT monitor_rect = monitor_info.rcMonitor; - window->monitor_width = monitor_rect.right - monitor_rect.left; - window->monitor_height = monitor_rect.bottom - monitor_rect.top; - - /* Minimized / maximized */ - if (window->flags & SYS_WINDOW_FLAG_SHOWING) { - WINDOWPLACEMENT placement = { .length = sizeof(placement) }; - GetWindowPlacement(hwnd, &placement); - if (placement.showCmd == SW_SHOWMINIMIZED) { - window->settings.flags |= SYS_WINDOW_SETTINGS_FLAG_MINIMIZED; - } else { - window->settings.flags &= ~SYS_WINDOW_SETTINGS_FLAG_MINIMIZED; - } - if (placement.showCmd == SW_SHOWMAXIMIZED - || ((window->settings.flags & SYS_WINDOW_SETTINGS_FLAG_MINIMIZED) && ((placement.flags & WPF_RESTORETOMAXIMIZED) != 0))) { - window->settings.flags |= SYS_WINDOW_SETTINGS_FLAG_MAXIMIZED; - } else { - window->settings.flags &= ~SYS_WINDOW_SETTINGS_FLAG_MAXIMIZED; - } - } - - /* Window dimensions */ - i32 x = client_rect.left; - i32 y = client_rect.top; - i32 width = client_rect.right - client_rect.left; - i32 height = client_rect.bottom - client_rect.top; - if (!(window->settings.flags & SYS_WINDOW_SETTINGS_FLAG_MINIMIZED)) { - window->x = x; - window->y = y; - window->width = width; - window->height = height; - if (!(window->settings.flags & (SYS_WINDOW_SETTINGS_FLAG_MAXIMIZED | SYS_WINDOW_SETTINGS_FLAG_FULLSCREEN))) { - /* Treat a window resize in non maximized/fullscreen mode as a - * settings change. - * - * TODO: make sure we check for fullscreen here too if we ever - * allow it. */ - window->settings.floating_x = x; - window->settings.floating_y = y; - window->settings.floating_width = width; - window->settings.floating_height = height; - } - } -} - -INTERNAL void win32_update_window_from_settings(struct win32_window *window, struct sys_window_settings *settings) -{ - HWND hwnd = window->hwnd; - - struct sys_window_settings old_settings = window->settings; - window->settings = *settings; - - i32 show_cmd = SW_HIDE; - if (window->flags & SYS_WINDOW_FLAG_SHOWING) { - show_cmd = SW_NORMAL; - if (settings->flags & SYS_WINDOW_SETTINGS_FLAG_MAXIMIZED) { - show_cmd = SW_SHOWMAXIMIZED; - } else if (settings->flags & SYS_WINDOW_SETTINGS_FLAG_MINIMIZED) { - show_cmd = SW_MINIMIZE; - } - } - - RECT rect = ZI; - - b32 old_fullscreen = old_settings.flags & SYS_WINDOW_SETTINGS_FLAG_FULLSCREEN; - b32 fullscreen = settings->flags & SYS_WINDOW_SETTINGS_FLAG_FULLSCREEN; - - if (fullscreen) { - if (!old_fullscreen) { - /* Entering fullscreen */ - SetWindowLongPtrW(hwnd, GWL_STYLE, WS_POPUP); - } - rect = (RECT) { - .left = 0, - .top = 0, - .right = window->monitor_width, - .bottom = window->monitor_height - }; - } else { - if (old_fullscreen) { - /* Leaving fullscreen */ - SetWindowLongPtrW(hwnd, GWL_STYLE, WS_OVERLAPPEDWINDOW); - } - rect = (RECT) { - .left = settings->floating_x, - .top = settings->floating_y, - .right = settings->floating_x + settings->floating_width, - .bottom = settings->floating_y + settings->floating_height - }; - AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, false); - } - - WINDOWPLACEMENT wp = { - .length = sizeof(WINDOWPLACEMENT), - .showCmd = show_cmd, - .rcNormalPosition = rect - }; - SetWindowPlacement(hwnd, &wp); - - /* Make window always on top when debugging */ -#if 0 -#if RTC - SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); -#endif -#endif - - { - struct arena_temp scratch = scratch_begin_no_conflict(); - wchar_t *title_wstr = wstr_from_string(scratch.arena, string_from_cstr_no_limit(settings->title)); - SetWindowTextW(hwnd, title_wstr); - scratch_end(scratch); - } -} - -INTERNAL void win32_window_wake(struct win32_window *window) -{ - /* Post a blank message to the window's thread message queue to wake it. */ - PostMessageW(window->hwnd, WM_NULL, 0, 0); -} - -INTERNAL LRESULT CALLBACK win32_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -{ - __prof; - struct win32_window *window = (struct win32_window *)GetWindowLongPtrW(hwnd, GWLP_USERDATA); - - if (!window) { - return DefWindowProcW(hwnd, msg, wparam, lparam); - } - - LRESULT result = 0; - b32 is_release = false; - switch (msg) { - case WM_CLOSE: - case WM_DESTROY: { - win32_window_process_event(window, (struct sys_event) { .kind = SYS_EVENT_KIND_QUIT }); - } break; - - case WM_PAINT: { - result = DefWindowProcW(hwnd, msg, wparam, lparam); - } break; - - case WM_ENTERSIZEMOVE: - case WM_MOVE: - case WM_MOVING: - case WM_SIZE: - case WM_SIZING: { - win32_update_window_from_system(window); - result = DefWindowProcW(hwnd, msg, wparam, lparam); - } break; - - /* Keyboard buttons */ - case WM_SYSKEYUP: - case WM_SYSKEYDOWN: { - if (LOWORD(wparam) != VK_MENU) { - result = DefWindowProcW(hwnd, msg, wparam, lparam); - } - } FALLTHROUGH; - case WM_KEYUP: - case WM_KEYDOWN: { - WORD vk_code = LOWORD(wparam); - b32 is_repeat = false; - - enum sys_event_kind event_kind = SYS_EVENT_KIND_NONE; - if (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN) { - event_kind = SYS_EVENT_KIND_BUTTON_DOWN; - is_repeat = (lparam & 0x40000000) != 0; - } else if (msg == WM_KEYUP || msg == WM_SYSKEYUP) { - event_kind = SYS_EVENT_KIND_BUTTON_UP; - } - - enum sys_btn button = SYS_BTN_NONE; - if (vk_code < countof(G.vk_btn_table)) { - button = G.vk_btn_table[vk_code]; - } - - win32_window_process_event( - window, - (struct sys_event) { - .kind = event_kind, - .button = button, - .is_repeat = is_repeat - } - ); - } break; - - /* Text */ - case WM_SYSCHAR: - case WM_CHAR: { - u16 utf16_char = (u32)wparam; - - /* Decode */ - u32 codepoint = 0; - if (uni_is_utf16_high_surrogate(utf16_char)) { - window->utf16_high_surrogate_last_input = utf16_char; - } else if (uni_is_utf16_low_surrogate(utf16_char)) { - u16 high = window->utf16_high_surrogate_last_input; - u16 low = utf16_char; - if (high) { - u16 utf16_pair_bytes[2] = { high, low }; - struct uni_decode_utf16_result decoded = uni_decode_utf16((struct string16) { .len = countof(utf16_pair_bytes), .text = utf16_pair_bytes }); - if (decoded.advance16 == 2 && decoded.codepoint < U32_MAX) { - codepoint = decoded.codepoint; - } - } - window->utf16_high_surrogate_last_input = 0; - } else { - window->utf16_high_surrogate_last_input = 0; - codepoint = utf16_char; - } - - if (codepoint) { - if (codepoint == '\r') { - codepoint = '\n'; /* Just treat all \r as newline */ - } - if((codepoint >= 32 && codepoint != 127) || codepoint == '\t' || codepoint == '\n') { - win32_window_process_event( - window, - (struct sys_event) { - .kind = SYS_EVENT_KIND_TEXT, - .text_codepoint = codepoint - } - ); - } - } - - - } break; - - /* Mouse buttons */ - case WM_LBUTTONUP: - case WM_MBUTTONUP: - case WM_RBUTTONUP: - case WM_XBUTTONUP: { - ReleaseCapture(); - is_release = true; - } FALLTHROUGH; - case WM_LBUTTONDOWN: - case WM_MBUTTONDOWN: - case WM_RBUTTONDOWN: - case WM_XBUTTONDOWN: { - if(!is_release) { - SetCapture(hwnd); - } - - enum sys_event_kind event_kind = is_release ? SYS_EVENT_KIND_BUTTON_UP : SYS_EVENT_KIND_BUTTON_DOWN; - enum sys_btn button = 0; - - switch(msg) { - case WM_LBUTTONUP: case WM_LBUTTONDOWN: button = SYS_BTN_M1; break; - case WM_RBUTTONUP: case WM_RBUTTONDOWN: button = SYS_BTN_M2; break; - case WM_MBUTTONUP: case WM_MBUTTONDOWN: button = SYS_BTN_M3; break; - case WM_XBUTTONUP: case WM_XBUTTONDOWN: { - u32 wparam_xbutton = GET_XBUTTON_WPARAM(wparam); - if (wparam_xbutton == XBUTTON1) { - button = SYS_BTN_M4; - } else if (wparam_xbutton == XBUTTON2) { - button = SYS_BTN_M5; - } - } break; - } - - if (button) { - win32_window_process_event( - window, - (struct sys_event) { - .kind = event_kind, - .button = button - } - ); - } - } break; - - /* Mouse wheel */ - case WM_MOUSEWHEEL: { - int delta = GET_WHEEL_DELTA_WPARAM(wparam); - i32 dir = delta >= 0 ? 1 : -1; - enum sys_btn button = dir >= 0 ? SYS_BTN_MWHEELUP : SYS_BTN_MWHEELDOWN; - for (i32 i = 0; i < (dir * delta); i += WHEEL_DELTA) { - /* Send a button down & button up event simultaneously */ - win32_window_process_event(window, (struct sys_event) { .kind = SYS_EVENT_KIND_BUTTON_DOWN, .button = button }); - win32_window_process_event(window, (struct sys_event) { .kind = SYS_EVENT_KIND_BUTTON_UP, .button = button }); - } - } break; - - /* Mouse move */ - case WM_MOUSEMOVE: { - i32 x = GET_X_LPARAM(lparam); - i32 y = GET_Y_LPARAM(lparam); - win32_window_process_event( - window, - (struct sys_event) { - .kind = SYS_EVENT_KIND_CURSOR_MOVE, - .cursor_position = V2(x, y) - } - ); - } break; - - /* Raw mouse move */ - case WM_INPUT: { - struct arena_temp scratch = scratch_begin_no_conflict(); - - /* Read raw input buffer */ - UINT buff_size; - GetRawInputData((HRAWINPUT)lparam, RID_INPUT, NULL, &buff_size, sizeof(RAWINPUTHEADER)); - u8 *buff = arena_push_array(scratch.arena, u8, buff_size); - if (GetRawInputData((HRAWINPUT)lparam, RID_INPUT, buff, &buff_size, sizeof(RAWINPUTHEADER)) != buff_size) { - logf_error("GetRawInputData did not return correct size"); - break; - } - RAWINPUT raw = ZI; - MEMCPY(&raw, buff, sizeof(RAWINPUT)); - - if (raw.header.dwType == RIM_TYPEMOUSE) { - i32 x = raw.data.mouse.lLastX; - i32 y = raw.data.mouse.lLastY; - struct v2 delta = V2(x, y); - win32_window_process_event( - window, - (struct sys_event) { - .kind = SYS_EVENT_KIND_MOUSE_MOVE, - .mouse_delta = delta - } - ); - } - - scratch_end(scratch); - } break; - - /* Minmax info */ - case WM_GETMINMAXINFO: { - /* Set minimum window size */ - LPMINMAXINFO mmi = (LPMINMAXINFO)lparam; - mmi->ptMinTrackSize.x = 100; - mmi->ptMinTrackSize.y = 100; - } break; - - default: { - result = DefWindowProcW(hwnd, msg, wparam, lparam); - } break; - } - - return result; -} - -struct sys_window *sys_window_alloc(void) -{ - __prof; - return (struct sys_window *)win32_window_alloc(); -} - -void sys_window_release(struct sys_window *sys_window) -{ - __prof; - struct win32_window *window = (struct win32_window *)sys_window; - win32_window_release(window); -} - -void sys_window_register_event_callback(struct sys_window *sys_window, sys_window_event_callback_func *func) -{ - struct win32_window *window = (struct win32_window *)sys_window; - struct sys_lock lock = sys_mutex_lock_e(window->event_callbacks_mutex); - { - if (window->event_callbacks_count + 1 > countof(window->event_callbacks)) { - sys_panic(LIT("Too many window event callbacks registered")); - } else { - window->event_callbacks[window->event_callbacks_count++] = func; - } - } - sys_mutex_unlock(&lock); -} - -void sys_window_unregister_event_callback(struct sys_window *sys_window, sys_window_event_callback_func *func) -{ - struct win32_window *window = (struct win32_window *)sys_window; - - struct sys_lock lock = sys_mutex_lock_e(window->event_callbacks_mutex); - { - u64 count = window->event_callbacks_count; - sys_window_event_callback_func *last = count > 0 ? window->event_callbacks[count - 1] : NULL; - - for (u64 i = 0; i < window->event_callbacks_count; ++i) { - if (window->event_callbacks[i] == func) { - if (func != last) { - /* Swap with last element */ - window->event_callbacks[i] = last; - } - --window->event_callbacks_count; - } - } - } - sys_mutex_unlock(&lock); -} - -void sys_window_update_settings(struct sys_window *sys_window, struct sys_window_settings *settings) -{ - __prof; - struct win32_window *window = (struct win32_window *)sys_window; - struct sys_lock lock = sys_mutex_lock_e(window->settings_mutex); - { - win32_update_window_from_settings(window, settings); - } - sys_mutex_unlock(&lock); -} - -/* FIXME: Lock settings mutex for these functions */ - -struct sys_window_settings sys_window_get_settings(struct sys_window *sys_window) -{ - struct win32_window *window = (struct win32_window *)sys_window; - return window->settings; -} - -void sys_window_show(struct sys_window *sys_window) -{ - struct win32_window *window = (struct win32_window *)sys_window; - HWND hwnd = window->hwnd; - struct sys_lock lock = sys_mutex_lock_e(window->settings_mutex); - { - i32 show_cmd = SW_NORMAL; - struct sys_window_settings *settings = &window->settings; - if (settings->flags & SYS_WINDOW_SETTINGS_FLAG_MAXIMIZED) { - show_cmd = SW_SHOWMAXIMIZED; - } else if (settings->flags & SYS_WINDOW_SETTINGS_FLAG_MINIMIZED) { - show_cmd = SW_MINIMIZE; - } - window->flags |= SYS_WINDOW_FLAG_SHOWING; - ShowWindow(hwnd, show_cmd); - BringWindowToTop(hwnd); - } - sys_mutex_unlock(&lock); -} - -struct v2 sys_window_get_size(struct sys_window *sys_window) -{ - struct win32_window *window = (struct win32_window *)sys_window; - return V2((f32)window->width, (f32)window->height); -} - -struct v2 sys_window_get_monitor_size(struct sys_window *sys_window) -{ - struct win32_window *window = (struct win32_window *)sys_window; - return V2((f32)window->monitor_width, (f32)window->monitor_height); -} - -u64 sys_window_get_internal_handle(struct sys_window *sys_window) -{ - struct win32_window *window = (struct win32_window *)sys_window; - return (u64)window->hwnd; -} - -void sys_window_cursor_set_pos(struct sys_window *sys_window, struct v2 pos) -{ - struct win32_window *window = (struct win32_window *)sys_window; - window->cursor_set_position = pos; - window->cursor_set_flags |= WIN32_WINDOW_CURSOR_SET_FLAG_POSITION; - win32_window_wake(window); -} - -void sys_window_cursor_show(struct sys_window *sys_window) -{ - struct win32_window *window = (struct win32_window *)sys_window; - window->cursor_set_flags |= WIN32_WINDOW_CURSOR_SET_FLAG_SHOW; - win32_window_wake(window); -} - -void sys_window_cursor_hide(struct sys_window *sys_window) -{ - struct win32_window *window = (struct win32_window *)sys_window; - window->cursor_set_flags |= WIN32_WINDOW_CURSOR_SET_FLAG_HIDE; - win32_window_wake(window); -} - -void sys_window_cursor_enable_clip(struct sys_window *sys_window, struct rect bounds) -{ - struct win32_window *window = (struct win32_window *)sys_window; - window->cursor_clip_bounds = bounds; - window->cursor_set_flags |= WIN32_WINDOW_CURSOR_SET_FLAG_ENABLE_CLIP; - win32_window_wake(window); -} - -void sys_window_cursor_disable_clip(struct sys_window *sys_window) -{ - struct win32_window *window = (struct win32_window *)sys_window; - window->cursor_set_flags |= WIN32_WINDOW_CURSOR_SET_FLAG_DISABLE_CLIP; - win32_window_wake(window); -} - -/* ========================== * - * Mutex - * ========================== */ - -INTERNAL void win32_mutex_init(struct win32_mutex *m) -{ -#if PROFILING_LOCKS - __proflock_ctx(profiling_ctx) = m->profiling_ctx; -#endif - MEMZERO_STRUCT(m); - m->srwlock = (SRWLOCK)SRWLOCK_INIT; -#if PROFILING_LOCKS - if (!profiling_ctx) { - __proflock_alloc(profiling_ctx); - } - m->profiling_ctx = profiling_ctx; -#endif -} - -struct sys_mutex *sys_mutex_alloc(void) -{ - __prof; - struct win32_mutex *m = NULL; - { - struct sys_lock lock = sys_mutex_lock_e(G.mutexes_mutex); - if (G.first_free_mutex) { - m = G.first_free_mutex; - G.first_free_mutex = m->next_free; - } else { - m = arena_push_no_zero(G.mutexes_arena, struct win32_mutex); - } - sys_mutex_unlock(&lock); - } - win32_mutex_init(m); - return (struct sys_mutex *)m; -} - -void sys_mutex_release(struct sys_mutex *mutex) -{ - __prof; - struct win32_mutex *m = (struct win32_mutex *)mutex; - ASSERT(atomic_i64_fetch(&m->count) == 0); /* Mutex should be unlocked */ - { - struct sys_lock lock = sys_mutex_lock_e(G.mutexes_mutex); - m->next_free = G.first_free_mutex; - G.first_free_mutex = m; - sys_mutex_unlock(&lock); - } -} - -struct sys_lock sys_mutex_lock_e(struct sys_mutex *mutex) -{ - struct win32_mutex *m = (struct win32_mutex *)mutex; - __proflock_before_exclusive_lock(m->profiling_ctx); - AcquireSRWLockExclusive((SRWLOCK *)&m->srwlock); - __proflock_after_exclusive_lock(m->profiling_ctx); -#if RTC - m->owner_tid = (u64)GetCurrentThreadId(); - atomic_i64_fetch_add(&m->count, 1); -#endif - struct sys_lock lock = ZI; - lock.exclusive = true; - lock.mutex = mutex; - return lock; -} - -struct sys_lock sys_mutex_lock_s(struct sys_mutex *mutex) -{ - struct win32_mutex *m = (struct win32_mutex *)mutex; - __proflock_before_shared_lock(m->profiling_ctx); - AcquireSRWLockShared((SRWLOCK *)&m->srwlock); - __proflock_after_shared_lock(m->profiling_ctx); -#if RTC - atomic_i64_fetch_add(&m->count, 1); -#endif - struct sys_lock lock = ZI; - lock.mutex = mutex; - return lock; -} - -void sys_mutex_unlock(struct sys_lock *lock) -{ - struct win32_mutex *m = (struct win32_mutex *)lock->mutex; -#if RTC - atomic_i64_fetch_add(&m->count, -1); - m->owner_tid = 0; -#endif - if (lock->exclusive) { - ReleaseSRWLockExclusive((SRWLOCK *)&m->srwlock); - __proflock_after_exclusive_unlock(m->profiling_ctx); - } else { - ReleaseSRWLockShared((SRWLOCK *)&m->srwlock); - __proflock_after_shared_unlock(m->profiling_ctx); - } - MEMZERO_STRUCT(lock); -} - -#if RTC -void sys_assert_locked_e(struct sys_lock *lock, struct sys_mutex *mutex) -{ - ASSERT(lock->mutex == mutex); - ASSERT(lock->exclusive == true); -} - -void sys_assert_locked_e_or_s(struct sys_lock *lock, struct sys_mutex *mutex) -{ - ASSERT(lock->mutex == mutex); -} -#endif - -/* ========================== * - * Condition variable - * ========================== */ - -INTERNAL struct win32_condition_variable *win32_condition_variable_alloc(void) -{ - __prof; - struct win32_condition_variable *cv = NULL; - { - struct sys_lock lock = sys_mutex_lock_e(G.condition_variables_mutex); - if (G.first_free_condition_variable) { - cv = G.first_free_condition_variable; - G.first_free_condition_variable = cv->next_free; - } else { - cv = arena_push(G.condition_variables_arena, struct win32_condition_variable); - } - sys_mutex_unlock(&lock); - } - - MEMZERO_STRUCT(cv); - InitializeConditionVariable(&cv->condition_variable); - - return cv; -} - -INTERNAL void win32_condition_variable_release(struct win32_condition_variable *w32cv) -{ - __prof; - struct sys_lock lock = sys_mutex_lock_e(G.condition_variables_mutex); - w32cv->next_free = G.first_free_condition_variable; - G.first_free_condition_variable = w32cv; - sys_mutex_unlock(&lock); -} - -struct sys_condition_variable *sys_condition_variable_alloc(void) -{ - __prof; - return (struct sys_condition_variable *)win32_condition_variable_alloc(); -} - -void sys_condition_variable_release(struct sys_condition_variable *sys_cv) -{ - __prof; - struct win32_condition_variable *cv = (struct win32_condition_variable *)sys_cv; - /* Condition variable must not have any sleepers (signal before releasing) */ - ASSERT(atomic_i64_fetch(&cv->num_waiters) == 0); - win32_condition_variable_release(cv); -} - -void sys_condition_variable_wait(struct sys_condition_variable *sys_cv, struct sys_lock *lock) -{ - struct win32_condition_variable *cv = (struct win32_condition_variable *)sys_cv; - struct win32_mutex *m = (struct win32_mutex *)lock->mutex; - b32 exclusive = lock->exclusive; -#if RTC - atomic_i64_fetch_add(&cv->num_waiters, 1); - if (exclusive) { - m->owner_tid = 0; - } - atomic_i64_fetch_add(&m->count, -1); -#endif - - /* TODO: Correct profiling of internal condition variable sleep / wait mutex state */ - if (exclusive) { - __proflock_after_exclusive_unlock(m->profiling_ctx); - } else { - __proflock_after_shared_unlock(m->profiling_ctx); - } - SleepConditionVariableSRW(&cv->condition_variable, (SRWLOCK *)&m->srwlock, INFINITE, exclusive ? 0 : CONDITION_VARIABLE_LOCKMODE_SHARED); - if (exclusive) { - __proflock_before_exclusive_lock(m->profiling_ctx); - __proflock_after_exclusive_lock(m->profiling_ctx); - } else { - __proflock_before_shared_lock(m->profiling_ctx); - __proflock_after_shared_lock(m->profiling_ctx); - } - -#if RTC - atomic_i64_fetch_add(&m->count, 1); - if (exclusive) { - m->owner_tid = (u64)GetCurrentThreadId(); - } - atomic_i64_fetch_add(&cv->num_waiters, -1); -#endif -} - -void sys_condition_variable_wait_time(struct sys_condition_variable *sys_cv, struct sys_lock *lock, f64 seconds) -{ - struct win32_condition_variable *cv = (struct win32_condition_variable *)sys_cv; - struct win32_mutex *m = (struct win32_mutex *)lock->mutex; - b32 exclusive = lock->exclusive; -#if RTC - atomic_i64_fetch_add(&cv->num_waiters, 1); - if (exclusive) { - m->owner_tid = 0; - } - atomic_i64_fetch_add(&m->count, -1); -#endif - u32 ms = (u32)math_round_to_int((f32)seconds * 1000.f); - - /* TODO: Correct profiling of internal condition variable sleep / wait mutex state */ - if (exclusive) { - __proflock_after_exclusive_unlock(m->profiling_ctx); - } else { - __proflock_after_shared_unlock(m->profiling_ctx); - } - SleepConditionVariableSRW(&cv->condition_variable, (SRWLOCK *)&m->srwlock, ms, exclusive ? 0 : CONDITION_VARIABLE_LOCKMODE_SHARED); - if (exclusive) { - __proflock_before_exclusive_lock(m->profiling_ctx); - __proflock_after_exclusive_lock(m->profiling_ctx); - } else { - __proflock_before_shared_lock(m->profiling_ctx); - __proflock_after_shared_lock(m->profiling_ctx); - } - -#if RTC - atomic_i64_fetch_add(&m->count, 1); - if (exclusive) { - m->owner_tid = (u64)GetCurrentThreadId(); - } - atomic_i64_fetch_add(&cv->num_waiters, -1); -#endif -} - -void sys_condition_variable_signal(struct sys_condition_variable *sys_cv, u32 count) -{ - struct win32_condition_variable *cv = (struct win32_condition_variable *)sys_cv; - /* Windows will wake all waiters if many single-wakes occur anyway, so we - * might as well wake all ourselves. - * https://devblogs.microsoft.com/oldnewthing/20180201-00/?p=97946 */ - if (count <= 24) { - for (u32 i = 0; i < count; ++i) { - WakeConditionVariable(&cv->condition_variable); - } - } else { - WakeAllConditionVariable(&cv->condition_variable); - } -} - -void sys_condition_variable_broadcast(struct sys_condition_variable *sys_cv) -{ - struct win32_condition_variable *cv = (struct win32_condition_variable *)sys_cv; - WakeAllConditionVariable(&cv->condition_variable); -} - -/* ========================== * - * Thread local storage - * ========================== */ - -struct win32_tls { - HANDLE sleep_timer; - struct thread_local_store store; -}; - -INTERNAL void win32_thread_set_tls(struct win32_tls *ctx) -{ - TlsSetValue(G.thread_tls_index, (LPVOID)ctx); -} - -INTERNAL struct win32_tls *win32_thread_get_tls(void) -{ - return TlsGetValue(G.thread_tls_index); -} - -INTERNAL struct win32_tls win32_tls_alloc(void) -{ - struct win32_tls tls = ZI; - tls.sleep_timer = CreateWaitableTimerExW(NULL, NULL, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS); - tls.store = thread_local_store_alloc(); - return tls; -} - -INTERNAL void win32_tls_release(struct win32_tls *tls) -{ - thread_local_store_release(&tls->store); - CloseHandle(tls->sleep_timer); -} - -struct thread_local_store *sys_thread_get_thread_local_store(void) -{ - struct win32_tls *thread_ctx = (struct win32_tls *)win32_thread_get_tls(); - return &thread_ctx->store; -} - -/* ========================== * - * Threads - * ========================== */ - -INTERNAL struct win32_thread *win32_thread_alloc(void) -{ - struct win32_thread *t = NULL; - struct sys_lock lock = sys_mutex_lock_e(G.threads_mutex); - { - if (G.threads_first_free) { - t = G.threads_first_free; - G.threads_first_free = t->next; - } else { - t = arena_push_no_zero(G.threads_arena, struct win32_thread); - } - MEMZERO_STRUCT(t); - if (!G.threads_first) { - G.threads_first = t; - } else { - G.threads_last->next = t; - } - t->prev = G.threads_last; - G.threads_last = t; - } - sys_mutex_unlock(&lock); - return t; -} - -INTERNAL void win32_thread_release(struct win32_thread *t) -{ - struct sys_lock lock = sys_mutex_lock_e(G.threads_mutex); - { - if (t->prev) { - t->prev->next = t->next; - } - if (t->next) { - t->next->prev = t->prev; - } - if (G.threads_first == t) { - G.threads_first = t->next; - } - if (G.threads_last == t) { - G.threads_last = t->prev; - } - t->next = G.threads_first_free; - } - sys_mutex_unlock(&lock); -} - -INTERNAL DWORD WINAPI win32_thread_proc(LPVOID vt) -{ - struct win32_thread *t = (struct win32_thread *)vt; - __profthread(t->thread_name_cstr); - - /* Initialize COM */ - CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); - - /* Initialize TLS */ - struct win32_tls tls = win32_tls_alloc(); - win32_thread_set_tls(&tls); - - /* Set thread name */ - if (t->thread_name_wstr[0] != 0) { - SetThreadDescription(GetCurrentThread(), t->thread_name_wstr); - } - - logf_info("New thread \"%F\" created with ID %F", FMT_STR(string_from_cstr_no_limit(t->thread_name_cstr)), FMT_UINT(sys_thread_id())); - - /* Enter thread entry point */ - t->entry_point(t->thread_data); - - /* Release TLS */ - win32_tls_release(&tls); - - /* Uninitialize COM */ - CoUninitialize(); - - return 0; -} - -struct sys_thread *sys_thread_alloc(sys_thread_func *entry_point, void *thread_data, struct string thread_name) -{ - __prof; - struct arena_temp scratch = scratch_begin_no_conflict(); - ASSERT(entry_point != NULL); - logf_info("Creating thread \"%F\"", FMT_STR(thread_name)); - - - /* Allocate thread object */ - struct win32_thread *t = win32_thread_alloc(); - t->entry_point = entry_point; - t->thread_data = thread_data; - - /* Copy thread name to params */ - { - u64 cstr_len = min_u64((countof(t->thread_name_cstr) - 1), thread_name.len); - MEMCPY(t->thread_name_cstr, thread_name.text, cstr_len * sizeof(*t->thread_name_cstr)); - t->thread_name_cstr[cstr_len] = 0; - } - { - struct string16 thread_name16 = string16_from_string(scratch.arena, thread_name); - u64 wstr_len = min_u64((countof(t->thread_name_wstr) - 1), thread_name16.len); - MEMCPY(t->thread_name_wstr, thread_name16.text, wstr_len * sizeof(*t->thread_name_wstr)); - t->thread_name_wstr[wstr_len] = 0; - } - - t->handle = CreateThread( - NULL, - SYS_THREAD_STACK_SIZE, - win32_thread_proc, - t, - 0, - NULL - ); - - if (!t->handle) { - sys_panic(LIT("Failed to create thread")); - } - - scratch_end(scratch); - return (struct sys_thread *)t; -} - -void sys_thread_wait_release(struct sys_thread *thread) -{ - __prof; - b32 success = sys_thread_try_release(thread, F32_INFINITY); - ASSERT(success); - (UNUSED)success; -} - -b32 sys_thread_try_release(struct sys_thread *thread, f32 timeout_seconds) -{ - __prof; - b32 success = false; - struct win32_thread *t = (struct win32_thread *)thread; - HANDLE handle = t->handle; - - /* Wait for thread to stop */ - if (handle) { - DWORD timeout_ms = (timeout_seconds == F32_INFINITY) ? INFINITE : math_round_to_int(timeout_seconds * 1000); - DWORD wait_res = WaitForSingleObject(handle, timeout_ms); - if (wait_res == WAIT_OBJECT_0) { - success = true; - CloseHandle(handle); - win32_thread_release(t); - } - } - - return success; -} - -void sys_thread_force_release(struct sys_thread *thread) -{ - __prof; - struct win32_thread *t = (struct win32_thread *)thread; - HANDLE handle = t->handle; - - /* Wait for thread to stop */ - if (handle) { - DWORD res = WaitForSingleObject(handle, INFINITE); - TerminateThread(handle, 0); - CloseHandle(handle); - - ASSERT(res != WAIT_FAILED); - (UNUSED)res; - } - - /* Release thread struct */ - win32_thread_release(t); -} - -u32 sys_thread_id(void) -{ - return GetCurrentThreadId(); -} - -#if RTC -void sys_thread_assert(u32 tid) -{ - ASSERT(sys_thread_id() == tid); -} -#endif - -/* ========================== * - * Message box - * ========================== */ - -void sys_message_box(enum sys_message_box_kind kind, struct string message) -{ - struct arena_temp scratch = scratch_begin_no_conflict(); - - wchar_t *message_wstr = wstr_from_string(scratch.arena, message); - const wchar_t *title = L""; - UINT mbox_type = MB_SETFOREGROUND; - - switch (kind) { - case SYS_MESSAGE_BOX_KIND_OK: { - mbox_type |= MB_ICONINFORMATION; - } break; - - case SYS_MESSAGE_BOX_KIND_WARNING: { - title = L"Warning"; - mbox_type |= MB_ICONWARNING; - } break; - - case SYS_MESSAGE_BOX_KIND_ERROR: { - title = L"Error"; - mbox_type |= MB_ICONERROR; - } break; - - case SYS_MESSAGE_BOX_KIND_FATAL: { - title = L"Fatal error"; - mbox_type |= MB_ICONSTOP; - } break; - } - - logf_info("Showing message box kind %F with text \"%F\"", FMT_SINT(kind), FMT_STR(message)); - MessageBoxExW(NULL, message_wstr, title, mbox_type, 0); - - scratch_end(scratch); -} - -/* ========================== * - * Clipboard - * ========================== */ - -void sys_set_clipboard_text(struct string str) -{ - if (OpenClipboard(0)) { - struct arena_temp scratch = scratch_begin_no_conflict(); - struct string16 str16 = string16_from_string(scratch.arena, str); - u64 str16_size_bytes = str16.len * 2; - EmptyClipboard(); - HANDLE handle = GlobalAlloc(GMEM_MOVEABLE, str16_size_bytes + 1); - if (handle) { - u16 *dest_wstr = (u16 *)GlobalLock(handle); - MEMCPY(dest_wstr, str16.text, str16_size_bytes); - dest_wstr[str16.len] = 0; - GlobalUnlock(handle); - SetClipboardData(CF_UNICODETEXT, handle); - } - CloseClipboard(); - scratch_end(scratch); - } -} - -struct string sys_get_clipboard_text(struct arena *arena) -{ - struct string res = ZI; - if (IsClipboardFormatAvailable(CF_UNICODETEXT) && OpenClipboard(0)) { - HANDLE handle = GetClipboardData(CF_UNICODETEXT); - if (handle) { - u16 *src_wstr = (u16 *)GlobalLock(handle); - res = string_from_string16(arena, string16_from_wstr_no_limit(src_wstr)); - GlobalUnlock(handle); - } - CloseClipboard(); - } - return res; -} - -/* ========================== * - * RNG - * ========================== */ - -void sys_true_rand(struct string b) -{ - BCryptGenRandom(BCRYPT_RNG_ALG_HANDLE, (PUCHAR)b.text, b.len, 0); -} - -u32 sys_num_logical_processors(void) -{ - return GetActiveProcessorCount(ALL_PROCESSOR_GROUPS); -} - -void sys_exit(void) -{ - ExitProcess(1); -} - -void sys_panic(struct string msg) -{ - if (atomic_i32_fetch_test_set(&G.panicking, 0, 1) == 0) { - log_panic(msg); - - wchar_t *wstr = G.panic_wstr; - u64 wstr_len = 0; - - wchar_t prefix[] = L"A fatal error has occured and the application needs to exit:\n\n"; - MEMCPY(wstr, prefix, min_u64(countof(G.panic_wstr), (countof(prefix) << 1))); - wstr_len += countof(prefix) - 1; - - /* Perform manual string encode to avoid any implicit memory - * allocation (in case allocation is unreliable) */ - struct string str8 = msg; - u64 pos8 = 0; - while (pos8 < str8.len) { - struct string str8_remaining = { .len = (str8.len - pos8), .text = str8.text + pos8 }; - struct uni_decode_utf8_result decoded = uni_decode_utf8(str8_remaining); - struct uni_encode_utf16_result encoded = uni_encode_utf16(decoded.codepoint); - u64 wstr_new_len = wstr_len + encoded.count16; - if (wstr_new_len < (countof(G.panic_wstr) - 1)) { - u16 *dest = wstr + wstr_len; - MEMCPY(dest, encoded.chars16, (encoded.count16 << 1)); - wstr_len = wstr_new_len; - pos8 += decoded.advance8; - } else { - break; - } - } - - wstr[wstr_len] = 0; - -#if RTC - MessageBoxExW(NULL, wstr, L"Fatal error", MB_ICONSTOP | MB_SETFOREGROUND | MB_TOPMOST, 0); - ASSERT(false); -#endif - - WRITE_BARRIER(); - SetEvent(G.panic_event); - - /* Wait for thread to be terminated */ - if (GetCurrentThreadId() != G.main_thread_id) { - Sleep(INFINITE); - } - } -} - -/* ========================== * - * Sleep - * ========================== */ - -/* https://blog.bearcats.nl/perfect-sleep-function/ */ - -INTERNAL void win32_precise_sleep_timer(f64 seconds, HANDLE timer) -{ - __prof; - - /* TODO: Does the high frequency timer even require setting / scaling of - * timeBeginPeriod/scheduler_period_ms? There isn't much documentation. */ - - i64 qpc_per_second = G.qpc_per_second; - i32 scheduler_period_ms = G.scheduler_period_ms; - - LARGE_INTEGER qpc; - QueryPerformanceCounter(&qpc); - i64 target_qpc = (i64)(qpc.QuadPart + seconds * qpc_per_second); - - /* TODO: Maybe increase tolerance for higher precision but more power usage */ - //const f64 tolerance = scheduler_period_ms * 0.001200; - const f64 tolerance = scheduler_period_ms * 0.000520; - //const f64 tolerance = scheduler_period_ms * 1; - - i64 max_ticks = (i64)scheduler_period_ms * 9500; - while (true) { - __profscope(Sleep part); - /* Break sleep up into parts that are lower than scheduler period */ - f64 remaining_seconds = (f64)(target_qpc - qpc.QuadPart) / (f64)qpc_per_second; - i64 sleep_ticks = (i64)((remaining_seconds - tolerance) * 10000000); - if (sleep_ticks <= 0) { - break; - } - LARGE_INTEGER due; - due.QuadPart = -(sleep_ticks > max_ticks ? max_ticks : sleep_ticks); - SetWaitableTimerEx(timer, &due, 0, NULL, NULL, NULL, 0); - WaitForSingleObject(timer, INFINITE); - QueryPerformanceCounter(&qpc); - } - - /* Spin for any remaining time */ - { - __profscope(Sleep spin); - while (qpc.QuadPart < target_qpc) { - YieldProcessor(); - QueryPerformanceCounter(&qpc); - } - } -} - -INTERNAL void win32_precise_sleep_legacy(f64 seconds) -{ - __prof; - i64 qpc_per_second = G.qpc_per_second; - i32 scheduler_period_ms = G.scheduler_period_ms; - - LARGE_INTEGER qpc; - QueryPerformanceCounter(&qpc); - i64 target_qpc = (i64)(qpc.QuadPart + seconds * qpc_per_second); - - /* TODO: Calculate tolerance */ - - /* TODO: Maybe increase tolerance for higher precision but more power usage */ - //const double tolerance = 1.02; - const double tolerance = 0.52 * scheduler_period_ms; - - /* Sleep */ - f64 sleep_ms = (seconds * 1000) - tolerance; - i32 sleep_slices = (i32)(sleep_ms / scheduler_period_ms); - if (sleep_slices > 0) { - __profscope(Legacy sleep part); - Sleep((DWORD)sleep_slices * scheduler_period_ms); - } - QueryPerformanceCounter(&qpc); - - /* Spin for any remaining time */ - { - __profscope(Legacy sleep spin); - while (qpc.QuadPart < target_qpc) { - YieldProcessor(); - QueryPerformanceCounter(&qpc); - } - } -} - -void sys_sleep_precise(f64 seconds) -{ - __prof; - HANDLE timer = win32_thread_get_tls()->sleep_timer; - if (timer) { - /* Use newer sleeping method */ - win32_precise_sleep_timer(seconds, timer); - } else { - /* Fall back to older sleep method if CREATE_WAITABLE_TIMER_HIGH_RESOLUTION - * is not available due to older windows version */ - win32_precise_sleep_legacy(seconds); - } -} - -void sys_sleep(f64 seconds) -{ - __prof; - u32 ms = max_u32(1, math_round_to_int((f32)(seconds * 1000.0))); - Sleep(ms); -} - -/* ========================== * - * Command line - * ========================== */ - -b32 sys_run_command(struct string cmd) -{ - b32 success = false; - { - struct arena_temp scratch = scratch_begin_no_conflict(); - wchar_t *cmd_wstr = wstr_from_string(scratch.arena, cmd); - STARTUPINFO si = ZI; - si.cb = sizeof(si); - PROCESS_INFORMATION pi = ZI; - success = CreateProcessW(NULL, cmd_wstr, NULL, NULL, FALSE, DETACHED_PROCESS, NULL, NULL, &si, &pi); - scratch_end(scratch); - } - return success; -} - - - - - - - - - - - - - - - - - - - - - - - -#if 0 - -/* ========================== * - * Testing - * ========================== */ - -#define FIBER_STACK_SIZE MEGABYTE(4) - -#define TJOB_DEF(name, arg) void name(void *arg) -typedef TJOB_DEF(tjob_func, arg); - -enum fiber_yield_reason { - FIBER_YIELD_REASON_NONE, - - FIBER_YIELD_REASON_SLEEP, - - FIBER_YIELD_REASON_DONE -}; - -struct fiber { - const char *name; - void *addr; - - struct tjob *current_job; - - enum fiber_yield_reason yield_reason; -}; - -struct tjob { - tjob_func *func; - void *arg; - - struct tjob *next; -}; - - -#define NUM_RUNNERS 6 -GLOBAL struct { - - struct atomic_i32 fibers_lock; - struct arena *fibers_arena; - - - - - - void *runner_fiber_addr; - struct fiber f1; - - struct arena *arena; - b32 shutdown; - struct atomic_i32 lock; - struct tjob *first_free_job; - struct tjob *first_job; - struct tjob *last_job; -} g_test; - - - -INTERNAL void atomic_lock(void) -{ - while (atomic_i32_fetch_test_set(&g_test.lock, 0, 1) != 0) { - ix_pause(); - } -} - -INTERNAL void atomic_unlock(void) -{ - atomic_i32_fetch_set(&g_test.lock, 0); -} - -INTERNAL void push_job(tjob_func *func, void *arg) -{ - atomic_lock(); - { - struct tjob *job = NULL; - if (g_test.first_free_job) { - job = g_test.first_free_job; - g_test.first_free_job = job->next; - } else { - job = arena_push_no_zero(g_test.arena, struct tjob); - } - *job = (struct tjob) { .func = func, .arg = arg }; - if (g_test.last_job) { - g_test.last_job->next = job; - } else { - g_test.first_job = job; - } - g_test.last_job = job; - } - atomic_unlock(); -} - - - - - - -INTERNAL void yield(struct fiber *fiber, enum fiber_yield_reason reason) -{ - fiber->yield_reason = reason; - __prof_fiber_leave; - SwitchToFiber(g_test.runner_fiber_addr); - __prof_fiber_enter(fiber->name); -} - -INTERNAL TJOB_DEF(test_job, arg) -{ - __prof; - struct fiber *fiber = arg; - (UNUSED)fiber; - (UNUSED)yield; - -#if 1 - Sleep(50); - yield(fiber, FIBER_YIELD_REASON_SLEEP); - Sleep(50); -#else - Sleep(50); -#endif -} - - - - - - - -INTERNAL void fiber_proc(void *vfiber) -{ - DEBUGBREAKABLE; - struct fiber *fiber = vfiber; - (UNUSED)fiber; - while (true) { - __prof_fiber_enter(fiber->name); - { - fiber->yield_reason = FIBER_YIELD_REASON_NONE; - fiber->current_job->func(fiber->current_job->arg); - fiber->yield_reason = FIBER_YIELD_REASON_DONE; - } - __prof_fiber_leave; - SwitchToFiber(g_test.runner_fiber_addr); - } -} - -struct runner_thread_param { - i32 id; -}; - -INTERNAL DWORD WINAPI runner_thread_proc(LPVOID tparam) -{ - __prof; - struct runner_thread_param *param = tparam; - struct arena *arena = arena_alloc(GIGABYTE(64)); - - /* Set thread name */ - { - struct string id_str = string_from_int(arena, param->id, 10, 1); - struct string name = string_format(arena, LIT("Runner thread %F"), FMT_STR(id_str)); - wchar_t *name_wstr = wstr_from_string(arena, name); - SetThreadDescription(GetCurrentThread(), name_wstr); - } - - g_test.runner_fiber_addr = ConvertThreadToFiber(NULL); - g_test.f1.name = "Fiber 1"; - g_test.f1.addr = CreateFiber(FIBER_STACK_SIZE, fiber_proc, &g_test.f1); - - b32 shutdown = false; - while (!shutdown) { - Sleep(100); - struct arena_temp temp = arena_temp_begin(arena); - - atomic_lock(); - shutdown = g_test.shutdown; - if (!shutdown && g_test.first_job) { - /* Pull job */ - struct tjob local_job = ZI; - { - struct tjob *job = g_test.first_job; - local_job = *job; - struct tjob *next = job->next; - g_test.first_job = next; - if (!next) { - g_test.last_job = NULL; - } - } - /* Run job */ - atomic_unlock(); - { - __profscope(Run job); - struct fiber *fiber = &g_test.f1; - fiber->current_job = &local_job; - fiber->yield_reason = FIBER_YIELD_REASON_NONE; - b32 done = false; - while (!done) { - SwitchToFiber(fiber->addr); - enum fiber_yield_reason yield_reason = fiber->yield_reason; - switch (yield_reason) { - default: break; - - case FIBER_YIELD_REASON_SLEEP: - { - __profscope(Sleep); - Sleep(100); - } break; - - case FIBER_YIELD_REASON_DONE: - { - done = true; - } break; - } - } - } - atomic_lock(); - } - atomic_unlock(); - arena_temp_end(temp); - } - - arena_release(arena); - return 0; -} - -INTERNAL void test_main(struct string cmdline) -{ - __prof; - (UNUSED)cmdline; - struct arena *arena = arena_alloc(GIGABYTE(64)); - { - g_test.arena = arena_alloc(GIGABYTE(64)); - - Sleep(1000); - for (u32 i = 0; i < 50; ++i) { - push_job(test_job, &g_test.f1); - } - - u32 num_runners = NUM_RUNNERS; - HANDLE *runner_thread_handles = arena_push_array(arena, HANDLE, num_runners); - struct runner_thread_param *tparams = arena_push_array(arena, struct runner_thread_param, num_runners); - for (u32 i = 0; i < num_runners; ++i) { - struct runner_thread_param *tparam = &tparams[i]; - tparam->id = i; - runner_thread_handles[i] = CreateThread(NULL, MEGABYTE(1), runner_thread_proc, tparam, 0, NULL); - //SetThreadDescription(GetCurrentThread(), t->thread_name_wstr); - } - - struct sys_thread *runner_thread = sys_thread_alloc(runner_thread_entry_point, NULL, LIT("Runner thread")); - Sleep(5000); - - { - atomic_lock(); - g_test.shutdown = true; - atomic_unlock(); - } - - for (u32 i = 0; i < num_runners; ++i) { - HANDLE handle = runner_thread_handles[i]; - DWORD wait_res = WaitForSingleObject(handle, INFINITE); - if (wait_res == WAIT_OBJECT_0) { - CloseHandle(handle); - } - } - } - arena_release(arena); -} - -#endif - - - - - - - - - - - - - - - -/* ========================== * - * Entry point - * ========================== */ - -INTERNAL SYS_THREAD_DEF(win32_app_thread_entry_point, arg) -{ - (UNUSED)arg; - struct arena_temp scratch = scratch_begin_no_conflict(); - struct string cmdline_args = string_from_wstr(scratch.arena, G.cmdline_args_wstr, countof(G.cmdline_args_wstr)); - app_entry_point(cmdline_args); - scratch_end(scratch); -} - -int CALLBACK wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev_instance, _In_ LPWSTR cmdline_wstr, _In_ int show_code) -{ - (UNUSED)instance; - (UNUSED)prev_instance; - (UNUSED)cmdline_wstr; - (UNUSED)show_code; - - u64 cmdline_len = wstr_len(cmdline_wstr, countof(G.cmdline_args_wstr) - 1); - MEMCPY(G.cmdline_args_wstr, cmdline_wstr, cmdline_len * sizeof(*cmdline_wstr)); - G.cmdline_args_wstr[cmdline_len] = 0; - - const wchar_t *error_msg = NULL; - - /* ========================== * - * Win32 setup - * ========================== */ - - G.main_thread_id = GetCurrentThreadId(); - SetThreadDescription(GetCurrentThread(), L"Main thread"); - - /* Set up panic event */ - G.panic_event = CreateEventW(NULL, true, false, NULL); - - /* Query system info */ - GetSystemInfo(&G.info); - - LARGE_INTEGER qpf; - QueryPerformanceFrequency(&qpf); - G.qpc_per_second = qpf.QuadPart; - G.ns_per_qpc = 1000000000 / qpf.QuadPart; - - LARGE_INTEGER qpc; - QueryPerformanceCounter(&qpc); - G.timer_start_qpc = qpc.QuadPart; - - TIMECAPS caps; - timeGetDevCaps(&caps, sizeof(caps)); - G.scheduler_period_ms = (i32)caps.wPeriodMin; - - /* Set up timing period */ - timeBeginPeriod(G.scheduler_period_ms); - - /* Setup mutexes */ - G.mutexes_arena = arena_alloc(GIGABYTE(64)); - struct win32_mutex *first_mutex = arena_push(G.mutexes_arena, struct win32_mutex); - win32_mutex_init(first_mutex); - G.mutexes_mutex = (struct sys_mutex *)first_mutex; - - /* Set up condition variables */ - G.condition_variables_mutex = sys_mutex_alloc(); - G.condition_variables_arena = arena_alloc(GIGABYTE(64)); - - /* Set up threads */ - G.threads_mutex = sys_mutex_alloc(); - G.threads_arena = arena_alloc(GIGABYTE(64)); - - /* Set up watches */ - G.watches_mutex = sys_mutex_alloc(); - G.watches_arena = arena_alloc(GIGABYTE(64)); - - /* Set up windows */ - G.windows_mutex = sys_mutex_alloc(); - G.windows_arena = arena_alloc(GIGABYTE(64)); - - /* Set up TLS index */ - G.thread_tls_index = TlsAlloc(); - if (G.thread_tls_index == TLS_OUT_OF_INDEXES) { - /* TODO: GetLastError */ - error_msg = L"Platform initialization error: TLS_OUT_OF_INDEXES"; - goto abort; - } - - /* Initialize vk table */ - win32_init_vk_btn_table(); - - /* Create window class */ - { - /* Register the window class */ - WNDCLASSEXW *wc = &G.window_class; - wc->cbSize = sizeof(WNDCLASSEX); - wc->lpszClassName = WINDOW_CLASS_NAME; - wc->hCursor = LoadCursor(NULL, IDC_ARROW); - wc->style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; - //wc->hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); - wc->lpfnWndProc = win32_window_proc; - wc->hInstance = instance; - - /* Use first icon resource as window icon (same as explorer) */ - wchar_t path[4096] = ZI; - GetModuleFileNameW(instance, path, countof(path)); - ExtractIconExW(path, 0, &wc->hIcon, &wc->hIconSm, 1); - - if (!RegisterClassExW(wc)) { - /* TODO: GetLastError */ - error_msg = L"Failed to register window class"; - goto abort; - } - } - - /* Register raw input */ - { - RAWINPUTDEVICE rid = (RAWINPUTDEVICE) { - .usUsagePage = 0x01, /* HID_USAGE_PAGE_GENERIC */ - .usUsage = 0x02, /* HID_USAGE_GENERIC_MOUSE */ - //.dwFlags = RIDEV_NOLEGACY /* Adds mouse and also ignores legacy mouse messages */ - }; - - if (!RegisterRawInputDevices(&rid, 1, sizeof(rid))) { - /* TODO: GetLastError */ - error_msg = L"Failed to register raw input device"; - goto abort; - } - } - - /* ========================== * - * App thread setup - * ========================== */ - - /* Initialize main thread context. */ - struct win32_tls main_thread_tls = win32_tls_alloc(); - win32_thread_set_tls(&main_thread_tls); - - /* Call app thread and wait for return */ - { - /* Start app thread */ - struct sys_thread *app_thread = sys_thread_alloc(&win32_app_thread_entry_point, NULL, LIT("[P0] App thread")); - - /* Get app thread handle */ - HANDLE app_thread_handle = 0; - struct sys_lock lock = sys_mutex_lock_s(G.threads_mutex); - { - struct win32_thread *wt = (struct win32_thread *)app_thread; - app_thread_handle = wt->handle; - } - sys_mutex_unlock(&lock); - - /* Wait for either app thread exit or panic */ - if (app_thread_handle) { - HANDLE wait_handles[] = { - app_thread_handle, - G.panic_event - }; - DWORD res = WaitForMultipleObjects(countof(wait_handles), wait_handles, false, INFINITE); - if (res == WAIT_OBJECT_0) { - sys_thread_force_release(app_thread); - } - ASSERT(res != WAIT_FAILED); - (UNUSED)res; - } - } - - /* Find any dangling threads that haven't exited gracefully by now */ - if (!atomic_i32_fetch(&G.panicking)) { - struct sys_lock lock = sys_mutex_lock_s(G.threads_mutex); - if (G.threads_first) { - struct arena_temp scratch = scratch_begin_no_conflict(); - u64 num_dangling_threads = 0; - struct string threads_msg = ZI; - threads_msg.text = arena_push_dry(scratch.arena, u8); - for (struct win32_thread *t = G.threads_first; t; t = t->next) { - struct string name = string_from_cstr(t->thread_name_cstr, countof(t->thread_name_cstr)); - threads_msg.len += string_format(scratch.arena, LIT(" \"%F\"\n"), FMT_STR(name)).len; - ++num_dangling_threads; - } - threads_msg = string_format(scratch.arena, LIT("%F dangling thread(s):\n%F"), FMT_UINT(num_dangling_threads), FMT_STR(threads_msg)); - sys_panic(threads_msg); - scratch_end(scratch); - } - sys_mutex_unlock(&lock); - } - - /* Check if panicking */ - if (atomic_i32_fetch(&G.panicking)) { - /* Wait for panic message to be ready */ - WaitForSingleObject(G.panic_event, INFINITE); - /* Set error and abort */ - error_msg = G.panic_wstr; - goto abort; - } - - /* Release main thread context */ - win32_tls_release(&main_thread_tls); - - /* ========================== * - * Abort - * ========================== */ - - abort: - - if (error_msg) { - MessageBoxExW(NULL, error_msg, L"Fatal error", MB_ICONSTOP | MB_SETFOREGROUND | MB_TOPMOST, 0); - ASSERT(false); - return 1; - } - -#if PROFILING - /* Launch profiler */ - if (!__prof_is_connected()) { - __profscope(Launch profiler); - STARTUPINFO si = ZI; - si.cb = sizeof(si); - PROCESS_INFORMATION pi = ZI; - wchar_t cmd[sizeof(PROFILING_CMD_WSTR)] = ZI; - MEMCPY(cmd, PROFILING_CMD_WSTR, sizeof(PROFILING_CMD_WSTR)); - b32 success = CreateProcessW(NULL, cmd, NULL, NULL, FALSE, DETACHED_PROCESS, NULL, NULL, &si, &pi); - if (success) { - while (!__prof_is_connected()) { - ix_pause(); - } - } else { - MessageBoxExW(NULL, L"Failed to launch tracy profiler using command " PROFILING_CMD_WSTR, L"Error", MB_ICONSTOP | MB_SETFOREGROUND | MB_TOPMOST, 0); - } - } -#endif - - return 0; -} - -/* ========================== * - * CRT Stub - * ========================== */ - -#if !CRTLIB - -#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) -{ - int result = wWinMain(GetModuleHandle(0), 0, GetCommandLineW(), 0); - ExitProcess(result); -} - -#pragma clang diagnostic pop - -#endif /* !CRTLIB */ - -#endif diff --git a/src/sys_win32.c b/src/sys_win32.c index 0057d4d8..1d0cf428 100644 --- a/src/sys_win32.c +++ b/src/sys_win32.c @@ -1,5 +1,3 @@ -#if FIBERS_TEST - #include "sys.h" #include "memory.h" #include "arena.h" @@ -32,6 +30,9 @@ #define SYS_WINDOW_EVENT_LISTENERS_MAX 512 #define WINDOW_CLASS_NAME L"power_play_window_class" +#define THREAD_STACK_SIZE MEGABYTE(4) +#define FIBER_STACK_SIZE MEGABYTE(4) + struct win32_mutex { SRWLOCK srwlock; struct win32_mutex *next_free; @@ -59,6 +60,7 @@ struct win32_thread { void *thread_data; char thread_name_cstr[256]; wchar_t thread_name_wstr[256]; + i32 profiler_group; struct win32_thread *next; struct win32_thread *prev; @@ -112,18 +114,84 @@ struct win32_window { +#define FIBER_NAME_PREFIX_CSTR "[F] Fiber " +#define FIBER_NAME_MAX_SIZE 64 +enum yield_kind { + YIELD_KIND_NONE, + YIELD_KIND_DONE, + YIELD_KIND_COOPERATIVE, + YIELD_KIND_SLEEP, + + NUM_YIELD_KINDS +}; + +struct alignas(64) fiber { + char *name_cstr; /* 8 bytes */ + /* ========================================================================= */ + i32 id; /* 4 bytes */ + i32 parent_id; /* 4 bytes */ + /* ========================================================================= */ + void *addr; /* 8 bytes */ + /* ========================================================================= */ + sys_job_func *job_func; /* 8 bytes */ + /* ========================================================================= */ + void *job_sig; /* 8 bytes */ + /* ========================================================================= */ + i32 job_id; /* 4 bytes */ + u8 pad0[4]; /* 4 bytes (padding) */ + /* ========================================================================= */ + enum yield_kind yield_kind; /* 4 bytes */ + u8 pad1[4]; /* 4 bytes (padding) */ + /* ========================================================================= */ + u8 pad_end[8]; /* 8 bytes (padding) */ +}; +STATIC_ASSERT(sizeof(struct fiber) == 64); /* Assume fiber fits in one cache line (increase if necessary) */ +STATIC_ASSERT(alignof(struct fiber) == 64); /* Avoid false sharing */ -#define FIBER_CTX_SLEEP_TIMER_INIT_MAGIC ((HANDLE)0x48e87857650169c8) struct alignas(64) fiber_ctx { - HANDLE sleep_timer; /* 8 bytes */ + /* ========================================================================= */ struct sys_scratch_ctx scratch_ctx; /* 16 bytes */ - u8 pad[40]; /* 40 bytes */ + /* ========================================================================= */ + u8 pad[40]; /* 40 bytes (padding) */ }; STATIC_ASSERT(sizeof(struct fiber_ctx) == 64); /* Assume ctx fits in one cache line (increase if necessary) */ STATIC_ASSERT(alignof(struct fiber_ctx) == 64); /* Avoid false sharing */ +struct alignas(64) runner_ctx { + i32 id; + HANDLE sleep_timer; +}; + +struct job_info { + i32 num_dispatched; + + i32 count; + sys_job_func *func; + void *sig; + + struct job_info *next; +}; + +struct alignas(64) job_queue { + struct atomic_i32 lock; + struct arena *arena; + + struct job_info *first; + struct job_info *last; + + struct job_info *first_free; +}; + +enum job_queue_kind { + JOB_QUEUE_KIND_HIGH_PRIORITY, + JOB_QUEUE_KIND_NORMAL_PRIORITY, + JOB_QUEUE_KIND_BACKGROUND, + + NUM_JOB_QUEUE_KINDS +}; + /* ========================== * @@ -180,30 +248,123 @@ GLOBAL struct { /* Fibers */ - struct atomic_i32 num_fibers; + i32 num_fibers; + i32 first_free_fiber_id; + struct arena *fiber_names_arena; + alignas(64) struct atomic_i32 fibers_lock; + struct fiber fibers[SYS_MAX_FIBERS]; struct fiber_ctx fiber_contexts[SYS_MAX_FIBERS]; + /* Jobs */ + struct job_queue job_queues[NUM_JOB_QUEUE_KINDS]; + + /* Runners */ + struct atomic_i32 runners_shutdown; + i32 num_runner_threads; + struct arena *runner_threads_arena; + struct sys_thread **runner_threads; + struct runner_ctx *runner_contexts; + + struct atomic_i32 runner_wake_gen; } G = ZI, DEBUG_ALIAS(G, G_sys_win32); - - /* ========================== * * Fibers * ========================== */ -INTERNAL i32 fiber_ctx_init(void) +enum fiber_kind { + FIBER_KIND_CONVERTED_THREAD, + FIBER_KIND_JOB_RUNNER +}; + +INTERNAL void job_fiber_entry(void *id_ptr); + +INTERNAL struct fiber *fiber_alloc(enum fiber_kind kind) { - i32 id = atomic_i32_fetch_add(&G.num_fibers, 1); - if (id >= SYS_MAX_FIBERS) { - sys_panic(LIT("Max fibers reached")); - } - struct fiber_ctx *ctx = &G.fiber_contexts[id]; + i32 fiber_id = 0; + struct fiber *fiber = NULL; + char *new_name_cstr = NULL; { - ctx->sleep_timer = FIBER_CTX_SLEEP_TIMER_INIT_MAGIC; + while (atomic_i32_fetch_test_set(&G.fibers_lock, 0, 1) != 0) ix_pause(); + { + fiber_id = G.first_free_fiber_id; + if (fiber_id && kind == FIBER_KIND_JOB_RUNNER) { + fiber = &G.fibers[fiber_id]; + G.first_free_fiber_id = fiber->parent_id; + } else { + fiber_id = G.num_fibers++; + if (fiber_id >= SYS_MAX_FIBERS) { + sys_panic(LIT("Max fibers reached")); + } + fiber = &G.fibers[fiber_id]; + new_name_cstr = arena_push_array(G.fiber_names_arena, char, FIBER_NAME_MAX_SIZE); + } + } + atomic_i32_fetch_set(&G.fibers_lock, 0); } - return id; + if (new_name_cstr != NULL) { + fiber->id = fiber_id; + + /* Id to ASCII */ + i32 id_div = fiber_id; + char id_chars[64] = ZI; + i32 id_chars_len = 0; + do { + i32 digit = id_div % 10; + id_div /= 10; + id_chars[id_chars_len] = ("0123456789")[digit]; + ++id_chars_len; + } while (id_div > 0); + i32 rev_start = 0; + i32 rev_end = id_chars_len - 1; + while (rev_start < rev_end) { + char a = id_chars[rev_start]; + char b = id_chars[rev_end]; + id_chars[rev_start] = b; + id_chars[rev_end] = a; + ++rev_start; + --rev_end; + } + + /* Concat fiber name */ + i32 name_size = 1; + STATIC_ASSERT(sizeof(sizeof(FIBER_NAME_PREFIX_CSTR)) <= FIBER_NAME_MAX_SIZE); + MEMCPY(new_name_cstr, FIBER_NAME_PREFIX_CSTR, sizeof(FIBER_NAME_PREFIX_CSTR)); + name_size += sizeof(FIBER_NAME_PREFIX_CSTR) - 2; + MEMCPY(new_name_cstr + name_size, id_chars, id_chars_len); + fiber->name_cstr = new_name_cstr; + + /* Init win32 fiber */ + if (kind == FIBER_KIND_JOB_RUNNER) { + fiber->addr = CreateFiber(FIBER_STACK_SIZE, job_fiber_entry, (void *)(i64)fiber_id); + } else { + fiber->addr = ConvertThreadToFiber((void *)(i64)fiber_id); + } + } + fiber->job_func = 0; + fiber->job_sig = 0; + fiber->job_id = 0; + fiber->yield_kind = 0; + fiber->parent_id = 0; + return fiber; +} + +INTERNAL void fiber_release(struct fiber *fiber, i32 fiber_id) +{ + while (atomic_i32_fetch_test_set(&G.fibers_lock, 0, 1) != 0) ix_pause(); + { + fiber->parent_id = G.first_free_fiber_id; + G.first_free_fiber_id = fiber_id; + } + atomic_i32_fetch_set(&G.fibers_lock, 0); +} + +INTERNAL struct fiber *fiber_from_id(i32 id) +{ + ASSERT(id >= 0 && id < SYS_MAX_FIBERS); + return &G.fibers[id]; } INTERNAL struct fiber_ctx *fiber_ctx_from_id(i32 id) @@ -212,11 +373,248 @@ INTERNAL struct fiber_ctx *fiber_ctx_from_id(i32 id) return &G.fiber_contexts[id]; } +/* ========================== * + * Test job + * ========================== */ + i32 sys_current_fiber_id(void) { return (i32)(i64)GetFiberData(); } +INTERNAL void yield(enum yield_kind kind) +{ + struct fiber *fiber = fiber_from_id(sys_current_fiber_id()); + i32 parent_fiber_id = fiber->parent_id; + if (parent_fiber_id <= 0) { + sys_panic(LIT("A top level fiber tried to yield")); + } + struct fiber *parent_fiber = fiber_from_id(parent_fiber_id); + { + __prof_fiber_leave; + fiber->yield_kind = kind; + SwitchToFiber(parent_fiber->addr); + __prof_fiber_enter(fiber->name_cstr, PROF_THREAD_GROUP_FIBERS + fiber->id); + } +} + +void sys_yield(void) +{ + yield(YIELD_KIND_COOPERATIVE); +} + +void sys_run(i32 count, sys_job_func *func, enum sys_job_priority priority) +{ + //priority = min_i32(priority, current_fiber_job_priority); /* Jobs can't create higher priority jobs */ + if (count >= 1 && func && priority >= 0 && priority < NUM_SYS_JOB_PRIORITIES) { + STATIC_ASSERT((i32)NUM_SYS_JOB_PRIORITIES == (i32)NUM_JOB_QUEUE_KINDS); /* Enums must have 1:1 mapping */ + enum job_queue_kind queue_kind = (enum job_queue_kind)priority; + struct job_queue *queue = &G.job_queues[queue_kind]; + while (atomic_i32_fetch_test_set(&queue->lock, 0, 1) != 0) ix_pause(); + { + struct job_info *info = NULL; + if (queue->first_free) { + info = queue->first_free; + queue->first_free = info->next; + } else { + info = arena_push(queue->arena, struct job_info); + } + info->count = count; + info->func = func; + if (queue->last) { + queue->last->next = info; + } else { + queue->first = info; + } + queue->last = info; + } + atomic_i32_fetch_set(&queue->lock, 0); + } else { + /* Invalid job parameters */ + sys_panic(LIT("Invalid job parameters")); + } +} + +struct bla_job_sig { + i32 _; +}; + +INTERNAL SYS_JOB_DEF(bla_job, job) +{ + __prof; + (UNUSED)job; + Sleep(20); + { + __profscope(Do tha yield); + yield(YIELD_KIND_SLEEP); + } + Sleep(20); +} + + +/* ========================== * + * Job fiber func + * ========================== */ + +INTERNAL void job_fiber_entry(void *id_ptr) +{ + i32 id = (i32)(i64)id_ptr; + struct fiber *fiber = fiber_from_id(id); + while (true) { + __prof_fiber_enter(fiber->name_cstr, PROF_THREAD_GROUP_FIBERS + fiber->id); + i32 parent_id = fiber->parent_id; + struct fiber *parent_fiber = fiber_from_id(parent_id); + void *parent_fiber_addr = parent_fiber->addr; + { + /* Run job */ + fiber->yield_kind = YIELD_KIND_NONE; + struct sys_job_data data = ZI; + data.id = fiber->job_id; + data.sig = fiber->job_sig; + fiber->job_func(data); + fiber->yield_kind = YIELD_KIND_DONE; + } + __prof_fiber_leave; + SwitchToFiber(parent_fiber_addr); + } +} + +/* ========================== * + * Test runners + * ========================== */ + +INTERNAL SYS_THREAD_DEF(runner_entry, runner_ctx_arg) +{ + struct runner_ctx *ctx = runner_ctx_arg; + (UNUSED)ctx; + + i32 runner_fiber_id = sys_current_fiber_id(); + + struct job_queue *queues[countof(G.job_queues)] = ZI; + for (u32 i = 0; i < countof(G.job_queues); ++i) { + queues[i] = &G.job_queues[i]; + } + + while (!atomic_i32_fetch(&G.runners_shutdown)) { + /* Pull job from queue */ + i32 job_id = 0; + sys_job_func *job_func = 0; + void *job_sig = 0; + for (u32 queue_index = 0; queue_index < countof(queues) && !job_func; ++queue_index) { + struct job_queue *queue = queues[queue_index]; + if (queue) { + while (atomic_i32_fetch_test_set(&queue->lock, 0, 1) != 0) ix_pause(); + struct job_info *info = queue->first; + while (info && !job_func) { + struct job_info *next = info->next; + job_id = info->num_dispatched++; + if (job_id < info->count) { + /* Pick job */ + job_func = info->func; + job_sig = info->sig; + if (job_id == (info->count - 1)) { + /* We're picking up the last dispatch, so dequeue the job */ + if (!next) { + queue->last = NULL; + } + queue->first = next; + info->next = queue->first_free; + queue->first_free = info; + } + } + info = next; + } + atomic_i32_fetch_set(&queue->lock, 0); + } + } + + /* Run job */ + if (job_func) { + __profscope(Run job); + struct fiber *fiber = fiber_alloc(FIBER_KIND_JOB_RUNNER); + fiber->job_func = job_func; + fiber->job_sig = job_sig; + fiber->job_id = job_id; + fiber->parent_id = runner_fiber_id; + b32 done = false; + while (!done) { + SwitchToFiber(fiber->addr); + enum yield_kind yield_kind = fiber->yield_kind; + switch (yield_kind) { + default: + { + /* Invalid yield kind */ + sys_panic(LIT("Fiber yielded with unknown yield kind")); + } break; + + case YIELD_KIND_SLEEP: + { + __profscope(Job sleep); + Sleep(100); + } break; + + case YIELD_KIND_DONE: + { + fiber_release(fiber, fiber->id); + done = true; + } break; + } + } + } + } +} + +INTERNAL SYS_THREAD_DEF(test_entry, _) +{ + struct arena_temp scratch = scratch_begin_no_conflict(); + (UNUSED)_; + + /* Init job queues */ + for (u32 i = 0; i < countof(G.job_queues); ++i) { + struct job_queue *queue = &G.job_queues[i]; + queue->arena = arena_alloc(GIGABYTE(64)); + } + + /* Start runners */ + G.num_runner_threads = 6; + G.runner_threads_arena = arena_alloc(GIGABYTE(64)); + G.runner_threads = arena_push_array(G.runner_threads_arena, struct sys_thread *, G.num_runner_threads); + G.runner_contexts = arena_push_array(G.runner_threads_arena, struct runner_ctx, G.num_runner_threads); + for (i32 i = 0; i < G.num_runner_threads; ++i) { + struct runner_ctx *ctx = &G.runner_contexts[i]; + ctx->id = i; + ctx->sleep_timer = CreateWaitableTimerExW(NULL, NULL, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS); + struct string id_str = string_from_int(scratch.arena, i, 10, 2); + struct string name = string_format(scratch.arena, LIT("Runner %F"), FMT_STR(id_str)); + G.runner_threads[i] = sys_thread_alloc(runner_entry, ctx, name, PROF_THREAD_GROUP_RUNNERS + i); + } + + /* Push test job */ + Sleep(300); + sys_run(2, bla_job, SYS_JOB_PRIORITY_NORMAL); + + /* Wait on runners */ + for (i32 i = 0; i < G.num_runner_threads; ++i) { + struct sys_thread *runner_thread = G.runner_threads[i]; + sys_thread_wait_release(runner_thread); + } + + scratch_end(scratch); +} + + + + + + + + + + + + + + /* ========================== * * Scratch context * ========================== */ @@ -249,6 +647,7 @@ struct sys_scratch_ctx *sys_scratch_ctx_from_fiber_id(i32 id) + /* ========================== * * Events * ========================== */ @@ -1143,7 +1542,7 @@ INTERNAL struct win32_window *win32_window_alloc(void) window->event_callbacks_mutex = sys_mutex_alloc(); /* Start window thread for processing events */ - window->event_thread = sys_thread_alloc(&window_thread_entry_point, window, LIT("[P4] Window thread")); + window->event_thread = sys_thread_alloc(&window_thread_entry_point, window, LIT("Window thread"), PROF_THREAD_GROUP_WINDOW); /* Wait for event thread to create actual window */ sync_flag_wait(&window->ready_sf); @@ -1983,10 +2382,9 @@ INTERNAL void win32_thread_release(struct win32_thread *t) INTERNAL DWORD WINAPI win32_thread_proc(LPVOID vt) { struct win32_thread *t = (struct win32_thread *)vt; - __profthread(t->thread_name_cstr); + __profthread(t->thread_name_cstr, t->profiler_group); - i32 fiber_id = fiber_ctx_init(); - ConvertThreadToFiber((void *)(i64)fiber_id); + fiber_alloc(FIBER_KIND_CONVERTED_THREAD); /* Initialize COM */ CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); @@ -2007,7 +2405,7 @@ INTERNAL DWORD WINAPI win32_thread_proc(LPVOID vt) return 0; } -struct sys_thread *sys_thread_alloc(sys_thread_func *entry_point, void *thread_data, struct string thread_name) +struct sys_thread *sys_thread_alloc(sys_thread_func *entry_point, void *thread_data, struct string thread_name, i32 profiler_group) { __prof; struct arena_temp scratch = scratch_begin_no_conflict(); @@ -2019,6 +2417,7 @@ struct sys_thread *sys_thread_alloc(sys_thread_func *entry_point, void *thread_d struct win32_thread *t = win32_thread_alloc(); t->entry_point = entry_point; t->thread_data = thread_data; + t->profiler_group = profiler_group; /* Copy thread name to params */ { @@ -2035,7 +2434,7 @@ struct sys_thread *sys_thread_alloc(sys_thread_func *entry_point, void *thread_d t->handle = CreateThread( NULL, - SYS_THREAD_STACK_SIZE, + THREAD_STACK_SIZE, win32_thread_proc, t, 0, @@ -2345,13 +2744,9 @@ INTERNAL void win32_precise_sleep_legacy(f64 seconds) void sys_sleep_precise(f64 seconds) { __prof; - struct fiber_ctx *ctx = fiber_ctx_from_id(sys_current_fiber_id()); + /* FIXME: Enable this */ +#if 0 HANDLE timer = ctx->sleep_timer; - if (timer == FIBER_CTX_SLEEP_TIMER_INIT_MAGIC) { - __profscope(Create high resolution timer); - timer = CreateWaitableTimerExW(NULL, NULL, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS); - ctx->sleep_timer = timer; - } if (timer) { /* Use newer sleeping method */ win32_precise_sleep_timer(timer, seconds); @@ -2360,6 +2755,10 @@ void sys_sleep_precise(f64 seconds) * is not available due to older windows version */ win32_precise_sleep_legacy(seconds); } +#else + (UNUSED)win32_precise_sleep_timer; + win32_precise_sleep_legacy(seconds); +#endif } void sys_sleep(f64 seconds) @@ -2409,7 +2808,6 @@ int CALLBACK wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev_instance, (UNUSED)show_code; #if PROFILING - /* Launch profiler */ { __profscope(Launch profiler); STARTUPINFO si = ZI; @@ -2420,14 +2818,19 @@ int CALLBACK wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev_instance, DeleteFileW(PROFILING_FILE_WSTR); b32 success = CreateProcessW(NULL, cmd, NULL, NULL, FALSE, DETACHED_PROCESS, NULL, NULL, &si, &pi); if (!success) { - MessageBoxExW(NULL, L"Failed to launch capture using command '" PROFILING_CMD_WSTR L"'. Is the app in your path?", L"Error", MB_ICONSTOP | MB_SETFOREGROUND | MB_TOPMOST, 0); + MessageBoxExW(NULL, L"Failed to launch profiler using command '" PROFILING_CMD_WSTR L"'.", L"Error", MB_ICONSTOP | MB_SETFOREGROUND | MB_TOPMOST, 0); } } #endif + __profthread("Main thread", PROF_THREAD_GROUP_MAIN); + + /* Init fibers */ + G.num_fibers = 1; /* Fiber at index 0 always nil */ + G.fiber_names_arena = arena_alloc(GIGABYTE(64)); + /* Convert main thread to fiber */ - i32 fiber_id = fiber_ctx_init(); - ConvertThreadToFiber((void *)(i64)fiber_id); + fiber_alloc(FIBER_KIND_CONVERTED_THREAD); u64 cmdline_len = wstr_len(cmdline_wstr, countof(G.cmdline_args_wstr) - 1); MEMCPY(G.cmdline_args_wstr, cmdline_wstr, cmdline_len * sizeof(*cmdline_wstr)); @@ -2535,7 +2938,10 @@ int CALLBACK wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev_instance, /* Call app thread and wait for return */ { /* Start app thread */ - struct sys_thread *app_thread = sys_thread_alloc(&win32_app_thread_entry_point, NULL, LIT("[P0] App thread")); + struct sys_thread *app_thread = sys_thread_alloc(&win32_app_thread_entry_point, NULL, LIT("App thread"), PROF_THREAD_GROUP_APP); + + /* Start test thread */ + struct sys_thread *test_thread = sys_thread_alloc(test_entry, NULL, LIT("Test thread"), PROF_THREAD_GROUP_APP); /* Get app thread handle */ HANDLE app_thread_handle = 0; @@ -2546,6 +2952,7 @@ int CALLBACK wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev_instance, } sys_mutex_unlock(&lock); + /* Wait for either app thread exit or panic */ if (app_thread_handle) { HANDLE wait_handles[] = { @@ -2559,6 +2966,10 @@ int CALLBACK wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev_instance, ASSERT(res != WAIT_FAILED); (UNUSED)res; } + + /* Shutdown test thread */ + atomic_i32_fetch_set(&G.runners_shutdown, 1); + sys_thread_wait_release(test_thread); } /* Find any dangling threads that haven't exited gracefully by now */ @@ -2629,6 +3040,3 @@ void __stdcall wWinMainCRTStartup(void) #pragma clang diagnostic pop #endif /* !CRTLIB */ - - -#endif diff --git a/src/ttf_dwrite.cpp b/src/ttf_dwrite.cpp index de016ca8..4e61128c 100644 --- a/src/ttf_dwrite.cpp +++ b/src/ttf_dwrite.cpp @@ -204,8 +204,6 @@ struct ttf_decode_result ttf_decode(struct arena *arena, struct string encoded, /* Compute glyph metrics */ DWRITE_GLYPH_METRICS glyph_metrics = ZI; - - error = font_face->GetDesignGlyphMetrics(&i, 1, &glyph_metrics, false); f32 off_x = (f32)bounding_box.left - raster_target_x; diff --git a/src/user.c b/src/user.c index 90e438a2..9d4a3315 100644 --- a/src/user.c +++ b/src/user.c @@ -1689,30 +1689,33 @@ INTERNAL void user_update(void) if (!G.debug_camera) { __profscope(Draw crosshair); struct v2 crosshair_pos = G.user_cursor; - - struct sprite_tag crosshair_tag = sprite_tag_from_path(LIT("sprite/crosshair.ase")); - struct sprite_texture *t = sprite_texture_from_tag_async(sprite_frame_scope, crosshair_tag); - + struct sprite_tag crosshair = sprite_tag_from_path(LIT("sprite/crosshair.ase")); + struct sprite_texture *t = sprite_texture_from_tag_async(sprite_frame_scope, crosshair); struct v2 size = V2(t->width, t->height); struct xform xf = XFORM_TRS(.t = crosshair_pos, .s = size); - draw_texture(G.ui_gp_flow, DRAW_TEXTURE_PARAMS(.xf = xf, .sprite = crosshair_tag)); - -#if 0 - struct rect cursor_clip = RECT_FROM_V2(G.user_screen_offset, G.user_size); - cursor_clip.pos = v2_add(cursor_clip.pos, v2_mul(size, 0.5f)); - cursor_clip.pos = v2_add(cursor_clip.pos, V2(1, 1)); - cursor_clip.size = v2_sub(cursor_clip.size, size); - sys_window_cursor_hide(G.window); - sys_window_cursor_enable_clip(G.window, cursor_clip); -#endif - } else { - __profscope(Update windows cursor); -#if 0 - sys_window_cursor_disable_clip(G.window); - sys_window_cursor_show(G.window); -#endif + draw_texture(G.ui_gp_flow, DRAW_TEXTURE_PARAMS(.xf = xf, .sprite = crosshair)); } + /* FIXME: Enable this */ +#if 0 + { + __profscope(Update window cursor); + if (G.debug_camera) { + sys_window_cursor_disable_clip(G.window); + sys_window_cursor_show(G.window); + } else { + struct sprite_texture *t = sprite_texture_from_tag_async(sprite_frame_scope, sprite_tag_from_path(LIT("sprite/crosshair.ase"))); + struct v2 size = V2(t->width, t->height); + struct rect cursor_clip = RECT_FROM_V2(G.user_screen_offset, G.user_size); + cursor_clip.pos = v2_add(cursor_clip.pos, v2_mul(size, 0.5f)); + cursor_clip.pos = v2_add(cursor_clip.pos, V2(1, 1)); + cursor_clip.size = v2_sub(cursor_clip.size, size); + sys_window_cursor_hide(G.window); + sys_window_cursor_enable_clip(G.window, cursor_clip); + } + } +#endif + /* ========================== * * Create user sim cmd * ========================== */