fix: improve minigame reconnect flow

This commit is contained in:
ud18010
2026-07-29 19:30:50 +08:00
parent 6a5e534d00
commit 42c3874d82
21 changed files with 979 additions and 52 deletions
@@ -5,6 +5,7 @@ using System.Globalization;
using System.IO;
using UnityEngine;
using XGame.MiniGame;
using XWorld.Framework;
using XWorld.Framework.Protocol;
using UObject = UnityEngine.Object;
@@ -37,13 +38,19 @@ namespace XGame
private XWebSocket socket;
private MiniGameNetChannel lobbyNet;
private MiniGameHost gameHost;
private MiniGameStageIsolation stageIsolation;
private LobbyWorldController lobbyWorld;
private WorldChatModule worldChat;
private readonly List<GameInfoMsg> games = new List<GameInfoMsg>();
private readonly List<NetMessage> pendingGameMessages = new List<NetMessage>();
private readonly HashSet<int> canceledMatchRequestIds = new HashSet<int>();
private bool lobbyActive;
private bool openingLogin;
private bool openingLobby;
private bool openingWaiting;
private bool matchRequestActive;
private int nextMatchRequestId;
private int currentMatchRequestId;
private string matchMessage = string.Empty;
private string pendingGameId = string.Empty;
private int pendingGameVersion;
@@ -409,6 +416,7 @@ namespace XGame
socket = sock;
lobbyNet = new MiniGameNetChannel(sock);
lobbyNet.OnGameMessage = OnLobbyGameMessage;
lobbyNet.OnFrameworkFrame = OnLobbyFrameworkFrame;
lobbyActive = true;
SetLobbyStatus("Lobby connected.");
@@ -455,6 +463,9 @@ namespace XGame
private void OnMatchClicked(string gameId)
{
matchRequestActive = true;
currentMatchRequestId = ++nextMatchRequestId;
canceledMatchRequestIds.Remove(currentMatchRequestId);
matchMessage = "Matching...";
pendingGameId = string.Empty;
pendingGameVersion = 0;
@@ -468,7 +479,7 @@ namespace XGame
if (string.IsNullOrEmpty(gameId))
{
lobbyNet.SendFramework(FrameworkOpcode.RandomMatchRequest, Array.Empty<byte>());
lobbyNet.SendFramework(FrameworkOpcode.RandomMatchRequest, new MatchControlMsg { RequestId = currentMatchRequestId }.Encode());
}
else
{
@@ -483,7 +494,7 @@ namespace XGame
}
pendingGameId = gameId;
pendingGameVersion = version;
lobbyNet.SendFramework(FrameworkOpcode.MatchRequest, new MatchRequestMsg { GameId = gameId, Version = version }.Encode());
lobbyNet.SendFramework(FrameworkOpcode.MatchRequest, new MatchRequestMsg { GameId = gameId, Version = version, RequestId = currentMatchRequestId }.Encode());
}
}
@@ -530,6 +541,8 @@ namespace XGame
private void OnCloseWaiting()
{
CancelCurrentMatchRequest();
matchRequestActive = false;
matchMessage = string.Empty;
pendingGameId = string.Empty;
pendingGameVersion = 0;
@@ -537,6 +550,34 @@ namespace XGame
OpenLobby();
}
private void CancelPendingMatch()
=> CancelPendingMatch(currentMatchRequestId);
private void CancelCurrentMatchRequest()
{
if (currentMatchRequestId != 0)
{
canceledMatchRequestIds.Add(currentMatchRequestId);
}
CancelPendingMatch(currentMatchRequestId);
}
private void CancelPendingMatch(int requestId)
{
if (lobbyNet == null || !lobbyNet.IsConnected)
{
return;
}
lobbyNet.SendFramework(FrameworkOpcode.MatchCancel, new MatchControlMsg { RequestId = requestId }.Encode());
}
private bool IsCurrentMatchResponse(int requestId)
=> matchRequestActive && (requestId == 0 || requestId == currentMatchRequestId);
private bool IsCanceledMatchResponse(int requestId)
=> requestId != 0 && canceledMatchRequestIds.Contains(requestId);
private void OnLobbyFrameworkFrame(Frame frame)
{
switch ((FrameworkOpcode)frame.Opcode)
@@ -549,6 +590,16 @@ namespace XGame
break;
case FrameworkOpcode.MatchAssigned:
MatchAssignedMsg assigned = MatchAssignedMsg.Decode(frame.Payload);
if (IsCanceledMatchResponse(assigned.RequestId))
{
Debug.Log("[CSharpClientApp] ignore canceled match assigned req=" + assigned.RequestId);
break;
}
if (!IsCurrentMatchResponse(assigned.RequestId))
{
Debug.Log("[CSharpClientApp] ignore stale match assigned req=" + assigned.RequestId);
break;
}
pendingGameId = assigned.GameId;
pendingGameVersion = assigned.Version;
Debug.Log("[CSharpClientApp] match assigned " + pendingGameId + "@" + pendingGameVersion);
@@ -556,10 +607,31 @@ namespace XGame
break;
case FrameworkOpcode.MatchFound:
MatchFoundMsg found = MatchFoundMsg.Decode(frame.Payload);
if (IsCanceledMatchResponse(found.RequestId))
{
Debug.Log("[CSharpClientApp] ignore canceled match found req=" + found.RequestId);
CancelPendingMatch(found.RequestId);
break;
}
if (!matchRequestActive && !string.IsNullOrEmpty(found.GameId))
{
Debug.Log("[CSharpClientApp] resume game room=" + found.RoomId + " game=" + found.GameId + "@" + found.Version);
EnterAssignedGame(found.GameId, found.Version, found);
break;
}
if (!IsCurrentMatchResponse(found.RequestId))
{
Debug.Log("[CSharpClientApp] ignore stale match found req=" + found.RequestId);
if (found.RequestId != 0)
{
CancelPendingMatch(found.RequestId);
}
break;
}
Debug.Log("[CSharpClientApp] match found room=" + found.RoomId + " self=" + found.SelfPlayerId);
if (string.IsNullOrEmpty(pendingGameId))
{
SetWaitingStatus("Room ready, but no game was assigned.", true);
AbortMatchAndReturnLobby("Match expired, please retry.", found.RequestId);
break;
}
SetWaitingStatus("Room ready.", false);
@@ -567,6 +639,16 @@ namespace XGame
break;
case FrameworkOpcode.Error:
ErrorMsg err = ErrorMsg.Decode(frame.Payload);
if (IsCanceledMatchResponse(err.RequestId))
{
Debug.Log("[CSharpClientApp] ignore canceled match error req=" + err.RequestId);
break;
}
if (!IsCurrentMatchResponse(err.RequestId))
{
Debug.Log("[CSharpClientApp] ignore stale match error req=" + err.RequestId);
break;
}
pendingGameId = string.Empty;
pendingGameVersion = 0;
SetWaitingStatus("Server error " + err.Code + ": " + err.Message, true);
@@ -590,11 +672,28 @@ namespace XGame
}
}
private void OnLobbyGameMessage(NetMessage message)
{
if (gameHost != null)
{
gameHost.QueueInitialMessage(message);
return;
}
pendingGameMessages.Add(message);
if (pendingGameMessages.Count > 16)
{
pendingGameMessages.RemoveAt(0);
}
}
private void EnterAssignedGame(string gameId, int version, MatchFoundMsg found)
{
matchRequestActive = false;
pendingGameId = string.Empty;
pendingGameVersion = 0;
lobbyActive = false;
SuspendLobbyStage();
lobbyWorld?.SendLeave();
CloseWorldChat();
lobbyNet = null;
@@ -607,17 +706,73 @@ namespace XGame
}
gameHost.OnGameExited = () =>
{
ResumeLobbyStage();
matchMessage = "Returned to lobby.";
lobbyNet = new MiniGameNetChannel(socket);
lobbyNet.OnGameMessage = OnLobbyGameMessage;
lobbyNet.OnFrameworkFrame = OnLobbyFrameworkFrame;
lobbyActive = true;
OpenLobby();
JoinLobbyWorld();
RequestGameList();
};
gameHost.EnterGame(ResolvedCdnBase(), gameId, version, socket, false);
// 大厅链路 MatchFound 由大厅通道消费,宿主收不到,须显式注入房间信息(结算弹框判胜负用)
gameHost.SetMatchInfo(found.RoomId, found.SelfPlayerId);
gameHost.SetMatchInfo(found.RoomId, found.SelfPlayerId, found.Players);
gameHost.EnterGame(ResolvedCdnBase(), gameId, version, socket, false);
for (int i = 0; i < pendingGameMessages.Count; i++)
{
gameHost.QueueInitialMessage(pendingGameMessages[i]);
}
pendingGameMessages.Clear();
}
private void AbortMatchAndReturnLobby(string message, int requestId)
{
int cancelRequestId = requestId != 0 ? requestId : currentMatchRequestId;
if (cancelRequestId != 0)
{
canceledMatchRequestIds.Add(cancelRequestId);
}
CancelPendingMatch(cancelRequestId);
matchRequestActive = false;
pendingGameId = string.Empty;
pendingGameVersion = 0;
CloseWaiting();
OpenLobby();
SetLobbyStatus(message);
}
private MiniGameStageIsolation ResolveStageIsolation()
{
if (stageIsolation != null)
{
return stageIsolation;
}
stageIsolation = GetComponent<MiniGameStageIsolation>();
if (stageIsolation == null)
{
stageIsolation = gameObject.AddComponent<MiniGameStageIsolation>();
}
return stageIsolation;
}
private void SuspendLobbyStage()
{
MiniGameStageIsolation isolation = ResolveStageIsolation();
if (isolation != null)
{
isolation.SuspendLobbyForMiniGame();
}
}
private void ResumeLobbyStage()
{
MiniGameStageIsolation isolation = ResolveStageIsolation();
if (isolation != null)
{
isolation.ResumeLobbyAfterMiniGame();
}
}
private string ResolvedCdnBase()