diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/CMakeLists.txt | 26 | ||||
-rw-r--r-- | src/events.c | 149 | ||||
-rw-r--r-- | src/events.h | 25 | ||||
-rw-r--r-- | src/game.c | 153 | ||||
-rw-r--r-- | src/game.h | 85 | ||||
-rw-r--r-- | src/netlem.c | 197 | ||||
-rw-r--r-- | src/netlem.h | 8 | ||||
-rw-r--r-- | src/netlem.h.in | 8 | ||||
-rw-r--r-- | src/netlem_ds.c | 501 | ||||
-rw-r--r-- | src/netlem_ds.h | 8 | ||||
-rw-r--r-- | src/test/test.c | 28 | ||||
-rw-r--r-- | src/utils.c | 41 | ||||
-rw-r--r-- | src/utils.h | 15 |
13 files changed, 1244 insertions, 0 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..02bf57c --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required (VERSION 2.6) +project (NetLemmings) + +# The version number. +set (NetLemmings_VERSION_MAJOR 0) +set (NetLemmings_VERSION_MINOR 1) + +# Check if all necessary libs are here +find_package(SDL REQUIRED) +find_package(SDL_net REQUIRED) + +# configure a header file to pass some of the CMake settings +# to the source code +configure_file ( + "${PROJECT_SOURCE_DIR}/netlem.h.in" + "${PROJECT_BINARY_DIR}/netlem.h" +) + +# add the binary tree to the search path for include files +# so that we will find NetLemmingsConfig.h +include_directories("${PROJECT_BINARY_DIR}") + +add_executable(netlem netlem.c) +add_definitions(-Wall -Wextra -pedantic -Werror -std=c99) +target_link_libraries(netlem SDL SDL_net) + diff --git a/src/events.c b/src/events.c new file mode 100644 index 0000000..95be9e9 --- /dev/null +++ b/src/events.c @@ -0,0 +1,149 @@ +#include <stdlib.h> // calloc +#include <string.h> //memcpy + +#include "events.h" + +inline int eventSerializedSize() { + //FIXME : A rendre portable + return sizeof(event_t); +} + +void eventSerialize(const event_t *e, char *buf) { + //FIXME : A rendre portable + memcpy(buf,e,sizeof(event_t)); +} + +int eventUnserialize(event_t *e, char *buf) { + //FIXME : A rendre portable et vérifier les champs + memcpy(e,buf,sizeof(event_t)); + return 0; +} + +void eventListInit(eventList_t *elist) { + elist->first=NULL; + elist->last=NULL; + elist->lock= SDL_CreateMutex(); +} + +void eventListFree(eventList_t *elist) { + event_t *next; + event_t *curr = elist->first; + while(curr!=NULL) { + next = curr->next; + free(curr); + curr=next; + } +} +int eventListLock(eventList_t *elist) { + return SDL_mutexP(elist->lock); +} + +int eventListUnlock(eventList_t *elist) { + return SDL_mutexV(elist->lock); +} + +void eventListAdd(eventList_t *elist, event_t *event) { + if (elist->first==NULL) { + elist->first=elist->last=event; + event->next=event->prev=NULL; + } else { + elist->last->next = event; + event->prev = elist->last; + event->next = NULL; + elist->last = event; + } +} + +event_t * eventListPop(eventList_t *elist) { + event_t *res = elist->first; + + if (res!=NULL) { + elist->first=res->next; + } + + return res; +} + +int eventListItemCount(eventList_t *elist) { + int i=0; + event_t *cur = elist->first; + while(cur!=NULL) { ++i, cur=cur->next; } + return i; +} + +// Private methods below (not present in .h file) +void _eventListCopyItemValues(event_t *dst, const event_t *src) { + dst->playerId = src->playerId; + dst->eventTick = src->eventTick; + dst->serverTick = src->serverTick; + dst->type = src->type; + dst->lemId = src->lemId; + dst->newRole = src->newRole; +} + + +void _eventList_sort(event_t *left, event_t *right) { + + event_t *start, *curr, copy; + + // If the left and right pointers are the same, then return + if (left == right) return; + + // Set the Start and the Current item pointers + start = left; + curr = start->next; + + // Loop until we get to the right + while (1) + { + // If the start item is less then the right + if ( + (start->eventTick < curr->eventTick) || + ( + (start->eventTick == curr->eventTick) && + (start->playerId < curr->playerId) + ) + ) + { + // Swap the items TODO : change references + _eventListCopyItemValues(©, curr); + _eventListCopyItemValues(curr, start); + _eventListCopyItemValues(start, ©); + } + + // Check if we have reached the right end + if (curr == right) break; + + // Move to the next item in the list + curr = curr->next; + } + + // Swap the First and Current items + _eventListCopyItemValues(©, left); + _eventListCopyItemValues(left, curr); + _eventListCopyItemValues(curr, ©); + + // Save this Current item + const event_t *oldCurr = curr; + + // Check if we need to sort the left hand size of the Current point + curr = curr->prev; + if (curr != NULL) + { + if ((left->prev != curr) && (curr->next != left)) + _eventList_sort(left, curr); + } + + // Check if we need to sort the right hand size of the Current point + curr = oldCurr->next; + if (curr != NULL) + { + if ((curr->prev != right) && (right->next != curr)) + _eventList_sort(curr, right); + } +} + +void eventList_sort(eventList_t *elist) { + _eventList_sort(elist->first,elist->last); +} + diff --git a/src/events.h b/src/events.h new file mode 100644 index 0000000..7f9ca50 --- /dev/null +++ b/src/events.h @@ -0,0 +1,25 @@ +#ifndef EVENTS_H +#define EVENTS_H + +#include "SDL/SDL.h" +#include "game.h" +#include "utils.h" + +int eventSerializedSize(); +void eventSerialize(const event_t *e, char *buf); +int eventUnserialize(event_t *e, char *buf); + +void eventListInit(eventList_t *elist); +void eventListFree(eventList_t *elist); + +int eventListLock(eventList_t *elist); +int eventListUnlock(eventList_t *elist); + +void eventListAdd(eventList_t *elist, event_t *event); +event_t * eventListPop(eventList_t *elist); + +int eventListItemCount(eventList_t *elist); + +void eventListSort(eventList_t *elist); + +#endif //EVENTS_H diff --git a/src/game.c b/src/game.c new file mode 100644 index 0000000..8015b89 --- /dev/null +++ b/src/game.c @@ -0,0 +1,153 @@ +#include "game.h" +#include "utils.h" +#include "events.h" + +#include <stdlib.h> // calloc +#include <string.h> // memset + +inline tick_t getGameCurrentTick(Uint32 startTime_ms) { +/* char buf[128]; + sprintf(buf, "SDL_GetTicks()==%i, startTime_ms==%i, TICK_DURATION_MS==%i", SDL_GetTicks(), startTime_ms, TICK_DURATION_MS); + logs(LOG_DEBUG, buf);*/ + tick_t t = SDL_GetTicks()-startTime_ms; //FIXME Débodements possibles ?!? + return t/TICK_DURATION_MS; +} + +int initNetGgame(netGame_t *ng, id_t lemCount) { + memset(ng,0,sizeof(netGame_t)); + + eventListInit(&(ng->elist)); + + ng->lemCount=lemCount; + + ng->local.lems = calloc(lemCount,sizeof(stateLem_t)); + if (ng->local.lems==NULL) { logp(LOG_ERROR, "initNetGame(), calloc()"); return 3; } + + ng->net.lems = calloc(lemCount,sizeof(stateLem_t)); + if (ng->net.lems==NULL) { logp(LOG_ERROR, "initNetGame(), calloc()"); return 4; } + + return 0; +} + +void freeNetGame(netGame_t *ng) { + eventListFree(&(ng->elist)); + free(ng->local.lems); + free(ng->net.lems); +} + +// Serialize en envoi une trame d'évènement sur le réseau +int sendEvent(TCPsocket sockDest, const event_t *e) { + int msgLen, result; + char *buf; + + msgLen=eventSerializedSize(); + buf=malloc(msgLen); + if ( buf==NULL ) { + logp(LOG_ERROR, "sendEvent(), malloc()"); + return 1; + } + eventSerialize(e, buf); + + result=SDLNet_TCP_Send(sockDest,buf,msgLen); + free(buf); + if(result<msgLen) { + logs2(LOG_WARN, "sendEvent(), SDLNet_TCP_Send() error", SDLNet_GetError()); + return 2; + } + + return 0; +} + +// Attends de recevoir une trame d'évènement du réseau pour un client donné et l'envoi dans la file à traiter +int receiveEvent(client_t *c, event_t *e) { + int result, bufLen; + char *buf; + + bufLen=eventSerializedSize(); + buf=malloc(bufLen); + if (buf==NULL) { + logp(LOG_ERROR, "receiveEvent(), malloc()"); + return 1; + } + + // Réception de l'évènement + result=SDLNet_TCP_Recv(c->sockClient,buf,bufLen); // Appel bloquant + if(result<=0) { + logs(LOG_ERROR, "receiveEvent(), SDLNet_TCP_Recv() error"); + free(buf); + return 2; + } + + // Désérialisation de l'évènement + result=eventUnserialize(e,buf); + if ( result!=0 ) { + logs(LOG_WARN, "receiveEvent(), eventUnserialize() error"); + free(buf); + return 4; + } + + free(buf); + return 0; +} + +int init() { + int result; + /*TODO : Avoir un struct de paramètre de config (taille écran) et un struct avec tous les éléments SDL manipulés dans le jeu + + //Initialisation des sous-systèmes de SDL + result = SDL_Init(SDL_INIT_AUDIO | SDL_INIT_VIDEO ); + if ( result != 0 ) { + logs2(LOG_ERROR, "init(), SDL_Init()", SDL_GetError()); + return 1; + } + atexit(SDL_Quit); + + //Mise en place de l'écran + screen = SDL_SetVideoMode( SCREEN_WIDTH, SCREEN_HEIGHT, 24, SDL_HWSURFACE | SDL_DOUBLEBUF ); + if( screen == NULL ) { + logs2(LOG_ERROR, "init(), SDL_SetVideoMode()", SDL_GetError()); + return 2; + } + + //Titre de la fenêtre SDL + SDL_WM_SetCaption(WIN_CAPTION, NULL); + + //Désactiver le pointeur de la souris + SDL_ShowCursor(0); + + // Allocation et initialisation des différents calques d'affichage et de collision + pTerrain = SDL_CreateRGBSurface(SDL_HWSURFACE, LEVEL_WIDTH, LEVEL_HEIGHT, SCREEN_BPP,0,0,0,0); + if( pTerrain == NULL ) { + logs2(LOG_ERROR, "init(), SDL_CreateRGBSurface()", SDL_GetError()); + return 3; + } + + pSpr_Lem = SDL_CreateRGBSurface(SDL_HWSURFACE, LEVEL_WIDTH, LEVEL_HEIGHT,SCREEN_BPP,0,0,0,0); + if( pSpr_Lem == NULL ) { + logs2(LOG_ERROR, "init(), SDL_CreateRGBSurface()", SDL_GetError()); + return 3; + } + + pStencil = SDL_CreateRGBSurface(SDL_SWSURFACE, LEVEL_WIDTH, LEVEL_HEIGHT, SCREEN_BPP,0,0,0,0); + if( pStencil == NULL ) { + logs2(LOG_ERROR, "init(), SDL_CreateRGBSurface()", SDL_GetError()); + return 3; + } + + pStencilFixe = SDL_CreateRGBSurface(SDL_SWSURFACE, LEVEL_WIDTH, LEVEL_HEIGHT, SCREEN_BPP,0,0,0,0); + if( pStencil == NULL ) { + logs2(LOG_ERROR, "init(), SDL_CreateRGBSurface()", SDL_GetError()); + return 3; + } +*/ + //Si tout s'est bien passé + return 0; +} + +void play(int tick) { + if (tick%100==0) { printf("tick==%i\n",tick); } + + //TODO : boucle de jeu principale ici (maj état de jeu) + SDL_Delay(rand()%8); +} + diff --git a/src/game.h b/src/game.h new file mode 100644 index 0000000..a8b68c4 --- /dev/null +++ b/src/game.h @@ -0,0 +1,85 @@ +#ifndef GAME_H +#define GAME_H + +#define DEFAULT_LISTEN_PORT 9999 +#define TICK_DURATION_MS 30 + +#include "SDL/SDL_net.h" + +typedef int tick_t; +typedef unsigned int id_t; +typedef struct { + unsigned int x,y; +} pos_t; + +enum eventType_t { eReady, eTimeSync, eLemAction }; + +typedef struct { + unsigned int dead : 1; + unsigned int side : 1; + unsigned int flotter : 1; + unsigned int climber : 1; + unsigned int remainCount: 4; + unsigned int currRole: 4; + unsigned int animState: 4; + pos_t pos; +} stateLem_t; + +enum gameState { gameNew, gameRunning, gameEnded }; + +typedef struct { + enum gameState state; + int startTime_ms; + int clientCount; + struct _client_t **clients; +} game_t; + +typedef struct _event_t { + id_t playerId; + tick_t eventTick; + tick_t serverTick; + + enum eventType_t type; + + id_t lemId; + unsigned int newRole: 4; + + struct _event_t *prev, *next; +} event_t; + +typedef struct { + event_t *first, *last; + SDL_mutex *lock; +} eventList_t; + +enum clientState { clientNew, clientReady }; + +typedef struct _client_t { + int numClient; + enum clientState state; + TCPsocket sockClient; + eventList_t events; + SDL_sem *semEventAvailable; + SDL_sem *semGameStart; + int lastEventSendTime_ms; + game_t *game; + stateLem_t *lems; +} client_t; + +typedef struct { + id_t lemCount; + eventList_t elist; + client_t local, net; //TODO : reprendre un peu, bcp de champs dupliqué pour rien +} netGame_t; + + +tick_t getGameCurrentTick(Uint32 startTime_ms); +int initNetGame(netGame_t *ng, id_t lemCount); +void freeNetGame(netGame_t *ng); +int sendEvent(TCPsocket sockDest, const event_t *e); +int receiveEvent(client_t *c, event_t *e); + +int init(); +void play(int tick); + +#endif //GAME_H diff --git a/src/netlem.c b/src/netlem.c new file mode 100644 index 0000000..e9a4304 --- /dev/null +++ b/src/netlem.c @@ -0,0 +1,197 @@ +#include <stdlib.h> +#include <stdio.h> +#include "SDL/SDL.h" +#include "SDL/SDL_net.h" +#include "SDL/SDL_thread.h" + +#include "netlem.h" +#include "game.h" +#include "events.h" +#include "utils.h" + +int tick=0; +SDL_sem *semGameStart; + +struct _processNetworkEvents_args { int *end; int *drift_ms; client_t *client; }; + +void processLocalEvents(); +int processNetworkEvents(void *a); +int updateGraphics(); + +int main(int argc, char **argv) { + const event_t evReady = {0,0,0,eReady,0,0,NULL,NULL}; + int end=0, drift_ms=0, timeBefore_ms, delay_ms, result, status; + client_t client; + struct _processNetworkEvents_args args; + char logMsg[128]; + + // connect to localhost at port 9999 using TCP (client) + IPaddress ip; + + if(argc!=2) { + fprintf(stderr, "Usage: %s <nom ou ip du serveur>\n", argv[0]); + return 1; + } + + openLog(NULL); + + sprintf(logMsg, "NetLemmings version %i.%i", NetLemmings_VERSION_MAJOR, NetLemmings_VERSION_MINOR); + logs(LOG_INFO, logMsg); + + if(SDLNet_ResolveHost(&ip,argv[1],9999)==-1) { + logs2(LOG_ERROR, "main(), SDLNet_ResolveHost()", SDLNet_GetError()); + return 2; + } + + client.sockClient=SDLNet_TCP_Open(&ip); + if(!client.sockClient) { + logs2(LOG_ERROR,"main(), SDLNet_TCP_Open()", SDLNet_GetError()); + return 3; + } + + result=init(); + if(result!=0) { + logs(LOG_ERROR,"main(), mySDLInit()"); + return 3; + } + + semGameStart=SDL_CreateSemaphore(0); + + args.end=&end; + args.drift_ms=&drift_ms; + args.client=&client; + SDL_Thread *timeSyncThread = SDL_CreateThread(processNetworkEvents, &args); + if(!timeSyncThread) { + logs2(LOG_ERROR,"main(), SDL_CreateThread()", SDL_GetError()); + return 4; + } + + //Signale au serveur que le client est pret + result=sendEvent(client.sockClient, &evReady); + if (result!=0) return 6; + + logs(LOG_INFO, "Waiting game start"); + result=SDL_SemWait(semGameStart); + if (result!=0) { + logs2(LOG_ERROR, "main(), SDL_SemWait()", SDL_GetError()); + return 7; + } + logs(LOG_INFO, "Game started !"); + + while(!end) { + timeBefore_ms = SDL_GetTicks(); + + processLocalEvents(); + play(tick++); + updateGraphics(); + + delay_ms=TICK_DURATION_MS-(SDL_GetTicks()-timeBefore_ms)+drift_ms; + if (delay_ms>0) SDL_Delay(delay_ms); //TODO Si le client rame trop, faut décrocher la partie + } + + SDLNet_TCP_Close(client.sockClient); + SDL_WaitThread(timeSyncThread, &status); + sprintf(logMsg, "TimeSync thread terminated with code %i", status); + logs(LOG_DEBUG, logMsg); + + closeLog(); + return 0; +} + +int processNetworkEvents(void *a) { + int result; + event_t e; + char logMsg[128]; + + struct _processNetworkEvents_args *args = (struct _processNetworkEvents_args *)a; + + while(! *(args->end) ) { + // logs(LOG_DEBUG, "Waiting event"); + result=receiveEvent(args->client,&e); + if (result != 0) { + logs(LOG_WARN, "processNetworkEvents(), receiveEvents() error"); + *(args->end)=1; + continue; //TODO : je doute que ça skipe vriament la syncrho du temps :s + } + // logs(LOG_DEBUG, "Got event"); + + *(args->drift_ms)=( tick - e.serverTick ); + sprintf(logMsg, "serverTick==%i, tick==%i, drift_ms==%i\n", e.serverTick, tick, *(args->drift_ms)); + logs(LOG_DEBUG, logMsg); + + + result=SDL_SemPost(semGameStart); + if (result!=0) { + logs2(LOG_ERROR, "main(), SDL_SemPost()", SDL_GetError()); + return 1; + } + + + switch(e.type) { + case eLemAction: + //TODO + break; + case eTimeSync: // Rien à faire dans ce cas, la synchro du temps se fait quelque soti le tyep d'évènement + break; + default: + logs(LOG_ERROR, "serveClient(), Unknown event type"); + } + } + // logs(LOG_DEBUG, "processNetworkEvents() : end"); + + return 0; +} + +void processLocalEvents() { + /*TODO : intégration des variables manipulées dans des structs qui vont bien +while( SDL_PollEvent( &event ) ) { + switch (event.type) { + case SDL_KEYBOARDEVENT: + switch(event.key.keysym.sym){ + case SDLK_HOME : decalFps = 0; break; + case SDLK_END : decalFps = FPS-11; break; + case SDLK_PAGEUP : if(decalFps>0){decalFps -=1;}break; + case SDLK_PAGEDOWN : if(decalFps<(FPS-1)){decalFps +=1;}break; + case SDLK_w : paint_stencil = (paint_stencil==0)? 1 : 0 ; break; + case SDLK_ESCAPE : quit = 1; break; + default:break; + } + break; + case SDL_MOUSEMOTION: + mouseX = event.motion.x; + mouseY = event.motion.y; + break; + case SDL_MOUSEBUTTONDOWN: + err=mouse_action(&gInit, mouseX, mouseY,camera.x,camera.y ); + if(err!=0){return err;} //FIXME : WTF ? + break; + case SDL_QUIT: + end=1; + break; + } + } + + if(mouseY <= LEVEL_HEIGHT){ + if (mouseX > (SCREEN_WIDTH - BOUND_SENSIBILITE)){ + if (camera.x < (LEVEL_WIDTH - SCREEN_WIDTH ) ) + {camera.x += CAM_VITESSE;} + } + if (mouseX < BOUND_SENSIBILITE){ + if (camera.x >= CAM_VITESSE ) + {camera.x -= CAM_VITESSE;} + } + */ +} + +int updateGraphics() { + //TODO : modifier les calques + + + //Mise à jour de l'écran + + /*if( SDL_Flip(screen) == -1 ) { + return 4; + }*/ + + return 0; +} diff --git a/src/netlem.h b/src/netlem.h new file mode 100644 index 0000000..f0566c5 --- /dev/null +++ b/src/netlem.h @@ -0,0 +1,8 @@ +#ifndef NETLEM_H +#define NETLEM_H + +// the configured options and settings for NetLemmings +#define NetLemmings_VERSION_MAJOR 0 +#define NetLemmings_VERSION_MINOR 1 + +#endif /*NETLEM_H*/ diff --git a/src/netlem.h.in b/src/netlem.h.in new file mode 100644 index 0000000..3da7f07 --- /dev/null +++ b/src/netlem.h.in @@ -0,0 +1,8 @@ +#ifndef NETLEM_H +#define NETLEM_H + +// the configured options and settings for NetLemmings +#define NetLemmings_VERSION_MAJOR @NetLemmings_VERSION_MAJOR@ +#define NetLemmings_VERSION_MINOR @NetLemmings_VERSION_MINOR@ + +#endif /*NETLEM_H*/ 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...) +*/ + diff --git a/src/netlem_ds.h b/src/netlem_ds.h new file mode 100644 index 0000000..cd28d03 --- /dev/null +++ b/src/netlem_ds.h @@ -0,0 +1,8 @@ +#ifndef NETLEM_DS_H +#define NETLEM_DS_H + +#define MAX_THREADS 2 +#define TIME_FOR_TIMESYNC_MS 2000 +#define MAX_EVENT_INTERVAL_MS 1000 + +#endif diff --git a/src/test/test.c b/src/test/test.c new file mode 100644 index 0000000..4f157b1 --- /dev/null +++ b/src/test/test.c @@ -0,0 +1,28 @@ + +#include <stdio.h> +#include "SDL/SDL.h" + + +Uint32 timeSyncInsertNullEventsIfNeeded(Uint32 interval, void *args) { + static i=0; + printf("%i\n",i++); + return 2000; +} + +int main(void) { + + SDL_TimerID timerTimeSync; + + + SDL_Init(SDL_INIT_TIMER); + printf("before addtimer\n"); + timerTimeSync = SDL_AddTimer(2000, timeSyncInsertNullEventsIfNeeded, NULL); + printf("after addtimer\n"); + + if ( timerTimeSync==NULL ) { + printf("bug addtimer\n"); + } + + SDL_Delay(100000); +} + diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 0000000..e2282fe --- /dev/null +++ b/src/utils.c @@ -0,0 +1,41 @@ +#include "utils.h" + +#include <stdlib.h> // exit() +#include <stdio.h> // FILE * +#include <string.h> // strerror_r() +#include <errno.h> + +static FILE *fdLog=NULL; + +void openLog(char path[]) { + if(path==NULL) { + fdLog=stderr; + } else { + //TODO : à implementer + fprintf(stderr,"openLog : not yet implemented\n"); + exit(128); + } +} + +void closeLog() { + if(fdLog!=NULL && fdLog!=stderr) { + fclose(fdLog); + } +} + +//TODO : ajouter une notion de niveau de masquage de messages +void logs(int level, char msg[]) { + fprintf(fdLog,"%i - %s\n", level, msg); +} + +void logs2(int level, char context[], char msg[]) { + fprintf(fdLog,"%i - %s : %s\n", level, context, msg); +} + +void logp(int level, char msg[]) { + char errbuf[128]; //TODO : Pas cool la taille fixe ici + + strerror_r(errno, errbuf, 128); + fprintf(fdLog,"%i - %s : %s\n", level, msg, errbuf); +} + diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..021816d --- /dev/null +++ b/src/utils.h @@ -0,0 +1,15 @@ +#ifndef UTILS_H +#define UTILS_H + +#define LOG_DEBUG 0 +#define LOG_INFO 1 +#define LOG_WARN 2 +#define LOG_ERROR 3 + +void openLog(char path[]); +void closeLog(); +void logs(int level, char msg[]); +void logs2(int level, char context[], char msg[]); +void logp(int level, char msg[]); + +#endif //UTILS_H |