diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..11264b7 --- /dev/null +++ b/.vscode/launch.json @@ -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" + } + ] +} + diff --git a/src/base/all.h b/src/base/all.h index a0bfb5d..d8f73b0 100644 --- a/src/base/all.h +++ b/src/base/all.h @@ -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 +#include + ///// TYPES // integer types typedef unsigned char u8; @@ -438,7 +442,27 @@ union Range1f32 DWORD input_mode; DWORD output_mode; } TermIOs; + // 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 # include # include # include @@ -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); diff --git a/src/client.c b/src/client.c index f833e5b..4f57e17 100644 --- a/src/client.c +++ b/src/client.c @@ -9,14 +9,13 @@ #include #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); diff --git a/src/lib/network.c b/src/lib/network.c index 9278e89..c1c3ca6 100644 --- a/src/lib/network.c +++ b/src/lib/network.c @@ -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); +} + diff --git a/src/lib/thread.c b/src/lib/thread.c deleted file mode 100644 index e081288..0000000 --- a/src/lib/thread.c +++ /dev/null @@ -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; -} - diff --git a/src/lib/thread.h b/src/lib/thread.h deleted file mode 100644 index c920975..0000000 --- a/src/lib/thread.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef LIB_THREAD_H -#define LIB_THREAD_H - -#include -#include -#include -#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 diff --git a/src/lib/tui.c b/src/lib/tui.c index cba2a4f..589e453 100644 --- a/src/lib/tui.c +++ b/src/lib/tui.c @@ -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); } diff --git a/src/render.c b/src/render.c deleted file mode 100644 index 7cbf303..0000000 --- a/src/render.c +++ /dev/null @@ -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"); - } - } - } -} -*/ diff --git a/src/server.c b/src/server.c index 95d042f..f27797e 100644 --- a/src/server.c +++ b/src/server.c @@ -6,14 +6,13 @@ * my_variable * MY_CONSTANT * */ -#include #include #include #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); diff --git a/src/shared.h b/src/shared.h index 0ab83c7..848d5f9 100644 --- a/src/shared.h +++ b/src/shared.h @@ -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