1764 lines
50 KiB
C
1764 lines
50 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 <Windows.h>
|
|
#include <ShlObj_core.h>
|
|
#include <fileapi.h>
|
|
#include <dwmapi.h>
|
|
#include <NTSecAPI.h>
|
|
|
|
#define SYS_WINDOW_EVENT_LISTENERS_MAX 512
|
|
|
|
struct win32_thread_params {
|
|
sys_thread_func *thread_func;
|
|
void *thread_data;
|
|
char thread_name_cstr[256];
|
|
|
|
struct win32_thread_params *next_free;
|
|
};
|
|
|
|
struct win32_condition_variable {
|
|
CONDITION_VARIABLE condition_variable;
|
|
struct win32_condition_variable *next_free;
|
|
};
|
|
|
|
struct win32_window {
|
|
u32 flags;
|
|
|
|
HWND hwnd;
|
|
struct sync_flag ready_sf;
|
|
|
|
struct sys_mutex settings_mutex;
|
|
struct sys_window_settings settings;
|
|
|
|
i32 monitor_width;
|
|
i32 monitor_height;
|
|
i32 border_left;
|
|
i32 border_top;
|
|
/* NOTE: width & height are unaffected by window minimization (they retain
|
|
* their pre-minimized values) */
|
|
i32 x, y, width, height;
|
|
|
|
b32 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;
|
|
LARGE_INTEGER timer_frequency;
|
|
LARGE_INTEGER timer_start;
|
|
i32 scheduler_period_ms;
|
|
DWORD thread_tls_index;
|
|
|
|
/* 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 thread_params_mutex;
|
|
struct arena thread_params_arena;
|
|
struct win32_thread_params *first_free_thread_params;
|
|
|
|
/* Windows */
|
|
WNDCLASSEX window_class;
|
|
struct sys_mutex windows_mutex;
|
|
struct arena windows_arena;
|
|
struct win32_window *first_free_window;
|
|
} L = { 0 } DEBUG_LVAR(L_sys_win32);
|
|
|
|
/* ========================== *
|
|
* Events
|
|
* ========================== */
|
|
|
|
/* https://git.rfleury.com/community/root_basic/src/commit/9b49fcd24e0c3f875b7c213e81a219bf8544bddb/code/os/gfx/win32/os_gfx_win32.c#L193 */
|
|
INTERNAL void win32_init_vk_btn_table(void)
|
|
{
|
|
MEMZERO_ARRAY(L.vk_btn_table);
|
|
|
|
for (u32 i = 'A', j = SYS_BTN_A; i <= 'Z'; i += 1, j += 1) {
|
|
L.vk_btn_table[i] = (enum sys_btn)j;
|
|
}
|
|
for (u32 i = '0', j = SYS_BTN_0; i <= '9'; i += 1, j += 1) {
|
|
L.vk_btn_table[i] = (enum sys_btn)j;
|
|
}
|
|
for (u32 i = VK_F1, j = SYS_BTN_F1; i <= VK_F24; i += 1, j += 1) {
|
|
L.vk_btn_table[i] = (enum sys_btn)j;
|
|
}
|
|
L.vk_btn_table[VK_ESCAPE] = SYS_BTN_ESC;
|
|
L.vk_btn_table[VK_OEM_3] = SYS_BTN_GRAVE_ACCENT;
|
|
L.vk_btn_table[VK_OEM_MINUS] = SYS_BTN_MINUS;
|
|
L.vk_btn_table[VK_OEM_PLUS] = SYS_BTN_EQUAL;
|
|
L.vk_btn_table[VK_BACK] = SYS_BTN_BACKSPACE;
|
|
L.vk_btn_table[VK_TAB] = SYS_BTN_TAB;
|
|
L.vk_btn_table[VK_SPACE] = SYS_BTN_SPACE;
|
|
L.vk_btn_table[VK_RETURN] = SYS_BTN_ENTER;
|
|
L.vk_btn_table[VK_CONTROL] = SYS_BTN_CTRL;
|
|
L.vk_btn_table[VK_SHIFT] = SYS_BTN_SHIFT;
|
|
L.vk_btn_table[VK_MENU] = SYS_BTN_ALT;
|
|
L.vk_btn_table[VK_UP] = SYS_BTN_UP;
|
|
L.vk_btn_table[VK_LEFT] = SYS_BTN_LEFT;
|
|
L.vk_btn_table[VK_DOWN] = SYS_BTN_DOWN;
|
|
L.vk_btn_table[VK_RIGHT] = SYS_BTN_RIGHT;
|
|
L.vk_btn_table[VK_DELETE] = SYS_BTN_DELETE;
|
|
L.vk_btn_table[VK_PRIOR] = SYS_BTN_PAGE_UP;
|
|
L.vk_btn_table[VK_NEXT] = SYS_BTN_PAGE_DOWN;
|
|
L.vk_btn_table[VK_HOME] = SYS_BTN_HOME;
|
|
L.vk_btn_table[VK_END] = SYS_BTN_END;
|
|
L.vk_btn_table[VK_OEM_2] = SYS_BTN_FORWARD_SLASH;
|
|
L.vk_btn_table[VK_OEM_PERIOD] = SYS_BTN_PERIOD;
|
|
L.vk_btn_table[VK_OEM_COMMA] = SYS_BTN_COMMA;
|
|
L.vk_btn_table[VK_OEM_7] = SYS_BTN_QUOTE;
|
|
L.vk_btn_table[VK_OEM_4] = SYS_BTN_LEFT_BRACKET;
|
|
L.vk_btn_table[VK_OEM_6] = SYS_BTN_RIGHT_BRACKET;
|
|
L.vk_btn_table[VK_INSERT] = SYS_BTN_INSERT;
|
|
L.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)
|
|
{
|
|
#pragma warning(push)
|
|
#pragma warning(disable : 6250) /* Disable warning "Calling 'VirtualFree' without MEM_RELEASE flag"*/
|
|
VirtualFree(address, size, MEM_DECOMMIT);
|
|
#pragma warning(pop)
|
|
}
|
|
|
|
/* ========================== *
|
|
* Wchar
|
|
*
|
|
* TODO: Move wide char ops to string.c and actually do proper decoding
|
|
* ========================== */
|
|
|
|
INTERNAL struct string wchar_path_to_string(struct arena *arena, wchar_t *src)
|
|
{
|
|
struct string str = { 0, arena_dry_push(arena, u8) };
|
|
while (1) {
|
|
wchar_t wchar = src[str.len];
|
|
if (wchar != 0) {
|
|
if (wchar == '\\') {
|
|
wchar = '/';
|
|
}
|
|
u8 *c = arena_push(arena, u8);
|
|
/* FIXME: We're ignoring the high byte here */
|
|
*c = (u8)(wchar & 0xFF);
|
|
++str.len;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
return str;
|
|
}
|
|
|
|
/* ========================== *
|
|
* File system
|
|
* ========================== */
|
|
|
|
struct string sys_get_write_path(struct arena *arena)
|
|
{
|
|
wchar_t *p = NULL;
|
|
/* TODO: cache this? */
|
|
HRESULT res = SHGetKnownFolderPath(
|
|
&FOLDERID_LocalAppData,
|
|
0,
|
|
NULL,
|
|
&p
|
|
);
|
|
struct string path = { 0 };
|
|
if (res == S_OK) {
|
|
path = wchar_path_to_string(arena, p);
|
|
}
|
|
CoTaskMemFree(p);
|
|
return path;
|
|
}
|
|
|
|
b32 sys_is_file(struct string path)
|
|
{
|
|
__prof;
|
|
struct temp_arena scratch = scratch_begin_no_conflict();
|
|
const char *path_cstr = string_to_cstr(scratch.arena, path);
|
|
DWORD attributes = GetFileAttributes(path_cstr);
|
|
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();
|
|
const char *path_cstr = string_to_cstr(scratch.arena, path);
|
|
DWORD attributes = GetFileAttributes(path_cstr);
|
|
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();
|
|
const char *path_cstr = string_to_cstr(scratch.arena, path);
|
|
b32 success = SHCreateDirectoryExA(NULL, path_cstr, NULL) == ERROR_SUCCESS;
|
|
(UNUSED)success;
|
|
ASSERT(success);
|
|
scratch_end(scratch);
|
|
}
|
|
|
|
struct sys_file sys_file_open_read(struct string path)
|
|
{
|
|
__prof;
|
|
struct temp_arena scratch = scratch_begin_no_conflict();
|
|
const char *path_cstr = string_to_cstr(scratch.arena, path);
|
|
|
|
/* TODO: Handle errors / non-existing file (handle == INVALID_HANDLE_VALUE) */
|
|
HANDLE handle = CreateFileA(
|
|
path_cstr,
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL
|
|
);
|
|
scratch_end(scratch);
|
|
struct sys_file file = (struct sys_file) { (u64)handle };
|
|
return file;
|
|
}
|
|
|
|
struct sys_file sys_file_open_write(struct string path)
|
|
{
|
|
__prof;
|
|
struct temp_arena scratch = scratch_begin_no_conflict();
|
|
const char *path_cstr = string_to_cstr(scratch.arena, path);
|
|
HANDLE handle = CreateFileA(
|
|
path_cstr,
|
|
GENERIC_WRITE,
|
|
0,
|
|
NULL,
|
|
CREATE_ALWAYS,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL
|
|
);
|
|
/* TODO: Handle errors / non-existing file (handle == INVALID_HANDLE_VALUE) */
|
|
scratch_end(scratch);
|
|
struct sys_file file = (struct sys_file) { (u64)handle };
|
|
return file;
|
|
}
|
|
|
|
struct sys_file sys_file_open_append(struct string path)
|
|
{
|
|
__prof;
|
|
struct temp_arena scratch = scratch_begin_no_conflict();
|
|
const char *path_cstr = string_to_cstr(scratch.arena, path);
|
|
HANDLE handle = CreateFileA(
|
|
path_cstr,
|
|
FILE_APPEND_DATA,
|
|
FILE_SHARE_READ,
|
|
NULL,
|
|
OPEN_ALWAYS,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL
|
|
);
|
|
/* TODO: Handle errors / non-existing file (handle == INVALID_HANDLE_VALUE) */
|
|
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 buffer sys_file_read_all(struct arena *arena, struct sys_file file)
|
|
{
|
|
__prof;
|
|
i64 size = 0;
|
|
GetFileSizeEx((HANDLE)file.handle, (PLARGE_INTEGER)&size);
|
|
|
|
struct buffer buff = {
|
|
.size = size,
|
|
.data = NULL
|
|
};
|
|
|
|
if (size > 0) {
|
|
/* ReadFile returns non-zero on success */
|
|
/* TODO: error checking */
|
|
arena_align(arena, 16);
|
|
buff.data = arena_push_array(arena, u8, size);
|
|
(UNUSED)ReadFile(
|
|
(HANDLE)file.handle,
|
|
buff.data,
|
|
(DWORD)buff.size,
|
|
NULL, /* lpNumberOfBytesRead */
|
|
NULL
|
|
);
|
|
}
|
|
return buff;
|
|
}
|
|
|
|
void sys_file_write(struct sys_file file, struct buffer data)
|
|
{
|
|
__prof;
|
|
/* TODO: Check what the real data limit is and chunk sequentially based on
|
|
* that (rather than failing) */
|
|
if (data.size >= 0x7FFF) {
|
|
struct temp_arena scratch = scratch_begin_no_conflict();
|
|
sys_panic(string_format(scratch.arena,
|
|
STR("Tried to write too many bytes to disk (%F)"),
|
|
FMT_UINT(data.size)));
|
|
//scratch_end(scratch);
|
|
}
|
|
|
|
/* WriteFile returns TRUE on success */
|
|
(UNUSED)WriteFile(
|
|
(HANDLE)file.handle,
|
|
data.data,
|
|
(DWORD)data.size,
|
|
NULL, /* lpNumberOfBytesWritten */
|
|
NULL
|
|
);
|
|
}
|
|
|
|
u64 sys_file_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);
|
|
}
|
|
|
|
/* ========================== *
|
|
* File map
|
|
* ========================== */
|
|
|
|
struct sys_file_map sys_file_map_open_read(struct sys_file file)
|
|
{
|
|
HANDLE map_handle = CreateFileMappingA(
|
|
(HANDLE)file.handle,
|
|
NULL,
|
|
PAGE_READONLY,
|
|
0,
|
|
0,
|
|
NULL
|
|
);
|
|
|
|
if (!map_handle) {
|
|
ASSERT(false);
|
|
return (struct sys_file_map) { 0 };
|
|
}
|
|
|
|
u64 size = sys_file_size(file);
|
|
u8 *base_ptr = NULL;
|
|
|
|
if (size > 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 */
|
|
CloseHandle(map_handle);
|
|
return (struct sys_file_map) { 0 };
|
|
}
|
|
|
|
return (struct sys_file_map) {
|
|
.handle = (u64)map_handle,
|
|
.mapped_memory = BUFFER(size, base_ptr)
|
|
};
|
|
}
|
|
|
|
void sys_file_map_close(struct sys_file_map map)
|
|
{
|
|
UnmapViewOfFile(map.mapped_memory.data);
|
|
CloseHandle((HANDLE)map.handle);
|
|
}
|
|
|
|
/* ========================== *
|
|
* Dir iter
|
|
* ========================== */
|
|
|
|
struct sys_dir_iter {
|
|
HANDLE handle;
|
|
char *dir_path_cstr;
|
|
};
|
|
|
|
struct sys_dir_iter *sys_dir_iter_begin(struct arena *arena, struct string dir_name)
|
|
{
|
|
struct temp_arena scratch = scratch_begin(arena);
|
|
struct sys_dir_iter *iter = arena_push_zero(arena, struct sys_dir_iter);
|
|
if (dir_name.len >= 1 && dir_name.text[dir_name.len - 1] == '/') {
|
|
struct string dir_path_w_asterisk = string_cat(scratch.arena, dir_name, STR("*"));
|
|
iter->dir_path_cstr = string_to_cstr(arena, dir_path_w_asterisk);
|
|
} else {
|
|
/* Invalid directory path supplied */
|
|
ASSERT(false);
|
|
}
|
|
scratch_end(scratch);
|
|
return iter;
|
|
}
|
|
|
|
struct sys_dir_iter_info *sys_dir_iter_next(struct arena *arena, struct sys_dir_iter *iter)
|
|
{
|
|
WIN32_FIND_DATA find_file_data = { 0 };
|
|
b32 found = false;
|
|
|
|
if (iter->handle) {
|
|
found = FindNextFileA(iter->handle, &find_file_data);
|
|
} else if (iter->dir_path_cstr) {
|
|
iter->handle = FindFirstFileExA(iter->dir_path_cstr, FindExInfoStandard, &find_file_data, FindExSearchNameMatch, NULL, FIND_FIRST_EX_LARGE_FETCH);
|
|
found = iter->handle != INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
struct sys_dir_iter_info *info = NULL;
|
|
if (found) {
|
|
struct string file_name = string_from_cstr(find_file_data.cFileName);
|
|
if (string_eq(file_name, STR(".")) || string_eq(file_name, STR(".."))) {
|
|
/* Skip initial '.' and '..' matches */
|
|
info = sys_dir_iter_next(arena, iter);
|
|
} else {
|
|
info = arena_push_zero(arena, struct sys_dir_iter_info);
|
|
info->is_dir = find_file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
|
|
info->file_name = string_cpy(arena, file_name);
|
|
}
|
|
}
|
|
|
|
return info;
|
|
}
|
|
|
|
void sys_dir_iter_end(struct sys_dir_iter *iter)
|
|
{
|
|
FindClose(iter->handle);
|
|
}
|
|
|
|
/* ========================== *
|
|
* Window
|
|
* ========================== */
|
|
|
|
INTERNAL void win32_update_window_from_system(struct win32_window *window);
|
|
|
|
INTERNAL void win32_window_process_event(struct win32_window *window, struct sys_event event)
|
|
{
|
|
{
|
|
sys_mutex_lock(&window->event_callbacks_mutex);
|
|
for (u64 i = 0; i < window->event_callbacks_count; ++i) {
|
|
window->event_callbacks[i](event);
|
|
}
|
|
sys_mutex_unlock(&window->event_callbacks_mutex);
|
|
}
|
|
}
|
|
|
|
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 = CreateWindowExA(
|
|
exstyle,
|
|
L.window_class.lpszClassName,
|
|
"",
|
|
WS_OVERLAPPEDWINDOW,
|
|
CW_USEDEFAULT,
|
|
CW_USEDEFAULT,
|
|
CW_USEDEFAULT,
|
|
CW_USEDEFAULT,
|
|
NULL,
|
|
NULL,
|
|
L.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 */
|
|
SetWindowLongPtrA(hwnd, GWLP_USERDATA, (LONG_PTR)window);
|
|
|
|
return hwnd;
|
|
}
|
|
|
|
INTERNAL void window_thread_entry_point(void *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);
|
|
win32_update_window_from_system(window);
|
|
BringWindowToTop(window->hwnd);
|
|
|
|
sync_flag_set(&window->ready_sf);
|
|
|
|
while (!window->event_thread_shutdown) {
|
|
MSG msg = {0};
|
|
GetMessage(&msg, 0, 0, 0);
|
|
|
|
switch (msg.message) {
|
|
case WM_QUIT: {
|
|
win32_window_process_event(window, (struct sys_event) { .kind = SYS_EVENT_KIND_QUIT });
|
|
} break;
|
|
|
|
default: {
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
} break;
|
|
}
|
|
|
|
/* TODO: update mouse pos w/ GetCursorPos */
|
|
}
|
|
|
|
/* Destroy window hwnd */
|
|
DestroyWindow(window->hwnd);
|
|
}
|
|
|
|
INTERNAL struct win32_window *win32_window_alloc(void)
|
|
{
|
|
struct win32_window *window = NULL;
|
|
|
|
sys_mutex_lock(&L.windows_mutex);
|
|
{
|
|
if (L.first_free_window) {
|
|
window = L.first_free_window;
|
|
L.first_free_window = window->next_free;
|
|
} else {
|
|
window = arena_push(&L.windows_arena, struct win32_window);
|
|
}
|
|
}
|
|
sys_mutex_unlock(&L.windows_mutex);
|
|
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_init(&window_thread_entry_point, window, STR("[P8] 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)
|
|
{
|
|
sys_mutex_lock(&L.windows_mutex);
|
|
{
|
|
window->next_free = L.first_free_window;
|
|
L.first_free_window = window;
|
|
|
|
/* Stop window thread */
|
|
window->event_thread_shutdown = true;
|
|
sys_thread_join(&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(&L.windows_mutex);
|
|
}
|
|
|
|
INTERNAL void win32_update_window_from_system(struct win32_window *window)
|
|
{
|
|
HWND hwnd = window->hwnd;
|
|
|
|
RECT window_rect = {0};
|
|
GetWindowRect(hwnd, &window_rect);
|
|
|
|
RECT client_rect = {0};
|
|
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 = {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 = {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 borders */
|
|
window->border_left = client_rect.left - window_rect.left;
|
|
window->border_top = client_rect.top - window_rect.top;
|
|
|
|
/* 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->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)) {
|
|
/* Treat a window resize in non-maximized 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;
|
|
window->settings = *settings;
|
|
|
|
/* Position & dimensions */
|
|
i32 adjusted_x = settings->floating_x - window->border_left;
|
|
i32 adjusted_y = settings->floating_y - window->border_top;
|
|
SetWindowPos(
|
|
hwnd,
|
|
0,
|
|
adjusted_x,
|
|
adjusted_y,
|
|
settings->floating_width + (window->border_left * 2),
|
|
/* I'm unsure why border_left being added to border_top is needed to
|
|
* retain the correct height. Coincidence? */
|
|
settings->floating_height + (window->border_left + window->border_top),
|
|
//SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_FRAMECHANGED
|
|
SWP_NOZORDER | SWP_NOOWNERZORDER
|
|
);
|
|
|
|
#if 0
|
|
if (settings->flags & SYS_WINDOW_SETTINGS_FLAG_MAXIMIZED) {
|
|
ShowWindow(hwnd, SW_SHOWMAXIMIZED);
|
|
window->flags |= SYS_WINDOW_FLAG_SHOWING;
|
|
}
|
|
|
|
if (settings->flags & SYS_WINDOW_SETTINGS_FLAG_MINIMIZED) {
|
|
ShowWindow(hwnd, SW_MINIMIZE);
|
|
window->flags |= SYS_WINDOW_FLAG_SHOWING;
|
|
}
|
|
|
|
if (!(window->flags & SYS_WINDOW_FLAG_SHOWING)) {
|
|
/* Open window for first time */
|
|
ShowWindow(hwnd, SW_NORMAL);
|
|
window->flags |= SYS_WINDOW_FLAG_SHOWING;
|
|
}
|
|
|
|
BringWindowToTop(hwnd);
|
|
#endif
|
|
SetWindowTextA(hwnd, settings->title);
|
|
}
|
|
|
|
INTERNAL struct v2 win32_window_get_mouse_pos(struct win32_window *window)
|
|
{
|
|
HWND hwnd = window->hwnd;
|
|
|
|
struct v2 res = V2(0, 0);
|
|
POINT p;
|
|
if (GetCursorPos(&p) && ScreenToClient(hwnd, &p)) {
|
|
res = V2(p.x, p.y);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
INTERNAL LRESULT CALLBACK win32_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
|
|
{
|
|
struct win32_window *window = (struct win32_window *)GetWindowLongPtrA(hwnd, GWLP_USERDATA);
|
|
|
|
if (!window) {
|
|
/* Window should be retrievable unless we're in any of these
|
|
* msgs triggered during initialization / destruction */
|
|
ASSERT(msg == WM_GETMINMAXINFO
|
|
|| msg == WM_NCCREATE
|
|
|| msg == WM_NCCALCSIZE
|
|
|| msg == WM_CREATE
|
|
|| msg == WM_NCDESTROY);
|
|
return DefWindowProcA(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 = DefWindowProcA(hwnd, msg, wparam, lparam);
|
|
} break;
|
|
|
|
case WM_MOVE:
|
|
case WM_MOVING:
|
|
case WM_SIZE:
|
|
case WM_SIZING: {
|
|
win32_update_window_from_system(window);
|
|
result = DefWindowProcA(hwnd, msg, wparam, lparam);
|
|
} break;
|
|
|
|
/* Keyboard buttons */
|
|
case WM_SYSKEYUP:
|
|
case WM_SYSKEYDOWN: {
|
|
result = DefWindowProcA(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(L.vk_btn_table)) {
|
|
button = L.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: {
|
|
u32 character = (u32)wparam;
|
|
if (character == '\r') {
|
|
character = '\n'; /* Just treat all \r as newline */
|
|
}
|
|
if((character >= 32 && character != 127) || character == '\t' || character == '\n') {
|
|
win32_window_process_event(
|
|
window,
|
|
(struct sys_event) {
|
|
.kind = SYS_EVENT_KIND_TEXT,
|
|
.text_character = character
|
|
}
|
|
);
|
|
}
|
|
} break;
|
|
|
|
/* Mouse buttons */
|
|
case WM_LBUTTONUP:
|
|
case WM_MBUTTONUP:
|
|
case WM_RBUTTONUP:
|
|
case WM_XBUTTONUP: {
|
|
ReleaseCapture();
|
|
is_release = 1;
|
|
} FALLTHROUGH;
|
|
case WM_LBUTTONDOWN:
|
|
case WM_MBUTTONDOWN:
|
|
case WM_RBUTTONDOWN:
|
|
case WM_XBUTTONDOWN: {
|
|
if(is_release == 0) {
|
|
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,
|
|
.position = win32_window_get_mouse_pos(window)
|
|
}
|
|
);
|
|
}
|
|
} 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;
|
|
|
|
|
|
default: {
|
|
result = DefWindowProcA(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)
|
|
{
|
|
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;
|
|
|
|
sys_mutex_lock(&window->event_callbacks_mutex);
|
|
{
|
|
if (window->event_callbacks_count + 1 > ARRAY_COUNT(window->event_callbacks)) {
|
|
sys_panic(STR("Too many window event callbacks registered"));
|
|
} else {
|
|
window->event_callbacks[window->event_callbacks_count++] = func;
|
|
}
|
|
}
|
|
sys_mutex_unlock(&window->event_callbacks_mutex);
|
|
}
|
|
|
|
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;
|
|
|
|
sys_mutex_lock(&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(&window->event_callbacks_mutex);
|
|
}
|
|
|
|
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;
|
|
sys_mutex_lock(&window->settings_mutex);
|
|
{
|
|
win32_update_window_from_settings(window, settings);
|
|
}
|
|
sys_mutex_unlock(&window->settings_mutex);
|
|
}
|
|
|
|
/* TODO: 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;
|
|
sys_mutex_lock(&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(&window->settings_mutex);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
struct v2 sys_window_get_mouse_pos(struct sys_window *sys_window)
|
|
{
|
|
struct win32_window *window = (struct win32_window *)sys_window->handle;
|
|
return win32_window_get_mouse_pos(window);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/* ========================== *
|
|
* Mutex
|
|
* ========================== */
|
|
|
|
struct sys_mutex sys_mutex_alloc(void)
|
|
{
|
|
__prof;
|
|
SRWLOCK srwlock = SRWLOCK_INIT;
|
|
struct sys_mutex mutex = {
|
|
.handle = *(u64 *)&srwlock
|
|
};
|
|
return mutex;
|
|
}
|
|
|
|
void sys_mutex_release(struct sys_mutex *mutex)
|
|
{
|
|
__prof;
|
|
(UNUSED)mutex;
|
|
/* Mutex must be unlocked */
|
|
ASSERT(mutex->owner_tid == 0);
|
|
}
|
|
|
|
void sys_mutex_lock(struct sys_mutex *mutex)
|
|
{
|
|
__prof;
|
|
AcquireSRWLockExclusive((SRWLOCK *)&mutex->handle);
|
|
#if RTC
|
|
mutex->owner_tid = (u64)GetCurrentThreadId();
|
|
GetThreadDescription(GetCurrentThread(), &mutex->owner_name);
|
|
#endif
|
|
}
|
|
|
|
void sys_mutex_unlock(struct sys_mutex *mutex)
|
|
{
|
|
__prof;
|
|
#if RTC
|
|
mutex->owner_name = L"None";
|
|
mutex->owner_tid = 0;
|
|
#endif
|
|
ReleaseSRWLockExclusive((SRWLOCK *)&mutex->handle);
|
|
}
|
|
|
|
#if RTC
|
|
void sys_mutex_assert_locked(struct sys_mutex *mutex)
|
|
{
|
|
ASSERT(mutex->owner_tid == (u64)GetCurrentThreadId());
|
|
}
|
|
#endif
|
|
|
|
/* ========================== *
|
|
* RW Mutex
|
|
* ========================== */
|
|
|
|
struct sys_rw_mutex sys_rw_mutex_alloc(void)
|
|
{
|
|
__prof;
|
|
SRWLOCK srwlock;
|
|
InitializeSRWLock(&srwlock);
|
|
struct sys_rw_mutex mutex = {
|
|
.handle = *(u64 *)&srwlock
|
|
};
|
|
return mutex;
|
|
}
|
|
|
|
void sys_rw_mutex_release(struct sys_rw_mutex *mutex)
|
|
{
|
|
__prof;
|
|
(UNUSED)mutex;
|
|
/* Mutex must be unlocked */
|
|
ASSERT(mutex->owner_tid == 0);
|
|
ASSERT(mutex->num_shared == 0);
|
|
}
|
|
|
|
void sys_rw_mutex_lock_exclusive(struct sys_rw_mutex *mutex)
|
|
{
|
|
__prof;
|
|
AcquireSRWLockExclusive((SRWLOCK *)&mutex->handle);
|
|
#if RTC
|
|
mutex->owner_tid = (u64)GetCurrentThreadId();
|
|
GetThreadDescription(GetCurrentThread(), &mutex->owner_name);
|
|
#endif
|
|
}
|
|
|
|
void sys_rw_mutex_unlock_exclusive(struct sys_rw_mutex *mutex)
|
|
{
|
|
__prof;
|
|
#if RTC
|
|
mutex->owner_name = L"None";
|
|
mutex->owner_tid = 0;
|
|
#endif
|
|
ReleaseSRWLockExclusive((SRWLOCK *)&mutex->handle);
|
|
}
|
|
|
|
void sys_rw_mutex_lock_shared(struct sys_rw_mutex *mutex)
|
|
{
|
|
__prof;
|
|
AcquireSRWLockShared((SRWLOCK *)&mutex->handle);
|
|
#if RTC
|
|
atomic_inc_eval64(&mutex->num_shared);
|
|
#endif
|
|
}
|
|
|
|
void sys_rw_mutex_unlock_shared(struct sys_rw_mutex *mutex)
|
|
{
|
|
__prof;
|
|
#if RTC
|
|
atomic_dec_eval64(&mutex->num_shared);
|
|
#endif
|
|
ReleaseSRWLockShared((SRWLOCK *)&mutex->handle);
|
|
}
|
|
|
|
#if RTC
|
|
void sys_rw_mutex_assert_locked_exclusive(struct sys_rw_mutex *mutex)
|
|
{
|
|
ASSERT(mutex->owner_tid == (u64)GetCurrentThreadId());
|
|
}
|
|
#endif
|
|
|
|
/* ========================== *
|
|
* Condition variable
|
|
* ========================== */
|
|
|
|
INTERNAL struct win32_condition_variable *win32_condition_variable_alloc(void)
|
|
{
|
|
__prof;
|
|
struct win32_condition_variable *cv = NULL;
|
|
{
|
|
sys_mutex_lock(&L.condition_variables_mutex);
|
|
if (L.first_free_condition_variable) {
|
|
cv = L.first_free_condition_variable;
|
|
L.first_free_condition_variable = cv->next_free;
|
|
} else {
|
|
cv = arena_push_zero(&L.condition_variables_arena, struct win32_condition_variable);
|
|
}
|
|
sys_mutex_unlock(&L.condition_variables_mutex);
|
|
}
|
|
|
|
MEMZERO_STRUCT(cv);
|
|
InitializeConditionVariable(&cv->condition_variable);
|
|
|
|
return cv;
|
|
}
|
|
|
|
INTERNAL void win32_condition_variable_release(struct win32_condition_variable *w32cv)
|
|
{
|
|
__prof;
|
|
sys_mutex_lock(&L.condition_variables_mutex);
|
|
{
|
|
w32cv->next_free = L.first_free_condition_variable;
|
|
L.first_free_condition_variable = w32cv;
|
|
}
|
|
sys_mutex_unlock(&L.condition_variables_mutex);
|
|
|
|
}
|
|
|
|
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(cv->num_sleepers == 0);
|
|
win32_condition_variable_release((struct win32_condition_variable *)cv->handle);
|
|
}
|
|
|
|
void sys_condition_variable_wait(struct sys_condition_variable *cv, struct sys_mutex *mutex)
|
|
{
|
|
__prof;
|
|
#if RTC
|
|
atomic_inc_eval64(&cv->num_sleepers);
|
|
#endif
|
|
SleepConditionVariableSRW((PCONDITION_VARIABLE)cv->handle, (SRWLOCK *)&mutex->handle, INFINITE, 0);
|
|
#if RTC
|
|
atomic_dec_eval64(&cv->num_sleepers);
|
|
#endif
|
|
}
|
|
|
|
void sys_condition_variable_wait_time(struct sys_condition_variable *cv, struct sys_mutex *mutex, f64 seconds)
|
|
{
|
|
__prof;
|
|
#if RTC
|
|
atomic_inc_eval64(&cv->num_sleepers);
|
|
#endif
|
|
u32 ms = (u32)math_round_f64(seconds * 1000.0);
|
|
SleepConditionVariableSRW((PCONDITION_VARIABLE)cv->handle, (SRWLOCK *)&mutex->handle, ms, 0);
|
|
#if RTC
|
|
atomic_dec_eval64(&cv->num_sleepers);
|
|
#endif
|
|
}
|
|
|
|
void sys_condition_variable_signal(struct sys_condition_variable *cv)
|
|
{
|
|
__prof;
|
|
WakeAllConditionVariable((PCONDITION_VARIABLE)cv->handle);
|
|
}
|
|
|
|
/* ========================== *
|
|
* Semaphore
|
|
* ========================== */
|
|
|
|
/* TODO: Use similar allocation scheme to mutex & condition var for speed? */
|
|
|
|
struct sys_semaphore sys_semaphore_alloc(u32 max_count)
|
|
{
|
|
struct sys_semaphore semaphore = {
|
|
.handle = (u64)CreateSemaphoreEx(0, 0, max_count, NULL, 0, SEMAPHORE_ALL_ACCESS)
|
|
};
|
|
return semaphore;
|
|
}
|
|
|
|
void sys_semaphore_release(struct sys_semaphore *semaphore)
|
|
{
|
|
__prof;
|
|
CloseHandle((HANDLE)semaphore->handle);
|
|
}
|
|
|
|
void sys_semaphore_wait(struct sys_semaphore *semaphore)
|
|
{
|
|
__prof;
|
|
WaitForSingleObjectEx((HANDLE)semaphore->handle, INFINITE, FALSE);
|
|
}
|
|
|
|
void sys_semaphore_signal(struct sys_semaphore *semaphore, u32 count)
|
|
{
|
|
__prof;
|
|
ReleaseSemaphore((HANDLE)semaphore->handle, count, NULL);
|
|
}
|
|
|
|
/* ========================== *
|
|
* Thread local storage
|
|
* ========================== */
|
|
|
|
struct win32_tls {
|
|
HANDLE sleep_timer;
|
|
struct scratch_context scratch_ctx;
|
|
struct worker_context worker_ctx;
|
|
};
|
|
|
|
INTERNAL void win32_thread_set_tls(struct win32_tls *ctx)
|
|
{
|
|
/* TODO: Fail if error */
|
|
TlsSetValue(L.thread_tls_index, (LPVOID)ctx);
|
|
}
|
|
|
|
INTERNAL struct win32_tls *win32_thread_get_tls(void)
|
|
{
|
|
/* TODO: Fail if error */
|
|
return TlsGetValue(L.thread_tls_index);
|
|
}
|
|
|
|
INTERNAL struct win32_tls win32_tls_alloc(void)
|
|
{
|
|
struct win32_tls tls = { 0 };
|
|
tls.sleep_timer = CreateWaitableTimerExW(NULL, NULL, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS);
|
|
tls.scratch_ctx = scratch_context_alloc();
|
|
tls.worker_ctx = worker_context_alloc();
|
|
return tls;
|
|
}
|
|
|
|
INTERNAL void win32_tls_release(struct win32_tls *tls)
|
|
{
|
|
worker_context_release(&tls->worker_ctx);
|
|
scratch_context_release(&tls->scratch_ctx);
|
|
CloseHandle(tls->sleep_timer);
|
|
}
|
|
|
|
struct scratch_context *sys_thread_get_scratch_context(void)
|
|
{
|
|
struct win32_tls *thread_ctx = (struct win32_tls *)win32_thread_get_tls();
|
|
return &thread_ctx->scratch_ctx;
|
|
}
|
|
|
|
struct worker_context *sys_thread_get_worker_context(void)
|
|
{
|
|
struct win32_tls *thread_ctx = (struct win32_tls *)win32_thread_get_tls();
|
|
return &thread_ctx->worker_ctx;
|
|
}
|
|
|
|
/* ========================== *
|
|
* Threads
|
|
* ========================== */
|
|
|
|
INTERNAL struct win32_thread_params *thread_params_alloc(void)
|
|
{
|
|
struct win32_thread_params *tp = NULL;
|
|
sys_mutex_lock(&L.thread_params_mutex);
|
|
{
|
|
if (L.first_free_thread_params) {
|
|
tp = L.first_free_thread_params;
|
|
L.first_free_thread_params = tp->next_free;
|
|
} else {
|
|
tp = arena_push(&L.thread_params_arena, struct win32_thread_params);
|
|
}
|
|
}
|
|
sys_mutex_unlock(&L.thread_params_mutex);
|
|
MEMZERO_STRUCT(tp);
|
|
return tp;
|
|
}
|
|
|
|
INTERNAL void thread_params_release(struct win32_thread_params *tp)
|
|
{
|
|
sys_mutex_lock(&L.thread_params_mutex);
|
|
{
|
|
tp->next_free = L.first_free_thread_params;
|
|
L.first_free_thread_params = tp;
|
|
}
|
|
sys_mutex_unlock(&L.thread_params_mutex);
|
|
}
|
|
|
|
INTERNAL DWORD WINAPI win32_thread_proc(LPVOID params)
|
|
{
|
|
struct win32_thread_params thread_params = *(struct win32_thread_params *)params;
|
|
thread_params_release((struct win32_thread_params *)params);
|
|
|
|
/* 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(thread_params.thread_name_cstr);
|
|
if (thread_name.len > 0) {
|
|
struct temp_arena scratch = scratch_begin_no_conflict();
|
|
wchar_t *wc_thread_name = string_to_wstr(scratch.arena, thread_name);
|
|
SetThreadDescription(GetCurrentThread(), wc_thread_name);
|
|
scratch_end(scratch);
|
|
}
|
|
|
|
logf_info("New thread \"%F\" with ID %F", FMT_STR(thread_name), FMT_UINT(sys_thread_id()));
|
|
|
|
/* Start thread */
|
|
thread_params.thread_func(thread_params.thread_data);
|
|
|
|
/* Release TLS */
|
|
win32_tls_release(&tls);
|
|
|
|
/* Uninitialize COM */
|
|
CoUninitialize();
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct sys_thread sys_thread_init(sys_thread_func *thread_func, void *thread_data, struct string thread_name)
|
|
{
|
|
struct sys_thread thread = { 0 };
|
|
|
|
ASSERT(thread_func != NULL);
|
|
|
|
/* Create thread params */
|
|
struct win32_thread_params *tp = thread_params_alloc();
|
|
tp->thread_func = thread_func;
|
|
tp->thread_data = thread_data;
|
|
|
|
/* Copy thread name to params */
|
|
string_to_cstr_buff(thread_name, BUFFER_FROM_ARRAY(tp->thread_name_cstr));
|
|
|
|
HANDLE handle = CreateThread(
|
|
NULL,
|
|
SYS_THREAD_STACK_SIZE,
|
|
win32_thread_proc,
|
|
tp,
|
|
0,
|
|
NULL
|
|
);
|
|
|
|
/* Initialize struct */
|
|
if (handle) {
|
|
/* Success */
|
|
thread.handle = (u64)handle;
|
|
} else {
|
|
sys_panic(STR("Failed to create thread"));
|
|
}
|
|
|
|
return thread;
|
|
}
|
|
|
|
void sys_thread_join(struct sys_thread *thread)
|
|
{
|
|
DWORD res = WaitForSingleObject((HANDLE)thread->handle, INFINITE);
|
|
CloseHandle((HANDLE)thread->handle);
|
|
|
|
(UNUSED)res;
|
|
ASSERT(res != WAIT_FAILED);
|
|
}
|
|
|
|
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)
|
|
{
|
|
char message_buff[4096] = { 0 };
|
|
char *message_cstr = string_to_cstr_buff(message, BUFFER_FROM_ARRAY(message_buff));
|
|
|
|
const char *title = "";
|
|
UINT mbox_type = 0;
|
|
|
|
switch (kind) {
|
|
case SYS_MESSAGE_BOX_KIND_OK: {
|
|
mbox_type = MB_ICONINFORMATION;
|
|
} break;
|
|
|
|
case SYS_MESSAGE_BOX_KIND_WARNING: {
|
|
title = "Warning";
|
|
mbox_type = MB_ICONWARNING;
|
|
} break;
|
|
|
|
case SYS_MESSAGE_BOX_KIND_ERROR: {
|
|
title = "Error";
|
|
mbox_type = MB_ICONERROR;
|
|
} break;
|
|
|
|
case SYS_MESSAGE_BOX_KIND_FATAL: {
|
|
title = "Fatal error";
|
|
mbox_type = MB_ICONSTOP;
|
|
} break;
|
|
}
|
|
|
|
MessageBoxExA(NULL, message_cstr, title, mbox_type, 0);
|
|
}
|
|
|
|
/* ========================== *
|
|
* Time
|
|
* ========================== */
|
|
|
|
/* prevent 64-bit overflow when computing relative timestamp
|
|
* https://github.com/floooh/sokol/blob/d4ac122f36d7659a18b312fd4fa2317fb9e06a63/sokol_time.h#L203
|
|
*/
|
|
INTERNAL i64 _win32_i64_muldiv(i64 value, i64 numer, i64 denom) {
|
|
i64 q = value / denom;
|
|
i64 r = value % denom;
|
|
return q * numer + r * numer / denom;
|
|
}
|
|
|
|
sys_timestamp_t sys_timestamp(void)
|
|
{
|
|
LARGE_INTEGER time;
|
|
QueryPerformanceCounter(&time);
|
|
return (u64)_win32_i64_muldiv(time.QuadPart - L.timer_start.QuadPart, 1000000000, L.timer_frequency.QuadPart);
|
|
}
|
|
|
|
f64 sys_timestamp_seconds(sys_timestamp_t ts)
|
|
{
|
|
return (f64)ts / 1000000000.0;
|
|
}
|
|
|
|
struct sys_local_time_info sys_local_time(void)
|
|
{
|
|
SYSTEMTIME lt;
|
|
GetLocalTime(<);
|
|
return (struct sys_local_time_info) {
|
|
.year = lt.wYear,
|
|
.month = lt.wMonth,
|
|
.dayOfWeek = lt.wDayOfWeek,
|
|
.day = lt.wDay,
|
|
.hour = lt.wHour,
|
|
.minute = lt.wMinute,
|
|
.second = lt.wSecond,
|
|
.milliseconds = lt.wMilliseconds
|
|
};
|
|
}
|
|
|
|
/* ========================== *
|
|
* Util
|
|
* ========================== */
|
|
|
|
u32 sys_num_logical_processors(void)
|
|
{
|
|
return GetActiveProcessorCount(ALL_PROCESSOR_GROUPS);
|
|
}
|
|
|
|
void sys_exit(void)
|
|
{
|
|
ExitProcess(1);
|
|
}
|
|
|
|
u32 sys_rand_u32(void)
|
|
{
|
|
u32 v;
|
|
RtlGenRandom(&v, sizeof(v));
|
|
return v;
|
|
}
|
|
|
|
/* Like sys_panic, but guaranteed to have no side-effects */
|
|
void sys_panic_raw(char *msg_cstr)
|
|
{
|
|
MessageBoxExA(NULL, msg_cstr, "Fatal error", MB_ICONSTOP, 0);
|
|
ASSERT(false);
|
|
sys_exit();
|
|
}
|
|
|
|
void sys_panic(struct string msg)
|
|
{
|
|
struct temp_arena scratch = scratch_begin_no_conflict();
|
|
logf_critical("Panicking: %F", FMT_STR(msg));
|
|
struct string prepend = STR("A fatal error has occured and the application needs to exit:\n\n");
|
|
msg = string_cat(scratch.arena, prepend, msg);
|
|
sys_panic_raw(string_to_cstr(scratch.arena, msg));
|
|
scratch_end(scratch);
|
|
}
|
|
|
|
/* ========================== *
|
|
* Sleep
|
|
* ========================== */
|
|
|
|
/* https://blog.bearcats.nl/perfect-sleep-function/ */
|
|
|
|
INTERNAL void win32_classic_sleep(f64 seconds)
|
|
{
|
|
__prof;
|
|
i64 qpc_per_second = L.timer_frequency.QuadPart;
|
|
i32 scheduler_period_ms = L.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);
|
|
}
|
|
}
|
|
}
|
|
|
|
INTERNAL void win32_timer_sleep(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 = L.timer_frequency.QuadPart;;
|
|
i32 scheduler_period_ms = L.scheduler_period_ms;
|
|
|
|
LARGE_INTEGER qpc;
|
|
QueryPerformanceCounter(&qpc);
|
|
INT64 target_qpc = (INT64)(qpc.QuadPart + seconds * qpc_per_second);
|
|
|
|
/* TODO: Maybe increase tolerance for higher precision but more power usage */
|
|
//const double tolerance = 0.001200 * scheduler_period_ms;
|
|
const double tolerance = 0.000520 * scheduler_period_ms;
|
|
|
|
INT64 max_ticks = (INT64)scheduler_period_ms * 9500;
|
|
while (true) {
|
|
__profscope(win32_sleep_part);
|
|
/* Break sleep up into parts that are lower than scheduler period */
|
|
double remaining_seconds = (double)(target_qpc - qpc.QuadPart) / (double)qpc_per_second;
|
|
INT64 sleep_ticks = (INT64)((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);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void sys_sleep(f64 seconds)
|
|
{
|
|
__prof;
|
|
HANDLE timer = win32_thread_get_tls()->sleep_timer;
|
|
if (timer) {
|
|
/* Use newer sleeping method */
|
|
win32_timer_sleep(seconds, timer);
|
|
} else {
|
|
/* Fall back to older sleep method if CREATE_WAITABLE_TIMER_HIGH_RESOLUTION
|
|
* is not available due to older windows version */
|
|
win32_classic_sleep(seconds);
|
|
}
|
|
}
|
|
|
|
/* ========================== *
|
|
* Entry point
|
|
* ========================== */
|
|
|
|
int CALLBACK WinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev_instance, _In_ LPSTR command_line, _In_ int show_code)
|
|
{
|
|
(UNUSED)instance;
|
|
(UNUSED)prev_instance;
|
|
(UNUSED)command_line;
|
|
(UNUSED)show_code;
|
|
|
|
const char *error_msg = NULL;
|
|
|
|
SetThreadDescription(GetCurrentThread(), L"Main thread");
|
|
|
|
/* Initialize COM */
|
|
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
|
|
|
|
/* Query system info */
|
|
GetSystemInfo(&L.info);
|
|
QueryPerformanceFrequency(&L.timer_frequency);
|
|
QueryPerformanceCounter(&L.timer_start);
|
|
{
|
|
TIMECAPS caps;
|
|
timeGetDevCaps(&caps, sizeof caps);
|
|
L.scheduler_period_ms = (i32)caps.wPeriodMin;
|
|
}
|
|
|
|
/* Set up timing period */
|
|
timeBeginPeriod(L.scheduler_period_ms);
|
|
|
|
/* Initialize lookup tables */
|
|
win32_init_vk_btn_table();
|
|
|
|
/* Set up TLS */
|
|
L.thread_tls_index = TlsAlloc();
|
|
if (L.thread_tls_index == TLS_OUT_OF_INDEXES) {
|
|
/* TODO: GetLastError */
|
|
error_msg = "Platform initialization error: TLS_OUT_OF_INDEXES";
|
|
goto error;
|
|
}
|
|
|
|
/* Initialize main thread context.
|
|
* This must happen before scratch memory can be used. */
|
|
struct win32_tls main_thread_tls = win32_tls_alloc();
|
|
win32_thread_set_tls(&main_thread_tls);
|
|
|
|
/* Set up condition variables */
|
|
L.condition_variables_mutex = sys_mutex_alloc();
|
|
L.condition_variables_arena = arena_alloc(GIGABYTE(64));
|
|
|
|
/* Set up threads */
|
|
L.thread_params_mutex = sys_mutex_alloc();
|
|
L.thread_params_arena = arena_alloc(GIGABYTE(64));
|
|
|
|
/* Set up windows */
|
|
L.windows_mutex = sys_mutex_alloc();
|
|
L.windows_arena = arena_alloc(GIGABYTE(64));
|
|
|
|
/* Create window class */
|
|
{
|
|
/* Register the window class */
|
|
WNDCLASSEX *wc = &L.window_class;
|
|
wc->cbSize = sizeof(WNDCLASSEX);
|
|
wc->lpszClassName = "power_play_window_class";
|
|
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) */
|
|
char path[MAX_PATH] = { 0 };
|
|
GetModuleFileName(instance, path, MAX_PATH);
|
|
ExtractIconEx(path, 0, &wc->hIcon, &wc->hIconSm, 1);
|
|
|
|
if (!RegisterClassExA(wc)) {
|
|
/* TODO: GetLastError */
|
|
error_msg = "Failed to register window class";
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
error:
|
|
if (error_msg) {
|
|
MessageBoxExA(NULL, error_msg, "Fatal initialization error", MB_ICONSTOP, 0);
|
|
ASSERT(false);
|
|
return 1;
|
|
}
|
|
|
|
/* App */
|
|
app_entry_point();
|
|
|
|
win32_tls_release(&main_thread_tls);
|
|
|
|
/* Uninitialize COM */
|
|
CoUninitialize();
|
|
|
|
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 WinMainCRTStartup(void)
|
|
{
|
|
int result = WinMain(GetModuleHandle(0), 0, GetCommandLineA(), 0);
|
|
ExitProcess(result);
|
|
}
|
|
|
|
#pragma clang diagnostic pop
|
|
|
|
#endif /* !CRTLIB */
|