Prediction
Intro
The Source prediction system is designed to minimize the impact of latency on a gamer's experience. Rather than experiencing a delay when the player presses a button (so the command can go to the server and back), the Source prediction system simulates the effect of the button press on the client immediately. When the response finally does come from the server, the client's speculative simulation is either determined correct or incorrect.
- 99% of the time, the client's speculation is correct. In that case, the client can go along happily as though he/she has no latency to the server.
- If the client's speculation is incorrect, then the client goes back and resimulates all of the commands it ran with mistaken data. In this case, the client will experience a small hitch in his/her view position, weapon animation, and such. Luckily, if the code is written correctly, then this case is fairly uncommon.
For more details you can read this document.
Details
The main things that need to be handled to have an entity use the prediction system are:
- Certain parts of the entity's code need to execute on both the client and the server. This code is referred to as
shared code
. - Any entity variables that are modified on both the client and server must be networked via data tables.
- These variables must also be added to a list called the
prediction table
.
Networking via data tables
A good place to start is to look at the code for a weapon. If you have the source code installed, you can look at the src\game_shared\basecombatweapon_shared.cpp
file. In here, you will see some #ifdef CLIENT_DLL and #ifdef GAME_DLL blocks. These are to separate out which code compiles in the client DLL and which code compiles into the server DLL. The (abridged) code below is performing step 2 of the process described above (putting the variables modified by shared code into a data table).
BEGIN_NETWORK_TABLE(CBaseCombatWeapon, DT_BaseCombatWeapon) #if !defined( CLIENT_DLL ) SendPropInt( SENDINFO(m_iState ), 8, SPROP_UNSIGNED ), SendPropEHandle( SENDINFO(m_hOwner) ), #else RecvPropInt( RECVINFO(m_iState )), RecvPropEHandle( RECVINFO(m_hOwner ) ), #endif END_NETWORK_TABLE()
Creating the prediction table
The prediction table describes the data in your entity that should be the same between the client and the server when the client speculatively simulates a command. It is a list of the variables that are networked and their types. If a value is allowed to vary slightly from the server's value (as in the case of a floating point number that you've sent with a reduced number of bits), then you can specify how much the value is allowed to vary. In the code below, this is done with the DEFINE_PRED_FIELD_TOL
macro.
Note: the prediction table only needs to exist in the client DLL, so you can wrap the whole thing with an #ifdef CLIENT_DLL.
#ifdef CLIENT_DLL BEGIN_PREDICTION_DATA( CBaseCombatWeapon ) DEFINE_PRED_FIELD( m_nNextThinkTick, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_FIELD( m_hOwner, FIELD_EHANDLE, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_FIELD_TOL( m_flNextPrimaryAttack, FIELD_FLOAT, FTYPEDESC_INSENDTABLE, TD_MSECTOLERANCE ), END_PREDICTION_DATA() #endif
Console Commands
If your add functionality to weapons and forget to follow the steps above, then an entity may jerk around or animate strangely. There are some console commands you can use to debug this.
cl_predictionlist
If you type cl_predictionlist 1
into the console, you will see a vgui window popup in the upper right-hand corner. This shows all the entities that are currently using the Source prediction system (ie: running on both the client and server). The left-hand column shows the index of each entity, and the middle column shows the class names of the entities.
Once you know the index of the entity you need to debug, you can use cl_pdump.
cl_pdump
If you set the value of cl_pdump to a predicted entity's index (from the left-hand column of the cl_predictionlist window), then it will overlay a big list of all of the entity's predicted variables. Then you can run around and you will find out which variables the client is mispredicting. The variables change red when the client has mispredicted a command.
Troubleshooting
Let's say you see a variable that turns red in the cl_pdump
panel from time to time. Now you know that somehow the client is producing a different value for this variable than the server is. This can usually be traced down to one of the following problems:
- The client is not running some code that the server is running. This would be the case if you had an
#ifdef GAME_DLL
or an#ifndef CLIENT_DLL
around a piece of code that could affect the value of the variable that's turning red. - Another variable that can wind up affecting the value of the red variable is not being transmitted via the data table. In this case, the client's value for that variable would always be wrong (since the server never transmitted it).
- It's also possible that you just forgot to add in the appropriate tolerance using the
DEFINE_PRED_FIELD_TOL
macro. For example, if you're transmitting a floating point variable with a value range of 0.0 - 255.0, and you only give it 4 bits of precision, then you're going to need a tolerance of about 17.0, or else the prediction system will think the variable's value is wrong when it is actually just different because the variable was compressed into 4 bits before sending it to the client.
Tracking down prediction problems is usually a matter of walking through any code that can affect the values that are turning red, and noticing which other variables affect their values. It can be tedious at first, but once you get the hang of it, you can track down prediction problems quickly.