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

This commit is contained in:
Tenari
2026-04-04 09:09:50 -05:00
parent 50e06f064a
commit 9eef988820
10 changed files with 931 additions and 487 deletions
+477 -32
View File
@@ -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);
}
-109
View File
@@ -1,109 +0,0 @@
#include "thread.h"
typedef struct ThreadQueue {
void* items;
u32 type_size;
u32 max;
u32 head;
u32 tail;
u32 count;
Mutex mutex;
Cond not_empty;
Cond not_full;
} ThreadQueue;
Thread spawnThread(void * (*threadFn)(void *), void* thread_arg) {
pthread_t thread;
pthread_create(&thread, NULL, threadFn, thread_arg);
Thread result = { thread };
return result;
}
Mutex newMutex() {
Mutex result = { PTHREAD_MUTEX_INITIALIZER };
return result;
}
Cond newCond() {
Cond result = { 0 };
pthread_cond_init(&result.cond, NULL);
return result;
}
void lockMutex(Mutex* m) {
pthread_mutex_lock(&m->mutex);
}
void unlockMutex(Mutex* m) {
pthread_mutex_unlock(&m->mutex);
}
void signalCond(Cond* cond) {
pthread_cond_signal(&cond->cond);
}
void waitForCondSignal(Cond* cond, Mutex* mutex) {
pthread_cond_wait(&cond->cond, &mutex->mutex);
}
fn ThreadQueue newThreadQueue(Arena* a, u32 type_size, u32 items_max) {
ThreadQueue result = {0};
result.type_size = type_size;
result.max = items_max;
result.items = arenaAllocArraySized(a, type_size, items_max);
result.mutex = newMutex();
result.not_full = newCond();
result.not_empty = newCond();
return result;
}
fn void threadSafeQueuePush(ThreadQueue* queue, void* item) {
lockMutex(&queue->mutex); {
while (queue->count == queue->max) {
waitForCondSignal(&queue->not_full, &queue->mutex);
}
memcpy(queue->items + (queue->tail * (sizeof item)), item, queue->type_size);
queue->tail = (queue->tail + 1) % queue->max;
queue->count++;
signalCond(&queue->not_empty);
} unlockMutex(&queue->mutex);
}
fn void* threadSafeQueuePop(ThreadQueue* q) {
void* result = NULL;
lockMutex(&q->mutex); {
while (q->count == 0) {
waitForCondSignal(&q->not_empty, &q->mutex);
}
result = &q->items[q->head];
q->head = (q->head + 1) % q->max;
q->count--;
signalCond(&q->not_full);
} unlockMutex(&q->mutex);
return result;
}
// immediately returns NULL if there's nothing in the ThreadQueue
fn void* threadSafeNonblockingQueuePop(ThreadQueue* q, void* copy_target, u64 len) {
void* result = NULL;
lockMutex(&q->mutex); {
if (q->count > 0) {
result = &q->items[q->head];
MemoryCopy(copy_target, result, len);
q->head = (q->head + 1) % q->max;
q->count--;
signalCond(&q->not_full);
}
} unlockMutex(&q->mutex);
return result;
}
-29
View File
@@ -1,29 +0,0 @@
#ifndef LIB_THREAD_H
#define LIB_THREAD_H
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include "../base/include.h"
typedef struct Thread {
pthread_t thread;
} Thread;
typedef struct Mutex {
pthread_mutex_t mutex;
} Mutex;
typedef struct Cond {
pthread_cond_t cond;
} Cond;
Thread spawnThread(void * (*threadFn)(void *), void* thread_arg);
Mutex newMutex();
Cond newCond();
void lockMutex(Mutex* m);
void unlockMutex(Mutex* m);
void signalCond(Cond* cond);
void waitForCondSignal(Cond* cond, Mutex* mutex);
#endif //LIB_THREAD_H
+112
View File
@@ -25,6 +25,14 @@
#define ANSI_HIGHLIGHT_GRAY (16)
#define 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);
}