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`
|
||||
#define dbg(fmt, ...) osDebugPrint(debug_mode, fmt, ##__VA_ARGS__)
|
||||
|
||||
///// SYSTEM INCLUDES I always want
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
///// TYPES
|
||||
// integer types
|
||||
typedef unsigned char u8;
|
||||
@@ -438,7 +442,27 @@ union Range1f32
|
||||
DWORD input_mode;
|
||||
DWORD output_mode;
|
||||
} 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
|
||||
# include <poll.h>
|
||||
# include <sys/socket.h>
|
||||
# include <netinet/in.h>
|
||||
# include <netdb.h>
|
||||
@@ -569,40 +593,42 @@ fn bool isAlphaUnderscoreSpace(u8 c);
|
||||
fn bool isSimplePrintable(u8 c);
|
||||
|
||||
///// OS-wrapped apis
|
||||
void osInit();
|
||||
void* osThreadContextGet();
|
||||
void osThreadContextSet(void* ctx);
|
||||
void osInit();
|
||||
void* osThreadContextGet();
|
||||
void osThreadContextSet(void* ctx);
|
||||
bool osThreadJoin(Thread handle, u64 endt_us);
|
||||
|
||||
fn Barrier osBarrierAlloc(u64 count);
|
||||
fn void osBarrierRelease(Barrier barrier);
|
||||
fn void osBarrierWait(Barrier barrier);
|
||||
fn void osBarrierRelease(Barrier barrier);
|
||||
fn void osBarrierWait(Barrier barrier);
|
||||
|
||||
// Memory
|
||||
fn void* osMemoryReserve(u64 size);
|
||||
fn void osMemoryCommit(void* memory, u64 size);
|
||||
fn void osMemoryDecommit(void* memory, u64 size);
|
||||
fn void osMemoryRelease(void* memory, u64 size);
|
||||
fn u64 osTimeMicrosecondsNow();
|
||||
fn void osSleepMicroseconds(u32 t);
|
||||
fn void* osMemoryReserve(u64 size);
|
||||
fn void osMemoryCommit(void* memory, u64 size);
|
||||
fn void osMemoryDecommit(void* memory, u64 size);
|
||||
fn void osMemoryRelease(void* memory, u64 size);
|
||||
fn u64 osTimeMicrosecondsNow();
|
||||
fn void osSleepMicroseconds(u32 t);
|
||||
|
||||
fn bool osFileExists(String filename);
|
||||
// Files
|
||||
fn bool osFileExists(String filename);
|
||||
fn String osFileRead(Arena* arena, ptr filepath);
|
||||
fn bool osFileCreate(String filename);
|
||||
fn bool osFileCreateWrite(String filename, String data);
|
||||
fn bool osFileWrite(String filename, String data);
|
||||
fn bool osFileCreate(String filename);
|
||||
fn bool osFileCreateWrite(String filename, String data);
|
||||
fn bool osFileWrite(String filename, String data);
|
||||
|
||||
fn void osDebugPrint(bool debug_mode, const char* format, ...);
|
||||
fn void osDebugPrint(bool debug_mode, const char* format, ...);
|
||||
|
||||
TermIOs osStartTUI(bool blocking);
|
||||
fn void osEndTUI(TermIOs old_terminal_attributes);
|
||||
fn Dim2 osGetTerminalDimensions();
|
||||
void osBlitToTerminal(ptr writeable_output_ansi_string, i64 count);
|
||||
void osReadConsoleInput(u8* buffer, u32 len);
|
||||
// Tui stuff
|
||||
TermIOs osStartTUI(bool blocking);
|
||||
fn void osEndTUI(TermIOs old_terminal_attributes);
|
||||
fn Dim2 osGetTerminalDimensions();
|
||||
void osBlitToTerminal(ptr writeable_output_ansi_string, i64 count);
|
||||
void osReadConsoleInput(u8* buffer, u32 len);
|
||||
|
||||
bool osInitNetwork();
|
||||
i32 osLanIPAddress();
|
||||
|
||||
bool osThreadJoin(Thread handle, u64 endt_us);
|
||||
// network stuff
|
||||
bool osInitNetwork();
|
||||
i32 osLanIPAddress();
|
||||
|
||||
///// Basic THREAD synchronization apis
|
||||
Thread spawnThread(void * (*threadFn)(void *), void* thread_arg);
|
||||
|
||||
+74
-140
@@ -9,14 +9,13 @@
|
||||
#include <string.h>
|
||||
#include "base/impl.c"
|
||||
#include "lib/network.c"
|
||||
#include "render.c"
|
||||
#include "lib/tui.c"
|
||||
#include "shared.h"
|
||||
//#include "assets/asset1.h"
|
||||
//#include "assets/asset2.h"
|
||||
//#include "assets/asset3.h"
|
||||
|
||||
///// #define a bunch of client-only tunable game constants
|
||||
#define SYSTEM_MESSAGES_LEN 32
|
||||
#define MAX_SYSTEM_MESSAGE_LEN 512
|
||||
#define GOAL_LOOPS_PER_S 50
|
||||
#define GOAL_LOOP_US 1000000/GOAL_LOOPS_PER_S
|
||||
#define LOGIN_NAME_BUFFER_LEN 16
|
||||
@@ -102,6 +101,8 @@ typedef struct MenuState {
|
||||
} MenuState;
|
||||
|
||||
typedef struct GameState {
|
||||
u32 udp_pings_sent;
|
||||
u32 udp_pongs_recieved;
|
||||
Screen screen;
|
||||
Screen old_screen;
|
||||
ThingList things;
|
||||
@@ -114,8 +115,7 @@ typedef struct GameState {
|
||||
MenuState menu;
|
||||
MenuState section; // for tabbing through selected "portions" of the screen
|
||||
u8 choices[4];
|
||||
UDPMessage keep_alive_msg;
|
||||
UDPClient client;
|
||||
MultiClient client;
|
||||
StringChunkList message_input;
|
||||
} GameState;
|
||||
|
||||
@@ -123,8 +123,6 @@ typedef struct GameState {
|
||||
global bool should_quit;
|
||||
global bool debug_mode = false;
|
||||
global Arena permanent_arena = {0};
|
||||
global u8List system_messages[SYSTEM_MESSAGES_LEN] = {0};
|
||||
global u8 system_message_index = 0;
|
||||
global GameState state = {0};
|
||||
global OutgoingMessageQueue* network_send_queue = {0};
|
||||
global ParsedServerMessageThreadQueue* network_recv_queue = {0};
|
||||
@@ -201,81 +199,6 @@ fn bool thingDelete(ThingList* list, u64 id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fn void addSystemMessage(u8* msg) {
|
||||
// save the message to our system_messages ring buffer
|
||||
memset(system_messages[system_message_index].items, 0, SYSTEM_MESSAGES_LEN);
|
||||
sprintf((char*)system_messages[system_message_index].items, "%s", msg);
|
||||
system_messages[system_message_index].length = strlen((char*)system_messages[system_message_index].items);
|
||||
system_message_index += 1;
|
||||
if (system_message_index == SYSTEM_MESSAGES_LEN) {
|
||||
system_message_index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fn void renderSystemMessages(Pixel* buf, Dim2 screen_dimensions, Box sys_msg_box) {
|
||||
i32 printable_lines = sys_msg_box.height - 2;
|
||||
if (printable_lines > SYSTEM_MESSAGES_LEN) {
|
||||
printable_lines = SYSTEM_MESSAGES_LEN;
|
||||
}
|
||||
for (i32 i = 0; i < printable_lines; i++) {
|
||||
i32 index = (system_message_index - 1 - i);
|
||||
if (index < 0) {
|
||||
index = SYSTEM_MESSAGES_LEN + index;
|
||||
}
|
||||
u32 y = sys_msg_box.y + (sys_msg_box.height - i) - 1;
|
||||
u8List sys_msg = system_messages[index];
|
||||
for (i32 j = 0; j < MAX_SYSTEM_MESSAGE_LEN && j < sys_msg_box.width-4; j++) {
|
||||
u32 pos = (sys_msg_box.x + 2+j) + (screen_dimensions.width * y);
|
||||
if (j < sys_msg.length) {
|
||||
if (sys_msg.items[j] != '\n') {
|
||||
buf[pos].bytes[0] = sys_msg.items[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn void renderPercentBar(TuiState* tui, u16 x, u16 y, u16 width, u8 ansi_color, u64 value, u64 max) {
|
||||
Pixel* buf = tui->frame_buffer;
|
||||
Dim2 screen_dimensions = tui->screen_dimensions;
|
||||
u16 pos = x + (screen_dimensions.width * y);
|
||||
buf[pos].bytes[0] = '[';
|
||||
pos = x+width + (screen_dimensions.width * y);
|
||||
buf[pos].bytes[0] = ']';
|
||||
pos = x+1 + (screen_dimensions.width * y);
|
||||
f32 base_ratio = 0;
|
||||
if (max != 0) {
|
||||
base_ratio = ((f32)value / (f32)max);
|
||||
}
|
||||
f32 raw_ratio = base_ratio * (width-2);
|
||||
u32 full_spaces_count = (u32) raw_ratio;
|
||||
f32 remainder = raw_ratio - full_spaces_count;
|
||||
for (u32 i = 0; i < full_spaces_count; i++) {
|
||||
buf[pos+i].foreground = ansi_color;
|
||||
renderUtf8CharToBuffer(buf, x+1+i, y, "█", screen_dimensions);
|
||||
}
|
||||
buf[pos+full_spaces_count].foreground = ansi_color;
|
||||
if (value == max) {
|
||||
renderUtf8CharToBuffer(buf, x+1+full_spaces_count, y, "█", screen_dimensions);
|
||||
} else if (remainder > 0.875) {
|
||||
renderUtf8CharToBuffer(buf, x+1+full_spaces_count, y, "▉", screen_dimensions);
|
||||
} else if (remainder > 0.75) {
|
||||
renderUtf8CharToBuffer(buf, x+1+full_spaces_count, y, "▊", screen_dimensions);
|
||||
} else if (remainder > 0.625) {
|
||||
renderUtf8CharToBuffer(buf, x+1+full_spaces_count, y, "▋", screen_dimensions);
|
||||
} else if (remainder > 0.5) {
|
||||
renderUtf8CharToBuffer(buf, x+1+full_spaces_count, y, "▌", screen_dimensions);
|
||||
} else if (remainder > 0.375) {
|
||||
renderUtf8CharToBuffer(buf, x+1+full_spaces_count, y, "▍", screen_dimensions);
|
||||
} else if (remainder > 0.25) {
|
||||
renderUtf8CharToBuffer(buf, x+1+full_spaces_count, y, "▎", screen_dimensions);
|
||||
} else if (remainder > 0.125) {
|
||||
renderUtf8CharToBuffer(buf, x+1+full_spaces_count, y, "▏", screen_dimensions);
|
||||
} else {
|
||||
renderUtf8CharToBuffer(buf, x+1+full_spaces_count, y, " ", screen_dimensions);
|
||||
}
|
||||
}
|
||||
|
||||
fn void renderSpeechBubble(TuiState* tui, u16 x, u16 y, u16 max_width, String message, SpeechTailDirection dir) {
|
||||
Pixel* buf = tui->frame_buffer;
|
||||
Dim2 screen_dimensions = tui->screen_dimensions;
|
||||
@@ -321,23 +244,6 @@ fn void renderSpeechBubble(TuiState* tui, u16 x, u16 y, u16 max_width, String me
|
||||
// TODO actually print the speech tail
|
||||
}
|
||||
|
||||
fn void renderStaticAssetToPixelBuffer(TuiState* tui, u8* asset, u32 len, u16 x, u16 y) {
|
||||
u16 line = 0;
|
||||
u16 x_in_line = 0;
|
||||
u16 pos = x + (tui->screen_dimensions.width * y);
|
||||
for (u32 i = 0; i < len; i++, x_in_line++) {
|
||||
if (asset[i] == '\n') {
|
||||
line += 1;
|
||||
x_in_line = 0;
|
||||
pos = x + (tui->screen_dimensions.width * (y+line));
|
||||
} else if (asset[i] == ' ') {
|
||||
// do nothing, we skip spaces in our assets
|
||||
} else {
|
||||
tui->frame_buffer[pos+x_in_line].bytes[0] = asset[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn void clearServerSentState() {
|
||||
//memset(&state.current_room, 0, sizeof(RenderableRoom));
|
||||
|
||||
@@ -347,7 +253,11 @@ fn void clearServerSentState() {
|
||||
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];
|
||||
dbg("handleIncomingMessage() of len=%d, message=%s\n", len, MESSAGE_STRINGS[msg_type]);
|
||||
u8List bytes = {len, len, message};
|
||||
@@ -355,6 +265,7 @@ fn void handleIncomingMessage(u8* message, u32 len, SocketAddress sender, i32 so
|
||||
ParsedServerMessage parsed = {0};
|
||||
parsed.type = msg_type;
|
||||
switch (msg_type) {
|
||||
case MessageUDPPong:
|
||||
case MessageNewAccountCreated:
|
||||
case MessageBadPw: {/*nothing to parse but the type*/} break;
|
||||
case MessageCharacterId: {
|
||||
@@ -370,29 +281,38 @@ fn void handleIncomingMessage(u8* message, u32 len, SocketAddress sender, i32 so
|
||||
psmThreadSafeQueuePush(network_recv_queue, &parsed);
|
||||
}
|
||||
|
||||
fn void* receiveNetworkUpdates(void* udp) {
|
||||
UDPClient client = *(UDPClient*)udp;
|
||||
dbg("receiveNetworkUpdates() sock=%d\n", client.socket);
|
||||
UDPServer server = {
|
||||
.ready = true,
|
||||
.server_address = client.server_address,
|
||||
.server_socket = client.socket
|
||||
};
|
||||
infiniteReadUDPServer(&server, handleIncomingMessage);
|
||||
fn void* receiveNetworkUpdates(void* multi) {
|
||||
MultiClient *client = (MultiClient*)multi;
|
||||
u32 usec_to_sleep = 1000000; // start @ 1 sec
|
||||
while (!should_quit) {
|
||||
if (!client->tcp_client.ready) {
|
||||
osSleepMicroseconds(usec_to_sleep);
|
||||
netReconnectTCPClient(&client->tcp_client);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
fn void* sendNetworkUpdates(void* udp) {
|
||||
i32 socket_fd = ((UDPClient*)udp)->socket;
|
||||
dbg("sendNetworkUpdates() sock=%d\n", socket_fd);
|
||||
fn void* sendNetworkUpdates(void* multi_client) {
|
||||
MultiClient *client = (MultiClient*)multi_client;
|
||||
u8List bytes_list = {0};
|
||||
while (!should_quit) {
|
||||
UDPMessage msg = {0};
|
||||
NetworkMessage msg = {0};
|
||||
outgoingMessageQueuePop(network_send_queue, &msg);
|
||||
bytes_list.items = msg.bytes;
|
||||
bytes_list.length = msg.bytes_len;
|
||||
bytes_list.capacity = msg.bytes_len;
|
||||
sendUDPu8List(socket_fd, &msg.address, &bytes_list);
|
||||
if (msg.tcp) {
|
||||
sendTCPMessage(msg);
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
@@ -400,7 +320,6 @@ fn void* sendNetworkUpdates(void* udp) {
|
||||
fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count) {
|
||||
GameState* state = (GameState*) s;
|
||||
state->loop_count = loop_count;
|
||||
UDPClient* udp = &state->client;
|
||||
state->old_screen = state->screen;
|
||||
Dim2 screen_dimensions = tui->screen_dimensions;
|
||||
bool game_screen_changed = state->old_screen != state->screen; // detect new screens
|
||||
@@ -410,6 +329,13 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count
|
||||
fflush(stdout);
|
||||
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
|
||||
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) {
|
||||
msg_iters += 0;
|
||||
switch (msg.type) {
|
||||
case MessageUDPPong: {
|
||||
state->udp_pongs_recieved += 1;
|
||||
} break;
|
||||
case MessageNewAccountCreated: {
|
||||
state->screen = ScreenCreateCharacter;
|
||||
} 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);
|
||||
msg_iters++;
|
||||
}
|
||||
|
||||
|
||||
// operate on screen-based input+state
|
||||
bool user_pressed_esc = input_buffer[0] == ASCII_ESCAPE && input_buffer[1] == 0;
|
||||
bool user_pressed_a_number = input_buffer[0] >= '1' && input_buffer[0] <= '9' && input_buffer[1] == 0;
|
||||
@@ -476,8 +405,8 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count
|
||||
state->menu.len = 2;
|
||||
} else {
|
||||
// send character color to server
|
||||
UDPMessage msg = {0};
|
||||
msg.address = udp->server_address;
|
||||
NetworkMessage msg = { .tcp = true, .socket_fd = state->client.tcp_client.socket };
|
||||
msg.address = state->client.tcp_client.server_address;
|
||||
msg.bytes_len = 2;
|
||||
// 1. msg type/CommandType
|
||||
msg.bytes[0] = CommandCreateCharacter;
|
||||
@@ -513,8 +442,23 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count
|
||||
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
|
||||
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;
|
||||
u32 tabs_y = 1 + 2 + 2;
|
||||
u32 tx = 2;
|
||||
@@ -573,13 +517,13 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count
|
||||
} else {
|
||||
u32 msg_idx = 0;
|
||||
// drop the login message into the network_send_queue
|
||||
UDPMessage msg = {0};
|
||||
msg.address = udp->server_address;
|
||||
NetworkMessage msg = { .tcp = true, .socket_fd = state->client.tcp_client.socket };
|
||||
msg.address = state->client.tcp_client.server_address;
|
||||
// 1. msg type/CommandType
|
||||
msg.bytes[msg_idx++] = CommandLogin;
|
||||
// 2. our LAN-IP to handle the case where we are on the same LAN as the guy we are trying to fight
|
||||
// and our "listened" UDP port
|
||||
msg_idx += writeU16ToBufferLE(msg.bytes + msg_idx, ~(udp->client_port));
|
||||
msg_idx += writeU16ToBufferLE(msg.bytes + msg_idx, ~(state->client.udp_client.client_port));
|
||||
msg_idx += writeI32ToBufferLE(msg.bytes + msg_idx, ~osLanIPAddress());
|
||||
// 2. how long is the name
|
||||
msg.bytes[msg_idx++] = state->login_state.name.length;
|
||||
@@ -671,11 +615,6 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count
|
||||
break;
|
||||
}
|
||||
|
||||
if (loop_count % 10 == 0) {
|
||||
outgoingMessageQueuePush(network_send_queue, &state->keep_alive_msg);
|
||||
//outgoingMessageQueuePush(network_send_queue, &testm);
|
||||
}
|
||||
|
||||
return should_quit;
|
||||
}
|
||||
|
||||
@@ -698,11 +637,7 @@ i32 main(i32 argc, ptr argv[]) {
|
||||
arenaInit(&state.string_arena.a);
|
||||
state.string_arena.mutex = newMutex();
|
||||
state.message_input = stringChunkListInit(&state.string_arena);
|
||||
for (i32 i = 0; i < SYSTEM_MESSAGES_LEN; i++) {
|
||||
system_messages[i].capacity = MAX_SYSTEM_MESSAGE_LEN;
|
||||
system_messages[i].length = 0;
|
||||
system_messages[i].items = arenaAllocArraySized(&permanent_arena, sizeof(u8), MAX_SYSTEM_MESSAGE_LEN);
|
||||
}
|
||||
initSystemMessages(&permanent_arena);
|
||||
|
||||
// network queues
|
||||
network_send_queue = newOutgoingMessageQueue(&permanent_arena);
|
||||
@@ -732,12 +667,11 @@ i32 main(i32 argc, ptr argv[]) {
|
||||
server_address = input_string;
|
||||
}
|
||||
}
|
||||
state.client = createUDPClient(7777, server_address);
|
||||
|
||||
// "hardcoded" keep alive message to periodically send to server
|
||||
state.keep_alive_msg.address = state.client.server_address;
|
||||
state.keep_alive_msg.bytes[0] = CommandKeepAlive;
|
||||
state.keep_alive_msg.bytes_len = 1;
|
||||
state.client = netCreateMultiClient(7777, server_address);
|
||||
if (!state.client.tcp_client.ready || !state.client.udp_client.ready) {
|
||||
printf("a net client wanst ready");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
Thread recv_thread = spawnThread(&receiveNetworkUpdates, &state.client);
|
||||
Thread send_thread = spawnThread(&sendNetworkUpdates, &state.client);
|
||||
|
||||
+477
-32
@@ -6,14 +6,40 @@
|
||||
#define NET_OUTGOING_MESSAGE_QUEUE_LEN 16
|
||||
#endif
|
||||
|
||||
#ifndef NET_SERVER_MAX_CLIENTS
|
||||
#define NET_SERVER_MAX_CLIENTS (16)
|
||||
#endif
|
||||
|
||||
typedef struct sockaddr_in SocketAddress;
|
||||
|
||||
typedef struct TCPServer {
|
||||
bool ready;
|
||||
i32 socket_fd;
|
||||
SocketAddress address;
|
||||
} TCPServer;
|
||||
|
||||
typedef struct UDPServer {
|
||||
bool ready;
|
||||
SocketAddress server_address;
|
||||
i32 server_socket;
|
||||
} 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 {
|
||||
bool ready;
|
||||
SocketAddress server_address;
|
||||
@@ -21,14 +47,22 @@ typedef struct UDPClient {
|
||||
i32 socket;
|
||||
} 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;
|
||||
i32 socket_fd;
|
||||
char* long_bytes; // used when message is longer than UDP max, memory must be managed by caller
|
||||
SocketAddress address;
|
||||
u8 bytes[UDP_MAX_MESSAGE_LEN];
|
||||
} UDPMessage;
|
||||
} NetworkMessage;
|
||||
|
||||
typedef struct OutgoingMessageQueue {
|
||||
UDPMessage items[NET_OUTGOING_MESSAGE_QUEUE_LEN];
|
||||
NetworkMessage items[NET_OUTGOING_MESSAGE_QUEUE_LEN];
|
||||
u32 head;
|
||||
u32 tail;
|
||||
u32 count;
|
||||
@@ -37,6 +71,7 @@ typedef struct OutgoingMessageQueue {
|
||||
Cond not_full;
|
||||
} OutgoingMessageQueue;
|
||||
|
||||
typedef void (*HandleMessageCb)(u8* udp_message, i32 bytes_recieved, SocketAddress sending_address, i32 socket);
|
||||
fn OutgoingMessageQueue* newOutgoingMessageQueue(Arena* a) {
|
||||
OutgoingMessageQueue* result = arenaAlloc(a, sizeof(OutgoingMessageQueue));
|
||||
MemoryZero(result, (sizeof *result));
|
||||
@@ -46,7 +81,7 @@ fn OutgoingMessageQueue* newOutgoingMessageQueue(Arena* a) {
|
||||
return result;
|
||||
}
|
||||
|
||||
fn void outgoingMessageQueuePush(OutgoingMessageQueue* queue, UDPMessage* msg) {
|
||||
fn void outgoingMessageQueuePush(OutgoingMessageQueue* queue, NetworkMessage* msg) {
|
||||
lockMutex(&queue->mutex); {
|
||||
while (queue->count == NET_OUTGOING_MESSAGE_QUEUE_LEN) {
|
||||
waitForCondSignal(&queue->not_full, &queue->mutex);
|
||||
@@ -60,11 +95,11 @@ fn void outgoingMessageQueuePush(OutgoingMessageQueue* queue, UDPMessage* msg) {
|
||||
} 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
|
||||
// copies the ParsedClientCommand into `copy_target` if there is something in the queue
|
||||
// and marks it as popped from the queue
|
||||
UDPMessage* result = NULL;
|
||||
NetworkMessage* result = NULL;
|
||||
|
||||
lockMutex(&q->mutex); {
|
||||
if (q->count > 0) {
|
||||
@@ -80,8 +115,8 @@ fn UDPMessage* outgoingMessageNonblockingQueuePop(OutgoingMessageQueue* q, UDPMe
|
||||
return result;
|
||||
}
|
||||
|
||||
fn UDPMessage* outgoingMessageQueuePop(OutgoingMessageQueue* q, UDPMessage* copy_target) {
|
||||
UDPMessage* result = NULL;
|
||||
fn NetworkMessage* outgoingMessageQueuePop(OutgoingMessageQueue* q, NetworkMessage* copy_target) {
|
||||
NetworkMessage* result = NULL;
|
||||
|
||||
lockMutex(&q->mutex); {
|
||||
while (q->count == 0) {
|
||||
@@ -99,17 +134,43 @@ fn UDPMessage* outgoingMessageQueuePop(OutgoingMessageQueue* q, UDPMessage* copy
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
fn bool socketAddressEqual(SocketAddress a, SocketAddress b) {
|
||||
return a.sin_addr.s_addr == b.sin_addr.s_addr
|
||||
&& a.sin_port == b.sin_port;
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
// the only real difference between a udp "server" and a "client" is the bind() syscall
|
||||
// that the server makes in order to specify a port/address that it's listening on
|
||||
UDPServer createUDPServer(u16 server_port) {
|
||||
UDPServer netCreateUDPServer(u16 server_port) {
|
||||
UDPServer result = {0};
|
||||
// define the address we'll be listening on
|
||||
result.server_address.sin_family = AF_INET;
|
||||
@@ -130,7 +191,7 @@ UDPServer createUDPServer(u16 server_port) {
|
||||
return result;
|
||||
}
|
||||
|
||||
UDPClient createUDPClient(u16 server_port, str addr) {
|
||||
UDPClient netCreateUDPClient(u16 server_port, str addr) {
|
||||
UDPClient result = {0};
|
||||
// define the address we'll be listening on
|
||||
result.server_address.sin_family = AF_INET;
|
||||
@@ -159,40 +220,424 @@ UDPClient createUDPClient(u16 server_port, str addr) {
|
||||
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};
|
||||
i32 bytes_recieved = 0;
|
||||
SocketAddress client_address = {0};
|
||||
i32 addrlen = sizeof(struct sockaddr);
|
||||
while (true) {
|
||||
bytes_recieved = recvfrom(server->server_socket, message_buffer, UDP_MAX_MESSAGE_LEN, 0, (struct sockaddr *)&client_address, (socklen_t*)&addrlen);
|
||||
|
||||
//gethostbyaddr: determine who sent the datagram
|
||||
//struct hostent* hostp = gethostbyaddr(
|
||||
// (const char *)&client_address.sin_addr.s_addr,
|
||||
// sizeof(client_address.sin_addr.s_addr),
|
||||
// AF_INET
|
||||
//);
|
||||
|
||||
//ptr printable_host_IP_address_string = inet_ntoa(client_address.sin_addr);
|
||||
|
||||
handleMessage(message_buffer, bytes_recieved, client_address, server->server_socket);
|
||||
MemoryZero(message_buffer, UDP_MAX_MESSAGE_LEN);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: sendall() to handle cases when the sendto() bytes return value is less than the intended bytes to send... stupid kernel fuckin wit us.
|
||||
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) {
|
||||
return sendto(
|
||||
using_socket,
|
||||
message->items,
|
||||
message->length,
|
||||
0,
|
||||
(const struct sockaddr *)to,
|
||||
sizeof(struct sockaddr)
|
||||
);
|
||||
return sendallto(using_socket, message->items, message->length, to);
|
||||
}
|
||||
|
||||
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 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
|
||||
typedef struct Pixel {
|
||||
u8 foreground;
|
||||
@@ -73,7 +81,53 @@ typedef struct StringSearchScore {
|
||||
u32 description_match_len;
|
||||
} StringSearchScore;
|
||||
|
||||
///// GLOBALS
|
||||
global u8List system_messages[SYSTEM_MESSAGES_LEN] = {0};
|
||||
global u8 system_message_index = 0;
|
||||
|
||||
///// 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) {
|
||||
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) {
|
||||
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_CONSTANT
|
||||
* */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/random.h>
|
||||
#include "shared.h"
|
||||
#include "base/impl.c"
|
||||
#define NET_OUTGOING_MESSAGE_QUEUE_LEN 64
|
||||
#include "lib/network.c"
|
||||
#include "render.c"
|
||||
#include "lib/tui.c"
|
||||
|
||||
///// CONSTANTS
|
||||
#define MAX_THINGS (2<<18)
|
||||
@@ -28,7 +27,6 @@
|
||||
#define GOAL_NETWORK_SEND_LOOP_US 1000000/GOAL_NETWORK_SEND_LOOPS_PER_S
|
||||
#define GOAL_GAME_LOOPS_PER_S 4
|
||||
#define GOAL_GAME_LOOP_US 1000000/GOAL_GAME_LOOPS_PER_S
|
||||
#define CLIENT_TIMEOUT_FRAMES GOAL_GAME_LOOPS_PER_S*3
|
||||
#define CHUNK_SIZE 64
|
||||
#define ACCOUNT_CHUNK_SIZE 64
|
||||
#define PARSED_CLIENT_COMMAND_THREAD_QUEUE_LEN 64
|
||||
@@ -41,6 +39,7 @@ typedef struct ParsedClientCommand {
|
||||
u16 alt_port;
|
||||
u32 sender_ip;
|
||||
u32 alt_ip;
|
||||
i32 socket_fd;
|
||||
StringChunkList name;
|
||||
StringChunkList pass;
|
||||
u64 id;
|
||||
@@ -94,13 +93,15 @@ typedef struct AccountChunk {
|
||||
} AccountChunk;
|
||||
|
||||
typedef struct Client {
|
||||
bool active;
|
||||
u16 lan_port;
|
||||
i32 lan_ip;
|
||||
i32 socket_fd; // the tcp socket
|
||||
ThingRef character_eid;
|
||||
u64 account_id;
|
||||
SocketAddress address;
|
||||
u64 account_id;
|
||||
u64 connected_on;
|
||||
CommandType commands[CLIENT_COMMAND_LIST_LEN];
|
||||
u64 last_ping;
|
||||
} Client;
|
||||
|
||||
typedef struct ClientList {
|
||||
@@ -110,6 +111,7 @@ typedef struct ClientList {
|
||||
} ClientList;
|
||||
|
||||
typedef struct State {
|
||||
bool should_quit;
|
||||
Mutex client_mutex;
|
||||
Mutex mutex;
|
||||
ClientList clients;
|
||||
@@ -117,6 +119,7 @@ typedef struct State {
|
||||
u64 frame;
|
||||
Arena game_scratch;
|
||||
StringArena string_arena;
|
||||
MultiServer server;
|
||||
ParsedClientCommandThreadQueue* network_recv_queue;
|
||||
OutgoingMessageQueue* network_send_queue;
|
||||
Things* things;
|
||||
@@ -344,10 +347,12 @@ fn void exitWithErrorMessage(ptr msg) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
fn u32 pushClient(ClientList* clients, SocketAddress addr) {
|
||||
Client new_client = {0};
|
||||
new_client.last_ping = state.frame;
|
||||
new_client.address = addr;
|
||||
fn u32 pushClient(ClientList* clients, SocketAddress addr, i32 socket_fd) {
|
||||
Client new_client = {
|
||||
.active = true,
|
||||
.socket_fd = socket_fd,
|
||||
.address = addr,
|
||||
};
|
||||
|
||||
// first, try to overwrite an old dc'ed client
|
||||
for (u32 i = 1; i < clients->length; i++) {
|
||||
@@ -398,11 +403,12 @@ fn u32 findClientHandleBySocketAddress(ClientList* clients, SocketAddress addres
|
||||
return 0;
|
||||
}
|
||||
|
||||
fn void handleIncomingMessage(u8* message, u32 len, SocketAddress sender, i32 socket) {
|
||||
dbg("%d: %s from %s:%d\n", len, command_type_strings[message[0]], inet_ntoa(sender.sin_addr), sender.sin_port);
|
||||
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);
|
||||
u32 msg_idx = 0;
|
||||
ParsedClientCommand parsed = {
|
||||
.type = (CommandType)message[msg_idx++],
|
||||
.socket_fd = socket,
|
||||
.sender_ip = sender.sin_addr.s_addr,
|
||||
.sender_port = sender.sin_port,
|
||||
};
|
||||
@@ -413,6 +419,7 @@ fn void handleIncomingMessage(u8* message, u32 len, SocketAddress sender, i32 so
|
||||
.capacity = 0,
|
||||
};
|
||||
switch (parsed.type) {
|
||||
case CommandUDPPing: {} break;
|
||||
case CommandLogin: {
|
||||
// parse the login command
|
||||
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);
|
||||
} break;
|
||||
case CommandKeepAlive:
|
||||
break;
|
||||
case CommandCreateCharacter: {
|
||||
printf("command create character received\n");
|
||||
parsed.byte = message[1];
|
||||
@@ -463,31 +468,52 @@ fn void handleIncomingMessage(u8* message, u32 len, SocketAddress sender, i32 so
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
fn void* receiveNetworkUpdates(void* udp) {
|
||||
UDPServer server = *(UDPServer*)udp;
|
||||
dbg("receiveNetworkUpdates() sock=%d\n", server.server_socket);
|
||||
infiniteReadUDPServer(&server, handleIncomingMessage);
|
||||
fn void removeClientBySocketFd(i32 socket_fd) {
|
||||
Client blank_client = {0};
|
||||
// i=1 because first client is null-client
|
||||
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;
|
||||
}
|
||||
|
||||
fn void* sendNetworkUpdates(void* sock) {
|
||||
fn void* sendNetworkUpdates(void* multi) {
|
||||
ThreadContext tctx = {0};
|
||||
tctxInit(&tctx);
|
||||
i32* socket_ptr = (i32*)sock;
|
||||
i32 socket = *socket_ptr;
|
||||
while (true) {
|
||||
MultiServer *server = (MultiServer*) multi;
|
||||
while (!state.should_quit) {
|
||||
u64 loop_start = osTimeMicrosecondsNow();
|
||||
|
||||
// 1. clear out our "outgoingMessage" queue
|
||||
{
|
||||
UDPMessage to_send = { 0 };
|
||||
UDPMessage* next_to_send = outgoingMessageNonblockingQueuePop(state.network_send_queue, &to_send);
|
||||
{
|
||||
NetworkMessage to_send = { 0 };
|
||||
NetworkMessage* next_to_send = outgoingMessageNonblockingQueuePop(state.network_send_queue, &to_send);
|
||||
u8List bytes_list = { 0 };
|
||||
while (next_to_send != NULL) {
|
||||
bytes_list.items = to_send.bytes;
|
||||
bytes_list.length = to_send.bytes_len;
|
||||
bytes_list.capacity = to_send.bytes_len;
|
||||
sendUDPu8List(socket, &to_send.address, &bytes_list);
|
||||
if (to_send.tcp) {
|
||||
char sbuf[SBUFLEN] = {0};
|
||||
sprintf(sbuf, "sendTCPMessage() sock=%d msg=%s len=%d\n", to_send.socket_fd, MESSAGE_STRINGS[to_send.bytes[0]], to_send.bytes_len);
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
for (u32 i = 1; i < state.clients.length; 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));
|
||||
continue;
|
||||
}
|
||||
@@ -516,8 +542,8 @@ fn void* sendNetworkUpdates(void* sock) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fn bool messageSendCharacterId(SocketAddress sender, ThingRef id) {
|
||||
UDPMessage outgoing_message = {0};
|
||||
fn bool messageSendCharacterId(i32 socket_fd, SocketAddress sender, ThingRef id) {
|
||||
NetworkMessage outgoing_message = { .tcp = true, .socket_fd = socket_fd };
|
||||
outgoing_message.address = sender;
|
||||
outgoing_message.bytes_len = 9;
|
||||
outgoing_message.bytes[0] = (u8)MessageCharacterId;
|
||||
@@ -536,7 +562,7 @@ fn void* gameLoop(void* params) {
|
||||
tctxInit(&tctx);
|
||||
printf("Lane %lld (%lld) of %lld starting.\n", lane_ctx->lane_idx, LaneIdx(), lane_ctx->lane_count);
|
||||
fflush(stdout);
|
||||
UDPMessage outgoing_message = {0};
|
||||
NetworkMessage outgoing_message = {0};
|
||||
u64 loop_start;
|
||||
u64 last_burn = 0;
|
||||
u64 last_hp_regen = 0;
|
||||
@@ -563,13 +589,19 @@ fn void* gameLoop(void* params) {
|
||||
u32 client_handle = findClientHandleBySocketAddress(&state.clients, sender);
|
||||
Client* client = &state.clients.items[client_handle];
|
||||
switch (msg.type) {
|
||||
case CommandKeepAlive: {
|
||||
dbg("KeepAlive for client_handle=%d, %ld", client_handle, state.frame);
|
||||
state.clients.items[client_handle].last_ping = state.frame;
|
||||
case CommandUDPPing: {
|
||||
// send UDP Pong right back
|
||||
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;
|
||||
case CommandLogin: {
|
||||
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];
|
||||
printf("pushed new client handle = %d\n", client_handle);
|
||||
}
|
||||
@@ -602,9 +634,11 @@ fn void* gameLoop(void* params) {
|
||||
printf(" pw matched\n");
|
||||
} else {
|
||||
// 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_len = 1;
|
||||
outgoing_message.address = sender;
|
||||
outgoingMessageQueuePush(state.network_send_queue, &outgoing_message);
|
||||
printf("MessageBadPw sent\n");
|
||||
break;
|
||||
@@ -622,12 +656,14 @@ fn void* gameLoop(void* params) {
|
||||
client->character_eid = existing_account->eid;
|
||||
|
||||
// tell the client their character id
|
||||
messageSendCharacterId(sender, existing_account->eid);
|
||||
messageSendCharacterId(client->socket_fd, sender, existing_account->eid);
|
||||
} else {
|
||||
// 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_len = 1;
|
||||
outgoing_message.address = sender;
|
||||
outgoingMessageQueuePush(state.network_send_queue, &outgoing_message);
|
||||
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);
|
||||
|
||||
// tell the client their character id
|
||||
messageSendCharacterId(sender, character.id);
|
||||
messageSendCharacterId(client->socket_fd, sender, character.id);
|
||||
} else {
|
||||
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.items = arenaAllocArray(&permanent_arena, Account, ACCOUNT_CHUNK_SIZE);
|
||||
state.things = arenaAllocZero(&permanent_arena, sizeof(Things));
|
||||
initSystemMessages(&permanent_arena);
|
||||
|
||||
// networking threads (send + receive)
|
||||
UDPServer listener = createUDPServer(SERVER_PORT);
|
||||
if (!listener.ready) {
|
||||
exitWithErrorMessage("Couldn't start the udp server");
|
||||
state.server = netCreateMultiServer(SERVER_PORT);
|
||||
if (!state.server.tcp_server.ready || !state.server.udp_server.ready) {
|
||||
exitWithErrorMessage("Couldn't start the tcp or udp server");
|
||||
}
|
||||
Thread send_thread = spawnThread(&sendNetworkUpdates, &listener.server_socket);
|
||||
Thread recv_thread = spawnThread(&receiveNetworkUpdates, &listener);
|
||||
Thread send_thread = spawnThread(&sendNetworkUpdates, &state.server);
|
||||
Thread recv_thread = spawnThread(&receiveNetworkUpdates, &state.server);
|
||||
|
||||
u64 lane_broadcast_val = 0;
|
||||
Barrier barrier = osBarrierAlloc(GAME_THREAD_CONCURRENCY);
|
||||
|
||||
+105
-3
@@ -6,6 +6,9 @@
|
||||
///// #define some game-tunable constants
|
||||
#define GAME_CONSTANT_ONE (1)
|
||||
#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_MESSAGE_SIZE (THING_HEADER_MESSAGE_SIZE+2+2+2+2+8+1+1)
|
||||
|
||||
@@ -49,16 +52,16 @@ typedef enum Direction {
|
||||
|
||||
typedef enum CommandType {
|
||||
CommandInvalid,
|
||||
CommandKeepAlive,
|
||||
CommandLogin,
|
||||
CommandCreateCharacter,
|
||||
CommandUDPPing,
|
||||
CommandType_Count,
|
||||
} CommandType;
|
||||
static const char* command_type_strings[] = {
|
||||
static const char* COMMAND_TYPE_STRINGS[] = {
|
||||
"Invalid",
|
||||
"KeepAlive",
|
||||
"Login",
|
||||
"CreateCharacter",
|
||||
"UDP Ping",
|
||||
};
|
||||
|
||||
typedef enum Message {
|
||||
@@ -66,6 +69,7 @@ typedef enum Message {
|
||||
MessageCharacterId,
|
||||
MessageBadPw,
|
||||
MessageNewAccountCreated,
|
||||
MessageUDPPong,
|
||||
Message_Count,
|
||||
} Message;
|
||||
static const char* MESSAGE_STRINGS[] = {
|
||||
@@ -73,6 +77,104 @@ static const char* MESSAGE_STRINGS[] = {
|
||||
"CharacterId",
|
||||
"BadPw",
|
||||
"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
|
||||
|
||||
Reference in New Issue
Block a user