Skip to content

tweak: decouple GUI window-move animations from the render frame rate#2833

Open
bobtista wants to merge 4 commits into
TheSuperHackers:mainfrom
bobtista:bobtista/tweak/decouple-window-move-animation
Open

tweak: decouple GUI window-move animations from the render frame rate#2833
bobtista wants to merge 4 commits into
TheSuperHackers:mainfrom
bobtista:bobtista/tweak/decouple-window-move-animation

Conversation

@bobtista

@bobtista bobtista commented Jun 27, 2026

Copy link
Copy Markdown

Addresses #2832, Similar to 2056, which decoupled the GameWindowTransitions.

Problem

Control bar, diplomacy/communicator panels slide animations go too fast when you increase render FPS

Approach

AnimateWindowManager::update() now paces itself with TheFramePacer->getBaseOverUpdateFpsRatio(): it accumulates base-rate (30 fps) frames and runs that many steps, carrying the fractional remainder between updates. Shell::update now pumps the manager every frame instead of through its 30 Hz gate, since the manager paces itself.

TODO:

[x] Testing
[x] Replicate to Generals

@greptile-apps

greptile-apps Bot commented Jun 27, 2026

Copy link
Copy Markdown

Greptile Summary

This PR decouples GUI window-move animations (control bar, diplomacy panels) from the render frame rate by introducing a frame-accumulator in AnimateWindowManager::update(). The old per-frame step() logic is extracted and the new update() drives it the correct number of base-rate (30 fps) steps using TheFramePacer->getBaseOverUpdateFpsRatio(), carrying fractional remainders between calls. A side effect of moving the Shell call outside the ~30 Hz gate is that both the shell path and in-game paths now follow the same per-render-frame cadence, letting the accumulator correctly pace both.

  • AnimateWindowManager – renamed old body to step(), added accumulator logic in update() and a new m_frameAccumulator member initialized in constructor/init/reset.
  • Shell.cpp (both copies) – m_animateWindowManager->update() is moved outside the ~30 Hz wall-clock gate so it runs every render frame and the built-in pacing handles timing independently.

Note: the PR description states "Wall-clock pacing is used here (rather than getBaseOverUpdateFpsRatio())" but the actual implementation uses getBaseOverUpdateFpsRatio(). The description appears to reflect an earlier design iteration; the final code is correct, and the 6-step catch-up cap the description mentions is correctly enforced by the minUpdateFps = 5.0f floor inside getBaseOverUpdateFpsRatio().

Confidence Score: 5/5

Safe to merge; the accumulator-based pacing is correctly implemented and the change is consistently replicated across both the Generals and Zero Hour codebases.

The accumulator pattern mirrors the established approach used elsewhere in the engine. The 6-step catch-up cap is correctly enforced by the minUpdateFps floor in getBaseOverUpdateFpsRatio(). The m_frameAccumulator is initialised in all three relevant lifecycle points (constructor, init, reset). Both Shell callers and ControlBar callers correctly converge on per-frame update() calls with the accumulator handling pacing for both.

No files require special attention.

Important Files Changed

