Added synchronization primitives and basic thread API for Win32
This commit is contained in:
parent
d42bf63d41
commit
9a75f4a2f1
@ -5,8 +5,8 @@ VERSION :: "0.1a";
|
|||||||
LIB_BASE_NAME :: "musa-lib";
|
LIB_BASE_NAME :: "musa-lib";
|
||||||
EXE_BASE_NAME :: "musa";
|
EXE_BASE_NAME :: "musa";
|
||||||
|
|
||||||
LIB_SOURCE_FILENAMES :: string.["unity_build_lib.cpp"];
|
LIB_SOURCE_FILENAMES :: string.["lib_main.cpp"];
|
||||||
EXE_SOURCE_FILENAMES :: string.["unity_build_exe.cpp"];
|
EXE_SOURCE_FILENAMES :: string.["exe_main.cpp"];
|
||||||
|
|
||||||
INCLUDE_DIRS :: string.[
|
INCLUDE_DIRS :: string.[
|
||||||
"src",
|
"src",
|
||||||
@ -85,7 +85,7 @@ build_cpp_lib :: (compile_debug: bool) -> bool {
|
|||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
META_GENERATED_HEADER_FILE_PATH :: "src/include/meta_generated.h";
|
META_GENERATED_HEADER_FILE_PATH :: "lib/meta_generated.h";
|
||||||
|
|
||||||
cpu_target: CPU_Tag = .X64;
|
cpu_target: CPU_Tag = .X64;
|
||||||
os_target: Operating_System_Tag = .WINDOWS;
|
os_target: Operating_System_Tag = .WINDOWS;
|
||||||
|
|||||||
@ -1,10 +1,4 @@
|
|||||||
#include "unity_build_lib.cpp"
|
#include "lib_main.cpp"
|
||||||
#include "Base_Entry_Point.cpp"
|
|
||||||
// #include "imgui-docking.cpp"
|
|
||||||
#include "src/OS_Win32.cpp" // should be in lib/
|
|
||||||
// #include "src/OS_Linux.cpp" // #TODO: Future.
|
|
||||||
// #include "src/OS_MacOS.cpp" // #TODO: Future.
|
|
||||||
|
|
||||||
|
|
||||||
#if OS_WINDOWS
|
#if OS_WINDOWS
|
||||||
#if BUILD_CONSOLE_INTERFACE
|
#if BUILD_CONSOLE_INTERFACE
|
||||||
@ -1,7 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "meta_generated.h"
|
|
||||||
|
|
||||||
#define LANG_CPP 1
|
#define LANG_CPP 1
|
||||||
#define BUILD_CONSOLE_INTERFACE BUILD_DEBUG
|
#define BUILD_CONSOLE_INTERFACE BUILD_DEBUG
|
||||||
|
|
||||||
@ -18,6 +16,7 @@
|
|||||||
#if OS_WINDOWS
|
#if OS_WINDOWS
|
||||||
#define WIN32_LEAN_AND_MEAN
|
#define WIN32_LEAN_AND_MEAN
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
|
#undef ERROR // why...
|
||||||
#else
|
#else
|
||||||
#error "This configuration is NOT supported. Only Windows with MSVC is currently supported."
|
#error "This configuration is NOT supported. Only Windows with MSVC is currently supported."
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -72,9 +72,8 @@ thread_static Thread_Context* thread_local_context;
|
|||||||
// Let's do arenas first.
|
// Let's do arenas first.
|
||||||
|
|
||||||
|
|
||||||
void init_thread_context(Thread_Context* tctx) {
|
force_inline void set_thread_context (Thread_Context* new_context) {
|
||||||
// Should be Arena-bootstrapped with Temp Arena maybe?
|
thread_local_context = new_context;
|
||||||
// #TODO - call from entry point.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Thread_Context* get_thread_context() {
|
Thread_Context* get_thread_context() {
|
||||||
|
|||||||
56
lib/Base/Threads.cpp
Normal file
56
lib/Base/Threads.cpp
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// #thread_primitives
|
||||||
|
#if OS_WINDOWS
|
||||||
|
struct Condition_Variable {
|
||||||
|
CONDITION_VARIABLE condition_variable;
|
||||||
|
};
|
||||||
|
struct Semaphore {
|
||||||
|
HANDLE event;
|
||||||
|
};
|
||||||
|
struct Mutex {
|
||||||
|
CRITICAL_SECTION csection;
|
||||||
|
};
|
||||||
|
struct OS_Thread {
|
||||||
|
HANDLE windows_thread;
|
||||||
|
s32 windows_thread_id;
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
#define POSIX_THREADS OS_LINUX || OS_MACOS || OS_IOS || OS_ANDROID
|
||||||
|
#if OS_MACOS
|
||||||
|
struct Semaphore {
|
||||||
|
task_t owner;
|
||||||
|
semaphore_t event = 0;
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
#if OS_LINUX || OS_ANDROID
|
||||||
|
struct Semaphore {
|
||||||
|
sem_t semaphore;
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
#if OS_IS_UNIX // #posix threads
|
||||||
|
struct OS_Thread {
|
||||||
|
pthread_t thread_handle;
|
||||||
|
Semaphore is_alive;
|
||||||
|
Semaphore suspended;
|
||||||
|
b32 is_done;
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct Thread;
|
||||||
|
typedef s64 (*Thread_Proc)(Thread* thread);
|
||||||
|
struct Thread {
|
||||||
|
Thread_Context* context;
|
||||||
|
s64 index;
|
||||||
|
Thread_Proc proc;
|
||||||
|
void* data;
|
||||||
|
|
||||||
|
OS_Thread os_thread;
|
||||||
|
};
|
||||||
|
|
||||||
|
global u32 next_thread_index = 1;
|
||||||
|
|
||||||
|
// internal Thread thread_start
|
||||||
|
|
||||||
|
|
||||||
|
// thread_create -> os_thread_launch
|
||||||
|
// thread_join -> os_thread_join
|
||||||
|
// thread_detach -> os_thread_detach
|
||||||
@ -1,24 +0,0 @@
|
|||||||
#if OS_WINDOWS
|
|
||||||
constexpr s64 FILETIME_TO_UNIX = 116444736000000000i64;
|
|
||||||
f64 GetUnixTimestamp() {
|
|
||||||
FILETIME fileTime;
|
|
||||||
GetSystemTimePreciseAsFileTime(&fileTime);
|
|
||||||
s64 ticks = ((s64)fileTime.dwHighDateTime << (s64)32) | (s64)fileTime.dwLowDateTime;
|
|
||||||
return (ticks - FILETIME_TO_UNIX) / (10.0 * 1000.0 * 1000.0);
|
|
||||||
}
|
|
||||||
s64 GetUnixTimestampNanoseconds() {
|
|
||||||
FILETIME fileTime;
|
|
||||||
GetSystemTimePreciseAsFileTime(&fileTime);
|
|
||||||
|
|
||||||
s64 ticks = ((s64)fileTime.dwHighDateTime << (s64)32)
|
|
||||||
| (s64)fileTime.dwLowDateTime; // in 100ns ticks
|
|
||||||
s64 unix_time = (ticks - FILETIME_TO_UNIX); // in 100ns ticks
|
|
||||||
|
|
||||||
s64 unix_time_nanoseconds = unix_time * 100;
|
|
||||||
|
|
||||||
return unix_time_nanoseconds;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#if OS_LINUX
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -14,7 +14,6 @@ internal void Bootstrap_Main_Thread_Context () {
|
|||||||
// thread_local_context->logger = init_logger();
|
// thread_local_context->logger = init_logger();
|
||||||
|
|
||||||
// debug_break();
|
// debug_break();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// #include "lib/Base/Arena_Array.h"
|
// #include "lib/Base/Arena_Array.h"
|
||||||
@ -1,3 +1,25 @@
|
|||||||
|
#if OS_WINDOWS
|
||||||
|
constexpr s64 FILETIME_TO_UNIX = 116444736000000000i64;
|
||||||
|
f64 GetUnixTimestamp() {
|
||||||
|
FILETIME fileTime;
|
||||||
|
GetSystemTimePreciseAsFileTime(&fileTime);
|
||||||
|
s64 ticks = ((s64)fileTime.dwHighDateTime << (s64)32) | (s64)fileTime.dwLowDateTime;
|
||||||
|
return (ticks - FILETIME_TO_UNIX) / (10.0 * 1000.0 * 1000.0);
|
||||||
|
}
|
||||||
|
s64 GetUnixTimestampNanoseconds() {
|
||||||
|
FILETIME fileTime;
|
||||||
|
GetSystemTimePreciseAsFileTime(&fileTime);
|
||||||
|
|
||||||
|
s64 ticks = ((s64)fileTime.dwHighDateTime << (s64)32)
|
||||||
|
| (s64)fileTime.dwLowDateTime; // in 100ns ticks
|
||||||
|
s64 unix_time = (ticks - FILETIME_TO_UNIX); // in 100ns ticks
|
||||||
|
|
||||||
|
s64 unix_time_nanoseconds = unix_time * 100;
|
||||||
|
|
||||||
|
return unix_time_nanoseconds;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
struct OS_System_Info {
|
struct OS_System_Info {
|
||||||
s32 logical_processor_count;
|
s32 logical_processor_count;
|
||||||
s32 physical_core_count;
|
s32 physical_core_count;
|
||||||
@ -155,41 +177,128 @@ internal void Win32_Entry_Point (int argc, WCHAR **argv) {
|
|||||||
Main_Entry_Point(argc, argv);
|
Main_Entry_Point(argc, argv);
|
||||||
}
|
}
|
||||||
|
|
||||||
// OS_Thread stuff
|
C_LINKAGE DWORD OS_Windows_Thread_Entry_Point (void* parameter) {
|
||||||
|
Thread* thread = (Thread*)parameter;
|
||||||
|
|
||||||
|
set_thread_context(thread->context);
|
||||||
|
|
||||||
|
DWORD result = (DWORD)thread->proc(thread);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// OS_Thread stuff
|
||||||
|
internal bool thread_init (Thread* thread, Thread_Proc proc, string thread_name) {
|
||||||
|
Assert(thread != nullptr && proc != nullptr);
|
||||||
|
|
||||||
|
DWORD windows_thread_id = 0;
|
||||||
|
|
||||||
|
HANDLE windows_thread = CreateThread(nullptr, 0, OS_Windows_Thread_Entry_Point,
|
||||||
|
thread, CREATE_SUSPENDED, &windows_thread_id);
|
||||||
|
|
||||||
|
if (windows_thread == 0 || windows_thread == INVALID_HANDLE_VALUE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
s64 this_thread_index = InterlockedIncrement(&next_thread_index);
|
||||||
|
|
||||||
|
// We may not always want such a bulky thread startup. The
|
||||||
|
// size of the starting arena and temp should be parameterized (+2 bytes)
|
||||||
|
// make thread_init_ex with params...
|
||||||
|
Arena* arena = next_arena(Arena_Reserve::Size_64G);
|
||||||
|
thread->context = New<Thread_Context>(get_allocator(arena));
|
||||||
|
thread->context->temp = next_arena(Arena_Reserve::Size_64G);
|
||||||
|
thread->context->arena = arena;
|
||||||
|
thread->context->allocator = get_allocator(arena);
|
||||||
|
thread->context->thread_idx = (s32)this_thread_index;
|
||||||
|
thread->context->thread_name = thread_name;
|
||||||
|
|
||||||
|
thread->os_thread.windows_thread = windows_thread;
|
||||||
|
thread->os_thread.windows_thread_id = windows_thread_id;
|
||||||
|
|
||||||
|
thread->proc = proc;
|
||||||
|
thread->index = this_thread_index;
|
||||||
|
// #TODO: prepare thread data
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void thread_start (Thread* thread) {
|
||||||
|
ResumeThread(thread->os_thread.windows_thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool thread_is_done (Thread* thread, s32 milliseconds=0) {
|
||||||
|
Assert(milliseconds >= -1);
|
||||||
|
|
||||||
|
DWORD result = WaitForSingleObject(thread->os_thread.windows_thread, (DWORD)milliseconds);
|
||||||
|
return result != WAIT_TIMEOUT;
|
||||||
|
}
|
||||||
|
|
||||||
// Individual Threads
|
// Individual Threads
|
||||||
// os_thread_create
|
|
||||||
// os_thread_join
|
|
||||||
// os_thread_detach
|
|
||||||
|
|
||||||
// Thread Group API?
|
// Thread Group API?
|
||||||
|
|
||||||
// mutexes
|
// #thread_primitives
|
||||||
// internal Mutex mutex_alloc(void) {return os_mutex_alloc();}
|
internal void mutex_init (Mutex* mutex) {
|
||||||
// internal void mutex_release(Mutex mutex) {os_mutex_release(mutex);}
|
InitializeCriticalSection(&mutex->csection);
|
||||||
// internal void mutex_take(Mutex mutex) {os_mutex_take(mutex);}
|
}
|
||||||
// internal void mutex_drop(Mutex mutex) {os_mutex_drop(mutex);}
|
internal void mutex_destroy (Mutex* mutex) {
|
||||||
//- rjf: condition variables
|
DeleteCriticalSection(&mutex->csection);
|
||||||
|
}
|
||||||
|
internal void lock (Mutex* mutex) {
|
||||||
|
EnterCriticalSection(&mutex->csection);
|
||||||
|
}
|
||||||
|
internal void unlock (Mutex* mutex) {
|
||||||
|
LeaveCriticalSection(&mutex->csection);
|
||||||
|
}
|
||||||
|
internal void semaphore_init (Semaphore* sem, s32 initial_value = 0);
|
||||||
|
internal void semaphore_init (Semaphore* sem, s32 initial_value) {
|
||||||
|
Assert(initial_value >= 0);
|
||||||
|
sem->event = CreateSemaphoreW(nullptr, initial_value, 0x7fffffff, nullptr);
|
||||||
|
}
|
||||||
|
internal void semaphore_destroy (Semaphore* sem) {
|
||||||
|
CloseHandle(sem->event);
|
||||||
|
}
|
||||||
|
internal void signal (Semaphore* sem) {
|
||||||
|
ReleaseSemaphore(sem->event, 1, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
// internal CondVar cond_var_alloc(void) {return os_cond_var_alloc();}
|
enum class Wait_For_Result : s32 {
|
||||||
// internal void cond_var_release(CondVar cv) {os_cond_var_release(cv);}
|
SUCCESS = 0,
|
||||||
// internal B32 cond_var_wait(CondVar cv, Mutex mutex, U64 endt_us) {return os_cond_var_wait(cv, mutex, endt_us);}
|
TIMEOUT = 1,
|
||||||
// internal B32 cond_var_wait_rw(CondVar cv, RWMutex mutex_rw, B32 write_mode, U64 endt_us) {return os_cond_var_wait_rw(cv, mutex_rw, write_mode, endt_us);}
|
ERROR = 2 // can't use ERROR because of Windows.h *sigh*
|
||||||
// internal void cond_var_signal(CondVar cv) {os_cond_var_signal(cv);}
|
};
|
||||||
// internal void cond_var_broadcast(CondVar cv) {os_cond_var_broadcast(cv);}
|
|
||||||
|
|
||||||
//- rjf: cross-process semaphores
|
internal Wait_For_Result wait_for (Semaphore* sem, s32 milliseconds=-1);
|
||||||
|
internal Wait_For_Result wait_for (Semaphore* sem, s32 milliseconds) {
|
||||||
|
DWORD res = 0;
|
||||||
|
if (milliseconds < 0) {
|
||||||
|
res = WaitForSingleObject(sem->event, INFINITE);
|
||||||
|
} else {
|
||||||
|
res = WaitForSingleObject(sem->event, (u32)milliseconds);
|
||||||
|
}
|
||||||
|
|
||||||
// internal Semaphore semaphore_alloc(U32 initial_count, U32 max_count, String8 name) {return os_semaphore_alloc(initial_count, max_count, name);}
|
Assert(res != WAIT_FAILED);
|
||||||
// internal void semaphore_release(Semaphore semaphore) {os_semaphore_release(semaphore);}
|
|
||||||
// internal Semaphore semaphore_open(String8 name) {return os_semaphore_open(name);}
|
|
||||||
// internal void semaphore_close(Semaphore semaphore) {os_semaphore_close(semaphore);}
|
|
||||||
// internal B32 semaphore_take(Semaphore semaphore, U64 endt_us) {return os_semaphore_take(semaphore, endt_us);}
|
|
||||||
// internal void semaphore_drop(Semaphore semaphore) {os_semaphore_drop(semaphore);}
|
|
||||||
|
|
||||||
//- rjf: barriers
|
if (res == WAIT_OBJECT_0) return Wait_For_Result::SUCCESS;
|
||||||
|
if (res == WAIT_TIMEOUT) return Wait_For_Result::TIMEOUT;
|
||||||
|
|
||||||
// internal Barrier barrier_alloc(U64 count) {return os_barrier_alloc(count);}
|
return Wait_For_Result::ERROR;
|
||||||
// internal void barrier_release(Barrier barrier) {os_barrier_release(barrier);}
|
}
|
||||||
// internal void barrier_wait(Barrier barrier) {os_barrier_wait(barrier);}
|
|
||||||
|
internal void condition_variable_init (Condition_Variable* cv) {
|
||||||
|
InitializeConditionVariable(&cv->condition_variable);
|
||||||
|
}
|
||||||
|
internal void condition_variable_destroy (Condition_Variable* cv) {
|
||||||
|
// No action required.
|
||||||
|
}
|
||||||
|
internal void wait (Condition_Variable* cv, Mutex* mutex, s32 wait_time_ms=-1);
|
||||||
|
internal void wait (Condition_Variable* cv, Mutex* mutex, s32 wait_time_ms) {
|
||||||
|
SleepConditionVariableCS(&cv->condition_variable, &mutex->csection, (DWORD)wait_time_ms);
|
||||||
|
}
|
||||||
|
internal void wake (Condition_Variable* cv) {
|
||||||
|
WakeConditionVariable(&cv->condition_variable);
|
||||||
|
}
|
||||||
|
internal void wake_all (Condition_Variable* cv) {
|
||||||
|
WakeAllConditionVariable(&cv->condition_variable);
|
||||||
|
}
|
||||||
@ -1,6 +1,11 @@
|
|||||||
// This is quite disorganized. There must be a better way to do this by moving the stuff that requires forward declaration to the top
|
// This is quite disorganized. There must be a better way to do this by moving the stuff that requires forward declaration to the top
|
||||||
// with a metaprogram.
|
// with a metaprogram.
|
||||||
|
|
||||||
|
|
||||||
|
// ~musa This is a unity build, where all source files in the project is combined into a single
|
||||||
|
// translation unit.
|
||||||
|
// lib_main.cpp can be treated as a single-header library and added to a project like that.
|
||||||
|
#include "lib/meta_generated.h"
|
||||||
#include "lib/Base/Base.h"
|
#include "lib/Base/Base.h"
|
||||||
#include "lib/Base/Allocator.h"
|
#include "lib/Base/Allocator.h"
|
||||||
#include "lib/Base/Array.h"
|
#include "lib/Base/Array.h"
|
||||||
@ -17,4 +22,18 @@
|
|||||||
#include "lib/Base/Allocator.cpp"
|
#include "lib/Base/Allocator.cpp"
|
||||||
#include "lib/Base/General_Purpose_Allocator.cpp"
|
#include "lib/Base/General_Purpose_Allocator.cpp"
|
||||||
#include "lib/Base/Basic.cpp"
|
#include "lib/Base/Basic.cpp"
|
||||||
#include "lib/Base/Timing.cpp"
|
|
||||||
|
// OS-Abstraction Layer
|
||||||
|
#include "lib/OS/Base_Entry_Point.cpp"
|
||||||
|
|
||||||
|
// #if OS_LINUX..
|
||||||
|
#include "lib/Base/Threads.cpp"
|
||||||
|
#if OS_WINDOWS
|
||||||
|
# include "lib/OS/OS_Win32.cpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// #include "imgui-docking.cpp"
|
||||||
|
|
||||||
|
// #include "src/OS_Linux.cpp" // #TODO: Future.
|
||||||
|
// #include "src/OS_MacOS.cpp" // #TODO: Future.
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user