Prediction: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
m (Nesciuse moved page Prediction/en to Prediction without leaving a redirect: Move en subpage to basepage)
 
(33 intermediate revisions by 12 users not shown)
Line 1: Line 1:
[[Category:Programming]]
{{LanguageBar}}
=Intro=
 
 
{{toc-right}}
 
'''Prediction''' is the notion of the client predicting the effects of the local player's actions without waiting for the server to confirm them. An entity's predicted state is tested against server commands as they arrive until either a match or a mis-match is detected.
 
In the vast majority of cases the client's prediction is confirmed by the server and it continues happily as if there was no latency. If there is a mis-match, which is rare if the prediction code is written correctly, then the client goes back and re-simulates all of the commands it ran with bad data. Depending on the severity of the error this can cause a 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]]. It only exists on the client.


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.
{{warning|Don't under any circumstances be tempted into thinking "these two values won't ever go out of sync". There is ''always'' the possibility of packet loss!}}


* 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.  
{{note|Because only [[CBasePlayer|players]] are lag compensated, attempts to hit other entities (not least NPCs) do not benefit from prediction - you still have to lead your aim by latency. If this is an issue, consider [[NPC Lag Compensation|lag compensating your NPCs]]. (In [[Left 4 Dead 2]], [[prop_physics]] can be selectively lag compensated.)}}
* 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.


<strike>For more details you can read [http://www.gdconf.com/archives/2001/bernier.doc this document].</strike>
== Implementation ==


=Details=
To predict an entity:


The main things that need to be handled to have an entity use the prediction system are:
# It should be '''manipulable by the local player'''. Otherwise, what is the point in predicting it?
# 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>.
# 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]].
# Any entity variables that are modified on both the client and server must be networked via [[Networking Entities|data tables]].
# The entity must make the call '''<code>SetPredictionEligible(true)</code>''', preferably in its constructor.
# These variables must also be added to a list called the <code>prediction table</code>.
# It must have a '''<code>bool ShouldPredict()</code>''' function on the client. Check if the local player is holding this entity and so forth here. {{note|Weapons must also implement <code>bool IsPredicted()</code>, again on the client. It should return <code>true</code> all the time.}}
# 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.


===Networking via data tables===
=== Prediction 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 <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]]).
All client-side variables modified by predicted player input must be added to a prediction table. There are three behaviours you can choose between:


<pre>
; <code>FTYPEDESC_INSENDTABLE</code>
BEGIN_NETWORK_TABLE(CBaseCombatWeapon, DT_BaseCombatWeapon)
: This is a networked variable. The client's predicted value will be tested against received server values, and if the two go out of synch a prediction error will be created.
#if !defined( CLIENT_DLL )
; <code>FTYPEDESC_NOERRORCHECK</code>
SendPropInt( SENDINFO(m_iState ), 8, SPROP_UNSIGNED ),
: This is a predicted value that can go out of synch without generating a prediction error. It may or may not be networked.
SendPropEHandle( SENDINFO(m_hOwner) ),
; <code>FTYPEDESC_PRIVATE</code>
#else
: This isn't predicted or networked at all, but is still registered so that it can inspected with <code>cl_pdump</code>. {{note|This type of variable won't be saved/restored when the prediction system winds time back to test against a new server command. If a predicted function increments it, the increment will be applied over and over again on the client as each new command is tested.}}
RecvPropInt( RECVINFO(m_iState )),
RecvPropEHandle( RECVINFO(m_hOwner ) ),
#endif
END_NETWORK_TABLE()
</pre>


