From b35e141eb907476e9e61769da2fa11b1cc72894b Mon Sep 17 00:00:00 2001 From: "[11EJ11]" Date: Thu, 2 Oct 2025 12:52:57 +1300 Subject: [PATCH 1/3] Retain tunnel object through refresh. Prevent multiple refreshes running at same time. Prevent duplicate tunnels. --- .../Domain/Multiplayer/CnCNet/CnCNetTunnel.cs | 24 ++++++- .../Multiplayer/CnCNet/TunnelHandler.cs | 62 ++++++++++++++++--- 2 files changed, 76 insertions(+), 10 deletions(-) diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/CnCNetTunnel.cs b/DXMainClient/Domain/Multiplayer/CnCNet/CnCNetTunnel.cs index 833baad8b..d83fa634f 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/CnCNetTunnel.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/CnCNetTunnel.cs @@ -34,7 +34,7 @@ public static CnCNetTunnel Parse(string str) string address = parts[0]; string[] detailedAddress = address.Split(new char[] { ':' }); - + tunnel.Address = detailedAddress[0]; tunnel.Port = int.Parse(detailedAddress[1]); tunnel.Country = parts[1]; @@ -86,7 +86,27 @@ public static CnCNetTunnel Parse(string str) public int PingInMs { get; set; } = -1; /// - /// Gets a list of player ports to use from a specific tunnel server. + /// Updates this tunnel's metadata from another tunnel instance, preserving Address, Port, and existing PingInMs. + /// + internal void UpdateFrom(CnCNetTunnel updatedTunnel) + { + Country = updatedTunnel.Country; + CountryCode = updatedTunnel.CountryCode; + Name = updatedTunnel.Name; + Clients = updatedTunnel.Clients; + MaxClients = updatedTunnel.MaxClients; + Official = updatedTunnel.Official; + Recommended = updatedTunnel.Recommended; + Version = updatedTunnel.Version; + + RequiresPassword = updatedTunnel.RequiresPassword; + Latitude = updatedTunnel.Latitude; + Longitude = updatedTunnel.Longitude; + Distance = updatedTunnel.Distance; + } + + /// + /// Gets a list of player ports to use from a specific V2 tunnel server. /// /// A list of player ports to use. public List GetPlayerPortInfo(int playerCount) diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/TunnelHandler.cs b/DXMainClient/Domain/Multiplayer/CnCNet/TunnelHandler.cs index b5952bccb..6f68fd50e 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/TunnelHandler.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/TunnelHandler.cs @@ -31,6 +31,9 @@ public class TunnelHandler : GameComponent private const int SUPPORTED_TUNNEL_VERSION = 2; + private readonly object _refreshLock = new object(); + private bool _refreshInProgress = false; + public TunnelHandler(WindowManager wm, CnCNetManager connectionManager) : base(wm.Game) { this.wm = wm; @@ -78,26 +81,65 @@ private void DoCurrentTunnelPinged() private void RefreshTunnelsAsync() { + lock (_refreshLock) + { + if (_refreshInProgress) + return; + _refreshInProgress = true; + } + Task.Factory.StartNew(() => { - List tunnels = RefreshTunnels(); - wm.AddCallback(new Action>(HandleRefreshedTunnels), tunnels); + try + { + List tunnels = RefreshTunnels(); + wm.AddCallback(new Action>(HandleRefreshedTunnels), tunnels); + } + finally + { + lock (_refreshLock) + { + _refreshInProgress = false; + } + } }); } - private void HandleRefreshedTunnels(List tunnels) + private void HandleRefreshedTunnels(List newTunnels) { - if (tunnels.Count > 0) - Tunnels = tunnels; + if (newTunnels.Count == 0) + { + TunnelsRefreshed?.Invoke(this, EventArgs.Empty); + return; + } - TunnelsRefreshed?.Invoke(this, EventArgs.Empty); + var existingTunnels = Tunnels.ToDictionary(t => $"{t.Address}:{t.Port}"); + var updatedTunnels = new List(); + + foreach (var newTunnel in newTunnels) + { + string key = $"{newTunnel.Address}:{newTunnel.Port}"; + if (existingTunnels.TryGetValue(key, out var existingTunnel)) + { + // update existing tunnels + existingTunnel.UpdateFrom(newTunnel); + updatedTunnels.Add(existingTunnel); + } + else + { + // add new tunnels + updatedTunnels.Add(newTunnel); + } + } - Task[] pingTasks = new Task[Tunnels.Count]; + // remove old tunnels + Tunnels = updatedTunnels; + TunnelsRefreshed?.Invoke(this, EventArgs.Empty); for (int i = 0; i < Tunnels.Count; i++) { if (UserINISettings.Instance.PingUnofficialCnCNetTunnels || Tunnels[i].Official || Tunnels[i].Recommended) - pingTasks[i] = PingListTunnelAsync(i); + _ = PingListTunnelAsync(i); } if (CurrentTunnel != null) @@ -212,6 +254,7 @@ private byte[] GetRawTunnelData(int retryCount = 2) private List RefreshTunnels() { List returnValue = new List(); + var seenAddresses = new HashSet(StringComparer.OrdinalIgnoreCase); FileInfo tunnelCacheFile = SafePath.GetFile(ProgramConstants.ClientUserFilesPath, "tunnel_cache"); @@ -239,6 +282,9 @@ private List RefreshTunnels() if (tunnel.Version != SUPPORTED_TUNNEL_VERSION) continue; + if (!seenAddresses.Add(tunnel.Address)) + continue; + returnValue.Add(tunnel); } catch (Exception ex) From 212033ddc18b2fae8a3a3ddc7272bacb8d776456 Mon Sep 17 00:00:00 2001 From: "[11EJ11]" Date: Thu, 2 Oct 2025 13:10:31 +1300 Subject: [PATCH 2/3] Fix dupe check --- DXMainClient/Domain/Multiplayer/CnCNet/TunnelHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/TunnelHandler.cs b/DXMainClient/Domain/Multiplayer/CnCNet/TunnelHandler.cs index 6f68fd50e..1b1283229 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/TunnelHandler.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/TunnelHandler.cs @@ -282,7 +282,7 @@ private List RefreshTunnels() if (tunnel.Version != SUPPORTED_TUNNEL_VERSION) continue; - if (!seenAddresses.Add(tunnel.Address)) + if (!seenAddresses.Add($"{tunnel.Address}:{tunnel.Port}")) continue; returnValue.Add(tunnel); From a1b74e63e58a3742fff0b6ba986b3e699027a1d7 Mon Sep 17 00:00:00 2001 From: "[11EJ11]" Date: Sun, 5 Oct 2025 14:39:56 +1300 Subject: [PATCH 3/3] Change Task.Factory.StartNew to Task.Run --- DXMainClient/Domain/Multiplayer/CnCNet/TunnelHandler.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/TunnelHandler.cs b/DXMainClient/Domain/Multiplayer/CnCNet/TunnelHandler.cs index 1b1283229..8de582b47 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/TunnelHandler.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/TunnelHandler.cs @@ -88,7 +88,7 @@ private void RefreshTunnelsAsync() _refreshInProgress = true; } - Task.Factory.StartNew(() => + Task.Run(() => { try { @@ -162,7 +162,7 @@ private void HandleRefreshedTunnels(List newTunnels) private Task PingListTunnelAsync(int index) { - return Task.Factory.StartNew(() => + return Task.Run(() => { Tunnels[index].UpdatePing(); DoTunnelPinged(index); @@ -171,7 +171,7 @@ private Task PingListTunnelAsync(int index) private Task PingCurrentTunnelAsync(bool checkTunnelList = false) { - return Task.Factory.StartNew(() => + return Task.Run(() => { var tunnel = CurrentTunnel; if (tunnel == null) return;