better base + networking layer + remove some garbage + update client to not crash on tcp disconnect
This commit is contained in:
Vendored
+30
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Debug Client",
|
||||||
|
"type": "cppdbg",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceFolder}/build/client",
|
||||||
|
"args": [],
|
||||||
|
"stopAtEntry": false,
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"environment": [],
|
||||||
|
"externalConsole": true,
|
||||||
|
"MIMode": "lldb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Debug Server",
|
||||||
|
"type": "cppdbg",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceFolder}/build/server",
|
||||||
|
"args": [],
|
||||||
|
"stopAtEntry": false,
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"environment": [],
|
||||||
|
"externalConsole": true,
|
||||||
|
"MIMode": "lldb"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
+51
-25
@@ -238,6 +238,10 @@
|
|||||||
// only valid if there's an in-scope variable bool `debug_mode`
|
// only valid if there's an in-scope variable bool `debug_mode`
|
||||||
#define dbg(fmt, ...) osDebugPrint(debug_mode, fmt, ##__VA_ARGS__)
|
#define dbg(fmt, ...) osDebugPrint(debug_mode, fmt, ##__VA_ARGS__)
|
||||||
|
|
||||||
|
///// SYSTEM INCLUDES I always want
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
///// TYPES
|
///// TYPES
|
||||||
// integer types
|
// integer types
|
||||||
typedef unsigned char u8;
|
typedef unsigned char u8;
|
||||||
@@ -438,7 +442,27 @@ union Range1f32
|
|||||||
DWORD input_mode;
|
DWORD input_mode;
|
||||||
DWORD output_mode;
|
DWORD output_mode;
|
||||||
} TermIOs;
|
} TermIOs;
|
||||||
|
// <poll.h> networking shim for windows
|
||||||
|
#ifndef POLLIN
|
||||||
|
# define POLLIN 0x0001
|
||||||
|
# define POLLPRI 0x0002
|
||||||
|
# define POLLOUT 0x0004
|
||||||
|
# define POLLERR 0x0008
|
||||||
|
# define POLLHUP 0x0010
|
||||||
|
# define POLLNVAL 0x0020
|
||||||
|
|
||||||
|
typedef struct pollfd {
|
||||||
|
SOCKET fd;
|
||||||
|
short events;
|
||||||
|
short revents;
|
||||||
|
} pollfd_t;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef int nfds_t;
|
||||||
|
|
||||||
|
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
|
||||||
#else
|
#else
|
||||||
|
# include <poll.h>
|
||||||
# include <sys/socket.h>
|
# include <sys/socket.h>
|
||||||
# include <netinet/in.h>
|
# include <netinet/in.h>
|
||||||
# include <netdb.h>
|
# include <netdb.h>
|
||||||
@@ -569,40 +593,42 @@ fn bool isAlphaUnderscoreSpace(u8 c);
|
|||||||
fn bool isSimplePrintable(u8 c);
|
fn bool isSimplePrintable(u8 c);
|
||||||
|
|
||||||
///// OS-wrapped apis
|
///// OS-wrapped apis
|
||||||
void osInit();
|
void osInit();
|
||||||
void* osThreadContextGet();
|
void* osThreadContextGet();
|
||||||
void osThreadContextSet(void* ctx);
|
void osThreadContextSet(void* ctx);
|
||||||
|
bool osThreadJoin(Thread handle, u64 endt_us);
|
||||||
|
|
||||||
fn Barrier osBarrierAlloc(u64 count);
|
fn Barrier osBarrierAlloc(u64 count);
|
||||||
fn void osBarrierRelease(Barrier barrier);
|
fn void osBarrierRelease(Barrier barrier);
|
||||||
fn void osBarrierWait(Barrier barrier);
|
fn void osBarrierWait(Barrier barrier);
|
||||||
|
|
||||||
// Memory
|
// Memory
|
||||||
fn void* osMemoryReserve(u64 size);
|
fn void* osMemoryReserve(u64 size);
|
||||||
fn void osMemoryCommit(void* memory, u64 size);
|
fn void osMemoryCommit(void* memory, u64 size);
|
||||||
fn void osMemoryDecommit(void* memory, u64 size);
|
fn void osMemoryDecommit(void* memory, u64 size);
|
||||||
fn void osMemoryRelease(void* memory, u64 size);
|
fn void osMemoryRelease(void* memory, u64 size);
|
||||||
fn u64 osTimeMicrosecondsNow();
|
fn u64 osTimeMicrosecondsNow();
|
||||||
fn void osSleepMicroseconds(u32 t);
|
fn void osSleepMicroseconds(u32 t);
|
||||||
|
|
||||||
fn bool osFileExists(String filename);
|
// Files
|
||||||
|
fn bool osFileExists(String filename);
|
||||||
fn String osFileRead(Arena* arena, ptr filepath);
|
fn String osFileRead(Arena* arena, ptr filepath);
|
||||||
fn bool osFileCreate(String filename);
|
fn bool osFileCreate(String filename);
|
||||||
fn bool osFileCreateWrite(String filename, String data);
|
fn bool osFileCreateWrite(String filename, String data);
|
||||||
fn bool osFileWrite(String filename, String data);
|
fn bool osFileWrite(String filename, String data);
|
||||||
|
|
||||||
fn void osDebugPrint(bool debug_mode, const char* format, ...);
|
fn void osDebugPrint(bool debug_mode, const char* format, ...);
|
||||||
|
|
||||||
TermIOs osStartTUI(bool blocking);
|
// Tui stuff
|
||||||
fn void osEndTUI(TermIOs old_terminal_attributes);
|
TermIOs osStartTUI(bool blocking);
|
||||||
fn Dim2 osGetTerminalDimensions();
|
fn void osEndTUI(TermIOs old_terminal_attributes);
|
||||||
void osBlitToTerminal(ptr writeable_output_ansi_string, i64 count);
|
fn Dim2 osGetTerminalDimensions();
|
||||||
void osReadConsoleInput(u8* buffer, u32 len);
|
void osBlitToTerminal(ptr writeable_output_ansi_string, i64 count);
|
||||||
|
void osReadConsoleInput(u8* buffer, u32 len);
|
||||||
|
|
||||||
bool osInitNetwork();
|
// network stuff
|
||||||
i32 osLanIPAddress();
|
bool osInitNetwork();
|
||||||
|
i32 osLanIPAddress();
|
||||||
bool osThreadJoin(Thread handle, u64 endt_us);
|
|
||||||
|
|
||||||
///// Basic THREAD synchronization apis
|
///// Basic THREAD synchronization apis
|
||||||
Thread spawnThread(void * (*threadFn)(void *), void* thread_arg);
|
Thread spawnThread(void * (*threadFn)(void *), void* thread_arg);
|
||||||
|
|||||||
+74
-140
@@ -9,14 +9,13 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "base/impl.c"
|
#include "base/impl.c"
|
||||||
#include "lib/network.c"
|
#include "lib/network.c"
|
||||||
#include "render.c"
|
#include "lib/tui.c"
|
||||||
|
#include "shared.h"
|
||||||
//#include "assets/asset1.h"
|
//#include "assets/asset1.h"
|
||||||
//#include "assets/asset2.h"
|
//#include "assets/asset2.h"
|
||||||
//#include "assets/asset3.h"
|
//#include "assets/asset3.h"
|
||||||
|
|
||||||
///// #define a bunch of client-only tunable game constants
|
///// #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_LOOPS_PER_S 50
|
||||||
#define GOAL_LOOP_US 1000000/GOAL_LOOPS_PER_S
|
#define GOAL_LOOP_US 1000000/GOAL_LOOPS_PER_S
|
||||||
#define LOGIN_NAME_BUFFER_LEN 16
|
#define LOGIN_NAME_BUFFER_LEN 16
|
||||||
@@ -102,6 +101,8 @@ typedef struct MenuState {
|
|||||||
} MenuState;
|
} MenuState;
|
||||||
|
|
||||||
typedef struct GameState {
|
typedef struct GameState {
|
||||||
|
u32 udp_pings_sent;
|
||||||
|
u32 udp_pongs_recieved;
|
||||||
Screen screen;
|
Screen screen;
|
||||||
Screen old_screen;
|
Screen old_screen;
|
||||||
ThingList things;
|
ThingList things;
|
||||||
@@ -114,8 +115,7 @@ typedef struct GameState {
|
|||||||
MenuState menu;
|
MenuState menu;
|
||||||
MenuState section; // for tabbing through selected "portions" of the screen
|
MenuState section; // for tabbing through selected "portions" of the screen
|
||||||
u8 choices[4];
|
u8 choices[4];
|
||||||
UDPMessage keep_alive_msg;
|
MultiClient client;
|
||||||
UDPClient client;
|
|
||||||
StringChunkList message_input;
|
StringChunkList message_input;
|
||||||
} GameState;
|
} GameState;
|
||||||
|
|
||||||
@@ -123,8 +123,6 @@ typedef struct GameState {
|
|||||||
global bool should_quit;
|
global bool should_quit;
|
||||||
global bool debug_mode = false;
|
global bool debug_mode = false;
|
||||||
global Arena permanent_arena = {0};
|
global Arena permanent_arena = {0};
|
||||||
global u8List system_messages[SYSTEM_MESSAGES_LEN] = {0};
|
|
||||||
global u8 system_message_index = 0;
|
|
||||||
global GameState state = {0};
|
global GameState state = {0};
|
||||||
global OutgoingMessageQueue* network_send_queue = {0};
|
global OutgoingMessageQueue* network_send_queue = {0};
|
||||||
global ParsedServerMessageThreadQueue* network_recv_queue = {0};
|
global ParsedServerMessageThreadQueue* network_recv_queue = {0};
|
||||||
@@ -201,81 +199,6 @@ fn bool thingDelete(ThingList* list, u64 id) {
|
|||||||
return false;
|
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) {
|
fn void renderSpeechBubble(TuiState* tui, u16 x, u16 y, u16 max_width, String message, SpeechTailDirection dir) {
|
||||||
Pixel* buf = tui->frame_buffer;
|
Pixel* buf = tui->frame_buffer;
|
||||||
Dim2 screen_dimensions = tui->screen_dimensions;
|
Dim2 screen_dimensions = tui->screen_dimensions;
|
||||||
@@ -321,23 +244,6 @@ fn void renderSpeechBubble(TuiState* tui, u16 x, u16 y, u16 max_width, String me
|
|||||||
// TODO actually print the speech tail
|
// 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() {
|
fn void clearServerSentState() {
|
||||||
//memset(&state.current_room, 0, sizeof(RenderableRoom));
|
//memset(&state.current_room, 0, sizeof(RenderableRoom));
|
||||||
|
|
||||||
@@ -347,7 +253,11 @@ fn void clearServerSentState() {
|
|||||||
state.things.items = arenaAllocArray(&state.thing_arena, Thing, state.things.capacity);
|
state.things.items = arenaAllocArray(&state.thing_arena, Thing, state.things.capacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn void handleIncomingMessage(u8* message, u32 len, SocketAddress sender, i32 socket) {
|
fn void handleIncomingMessage(u8* message, i32 len, SocketAddress sender, i32 socket) {
|
||||||
|
if (len <= 0) {
|
||||||
|
addSystemMessage((u8*)"len<=0 messages not supported");
|
||||||
|
return;
|
||||||
|
}
|
||||||
Message msg_type = message[0];
|
Message msg_type = message[0];
|
||||||
dbg("handleIncomingMessage() of len=%d, message=%s\n", len, MESSAGE_STRINGS[msg_type]);
|
dbg("handleIncomingMessage() of len=%d, message=%s\n", len, MESSAGE_STRINGS[msg_type]);
|
||||||
u8List bytes = {len, len, message};
|
u8List bytes = {len, len, message};
|
||||||
@@ -355,6 +265,7 @@ fn void handleIncomingMessage(u8* message, u32 len, SocketAddress sender, i32 so
|
|||||||
ParsedServerMessage parsed = {0};
|
ParsedServerMessage parsed = {0};
|
||||||
parsed.type = msg_type;
|
parsed.type = msg_type;
|
||||||
switch (msg_type) {
|
switch (msg_type) {
|
||||||
|
case MessageUDPPong:
|
||||||
case MessageNewAccountCreated:
|
case MessageNewAccountCreated:
|
||||||
case MessageBadPw: {/*nothing to parse but the type*/} break;
|
case MessageBadPw: {/*nothing to parse but the type*/} break;
|
||||||
case MessageCharacterId: {
|
case MessageCharacterId: {
|
||||||
@@ -370,29 +281,38 @@ fn void handleIncomingMessage(u8* message, u32 len, SocketAddress sender, i32 so
|
|||||||
psmThreadSafeQueuePush(network_recv_queue, &parsed);
|
psmThreadSafeQueuePush(network_recv_queue, &parsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn void* receiveNetworkUpdates(void* udp) {
|
fn void* receiveNetworkUpdates(void* multi) {
|
||||||
UDPClient client = *(UDPClient*)udp;
|
MultiClient *client = (MultiClient*)multi;
|
||||||
dbg("receiveNetworkUpdates() sock=%d\n", client.socket);
|
u32 usec_to_sleep = 1000000; // start @ 1 sec
|
||||||
UDPServer server = {
|
while (!should_quit) {
|
||||||
.ready = true,
|
if (!client->tcp_client.ready) {
|
||||||
.server_address = client.server_address,
|
osSleepMicroseconds(usec_to_sleep);
|
||||||
.server_socket = client.socket
|
netReconnectTCPClient(&client->tcp_client);
|
||||||
};
|
}
|
||||||
infiniteReadUDPServer(&server, handleIncomingMessage);
|
if (client->tcp_client.ready) {
|
||||||
|
usec_to_sleep = 1000000; // start @ 1 sec
|
||||||
|
netInfiniteReadMultiClient(client, &should_quit, handleIncomingMessage, addSystemMessage);
|
||||||
|
} else {
|
||||||
|
usec_to_sleep = Min(usec_to_sleep * 1.5, 60000000); // cap out at 1 minute of sleeping
|
||||||
|
}
|
||||||
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn void* sendNetworkUpdates(void* udp) {
|
fn void* sendNetworkUpdates(void* multi_client) {
|
||||||
i32 socket_fd = ((UDPClient*)udp)->socket;
|
MultiClient *client = (MultiClient*)multi_client;
|
||||||
dbg("sendNetworkUpdates() sock=%d\n", socket_fd);
|
|
||||||
u8List bytes_list = {0};
|
u8List bytes_list = {0};
|
||||||
while (!should_quit) {
|
while (!should_quit) {
|
||||||
UDPMessage msg = {0};
|
NetworkMessage msg = {0};
|
||||||
outgoingMessageQueuePop(network_send_queue, &msg);
|
outgoingMessageQueuePop(network_send_queue, &msg);
|
||||||
bytes_list.items = msg.bytes;
|
if (msg.tcp) {
|
||||||
bytes_list.length = msg.bytes_len;
|
sendTCPMessage(msg);
|
||||||
bytes_list.capacity = msg.bytes_len;
|
} else {
|
||||||
sendUDPu8List(socket_fd, &msg.address, &bytes_list);
|
bytes_list.items = msg.bytes;
|
||||||
|
bytes_list.length = msg.bytes_len;
|
||||||
|
bytes_list.capacity = msg.bytes_len;
|
||||||
|
sendUDPu8List(client->udp_client.socket, &msg.address, &bytes_list);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -400,7 +320,6 @@ fn void* sendNetworkUpdates(void* udp) {
|
|||||||
fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count) {
|
fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count) {
|
||||||
GameState* state = (GameState*) s;
|
GameState* state = (GameState*) s;
|
||||||
state->loop_count = loop_count;
|
state->loop_count = loop_count;
|
||||||
UDPClient* udp = &state->client;
|
|
||||||
state->old_screen = state->screen;
|
state->old_screen = state->screen;
|
||||||
Dim2 screen_dimensions = tui->screen_dimensions;
|
Dim2 screen_dimensions = tui->screen_dimensions;
|
||||||
bool game_screen_changed = state->old_screen != state->screen; // detect new screens
|
bool game_screen_changed = state->old_screen != state->screen; // detect new screens
|
||||||
@@ -410,6 +329,13 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count
|
|||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
return should_quit;
|
return should_quit;
|
||||||
}
|
}
|
||||||
|
char sbuf[SBUFLEN] = {0};
|
||||||
|
if (!state->client.tcp_client.ready) {
|
||||||
|
state->screen = ScreenLogin;
|
||||||
|
state->login_state.state = LoginScreenStateInit;
|
||||||
|
renderStrToBuffer(tui->frame_buffer, 5, 1, "Network disconnected. Reconnecting...", screen_dimensions);
|
||||||
|
return should_quit;
|
||||||
|
}
|
||||||
|
|
||||||
// process server messages
|
// process server messages
|
||||||
u32 msg_iters = 0;
|
u32 msg_iters = 0;
|
||||||
@@ -418,6 +344,9 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count
|
|||||||
while (next_net_msg != NULL) {
|
while (next_net_msg != NULL) {
|
||||||
msg_iters += 0;
|
msg_iters += 0;
|
||||||
switch (msg.type) {
|
switch (msg.type) {
|
||||||
|
case MessageUDPPong: {
|
||||||
|
state->udp_pongs_recieved += 1;
|
||||||
|
} break;
|
||||||
case MessageNewAccountCreated: {
|
case MessageNewAccountCreated: {
|
||||||
state->screen = ScreenCreateCharacter;
|
state->screen = ScreenCreateCharacter;
|
||||||
} break;
|
} break;
|
||||||
@@ -441,7 +370,7 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count
|
|||||||
next_net_msg = psmThreadSafeNonblockingQueuePop(network_recv_queue, &msg);
|
next_net_msg = psmThreadSafeNonblockingQueuePop(network_recv_queue, &msg);
|
||||||
msg_iters++;
|
msg_iters++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// operate on screen-based input+state
|
// operate on screen-based input+state
|
||||||
bool user_pressed_esc = input_buffer[0] == ASCII_ESCAPE && input_buffer[1] == 0;
|
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_a_number = input_buffer[0] >= '1' && input_buffer[0] <= '9' && input_buffer[1] == 0;
|
||||||
@@ -476,8 +405,8 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count
|
|||||||
state->menu.len = 2;
|
state->menu.len = 2;
|
||||||
} else {
|
} else {
|
||||||
// send character color to server
|
// send character color to server
|
||||||
UDPMessage msg = {0};
|
NetworkMessage msg = { .tcp = true, .socket_fd = state->client.tcp_client.socket };
|
||||||
msg.address = udp->server_address;
|
msg.address = state->client.tcp_client.server_address;
|
||||||
msg.bytes_len = 2;
|
msg.bytes_len = 2;
|
||||||
// 1. msg type/CommandType
|
// 1. msg type/CommandType
|
||||||
msg.bytes[0] = CommandCreateCharacter;
|
msg.bytes[0] = CommandCreateCharacter;
|
||||||
@@ -513,8 +442,23 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count
|
|||||||
should_quit = true;
|
should_quit = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (input_buffer[0] == 'p' && input_buffer[1] == 0) {
|
||||||
|
NetworkMessage msg = { .tcp = false, .socket_fd = state->client.udp_client.socket };
|
||||||
|
msg.address = state->client.udp_client.server_address;
|
||||||
|
msg.bytes[0] = CommandUDPPing;
|
||||||
|
msg.bytes_len = 1;
|
||||||
|
outgoingMessageQueuePush(network_send_queue, &msg);
|
||||||
|
|
||||||
|
state->udp_pings_sent += 1;
|
||||||
|
}
|
||||||
|
|
||||||
// RENDERING
|
// RENDERING
|
||||||
renderStrToBuffer(tui->frame_buffer, 5, 1, "Games typically would render something here...", screen_dimensions);
|
renderStrToBuffer(tui->frame_buffer, 5, 1, "Games typically would render something here...", screen_dimensions);
|
||||||
|
|
||||||
|
MemoryZero(sbuf, SBUFLEN);
|
||||||
|
sprintf(sbuf, "Press 'P' to send UDP Ping. Pings: %d Pongs: %d", state->udp_pings_sent, state->udp_pongs_recieved);
|
||||||
|
renderStrToBuffer(tui->frame_buffer, 5, 2, sbuf, screen_dimensions);
|
||||||
|
|
||||||
bool room_active = state->section.selected_index == 0;
|
bool room_active = state->section.selected_index == 0;
|
||||||
u32 tabs_y = 1 + 2 + 2;
|
u32 tabs_y = 1 + 2 + 2;
|
||||||
u32 tx = 2;
|
u32 tx = 2;
|
||||||
@@ -573,13 +517,13 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count
|
|||||||
} else {
|
} else {
|
||||||
u32 msg_idx = 0;
|
u32 msg_idx = 0;
|
||||||
// drop the login message into the network_send_queue
|
// drop the login message into the network_send_queue
|
||||||
UDPMessage msg = {0};
|
NetworkMessage msg = { .tcp = true, .socket_fd = state->client.tcp_client.socket };
|
||||||
msg.address = udp->server_address;
|
msg.address = state->client.tcp_client.server_address;
|
||||||
// 1. msg type/CommandType
|
// 1. msg type/CommandType
|
||||||
msg.bytes[msg_idx++] = CommandLogin;
|
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
|
// 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
|
// and our "listened" UDP port
|
||||||
msg_idx += writeU16ToBufferLE(msg.bytes + msg_idx, ~(udp->client_port));
|
msg_idx += writeU16ToBufferLE(msg.bytes + msg_idx, ~(state->client.udp_client.client_port));
|
||||||
msg_idx += writeI32ToBufferLE(msg.bytes + msg_idx, ~osLanIPAddress());
|
msg_idx += writeI32ToBufferLE(msg.bytes + msg_idx, ~osLanIPAddress());
|
||||||
// 2. how long is the name
|
// 2. how long is the name
|
||||||
msg.bytes[msg_idx++] = state->login_state.name.length;
|
msg.bytes[msg_idx++] = state->login_state.name.length;
|
||||||
@@ -671,11 +615,6 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loop_count % 10 == 0) {
|
|
||||||
outgoingMessageQueuePush(network_send_queue, &state->keep_alive_msg);
|
|
||||||
//outgoingMessageQueuePush(network_send_queue, &testm);
|
|
||||||
}
|
|
||||||
|
|
||||||
return should_quit;
|
return should_quit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -698,11 +637,7 @@ i32 main(i32 argc, ptr argv[]) {
|
|||||||
arenaInit(&state.string_arena.a);
|
arenaInit(&state.string_arena.a);
|
||||||
state.string_arena.mutex = newMutex();
|
state.string_arena.mutex = newMutex();
|
||||||
state.message_input = stringChunkListInit(&state.string_arena);
|
state.message_input = stringChunkListInit(&state.string_arena);
|
||||||
for (i32 i = 0; i < SYSTEM_MESSAGES_LEN; i++) {
|
initSystemMessages(&permanent_arena);
|
||||||
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 queues
|
||||||
network_send_queue = newOutgoingMessageQueue(&permanent_arena);
|
network_send_queue = newOutgoingMessageQueue(&permanent_arena);
|
||||||
@@ -732,12 +667,11 @@ i32 main(i32 argc, ptr argv[]) {
|
|||||||
server_address = input_string;
|
server_address = input_string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
state.client = createUDPClient(7777, server_address);
|
state.client = netCreateMultiClient(7777, server_address);
|
||||||
|
if (!state.client.tcp_client.ready || !state.client.udp_client.ready) {
|
||||||
// "hardcoded" keep alive message to periodically send to server
|
printf("a net client wanst ready");
|
||||||
state.keep_alive_msg.address = state.client.server_address;
|
exit(1);
|
||||||
state.keep_alive_msg.bytes[0] = CommandKeepAlive;
|
}
|
||||||
state.keep_alive_msg.bytes_len = 1;
|
|
||||||
|
|
||||||
Thread recv_thread = spawnThread(&receiveNetworkUpdates, &state.client);
|
Thread recv_thread = spawnThread(&receiveNetworkUpdates, &state.client);
|
||||||
Thread send_thread = spawnThread(&sendNetworkUpdates, &state.client);
|
Thread send_thread = spawnThread(&sendNetworkUpdates, &state.client);
|
||||||
|
|||||||
+477
-32
@@ -6,14 +6,40 @@
|
|||||||
#define NET_OUTGOING_MESSAGE_QUEUE_LEN 16
|
#define NET_OUTGOING_MESSAGE_QUEUE_LEN 16
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef NET_SERVER_MAX_CLIENTS
|
||||||
|
#define NET_SERVER_MAX_CLIENTS (16)
|
||||||
|
#endif
|
||||||
|
|
||||||
typedef struct sockaddr_in SocketAddress;
|
typedef struct sockaddr_in SocketAddress;
|
||||||
|
|
||||||
|
typedef struct TCPServer {
|
||||||
|
bool ready;
|
||||||
|
i32 socket_fd;
|
||||||
|
SocketAddress address;
|
||||||
|
} TCPServer;
|
||||||
|
|
||||||
typedef struct UDPServer {
|
typedef struct UDPServer {
|
||||||
bool ready;
|
bool ready;
|
||||||
SocketAddress server_address;
|
SocketAddress server_address;
|
||||||
i32 server_socket;
|
i32 server_socket;
|
||||||
} UDPServer;
|
} UDPServer;
|
||||||
|
|
||||||
|
typedef struct MultiServer {
|
||||||
|
TCPServer tcp_server;
|
||||||
|
UDPServer udp_server;
|
||||||
|
} MultiServer;
|
||||||
|
|
||||||
|
typedef struct ServableUDPInfo {
|
||||||
|
UDPServer server;
|
||||||
|
void (*callback)(u8* udp_message, i32 udp_len, SocketAddress sending_address, i32 socket);
|
||||||
|
} ServableUDPInfo;
|
||||||
|
|
||||||
|
typedef struct TCPClient {
|
||||||
|
bool ready;
|
||||||
|
SocketAddress server_address;
|
||||||
|
i32 socket;
|
||||||
|
} TCPClient;
|
||||||
|
|
||||||
typedef struct UDPClient {
|
typedef struct UDPClient {
|
||||||
bool ready;
|
bool ready;
|
||||||
SocketAddress server_address;
|
SocketAddress server_address;
|
||||||
@@ -21,14 +47,22 @@ typedef struct UDPClient {
|
|||||||
i32 socket;
|
i32 socket;
|
||||||
} UDPClient;
|
} UDPClient;
|
||||||
|
|
||||||
typedef struct UDPMessage {
|
typedef struct MultiClient {
|
||||||
|
TCPClient tcp_client;
|
||||||
|
UDPClient udp_client;
|
||||||
|
} MultiClient;
|
||||||
|
|
||||||
|
typedef struct NetworkMessage {
|
||||||
|
bool tcp; // default = false = UDP, just sendto(address)
|
||||||
u16 bytes_len;
|
u16 bytes_len;
|
||||||
|
i32 socket_fd;
|
||||||
|
char* long_bytes; // used when message is longer than UDP max, memory must be managed by caller
|
||||||
SocketAddress address;
|
SocketAddress address;
|
||||||
u8 bytes[UDP_MAX_MESSAGE_LEN];
|
u8 bytes[UDP_MAX_MESSAGE_LEN];
|
||||||
} UDPMessage;
|
} NetworkMessage;
|
||||||
|
|
||||||
typedef struct OutgoingMessageQueue {
|
typedef struct OutgoingMessageQueue {
|
||||||
UDPMessage items[NET_OUTGOING_MESSAGE_QUEUE_LEN];
|
NetworkMessage items[NET_OUTGOING_MESSAGE_QUEUE_LEN];
|
||||||
u32 head;
|
u32 head;
|
||||||
u32 tail;
|
u32 tail;
|
||||||
u32 count;
|
u32 count;
|
||||||
@@ -37,6 +71,7 @@ typedef struct OutgoingMessageQueue {
|
|||||||
Cond not_full;
|
Cond not_full;
|
||||||
} OutgoingMessageQueue;
|
} OutgoingMessageQueue;
|
||||||
|
|
||||||
|
typedef void (*HandleMessageCb)(u8* udp_message, i32 bytes_recieved, SocketAddress sending_address, i32 socket);
|
||||||
fn OutgoingMessageQueue* newOutgoingMessageQueue(Arena* a) {
|
fn OutgoingMessageQueue* newOutgoingMessageQueue(Arena* a) {
|
||||||
OutgoingMessageQueue* result = arenaAlloc(a, sizeof(OutgoingMessageQueue));
|
OutgoingMessageQueue* result = arenaAlloc(a, sizeof(OutgoingMessageQueue));
|
||||||
MemoryZero(result, (sizeof *result));
|
MemoryZero(result, (sizeof *result));
|
||||||
@@ -46,7 +81,7 @@ fn OutgoingMessageQueue* newOutgoingMessageQueue(Arena* a) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn void outgoingMessageQueuePush(OutgoingMessageQueue* queue, UDPMessage* msg) {
|
fn void outgoingMessageQueuePush(OutgoingMessageQueue* queue, NetworkMessage* msg) {
|
||||||
lockMutex(&queue->mutex); {
|
lockMutex(&queue->mutex); {
|
||||||
while (queue->count == NET_OUTGOING_MESSAGE_QUEUE_LEN) {
|
while (queue->count == NET_OUTGOING_MESSAGE_QUEUE_LEN) {
|
||||||
waitForCondSignal(&queue->not_full, &queue->mutex);
|
waitForCondSignal(&queue->not_full, &queue->mutex);
|
||||||
@@ -60,11 +95,11 @@ fn void outgoingMessageQueuePush(OutgoingMessageQueue* queue, UDPMessage* msg) {
|
|||||||
} unlockMutex(&queue->mutex);
|
} unlockMutex(&queue->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn UDPMessage* outgoingMessageNonblockingQueuePop(OutgoingMessageQueue* q, UDPMessage* copy_target) {
|
fn NetworkMessage* outgoingMessageNonblockingQueuePop(OutgoingMessageQueue* q, NetworkMessage* copy_target) {
|
||||||
// immediately returns NULL if there's nothing in the ThreadQueue
|
// immediately returns NULL if there's nothing in the ThreadQueue
|
||||||
// copies the ParsedClientCommand into `copy_target` if there is something in the queue
|
// copies the ParsedClientCommand into `copy_target` if there is something in the queue
|
||||||
// and marks it as popped from the queue
|
// and marks it as popped from the queue
|
||||||
UDPMessage* result = NULL;
|
NetworkMessage* result = NULL;
|
||||||
|
|
||||||
lockMutex(&q->mutex); {
|
lockMutex(&q->mutex); {
|
||||||
if (q->count > 0) {
|
if (q->count > 0) {
|
||||||
@@ -80,8 +115,8 @@ fn UDPMessage* outgoingMessageNonblockingQueuePop(OutgoingMessageQueue* q, UDPMe
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn UDPMessage* outgoingMessageQueuePop(OutgoingMessageQueue* q, UDPMessage* copy_target) {
|
fn NetworkMessage* outgoingMessageQueuePop(OutgoingMessageQueue* q, NetworkMessage* copy_target) {
|
||||||
UDPMessage* result = NULL;
|
NetworkMessage* result = NULL;
|
||||||
|
|
||||||
lockMutex(&q->mutex); {
|
lockMutex(&q->mutex); {
|
||||||
while (q->count == 0) {
|
while (q->count == 0) {
|
||||||
@@ -99,17 +134,43 @@ fn UDPMessage* outgoingMessageQueuePop(OutgoingMessageQueue* q, UDPMessage* copy
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn bool socketAddressEqual(SocketAddress a, SocketAddress b) {
|
fn bool socketAddressEqual(SocketAddress a, SocketAddress b) {
|
||||||
return a.sin_addr.s_addr == b.sin_addr.s_addr
|
return a.sin_addr.s_addr == b.sin_addr.s_addr
|
||||||
&& a.sin_port == b.sin_port;
|
&& a.sin_port == b.sin_port;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i32 netRecvExact(i32 socket, void* buf, u16 bytes_to_recv) {
|
||||||
|
i32 got, got_this_iter;
|
||||||
|
for (got = 0; got < bytes_to_recv; got += got_this_iter) {
|
||||||
|
got_this_iter = recv(socket, (char*)buf + got, bytes_to_recv - got, 0);
|
||||||
|
if (got_this_iter <= 0) return got_this_iter;
|
||||||
|
}
|
||||||
|
return got;
|
||||||
|
}
|
||||||
|
|
||||||
|
i32 netRecvMessage(i32 socket, u8* message_buffer, void (*addSystemMessage)(u8* msg)) {
|
||||||
|
i32 bytes_recieved;
|
||||||
|
u16 msg_len;
|
||||||
|
i32 first_recv_got = netRecvExact(socket, &msg_len, 2);
|
||||||
|
if (first_recv_got <= 0) {
|
||||||
|
return first_recv_got;
|
||||||
|
}
|
||||||
|
msg_len = ntohs(msg_len); // parse it to our correct byte order
|
||||||
|
|
||||||
|
bytes_recieved = netRecvExact(socket, message_buffer, msg_len);
|
||||||
|
if (addSystemMessage != NULL) {
|
||||||
|
char sbuf[128] = {0};
|
||||||
|
sprintf(sbuf, "bytes_recieved=%d\n", bytes_recieved);
|
||||||
|
addSystemMessage((u8*)sbuf);
|
||||||
|
}
|
||||||
|
return bytes_recieved;
|
||||||
|
}
|
||||||
|
|
||||||
// ONLY WORKS ON POSIX. taken from https://gist.github.com/miekg/a61d55a8ec6560ad6c4a2747b21e6128
|
// 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
|
// 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
|
// that the server makes in order to specify a port/address that it's listening on
|
||||||
UDPServer createUDPServer(u16 server_port) {
|
UDPServer netCreateUDPServer(u16 server_port) {
|
||||||
UDPServer result = {0};
|
UDPServer result = {0};
|
||||||
// define the address we'll be listening on
|
// define the address we'll be listening on
|
||||||
result.server_address.sin_family = AF_INET;
|
result.server_address.sin_family = AF_INET;
|
||||||
@@ -130,7 +191,7 @@ UDPServer createUDPServer(u16 server_port) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
UDPClient createUDPClient(u16 server_port, str addr) {
|
UDPClient netCreateUDPClient(u16 server_port, str addr) {
|
||||||
UDPClient result = {0};
|
UDPClient result = {0};
|
||||||
// define the address we'll be listening on
|
// define the address we'll be listening on
|
||||||
result.server_address.sin_family = AF_INET;
|
result.server_address.sin_family = AF_INET;
|
||||||
@@ -159,40 +220,424 @@ UDPClient createUDPClient(u16 server_port, str addr) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void infiniteReadUDPServer(UDPServer* server, void (*handleMessage)(u8* udp_message, u32 udp_len, SocketAddress sending_address, i32 socket)) {
|
void netInfiniteReadUDPClient(UDPClient* client, HandleMessageCb handleMessage) {
|
||||||
|
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(client->socket, message_buffer, UDP_MAX_MESSAGE_LEN, 0, (struct sockaddr *)&client_address, (socklen_t*)&addrlen);
|
||||||
|
handleMessage(message_buffer, bytes_recieved, client_address, client->socket);
|
||||||
|
MemoryZero(message_buffer, UDP_MAX_MESSAGE_LEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void netInfiniteReadUDPServer(UDPServer* server, HandleMessageCb handleMessage) {
|
||||||
u8 message_buffer[UDP_MAX_MESSAGE_LEN] = {0};
|
u8 message_buffer[UDP_MAX_MESSAGE_LEN] = {0};
|
||||||
i32 bytes_recieved = 0;
|
i32 bytes_recieved = 0;
|
||||||
SocketAddress client_address = {0};
|
SocketAddress client_address = {0};
|
||||||
i32 addrlen = sizeof(struct sockaddr);
|
i32 addrlen = sizeof(struct sockaddr);
|
||||||
while (true) {
|
while (true) {
|
||||||
bytes_recieved = recvfrom(server->server_socket, message_buffer, UDP_MAX_MESSAGE_LEN, 0, (struct sockaddr *)&client_address, (socklen_t*)&addrlen);
|
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);
|
handleMessage(message_buffer, bytes_recieved, client_address, server->server_socket);
|
||||||
MemoryZero(message_buffer, UDP_MAX_MESSAGE_LEN);
|
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.
|
TCPClient netCreateTCPClient(u16 server_port, str addr) {
|
||||||
|
TCPClient 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_STREAM, 0);
|
||||||
|
if (result.socket < 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
socklen_t addr_len = sizeof(struct sockaddr_in);
|
||||||
|
i32 connect_result = connect(result.socket, (struct sockaddr *)&result.server_address, addr_len);
|
||||||
|
result.ready = connect_result != -1;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool netReconnectTCPClient(TCPClient* client) {
|
||||||
|
socklen_t addr_len = sizeof(struct sockaddr_in);
|
||||||
|
client->socket = socket(PF_INET, SOCK_STREAM, 0);
|
||||||
|
if (client->socket < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
i32 connect_result = connect(client->socket, (struct sockaddr *)&client->server_address, addr_len);
|
||||||
|
client->ready = connect_result != -1;
|
||||||
|
return client->ready;
|
||||||
|
}
|
||||||
|
|
||||||
|
TCPServer netCreateTCPServer(u16 server_port) {
|
||||||
|
TCPServer result = {0};
|
||||||
|
// define the address we'll be listening on
|
||||||
|
result.address.sin_family = AF_INET;
|
||||||
|
result.address.sin_addr.s_addr = inet_addr("0.0.0.0");//htonl(INADDR_ANY);
|
||||||
|
result.address.sin_port = htons(server_port);
|
||||||
|
|
||||||
|
// get a FileDescriptor number from the OS to use for our TCP socket
|
||||||
|
result.socket_fd = socket(PF_INET, SOCK_STREAM, 0);
|
||||||
|
if (result.socket_fd < 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
// to let us immediately kill and restart server
|
||||||
|
i32 optval = 1;
|
||||||
|
setsockopt(result.socket_fd, SOL_SOCKET, SO_REUSEADDR, (const void *)&optval, sizeof(i32));
|
||||||
|
|
||||||
|
// bind() the TCP
|
||||||
|
result.ready = bind(result.socket_fd, (struct sockaddr *)&result.address, sizeof(result.address)) >= 0;
|
||||||
|
if (result.ready) {
|
||||||
|
result.ready = result.ready && (listen(result.socket_fd, 10) >= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void netInfiniteReadTCPServer(
|
||||||
|
TCPServer* server,
|
||||||
|
bool* should_quit,
|
||||||
|
HandleMessageCb handleMessage,
|
||||||
|
void (*closeConnection)(i32 socket_fd),
|
||||||
|
void (*addSystemMessage)(u8* msg)
|
||||||
|
) {
|
||||||
|
u8 message_buffer[UDP_MAX_MESSAGE_LEN] = {0};
|
||||||
|
i32 bytes_recieved = 0;
|
||||||
|
SocketAddress client_address = {0};
|
||||||
|
i32 addrlen = sizeof(struct sockaddr);
|
||||||
|
i32 new_fd;
|
||||||
|
struct pollfd pollable_fds[NET_SERVER_MAX_CLIENTS+1] = {0}; // +1 for the listener socket
|
||||||
|
// poll the `listen()`ed socket_fd
|
||||||
|
pollable_fds[0].fd = server->socket_fd;
|
||||||
|
pollable_fds[0].events = POLLIN;
|
||||||
|
u32 pollable_fd_count = 1;
|
||||||
|
|
||||||
|
i32 poll_event_count;
|
||||||
|
while (*should_quit == false) {
|
||||||
|
poll_event_count = poll(pollable_fds, pollable_fd_count, 3000); // times out after 3 seconds so that quitting the server will actually quit the server process in relatively short order.
|
||||||
|
if (poll_event_count == -1) {
|
||||||
|
// TODO handle error better
|
||||||
|
*should_quit = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for(u32 i = 0; i < pollable_fd_count; i++) {
|
||||||
|
bool is_fd_readable = pollable_fds[i].revents & (POLLIN | POLLHUP);
|
||||||
|
if (is_fd_readable) {
|
||||||
|
bool is_fd_main_server_listener = pollable_fds[i].fd == server->socket_fd;
|
||||||
|
if (is_fd_main_server_listener) { // it's a new connection
|
||||||
|
new_fd = accept(server->socket_fd, (struct sockaddr *)&client_address, (socklen_t*)&addrlen);
|
||||||
|
if (new_fd == -1) {
|
||||||
|
if (addSystemMessage != NULL) {
|
||||||
|
addSystemMessage((u8*)"TODO: handle this accept() error for real, bitch");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (pollable_fd_count < NET_SERVER_MAX_CLIENTS+1) {
|
||||||
|
pollable_fds[pollable_fd_count].fd = new_fd;
|
||||||
|
pollable_fds[pollable_fd_count].events = POLLIN | POLLHUP;
|
||||||
|
pollable_fds[pollable_fd_count].revents = 0;
|
||||||
|
pollable_fd_count++;
|
||||||
|
if (addSystemMessage != NULL) {
|
||||||
|
char sbuf[128] = {0};
|
||||||
|
sprintf(sbuf, "new connection on socket=%d, total=%d\n", new_fd, pollable_fd_count);
|
||||||
|
addSystemMessage((u8*)sbuf);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
send(new_fd, "server full", 11, 0); // go away sir, we are out of space to keep track of this socket
|
||||||
|
close(new_fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {// Otherwise we're just a regular client
|
||||||
|
bytes_recieved = netRecvMessage(pollable_fds[i].fd, message_buffer, addSystemMessage);
|
||||||
|
if (bytes_recieved <= 0) { // error condition
|
||||||
|
bool client_hung_up = bytes_recieved == 0;
|
||||||
|
// TODO do something with the error case
|
||||||
|
closeConnection(pollable_fds[i].fd);
|
||||||
|
close(pollable_fds[i].fd);
|
||||||
|
if (addSystemMessage != NULL) {
|
||||||
|
char sbuf[256] = {0};
|
||||||
|
sprintf(sbuf, "closed connection on socket=%d, client_hung_up? %s\n", pollable_fds[i].fd, client_hung_up ? "yes" : "no");
|
||||||
|
addSystemMessage((u8*)sbuf);
|
||||||
|
}
|
||||||
|
// copy the last one over the current one and "forget" the last one by decrementing the count
|
||||||
|
pollable_fds[i] = pollable_fds[--pollable_fd_count];
|
||||||
|
} else { // we got an actual message from this guy
|
||||||
|
handleMessage(message_buffer, bytes_recieved, client_address, pollable_fds[i].fd);
|
||||||
|
}
|
||||||
|
MemoryZero(message_buffer, UDP_MAX_MESSAGE_LEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void netInfiniteReadTCPClient(
|
||||||
|
TCPClient *client,
|
||||||
|
bool* should_quit,
|
||||||
|
HandleMessageCb handleMessage,
|
||||||
|
void (*addSystemMessage)(u8* msg)
|
||||||
|
) {
|
||||||
|
u8 message_buffer[UDP_MAX_MESSAGE_LEN] = {0};
|
||||||
|
i32 bytes_recieved = 0;
|
||||||
|
bool got_connection_error = false;
|
||||||
|
while ((*should_quit) == false && got_connection_error == false) {
|
||||||
|
bytes_recieved = netRecvMessage(client->socket, message_buffer, addSystemMessage);
|
||||||
|
if (bytes_recieved == -1) {
|
||||||
|
if (addSystemMessage != NULL) {
|
||||||
|
char sbuf[128] = {0};
|
||||||
|
sprintf(sbuf, "TODO handle this error, bytes_recieved=%d\n", bytes_recieved);
|
||||||
|
addSystemMessage((u8*)sbuf);
|
||||||
|
}
|
||||||
|
got_connection_error = true;
|
||||||
|
client->ready = false;
|
||||||
|
close(client->socket);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMessage(message_buffer, bytes_recieved, client->server_address, client->socket);
|
||||||
|
MemoryZero(message_buffer, UDP_MAX_MESSAGE_LEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiServer netCreateMultiServer(u16 server_port) {
|
||||||
|
MultiServer result = {0};
|
||||||
|
result.tcp_server = netCreateTCPServer(server_port);
|
||||||
|
result.udp_server = netCreateUDPServer(server_port);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiClient netCreateMultiClient(u16 server_port, str addr) {
|
||||||
|
MultiClient result = {0};
|
||||||
|
result.tcp_client = netCreateTCPClient(server_port, addr);
|
||||||
|
result.udp_client = netCreateUDPClient(server_port, addr);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a new thread for listening for UDP and infinite loop on this thread for the TCP server
|
||||||
|
void netInfiniteReadMultiServer(
|
||||||
|
MultiServer* server,
|
||||||
|
bool* should_quit,
|
||||||
|
HandleMessageCb handleMessage,
|
||||||
|
void (*closeConnection)(i32 socket_fd),
|
||||||
|
void (*addSystemMessage)(u8* msg)
|
||||||
|
) {
|
||||||
|
u8 message_buffer[UDP_MAX_MESSAGE_LEN] = {0};
|
||||||
|
i32 bytes_recieved = 0;
|
||||||
|
SocketAddress client_address = {0};
|
||||||
|
i32 addrlen = sizeof(struct sockaddr);
|
||||||
|
i32 new_fd;
|
||||||
|
struct pollfd pollable_fds[NET_SERVER_MAX_CLIENTS+1] = {0}; // +1 for the listener socket
|
||||||
|
// poll the `listen()`ed socket_fd
|
||||||
|
pollable_fds[0].fd = server->tcp_server.socket_fd;
|
||||||
|
pollable_fds[0].events = POLLIN;
|
||||||
|
// poll the udp socket_fd also
|
||||||
|
pollable_fds[1].fd = server->udp_server.server_socket;
|
||||||
|
pollable_fds[1].events = POLLIN;
|
||||||
|
u32 pollable_fd_count = 2;
|
||||||
|
|
||||||
|
i32 poll_event_count;
|
||||||
|
while (*should_quit == false) {
|
||||||
|
poll_event_count = poll(pollable_fds, pollable_fd_count, 3000); // times out after 3 seconds so that quitting the server will actually quit the server process in relatively short order.
|
||||||
|
if (poll_event_count == -1) {
|
||||||
|
// TODO handle error better
|
||||||
|
*should_quit = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for(u32 i = 0; i < pollable_fd_count; i++) {
|
||||||
|
bool is_fd_readable = pollable_fds[i].revents & (POLLIN | POLLHUP);
|
||||||
|
if (is_fd_readable) {
|
||||||
|
printf("readable fd=%d\n", pollable_fds[i].fd);
|
||||||
|
bool is_fd_tcp_server_listener = pollable_fds[i].fd == server->tcp_server.socket_fd;
|
||||||
|
bool is_fd_udp_server_listener = pollable_fds[i].fd == server->udp_server.server_socket;
|
||||||
|
if (is_fd_tcp_server_listener) { // it's a new connection
|
||||||
|
printf("reading a tcp connection\n");
|
||||||
|
new_fd = accept(server->tcp_server.socket_fd, (struct sockaddr *)&client_address, (socklen_t*)&addrlen);
|
||||||
|
// TODO do something with client_address
|
||||||
|
if (new_fd == -1) {
|
||||||
|
if (addSystemMessage != NULL) {
|
||||||
|
addSystemMessage((u8*)"TODO: handle this accept() error for real, bitch");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (pollable_fd_count < NET_SERVER_MAX_CLIENTS+1) {
|
||||||
|
pollable_fds[pollable_fd_count].fd = new_fd;
|
||||||
|
pollable_fds[pollable_fd_count].events = POLLIN | POLLHUP;
|
||||||
|
pollable_fds[pollable_fd_count].revents = 0;
|
||||||
|
pollable_fd_count++;
|
||||||
|
if (addSystemMessage != NULL) {
|
||||||
|
char sbuf[128] = {0};
|
||||||
|
sprintf(sbuf, "new connection on socket=%d, total=%d\n", new_fd, pollable_fd_count);
|
||||||
|
addSystemMessage((u8*)sbuf);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
send(new_fd, "server full", 11, 0); // go away sir, we are out of space to keep track of this socket
|
||||||
|
close(new_fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (is_fd_udp_server_listener) {
|
||||||
|
printf("reading a udp message\n");
|
||||||
|
bytes_recieved = recvfrom(
|
||||||
|
server->udp_server.server_socket,
|
||||||
|
message_buffer,
|
||||||
|
UDP_MAX_MESSAGE_LEN,
|
||||||
|
0,
|
||||||
|
(struct sockaddr *)&client_address,
|
||||||
|
(socklen_t*)&addrlen
|
||||||
|
);
|
||||||
|
handleMessage(message_buffer, bytes_recieved, client_address, server->udp_server.server_socket);
|
||||||
|
MemoryZero(message_buffer, UDP_MAX_MESSAGE_LEN);
|
||||||
|
} else {// Otherwise we're just a regular client
|
||||||
|
printf("reading a tcp message\n");
|
||||||
|
bytes_recieved = netRecvMessage(pollable_fds[i].fd, message_buffer, addSystemMessage);
|
||||||
|
if (bytes_recieved <= 0) { // error condition
|
||||||
|
bool client_hung_up = bytes_recieved == 0;
|
||||||
|
// TODO do something with the error case
|
||||||
|
closeConnection(pollable_fds[i].fd);
|
||||||
|
close(pollable_fds[i].fd);
|
||||||
|
if (addSystemMessage != NULL) {
|
||||||
|
char sbuf[256] = {0};
|
||||||
|
sprintf(sbuf, "closed connection on socket=%d, client_hung_up? %s\n", pollable_fds[i].fd, client_hung_up ? "yes" : "no");
|
||||||
|
addSystemMessage((u8*)sbuf);
|
||||||
|
}
|
||||||
|
// copy the last one over the current one and "forget" the last one by decrementing the count
|
||||||
|
pollable_fds[i] = pollable_fds[--pollable_fd_count];
|
||||||
|
} else { // we got an actual message from this guy
|
||||||
|
handleMessage(message_buffer, bytes_recieved, client_address, pollable_fds[i].fd);
|
||||||
|
}
|
||||||
|
MemoryZero(message_buffer, UDP_MAX_MESSAGE_LEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// will return only when either should_quit is true or we got a connection error on the tcp client
|
||||||
|
void netInfiniteReadMultiClient(
|
||||||
|
MultiClient* client,
|
||||||
|
bool* should_quit,
|
||||||
|
HandleMessageCb handleMessage,
|
||||||
|
void (*addSystemMessage)(u8* msg)
|
||||||
|
) {
|
||||||
|
fd_set master; // master file descriptor list
|
||||||
|
fd_set read_fds; // temp file descriptor list for select()
|
||||||
|
i32 fdmax; // maximum file descriptor number
|
||||||
|
FD_ZERO(&master); // clear the master and temp sets
|
||||||
|
FD_ZERO(&read_fds);
|
||||||
|
// add both client sockets to the master set
|
||||||
|
FD_SET(client->udp_client.socket, &master);
|
||||||
|
FD_SET(client->tcp_client.socket, &master);
|
||||||
|
// keep track of the biggest file descriptor
|
||||||
|
fdmax = Max(client->udp_client.socket, client->tcp_client.socket); // so far, it's this one
|
||||||
|
|
||||||
|
u8 message_buffer[UDP_MAX_MESSAGE_LEN] = {0};
|
||||||
|
i32 bytes_recieved = 0;
|
||||||
|
SocketAddress client_address = {0};
|
||||||
|
i32 addrlen = sizeof(struct sockaddr);
|
||||||
|
bool got_connection_error = false;
|
||||||
|
while (*should_quit == false && got_connection_error == false) {
|
||||||
|
read_fds = master; // copy it
|
||||||
|
if (select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1) {
|
||||||
|
perror("select");
|
||||||
|
got_connection_error = true;
|
||||||
|
client->tcp_client.ready = false;
|
||||||
|
close(client->tcp_client.socket);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(i32 i = 0; i <= fdmax; i++) {
|
||||||
|
if (FD_ISSET(i, &read_fds)) {
|
||||||
|
bool is_tcp_socket = i == client->tcp_client.socket;
|
||||||
|
if (is_tcp_socket) {
|
||||||
|
bytes_recieved = netRecvMessage(client->tcp_client.socket, message_buffer, addSystemMessage);
|
||||||
|
if (bytes_recieved == -1) {
|
||||||
|
if (addSystemMessage != NULL) {
|
||||||
|
char sbuf[128] = {0};
|
||||||
|
sprintf(sbuf, "TODO handle this error, bytes_recieved=%d\n", bytes_recieved);
|
||||||
|
addSystemMessage((u8*)sbuf);
|
||||||
|
}
|
||||||
|
got_connection_error = true;
|
||||||
|
client->tcp_client.ready = false;
|
||||||
|
close(client->tcp_client.socket);
|
||||||
|
continue;
|
||||||
|
} else if (bytes_recieved == 0) { // the server hung up
|
||||||
|
if (addSystemMessage != NULL) {
|
||||||
|
char sbuf[128] = {0};
|
||||||
|
sprintf(sbuf, "the server seems to have hung up on us, bytes_recieved=%d\n", bytes_recieved);
|
||||||
|
addSystemMessage((u8*)sbuf);
|
||||||
|
}
|
||||||
|
got_connection_error = true;
|
||||||
|
client->tcp_client.ready = false;
|
||||||
|
close(client->tcp_client.socket);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
handleMessage(message_buffer, bytes_recieved, client->tcp_client.server_address, client->tcp_client.socket);
|
||||||
|
MemoryZero(message_buffer, UDP_MAX_MESSAGE_LEN);
|
||||||
|
} else { // UDP
|
||||||
|
bytes_recieved = recvfrom(client->udp_client.socket, message_buffer, UDP_MAX_MESSAGE_LEN, 0, (struct sockaddr *)&client_address, (socklen_t*)&addrlen);
|
||||||
|
handleMessage(message_buffer, bytes_recieved, client_address, client->udp_client.socket);
|
||||||
|
MemoryZero(message_buffer, UDP_MAX_MESSAGE_LEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i32 sendallto(i32 socket, void* buf, i32 len, SocketAddress* to) {
|
||||||
|
u32 total_sent = 0;
|
||||||
|
i32 left_to_send = len;
|
||||||
|
i32 sent_this_round;
|
||||||
|
while(total_sent < len) {
|
||||||
|
sent_this_round = sendto(
|
||||||
|
socket,
|
||||||
|
buf+total_sent,
|
||||||
|
left_to_send,
|
||||||
|
0,
|
||||||
|
(const struct sockaddr *)to,
|
||||||
|
sizeof(struct sockaddr)
|
||||||
|
);
|
||||||
|
if (sent_this_round == -1) { return -1; }
|
||||||
|
total_sent += sent_this_round;
|
||||||
|
left_to_send -= sent_this_round;
|
||||||
|
}
|
||||||
|
return total_sent;
|
||||||
|
}
|
||||||
|
|
||||||
i32 sendUDPu8List(i32 using_socket, SocketAddress* to, u8List* message) {
|
i32 sendUDPu8List(i32 using_socket, SocketAddress* to, u8List* message) {
|
||||||
return sendto(
|
return sendallto(using_socket, message->items, message->length, to);
|
||||||
using_socket,
|
|
||||||
message->items,
|
|
||||||
message->length,
|
|
||||||
0,
|
|
||||||
(const struct sockaddr *)to,
|
|
||||||
sizeof(struct sockaddr)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
i32 sendUDPMessage(UDPServer* to, u8* message, u32 len) {
|
i32 sendUDPMessage(UDPServer* to, u8* message, u32 len) {
|
||||||
return sendto(to->server_socket, message, len, 0, (struct sockaddr *)&to->server_address, sizeof(struct sockaddr));
|
return sendallto(to->server_socket, message, len, &to->server_address);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i32 sendall(i32 socket, void* buf, i32 len) {
|
||||||
|
u32 total_sent = 0;
|
||||||
|
i32 left_to_send = len;
|
||||||
|
i32 sent_this_round;
|
||||||
|
while(total_sent < len) {
|
||||||
|
sent_this_round = send(socket, buf+total_sent, left_to_send, 0);
|
||||||
|
if (sent_this_round == -1) { return -1; }
|
||||||
|
total_sent += sent_this_round;
|
||||||
|
left_to_send -= sent_this_round;
|
||||||
|
}
|
||||||
|
return total_sent;
|
||||||
|
}
|
||||||
|
|
||||||
|
i32 sendTCPMessage(NetworkMessage msg) {
|
||||||
|
assert(msg.tcp);
|
||||||
|
assert(msg.socket_fd >= 0);
|
||||||
|
u16 msg_len = htons(msg.bytes_len);
|
||||||
|
i32 result = sendall(msg.socket_fd, (void*)&msg_len, 2);
|
||||||
|
if (result == -1) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return sendall(msg.socket_fd, msg.bytes, msg.bytes_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,109 +0,0 @@
|
|||||||
#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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
#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
|
|
||||||
+112
@@ -25,6 +25,14 @@
|
|||||||
#define ANSI_HIGHLIGHT_GRAY (16)
|
#define ANSI_HIGHLIGHT_GRAY (16)
|
||||||
#define MAX_COMMAND_PALETTE_COMMANDS (1000)
|
#define MAX_COMMAND_PALETTE_COMMANDS (1000)
|
||||||
|
|
||||||
|
#ifndef SYSTEM_MESSAGES_LEN
|
||||||
|
# define SYSTEM_MESSAGES_LEN (32)
|
||||||
|
#endif
|
||||||
|
#ifndef MAX_SYSTEM_MESSAGE_LEN
|
||||||
|
# define MAX_SYSTEM_MESSAGE_LEN (512)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
///// TYPES
|
///// TYPES
|
||||||
typedef struct Pixel {
|
typedef struct Pixel {
|
||||||
u8 foreground;
|
u8 foreground;
|
||||||
@@ -73,7 +81,53 @@ typedef struct StringSearchScore {
|
|||||||
u32 description_match_len;
|
u32 description_match_len;
|
||||||
} StringSearchScore;
|
} StringSearchScore;
|
||||||
|
|
||||||
|
///// GLOBALS
|
||||||
|
global u8List system_messages[SYSTEM_MESSAGES_LEN] = {0};
|
||||||
|
global u8 system_message_index = 0;
|
||||||
|
|
||||||
///// Functions()
|
///// Functions()
|
||||||
|
fn void initSystemMessages(Arena* a) {
|
||||||
|
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(a, sizeof(u8), MAX_SYSTEM_MESSAGE_LEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 u32 rgbToNum(RGB rgb) {
|
fn u32 rgbToNum(RGB rgb) {
|
||||||
return ((rgb.r<<16) | (rgb.g<<8) | rgb.b);
|
return ((rgb.r<<16) | (rgb.g<<8) | rgb.b);
|
||||||
}
|
}
|
||||||
@@ -216,6 +270,64 @@ fn void renderStringChunkList(TuiState* tui, StringChunkList* list, u16 x, u16 y
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 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 u32 sprintfAnsiMoveCursorTo(ptr output, u16 x, u16 y) {
|
fn u32 sprintfAnsiMoveCursorTo(ptr output, u16 x, u16 y) {
|
||||||
return sprintf(output, "\x1b[%d;%df",y,x);
|
return sprintf(output, "\x1b[%d;%df",y,x);
|
||||||
}
|
}
|
||||||
|
|||||||
-104
@@ -1,104 +0,0 @@
|
|||||||
#include "shared.h"
|
|
||||||
#include "lib/tui.c"
|
|
||||||
|
|
||||||
#define MAX_SCREEN_HEIGHT 300
|
|
||||||
#define MAX_SCREEN_WIDTH 800
|
|
||||||
|
|
||||||
fn str charForThing(ThingType e) {
|
|
||||||
switch (e) {
|
|
||||||
case ThingWall:
|
|
||||||
return "# ";
|
|
||||||
case ThingDoor:
|
|
||||||
return "🚪";
|
|
||||||
case ThingCharacter:
|
|
||||||
return "🧙";
|
|
||||||
//return "웃";
|
|
||||||
case ThingNull:
|
|
||||||
case ThingType_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 = charForThing(room->foreground[roompos]);
|
|
||||||
RGB background_color = colorForTile(room->background[roompos]);
|
|
||||||
if (room->visible[roompos]) {
|
|
||||||
buf[bufpos].foreground = ansiColorForThing(room->foreground[roompos]);
|
|
||||||
if (room->light[roompos] < VISIBLE_BRIGHTNESS_CUTOFF) {
|
|
||||||
fg_char = charForThing(ThingMurkyUnknown);
|
|
||||||
background_color = rgbDarken(background_color, 0.8);
|
|
||||||
}
|
|
||||||
} else if (room->memory[roompos] == RememberedTileQualityDim) {
|
|
||||||
background_color = rgbDarken(background_color, 0.4);
|
|
||||||
buf[bufpos].foreground = ansiColorForThing(room->foreground[roompos]);
|
|
||||||
fg_char = charForThing(ThingMurkyUnknown);
|
|
||||||
} else if (room->memory[roompos] == RememberedTileQualityClear) {
|
|
||||||
background_color = rgbDarken(background_color, 0.6);
|
|
||||||
buf[bufpos].foreground = ansiColorForThing(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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
+82
-45
@@ -6,14 +6,13 @@
|
|||||||
* my_variable
|
* my_variable
|
||||||
* MY_CONSTANT
|
* MY_CONSTANT
|
||||||
* */
|
* */
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <sys/random.h>
|
#include <sys/random.h>
|
||||||
#include "shared.h"
|
#include "shared.h"
|
||||||
#include "base/impl.c"
|
#include "base/impl.c"
|
||||||
#define NET_OUTGOING_MESSAGE_QUEUE_LEN 64
|
#define NET_OUTGOING_MESSAGE_QUEUE_LEN 64
|
||||||
#include "lib/network.c"
|
#include "lib/network.c"
|
||||||
#include "render.c"
|
#include "lib/tui.c"
|
||||||
|
|
||||||
///// CONSTANTS
|
///// CONSTANTS
|
||||||
#define MAX_THINGS (2<<18)
|
#define MAX_THINGS (2<<18)
|
||||||
@@ -28,7 +27,6 @@
|
|||||||
#define GOAL_NETWORK_SEND_LOOP_US 1000000/GOAL_NETWORK_SEND_LOOPS_PER_S
|
#define GOAL_NETWORK_SEND_LOOP_US 1000000/GOAL_NETWORK_SEND_LOOPS_PER_S
|
||||||
#define GOAL_GAME_LOOPS_PER_S 4
|
#define GOAL_GAME_LOOPS_PER_S 4
|
||||||
#define GOAL_GAME_LOOP_US 1000000/GOAL_GAME_LOOPS_PER_S
|
#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 CHUNK_SIZE 64
|
||||||
#define ACCOUNT_CHUNK_SIZE 64
|
#define ACCOUNT_CHUNK_SIZE 64
|
||||||
#define PARSED_CLIENT_COMMAND_THREAD_QUEUE_LEN 64
|
#define PARSED_CLIENT_COMMAND_THREAD_QUEUE_LEN 64
|
||||||
@@ -41,6 +39,7 @@ typedef struct ParsedClientCommand {
|
|||||||
u16 alt_port;
|
u16 alt_port;
|
||||||
u32 sender_ip;
|
u32 sender_ip;
|
||||||
u32 alt_ip;
|
u32 alt_ip;
|
||||||
|
i32 socket_fd;
|
||||||
StringChunkList name;
|
StringChunkList name;
|
||||||
StringChunkList pass;
|
StringChunkList pass;
|
||||||
u64 id;
|
u64 id;
|
||||||
@@ -94,13 +93,15 @@ typedef struct AccountChunk {
|
|||||||
} AccountChunk;
|
} AccountChunk;
|
||||||
|
|
||||||
typedef struct Client {
|
typedef struct Client {
|
||||||
|
bool active;
|
||||||
u16 lan_port;
|
u16 lan_port;
|
||||||
i32 lan_ip;
|
i32 lan_ip;
|
||||||
|
i32 socket_fd; // the tcp socket
|
||||||
ThingRef character_eid;
|
ThingRef character_eid;
|
||||||
u64 account_id;
|
|
||||||
SocketAddress address;
|
SocketAddress address;
|
||||||
|
u64 account_id;
|
||||||
|
u64 connected_on;
|
||||||
CommandType commands[CLIENT_COMMAND_LIST_LEN];
|
CommandType commands[CLIENT_COMMAND_LIST_LEN];
|
||||||
u64 last_ping;
|
|
||||||
} Client;
|
} Client;
|
||||||
|
|
||||||
typedef struct ClientList {
|
typedef struct ClientList {
|
||||||
@@ -110,6 +111,7 @@ typedef struct ClientList {
|
|||||||
} ClientList;
|
} ClientList;
|
||||||
|
|
||||||
typedef struct State {
|
typedef struct State {
|
||||||
|
bool should_quit;
|
||||||
Mutex client_mutex;
|
Mutex client_mutex;
|
||||||
Mutex mutex;
|
Mutex mutex;
|
||||||
ClientList clients;
|
ClientList clients;
|
||||||
@@ -117,6 +119,7 @@ typedef struct State {
|
|||||||
u64 frame;
|
u64 frame;
|
||||||
Arena game_scratch;
|
Arena game_scratch;
|
||||||
StringArena string_arena;
|
StringArena string_arena;
|
||||||
|
MultiServer server;
|
||||||
ParsedClientCommandThreadQueue* network_recv_queue;
|
ParsedClientCommandThreadQueue* network_recv_queue;
|
||||||
OutgoingMessageQueue* network_send_queue;
|
OutgoingMessageQueue* network_send_queue;
|
||||||
Things* things;
|
Things* things;
|
||||||
@@ -344,10 +347,12 @@ fn void exitWithErrorMessage(ptr msg) {
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn u32 pushClient(ClientList* clients, SocketAddress addr) {
|
fn u32 pushClient(ClientList* clients, SocketAddress addr, i32 socket_fd) {
|
||||||
Client new_client = {0};
|
Client new_client = {
|
||||||
new_client.last_ping = state.frame;
|
.active = true,
|
||||||
new_client.address = addr;
|
.socket_fd = socket_fd,
|
||||||
|
.address = addr,
|
||||||
|
};
|
||||||
|
|
||||||
// first, try to overwrite an old dc'ed client
|
// first, try to overwrite an old dc'ed client
|
||||||
for (u32 i = 1; i < clients->length; i++) {
|
for (u32 i = 1; i < clients->length; i++) {
|
||||||
@@ -398,11 +403,12 @@ fn u32 findClientHandleBySocketAddress(ClientList* clients, SocketAddress addres
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn void handleIncomingMessage(u8* message, u32 len, SocketAddress sender, i32 socket) {
|
fn void handleIncomingMessage(u8* message, i32 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);
|
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;
|
u32 msg_idx = 0;
|
||||||
ParsedClientCommand parsed = {
|
ParsedClientCommand parsed = {
|
||||||
.type = (CommandType)message[msg_idx++],
|
.type = (CommandType)message[msg_idx++],
|
||||||
|
.socket_fd = socket,
|
||||||
.sender_ip = sender.sin_addr.s_addr,
|
.sender_ip = sender.sin_addr.s_addr,
|
||||||
.sender_port = sender.sin_port,
|
.sender_port = sender.sin_port,
|
||||||
};
|
};
|
||||||
@@ -413,6 +419,7 @@ fn void handleIncomingMessage(u8* message, u32 len, SocketAddress sender, i32 so
|
|||||||
.capacity = 0,
|
.capacity = 0,
|
||||||
};
|
};
|
||||||
switch (parsed.type) {
|
switch (parsed.type) {
|
||||||
|
case CommandUDPPing: {} break;
|
||||||
case CommandLogin: {
|
case CommandLogin: {
|
||||||
// parse the login command
|
// parse the login command
|
||||||
parsed.alt_port = ~readU16FromBufferLE(message + msg_idx);
|
parsed.alt_port = ~readU16FromBufferLE(message + msg_idx);
|
||||||
@@ -447,8 +454,6 @@ fn void handleIncomingMessage(u8* message, u32 len, SocketAddress sender, i32 so
|
|||||||
|
|
||||||
printf("Logging in player: %s %d %s\n", MESSAGE_STRINGS[parsed.type], name_len, message + 7);
|
printf("Logging in player: %s %d %s\n", MESSAGE_STRINGS[parsed.type], name_len, message + 7);
|
||||||
} break;
|
} break;
|
||||||
case CommandKeepAlive:
|
|
||||||
break;
|
|
||||||
case CommandCreateCharacter: {
|
case CommandCreateCharacter: {
|
||||||
printf("command create character received\n");
|
printf("command create character received\n");
|
||||||
parsed.byte = message[1];
|
parsed.byte = message[1];
|
||||||
@@ -463,31 +468,52 @@ fn void handleIncomingMessage(u8* message, u32 len, SocketAddress sender, i32 so
|
|||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn void* receiveNetworkUpdates(void* udp) {
|
fn void removeClientBySocketFd(i32 socket_fd) {
|
||||||
UDPServer server = *(UDPServer*)udp;
|
Client blank_client = {0};
|
||||||
dbg("receiveNetworkUpdates() sock=%d\n", server.server_socket);
|
// i=1 because first client is null-client
|
||||||
infiniteReadUDPServer(&server, handleIncomingMessage);
|
for (u32 i = 1; i < state.clients.length; i++) {
|
||||||
|
if (state.clients.items[i].active && state.clients.items[i].socket_fd == socket_fd) {
|
||||||
|
state.clients.items[i] = blank_client;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn void* receiveNetworkUpdates(void* multi) {
|
||||||
|
MultiServer server = *(MultiServer*)multi;
|
||||||
|
netInfiniteReadMultiServer(
|
||||||
|
&server,
|
||||||
|
&state.should_quit,
|
||||||
|
handleIncomingMessage,
|
||||||
|
removeClientBySocketFd,
|
||||||
|
addSystemMessage
|
||||||
|
);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn void* sendNetworkUpdates(void* sock) {
|
fn void* sendNetworkUpdates(void* multi) {
|
||||||
ThreadContext tctx = {0};
|
ThreadContext tctx = {0};
|
||||||
tctxInit(&tctx);
|
tctxInit(&tctx);
|
||||||
i32* socket_ptr = (i32*)sock;
|
MultiServer *server = (MultiServer*) multi;
|
||||||
i32 socket = *socket_ptr;
|
while (!state.should_quit) {
|
||||||
while (true) {
|
|
||||||
u64 loop_start = osTimeMicrosecondsNow();
|
u64 loop_start = osTimeMicrosecondsNow();
|
||||||
|
|
||||||
// 1. clear out our "outgoingMessage" queue
|
// 1. clear out our "outgoingMessage" queue
|
||||||
{
|
{
|
||||||
UDPMessage to_send = { 0 };
|
NetworkMessage to_send = { 0 };
|
||||||
UDPMessage* next_to_send = outgoingMessageNonblockingQueuePop(state.network_send_queue, &to_send);
|
NetworkMessage* next_to_send = outgoingMessageNonblockingQueuePop(state.network_send_queue, &to_send);
|
||||||
u8List bytes_list = { 0 };
|
u8List bytes_list = { 0 };
|
||||||
while (next_to_send != NULL) {
|
while (next_to_send != NULL) {
|
||||||
bytes_list.items = to_send.bytes;
|
if (to_send.tcp) {
|
||||||
bytes_list.length = to_send.bytes_len;
|
char sbuf[SBUFLEN] = {0};
|
||||||
bytes_list.capacity = to_send.bytes_len;
|
sprintf(sbuf, "sendTCPMessage() sock=%d msg=%s len=%d\n", to_send.socket_fd, MESSAGE_STRINGS[to_send.bytes[0]], to_send.bytes_len);
|
||||||
sendUDPu8List(socket, &to_send.address, &bytes_list);
|
addSystemMessage((u8*)sbuf);
|
||||||
|
sendTCPMessage(to_send);
|
||||||
|
} else { // UDP
|
||||||
|
bytes_list.items = to_send.bytes;
|
||||||
|
bytes_list.length = to_send.bytes_len;
|
||||||
|
bytes_list.capacity = to_send.bytes_len;
|
||||||
|
sendUDPu8List(server->udp_server.server_socket, &to_send.address, &bytes_list);
|
||||||
|
}
|
||||||
next_to_send = outgoingMessageNonblockingQueuePop(state.network_send_queue, &to_send);
|
next_to_send = outgoingMessageNonblockingQueuePop(state.network_send_queue, &to_send);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -496,7 +522,7 @@ fn void* sendNetworkUpdates(void* sock) {
|
|||||||
// WARNING the `i` starts at 1 here because state.clients.items[0] is a "null" Client
|
// 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++) {
|
for (u32 i = 1; i < state.clients.length; i++) {
|
||||||
Client client = state.clients.items[i];
|
Client client = state.clients.items[i];
|
||||||
if (client.last_ping+CLIENT_TIMEOUT_FRAMES < state.frame) {
|
if (client.active == false) {
|
||||||
memset(&state.clients.items[i], 0, sizeof(Client));
|
memset(&state.clients.items[i], 0, sizeof(Client));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -516,8 +542,8 @@ fn void* sendNetworkUpdates(void* sock) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bool messageSendCharacterId(SocketAddress sender, ThingRef id) {
|
fn bool messageSendCharacterId(i32 socket_fd, SocketAddress sender, ThingRef id) {
|
||||||
UDPMessage outgoing_message = {0};
|
NetworkMessage outgoing_message = { .tcp = true, .socket_fd = socket_fd };
|
||||||
outgoing_message.address = sender;
|
outgoing_message.address = sender;
|
||||||
outgoing_message.bytes_len = 9;
|
outgoing_message.bytes_len = 9;
|
||||||
outgoing_message.bytes[0] = (u8)MessageCharacterId;
|
outgoing_message.bytes[0] = (u8)MessageCharacterId;
|
||||||
@@ -536,7 +562,7 @@ fn void* gameLoop(void* params) {
|
|||||||
tctxInit(&tctx);
|
tctxInit(&tctx);
|
||||||
printf("Lane %lld (%lld) of %lld starting.\n", lane_ctx->lane_idx, LaneIdx(), lane_ctx->lane_count);
|
printf("Lane %lld (%lld) of %lld starting.\n", lane_ctx->lane_idx, LaneIdx(), lane_ctx->lane_count);
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
UDPMessage outgoing_message = {0};
|
NetworkMessage outgoing_message = {0};
|
||||||
u64 loop_start;
|
u64 loop_start;
|
||||||
u64 last_burn = 0;
|
u64 last_burn = 0;
|
||||||
u64 last_hp_regen = 0;
|
u64 last_hp_regen = 0;
|
||||||
@@ -563,13 +589,19 @@ fn void* gameLoop(void* params) {
|
|||||||
u32 client_handle = findClientHandleBySocketAddress(&state.clients, sender);
|
u32 client_handle = findClientHandleBySocketAddress(&state.clients, sender);
|
||||||
Client* client = &state.clients.items[client_handle];
|
Client* client = &state.clients.items[client_handle];
|
||||||
switch (msg.type) {
|
switch (msg.type) {
|
||||||
case CommandKeepAlive: {
|
case CommandUDPPing: {
|
||||||
dbg("KeepAlive for client_handle=%d, %ld", client_handle, state.frame);
|
// send UDP Pong right back
|
||||||
state.clients.items[client_handle].last_ping = state.frame;
|
outgoing_message.tcp = false;
|
||||||
|
outgoing_message.socket_fd = state.server.udp_server.server_socket;
|
||||||
|
outgoing_message.bytes[0] = MessageUDPPong;
|
||||||
|
outgoing_message.bytes_len = 1;
|
||||||
|
outgoing_message.address = sender;
|
||||||
|
outgoingMessageQueuePush(state.network_send_queue, &outgoing_message);
|
||||||
|
printf("MessageUDPPong sent\n");
|
||||||
} break;
|
} break;
|
||||||
case CommandLogin: {
|
case CommandLogin: {
|
||||||
if (client_handle == 0) {
|
if (client_handle == 0) {
|
||||||
client_handle = pushClient(&state.clients, sender);
|
client_handle = pushClient(&state.clients, sender, msg.socket_fd);
|
||||||
client = &state.clients.items[client_handle];
|
client = &state.clients.items[client_handle];
|
||||||
printf("pushed new client handle = %d\n", client_handle);
|
printf("pushed new client handle = %d\n", client_handle);
|
||||||
}
|
}
|
||||||
@@ -602,9 +634,11 @@ fn void* gameLoop(void* params) {
|
|||||||
printf(" pw matched\n");
|
printf(" pw matched\n");
|
||||||
} else {
|
} else {
|
||||||
// tell the client they did a bad pw
|
// tell the client they did a bad pw
|
||||||
|
outgoing_message.tcp = true;
|
||||||
|
outgoing_message.socket_fd = client->socket_fd;
|
||||||
|
outgoing_message.address = sender;
|
||||||
outgoing_message.bytes[0] = (u8)MessageBadPw;
|
outgoing_message.bytes[0] = (u8)MessageBadPw;
|
||||||
outgoing_message.bytes_len = 1;
|
outgoing_message.bytes_len = 1;
|
||||||
outgoing_message.address = sender;
|
|
||||||
outgoingMessageQueuePush(state.network_send_queue, &outgoing_message);
|
outgoingMessageQueuePush(state.network_send_queue, &outgoing_message);
|
||||||
printf("MessageBadPw sent\n");
|
printf("MessageBadPw sent\n");
|
||||||
break;
|
break;
|
||||||
@@ -622,12 +656,14 @@ fn void* gameLoop(void* params) {
|
|||||||
client->character_eid = existing_account->eid;
|
client->character_eid = existing_account->eid;
|
||||||
|
|
||||||
// tell the client their character id
|
// tell the client their character id
|
||||||
messageSendCharacterId(sender, existing_account->eid);
|
messageSendCharacterId(client->socket_fd, sender, existing_account->eid);
|
||||||
} else {
|
} else {
|
||||||
// tell the client they made a new account
|
// tell the client they made a new account
|
||||||
|
outgoing_message.tcp = true;
|
||||||
|
outgoing_message.socket_fd = client->socket_fd;
|
||||||
|
outgoing_message.address = sender;
|
||||||
outgoing_message.bytes[0] = (u8)MessageNewAccountCreated;
|
outgoing_message.bytes[0] = (u8)MessageNewAccountCreated;
|
||||||
outgoing_message.bytes_len = 1;
|
outgoing_message.bytes_len = 1;
|
||||||
outgoing_message.address = sender;
|
|
||||||
outgoingMessageQueuePush(state.network_send_queue, &outgoing_message);
|
outgoingMessageQueuePush(state.network_send_queue, &outgoing_message);
|
||||||
printf("MessageNewAccountCreated sent\n");
|
printf("MessageNewAccountCreated sent\n");
|
||||||
}
|
}
|
||||||
@@ -650,7 +686,7 @@ fn void* gameLoop(void* params) {
|
|||||||
printf("character_eid=%d, client_handle=%d, acct_id=%lld\n", account->eid.i, client_handle, account->id);
|
printf("character_eid=%d, client_handle=%d, acct_id=%lld\n", account->eid.i, client_handle, account->id);
|
||||||
|
|
||||||
// tell the client their character id
|
// tell the client their character id
|
||||||
messageSendCharacterId(sender, character.id);
|
messageSendCharacterId(client->socket_fd, sender, character.id);
|
||||||
} else {
|
} else {
|
||||||
printf("client tried to create a character when he already has one.");
|
printf("client tried to create a character when he already has one.");
|
||||||
}
|
}
|
||||||
@@ -720,14 +756,15 @@ i32 main(i32 argc, ptr argv[]) {
|
|||||||
state.accounts.capacity = ACCOUNT_CHUNK_SIZE;
|
state.accounts.capacity = ACCOUNT_CHUNK_SIZE;
|
||||||
state.accounts.items = arenaAllocArray(&permanent_arena, Account, ACCOUNT_CHUNK_SIZE);
|
state.accounts.items = arenaAllocArray(&permanent_arena, Account, ACCOUNT_CHUNK_SIZE);
|
||||||
state.things = arenaAllocZero(&permanent_arena, sizeof(Things));
|
state.things = arenaAllocZero(&permanent_arena, sizeof(Things));
|
||||||
|
initSystemMessages(&permanent_arena);
|
||||||
|
|
||||||
// networking threads (send + receive)
|
// networking threads (send + receive)
|
||||||
UDPServer listener = createUDPServer(SERVER_PORT);
|
state.server = netCreateMultiServer(SERVER_PORT);
|
||||||
if (!listener.ready) {
|
if (!state.server.tcp_server.ready || !state.server.udp_server.ready) {
|
||||||
exitWithErrorMessage("Couldn't start the udp server");
|
exitWithErrorMessage("Couldn't start the tcp or udp server");
|
||||||
}
|
}
|
||||||
Thread send_thread = spawnThread(&sendNetworkUpdates, &listener.server_socket);
|
Thread send_thread = spawnThread(&sendNetworkUpdates, &state.server);
|
||||||
Thread recv_thread = spawnThread(&receiveNetworkUpdates, &listener);
|
Thread recv_thread = spawnThread(&receiveNetworkUpdates, &state.server);
|
||||||
|
|
||||||
u64 lane_broadcast_val = 0;
|
u64 lane_broadcast_val = 0;
|
||||||
Barrier barrier = osBarrierAlloc(GAME_THREAD_CONCURRENCY);
|
Barrier barrier = osBarrierAlloc(GAME_THREAD_CONCURRENCY);
|
||||||
|
|||||||
+105
-3
@@ -6,6 +6,9 @@
|
|||||||
///// #define some game-tunable constants
|
///// #define some game-tunable constants
|
||||||
#define GAME_CONSTANT_ONE (1)
|
#define GAME_CONSTANT_ONE (1)
|
||||||
#define GAME_CONSTANT_TWO (2)
|
#define GAME_CONSTANT_TWO (2)
|
||||||
|
#define SBUFLEN (512)
|
||||||
|
#define MAX_SCREEN_HEIGHT 300
|
||||||
|
#define MAX_SCREEN_WIDTH 800
|
||||||
#define THING_HEADER_MESSAGE_SIZE (8+8+1+1+1)
|
#define THING_HEADER_MESSAGE_SIZE (8+8+1+1+1)
|
||||||
#define THING_MESSAGE_SIZE (THING_HEADER_MESSAGE_SIZE+2+2+2+2+8+1+1)
|
#define THING_MESSAGE_SIZE (THING_HEADER_MESSAGE_SIZE+2+2+2+2+8+1+1)
|
||||||
|
|
||||||
@@ -49,16 +52,16 @@ typedef enum Direction {
|
|||||||
|
|
||||||
typedef enum CommandType {
|
typedef enum CommandType {
|
||||||
CommandInvalid,
|
CommandInvalid,
|
||||||
CommandKeepAlive,
|
|
||||||
CommandLogin,
|
CommandLogin,
|
||||||
CommandCreateCharacter,
|
CommandCreateCharacter,
|
||||||
|
CommandUDPPing,
|
||||||
CommandType_Count,
|
CommandType_Count,
|
||||||
} CommandType;
|
} CommandType;
|
||||||
static const char* command_type_strings[] = {
|
static const char* COMMAND_TYPE_STRINGS[] = {
|
||||||
"Invalid",
|
"Invalid",
|
||||||
"KeepAlive",
|
|
||||||
"Login",
|
"Login",
|
||||||
"CreateCharacter",
|
"CreateCharacter",
|
||||||
|
"UDP Ping",
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef enum Message {
|
typedef enum Message {
|
||||||
@@ -66,6 +69,7 @@ typedef enum Message {
|
|||||||
MessageCharacterId,
|
MessageCharacterId,
|
||||||
MessageBadPw,
|
MessageBadPw,
|
||||||
MessageNewAccountCreated,
|
MessageNewAccountCreated,
|
||||||
|
MessageUDPPong,
|
||||||
Message_Count,
|
Message_Count,
|
||||||
} Message;
|
} Message;
|
||||||
static const char* MESSAGE_STRINGS[] = {
|
static const char* MESSAGE_STRINGS[] = {
|
||||||
@@ -73,6 +77,104 @@ static const char* MESSAGE_STRINGS[] = {
|
|||||||
"CharacterId",
|
"CharacterId",
|
||||||
"BadPw",
|
"BadPw",
|
||||||
"NewAccountCreated",
|
"NewAccountCreated",
|
||||||
|
"UDP Pong",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn str charForThing(ThingType e) {
|
||||||
|
switch (e) {
|
||||||
|
case ThingWall:
|
||||||
|
return "# ";
|
||||||
|
case ThingDoor:
|
||||||
|
return "🚪";
|
||||||
|
case ThingCharacter:
|
||||||
|
return "🧙";
|
||||||
|
//return "웃";
|
||||||
|
case ThingNull:
|
||||||
|
case ThingType_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 = charForThing(room->foreground[roompos]);
|
||||||
|
RGB background_color = colorForTile(room->background[roompos]);
|
||||||
|
if (room->visible[roompos]) {
|
||||||
|
buf[bufpos].foreground = ansiColorForThing(room->foreground[roompos]);
|
||||||
|
if (room->light[roompos] < VISIBLE_BRIGHTNESS_CUTOFF) {
|
||||||
|
fg_char = charForThing(ThingMurkyUnknown);
|
||||||
|
background_color = rgbDarken(background_color, 0.8);
|
||||||
|
}
|
||||||
|
} else if (room->memory[roompos] == RememberedTileQualityDim) {
|
||||||
|
background_color = rgbDarken(background_color, 0.4);
|
||||||
|
buf[bufpos].foreground = ansiColorForThing(room->foreground[roompos]);
|
||||||
|
fg_char = charForThing(ThingMurkyUnknown);
|
||||||
|
} else if (room->memory[roompos] == RememberedTileQualityClear) {
|
||||||
|
background_color = rgbDarken(background_color, 0.6);
|
||||||
|
buf[bufpos].foreground = ansiColorForThing(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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
#endif //GAMESHARED_H
|
#endif //GAMESHARED_H
|
||||||
|
|||||||
Reference in New Issue
Block a user