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 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,
|
||||
ILogger<BattleSession> log)
|
||||
{
|
||||
@@ -132,6 +144,10 @@ public sealed class BattleSession
|
||||
internal IReadOnlyList<DispatchRoute> ComputeFrames(
|
||||
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 other = ReferenceEquals(from, A) ? B : A;
|
||||
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