P_W32_SharedCtx P_W32_shared_ctx = ZI; //////////////////////////////// //~ Win32 libs #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") #pragma comment(lib, "synchronization") #pragma comment(lib, "avrt") #pragma comment(lib, "ws2_32.lib") //////////////////////////////// //~ Win32 ticket mutex void P_W32_LockTicketMutex(P_W32_TicketMutex *tm) { i64 ticket = Atomic64FetchAdd(&tm->ticket.v, 1); while (Atomic64Fetch(&tm->serving.v) != ticket) { IxPause(); } } void P_W32_UnlockTicketMutex(P_W32_TicketMutex *tm) { Atomic64FetchAdd(&tm->serving.v, 1); } //////////////////////////////// //~ Win32 thread DWORD WINAPI P_W32_Win32ThreadProc(LPVOID vt) { P_W32_AllocFiber(0); P_W32_Thread *t = (P_W32_Thread *)vt; __profthread(t->thread_name_cstr, t->profiler_group); /* Initialize COM */ CoInitializeEx(0, COINIT_MULTITHREADED); /* Set thread name */ if (t->thread_name_wstr[0] != 0) { SetThreadDescription(GetCurrentThread(), t->thread_name_wstr); } P_LogInfoF("New thread \"%F\" created with ID %F", FmtString(StringFromCstrNoLimit(t->thread_name_cstr)), FmtUint(P_GetThreadId())); /* Enter thread entry point */ t->entry_point(t->thread_data); /* Uninitialize COM */ CoUninitialize(); return 0; } P_W32_Thread *P_W32_AllocThread(P_W32_ThreadFunc *entry_point, void *thread_data, String thread_name, i32 profiler_group) { __prof; TempArena scratch = BeginScratchNoConflict(); P_W32_SharedCtx *g = &P_W32_shared_ctx; Assert(entry_point != 0); P_LogInfoF("Creating thread \"%F\"", FmtString(thread_name)); /* Allocate thread object */ P_W32_Thread *t = 0; { P_Lock lock = P_LockE(&g->threads_mutex); if (g->first_free_thread) { t = g->first_free_thread; g->first_free_thread = t->next; } else { t = PushStructNoZero(g->threads_arena, P_W32_Thread); } ZeroStruct(t); if (g->last_thread) { g->last_thread->next = t; t->prev = g->last_thread; } else { g->first_thread = t; } g->last_thread = t; P_Unlock(&lock); } t->entry_point = entry_point; t->thread_data = thread_data; t->profiler_group = profiler_group; /* CopyStruct thread name to params */ { u64 CstrLen = MinU64((countof(t->thread_name_cstr) - 1), thread_name.len); CopyBytes(t->thread_name_cstr, thread_name.text, CstrLen * sizeof(*t->thread_name_cstr)); t->thread_name_cstr[CstrLen] = 0; } { String16 thread_name16 = String16FromString(scratch.arena, thread_name); u64 WstrLen = MinU64((countof(t->thread_name_wstr) - 1), thread_name16.len); CopyBytes(t->thread_name_wstr, thread_name16.text, WstrLen * sizeof(*t->thread_name_wstr)); t->thread_name_wstr[WstrLen] = 0; } t->handle = CreateThread( 0, P_W32_ThreadStackSize, P_W32_Win32ThreadProc, t, 0, 0 ); if (!t->handle) { P_Panic(Lit("Failed to create thread")); } EndScratch(scratch); return (P_W32_Thread *)t; } /* Returns 0 if the thread could not release in specified timeout (e.g. because it is still running) */ b32 P_W32_TryReleaseThread(P_W32_Thread *thread, f32 timeout_seconds) { __prof; P_W32_SharedCtx *g = &P_W32_shared_ctx; b32 success = 0; P_W32_Thread *t = (P_W32_Thread *)thread; HANDLE handle = t->handle; if (handle) { /* Wait for thread to stop */ DWORD timeout_ms = (timeout_seconds > 10000000) ? INFINITE : RoundF32ToI32(timeout_seconds * 1000); DWORD wait_res = WaitForSingleObject(handle, timeout_ms); if (wait_res == WAIT_OBJECT_0) { /* Release thread */ success = 1; CloseHandle(handle); { P_Lock lock = P_LockE(&g->threads_mutex); { P_W32_Thread *prev = t->prev; P_W32_Thread *next = t->next; if (prev) { prev->next = next; } else { g->first_thread = next; } if (next) { next->prev = prev; } else { g->last_thread = prev; } t->next = g->first_free_thread; g->first_free_thread = t; } P_Unlock(&lock); } } } return success; } void P_W32_WaitReleaseThread(P_W32_Thread *thread) { __prof; b32 success = P_W32_TryReleaseThread(thread, F32Infinity); Assert(success); (UNUSED)success; } //////////////////////////////// //~ Win32 wait list /* REQUIRED: Caller must have acquired `wake_lock` for each fiber in array */ void P_W32_WakeLockedFibers(i32 num_fibers, P_W32_Fiber **fibers) { P_W32_SharedCtx *g = &P_W32_shared_ctx; /* Update wait lists */ for (i32 i = 0; i < num_fibers; ++i) { P_W32_Fiber *fiber = fibers[i]; u64 wait_addr = fiber->wait_addr; u64 wait_time = fiber->wait_time; /* Lock & search wait bins */ /* TODO: Cache these in parameters since caller has one of them already calculated */ P_W32_WaitBin *wait_addr_bin = 0; P_W32_WaitBin *wait_time_bin = 0; P_W32_WaitList *wait_addr_list = 0; P_W32_WaitList *wait_time_list = 0; if (wait_addr != 0) { wait_addr_bin = &g->wait_addr_bins[wait_addr % P_W32_NumWaitAddrBins]; P_W32_LockTicketMutex(&wait_addr_bin->lock); for (P_W32_WaitList *tmp = wait_addr_bin->first_wait_list; tmp && !wait_addr_list; tmp = tmp->next_in_bin) { if (tmp->value == (u64)wait_addr) { wait_addr_list = tmp; } } } if (wait_time != 0) { wait_time_bin = &g->wait_time_bins[wait_time % P_W32_NumWaitTimeBins]; P_W32_LockTicketMutex(&wait_time_bin->lock); for (P_W32_WaitList *tmp = wait_time_bin->first_wait_list; tmp && !wait_time_list; tmp = tmp->next_in_bin) { if (tmp->value == (u64)wait_time) { wait_time_list = tmp; } } } { /* Remove from addr list */ if (wait_addr_list) { if (--wait_addr_list->num_waiters == 0) { /* Free addr list */ P_W32_WaitList *prev = wait_addr_list->prev_in_bin; P_W32_WaitList *next = wait_addr_list->next_in_bin; if (prev) { prev->next_in_bin = next; } else { wait_addr_bin->first_wait_list = next; } if (next) { next->prev_in_bin = prev; } else { wait_addr_bin->last_wait_list = prev; } wait_addr_list->next_in_bin = wait_addr_bin->first_free_wait_list; wait_addr_bin->first_free_wait_list = wait_addr_list; } else { i16 prev_id = fiber->prev_addr_waiter; i16 next_id = fiber->next_addr_waiter; if (prev_id) { P_W32_FiberFromId(prev_id)->next_addr_waiter = next_id; } else { wait_addr_list->first_waiter = next_id; } if (next_id) { P_W32_FiberFromId(next_id)->prev_addr_waiter = prev_id; } else { wait_addr_list->last_waiter = prev_id; } } fiber->wait_addr = 0; fiber->prev_addr_waiter = 0; fiber->next_addr_waiter = 0; } /* Remove from time list */ if (wait_time_list) { if (--wait_time_list->num_waiters == 0) { /* Free time list */ P_W32_WaitList *prev = wait_time_list->prev_in_bin; P_W32_WaitList *next = wait_time_list->next_in_bin; if (prev) { prev->next_in_bin = next; } else { wait_time_bin->first_wait_list = next; } if (next) { next->prev_in_bin = prev; } else { wait_time_bin->last_wait_list = prev; } wait_time_list->next_in_bin = wait_time_bin->first_free_wait_list; wait_time_bin->first_free_wait_list = wait_time_list; } else { i16 prev_id = fiber->prev_time_waiter; i16 next_id = fiber->next_time_waiter; if (prev_id) { P_W32_FiberFromId(prev_id)->next_time_waiter = next_id; } else { wait_time_list->first_waiter = next_id; } if (next_id) { P_W32_FiberFromId(next_id)->prev_time_waiter = prev_id; } else { wait_time_list->last_waiter = prev_id; } } fiber->wait_time = 0; fiber->prev_time_waiter = 0; fiber->next_time_waiter = 0; } /* Unlock fiber */ Atomic32FetchSet(&fiber->wake_lock, 0); } /* Unlock wait bins */ if (wait_time_bin != 0) P_W32_UnlockTicketMutex(&wait_time_bin->lock); if (wait_addr_bin != 0) P_W32_UnlockTicketMutex(&wait_addr_bin->lock); } /* Resume jobs */ /* TODO: Batch submit waiters based on queue kind rather than one at a time */ i32 job_counts_per_pool[P_Pool_Count] = ZI; for (i32 i = 0; i < num_fibers; ++i) { P_W32_Fiber *fiber = fibers[i]; P_Pool pool_kind = fiber->job_pool; ++job_counts_per_pool[pool_kind]; P_W32_JobPool *pool = &g->job_pools[pool_kind]; P_W32_JobQueue *queue = &pool->job_queues[fiber->job_priority]; P_W32_LockTicketMutex(&queue->lock); { P_W32_JobInfo *info = 0; if (queue->first_free) { info = queue->first_free; queue->first_free = info->next; } else { info = PushStructNoZero(queue->arena, P_W32_JobInfo); } ZeroStruct(info); info->count = 1; info->num_dispatched = fiber->job_id; info->func = fiber->job_func; info->sig = fiber->job_sig; info->counter = fiber->job_counter; info->fiber_id = fiber->id; if (queue->first) { info->next = queue->first; } else { queue->last = info; } queue->first = info; } P_W32_UnlockTicketMutex(&queue->lock); } /* Wake workers */ if (num_fibers > 0) { for (P_Pool pool_kind = 0; pool_kind < (i32)countof(job_counts_per_pool); ++pool_kind) { i32 job_count = job_counts_per_pool[pool_kind]; if (job_count > 0) { P_W32_JobPool *pool = &g->job_pools[pool_kind]; P_W32_LockTicketMutex(&pool->workers_wake_lock); { Atomic64FetchAdd(&pool->num_jobs_in_queue.v, job_count); if (job_count >= P_W32_WakeAllThreshold) { WakeByAddressAll(&pool->num_jobs_in_queue); } else { for (i32 i = 0; i < job_count; ++i) { WakeByAddressSingle(&pool->num_jobs_in_queue); } } } P_W32_UnlockTicketMutex(&pool->workers_wake_lock); } } } } void P_W32_WakeByAddress(void *addr, i32 count) { TempArena scratch = BeginScratchNoConflict(); P_W32_SharedCtx *g = &P_W32_shared_ctx; u64 wait_addr_bin_index = (u64)addr % P_W32_NumWaitAddrBins; P_W32_WaitBin *wait_addr_bin = &g->wait_addr_bins[wait_addr_bin_index]; P_W32_WaitList *wait_addr_list = 0; /* Get list of waiting fibers */ i32 num_fibers = 0; P_W32_Fiber **fibers = 0; { P_W32_LockTicketMutex(&wait_addr_bin->lock); { /* Search for wait addr list */ for (P_W32_WaitList *tmp = wait_addr_bin->first_wait_list; tmp && !wait_addr_list; tmp = tmp->next_in_bin) { if (tmp->value == (u64)addr) { wait_addr_list = tmp; } } /* Lock fibers & build array */ if (wait_addr_list) { fibers = PushStructsNoZero(scratch.arena, P_W32_Fiber *, wait_addr_list->num_waiters); for (P_W32_Fiber *fiber = P_W32_FiberFromId(wait_addr_list->first_waiter); fiber && num_fibers < count; fiber = P_W32_FiberFromId(fiber->next_addr_waiter)) { if (Atomic32FetchTestSet(&fiber->wake_lock, 0, 1) == 0) { fibers[num_fibers] = fiber; ++num_fibers; } } } } P_W32_UnlockTicketMutex(&wait_addr_bin->lock); } if (num_fibers > 0) { P_W32_WakeLockedFibers(num_fibers, fibers); } /* Wake win32 blocking thread waiters */ if (count >= P_W32_WakeAllThreshold) { WakeByAddressAll(addr); } else { for (i32 i = 0; i < count; ++i) { WakeByAddressSingle(addr); } } EndScratch(scratch); } void P_W32_WakeByTime(u64 time) { TempArena scratch = BeginScratchNoConflict(); P_W32_SharedCtx *g = &P_W32_shared_ctx; u64 wait_time_bin_index = (u64)time % P_W32_NumWaitTimeBins; P_W32_WaitBin *wait_time_bin = &g->wait_time_bins[wait_time_bin_index]; P_W32_WaitList *wait_time_list = 0; /* Build list of waiters to resume */ i32 num_fibers = 0; P_W32_Fiber **fibers = 0; { P_W32_LockTicketMutex(&wait_time_bin->lock); { /* Search for wait time list */ for (P_W32_WaitList *tmp = wait_time_bin->first_wait_list; tmp && !wait_time_list; tmp = tmp->next_in_bin) { if (tmp->value == (u64)time) { wait_time_list = tmp; } } if (wait_time_list) { /* Set waiter wake status & build fibers list */ fibers = PushStructsNoZero(scratch.arena, P_W32_Fiber *, wait_time_list->num_waiters); for (P_W32_Fiber *fiber = P_W32_FiberFromId(wait_time_list->first_waiter); fiber; fiber = P_W32_FiberFromId(fiber->next_time_waiter)) { if (Atomic32FetchTestSet(&fiber->wake_lock, 0, 1) == 0) { fibers[num_fibers] = fiber; ++num_fibers; } } } } P_W32_UnlockTicketMutex(&wait_time_bin->lock); } P_W32_WakeLockedFibers(num_fibers, fibers); EndScratch(scratch); } //////////////////////////////// //~ Win32 fiber /* If `pool` is 0, then the currently running thread will be converted into a fiber */ P_W32_Fiber *P_W32_AllocFiber(P_W32_JobPool *pool) { P_W32_SharedCtx *g = &P_W32_shared_ctx; i16 fiber_id = 0; P_W32_Fiber *fiber = 0; char *new_name_cstr = 0; { if (pool != 0) { P_W32_LockTicketMutex(&pool->free_fibers_lock); if (pool->first_free_fiber_id) { fiber_id = pool->first_free_fiber_id; fiber = &g->fibers[fiber_id]; pool->first_free_fiber_id = fiber->parent_id; } P_W32_UnlockTicketMutex(&pool->free_fibers_lock); } if (!fiber_id) { P_W32_LockTicketMutex(&g->fibers_lock); { { fiber_id = g->num_fibers++; if (fiber_id >= MaxFibers) { P_Panic(Lit("Max fibers reached")); } fiber = &g->fibers[fiber_id]; new_name_cstr = PushStructs(g->fiber_names_arena, char, P_W32_FiberNameMaxSize); } } P_W32_UnlockTicketMutex(&g->fibers_lock); } } if (new_name_cstr != 0) { __profn("Initialize fiber"); fiber->id = fiber_id; /* Id to ASCII */ i32 id_div = fiber_id; char id_chars[64] = ZI; i32 id_chars_len = 0; do { i32 digit = id_div % 10; id_div /= 10; id_chars[id_chars_len] = ("0123456789")[digit]; ++id_chars_len; } while (id_div > 0); i32 rev_start = 0; i32 rev_end = id_chars_len - 1; while (rev_start < rev_end) { char swp = id_chars[rev_start]; id_chars[rev_start] = id_chars[rev_end]; id_chars[rev_end] = swp; ++rev_start; --rev_end; } /* Concat fiber name */ i32 name_size = 1; Assert(sizeof(sizeof(P_W32_FiberNamePrefixCstr)) <= P_W32_FiberNameMaxSize); CopyBytes(new_name_cstr, P_W32_FiberNamePrefixCstr, sizeof(P_W32_FiberNamePrefixCstr)); name_size += sizeof(P_W32_FiberNamePrefixCstr) - 2; CopyBytes(new_name_cstr + name_size, id_chars, id_chars_len); name_size += id_chars_len; CopyBytes(new_name_cstr + name_size, P_W32_FiberNameSuffixCstr, sizeof(P_W32_FiberNameSuffixCstr)); name_size += sizeof(P_W32_FiberNameSuffixCstr) - 2; fiber->name_cstr = new_name_cstr; /* Init win32 fiber */ if (pool != 0) { __profn("CreateFiber"); fiber->addr = CreateFiber(P_W32_FiberStackSize, P_W32_FiberEntryPoint, (void *)(i64)fiber_id); } else { /* Fiber is not a part of a job pool, convert thread to fiber */ __profn("ConvertThreadToFiber"); fiber->addr = ConvertThreadToFiber((void *)(i64)fiber_id); } } fiber->wait_addr = 0; fiber->wait_time = 0; fiber->prev_addr_waiter = 0; fiber->next_addr_waiter = 0; fiber->prev_time_waiter = 0; fiber->next_time_waiter = 0; fiber->job_func = 0; fiber->job_sig = 0; fiber->job_id = 0; fiber->job_pool = 0; fiber->job_priority = 0; fiber->job_counter = 0; fiber->yield_param = 0; fiber->parent_id = 0; return fiber; } void P_W32_ReleaseFiber(P_W32_JobPool *pool, P_W32_Fiber *fiber) { P_W32_LockTicketMutex(&pool->free_fibers_lock); { i16 fiber_id = fiber->id; fiber->parent_id = pool->first_free_fiber_id; pool->first_free_fiber_id = fiber_id; } P_W32_UnlockTicketMutex(&pool->free_fibers_lock); } ForceInline P_W32_Fiber *P_W32_FiberFromId(i16 id) { P_W32_SharedCtx *g = &P_W32_shared_ctx; if (id <= 0) { return 0; } else { return &g->fibers[id]; } } ForceNoInline void P_W32_FiberResume(P_W32_Fiber *fiber) { MemoryBarrier(); SwitchToFiber(fiber->addr); MemoryBarrier(); } void P_W32_YieldFiber(P_W32_Fiber *fiber, P_W32_Fiber *parent_fiber) { (UNUSED)fiber; Assert(fiber->id == FiberId()); Assert(parent_fiber->id == fiber->parent_id); Assert(parent_fiber->id > 0); { __prof_fiber_leave(); P_W32_FiberResume(parent_fiber); __prof_fiber_enter(fiber->name_cstr, PROF_THREAD_GROUP_FIBERS - Mebi(fiber->job_pool) + Kibi(1) + fiber->id); } } void P_W32_FiberEntryPoint(void *id_ptr) { i16 id = (i32)(i64)id_ptr; volatile P_W32_Fiber *fiber = P_W32_FiberFromId(id); __prof_fiber_enter(fiber->name_cstr, PROF_THREAD_GROUP_FIBERS - Mebi(fiber->job_pool) + Kibi(1) + fiber->id); for (;;) { /* Run job */ { P_W32_YieldParam *yield_param = fiber->yield_param; yield_param->kind = P_W32_YieldKind_None; P_JobData data = ZI; data.id = fiber->job_id; data.sig = fiber->job_sig; { MemoryBarrier(); fiber->job_func(data); MemoryBarrier(); } } /* Job completed, yield */ { /* Decrement job counter */ P_Counter *job_counter = fiber->job_counter; if (job_counter) { P_CounterAdd(job_counter, -1); } /* Yield to worker */ fiber->yield_param->kind = P_W32_YieldKind_Done; P_W32_Fiber *parent_fiber = P_W32_FiberFromId(fiber->parent_id); P_W32_YieldFiber((P_W32_Fiber *)fiber, parent_fiber); } } } //////////////////////////////// //~ Win32 job worker P_W32_ThreadDef(P_W32_JobWorkerEntryFunc, worker_ctx_arg) { P_W32_SharedCtx *g = &P_W32_shared_ctx; P_W32_WorkerCtx *ctx = worker_ctx_arg; P_Pool pool_kind = ctx->pool_kind; P_W32_JobPool *pool = &g->job_pools[pool_kind]; (UNUSED)ctx; { /* TODO: Heuristic pinning */ /* TODO: Pin non-worker threads to other cores */ HANDLE thread_handle = GetCurrentThread(); if (pool->thread_priority) { __profn("Set priority"); b32 success = SetThreadPriority(thread_handle, pool->thread_priority) != 0; Assert(success); (UNUSED)success; } #if 0 if (pool->thread_affinity_mask) { __profn("Set affinity"); b32 success = SetThreadAffinityMask(thread_handle, pool->thread_affinity_mask) != 0; #if RtcIsEnabled || ProfilingIsEnabled { /* Retry until external tools can set correct process affinity */ i32 delay_ms = 16; while (!success && delay_ms <= 1024) { __profn("Affinity retry"); Sleep(delay_ms); success = SetThreadAffinityMask(thread_handle, pool->thread_affinity_mask) != 0; delay_ms *= 2; } } #endif Assert(success); (UNUSED)success; } #endif if (pool->thread_is_audio) { /* https://learn.microsoft.com/en-us/windows/win32/procthread/multimedia-class-scheduler-service#registry-settings */ __profn("Set mm thread characteristics"); DWORD task = 0; HANDLE mmc_handle = AvSetMmThreadCharacteristics(L"Pro Audio", &task); Assert(mmc_handle); (UNUSED)mmc_handle; } } i32 worker_fiber_id = FiberId(); P_W32_Fiber *job_fiber = 0; b32 shutdown = 0; while (!shutdown) { /* Pull job from queue */ P_Priority job_priority = 0; i16 job_fiber_id = 0; i32 job_id = 0; P_JobFunc *job_func = 0; void *job_sig = 0; P_Counter *job_counter = 0; { //__profnc("Pull job", Rgb32F(0.75, 0.75, 0)); for (P_Priority priority = 0; priority < (i32)countof(pool->job_queues) && !job_func; ++priority) { P_W32_JobQueue *queue = &pool->job_queues[priority]; if (queue) { P_W32_LockTicketMutex(&queue->lock); { P_W32_JobInfo *info = queue->first; while (info && !job_func) { P_W32_JobInfo *next = info->next; b32 dequeue = 0; if (info->fiber_id <= 0) { job_id = info->num_dispatched++; if (job_id < info->count) { /* Pick job */ Atomic64FetchAdd(&pool->num_jobs_in_queue.v, -1); job_priority = priority; job_func = info->func; job_sig = info->sig; job_counter = info->counter; if (job_id == (info->count - 1)) { /* We're picking up the last dispatch, so dequeue the job */ dequeue = 1; } } } else { /* This job is to be resumed from a yield */ Atomic64FetchAdd(&pool->num_jobs_in_queue.v, -1); job_fiber_id = info->fiber_id; job_priority = priority; job_id = info->num_dispatched; job_func = info->func; job_sig = info->sig; job_counter = info->counter; dequeue = 1; } if (dequeue) { if (!next) { queue->last = 0; } queue->first = next; info->next = queue->first_free; queue->first_free = info; } info = next; } } P_W32_UnlockTicketMutex(&queue->lock); } } } /* Use resumed fiber if present */ if (job_fiber_id > 0) { if (job_fiber) { P_W32_ReleaseFiber(pool, job_fiber); } job_fiber = P_W32_FiberFromId(job_fiber_id); } /* Run fiber */ if (job_func) { if (!job_fiber) { job_fiber = P_W32_AllocFiber(pool); } job_fiber_id = job_fiber->id; { __profnc("Run fiber", Rgb32F(1, 1, 1)); __profvalue(job_fiber->id); P_W32_YieldParam yield = ZI; job_fiber->parent_id = worker_fiber_id; job_fiber->job_func = job_func; job_fiber->job_sig = job_sig; job_fiber->job_id = job_id; job_fiber->job_pool = pool_kind; job_fiber->job_priority = job_priority; job_fiber->job_counter = job_counter; job_fiber->yield_param = &yield; b32 done = 0; while (!done) { P_W32_FiberResume(job_fiber); switch (yield.kind) { default: { /* Invalid yield kind */ TempArena scratch = BeginScratchNoConflict(); P_Panic(StringFormat(scratch.arena, Lit("Invalid fiber yield kind \"%F\""), FmtSint(yield.kind))); EndScratch(scratch); } break; case P_W32_YieldKind_Wait: { __profn("Process fiber wait"); volatile void *wait_addr = yield.wait.addr; void *wait_cmp = yield.wait.cmp; u32 wait_size = yield.wait.size; i64 wait_timeout_ns = yield.wait.timeout_ns; i64 wait_time = 0; if (wait_timeout_ns > 0 && wait_timeout_ns < I64Max) { u64 current_scheduler_cycle = Atomic64Fetch(&g->current_scheduler_cycle.v); i64 current_scheduler_cycle_period_ns = Atomic64Fetch(&g->current_scheduler_cycle_period_ns.v); wait_time = current_scheduler_cycle + MaxI64((i64)((f64)wait_timeout_ns / (f64)current_scheduler_cycle_period_ns), 1); } u64 wait_addr_bin_index = (u64)wait_addr % P_W32_NumWaitAddrBins; u64 wait_time_bin_index = (u64)wait_time % P_W32_NumWaitTimeBins; P_W32_WaitBin *wait_addr_bin = &g->wait_addr_bins[wait_addr_bin_index]; P_W32_WaitBin *wait_time_bin = &g->wait_time_bins[wait_time_bin_index]; if (wait_addr != 0) P_W32_LockTicketMutex(&wait_addr_bin->lock); { if (wait_time != 0) P_W32_LockTicketMutex(&wait_time_bin->lock); { b32 cancel_wait = wait_addr == 0 && wait_time == 0; if (wait_addr != 0) { switch (wait_size) { case 1: cancel_wait = (u8)_InterlockedCompareExchange8(wait_addr, 0, 0) != *(u8 *)wait_cmp; break; case 2: cancel_wait = (u16)_InterlockedCompareExchange16(wait_addr, 0, 0) != *(u16 *)wait_cmp; break; case 4: cancel_wait = (u32)_InterlockedCompareExchange(wait_addr, 0, 0) != *(u32 *)wait_cmp; break; case 8: cancel_wait = (u64)_InterlockedCompareExchange64(wait_addr, 0, 0) != *(u64 *)wait_cmp; break; default: cancel_wait = 1; Assert(0); break; /* Invalid wait size */ } } if (wait_time != 0 && !cancel_wait) { cancel_wait = wait_time <= Atomic64Fetch(&g->current_scheduler_cycle.v); } if (!cancel_wait) { if (wait_addr != 0) { /* Search for wait addr list in bin */ P_W32_WaitList *wait_addr_list = 0; for (P_W32_WaitList *tmp = wait_addr_bin->first_wait_list; tmp && !wait_addr_list; tmp = tmp->next_in_bin) { if (tmp->value == (u64)wait_addr) { wait_addr_list = tmp; } } /* Allocate new wait addr list */ if (!wait_addr_list) { if (wait_addr_bin->first_free_wait_list) { wait_addr_list = wait_addr_bin->first_free_wait_list; wait_addr_bin->first_free_wait_list = wait_addr_list->next_in_bin; } else { P_W32_LockTicketMutex(&g->wait_lists_arena_lock); { wait_addr_list = PushStructNoZero(g->wait_lists_arena, P_W32_WaitList); } P_W32_UnlockTicketMutex(&g->wait_lists_arena_lock); } ZeroStruct(wait_addr_list); wait_addr_list->value = (u64)wait_addr; if (wait_addr_bin->last_wait_list) { wait_addr_bin->last_wait_list->next_in_bin = wait_addr_list; wait_addr_list->prev_in_bin = wait_addr_bin->last_wait_list; } else { wait_addr_bin->first_wait_list = wait_addr_list; } wait_addr_bin->last_wait_list = wait_addr_list; } /* Insert fiber into wait addr list */ job_fiber->wait_addr = (u64)wait_addr; if (wait_addr_list->last_waiter) { P_W32_FiberFromId(wait_addr_list->last_waiter)->next_addr_waiter = job_fiber_id; job_fiber->prev_addr_waiter = wait_addr_list->last_waiter; } else { wait_addr_list->first_waiter = job_fiber_id; } wait_addr_list->last_waiter = job_fiber_id; ++wait_addr_list->num_waiters; } if (wait_time != 0) { /* Search for wait time list in bin */ P_W32_WaitList *wait_time_list = 0; for (P_W32_WaitList *tmp = wait_time_bin->first_wait_list; tmp && !wait_time_list; tmp = tmp->next_in_bin) { if (tmp->value == (u64)wait_time) { wait_time_list = tmp; } } /* Allocate new wait time list */ if (!wait_time_list) { if (wait_time_bin->first_free_wait_list) { wait_time_list = wait_time_bin->first_free_wait_list; wait_time_bin->first_free_wait_list = wait_time_list->next_in_bin; } else { P_W32_LockTicketMutex(&g->wait_lists_arena_lock); { wait_time_list = PushStructNoZero(g->wait_lists_arena, P_W32_WaitList); } P_W32_UnlockTicketMutex(&g->wait_lists_arena_lock); } ZeroStruct(wait_time_list); wait_time_list->value = wait_time; if (wait_time_bin->last_wait_list) { wait_time_bin->last_wait_list->next_in_bin = wait_time_list; wait_time_list->prev_in_bin = wait_time_bin->last_wait_list; } else { wait_time_bin->first_wait_list = wait_time_list; } wait_time_bin->last_wait_list = wait_time_list; } /* Insert fiber into wait time list */ job_fiber->wait_time = wait_time; if (wait_time_list->last_waiter) { P_W32_FiberFromId(wait_time_list->last_waiter)->next_time_waiter = job_fiber_id; job_fiber->prev_time_waiter = wait_time_list->last_waiter; } else { wait_time_list->first_waiter = job_fiber_id; } wait_time_list->last_waiter = job_fiber_id; ++wait_time_list->num_waiters; } /* PopStruct worker's job fiber */ job_fiber = 0; done = 1; } } if (wait_time != 0) P_W32_UnlockTicketMutex(&wait_time_bin->lock); } if (wait_addr != 0) P_W32_UnlockTicketMutex(&wait_addr_bin->lock); } break; case P_W32_YieldKind_Done: { done = 1; } break; } } } } /* Wait for job */ i64 num_jobs_in_queue = Atomic64Fetch(&pool->num_jobs_in_queue.v); shutdown = Atomic32Fetch(&pool->workers_shutdown.v); if (num_jobs_in_queue <= 0 && !shutdown) { //__profnc("Wait for job", Rgb32F(0.75, 0.75, 0)); P_W32_LockTicketMutex(&pool->workers_wake_lock); { num_jobs_in_queue = Atomic64Fetch(&pool->num_jobs_in_queue.v); shutdown = Atomic32Fetch(&pool->workers_shutdown.v); while (num_jobs_in_queue <= 0 && !shutdown) { { P_W32_UnlockTicketMutex(&pool->workers_wake_lock); WaitOnAddress(&pool->num_jobs_in_queue, &num_jobs_in_queue, sizeof(num_jobs_in_queue), INFINITE); P_W32_LockTicketMutex(&pool->workers_wake_lock); } shutdown = Atomic32Fetch(&pool->workers_shutdown.v); num_jobs_in_queue = Atomic64Fetch(&pool->num_jobs_in_queue.v); } } P_W32_UnlockTicketMutex(&pool->workers_wake_lock); } } /* Worker shutdown */ if (job_fiber) { P_W32_ReleaseFiber(pool, job_fiber); } } //////////////////////////////// //~ Win32 job scheduler P_W32_ThreadDef(P_W32_JobSchedulerEntryFunc, _) { struct P_W32_SharedCtx *g = &P_W32_shared_ctx; (UNUSED)_; { i32 priority = THREAD_PRIORITY_TIME_CRITICAL; b32 success = SetThreadPriority(GetCurrentThread(), priority); (UNUSED)success; Assert(success); } /* Create high resolution timer */ HANDLE timer = CreateWaitableTimerExW(0, 0, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS); if (!timer) { P_Panic(Lit("Failed to create high resolution timer")); } /* Create rolling buffer of scheduler cycles initialized to default value */ i32 periods_index = 0; i64 periods[P_W32_NumRollingSchedulerPeriods] = ZI; for (i32 i = 0; i < (i32)countof(periods); ++i) { periods[i] = P_W32_DefaultSchedulerPeriodNs; } i64 last_cycle_ns = 0; while (!Atomic32Fetch(&g->shutdown)) { __profn("Job scheduler cycle"); { __profn("Job scheduler wait"); LARGE_INTEGER due = ZI; due.QuadPart = -1; //due.QuadPart = -10000; //due.QuadPart = -32000; //due.QuadPart = -12000; //due.QuadPart = -8000; SetWaitableTimerEx(timer, &due, 0, 0, 0, 0, 0); WaitForSingleObject(timer, INFINITE); } /* Calculate mean period */ i64 now_ns = P_TimeNs(); i64 period_ns = last_cycle_ns == 0 ? P_W32_DefaultSchedulerPeriodNs : now_ns - last_cycle_ns; last_cycle_ns = now_ns; /* Calculate mean period */ { periods[periods_index++] = period_ns; if (periods_index == countof(periods)) { periods_index = 0; } f64 periods_sum_ns = 0; for (i32 i = 0; i < (i32)countof(periods); ++i) { periods_sum_ns += (f64)periods[i]; } f64 mean_ns = periods_sum_ns / (f64)countof(periods); Atomic64FetchSet(&g->current_scheduler_cycle_period_ns.v, RoundF64ToI64(mean_ns)); } { __profn("Job scheduler run"); i64 current_cycle = Atomic64FetchAdd(&g->current_scheduler_cycle.v, 1) + 1; P_W32_WakeByTime((u64)current_cycle); } } } //////////////////////////////// //~ Win32 time P_DateTime P_W32_DateTimeFromWin32SystemTime(SYSTEMTIME st) { return (P_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 }; } //////////////////////////////// //~ Win32 file system String P_W32_StringFromWin32Path(Arena *arena, wchar_t *src) { String res = { .len = 0, .text = PushDry(arena, u8) }; while (*src) { String16 decode_str = { .len = *(src + 1) ? 2 : 1, .text = src }; Utf16DecodeResult decoded = DecodeUtf16(decode_str); Utf8EncodeResult encoded = EncodeUtf8(decoded.codepoint); u8 *dest = PushStructsNoZero(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; } //////////////////////////////// //~ Win32 window P_W32_Window *P_W32_AllocWindow(void) { P_W32_SharedCtx *g = &P_W32_shared_ctx; P_W32_Window *window = 0; { P_Lock lock = P_LockE(&g->windows_mutex); if (g->first_free_window) { window = g->first_free_window; g->first_free_window = window->next_free; } else { window = PushStructNoZero(g->windows_arena, P_W32_Window); } P_Unlock(&lock); } ZeroStruct(window); window->event_arenas[0] = AllocArena(Gibi(64)); window->event_arenas[1] = AllocArena(Gibi(64)); /* Start window event thread */ /* NOTE: This thread must finish building for the window to actually be * created and receive a HWND, because on Windows a the event proc must run on * the same thread that created the window. */ P_CounterAdd(&window->ready_fence, 1); window->window_thread = P_W32_AllocThread(&P_W32_WindowThreadEntryFunc, window, Lit("Window thread"), PROF_THREAD_GROUP_WINDOW); P_WaitOnCounter(&window->ready_fence); return window; } void P_W32_ReleaseWindow(P_W32_Window *window) { /* Stop window threads */ Atomic32FetchSet(&window->shutdown, 1); P_W32_SharedCtx *g = &P_W32_shared_ctx; P_W32_WakeWindow(window); P_W32_WaitReleaseThread(window->window_thread); P_Lock lock = P_LockE(&g->windows_mutex); { window->next_free = g->first_free_window; g->first_free_window = window; } P_Unlock(&lock); } HWND P_W32_InitWindow(P_W32_Window *window) { struct P_W32_SharedCtx *g = &P_W32_shared_ctx; /* * 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, 0, 0, g->window_class.hInstance, 0 ); /* Dark mode */ BOOL dark_mode = 1; 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; } //////////////////////////////// //~ Win32 window settings void P_W32_UpdateWindowFromSystem(P_W32_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 & P_WindowFlag_Showing) { WINDOWPLACEMENT placement = { .length = sizeof(placement) }; GetWindowPlacement(hwnd, &placement); if (placement.showCmd == SW_SHOWMINIMIZED) { window->settings.flags |= P_WindowSettingsFlag_Minimized; } else { window->settings.flags &= ~P_WindowSettingsFlag_Minimized; } if (placement.showCmd == SW_SHOWMAXIMIZED || ((window->settings.flags & P_WindowSettingsFlag_Minimized) && ((placement.flags & WPF_RESTORETOMAXIMIZED) != 0))) { window->settings.flags |= P_WindowSettingsFlag_Maximized; } else { window->settings.flags &= ~P_WindowSettingsFlag_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 & P_WindowSettingsFlag_Minimized)) { window->x = x; window->y = y; window->width = width; window->height = height; if (!(window->settings.flags & (P_WindowSettingsFlag_Maximized | P_WindowSettingsFlag_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; } } } void P_W32_UpdateWindowFromSettings(P_W32_Window *window, P_WindowSettings *settings) { HWND hwnd = window->hwnd; P_WindowSettings old_settings = window->settings; window->settings = *settings; i32 show_cmd = SW_HIDE; if (window->flags & P_WindowFlag_Showing) { show_cmd = SW_NORMAL; if (settings->flags & P_WindowSettingsFlag_Maximized) { show_cmd = SW_SHOWMAXIMIZED; } else if (settings->flags & P_WindowSettingsFlag_Minimized) { show_cmd = SW_MINIMIZE; } } RECT rect = ZI; b32 old_fullscreen = old_settings.flags & P_WindowSettingsFlag_Fullscreen; b32 fullscreen = settings->flags & P_WindowSettingsFlag_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, 0); } WINDOWPLACEMENT wp = { .length = sizeof(WINDOWPLACEMENT), .showCmd = show_cmd, .rcNormalPosition = rect }; SetWindowPlacement(hwnd, &wp); { TempArena scratch = BeginScratchNoConflict(); wchar_t *title_wstr = WstrFromString(scratch.arena, StringFromCstrNoLimit(settings->title)); SetWindowTextW(hwnd, title_wstr); EndScratch(scratch); } } //////////////////////////////// //~ Win32 window thread P_W32_ThreadDef(P_W32_WindowThreadEntryFunc, arg) { P_W32_Window *window = (P_W32_Window *)arg; /* Win32 limitation: Window must be initialized on same thread that processes events */ window->hwnd = P_W32_InitWindow(window); P_W32_UpdateWindowFromSystem(window); BringWindowToTop(window->hwnd); P_CounterAdd(&window->ready_fence, -1); while (!Atomic32Fetch(&window->shutdown)) { MSG msg = ZI; { GetMessageW(&msg, 0, 0, 0); } { __profn("Process window message"); if (!Atomic32Fetch(&window->shutdown)) { TranslateMessage(&msg); DispatchMessageW(&msg); } } } /* Destroy window hwnd */ DestroyWindow(window->hwnd); } void P_W32_ProcessWindowEvent(P_W32_Window *window, P_WindowEvent event) { __prof; P_Lock lock = P_LockE(&window->event_arena_swp_mutex); { *PushStruct(window->event_arenas[window->current_event_arena_index], P_WindowEvent) = event; } P_Unlock(&lock); } void P_W32_WakeWindow(P_W32_Window *window) { /* Post a blank message to the window's thread message queue to wake it. */ PostMessageW(window->hwnd, 0, 0, 0); } LRESULT CALLBACK P_W32_Win32WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { __prof; P_W32_SharedCtx *g = &P_W32_shared_ctx; P_W32_Window *window = (P_W32_Window *)GetWindowLongPtrW(hwnd, GWLP_USERDATA); if (!window) { return DefWindowProcW(hwnd, msg, wparam, lparam); } /* Update cursor */ if (GetFocus() == window->hwnd) { u32 cursor_flags = window->cursor_set_flags; /* Hide cursor */ if (cursor_flags & P_W32_CursorFlag_Hide) { while (ShowCursor(0) >= 0); } /* Show cursor */ if (cursor_flags & P_W32_CursorFlag_Show) { while (ShowCursor(1) < 0); } /* Update position */ if (cursor_flags & P_W32_CursorFlag_Position) { Vec2 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 & P_W32_CursorFlag_DisableClip) { ClipCursor(0); } /* Clip cursor in window window */ if (cursor_flags & P_W32_CursorFlag_EnableClip) { i32 left = window->x + RoundF32ToI32(window->cursor_clip_bounds.x); i32 right = left + RoundF32ToI32(window->cursor_clip_bounds.width); i32 top = window->y + RoundF32ToI32(window->cursor_clip_bounds.y); i32 bottom = top + RoundF32ToI32(window->cursor_clip_bounds.height); RECT clip = { .left = ClampI32(left, window->x, window->x + window->width), .right = ClampI32(right, window->x, window->x + window->width), .top = ClampI32(top, window->y, window->y + window->height), .bottom = ClampI32(bottom, window->y, window->y + window->height) }; ClipCursor(&clip); } window->cursor_set_flags = 0; } /* Update always on top */ { u32 toggles = Atomic32FetchSet(&window->topmost_toggles, 0); if (toggles % 2 != 0) { b32 new_topmost = !window->is_topmost; if (new_topmost) { SetWindowText(hwnd, L"============================= TOP ============================="); } else { SetWindowText(hwnd, L""); } SetWindowPos(hwnd, new_topmost ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); window->is_topmost = new_topmost; } } LRESULT result = 0; b32 is_release = 0; switch (msg) { case WM_QUIT: case WM_CLOSE: case WM_DESTROY: { P_W32_ProcessWindowEvent(window, (P_WindowEvent) { .kind = P_WindowEventKind_Quit }); } break; case WM_PAINT: { result = DefWindowProcW(hwnd, msg, wparam, lparam); } break; case WM_ENTERSIZEMOVE: case WM_MOVE: case WM_MOVING: case WM_SIZE: case WM_SIZING: { P_W32_UpdateWindowFromSystem(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 = 0; P_WindowEventKind event_kind = P_WindowEventKind_None; if (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN) { event_kind = P_WindowEventKind_ButtonDown; is_repeat = (lparam & 0x40000000) != 0; } else if (msg == WM_KEYUP || msg == WM_SYSKEYUP) { event_kind = P_WindowEventKind_ButtonUp; } P_Btn button = P_Btn_None; if (vk_code < countof(g->vk_btn_table)) { button = g->vk_btn_table[vk_code]; } P_W32_ProcessWindowEvent( window, (P_WindowEvent) { .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 (IsUtf16HighSurrogate(utf16_char)) { window->utf16_high_surrogate_last_input = utf16_char; } else if (IsUtf16LowSurrogate(utf16_char)) { u16 high = window->utf16_high_surrogate_last_input; u16 low = utf16_char; if (high) { u16 utf16_pair_bytes[2] = { high, low }; Utf16DecodeResult decoded = DecodeUtf16((String16) { .len = countof(utf16_pair_bytes), .text = utf16_pair_bytes }); if (decoded.advance16 == 2 && decoded.codepoint < U32Max) { 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') { P_W32_ProcessWindowEvent( window, (P_WindowEvent) { .kind = P_WindowEventKind_Text, .text_codepoint = codepoint } ); } } } 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) { SetCapture(hwnd); } P_WindowEventKind event_kind = is_release ? P_WindowEventKind_ButtonUp : P_WindowEventKind_ButtonDown; P_Btn button = 0; switch (msg) { case WM_LBUTTONUP: case WM_LBUTTONDOWN: button = P_Btn_M1; break; case WM_RBUTTONUP: case WM_RBUTTONDOWN: button = P_Btn_M2; break; case WM_MBUTTONUP: case WM_MBUTTONDOWN: button = P_Btn_M3; break; case WM_XBUTTONUP: case WM_XBUTTONDOWN: { u32 wparam_xbutton = GET_XBUTTON_WPARAM(wparam); if (wparam_xbutton == XBUTTON1) { button = P_Btn_M4; } else if (wparam_xbutton == XBUTTON2) { button = P_Btn_M5; } } break; } if (button) { P_W32_ProcessWindowEvent( window, (P_WindowEvent) { .kind = event_kind, .button = button } ); } } break; /* Mouse wheel */ case WM_MOUSEWHEEL: { int delta = GET_WHEEL_DELTA_WPARAM(wparam); i32 dir = delta >= 0 ? 1 : -1; P_Btn button = dir >= 0 ? P_Btn_MWheelUp : P_Btn_MWheelDown; for (i32 i = 0; i < (dir * delta); i += WHEEL_DELTA) { /* Send a button down & button up event simultaneously */ P_W32_ProcessWindowEvent(window, (P_WindowEvent) { .kind = P_WindowEventKind_ButtonDown, .button = button }); P_W32_ProcessWindowEvent(window, (P_WindowEvent) { .kind = P_WindowEventKind_ButtonUp, .button = button }); } } break; /* Mouse move */ case WM_MOUSEMOVE: { i32 x = GET_X_LPARAM(lparam); i32 y = GET_Y_LPARAM(lparam); P_W32_ProcessWindowEvent( window, (P_WindowEvent) { .kind = P_WindowEventKind_CursorMove, .cursor_position = VEC2(x, y) } ); } break; /* Raw mouse move */ case WM_INPUT: { TempArena scratch = BeginScratchNoConflict(); /* Read raw input buffer */ UINT buff_size; GetRawInputData((HRAWINPUT)lparam, RID_INPUT, 0, &buff_size, sizeof(RAWINPUTHEADER)); u8 *buff = PushStructs(scratch.arena, u8, buff_size); if (GetRawInputData((HRAWINPUT)lparam, RID_INPUT, buff, &buff_size, sizeof(RAWINPUTHEADER)) != buff_size) { P_LogErrorF("GetRawInputData did not return correct size"); break; } RAWINPUT raw = ZI; CopyBytes(&raw, buff, sizeof(RAWINPUT)); if (raw.header.dwType == RIM_TYPEMOUSE) { i32 x = raw.data.mouse.lLastX; i32 y = raw.data.mouse.lLastY; Vec2 delta = VEC2(x, y); P_W32_ProcessWindowEvent( window, (P_WindowEvent) { .kind = P_WindowEventKind_MouseMove, .mouse_delta = delta } ); } EndScratch(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; } //////////////////////////////// //~ Win32 Address P_W32_Address P_W32_Win32AddressFromPlatformAddress(P_Address addr) { P_W32_Address res = ZI; if (addr.family == P_AddressFamily_Ipv4) { res.family = AF_INET; res.size = sizeof(struct sockaddr_in); res.sin.sin_port = addr.portnb; res.sin.sin_family = res.family; CopyBytes(&res.sin.sin_addr, addr.ipnb, 4); } else { res.family = AF_INET6; res.sin6.sin6_port = addr.portnb; res.sin6.sin6_family = res.family; res.size = sizeof(struct sockaddr_in6); CopyBytes(&res.sin6.sin6_addr.s6_addr, addr.ipnb, 16); } return res; } /* If supplied address has ip INADDR_ANY (0), convert ip to localhost */ P_W32_Address P_W32_ConvertAnyaddrToLocalhost(P_W32_Address addr) { if (addr.family == AF_INET) { u8 *bytes = (u8 *)&addr.sin.sin_addr; b32 is_any = 1; for (u64 i = 0; i < 4; ++i) { if (bytes[i] != 0) { is_any = 0; break; } } if (is_any) { bytes[0] = 127; bytes[3] = 1; } } else if (addr.family == AF_INET6) { u8 *bytes = (u8 *)&addr.sin.sin_addr; b32 is_any = 1; for (u64 i = 0; i < 16; ++i) { if (bytes[i] != 0) { is_any = 0; break; } } if (is_any) { bytes[15] = 1; } } return addr; } P_Address P_W32_PlatformAddressFromWin32Address(P_W32_Address ws_addr) { P_Address res = ZI; if (ws_addr.family == AF_INET) { res.family = P_AddressFamily_Ipv4; res.portnb = ws_addr.sin.sin_port; CopyBytes(res.ipnb, &ws_addr.sin.sin_addr, 4); res.valid = 1; } else if (ws_addr.family == AF_INET6) { res.family = P_AddressFamily_Ipv6; res.portnb = ws_addr.sin6.sin6_port; CopyBytes(res.ipnb, &ws_addr.sin6.sin6_addr.s6_addr, 16); res.valid = 1; } return res; } //////////////////////////////// //~ Wait / wake void P_Wait(volatile void *addr, void *cmp, u32 size, i64 timeout_ns) { P_W32_Fiber *fiber = P_W32_FiberFromId(FiberId()); i16 parent_id = fiber->parent_id; if (parent_id != 0) { *fiber->yield_param = (P_W32_YieldParam) { .kind = P_W32_YieldKind_Wait, .wait = { .addr = addr, .cmp = cmp, .size = size, .timeout_ns = timeout_ns } }; P_W32_YieldFiber(fiber, P_W32_FiberFromId(parent_id)); } else { i32 timeout_ms = 0; if (timeout_ns > 10000000000000000ll) { timeout_ms = INFINITE; } else if (timeout_ns != 0) { timeout_ms = timeout_ns / 1000000; timeout_ms += (timeout_ms == 0) * SignF32(timeout_ns); } if (addr == 0) { Sleep(timeout_ms); } else { WaitOnAddress(addr, cmp, size, timeout_ms); } } } void P_Wake(void *addr, i32 count) { P_W32_WakeByAddress(addr, count); } //////////////////////////////// //~ Job void P_Run(i32 count, P_JobFunc *func, void *sig, P_Pool pool_kind, P_Priority priority, P_Counter *counter) { __prof; struct P_W32_SharedCtx *g = &P_W32_shared_ctx; if (count > 0) { if (counter) { P_CounterAdd(counter, count); } P_W32_Fiber *fiber = P_W32_FiberFromId(FiberId()); priority = ClampI32(priority, fiber->job_priority, P_Priority_Count - 1); /* A job cannot create a job with a higher priority than itself */ if (pool_kind == P_Pool_Inherit) { pool_kind = fiber->job_pool; } P_W32_JobPool *pool = &g->job_pools[pool_kind]; P_W32_JobQueue *queue = &pool->job_queues[priority]; P_W32_LockTicketMutex(&queue->lock); { P_W32_JobInfo *info = 0; if (queue->first_free) { info = queue->first_free; queue->first_free = info->next; } else { info = PushStructNoZero(queue->arena, P_W32_JobInfo); } ZeroStruct(info); info->count = count; info->func = func; info->sig = sig; info->counter = counter; if (queue->last) { queue->last->next = info; } else { queue->first = info; } queue->last = info; } P_W32_UnlockTicketMutex(&queue->lock); /* Wake workers */ { P_W32_LockTicketMutex(&pool->workers_wake_lock); { Atomic64FetchAdd(&pool->num_jobs_in_queue.v, count); if (count >= P_W32_WakeAllThreshold) { WakeByAddressAll(&pool->num_jobs_in_queue); } else { for (i32 i = 0; i < count; ++i) { WakeByAddressSingle(&pool->num_jobs_in_queue); } } } P_W32_UnlockTicketMutex(&pool->workers_wake_lock); } } } //////////////////////////////// //~ Time P_DateTime P_LocalTime(void) { SYSTEMTIME lt; GetLocalTime(<); return P_W32_DateTimeFromWin32SystemTime(lt); } i64 P_TimeNs(void) { struct P_W32_SharedCtx *g = &P_W32_shared_ctx; LARGE_INTEGER qpc; QueryPerformanceCounter(&qpc); i64 res = (qpc.QuadPart - g->timer_start_qpc) * g->ns_per_qpc; return res; } //////////////////////////////// //~ File system String P_GetWritePath(Arena *arena) { u16 *p = 0; /* TODO: cache this? */ HRESULT res = SHGetKnownFolderPath( &FOLDERID_LocalAppData, 0, 0, &p ); String path = ZI; if (res == S_OK) { path = P_W32_StringFromWin32Path(arena, p); } CoTaskMemFree(p); return path; } b32 P_IsFile(String path) { __prof; TempArena scratch = BeginScratchNoConflict(); wchar_t *path_wstr = WstrFromString(scratch.arena, path); DWORD attributes = GetFileAttributesW(path_wstr); EndScratch(scratch); return attributes != INVALID_FILE_ATTRIBUTES && !(attributes & FILE_ATTRIBUTE_DIRECTORY); } b32 P_IsDir(String path) { TempArena scratch = BeginScratchNoConflict(); wchar_t *path_wstr = WstrFromString(scratch.arena, path); DWORD attributes = GetFileAttributesW(path_wstr); EndScratch(scratch); return attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY); } void P_MkDir(String path) { __prof; TempArena scratch = BeginScratchNoConflict(); wchar_t *path_wstr = WstrFromString(scratch.arena, path); int err_code = SHCreateDirectory(0, path_wstr); 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) { String msg = StringFormat(scratch.arena, Lit("Failed to create directory \"%F\": %F"), FmtString(path), FmtString(err)); P_Panic(msg); } EndScratch(scratch); } P_File P_OpenFileRead(String path) { __prof; TempArena scratch = BeginScratchNoConflict(); P_File file = ZI; wchar_t *path_wstr = WstrFromString(scratch.arena, path); HANDLE handle = CreateFileW( path_wstr, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0 ); file.handle = (u64)handle; file.valid = handle != INVALID_HANDLE_VALUE; EndScratch(scratch); return file; } P_File P_OpenFileReadWait(String path) { __prof; TempArena scratch = BeginScratchNoConflict(); P_File file = ZI; wchar_t *path_wstr = WstrFromString(scratch.arena, path); i32 delay_ms = 1; HANDLE handle; while ((handle = CreateFileW(path_wstr, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)) == INVALID_HANDLE_VALUE) { if (GetLastError() == ERROR_SHARING_VIOLATION) { __profn("File share conflict delay"); Sleep(delay_ms); if (delay_ms < 1024) { delay_ms *= 2; } } else { break; } } file.handle = (u64)handle; file.valid = handle != INVALID_HANDLE_VALUE; EndScratch(scratch); return file; } P_File P_OpenFileWrite(String path) { __prof; TempArena scratch = BeginScratchNoConflict(); P_File file = ZI; wchar_t *path_wstr = WstrFromString(scratch.arena, path); HANDLE handle = CreateFileW( path_wstr, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0 ); file.handle = (u64)handle; file.valid = handle != INVALID_HANDLE_VALUE; EndScratch(scratch); return file; } P_File P_OpenFileAppend(String path) { __prof; TempArena scratch = BeginScratchNoConflict(); P_File file = ZI; wchar_t *path_wstr = WstrFromString(scratch.arena, path); HANDLE handle = CreateFileW( path_wstr, FILE_APPEND_DATA, FILE_SHARE_READ, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0 ); file.handle = (u64)handle; file.valid = handle != INVALID_HANDLE_VALUE; EndScratch(scratch); return file; } void P_CloseFIle(P_File file) { __prof; if (file.handle) { CloseHandle((HANDLE)file.handle); } } String P_ReadFile(Arena *arena, P_File file) { __prof; i64 size = 0; GetFileSizeEx((HANDLE)file.handle, (PLARGE_INTEGER)&size); String s = { .len = size, .text = 0 }; if (size > 0) { /* ReadFile returns non-zero on success */ /* TODO: error checking */ AlignArena(arena, 16); s.text = PushStructsNoZero(arena, u8, size); (UNUSED)ReadFile( (HANDLE)file.handle, s.text, (DWORD)s.len, 0, /* lpNumberOfBytesRead */ 0 ); } return s; } void P_WriteFile(P_File file, String data) { __prof; /* TODO: Check what the real data limit is and chunk sequentially based on * that (rather than failing) */ if (data.len >= 0x7FFF) { TempArena scratch = BeginScratchNoConflict(); P_Panic(StringFormat(scratch.arena, Lit("Tried to write too many bytes to disk (%F)"), FmtUint(data.len))); EndScratch(scratch); } /* WriteFile returns TRUE on success */ (UNUSED)WriteFile( (HANDLE)file.handle, data.text, (DWORD)data.len, 0, /* lpNumberOfBytesWritten */ 0 ); } u64 P_GetFileSize(P_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); } P_FileTime P_GetFileTime(P_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 (P_FileTime) { .created = P_W32_DateTimeFromWin32SystemTime(st_created), .accessed = P_W32_DateTimeFromWin32SystemTime(st_accessed), .modified = P_W32_DateTimeFromWin32SystemTime(st_modified) }; } else { return (P_FileTime) { 0 }; } } //////////////////////////////// //~ File map P_FileMap P_OpenFileMap(P_File file) { __prof; P_FileMap map = ZI; u64 size = P_GetFileSize(file); u8 *base_ptr = 0; HANDLE map_handle = 0; if (size > 0) { map_handle = CreateFileMappingW( (HANDLE)file.handle, 0, PAGE_READONLY, 0, 0, 0 ); if (map_handle != INVALID_HANDLE_VALUE) { base_ptr = MapViewOfFile( map_handle, FILE_MAP_READ, 0, 0, 0 ); if (base_ptr == 0) { /* Failed to create view */ CloseHandle(map_handle); map_handle = INVALID_HANDLE_VALUE; } } } if (map_handle == INVALID_HANDLE_VALUE) { size = 0; } map.handle = (u64)map_handle; map.mapped_memory = STRING(size, base_ptr); map.valid = map_handle != INVALID_HANDLE_VALUE && base_ptr != 0; return map; } void P_CloseFileMap(P_FileMap map) { if (map.mapped_memory.text) { UnmapViewOfFile(map.mapped_memory.text); } if (map.handle) { CloseHandle((HANDLE)map.handle); } } String P_GetFileMapData(P_FileMap map) { return map.mapped_memory; } //////////////////////////////// //~ Watch P_Watch *P_AllocWatch(String dir_path) { TempArena scratch = BeginScratchNoConflict(); struct P_W32_SharedCtx *g = &P_W32_shared_ctx; P_W32_Watch *w32_watch = 0; { P_Lock lock = P_LockE(&g->watches_mutex); { if (g->watches_first_free) { w32_watch = g->watches_first_free; g->watches_first_free = w32_watch->next_free; } else { w32_watch = PushStructNoZero(g->watches_arena, P_W32_Watch); } } P_Unlock(&lock); } ZeroStruct(w32_watch); wchar_t *dir_path_wstr = WstrFromString(scratch.arena, dir_path); w32_watch->dir_handle = CreateFileW( dir_path_wstr, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, 0 ); w32_watch->wake_handle = CreateEventW(0, 0, 0, 0); EndScratch(scratch); return (P_Watch *)w32_watch; } void P_ReleaseWatch(P_Watch *dw) { P_W32_Watch *w32_watch = (P_W32_Watch *)dw; struct P_W32_SharedCtx *g = &P_W32_shared_ctx; CloseHandle(w32_watch->dir_handle); CloseHandle(w32_watch->wake_handle); P_Lock lock = P_LockE(&g->watches_mutex); { w32_watch->next_free = g->watches_first_free; g->watches_first_free = w32_watch; } P_Unlock(&lock); } P_WatchInfoList P_ReadWatchWait(Arena *arena, P_Watch *dw) { __prof; P_W32_Watch *w32_watch = (P_W32_Watch *)dw; P_WatchInfoList list = ZI; DWORD filter = FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_CREATION; b32 done = 0; while (!done) { OVERLAPPED ov = ZI; ov.hEvent = CreateEventW(0, 0, 0, 0); Assert(ov.hEvent); BOOL success = ReadDirectoryChangesW(w32_watch->dir_handle, w32_watch->results_buff, countof(w32_watch->results_buff), 1, filter, 0, &ov, 0); (UNUSED)success; Assert(success); HANDLE handles[] = { ov.hEvent, w32_watch->wake_handle }; DWORD wait_res = WaitForMultipleObjects(2, handles, 0, INFINITE); if (wait_res == WAIT_OBJECT_0) { i64 offset = 0; while (!done) { FILE_NOTIFY_INFORMATION *res = (FILE_NOTIFY_INFORMATION *)(w32_watch->results_buff + offset); P_WatchInfo *info = PushStruct(arena, P_WatchInfo); if (list.last) { list.last->next = info; info->prev = list.last; } else { list.first = info; } list.last = info; ++list.count; String16 name16 = ZI; name16.text = res->FileName; name16.len = res->FileNameLength / sizeof(wchar_t); info->name = StringFromString16(arena, name16); for (u64 i = 0; i < info->name.len; ++i) { if (info->name.text[i] == '\\') { info->name.text[i] = '/'; } } switch (res->Action) { case FILE_ACTION_ADDED: { info->kind = P_WatchInfoKind_Added; } break; case FILE_ACTION_REMOVED: { info->kind = P_WatchInfoKind_Removed; } break; case FILE_ACTION_MODIFIED: { info->kind = P_WatchInfoKind_Modified; } break; case FILE_ACTION_RENAMED_OLD_NAME: { info->kind = P_WatchInfoKind_RenamedOld; } break; case FILE_ACTION_RENAMED_NEW_NAME: { info->kind = P_WatchInfoKind_RenamedNew; } break; default: { info->kind = P_WatchInfoKind_Unknown; } break; } if (res->NextEntryOffset == 0) { done = 1; } else { offset += res->NextEntryOffset; } } } else if (wait_res == WAIT_OBJECT_0 + 1) { ResetEvent(w32_watch->wake_handle); done = 1; } else { Assert(0); } } return list; } void P_WakeWatch(P_Watch *dw) { P_W32_Watch *w32_watch = (P_W32_Watch *)dw; SetEvent(w32_watch->wake_handle); } //////////////////////////////// //~ Window P_Window *P_AllocWindow(void) { __prof; return (P_Window *)P_W32_AllocWindow(); } void P_ReleaseWindow(P_Window *p_window) { __prof; P_W32_Window *window = (P_W32_Window *)p_window; P_W32_ReleaseWindow(window); } //- Window events P_WindowEventArray P_PopWindowEvents(Arena *arena, P_Window *p_window) { __prof; P_W32_Window *window = (P_W32_Window *)p_window; i32 event_arena_index = 0; { /* Swap event buffers */ P_Lock lock = P_LockE(&window->event_arena_swp_mutex); event_arena_index = window->current_event_arena_index; window->current_event_arena_index = 1 - window->current_event_arena_index; P_Unlock(&lock); } Arena *events_arena = window->event_arenas[event_arena_index]; P_WindowEventArray events = ZI; events.count = events_arena->pos / sizeof(P_WindowEvent); events.events = PushStructsNoZero(arena, P_WindowEvent, events.count); CopyBytes(events.events, ArenaBase(events_arena), events_arena->pos); ResetArena(events_arena); return events; } //- Window settings void P_UpdateWindowSettings(P_Window *p_window, P_WindowSettings *settings) { __prof; P_W32_Window *window = (P_W32_Window *)p_window; P_Lock lock = P_LockE(&window->settings_mutex); { P_W32_UpdateWindowFromSettings(window, settings); } P_Unlock(&lock); } /* FIXME: Lock settings mutex for these functions */ P_WindowSettings P_GetWindowSettings(P_Window *p_window) { P_W32_Window *window = (P_W32_Window *)p_window; return window->settings; } void P_ShowWindow(P_Window *p_window) { P_W32_Window *window = (P_W32_Window *)p_window; HWND hwnd = window->hwnd; P_Lock lock = P_LockE(&window->settings_mutex); { i32 show_cmd = SW_NORMAL; P_WindowSettings *settings = &window->settings; if (settings->flags & P_WindowSettingsFlag_Maximized) { show_cmd = SW_SHOWMAXIMIZED; } else if (settings->flags & P_WindowSettingsFlag_Minimized) { show_cmd = SW_MINIMIZE; } window->flags |= P_WindowFlag_Showing; ShowWindow(hwnd, show_cmd); BringWindowToTop(hwnd); } P_Unlock(&lock); } void P_SetWindowCursorPos(P_Window *p_window, Vec2 pos) { P_W32_Window *window = (P_W32_Window *)p_window; window->cursor_set_position = pos; window->cursor_set_flags |= P_W32_CursorFlag_Position; P_W32_WakeWindow(window); } void P_ShowWindowCursor(P_Window *p_window) { P_W32_Window *window = (P_W32_Window *)p_window; window->cursor_set_flags |= P_W32_CursorFlag_Show; P_W32_WakeWindow(window); } void P_HideWindowCursor(P_Window *p_window) { P_W32_Window *window = (P_W32_Window *)p_window; window->cursor_set_flags |= P_W32_CursorFlag_Hide; P_W32_WakeWindow(window); } void P_EnableWindoweCursorClip(P_Window *p_window, Rect bounds) { P_W32_Window *window = (P_W32_Window *)p_window; window->cursor_clip_bounds = bounds; window->cursor_set_flags |= P_W32_CursorFlag_EnableClip; P_W32_WakeWindow(window); } void P_DisableWindoweCursorClip(P_Window *p_window) { P_W32_Window *window = (P_W32_Window *)p_window; window->cursor_set_flags |= P_W32_CursorFlag_DisableClip; P_W32_WakeWindow(window); } void P_ToggleWindowTopmost(P_Window *p_window) { P_W32_Window *window = (P_W32_Window *)p_window; Atomic32FetchAdd(&window->topmost_toggles, 1); P_W32_WakeWindow(window); } //- Window info Vec2 P_GetWindowSize(P_Window *p_window) { P_W32_Window *window = (P_W32_Window *)p_window; return VEC2((f32)window->width, (f32)window->height); } Vec2 P_GetWindowMonitorSize(P_Window *p_window) { P_W32_Window *window = (P_W32_Window *)p_window; return VEC2((f32)window->monitor_width, (f32)window->monitor_height); } u64 P_GetInternalWindowHandle(P_Window *p_window) { P_W32_Window *window = (P_W32_Window *)p_window; return (u64)window->hwnd; } //////////////////////////////// //~ Address P_Address P_AddressFromIpPortCstr(char *ip_cstr, char *port_cstr) { P_Address res = ZI; struct addrinfo hints = ZI; hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; hints.ai_flags = AI_PASSIVE; struct addrinfo *ai_res = 0; i32 status = getaddrinfo(ip_cstr, port_cstr, &hints, &ai_res); if (status == 0) { while (ai_res) { if (ai_res->ai_family == AF_INET) { struct sockaddr_in *sockaddr = (struct sockaddr_in *)ai_res->ai_addr; res.valid = 1; res.family = P_AddressFamily_Ipv4; res.portnb = sockaddr->sin_port; StaticAssert(sizeof(sockaddr->sin_addr) == 4); CopyBytes(res.ipnb, (void *)&sockaddr->sin_addr, 4); break; } else if (ai_res->ai_family == AF_INET6) { /* TODO: Enable ipv6 */ #if 0 struct sockaddr_in6 *sockaddr = (struct sockaddr_in6 *)ai_res->ai_addr; res.valid = 1; res.family = P_AddressFamily_Ipv6; res.portnb = sockaddr->sin6_port; StaticAssert(sizeof(sockaddr->sin6_addr) == 16); CopyBytes(res.ipnb, (void *)&sockaddr->sin6_addr, 16); break; #endif } ai_res = ai_res->ai_next; } freeaddrinfo(ai_res); } return res; } P_Address P_AddressFromString(String str) { /* Parse string into ip & port */ u8 ip_buff[1024]; u8 port_buff[countof(ip_buff)]; char *ip_cstr = 0; char *port_cstr = 0; { u64 colon_count = 0; for (u64 i = 0; i < str.len; ++i) { u8 c = str.text[i]; if (c == ':') { ++colon_count; } } u64 ip_len = 0; u64 port_len = 0; u64 parse_len = MinU64(MinU64(str.len, countof(ip_buff) - 1), countof(port_buff) - 1); if (colon_count > 1 && str.text[0] == '[') { /* Parse ipv6 with port */ b32 parse_addr = 1; for (u64 i = 1; i < parse_len; ++i) { u8 c = str.text[i]; if (parse_addr) { if (c == ']') { parse_addr = 0; } else { ip_buff[ip_len] = c; ++ip_len; } } else if (c != ':') { port_buff[port_len] = c; ++port_len; } } } else if (colon_count == 1) { /* Parse address with port */ b32 parse_addr = 1; for (u64 i = 0; i < parse_len; ++i) { u8 c = str.text[i]; if (parse_addr) { if (c == ':') { parse_addr = 0; } else { ip_buff[ip_len] = c; ++ip_len; } } else { port_buff[port_len] = c; ++port_len; } } } else { /* CopyStruct address without port */ ip_len = MinU64(str.len, countof(ip_buff) - 1); CopyBytes(ip_buff, str.text, ip_len); } if (ip_len > 0) { ip_buff[ip_len] = 0; ip_cstr = (char *)ip_buff; } if (port_len > 0) { port_buff[port_len] = 0; port_cstr = (char *)port_buff; } } P_Address res = P_AddressFromIpPortCstr(ip_cstr, port_cstr); return res; } P_Address P_AddressFromPort(u16 port) { u8 port_buff[128]; char *port_cstr = 0; { u8 port_buff_reverse[countof(port_buff)]; u64 port_len = 0; while (port > 0 && port_len < (countof(port_buff) - 1)) { u8 digit = port % 10; port /= 10; port_buff_reverse[port_len] = '0' + digit; ++port_len; } for (u64 i = 0; i < port_len; ++i) { u64 j = port_len - 1 - i; port_buff[i] = port_buff_reverse[j]; } if (port_len > 0) { port_buff[port_len] = 0; port_cstr = (char *)port_buff; } } P_Address res = P_AddressFromIpPortCstr(0, port_cstr); return res; } String P_StringFromAddress(Arena *arena, P_Address address) { String res = ZI; if (address.family == P_AddressFamily_Ipv6) { /* TODO */ } else { u8 ip[4]; for (u32 i = 0; i < 4; ++i) { ip[i] = ntohs(address.ipnb[i]); } u16 port = ntohs(address.portnb); res = StringFormat(arena, Lit("%F.%F.%F.%F:%F"), FmtUint(ip[0]), FmtUint(ip[1]), FmtUint(ip[2]), FmtUint(ip[3]), FmtUint(port)); } return res; } b32 P_AddressIsEqual(P_Address a, P_Address b) { return EqStruct(&a, &b); } //////////////////////////////// //~ Sock P_Sock *P_AllocSock(u16 listen_port, u64 sndbuf_size, u64 rcvbuf_size) { P_W32_SharedCtx *g = &P_W32_shared_ctx; P_W32_Sock *ws = 0; { P_Lock lock = P_LockE(&g->socks_mutex); if (g->first_free_sock) { ws = g->first_free_sock; g->first_free_sock = ws->next_free; } else { ws = PushStructNoZero(g->socks_arena, P_W32_Sock); } P_Unlock(&lock); } ZeroStruct(ws); P_Address addr = P_AddressFromPort(listen_port); P_W32_Address bind_address = P_W32_Win32AddressFromPlatformAddress(addr); ws->sock = socket(bind_address.family, SOCK_DGRAM, IPPROTO_UDP); { i32 sb = sndbuf_size; i32 rb = rcvbuf_size; u32 imode = 1; setsockopt(ws->sock, SOL_SOCKET, SO_SNDBUF, (char *)&sb, sizeof(sb)); setsockopt(ws->sock, SOL_SOCKET, SO_RCVBUF, (char *)&rb, sizeof(rb)); ioctlsocket(ws->sock, FIONBIO, (unsigned long *)&imode); } bind(ws->sock, &bind_address.sa, bind_address.size); return (P_Sock *)ws; } void P_ReleaseSock(P_Sock *sock) { P_W32_SharedCtx *g = &P_W32_shared_ctx; P_W32_Sock *ws = (P_W32_Sock *)sock; closesocket(ws->sock); P_Lock lock = P_LockE(&g->socks_mutex); { ws->next_free = g->first_free_sock; g->first_free_sock = ws; } P_Unlock(&lock); } P_SockReadResult P_ReadSock(Arena *arena, P_Sock *sock) { P_W32_Sock *ws = (P_W32_Sock *)sock; u64 read_buff_size = Kibi(64); String read_buff = ZI; read_buff.len = read_buff_size; read_buff.text = PushStructsNoZero(arena, u8, read_buff_size); P_SockReadResult res = ZI; P_W32_Address ws_addr = ZI; ws_addr.size = sizeof(ws_addr.sas); i32 size = recvfrom(ws->sock, (char *)read_buff.text, read_buff.len, 0, &ws_addr.sa, &ws_addr.size); ws_addr.family = ws_addr.sin.sin_family; res.address = P_W32_PlatformAddressFromWin32Address(ws_addr); if (size >= 0) { AddGstat(GSTAT_SOCK_BYTES_RECEIVED, size); res.data.text = read_buff.text; res.data.len = size; res.valid = 1; /* PopStruct arena back to end of msg */ PopTo(arena, arena->pos - read_buff_size + size); } else { #if RtcIsEnabled i32 err = WSAGetLastError(); if (err != WSAEWOULDBLOCK && err != WSAETIMEDOUT && err != WSAECONNRESET) { Assert(0); } #endif } return res; } void P_WriteSock(P_Sock *sock, P_Address address, String data) { P_W32_Sock *ws = (P_W32_Sock *)sock; P_W32_Address ws_addr = P_W32_Win32AddressFromPlatformAddress(address); i32 size = sendto(ws->sock, (char *)data.text, data.len, 0, &ws_addr.sa, ws_addr.size); if (size > 0) { AddGstat(GSTAT_SOCK_BYTES_SENT, size); } #if RtcIsEnabled if (size != (i32)data.len) { i32 err = WSAGetLastError(); (UNUSED)err; Assert(0); } #endif } //////////////////////////////// //~ Util void P_MessageBox(P_MessageBoxKind kind, String message) { TempArena scratch = BeginScratchNoConflict(); wchar_t *message_wstr = WstrFromString(scratch.arena, message); const wchar_t *title = L""; UINT mbox_type = MB_SETFOREGROUND; switch (kind) { case P_MessageBoxKind_Ok: { mbox_type |= MB_ICONINFORMATION; } break; case P_MessageBoxKind_Warning: { title = L"Warning"; mbox_type |= MB_ICONWARNING; } break; case P_MessageBoxKind_Error: { title = L"Error"; mbox_type |= MB_ICONERROR; } break; case P_MessageBoxKind_Fatal: { title = L"Fatal error"; mbox_type |= MB_ICONSTOP; } break; } P_LogDebugF("Showing message box kind %F with text \"%F\"", FmtSint(kind), FmtString(message)); MessageBoxExW(0, message_wstr, title, mbox_type, 0); EndScratch(scratch); } void P_SetClipboardText(String str) { if (OpenClipboard(0)) { TempArena scratch = BeginScratchNoConflict(); String16 str16 = String16FromString(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); CopyBytes(dest_wstr, str16.text, str16_size_bytes); dest_wstr[str16.len] = 0; GlobalUnlock(handle); SetClipboardData(CF_UNICODETEXT, handle); } CloseClipboard(); EndScratch(scratch); } } String P_GetClipboardText(Arena *arena) { String res = ZI; if (IsClipboardFormatAvailable(CF_UNICODETEXT) && OpenClipboard(0)) { HANDLE handle = GetClipboardData(CF_UNICODETEXT); if (handle) { u16 *src_wstr = (u16 *)GlobalLock(handle); res = StringFromString16(arena, String16FromWstrNoLimit(src_wstr)); GlobalUnlock(handle); } CloseClipboard(); } return res; } u32 P_GetLogicalProcessorCount(void) { return GetActiveProcessorCount(ALL_PROCESSOR_GROUPS); } u32 P_GetThreadId(void) { return GetCurrentThreadId(); } i64 P_GetCurrentSchedulerPeriodNs(void) { P_W32_SharedCtx *g = &P_W32_shared_ctx; return Atomic64Fetch(&g->current_scheduler_cycle_period_ns.v); } //////////////////////////////// //~ Sleep void P_SleepPrecise(i64 sleep_time_ns) { __prof; i64 big_sleep = P_GetCurrentSchedulerPeriodNs(); i64 tolerance = (f64)big_sleep * 0.5; //i64 tolerance = 1000000000; i64 now_ns = P_TimeNs(); i64 target_ns = now_ns + sleep_time_ns; /* Sleep */ while (now_ns < target_ns - big_sleep - tolerance) { __profn("Sleep part"); P_Wait(0, 0, 0, big_sleep); now_ns = P_TimeNs(); } /* Spin */ { __profn("Sleep spin"); while (now_ns < target_ns) { IxPause(); now_ns = P_TimeNs(); } } } void P_SleepFrame(i64 last_frame_time_ns, i64 target_dt_ns) { if (last_frame_time_ns != 0 && target_dt_ns > 0) { i64 now_ns = P_TimeNs(); i64 last_frame_dt_ns = now_ns - last_frame_time_ns; i64 sleep_time_ns = target_dt_ns - last_frame_dt_ns; if (sleep_time_ns > 0) { P_SleepPrecise(sleep_time_ns); } } } //////////////////////////////// //~ Exit void P_OnExit(P_ExitFunc *func) { P_W32_SharedCtx *g = &P_W32_shared_ctx; i32 index = Atomic32FetchAdd(&g->num_exit_funcs, 1); if (index >= P_W32_MaxOnExitFuncs) { P_Panic(Lit("Maximum on exit functions registered")); } g->exit_funcs[index] = func; } void P_Exit(void) { P_W32_SharedCtx *g = &P_W32_shared_ctx; SetEvent(g->exit_begin_event); } void P_Panic(String msg) { P_W32_SharedCtx *g = &P_W32_shared_ctx; if (Atomic32FetchTestSet(&g->panicking, 0, 1) == 0) { log_panic(msg); wchar_t *wstr = g->panic_wstr; u64 WstrLen = 0; wchar_t prefix[] = L"A fatal error has occured and the application needs to exit:\n\n"; CopyBytes(wstr, prefix, MinU64(countof(g->panic_wstr), (countof(prefix) << 1))); WstrLen += countof(prefix) - 1; /* Perform manual string encode to avoid any implicit memory * allocation (in case allocation is unreliable) */ String str8 = msg; u64 pos8 = 0; while (pos8 < str8.len) { String str8_remaining = { .len = (str8.len - pos8), .text = str8.text + pos8 }; Utf8DecodeResult decoded = DecodeUtf8(str8_remaining); Utf16EncodeResult encoded = EncodeUtf16(decoded.codepoint); u64 wstr_new_len = WstrLen + encoded.count16; if (wstr_new_len < (countof(g->panic_wstr) - 1)) { u16 *dest = wstr + WstrLen; CopyBytes(dest, encoded.chars16, (encoded.count16 << 1)); WstrLen = wstr_new_len; pos8 += decoded.advance8; } else { break; } } wstr[WstrLen] = 0; #if RtcIsEnabled MessageBoxExW(0, wstr, L"Fatal error", MB_ICONSTOP | MB_SETFOREGROUND | MB_TOPMOST, 0); Assert(0); #endif SetEvent(g->panic_event); /* Wait for process termination */ if (GetCurrentThreadId() != g->main_thread_id) { Sleep(INFINITE); } } } //////////////////////////////// //~ Win32 entry point void P_W32_InitBtnTable(void) { P_W32_SharedCtx *g = &P_W32_shared_ctx; ZeroArray(g->vk_btn_table); for (u32 i = 'A', j = P_Btn_A; i <= 'Z'; ++i, ++j) { g->vk_btn_table[i] = (P_Btn)j; } for (u32 i = '0', j = P_Btn_0; i <= '9'; ++i, ++j) { g->vk_btn_table[i] = (P_Btn)j; } for (u32 i = VK_F1, j = P_Btn_F1; i <= VK_F24; ++i, ++j) { g->vk_btn_table[i] = (P_Btn)j; } g->vk_btn_table[VK_ESCAPE] = P_Btn_ESC; g->vk_btn_table[VK_OEM_3] = P_Btn_GraveAccent; g->vk_btn_table[VK_OEM_MINUS] = P_Btn_Minus; g->vk_btn_table[VK_OEM_PLUS] = P_Btn_Equal; g->vk_btn_table[VK_BACK] = P_Btn_Backspace; g->vk_btn_table[VK_TAB] = P_Btn_Tab; g->vk_btn_table[VK_SPACE] = P_Btn_Space; g->vk_btn_table[VK_RETURN] = P_Btn_Enter; g->vk_btn_table[VK_CONTROL] = P_Btn_Ctrl; g->vk_btn_table[VK_SHIFT] = P_Btn_Shift; g->vk_btn_table[VK_MENU] = P_Btn_Alt; g->vk_btn_table[VK_UP] = P_Btn_Up; g->vk_btn_table[VK_LEFT] = P_Btn_Left; g->vk_btn_table[VK_DOWN] = P_Btn_Down; g->vk_btn_table[VK_RIGHT] = P_Btn_Right; g->vk_btn_table[VK_DELETE] = P_Btn_Delete; g->vk_btn_table[VK_PRIOR] = P_Btn_PageUp; g->vk_btn_table[VK_NEXT] = P_Btn_PageDown; g->vk_btn_table[VK_HOME] = P_Btn_Home; g->vk_btn_table[VK_END] = P_Btn_End; g->vk_btn_table[VK_OEM_2] = P_Btn_ForwardSlash; g->vk_btn_table[VK_OEM_PERIOD] = P_Btn_Period; g->vk_btn_table[VK_OEM_COMMA] = P_Btn_Comma; g->vk_btn_table[VK_OEM_7] = P_Btn_Quote; g->vk_btn_table[VK_OEM_4] = P_Btn_LeftBracket; g->vk_btn_table[VK_OEM_6] = P_Btn_RightBracket; g->vk_btn_table[VK_INSERT] = P_Btn_Insert; g->vk_btn_table[VK_OEM_1] = P_Btn_Semicolon; } P_JobDef(P_W32_AppStartupJob, _) { (UNUSED)_; P_W32_SharedCtx *g = &P_W32_shared_ctx; TempArena scratch = BeginScratchNoConflict(); { String cmdline_args = StringFromWstr(scratch.arena, g->cmdline_args_wstr, countof(g->cmdline_args_wstr)); P_AppStartup(cmdline_args); SetEvent(g->startup_end_event); } EndScratch(scratch); } P_JobDef(P_W32_AppShutdownJob, _) { __prof; P_W32_SharedCtx *g = &P_W32_shared_ctx; (UNUSED)_; i32 num_funcs = Atomic32Fetch(&g->num_exit_funcs); for (i32 i = num_funcs - 1; i >= 0; --i) { P_ExitFunc *func = g->exit_funcs[i]; func(); } SetEvent(g->exit_end_event); } 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; __profthread("Main thread", PROF_THREAD_GROUP_MAIN); P_W32_SharedCtx *g = &P_W32_shared_ctx; #if ProfilingIsEnabled /* Start profiler */ { __profn("Launch profiler"); STARTUPINFO si = ZI; si.cb = sizeof(si); PROCESS_INFORMATION pi = ZI; wchar_t cmd[sizeof(ProfilingIsEnabled_CMD_WSTR)] = ZI; CopyBytes(cmd, ProfilingIsEnabled_CMD_WSTR, sizeof(ProfilingIsEnabled_CMD_WSTR)); DeleteFileW(ProfilingIsEnabled_FILE_WSTR); b32 success = CreateProcessW(0, cmd, 0, 0, 0, DETACHED_PROCESS, 0, 0, &si, &pi); if (!success) { MessageBoxExW(0, L"Failed to launch profiler using command '" ProfilingIsEnabled_CMD_WSTR L"'.", L"Error", MB_ICONSTOP | MB_SETFOREGROUND | MB_TOPMOST, 0); } } /* Set internal profiler thread affinities */ { __profn("Set profiler thread affinities"); wchar_t *prefix_name_wstr = PROFILER_THREAD_PREFIX_WSTR; u64 prefix_name_wstr_len = ((i32)sizeof(PROFILER_THREAD_PREFIX_WSTR) >> 1) - 1; if (prefix_name_wstr_len > 0 && PROFILER_THREAD_AFFINITY_MASK != 0) { DWORD proc_id = GetCurrentProcessId(); HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); if (snapshot != INVALID_HANDLE_VALUE) { THREADENTRY32 te = ZI; te.dwSize = sizeof(THREADENTRY32); if (Thread32First(snapshot, &te)) { do { if (te.th32OwnerProcessID == proc_id) { i32 thread_id = te.th32ThreadID; HANDLE thread = OpenThread(THREAD_ALL_ACCESS, FALSE, thread_id); if (thread) { wchar_t *thread_name_wstr = 0; HRESULT hr = GetThreadDescription(thread, &thread_name_wstr); if (SUCCEEDED(hr)) { u64 thread_name_len = WstrLenNoLimit(thread_name_wstr); if (thread_name_len >= prefix_name_wstr_len && EqBytes(thread_name_wstr, prefix_name_wstr, prefix_name_wstr_len)) { __profn("Set profiler thread affinity"); b32 success = SetThreadAffinityMask(thread, PROFILER_THREAD_AFFINITY_MASK) != 0; { /* Retry until external tools can set correct process affinity */ i32 delay_ms = 16; while (!success && delay_ms <= 1024) { __profn("Profiler thread affinity retry"); Sleep(delay_ms); success = SetThreadAffinityMask(thread, PROFILER_THREAD_AFFINITY_MASK) != 0; delay_ms *= 2; } } Assert(success); (UNUSED)success; } } CloseHandle(thread); } } } while (Thread32Next(snapshot, &te)); } } CloseHandle(snapshot); } } #endif /* Set up exit events */ g->panic_event = CreateEventW(0, 1, 0, 0); g->startup_end_event = CreateEventW(0, 1, 0, 0); g->exit_begin_event = CreateEventW(0, 1, 0, 0); g->exit_end_event = CreateEventW(0, 1, 0, 0); /* Init timer */ { LARGE_INTEGER qpf; QueryPerformanceFrequency(&qpf); g->ns_per_qpc = 1000000000 / qpf.QuadPart; } { LARGE_INTEGER qpc; QueryPerformanceCounter(&qpc); g->timer_start_qpc = qpc.QuadPart; } /* Init fibers */ g->num_fibers = 1; /* Fiber at index 0 always nil */ g->fiber_names_arena = AllocArena(Gibi(64)); /* Init wait lists */ g->wait_lists_arena = AllocArena(Gibi(64)); /* Convert main thread to fiber */ P_W32_AllocFiber(0); /* Init job pools */ for (P_Pool pool_kind = 0; pool_kind < (i32)countof(g->job_pools); ++pool_kind) { P_W32_JobPool *pool = &g->job_pools[pool_kind]; /* Init queues */ for (P_Priority priority = 0; priority < (i32)countof(pool->job_queues); ++priority) { P_W32_JobQueue *queue = &pool->job_queues[priority]; queue->arena = AllocArena(Gibi(64)); } } u64 cmdline_len = WstrLen(cmdline_wstr, countof(g->cmdline_args_wstr) - 1); CopyBytes(g->cmdline_args_wstr, cmdline_wstr, cmdline_len * sizeof(*cmdline_wstr)); g->cmdline_args_wstr[cmdline_len] = 0; g->main_thread_id = GetCurrentThreadId(); SetThreadDescription(GetCurrentThread(), L"Main thread"); /* Query system info */ GetSystemInfo(&g->info); /* Initialize vk table */ P_W32_InitBtnTable(); /* Create window class */ { /* Register the window class */ WNDCLASSEXW *wc = &g->window_class; wc->cbSize = sizeof(WNDCLASSEX); wc->lpszClassName = P_W32_WindowClassName; wc->hCursor = LoadCursor(0, IDC_ARROW); wc->style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; //wc->hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); wc->lpfnWndProc = P_W32_Win32WindowProc; wc->hInstance = instance; /* Use first icon resource as window icon (same as explorer) */ wchar_t path[4096] = ZI; GetModuleFileNameW(instance, path, countof(path)); ExtractIconExW(path, 0, &wc->hIcon, &wc->hIconSm, 1); if (!RegisterClassExW(wc)) { P_Panic(Lit("Failed to register window class")); } } /* Register raw input */ if (!Atomic32Fetch(&g->panicking)) { 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 */ }; b32 success = RegisterRawInputDevices(&rid, 1, sizeof(rid)); Assert(success); (UNUSED)success; } /* Init threads pool */ g->threads_arena = AllocArena(Gibi(64)); /* Init watches pool */ g->watches_arena = AllocArena(Gibi(64)); /* Init windows pool */ g->windows_arena = AllocArena(Gibi(64)); /* Init winsock */ WSAStartup(MAKEWORD(2, 2), &g->wsa_data); g->socks_arena = AllocArena(Gibi(64)); /* Start job scheduler */ Atomic64FetchSet(&g->current_scheduler_cycle_period_ns.v, P_W32_DefaultSchedulerPeriodNs); P_W32_Thread *scheduler_thread = P_W32_AllocThread(P_W32_JobSchedulerEntryFunc, 0, Lit("Scheduler thread"), PROF_THREAD_GROUP_SCHEDULER); //- Start job workers /* TODO: Heuristic worker counts & affinities */ { __profn("Start job workers"); for (P_Pool pool_kind = 0; pool_kind < (i32)countof(g->job_pools); ++pool_kind) { P_W32_JobPool *pool = &g->job_pools[pool_kind]; String name_fmt = ZI; i32 prof_group = PROF_THREAD_GROUP_FIBERS - Mebi(pool_kind); switch (pool_kind) { default: Assert(0); break; case P_Pool_Sim: { name_fmt = Lit("Sim worker #%F"); pool->num_worker_threads = 4; pool->thread_affinity_mask = 0x000000000000000Full; pool->thread_priority = THREAD_PRIORITY_TIME_CRITICAL; } break; case P_Pool_User: { name_fmt = Lit("User worker #%F"); pool->num_worker_threads = 4; pool->thread_affinity_mask = 0x00000000000000F0ull; pool->thread_priority = THREAD_PRIORITY_TIME_CRITICAL; } break; case P_Pool_Audio: { name_fmt = Lit("Audio worker #%F"); pool->num_worker_threads = 2; pool->thread_affinity_mask = 0x0000000000000300ull; pool->thread_priority = THREAD_PRIORITY_TIME_CRITICAL; pool->thread_is_audio = 1; } break; case P_Pool_Background: { name_fmt = Lit("Background worker #%F"); pool->num_worker_threads = 2; pool->thread_affinity_mask = 0x0000000000000C00ull; } break; case P_Pool_Floating: { name_fmt = Lit("Floating worker #%F"); pool->num_worker_threads = 8; pool->thread_affinity_mask = 0x0000000000000FFFull; } break; } pool->worker_threads_arena = AllocArena(Gibi(64)); pool->worker_threads = PushStructs(pool->worker_threads_arena, P_W32_Thread *, pool->num_worker_threads); pool->worker_contexts = PushStructs(pool->worker_threads_arena, P_W32_WorkerCtx, pool->num_worker_threads); for (i32 i = 0; i < pool->num_worker_threads; ++i) { P_W32_WorkerCtx *ctx = &pool->worker_contexts[i]; ctx->pool_kind = pool_kind; ctx->id = i; String name = StringFormat(pool->worker_threads_arena, name_fmt, FmtSint(i)); pool->worker_threads[i] = P_W32_AllocThread(P_W32_JobWorkerEntryFunc, ctx, name, prof_group + i); } } } //- App startup /* Run app start job */ if (!Atomic32Fetch(&g->panicking)) { P_Run(1, P_W32_AppStartupJob, 0, P_Pool_Floating, P_Priority_High, 0); } /* Wait for startup end or panic */ if (!Atomic32Fetch(&g->panicking)) { HANDLE handles[] = { g->startup_end_event, g->panic_event }; WaitForMultipleObjects(countof(handles), handles, 0, INFINITE); } /* Wait for exit start or panic */ if (!Atomic32Fetch(&g->panicking)) { HANDLE handles[] = { g->exit_begin_event, g->panic_event }; WaitForMultipleObjects(countof(handles), handles, 0, INFINITE); } //- App shutdown /* Run exit callbacks job */ if (!Atomic32Fetch(&g->panicking)) { P_Run(1, P_W32_AppShutdownJob, 0, P_Pool_Floating, P_Priority_High, 0); } /* Wait for exit end or panic */ if (!Atomic32Fetch(&g->panicking)) { HANDLE handles[] = { g->exit_end_event, g->panic_event }; WaitForMultipleObjects(countof(handles), handles, 0, INFINITE); } /* Signal shutdown */ if (!Atomic32Fetch(&g->panicking)) { Atomic32FetchSet(&g->shutdown, 1); for (P_Pool pool_kind = 0; pool_kind < (i32)countof(g->job_pools); ++pool_kind) { P_W32_JobPool *pool = &g->job_pools[pool_kind]; P_W32_LockTicketMutex(&pool->workers_wake_lock); { Atomic32FetchSet(&pool->workers_shutdown.v, 1); Atomic64FetchSet(&pool->num_jobs_in_queue.v, -100000); WakeByAddressAll(&pool->num_jobs_in_queue); } P_W32_UnlockTicketMutex(&pool->workers_wake_lock); } } /* Wait on worker threads */ if (!Atomic32Fetch(&g->panicking)) { for (P_Pool pool_kind = 0; pool_kind < (i32)countof(g->job_pools); ++pool_kind) { P_W32_JobPool *pool = &g->job_pools[pool_kind]; for (i32 i = 0; i < pool->num_worker_threads; ++i) { P_W32_Thread *worker_thread = pool->worker_threads[i]; P_W32_WaitReleaseThread(worker_thread); } } } /* Wait on scheduler thread */ if (!Atomic32Fetch(&g->panicking)) { P_W32_WaitReleaseThread(scheduler_thread); } /* Find any dangling threads that haven't exited gracefully by now */ if (!Atomic32Fetch(&g->panicking)) { P_Lock lock = P_LockS(&g->threads_mutex); if (g->first_thread) { TempArena scratch = BeginScratchNoConflict(); u64 num_dangling_threads = 0; String threads_msg = ZI; threads_msg.text = PushDry(scratch.arena, u8); for (P_W32_Thread *t = g->first_thread; t; t = t->next) { String name = StringFromCstr(t->thread_name_cstr, countof(t->thread_name_cstr)); threads_msg.len += StringFormat(scratch.arena, Lit(" \"%F\"\n"), FmtString(name)).len; ++num_dangling_threads; } threads_msg = StringFormat(scratch.arena, Lit("%F dangling thread(s):\n%F"), FmtUint(num_dangling_threads), FmtString(threads_msg)); P_Panic(threads_msg); EndScratch(scratch); } P_Unlock(&lock); } /* Exit */ i32 exit_code = 0; if (Atomic32Fetch(&g->panicking)) { WaitForSingleObject(g->panic_event, INFINITE); MessageBoxExW(0, g->panic_wstr, L"Fatal error", MB_ICONSTOP | MB_SETFOREGROUND | MB_TOPMOST, 0); exit_code = 1; } return exit_code; } //- CRT stub #if !CrtlibIsEnabled #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 /* !CrtlibIsEnabled */