完成阶段 4

This commit is contained in:
SepComet
2026-03-27 08:04:25 +08:00
parent f053c9ad0d
commit ff9ee1291f
47 changed files with 2481 additions and 460 deletions
@@ -1,4 +1,5 @@
using System;
using System;
using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;
using Google.Protobuf;
@@ -17,7 +18,7 @@ namespace Tests.EditMode.Network
public void SendMessage_WithoutTarget_UsesDefaultSend()
{
var transport = new FakeTransport();
var manager = new MessageManager(transport);
var manager = new MessageManager(transport, new MainThreadNetworkDispatcher());
var message = new Heartbeat();
manager.SendMessage(message, MessageType.Heartbeat);
@@ -35,7 +36,7 @@ namespace Tests.EditMode.Network
public void SendMessage_WithTarget_UsesExplicitSend()
{
var transport = new FakeTransport();
var manager = new MessageManager(transport);
var manager = new MessageManager(transport, new MainThreadNetworkDispatcher());
var message = new LoginRequest
{
PlayerId = "player-1",
@@ -57,7 +58,7 @@ namespace Tests.EditMode.Network
public void BroadcastMessage_UsesBroadcastSend()
{
var transport = new FakeTransport();
var manager = new MessageManager(transport);
var manager = new MessageManager(transport, new MainThreadNetworkDispatcher());
var message = new Heartbeat();
manager.BroadcastMessage(message, MessageType.Heartbeat);
@@ -71,10 +72,10 @@ namespace Tests.EditMode.Network
}
[Test]
public void Receive_ValidEnvelope_DispatchesRegisteredHandler()
public void Receive_ValidEnvelope_IsDeferredUntilDrain()
{
var transport = new FakeTransport();
var manager = new MessageManager(transport);
var manager = new MessageManager(transport, new MainThreadNetworkDispatcher());
var handled = false;
IPEndPoint receivedSender = null;
byte[] receivedPayload = null;
@@ -89,26 +90,33 @@ namespace Tests.EditMode.Network
transport.EmitReceive(BuildEnvelope(MessageType.Heartbeat, message), Sender);
Assert.That(handled, Is.False);
Assert.That(manager.PendingMessageCount, Is.EqualTo(1));
manager.DrainPendingMessagesAsync().GetAwaiter().GetResult();
Assert.That(handled, Is.True);
Assert.That(receivedSender, Is.EqualTo(Sender));
Assert.That(receivedPayload, Is.EqualTo(message.ToByteArray()));
Assert.That(manager.PendingMessageCount, Is.EqualTo(0));
}
[Test]
public void Receive_UnregisteredMessage_DoesNotThrow()
{
var transport = new FakeTransport();
var manager = new MessageManager(transport);
var manager = new MessageManager(transport, new MainThreadNetworkDispatcher());
Assert.DoesNotThrow(() =>
transport.EmitReceive(BuildEnvelope(MessageType.Heartbeat, new Heartbeat()), Sender));
Assert.That(manager.PendingMessageCount, Is.EqualTo(0));
}
[Test]
public void Receive_InvalidBytes_DoesNotBreakFollowingDispatch()
{
var transport = new FakeTransport();
var manager = new MessageManager(transport);
var manager = new MessageManager(transport, new MainThreadNetworkDispatcher());
var handledCount = 0;
manager.RegisterHandler(MessageType.Heartbeat, (payload, sender) =>
@@ -119,9 +127,37 @@ namespace Tests.EditMode.Network
Assert.DoesNotThrow(() => transport.EmitReceive(new byte[] { 0x01, 0x02, 0x03 }, Sender));
transport.EmitReceive(BuildEnvelope(MessageType.Heartbeat, new Heartbeat()), Sender);
Assert.That(handledCount, Is.EqualTo(0));
Assert.That(manager.PendingMessageCount, Is.EqualTo(1));
manager.DrainPendingMessagesAsync().GetAwaiter().GetResult();
Assert.That(handledCount, Is.EqualTo(1));
}
[Test]
public void Receive_MultipleMessages_PreserveEnqueueOrder()
{
var transport = new FakeTransport();
var manager = new MessageManager(transport, new MainThreadNetworkDispatcher());
var handledSpeeds = new List<int>();
manager.RegisterHandler(MessageType.LoginRequest, (payload, sender) =>
{
handledSpeeds.Add(LoginRequest.Parser.ParseFrom(payload).Speed);
});
transport.EmitReceive(BuildEnvelope(MessageType.LoginRequest, new LoginRequest { PlayerId = "a", Speed = 1 }), Sender);
transport.EmitReceive(BuildEnvelope(MessageType.LoginRequest, new LoginRequest { PlayerId = "b", Speed = 2 }), Sender);
Assert.That(handledSpeeds, Is.Empty);
Assert.That(manager.PendingMessageCount, Is.EqualTo(2));
manager.DrainPendingMessagesAsync().GetAwaiter().GetResult();
Assert.That(handledSpeeds, Is.EqualTo(new[] { 1, 2 }));
}
private static byte[] BuildEnvelope(MessageType type, IMessage payload)
{
return new Envelope
@@ -0,0 +1,136 @@
using System;
using System.Net;
using System.Threading.Tasks;
using Google.Protobuf;
using Network.Defines;
using Network.NetworkApplication;
using Network.NetworkHost;
using Network.NetworkTransport;
using NUnit.Framework;
namespace Tests.EditMode.Network
{
public class SharedNetworkFoundationTests
{
private static readonly IPEndPoint Sender = new(IPAddress.Loopback, 9000);
[Test]
public void SharedNetworkRuntime_UsesInjectedDispatcherForDeferredClientStyleDispatch()
{
var transport = new FakeTransport();
var runtime = new SharedNetworkRuntime(transport, new MainThreadNetworkDispatcher());
var handled = false;
runtime.MessageManager.RegisterHandler(MessageType.Heartbeat, (payload, sender) =>
{
handled = true;
});
transport.EmitReceive(BuildEnvelope(MessageType.Heartbeat, new Heartbeat()), Sender);
Assert.That(handled, Is.False);
Assert.That(runtime.MessageManager.PendingMessageCount, Is.EqualTo(1));
runtime.DrainPendingMessagesAsync().GetAwaiter().GetResult();
Assert.That(handled, Is.True);
Assert.That(runtime.MessageManager.PendingMessageCount, Is.EqualTo(0));
}
[Test]
public void ServerNetworkHost_UsesImmediateDispatcherByDefault()
{
var transport = new FakeTransport();
var host = new ServerNetworkHost(transport);
var handled = false;
host.MessageManager.RegisterHandler(MessageType.Heartbeat, (payload, sender) =>
{
handled = true;
});
transport.EmitReceive(BuildEnvelope(MessageType.Heartbeat, new Heartbeat()), Sender);
Assert.That(handled, Is.True);
Assert.That(host.MessageManager.PendingMessageCount, Is.EqualTo(0));
}
[Test]
public void SharedHosts_PreserveEnvelopeProtocolContract()
{
var transport = new FakeTransport();
var runtime = new SharedNetworkRuntime(transport, new ImmediateNetworkMessageDispatcher());
var message = new LoginRequest
{
PlayerId = "shared-player",
Speed = 7
};
runtime.MessageManager.SendMessage(message, MessageType.LoginRequest, Sender);
var envelope = Envelope.Parser.ParseFrom(transport.LastSendToData);
Assert.That(envelope.Type, Is.EqualTo((int)MessageType.LoginRequest));
Assert.That(LoginRequest.Parser.ParseFrom(envelope.Payload).PlayerId, Is.EqualTo("shared-player"));
Assert.That(LoginRequest.Parser.ParseFrom(envelope.Payload).Speed, Is.EqualTo(7));
Assert.That(transport.LastSendTarget, Is.EqualTo(Sender));
}
private static byte[] BuildEnvelope(MessageType type, IMessage payload)
{
return new Envelope
{
Type = (int)type,
Payload = payload.ToByteString()
}.ToByteArray();
}
private sealed class FakeTransport : ITransport
{
public byte[] LastSendToData { get; private set; }
public IPEndPoint LastSendTarget { get; private set; }
public event Action<byte[], IPEndPoint> OnReceive;
public Task StartAsync()
{
return Task.CompletedTask;
}
public void Stop()
{
}
public void Send(byte[] data)
{
}
public void SendTo(byte[] data, IPEndPoint target)
{
LastSendToData = Copy(data);
LastSendTarget = target;
}
public void SendToAll(byte[] data)
{
}
public void EmitReceive(byte[] data, IPEndPoint sender)
{
OnReceive?.Invoke(Copy(data), sender);
}
private static byte[] Copy(byte[] data)
{
if (data == null)
{
return null;
}
var copy = new byte[data.Length];
Array.Copy(data, copy, data.Length);
return copy;
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cb9381c1693943c79457fc3fe9174e4a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,27 @@
using System;
using System.Linq;
using Network.NetworkTransport;
using NUnit.Framework;
namespace Tests.EditMode.Network
{
public class TransportArchitectureTests
{
[Test]
public void KcpTransport_ImplementsITransport()
{
Assert.That(typeof(ITransport).IsAssignableFrom(typeof(KcpTransport)), Is.True);
}
[Test]
public void ReliableUdpTransport_IsNotAvailable()
{
var reliableUdpTransportType = AppDomain.CurrentDomain
.GetAssemblies()
.Select(assembly => assembly.GetType("Network.NetworkTransport.ReliableUdpTransport", throwOnError: false))
.FirstOrDefault(type => type != null);
Assert.That(reliableUdpTransportType, Is.Null);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: acd10ddd335680e44bce8246024e7f6a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: