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.
This commit is contained in:
Barry Kane 2022-12-21 00:49:26 +00:00
parent 15d82f59ee
commit 6a653c75b9
8 changed files with 191 additions and 125 deletions

View File

@ -14,6 +14,7 @@
#include <sys/socket.h> #include <sys/socket.h>
#include <gnutls/gnutls.h> #include <gnutls/gnutls.h>
#include "../queue.h"
#include "../constants.h" #include "../constants.h"
#include "../playerdata.h" #include "../playerdata.h"
#include "../texteffects.h" #include "../texteffects.h"
@ -225,7 +226,7 @@ int main(int argc, char ** argv)
serverAddress.sin_port = htons(port); serverAddress.sin_port = htons(port);
// Connect the server and client sockets, Kronk: // 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); slowPrint("Connection with the Silverkin Industries Comm-Link Server Failed:\nPlease contact your service representative.\n", characterDelay);
exit(0); exit(0);

View File

@ -17,7 +17,7 @@
// ======================= // =======================
// Thread function which runs the main game loop, given the needed parameters: // Thread function which runs the main game loop, given the needed parameters:
void * gameLogicLoop(void * parameters) void * gameLogicHandler(void * parameters)
{ {
gameLogicParameters * threadParameters = parameters; gameLogicParameters * threadParameters = parameters;
inputMessage * currentInput = NULL; inputMessage * currentInput = NULL;
@ -25,11 +25,17 @@ void * gameLogicLoop(void * parameters)
while(true) while(true)
{ {
// Evaluate remaining commands: // Evaluate remaining commands:
if(commandQueue->currentLength != 0) while(commandQueue->currentLength != 0)
{ {
evaluateNextCommand(threadParameters, commandQueue); 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: // Check for new messages and pop them off the queue:
if(threadParameters->inputQueue->itemCount != 0) if(threadParameters->inputQueue->itemCount != 0)
{ {

View File

@ -8,35 +8,10 @@
#include "playerdata.h" #include "playerdata.h"
#include "inputoutput.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 commandEvent;
typedef struct commandEvent typedef struct commandEvent
{ {
@ -56,6 +31,32 @@ typedef struct commandQueue
commandEvent * front; commandEvent * front;
} commandQueue; } 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: // Create a commandQueue:
commandQueue * createCommandQueue(void); commandQueue * createCommandQueue(void);
@ -82,9 +83,6 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue);
// -=[ Gameplay Primitives ]=-: // -=[ Gameplay Primitives ]=-:
// ============================ // ============================
// Player movement:
int movePlayerToArea(playerInfo * player, char * requestedPath);
typedef enum outcome typedef enum outcome
{ {
CRITICAL_FAILURE, CRITICAL_FAILURE,
@ -94,6 +92,9 @@ typedef enum outcome
ERROR ERROR
} outcome; } outcome;
// Player movement:
int movePlayerToArea(playerInfo * player, char * requestedPath);
// Run a stat check: // Run a stat check:
outcome statCheck(playerInfo * player, int chance, coreStat statToCheck); outcome statCheck(playerInfo * player, int chance, coreStat statToCheck);

View File

@ -5,7 +5,10 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <stdbool.h> #include <stdbool.h>
#include <pthread.h>
#include <gnutls/gnutls.h> #include <gnutls/gnutls.h>
#include "queue.h"
#include "constants.h" #include "constants.h"
#include "playerdata.h" #include "playerdata.h"
#include "inputoutput.h" #include "inputoutput.h"
@ -46,6 +49,7 @@ int messageReceive(gnutls_session_t receiveFromSession, userMessage * receiveToM
return returnValue; return returnValue;
} }
// Allocate and initialize an outputMessage targeted to a variable amount of players:
outputMessage * createTargetedOutputMessage(userMessage * messageToQueue, playerInfo ** recipients, int recipientsCount) outputMessage * createTargetedOutputMessage(userMessage * messageToQueue, playerInfo ** recipients, int recipientsCount)
{ {
// Allocate a new output message: // Allocate a new output message:
@ -64,10 +68,72 @@ outputMessage * createTargetedOutputMessage(userMessage * messageToQueue, player
return newOutputMessage; 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) void userInputSanatize(char * inputString, int length)
{ {
for(int index = 0; index <= length; index++) for(int index = 0; index <= length; index++)
{ {
// If it's not a printable character, it has no buisness being here:
if(!isprint(inputString[index])) if(!isprint(inputString[index]))
{ {
inputString[index] = '\n'; inputString[index] = '\n';
@ -75,18 +141,22 @@ void userInputSanatize(char * inputString, int length)
break; break;
} }
} }
// Make sure it's null-terminated:
inputString[length - 1] = '\0'; inputString[length - 1] = '\0';
} }
// Sanatize user names so they display correctly;
void userNameSanatize(char * inputString, int length) void userNameSanatize(char * inputString, int length)
{ {
for(int index = 0; index <= length; index++) for(int index = 0; index <= length; index++)
{ {
// If it's not a printable character, it has no buisness being here:
if(!isprint(inputString[index])) if(!isprint(inputString[index]))
{ {
inputString[index] = '\0'; inputString[index] = '\0';
break; break;
} }
} }
// Make sure it's null-terminated:
inputString[length - 1] = '\0'; inputString[length - 1] = '\0';
} }

View File

@ -4,33 +4,36 @@
#ifndef INPUTOUTPUT_H #ifndef INPUTOUTPUT_H
#define INPUTOUTPUT_H #define INPUTOUTPUT_H
#include <ctype.h> #include <ctype.h>
#include <stdbool.h>
#include <stdlib.h> #include <stdlib.h>
#include "constants.h" #include <stdbool.h>
#include "playerdata.h"
#include <gnutls/gnutls.h> #include <gnutls/gnutls.h>
// 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 typedef struct userMessage
{ {
char senderName[32]; char senderName[32];
char messageContent[MAX]; char messageContent[MAX];
} userMessage; } userMessage;
// ================== // Contains a userMessage and a pointer to the playerInfo of the connection which sent it:
// -=[Message I/O]=-: typedef struct inputMessage
// ================== {
playerInfo * sender;
userMessage * content;
} inputMessage;
// Sends a message to a given TLS session, wraps the calls to gnutls_write: // Contains a userMessage and pointers to the playerInfo of the recipients and the number of them:
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;
typedef struct outputMessage typedef struct outputMessage
{ {
int recipientsCount; int recipientsCount;
@ -38,27 +41,34 @@ typedef struct outputMessage
playerInfo ** recipients; playerInfo ** recipients;
} outputMessage; } 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: // Create a targetedOutput message to be delivered to the players pointed to in recipients:
outputMessage * createTargetedOutputMessage(userMessage * messageToQueue, playerInfo ** recipients, int recipientCount); outputMessage * createTargetedOutputMessage(userMessage * messageToQueue, playerInfo ** recipients, int recipientCount);
// ================== // A function for the output thread, which sends queued messages:
// -=[Input Queue]=-: void * outputThreadHandler(void * parameters);
// ==================
typedef struct inputMessage inputMessage;
typedef struct inputMessage
{
playerInfo * sender;
userMessage * content;
} inputMessage;
// ======================= // Sanatize user input to ensure it's okay to process:
// -=[Input Sanitation]=-:
// =======================
// Sanatize user input to ensure it's okay to send to the server:
void userInputSanatize(char * inputString, int length); 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); void userNameSanatize(char * inputString, int length);
#endif #endif

View File

@ -1,5 +1,6 @@
// queue.c: Implements the queue data type and associated functions for SilverMUD. // queue.c: Implements the queue data type and associated functions for SilverMUD.
// Barry Kane, 2022 // Barry Kane, 2022
#include <pthread.h>
#include "queue.h" #include "queue.h"
// Allocates and instantiates a queue: // Allocates and instantiates a queue:
@ -13,6 +14,10 @@ queue * createQueue(void)
newQueue->front = NULL; newQueue->front = NULL;
newQueue->back = 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 the pointer to the new queue:
return newQueue; return newQueue;
} }
@ -27,7 +32,10 @@ void destroyQueue(queue ** queue)
} }
// Deallocate the 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: // 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: // Check if the queue is locked, and wait:
while (queue->lock); while (queue->lock);
// Lock the queue: // Lock the queue:
queue->lock = true; queue->lock = true;
@ -203,4 +211,7 @@ void pushQueue(queue * queue, void * data, queueDataType type)
// Unlock the queue: // Unlock the queue:
queue->lock = false; queue->lock = false;
// Flag that the queue was modified:
pthread_cond_broadcast(&queue->condition);
} }

