-
-
Save isilkor/501c2a05901fb9af27c8 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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