This commit is contained in:
SepComet 2026-04-06 12:23:09 +08:00
parent 3446dcd6f1
commit a1ede230bb
4 changed files with 47 additions and 16 deletions

View File

@ -5,7 +5,8 @@
"Bash(openspec status:*)", "Bash(openspec status:*)",
"Bash(openspec instructions:*)", "Bash(openspec instructions:*)",
"Bash(dotnet build:*)", "Bash(dotnet build:*)",
"Bash(dotnet test:*)" "Bash(dotnet test:*)",
"Bash(dotnet Temp/Bin/Debug/Network.EditMode.Tests/Network.EditMode.Tests.dll)"
] ]
}, },
"outputStyle": "default" "outputStyle": "default"

View File

@ -1,22 +1,60 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Network.Defines; using Network.Defines;
namespace Network.NetworkApplication namespace Network.NetworkApplication
{ {
/// <summary>
/// Resolves the delivery policy for each <see cref="MessageType"/>.
/// Policies are intentionally explicit here so that adding a new <see cref="MessageType"/>
/// requires an intentional decision rather than silently falling through to a default.
/// </summary>
public sealed class DefaultMessageDeliveryPolicyResolver : IMessageDeliveryPolicyResolver public sealed class DefaultMessageDeliveryPolicyResolver : IMessageDeliveryPolicyResolver
{ {
private static readonly IReadOnlyDictionary<MessageType, DeliveryPolicy> DefaultPolicies = private static readonly IReadOnlyDictionary<MessageType, DeliveryPolicy> Policies =
new Dictionary<MessageType, DeliveryPolicy> new Dictionary<MessageType, DeliveryPolicy>
{ {
// High-frequency sync lane: latest-wins, stale-drop.
// These messages are sent every frame and a stale value is never useful.
{ MessageType.MoveInput, DeliveryPolicy.HighFrequencySync }, { MessageType.MoveInput, DeliveryPolicy.HighFrequencySync },
{ MessageType.PlayerState, DeliveryPolicy.HighFrequencySync } { MessageType.PlayerState, DeliveryPolicy.HighFrequencySync },
// Reliable ordered lane: guaranteed delivery, ordered.
// ShootInput carries player intent; losing or reordering it changes gameplay outcomes.
{ MessageType.ShootInput, DeliveryPolicy.ReliableOrdered },
// CombatEvent is a server-authoritative result; clients must receive it reliably
// and in order to maintain consistent HP/death state.
{ MessageType.CombatEvent, DeliveryPolicy.ReliableOrdered },
// PlayerJoin carries spawn data that the client needs to instantiate a player.
// Reliable ordered ensures the client receives it before gameplay begins.
{ MessageType.PlayerJoin, DeliveryPolicy.ReliableOrdered },
// Login/logout are session-control messages that must not be lost or reordered.
{ MessageType.LoginRequest, DeliveryPolicy.ReliableOrdered },
{ MessageType.LoginResponse, DeliveryPolicy.ReliableOrdered },
{ MessageType.LogoutRequest, DeliveryPolicy.ReliableOrdered },
// Heartbeat carries server tick used for clock sync; a missing sample is
// simply a lost sample — no value in stale delivery.
{ MessageType.Heartbeat, DeliveryPolicy.ReliableOrdered },
{ MessageType.HeartbeatResponse, DeliveryPolicy.ReliableOrdered },
}; };
public DeliveryPolicy Resolve(MessageType messageType) public DeliveryPolicy Resolve(MessageType messageType)
{ {
return DefaultPolicies.TryGetValue(messageType, out var policy) if (Policies.TryGetValue(messageType, out var policy))
? policy {
: DeliveryPolicy.ReliableOrdered; return policy;
}
// A new MessageType was added without an explicit policy decision.
// Fail fast so the omission is noticed rather than silently defaulting.
throw new System.ArgumentException(
$"MessageType '{messageType}' has no assigned {nameof(DeliveryPolicy)}. " +
"Add an explicit entry in the policy dictionary.",
nameof(messageType));
} }
} }
} }

View File

@ -1,5 +1,4 @@
using System; using System;
using System.Threading.Tasks;
using Network.NetworkHost; using Network.NetworkHost;
using Network.NetworkTransport; using Network.NetworkTransport;
@ -63,8 +62,6 @@ namespace Network.NetworkApplication
ServerAuthoritativeCombatConfiguration authoritativeCombat = null, ServerAuthoritativeCombatConfiguration authoritativeCombat = null,
IAuthoritativeMovementWorldValidator authoritativeMovementWorldValidator = null) IAuthoritativeMovementWorldValidator authoritativeMovementWorldValidator = null)
{ {
ValidateDualPortConfiguration(reliablePort, syncPort);
transportFactory ??= static port => new KcpTransport(port); transportFactory ??= static port => new KcpTransport(port);
var reliableTransport = transportFactory(reliablePort) var reliableTransport = transportFactory(reliablePort)
@ -91,11 +88,6 @@ namespace Network.NetworkApplication
authoritativeMovementWorldValidator ?? PermissiveAuthoritativeMovementWorldValidator.Instance); authoritativeMovementWorldValidator ?? PermissiveAuthoritativeMovementWorldValidator.Instance);
} }
public static Task<ServerRuntimeHandle> StartServerRuntimeAsync(ServerRuntimeConfiguration configuration)
{
return ServerRuntimeEntryPoint.StartAsync(configuration);
}
private static void ValidateDualPortConfiguration(int reliablePort, int? syncPort) private static void ValidateDualPortConfiguration(int reliablePort, int? syncPort)
{ {
if (reliablePort <= 0) if (reliablePort <= 0)

View File

@ -24,7 +24,7 @@ namespace Tests.EditMode.Network
TransportFactory = port => CreateTransport(createdTransports, port) TransportFactory = port => CreateTransport(createdTransports, port)
}; };
using var runtime = NetworkIntegrationFactory.StartServerRuntimeAsync(configuration).GetAwaiter().GetResult(); using var runtime = ServerRuntimeEntryPoint.StartAsync(configuration).GetAwaiter().GetResult();
Assert.That(createdTransports.Keys, Is.EquivalentTo(new[] { 9000 })); Assert.That(createdTransports.Keys, Is.EquivalentTo(new[] { 9000 }));
Assert.That(runtime.IsRunning, Is.True); Assert.That(runtime.IsRunning, Is.True);