Overhaul client latency calculations.

The ping/pong orders are replaced with a dedicated
(and much smaller) Ping packet that is handled
directly in the client and server Connection wrappers.

This allows clients to respond when the orders are
processed, instead of queuing the pong order to be
sent in the next frame (which added an extra 120ms
of unwanted latency).

The ping frequency has been raised to 1Hz, and pings
are now routed through the server events queue in
preparation for the future dynamic latency system.

The raw ping numbers are no longer sent to clients,
the server instead evaluates a single ConnectionQuality
value that in the future may be based on more than
just the ping times.
This commit is contained in:
Paul Chote
2021-09-20 22:44:49 +01:00
committed by abcdefg30
parent 67face8cf0
commit df798fb620
12 changed files with 161 additions and 150 deletions

View File

@@ -304,6 +304,14 @@ 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))
{
// 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);
record = false;
}
else if (OrderIO.TryParseAck(p, out var ackFrame))
{
if (!sentOrders.TryDequeue(out var q))

View File

@@ -19,6 +19,7 @@ namespace OpenRA
public enum OrderType : byte
{
Ack = 0x10,
Ping = 0x20,
SyncHash = 0x65,
Disconnect = 0xBF,
Handshake = 0xFE,

View File

@@ -109,6 +109,27 @@ namespace OpenRA.Network
return true;
}
public static bool TryParsePing(int fromClient, byte[] packet, out byte[] ping)
{
// Valid Ping packets are only ever generated by the server
if (fromClient != 0 || packet.Length != 13 || packet[4] != (byte)OrderType.Ping)
{
ping = null;
return false;
}
// Valid Ping packets always have frame 0
var frame = BitConverter.ToInt32(packet, 0);
if (frame != 0)
{
ping = null;
return false;
}
ping = packet;
return true;
}
public static bool TryParseAck((int FromClient, byte[] Data) packet, out int frame)
{
// Ack packets are only accepted from the server

View File

@@ -22,7 +22,6 @@ namespace OpenRA.Network
public class Session
{
public List<Client> Clients = new List<Client>();
public List<ClientPing> ClientPings = new List<ClientPing>();
// Keyed by the PlayerReference id that the slot corresponds to
public Dictionary<string, Slot> Slots = new Dictionary<string, Slot>();
@@ -60,10 +59,6 @@ namespace OpenRA.Network
session.Clients.Add(Client.Deserialize(node.Value));
break;
case "ClientPing":
session.ClientPings.Add(ClientPing.Deserialize(node.Value));
break;
case "GlobalSettings":
session.GlobalSettings = Global.Deserialize(node.Value);
break;
@@ -122,6 +117,8 @@ namespace OpenRA.Network
public enum ClientState { NotReady, Invalid, Ready, Disconnected = 1000 }
public enum ConnectionQuality { Good, Moderate, Poor }
public class Client
{
public static Client Deserialize(MiniYaml data)
@@ -142,6 +139,7 @@ namespace OpenRA.Network
public string IPAddress;
public string AnonymizedIPAddress;
public string Location;
public ConnectionQuality ConnectionQuality = ConnectionQuality.Good;
public ClientState State = ClientState.Invalid;
public int Team;
@@ -164,29 +162,6 @@ namespace OpenRA.Network
}
}
public ClientPing PingFromClient(Client client)
{
return ClientPings.SingleOrDefault(p => p.Index == client.Index);
}
public class ClientPing
{
public int Index;
public long Latency = -1;
public long LatencyJitter = -1;
public long[] LatencyHistory = { };
public static ClientPing Deserialize(MiniYaml data)
{
return FieldLoader.Load<ClientPing>(data);
}
public MiniYamlNode Serialize()
{
return new MiniYamlNode($"ClientPing@{Index}", FieldSaver.Save(this));
}
}
public class Slot
{
public string PlayerReference; // PlayerReference to bind against.
@@ -293,9 +268,6 @@ namespace OpenRA.Network
foreach (var client in Clients)
sessionData.Add(client.Serialize());
foreach (var clientPing in ClientPings)
sessionData.Add(clientPing.Serialize());
foreach (var slot in Slots)
sessionData.Add(slot.Value.Serialize());

View File

@@ -292,24 +292,20 @@ namespace OpenRA.Network
break;
}
case "SyncClientPings":
case "SyncConnectionQuality":
{
var pings = new List<Session.ClientPing>();
var nodes = MiniYaml.FromString(order.TargetString);
foreach (var node in nodes)
{
var strings = node.Key.Split('@');
if (strings[0] == "ClientPing")
pings.Add(Session.ClientPing.Deserialize(node.Value));
if (strings[0] == "ConnectionQuality")
{
var client = orderManager.LobbyInfo.Clients.FirstOrDefault(c => c.Index == int.Parse(strings[1]));
if (client != null)
client.ConnectionQuality = FieldLoader.GetValue<Session.ConnectionQuality>("ConnectionQuality", node.Value.Value);
}
}
orderManager.LobbyInfo.ClientPings = pings;
break;
}
case "Ping":
{
orderManager.IssueOrder(Order.FromTargetString("Pong", order.TargetString, true));
break;
}