From 483006eadc42bc2cd33214403d00f9d4e93a59cd Mon Sep 17 00:00:00 2001 From: mischa <16416509+vis2k@users.noreply.github.com> Date: Tue, 18 Jul 2023 17:19:12 +0800 Subject: [PATCH] feature: NetworkConnectionToClient.rtt via Ping & Pong Messages (#3545) * NetworkConnectionToClient: send Ping message every PingFrequency * breaking: NetworkPing/PongMessage .clientTime renamed to .localTime because it'll be used in both directions * Server->Client->Server Ping/Pong messages and rtt * don't ping in host mode * adjust tests * TODO --- Assets/Mirror/Core/LocalConnectionToClient.cs | 3 ++ Assets/Mirror/Core/Messages.cs | 13 ++++---- Assets/Mirror/Core/NetworkClient.cs | 1 + .../Mirror/Core/NetworkConnectionToClient.cs | 27 +++++++++++++++- Assets/Mirror/Core/NetworkServer.cs | 1 + Assets/Mirror/Core/NetworkTime.cs | 31 +++++++++++++++++-- .../NetworkConnectionToClientTests.cs | 2 ++ 7 files changed, 67 insertions(+), 11 deletions(-) diff --git a/Assets/Mirror/Core/LocalConnectionToClient.cs b/Assets/Mirror/Core/LocalConnectionToClient.cs index 67c964974..5b62122a2 100644 --- a/Assets/Mirror/Core/LocalConnectionToClient.cs +++ b/Assets/Mirror/Core/LocalConnectionToClient.cs @@ -29,6 +29,9 @@ internal override void Send(ArraySegment segment, int channelId = Channels // true because local connections never timeout internal override bool IsAlive(float timeout) => true; + // don't ping host client in host mode + protected override void UpdatePing() {} + internal void DisconnectInternal() { // set not ready and handle clientscene disconnect in any case diff --git a/Assets/Mirror/Core/Messages.cs b/Assets/Mirror/Core/Messages.cs index a1bcf551e..5bdf0547a 100644 --- a/Assets/Mirror/Core/Messages.cs +++ b/Assets/Mirror/Core/Messages.cs @@ -111,22 +111,21 @@ public struct EntityStateMessage : NetworkMessage public ArraySegment payload; } - // A client sends this message to the server - // to calculate RTT and synchronize time + // whoever wants to measure rtt, sends this to the other end. public struct NetworkPingMessage : NetworkMessage { - public double clientTime; + public double localTime; public NetworkPingMessage(double value) { - clientTime = value; + localTime = value; } } - // The server responds with this message - // The client can use this to calculate RTT and sync time + // the other end responds with this message. + // we can use this to calculate rtt. public struct NetworkPongMessage : NetworkMessage { - public double clientTime; + public double localTime; } } diff --git a/Assets/Mirror/Core/NetworkClient.cs b/Assets/Mirror/Core/NetworkClient.cs index 803d38500..05a49f4cb 100644 --- a/Assets/Mirror/Core/NetworkClient.cs +++ b/Assets/Mirror/Core/NetworkClient.cs @@ -471,6 +471,7 @@ internal static void RegisterMessageHandlers(bool hostMode) RegisterHandler(OnObjectDestroy); RegisterHandler(OnObjectHide); RegisterHandler(NetworkTime.OnClientPong, false); + RegisterHandler(NetworkTime.OnClientPing, false); RegisterHandler(OnSpawn); RegisterHandler(OnObjectSpawnStarted); RegisterHandler(OnObjectSpawnFinished); diff --git a/Assets/Mirror/Core/NetworkConnectionToClient.cs b/Assets/Mirror/Core/NetworkConnectionToClient.cs index 2bf1def70..7fff07d9c 100644 --- a/Assets/Mirror/Core/NetworkConnectionToClient.cs +++ b/Assets/Mirror/Core/NetworkConnectionToClient.cs @@ -46,6 +46,14 @@ public class NetworkConnectionToClient : NetworkConnection // Snapshot Buffer size limit to avoid ever growing list memory consumption attacks from clients. public int snapshotBufferSizeLimit = 64; + // ping for rtt (round trip time) + // useful for statistics, lag compensation, etc. + double lastPingTime = 0; + internal ExponentialMovingAverage _rtt = new ExponentialMovingAverage(NetworkTime.PingWindowSize); + + /// Round trip time (in seconds) that it takes a message to go server->client->server. + public double rtt => _rtt.Value; + public NetworkConnectionToClient(int networkConnectionId) : base(networkConnectionId) { @@ -175,12 +183,29 @@ internal void BufferRpc(RpcMessage message, int channelId) } } + protected virtual void UpdatePing() + { + // localTime (double) instead of Time.time for accuracy over days + if (NetworkTime.localTime >= lastPingTime + NetworkTime.PingInterval) + { + // TODO it would be safer for the server to store the last N + // messages' timestamp and only send a message number. + // This way client's can't just modify the timestamp. + NetworkPingMessage pingMessage = new NetworkPingMessage(NetworkTime.localTime); + Send(pingMessage, Channels.Unreliable); + lastPingTime = NetworkTime.localTime; + } + } + internal override void Update() { // send rpc buffers FlushRpcs(reliableRpcs, Channels.Reliable); FlushRpcs(unreliableRpcs, Channels.Unreliable); + // ping client for rtt + UpdatePing(); + // call base update to flush out batched messages base.Update(); } @@ -250,7 +275,7 @@ internal void DestroyOwnedObjects() { // unspawn scene objects, destroy instantiated objects. // fixes: https://github.com/MirrorNetworking/Mirror/issues/3538 - if (netIdentity.sceneId != 0) + if (netIdentity.sceneId != 0) NetworkServer.UnSpawn(netIdentity.gameObject); else NetworkServer.Destroy(netIdentity.gameObject); diff --git a/Assets/Mirror/Core/NetworkServer.cs b/Assets/Mirror/Core/NetworkServer.cs index c32166f8a..f613525c0 100644 --- a/Assets/Mirror/Core/NetworkServer.cs +++ b/Assets/Mirror/Core/NetworkServer.cs @@ -266,6 +266,7 @@ internal static void RegisterMessageHandlers() RegisterHandler(OnClientReadyMessage); RegisterHandler(OnCommandMessage); RegisterHandler(NetworkTime.OnServerPing, false); + RegisterHandler(NetworkTime.OnServerPong, false); RegisterHandler(OnEntityStateMessage, true); RegisterHandler(OnTimeSnapshotMessage, true); } diff --git a/Assets/Mirror/Core/NetworkTime.cs b/Assets/Mirror/Core/NetworkTime.cs index f68ab128c..fbbbe64f8 100644 --- a/Assets/Mirror/Core/NetworkTime.cs +++ b/Assets/Mirror/Core/NetworkTime.cs @@ -109,15 +109,16 @@ internal static void UpdateClient() } } + // client rtt calculation ////////////////////////////////////////////// // executed at the server when we receive a ping message // reply with a pong containing the time from the client // and time from the server internal static void OnServerPing(NetworkConnectionToClient conn, NetworkPingMessage message) { - // Debug.Log($"OnPingServerMessage conn:{conn}"); + // Debug.Log($"OnServerPing conn:{conn}"); NetworkPongMessage pongMessage = new NetworkPongMessage { - clientTime = message.clientTime, + localTime = message.localTime, }; conn.Send(pongMessage, Channels.Unreliable); } @@ -128,8 +129,32 @@ internal static void OnServerPing(NetworkConnectionToClient conn, NetworkPingMes internal static void OnClientPong(NetworkPongMessage message) { // how long did this message take to come back - double newRtt = localTime - message.clientTime; + double newRtt = localTime - message.localTime; _rtt.Add(newRtt); } + + // server rtt calculation ////////////////////////////////////////////// + // Executed at the client when we receive a ping message from the server. + // in other words, this is for server sided ping + rtt calculation. + // reply with a pong containing the time from the server + internal static void OnClientPing(NetworkPingMessage message) + { + // Debug.Log($"OnClientPing conn:{conn}"); + NetworkPongMessage pongMessage = new NetworkPongMessage + { + localTime = message.localTime, + }; + NetworkClient.Send(pongMessage, Channels.Unreliable); + } + + // Executed at the server when we receive a Pong message back. + // find out how long it took since we sent the Ping + // and update time offset + internal static void OnServerPong(NetworkConnectionToClient conn, NetworkPongMessage message) + { + // how long did this message take to come back + double newRtt = localTime - message.localTime; + conn._rtt.Add(newRtt); + } } } diff --git a/Assets/Mirror/Tests/Editor/NetworkConnection/NetworkConnectionToClientTests.cs b/Assets/Mirror/Tests/Editor/NetworkConnection/NetworkConnectionToClientTests.cs index b861a06af..75b349c92 100644 --- a/Assets/Mirror/Tests/Editor/NetworkConnection/NetworkConnectionToClientTests.cs +++ b/Assets/Mirror/Tests/Editor/NetworkConnection/NetworkConnectionToClientTests.cs @@ -35,6 +35,7 @@ public void Send_BatchesUntilUpdate() { // create connection and send NetworkConnectionToClient connection = new NetworkConnectionToClient(42); + NetworkTime.PingInterval = float.MaxValue; // disable ping for this test byte[] message = {0x01, 0x02}; connection.Send(new ArraySegment(message)); @@ -63,6 +64,7 @@ public void SendBatchingResetsPreviousWriter() // create connection NetworkConnectionToClient connection = new NetworkConnectionToClient(42); + NetworkTime.PingInterval = float.MaxValue; // disable ping for this test // send and update big message byte[] message = {0x01, 0x02};