diff --git a/OpenRA.Game/Network/Connection.cs b/OpenRA.Game/Network/Connection.cs index 43cc521baf..998c0c053e 100644 --- a/OpenRA.Game/Network/Connection.cs +++ b/OpenRA.Game/Network/Connection.cs @@ -304,12 +304,11 @@ namespace OpenRA.Network orderManager.ReceiveDisconnect(disconnect.ClientId, disconnect.Frame); else if (OrderIO.TryParseSync(p.Data, out var sync)) orderManager.ReceiveSync(sync); - else if (OrderIO.TryParsePing(p.FromClient, p.Data, out var ping)) + else if (OrderIO.TryParsePingRequest(p, out var timestamp)) { - // The Ping packet is sent back directly without changes // Note that processing this here, rather than in NetworkConnectionReceive, // so that poor world tick performance can be reflected in the latency measurement - Send(ping); + Send(OrderIO.SerializePingResponse(timestamp, (byte)orderManager.OrderQueueLength)); record = false; } else if (OrderIO.TryParseAck(p, out var ackFrame, out var ackCount)) diff --git a/OpenRA.Game/Network/OrderIO.cs b/OpenRA.Game/Network/OrderIO.cs index a6c3ad7816..c3c840416b 100644 --- a/OpenRA.Game/Network/OrderIO.cs +++ b/OpenRA.Game/Network/OrderIO.cs @@ -93,6 +93,16 @@ namespace OpenRA.Network return ms.GetBuffer(); } + public static byte[] SerializePingResponse(long timestamp, byte queueLength) + { + var ms = new MemoryStream(14); + ms.WriteArray(BitConverter.GetBytes(0)); + ms.WriteByte((byte)OrderType.Ping); + ms.WriteArray(BitConverter.GetBytes(timestamp)); + ms.WriteByte(queueLength); + return ms.GetBuffer(); + } + public static bool TryParseDisconnect((int FromClient, byte[] Data) packet, out (int Frame, int ClientId) disconnect) { // Valid Disconnect packets are only ever generated by the server @@ -123,24 +133,24 @@ namespace OpenRA.Network return true; } - public static bool TryParsePing(int fromClient, byte[] packet, out byte[] ping) + public static bool TryParsePingRequest((int FromClient, byte[] Data) packet, out long timestamp) { - // Valid Ping packets are only ever generated by the server - if (fromClient != 0 || packet.Length != 13 || packet[4] != (byte)OrderType.Ping) + // Valid Ping requests are only ever generated by the server + if (packet.FromClient != 0 || packet.Data.Length != 13 || packet.Data[4] != (byte)OrderType.Ping) { - ping = null; + timestamp = 0; return false; } // Valid Ping packets always have frame 0 - var frame = BitConverter.ToInt32(packet, 0); + var frame = BitConverter.ToInt32(packet.Data, 0); if (frame != 0) { - ping = null; + timestamp = 0; return false; } - ping = packet; + timestamp = BitConverter.ToInt64(packet.Data, 5); return true; } diff --git a/OpenRA.Game/Network/OrderManager.cs b/OpenRA.Game/Network/OrderManager.cs index 61d08edda3..6eb158b8cb 100644 --- a/OpenRA.Game/Network/OrderManager.cs +++ b/OpenRA.Game/Network/OrderManager.cs @@ -29,6 +29,7 @@ namespace OpenRA.Network public Session LobbyInfo = new Session(); public Session.Client LocalClient => LobbyInfo.ClientWithIndex(Connection.LocalClientId); public World World; + public int OrderQueueLength => pendingOrders.Count > 0 ? pendingOrders.Min(q => q.Value.Count) : 0; public string ServerError = null; public bool AuthenticationFailed = false; diff --git a/OpenRA.Game/Server/Connection.cs b/OpenRA.Game/Server/Connection.cs index 79d47fb0b4..2c0bdb95cc 100644 --- a/OpenRA.Game/Server/Connection.cs +++ b/OpenRA.Game/Server/Connection.cs @@ -128,13 +128,13 @@ namespace OpenRA.Server { // Ping packets are sent and processed internally within this thread to reduce // server-introduced latencies from polling loops - if (expectLength == 9 && bytes[0] == (byte)OrderType.Ping) + if (expectLength == 10 && bytes[0] == (byte)OrderType.Ping) { if (pingHistory.Count == MaxPingSamples) pingHistory.Dequeue(); pingHistory.Enqueue((int)(Game.RunTime - BitConverter.ToInt64(bytes, 1))); - server.OnConnectionPing(this, pingHistory.ToArray()); + server.OnConnectionPing(this, pingHistory.ToArray(), bytes[9]); } else server.OnConnectionPacket(this, frame, bytes); diff --git a/OpenRA.Game/Server/ProtocolVersion.cs b/OpenRA.Game/Server/ProtocolVersion.cs index 3256859bca..b04ffd56ef 100644 --- a/OpenRA.Game/Server/ProtocolVersion.cs +++ b/OpenRA.Game/Server/ProtocolVersion.cs @@ -41,6 +41,7 @@ namespace OpenRA.Server // - byte containing the number of sent order packets to apply // - 0x20: Ping // - Int64 containing the server timestamp when the ping was generated + // - [client -> server only] byte containing the number of frames ready to simulate // // A connection handshake begins when a client opens a connection to the server: // - Server sends: diff --git a/OpenRA.Game/Server/Server.cs b/OpenRA.Game/Server/Server.cs index d11b7c5484..211e5fa736 100644 --- a/OpenRA.Game/Server/Server.cs +++ b/OpenRA.Game/Server/Server.cs @@ -323,9 +323,9 @@ namespace OpenRA.Server events.Add(new ConnectionPacketEvent(conn, frame, data)); } - internal void OnConnectionPing(Connection conn, int[] pingHistory) + internal void OnConnectionPing(Connection conn, int[] pingHistory, byte queueLength) { - events.Add(new ConnectionPingEvent(conn, pingHistory)); + events.Add(new ConnectionPingEvent(conn, pingHistory, queueLength)); } internal void OnConnectionDisconnect(Connection conn) @@ -1021,7 +1021,7 @@ namespace OpenRA.Server } } - public void ReceivePing(Connection conn, int[] pingHistory) + public void ReceivePing(Connection conn, int[] pingHistory, byte queueLength) { // Levels set relative to the default order lag of 3 net ticks (360ms) // TODO: Adjust this once dynamic lag is implemented @@ -1367,16 +1367,18 @@ namespace OpenRA.Server { readonly Connection connection; readonly int[] pingHistory; + readonly byte queueLength; - public ConnectionPingEvent(Connection connection, int[] pingHistory) + public ConnectionPingEvent(Connection connection, int[] pingHistory, byte queueLength) { this.connection = connection; this.pingHistory = pingHistory; + this.queueLength = queueLength; } void IServerEvent.Invoke(Server server) { - server.ReceivePing(connection, pingHistory); + server.ReceivePing(connection, pingHistory, queueLength); } }