shithub: choc

Download patch

ref: 94ed513e58e869818ef8c088fa2c07c71f97ced4
parent: b00a10ae3398b04c6d6ad890fad80bd53aa7d763
author: Simon Howard <fraggle@soulsphere.org>
date: Fri Nov 9 19:55:34 EST 2018

net: Improve -newsync to use a PID controller.

The previous -newsync code wasn't too bad but was basically a hacky
version of a PID controller. So replace this with an actual PID
controller with tuning parameters manually tuned based on an evening
of experimentation. So far this seems to be a significant improvement
over the old -newsync code.

--- a/src/net_client.c
+++ b/src/net_client.c
@@ -150,6 +150,12 @@
 static boolean need_to_acknowledge;
 static unsigned int gamedata_recv_time;
 
+// The latency (time between when we sent our command and we got all
+// the other players' commands from the server) for the last tic we
+// received. We include this latency in tics we send to the server so
+// that they can adjust to us.
+static int last_latency;
+
 // Hash checksums of our wad directory and dehacked data.
 
 sha1_digest_t net_local_wad_sha1sum;
@@ -159,10 +165,6 @@
 
 unsigned int net_local_is_freedoom;
 
-// Average time between sending our ticcmd and receiving from the server
-
-static fixed_t average_latency;
-
 #define NET_CL_ExpandTicNum(b) NET_ExpandTicNum(recvwindow_start, (b))
 
 // Called when we become disconnected from the server
@@ -172,20 +174,15 @@
     D_ReceiveTic(NULL, NULL);
 }
 
-// Expand a net_full_ticcmd_t, applying the diffs in cmd->cmds as
-// patches against recvwindow_cmd_base.  Place the results into
-// the d_net.c structures (netcmds/nettics) and save the new ticcmd
-// back into recvwindow_cmd_base.
-
-static void NET_CL_ExpandFullTiccmd(net_full_ticcmd_t *cmd, unsigned int seq,
-                                    ticcmd_t *ticcmds)
+// Called when a packet is received from the server containing game
+// data. This updates the clock synchronization variable (offsetms)
+// using a PID filter that keeps client clocks in sync.
+static void UpdateClockSync(unsigned int seq,
+                            unsigned int remote_latency)
 {
-    int latency;
-    fixed_t adjustment;
-    int i;
+    static int last_error, cumul_error;
+    int latency, error;
 
-    // Update average_latency
-
     if (seq == send_queue[seq % BACKUPTICS].seq)
     {
         latency = I_GetTimeMS() - send_queue[seq % BACKUPTICS].time;
@@ -199,43 +196,38 @@
     }
     else
     {
-        latency = -1;
+        return;
     }
 
-    if (latency >= 0)
-    {
-        if (seq <= 20)
-        {
-            average_latency = latency * FRACUNIT;
-        }
-        else
-        {
-            // Low level filter
+    // PID filter. These are manually trained parameters.
+#define KP 0.1
+#define KI 0.01
+#define KD 0.02
 
-            average_latency = (fixed_t)((average_latency * 0.9)
-                            + (latency * FRACUNIT * 0.1));
-        }
-    }
+    // How does our latency compare to the worst other player?
+    error = latency - remote_latency;
+    cumul_error += error;
 
-    //printf("latency: %i\tremote:%i\n", average_latency / FRACUNIT, 
-    //                                   cmd->latency);
+    offsetms = KP * (FRACUNIT * error)
+             - KI * (FRACUNIT * cumul_error)
+             + (KD * FRACUNIT) * (last_error - error);
 
-    // Possibly adjust offsetms in d_net.c, try to make players all have
-    // the same lag.  Don't adjust in the first few tics of play, as 
-    // we don't have an accurate value for average_latency yet.
+    last_error = error;
+    last_latency = latency;
 
-    if (seq > TICRATE)
-    {
-        adjustment = (cmd->latency * FRACUNIT) - average_latency;
+    //printf("%i,%i,%i\n", latency, remote_latency, offsetms);
+}
 
-        // Only adjust very slightly; the cumulative effect over 
-        // multiple tics will sort it out.
+// Expand a net_full_ticcmd_t, applying the diffs in cmd->cmds as
+// patches against recvwindow_cmd_base.  Place the results into
+// the d_net.c structures (netcmds/nettics) and save the new ticcmd
+// back into recvwindow_cmd_base.
 
-        adjustment = adjustment / 100;
+static void NET_CL_ExpandFullTiccmd(net_full_ticcmd_t *cmd, unsigned int seq,
+                                    ticcmd_t *ticcmds)
+{
+    int i;
 
-        offsetms += adjustment;
-    }
-
     // Expand tic diffs for all players
     
     for (i=0; i<NET_MAXPLAYERS; ++i)
@@ -375,7 +367,7 @@
 
         sendobj = &send_queue[i % BACKUPTICS];
 
-        NET_WriteInt16(packet, average_latency / FRACUNIT);
+        NET_WriteInt16(packet, last_latency);
 
         NET_WriteTiccmdDiff(packet, &sendobj->cmd, settings.lowres_turn);
     }
@@ -715,11 +707,21 @@
         }
 
         // Store in the receive window
-        
+
         recvobj = &recvwindow[index];
 
         recvobj->active = true;
         recvobj->cmd = cmd;
+
+        // If a packet is lost or arrives out of order, we might get
+        // the tic in the next packet instead (because of extratic).
+        // If that's the case then the latency for receiving that tic
+        // now will be bogus. So we only use the last tic in the packet
+        // to trigger a clock sync update.
+        if (i == num_tics - 1)
+        {
+            UpdateClockSync(seq + i, cmd.latency);
+        }
     }
 
     // Has this been received out of sequence, ie. have we not received