diff --git a/Core/GameEngine/Include/Common/FramePacer.h b/Core/GameEngine/Include/Common/FramePacer.h index c8681cab7a7..7f3fa3587cc 100644 --- a/Core/GameEngine/Include/Common/FramePacer.h +++ b/Core/GameEngine/Include/Common/FramePacer.h @@ -42,6 +42,7 @@ class FramePacer void reset(); ///< Move the frame timing anchor to now and predict the next update time from the target frame rate. Call after a long blocking operation so its duration does not leak into the next frame delta. void setFramesPerSecondLimit( Int fps ); ///< Set the update fps limit. + UnsignedInt changeFramesPerSecondLimit( FpsValueChange change ); ///< Step the update fps limit to the next/prev preset, apply it, and return the new value. 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. @@ -57,9 +58,9 @@ class FramePacer 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. + UnsignedInt changeLogicTimeScaleFps( FpsValueChange change ); ///< Step the logic time scale fps to the next/prev preset, apply and enable/disable it as needed, and return the resulting value. + void enableLogicTimeScale( Bool enable, Int fps = -1 ); ///< Enable or disable the logic time scale setup. If fps >= 0, also sets the logic time scale fps before enabling. 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. diff --git a/Core/GameEngine/Include/Common/FrameRateLimit.h b/Core/GameEngine/Include/Common/FrameRateLimit.h index a729313d005..5274df301c1 100644 --- a/Core/GameEngine/Include/Common/FrameRateLimit.h +++ b/Core/GameEngine/Include/Common/FrameRateLimit.h @@ -50,11 +50,12 @@ class RenderFpsPreset UncappedFpsValue = 1000000, }; - static UnsignedInt getNextFpsValue(UnsignedInt value); - static UnsignedInt getPrevFpsValue(UnsignedInt value); static UnsignedInt changeFpsValue(UnsignedInt value, FpsValueChange change); private: + static UnsignedInt getNextFpsValue(UnsignedInt value); + static UnsignedInt getPrevFpsValue(UnsignedInt value); + static const UnsignedInt s_fpsValues[]; }; @@ -62,18 +63,13 @@ class RenderFpsPreset class LogicTimeScaleFpsPreset { public: - enum CPP_11(: UnsignedInt) - { -#if RTS_DEBUG - MinFpsValue = 5, -#else - MinFpsValue = LOGICFRAMES_PER_SECOND, -#endif - StepFpsValue = 5, - }; - static UnsignedInt getNextFpsValue(UnsignedInt value); - static UnsignedInt getPrevFpsValue(UnsignedInt value); - static UnsignedInt changeFpsValue(UnsignedInt value, FpsValueChange change); + static UnsignedInt changeFpsValue(UnsignedInt value, FpsValueChange change, UnsignedInt snapValue = 0); + +private: + static UnsignedInt getNextFpsValue(UnsignedInt value, UnsignedInt snapValue = 0); + static UnsignedInt getPrevFpsValue(UnsignedInt value, UnsignedInt snapValue = 0); + + static const UnsignedInt s_fpsValues[]; }; diff --git a/Core/GameEngine/Source/Common/FramePacer.cpp b/Core/GameEngine/Source/Common/FramePacer.cpp index 8f83bff10fb..5c7d6432717 100644 --- a/Core/GameEngine/Source/Common/FramePacer.cpp +++ b/Core/GameEngine/Source/Common/FramePacer.cpp @@ -70,6 +70,17 @@ void FramePacer::setFramesPerSecondLimit( Int fps ) m_maxFPS = fps; } +UnsignedInt FramePacer::changeFramesPerSecondLimit( FpsValueChange change ) +{ + UnsignedInt maxRenderFps = getFramesPerSecondLimit(); + maxRenderFps = RenderFpsPreset::changeFpsValue(maxRenderFps, change); + + setFramesPerSecondLimit(maxRenderFps); + TheWritableGlobalData->m_useFpsLimit = (maxRenderFps != RenderFpsPreset::UncappedFpsValue); + + return maxRenderFps; +} + Int FramePacer::getFramesPerSecondLimit() const { return m_maxFPS; @@ -111,7 +122,8 @@ Bool FramePacer::isActualFramesPerSecondLimitEnabled() const Int FramePacer::getActualFramesPerSecondLimit() const { - return isActualFramesPerSecondLimitEnabled() ? getFramesPerSecondLimit() : RenderFpsPreset::UncappedFpsValue; + const Int limit = isActualFramesPerSecondLimitEnabled() ? getFramesPerSecondLimit() : RenderFpsPreset::UncappedFpsValue; + return (TheNetwork != nullptr) ? std::max(limit, static_cast(LOGICFRAMES_PER_SECOND)) : limit; } Real FramePacer::getUpdateTime() const @@ -151,18 +163,43 @@ Bool FramePacer::isGameHalted() const return m_isGameHalted; } -void FramePacer::setLogicTimeScaleFps( Int fps ) +Int FramePacer::getLogicTimeScaleFps() const { - m_logicTimeScaleFPS = fps; + return m_logicTimeScaleFPS; } -Int FramePacer::getLogicTimeScaleFps() const +UnsignedInt FramePacer::changeLogicTimeScaleFps( FpsValueChange change ) { - return m_logicTimeScaleFPS; + const UnsignedInt maxRenderFps = getFramesPerSecondLimit(); + UnsignedInt logicTimeScaleFps = getLogicTimeScaleFps(); + + if (!isLogicTimeScaleEnabled()) + { + logicTimeScaleFps = maxRenderFps; + } + + logicTimeScaleFps = LogicTimeScaleFpsPreset::changeFpsValue(logicTimeScaleFps, change, maxRenderFps); + + // Ensure logic FPS never exceeds render FPS + if (logicTimeScaleFps > maxRenderFps && logicTimeScaleFps != RenderFpsPreset::UncappedFpsValue) + { + logicTimeScaleFps = maxRenderFps; + } + + const bool enableTimescale = (logicTimeScaleFps < maxRenderFps); + + // TheSuperHackers @info Preserve the last real FPS in m_logicTimeScaleFPS so re-enabling timescale resumes from a sane value. + enableLogicTimeScale(enableTimescale, enableTimescale ? (Int)logicTimeScaleFps : -1); + + return getLogicTimeScaleFps(); } -void FramePacer::enableLogicTimeScale( Bool enable ) +void FramePacer::enableLogicTimeScale( Bool enable, Int fps ) { + if (fps >= 0) + { + m_logicTimeScaleFPS = fps; + } m_enableLogicTimeScale = enable; } diff --git a/Core/GameEngine/Source/Common/FrameRateLimit.cpp b/Core/GameEngine/Source/Common/FrameRateLimit.cpp index 940b44f88c4..45a19517821 100644 --- a/Core/GameEngine/Source/Common/FrameRateLimit.cpp +++ b/Core/GameEngine/Source/Common/FrameRateLimit.cpp @@ -67,9 +67,11 @@ void FrameRateLimit::reset() const UnsignedInt RenderFpsPreset::s_fpsValues[] = { - 30, 50, 56, 60, 65, 70, 72, 75, 80, 85, 90, 100, 110, 120, 144, 240, 480, UncappedFpsValue }; + 15, 30, 50, 56, 60, 65, 70, 72, 75, 80, 85, 90, 100, 110, 120, 144, 240, 480, UncappedFpsValue }; -static_assert(LOGICFRAMES_PER_SECOND <= 30, "Min FPS values need to be revisited!"); +// TheSuperHackers @info s_fpsValues MUST be strictly ascending; the search loops break on first match. +const UnsignedInt LogicTimeScaleFpsPreset::s_fpsValues[] = { + 1, 5, 15, 30, 45, 60, 75, 90, 105, 120, 240, 480, 960, RenderFpsPreset::UncappedFpsValue }; UnsignedInt RenderFpsPreset::getNextFpsValue(UnsignedInt value) { @@ -109,30 +111,75 @@ UnsignedInt RenderFpsPreset::changeFpsValue(UnsignedInt value, FpsValueChange ch } } - -UnsignedInt LogicTimeScaleFpsPreset::getNextFpsValue(UnsignedInt value) +UnsignedInt LogicTimeScaleFpsPreset::getNextFpsValue(UnsignedInt value, UnsignedInt snapValue) { - return value + StepFpsValue; + UnsignedInt nextValue = RenderFpsPreset::UncappedFpsValue; + + // Check if snapValue (e.g. current render FPS) is the next closest candidate + if (snapValue > value && snapValue < nextValue) + { + nextValue = snapValue; + } + + // Check predefined steps + for (size_t i = 0; i < ARRAY_SIZE(s_fpsValues); ++i) + { + const UnsignedInt fpsValue = s_fpsValues[i]; + if (fpsValue > value) + { + if (fpsValue < nextValue) + { + nextValue = fpsValue; + } + break; + } + } + + return nextValue; } -UnsignedInt LogicTimeScaleFpsPreset::getPrevFpsValue(UnsignedInt value) +UnsignedInt LogicTimeScaleFpsPreset::getPrevFpsValue(UnsignedInt value, UnsignedInt snapValue) { - if (value - StepFpsValue < MinFpsValue) + UnsignedInt prevValue = s_fpsValues[0]; // Floor/seed for the search loop + + // Check if snapValue (e.g. current render FPS) is the previous closest candidate. + // Note: if snapValue == value, neither branch below fires and the snap point is + // intentionally skipped — the caller must step to a different preset. + if (snapValue > prevValue && snapValue < value) { - return MinFpsValue; + prevValue = snapValue; } - else + + // Check predefined steps + for (int i = (int)ARRAY_SIZE(s_fpsValues) - 1; i >= 0; --i) { - return value - StepFpsValue; + const UnsignedInt fpsValue = s_fpsValues[i]; + if (fpsValue < value) + { + if (fpsValue > prevValue) + { + prevValue = fpsValue; + } + + break; + } } + + return prevValue; } -UnsignedInt LogicTimeScaleFpsPreset::changeFpsValue(UnsignedInt value, FpsValueChange change) +UnsignedInt LogicTimeScaleFpsPreset::changeFpsValue(UnsignedInt value, FpsValueChange change, UnsignedInt snapValue) { switch (change) { - default: - case FpsValueChange_Increase: return getNextFpsValue(value); - case FpsValueChange_Decrease: return getPrevFpsValue(value); + case FpsValueChange_Increase: + return getNextFpsValue(value, snapValue); + + case FpsValueChange_Decrease: + return getPrevFpsValue(value, snapValue); + + default: + assert(false); + return value; } } diff --git a/Core/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp b/Core/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp index 7e322b28241..d9cddc72eb9 100644 --- a/Core/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp +++ b/Core/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp @@ -182,11 +182,7 @@ Bool hasThingsInProduction(PlayerType playerType) bool changeMaxRenderFps(FpsValueChange change) { - UnsignedInt maxRenderFps = TheFramePacer->getFramesPerSecondLimit(); - maxRenderFps = RenderFpsPreset::changeFpsValue(maxRenderFps, change); - - TheFramePacer->setFramesPerSecondLimit(maxRenderFps); - TheWritableGlobalData->m_useFpsLimit = (maxRenderFps != RenderFpsPreset::UncappedFpsValue); + const UnsignedInt maxRenderFps = TheFramePacer->changeFramesPerSecondLimit(change); UnicodeString message; @@ -209,40 +205,7 @@ bool changeLogicTimeScale(FpsValueChange change) if (TheNetwork != nullptr) return false; - const UnsignedInt maxRenderFps = TheFramePacer->getFramesPerSecondLimit(); - UnsignedInt maxRenderRemainder = LogicTimeScaleFpsPreset::StepFpsValue; - maxRenderRemainder -= maxRenderFps % LogicTimeScaleFpsPreset::StepFpsValue; - maxRenderRemainder %= LogicTimeScaleFpsPreset::StepFpsValue; - - 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 (!TheFramePacer->isLogicTimeScaleEnabled()) - { - logicTimeScaleFps = maxRenderFps + maxRenderRemainder; - } - // Ceil the value at the max render fps value plus a bit so that the next fps - // value decrease would undercut the max render fps at the correct step value. - // Example: render fps 72 -> logic value ceiled to 75 -> decreased to 70. - logicTimeScaleFps = min(logicTimeScaleFps, maxRenderFps + maxRenderRemainder); - logicTimeScaleFps = LogicTimeScaleFpsPreset::changeFpsValue(logicTimeScaleFps, change); - - // Set value before potentially disabling it. - if (TheFramePacer->isLogicTimeScaleEnabled()) - { - TheFramePacer->setLogicTimeScaleFps(logicTimeScaleFps); - } - - TheFramePacer->enableLogicTimeScale(logicTimeScaleFps < maxRenderFps); - - // Set value after potentially enabling it. - if (TheFramePacer->isLogicTimeScaleEnabled()) - { - TheFramePacer->setLogicTimeScaleFps(logicTimeScaleFps); - } - - logicTimeScaleFps = TheFramePacer->getLogicTimeScaleFps(); + const UnsignedInt logicTimeScaleFps = TheFramePacer->changeLogicTimeScaleFps(change); const UnsignedInt actualLogicTimeScaleFps = TheFramePacer->getActualLogicTimeScaleFps(); const Real actualLogicTimeScaleRatio = TheFramePacer->getActualLogicTimeScaleRatio();