process TODO.md step 9
This commit is contained in:
parent
101694a3b0
commit
1b1598dbb7
|
|
@ -0,0 +1,110 @@
|
||||||
|
using System;
|
||||||
|
using Network.NetworkHost;
|
||||||
|
using Network.NetworkTransport;
|
||||||
|
|
||||||
|
namespace Network.NetworkApplication
|
||||||
|
{
|
||||||
|
public static class NetworkIntegrationFactory
|
||||||
|
{
|
||||||
|
public static SharedNetworkRuntime CreateClientRuntime(
|
||||||
|
string serverIp,
|
||||||
|
int reliablePort,
|
||||||
|
INetworkMessageDispatcher dispatcher,
|
||||||
|
int? syncPort = null,
|
||||||
|
SessionReconnectPolicy reconnectPolicy = null,
|
||||||
|
Func<DateTimeOffset> utcNowProvider = null,
|
||||||
|
IMessageDeliveryPolicyResolver deliveryPolicyResolver = null,
|
||||||
|
SyncSequenceTracker syncSequenceTracker = null,
|
||||||
|
ClockSyncState clockSync = null,
|
||||||
|
Func<string, int, ITransport> transportFactory = null)
|
||||||
|
{
|
||||||
|
if (dispatcher == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(dispatcher));
|
||||||
|
}
|
||||||
|
|
||||||
|
ValidateDualPortConfiguration(reliablePort, syncPort);
|
||||||
|
|
||||||
|
transportFactory ??= static (ip, port) => new KcpTransport(ip, port);
|
||||||
|
|
||||||
|
var reliableTransport = transportFactory(serverIp, reliablePort)
|
||||||
|
?? throw new InvalidOperationException("Reliable transport factory returned null.");
|
||||||
|
var syncTransport = syncPort.HasValue
|
||||||
|
? transportFactory(serverIp, syncPort.Value)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (syncPort.HasValue && syncTransport == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Sync transport factory returned null.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SharedNetworkRuntime(
|
||||||
|
reliableTransport,
|
||||||
|
dispatcher,
|
||||||
|
reconnectPolicy,
|
||||||
|
utcNowProvider,
|
||||||
|
syncTransport,
|
||||||
|
deliveryPolicyResolver,
|
||||||
|
syncSequenceTracker,
|
||||||
|
clockSync);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ServerNetworkHost CreateServerHost(
|
||||||
|
int reliablePort,
|
||||||
|
int? syncPort = null,
|
||||||
|
INetworkMessageDispatcher dispatcher = null,
|
||||||
|
SessionReconnectPolicy reconnectPolicy = null,
|
||||||
|
Func<DateTimeOffset> utcNowProvider = null,
|
||||||
|
IMessageDeliveryPolicyResolver deliveryPolicyResolver = null,
|
||||||
|
SyncSequenceTracker syncSequenceTracker = null,
|
||||||
|
Func<int, ITransport> transportFactory = null)
|
||||||
|
{
|
||||||
|
ValidateDualPortConfiguration(reliablePort, syncPort);
|
||||||
|
|
||||||
|
transportFactory ??= static port => new KcpTransport(port);
|
||||||
|
|
||||||
|
var reliableTransport = transportFactory(reliablePort)
|
||||||
|
?? throw new InvalidOperationException("Reliable transport factory returned null.");
|
||||||
|
var syncTransport = syncPort.HasValue
|
||||||
|
? transportFactory(syncPort.Value)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (syncPort.HasValue && syncTransport == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Sync transport factory returned null.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ServerNetworkHost(
|
||||||
|
reliableTransport,
|
||||||
|
dispatcher,
|
||||||
|
reconnectPolicy,
|
||||||
|
utcNowProvider,
|
||||||
|
syncTransport,
|
||||||
|
deliveryPolicyResolver,
|
||||||
|
syncSequenceTracker);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ValidateDualPortConfiguration(int reliablePort, int? syncPort)
|
||||||
|
{
|
||||||
|
if (reliablePort <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(reliablePort), "Reliable port must be positive.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!syncPort.HasValue)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (syncPort.Value <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(syncPort), "Sync port must be positive.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (syncPort.Value == reliablePort)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Sync port must differ from reliable port.", nameof(syncPort));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: bfefd18b5face1d4496541e42b3a011b
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -3,13 +3,15 @@ using System.Net;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Network.Defines;
|
using Network.Defines;
|
||||||
using Network.NetworkApplication;
|
using Network.NetworkApplication;
|
||||||
using Network.NetworkTransport;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using Vector3 = UnityEngine.Vector3;
|
using Vector3 = UnityEngine.Vector3;
|
||||||
|
|
||||||
public class NetworkManager : MonoBehaviour
|
public class NetworkManager : MonoBehaviour
|
||||||
{
|
{
|
||||||
private const int MaxNetworkMessagesPerFrame = 32;
|
private const int MaxNetworkMessagesPerFrame = 32;
|
||||||
|
private const string DefaultServerIp = "127.0.0.1";
|
||||||
|
private const int DefaultReliablePort = 8080;
|
||||||
|
private const int DefaultSyncPort = 8081;
|
||||||
|
|
||||||
public static NetworkManager Instance;
|
public static NetworkManager Instance;
|
||||||
private SharedNetworkRuntime _networkRuntime;
|
private SharedNetworkRuntime _networkRuntime;
|
||||||
|
|
@ -18,6 +20,9 @@ public class NetworkManager : MonoBehaviour
|
||||||
private Task _networkDrainTask = Task.CompletedTask;
|
private Task _networkDrainTask = Task.CompletedTask;
|
||||||
[SerializeField] private GameObject _wrongWindow;
|
[SerializeField] private GameObject _wrongWindow;
|
||||||
[SerializeField] private bool _enableNetworkDiagnosticsOverlay = true;
|
[SerializeField] private bool _enableNetworkDiagnosticsOverlay = true;
|
||||||
|
[SerializeField] private string _serverIp = DefaultServerIp;
|
||||||
|
[SerializeField] private int _reliablePort = DefaultReliablePort;
|
||||||
|
[SerializeField] private int _syncPort = DefaultSyncPort;
|
||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
|
|
@ -28,9 +33,13 @@ public class NetworkManager : MonoBehaviour
|
||||||
|
|
||||||
private IEnumerator InitNetwork()
|
private IEnumerator InitNetwork()
|
||||||
{
|
{
|
||||||
var transport = new KcpTransport("127.0.0.1", 8080);
|
|
||||||
var dispatcher = new MainThreadNetworkDispatcher();
|
var dispatcher = new MainThreadNetworkDispatcher();
|
||||||
_networkRuntime = new SharedNetworkRuntime(transport, dispatcher);
|
int? syncPort = _syncPort > 0 ? _syncPort : null;
|
||||||
|
_networkRuntime = NetworkIntegrationFactory.CreateClientRuntime(
|
||||||
|
_serverIp,
|
||||||
|
_reliablePort,
|
||||||
|
dispatcher,
|
||||||
|
syncPort: syncPort);
|
||||||
_networkRuntime.LifecycleChanged += HandleLifecycleChanged;
|
_networkRuntime.LifecycleChanged += HandleLifecycleChanged;
|
||||||
|
|
||||||
var startTask = _networkRuntime.StartAsync();
|
var startTask = _networkRuntime.StartAsync();
|
||||||
|
|
@ -227,4 +236,4 @@ public class NetworkManager : MonoBehaviour
|
||||||
_networkRuntime.MessageManager.SendMessage(request, MessageType.LogoutRequest);
|
_networkRuntime.MessageManager.SendMessage(request, MessageType.LogoutRequest);
|
||||||
Debug.Log($"Sent logout request to player {playerId}");
|
Debug.Log($"Sent logout request to player {playerId}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Google.Protobuf;
|
using Google.Protobuf;
|
||||||
|
|
@ -141,6 +142,89 @@ namespace Tests.EditMode.Network
|
||||||
Assert.That(session.SessionManager.State, Is.EqualTo(ConnectionState.TransportConnected));
|
Assert.That(session.SessionManager.State, Is.EqualTo(ConnectionState.TransportConnected));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void NetworkIntegrationFactory_CreateClientRuntime_WithSyncPort_UsesDistinctTransportsPerLane()
|
||||||
|
{
|
||||||
|
var createdTransports = new Dictionary<int, FakeTransport>();
|
||||||
|
var runtime = NetworkIntegrationFactory.CreateClientRuntime(
|
||||||
|
"127.0.0.1",
|
||||||
|
8080,
|
||||||
|
new ImmediateNetworkMessageDispatcher(),
|
||||||
|
syncPort: 8081,
|
||||||
|
transportFactory: (serverIp, port) =>
|
||||||
|
{
|
||||||
|
var transport = new FakeTransport();
|
||||||
|
createdTransports.Add(port, transport);
|
||||||
|
return transport;
|
||||||
|
});
|
||||||
|
var moveInput = new MoveInput
|
||||||
|
{
|
||||||
|
PlayerId = "shared-player",
|
||||||
|
Tick = 77,
|
||||||
|
MoveX = 1f
|
||||||
|
};
|
||||||
|
|
||||||
|
runtime.MessageManager.SendMessage(moveInput, MessageType.MoveInput);
|
||||||
|
runtime.MessageManager.SendMessage(new Heartbeat(), MessageType.Heartbeat);
|
||||||
|
|
||||||
|
Assert.That(createdTransports.Keys, Is.EquivalentTo(new[] { 8080, 8081 }));
|
||||||
|
Assert.That(runtime.Transport, Is.SameAs(createdTransports[8080]));
|
||||||
|
Assert.That(runtime.SyncTransport, Is.SameAs(createdTransports[8081]));
|
||||||
|
Assert.That(createdTransports[8080].SendCallCount, Is.EqualTo(1));
|
||||||
|
Assert.That(createdTransports[8081].SendCallCount, Is.EqualTo(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void NetworkIntegrationFactory_CreateServerHost_WithSyncPort_UsesDistinctTransportsPerLane()
|
||||||
|
{
|
||||||
|
var createdTransports = new Dictionary<int, FakeTransport>();
|
||||||
|
var host = NetworkIntegrationFactory.CreateServerHost(
|
||||||
|
9000,
|
||||||
|
syncPort: 9001,
|
||||||
|
transportFactory: port =>
|
||||||
|
{
|
||||||
|
var transport = new FakeTransport();
|
||||||
|
createdTransports.Add(port, transport);
|
||||||
|
return transport;
|
||||||
|
});
|
||||||
|
var moveInput = new MoveInput
|
||||||
|
{
|
||||||
|
PlayerId = "server-player",
|
||||||
|
Tick = 88,
|
||||||
|
MoveY = 1f
|
||||||
|
};
|
||||||
|
|
||||||
|
host.MessageManager.SendMessage(moveInput, MessageType.MoveInput);
|
||||||
|
host.MessageManager.SendMessage(new Heartbeat(), MessageType.Heartbeat);
|
||||||
|
|
||||||
|
Assert.That(createdTransports.Keys, Is.EquivalentTo(new[] { 9000, 9001 }));
|
||||||
|
Assert.That(host.Transport, Is.SameAs(createdTransports[9000]));
|
||||||
|
Assert.That(host.SyncTransport, Is.SameAs(createdTransports[9001]));
|
||||||
|
Assert.That(createdTransports[9000].SendCallCount, Is.EqualTo(1));
|
||||||
|
Assert.That(createdTransports[9001].SendCallCount, Is.EqualTo(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void NetworkIntegrationFactory_CreateServerHost_WithoutSyncPort_PreservesSingleTransportFallback()
|
||||||
|
{
|
||||||
|
var reliableTransport = new FakeTransport();
|
||||||
|
var host = NetworkIntegrationFactory.CreateServerHost(
|
||||||
|
9000,
|
||||||
|
transportFactory: _ => reliableTransport);
|
||||||
|
var moveInput = new MoveInput
|
||||||
|
{
|
||||||
|
PlayerId = "fallback-player",
|
||||||
|
Tick = 99,
|
||||||
|
MoveX = -1f
|
||||||
|
};
|
||||||
|
|
||||||
|
host.MessageManager.SendMessage(moveInput, MessageType.MoveInput);
|
||||||
|
|
||||||
|
Assert.That(host.Transport, Is.SameAs(reliableTransport));
|
||||||
|
Assert.That(host.SyncTransport, Is.Null);
|
||||||
|
Assert.That(reliableTransport.SendCallCount, Is.EqualTo(1));
|
||||||
|
}
|
||||||
|
|
||||||
private static byte[] BuildEnvelope(MessageType type, IMessage payload)
|
private static byte[] BuildEnvelope(MessageType type, IMessage payload)
|
||||||
{
|
{
|
||||||
return new Envelope
|
return new Envelope
|
||||||
|
|
|
||||||
12
TODO.md
12
TODO.md
|
|
@ -117,15 +117,15 @@ Acceptance:
|
||||||
|
|
||||||
### 9. Wire Dual Transports In The Integration Layer
|
### 9. Wire Dual Transports In The Integration Layer
|
||||||
|
|
||||||
- [ ] Update the client integration entry point, likely [`Assets/Scripts/NetworkManager.cs`](D:/Learn/GameLearn/UnityProjects/NetworkFW/Assets/Scripts/NetworkManager.cs)
|
- [x] Update the client integration entry point, likely [`Assets/Scripts/NetworkManager.cs`](D:/Learn/GameLearn/UnityProjects/NetworkFW/Assets/Scripts/NetworkManager.cs)
|
||||||
- [ ] Update the server startup integration point
|
- [x] Update the server startup integration point
|
||||||
- [ ] Instantiate one reliable transport and one sync transport
|
- [x] Instantiate one reliable transport and one sync transport
|
||||||
- [ ] Ensure runtime construction uses both transports instead of a single shared instance
|
- [x] Ensure runtime construction uses both transports instead of a single shared instance
|
||||||
|
|
||||||
Acceptance:
|
Acceptance:
|
||||||
|
|
||||||
- [ ] Runtime uses logical dual-lane routing backed by two transport instances
|
- [x] Runtime uses logical dual-lane routing backed by two transport instances
|
||||||
- [ ] Logging or tests confirm movement/state traffic and reliable event traffic are separated
|
- [x] Logging or tests confirm movement/state traffic and reliable event traffic are separated
|
||||||
|
|
||||||
### 10. Build And Test
|
### 10. Build And Test
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
schema: spec-driven
|
||||||
|
created: 2026-03-28
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
## Context
|
||||||
|
|
||||||
|
The shared networking layer already separates reliable messaging from sync-heavy traffic conceptually, but the runtime integration path still tends to compose a single transport dependency and leaves lane selection to host-specific setup. This makes the dual-transport design incomplete: shared services can express sync intent, yet composition can still collapse both lanes into one implicit path. The repository also requires preserving the existing client single-session flow and avoiding Unity-specific dependencies in shared networking code.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:**
|
||||||
|
- Allow integration-layer composition to pass both reliable and sync transports into shared networking services.
|
||||||
|
- Keep the single-transport path valid by falling back to the reliable transport when no dedicated sync transport is configured.
|
||||||
|
- Centralize lane selection inside shared networking/session code so client and server hosts follow the same wiring rules.
|
||||||
|
- Add regression coverage for both client single-session and server multi-session composition paths.
|
||||||
|
|
||||||
|
**Non-Goals:**
|
||||||
|
- Redesign transport implementations or message schemas.
|
||||||
|
- Introduce Unity-only adapters into `Assets/Scripts/Network/`.
|
||||||
|
- Change message delivery policy beyond what is required to connect existing sync traffic to the proper transport.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
### Decision: Treat sync transport as an optional secondary dependency
|
||||||
|
The integration layer will accept a primary reliable transport and an optional sync transport. Shared services will retain a deterministic fallback to the primary transport when the secondary dependency is absent.
|
||||||
|
|
||||||
|
This preserves existing callers and lets the change land without forcing all hosts to upgrade in one step.
|
||||||
|
|
||||||
|
Alternative considered: require dual transports everywhere. Rejected because it would break the current single-session client setup and create unnecessary migration pressure.
|
||||||
|
|
||||||
|
### Decision: Keep transport ownership at session/runtime composition boundaries
|
||||||
|
Session managers, message managers, or equivalent composition roots should receive both transport references during construction so downstream routing logic can stay host-agnostic.
|
||||||
|
|
||||||
|
This keeps transport selection in shared code and prevents Unity or host bootstrapping layers from re-implementing routing rules.
|
||||||
|
|
||||||
|
Alternative considered: inject a host-side selector callback. Rejected because it spreads transport policy across hosts and makes regression coverage weaker.
|
||||||
|
|
||||||
|
### Decision: Encode fallback behavior in requirements and tests
|
||||||
|
The change will specify that sync-routed traffic uses the sync transport when available and otherwise uses the primary reliable transport. Tests should cover both client single-session and server multi-session variants.
|
||||||
|
|
||||||
|
Alternative considered: rely on implementation comments only. Rejected because the fallback contract is easy to regress during future refactors.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- [Risk] Integration constructors may grow more complex with optional transport parameters. → Mitigation: keep the extra dependency scoped to composition roots and default the sync transport explicitly.
|
||||||
|
- [Risk] Hosts may accidentally provide mismatched transport instances across session types. → Mitigation: express wiring requirements in specs and add regression tests for composition paths.
|
||||||
|
- [Risk] Existing tests may only cover single-session client behavior. → Mitigation: add server multi-session coverage as part of the task list.
|
||||||
|
|
||||||
|
## Migration Plan
|
||||||
|
|
||||||
|
1. Extend composition APIs to accept the optional sync transport without breaking existing call sites.
|
||||||
|
2. Update shared routing/session initialization to store and use both dependencies.
|
||||||
|
3. Add or adjust edit-mode tests for fallback and dedicated sync-lane behavior.
|
||||||
|
4. Rollback, if needed, by removing the optional sync transport wiring while retaining the original single-transport constructor path.
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
|
||||||
|
- Which concrete integration types currently own transport composition for client and server entry points?
|
||||||
|
- Are there any message categories besides sync traffic that should explicitly target the secondary transport at composition time?
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
## Why
|
||||||
|
|
||||||
|
The networking stack already distinguishes between reliable gameplay messaging and high-frequency sync traffic, but the integration layer still assumes a single transport path in its runtime wiring. Step 9 is needed now to expose the dual-transport design at composition time so hosts can route each lane consistently without breaking the existing single-session client path.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- Wire the integration layer so session/runtime composition can accept both a primary reliable transport and a sync transport.
|
||||||
|
- Preserve backward-compatible behavior when only the primary transport is provided by continuing to use the reliable path for all traffic that lacks a dedicated sync lane.
|
||||||
|
- Update message/session integration contracts so transport selection is resolved in shared networking code rather than by host-specific call sites.
|
||||||
|
- Add regression coverage for client single-session and server multi-session integration paths that depend on dual-transport wiring.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
- None.
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
- `shared-network-foundation`: Extend the shared composition contract so network managers and related services can be constructed with dual transports while preserving the existing single-transport fallback.
|
||||||
|
- `network-session-lifecycle`: Update runtime/session wiring requirements so sessions initialize and retain both reliable and sync transport dependencies where available.
|
||||||
|
- `network-sync-strategy`: Require integration-layer routing to connect sync traffic to the dedicated sync transport instead of relying on host-side manual wiring.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
- Affected code under `Assets/Scripts/Network/` for integration/composition, session initialization, and transport-aware message dispatch.
|
||||||
|
- Edit-mode regression tests under `Assets/Tests/EditMode/Network/`.
|
||||||
|
- No Unity-specific dependency changes; Unity adapters should remain outside shared networking code.
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Sessions retain dual transport wiring
|
||||||
|
Session lifecycle components SHALL initialize session-scoped networking services with both the primary reliable transport and the optional sync transport supplied by the integration layer.
|
||||||
|
|
||||||
|
#### Scenario: Client single-session initialization with dual transports
|
||||||
|
- **WHEN** the client integration path creates a single session and a sync transport is configured
|
||||||
|
- **THEN** the session-scoped services SHALL retain both transport references for subsequent message routing
|
||||||
|
|
||||||
|
#### Scenario: Server multi-session initialization with fallback transport
|
||||||
|
- **WHEN** the server integration path creates session-scoped services without a dedicated sync transport
|
||||||
|
- **THEN** each session SHALL continue to initialize successfully and SHALL use the primary reliable transport as the fallback lane
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Integration wiring enforces sync lane selection
|
||||||
|
The networking stack SHALL route sync-designated traffic through the sync transport when the integration layer provides one, and SHALL fall back to the primary reliable transport when it does not.
|
||||||
|
|
||||||
|
#### Scenario: Dedicated sync transport available
|
||||||
|
- **WHEN** sync-designated traffic is sent from a session whose integration wiring includes a sync transport
|
||||||
|
- **THEN** the traffic SHALL be dispatched on the sync transport instead of the primary reliable transport
|
||||||
|
|
||||||
|
#### Scenario: Dedicated sync transport unavailable
|
||||||
|
- **WHEN** sync-designated traffic is sent from a session whose integration wiring does not include a sync transport
|
||||||
|
- **THEN** the traffic SHALL be dispatched on the primary reliable transport without failing session operation
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Shared network composition accepts dual transports
|
||||||
|
The shared networking composition layer SHALL allow construction of network managers and related shared services with a primary reliable transport and an optional sync transport.
|
||||||
|
|
||||||
|
#### Scenario: Integration receives both transports
|
||||||
|
- **WHEN** a host composes the shared networking stack with both a reliable transport and a sync transport
|
||||||
|
- **THEN** the shared composition path SHALL retain both dependencies for downstream routing and session services
|
||||||
|
|
||||||
|
#### Scenario: Integration receives only one transport
|
||||||
|
- **WHEN** a host composes the shared networking stack with only the reliable transport
|
||||||
|
- **THEN** the shared composition path SHALL remain valid and SHALL treat the reliable transport as the fallback lane for traffic without a dedicated secondary transport
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
## 1. Extend Integration Composition
|
||||||
|
|
||||||
|
- [x] 1.1 Identify the shared integration/composition entry points that currently construct networking services with a single transport.
|
||||||
|
- [x] 1.2 Update the relevant constructors or factory methods to accept an optional sync transport alongside the primary reliable transport.
|
||||||
|
- [x] 1.3 Preserve backward-compatible call paths so existing single-transport composition still builds and defaults correctly.
|
||||||
|
|
||||||
|
## 2. Wire Dual Transports Through Session Services
|
||||||
|
|
||||||
|
- [x] 2.1 Update session-scoped networking services to retain both transport references provided by the integration layer.
|
||||||
|
- [x] 2.2 Route sync-designated traffic to the sync transport when present and fall back to the reliable transport otherwise.
|
||||||
|
- [x] 2.3 Ensure the shared wiring keeps host-specific transport policy out of Unity-only or integration call sites.
|
||||||
|
|
||||||
|
## 3. Verify Regression Coverage
|
||||||
|
|
||||||
|
- [x] 3.1 Add or update edit-mode tests for client single-session composition with both reliable and sync transports.
|
||||||
|
- [x] 3.2 Add or update edit-mode tests for server multi-session composition when no dedicated sync transport is provided.
|
||||||
|
- [x] 3.3 Run `dotnet build Network.EditMode.Tests.csproj -v minimal` and `dotnet test Network.EditMode.Tests.csproj --no-build -v minimal` after implementation.
|
||||||
|
|
@ -41,4 +41,15 @@ The shared networking core SHALL manage timeout detection, disconnect transition
|
||||||
#### Scenario: Login failure is distinct from transport disconnect
|
#### Scenario: Login failure is distinct from transport disconnect
|
||||||
- **WHEN** authentication or login fails while the transport session is still active
|
- **WHEN** authentication or login fails while the transport session is still active
|
||||||
- **THEN** the shared lifecycle reports a login-failed state for that managed session
|
- **THEN** the shared lifecycle reports a login-failed state for that managed session
|
||||||
- **THEN** hosts can handle that failure separately from a transport disconnect or heartbeat timeout
|
- **THEN** hosts can handle that failure separately from a transport disconnect or heartbeat timeout
|
||||||
|
|
||||||
|
### Requirement: Sessions retain dual transport wiring
|
||||||
|
Session lifecycle components SHALL initialize session-scoped networking services with both the primary reliable transport and the optional sync transport supplied by the integration layer.
|
||||||
|
|
||||||
|
#### Scenario: Client single-session initialization with dual transports
|
||||||
|
- **WHEN** the client integration path creates a single session and a sync transport is configured
|
||||||
|
- **THEN** the session-scoped services SHALL retain both transport references for subsequent message routing
|
||||||
|
|
||||||
|
#### Scenario: Server multi-session initialization with fallback transport
|
||||||
|
- **WHEN** the server integration path creates session-scoped services without a dedicated sync transport
|
||||||
|
- **THEN** each session SHALL continue to initialize successfully and SHALL use the primary reliable transport as the fallback lane
|
||||||
|
|
|
||||||
|
|
@ -59,4 +59,15 @@ The shared networking core SHALL process server-tick or clock-synchronization sa
|
||||||
#### Scenario: Hosts can consume smoothed clock data for prediction
|
#### Scenario: Hosts can consume smoothed clock data for prediction
|
||||||
- **WHEN** prediction or reconciliation code needs the current server-time estimate
|
- **WHEN** prediction or reconciliation code needs the current server-time estimate
|
||||||
- **THEN** it reads that estimate from the clock-sync strategy or state object
|
- **THEN** it reads that estimate from the clock-sync strategy or state object
|
||||||
- **THEN** it does not query `SessionManager` for authoritative clock ownership
|
- **THEN** it does not query `SessionManager` for authoritative clock ownership
|
||||||
|
|
||||||
|
### Requirement: Integration wiring enforces sync lane selection
|
||||||
|
The networking stack SHALL route sync-designated traffic through the sync transport when the integration layer provides one, and SHALL fall back to the primary reliable transport when it does not.
|
||||||
|
|
||||||
|
#### Scenario: Dedicated sync transport available
|
||||||
|
- **WHEN** sync-designated traffic is sent from a session whose integration wiring includes a sync transport
|
||||||
|
- **THEN** the traffic SHALL be dispatched on the sync transport instead of the primary reliable transport
|
||||||
|
|
||||||
|
#### Scenario: Dedicated sync transport unavailable
|
||||||
|
- **WHEN** sync-designated traffic is sent from a session whose integration wiring does not include a sync transport
|
||||||
|
- **THEN** the traffic SHALL be dispatched on the primary reliable transport without failing session operation
|
||||||
|
|
|
||||||
|
|
@ -69,4 +69,15 @@ The shared network foundation SHALL include host-agnostic session lifecycle orch
|
||||||
#### Scenario: Server host composes shared foundation with multi-session orchestration
|
#### Scenario: Server host composes shared foundation with multi-session orchestration
|
||||||
- **WHEN** a non-Unity server host constructs the runtime networking stack for multiple remote peers
|
- **WHEN** a non-Unity server host constructs the runtime networking stack for multiple remote peers
|
||||||
- **THEN** it uses the shared transport and message-routing foundation together with shared multi-session lifecycle orchestration
|
- **THEN** it uses the shared transport and message-routing foundation together with shared multi-session lifecycle orchestration
|
||||||
- **THEN** server-specific cleanup, admission, and gameplay reactions stay in the server host adapter rather than forking the shared lifecycle contract
|
- **THEN** server-specific cleanup, admission, and gameplay reactions stay in the server host adapter rather than forking the shared lifecycle contract
|
||||||
|
|
||||||
|
### Requirement: Shared network composition accepts dual transports
|
||||||
|
The shared networking composition layer SHALL allow construction of network managers and related shared services with a primary reliable transport and an optional sync transport.
|
||||||
|
|
||||||
|
#### Scenario: Integration receives both transports
|
||||||
|
- **WHEN** a host composes the shared networking stack with both a reliable transport and a sync transport
|
||||||
|
- **THEN** the shared composition path SHALL retain both dependencies for downstream routing and session services
|
||||||
|
|
||||||
|
#### Scenario: Integration receives only one transport
|
||||||
|
- **WHEN** a host composes the shared networking stack with only the reliable transport
|
||||||
|
- **THEN** the shared composition path SHALL remain valid and SHALL treat the reliable transport as the fallback lane for traffic without a dedicated secondary transport
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue