Prediction: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
m (moved Working With Prediction to Prediction over redirect)
(rewrote up to "Prediction tables")
Line 1: Line 1:
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.
{{otherlang2
| ru = Working With Prediction:ru
}}


* 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.
{{toc-right}}
* 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 [[Lag Compensation]].
'''Prediction''' is the notion of the client predicting the effects of the local player's actions without waiting for the server to confirm them, minimizing the impact of latency. When the response does arrive from the server, the client's predicted simulation is checked against the actual data.


==Details==
In the vast majority of cases the client's prediction is correct, and it continues happily as if there was no latency to the server. If it is incorrect, which is rare if the prediction code is written correctly, the client goes back and re-simulates all of the commands it ran with bad data. This will cause a very noticeable hitch in the player's position and state, and possibly the state of the world too.


The main things that need to be handled to have an entity use the prediction system are:
Prediction is closely linked to [[lag compensation]], but is separate from [[interpolation]].
# Certain parts of the entity's code need to execute on both the client and the server. This code is referred to as <code>shared code</code>.
# Any entity variables that are modified on both the client and server must be networked via [[Networking Entities|data tables]].
# These variables must also be added to a list called the [[prediction table]].


===Networking via data tables===
{{note|Because only [[CBasePlayer|players]] are lag compensated, attempts to hit other entities (not least [[func_movelinear]]) do not benefit from prediction - you still have to lead your aim.}}


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 <code>src\game_shared\basecombatweapon_shared.cpp</code> 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 [[Networking Entities|data table]]).
== Implementation ==


BEGIN_NETWORK_TABLE(CBaseCombatWeapon, DT_BaseCombatWeapon)
To predict an entity:
#ifndef 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 entity should be '''manipulable by the local player'''. Otherwise, what is the point in predicting it?
# The functions that are to be predicted must '''exist and be identical on both the client and server'''. This is achieved by making them [[shared code]].
# The entity must make the call '''<code>SetPredictionEligible(true)</code>''', preferably in its constructor.
# The entity must have two functions that return true for frames in which it should be predicted:
#* <code>bool '''ShouldPredict()'''</code>, on the client
#* <code>bool '''IsPredicted()'''</code>, on the server
# Any variables that must be synchronised between the client and server must be both [[Networking Entities|networked]] and present in the entity's 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 <code>[[DEFINE_PRED_FIELD_TOL]]</code> macro.
== Prediction tables ==


