better base + networking layer + remove some garbage + update client to not crash on tcp disconnect

This commit is contained in:
Tenari
2026-04-04 09:09:50 -05:00
parent 50e06f064a
commit 9eef988820
10 changed files with 931 additions and 487 deletions
+30
View File
@@ -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
View File
@@ -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);
+73 -139
View File
@@ -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;
@@ -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
View File
@@ -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);
}
-109
View File
@@ -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;
}
-29
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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