diff --git a/Core/GameEngine/CMakeLists.txt b/Core/GameEngine/CMakeLists.txt index 43460b8ecf..0a58dfc431 100644 --- a/Core/GameEngine/CMakeLists.txt +++ b/Core/GameEngine/CMakeLists.txt @@ -39,6 +39,7 @@ set(GAMEENGINE_SRC # Include/Common/Errors.h Include/Common/file.h Include/Common/FileSystem.h + Include/Common/FramePacer.h Include/Common/FrameRateLimit.h # Include/Common/FunctionLexicon.h Include/Common/GameAudio.h @@ -570,6 +571,7 @@ set(GAMEENGINE_SRC # Source/Common/DamageFX.cpp # Source/Common/Dict.cpp # Source/Common/DiscreteCircle.cpp + Source/Common/FramePacer.cpp Source/Common/FrameRateLimit.cpp # Source/Common/GameEngine.cpp # Source/Common/GameLOD.cpp diff --git a/Core/GameEngine/Include/Common/FramePacer.h b/Core/GameEngine/Include/Common/FramePacer.h new file mode 100644 index 0000000000..66955c4585 --- /dev/null +++ b/Core/GameEngine/Include/Common/FramePacer.h @@ -0,0 +1,82 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ +#pragma once + +#include "Common/FrameRateLimit.h" + + +// TheSuperHackers @todo Use unsigned integers for fps values +// TheSuperHackers @todo Consolidate the GlobalData::m_useFpsLimit and FramePacer::m_enableFpsLimit +// TheSuperHackers @todo Implement new fast forward in here +class FramePacer +{ +public: + + typedef UnsignedInt LogicTimeQueryFlags; + enum LogicTimeQueryFlags_ CPP_11(: LogicTimeQueryFlags) + { + IgnoreFrozenTime = 1<<0, ///< Ignore frozen time for the query + IgnoreHaltedGame = 1<<1, ///< Ignore halted game for the query + }; + + FramePacer(); + ~FramePacer(); + + void update(); ///< Signal that the app/render update is done and wait for the fps limit if applicable. + + void setFramesPerSecondLimit( Int fps ); ///< Set the update fps limit. + Int getFramesPerSecondLimit() const; ///< Get the update fps limit. + void enableFramesPerSecondLimit( Bool enable ); ///< Enable or disable the update fps limit. + Bool isFramesPerSecondLimitEnabled() const; ///< Returns whether the fps limit is enabled here. + Bool isActualFramesPerSecondLimitEnabled() const; ///< Returns whether the fps limit is actually enabled when considering all game settings and setups. + Int getActualFramesPerSecondLimit() const; // Get the actual update fps limit. + + Real getUpdateTime() const; ///< Get the last update delta time in seconds. + Real getUpdateFps() const; ///< Get the last update fps. + + void setTimeFrozen(Bool frozen); ///< Set time frozen. Allows scripted camera movement. + void setGameHalted(Bool halted); ///< Set game halted. Does not allow scripted camera movement. + Bool isTimeFrozen() const; + Bool isGameHalted() const; + + 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. + Int getLogicTimeScaleFps() const; ///< Get the raw logic time scale fps value. + void enableLogicTimeScale( Bool enable ); ///< Enable or disable the logic time scale setup. If disabled, the simulation time scale is bound to the render frame time or network update time. + Bool isLogicTimeScaleEnabled() const; ///< Check whether the logic time scale setup is enabled. + Int getActualLogicTimeScaleFps(LogicTimeQueryFlags flags = 0) const; ///< Get the real logic time scale fps, depending on the max render fps, network state and enabled state. + Real getActualLogicTimeScaleRatio(LogicTimeQueryFlags flags = 0) const; ///< Get the real logic time scale ratio, depending on the max render fps, network state and enabled state. + 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. + Real getLogicTimeStepSeconds(LogicTimeQueryFlags flags = 0) const; ///< Get the logic time step in seconds + Real getLogicTimeStepMilliseconds(LogicTimeQueryFlags flags = 0) const; ///< Get the logic time step in milliseconds + +protected: + + FrameRateLimit m_frameRateLimit; + + Int m_maxFPS; ///< Maximum frames per second for rendering + Int m_logicTimeScaleFPS; ///< Maximum frames per second for logic time scale + + Real m_updateTime; ///< Last update delta time in seconds + + Bool m_enableFpsLimit; + Bool m_enableLogicTimeScale; + Bool m_isTimeFrozen; + Bool m_isGameHalted; +}; + +extern FramePacer* TheFramePacer; diff --git a/Core/GameEngine/Include/Common/FrameRateLimit.h b/Core/GameEngine/Include/Common/FrameRateLimit.h index 3e3c80f0ea..5bb2b5fd3a 100644 --- a/Core/GameEngine/Include/Common/FrameRateLimit.h +++ b/Core/GameEngine/Include/Common/FrameRateLimit.h @@ -29,8 +29,8 @@ class FrameRateLimit Real wait(UnsignedInt maxFps); private: - LARGE_INTEGER m_freq; - LARGE_INTEGER m_start; + Int64 m_freq; + Int64 m_start; }; diff --git a/Core/GameEngine/Source/Common/FramePacer.cpp b/Core/GameEngine/Source/Common/FramePacer.cpp new file mode 100644 index 0000000000..0f23389900 --- /dev/null +++ b/Core/GameEngine/Source/Common/FramePacer.cpp @@ -0,0 +1,207 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ +#include "PreRTS.h" + +#include "Common/FramePacer.h" + +#include "GameClient/View.h" + +#include "GameLogic/GameLogic.h" +#include "GameLogic/ScriptEngine.h" + +#include "GameNetwork/NetworkDefs.h" +#include "GameNetwork/NetworkInterface.h" + + +FramePacer* TheFramePacer = NULL; + +FramePacer::FramePacer() +{ + // Set the time slice size to 1 ms. + timeBeginPeriod(1); + + m_maxFPS = BaseFps; + m_logicTimeScaleFPS = LOGICFRAMES_PER_SECOND; + m_updateTime = 1.0f / (Real)BaseFps; // initialized to something to avoid division by zero on first use + m_enableFpsLimit = FALSE; + m_enableLogicTimeScale = FALSE; + m_isTimeFrozen = FALSE; + m_isGameHalted = FALSE; +} + +FramePacer::~FramePacer() +{ + // Restore the previous time slice for Windows. + timeEndPeriod(1); +} + +void FramePacer::update() +{ + // TheSuperHackers @bugfix xezon 05/08/2025 Re-implements the frame rate limiter + // with higher resolution counters to cap the frame rate more accurately to the desired limit. + const UnsignedInt maxFps = getActualFramesPerSecondLimit();// allowFpsLimit ? getFramesPerSecondLimit() : RenderFpsPreset::UncappedFpsValue; + m_updateTime = m_frameRateLimit.wait(maxFps); +} + +void FramePacer::setFramesPerSecondLimit( Int fps ) +{ + DEBUG_LOG(("FramePacer::setFramesPerSecondLimit() - setting max fps to %d (TheGlobalData->m_useFpsLimit == %d)", fps, TheGlobalData->m_useFpsLimit)); + m_maxFPS = fps; +} + +Int FramePacer::getFramesPerSecondLimit() const +{ + return m_maxFPS; +} + +void FramePacer::enableFramesPerSecondLimit( Bool enable ) +{ + m_enableFpsLimit = enable; +} + +Bool FramePacer::isFramesPerSecondLimitEnabled() const +{ + return m_enableFpsLimit; +} + +Bool FramePacer::isActualFramesPerSecondLimitEnabled() const +{ + Bool allowFpsLimit = true; + + if (TheTacticalView != NULL) + { + allowFpsLimit &= TheTacticalView->getTimeMultiplier()<=1 && !TheScriptEngine->isTimeFast(); + } + + if (TheGameLogic != NULL) + { +#if defined(_ALLOW_DEBUG_CHEATS_IN_RELEASE) + allowFpsLimit &= !(!TheGameLogic->isGamePaused() && TheGlobalData->m_TiVOFastMode); +#else //always allow this cheat key if we're in a replay game. + allowFpsLimit &= !(!TheGameLogic->isGamePaused() && TheGlobalData->m_TiVOFastMode && TheGameLogic->isInReplayGame()); +#endif + } + + allowFpsLimit &= TheGlobalData->m_useFpsLimit; + allowFpsLimit &= isFramesPerSecondLimitEnabled(); + + return allowFpsLimit; +} + +Int FramePacer::getActualFramesPerSecondLimit() const +{ + return isActualFramesPerSecondLimitEnabled() ? getFramesPerSecondLimit() : RenderFpsPreset::UncappedFpsValue; +} + +Real FramePacer::getUpdateTime() const +{ + return m_updateTime; +} + +Real FramePacer::getUpdateFps() const +{ + return 1.0f / m_updateTime; +} + +void FramePacer::setTimeFrozen(Bool frozen) +{ + m_isTimeFrozen = frozen; +} + +void FramePacer::setGameHalted(Bool halted) +{ + m_isGameHalted = halted; +} + +Bool FramePacer::isTimeFrozen() const +{ + return m_isTimeFrozen; +} + +Bool FramePacer::isGameHalted() const +{ + return m_isGameHalted; +} + +void FramePacer::setLogicTimeScaleFps( Int fps ) +{ + m_logicTimeScaleFPS = fps; +} + +Int FramePacer::getLogicTimeScaleFps() const +{ + return m_logicTimeScaleFPS; +} + +void FramePacer::enableLogicTimeScale( Bool enable ) +{ + m_enableLogicTimeScale = enable; +} + +Bool FramePacer::isLogicTimeScaleEnabled() const +{ + return m_enableLogicTimeScale; +} + +Int FramePacer::getActualLogicTimeScaleFps(LogicTimeQueryFlags flags) const +{ + if (m_isTimeFrozen && (flags & IgnoreFrozenTime) == 0) + { + return 0; + } + + if (m_isGameHalted && (flags & IgnoreHaltedGame) == 0) + { + return 0; + } + + if (TheNetwork != NULL) + { + return TheNetwork->getFrameRate(); + } + + if (isLogicTimeScaleEnabled()) + { + return getLogicTimeScaleFps(); + } + + // Returns uncapped value to align with the render update as per the original game behavior. + return RenderFpsPreset::UncappedFpsValue; +} + +Real FramePacer::getActualLogicTimeScaleRatio(LogicTimeQueryFlags flags) const +{ + return (Real)getActualLogicTimeScaleFps(flags) / LOGICFRAMES_PER_SECONDS_REAL; +} + +Real FramePacer::getActualLogicTimeScaleOverFpsRatio(LogicTimeQueryFlags flags) const +{ + // TheSuperHackers @info Clamps ratio to min 1, because the logic + // frame rate is currently capped by the render frame rate. + return min(1.0f, (Real)getActualLogicTimeScaleFps(flags) / getUpdateFps()); +} + +Real FramePacer::getLogicTimeStepSeconds(LogicTimeQueryFlags flags) const +{ + return SECONDS_PER_LOGICFRAME_REAL * getActualLogicTimeScaleOverFpsRatio(flags); +} + +Real FramePacer::getLogicTimeStepMilliseconds(LogicTimeQueryFlags flags) const +{ + return MSEC_PER_LOGICFRAME_REAL * getActualLogicTimeScaleOverFpsRatio(flags); +} diff --git a/Core/GameEngine/Source/Common/FrameRateLimit.cpp b/Core/GameEngine/Source/Common/FrameRateLimit.cpp index 5144f05403..b8c7f1ff7a 100644 --- a/Core/GameEngine/Source/Common/FrameRateLimit.cpp +++ b/Core/GameEngine/Source/Common/FrameRateLimit.cpp @@ -22,15 +22,19 @@ FrameRateLimit::FrameRateLimit() { - QueryPerformanceFrequency(&m_freq); - QueryPerformanceCounter(&m_start); + LARGE_INTEGER freq; + LARGE_INTEGER start; + QueryPerformanceFrequency(&freq); + QueryPerformanceCounter(&start); + m_freq = freq.QuadPart; + m_start = start.QuadPart; } Real FrameRateLimit::wait(UnsignedInt maxFps) { LARGE_INTEGER tick; QueryPerformanceCounter(&tick); - double elapsedSeconds = static_cast(tick.QuadPart - m_start.QuadPart) / m_freq.QuadPart; + double elapsedSeconds = static_cast(tick.QuadPart - m_start) / m_freq; const double targetSeconds = 1.0 / maxFps; const double sleepSeconds = targetSeconds - elapsedSeconds - 0.002; // leave ~2ms for spin wait @@ -45,11 +49,11 @@ Real FrameRateLimit::wait(UnsignedInt maxFps) do { QueryPerformanceCounter(&tick); - elapsedSeconds = static_cast(tick.QuadPart - m_start.QuadPart) / m_freq.QuadPart; + elapsedSeconds = static_cast(tick.QuadPart - m_start) / m_freq; } while (elapsedSeconds < targetSeconds); - m_start = tick; + m_start = tick.QuadPart; return (Real)elapsedSeconds; } diff --git a/Core/Libraries/Source/WWVegas/WWLib/WWCommon.h b/Core/Libraries/Source/WWVegas/WWLib/WWCommon.h index 77c231a6d5..1f59b88449 100644 --- a/Core/Libraries/Source/WWVegas/WWLib/WWCommon.h +++ b/Core/Libraries/Source/WWVegas/WWLib/WWCommon.h @@ -31,7 +31,8 @@ enum { // TheSuperHackers @info The original WWSync was 33 ms, ~30 fps, integer. // Changing this will require tweaking all Drawable code that concerns the ww3d time step, including locomotion physics. - WWSyncPerSecond = 30 + WWSyncPerSecond = 30, + WWSyncMilliseconds = 1000 / WWSyncPerSecond, }; #if defined(_MSC_VER) && _MSC_VER < 1300 diff --git a/Core/Tools/W3DView/GraphicView.cpp b/Core/Tools/W3DView/GraphicView.cpp index 185c4465c6..bb31526a1d 100644 --- a/Core/Tools/W3DView/GraphicView.cpp +++ b/Core/Tools/W3DView/GraphicView.cpp @@ -436,7 +436,8 @@ CGraphicView::RepaintView // Simple check to avoid re-entrance // static bool _already_painting = false; - if (_already_painting) return; + if (_already_painting) + return; _already_painting = true; // @@ -452,10 +453,15 @@ CGraphicView::RepaintView m_dwLastFrameUpdate = cur_ticks; // Update the W3D frame times according to our elapsed tick count - if (ticks_to_use == 0) { - WW3D::Sync (WW3D::Get_Sync_Time() + (ticks_elapsed * m_animationSpeed)); - } else { - WW3D::Sync (WW3D::Get_Sync_Time() + ticks_to_use); + if (ticks_to_use == 0) + { + WW3D::Update_Logic_Frame_Time(ticks_elapsed * m_animationSpeed); + WW3D::Sync(WW3D::Get_Fractional_Sync_Milliseconds() >= WWSyncMilliseconds); + } + else + { + WW3D::Update_Logic_Frame_Time(ticks_to_use); + WW3D::Sync(true); } // Do we need to update the current animation? diff --git a/Generals/Code/GameEngine/Include/Common/GameEngine.h b/Generals/Code/GameEngine/Include/Common/GameEngine.h index 90be51a76c..8dc44e396e 100644 --- a/Generals/Code/GameEngine/Include/Common/GameEngine.h +++ b/Generals/Code/GameEngine/Include/Common/GameEngine.h @@ -55,15 +55,6 @@ class ParticleSystemManager; class GameEngine : public SubsystemInterface { -public: - - typedef UnsignedInt LogicTimeQueryFlags; - enum LogicTimeQueryFlags_ CPP_11(: LogicTimeQueryFlags) - { - IgnoreFrozenTime = 1<<0, // Ignore frozen time for the query - IgnoreHaltedGame = 1<<1, // Ignore halted game for the query - }; - public: GameEngine( void ); @@ -76,24 +67,9 @@ class GameEngine : public SubsystemInterface virtual void execute( void ); /**< The "main loop" of the game engine. It will not return until the game exits. */ - virtual void setFramesPerSecondLimit( Int fps ); ///< Set the max render and engine update fps. - virtual Int getFramesPerSecondLimit( void ); ///< Get the max render and engine update fps. - Real getUpdateTime(); ///< Get the last engine update delta time in seconds. - Real getUpdateFps(); ///< Get the last engine update fps. - static Bool isTimeFrozen(); ///< Returns true if a script has frozen time. static Bool isGameHalted(); ///< Returns true if the game is paused or the network is stalling. - 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. - virtual Int getLogicTimeScaleFps(); ///< Get the raw logic time scale fps value. - 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. - virtual Bool isLogicTimeScaleEnabled(); ///< Check whether the logic time scale setup is enabled. - Int getActualLogicTimeScaleFps(LogicTimeQueryFlags flags = 0); ///< Get the real logic time scale fps, depending on the max render fps, network state and enabled state. - Real getActualLogicTimeScaleRatio(LogicTimeQueryFlags flags = 0); ///< Get the real logic time scale ratio, depending on the max render fps, network state and enabled state. - 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. - Real getLogicTimeStepSeconds(LogicTimeQueryFlags flags = 0); ///< Get the logic time step in seconds - Real getLogicTimeStepMilliseconds(LogicTimeQueryFlags flags = 0); ///< Get the logic time step in milliseconds - virtual void setQuitting( Bool quitting ); ///< set quitting status virtual Bool getQuitting(void); ///< is app getting ready to quit. @@ -125,18 +101,10 @@ class GameEngine : public SubsystemInterface virtual ParticleSystemManager* createParticleSystemManager( void ) = 0; virtual AudioManager *createAudioManager( void ) = 0; ///< Factory for Audio Manager - Int m_maxFPS; ///< Maximum frames per second for rendering - Int m_logicTimeScaleFPS; ///< Maximum frames per second for logic time scale - - Real m_updateTime; ///< Last engine update delta time in seconds Real m_logicTimeAccumulator; ///< Frame time accumulated towards submitting a new logic frame Bool m_quitting; ///< true when we need to quit the game Bool m_isActive; ///< app has OS focus. - Bool m_enableLogicTimeScale; - Bool m_isTimeFrozen; - Bool m_isGameHalted; - }; inline void GameEngine::setQuitting( Bool quitting ) { m_quitting = quitting; } diff --git a/Generals/Code/GameEngine/Source/Common/GameEngine.cpp b/Generals/Code/GameEngine/Source/Common/GameEngine.cpp index 58a1419c12..7943cab329 100644 --- a/Generals/Code/GameEngine/Source/Common/GameEngine.cpp +++ b/Generals/Code/GameEngine/Source/Common/GameEngine.cpp @@ -32,6 +32,7 @@ #include "Common/AudioAffect.h" #include "Common/BuildAssistant.h" #include "Common/CRCDebug.h" +#include "Common/FramePacer.h" #include "Common/Radar.h" #include "Common/PlayerTemplate.h" #include "Common/Team.h" @@ -44,7 +45,6 @@ #include "Common/ThingFactory.h" #include "Common/file.h" #include "Common/FileSystem.h" -#include "Common/FrameRateLimit.h" #include "Common/ArchiveFileSystem.h" #include "Common/LocalFileSystem.h" #include "Common/CDManager.h" @@ -249,19 +249,10 @@ static void updateWindowTitle() //------------------------------------------------------------------------------------------------- GameEngine::GameEngine( void ) { - // Set the time slice size to 1 ms. - timeBeginPeriod(1); - // initialize to non garbage values - m_maxFPS = BaseFps; - m_logicTimeScaleFPS = LOGICFRAMES_PER_SECOND; - m_updateTime = 1.0f / BaseFps; // initialized to something to avoid division by zero on first use m_logicTimeAccumulator = 0.0f; m_quitting = FALSE; m_isActive = FALSE; - m_enableLogicTimeScale = FALSE; - m_isTimeFrozen = FALSE; - m_isGameHalted = FALSE; _Module.Init(NULL, ApplicationHInstance, NULL); } @@ -307,34 +298,6 @@ GameEngine::~GameEngine() #ifdef PERF_TIMERS PerfGather::termPerfDump(); #endif - - // Restore the previous time slice for Windows. - timeEndPeriod(1); -} - -//------------------------------------------------------------------------------------------------- -void GameEngine::setFramesPerSecondLimit( Int fps ) -{ - DEBUG_LOG(("GameEngine::setFramesPerSecondLimit() - setting max fps to %d (TheGlobalData->m_useFpsLimit == %d)", fps, TheGlobalData->m_useFpsLimit)); - m_maxFPS = fps; -} - -//------------------------------------------------------------------------------------------------- -Int GameEngine::getFramesPerSecondLimit( void ) -{ - return m_maxFPS; -} - -//------------------------------------------------------------------------------------------------- -Real GameEngine::getUpdateTime() -{ - return m_updateTime; -} - -//------------------------------------------------------------------------------------------------- -Real GameEngine::getUpdateFps() -{ - return 1.0f / m_updateTime; } //------------------------------------------------------------------------------------------------- @@ -376,80 +339,6 @@ Bool GameEngine::isGameHalted() return false; } -//------------------------------------------------------------------------------------------------- -void GameEngine::setLogicTimeScaleFps( Int fps ) -{ - m_logicTimeScaleFPS = fps; -} - -//------------------------------------------------------------------------------------------------- -Int GameEngine::getLogicTimeScaleFps() -{ - return m_logicTimeScaleFPS; -} - -//------------------------------------------------------------------------------------------------- -void GameEngine::enableLogicTimeScale( Bool enable ) -{ - m_enableLogicTimeScale = enable; -} - -//------------------------------------------------------------------------------------------------- -Bool GameEngine::isLogicTimeScaleEnabled() -{ - return m_enableLogicTimeScale; -} - -//------------------------------------------------------------------------------------------------- -Int GameEngine::getActualLogicTimeScaleFps(LogicTimeQueryFlags flags) -{ - if (m_isTimeFrozen && (flags & IgnoreFrozenTime) == 0) - { - return 0; - } - - if (m_isGameHalted && (flags & IgnoreHaltedGame) == 0) - { - return 0; - } - - if (TheNetwork != NULL) - { - return TheNetwork->getFrameRate(); - } - - if (isLogicTimeScaleEnabled()) - { - return min(getLogicTimeScaleFps(), getFramesPerSecondLimit()); - } - - return getFramesPerSecondLimit(); -} - -//------------------------------------------------------------------------------------------------- -Real GameEngine::getActualLogicTimeScaleRatio(LogicTimeQueryFlags flags) -{ - return (Real)getActualLogicTimeScaleFps(flags) / LOGICFRAMES_PER_SECONDS_REAL; -} - -//------------------------------------------------------------------------------------------------- -Real GameEngine::getActualLogicTimeScaleOverFpsRatio(LogicTimeQueryFlags flags) -{ - // TheSuperHackers @info Clamps ratio to min 1, because the logic - // frame rate is currently capped by the render frame rate. - return min(1.0f, (Real)getActualLogicTimeScaleFps(flags) / getUpdateFps()); -} - -Real GameEngine::getLogicTimeStepSeconds(LogicTimeQueryFlags flags) -{ - return SECONDS_PER_LOGICFRAME_REAL * getActualLogicTimeScaleOverFpsRatio(flags); -} - -Real GameEngine::getLogicTimeStepMilliseconds(LogicTimeQueryFlags flags) -{ - return MSEC_PER_LOGICFRAME_REAL * getActualLogicTimeScaleOverFpsRatio(flags); -} - /** ----------------------------------------------------------------------------------------------- * Initialize the game engine by initializing the GameLogic and GameClient. */ @@ -601,7 +490,7 @@ void GameEngine::init() TheSubsystemList->postProcessLoadAll(); - setFramesPerSecondLimit(TheGlobalData->m_framesPerSecondLimit); + TheFramePacer->setFramesPerSecondLimit(TheGlobalData->m_framesPerSecondLimit); TheAudio->setOn(TheGlobalData->m_audioOn && TheGlobalData->m_musicOn, AudioAffect_Music); TheAudio->setOn(TheGlobalData->m_audioOn && TheGlobalData->m_soundsOn, AudioAffect_Sound); @@ -754,8 +643,8 @@ Bool GameEngine::canUpdateGameLogic() // Must be first. TheGameLogic->preUpdate(); - m_isTimeFrozen = isTimeFrozen(); - m_isGameHalted = isGameHalted(); + TheFramePacer->setTimeFrozen(isTimeFrozen()); + TheFramePacer->setGameHalted(isGameHalted()); if (TheNetwork != NULL) { @@ -767,6 +656,7 @@ Bool GameEngine::canUpdateGameLogic() } } +/// ----------------------------------------------------------------------------------------------- Bool GameEngine::canUpdateNetworkGameLogic() { DEBUG_ASSERTCRASH(TheNetwork != NULL, ("TheNetwork is NULL")); @@ -774,7 +664,7 @@ Bool GameEngine::canUpdateNetworkGameLogic() if (TheNetwork->isFrameDataReady()) { // Important: The Network is definitely no longer stalling. - m_isGameHalted = false; + TheFramePacer->setGameHalted(false); return true; } @@ -782,11 +672,12 @@ Bool GameEngine::canUpdateNetworkGameLogic() return false; } +/// ----------------------------------------------------------------------------------------------- Bool GameEngine::canUpdateRegularGameLogic() { - const Bool enabled = isLogicTimeScaleEnabled(); - const Int logicTimeScaleFps = getLogicTimeScaleFps(); - const Int maxRenderFps = getFramesPerSecondLimit(); + const Bool enabled = TheFramePacer->isLogicTimeScaleEnabled(); + const Int logicTimeScaleFps = TheFramePacer->getLogicTimeScaleFps(); + const Int maxRenderFps = TheFramePacer->getFramesPerSecondLimit(); #if defined(_ALLOW_DEBUG_CHEATS_IN_RELEASE) const Bool useFastMode = TheGlobalData->m_TiVOFastMode; @@ -804,7 +695,7 @@ Bool GameEngine::canUpdateRegularGameLogic() // TheSuperHackers @tweak xezon 06/08/2025 // The logic time step is now decoupled from the render update. const Real targetFrameTime = 1.0f / logicTimeScaleFps; - m_logicTimeAccumulator += min(m_updateTime, targetFrameTime); + m_logicTimeAccumulator += min(TheFramePacer->getUpdateTime(), targetFrameTime); if (m_logicTimeAccumulator >= targetFrameTime) { @@ -847,8 +738,8 @@ void GameEngine::update( void ) } const Bool canUpdate = canUpdateGameLogic(); - const Bool canUpdateLogic = canUpdate && !m_isGameHalted && !m_isTimeFrozen; - const Bool canUpdateScript = canUpdate && !m_isGameHalted; + const Bool canUpdateLogic = canUpdate && !TheFramePacer->isGameHalted() && !TheFramePacer->isTimeFrozen(); + const Bool canUpdateScript = canUpdate && !TheFramePacer->isGameHalted(); if (canUpdateLogic) { @@ -873,8 +764,6 @@ extern HWND ApplicationHWnd; */ void GameEngine::execute( void ) { - FrameRateLimit* frameRateLimit = new FrameRateLimit(); - #if defined(RTS_DEBUG) DWORD startTime = timeGetTime() / 1000; #endif @@ -943,34 +832,7 @@ void GameEngine::execute( void ) } } - { - { - Bool allowFpsLimit = TheTacticalView->getTimeMultiplier()<=1 && !TheScriptEngine->isTimeFast(); - - // I'm disabling this in debug because many people need alt-tab capability. If you happen to be - // doing performance tuning, please just change this on your local system. -MDC - #if defined(RTS_DEBUG) - if (allowFpsLimit) - ::Sleep(1); // give everyone else a tiny time slice. - #endif - - - #if defined(_ALLOW_DEBUG_CHEATS_IN_RELEASE) - allowFpsLimit &= !(!TheGameLogic->isGamePaused() && TheGlobalData->m_TiVOFastMode); - #else //always allow this cheat key if we're in a replay game. - allowFpsLimit &= !(!TheGameLogic->isGamePaused() && TheGlobalData->m_TiVOFastMode && TheGameLogic->isInReplayGame()); - #endif - { - // TheSuperHackers @bugfix xezon 05/08/2025 Re-implements the frame rate limiter - // with higher resolution counters to cap the frame rate more accurately to the desired limit. - allowFpsLimit &= TheGlobalData->m_useFpsLimit; - const UnsignedInt maxFps = allowFpsLimit ? getFramesPerSecondLimit() : RenderFpsPreset::UncappedFpsValue; - m_updateTime = frameRateLimit->wait(maxFps); - } - - } - } - + TheFramePacer->update(); } #ifdef PERF_TIMERS @@ -983,8 +845,6 @@ void GameEngine::execute( void ) #endif } - - delete frameRateLimit; } /** ----------------------------------------------------------------------------------------------- diff --git a/Generals/Code/GameEngine/Source/Common/GameMain.cpp b/Generals/Code/GameEngine/Source/Common/GameMain.cpp index cf5f2e3be8..870829597b 100644 --- a/Generals/Code/GameEngine/Source/Common/GameMain.cpp +++ b/Generals/Code/GameEngine/Source/Common/GameMain.cpp @@ -28,6 +28,7 @@ #include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine +#include "Common/FramePacer.h" #include "Common/GameEngine.h" #include "Common/ReplaySimulation.h" @@ -39,6 +40,8 @@ Int GameMain() { int exitcode = 0; // initialize the game engine using factory function + TheFramePacer = new FramePacer(); + TheFramePacer->enableFramesPerSecondLimit(TRUE); TheGameEngine = CreateGameEngine(); TheGameEngine->init(); @@ -53,6 +56,8 @@ Int GameMain() } // since execute() returned, we are exiting the game + delete TheFramePacer; + TheFramePacer = NULL; delete TheGameEngine; TheGameEngine = NULL; diff --git a/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp b/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp index 30232b73e0..f8c9bbe9a5 100644 --- a/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -35,8 +35,8 @@ #include "Common/BuildAssistant.h" #include "Common/ClientUpdateModule.h" #include "Common/DrawModule.h" +#include "Common/FramePacer.h" #include "Common/GameAudio.h" -#include "Common/GameEngine.h" #include "Common/GameLOD.h" #include "Common/GameState.h" #include "Common/GlobalData.h" @@ -4785,7 +4785,7 @@ void TintEnvelope::setDecayFrames( UnsignedInt frames ) void TintEnvelope::update(void) { // TheSuperHackers @tweak The tint time step is now decoupled from the render update. - const Real timeScale = TheGameEngine->getActualLogicTimeScaleOverFpsRatio(); + const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); switch ( m_envState ) { diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp index eb51606ebb..37fab26756 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp @@ -30,6 +30,7 @@ // INCLUDES /////////////////////////////////////////////////////////////////////////////////////// #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine +#include "Common/FramePacer.h" #include "Common/GameEngine.h" #include "Common/GameState.h" #include "Common/MessageStream.h" @@ -215,7 +216,7 @@ static void restartMissionMenu() Int rankPointsStartedWith = TheGameLogic->getRankPointsToAddAtGameStart();// must write down before reset GameDifficulty diff = TheScriptEngine->getGlobalDifficulty(); - Int fps = TheGameEngine->getFramesPerSecondLimit(); + Int fps = TheFramePacer->getFramesPerSecondLimit(); TheGameLogic->clearGameData(FALSE); TheGameEngine->setQuitting(FALSE); diff --git a/Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp b/Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp index ed5de55706..0cd5764c2a 100644 --- a/Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp @@ -32,8 +32,8 @@ #define DEFINE_SHADOW_NAMES #include "Common/ActionManager.h" +#include "Common/FramePacer.h" #include "Common/GameAudio.h" -#include "Common/GameEngine.h" #include "Common/GameType.h" #include "Common/MessageStream.h" #include "Common/PerfTimer.h" @@ -46,7 +46,6 @@ #include "Common/BuildAssistant.h" #include "Common/Recorder.h" #include "Common/SpecialPower.h" -#include "Common/FrameRateLimit.h" #include "GameClient/Anim2D.h" #include "GameClient/ControlBar.h" @@ -1889,7 +1888,7 @@ void InGameUI::update( void ) if (m_cameraRotatingLeft || m_cameraRotatingRight || m_cameraZoomingIn || m_cameraZoomingOut) { // TheSuperHackers @tweak The camera rotation and zoom are now decoupled from the render update. - const Real fpsRatio = (Real)BaseFps / TheGameEngine->getUpdateFps(); + const Real fpsRatio = (Real)BaseFps / TheFramePacer->getUpdateFps(); const Real rotateAngle = TheGlobalData->m_keyboardCameraRotateSpeed * fpsRatio; const Real zoomHeight = (Real)View::ZoomHeightPerSecond * fpsRatio; @@ -5871,7 +5870,7 @@ void InGameUI::drawRenderFps(Int &x, Int &y) UnsignedInt renderFpsLimit = 0u; if (TheGlobalData->m_useFpsLimit) { - renderFpsLimit = (UnsignedInt)TheGameEngine->getFramesPerSecondLimit(); + renderFpsLimit = (UnsignedInt)TheFramePacer->getFramesPerSecondLimit(); if (renderFpsLimit == RenderFpsPreset::UncappedFpsValue) { renderFpsLimit = 0u; diff --git a/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp b/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp index 37241be0b2..678cd66653 100644 --- a/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp @@ -32,7 +32,7 @@ #include "Common/AudioAffect.h" #include "Common/ActionManager.h" -#include "Common/FrameRateLimit.h" +#include "Common/FramePacer.h" #include "Common/GameAudio.h" #include "Common/GameEngine.h" #include "Common/GameType.h" @@ -187,10 +187,10 @@ Bool hasThingsInProduction(PlayerType playerType) bool changeMaxRenderFps(FpsValueChange change) { - UnsignedInt maxRenderFps = TheGameEngine->getFramesPerSecondLimit(); + UnsignedInt maxRenderFps = TheFramePacer->getFramesPerSecondLimit(); maxRenderFps = RenderFpsPreset::changeFpsValue(maxRenderFps, change); - TheGameEngine->setFramesPerSecondLimit(maxRenderFps); + TheFramePacer->setFramesPerSecondLimit(maxRenderFps); TheWritableGlobalData->m_useFpsLimit = (maxRenderFps != RenderFpsPreset::UncappedFpsValue); UnicodeString message; @@ -214,16 +214,16 @@ bool changeLogicTimeScale(FpsValueChange change) if (TheNetwork != NULL) return false; - const UnsignedInt maxRenderFps = TheGameEngine->getFramesPerSecondLimit(); + const UnsignedInt maxRenderFps = TheFramePacer->getFramesPerSecondLimit(); UnsignedInt maxRenderRemainder = LogicTimeScaleFpsPreset::StepFpsValue; maxRenderRemainder -= maxRenderFps % LogicTimeScaleFpsPreset::StepFpsValue; maxRenderRemainder %= LogicTimeScaleFpsPreset::StepFpsValue; - UnsignedInt logicTimeScaleFps = TheGameEngine->getLogicTimeScaleFps(); + UnsignedInt logicTimeScaleFps = TheFramePacer->getLogicTimeScaleFps(); // Set the value to the max render fps value plus a bit when time scale is // disabled. This ensures that the time scale does not re-enable with a // 'surprise' value. - if (!TheGameEngine->isLogicTimeScaleEnabled()) + if (!TheFramePacer->isLogicTimeScaleEnabled()) { logicTimeScaleFps = maxRenderFps + maxRenderRemainder; } @@ -234,26 +234,26 @@ bool changeLogicTimeScale(FpsValueChange change) logicTimeScaleFps = LogicTimeScaleFpsPreset::changeFpsValue(logicTimeScaleFps, change); // Set value before potentially disabling it. - if (TheGameEngine->isLogicTimeScaleEnabled()) + if (TheFramePacer->isLogicTimeScaleEnabled()) { - TheGameEngine->setLogicTimeScaleFps(logicTimeScaleFps); + TheFramePacer->setLogicTimeScaleFps(logicTimeScaleFps); } - TheGameEngine->enableLogicTimeScale(logicTimeScaleFps < maxRenderFps); + TheFramePacer->enableLogicTimeScale(logicTimeScaleFps < maxRenderFps); // Set value after potentially enabling it. - if (TheGameEngine->isLogicTimeScaleEnabled()) + if (TheFramePacer->isLogicTimeScaleEnabled()) { - TheGameEngine->setLogicTimeScaleFps(logicTimeScaleFps); + TheFramePacer->setLogicTimeScaleFps(logicTimeScaleFps); } - logicTimeScaleFps = TheGameEngine->getLogicTimeScaleFps(); - const UnsignedInt actualLogicTimeScaleFps = TheGameEngine->getActualLogicTimeScaleFps(); - const Real actualLogicTimeScaleRatio = TheGameEngine->getActualLogicTimeScaleRatio(); + logicTimeScaleFps = TheFramePacer->getLogicTimeScaleFps(); + const UnsignedInt actualLogicTimeScaleFps = TheFramePacer->getActualLogicTimeScaleFps(); + const Real actualLogicTimeScaleRatio = TheFramePacer->getActualLogicTimeScaleRatio(); UnicodeString message; - if (TheGameEngine->isLogicTimeScaleEnabled()) + if (TheFramePacer->isLogicTimeScaleEnabled()) { message = TheGameText->FETCH_OR_SUBSTITUTE_FORMAT("GUI:SetLogicTimeScaleFps", L"Logic Time Scale FPS is %u (actual %u, ratio %.02f)", logicTimeScaleFps, actualLogicTimeScaleFps, actualLogicTimeScaleRatio); diff --git a/Generals/Code/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp b/Generals/Code/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp index 34907e5b62..ad6242db93 100644 --- a/Generals/Code/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp @@ -28,8 +28,8 @@ #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine +#include "Common/FramePacer.h" #include "Common/GameType.h" -#include "Common/GameEngine.h" #include "Common/MessageStream.h" #include "Common/Player.h" #include "Common/PlayerList.h" @@ -440,7 +440,7 @@ GameMessageDisposition LookAtTranslator::translateGameMessage(const GameMessage { // TheSuperHackers @bugfix Mauller 07/06/2025 The camera scrolling is now decoupled from the render update. - const Real fpsRatio = (Real)BaseFps / TheGameEngine->getUpdateFps(); + const Real fpsRatio = (Real)BaseFps / TheFramePacer->getUpdateFps(); switch (m_scrollType) { diff --git a/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp b/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp index 479f5bd42c..a46d193b26 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp @@ -31,8 +31,8 @@ #include "Common/AudioAffect.h" #include "Common/AudioHandleSpecialValues.h" +#include "Common/FramePacer.h" #include "Common/GameAudio.h" -#include "Common/GameEngine.h" #include "Common/MapObject.h" // For MAP_XY_FACTOR #include "Common/PartitionSolver.h" #include "Common/Player.h" @@ -6583,11 +6583,11 @@ void ScriptActions::executeAction( ScriptAction *pAction ) case ScriptAction::SET_FPS_LIMIT: if (!pAction->getParameter(0)->getInt()) { - TheGameEngine->setFramesPerSecondLimit(TheGlobalData->m_framesPerSecondLimit); + TheFramePacer->setFramesPerSecondLimit(TheGlobalData->m_framesPerSecondLimit); } else { - TheGameEngine->setFramesPerSecondLimit(pAction->getParameter(0)->getInt()); + TheFramePacer->setFramesPerSecondLimit(pAction->getParameter(0)->getInt()); } // Setting the fps limit doesn't do much good if we don't use it. jba. TheWritableGlobalData->m_useFpsLimit = true; diff --git a/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp b/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp index 7ee2c1ec16..b9678cbefe 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp @@ -31,7 +31,7 @@ #include "Common/DataChunk.h" #include "Common/file.h" #include "Common/FileSystem.h" -#include "Common/GameEngine.h" +#include "Common/FramePacer.h" #include "Common/GameState.h" #include "Common/LatchRestore.h" #include "Common/MessageStream.h" @@ -4530,8 +4530,8 @@ void ScriptEngine::init( void ) void ScriptEngine::reset( void ) { // setting FPS limit in case a script had changed it - if (TheGameEngine && TheGlobalData) - TheGameEngine->setFramesPerSecondLimit(TheGlobalData->m_framesPerSecondLimit); + if (TheFramePacer && TheGlobalData) + TheFramePacer->setFramesPerSecondLimit(TheGlobalData->m_framesPerSecondLimit); if (TheScriptActions) { TheScriptActions->reset(); diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 92bf469c28..fec5363934 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -33,6 +33,7 @@ #include "Common/AudioHandleSpecialValues.h" #include "Common/BuildAssistant.h" #include "Common/CRCDebug.h" +#include "Common/FramePacer.h" #include "Common/GameAudio.h" #include "Common/GameEngine.h" #include "Common/GameState.h" @@ -3718,8 +3719,8 @@ void GameLogic::setGamePausedInFrame( UnsignedInt frame, Bool disableLogicTimeSc if (disableLogicTimeScale) { - m_logicTimeScaleEnabledMemory = TheGameEngine->isLogicTimeScaleEnabled(); - TheGameEngine->enableLogicTimeScale(FALSE); + m_logicTimeScaleEnabledMemory = TheFramePacer->isLogicTimeScaleEnabled(); + TheFramePacer->enableLogicTimeScale(FALSE); } } } @@ -3758,7 +3759,7 @@ void GameLogic::pauseGameLogic(Bool paused) if (!paused && m_logicTimeScaleEnabledMemory) { m_logicTimeScaleEnabledMemory = FALSE; - TheGameEngine->enableLogicTimeScale(TRUE); + TheFramePacer->enableLogicTimeScale(TRUE); } } diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp index 3ffbd73acd..1726d02196 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp @@ -31,6 +31,7 @@ #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine #include "Common/CRCDebug.h" +#include "Common/FramePacer.h" #include "Common/GameAudio.h" #include "Common/GameEngine.h" #include "Common/GlobalData.h" @@ -427,7 +428,7 @@ void GameLogic::logicMessageDispatcher( GameMessage *msg, void *userData ) if (maxFPS < 1 || maxFPS > 1000) maxFPS = TheGlobalData->m_framesPerSecondLimit; DEBUG_LOG(("Setting max FPS limit to %d FPS", maxFPS)); - TheGameEngine->setFramesPerSecondLimit(maxFPS); + TheFramePacer->setFramesPerSecondLimit(maxFPS); TheWritableGlobalData->m_useFpsLimit = true; } diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp index e6209f3ef5..ed0dd771aa 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp @@ -41,8 +41,8 @@ static void drawFramerateBar(void); #include // USER INCLUDES ////////////////////////////////////////////////////////////// +#include "Common/FramePacer.h" #include "Common/ThingFactory.h" -#include "Common/GameEngine.h" #include "Common/GlobalData.h" #include "Common/PerfTimer.h" #include "Common/FileSystem.h" @@ -975,7 +975,7 @@ void W3DDisplay::gatherDebugStats( void ) //Int LOD = TheGlobalData->m_terrainLOD; //unibuffer.format( L"FPS: %.2f, %.2fms mapLOD=%d [cumu FPS=%.2f] draws: %.2f sort: %.2f", fps, ms, LOD, cumuFPS, drawsPerFrame,sortPolysPerFrame); if (TheGlobalData->m_useFpsLimit) - unibuffer.format( L"%.2f/%d FPS, ", fps, TheGameEngine->getFramesPerSecondLimit()); + unibuffer.format( L"%.2f/%d FPS, ", fps, TheFramePacer->getFramesPerSecondLimit()); else unibuffer.format( L"%.2f FPS, ", fps); @@ -1673,7 +1673,7 @@ void W3DDisplay::draw( void ) // //PredictiveLODOptimizerClass::Optimize_LODs( 5000 ); - Bool freezeTime = TheGameEngine->isTimeFrozen() || TheGameEngine->isGameHalted(); + Bool freezeTime = TheFramePacer->isTimeFrozen() || TheFramePacer->isGameHalted(); /// @todo: I'm assuming the first view is our main 3D view. W3DView *primaryW3DView=(W3DView *)getFirstView(); @@ -1709,7 +1709,7 @@ void W3DDisplay::draw( void ) } } - WW3D::Update_Logic_Frame_Time(TheGameEngine->getLogicTimeStepMilliseconds()); + WW3D::Update_Logic_Frame_Time(TheFramePacer->getLogicTimeStepMilliseconds()); // TheSuperHackers @info This binds the WW3D update to the logic update. WW3D::Sync(TheGameLogic->hasUpdated()); diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp index 31e10ef8f5..538368efac 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp @@ -38,7 +38,7 @@ // USER INCLUDES ////////////////////////////////////////////////////////////////////////////////// #include "Common/BuildAssistant.h" -#include "Common/GameEngine.h" +#include "Common/FramePacer.h" #include "Common/GlobalData.h" #include "Common/Module.h" #include "Common/RandomValue.h" @@ -905,7 +905,7 @@ Bool W3DView::updateCameraMovements() m_previousLookAtPosition = *getPosition(); // TheSuperHackers @tweak The scripted camera movement is now decoupled from the render update. // The scripted camera will still move when the time is frozen, but not when the game is halted. - moveAlongWaypointPath(TheGameEngine->getLogicTimeStepMilliseconds(GameEngine::IgnoreFrozenTime)); + moveAlongWaypointPath(TheFramePacer->getLogicTimeStepMilliseconds(FramePacer::IgnoreFrozenTime)); didUpdate = true; } if (m_doingScriptedCameraLock) @@ -1206,7 +1206,7 @@ void W3DView::update(void) // if scrolling, only adjust if we're too close or too far if (m_scrollAmount.length() < m_scrollAmountCutoff || (m_currentHeightAboveGround < m_minHeightAboveGround) || (TheGlobalData->m_enforceMaxCameraHeight && m_currentHeightAboveGround > m_maxHeightAboveGround)) { - const Real fpsRatio = (Real)BaseFps / TheGameEngine->getUpdateFps(); + const Real fpsRatio = (Real)BaseFps / TheFramePacer->getUpdateFps(); const Real zoomAdj = (desiredZoom - m_zoom) * TheGlobalData->m_cameraAdjustSpeed * fpsRatio; if (fabs(zoomAdj) >= 0.0001f) // only do positive { @@ -1218,7 +1218,7 @@ void W3DView::update(void) else if (!didScriptedMovement) { // we're not scrolling; settle toward desired height above ground - const Real fpsRatio = (Real)BaseFps / TheGameEngine->getUpdateFps(); + const Real fpsRatio = (Real)BaseFps / TheFramePacer->getUpdateFps(); const Real zoomAdj = (m_zoom - desiredZoom) * TheGlobalData->m_cameraAdjustSpeed * fpsRatio; if (fabs(zoomAdj) >= 0.0001f) { diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp index 784851861e..2119e618d7 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp @@ -49,7 +49,7 @@ #include "mesh.h" #include "matinfo.h" -#include "Common/GameEngine.h" +#include "Common/FramePacer.h" #include "Common/GameState.h" #include "Common/GlobalData.h" #include "Common/PerfTimer.h" @@ -1187,7 +1187,7 @@ void WaterRenderObjClass::enableWaterGrid(Bool state) void WaterRenderObjClass::update( void ) { // TheSuperHackers @tweak The water movement time step is now decoupled from the render update. - const Real timeScale = TheGameEngine->getActualLogicTimeScaleOverFpsRatio(); + const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); { constexpr const Real MagicOffset = 0.0125f * 33 / 5000; ///< the work of top Munkees; do not question it diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWaterTracks.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWaterTracks.cpp index 61f71d2de7..4edfd72bde 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWaterTracks.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWaterTracks.cpp @@ -47,7 +47,7 @@ #include "W3DDevice/GameClient/W3DWaterTracks.h" #include "GameClient/InGameUI.h" #include "GameLogic/TerrainLogic.h" -#include "Common/GameEngine.h" +#include "Common/FramePacer.h" #include "Common/GlobalData.h" #include "Common/UnicodeString.h" #include "Common/file.h" @@ -293,7 +293,7 @@ Int WaterTracksObj::update(Int msElapsed) Int WaterTracksObj::render(DX8VertexBufferClass *vertexBuffer, Int batchStart) { // TheSuperHackers @tweak The wave movement time step is now decoupled from the render update. - m_elapsedMs += TheGameEngine->getLogicTimeStepMilliseconds(); + m_elapsedMs += TheFramePacer->getLogicTimeStepMilliseconds(); VertexFormatXYZDUV1 *vb; Vector2 waveTailOrigin,waveFrontOrigin; diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/ww3d.h b/Generals/Code/Libraries/Source/WWVegas/WW3D2/ww3d.h index 4cb8c5bf9b..47069d614c 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/ww3d.h +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/ww3d.h @@ -173,6 +173,7 @@ class WW3D static void Sync(bool step); static unsigned int Get_Sync_Time(void) { return SyncTime; } static unsigned int Get_Sync_Frame_Time(void) { return SyncTime - PreviousSyncTime; } + static unsigned int Get_Fractional_Sync_Milliseconds() { return FractionalSyncMs; } static float Get_Logic_Frame_Time_Milliseconds() { return LogicFrameTimeMs; } static float Get_Logic_Frame_Time_Seconds() { return LogicFrameTimeMs * 0.001f; } static unsigned int Get_Frame_Count(void) { return FrameCount; } diff --git a/Generals/Code/Tools/GUIEdit/Source/EditWindow.cpp b/Generals/Code/Tools/GUIEdit/Source/EditWindow.cpp index 3b8c95eb68..08eec4d1a3 100644 --- a/Generals/Code/Tools/GUIEdit/Source/EditWindow.cpp +++ b/Generals/Code/Tools/GUIEdit/Source/EditWindow.cpp @@ -49,6 +49,7 @@ // USER INCLUDES ////////////////////////////////////////////////////////////// #include "Common/Debug.h" +#include "Common/FramePacer.h" #include "GameClient/Display.h" #include "GameClient/GameWindowManager.h" #include "W3DDevice/GameClient/W3DFileSystem.h" @@ -1456,13 +1457,9 @@ void EditWindow::drawGrid( void ) //============================================================================= void EditWindow::draw( void ) { - static UnsignedInt syncTime = 0; - // allow W3D to update its internals - WW3D::Sync( syncTime ); - - // for now, use constant time steps to avoid animations running independent of framerate - syncTime += 50; + WW3D::Update_Logic_Frame_Time(TheFramePacer->getLogicTimeStepMilliseconds()); + WW3D::Sync(WW3D::Get_Fractional_Sync_Milliseconds() >= WWSyncMilliseconds); // start render block WW3D::Begin_Render( true, true, Vector3( m_backgroundColor.red, @@ -1479,6 +1476,7 @@ void EditWindow::draw( void ) // render is all done! WW3D::End_Render(); + TheFramePacer->update(); } // EditWindow::setSize ======================================================== diff --git a/Generals/Code/Tools/GUIEdit/Source/WinMain.cpp b/Generals/Code/Tools/GUIEdit/Source/WinMain.cpp index 1d692600e3..9cfb49d204 100644 --- a/Generals/Code/Tools/GUIEdit/Source/WinMain.cpp +++ b/Generals/Code/Tools/GUIEdit/Source/WinMain.cpp @@ -50,8 +50,8 @@ // USER INCLUDES ////////////////////////////////////////////////////////////// #include "Common/Debug.h" +#include "Common/FramePacer.h" #include "Common/GameMemory.h" -#include "Common/GameEngine.h" #include "GameClient/GameWindowManager.h" #include "Win32Device/GameClient/Win32Mouse.h" #include "resource.h" @@ -224,6 +224,8 @@ Int APIENTRY WinMain(HINSTANCE hInstance, return FALSE; TheEditor->init(); + TheFramePacer = new FramePacer(); + // // see if we have any messages to process, a NULL window handle tells the // OS to look at the main window associated with the calling thread, us! @@ -262,7 +264,7 @@ Int APIENTRY WinMain(HINSTANCE hInstance, else { - // udpate our universe + // update our universe TheEditor->update(); Sleep(1); @@ -271,6 +273,9 @@ Int APIENTRY WinMain(HINSTANCE hInstance, } // shutdown GUIEdit data + delete TheFramePacer; + TheFramePacer = NULL; + delete TheEditor; TheEditor = NULL; diff --git a/Generals/Code/Tools/WorldBuilder/src/WorldBuilder.cpp b/Generals/Code/Tools/WorldBuilder/src/WorldBuilder.cpp index bf4afd9aa6..9d440098c8 100644 --- a/Generals/Code/Tools/WorldBuilder/src/WorldBuilder.cpp +++ b/Generals/Code/Tools/WorldBuilder/src/WorldBuilder.cpp @@ -33,6 +33,7 @@ //#include #include "W3DDevice/GameClient/W3DFileSystem.h" +#include "Common/FramePacer.h" #include "Common/GlobalData.h" #include "WHeightMapEdit.h" //#include "Common/GameFileSystem.h" @@ -338,6 +339,8 @@ BOOL CWorldBuilderApp::InitInstance() initSubsystem(TheWritableGlobalData, new GlobalData(), "Data\\INI\\Default\\GameData", "Data\\INI\\GameData"); + TheFramePacer = new FramePacer(); + #if defined(RTS_DEBUG) ini.loadFileDirectory( AsciiString( "Data\\INI\\GameDataDebug" ), INI_LOAD_MULTIFILE, NULL ); #endif @@ -634,6 +637,9 @@ int CWorldBuilderApp::ExitInstance() WorldHeightMapEdit::shutdown(); + delete TheFramePacer; + TheFramePacer = NULL; + delete TheFileSystem; TheFileSystem = NULL; TextureLoadTaskClass::shutdown(); diff --git a/Generals/Code/Tools/WorldBuilder/src/wbview3d.cpp b/Generals/Code/Tools/WorldBuilder/src/wbview3d.cpp index 2b07796448..1bb3ce444e 100644 --- a/Generals/Code/Tools/WorldBuilder/src/wbview3d.cpp +++ b/Generals/Code/Tools/WorldBuilder/src/wbview3d.cpp @@ -31,7 +31,6 @@ #include "W3DDevice/GameClient/Module/W3DModelDraw.h" #include "agg_def.h" #include "part_ldr.h" -#include "rendobj.h" #include "hanim.h" #include "dx8wrapper.h" #include "dx8indexbuffer.h" @@ -78,6 +77,7 @@ #include "WorldBuilder.h" #include "wbview3d.h" #include "Common/Debug.h" +#include "Common/FramePacer.h" #include "Common/ThingFactory.h" #include "GameClient/Water.h" #include "Common/WellKnownKeys.h" @@ -1981,12 +1981,18 @@ void WbView3d::redraw(void) m_showPolygonTriggers || PolygonTool::isActive()); } - WW3D::Sync( GetTickCount() ); + WW3D::Update_Logic_Frame_Time(TheFramePacer->getLogicTimeStepMilliseconds()); + WW3D::Sync(WW3D::Get_Fractional_Sync_Milliseconds() >= WWSyncMilliseconds); + m_buildRedMultiplier += (GetTickCount()-m_time)/500.0f; if (m_buildRedMultiplier>4.0f || m_buildRedMultiplier<0) { m_buildRedMultiplier = 0; } + render(); + + TheFramePacer->update(); + m_time = ::GetTickCount(); } diff --git a/GeneralsMD/Code/GameEngine/Include/Common/GameEngine.h b/GeneralsMD/Code/GameEngine/Include/Common/GameEngine.h index d952b45f58..5ce1fed5a7 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/GameEngine.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/GameEngine.h @@ -55,15 +55,6 @@ class ParticleSystemManager; class GameEngine : public SubsystemInterface { -public: - - typedef UnsignedInt LogicTimeQueryFlags; - enum LogicTimeQueryFlags_ CPP_11(: LogicTimeQueryFlags) - { - IgnoreFrozenTime = 1<<0, // Ignore frozen time for the query - IgnoreHaltedGame = 1<<1, // Ignore halted game for the query - }; - public: GameEngine( void ); @@ -76,24 +67,9 @@ class GameEngine : public SubsystemInterface virtual void execute( void ); /**< The "main loop" of the game engine. It will not return until the game exits. */ - virtual void setFramesPerSecondLimit( Int fps ); ///< Set the max render and engine update fps. - virtual Int getFramesPerSecondLimit( void ); ///< Get the max render and engine update fps. - Real getUpdateTime(); ///< Get the last engine update delta time in seconds. - Real getUpdateFps(); ///< Get the last engine update fps. - static Bool isTimeFrozen(); ///< Returns true if a script has frozen time. static Bool isGameHalted(); ///< Returns true if the game is paused or the network is stalling. - 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. - virtual Int getLogicTimeScaleFps(); ///< Get the raw logic time scale fps value. - 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. - virtual Bool isLogicTimeScaleEnabled(); ///< Check whether the logic time scale setup is enabled. - Int getActualLogicTimeScaleFps(LogicTimeQueryFlags flags = 0); ///< Get the real logic time scale fps, depending on the max render fps, network state and enabled state. - Real getActualLogicTimeScaleRatio(LogicTimeQueryFlags flags = 0); ///< Get the real logic time scale ratio, depending on the max render fps, network state and enabled state. - 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. - Real getLogicTimeStepSeconds(LogicTimeQueryFlags flags = 0); ///< Get the logic time step in seconds - Real getLogicTimeStepMilliseconds(LogicTimeQueryFlags flags = 0); ///< Get the logic time step in milliseconds - virtual void setQuitting( Bool quitting ); ///< set quitting status virtual Bool getQuitting(void); ///< is app getting ready to quit. @@ -124,18 +100,10 @@ class GameEngine : public SubsystemInterface virtual ParticleSystemManager* createParticleSystemManager( void ) = 0; virtual AudioManager *createAudioManager( void ) = 0; ///< Factory for Audio Manager - Int m_maxFPS; ///< Maximum frames per second for rendering - Int m_logicTimeScaleFPS; ///< Maximum frames per second for logic time scale - - Real m_updateTime; ///< Last engine update delta time in seconds Real m_logicTimeAccumulator; ///< Frame time accumulated towards submitting a new logic frame Bool m_quitting; ///< true when we need to quit the game Bool m_isActive; ///< app has OS focus. - Bool m_enableLogicTimeScale; - Bool m_isTimeFrozen; - Bool m_isGameHalted; - }; inline void GameEngine::setQuitting( Bool quitting ) { m_quitting = quitting; } diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp index 02d786b4e5..2b6bcad36d 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp @@ -32,6 +32,7 @@ #include "Common/AudioAffect.h" #include "Common/BuildAssistant.h" #include "Common/CRCDebug.h" +#include "Common/FramePacer.h" #include "Common/Radar.h" #include "Common/PlayerTemplate.h" #include "Common/Team.h" @@ -44,7 +45,6 @@ #include "Common/ThingFactory.h" #include "Common/file.h" #include "Common/FileSystem.h" -#include "Common/FrameRateLimit.h" #include "Common/ArchiveFileSystem.h" #include "Common/LocalFileSystem.h" #include "Common/CDManager.h" @@ -247,19 +247,10 @@ static void updateWindowTitle() //------------------------------------------------------------------------------------------------- GameEngine::GameEngine( void ) { - // Set the time slice size to 1 ms. - timeBeginPeriod(1); - // initialize to non garbage values - m_maxFPS = BaseFps; - m_logicTimeScaleFPS = LOGICFRAMES_PER_SECOND; - m_updateTime = 1.0f / BaseFps; // initialized to something to avoid division by zero on first use m_logicTimeAccumulator = 0.0f; m_quitting = FALSE; m_isActive = FALSE; - m_enableLogicTimeScale = FALSE; - m_isTimeFrozen = FALSE; - m_isGameHalted = FALSE; _Module.Init(NULL, ApplicationHInstance, NULL); } @@ -308,34 +299,6 @@ GameEngine::~GameEngine() #ifdef PERF_TIMERS PerfGather::termPerfDump(); #endif - - // Restore the previous time slice for Windows. - timeEndPeriod(1); -} - -//------------------------------------------------------------------------------------------------- -void GameEngine::setFramesPerSecondLimit( Int fps ) -{ - DEBUG_LOG(("GameEngine::setFramesPerSecondLimit() - setting max fps to %d (TheGlobalData->m_useFpsLimit == %d)", fps, TheGlobalData->m_useFpsLimit)); - m_maxFPS = fps; -} - -//------------------------------------------------------------------------------------------------- -Int GameEngine::getFramesPerSecondLimit( void ) -{ - return m_maxFPS; -} - -//------------------------------------------------------------------------------------------------- -Real GameEngine::getUpdateTime() -{ - return m_updateTime; -} - -//------------------------------------------------------------------------------------------------- -Real GameEngine::getUpdateFps() -{ - return 1.0f / m_updateTime; } //------------------------------------------------------------------------------------------------- @@ -377,80 +340,6 @@ Bool GameEngine::isGameHalted() return false; } -//------------------------------------------------------------------------------------------------- -void GameEngine::setLogicTimeScaleFps( Int fps ) -{ - m_logicTimeScaleFPS = fps; -} - -//------------------------------------------------------------------------------------------------- -Int GameEngine::getLogicTimeScaleFps() -{ - return m_logicTimeScaleFPS; -} - -//------------------------------------------------------------------------------------------------- -void GameEngine::enableLogicTimeScale( Bool enable ) -{ - m_enableLogicTimeScale = enable; -} - -//------------------------------------------------------------------------------------------------- -Bool GameEngine::isLogicTimeScaleEnabled() -{ - return m_enableLogicTimeScale; -} - -//------------------------------------------------------------------------------------------------- -Int GameEngine::getActualLogicTimeScaleFps(LogicTimeQueryFlags flags) -{ - if (m_isTimeFrozen && (flags & IgnoreFrozenTime) == 0) - { - return 0; - } - - if (m_isGameHalted && (flags & IgnoreHaltedGame) == 0) - { - return 0; - } - - if (TheNetwork != NULL) - { - return TheNetwork->getFrameRate(); - } - - if (isLogicTimeScaleEnabled()) - { - return min(getLogicTimeScaleFps(), getFramesPerSecondLimit()); - } - - return getFramesPerSecondLimit(); -} - -//------------------------------------------------------------------------------------------------- -Real GameEngine::getActualLogicTimeScaleRatio(LogicTimeQueryFlags flags) -{ - return (Real)getActualLogicTimeScaleFps(flags) / LOGICFRAMES_PER_SECONDS_REAL; -} - -//------------------------------------------------------------------------------------------------- -Real GameEngine::getActualLogicTimeScaleOverFpsRatio(LogicTimeQueryFlags flags) -{ - // TheSuperHackers @info Clamps ratio to min 1, because the logic - // frame rate is currently capped by the render frame rate. - return min(1.0f, (Real)getActualLogicTimeScaleFps(flags) / getUpdateFps()); -} - -Real GameEngine::getLogicTimeStepSeconds(LogicTimeQueryFlags flags) -{ - return SECONDS_PER_LOGICFRAME_REAL * getActualLogicTimeScaleOverFpsRatio(flags); -} - -Real GameEngine::getLogicTimeStepMilliseconds(LogicTimeQueryFlags flags) -{ - return MSEC_PER_LOGICFRAME_REAL * getActualLogicTimeScaleOverFpsRatio(flags); -} - /** ----------------------------------------------------------------------------------------------- * Initialize the game engine by initializing the GameLogic and GameClient. */ @@ -765,7 +654,7 @@ void GameEngine::init() TheSubsystemList->postProcessLoadAll(); - setFramesPerSecondLimit(TheGlobalData->m_framesPerSecondLimit); + TheFramePacer->setFramesPerSecondLimit(TheGlobalData->m_framesPerSecondLimit); TheAudio->setOn(TheGlobalData->m_audioOn && TheGlobalData->m_musicOn, AudioAffect_Music); TheAudio->setOn(TheGlobalData->m_audioOn && TheGlobalData->m_soundsOn, AudioAffect_Sound); @@ -929,8 +818,8 @@ Bool GameEngine::canUpdateGameLogic() // Must be first. TheGameLogic->preUpdate(); - m_isTimeFrozen = isTimeFrozen(); - m_isGameHalted = isGameHalted(); + TheFramePacer->setTimeFrozen(isTimeFrozen()); + TheFramePacer->setGameHalted(isGameHalted()); if (TheNetwork != NULL) { @@ -942,6 +831,7 @@ Bool GameEngine::canUpdateGameLogic() } } +/// ----------------------------------------------------------------------------------------------- Bool GameEngine::canUpdateNetworkGameLogic() { DEBUG_ASSERTCRASH(TheNetwork != NULL, ("TheNetwork is NULL")); @@ -949,7 +839,7 @@ Bool GameEngine::canUpdateNetworkGameLogic() if (TheNetwork->isFrameDataReady()) { // Important: The Network is definitely no longer stalling. - m_isGameHalted = false; + TheFramePacer->setGameHalted(false); return true; } @@ -957,11 +847,12 @@ Bool GameEngine::canUpdateNetworkGameLogic() return false; } +/// ----------------------------------------------------------------------------------------------- Bool GameEngine::canUpdateRegularGameLogic() { - const Bool enabled = isLogicTimeScaleEnabled(); - const Int logicTimeScaleFps = getLogicTimeScaleFps(); - const Int maxRenderFps = getFramesPerSecondLimit(); + const Bool enabled = TheFramePacer->isLogicTimeScaleEnabled(); + const Int logicTimeScaleFps = TheFramePacer->getLogicTimeScaleFps(); + const Int maxRenderFps = TheFramePacer->getFramesPerSecondLimit(); #if defined(_ALLOW_DEBUG_CHEATS_IN_RELEASE) const Bool useFastMode = TheGlobalData->m_TiVOFastMode; @@ -979,7 +870,7 @@ Bool GameEngine::canUpdateRegularGameLogic() // TheSuperHackers @tweak xezon 06/08/2025 // The logic time step is now decoupled from the render update. const Real targetFrameTime = 1.0f / logicTimeScaleFps; - m_logicTimeAccumulator += min(m_updateTime, targetFrameTime); + m_logicTimeAccumulator += min(TheFramePacer->getUpdateTime(), targetFrameTime); if (m_logicTimeAccumulator >= targetFrameTime) { @@ -1022,8 +913,8 @@ void GameEngine::update( void ) } const Bool canUpdate = canUpdateGameLogic(); - const Bool canUpdateLogic = canUpdate && !m_isGameHalted && !m_isTimeFrozen; - const Bool canUpdateScript = canUpdate && !m_isGameHalted; + const Bool canUpdateLogic = canUpdate && !TheFramePacer->isGameHalted() && !TheFramePacer->isTimeFrozen(); + const Bool canUpdateScript = canUpdate && !TheFramePacer->isGameHalted(); if (canUpdateLogic) { @@ -1048,8 +939,6 @@ extern HWND ApplicationHWnd; */ void GameEngine::execute( void ) { - FrameRateLimit* frameRateLimit = new FrameRateLimit(); - #if defined(RTS_DEBUG) DWORD startTime = timeGetTime() / 1000; #endif @@ -1118,34 +1007,7 @@ void GameEngine::execute( void ) } } - { - { - Bool allowFpsLimit = TheTacticalView->getTimeMultiplier()<=1 && !TheScriptEngine->isTimeFast(); - - // I'm disabling this in debug because many people need alt-tab capability. If you happen to be - // doing performance tuning, please just change this on your local system. -MDC - #if defined(RTS_DEBUG) - if (allowFpsLimit) - ::Sleep(1); // give everyone else a tiny time slice. - #endif - - - #if defined(_ALLOW_DEBUG_CHEATS_IN_RELEASE) - allowFpsLimit &= !(!TheGameLogic->isGamePaused() && TheGlobalData->m_TiVOFastMode); - #else //always allow this cheat key if we're in a replay game. - allowFpsLimit &= !(!TheGameLogic->isGamePaused() && TheGlobalData->m_TiVOFastMode && TheGameLogic->isInReplayGame()); - #endif - { - // TheSuperHackers @bugfix xezon 05/08/2025 Re-implements the frame rate limiter - // with higher resolution counters to cap the frame rate more accurately to the desired limit. - allowFpsLimit &= TheGlobalData->m_useFpsLimit; - const UnsignedInt maxFps = allowFpsLimit ? getFramesPerSecondLimit() : RenderFpsPreset::UncappedFpsValue; - m_updateTime = frameRateLimit->wait(maxFps); - } - - } - } - + TheFramePacer->update(); } #ifdef PERF_TIMERS @@ -1158,8 +1020,6 @@ void GameEngine::execute( void ) #endif } - - delete frameRateLimit; } /** ----------------------------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GameMain.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GameMain.cpp index 7ee88428c0..7c72fc4e1e 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GameMain.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GameMain.cpp @@ -28,6 +28,7 @@ #include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine +#include "Common/FramePacer.h" #include "Common/GameEngine.h" #include "Common/ReplaySimulation.h" @@ -39,6 +40,8 @@ Int GameMain() { int exitcode = 0; // initialize the game engine using factory function + TheFramePacer = new FramePacer(); + TheFramePacer->enableFramesPerSecondLimit(TRUE); TheGameEngine = CreateGameEngine(); TheGameEngine->init(); @@ -53,6 +56,8 @@ Int GameMain() } // since execute() returned, we are exiting the game + delete TheFramePacer; + TheFramePacer = NULL; delete TheGameEngine; TheGameEngine = NULL; diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp index 416da00b5b..ed091e87c8 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -36,8 +36,8 @@ #include "Common/BuildAssistant.h" #include "Common/ClientUpdateModule.h" #include "Common/DrawModule.h" +#include "Common/FramePacer.h" #include "Common/GameAudio.h" -#include "Common/GameEngine.h" #include "Common/GameLOD.h" #include "Common/GameState.h" #include "Common/GlobalData.h" @@ -5542,7 +5542,7 @@ void TintEnvelope::setDecayFrames( UnsignedInt frames ) void TintEnvelope::update(void) { // TheSuperHackers @tweak The tint time step is now decoupled from the render update. - const Real timeScale = TheGameEngine->getActualLogicTimeScaleOverFpsRatio(); + const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); switch ( m_envState ) { diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp index d247d84086..81f65ec654 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp @@ -30,6 +30,7 @@ // INCLUDES /////////////////////////////////////////////////////////////////////////////////////// #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine +#include "Common/FramePacer.h" #include "Common/GameEngine.h" #include "Common/GameState.h" #include "Common/MessageStream.h" @@ -215,7 +216,7 @@ static void restartMissionMenu() Int rankPointsStartedWith = TheGameLogic->getRankPointsToAddAtGameStart();// must write down before reset GameDifficulty diff = TheScriptEngine->getGlobalDifficulty(); - Int fps = TheGameEngine->getFramesPerSecondLimit(); + Int fps = TheFramePacer->getFramesPerSecondLimit(); TheGameLogic->clearGameData(FALSE); TheGameEngine->setQuitting(FALSE); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp index 70f367d029..4c331e3f88 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp @@ -32,8 +32,8 @@ #define DEFINE_SHADOW_NAMES #include "Common/ActionManager.h" +#include "Common/FramePacer.h" #include "Common/GameAudio.h" -#include "Common/GameEngine.h" #include "Common/GameType.h" #include "Common/MessageStream.h" #include "Common/PerfTimer.h" @@ -46,7 +46,6 @@ #include "Common/BuildAssistant.h" #include "Common/Recorder.h" #include "Common/SpecialPower.h" -#include "Common/FrameRateLimit.h" #include "GameClient/Anim2D.h" #include "GameClient/ControlBar.h" @@ -1945,7 +1944,7 @@ void InGameUI::update( void ) if (m_cameraRotatingLeft || m_cameraRotatingRight || m_cameraZoomingIn || m_cameraZoomingOut) { // TheSuperHackers @tweak The camera rotation and zoom are now decoupled from the render update. - const Real fpsRatio = (Real)BaseFps / TheGameEngine->getUpdateFps(); + const Real fpsRatio = (Real)BaseFps / TheFramePacer->getUpdateFps(); const Real rotateAngle = TheGlobalData->m_keyboardCameraRotateSpeed * fpsRatio; const Real zoomHeight = (Real)View::ZoomHeightPerSecond * fpsRatio; @@ -6044,7 +6043,7 @@ void InGameUI::drawRenderFps(Int &x, Int &y) UnsignedInt renderFpsLimit = 0u; if (TheGlobalData->m_useFpsLimit) { - renderFpsLimit = (UnsignedInt)TheGameEngine->getFramesPerSecondLimit(); + renderFpsLimit = (UnsignedInt)TheFramePacer->getFramesPerSecondLimit(); if (renderFpsLimit == RenderFpsPreset::UncappedFpsValue) { renderFpsLimit = 0u; diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp index 7dd70033b9..fadbc5f232 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp @@ -32,7 +32,7 @@ #include "Common/AudioAffect.h" #include "Common/ActionManager.h" -#include "Common/FrameRateLimit.h" +#include "Common/FramePacer.h" #include "Common/GameAudio.h" #include "Common/GameEngine.h" #include "Common/GameType.h" @@ -190,10 +190,10 @@ Bool hasThingsInProduction(PlayerType playerType) bool changeMaxRenderFps(FpsValueChange change) { - UnsignedInt maxRenderFps = TheGameEngine->getFramesPerSecondLimit(); + UnsignedInt maxRenderFps = TheFramePacer->getFramesPerSecondLimit(); maxRenderFps = RenderFpsPreset::changeFpsValue(maxRenderFps, change); - TheGameEngine->setFramesPerSecondLimit(maxRenderFps); + TheFramePacer->setFramesPerSecondLimit(maxRenderFps); TheWritableGlobalData->m_useFpsLimit = (maxRenderFps != RenderFpsPreset::UncappedFpsValue); UnicodeString message; @@ -217,16 +217,16 @@ bool changeLogicTimeScale(FpsValueChange change) if (TheNetwork != NULL) return false; - const UnsignedInt maxRenderFps = TheGameEngine->getFramesPerSecondLimit(); + const UnsignedInt maxRenderFps = TheFramePacer->getFramesPerSecondLimit(); UnsignedInt maxRenderRemainder = LogicTimeScaleFpsPreset::StepFpsValue; maxRenderRemainder -= maxRenderFps % LogicTimeScaleFpsPreset::StepFpsValue; maxRenderRemainder %= LogicTimeScaleFpsPreset::StepFpsValue; - UnsignedInt logicTimeScaleFps = TheGameEngine->getLogicTimeScaleFps(); + UnsignedInt logicTimeScaleFps = TheFramePacer->getLogicTimeScaleFps(); // Set the value to the max render fps value plus a bit when time scale is // disabled. This ensures that the time scale does not re-enable with a // 'surprise' value. - if (!TheGameEngine->isLogicTimeScaleEnabled()) + if (!TheFramePacer->isLogicTimeScaleEnabled()) { logicTimeScaleFps = maxRenderFps + maxRenderRemainder; } @@ -237,26 +237,26 @@ bool changeLogicTimeScale(FpsValueChange change) logicTimeScaleFps = LogicTimeScaleFpsPreset::changeFpsValue(logicTimeScaleFps, change); // Set value before potentially disabling it. - if (TheGameEngine->isLogicTimeScaleEnabled()) + if (TheFramePacer->isLogicTimeScaleEnabled()) { - TheGameEngine->setLogicTimeScaleFps(logicTimeScaleFps); + TheFramePacer->setLogicTimeScaleFps(logicTimeScaleFps); } - TheGameEngine->enableLogicTimeScale(logicTimeScaleFps < maxRenderFps); + TheFramePacer->enableLogicTimeScale(logicTimeScaleFps < maxRenderFps); // Set value after potentially enabling it. - if (TheGameEngine->isLogicTimeScaleEnabled()) + if (TheFramePacer->isLogicTimeScaleEnabled()) { - TheGameEngine->setLogicTimeScaleFps(logicTimeScaleFps); + TheFramePacer->setLogicTimeScaleFps(logicTimeScaleFps); } - logicTimeScaleFps = TheGameEngine->getLogicTimeScaleFps(); - const UnsignedInt actualLogicTimeScaleFps = TheGameEngine->getActualLogicTimeScaleFps(); - const Real actualLogicTimeScaleRatio = TheGameEngine->getActualLogicTimeScaleRatio(); + logicTimeScaleFps = TheFramePacer->getLogicTimeScaleFps(); + const UnsignedInt actualLogicTimeScaleFps = TheFramePacer->getActualLogicTimeScaleFps(); + const Real actualLogicTimeScaleRatio = TheFramePacer->getActualLogicTimeScaleRatio(); UnicodeString message; - if (TheGameEngine->isLogicTimeScaleEnabled()) + if (TheFramePacer->isLogicTimeScaleEnabled()) { message = TheGameText->FETCH_OR_SUBSTITUTE_FORMAT("GUI:SetLogicTimeScaleFps", L"Logic Time Scale FPS is %u (actual %u, ratio %.02f)", logicTimeScaleFps, actualLogicTimeScaleFps, actualLogicTimeScaleRatio); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp index e753b25869..fc20aa0185 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp @@ -28,8 +28,8 @@ #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine +#include "Common/FramePacer.h" #include "Common/GameType.h" -#include "Common/GameEngine.h" #include "Common/MessageStream.h" #include "Common/Player.h" #include "Common/PlayerList.h" @@ -439,7 +439,7 @@ GameMessageDisposition LookAtTranslator::translateGameMessage(const GameMessage { // TheSuperHackers @bugfix Mauller 07/06/2025 The camera scrolling is now decoupled from the render update. - const Real fpsRatio = (Real)BaseFps / TheGameEngine->getUpdateFps(); + const Real fpsRatio = (Real)BaseFps / TheFramePacer->getUpdateFps(); switch (m_scrollType) { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp index 710b49eeee..dbe9e4a5e8 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp @@ -31,8 +31,8 @@ #include "Common/AudioAffect.h" #include "Common/AudioHandleSpecialValues.h" +#include "Common/FramePacer.h" #include "Common/GameAudio.h" -#include "Common/GameEngine.h" #include "Common/MapObject.h" // For MAP_XY_FACTOR #include "Common/PartitionSolver.h" #include "Common/Player.h" @@ -7129,11 +7129,11 @@ void ScriptActions::executeAction( ScriptAction *pAction ) case ScriptAction::SET_FPS_LIMIT: if (!pAction->getParameter(0)->getInt()) { - TheGameEngine->setFramesPerSecondLimit(TheGlobalData->m_framesPerSecondLimit); + TheFramePacer->setFramesPerSecondLimit(TheGlobalData->m_framesPerSecondLimit); } else { - TheGameEngine->setFramesPerSecondLimit(pAction->getParameter(0)->getInt()); + TheFramePacer->setFramesPerSecondLimit(pAction->getParameter(0)->getInt()); } // Setting the fps limit doesn't do much good if we don't use it. jba. TheWritableGlobalData->m_useFpsLimit = true; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp index 364cadf7ec..ee5d5fd1f5 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp @@ -31,7 +31,7 @@ #include "Common/DataChunk.h" #include "Common/file.h" #include "Common/FileSystem.h" -#include "Common/GameEngine.h" +#include "Common/FramePacer.h" #include "Common/GameState.h" #include "Common/LatchRestore.h" #include "Common/MessageStream.h" @@ -5265,8 +5265,8 @@ void ScriptEngine::init( void ) void ScriptEngine::reset( void ) { // setting FPS limit in case a script had changed it - if (TheGameEngine && TheGlobalData) - TheGameEngine->setFramesPerSecondLimit(TheGlobalData->m_framesPerSecondLimit); + if (TheFramePacer && TheGlobalData) + TheFramePacer->setFramesPerSecondLimit(TheGlobalData->m_framesPerSecondLimit); if (TheScriptActions) { TheScriptActions->reset(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index d7f82871e4..6e9ec822f2 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -33,6 +33,7 @@ #include "Common/AudioHandleSpecialValues.h" #include "Common/BuildAssistant.h" #include "Common/CRCDebug.h" +#include "Common/FramePacer.h" #include "Common/GameAudio.h" #include "Common/GameEngine.h" #include "Common/GameLOD.h" @@ -4271,8 +4272,8 @@ void GameLogic::setGamePausedInFrame( UnsignedInt frame, Bool disableLogicTimeSc if (disableLogicTimeScale) { - m_logicTimeScaleEnabledMemory = TheGameEngine->isLogicTimeScaleEnabled(); - TheGameEngine->enableLogicTimeScale(FALSE); + m_logicTimeScaleEnabledMemory = TheFramePacer->isLogicTimeScaleEnabled(); + TheFramePacer->enableLogicTimeScale(FALSE); } } } @@ -4311,7 +4312,7 @@ void GameLogic::pauseGameLogic(Bool paused) if (!paused && m_logicTimeScaleEnabledMemory) { m_logicTimeScaleEnabledMemory = FALSE; - TheGameEngine->enableLogicTimeScale(TRUE); + TheFramePacer->enableLogicTimeScale(TRUE); } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp index 889b7a3ca5..b680f82c4f 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp @@ -31,6 +31,7 @@ #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine #include "Common/CRCDebug.h" +#include "Common/FramePacer.h" #include "Common/GameAudio.h" #include "Common/GameEngine.h" #include "Common/GlobalData.h" @@ -436,7 +437,7 @@ void GameLogic::logicMessageDispatcher( GameMessage *msg, void *userData ) if (maxFPS < 1 || maxFPS > 1000) maxFPS = TheGlobalData->m_framesPerSecondLimit; DEBUG_LOG(("Setting max FPS limit to %d FPS", maxFPS)); - TheGameEngine->setFramesPerSecondLimit(maxFPS); + TheFramePacer->setFramesPerSecondLimit(maxFPS); TheWritableGlobalData->m_useFpsLimit = true; } diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp index b2949a68ca..352b9d97c7 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp @@ -41,8 +41,8 @@ static void drawFramerateBar(void); #include // USER INCLUDES ////////////////////////////////////////////////////////////// +#include "Common/FramePacer.h" #include "Common/ThingFactory.h" -#include "Common/GameEngine.h" #include "Common/GlobalData.h" #include "Common/PerfTimer.h" #include "Common/FileSystem.h" @@ -1025,7 +1025,7 @@ void W3DDisplay::gatherDebugStats( void ) Int LOD = TheGlobalData->m_terrainLOD; //unibuffer.format( L"FPS: %.2f, %.2fms mapLOD=%d [cumu FPS=%.2f] draws: %.2f sort: %.2f", fps, ms, LOD, cumuFPS, drawsPerFrame,sortPolysPerFrame); if (TheGlobalData->m_useFpsLimit) - unibuffer.format( L"%.2f/%d FPS, ", fps, TheGameEngine->getFramesPerSecondLimit()); + unibuffer.format( L"%.2f/%d FPS, ", fps, TheFramePacer->getFramesPerSecondLimit()); else unibuffer.format( L"%.2f FPS, ", fps); @@ -1754,7 +1754,7 @@ void W3DDisplay::draw( void ) // //PredictiveLODOptimizerClass::Optimize_LODs( 5000 ); - Bool freezeTime = TheGameEngine->isTimeFrozen() || TheGameEngine->isGameHalted(); + Bool freezeTime = TheFramePacer->isTimeFrozen() || TheFramePacer->isGameHalted(); /// @todo: I'm assuming the first view is our main 3D view. W3DView *primaryW3DView=(W3DView *)getFirstView(); @@ -1790,7 +1790,7 @@ void W3DDisplay::draw( void ) } } - WW3D::Update_Logic_Frame_Time(TheGameEngine->getLogicTimeStepMilliseconds()); + WW3D::Update_Logic_Frame_Time(TheFramePacer->getLogicTimeStepMilliseconds()); // TheSuperHackers @info This binds the WW3D update to the logic update. WW3D::Sync(TheGameLogic->hasUpdated()); diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp index a8b8d3474e..3cf288e5da 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp @@ -38,7 +38,7 @@ // USER INCLUDES ////////////////////////////////////////////////////////////////////////////////// #include "Common/BuildAssistant.h" -#include "Common/GameEngine.h" +#include "Common/FramePacer.h" #include "Common/GlobalData.h" #include "Common/Module.h" #include "Common/RandomValue.h" @@ -399,7 +399,7 @@ void W3DView::buildCameraTransform( Matrix3D *transform ) //WST 11/12/2002 New camera shaker system // TheSuperHackers @tweak The camera shaker is now decoupled from the render update. - CameraShakerSystem.Timestep(TheGameEngine->getLogicTimeStepMilliseconds()); + CameraShakerSystem.Timestep(TheFramePacer->getLogicTimeStepMilliseconds()); CameraShakerSystem.Update_Camera_Shaker(sourcePos, &m_shakerAngles); transform->Rotate_X(m_shakerAngles.X); transform->Rotate_Y(m_shakerAngles.Y); @@ -1046,7 +1046,7 @@ Bool W3DView::updateCameraMovements() m_previousLookAtPosition = *getPosition(); // TheSuperHackers @tweak The scripted camera movement is now decoupled from the render update. // The scripted camera will still move when the time is frozen, but not when the game is halted. - moveAlongWaypointPath(TheGameEngine->getLogicTimeStepMilliseconds(GameEngine::IgnoreFrozenTime)); + moveAlongWaypointPath(TheFramePacer->getLogicTimeStepMilliseconds(FramePacer::IgnoreFrozenTime)); didUpdate = true; } if (m_doingScriptedCameraLock) @@ -1355,7 +1355,7 @@ void W3DView::update(void) // if scrolling, only adjust if we're too close or too far if (m_scrollAmount.length() < m_scrollAmountCutoff || (m_currentHeightAboveGround < m_minHeightAboveGround) || (TheGlobalData->m_enforceMaxCameraHeight && m_currentHeightAboveGround > m_maxHeightAboveGround)) { - const Real fpsRatio = (Real)BaseFps / TheGameEngine->getUpdateFps(); + const Real fpsRatio = (Real)BaseFps / TheFramePacer->getUpdateFps(); const Real zoomAdj = (desiredZoom - m_zoom) * TheGlobalData->m_cameraAdjustSpeed * fpsRatio; if (fabs(zoomAdj) >= 0.0001f) // only do positive { @@ -1367,7 +1367,7 @@ void W3DView::update(void) else if (!didScriptedMovement) { // we're not scrolling; settle toward desired height above ground - const Real fpsRatio = (Real)BaseFps / TheGameEngine->getUpdateFps(); + const Real fpsRatio = (Real)BaseFps / TheFramePacer->getUpdateFps(); const Real zoomAdj = (m_zoom - desiredZoom) * TheGlobalData->m_cameraAdjustSpeed * fpsRatio; if (fabs(zoomAdj) >= 0.0001f) { diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp index e9599a7a25..9ef4271dcc 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp @@ -49,7 +49,7 @@ #include "mesh.h" #include "matinfo.h" -#include "Common/GameEngine.h" +#include "Common/FramePacer.h" #include "Common/GameState.h" #include "Common/GlobalData.h" #include "Common/PerfTimer.h" @@ -1210,7 +1210,7 @@ void WaterRenderObjClass::enableWaterGrid(Bool state) void WaterRenderObjClass::update( void ) { // TheSuperHackers @tweak The water movement time step is now decoupled from the render update. - const Real timeScale = TheGameEngine->getActualLogicTimeScaleOverFpsRatio(); + const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); { constexpr const Real MagicOffset = 0.0125f * 33 / 5000; ///< the work of top Munkees; do not question it diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWaterTracks.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWaterTracks.cpp index 8169dbb7f7..98c2556fbf 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWaterTracks.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWaterTracks.cpp @@ -50,7 +50,7 @@ #include "GameClient/InGameUI.h" #include "GameClient/Water.h" #include "GameLogic/TerrainLogic.h" -#include "Common/GameEngine.h" +#include "Common/FramePacer.h" #include "Common/GlobalData.h" #include "Common/UnicodeString.h" #include "Common/file.h" @@ -296,7 +296,7 @@ Int WaterTracksObj::update(Int msElapsed) Int WaterTracksObj::render(DX8VertexBufferClass *vertexBuffer, Int batchStart) { // TheSuperHackers @tweak The wave movement time step is now decoupled from the render update. - m_elapsedMs += TheGameEngine->getLogicTimeStepMilliseconds(); + m_elapsedMs += TheFramePacer->getLogicTimeStepMilliseconds(); VertexFormatXYZDUV1 *vb; Vector2 waveTailOrigin,waveFrontOrigin; diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.h b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.h index 0057dbf989..5e8c5a9fc2 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.h +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.h @@ -173,6 +173,7 @@ class WW3D static void Sync(bool step); static unsigned int Get_Sync_Time(void) { return SyncTime; } static unsigned int Get_Sync_Frame_Time(void) { return SyncTime - PreviousSyncTime; } + static unsigned int Get_Fractional_Sync_Milliseconds() { return FractionalSyncMs; } static float Get_Logic_Frame_Time_Milliseconds() { return LogicFrameTimeMs; } static float Get_Logic_Frame_Time_Seconds() { return LogicFrameTimeMs * 0.001f; } static unsigned int Get_Frame_Count(void) { return FrameCount; } diff --git a/GeneralsMD/Code/Tools/GUIEdit/Source/EditWindow.cpp b/GeneralsMD/Code/Tools/GUIEdit/Source/EditWindow.cpp index b53ed8a602..9c577027cc 100644 --- a/GeneralsMD/Code/Tools/GUIEdit/Source/EditWindow.cpp +++ b/GeneralsMD/Code/Tools/GUIEdit/Source/EditWindow.cpp @@ -49,6 +49,7 @@ // USER INCLUDES ////////////////////////////////////////////////////////////// #include "Common/Debug.h" +#include "Common/FramePacer.h" #include "GameClient/Display.h" #include "GameClient/GameWindowManager.h" #include "W3DDevice/GameClient/W3DFileSystem.h" @@ -1470,13 +1471,9 @@ void EditWindow::drawGrid( void ) //============================================================================= void EditWindow::draw( void ) { - static UnsignedInt syncTime = 0; - // allow W3D to update its internals - WW3D::Sync( syncTime ); - - // for now, use constant time steps to avoid animations running independent of framerate - syncTime += 50; + WW3D::Update_Logic_Frame_Time(TheFramePacer->getLogicTimeStepMilliseconds()); + WW3D::Sync(WW3D::Get_Fractional_Sync_Milliseconds() >= WWSyncMilliseconds); // start render block WW3D::Begin_Render( true, true, Vector3( m_backgroundColor.red, @@ -1493,6 +1490,7 @@ void EditWindow::draw( void ) // render is all done! WW3D::End_Render(); + TheFramePacer->update(); } // EditWindow::setSize ======================================================== diff --git a/GeneralsMD/Code/Tools/GUIEdit/Source/WinMain.cpp b/GeneralsMD/Code/Tools/GUIEdit/Source/WinMain.cpp index 4a1a529a61..b6b4375057 100644 --- a/GeneralsMD/Code/Tools/GUIEdit/Source/WinMain.cpp +++ b/GeneralsMD/Code/Tools/GUIEdit/Source/WinMain.cpp @@ -50,8 +50,8 @@ // USER INCLUDES ////////////////////////////////////////////////////////////// #include "Common/Debug.h" +#include "Common/FramePacer.h" #include "Common/GameMemory.h" -#include "Common/GameEngine.h" #include "GameClient/GameWindowManager.h" #include "Win32Device/GameClient/Win32Mouse.h" #include "resource.h" @@ -224,6 +224,8 @@ Int APIENTRY WinMain(HINSTANCE hInstance, return FALSE; TheEditor->init(); + TheFramePacer = new FramePacer(); + // // see if we have any messages to process, a NULL window handle tells the // OS to look at the main window associated with the calling thread, us! @@ -262,7 +264,7 @@ Int APIENTRY WinMain(HINSTANCE hInstance, else { - // udpate our universe + // update our universe TheEditor->update(); Sleep(1); @@ -271,6 +273,9 @@ Int APIENTRY WinMain(HINSTANCE hInstance, } // shutdown GUIEdit data + delete TheFramePacer; + TheFramePacer = NULL; + delete TheEditor; TheEditor = NULL; diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilder.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilder.cpp index 2225c6f9a2..dead6bdcc3 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilder.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilder.cpp @@ -33,6 +33,7 @@ //#include #include "W3DDevice/GameClient/W3DFileSystem.h" +#include "Common/FramePacer.h" #include "Common/GlobalData.h" #include "WHeightMapEdit.h" //#include "Common/GameFileSystem.h" @@ -350,6 +351,8 @@ BOOL CWorldBuilderApp::InitInstance() initSubsystem(TheWritableGlobalData, new GlobalData(), "Data\\INI\\Default\\GameData", "Data\\INI\\GameData"); + TheFramePacer = new FramePacer(); + #if defined(RTS_DEBUG) ini.loadFileDirectory( AsciiString( "Data\\INI\\GameDataDebug" ), INI_LOAD_MULTIFILE, NULL ); #endif @@ -654,6 +657,9 @@ int CWorldBuilderApp::ExitInstance() WorldHeightMapEdit::shutdown(); + delete TheFramePacer; + TheFramePacer = NULL; + delete TheFileSystem; TheFileSystem = NULL; diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/wbview3d.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/wbview3d.cpp index df6af034c5..751e1d830d 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/wbview3d.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/wbview3d.cpp @@ -31,7 +31,6 @@ #include "W3DDevice/GameClient/Module/W3DModelDraw.h" #include "agg_def.h" #include "part_ldr.h" -#include "rendobj.h" #include "hanim.h" #include "dx8wrapper.h" #include "dx8indexbuffer.h" @@ -78,6 +77,7 @@ #include "WorldBuilder.h" #include "wbview3d.h" #include "Common/Debug.h" +#include "Common/FramePacer.h" #include "Common/ThingFactory.h" #include "GameClient/Water.h" #include "Common/WellKnownKeys.h" @@ -2058,12 +2058,18 @@ void WbView3d::redraw(void) m_showBoundingBoxes, m_showSightRanges, m_showWeaponRanges, m_showSoundCircles, m_highlightTestArt, m_showLetterbox); } - WW3D::Sync( GetTickCount() ); + WW3D::Update_Logic_Frame_Time(TheFramePacer->getLogicTimeStepMilliseconds()); + WW3D::Sync(WW3D::Get_Fractional_Sync_Milliseconds() >= WWSyncMilliseconds); + m_buildRedMultiplier += (GetTickCount()-m_time)/500.0f; if (m_buildRedMultiplier>4.0f || m_buildRedMultiplier<0) { m_buildRedMultiplier = 0; } + render(); + + TheFramePacer->update(); + m_time = ::GetTickCount(); }