Decoupling Update Rates From Render Rates For Flexible Game Loops

Decoupling Game Logic from Rendering

Game engines traditionally tie their game logic update rate directly to the frame rate. This means that the game simulation and state is updated in lockstep with every frame rendered. While simple in concept, this tight coupling causes problems when frame rates fluctuate or vary across different hardware.

The problem with tying game logic to frame rate

Tying the game logic update rate directly to the rendering frame rate couples two very different subsystems that have no direct dependency. Rendering is responsible for drawing graphics each frame, while game logic handles discrete updates to the simulation state. When they are coupled together, the game simulation outcome and consistency is beholden to the graphical performance and capabilities of the user’s hardware.

For example, on hardware that can only achieve 30 FPS, the game logic will likewise only update 30 times per second. On faster hardware reaching 60 FPS, the logic would update twice as frequently. This variance breaks determinism and makes gameplay dependent on framerate rather than only dependent on the passage of real time.

This framerate dependency also causes problems when dealing with frame rate drops or spikes. A temporary slowdown that reduces FPS would cause the game logic to lag and either jump ahead to catch up, or pause completely waiting for the next frame. These hitches disrupt the continuity of the simulation and timeflow. When FPS spikes, extra unnecessary intermediate states get pointlessly simulated between two time points.

What is game logic?

Game logic refers to the code that handles discrete updates to the state of the gameplay simulation. This includes tasks like:

  • Updating positions of game objects based on velocities
  • Detecting collisions between game objects
  • Running AI calculations to decide NPC behaviors
  • Advancing the game from one turn to the next
  • Checking win conditions and scoring

The key attributes of game logic are:

  • Discrete – Made up of individual atomic ticks and steps
  • Deterministic – Always produces same output for a given input
  • Temporal – Simulates the passage of time

What is rendering?

Rendering refers to the graphics engine subsystem that draws each frame of animation to the screen. This consists of tasks like:

  • Spatial mapping from 3D coordinates to 2D screen positions
  • Transforming vertex positions and orientations
  • Simulating physics behaviors for visual effects
  • Making draw calls to output geometry data
  • Post-processing visual effects like lighting, shadows, etc.

The key attributes of rendering are:

  • Continuous – Frames are displayed smoothly over time
  • Variable rate – Framerate depends on hardware and scene complexity
  • Visual – Outputs images to be displayed

Why decouple them?

Decoupling the game logic update rate from the render rate provides greater flexibility, consistency, and performance:

  • Game logic can run at a regulated, fixed timestep for consistency rather than varying with frame rate
  • Simulation outcome will be the same across different hardware
  • Temporary FPS drops won’t affect game state continuity
  • Gameplay would still work without real-time graphics or rendering
  • Logic and rendering can be scaled independently across cores/threads

Using a Fixed Timestep

The fixed timestep approach

The fixed timestep approach to decoupled game loop architecture sets a constant timestep value for updating the game state that is independent of frame rate. The state update runs in a loop continuously accumulating fixed timesteps. Rendering still occurs every frame but interpolates between the previous and current fixed state steps.

Example code


// Fixed timestep game loop
while (running) {
  double currentTime = getSystemTime();

  // Game logic update
  accumulateTime += dt;  
  while(accumulateTime > fixedTimestep) {
    previousState = currentState;
    currentState = simulate(previousState, fixedTimestep); 
    accumulateTime -= fixedTimestep;  
  }

  // Rendering - interpolate between frames
  interpolateBetweenStates(previousState, currentState, accumulateTime); 
  drawFrame();
}  

Key aspects:

  • fixedTimestep: The constant timestep for game logic updates (e.g. 0.02 sec)
  • accumulateTime: Tracks fractional leftover time between fixed steps
  • previousState/currentState: Used for interpolating rendering
  • simulate(): Handles advancing game state by fixed timestep
  • interpolateBetweenStates(): Blends previous and current state for smooth rendering

Benefits

Using a fixed timestep for game logic has the following advantages:

  • Game simulation is unaffected by frame rate changes or drops
  • Gameplay remains consistent across different hardware
  • Temporal continuity is maintained without jittering or pausing
  • Excess FPS doesn’t use unnecessary CPU on extra logic updates

Variable Game Logic with Fixed Rendering

The variable timestep approach

An alternative decoupled approach is to use a fixed rendering rate with a variable game logic update rate. With this method, screen drawing happens each frame as consistently as possible. Meanwhile, the state simulation updates occur less frequently, only when a certain logic timestep threshold is reached.

Example code

  
// Variable timestep game loop
while (running) {
  double currentTime = getSystemTime();
  drawFrame(); // Render every frame 
  
  // Only update at 10fps  
  if (currentTime - lastUpdate > 0.1) {  
     lastUpdate = currentTime;
     simulate(currentState, currentTime - lastUpdate); 
  }
}

Key aspects:

  • drawFrame() runs every iteration to render as fast as possible
  • simulate() only occurs if 0.1 sec of time has passed since last state update
  • Passes actual elapsed time into simulate() rather than fixed timesteps

Benefits

The variable timestep approach has pros and cons too:

  • Ensures smooth graphics rendering pipeline without gaps
  • Logic updates less frequently so uses less overall CPU
  • Can extend battery life on mobile devices
  • State updates may not be fully deterministic or consistent

Conclusion

Decoupling game logic updates from rendering frame rates is important for building robust game architecture. It eliminates unwanted interdependency between two subsytems with very different profiles.

Using fixed timesteps for game simulation provides highly consistent, deterministic gameplay immune to framerate changes. Variable logic rates paired with fixed rendering consumes less energy while still rendering smooth graphics.

Choosing the right decoupling approach comes down to the needs and priority tradeoffs in each game. But some level of decoupling provides both flexibility for the engine and solid assurance for the player.

Leave a Reply

Your email address will not be published. Required fields are marked *