Fixed ENetClient ConnectToHost can't return NetErrorInfo
Added CombatStateSynchronizer Timeout and Resend mechanism
This commit is contained in:
@@ -0,0 +1,31 @@
|
|||||||
|
using MegaCrit.Sts2.Core.Debug;
|
||||||
|
using MegaCrit.Sts2.Core.Logging;
|
||||||
|
|
||||||
|
namespace SlayTheSpire2.LAN.Multiplayer.Helpers
|
||||||
|
{
|
||||||
|
internal static class TaskGenericHelper
|
||||||
|
{
|
||||||
|
public static Task<T> RunSafely<T>(Task<T> task)
|
||||||
|
{
|
||||||
|
return LogTaskExceptions(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<T> LogTaskExceptions<T>(Task<T> task)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await task;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (ex is not TaskCanceledException)
|
||||||
|
{
|
||||||
|
Log.Error(ex.ToString());
|
||||||
|
SentryService.CaptureException(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
using HarmonyLib;
|
||||||
|
using MegaCrit.Sts2.Core.Context;
|
||||||
|
using MegaCrit.Sts2.Core.Helpers;
|
||||||
|
using MegaCrit.Sts2.Core.Multiplayer;
|
||||||
|
using MegaCrit.Sts2.Core.Multiplayer.Game;
|
||||||
|
using MegaCrit.Sts2.Core.Multiplayer.Game.Lobby;
|
||||||
|
using MegaCrit.Sts2.Core.Multiplayer.Messages.Game;
|
||||||
|
using MegaCrit.Sts2.Core.Runs;
|
||||||
|
using MegaCrit.Sts2.Core.Saves.Runs;
|
||||||
|
using Logger = MegaCrit.Sts2.Core.Logging.Logger;
|
||||||
|
|
||||||
|
namespace SlayTheSpire2.LAN.Multiplayer.Patchs
|
||||||
|
{
|
||||||
|
[HarmonyPatch(typeof(CombatStateSynchronizer), "WaitForSync")]
|
||||||
|
internal class CombatStateSynchronizerWaitForSyncPatch
|
||||||
|
{
|
||||||
|
private static bool Prefix(CombatStateSynchronizer __instance, Logger ____logger,
|
||||||
|
INetGameService ____netService, TaskCompletionSource? ____syncCompletionSource,
|
||||||
|
Dictionary<ulong, SerializablePlayer> ____syncData, RunState ____runState, RunLobby? ____runLobby,
|
||||||
|
SerializableRunRngSet? ____rngSet, SerializableRelicGrabBag? ____sharedRelicGrabBag, ref Task __result)
|
||||||
|
{
|
||||||
|
//Whether is LAN game was not checked, because the sync issue may also occur when connect via Steam
|
||||||
|
|
||||||
|
__result = TaskHelper.RunSafely(WaitForSync(__instance, ____logger, ____netService,
|
||||||
|
____syncCompletionSource, ____syncData, ____runState, ____runLobby, ____rngSet,
|
||||||
|
____sharedRelicGrabBag));
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task WaitForSync(CombatStateSynchronizer instance, Logger logger,
|
||||||
|
INetGameService netService, TaskCompletionSource? syncCompletionSource,
|
||||||
|
Dictionary<ulong, SerializablePlayer> syncData, RunState runState, RunLobby? runLobby,
|
||||||
|
SerializableRunRngSet? rngSet, SerializableRelicGrabBag? sharedRelicGrabBag)
|
||||||
|
{
|
||||||
|
logger.Debug("Waiting to receive all sync messages from all clients");
|
||||||
|
if (netService.Type == NetGameType.Singleplayer || instance.IsDisabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (syncCompletionSource == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("StartSync must be called before WaitForSync!");
|
||||||
|
}
|
||||||
|
|
||||||
|
var startTime = DateTime.Now;
|
||||||
|
var lastResendTick = DateTime.Now;
|
||||||
|
|
||||||
|
const int timeoutMs = 30;
|
||||||
|
const int resendIntervalMs = 5;
|
||||||
|
|
||||||
|
while (!syncCompletionSource.Task.IsCompleted)
|
||||||
|
{
|
||||||
|
if ((DateTime.Now - startTime).TotalMilliseconds > timeoutMs)
|
||||||
|
{
|
||||||
|
logger.Warn("Sync timeout, skipping waiting for players");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (netService.Type == NetGameType.Host &&
|
||||||
|
(DateTime.Now - lastResendTick).TotalMilliseconds > resendIntervalMs && rngSet != null &&
|
||||||
|
sharedRelicGrabBag != null)
|
||||||
|
{
|
||||||
|
logger.Debug("Resending rng sync message");
|
||||||
|
|
||||||
|
var message = new SyncRngMessage
|
||||||
|
{
|
||||||
|
rng = rngSet,
|
||||||
|
sharedRelicGrabBag = sharedRelicGrabBag
|
||||||
|
};
|
||||||
|
|
||||||
|
netService.SendMessage(message);
|
||||||
|
|
||||||
|
lastResendTick = DateTime.Now;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var syncDatum in syncData)
|
||||||
|
{
|
||||||
|
if (runLobby != null && !runLobby.ConnectedPlayerIds.Contains(syncDatum.Key))
|
||||||
|
{
|
||||||
|
logger.Debug($"Skipping sync for disconnected player {syncDatum.Key}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var player = runState.GetPlayer(syncDatum.Key);
|
||||||
|
if (!LocalContext.IsMe(player))
|
||||||
|
{
|
||||||
|
player?.SyncWithSerializedPlayer(syncDatum.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (netService.Type != NetGameType.Host)
|
||||||
|
{
|
||||||
|
if (rngSet != null)
|
||||||
|
{
|
||||||
|
runState.Rng.LoadFromSerializable(rngSet);
|
||||||
|
}
|
||||||
|
else if (runState.Players.Count > 1)
|
||||||
|
{
|
||||||
|
logger.Error(
|
||||||
|
"There are two or more players and we are a client, but we never received the RNG set!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sharedRelicGrabBag != null)
|
||||||
|
{
|
||||||
|
runState.SharedRelicGrabBag.LoadFromSerializable(sharedRelicGrabBag);
|
||||||
|
}
|
||||||
|
else if (runState.Players.Count > 1)
|
||||||
|
{
|
||||||
|
logger.Error(
|
||||||
|
"There are two or more players and we are a client, but we never received the shared relic grab bag!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
syncData.Clear();
|
||||||
|
var traverse = Traverse.Create(instance);
|
||||||
|
traverse.Field("_rngSet").SetValue(null);
|
||||||
|
traverse.Field("_sharedRelicGrabBag").SetValue(null);
|
||||||
|
traverse.Field("_syncCompletionSource").SetValue(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
using Godot;
|
using Godot;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
using MegaCrit.Sts2.Core.Entities.Multiplayer;
|
using MegaCrit.Sts2.Core.Entities.Multiplayer;
|
||||||
using MegaCrit.Sts2.Core.Helpers;
|
|
||||||
using MegaCrit.Sts2.Core.Multiplayer.Transport;
|
using MegaCrit.Sts2.Core.Multiplayer.Transport;
|
||||||
using MegaCrit.Sts2.Core.Multiplayer.Transport.ENet;
|
using MegaCrit.Sts2.Core.Multiplayer.Transport.ENet;
|
||||||
using SlayTheSpire2.LAN.Multiplayer.Helpers;
|
using SlayTheSpire2.LAN.Multiplayer.Helpers;
|
||||||
@@ -23,9 +22,10 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.ENet
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static bool Prefix(ENetClient __instance, ulong netId, string ip, ushort port,
|
private static bool Prefix(ENetClient __instance, ulong netId, string ip, ushort port,
|
||||||
CancellationToken cancelToken, Logger ____logger, INetClientHandler ____handler, ref Task __result)
|
CancellationToken cancelToken, Logger ____logger, INetClientHandler ____handler,
|
||||||
|
ref Task<NetErrorInfo?> __result)
|
||||||
{
|
{
|
||||||
__result = TaskHelper.RunSafely(ConnectToHost(__instance, ____logger, ____handler, netId, ip, port,
|
__result = TaskGenericHelper.RunSafely(ConnectToHost(__instance, ____logger, ____handler, netId, ip, port,
|
||||||
cancelToken));
|
cancelToken));
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using MegaCrit.Sts2.Core.Multiplayer.Connection;
|
|||||||
using MegaCrit.Sts2.Core.Multiplayer.Game;
|
using MegaCrit.Sts2.Core.Multiplayer.Game;
|
||||||
using MegaCrit.Sts2.Core.Multiplayer.Messages.Lobby;
|
using MegaCrit.Sts2.Core.Multiplayer.Messages.Lobby;
|
||||||
using MegaCrit.Sts2.Core.Platform;
|
using MegaCrit.Sts2.Core.Platform;
|
||||||
|
using SlayTheSpire2.LAN.Multiplayer.Helpers;
|
||||||
using SlayTheSpire2.LAN.Multiplayer.Models;
|
using SlayTheSpire2.LAN.Multiplayer.Models;
|
||||||
using SlayTheSpire2.LAN.Multiplayer.Services;
|
using SlayTheSpire2.LAN.Multiplayer.Services;
|
||||||
|
|
||||||
@@ -17,7 +18,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs
|
|||||||
{
|
{
|
||||||
private static void Postfix(JoinFlow __instance, ref Task<ClientLobbyJoinResponseMessage> __result)
|
private static void Postfix(JoinFlow __instance, ref Task<ClientLobbyJoinResponseMessage> __result)
|
||||||
{
|
{
|
||||||
__result = AttemptJoin(__instance, __result);
|
__result = TaskGenericHelper.RunSafely(AttemptJoin(__instance, __result));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<ClientLobbyJoinResponseMessage> AttemptJoin(JoinFlow joinFlow,
|
private static async Task<ClientLobbyJoinResponseMessage> AttemptJoin(JoinFlow joinFlow,
|
||||||
@@ -44,7 +45,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs
|
|||||||
{
|
{
|
||||||
private static void Postfix(JoinFlow __instance, ref Task<ClientLoadJoinResponseMessage> __result)
|
private static void Postfix(JoinFlow __instance, ref Task<ClientLoadJoinResponseMessage> __result)
|
||||||
{
|
{
|
||||||
__result = AttemptLoadJoin(__instance, __result);
|
__result = TaskGenericHelper.RunSafely(AttemptLoadJoin(__instance, __result));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<ClientLoadJoinResponseMessage> AttemptLoadJoin(JoinFlow joinFlow,
|
private static async Task<ClientLoadJoinResponseMessage> AttemptLoadJoin(JoinFlow joinFlow,
|
||||||
@@ -71,7 +72,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs
|
|||||||
{
|
{
|
||||||
private static void Postfix(JoinFlow __instance, ref Task<ClientRejoinResponseMessage> __result)
|
private static void Postfix(JoinFlow __instance, ref Task<ClientRejoinResponseMessage> __result)
|
||||||
{
|
{
|
||||||
__result = AttemptRejoin(__instance, __result);
|
__result = TaskGenericHelper.RunSafely(AttemptRejoin(__instance, __result));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<ClientRejoinResponseMessage> AttemptRejoin(JoinFlow joinFlow,
|
private static async Task<ClientRejoinResponseMessage> AttemptRejoin(JoinFlow joinFlow,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using MegaCrit.Sts2.Core.Nodes.Screens.CustomRun;
|
|||||||
using MegaCrit.Sts2.Core.Nodes.Screens.DailyRun;
|
using MegaCrit.Sts2.Core.Nodes.Screens.DailyRun;
|
||||||
using MegaCrit.Sts2.Core.Nodes.Screens.MainMenu;
|
using MegaCrit.Sts2.Core.Nodes.Screens.MainMenu;
|
||||||
using MegaCrit.Sts2.Core.Platform;
|
using MegaCrit.Sts2.Core.Platform;
|
||||||
|
using SlayTheSpire2.LAN.Multiplayer.Helpers;
|
||||||
using SlayTheSpire2.LAN.Multiplayer.Services;
|
using SlayTheSpire2.LAN.Multiplayer.Services;
|
||||||
|
|
||||||
// ReSharper disable UnusedMember.Global
|
// ReSharper disable UnusedMember.Global
|
||||||
@@ -97,7 +98,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
|
|||||||
{
|
{
|
||||||
if (____lobby.NetService.Platform == PlatformType.None)
|
if (____lobby.NetService.Platform == PlatformType.None)
|
||||||
{
|
{
|
||||||
__result = RunScreenService.ShouldAllowRunToBegin(____lobby);
|
__result = TaskGenericHelper.RunSafely(RunScreenService.ShouldAllowRunToBegin(____lobby));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +113,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs.Screens
|
|||||||
{
|
{
|
||||||
if (____runLobby.NetService.Platform == PlatformType.None)
|
if (____runLobby.NetService.Platform == PlatformType.None)
|
||||||
{
|
{
|
||||||
__result = RunScreenService.ShouldAllowRunToBegin(____runLobby);
|
__result = TaskGenericHelper.RunSafely(RunScreenService.ShouldAllowRunToBegin(____runLobby));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user