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

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;
}