Skip to content

Commit 71fc4ac

Browse files
committed
Added events for when server forces a reconcile
1 parent 3d5ba57 commit 71fc4ac

File tree

4 files changed

+70
-22
lines changed

4 files changed

+70
-22
lines changed

Runtime/Code/Player/Character/MovementSystem/CharacterMovement.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ public bool disableInput {
122122
private bool crouchInput;
123123

124124
//Prediction
125-
private bool queueReplay = false;
125+
private bool queueForcedReconcile = false;
126126
#endregion
127127

128128
#region SYNC DATA
@@ -293,9 +293,9 @@ public void RunMovementTick(bool isReplay = false){
293293
Move(currentMoveState.currentMoveInput);
294294

295295
//Queue after a movement tick so we have the updated position and velocity
296-
if(isServerOnly && queueReplay){
297-
predictedMovement.ForceReplay();
298-
queueReplay = false;
296+
if(isServerOnly && queueForcedReconcile){
297+
queueForcedReconcile = false;
298+
predictedMovement.ForceReconcile();
299299
}
300300

301301
OnEndMove?.Invoke(currentMoveState, isReplay);
@@ -897,7 +897,8 @@ public void TeleportAndLook(Vector3 position, Vector3 lookVector) {
897897
//Teleport Locally
898898
TeleportInternal(position, lookVector);
899899
if(isServerAuth && isServerOnly){
900-
this.queueReplay = true;
900+
Debug.Log("Queueing Forced Reconcile");
901+
this.queueForcedReconcile = true;
901902
}
902903
} else if(!isServerAuth && isServerOnly){
903904
//Tell client to teleport
@@ -1001,7 +1002,7 @@ public void SetImpulse(Vector3 impulse){
10011002
//Locally
10021003
SetImpulseInternal(impulse);
10031004
if(isServerAuth && isServerOnly){
1004-
this.queueReplay = true;
1005+
this.queueForcedReconcile = true;
10051006
}
10061007
} else if(!isServerAuth && isServerOnly){
10071008
//Tell client

Runtime/Code/Player/Character/MovementSystem/ClientPrediction/AirshipPredictedController.cs

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ public abstract class AirshipPredictedController<T> : NetworkBehaviour, IPredict
4242
[Tooltip("Optimization. Reduce the number of reconciles by only reconciling every N frames")]
4343
public int checkReconcileEveryNthFrame = 4;
4444

45+
[Tooltip("If true the object will replay after a forced reconcile. If false the object will clear its history and start over from the point of the server")]
46+
public bool replayForcedReconciles = false;
47+
4548
[Tooltip("Applying server corrections one frame ahead gives much better results. We don't know why yet, so this is an option for now.")]
4649
public int serverSerializationOffset = 1;
4750
public int replayTickOffset = 0;
@@ -119,6 +122,8 @@ public abstract class AirshipPredictedController<T> : NetworkBehaviour, IPredict
119122
#endregion
120123

121124
#region VIRTUAL
125+
public virtual void OnServerForceReconcile(){}
126+
public virtual void OnClientForcedReconcile(){}
122127
public virtual string friendlyName => "Prediction: " + gameObject.GetInstanceID();
123128
public virtual float guid => gameObject.GetInstanceID();
124129

@@ -233,10 +238,10 @@ protected virtual void OnPhysicsTick() {
233238

234239

235240
#region PREDICTION
236-
private bool forceNextReplay = false;
237-
public void ForceReplay(){
241+
private bool forceNextReconcile = false;
242+
public void ForceReconcile(){
238243
if(isServerOnly){
239-
forceNextReplay = true;
244+
forceNextReconcile = true;
240245
}
241246
}
242247

@@ -325,19 +330,26 @@ protected void OnReceivedState(int serverTick, T serverState, bool forceReplay)
325330

326331
if(forceReplay){
327332
int forcedIndex;
328-
if(stateHistory.Count <= 2){
329-
//Shouldn't this be apply state so it actually saves into the history?
330-
//ApplyState(serverState);
331-
SnapTo(serverState);
332-
return;
333-
} else if (Sample(stateHistory, serverTick, out T before, out forcedIndex)) {
333+
if(!replayForcedReconciles || stateHistory.Count <= 2){
334+
print("FORCING RECONCILE");
335+
stateHistory.Clear();
336+
ApplyState(serverState);
337+
}
338+
// else if(stateHistory.Count <= 2){
339+
// //Shouldn't this be apply state so it actually saves into the history?
340+
// //ApplyState(serverState);
341+
// SnapTo(serverState);
342+
// }
343+
else if (Sample(stateHistory, serverTick, out T before, out forcedIndex)) {
344+
print("FORCING REPLAY");
334345
AirshipPredictionManager.instance.QueueReplay(this, serverState, lastRecorded.tick - 1 + replayTickOffset, forcedIndex);
335-
return;
336346
} else {
337347
// something went very wrong. sampling should've worked.
338348
Debug.LogError("Unable to sample with a forced replay at tick: " + serverTick);
339349
PrintHistory();
340350
}
351+
OnClientForcedReconcile();
352+
return;
341353
}
342354

343355
// correction requires at least 2 existing states for 'before' and 'after'.
@@ -504,8 +516,11 @@ public override void OnSerialize(NetworkWriter writer, bool initialState) {
504516
writer.WriteInt(serverTick);
505517

506518
//Pass a bool to signify if client MUST replay
507-
writer.WriteBool(forceNextReplay);
508-
forceNextReplay = false;
519+
writer.WriteBool(forceNextReconcile);
520+
if(forceNextReconcile){
521+
OnServerForceReconcile();
522+
forceNextReconcile = false;
523+
}
509524

510525
//Let the child class serialize its data
511526
SerializeState(writer);
@@ -527,8 +542,11 @@ public override void OnDeserialize(NetworkReader reader, bool initialState) {
527542
//Get server tick
528543
SetServerTick(reader.ReadInt());
529544

530-
//Must we replay?
531-
var forceReplay = reader.ReadBool();
545+
//Must we force to the server?
546+
var forceReconcile = reader.ReadBool();
547+
if(forceReconcile){
548+
OnServerForceReconcile();
549+
}
532550

533551
//print("GOT server tick: " + serverTick + " predictedTick: " + predictedTick + " offset: " + offset + " offsetTime: " + NetworkTime.predictionErrorUnadjusted);
534552

@@ -549,7 +567,7 @@ public override void OnDeserialize(NetworkReader reader, bool initialState) {
549567
//print("Deserialize time: " + timestamp + " readerTime: " + serverDeltaTime);
550568
// process received state
551569
try{
552-
OnReceivedState(serverTick, newState, forceReplay);
570+
OnReceivedState(serverTick, newState, forceReconcile);
553571
}catch(Exception e){
554572
Debug.LogError("Error on recieved state: " + e.Message + " trace: " + e.StackTrace);
555573
}

Runtime/Code/Player/Character/MovementSystem/ClientPrediction/AirshipPredictionManager.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using Mirror;
4+
using UnityEditorInternal;
45
using UnityEngine;
56

67
public class AirshipPredictionManager : MonoBehaviour {
@@ -47,6 +48,7 @@ internal class RigidbodyState{
4748
public Quaternion currentRotation;
4849
public Vector3 lastPosition;
4950
public Quaternion lastRotation;
51+
public int snapTicks = 0;
5052

5153
public RigidbodyState(Rigidbody rigid, Transform graphicsHolder){
5254
this.rigid = rigid;
@@ -108,6 +110,12 @@ public void RegisterRigidbody(Rigidbody rigid, Transform graphicsHolder) {
108110
public void UnRegisterRigidbody(Rigidbody rigid) {
109111
this.currentTrackedRigidbodies.Remove(rigid.GetInstanceID());
110112
}
113+
114+
public void SnapRigidbody(Rigidbody rigid, int numberOfTicks = 1){
115+
if(this.currentTrackedRigidbodies.TryGetValue(rigid.GetInstanceID(), out RigidbodyState state)){
116+
state.snapTicks = numberOfTicks;
117+
}
118+
}
111119
#endregion
112120

113121
#region UPDATE
@@ -188,6 +196,12 @@ public void InterpolateBodies(){
188196
//TODO: Sort the rigidbodies by depth (how deep in heirarchy?) so that we update nested rigidbodies in the correct order
189197
foreach(var kvp in currentTrackedRigidbodies){
190198
var rigidData = kvp.Value;
199+
if(rigidData.snapTicks > 0){
200+
print("Snapping rigidbody");
201+
rigidData.snapTicks--;
202+
rigidData.lastPosition = rigidData.currentPosition;
203+
rigidData.lastRotation = rigidData.currentRotation;
204+
}
191205
// rigidData.graphicsHolder.SetPositionAndRotation(
192206
// Vector3.Lerp(rigidData.lastPosition, rigidData.currentPosition, interpolationTime),
193207
// Quaternion.Lerp(rigidData.lastRotation, rigidData.currentRotation, interpolationTime)

Runtime/Code/Player/Character/MovementSystem/ClientPrediction/CharacterMovement/AirshipPredictedCharacterMovement.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ public class AirshipPredictedCharacterMovement : AirshipPredictedController<Char
1616
[Header("Character References")]
1717
public CharacterMovement movement;
1818

19-
[Header("Variables")]
19+
[Header("Character Variables")]
20+
public bool snapPositionOnReconcile = true;
2021

2122
#endregion
2223

@@ -195,6 +196,20 @@ public override void SnapTo(CharacterMovementState newState){
195196
}
196197
}
197198

199+
public override void OnClientForcedReconcile() {
200+
if(snapPositionOnReconcile){
201+
print("Telling prediction to snap");
202+
//Snap the movement so it doesn't lerp to the forced position
203+
AirshipPredictionManager.instance.SnapRigidbody(this.movement.rigidbody, 5);
204+
}
205+
}
206+
207+
public override void OnServerForceReconcile() {
208+
if(!replayForcedReconciles){
209+
this.recievedInputs.Clear();
210+
}
211+
}
212+
198213
public override void OnReplayStarted(AirshipPredictedState initialState, int historyIndex){
199214
if(showLogs){
200215
print("Replay start: " + initialState.tick);

0 commit comments

Comments
 (0)