diff --git a/Core/GameEngine/Include/Common/FrameRateLimit.h b/Core/GameEngine/Include/Common/FrameRateLimit.h index 5bb2b5fd3a7..bda06007b3e 100644 --- a/Core/GameEngine/Include/Common/FrameRateLimit.h +++ b/Core/GameEngine/Include/Common/FrameRateLimit.h @@ -61,18 +61,12 @@ 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 getNextFpsValue(UnsignedInt value, UnsignedInt snapValue = 0); + static UnsignedInt getPrevFpsValue(UnsignedInt value, UnsignedInt snapValue = 0); + static UnsignedInt changeFpsValue(UnsignedInt value, FpsValueChange change, UnsignedInt snapValue = 0); + +private: + static const UnsignedInt s_fpsValues[]; }; diff --git a/Core/GameEngine/Source/Common/FrameRateLimit.cpp b/Core/GameEngine/Source/Common/FrameRateLimit.cpp index ffd76c23439..42136119917 100644 --- a/Core/GameEngine/Source/Common/FrameRateLimit.cpp +++ b/Core/GameEngine/Source/Common/FrameRateLimit.cpp @@ -60,9 +60,11 @@ Real FrameRateLimit::wait(UnsignedInt maxFps) 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) { @@ -102,30 +104,74 @@ UnsignedInt RenderFpsPreset::changeFpsValue(UnsignedInt value, FpsValueChange ch } } - -UnsignedInt LogicTimeScaleFpsPreset::getNextFpsValue(UnsignedInt value) +UnsignedInt LogicTimeScaleFpsPreset::getNextFpsValue(UnsignedInt value, UnsignedInt snapValue) { - return value + StepFpsValue; + UnsignedInt nextValue = s_fpsValues[ARRAY_SIZE(s_fpsValues) - 1]; // Defaults to Uncapped + + // 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/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp b/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp index c6235b0f7da..120faf36aef 100644 --- a/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp @@ -211,38 +211,30 @@ bool changeLogicTimeScale(FpsValueChange change) 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; + logicTimeScaleFps = maxRenderFps; } - // 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()) + logicTimeScaleFps = LogicTimeScaleFpsPreset::changeFpsValue(logicTimeScaleFps, change, maxRenderFps); + + // Ensure logic FPS never exceeds render FPS + if (logicTimeScaleFps > maxRenderFps && logicTimeScaleFps != RenderFpsPreset::UncappedFpsValue) { - TheFramePacer->setLogicTimeScaleFps(logicTimeScaleFps); + logicTimeScaleFps = maxRenderFps; } - TheFramePacer->enableLogicTimeScale(logicTimeScaleFps < maxRenderFps); + const bool enableTimescale = (logicTimeScaleFps < maxRenderFps); - // Set value after potentially enabling it. - if (TheFramePacer->isLogicTimeScaleEnabled()) + // TheSuperHackers @info Preserve the last real FPS in m_logicTimeScaleFPS so re-enabling timescale resumes from a sane value. + if (enableTimescale) { TheFramePacer->setLogicTimeScaleFps(logicTimeScaleFps); } + TheFramePacer->enableLogicTimeScale(enableTimescale); logicTimeScaleFps = TheFramePacer->getLogicTimeScaleFps(); const UnsignedInt actualLogicTimeScaleFps = TheFramePacer->getActualLogicTimeScaleFps(); const Real actualLogicTimeScaleRatio = TheFramePacer->getActualLogicTimeScaleRatio(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp index 830faa70ac9..63e5953483b 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp @@ -210,38 +210,30 @@ bool changeLogicTimeScale(FpsValueChange change) 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; + logicTimeScaleFps = maxRenderFps; } - // 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()) + logicTimeScaleFps = LogicTimeScaleFpsPreset::changeFpsValue(logicTimeScaleFps, change, maxRenderFps); + + // Ensure logic FPS never exceeds render FPS + if (logicTimeScaleFps > maxRenderFps && logicTimeScaleFps != RenderFpsPreset::UncappedFpsValue) { - TheFramePacer->setLogicTimeScaleFps(logicTimeScaleFps); + logicTimeScaleFps = maxRenderFps; } - TheFramePacer->enableLogicTimeScale(logicTimeScaleFps < maxRenderFps); + const bool enableTimescale = (logicTimeScaleFps < maxRenderFps); - // Set value after potentially enabling it. - if (TheFramePacer->isLogicTimeScaleEnabled()) + // TheSuperHackers @info Preserve the last real FPS in m_logicTimeScaleFPS so re-enabling timescale resumes from a sane value. + if (enableTimescale) { TheFramePacer->setLogicTimeScaleFps(logicTimeScaleFps); } + TheFramePacer->enableLogicTimeScale(enableTimescale); logicTimeScaleFps = TheFramePacer->getLogicTimeScaleFps(); const UnsignedInt actualLogicTimeScaleFps = TheFramePacer->getActualLogicTimeScaleFps(); const Real actualLogicTimeScaleRatio = TheFramePacer->getActualLogicTimeScaleRatio();