Skip to content

tweak(fps): Decouple logic time step from render update #1451

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

xezon
Copy link

@xezon xezon commented Aug 12, 2025

Merge with Rebase

This change has several commits to fix and improve several aspects in regards to the render frame time in relation to the logic time step.

The frame limiter has been reimplemented with a high accuracy counter for more accurate FPS capping.

The following systems are now decoupled from the render update:

  • non-network logic update
  • WW3D::Sync
  • Scripted camera movement time step
  • Drawable time step
  • Camera shaker time step
  • Camera zoom and rotation time step

There are way more things that could be decoupled but for starters this appears to be good enough and usable.

To control the Logic Time Scale FPS and Render FPS the following default key mapping are added:

Key mapping Action
CTRL + Numpad+ Increase Max Render FPS with preset values
CTRL + Numpad- Decrease Max Render FPS with preset values
Shift + CTRL + Numpad+ Increase Logic Time Scale FPS by 5
Shift + CTRL + Numpad- Decrease Logic Time Scale FPS by 5

Changes to the FPS are currently NOT saved to Options.ini and the game still applies its frame rate adjustements in Skirmish, Campaign and similar as usual. By default, the game behaves as originally and this feature is currently entirely opt-in with the key mappings.

TODO

  • Replicate in Generals

@xezon xezon added this to the Important features milestone Aug 12, 2025
@xezon xezon added Bug Something is not working right, typically is user facing Enhancement Is new feature or request Major Severity: Minor < Major < Critical < Blocker Gen Relates to Generals ZH Relates to Zero Hour Rendering Is Rendering related labels Aug 12, 2025
@xezon xezon force-pushed the xezon/decouple-logic-render branch from 2b88c43 to 1036d47 Compare August 12, 2025 21:59
MSG_META_INCREASE_MAX_RENDER_FPS, ///< TheSuperHackers @feature Increase the max render fps
MSG_META_DECREASE_MAX_RENDER_FPS, ///< TheSuperHackers @feature Decrease the max render fps
MSG_META_INCREASE_MAX_LOGIC_FPS, ///< TheSuperHackers @feature Increase the max logic fps
MSG_META_DECREASE_MAX_LOGIC_FPS, ///< TheSuperHackers @feature Decrease the max logic fps
Copy link
Author

Choose a reason for hiding this comment

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

This needs renaming

Copy link
Author

Choose a reason for hiding this comment

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

Fixed

