net changes?

This commit is contained in:
2025-05-05 10:55:54 -05:00
parent 28e8466a2a
commit 8411c2981b
8 changed files with 165 additions and 643 deletions

View File

@ -9,5 +9,4 @@ target_sources(${DUSK_TARGET_NAME}
client.c
)
# Subdirs
add_subdirectory(networked)
# Subdirs

View File

@ -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;
}
//
}

View File

@ -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();
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);

View File

@ -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"

View File

@ -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);

View File

@ -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
)

View File

@ -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;
}

View File

@ -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 <arpa/inet.h>
#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