power_play/src/platform/platform_win32.h
2025-07-30 20:33:52 -05:00

466 lines
12 KiB
C

////////////////////////////////
//~ Win32 headers
#pragma warning(push, 0)
# define UNICODE
# define WIN32_LEAN_AND_MEAN
# include <Windows.h>
# include <WinSock2.h>
# include <TlHelp32.h>
# include <WS2tcpip.h>
# include <windowsx.h>
# include <ShlObj_core.h>
# include <fileapi.h>
# include <dwmapi.h>
# include <bcrypt.h>
# include <avrt.h>
# include <shellapi.h>
#pragma warning(pop)
////////////////////////////////
//~ Ticket mutex types
Struct(P_W32_TicketMutex)
{
Atomic64Padded ticket;
Atomic64Padded serving;
};
////////////////////////////////
//~ Thread types
#define P_W32_ThreadStackSize Kibi(64)
#define P_W32_ThreadDef(name, arg_name) void name(void *arg_name)
typedef P_W32_ThreadDef(P_W32_ThreadFunc, data);
Struct(P_W32_Thread)
{
P_W32_ThreadFunc *entry_point;
void *thread_data;
char thread_name_cstr[256];
wchar_t thread_name_wstr[256];
i32 profiler_group;
P_W32_Thread *next;
P_W32_Thread *prev;
HANDLE handle;
};
////////////////////////////////
//~ Wait list types
AlignedStruct(P_W32_WaitList, 64)
{
u64 value;
i16 first_waiter;
i16 last_waiter;
i32 num_waiters;
P_W32_WaitList *next_in_bin;
P_W32_WaitList *prev_in_bin;
};
StaticAssert(alignof(P_W32_WaitList) == 64); /* Avoid false sharing */
AlignedStruct(P_W32_WaitBin, 64)
{
P_W32_WaitList *first_wait_list;
P_W32_WaitList *last_wait_list;
P_W32_WaitList *first_free_wait_list;
P_W32_TicketMutex lock;
};
StaticAssert(alignof(P_W32_WaitBin) == 64); /* Avoid false sharing */
////////////////////////////////
//~ Fiber types
#define P_W32_FiberStackSize Mebi(4)
#define P_W32_FiberNamePrefixCstr "Fiber ["
#define P_W32_FiberNameSuffixCstr "]"
#define P_W32_FiberNameMaxSize 64
//- Yield param
typedef i32 P_W32_YieldKind; enum
{
P_W32_YieldKind_None,
P_W32_YieldKind_Done,
P_W32_YieldKind_Wait,
P_W32_YieldKind_Count
};
Struct(P_W32_YieldParam)
{
P_W32_YieldKind kind;
union
{
struct
{
volatile void *addr;
void *cmp;
u32 size;
i64 timeout_ns;
} wait;
};
};
//- Fiber
AlignedStruct(P_W32_Fiber, 64)
{
/* ---------------------------------------------------- */
void *addr; /* 08 bytes */
/* ---------------------------------------------------- */
char *name_cstr; /* 08 bytes */
/* ---------------------------------------------------- */
Atomic32 wake_lock; /* 04 bytes (4 byte alignment) */
i16 id; /* 02 bytes */
i16 parent_id; /* 02 bytes */
/* ---------------------------------------------------- */
u64 wait_addr; /* 08 bytes */
/* ---------------------------------------------------- */
u64 wait_time; /* 08 bytes */
/* ---------------------------------------------------- */
i16 next_addr_waiter; /* 02 bytes */
i16 prev_addr_waiter; /* 02 bytes */
i16 next_time_waiter; /* 02 bytes */
i16 prev_time_waiter; /* 02 bytes */
/* ---------------------------------------------------- */
u8 _pad1[8]; /* 08 bytes (padding) */
/* ---------------------------------------------------- */
u8 _pad2[8]; /* 08 bytes (padding) */
/* ---------------------------------------------------- */
/* -------------------- Cache line -------------------- */
/* ---------------------------------------------------- */
P_JobFunc *job_func; /* 08 bytes */
/* ---------------------------------------------------- */
void *job_sig; /* 08 bytes */
/* ---------------------------------------------------- */
i32 job_id; /* 04 bytes */
i16 job_pool; /* 02 bytes */
i16 job_priority; /* 02 bytes */
/* ---------------------------------------------------- */
P_Counter *job_counter; /* 08 bytes */
/* ---------------------------------------------------- */
P_W32_YieldParam *yield_param; /* 08 bytes */
/* ---------------------------------------------------- */
u8 _pad3[24]; /* 24 bytes (padding) */
};
StaticAssert(sizeof(P_W32_Fiber) == 128); /* Padding validation (increase if necessary) */
StaticAssert(alignof(P_W32_Fiber) == 64); /* Avoid false sharing */
StaticAssert(offsetof(P_W32_Fiber, wake_lock) % 4 == 0); /* Atomic must be aligned */
////////////////////////////////
//~ Job queue types
//- Worker ctx
AlignedStruct(P_W32_WorkerCtx, 64)
{
P_Pool pool_kind;
i32 id;
};
//- Job info
Struct(P_W32_JobInfo)
{
i32 num_dispatched;
i32 count;
P_JobFunc *func;
void *sig;
P_Counter *counter;
i16 fiber_id; /* If the job is being resumed from a yield */
P_W32_JobInfo *next;
};
//- Job queue
AlignedStruct(P_W32_JobQueue, 64)
{
P_W32_TicketMutex lock;
Arena *arena;
P_W32_JobInfo *first;
P_W32_JobInfo *last;
P_W32_JobInfo *first_free;
};
//- Job pool
AlignedStruct(P_W32_JobPool, 64)
{
/* Jobs */
P_W32_JobQueue job_queues[P_Priority_Count];
P_W32_TicketMutex free_fibers_lock;
i16 first_free_fiber_id;
/* Workers */
Atomic32Padded workers_shutdown;
Atomic64Padded num_jobs_in_queue;
P_W32_TicketMutex workers_wake_lock;
i32 num_worker_threads;
i32 thread_priority;
u64 thread_affinity_mask;
b32 thread_is_audio;
Arena *worker_threads_arena;
P_W32_Thread **worker_threads;
P_W32_WorkerCtx *worker_contexts;
};
////////////////////////////////
//~ Window types
typedef i32 P_W32_CursorFlag; enum
{
P_W32_CursorFlag_None = (0 << 0),
P_W32_CursorFlag_Position = (1 << 0),
P_W32_CursorFlag_Hide = (1 << 1),
P_W32_CursorFlag_Show = (1 << 2),
P_W32_CursorFlag_EnableClip = (1 << 3),
P_W32_CursorFlag_DisableClip = (1 << 4)
};
Struct(P_W32_Window)
{
u32 flags;
HWND hwnd;
P_Counter ready_fence;
u16 utf16_high_surrogate_last_input;
P_Mutex settings_mutex;
P_WindowSettings 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;
Vec2 cursor_set_position;
Rect cursor_clip_bounds;
Atomic32 topmost_toggles;
b32 is_topmost;
P_Mutex event_arena_swp_mutex;
i32 current_event_arena_index;
Arena *event_arenas[2];
P_W32_Thread *window_thread;
Atomic32 shutdown;
P_W32_Window *next_free;
};
////////////////////////////////
//~ Watch types
Struct(P_W32_Watch)
{
HANDLE dir_handle;
HANDLE wake_handle;
P_W32_Watch *next_free;
u8 results_buff[Kibi(64)];
};
////////////////////////////////
//~ Address types
Struct(P_W32_Address)
{
i32 size;
i32 family;
union
{
struct sockaddr_storage sas;
struct sockaddr sa;
struct sockaddr_in sin;
struct sockaddr_in6 sin6;
};
};
////////////////////////////////
//~ Sock types
Struct(P_W32_Sock)
{
SOCKET sock;
P_W32_Sock *next_free;
};
////////////////////////////////
//~ Shared state
#define P_W32_WindowClassName L"power_play_window_class"
/* Assume scheduler cycle is 20hz at start to be conservative */
#define P_W32_DefaultSchedulerPeriodNs 50000000
#define P_W32_NumRollingSchedulerPeriods 1000
#define P_W32_NumWaitAddrBins 16384
#define P_W32_NumWaitTimeBins 1024
#define P_W32_MaxOnExitFuncs 1024
/* Arbitrary threshold for determining when to fall back from a looped WakeByAddressSingle to WakeByAddressAll */
#define P_W32_WakeAllThreshold 16
Struct(P_W32_SharedCtx)
{
SYSTEM_INFO info;
i64 timer_start_qpc;
i64 ns_per_qpc;
u32 main_thread_id;
Atomic32 shutdown;
wchar_t cmdline_args_wstr[8192];
//- Application control flow
Atomic32 panicking;
wchar_t panic_wstr[4096];
HANDLE panic_event;
HANDLE startup_end_event;
HANDLE exit_begin_event;
HANDLE exit_end_event;
//- Key lookup table
P_Btn vk_btn_table[256];
//- Worker thread pool
P_Mutex threads_mutex;
Arena *threads_arena;
P_W32_Thread *first_thread;
P_W32_Thread *last_thread;
P_W32_Thread *first_free_thread;
//- Watches pool
P_Mutex watches_mutex;
Arena *watches_arena;
P_W32_Watch *watches_first_free;
//- Windows pool
WNDCLASSEXW window_class;
P_Mutex windows_mutex;
Arena *windows_arena;
P_W32_Window *first_free_window;
//- Socket pool
WSADATA wsa_data;
Arena *socks_arena;
P_Mutex socks_mutex;
P_W32_Sock *first_free_sock;
//- Exit funcs
Atomic32 num_exit_funcs;
P_ExitFunc *exit_funcs[P_W32_MaxOnExitFuncs];
//- Scheduler
Atomic64Padded current_scheduler_cycle;
Atomic64Padded current_scheduler_cycle_period_ns;
//- Fibers
P_W32_TicketMutex fibers_lock;
i16 num_fibers;
Arena *fiber_names_arena;
P_W32_Fiber fibers[MaxFibers];
//- Wait lists
Atomic64Padded waiter_wake_gen;
P_W32_TicketMutex wait_lists_arena_lock;
Arena *wait_lists_arena;
//- Wait tables
P_W32_WaitBin wait_addr_bins[P_W32_NumWaitAddrBins];
P_W32_WaitBin wait_time_bins[P_W32_NumWaitTimeBins];
//- Job pools
P_W32_JobPool job_pools[P_Pool_Count];
};
extern P_W32_SharedCtx P_W32_shared_ctx;
////////////////////////////////
//~ Ticket mutex operations
void P_W32_LockTicketMutex(P_W32_TicketMutex *tm);
void P_W32_UnlockTicketMutex(P_W32_TicketMutex *tm);
////////////////////////////////
//~ Thread operations
DWORD WINAPI P_W32_Win32ThreadProc(LPVOID vt);
P_W32_Thread *P_W32_AllocThread(P_W32_ThreadFunc *entry_point, void *thread_data, String thread_name, i32 profiler_group);
b32 P_W32_TryReleaseThread(P_W32_Thread *thread, f32 timeout_seconds);
void P_W32_WaitReleaseThread(P_W32_Thread *thread);
////////////////////////////////
//~ Wait list operations
void P_W32_WakeLockedFibers(i32 num_fibers, P_W32_Fiber **fibers);
void P_W32_WakeByAddress(void *addr, i32 count);
void P_W32_WakeByTime(u64 time);
////////////////////////////////
//~ Fiber operations
P_W32_Fiber *P_W32_AllocFiber(P_W32_JobPool *pool);
void P_W32_ReleaseFiber(P_W32_JobPool *pool, P_W32_Fiber *fiber);
ForceInline P_W32_Fiber *P_W32_FiberFromId(i16 id);
ForceNoInline void P_W32_FiberResume(P_W32_Fiber *fiber);
void P_W32_YieldFiber(P_W32_Fiber *fiber, P_W32_Fiber *parent_fiber);
void P_W32_FiberEntryPoint(void *id_ptr);
////////////////////////////////
//~ Workers
P_W32_ThreadDef(P_W32_JobWorkerEntryFunc, worker_ctx_arg);
P_W32_ThreadDef(P_W32_JobSchedulerEntryFunc, _);
////////////////////////////////
//~ Time operations
P_DateTime P_W32_DateTimeFromWin32SystemTime(SYSTEMTIME st);
////////////////////////////////
//~ File system operations
String P_W32_StringFromWin32Path(Arena *arena, wchar_t *src);
////////////////////////////////
//~ Window operations
P_W32_Window *P_W32_AllocWindow(void);
void P_W32_ReleaseWindow(P_W32_Window *window);
HWND P_W32_InitWindow(P_W32_Window *window);
//- Window settings
void P_W32_UpdateWindowFromSystem(P_W32_Window *window);
void P_W32_UpdateWindowFromSettings(P_W32_Window *window, P_WindowSettings *settings);
//- Window thread
P_W32_ThreadDef(P_W32_WindowThreadEntryFunc, arg);
void P_W32_ProcessWindowEvent(P_W32_Window *window, P_WindowEvent event);
void P_W32_WakeWindow(P_W32_Window *window);
LRESULT CALLBACK P_W32_Win32WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
////////////////////////////////
//~ Address operations
P_W32_Address P_W32_Win32AddressFromPlatformAddress(P_Address addr);
P_W32_Address P_W32_ConvertAnyaddrToLocalhost(P_W32_Address addr);
P_Address P_W32_PlatformAddressFromWin32Address(P_W32_Address ws_addr);
////////////////////////////////
//~ Entry point
void P_W32_InitBtnTable(void);
P_JobDef(P_W32_AppStartupJob, _);
P_JobDef(P_W32_AppShutdownJob, _);