refactor(battle-node): name ComputeFrames routes as DispatchRoute
This commit is contained in:
@@ -2,6 +2,7 @@ using Microsoft.Extensions.Logging;
|
|||||||
using SVSim.BattleNode.Lifecycle;
|
using SVSim.BattleNode.Lifecycle;
|
||||||
using SVSim.BattleNode.Protocol;
|
using SVSim.BattleNode.Protocol;
|
||||||
using SVSim.BattleNode.Protocol.Bodies;
|
using SVSim.BattleNode.Protocol.Bodies;
|
||||||
|
using SVSim.BattleNode.Sessions.Dispatch;
|
||||||
using SVSim.BattleNode.Sessions.Participants;
|
using SVSim.BattleNode.Sessions.Participants;
|
||||||
|
|
||||||
namespace SVSim.BattleNode.Sessions;
|
namespace SVSim.BattleNode.Sessions;
|
||||||
@@ -134,10 +135,10 @@ public sealed class BattleSession
|
|||||||
/// <see cref="Phase"/>. Extracted so unit tests can drive the dispatch without
|
/// <see cref="Phase"/>. Extracted so unit tests can drive the dispatch without
|
||||||
/// standing up real participants.
|
/// standing up real participants.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal IReadOnlyList<(IBattleParticipant Target, MsgEnvelope Frame, bool NoStock)> ComputeFrames(
|
internal IReadOnlyList<DispatchRoute> ComputeFrames(
|
||||||
IBattleParticipant from, MsgEnvelope env)
|
IBattleParticipant from, MsgEnvelope env)
|
||||||
{
|
{
|
||||||
var result = new List<(IBattleParticipant, MsgEnvelope, bool)>();
|
var result = new List<DispatchRoute>();
|
||||||
var other = ReferenceEquals(from, A) ? B : A;
|
var other = ReferenceEquals(from, A) ? B : A;
|
||||||
var phaseFrom = from as IHasHandshakePhase;
|
var phaseFrom = from as IHasHandshakePhase;
|
||||||
|
|
||||||
@@ -148,7 +149,7 @@ public sealed class BattleSession
|
|||||||
switch (env.Uri)
|
switch (env.Uri)
|
||||||
{
|
{
|
||||||
case NetworkBattleUri.InitNetwork when phaseFrom?.Phase == BattleSessionPhase.AwaitingInitNetwork:
|
case NetworkBattleUri.InitNetwork when phaseFrom?.Phase == BattleSessionPhase.AwaitingInitNetwork:
|
||||||
result.Add((from, BuildAck(NetworkBattleUri.InitNetwork), true));
|
result.Add(new DispatchRoute(from, BuildAck(NetworkBattleUri.InitNetwork), true));
|
||||||
phaseFrom!.Phase = BattleSessionPhase.AwaitingInitBattle;
|
phaseFrom!.Phase = BattleSessionPhase.AwaitingInitBattle;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -180,7 +181,7 @@ public sealed class BattleSession
|
|||||||
case NetworkBattleUri.InitBattle
|
case NetworkBattleUri.InitBattle
|
||||||
when Type == BattleType.Bot && phaseFrom?.Phase == BattleSessionPhase.AwaitingInitBattle:
|
when Type == BattleType.Bot && phaseFrom?.Phase == BattleSessionPhase.AwaitingInitBattle:
|
||||||
// Ack only — NO Matched push.
|
// Ack only — NO Matched push.
|
||||||
result.Add((from, BuildAck(NetworkBattleUri.InitBattle), true));
|
result.Add(new DispatchRoute(from, BuildAck(NetworkBattleUri.InitBattle), true));
|
||||||
phaseFrom!.Phase = BattleSessionPhase.AwaitingLoaded;
|
phaseFrom!.Phase = BattleSessionPhase.AwaitingLoaded;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -199,7 +200,7 @@ public sealed class BattleSession
|
|||||||
// Judge to sender ONLY (not broadcast — there's no real other side).
|
// Judge to sender ONLY (not broadcast — there's no real other side).
|
||||||
// The client's JudgeOperation → ControlTurnStartPlayer flips back to
|
// The client's JudgeOperation → ControlTurnStartPlayer flips back to
|
||||||
// the local AI's turn after this Judge arrives.
|
// the local AI's turn after this Judge arrives.
|
||||||
result.Add((from, BuildJudgeBroadcast(), false));
|
result.Add(new DispatchRoute(from, BuildJudgeBroadcast(), false));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case NetworkBattleUri.InitBattle when phaseFrom?.Phase == BattleSessionPhase.AwaitingInitBattle:
|
case NetworkBattleUri.InitBattle when phaseFrom?.Phase == BattleSessionPhase.AwaitingInitBattle:
|
||||||
@@ -207,7 +208,7 @@ public sealed class BattleSession
|
|||||||
// selfInfo from from.Context and oppoInfo from other.Context (the scripted
|
// selfInfo from from.Context and oppoInfo from other.Context (the scripted
|
||||||
// bot's Context fixture preserves the prod-captured cosmetics that previously
|
// bot's Context fixture preserves the prod-captured cosmetics that previously
|
||||||
// lived in ScriptedProfiles).
|
// lived in ScriptedProfiles).
|
||||||
result.Add((from, ScriptedLifecycle.BuildMatched(
|
result.Add(new DispatchRoute(from, ScriptedLifecycle.BuildMatched(
|
||||||
from.Context, other.Context,
|
from.Context, other.Context,
|
||||||
from.ViewerId, other.ViewerId,
|
from.ViewerId, other.ViewerId,
|
||||||
BattleId, ScriptedProfiles.BattleSeed), false));
|
BattleId, ScriptedProfiles.BattleSeed), false));
|
||||||
@@ -222,9 +223,9 @@ public sealed class BattleSession
|
|||||||
// arm (its silent Loaded arm above wins the match). A per-battle coin-flip is a
|
// arm (its silent Loaded arm above wins the match). A per-battle coin-flip is a
|
||||||
// follow-up (see plan § Out of scope).
|
// follow-up (see plan § Out of scope).
|
||||||
var turnState = ReferenceEquals(from, A) ? 0 : 1;
|
var turnState = ReferenceEquals(from, A) ? 0 : 1;
|
||||||
result.Add((from, ScriptedLifecycle.BuildBattleStart(
|
result.Add(new DispatchRoute(from, ScriptedLifecycle.BuildBattleStart(
|
||||||
from.Context, other.Context, from.ViewerId, turnState), false));
|
from.Context, other.Context, from.ViewerId, turnState), false));
|
||||||
result.Add((from, ScriptedLifecycle.BuildDeal(), false));
|
result.Add(new DispatchRoute(from, ScriptedLifecycle.BuildDeal(), false));
|
||||||
phaseFrom!.Phase = BattleSessionPhase.AwaitingSwap;
|
phaseFrom!.Phase = BattleSessionPhase.AwaitingSwap;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -233,7 +234,7 @@ public sealed class BattleSession
|
|||||||
{
|
{
|
||||||
var hand = ScriptedLifecycle.ComputeHandAfterSwap(ExtractIdxList(env));
|
var hand = ScriptedLifecycle.ComputeHandAfterSwap(ExtractIdxList(env));
|
||||||
// SwapResponse is always immediate — it completes the sender's own mulligan UI.
|
// SwapResponse is always immediate — it completes the sender's own mulligan UI.
|
||||||
result.Add((from, ScriptedLifecycle.BuildSwapResponse(hand), false));
|
result.Add(new DispatchRoute(from, ScriptedLifecycle.BuildSwapResponse(hand), false));
|
||||||
_postSwapHands[from] = hand;
|
_postSwapHands[from] = hand;
|
||||||
phaseFrom!.Phase = BattleSessionPhase.AfterReady;
|
phaseFrom!.Phase = BattleSessionPhase.AfterReady;
|
||||||
|
|
||||||
@@ -251,7 +252,7 @@ public sealed class BattleSession
|
|||||||
var ready = opponent is IHasHandshakePhase && _postSwapHands.TryGetValue(opponent, out var oppoHand)
|
var ready = opponent is IHasHandshakePhase && _postSwapHands.TryGetValue(opponent, out var oppoHand)
|
||||||
? ScriptedLifecycle.BuildReady(_postSwapHands[p], oppoHand) // both hands known
|
? ScriptedLifecycle.BuildReady(_postSwapHands[p], oppoHand) // both hands known
|
||||||
: ScriptedLifecycle.BuildReady(_postSwapHands[p]); // non-interactive opponent
|
: ScriptedLifecycle.BuildReady(_postSwapHands[p]); // non-interactive opponent
|
||||||
result.Add((p, ready, false));
|
result.Add(new DispatchRoute(p, ready, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -264,14 +265,14 @@ public sealed class BattleSession
|
|||||||
{
|
{
|
||||||
var turnEndBroadcast = BuildTurnEndBroadcast();
|
var turnEndBroadcast = BuildTurnEndBroadcast();
|
||||||
var judgeBroadcast = BuildJudgeBroadcast();
|
var judgeBroadcast = BuildJudgeBroadcast();
|
||||||
result.Add((from, turnEndBroadcast, false));
|
result.Add(new DispatchRoute(from, turnEndBroadcast, false));
|
||||||
result.Add((other, turnEndBroadcast, false));
|
result.Add(new DispatchRoute(other, turnEndBroadcast, false));
|
||||||
result.Add((from, judgeBroadcast, false));
|
result.Add(new DispatchRoute(from, judgeBroadcast, false));
|
||||||
result.Add((other, judgeBroadcast, false));
|
result.Add(new DispatchRoute(other, judgeBroadcast, false));
|
||||||
}
|
}
|
||||||
else if (Type == BattleType.Scripted)
|
else if (Type == BattleType.Scripted)
|
||||||
{
|
{
|
||||||
result.Add((other, env, false));
|
result.Add(new DispatchRoute(other, env, false));
|
||||||
}
|
}
|
||||||
// Bot type: no-op (NoOpBot swallows; client handles its own turn end).
|
// Bot type: no-op (NoOpBot swallows; client handles its own turn end).
|
||||||
break;
|
break;
|
||||||
@@ -286,9 +287,9 @@ public sealed class BattleSession
|
|||||||
// this dispatch arm owns it. NoOpBotParticipant swallows. Phase → Terminal
|
// this dispatch arm owns it. NoOpBotParticipant swallows. Phase → Terminal
|
||||||
// so the RunAsync cascade doesn't synthesize a follow-up BattleFinish.
|
// so the RunAsync cascade doesn't synthesize a follow-up BattleFinish.
|
||||||
case NetworkBattleUri.TurnEndFinal when phaseFrom?.Phase == BattleSessionPhase.AfterReady:
|
case NetworkBattleUri.TurnEndFinal when phaseFrom?.Phase == BattleSessionPhase.AfterReady:
|
||||||
result.Add((other, env, false));
|
result.Add(new DispatchRoute(other, env, false));
|
||||||
result.Add((from, BuildBattleFinish(BattleResult.LifeWin), true));
|
result.Add(new DispatchRoute(from, BuildBattleFinish(BattleResult.LifeWin), true));
|
||||||
result.Add((other, BuildBattleFinish(BattleResult.LifeLose), true));
|
result.Add(new DispatchRoute(other, BuildBattleFinish(BattleResult.LifeLose), true));
|
||||||
Phase = BattleSessionPhase.Terminal;
|
Phase = BattleSessionPhase.Terminal;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -297,8 +298,8 @@ public sealed class BattleSession
|
|||||||
// proper retire codes. Bots swallow their push (no real-opponent state).
|
// proper retire codes. Bots swallow their push (no real-opponent state).
|
||||||
case NetworkBattleUri.Retire:
|
case NetworkBattleUri.Retire:
|
||||||
case NetworkBattleUri.Kill:
|
case NetworkBattleUri.Kill:
|
||||||
result.Add((from, BuildBattleFinish(BattleResult.RetireLose), true));
|
result.Add(new DispatchRoute(from, BuildBattleFinish(BattleResult.RetireLose), true));
|
||||||
result.Add((other, BuildBattleFinish(BattleResult.RetireWin), true));
|
result.Add(new DispatchRoute(other, BuildBattleFinish(BattleResult.RetireWin), true));
|
||||||
Phase = BattleSessionPhase.Terminal;
|
Phase = BattleSessionPhase.Terminal;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -317,7 +318,7 @@ public sealed class BattleSession
|
|||||||
case NetworkBattleUri.Judge when IsRealForwardableFromScripted(from, env):
|
case NetworkBattleUri.Judge when IsRealForwardableFromScripted(from, env):
|
||||||
// Generic forwarder for scripted-bot emissions. The Scripted bot's TurnStart,
|
// Generic forwarder for scripted-bot emissions. The Scripted bot's TurnStart,
|
||||||
// TurnEnd, and Judge are intended for the real participant.
|
// TurnEnd, and Judge are intended for the real participant.
|
||||||
result.Add((other, env, false));
|
result.Add(new DispatchRoute(other, env, false));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Gameplay-frame forwarding (post-AfterReady). Unified across types:
|
// Gameplay-frame forwarding (post-AfterReady). Unified across types:
|
||||||
@@ -331,7 +332,7 @@ public sealed class BattleSession
|
|||||||
case NetworkBattleUri.Echo when BothAfterReady():
|
case NetworkBattleUri.Echo when BothAfterReady():
|
||||||
case NetworkBattleUri.TurnEndActions when BothAfterReady():
|
case NetworkBattleUri.TurnEndActions when BothAfterReady():
|
||||||
case NetworkBattleUri.JudgeResult when BothAfterReady():
|
case NetworkBattleUri.JudgeResult when BothAfterReady():
|
||||||
result.Add((other, env, false));
|
result.Add(new DispatchRoute(other, env, false));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
8
SVSim.BattleNode/Sessions/Dispatch/DispatchRoute.cs
Normal file
8
SVSim.BattleNode/Sessions/Dispatch/DispatchRoute.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using SVSim.BattleNode.Protocol;
|
||||||
|
|
||||||
|
namespace SVSim.BattleNode.Sessions.Dispatch;
|
||||||
|
|
||||||
|
/// <summary>One routing decision: deliver <paramref name="Frame"/> to <paramref name="Target"/>.
|
||||||
|
/// Named form of the tuple <c>ComputeFrames</c> historically returned. <paramref name="NoStock"/>
|
||||||
|
/// true for control frames (BattleFinish, ack) — bypasses playSeq assignment + archive.</summary>
|
||||||
|
internal readonly record struct DispatchRoute(IBattleParticipant Target, MsgEnvelope Frame, bool NoStock);
|
||||||
Reference in New Issue
Block a user