View File

@ -9,6 +9,9 @@
// -=[ Data Structures ]=-: // -=[ Data Structures ]=-:
// ======================== // ========================
// Let the compiler know there will be structs defined elsewhere:
typedef struct queue queue;
typedef enum queueDataType typedef enum queueDataType
{ {
EVENT, EVENT,
@ -38,6 +41,8 @@ typedef struct queue
size_t itemCount; size_t itemCount;
queueNode * front; queueNode * front;
queueNode * back; queueNode * back;
pthread_mutex_t mutex;
pthread_cond_t condition;
} queue; } queue;
// ======================== // ========================

View File

@ -40,7 +40,7 @@ int main(int argc, char ** argv)
int socketFileDesc, connectionFileDesc, length, clientsAmount, int socketFileDesc, connectionFileDesc, length, clientsAmount,
socketCheck, activityCheck, returnVal; socketCheck, activityCheck, returnVal;
fd_set connectedClients; fd_set connectedClients;
pthread_t gameLogicThread; pthread_t gameLogicThread, outputThread;
int clientSockets[PLAYERCOUNT]; int clientSockets[PLAYERCOUNT];
userMessage sendBuffer, receiveBuffer; userMessage sendBuffer, receiveBuffer;
playerInfo connectedPlayers[PLAYERCOUNT]; playerInfo connectedPlayers[PLAYERCOUNT];
@ -54,11 +54,11 @@ int main(int argc, char ** argv)
{ {
switch(currentopt) switch(currentopt)
{ {
case 'd': case 'd':
{ {
delay = atoi(optarg); delay = atoi(optarg);
break; break;
} }
} }
} }
@ -146,7 +146,6 @@ int main(int argc, char ** argv)
slowPrint("\tSocket Creation is:\t\033[32;40mGREEN.\033[0m\n", delay); slowPrint("\tSocket Creation is:\t\033[32;40mGREEN.\033[0m\n", delay);
} }
//
bzero(&serverAddress, sizeof(serverAddress)); bzero(&serverAddress, sizeof(serverAddress));
// Assign IP and port: // Assign IP and port:
@ -207,11 +206,19 @@ int main(int argc, char ** argv)
gameLogicThreadParameters->outputQueue = outputQueue; gameLogicThreadParameters->outputQueue = outputQueue;
gameLogicThreadParameters->inputQueue = inputQueue; gameLogicThreadParameters->inputQueue = inputQueue;
gameLogicThreadParameters->areaList = areas; 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("\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) while(true)
{ {
@ -239,7 +246,7 @@ int main(int argc, char ** argv)
} }
// See if a connection is ready to be interacted with: // 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: // Check if select() worked:
if ((activityCheck < 0) && (errno != EINTR)) if ((activityCheck < 0) && (errno != EINTR))
@ -286,7 +293,6 @@ int main(int argc, char ** argv)
// Push the new message onto the queue: // Push the new message onto the queue:
pushQueue(inputQueue, newMessage, INPUT_MESSAGE); pushQueue(inputQueue, newMessage, INPUT_MESSAGE);
break; 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(gameLogicThread);
pthread_cancel(outputThread);
exit(EXIT_SUCCESS); exit(EXIT_SUCCESS);
} }