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 <gnutls/gnutls.h>
#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);

View File

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

View File

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

View File

@ -5,7 +5,10 @@
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <pthread.h>
#include <gnutls/gnutls.h>
#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';
}

View File

@ -4,33 +4,36 @@
#ifndef INPUTOUTPUT_H
#define INPUTOUTPUT_H
#include <ctype.h>
#include <stdbool.h>
#include <stdlib.h>
#include "constants.h"
#include "playerdata.h"
#include <stdbool.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
{
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

View File

@ -1,5 +1,6 @@
// queue.c: Implements the queue data type and associated functions for SilverMUD.
// Barry Kane, 2022
#include <pthread.h>
#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);
}

View File

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

View File

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