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:
@@ -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))
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace OpenRA
|
||||
public enum OrderType : byte
|
||||
{
|
||||
Ack = 0x10,
|
||||
Ping = 0x20,
|
||||
SyncHash = 0x65,
|
||||
Disconnect = 0xBF,
|
||||
Handshake = 0xFE,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user