diff --git a/src/network/client/CMakeLists.txt b/src/network/client/CMakeLists.txt index f37255e..e2217ee 100644 --- a/src/network/client/CMakeLists.txt +++ b/src/network/client/CMakeLists.txt @@ -9,5 +9,4 @@ target_sources(${DUSK_TARGET_NAME} client.c ) -# Subdirs -add_subdirectory(networked) \ No newline at end of file +# Subdirs \ No newline at end of file diff --git a/src/network/client/client.c b/src/network/client/client.c index 184a07a..aafec49 100644 --- a/src/network/client/client.c +++ b/src/network/client/client.c @@ -6,122 +6,91 @@ */ #include "client.h" -#include "assert/assert.h" #include "util/memory.h" -#include "console/console.h" -#include "network/server/server.h" +#include "assert/assert.h" client_t CLIENT; -void cmdJoin(const consolecmdexec_t *exec) { - clientconnect_t connect; - - if(exec->argc > 0) { - connect.type = CLIENT_TYPE_NETWORKED; - connect.networked.port = SERVER_DEFAULT_PORT; - } else { - connect.type = CLIENT_TYPE_SINGLE_PLAYER; - } - - errorret_t ret = clientConnect(connect); - if(ret == ERROR_OK) { - consolePrint("Connected to server"); - } else { - consolePrint("Failed to connect to server: %s", errorString()); - } - errorFlush(); -} - -void cmdLeave(const consolecmdexec_t *exec) { - clientDisconnect(); -} - void clientInit() { memoryZero(&CLIENT, sizeof(client_t)); - consoleRegCmd("join", cmdJoin); - consoleRegCmd("leave", cmdLeave); } -errorret_t clientConnect(const clientconnect_t connect) { - errorret_t ret; +void clientConnect(const clientconnect_t connect) { + int32_t error; - // Don't connect if already connected. - if(CLIENT.state != CLIENT_STATE_DISCONNECTED) { - return error("Client is already connected"); - } - - // Init the client. + assertTrue( + CLIENT.state == CLIENT_STATE_DISCONNECTED, + "Client is not in a disconnected state." + ); + + // Reset state + packetQueueInit(&CLIENT.queue); + CLIENT.error = CLIENT_ERROR_NO_ERROR; + CLIENT.state = CLIENT_STATE_CONNECTING; CLIENT.type = connect.type; - packetQueueInit(&CLIENT.packetQueue); - - // Change how we connect based on the type. + + // Init the properties based on the type of client. switch(connect.type) { case CLIENT_TYPE_NETWORKED: - ret = networkedClientConnect(&CLIENT, connect.networked); + networkClientConnect(&CLIENT, connect); + break; + + case CLIENT_TYPE_SINGLE_PLAYER: break; default: - assertUnreachable("Invalid client type"); + assertUnreachable("Invalid client connection type."); + break; } - // Should be connected now. - if(ret != ERROR_OK) CLIENT.state = CLIENT_STATE_DISCONNECTED; - return ret; -} - -void clientUpdate() { - packet_t packet; - int32_t ret; - errorret_t err; - - // Don't do anything if not connected - if(CLIENT.state == CLIENT_STATE_DISCONNECTED) return; - - do { - // Pop the packet off the queue. - ret = packetQueuePopIn( - &CLIENT.packetQueue, - &packet - ); - - // No more packets to process, just break out. - if(ret == 0) break; - - // An error occured? - if(ret < 0) { - clientDisconnect(); - consolePrint("Failed to pop packet"); - return; - } - - // Process the packet - err = packetClientProcess(&packet, &CLIENT); - if(err != ERROR_OK) { - clientDisconnect(); - consolePrint("Failed to process packet %s", errorString()); - errorFlush(); - return; - } - } while(true); + // Spawn the connect thread which will be responsible from here on out. + error = pthread_create( + &CLIENT.connectThread, + NULL, + (void *(*)(void *))clientThreadConnect, + &CLIENT + ); + if(error != 0) { + CLIENT.state = CLIENT_STATE_DISCONNECTED; + CLIENT.error = CLIENT_ERROR_CONNECT_THREAD_SPAWN_FAILED; + } } void clientDisconnect() { - // Don't disconnect if already disconnected. if(CLIENT.state == CLIENT_STATE_DISCONNECTED) return; + if(CLIENT.state == CLIENT_STATE_DISCONNECTING) return; +} - // Disconnect the client. - switch(CLIENT.type) { - case CLIENT_TYPE_NETWORKED: - networkedClientDisconnect(&CLIENT); - break; - - default: - assertUnreachable("Invalid client type"); - } - - consolePrint("Client disconnected"); +void clientUpdate() { + assertIsMainThread("Client update called from non-main thread."); } void clientDispose() { - clientDisconnect(); + if(CLIENT.state != CLIENT_STATE_DISCONNECTED) { + clientDisconnect(); + } +} + +void * clientThreadConnect(void *arg) { + assertNotNull(arg, "Client thread connect argument is null."); + assertNotMainThread("Client thread connect called from main thread."); + + client_t *client = (client_t *)arg; + assertTrue(client == &CLIENT, "Invalid client thread arg."); + + if(client->state) return NULL; + + switch(client->type) { + case CLIENT_TYPE_NETWORKED: + break; + + case CLIENT_TYPE_SINGLE_PLAYER: + break; + + default: + assertUnreachable("Invalid client type."); + break; + } + + // } \ No newline at end of file diff --git a/src/network/client/client.h b/src/network/client/client.h index 1b78845..aec8898 100644 --- a/src/network/client/client.h +++ b/src/network/client/client.h @@ -6,14 +6,20 @@ */ #pragma once -#include "network/client/networked/networkedclient.h" #include "network/packet/packetqueue.h" +#include "networkclient.h" +/** + * Type of connection used for a client. + */ typedef enum { - CLIENT_TYPE_NETWORKED, - CLIENT_TYPE_SINGLE_PLAYER, + CLIENT_TYPE_SINGLE_PLAYER = 0, + CLIENT_TYPE_NETWORKED = 1, } clienttype_t; +/** + * The state of the client and what it is doing. + */ typedef enum { CLIENT_STATE_DISCONNECTED = 0, CLIENT_STATE_CONNECTING, @@ -21,50 +27,91 @@ typedef enum { CLIENT_STATE_DISCONNECTING, } clientstate_t; +/** + * Information used to connect to a server. + */ typedef struct clientconnect_s { clienttype_t type; - union { - networkedclientconnect_t networked; - }; } clientconnect_t; +/** + * Error codes for the client. + */ +typedef enum { + CLIENT_ERROR_NO_ERROR = 0, + CLIENT_ERROR_CONNECT_THREAD_SPAWN_FAILED, +} clienterror_t; + +/** + * The clients state and information. + */ typedef struct client_s { clientstate_t state; clienttype_t type; - packetqueue_t packetQueue; + packetqueue_t queue; + clienterror_t error; + + pthread_t connectThread; + union { - networkedclient_t networked; + networkclient_t network; }; } client_t; extern client_t CLIENT; /** - * Initializes the client. - * - * @return Error code indicating success or failure. + * Initializes the client object, does not perform any connection. */ void clientInit(); /** - * Connects to a server. + * Initiates a client connection, this function will not block and will return + * immediately even if a connection issue occurs. It is up to the caller to + * handle the connection state and any errors that occur. * - * @param connect Connection information. - * @return Error code indicating success or failure. + * Client must be already disconnected before calling this function. + * + * @param connect The connection information to use. */ -errorret_t clientConnect(const clientconnect_t connect); +void clientConnect(const clientconnect_t connect); /** - * Updates the client state. - */ -void clientUpdate(); - -/** - * Disconnects the client from the server. + * Requests the client to disconnect from the server. This will not block and + * will return immediately. The client will handle the disconnection in the + * background. */ void clientDisconnect(); /** - * Cleans up the client resources. + * Internal method that writes a packet to the client immediately. */ -void clientDispose(); \ No newline at end of file +clienterror_t clientWritePacket(const packet_t *packet); + +/** + * Internal method that reads a packet from the client immediately. + * + * @param packet The packet to read into. + * @return Error code indicating success or failure. + */ +clienterror_t clientReadPacket(const packet_t *packet); + +/** + * Main update loop for the client, this will handle all incoming packets and + * update the client state. + */ +void clientUpdate(); + +/** + * Disposes of the client, cleans up any resources and will force disconnect if + * not already disconnected. + */ +void clientDispose(); + +/** + * Thread function responsible for initiating and handshaking the connection. + * + * @param arg The client connection information. + * @return Returns NULL, unless an error occurs. + */ +void * clientThreadConnect(void *arg); \ No newline at end of file diff --git a/src/network/client/networkclient.c b/src/network/client/networkclient.c new file mode 100644 index 0000000..6a96b64 --- /dev/null +++ b/src/network/client/networkclient.c @@ -0,0 +1,8 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "networkclient.h" \ No newline at end of file diff --git a/src/network/client/networkclient.h b/src/network/client/networkclient.h new file mode 100644 index 0000000..93ff7c5 --- /dev/null +++ b/src/network/client/networkclient.h @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "dusk.h" + +typedef struct client_s client_t; +typedef struct clientconnect_s clientconnect_t; + +typedef struct { + void *nothing; +} networkclient_t; + +/** + * Initialize the client. + * + * @param client The client to initialize. + * @param connect The connection information. + */ +void networkClientConnect(client_t *client, const clientconnect_t connect); \ No newline at end of file diff --git a/src/network/client/networked/CMakeLists.txt b/src/network/client/networked/CMakeLists.txt deleted file mode 100644 index b0ac150..0000000 --- a/src/network/client/networked/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) 2025 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -# Sources -target_sources(${DUSK_TARGET_NAME} - PRIVATE - networkedclient.c -) \ No newline at end of file diff --git a/src/network/client/networked/networkedclient.c b/src/network/client/networked/networkedclient.c deleted file mode 100644 index 8971555..0000000 --- a/src/network/client/networked/networkedclient.c +++ /dev/null @@ -1,417 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "network/client/client.h" -#include "assert/assert.h" -#include "console/console.h" - -errorret_t networkedClientConnect( - client_t *client, - const networkedclientconnect_t connInfo -) { - int32_t ret; - packet_t packet; - errorret_t err; - char_t *ip = "127.0.0.1"; - - // Validate the params - assertNotNull(client, "Client is NULL"); - assertTrue(client->type == CLIENT_TYPE_NETWORKED, "Client is not networked"); - assertIsMainThread("Client connect must be on main thread"); - - // Set the state to connecting. - client->state = CLIENT_STATE_CONNECTING; - consolePrint("Connecting to server %s:%d", ip, connInfo.port); - - // Create a socket - client->networked.socket = socket(AF_INET, SOCK_STREAM, 0); - if(client->networked.socket < 0) { - return error( - "Failed to create socket %s", - errno > 0 ? strerror(errno) : "Unknown error" - ); - } - - // Set ip address and port - client->networked.address.sin_family = AF_INET; - client->networked.address.sin_port = htons(connInfo.port); - client->networked.address.sin_addr.s_addr = inet_addr(ip); - ret = inet_pton(AF_INET, ip, &client->networked.address.sin_addr); - if(ret <= 0) { - close(client->networked.socket); - return error( - "Invalid or bad IP address %s: %s", - ip, - errno > 0 ? strerror(errno) : "Unknown error" - ); - } - - // Connect to the server - ret = connect( - client->networked.socket, - (struct sockaddr *)&client->networked.address, - sizeof(client->networked.address) - ); - if(ret < 0) { - close(client->networked.socket); - switch(errno) { - case ECONNREFUSED: - return error("Failed to connect: Connection refused"); - case ETIMEDOUT: - return error("Failed to connect: Connection timed out"); - case ENETUNREACH: - return error("Failed to connect: Network unreachable"); - default: - return error("Failed to connect: Unknown error"); - } - } - - // Initialize mutexes and locks and condition variable - pthread_mutex_init(&client->networked.lock, NULL); - pthread_cond_init(&client->networked.cond, NULL); - pthread_mutex_init(&client->networked.readLock, NULL); - pthread_mutex_init(&client->networked.writeLock, NULL); - - // Send the version - { - const char_t *message = "DUSK|"DUSK_VERSION; - ssize_t sent = send( - client->networked.socket, - message, - strlen(message), - 0 - ); - } - - // We should now receive a welcome packet or a disconnect packet. - err = networkedClientReadPacket(client, &packet); - if(err) return err; - switch(packet.type) { - case PACKET_TYPE_WELCOME: - err = packetWelcomeClientProcess(&packet, client); - if(err) return err; - break; - - case PACKET_TYPE_DISCONNECT: - err = packetDisconnectClientProcess(&packet, client); - if(err) return err; - return error("Server disconnected"); - - default: - return error("Server did not send welcome message."); - } - - // Connection was established, hand off to read thread - ret = pthread_create( - &client->networked.readThread, - NULL, - networkedClientReadThread, - client - ); - if(ret != 0) { - close(client->networked.socket); - pthread_mutex_destroy(&client->networked.readLock); - pthread_mutex_destroy(&client->networked.writeLock); - return error( - "Failed to create client read thread %s", - errno > 0 ? strerror(errno) : "Unknown error" - ); - } - - // Wait for the read thread to signal that it has started - pthread_mutex_lock(&client->networked.lock); - while (client->state == CLIENT_STATE_CONNECTING) { - pthread_cond_wait(&client->networked.cond, &client->networked.lock); - } - pthread_mutex_unlock(&client->networked.lock); - - // Start the write thread after the handshake - ret = pthread_create( - &client->networked.writeThread, - NULL, - networkedClientWriteThread, // Renamed from networkedClientRightThread - client - ); - if(ret != 0) { - close(client->networked.socket); - pthread_mutex_destroy(&client->networked.readLock); - pthread_mutex_destroy(&client->networked.writeLock); - return error( - "Failed to create client write thread %s", - errno > 0 ? strerror(errno) : "Unknown error" - ); - } - - return ERROR_OK; -} - -void networkedClientDisconnect(client_t *client) { - assertNotNull(client, "Client is NULL"); - assertIsMainThread("Client disconnect must be on main thread"); - assertTrue(client->type == CLIENT_TYPE_NETWORKED, "Client is not networked"); - assertTrue(client->state == CLIENT_STATE_CONNECTED, "Client not connected"); - - pthread_mutex_lock(&client->networked.lock); - client->state = CLIENT_STATE_DISCONNECTING; - - struct timespec timeout; - clock_gettime(CLOCK_REALTIME, &timeout); - timeout.tv_sec += 1; // Wait for up to 1 second - - while (client->state == CLIENT_STATE_DISCONNECTING) { - if(pthread_cond_timedwait( - &client->networked.cond, - &client->networked.lock, - &timeout - ) == ETIMEDOUT) { - consolePrint("Client disconnect timed out, force closing"); - break; - } - } - pthread_mutex_unlock(&client->networked.lock); - - client->state = CLIENT_STATE_DISCONNECTED; - - // Wait for the threads to finish - if (client->networked.readThread) { - pthread_join(client->networked.readThread, NULL); - client->networked.readThread = 0; - } - - if (client->networked.writeThread) { - pthread_join(client->networked.writeThread, NULL); - client->networked.writeThread = 0; - } - - // Destroy read and write locks - pthread_mutex_destroy(&client->networked.readLock); - pthread_mutex_destroy(&client->networked.writeLock); - - // Close the socket - if (client->networked.socket) { - shutdown(client->networked.socket, SHUT_RDWR); - close(client->networked.socket); - client->networked.socket = 0; - } - - // Destroy mutex and condition variable - pthread_mutex_destroy(&client->networked.lock); - pthread_cond_destroy(&client->networked.cond); -} - -errorret_t networkedClientWrite( - const client_t *client, - const uint8_t *data, - const size_t len -) { - assertNotNull(client, "Client is NULL"); - assertNotNull(data, "Data is NULL"); - assertTrue(len > 0, "Data length is 0"); - assertTrue(client->type == CLIENT_TYPE_NETWORKED, "Client is not networked"); - - if(client->state == CLIENT_STATE_DISCONNECTED) { - return error("Client is disconnected"); - } - - ssize_t sent = send(client->networked.socket, data, len, 0); - if(sent < 0) return error("Failed to send data"); - return ERROR_OK; -} - -errorret_t networkedClientWritePacket( - const client_t *client, - const packet_t *packet -) { - assertNotNull(packet, "Packet is NULL"); - assertTrue(packet->type != PACKET_TYPE_INVALID, "Packet type is INVALID"); - assertTrue(packet->length > 0, "Packet length is 0"); - assertTrue( - packet->length <= sizeof(packetdata_t), - "Packet length is too large (1)" - ); - - size_t fullSize = sizeof(packet_t) - sizeof(packet->data) + packet->length; - assertTrue(fullSize <= sizeof(packet_t), "Packet size is too large (2)"); - return networkedClientWrite(client, (const uint8_t *)packet, fullSize); -} - -errorret_t networkedClientReadPacket( - const client_t *client, - packet_t *packet -) { - uint8_t buffer[sizeof(packet_t)]; - - assertNotNull(client, "Client is NULL"); - assertNotNull(packet, "Packet is NULL"); - assertTrue(client->type == CLIENT_TYPE_NETWORKED, "Client is not networked"); - - if(client->state == CLIENT_STATE_DISCONNECTED) { - return error("Client is disconnected"); - } - - // Read the packet header - ssize_t read = recv( - client->networked.socket, - buffer, - sizeof(packettype_t), - 0 - ); - if(read != sizeof(packettype_t)) { - return error( - "Failed to read packet header %s", - errno > 0 ? strerror(errno) : "Unknown error" - ); - } - - packet->type = *(packettype_t *)buffer; - - // Read the packet length - read = recv( - client->networked.socket, - buffer, - sizeof(uint32_t), - 0 - ); - if(read != sizeof(uint32_t)) { - return error( - "Failed to read packet length %s", - errno > 0 ? strerror(errno) : "Unknown error" - ); - } - if(read > sizeof(packetdata_t)) { - return error("Packet length is too large"); - } - packet->length = *(uint32_t *)buffer; - - // Now, read the packet data - read = recv( - client->networked.socket, - (uint8_t *)&packet->data, - packet->length, - 0 - ); - if(read != packet->length) { - return error( - "Failed to read packet data %s", - errno > 0 ? strerror(errno) : "Unknown error" - ); - } - return ERROR_OK; -} - -void * networkedClientReadThread(void *arg) { - assertNotNull(arg, "Client thread argument is NULL"); - assertNotMainThread("Client thread must not be on main thread"); - - packet_t packet; - client_t *client = (client_t *)arg; - assertTrue( - client->type == CLIENT_TYPE_NETWORKED, - "Client thread argument is not networked" - ); - assertTrue( - client->state == CLIENT_STATE_CONNECTING, - "Client thread argument is not connecting" - ); - - // Notify the main thread that we are connected - pthread_mutex_lock(&client->networked.lock); - client->state = CLIENT_STATE_CONNECTED; - pthread_cond_signal(&client->networked.cond); - pthread_mutex_unlock(&client->networked.lock); - - while(client->state == CLIENT_STATE_CONNECTED) { - pthread_mutex_lock(&client->networked.readLock); - - // Read a packet from the server - errorret_t err = networkedClientReadPacket(client, &packet); - if(err) { - consolePrint("Failed to read packet %s", errorString()); - errorFlush(); - pthread_mutex_unlock(&client->networked.readLock); - break; - } - - // Handle disconnect packets here - if(packet.type == PACKET_TYPE_DISCONNECT) { - err = packetDisconnectClientProcess(&packet, client); - if(err) { - consolePrint(errorString()); - errorFlush(); - } else { - consolePrint("Server disconnected"); - } - client->state = CLIENT_STATE_DISCONNECTING; - pthread_mutex_unlock(&client->networked.readLock); - break; - } - - packetQueuePushIn( - &client->packetQueue, - &packet - ); - - pthread_mutex_unlock(&client->networked.readLock); - } - - client->state = CLIENT_STATE_DISCONNECTED; - - pthread_mutex_lock(&client->networked.lock); - client->state = CLIENT_STATE_DISCONNECTED; - pthread_cond_signal(&client->networked.cond); // Notify the main thread - pthread_mutex_unlock(&client->networked.lock); -} - -void * networkedClientWriteThread(void *arg) { - packet_t packet; - int32_t ret; - errorret_t err; - - assertNotNull(arg, "Write thread argument is NULL"); - assertNotMainThread("Write thread must not be on main thread"); - - client_t *client = (client_t *)arg; - assertTrue( - client->type == CLIENT_TYPE_NETWORKED, - "Write thread argument is not networked" - ); - - while(client->state == CLIENT_STATE_CONNECTED) { - pthread_mutex_lock(&client->networked.writeLock); - - ret = packetQueuePopOut( - &client->packetQueue, - &packet - ); - - if(ret == 0) { - pthread_mutex_unlock(&client->networked.writeLock); - continue; - } - - if(ret < 0) { - consolePrint("Failed to pop packet from queue %s", errorString()); - errorFlush(); - client->state = CLIENT_STATE_DISCONNECTING; - pthread_mutex_unlock(&client->networked.writeLock); - break; - } - - err = networkedClientWritePacket(client, &packet); - if(err) { - consolePrint("Failed to write packet %s", errorString()); - errorFlush(); - client->state = CLIENT_STATE_DISCONNECTING; - pthread_mutex_unlock(&client->networked.writeLock); - break; - } - - pthread_mutex_unlock(&client->networked.writeLock); // Unlock after writing - } - - return NULL; -} \ No newline at end of file diff --git a/src/network/client/networked/networkedclient.h b/src/network/client/networked/networkedclient.h deleted file mode 100644 index d1001ab..0000000 --- a/src/network/client/networked/networkedclient.h +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" -#include -#include "network/packet/packetqueue.h" - -typedef struct client_s client_t; -typedef struct clientconnect_s clientconnect_t; - -typedef struct { - uint16_t port; -} networkedclientconnect_t; - -typedef struct { - int32_t socket; - struct sockaddr_in address; - pthread_t readThread; - pthread_t writeThread; - pthread_mutex_t lock; - pthread_cond_t cond; - pthread_mutex_t readLock; - pthread_mutex_t writeLock; -} networkedclient_t; - -/** - * Connects to a networked server. - * - * @param client Pointer to the client structure. - * @param connect Connection information. - */ -errorret_t networkedClientConnect( - client_t *client, - const networkedclientconnect_t connect -); - -/** - * Closes the connection to a networked server. - * - * @param client Pointer to the client structure. - */ -void networkedClientDisconnect(client_t *client); - -/** - * Writes data to the networked server. - * - * @param client Pointer to the client structure. - * @param data Data to write. - * @param len Length of the data. - * @return Error code. - */ -errorret_t networkedClientWrite( - const client_t *client, - const uint8_t *data, - const size_t len -); - -/** - * Writes a packet to the networked server. - * - * @param client Pointer to the client structure. - * @param packet Pointer to the packet structure. - * @return Error code. - */ -errorret_t networkedClientWritePacket( - const client_t *client, - const packet_t *packet -); - -/** - * Reads a packet from the networked server. - * - * @param client Pointer to the client structure. - * @param packet Pointer to the packet structure to read into. - * @return Error code. - */ -errorret_t networkedClientReadPacket(const client_t *client, packet_t *packet); - -/** - * Thread function for handling networked client connections. - * - * @param arg Pointer to the client structure. - * @return NULL. - */ -void * networkedClientReadThread(void *arg); // Renamed from networkedClientThread - -/** - * Thread function for handling additional client operations. - * - * @param arg Pointer to the client structure. - * @return NULL. - */ -void * networkedClientWriteThread(void *arg); // Renamed from networkedClientRightThread \ No newline at end of file