These behaviours are implemented with the '''<code>DEFINE_PRED_FIELD()</code>''' and '''<code>DEFINE_PRED_FIELD_TOL()</code>''' macros. The latter allows you to specify a tolerance within which an int or float is allowed to differ without generating prediction errors. This is mainly intended for use when a value has been rounded prior to transmission: floats are often trimmed to 1ms resolution, for instance (the #define <code>TD_MSECTOLERANCE</code> exists especially for this situation - otherwise, you should pass a numeric value).


===Creating the prediction table===
Be cautious when extrapolating from a rounded figure in these circumstances. Small differences in the transmitted value can lead to large changes in the results of your calculations!


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.
{{todo|<code>DEFINE_PRED_TYPEDESCRIPTION</code>}}


Note: the prediction table only needs to exist in the client DLL, so you can wrap the whole thing with an '''#ifdef CLIENT_DLL'''.
==== Example ====


<pre>
<source lang=cpp>#ifdef CLIENT_DLL
#ifdef CLIENT_DLL
BEGIN_PREDICTION_DATA( CBaseCombatWeapon )
BEGIN_PREDICTION_DATA( CBaseCombatWeapon )
DEFINE_PRED_FIELD( m_nNextThinkTick, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_nNextThinkTick, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
Line 46: Line 49:
DEFINE_PRED_FIELD_TOL( m_flNextPrimaryAttack, FIELD_FLOAT, FTYPEDESC_INSENDTABLE, TD_MSECTOLERANCE ),
DEFINE_PRED_FIELD_TOL( m_flNextPrimaryAttack, FIELD_FLOAT, FTYPEDESC_INSENDTABLE, TD_MSECTOLERANCE ),
END_PREDICTION_DATA()
END_PREDICTION_DATA()
#endif
#endif</source>
</pre>
 
=== Predicting entity creation ===
 
The standard prediction system can maintain an existing entity's state, but it does not help with creating new entities. <code>[http://www.mail-archive.com/hlcoders@list.valvesoftware.com/msg09429.html CreatePredictedEntityByName()]</code> does. It creates a new entity on the client and swaps it for the real one when it arrives from the server. Needless to say, you should call it from [[shared code]].
 
<source lang=cpp>
CBaseEntity::CreatePredictedEntityByName( char* classname, char* class_file, int line, bool persist = false )
</source>
 
Yes, you really do enter in the name of the file in which the class is declared, called <code>module</code> in the actual code, and its line number ({{confirm|which means that the entity must have a shared class declaration}}). They appear to be used to generate a fingerprint for the entity. <code>persist</code> causes the entity to be tested during reprediction.


If the predicted entity contains data that was not sent to the server, you should copy it into the real entity in <code>C_BaseEntity::OnPredictedEntityRemove()</code>.


==Console Commands==
== Tools and tricks ==


If you 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.
=== IsFirstTimePredicted() ===


Testing <code>prediction->IsFirstTimePredicted()</code> ensures that code is only executed when the client first predicts an action, and not when it checks against subsequent server updates. You must <code>#include "prediction.h"</code> to access it.


===cl_predictionlist===
=== SharedRandom() ===


If you type <code>cl_predictionlist 1</code> 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.
Use this function for random numbers. The seed it uses is based on the usercmd number, making the result identical on client and server.


Once you know the index of the entity you need to debug, you can use cl_pdump.
=== CDisablePredictionFiltering ===


{{todo}}


===cl_pdump===
=== Suppressing network data ===


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.
Non-critical events like weapon effects can be done entirely on the client. Suppressing that data is a good idea as it results in reduced bandwidth usage.


=Troubleshooting=
<code>[[IPredictionSystem]]::SuppressHostEvents()</code> is meant for this. When it is called, all network data to the given player is halted. Sending NULL again will turn transmission back on. For example:


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:
<source lang=cpp>if ( pPlayer->IsPredictingWeapons() )
IPredictionSystem::SuppressHostEvents( pPlayer );
 
pWeapon->CreateEffects();
 
IPredictionSystem::SuppressHostEvents( NULL );</source>
 
Note that after doing the above you will need to write code somewhere else that makes the suppressed player's client generate the effects itself.
 
== Sample ==
 
[[Prediction/Sample weapon|Here is a simple weapon that prints prediction information to the console.]] Use its rapid secondary fire with net_fakelag 200 (or above) to see how the engine handles multiple predicted shots.
 
{{note|The gun will appear jittery when lagged unless your player entity is creating a predicted viewmodel.}}
 
== 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 <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.
Line 75: Line 109:


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.
== See also ==
* [[Source Multiplayer Networking#Input prediction]]
* [[Lag compensation]]
* [[Interpolation]]
[[Category:Networking]]
[[Category:Programming]]

Latest revision as of 09:02, 12 July 2024

English (en)Русский (ru)中文 (zh)Translate (Translate)


Prediction is the notion of the client predicting the effects of the local player's actions without waiting for the server to confirm them. An entity's predicted state is tested against server commands as they arrive until either a match or a mis-match is detected.

In the vast majority of cases the client's prediction is confirmed by the server and it continues happily as if there was no latency. If there is a mis-match, which is rare if the prediction code is written correctly, then the client goes back and re-simulates all of the commands it ran with bad data. Depending on the severity of the error this can cause a 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. It only exists on the client.

Warning.pngWarning:Don't under any circumstances be tempted into thinking "these two values won't ever go out of sync". There is always the possibility of packet loss!
Note.pngNote:Because only players are lag compensated, attempts to hit other entities (not least NPCs) do not benefit from prediction - you still have to lead your aim by latency. If this is an issue, consider lag compensating your NPCs. (In Left 4 Dead 2, prop_physics can be selectively lag compensated.)

Implementation

To predict an entity:

  1. It 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. It must have a bool ShouldPredict() function on the client. Check if the local player is holding this entity and so forth here.
    Note.pngNote:Weapons must also implement bool IsPredicted(), again on the client. It should return true all the time.
  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

All client-side variables modified by predicted player input must be added to a prediction table. There are three behaviours you can choose between:

FTYPEDESC_INSENDTABLE
This is a networked variable. The client's predicted value will be tested against received server values, and if the two go out of synch a prediction error will be created.
FTYPEDESC_NOERRORCHECK
This is a predicted value that can go out of synch without generating a prediction error. It may or may not be networked.
FTYPEDESC_PRIVATE
This isn't predicted or networked at all, but is still registered so that it can inspected with cl_pdump.
Note.pngNote:This type of variable won't be saved/restored when the prediction system winds time back to test against a new server command. If a predicted function increments it, the increment will be applied over and over again on the client as each new command is tested.

These behaviours are implemented with the DEFINE_PRED_FIELD() and DEFINE_PRED_FIELD_TOL() macros. The latter allows you to specify a tolerance within which an int or float is allowed to differ without generating prediction errors. This is mainly intended for use when a value has been rounded prior to transmission: floats are often trimmed to 1ms resolution, for instance (the #define TD_MSECTOLERANCE exists especially for this situation - otherwise, you should pass a numeric value).

Be cautious when extrapolating from a rounded figure in these circumstances. Small differences in the transmitted value can lead to large changes in the results of your calculations!

Todo: DEFINE_PRED_TYPEDESCRIPTION

Example

#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

Predicting entity creation

The standard prediction system can maintain an existing entity's state, but it does not help with creating new entities. CreatePredictedEntityByName() does. It creates a new entity on the client and swaps it for the real one when it arrives from the server. Needless to say, you should call it from shared code.

CBaseEntity::CreatePredictedEntityByName( char* classname, char* class_file, int line, bool persist = false )

Yes, you really do enter in the name of the file in which the class is declared, called module in the actual code, and its line number (

Confirm:which means that the entity must have a shared class declaration

). They appear to be used to generate a fingerprint for the entity. persist causes the entity to be tested during reprediction.

If the predicted entity contains data that was not sent to the server, you should copy it into the real entity in C_BaseEntity::OnPredictedEntityRemove().

Tools and tricks

IsFirstTimePredicted()

Testing prediction->IsFirstTimePredicted() ensures that code is only executed when the client first predicts an action, and not when it checks against subsequent server updates. You must #include "prediction.h" to access it.

SharedRandom()

Use this function for random numbers. The seed it uses is based on the usercmd number, making the result identical on client and server.

CDisablePredictionFiltering

[Todo]

Suppressing network data

Non-critical events like weapon effects can be done entirely on the client. Suppressing that data is a good idea as it results in reduced bandwidth usage.

IPredictionSystem::SuppressHostEvents() is meant for this. When it is called, all network data to the given player is halted. Sending NULL again will turn transmission back on. For example:

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

pWeapon->CreateEffects();

IPredictionSystem::SuppressHostEvents( NULL );

Note that after doing the above you will need to write code somewhere else that makes the suppressed player's client generate the effects itself.

Sample

Here is a simple weapon that prints prediction information to the console. Use its rapid secondary fire with net_fakelag 200 (or above) to see how the engine handles multiple predicted shots.

Note.pngNote:The gun will appear jittery when lagged unless your player entity is creating a predicted viewmodel.

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