diff --git a/OpenRA.Game/Network/Session.cs b/OpenRA.Game/Network/Session.cs index 58d4242b2f..d60ea085f9 100644 --- a/OpenRA.Game/Network/Session.cs +++ b/OpenRA.Game/Network/Session.cs @@ -18,6 +18,7 @@ namespace OpenRA.Network public class Session { public List Clients = new List(); + public List ClientPings = new List(); // Keyed by the PlayerReference id that the slot corresponds to public Dictionary Slots = new Dictionary(); @@ -45,6 +46,10 @@ namespace OpenRA.Network session.Clients.Add(FieldLoader.Load(y.Value)); break; + case "ClientPing": + session.ClientPings.Add(FieldLoader.Load(y.Value)); + break; + case "Slot": var s = FieldLoader.Load(y.Value); session.Slots.Add(s.PlayerReference, s); @@ -107,6 +112,23 @@ namespace OpenRA.Network public bool IsReady { get { return State == ClientState.Ready; } } public bool IsInvalid { get { return State == ClientState.Invalid; } } public bool IsObserver { get { return Slot == null; } } + + public string Serialize() + { + var clientData = new List(); + clientData.Add(new MiniYamlNode("Client@{0}".F(this.Index), FieldSaver.Save(this))); + return clientData.WriteToString(); + } + } + + public ClientPing PingFromClient(Client client) + { + return ClientPings.SingleOrDefault(p => p.Index == client.Index); + } + + public class ClientPing + { + public int Index; public int Latency = -1; public int LatencyJitter = -1; public int[] LatencyHistory = { }; @@ -114,7 +136,7 @@ namespace OpenRA.Network public string Serialize() { var clientData = new List(); - clientData.Add(new MiniYamlNode("Client@{0}".F(this.Index), FieldSaver.Save(this))); + clientData.Add(new MiniYamlNode("ClientPing@{0}".F(this.Index), FieldSaver.Save(this))); return clientData.WriteToString(); } } @@ -174,6 +196,9 @@ namespace OpenRA.Network foreach (var client in Clients) sessionData.Append(client.Serialize()); + foreach (var clientPing in ClientPings) + sessionData.Append(clientPing.Serialize()); + foreach (var slot in Slots) sessionData.Append(slot.Value.Serialize()); diff --git a/OpenRA.Game/Network/UnitOrders.cs b/OpenRA.Game/Network/UnitOrders.cs index f7f629e800..94e7f95828 100644 --- a/OpenRA.Game/Network/UnitOrders.cs +++ b/OpenRA.Game/Network/UnitOrders.cs @@ -218,6 +218,21 @@ namespace OpenRA.Network break; } + case "SyncClientPings": + { + var pings = new List(); + var nodes = MiniYaml.FromString(order.TargetString); + foreach (var node in nodes) + { + var strings = node.Key.Split('@'); + if (strings[0] == "ClientPing") + pings.Add(FieldLoader.Load(node.Value)); + } + + orderManager.LobbyInfo.ClientPings = pings; + break; + } + case "SetStance": { if (!Game.orderManager.LobbyInfo.GlobalSettings.FragileAlliances) diff --git a/OpenRA.Game/Server/Server.cs b/OpenRA.Game/Server/Server.cs index 614221da22..80d34a19c9 100644 --- a/OpenRA.Game/Server/Server.cs +++ b/OpenRA.Game/Server/Server.cs @@ -322,6 +322,9 @@ namespace OpenRA.Server PreConns.Remove(newConn); Conns.Add(newConn); LobbyInfo.Clients.Add(client); + var clientPing = new Session.ClientPing(); + clientPing.Index = client.Index; + LobbyInfo.ClientPings.Add(clientPing); Log.Write("server", "Client {0}: Accepted connection from {1}.", newConn.PlayerIndex, newConn.socket.RemoteEndPoint); @@ -442,6 +445,7 @@ namespace OpenRA.Server switch (so.Name) { case "Command": + { bool handled = false; foreach (var t in serverTraits.WithInterface()) if (handled = t.InterpretCommand(this, conn, GetClient(conn), so.Data)) @@ -454,7 +458,7 @@ namespace OpenRA.Server } break; - + } case "HandshakeResponse": ValidateClient(conn, so.Data); break; @@ -472,20 +476,22 @@ namespace OpenRA.Server break; } - var fromClient = GetClient(conn); - var history = fromClient.LatencyHistory.ToList(); + var pingFromClient = LobbyInfo.PingFromClient(GetClient(conn)); + if (pingFromClient == null) + return; + + var history = pingFromClient.LatencyHistory.ToList(); history.Add(Environment.TickCount - pingSent); // Cap ping history at 5 values (25 seconds) if (history.Count > 5) history.RemoveRange(0, history.Count - 5); - fromClient.Latency = history.Sum() / history.Count; - fromClient.LatencyJitter = (history.Max() - history.Min()) / 2; - fromClient.LatencyHistory = history.ToArray(); + pingFromClient.Latency = history.Sum() / history.Count; + pingFromClient.LatencyJitter = (history.Max() - history.Min()) / 2; + pingFromClient.LatencyHistory = history.ToArray(); - if (State == ServerState.WaitingPlayers) - SyncLobbyClients(); // TODO: SyncClientLatency + SyncClientPing(); break; } @@ -614,6 +620,19 @@ namespace OpenRA.Server t.LobbyInfoSynced(this); } + public void SyncClientPing() + { + var clientPings = new System.Text.StringBuilder(); + foreach (var ping in LobbyInfo.ClientPings) + clientPings.Append(ping.Serialize()); + + DispatchOrders(null, 0, + new ServerOrder("SyncClientPings", clientPings.ToString()).Serialize()); + + foreach (var t in serverTraits.WithInterface()) + t.LobbyInfoSynced(this); + } + public void StartGame() { listener.Stop(); diff --git a/OpenRA.Mods.RA/Player/PlayerStatistics.cs b/OpenRA.Mods.RA/Player/PlayerStatistics.cs index 71175c7bc7..333e63e361 100644 --- a/OpenRA.Mods.RA/Player/PlayerStatistics.cs +++ b/OpenRA.Mods.RA/Player/PlayerStatistics.cs @@ -95,6 +95,9 @@ namespace OpenRA.Mods.RA case "SyncClientInfo": case "SyncLobbySlots": case "SyncLobbyGlobalSettings": + case "SyncClientPing": + case "Ping": + case "Pong": return; } if (order.OrderString.StartsWith("Dev")) diff --git a/OpenRA.Mods.RA/ServerTraits/LobbyCommands.cs b/OpenRA.Mods.RA/ServerTraits/LobbyCommands.cs index 5b1866dec9..eaef9dc6ad 100644 --- a/OpenRA.Mods.RA/ServerTraits/LobbyCommands.cs +++ b/OpenRA.Mods.RA/ServerTraits/LobbyCommands.cs @@ -174,7 +174,11 @@ namespace OpenRA.Mods.RA.Server if (occupant != null) { if (occupant.Bot != null) + { server.LobbyInfo.Clients.Remove(occupant); + var ping = server.LobbyInfo.PingFromClient(occupant); + server.LobbyInfo.ClientPings.Remove(ping); + } else { var occupantConn = server.Conns.FirstOrDefault(c => c.PlayerIndex == occupant.Index); @@ -203,7 +207,11 @@ namespace OpenRA.Mods.RA.Server // Slot may have a bot in it var occupant = server.LobbyInfo.ClientInSlot(s); if (occupant != null && occupant.Bot != null) + { server.LobbyInfo.Clients.Remove(occupant); + var ping = server.LobbyInfo.PingFromClient(occupant); + server.LobbyInfo.ClientPings.Remove(ping); + } server.SyncLobbyClients(); return true; diff --git a/OpenRA.Mods.RA/Widgets/Logic/ClientTooltipLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/ClientTooltipLogic.cs index 1805c23fa0..1e17f43136 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/ClientTooltipLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/ClientTooltipLogic.cs @@ -71,8 +71,10 @@ namespace OpenRA.Mods.RA.Widgets.Logic }; admin.IsVisible = () => orderManager.LobbyInfo.ClientWithIndex(clientIndex).IsAdmin; - latency.GetText = () => LobbyUtils.LatencyDescription(orderManager.LobbyInfo.ClientWithIndex(clientIndex).Latency); - latency.GetColor = () => LobbyUtils.LatencyColor(orderManager.LobbyInfo.ClientWithIndex(clientIndex).Latency); + var client = orderManager.LobbyInfo.ClientWithIndex(clientIndex); + var ping = orderManager.LobbyInfo.PingFromClient(client); + latency.GetText = () => LobbyUtils.LatencyDescription(ping); + latency.GetColor = () => LobbyUtils.LatencyColor(ping); var address = orderManager.LobbyInfo.ClientWithIndex(clientIndex).IpAddress; if (address == "127.0.0.1" && UPnP.NatDevice != null) address = UPnP.NatDevice.GetExternalIP().ToString(); diff --git a/OpenRA.Mods.RA/Widgets/Logic/LobbyUtils.cs b/OpenRA.Mods.RA/Widgets/Logic/LobbyUtils.cs index d16c28686e..4bfb250620 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/LobbyUtils.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/LobbyUtils.cs @@ -163,26 +163,32 @@ namespace OpenRA.Mods.RA.Widgets.Logic } } - public static Color LatencyColor(int latency) + public static Color LatencyColor(Session.ClientPing ping) { + if (ping == null) + return Color.Gray; + // Levels set relative to the default order lag of 3 net ticks (360ms) // TODO: Adjust this once dynamic lag is implemented - if (latency < 0) + if (ping.Latency < 0) return Color.Gray; - if (latency < 300) + if (ping.Latency < 300) return Color.LimeGreen; - if (latency < 600) + if (ping.Latency < 600) return Color.Orange; return Color.Red; } - public static string LatencyDescription(int latency) + public static string LatencyDescription(Session.ClientPing ping) { - if (latency < 0) + if (ping == null) return "Unknown"; - if (latency < 300) + + if (ping.Latency < 0) + return "Unknown"; + if (ping.Latency < 300) return "Good"; - if (latency < 600) + if (ping.Latency < 600) return "Moderate"; return "Poor"; } @@ -216,7 +222,8 @@ namespace OpenRA.Mods.RA.Widgets.Logic block.IsVisible = () => visible; if (visible) - block.Get("LATENCY_COLOR").GetColor = () => LatencyColor(c.Latency); + block.Get("LATENCY_COLOR").GetColor = () => LatencyColor( + orderManager.LobbyInfo.PingFromClient(c)); var tooltip = parent.Get("CLIENT_REGION"); tooltip.IsVisible = () => visible;