diff --git a/build.jai b/build.jai index ceb14d7..7a97942 100644 --- a/build.jai +++ b/build.jai @@ -5,8 +5,8 @@ VERSION :: "0.1a"; LIB_BASE_NAME :: "musa-lib"; EXE_BASE_NAME :: "musa"; -LIB_SOURCE_FILENAMES :: string.["unity_build_lib.cpp"]; -EXE_SOURCE_FILENAMES :: string.["unity_build_exe.cpp"]; +LIB_SOURCE_FILENAMES :: string.["lib_main.cpp"]; +EXE_SOURCE_FILENAMES :: string.["exe_main.cpp"]; INCLUDE_DIRS :: string.[ "src", @@ -85,7 +85,7 @@ build_cpp_lib :: (compile_debug: bool) -> bool { 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; os_target: Operating_System_Tag = .WINDOWS; diff --git a/unity_build_exe.cpp b/exe_main.cpp similarity index 56% rename from unity_build_exe.cpp rename to exe_main.cpp index 4735e9e..edbac13 100644 --- a/unity_build_exe.cpp +++ b/exe_main.cpp @@ -1,10 +1,4 @@ -#include "unity_build_lib.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. - +#include "lib_main.cpp" #if OS_WINDOWS #if BUILD_CONSOLE_INTERFACE diff --git a/lib/Base/Base.h b/lib/Base/Base.h index 4b0f64e..a1c76e9 100644 --- a/lib/Base/Base.h +++ b/lib/Base/Base.h @@ -1,7 +1,5 @@ #pragma once -#include "meta_generated.h" - #define LANG_CPP 1 #define BUILD_CONSOLE_INTERFACE BUILD_DEBUG @@ -18,6 +16,7 @@ #if OS_WINDOWS #define WIN32_LEAN_AND_MEAN #include + #undef ERROR // why... #else #error "This configuration is NOT supported. Only Windows with MSVC is currently supported." #endif diff --git a/lib/Base/Base_Thread_Context.cpp b/lib/Base/Base_Thread_Context.cpp index 3f2bbe6..f7878bc 100644 --- a/lib/Base/Base_Thread_Context.cpp +++ b/lib/Base/Base_Thread_Context.cpp @@ -72,9 +72,8 @@ thread_static Thread_Context* thread_local_context; // Let's do arenas first. -void init_thread_context(Thread_Context* tctx) { - // Should be Arena-bootstrapped with Temp Arena maybe? - // #TODO - call from entry point. +force_inline void set_thread_context (Thread_Context* new_context) { + thread_local_context = new_context; } Thread_Context* get_thread_context() { diff --git a/lib/Base/Threads.cpp b/lib/Base/Threads.cpp new file mode 100644 index 0000000..bc7c6c6 --- /dev/null +++ b/lib/Base/Threads.cpp @@ -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 diff --git a/lib/Base/Timing.cpp b/lib/Base/Timing.cpp deleted file mode 100644 index 9c59971..0000000 --- a/lib/Base/Timing.cpp +++ /dev/null @@ -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 diff --git a/src/Base_Entry_Point.cpp b/lib/OS/Base_Entry_Point.cpp similarity index 99% rename from src/Base_Entry_Point.cpp rename to lib/OS/Base_Entry_Point.cpp index 2110362..faac4aa 100644 --- a/src/Base_Entry_Point.cpp +++ b/lib/OS/Base_Entry_Point.cpp @@ -14,7 +14,6 @@ internal void Bootstrap_Main_Thread_Context () { // thread_local_context->logger = init_logger(); // debug_break(); - } // #include "lib/Base/Arena_Array.h" diff --git a/src/OS_Win32.cpp b/lib/OS/OS_Win32.cpp similarity index 53% rename from src/OS_Win32.cpp rename to lib/OS/OS_Win32.cpp index 4905193..332aee8 100644 --- a/src/OS_Win32.cpp +++ b/lib/OS/OS_Win32.cpp @@ -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 { s32 logical_processor_count; s32 physical_core_count; @@ -155,41 +177,128 @@ internal void Win32_Entry_Point (int argc, WCHAR **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(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 -// os_thread_create -// os_thread_join -// os_thread_detach // Thread Group API? -// mutexes -// internal Mutex mutex_alloc(void) {return os_mutex_alloc();} -// internal void mutex_release(Mutex mutex) {os_mutex_release(mutex);} -// internal void mutex_take(Mutex mutex) {os_mutex_take(mutex);} -// internal void mutex_drop(Mutex mutex) {os_mutex_drop(mutex);} -//- rjf: condition variables +// #thread_primitives +internal void mutex_init (Mutex* mutex) { + InitializeCriticalSection(&mutex->csection); +} +internal void mutex_destroy (Mutex* mutex) { + 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();} -// internal void cond_var_release(CondVar cv) {os_cond_var_release(cv);} -// internal B32 cond_var_wait(CondVar cv, Mutex mutex, U64 endt_us) {return os_cond_var_wait(cv, mutex, endt_us);} -// 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);} -// internal void cond_var_signal(CondVar cv) {os_cond_var_signal(cv);} -// internal void cond_var_broadcast(CondVar cv) {os_cond_var_broadcast(cv);} +enum class Wait_For_Result : s32 { + SUCCESS = 0, + TIMEOUT = 1, + ERROR = 2 // can't use ERROR because of Windows.h *sigh* +}; -//- 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); + } + + Assert(res != WAIT_FAILED); + + if (res == WAIT_OBJECT_0) return Wait_For_Result::SUCCESS; + if (res == WAIT_TIMEOUT) return Wait_For_Result::TIMEOUT; + + return Wait_For_Result::ERROR; +} -// internal Semaphore semaphore_alloc(U32 initial_count, U32 max_count, String8 name) {return os_semaphore_alloc(initial_count, max_count, name);} -// 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 - -// internal Barrier barrier_alloc(U64 count) {return os_barrier_alloc(count);} -// 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); +} diff --git a/lib/Base/meta_generated.h b/lib/meta_generated.h similarity index 100% rename from lib/Base/meta_generated.h rename to lib/meta_generated.h diff --git a/unity_build_lib.cpp b/lib_main.cpp similarity index 54% rename from unity_build_lib.cpp rename to lib_main.cpp index df4c8af..405467b 100644 --- a/unity_build_lib.cpp +++ b/lib_main.cpp @@ -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 // 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/Allocator.h" #include "lib/Base/Array.h" @@ -17,4 +22,18 @@ #include "lib/Base/Allocator.cpp" #include "lib/Base/General_Purpose_Allocator.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. +