Skip to content

Commit 05ffd56

Browse files
committed
refactor(gameengine): Move game time related code into new FramePacer class (#1688)
1 parent 403696c commit 05ffd56

File tree

35 files changed

+390
-465
lines changed

35 files changed

+390
-465
lines changed

Core/GameEngine/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ set(GAMEENGINE_SRC
3939
# Include/Common/Errors.h
4040
Include/Common/file.h
4141
Include/Common/FileSystem.h
42+
Include/Common/FramePacer.h
4243
Include/Common/FrameRateLimit.h
4344
# Include/Common/FunctionLexicon.h
4445
Include/Common/GameAudio.h
@@ -570,6 +571,7 @@ set(GAMEENGINE_SRC
570571
# Source/Common/DamageFX.cpp
571572
# Source/Common/Dict.cpp
572573
# Source/Common/DiscreteCircle.cpp
574+
Source/Common/FramePacer.cpp
573575
Source/Common/FrameRateLimit.cpp
574576
# Source/Common/GameEngine.cpp
575577
# Source/Common/GameLOD.cpp
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
** Command & Conquer Generals Zero Hour(tm)
3+
** Copyright 2025 Electronic Arts Inc.
4+
**
5+
** This program is free software: you can redistribute it and/or modify
6+
** it under the terms of the GNU General Public License as published by
7+
** the Free Software Foundation, either version 3 of the License, or
8+
** (at your option) any later version.
9+
**
10+
** This program is distributed in the hope that it will be useful,
11+
** but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
** GNU General Public License for more details.
14+
**
15+
** You should have received a copy of the GNU General Public License
16+
** along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
#pragma once
19+
20+
#include "Common/FrameRateLimit.h"
21+
22+
23+
class FramePacer
24+
{
25+
public:
26+
27+
typedef UnsignedInt LogicTimeQueryFlags;
28+
enum LogicTimeQueryFlags_ CPP_11(: LogicTimeQueryFlags)
29+
{
30+
IgnoreFrozenTime = 1<<0, ///< Ignore frozen time for the query
31+
IgnoreHaltedGame = 1<<1, ///< Ignore halted game for the query
32+
};
33+
34+
FramePacer();
35+
~FramePacer();
36+
37+
void update(); ///< Signal that the app/render update is done and wait for the fps limit if applicable.
38+
39+
void setFramesPerSecondLimit( Int fps ); ///< Set the max update fps.
40+
Int getFramesPerSecondLimit() const; ///< Get the max update fps.
41+
Real getUpdateTime() const; ///< Get the last update delta time in seconds.
42+
Real getUpdateFps() const; ///< Get the last update fps.
43+
44+
void setTimeFrozen(Bool frozen); ///< Set time frozen. Allows scripted camera movement.
45+
void setGameHalted(Bool halted); ///< Set game halted. Does not allow scripted camera movement.
46+
Bool isTimeFrozen() const;
47+
Bool isGameHalted() const;
48+
49+
void setLogicTimeScaleFps( Int fps ); ///< Set the logic time scale fps and therefore scale the simulation time. Is capped by the max render fps and does not apply to network matches.
50+
Int getLogicTimeScaleFps() const; ///< Get the raw logic time scale fps value.
51+
void enableLogicTimeScale( Bool enable ); ///< Enable the logic time scale setup. If disabled, the simulation time scale is bound to the render frame time or network update time.
52+
Bool isLogicTimeScaleEnabled() const; ///< Check whether the logic time scale setup is enabled.
53+
Int getActualLogicTimeScaleFps(LogicTimeQueryFlags flags = 0) const; ///< Get the real logic time scale fps, depending on the max render fps, network state and enabled state.
54+
Real getActualLogicTimeScaleRatio(LogicTimeQueryFlags flags = 0) const; ///< Get the real logic time scale ratio, depending on the max render fps, network state and enabled state.
55+
Real getActualLogicTimeScaleOverFpsRatio(LogicTimeQueryFlags flags = 0) const; ///< Get the real logic time scale over render fps ratio, used to scale down steps in render updates to match logic updates.
56+
Real getLogicTimeStepSeconds(LogicTimeQueryFlags flags = 0) const; ///< Get the logic time step in seconds
57+
Real getLogicTimeStepMilliseconds(LogicTimeQueryFlags flags = 0) const; ///< Get the logic time step in milliseconds
58+
59+
protected:
60+
61+
FrameRateLimit m_frameRateLimit;
62+
63+
Int m_maxFPS; ///< Maximum frames per second for rendering
64+
Int m_logicTimeScaleFPS; ///< Maximum frames per second for logic time scale
65+
66+
Real m_updateTime; ///< Last update delta time in seconds
67+
68+
Bool m_enableLogicTimeScale;
69+
Bool m_isTimeFrozen;
70+
Bool m_isGameHalted;
71+
};
72+
73+
extern FramePacer* TheFramePacer;
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/*
2+
** Command & Conquer Generals Zero Hour(tm)
3+
** Copyright 2025 Electronic Arts Inc.
4+
**
5+
** This program is free software: you can redistribute it and/or modify
6+
** it under the terms of the GNU General Public License as published by
7+
** the Free Software Foundation, either version 3 of the License, or
8+
** (at your option) any later version.
9+
**
10+
** This program is distributed in the hope that it will be useful,
11+
** but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
** GNU General Public License for more details.
14+
**
15+
** You should have received a copy of the GNU General Public License
16+
** along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
#include "PreRTS.h"
19+
20+
#include "Common/FramePacer.h"
21+
22+
#include "GameClient/View.h"
23+
24+
#include "GameLogic/GameLogic.h"
25+
#include "GameLogic/ScriptEngine.h"
26+
27+
#include "GameNetwork/NetworkDefs.h"
28+
#include "GameNetwork/NetworkInterface.h"
29+
30+
31+
FramePacer* TheFramePacer = NULL;
32+
33+
FramePacer::FramePacer()
34+
{
35+
// Set the time slice size to 1 ms.
36+
timeBeginPeriod(1);
37+
38+
m_maxFPS = BaseFps;
39+
m_logicTimeScaleFPS = LOGICFRAMES_PER_SECOND;
40+
m_updateTime = 1.0f / BaseFps; // initialized to something to avoid division by zero on first use
41+
m_enableLogicTimeScale = FALSE;
42+
m_isTimeFrozen = FALSE;
43+
m_isGameHalted = FALSE;
44+
}
45+
46+
FramePacer::~FramePacer()
47+
{
48+
// Restore the previous time slice for Windows.
49+
timeEndPeriod(1);
50+
}
51+
52+
void FramePacer::update()
53+
{
54+
Bool allowFpsLimit = TheTacticalView->getTimeMultiplier()<=1 && !TheScriptEngine->isTimeFast();
55+
56+
// I'm disabling this in debug because many people need alt-tab capability. If you happen to be
57+
// doing performance tuning, please just change this on your local system. -MDC
58+
#if defined(RTS_DEBUG)
59+
if (allowFpsLimit)
60+
::Sleep(1); // give everyone else a tiny time slice.
61+
#endif
62+
63+
#if defined(_ALLOW_DEBUG_CHEATS_IN_RELEASE)
64+
allowFpsLimit &= !(!TheGameLogic->isGamePaused() && TheGlobalData->m_TiVOFastMode);
65+
#else //always allow this cheat key if we're in a replay game.
66+
allowFpsLimit &= !(!TheGameLogic->isGamePaused() && TheGlobalData->m_TiVOFastMode && TheGameLogic->isInReplayGame());
67+
#endif
68+
69+
// TheSuperHackers @bugfix xezon 05/08/2025 Re-implements the frame rate limiter
70+
// with higher resolution counters to cap the frame rate more accurately to the desired limit.
71+
allowFpsLimit &= TheGlobalData->m_useFpsLimit;
72+
const UnsignedInt maxFps = allowFpsLimit ? getFramesPerSecondLimit() : RenderFpsPreset::UncappedFpsValue;
73+
m_updateTime = m_frameRateLimit.wait(maxFps);
74+
}
75+
76+
void FramePacer::setFramesPerSecondLimit( Int fps )
77+
{
78+
DEBUG_LOG(("FramePacer::setFramesPerSecondLimit() - setting max fps to %d (TheGlobalData->m_useFpsLimit == %d)", fps, TheGlobalData->m_useFpsLimit));
79+
m_maxFPS = fps;
80+
}
81+
82+
Int FramePacer::getFramesPerSecondLimit() const
83+
{
84+
return m_maxFPS;
85+
}
86+
87+
Real FramePacer::getUpdateTime() const
88+
{
89+
return m_updateTime;
90+
}
91+
92+
Real FramePacer::getUpdateFps() const
93+
{
94+
return 1.0f / m_updateTime;
95+
}
96+
97+
void FramePacer::setTimeFrozen(Bool frozen)
98+
{
99+
m_isTimeFrozen = frozen;
100+
}
101+
102+
void FramePacer::setGameHalted(Bool halted)
103+
{
104+
m_isGameHalted = halted;
105+
}
106+
107+
Bool FramePacer::isTimeFrozen() const
108+
{
109+
return m_isTimeFrozen;
110+
}
111+
112+
Bool FramePacer::isGameHalted() const
113+
{
114+
return m_isGameHalted;
115+
}
116+
117+
void FramePacer::setLogicTimeScaleFps( Int fps )
118+
{
119+
m_logicTimeScaleFPS = fps;
120+
}
121+
122+
Int FramePacer::getLogicTimeScaleFps() const
123+
{
124+
return m_logicTimeScaleFPS;
125+
}
126+
127+
void FramePacer::enableLogicTimeScale( Bool enable )
128+
{
129+
m_enableLogicTimeScale = enable;
130+
}
131+
132+
Bool FramePacer::isLogicTimeScaleEnabled() const
133+
{
134+
return m_enableLogicTimeScale;
135+
}
136+
137+
Int FramePacer::getActualLogicTimeScaleFps(LogicTimeQueryFlags flags) const
138+
{
139+
if (m_isTimeFrozen && (flags & IgnoreFrozenTime) == 0)
140+
{
141+
return 0;
142+
}
143+
144+
if (m_isGameHalted && (flags & IgnoreHaltedGame) == 0)
145+
{
146+
return 0;
147+
}
148+
149+
if (TheNetwork != NULL)
150+
{
151+
return TheNetwork->getFrameRate();
152+
}
153+
154+
if (isLogicTimeScaleEnabled())
155+
{
156+
return min(getLogicTimeScaleFps(), getFramesPerSecondLimit());
157+
}
158+
159+
return getFramesPerSecondLimit();
160+
}
161+
162+
Real FramePacer::getActualLogicTimeScaleRatio(LogicTimeQueryFlags flags) const
163+
{
164+
return (Real)getActualLogicTimeScaleFps(flags) / LOGICFRAMES_PER_SECONDS_REAL;
165+
}
166+
167+
Real FramePacer::getActualLogicTimeScaleOverFpsRatio(LogicTimeQueryFlags flags) const
168+
{
169+
// TheSuperHackers @info Clamps ratio to min 1, because the logic
170+
// frame rate is currently capped by the render frame rate.
171+
return min(1.0f, (Real)getActualLogicTimeScaleFps(flags) / getUpdateFps());
172+
}
173+
174+
Real FramePacer::getLogicTimeStepSeconds(LogicTimeQueryFlags flags) const
175+
{
176+
return SECONDS_PER_LOGICFRAME_REAL * getActualLogicTimeScaleOverFpsRatio(flags);
177+
}
178+
179+
Real FramePacer::getLogicTimeStepMilliseconds(LogicTimeQueryFlags flags) const
180+
{
181+
return MSEC_PER_LOGICFRAME_REAL * getActualLogicTimeScaleOverFpsRatio(flags);
182+
}

Generals/Code/GameEngine/Include/Common/GameEngine.h

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,6 @@ class ParticleSystemManager;
5555

5656
class GameEngine : public SubsystemInterface
5757
{
58-
public:
59-
60-
typedef UnsignedInt LogicTimeQueryFlags;
61-
enum LogicTimeQueryFlags_ CPP_11(: LogicTimeQueryFlags)
62-
{
63-
IgnoreFrozenTime = 1<<0, // Ignore frozen time for the query
64-
IgnoreHaltedGame = 1<<1, // Ignore halted game for the query
65-
};
66-
6758
public:
6859

6960
GameEngine( void );
@@ -76,24 +67,9 @@ class GameEngine : public SubsystemInterface
7667
virtual void execute( void ); /**< The "main loop" of the game engine.
7768
It will not return until the game exits. */
7869

79-
virtual void setFramesPerSecondLimit( Int fps ); ///< Set the max render and engine update fps.
80-
virtual Int getFramesPerSecondLimit( void ); ///< Get the max render and engine update fps.
81-
Real getUpdateTime(); ///< Get the last engine update delta time in seconds.
82-
Real getUpdateFps(); ///< Get the last engine update fps.
83-
8470
static Bool isTimeFrozen(); ///< Returns true if a script has frozen time.
8571
static Bool isGameHalted(); ///< Returns true if the game is paused or the network is stalling.
8672

87-
virtual void setLogicTimeScaleFps( Int fps ); ///< Set the logic time scale fps and therefore scale the simulation time. Is capped by the max render fps and does not apply to network matches.
88-
virtual Int getLogicTimeScaleFps(); ///< Get the raw logic time scale fps value.
89-
virtual void enableLogicTimeScale( Bool enable ); ///< Enable the logic time scale setup. If disabled, the simulation time scale is bound to the render frame time or network update time.
90-
virtual Bool isLogicTimeScaleEnabled(); ///< Check whether the logic time scale setup is enabled.
91-
Int getActualLogicTimeScaleFps(LogicTimeQueryFlags flags = 0); ///< Get the real logic time scale fps, depending on the max render fps, network state and enabled state.
92-
Real getActualLogicTimeScaleRatio(LogicTimeQueryFlags flags = 0); ///< Get the real logic time scale ratio, depending on the max render fps, network state and enabled state.
93-
Real getActualLogicTimeScaleOverFpsRatio(LogicTimeQueryFlags flags = 0); ///< Get the real logic time scale over render fps ratio, used to scale down steps in render updates to match logic updates.
94-
Real getLogicTimeStepSeconds(LogicTimeQueryFlags flags = 0); ///< Get the logic time step in seconds
95-
Real getLogicTimeStepMilliseconds(LogicTimeQueryFlags flags = 0); ///< Get the logic time step in milliseconds
96-
9773
virtual void setQuitting( Bool quitting ); ///< set quitting status
9874
virtual Bool getQuitting(void); ///< is app getting ready to quit.
9975

@@ -125,18 +101,10 @@ class GameEngine : public SubsystemInterface
125101
virtual ParticleSystemManager* createParticleSystemManager( void ) = 0;
126102
virtual AudioManager *createAudioManager( void ) = 0; ///< Factory for Audio Manager
127103

128-
Int m_maxFPS; ///< Maximum frames per second for rendering
129-
Int m_logicTimeScaleFPS; ///< Maximum frames per second for logic time scale
130-
131-
Real m_updateTime; ///< Last engine update delta time in seconds
132104
Real m_logicTimeAccumulator; ///< Frame time accumulated towards submitting a new logic frame
133105

134106
Bool m_quitting; ///< true when we need to quit the game
135107
Bool m_isActive; ///< app has OS focus.
136-
Bool m_enableLogicTimeScale;
137-
Bool m_isTimeFrozen;
138-
Bool m_isGameHalted;
139-
140108
};
141109

142110
inline void GameEngine::setQuitting( Bool quitting ) { m_quitting = quitting; }

0 commit comments

Comments
 (0)