mirror of
https://github.com/Mauler125/r5sdk.git
synced 2025-02-09 19:15:03 +01:00
DirtySDK (EA's Dirty Sockets library) will be used for the LiveAPI implementation, and depends on: EABase, EAThread.
1652 lines
58 KiB
C
1652 lines
58 KiB
C
/*H********************************************************************************/
|
|
/*!
|
|
\File netgamedistserv.c
|
|
|
|
\Description
|
|
Server module to handle 2+ NetGameDist connections in a client/server
|
|
architecture.
|
|
|
|
\Copyright
|
|
Copyright (c) 2007 Electronic Arts Inc.
|
|
|
|
\Version 02/01/2007 (jbrookes) First Version
|
|
*/
|
|
/********************************************************************************H*/
|
|
|
|
/*** Include files ****************************************************************/
|
|
|
|
#include <string.h>
|
|
|
|
#include "DirtySDK/dirtysock.h"
|
|
#include "DirtySDK/dirtysock/dirtymem.h"
|
|
#include "DirtySDK/game/netgamepkt.h"
|
|
#include "DirtySDK/game/netgamedistserv.h"
|
|
|
|
/*** Defines **********************************************************************/
|
|
|
|
#define MONITOR_HIGHWATER (0)
|
|
#define DISTSERV_MAX_MULTIPACKET_LENGTH (1100)
|
|
#define DISTSERV_MAX_FIXEDRATE_MULTIPLE (3)
|
|
#define DISTSERV_DEFAULT_FIXEDRATE (33)
|
|
|
|
/*** Type Definitions *************************************************************/
|
|
|
|
//! client state
|
|
typedef struct NetGameDistServClientT
|
|
{
|
|
NetGameDistRefT *pGameDist; //!< client dist ref
|
|
char strName[32]; //!< client name
|
|
uint8_t aLocalInput[NETGAME_DATAPKT_MAXSIZE]; //!< local input buffer (TODO - this will be a queue)
|
|
int32_t iLocalInputSize; //!< size of input data
|
|
int32_t iPeekKey; //!< delta at the time of our peek.
|
|
uint8_t bLocalInput; //!< whether we have local input or not
|
|
uint8_t iLocalInputType; //!< type of input
|
|
uint8_t bInitialized; //!< true if client is initialized
|
|
uint8_t bDisconnected; //!< whether the client is disconnected or not
|
|
int32_t iDisconnectReason; //!< why the disconnect
|
|
int32_t iCount; //!< total number of packets received
|
|
int32_t iCRC; //!< latest client-side CRC received
|
|
uint8_t uCRCValid; //!< whether we got a CRC response to the last request
|
|
uint8_t bRemoteRcv; //!< last 'rrcv' value (is client ready to recv)
|
|
uint8_t bRemoteSnd; //!< last 'rsnd' value (is client ready to send)
|
|
uint8_t bReallyPeeked; //!< indicate whether we peeked this client before InputLocalMulti this frame (to address delayed input scenarios)
|
|
uint32_t uNakSent; //!< previous nak sent stat
|
|
uint32_t uPackLost; //!< previous packet loss stat
|
|
char strDiscReason[GMDIST_ERROR_SIZE];
|
|
} NetGameDistServClientT;
|
|
|
|
//! client list
|
|
typedef struct NetGameDistServClientListT
|
|
{
|
|
int32_t iNumClients; //!< current number of clients in list
|
|
int32_t iMaxClients; //!< max number of clients in list
|
|
NetGameDistServClientT Clients[1]; //!< variable-length array of clients
|
|
} NetGameDistServClientListT;
|
|
|
|
//! module state
|
|
struct NetGameDistServT
|
|
{
|
|
//! module memory group
|
|
int32_t iMemGroup;
|
|
void *pMemGroupUserData;
|
|
|
|
//! verbose debug output level
|
|
int32_t iVerbosity;
|
|
|
|
//! Last stats update time
|
|
uint32_t uLastStatsUpdate;
|
|
|
|
// debugging
|
|
uint32_t aPeekTimes[32];
|
|
uint32_t aRecvTimes[32];
|
|
uint32_t aSendTimes[32];
|
|
uint32_t aEscalate[32];
|
|
int32_t iFixedRate;
|
|
|
|
uint32_t uNbFramesNoData;
|
|
|
|
uint32_t uCRCRate; //!< number of frames between challenges
|
|
uint32_t uCRCResponseLimit; //!< number of frames allowed to receive response
|
|
uint32_t uCRCRemainingFrames; //!< number of frames remaining until next challenge
|
|
uint32_t uCRCResponseCountdown; //!< number of frames remaining to accept CRC responses
|
|
|
|
uint8_t uSendThreshold;
|
|
|
|
#if MONITOR_HIGHWATER
|
|
int32_t iHighWaterInputQueue;
|
|
int32_t iHighWaterOutputQueue;
|
|
uint8_t uHighWaterChanged;
|
|
#endif
|
|
|
|
int32_t iFirstClient;
|
|
uint32_t uLastFlowUpdateTime;
|
|
uint32_t uOutputMultiPacketCount; //!< total output dist multi packet
|
|
uint32_t uCurStatClientCount; //!< current stat client count
|
|
uint8_t bClientCountChanged; //!< did the client count change between stat sampling interval?
|
|
uint8_t bFlowEnabled; //!< whether all clients are ready to receive.
|
|
uint8_t bFlowEnabledChanged; //!< did flow control change between stat sampling interval?
|
|
uint8_t bNoInputMode; //!< TRUE means: outbound multipackets are no longer generated because inbound packets have not been coming in for multiple frames
|
|
uint8_t bNoInputModeChanged; //!< did no input mode change between stat sampling interval?
|
|
uint8_t uMultiPacketVersion; //!< sparse multi-packet version number. Changes when player list changes
|
|
uint8_t _pad[2];
|
|
|
|
NetGameDistServLoggingCbT *pLoggingCb; //!< logging callback
|
|
void *pUserData; //!< logging userdata
|
|
|
|
//! variable-length array of clients -- must come last!
|
|
NetGameDistServClientListT ClientList;
|
|
};
|
|
|
|
/*** Variables ********************************************************************/
|
|
|
|
/*** Private Functions ************************************************************/
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function _NetGameDistServLogPrintf
|
|
|
|
\Description
|
|
Log printing for netgamedistserv module
|
|
|
|
\Input *pDistServ - module state
|
|
\Input *pFormat - printf format string
|
|
\Input ... - variable argument list
|
|
|
|
\Version 06/26/2019 (eesponda)
|
|
*/
|
|
/********************************************************************************F*/
|
|
static void _NetGameDistServLogPrintf(NetGameDistServT *pDistServ, const char *pFormat, ...)
|
|
{
|
|
char strText[2048];
|
|
int32_t iOffset = 0;
|
|
va_list Args;
|
|
|
|
// format prefix
|
|
iOffset += ds_snzprintf(strText+iOffset, sizeof(strText)-iOffset, "netgamedistserv: ");
|
|
|
|
// format output
|
|
va_start(Args, pFormat);
|
|
iOffset += ds_vsnprintf(strText+iOffset, sizeof(strText)-iOffset, pFormat, Args);
|
|
va_end(Args);
|
|
|
|
// forward to callback if registered
|
|
if ((pDistServ != NULL) && (pDistServ->pLoggingCb != NULL))
|
|
{
|
|
pDistServ->pLoggingCb(strText, pDistServ->pUserData);
|
|
}
|
|
else
|
|
{
|
|
NetPrintf(("%s", strText));
|
|
}
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function _NetGameDistServLogPrintfVerbose
|
|
|
|
\Description
|
|
Log printing for netgamedistserv module at varying verbosity levels
|
|
|
|
\Input *pDistServ - module state
|
|
\Input iCheckLevel - the level we are checking our internal iVerbosity against
|
|
\Input *pFormat - printf format string
|
|
\Input ... - variable argument list
|
|
|
|
\Version 06/26/2019 (eesponda)
|
|
*/
|
|
/********************************************************************************F*/
|
|
static void _NetGameDistServLogPrintfVerbose(NetGameDistServT *pDistServ, int32_t iCheckLevel, const char *pFormat, ...)
|
|
{
|
|
char strText[2048];
|
|
va_list Args;
|
|
|
|
// no-op
|
|
if (pDistServ->iVerbosity <= iCheckLevel)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// format output
|
|
va_start(Args, pFormat);
|
|
ds_vsnprintf(strText, sizeof(strText), pFormat, Args);
|
|
va_end(Args);
|
|
|
|
// format to the logging function
|
|
_NetGameDistServLogPrintf(pDistServ, "%s", strText);
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function _NetGameDistServSanityCheckIn
|
|
|
|
\Description
|
|
Perform sanity checking on input.
|
|
|
|
\Input *pDistServ - module state
|
|
\Input iClient - client to check
|
|
\Input iRet - result from NetGameDistInputPeek()
|
|
|
|
\Version 01/01/2007 (jrainy)
|
|
*/
|
|
/********************************************************************************F*/
|
|
static void _NetGameDistServSanityCheckIn(NetGameDistServT *pDistServ, int32_t iClient, int32_t iRet)
|
|
{
|
|
int32_t iDelay;
|
|
uint32_t uCurTick;
|
|
|
|
uCurTick = NetTick();
|
|
|
|
// no need to perform sanity checking on input when flow is disabled
|
|
if (pDistServ->bFlowEnabled)
|
|
{
|
|
iDelay = uCurTick - pDistServ->aRecvTimes[iClient];
|
|
if (iRet > 0)
|
|
{
|
|
pDistServ->aRecvTimes[iClient] = uCurTick;
|
|
|
|
if (iDelay < 200)
|
|
{
|
|
pDistServ->aEscalate[iClient] = 0;
|
|
}
|
|
}
|
|
if ((iDelay > 200) && pDistServ->aRecvTimes[iClient] && (pDistServ->aEscalate[iClient] < 1))
|
|
{
|
|
_NetGameDistServLogPrintfVerbose(pDistServ, 0, "no input in 200 ms from client %d\n", iClient);
|
|
pDistServ->aEscalate[iClient] = 1;
|
|
}
|
|
else if ((iDelay > 500) && pDistServ->aRecvTimes[iClient] && (pDistServ->aEscalate[iClient] < 2))
|
|
{
|
|
_NetGameDistServLogPrintfVerbose(pDistServ, 0, "no input in 500 ms from client %d\n", iClient);
|
|
pDistServ->aEscalate[iClient] = 2;
|
|
}
|
|
else if ((iDelay > 1000) && pDistServ->aRecvTimes[iClient])
|
|
{
|
|
_NetGameDistServLogPrintfVerbose(pDistServ, 0, "no input in 1000 ms from client %d\n", iClient);
|
|
pDistServ->aRecvTimes[iClient] = uCurTick;
|
|
pDistServ->aEscalate[iClient] = 0;
|
|
}
|
|
|
|
iDelay = uCurTick - pDistServ->aPeekTimes[iClient];
|
|
pDistServ->aPeekTimes[iClient] = uCurTick;
|
|
if (iDelay > (pDistServ->iFixedRate * DISTSERV_MAX_FIXEDRATE_MULTIPLE))
|
|
{
|
|
_NetGameDistServLogPrintfVerbose(pDistServ, 0, "no peek in %d ms from client %d\n", iDelay, iClient);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function _NetGameDistServSanityCheckOut
|
|
|
|
\Description
|
|
Perform sanity checking on output.
|
|
|
|
\Input *pDistServ - module state
|
|
\Input iClient - client to check
|
|
\Input iRet - result from NetGameDistInputLocalMulti()
|
|
|
|
\Version 01/01/2007 (jrainy)
|
|
*/
|
|
/********************************************************************************F*/
|
|
static void _NetGameDistServSanityCheckOut(NetGameDistServT *pDistServ, int32_t iClient, int32_t iRet)
|
|
{
|
|
int32_t iDelay;
|
|
uint32_t uCurTick;
|
|
|
|
uCurTick = NetTick();
|
|
|
|
iDelay = uCurTick - pDistServ->aSendTimes[iClient];
|
|
if (iRet == 1)
|
|
{
|
|
pDistServ->aSendTimes[iClient] = uCurTick;
|
|
}
|
|
if ((iDelay > (pDistServ->iFixedRate * DISTSERV_MAX_FIXEDRATE_MULTIPLE)) && pDistServ->aSendTimes[iClient])
|
|
{
|
|
_NetGameDistServLogPrintfVerbose(pDistServ, 0, "no send in %d ms to client %d\n", iDelay, iClient);
|
|
pDistServ->aSendTimes[iClient] = uCurTick;
|
|
}
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function _NetGameDistServFlowControl
|
|
|
|
\Description
|
|
Send flow control update from the server as appropriate
|
|
|
|
\Input *pDistServ - module state
|
|
|
|
\Version 12/03/2007 (jrainy)
|
|
*/
|
|
/********************************************************************************F*/
|
|
static void _NetGameDistServFlowControl(NetGameDistServT *pDistServ)
|
|
{
|
|
int32_t iClient;
|
|
NetGameDistServClientT *pDistClient;
|
|
uint8_t bPrevFlowEnabled; //!< previous reported stat flow enabled boolean
|
|
|
|
bPrevFlowEnabled = pDistServ->bFlowEnabled;
|
|
pDistServ->bFlowEnabled = TRUE;
|
|
|
|
for (iClient = 0; iClient < pDistServ->ClientList.iMaxClients; iClient++)
|
|
{
|
|
pDistClient = &pDistServ->ClientList.Clients[iClient];
|
|
|
|
if (pDistClient->bInitialized && !pDistClient->bDisconnected)
|
|
{
|
|
if (!pDistClient->bRemoteRcv)
|
|
{
|
|
pDistServ->bFlowEnabled = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
_NetGameDistServLogPrintf(pDistServ, "sending flow update lrcv %d\n", pDistServ->bFlowEnabled);
|
|
for (iClient = 0; iClient < pDistServ->ClientList.iMaxClients; iClient++)
|
|
{
|
|
pDistClient = &pDistServ->ClientList.Clients[iClient];
|
|
|
|
if (pDistClient->bInitialized && !pDistClient->bDisconnected)
|
|
{
|
|
NetGameDistControl(pDistClient->pGameDist, 'lrcv', pDistServ->bFlowEnabled, NULL);
|
|
}
|
|
}
|
|
|
|
/* If flow control changed, flag the boolean used to remember that it happened at least once
|
|
during the metrics sampling interval. */
|
|
if (bPrevFlowEnabled != pDistServ->bFlowEnabled)
|
|
{
|
|
if (pDistServ->bFlowEnabledChanged == FALSE)
|
|
{
|
|
_NetGameDistServLogPrintfVerbose(pDistServ, 1, "metric sampling interval marked invalid for idpps and inputdrop because flow control changed (flow enabled = %d)\n", pDistServ->bFlowEnabled);
|
|
}
|
|
pDistServ->bFlowEnabledChanged = TRUE;
|
|
}
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function _NetGameDistServDiscClient
|
|
|
|
\Description
|
|
Destroy game dist refs.
|
|
|
|
\Input *pDistServ - module state
|
|
\Input *pDistClient - client to disconnect from
|
|
\Input iDistClient - index of client
|
|
\Input iReason - disconnection reason
|
|
|
|
\Version 04/26/2007 (jbrookes)
|
|
*/
|
|
/********************************************************************************F*/
|
|
static void _NetGameDistServDiscClient(NetGameDistServT *pDistServ, NetGameDistServClientT *pDistClient, int32_t iDistClient, int32_t iReason)
|
|
{
|
|
// mark as disconnected
|
|
if (!pDistClient->bDisconnected)
|
|
{
|
|
_NetGameDistServLogPrintf(pDistServ, "disconnecting from client %d\n", iDistClient);
|
|
pDistClient->bDisconnected = TRUE;
|
|
pDistClient->iDisconnectReason = iReason;
|
|
}
|
|
else if (pDistClient->pGameDist != NULL)
|
|
{
|
|
_NetGameDistServLogPrintf(pDistServ, "warning -- client %d has gamedist but is disconnected\n", iDistClient);
|
|
}
|
|
|
|
// destroy dist
|
|
if (pDistClient->pGameDist != NULL)
|
|
{
|
|
NetGameDistGetErrorText(pDistClient->pGameDist, pDistClient->strDiscReason, sizeof(pDistClient->strDiscReason));
|
|
|
|
_NetGameDistServLogPrintfVerbose(pDistServ, 0, "deleting dist ref for client %s/%d\n", pDistClient->strName, iDistClient);
|
|
NetGameDistDestroy(pDistClient->pGameDist);
|
|
pDistClient->pGameDist = NULL;
|
|
}
|
|
|
|
_NetGameDistServFlowControl(pDistServ);
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function _NetGameDistUpdateMulti
|
|
|
|
\Description
|
|
Update Multi configuration when a client is added or removed
|
|
|
|
\Input *pDistServ - module state
|
|
|
|
\Version 02/13/2007 (jbrookes)
|
|
*/
|
|
/********************************************************************************F*/
|
|
static void _NetGameDistUpdateMulti(NetGameDistServT *pDistServ)
|
|
{
|
|
NetGameDistServClientT *pDistClient;
|
|
int32_t iClient;
|
|
int32_t iLastClient = 0;
|
|
uint32_t uMask = 0;
|
|
|
|
for (iClient = 0; iClient < pDistServ->ClientList.iMaxClients; iClient++)
|
|
{
|
|
pDistClient = &pDistServ->ClientList.Clients[iClient];
|
|
if (pDistClient->bInitialized)
|
|
{
|
|
iLastClient = iClient;
|
|
|
|
// set the bits in the mask for all initialized players
|
|
uMask |= (1 << iClient);
|
|
}
|
|
}
|
|
|
|
// increment the multipacket version
|
|
pDistServ->uMultiPacketVersion++;
|
|
|
|
for (iClient = 0; iClient < pDistServ->ClientList.iMaxClients; iClient++)
|
|
{
|
|
pDistClient = &pDistServ->ClientList.Clients[iClient];
|
|
if (pDistClient->pGameDist != NULL)
|
|
{
|
|
_NetGameDistServLogPrintf(pDistServ, "issuing NetGameDistMultiSetup %d %d\n", iClient, iLastClient + 1);
|
|
NetGameDistMultiSetup(pDistClient->pGameDist, iClient, iLastClient + 1);
|
|
|
|
_NetGameDistServLogPrintf(pDistServ, "issuing NetGameDistMetaSetup 1 0x%0x %d\n", uMask, pDistServ->uMultiPacketVersion);
|
|
NetGameDistMetaSetup(pDistClient->pGameDist, 1, uMask, pDistServ->uMultiPacketVersion);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function _NetGameDistServDrop
|
|
|
|
\Description
|
|
Default implementation of drop function.
|
|
|
|
\Input *pLinkRef - module state
|
|
\Input *pExisting - input packet at end of the input queue
|
|
\Input *pIncoming - newly received input packet
|
|
\Input uTypeExisting - type of the packet in the queue
|
|
\Input uTypeIncoming - type of the new packet
|
|
|
|
\Output
|
|
int8_t - TRUE if the input packet can be dropped, FALSE otherwise
|
|
|
|
\Version 02/27/2007 (jrainy)
|
|
*/
|
|
/********************************************************************************F*/
|
|
static int8_t _NetGameDistServDrop(void *pLinkRef, void *pExisting, void *pIncoming, uint8_t uTypeExisting, uint8_t uTypeIncoming)
|
|
{
|
|
return(uTypeExisting == GMDIST_DATA_INPUT_DROPPABLE);
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function _NetGameDistServHandleCRCResponses
|
|
|
|
\Description
|
|
Handle the CRC responses. Check the current response status of all clients
|
|
|
|
\Input *pDistServ - module state
|
|
|
|
\Notes
|
|
Can only compare 64 different CRCs. If we have over 64 clients, the function
|
|
will still function correctly, as long as there is more than 64 different
|
|
CRCs being returned by the clients (highly unlikely). If there's too many
|
|
different CRCs received, we'll pick the one with majority from the 64 first
|
|
clients.
|
|
|
|
\Version 03/23/2009 (jrainy)
|
|
*/
|
|
/********************************************************************************F*/
|
|
static void _NetGameDistServHandleCRCResponses(NetGameDistServT *pDistServ)
|
|
{
|
|
NetGameDistServClientT *pDistClient;
|
|
int32_t iClient,iIndex;
|
|
uint8_t uWaiting = FALSE;
|
|
uint8_t uTied = FALSE;
|
|
int32_t iCRCs[64] = {0};
|
|
int32_t iOccurences[64] = {0};
|
|
int32_t iBestCRC = 0;
|
|
int32_t iBestOccurences = 0;
|
|
|
|
// for all the current clients
|
|
for (iClient = 0; iClient < pDistServ->ClientList.iMaxClients; iClient++)
|
|
{
|
|
pDistClient = &pDistServ->ClientList.Clients[iClient];
|
|
|
|
// skip uninitialized/disconnected clients
|
|
if (!pDistClient->bInitialized || pDistClient->bDisconnected)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// mark us waiting if this client didn't send its CRC yet
|
|
if (!pDistClient->uCRCValid)
|
|
{
|
|
uWaiting = TRUE;
|
|
}
|
|
else
|
|
{
|
|
// For all the CRC seen already
|
|
for(iIndex = 0; iIndex < 64; iIndex++)
|
|
{
|
|
// count the repeats
|
|
if (pDistClient->iCRC == iCRCs[iIndex])
|
|
{
|
|
iOccurences[iIndex]++;
|
|
|
|
if (iOccurences[iIndex] > iBestOccurences)
|
|
{
|
|
iBestOccurences = iOccurences[iIndex];
|
|
iBestCRC = iCRCs[iIndex];
|
|
}
|
|
|
|
break;
|
|
}
|
|
else if (iOccurences[iIndex] == 0)
|
|
{
|
|
// remember the newly seen CRCs
|
|
iCRCs[iIndex] = pDistClient->iCRC;
|
|
iOccurences[iIndex] = 1;
|
|
|
|
if (iOccurences[iIndex] > iBestOccurences)
|
|
{
|
|
iBestOccurences = iOccurences[iIndex];
|
|
iBestCRC = iCRCs[iIndex];
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// if the wait is over, (received all responses, or number of frames)
|
|
if (!uWaiting || (pDistServ->uCRCResponseCountdown == 0))
|
|
{
|
|
for(iIndex = 0; iIndex < 64; iIndex++)
|
|
{
|
|
if ((iOccurences[iIndex] == iBestOccurences) && (iCRCs[iIndex] != iBestCRC))
|
|
{
|
|
uTied = TRUE;
|
|
}
|
|
}
|
|
|
|
for (iClient = 0; iClient < pDistServ->ClientList.iMaxClients; iClient++)
|
|
{
|
|
pDistClient = &pDistServ->ClientList.Clients[iClient];
|
|
|
|
// skip disconnected clients
|
|
if (!pDistClient->bInitialized || pDistClient->bDisconnected)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!pDistClient->uCRCValid || (pDistClient->iCRC != iBestCRC) || uTied)
|
|
{
|
|
_NetGameDistServLogPrintf(pDistServ, "client %d failed CRC challenge: %d %d %d %d\n", iClient, pDistClient->uCRCValid, pDistClient->iCRC, iBestCRC, uTied);
|
|
|
|
if (uTied)
|
|
{
|
|
_NetGameDistServDiscClient(pDistServ, pDistClient, iClient, GMDIST_DESYNCED_ALL_PLAYERS);
|
|
}
|
|
else
|
|
{
|
|
_NetGameDistServDiscClient(pDistServ, pDistClient, iClient, GMDIST_DESYNCED);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_NetGameDistServLogPrintfVerbose(pDistServ, 2, "client %d passed CRC challenge: %d\n", iClient, pDistClient->iCRC);
|
|
}
|
|
|
|
pDistClient->uCRCValid = FALSE;
|
|
pDistClient->iCRC = 0;
|
|
}
|
|
|
|
// go back to challenge mode
|
|
pDistServ->uCRCResponseCountdown = 0;
|
|
pDistServ->uCRCRemainingFrames = pDistServ->uCRCRate;
|
|
}
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function _NetGameDistServPrepareInputs
|
|
|
|
\Description
|
|
Prepare the inputs for sending a multipacket
|
|
|
|
\Input *pDistServ - module state
|
|
\Input **pInputs - array of pointers to set to data buffers
|
|
\Input *pInputSizes - array of sizes to set
|
|
\Input *pInputTypes - array of types to set
|
|
\Input *pInputUsed - array of booleans, indicates whether a given player's input was used
|
|
\Input *pInputDropped - array of booleans, indicates whether a given player's input was dropped
|
|
|
|
\Output
|
|
uint8_t - indicates whether any data was available and prepared
|
|
|
|
\Version 09/22/2009 (jrainy)
|
|
*/
|
|
/********************************************************************************F*/
|
|
static uint8_t _NetGameDistServPrepareInputs(NetGameDistServT *pDistServ, void** pInputs, int32_t* pInputSizes, uint8_t* pInputTypes, uint8_t* pInputUsed, uint8_t* pInputDropped)
|
|
{
|
|
int32_t iClient, iIndex;
|
|
int32_t iRunningLength, iRunningLengthDroppable;
|
|
int32_t iAllowed, iAllowedDroppable;
|
|
uint8_t bDataAvailable;
|
|
|
|
NetGameDistServClientT *pDistClient;
|
|
|
|
for (iClient = 0; iClient < pDistServ->ClientList.iMaxClients; iClient++)
|
|
{
|
|
pInputSizes[iClient] = 0;
|
|
pInputTypes[iClient] = GMDIST_DATA_NONE;
|
|
pInputUsed[iClient] = FALSE;
|
|
}
|
|
|
|
iRunningLength = 0;
|
|
iRunningLengthDroppable = 0;
|
|
|
|
for (iIndex = 0, bDataAvailable = FALSE; iIndex < pDistServ->ClientList.iMaxClients; iIndex++)
|
|
{
|
|
iClient = (iIndex + pDistServ->iFirstClient) % pDistServ->ClientList.iMaxClients;
|
|
pDistClient = &pDistServ->ClientList.Clients[iClient];
|
|
|
|
if (pDistClient->bLocalInput)
|
|
{
|
|
if (pDistClient->iLocalInputType == GMDIST_DATA_INPUT_DROPPABLE)
|
|
{
|
|
iRunningLengthDroppable += pDistClient->iLocalInputSize;
|
|
}
|
|
else
|
|
{
|
|
if (iRunningLength + pDistClient->iLocalInputSize < DISTSERV_MAX_MULTIPACKET_LENGTH)
|
|
{
|
|
iRunningLength += pDistClient->iLocalInputSize;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
iAllowed = iRunningLength;
|
|
iAllowedDroppable = DISTSERV_MAX_MULTIPACKET_LENGTH - iAllowed;
|
|
iRunningLength = 0;
|
|
iRunningLengthDroppable = 0;
|
|
|
|
for (iIndex = 0, bDataAvailable = FALSE; iIndex < pDistServ->ClientList.iMaxClients; iIndex++)
|
|
{
|
|
iClient = (iIndex + pDistServ->iFirstClient) % pDistServ->ClientList.iMaxClients;
|
|
pDistClient = &pDistServ->ClientList.Clients[iClient];
|
|
pInputDropped[iClient] = FALSE;
|
|
|
|
pInputs[iClient] = pDistClient->aLocalInput;
|
|
if (!pDistClient->bInitialized || pDistClient->bDisconnected)
|
|
{
|
|
pInputSizes[iClient] = 0;
|
|
pInputTypes[iClient] = GMDIST_DATA_DISCONNECT;
|
|
}
|
|
else if (pDistClient->bLocalInput)
|
|
{
|
|
if (pDistClient->iLocalInputType == GMDIST_DATA_INPUT_DROPPABLE)
|
|
{
|
|
iRunningLengthDroppable += pDistClient->iLocalInputSize;
|
|
if (iRunningLengthDroppable > iAllowedDroppable)
|
|
{
|
|
pInputDropped[iClient] = TRUE;
|
|
|
|
_NetGameDistServLogPrintfVerbose(pDistServ, 1, "dropping packet for iClient %d, iIndex %d\n", iClient, iIndex);
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
iRunningLength += pDistClient->iLocalInputSize;
|
|
if (iRunningLength > iAllowed)
|
|
{
|
|
_NetGameDistServLogPrintfVerbose(pDistServ, 1, "delaying packet for iClient %d, iIndex %d\n", iClient, iIndex);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
pInputSizes[iClient] = pDistClient->iLocalInputSize;
|
|
pInputTypes[iClient] = pDistClient->iLocalInputType;
|
|
bDataAvailable = TRUE;
|
|
}
|
|
|
|
pInputUsed[iClient] = TRUE;
|
|
|
|
pDistClient->iCount += pDistClient->iPeekKey;
|
|
}
|
|
|
|
return(bDataAvailable);
|
|
}
|
|
|
|
/*** Public functions *************************************************************/
|
|
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function NetGameDistServCreate
|
|
|
|
\Description
|
|
Create the NetGameDistServ module.
|
|
|
|
\Input iMaxClients - maximum number of clients supported
|
|
\Input iVerbosity - debug verbose level
|
|
|
|
\Output
|
|
NetGameDistServT * - module state, or NULL if create failed
|
|
|
|
\Version 02/01/2007 (jbrookes)
|
|
*/
|
|
/********************************************************************************F*/
|
|
NetGameDistServT *NetGameDistServCreate(int32_t iMaxClients, int32_t iVerbosity)
|
|
{
|
|
NetGameDistServT *pDistServ;
|
|
int32_t iMemSize;
|
|
int32_t iMemGroup;
|
|
void *pMemGroupUserData;
|
|
|
|
// Query current mem group data
|
|
DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData);
|
|
|
|
// calculate memory size based on number of clients
|
|
iMemSize = sizeof(*pDistServ) + (sizeof(pDistServ->ClientList.Clients[0]) * (iMaxClients - 1));
|
|
|
|
// allocate and init module state
|
|
if ((pDistServ = DirtyMemAlloc(iMemSize, NETGAMEDISTSERV_MEMID, iMemGroup, pMemGroupUserData)) == NULL)
|
|
{
|
|
_NetGameDistServLogPrintf(NULL, "could not allocate module state\n");
|
|
return(NULL);
|
|
}
|
|
ds_memclr(pDistServ, iMemSize);
|
|
pDistServ->iMemGroup = iMemGroup;
|
|
pDistServ->pMemGroupUserData = pMemGroupUserData;
|
|
|
|
// init other module state
|
|
pDistServ->ClientList.iMaxClients = iMaxClients;
|
|
pDistServ->iVerbosity = iVerbosity;
|
|
pDistServ->uSendThreshold = 4;
|
|
pDistServ->uLastFlowUpdateTime = NetTick();
|
|
pDistServ->uCRCResponseCountdown = 0;
|
|
pDistServ->uCRCRemainingFrames = pDistServ->uCRCRate;
|
|
pDistServ->iFixedRate = DISTSERV_DEFAULT_FIXEDRATE;
|
|
|
|
// return module ref to caller
|
|
return(pDistServ);
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function NetGameDistServDestroy
|
|
|
|
\Description
|
|
Destroy the NetGameDistServ module.
|
|
|
|
\Input *pDistServ - module state
|
|
|
|
\Version 02/01/2007 (jbrookes)
|
|
*/
|
|
/********************************************************************************F*/
|
|
void NetGameDistServDestroy(NetGameDistServT *pDistServ)
|
|
{
|
|
DirtyMemFree(pDistServ, NETGAMEDISTSERV_MEMID, pDistServ->iMemGroup, pDistServ->pMemGroupUserData);
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function NetGameDistServAddClient
|
|
|
|
\Description
|
|
Add a client to the client list
|
|
|
|
\Input *pDistServ - module state
|
|
\Input iClient - index of slot to add client to
|
|
\Input *pLinkRef - link ref to create dist with
|
|
\Input *pClientName - name of client
|
|
|
|
\Output
|
|
int32_t - negative=error, else success
|
|
|
|
\Version 02/05/2007 (jbrookes)
|
|
*/
|
|
/********************************************************************************F*/
|
|
int32_t NetGameDistServAddClient(NetGameDistServT *pDistServ, int32_t iClient, NetGameLinkRefT *pLinkRef, const char *pClientName)
|
|
{
|
|
// ref given client index
|
|
NetGameDistServClientT *pDistClient = &pDistServ->ClientList.Clients[iClient];
|
|
|
|
// make sure this slot isn't already taken
|
|
if (pDistClient->bInitialized && !pDistClient->bDisconnected)
|
|
{
|
|
_NetGameDistServLogPrintf(pDistServ, "skipping add of client to slot %d when slot is not empty\n", iClient);
|
|
return(-1);
|
|
}
|
|
|
|
// save info and mark as initialized
|
|
ds_memclr(pDistClient, sizeof(*pDistClient));
|
|
ds_strnzcpy(pDistClient->strName, pClientName, sizeof(pDistClient->strName));
|
|
pDistClient->bInitialized = TRUE;
|
|
|
|
// create dist ref for client
|
|
_NetGameDistServLogPrintfVerbose(pDistServ, 0, "creating dist ref for client %s\n", pDistClient->strName);
|
|
DirtyMemGroupEnter(pDistServ->iMemGroup, pDistServ->pMemGroupUserData);
|
|
pDistClient->pGameDist = NetGameDistCreate(pLinkRef,
|
|
(NetGameDistStatProc *)NetGameLinkStatus,
|
|
(NetGameDistSendProc *)NetGameLinkSend,
|
|
(NetGameDistRecvProc *)NetGameLinkRecv,
|
|
GMDIST_DEFAULT_BUFFERSIZE_IN * 20,
|
|
GMDIST_DEFAULT_BUFFERSIZE_OUT * 20);
|
|
DirtyMemGroupLeave();
|
|
|
|
NetGameDistSetServer(pDistClient->pGameDist, TRUE);
|
|
NetGameDistSetProc(pDistClient->pGameDist, 'drop', (void *)_NetGameDistServDrop);
|
|
|
|
// update client count
|
|
pDistServ->ClientList.iNumClients++;
|
|
|
|
// this call is required for dirtycast to announce "no longer receiving" upon join-in-progress
|
|
// DirtyCast will reenter the "receiving" state when the joiner explicitly uses NetGameDistControl('lrcv')
|
|
_NetGameDistServFlowControl(pDistServ);
|
|
|
|
// update dist multi configuration
|
|
_NetGameDistUpdateMulti(pDistServ);
|
|
|
|
// return success
|
|
return(0);
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function NetGameDistServDelClient
|
|
|
|
\Description
|
|
Delete a client from the client list
|
|
|
|
\Input *pDistServ - module state
|
|
\Input iClient - index of slot to delete client from
|
|
|
|
\Output
|
|
int32_t - negative=error, else success
|
|
|
|
\Version 02/05/2007 (jbrookes)
|
|
*/
|
|
/********************************************************************************F*/
|
|
int32_t NetGameDistServDelClient(NetGameDistServT *pDistServ, int32_t iClient)
|
|
{
|
|
// ref given client index
|
|
NetGameDistServClientT *pDistClient;
|
|
|
|
// make sure this is a valid index
|
|
if (iClient >= pDistServ->ClientList.iMaxClients)
|
|
{
|
|
_NetGameDistServLogPrintf(pDistServ, "skipping delete of client %d not in dist list\n", iClient);
|
|
return(-1);
|
|
}
|
|
|
|
// ref client
|
|
pDistClient = &pDistServ->ClientList.Clients[iClient];
|
|
|
|
// disconnect from client
|
|
_NetGameDistServDiscClient(pDistServ, pDistClient, iClient, GMDIST_DELETED);
|
|
|
|
// clear slot in client list
|
|
ds_memclr(pDistClient, sizeof(*pDistClient));
|
|
|
|
// update client count
|
|
pDistServ->ClientList.iNumClients--;
|
|
|
|
// this used to be done only while game was not started
|
|
// with meta-information, we can now do it at any time
|
|
_NetGameDistUpdateMulti(pDistServ);
|
|
|
|
// return success
|
|
return(0);
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function NetGameDistServDiscClient
|
|
|
|
\Description
|
|
Mark the specified client as disconnect. This will prevent sends and update
|
|
|
|
\Input *pDistServ - module state
|
|
\Input iClient - index of slot holding client to update
|
|
|
|
\Output
|
|
int32_t - negative=error, else success
|
|
|
|
\Version 03/15/2007 (jrainy)
|
|
*/
|
|
/********************************************************************************F*/
|
|
int32_t NetGameDistServDiscClient(NetGameDistServT *pDistServ, int32_t iClient)
|
|
{
|
|
// make sure this is a valid index
|
|
if (iClient >= pDistServ->ClientList.iMaxClients)
|
|
{
|
|
_NetGameDistServLogPrintf(pDistServ, "skipping disc of client %d not in dist list\n", iClient);
|
|
return(-1);
|
|
}
|
|
|
|
// destroy connection refs and mark as disconnected
|
|
_NetGameDistServDiscClient(pDistServ, &pDistServ->ClientList.Clients[iClient], iClient, GMDIST_DISCONNECTED);
|
|
return(TRUE);
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function NetGameDistServUpdateClient
|
|
|
|
\Description
|
|
Update the Dist ref for the specified client.
|
|
|
|
\Input *pDistServ - module state
|
|
\Input iClient - index of slot holding client to update
|
|
|
|
\Output
|
|
int32_t - negative=disconnected, else zero
|
|
|
|
\Version 02/05/2007 (jbrookes)
|
|
*/
|
|
/********************************************************************************F*/
|
|
int32_t NetGameDistServUpdateClient(NetGameDistServT *pDistServ, int32_t iClient)
|
|
{
|
|
NetGameDistServClientT *pDistClient = &pDistServ->ClientList.Clients[iClient];
|
|
int32_t iDistErr=0;
|
|
|
|
// don't update uninitialized slots
|
|
if (!pDistClient->bInitialized)
|
|
{
|
|
return(0);
|
|
}
|
|
|
|
// don't update disconnected clients
|
|
if (pDistClient->bDisconnected)
|
|
{
|
|
return(pDistClient->iDisconnectReason ? pDistClient->iDisconnectReason : -1);
|
|
}
|
|
|
|
// update client's dist ref
|
|
NetGameDistUpdate(pDistClient->pGameDist);
|
|
|
|
// check for error
|
|
iDistErr = NetGameDistGetError(pDistClient->pGameDist);
|
|
if (iDistErr != 0)
|
|
{
|
|
_NetGameDistServLogPrintf(pDistServ, "NetGameDistGetError() returned %d\n", iDistErr);
|
|
_NetGameDistServDiscClient(pDistServ, pDistClient, iClient, iDistErr);
|
|
return(iDistErr);
|
|
}
|
|
|
|
// return status
|
|
return(0);
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function NetGameDistServUpdate
|
|
|
|
\Description
|
|
Update the DistServ module. This function is expected to be called at the
|
|
desired output rate.
|
|
|
|
\Input *pDistServ - module state
|
|
|
|
\Version 02/12/2007 (jrainy)
|
|
*/
|
|
/********************************************************************************F*/
|
|
void NetGameDistServUpdate(NetGameDistServT *pDistServ)
|
|
{
|
|
NetGameDistServClientT *pDistClient;
|
|
int32_t iClient, iResult;
|
|
int32_t aInputSizes[GMDIST_MAX_CLIENTS];
|
|
uint8_t aInputTypes[GMDIST_MAX_CLIENTS];
|
|
uint8_t aInputUsed[GMDIST_MAX_CLIENTS];
|
|
uint8_t aInputDropped[GMDIST_MAX_CLIENTS];
|
|
NetGameDistStatT aStats[GMDIST_MAX_CLIENTS];
|
|
NetGameLinkStatT Stat;
|
|
uint8_t bDataAvailable;
|
|
uint8_t bRemoteRcv, bRemoteSnd;
|
|
uint8_t uCRCevent = FALSE;
|
|
uint32_t uSendThreshold;
|
|
void *pInputs[GMDIST_MAX_CLIENTS];
|
|
|
|
int32_t iPeekLength[GMDIST_MAX_CLIENTS];
|
|
int32_t iPeekResult[GMDIST_MAX_CLIENTS] = {0};
|
|
uint8_t iType[GMDIST_MAX_CLIENTS];
|
|
static char aLocalInput[GMDIST_MAX_CLIENTS][NETGAME_DATAPKT_MAXSIZE];
|
|
uint8_t uNewFlow[GMDIST_MAX_CLIENTS];
|
|
uint32_t uNumClientsInitConn = 0;
|
|
uint32_t uNow = NetTick();
|
|
uint8_t bPrevNoInputMode; //!< previous no input mode
|
|
|
|
// every 100 sends, send stats to clients
|
|
if ((pDistServ->uLastStatsUpdate == 0) || (NetTickDiff(uNow, pDistServ->uLastStatsUpdate) > 2000))
|
|
{
|
|
pDistServ->uLastStatsUpdate = uNow;
|
|
|
|
// gather stats for all connected clients
|
|
for (iClient = 0; iClient < pDistServ->ClientList.iMaxClients; iClient++)
|
|
{
|
|
pDistClient = &pDistServ->ClientList.Clients[iClient];
|
|
if (pDistClient->bInitialized && !pDistClient->bDisconnected)
|
|
{
|
|
NetGameDistStatus(pDistClient->pGameDist, 'stat', 0, &Stat, sizeof(NetGameLinkStatT));
|
|
aStats[iClient].late = SocketHtons(Stat.late);
|
|
aStats[iClient].bps = SocketHtons(Stat.outbps);
|
|
aStats[iClient].pps = (uint8_t)Stat.outpps;
|
|
aStats[iClient].slen = (uint8_t)NetGameDistStatus(pDistClient->pGameDist, 'slen', 0, NULL, 0);
|
|
aStats[iClient].naksent = (uint8_t)(Stat.lnaksent - pDistClient->uNakSent);
|
|
aStats[iClient].plost = (uint8_t)(Stat.lpacklost - pDistClient->uPackLost);
|
|
pDistClient->uNakSent = Stat.lnaksent;
|
|
pDistClient->uPackLost = Stat.lpacklost;
|
|
}
|
|
else
|
|
{
|
|
ds_memclr(&aStats[iClient], sizeof(aStats[0]));
|
|
}
|
|
}
|
|
|
|
// broadcast updated stat info to all connected clients
|
|
for (iClient = 0; iClient < pDistServ->ClientList.iMaxClients; iClient++)
|
|
{
|
|
pDistClient = &pDistServ->ClientList.Clients[iClient];
|
|
if(pDistClient->bInitialized && !pDistClient->bDisconnected)
|
|
{
|
|
NetGameDistSendStats(pDistClient->pGameDist, aStats);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if MONITOR_HIGHWATER
|
|
for (iClient = 0; iClient < pDistServ->ClientList.iMaxClients; iClient++)
|
|
{
|
|
int32_t iRet;
|
|
|
|
pDistClient = &pDistServ->ClientList.Clients[iClient];
|
|
if(pDistClient->bInitialized && !pDistClient->bDisconnected)
|
|
{
|
|
iRet = GMDIST_Modulo(NetGameDistStatus(pDistClient->pGameDist, '?inp', 0, NULL, 0) - NetGameDistStatus(pDistClient->pGameDist, '?cmp', 0, NULL, 0), NetGameDistStatus(NULL, 'pwin', 0, NULL, 0));
|
|
if (iRet > pDistServ->iHighWaterInputQueue)
|
|
{
|
|
pDistServ->iHighWaterInputQueue = iRet;
|
|
pDistServ->uHighWaterChanged = TRUE;
|
|
}
|
|
|
|
iRet = GMDIST_Modulo(NetGameDistStatus(pDistClient->pGameDist, '?out', 0, NULL, 0) - NetGameDistStatus(pDistClient->pGameDist, '?snd', 0, NULL, 0), NetGameDistStatus(NULL, 'pwin', 0, NULL, 0));
|
|
if (iRet > pDistServ->iHighWaterOutputQueue)
|
|
{
|
|
pDistServ->iHighWaterOutputQueue = iRet;
|
|
pDistServ->uHighWaterChanged = TRUE;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
for (iClient = 0; iClient < pDistServ->ClientList.iMaxClients; iClient++)
|
|
{
|
|
pDistClient = &pDistServ->ClientList.Clients[iClient];
|
|
|
|
if (pDistClient->bInitialized && !pDistClient->bDisconnected)
|
|
{
|
|
if (!pDistClient->bLocalInput)
|
|
{
|
|
int32_t iLength = sizeof(pDistClient->aLocalInput);
|
|
// check for incoming data on this dist
|
|
iResult = NetGameDistInputPeek(pDistClient->pGameDist, &pDistClient->iLocalInputType, pDistClient->aLocalInput, &iLength);
|
|
pDistClient->bReallyPeeked = TRUE;
|
|
_NetGameDistServSanityCheckIn(pDistServ, iClient, iResult);
|
|
|
|
if (iResult > 0)
|
|
{
|
|
pDistClient->iLocalInputSize = iLength;
|
|
pDistClient->bLocalInput = TRUE;
|
|
pDistClient->iPeekKey = iResult;
|
|
}
|
|
else if (iResult < 0)
|
|
{
|
|
_NetGameDistServLogPrintf(pDistServ, "peek error for client %d\n", iClient);
|
|
_NetGameDistServDiscClient(pDistServ, pDistClient, iClient, GMDIST_PEEK_ERROR);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
bRemoteRcv = NetGameDistStatus(pDistClient->pGameDist,'rrcv', 0, NULL, 0);
|
|
bRemoteSnd = NetGameDistStatus(pDistClient->pGameDist,'rsnd', 0, NULL, 0);
|
|
|
|
if ((bRemoteRcv != pDistClient->bRemoteRcv) || (bRemoteSnd != pDistClient->bRemoteSnd))
|
|
{
|
|
pDistClient->bRemoteRcv = bRemoteRcv;
|
|
pDistClient->bRemoteSnd = bRemoteSnd;
|
|
|
|
_NetGameDistServLogPrintf(pDistServ, "client %d got GAME_PACKET_INPUT_FLOW (send %d, recv %d)\n", iClient, pDistClient->bRemoteSnd, pDistClient->bRemoteRcv);
|
|
_NetGameDistServFlowControl(pDistServ);
|
|
pDistServ->uLastFlowUpdateTime = NetTick();
|
|
}
|
|
}
|
|
}
|
|
|
|
bDataAvailable = _NetGameDistServPrepareInputs(pDistServ, pInputs, aInputSizes, aInputTypes, aInputUsed, aInputDropped);
|
|
|
|
if (!bDataAvailable)
|
|
{
|
|
pDistServ->uNbFramesNoData++;
|
|
}
|
|
else
|
|
{
|
|
pDistServ->uNbFramesNoData = 0;
|
|
}
|
|
|
|
uSendThreshold = pDistServ->uSendThreshold;
|
|
|
|
if (!pDistServ->bFlowEnabled)
|
|
{
|
|
uSendThreshold = 1;
|
|
}
|
|
|
|
// don't send if there was no data available for a while
|
|
if ((uSendThreshold == 0) || (pDistServ->uNbFramesNoData < uSendThreshold))
|
|
{
|
|
if (pDistServ->uCRCRemainingFrames)
|
|
{
|
|
if (pDistServ->bFlowEnabled)
|
|
{
|
|
pDistServ->uCRCRemainingFrames--;
|
|
}
|
|
if (pDistServ->uCRCRemainingFrames == 0)
|
|
{
|
|
int32_t iClientCount = 0;
|
|
|
|
// if we happen to send to only one client, below,
|
|
for (iClient = 0; iClient < pDistServ->ClientList.iMaxClients; iClient++)
|
|
{
|
|
pDistClient = &pDistServ->ClientList.Clients[iClient];
|
|
if (pDistClient->bInitialized && (!pDistClient->bDisconnected))
|
|
{
|
|
iClientCount++;
|
|
}
|
|
}
|
|
|
|
if (iClientCount <= 1)
|
|
{
|
|
// don't send CRC request to single players
|
|
pDistServ->uCRCRemainingFrames = pDistServ->uCRCRate;
|
|
pDistServ->uCRCResponseCountdown = 0;
|
|
}
|
|
else
|
|
{
|
|
for (iClient = 0; iClient < pDistServ->ClientList.iMaxClients; iClient++)
|
|
{
|
|
aInputTypes[iClient] |= GMDIST_DATA_CRC_REQUEST;
|
|
_NetGameDistServLogPrintfVerbose(pDistServ, 2, "setting GMDIST_DATA_CRC_REQUEST in aInputTypes[%d]\n", iClient);
|
|
}
|
|
|
|
pDistServ->uCRCResponseCountdown = pDistServ->uCRCResponseLimit;
|
|
}
|
|
}
|
|
}
|
|
|
|
bPrevNoInputMode = pDistServ->bNoInputMode;
|
|
pDistServ->bNoInputMode = FALSE;
|
|
|
|
// scan through client list, and queue all inputs for sending
|
|
for (iClient = 0; iClient < pDistServ->ClientList.iMaxClients; iClient++)
|
|
{
|
|
uNewFlow[iClient] = FALSE;
|
|
|
|
pDistClient = &pDistServ->ClientList.Clients[iClient];
|
|
|
|
// skip disconnected clients
|
|
if (!pDistClient->bInitialized || pDistClient->bDisconnected)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
uNumClientsInitConn++;
|
|
|
|
if (pDistServ->uCRCResponseCountdown)
|
|
{
|
|
if (NetGameDistStatus(pDistClient->pGameDist, 'rcrc', 0, NULL, 0))
|
|
{
|
|
pDistClient->iCRC = NetGameDistStatus(pDistClient->pGameDist, 'rcrc', 1, NULL, 0);
|
|
pDistClient->uCRCValid = TRUE;
|
|
|
|
_NetGameDistServLogPrintfVerbose(pDistServ, 2, "got CRC response %d back from client %d\n", pDistClient->iCRC, iClient);
|
|
|
|
uCRCevent = TRUE;
|
|
}
|
|
}
|
|
|
|
if (!pDistClient->bReallyPeeked)
|
|
{
|
|
iPeekLength[iClient] = sizeof(aLocalInput[iClient]);
|
|
iPeekResult[iClient] = NetGameDistInputPeek(pDistClient->pGameDist, &(iType[iClient]), &(aLocalInput[iClient]), &(iPeekLength[iClient]));
|
|
|
|
if (iPeekResult[iClient] > 0)
|
|
{
|
|
uNewFlow[iClient] = TRUE;
|
|
}
|
|
}
|
|
|
|
pDistClient->bReallyPeeked = FALSE;
|
|
|
|
// queue input into client
|
|
if ((iResult = NetGameDistInputLocalMulti(pDistClient->pGameDist, aInputTypes, pInputs, aInputSizes, pDistClient->iPeekKey)) == 1)
|
|
{
|
|
pDistServ->uOutputMultiPacketCount++;
|
|
_NetGameDistServLogPrintfVerbose(pDistServ, 2, "queued input into client %s\n", pDistClient->strName);
|
|
}
|
|
else
|
|
{
|
|
_NetGameDistServLogPrintf(pDistServ, "NetGameDistInputLocal() failed for client %s (err=%d)\n", pDistClient->strName, iResult);
|
|
|
|
if (iResult == GMDIST_INVALID)
|
|
{
|
|
_NetGameDistServDiscClient(pDistServ, pDistClient, iClient, GMDIST_INPUTLOCAL_FAILED_INVALID);
|
|
}
|
|
else if (iResult == GMDIST_OVERFLOW_MULTI)
|
|
{
|
|
_NetGameDistServDiscClient(pDistServ, pDistClient, iClient, GMDIST_INPUTLOCAL_FAILED_MULTI);
|
|
}
|
|
else if (iResult == GMDIST_OVERFLOW_WINDOW)
|
|
{
|
|
_NetGameDistServDiscClient(pDistServ, pDistClient, iClient, GMDIST_INPUTLOCAL_FAILED_WINDOW);
|
|
}
|
|
else
|
|
{
|
|
_NetGameDistServDiscClient(pDistServ, pDistClient, iClient, GMDIST_INPUTLOCAL_FAILED);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (aInputUsed[iClient] || aInputDropped[iClient])
|
|
{
|
|
pDistClient->bLocalInput = FALSE;
|
|
pDistClient->iPeekKey = 0;
|
|
}
|
|
|
|
// sanity check output
|
|
_NetGameDistServSanityCheckOut(pDistServ, iClient, iResult);
|
|
|
|
// advance the state
|
|
if ((iResult = NetGameDistInputQueryMulti(pDistClient->pGameDist, NULL, NULL, NULL)) > 0)
|
|
{
|
|
_NetGameDistServLogPrintfVerbose(pDistServ, 2, "sent input %d\n", iResult);
|
|
}
|
|
else if (iResult)
|
|
{
|
|
_NetGameDistServLogPrintf(pDistServ, "NetGameDistInputQueryMulti() failed (err=%d)\n", iResult);
|
|
_NetGameDistServDiscClient(pDistServ, pDistClient, iClient, GMDIST_INPUTQUERY_FAILED);
|
|
continue;
|
|
}
|
|
|
|
if (uNewFlow[iClient] && !aInputUsed[iClient])
|
|
{
|
|
_NetGameDistServLogPrintf(pDistServ, "we're getting behind on large packet delaying for client %d\n", iClient);
|
|
_NetGameDistServDiscClient(pDistServ, pDistClient, iClient, GMDIST_INPUTQUERY_FAILED);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* If the client count changed, flag the boolean used to remember that it happened at least once
|
|
during the metrics sampling interval. */
|
|
if (pDistServ->uCurStatClientCount != uNumClientsInitConn)
|
|
{
|
|
if (pDistServ->bClientCountChanged == FALSE)
|
|
{
|
|
_NetGameDistServLogPrintfVerbose(pDistServ, 1, "metric sampling interval marked invalid for idpps, odmpps and inputdrop because client count changed (client count = %d)\n", pDistServ->uCurStatClientCount);
|
|
}
|
|
pDistServ->bClientCountChanged = TRUE;
|
|
pDistServ->uCurStatClientCount = uNumClientsInitConn;
|
|
}
|
|
|
|
for (iClient = 0; iClient < pDistServ->ClientList.iMaxClients; iClient++)
|
|
{
|
|
pDistClient = &pDistServ->ClientList.Clients[iClient];
|
|
|
|
if (uNewFlow[iClient])
|
|
{
|
|
pDistClient->iLocalInputType = iType[iClient];
|
|
pDistClient->iLocalInputSize = iPeekLength[iClient];
|
|
pDistClient->iPeekKey = iPeekResult[iClient];
|
|
pDistClient->bLocalInput = TRUE;
|
|
|
|
ds_memcpy_s(pDistClient->aLocalInput, sizeof(pDistClient->aLocalInput), aLocalInput[iClient], sizeof(aLocalInput[iClient]));
|
|
|
|
_NetGameDistServSanityCheckIn(pDistServ, iClient, iPeekResult[iClient]);
|
|
}
|
|
}
|
|
|
|
// if something occured, or we have a active countdown
|
|
if (pDistServ->uCRCResponseCountdown || uCRCevent)
|
|
{
|
|
// countdown
|
|
if (pDistServ->bFlowEnabled && pDistServ->uCRCResponseCountdown)
|
|
{
|
|
pDistServ->uCRCResponseCountdown--;
|
|
}
|
|
|
|
// if something occured or we just reached zero.
|
|
if (uCRCevent || (pDistServ->uCRCResponseCountdown == 0))
|
|
{
|
|
_NetGameDistServHandleCRCResponses(pDistServ);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bPrevNoInputMode = pDistServ->bNoInputMode;
|
|
pDistServ->bNoInputMode = TRUE;
|
|
}
|
|
|
|
/* If 'no input mode' changed, flag the boolean used to remember that it happened at least once
|
|
during the metrics sampling interval. */
|
|
if (bPrevNoInputMode != pDistServ->bNoInputMode)
|
|
{
|
|
if (pDistServ->bNoInputModeChanged == FALSE)
|
|
{
|
|
_NetGameDistServLogPrintfVerbose(pDistServ, 1, "metric sampling interval marked invalid for odmpps because no input mode changed (no input mode = %d)\n", pDistServ->bNoInputMode);
|
|
}
|
|
pDistServ->bNoInputModeChanged = TRUE;
|
|
}
|
|
|
|
// $$ jrainy -- fixed up the below code to work without collapsing but introduced a very slight
|
|
// side-effect if we have a list with a big empty area (Alice, [Empty], [Empty], [Empty], [Empty], Bob)
|
|
// Bob will be able to get his oversized packets through more easily than Alice.
|
|
// $$ TODO: A better change would be to increment iFirstclient to the next allocated/initialized
|
|
// spot instead of by just 1. (minor for now)
|
|
if (pDistServ->ClientList.iMaxClients != 0)
|
|
{
|
|
pDistServ->iFirstClient = (pDistServ->iFirstClient + 1) % pDistServ->ClientList.iMaxClients;
|
|
}
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function NetGameDistServHighWaterChanged
|
|
|
|
\Description
|
|
Return whether the highwater mark changed, and the current highwater values.
|
|
|
|
\Input *pDistServ - module state
|
|
\Input pHighWaterInputQueue - input queue
|
|
\Input pHighWaterOutputQueue - output queue
|
|
|
|
\Output
|
|
uint8_t - whether the highwater mark changed since last call
|
|
|
|
\Version 06/05/2008 (jrainy)
|
|
*/
|
|
/********************************************************************************F*/
|
|
uint8_t NetGameDistServHighWaterChanged(NetGameDistServT *pDistServ, int32_t* pHighWaterInputQueue, int32_t* pHighWaterOutputQueue)
|
|
{
|
|
#if MONITOR_HIGHWATER
|
|
uint8_t bChanged = pDistServ->uHighWaterChanged;
|
|
|
|
if (pHighWaterInputQueue != NULL)
|
|
{
|
|
*pHighWaterInputQueue = pDistServ->iHighWaterInputQueue;
|
|
}
|
|
if (pHighWaterOutputQueue != NULL)
|
|
{
|
|
*pHighWaterOutputQueue = pDistServ->iHighWaterOutputQueue;
|
|
}
|
|
|
|
pDistServ->uHighWaterChanged = FALSE;
|
|
return(bChanged);
|
|
#else
|
|
return(FALSE);
|
|
#endif
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function NetGameDistServExplainError
|
|
|
|
\Description
|
|
return the lastest error reported by netgamedist, for client iClient
|
|
|
|
\Input *pDistServ - module state
|
|
\Input iClient - client to get the error for.
|
|
|
|
\Output
|
|
char * - the latest netgamedist error
|
|
|
|
\Version 08/15/2008 (jrainy)
|
|
*/
|
|
/********************************************************************************F*/
|
|
char* NetGameDistServExplainError(NetGameDistServT *pDistServ, int32_t iClient)
|
|
{
|
|
return(pDistServ->ClientList.Clients[iClient].strDiscReason);
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function NetGameDistServControl
|
|
|
|
\Description
|
|
Control behavior of module.
|
|
|
|
\Input *pDistServ - pointer to module state
|
|
\Input iControl - status selector
|
|
\Input iValue - control value
|
|
\Input iValue2 - control value
|
|
\Input *pValue - control value
|
|
|
|
\Notes
|
|
iControl can be one of the following:
|
|
|
|
\verbatim
|
|
'crcc' - set the CRC challenge send rate in number of frames (0 to disable)
|
|
'crcr' - set the CRC max response times in number of frames (0 makes it infinite)
|
|
'mpty' - set the number(iValue) of empty frames needed to pause traffic
|
|
'rate' - set the fixed rate the server has been configured to run at via iValue
|
|
'rsta' - reset stat variables (flow control/no input mode bool)
|
|
\endverbatim
|
|
|
|
\Version 03/17/2009 (jrainy)
|
|
*/
|
|
/********************************************************************************F*/
|
|
int32_t NetGameDistServControl(NetGameDistServT *pDistServ, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue)
|
|
{
|
|
if (iControl == 'crcc')
|
|
{
|
|
if ((uint32_t)iValue != pDistServ->uCRCRate)
|
|
{
|
|
//set the rate
|
|
pDistServ->uCRCRate = iValue;
|
|
|
|
//start counting down
|
|
pDistServ->uCRCRemainingFrames = iValue;
|
|
_NetGameDistServLogPrintf(pDistServ, "setting CRC rate to %d\n", iValue);
|
|
|
|
}
|
|
return(0);
|
|
}
|
|
if (iControl == 'crcr')
|
|
{
|
|
pDistServ->uCRCResponseLimit = iValue;
|
|
return(0);
|
|
}
|
|
if (iControl == 'mpty')
|
|
{
|
|
pDistServ->uSendThreshold = iValue;
|
|
return(0);
|
|
}
|
|
if (iControl == 'rate')
|
|
{
|
|
pDistServ->iFixedRate = iValue;
|
|
return(0);
|
|
}
|
|
if (iControl == 'rsta')
|
|
{
|
|
pDistServ->bClientCountChanged = FALSE;
|
|
pDistServ->bFlowEnabledChanged = FALSE;
|
|
pDistServ->bNoInputModeChanged = FALSE;
|
|
|
|
return(0);
|
|
}
|
|
|
|
return(-1);
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function NetGameDistServStatus2
|
|
|
|
\Description
|
|
Get status information.
|
|
|
|
\Input *pDistServ - pointer to module state
|
|
\Input iSelect - status selector
|
|
\Input iValue - selector specific
|
|
\Input *pBuf - [out] storage for selector-specific output
|
|
\Input iBufSize - size of output buffer
|
|
|
|
\Output
|
|
int32_t - selector specific
|
|
|
|
\Notes
|
|
iSelect can be one of the following:
|
|
|
|
\verbatim
|
|
'clnu' - returns the current client count used for stat (note this not the same as iNumClients in client list)
|
|
'drop' - returns total dropped input packet count in pValue (uint32_t) for client index in iValue, the value is invalid if GameServerDistStatus() return -1;
|
|
'ftim' - flow time. Time (ms) since the last flow control packet was received.
|
|
'icnt' - returns total input dist packet count in pValue (uint32_t) for client index in iValue, the value is invalid if GameServerDistStatus() return -1;
|
|
'ninp' - whether we are currently in the mode where no inputs are sent due to stall in input arrival
|
|
'ocnt' - reutrns output dist multi packet total in pValue (uint32_t), the value is invalid if GameServerDistStatus() return -1;
|
|
\endverbatim
|
|
|
|
\Version 09/18/2019 (tcho)
|
|
*/
|
|
/********************************************************************************F*/
|
|
int32_t NetGameDistServStatus2(NetGameDistServT *pDistServ, int32_t iSelect, int32_t iValue, void *pBuf, int32_t iBufSize)
|
|
{
|
|
// total number of clients counted in the update loop
|
|
if (iSelect == 'clnu')
|
|
{
|
|
return(pDistServ->uCurStatClientCount);
|
|
}
|
|
|
|
// total input packet dropped count for a client or total input dist packet count per client
|
|
if ((iSelect == 'drop') || (iSelect == 'icnt'))
|
|
{
|
|
uint32_t uInputDropCount;
|
|
uint32_t uInputCount;
|
|
NetGameDistServClientT *pDistClient;
|
|
|
|
if ((iValue < 0) || (iValue >= pDistServ->ClientList.iMaxClients))
|
|
{
|
|
// invalid index
|
|
return(-1);
|
|
}
|
|
|
|
if ((pBuf == NULL) || (iBufSize < (int32_t)sizeof(uint32_t)))
|
|
{
|
|
// invalid output parameter
|
|
return(-2);
|
|
}
|
|
|
|
pDistClient = &pDistServ->ClientList.Clients[iValue];
|
|
|
|
// only report a value if the client is initialized and connected
|
|
if (pDistClient->bInitialized && !pDistClient->bDisconnected)
|
|
{
|
|
if (iSelect == 'drop')
|
|
{
|
|
uInputDropCount = NetGameDistStatus(pDistClient->pGameDist, 'drop', 0, NULL, 0);
|
|
ds_memcpy(pBuf, &uInputDropCount, sizeof(uint32_t));
|
|
}
|
|
|
|
if (iSelect == 'icnt')
|
|
{
|
|
uInputCount = NetGameDistStatus(pDistClient->pGameDist, 'icnt', 0, NULL, 0);
|
|
ds_memcpy(pBuf, &uInputCount, sizeof(uint32_t));
|
|
}
|
|
|
|
if ((pDistServ->bClientCountChanged == TRUE) || (pDistServ->bFlowEnabledChanged == TRUE) || (pDistServ->bFlowEnabled == FALSE))
|
|
{
|
|
/* value copied in pBuf is valid but sampling interval should be ignored because conditions are not met
|
|
to properly calculate per-client rate of drops or per-client rate of inbound inputs. */
|
|
return(1);
|
|
}
|
|
|
|
return(0);
|
|
}
|
|
else
|
|
{
|
|
ds_memclr(pBuf, iBufSize);
|
|
return(-3);
|
|
}
|
|
}
|
|
|
|
if (iSelect == 'ftim')
|
|
{
|
|
return(NetTickDiff(NetTick(), pDistServ->uLastFlowUpdateTime));
|
|
}
|
|
|
|
if (iSelect == 'ninp')
|
|
{
|
|
return(pDistServ->bNoInputMode);
|
|
}
|
|
|
|
// total output multi dist count per server
|
|
if (iSelect == 'ocnt')
|
|
{
|
|
if ((iValue < 0) || (iValue >= pDistServ->ClientList.iMaxClients))
|
|
{
|
|
// invalid index
|
|
return(-1);
|
|
}
|
|
|
|
if ((pBuf == NULL) || (iBufSize < (int32_t)sizeof(uint32_t)))
|
|
{
|
|
// invalid output parameter
|
|
return(-2);
|
|
}
|
|
|
|
ds_memcpy(pBuf, &pDistServ->uOutputMultiPacketCount, sizeof(uint32_t));
|
|
|
|
if ((pDistServ->uCurStatClientCount == 0) || (pDistServ->bClientCountChanged == TRUE) || (pDistServ->bNoInputModeChanged == TRUE) || (pDistServ->bNoInputMode == TRUE))
|
|
{
|
|
/* value copied in pBuf is valid but sampling interval should be ignored because conditions are not met
|
|
to properly calculate rate of outbound multipackets */
|
|
return(1);
|
|
}
|
|
|
|
return(0);
|
|
}
|
|
|
|
return(-1);
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function NetGameDistServStatus
|
|
|
|
\Description
|
|
Get status information.
|
|
|
|
\Input *pDistServ - pointer to module state
|
|
\Input iSelect - status selector
|
|
\Input *pBuf - [out] storage for selector-specific output
|
|
\Input iBufSize - size of output buffer
|
|
|
|
\Output
|
|
int32_t - selector specific
|
|
|
|
\Notes
|
|
falls through to NetGameDistServStatus2()
|
|
|
|
\Version 24/09/2012 (jrainy) first version
|
|
*/
|
|
/********************************************************************************F*/
|
|
int32_t NetGameDistServStatus(NetGameDistServT *pDistServ, int32_t iSelect, void *pBuf, int32_t iBufSize)
|
|
{
|
|
return(NetGameDistServStatus2(pDistServ, iSelect, 0, pBuf, iBufSize));
|
|
}
|
|
|
|
/*F********************************************************************************/
|
|
/*!
|
|
\Function NetGameDistServSetLoggingCallback
|
|
|
|
\Description
|
|
Set the logging callback
|
|
|
|
\Input *pDistServ - pointer to module state
|
|
\Input *pLoggingCb - logging callback
|
|
\Input *pUserData - logging userdata
|
|
|
|
\Version 06/26/2019 (eesponda)
|
|
*/
|
|
/********************************************************************************F*/
|
|
void NetGameDistServSetLoggingCallback(NetGameDistServT *pDistServ, NetGameDistServLoggingCbT *pLoggingCb, void *pUserData)
|
|
{
|
|
pDistServ->pLoggingCb = pLoggingCb;
|
|
pDistServ->pUserData = pUserData;
|
|
}
|