power_play/src/sys_win32.c
2025-02-19 08:51:20 -06:00

2236 lines
67 KiB
C

#include "sys.h"
#include "memory.h"
#include "app.h"
#include "string.h"
#include "arena.h"
#include "scratch.h"
#include "atomic.h"
#include "work.h"
#include "log.h"
#include "math.h"
#include "util.h"
#include "thread_local.h"
#include "uni.h"
#pragma warning(push, 0)
# define UNICODE
# include <Windows.h>
# include <windowsx.h>
# include <ShlObj_core.h>
# include <fileapi.h>
# include <dwmapi.h>
# include <bcrypt.h>
#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_thread {
sys_thread_entry_point_func *entry_point;
void *thread_data;
char thread_name_cstr[256];
struct win32_thread *next;
struct win32_thread *prev;
HANDLE handle;
};
struct win32_condition_variable {
CONDITION_VARIABLE condition_variable;
struct win32_condition_variable *next_free;
};
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];
/* Condition variables */
struct sys_mutex condition_variables_mutex;
struct arena condition_variables_arena;
struct win32_condition_variable *first_free_condition_variable;
/* Thread params */
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;
/* Windows */
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(&lt);
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_dry_push(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(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 temp_arena 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 temp_arena 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 temp_arena 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 temp_arena scratch = scratch_begin_no_conflict();
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
);
scratch_end(scratch);
struct sys_file file = { (u64)handle };
return file;
}
struct sys_file sys_file_open_read_wait(struct string path)
{
__prof;
struct temp_arena scratch = scratch_begin_no_conflict();
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;
}
}
scratch_end(scratch);
return (struct sys_file) { (u64)handle };
}
struct sys_file sys_file_open_write(struct string path)
{
__prof;
struct temp_arena scratch = scratch_begin_no_conflict();
wchar_t *path_wstr = wstr_from_string(scratch.arena, path);
/* TODO: Handle errors / non-existing file (handle == INVALID_HANDLE_VALUE) */
HANDLE handle = CreateFileW(
path_wstr,
GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL
);
scratch_end(scratch);
return (struct sys_file) { (u64)handle };
}
struct sys_file sys_file_open_append(struct string path)
{
__prof;
struct temp_arena scratch = scratch_begin_no_conflict();
wchar_t *path_wstr = wstr_from_string(scratch.arena, path);
/* TODO: Handle errors / non-existing file (handle == INVALID_HANDLE_VALUE) */
HANDLE handle = CreateFileW(
path_wstr,
FILE_APPEND_DATA,
FILE_SHARE_READ,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL
);
scratch_end(scratch);
struct sys_file file = (struct sys_file) { (u64)handle };
return file;
}
void sys_file_close(struct sys_file file)
{
__prof;
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(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 temp_arena 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;
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) {
ASSERT(false);
return (struct sys_file_map) { 0 };
}
base_ptr = MapViewOfFile(
map_handle,
FILE_MAP_READ,
0,
0,
0
);
if (!base_ptr) {
/* Failed to create view */
ASSERT(false);
CloseHandle(map_handle);
return (struct sys_file_map) { 0 };
}
} else {
/* File is empty */
ASSERT(false);
return (struct sys_file_map) { 0 };
}
return (struct sys_file_map) {
.handle = (u64)map_handle,
.mapped_memory = STRING(size, base_ptr)
};
}
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_zero(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);
}
}
/* ========================== *
* 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_ENTRY_POINT_FUNC_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_eval(&window->event_thread_shutdown)) {
MSG msg = ZI;
GetMessageW(&msg, 0, 0, 0);
if (atomic_i32_eval(&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(&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_eval_exchange(&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);
{
struct temp_arena 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)
{
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_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 < ARRAY_COUNT(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 = ARRAY_COUNT(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_M3;
} else if (wparam_xbutton == XBUTTON2) {
button = SYS_BTN_M4;
}
} 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 temp_arena 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_zero(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 = (RAWINPUT *)buff;
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;
struct sys_window w = {
.handle = (u64)win32_window_alloc()
};
return w;
}
void sys_window_release(struct sys_window *sys_window)
{
__prof;
struct win32_window *window = (struct win32_window *)sys_window->handle;
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->handle;
struct sys_lock lock = sys_mutex_lock_e(&window->event_callbacks_mutex);
{
if (window->event_callbacks_count + 1 > ARRAY_COUNT(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->handle;
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->handle;
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->handle;
return window->settings;
}
void sys_window_show(struct sys_window *sys_window)
{
struct win32_window *window = (struct win32_window *)sys_window->handle;
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->handle;
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->handle;
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->handle;
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->handle;
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->handle;
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->handle;
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->handle;
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->handle;
window->cursor_set_flags |= WIN32_WINDOW_CURSOR_SET_FLAG_DISABLE_CLIP;
win32_window_wake(window);
}
/* ========================== *
* Mutex
* ========================== */
struct sys_mutex sys_mutex_alloc(void)
{
__prof;
struct sys_mutex mutex = ZI;
__proflock_alloc(mutex.profiling_ctx);
SRWLOCK srwlock = SRWLOCK_INIT;
mutex.handle = *(u64 *)&srwlock;
return mutex;
}
void sys_mutex_release(struct sys_mutex *mutex)
{
__prof;
(UNUSED)mutex;
__proflock_release(mutex->profiling_ctx);
/* Mutex should be unlocked */
ASSERT(atomic_i64_eval(&mutex->count) == 0);
}
struct sys_lock sys_mutex_lock_e(struct sys_mutex *mutex)
{
__prof;
__proflock_before_exclusive_lock(mutex->profiling_ctx);
AcquireSRWLockExclusive((SRWLOCK *)&mutex->handle);
__proflock_after_exclusive_lock(mutex->profiling_ctx);
#if RTC
mutex->owner_tid = (u64)GetCurrentThreadId();
atomic_i64_inc_eval(&mutex->count);
#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)
{
__prof;
__proflock_before_shared_lock(mutex->profiling_ctx);
AcquireSRWLockShared((SRWLOCK *)&mutex->handle);
__proflock_after_shared_lock(mutex->profiling_ctx);
#if RTC
atomic_i64_inc_eval(&mutex->count);
#endif
struct sys_lock lock = ZI;
lock.mutex = mutex;
return lock;
}
void sys_mutex_unlock(struct sys_lock *lock)
{
__prof;
#if RTC
atomic_i64_dec_eval(&lock->mutex->count);
lock->mutex->owner_tid = 0;
#endif
if (lock->exclusive) {
ReleaseSRWLockExclusive((SRWLOCK *)&lock->mutex->handle);
__proflock_after_exclusive_unlock(lock->mutex->profiling_ctx);
} else {
ReleaseSRWLockShared((SRWLOCK *)&lock->mutex->handle);
__proflock_after_shared_unlock(lock->mutex->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_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_zero(&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;
struct sys_condition_variable cv = {
.handle = (u64)win32_condition_variable_alloc()
};
return cv;
}
void sys_condition_variable_release(struct sys_condition_variable *cv)
{
__prof;
/* Condition variable must not have any sleepers (signal before releasing) */
ASSERT(atomic_i64_eval(&cv->num_waiters) == 0);
win32_condition_variable_release((struct win32_condition_variable *)cv->handle);
}
void sys_condition_variable_wait(struct sys_condition_variable *cv, struct sys_lock *lock)
{
__prof;
struct sys_mutex *mutex = lock->mutex;
b32 exclusive = lock->exclusive;
#if RTC
atomic_i64_inc_eval(&cv->num_waiters);
if (exclusive) {
mutex->owner_tid = 0;
}
atomic_i64_dec_eval(&mutex->count);
#endif
struct win32_condition_variable *w32cv = (struct win32_condition_variable *)cv->handle;
/* TODO: Correct profiling of internal condition variable sleep / wait mutex state */
if (exclusive) {
__proflock_after_exclusive_unlock(mutex->profiling_ctx);
} else {
__proflock_after_shared_unlock(mutex->profiling_ctx);
}
SleepConditionVariableSRW(&w32cv->condition_variable, (SRWLOCK *)&mutex->handle, INFINITE, exclusive ? 0 : CONDITION_VARIABLE_LOCKMODE_SHARED);
if (exclusive) {
__proflock_before_exclusive_lock(mutex->profiling_ctx);
__proflock_after_exclusive_lock(mutex->profiling_ctx);
} else {
__proflock_before_shared_lock(mutex->profiling_ctx);
__proflock_after_shared_lock(mutex->profiling_ctx);
}
#if RTC
atomic_i64_inc_eval(&mutex->count);
if (exclusive) {
mutex->owner_tid = (u64)GetCurrentThreadId();
}
atomic_i64_dec_eval(&cv->num_waiters);
#endif
}
void sys_condition_variable_wait_time(struct sys_condition_variable *cv, struct sys_lock *lock, f64 seconds)
{
__prof;
struct sys_mutex *mutex = lock->mutex;
b32 exclusive = lock->exclusive;
#if RTC
atomic_i64_inc_eval(&cv->num_waiters);
if (exclusive) {
mutex->owner_tid = 0;
}
atomic_i64_dec_eval(&mutex->count);
#endif
struct win32_condition_variable *w32cv = (struct win32_condition_variable *)cv->handle;
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(mutex->profiling_ctx);
} else {
__proflock_after_shared_unlock(mutex->profiling_ctx);
}
SleepConditionVariableSRW(&w32cv->condition_variable, (SRWLOCK *)&mutex->handle, ms, exclusive ? 0 : CONDITION_VARIABLE_LOCKMODE_SHARED);
if (exclusive) {
__proflock_before_exclusive_lock(mutex->profiling_ctx);
__proflock_after_exclusive_lock(mutex->profiling_ctx);
} else {
__proflock_before_shared_lock(mutex->profiling_ctx);
__proflock_after_shared_lock(mutex->profiling_ctx);
}
#if RTC
atomic_i64_inc_eval(&mutex->count);
if (exclusive) {
mutex->owner_tid = (u64)GetCurrentThreadId();
}
atomic_i64_dec_eval(&cv->num_waiters);
#endif
}
void sys_condition_variable_signal(struct sys_condition_variable *cv, u32 count)
{
__prof;
struct win32_condition_variable *w32cv = (struct win32_condition_variable *)cv->handle;
/* 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(&w32cv->condition_variable);
}
} else {
WakeAllConditionVariable(&w32cv->condition_variable);
}
}
void sys_condition_variable_broadcast(struct sys_condition_variable *cv)
{
__prof;
struct win32_condition_variable *w32cv = (struct win32_condition_variable *)cv->handle;
WakeAllConditionVariable(&w32cv->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)
{
/* TODO: Fail if error */
TlsSetValue(G.thread_tls_index, (LPVOID)ctx);
}
INTERNAL struct win32_tls *win32_thread_get_tls(void)
{
/* TODO: Fail if error */
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(&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 */
struct string thread_name = string_from_cstr_no_limit(t->thread_name_cstr);
if (thread_name.len > 0) {
/* FIXME: Don't use scratch arena here, to avoid scratch TLS being initialized for threads that don't need them otherwise */
struct temp_arena scratch = scratch_begin_no_conflict();
wchar_t *wc_thread_name = wstr_from_string(scratch.arena, thread_name);
SetThreadDescription(GetCurrentThread(), wc_thread_name);
scratch_end(scratch);
}
logf_info("New thread \"%F\" created with ID %F", FMT_STR(thread_name), 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_entry_point_func *entry_point, void *thread_data, struct string thread_name)
{
__prof;
ASSERT(entry_point != NULL);
logf_info("Creating thread \"%F\"", FMT_STR(thread_name));
struct sys_thread res = ZI;
/* 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 */
cstr_buff_from_string(STRING_FROM_ARRAY(t->thread_name_cstr), thread_name);
t->handle = CreateThread(
NULL,
SYS_THREAD_STACK_SIZE,
win32_thread_proc,
t,
0,
NULL
);
if (!t->handle) {
sys_panic(LIT("Failed to create thread"));
}
res.handle = (u64)t;
return res;
}
void sys_thread_wait_release(struct sys_thread *thread)
{
__prof;
struct win32_thread *t = (struct win32_thread *)thread->handle;
HANDLE handle = t->handle;
/* Wait for thread to stop */
if (handle) {
DWORD res = WaitForSingleObject(handle, INFINITE);
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 temp_arena 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: \"%F\"", 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 temp_arena 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_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_eval_compare_exchange(&G.panicking, 0, 1) == 0) {
log_panic(msg);
ASSERT(false);
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(ARRAY_COUNT(G.panic_wstr), (ARRAY_COUNT(prefix) << 1)));
wstr_len += ARRAY_COUNT(prefix) - 1;
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 < (ARRAY_COUNT(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;
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(win32_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(win32_sleep);
Sleep((DWORD)sleep_slices * scheduler_period_ms);
}
{
__profscope(win32_qpc);
QueryPerformanceCounter(&qpc);
}
/* Spin for any remaining time */
{
__profscope(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);
}
/* ========================== *
* Entry point
* ========================== */
INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(win32_app_thread_entry_point, arg)
{
(UNUSED)arg;
struct temp_arena scratch = scratch_begin_no_conflict();
struct string cmdline_args = string_from_wstr(scratch.arena, G.cmdline_args_wstr, ARRAY_COUNT(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, ARRAY_COUNT(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);
/* 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 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, ARRAY_COUNT(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.handle;
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(ARRAY_COUNT(wait_handles), wait_handles, false, INFINITE);
ASSERT(res != WAIT_FAILED);
(UNUSED)res;
}
}
/* Check if panicking */
if (atomic_i32_eval(&G.panicking)) {
/* Wait for panic message to be ready */
WaitForSingleObject(G.panic_event, INFINITE);
/* Force stop threads */
for (struct win32_thread *t = G.threads_last; t; t = t->prev) {
HANDLE handle = t->handle;
TerminateThread(handle, 0);
CloseHandle(handle);
}
/* 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;
}
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 */