refactor(battle-node): add frame-handler contract, context, and empty registry shim
This commit is contained in:
@@ -30,6 +30,18 @@ public sealed class BattleSession
|
|||||||
public IBattleParticipant B { get; }
|
public IBattleParticipant B { get; }
|
||||||
public BattleSessionPhase Phase => _state.SessionPhase;
|
public BattleSessionPhase Phase => _state.SessionPhase;
|
||||||
|
|
||||||
|
// Per-URI handlers consulted before the legacy switch. Populated incrementally (Tasks 5-14);
|
||||||
|
// each registered URI is served by its handler and its legacy switch arm goes dead.
|
||||||
|
private static readonly IReadOnlyDictionary<NetworkBattleUri, IFrameHandler> Handlers =
|
||||||
|
new Dictionary<NetworkBattleUri, IFrameHandler>();
|
||||||
|
|
||||||
|
private FrameDispatchContext BuildContext(IBattleParticipant from, MsgEnvelope env) =>
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
A = A, B = B, From = from, Other = ReferenceEquals(from, A) ? B : A,
|
||||||
|
Env = env, Type = Type, BattleId = BattleId, State = _state,
|
||||||
|
};
|
||||||
|
|
||||||
public BattleSession(string battleId, BattleType type, IBattleParticipant a, IBattleParticipant b,
|
public BattleSession(string battleId, BattleType type, IBattleParticipant a, IBattleParticipant b,
|
||||||
ILogger<BattleSession> log)
|
ILogger<BattleSession> log)
|
||||||
{
|
{
|
||||||
@@ -132,6 +144,10 @@ public sealed class BattleSession
|
|||||||
internal IReadOnlyList<DispatchRoute> ComputeFrames(
|
internal IReadOnlyList<DispatchRoute> ComputeFrames(
|
||||||
IBattleParticipant from, MsgEnvelope env)
|
IBattleParticipant from, MsgEnvelope env)
|
||||||
{
|
{
|
||||||
|
if (Handlers.TryGetValue(env.Uri, out var handler))
|
||||||
|
return handler.Handle(BuildContext(from, env));
|
||||||
|
|
||||||
|
// --- legacy switch (shrinking; deleted in the final task) ---
|
||||||
var result = new List<DispatchRoute>();
|
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;
|
||||||
|
|||||||
41
SVSim.BattleNode/Sessions/Dispatch/FrameDispatchContext.cs
Normal file
41
SVSim.BattleNode/Sessions/Dispatch/FrameDispatchContext.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
using SVSim.BattleNode.Lifecycle;
|
||||||
|
using SVSim.BattleNode.Protocol;
|
||||||
|
using SVSim.BattleNode.Sessions.Participants; // IHasHandshakePhase
|
||||||
|
|
||||||
|
namespace SVSim.BattleNode.Sessions.Dispatch;
|
||||||
|
|
||||||
|
/// <summary>Everything a handler reads or mutates for one inbound frame. <see cref="A"/>/<see cref="B"/>
|
||||||
|
/// are the session's positional participants (preserved so handlers that iterate participants in a
|
||||||
|
/// stable order — e.g. the mulligan barrier — match the legacy switch byte-for-byte). <see cref="From"/>
|
||||||
|
/// is the sender; <see cref="Other"/> is the non-sender.</summary>
|
||||||
|
internal sealed class FrameDispatchContext
|
||||||
|
{
|
||||||
|
internal required IBattleParticipant A { get; init; }
|
||||||
|
internal required IBattleParticipant B { get; init; }
|
||||||
|
internal required IBattleParticipant From { get; init; }
|
||||||
|
internal required IBattleParticipant Other { get; init; }
|
||||||
|
internal required MsgEnvelope Env { get; init; }
|
||||||
|
internal required BattleType Type { get; init; }
|
||||||
|
internal required string BattleId { get; init; }
|
||||||
|
internal required BattleSessionState State { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Sender's per-side handshake phase (null for a non-IHasHandshakePhase participant,
|
||||||
|
/// e.g. NoOpBot or the dispatch-test scripted-bot stub). Setting it advances the sender.</summary>
|
||||||
|
internal BattleSessionPhase? SenderPhase
|
||||||
|
{
|
||||||
|
get => (From as IHasHandshakePhase)?.Phase;
|
||||||
|
set { if (From is IHasHandshakePhase p && value is { } v) p.Phase = v; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Both participants have completed the handshake. Reads A/B (not From/Other) so the
|
||||||
|
/// result is identical regardless of which side sent the frame — matches legacy BothAfterReady.</summary>
|
||||||
|
internal bool BothAfterReady() =>
|
||||||
|
(A as IHasHandshakePhase)?.Phase == BattleSessionPhase.AfterReady &&
|
||||||
|
(B as IHasHandshakePhase)?.Phase == BattleSessionPhase.AfterReady;
|
||||||
|
|
||||||
|
/// <summary>True for any participant carrying the synthetic opponent viewer id — i.e. a
|
||||||
|
/// <c>ScriptedBotParticipant</c> OR a <c>NoOpBotParticipant</c>. Callers that must exclude Bot
|
||||||
|
/// mode rely on a preceding <c>Type == BattleType.Bot</c> guard. Mirrors the legacy
|
||||||
|
/// IsRealForwardableFromScripted guard.</summary>
|
||||||
|
internal bool IsScriptedBot(IBattleParticipant p) => p.ViewerId == ScriptedLifecycle.FakeOpponentViewerId;
|
||||||
|
}
|
||||||
10
SVSim.BattleNode/Sessions/Dispatch/IFrameHandler.cs
Normal file
10
SVSim.BattleNode/Sessions/Dispatch/IFrameHandler.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace SVSim.BattleNode.Sessions.Dispatch;
|
||||||
|
|
||||||
|
/// <summary>Handles one (or more) inbound URI(s). Pure: returns the routes to dispatch and may
|
||||||
|
/// mutate <see cref="FrameDispatchContext.State"/> / advance <see cref="FrameDispatchContext.SenderPhase"/>,
|
||||||
|
/// but does not touch the wire. Stateless singletons live in BattleSession's registry; a single
|
||||||
|
/// handler may be registered under multiple URIs (e.g. Retire/Kill).</summary>
|
||||||
|
internal interface IFrameHandler
|
||||||
|
{
|
||||||
|
IReadOnlyList<DispatchRoute> Handle(FrameDispatchContext ctx);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user