else
{
// TheSuperHackers @tweak xezon 06/08/2025 Puts the single player logic update behind a
// logic frame cap to decouple the render frame rate from the game simulation frame rate.
Copy link
Author

Choose a reason for hiding this comment

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

decouple render update from logic time step

Copy link
Author

Choose a reason for hiding this comment

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

Fixed

{
//Keyboard zoom out
TheTacticalView->zoomOut();
// TheSuperHackers @tweak Decouples camera rotation and zoom from draw (aka render update)
Copy link
Author

Choose a reason for hiding this comment

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

Simplify to just "from render update". Multiple times.

Copy link
Author

Choose a reason for hiding this comment

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

Fixed

}

// TheSuperHackers @tweak xezon 12/08/2025 The WW3D Sync is no longer tied
// to the draw, but is advanced separately for every fixed time step.
Copy link
Author

Choose a reason for hiding this comment

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

render update

Copy link
Author

Choose a reason for hiding this comment

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

Fixed

//Keyboard zoom out
TheTacticalView->zoomOut();
// TheSuperHackers @tweak Decouples camera rotation and zoom from draw (aka render update)
const Real logicTimeScaleOverFpsRatio = TheGameEngine->getActualLogicTimeScaleOverFpsRatio();
Copy link
Author

Choose a reason for hiding this comment

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

I think the camera zoom and rotate should be decoupled from the logic time scale as well. It should just always be the same speed unless the user changes some specific option for it.

Copy link
Author

Choose a reason for hiding this comment

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

Fixed

@Mauller
Copy link

Mauller commented Aug 13, 2025

You need to update your new 'm_logicTimeScaleFPS' from the game speed option in SkirmishGameOptionsMenu.cpp the idea was to allow the game to run faster. But since the logic and FPS were tied together, they just increase the FPS cap in the original implementation.

This will also have a knock on effect that the FPS cap will also need to be increased to match the increased logic rate.

	GameWindow *sliderGameSpeed = TheWindowManager->winGetWindowFromId( parentSkirmishGameOptions, sliderGameSpeedID );
	Int maxFPS = GadgetSliderGetPosition( sliderGameSpeed );
	setInt("FPS", maxFPS);

@@ -100,9 +108,15 @@ 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 allowed
Int m_maxFPS; ///< Maximum frames per second for rendering
Int m_logicTimeScaleFPS; ///< Maximum frames per second for logic time scale
Copy link

Choose a reason for hiding this comment

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

It would be better to rename m_maxFPS to make it more relevant to the rendering FPS.

m_maxRenderFPS for example.

For the logic one, it would be better to have the naming match the rendering.

m_maxLogicFPS etc.

Copy link
Author

@xezon xezon Aug 13, 2025

Choose a reason for hiding this comment

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

I did not intent to rename m_maxFPS for this change to have a bit less diff.

Copy link
Author

Choose a reason for hiding this comment

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

m_maxLogicFPS is not the correct terminology for this. Logic FPS is what we currently refer to as enum LOGICFRAMES_PER_SECOND=30.

Copy link

Choose a reason for hiding this comment

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

m_maxLogicFPS is not the correct terminology for this. Logic FPS is what we currently refer to as enum LOGICFRAMES_PER_SECOND=30.

It's the currently set max logic frame rate, LOGICFRAMES_PER_SECOND is the default maximum value. But since we can / need to be able to vary the current max logic frame rate, it makes sense to call it that for the variable.

Copy link
Author

Choose a reason for hiding this comment

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

m_logicTimeScaleFPS is conceptually not equivalent to LOGICFRAMES_PER_SECOND or m_maxLogicFPS. It effectively is a ratio that scales the logic fps.

Copy link
Author

Choose a reason for hiding this comment

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

You ask me to rename it to m_maxLogicFPS but I am telling you this is not the right name for it. Originally I used this exact name for it, until I realized it is misleading because it would be pretty much identical to LOGICFRAMES_PER_SECOND but are not the same thing. This is why I called it Logic Time Scale.

Currently Logic Time Scale is capped by the Render Update. We could perhaps also uncap it and make it a substitute for fast forwarding globally.

Copy link

Choose a reason for hiding this comment

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

It's not misleading though, i think you are putting too much emphasis on what LOGICFRAMES_PER_SECOND means compared to what it actually is meant to be.

LOGICFRAMES_PER_SECOND is the default maximum logic frame rate during normal gameplay etc. It's basically a value used for default configuration and normal configuration.
While your m_logicTimeScaleFps is the max logic FPS that is being used within the game at runtime. Which can vary to allow fast gameplay mode in skirmish or within mod maps etc.

Both values are related but mean different things.

Logic tick rate being capped by the rendering is fine, but that's a different problem overall. The logic tick rate does not need to exceed the rendering rate and probably never should. But varying the max logic rate implements the fast forward functionality, so of course the render rate has to increase if the logic rate was to exceed it.

Copy link
Author

@xezon xezon Aug 13, 2025

Choose a reason for hiding this comment

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

Thing is, when we make LOGICFRAMES_PER_SECOND configurable, then what will be its name? Given your name proposal, it would end up being something like:

Int m_logicFPS;
Int m_maxLogicFPS;

I disagree with giving these 2 things the same name.

This will be better:

Int m_logicFPS;
Int m_logicTimeScaleFPS;

The logic tick rate does not need to exceed the rendering rate and probably never should.

It does so in the original game, during fast forwarding. I think it is fine to do that. I can explore this in a follow up change.

Copy link

Choose a reason for hiding this comment

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

Thing is, when we make LOGICFRAMES_PER_SECOND configurable, then what will be its name?

At that point it would be like with any other configurable variable. we have the config handling set Int m_maxLogicFPS; at startup etc. Or it uses the constant LOGICFRAMES_PER_SECOND as the default.

Copy link
Author

Choose a reason for hiding this comment

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

I expect it will be possible to change LOGICFRAMES_PER_SECOND at runtime to any value above 0.

Int m_logicTimeScaleFPS; ///< Maximum frames per second for logic time scale

Real m_frameTime; ///< Last render frame time
Real m_logicFrameTimeAccumulator; ///< Frame time accumulated towards submitting a new logic frame
Copy link

Choose a reason for hiding this comment

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

Would be better to distinguish this as rendering frame time in the name of the variable m_renderFrameTime etc.

Copy link
Author

Choose a reason for hiding this comment

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

Fixed

if ( ! TheGlobalData->m_TiVOFastMode )
#else //always allow this cheatkey if we're in a replaygame.
if ( ! (TheGlobalData->m_TiVOFastMode && TheGameLogic->isInReplayGame()))
if ( ! TheGlobalData->m_TiVOFastMode )
Copy link

Choose a reason for hiding this comment

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

Does fast forwarding actually work in replays?

The logic here looks like it does not affect the logic rate so would actually break replay fast forwarding.

Copy link
Author

Choose a reason for hiding this comment

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

Last time I checked it worked, but I can retest.

Copy link

Choose a reason for hiding this comment

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

This is fine in this update then if you have the logic frame rate limiting disabled by default.

But once it does get enabled it will break fast forwarding at this point.

Copy link
Author

Choose a reason for hiding this comment

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

I tested with capped and uncapped logic time scale and fast forward worked.

@xezon
Copy link
Author

xezon commented Aug 13, 2025

You need to update your new 'm_logicTimeScaleFPS' from the game speed option in SkirmishGameOptionsMenu.cpp the idea was to allow the game to run faster. But since the logic and FPS were tied together, they just increase the FPS cap in the original implementation.

No. This change does not intent to touch it yet as described in the pull request description. For now, the new limits are only accessible with the new key mappings and the game still applies its fps limits as it always did.

The reason for that is, this change is already big enough and I do not want to risk breaking anything yet. It is a more careful rollout.

@Mauller
Copy link

Mauller commented Aug 13, 2025

You need to update your new 'm_logicTimeScaleFPS' from the game speed option in SkirmishGameOptionsMenu.cpp the idea was to allow the game to run faster. But since the logic and FPS were tied together, they just increase the FPS cap in the original implementation.

No. This change does not intent to touch it yet as described in the pull request description. For now, the new limits are only accessible with the new key mappings and the game still applies its fps limits as it always did.

The reason for that is, this change is already big enough and I do not want to risk breaking anything yet. It is a more careful rollout.

So this is a breaking change then as it breaks the game speed adjustment in skirmish.

@xezon
Copy link
Author

xezon commented Aug 13, 2025

So this is a breaking change then as it breaks the game speed adjustment in skirmish.

Can you elaborate? When I tested, by default, it did not change behavior.

@Mauller
Copy link

Mauller commented Aug 13, 2025

So this is a breaking change then as it breaks the game speed adjustment in skirmish.

Can you elaborate? When I tested, by default, it did not change behavior.

The game speed adjustment is there to allow the game logic to tick faster than 30 frames, but this was tied to FPS at the same time in the original implementation. it was never about allowing a higher framerate.

If you are only increasing render FPS limit then the game logic won't be running faster for the faster game speed.

@xezon
Copy link
Author

xezon commented Aug 13, 2025

It does, because Logic Time Scale FPS is disabled by default. Unless I made a mistake or the new key mappings are pressed in game, the default game behaviour should be the exact same. I suggest to give this a test in game.


bool changeLogicTimeScale(FpsValueChange change)
{
if (TheNetwork == NULL)
Copy link

Choose a reason for hiding this comment

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

can be made simpler by inverting the logic and making this an early return.

Copy link
Author

Choose a reason for hiding this comment

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

Fixed

@Mauller
Copy link

Mauller commented Aug 13, 2025

It does, because Logic Time Scale FPS is disabled by default. Unless I made a mistake or the new key mappings are pressed in game, the default game behaviour should be the exact same. I suggest to give this a test in game.

Ah i see now, i thought you had the logic frame rate locking always enabled.

@aliendroid1
Copy link
Collaborator

Are the key mappings hard coded?

@xezon
Copy link
Author

xezon commented Aug 13, 2025

The default key mappings are. They can be overriden in CommandMap.ini

@WWB2-account
Copy link

It would be good to investigate how this change interacts with already existing ways to change game speed and FPS. There are scripts that do very similar things as this PR.

This change has the potential to break every single script in all maps in the history of the game if we're not careful. Investigation of internal script timers, frame counters and more is probably needed to verify this doesn't break anything. Changing scripts in general should be done with utmost consideration.

Additionally, I can imagine that in some cases the game logic should not be increased beyond a specific value. Examples are cinematic intros in campaign maps (eg. ZH USA 01), or maps with many audio events like Generals' Challenge maps. In such cases the logic should have a custom cap specifically tied to that map, likely specified in the map.ini.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug Something is not working right, typically is user facing Enhancement Is new feature or request Gen Relates to Generals Major Severity: Minor < Major < Critical < Blocker Rendering Is Rendering related ZH Relates to Zero Hour
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Separate game logic from framerate
4 participants