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
+57
View File
@@ -140,6 +140,42 @@ namespace XWorld.Server.Gateway.Tests
Assert.Contains(Frames(c1b), f => f.Channel == Channel.Game);
}
[Fact]
public void Reconnect_ReplaysMatchFoundAndLatestGameSnapshot()
{
var loop = NewLoop(reconnectWindow: 100);
var c1 = new FakeConnection(1);
var c2 = new FakeConnection(2);
loop.Submit(new ConnectCommand { PlayerId = 1, Connection = c1 });
loop.Submit(new ConnectCommand { PlayerId = 2, Connection = c2 });
loop.Submit(new FrameCommand { PlayerId = 1, Frame = FrameCodec.Decode(JoinFrame("rps", 1)) });
loop.Submit(new FrameCommand { PlayerId = 2, Frame = FrameCodec.Decode(JoinFrame("rps", 1)) });
loop.DrainAndTick(1f);
loop.Submit(new DisconnectCommand { PlayerId = 1, Connection = c1 });
loop.DrainAndTick(1f);
var c1b = new FakeConnection(1);
loop.Submit(new ConnectCommand { PlayerId = 1, Connection = c1b });
loop.DrainAndTick(0f);
Frame resumeFrame = Frames(c1b).Find(f =>
f.Channel == Channel.Framework && f.Opcode == (ushort)FrameworkOpcode.MatchFound);
Assert.NotEqual(default, resumeFrame);
MatchFoundMsg resume = MatchFoundMsg.Decode(resumeFrame.Payload);
Assert.Equal("rps", resume.GameId);
Assert.Equal(1, resume.Version);
Assert.Equal(1, resume.SelfPlayerId);
Assert.Equal(2, resume.Players.Count);
Assert.Contains(Frames(c1b), f => f.Channel == Channel.Game);
}
[Fact]
public void DefaultReconnectWindow_IsThreeMinutesAtDefaultTickRate()
{
Assert.Equal(1800, ServerLoop.DefaultReconnectWindowTicks);
}
[Fact]
public void ExpiredSession_IsSwept()
{
@@ -173,6 +209,27 @@ namespace XWorld.Server.Gateway.Tests
Assert.Contains(Frames(c1), f => f.Channel == Channel.Framework && f.Opcode == (ushort)FrameworkOpcode.Error);
}
[Fact]
public void MatchCancel_WhileQueued_CancelsQueuedMatchAndAllowsPlayerToMatchAgain()
{
var loop = NewLoop();
var c = new FakeConnection(1);
loop.Submit(new ConnectCommand { PlayerId = 1, Connection = c });
loop.Submit(new FrameCommand { PlayerId = 1, Frame = FrameCodec.Decode(JoinFrame("rps", 1)) });
loop.DrainAndTick(1f);
c.Sent.Clear();
loop.Submit(new FrameCommand { PlayerId = 1, Frame = new Frame(Channel.Framework, (ushort)FrameworkOpcode.MatchCancel, System.Array.Empty<byte>()) });
loop.Submit(new FrameCommand { PlayerId = 1, Frame = new Frame(Channel.Framework, (ushort)FrameworkOpcode.RandomMatchRequest, System.Array.Empty<byte>()) });
loop.DrainAndTick(1f);
Assert.Contains(Frames(c), f => f.Channel == Channel.Framework && f.Opcode == (ushort)FrameworkOpcode.MatchAssigned);
Assert.DoesNotContain(Frames(c), f =>
f.Channel == Channel.Framework &&
f.Opcode == (ushort)FrameworkOpcode.Error &&
ErrorMsg.Decode(f.Payload).Message == "already in room or queue");
}
[Fact]
public void GameFrame_WithNoRoom_IsIgnoredSafely()
{