Filename Overview
Generals/Code/GameEngine/Source/GameClient/GUI/AnimateWindowManager.cpp Extracts per-frame logic into step(), adds m_frameAccumulator and accumulator-driven update(); accumulator is properly reset in constructor, init(), and reset().
GeneralsMD/Code/GameEngine/Source/GameClient/GUI/AnimateWindowManager.cpp Mirror of the Generals copy; identical accumulator-based changes applied correctly.
Generals/Code/GameEngine/Include/GameClient/AnimateWindowManager.h Adds private step() declaration and m_frameAccumulator member; both correctly placed in the private section.
GeneralsMD/Code/GameEngine/Include/GameClient/AnimateWindowManager.h Mirror of Generals header; private step() and m_frameAccumulator correctly added.
Generals/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp Moves animate-window update call outside the ~30 Hz gate so it fires every render frame; the accumulator in AnimateWindowManager now handles pacing correctly for both the shell and in-game paths.
GeneralsMD/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp Mirror of Generals Shell.cpp change; update call moved outside gate consistently.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Engine as Render Loop
    participant Shell as Shell::update()
    participant CB as ControlBar::update()
    participant AWM as AnimateWindowManager::update()
    participant FP as FramePacer
    participant Step as AnimateWindowManager::step()

    Note over Shell: ~30 Hz gate (unchanged)
    Engine->>Shell: every render frame
    Shell->>Shell: "if (now - lastUpdate >= 33ms)"
    Shell->>Shell: runUpdate, schemeManager::update
    Shell->>AWM: update() [every render frame, outside gate]
    AWM->>FP: getBaseOverUpdateFpsRatio()
    FP-->>AWM: "BaseFps / updateFps (e.g. 0.5 @ 60fps)"
    AWM->>AWM: "accumulator += ratio"
    AWM->>AWM: "steps = floor(accumulator)"
    loop for each whole step
        AWM->>Step: step()
        Step->>Step: advance all registered window animations
    end

    Engine->>CB: every render frame
    CB->>AWM: update() [per-frame, always was]
    AWM->>FP: getBaseOverUpdateFpsRatio()
    FP-->>AWM: BaseFps / updateFps
    AWM->>AWM: "accumulate & step"
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Engine as Render Loop
    participant Shell as Shell::update()
    participant CB as ControlBar::update()
    participant AWM as AnimateWindowManager::update()
    participant FP as FramePacer
    participant Step as AnimateWindowManager::step()

    Note over Shell: ~30 Hz gate (unchanged)
    Engine->>Shell: every render frame
    Shell->>Shell: "if (now - lastUpdate >= 33ms)"
    Shell->>Shell: runUpdate, schemeManager::update
    Shell->>AWM: update() [every render frame, outside gate]
    AWM->>FP: getBaseOverUpdateFpsRatio()
    FP-->>AWM: "BaseFps / updateFps (e.g. 0.5 @ 60fps)"
    AWM->>AWM: "accumulator += ratio"
    AWM->>AWM: "steps = floor(accumulator)"
    loop for each whole step
        AWM->>Step: step()
        Step->>Step: advance all registered window animations
    end

    Engine->>CB: every render frame
    CB->>AWM: update() [per-frame, always was]
    AWM->>FP: getBaseOverUpdateFpsRatio()
    FP-->>AWM: BaseFps / updateFps
    AWM->>AWM: "accumulate & step"
Loading

Reviews (4): Last reviewed commit: "tweak: use frame pacer ratio for animati..." | Re-trigger Greptile

@xezon xezon left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try to use less AI code generation for better code

Comment thread GeneralsMD/Code/GameEngine/Include/GameClient/AnimateWindowManager.h Outdated
Comment thread GeneralsMD/Code/GameEngine/Include/GameClient/AnimateWindowManager.h Outdated
Comment thread GeneralsMD/Code/GameEngine/Source/GameClient/GUI/AnimateWindowManager.cpp Outdated
Comment thread GeneralsMD/Code/GameEngine/Source/GameClient/GUI/AnimateWindowManager.cpp Outdated
Comment thread GeneralsMD/Code/GameEngine/Include/GameClient/AnimateWindowManager.h Outdated
Comment thread GeneralsMD/Code/GameEngine/Source/GameClient/GUI/AnimateWindowManager.cpp Outdated
Comment thread GeneralsMD/Code/GameEngine/Source/GameClient/GUI/AnimateWindowManager.cpp Outdated
@bobtista bobtista force-pushed the bobtista/tweak/decouple-window-move-animation branch from 76414cb to e5a23b5 Compare July 6, 2026 20:33
@bobtista bobtista force-pushed the bobtista/tweak/decouple-window-move-animation branch from e5a23b5 to e829b80 Compare July 6, 2026 20:40
Real deltaSeconds = TheFramePacer->getUpdateTime();

// Clamp the delta so a long stall (load, alt-tab) cannot snap an animation to its end.
const Real maxCatchUpSeconds = 0.2f;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be simplified to

const Real deltaSeconds = std::min(TheFramePacer->getUpdateTime(), 0.2f);

deltaSeconds = maxCatchUpSeconds;
}

m_updateAccumulator += deltaSeconds * (Real)BaseFps;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this perhaps do m_updateAccumulator += TheFramePacer->getBaseOverUpdateFpsRatio() ?

clearWinList(m_winMustFinishList);
m_needsUpdate = FALSE;
m_reverse = FALSE;
m_updateAccumulator = 0.0f;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe call it m_frameAccumulator.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants