From 6a653c75b98e59f03909da05711a09b834311f01 Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Wed, 21 Dec 2022 00:49:26 +0000 Subject: [PATCH] Implemented proper thread sleeping and additional output thread - Replaced previous inefficient "spin-waiting" with proper thread sleeping. - Added threading primitives to the queue type to enable this. - Added additional thread for output management. - Miscellanous cleanup and restructuring. --- src/client/SilverMUDClient.c | 3 +- src/gamelogic.c | 10 ++++- src/gamelogic.h | 63 +++++++++++++++--------------- src/inputoutput.c | 70 +++++++++++++++++++++++++++++++++ src/inputoutput.h | 74 ++++++++++++++++++++--------------- src/queue.c | 15 ++++++- src/queue.h | 5 +++ src/server/SilverMUDServer.c | 76 +++++++++--------------------------- 8 files changed, 191 insertions(+), 125 deletions(-) diff --git a/src/client/SilverMUDClient.c b/src/client/SilverMUDClient.c index be8b3da..df6ac0a 100644 --- a/src/client/SilverMUDClient.c +++ b/src/client/SilverMUDClient.c @@ -14,6 +14,7 @@ #include #include +#include "../queue.h" #include "../constants.h" #include "../playerdata.h" #include "../texteffects.h" @@ -225,7 +226,7 @@ int main(int argc, char ** argv) serverAddress.sin_port = htons(port); // Connect the server and client sockets, Kronk: - if (connect(socketFileDesc, (sockaddr*)&serverAddress, sizeof(serverAddress)) != 0) + if (connect(socketFileDesc, (sockaddr*)&serverAddress, sizeof(serverAddress)) != 0) { slowPrint("Connection with the Silverkin Industries Comm-Link Server Failed:\nPlease contact your service representative.\n", characterDelay); exit(0); diff --git a/src/gamelogic.c b/src/gamelogic.c index b3ef2a4..60b3e85 100644 --- a/src/gamelogic.c +++ b/src/gamelogic.c @@ -17,7 +17,7 @@ // ======================= // Thread function which runs the main game loop, given the needed parameters: -void * gameLogicLoop(void * parameters) +void * gameLogicHandler(void * parameters) { gameLogicParameters * threadParameters = parameters; inputMessage * currentInput = NULL; @@ -25,11 +25,17 @@ void * gameLogicLoop(void * parameters) while(true) { // Evaluate remaining commands: - if(commandQueue->currentLength != 0) + while(commandQueue->currentLength != 0) { evaluateNextCommand(threadParameters, commandQueue); } + // Wait if there is nothing to do: + if(threadParameters->inputQueue->itemCount == 0) + { + pthread_cond_wait(&threadParameters->inputQueue->condition, &threadParameters->inputQueue->mutex); + } + // Check for new messages and pop them off the queue: if(threadParameters->inputQueue->itemCount != 0) { diff --git a/src/gamelogic.h b/src/gamelogic.h index 97d7d76..631ef6b 100644 --- a/src/gamelogic.h +++ b/src/gamelogic.h @@ -8,35 +8,10 @@ #include "playerdata.h" #include "inputoutput.h" -// Let the compiler know there will be structs defined elsewhere: -typedef struct queue queue; +// ======================== +// -=[ Data Structures ]=-: +// ======================== -// ======================= -// -=[ Main Game Loop ]=-: -// ======================= - -// A data-structure containing the needed parameters for a main game loop: -typedef struct gameLogicParameters -{ - // Players: - int * playerCount; - playerInfo * connectedPlayers; - - // Queues: - queue * inputQueue; - queue * outputQueue; - - // Lists: - list * areaList; - list * globalSkillList; -} gameLogicParameters; - -// Thread function which runs the main game loop, given the needed parameters: -void * gameLogicLoop(void * parameters); - -// ====================== -// -=[ Command Queue ]=-: -// ====================== typedef struct commandEvent commandEvent; typedef struct commandEvent { @@ -56,6 +31,32 @@ typedef struct commandQueue commandEvent * front; } commandQueue; +// A data-structure containing the needed parameters for a main game loop: +typedef struct gameLogicParameters +{ + // Players: + int * playerCount; + playerInfo * connectedPlayers; + + // Queues: + queue * inputQueue; + queue * outputQueue; + + // Lists: + list * areaList; + list * globalSkillList; +} gameLogicParameters; + +// ======================== +// -=[ Functions ]=-: +// ======================== + +// Player movement: +int movePlayerToArea(playerInfo * player, char * requestedPath); + +// Thread function which runs the main game loop, given the needed parameters: +void * gameLogicHandler(void * parameters); + // Create a commandQueue: commandQueue * createCommandQueue(void); @@ -82,9 +83,6 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue); // -=[ Gameplay Primitives ]=-: // ============================ -// Player movement: -int movePlayerToArea(playerInfo * player, char * requestedPath); - typedef enum outcome { CRITICAL_FAILURE, @@ -94,6 +92,9 @@ typedef enum outcome ERROR } outcome; +// Player movement: +int movePlayerToArea(playerInfo * player, char * requestedPath); + // Run a stat check: outcome statCheck(playerInfo * player, int chance, coreStat statToCheck); diff --git a/src/inputoutput.c b/src/inputoutput.c index 93912d6..4bd4b10 100644 --- a/src/inputoutput.c +++ b/src/inputoutput.c @@ -5,7 +5,10 @@ #include #include #include +#include #include + +#include "queue.h" #include "constants.h" #include "playerdata.h" #include "inputoutput.h" @@ -46,6 +49,7 @@ int messageReceive(gnutls_session_t receiveFromSession, userMessage * receiveToM return returnValue; } +// Allocate and initialize an outputMessage targeted to a variable amount of players: outputMessage * createTargetedOutputMessage(userMessage * messageToQueue, playerInfo ** recipients, int recipientsCount) { // Allocate a new output message: @@ -64,10 +68,72 @@ outputMessage * createTargetedOutputMessage(userMessage * messageToQueue, player return newOutputMessage; } +// A function for the output thread, which sends queued messages: +void * outputThreadHandler(void * parameters) +{ + outputThreadParameters * variables = parameters; + queue * outputQueue = variables->outputQueue; + gnutls_session_t * tlssessions = variables->tlssessions; + playerInfo * connectedPlayers = variables->connectedPlayers; + + while(true) + { + if(outputQueue->itemCount == 0) + { + pthread_cond_wait(&outputQueue->condition, &outputQueue->mutex); + } + // Run through the output queue and send all unused messages: + while(outputQueue->itemCount != 0) + { + // Wait until the queue unlocks: + while(outputQueue->lock); + + // Lock the queue: + outputQueue->lock = true; + + // Get a message off the queue: + outputMessage * message = peekQueue(outputQueue)->data.outputMessage; + + // Unlock the queue: + outputQueue->lock = false; + + // If the first target is set to NULL, it's intended for all connected: + if(message->recipientsCount == 0) + { + for (int index = 0; index < PLAYERCOUNT; index++) + { + messageSend(tlssessions[index], message->content); + } + } + // Otherwise, send it only to the targeted players: + else + { + int targetIndex = 0; + for(int index = 0; index < PLAYERCOUNT; index++) + { + if(targetIndex == message->recipientsCount) + { + break; + } + if(&connectedPlayers[index] == message->recipients[targetIndex]) + { + targetIndex++; + messageSend(tlssessions[index], message->content); + } + } + } + // Remove the output message from the queue: + popQueue(outputQueue); + } + } +} + +// Sanatize user input to ensure it's okay to process: void userInputSanatize(char * inputString, int length) { for(int index = 0; index <= length; index++) { + // If it's not a printable character, it has no buisness being here: if(!isprint(inputString[index])) { inputString[index] = '\n'; @@ -75,18 +141,22 @@ void userInputSanatize(char * inputString, int length) break; } } + // Make sure it's null-terminated: inputString[length - 1] = '\0'; } +// Sanatize user names so they display correctly; void userNameSanatize(char * inputString, int length) { for(int index = 0; index <= length; index++) { + // If it's not a printable character, it has no buisness being here: if(!isprint(inputString[index])) { inputString[index] = '\0'; break; } } + // Make sure it's null-terminated: inputString[length - 1] = '\0'; } diff --git a/src/inputoutput.h b/src/inputoutput.h index 72ad893..f554832 100644 --- a/src/inputoutput.h +++ b/src/inputoutput.h @@ -4,33 +4,36 @@ #ifndef INPUTOUTPUT_H #define INPUTOUTPUT_H #include -#include #include -#include "constants.h" -#include "playerdata.h" +#include #include -// A message datastructure containing a user/character name and the content: +#include "queue.h" +#include "constants.h" +#include "playerdata.h" + +// Let the compiler know there will be structs defined elsewhere: +typedef struct queue queue; + +// ======================== +// -=[ Data Structures ]=-: +// ======================== + +// Contains a character/player name and the content of a message: typedef struct userMessage { char senderName[32]; char messageContent[MAX]; } userMessage; -// ================== -// -=[Message I/O]=-: -// ================== +// Contains a userMessage and a pointer to the playerInfo of the connection which sent it: +typedef struct inputMessage +{ + playerInfo * sender; + userMessage * content; +} inputMessage; -// Sends a message to a given TLS session, wraps the calls to gnutls_write: -int messageSend(gnutls_session_t receivingSession, userMessage * messageToSend); - -// Receives a message from a given TLS session, wraps the calls to gnutls_read: -int messageReceive(gnutls_session_t receiveFromSession, userMessage * receiveToMessage); - -// =================== -// -=[Output Queue]=-: -// =================== -typedef struct outputMessage outputMessage; +// Contains a userMessage and pointers to the playerInfo of the recipients and the number of them: typedef struct outputMessage { int recipientsCount; @@ -38,27 +41,34 @@ typedef struct outputMessage playerInfo ** recipients; } outputMessage; +// Contains the relevant parameters for the outputThreadLoop function: +typedef struct outputThreadParameters +{ + queue * outputQueue; + gnutls_session_t * tlssessions; + playerInfo * connectedPlayers; +} outputThreadParameters; + +// ======================== +// -=[ Functions ]=-: +// ======================== + +// Sends a message to a given TLS session, wraps the calls to gnutls_write: +int messageSend(gnutls_session_t receivingSession, userMessage * messageToSend); + +// Receives a message from a given TLS session, wraps the calls to gnutls_read: +int messageReceive(gnutls_session_t receiveFromSession, userMessage * receiveToMessage); + // Create a targetedOutput message to be delivered to the players pointed to in recipients: outputMessage * createTargetedOutputMessage(userMessage * messageToQueue, playerInfo ** recipients, int recipientCount); -// ================== -// -=[Input Queue]=-: -// ================== -typedef struct inputMessage inputMessage; -typedef struct inputMessage -{ - playerInfo * sender; - userMessage * content; -} inputMessage; +// A function for the output thread, which sends queued messages: +void * outputThreadHandler(void * parameters); -// ======================= -// -=[Input Sanitation]=-: -// ======================= - -// Sanatize user input to ensure it's okay to send to the server: +// Sanatize user input to ensure it's okay to process: void userInputSanatize(char * inputString, int length); -// Sanatize user names so they display correctly; +// Sanatize user names so they display correctly: void userNameSanatize(char * inputString, int length); #endif diff --git a/src/queue.c b/src/queue.c index 22fc1bf..d137525 100644 --- a/src/queue.c +++ b/src/queue.c @@ -1,5 +1,6 @@ // queue.c: Implements the queue data type and associated functions for SilverMUD. // Barry Kane, 2022 +#include #include "queue.h" // Allocates and instantiates a queue: @@ -13,6 +14,10 @@ queue * createQueue(void) newQueue->front = NULL; newQueue->back = NULL; + // Create the threading constructs: + newQueue->mutex = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER; + newQueue->condition = (pthread_cond_t)PTHREAD_COND_INITIALIZER; + // Return the pointer to the new queue: return newQueue; } @@ -27,7 +32,10 @@ void destroyQueue(queue ** queue) } // Deallocate the queue: - free(*queue); + free(*queue); + + // Point the queue pointer to NULL; + *queue = NULL; } // Returns the data at the front of the given queue: @@ -41,7 +49,7 @@ void popQueue(queue * queue) { // Check if the queue is locked, and wait: while (queue->lock); - + // Lock the queue: queue->lock = true; @@ -203,4 +211,7 @@ void pushQueue(queue * queue, void * data, queueDataType type) // Unlock the queue: queue->lock = false; + + // Flag that the queue was modified: + pthread_cond_broadcast(&queue->condition); } diff --git a/src/queue.h b/src/queue.h index 6bc6afe..c2691df 100644 --- a/src/queue.h +++ b/src/queue.h @@ -9,6 +9,9 @@ // -=[ Data Structures ]=-: // ======================== +// Let the compiler know there will be structs defined elsewhere: +typedef struct queue queue; + typedef enum queueDataType { EVENT, @@ -38,6 +41,8 @@ typedef struct queue size_t itemCount; queueNode * front; queueNode * back; + pthread_mutex_t mutex; + pthread_cond_t condition; } queue; // ======================== diff --git a/src/server/SilverMUDServer.c b/src/server/SilverMUDServer.c index adf6a60..fe63799 100644 --- a/src/server/SilverMUDServer.c +++ b/src/server/SilverMUDServer.c @@ -40,7 +40,7 @@ int main(int argc, char ** argv) int socketFileDesc, connectionFileDesc, length, clientsAmount, socketCheck, activityCheck, returnVal; fd_set connectedClients; - pthread_t gameLogicThread; + pthread_t gameLogicThread, outputThread; int clientSockets[PLAYERCOUNT]; userMessage sendBuffer, receiveBuffer; playerInfo connectedPlayers[PLAYERCOUNT]; @@ -54,11 +54,11 @@ int main(int argc, char ** argv) { switch(currentopt) { - case 'd': - { - delay = atoi(optarg); - break; - } + case 'd': + { + delay = atoi(optarg); + break; + } } } @@ -146,7 +146,6 @@ int main(int argc, char ** argv) slowPrint("\tSocket Creation is:\t\033[32;40mGREEN.\033[0m\n", delay); } - // bzero(&serverAddress, sizeof(serverAddress)); // Assign IP and port: @@ -207,11 +206,19 @@ int main(int argc, char ** argv) gameLogicThreadParameters->outputQueue = outputQueue; gameLogicThreadParameters->inputQueue = inputQueue; gameLogicThreadParameters->areaList = areas; - pthread_create(&gameLogicThread, NULL, &gameLogicLoop, gameLogicThreadParameters); + pthread_create(&gameLogicThread, NULL, &gameLogicHandler, gameLogicThreadParameters); slowPrint("\tEvent Thread is:\t\033[32;40mGREEN.\033[0m\n", delay); - slowPrint("=====\n", delay); - struct timeval timeout = {0, 500}; + + // Prepare the output queue thread: + outputThreadParameters * outputParameters = malloc(sizeof(outputThreadParameters)); + outputParameters->outputQueue = outputQueue; + outputParameters->tlssessions = tlssessions; + outputParameters->connectedPlayers = connectedPlayers; + pthread_create(&outputThread, NULL, &outputThreadHandler, outputParameters); + + slowPrint("\tOutput Thread is:\t\033[32;40mGREEN.\033[0m\n", delay); + slowPrint("=====\n", delay); while(true) { @@ -239,7 +246,7 @@ int main(int argc, char ** argv) } // See if a connection is ready to be interacted with: - activityCheck = select((clientsAmount + 1), &connectedClients, NULL, NULL, &timeout); + activityCheck = select((clientsAmount + 1), &connectedClients, NULL, NULL, NULL); // Check if select() worked: if ((activityCheck < 0) && (errno != EINTR)) @@ -286,7 +293,6 @@ int main(int argc, char ** argv) // Push the new message onto the queue: pushQueue(inputQueue, newMessage, INPUT_MESSAGE); - break; } } @@ -340,52 +346,8 @@ int main(int argc, char ** argv) } } } - - // Run through the output queue and send all unused messages: - while(outputQueue->itemCount != 0) - { - // Wait until the queue unlocks: - while(outputQueue->lock); - - // Lock the queue: - outputQueue->lock = true; - - // Get a message off the queue: - outputMessage * message = peekQueue(outputQueue)->data.outputMessage; - - // Unlock the queue: - outputQueue->lock = false; - - // If the first target is set to NULL, it's intended for all connected: - if(message->recipientsCount == 0) - { - for (int index = 0; index < PLAYERCOUNT; index++) - { - messageSend(tlssessions[index], message->content); - } - } - // Otherwise, send it only to the targeted players: - else - { - int targetIndex = 0; - for(int index = 0; index < PLAYERCOUNT; index++) - { - if(targetIndex == message->recipientsCount) - { - break; - } - if(&connectedPlayers[index] == message->recipients[targetIndex]) - { - targetIndex++; - messageSend(tlssessions[index], message->content); - } - } - } - // Remove the output message from the queue: - popQueue(outputQueue); - } } pthread_cancel(gameLogicThread); + pthread_cancel(outputThread); exit(EXIT_SUCCESS); } -