empty client-server game
This commit is contained in:
+599
@@ -0,0 +1,599 @@
|
||||
// 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
|
||||
|
||||
#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 <string.h>
|
||||
#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__)
|
||||
|
||||
///// 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 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 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 <Windows.h>
|
||||
# include <winsock2.h>
|
||||
# include <iphlpapi.h>
|
||||
# include <ws2tcpip.h>
|
||||
# include <timeapi.h>
|
||||
# include <conio.h>
|
||||
typedef struct {
|
||||
DWORD input_mode;
|
||||
DWORD output_mode;
|
||||
} TermIOs;
|
||||
#else
|
||||
# include <sys/socket.h>
|
||||
# include <netinet/in.h>
|
||||
# include <netdb.h>
|
||||
# include <arpa/inet.h>
|
||||
# include <net/if.h>
|
||||
# include <ifaddrs.h>
|
||||
# include <termios.h>
|
||||
# include <sys/ioctl.h>
|
||||
# include <pthread.h>
|
||||
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;
|
||||
|
||||
///// 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);
|
||||
|
||||
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);
|
||||
|
||||
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, ...);
|
||||
|
||||
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);
|
||||
|
||||
bool osInitNetwork();
|
||||
i32 osLanIPAddress();
|
||||
|
||||
bool osThreadJoin(Thread handle, u64 endt_us);
|
||||
|
||||
///// 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))
|
||||
|
||||
|
||||
#endif// BASE_ALL_H
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
#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"
|
||||
|
||||
#endif // BASE_IMPL_C
|
||||
@@ -0,0 +1,139 @@
|
||||
#include <sys/mman.h>
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#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);
|
||||
}
|
||||
}
|
||||
@@ -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--;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
#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* 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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 <errno.h>
|
||||
|
||||
#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 */
|
||||
@@ -0,0 +1,49 @@
|
||||
/** Pthread-barrier implementation for macOS */
|
||||
#ifndef PTHREAD_BARRIER_H
|
||||
#define PTHREAD_BARRIER_H
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#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 */
|
||||
@@ -0,0 +1,66 @@
|
||||
#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 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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,326 @@
|
||||
#include "string.h"
|
||||
#include "os.h"
|
||||
#include <userenv.h>
|
||||
#include <stdio.h>
|
||||
|
||||
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);
|
||||
}
|
||||
+743
@@ -0,0 +1,743 @@
|
||||
/*
|
||||
* Code conventions:
|
||||
* MyStructType
|
||||
* myFunction()
|
||||
* MyMacro()
|
||||
* my_variable
|
||||
* MY_CONSTANT
|
||||
* */
|
||||
#include <string.h>
|
||||
#include "base/impl.c"
|
||||
#include "lib/network.c"
|
||||
#include "render.c"
|
||||
#include "string_chunk.c"
|
||||
//#include "assets/asset1.h"
|
||||
//#include "assets/asset2.h"
|
||||
//#include "assets/asset3.h"
|
||||
|
||||
///// #define a bunch of client-only tunable game constants
|
||||
#define SYSTEM_MESSAGES_LEN 32
|
||||
#define MAX_SYSTEM_MESSAGE_LEN 512
|
||||
#define GOAL_LOOPS_PER_S 50
|
||||
#define GOAL_LOOP_US 1000000/GOAL_LOOPS_PER_S
|
||||
#define LOGIN_NAME_BUFFER_LEN 16
|
||||
#define PARSED_SERVER_MESSAGE_THREAD_QUEUE_LEN 16
|
||||
#define MAIN_GAME_TAB_COUNT (2)
|
||||
|
||||
///// TYPES
|
||||
typedef enum Screen {
|
||||
ScreenLogin,
|
||||
ScreenCreateCharacter,
|
||||
ScreenMainGame,
|
||||
ScreenDefeat,
|
||||
ScreenVictory,
|
||||
Screen_Count
|
||||
} Screen;
|
||||
|
||||
typedef enum LoginScreenState {
|
||||
LoginScreenStateInit,
|
||||
LoginScreenStateLoading,
|
||||
LoginScreenStateBadPw,
|
||||
LoginScreenState_Count
|
||||
} LoginScreenState;
|
||||
|
||||
typedef enum SpeechTailDirection {
|
||||
SpeechTailDirectionBottomRight,
|
||||
SpeechTailDirectionBottomLeft,
|
||||
SpeechTailDirectionRight,
|
||||
SpeechTailDirectionLeft,
|
||||
SpeechTailDirection_Count
|
||||
} SpeechTailDirection;
|
||||
|
||||
typedef struct Entity {
|
||||
EntityType type;
|
||||
u8 x;
|
||||
u8 y;
|
||||
u8 color;
|
||||
u64 features;
|
||||
u64 id;
|
||||
StringChunkList name;
|
||||
} Entity;
|
||||
|
||||
typedef struct EntityList {
|
||||
u64 length; // the currently used length
|
||||
u64 capacity;
|
||||
Entity* items;
|
||||
} EntityList;
|
||||
|
||||
typedef struct ParsedServerMessage {
|
||||
Message type;
|
||||
u16 port;
|
||||
u16 port2;
|
||||
u32 ip;
|
||||
u32 ip2;
|
||||
u64 id;
|
||||
u64 server_frame;
|
||||
XYZ xyz;
|
||||
//Entity entities[PARSED_CLIENT_ENTITY_LEN];
|
||||
//u64 ids[PARSED_IDS_LEN];
|
||||
} ParsedServerMessage;
|
||||
|
||||
typedef struct ParsedServerMessageThreadQueue {
|
||||
ParsedServerMessage items[PARSED_SERVER_MESSAGE_THREAD_QUEUE_LEN];
|
||||
u32 head;
|
||||
u32 tail;
|
||||
u32 count;
|
||||
Mutex mutex;
|
||||
Cond not_empty;
|
||||
Cond not_full;
|
||||
} ParsedServerMessageThreadQueue;
|
||||
|
||||
typedef struct LoginState {
|
||||
LoginScreenState state;
|
||||
u8 selected_field;
|
||||
u8 field_index;
|
||||
String name;
|
||||
String password;
|
||||
} LoginState;
|
||||
|
||||
typedef struct MenuState {
|
||||
u8 selected_index;
|
||||
u8 len;
|
||||
u64 id;
|
||||
} MenuState;
|
||||
|
||||
typedef struct GameState {
|
||||
Screen screen;
|
||||
Screen old_screen;
|
||||
EntityList entities;
|
||||
Entity me;
|
||||
u64 server_frame;
|
||||
u64 loop_count;
|
||||
Arena entity_arena;
|
||||
StringArena string_arena;
|
||||
LoginState login_state;
|
||||
MenuState menu;
|
||||
MenuState section; // for tabbing through selected "portions" of the screen
|
||||
u8 choices[4];
|
||||
UDPMessage keep_alive_msg;
|
||||
UDPClient client;
|
||||
StringChunkList message_input;
|
||||
} GameState;
|
||||
|
||||
///// GLOBALS
|
||||
global bool should_quit;
|
||||
global bool debug_mode = false;
|
||||
global Arena permanent_arena = {0};
|
||||
global u8List system_messages[SYSTEM_MESSAGES_LEN] = {0};
|
||||
global u8 system_message_index = 0;
|
||||
global GameState state = {0};
|
||||
global OutgoingMessageQueue* network_send_queue = {0};
|
||||
global ParsedServerMessageThreadQueue* network_recv_queue = {0};
|
||||
global str TABS[] = {"Debug", "Speak"};
|
||||
|
||||
///// FUNCTIONS
|
||||
fn ParsedServerMessageThreadQueue* newPSMThreadQueue(Arena* a) {
|
||||
ParsedServerMessageThreadQueue* result = arenaAlloc(a, sizeof(ParsedServerMessageThreadQueue));
|
||||
MemoryZero(result, (sizeof *result));
|
||||
result->mutex = newMutex();
|
||||
result->not_full = newCond();
|
||||
result->not_empty = newCond();
|
||||
return result;
|
||||
}
|
||||
|
||||
fn void psmThreadSafeQueuePush(ParsedServerMessageThreadQueue* queue, ParsedServerMessage* msg) {
|
||||
lockMutex(&queue->mutex); {
|
||||
while (queue->count == PARSED_SERVER_MESSAGE_THREAD_QUEUE_LEN) {
|
||||
waitForCondSignal(&queue->not_full, &queue->mutex);
|
||||
}
|
||||
|
||||
MemoryCopy(&queue->items[queue->tail], msg, (sizeof *msg));
|
||||
queue->tail = (queue->tail + 1) % PARSED_SERVER_MESSAGE_THREAD_QUEUE_LEN;
|
||||
queue->count++;
|
||||
|
||||
signalCond(&queue->not_empty);
|
||||
} unlockMutex(&queue->mutex);
|
||||
}
|
||||
|
||||
fn ParsedServerMessage* psmThreadSafeNonblockingQueuePop(ParsedServerMessageThreadQueue* q, ParsedServerMessage* copy_target) {
|
||||
// immediately returns NULL if there's nothing in the ThreadQueue
|
||||
// copies the ParsedServerMessage into `copy_target` if there is something in the queue
|
||||
// and marks it as popped from the queue
|
||||
ParsedServerMessage* result = NULL;
|
||||
|
||||
lockMutex(&q->mutex); {
|
||||
if (q->count > 0) {
|
||||
result = &q->items[q->head];
|
||||
MemoryCopy(copy_target, result, (sizeof *copy_target));
|
||||
q->head = (q->head + 1) % PARSED_SERVER_MESSAGE_THREAD_QUEUE_LEN;
|
||||
q->count--;
|
||||
|
||||
signalCond(&q->not_full);
|
||||
}
|
||||
} unlockMutex(&q->mutex);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
fn void entityPush(EntityList* list, Entity e) {
|
||||
if (list->length >= list->capacity) {
|
||||
arenaAllocArray(&state.entity_arena, Entity, list->capacity);
|
||||
list->capacity = list->capacity * 2;
|
||||
}
|
||||
list->items[list->length] = e;
|
||||
list->length += 1;
|
||||
}
|
||||
|
||||
fn bool entityDelete(EntityList* list, u64 id) {
|
||||
assert(list->length > 0);
|
||||
if (list->items[list->length - 1].id == id) {
|
||||
list->length -= 1;
|
||||
return true;
|
||||
} else {
|
||||
for (u32 i = 0; i < list->length; i++) {
|
||||
if (list->items[i].id == id) {
|
||||
// copy the last one over the one we are deleting
|
||||
list->items[i] = list->items[list->length - 1];
|
||||
list->length -= 1;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn void addSystemMessage(u8* msg) {
|
||||
// save the message to our system_messages ring buffer
|
||||
memset(system_messages[system_message_index].items, 0, SYSTEM_MESSAGES_LEN);
|
||||
sprintf((char*)system_messages[system_message_index].items, "%s", msg);
|
||||
system_messages[system_message_index].length = strlen((char*)system_messages[system_message_index].items);
|
||||
system_message_index += 1;
|
||||
if (system_message_index == SYSTEM_MESSAGES_LEN) {
|
||||
system_message_index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fn void renderSystemMessages(Pixel* buf, Dim2 screen_dimensions, Box sys_msg_box) {
|
||||
i32 printable_lines = sys_msg_box.height - 2;
|
||||
if (printable_lines > SYSTEM_MESSAGES_LEN) {
|
||||
printable_lines = SYSTEM_MESSAGES_LEN;
|
||||
}
|
||||
for (i32 i = 0; i < printable_lines; i++) {
|
||||
i32 index = (system_message_index - 1 - i);
|
||||
if (index < 0) {
|
||||
index = SYSTEM_MESSAGES_LEN + index;
|
||||
}
|
||||
u32 y = sys_msg_box.y + (sys_msg_box.height - i) - 1;
|
||||
u8List sys_msg = system_messages[index];
|
||||
for (i32 j = 0; j < MAX_SYSTEM_MESSAGE_LEN && j < sys_msg_box.width-4; j++) {
|
||||
u32 pos = (sys_msg_box.x + 2+j) + (screen_dimensions.width * y);
|
||||
if (j < sys_msg.length) {
|
||||
if (sys_msg.items[j] != '\n') {
|
||||
buf[pos].bytes[0] = sys_msg.items[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn void renderPercentBar(TuiState* tui, u16 x, u16 y, u16 width, u8 ansi_color, u64 value, u64 max) {
|
||||
Pixel* buf = tui->frame_buffer;
|
||||
Dim2 screen_dimensions = tui->screen_dimensions;
|
||||
u16 pos = x + (screen_dimensions.width * y);
|
||||
buf[pos].bytes[0] = '[';
|
||||
pos = x+width + (screen_dimensions.width * y);
|
||||
buf[pos].bytes[0] = ']';
|
||||
pos = x+1 + (screen_dimensions.width * y);
|
||||
f32 base_ratio = 0;
|
||||
if (max != 0) {
|
||||
base_ratio = ((f32)value / (f32)max);
|
||||
}
|
||||
f32 raw_ratio = base_ratio * (width-2);
|
||||
u32 full_spaces_count = (u32) raw_ratio;
|
||||
f32 remainder = raw_ratio - full_spaces_count;
|
||||
for (u32 i = 0; i < full_spaces_count; i++) {
|
||||
buf[pos+i].foreground = ansi_color;
|
||||
renderUtf8CharToBuffer(buf, x+1+i, y, "█", screen_dimensions);
|
||||
}
|
||||
buf[pos+full_spaces_count].foreground = ansi_color;
|
||||
if (value == max) {
|
||||
renderUtf8CharToBuffer(buf, x+1+full_spaces_count, y, "█", screen_dimensions);
|
||||
} else if (remainder > 0.875) {
|
||||
renderUtf8CharToBuffer(buf, x+1+full_spaces_count, y, "▉", screen_dimensions);
|
||||
} else if (remainder > 0.75) {
|
||||
renderUtf8CharToBuffer(buf, x+1+full_spaces_count, y, "▊", screen_dimensions);
|
||||
} else if (remainder > 0.625) {
|
||||
renderUtf8CharToBuffer(buf, x+1+full_spaces_count, y, "▋", screen_dimensions);
|
||||
} else if (remainder > 0.5) {
|
||||
renderUtf8CharToBuffer(buf, x+1+full_spaces_count, y, "▌", screen_dimensions);
|
||||
} else if (remainder > 0.375) {
|
||||
renderUtf8CharToBuffer(buf, x+1+full_spaces_count, y, "▍", screen_dimensions);
|
||||
} else if (remainder > 0.25) {
|
||||
renderUtf8CharToBuffer(buf, x+1+full_spaces_count, y, "▎", screen_dimensions);
|
||||
} else if (remainder > 0.125) {
|
||||
renderUtf8CharToBuffer(buf, x+1+full_spaces_count, y, "▏", screen_dimensions);
|
||||
} else {
|
||||
renderUtf8CharToBuffer(buf, x+1+full_spaces_count, y, " ", screen_dimensions);
|
||||
}
|
||||
}
|
||||
|
||||
fn void renderSpeechBubble(TuiState* tui, u16 x, u16 y, u16 max_width, String message, SpeechTailDirection dir) {
|
||||
Pixel* buf = tui->frame_buffer;
|
||||
Dim2 screen_dimensions = tui->screen_dimensions;
|
||||
// calculate dimenions
|
||||
u32 height = (message.length / max_width) + 1;
|
||||
u32 width = message.length;
|
||||
if (message.length > max_width) {
|
||||
width = max_width;
|
||||
}
|
||||
if (width < 12) {
|
||||
width = 12; // minimum width
|
||||
}
|
||||
|
||||
// print the upper border
|
||||
renderUtf8CharToBuffer(buf, x, y, "╭", screen_dimensions);
|
||||
for (i32 i = 0; i < width+1; i++) {
|
||||
renderUtf8CharToBuffer(buf, x+1+i, y, "─", screen_dimensions);
|
||||
}
|
||||
renderUtf8CharToBuffer(buf, x+width+1, y, "╮", screen_dimensions);
|
||||
|
||||
// start printing the rows
|
||||
for (u32 i = 0; i < height; i++) {
|
||||
renderUtf8CharToBuffer(buf, x, y+1+i, "│", screen_dimensions);
|
||||
// print the line of text
|
||||
for (u32 j = 0; j < width; j++) {
|
||||
u32 character_index = (i*(width-1))+j;
|
||||
u16 pos = (x+1+j) + (screen_dimensions.width * (y+1+i));
|
||||
if (message.length > character_index) {
|
||||
buf[pos].bytes[0] = message.bytes[character_index];
|
||||
} else {
|
||||
buf[pos].bytes[0] = ' ';
|
||||
}
|
||||
}
|
||||
renderUtf8CharToBuffer(buf, x+1+width, y+1+i, "│", screen_dimensions);
|
||||
}
|
||||
|
||||
// print the bottom box border
|
||||
renderUtf8CharToBuffer(buf, x, y+1+height, "╰", screen_dimensions);
|
||||
for (i32 i = 0; i < width+1; i++) {
|
||||
renderUtf8CharToBuffer(buf, x+1+i, y+1+height, "─", screen_dimensions);
|
||||
}
|
||||
renderUtf8CharToBuffer(buf, x+width+1, y+1+height, "╯", screen_dimensions);
|
||||
// TODO actually print the speech tail
|
||||
}
|
||||
|
||||
fn void renderStaticAssetToPixelBuffer(TuiState* tui, u8* asset, u32 len, u16 x, u16 y) {
|
||||
u16 line = 0;
|
||||
u16 x_in_line = 0;
|
||||
u16 pos = x + (tui->screen_dimensions.width * y);
|
||||
for (u32 i = 0; i < len; i++, x_in_line++) {
|
||||
if (asset[i] == '\n') {
|
||||
line += 1;
|
||||
x_in_line = 0;
|
||||
pos = x + (tui->screen_dimensions.width * (y+line));
|
||||
} else if (asset[i] == ' ') {
|
||||
// do nothing, we skip spaces in our assets
|
||||
} else {
|
||||
tui->frame_buffer[pos+x_in_line].bytes[0] = asset[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn void clearServerSentState() {
|
||||
//memset(&state.current_room, 0, sizeof(RenderableRoom));
|
||||
|
||||
arenaClear(&state.entity_arena);
|
||||
state.entities.capacity = 64;
|
||||
state.entities.length = 0;
|
||||
state.entities.items = arenaAllocArray(&state.entity_arena, Entity, state.entities.capacity);
|
||||
}
|
||||
|
||||
fn void handleIncomingMessage(u8* message, u32 len, SocketAddress sender, i32 socket) {
|
||||
Message msg_type = message[0];
|
||||
dbg("handleIncomingMessage() of len=%d, message=%s\n", len, MESSAGE_STRINGS[msg_type]);
|
||||
u8List bytes = {len, len, message};
|
||||
u64 msg_pos = 1;
|
||||
ParsedServerMessage parsed = {0};
|
||||
parsed.type = msg_type;
|
||||
switch (msg_type) {
|
||||
case MessageNewAccountCreated:
|
||||
case MessageBadPw: {/*nothing to parse but the type*/} break;
|
||||
case MessageCharacterId: {
|
||||
parsed.id = readU64FromBufferLE(message + 1);
|
||||
} break;
|
||||
case Message_Count:
|
||||
assert(false && "invalid msg_type detected");
|
||||
break;
|
||||
case MessageInvalid:
|
||||
addSystemMessage((u8*)"NOT IMPLEMENTED");
|
||||
break;
|
||||
}
|
||||
psmThreadSafeQueuePush(network_recv_queue, &parsed);
|
||||
}
|
||||
|
||||
fn void* receiveNetworkUpdates(void* udp) {
|
||||
UDPClient client = *(UDPClient*)udp;
|
||||
dbg("receiveNetworkUpdates() sock=%d\n", client.socket);
|
||||
UDPServer server = {
|
||||
.ready = true,
|
||||
.server_address = client.server_address,
|
||||
.server_socket = client.socket
|
||||
};
|
||||
infiniteReadUDPServer(&server, handleIncomingMessage);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fn void* sendNetworkUpdates(void* udp) {
|
||||
i32 socket_fd = ((UDPClient*)udp)->socket;
|
||||
dbg("sendNetworkUpdates() sock=%d\n", socket_fd);
|
||||
u8List bytes_list = {0};
|
||||
while (!should_quit) {
|
||||
UDPMessage msg = {0};
|
||||
outgoingMessageQueuePop(network_send_queue, &msg);
|
||||
bytes_list.items = msg.bytes;
|
||||
bytes_list.length = msg.bytes_len;
|
||||
bytes_list.capacity = msg.bytes_len;
|
||||
sendUDPu8List(socket_fd, &msg.address, &bytes_list);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count) {
|
||||
GameState* state = (GameState*) s;
|
||||
state->loop_count = loop_count;
|
||||
UDPClient* udp = &state->client;
|
||||
state->old_screen = state->screen;
|
||||
Dim2 screen_dimensions = tui->screen_dimensions;
|
||||
bool game_screen_changed = state->old_screen != state->screen; // detect new screens
|
||||
tui->redraw = tui->redraw || game_screen_changed;
|
||||
if (screen_dimensions.height > MAX_SCREEN_HEIGHT || screen_dimensions.width > MAX_SCREEN_WIDTH) {
|
||||
printf("\033[2J\033[%d;%df Your screen is too damn big. Shrink it to %dx%d max, bro... geez", 1,1, MAX_SCREEN_WIDTH, MAX_SCREEN_HEIGHT);
|
||||
fflush(stdout);
|
||||
return should_quit;
|
||||
}
|
||||
|
||||
// process server messages
|
||||
u32 msg_iters = 0;
|
||||
ParsedServerMessage msg = {0};
|
||||
ParsedServerMessage* next_net_msg = psmThreadSafeNonblockingQueuePop(network_recv_queue, &msg);
|
||||
while (next_net_msg != NULL) {
|
||||
msg_iters += 0;
|
||||
switch (msg.type) {
|
||||
case MessageNewAccountCreated: {
|
||||
state->screen = ScreenCreateCharacter;
|
||||
} break;
|
||||
case MessageBadPw: {
|
||||
state->login_state.state = LoginScreenStateBadPw;
|
||||
} break;
|
||||
case MessageCharacterId: {
|
||||
state->me.id = msg.id;
|
||||
char bytes[256] = {0};
|
||||
sprintf(bytes,"got character id: %lld", msg.id);
|
||||
addSystemMessage((u8*)bytes);
|
||||
state->screen = ScreenMainGame;
|
||||
state->menu.selected_index = 0;
|
||||
state->section.selected_index = 0;
|
||||
} break;
|
||||
case Message_Count:
|
||||
case MessageInvalid:
|
||||
assert(false && "invalid message from queue");
|
||||
break;
|
||||
}
|
||||
next_net_msg = psmThreadSafeNonblockingQueuePop(network_recv_queue, &msg);
|
||||
msg_iters++;
|
||||
}
|
||||
|
||||
// operate on screen-based input+state
|
||||
bool user_pressed_esc = input_buffer[0] == ASCII_ESCAPE && input_buffer[1] == 0;
|
||||
bool user_pressed_a_number = input_buffer[0] >= '1' && input_buffer[0] <= '9' && input_buffer[1] == 0;
|
||||
bool user_pressed_up = input_buffer[0] == 27 && input_buffer[1] == 91 && input_buffer[2] == 65;
|
||||
bool user_pressed_down = input_buffer[0] == 27 && input_buffer[1] == 91 && input_buffer[2] == 66;
|
||||
bool user_pressed_left = input_buffer[0] == 27 && input_buffer[1] == 91 && input_buffer[2] == 68;
|
||||
bool user_pressed_right = input_buffer[0] == 27 && input_buffer[1] == 91 && input_buffer[2] == 67;
|
||||
bool user_pressed_backspace = input_buffer[0] == ASCII_BACKSPACE || input_buffer[0] == ASCII_DEL;
|
||||
bool user_pressed_enter = input_buffer[0] == ASCII_RETURN || input_buffer[0] == ASCII_LINE_FEED;
|
||||
switch (state->screen) {
|
||||
case ScreenCreateCharacter: {
|
||||
//// SIMULATION
|
||||
state->screen = ScreenMainGame; // TODO change this if you want to actually have character creation
|
||||
if (state->me.id != 0) {
|
||||
state->screen = ScreenMainGame;
|
||||
break;
|
||||
}
|
||||
|
||||
if (input_buffer[0] == 'q' || user_pressed_esc) {
|
||||
should_quit = true;
|
||||
} else if (user_pressed_down || user_pressed_right) {
|
||||
state->menu.selected_index++;
|
||||
} else if (user_pressed_up || user_pressed_left) {
|
||||
state->menu.selected_index--;
|
||||
} else if (user_pressed_a_number) {
|
||||
state->menu.selected_index = input_buffer[0] - '1';
|
||||
} else if (input_buffer[0] == ASCII_RETURN || input_buffer[0] == ASCII_LINE_FEED) {
|
||||
if (state->section.selected_index == 0) {
|
||||
state->choices[0] = state->menu.selected_index;
|
||||
state->section.selected_index += 1;
|
||||
state->menu.selected_index = 0;
|
||||
state->menu.len = 2;
|
||||
} else {
|
||||
// send character color to server
|
||||
UDPMessage msg = {0};
|
||||
msg.address = udp->server_address;
|
||||
msg.bytes_len = 2;
|
||||
// 1. msg type/CommandType
|
||||
msg.bytes[0] = CommandCreateCharacter;
|
||||
// 2. what color
|
||||
msg.bytes[1] = ANSI_HP_RED;//WIZARD_COLORS[state->choices[0]];
|
||||
|
||||
outgoingMessageQueuePush(network_send_queue, &msg);
|
||||
|
||||
state->screen = ScreenMainGame;
|
||||
}
|
||||
}
|
||||
|
||||
if (state->menu.selected_index >= state->menu.len) {
|
||||
state->menu.selected_index = 0;
|
||||
}
|
||||
|
||||
//// RENDERING
|
||||
u16 line = 2;
|
||||
// 1. draw the outline
|
||||
Box b = { .x = 2, .y = line, .width = screen_dimensions.width - 5, .height = screen_dimensions.height - 5 };
|
||||
drawAnsiBox(tui->frame_buffer, b, screen_dimensions, true);
|
||||
|
||||
// 2. draw the header label
|
||||
str label = "Create Character";
|
||||
renderStrToBuffer(tui->frame_buffer, (screen_dimensions.width - (strlen(label)+4))/2, ++line, label, screen_dimensions);
|
||||
|
||||
line++;
|
||||
// TODO: game-specific character creation stuff
|
||||
} break;
|
||||
case ScreenMainGame: {
|
||||
// SIMULATION
|
||||
if (input_buffer[0] == 'q' || user_pressed_esc) {
|
||||
should_quit = true;
|
||||
}
|
||||
|
||||
// RENDERING
|
||||
renderStrToBuffer(tui->frame_buffer, 5, 1, "Games typically would render something here...", screen_dimensions);
|
||||
bool room_active = state->section.selected_index == 0;
|
||||
u32 tabs_y = 1 + 2 + 2;
|
||||
u32 tx = 2;
|
||||
for (u32 i = 0; i < MAIN_GAME_TAB_COUNT; i++) {
|
||||
u32 tab_len = strlen(TABS[i]);
|
||||
Box b = { .x = tx, .y = tabs_y, .width = tab_len+1, .height = 1 };
|
||||
drawAnsiBox(tui->frame_buffer, b, screen_dimensions, state->menu.selected_index == i);
|
||||
renderStrToBuffer(tui->frame_buffer, tx+1, tabs_y+ 1, TABS[i], screen_dimensions);
|
||||
tx += (tab_len + 3);
|
||||
}
|
||||
// draw the system messages box
|
||||
u32 box_y = tabs_y + 3;
|
||||
Box box = {
|
||||
.x = 1,
|
||||
.y = box_y,
|
||||
.width = screen_dimensions.width - 4,
|
||||
.height = screen_dimensions.height - box_y - 2,
|
||||
};
|
||||
drawAnsiBox(tui->frame_buffer, box, screen_dimensions, !room_active);
|
||||
if (state->menu.selected_index == 0) { // 0 is TABS[0] is "Debug"
|
||||
renderSystemMessages(tui->frame_buffer, tui->screen_dimensions, box);
|
||||
} else if (state->menu.selected_index == 1) { // 1 is TABS[1] is "Speak"
|
||||
renderStrToBuffer(tui->frame_buffer, box.x+2, box.y+1, "You haven't heard anything interesting lately...", screen_dimensions);
|
||||
}
|
||||
} break;
|
||||
case ScreenLogin: {
|
||||
// SIMULATION
|
||||
if (user_pressed_esc) {
|
||||
should_quit = true;
|
||||
}
|
||||
if (state->login_state.state == LoginScreenStateLoading) {
|
||||
break;
|
||||
}
|
||||
String* field;
|
||||
if (state->login_state.selected_field == 0) {
|
||||
field = &state->login_state.name;
|
||||
} else {
|
||||
field = &state->login_state.password;
|
||||
}
|
||||
if (isAlphaUnderscoreSpace(input_buffer[0])) {
|
||||
field->bytes[state->login_state.field_index] = input_buffer[0];
|
||||
if (state->login_state.field_index+1 < field->capacity) {
|
||||
state->login_state.field_index += 1;
|
||||
field->length += 1;
|
||||
}
|
||||
} else if (user_pressed_backspace) {
|
||||
field->bytes[state->login_state.field_index] = 0;
|
||||
if (state->login_state.field_index > 0) {
|
||||
state->login_state.field_index -= 1;
|
||||
field->length -= 1;
|
||||
}
|
||||
} else if (input_buffer[0] == ASCII_RETURN || input_buffer[0] == ASCII_LINE_FEED) {
|
||||
if (state->login_state.selected_field == 0) {
|
||||
state->login_state.selected_field = 1;
|
||||
state->login_state.field_index = 0;
|
||||
} else {
|
||||
u32 msg_idx = 0;
|
||||
// drop the login message into the network_send_queue
|
||||
UDPMessage msg = {0};
|
||||
msg.address = udp->server_address;
|
||||
// 1. msg type/CommandType
|
||||
msg.bytes[msg_idx++] = CommandLogin;
|
||||
// 2. our LAN-IP to handle the case where we are on the same LAN as the guy we are trying to fight
|
||||
// and our "listened" UDP port
|
||||
msg_idx += writeU16ToBufferLE(msg.bytes + msg_idx, ~(udp->client_port));
|
||||
msg_idx += writeI32ToBufferLE(msg.bytes + msg_idx, ~osLanIPAddress());
|
||||
// 2. how long is the name
|
||||
msg.bytes[msg_idx++] = state->login_state.name.length;
|
||||
// 3. the name
|
||||
memcpy(msg.bytes + msg_idx, state->login_state.name.bytes, state->login_state.name.length);
|
||||
// 4. the password
|
||||
memcpy(msg.bytes + msg_idx + state->login_state.name.length, state->login_state.password.bytes, state->login_state.password.length);
|
||||
msg.bytes_len = msg_idx + state->login_state.name.length + state->login_state.password.length;
|
||||
addSystemMessage((u8*)state->login_state.name.bytes);
|
||||
addSystemMessage((u8*)state->login_state.password.bytes);
|
||||
outgoingMessageQueuePush(network_send_queue, &msg);
|
||||
// show loading signal, stop accepting input
|
||||
state->login_state.state = LoginScreenStateLoading;
|
||||
}
|
||||
}
|
||||
|
||||
// RENDERING
|
||||
// TODO: 1. DiffieHelman exchange
|
||||
// 2. encrypt password
|
||||
// 3. send [CommandLogin uname-len username encrypted_pass]
|
||||
if (screen_dimensions.height < 30 || screen_dimensions.width < 80) {
|
||||
str warning_label = "Screen too smol. Plz resize.";
|
||||
renderStrToBuffer(tui->frame_buffer, 6, 2, warning_label, screen_dimensions);
|
||||
} else {
|
||||
u32 width = screen_dimensions.width / 3;
|
||||
u32 height = screen_dimensions.height / 4;
|
||||
u32 x = (screen_dimensions.width - width) / 2; // center
|
||||
u32 y = (screen_dimensions.height - height) / 2; // center
|
||||
// outline
|
||||
Box b = { .x = x, .y = y, .width = width, .height = height };
|
||||
drawAnsiBox(tui->frame_buffer, b, screen_dimensions, true);
|
||||
// labels
|
||||
str login_label = " Please login:";
|
||||
renderStrToBuffer(tui->frame_buffer, x+1, y+1, login_label, screen_dimensions);
|
||||
str name_label = " Name: ";
|
||||
renderStrToBuffer(tui->frame_buffer, x+1, y+2, name_label, screen_dimensions);
|
||||
str pw_label = " Password: ";
|
||||
renderStrToBuffer(tui->frame_buffer, x+1, y+3, pw_label, screen_dimensions);
|
||||
// render name
|
||||
u16 pos = ((y+2)*screen_dimensions.width) + (x+11);
|
||||
for (u32 i = 0; i < state->login_state.name.length; i++) {
|
||||
tui->frame_buffer[pos+i].bytes[0] = state->login_state.name.bytes[i];
|
||||
}
|
||||
// render password
|
||||
pos = ((y+3)*screen_dimensions.width) + (x+15);
|
||||
for (u32 i = 0; i < state->login_state.password.length; i++) {
|
||||
tui->frame_buffer[pos+i].bytes[0] = '*';
|
||||
}
|
||||
if (state->login_state.state == LoginScreenStateLoading) {
|
||||
renderStrToBuffer(tui->frame_buffer, x+5, y+5, "Loading...", screen_dimensions);
|
||||
} else if (state->login_state.state == LoginScreenStateBadPw) {
|
||||
renderStrToBuffer(tui->frame_buffer, x+5, y+5, "Bad password, dumbass", screen_dimensions);
|
||||
}
|
||||
// calculate where to put the cursor
|
||||
if (state->login_state.selected_field == 0) {
|
||||
tui->cursor.y = y+2;
|
||||
tui->cursor.x = x+11+state->login_state.name.length;
|
||||
} else {
|
||||
tui->cursor.y = y+3;
|
||||
tui->cursor.x = x+15+state->login_state.password.length;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case ScreenVictory: {
|
||||
// SIMULATION
|
||||
if (user_pressed_esc || input_buffer[0] == ASCII_RETURN || input_buffer[0] == ASCII_LINE_FEED) {
|
||||
state->screen = ScreenMainGame;
|
||||
}
|
||||
|
||||
// RENDERING
|
||||
renderStrToBuffer(tui->frame_buffer, 10, 10, "You WIN!!!", screen_dimensions);
|
||||
} break;
|
||||
case ScreenDefeat: {
|
||||
// SIMULATION
|
||||
if (user_pressed_esc || input_buffer[0] == ASCII_RETURN || input_buffer[0] == ASCII_LINE_FEED) {
|
||||
state->me.id = 0;
|
||||
state->menu.selected_index = 0;
|
||||
state->section.selected_index = 0;
|
||||
clearServerSentState();
|
||||
state->screen = ScreenCreateCharacter;
|
||||
}
|
||||
|
||||
// RENDERING
|
||||
renderStrToBuffer(tui->frame_buffer, 10, 10, "You died...", screen_dimensions);
|
||||
renderStrToBuffer(tui->frame_buffer, 10, 11, "[press ESC to continue]", screen_dimensions);
|
||||
} break;
|
||||
case Screen_Count:
|
||||
assert(false && "unhandled screen");
|
||||
break;
|
||||
}
|
||||
|
||||
if (loop_count % 10 == 0) {
|
||||
outgoingMessageQueuePush(network_send_queue, &state->keep_alive_msg);
|
||||
//outgoingMessageQueuePush(network_send_queue, &testm);
|
||||
}
|
||||
|
||||
return should_quit;
|
||||
}
|
||||
|
||||
i32 main(i32 argc, ptr argv[]) {
|
||||
assert(Message_Count < 256);
|
||||
assert(CommandType_Count < 256);
|
||||
osInit();
|
||||
// 3 threads, each their own loop:
|
||||
// 1. recvNetwork()
|
||||
// 2. sendNetwork()
|
||||
// 3. input/update/draw "normal" gameloop
|
||||
// - which is actually "wide" doing each room in parallel across N threads
|
||||
|
||||
// clear and init the state
|
||||
arenaInit(&permanent_arena);
|
||||
arenaInit(&state.entity_arena);
|
||||
state.entities.capacity = 64;
|
||||
state.entities.length = 0;
|
||||
state.entities.items = arenaAllocArray(&state.entity_arena, Entity, state.entities.capacity);
|
||||
arenaInit(&state.string_arena.a);
|
||||
state.string_arena.mutex = newMutex();
|
||||
state.message_input = stringChunkListInit(&state.string_arena);
|
||||
for (i32 i = 0; i < SYSTEM_MESSAGES_LEN; i++) {
|
||||
system_messages[i].capacity = MAX_SYSTEM_MESSAGE_LEN;
|
||||
system_messages[i].length = 0;
|
||||
system_messages[i].items = arenaAllocArraySized(&permanent_arena, sizeof(u8), MAX_SYSTEM_MESSAGE_LEN);
|
||||
}
|
||||
|
||||
// network queues
|
||||
network_send_queue = newOutgoingMessageQueue(&permanent_arena);
|
||||
network_recv_queue = newPSMThreadQueue(&permanent_arena);
|
||||
|
||||
// draw login screen, get input username + password
|
||||
state.screen = ScreenLogin;
|
||||
state.login_state.name.capacity = LOGIN_NAME_BUFFER_LEN;
|
||||
state.login_state.name.bytes = arenaAllocArray(&permanent_arena, char, state.login_state.name.capacity);
|
||||
state.login_state.password.capacity = LOGIN_NAME_BUFFER_LEN;
|
||||
state.login_state.password.bytes = arenaAllocArray(&permanent_arena, char, state.login_state.password.capacity);
|
||||
|
||||
// network incantation for connecting to the server
|
||||
if (osInitNetwork() != true) {
|
||||
printf("bad network startup");
|
||||
exit(1);
|
||||
}
|
||||
state.client = createUDPClient(7777, argc > 1 ? argv[1] : NULL);
|
||||
|
||||
// "hardcoded" keep alive message to periodically send to server
|
||||
state.keep_alive_msg.address = state.client.server_address;
|
||||
state.keep_alive_msg.bytes[0] = CommandKeepAlive;
|
||||
state.keep_alive_msg.bytes_len = 1;
|
||||
|
||||
Thread recv_thread = spawnThread(&receiveNetworkUpdates, &state.client);
|
||||
Thread send_thread = spawnThread(&sendNetworkUpdates, &state.client);
|
||||
|
||||
// game loop (read input, simulate next frame, render)
|
||||
infiniteUILoop(
|
||||
MAX_SCREEN_WIDTH,
|
||||
MAX_SCREEN_HEIGHT,
|
||||
GOAL_LOOP_US,
|
||||
&state,
|
||||
updateAndRender
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
#include "../base/all.h"
|
||||
|
||||
// https://stackoverflow.com/questions/1098897/what-is-the-largest-safe-udp-packet-size-on-the-internet
|
||||
#define UDP_MAX_MESSAGE_LEN 508
|
||||
#ifndef NET_OUTGOING_MESSAGE_QUEUE_LEN
|
||||
#define NET_OUTGOING_MESSAGE_QUEUE_LEN 16
|
||||
#endif
|
||||
|
||||
typedef struct sockaddr_in SocketAddress;
|
||||
|
||||
typedef struct UDPServer {
|
||||
bool ready;
|
||||
SocketAddress server_address;
|
||||
i32 server_socket;
|
||||
} UDPServer;
|
||||
|
||||
typedef struct UDPClient {
|
||||
bool ready;
|
||||
SocketAddress server_address;
|
||||
u16 client_port;
|
||||
i32 socket;
|
||||
} UDPClient;
|
||||
|
||||
typedef struct UDPMessage {
|
||||
u16 bytes_len;
|
||||
SocketAddress address;
|
||||
u8 bytes[UDP_MAX_MESSAGE_LEN];
|
||||
} UDPMessage;
|
||||
|
||||
typedef struct OutgoingMessageQueue {
|
||||
UDPMessage items[NET_OUTGOING_MESSAGE_QUEUE_LEN];
|
||||
u32 head;
|
||||
u32 tail;
|
||||
u32 count;
|
||||
Mutex mutex;
|
||||
Cond not_empty;
|
||||
Cond not_full;
|
||||
} OutgoingMessageQueue;
|
||||
|
||||
fn OutgoingMessageQueue* newOutgoingMessageQueue(Arena* a) {
|
||||
OutgoingMessageQueue* result = arenaAlloc(a, sizeof(OutgoingMessageQueue));
|
||||
MemoryZero(result, (sizeof *result));
|
||||
result->mutex = newMutex();
|
||||
result->not_full = newCond();
|
||||
result->not_empty = newCond();
|
||||
return result;
|
||||
}
|
||||
|
||||
fn void outgoingMessageQueuePush(OutgoingMessageQueue* queue, UDPMessage* msg) {
|
||||
lockMutex(&queue->mutex); {
|
||||
while (queue->count == NET_OUTGOING_MESSAGE_QUEUE_LEN) {
|
||||
waitForCondSignal(&queue->not_full, &queue->mutex);
|
||||
}
|
||||
|
||||
MemoryCopy(&queue->items[queue->tail], msg, (sizeof *msg));
|
||||
queue->tail = (queue->tail + 1) % NET_OUTGOING_MESSAGE_QUEUE_LEN;
|
||||
queue->count++;
|
||||
|
||||
signalCond(&queue->not_empty);
|
||||
} unlockMutex(&queue->mutex);
|
||||
}
|
||||
|
||||
fn UDPMessage* outgoingMessageNonblockingQueuePop(OutgoingMessageQueue* q, UDPMessage* copy_target) {
|
||||
// immediately returns NULL if there's nothing in the ThreadQueue
|
||||
// copies the ParsedClientCommand into `copy_target` if there is something in the queue
|
||||
// and marks it as popped from the queue
|
||||
UDPMessage* result = NULL;
|
||||
|
||||
lockMutex(&q->mutex); {
|
||||
if (q->count > 0) {
|
||||
result = &q->items[q->head];
|
||||
MemoryCopy(copy_target, result, (sizeof *copy_target));
|
||||
q->head = (q->head + 1) % NET_OUTGOING_MESSAGE_QUEUE_LEN;
|
||||
q->count--;
|
||||
|
||||
signalCond(&q->not_full);
|
||||
}
|
||||
} unlockMutex(&q->mutex);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
fn UDPMessage* outgoingMessageQueuePop(OutgoingMessageQueue* q, UDPMessage* copy_target) {
|
||||
UDPMessage* result = NULL;
|
||||
|
||||
lockMutex(&q->mutex); {
|
||||
while (q->count == 0) {
|
||||
waitForCondSignal(&q->not_empty, &q->mutex);
|
||||
}
|
||||
|
||||
result = &q->items[q->head];
|
||||
MemoryCopy(copy_target, result, (sizeof *copy_target));
|
||||
q->head = (q->head + 1) % NET_OUTGOING_MESSAGE_QUEUE_LEN;
|
||||
q->count--;
|
||||
|
||||
signalCond(&q->not_full);
|
||||
} unlockMutex(&q->mutex);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
fn bool socketAddressEqual(SocketAddress a, SocketAddress b) {
|
||||
return a.sin_addr.s_addr == b.sin_addr.s_addr
|
||||
&& a.sin_port == b.sin_port;
|
||||
}
|
||||
|
||||
// ONLY WORKS ON POSIX. taken from https://gist.github.com/miekg/a61d55a8ec6560ad6c4a2747b21e6128
|
||||
|
||||
// the only real difference between a udp "server" and a "client" is the bind() syscall
|
||||
// that the server makes in order to specify a port/address that it's listening on
|
||||
UDPServer createUDPServer(u16 server_port) {
|
||||
UDPServer result = {0};
|
||||
// define the address we'll be listening on
|
||||
result.server_address.sin_family = AF_INET;
|
||||
result.server_address.sin_addr.s_addr = inet_addr("0.0.0.0");//htonl(INADDR_ANY);
|
||||
result.server_address.sin_port = htons(server_port);
|
||||
|
||||
// get a FileDescriptor number from the OS to use for our socket
|
||||
result.server_socket = socket(PF_INET, SOCK_DGRAM, 0);
|
||||
if (result.server_socket < 0) {
|
||||
return result;
|
||||
}
|
||||
// to let us immediately kill and restart server
|
||||
i32 optval = 1;
|
||||
setsockopt(result.server_socket, SOL_SOCKET, SO_REUSEADDR, (const void *)&optval, sizeof(i32));
|
||||
|
||||
result.ready = bind(result.server_socket, (struct sockaddr *)&result.server_address, sizeof(result.server_address)) >= 0;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
UDPClient createUDPClient(u16 server_port, str addr) {
|
||||
UDPClient result = {0};
|
||||
// define the address we'll be listening on
|
||||
result.server_address.sin_family = AF_INET;
|
||||
if (addr == 0) {
|
||||
result.server_address.sin_addr.s_addr = inet_addr("127.0.0.1");
|
||||
} else {
|
||||
result.server_address.sin_addr.s_addr = inet_addr(addr);
|
||||
}
|
||||
result.server_address.sin_port = htons(server_port);
|
||||
|
||||
// get a FileDescriptor number from the OS to use for our socket
|
||||
result.socket = socket(PF_INET, SOCK_DGRAM, 0);
|
||||
if (result.socket < 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
struct sockaddr_in client_address = {0};
|
||||
client_address.sin_family = AF_INET;
|
||||
client_address.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
client_address.sin_port = 0;
|
||||
result.ready = bind(result.socket, (struct sockaddr *)&client_address, sizeof(client_address)) >= 0;
|
||||
struct sockaddr_in empty_addr;
|
||||
socklen_t addr_len = sizeof(empty_addr);
|
||||
getsockname(result.socket, (struct sockaddr *)&empty_addr, &addr_len);
|
||||
result.client_port = ntohs(empty_addr.sin_port);
|
||||
return result;
|
||||
}
|
||||
|
||||
void infiniteReadUDPServer(UDPServer* server, void (*handleMessage)(u8* udp_message, u32 udp_len, SocketAddress sending_address, i32 socket)) {
|
||||
u8 message_buffer[UDP_MAX_MESSAGE_LEN] = {0};
|
||||
i32 bytes_recieved = 0;
|
||||
SocketAddress client_address = {0};
|
||||
i32 addrlen = sizeof(struct sockaddr);
|
||||
while (true) {
|
||||
bytes_recieved = recvfrom(server->server_socket, message_buffer, UDP_MAX_MESSAGE_LEN, 0, (struct sockaddr *)&client_address, (socklen_t*)&addrlen);
|
||||
|
||||
//gethostbyaddr: determine who sent the datagram
|
||||
//struct hostent* hostp = gethostbyaddr(
|
||||
// (const char *)&client_address.sin_addr.s_addr,
|
||||
// sizeof(client_address.sin_addr.s_addr),
|
||||
// AF_INET
|
||||
//);
|
||||
|
||||
//ptr printable_host_IP_address_string = inet_ntoa(client_address.sin_addr);
|
||||
|
||||
handleMessage(message_buffer, bytes_recieved, client_address, server->server_socket);
|
||||
MemoryZero(message_buffer, UDP_MAX_MESSAGE_LEN);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: sendall() to handle cases when the sendto() bytes return value is less than the intended bytes to send... stupid kernel fuckin wit us.
|
||||
i32 sendUDPu8List(i32 using_socket, SocketAddress* to, u8List* message) {
|
||||
return sendto(
|
||||
using_socket,
|
||||
message->items,
|
||||
message->length,
|
||||
0,
|
||||
(const struct sockaddr *)to,
|
||||
sizeof(struct sockaddr)
|
||||
);
|
||||
}
|
||||
|
||||
i32 sendUDPMessage(UDPServer* to, u8* message, u32 len) {
|
||||
return sendto(to->server_socket, message, len, 0, (struct sockaddr *)&to->server_address, sizeof(struct sockaddr));
|
||||
}
|
||||
+7988
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,109 @@
|
||||
#include "thread.h"
|
||||
|
||||
typedef struct ThreadQueue {
|
||||
void* items;
|
||||
u32 type_size;
|
||||
u32 max;
|
||||
u32 head;
|
||||
u32 tail;
|
||||
u32 count;
|
||||
Mutex mutex;
|
||||
Cond not_empty;
|
||||
Cond not_full;
|
||||
} ThreadQueue;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
fn ThreadQueue newThreadQueue(Arena* a, u32 type_size, u32 items_max) {
|
||||
ThreadQueue result = {0};
|
||||
result.type_size = type_size;
|
||||
result.max = items_max;
|
||||
result.items = arenaAllocArraySized(a, type_size, items_max);
|
||||
result.mutex = newMutex();
|
||||
result.not_full = newCond();
|
||||
result.not_empty = newCond();
|
||||
return result;
|
||||
}
|
||||
|
||||
fn void threadSafeQueuePush(ThreadQueue* queue, void* item) {
|
||||
lockMutex(&queue->mutex); {
|
||||
while (queue->count == queue->max) {
|
||||
waitForCondSignal(&queue->not_full, &queue->mutex);
|
||||
}
|
||||
|
||||
memcpy(queue->items + (queue->tail * (sizeof item)), item, queue->type_size);
|
||||
queue->tail = (queue->tail + 1) % queue->max;
|
||||
queue->count++;
|
||||
|
||||
signalCond(&queue->not_empty);
|
||||
} unlockMutex(&queue->mutex);
|
||||
}
|
||||
|
||||
fn void* threadSafeQueuePop(ThreadQueue* q) {
|
||||
void* result = NULL;
|
||||
|
||||
lockMutex(&q->mutex); {
|
||||
while (q->count == 0) {
|
||||
waitForCondSignal(&q->not_empty, &q->mutex);
|
||||
}
|
||||
|
||||
result = &q->items[q->head];
|
||||
q->head = (q->head + 1) % q->max;
|
||||
q->count--;
|
||||
|
||||
signalCond(&q->not_full);
|
||||
} unlockMutex(&q->mutex);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// immediately returns NULL if there's nothing in the ThreadQueue
|
||||
fn void* threadSafeNonblockingQueuePop(ThreadQueue* q, void* copy_target, u64 len) {
|
||||
void* result = NULL;
|
||||
|
||||
lockMutex(&q->mutex); {
|
||||
if (q->count > 0) {
|
||||
result = &q->items[q->head];
|
||||
MemoryCopy(copy_target, result, len);
|
||||
q->head = (q->head + 1) % q->max;
|
||||
q->count--;
|
||||
|
||||
signalCond(&q->not_full);
|
||||
}
|
||||
} unlockMutex(&q->mutex);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
#ifndef LIB_THREAD_H
|
||||
#define LIB_THREAD_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <pthread.h>
|
||||
#include "../base/include.h"
|
||||
|
||||
typedef struct Thread {
|
||||
pthread_t thread;
|
||||
} Thread;
|
||||
|
||||
typedef struct Mutex {
|
||||
pthread_mutex_t mutex;
|
||||
} Mutex;
|
||||
|
||||
typedef struct Cond {
|
||||
pthread_cond_t cond;
|
||||
} Cond;
|
||||
|
||||
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);
|
||||
|
||||
#endif //LIB_THREAD_H
|
||||
+579
@@ -0,0 +1,579 @@
|
||||
#include "../base/all.h"
|
||||
#include "../string_chunk.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#define UTF8_MAX_WIDTH 4
|
||||
#define ANSI_HP_RED (196)
|
||||
#define ANSI_MP_BLUE (33)
|
||||
#define ANSI_LIGHT_GREEN (82)
|
||||
#define ANSI_MID_GREEN (70)
|
||||
#define ANSI_DARK_GREEN (35)
|
||||
#define ANSI_BLACK (16)
|
||||
#define ANSI_WHITE (15)
|
||||
#define ANSI_BROWN (130)
|
||||
#define ANSI_DULL_RED (1)
|
||||
#define ANSI_HIGHLIGHT_RED (9)
|
||||
#define ANSI_DULL_GREEN (2)
|
||||
#define ANSI_HIGHLIGHT_GREEN (10)
|
||||
#define ANSI_DULL_YELLOW (3)
|
||||
#define ANSI_HIGHLIGHT_YELLOW (11)
|
||||
#define ANSI_DULL_BLUE (4)
|
||||
#define ANSI_HIGHLIGHT_BLUE (12)
|
||||
#define ANSI_DULL_GRAY (7)
|
||||
#define ANSI_HIGHLIGHT_GRAY (16)
|
||||
#define MAX_COMMAND_PALETTE_COMMANDS (1000)
|
||||
|
||||
///// TYPES
|
||||
typedef struct Pixel {
|
||||
u8 foreground;
|
||||
u8 background;
|
||||
u8 bytes[UTF8_MAX_WIDTH];
|
||||
} Pixel;
|
||||
|
||||
typedef struct TuiState {
|
||||
bool redraw;
|
||||
ptr writeable_output_ansi_string;
|
||||
Pixel* frame_buffer;
|
||||
Pixel* back_buffer;
|
||||
u64 buffer_len; // how many Pixels
|
||||
Pos2 cursor;
|
||||
Pos2 prev_cursor;
|
||||
Dim2 screen_dimensions;
|
||||
Dim2 prev_screen_dimensions;
|
||||
} TuiState;
|
||||
|
||||
typedef struct RGB {
|
||||
u8 r;
|
||||
u8 g;
|
||||
u8 b;
|
||||
} RGB;
|
||||
|
||||
typedef struct CommandPaletteCommand {
|
||||
u32 id;
|
||||
ptr display_name;
|
||||
ptr description;
|
||||
ptr* tags;
|
||||
} CommandPaletteCommand;
|
||||
|
||||
typedef struct CommandPaletteCommandList {
|
||||
u32 length;
|
||||
CommandPaletteCommand* items;
|
||||
} CommandPaletteCommandList;
|
||||
|
||||
typedef struct StringSearchScore {
|
||||
bool name_matched;
|
||||
bool description_matched;
|
||||
u16 tag_match_count;
|
||||
u32 score;
|
||||
u32 name_match_start;
|
||||
u32 name_match_len;
|
||||
u32 description_match_start;
|
||||
u32 description_match_len;
|
||||
} StringSearchScore;
|
||||
|
||||
///// Functions()
|
||||
fn u32 rgbToNum(RGB rgb) {
|
||||
return ((rgb.r<<16) | (rgb.g<<8) | rgb.b);
|
||||
}
|
||||
|
||||
fn u8 rgbToAnsi(RGB color) {
|
||||
u8 r = color.r / (255 / 5);
|
||||
u8 g = color.g / (255 / 5);
|
||||
u8 b = color.b / (255 / 5);
|
||||
return 16 + (36*r) + (6*g) + b;
|
||||
}
|
||||
|
||||
fn RGB rgbDarken(RGB color, f32 factor) {
|
||||
RGB result = {
|
||||
.r = ((f32)color.r) * factor,
|
||||
.g = ((f32)color.g) * factor,
|
||||
.b = ((f32)color.b) * factor,
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
fn RGB ansiToRGB(u8 ansi) {
|
||||
ansi -= 16;
|
||||
u8 b = ansi % 51;
|
||||
ansi -= ()
|
||||
u8 g =
|
||||
}
|
||||
*/
|
||||
|
||||
fn bool rgbEq(RGB a, RGB b) {
|
||||
return a.r == b.r && a.g == b.g && a.b == b.b;
|
||||
}
|
||||
|
||||
fn bool isPixelEq(Pixel a, Pixel b) {
|
||||
return a.background == b.background
|
||||
&& a.foreground == b.foreground
|
||||
&& a.bytes[0] == b.bytes[0]
|
||||
&& a.bytes[1] == b.bytes[1]
|
||||
&& a.bytes[2] == b.bytes[2]
|
||||
&& a.bytes[3] == b.bytes[3]
|
||||
;
|
||||
}
|
||||
|
||||
fn TuiState tuiInit(Arena* a, u64 buffer_len) {
|
||||
TuiState result = {
|
||||
.redraw = false,
|
||||
.writeable_output_ansi_string = arenaAlloc(a, MB(1)),
|
||||
.buffer_len = buffer_len,
|
||||
.back_buffer = arenaAllocArray(a, Pixel, buffer_len), // allocate biggest possible dimensions
|
||||
.frame_buffer = arenaAllocArray(a, Pixel, buffer_len), // allocate biggest possible dimensions
|
||||
};
|
||||
MemoryZero(result.back_buffer, buffer_len * sizeof(Pixel));
|
||||
MemoryZero(result.frame_buffer, buffer_len * sizeof(Pixel));
|
||||
return result;
|
||||
}
|
||||
|
||||
fn void copyStr(u8* bytes, str cstring) {
|
||||
for (u32 i = 0; i < strlen(cstring); i++) {
|
||||
bytes[i] = cstring[i];
|
||||
}
|
||||
}
|
||||
|
||||
fn void drawAnsiBox(Pixel* buf, Box box, Dim2 sd, bool bold) {
|
||||
str items[] = {"┌","─","┐","│","└","┘"};
|
||||
str b_items[] = {"┏","━","┓","┃","┗","┛"};
|
||||
ptr* use = (ptr*)items;
|
||||
if (bold) {
|
||||
use = (ptr*)b_items;
|
||||
}
|
||||
u32 pos = XYToPos(box.x, box.y, sd.width);
|
||||
|
||||
// print the upper box border
|
||||
copyStr(buf[pos].bytes, use[0]);
|
||||
for (i32 i = 0; i < box.width; i++) {
|
||||
copyStr(buf[pos+1+i].bytes, use[1]);
|
||||
}
|
||||
copyStr(buf[pos+1+box.width].bytes, use[2]);
|
||||
|
||||
// start printing the rows
|
||||
for (i32 i = 0; i < box.height; i++) {
|
||||
pos = XYToPos(box.x, (box.y+1+i), sd.width); // move cursor to beginning of the row
|
||||
copyStr(buf[pos].bytes, use[3]);
|
||||
copyStr(buf[pos+1+box.width].bytes, use[3]);
|
||||
}
|
||||
|
||||
// print the bottom box border
|
||||
pos = XYToPos(box.x, (box.y+1+box.height), sd.width);
|
||||
copyStr(buf[pos].bytes, use[4]);
|
||||
for (i32 i = 0; i < box.width; i++) {
|
||||
copyStr(buf[pos+1+i].bytes, use[1]);
|
||||
}
|
||||
copyStr(buf[pos+1+box.width].bytes, use[5]);
|
||||
}
|
||||
|
||||
fn void renderUtf8CharToBuffer(Pixel* buf, u16 x, u16 y, str text, Dim2 screen_dimensions) {
|
||||
assert(strlen(text) <= UTF8_MAX_WIDTH);
|
||||
u32 pos = x + (screen_dimensions.width*y);
|
||||
for (u32 i = 0; i < strlen(text); i++) {
|
||||
buf[pos].bytes[i] = text[i];
|
||||
}
|
||||
}
|
||||
|
||||
fn void renderStrToBufferMaxWidth(Pixel* buf, u16 x, u16 y, str text, u16 width, Dim2 screen_dimensions) {
|
||||
u32 pos = x + (screen_dimensions.width*y);
|
||||
for (u32 i = 0; i < strlen(text); i++) {
|
||||
buf[pos + (i % width)].background = 0;
|
||||
buf[pos + (i % width)].foreground = 0;
|
||||
buf[pos + (i % width)].bytes[0] = text[i];
|
||||
if (i % width == (width-1)) { // on last char i inside width
|
||||
pos += screen_dimensions.width; // move pos to next line
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn void renderStrToBufferMaxWidthWithoutChangingColor(Pixel* buf, u16 x, u16 y, str text, u16 width, Dim2 screen_dimensions) {
|
||||
u32 pos = x + (screen_dimensions.width*y);
|
||||
for (u32 i = 0; i < strlen(text); i++) {
|
||||
buf[pos + (i % width)].bytes[0] = text[i];
|
||||
if (i % width == (width-1)) { // on last char i inside width
|
||||
pos += screen_dimensions.width; // move pos to next line
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn void renderStrToBuffer(Pixel* buf, u16 x, u16 y, str text, Dim2 screen_dimensions) {
|
||||
u32 pos = x + (screen_dimensions.width*y);
|
||||
for (u32 i = 0; i < strlen(text); i++) {
|
||||
buf[pos+i].bytes[0] = text[i];
|
||||
}
|
||||
}
|
||||
|
||||
fn void renderStringChunkList(TuiState* tui, StringChunkList* list, u16 x, u16 y) {
|
||||
u32 pos = XYToPos(x, y, tui->screen_dimensions.width);
|
||||
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;
|
||||
}
|
||||
tui->frame_buffer[pos+i].bytes[0] = *((char*)(chunk + 1) + (i%STRING_CHUNK_PAYLOAD_SIZE));
|
||||
}
|
||||
}
|
||||
|
||||
fn u32 sprintfAnsiMoveCursorTo(ptr output, u16 x, u16 y) {
|
||||
return sprintf(output, "\x1b[%d;%df",y,x);
|
||||
}
|
||||
|
||||
fn void printfBufferAndSwap(TuiState* tui) {
|
||||
Pixel* old = tui->back_buffer;
|
||||
Pixel* next = tui->frame_buffer;
|
||||
u32 length = tui->screen_dimensions.height * tui->screen_dimensions.width;
|
||||
bool screen_dimensions_changed = tui->screen_dimensions.height != tui->prev_screen_dimensions.height
|
||||
|| tui->screen_dimensions.width != tui->prev_screen_dimensions.width;
|
||||
bool should_redraw_whole_screen = screen_dimensions_changed || tui->redraw;
|
||||
|
||||
// "quick" exit this fn if old == next
|
||||
if (!should_redraw_whole_screen) {
|
||||
bool old_equals_new = true;
|
||||
for (u32 i = 0; i < length; i++) {
|
||||
if (old[i].background != next[i].background
|
||||
|| old[i].foreground != next[i].foreground
|
||||
|| old[i].bytes[0] != next[i].bytes[0]
|
||||
|| old[i].bytes[1] != next[i].bytes[1]
|
||||
|| old[i].bytes[2] != next[i].bytes[2]
|
||||
|| old[i].bytes[3] != next[i].bytes[3]) {
|
||||
old_equals_new = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (old_equals_new
|
||||
&& tui->prev_cursor.x == tui->cursor.x
|
||||
&& tui->prev_cursor.y == tui->cursor.y
|
||||
) {
|
||||
length += 0; // debugging helper line
|
||||
return; // skip all the write() and sprintf() calls, since the frames are the same.
|
||||
}
|
||||
}
|
||||
|
||||
ptr output = tui->writeable_output_ansi_string;
|
||||
u8 bg = 0;
|
||||
u8 fg = 0;
|
||||
u16 x = 1;
|
||||
u16 y = 1;
|
||||
u16 last_x = 0;
|
||||
u16 last_y = 0;
|
||||
str RESET_STYLES = "\033[0m";
|
||||
str CLEAR_SCREEN = "\033[2J";
|
||||
|
||||
if (should_redraw_whole_screen) {
|
||||
output += sprintf(output, "\033[0m\033[2J");
|
||||
output += sprintfAnsiMoveCursorTo(output, x, y);
|
||||
bool printed_last = false;
|
||||
for (u32 i = 0; i < length; i++) {
|
||||
x = (i % tui->screen_dimensions.width) + 1;
|
||||
y = (i / tui->screen_dimensions.width) + 1;
|
||||
if (next[i].bytes[0] != 0) {
|
||||
if (printed_last == false) {
|
||||
output += sprintfAnsiMoveCursorTo(output, x, y);
|
||||
}
|
||||
char bytes[5] = {0}; // do this nonsense to ensure null-terminated characters
|
||||
bytes[0] = next[i].bytes[0];
|
||||
bytes[1] = next[i].bytes[1];
|
||||
bytes[2] = next[i].bytes[2];
|
||||
bytes[3] = next[i].bytes[3];
|
||||
|
||||
if (next[i].background != bg || next[i].foreground != fg) {
|
||||
bg = next[i].background;
|
||||
fg = next[i].foreground;
|
||||
if (bg == 0 && fg == 0) {
|
||||
output += sprintf(output, "%s%s", RESET_STYLES, bytes);
|
||||
} else if (bg != 0 && fg == 0) {
|
||||
output += sprintf(output, "\033[48;5;%dm%s", bg, bytes);
|
||||
} else if (bg == 0 && fg != 0) {
|
||||
output += sprintf(output, "\033[38;5;%dm%s", fg, bytes);
|
||||
} else {
|
||||
output += sprintf(output, "\033[48;5;%d;38;5;%dm%s", bg, fg, bytes);
|
||||
}
|
||||
} else {
|
||||
output += sprintf(output, "%s", bytes);
|
||||
}
|
||||
printed_last = true;
|
||||
} else {
|
||||
printed_last = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// clearing pass, to overwrite things that were there on the last frame, but are no longer present
|
||||
// we do this before the "rendering" pass so that multi-space characters (emojis) are easier to deal with
|
||||
output += sprintf(output, "%s", RESET_STYLES);
|
||||
output += sprintfAnsiMoveCursorTo(output, x, y);
|
||||
for (u32 i = 0; i < length; i++) {
|
||||
x = (i % tui->screen_dimensions.width) + 1;
|
||||
y = (i / tui->screen_dimensions.width) + 1;
|
||||
bool needs_clearing = (next[i].bytes[0] == 0 && old[i].bytes[0] != 0)
|
||||
|| (next[i].background == 0 && old[i].background != 0)
|
||||
|| (next[i].foreground == 0 && old[i].foreground != 0);
|
||||
if (needs_clearing) {
|
||||
bool last_printed_pos_is_adjacent_to_current_x_y = last_x+1 == x && last_y == y;
|
||||
if (!last_printed_pos_is_adjacent_to_current_x_y) {
|
||||
output += sprintfAnsiMoveCursorTo(output, x, y);
|
||||
}
|
||||
output += sprintf(output, " ");
|
||||
last_x = x;
|
||||
last_y = y;
|
||||
}
|
||||
}
|
||||
|
||||
// rendering pass
|
||||
last_x = 0;
|
||||
last_y = 0;
|
||||
for (u32 i = 0; i < length; i++) {
|
||||
x = (i % tui->screen_dimensions.width) + 1;
|
||||
y = (i / tui->screen_dimensions.width) + 1;
|
||||
if (!isPixelEq(old[i], next[i])) {
|
||||
if (next[i].bytes[0] != 0) {
|
||||
bool last_printed_pos_is_adjacent_to_current_x_y = last_x+1 == x && last_y == y;
|
||||
if (!last_printed_pos_is_adjacent_to_current_x_y) {
|
||||
output += sprintfAnsiMoveCursorTo(output, x, y);
|
||||
}
|
||||
|
||||
u8 bytes[5] = {0}; // do this nonsense to ensure null-terminated characters
|
||||
bytes[0] = next[i].bytes[0];
|
||||
bytes[1] = next[i].bytes[1];
|
||||
bytes[2] = next[i].bytes[2];
|
||||
bytes[3] = next[i].bytes[3];
|
||||
|
||||
if (next[i].background != bg || next[i].foreground != fg) {
|
||||
bg = next[i].background;
|
||||
fg = next[i].foreground;
|
||||
if (bg == 0 && fg == 0) {
|
||||
output += sprintf(output, "%s%s", RESET_STYLES, bytes);
|
||||
} else if (bg != 0 && fg == 0) {
|
||||
output += sprintf(output, "\033[48;5;%dm%s", bg, bytes);
|
||||
} else if (bg == 0 && fg != 0) {
|
||||
output += sprintf(output, "\033[38;5;%dm%s", fg, bytes);
|
||||
} else {
|
||||
output += sprintf(output, "\033[48;5;%d;38;5;%dm%s", bg, fg, bytes);
|
||||
}
|
||||
} else {
|
||||
output += sprintf(output, "%s", bytes);
|
||||
}
|
||||
|
||||
last_x = x;
|
||||
last_y = y;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output += sprintfAnsiMoveCursorTo(output, tui->cursor.x+1, tui->cursor.y+1); // re-position the cursor according to what the rendering logic set it to
|
||||
|
||||
// finally write our whole string to the terminal
|
||||
i64 count = output - tui->writeable_output_ansi_string;
|
||||
osBlitToTerminal(tui->writeable_output_ansi_string, count);
|
||||
|
||||
// swap our buffers
|
||||
Pixel* tmp = tui->back_buffer;
|
||||
tui->back_buffer = tui->frame_buffer;
|
||||
tui->frame_buffer = tmp;
|
||||
|
||||
// reset the `redraw` flag
|
||||
tui->redraw = false;
|
||||
}
|
||||
|
||||
fn u32 matchCommandPaletteCommands(String current_search, CommandPaletteCommandList commands, u32 menu_index, u32* scores, StringSearchScore* score_details) {
|
||||
// returns the `commands` id that matches the `menu_index`
|
||||
for (u32 i = 0; i < commands.length; i++) {
|
||||
CommandPaletteCommand* cmd = &commands.items[i];
|
||||
bool name_matches = false;
|
||||
for (u32 j = 0; j < strlen(cmd->display_name); j++) {
|
||||
if (current_search.length > 0 && lowerAscii(cmd->display_name[j]) == lowerAscii(current_search.bytes[0])) {
|
||||
for (u32 k = 0; k < current_search.length && k+j < strlen(cmd->display_name); k++) {
|
||||
if (lowerAscii(current_search.bytes[k]) != lowerAscii(cmd->display_name[j+k])) {
|
||||
break;
|
||||
}
|
||||
if (k == current_search.length - 1) {
|
||||
name_matches = true;
|
||||
score_details[i].name_match_start = j;
|
||||
score_details[i].name_match_len = current_search.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
bool description_matches = false;
|
||||
u32 tag_match_count = 0;
|
||||
scores[i] = (name_matches * 100 * MAX_COMMAND_PALETTE_COMMANDS) +
|
||||
(description_matches * 10 * MAX_COMMAND_PALETTE_COMMANDS) +
|
||||
(tag_match_count * MAX_COMMAND_PALETTE_COMMANDS) +
|
||||
cmd->id;
|
||||
score_details[i].score = scores[i];
|
||||
score_details[i].name_matched = name_matches;
|
||||
score_details[i].description_matched = description_matches;
|
||||
score_details[i].tag_match_count = tag_match_count;
|
||||
}
|
||||
u32Quicksort(scores, 0, commands.length - 1);
|
||||
u32ReverseArray(scores, commands.length);
|
||||
return (scores[menu_index] % MAX_COMMAND_PALETTE_COMMANDS);
|
||||
}
|
||||
|
||||
fn Pos2 renderCommandPalette(TuiState* tui, String current_search, CommandPaletteCommandList commands, u32 menu_index) {
|
||||
// returns the cursor position as Pos2
|
||||
|
||||
ScratchMem scratch = scratchGet();
|
||||
Dim2 sd = tui->screen_dimensions;
|
||||
Pos2 result = { 0 };
|
||||
|
||||
// draw the outline
|
||||
Box outline = {
|
||||
.width = sd.width / 2,
|
||||
.height = sd.height / 2,
|
||||
};
|
||||
outline.x = outline.width / 2;
|
||||
outline.y = outline.height / 2;
|
||||
drawAnsiBox(tui->frame_buffer, outline, sd, true);
|
||||
|
||||
// draw the "search bar"
|
||||
result.x = outline.x + 1 + current_search.length;
|
||||
result.y = outline.y + 1;
|
||||
renderStrToBufferMaxWidth(tui->frame_buffer, outline.x+1, outline.y+1, current_search.bytes, outline.width - 2, sd);
|
||||
for (u32 i = 1; i < outline.width-1; i++) {
|
||||
u32 pos = XYToPos(outline.x+1, outline.y+2, sd.width);
|
||||
copyStr(tui->frame_buffer[pos].bytes, "━");
|
||||
}
|
||||
|
||||
// sort the command options
|
||||
u32* scores = arenaAllocArray(&scratch.arena, u32, commands.length);
|
||||
StringSearchScore* score_details = arenaAllocArray(&scratch.arena, StringSearchScore, commands.length);
|
||||
matchCommandPaletteCommands(current_search, commands, menu_index, scores, score_details);
|
||||
|
||||
// draw the command options
|
||||
u32 x = outline.x + 1;
|
||||
u32 y = outline.y + 3;
|
||||
for (u32 i = 0; (y-outline.y) < (outline.height-1) && i < commands.length; i++, y+=2) {
|
||||
if (i == menu_index) {
|
||||
for (u32 ii = 0; ii < outline.width-1; ii++) {
|
||||
u32 pos = XYToPos(x+ii, y, tui->screen_dimensions.width);
|
||||
tui->frame_buffer[pos].bytes[0] = ' ';
|
||||
tui->frame_buffer[pos].foreground = ANSI_BLACK;
|
||||
tui->frame_buffer[pos].background = ANSI_WHITE;
|
||||
pos = XYToPos(x+ii, y+1, tui->screen_dimensions.width);
|
||||
tui->frame_buffer[pos].bytes[0] = ' ';
|
||||
tui->frame_buffer[pos].foreground = ANSI_BLACK;
|
||||
tui->frame_buffer[pos].background = ANSI_WHITE;
|
||||
}
|
||||
}
|
||||
u32 id = scores[i] % MAX_COMMAND_PALETTE_COMMANDS;
|
||||
StringSearchScore score = score_details[id];
|
||||
CommandPaletteCommand* cmd = NULL;
|
||||
for (u32 j = 0; j < commands.length; j++) {
|
||||
if (commands.items[j].id == id) {
|
||||
cmd = &commands.items[j];
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(cmd != NULL);
|
||||
if (score.name_matched) {
|
||||
u32 end_idx = score.name_match_len + score.name_match_start;
|
||||
for (u32 ii = 0; ii < strlen(cmd->display_name); ii++) {
|
||||
u32 pos = XYToPos(x+ii, y, tui->screen_dimensions.width);
|
||||
if (ii >= score.name_match_start && ii < end_idx) {
|
||||
if (i == menu_index) {
|
||||
tui->frame_buffer[pos].foreground = ANSI_DULL_RED;
|
||||
} else {
|
||||
tui->frame_buffer[pos].foreground = ANSI_HIGHLIGHT_YELLOW;
|
||||
tui->frame_buffer[pos].background = 0;
|
||||
}
|
||||
}
|
||||
tui->frame_buffer[pos].bytes[0] = cmd->display_name[ii];
|
||||
}
|
||||
} else {
|
||||
renderStrToBufferMaxWidthWithoutChangingColor(tui->frame_buffer, x, y, cmd->display_name, outline.width - 2, sd);
|
||||
}
|
||||
renderStrToBufferMaxWidthWithoutChangingColor(tui->frame_buffer, x, y+1, cmd->description, outline.width - 2, sd);
|
||||
}
|
||||
|
||||
scratchReturn(&scratch);
|
||||
return result;
|
||||
}
|
||||
|
||||
fn void renderChoiceMenu(TuiState* tui, u16 x, u16 y, ptr options[], u32 len, bool choosable, u32 selected_index, u8* colors) {
|
||||
for (u32 i = 0; i < len; i++) {
|
||||
u32 pos = x + (tui->screen_dimensions.width*(y+i));
|
||||
if (choosable && selected_index == i) {
|
||||
tui->cursor.x = x;
|
||||
tui->cursor.y = y+i;
|
||||
}
|
||||
u8 bytes[80] = {0};
|
||||
sprintf((char*)bytes, "- %d. ", i+1);
|
||||
u32 offset = strlen((char*)bytes);
|
||||
u32 bytes_remaining = (80 - offset);
|
||||
for (
|
||||
u32 j = 0;
|
||||
j < strlen(options[i]) && j < bytes_remaining;
|
||||
j++
|
||||
) {
|
||||
bytes[offset+j] = options[i][j];
|
||||
}
|
||||
// write our `bytes` buffer into the Pixel* buf
|
||||
for (u32 j = 0; j < 80; j++) {
|
||||
tui->frame_buffer[pos+j].bytes[0] = bytes[j];
|
||||
if (choosable && selected_index == i) {
|
||||
tui->frame_buffer[pos+j].foreground = ANSI_BLACK;
|
||||
tui->frame_buffer[pos+j].background = ANSI_WHITE;
|
||||
} else {
|
||||
tui->frame_buffer[pos+j].foreground = colors == NULL ? ANSI_WHITE : colors[i];
|
||||
tui->frame_buffer[pos+j].background = ANSI_BLACK;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn void infiniteUILoop(
|
||||
u32 max_screen_width,
|
||||
u32 max_screen_height,
|
||||
u64 goal_input_loop_us,
|
||||
void* state,
|
||||
// updateAndRender should return a bool `should_quit`
|
||||
bool (*updateAndRender)(TuiState* tui, void* state, u8* input_buffer, u64 loop_count)
|
||||
) {
|
||||
bool should_quit = false;
|
||||
Arena permanent_arena = {0};
|
||||
arenaInit(&permanent_arena);
|
||||
// set up the TUI incantations
|
||||
TermIOs old_terminal_attributes = osStartTUI(false);
|
||||
TuiState tui = tuiInit(&permanent_arena, max_screen_width*max_screen_height);
|
||||
tui.screen_dimensions = osGetTerminalDimensions();
|
||||
|
||||
// ui loop (read input, simulate next frame, render)
|
||||
u8 input_buffer[5] = {0};
|
||||
u64 loop_start;
|
||||
u64 loop_count = 0;
|
||||
while (!should_quit) {
|
||||
loop_count += 1;
|
||||
loop_start = osTimeMicrosecondsNow();
|
||||
|
||||
// both reads local input from the keyboard AND from the network
|
||||
osReadConsoleInput(input_buffer, 4);
|
||||
|
||||
// prep rendering
|
||||
MemoryZero(tui.frame_buffer, tui.buffer_len * sizeof(Pixel));
|
||||
tui.prev_screen_dimensions = tui.screen_dimensions;
|
||||
if (loop_count % 17 == 0) { // every 17 frames, update our terminal_dimensions
|
||||
tui.screen_dimensions = osGetTerminalDimensions();
|
||||
}
|
||||
tui.prev_cursor = tui.cursor;// save last frame's cursor
|
||||
|
||||
// operate on input + render new tui.frame_buffer
|
||||
should_quit = updateAndRender(&tui, state, input_buffer, loop_count);
|
||||
|
||||
printfBufferAndSwap(&tui);
|
||||
|
||||
// loop timing
|
||||
u32 loop_duration = osTimeMicrosecondsNow() - loop_start;
|
||||
i32 remaining_time = goal_input_loop_us - loop_duration;
|
||||
if (remaining_time > 0) {
|
||||
osSleepMicroseconds(remaining_time);
|
||||
}
|
||||
}
|
||||
|
||||
// cleanup terminal TUI incantations
|
||||
osEndTUI(old_terminal_attributes);
|
||||
}
|
||||
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
#include "shared.h"
|
||||
#include "lib/tui.c"
|
||||
|
||||
#define MAX_SCREEN_HEIGHT 300
|
||||
#define MAX_SCREEN_WIDTH 800
|
||||
|
||||
fn str charForEntity(EntityType e) {
|
||||
switch (e) {
|
||||
case EntityWall:
|
||||
return "# ";
|
||||
case EntityDoor:
|
||||
return "🚪";
|
||||
case EntityCharacter:
|
||||
return "🧙";
|
||||
//return "웃";
|
||||
case EntityNull:
|
||||
case EntityType_Count:
|
||||
//default: // commented out so that we get a warning for missing entity
|
||||
return " ";
|
||||
}
|
||||
}
|
||||
|
||||
/* HERE IS WHERE I USUALLY PUT THE "room" OR "map" DRAWING FUNCTION
|
||||
*
|
||||
fn void renderRoom(Pixel* buf, u32 x, u32 y, RenderableRoom* room, Dim2 screen_dimensions, bool active) {
|
||||
// x,y is starting cursor location of upper-left corner
|
||||
|
||||
Box b = { .x = x, .y = y, .width = ROOM_WIDTH*2, .height = ROOM_HEIGHT };
|
||||
drawAnsiBox(buf, b, screen_dimensions, active);
|
||||
|
||||
// start printing the rows
|
||||
// move cursor to beginning of the room
|
||||
for (i32 j = 0; j < ROOM_HEIGHT; j++) {
|
||||
for (i32 i = 0; i < ROOM_WIDTH; i++) {
|
||||
u32 roompos = XYToPos(i, j, ROOM_WIDTH);
|
||||
if (!room->visible[roompos] && room->memory[roompos] == RememberedTileQualityNone) {
|
||||
continue;
|
||||
}
|
||||
u32 bufpos = (x+1+(i*2)) + (screen_dimensions.width*(y+1+j));
|
||||
|
||||
str fg_char = charForEntity(room->foreground[roompos]);
|
||||
RGB background_color = colorForTile(room->background[roompos]);
|
||||
if (room->visible[roompos]) {
|
||||
buf[bufpos].foreground = ansiColorForEntity(room->foreground[roompos]);
|
||||
if (room->light[roompos] < VISIBLE_BRIGHTNESS_CUTOFF) {
|
||||
fg_char = charForEntity(EntityMurkyUnknown);
|
||||
background_color = rgbDarken(background_color, 0.8);
|
||||
}
|
||||
} else if (room->memory[roompos] == RememberedTileQualityDim) {
|
||||
background_color = rgbDarken(background_color, 0.4);
|
||||
buf[bufpos].foreground = ansiColorForEntity(room->foreground[roompos]);
|
||||
fg_char = charForEntity(EntityMurkyUnknown);
|
||||
} else if (room->memory[roompos] == RememberedTileQualityClear) {
|
||||
background_color = rgbDarken(background_color, 0.6);
|
||||
buf[bufpos].foreground = ansiColorForEntity(room->foreground[roompos]);
|
||||
}
|
||||
buf[bufpos].background = rgbToAnsi(background_color);
|
||||
Utf8Character first_character_class = classifyUtf8Character((u8)fg_char[0]);
|
||||
// if it's a single ASCII character
|
||||
if (first_character_class == Utf8CharacterAscii && fg_char[1] == '\0') {
|
||||
buf[bufpos].bytes[0] = fg_char[0];
|
||||
// if it's a pair of ASCII characters
|
||||
} else if (first_character_class == Utf8CharacterAscii && fg_char[1] > 0 && fg_char[2] == '\0') {
|
||||
buf[bufpos].bytes[0] = fg_char[0];
|
||||
buf[bufpos+1].bytes[0] = fg_char[1];
|
||||
buf[bufpos+1].background = buf[bufpos].background;
|
||||
buf[bufpos+1].foreground = buf[bufpos].foreground;
|
||||
} else if (first_character_class == Utf8CharacterThreeByte) {
|
||||
if (strlen(fg_char) == 3) {// handle 2-wide characters
|
||||
buf[bufpos+1].background = buf[bufpos].background;
|
||||
buf[bufpos+1].foreground = buf[bufpos].foreground;
|
||||
}
|
||||
for (u32 k = 0; k < strlen(fg_char)/3; k++) {
|
||||
if (k != 0) {
|
||||
buf[bufpos+k].background = buf[bufpos].background;
|
||||
buf[bufpos+k].foreground = buf[bufpos].foreground;
|
||||
}
|
||||
for (u32 l = 0; l < 3; l++) {
|
||||
buf[bufpos+k].bytes[l] = fg_char[l+(k*3)];
|
||||
}
|
||||
}
|
||||
} else if (first_character_class == Utf8CharacterFourByte) {
|
||||
if (strlen(fg_char) == UTF8_MAX_WIDTH) {// assume all of these characters are 2-wide
|
||||
buf[bufpos+1].background = buf[bufpos].background;
|
||||
buf[bufpos+1].foreground = buf[bufpos].foreground;
|
||||
}
|
||||
for (u32 k = 0; k < strlen(fg_char)/UTF8_MAX_WIDTH; k++) {
|
||||
for (u32 l = 0; l < UTF8_MAX_WIDTH; l++) {
|
||||
buf[bufpos+k].bytes[l] = fg_char[l+(k*UTF8_MAX_WIDTH)];
|
||||
}
|
||||
}
|
||||
// handle secondary bytes that didn't divide evenly
|
||||
for (u32 k = 0; k < strlen(fg_char)%UTF8_MAX_WIDTH; k++) {
|
||||
buf[bufpos+1].background = buf[bufpos].background;
|
||||
buf[bufpos+1].foreground = buf[bufpos].foreground;
|
||||
buf[bufpos+1].bytes[k] = fg_char[UTF8_MAX_WIDTH+k];
|
||||
}
|
||||
} else {
|
||||
assert(false && "unhandled bullshit");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
+785
@@ -0,0 +1,785 @@
|
||||
/*
|
||||
* Code conventions:
|
||||
* MyStructType
|
||||
* myFunction()
|
||||
* MyMacro()
|
||||
* my_variable
|
||||
* MY_CONSTANT
|
||||
* */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/random.h>
|
||||
#include "shared.h"
|
||||
#include "base/impl.c"
|
||||
#define NET_OUTGOING_MESSAGE_QUEUE_LEN 64
|
||||
#include "lib/network.c"
|
||||
#include "render.c"
|
||||
#include "string_chunk.c"
|
||||
|
||||
///// CONSTANTS
|
||||
#define MAX_ENTITIES (2<<18)
|
||||
#define LEFT_ROOM_ENTITES_LEN (KB(1))
|
||||
#define ROOM_MAP_COLLISIONS_LEN MAX_ROOMS/8
|
||||
#define CLIENT_COMMAND_LIST_LEN 8
|
||||
#define SERVER_PORT 7777
|
||||
#define SERVER_MAX_HEAP_MEMORY MB(256)
|
||||
#define SERVER_MAX_CLIENTS 16
|
||||
#define GAME_THREAD_CONCURRENCY 4
|
||||
#define GOAL_NETWORK_SEND_LOOPS_PER_S 4
|
||||
#define GOAL_NETWORK_SEND_LOOP_US 1000000/GOAL_NETWORK_SEND_LOOPS_PER_S
|
||||
#define GOAL_GAME_LOOPS_PER_S 4
|
||||
#define GOAL_GAME_LOOP_US 1000000/GOAL_GAME_LOOPS_PER_S
|
||||
#define CLIENT_TIMEOUT_FRAMES GOAL_GAME_LOOPS_PER_S*3
|
||||
#define CHUNK_SIZE 64
|
||||
#define ACCOUNT_CHUNK_SIZE 64
|
||||
#define PARSED_CLIENT_COMMAND_THREAD_QUEUE_LEN 64
|
||||
|
||||
///// TypeDefs
|
||||
typedef struct ParsedClientCommand {
|
||||
CommandType type;
|
||||
u8 byte;
|
||||
u16 sender_port;
|
||||
u16 alt_port;
|
||||
u32 sender_ip;
|
||||
u32 alt_ip;
|
||||
StringChunkList name;
|
||||
StringChunkList pass;
|
||||
u64 id;
|
||||
} ParsedClientCommand;
|
||||
|
||||
typedef struct ParsedClientCommandThreadQueue {
|
||||
ParsedClientCommand items[PARSED_CLIENT_COMMAND_THREAD_QUEUE_LEN];
|
||||
u32 head;
|
||||
u32 tail;
|
||||
u32 count;
|
||||
Mutex mutex;
|
||||
Cond not_empty;
|
||||
Cond not_full;
|
||||
} ParsedClientCommandThreadQueue;
|
||||
|
||||
typedef struct Entity {
|
||||
bool changed;
|
||||
u8 x;
|
||||
u8 y;
|
||||
u8 color;
|
||||
EntityType type;
|
||||
u32 misc;
|
||||
u64 id;
|
||||
u64 features;
|
||||
} Entity;
|
||||
|
||||
typedef struct Account {
|
||||
u64 id;
|
||||
String name;
|
||||
String pw;
|
||||
u64 eid;
|
||||
} Account;
|
||||
|
||||
typedef struct AccountChunk {
|
||||
u64 length; // the currently used # of accounts in this chunk
|
||||
u64 capacity; // the "chunk size" / space in this chunk
|
||||
struct AccountChunk* next; // the next chunk
|
||||
Account* items; // the actual accounts
|
||||
} AccountChunk;
|
||||
|
||||
typedef struct EntityList {
|
||||
u64 length; // the currently used length
|
||||
u64 capacity;
|
||||
Entity* items;
|
||||
} EntityList;
|
||||
|
||||
typedef struct EntityChunk {
|
||||
u64 length; // the currently used # of entities in this chunk
|
||||
u64 capacity; // the "chunk size" / space in this chunk
|
||||
struct EntityChunk* next; // the next chunk
|
||||
Entity* items; // the actual entities
|
||||
} EntityChunk;
|
||||
|
||||
typedef struct ChunkedEntityList {
|
||||
u64 length; // the current number of entities
|
||||
u64 chunk_size; // the # of entities per chunk
|
||||
u64 chunks; // the # of chunks in this list so far
|
||||
EntityChunk* first; // the first chunk of entities
|
||||
} ChunkedEntityList;
|
||||
|
||||
typedef struct Client {
|
||||
u16 lan_port;
|
||||
i32 lan_ip;
|
||||
u64 character_eid;
|
||||
u64 account_id;
|
||||
SocketAddress address;
|
||||
CommandType commands[CLIENT_COMMAND_LIST_LEN];
|
||||
u64 last_ping;
|
||||
} Client;
|
||||
|
||||
typedef struct ClientList {
|
||||
u64 length;
|
||||
u64 capacity;
|
||||
Client* items;
|
||||
} ClientList;
|
||||
|
||||
typedef struct State {
|
||||
Mutex client_mutex;
|
||||
Mutex mutex;
|
||||
ClientList clients;
|
||||
u64 next_eid;
|
||||
AccountChunk accounts;
|
||||
u64 frame;
|
||||
Arena game_scratch;
|
||||
StringArena string_arena;
|
||||
ParsedClientCommandThreadQueue* network_recv_queue;
|
||||
OutgoingMessageQueue* network_send_queue;
|
||||
} State;
|
||||
|
||||
///// Global Variables
|
||||
global State state = { 0 };
|
||||
global Arena permanent_arena = { 0 };
|
||||
global const Entity NULL_ENTITY = { 0 };
|
||||
global bool debug_mode = false;
|
||||
global ChunkedEntityList free_chunks = { 0, CHUNK_SIZE, 0, NULL };
|
||||
|
||||
///// functionImplementations()
|
||||
fn ParsedClientCommandThreadQueue* newPCCThreadQueue(Arena* a) {
|
||||
ParsedClientCommandThreadQueue* result = arenaAlloc(a, sizeof(ParsedClientCommandThreadQueue));
|
||||
MemoryZero(result, (sizeof *result));
|
||||
result->mutex = newMutex();
|
||||
result->not_full = newCond();
|
||||
result->not_empty = newCond();
|
||||
return result;
|
||||
}
|
||||
|
||||
fn void pccThreadSafeQueuePush(ParsedClientCommandThreadQueue* queue, ParsedClientCommand* msg) {
|
||||
lockMutex(&queue->mutex); {
|
||||
while (queue->count == PARSED_CLIENT_COMMAND_THREAD_QUEUE_LEN) {
|
||||
waitForCondSignal(&queue->not_full, &queue->mutex);
|
||||
}
|
||||
|
||||
MemoryCopy(&queue->items[queue->tail], msg, (sizeof *msg));
|
||||
queue->tail = (queue->tail + 1) % PARSED_CLIENT_COMMAND_THREAD_QUEUE_LEN;
|
||||
queue->count++;
|
||||
|
||||
signalCond(&queue->not_empty);
|
||||
} unlockMutex(&queue->mutex);
|
||||
}
|
||||
|
||||
fn ParsedClientCommand* pccThreadSafeNonblockingQueuePop(ParsedClientCommandThreadQueue* q, ParsedClientCommand* copy_target) {
|
||||
// immediately returns NULL if there's nothing in the ThreadQueue
|
||||
// copies the ParsedClientCommand into `copy_target` if there is something in the queue
|
||||
// and marks it as popped from the queue
|
||||
ParsedClientCommand* result = NULL;
|
||||
|
||||
lockMutex(&q->mutex); {
|
||||
if (q->count > 0) {
|
||||
result = &q->items[q->head];
|
||||
MemoryCopy(copy_target, result, (sizeof *copy_target));
|
||||
q->head = (q->head + 1) % PARSED_CLIENT_COMMAND_THREAD_QUEUE_LEN;
|
||||
q->count--;
|
||||
|
||||
signalCond(&q->not_full);
|
||||
}
|
||||
} unlockMutex(&q->mutex);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
fn u64 entitySerialize(Entity current, Account* acct, u64 index, u8 bytes[]) {
|
||||
// send entity header (common to all entity types)
|
||||
index += writeU64ToBufferLE(bytes + index, current.id);
|
||||
index += writeU64ToBufferLE(bytes + index, current.features);
|
||||
bytes[index++] = current.x;
|
||||
bytes[index++] = current.y;
|
||||
bytes[index++] = (u8)current.type; // EntityType
|
||||
|
||||
//if (CheckFlag(current.features, FeatureRegensHp)) {
|
||||
// index += writeU16ToBufferLE(bytes + index, current.hp);
|
||||
// index += writeU16ToBufferLE(bytes + index, current.max_hp);
|
||||
//}
|
||||
|
||||
// send character-specific details (color and name)
|
||||
if (current.type == EntityCharacter) {
|
||||
bytes[index++] = current.color;
|
||||
// send name string
|
||||
index += writeU64ToBufferLE(bytes + index, acct->name.length);
|
||||
for (u32 j = 0; j < acct->name.length; j++) {
|
||||
bytes[index++] = acct->name.bytes[j];
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
fn u64 entityFeaturesFromType(EntityType type) {
|
||||
u64 result = 0;
|
||||
switch (type) {
|
||||
case EntityCharacter: {
|
||||
SetFlag(result, FeatureWalksAround);
|
||||
SetFlag(result, FeatureCanFight);
|
||||
} break;
|
||||
default: {
|
||||
} break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
fn Account* findAccountById(u64 id) {
|
||||
AccountChunk* current = &state.accounts;
|
||||
while (current != NULL) {
|
||||
for (u32 i = 0; i < current->length; i++) {
|
||||
if (current->items[i].id == id) {
|
||||
return ¤t->items[i];
|
||||
}
|
||||
}
|
||||
current = current->next;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fn Account* findAccountByEId(u64 id) {
|
||||
AccountChunk* current = &state.accounts;
|
||||
while (current != NULL) {
|
||||
for (u32 i = 0; i < current->length; i++) {
|
||||
if (current->items[i].eid == id) {
|
||||
return ¤t->items[i];
|
||||
}
|
||||
}
|
||||
current = current->next;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fn Account* findAccountByName(String name) {
|
||||
AccountChunk* current = &state.accounts;
|
||||
while (current != NULL) {
|
||||
for (u32 i = 0; i < current->length; i++) {
|
||||
if (stringsEq(¤t->items[i].name, &name)) {
|
||||
return ¤t->items[i];
|
||||
}
|
||||
}
|
||||
current = current->next;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fn Account* newAccount(Arena* a, Account details) {
|
||||
u64 id = 0;
|
||||
AccountChunk* current = &state.accounts;
|
||||
while (current != NULL) {
|
||||
id += current->length;
|
||||
if (current->length < current->capacity) {
|
||||
Account* result = ¤t->items[current->length];
|
||||
*result = details;
|
||||
result->id = id;
|
||||
current->length += 1;
|
||||
printf("new account created id=%lld\n", id);
|
||||
return result;
|
||||
}
|
||||
if (current->next == NULL) {
|
||||
// alloc next chunk of accounts
|
||||
AccountChunk* next = arenaAlloc(a, sizeof(AccountChunk));
|
||||
next->capacity = ACCOUNT_CHUNK_SIZE;
|
||||
next->items = arenaAllocArray(a, Account, next->capacity);
|
||||
current->next = next;
|
||||
}
|
||||
current = current->next;
|
||||
}
|
||||
assert(false); // never should be reached
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fn u64 pushEntity(EntityChunk* chunk, Entity e) {
|
||||
assert(chunk->length < chunk->capacity);
|
||||
chunk->items[chunk->length] = e;
|
||||
u64 result = chunk->length;
|
||||
chunk->length += 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
fn bool isSpaceInChunk(EntityChunk* chunk) {
|
||||
return chunk->length < chunk->capacity;
|
||||
}
|
||||
|
||||
fn bool allChunksFull(ChunkedEntityList list) {
|
||||
return list.length >= (list.chunk_size * list.chunks);
|
||||
}
|
||||
|
||||
fn EntityChunk* pushNewChunk(Arena* a, ChunkedEntityList* list) {
|
||||
EntityChunk* new_chunk;
|
||||
if (free_chunks.length > 0) {
|
||||
new_chunk = free_chunks.first;
|
||||
free_chunks.length -= 1;
|
||||
free_chunks.first = new_chunk->next;
|
||||
} else {
|
||||
new_chunk = arenaAlloc(a, sizeof(EntityChunk));
|
||||
// alloc the new chunk of entities
|
||||
new_chunk->length = 0;
|
||||
new_chunk->capacity = list->chunk_size;
|
||||
new_chunk->next = NULL;
|
||||
new_chunk->items = arenaAllocArray(a, Entity, list->chunk_size);
|
||||
}
|
||||
// bookeeping in the list
|
||||
list->chunks += 1;
|
||||
if (list->first == NULL) {
|
||||
list->first = new_chunk;
|
||||
} else {
|
||||
EntityChunk* last = list->first;
|
||||
while (last->next != NULL) {
|
||||
last = last->next;
|
||||
}
|
||||
last->next = new_chunk;
|
||||
}
|
||||
return new_chunk;
|
||||
}
|
||||
|
||||
fn Entity* entityPtrFromChunkList(ChunkedEntityList* list, i32 index) {
|
||||
Entity* result = (Entity*)&NULL_ENTITY;
|
||||
i32 chunk_index = index / list->chunk_size;
|
||||
EntityChunk* chunk = list->first;
|
||||
for (i32 i = 0; i < chunk_index; i++) {
|
||||
chunk = chunk->next;
|
||||
}
|
||||
result = &chunk->items[index % list->chunk_size];
|
||||
return result;
|
||||
}
|
||||
|
||||
fn Entity entityFromChunkList(ChunkedEntityList* list, i32 index) {
|
||||
Entity result = {0};
|
||||
i32 chunk_index = index / list->chunk_size;
|
||||
EntityChunk* chunk = list->first;
|
||||
for (i32 i = 0; i < chunk_index; i++) {
|
||||
chunk = chunk->next;
|
||||
}
|
||||
result = chunk->items[index % list->chunk_size];
|
||||
return result;
|
||||
}
|
||||
|
||||
fn bool deleteLastEntity(ChunkedEntityList* list, EntityChunk* last_chunk, EntityChunk* second_to_last_chunk) {
|
||||
last_chunk->length -= 1;
|
||||
list->length -= 1;
|
||||
// don't delete the room's only chunk, but otherwise move the chunk to the free-list
|
||||
if (last_chunk->length == 0 && last_chunk != list->first) {
|
||||
list->chunks -= 1;
|
||||
second_to_last_chunk->next = NULL;
|
||||
free_chunks.length += 1;
|
||||
EntityChunk* last_free_chunk = free_chunks.first;
|
||||
if (last_free_chunk) {
|
||||
while (last_free_chunk->next != NULL) {
|
||||
last_free_chunk = last_free_chunk->next;
|
||||
}
|
||||
last_free_chunk->next = last_chunk;
|
||||
} else {
|
||||
free_chunks.first = last_chunk;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
fn void exitWithErrorMessage(ptr msg) {
|
||||
printf("error: %s", msg);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
fn u32 pushClient(ClientList* clients, SocketAddress addr) {
|
||||
Client new_client = {0};
|
||||
new_client.last_ping = state.frame;
|
||||
new_client.address = addr;
|
||||
|
||||
// first, try to overwrite an old dc'ed client
|
||||
for (u32 i = 1; i < clients->length; i++) {
|
||||
if (clients->items[i].character_eid == 0) {
|
||||
clients->items[i] = new_client;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
// if there are none, then add the client to the end of the list
|
||||
assert(clients->capacity > clients->length);//TODO grow the list if we need to
|
||||
clients->items[clients->length] = new_client;
|
||||
u32 result = clients->length;
|
||||
clients->length += 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
fn bool deleteClientByEId(ClientList* clients, u64 id) {
|
||||
bool succeeded = false;
|
||||
Client blank_client = {0};
|
||||
// i=1 because first client is null-client
|
||||
for (u32 i = 1; i < clients->length && !succeeded; i++) {
|
||||
if (clients->items[i].character_eid == id) {
|
||||
clients->items[i] = blank_client;
|
||||
succeeded = true;
|
||||
}
|
||||
}
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
fn u32 findClientHandleByEId(ClientList* clients, u64 id) {
|
||||
for (u32 i = 0; i < clients->length; i++) {
|
||||
Client c = clients->items[i];
|
||||
if (c.character_eid == id) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
fn u32 findClientHandleBySocketAddress(ClientList* clients, SocketAddress address) {
|
||||
for (u32 i = 0; i < clients->length; i++) {
|
||||
Client c = clients->items[i];
|
||||
if (socketAddressEqual(address, c.address)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
fn void handleIncomingMessage(u8* message, u32 len, SocketAddress sender, i32 socket) {
|
||||
dbg("%d: %s from %s:%d\n", len, command_type_strings[message[0]], inet_ntoa(sender.sin_addr), sender.sin_port);
|
||||
u32 msg_idx = 0;
|
||||
ParsedClientCommand parsed = {
|
||||
.type = (CommandType)message[msg_idx++],
|
||||
.sender_ip = sender.sin_addr.s_addr,
|
||||
.sender_port = sender.sin_port,
|
||||
};
|
||||
u8 temp_bytes[UDP_MAX_MESSAGE_LEN] = {0};
|
||||
String temp_str = {
|
||||
.bytes = (char*)temp_bytes,
|
||||
.length = 0,
|
||||
.capacity = 0,
|
||||
};
|
||||
switch (parsed.type) {
|
||||
case CommandLogin: {
|
||||
// parse the login command
|
||||
parsed.alt_port = ~readU16FromBufferLE(message + msg_idx);
|
||||
msg_idx += 2;
|
||||
parsed.alt_ip = ~readI32FromBufferLE(message + msg_idx);
|
||||
msg_idx += 4;
|
||||
|
||||
// parse the name
|
||||
u8 name_len = message[msg_idx++];
|
||||
MemoryZero(temp_bytes, UDP_MAX_MESSAGE_LEN);
|
||||
temp_str.length = name_len;
|
||||
temp_str.capacity = temp_str.length+1;
|
||||
for (u32 j = 0; j < temp_str.length; j++) {
|
||||
temp_str.bytes[j] = message[msg_idx+j];
|
||||
}
|
||||
parsed.name = allocStringChunkList(&state.string_arena, temp_str);
|
||||
msg_idx += temp_str.length;
|
||||
|
||||
// parse the password
|
||||
u32 pw_len = 0;
|
||||
while (message[msg_idx+pw_len]) {
|
||||
pw_len += 1;
|
||||
}
|
||||
MemoryZero(temp_bytes, UDP_MAX_MESSAGE_LEN);
|
||||
temp_str.length = pw_len;
|
||||
temp_str.capacity = temp_str.length+1;
|
||||
for (u32 j = 0; j < temp_str.length; j++) {
|
||||
temp_str.bytes[j] = message[msg_idx+j];
|
||||
}
|
||||
parsed.pass = allocStringChunkList(&state.string_arena, temp_str);
|
||||
msg_idx += temp_str.length;
|
||||
|
||||
printf("Logging in player: %s %d %s\n", MESSAGE_STRINGS[parsed.type], name_len, message + 7);
|
||||
} break;
|
||||
case CommandKeepAlive:
|
||||
break;
|
||||
case CommandCreateCharacter: {
|
||||
printf("command create character received\n");
|
||||
parsed.byte = message[1];
|
||||
} break;
|
||||
case CommandInvalid:
|
||||
case CommandType_Count: {
|
||||
dbg("invalid command type");
|
||||
} break;
|
||||
}
|
||||
|
||||
pccThreadSafeQueuePush(state.network_recv_queue, &parsed);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
fn void* receiveNetworkUpdates(void* udp) {
|
||||
UDPServer server = *(UDPServer*)udp;
|
||||
dbg("receiveNetworkUpdates() sock=%d\n", server.server_socket);
|
||||
infiniteReadUDPServer(&server, handleIncomingMessage);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fn void* sendNetworkUpdates(void* sock) {
|
||||
ThreadContext tctx = {0};
|
||||
tctxInit(&tctx);
|
||||
i32* socket_ptr = (i32*)sock;
|
||||
i32 socket = *socket_ptr;
|
||||
while (true) {
|
||||
u64 loop_start = osTimeMicrosecondsNow();
|
||||
|
||||
// 1. clear out our "outgoingMessage" queue
|
||||
{
|
||||
UDPMessage to_send = { 0 };
|
||||
UDPMessage* next_to_send = outgoingMessageNonblockingQueuePop(state.network_send_queue, &to_send);
|
||||
u8List bytes_list = { 0 };
|
||||
while (next_to_send != NULL) {
|
||||
bytes_list.items = to_send.bytes;
|
||||
bytes_list.length = to_send.bytes_len;
|
||||
bytes_list.capacity = to_send.bytes_len;
|
||||
sendUDPu8List(socket, &to_send.address, &bytes_list);
|
||||
next_to_send = outgoingMessageNonblockingQueuePop(state.network_send_queue, &to_send);
|
||||
}
|
||||
}
|
||||
|
||||
lockMutex(&state.client_mutex); {
|
||||
// WARNING the `i` starts at 1 here because state.clients.items[0] is a "null" Client
|
||||
for (u32 i = 1; i < state.clients.length; i++) {
|
||||
Client client = state.clients.items[i];
|
||||
if (client.last_ping+CLIENT_TIMEOUT_FRAMES < state.frame) {
|
||||
memset(&state.clients.items[i], 0, sizeof(Client));
|
||||
continue;
|
||||
}
|
||||
if (client.character_eid == 0) {
|
||||
continue; // they are still creating their character
|
||||
}
|
||||
|
||||
}
|
||||
} unlockMutex(&state.client_mutex);
|
||||
|
||||
u32 loop_duration = osTimeMicrosecondsNow() - loop_start;
|
||||
i32 remaining_time = GOAL_NETWORK_SEND_LOOP_US - loop_duration;
|
||||
if (remaining_time > 0) {
|
||||
osSleepMicroseconds(remaining_time);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fn void* gameLoop(void* params) {
|
||||
LaneCtx* lane_ctx = (LaneCtx*)params;
|
||||
ThreadContext tctx = {
|
||||
.lane_ctx = *lane_ctx,
|
||||
};
|
||||
tctxInit(&tctx);
|
||||
printf("Lane %lld (%lld) of %lld starting.\n", lane_ctx->lane_idx, LaneIdx(), lane_ctx->lane_count);
|
||||
fflush(stdout);
|
||||
UDPMessage outgoing_message = {0};
|
||||
u64 loop_start;
|
||||
u64 last_burn = 0;
|
||||
u64 last_hp_regen = 0;
|
||||
Arena scratch_arena = {0};
|
||||
arenaInit(&scratch_arena);
|
||||
while (true) {
|
||||
loop_start = osTimeMicrosecondsNow();
|
||||
|
||||
if (LaneIdx() == 0) { // narrow
|
||||
state.frame += 1;
|
||||
|
||||
// 1. process client messages
|
||||
lockMutex(&state.client_mutex); lockMutex(&state.mutex); {
|
||||
|
||||
u32 msg_iters = 0;
|
||||
SocketAddress sender = {0};
|
||||
ParsedClientCommand msg = {0};
|
||||
ParsedClientCommand* next_net_msg = pccThreadSafeNonblockingQueuePop(state.network_recv_queue, &msg);
|
||||
while (next_net_msg != NULL) {
|
||||
msg_iters += 0;
|
||||
sender.sin_addr.s_addr = msg.sender_ip;
|
||||
sender.sin_port = msg.sender_port;
|
||||
// find which client it is
|
||||
u32 client_handle = findClientHandleBySocketAddress(&state.clients, sender);
|
||||
Client* client = &state.clients.items[client_handle];
|
||||
switch (msg.type) {
|
||||
case CommandKeepAlive: {
|
||||
dbg("KeepAlive for client_handle=%d, %ld", client_handle, state.frame);
|
||||
state.clients.items[client_handle].last_ping = state.frame;
|
||||
} break;
|
||||
case CommandLogin: {
|
||||
if (client_handle == 0) {
|
||||
client_handle = pushClient(&state.clients, sender);
|
||||
client = &state.clients.items[client_handle];
|
||||
printf("pushed new client handle = %d\n", client_handle);
|
||||
}
|
||||
// update/set the lan_ip/port info for p2p connections
|
||||
client->lan_ip = htonl(msg.alt_ip);
|
||||
client->lan_port = htons(msg.alt_port);
|
||||
|
||||
/*
|
||||
struct in_addr ipaddr;
|
||||
ipaddr.s_addr = htonl(msg.alt_ip);
|
||||
printf("client #%d: SENDER=%s:%d\n", client_handle, inet_ntoa(sender.sin_addr), ntohs(sender.sin_port));
|
||||
printf(" LAN=%s:%d %d vs %d vs %d\n", inet_ntoa(ipaddr), msg.alt_port, msg.alt_ip, htonl(msg.alt_ip), sender.sin_addr.s_addr);
|
||||
*/
|
||||
|
||||
String name = stringChunkToString(&permanent_arena, msg.name);
|
||||
releaseStringChunkList(&state.string_arena, &msg.name);
|
||||
|
||||
String pw = stringChunkToString(&permanent_arena, msg.pass);
|
||||
releaseStringChunkList(&state.string_arena, &msg.pass);
|
||||
|
||||
Account* existing_account = findAccountByName(name);
|
||||
printf("name(%d): %s pw(%d): %s acct?: %d\n", name.length, name.bytes, pw.length, pw.bytes, existing_account != NULL);
|
||||
fflush(stdout);
|
||||
if (existing_account) {
|
||||
printf(" existing account\n");
|
||||
bool pw_matches = stringsEq(&pw, &existing_account->pw);
|
||||
arenaDealloc(&permanent_arena, pw.capacity);
|
||||
arenaDealloc(&permanent_arena, name.capacity);
|
||||
if (pw_matches) {
|
||||
printf(" pw matched\n");
|
||||
} else {
|
||||
// tell the client they did a bad pw
|
||||
outgoing_message.bytes[0] = (u8)MessageBadPw;
|
||||
outgoing_message.bytes_len = 1;
|
||||
outgoing_message.address = sender;
|
||||
outgoingMessageQueuePush(state.network_send_queue, &outgoing_message);
|
||||
printf("MessageBadPw sent\n");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
Account new_account = {
|
||||
.eid = 0,
|
||||
.name = name,
|
||||
.pw = pw,
|
||||
};
|
||||
existing_account = newAccount(&permanent_arena, new_account);
|
||||
}
|
||||
client->account_id = existing_account->id;
|
||||
if (existing_account->eid != 0) {
|
||||
client->character_eid = existing_account->eid;
|
||||
|
||||
// tell the client their character id
|
||||
outgoing_message.bytes[0] = (u8)MessageCharacterId;
|
||||
writeU64ToBufferLE(outgoing_message.bytes + 1, existing_account->eid);
|
||||
outgoing_message.bytes_len = 9;
|
||||
outgoing_message.address = sender;
|
||||
outgoingMessageQueuePush(state.network_send_queue, &outgoing_message);
|
||||
printf("MessageCharacterId sent\n");
|
||||
} else {
|
||||
// tell the client they made a new account
|
||||
outgoing_message.bytes[0] = (u8)MessageNewAccountCreated;
|
||||
outgoing_message.bytes_len = 1;
|
||||
outgoing_message.address = sender;
|
||||
outgoingMessageQueuePush(state.network_send_queue, &outgoing_message);
|
||||
printf("MessageNewAccountCreated sent\n");
|
||||
}
|
||||
printf("eid=%lld, client_handle=%d, acct_id=%lld\n", existing_account->eid, client_handle, existing_account->id);
|
||||
} break;
|
||||
case CommandCreateCharacter: {
|
||||
if (client->character_eid == 0) {
|
||||
// Create new character
|
||||
XYZ room_xyz = {0, 0, 0 };
|
||||
Entity character = {
|
||||
.type = EntityCharacter,
|
||||
.id = state.next_eid++,
|
||||
.changed = true,
|
||||
.features = entityFeaturesFromType(EntityCharacter),
|
||||
.color = msg.byte,
|
||||
};
|
||||
dbg("made new character id=%ld\n", character.id);
|
||||
client->character_eid = character.id;
|
||||
Account* account = findAccountById(client->account_id);
|
||||
account->eid = character.id;
|
||||
printf("character_eid=%lld, client_handle=%d, acct_id=%lld\n", account->eid, client_handle, account->id);
|
||||
|
||||
// tell the client their character id
|
||||
outgoing_message.address = sender;
|
||||
outgoing_message.bytes_len = 9;
|
||||
outgoing_message.bytes[0] = (u8)MessageCharacterId;
|
||||
writeU64ToBufferLE(outgoing_message.bytes + 1, character.id);
|
||||
outgoingMessageQueuePush(state.network_send_queue, &outgoing_message);
|
||||
printf("MessageCharacterId sent\n");
|
||||
} else {
|
||||
printf("client tried to create a character when he already has one.");
|
||||
}
|
||||
} break;
|
||||
case CommandType_Count:
|
||||
case CommandInvalid:
|
||||
dbg("invalid message from queue");
|
||||
break;
|
||||
}
|
||||
next_net_msg = pccThreadSafeNonblockingQueuePop(state.network_recv_queue, &msg);
|
||||
msg_iters++;
|
||||
}
|
||||
|
||||
} unlockMutex(&state.mutex); unlockMutex(&state.client_mutex);
|
||||
}
|
||||
|
||||
LaneSync();
|
||||
|
||||
// 2. tick non-user entities
|
||||
// iterate all the rooms
|
||||
/*
|
||||
Room* room = NULL;
|
||||
Range1u64 room_range = LaneRange(MAX_ROOMS);
|
||||
for (u32 i = room_range.min; i < room_range.max; i++) {
|
||||
room = &state.rooms->items[i];
|
||||
simulateRoom(&scratch_arena, room, burn_tick, regen_hp_tick);
|
||||
}
|
||||
*/
|
||||
|
||||
// 3. scratch cleanup
|
||||
arenaClear(&scratch_arena);
|
||||
|
||||
// 4. loop timing
|
||||
u32 loop_duration = osTimeMicrosecondsNow() - loop_start;
|
||||
i32 remaining_time = GOAL_GAME_LOOP_US - loop_duration;
|
||||
if (remaining_time > 0) {
|
||||
osSleepMicroseconds(remaining_time);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// THE SERVER
|
||||
i32 main(i32 argc, ptr argv[]) {
|
||||
osInit();
|
||||
// multi-thread architecture:
|
||||
// - the gameLoop() which just inifinite loops every "tick" and processes user input and updates gameworld state
|
||||
// - TODO: actually make this N "lanes" as described https://www.rfleury.com/p/multi-core-by-default
|
||||
// such that we can split room-work among all the various cores on our machine
|
||||
// - one is the sendNetworkUpdates() infinite loop, which sends a UDP snapshot-or-delta update to each connected client N/sec
|
||||
// - one is the receiveNetworkUpdates() infinte loop, which waits for new UDP messages from clients
|
||||
|
||||
// 1. initialize gameworld, and spin off infinite game-loop thread
|
||||
// 2. spin off sendNetworkUpdates() infinite loop thread
|
||||
// 3. infinitely wait for incoming UDP messages and process them (usually by just dropping user-commands into the relevant block of shared memory)
|
||||
|
||||
// 1. initialize gameworld, and spin off infinite game-loop thread
|
||||
arenaInit(&permanent_arena);
|
||||
arenaInit(&state.game_scratch);
|
||||
arenaInit(&state.string_arena.a);
|
||||
state.string_arena.mutex = newMutex();
|
||||
state.client_mutex = newMutex();
|
||||
state.mutex = newMutex();
|
||||
state.network_recv_queue = newPCCThreadQueue(&permanent_arena);
|
||||
state.network_send_queue = newOutgoingMessageQueue(&permanent_arena);
|
||||
// alloc the global hashmap of rooms
|
||||
// init + alloc clients
|
||||
state.clients.capacity = SERVER_MAX_CLIENTS;
|
||||
state.clients.length = 1; // making entry 0 to be a "null" client
|
||||
state.clients.items = (Client*)arenaAllocArray(&permanent_arena, Client, SERVER_MAX_CLIENTS);
|
||||
state.accounts.capacity = ACCOUNT_CHUNK_SIZE;
|
||||
state.accounts.items = arenaAllocArray(&permanent_arena, Account, ACCOUNT_CHUNK_SIZE);
|
||||
|
||||
// 2. spin off sendNetworkUpdates() infinite loop thread
|
||||
UDPServer listener = createUDPServer(SERVER_PORT);
|
||||
if (!listener.ready) {
|
||||
exitWithErrorMessage("Couldn't start the udp server");
|
||||
}
|
||||
Thread send_thread = spawnThread(&sendNetworkUpdates, &listener.server_socket);
|
||||
// 3. infinitely wait for incoming UDP messages and process them (usually by just dropping user-commands into the relevant block of shared memory)
|
||||
Thread recv_thread = spawnThread(&receiveNetworkUpdates, &listener);
|
||||
|
||||
u64 lane_broadcast_val = 0;
|
||||
Barrier barrier = osBarrierAlloc(GAME_THREAD_CONCURRENCY);
|
||||
LaneCtx lane_ctxs[GAME_THREAD_CONCURRENCY] = {0};
|
||||
Thread game_threads[GAME_THREAD_CONCURRENCY] = {0};
|
||||
for (u32 i = 0; i < GAME_THREAD_CONCURRENCY; i++) {
|
||||
lane_ctxs[i].lane_idx = i;
|
||||
lane_ctxs[i].lane_count = GAME_THREAD_CONCURRENCY;
|
||||
lane_ctxs[i].barrier = barrier;
|
||||
lane_ctxs[i].broadcast_memory = &lane_broadcast_val;
|
||||
game_threads[i] = spawnThread(&gameLoop, &lane_ctxs[i]);
|
||||
}
|
||||
for (u32 i = 0; i < GAME_THREAD_CONCURRENCY; i++) {
|
||||
osThreadJoin(game_threads[i], MAX_u64);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
#include "base/all.h"
|
||||
|
||||
#ifndef GAMESHARED_H
|
||||
#define GAMESHARED_H
|
||||
|
||||
///// #define some game-tunable constants
|
||||
#define GAME_CONSTANT_ONE (1)
|
||||
#define GAME_CONSTANT_TWO (2)
|
||||
|
||||
typedef enum EntityFeature {
|
||||
FeatureWalksAround,
|
||||
FeatureCanFight,
|
||||
EntityFeature_Count
|
||||
} EntityFeature;
|
||||
|
||||
typedef enum EntityType {
|
||||
EntityNull,
|
||||
EntityWall,
|
||||
EntityDoor,
|
||||
EntityCharacter,
|
||||
EntityType_Count,
|
||||
} EntityType;
|
||||
static const char* ENTITY_STRINGS[] = {
|
||||
"NULL",
|
||||
"Wall",
|
||||
"Door",
|
||||
"Character",
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
i32 x;
|
||||
i32 y;
|
||||
i32 z;
|
||||
} XYZ;
|
||||
|
||||
typedef enum Direction {
|
||||
DirectionInvalid,
|
||||
North,
|
||||
South,
|
||||
East,
|
||||
West,
|
||||
Up,
|
||||
Down,
|
||||
Direction_Count
|
||||
} Direction;
|
||||
|
||||
typedef enum CommandType {
|
||||
CommandInvalid,
|
||||
CommandKeepAlive,
|
||||
CommandLogin,
|
||||
CommandCreateCharacter,
|
||||
CommandType_Count,
|
||||
} CommandType;
|
||||
static const char* command_type_strings[] = {
|
||||
"Invalid",
|
||||
"KeepAlive",
|
||||
"Login",
|
||||
"CreateCharacter",
|
||||
};
|
||||
|
||||
#define ENTITY_HEADER_MESSAGE_SIZE (8+8+1+1+1)
|
||||
#define ENTITY_MESSAGE_SIZE (ENTITY_HEADER_MESSAGE_SIZE+2+2+2+2+8+1+1)
|
||||
typedef enum Message {
|
||||
MessageInvalid,
|
||||
MessageCharacterId,
|
||||
MessageBadPw,
|
||||
MessageNewAccountCreated,
|
||||
Message_Count,
|
||||
} Message;
|
||||
static const char* MESSAGE_STRINGS[] = {
|
||||
"Invalid",
|
||||
"CharacterId",
|
||||
"BadPw",
|
||||
"NewAccountCreated",
|
||||
};
|
||||
|
||||
#endif //GAMESHARED_H
|
||||
@@ -0,0 +1,162 @@
|
||||
#include "string_chunk.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));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#ifndef STRING_CHUNK_H
|
||||
#define STRING_CHUNK_H
|
||||
|
||||
#include "base/all.h"
|
||||
|
||||
#define STRING_CHUNK_PAYLOAD_SIZE (64 - sizeof(StringChunk*))
|
||||
|
||||
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 //STRING_CHUNK_H
|
||||
Reference in New Issue
Block a user