Kawe Mazidjatari b3a68ed095 Add EABase, EAThread and DirtySDK to R5sdk
DirtySDK (EA's Dirty Sockets library) will be used for the LiveAPI implementation, and depends on: EABase, EAThread.
2024-04-05 18:29:03 +02:00

2580 lines
90 KiB
C
Raw Blame History

/*H*************************************************************************************************/
/*!
\File netgamedist.c
\Description
This file provides some upper layer protocol abstractions such as controller packet
buffering and exchange logic.
\Notes
\verbatim
From a "client" perspective:
pRef->InpBufData is the inbound queue where inbound multipackets from server are accumulated.
* pRef->InpBufData.iBeg identifies the oldest entry in that queue
(i.e. the next entry to be consumed locally)
* pRef->InpBufData.iEnd identifies the next free entry in that queue
(i.e. the next spot to write into when we read from the socket)
* An overflow in that queue is always detected by comparing pRef->InpBufData.iBeg and pRef->InpBufData.iEnd.
pRef->OutBufData is the outbound queue where outbound packets are accumulated pending transmission to the server.
* pRef->OutBufData.iBeg identifies the oldest entry in that queue
(i.e. the next entry to be sent over the network)
* pRef->OutBufData.iEnd identifies the next free entry in that queue
(i.e. the next spot to write into when user submits data)
* SPECIFICITY: When pRef->OutBufData.iBeg is advanced, the entry that it used to point to is NOT YET invalidated
because it still needs to be surfaced up to the user later when paired with a inbound bundle from the server.
To track those "pending entries" located before the position of pRef->OutBufData.iBeg, this third pointer
is used: "pRef->InpBufData.iBeg+pRef->iIOOffset". Notice that it consists of a pointer tracking the
other queue + an adjustment offset.
* An overflow in that queue is always detected by comparing "pRef->InpBufData.iBeg+pRef->iIOOffset"
and pRef->OutBufData.iEnd.
From a "server" perspective:
pRef->InpBufData is the inbound queue where inbound packets from a specific client are accumulated.
* pRef->InpBufData.iBeg identifies the oldest entry in that queue
(i.e. the next entry to be consumed locally)
* pRef->InpBufData.iEnd identifies the next free entry in that queue
(i.e. the next spot to write into when we read from the socket)
* An overflow in that queue is always detected by comparing pRef->InpBufData.iBeg and pRef->InpBufData.iEnd.
pRef->OutBufData is the outbound queue where outbound multipackets are accumulated pending transmission to a specific client.
* pRef->OutBufData.iBeg identifies the oldest entry in that queue
(i.e. the next entry to be sent over the network)
* pRef->OutBufData.iEnd identifies the next free entry in that queue
(i.e. the next spot to write into when the server has a bundle of paired inputs to submit for transmission)
* An overflow in that queue is always detected by comparing pRef->OutBufData.iBeg and pRef->OutBufData.iEnd
\endverbatim
\Copyright
Copyright (c) Tiburon Entertainment / Electronic Arts 2000-2018. ALL RIGHTS RESERVED.
\Version 1.0 12/20/00 (GWS) Based on split of GmClient.c
\Version 1.1 12/31/01 (GWS) Cleaned up and made really platform independent
\Version 1.2 12/03/09 (mclouatre) Added configurable run-time verbosity
*/
/*************************************************************************************************H*/
/*** Include files *********************************************************************/
#include <string.h>
#include <stdio.h>
#include "DirtySDK/dirtysock.h"
#include "DirtySDK/dirtysock/dirtymem.h"
#include "DirtySDK/dirtysock/dirtylib.h"
#include "DirtySDK/game/netgamepkt.h"
#include "DirtySDK/game/netgamelink.h"
#include "DirtySDK/game/netgamedist.h"
/*** Defines ***************************************************************************/
#define NETGAMEDIST_VERBOSITY (2)
#define TIMING_DEBUG (0)
#define PING_DEBUG (0)
#define INPUTCHECK_LOGGING_DELAY (15) // 15 msec
#define GMDIST_META_ARRAY_SIZE (32) // how many past versions of sparse multipacket to keep
// PACKET_WINDOW can be overriden at build time with the nant global property called dirtysdk-distpktwindow-size
#ifndef PACKET_WINDOW
#define PACKET_WINDOW (64)
#endif
/*** Macros ****************************************************************************/
/*** Type Definitions ******************************************************************/
//! Describe an entry in the flat buffer.
typedef struct GameBufferLookupT
{
uint32_t uInsertTime; //!< time when packet was queued
uint32_t uPos; //!< indexes into OutBufData.pControllerIO or InpBufData.pControllerIO
uint16_t uLen;
uint16_t uLenSize;
} GameBufferLookupT;
//! Flat buffer structure. Implements a wrapping queue of packets.
typedef struct GameBufferDataT
{
//! incoming and outgoing controller packets
unsigned char *pControllerIO;
//! length of the io buffer
uint32_t uBufLen;
// input/output lookup, addresses into pControllerIO
GameBufferLookupT IOLookUp[PACKET_WINDOW];
//! index of first packet to send/process in pControllerIO
int32_t iBeg;
//! index of last packet to send/process in pControllerIO
int32_t iEnd;
} GameBufferDataT;
//! Describes one version of multipacket.
typedef struct NetGameDistMetaInfoT
{
//! which entries are used
uint32_t uMask;
//! number of players used
uint8_t uPlayerCount;
//! version number
uint8_t uVer;
} NetGameDistMetaInfoT;
//! netgamedist internal state
struct NetGameDistRefT
{
//! module memory group
int32_t iMemGroup;
void *pMemGroupUserData;
//! output buffer for packets and lookup table
GameBufferDataT OutBufData;
//! input buffer for packets and lookup table
GameBufferDataT InpBufData;
//! offset between the input and output queues
int32_t iIOOffset;
//! local sequence number
uint32_t uLocalSeq;
//! global sequence number
uint32_t uGlobalSeq;
//! external status monitoring
NetGameLinkStatT NetGameLinkStats;
//! current exchange rate
int32_t iInputRate;
//! input exchange window
int32_t iInputWind;
//! clamp the min window size
int32_t iInputMini;
//! clamp the max window size
int32_t iInputMaxi;
//! when to recalc window
uint32_t uInpCalc;
//! when packet was last send
uint32_t uInpNext;
//! netgamelink ref
void *pNetGameLinkRef;
//! netgamelink stat func
NetGameDistStatProc *pStatProc;
//! netgame send function
NetGameDistSendProc *pSendProc;
//! netgame recv function
NetGameDistRecvProc *pRecvProc;
NetGameDistDropProc *pDropProc;
NetGameDistPeekProc *pPeekProc;
NetGameDistLinkCtrlProc *pLinkCtrlProc;
//! when bActAsServer is true the index is of the player that owns the dist
//! if bActAsServer is false the index is of the local player in the dist game
uint32_t uDistIndex;
//! the total number of players
uint32_t uTotalPlyrs;
//! true if we are receiving multi packets from dirtycast in OTP mode
//! false if when inbound packets originated from the client in a 2 player mode
uint32_t bRecvMulti;
//! whether we are acting as the server.
uint32_t bActAsServer;
//! a max packet for use by input
NetGameMaxPacketT MaxPkt;
//! a max packet for use by input
char aMultiBuf[NETGAME_DATAPKT_MAXSIZE];
//! the number of writes to a position in the input queue, (dropproc)
int32_t aPacketId[PACKET_WINDOW];
//! latest stats received by each pClient
NetGameDistStatT aRecvStats[GMDIST_MAX_CLIENTS];
//! Error condition. Set during calls like update, if an error occurs.
int32_t iErrCond;
int32_t iLastSentDelta;
uint32_t uSkippedInputCheckLogCount;
uint32_t uLastInputCheckLogTick;
int32_t iInboundDropPktCnt;
int32_t iInboundPktCnt;
int32_t iOutboundPktCnt;
//! total wait time in input queue
int32_t iWaitTimeTotal;
//! total Input deqeued count
int32_t iInboundDequeueCnt;
//! total dist processing time
int32_t iDistProcTimeTotal;
//! total dist inputs processed
int32_t iDistProcCnt;
char strErrorText[GMDIST_ERROR_SIZE];
//! whether we must update the flow control flags to the game server
uint8_t bUpdateFlowCtrl;
//! the range of meta info we must update the
uint8_t uUpdateMetaInfoBeg;
uint8_t uUpdateMetaInfoEnd;
//! whether we are ready to send or not
uint8_t bRdySend;
//! whether we are ready to receive or not
uint8_t bRdyRecv;
//! whether remote is ready to send or not
uint8_t bRdySendRemote;
//! whether remote is ready to receive or not
uint8_t bRdyRecvRemote;
uint32_t uLocalCRC;
uint8_t bLocalCRCValid;
uint32_t uRemoteCRC;
uint8_t bRemoteCRCValid;
//! debug output verbosity
uint8_t uVerbose;
//! boolean indicating whether we want to surface CRC cahllenges from the GS
uint8_t bCRCChallenges;
//! boolean indicating whether we received meta information on the packet layout
uint8_t bGotMetaInfo;
//! boolean indicating whether we are sending sparse multi-packets
uint8_t bSparse;
//! Meta information about sparse multi-packets to send
NetGameDistMetaInfoT aMetaInfoToSend[GMDIST_META_ARRAY_SIZE];
//! Received meta information about sparse multi-packets (wraps around)
NetGameDistMetaInfoT aMetaInfo[GMDIST_META_ARRAY_SIZE];
//! The version meta information from the last peeked or queried packet
uint32_t uLastQueriedVersion;
};
/*** Function Prototypes ***************************************************************/
/*** Variables *************************************************************************/
// Private variables
#if DIRTYCODE_LOGGING
// The following is meant to be indexed with the corresponding constants defined in netgamedist.h.
// Array contents need to be tailored if constants are removed, modified or added.
static char _strNetGameDistDataTypes[6][32] =
{"INVALID",
"GMDIST_DATA_NONE",
"GMDIST_DATA_INPUT",
"GMDIST_DATA_INPUT_DROPPABLE",
"GMDIST_DATA_DISCONNECT",
"GMDIST_DATA_NODATA"};
#endif
// Public variables
/*** Private Functions *****************************************************************/
#if PING_DEBUG
/*F*************************************************************************************************/
/*!
\Function _PingHistory
\Description
Display the uPing history [DEBUG only]
\Input *pStats - pointer to NetGameLinkStat struct
\Version 12/20/00 (GWS)
*/
/*************************************************************************************************F*/
static void _PingHistory(const NetGameLinkStatT *pStats)
{
int32_t iPing;
int32_t iIndex;
char strMin[64];
char strMax[64];
char strAvg[64];
char strCnt[64];
const NetGameLinkHistT *pHist;
static uint32_t uPrev = 0;
// see if its time
if (pStats->pingslot == uPrev)
{
return;
}
uPrev = pStats->pingslot;
iPing = 0;
for (iIndex = 0; iIndex < PING_HISTORY; ++iIndex)
{
pHist = pStats->pinghist + ((pStats->pingslot - iIndex) & (PING_HISTORY-1));
strMin[iIndex] = '0'+pHist->min/50;
strMax[iIndex] = '0'+pHist->max/50;
strAvg[iIndex] = '0'+pHist->avg/50;
strCnt[iIndex] = '0'+pHist->cnt;
iPing += pHist->avg;
}
strMin[iIndex] = 0;
strMax[iIndex] = 0;
strAvg[iIndex] = 0;
strCnt[iIndex] = 0;
iPing /= PING_HISTORY;
NetPrintf(("history(%d/%d): ping=%d, late=%d, calc=%d\n", pStats->pingslot, pStats->pingtick, pStats->ping, pStats->late, iPing));
NetPrintf((" %s\n", strMin));
NetPrintf((" %s\n", strMax));
NetPrintf((" %s\n", strAvg));
NetPrintf((" %s\n", strCnt));
}
#endif
/*F*************************************************************************************************/
/*!
\Function _SetDataPtrTry
\Description
returns the position in the input or output buffer for addition of a packet of length uLength.
\Input *pRef - reference pointer
\Input *pBuffer - buffer pointer
\Input uLength - length of the next packet to be stored
\Output
int32_t - pos if a space was found. -1 if the buffer cannot accomodate uLength
\Version 02/08/07 (jrainy)
*/
/*************************************************************************************************F*/
static int32_t _SetDataPtrTry(NetGameDistRefT *pRef, GameBufferDataT *pBuffer, uint16_t uLength)
{
uint32_t uIndexA, uIndexB;
uint32_t uPosA;
if (uLength > pBuffer->uBufLen)
{
ds_snzprintf(pRef->strErrorText, sizeof(pRef->strErrorText), "overflow in _SetDataPtrTry. requested length (%d) > buffer size (%d), ", uLength, pBuffer->uBufLen);
NetPrintf(("netgamedist: [%p] critical failure in _SetDataPtrTry()\n", pRef));
return(-1);
}
// identify the IOLookup index that points to the last-written entry (uIndexA), and find out where the corresponding next free byte exactly is (uPosA)
uIndexA = GMDIST_Modulo(pBuffer->iEnd - 1, PACKET_WINDOW);
uPosA = pBuffer->IOLookUp[uIndexA].uPos + pBuffer->IOLookUp[uIndexA].uLen;
uPosA = ((uPosA + 3)&~3); // aligns posA to the next 4-byte boundary
// identify the IOLookup index that points to the oldest valid entry (uIndexB)
if ((pBuffer == &pRef->InpBufData) || pRef->bActAsServer)
{
/* for the input queue (inbound data from either server or client) or the server output queue (outbound data to client)
the iBeg index identifies the oldest valid data */
uIndexB = pBuffer->iBeg;
}
else
{
/* For the client output queue (outbound data to server), the iBeg index identifies the next packet to be
sent to the server not the oldest valid data. The oldest valid data, i.e. pending data to be
notified as paired by the game server, is rather always identified using a combination of
the "inbound" iBeg and the iOOffset. */
uIndexB = GMDIST_Modulo(pRef->InpBufData.iBeg + pRef->iIOOffset, PACKET_WINDOW);
}
// if the buffer is empty then we can just start filling it in from the beginning
if (uIndexB == (unsigned)pBuffer->iEnd)
{
return(0);
}
else
{
/* note: uIndexB can be used to access valid data in IOLookup[] only if uIndexB != pBuffer->iEnd
because pBuffer->iEnd points to the next "free" entry... so it contains no valid data yet. */
// find out where the last valid byte exactly is (uPosB)
uint32_t uPosB = pBuffer->IOLookUp[uIndexB].uPos;
if (uPosA >= uPosB)
{
// if we can't fit at the end, let's retry from the beginning
if ((uPosA + uLength) > pBuffer->uBufLen)
{
uPosA = 0;
// fall through to the next 'if'
}
else
{
return(uPosA);
}
}
if (uPosB >= uPosA)
{
if ((uPosA + uLength) >= uPosB)
{
ds_snzprintf(pRef->strErrorText, sizeof(pRef->strErrorText), "overflow in _SetDataPtrTry. indexA was %d, indexB was %d, posA was %d, posB was %d, ", uIndexA, uIndexB, pBuffer->IOLookUp[uIndexA].uPos + pBuffer->IOLookUp[uIndexA].uLen, uPosB);
NetPrintf(("netgamedist: [%p] %s buffer full.\n", pRef, ((pBuffer == &pRef->InpBufData)?"input":"output")));
return(-1);
}
else
{
return(uPosA);
}
}
}
// unreachable code. If we get here something went terribly wrong.
ds_snzprintf(pRef->strErrorText, sizeof(pRef->strErrorText), "_SetDataPtrTry reached unreachable code.");
NetPrintf(("netgamedist: [%p] critical failure in _SetDataPtrTry() - unreachable code\n", pRef));
return(-1);
}
/*F*************************************************************************************************/
/*!
\Function _SetDataPtr
\Description
Prepares the input or output buffer for addition of a packet of length uLength.
\Input *pRef - reference pointer
\Input *pBuffer - buffer pointer
\Input uLength - length of the next packet to be stored
\Output
uint8_t - TRUE if a space was found. FALSE if the buffer cannot accomodate uLength
\Version 02/08/07 (jrainy)
*/
/*************************************************************************************************F*/
static uint8_t _SetDataPtr(NetGameDistRefT *pRef, GameBufferDataT *pBuffer, uint16_t uLength)
{
int32_t uPos = _SetDataPtrTry(pRef, pBuffer, uLength);
if (uPos != -1)
{
pBuffer->IOLookUp[pBuffer->iEnd].uPos = uPos;
return(TRUE);
}
return(FALSE);
}
/*F*************************************************************************************************/
/*!
\Function _NetGameDistCheckWindow
\Description
Handle the window stretching. If we have too many unacknowledged packets, we start sending less often
\Input *pRef - The NetGameDist ref
\Input iRemain - Remaining frame time (before window stretching)
\Input uTick - Current tick
\Version 12/21/09 (jrainy)
*/
/*************************************************************************************************F*/
static void _NetGameDistCheckWindow(NetGameDistRefT *pRef, int32_t iRemain, uint32_t uTick)
{
int32_t iQueue;
if (!pRef->bRecvMulti)
{
// stretch cycle if we are over window
iQueue = GMDIST_Modulo(pRef->OutBufData.iEnd - GMDIST_Modulo(pRef->InpBufData.iBeg + pRef->iIOOffset, PACKET_WINDOW), PACKET_WINDOW);
if ((iQueue > pRef->iInputWind) && (iRemain < pRef->iInputRate /2))
{
#if TIMING_DEBUG
// dont show single cycle adjustments
if (uTick+pRef->iInputRate/2-pRef->uInpNext != 1)
{
NetPrintf(("netgamedist: [%p] stretching cycle (que=%d, win=%d, tick=%d, add=%d)\n",
pRef, iQueue, pRef->iInputWind, uTick, (uTick+pRef->iInputRate/2)-pRef->uInpNext));
}
#endif
// stretch the next send
pRef->uInpNext = uTick+pRef->iInputRate /2;
}
}
}
/*F*************************************************************************************************/
/*!
\Function _NetGameDistUpdateSendTime
\Description
Update the next send time after sending each packet. In p2p mode, clamp the next send to
no earlier than half a frame after *now* and no later than two frames after *now*
\Input *pRef - The NetGameDist ref
\Version 12/21/09 (jrainy)
*/
/*************************************************************************************************F*/
static void _NetGameDistUpdateSendTime(NetGameDistRefT *pRef)
{
uint32_t uTick;
int32_t iNext;
// get current time
uTick = NetTick();
// record the send time so we can schedule next packet
pRef->uInpNext += pRef->iInputRate;
if (!pRef->bRecvMulti)
{
// figure time till next send
iNext = pRef->uInpNext - uTick;
// clamp the time range to half/double rate
if (iNext < pRef->iInputRate / 2)
{
#if TIMING_DEBUG
NetPrintf(("send clamping to half (was %d)\n", iNext));
#endif
pRef->uInpNext = uTick + pRef->iInputRate /2;
}
if (iNext > pRef->iInputRate * 2)
{
#if TIMING_DEBUG
NetPrintf(("send clamping to double (was %d)\n", iNext));
#endif
pRef->uInpNext = uTick + pRef->iInputRate *2;
}
}
}
/*F*************************************************************************************************/
/*!
\Function _NetGameDistSendInput
\Description
Provide local input data
\Input *pRef - reference pointer
\Input *pBuffer - controller data
\Input iLength - data length
\Input iLengthSize - size of length buffer
\Output
int32_t - negative=error (including GMDIST_OVERFLOW=overflow), positive=packet successfully sent or saved to NetGameDist send buffer
\Version 12/20/00 (GWS)
*/
/*************************************************************************************************F*/
static int32_t _NetGameDistSendInput(NetGameDistRefT *pRef, void *pBuffer, int32_t iLength, int32_t iLengthSize)
{
int32_t iNext, iResult, iBeg;
unsigned char *pData;
// add packet to queue
if (pBuffer != NULL)
{
// verify length is valid
if (iLength < 0)
{
ds_snzprintf(pRef->strErrorText, sizeof(pRef->strErrorText), "netgamedist: _NetGameDistSendInput with iLength %d.", iLength);
NetPrintf(("netgamedist: invalid buffer length passed to _NetGameDistSendInput()!\n"));
return(GMDIST_INVALID);
}
// see if room to buffer packet
iNext = GMDIST_Modulo(pRef->OutBufData.iEnd+1, PACKET_WINDOW);
// the - 1 steals a spot but prevents:
// input local prechanging iooffset triggering this
iBeg = GMDIST_Modulo(pRef->InpBufData.iBeg + pRef->iIOOffset - 1, PACKET_WINDOW);
if (!pRef->bActAsServer && (iNext == iBeg))
{
ds_snzprintf(pRef->strErrorText, sizeof(pRef->strErrorText), "netgamedist: _NetGameDistSendInput with iNext %d and iBeg %d.", iNext, iBeg);
return(GMDIST_OVERFLOW);
}
if (iNext == pRef->OutBufData.iBeg)
{
ds_snzprintf(pRef->strErrorText, sizeof(pRef->strErrorText), "netgamedist: _NetGameDistSendInput with iNext %d and OutBufData.iBeg %d.", iNext, pRef->OutBufData.iBeg);
return(GMDIST_OVERFLOW);
}
// point to buffer
if (!_SetDataPtr(pRef, &pRef->OutBufData, iLength))
{
return(GMDIST_OVERFLOW);
}
pData = pRef->OutBufData.pControllerIO+pRef->OutBufData.IOLookUp[pRef->OutBufData.iEnd].uPos;
// copy data into buffer
pRef->OutBufData.IOLookUp[pRef->OutBufData.iEnd].uLen = iLength;
pRef->OutBufData.IOLookUp[pRef->OutBufData.iEnd].uLenSize = iLengthSize;
ds_memcpy(pData, pBuffer, iLength);
// save down the time
pRef->OutBufData.IOLookUp[pRef->OutBufData.iEnd].uInsertTime = NetTick();
// incorporate into buffer
pRef->OutBufData.iEnd = iNext;
// moved from inside the 'while' below
// this will update the send time on send attempt, not on actual send.
// i.e. "queue packets at regular interval", not "queue packets so that
// they send at regular interval", which is not really doable anyway.
_NetGameDistUpdateSendTime(pRef);
}
// try and send packet
// prevent sending if we have pending metainfo to send as it will affect the format
while ((pRef->OutBufData.iBeg != pRef->OutBufData.iEnd) && (pRef->uUpdateMetaInfoBeg == pRef->uUpdateMetaInfoEnd))
{
// point to buffer
pData = pRef->OutBufData.pControllerIO+pRef->OutBufData.IOLookUp[pRef->OutBufData.iBeg].uPos;
// setup a data input packet
if (pRef->bActAsServer)
{
if (pRef->OutBufData.IOLookUp[pRef->OutBufData.iBeg].uLenSize == 2)
{
pRef->MaxPkt.head.kind = GAME_PACKET_INPUT_MULTI_FAT;
}
else
{
pRef->MaxPkt.head.kind = GAME_PACKET_INPUT_MULTI;
}
}
else
{
pRef->MaxPkt.head.kind = GAME_PACKET_INPUT;
}
pRef->MaxPkt.head.len = pRef->OutBufData.IOLookUp[pRef->OutBufData.iBeg].uLen;
ds_memcpy(pRef->MaxPkt.body.data, pData, pRef->MaxPkt.head.len);
// try and send
iResult = pRef->pSendProc(pRef->pNetGameLinkRef, (NetGamePacketT*)&pRef->MaxPkt, 1);
if (iResult > 0)
{
// all is well -- remove from buffer
pRef->OutBufData.iBeg = GMDIST_Modulo(pRef->OutBufData.iBeg+1, PACKET_WINDOW);
}
else if (iResult < 0)
{
ds_snzprintf(pRef->strErrorText, sizeof(pRef->strErrorText), "netgamedist: GMDIST_SENDPROC_FAILED result is %d.", iResult);
pRef->iErrCond = GMDIST_SENDPROC_FAILED;
NetPrintf(("netgamedist: sendproc failed in _NetGameDistSendInput()!\n"));
return(GMDIST_SENDPROC_FAILED);
}
else
{
// lower-level transport is out of send buffer space at the moment<6E> exit for now and try again later.
break;
}
}
// packet was either buffered and/or sent
return(1);
}
/*F*************************************************************************************************/
/*!
\Function _ProcessPacketType
\Description
Whenever a packet of a given type is received, this function is called
to do any specific process.
\Input *pRef - reference pointer
\Input uPacketType - the packet type
\Version 02/22/07 (jrainy)
*/
/*************************************************************************************************F*/
static void _ProcessPacketType(NetGameDistRefT *pRef, uint8_t uPacketType)
{
if (!pRef->bActAsServer && ((uPacketType == GAME_PACKET_INPUT_MULTI) || (uPacketType == GAME_PACKET_INPUT_MULTI_FAT)))
{
pRef->bRecvMulti = TRUE;
}
}
/*F*************************************************************************************************/
/*!
\Function _NetGameDistSendFlowUpdate
\Description
Sends a packet to the server enabling or disabling flow control
\Input *pRef - reference pointer
\Version 09/29/09 (jrainy)
*/
/*************************************************************************************************F*/
static void _NetGameDistSendFlowUpdate(NetGameDistRefT *pRef)
{
int32_t iResult;
pRef->MaxPkt.head.kind = GAME_PACKET_INPUT_FLOW;
pRef->MaxPkt.head.len = 7;
pRef->MaxPkt.body.data[0] = pRef->bRdySend;
pRef->MaxPkt.body.data[1] = pRef->bRdyRecv;
pRef->MaxPkt.body.data[2] = pRef->bLocalCRCValid;
pRef->MaxPkt.body.data[3] = (pRef->uLocalCRC >> 24);
pRef->MaxPkt.body.data[4] = (pRef->uLocalCRC >> 16);
pRef->MaxPkt.body.data[5] = (pRef->uLocalCRC >> 8);
pRef->MaxPkt.body.data[6] = pRef->uLocalCRC;
pRef->bLocalCRCValid = FALSE;
iResult = pRef->pSendProc(pRef->pNetGameLinkRef, (NetGamePacketT*)&pRef->MaxPkt, 1);
if (iResult < 0)
{
NetPrintf(("netgamedist: [%p] Flow update failed (error=%d)!\n", pRef, iResult));
pRef->bUpdateFlowCtrl = FALSE;
}
else if (iResult > 0)
{
pRef->bUpdateFlowCtrl = FALSE;
}
else
{
NetPrintf(("netgamedist: [%p] Flow update deferred (error=overflow)!\n", pRef));
// nothing to do here, the caller - NetGameDistUpdate - will try again later.
}
}
/*F*************************************************************************************************/
/*!
\Function _NetGameDistSendMetaInfo
\Description
Sends a packet to the client describing the format of upcoming packets
\Input *pRef - reference pointer
\Version 09/29/09 (jrainy)
*/
/*************************************************************************************************F*/
static void _NetGameDistSendMetaInfo(NetGameDistRefT *pRef)
{
int32_t iResult;
NetGameDistMetaInfoT *pMetaInfo;
while(pRef->uUpdateMetaInfoBeg != pRef->uUpdateMetaInfoEnd)
{
pMetaInfo = &pRef->aMetaInfoToSend[pRef->uUpdateMetaInfoBeg];
pRef->MaxPkt.head.kind = GAME_PACKET_INPUT_META;
pRef->MaxPkt.head.len = 5;
pRef->MaxPkt.body.data[0] = pMetaInfo->uVer;
pRef->MaxPkt.body.data[1] = pMetaInfo->uMask >> 24;
pRef->MaxPkt.body.data[2] = pMetaInfo->uMask >> 16;
pRef->MaxPkt.body.data[3] = pMetaInfo->uMask >> 8;
pRef->MaxPkt.body.data[4] = pMetaInfo->uMask;
iResult = pRef->pSendProc(pRef->pNetGameLinkRef, (NetGamePacketT*)&pRef->MaxPkt, 1);
if (iResult < 0)
{
NetPrintf(("netgamedist: [%p] Meta info update failed (error=%d)!\n", pRef, iResult));
pRef->uUpdateMetaInfoBeg = GMDIST_Modulo(pRef->uUpdateMetaInfoBeg + 1, GMDIST_META_ARRAY_SIZE);
}
else if (iResult > 0)
{
NetPrintf(("netgamedist: [%p] Meta info update sent %d 0x%08x !\n", pRef, pMetaInfo->uVer, pMetaInfo->uMask));
pRef->uUpdateMetaInfoBeg = GMDIST_Modulo(pRef->uUpdateMetaInfoBeg + 1, GMDIST_META_ARRAY_SIZE);
}
else
{
NetPrintf(("netgamedist: [%p] Meta info update deferred (error=overflow)!\n", pRef));
// nothing to do here, the caller - NetGameDistUpdate - will try again later.
break;
}
}
}
/*F*************************************************************************************************/
/*!
\Function _NetGameDistCountBits
\Description
Count the number of bits set in a given uint32_t variable
\Input *pRef - reference pointer
\Input uMask - mask
\Output
uint32_t - the number of bits set
\Version 09/26/09 (jrainy)
*/
/*************************************************************************************************F*/
static uint32_t _NetGameDistCountBits(NetGameDistRefT *pRef, uint32_t uMask)
{
uint32_t uCount = 0;
while(uMask)
{
uCount += (uMask & 1);
uMask /= 2;
};
return(uCount);
}
#if DIRTYCODE_LOGGING
/*F*************************************************************************************************/
/*!
\Function _NetGameDistInputCheckLog
\Description
Logs the values returned by NetGameDistInputCheck.
\Input *pRef - reference pointer
\Input *pSend - the pointer to pSend the client passed to InputCheck
\Input *pRecv - the pointer to pRecv the client passed to InputCheck
\Version 12/21/09 (jrainy)
*/
/*************************************************************************************************F*/
static void _NetGameDistInputCheckLog(NetGameDistRefT *pRef, int32_t *pSend, int32_t *pRecv)
{
uint32_t uCurrentTick = NetTick();
uint32_t uDelay;
// Calculate delay since last log
uDelay = NetTickDiff(uCurrentTick, pRef->uLastInputCheckLogTick);
// Check if condition to display the trace is met.
// Condition is: meaningful send/recv info ready OR skip timeout expire
if ( (pSend && (*pSend==0)) || (pRecv && (*pRecv!=0)) || // check meaningful send/recv
(uDelay >= INPUTCHECK_LOGGING_DELAY) ) // check skip timeout
{
if (pSend && pRecv)
{
NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "netgamedist: [%p] exiting NetGameDistInputCheck() tick=%d, *pSend=%d, *pRecv=%d logskipped_count=%d\n",
pRef, uCurrentTick, *pSend, *pRecv, pRef->uSkippedInputCheckLogCount));
}
else if (pSend && !pRecv)
{
NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "netgamedist: [%p] exiting NetGameDistInputCheck() tick=%d, *pSend=%d, pRecv=NULL logskipped_count=%d\n",
pRef, uCurrentTick, *pSend, pRef->uSkippedInputCheckLogCount));
}
else if (!pSend && pRecv)
{
NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "netgamedist: [%p] exiting NetGameDistInputCheck() tick=%d, pSend=NULL, *pRecv=%d logskipped_count=%d\n",
pRef, uCurrentTick, *pRecv, pRef->uSkippedInputCheckLogCount));
}
else
{
NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "netgamedist: [%p] exiting NetGameDistInputCheck() tick=%d, pSend=NULL, pRecv=NULL logskipped_count=%d\n",
pRef, uCurrentTick, pRef->uSkippedInputCheckLogCount));
}
// Re-initialize variables used to skip some logs
pRef->uSkippedInputCheckLogCount = 0;
pRef->uLastInputCheckLogTick = uCurrentTick;
}
else
{
pRef->uSkippedInputCheckLogCount++;
}
}
#endif
/*F*************************************************************************************************/
/*!
\Function _NetGameDistInputPeekRecv
\Description
Behaves just as a receive function, taking data from netgamelink. However, we peek first and
if an overflow is detected, we do not take the packet from netgamelink. In this case, we
return 0, as if doing was ready to be received
\Input *pRef - reference pointer
\Input *pBuf - buffer to receive into
\Input iLen - available length in buf
\Output
int32_t - recvproc return value, or forced to 0 if we'd be in overflow
\Version 08/15/11 (jrainy)
*/
/*************************************************************************************************F*/
static int32_t _NetGameDistInputPeekRecv(NetGameDistRefT *pRef, NetGamePacketT *pBuf, int32_t iLen)
{
NetGamePacketT* pPeekPacket = NULL;
if (pRef->pPeekProc)
{
uint32_t uDistMask = (1 << GAME_PACKET_INPUT) |
(1 << GAME_PACKET_INPUT_MULTI) |
(1 << GAME_PACKET_INPUT_MULTI_FAT);
(*pRef->pPeekProc)(pRef->pNetGameLinkRef, &pPeekPacket, uDistMask);
// ok, we have a mean to see what is coming, and we got something.
if (pPeekPacket)
{
// let's check if we have space for it.
// for completeness, we may check buffer overflow on slots and dropproc, but this seems overkill
if (_SetDataPtrTry(pRef, &pRef->InpBufData, pPeekPacket->head.len + 1) < 0)
{
NetPrintf(("netgamedist: [%p] not receiving from link layer a packet of type %d and length %d because our buffer is full\n", pRef, pPeekPacket->head.kind, pPeekPacket->head.len));
// act as if the recvproc had nothing.
return(0);
}
}
}
return((*pRef->pRecvProc)(pRef->pNetGameLinkRef, pBuf, iLen, TRUE));
}
/*** Public Functions ******************************************************************/
/*F*************************************************************************************************/
/*!
\Function NetGameDistCreate
\Description
Create the game pClient
\Input *pNetGameLinkRef - netgamelink module ref
\Input *pStatProc - netgamelink stat callback
\Input *pSendProc - game send func
\Input *pRecvProc - game recv func
\Input uInBufferSize - input buffer size, plan around PACKET_WINDOW*n*packets
\Input uOutBufferSize - output buffer size, plan around PACKET_WINDOW*packets
\Output
NetGameDistRefT * - pointer to module state
\Version 12/20/00 (GWS)
*/
/*************************************************************************************************F*/
NetGameDistRefT *NetGameDistCreate(void *pNetGameLinkRef, NetGameDistStatProc *pStatProc, NetGameDistSendProc *pSendProc, NetGameDistRecvProc *pRecvProc, uint32_t uInBufferSize, uint32_t uOutBufferSize )
{
NetGameDistRefT *pRef;
int32_t iMemGroup;
void *pMemGroupUserData;
// Query current mem group data
DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData);
// allocate and init module state
if ((pRef = DirtyMemAlloc(sizeof(*pRef), NETGAMEDIST_MEMID, iMemGroup, pMemGroupUserData)) == NULL)
{
NetPrintf(("netgamedist: [%p] unable to allocate module state\n", pRef));
return(NULL);
}
ds_memclr(pRef, sizeof(*pRef));
pRef->iMemGroup = iMemGroup;
pRef->pMemGroupUserData = pMemGroupUserData;
pRef->iLastSentDelta = -1;
// Allocate the input and output buffers
pRef->InpBufData.pControllerIO = DirtyMemAlloc( uInBufferSize, NETGAMEDIST_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData );
pRef->OutBufData.pControllerIO = DirtyMemAlloc( uOutBufferSize, NETGAMEDIST_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData );
ds_memclr(pRef->InpBufData.pControllerIO, uInBufferSize);
ds_memclr(pRef->OutBufData.pControllerIO, uOutBufferSize);
pRef->InpBufData.uBufLen = uInBufferSize;
pRef->OutBufData.uBufLen = uOutBufferSize;
// save the link layer callbacks
pRef->pNetGameLinkRef = pNetGameLinkRef;
pRef->pStatProc = pStatProc;
pRef->pSendProc = pSendProc;
pRef->pRecvProc = pRecvProc;
// set default controller exchange rate
pRef->iInputRate = 50;
// set the defaults
pRef->iInputMini = 1;
pRef->iInputMaxi = 10;
// Defaults to two players so that the non-multi version behaves as it used to.
pRef->uTotalPlyrs = 2;
// Set default verbosity level
pRef->uVerbose = 1;
// init check tick counter
pRef->uLastInputCheckLogTick = NetTick();
NetPrintf(("netgamedist: module created with PACKET_WINDOW = %d\n", PACKET_WINDOW));
return(pRef);
}
/*F*************************************************************************************************/
/*!
\Function NetGameDistDestroy
\Description
Destroy the game pClient
\Input *pRef - reference pointer
\Version 12/20/00 (GWS)
*/
/*************************************************************************************************F*/
void NetGameDistDestroy(NetGameDistRefT *pRef)
{
DirtyMemFree( pRef->InpBufData.pControllerIO, NETGAMEDIST_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData );
DirtyMemFree( pRef->OutBufData.pControllerIO, NETGAMEDIST_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData );
pRef->InpBufData.pControllerIO = NULL;
pRef->OutBufData.pControllerIO = NULL;
// free our memory
DirtyMemFree(pRef, NETGAMEDIST_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData);
}
/*F*************************************************************************************************/
/*!
\Function NetGameDistStatus
\Description
Get status information
\Input *pRef - reference pointer
\Input iSelect - selector
\Input iValue - input value
\Input *pBuf - output buffer
\Input iBufSize - output buffer size
\Output
int32_t - selector specific return value
\Notes
This is read-only data which can be read at any time. This reference becomes
invalid when the module is destroyed.
The count of [?snd..?out] is the number of packets ready to send. The min of
[?cmp..?inp] and [?cmp..?out] is the number of packets ready to process (because
you need a packet from each peer in order to perform processing).
Selectors are:
\verbatim
SELECTOR RETURN RESULT
'dcnt' returns the total inbound dequeued count
'drop' returns the total input packet dropped
'icnt' returns the total inbound packet count
'late' returns the latency from the link stats
'mult' returns the number of players and writes their stats to pBuf
'ocnt' returns the total outbound packet count
'pcnt' returns the total packet processed count (full round trip)
'plat' returns the end-to-end latency in number of packets
'prti' returns the total packet processed time (full round trip)
'pwin' returns PACKET_WINDOW configured for netgamedist
'qver' returns the last queried packet version
'rate' returns current exchange rate
'rcrc' if iValue==0 returns whether we have a value otherwise returns the value itself
'rrcv' returns if remote is ready to receive or not
'rsnd' returns if remote is ready to send or not
'stat' returns the link stats via pBuf
'wait' returns total inbound packet wait time;
'wind' returns the current exchange window
'?cmp' returns the offset of first packet to process in input buffer
'?cws' returns if we can send. FALSE if sending would overflow the send queue, iValue is length
'?inp' returns the offset of last packet to process in input buffer
'?out' returns the offset of last packet to send in output buffer
'?snd' returns the offset of first packet to send in output buffer
\endverbatim
Unhandled selectors are passed on to NetGameLink
\Version 12/20/00 (GWS)
*/
/*************************************************************************************************F*/
int32_t NetGameDistStatus(NetGameDistRefT *pRef, int32_t iSelect, int32_t iValue, void *pBuf, int32_t iBufSize)
{
if (iSelect == 'dcnt')
{
return(pRef->iInboundDequeueCnt);
}
if (iSelect == 'drop')
{
return(pRef->iInboundDropPktCnt);
}
if (iSelect == 'icnt')
{
return(pRef->iInboundPktCnt);
}
if (iSelect == 'late')
{
return(pRef->NetGameLinkStats.late);
}
if (iSelect == 'mult')
{
// Make user-provide buffer is large enough to receive a pointer
if ((pBuf != NULL) && (iBufSize >= (int32_t)(sizeof(NetGameDistStatT) * pRef->uTotalPlyrs)))
{
ds_memcpy(pBuf, pRef->aRecvStats, sizeof(NetGameDistStatT) * pRef->uTotalPlyrs);
return(pRef->uTotalPlyrs);
}
else
{
// unhandled
return(-1);
}
}
if (iSelect == 'ocnt')
{
return(pRef->iOutboundPktCnt);
}
if (iSelect == 'plat')
{
return(GMDIST_Modulo(pRef->OutBufData.iEnd - GMDIST_Modulo(pRef->InpBufData.iBeg + pRef->iIOOffset, PACKET_WINDOW), PACKET_WINDOW));
}
if (iSelect == 'pcnt')
{
return(pRef->iDistProcCnt);
}
if (iSelect == 'prti')
{
return(pRef->iDistProcTimeTotal);
}
if (iSelect == 'pwin')
{
return(PACKET_WINDOW);
}
if (iSelect == 'qver')
{
return(pRef->uLastQueriedVersion);
}
if (iSelect == 'rate')
{
return(pRef->iInputRate);
}
if (iSelect == 'rcrc')
{
if (iValue)
{
int32_t ret = pRef->uRemoteCRC;
pRef->uRemoteCRC = 0;
pRef->bRemoteCRCValid = FALSE;
return(ret);
}
else
{
return(pRef->bRemoteCRCValid);
}
}
if (iSelect == 'rrcv')
{
return(pRef->bRdyRecvRemote);
}
if (iSelect == 'rsnd')
{
return(pRef->bRdySendRemote);
}
if (iSelect == 'stat')
{
(pRef->pStatProc)(pRef->pNetGameLinkRef, iSelect, iValue, &pRef->NetGameLinkStats, sizeof(NetGameLinkStatT));
// Make user-provide buffer is large enough to receive a pointer
if ((pBuf != NULL) && (iBufSize >= (int32_t)sizeof(NetGameLinkStatT)))
{
ds_memcpy(pBuf, &pRef->NetGameLinkStats, sizeof(NetGameLinkStatT));
return(0);
}
else
{
// unhandled
return(-1);
}
}
if (iSelect == 'wait')
{
return(pRef->iWaitTimeTotal);
}
if (iSelect == 'wind')
{
return(pRef->iInputWind);
}
if (iSelect == '?cmp')
{
return(pRef->InpBufData.iBeg);
}
if (iSelect == '?cws')
{
int32_t iNext;
if (iValue < 0)
{
return(FALSE);
}
iNext = GMDIST_Modulo(pRef->OutBufData.iEnd +1, PACKET_WINDOW);
// mclouatre April 26th 2018 - unclear to me what the -1 is for in the condition below
if (!pRef->bActAsServer && (iNext == GMDIST_Modulo(pRef->InpBufData.iBeg + pRef->iIOOffset - 1, PACKET_WINDOW)))
{
ds_snzprintf(pRef->strErrorText, sizeof(pRef->strErrorText), "'?cws' failure while comparing indices");
return(FALSE);
}
if (_SetDataPtrTry(pRef, &pRef->OutBufData, iValue) == -1)
{
return(FALSE);
}
return(TRUE);
}
if (iSelect == '?inp')
{
return(pRef->InpBufData.iEnd);
}
if (iSelect == '?out')
{
return(pRef->OutBufData.iEnd);
}
if (iSelect == '?snd')
{
return(pRef->OutBufData.iBeg);
}
// fallthrough to NetGameLinkStatus
return((pRef->pStatProc)(pRef->pNetGameLinkRef, iSelect, iValue, pBuf, iBufSize));
}
/*F*************************************************************************************************/
/*!
\Function NetGameDistSetServer
\Description
Get pointer to status counters
\Input *pRef - reference pointer
\Input bActAsServer - boolean, whether NetGameDist should send multi-packets
\Version 02/26/07 (jrainy)
*/
/*************************************************************************************************F*/
void NetGameDistSetServer(NetGameDistRefT *pRef, uint8_t bActAsServer)
{
// the trigraph is there to make sure we don't store non-{0,1} value in.
pRef->bActAsServer = bActAsServer ? TRUE : FALSE;
}
/*F*************************************************************************************************/
/*!
\Function NetGameDistMultiSetup
\Description
Tells NetGameDist how many players there are in the game, and which one we are.
Will only affect the order packets are received from NetGameDistInputQueryMulti.
\Input *pRef - netgamelink module ref
\Input iDistIndex - which player we are
\Input iTotPlrs - total number of players
\Version 02/08/07 (jrainy)
*/
/*************************************************************************************************F*/
void NetGameDistMultiSetup(NetGameDistRefT *pRef, int32_t iDistIndex, int32_t iTotPlrs)
{
NetPrintf(("netgamedist: [%p] NetGameDistMultiSetup index: %d, total players: %d\n", pRef, iDistIndex, iTotPlrs));
pRef->uTotalPlyrs = iTotPlrs;
pRef->uDistIndex = iDistIndex;
}
/*F*************************************************************************************************/
/*!
\Function NetGameDistMetaSetup
\Description
Tells NetGameDist whether to enable meta-information sending, and
sets the mask & the version number for the meta-information.
\Input *pRef - netgamelink module ref
\Input bSparse - enable or disable meta-information sending (disabling after enabling is currently not-supported on the client)
\Input uMask - set the mask for the meta-information
\Input uVersion - set the version number for the meta-information.
\Version 08/29/11 (szhu)
*/
/*************************************************************************************************F*/
void NetGameDistMetaSetup(NetGameDistRefT *pRef, uint8_t bSparse, uint32_t uMask, uint32_t uVersion)
{
NetPrintf(("netgamedist: [%p] NetGameDistMetaSetup Enabled: %s, Mask: 0x%x, Version: %d\n",
pRef, bSparse?"TRUE":"FALSE", uMask, uVersion));
pRef->bSparse = bSparse;
pRef->aMetaInfoToSend[pRef->uUpdateMetaInfoEnd].uMask = uMask;
pRef->aMetaInfoToSend[pRef->uUpdateMetaInfoEnd].uPlayerCount = _NetGameDistCountBits(pRef, uMask);
pRef->aMetaInfoToSend[pRef->uUpdateMetaInfoEnd].uVer = GMDIST_Modulo(uVersion, GMDIST_META_ARRAY_SIZE);
pRef->uUpdateMetaInfoEnd = GMDIST_Modulo(pRef->uUpdateMetaInfoEnd + 1, GMDIST_META_ARRAY_SIZE);
}
/*F*************************************************************************************************/
/*!
\Function NetGameDistInputPeek
\Description
See if a completed packet is ready
\Input *pRef - reference pointer
\Input *pType - will be filled with data type
\Input *pPeer - buffer sent by peer
\Input *pPlen - length of data in peer buffer. Must pass in the length of pPeer
\Output
int32_t - zero=no data pending, negative=error, positive=data returned
\Version 02/05/2007 (JLB)
*/
/*************************************************************************************************F*/
int32_t NetGameDistInputPeek(NetGameDistRefT *pRef, uint8_t *pType, void *pPeer, int32_t *pPlen)
{
uint8_t *pPsrc;
int32_t iLength;
uint8_t uInputKind;
int16_t iLengthSize = 1;
uint8_t uOffsetLengths;
uint8_t uOffsetBuffer;
uint8_t uOffsetTypes;
uint8_t uOffsetVersion;
uint16_t uPlayerCount = pRef->uTotalPlyrs;
uint8_t uVer = 0;
// if no remote data, do an update
if (pRef->InpBufData.iBeg == pRef->InpBufData.iEnd)
{
NetGameDistUpdate(pRef);
}
// no packets from peer?
if (pRef->InpBufData.iBeg == pRef->InpBufData.iEnd)
{
return(0);
}
// point to data buffer
pPsrc = pRef->InpBufData.pControllerIO+pRef->InpBufData.IOLookUp[pRef->InpBufData.iBeg].uPos;
uInputKind = pPsrc[pRef->InpBufData.IOLookUp[pRef->InpBufData.iBeg].uLen - 1];
if (uInputKind == GAME_PACKET_INPUT_MULTI_FAT)
{
iLengthSize = 2;
}
// find the location to lookup the version
uOffsetVersion = pRef->bRecvMulti ? 1 : 0;
// extract the version number of the packet, and player count, if the server ever gave us metadata
if (pRef->bGotMetaInfo && pRef->bRecvMulti)
{
uVer = GMDIST_Modulo(pPsrc[uOffsetVersion], GMDIST_META_ARRAY_SIZE);
uPlayerCount = pRef->aMetaInfo[uVer].uPlayerCount;
pRef->uLastQueriedVersion = uVer;
}
// always pack packets as if there was at least 2 players (to avoid negatively-sized fields)
if (pRef->bRecvMulti && (pRef->uTotalPlyrs == 1))
{
uPlayerCount = 2;
}
// compute offsets to various parts
uOffsetTypes = uOffsetVersion + (pRef->bGotMetaInfo ? 1 : 0);
uOffsetLengths = uOffsetTypes + (pRef->bRecvMulti ? (uPlayerCount / 2) : 1);
uOffsetBuffer = uOffsetLengths + (pRef->bRecvMulti ? (iLengthSize * (uPlayerCount - 2)) : 0);
// copy over the data, -1 to skip the packet kind tacked at the end
iLength = pRef->InpBufData.IOLookUp[pRef->InpBufData.iBeg].uLen - uOffsetBuffer - 1;
if (pPeer != NULL)
{
//check the passed-in length to prevent overflow
if (iLength > *pPlen)
{
ds_snzprintf(pRef->strErrorText, sizeof(pRef->strErrorText), "NetGameDistInputPeek error. length is %d, *pLen is %d.", iLength, *pPlen);
// fail if the buffer is insufficient
*pPlen = iLength;
return(GMDIST_OVERFLOW);
}
ds_memcpy(pPeer, pPsrc + uOffsetBuffer, iLength);
}
*pPlen = iLength;
if (pType != NULL)
{
*pType = *(pPsrc + (pRef->bRecvMulti ? 1 : 0));
}
#if DIRTYCODE_LOGGING
NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "netgamedist: [%p] peeked type %d len %d\n", pRef, pType?*pType:0, pPlen?*pPlen:0));
#endif
// return success
return(pRef->aPacketId[pRef->InpBufData.iBeg]);
}
/*F*************************************************************************************************/
/*!
\Function NetGameDistInputQuery
\Description
See if a completed packet is ready
\Input *pRef - reference pointer
\Input *pOurs - buffer previously sent with NetGameDistInputLocal
\Input *pOlen - length of data in ours buffer
\Input *pPeer - buffer sent by peer
\Input *pPlen - length of data in peer buffer
\Output
int32_t - zero=no data pending, negative=error, positive=data returned
\Version 12/20/00 (GWS)
*/
/*************************************************************************************************F*/
int32_t NetGameDistInputQuery(NetGameDistRefT *pRef, void *pOurs, int32_t *pOlen, void *pPeer, int32_t *pPlen)
{
void* aInputs[2];
int32_t aLengths[2];
uint8_t aTypes[2];
int32_t iRet;
if ( pRef->uTotalPlyrs != 2 )
{
return(GMDIST_BADSETUP);
}
// for a 2 player setup our index is always 0 and the peer is always at 1
aInputs[0] = pOurs;
aInputs[1] = pPeer;
iRet = NetGameDistInputQueryMulti(pRef, aTypes, aInputs, aLengths);
*pOlen = aLengths[0];
*pPlen = aLengths[1];
return(iRet);
}
/*F*************************************************************************************************/
/*!
\Function NetGameDistInputQueryMulti
\Description
See if a completed packet is ready
\Input *pRef - reference pointer
\Input *pDataTypes - array of data types (GMDIST_DATA_NONE, GMDIST_DATA_INPUT, GMDIST_DATA_INPUT_DROPPABLE, GMDIST_DATA_DISCONNECT).
\Input **ppInputs - array of pointers to inputs to receive.
\Input *pLen - array of lengths from the data received.
\Output
int32_t - zero=no data pending, negative=error, positive=data returned
\Version 02/08/07 (jrainy)
*/
/*************************************************************************************************F*/
int32_t NetGameDistInputQueryMulti(NetGameDistRefT *pRef, uint8_t *pDataTypes, void **ppInputs, int32_t *pLen)
{
unsigned char *pOsrc;
uint32_t uIndex, uCount, uPos, uRecvLen;
unsigned char *pRecvBuf;
uint8_t *pLengths;
uint8_t *pTypePos;
uint8_t uDelta;
uint8_t uOffsetLengths;
uint8_t uOffsetBuffer;
uint8_t uOffsetTypes;
uint8_t uOffsetVersion;
uint32_t uOurInp;
uint16_t uPlayerCount = pRef->uTotalPlyrs;
uint8_t bCRCRequest = FALSE;
uint8_t uVer = 0;
// if waiting on remote data, do an update
if (pRef->InpBufData.iBeg == pRef->InpBufData.iEnd)
{
NetGameDistUpdate(pRef);
}
// check for inputs ready to process; in 2p mode we also require a non-empty output buffer to pair with
if ((pRef->InpBufData.iBeg == pRef->InpBufData.iEnd) || (!pRef->bActAsServer && !pRef->bRecvMulti && (GMDIST_Modulo(pRef->InpBufData.iBeg + pRef->iIOOffset, PACKET_WINDOW) == pRef->OutBufData.iEnd)))
{
#if DIRTYCODE_LOGGING
NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "netgamedist: [%p] exiting NetGameDistInputQueryMulti() retval=0 tick=%d len=n/a type=n/a\n", pRef, NetTick()));
#endif
return(0);
}
if (ppInputs)
{
// access the first byte after the received data
uint8_t* pPacketKind = pRef->InpBufData.pControllerIO + pRef->InpBufData.IOLookUp[pRef->InpBufData.iBeg].uPos + pRef->InpBufData.IOLookUp[pRef->InpBufData.iBeg].uLen - 1;
int16_t iLengthSize = 1;
if (*pPacketKind == GAME_PACKET_INPUT_MULTI_FAT)
{
iLengthSize = 2;
}
// Go through the received packet and fill all the input data except ours, then copy our own from outlkup
uCount = 0;
uPos = 0;
uRecvLen = pRef->InpBufData.IOLookUp[pRef->InpBufData.iBeg].uLen - 1;
pRecvBuf = pRef->InpBufData.pControllerIO+pRef->InpBufData.IOLookUp[pRef->InpBufData.iBeg].uPos;
uDelta = pRef->bRecvMulti ? pRecvBuf[0] : 1;
uOffsetVersion = pRef->bRecvMulti ? 1 : 0;
// extract the version number of the packet, and player count, if the server ever gave us metadata
if (pRef->bGotMetaInfo && pRef->bRecvMulti)
{
uVer = GMDIST_Modulo(((char *)pRecvBuf)[uOffsetVersion], GMDIST_META_ARRAY_SIZE);
uPlayerCount = pRef->aMetaInfo[uVer].uPlayerCount;
pRef->uLastQueriedVersion = uVer;
}
// always pack packets as if there was at least 2 players (to avoid negatively-sized fields)
if (pRef->bRecvMulti && (uPlayerCount == 1))
{
uPlayerCount = 2;
}
// compute offsets to various parts
uOffsetTypes = uOffsetVersion + (pRef->bGotMetaInfo ? 1 : 0);
uOffsetLengths = uOffsetTypes + (pRef->bRecvMulti ? (uPlayerCount / 2) : 1);
uOffsetBuffer = uOffsetLengths + (pRef->bRecvMulti ? (iLengthSize * (uPlayerCount - 2)) : 0);
pLengths = pRecvBuf + uOffsetLengths;
pTypePos = pRecvBuf + uOffsetTypes;
pRecvBuf += uOffsetBuffer;
for (uIndex = 0; uIndex < pRef->uTotalPlyrs; uIndex++)
{
/* if this is not our own input and we are either
sending for everyone
or this is a player we are including in the current sparse multi-packet
then, add it in */
if (uIndex != pRef->uDistIndex)
{
if (!pRef->bGotMetaInfo || (pRef->aMetaInfo[uVer].uMask & (1 << uIndex)))
{
/* compute the length of packet for player i.
last length is known (ours) second to last is implicit (total - known ones) */
if (uCount == (uPlayerCount - 2u))
{
pLen[uIndex] = uRecvLen - uPos - uOffsetBuffer;
}
else
{
if (iLengthSize == 2)
{
pLen[uIndex] = (pLengths[uCount * iLengthSize] * 256) + pLengths[uCount * iLengthSize + 1];
}
else
{
pLen[uIndex] = pLengths[uCount];
}
}
// prevent invalid sizes if bogus data is received
if (pLen[uIndex] < 0)
{
pLen[uIndex] = 0;
}
// clamp the total read length to the received buffer, to prevent overflow.
if (pLen[uIndex] + uPos + uOffsetBuffer > uRecvLen)
{
NetPrintfVerbose((pRef->uVerbose, 0, "netgamedist: Warning ! Buffer overrun. Packet trimmed.\n"));
pLen[uIndex] = uRecvLen - uPos - uOffsetBuffer;
}
// compute the type of packet for player i. (nibbles)
if (uCount % 2)
{
pDataTypes[uIndex] = *pTypePos >> 4;
pTypePos++;
}
else
{
pDataTypes[uIndex] = *pTypePos & 0x0f;
}
// copy the packet for player i.
if (((pDataTypes[uIndex] & ~GMDIST_DATA_CRC_REQUEST) == GMDIST_DATA_INPUT) ||
((pDataTypes[uIndex] & ~GMDIST_DATA_CRC_REQUEST) == GMDIST_DATA_INPUT_DROPPABLE))
{
ds_memcpy(ppInputs[uIndex], pRecvBuf + uPos, pLen[uIndex]);
uPos += pLen[uIndex];
}
else
{
pLen[uIndex] = 0;
}
// if crc-checking is disabled locally, clear the "crc request" bit
if (!pRef->bCRCChallenges)
{
pDataTypes[uIndex] &= ~GMDIST_DATA_CRC_REQUEST;
}
/* set a local variable if any of the input has a crc request in.
this is to set the "crc request" bit in local input too, to ease our customers job. */
if (pDataTypes[uIndex] & GMDIST_DATA_CRC_REQUEST)
{
bCRCRequest = TRUE;
}
uCount++;
}
else
{
pDataTypes[uIndex] = GMDIST_DATA_NODATA;
pLen[uIndex] = 0;
}
}
}
if (uDelta)
{
uint8_t uDropIndex;
// update dist process times for dropped inputs (inputs dropped are considered processed)
for (uDropIndex = 0; uDropIndex < (uDelta - 1) ; uDropIndex++, pRef->iDistProcCnt++)
{
uint32_t uCurInp = GMDIST_Modulo(pRef->InpBufData.iBeg + uDropIndex, PACKET_WINDOW);
pRef->iDistProcTimeTotal += NetTickDiff(NetTick(), pRef->OutBufData.IOLookUp[uCurInp].uInsertTime);
}
// point to data buffers. Our local storage has just 1-byte type field
pRef->iIOOffset += (uDelta - 1);
uOurInp = GMDIST_Modulo(pRef->InpBufData.iBeg + pRef->iIOOffset, PACKET_WINDOW);
pOsrc = pRef->OutBufData.pControllerIO + pRef->OutBufData.IOLookUp[uOurInp].uPos + 1;
// copy over the data
if (pRef->OutBufData.IOLookUp[uOurInp].uLen != 0)
{
pLen[pRef->uDistIndex] = pRef->OutBufData.IOLookUp[uOurInp].uLen - 1;
ds_memcpy(ppInputs[pRef->uDistIndex], pOsrc, pLen[pRef->uDistIndex]);
pRef->iDistProcTimeTotal += NetTickDiff(NetTick(), pRef->OutBufData.IOLookUp[uOurInp].uInsertTime);
pRef->iDistProcCnt++;
pDataTypes[pRef->uDistIndex] = *(pRef->OutBufData.pControllerIO + pRef->OutBufData.IOLookUp[uOurInp].uPos);
}
else
{
NetPrintf(("netgamedist: detected invalid pairing index between input and output queue; ignoring"));
pLen[pRef->uDistIndex] = 0;
pDataTypes[pRef->uDistIndex] = GMDIST_DATA_NONE;
}
}
else
{
pRef->iIOOffset = GMDIST_Modulo(pRef->iIOOffset - 1, PACKET_WINDOW);
pDataTypes[pRef->uDistIndex] = GMDIST_DATA_NONE;
pLen[pRef->uDistIndex] = 0;
}
if (bCRCRequest)
{
pDataTypes[pRef->uDistIndex] |= GMDIST_DATA_CRC_REQUEST;
}
}
// calculate time in queue and add it to the total
pRef->iWaitTimeTotal += NetTickDiff(NetTick(), pRef->InpBufData.IOLookUp[pRef->InpBufData.iBeg].uInsertTime);
pRef->iInboundDequeueCnt++;
// return item from buffer
pRef->InpBufData.iBeg = GMDIST_Modulo(pRef->InpBufData.iBeg + 1, PACKET_WINDOW);
// update global sequence number
pRef->uGlobalSeq += 1;
#if DIRTYCODE_LOGGING
NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "netgamedist: [%p] exiting NetGameDistInputQueryMulti() retval=%d tick=%d\n", pRef, pRef->uGlobalSeq, NetTick()));
NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, " ==== Dumping player-specific data returned in [OUT] params\n"));
if (ppInputs)
{
for (uIndex = 0; uIndex < pRef->uTotalPlyrs; uIndex++)
{
uint8_t uDataType = (pDataTypes[uIndex] & ~GMDIST_DATA_CRC_REQUEST);
NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, " == Player %u --> len=%d type=%s\n", uIndex, pLen[uIndex],
_strNetGameDistDataTypes[(((uDataType >= GMDIST_DATA_NONE) && (uDataType <= GMDIST_DATA_NODATA)) ? uDataType : 0)]));
}
}
NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, " ==== End of player-specific data dump\n"));
#endif
// return the sequence number
return(pRef->uGlobalSeq);
}
/*F*************************************************************************************************/
/*!
\Function NetGameDistInputLocal
\Description
Provide local input data
\Input *pRef - reference pointer
\Input *pBuffer - controller data
\Input iLength - data length
\Output
int32_t - negative=error (including GMDIST_OVERFLOW=overflow), positive=successfully sent or queued
\Version 12/20/00 (GWS)
*/
/*************************************************************************************************F*/
int32_t NetGameDistInputLocal(NetGameDistRefT *pRef, void *pBuffer, int32_t iLength)
{
if (!pBuffer)
{
return(_NetGameDistSendInput(pRef, NULL, iLength, 0));
}
pRef->aMultiBuf[0] = GMDIST_DATA_INPUT;
ds_memcpy(pRef->aMultiBuf + 1, pBuffer, iLength);
return(_NetGameDistSendInput(pRef, pRef->aMultiBuf, iLength + 1, 1));
}
/*F*************************************************************************************************/
/*!
\Function NetGameDistInputLocalMulti
\Description
Provide local input data for all players, including ours, which will be discarded
\Input *pRef - reference pointer
\Input *pTypesArray - array of data types (GMDIST_DATA_INPUT_DROPPABLE, GMDIST_DATA_INPUT)
\Input **ppBuffer - array of controller data
\Input *pLengthsArray - array of data length
\Input iDelta - game team should pass in 1, other values only usable on server
\Output
int32_t - negative=error (including GMDIST_OVERFLOW*=overflow), positive=successfully sent or queued
\Version 02/08/07 (jrainy)
*/
/*************************************************************************************************F*/
int32_t NetGameDistInputLocalMulti(NetGameDistRefT *pRef, uint8_t *pTypesArray, void **ppBuffer, int32_t *pLengthsArray, int32_t iDelta)
{
uint32_t uIndex, uPos, uCount;
uint8_t uOffsetLengths;
uint8_t uOffsetBuffer;
uint8_t uOffsetTypes;
uint8_t uOffsetVersion;
uint8_t *pLengths;
uint8_t *pTypePos;
uint8_t uOurType, uType;
uint32_t uSendSize;
int16_t iLengthSize = 1;
uint16_t uPlayerCount = pRef->bActAsServer ? pRef->uTotalPlyrs : 1;
uint16_t uLastPlayer = uPlayerCount;
int32_t iResult;
// check if any input forces us to send fat multi-packets.
for (uIndex = 0; uIndex < uPlayerCount; uIndex++)
{
if (pLengthsArray[uIndex] > 0xFF)
{
iLengthSize = 2;
}
}
// if we are sending sparse multipacket, adjust the player count accordingly
if (pRef->bSparse && pRef->bActAsServer)
{
uPlayerCount = pRef->aMetaInfoToSend[GMDIST_Modulo(pRef->uUpdateMetaInfoEnd - 1, GMDIST_META_ARRAY_SIZE)].uPlayerCount;
}
// always pack multipacket as if there was at least 2 players (simplify the logic)
if (pRef->bActAsServer && (uPlayerCount == 1))
{
uLastPlayer = 2;
uPlayerCount = 2;
}
// compute the offset to various parts of the multipacket
uOffsetVersion = pRef->bActAsServer ? 1 : 0;
uOffsetTypes = uOffsetVersion + ((pRef->bSparse && pRef->bActAsServer) ? 1 : 0);
uOffsetLengths = uOffsetTypes + (pRef->bActAsServer ? (uPlayerCount / 2) : 1);
uOffsetBuffer = uOffsetLengths + (pRef->bActAsServer ? (iLengthSize * (uPlayerCount - 2)) : 0);
// if server mode
if (pRef->bActAsServer)
{
// compute the total packet size
uSendSize = uOffsetBuffer;
for (uIndex = 0; uIndex < pRef->uTotalPlyrs; uIndex++)
{
if (uIndex != pRef->uDistIndex)
{
uSendSize += pLengthsArray[uIndex];
}
}
// bail out if the packet would overflow
if (uSendSize >= NETGAME_DATAPKT_MAXSIZE)
{
ds_snzprintf(pRef->strErrorText, sizeof(pRef->strErrorText), "Multipacket bigger than NETGAME_DATAPKT_MAXSIZE (%d). Discarding and reporting overflow.\n", NETGAME_DATAPKT_MAXSIZE);
NetPrintf(("netgamedist: [%p] Multipacket bigger than NETGAME_DATAPKT_MAXSIZE (%d). Discarding and reporting overflow.\n", pRef, NETGAME_DATAPKT_MAXSIZE));
return(GMDIST_OVERFLOW_MULTI);
}
// mark the version, if we are sending meta information
if (pRef->bSparse)
{
pRef->aMultiBuf[uOffsetVersion] = GMDIST_Modulo(pRef->aMetaInfoToSend[GMDIST_Modulo(pRef->uUpdateMetaInfoEnd - 1, GMDIST_META_ARRAY_SIZE)].uVer, GMDIST_META_ARRAY_SIZE);
}
uOurType = (pTypesArray[pRef->uDistIndex] & ~GMDIST_DATA_CRC_REQUEST);
if (uOurType == GMDIST_DATA_NONE)
{
pRef->aMultiBuf[0] = 0;
}
else
{
if (((iDelta - 1) - pRef->iLastSentDelta) >= PACKET_WINDOW)
{
ds_snzprintf(pRef->strErrorText, sizeof(pRef->strErrorText), "GMDIST_OVERFLOW_WINDOW (pRef->m_packetid[pRef->InpBufData.iBeg]-1) is %d, pRef->iLastSentDelta is %d.\n", (pRef->aPacketId[pRef->InpBufData.iBeg]-1), pRef->iLastSentDelta);
return(GMDIST_OVERFLOW_WINDOW);
}
pRef->aMultiBuf[0] = (iDelta-1) - pRef->iLastSentDelta;
pRef->iLastSentDelta = (iDelta-1);
}
pRef->aPacketId[pRef->InpBufData.iBeg] = 0;
if (uOurType == GMDIST_DATA_NONE)
{
pRef->iIOOffset = GMDIST_Modulo(pRef->iIOOffset + 1, PACKET_WINDOW);
}
}
pLengths = (unsigned char *)pRef->aMultiBuf + uOffsetLengths;
pTypePos = (unsigned char *)pRef->aMultiBuf + uOffsetTypes;
uPos = uOffsetBuffer;
uCount = 0;
for (uIndex = 0; uIndex < uLastPlayer; uIndex++)
{
uType = (pTypesArray[uIndex] & ~GMDIST_DATA_CRC_REQUEST);
if (((uType == GMDIST_DATA_NONE) || (uType == GMDIST_DATA_DISCONNECT)) && !pRef->bActAsServer)
{
#if DIRTYCODE_LOGGING
NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY,"netgamedist: [%p] Exiting NetGameDistInputLocalMulti() because of invalid data type.\n", pRef));
#endif
return(GMDIST_INVALID);
}
// if sending multipackets, don<6F>t send input back to user who originally sent it
if (pRef->bActAsServer && (uIndex == pRef->uDistIndex))
{
continue;
}
// if sending sparse multipackets, skip sending blank inputs for users that aren't in the game
if (pRef->bSparse && ((pRef->aMetaInfoToSend[GMDIST_Modulo(pRef->uUpdateMetaInfoEnd - 1, GMDIST_META_ARRAY_SIZE)].uMask & (1 << uIndex)) == 0))
{
continue;
}
// put the data in
ds_memcpy(pRef->aMultiBuf + uPos, ppBuffer[uIndex], pLengthsArray[uIndex]);
uPos += pLengthsArray[uIndex];
if (pRef->bActAsServer)
{
if (uCount < (uPlayerCount - 2u))
{
// we assign the length here for normal packets
if (iLengthSize == 1)
{
pLengths[uCount] = pLengthsArray[uIndex];
}
else
{
// and for fat multipackets
pLengths[uCount * 2] = pLengthsArray[uIndex] / 256;
pLengths[uCount * 2 + 1] = pLengthsArray[uIndex] % 256;
}
}
}
if (uCount % 2)
{
*pTypePos = (*pTypePos & 0x0f) | ((pTypesArray[uIndex] << 4) & 0xf0);
pTypePos++;
}
else
{
*pTypePos = (pTypesArray[uIndex] & 0x0f);
}
uCount++;
}
if (!NetGameDistStatus(pRef, '?cws', uPos, NULL, 0))
{
#if DIRTYCODE_LOGGING
NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "netgamedist: [%p] Exiting NetGameDistInputLocalMulti() with return value GMDIST_OVERFLOW.\n", pRef));
#endif
return(GMDIST_OVERFLOW);
}
#if DIRTYCODE_LOGGING
{
uint8_t uDataType = (pTypesArray[0] & ~GMDIST_DATA_CRC_REQUEST);
NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "netgamedist: [%p] NetGameDistInputLocalMulti() called with tick=%d len=%d type=%s\n",
pRef, NetTick(), pLengthsArray[0], _strNetGameDistDataTypes[(((uDataType >= GMDIST_DATA_NONE) && (uDataType <= GMDIST_DATA_NODATA)) ? uDataType : 0)]));
}
#endif
// increment output packet count
if ((iResult = _NetGameDistSendInput(pRef, pRef->aMultiBuf, uPos, iLengthSize)) > 0)
{
pRef->iOutboundPktCnt++;
}
// pass the size of length used to the send input function
return(iResult);
}
/*F*************************************************************************************************/
/*!
\Function NetGameDistInputCheck
\Description
Check input status (see how long till next)
\Input *pRef - reference pointer
\Input *pSend - (optional) stores time until next packet should be sent
\Input *pRecv - (optional) stores whether data is available
\Version 12/20/00 (GWS)
*/
/*************************************************************************************************F*/
void NetGameDistInputCheck(NetGameDistRefT *pRef, int32_t *pSend, int32_t *pRecv)
{
int32_t iRemain;
uint32_t uTick;
int32_t iInData;
int32_t iOutData;
// call the status proc
(pRef->pStatProc)(pRef->pNetGameLinkRef, 'stat', 0, &pRef->NetGameLinkStats, sizeof(NetGameLinkStatT));
// get current time
uTick = NetTick();
// make sure next send time is initialized
if (pRef->uInpNext == 0)
{
pRef->uInpNext = uTick;
}
// see if its time to recalc network parms
if (uTick > pRef->uInpCalc)
{
// set the number of unacknowledged packets permitted
// (this number must be large enough to cover the network latency
pRef->iInputWind = (pRef->NetGameLinkStats.late+pRef->iInputRate)/pRef->iInputRate;
if (pRef->bRecvMulti)
{
pRef->iInputWind *= 2;
}
if (pRef->iInputWind < pRef->iInputMini)
{
pRef->iInputWind = pRef->iInputMini;
}
if (pRef->iInputWind > pRef->iInputMaxi)
{
pRef->iInputWind = pRef->iInputMaxi;
}
if (pRef->iInputWind > 500/pRef->iInputRate)
{
pRef->iInputWind = 500/pRef->iInputRate;
}
// determine time till next update
pRef->uInpCalc = uTick + pRef->iInputRate;
}
// figure out time remaining until next send
iRemain = pRef->uInpNext - uTick;
// clamp to valid rate
if (iRemain < 0)
{
iRemain = 0;
}
if (iRemain > pRef->iInputRate *2)
{
iRemain = pRef->iInputRate *2;
}
_NetGameDistCheckWindow(pRef, iRemain, uTick);
// figure out time till next send
iRemain = pRef->uInpNext - uTick;
// clamp to valid rate
if (iRemain < 0)
{
iRemain = 0;
}
// return time until next packet should be sent
if (pSend != NULL)
{
// make sure rate is set
if (pRef->iInputRate == 0)
{
// data is not initialized -- just return a delay
*pSend = 50;
}
else if ((pRef->OutBufData.iBeg != pRef->OutBufData.iEnd) || (!pRef->bRdyRecvRemote))
{
// dont send while something in output queue
*pSend = 10;
}
else
{
*pSend = iRemain;
}
}
// indicate if a packet is waiting
if (pRecv != NULL)
{
// if waiting on remote data, do an update
if (pRef->InpBufData.iBeg == pRef->InpBufData.iEnd)
{
NetGameDistUpdate(pRef);
}
// get input data queue length
iInData = GMDIST_Modulo(pRef->InpBufData.iEnd - pRef->InpBufData.iBeg, PACKET_WINDOW);
// get output data queue length (in multi-mode we assume one so we don't gate receiving due to an empty output queue)
iOutData = !pRef->bRecvMulti ? GMDIST_Modulo(pRef->OutBufData.iEnd - GMDIST_Modulo(pRef->InpBufData.iBeg + pRef->iIOOffset, PACKET_WINDOW), PACKET_WINDOW) : 1;
// write if data is available (non-zero=available)
*pRecv = (iInData < iOutData) ? iInData : iOutData;
}
#if DIRTYCODE_LOGGING
_NetGameDistInputCheckLog(pRef, pSend, pRecv);
#endif
}
/*F*************************************************************************************************/
/*!
\Function NetGameDistInputRate
\Description
Set the input rate
\Input *pRef - reference pointer
\Input iRate - new input rate
\Version 12/20/00 (GWS)
*/
/*************************************************************************************************F*/
void NetGameDistInputRate(NetGameDistRefT *pRef, int32_t iRate)
{
// save rate if changed
if (iRate > 0)
{
pRef->iInputRate = iRate;
}
}
/*F*************************************************************************************************/
/*!
\Function NetGameDistInputClear
\Description
Flush the input queue.
\Input *pRef - reference pointer
\Notes
This must be done with independent synchronization before and after to avoid issues.
\Version 12/20/00 (GWS)
*/
/*************************************************************************************************F*/
void NetGameDistInputClear(NetGameDistRefT *pRef)
{
// reset buffer pointers
pRef->OutBufData.iEnd = 0;
pRef->OutBufData.iBeg = 0;
pRef->InpBufData.iEnd = 0;
pRef->InpBufData.iBeg = 0;
// reset sequence numbers
pRef->uLocalSeq = 0;
pRef->uGlobalSeq = 0;
// reset the iooffset between the two queues.
pRef->iIOOffset = 0;
// reset the send time
pRef->uInpNext = NetTick();
}
/*F*************************************************************************************************/
/*!
\Function NetGameDistControl
\Description
Set NetGameDist operation parameters
\Input *pRef - reference pointer
\Input iSelect - item to tweak
\Input iValue - tweak value
\Input pValue - pointer tweak value
\Output
int32_t - selector specific
\Notes
Selectors are:
\verbatim
SELECTOR DESCRIPTION
'clri' clear the local queue of inputs
'crcs' enable/disable the reception of CRC challenges
'lcrc' to provide the current CRC as request by NetGameDist
'lrcv' local recv flow control. Set whether we are ready to receive or not.
'lsnd' local send flow control. Set whether we are ready to send or not.
'maxi' max window size clamp
'mini' min window size clamp
'spam' sets debug output verbosity (0...n)
\endverbatim
\Version 11/18/08 (jrainy)
*/
/*************************************************************************************************F*/
int32_t NetGameDistControl(NetGameDistRefT *pRef, int32_t iSelect, int32_t iValue, void *pValue)
{
if (iSelect == 'clri')
{
NetGameDistInputClear(pRef);
return(0);
}
if (iSelect == 'crcs')
{
pRef->bCRCChallenges = iValue;
return(0);
}
if (iSelect == 'lcrc')
{
pRef->bUpdateFlowCtrl = TRUE;
pRef->uLocalCRC = iValue;
pRef->bLocalCRCValid = TRUE;
return(0);
}
if (iSelect == 'lrcv')
{
if (iValue != pRef->bRdyRecv)
{
pRef->bUpdateFlowCtrl = TRUE;
pRef->bRdyRecv = iValue;
NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "netgamedist: [%p] flow control change -> %s to receive\n", pRef, (pRef->bRdyRecv ? "ready" : "not ready")));
}
return(pRef->bRdyRecv);
}
if (iSelect == 'lsnd')
{
if (iValue != pRef->bRdySend)
{
pRef->bUpdateFlowCtrl = TRUE;
pRef->bRdySend = iValue;
NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "netgamedist: [%p] flow control change -> %s to send\n", pRef, (pRef->bRdySend ? "ready" : "not ready")));
}
return(pRef->bRdySend);
}
if (iSelect == 'maxi')
{
if (iValue >= 0)
{
pRef->iInputMaxi = iValue;
}
return(pRef->iInputMaxi);
}
if (iSelect == 'mini')
{
if (iValue >= 0)
{
pRef->iInputMini = iValue;
}
return(pRef->iInputMini);
}
if (iSelect == 'spam')
{
pRef->uVerbose = iValue;
return(0);
}
// no action
return(-1);
}
/*F*************************************************************************************************/
/*!
\Function NetGameDistUpdate
\Description
Perform periodic tasks.
\Input *pRef - reference pointer
\Output
uint32_t - current sequence number
\Notes
Application must call this every 100ms or so.
\Version 12/20/00 (GWS)
*/
/*************************************************************************************************F*/
uint32_t NetGameDistUpdate(NetGameDistRefT *pRef)
{
NetGamePacketT* pPacket = (NetGamePacketT*)&pRef->MaxPkt;
unsigned char *pData;
int32_t iNext, iCurrent;
uint32_t uIndex, uPos;
unsigned char *pExisting;
unsigned char *pIncoming;
// update the input rate if needed
NetGameDistInputRate(pRef, 0);
// send any pending game packets
NetGameDistInputLocal(pRef, NULL, 0);
// grab incoming packets
while (( GMDIST_Modulo(pRef->InpBufData.iEnd + 1, PACKET_WINDOW) != pRef->InpBufData.iBeg) && (_NetGameDistInputPeekRecv(pRef, pPacket, 1) > 0))
{
// dispatch the packet according to type
if ((pPacket->head.kind == GAME_PACKET_INPUT) ||
(pPacket->head.kind == GAME_PACKET_INPUT_MULTI) ||
(pPacket->head.kind == GAME_PACKET_INPUT_MULTI_FAT))
{
pRef->iInboundPktCnt++;
iNext = GMDIST_Modulo(pRef->InpBufData.iEnd + 1, PACKET_WINDOW);
if (pRef->InpBufData.iBeg != pRef->InpBufData.iEnd && !pRef->bRecvMulti && pRef->bActAsServer && pRef->pDropProc)
{
iCurrent = GMDIST_Modulo(pRef->InpBufData.iEnd - 1, PACKET_WINDOW);
pExisting = (pRef->InpBufData.pControllerIO + pRef->InpBufData.IOLookUp[iCurrent].uPos);
pIncoming = pPacket->body.data;
if (pRef->pDropProc(pRef, pExisting + 1, pIncoming + 1, pExisting[0], pIncoming[0]))
{
iNext = pRef->InpBufData.iEnd;
pRef->iInboundDropPktCnt++;
pRef->InpBufData.iEnd = GMDIST_Modulo(pRef->InpBufData.iEnd - 1, PACKET_WINDOW);
}
}
_ProcessPacketType(pRef, pPacket->head.kind);
// check for overflow
if (iNext == pRef->InpBufData.iBeg)
{
// ignore the packet
NetPrintf(("netgamedist: [%p] NetGameDistUpdate() - buffer overflow (queue full)!\n", pRef));
ds_snzprintf(pRef->strErrorText, sizeof(pRef->strErrorText), "NetGameDistUpdate, buffer overflow (queue full).");
pRef->iErrCond = GMDIST_QUEUE_FULL;
return((unsigned)(-1));
}
else
{
// point to data buffer
// we currently write one byte past the buffer after reserving one extra byte
// the len set in the data structure includes this extra byte past the packet data
if (_SetDataPtr(pRef, &pRef->InpBufData, pPacket->head.len + 1))
{
pData = pRef->InpBufData.pControllerIO+pRef->InpBufData.IOLookUp[pRef->InpBufData.iEnd].uPos;
pRef->InpBufData.IOLookUp[pRef->InpBufData.iEnd].uLen = pPacket->head.len + 1;
// copy into buffer
ds_memcpy(pData, pPacket->body.data, pPacket->head.len);
pData[pPacket->head.len] = pPacket->head.kind;
pRef->aPacketId[pRef->InpBufData.iEnd] = pRef->iInboundPktCnt;
// update queue time
pRef->InpBufData.IOLookUp[pRef->InpBufData.iEnd].uInsertTime = pPacket->head.when;
// add item to buffer
pRef->InpBufData.iEnd = iNext;
// display number of packets in buffer
#if TIMING_DEBUG
NetPrintf(("netgamedist: [%p] NetGameDistUpdate() inpbuf=%d packets\n", pRef,
GMDIST_Modulo(pRef->InpBufData.iEnd - pRef->InpBufData.iBeg, PACKET_WINDOW)));
#endif
}
else
{
NetPrintf(("netgamedist: [%p] NetGameDistUpdate() - buffer overflow (memory)!\n", pRef));
pRef->iErrCond = GMDIST_QUEUE_MEMORY;
return((unsigned)(-1));
}
}
}
else if (pPacket->head.kind == GAME_PACKET_STATS)
{
// clear the contents, if a player leaves the game we don't want their stats to be misrepresented
ds_memclr(pRef->aRecvStats, sizeof(pRef->aRecvStats));
for (uPos = 0, uIndex = 0; uPos < pPacket->head.len; uIndex += 1)
{
ds_memcpy(&pRef->aRecvStats[uIndex], pPacket->body.data + uPos, sizeof(*pRef->aRecvStats) );
uPos += sizeof(*pRef->aRecvStats);
pRef->aRecvStats[uIndex].late = SocketNtohs(pRef->aRecvStats[uIndex].late);
pRef->aRecvStats[uIndex].bps = SocketNtohs(pRef->aRecvStats[uIndex].bps);
}
}
else if (pPacket->head.kind == GAME_PACKET_INPUT_FLOW)
{
pRef->bRdySendRemote = pPacket->body.data[0];
pRef->bRdyRecvRemote = pPacket->body.data[1];
if (pPacket->head.len >= 7)
{
if (pRef->MaxPkt.body.data[2])
{
pRef->bRemoteCRCValid = TRUE;
pRef->uRemoteCRC = (pRef->MaxPkt.body.data[6] |
(pRef->MaxPkt.body.data[5] << 8) |
(pRef->MaxPkt.body.data[4] << 16) |
(pRef->MaxPkt.body.data[3] << 24));
}
#if DIRTYCODE_LOGGING
NetPrintfVerbose((pRef->uVerbose, NETGAMEDIST_VERBOSITY, "netgamedist: [%p] bRemoteCRCValid is %d, uRemoteCRC is %d\n", pRef, pRef->bRemoteCRCValid, pRef->uRemoteCRC));
#endif
}
NetPrintf(("netgamedist: [%p] Got GAME_PACKET_INPUT_FLOW (send %d recv %d)\n", pRef, pRef->bRdySendRemote, pRef->bRdyRecvRemote));
}
else if (pPacket->head.kind == GAME_PACKET_INPUT_META)
{
// process meta-information about sparse multi-packets
uint8_t uVer;
uint32_t uMask;
pRef->bGotMetaInfo = TRUE;
uVer = GMDIST_Modulo(pPacket->body.data[0], GMDIST_META_ARRAY_SIZE);
uMask = ((pPacket->body.data[1] << 24) +
(pPacket->body.data[2] << 16) +
(pPacket->body.data[3] << 8) +
(pPacket->body.data[4]));
NetPrintf(("netgamedist: Got GAME_PACKET_INPUT_META (version %d mask 0x%08x)\n", uVer, uMask));
pRef->aMetaInfo[uVer].uMask = uMask;
pRef->aMetaInfo[uVer].uPlayerCount = _NetGameDistCountBits(pRef, uMask);
pRef->aMetaInfo[uVer].uVer = uVer;
}
}
// update link status
NetGameDistStatus(pRef, 'stat', 0, NULL, 0);
// show the history
#if PING_DEBUG
_PingHistory(&pRef->NetGameLinkStats);
#endif
// send meta-information as needed
if (pRef->uUpdateMetaInfoBeg != pRef->uUpdateMetaInfoEnd)
{
_NetGameDistSendMetaInfo(pRef);
}
// send flow control updates as needed
if (pRef->bUpdateFlowCtrl)
{
_NetGameDistSendFlowUpdate(pRef);
}
// return current sequence number
return(pRef->uGlobalSeq);
}
/*F*************************************************************************************************/
/*!
\Function NetGameDistSetProc
\Description
Sets or override the various procedure pointers.
\Input *pRef - reference pointer
\Input iKind - kind
\Input *pProc - proc
\Notes
'drop' should only be used on game servers
\Version 02/27/07 (jrainy)
*/
/*************************************************************************************************F*/
void NetGameDistSetProc(NetGameDistRefT *pRef, int32_t iKind, void *pProc)
{
switch(iKind)
{
case 'drop':
pRef->pDropProc = (NetGameDistDropProc *)pProc;
break;
case 'peek':
pRef->pPeekProc = (NetGameDistPeekProc *)pProc;
break;
case 'recv':
pRef->pRecvProc = (NetGameDistRecvProc *)pProc;
break;
case 'send':
pRef->pSendProc = (NetGameDistSendProc *)pProc;
break;
case 'stat':
pRef->pStatProc = (NetGameDistStatProc *)pProc;
break;
case 'link':
pRef->pLinkCtrlProc = (NetGameDistLinkCtrlProc *)pProc;
break;
}
}
/*F*************************************************************************************************/
/*!
\Function NetGameDistSendStats
\Description
Send stats. Meant to be used by the game server to send stats regarding all clients
\Input *pRef - reference pointer
\Input *pStats - uTotalPlyrs-sized array of stats
\Version 03/14/07 (jrainy)
*/
/*************************************************************************************************F*/
void NetGameDistSendStats(NetGameDistRefT *pRef, NetGameDistStatT *pStats)
{
uint32_t uIndex;
int32_t iPos;
pRef->MaxPkt.head.kind = GAME_PACKET_STATS;
pRef->MaxPkt.head.len = (uint16_t)sizeof(NetGameDistStatT) * pRef->uTotalPlyrs;
iPos = 0;
for (uIndex = 0; uIndex<pRef->uTotalPlyrs; uIndex++)
{
ds_memcpy(pRef->MaxPkt.body.data + iPos, &pStats[uIndex], sizeof(NetGameDistStatT));
iPos += sizeof(NetGameDistStatT);
}
pRef->pSendProc(pRef->pNetGameLinkRef, (NetGamePacketT*)&pRef->MaxPkt, 1);
}
/*F*************************************************************************************************/
/*!
\Function NetGameDistGetError
\Description
return non-zero if there ever was an overflow
\Input *pRef - reference pointer
\Output
int32_t - 0 if there was no error, positive >0 otherwise.
\Version 03/23/07 (jrainy)
*/
/*************************************************************************************************F*/
int32_t NetGameDistGetError(NetGameDistRefT *pRef)
{
return(pRef->iErrCond);
}
/*F*************************************************************************************************/
/*!
\Function NetGameDistGetErrorText
\Description
copies into the passed buffer the last error text.
\Input *pRef - reference pointer
\Input *pErrorBuffer - buffer to write into
\Input iBufSize - buf size
\Version 08/15/08 (jrainy)
*/
/*************************************************************************************************F*/
void NetGameDistGetErrorText(NetGameDistRefT *pRef, char *pErrorBuffer, int32_t iBufSize)
{
int32_t iMinSize = sizeof(pRef->strErrorText);
if (iBufSize < iMinSize)
{
iMinSize = iBufSize;
}
strncpy(pErrorBuffer, pRef->strErrorText, iMinSize);
pErrorBuffer[iMinSize - 1] = 0;
}
/*F*************************************************************************************************/
/*!
\Function NetGameDistResetError
\Description
reset the overflow error condition to 0.
\Input *pRef - reference pointer
\Version 03/23/07 (jrainy)
*/
/*************************************************************************************************F*/
void NetGameDistResetError(NetGameDistRefT *pRef)
{
pRef->iErrCond = 0;
}