openclonk/src/network/C4GameControlNetwork.cpp

918 lines
26 KiB
C++

/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
* Copyright (c) 2009-2016, The OpenClonk Team and contributors
*
* Distributed under the terms of the ISC license; see accompanying file
* "COPYING" for details.
*
* "Clonk" is a registered trademark of Matthes Bender, used with permission.
* See accompanying file "TRADEMARK" for details.
*
* To redistribute this file separately, substitute the full license texts
* for the above references.
*/
/* control managament: network part */
#include "C4Include.h"
#include "network/C4GameControlNetwork.h"
#include "control/C4GameControl.h"
#include "game/C4Application.h"
#include "game/C4GraphicsSystem.h"
// *** C4GameControlNetwork
C4GameControlNetwork::C4GameControlNetwork(C4GameControl *pnParent)
: fEnabled(false), fRunning(false), iClientID(C4ClientIDUnknown),
fActivated(false), iTargetTick(-1),
iControlPreSend(1), tWaitStart(C4TimeMilliseconds::PositiveInfinity), iAvgControlSendTime(0), iTargetFPS(38),
iControlSent(0), iControlReady(0),
pCtrlStack(nullptr),
tNextControlRequest(0),
pParent(pnParent)
{
assert(pParent);
}
C4GameControlNetwork::~C4GameControlNetwork()
{
Clear();
}
bool C4GameControlNetwork::Init(int32_t inClientID, bool fnHost, int32_t iStartTick, bool fnActivated, C4Network2 *pnNetwork) // by main thread
{
if (IsEnabled()) Clear();
// init
iClientID = inClientID; fHost = fnHost;
::Control.ControlTick = iStartTick;
iControlSent = iControlReady = ::Control.getNextControlTick() - 1;
fActivated = fnActivated;
pNetwork = pnNetwork;
// check
CheckCompleteCtrl(false);
// make sure no control has been lost
pNetwork->Clients.BroadcastMsgToConnClients(MkC4NetIOPacket(PID_ControlReq, C4PacketControlReq(iControlReady + 1)));
// ok
fEnabled = true; fRunning = false;
iTargetFPS = 38;
tNextControlRequest = C4TimeMilliseconds::Now() + C4ControlRequestInterval;
tWaitStart = C4TimeMilliseconds::PositiveInfinity;
return true;
}
void C4GameControlNetwork::Clear() // by main thread
{
fEnabled = false; fRunning = false;
iAvgControlSendTime = 0;
ClearCtrl(); ClearClients();
// clear sync control
SyncControl.Clear();
while (pSyncCtrlQueue)
{
C4GameControlPacket *pPkt = pSyncCtrlQueue;
pSyncCtrlQueue = pPkt->pNext;
delete pPkt;
}
}
void C4GameControlNetwork::Execute() // by main thread
{
// Control ticks only
if (Game.FrameCounter % ::Control.ControlRate)
return;
// Save time the control tick was reached
tWaitStart = C4TimeMilliseconds::Now();
// Execute any queued sync control
ExecQueuedSyncCtrl();
}
bool C4GameControlNetwork::CtrlReady(int32_t iTick) // by main thread
{
// check for complete control and pack it
CheckCompleteCtrl(false);
// control ready?
return iControlReady >= iTick;
}
bool C4GameControlNetwork::GetControl(C4Control *pCtrl, int32_t iTick) // by main thread
{
// lock
CStdLock CtrlLock(&CtrlCSec);
// look for control
C4GameControlPacket *pPkt = getCtrl(C4ClientIDAll, iTick);
if (!pPkt)
return false;
// set
pCtrl->Clear();
pCtrl->Append(pPkt->getControl());
// calc performance
CalcPerformance(iTick);
tWaitStart = C4TimeMilliseconds::PositiveInfinity;
// ok
return true;
}
bool C4GameControlNetwork::ClientReady(int32_t iClientID, int32_t iTick) // by main thread
{
if (eMode == CNM_Central && !fHost) return true;
return !!getCtrl(iClientID, iTick);
}
int32_t C4GameControlNetwork::ClientPerfStat(int32_t iClientID) // by main thread
{
if (eMode == CNM_Central && !fHost) return true;
// get client
CStdLock ClientsLock(&ClientsCSec);
C4GameControlClient *pClient = getClient(iClientID);
// return performance
return pClient ? pClient->getPerfStat() : 0;
}
int32_t C4GameControlNetwork::ClientNextControl(int32_t iClientID) // by main thread
{
// get client
CStdLock ClientsLock(&ClientsCSec);
C4GameControlClient *pClient = getClient(iClientID);
// return performance
return pClient ? pClient->getNextControl() : 0;
}
bool C4GameControlNetwork::CtrlNeeded(int32_t iFrame) const // by main thread
{
if (!IsEnabled() || !fActivated) return false;
// check: should we send something at the moment?
int32_t iSendFor = pParent->getCtrlTick(iFrame + iControlPreSend);
// target tick set? do special check
if (iTargetTick >= 0 && iControlSent >= iTargetTick) return false;
// control sent for this ctrl tick?
return iSendFor > iControlSent;
}
void C4GameControlNetwork::DoInput(const C4Control &Input) // by main thread
{
if (!fEnabled) return;
// pack
C4GameControlPacket *pCtrl = new C4GameControlPacket();
pCtrl->Set(iClientID, iControlSent+1, Input);
// client in central or async mode: send to host (will resend it to the other clients)
C4NetIOPacket CtrlPkt = MkC4NetIOPacket(PID_Control, *pCtrl);
if (eMode != CNM_Decentral)
{
if (!fHost)
if (!pNetwork->Clients.SendMsgToHost(CtrlPkt))
Application.InteractiveThread.ThreadLog("Failed to send control to host!");
}
// decentral mode: always broadcast to everybody
else
{
assert (eMode == CNM_Decentral);
if (!pNetwork->Clients.BroadcastMsgToClients(CtrlPkt))
Application.InteractiveThread.ThreadLog("Failed to broadcast control!");
}
// add to list
AddCtrl(pCtrl);
// ok, control is sent for this control tick
iControlSent++;
// ctrl complete?
CheckCompleteCtrl(false);
}
void C4GameControlNetwork::DoInput(C4PacketType eCtrlType, C4ControlPacket *pCtrl, C4ControlDeliveryType eDelivery) // by main thread
{
if (!fEnabled) return;
// Create packet
C4PacketControlPkt CtrlPkt(eDelivery, C4IDPacket(eCtrlType, pCtrl, false));
switch (eDelivery)
{
// Sync control
case CDT_Sync:
{
if (!fHost)
{
// Client: send to host
if (!::Network.Clients.SendMsgToHost(MkC4NetIOPacket(PID_ControlPkt, CtrlPkt)))
{ LogFatal("Network: could not send direct control packet!"); break; }
delete pCtrl;
}
else
{
// Host: send to all clients
::Network.Clients.BroadcastMsgToClients(MkC4NetIOPacket(PID_ControlPkt, CtrlPkt));
// Execute at once, if possible
if (::Network.isFrozen())
{
pParent->ExecControlPacket(eCtrlType, pCtrl);
delete pCtrl;
C4PacketExecSyncCtrl Pkt(pParent->ControlTick);
::Network.Clients.BroadcastMsgToClients(MkC4NetIOPacket(PID_ExecSyncCtrl, Pkt));
}
else
{
// Safe back otherwise
SyncControl.Add(eCtrlType, pCtrl);
// And sync
::Network.Sync();
}
}
}
break;
// Direct/private control:
case CDT_Direct:
case CDT_Private:
{
// Send to all clients
if (!::Network.Clients.BroadcastMsgToClients(MkC4NetIOPacket(PID_ControlPkt, CtrlPkt)))
{ LogFatal("Network: could not send direct control packet!"); break; }
// Exec at once
pParent->ExecControlPacket(eCtrlType, pCtrl);
delete pCtrl;
}
break;
// Only some delivery types support single packets (queue control must be tick-stamped)
default:
assert(false);
}
}
C4ControlDeliveryType C4GameControlNetwork::DecideControlDelivery() const
{
// This doesn't make sense for clients
if (!fHost)
return CDT_Queue;
// Decide the fastest control delivery type atm. Note this is a guess.
// Control sent with the returned delivery type may in theory be delayed infinitely.
if (::Network.isFrozen())
return CDT_Sync;
if (!Game.Clients.getLocal()->isActivated())
return CDT_Sync;
return CDT_Queue;
}
void C4GameControlNetwork::ExecSyncControl() // by main thread
{
assert(fHost);
// This is a callback from C4Network informing that a point where accumulated sync control
// can be executed has been reached (it's "momentarily" safe to execute)
// Nothing to do? Save some sweat.
if (!SyncControl.firstPkt())
return;
// So let's spread the word, so clients will call ExecSyncControl, too.
C4PacketExecSyncCtrl Pkt(pParent->ControlTick);
::Network.Clients.BroadcastMsgToClients(MkC4NetIOPacket(PID_ExecSyncCtrl, Pkt));
// Execute it
ExecSyncControl(Pkt.getControlTick());
}
void C4GameControlNetwork::ExecSyncControl(int32_t iControlTick) // by main thread
{
// Nothing to do?
if (!SyncControl.firstPkt())
return;
// Copy control and clear
C4Control Control = SyncControl;
SyncControl.Clear();
// Given control tick reached?
if (pParent->ControlTick == iControlTick)
pParent->ExecControl(Control);
else if (pParent->ControlTick > iControlTick)
// The host should make sure this doesn't happen.
LogF("Network: Fatal: got sync control to execute for ctrl tick %d, but already in ctrl tick %d!", iControlTick, pParent->ControlTick);
else
// This sync control must be executed later, so safe it back
AddSyncCtrlToQueue(Control, iControlTick);
}
void C4GameControlNetwork::AddClient(int32_t iClientID, const char *szName) // by main thread
{
// security
if (!fEnabled || getClient(iClientID)) return;
// create new
C4GameControlClient *pClient = new C4GameControlClient();
pClient->Set(iClientID, szName);
pClient->SetNextControl(::Control.ControlTick);
// add client
AddClient(pClient);
}
void C4GameControlNetwork::ClearClients()
{
CStdLock ClientsLock(&ClientsCSec);
while (pClients) { C4GameControlClient *pClient = pClients; RemoveClient(pClient); delete pClient; }
}
void C4GameControlNetwork::CopyClientList(const C4ClientList &rClients)
{
CStdLock ClientLock(&ClientsCSec);
// create local copy of activated client list
ClearClients();
C4Client *pClient = nullptr;
while ((pClient = rClients.getClient(pClient)))
if (pClient->isActivated())
AddClient(pClient->getID(), pClient->getName());
}
void C4GameControlNetwork::SetRunning(bool fnRunning, int32_t inTargetTick) // by main thread
{
assert(fEnabled);
// check for redundant update, stop if running (safety)
if (fRunning == fnRunning && iTargetTick == inTargetTick) return;
fRunning = false;
// set
iTargetTick = inTargetTick;
fRunning = fnRunning;
// run?
if (fRunning)
{
// refresh client list
CopyClientList(Game.Clients);
// check for complete ctrl
CheckCompleteCtrl(false);
}
}
void C4GameControlNetwork::SetActivated(bool fnActivated) // by main thread
{
assert(fEnabled);
// no change? ignore
if (fActivated == fnActivated) return;
// set
fActivated = fnActivated;
// Activated? Start to send control at next tick
if (fActivated)
iControlSent = ::Control.getNextControlTick() - 1;
}
void C4GameControlNetwork::SetCtrlMode(C4GameControlNetworkMode enMode) // by main thread
{
assert(fEnabled);
// no change?
if (eMode == enMode) return;
// set mode
eMode = enMode;
// changed to decentral? rebroadcast all own control
if (enMode == CNM_Decentral)
{
CStdLock CtrlLock(&CtrlCSec); C4GameControlPacket *pPkt;
for (int32_t iCtrlTick = ::Control.ControlTick; (pPkt = getCtrl(iClientID, iCtrlTick)); iCtrlTick++)
::Network.Clients.BroadcastMsgToClients(MkC4NetIOPacket(PID_Control, *pPkt));
}
else if (enMode == CNM_Central && fHost)
{
CStdLock CtrlLock(&CtrlCSec); C4GameControlPacket *pPkt;
for (int32_t iCtrlTick = ::Control.ControlTick; (pPkt = getCtrl(C4ClientIDAll, iCtrlTick)); iCtrlTick++)
::Network.Clients.BroadcastMsgToClients(MkC4NetIOPacket(PID_Control, *pPkt));
}
}
void C4GameControlNetwork::CalcPerformance(int32_t iCtrlTick)
{
CStdLock ControlLock(&CtrlCSec);
CStdLock ClientLock(&ClientsCSec);
// should only be called if ready
assert(CtrlReady(iCtrlTick));
// calc perfomance for all clients
int32_t iClientsPing=0; int32_t iPingClientCount=0; int32_t iNumTunnels=0; int32_t iHostPing=0;
for (C4GameControlClient *pClient = pClients; pClient; pClient = pClient->pNext)
{
// Some rudimentary PreSend-calculation
// get associated connection - nullptr for self
C4Network2Client *pNetClt = ::Network.Clients.GetClientByID(pClient->getClientID());
if (pNetClt && !pNetClt->isLocal())
{
C4Network2IOConnection *pConn = pNetClt->getMsgConn();
if (!pConn)
// remember tunnel
++iNumTunnels;
else
// store ping
if (pClient->getClientID() == C4ClientIDHost)
iHostPing = pConn->getPingTime();
else
{
iClientsPing += pConn->getPingTime();
++iPingClientCount;
}
}
// Performance statistics
// find control (may not be found, if we only got the complete ctrl)
C4GameControlPacket *pCtrl = getCtrl(pClient->getClientID(), iCtrlTick);
if (!pCtrl || tWaitStart.IsInfinite()) continue;
// calc stats
pClient->AddPerf(pCtrl->getTime() - tWaitStart);
}
// Now do PreSend-calcs based on ping times
int32_t iControlSendTime;
if (eMode == CNM_Decentral)
{
// average ping time
iControlSendTime = (iClientsPing + iHostPing * (iNumTunnels + 1)) / (iPingClientCount + iNumTunnels + 1);
// decentral mode: Only half the ping is used if there are no tunnels
if (!iNumTunnels) iControlSendTime /= 2;
}
else
{
// central mode: Control must go to host and back
iControlSendTime = iHostPing;
}
// calc some average
if (iControlSendTime)
{
iAvgControlSendTime = (iAvgControlSendTime * 149 + iControlSendTime * 1000) / 150;
// now calculate the all-time optimum PreSend there is
int32_t iBestPreSend = Clamp((iTargetFPS * iAvgControlSendTime) / 1000000 + 1, 1, 15);
// fixed PreSend?
if (iTargetFPS <= 0) iBestPreSend = -iTargetFPS;
// Ha! Set it!
if (getControlPreSend() != iBestPreSend)
{
setControlPreSend(iBestPreSend);
::GraphicsSystem.FlashMessage(FormatString("PreSend: %d - TargetFPS: %d", iBestPreSend, iTargetFPS).getData());
}
}
}
void C4GameControlNetwork::HandlePacket(char cStatus, const C4PacketBase *pPacket, C4Network2IOConnection *pConn)
{
// security
if (!pConn)
return;
#define GETPKT(type, name) \
assert(pPacket); const type &name = \
static_cast<const type &>(*pPacket);
switch (cStatus)
{
case PID_Control: // control
{
GETPKT(C4GameControlPacket, rPkt)
HandleControl(pConn->getClientID(), rPkt);
}
break;
case PID_ControlReq: // control request
{
if (!IsEnabled()) break;
if (pConn->isClosed() || !pConn->isAccepted()) break;
GETPKT(C4PacketControlReq, rPkt)
HandleControlReq(rPkt, pConn);
}
break;
case PID_ControlPkt: // single control packet (main thread!)
{
GETPKT(C4PacketControlPkt, rPkt)
// security
if (!fEnabled) break;
if (rPkt.getCtrl().getPktType() < CID_First) break;
// create copy (HandleControlPkt will store or delete)
C4IDPacket Copy(rPkt.getCtrl());
// some sanity checks
C4ControlPacket *pCtrlPkt = static_cast<C4ControlPacket *>(Copy.getPkt());
if (!pConn->isHost() && pConn->getClientID() != pCtrlPkt->getByClient())
break;
// handle
HandleControlPkt(Copy.getPktType(), pCtrlPkt, rPkt.getDelivery());
Copy.Default();
}
break;
case PID_ExecSyncCtrl:
{
GETPKT(C4PacketExecSyncCtrl, rPkt)
// security
if (!fEnabled) break;
// handle
ExecSyncControl(rPkt.getControlTick());
}
break;
}
#undef GETPKT
}
void C4GameControlNetwork::OnResComplete(C4Network2Res *pRes)
{
// player?
if (pRes->getType() == NRT_Player)
// check for ctrl ready
CheckCompleteCtrl(true);
}
void C4GameControlNetwork::HandleControl(int32_t iByClientID, const C4GameControlPacket &rPkt)
{
// already got that control? just ignore
if (getCtrl(rPkt.getClientID(), rPkt.getCtrlTick())) return;
// create copy, add to list
C4GameControlPacket *pCopy = new C4GameControlPacket(rPkt);
AddCtrl(pCopy);
// check: control complete?
if (IsEnabled())
CheckCompleteCtrl(true);
// note that C4GameControlNetwork will save incoming control even before
// Init() was called.
}
void C4GameControlNetwork::HandleControlReq(const C4PacketControlReq &rPkt, C4Network2IOConnection *pConn)
{
CStdLock CtrlLock(&CtrlCSec);
for (int iTick = rPkt.getCtrlTick(); ; iTick++)
{
// search complete control
C4GameControlPacket *pCtrl = getCtrl(C4ClientIDAll, iTick);
if (pCtrl)
{
// send
pConn->Send(MkC4NetIOPacket(PID_Control, *pCtrl));
continue;
}
// send everything we have for this tick (this is an emergency case, so efficiency
// isn't that important for now).
bool fFound = false;
for (pCtrl = pCtrlStack; pCtrl; pCtrl = pCtrl->pNext)
if (pCtrl->getCtrlTick() == iTick)
{
pConn->Send(MkC4NetIOPacket(PID_Control, *pCtrl));
fFound = true;
}
// nothing found for this tick?
if (!fFound) break;
}
}
void C4GameControlNetwork::HandleControlPkt(C4PacketType eCtrlType, C4ControlPacket *pCtrl, C4ControlDeliveryType eType) // main thread
{
// direct control? execute at once
if (eType == CDT_Direct || eType == CDT_Private)
{
pParent->ExecControlPacket(eCtrlType, pCtrl);
delete pCtrl;
return;
}
// sync ctrl from client? resend
if (fHost && eType == CDT_Sync)
{
DoInput(eCtrlType, pCtrl, eType);
return;
}
// Execute queued control first
ExecQueuedSyncCtrl();
// Execute at once, if possible
if (::Network.isFrozen())
{
pParent->ExecControlPacket(eCtrlType, pCtrl);
delete pCtrl;
}
else
{
// Safe back otherwise
SyncControl.Add(eCtrlType, pCtrl);
}
}
C4GameControlClient *C4GameControlNetwork::getClient(int32_t iID) // by both
{
CStdLock ClientsLock(&ClientsCSec);
for (C4GameControlClient *pClient = pClients; pClient; pClient = pClient->pNext)
if (pClient->getClientID() == iID)
return pClient;
return nullptr;
}
void C4GameControlNetwork::AddClient(C4GameControlClient *pClient) // by main thread
{
if (!pClient) return;
// lock
CStdLock ClientsLock(&ClientsCSec);
// add (ordered)
C4GameControlClient *pPrev = nullptr, *pPos = pClients;
for (; pPos; pPrev = pPos, pPos = pPos->pNext)
if (pPos->getClientID() > pClient->getClientID())
break;
// insert
(pPrev ? pPrev->pNext : pClients) = pClient;
pClient->pNext = pPos;
}
void C4GameControlNetwork::RemoveClient(C4GameControlClient *pClient) // by main thread
{
// obtain lock
CStdLock ClientsLock(&ClientsCSec);
// first client?
if (pClient == pClients)
pClients = pClient->pNext;
else
{
C4GameControlClient *pPrev;
for (pPrev = pClients; pPrev && pPrev->pNext; pPrev = pPrev->pNext)
if (pPrev->pNext == pClient)
break;
if (pPrev && pPrev->pNext == pClient)
pPrev->pNext = pClient->pNext;
}
}
C4GameControlPacket *C4GameControlNetwork::getCtrl(int32_t iClientID, int32_t iCtrlTick) // by both
{
// lock
CStdLock CtrlLock(&CtrlCSec);
// search
for (C4GameControlPacket *pCtrl = pCtrlStack; pCtrl; pCtrl = pCtrl->pNext)
if (pCtrl->getClientID() == iClientID && pCtrl->getCtrlTick() == iCtrlTick)
return pCtrl;
return nullptr;
}
void C4GameControlNetwork::AddCtrl(C4GameControlPacket *pCtrl) // by both
{
// lock
CStdLock CtrlLock(&CtrlCSec);
// add to list
pCtrl->pNext = pCtrlStack;
pCtrlStack = pCtrl;
}
void C4GameControlNetwork::ClearCtrl(int32_t iBeforeTick) // by main thread
{
// lock
CStdLock CtrlLock(&CtrlCSec);
// clear all old control
C4GameControlPacket *pCtrl = pCtrlStack, *pLast = nullptr;
while (pCtrl)
{
// old?
if (iBeforeTick == -1 || pCtrl->getCtrlTick() < iBeforeTick)
{
// unlink
C4GameControlPacket *pDelete = pCtrl;
pCtrl = pCtrl->pNext;
(pLast ? pLast->pNext : pCtrlStack) = pCtrl;
// delete
delete pDelete;
}
else
{
pLast = pCtrl;
pCtrl = pCtrl->pNext;
}
}
}
void C4GameControlNetwork::CheckCompleteCtrl(bool fSetEvent) // by both
{
// only when running (client list may be invalid)
if (!fRunning || !fEnabled) return;
CStdLock CtrlLock(&CtrlCSec);
CStdLock ClientLock(&ClientsCSec);
for (;;)
{
// control available?
C4GameControlPacket *pComplete = getCtrl(C4ClientIDAll, iControlReady + 1);
// get stop tick
int32_t iStopTick = iTargetTick;
if (pSyncCtrlQueue)
if (iStopTick < 0 || iStopTick > pSyncCtrlQueue->getCtrlTick())
iStopTick = pSyncCtrlQueue->getCtrlTick();
// pack control?
if (!pComplete)
{
// own control not ready?
if (fActivated && iControlSent <= iControlReady) break;
// no clients? no need to pack more than one tick into the future
if (!pClients && ::Control.ControlTick <= iControlReady) break;
// stop packing?
if (iStopTick >= 0 && iControlReady + 1 >= iStopTick) break;
// central mode and not host?
if (eMode != CNM_Decentral && !fHost) break;
// (try to) pack
if (!(pComplete = PackCompleteCtrl(iControlReady + 1)))
break;
}
// preexecute to check if it's ready for execute
if (!pComplete->getControl().PreExecute())
break;
// ok, control for this tick is ready
iControlReady++;
// tell the main thread to move on
if (fSetEvent && Game.GameGo && iControlReady >= ::Control.ControlTick)
Application.NextTick();
}
// clear old ctrl
if (::Control.ControlTick >= C4ControlBacklog)
ClearCtrl(::Control.ControlTick - C4ControlBacklog);
// target ctrl tick to reach?
if (iControlReady < iTargetTick &&
(!fActivated || iControlSent > iControlReady) &&
C4TimeMilliseconds::Now() >= tNextControlRequest)
{
Application.InteractiveThread.ThreadLogS("Network: Recovering: Requesting control for tick %d...", iControlReady + 1);
// make request
C4NetIOPacket Pkt = MkC4NetIOPacket(PID_ControlReq, C4PacketControlReq(iControlReady + 1));
// send control requests
if (eMode == CNM_Central)
::Network.Clients.SendMsgToHost(Pkt);
else
::Network.Clients.BroadcastMsgToConnClients(Pkt);
// set time for next request
tNextControlRequest = C4TimeMilliseconds::Now() + C4ControlRequestInterval;
}
}
C4GameControlPacket *C4GameControlNetwork::PackCompleteCtrl(int32_t iTick)
{
CStdLock CtrlLock(&CtrlCSec);
CStdLock ClientLock(&ClientsCSec);
// check if ctrl by all clients is ready
C4GameControlClient *pClient;
for (pClient = pClients; pClient; pClient = pClient->pNext)
if (!getCtrl(pClient->getClientID(), iTick))
break;
if (pClient)
{
// async mode: wait n extra frames for slow clients
const int iMaxWait = (Config.Network.AsyncMaxWait * 1000) / iTargetFPS;
if (eMode != CNM_Async || C4TimeMilliseconds::Now() <= tWaitStart + iMaxWait)
return nullptr;
}
// create packet
C4GameControlPacket *pComplete = new C4GameControlPacket();
pComplete->Set(C4ClientIDAll, iTick);
// pack everything in ID order (client list is ordered this way)
C4GameControlPacket *pCtrl;
for (pClient = pClients; pClient; pClient = pClient->pNext)
while (pClient->getNextControl() <= iTick)
{
// get control
int32_t iNextControl = pClient->getNextControl();
pCtrl = getCtrl(pClient->getClientID(), iNextControl);
if (!pCtrl) break;
pClient->SetNextControl(iNextControl + 1);
assert(pCtrl);
// add
pComplete->Add(*pCtrl);
}
// add to list
AddCtrl(pComplete);
// host: send to clients (central and async mode)
if (eMode != CNM_Decentral)
::Network.Clients.BroadcastMsgToConnClients(MkC4NetIOPacket(PID_Control, *pComplete));
// advance control request time
tNextControlRequest = std::max(tNextControlRequest, C4TimeMilliseconds::Now() + C4ControlRequestInterval);
// return
return pComplete;
}
void C4GameControlNetwork::AddSyncCtrlToQueue(const C4Control &Ctrl, int32_t iTick) // by main thread
{
// search place in queue. It's vitally important that new packets are placed
// behind packets for the same tick, so they will be executed in the right order.
C4GameControlPacket *pAfter = nullptr, *pBefore = pSyncCtrlQueue;
while (pBefore && pBefore->getCtrlTick() <= iTick)
{ pAfter = pBefore; pBefore = pBefore->pNext; }
// create
C4GameControlPacket *pnPkt = new C4GameControlPacket();
pnPkt->Set(C4ClientIDUnknown, iTick, Ctrl);
// insert
(pAfter ? pAfter->pNext : pSyncCtrlQueue) = pnPkt;
pnPkt->pNext = pBefore;
}
void C4GameControlNetwork::ExecQueuedSyncCtrl() // by main thread
{
// security
while (pSyncCtrlQueue && pSyncCtrlQueue->getCtrlTick() < pParent->ControlTick)
{
LogF("Network: Fatal: got sync control to execute for ctrl tick %d, but already in ctrl tick %d!", pSyncCtrlQueue->getCtrlTick(), pParent->ControlTick);
// remove it
C4GameControlPacket *pPkt = pSyncCtrlQueue;
pSyncCtrlQueue = pPkt->pNext;
delete pPkt;
}
// nothing to do?
if (!pSyncCtrlQueue || pSyncCtrlQueue->getCtrlTick() > pParent->ControlTick)
return;
// this should hold by now
assert(pSyncCtrlQueue && pSyncCtrlQueue->getCtrlTick() == pParent->ControlTick);
do
{
// execute it
pParent->ExecControl(pSyncCtrlQueue->getControl());
// remove the packet
C4GameControlPacket *pPkt = pSyncCtrlQueue;
pSyncCtrlQueue = pPkt->pNext;
delete pPkt;
}
while (pSyncCtrlQueue && pSyncCtrlQueue->getCtrlTick() == pParent->ControlTick);
// refresh copy of client list
CopyClientList(Game.Clients);
}
// *** C4GameControlPacket
C4GameControlPacket::C4GameControlPacket()
: iClientID(C4ClientIDUnknown),
tTime(C4TimeMilliseconds::Now())
{
}
C4GameControlPacket::C4GameControlPacket(const C4GameControlPacket &Pkt2)
: C4PacketBase(Pkt2), iClientID(Pkt2.getClientID()),
iCtrlTick(Pkt2.getCtrlTick()),
tTime(C4TimeMilliseconds::Now())
{
Ctrl.Copy(Pkt2.getControl());
}
C4GameControlPacket &C4GameControlPacket::operator = (const C4GameControlPacket &Pkt2)
{
Set(Pkt2.getClientID(), Pkt2.getCtrlTick(), Pkt2.getControl());
return *this;
}
void C4GameControlPacket::Set(int32_t inClientID, int32_t inCtrlTick)
{
iClientID = inClientID;
iCtrlTick = inCtrlTick;
}
void C4GameControlPacket::Set(int32_t inClientID, int32_t inCtrlTick, const C4Control &nCtrl)
{
iClientID = inClientID;
iCtrlTick = inCtrlTick;
Ctrl.Copy(nCtrl);
}
void C4GameControlPacket::Add(const C4GameControlPacket &Ctrl2)
{
Ctrl.Append(Ctrl2.getControl());
}
void C4GameControlPacket::CompileFunc(StdCompiler *pComp)
{
pComp->Value(mkNamingAdapt(mkIntPackAdapt(iClientID), "ClientID", C4ClientIDUnknown));
pComp->Value(mkNamingAdapt(mkIntPackAdapt(iCtrlTick), "CtrlTick", -1));
pComp->Value(mkNamingAdapt(Ctrl, "Ctrl"));
}
// *** C4GameControlClient
C4GameControlClient::C4GameControlClient()
: iClientID(C4ClientIDUnknown)
{
szName[0] = '\0';
}
int32_t C4GameControlClient::getPerfStat() const
{
return iPerformance / 100;
}
void C4GameControlClient::Set(int32_t inClientID, const char *sznName)
{
iClientID = inClientID;
SCopy(sznName, szName, sizeof(szName)-1);
}
void C4GameControlClient::AddPerf(int32_t iTime)
{
iPerformance += (iTime * 100 - iPerformance) / 100;
}