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

2194 lines
80 KiB
C

/*H*************************************************************************************************/
/*!
\File netgamelink.c
\Description
This module provides a packet layer peer-peer interface which utilizes
a lower layer "comm" module. This module is used either by netgamedist
(if it is being used) or can be accessed directly if custom online game
logic is being used. This module also calculates latency (ping) and
maintains statistics about the connection (number of bytes send/received).
The direct application interface is currently weak but will be improved.
\Copyright
Copyright (c) Tiburon Entertainment / Electronic Arts 2002. ALL RIGHTS RESERVED.
\Version 1.0 12/19/00 (GWS) First Version
*/
/*************************************************************************************************H*/
/*** Include files *********************************************************************/
#include <string.h> /* memset/ds_memcpy */
#include "DirtySDK/dirtysock.h"
#include "DirtySDK/dirtysock/dirtymem.h"
#include "DirtySDK/game/netgamepkt.h"
#include "DirtySDK/game/netgamelink.h"
#include "DirtySDK/comm/commall.h"
/*** Defines ***************************************************************************/
#define NETGAMELINK_KEEPALIVE_TIME (500)
//! min/max values for NetGameLink QoS settings
#define NETGAMELINK_QOS_DURATION_MIN (0)
#define NETGAMELINK_QOS_DURATION_MAX (10000)
#define NETGAMELINK_QOS_INTERVAL_MIN (10)
#define NETGAMELINK_QOS_PACKETSIZE_MIN (50)
/*** Macros ****************************************************************************/
/*** Type Definitions ******************************************************************/
//! netgamelink internal state
struct NetGameLinkRefT
{
//! port used to communicate with server
CommRef *pPort;
//! flag to indicate if we own port (if we need to destroy when done)
int32_t iPortOwner;
//! module memory group
int32_t iMemGroup;
void *pMemGroupUserData;
//! tick at which last packet was sent to server (in ms)
uint32_t uLastSend;
//! tick at which last sync packet was received from server (in ms)
uint32_t uLastRecv;
//! tick at which last sync packet contributed to latency average calculation
uint32_t uLastAvg;
//! last echo requested by server (used for rtt calc)
uint32_t uLastEcho;
//! last time a sync packet was sent
uint32_t uLastSync;
//! last time the history slot changed
uint32_t uLastHist;
//! the time weighted rtt average
int32_t iRttAvg;
//! the time weighted rtt deviation
int32_t iRttDev;
//! external status monitoring
NetGameLinkStatT NetGameLinkStats;
//! stats over a one second period
int32_t iInpBytes; //!< input bytes
int32_t iInpPackets; //!< input packets
int32_t iInpRawBytes; //!< input raw bytes
int32_t iInpRawPackets; //!< input raw packets
int32_t iOverhead; //!< send overhead
int32_t iRcvOverhead; //!< recv overhead
int32_t iRcvRawBytes; //!< recv raw bytes
int32_t iRcvRawPackets; //!< recv raw packet (commudp recvs)
int32_t iRcvPackets; //!< recv packets
int32_t iRcvBytes; //!< recv bytes
// track packets sent/received for sending to peer in sync packets
//! stats - raw packets sent
int32_t iPackSent;
//! stats - raw packets received
int32_t iPackRcvd;
//! stats - remote->local packets lost
int32_t iR2LPackLost;
//! stats - naks sent
int32_t iNakSent;
//! point to receive buffer
char *pBuffer;
//! length of data in receive buffer
int32_t iBufLen;
//! size of receive buffer
int32_t iBufMax;
//! protect resources
NetCritT Crit;
//! count of missed accessed
int32_t iProcess;
//! data for m_callproc
void *pCallData;
//! callback for incoming packet notification
void (*pCallProc)(void *pCalldata, int32_t iKind);
//! callback pending
int32_t iCallPending;
//! count calls to NetGameLinkRecv() to limit callback access
int32_t iRecvProcCnt;
//! send enable/disable
int32_t bSendEnabled;
//! sync enable/disable
int32_t bSyncEnabled;
//! list of active streams
NetGameLinkStreamT *m_stream;
int32_t iStrmMaxPktSize;
//! used for tracking QoS packets
uint32_t uQosSendInterval;
uint32_t uQosLastSendTime;
uint32_t uQosStartTime;
uint16_t uQosPacketsToSend;
uint16_t uQosPacketsToRecv;
uint16_t uQosPacketSize;
int32_t iQosCumulLate;
int32_t iQosLateCount;
int32_t iQosAvgLate;
//! verbosity
int32_t iVerbosity;
};
typedef struct GameStreamT
{
int32_t iKind; //!< block type
uint32_t uSync; //!< sync sequence number
int32_t iSent; //!< amount sent so far
int32_t iSize; //!< total block size
int32_t iSubchan; //!< subchannel index/
} GameStreamT;
/*** Function Prototypes ***************************************************************/
/*** Variables *************************************************************************/
// Private variables
// Public variables
/*** Private Functions *****************************************************************/
/*F*************************************************************************************/
/*!
\Function _NetGameLinkUpdateLocalPacketLoss
\Description
Calculate local packet loss based on a difference of remote packets sent and
local packets received.
\Input uLPackRcvd - count of packets received locally
\Input uRPackSent - count of packets sent by remote peer (cumulative psnt
values of all received sync packets)
\Input uOldLPackLost - old count of packets lost
\Output
uint32_t - updated count of packets lost
\Notes
\verbatim
This function returns the number of packets lost at this end of the connection
since the beginning. It is obtained by subtracting uRPackSent (the number of
packets sent by the remote end - cumulative psnt values of all received sync
packets) from uLPackRcvd (the number of packets received locally).
The following two scenarios indicate that a sync packet itself suffered packet
loss, and it relied on the packet resending mechanisms of commudp to finally
make it to us:
1 - uLPackRcvd is larger than uRPackSent (subtracting them would return a
negative value)
2 - uLPackRcvd is smaller than uRPackSent, but the last saved count of
packets lost is larger than the new one.
For both scenarios, we skip updating the packets lost count to avoid a
possibly negative or under-valued packet loss calculation, and we deal with
that iteration as if no packet loss was detected.
The assumption here is that the next sync packet that makes it to us without
being "resent" will end up updating the packet lost counters with a "coherent"
value as its "psnt" field will include all packets that were used for packet
retransmission. At the end, this is guaranteeing the coherency of the packets
lost count (non negative value and only increasing over time) by delaying its
update.
\endverbatim
\Version 09/02/14 (mclouatre)
*/
/*************************************************************************************F*/
static uint32_t _NetGameLinkUpdateLocalPacketLoss(uint32_t uLPackRcvd, uint32_t uRPackSent, uint32_t uOldLPackLost)
{
uint32_t uUpdatedLPackLost = uOldLPackLost;
// only proceed if packet loss is not negative
if (uRPackSent > uLPackRcvd)
{
// only proceed if packet loss is larger than last calculated value
if ((uRPackSent - uLPackRcvd) > uOldLPackLost)
{
uUpdatedLPackLost = uRPackSent - uLPackRcvd;
}
}
return(uUpdatedLPackLost);
}
/*F*************************************************************************************/
/*!
\Function _NetGameLinkGetSync
\Description
Extract sync packet from buffer, and return a pointer to the sync packet location in the buffer
\Input *pPacket - pointer to packet structure
\Input *pPacketData - pointer to packet data (used instead of packet buffer ref to allow split use)
\Input *pSync - [out] output buffer for extracted sync packet
\Output
NetGamePacketSyncT * - pointer to start of sync packet in buffer, or NULL if invalid
\Version 09/09/11 (jbrookes)
*/
/*************************************************************************************F*/
static NetGamePacketSyncT *_NetGameLinkGetSync(NetGamePacketT *pPacket, uint8_t *pPacketData, NetGamePacketSyncT *pSync)
{
uint32_t uSyncSize=0;
// validate sync length byte is available
if (pPacket->head.len < 1)
{
NetPrintf(("netgamelink: received a sync packet with no data\n"));
return(NULL);
}
// validate sync size
if ((uSyncSize = (uint32_t)pPacketData[pPacket->head.len-1]) != sizeof(*pSync))
{
NetPrintf(("netgamelink: received a sync with an invalid size (got=%d, expected=%d)\n", uSyncSize, sizeof(*pSync)));
return(NULL);
}
// validate packet is large enough to hold sync
if (pPacket->head.len < uSyncSize)
{
NetPrintf(("netgamelink: received a sync too large for packet (len=%d)\n", pPacket->head.len));
return(NULL);
}
// locate sync at end of packet & subtract from packet length, copy to output
pPacket->head.len -= uSyncSize;
ds_memcpy(pSync, pPacketData+pPacket->head.len, uSyncSize);
// return sync packet pointer to caller
return((NetGamePacketSyncT *)(pPacketData+pPacket->head.len));
}
/*F*************************************************************************************************/
/*!
\Function _NetGameLinkSendPacket
\Description
Send a packet to the server
\Input *pRef - reference pointer
\Input *pPacket - pointer to completed packet
\Input uCurrTick- current tick
\Output
int32_t - bad error, zero=unable to send now, positive=sent
\Notes
Adds timestamp, rtt and duplex information to packet
\Version 12/19/00 (GWS)
*/
/*************************************************************************************************F*/
static int32_t _NetGameLinkSendPacket(NetGameLinkRefT *pRef, NetGamePacketT *pPacket, uint32_t uCurrTick)
{
int32_t iResult;
int32_t iSynced = 0;
NetGamePacketSyncT sync;
uint32_t uPacketFlags, uPacketType;
uint32_t uPackSent=0, uPackRcvd=0, uLPktLost=0, uNakSent=0;
// sending enabled?
if (pRef->bSendEnabled == FALSE)
{
NetPrintf(("netgamelink: warning -- trying to send packet over a sending-disabled link (%d bytes)\n", pPacket->head.len));
return(1);
}
// return error for oversized packets
if ((pPacket->head.len + NETGAME_DATAPKT_MAXTAIL) > pRef->pPort->maxwid)
{
NetPrintf(("netgamelink: oversized packet send (%d bytes)\n", pPacket->head.len));
return(-1);
}
/* see if we should add sync info:
if _NetGameLinkSendPacket was called explicitly to send a sync packet there is no need to make any further checks
otherwise if a reliable packet game was being sent and it is time to send a sync packet */
if (((pPacket->head.kind & GAME_PACKET_SYNC) || ((pPacket->head.kind != GAME_PACKET_USER_UNRELIABLE) && (uCurrTick-pRef->uLastSync > NETGAMELINK_KEEPALIVE_TIME/2))) && (pRef->bSyncEnabled == TRUE))
{
// build the sync packet
ds_memclr(&sync, sizeof(sync));
sync.size = sizeof(sync);
sync.echo = SocketHtonl(uCurrTick);
sync.repl = SocketHtonl(pRef->uLastEcho+(uCurrTick-pRef->uLastRecv));
sync.late = SocketHtons((int16_t)((pRef->iRttAvg+pRef->iRttDev+1)/2));
uLPktLost = _NetGameLinkUpdateLocalPacketLoss(pRef->NetGameLinkStats.lpackrcvd, pRef->NetGameLinkStats.rpacksent, (unsigned)pRef->iR2LPackLost);
uPackSent = pRef->pPort->packsent;
uNakSent = pRef->pPort->naksent;
uPackRcvd = pRef->pPort->packrcvd;
sync.plost = (uint8_t)(uLPktLost - pRef->iR2LPackLost);
sync.psnt = (uint8_t)(uPackSent - pRef->iPackSent);
sync.nsnt = (uint8_t)(uNakSent - pRef->iNakSent);
sync.prcvd = (uint8_t)(uPackRcvd - pRef->iPackRcvd);
// if we have not received a sync packet from the remote peer, our repl field is not valid, so we tell the remote peer to ignore it
if (pRef->NetGameLinkStats.rpacksent == 0)
{
sync.flags |= NETGAME_SYNCFLAG_REPLINVALID;
}
// piggyback on existing packet
ds_memcpy(pPacket->body.data+pPacket->head.len, &sync, sizeof(sync));
pPacket->head.len += sizeof(sync);
pPacket->head.kind |= GAME_PACKET_SYNC;
iSynced = 1;
}
// put type as last byte
pPacket->body.data[pPacket->head.len] = pPacket->head.kind;
// determine packet flags
uPacketType = pPacket->head.kind & ~GAME_PACKET_SYNC;
if (((uPacketType <= GAME_PACKET_ONE_BEFORE_FIRST) || (uPacketType >= GAME_PACKET_ONE_PAST_LAST)) && (pPacket->head.kind != GAME_PACKET_SYNC))
{
NetPrintf(("netgamelink: warning -- send unrecognized packet kind %d\n", pPacket->head.kind));
}
if (uPacketType == GAME_PACKET_USER_UNRELIABLE)
{
uPacketFlags = COMM_FLAGS_UNRELIABLE;
}
else if (uPacketType == GAME_PACKET_USER_BROADCAST)
{
uPacketFlags = COMM_FLAGS_UNRELIABLE|COMM_FLAGS_BROADCAST;
}
else
{
uPacketFlags = COMM_FLAGS_RELIABLE;
}
// send the packet
iResult = pRef->pPort->Send(pRef->pPort, pPacket->body.data, pPacket->head.len + 1, uPacketFlags);
// if we added sync info, remove it now
if (iSynced)
{
pPacket->head.len -= sizeof(sync);
pPacket->head.kind ^= GAME_PACKET_SYNC;
}
// make sure send succeeded
if (iResult > 0)
{
// record the send time
pRef->uLastSend = uCurrTick;
// if it was a sync packet, update sync info
if (iSynced)
{
pRef->uLastSync = uCurrTick;
pRef->iPackSent = uPackSent;
pRef->iNakSent = uNakSent;
pRef->iPackRcvd = uPackRcvd;
pRef->iR2LPackLost = uLPktLost;
}
// update the stats
pRef->iInpBytes += pPacket->head.len;
pRef->iInpPackets += 1;
pRef->NetGameLinkStats.sent += pPacket->head.len;
pRef->NetGameLinkStats.sentlast = pRef->uLastSend;
// see if we should turn off send light
if ((pRef->NetGameLinkStats.sentshow != 0) && (pRef->uLastSend -pRef->NetGameLinkStats.sentshow > 100))
{
pRef->NetGameLinkStats.sentshow = 0;
pRef->NetGameLinkStats.senthide = pRef->uLastSend;
}
// see if we should turn on send light
if ((pRef->NetGameLinkStats.senthide != 0) && (pRef->uLastSend -pRef->NetGameLinkStats.senthide > 100))
{
pRef->NetGameLinkStats.senthide = 0;
pRef->NetGameLinkStats.sentshow = pRef->uLastSend;
}
}
else
{
// NetPrintf(("GmLink: send failed! (kind=%d, len=%d, synced=%d)\n",
// packet->head.kind, packet->head.len, iSynced));
}
// return the result code
return(iResult);
}
/*F*************************************************************************************************/
/*!
\Function _NetGameLinkRecvPacket0
\Description
Process incoming data packet
\Input *pRef - reference pointer
\Input uCurrTick- current tick
\Output
int32_t - zero=no packet processed, positive=packet processed
\Version 12/19/00 (GWS)
*/
/*************************************************************************************************F*/
static int32_t _NetGameLinkRecvPacket0(NetGameLinkRefT *pRef, uint32_t uCurrTick)
{
int32_t iSize;
int16_t iDelta;
uint32_t uWhen, uKind;
NetGamePacketT *pPacket;
NetGameLinkHistT *pHistory;
// calculate buffer space free, making sure to include packet header overhead (not sent, but queued)
if ((iSize = pRef->iBufMax -pRef->iBufLen -sizeof(pPacket->head)) <= 0)
{
return(0);
}
else if (iSize > pRef->pPort->maxwid)
{
iSize = pRef->pPort->maxwid;
}
// setup packet buffer
pPacket = (NetGamePacketT *)(pRef->pBuffer +pRef->iBufLen);
// see if packet is available
iSize = pRef->pPort->Recv(pRef->pPort, &pPacket->body, iSize, &uWhen);
if ((iSize <= 0) || (iSize > pRef->pPort->maxwid))
{
#if DIRTYCODE_LOGGING
if (iSize != COMM_NODATA)
{
NetPrintf(("netgamelink: Recv() returned %d\n", iSize));
}
#endif
return(0);
}
pPacket->head.len = iSize;
pPacket->head.size = 0;
pPacket->head.when = uWhen;
pRef->NetGameLinkStats.tick = uCurrTick;
pRef->NetGameLinkStats.tickseqn += 1;
// update the stats
pRef->NetGameLinkStats.rcvdlast = uCurrTick;
pRef->NetGameLinkStats.rcvd += pPacket->head.len;
pRef->iRcvBytes += pPacket->head.len;
pRef->iRcvPackets += 1;
// see if we should turn off receive light
if ((pRef->NetGameLinkStats.rcvdshow != 0) && (uCurrTick-pRef->NetGameLinkStats.rcvdshow > 100))
{
pRef->NetGameLinkStats.rcvdshow = 0;
pRef->NetGameLinkStats.rcvdhide = uCurrTick;
}
// see if we should turn on receive light
if ((pRef->NetGameLinkStats.rcvdhide != 0) && (uCurrTick-pRef->NetGameLinkStats.rcvdhide > 100))
{
pRef->NetGameLinkStats.rcvdhide = 0;
pRef->NetGameLinkStats.rcvdshow = uCurrTick;
}
// extract the kind field and fix the length
pPacket->head.len -= 1;
pPacket->head.kind = pPacket->body.data[pPacket->head.len];
// get packet kind
uKind = pPacket->head.kind & ~GAME_PACKET_SYNC;
// warn if kind is invalid
if (((uKind <= GAME_PACKET_ONE_BEFORE_FIRST) || (uKind >= GAME_PACKET_ONE_PAST_LAST)) && (pPacket->head.kind != GAME_PACKET_SYNC))
{
NetPrintf(("netgamelink: warning -- recv unrecognized packet kind %d\n", pPacket->head.kind));
return(1);
}
// see if this packet contains timing info
if (pPacket->head.kind & GAME_PACKET_SYNC)
{
// get sync packet info
NetGamePacketSyncT Sync;
if (_NetGameLinkGetSync(pPacket, pPacket->body.data, &Sync) == NULL)
{
return(1);
}
// remove sync bit
pPacket->head.kind ^= GAME_PACKET_SYNC;
// update the latency stat
pRef->NetGameLinkStats.late = SocketNtohs(Sync.late);
// calculate instantaneous rtt
if ((Sync.flags & NETGAME_SYNCFLAG_REPLINVALID) == 0)
{
/* delta = recvtime - sendtime + 1
The +1 is needed because the peer may think the packet stayed in
its queue for 1ms while we may think it was returned within the
same millisecond. This happens because the clocks are not precisely
synchronized. Adding 1 avoids the issue without compromising precision. */
iDelta = (int16_t)(uWhen - SocketNtohl(Sync.repl) + 1);
}
else
{
// remote peer has indicated their repl field is invalid, so we ignore sync latency info
iDelta = -1;
}
if ((iDelta >= 0) && (iDelta <= 2500))
{
// figure out elapsed time since last packet
int32_t iElapsed = uWhen-pRef->uLastAvg;
if (iElapsed < 10)
{
iElapsed = 10;
}
pRef->uLastAvg = uWhen;
// perform rtt calc using weighted time average
if (iElapsed < RTT_SAMPLE_PERIOD)
{
// figure out weight of existing data
int32_t iWeight = RTT_SAMPLE_PERIOD-iElapsed;
// figure deviation first since it uses average
int32_t iDeviate = iDelta - pRef->iRttAvg;
if (iDeviate < 0)
iDeviate = -iDeviate;
// calc weighted deviation
pRef->iRttDev = (iWeight*pRef->iRttDev+iElapsed*iDeviate)/RTT_SAMPLE_PERIOD;
// calc weighted average
pRef->iRttAvg = (iWeight*pRef->iRttAvg+iElapsed*iDelta)/RTT_SAMPLE_PERIOD;
}
else
{
// if more than our scale has elapsed, use this data
pRef->iRttAvg = iDelta;
pRef->iRttDev = 0;
}
// save copy of ping in stats table
pRef->NetGameLinkStats.ping = pRef->iRttAvg+pRef->iRttDev;
pRef->NetGameLinkStats.pingavg = pRef->iRttAvg;
pRef->NetGameLinkStats.pingdev = pRef->iRttDev;
// see if this is a new recod
if (uWhen-pRef->uLastHist >= PING_LENGTH)
{
// remember the update time
pRef->uLastHist = uWhen;
// advance to next ping slot
pRef->NetGameLinkStats.pingslot = (pRef->NetGameLinkStats.pingslot + 1) % PING_HISTORY;
pHistory = pRef->NetGameLinkStats.pinghist + pRef->NetGameLinkStats.pingslot;
// save the information
pHistory->min = iDelta;
pHistory->max = iDelta;
pHistory->avg = iDelta;
pHistory->cnt = 1;
}
else
{
// update the information
pHistory = pRef->NetGameLinkStats.pinghist + pRef->NetGameLinkStats.pingslot;
if (iDelta < pHistory->min)
pHistory->min = iDelta;
if (iDelta > pHistory->max)
pHistory->max = iDelta;
pHistory->avg = ((pHistory->avg * pHistory->cnt) + iDelta) / (pHistory->cnt+1);
pHistory->cnt += 1;
}
// save this update time
pRef->NetGameLinkStats.pingtick = uWhen;
}
else if ((Sync.flags & NETGAME_SYNCFLAG_REPLINVALID) == 0)
{
NetPrintf(("netgamelink: sync delta %d is out of range (when=0x%08x,sync.repl=0x%08x)\n", iDelta, uWhen, SocketNtohl(Sync.repl) ));
}
// extract timing information
pRef->uLastRecv = uWhen;
pRef->uLastEcho = SocketNtohl(Sync.echo);
// update remote peer's packets sent/recv stat tracker
if (pRef->NetGameLinkStats.rpacksent == 0)
{
/*
bias initial update by one to account for the commdup packet conveying this NetGameLinkSyncT packet not being in the count (but it should really be)
no need to do so afterwards because the count will include the previous packet for which the count is already biased
*/
pRef->NetGameLinkStats.rpacksent += 1;
}
pRef->NetGameLinkStats.rpacksent += Sync.psnt;
pRef->NetGameLinkStats.rnaksent += Sync.nsnt;
pRef->NetGameLinkStats.rpackrcvd += Sync.prcvd;
pRef->NetGameLinkStats.rpacklost += Sync.plost;
// update packets received, packets saved, packets sent and packets lost; this is done here to keep in sync with rpacksent and rpackrcvd
pRef->NetGameLinkStats.lnaksent = pRef->pPort->naksent;
pRef->NetGameLinkStats.lpackrcvd = pRef->pPort->packrcvd;
pRef->NetGameLinkStats.lpacksaved = pRef->pPort->packsaved;
pRef->NetGameLinkStats.lpacksent = pRef->pPort->packsent;
pRef->NetGameLinkStats.lpacklost = _NetGameLinkUpdateLocalPacketLoss(pRef->NetGameLinkStats.lpackrcvd, pRef->NetGameLinkStats.rpacksent, pRef->NetGameLinkStats.lpacklost);
}
// save packet if something remains (was not just sync)
if (pPacket->head.kind != 0)
{
pPacket->head.size = (sizeof(pPacket->head)+pPacket->head.len+3) & 0x7ffc;
pRef->iBufLen += pPacket->head.size;
}
return(1);
}
/*F*************************************************************************************************/
/*!
\Function _NetGameLinkRecvPacket
\Description
Call _NetGameLinkRecvPacket0 if we haven't already
\Input *pRef - reference pointer
\Input uCurrTick- current tick
\Output
int32_t - zero=no packet processed, positive=packet processed
\Version 12/19/00 (GWS)
*/
/*************************************************************************************************F*/
static int32_t _NetGameLinkRecvPacket(NetGameLinkRefT *pRef, uint32_t uCurrTick)
{
int32_t iRetVal;
// limit call depth to prevent a callback from calling us more than once
if (pRef->iRecvProcCnt > 0)
{
#if DIRTYCODE_LOGGING && 0
NetPrintf(("netgamelink: m_recvprocct=%d, unable to call _NetGameLinkRecvPacket0\n", pRef->m_recvprocct));
#endif
return(0);
}
pRef->iRecvProcCnt = 1;
iRetVal = _NetGameLinkRecvPacket0(pRef, uCurrTick);
pRef->iRecvProcCnt = 0;
return(iRetVal);
}
/*F*************************************************************************************************/
/*!
\Function _NetGameLinkProcess
\Description
Main send/receive processing loop
\Input *pRef - reference pointer
\Input uCurrTick- current tick
\Version 12/19/00 (GWS)
*/
/*************************************************************************************************F*/
static void _NetGameLinkProcess(NetGameLinkRefT *pRef, uint32_t uCurrTick)
{
uint32_t uRange;
// grab any pending packets
while (_NetGameLinkRecvPacket(pRef, uCurrTick) > 0)
{
pRef->iCallPending += 1;
}
// mark as processing complete
pRef->iProcess = 0;
// handle stats update (once per second)
pRef->NetGameLinkStats.tick = uCurrTick;
pRef->NetGameLinkStats.tickseqn += 1;
uRange = uCurrTick - pRef->NetGameLinkStats.stattick;
if (uRange >= 1000)
{
// calc bytes per second, raw bytes per second, and network bytes per second
pRef->NetGameLinkStats.outbps = (pRef->iInpBytes *1000)/uRange;
pRef->NetGameLinkStats.outrps = (pRef->pPort->datasent-pRef->iInpRawBytes)*1000/uRange;
pRef->NetGameLinkStats.outnps = ((pRef->pPort->datasent-pRef->iInpRawBytes)+(pRef->pPort->overhead-pRef->iOverhead))*1000/uRange;
pRef->NetGameLinkStats.inrps = ((pRef->pPort->datarcvd-pRef->iRcvRawBytes)*1000)/uRange;
pRef->NetGameLinkStats.inbps = (pRef->iRcvBytes * 1000)/uRange;
pRef->NetGameLinkStats.innps = ((pRef->pPort->datarcvd-pRef->iRcvRawBytes)+(pRef->pPort->rcvoverhead-pRef->iRcvOverhead))*1000/uRange;
// calculate packets per second and raw packets per second
pRef->NetGameLinkStats.outpps = ((pRef->iInpPackets *1000)+500)/uRange;
pRef->NetGameLinkStats.outrpps = ((pRef->pPort->packsent-pRef->iInpRawPackets)*1000+500)/uRange;
pRef->NetGameLinkStats.inpps = ((pRef->iRcvPackets *1000)+500)/uRange;
pRef->NetGameLinkStats.inrpps = ((pRef->pPort->packrcvd - pRef->iRcvRawPackets)*1000+500)/uRange;
// reset tracking variables
pRef->iInpBytes = pRef->iInpPackets = 0;
pRef->iRcvBytes = pRef->iRcvPackets = 0;
pRef->iInpRawBytes = pRef->pPort->datasent;
pRef->iInpRawPackets = pRef->pPort->packsent;
pRef->iRcvRawPackets = pRef->pPort->packrcvd;
pRef->iRcvRawBytes = pRef->pPort->datarcvd;
pRef->iOverhead = pRef->pPort->overhead;
pRef->iRcvOverhead = pRef->pPort->rcvoverhead;
// remember stat update time
pRef->NetGameLinkStats.stattick = uCurrTick;
}
// see if we should turn off send light
if ((pRef->NetGameLinkStats.sentshow != 0) && (uCurrTick-pRef->NetGameLinkStats.sentshow > 100))
{
pRef->NetGameLinkStats.sentshow = 0;
pRef->NetGameLinkStats.senthide = uCurrTick;
}
// see if we should turn on send light
if ((pRef->NetGameLinkStats.senthide != 0) && (uCurrTick-pRef->NetGameLinkStats.senthide > 100) && (uCurrTick-pRef->NetGameLinkStats.sentlast < 50))
{
pRef->NetGameLinkStats.senthide = 0;
pRef->NetGameLinkStats.sentshow = uCurrTick;
}
// see if we should turn off receive light
if ((pRef->NetGameLinkStats.rcvdshow != 0) && (uCurrTick-pRef->NetGameLinkStats.rcvdshow > 100))
{
pRef->NetGameLinkStats.rcvdshow = 0;
pRef->NetGameLinkStats.rcvdhide = uCurrTick;
}
// see if we should turn on receive light
if ((pRef->NetGameLinkStats.rcvdhide != 0) && (uCurrTick-pRef->NetGameLinkStats.rcvdhide > 100) && (uCurrTick-pRef->NetGameLinkStats.rcvdlast < 50))
{
pRef->NetGameLinkStats.rcvdhide = 0;
pRef->NetGameLinkStats.rcvdshow = uCurrTick;
}
// send keepalive/sync if needed
if ((uCurrTick-pRef->uLastSend > NETGAMELINK_KEEPALIVE_TIME) && (pRef->bSyncEnabled == TRUE))
{
// make sure we do not overflow output queue
int32_t iQueue = pRef->pPort->Send(pRef->pPort, NULL, 0, COMM_FLAGS_RELIABLE);
// the exact buffer limit is unimportant, but it needs something
// to avoid overrun during a semi-persistant failure
if ((iQueue > 0) && (iQueue < (pRef->pPort->maxout/4)))
{
// send sync packet
NetGamePacketT spacket;
spacket.head.kind = GAME_PACKET_SYNC;
spacket.head.len = 0;
_NetGameLinkSendPacket(pRef, &spacket, uCurrTick);
}
}
}
/*F*************************************************************************************************/
/*!
\Function _NetGameLinkPrintStats
\Description
Prints the current NetGameLink stats.
\Input *pRef - reference pointer
\Version 09/03/14 (mcorcoran)
*/
/*************************************************************************************************F*/
static void _NetGameLinkPrintStats(NetGameLinkRefT *pRef)
{
NetGameLinkStatT NetGameLinkStats;
NetGameLinkStatus(pRef, 'stat', 0, &NetGameLinkStats, sizeof(NetGameLinkStats));
NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: QoS Results ------------------------------------------------------------------------------\n"));
NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: latency over QoS period %d\n", pRef->iQosAvgLate));
NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: latency over last sampling period %d (sampl. prd = %d ms)\n", NetGameLinkStats.late, RTT_SAMPLE_PERIOD));
NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: when connection established %d\n", NetGameLinkStats.conn));
NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: when data most recently sent %d\n", NetGameLinkStats.sentlast));
NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: when data most recently received %d\n", NetGameLinkStats.rcvdlast));
NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: ping deviation %d\n", NetGameLinkStats.pingdev));
NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: ping average %d\n", NetGameLinkStats.pingavg));
NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: number of bytes sent %d\n", NetGameLinkStats.sent));
NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: number of bytes received %d\n", NetGameLinkStats.rcvd));
NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: number of packets sent to peer since start (at time = last inbound sync pkt) %d\n", NetGameLinkStats.lpacksent));
NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: number of packets sent to peer since start (at time = now) %d\n", NetGameLinkStats.lpacksent_now));
NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: number of packets received from peer since start %d\n", NetGameLinkStats.lpackrcvd));
NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: number of packets sent by peer (to us) since start %d\n", NetGameLinkStats.rpacksent));
NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: number of packets received by peer (from us) since start %d\n", NetGameLinkStats.rpackrcvd));
NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: local->remote packets lost: number of packets (from us) lost by peer since start %d\n", NetGameLinkStats.rpacklost));
NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: packets saved: number of packets recovered by via redundancy mechanisms %d\n", NetGameLinkStats.lpacksaved));
NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: number of NAKs sent by peer (to us) since start %d\n", NetGameLinkStats.rnaksent));
NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: packets per sec sent (user packets) %d\n", NetGameLinkStats.outpps));
NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: raw packets per sec sent (packets sent to network) %d\n", NetGameLinkStats.outrpps));
NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: bytes per sec sent (user data) %d\n", NetGameLinkStats.outbps));
NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: raw bytes per sec sent (data sent to network) %d\n", NetGameLinkStats.outrps));
NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: network bytes per sec sent (inrps + estimated UDP/Eth frame overhead) %d\n", NetGameLinkStats.outnps));
NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: packets per sec received (user packets) %d\n", NetGameLinkStats.inpps));
NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: raw packets per sec received (packets sent to network) %d\n", NetGameLinkStats.inrpps));
NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: bytes per sec received (user data) %d\n", NetGameLinkStats.inbps));
NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: raw bytes per sec received (data sent to network) %d\n", NetGameLinkStats.inrps));
NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: network bytes per sec received (inrps + estimated UDP/Eth frame overhead) %d\n", NetGameLinkStats.innps));
NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: ------------------------------------------------------------------------------------------\n"));
}
/*F*************************************************************************************************/
/*!
\Function _NetGameLinkNotify
\Description
Main notification from lower layer
\Input *pCommRef- generic comm pointer
\Input iEvent - event type
\Version 12/19/00 (GWS)
*/
/*************************************************************************************************F*/
static void _NetGameLinkNotify(CommRef *pCommRef, int32_t iEvent)
{
NetGameLinkRefT *pRef = (NetGameLinkRefT *)pCommRef->refptr;
// make sure we are exclusive
if (NetCritTry(&pRef->Crit))
{
// do the processing
_NetGameLinkProcess(pRef, NetTick());
// do callback if needed
if ((pRef->iBufLen > 0) && (pRef->pCallProc != NULL) && (pRef->iCallPending > 0))
{
pRef->iCallPending = 0;
(pRef->pCallProc)(pRef->pCallData, 1);
}
// free access
NetCritLeave(&pRef->Crit);
}
else
{
// count the miss
pRef->iProcess += 1;
if (pRef->iProcess > 0)
{
// NetPrintf(("netgamelink: missed %d events\n", pRef->iProcess));
}
}
}
/*F*************************************************************************************************/
/*!
\Function _NetGameLinkSendCallback
\Description
Handle notification of send (or re-send) of data from comm layer.
\Input *pComm - pointer to comm ref
\Input *pPacket - packet data that is being sent
\Input iPacketLen - packet length
\Input uCurrTick - current timestamp
\Version 09/09/11 (jbrookes)
*/
/*************************************************************************************************F*/
static void _NetGameLinkSendCallback(CommRef *pComm, void *pPacket, int32_t iPacketLen, uint32_t uCurrTick)
{
uint8_t *pPacketData = (uint8_t *)pPacket;
NetGamePacketSyncT Sync, *pSync;
NetGamePacketHeadT Head;
uint32_t uTickDiff;
// does this packet include a sync packet?
if ((pPacketData[iPacketLen-1] & GAME_PACKET_SYNC) == 0)
{
return;
}
// extract sync packet
Head.kind = pPacketData[iPacketLen-1];
Head.len = iPacketLen-1;
if ((pSync = _NetGameLinkGetSync((NetGamePacketT *)&Head, pPacketData, &Sync)) != NULL)
{
// compare currtick with current tick
uTickDiff = NetTickDiff(uCurrTick, SocketNtohl(Sync.echo));
// refresh echo and repl and write back sync packet
Sync.echo = SocketHtonl(uCurrTick);
Sync.repl = SocketHtonl((SocketNtohl(Sync.repl) + uTickDiff));
ds_memcpy(pSync, &Sync, sizeof(Sync));
/* Note:
Unlike echo and repl, other sync packet fields (psnt, plost, nsnt, prcvd,...) are intentionally not
refreshed here because they convey update of counts between two sync packets (they are delta values).
Any update in the values of these counters not included in this sync packet (regardless of this
packet being resent or not) will be safely covered by the next sync packet. */
}
}
/*F*************************************************************************************************/
/*!
\Function _NetGameLinkPollStream
\Description
Poll to see if we should send stream data
\Input *pRef - reference pointer
\Output
int32_t - send count
\Version 01/11/10 (jrainy)
*/
/*************************************************************************************************F*/
static int32_t _NetGameLinkPollStream(NetGameLinkRefT *pRef)
{
int32_t iCount = 0, iSize, iResult;
NetGameMaxPacketT Packet;
GameStreamT Block;
NetGameLinkStreamT *pStream;
// see if any stream has pending data
for (pStream = pRef->m_stream; pStream != NULL; pStream = pStream->pNext)
{
// see if anything to send
while (pStream->iOutProg < pStream->iOutSize)
{
int32_t iLimit;
// get data block
ds_memcpy(&Block, pStream->pOutData+pStream->iOutProg, sizeof(Block));
// setup data packet
Packet.head.kind = GAME_PACKET_LINK_STRM;
// set up packet 'size' field, which is size | subchannel
iSize = (Block.iSize & ~0xff000000) | ((Block.iSubchan & 0xff) << 24);
// store stream header fields in network order
Packet.body.strm.ident = SocketHtonl(pStream->iIdent);
Packet.body.strm.kind = SocketHtonl(Block.iKind);
Packet.body.strm.size = SocketHtonl(iSize);
iSize = Block.iSize -Block.iSent;
// don't overflow our buffer
iLimit = (signed)sizeof(Packet.body.strm.data);
if (iSize > iLimit)
{
iSize = iLimit;
}
// don't overflow the underlying layer max packet size
iLimit = pRef->iStrmMaxPktSize;
if (iSize > iLimit)
{
iSize = iLimit;
}
ds_memcpy(Packet.body.strm.data, pStream->pOutData+pStream->iOutProg+sizeof(Block)+Block.iSent, iSize);
Packet.head.len = (uint16_t)(sizeof(Packet.body.strm)-sizeof(Packet.body.strm.data)+iSize);
// try and queue/send the packet
iResult = NetGameLinkSend(pRef, (NetGamePacketT *)&Packet, 1);
if (iResult < 0)
{
NetPrintf(("netgamelink: [%p] stream send failed for stream 0x%08x!\n", pRef, pStream));
break;
}
else if (iResult == 0)
{
break;
}
// advance the sent count
Block.iSent += iSize;
if (Block.iSent != Block.iSize)
{
// save back updated block for next time (could just update .sent, but this is easier due to alignment)
ds_memcpy(pStream->pOutData+pStream->iOutProg, &Block, sizeof(Block));
}
else
{
pStream->iOutProg += sizeof(Block)+Block.iSize;
// see if we should shift the buffer (more than 75% full and shift would gain >33%)
if ((pStream->iOutProg < pStream->iOutSize) &&
(pStream->iOutSize*4 > pStream->iOutMaxm*3) &&
(pStream->iOutProg*3 > pStream->iOutMaxm*1))
{
// do the shift
memmove(pStream->pOutData, pStream->pOutData+pStream->iOutProg, pStream->iOutSize-pStream->iOutProg);
pStream->iOutSize -= pStream->iOutProg;
pStream->iOutProg = 0;
}
}
// count the send
++iCount;
}
}
// return send count
return(iCount);
}
/*F*************************************************************************************************/
/*!
\Function _NetGameLinkSendStream
\Description
Let user queue up a buffer to send
\Input *pStream - stream to send on
\Input iSubChan - subchannel stream is to be received on
\Input iKind - kind of data (used to determine if sync)
\Input *pBuffer - pointer to data to send
\Input iLength - length of data to send
\Output
int32_t - negative=error, zero=busy (send again later), positive=sent
\Version 01/11/10 (jrainy)
*/
/*************************************************************************************************F*/
static int32_t _NetGameLinkSendStream(NetGameLinkStreamT *pStream, int32_t iSubChan, int32_t iKind, void *pBuffer, int32_t iLength)
{
GameStreamT Block;
// if this is an orphaned stream, return an error
if (pStream->pClient == NULL)
{
return(-1); //todo
}
// allow negative length to mean strlen(data)+1
if (iLength < 0)
{
for (iLength = 0; ((char *)pBuffer)[iLength] != '\0'; ++iLength)
;
++iLength;
}
// make sure send buffer does not exceed total input buffer
// (do this AFTER negative length check)
if (iLength >= pStream->iInpMaxm)
{
return(-1);
}
// see if we can reset buffer pointers
if (pStream->iOutProg >= pStream->iOutSize)
{
pStream->iOutProg = pStream->iOutSize = 0;
}
// make sure send buffer does not exceed current output buffer
if (iLength+sizeof(GameStreamT) >= (unsigned)pStream->iOutMaxm-pStream->iOutSize)
{
return(0);
}
// if this is a sync, make sure we have space in sync buffer
if ((iKind > '~ ') && (iLength+sizeof(GameStreamT) >= (unsigned)pStream->iSynMaxm-pStream->iSynSize))
{
return(0);
}
// see if this is a length query
if (pBuffer == NULL)
{
// calc main buffer space
iLength = pStream->iOutMaxm-pStream->iOutSize;
// if this is sync, limit based on sync buffer
if ((iKind > '~ ') && (iLength > pStream->iSynMaxm-pStream->iSynSize))
{
iLength = pStream->iSynMaxm-pStream->iSynSize;
}
// subtract packet header size
iLength -= sizeof(GameStreamT);
if (iLength < 0)
{
iLength = 0;
}
// return available size
return(iLength);
}
// setup the header block
Block.iKind = iKind;
Block.uSync = 0;
Block.iSent = 0;
Block.iSize = iLength;
Block.iSubchan = iSubChan;
// if sync, copy into sync buffer
if (iKind > '~ ')
{
// set sync sequence number
Block.uSync = 0;
// copy into sync byffer
ds_memcpy(pStream->pSynData+pStream->iSynSize, &Block, sizeof(Block));
ds_memcpy(pStream->pSynData+pStream->iSynSize+sizeof(Block), pBuffer, iLength);
pStream->iSynSize += sizeof(Block)+iLength;
}
// do a memmove just in case data source is already in this buffer
// (this is an optimization used for database access)
memmove(pStream->pOutData+pStream->iOutSize+sizeof(Block), pBuffer, iLength);
ds_memcpy(pStream->pOutData+pStream->iOutSize, &Block, sizeof(Block));
pStream->iOutSize += sizeof(Block)+iLength;
if (pStream->iOutSize > pStream->iHighWaterUsed)
{
pStream->iHighWaterUsed = pStream->iOutSize;
}
if ((pStream->iOutSize - pStream->iOutProg) > pStream->iHighWaterNeeded)
{
pStream->iHighWaterNeeded = pStream->iOutSize - pStream->iOutProg;
}
// attempt immediate send
_NetGameLinkPollStream(pStream->pClient);
// return send status
return(iLength);
}
/*F*************************************************************************************************/
/*!
\Function _NetGameLinkRecvStream
\Description
Process a received stream packet
\Input *pRef - reference pointer
\Input *pPacket - packet to process
\Output
int32_t - -1=no match, -2=overflow error, 0=valid packet received
\Version 01/11/10 (jrainy)
*/
/*************************************************************************************************F*/
static int32_t _NetGameLinkRecvStream(NetGameLinkRefT *pRef, NetGamePacketT *pPacket)
{
int32_t iRes = -1, iSize;
NetGameLinkStreamT *pStream;
NetGameLinkStreamInpT *pInp;
int32_t iSubChannel;
// see if this packet matches any of the streams
for (pStream = pRef->m_stream; pStream != NULL; pStream = pStream->pNext)
{
// see if this is the matching stream
if (pStream->iIdent == pPacket->body.strm.ident)
{
// extract size/subchannel from 'size' member
iSize = pPacket->body.strm.size & ~0xff000000;
iSubChannel = (unsigned)pPacket->body.strm.size >> 24;
if ((iSubChannel < 0) || (iSubChannel >= (pStream->iSubchan+1)))
{
NetPrintf(("netgamelink: [%p] warning, received packet on stream '%C' with invalid packet subchannel %d\n", pRef, pPacket->body.strm.ident, iSubChannel));
return(-1);
}
// ref channel
pInp = pStream->pInp + iSubChannel;
// default to overflow error
iRes = GMDIST_OVERFLOW;
// validate the packet size
if (iSize <= pStream->iInpMaxm)
{
/* auto-clear packet if anything looks inconsistant -- note that this is expected behavior
for the first packet fragment in a sequence of packet fragments */
if ((pPacket->body.strm.kind != pInp->iInpKind) || (iSize != pInp->iInpSize))
{
// reset progress
pInp->iInpProg = 0;
// save basic packet header
pInp->iInpKind = pPacket->body.strm.kind;
pInp->iInpSize = iSize;
}
// figure out size of data in packet
iSize = pPacket->head.len-(sizeof(pPacket->body.strm)-sizeof(pPacket->body.strm.data));
if (iSize > pInp->iInpSize-pInp->iInpProg)
{
NetPrintf(("netgamelink: [%p] clamped stream packet size from %d to %d on stream '%C'\n", pRef, iSize, pInp->iInpSize-pInp->iInpProg, pPacket->body.strm.ident));
iSize = pInp->iInpSize-pInp->iInpProg;
}
// append to existing packet
ds_memcpy(pInp->pInpData+pInp->iInpProg, pPacket->body.strm.data, iSize);
pInp->iInpProg += iSize;
// see if packet is complete
if (pInp->iInpProg == pInp->iInpSize)
{
// deliver packet
if (pStream->Recv != NULL)
{
pStream->Recv(pStream, iSubChannel, pInp->iInpKind, pInp->pInpData, pInp->iInpSize);
}
else
{
NetPrintf(("netgamelink: [%p] no registered stream recv handler on stream '%C'\n", pRef, pPacket->body.strm.ident));
}
// clear from buffer
pInp->iInpProg = 0;
}
// packet was valid
iRes = 0;
}
else
{
NetPrintf(("netgamelink: [%p] invalid stream packet size (%d >= %d) on stream '%C'\n",
pRef, pPacket->body.strm.size, pStream->iInpMaxm, pPacket->body.strm.ident));
}
return(iRes);
}
}
// didn't find the stream
NetPrintf(("netgamelink: [%p] could not find stream for stream packet with iIdent '%C'\n", pRef, pPacket->body.strm.ident));
// return result
return(iRes);
}
/*F*************************************************************************************************/
/*!
\Function _NetGameLinkUpdateQos
\Description
Send and receive QoS packets.
\Input *pRef - reference pointer
\Version 09/03/14 (mcorcoran)
*/
/*************************************************************************************************F*/
static void _NetGameLinkUpdateQos(NetGameLinkRefT *pRef)
{
NetGameMaxPacketT QosPacket;
uint32_t uNow = NetTick();
// save QoS start time
if (pRef->uQosStartTime == 0)
{
pRef->uQosStartTime = uNow;
}
// init QoS send time
if ((pRef->uQosPacketsToSend > 0) && (pRef->uQosLastSendTime == 0))
{
/* We are now expecting QoS packet from the other end of the link.
Make this value non-zero to satisfy the while() condition below. */
pRef->uQosPacketsToRecv = 1;
pRef->uQosLastSendTime = uNow - (pRef->uQosSendInterval+1);
}
// check if it's time to send a QoS packet.
if ((pRef->uQosPacketsToSend > 0) && (NetTickDiff(uNow, pRef->uQosLastSendTime) > (signed)pRef->uQosSendInterval))
{
// set qos last send time (reserve zero for uninitialized)
pRef->uQosLastSendTime = (uNow != 0) ? uNow : uNow-1;
pRef->uQosPacketsToSend -= 1;
// create and send the packet
QosPacket.head.kind = GAME_PACKET_USER;
QosPacket.head.len = pRef->uQosPacketSize;
ds_memclr(QosPacket.body.data, QosPacket.head.len);
// the other end of the link needs to know how many more packets to expect.
QosPacket.body.data[0] = (uint8_t)(pRef->uQosPacketsToSend >> 8);
QosPacket.body.data[1] = (uint8_t)(pRef->uQosPacketsToSend >> 0);
NetGameLinkSend(pRef, (NetGamePacketT*)&QosPacket, 1);
NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: [%p] sent QoS packet, size(%d), remaining packets to send (%d)\n", pRef, QosPacket.head.len, pRef->uQosPacketsToSend));
}
while ((pRef->uQosPacketsToRecv > 0) && NetGameLinkRecv(pRef, (NetGamePacketT*)&QosPacket, 1, FALSE))
{
// the other end of the link tells us how many more packets to expect (wait for before transitioning to active)
pRef->uQosPacketsToRecv =
(QosPacket.body.data[0] << 8) |
(QosPacket.body.data[1] << 0);
NetPrintfVerbose((pRef->iVerbosity, 3, "netgamelink: [%p] received QoS packet, size(%d), remaining packets to recv (%d)\n", pRef, QosPacket.head.len, pRef->uQosPacketsToRecv));
}
// have we finished sending and receiving everything yet?
if ((pRef->uQosPacketsToSend == 0) && (pRef->uQosPacketsToRecv == 0))
{
// we're done, print available stats
_NetGameLinkPrintStats(pRef);
}
// only start averaging latency when 1 sample period is complete
if (NetTickDiff(uNow, pRef->uQosStartTime) > RTT_SAMPLE_PERIOD)
{
pRef->iQosCumulLate += pRef->NetGameLinkStats.late;
pRef->iQosLateCount++;
pRef->iQosAvgLate = pRef->iQosCumulLate / pRef->iQosLateCount;
}
}
/*F*************************************************************************************************/
/*!
\Function _NetGameLinkDrainRecvStream
\Description
Need for streams handling. Received all data on the stream.
\Input *pRef - reference pointer
\Version 09/03/14 (mcorcoran)
*/
/*************************************************************************************************F*/
static void _NetGameLinkDrainRecvStream(NetGameLinkRefT *pRef)
{
NetGameMaxPacketT Packet;
while (NetGameLinkRecv2(pRef, (NetGamePacketT*)&Packet, 1, 1 << GAME_PACKET_LINK_STRM))
{
// convert stream header from network to host order
Packet.body.strm.ident = SocketNtohl(Packet.body.strm.ident);
Packet.body.strm.kind = SocketNtohl(Packet.body.strm.kind);
Packet.body.strm.size = SocketNtohl(Packet.body.strm.size);
// process the packet
_NetGameLinkRecvStream(pRef, (NetGamePacketT *)&Packet);
}
}
/*** Public Functions ******************************************************************/
/*F*************************************************************************************************/
/*!
\Function NetGameLinkCreate
\Description
Construct the game client
\Input *pCommRef - the connection from NetGameUtilComplete()
\Input iOwner - if TRUE, NetGameLink will assume ownership of the port (ie, delete it when done)
\Input iBufLen - length of input buffer
\Output
NetGameLinkRefT * - pointer to new NetGameLinkRef
\Version 12/19/00 (GWS)
*/
/*************************************************************************************************F*/
NetGameLinkRefT *NetGameLinkCreate(void *pCommRef, int32_t iOwner, int32_t iBufLen)
{
int32_t iIndex;
uint32_t uTick;
CommRef *pPort = pCommRef;
NetGameLinkRefT *pRef;
int32_t iMemGroup;
void *pMemGroupUserData;
NetGameMaxPacketT Packet;
// Query current mem group data
DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData);
// allocate and init module state
if ((pRef = DirtyMemAlloc(sizeof(*pRef), NETGAMELINK_MEMID, iMemGroup, pMemGroupUserData)) == NULL)
{
NetPrintf(("netgamelink: unable to allocate module state\n"));
return(NULL);
}
ds_memclr(pRef, sizeof(*pRef));
pRef->iMemGroup = iMemGroup;
pRef->pMemGroupUserData = pMemGroupUserData;
// assign port info
pRef->pPort = pPort;
pRef->iPortOwner = iOwner;
// allocate input buffer
if (iBufLen < 4096)
{
iBufLen = 4096;
}
pRef->pBuffer = DirtyMemAlloc(iBufLen, NETGAMELINK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData);
pRef->iBufMax = iBufLen;
// reset the timing info
pRef->iRttAvg = 0;
pRef->iRttDev = 0;
// setup connection time for reference
uTick = NetTick();
pRef->NetGameLinkStats.conn = uTick;
pRef->NetGameLinkStats.senthide = uTick;
pRef->NetGameLinkStats.rcvdhide = uTick;
pRef->NetGameLinkStats.rcvdlast = uTick;
pRef->NetGameLinkStats.sentlast = uTick;
pRef->NetGameLinkStats.stattick = uTick;
pRef->NetGameLinkStats.isconn = FALSE;
pRef->NetGameLinkStats.isopen = FALSE;
// fill ping history with bogus starting data
for (iIndex = 0; iIndex < PING_HISTORY; ++iIndex)
{
pRef->NetGameLinkStats.pinghist[iIndex].min = PING_DEFAULT;
pRef->NetGameLinkStats.pinghist[iIndex].max = PING_DEFAULT;
pRef->NetGameLinkStats.pinghist[iIndex].avg = PING_DEFAULT;
pRef->NetGameLinkStats.pinghist[iIndex].cnt = 1;
}
// setup critical section
NetCritInit(&pRef->Crit, "netgamelink");
// setup for callbacks
pPort->refptr = pRef;
pPort->Callback(pPort, _NetGameLinkNotify);
pPort->SendCallback = _NetGameLinkSendCallback;
// default sending and syncs to enabled
pRef->bSendEnabled = TRUE;
pRef->bSyncEnabled = TRUE;
// calculate the max packet size based on the cudp max width (minus the stream overhead)
pRef->iStrmMaxPktSize = pPort->maxwid - (sizeof(Packet.body.strm) - sizeof(Packet.body.strm.data));
// other QoS items memset() to zero above
pRef->uQosSendInterval = NETGAMELINK_QOS_INTERVAL_MIN;
pRef->uQosPacketSize = NETGAME_DATAPKT_MAXSIZE;
pRef->iVerbosity = 1;
return(pRef);
}
/*F*************************************************************************************************/
/*!
\Function NetGameLinkDestroy
\Description
Destruct the game client
\Input *pRef - reference pointer
\Version 12/19/00 (GWS)
*/
/*************************************************************************************************F*/
void NetGameLinkDestroy(NetGameLinkRefT *pRef)
{
// dont need callback
pRef->pPort->Callback(pRef->pPort, NULL);
// we own the port -- get rid of it
if (pRef->iPortOwner)
pRef->pPort->Destroy(pRef->pPort);
// free receive buffer
DirtyMemFree(pRef->pBuffer, NETGAMELINK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData);
// free critical section
NetCritKill(&pRef->Crit);
// free our memory
DirtyMemFree(pRef, NETGAMELINK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData);
}
/*F*************************************************************************************************/
/*!
\Function NetGameLinkCallback
\Description
Register a callback function
\Input *pRef - reference pointer
\Input *pCallData - caller reference data
\Input *pCallProc - callback function
\Version 12/19/00 (GWS)
*/
/*************************************************************************************************F*/
void NetGameLinkCallback(NetGameLinkRefT *pRef, void *pCallData, void (*pCallProc)(void *pCallData, int32_t iKind))
{
pRef->pCallData = pCallData;
pRef->pCallProc = pCallProc;
}
/*F*************************************************************************************************/
/*!
\Function NetGameLinkStatus
\Description
Return current link status
\Input *pRef - reference pointer
\Input iSelect - selector
\Input iValue - input value
\Input pBuf - output buffer
\Input iBufSize - output buffer size
\Output
int32_t - selector specific return value
\Notes
iSelect can be one of the following:
\verbatim
'mwid' - returns the commudp packet max width field
'qlat' - average latency calculated during the initial qos phase
'sinf' - returns SocketInfo() with iValue being the status selector (iData param is unsupported)
'slen' - returns current length of send queue
'sque' - returns TRUE if send queue is empty, else FALSE
'stat' - Fills out a NetGameLinkStatT with QoS info. pBuf=&NetGameLinkStatT, iBufSize=sizeof(NetGameLinkStatT)
\endverbatim
\Version 12/19/00 (GWS)
*/
/*************************************************************************************************F*/
int32_t NetGameLinkStatus(NetGameLinkRefT *pRef, int32_t iSelect, int32_t iValue, void *pBuf, int32_t iBufSize)
{
// read commudp mwid field
if (iSelect == 'mwid')
{
return(pRef->pPort->maxwid);
}
if (iSelect == 'qlat')
{
return(pRef->iQosAvgLate);
}
if ((iSelect == 'sinf') && (pRef->pPort != NULL) && (pRef->pPort->sockptr != NULL))
{
return(SocketInfo(pRef->pPort->sockptr, iValue, 0, pBuf, iBufSize));
}
if ((iSelect == 'sque') || (iSelect == 'slen'))
{
// get output queue position
int32_t iQueue = pRef->pPort->Send(pRef->pPort, NULL, 0, COMM_FLAGS_RELIABLE);
if (iSelect == 'sque')
{
// if output queue position is one send queue is empty (assume error=empty)
iQueue = (iQueue > 0) ? (iQueue == 1) : TRUE;
}
return(iQueue);
}
if (iSelect == 'stat')
{
volatile uint32_t uSeqn;
uint32_t uPortStat;
do
{
// remember pre-update ticks
uSeqn = pRef->NetGameLinkStats.tickseqn;
// update tick is same as movement tick
pRef->NetGameLinkStats.tick = NetTick();
// if things changed during out assignment, do it again
} while (pRef->NetGameLinkStats.tickseqn != uSeqn);
// update port status
uPortStat = pRef->pPort->Status(pRef->pPort);
pRef->NetGameLinkStats.isopen = ((uPortStat == COMM_CONNECTING) || (uPortStat == COMM_ONLINE)) ? TRUE : FALSE;
pRef->NetGameLinkStats.isopen = pRef->NetGameLinkStats.isopen && (pRef->uQosPacketsToSend == 0) && (pRef->uQosPacketsToRecv == 0);
// update packet sent counter
pRef->NetGameLinkStats.lpacksent_now = pRef->pPort->packsent;
// make sure user-provided buffer is large enough to receive a pointer
if ((pBuf != NULL) && (iBufSize >= (int32_t)sizeof(NetGameLinkStatT)))
{
ds_memcpy(pBuf, &pRef->NetGameLinkStats, sizeof(NetGameLinkStatT));
return(0);
}
else
{
// unhandled
return(-1);
}
}
return(-1);
}
/*F*************************************************************************************************/
/*!
\Function NetGameLinkSend
\Description
Handle incoming packet stream from upper layer
\Input *pRef - reference pointer
\Input *pPkt - packet list (1 or more)
\Input iLen - size packet list (1=one packet)
\Output
int32_t - negative=bad error, zero=unable to send now (overflow), positive=bytes sent
\Version 12/19/00 (GWS)
*/
/*************************************************************************************************F*/
int32_t NetGameLinkSend(NetGameLinkRefT *pRef, NetGamePacketT *pPkt, int32_t iLen)
{
int32_t iCnt = 0, iErr;
uint32_t uCurrTick = NetTick();
// get exclusive access
NetCritEnter(&pRef->Crit);
// see if we need to handle missed event
if (pRef->iProcess > 0)
{
NetPrintf(("netgamelink: processing missed event\n"));
_NetGameLinkProcess(pRef, uCurrTick);
}
// walk the packet list
while (iLen > 0)
{
if ((iErr = _NetGameLinkSendPacket(pRef, pPkt, uCurrTick)) <= 0)
{
// don't spam on overflow
if (iErr != 0)
{
NetPrintf(("netgamelink: error %d sending packet\n", iErr));
}
// should _NetGameLinkSendPacket fail at first send attempt, return err to caller.
if (iCnt == 0)
{
iCnt = iErr;
}
break;
}
// calc packet size for them
pPkt->head.size = (sizeof(pPkt->head)+pPkt->head.len+sizeof(NetGamePacketSyncT)+3)&0x7ffc;
// count the packet size
iCnt += pPkt->head.size;
// stop if this was single packet
if (iLen == 1)
{
break;
}
// skip to next packet
iLen -= pPkt->head.size;
pPkt = (NetGamePacketT *)(((char *)pPkt)+pPkt->head.size);
}
// release exclusive access
NetCritLeave(&pRef->Crit);
// return bytes sent
return(iCnt);
}
/*F*************************************************************************************************/
/*!
\Function NetGameLinkPeek2
\Description
Peek into the buffer, for the first packet type matching mask
\Input *pRef - reference pointer
\Input **ppPkt - (optional) storage for pointer to current packet data
\Input uMask - which packet types we want. bitmask, addressed by GAME_PACKET values
\Output
int32_t - buffer size
\Version 08/08/11 (jrainy)
*/
/*************************************************************************************************F*/
int32_t NetGameLinkPeek2(NetGameLinkRefT *pRef, NetGamePacketT **ppPkt, uint32_t uMask)
{
int32_t iBufLen = pRef->iBufLen;
int32_t iIndex, iKind;
NetGamePacketT *pPkt0;
int32_t iPktSize = 0;
// I do not understand the reason for && (iBufLen > 0).
// Kept to maintain current behaviour
// TODO: investigate
if ((ppPkt != NULL) && (iBufLen > 0))
{
// while going through our buffer of received packets
for (iIndex = 0; iIndex < pRef->iBufLen;)
{
// extract size of current head packet
pPkt0 = (NetGamePacketT *)(pRef->pBuffer + iIndex);
iPktSize = pPkt0->head.size;
// if the current packet matches what we want
iKind = (((NetGamePacketT *)(pRef->pBuffer + iIndex))->head.kind) & ~GAME_PACKET_SYNC;
if (uMask & (1 << iKind))
{
*ppPkt = pPkt0;
break;
}
else
{
// skip over the non-matching packets.
iIndex += iPktSize;
}
}
}
else if (ppPkt != NULL)
{
*ppPkt = NULL;
}
// return buffer size
return(iBufLen);
}
/*F*************************************************************************************************/
/*!
\Function NetGameLinkPeek
\Description
Peek into the buffer
\Input *pRef - reference pointer
\Input **ppPkt - (optional) storage for pointer to current packet data
\Output
int32_t - buffer size
\Version 12/19/00 (GWS)
*/
/*************************************************************************************************F*/
int32_t NetGameLinkPeek(NetGameLinkRefT *pRef, NetGamePacketT **ppPkt)
{
return(NetGameLinkPeek2(pRef, ppPkt, (unsigned)~0));
}
/*F*************************************************************************************************/
/*!
\Function NetGameLinkRecv2
\Description
Outgoing packet stream to upper layer
\Input *pRef - reference pointer
\Input *pBuf - storage for packet list (1 or more)
\Input iLen - size packet list (1=one packet)
\Input uMask - which packet types we wanted. bitmask, addressed by GAME_PACKET values
\Output
int32_t - 0=no data available, else number of packets received
\Version 01/11/10 (jrainy)
*/
/*************************************************************************************************F*/
int32_t NetGameLinkRecv2(NetGameLinkRefT *pRef, NetGamePacketT *pBuf, int32_t iLen, uint32_t uMask)
{
NetGamePacketT *pPkt;
uint32_t uCurrTick = NetTick();
int32_t iIndex, iKind;
int32_t iLenRead = 0;
int32_t iPktSize = 0;
// disable callback
NetCritEnter(&pRef->Crit);
// if no data in queue, check with lower layer
if (pRef->iBufLen == 0)
{
while (_NetGameLinkRecvPacket(pRef, uCurrTick) > 0)
;
}
// see if there is anything to process
if (pRef->iBufLen > 0)
{
// while going through our buffer of received packets
for (iIndex = 0; iIndex < pRef->iBufLen;)
{
// extract size of current head packet
pPkt = (NetGamePacketT *)(pRef->pBuffer + iIndex);
iPktSize = pPkt->head.size;
// don't access pkt past here, as the memmove will overwrite it
// if the current packet matches what we want
iKind = (((NetGamePacketT *)(pRef->pBuffer + iIndex))->head.kind) & ~GAME_PACKET_SYNC;
if (uMask & (1 << iKind))
{
// if we can fit it in
if ((iPktSize <= iLen) || (iLen == 1))
{
// copy the packet to return buffer
ds_memcpy(pBuf, pRef->pBuffer + iIndex, iPktSize);
iLen -= iPktSize;
pBuf = (NetGamePacketT *)(((char*)pBuf) + iPktSize);
iLenRead += iPktSize;
// remove from our list of received packets
memmove(pRef->pBuffer + iIndex, pRef->pBuffer + iIndex + iPktSize, pRef->iBufLen - iIndex - iPktSize);
pRef->iBufLen -= iPktSize;
// if len was 1, it's now negative, we got our packet, bail out
if (iLen < 0)
{
break;
}
}
else
{
// bail out, we got all we could
break;
}
}
else
{
// skip over the non-matching packets.
iIndex += iPktSize;
}
}
}
// enable access
NetCritLeave(&pRef->Crit);
// return length
return(iLenRead);
}
/*F*************************************************************************************************/
/*!
\Function NetGameLinkRecv
\Description
Outgoing packet stream to upper layer
\Input *pRef - reference pointer
\Input *pBuf - storage for packet list (1 or more)
\Input iLen - size packet list (1=one packet)
\Input bDist - whether dist packets are to be received. Use FALSE.
\Output
int32_t - 0=no data available, else number of packets received
\Version 01/11/10 (jrainy)
*/
/*************************************************************************************************F*/
int32_t NetGameLinkRecv(NetGameLinkRefT *pRef, NetGamePacketT *pBuf, int32_t iLen, uint8_t bDist)
{
uint32_t uDistMask = (1 << GAME_PACKET_INPUT) |
(1 << GAME_PACKET_INPUT_MULTI) |
(1 << GAME_PACKET_INPUT_MULTI_FAT) |
(1 << GAME_PACKET_STATS) |
(1 << GAME_PACKET_INPUT_FLOW) |
(1 << GAME_PACKET_INPUT_META);
return(NetGameLinkRecv2(pRef, pBuf, iLen, (bDist ? uDistMask : ~uDistMask) & ~(1 << GAME_PACKET_LINK_STRM)));
}
/*F*************************************************************************************************/
/*!
\Function NetGameLinkControl
\Description
NetGameLink control function. Different selectors control different behaviors.
\Input *pRef - reference pointer
\Input iControl - control selector
\Input iValue - selector-specific value
\Input pValue - selector-specific pointer
\Output
int32_t - selector specific, or negative if failure
\Notes
iControl can be one of the following:
\verbatim
lqos: set individual QoS packet size
rlmt: set redundant packet size limit
send: enable/disable sending
spam: set debug verbosity level
sqos: set QoS duration and interval
sync: enable/disable sync packets
\endverbatim
Unhandled selectors are passed through to the underlying comm module
\Version 07/07/03 (jbrookes)
*/
/*************************************************************************************************F*/
int32_t NetGameLinkControl(NetGameLinkRefT *pRef, int32_t iControl, int32_t iValue, void *pValue)
{
// set individual QoS packet size
if (iControl == 'lqos')
{
if ((iValue < NETGAMELINK_QOS_PACKETSIZE_MIN) || (iValue > NETGAME_DATAPKT_MAXSIZE))
{
NetPrintf(("netgamelink: [%p] invalid QoS packet size specified (%d). QoS packets must be >= %d and <= %d\n", pRef, iValue, NETGAMELINK_QOS_PACKETSIZE_MIN, NETGAME_DATAPKT_MAXSIZE));
iValue = (iValue < NETGAMELINK_QOS_PACKETSIZE_MIN ? NETGAMELINK_QOS_PACKETSIZE_MIN : iValue);
iValue = (iValue > NETGAME_DATAPKT_MAXSIZE ? NETGAME_DATAPKT_MAXSIZE : iValue);
}
NetPrintf(("netgamelink: [%p] QoS packet size set to %d\n", pRef, iValue));
pRef->uQosPacketSize = iValue;
return(0);
}
// set redundant packet size limit
if (iControl == 'rlmt')
{
NetPrintf(("netgamelink: [%p] updating redundant packet size limit for underlying comm (%d)\n", pRef, iValue));
return(pRef->pPort->Control(pRef->pPort, iControl, iValue, pValue));
}
// set send/recv value
if (iControl == 'send')
{
pRef->bSendEnabled = iValue;
return(1);
}
// set debug verbosity level
if (iControl == 'spam')
{
if (iValue >= 0 && iValue <= 5)
{
pRef->iVerbosity = iValue;
return(1);
}
return(0);
}
// set QoS duration and interval
if (iControl == 'sqos')
{
int32_t iValue2 = *(int32_t*)pValue;
if (pRef->uQosLastSendTime != 0)
{
NetPrintf(("netgamelink: [%p] cannot change QoS duration or interval while QoS is in progress or is already finished.", pRef));
return(-1);
}
if ((iValue < NETGAMELINK_QOS_DURATION_MIN) || (iValue > NETGAMELINK_QOS_DURATION_MAX))
{
NetPrintf(("netgamelink: [%p] invalid QoS duration period provided (%d). QoS duration must be >= %d and <= %s ms\n", pRef, iValue, NETGAMELINK_QOS_DURATION_MIN, NETGAMELINK_QOS_DURATION_MAX));
iValue = (iValue < NETGAMELINK_QOS_DURATION_MIN ? NETGAMELINK_QOS_DURATION_MIN : iValue);
iValue = (iValue > NETGAMELINK_QOS_DURATION_MAX ? NETGAMELINK_QOS_DURATION_MAX : iValue);
}
if ((iValue != 0) && ((iValue2 < NETGAMELINK_QOS_INTERVAL_MIN) || (iValue2 > iValue)))
{
NetPrintf(("netgamelink: [%p] invalid QoS interval provided (%d). QoS interval must be >= %d and <= 'QoS duration period'\n", pRef, iValue2, NETGAMELINK_QOS_INTERVAL_MIN));
iValue2 = (iValue2 < NETGAMELINK_QOS_INTERVAL_MIN ? NETGAMELINK_QOS_INTERVAL_MIN : iValue2);
iValue2 = (iValue2 > iValue ? iValue : iValue2);
}
NetPrintf(("netgamelink: [%p] QoS duration period set to %d ms %s\n", pRef, iValue, ((iValue == 0) ? "(QoS disabled)" : "")));
pRef->uQosPacketsToSend = (iValue == 0 ? 0 : iValue / iValue2);
NetPrintf(("netgamelink: [%p] QoS packet count set to %d packets %s\n", pRef, pRef->uQosPacketsToSend, ((pRef->uQosPacketsToSend == 0) ? "(QoS disabled)" : "")));
pRef->uQosSendInterval = iValue2;
NetPrintf(("netgamelink: [%p] QoS interval set to %d ms\n", pRef, pRef->uQosSendInterval));
return(0);
}
// queue checks (Deprecated use netgamelinkstatus versions instead) $$todo Remove in future release
if ((iControl == 'sque') || (iControl == 'slen'))
{
// get output queue position
int32_t iQueue = pRef->pPort->Send(pRef->pPort, NULL, 0, COMM_FLAGS_RELIABLE);
if (iControl == 'sque')
{
// if output queue position is one send queue is empty (assume error=empty)
iQueue = (iQueue > 0) ? (iQueue == 1) : TRUE;
}
return(iQueue);
}
// enable/disable sync packets
if (iControl == 'sync')
{
pRef->bSyncEnabled = iValue;
return(0);
}
// unhandled; pass through to comm module if available
return((pRef->pPort != NULL) ? pRef->pPort->Control(pRef->pPort, iControl, iValue, pValue) : -1);
}
/*F*************************************************************************************************/
/*!
\Function NetGameLinkUpdate
\Description
Provides NetGameLink with time, at regular interval. Need for streams handling.
Also handles QoS stage when during link startup.
\Input *pRef - reference pointer
\Output
uint32_t - zero
\Version 01/11/10 (jrainy)
*/
/*************************************************************************************************F*/
uint32_t NetGameLinkUpdate(NetGameLinkRefT *pRef)
{
// Are we currently in the QoS phase?
if ((pRef->uQosPacketsToSend > 0) || (pRef->uQosPacketsToRecv > 0))
{
// Send and receive QoS packets
_NetGameLinkUpdateQos(pRef);
}
else
{
// Handle normal link data
_NetGameLinkDrainRecvStream(pRef);
_NetGameLinkPollStream(pRef);
}
return(0);
}
/*F*************************************************************************************************/
/*!
\Function NetGameLinkCreateStream
\Description
Allocate a stream
\Input *pRef - module state reference
\Input iSubChan - number of subchannels (zero for a normal stream)
\Input iIdent - unique stream iIdent
\Input iInpLen - size of input buffer
\Input iOutLen - size of output buffer
\Input iSynLen - size of sync buffer
\Output
NetGameLinkStreamT * - new stream, or NULL if the iIdent was not unique
\Version 12/20/00 (GWS)
*/
/*************************************************************************************************F*/
NetGameLinkStreamT *NetGameLinkCreateStream(NetGameLinkRefT *pRef, int32_t iSubChan, int32_t iIdent, int32_t iInpLen, int32_t iOutLen, int32_t iSynLen)
{
NetGameLinkStreamT *pStream;
int32_t iInpSize;
char *pInpData;
// make sure the pipe identifier is unique
for (pStream = pRef->m_stream; pStream != NULL; pStream = pStream->pNext)
{
// dont create a duplicate stream
if (pStream->iIdent == iIdent)
{
NetPrintf(("netgamelink: [%p] error -- attempting to create duplicate stream '%c%c%c%c'\n",
pRef, (uint8_t)(iIdent>>24), (uint8_t)(iIdent>>16), (uint8_t)(iIdent>>8), (uint8_t)iIdent));
return(NULL);
}
}
// allocate a pipe record
if ((pStream = (NetGameLinkStreamT *) DirtyMemAlloc(sizeof(*pStream), NETGAMELINK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData)) == NULL)
{
NetPrintf(("netgamelink: [%p] unable to allocate stream\n", pRef));
return(NULL);
}
ds_memclr(pStream, sizeof(*pStream));
// setup the data fields
pStream->pClient = pRef;
pStream->iIdent = iIdent;
pStream->iSubchan = iSubChan;
pStream->Send = _NetGameLinkSendStream;
pStream->Recv = NULL;
// allocate input buffer plus input buffer tracking structure(s)
pStream->iInpMaxm = iInpLen;
iInpSize = sizeof(*pStream->pInp) * (pStream->iSubchan + 1);
pStream->pInp = DirtyMemAlloc(iInpSize + (iInpLen * (pStream->iSubchan + 1)), NETGAMELINK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData);
ds_memclr(pStream->pInp, iInpSize);
for (iSubChan = 0, pInpData = (char *)pStream->pInp + iInpSize; iSubChan < pStream->iSubchan+1; iSubChan++)
{
pStream->pInp[iSubChan].pInpData = pInpData;
pInpData += iInpLen;
}
// allocate output buffer
if (iOutLen < iInpLen)
{
iOutLen = iInpLen;
}
pStream->iOutMaxm = iOutLen;
pStream->pOutData = DirtyMemAlloc(iOutLen, NETGAMELINK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData);
// allocate sync buffer
pStream->iSynMaxm = iSynLen;
if (iSynLen > 0)
{
pStream->pSynData = DirtyMemAlloc(iSynLen, NETGAMELINK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData);
}
// put into list
pStream->pNext = pRef->m_stream;
pRef->m_stream = pStream;
return(pStream);
}
/*F*************************************************************************************************/
/*!
\Function NetGameLinkDestroyStream
\Description
Destroy a stream
\Input *pRef - reference pointer
\Input *pStream - pointer to stream to destroy
\Version 12/20/00 (GWS)
*/
/*************************************************************************************************F*/
void NetGameLinkDestroyStream(NetGameLinkRefT *pRef, NetGameLinkStreamT *pStream)
{
NetGameLinkStreamT **ppLink;
// make sure stream is valid
if (pStream != NULL)
{
// if stream is active, remove the link
if (pStream->pClient != NULL)
{
// locate the stream in the list for removal
for (ppLink = &pStream->pClient->m_stream; *ppLink != pStream; ppLink = &((*ppLink)->pNext))
{
// see if at end of list
if (*ppLink == NULL)
{
return;
}
}
// remove stream from list
*ppLink = pStream->pNext;
}
// now the tricky part-- make sure nobody is still processing this stream
// release the resources
if (pStream->pSynData != NULL)
{
DirtyMemFree(pStream->pSynData, NETGAMELINK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData);
}
DirtyMemFree(pStream->pInp, NETGAMELINK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData);
DirtyMemFree(pStream->pOutData, NETGAMELINK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData);
DirtyMemFree(pStream, NETGAMELINK_MEMID, pRef->iMemGroup, pRef->pMemGroupUserData);
}
}