{{note|The prediction table only needs to exist in the client DLL, so you can wrap the whole thing with an <code>#ifdef CLIENT_DLL</code>.}}
{{todo|What is the ''effect'' of a variable being in a prediction table?}}


#ifdef CLIENT_DLL
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 <code>DEFINE_PRED_FIELD_TOL</code> macro.
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===
{{tip|The prediction table only needs to exist in the client DLL, so you can wrap the whole thing with an <code>#ifdef CLIENT_DLL</code>.}}


If you add functionality to weapons and forget to follow the steps above, then an entity may jerk around or animate strangely. You can use <code>[[cl_predictionlist]]</code> and <code>[[cl_pdump]]</code> to debug this.
<source lang=cpp>#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</source>


=== Suppressing networkdata ===
== Suppressing networkdata ==


Most weapons have shared code, the same code for client and server. This will allow the client to predict things. Non-critical things like weapon effects can be entirely done at the client, without the server sending the [[effectdata]] to all clients. In this case suppressing the data is a good idea, which results in less bandwidth usage.
Most weapons have shared code, the same code for client and server. This will allow the client to predict things. Non-critical things like weapon effects can be entirely done at the client, without the server sending the [[effectdata]] to all clients. In this case suppressing the data is a good idea, which results in less bandwidth usage.


The interface <code>[[IPredictionSystem]]</code> is meant for this.
The interface <code>[[IPredictionSystem]]</code> is meant for this.
When <code>IPredictionSystem::[[SuppressHostEvents()|SuppressHostEvents]]( pPlayer );</code> is called, all networkdata to that player is halted. Sending NULL again will stop the suppressing. For example:
When <code>IPredictionSystem::SuppressHostEvents(pPlayer)</code> is called, all networkdata to that player is halted. Sending NULL again will stop the suppressing. For example:


if ( pPlayer->IsPredictingWeapons() )
<source lang=cpp>if ( pPlayer->IsPredictingWeapons() )
IPredictionSystem::SuppressHostEvents( pPlayer );
IPredictionSystem::SuppressHostEvents( pPlayer );
 
pWeapon->CreateEffects();
pWeapon->CreateEffects();
 
IPredictionSystem::SuppressHostEvents( NULL );
IPredictionSystem::SuppressHostEvents( NULL );</source>


==Troubleshooting==
==Troubleshooting==


Let's say you see a variable that turns red in the <code>cl_pdump</code> 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:
If you add functionality to weapons and forget to follow the steps above, then an entity may jerk around or animate strangely. You can use <code>[[cl_predictionlist]]</code> and <code>[[cl_pdump]]</code> to debug this. Changing <code>[[cl_pred_optimize]]</code> can also help sometimes.
 
Now let's say you see a variable that turns red in the <code>cl_pdump</code> 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 <code>#ifdef GAME_DLL</code> or an <code>#ifndef CLIENT_DLL</code> around a piece of code that could affect the value of the variable that's turning red.
# The client is not running some code that the server is running. This would be the case if you had an <code>#ifdef GAME_DLL</code> or an <code>#ifndef CLIENT_DLL</code> 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).
# 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 <code>[[DEFINE_PRED_FIELD_TOL]]</code> 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.
# It's also possible that you just forgot to add in the appropriate tolerance using the <code>DEFINE_PRED_FIELD_TOL</code> 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.
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.


{{otherlang:en}}
== See also ==
{{otherlang:en:ru|Working With Prediction:ru}}
 
* [[Networking Entities]]
* [[Lag compensation]]


[[Category:Networking]]
[[Category:Networking]]

Revision as of 12:37, 12 August 2009

Template:Otherlang2

Prediction is the notion of the client predicting the effects of the local player's actions without waiting for the server to confirm them, minimizing the impact of latency. When the response does arrive from the server, the client's predicted simulation is checked against the actual data.

In the vast majority of cases the client's prediction is correct, and it continues happily as if there was no latency to the server. If it is incorrect, which is rare if the prediction code is written correctly, the client goes back and re-simulates all of the commands it ran with bad data. This will cause a very noticeable hitch in the player's position and state, and possibly the state of the world too.

Prediction is closely linked to lag compensation, but is separate from interpolation.

Note.pngNote:Because only players are lag compensated, attempts to hit other entities (not least func_movelinear) do not benefit from prediction - you still have to lead your aim.

Implementation

To predict an entity:

  1. The entity should be manipulable by the local player. Otherwise, what is the point in predicting it?
  2. The functions that are to be predicted must exist and be identical on both the client and server. This is achieved by making them shared code.
  3. The entity must make the call SetPredictionEligible(true), preferably in its constructor.
  4. The entity must have two functions that return true for frames in which it should be predicted:
    • bool ShouldPredict(), on the client
    • bool IsPredicted(), on the server
  5. Any variables that must be synchronised between the client and server must be both networked and present in the entity's prediction table.

Prediction tables

Todo: What is the effect of a variable being in a 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.

Tip.pngTip: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

Suppressing networkdata

Most weapons have shared code, the same code for client and server. This will allow the client to predict things. Non-critical things like weapon effects can be entirely done at the client, without the server sending the effectdata to all clients. In this case suppressing the data is a good idea, which results in less bandwidth usage.

The interface IPredictionSystem is meant for this. When IPredictionSystem::SuppressHostEvents(pPlayer) is called, all networkdata to that player is halted. Sending NULL again will stop the suppressing. For example:

if ( pPlayer->IsPredictingWeapons() )
	IPredictionSystem::SuppressHostEvents( pPlayer );

pWeapon->CreateEffects();

IPredictionSystem::SuppressHostEvents( NULL );

Troubleshooting

If you add functionality to weapons and forget to follow the steps above, then an entity may jerk around or animate strangely. You can use cl_predictionlist and cl_pdump to debug this. Changing cl_pred_optimize can also help sometimes.

Now 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:

  1. 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.
  2. 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).
  3. 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.

See also