S&box Wiki

Revision Difference

Prediction#544616

<cat>Code.Network</cat> <title>Prediction</title> ⤶ [Prediction](https://developer.valvesoftware.com/wiki/Prediction) refers to one of the systems Source (and Source 2) uses to smooth out the multiplayer experience, this system 'predicts' what effects a player's input will have on the world (by running the same calculations the server does multiple times and comparing them) and displays the result without having to wait for the server to both receive the player's input and send the result back. ⤶ Without prediction the game would be effectively unplayable for anyone with more than 50ms of ping, only getting worse as it increases further. Incorrectly setting up prediction can lead to all sorts of issues in multiplayer such as:⤶ ⤶ * Sluggish/slow responses to player input⤶ * Stuttering/hitching⤶ * Duplicated effects/animations⤶ * Broken hit registration⤶ ⤶ <note>Prediction only exists on the client, the server isn't aware of it's existence nor does it take it into account for anything. If the client's predictions don't line up with the server the server's values will take precedence 100% of the time.</note>⤶ ⤶ #Where to use prediction⤶ ⤶ Prediction should be used for anything that is **directly** affected by a player's inputs, this includes:⤶ ⤶ * The player itself⤶ * Any controllers or animators attached to the player⤶ * The player's ActiveChild (Weapons and other `IPlayerControllable` entities like vehicles)⤶ ⤶ Technically every entity with a player as it's owner is predictable, but those are the main ones you should be worried about.⤶ ⤶ <warning>The physics engine is [nondeterministic](https://en.wikipedia.org/wiki/Nondeterministic_algorithm) and cannot be predicted, this extends to any entity that uses it for movement. ⤶ If you've ever wondered why the Half-Life 2 vehicles don't handle well in multiplayer, this is why.</warning>⤶ ⤶ #Predicted methods⤶ ⤶ Prediction isn't something you can start yourself, it's a context that is set by the engine for certain methods (and any code that branches off from those methods) These methods are:⤶ ⤶ <warning>Async tasks or any other 'delayed' code will break prediction, use `TimeSince` and check for completion in subsequent calls instead if you need to delay something</warning>⤶ ⤶ * `Player.Simulate( Client client )` which calls `ActiveChild.Simulate( Client client )` by default⤶ * `PlayerController.Simulate( Client client )`⤶ * `PlayerAnimator.Simulate( Client client )`⤶ ⤶ #How to implement prediction⤶ ⤶ Adding prediction to an entity is fairly straightforward if you know what you're doing. ⤶ * Keep your code the same between client and server as much as possible⤶ * Mark any networked properties that change during predicted hooks with `[Net, Predicted]`⤶ * Wrap clientside code that isn't supposed to be predicted (think notifications) in `using (Prediction.Off())` to keep them from running multiple times⤶ ⤶ If you've done all of this the end result might look something like this example based on [dm98](https://github.com/Facepunch/dm98):⤶ ⤶ ```cs⤶ public partial class ExampleWeapon : BaseWeapon⤶ ⤶ Imagine you shoot a gun in the game. Without prediction you would press the mouse button, it'd send a message to the server, the server would run the command, the gun would shoot on the server, it'd send sounds and effects down to the client, then you'd see the effects happen. We're talking about a 30ms round trip at best, half a second at worst. It would be noticeable. ⤶ With prediction it runs the same, but while it's waiting for the server it predicts what's going to happen. It runs the command clientside, so everything is instant. ⤶ ⤶ # Prediction Errors⤶ ⤶ When the server runs the Tick and sends the results to the client, the client can compare the entity and it's predicted variables to the actual results from the tick that ran on the server. ⤶ ⤶ If any of the values are different then we have a prediction error. So the client copies all the variables from the server value and re-runs any subsequent ticks, to try and fix it all.⤶ ⤶ # What should be predicted?⤶ ⤶ Anything that changes your pawn's position or rotation is predicted. Movement code should match and be deterministic on both client and server. The physics aren't deterministic so you can't use physics in a predictable fashion.⤶ In your weapons you should make things like ammo count predictable.⤶ ⤶ ⤶ # Where is Prediction?⤶ ⤶ The main entry point for prediction is in your gamemode class. This is the default behaviour if you don't override it.⤶ ⤶ ```⤶ /// <summary>⤶ /// Called each tick. /// Serverside: Called for each client every tick⤶ /// Clientside: Called for each tick for local client. Can be called multiple times per tick.⤶ /// </summary>⤶ public override void Simulate( Client cl )⤶ {⤶ cl.Pawn?.Simulate( cl );⤶ }⤶ ```⤶ ⤶ As you can see the default behaviour is to call Simulate on the client's Pawn. In this function you should do movement and input. ⤶ If an entity has your Pawn as the owner it becomes predictable too. So, for example, you might call `Weapon.Simulate( cl )` in your pawn's Simulate method to allow the weapons to fire.⤶ ⤶ ⤶ # Sounds and RPCs⤶ ⤶ Imagine again if you shoot a gun, but this time it plays a sound. So first of all you'd hear the gun shot from the client version (which only you would hear obviously).. but then when the server version is run it would also play a sound for all the other clients to hear.⤶ The predicting client running the prediction doesn't hear this sound because it's culled. The server assumes that this client has already heard it so there's no need to send it again.⤶ ⤶ The same goes for RPCs.⤶ ⤶ You might want to force the client to hear this sound. Maybe you're running some code that can only run on the server, like if you shot something and someone died. That code wouldn't run on the client - so it couldn't be predictable. In these cases you can turn prediction off, like this.⤶ ⤶ ```⤶ using ( Prediction.Off() )⤶ { [Net, Predicted]⤶ public int Ammo { get; set; }⤶ ⤶ public override float PrimaryRate => 5.0f;⤶ ⤶ public bool TakeAmmo( int amount )⤶ {⤶ if ( Ammo < amount )⤶ return false;⤶ ⤶ Ammo -= amount; // Predicted context still exists here since we're calling it from AttackPrimary()⤶ ⤶ return true;⤶ }⤶ ⤶ // AttackPrimary is our predicted 'hook'⤶ public override void AttackPrimary()⤶ {⤶ TimeSincePrimaryAttack = 0; // Defined as [NetPredicted] in BaseWeapon⤶ ⤶ if ( !TakeAmmo( 1 ) )⤶ return;⤶ ⤶ Owner.SetAnimParam( "b_attack", true );⤶ ⤶ ShootBullet( 0.1f, 150.0f, Rand.Float( 6, 10 ), 3.0f );⤶ }⤶ ⤶ protected void ShootBullet( float spread, float force, float damage, float size )⤶ {⤶ Vector3 forward = Owner.EyeRot.Forward + (Vector3.Random + Vector3.Random + Vector3.Random + Vector3.Random) * spread * 0.25f;⤶ ⤶ foreach ( var tr in TraceBullet( Owner.EyePos, Owner.EyePos + forward.Normal * 5000, size ) )⤶ {⤶ tr.Surface.DoBulletImpact( tr );⤶ ⤶ // TODO FOR EXAMPLE: Isn't the Prediction.Off down there unnecessary since clients peace out up here?⤶ if ( IsClient || !tr.Entity.IsValid() )⤶ continue;⤶ ⤶ using ( Prediction.Off() )⤶ {⤶ DamageInfo info = DamageInfo.FromBullet( tr.EndPos, forward.Normal * force, damage )⤶ .UsingTraceResult( tr )⤶ .WithAttacker( Owner )⤶ .WithWeapon( this );⤶ ⤶ tr.Entity.TakeDamage( info );⤶ }⤶ }⤶ }⤶ // Play sound here⤶ } ```