empty client-server game

This commit is contained in:
Tenari
2026-01-31 09:59:03 -06:00
commit 565c1562c6
32 changed files with 13104 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
*.swp
build/*
Executable
+47
View File
@@ -0,0 +1,47 @@
#!/bin/sh
if [ "$1" = "server" ]; then
echo "building server"
rm ./build/server
rm -rf ./build/server.dSYM
if [[ $(uname) == "Darwin" ]]; then
echo " for macOS"
gcc -std=c99 -g -o build/server src/server.c -lpthread
else
echo " for Linux"
gcc -std=c99 -D_POSIX_C_SOURCE=200809L -g -o build/server src/server.c -lpthread
fi
if [ "$2" = "run" ]; then
./build/server
elif [ "$2" = "debug-run" ]; then
../lldbg/bin/lldbgui ./build/server
fi
elif [ "$1" = "editor" ]; then
echo "building editor"
rm ./build/editor
rm -rf ./build/editor.dSYM
gcc -std=c99 -g -o build/editor src/room_editor.c
if [ "$2" = "run" ]; then
./build/editor $3
fi
else
#echo "building assets"
#for item in "asset1" "asset2" "asset3"; do
# rm "src/assets/$item.h"
# xxd -i "assets/$item.txt" > "src/assets/$item.h"
#done
echo "building client"
rm ./build/client
rm -rf ./build/client.dSYM
gcc -std=c99 -g -o build/client src/client.c -lpthread
if [ "$2" = "run" ]; then
if [ "$3" ]; then
./build/client $3
else
./build/client
fi
elif [ "$2" = "debug-run" ]; then
../lldbg/bin/lldbgui ./build/client
fi
fi
View File
+599
View File
@@ -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
+45
View File
@@ -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) {
}
+12
View File
@@ -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
+139
View File
@@ -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);
}
}
+136
View File
@@ -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);
}
}
+68
View File
@@ -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--;
}
}
+94
View File
@@ -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);
}
+12
View File
@@ -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
+102
View File
@@ -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 */
+49
View File
@@ -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 */
+66
View File
@@ -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);
}
+216
View File
@@ -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;
}
+83
View File
@@ -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);
}
}
+36
View File
@@ -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);
}
+114
View File
@@ -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
+147
View File
@@ -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;
}
+326
View File
@@ -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
View File
@@ -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;
}
+198
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+109
View File
@@ -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;
}
+29
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 &current->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 &current->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(&current->items[i].name, &name)) {
return &current->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 = &current->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;
}
+77
View File
@@ -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
+162
View File
@@ -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));
}
}
+33
View File
@@ -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
+4
View File
@@ -0,0 +1,4 @@
@echo off
del /q .\build\client.exe 2>nul
gcc --static -std=c99 -g -o build/client src/client.c -lpthread -lws2_32 -lwinmm -liphlpapi