From 42085666e99cd016f1720f1016289f8ced28424f Mon Sep 17 00:00:00 2001 From: Tenari Date: Sat, 4 Apr 2026 09:15:37 -0500 Subject: [PATCH] base layer, mostly stolen --- all.h | 691 ++++++++++++++++++++++++++++++++++++++++++++++ entry.c | 45 +++ impl.c | 13 + linux_os.c | 139 ++++++++++ mac_os.c | 136 +++++++++ math.c | 68 +++++ memory.c | 100 +++++++ os.c | 12 + pthread_barrier.c | 102 +++++++ pthread_barrier.h | 49 ++++ serialize.c | 89 ++++++ string.c | 216 +++++++++++++++ string_chunk.c | 162 +++++++++++ tctx.c | 83 ++++++ thread.c | 36 +++ thread_context.h | 114 ++++++++ unix_os.c | 147 ++++++++++ win32_os.c | 326 ++++++++++++++++++++++ 18 files changed, 2528 insertions(+) create mode 100644 all.h create mode 100644 entry.c create mode 100644 impl.c create mode 100644 linux_os.c create mode 100644 mac_os.c create mode 100644 math.c create mode 100644 memory.c create mode 100644 os.c create mode 100644 pthread_barrier.c create mode 100644 pthread_barrier.h create mode 100644 serialize.c create mode 100644 string.c create mode 100644 string_chunk.c create mode 100644 tctx.c create mode 100644 thread.c create mode 100644 thread_context.h create mode 100644 unix_os.c create mode 100644 win32_os.c diff --git a/all.h b/all.h new file mode 100644 index 0000000..d8f73b0 --- /dev/null +++ b/all.h @@ -0,0 +1,691 @@ +// good base layer overview https://www.youtube.com/watch?v=bUOOaXf9qIM +#ifndef BASE_ALL_H +#define BASE_ALL_H + +///// Context Cracking +// Development Settings +#if !defined(ENABLE_ASSERT) +# define ENABLE_ASSERT 1 +#endif +#if !defined(ENABLE_SANITIZER) +# define ENABLE_SANITIZER 0 +#endif +#if !defined(ENABLE_MANUAL_PROFILE) +# define ENABLE_MANUAL_PROFILE 0 +#endif +#if !defined(ENABLE_AUTO_PROFILE) +# define ENABLE_AUTO_PROFILE 0 +#endif + +#if defined(ENABLE_ANY_PROFILE) +# error user should not configure ENABLE_ANY_PROFILE +#endif + +#if ENABLE_MANUAL_PROFILE || ENABLE_AUTO_PROFILE +# define ENABLE_ANY_PROFILE 1 +#else +# define ENABLE_ANY_PROFILE 0 +#endif + +// Untangle Compiler, OS & Architecture +#if defined(__clang__) +# define COMPILER_CLANG 1 + +# if defined(_WIN32) +# define OS_WINDOWS 1 +# elif defined(__gnu_linux__) +# define OS_LINUX 1 +# elif defined(__APPLE__) && defined(__MACH__) +# define OS_MAC 1 +# else +# error missing OS detection +# endif + +# if defined(__amd64__) +# define ARCH_X64 1 +// TODO verify this works on clang +# elif defined(__i386__) +# define ARCH_X86 1 +// TODO verify this works on clang +# elif defined(__arm__) +# define ARCH_ARM 1 +// TODO verify this works on clang +# elif defined(__aarch64__) +# define ARCH_ARM64 1 +# else +# error missing ARCH detection +# endif + +#elif defined(_MSC_VER) +# define COMPILER_CL 1 + +# if defined(_WIN32) +# define OS_WINDOWS 1 +# else +# error missing OS detection +# endif + +# if defined(_M_AMD64) +# define ARCH_X64 1 +# elif defined(_M_I86) +# define ARCH_X86 1 +# elif defined(_M_ARM) +# define ARCH_ARM 1 +// TODO ARM64? +# else +# error missing ARCH detection +# endif + +#elif defined(__GNUC__) +# define COMPILER_GCC 1 + +# if defined(_WIN32) +# define OS_WINDOWS 1 +# elif defined(__gnu_linux__) +# define OS_LINUX 1 +# elif defined(__APPLE__) && defined(__MACH__) +# define OS_MAC 1 +# else +# error missing OS detection +# endif + +# if defined(__amd64__) +# define ARCH_X64 1 +# elif defined(__i386__) +# define ARCH_X86 1 +# elif defined(__arm__) +# define ARCH_ARM 1 +# elif defined(__aarch64__) +# define ARCH_ARM64 1 +# else +# error missing ARCH detection +# endif + +#else +# error no context cracking for this compiler +#endif + +#if !defined(COMPILER_CL) +# define COMPILER_CL 0 +#endif +#if !defined(COMPILER_CLANG) +# define COMPILER_CLANG 0 +#endif +#if !defined(COMPILER_GCC) +# define COMPILER_GCC 0 +#endif +#if !defined(OS_WINDOWS) +# define OS_WINDOWS 0 +#endif +#if !defined(OS_LINUX) +# define OS_LINUX 0 +#endif +#if !defined(OS_MAC) +# define OS_MAC 0 +#endif +#if !defined(ARCH_X64) +# define ARCH_X64 0 +#endif +#if !defined(ARCH_X86) +# define ARCH_X86 0 +#endif +#if !defined(ARCH_ARM) +# define ARCH_ARM 0 +#endif +#if !defined(ARCH_ARM64) +# define ARCH_ARM64 0 +#endif + +// Language +#if defined(__cplusplus) +# define LANG_CXX 1 +#else +# define LANG_C 1 +#endif + +#if !defined(LANG_CXX) +# define LANG_CXX 0 +#endif +#if !defined(LANG_C) +# define LANG_C 0 +#endif + +// Profiler +#if !defined(PROFILER_SPALL) +# define PROFILER_SPALL 0 +#endif + +// Determine Intrinsics Mode +#if OS_WINDOWS +# if COMPILER_CL || COMPILER_CLANG +# define INTRINSICS_MICROSOFT 1 +# endif +#endif + +#if !defined(INTRINSICS_MICROSOFT) +# define INTRINSICS_MICROSOFT 0 +#endif + +// Setup Pointer Size Macro +#if ARCH_X64 || ARCH_ARM64 +# define ARCH_ADDRSIZE 64 +#else +# define ARCH_ADDRSIZE 32 +#endif + +///// MACROS +#define global static +#define internal static +#define fn static + +#define arrayLen(a) (sizeof(a)/sizeof(*(a))) + +#define intFromPtr(p) (U64)((U8*)p - (U8*)0) +#define ptrFromInt(n) (void*)((U8*)0 + (n)) + +#define stmnt(S) do{ S }while(0) + +#if !defined(assertBreak) +# define assertBreak() (*(volatile int*)0 = 0) +#endif + +#ifndef offsetof +# define offsetof(st, m) ((size_t)&(((st*)0)->m)) +#endif + +#if ENABLE_ASSERT +# define assert(c) stmnt( if (!(c)){ assertBreak(); } ) +#else +# define assert(c) +#endif + +#define ToBool(x) ((x) != 0) + +#define KB(x) ((x) << 10) +#define MB(x) ((x) << 20) +#define GB(x) ((x) << 30) +#define TB(x) ((u64)(x) << 40llu) + +#include +#define MemoryCopy(d,s,z) memmove((d), (s), (z)) +#define MemoryCopyStruct(d,s) MemoryCopy((d),(s), Min(sizeof(*(d)) , sizeof(*(s)))) +#define MemoryZero(d,z) memset((d), 0, (z)) +#define MemoryZeroStruct(d,s) MemoryZero((d),sizeof(s)) +#define Min(a,b) (((a)<(b))?(a):(b)) +#define Max(a,b) (((a)>(b))?(a):(b)) +#define Abs(x) (((x)<0)?((x)*-1):(x)) + +#define SetFlag(flags, bit) ((flags) |= (1ULL << (bit))) +#define ClearFlag(flags, bit) ((flags) &= ~(1ULL << (bit))) +#define ToggleFlag(flags, bit) ((flags) ^= (1ULL << (bit))) +#define CheckFlag(flags, bit) (((flags) >> (bit)) & 1ULL) + +#define QueuePush(f,l,n) (((f)==NULL) ? ((f)=(l)=(n)) : ((l)->next=(n),(l)=(n),(n)->next = NULL)) + +#define DEFAULT_ALIGNMENT sizeof(void*) +#define isPowerOfTwo(x) ((x & (x-1)) == 0) + +#define XYToPos(x, y, w) ((u32)(((u32)(x)) + (((u32)(y)) * (w)))) + +#if COMPILER_MSVC +# define thread_static __declspec(thread) +#elif COMPILER_CLANG || COMPILER_GCC +# define thread_static __thread +#else +# error thread_static not defined for this compiler. +#endif + +// only valid if there's an in-scope variable bool `debug_mode` +#define dbg(fmt, ...) osDebugPrint(debug_mode, fmt, ##__VA_ARGS__) + +///// SYSTEM INCLUDES I always want +#include +#include + +///// TYPES +// integer types +typedef unsigned char u8; +typedef signed char i8; +typedef unsigned short u16; +typedef short i16; +typedef unsigned int u32; +typedef int i32; +typedef unsigned long long u64; +typedef signed long long i64; +typedef char * usize; +typedef char * ptr; +typedef const char* str; + +// Floating point types +typedef float f32; +typedef double f64; + +//typedef long double f80; only returning 8 bytes on my mac for some reason +/* + printf("u8 %d\n", (int)sizeof(u8) * 8); + printf("i8 %d\n", (int)sizeof(i8) * 8); + printf("u16 %d\n", (int)sizeof(u16) * 8); + printf("i16 %d\n", (int)sizeof(i16) * 8); + printf("u32 %d\n", (int)sizeof(u32) * 8); + printf("i32 %d\n", (int)sizeof(i32) * 8); + printf("u64 %d\n", (int)sizeof(u64) * 8); + printf("i64 %d\n", (int)sizeof(i64) * 8); + printf("usize %d\n", (int)sizeof(usize) * 8); + + printf("f32 %d\n", (int)sizeof(f32) * 8); + printf("f64 %d\n", (int)sizeof(f64) * 8); + printf("f80 %d\n", (int)sizeof(f80) * 8); +*/ + +// Boolean types +typedef u8 b8; +typedef u32 b32; +#ifndef bool +# define bool b8 +#endif + +#define true 1 +#define false 0 + +// Structs +typedef struct Arena { + u8* memory; + u64 max; + u64 alloc_position; + u64 commit_position; + b8 static_size; +} Arena; + +typedef struct PtrArray { + u32 length; + u32 capacity; + ptr* items; +} PtrArray; + +typedef struct u8List { + u32 length; + u32 capacity; + u8* items; +} u8List; + +typedef struct String { + u32 length; + u32 capacity; + ptr bytes; +} String; + +typedef struct StringUTF16Const { + u16* string; + u64 size; +} StringUTF16Const; + +typedef enum Utf8Character { + Utf8CharacterAscii, + Utf8CharacterTwoByte, + Utf8CharacterThreeByte, + Utf8CharacterFourByte, + Utf8Character_Count, +} Utf8Character; + +typedef enum FieldType { + FieldTypeU8, + FieldTypeU16, + FieldTypeU32, + FieldTypeFloat, + FieldTypeString, + FieldTypeEnum, + FieldType_Count, +} FieldType; + +typedef struct FieldDescriptor { + str name; + FieldType type; + size_t offset; + int width; // column width for display + str* enum_vals; +} FieldDescriptor; + +typedef struct TableDrawInfo { + u32 x_offset; + u32 y_offset; + u32 rows; + u32 cols; +} TableDrawInfo; + +typedef struct Box { + u32 x; + u32 y; + u32 height; + u32 width; +} Box; + +typedef struct Dim2 { + u16 height; + u16 width; +} Dim2; + +typedef struct Pos2 { + u16 x; + u16 y; +} Pos2; + +typedef struct Pos2u8 { + u8 x; + u8 y; +} Pos2u8; + +typedef union Range1u32 Range1u32; +union Range1u32 +{ + struct + { + u32 min; + u32 max; + }; + u32 v[2]; +}; + +typedef union Range1i32 Range1i32; +union Range1i32 +{ + struct + { + i32 min; + i32 max; + }; + i32 v[2]; +}; + +typedef union Range1u64 Range1u64; +union Range1u64 +{ + struct + { + u64 min; + u64 max; + }; + u64 v[2]; +}; + +typedef union Range1i64 Range1i64; +union Range1i64 +{ + struct + { + i64 min; + i64 max; + }; + i64 v[2]; +}; + +typedef union Range1f32 Range1f32; +union Range1f32 +{ + struct + { + f32 min; + f32 max; + }; + f32 v[2]; +}; + +#if OS_WINDOWS +# define WIN32_LEAN_AND_MEAN +# include +# include +# include +# include +# include +# include +# include + typedef struct { + DWORD input_mode; + DWORD output_mode; + } TermIOs; + // networking shim for windows +#ifndef POLLIN +# define POLLIN 0x0001 +# define POLLPRI 0x0002 +# define POLLOUT 0x0004 +# define POLLERR 0x0008 +# define POLLHUP 0x0010 +# define POLLNVAL 0x0020 + + typedef struct pollfd { + SOCKET fd; + short events; + short revents; + } pollfd_t; +#endif + + typedef int nfds_t; + + int poll(struct pollfd *fds, nfds_t nfds, int timeout); +#else +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include + typedef struct termios TermIOs; +#endif + +typedef struct Barrier { + u64 a[1]; +} Barrier; + +typedef struct CondVar { + u64 a[1]; +} CondVar; + +typedef struct Thread { + pthread_t thread; +} Thread; + +typedef struct Mutex { + pthread_mutex_t mutex; +} Mutex; + +typedef struct Cond { + pthread_cond_t cond; +} Cond; + +#define M_SCRATCH_SIZE KB(16) +typedef struct ScratchFreeListNode ScratchFreeListNode; +struct ScratchFreeListNode { + ScratchFreeListNode* next; + u32 index; +}; + +typedef struct ScratchMem { + Arena arena; + u32 index; + u64 pos; +} ScratchMem; + +typedef struct LaneCtx { + u64 lane_idx; + u64 lane_count; + Barrier barrier; + u64 *broadcast_memory; +} LaneCtx; + +typedef struct ThreadContext { + Arena arena; // scratch + u32 max_created; + ScratchFreeListNode* free_list; + LaneCtx lane_ctx; +} ThreadContext; + +///// HARDCODED GLOBALS +global const u64 MAX_u64 = 0xffffffffffffffffull; +global const u32 MAX_u32 = 0xffffffff; +global const u16 MAX_u16 = 0xffff; +global const u8 MAX_u8 = 0xff; +#define EULERS_E (2.71828) +#define PI (3.14159265358979323846) + +///// CUSTOM ENTRY POINT +/* TODO? +fn void mainThreadBaseEntryPoint(i32 argc, char **argv); +fn void asyncThreadEntryPoint(void *params); +fn void supplement_thread_base_entry_point(void (*entry_point)(void *params), void *params); +fn u64 update_tick_idx(void); +fn b32 update(void); +*/ + +///// MATH +fn Range1u64 range1u64Create(u64 min, u64 max); +fn Range1u64 mRangeFromNIdxMCount(u64 n_idx, u64 n_count, u64 m_count); +fn void u32Quicksort(u32 arr[], u32 low, u32 high); +fn void u32ReverseArray(u32 arr[], u32 size); + +///// MEMORY (Arenas) +#define ARENA_MAX GB(1) +// this was an evil bug to figure out +#if defined(OS_MAC) +# define ARENA_COMMIT_SIZE KB(16) +#else +# define ARENA_COMMIT_SIZE KB(8) +#endif + +fn void* arenaAlloc(Arena* arena, u64 size); +fn void* arenaAllocZero(Arena* arena, u64 size); +fn void arenaDealloc(Arena* arena, u64 size); +fn void arenaDeallocTo(Arena* arena, u64 pos); +fn void* arenaRaise(Arena* arena, void* ptr, u64 size); +fn void* arenaAllocArraySized(Arena* arena, u64 elem_size, u64 count); +#define arenaAllocArray(arena, elem_type, count) arenaAllocArraySized(arena, sizeof(elem_type), count) + +fn void arenaInit(Arena* arena); +fn void arenaInitSized(Arena* arena, u64 max); +fn void arenaClear(Arena* arena); +fn void arenaFree(Arena* arena); + +ScratchMem scratchGet(void); +void scratchReset(ScratchMem* scratch); +void scratchReturn(ScratchMem* scratch); + +///// STRINGS +#define ASCII_TAB (9) +#define ASCII_LINE_FEED (10) +#define ASCII_RETURN (13) +#define ASCII_ESCAPE (27) +#define ASCII_DEL (127) +#define ASCII_BACKSPACE (8) + +fn bool stringsEq(String* a, String* b); +fn bool cStringEqString(str a, String* b); +fn Utf8Character classifyUtf8Character(u8 c); +fn bool isUtf8Ascii(u8 c); +fn bool isUtf8TwoByte(u8 c); +fn bool isUtf8ThreeByte(u8 c); +fn bool isUtf8FourByte(u8 c); +fn u8 lowerAscii(u8 c); +fn u8 upperAscii(u8 c); +fn StringUTF16Const str16FromStr8(Arena* a, String string); +fn bool isAlphaUnderscoreSpace(u8 c); +fn bool isSimplePrintable(u8 c); + +///// OS-wrapped apis +void osInit(); +void* osThreadContextGet(); +void osThreadContextSet(void* ctx); +bool osThreadJoin(Thread handle, u64 endt_us); + +fn Barrier osBarrierAlloc(u64 count); +fn void osBarrierRelease(Barrier barrier); +fn void osBarrierWait(Barrier barrier); + +// Memory +fn void* osMemoryReserve(u64 size); +fn void osMemoryCommit(void* memory, u64 size); +fn void osMemoryDecommit(void* memory, u64 size); +fn void osMemoryRelease(void* memory, u64 size); +fn u64 osTimeMicrosecondsNow(); +fn void osSleepMicroseconds(u32 t); + +// Files +fn bool osFileExists(String filename); +fn String osFileRead(Arena* arena, ptr filepath); +fn bool osFileCreate(String filename); +fn bool osFileCreateWrite(String filename, String data); +fn bool osFileWrite(String filename, String data); + +fn void osDebugPrint(bool debug_mode, const char* format, ...); + +// Tui stuff +TermIOs osStartTUI(bool blocking); +fn void osEndTUI(TermIOs old_terminal_attributes); +fn Dim2 osGetTerminalDimensions(); +void osBlitToTerminal(ptr writeable_output_ansi_string, i64 count); +void osReadConsoleInput(u8* buffer, u32 len); + +// network stuff +bool osInitNetwork(); +i32 osLanIPAddress(); + +///// Basic THREAD synchronization apis +Thread spawnThread(void * (*threadFn)(void *), void* thread_arg); +Mutex newMutex(); +Cond newCond(); +void lockMutex(Mutex* m); +void unlockMutex(Mutex* m); +void signalCond(Cond* cond); +void waitForCondSignal(Cond* cond, Mutex* mutex); + +///// Multi-Core by Default ThreadContext stuff +void tctxInit(ThreadContext* ctx); +void tctxFree(ThreadContext* ctx); +fn ThreadContext *tctxSelected(void); + +ScratchMem tctxScratchGet(ThreadContext* ctx); +void tctxScratchReset(ThreadContext* ctx, ScratchMem* scratch); +void tctxScratchReturn(ThreadContext* ctx, ScratchMem* scratch); + +fn LaneCtx tctxSetLaneCtx(LaneCtx lane_ctx); +fn void tctxLaneBarrierWait(void *broadcast_ptr, u64 broadcast_size, u64 broadcast_src_lane_idx); +#define LaneIdx() (tctxSelected()->lane_ctx.lane_idx) +#define LaneCount() (tctxSelected()->lane_ctx.lane_count) +#define LaneFromTaskIdx(idx) ((idx)%LaneCount()) +#define LaneCtx(ctx) tctxSetLaneCtx((ctx)) +#define LaneSync() tctxLaneBarrierWait(0, 0, 0) +#define LaneSyncu64(pointer, src_lane_idx) tctxLaneBarrierWait((pointer), sizeof(*(pointer)), (src_lane_idx)) +#define LaneRange(count) mRangeFromNIdxMCount(LaneIdx(), LaneCount(), (count)) + +///// StringChunk stuff +#ifndef STRING_CHUNK_PAYLOAD_SIZE +#define STRING_CHUNK_PAYLOAD_SIZE (64 - sizeof(StringChunk*)) +#endif + +typedef struct StringChunk { + struct StringChunk *next; // essentially a header, followed by a fixed maximum str bytes +} StringChunk; + +typedef struct StringChunkList { + StringChunk* first; + StringChunk* last; + u64 count; + u64 total_size; +} StringChunkList; + +typedef struct StringArena { + Arena a; + StringChunk* first_free_str_chunk; + Mutex mutex; +} StringArena; + +fn StringChunkList allocStringChunkList(StringArena* a, String string); +fn void releaseStringChunkList(StringArena* a, StringChunkList* list); +fn String stringChunkToString(Arena* a, StringChunkList list); +fn void stringChunkListAppend(StringArena* a, StringChunkList* list, String string); +fn void stringChunkListDeleteLast(StringArena* a, StringChunkList* list); +fn StringChunkList stringChunkListInit(StringArena* a); +fn void stringChunkCopyToBuffer(StringChunkList* list, u8* buffer, u32 len); + +#endif// BASE_ALL_H diff --git a/entry.c b/entry.c new file mode 100644 index 0000000..c38c5ef --- /dev/null +++ b/entry.c @@ -0,0 +1,45 @@ +#include "all.h" + +fn void mainThreadBaseEntryPoint(i32 argc, char **argv) { + Thread* async_threads = 0; + u64 lane_broadcast_val = 0; + { + u32 num_main_threads = 1; + u32 num_async_threads = 12;//TODO: os_get_system_info()->logical_processor_count; + u32 num_main_threads_clamped = Min(num_async_threads, num_main_threads); + num_async_threads -= num_main_threads_clamped; + String num_async_threads_string = cmd_line_string(&cmdline, str8_lit("async_thread_count")); + if(num_async_threads_string.size != 0) + { + try_u64_from_str8_c_rules(num_async_threads_string, &num_async_threads); + } + num_async_threads = Max(1, num_async_threads); + Barrier barrier = barrier_alloc(num_async_threads); + LaneCtx *lane_ctxs = push_array(scratch.arena, LaneCtx, num_async_threads); + async_threads_count = num_async_threads; + async_threads = push_array(scratch.arena, Thread, async_threads_count); + for EachIndex(idx, num_async_threads) + { + lane_ctxs[idx].lane_idx = idx; + lane_ctxs[idx].lane_count = async_threads_count; + lane_ctxs[idx].barrier = barrier; + lane_ctxs[idx].broadcast_memory = &lane_broadcast_val; + async_threads[idx] = thread_launch(async_thread_entry_point, &lane_ctxs[idx]); + } + } + + //- rjf: call into entry point + entry_point(&cmdline); + + //- rjf: join async threads + ins_atomic_u32_inc_eval(&global_async_exit); + cond_var_broadcast(async_tick_start_cond_var); + for EachIndex(idx, async_threads_count) + { + thread_join(async_threads[idx], max_U64); + } +} + +fn void asyncThreadEntryPoint(void *params) { +} + diff --git a/impl.c b/impl.c new file mode 100644 index 0000000..b2c13fd --- /dev/null +++ b/impl.c @@ -0,0 +1,13 @@ +#ifndef BASE_IMPL_C +#define BASE_IMPL_C + +#include "math.c" +#include "os.c" +#include "memory.c" +#include "serialize.c" +#include "string.c" +#include "tctx.c" +#include "thread.c" +#include "string_chunk.c" + +#endif // BASE_IMPL_C diff --git a/linux_os.c b/linux_os.c new file mode 100644 index 0000000..b3f6f4b --- /dev/null +++ b/linux_os.c @@ -0,0 +1,139 @@ +#include +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include "all.h" + +global pthread_barrier_t linux_thread_barrier; + +fn Barrier osBarrierAlloc(u64 count) { + pthread_barrier_init(&linux_thread_barrier, NULL, count); + Barrier result = {(u64)&linux_thread_barrier}; + return result; +} + +fn void osBarrierRelease(Barrier barrier) { + pthread_barrier_t* addr = (pthread_barrier_t*)barrier.a[0]; + pthread_barrier_destroy(addr); +} + +fn void osBarrierWait(Barrier barrier) { + pthread_barrier_t* addr = (pthread_barrier_t*)barrier.a[0]; + pthread_barrier_wait(addr); +} + +// Time +fn u64 osTimeMicrosecondsNow() { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ((u64)ts.tv_sec * 1000000) + ((u64)ts.tv_nsec / 1000000); +} + +#define MICROSECONDS_PER_SECOND 1000000 +#define NANOSECONDS_PER_MICROSECOND 1000 +fn void osSleepMicroseconds(u32 t) { + struct timespec ts = { t / MICROSECONDS_PER_SECOND, (t % MICROSECONDS_PER_SECOND)*NANOSECONDS_PER_MICROSECOND }; + nanosleep(&ts, NULL); +} + +// Files +fn bool osFileExists(String filename) { + bool result = access((str)filename.bytes, F_OK) == 0; + return result; +} + +fn String osFileRead(Arena* arena, ptr filepath) { + struct stat st; + stat(filepath, &st); + String result = { st.st_size, st.st_size, 0 }; + result.bytes = arenaAlloc(arena, st.st_size); + + size_t handle = open(filepath, O_RDWR, S_IRUSR | S_IRGRP | S_IROTH); + read(handle, result.bytes, st.st_size); + close(handle); + + return result; +} + +fn bool osFileCreate(String filename) { + /* + M_Scratch scratch = scratch_get(); + string nt = str_copy(&scratch.arena, filename); + bool result = true; + size_t handle = open((const char*) nt.str, O_RDWR | O_CREAT, S_IRUSR | S_IRGRP | S_IROTH); + if (handle == -1) { + result = false; + } + scratch_return(&scratch); + close(handle); + return true; + */ + bool result = true; + size_t handle = open((str)filename.bytes, O_RDWR | O_CREAT, S_IRUSR | S_IRGRP | S_IROTH); + if (handle == -1) { + result = false; + } + if (close(handle) == -1) { + result = false; + } + return result; +} + +fn bool osFileCreateWrite(String filename, String data) { + /* + M_Scratch scratch = scratch_get(); + string nt = str_copy(&scratch.arena, filename); + b32 result = true; + size_t handle = + open((const char*) nt.str, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IRGRP | S_IROTH); + if (handle == -1) result = false; + write(handle, data.str, data.size); + close(handle); + scratch_return(&scratch); + return result; + */ + bool result = true; + size_t handle = open( + (str)filename.bytes, + O_RDWR | O_CREAT | O_TRUNC, + S_IRUSR | S_IRGRP | S_IROTH + ); + if (handle == -1) result = false; + write(handle, data.bytes, data.length); + close(handle); + return result; +} + +fn bool osFileWrite(String filename, String data) { + /* + M_Scratch scratch = scratch_get(); + string nt = str_copy(&scratch.arena, filename); + b32 result = true; + size_t handle = + open((const char*) nt.str, O_RDWR | O_TRUNC, S_IRUSR | S_IRGRP | S_IROTH); + if (handle == -1) result = false; + write(handle, data.str, data.size); + close(handle); + */ + bool result = true; + size_t handle = open((str) filename.bytes, O_RDWR | O_TRUNC, S_IRUSR | S_IRGRP | S_IROTH); + if (handle == -1) result = false; + write(handle, data.bytes, data.length); + close(handle); + return result; +} + + +// Misc +fn void osDebugPrint(bool debug_mode, const char * format, ... ) { + if (debug_mode) { + va_list args; + va_start(args, format); + vprintf(format, args); + va_end(args); + } +} diff --git a/mac_os.c b/mac_os.c new file mode 100644 index 0000000..ede0437 --- /dev/null +++ b/mac_os.c @@ -0,0 +1,136 @@ +#include +#include +#include +#include +#include +#include +#include +#include "all.h" +#include "pthread_barrier.h" + +global pthread_barrier_t macos_thread_barrier; + +fn Barrier osBarrierAlloc(u64 count) { + pthread_barrier_init(&macos_thread_barrier, NULL, count); + Barrier result = {(u64)&macos_thread_barrier}; + return result; +} + +fn void osBarrierRelease(Barrier barrier) { + pthread_barrier_t* addr = (pthread_barrier_t*)barrier.a[0]; + pthread_barrier_destroy(addr); +} + +fn void osBarrierWait(Barrier barrier) { + pthread_barrier_t* addr = (pthread_barrier_t*)barrier.a[0]; + pthread_barrier_wait(addr); +} + +// Time +fn u64 osTimeMicrosecondsNow() { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC_RAW, &ts); + return ((u64)ts.tv_sec * 1000000) + ((u64)ts.tv_nsec / 1000000); +} + +fn void osSleepMicroseconds(u32 t) { + usleep(t); +} + +// Files +fn bool osFileExists(String filename) { + bool result = access((str)filename.bytes, F_OK) == 0; + return result; +} + +fn String osFileRead(Arena* arena, ptr filepath) { + struct stat st; + stat(filepath, &st); + String result = { st.st_size, st.st_size, 0 }; + result.bytes = arenaAlloc(arena, st.st_size); + + size_t handle = open(filepath, O_RDWR, S_IRUSR | S_IRGRP | S_IROTH); + read(handle, result.bytes, st.st_size); + close(handle); + + return result; +} + +fn bool osFileCreate(String filename) { + /* + M_Scratch scratch = scratch_get(); + string nt = str_copy(&scratch.arena, filename); + bool result = true; + size_t handle = open((const char*) nt.str, O_RDWR | O_CREAT, S_IRUSR | S_IRGRP | S_IROTH); + if (handle == -1) { + result = false; + } + scratch_return(&scratch); + close(handle); + return true; + */ + bool result = true; + size_t handle = open((str)filename.bytes, O_RDWR | O_CREAT, S_IRUSR | S_IRGRP | S_IROTH); + if (handle == -1) { + result = false; + } + if (close(handle) == -1) { + result = false; + } + return result; +} + +fn bool osFileCreateWrite(String filename, String data) { + /* + M_Scratch scratch = scratch_get(); + string nt = str_copy(&scratch.arena, filename); + b32 result = true; + size_t handle = + open((const char*) nt.str, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IRGRP | S_IROTH); + if (handle == -1) result = false; + write(handle, data.str, data.size); + close(handle); + scratch_return(&scratch); + return result; + */ + bool result = true; + size_t handle = open( + (str)filename.bytes, + O_RDWR | O_CREAT | O_TRUNC, + S_IRUSR | S_IRGRP | S_IROTH + ); + if (handle == -1) result = false; + write(handle, data.bytes, data.length); + close(handle); + return result; +} + +fn bool osFileWrite(String filename, String data) { + /* + M_Scratch scratch = scratch_get(); + string nt = str_copy(&scratch.arena, filename); + b32 result = true; + size_t handle = + open((const char*) nt.str, O_RDWR | O_TRUNC, S_IRUSR | S_IRGRP | S_IROTH); + if (handle == -1) result = false; + write(handle, data.str, data.size); + close(handle); + */ + bool result = true; + size_t handle = open((str) filename.bytes, O_RDWR | O_TRUNC, S_IRUSR | S_IRGRP | S_IROTH); + if (handle == -1) result = false; + write(handle, data.bytes, data.length); + close(handle); + return result; +} + + +// Misc +fn void osDebugPrint(bool debug_mode, const char * format, ... ) { + if (debug_mode) { + va_list args; + va_start(args, format); + vprintf(format, args); + va_end(args); + } +} diff --git a/math.c b/math.c new file mode 100644 index 0000000..1035af0 --- /dev/null +++ b/math.c @@ -0,0 +1,68 @@ +#include "all.h" + +fn Range1u64 range1u64Create(u64 min, u64 max) { + Range1u64 result = { + .min = min, + .max = max + }; + if (result.min > result.max) { + result.max = min; + result.min = max; + } + return result; +} + +fn Range1u64 mRangeFromNIdxMCount(u64 n_idx, u64 n_count, u64 m_count) { + u64 main_idxes_per_lane = m_count / n_count; + u64 leftover_idxes_count = m_count - main_idxes_per_lane * n_count; + u64 leftover_idxes_before_this_lane_count = Min(n_idx, leftover_idxes_count); + u64 lane_base_idx = n_idx*main_idxes_per_lane + leftover_idxes_before_this_lane_count; + u64 lane_base_idx__clamped = Min(lane_base_idx, m_count); + u64 lane_opl_idx = lane_base_idx__clamped + main_idxes_per_lane + ((n_idx < leftover_idxes_count) ? 1 : 0); + u64 lane_opl_idx__clamped = Min(lane_opl_idx, m_count); + Range1u64 result = range1u64Create(lane_base_idx__clamped, lane_opl_idx__clamped); + return result; +} + +void u32Swap(u32* a, u32* b) { + int t = *a; + *a = *b; + *b = t; +} + +u32 u32ArrPartition(u32 arr[], u32 low, u32 high) { + u32 pivot = arr[high]; + u32 i = low - 1; + + for (u32 j = low; j < high; j++) { + if (arr[j] < pivot) { + i++; + u32Swap(&arr[i], &arr[j]); + } + } + u32Swap(&arr[i + 1], &arr[high]); + return i + 1; +} + +fn void u32Quicksort(u32 arr[], u32 low, u32 high) { + if (low < high) { + u32 pi = u32ArrPartition(arr, low, high); + if (pi != 0) { + u32Quicksort(arr, low, pi - 1); + } + u32Quicksort(arr, pi + 1, high); + } +} + +fn void u32ReverseArray(u32 arr[], u32 size) { + u32 start = 0; + u32 end = size - 1; + while (start < end) { + u32 temp = arr[start]; + arr[start] = arr[end]; + arr[end] = temp; + start++; + end--; + } +} + diff --git a/memory.c b/memory.c new file mode 100644 index 0000000..b0655ac --- /dev/null +++ b/memory.c @@ -0,0 +1,100 @@ +#include "all.h" + +fn u64 alignForward(u64 pointer, u64 align) { + u64 p, modulo; + assert(isPowerOfTwo(align)); + p = pointer; + // Same as (p % a) but faster as 'a' is a power of two + modulo = p & (align-1); + if (modulo != 0) { + // If 'p' address is not aligned, push the address to the + // next value which is aligned + p += align - modulo; + } + return p; +} + +fn void* arenaAlloc(Arena* arena, u64 size) { + void* memory = 0; + size = alignForward(size, DEFAULT_ALIGNMENT); + if (arena->alloc_position + size > arena->commit_position) { + if (!arena->static_size) { + u64 commit_size = size; + + commit_size += ARENA_COMMIT_SIZE - 1; + commit_size -= commit_size % ARENA_COMMIT_SIZE; + + if (arena->commit_position < arena->max) { + osMemoryCommit(arena->memory + arena->commit_position, commit_size); + arena->commit_position += commit_size; + } else { + assert(0 && "Arena is out of memory"); + } + } else { + assert(0 && "Static-Size Arena is out of memory"); + } + } + + memory = arena->memory + arena->alloc_position; + arena->alloc_position += size; + return memory; +} + +fn void* arenaAllocZero(Arena* a, u64 size) { + void* result = arenaAlloc(a, size); + MemoryZero(result, size); + return result; +} + +fn void* arenaAllocArraySized(Arena* arena, u64 elem_size, u64 count) { + return arenaAlloc(arena, elem_size * count); +} + +fn void arenaDealloc(Arena* arena, u64 size) { + if (size > arena->alloc_position) size = arena->alloc_position; + arena->alloc_position -= size; +} + +fn void arenaInit(Arena* arena) { + MemoryZeroStruct(arena, Arena); + arena->max = ARENA_MAX; + arena->memory = osMemoryReserve(arena->max); + arena->alloc_position = 0; + arena->commit_position = 0; + arena->static_size = false; +} + +// WARNING: segfault problems with this approach +fn void arenaInitStatic(Arena* arena, u64 max) { + MemoryZeroStruct(arena, Arena); + arena->max = max; + arena->memory = osMemoryReserve(arena->max); + osMemoryCommit(arena->memory, max + (max % ARENA_COMMIT_SIZE)); + arena->alloc_position = 0; + arena->commit_position = 0; + arena->static_size = true; +} + + +fn void arenaClear(Arena* a) { + a->alloc_position = 0; +} + +fn void arenaFree(Arena* a) { + osMemoryRelease(a->memory, a->max); +} + +ScratchMem scratchGet(void) { + ThreadContext* ctx = (ThreadContext*)osThreadContextGet(); + return tctxScratchGet(ctx); +} + +void scratchReset(ScratchMem* scratch) { + ThreadContext* ctx = (ThreadContext*)osThreadContextGet(); + tctxScratchReset(ctx, scratch); +} + +void scratchReturn(ScratchMem* scratch) { + ThreadContext* ctx = (ThreadContext*)osThreadContextGet(); + tctxScratchReturn(ctx, scratch); +} diff --git a/os.c b/os.c new file mode 100644 index 0000000..d979442 --- /dev/null +++ b/os.c @@ -0,0 +1,12 @@ +#include "all.h" + +#if OS_WINDOWS +# include "win32_os.c" +#elif OS_LINUX +# include "linux_os.c" +# include "unix_os.c" +#elif OS_MAC +# include "pthread_barrier.c" +# include "mac_os.c" +# include "unix_os.c" +#endif diff --git a/pthread_barrier.c b/pthread_barrier.c new file mode 100644 index 0000000..495cbc9 --- /dev/null +++ b/pthread_barrier.c @@ -0,0 +1,102 @@ +/* (C) Copyright 2019 Robert Sauter + * SPDX-License-Identifier: MIT + */ + +/** Pthread-barrier implementation for macOS using a pthread mutex and condition variable */ + + +#include "pthread_barrier.h" +#include + +#ifdef __APPLE__ + +int pthread_barrier_init(pthread_barrier_t *__restrict barrier, + const pthread_barrierattr_t * __restrict attr, + unsigned count) { + if (count == 0) { + return EINVAL; + } + + int ret; + + pthread_condattr_t condattr; + pthread_condattr_init(&condattr); + if (attr) { + int pshared; + ret = pthread_barrierattr_getpshared(attr, &pshared); + if (ret) { + return ret; + } + ret = pthread_condattr_setpshared(&condattr, pshared); + if (ret) { + return ret; + } + } + + ret = pthread_mutex_init(&barrier->mutex, attr); + if (ret) { + return ret; + } + + ret = pthread_cond_init(&barrier->cond, &condattr); + if (ret) { + pthread_mutex_destroy(&barrier->mutex); + return ret; + } + + barrier->count = count; + barrier->left = count; + barrier->round = 0; + + return 0; +} + +int pthread_barrier_destroy(pthread_barrier_t *barrier) { + if (barrier->count == 0) { + return EINVAL; + } + + barrier->count = 0; + int rm = pthread_mutex_destroy(&barrier->mutex); + int rc = pthread_cond_destroy(&barrier->cond); + return rm ? rm : rc; +} + + +int pthread_barrier_wait(pthread_barrier_t *barrier) { + pthread_mutex_lock(&barrier->mutex); + if (--barrier->left) { + unsigned round = barrier->round; + do { + pthread_cond_wait(&barrier->cond, &barrier->mutex); + } while (round == barrier->round); + pthread_mutex_unlock(&barrier->mutex); + return 0; + } else { + barrier->round += 1; + barrier->left = barrier->count; + pthread_cond_broadcast(&barrier->cond); + pthread_mutex_unlock(&barrier->mutex); + return PTHREAD_BARRIER_SERIAL_THREAD; + } +} + + +int pthread_barrierattr_init(pthread_barrierattr_t *attr) { + return pthread_mutexattr_init(attr); +} + +int pthread_barrierattr_destroy(pthread_barrierattr_t *attr) { + return pthread_mutexattr_destroy(attr); +} + +int pthread_barrierattr_getpshared(const pthread_barrierattr_t *__restrict attr, + int *__restrict pshared) { + return pthread_mutexattr_getpshared(attr, pshared); +} + +int pthread_barrierattr_setpshared(pthread_barrierattr_t *attr, int pshared) { + return pthread_mutexattr_setpshared(attr, pshared); +} + +#endif /* __APPLE */ \ No newline at end of file diff --git a/pthread_barrier.h b/pthread_barrier.h new file mode 100644 index 0000000..0db4acd --- /dev/null +++ b/pthread_barrier.h @@ -0,0 +1,49 @@ +/** Pthread-barrier implementation for macOS */ +#ifndef PTHREAD_BARRIER_H +#define PTHREAD_BARRIER_H + +#include + +#ifdef __APPLE__ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef PTHREAD_BARRIER_SERIAL_THREAD +# define PTHREAD_BARRIER_SERIAL_THREAD -1 +#endif + +typedef pthread_mutexattr_t pthread_barrierattr_t; + +/* structure for internal use that should be considered opaque */ +typedef struct { + pthread_mutex_t mutex; + pthread_cond_t cond; + unsigned count; + unsigned left; + unsigned round; +} pthread_barrier_t; + +int pthread_barrier_init(pthread_barrier_t *__restrict barrier, + const pthread_barrierattr_t * __restrict attr, + unsigned count); +int pthread_barrier_destroy(pthread_barrier_t *barrier); + +int pthread_barrier_wait(pthread_barrier_t *barrier); + +int pthread_barrierattr_init(pthread_barrierattr_t *attr); +int pthread_barrierattr_destroy(pthread_barrierattr_t *attr); +int pthread_barrierattr_getpshared(const pthread_barrierattr_t *__restrict attr, + int *__restrict pshared); +int pthread_barrierattr_setpshared(pthread_barrierattr_t *attr, + int pshared); + + +#ifdef __cplusplus +} +#endif + +#endif /* __APPLE__ */ + +#endif /* PTHREAD_BARRIER_H */ diff --git a/serialize.c b/serialize.c new file mode 100644 index 0000000..93e0815 --- /dev/null +++ b/serialize.c @@ -0,0 +1,89 @@ +#include "all.h" + +fn u64 writeU64ToBufferLE(u8* buffer, u64 value) { + buffer[0] = (u8)(value & 0xFF); + buffer[1] = (u8)((value >> 8) & 0xFF); + buffer[2] = (u8)((value >> 16) & 0xFF); + buffer[3] = (u8)((value >> 24) & 0xFF); + buffer[4] = (u8)((value >> 32) & 0xFF); + buffer[5] = (u8)((value >> 40) & 0xFF); + buffer[6] = (u8)((value >> 48) & 0xFF); + buffer[7] = (u8)((value >> 56) & 0xFF); + return 8;// number of bytes written +} + +fn u64 writeU32ToBufferLE(u8* buffer, u32 value) { + buffer[0] = (u8)(value & 0xFF); + buffer[1] = (u8)((value >> 8) & 0xFF); + buffer[2] = (u8)((value >> 16) & 0xFF); + buffer[3] = (u8)((value >> 24) & 0xFF); + return 4;// number of bytes written +} + +fn u64 writeI32ToBufferLE(u8* buffer, i32 value) { + buffer[0] = (u8)(value & 0xFF); + buffer[1] = (u8)((value >> 8) & 0xFF); + buffer[2] = (u8)((value >> 16) & 0xFF); + buffer[3] = (u8)((value >> 24) & 0xFF); + return 4;// number of bytes written +} + +fn u64 writeU16ToBufferLE(u8* buffer, u16 value) { + buffer[0] = (u8)(value & 0xFF); + buffer[1] = (u8)((value >> 8) & 0xFF); + return 2;// number of bytes written +} + +fn u64 writeF32ToBufferLE(u8* buffer, f32 value) { + u32 bits; + memcpy(&bits, &value, sizeof(u32)); // reinterpret float bits as integer + + buffer[0] = (u8)(bits & 0xFF); + buffer[1] = (u8)((bits >> 8) & 0xFF); + buffer[2] = (u8)((bits >> 16) & 0xFF); + buffer[3] = (u8)((bits >> 24) & 0xFF); + return 4;// number of bytes written +} + +fn u64 readU64FromBufferLE(u8 *buffer) { + return (u64)buffer[0] | + ((u64)buffer[1] << 8) | + ((u64)buffer[2] << 16) | + ((u64)buffer[3] << 24) | + ((u64)buffer[4] << 32) | + ((u64)buffer[5] << 40) | + ((u64)buffer[6] << 48) | + ((u64)buffer[7] << 56); +} + +fn u32 readU32FromBufferLE(u8 *buffer) { + return (u32)buffer[0] | + ((u32)buffer[1] << 8) | + ((u32)buffer[2] << 16) | + ((u32)buffer[3] << 24); +} + +fn i32 readI32FromBufferLE(u8 *buffer) { + return (i32)buffer[0] | + ((i32)buffer[1] << 8) | + ((i32)buffer[2] << 16) | + ((i32)buffer[3] << 24); +} + +fn u16 readU16FromBufferLE(u8 *buffer) { + return (u16)buffer[0] | + ((u16)buffer[1] << 8); +} + +fn f32 readF32FromBufferLE(u8 *buf) { + u32 bits = 0; + bits |= (u32)(u8)buf[0] << 0; + bits |= (u32)(u8)buf[1] << 8; + bits |= (u32)(u8)buf[2] << 16; + bits |= (u32)(u8)buf[3] << 24; + + f32 value; + memcpy(&value, &bits, sizeof(f32)); + return value; +} + diff --git a/string.c b/string.c new file mode 100644 index 0000000..d475b7e --- /dev/null +++ b/string.c @@ -0,0 +1,216 @@ +#include "all.h" + +fn bool stringsEq(String* a, String* b) { + if (a->length != b->length) { + return false; + } + for (i32 i = 0; i < a->length; i++) { + if (a->bytes[i] != b->bytes[i]) { + return false; + } + } + return true; +} + +fn bool cStringEqString(str a, String* b) { + if (strlen(a) != b->length) { + return false; + } + for (i32 i = 0; i < b->length; i++) { + if (a[i] != b->bytes[i]) { + return false; + } + } + return true; +} + +fn Utf8Character classifyUtf8Character(u8 c) { + /*two_byte utf8 starts with 1100. 192 + three_byte utf8 starts with 1110. 224 + four_byte utf8 starts with 1111. 240*/ + if (c <= 127) { + return Utf8CharacterAscii; + } else if (c >= 192 && c < 224) { + return Utf8CharacterTwoByte; + } else if (c >= 224 && c < 240) { + return Utf8CharacterThreeByte; + } else if (c >= 240) { + return Utf8CharacterFourByte; + } else { + assert(false && "Not a valid utf8 starting byte"); + return Utf8Character_Count; + } +} + +fn bool isUtf8Ascii(u8 c) { + return classifyUtf8Character(c) == Utf8CharacterAscii; +} + +fn bool isUtf8TwoByte(u8 c) { + return classifyUtf8Character(c) == Utf8CharacterTwoByte; +} + +fn bool isUtf8ThreeByte(u8 c) { + return classifyUtf8Character(c) == Utf8CharacterThreeByte; +} + +fn bool isUtf8FourByte(u8 c) { + return classifyUtf8Character(c) == Utf8CharacterFourByte; +} + +fn u8 lowerAscii(u8 c) { + if (c >= 65 && c <= 90) { + return c + 32; + } + return c; +} + +fn u8 upperAscii(u8 c) { + if (c >= 97 && c <= 122) { + return c - 32; + } + return c; +} + +fn bool isAlphaUnderscoreSpace(u8 c) { + return ((c >= 'A' && c <= 'Z') + || (c >= 'a' && c <= 'z') + || c == ' ' + || c == '_'); +} + +fn bool isSimplePrintable(u8 c) { + return (c >= ' ' && c <= '~'); +} + +typedef struct StrDecode { + u32 codepoint; + u32 size; +} StrDecode; + +fn StrDecode strDecodeUTF8(u8 *str, u32 cap){ + u8 length[] = { + 1, 1, 1, 1, // 000xx + 1, 1, 1, 1, + 1, 1, 1, 1, + 1, 1, 1, 1, + 0, 0, 0, 0, // 100xx + 0, 0, 0, 0, + 2, 2, 2, 2, // 110xx + 3, 3, // 1110x + 4, // 11110 + 0, // 11111 + }; + u8 first_byte_mask[] = { 0, 0x7F, 0x1F, 0x0F, 0x07 }; + u8 final_shift[] = { 0, 18, 12, 6, 0 }; + + StrDecode result = {0}; + if (cap > 0){ + result.codepoint = '#'; + result.size = 1; + + u8 byte = str[0]; + u8 l = length[byte >> 3]; + if (0 < l && l <= cap){ + u32 cp = (byte & first_byte_mask[l]) << 18; + switch (l){ + case 4: cp |= ((str[3] & 0x3F) << 0); + case 3: cp |= ((str[2] & 0x3F) << 6); + case 2: cp |= ((str[1] & 0x3F) << 12); + default: break; + } + cp >>= final_shift[l]; + + result.codepoint = cp; + result.size = l; + } + } + + return result; +} + +fn u32 strEncodeUTF8(u8 *dst, u32 codepoint){ + u32 size = 0; + if (codepoint < (1 << 8)) { + dst[0] = codepoint; + size = 1; + } else if (codepoint < (1 << 11)) { + dst[0] = 0xC0 | (codepoint >> 6); + dst[1] = 0x80 | (codepoint & 0x3F); + size = 2; + } + else if (codepoint < (1 << 16)) { + dst[0] = 0xE0 | (codepoint >> 12); + dst[1] = 0x80 | ((codepoint >> 6) & 0x3F); + dst[2] = 0x80 | (codepoint & 0x3F); + size = 3; + } else if (codepoint < (1 << 21)) { + dst[0] = 0xF0 | (codepoint >> 18); + dst[1] = 0x80 | ((codepoint >> 12) & 0x3F); + dst[2] = 0x80 | ((codepoint >> 6) & 0x3F); + dst[3] = 0x80 | (codepoint & 0x3F); + size = 4; + } else { + dst[0] = '#'; + size = 1; + } + return size; +} + +fn StrDecode strDecodeUTF16(u16 *str, u32 cap){ + StrDecode result = {'#', 1}; + u16 x = str[0]; + if (x < 0xD800 || 0xDFFF < x) { + result.codepoint = x; + } else if (cap >= 2) { + u16 y = str[1]; + if (0xD800 <= x && x < 0xDC00 && + 0xDC00 <= y && y < 0xE000 + ) { + u16 xj = x - 0xD800; + u16 yj = y - 0xDc00; + u32 xy = (xj << 10) | yj; + result.codepoint = xy + 0x10000; + result.size = 2; + } + } + return result; +} + +fn u32 strEncodeUTF16(u16 *dst, u32 codepoint){ + u32 size = 0; + if (codepoint < 0x10000) { + dst[0] = codepoint; + size = 1; + } else { + u32 cpj = codepoint - 0x10000; + dst[0] = (cpj >> 10) + 0xD800; + dst[1] = (cpj & 0x3FF) + 0xDC00; + size = 2; + } + return(size); +} + +fn StringUTF16Const str16FromStr8(Arena* arena, String string) { + u16* memory = arenaAllocArray(arena, u16, string.length * 2 + 1); + + u16* dptr = memory; + u8* ptr = (u8*)string.bytes; + u8* opl = (u8*)string.bytes + string.length; + for (; ptr < opl;){ + StrDecode decode = strDecodeUTF8(ptr, (u64)(opl - ptr)); + u32 enc_size = strEncodeUTF16(dptr, decode.codepoint); + ptr += decode.size; + dptr += enc_size; + } + + *dptr = 0; + + u64 alloc_count = string.length*2 + 1; + u64 string_count = (u64)(dptr - memory); + u64 unused_count = alloc_count - string_count - 1; + arenaDealloc(arena, unused_count * sizeof(*memory)); + + StringUTF16Const result = { memory, string_count }; + return result; +} diff --git a/string_chunk.c b/string_chunk.c new file mode 100644 index 0000000..de25a55 --- /dev/null +++ b/string_chunk.c @@ -0,0 +1,162 @@ +#include "all.h" + +fn StringChunkList allocStringChunkList(StringArena* a, String string) { + StringChunkList result = {0}; + u64 needed_chunks = (string.length + (STRING_CHUNK_PAYLOAD_SIZE-1)) / STRING_CHUNK_PAYLOAD_SIZE; + u64 bytes_left = string.length; + u64 string_offset = 0; + lockMutex(&a->mutex); { + for (u32 i = 0; i < needed_chunks; i++) { + StringChunk* chunk = a->first_free_str_chunk; + if (chunk == NULL) { + chunk = (StringChunk*)arenaAlloc(&a->a, sizeof(StringChunk)+STRING_CHUNK_PAYLOAD_SIZE); + } else { + a->first_free_str_chunk = a->first_free_str_chunk->next; + } + chunk->next = NULL; // makes sure we don't have a pointer to any other free_str_chunks + u64 bytes_to_copy = Min(bytes_left, STRING_CHUNK_PAYLOAD_SIZE); + // ryan's impl used chunk+1 which seems like a bug but what do I know he had a working demo + MemoryCopy(chunk+1, string.bytes+string_offset, bytes_to_copy); + QueuePush(result.first, result.last, chunk); + result.count += 1; + result.total_size += bytes_to_copy; + bytes_left -= bytes_to_copy; + string_offset += bytes_to_copy; + } + } unlockMutex(&a->mutex); + return result; +} + +fn void releaseStringChunkList(StringArena* a, StringChunkList* list) { + StringChunk* chunk = list->first; + lockMutex(&a->mutex); { + for (StringChunk* next = NULL; chunk != NULL; chunk = next) { + next = chunk->next; + chunk->next = a->first_free_str_chunk; + a->first_free_str_chunk = chunk; + } + } unlockMutex(&a->mutex); + MemoryZeroStruct(list, StringChunkList); +} + +fn String stringChunkToString(Arena* a, StringChunkList list) { + String result = { + .length = list.total_size, + .capacity = list.total_size + 1, + .bytes = arenaAllocArray(a, u8, list.total_size+1), + }; + // copy the string bytes out of the StringChunkList into the correctly-sized String + StringChunk* chunk = list.first; + for (u32 i = 0; i < list.total_size; i++) { + if (i > 0 && i % STRING_CHUNK_PAYLOAD_SIZE == 0) { + chunk = chunk->next; + } + result.bytes[i] = *((char*)(chunk + 1) + (i%STRING_CHUNK_PAYLOAD_SIZE)); + } + return result; +} + +fn void stringChunkListAppend(StringArena* a, StringChunkList* list, String string) { + u64 capacity = list->count * STRING_CHUNK_PAYLOAD_SIZE; + u64 remaining_space = capacity - list->total_size; + u64 bytes_in_last_chunk = (list->total_size % STRING_CHUNK_PAYLOAD_SIZE); + if (string.length < remaining_space) { + MemoryCopy(((u8*)list->last)+sizeof(StringChunk*)+(bytes_in_last_chunk), string.bytes, string.length); + list->total_size += string.length; + } else { + u64 string_offset = 0; + u64 bytes_left = string.length; + u64 bytes_to_copy = Min(bytes_left, remaining_space); + if (remaining_space > 0) { + // fill up the last chunk + MemoryCopy(((u8*)(list->last+1))+(bytes_in_last_chunk), string.bytes, bytes_to_copy); + bytes_left -= bytes_to_copy; + string_offset += bytes_to_copy; + list->total_size += bytes_to_copy; + } + + // then figure out how many more chunks we need + u64 needed_chunks = (bytes_left + (STRING_CHUNK_PAYLOAD_SIZE-1)) / STRING_CHUNK_PAYLOAD_SIZE; + lockMutex(&a->mutex); { + for (u32 i = 0; i < needed_chunks; i++) { + StringChunk* chunk = a->first_free_str_chunk; + if (chunk == NULL) { + chunk = (StringChunk*)arenaAlloc(&a->a, sizeof(StringChunk)+STRING_CHUNK_PAYLOAD_SIZE); + } else { + a->first_free_str_chunk = a->first_free_str_chunk->next; + } + chunk->next = NULL; // makes sure we don't have a pointer to any other free_str_chunks + bytes_to_copy = Min(bytes_left, STRING_CHUNK_PAYLOAD_SIZE); + // ryan's impl used chunk+1 which seems like a bug but what do I know he had a working demo + MemoryCopy(chunk+1, string.bytes+string_offset, bytes_to_copy); + QueuePush(list->first, list->last, chunk); + list->count += 1; + list->total_size += bytes_to_copy; + bytes_left -= bytes_to_copy; + string_offset += bytes_to_copy; + } + } unlockMutex(&a->mutex); + } +} + +fn void stringChunkListDeleteLast(StringArena* a, StringChunkList* list) { + if (list->total_size == 0) return; + + u64 capacity = list->count * STRING_CHUNK_PAYLOAD_SIZE; + u64 remaining_space = capacity - list->total_size; + u64 bytes_in_last_chunk = STRING_CHUNK_PAYLOAD_SIZE - remaining_space; + + bool theres_only_one_chunk = list->total_size <= STRING_CHUNK_PAYLOAD_SIZE; + bool last_chunk_only_has_one_byte = bytes_in_last_chunk == 1; + + if (theres_only_one_chunk) { + ptr char_to_delete = ((char*)(list->last + 1) + (list->total_size-1)); + char_to_delete[0] = '\0'; + } else if (last_chunk_only_has_one_byte) { + // remove the last chunk when it's not the only chunk + StringChunk* second_to_last_chunk = list->first; + while (second_to_last_chunk->next != list->last && second_to_last_chunk->next != NULL) { + second_to_last_chunk = second_to_last_chunk->next; + } + second_to_last_chunk->next = NULL; + lockMutex(&a->mutex); { + list->last->next = a->first_free_str_chunk; + a->first_free_str_chunk = list->last; + } unlockMutex(&a->mutex); + list->last = second_to_last_chunk; + list->count -= 1; + } else { + ptr char_to_delete = ((char*)(list->last + 1) + ((list->total_size-1) % STRING_CHUNK_PAYLOAD_SIZE)); + char_to_delete[0] = '\0'; + } + list->total_size -= 1; +} + +fn StringChunkList stringChunkListInit(StringArena* a) { + StringChunkList result = {0}; + lockMutex(&a->mutex); { + StringChunk* chunk = a->first_free_str_chunk; + if (chunk == NULL) { + chunk = (StringChunk*)arenaAlloc(&a->a, sizeof(StringChunk)+STRING_CHUNK_PAYLOAD_SIZE); + } else { + a->first_free_str_chunk = a->first_free_str_chunk->next; + } + chunk->next = NULL; // makes sure we don't have a pointer to any other free_str_chunks + MemoryZero(chunk+1, STRING_CHUNK_PAYLOAD_SIZE); + QueuePush(result.first, result.last, chunk); + result.count += 1; + } unlockMutex(&a->mutex); + return result; +} + +fn void stringChunkCopyToBuffer(StringChunkList* list, u8* buffer, u32 len) { + assert(list->total_size <= len); + + StringChunk* chunk = list->first; + for (u32 i = 0; i < list->total_size; i++) { + if (i > 0 && i % STRING_CHUNK_PAYLOAD_SIZE == 0) { + chunk = chunk->next; + } + buffer[i] = *((char*)(chunk + 1) + (i%STRING_CHUNK_PAYLOAD_SIZE)); + } +} diff --git a/tctx.c b/tctx.c new file mode 100644 index 0000000..aaa9b00 --- /dev/null +++ b/tctx.c @@ -0,0 +1,83 @@ +#include "all.h" + +void tctxInit(ThreadContext* ctx) { + arenaInit(&ctx->arena); + osThreadContextSet(ctx); +} + +void tctxFree(ThreadContext* ctx) { + arenaFree(&ctx->arena); + osThreadContextSet(ctx); +} + +fn ThreadContext *tctxSelected(void) { + return (ThreadContext*)osThreadContextGet(); +} + +ScratchMem tctxScratchGet(ThreadContext* ctx) { + if (!ctx->free_list) { + ScratchMem scratch = {0}; + + scratch.arena.memory = arenaAlloc(&ctx->arena, M_SCRATCH_SIZE); + scratch.arena.max = M_SCRATCH_SIZE; + scratch.arena.alloc_position = 0; + scratch.arena.commit_position = M_SCRATCH_SIZE; + scratch.arena.static_size = true; + + ctx->max_created++; + return scratch; + } else { + ScratchMem scratch = {0}; + + scratch.arena.memory = (u8*) ctx->free_list; + scratch.arena.max = M_SCRATCH_SIZE; + scratch.arena.alloc_position = 0; + scratch.arena.commit_position = M_SCRATCH_SIZE; + scratch.arena.static_size = true; + + ctx->free_list = ctx->free_list->next; + return scratch; + } +} + +void tctxScratchReset(ThreadContext* ctx, ScratchMem* scratch) { + scratch->arena.alloc_position = 0; +} + +void tctxScratchReturn(ThreadContext* ctx, ScratchMem* scratch) { + ScratchFreeListNode* prev_head = ctx->free_list; + ctx->free_list = (ScratchFreeListNode*) scratch->arena.memory; + ctx->free_list->next = prev_head; +} + +fn LaneCtx tctxSetLaneCtx(LaneCtx lane_ctx) { + ThreadContext *tctx = tctxSelected(); + LaneCtx restore = tctx->lane_ctx; + tctx->lane_ctx = lane_ctx; + return restore; +} + +fn void tctxLaneBarrierWait(void *broadcast_ptr, u64 broadcast_size, u64 broadcast_src_lane_idx) { + ThreadContext *tctx = tctxSelected(); + + // broadcasting -> copy to broadcast memory on source lane + u64 broadcast_size_clamped = Min(broadcast_size, sizeof(tctx->lane_ctx.broadcast_memory[0])); + if(broadcast_ptr != 0 && LaneIdx() == broadcast_src_lane_idx) { + MemoryCopy(tctx->lane_ctx.broadcast_memory, broadcast_ptr, broadcast_size_clamped); + } + + // all cases: barrier + osBarrierWait(tctx->lane_ctx.barrier); + + // broadcasting -> copy from broadcast memory on destination lanes + if(broadcast_ptr != 0 && LaneIdx() != broadcast_src_lane_idx) + { + MemoryCopy(broadcast_ptr, tctx->lane_ctx.broadcast_memory, broadcast_size_clamped); + } + + // broadcasting -> barrier on all lanes + if(broadcast_ptr != 0) + { + osBarrierWait(tctx->lane_ctx.barrier); + } +} diff --git a/thread.c b/thread.c new file mode 100644 index 0000000..587a71d --- /dev/null +++ b/thread.c @@ -0,0 +1,36 @@ +#include "all.h" + +Thread spawnThread(void * (*threadFn)(void *), void* thread_arg) { + pthread_t thread; + pthread_create(&thread, NULL, threadFn, thread_arg); + Thread result = { thread }; + return result; +} + +Mutex newMutex() { + Mutex result = { PTHREAD_MUTEX_INITIALIZER }; + return result; +} + +Cond newCond() { + Cond result = { 0 }; + pthread_cond_init(&result.cond, NULL); + return result; +} + +void lockMutex(Mutex* m) { + pthread_mutex_lock(&m->mutex); +} + +void unlockMutex(Mutex* m) { + pthread_mutex_unlock(&m->mutex); +} + +void signalCond(Cond* cond) { + pthread_cond_signal(&cond->cond); +} + +void waitForCondSignal(Cond* cond, Mutex* mutex) { + pthread_cond_wait(&cond->cond, &mutex->mutex); +} + diff --git a/thread_context.h b/thread_context.h new file mode 100644 index 0000000..c18d88d --- /dev/null +++ b/thread_context.h @@ -0,0 +1,114 @@ +#include "types.h" +#include "memory.h" +#ifndef BASE_THREAD_CONTEXT_H +#define BASE_THREAD_CONTEXT_H + +typedef struct LaneCtx { + u64 lane_idx; + u64 lane_count; + Barrier barrier; + u64 *broadcast_memory; +} LaneCtx; + +typedef struct AccessPt AccessPt; +struct AccessPt +{ + u64 access_refcount; + u64 last_time_touched_us; + u64 last_update_idx_touched; +}; + +typedef struct AccessPtExpireParams AccessPtExpireParams; +struct AccessPtExpireParams +{ + u64 time; + u64 update_idxs; +}; + +typedef struct Touch Touch; +struct Touch +{ + Touch *next; + AccessPt *pt; + CondVar cv; +}; + +typedef struct Access Access; +struct Access +{ + Access *next; + Touch *top_touch; +}; + +typedef struct TCtx { + // scratch arenas + Arena *arenas[2]; + u8 thread_name[32]; + u64 thread_name_size; + + LaneCtx lane_ctx; + + // source location info + char *file_name; + u64 line_number; + + // accesses + Arena *access_arena; + Access *free_access; + Touch *free_touch; + + u64 *progress_counter_ptr; + u64 *progress_target_ptr; +} TCtx; + +//////////////////////////////// +// Thread Context Functions + +// thread-context allocation & selection +fn TCtx *tctxAlloc(void); +fn void tctxRelease(TCtx *tctx); +fn void tctxSelect(TCtx *tctx); +fn TCtx *tctxSelected(void); + +//- rjf: scratch arenas +internal Arena *tctx_get_scratch(Arena **conflicts, U64 count); +#define scratch_begin(conflicts, count) temp_begin(tctx_get_scratch((conflicts), (count))) +#define scratch_end(scratch) temp_end(scratch) + +//- rjf: lane metadata +internal LaneCtx tctx_set_lane_ctx(LaneCtx lane_ctx); +internal void tctx_lane_barrier_wait(void *broadcast_ptr, U64 broadcast_size, U64 broadcast_src_lane_idx); +#define lane_idx() (tctx_selected()->lane_ctx.lane_idx) +#define lane_count() (tctx_selected()->lane_ctx.lane_count) +#define lane_from_task_idx(idx) ((idx)%lane_count()) +#define lane_ctx(ctx) tctx_set_lane_ctx((ctx)) +#define lane_sync() tctx_lane_barrier_wait(0, 0, 0) +#define lane_sync_u64(ptr, src_lane_idx) tctx_lane_barrier_wait((ptr), sizeof(*(ptr)), (src_lane_idx)) +#define lane_range(count) m_range_from_n_idx_m_count(lane_idx(), lane_count(), (count)) + +//- rjf: thread names +internal void tctx_set_thread_name(String8 name); +internal String8 tctx_get_thread_name(void); + +//- rjf: thread source-locations +internal void tctx_write_srcloc(char *file_name, U64 line_number); +internal void tctx_read_srcloc(char **file_name, U64 *line_number); +#define tctx_write_this_srcloc() tctx_write_srcloc(__FILE__, __LINE__) + +//- rjf: access scopes +internal Access *access_open(void); +internal void access_close(Access *access); +internal void access_touch(Access *access, AccessPt *pt, CondVar cv); + +//- rjf: access points +internal B32 access_pt_is_expired_(AccessPt *pt, AccessPtExpireParams *params); +#define access_pt_is_expired(pt, ...) access_pt_is_expired_((pt), &(AccessPtExpireParams){.time = 2000000, .update_idxs = 2, __VA_ARGS__}) + +//- rjf: progress counters +#define set_progress_ptr(ptr) (tctx_selected()->progress_counter_ptr = (ptr)) +#define set_progress_target_ptr(ptr) (tctx_selected()->progress_target_ptr = (ptr)) +#define set_progress(val) (tctx_selected()->progress_counter_ptr ? ins_atomic_u64_eval_assign(tctx_selected()->progress_counter_ptr, (val)) : (void)0) +#define add_progress(val) (tctx_selected()->progress_counter_ptr ? ins_atomic_u64_add_eval(tctx_selected()->progress_counter_ptr, (val)) : (void)0) +#define set_progress_target(val) (tctx_selected()->progress_target_ptr ? ins_atomic_u64_eval_assign(tctx_selected()->progress_target_ptr, (val)) : (void)0) + +#endif // BASE_THREAD_CONTEXT_H diff --git a/unix_os.c b/unix_os.c new file mode 100644 index 0000000..c39c96f --- /dev/null +++ b/unix_os.c @@ -0,0 +1,147 @@ +#include "all.h" + +global pthread_key_t linux_thread_context_key; +// ThreadContext +void osInit() { + pthread_key_create(&linux_thread_context_key, NULL); +} + +void* osThreadContextGet() { + return pthread_getspecific(linux_thread_context_key); +} + +void osThreadContextSet(void* ctx) { + pthread_setspecific(linux_thread_context_key, ctx); +} + +bool osThreadJoin(Thread handle, u64 endt_us) { + /* + if(MemoryIsZeroStruct(&handle)) { return 0; } + OS_LNX_Entity *entity = (OS_LNX_Entity *)handle.u64[0]; + int join_result = pthread_join(entity->thread.handle, 0); + B32 result = (join_result == 0); + os_lnx_entity_release(entity); + return result; + */ + i32 join_result = pthread_join(handle.thread, NULL); + bool result = join_result == 0; + return result; +} + +// Memory +fn void* osMemoryReserve(u64 size) { + void* result = mmap(((void*)0), size, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0); + if(result == MAP_FAILED) { + result = 0; + } + return result; +} + +fn void osMemoryCommit(void* memory, u64 size) { + i32 result = mprotect(memory, size, PROT_READ | PROT_WRITE); + assert(result == 0 && "osMemoryCommit() failed"); +} + +fn void osMemoryDecommit(void* memory, u64 size) { + mprotect(memory, size, PROT_NONE); +} + +fn void osMemoryRelease(void* memory, u64 size) { + munmap(memory, size); +} + +// TUI +TermIOs osStartTUI(bool blocking) { + // set up the TUI incantations + printf("\033[?1049h"); // go to alternate buffer + TermIOs terminal_attributes, old_terminal_attributes; + tcgetattr(STDOUT_FILENO, &terminal_attributes); + old_terminal_attributes = terminal_attributes; + terminal_attributes.c_lflag &= ~(ICANON | ECHO); // dont echo keypresses, dont wait for carriage return + tcsetattr(STDOUT_FILENO, TCSANOW, &terminal_attributes); + if (!blocking) { + fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK); // non-blocking input mode + } + fflush(stdout); + return old_terminal_attributes; +} + +fn void osEndTUI(TermIOs old_terminal_attributes) { + tcsetattr(STDOUT_FILENO, TCSANOW, &old_terminal_attributes); + + // cleanup terminal TUI incantations + printf("\033[?1049l"); + fflush(stdout); +} + +fn Dim2 osGetTerminalDimensions() { + Dim2 result = {0}; + struct winsize ws; + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1) { + //perror("ioctl TIOCGWINSZ failed"); + //exit(1); + return result; + } + result.width = ws.ws_col; + result.height = ws.ws_row; + return result; +} + +void osBlitToTerminal(ptr writeable_output_ansi_string, i64 count) { + int flags = fcntl(STDOUT_FILENO, F_GETFL); + fcntl(STDOUT_FILENO, F_SETFL, flags & ~O_NONBLOCK); + + u64 total = 0; + while (total < count) { + i64 written_bytes = write(STDOUT_FILENO, writeable_output_ansi_string + total, count - total); + /* TODO handle this error + if (written_bytes < 0) { + if (errno == EINTR) continue; // Interrupted, retry + return -1; // Real error + } + */ + total += written_bytes; + } + fcntl(STDOUT_FILENO, F_SETFL, flags); +} + +bool osInitNetwork() { return true; } + +void osReadConsoleInput(u8* buffer, u32 len) { + MemoryZero(buffer, len); // reset the input so it's not contaminated by last keystroke + read(STDIN_FILENO, buffer, len); +} + +#define LOCALHOST_127 16777343 +i32 osLanIPAddress() { // returns as HOST byte-order + i32 result = 0; + struct ifaddrs *ifaddr, *ifa; + if (getifaddrs(&ifaddr) != -1) { + for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL) continue; + + int family = ifa->ifa_addr->sa_family; + + if (family == AF_INET) { + struct sockaddr_in addr = *(struct sockaddr_in*)ifa->ifa_addr; + if (addr.sin_addr.s_addr != LOCALHOST_127) { + freeifaddrs(ifaddr); + //printf("%s %d %d", inet_ntoa(addr.sin_addr), addr.sin_addr.s_addr, ntohl(addr.sin_addr.s_addr)); + return ntohl(addr.sin_addr.s_addr); + } + /* + char host[NI_MAXHOST]; + int s = getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in), + host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST); + + if (s == 0) { + printf("Interface: %s\tAddress: %s\n", ifa->ifa_name, host); + } + */ + } + } + + freeifaddrs(ifaddr); + } + return result; +} diff --git a/win32_os.c b/win32_os.c new file mode 100644 index 0000000..4f1b061 --- /dev/null +++ b/win32_os.c @@ -0,0 +1,326 @@ +#include "string.h" +#include "os.h" +#include +#include + +static u64 w32_ticks_per_sec = 1; +static u32 w32_thread_context_index; + +void osInit() { + LARGE_INTEGER perf_freq = {0}; + if (QueryPerformanceFrequency(&perf_freq)) { + w32_ticks_per_sec = ((u64)perf_freq.HighPart << 32) | perf_freq.LowPart; + } + timeBeginPeriod(1); + + w32_thread_context_index = TlsAlloc(); +} + +void* osThreadContextGet() { + return TlsGetValue(w32_thread_context_index); +} + +void osThreadContextSet(void* ctx) { + TlsSetValue(w32_thread_context_index, ctx); +} + +// Memory +fn void* osMemoryReserve(u64 size) { + return VirtualAlloc(0, size, MEM_RESERVE, PAGE_NOACCESS); +} + +fn void osMemoryCommit(void* memory, u64 size) { + VirtualAlloc(memory, size, MEM_COMMIT, PAGE_READWRITE); +} + +fn void osMemoryDecommit(void* memory, u64 size) { + VirtualFree(memory, size, MEM_DECOMMIT); +} + +fn void osMemoryRelease(void* memory, u64 size) { + VirtualFree(memory, 0, MEM_RELEASE); +} + +// Time +fn u64 osTimeMicrosecondsNow() { + u64 result = 0; + LARGE_INTEGER perf_counter = {0}; + if (QueryPerformanceCounter(&perf_counter)) { + u64 ticks = ((u64)perf_counter.HighPart << 32) | perf_counter.LowPart; + result = ticks * 1000000 / w32_ticks_per_sec; + } + return result; +} + +#define MICROSECONDS_PER_MILLISECOND 1000 +fn void osSleepMicroseconds(u32 t) { + Sleep(t / MICROSECONDS_PER_MILLISECOND); +} + +// Files +fn bool osFileExists(String filename) { + assert(false && "Not Implemented"); + ScratchMem scratch = scratchGet(); + StringUTF16Const filename16 = str16FromStr8(&scratch.arena, filename); + DWORD ret = GetFileAttributesW((WCHAR*)filename16.string); + scratchReturn(&scratch); + return (ret != INVALID_FILE_ATTRIBUTES && !(ret & FILE_ATTRIBUTE_DIRECTORY)); +} + +fn String osFileRead(Arena* arena, ptr filepath) { + assert(false && "Not Implemented"); + String result = {0}; + return result; +} + +fn bool osFileCreate(String filename) { + assert(false && "Not Implemented"); + return false; +} + +fn bool osFileCreateWrite(String filename, String data) { + assert(false && "Not Implemented"); + bool result = true; + return result; +} + +fn bool osFileWrite(String filename, String data) { + assert(false && "Not Implemented"); + bool result = true; + return result; +} + + +// Misc +fn void osDebugPrint(bool debug_mode, const char * format, ... ) { + if (debug_mode) { + va_list args; + va_start(args, format); + vprintf(format, args); + va_end(args); + } +} + + +// TUI +TermIOs osStartTUI(bool blocking) { + TermIOs old_settings; + + // Windows implementation + HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE); + HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE); + + // Save old console modes + GetConsoleMode(hStdin, &old_settings.input_mode); + GetConsoleMode(hStdout, &old_settings.output_mode); + + // Set up alternate screen buffer + printf("\033[?1049h"); + fflush(stdout); + + // Disable line input and echo (equivalent to ~ICANON and ~ECHO) + DWORD new_input_mode = old_settings.input_mode; + new_input_mode &= ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT); + + // Enable virtual terminal processing for ANSI escape sequences + DWORD new_output_mode = old_settings.output_mode; + new_output_mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; + + SetConsoleMode(hStdin, new_input_mode); + SetConsoleMode(hStdout, new_output_mode); + + SetConsoleOutputCP(CP_UTF8); + + // Note: Windows console is inherently non-blocking when using + // ENABLE_LINE_INPUT disabled. You can check for input with: + // DWORD events; + // GetNumberOfConsoleInputEvents(hStdin, &events); + // Or use PeekConsoleInput() before ReadConsoleInput() + + return old_settings; +} + +fn void osEndTUI(TermIOs old_terminal_attributes) { + HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE); + HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE); + + // Restore console modes + SetConsoleMode(hStdin, old_terminal_attributes.input_mode); + SetConsoleMode(hStdout, old_terminal_attributes.output_mode); + + // cleanup terminal TUI incantations + printf("\033[?1049l"); + fflush(stdout); +} + +fn Dim2 osGetTerminalDimensions() { + Dim2 result = {0}; + + HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFO csbi; + + if (GetConsoleScreenBufferInfo(hStdout, &csbi)) { + result.width = csbi.srWindow.Right - csbi.srWindow.Left + 1; + result.height = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; + } else { + exit(1); + } + + return result; +} + +void osBlitToTerminal(ptr writeable_output_ansi_string, i64 count) { + HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE); + DWORD written; + WriteConsole(hStdout, writeable_output_ansi_string, count, &written, NULL); + assert(written == count); +} + +bool osInitNetwork() { + WSADATA wsaData; + + if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0) { + return false; + } + + if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) { + WSACleanup(); + return false; + } + + return true; +} + +void osReadConsoleInput(u8* buffer, u32 len) { + MemoryZero(buffer, len); // reset the input so it's not contaminated by last keystroke + + if (_kbhit()) { + buffer[0] = _getch(); + bool first_byte_is_special = buffer[0] == 0 || buffer[0] == 224; + if (first_byte_is_special && _kbhit()) { + u8 windows_key = _getch(); + switch (windows_key) { + case 72: buffer[0] = 27; buffer[1] = 91; buffer[2] = 65; break; // up + case 75: buffer[0] = 27; buffer[1] = 91; buffer[2] = 68; break; // left + case 77: buffer[0] = 27; buffer[1] = 91; buffer[2] = 67; break; // right + case 80: buffer[0] = 27; buffer[1] = 91; buffer[2] = 66; break; // down + default: buffer[1] = windows_key; + } + /*if (_kbhit()) { + buffer[2] = _getch(); + if (_kbhit()) { + buffer[3] = _getch(); + } + }*/ + } + } + /* + HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE); + INPUT_RECORD ir; + DWORD read; + if (PeekConsoleInput(hStdin, &ir, 1, &read) && read > 0) { + ReadConsole(hStdin, buffer, len, &read, NULL); + ReadConsoleInput(hStdin, &ir, 1, &read); + if (ir.EventType == KEY_EVENT) { + buffer[0] = ir.Event.KeyEvent.uChar.AsciiChar; + } + } + */ +} + +i32 osLanIPAddress() { + i32 result = 0; + /* + IP_ADAPTER_INFO *pAdapterInfo; + IP_ADAPTER_INFO *pAdapter = NULL; + DWORD dwRetVal = 0; + ULONG ulOutBufLen; + + pAdapterInfo = (IP_ADAPTER_INFO *)malloc(sizeof(IP_ADAPTER_INFO)); + ulOutBufLen = sizeof(IP_ADAPTER_INFO); + + // Make an initial call to GetAdaptersInfo to get the necessary size + if (GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) == ERROR_BUFFER_OVERFLOW) { + free(pAdapterInfo); + pAdapterInfo = (IP_ADAPTER_INFO *)malloc(ulOutBufLen); + } + + if ((dwRetVal = GetAdaptersInfo(pAdapterInfo, &ulOutBufLen)) == NO_ERROR) { + pAdapter = pAdapterInfo; + printf("LAN Addresses:\n"); + + // TODO: test this on windows + while (pAdapter) { + printf("Interface: %s\n", pAdapter->AdapterName); + printf("Description: %s\n", pAdapter->Description); + printf("IP Address: %s\n", pAdapter->IpAddressList.IpAddress.String); + printf("IP Address: %d\n", pAdapter->Address[0] << 24 | pAdapter->Address[1] << 16 | pAdapter->Address[2] << 8 | pAdapter->Address[3]); + printf("\n"); + pAdapter = pAdapter->Next; + } + } else { + printf("GetAdaptersInfo failed: %ld\n", dwRetVal); + } + + if (pAdapterInfo) { + free(pAdapterInfo); + } + */ + WSADATA wsa; + char hostname[256]; + struct addrinfo hints, *final = NULL, *ptr = NULL; + struct sockaddr_in *sockaddr_ipv4; + + // Initialize Winsock + if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) { + return 0; + } + + // Get hostname + if (gethostname(hostname, sizeof(hostname)) == SOCKET_ERROR) { + WSACleanup(); + return 0; + } + + // Set up hints for getaddrinfo + ZeroMemory(&hints, sizeof(hints)); + hints.ai_family = AF_INET; // IPv4 + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + // Get address info + if (getaddrinfo(hostname, NULL, &hints, &final) != 0) { + WSACleanup(); + return 0; + } + + // Loop through results to find a non-loopback address + for (ptr = final; ptr != NULL; ptr = ptr->ai_next) { + sockaddr_ipv4 = (struct sockaddr_in *)ptr->ai_addr; + unsigned long addr = ntohl(sockaddr_ipv4->sin_addr.s_addr); + + // Skip loopback addresses (127.x.x.x) + if ((addr >> 24) != 127) { + result = sockaddr_ipv4->sin_addr.s_addr; + break; + } + } + + freeaddrinfo(final); + WSACleanup(); + + return ntohl(result); +} + +bool osThreadJoin(Thread handle, u64 endt_us) { + DWORD sleep_ms = os_w32_sleep_ms_from_endt_us(endt_us); + OS_W32_Entity *entity = (OS_W32_Entity *)PtrFromInt(handle.u64[0]); + DWORD wait_result = WAIT_OBJECT_0; + if(entity != 0) + { + wait_result = WaitForSingleObject(entity->thread.handle, sleep_ms); + CloseHandle(entity->thread.handle); + os_w32_entity_release(entity); + } + return (wait_result == WAIT_OBJECT_0); +}