Simulation vs. Presentation
When creating a non-networked game, simulating a frame and preparing the scene to render the next frame are typically the same process. Objects are generally moved and updated from the previous frame and then the next frame is presented. When developing with SnapNet, however, there are important differences between these two phases.
In the context of SnapNet, the simulation refers to any logic or code that advances the networked state i.e., gameplay code. In a non-networked game, when your code to advance a frame is executed, you know that code was last executed to evaluate the previous frame and that it will only run once to evaluate the next frame before being presented to the player. When using SnapNet, this is not necessarily the case. Due to the need to reconcile new data from the network with events that have already been presented, the simulation may rewind to an earlier state and/or advance the simulation multiple frames from one rendered frame to the next.
Restoring to a previous state and advancing multiple frames in a single render frame can be problematic for many modern systems due to both functionality and performance constraints. For example, consider the case of a player character with a cape driven by cloth physics. It can be very tricky if not infeasible to accurately save and restore the state of your cloth physics and performance intensive to run it multiple times per frame. However, it is also uncommon for the physics on the cape to have a meaningful impact on gameplay. Ideally, the cape would simulate just once per render frame after the gameplay is done simulating. This is where the presentation phase comes in.
Because your simulation code can run many times per frame rendered, it generally needs to be more lightweight and performant than the equivalent non-networked code to reach the same performance target. However, any logic that is cosmetic only and does not affect the simulation can still be run only once per frame, just like it would in a non-networked game. Once the SnapNet client has advanced the simulation it sets up for rendering and any subsequent code is then guaranteed to run exactly once per rendered frame. Understanding this simulation/presentation split is critical to reaching your bandwidth and performance targets.
The role of presentation code is to set up the scene based on the current state of the simulation. This is where meshes are spawned or attached, shader parameters are set, visual FX and sounds are played, etc.
To handle any aliasing between the simulation rate and the presentation rate e.g., simulating at 60 Hz but rendering at 144 Hz, SnapNet leverages the same functionality it uses to interpolate between network updates to automatically interpolate between simulation frames for rendering. The end result is a predictable fixed tick rate simulation that can render smoothly at any frame rate.
A Simple Example
Let’s take a look at what your main game loop might look like when driving and rendering a single player character.
Traditional offline flow
The following diagram shows what your main game loop might look like for a traditional offline game. Although it all happens in sequence, you can see that the parts of the loop that handle advancing the simulation vs. the parts that handle presentation are distinct, even if that’s not a distinction that generally matters when developing an offline title.
Online flow with SnapNet
The following diagram shows how this main loop would get restructured when developing this same game with SnapNet. Note that, because the latest state that you received from the server may or may not be the latest frame you wish to present to the player, the simulation code could run 0 or many times per rendered frame. This is why it is so important to keep that code isolated and as performant as possible.