summaryrefslogtreecommitdiff
path: root/src/netlem_ds.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/netlem_ds.c')
-rw-r--r--src/netlem_ds.c501
1 files changed, 501 insertions, 0 deletions
diff --git a/src/netlem_ds.c b/src/netlem_ds.c
new file mode 100644
index 0000000..507b4fb
--- /dev/null
+++ b/src/netlem_ds.c
@@ -0,0 +1,501 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <signal.h>
+
+#include "SDL/SDL.h"
+#include "SDL/SDL_net.h"
+#include "SDL/SDL_thread.h"
+
+#include "game.h"
+#include "netlem_ds.h"
+#include "events.h"
+#include "utils.h"
+
+//struct _timerArgs_t { int *numClients; client_t *clients[]; }; // Comprendre pq ça chie ça
+struct _timerArgs_t { int *numClients; client_t **clients; };
+
+game_t evilGlobalGameVariable = { gameNew, 0, 0, NULL };
+int end=0;
+
+void signals(int signum);
+Uint32 timeSyncInsertNullEventsIfNeeded(Uint32 interval, void *args);
+TCPsocket initServerSocket(int port, SDLNet_SocketSet *sockSetServer);
+int acceptClient(TCPsocket sockServer, int *numThreads, client_t *clients[], SDL_Thread *threads[] );
+int serveClient(void *args);
+int sendEvents(void *args);
+int checkIfAllClientAre(enum clientState state, game_t *game);
+int startGame(game_t *game);
+
+int main(int argc, char *argv[]) {
+
+ int numThreads=0, result;
+ struct _timerArgs_t timerArgs;
+
+ char logMsg[128];
+ SDL_Thread *threads[MAX_THREADS];
+ client_t *clients[MAX_THREADS];
+
+ TCPsocket sockServer;
+ SDLNet_SocketSet sockSetServer;
+ SDL_TimerID timerTimeSync;
+
+ //TODO : dégager ce bidule temporaire
+ evilGlobalGameVariable.clients=clients;
+
+ openLog(NULL);
+
+ logs(LOG_INFO, "Server initialization in progress...");
+
+ result = SDL_Init(SDL_INIT_TIMER);
+ if ( result != 0 ) {
+ logs2(LOG_ERROR, "main(), SDL_Init()", SDL_GetError());
+ return 1;
+ }
+ atexit(SDL_Quit);
+ signal(2,signals);
+
+
+ // Ecoute sur port principal pour recevoir les clients
+ sockServer=initServerSocket(DEFAULT_LISTEN_PORT, &sockSetServer);
+ if (sockServer == NULL ) {
+ return 2;
+ }
+
+ // Mise en place du timer pour executer périodiquement la fonction server-side pour la synchro du temps
+ timerArgs.numClients=&numThreads;
+ timerArgs.clients=clients;
+ timerTimeSync = SDL_AddTimer(1000, timeSyncInsertNullEventsIfNeeded, &timerArgs);
+ if ( timerTimeSync == NULL ) {
+ logs2(LOG_ERROR, "main(), SDL_AddTimer()", SDL_GetError());
+ return 3;
+ }
+
+ logs(LOG_INFO, "Server ready to accept clients");
+ // Acceptation des clients qui tentent de se connecter
+ while(!end) {
+ SDLNet_CheckSockets(sockSetServer, -1); // Appel bloquant jusqu'à activité
+ acceptClient(sockServer, &numThreads, clients, threads);
+ }
+
+ logs(LOG_INFO, "Server is shuting down...");
+
+ // Libération de toutes les ressources
+ SDLNet_TCP_Close(sockServer);
+ SDLNet_FreeSocketSet(sockSetServer);
+ SDL_RemoveTimer(timerTimeSync);
+
+ // Attente de terminaison des autres threads
+ logs(LOG_DEBUG, "Waiting all remaining threads\n");
+ while(numThreads>=0) {
+ int status;
+ SDL_WaitThread(threads[numThreads-1], &status);
+ --numThreads;
+ sprintf(logMsg, "Thread %i terminated with code %i", numThreads,status);
+ logs(LOG_DEBUG, logMsg);
+ }
+
+
+ logs(LOG_INFO, "Server down");
+
+ closeLog();
+
+ return 0;
+}
+
+void signals(int signum) {
+ static int force=0;
+ char buf[128];
+ sprintf(buf, "Caught signal %i", signum);
+ logs(LOG_WARN, buf);
+
+ if(!force) {
+ end=1;
+ force=1;
+ logs(LOG_WARN, "Trying to stop smoothly...");
+ } else {
+ logs(LOG_WARN, "Emergency stop");
+ exit(1);
+ }
+}
+
+Uint32 timeSyncInsertNullEventsIfNeeded(Uint32 interval, void *args) {
+ const event_t evTimeSyncModel = {0,0,0,eTimeSync, 0,0, NULL,NULL};
+ static int inProgress=0;
+
+ game_t *game=NULL;
+ client_t *cli=NULL;
+ event_t *evTimeSync=NULL;
+ int i;
+
+// char buf[128];
+
+ struct _timerArgs_t *a=(struct _timerArgs_t *)args;
+
+// logs(LOG_DEBUG, "timeSyncInsertNullEventsIfNeeded() : begin");
+
+ if (inProgress != 0) {
+ logs(LOG_WARN, "timeSyncInsertNullEventsIfNeeded() takes too long !");
+ return 200;
+ }
+ inProgress=1;
+
+// sprintf(buf, "*(a->numClients)==%i", *(a->numClients));
+// logs2(LOG_DEBUG, "timeSyncInsertNullEventsIfNeeded()", buf);
+
+ //TODO : be _globally_ thread-safe for all the loop
+ for(i=0 ; i < *(a->numClients) ; ++i) {
+ cli=a->clients[i];
+ game=cli->game;
+
+// sprintf(buf, "game->state==%i, SDL_GetTicks()==%i, cli->lastEventSendTime_ms==%i", game->state, SDL_GetTicks(), cli->lastEventSendTime_ms);
+// logs2(LOG_DEBUG, "timeSyncInsertNullEventsIfNeeded()", buf);
+
+ if ( game->state == gameRunning &&
+ ( (SDL_GetTicks() - cli->lastEventSendTime_ms) > (0.9 * MAX_EVENT_INTERVAL_MS) )
+ ) {
+ evTimeSync=malloc(sizeof(event_t));
+ if (evTimeSync==NULL) {
+ logp(LOG_ERROR, "timeSyncInsertNullEventsIfNeeded(), malloc()");
+ return 0;
+ }
+ memcpy(evTimeSync,&evTimeSyncModel,sizeof(event_t));
+ eventListAdd((&cli->events), evTimeSync);
+ SDL_SemPost(cli->semEventAvailable); // FIXME Code retour
+ }
+ }
+
+// logs(LOG_DEBUG, "timeSyncInsertNullEventsIfNeeded() : end");
+
+ inProgress=0;
+
+ return 1000; // TODO : optimiser la régularité avec la variable interval
+}
+
+TCPsocket initServerSocket(int port, SDLNet_SocketSet *sockSetServer) {
+ int result;
+ TCPsocket sockServer;
+ IPaddress ip;
+
+ // Crée un socket TCP en écoute sur le port passé en paramètre bindé en 0.0.0.0
+ if(SDLNet_ResolveHost(&ip,NULL,port)==-1) {
+ logs2(LOG_ERROR, "initServerSocket(), SDLNet_ResolveHost()", SDLNet_GetError());
+ return NULL;
+ }
+
+ sockServer=SDLNet_TCP_Open(&ip);
+ if(!sockServer) {
+ logs2(LOG_ERROR, "initServerSocket(), SDLNet_TCP_Open()", SDLNet_GetError());
+ return NULL;
+ }
+
+ // Crée un socketSet qui permet essentiellement d'utiliser SDLNet_Check_Sockets()
+ if (sockSetServer != NULL) {
+ *sockSetServer = SDLNet_AllocSocketSet(1);
+ if ( *sockSetServer < 0 ) {
+ logs2(LOG_ERROR, "initServerSocket(), SDLNet_SocketSet()", SDLNet_GetError());
+ return NULL;
+ }
+
+ result = SDLNet_TCP_AddSocket(*sockSetServer,sockServer);
+ if ( result < 0 ) {
+ logs2(LOG_ERROR, "initServerSocket(), SDLNet_TCP_AddSocket()", SDLNet_GetError());
+ return NULL;
+ }
+ }
+
+ return sockServer;
+}
+
+int acceptClient(TCPsocket sockServer, int *numThreads, client_t *clients[], SDL_Thread *threads[] ) {
+ //TODO : faire une vraie gestion d'un pool de threads et de clients
+ TCPsocket sockClient;
+ SDL_Thread *threadClient=NULL;
+ client_t *cli=NULL;
+
+ sockClient=SDLNet_TCP_Accept(sockServer); // Appel non bloquant (contrairement à accept() en unix)
+ if(!sockClient) {
+ logs2(LOG_WARN, "acceptClient(), SDLNet_TCP_Accept()", SDLNet_GetError());
+ return 1;
+ }
+
+ if(*numThreads >= MAX_THREADS) {
+ logs2(LOG_WARN, "acceptClient()", "Too many threads");
+ SDLNet_TCP_Close(sockClient);
+ return 2;
+ }
+
+ //Préparation de la structure de données pour stocker l'état du nouveau client
+ cli=malloc(sizeof(client_t));
+ if( cli==NULL ) {
+ logp(LOG_ERROR, "acceptClient()), malloc()");
+ SDLNet_TCP_Close(sockClient);
+ return 3;
+ }
+
+ cli->numClient=*numThreads;
+ cli->state=clientNew;
+ cli->sockClient = sockClient;
+ eventListInit(&cli->events);
+ cli->semEventAvailable = SDL_CreateSemaphore(0); //FIXME : test retour
+ cli->semGameStart = SDL_CreateSemaphore(0);
+ cli->lastEventSendTime_ms=0;
+ cli->game=NULL;
+ cli->lems=NULL;
+
+ clients[(*numThreads)]=cli;
+ //TODO : Dégager ce truc temporaire
+ evilGlobalGameVariable.clientCount=(*numThreads)+1;
+
+ //Préparation du thread correspondant à ce nouveau client (modèle 1 client => 1 thread)
+ threadClient = SDL_CreateThread(serveClient, (void *)cli);
+ if(!threadClient) {
+ logs2(LOG_ERROR, "acceptClient(), SDL_CreateThread()", SDL_GetError());
+ SDLNet_TCP_Close(sockClient);
+ SDL_DestroySemaphore(cli->semEventAvailable);
+ SDL_DestroySemaphore(cli->semGameStart);
+ eventListFree(&(cli->events));
+ free(cli);
+ return 5;
+ }
+
+ //Inscrption dans le pool de clients et de threads
+ threads[(*numThreads)]=threadClient;
+ ++(*numThreads);
+
+
+ return 0;
+}
+
+// Fonction exécutée par le thread associé à un client
+int serveClient(void *args) {
+ int i, result;
+ char logMsg[128];
+ const char *hostTmp;
+ char host[128];
+ IPaddress *ipRemote=NULL;
+ SDL_Thread *threadSendEvents=NULL;
+ event_t e;
+
+ client_t *otherClient=NULL;
+ client_t *c=(client_t *) args;
+
+
+ // Récupération d'infos à propos du client (au sens réseau) : nom d'hôte et port distant
+ ipRemote=SDLNet_TCP_GetPeerAddress(c->sockClient);
+ if(!ipRemote) {
+ logs2(LOG_ERROR, "serveClient(), SDLNet_TCP_GetPeerAddress", SDLNet_GetError());
+ SDLNet_TCP_Close(c->sockClient);
+ free(args);
+ return 1;
+ }
+
+ hostTmp=SDLNet_ResolveIP(ipRemote);
+ if(!hostTmp) {
+ logs2(LOG_INFO, "serveClient(), SDLNet_ResolveIP", SDLNet_GetError());
+ } else {
+ strcpy(host, hostTmp); //FIXME possible buffer overflow
+ // Add a default "(Unknown)" value)
+ }
+
+ //TODO : faire ici toute la partie de l'automate côté serveur pour la phrase d'initialisation de la partie
+ c->game=&evilGlobalGameVariable;
+
+ // On attends le signal du client qu'il envoi lorsqu'il a chargé les ressources nécessaires a la partie
+ sprintf(logMsg, "Waiting client %i (%s)", c->numClient, host);
+ logs(LOG_INFO, logMsg);
+
+ //FIXME : utiliser reveiveEvent
+ result=SDLNet_TCP_Recv(c->sockClient,&e,sizeof(e));
+ if(result<=0) {
+ logs2(LOG_ERROR, "serveClient(), SDLNet_TCP_Recv()", SDLNet_GetError());
+ SDLNet_TCP_Close(c->sockClient);
+ free(args);
+ return 3;
+ }
+ if ( e.type != eReady) {
+ logs(LOG_WARN, "serveClient(), first event received is not eReady");
+ SDLNet_TCP_Close(c->sockClient);
+ free(args);
+ return 4;
+ }
+
+ // Création d'un nouveau thread par client pour l'envoi asynchrone des évènements
+ threadSendEvents = SDL_CreateThread(sendEvents, args);
+ if(!threadSendEvents) {
+ logs2(LOG_ERROR, "serveClient(), SDL_CreateThread()", SDL_GetError());
+ SDLNet_TCP_Close(c->sockClient);
+ free(args);
+ return 5;
+ }
+
+ sprintf(logMsg, "Client %i (%s) ready", c->numClient, host);
+ logs(LOG_INFO, logMsg);
+
+ // Si tous les (autres) clients de la partie sont prêts, on initie le début de la partie
+ c->state=clientReady;
+ if ( c->game->clientCount == 2 && checkIfAllClientAre(clientReady, c->game) == 0) {
+ result=startGame(c->game);
+ if (result!=0) {
+ logs(LOG_ERROR, "serveClient(), startGame() error");
+ return 5; //FIXME : 5bis !
+ }
+ }
+
+ // Ce thread se bloque jusqu'au démarrage de la partie (débloquage immédiat si on est rentré dans le if précédent)
+ logs(LOG_INFO, "Waiting for game start");
+ result=SDL_SemWait(c->semGameStart);
+ if (result!=0) {
+ logs2(LOG_ERROR, "serveClient(), SDL_SemWait()", SDL_GetError());
+ SDLNet_TCP_Close(c->sockClient);
+ free(args);
+ return 6;
+ }
+ logs(LOG_INFO, "Game Started !");
+
+ // Boucle principale de jeu pour chaque client du point de vue du serveur
+ while(c->game->state != gameEnded ) {
+ result=receiveEvent(c, &e);
+ if(result!=0) {
+ c->game->state = gameEnded;
+ continue;
+ }
+
+ switch(e.type) {
+ case eLemAction:
+ for( i=0 ; i < (c->game->clientCount); ++i ) {
+ otherClient=c->game->clients[i];
+ eventListAdd(&(otherClient->events), &e);
+ SDL_SemPost(otherClient->semEventAvailable); //FIXME code de retour
+ }
+ break;
+ default:
+ logs(LOG_ERROR, "serveClient(), Unknown event type");
+ }
+ }
+
+ // La partie est terminée, provoque et attends la terminaison du threadSendEvents
+ result=SDL_SemPost(c->semEventAvailable);
+ if(result!=0) {
+ logs(LOG_ERROR, "serveClient(), SDL_SemPost() error");
+ SDLNet_TCP_Close(c->sockClient);
+ free(args);
+ return 7;
+ }
+ SDL_WaitThread(threadSendEvents, &result);
+
+ logs2(LOG_INFO, "serveClient() ended for", (char *)host);
+
+ SDLNet_TCP_Close(c->sockClient);
+ free(args);
+
+ return 0;
+}
+
+// Fonction principale du thread qui envoi sur le réseau les event_t qui sont en file pour chaque client
+int sendEvents(void *args) {
+ char *buf=NULL/*, bufDbg[128]*/;
+ int i, result, eLen, msgLen;
+ client_t *c=(client_t *) args;
+ event_t *e;
+
+ // S'autodétruit si la partie est terminée
+ // Note : peut être bloqué sur SemWait, donc faire un V après avoir changé le game state
+ while(c->game->state != gameEnded ) {
+ //Attends qu'un ou des events soient disponibles
+ result=SDL_SemWait(c->semEventAvailable);
+ if(result!=0) {
+ logs(LOG_ERROR, "sendEvents(), SDL_SemWait() error");
+ return 1;
+ }
+
+// logs(LOG_DEBUG, "Event available !");
+
+ // Verrouile, enumère et sérialise dans buf tous les évènements de la file
+ eventListLock(&(c->events)); //FIXME : check return code
+ eLen=eventSerializedSize();
+ msgLen=eLen * eventListItemCount((&c->events));
+ buf=malloc(msgLen);
+ if ( buf==NULL ) {
+ logp(LOG_ERROR, "sendEvents(), malloc()");
+ return 2;
+ }
+
+ i=0;
+ while ((e=eventListPop(&(c->events))) != NULL) {
+ //Sending timestamp for time synchronization
+ e->serverTick=getGameCurrentTick(c->game->startTime_ms);
+ //Serialize messages and put them contiguous in buf
+ eventSerialize(e, buf+eLen*i++);
+ free(e);
+ }
+ eventListUnlock(&(c->events)); //FIXME : check return code
+
+ // Envoi ces évènements sérialisés sur le réseau
+ if (msgLen>0) {
+ result=SDLNet_TCP_Send(c->sockClient,buf,msgLen);
+ if(result<msgLen) {
+ logs2(LOG_WARN, "sendEvents(), SDLNet_TCP_Send() error", SDLNet_GetError());
+ return 3;
+ }
+ }
+ }
+
+ return 0;
+}
+
+// Retourne 0 si tous les clients d'une partie donnée sont dan sun état donné (1 sinon)
+inline int checkIfAllClientAre(enum clientState state, game_t *game) {
+ int i;
+ for(i=0 ; i < (game->clientCount) ; ++i ) {
+ if(game->clients[i]->state != state) { return 1; }
+ }
+ return 0;
+}
+
+int startGame(game_t *game) {
+ int i, result;
+ client_t *cli;
+
+ char buf[128];
+
+ // Date de début dans le futur pour laisser du temps pour converger
+ // à l'algorithme de synchronisation du temps
+ game->startTime_ms=SDL_GetTicks()+TIME_FOR_TIMESYNC_MS;
+
+ sprintf(buf, "game->startTime_ms==%i", game->startTime_ms);
+ logs(LOG_DEBUG, buf);
+
+ game->state = gameRunning;
+
+ // On débloque tous les threads des clients qui sont en attente
+ for(i=0 ; i < (game->clientCount) ; ++i) {
+ cli=game->clients[i];
+ result=SDL_SemPost(cli->semGameStart);
+ if (result!=0) {
+ logs2(LOG_ERROR, "startGame(), SDL_SemPost()", SDL_GetError());
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/*
+Machine a états complète à implémenter
+on lance le serveur, il se met en écoute et lance un thread / timer qui compte le temps (globalServerTick)
+un client demande une partie réseau, se connecte au serveur, donne un username
+le serveur le met dans un pool de client en attente et lui envoi la liste des clients en attente
+un second client demande une partie réseau, idem
+la liste des clients en attente est renvoyée au premier client (enfait à tous les clients)
+le client1 choisi le client2 en coequipier et envoi cette info au serveur
+le serveur transmet au client2 qui affiche la proposition
+le client2 accepte et renvoi cette acceptation au serveur (s'il refuse rien n'est transmis)
+le serveur voit que les 2 clients sont ok pour jouer ensemble. (si le client1 ne s'est pas barré ou a commencé ailleurs). Le serveur envoi un paquet à chacun : une demande de paramètres de jeu au premier (car il a initié) et une attente pour l'autre.
+Le client1 modifier des paramètres de jeu, les transmet au serveur qui les forwarde au client2 pour acceptation.
+Le client2 accepte les paramètres et envoi un paquet pour le ire au serveur.
+Le serveur envoi un paquet à chacun pour dire de charger la partie.
+Quand un client est (presque) prêt, il envoi un premier event NULL.
+Quand le serveur a un event null de chaque joueur, il lance la partie en ajoutant la partie dans la liste des parties actives, en positionnant la variable tickGameStart à la valeur ( globalServerTick + TIME_FOR_TIMESYNC_MS ).
+Les envois d'évèments commencent donc (un timer réveille une procédure qui checke la lastSentEventTick, si trop vieux, envoi d'un event NULL pour la synchro du temps)
+Le reste de la logique est déclanhée sur la réception d'évènement des clients (remplissage de liste d'évènement, tri et renvoi à tous...)
+*/
+