From c1284649d20a388fead0fc8e3109428104e8cf11 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Sat, 27 Jun 2026 16:26:14 -0400 Subject: [PATCH 1/2] tweak: decouple GUI window-move animations from the render frame rate --- .../Include/GameClient/AnimateWindowManager.h | 4 ++ .../GameClient/GUI/AnimateWindowManager.cpp | 38 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/AnimateWindowManager.h b/GeneralsMD/Code/GameEngine/Include/GameClient/AnimateWindowManager.h index f232411213e..c26a0909fd8 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/AnimateWindowManager.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/AnimateWindowManager.h @@ -192,6 +192,10 @@ class AnimateWindowManager : public SubsystemInterface ProcessAnimateWindowSlideFromTopFast *m_slideFromTopFast; ///< holds the process in which the windows slide from the top,fast ProcessAnimateWindow *getProcessAnimate( AnimTypes animType); ///< returns the process for the kind of animation we need. + void updateStep(); ///< Runs a single base-rate step of all registered window animations + UnsignedInt m_lastUpdateTime; ///< Wall-clock time of the previous update, for frame-rate independent pacing + Real m_updateAccumulator; ///< Carries fractional base-rate steps between updates + }; //----------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/AnimateWindowManager.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/AnimateWindowManager.cpp index 151464bdc04..07b3abf8f42 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/AnimateWindowManager.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/AnimateWindowManager.cpp @@ -133,6 +133,8 @@ AnimateWindowManager::AnimateWindowManager() m_winList.clear(); m_needsUpdate = FALSE; m_reverse = FALSE; + m_lastUpdateTime = 0; + m_updateAccumulator = 0.0f; m_winMustFinishList.clear(); } AnimateWindowManager::~AnimateWindowManager() @@ -159,6 +161,8 @@ void AnimateWindowManager::init() clearWinList(m_winMustFinishList); m_needsUpdate = FALSE; m_reverse = FALSE; + m_lastUpdateTime = 0; + m_updateAccumulator = 0.0f; } void AnimateWindowManager::reset() @@ -168,9 +172,43 @@ void AnimateWindowManager::reset() clearWinList(m_winMustFinishList); m_needsUpdate = FALSE; m_reverse = FALSE; + m_lastUpdateTime = 0; + m_updateAccumulator = 0.0f; } +// TheSuperHackers @tweak bobtista 27/06/2026 Decouple GUI window-move animations from the +// render frame rate. The animation logic runs at the historic base rate; here we run the +// number of base-rate steps that match the elapsed wall-clock time, carrying the fractional +// remainder between updates. This is independent of how often update() is called, so callers +// pumped per render frame and callers throttled to a fixed rate both animate at the same speed. void AnimateWindowManager::update() +{ + const UnsignedInt now = timeGetTime(); + if (m_lastUpdateTime == 0) + { + m_lastUpdateTime = now; + } + const UnsignedInt elapsed = now - m_lastUpdateTime; + m_lastUpdateTime = now; + + m_updateAccumulator += (Real)elapsed * ((Real)BaseFps / 1000.0f); + Int steps = (Int)m_updateAccumulator; + m_updateAccumulator -= (Real)steps; + + // Cap the catch-up burst so a long stall (load, alt-tab) cannot snap an animation instantly. + const Int maxSteps = 6; + if (steps > maxSteps) + { + steps = maxSteps; + } + + while (steps-- > 0) + { + updateStep(); + } +} + +void AnimateWindowManager::updateStep() { ProcessAnimateWindow *processAnim = nullptr; From 76414cba5ddab3411954100b4e83b43e6e59b9c1 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Sat, 27 Jun 2026 16:27:19 -0400 Subject: [PATCH 2/2] tweak: decouple GUI window-move animations from the render frame rate (Generals) --- .../Include/GameClient/AnimateWindowManager.h | 4 ++ .../GameClient/GUI/AnimateWindowManager.cpp | 38 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/Generals/Code/GameEngine/Include/GameClient/AnimateWindowManager.h b/Generals/Code/GameEngine/Include/GameClient/AnimateWindowManager.h index aad9e80b7a2..2ad5c53812c 100644 --- a/Generals/Code/GameEngine/Include/GameClient/AnimateWindowManager.h +++ b/Generals/Code/GameEngine/Include/GameClient/AnimateWindowManager.h @@ -192,6 +192,10 @@ class AnimateWindowManager : public SubsystemInterface ProcessAnimateWindowSlideFromTopFast *m_slideFromTopFast; ///< holds the process in wich the windows slide from the top,fast ProcessAnimateWindow *getProcessAnimate( AnimTypes animType); ///< returns the process for the kind of animation we need. + void updateStep(); ///< Runs a single base-rate step of all registered window animations + UnsignedInt m_lastUpdateTime; ///< Wall-clock time of the previous update, for frame-rate independent pacing + Real m_updateAccumulator; ///< Carries fractional base-rate steps between updates + }; //----------------------------------------------------------------------------- diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/AnimateWindowManager.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/AnimateWindowManager.cpp index c2b73a16b11..1f32a7b91e2 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/AnimateWindowManager.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/AnimateWindowManager.cpp @@ -133,6 +133,8 @@ AnimateWindowManager::AnimateWindowManager() m_winList.clear(); m_needsUpdate = FALSE; m_reverse = FALSE; + m_lastUpdateTime = 0; + m_updateAccumulator = 0.0f; m_winMustFinishList.clear(); } AnimateWindowManager::~AnimateWindowManager() @@ -159,6 +161,8 @@ void AnimateWindowManager::init() clearWinList(m_winMustFinishList); m_needsUpdate = FALSE; m_reverse = FALSE; + m_lastUpdateTime = 0; + m_updateAccumulator = 0.0f; } void AnimateWindowManager::reset() @@ -168,9 +172,43 @@ void AnimateWindowManager::reset() clearWinList(m_winMustFinishList); m_needsUpdate = FALSE; m_reverse = FALSE; + m_lastUpdateTime = 0; + m_updateAccumulator = 0.0f; } +// TheSuperHackers @tweak bobtista 27/06/2026 Decouple GUI window-move animations from the +// render frame rate. The animation logic runs at the historic base rate; here we run the +// number of base-rate steps that match the elapsed wall-clock time, carrying the fractional +// remainder between updates. This is independent of how often update() is called, so callers +// pumped per render frame and callers throttled to a fixed rate both animate at the same speed. void AnimateWindowManager::update() +{ + const UnsignedInt now = timeGetTime(); + if (m_lastUpdateTime == 0) + { + m_lastUpdateTime = now; + } + const UnsignedInt elapsed = now - m_lastUpdateTime; + m_lastUpdateTime = now; + + m_updateAccumulator += (Real)elapsed * ((Real)BaseFps / 1000.0f); + Int steps = (Int)m_updateAccumulator; + m_updateAccumulator -= (Real)steps; + + // Cap the catch-up burst so a long stall (load, alt-tab) cannot snap an animation instantly. + const Int maxSteps = 6; + if (steps > maxSteps) + { + steps = maxSteps; + } + + while (steps-- > 0) + { + updateStep(); + } +} + +void AnimateWindowManager::updateStep() { ProcessAnimateWindow *processAnim = nullptr;