/* * file com_dgram.c - base struct und functions for datagram connections * * $Id: com_dgram.c,v 1.3 2004/05/14 10:00:33 alfie Exp $ * * Program XBLAST * (C) by Oliver Vogel (e-mail: m.vogel@ndh.net) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2; or (at your option) * any later version * * This program is distributed in the hope that it will be entertaining, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILTY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc. * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "com_dgram.h" /* * local macros */ #define GAME_TIME_PING 0xFFFF #define PLAYER_MASK_FINISH 0xFF /* * local variables */ static unsigned char buffer[MAX_DGRAM_SIZE]; /* * pack player action */ static size_t PackPlayerAction (PackedPlayerAction *dst, const PlayerAction *src) { size_t i, j; unsigned char action; assert (NULL != dst); assert (NULL != src); dst->mask = 0; for (i = 0, j = 0; i < MAX_PLAYER; i ++) { action = PlayerActionToByte (src + i); if (0 != action) { dst->mask |= (1u << i); dst->action[j] = action; j ++; } } dst->numBytes = j + 1; return dst->numBytes; } /* PackPlayerAction */ /* * unpack player action */ static size_t UnpackPlayerAction (PlayerAction *dst, const unsigned char *buf) { size_t i, j; unsigned char mask; assert (NULL != dst); assert (NULL != buf); for (i = 0, j = 1, mask = 1; i < MAX_PLAYER; i ++, mask <<= 1u) { if (buf[0] & mask) { PlayerActionFromByte (dst + i, buf[j]); j ++; } else { PlayerActionFromByte (dst + i, 0x00); } } return j; } /* UnpackPlayerAction */ /* * Datagram with ping times has arrived */ static void HandlePing (XBCommDgram *dComm, const unsigned char *data, size_t len) { size_t i, j; assert (NULL != dComm); if (len > 0) { assert (NULL != data); for (i = 1, j = 0; i < MAX_HOSTS && j < len; i ++, j += 2) { (*dComm->pingFunc) (dComm, i, (data[j+1] << 8) + data[j]); } } (*dComm->pingFunc) (dComm, 0, 0); #ifdef DEBUG_TELE Dbg_Out ("dgram: handle ping (%u)\n", len); #endif } /* HandlePingEx */ /* * datagram with frame data has arrived */ static void HandleFrames (XBCommDgram *dComm, unsigned gameTime, const unsigned char *data, size_t len) { size_t i; XBBool ignored; static PlayerAction playerAction[MAX_PLAYER]; /* this frame is in the future ... */ if (gameTime > dComm->nextFrame) { Dbg_Out ("dgram: handle frames %d-%d lost\n", dComm->nextFrame, gameTime-1); /* TODO: inform application about stall/out of sync */ } /* now set first frame for server */ if (! dComm->primary) { dComm->first = gameTime; } i = 0; while (i < len) { ignored = XBFalse; if (PLAYER_MASK_FINISH == data[i]) { assert (NULL != dComm->finishFunc); (*dComm->finishFunc) (dComm); Dbg_Out ("dgram: handle frames FINISH received\n"); i ++; } else { i += UnpackPlayerAction (playerAction, data + i); if (gameTime != dComm->nextFrame) { ignored = XBTrue; #ifdef DEBUG_TELE Dbg_Out ("dgram: handle frames %d != %d ignored\n", gameTime, dComm->nextFrame); #endif } else { /* inform application: this is the frame we expect */ assert (dComm->actionFunc != NULL); (*dComm->actionFunc) (dComm, gameTime, playerAction); #ifdef DEBUG_TELE Dbg_Out ("dgram: handle frames %d used\n", gameTime); #endif } } /* adjust datagrams to send */ if (! ignored) { dComm->nextFrame ++; } /* ready for next frame */ gameTime ++; } /* now set first frame for server */ if (dComm->primary) { dComm->first = gameTime; } #ifdef DEBUG_TELE Dbg_Out ("dgram: handle frames first=%4u next=%4u nextFrame=%4u\n", dComm->first, dComm->next, dComm->nextFrame); #endif } /* HandleFrames */ /* * */ static XBCommResult ReadDgram (XBComm *comm) { XBDatagram *rcv; const unsigned char *data; size_t len; unsigned gameTime; const char * host; unsigned short port; XBCommDgram *dComm = (XBCommDgram *) comm; assert (NULL != dComm); if (NULL == dComm->host) { rcv = Net_ReceiveDatagram (comm->socket); } else { rcv = Net_ReceiveDatagramFrom (comm->socket, &host, &port); if (0 == strcmp (host, dComm->host)) { /* we expected connect datagram from this host => connect socket ti host and port */ dComm->connected = Net_ConnectUdp (comm->socket, host, port); dComm->host = NULL; return dComm->connected ? XCR_OK : XCR_Error; } } if (NULL != rcv) { gettimeofday (&dComm->lastRcv, NULL); /* send data to application */ data = Net_DgramData (rcv, &len); if (len == 0) { /* just a ping without data */ HandlePing (dComm, data, 0); } else if (len >= 2) { gameTime = (data[1] << 8) + data[0]; if (GAME_TIME_PING == gameTime) { /* ping with oing time of peers */ HandlePing (dComm, data + 2, len - 2); } else { /* ingame data (keys or finish) */ HandleFrames (dComm, gameTime, data + 2, len - 2); } } Net_DeleteDatagram (rcv); } return XCR_OK; } /* ReadDgram */ /* * send current datagram to server */ static XBCommResult WriteDgram (XBComm *comm) { XBCommDgram *dComm = (XBCommDgram *) comm; assert (NULL != comm); Socket_UnregisterWrite (CommSocket (comm)); if (NULL != dComm->snd) { if (! Net_SendDatagram (dComm->snd, comm->socket) ) { return XCR_Error; } Net_DeleteDatagram (dComm->snd); dComm->snd = NULL; gettimeofday (&dComm->lastSnd, NULL); } return XCR_OK; } /* ReadDgServer */ /* * */ static XBCommResult DeleteDgram (XBComm *comm) { XBCommDgram *dgram = (XBCommDgram *) comm; assert (dgram != NULL); if (NULL != dgram->snd) { Net_DeleteDatagram (dgram->snd); } CommFinish (&dgram->comm); free (dgram); return XCR_OK; } /* DeleteDgram */ /* * create datagramm connection server */ XBComm * Dgram_CommInit (XBCommDgram *dComm, XBCommType commType, XBSocket *pSocket, XBBool primary, DgramPingFunc pingFunc, DgramFinishFunc finishFunc, DgramActionFunc actionFunc) { assert (NULL != dComm); assert (NULL != finishFunc); assert (NULL != actionFunc); /* set values */ CommInit (&dComm->comm, commType, pSocket, ReadDgram, WriteDgram, DeleteDgram); dComm->snd = NULL; dComm->port = Net_LocalPort (pSocket); dComm->primary = primary; dComm->first = 0; dComm->next = 0; dComm->nextFrame = 0; dComm->pingFunc = pingFunc; dComm->finishFunc = finishFunc; dComm->actionFunc = actionFunc; dComm->lastSnd.tv_sec = 0; dComm->lastSnd.tv_usec = 0; dComm->lastRcv.tv_sec = 0; dComm->lastRcv.tv_usec = 0; memset (dComm->ppa, 0, sizeof (dComm->ppa)); /* that's all */ return &dComm->comm; } /* D2C_CreateComm */ /* * get port for client */ unsigned short Dgram_Port (const XBCommDgram *dComm) { /* sanity checks */ assert (dComm != NULL); /* get value */ return dComm->port; } /* D2C_Port */ /* * reset datagram connection */ void Dgram_Reset (XBCommDgram *dComm) { assert (dComm != NULL); dComm->next = 1; dComm->first = 1; dComm->nextFrame = 1; memset (dComm->ppa, 0, sizeof (dComm->ppa)); } /* Dgram_Reset */ /* * just send a ping */ void Dgram_SendPing (XBCommDgram *dComm) { if (NULL == dComm->snd) { dComm->snd = Net_CreateDatagram (NULL, 0); Socket_RegisterWrite (CommSocket (&dComm->comm)); } } /* Dgram_SendPing */ /* * just send a ping */ void Dgram_SendPingData (XBCommDgram *dComm, const int pingTime[]) { size_t i; unsigned char pingData[2*MAX_HOSTS]; if (NULL == dComm->snd) { assert (NULL != pingTime); /* setup buffer with ping times */ pingData[0] = 0xFF & (GAME_TIME_PING); pingData[1] = 0xFF & (GAME_TIME_PING >> 8); for (i = 1; i < MAX_HOSTS; i++) { pingData[2*i] = 0xFF & ((unsigned) pingTime[i]); pingData[2*i+1] = 0xFF & ((unsigned) pingTime[i] >> 8); } dComm->snd = Net_CreateDatagram (pingData, 2*MAX_HOSTS); Socket_RegisterWrite (dComm->comm.socket); } } /* Dgram_SendPing */ /* * send player action to client */ static void SendBuffer (XBCommDgram *dComm) { int i; size_t len; /* sanity check */ assert (dComm != NULL); /* clear any old datagrams */ if (NULL != dComm->snd) { Dbg_Out ("dgram: send buffer %u resend\n", dComm->next - 1); Net_DeleteDatagram (dComm->snd); dComm->snd = NULL; } /* set time stamp */ memset (buffer, 0, sizeof (buffer)); buffer[0] = 0xFF & dComm->first; buffer[1] = 0xFF & (dComm->first >> 8); /* now send data from first frame to current game time */ len = 2; for (i = dComm->first; i < dComm->next; i ++) { /* check if any space is left in datagram buffer */ if (dComm->ppa[i].numBytes + len > sizeof (buffer) ) { break; } /* copy to buffer */ memcpy (buffer + len, &dComm->ppa[i].mask, dComm->ppa[i].numBytes); len += dComm->ppa[i].numBytes; } /* prepare sending */ Socket_RegisterWrite (dComm->comm.socket); dComm->snd = Net_CreateDatagram (buffer, len); } /* Dgram_SendPlayerAction */ /* * send player action to client */ void Dgram_SendPlayerAction (XBCommDgram *dComm, int gameTime, const PlayerAction *playerAction) { /* sanity check */ assert (dComm != NULL); assert (gameTime < NUM_PLAYER_ACTION-1); assert (playerAction != NULL); /* pack data */ PackPlayerAction (dComm->ppa + gameTime, playerAction); dComm->next = gameTime + 1; /* try to send it */ SendBuffer (dComm); } /* Dgram_SendPlayerAction */ /* * acknowledge level finish */ void Dgram_SendFinish (XBCommDgram *dComm, int gameTime) { /* sanity check */ assert (dComm != NULL); assert (gameTime < NUM_PLAYER_ACTION); /* pack data */ dComm->ppa[gameTime].numBytes = 1; dComm->ppa[gameTime].mask = PLAYER_MASK_FINISH; dComm->next = gameTime + 1; /* try to send it */ SendBuffer (dComm); } /* Dgram_SendFinish */ /* * flush last remain data */ XBBool Dgram_Flush (XBCommDgram *dComm) { /* check if finished has already arrived */ if (dComm->primary) { if (dComm->first > dComm->next) { return XBFalse; } } else { if (dComm->first >= dComm->next) { return XBFalse; } } /* try to send it */ SendBuffer (dComm); return XBTrue; } /* Dgram_Flush */ /* * end of file com_dgram.c */