Skip to content

Instantly share code, notes, and snippets.

@isilkor
Created October 16, 2015 19:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save isilkor/501c2a05901fb9af27c8 to your computer and use it in GitHub Desktop.
Save isilkor/501c2a05901fb9af27c8 to your computer and use it in GitHub Desktop.
/* control management */
#include "C4Include.h"
#ifndef BIG_C4INCLUDE
#include <C4Application.h>
#include <C4Game.h>
#include <C4GameControl.h>
#include <C4GameOverDlg.h>
#include <C4Record.h>
#include <C4Log.h>
#include <C4Network2Stats.h>
#endif
#ifdef _MSC_VER
#pragma warning (disable: 4355)
#endif
// *** C4GameControl
C4GameControl::C4GameControl()
: Network(this)
{
Default();
}
C4GameControl::~C4GameControl()
{
Clear();
}
bool C4GameControl::InitLocal(C4Client *pLocal)
{
eMode = CM_Local; fPreInit = fInitComplete = true;
fHost = true; iClientID = pLocal->getID();
ControlRate = 1;
// ok
return true;
}
bool C4GameControl::InitNetwork(C4Client *pLocal)
{
// network should already be initialized (by C4Network2)
if(!Network.IsEnabled())
return false;
// set mode
eMode = CM_Network; fPreInit = fInitComplete = true;
fHost = pLocal->isHost(); iClientID = pLocal->getID();
// control rate by parameters
ControlRate = Game.Parameters.ControlRate;
// ok
return true;
}
bool C4GameControl::InitReplay(C4Group &rGroup)
{
// open replay
pPlayback = new C4Playback();
if(!pPlayback->Open(rGroup))
{
LogFatal(LoadResStr("IDS_ERR_REPLAYREAD"));
delete pPlayback; pPlayback = NULL;
return false;
}
// set mode
eMode = CM_Replay; fInitComplete = true;
fHost = false; iClientID = C4ClientIDUnknown;
// control rate by parameters
ControlRate = Game.Parameters.ControlRate;
// just in case
StopRecord();
// ok
return true;
}
void C4GameControl::ChangeToLocal()
{
// changes from any given mode to local
// (emergency - think of network disconnect)
// remove all non-local clients
Game.Clients.RemoveRemote();
// activate local client
if(Game.Clients.getLocal())
Game.Clients.getLocal()->SetActivated(true);
// network: clear network
if(eMode == CM_Network)
{
Network.Clear();
if(Game.Network.isEnabled())
Game.Network.Clear();
}
// replay: close playback
else if(eMode == CM_Replay)
{ delete pPlayback; pPlayback = NULL; }
// we're now managing our own player info list; make sure counter works
Game.PlayerInfos.FixIDCounter();
// start the game, if we're not in the game over dialog
// (otherwise, clients start game when host disconnected!)
if (!C4GameOverDlg::IsShown()) Game.HaltCount = 0;
// set status
eMode = CM_Local; fHost = true;
ControlRate = 1;
}
void C4GameControl::OnGameSynchronizing()
{
// start record if desired
if (fRecordNeeded)
{
fRecordNeeded = false;
StartRecord(false, false);
}
}
bool C4GameControl::StartRecord(bool fInitial, bool fStreaming)
{
assert(fInitComplete);
// already recording?
if(pRecord) StopRecord();
// start
pRecord = new C4Record();
if(!pRecord->Start(fInitial))
{
delete pRecord; pRecord = NULL;
return false;
}
// streaming
if(fStreaming)
{
if(!pRecord->StartStreaming(fInitial) ||
!Game.Network.StartStreaming(pRecord))
{
delete pRecord; pRecord = NULL;
return false;
}
}
// runtime records executed through queue: Must record initial control
if (pExecutingControl)
pRecord->Rec(*pExecutingControl, Game.FrameCounter);
// ok
return true;
}
void C4GameControl::StopRecord(StdStrBuf *pRecordName, BYTE *pRecordSHA1)
{
if(pRecord)
{
Game.Network.FinishStreaming();
pRecord->Stop(pRecordName, pRecordSHA1);
// just delete
delete pRecord; pRecord = NULL;
}
}
void C4GameControl::RequestRuntimeRecord()
{
if (!IsRuntimeRecordPossible()) return; // cannot record
fRecordNeeded = true;
// request through a synchronize-call
// currnetly do not request, but start record with next gamesync, so network runtime join can be debugged
#ifndef DEBUGREC
Game.Control.DoInput(CID_Synchronize, new C4ControlSynchronize(false, true), CDT_Queue);
#endif
}
bool C4GameControl::IsRuntimeRecordPossible() const
{
// already requested?
if (fRecordNeeded) return false;
// not from replay
if (isReplay()) return false;
// not if recording already
if (isRecord()) return false;
// Record OK
return true;
}
bool C4GameControl::RecAddFile(const char *szLocalFilename, const char *szAddAs)
{
if (!isRecord() || !pRecord) return false;
return pRecord->AddFile(szLocalFilename, szAddAs);
}
void C4GameControl::Clear()
{
StopRecord();
ChangeToLocal();
Default();
}
void C4GameControl::Default()
{
Input.Clear();
Network.Clear();
eMode = CM_None;
fHost = fPreInit = fInitComplete = false;
iClientID = C4ClientIDUnknown;
pRecord = NULL;
pPlayback = NULL;
SyncChecks.Clear();
iPrepareStart = -1;
ControlRate = BoundBy<int>(Config.Network.ControlRate, 1, C4MaxControlRate);
ControlTick = 0;
SyncRate = C4SyncCheckRate;
DoSync = false;
fRecordNeeded = false;
pExecutingControl = NULL;
}
bool C4GameControl::Prepare()
{
assert(fInitComplete);
// Prepare control, return true if everything is ready for GameGo.
// Save start of preperation
if(!(Game.FrameCounter % ControlRate) && iPrepareStart == -1)
iPrepareStart = timeGetTime();
switch(eMode)
{
// full steam ahead
case CM_Local: case CM_Replay:
return true;
case CM_Network:
Network.Execute();
// deactivated and got control: request activate
if(Input.firstPkt() && !Game.Clients.getLocal()->isActivated())
Game.Network.RequestActivate();
// needs input?
while(Network.CtrlNeeded(Game.FrameCounter))
{
Network.DoInput(Input);
Input.Clear();
}
// control tick?
if(Game.FrameCounter % ControlRate)
return true;
// check GameGo
return Network.CtrlReady(ControlTick);
}
return false;
}
void C4GameControl::Execute()
{
// Execute all available control
assert(fInitComplete);
// control tick? replay must always be executed.
if(!isReplay() && Game.FrameCounter % ControlRate)
return;
// Get control
C4Control Control;
if(eMode == CM_Local)
{
// control = input
Control.Take(Input);
}
if(eMode == CM_Network)
{
// calc performance
Network.CalcPerformance(ControlTick, iPrepareStart);
// control = network input
if(!Network.GetControl(&Control, ControlTick))
{
LogFatal("Network: could not retrieve control from C4GameControlNetwork!");
return;
}
}
if(eMode == CM_Replay)
{
if(!pPlayback) { ChangeToLocal(); return; }
// control = replay data
pPlayback->ExecuteControl(&Control, Game.FrameCounter);
}
// Record: Save ctrl
if(pRecord)
pRecord->Rec(Control, Game.FrameCounter);
// debug: recheck PreExecute
assert(Control.PreExecute());
// execute
pExecutingControl = &Control;
Control.Execute();
Control.Clear();
pExecutingControl = NULL;
// statistics record
if (Game.pNetworkStatistics) Game.pNetworkStatistics->ExecuteControlFrame();
iPrepareStart = -1;
}
void C4GameControl::Ticks()
{
assert(fInitComplete);
if(!(Game.FrameCounter % ControlRate))
ControlTick++;
if(!(Game.FrameCounter % SyncRate))
DoSync = true;
// calc next tick without waiting for timer? (catchup cases)
if(eMode == CM_Network)
if(Network.CtrlOverflow(ControlTick))
{
Game.GameGo = true;
if(Network.GetBehind(ControlTick) >= 5)
if(Game.FrameCounter % ((Network.GetBehind(ControlTick) + 15) / 20))
Game.DoSkipFrame = true;
}
}
bool C4GameControl::CtrlTickReached(int32_t iTick)
{
// 1. control tick reached?
if(ControlTick < iTick) return false;
// 2. control tick?
if(Game.FrameCounter % ControlRate) return false;
// ok then
return true;
}
int32_t C4GameControl::getCtrlTick(int32_t iFrame) const
{
// returns control tick by frame. Note this is a guess, as the control rate
// can always change, so don't rely on the return value too much.
return iFrame / ControlRate + ControlTick - Game.FrameCounter / ControlRate;
}
int32_t C4GameControl::getNextControlTick() const
{
return ControlTick + (Game.FrameCounter % ControlRate ? 1 : 0);
}
void C4GameControl::AdjustControlRate(int32_t iBy)
{
// control host only
if(isCtrlHost())
Game.Control.DoInput(CID_Set, new C4ControlSet(C4CVT_ControlRate, iBy), CDT_Decide);
}
void C4GameControl::SetActivated(bool fnActivated)
{
fActivated = fnActivated;
if(eMode == CM_Network)
Network.SetActivated(fnActivated);
}
void C4GameControl::DoInput(C4PacketType eCtrlType, C4ControlPacket *pPkt, C4ControlDeliveryType eDelivery)
{
assert(fPreInit);
// check if the control can be executed
if(eDelivery == CDT_Direct || eDelivery == CDT_Private)
assert(!pPkt->Sync());
if(!fInitComplete)
assert(pPkt->Lobby());
// decide control type
if(eDelivery == CDT_Decide)
eDelivery = DecideControlDelivery();
// queue?
if(eDelivery == CDT_Queue)
{
// add, will be executed/sent later
Input.Add(eCtrlType, pPkt);
return;
}
// Network?
if(isNetwork())
{
Network.DoInput(eCtrlType, pPkt, eDelivery);
}
else
{
// Local mode: execute at once
ExecControlPacket(eCtrlType, pPkt);
delete pPkt;
}
}
void C4GameControl::DbgRec(C4RecordChunkType eType, const uint8_t *pData, size_t iSize)
{
#ifdef DEBUGREC
if (DoNoDebugRec>0) return;
// record data
if(pRecord)
pRecord->Rec(Game.FrameCounter,
DecompileToBuf<StdCompilerBinWrite>(C4PktDebugRec(eType, StdBuf(pData, iSize))),
eType);
// check against playback
if (pPlayback)
pPlayback->Check(eType, pData, iSize);
#endif // DEBUGREC
}
C4ControlDeliveryType C4GameControl::DecideControlDelivery()
{
// network
if(eMode == CM_Network)
return Network.DecideControlDelivery();
// use direct
return CDT_Direct;
}
void C4GameControl::DoSyncCheck()
{
// only once
if(!DoSync) return;
DoSync = false;
// create sync check
C4ControlSyncCheck *pSyncCheck = new C4ControlSyncCheck();
pSyncCheck->Set();
// host?
if(fHost)
// add sync check to control queue or send it directly if the queue isn't active
DoInput(CID_SyncCheck, pSyncCheck, fActivated ? CDT_Queue : CDT_Direct);
else
{
// already have sync check?
C4ControlSyncCheck* pSyncCheck2 = GetSyncCheck(Game.FrameCounter);
if(!pSyncCheck2)
// add to sync check array
SyncChecks.Add(CID_SyncCheck, pSyncCheck);
else
{
// check
pSyncCheck->Execute();
delete pSyncCheck;
}
}
// remove old
RemoveOldSyncChecks();
}
void C4GameControl::ExecControl(const C4Control &rCtrl)
{
// nothing to do?
if(!rCtrl.firstPkt()) return;
// execute it
if(!rCtrl.PreExecute()) Log("Control: PreExecute failed for sync control!");
rCtrl.Execute();
// record
if(pRecord)
pRecord->Rec(rCtrl, Game.FrameCounter);
}
void C4GameControl::ExecControlPacket(C4PacketType eCtrlType, C4ControlPacket *pPkt)
{
// execute it
if(!pPkt->PreExecute()) Log("Control: PreExecute failed for direct control!");
pPkt->Execute();
// record it
if(pRecord)
pRecord->Rec(eCtrlType, pPkt, Game.FrameCounter);
}
C4ControlSyncCheck *C4GameControl::GetSyncCheck(int32_t iTick)
{
for(C4IDPacket *pPkt = SyncChecks.firstPkt(); pPkt; pPkt = SyncChecks.nextPkt(pPkt))
{
// should be a sync check
if(pPkt->getPktType() != CID_SyncCheck) continue;
// get sync check
C4ControlSyncCheck *pCheck = static_cast<C4ControlSyncCheck *>(pPkt->getPkt());
// packet that's searched for?
if(pCheck->getFrame() == iTick)
return pCheck;
}
return NULL;
}
void C4GameControl::RemoveOldSyncChecks()
{
C4IDPacket *pNext;
for(C4IDPacket *pPkt = SyncChecks.firstPkt(); pPkt; pPkt = pNext)
{
pNext = SyncChecks.nextPkt(pPkt);
// should be a sync check
if(pPkt->getPktType() != CID_SyncCheck) continue;
// remove?
C4ControlSyncCheck *pCheck = static_cast<C4ControlSyncCheck *>(pPkt->getPkt());
if(pCheck->getFrame() < Game.FrameCounter - C4SyncCheckMaxKeep)
SyncChecks.Delete(pPkt);
}
}
/* control management */
#ifndef INC_C4GameControl
#define INC_C4GameControl
#include "C4Control.h"
#include "C4Network2Client.h"
#include "C4Record.h"
enum C4ControlMode
{
CM_None,
CM_Local, // control = input
CM_Network, // control = input + network input
CM_Replay, // control = replay
};
enum C4ControlDeliveryType
{
CDT_Queue = 0, // Send in control queue (sync)
CDT_Sync = 1, // Send, delay execution until net is sync (sync)
CDT_Direct = 2, // Send directly to all clients (not sync)
CDT_Private = 3, // Send only to some clients (not sync, obviously)
CDT_Decide, // Use whatever sync mode seems fastest atm (sync)
};
// Additional notes / requirements:
// * Direct control always reaches all clients faster than queue control.
// * Sync is the only synchronous control mode were it's garantueed that
// the control is actually executed within a fixed time.
// * CDT_Decide is guesswork, control isn't garantueed to be faster.
#include "C4GameControlNetwork.h"
#ifdef _DEBUG
const int32_t C4SyncCheckRate = 1,
#else
const int32_t C4SyncCheckRate = 100,
#endif
C4SyncCheckMaxKeep = 50;
class C4GameControl
{
friend class C4ControlSyncCheck;
friend class C4GameControlNetwork;
public:
C4GameControl();
~C4GameControl();
public:
C4Control Input;
C4GameControlNetwork Network;
protected:
C4ControlMode eMode;
bool fPreInit, fInitComplete;
bool fHost; // (set for local, too)
bool fActivated;
bool fRecordNeeded;
int32_t iClientID;
C4Record *pRecord;
C4Playback *pPlayback;
C4Control SyncChecks;
C4GameControlClient *pClients;
int32_t iPrepareStart;
C4Control *pExecutingControl; // Control that is in the process of being executed - needed by non-initial records
public:
// ticks
int32_t ControlRate;
int32_t ControlTick;
int32_t SyncRate;
bool DoSync;
public:
// configuration
bool isLocal() const { return eMode == CM_Local; }
bool isNetwork() const { return eMode == CM_Network; }
bool isReplay() const { return eMode == CM_Replay; }
bool isCtrlHost() const { return fHost; }
bool isRecord() const { return !! pRecord; }
int32_t ClientID() const { return iClientID; }
bool SyncMode() const { return eMode != CM_Local || pRecord; }
bool NoInput() const { return isReplay(); }
// client list
C4GameControlClient *getClient(int32_t iID);
C4GameControlClient *getClient(const char *szName);
// initialization
bool InitLocal(C4Client *pLocal);
bool InitNetwork(C4Client *pLocal);
bool InitReplay(C4Group &rGroup);
void ChangeToLocal();
void Clear();
void Default();
// records
bool StartRecord(bool fInitial, bool fStreaming);
void StopRecord(StdStrBuf *pRecordName = NULL, BYTE *pRecordSHA1 = NULL);
void RequestRuntimeRecord();
bool IsRuntimeRecordPossible() const;
bool RecAddFile(const char *szLocalFilename, const char *szAddAs);
// execution
bool Prepare();
void Execute();
void Ticks();
// public helpers
bool CtrlTickReached(int32_t iTick);
int32_t getCtrlTick(int32_t iFrame) const;
int32_t getNextControlTick() const;
// control rate
void AdjustControlRate(int32_t iBy);
bool KeyAdjustControlRate(int32_t iBy)
{ AdjustControlRate(iBy); return true; }
// activation
void SetActivated(bool fActivated);
// input
void DoInput(C4PacketType eCtrlType, C4ControlPacket *pPkt, C4ControlDeliveryType eDelivery);
void DbgRec(C4RecordChunkType eType, const uint8_t *pData=NULL, size_t iSize=0); // record debug stuff
C4ControlDeliveryType DecideControlDelivery();
// sync check
void DoSyncCheck();
// execute and record control (by self or C4GameControlNetwork)
void ExecControl(const C4Control &rCtrl);
void ExecControlPacket(C4PacketType eCtrlType, class C4ControlPacket *pPkt);
void OnGameSynchronizing(); // start record if desired
protected:
// sync checks
C4ControlSyncCheck *GetSyncCheck(int32_t iTick);
void RemoveOldSyncChecks();
};
#endif // INC_C4GameControl
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment