diff --git a/.github/workflows/nuget-publish.yml b/.github/workflows/nuget-publish.yml new file mode 100644 index 00000000..fd5ee070 --- /dev/null +++ b/.github/workflows/nuget-publish.yml @@ -0,0 +1,88 @@ +name: Publish NuGet Package + +on: + workflow_dispatch: + +jobs: + publish-release: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + + - name: Restore dependencies + run: dotnet restore + + - name: Build LiteNetLib + run: dotnet build LiteNetLib/LiteNetLib.csproj --configuration Release + + - name: Check version and pack if changed + id: check-pack + run: | + echo "Checking for version changes..." + + PROJECT_PATH="LiteNetLib/LiteNetLib.csproj" + PACKAGE_NAME="litenetlib" + + # Extract version from project file + LOCAL_VERSION=$(grep -o '[^<]*' "$PROJECT_PATH" | sed 's/\(.*\)<\/PackageVersion>/\1/') + + if [ -z "$LOCAL_VERSION" ]; then + echo "No tag found in $PROJECT_PATH" + echo "has_packages=false" >> $GITHUB_OUTPUT + exit 0 + fi + + # Get the latest version from NuGet.org + echo "Checking NuGet.org for $PACKAGE_NAME..." + NUGET_VERSION=$(curl -s "https://api.nuget.org/v3-flatcontainer/$PACKAGE_NAME/index.json" | grep -o '"[^"]*"' | sed 's/"//g' | sort -V | tail -n 1) + + echo "Local version: $LOCAL_VERSION" + echo "NuGet version: $NUGET_VERSION" + + # Compare versions + if [ "$LOCAL_VERSION" != "$NUGET_VERSION" ]; then + echo "Version changed - will pack and publish" + mkdir -p ./nupkgs + dotnet pack "$PROJECT_PATH" --configuration Release --no-build --output ./nupkgs + echo "has_packages=true" >> $GITHUB_OUTPUT + else + echo "Version unchanged - skipping" + echo "has_packages=false" >> $GITHUB_OUTPUT + fi + + - name: Push to NuGet.org + if: steps.check-pack.outputs.has_packages == 'true' + env: + NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} + run: | + echo "Publishing to NuGet.org..." + for package in ./nupkgs/*.nupkg; do + if [ -f "$package" ]; then + echo "Publishing $package..." + dotnet nuget push "$package" --api-key "$NUGET_API_KEY" --source https://api.nuget.org/v3/index.json --skip-duplicate || { + echo "Failed to publish $package" + exit 1 + } + fi + done + + - name: Upload package as artifact + if: steps.check-pack.outputs.has_packages == 'true' + uses: actions/upload-artifact@v4 + with: + name: nuget-package + path: ./nupkgs/*.nupkg + retention-days: 7 + + - name: No package to publish + if: steps.check-pack.outputs.has_packages == 'false' + run: echo "Version unchanged - nothing to publish" diff --git a/.gitignore b/.gitignore index dbffa72f..3efd0e52 100644 --- a/.gitignore +++ b/.gitignore @@ -160,6 +160,8 @@ publish/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config +!LiteNetLibSampleUnity/Packages/* + # Windows Azure Build Output csx/ *.build.csdef diff --git a/LICENSE.txt b/LICENSE.txt index ecdcd0b6..ec1bddf3 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 Ruslan Pyrch +Copyright (c) 2025 Ruslan Pyrch Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/LibSample/AesEncryptLayer.cs b/LibSample/AesEncryptLayer.cs index 41d69238..1df94508 100644 --- a/LibSample/AesEncryptLayer.cs +++ b/LibSample/AesEncryptLayer.cs @@ -19,7 +19,7 @@ public class AesEncryptLayer : PacketLayerBase public const int KeySizeInBytes = KeySize / 8; public const int BlockSizeInBytes = BlockSize / 8; - private readonly AesCryptoServiceProvider _aes; + private readonly Aes _aes; private ICryptoTransform _encryptor; private byte[] cipherBuffer = new byte[1500]; //Max possible UDP packet size private ICryptoTransform _decryptor; @@ -36,7 +36,7 @@ public AesEncryptLayer(byte[] key) : base(BlockSizeInBytes * 2) if (key.Length != KeySizeInBytes) throw new NotSupportedException("EncryptLayer only supports keysize " + KeySize); //Switch this with AesGCM for better performance, requires .NET Core 3.0 or Standard 2.1 - _aes = new AesCryptoServiceProvider(); + _aes = Aes.Create(); _aes.KeySize = KeySize; _aes.BlockSize = BlockSize; _aes.Key = key; @@ -47,22 +47,20 @@ public AesEncryptLayer(byte[] key) : base(BlockSizeInBytes * 2) _decryptor = _aes.CreateDecryptor(); } - public override void ProcessInboundPacket(ref IPEndPoint endPoint, ref byte[] data, ref int offset, ref int length) + public override void ProcessInboundPacket(ref IPEndPoint endPoint, ref byte[] data, ref int length) { //Can't copy directly to _aes.IV. It won't work for some reason. - Buffer.BlockCopy(data, offset, ivBuffer, 0, ivBuffer.Length); + Buffer.BlockCopy(data, 0, ivBuffer, 0, ivBuffer.Length); //_aes.IV = ivBuffer; _decryptor = _aes.CreateDecryptor(_aes.Key, ivBuffer); - offset += BlockSizeInBytes; //int currentRead = ivBuffer.Length; //int currentWrite = 0; //TransformBlocks(_decryptor, data, length, ref currentRead, ref currentWrite); - byte[] lastBytes = _decryptor.TransformFinalBlock(data, offset, length - offset); + byte[] lastBytes = _decryptor.TransformFinalBlock(data, BlockSizeInBytes, length - BlockSizeInBytes); data = lastBytes; - offset = 0; length = lastBytes.Length; } diff --git a/LibSample/AesEncryptionTest.cs b/LibSample/AesEncryptionTest.cs index 0876edfb..e46e249d 100644 --- a/LibSample/AesEncryptionTest.cs +++ b/LibSample/AesEncryptionTest.cs @@ -37,7 +37,7 @@ private void AesLayerEncryptDecrypt() //Copy array so we dont read and write to same array byte[] inboundData = new byte[outbound.Length]; outbound.CopyTo(inboundData, 0); - inboundLayer.ProcessInboundPacket(ref emptyEndPoint, ref inboundData, ref start, ref length); + inboundLayer.ProcessInboundPacket(ref emptyEndPoint, ref inboundData, ref length); Console.WriteLine(Encoding.ASCII.GetString(inboundData, 0, length)); byte[] expectedPlaintext = Encoding.ASCII.GetBytes(testData); diff --git a/LibSample/BroadcastTest.cs b/LibSample/BroadcastTest.cs index 1f44b478..6720a3e3 100644 --- a/LibSample/BroadcastTest.cs +++ b/LibSample/BroadcastTest.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.Threading; @@ -15,7 +16,7 @@ private class ClientListener : INetEventListener public void OnPeerConnected(NetPeer peer) { - Console.WriteLine("[Client {0}] connected to: {1}:{2}", Client.LocalPort, peer.EndPoint.Address, peer.EndPoint.Port); + Console.WriteLine("[Client {0}] connected to: {1}:{2}", Client.LocalPort, peer.Address, peer.Port); } public void OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo) @@ -58,19 +59,21 @@ private class ServerListener : INetEventListener { public NetManager Server; + private readonly List _peersList = new List(); + public void OnPeerConnected(NetPeer peer) { - Console.WriteLine("[Server] Peer connected: " + peer.EndPoint); - var peers = Server.ConnectedPeerList; - foreach (var netPeer in peers) + Console.WriteLine("[Server] Peer connected: " + peer); + Server.GetConnectedPeers(_peersList); + foreach (var netPeer in _peersList) { - Console.WriteLine("ConnectedPeersList: id={0}, ep={1}", netPeer.Id, netPeer.EndPoint); + Console.WriteLine("ConnectedPeersList: id={0}, ep={1}", netPeer.Id, netPeer); } } public void OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo) { - Console.WriteLine("[Server] Peer disconnected: " + peer.EndPoint + ", reason: " + disconnectInfo.Reason); + Console.WriteLine("[Server] Peer disconnected: " + peer + ", reason: " + disconnectInfo.Reason); } public void OnNetworkError(IPEndPoint endPoint, SocketError socketErrorCode) @@ -86,9 +89,9 @@ public void OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channelN public void OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType) { Console.WriteLine("[Server] ReceiveUnconnected {0}. From: {1}. Data: {2}", messageType, remoteEndPoint, reader.GetString(100)); - NetDataWriter wrtier = new NetDataWriter(); - wrtier.Put("SERVER DISCOVERY RESPONSE"); - Server.SendUnconnectedMessage(wrtier, remoteEndPoint); + NetDataWriter writer = new NetDataWriter(); + writer.Put("SERVER DISCOVERY RESPONSE"); + Server.SendUnconnectedMessage(writer, remoteEndPoint); } public void OnNetworkLatencyUpdate(NetPeer peer, int latency) @@ -115,7 +118,7 @@ public void Run() NetManager server = new NetManager(_serverListener) { BroadcastReceiveEnabled = true, - IPv6Mode = IPv6Mode.DualMode + IPv6Enabled = true }; if (!server.Start(9050)) { @@ -133,7 +136,7 @@ public void Run() UnconnectedMessagesEnabled = true, SimulateLatency = true, SimulationMaxLatency = 1500, - IPv6Mode = IPv6Mode.DualMode + IPv6Enabled = true }; _clientListener1.Client = client1; if (!client1.Start()) @@ -149,7 +152,7 @@ public void Run() UnconnectedMessagesEnabled = true, SimulateLatency = true, SimulationMaxLatency = 1500, - IPv6Mode = IPv6Mode.DualMode + IPv6Enabled = true }; _clientListener2.Client = client2; diff --git a/LibSample/EchoMessagesTest.cs b/LibSample/EchoMessagesTest.cs index 150a6379..7a8dc9b9 100644 --- a/LibSample/EchoMessagesTest.cs +++ b/LibSample/EchoMessagesTest.cs @@ -15,7 +15,7 @@ private class ClientListener : INetEventListener { public void OnPeerConnected(NetPeer peer) { - Console.WriteLine("[Client] connected to: {0}:{1}", peer.EndPoint.Address, peer.EndPoint.Port); + Console.WriteLine("[Client] connected to: {0}:{1}", peer.Address, peer.Port); NetDataWriter dataWriter = new NetDataWriter(); for (int i = 0; i < 5; i++) @@ -103,17 +103,17 @@ private class ServerListener : INetEventListener public void OnPeerConnected(NetPeer peer) { - Console.WriteLine("[Server] Peer connected: " + peer.EndPoint); + Console.WriteLine("[Server] Peer connected: " + peer); foreach (var netPeer in Server) { if(netPeer.ConnectionState == ConnectionState.Connected) - Console.WriteLine("ConnectedPeersList: id={0}, ep={1}", netPeer.Id, netPeer.EndPoint); + Console.WriteLine("ConnectedPeersList: id={0}, ep={1}", netPeer.Id, netPeer); } } public void OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo) { - Console.WriteLine("[Server] Peer disconnected: " + peer.EndPoint + ", reason: " + disconnectInfo.Reason); + Console.WriteLine("[Server] Peer disconnected: " + peer + ", reason: " + disconnectInfo.Reason); } public void OnNetworkError(IPEndPoint endPoint, SocketError socketErrorCode) diff --git a/LibSample/GithubSample.cs b/LibSample/GithubSample.cs index 4bb8d389..0a680449 100644 --- a/LibSample/GithubSample.cs +++ b/LibSample/GithubSample.cs @@ -23,10 +23,10 @@ public void Server() listener.PeerConnectedEvent += peer => { - Console.WriteLine("We got connection: {0}", peer.EndPoint); // Show peer ip - NetDataWriter writer = new NetDataWriter(); // Create writer class - writer.Put("Hello client!"); // Put some string - peer.Send(writer, DeliveryMethod.ReliableOrdered); // Send with reliability + Console.WriteLine("We got connection: {0}", peer); // Show peer ip + NetDataWriter writer = new NetDataWriter(); // Create writer class + writer.Put("Hello client!"); // Put some string + peer.Send(writer, DeliveryMethod.ReliableOrdered); // Send with reliability }; while (!Console.KeyAvailable) diff --git a/LibSample/HolePunchServerTest.cs b/LibSample/HolePunchServerTest.cs index eb97c88d..d7564d56 100644 --- a/LibSample/HolePunchServerTest.cs +++ b/LibSample/HolePunchServerTest.cs @@ -91,7 +91,7 @@ public void Run() clientListener.PeerConnectedEvent += peer => { - Console.WriteLine("PeerConnected: " + peer.EndPoint); + Console.WriteLine("PeerConnected: " + peer); }; clientListener.ConnectionRequestEvent += request => @@ -122,7 +122,7 @@ public void Run() _c1 = new NetManager(clientListener) { - IPv6Mode = IPv6Mode.DualMode, + IPv6Enabled = true, NatPunchEnabled = true }; _c1.NatPunchModule.Init(natPunchListener1); @@ -130,7 +130,7 @@ public void Run() _c2 = new NetManager(clientListener) { - IPv6Mode = IPv6Mode.DualMode, + IPv6Enabled = true, NatPunchEnabled = true }; _c2.NatPunchModule.Init(natPunchListener2); @@ -138,7 +138,7 @@ public void Run() _puncher = new NetManager(clientListener) { - IPv6Mode = IPv6Mode.DualMode, + IPv6Enabled = true, NatPunchEnabled = true }; _puncher.Start(ServerPort); diff --git a/LibSample/LibSample.csproj b/LibSample/LibSample.csproj index 5c6fe293..f1d24079 100644 --- a/LibSample/LibSample.csproj +++ b/LibSample/LibSample.csproj @@ -2,8 +2,7 @@ Exe - net6.0;net5.0;netcoreapp3.1 - net6.0;net5.0;net471;netcoreapp3.1 + net8.0;net9.0 diff --git a/LibSample/PacketProcessorExample.cs b/LibSample/PacketProcessorExample.cs index 44411a71..46ad36ee 100644 --- a/LibSample/PacketProcessorExample.cs +++ b/LibSample/PacketProcessorExample.cs @@ -37,6 +37,7 @@ class PacketProcessorExample : IExample private readonly NetPacketProcessor _netPacketProcessor = new NetPacketProcessor(); private NetManager _client; private NetManager _server; + private NetDataWriter _writer = new NetDataWriter(); public void Run() { @@ -65,10 +66,9 @@ public void Run() new CustomStruct {X = 5, Y = -28}, new CustomStruct {X = -114, Y = 65535} }; - _netPacketProcessor.Send( - peer, - new ArgumentsForLogin {Password = "pass", SomeInt = 5, UserId = "someUser", SomeList = testList}, - DeliveryMethod.ReliableOrdered); + _writer.Reset(); + _netPacketProcessor.Write(_writer, new ArgumentsForLogin { Password = "pass", SomeInt = 5, UserId = "someUser", SomeList = testList }); + peer.Send(_writer, DeliveryMethod.ReliableOrdered); }; //start client/server diff --git a/LibSample/SerializerBenchmark.cs b/LibSample/SerializerBenchmark.cs index 8d5c381e..31034ebb 100644 --- a/LibSample/SerializerBenchmark.cs +++ b/LibSample/SerializerBenchmark.cs @@ -131,7 +131,6 @@ public void Run() //Test serializer performance Stopwatch stopwatch = new Stopwatch(); - BinaryFormatter binaryFormatter = new BinaryFormatter(); MemoryStream memoryStream = new MemoryStream(); NetDataWriter netDataWriter = new NetDataWriter(); @@ -158,14 +157,6 @@ public void Run() double c = Math.Sin(i); } - //Test binary formatter - stopwatch.Start(); - for (int i = 0; i < LoopLength; i++) - binaryFormatter.Serialize(memoryStream, samplePacket); - stopwatch.Stop(); - Console.WriteLine("BinaryFormatter time: " + stopwatch.ElapsedMilliseconds + " ms"); - Console.WriteLine("BinaryFormatter size: " + memoryStream.Position / LoopLength); - DataWriterTest(netDataWriter, stopwatch, samplePacket); NetSerializerTest(netSerializer, netDataWriter, stopwatch, samplePacket); diff --git a/LibSample/SpeedBench.cs b/LibSample/SpeedBench.cs index aa9a163f..09dcb5f4 100644 --- a/LibSample/SpeedBench.cs +++ b/LibSample/SpeedBench.cs @@ -69,7 +69,7 @@ void INetEventListener.OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, Ne void INetEventListener.OnPeerConnected(NetPeer peer) { - Console.WriteLine($"Server: client connected: {peer.EndPoint}"); + Console.WriteLine($"Server: client connected: {peer}"); } void INetEventListener.OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo) diff --git a/LiteNetLib.Tests/CRC32LayerTest.cs b/LiteNetLib.Tests/CRC32LayerTest.cs index 01b4c70d..c6b716e6 100644 --- a/LiteNetLib.Tests/CRC32LayerTest.cs +++ b/LiteNetLib.Tests/CRC32LayerTest.cs @@ -25,11 +25,10 @@ public void ReturnsDataWithoutChecksum() { byte[] packet = GetTestPacketWithCrc(); - int offset = 0; int length = packet.Length; - _crc32Layer.ProcessInboundPacket(ref _dummyEndpoint, ref packet, ref offset, ref length); + _crc32Layer.ProcessInboundPacket(ref _dummyEndpoint, ref packet, ref length); - Assert.AreEqual(packet.Length - CRC32C.ChecksumSize, length); + Assert.That(length, Is.EqualTo(packet.Length - CRC32C.ChecksumSize)); } [Test] @@ -40,11 +39,10 @@ public void ReturnsNilCountForBadChecksum() //Fake a change to the data to cause data/crc missmatch packet[4] = 0; - int offset = 0; int length = packet.Length; - _crc32Layer.ProcessInboundPacket(ref _dummyEndpoint, ref packet, ref offset, ref length); + _crc32Layer.ProcessInboundPacket(ref _dummyEndpoint, ref packet, ref length); - Assert.AreEqual(0, length); + Assert.That(length, Is.EqualTo(0)); } [Test] @@ -52,12 +50,11 @@ public void ReturnsNilCountForTooShortMessage() { byte[] packet = new byte[2]; - int offset = 0; int length = packet.Length; - _crc32Layer.ProcessInboundPacket(ref _dummyEndpoint, ref packet, ref offset, ref length); + _crc32Layer.ProcessInboundPacket(ref _dummyEndpoint, ref packet, ref length); - Assert.AreEqual(0, length); + Assert.That(length, Is.EqualTo(0)); } [Test] @@ -72,7 +69,7 @@ public void CanSendAndReceiveSameMessage() int offset = 0; int length = message.Length; _crc32Layer.ProcessOutBoundPacket(ref _dummyEndpoint, ref package, ref offset, ref length); - _crc32Layer.ProcessInboundPacket(ref _dummyEndpoint, ref package, ref offset, ref length); + _crc32Layer.ProcessInboundPacket(ref _dummyEndpoint, ref package, ref length); } private static byte[] GetTestPacketWithCrc() diff --git a/LiteNetLib.Tests/CommunicationTest.cs b/LiteNetLib.Tests/CommunicationTest.cs index cb8b905e..fcc8224f 100644 --- a/LiteNetLib.Tests/CommunicationTest.cs +++ b/LiteNetLib.Tests/CommunicationTest.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.Text; @@ -11,15 +12,6 @@ namespace LiteNetLib.Tests { - class LibErrorChecker : INetLogger - { - public void WriteNet(NetLogLevel level, string str, params object[] args) - { - if(level == NetLogLevel.Error || level == NetLogLevel.Warning) - Assert.Fail(str, args); - } - } - [TestFixture] [Category("Communication")] public class CommunicationTest @@ -38,12 +30,15 @@ public void TearDown() ManagerStack?.Dispose(); } - private const int DefaultPort = 9050; + private static readonly int DefaultPort = TestPorts.ForFramework(9050); + private static readonly int ReconnectPort = TestPorts.ForFramework(10123); + private static readonly int ReuseClientPort = TestPorts.ForFramework(9049); private const string DefaultAppKey = "test_server"; + private static readonly byte[] DefaultAppKeyBytes = [12, 0, 116, 101, 115, 116, 95, 115, 101, 114, 118, 101, 114]; public NetManagerStack ManagerStack { get; set; } - [Test, Timeout(TestTimeout)] + [Test, CancelAfter(TestTimeout)] public void ConnectionByIpV4() { var server = ManagerStack.Server(1); @@ -56,45 +51,40 @@ public void ConnectionByIpV4() server.PollEvents(); } - Assert.AreEqual(1, server.ConnectedPeersCount); - Assert.AreEqual(1, client.ConnectedPeersCount); + Assert.That(server.ConnectedPeersCount, Is.EqualTo(1)); + Assert.That(client.ConnectedPeersCount, Is.EqualTo(1)); } - [Test, Timeout(TestTimeout)] - public void ConnectionDualMode() + [Test, CancelAfter(TestTimeout)] + public void P2PConnect() { - var listener = new EventBasedNetListener(); - var server = new NetManager(listener, new Crc32cLayer()); - server.IPv6Mode = IPv6Mode.DualMode; - server.Start(DefaultPort); - - listener.ConnectionRequestEvent += request => request.AcceptIfKey(DefaultAppKey); - var client1 = ManagerStack.Client(1); var client2 = ManagerStack.Client(2); - client1.Connect("127.0.0.1", DefaultPort, DefaultAppKey); - client2.Connect("::1", DefaultPort, DefaultAppKey); - while (server.ConnectedPeersCount != 2 || client1.ConnectedPeersCount != 1 || client2.ConnectedPeersCount != 1) + client1.Connect("127.0.0.1", client2.LocalPort, DefaultAppKey); + client2.Connect("127.0.0.1", client1.LocalPort, DefaultAppKey); + + while (client1.ConnectedPeersCount != 1 || client2.ConnectedPeersCount != 1) { Thread.Sleep(15); - server.PollEvents(); + client1.PollEvents(); + client2.PollEvents(); } - Assert.AreEqual(2, server.ConnectedPeersCount); - Assert.AreEqual(1, client1.ConnectedPeersCount); - Assert.AreEqual(1, client2.ConnectedPeersCount); - server.Stop(false); + Assert.That(client1.ConnectedPeersCount, Is.EqualTo(1)); + Assert.That(client2.ConnectedPeersCount, Is.EqualTo(1)); } - [Test, Timeout(TestTimeout)] - public void P2PConnect() + [Test, CancelAfter(TestTimeout)] + public void P2PConnectWithSpan() { var client1 = ManagerStack.Client(1); var client2 = ManagerStack.Client(2); - client1.Connect("127.0.0.1", client2.LocalPort, DefaultAppKey); - client2.Connect("127.0.0.1", client1.LocalPort, DefaultAppKey); + IPEndPoint endPoint1 = new IPEndPoint(IPAddress.Loopback, client2.LocalPort); + IPEndPoint endPoint2 = new IPEndPoint(IPAddress.Loopback, client1.LocalPort); + client1.Connect(endPoint1, DefaultAppKeyBytes.AsSpan()); + client2.Connect(endPoint2, DefaultAppKeyBytes.AsSpan()); while (client1.ConnectedPeersCount != 1 || client2.ConnectedPeersCount != 1) { @@ -103,11 +93,11 @@ public void P2PConnect() client2.PollEvents(); } - Assert.AreEqual(1, client1.ConnectedPeersCount); - Assert.AreEqual(1, client2.ConnectedPeersCount); + Assert.That(client1.ConnectedPeersCount, Is.EqualTo(1)); + Assert.That(client2.ConnectedPeersCount, Is.EqualTo(1)); } - [Test, Timeout(TestTimeout)] + [Test, CancelAfter(TestTimeout)] public void ConnectionByIpV4Unsynced() { var server = ManagerStack.Server(1); @@ -121,11 +111,11 @@ public void ConnectionByIpV4Unsynced() Thread.Sleep(15); } - Assert.AreEqual(1, server.ConnectedPeersCount); - Assert.AreEqual(1, client.ConnectedPeersCount); + Assert.That(server.ConnectedPeersCount, Is.EqualTo(1)); + Assert.That(client.ConnectedPeersCount, Is.EqualTo(1)); } - [Test, Timeout(TestTimeout)] + [Test, CancelAfter(TestTimeout)] public void DeliveryTest() { var server = ManagerStack.Server(1); @@ -135,7 +125,7 @@ public void DeliveryTest() const int testSize = 250 * 1024; ManagerStack.ClientListener(1).DeliveryEvent += (peer, obj) => { - Assert.AreEqual(5, (int)obj); + Assert.That((int)obj, Is.EqualTo(5)); msgDelivered = true; }; ManagerStack.ClientListener(1).PeerConnectedEvent += peer => @@ -150,11 +140,11 @@ public void DeliveryTest() }; ManagerStack.ServerListener(1).NetworkReceiveEvent += (peer, reader, channel, method) => { - Assert.AreEqual(testSize, reader.UserDataSize); - Assert.AreEqual(196, reader.RawData[reader.UserDataOffset]); - Assert.AreEqual(32, reader.RawData[reader.UserDataOffset + 7000]); - Assert.AreEqual(200, reader.RawData[reader.UserDataOffset + 12499]); - Assert.AreEqual(254, reader.RawData[reader.UserDataOffset + testSize - 1]); + Assert.That(reader.UserDataSize, Is.EqualTo(testSize)); + Assert.That(reader.RawData[reader.UserDataOffset], Is.EqualTo(196)); + Assert.That(reader.RawData[reader.UserDataOffset + 7000], Is.EqualTo(32)); + Assert.That(reader.RawData[reader.UserDataOffset + 12499], Is.EqualTo(200)); + Assert.That(reader.RawData[reader.UserDataOffset + testSize - 1], Is.EqualTo(254)); msgReceived = true; }; @@ -167,11 +157,11 @@ public void DeliveryTest() client.PollEvents(); } - Assert.AreEqual(1, server.ConnectedPeersCount); - Assert.AreEqual(1, client.ConnectedPeersCount); + Assert.That(server.ConnectedPeersCount, Is.EqualTo(1)); + Assert.That(client.ConnectedPeersCount, Is.EqualTo(1)); } - [Test, Timeout(TestTimeout)] + [Test, CancelAfter(TestTimeout)] public void PeerNotFoundTest() { var server = ManagerStack.Server(1); @@ -193,13 +183,13 @@ public void PeerNotFoundTest() } client.PollEvents(); - Assert.AreEqual(0, server.ConnectedPeersCount); - Assert.AreEqual(0, client.ConnectedPeersCount); - Assert.IsTrue(disconnectInfo.HasValue); - Assert.AreEqual(DisconnectReason.PeerNotFound, disconnectInfo.Value.Reason); + Assert.That(server.ConnectedPeersCount, Is.EqualTo(0)); + Assert.That(client.ConnectedPeersCount, Is.EqualTo(0)); + Assert.That(disconnectInfo.HasValue, Is.True); + Assert.That(disconnectInfo.Value.Reason, Is.EqualTo(DisconnectReason.PeerNotFound)); } - [Test, Timeout(10000)] + [Test, CancelAfter(10000)] public void ConnectionFailedTest() { NetManager client = ManagerStack.Client(1); @@ -221,11 +211,11 @@ public void ConnectionFailedTest() client.PollEvents(); } - Assert.True(result); - Assert.AreEqual(DisconnectReason.ConnectionFailed, disconnectInfo.Reason); + Assert.That(result, Is.True); + Assert.That(disconnectInfo.Reason, Is.EqualTo(DisconnectReason.ConnectionFailed)); } - [Test, Timeout(10000)] + [Test, CancelAfter(10000)] public void NetPeerDisconnectTimeout() { NetManager client = ManagerStack.Client(1); @@ -243,18 +233,18 @@ public void NetPeerDisconnectTimeout() client.PollEvents(); } - Assert.AreEqual(ConnectionState.Connected, clientServerPeer.ConnectionState); - Assert.True(server.ConnectedPeersCount == 1); + Assert.That(clientServerPeer.ConnectionState, Is.EqualTo(ConnectionState.Connected)); + Assert.That(server.ConnectedPeersCount, Is.EqualTo(1)); ManagerStack.ClientListener(1).PeerDisconnectedEvent += (peer, info) => { - Assert.AreEqual(clientServerPeer, peer); - Assert.AreEqual(DisconnectReason.Timeout, info.Reason); + Assert.That(peer, Is.EqualTo(clientServerPeer)); + Assert.That(info.Reason, Is.EqualTo(DisconnectReason.Timeout)); }; server.Stop(); - Assert.True(server.ConnectedPeersCount == 0); + Assert.That(server.ConnectedPeersCount, Is.EqualTo(0)); while (client.ConnectedPeersCount == 1) { Thread.Sleep(15); @@ -282,7 +272,7 @@ public void ReconnectTest() }; client.Stop(); - client.Start(10123); + client.Start(ReconnectPort); client.Connect("127.0.0.1", DefaultPort, DefaultAppKey); while (connectCount < 2) @@ -291,7 +281,7 @@ public void ReconnectTest() { client.Stop(); Thread.Sleep(500); - client.Start(10123); + client.Start(ReconnectPort); client.Connect("127.0.0.1", DefaultPort, DefaultAppKey); reconnected = true; } @@ -299,7 +289,7 @@ public void ReconnectTest() server.PollEvents(); Thread.Sleep(15); } - Assert.AreEqual(2, connectCount); + Assert.That(connectCount, Is.EqualTo(2)); } [Test] @@ -316,8 +306,8 @@ public void RejectTest() }; ManagerStack.ClientListener(1).PeerDisconnectedEvent += (peer, info) => { - Assert.AreEqual(true, info.Reason == DisconnectReason.ConnectionRejected); - Assert.AreEqual("reject_test", Encoding.UTF8.GetString(info.AdditionalData.GetRemainingBytes())); + Assert.That(info.Reason, Is.EqualTo(DisconnectReason.ConnectionRejected)); + Assert.That(Encoding.UTF8.GetString(info.AdditionalData.GetRemainingBytes()), Is.EqualTo("reject_test")); rejectReceived = true; }; @@ -330,8 +320,8 @@ public void RejectTest() Thread.Sleep(15); } - Assert.AreEqual(0, server.ConnectedPeersCount); - Assert.AreEqual(0, client.ConnectedPeersCount); + Assert.That(server.ConnectedPeersCount, Is.EqualTo(0)); + Assert.That(client.ConnectedPeersCount, Is.EqualTo(0)); } [Test] @@ -348,8 +338,8 @@ public void RejectForceTest() }; ManagerStack.ClientListener(1).PeerDisconnectedEvent += (peer, info) => { - Assert.AreEqual(true, info.Reason == DisconnectReason.ConnectionRejected); - Assert.AreEqual("reject_test", Encoding.UTF8.GetString(info.AdditionalData.GetRemainingBytes())); + Assert.That(info.Reason, Is.EqualTo(DisconnectReason.ConnectionRejected)); + Assert.That(Encoding.UTF8.GetString(info.AdditionalData.GetRemainingBytes()), Is.EqualTo("reject_test")); rejectReceived = true; }; @@ -362,11 +352,11 @@ public void RejectForceTest() Thread.Sleep(15); } - Assert.AreEqual(0, server.ConnectedPeersCount); - Assert.AreEqual(0, client.ConnectedPeersCount); + Assert.That(server.ConnectedPeersCount, Is.EqualTo(0)); + Assert.That(client.ConnectedPeersCount, Is.EqualTo(0)); } - [Test, Timeout(10000)] + [Test, CancelAfter(10000)] public void NetPeerDisconnectAll() { NetManager client = ManagerStack.Client(1); @@ -384,20 +374,20 @@ public void NetPeerDisconnectAll() client2.PollEvents(); } - Assert.AreEqual(ConnectionState.Connected, clientServerPeer.ConnectionState); - Assert.AreEqual(2, server.GetPeersCount(ConnectionState.Connected)); + Assert.That(clientServerPeer.ConnectionState, Is.EqualTo(ConnectionState.Connected)); + Assert.That(server.GetPeersCount(ConnectionState.Connected), Is.EqualTo(2)); ManagerStack.ClientListener(1).PeerDisconnectedEvent += (peer, info) => { byte[] bytes = info.AdditionalData.GetRemainingBytes(); - Assert.AreEqual(new byte[] { 1, 2, 3, 4 }, bytes); - Assert.AreEqual(clientServerPeer, peer); - Assert.AreEqual(DisconnectReason.RemoteConnectionClose, info.Reason); + Assert.That(bytes, Is.EqualTo(new byte[] { 1, 2, 3, 4 }).AsCollection); + Assert.That(peer, Is.EqualTo(clientServerPeer)); + Assert.That(info.Reason, Is.EqualTo(DisconnectReason.RemoteConnectionClose)); }; server.DisconnectAll(new byte[]{1, 2, 3, 4}, 0, 4); - Assert.AreEqual(0, server.GetPeersCount(ConnectionState.Connected)); + Assert.That(server.GetPeersCount(ConnectionState.Connected), Is.EqualTo(0)); while (client.GetPeersCount(ConnectionState.Connected) != 0) { @@ -409,11 +399,11 @@ public void NetPeerDisconnectAll() //Wait for client 'ShutdownOk' response Thread.Sleep(100); - Assert.AreEqual(0, server.ConnectedPeersCount); - Assert.AreEqual(ConnectionState.Disconnected, clientServerPeer.ConnectionState); + Assert.That(server.ConnectedPeersCount, Is.EqualTo(0)); + Assert.That(clientServerPeer.ConnectionState, Is.EqualTo(ConnectionState.Disconnected)); } - [Test, Timeout(TestTimeout)] + [Test, CancelAfter(TestTimeout)] public void DisconnectFromServerTest() { NetManager server = ManagerStack.Server(1); @@ -439,13 +429,13 @@ public void DisconnectFromServerTest() server.PollEvents(); } - Assert.True(clientDisconnected); - Assert.True(serverDisconnected); - Assert.AreEqual(0, server.ConnectedPeersCount); - Assert.AreEqual(0, client.ConnectedPeersCount); + Assert.That(clientDisconnected, Is.True); + Assert.That(serverDisconnected, Is.True); + Assert.That(server.ConnectedPeersCount, Is.EqualTo(0)); + Assert.That(client.ConnectedPeersCount, Is.EqualTo(0)); } - [Test, Timeout(5000)] + [Test, CancelAfter(5000)] public void EncryptTest() { EventBasedNetListener srvListener = new EventBasedNetListener(); @@ -464,20 +454,20 @@ public void EncryptTest() srv.PollEvents(); } Thread.Sleep(200); - Assert.AreEqual(1, srv.ConnectedPeersCount); - Assert.AreEqual(1, cli.ConnectedPeersCount); + Assert.That(srv.ConnectedPeersCount, Is.EqualTo(1)); + Assert.That(cli.ConnectedPeersCount, Is.EqualTo(1)); cli.Stop(); srv.Stop(); } - [Test, Timeout(5000)] + [Test, CancelAfter(5000)] public void ConnectAfterDisconnectWithSamePort() { NetManager server = ManagerStack.Server(1); EventBasedNetListener listener = new EventBasedNetListener(); NetManager client = new NetManager(listener, new Crc32cLayer()); - Assert.True(client.Start(9049)); + Assert.That(client.Start(ReuseClientPort), Is.True); client.Connect("127.0.0.1", DefaultPort, DefaultAppKey); while (server.ConnectedPeersCount != 1) { @@ -491,7 +481,7 @@ public void ConnectAfterDisconnectWithSamePort() { connected = true; }; - Assert.True(client.Start(9049)); + Assert.That(client.Start(ReuseClientPort), Is.True); client.Connect("127.0.0.1", DefaultPort, DefaultAppKey); while (!connected) @@ -501,13 +491,13 @@ public void ConnectAfterDisconnectWithSamePort() client.PollEvents(); } - Assert.True(connected); - Assert.AreEqual(1, server.ConnectedPeersCount); - Assert.AreEqual(1, client.ConnectedPeersCount); + Assert.That(connected, Is.True); + Assert.That(server.ConnectedPeersCount, Is.EqualTo(1)); + Assert.That(client.ConnectedPeersCount, Is.EqualTo(1)); client.Stop(); } - [Test, Timeout(TestTimeout)] + [Test, CancelAfter(TestTimeout)] public void DisconnectFromClientTest() { NetManager server = ManagerStack.Server(1); @@ -517,13 +507,13 @@ public void DisconnectFromClientTest() ManagerStack.ClientListener(1).PeerDisconnectedEvent += (peer, info) => { - Assert.AreEqual(DisconnectReason.DisconnectPeerCalled, info.Reason); - Assert.AreEqual(0, client.ConnectedPeersCount); + Assert.That(info.Reason, Is.EqualTo(DisconnectReason.DisconnectPeerCalled)); + Assert.That(client.ConnectedPeersCount, Is.EqualTo(0)); clientDisconnected = true; }; ManagerStack.ServerListener(1).PeerDisconnectedEvent += (peer, info) => { - Assert.AreEqual(DisconnectReason.RemoteConnectionClose, info.Reason); + Assert.That(info.Reason, Is.EqualTo(DisconnectReason.RemoteConnectionClose)); serverDisconnected = true; }; @@ -544,13 +534,13 @@ public void DisconnectFromClientTest() server.PollEvents(); } - Assert.True(clientDisconnected); - Assert.True(serverDisconnected); - Assert.AreEqual(0, server.ConnectedPeersCount); - Assert.AreEqual(0, client.ConnectedPeersCount); + Assert.That(clientDisconnected, Is.True); + Assert.That(serverDisconnected, Is.True); + Assert.That(server.ConnectedPeersCount, Is.EqualTo(0)); + Assert.That(client.ConnectedPeersCount, Is.EqualTo(0)); } - [Test, Timeout(10000)] + [Test, CancelAfter(10000)] public void ChannelsTest() { const int channelsCount = 64; @@ -587,7 +577,7 @@ public void ChannelsTest() }; ManagerStack.ServerListener(1).NetworkReceiveEvent += (peer, reader, channel, method) => { - Assert.AreEqual((DeliveryMethod)reader.GetByte(), method); + Assert.That((DeliveryMethod)reader.GetByte(), Is.EqualTo(method)); messagesReceived++; }; client.Connect("127.0.0.1", DefaultPort, DefaultAppKey); @@ -599,12 +589,12 @@ public void ChannelsTest() Thread.Sleep(15); } - Assert.AreEqual(methods.Length*channelsCount, messagesReceived); - Assert.AreEqual(1, server.ConnectedPeersCount); - Assert.AreEqual(1, client.ConnectedPeersCount); + Assert.That(messagesReceived, Is.EqualTo(methods.Length * channelsCount)); + Assert.That(server.ConnectedPeersCount, Is.EqualTo(1)); + Assert.That(client.ConnectedPeersCount, Is.EqualTo(1)); } - [Test, Timeout(TestTimeout)] + [Test, CancelAfter(TestTimeout)] public void ConnectionByIpV6() { var server = ManagerStack.Server(1); @@ -617,11 +607,11 @@ public void ConnectionByIpV6() server.PollEvents(); } - Assert.AreEqual(1, server.ConnectedPeersCount); - Assert.AreEqual(1, client.ConnectedPeersCount); + Assert.That(server.ConnectedPeersCount, Is.EqualTo(1)); + Assert.That(client.ConnectedPeersCount, Is.EqualTo(1)); } - [Test, Timeout(10000)] + [Test, CancelAfter(10000)] public void DiscoveryBroadcastTest() { var server = ManagerStack.Server(1); @@ -650,8 +640,8 @@ public void DiscoveryBroadcastTest() { if (point.AddressFamily == AddressFamily.InterNetworkV6) return; - Assert.AreEqual(type, UnconnectedMessageType.BasicMessage); - Assert.AreEqual("Server response", reader.GetString()); + Assert.That(type, Is.EqualTo(UnconnectedMessageType.BasicMessage)); + Assert.That(reader.GetString(), Is.EqualTo("Server response")); ManagerStack.Client(cache).Connect(point, DefaultAppKey); }; } @@ -666,27 +656,27 @@ public void DiscoveryBroadcastTest() Thread.Sleep(15); } - Assert.AreEqual(clientCount, server.ConnectedPeersCount); + Assert.That(server.ConnectedPeersCount, Is.EqualTo(clientCount)); ManagerStack.ClientForeach( (i, manager, l) => { - Assert.AreEqual(manager.ConnectedPeersCount, 1); + Assert.That(manager.ConnectedPeersCount, Is.EqualTo(1)); }); } [Test] public void HelperManagerStackTest() { - Assert.AreSame(ManagerStack.Client(1), ManagerStack.Client(1)); - Assert.AreNotSame(ManagerStack.Client(1), ManagerStack.Client(2)); - Assert.AreSame(ManagerStack.Client(2), ManagerStack.Client(2)); + Assert.That(ManagerStack.Client(1), Is.SameAs(ManagerStack.Client(1))); + Assert.That(ManagerStack.Client(1), Is.Not.SameAs(ManagerStack.Client(2))); + Assert.That(ManagerStack.Client(2), Is.SameAs(ManagerStack.Client(2))); - Assert.AreSame(ManagerStack.Server(1), ManagerStack.Server(1)); - Assert.AreNotSame(ManagerStack.Server(1), ManagerStack.Client(1)); - Assert.AreNotSame(ManagerStack.Server(1), ManagerStack.Client(2)); + Assert.That(ManagerStack.Server(1), Is.SameAs(ManagerStack.Server(1))); + Assert.That(ManagerStack.Server(1), Is.Not.SameAs(ManagerStack.Client(1))); + Assert.That(ManagerStack.Server(1), Is.Not.SameAs(ManagerStack.Client(2))); } - [Test, Timeout(TestTimeout)] + [Test, CancelAfter(TestTimeout)] public void ManualMode() { var serverListener = new EventBasedNetListener(); @@ -695,7 +685,7 @@ public void ManualMode() serverListener.ConnectionRequestEvent += request => request.AcceptIfKey(DefaultAppKey); var client = ManagerStack.Client(1); - Assert.IsTrue(server.StartInManualMode(DefaultPort)); + Assert.That(server.StartInManualMode(DefaultPort), Is.True); client.Connect("127.0.0.1", DefaultPort, DefaultAppKey); @@ -706,12 +696,12 @@ public void ManualMode() server.ManualUpdate(15); } - Assert.AreEqual(1, server.ConnectedPeersCount); - Assert.AreEqual(1, client.ConnectedPeersCount); + Assert.That(server.ConnectedPeersCount, Is.EqualTo(1)); + Assert.That(client.ConnectedPeersCount, Is.EqualTo(1)); server.Stop(); } - [Test] + [Test, CancelAfter(TestTimeout)] public void SendRawDataToAll() { var clientCount = 10; @@ -729,9 +719,9 @@ public void SendRawDataToAll() server.PollEvents(); } - Assert.AreEqual(server.ConnectedPeersCount, clientCount); + Assert.That(server.ConnectedPeersCount, Is.EqualTo(clientCount)); Thread.Sleep(100); - ManagerStack.ClientForeach((i, manager, l) => Assert.AreEqual(manager.ConnectedPeersCount, 1)); + ManagerStack.ClientForeach((i, manager, l) => Assert.That(manager.ConnectedPeersCount, Is.EqualTo(1))); var dataStack = new Stack(clientCount); @@ -748,12 +738,12 @@ public void SendRawDataToAll() Thread.Sleep(10); } - Assert.AreEqual(dataStack.Count, clientCount); + Assert.That(dataStack.Count, Is.EqualTo(clientCount)); - Assert.AreEqual(server.ConnectedPeersCount, clientCount); + Assert.That(server.ConnectedPeersCount, Is.EqualTo(clientCount)); for (ushort i = 1; i <= clientCount; i++) { - Assert.AreEqual(ManagerStack.Client(i).ConnectedPeersCount, 1); + Assert.That(ManagerStack.Client(i).ConnectedPeersCount, Is.EqualTo(1)); Assert.That(data, Is.EqualTo(dataStack.Pop()).AsCollection); } } diff --git a/LiteNetLib.Tests/LibErrorChecker.cs b/LiteNetLib.Tests/LibErrorChecker.cs new file mode 100644 index 00000000..d99538a7 --- /dev/null +++ b/LiteNetLib.Tests/LibErrorChecker.cs @@ -0,0 +1,13 @@ +using System; +using NUnit.Framework; + +namespace LiteNetLib.Tests; + +class LibErrorChecker : INetLogger +{ + public void WriteNet(NetLogLevel level, string str, params object[] args) + { + if(level == NetLogLevel.Error || level == NetLogLevel.Warning) + Assert.Fail(string.Format(str, args)); + } +} diff --git a/LiteNetLib.Tests/LiteCommunicationTest.cs b/LiteNetLib.Tests/LiteCommunicationTest.cs new file mode 100644 index 00000000..608d655d --- /dev/null +++ b/LiteNetLib.Tests/LiteCommunicationTest.cs @@ -0,0 +1,697 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using LiteNetLib.Layers; +using LiteNetLib.Tests.TestUtility; +using LiteNetLib.Utils; + +using NUnit.Framework; + +namespace LiteNetLib.Tests +{ + [TestFixture] + [Category("Communication")] + public class LiteCommunicationTest + { + const int TestTimeout = 4000; + [SetUp] + public void Init() + { + NetDebug.Logger = new LibErrorChecker(); + ManagerStack = new LiteNetManagerStack(DefaultAppKey, DefaultPort); + } + + [TearDown] + public void TearDown() + { + ManagerStack?.Dispose(); + } + + private static readonly int DefaultPort = TestPorts.ForFramework(9050); + private static readonly int ReconnectPort = TestPorts.ForFramework(10123); + private static readonly int ReuseClientPort = TestPorts.ForFramework(9049); + private const string DefaultAppKey = "test_server"; + private static readonly byte[] DefaultAppKeyBytes = [12, 0, 116, 101, 115, 116, 95, 115, 101, 114, 118, 101, 114]; + + public LiteNetManagerStack ManagerStack { get; set; } + + [Test, CancelAfter(TestTimeout)] + public void ConnectionByIpV4() + { + var server = ManagerStack.Server(1); + var client = ManagerStack.Client(1); + client.Connect("127.0.0.1", DefaultPort, DefaultAppKey); + + while (server.ConnectedPeersCount != 1 || client.ConnectedPeersCount != 1) + { + Thread.Sleep(15); + server.PollEvents(); + } + + Assert.That(server.ConnectedPeersCount, Is.EqualTo(1)); + Assert.That(client.ConnectedPeersCount, Is.EqualTo(1)); + } + + [Test, CancelAfter(TestTimeout)] + public void P2PConnect() + { + var client1 = ManagerStack.Client(1); + var client2 = ManagerStack.Client(2); + + client1.Connect("127.0.0.1", client2.LocalPort, DefaultAppKey); + client2.Connect("127.0.0.1", client1.LocalPort, DefaultAppKey); + + while (client1.ConnectedPeersCount != 1 || client2.ConnectedPeersCount != 1) + { + Thread.Sleep(15); + client1.PollEvents(); + client2.PollEvents(); + } + + Assert.That(client1.ConnectedPeersCount, Is.EqualTo(1)); + Assert.That(client2.ConnectedPeersCount, Is.EqualTo(1)); + } + + [Test, CancelAfter(TestTimeout)] + public void P2PConnectWithSpan() + { + var client1 = ManagerStack.Client(1); + var client2 = ManagerStack.Client(2); + + IPEndPoint endPoint1 = new IPEndPoint(IPAddress.Loopback, client2.LocalPort); + IPEndPoint endPoint2 = new IPEndPoint(IPAddress.Loopback, client1.LocalPort); + client1.Connect(endPoint1, DefaultAppKeyBytes.AsSpan()); + client2.Connect(endPoint2, DefaultAppKeyBytes.AsSpan()); + + while (client1.ConnectedPeersCount != 1 || client2.ConnectedPeersCount != 1) + { + Thread.Sleep(15); + client1.PollEvents(); + client2.PollEvents(); + } + + Assert.That(client1.ConnectedPeersCount, Is.EqualTo(1)); + Assert.That(client2.ConnectedPeersCount, Is.EqualTo(1)); + } + + [Test, CancelAfter(TestTimeout)] + public void ConnectionByIpV4Unsynced() + { + var server = ManagerStack.Server(1); + server.UnsyncedEvents = true; + var client = ManagerStack.Client(1); + client.UnsyncedEvents = true; + client.Connect("127.0.0.1", DefaultPort, DefaultAppKey); + + while (server.ConnectedPeersCount != 1 || client.ConnectedPeersCount != 1) + { + Thread.Sleep(15); + } + + Assert.That(server.ConnectedPeersCount, Is.EqualTo(1)); + Assert.That(client.ConnectedPeersCount, Is.EqualTo(1)); + } + + [Test, CancelAfter(TestTimeout)] + public void DeliveryTest() + { + var server = ManagerStack.Server(1); + var client = ManagerStack.Client(1); + bool msgDelivered = false; + bool msgReceived = false; + const int testSize = 250 * 1024; + ManagerStack.ClientListener(1).DeliveryEvent += (peer, obj) => + { + Assert.That((int)obj, Is.EqualTo(5)); + msgDelivered = true; + }; + ManagerStack.ClientListener(1).PeerConnectedEvent += peer => + { + int testData = 5; + byte[] arr = new byte[testSize]; + arr[0] = 196; + arr[7000] = 32; + arr[12499] = 200; + arr[testSize - 1] = 254; + peer.SendWithDeliveryEvent(arr, DeliveryMethod.ReliableUnordered, testData); + }; + ManagerStack.ServerListener(1).NetworkReceiveEvent += (peer, reader, method) => + { + Assert.That(reader.UserDataSize, Is.EqualTo(testSize)); + Assert.That(reader.RawData[reader.UserDataOffset], Is.EqualTo(196)); + Assert.That(reader.RawData[reader.UserDataOffset + 7000], Is.EqualTo(32)); + Assert.That(reader.RawData[reader.UserDataOffset + 12499], Is.EqualTo(200)); + Assert.That(reader.RawData[reader.UserDataOffset + testSize - 1], Is.EqualTo(254)); + msgReceived = true; + }; + + client.Connect("127.0.0.1", DefaultPort, DefaultAppKey); + + while (server.ConnectedPeersCount != 1 || client.ConnectedPeersCount != 1 || !msgDelivered || !msgReceived) + { + Thread.Sleep(15); + server.PollEvents(); + client.PollEvents(); + } + + Assert.That(server.ConnectedPeersCount, Is.EqualTo(1)); + Assert.That(client.ConnectedPeersCount, Is.EqualTo(1)); + } + + [Test, CancelAfter(TestTimeout)] + public void PeerNotFoundTest() + { + var server = ManagerStack.Server(1); + var client = ManagerStack.Client(1); + DisconnectInfo? disconnectInfo = null; + ManagerStack.ClientListener(1).PeerDisconnectedEvent += (peer, info) => disconnectInfo = info; + client.Connect("127.0.0.1", DefaultPort, DefaultAppKey); + + while (server.ConnectedPeersCount != 1 || client.ConnectedPeersCount != 1) + { + Thread.Sleep(15); + server.PollEvents(); + } + server.Stop(false); + server.Start(DefaultPort); + while (client.ConnectedPeersCount == 1) + { + Thread.Sleep(15); + } + client.PollEvents(); + + Assert.That(server.ConnectedPeersCount, Is.EqualTo(0)); + Assert.That(client.ConnectedPeersCount, Is.EqualTo(0)); + Assert.That(disconnectInfo.HasValue, Is.True); + Assert.That(disconnectInfo.Value.Reason, Is.EqualTo(DisconnectReason.PeerNotFound)); + } + + [Test, CancelAfter(10000)] + public void ConnectionFailedTest() + { + var client = ManagerStack.Client(1); + + var result = false; + DisconnectInfo disconnectInfo = default; + + ManagerStack.ClientListener(1).PeerDisconnectedEvent += (peer, info) => + { + result = true; + disconnectInfo = info; + }; + + client.Connect("127.0.0.2", DefaultPort, DefaultAppKey); + + while (!result) + { + Thread.Sleep(15); + client.PollEvents(); + } + + Assert.That(result, Is.True); + Assert.That(disconnectInfo.Reason, Is.EqualTo(DisconnectReason.ConnectionFailed)); + } + + [Test, CancelAfter(10000)] + public void NetPeerDisconnectTimeout() + { + var client = ManagerStack.Client(1); + var server = ManagerStack.Server(1); + + //Default 5 sec timeout for local network is too mach, set 1 for test + server.DisconnectTimeout = 1000; + + var clientServerPeer = client.Connect("127.0.0.1", DefaultPort, DefaultAppKey); + + while (clientServerPeer.ConnectionState != ConnectionState.Connected) + { + Thread.Sleep(15); + server.PollEvents(); + client.PollEvents(); + } + + Assert.That(clientServerPeer.ConnectionState, Is.EqualTo(ConnectionState.Connected)); + Assert.That(server.ConnectedPeersCount, Is.EqualTo(1)); + + ManagerStack.ClientListener(1).PeerDisconnectedEvent += (peer, info) => + { + Assert.That(peer, Is.EqualTo(clientServerPeer)); + Assert.That(info.Reason, Is.EqualTo(DisconnectReason.Timeout)); + }; + + server.Stop(); + + Assert.That(server.ConnectedPeersCount, Is.EqualTo(0)); + while (client.ConnectedPeersCount == 1) + { + Thread.Sleep(15); + } + } + + [Test] + public void ReconnectTest() + { + var server = ManagerStack.Server(1); + var client = ManagerStack.Client(1); + int connectCount = 0; + bool reconnected = false; + ManagerStack.ServerListener(1).PeerConnectedEvent += peer => + { + if (connectCount == 0) + { + byte[] data = {1,2,3,4,5,6,7,8,9}; + for (int i = 0; i < 1000; i++) + { + peer.Send(data, DeliveryMethod.ReliableOrdered); + } + } + connectCount++; + }; + + client.Stop(); + client.Start(ReconnectPort); + client.Connect("127.0.0.1", DefaultPort, DefaultAppKey); + + while (connectCount < 2) + { + if (connectCount == 1 && !reconnected) + { + client.Stop(); + Thread.Sleep(500); + client.Start(ReconnectPort); + client.Connect("127.0.0.1", DefaultPort, DefaultAppKey); + reconnected = true; + } + client.PollEvents(); + server.PollEvents(); + Thread.Sleep(15); + } + Assert.That(connectCount, Is.EqualTo(2)); + } + + [Test] + public void RejectTest() + { + var server = ManagerStack.Server(1); + var client = ManagerStack.Client(1); + bool rejectReceived = false; + + ManagerStack.ServerListener(1).ClearConnectionRequestEvent(); + ManagerStack.ServerListener(1).ConnectionRequestEvent += request => + { + request.Reject(Encoding.UTF8.GetBytes("reject_test")); + }; + ManagerStack.ClientListener(1).PeerDisconnectedEvent += (peer, info) => + { + Assert.That(info.Reason, Is.EqualTo(DisconnectReason.ConnectionRejected)); + Assert.That(Encoding.UTF8.GetString(info.AdditionalData.GetRemainingBytes()), Is.EqualTo("reject_test")); + rejectReceived = true; + }; + + client.Connect("127.0.0.1", DefaultPort, DefaultAppKey); + + while (!rejectReceived) + { + client.PollEvents(); + server.PollEvents(); + Thread.Sleep(15); + } + + Assert.That(server.ConnectedPeersCount, Is.EqualTo(0)); + Assert.That(client.ConnectedPeersCount, Is.EqualTo(0)); + } + + [Test] + public void RejectForceTest() + { + var server = ManagerStack.Server(1); + var client = ManagerStack.Client(1); + bool rejectReceived = false; + + ManagerStack.ServerListener(1).ClearConnectionRequestEvent(); + ManagerStack.ServerListener(1).ConnectionRequestEvent += request => + { + request.RejectForce(Encoding.UTF8.GetBytes("reject_test")); + }; + ManagerStack.ClientListener(1).PeerDisconnectedEvent += (peer, info) => + { + Assert.That(info.Reason, Is.EqualTo(DisconnectReason.ConnectionRejected)); + Assert.That(Encoding.UTF8.GetString(info.AdditionalData.GetRemainingBytes()), Is.EqualTo("reject_test")); + rejectReceived = true; + }; + + client.Connect("127.0.0.1", DefaultPort, DefaultAppKey); + + while (!rejectReceived) + { + client.PollEvents(); + server.PollEvents(); + Thread.Sleep(15); + } + + Assert.That(server.ConnectedPeersCount, Is.EqualTo(0)); + Assert.That(client.ConnectedPeersCount, Is.EqualTo(0)); + } + + [Test, CancelAfter(10000)] + public void NetPeerDisconnectAll() + { + var client = ManagerStack.Client(1); + var client2 = ManagerStack.Client(2); + var server = ManagerStack.Server(1); + + var clientServerPeer = client.Connect("127.0.0.1", DefaultPort, DefaultAppKey); + client2.Connect("127.0.0.1", DefaultPort, DefaultAppKey); + + while (clientServerPeer.ConnectionState != ConnectionState.Connected) + { + Thread.Sleep(15); + server.PollEvents(); + client.PollEvents(); + client2.PollEvents(); + } + + Assert.That(clientServerPeer.ConnectionState, Is.EqualTo(ConnectionState.Connected)); + Assert.That(server.GetPeersCount(ConnectionState.Connected), Is.EqualTo(2)); + + ManagerStack.ClientListener(1).PeerDisconnectedEvent += (peer, info) => + { + byte[] bytes = info.AdditionalData.GetRemainingBytes(); + Assert.That(bytes, Is.EqualTo(new byte[] { 1, 2, 3, 4 }).AsCollection); + Assert.That(peer, Is.EqualTo(clientServerPeer)); + Assert.That(info.Reason, Is.EqualTo(DisconnectReason.RemoteConnectionClose)); + }; + + server.DisconnectAll(new byte[]{1, 2, 3, 4}, 0, 4); + + Assert.That(server.GetPeersCount(ConnectionState.Connected), Is.EqualTo(0)); + + while (client.GetPeersCount(ConnectionState.Connected) != 0) + { + Thread.Sleep(15); + client.PollEvents(); + server.PollEvents(); + } + + //Wait for client 'ShutdownOk' response + Thread.Sleep(100); + + Assert.That(server.ConnectedPeersCount, Is.EqualTo(0)); + Assert.That(clientServerPeer.ConnectionState, Is.EqualTo(ConnectionState.Disconnected)); + } + + [Test, CancelAfter(TestTimeout)] + public void DisconnectFromServerTest() + { + var server = ManagerStack.Server(1); + var client = ManagerStack.Client(1); + var clientDisconnected = false; + var serverDisconnected = false; + ManagerStack.ClientListener(1).PeerDisconnectedEvent += (peer, info) => { clientDisconnected = true; }; + ManagerStack.ServerListener(1).PeerDisconnectedEvent += (peer, info) => { serverDisconnected = true; }; + + client.Connect("127.0.0.1", DefaultPort, DefaultAppKey); + while (server.ConnectedPeersCount != 1) + { + Thread.Sleep(15); + server.PollEvents(); + } + + server.DisconnectPeer(server.FirstPeer); + + while (!(clientDisconnected && serverDisconnected)) + { + Thread.Sleep(15); + client.PollEvents(); + server.PollEvents(); + } + + Assert.That(clientDisconnected, Is.True); + Assert.That(serverDisconnected, Is.True); + Assert.That(server.ConnectedPeersCount, Is.EqualTo(0)); + Assert.That(client.ConnectedPeersCount, Is.EqualTo(0)); + } + + [Test, CancelAfter(5000)] + public void EncryptTest() + { + var srvListener = new EventBasedLiteNetListener(); + var cliListener = new EventBasedLiteNetListener(); + var srv = new LiteNetManager(srvListener, new XorEncryptLayer("secret_key")); + var cli = new LiteNetManager(cliListener, new XorEncryptLayer("secret_key")); + srv.Start(DefaultPort + 1); + cli.Start(); + + srvListener.ConnectionRequestEvent += request => { request.AcceptIfKey(DefaultAppKey); }; + cli.Connect("127.0.0.1", DefaultPort + 1, DefaultAppKey); + + while (srv.ConnectedPeersCount != 1) + { + Thread.Sleep(15); + srv.PollEvents(); + } + Thread.Sleep(200); + Assert.That(srv.ConnectedPeersCount, Is.EqualTo(1)); + Assert.That(cli.ConnectedPeersCount, Is.EqualTo(1)); + cli.Stop(); + srv.Stop(); + } + + [Test, CancelAfter(5000)] + public void ConnectAfterDisconnectWithSamePort() + { + var server = ManagerStack.Server(1); + + var listener = new EventBasedLiteNetListener(); + var client = new LiteNetManager(listener, new Crc32cLayer()); + Assert.That(client.Start(ReuseClientPort), Is.True); + client.Connect("127.0.0.1", DefaultPort, DefaultAppKey); + while (server.ConnectedPeersCount != 1) + { + Thread.Sleep(15); + server.PollEvents(); + } + client.Stop(); + + var connected = false; + listener.PeerConnectedEvent += (peer) => + { + connected = true; + }; + Assert.That(client.Start(ReuseClientPort), Is.True); + client.Connect("127.0.0.1", DefaultPort, DefaultAppKey); + + while (!connected) + { + Thread.Sleep(15); + server.PollEvents(); + client.PollEvents(); + } + + Assert.That(connected, Is.True); + Assert.That(server.ConnectedPeersCount, Is.EqualTo(1)); + Assert.That(client.ConnectedPeersCount, Is.EqualTo(1)); + client.Stop(); + } + + [Test, CancelAfter(TestTimeout)] + public void DisconnectFromClientTest() + { + var server = ManagerStack.Server(1); + var client = ManagerStack.Client(1); + var clientDisconnected = false; + var serverDisconnected = false; + + ManagerStack.ClientListener(1).PeerDisconnectedEvent += (peer, info) => + { + Assert.That(info.Reason, Is.EqualTo(DisconnectReason.DisconnectPeerCalled)); + Assert.That(client.ConnectedPeersCount, Is.EqualTo(0)); + clientDisconnected = true; + }; + ManagerStack.ServerListener(1).PeerDisconnectedEvent += (peer, info) => + { + Assert.That(info.Reason, Is.EqualTo(DisconnectReason.RemoteConnectionClose)); + serverDisconnected = true; + }; + + var serverPeer = client.Connect("127.0.0.1", DefaultPort, DefaultAppKey); + while (server.ConnectedPeersCount != 1) + { + Thread.Sleep(15); + server.PollEvents(); + } + + //User server peer from client + serverPeer.Disconnect(); + + while (!(clientDisconnected && serverDisconnected)) + { + Thread.Sleep(15); + client.PollEvents(); + server.PollEvents(); + } + + Assert.That(clientDisconnected, Is.True); + Assert.That(serverDisconnected, Is.True); + Assert.That(server.ConnectedPeersCount, Is.EqualTo(0)); + Assert.That(client.ConnectedPeersCount, Is.EqualTo(0)); + } + + [Test, CancelAfter(TestTimeout)] + public void ConnectionByIpV6() + { + var server = ManagerStack.Server(1); + var client = ManagerStack.Client(1); + client.Connect("::1", DefaultPort, DefaultAppKey); + + while (server.ConnectedPeersCount != 1 || client.ConnectedPeersCount != 1) + { + Thread.Sleep(15); + server.PollEvents(); + } + + Assert.That(server.ConnectedPeersCount, Is.EqualTo(1)); + Assert.That(client.ConnectedPeersCount, Is.EqualTo(1)); + } + + [Test, CancelAfter(10000)] + public void DiscoveryBroadcastTest() + { + var server = ManagerStack.Server(1); + var clientCount = 10; + + server.BroadcastReceiveEnabled = true; + + var writer = new NetDataWriter(); + writer.Put("Client request"); + + ManagerStack.ServerListener(1).NetworkReceiveUnconnectedEvent += (point, reader, type) => + { + if (type == UnconnectedMessageType.Broadcast) + { + var serverWriter = new NetDataWriter(); + serverWriter.Put("Server response"); + server.SendUnconnectedMessage(serverWriter, point); + } + }; + + for (ushort i = 1; i <= clientCount; i++) + { + var cache = i; + ManagerStack.Client(i).UnconnectedMessagesEnabled = true; + ManagerStack.ClientListener(i).NetworkReceiveUnconnectedEvent += (point, reader, type) => + { + if (point.AddressFamily == AddressFamily.InterNetworkV6) + return; + Assert.That(type, Is.EqualTo(UnconnectedMessageType.BasicMessage)); + Assert.That(reader.GetString(), Is.EqualTo("Server response")); + ManagerStack.Client(cache).Connect(point, DefaultAppKey); + }; + } + + ManagerStack.ClientForeach((i, manager, l) => manager.SendBroadcast(writer, DefaultPort)); + + while (server.ConnectedPeersCount < clientCount) + { + server.PollEvents(); + ManagerStack.ClientForeach((i, manager, l) => manager.PollEvents()); + + Thread.Sleep(15); + } + + Assert.That(server.ConnectedPeersCount, Is.EqualTo(clientCount)); + ManagerStack.ClientForeach( + (i, manager, l) => + { + Assert.That(manager.ConnectedPeersCount, Is.EqualTo(1)); + }); + } + + [Test] + public void HelperManagerStackTest() + { + Assert.That(ManagerStack.Client(1), Is.SameAs(ManagerStack.Client(1))); + Assert.That(ManagerStack.Client(1), Is.Not.SameAs(ManagerStack.Client(2))); + Assert.That(ManagerStack.Client(2), Is.SameAs(ManagerStack.Client(2))); + + Assert.That(ManagerStack.Server(1), Is.SameAs(ManagerStack.Server(1))); + Assert.That(ManagerStack.Server(1), Is.Not.SameAs(ManagerStack.Client(1))); + Assert.That(ManagerStack.Server(1), Is.Not.SameAs(ManagerStack.Client(2))); + } + + [Test, CancelAfter(TestTimeout)] + public void ManualMode() + { + var serverListener = new EventBasedLiteNetListener(); + var server = new LiteNetManager(serverListener, new Crc32cLayer()); + + serverListener.ConnectionRequestEvent += request => request.AcceptIfKey(DefaultAppKey); + + var client = ManagerStack.Client(1); + Assert.That(server.StartInManualMode(DefaultPort), Is.True); + + client.Connect("127.0.0.1", DefaultPort, DefaultAppKey); + + while (server.ConnectedPeersCount != 1 || client.ConnectedPeersCount != 1) + { + Thread.Sleep(15); + server.PollEvents(); + server.ManualUpdate(15); + } + + Assert.That(server.ConnectedPeersCount, Is.EqualTo(1)); + Assert.That(client.ConnectedPeersCount, Is.EqualTo(1)); + server.Stop(); + } + + [Test, CancelAfter(TestTimeout)] + public void SendRawDataToAll() + { + var clientCount = 10; + + var server = ManagerStack.Server(1); + + for (ushort i = 1; i <= clientCount; i++) + { + ManagerStack.Client(i).Connect("127.0.0.1", DefaultPort, DefaultAppKey); + } + + while (server.ConnectedPeersCount < clientCount) + { + Thread.Sleep(15); + server.PollEvents(); + } + + Assert.That(server.ConnectedPeersCount, Is.EqualTo(clientCount)); + Thread.Sleep(100); + ManagerStack.ClientForeach((i, manager, l) => Assert.That(manager.ConnectedPeersCount, Is.EqualTo(1))); + + var dataStack = new Stack(clientCount); + + ManagerStack.ClientForeach( + (i, manager, l) => l.NetworkReceiveEvent += (peer, reader, type) => dataStack.Push(reader.GetRemainingBytes())); + + var data = Encoding.Default.GetBytes("TextForTest"); + server.SendToAll(data, DeliveryMethod.ReliableUnordered); + + while (dataStack.Count < clientCount) + { + ManagerStack.ClientForeach((i, manager, l) => manager.PollEvents()); + + Thread.Sleep(10); + } + + Assert.That(dataStack.Count, Is.EqualTo(clientCount)); + + Assert.That(server.ConnectedPeersCount, Is.EqualTo(clientCount)); + for (ushort i = 1; i <= clientCount; i++) + { + Assert.That(ManagerStack.Client(i).ConnectedPeersCount, Is.EqualTo(1)); + Assert.That(data, Is.EqualTo(dataStack.Pop()).AsCollection); + } + } + } +} diff --git a/LiteNetLib.Tests/LiteNetLib.Tests.csproj b/LiteNetLib.Tests/LiteNetLib.Tests.csproj index 02d3b717..b0bb097e 100644 --- a/LiteNetLib.Tests/LiteNetLib.Tests.csproj +++ b/LiteNetLib.Tests/LiteNetLib.Tests.csproj @@ -2,16 +2,15 @@ Library - net6.0;net5.0;netcoreapp3.1 - net6.0;net5.0;net471;netcoreapp3.1 + net8.0;net9.0 - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -21,8 +20,4 @@ - - - - diff --git a/LiteNetLib.Tests/NetSerializerTest.cs b/LiteNetLib.Tests/NetSerializerTest.cs index 39ed8be8..a8bd036a 100644 --- a/LiteNetLib.Tests/NetSerializerTest.cs +++ b/LiteNetLib.Tests/NetSerializerTest.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; using System.Runtime.Serialization; using LiteNetLib.Utils; @@ -16,17 +18,23 @@ public void Init() _samplePacket = new SamplePacket { SomeFloat = 3.42f, - SomeIntArray = new[] {6, 5, 4}, + SomeIntArray = new[] { 6, 5, 4 }, SomeString = "Test String", + SomeGuid = Guid.NewGuid(), SomeVector2 = new SomeVector2(4, 5), - SomeVectors = new[] {new SomeVector2(1, 2), new SomeVector2(3, 4)}, + SomeVectors = new[] { new SomeVector2(1, 2), new SomeVector2(3, 4) }, + SomeFastVector3 = new SomeFastVector3(0.21f, 100.25f, -400.0f), + SomeFastVectors = new[] { new SomeFastVector3(1.0f, 2.0f, 3.0f), new SomeFastVector3(4.0f, 5.0f, 6.0f) }, SomeEnum = TestEnum.B, SomeByteArray = new byte[] { 255, 1, 0 }, - TestObj = new SampleNetSerializable {Value = 5}, - TestArray = new [] { new SampleNetSerializable { Value = 6 }, new SampleNetSerializable { Value = 15 } }, - SampleClassArray = new[] { new SampleClass { Value = 6 }, new SampleClass { Value = 15 } }, - SampleClassList = new List { new SampleClass { Value = 1 }, new SampleClass { Value = 5 }}, - VectorList = new List { new SomeVector2(-1,-2), new SomeVector2(700, 800) }, + TestObj = new SampleNetSerializable { Value = 5 }, + TestArray = new[] { new SampleNetSerializable { Value = 6 }, new SampleNetSerializable { Value = 15 } }, + SampleClassArray = new[] + { FillTestArray(new SampleClass { Value = 6, TestEnum = TestEnum.C }), FillTestArray(new SampleClass { Value = 15, TestEnum = TestEnum.B }) }, + SampleClassList = new List + { FillTestArray(new SampleClass { Value = 1 }), FillTestArray(new SampleClass { Value = 5 }) }, + VectorList = new List { new SomeVector2(-1, -2), new SomeVector2(700, 800) }, + FastVectorList = new List { new SomeFastVector3(-100.0f, -200.0f, -300.0f), new SomeFastVector3(100.0f, 200.0f, 300.0f) }, IgnoreMe = 1337 }; @@ -34,6 +42,7 @@ public void Init() _packetProcessor.RegisterNestedType(); _packetProcessor.RegisterNestedType(() => new SampleClass()); _packetProcessor.RegisterNestedType(SomeVector2.Serialize, SomeVector2.Deserialize); + _packetProcessor.RegisterNestedType(SomeFastVector3.Serialize, SomeFastVector3.Deserialize); } private SamplePacket _samplePacket; @@ -65,6 +74,30 @@ public static SomeVector2 Deserialize(NetDataReader reader) } } + private readonly struct SomeFastVector3 + { + public readonly float X; + public readonly float Y; + public readonly float Z; + + public SomeFastVector3(float x, float y, float z) + { + X = x; + Y = y; + Z = z; + } + + public static void Serialize(NetDataWriter writer, SomeFastVector3 vector) + { + writer.PutUnmanaged(vector); + } + + public static SomeFastVector3 Deserialize(NetDataReader reader) + { + return reader.GetUnmanaged(); + } + } + private struct SampleNetSerializable : INetSerializable { public int Value; @@ -83,20 +116,28 @@ public void Deserialize(NetDataReader reader) private class SampleClass : INetSerializable { public int Value; + public ChildClass[] TestArray = Array.Empty(); + public TestEnum TestEnum; public void Serialize(NetDataWriter writer) { writer.Put(Value); + writer.PutArray(TestArray); + writer.PutEnum(TestEnum); } public void Deserialize(NetDataReader reader) { Value = reader.GetInt(); + TestArray = reader.GetArray(); + TestEnum = reader.GetEnum(); } public override bool Equals(object obj) { - return ((SampleClass)obj).Value == Value; + var other = (SampleClass)obj; + return other.Value == Value && (TestArray is null && other.TestArray is null || TestArray != null && + other.TestArray != null && TestArray.SequenceEqual(other.TestArray)) && other.TestEnum == TestEnum; } public override int GetHashCode() @@ -105,6 +146,31 @@ public override int GetHashCode() } } + private class ChildClass : INetSerializable + { + public int Value; + + public void Serialize(NetDataWriter writer) + { + writer.Put(Value); + } + + public void Deserialize(NetDataReader reader) + { + Value = reader.GetInt(); + } + + public override bool Equals(object obj) + { + return ((ChildClass)obj).Value == Value; + } + + public override int GetHashCode() + { + return Value.GetHashCode(); + } + } + private enum TestEnum { A = 1, @@ -119,14 +185,18 @@ private class SamplePacket public int[] SomeIntArray { get; set; } public byte[] SomeByteArray { get; set; } public string SomeString { get; set; } + public Guid SomeGuid { get; set; } public SomeVector2 SomeVector2 { get; set; } public SomeVector2[] SomeVectors { get; set; } + public SomeFastVector3 SomeFastVector3 { get; set; } + public SomeFastVector3[] SomeFastVectors { get; set; } public TestEnum SomeEnum { get; set; } public SampleNetSerializable TestObj { get; set; } public SampleNetSerializable[] TestArray { get; set; } public SampleClass[] SampleClassArray { get; set; } public List SampleClassList { get; set; } public List VectorList { get; set; } + public List FastVectorList { get; set; } [IgnoreDataMember] public int IgnoreMe { get; set; } } @@ -140,6 +210,15 @@ private static bool AreSame(string s1, string s2) return s1 == s2; } + private static SampleClass FillTestArray(SampleClass obj) + { + var random = new Random(); + obj.TestArray = new ChildClass[random.Next(10)]; + for (int i = 0; i < obj.TestArray.Length; i++) + obj.TestArray[i] = new ChildClass { Value = random.Next() }; + return obj; + } + [Test, MaxTime(2000)] public void CustomPackageTest() { @@ -157,48 +236,54 @@ public void CustomPackageTest() _packetProcessor.ReadAllPackets(reader); - Assert.NotNull(readPackage); - Assert.IsTrue(AreSame(_samplePacket.EmptyString, readPackage.EmptyString)); - Assert.AreEqual(_samplePacket.SomeFloat, readPackage.SomeFloat); - Assert.AreEqual(_samplePacket.SomeIntArray, readPackage.SomeIntArray); - Assert.IsTrue(AreSame(_samplePacket.SomeString, readPackage.SomeString)); - Assert.AreEqual(_samplePacket.SomeVector2, readPackage.SomeVector2); - Assert.AreEqual(_samplePacket.SomeVectors, readPackage.SomeVectors); - Assert.AreEqual(_samplePacket.SomeEnum, readPackage.SomeEnum); - Assert.AreEqual(_samplePacket.TestObj.Value, readPackage.TestObj.Value); - Assert.AreEqual(_samplePacket.TestArray, readPackage.TestArray); - Assert.AreEqual(_samplePacket.SomeByteArray, readPackage.SomeByteArray); - Assert.AreEqual(_samplePacket.SampleClassArray, readPackage.SampleClassArray); - Assert.AreEqual(0, readPackage.IgnoreMe); // expect 0 because it should be ignored - CollectionAssert.AreEqual(_samplePacket.SampleClassList, readPackage.SampleClassList); - CollectionAssert.AreEqual(_samplePacket.VectorList, readPackage.VectorList); + Assert.That(readPackage, Is.Not.Null); + Assert.That(AreSame(_samplePacket.EmptyString, readPackage.EmptyString), Is.True); + Assert.That(readPackage.SomeFloat, Is.EqualTo(_samplePacket.SomeFloat)); + Assert.That(readPackage.SomeIntArray, Is.EqualTo(_samplePacket.SomeIntArray).AsCollection); + Assert.That(AreSame(_samplePacket.SomeString, readPackage.SomeString), Is.True); + Assert.That(readPackage.SomeGuid, Is.EqualTo(_samplePacket.SomeGuid)); + Assert.That(readPackage.SomeVector2, Is.EqualTo(_samplePacket.SomeVector2)); + Assert.That(readPackage.SomeVectors, Is.EqualTo(_samplePacket.SomeVectors).AsCollection); + Assert.That(readPackage.SomeFastVector3, Is.EqualTo(_samplePacket.SomeFastVector3)); + Assert.That(readPackage.SomeFastVectors, Is.EqualTo(_samplePacket.SomeFastVectors).AsCollection); + Assert.That(readPackage.SomeEnum, Is.EqualTo(_samplePacket.SomeEnum)); + Assert.That(readPackage.TestObj.Value, Is.EqualTo(_samplePacket.TestObj.Value)); + Assert.That(readPackage.TestArray, Is.EqualTo(_samplePacket.TestArray).AsCollection); + Assert.That(readPackage.SomeByteArray, Is.EqualTo(_samplePacket.SomeByteArray).AsCollection); + Assert.That(readPackage.SampleClassArray, Is.EqualTo(_samplePacket.SampleClassArray).AsCollection); + Assert.That(readPackage.IgnoreMe, Is.EqualTo(0)); // expect 0 because it should be ignored + Assert.That(readPackage.SampleClassList, Is.EqualTo(_samplePacket.SampleClassList).AsCollection); + Assert.That(readPackage.VectorList, Is.EqualTo(_samplePacket.VectorList).AsCollection); + Assert.That(readPackage.FastVectorList, Is.EqualTo(_samplePacket.FastVectorList).AsCollection); //remove test _samplePacket.SampleClassList.RemoveAt(0); _samplePacket.SampleClassArray = new []{new SampleClass {Value = 1}}; _samplePacket.VectorList.RemoveAt(0); + _samplePacket.FastVectorList.RemoveAt(0); writer.Reset(); _packetProcessor.Write(writer, _samplePacket); reader.SetSource(writer); _packetProcessor.ReadAllPackets(reader); - Assert.AreEqual(_samplePacket.SampleClassArray, readPackage.SampleClassArray); - CollectionAssert.AreEqual(_samplePacket.SampleClassList, readPackage.SampleClassList); + Assert.That(readPackage.SampleClassArray, Is.EqualTo(_samplePacket.SampleClassArray).AsCollection); + Assert.That(readPackage.SampleClassList, Is.EqualTo(_samplePacket.SampleClassList).AsCollection); //add test _samplePacket.SampleClassList.Add(new SampleClass { Value = 152 }); _samplePacket.SampleClassList.Add(new SampleClass { Value = 154 }); _samplePacket.SampleClassArray = new[] { new SampleClass { Value = 1 }, new SampleClass { Value = 2 }, new SampleClass { Value = 3 } }; _samplePacket.VectorList.Add(new SomeVector2(500,600)); + _samplePacket.FastVectorList.Add(new SomeFastVector3(500.0f, 600.0f, 700.0f)); writer.Reset(); _packetProcessor.Write(writer, _samplePacket); reader.SetSource(writer); _packetProcessor.ReadAllPackets(reader); - Assert.AreEqual(_samplePacket.SampleClassArray, readPackage.SampleClassArray); - CollectionAssert.AreEqual(_samplePacket.SampleClassList, readPackage.SampleClassList); + Assert.That(readPackage.SampleClassArray, Is.EqualTo(_samplePacket.SampleClassArray).AsCollection); + Assert.That(readPackage.SampleClassList, Is.EqualTo(_samplePacket.SampleClassList).AsCollection); } } } diff --git a/LiteNetLib.Tests/ReaderWriterSimpleDataTest.cs b/LiteNetLib.Tests/ReaderWriterSimpleDataTest.cs index e54d4d33..03725915 100644 --- a/LiteNetLib.Tests/ReaderWriterSimpleDataTest.cs +++ b/LiteNetLib.Tests/ReaderWriterSimpleDataTest.cs @@ -1,6 +1,8 @@ using LiteNetLib.Utils; using NUnit.Framework; +using System; +using System.Net; namespace LiteNetLib.Tests { @@ -14,10 +16,10 @@ public void WriteReadBool() var ndw = new NetDataWriter(); ndw.Put(true); - var ndr = new NetDataReader(ndw.Data); + var ndr = new NetDataReader(ndw); var readBool = ndr.GetBool(); - Assert.AreEqual(readBool, true); + Assert.That(readBool, Is.True); } [Test] @@ -26,7 +28,7 @@ public void WriteReadBoolArray() var ndw = new NetDataWriter(); ndw.PutArray(new[] {true, false, true, false, false}); - var ndr = new NetDataReader(ndw.Data); + var ndr = new NetDataReader(ndw); var readBoolArray = ndr.GetBoolArray(); Assert.That(new[] {true, false, true, false, false}, Is.EqualTo(readBoolArray).AsCollection); @@ -38,10 +40,10 @@ public void WriteReadByte() var ndw = new NetDataWriter(); ndw.Put((byte) 8); - var ndr = new NetDataReader(ndw.Data); + var ndr = new NetDataReader(ndw); var readByte = ndr.GetByte(); - Assert.AreEqual(readByte, (byte) 8); + Assert.That(readByte, Is.EqualTo((byte) 8)); } [Test] @@ -50,7 +52,7 @@ public void WriteReadByteArray() var ndw = new NetDataWriter(); ndw.Put(new byte[] {1, 2, 4, 8, 16, byte.MaxValue, byte.MinValue}); - var ndr = new NetDataReader(ndw.Data); + var ndr = new NetDataReader(ndw); var readByteArray = new byte[7]; ndr.GetBytes(readByteArray, 7); @@ -59,16 +61,36 @@ public void WriteReadByteArray() Is.EqualTo(readByteArray).AsCollection); } +#if NET5_0_OR_GREATER + [Test] + public void WriteReadByteSpan() + { + Span tempBytes = new byte[] { 1, 2, 4, 8 }; + var ndw = new NetDataWriter(); + ndw.Put(tempBytes); + Span anotherTempBytes = new byte[] { 16, byte.MaxValue, byte.MinValue }; + ndw.Put(anotherTempBytes); + + var ndr = new NetDataReader(ndw); + var readByteArray = new byte[7]; + ndr.GetBytes(readByteArray, 7); + + Assert.That( + new byte[] { 1, 2, 4, 8, 16, byte.MaxValue, byte.MinValue }, + Is.EqualTo(readByteArray).AsCollection); + } +#endif + [Test] public void WriteReadDouble() { var ndw = new NetDataWriter(); ndw.Put(3.1415); - var ndr = new NetDataReader(ndw.Data); + var ndr = new NetDataReader(ndw); var readDouble = ndr.GetDouble(); - Assert.AreEqual(readDouble, 3.1415); + Assert.That(readDouble, Is.EqualTo(3.1415)); } [Test] @@ -77,7 +99,7 @@ public void WriteReadDoubleArray() var ndw = new NetDataWriter(); ndw.PutArray(new[] {1.1, 2.2, 3.3, 4.4, double.MaxValue, double.MinValue}); - var ndr = new NetDataReader(ndw.Data); + var ndr = new NetDataReader(ndw); var readDoubleArray = ndr.GetDoubleArray(); Assert.That( @@ -91,10 +113,10 @@ public void WriteReadFloat() var ndw = new NetDataWriter(); ndw.Put(3.1415f); - var ndr = new NetDataReader(ndw.Data); + var ndr = new NetDataReader(ndw); var readFloat = ndr.GetFloat(); - Assert.AreEqual(readFloat, 3.1415f); + Assert.That(readFloat, Is.EqualTo(3.1415f)); } [Test] @@ -103,7 +125,7 @@ public void WriteReadFloatArray() var ndw = new NetDataWriter(); ndw.PutArray(new[] {1.1f, 2.2f, 3.3f, 4.4f, float.MaxValue, float.MinValue}); - var ndr = new NetDataReader(ndw.Data); + var ndr = new NetDataReader(ndw); var readFloatArray = ndr.GetFloatArray(); Assert.That( @@ -117,10 +139,10 @@ public void WriteReadInt() var ndw = new NetDataWriter(); ndw.Put(32); - var ndr = new NetDataReader(ndw.Data); + var ndr = new NetDataReader(ndw); var readInt = ndr.GetInt(); - Assert.AreEqual(readInt, 32); + Assert.That(readInt, Is.EqualTo(32)); } [Test] @@ -129,7 +151,7 @@ public void WriteReadIntArray() var ndw = new NetDataWriter(); ndw.PutArray(new[] {1, 2, 3, 4, 5, 6, 7, int.MaxValue, int.MinValue}); - var ndr = new NetDataReader(ndw.Data); + var ndr = new NetDataReader(ndw); var readIntArray = ndr.GetIntArray(); Assert.That(new[] {1, 2, 3, 4, 5, 6, 7, int.MaxValue, int.MinValue}, Is.EqualTo(readIntArray).AsCollection); @@ -141,10 +163,10 @@ public void WriteReadLong() var ndw = new NetDataWriter(); ndw.Put(64L); - var ndr = new NetDataReader(ndw.Data); + var ndr = new NetDataReader(ndw); var readLong = ndr.GetLong(); - Assert.AreEqual(readLong, 64L); + Assert.That(readLong, Is.EqualTo(64L)); } [Test] @@ -153,7 +175,7 @@ public void WriteReadLongArray() var ndw = new NetDataWriter(); ndw.PutArray(new[] {1L, 2L, 3L, 4L, long.MaxValue, long.MinValue}); - var ndr = new NetDataReader(ndw.Data); + var ndr = new NetDataReader(ndw); var readLongArray = ndr.GetLongArray(); Assert.That(new[] {1L, 2L, 3L, 4L, long.MaxValue, long.MinValue}, Is.EqualTo(readLongArray).AsCollection); @@ -165,10 +187,10 @@ public void WriteReadNetEndPoint() var ndw = new NetDataWriter(); ndw.Put(NetUtils.MakeEndPoint("127.0.0.1", 7777)); - var ndr = new NetDataReader(ndw.Data); - var readNetEndPoint = ndr.GetNetEndPoint(); + var ndr = new NetDataReader(ndw); + var readNetEndPoint = ndr.GetIPEndPoint(); - Assert.AreEqual(readNetEndPoint, NetUtils.MakeEndPoint("127.0.0.1", 7777)); + Assert.That(readNetEndPoint, Is.EqualTo(NetUtils.MakeEndPoint("127.0.0.1", 7777))); } [Test] @@ -177,10 +199,10 @@ public void WriteReadSByte() var ndw = new NetDataWriter(); ndw.Put((sbyte) 8); - var ndr = new NetDataReader(ndw.Data); + var ndr = new NetDataReader(ndw); var readSByte = ndr.GetSByte(); - Assert.AreEqual(readSByte, (sbyte) 8); + Assert.That(readSByte, Is.EqualTo((sbyte) 8)); } [Test] @@ -189,10 +211,10 @@ public void WriteReadShort() var ndw = new NetDataWriter(); ndw.Put((short) 16); - var ndr = new NetDataReader(ndw.Data); + var ndr = new NetDataReader(ndw); var readShort = ndr.GetShort(); - Assert.AreEqual(readShort, (short) 16); + Assert.That(readShort, Is.EqualTo((short) 16)); } [Test] @@ -201,7 +223,7 @@ public void WriteReadShortArray() var ndw = new NetDataWriter(); ndw.PutArray(new short[] {1, 2, 3, 4, 5, 6, short.MaxValue, short.MinValue}); - var ndr = new NetDataReader(ndw.Data); + var ndr = new NetDataReader(ndw); var readShortArray = ndr.GetShortArray(); Assert.That( @@ -215,10 +237,10 @@ public void WriteReadString() var ndw = new NetDataWriter(); ndw.Put("String", 10); - var ndr = new NetDataReader(ndw.Data); + var ndr = new NetDataReader(ndw); var readString = ndr.GetString(10); - Assert.AreEqual(readString, "String"); + Assert.That(readString, Is.EqualTo("String")); } [Test] @@ -227,7 +249,7 @@ public void WriteReadStringArray() var ndw = new NetDataWriter(); ndw.PutArray(new[] {"First", "Second", "Third", "Fourth"}); - var ndr = new NetDataReader(ndw.Data); + var ndr = new NetDataReader(ndw); var readStringArray = ndr.GetStringArray(10); Assert.That(new[] {"First", "Second", "Third", "Fourth"}, Is.EqualTo(readStringArray).AsCollection); @@ -239,10 +261,10 @@ public void WriteReadUInt() var ndw = new NetDataWriter(); ndw.Put(34U); - var ndr = new NetDataReader(ndw.Data); + var ndr = new NetDataReader(ndw); var readUInt = ndr.GetUInt(); - Assert.AreEqual(readUInt, 34U); + Assert.That(readUInt, Is.EqualTo(34U)); } [Test] @@ -251,7 +273,7 @@ public void WriteReadUIntArray() var ndw = new NetDataWriter(); ndw.PutArray(new[] {1U, 2U, 3U, 4U, 5U, 6U, uint.MaxValue, uint.MinValue}); - var ndr = new NetDataReader(ndw.Data); + var ndr = new NetDataReader(ndw); var readUIntArray = ndr.GetUIntArray(); Assert.That( @@ -265,10 +287,10 @@ public void WriteReadULong() var ndw = new NetDataWriter(); ndw.Put(64UL); - var ndr = new NetDataReader(ndw.Data); + var ndr = new NetDataReader(ndw); var readULong = ndr.GetULong(); - Assert.AreEqual(readULong, 64UL); + Assert.That(readULong, Is.EqualTo(64UL)); } [Test] @@ -277,7 +299,7 @@ public void WriteReadULongArray() var ndw = new NetDataWriter(); ndw.PutArray(new[] {1UL, 2UL, 3UL, 4UL, 5UL, ulong.MaxValue, ulong.MinValue}); - var ndr = new NetDataReader(ndw.Data); + var ndr = new NetDataReader(ndw); var readULongArray = ndr.GetULongArray(); Assert.That( @@ -291,10 +313,28 @@ public void WriteReadUShort() var ndw = new NetDataWriter(); ndw.Put((ushort) 16); - var ndr = new NetDataReader(ndw.Data); + var ndr = new NetDataReader(ndw); var readUShort = ndr.GetUShort(); - Assert.AreEqual(readUShort, (ushort) 16); + Assert.That(readUShort, Is.EqualTo((ushort) 16)); + } + + [Test] + public void WriteReadIPEndPoint() + { + var ndw = new NetDataWriter(); + var ipep = new IPEndPoint(IPAddress.Broadcast, 12345); + var ipep6 = new IPEndPoint(IPAddress.IPv6Loopback, 12345); + ndw.Put(ipep); + ndw.Put(ipep6); + + var ndr = new NetDataReader(ndw); + var readIpep = ndr.GetIPEndPoint(); + var readIpep6 = ndr.GetIPEndPoint(); + + Assert.That(readIpep, Is.EqualTo(ipep)); + Assert.That(readIpep6, Is.EqualTo(ipep6)); + Assert.That(ndr.AvailableBytes, Is.EqualTo(0)); } } } diff --git a/LiteNetLib.Tests/TestUtility/NetManagerStack.cs b/LiteNetLib.Tests/TestUtility/NetManagerStack.cs index 8fe32528..4ade127c 100644 --- a/LiteNetLib.Tests/TestUtility/NetManagerStack.cs +++ b/LiteNetLib.Tests/TestUtility/NetManagerStack.cs @@ -6,36 +6,34 @@ namespace LiteNetLib.Tests.TestUtility { - public class NetManagerStack : IDisposable + public abstract class NetManagerStack : IDisposable where TManager : LiteNetManager { private struct NetContainer { - public readonly NetManager Manager; - public readonly EventBasedNetListener Listener; + public readonly TManager Manager; + public readonly TListener Listener; - public NetContainer(NetManager netManager, EventBasedNetListener listener) + public NetContainer(TManager netManager, TListener listener) { Manager = netManager; Listener = listener; } } - private readonly string _appKey; + protected readonly string AppKey; private readonly int _serverPort; - private readonly HashSet _clientIds = new HashSet(); - private readonly HashSet _serverIds = new HashSet(); - - private readonly Dictionary _managers = - new Dictionary(); + private readonly HashSet _clientIds = new(); + private readonly HashSet _serverIds = new(); + private readonly Dictionary _managers = new(); public NetManagerStack(string appKey, int serverPort) { - _appKey = appKey; + AppKey = appKey; _serverPort = serverPort; } - public void ClientForeach(Action action) + public void ClientForeach(Action action) { foreach (var id in _clientIds) { @@ -44,34 +42,34 @@ public void ClientForeach(Action acti } } - public void ServerForeach(Action action) + public void ServerForeach(Action action) { - foreach (var id in _clientIds) + foreach (var id in _serverIds) { var tuple = GetNetworkManager(id, false); action(id, tuple.Manager, tuple.Listener); } } - public NetManager Client(ushort id) + public TManager Client(ushort id) { _clientIds.Add(id); return GetNetworkManager(id, true).Manager; } - public EventBasedNetListener ClientListener(ushort id) + public TListener ClientListener(ushort id) { _clientIds.Add(id); return GetNetworkManager(id, true).Listener; } - public NetManager Server(ushort id) + public TManager Server(ushort id) { _serverIds.Add(id); return GetNetworkManager(id, false).Manager; } - public EventBasedNetListener ServerListener(ushort id) + public TListener ServerListener(ushort id) { _serverIds.Add(id); return GetNetworkManager(id, false).Listener; @@ -80,41 +78,31 @@ public EventBasedNetListener ServerListener(ushort id) public void Dispose() { foreach (var manager in _managers.Values.Select(v => v.Manager)) - { manager.Stop(); - } } + protected abstract (TManager,TListener) CreateNetworkManager(); + private NetContainer GetNetworkManager(ushort id, bool isClient) { - NetContainer container; if (id == 0) { Assert.Fail("Id cannot be 0"); } var key = isClient ? id : (uint) id << 16; - if (!_managers.TryGetValue(key, out container)) + if (!_managers.TryGetValue(key, out var container)) { - var listener = new EventBasedNetListener(); - listener.ConnectionRequestEvent += request => - { - request.AcceptIfKey(_appKey); - }; - NetManager netManager = new NetManager(listener, new Crc32cLayer()); + var (netManager, listener) = CreateNetworkManager(); if (isClient) { if (!netManager.Start()) - { Assert.Fail($"Client {id} start failed"); - } } else { if (!netManager.Start(_serverPort)) - { Assert.Fail($"Server {id} on port{_serverPort} start failed"); - } } container = new NetContainer(netManager, listener); @@ -124,4 +112,32 @@ private NetContainer GetNetworkManager(ushort id, bool isClient) return container; } } + + public class LiteNetManagerStack : NetManagerStack + { + public LiteNetManagerStack(string appKey, int serverPort) : base(appKey, serverPort) + { + } + + protected override (LiteNetManager, EventBasedLiteNetListener) CreateNetworkManager() + { + var listener = new EventBasedLiteNetListener(); + listener.ConnectionRequestEvent += request =>request.AcceptIfKey(AppKey); + return (new LiteNetManager(listener, new Crc32cLayer()), listener); + } + } + + public class NetManagerStack : NetManagerStack + { + public NetManagerStack(string appKey, int serverPort) : base(appKey, serverPort) + { + } + + protected override (NetManager, EventBasedNetListener) CreateNetworkManager() + { + var listener = new EventBasedNetListener(); + listener.ConnectionRequestEvent += request =>request.AcceptIfKey(AppKey); + return (new NetManager(listener, new Crc32cLayer()), listener); + } + } } diff --git a/LiteNetLib.Tests/TestUtility/TestPorts.cs b/LiteNetLib.Tests/TestUtility/TestPorts.cs new file mode 100644 index 00000000..70b63934 --- /dev/null +++ b/LiteNetLib.Tests/TestUtility/TestPorts.cs @@ -0,0 +1,32 @@ +using System; +using System.Runtime.Versioning; + +namespace LiteNetLib.Tests.TestUtility +{ + internal static class TestPorts + { + private const int BaselineFrameworkMajor = 8; + private const int FrameworkPortBlockSize = 1000; + + public static int ForFramework(int basePort) + { + var frameworkAttribute = (TargetFrameworkAttribute)Attribute.GetCustomAttribute( + typeof(TestPorts).Assembly, + typeof(TargetFrameworkAttribute)); + + if (frameworkAttribute == null) + return basePort; + + const string versionMarker = "Version=v"; + var versionStart = frameworkAttribute.FrameworkName.IndexOf(versionMarker, StringComparison.Ordinal); + if (versionStart < 0) + return basePort; + + var versionText = frameworkAttribute.FrameworkName.Substring(versionStart + versionMarker.Length); + if (!Version.TryParse(versionText, out var version)) + return basePort; + + return basePort + Math.Max(0, version.Major - BaselineFrameworkMajor) * FrameworkPortBlockSize; + } + } +} diff --git a/LiteNetLib.Tests/packages.config b/LiteNetLib.Tests/packages.config deleted file mode 100644 index b68e40f9..00000000 --- a/LiteNetLib.Tests/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/LiteNetLib.Trim.Dummy/LiteNetLib.Trim.Dummy.csproj b/LiteNetLib.Trim.Dummy/LiteNetLib.Trim.Dummy.csproj new file mode 100644 index 00000000..8fe4051b --- /dev/null +++ b/LiteNetLib.Trim.Dummy/LiteNetLib.Trim.Dummy.csproj @@ -0,0 +1,17 @@ + + + + Exe + net8.0 + enable + enable + true + + + + + + + + + \ No newline at end of file diff --git a/LiteNetLib.Trim.Dummy/Program.cs b/LiteNetLib.Trim.Dummy/Program.cs new file mode 100644 index 00000000..b49dd580 --- /dev/null +++ b/LiteNetLib.Trim.Dummy/Program.cs @@ -0,0 +1,3 @@ +// I'm a dummy for assembly trimming since the built-in analyzer isn't perfect yet. +// Please build me with `dotnet publish -c Release -r win-x64` (or similar) and test for warnings. +Console.WriteLine("Hello, World!"); diff --git a/LiteNetLib.sln b/LiteNetLib.sln index 9956f9cf..853c8778 100644 --- a/LiteNetLib.sln +++ b/LiteNetLib.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29519.181 +# Visual Studio Version 17 +VisualStudioVersion = 17.7.34031.279 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LiteNetLib.Tests", "LiteNetLib.Tests\LiteNetLib.Tests.csproj", "{6B591E33-E71F-4A37-A08D-B56DD47EDE3F}" EndProject @@ -14,6 +14,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibSample", "LibSample\LibSample.csproj", "{6499F9B4-0814-42EE-90A5-119AEA07E7EE}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LiteNetLib.Trim.Dummy", "LiteNetLib.Trim.Dummy\LiteNetLib.Trim.Dummy.csproj", "{5903804F-8AB4-4AF7-9152-BED0561E9C7D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -102,6 +104,30 @@ Global {6499F9B4-0814-42EE-90A5-119AEA07E7EE}.Release|x64.Build.0 = Release|Any CPU {6499F9B4-0814-42EE-90A5-119AEA07E7EE}.Release|x86.ActiveCfg = Release|Any CPU {6499F9B4-0814-42EE-90A5-119AEA07E7EE}.Release|x86.Build.0 = Release|Any CPU + {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.Debug|ARM.ActiveCfg = Debug|Any CPU + {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.Debug|ARM.Build.0 = Debug|Any CPU + {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.Debug|x64.ActiveCfg = Debug|Any CPU + {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.Debug|x64.Build.0 = Debug|Any CPU + {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.Debug|x86.ActiveCfg = Debug|Any CPU + {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.Debug|x86.Build.0 = Debug|Any CPU + {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.DebugUnsafe|Any CPU.ActiveCfg = Debug|Any CPU + {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.DebugUnsafe|Any CPU.Build.0 = Debug|Any CPU + {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.DebugUnsafe|ARM.ActiveCfg = Debug|Any CPU + {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.DebugUnsafe|ARM.Build.0 = Debug|Any CPU + {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.DebugUnsafe|x64.ActiveCfg = Debug|Any CPU + {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.DebugUnsafe|x64.Build.0 = Debug|Any CPU + {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.DebugUnsafe|x86.ActiveCfg = Debug|Any CPU + {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.DebugUnsafe|x86.Build.0 = Debug|Any CPU + {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.Release|Any CPU.Build.0 = Release|Any CPU + {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.Release|ARM.ActiveCfg = Release|Any CPU + {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.Release|ARM.Build.0 = Release|Any CPU + {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.Release|x64.ActiveCfg = Release|Any CPU + {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.Release|x64.Build.0 = Release|Any CPU + {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.Release|x86.ActiveCfg = Release|Any CPU + {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/LiteNetLib/BaseChannel.cs b/LiteNetLib/BaseChannel.cs index 261839d9..1dd969c5 100644 --- a/LiteNetLib/BaseChannel.cs +++ b/LiteNetLib/BaseChannel.cs @@ -1,36 +1,63 @@ -using System.Collections.Concurrent; +using System.Collections.Generic; using System.Threading; namespace LiteNetLib { + /// + /// Base class for reliable and sequenced communication channels.
+ /// Handles the queuing and scheduling of outgoing packets. + ///
internal abstract class BaseChannel { - protected readonly NetPeer Peer; - protected readonly ConcurrentQueue OutgoingQueue; + /// + /// The peer associated with this channel. + /// + protected readonly LiteNetPeer Peer; + /// + /// Queue containing packets waiting to be sent over the network. + /// + protected readonly Queue OutgoingQueue = new Queue(NetConstants.DefaultWindowSize); private int _isAddedToPeerChannelSendQueue; + /// + /// Gets the number of packets currently residing in the outgoing queue. + /// public int PacketsInQueue => OutgoingQueue.Count; - protected BaseChannel(NetPeer peer) - { + /// + /// Initializes a new instance of the class. + /// + /// The peer that owns this channel. + protected BaseChannel(LiteNetPeer peer) => Peer = peer; - OutgoingQueue = new ConcurrentQueue(); - } + /// + /// Adds a packet to the outgoing queue and notifies the peer to schedule a send update. + /// + /// The packet to be enqueued. public void AddToQueue(NetPacket packet) { - OutgoingQueue.Enqueue(packet); + lock (OutgoingQueue) + { + OutgoingQueue.Enqueue(packet); + } AddToPeerChannelSendQueue(); } + /// + /// Thread-safely marks this channel as having pending data and adds it to the peer's update list. + /// protected void AddToPeerChannelSendQueue() { if (Interlocked.CompareExchange(ref _isAddedToPeerChannelSendQueue, 1, 0) == 0) - { Peer.AddToReliableChannelSendQueue(this); - } } + /// + /// Attempts to send packets from the queue. If the queue becomes empty or throttled, + /// it resets the update flag. + /// + /// if there are still packets remaining to be sent in future updates. public bool SendAndCheckQueue() { bool hasPacketsToSend = SendNextPackets(); @@ -40,7 +67,17 @@ public bool SendAndCheckQueue() return hasPacketsToSend; } - protected abstract bool SendNextPackets(); + /// + /// Abstract method to implement the specific logic for sending packets (e.g., windowing for reliable). + /// + /// if packets were sent and the channel should remain in the send queue. + public abstract bool SendNextPackets(); + + /// + /// Abstract method to handle an incoming packet received on this specific channel. + /// + /// The received packet. + /// if the packet was processed successfully. public abstract bool ProcessPacket(NetPacket packet); } } diff --git a/LiteNetLib/ConnectionRequest.cs b/LiteNetLib/ConnectionRequest.cs index 4a2cdd93..080009ab 100644 --- a/LiteNetLib/ConnectionRequest.cs +++ b/LiteNetLib/ConnectionRequest.cs @@ -1,4 +1,5 @@ -using System.Net; +using System; +using System.Net; using System.Threading; using LiteNetLib.Utils; @@ -12,16 +13,22 @@ internal enum ConnectionRequestResult RejectForce } - public class ConnectionRequest + public class LiteConnectionRequest { - private readonly NetManager _listener; + private readonly LiteNetManager _listener; private int _used; + /// + /// Data sent by the remote peer in the connection request. + /// public NetDataReader Data => InternalPacket.Data; internal ConnectionRequestResult Result { get; private set; } internal NetConnectRequestPacket InternalPacket; + /// + /// The remote endpoint (IP and Port) of the peer requesting the connection. + /// public readonly IPEndPoint RemoteEndPoint; internal void UpdateRequest(NetConnectRequestPacket connectRequest) @@ -37,19 +44,26 @@ internal void UpdateRequest(NetConnectRequestPacket connectRequest) InternalPacket = connectRequest; } - private bool TryActivate() - { - return Interlocked.CompareExchange(ref _used, 1, 0) == 0; - } + private bool TryActivate() => + Interlocked.CompareExchange(ref _used, 1, 0) == 0; - internal ConnectionRequest(IPEndPoint remoteEndPoint, NetConnectRequestPacket requestPacket, NetManager listener) + internal LiteConnectionRequest(IPEndPoint remoteEndPoint, NetConnectRequestPacket requestPacket, LiteNetManager listener) { InternalPacket = requestPacket; RemoteEndPoint = remoteEndPoint; _listener = listener; } - public NetPeer AcceptIfKey(string key) + /// + /// Accepts the connection if the first string in the matches the provided key. + /// + /// + /// This is a helper method for simple password/key validation. + /// If the key does not match or data is invalid, the connection is automatically rejected. + /// + /// The required string key to match. + /// A new if the key matches and connection is accepted; otherwise, null. + public LiteNetPeer AcceptIfKey(string key) { if (!TryActivate()) return null; @@ -63,10 +77,10 @@ public NetPeer AcceptIfKey(string key) NetDebug.WriteError("[AC] Invalid incoming data"); } if (Result == ConnectionRequestResult.Accept) - return _listener.OnConnectionSolved(this, null, 0, 0); + return _listener.OnConnectionSolved(this, ReadOnlySpan.Empty); Result = ConnectionRequestResult.Reject; - _listener.OnConnectionSolved(this, null, 0, 0); + _listener.OnConnectionSolved(this, ReadOnlySpan.Empty); return null; } @@ -74,61 +88,127 @@ public NetPeer AcceptIfKey(string key) /// Accept connection and get new NetPeer as result /// /// Connected NetPeer - public NetPeer Accept() + public LiteNetPeer Accept() { if (!TryActivate()) return null; Result = ConnectionRequestResult.Accept; - return _listener.OnConnectionSolved(this, null, 0, 0); + return _listener.OnConnectionSolved(this, ReadOnlySpan.Empty); } - public void Reject(byte[] rejectData, int start, int length, bool force) + /// + /// Rejects the connection request. + /// + /// Optional user data to send along with the rejection packet. + /// + /// If , immediately removes the request, if is not empty a reject packet is also sent.
+ /// If , creates a temporary peer that sends rejection packets and lingers in memory until a timeout occurs to handle late-arriving packets. + /// + public void Reject(ReadOnlySpan rejectData, bool force) { if (!TryActivate()) return; Result = force ? ConnectionRequestResult.RejectForce : ConnectionRequestResult.Reject; - _listener.OnConnectionSolved(this, rejectData, start, length); + _listener.OnConnectionSolved(this, rejectData); } - public void Reject(byte[] rejectData, int start, int length) - { - Reject(rejectData, start, length, false); - } + /// + /// Rejects the connection request. + /// + /// Optional user data to send along with the rejection packet. + /// Offset in the array. + /// Length of the data to be sent from the array. + /// + /// If , immediately removes the request, if is not a reject packet is also sent.
+ /// If , creates a temporary peer that sends rejection packets and lingers in memory until a timeout occurs to handle late-arriving packets. + /// + public void Reject(byte[] rejectData, int start, int length, bool force) => + Reject(new ReadOnlySpan(rejectData, start, length), force); + /// + /// Rejects the connection reliably. Creates a temporary peer to handle packet loss. + /// + /// Data to send with the rejection. + /// Offset in the array. + /// Length of the data to be sent. + public void Reject(byte[] rejectData, int start, int length) => + Reject(new ReadOnlySpan(rejectData, start, length), false); - public void RejectForce(byte[] rejectData, int start, int length) - { - Reject(rejectData, start, length, true); - } + /// + /// Rejects the connection immediately without reliability. + /// Minimizes resource usage by not creating an internal peer. + /// + /// Data to send with the rejection. + /// Offset in the array. + /// Length of the data to be sent. + public void RejectForce(byte[] rejectData, int start, int length) => + Reject(new ReadOnlySpan(rejectData, start, length), true); - public void RejectForce() - { - Reject(null, 0, 0, true); - } + /// + /// Rejects the connection immediately without sending any packet. + /// + public void RejectForce() => + Reject(ReadOnlySpan.Empty, true); - public void RejectForce(byte[] rejectData) - { - Reject(rejectData, 0, rejectData.Length, true); - } + /// + /// Rejects the connection immediately without reliability. + /// + /// Data to send with the rejection. + public void RejectForce(byte[] rejectData) => + Reject(new ReadOnlySpan(rejectData), true); - public void RejectForce(NetDataWriter rejectData) - { - Reject(rejectData.Data, 0, rejectData.Length, true); - } + /// + /// Rejects the connection immediately without reliability using data from a . + /// + /// Writer containing the data to send. + public void RejectForce(NetDataWriter rejectData) => + Reject(rejectData.AsReadOnlySpan(), true); - public void Reject() - { - Reject(null, 0, 0, false); - } + /// + /// Rejects the connection immediately without reliability. + /// + /// Data to send with the rejection. + public void RejectForce(ReadOnlySpan rejectData) => + Reject(rejectData, true); - public void Reject(byte[] rejectData) - { - Reject(rejectData, 0, rejectData.Length, false); - } + /// + /// Rejects the connection reliably without additional data. + /// + public void Reject() => + Reject(ReadOnlySpan.Empty, false); - public void Reject(NetDataWriter rejectData) + /// + /// Rejects the connection reliably. + /// + /// Data to send with the rejection. + public void Reject(byte[] rejectData) => + Reject(new ReadOnlySpan(rejectData), false); + + /// + /// Rejects the connection reliably using data from a . + /// + /// Writer containing the data to send. + public void Reject(NetDataWriter rejectData) => + Reject(rejectData.AsReadOnlySpan(), false); + + /// + /// Rejects the connection reliably. + /// + /// Data to send with the rejection. + public void Reject(ReadOnlySpan rejectData) => + Reject(rejectData, false); + } + + public class ConnectionRequest : LiteConnectionRequest + { + internal ConnectionRequest(IPEndPoint remoteEndPoint, NetConnectRequestPacket requestPacket, LiteNetManager listener) : base(remoteEndPoint, requestPacket, listener) { - Reject(rejectData.Data, 0, rejectData.Length, false); } + + /// + public new NetPeer AcceptIfKey(string key) => (NetPeer)base.AcceptIfKey(key); + + /// + public new NetPeer Accept() => (NetPeer)base.Accept(); } } diff --git a/LiteNetLib/INetEventListener.cs b/LiteNetLib/INetEventListener.cs index 13d8852e..17891c6e 100644 --- a/LiteNetLib/INetEventListener.cs +++ b/LiteNetLib/INetEventListener.cs @@ -18,13 +18,34 @@ public enum UnconnectedMessageType /// public enum DisconnectReason { + /// + /// Connection to host failed + /// ConnectionFailed, + + /// + /// Timeout + /// Timeout, + HostUnreachable, NetworkUnreachable, + + /// + /// Remote host disconnected peer + /// RemoteConnectionClose, + + /// + /// Disconnect called locally + /// DisconnectPeerCalled, + + /// + /// Connection rejected by remote host + /// ConnectionRejected, + InvalidProtocol, UnknownHost, Reconnect, @@ -53,6 +74,9 @@ public struct DisconnectInfo public NetPacketReader AdditionalData; } + /// + /// Interface for implementing own INetEventListener. This is a bit faster than use EventBasedListener + /// public interface INetEventListener { /// @@ -104,169 +128,397 @@ public interface INetEventListener /// /// Request information (EndPoint, internal id, additional data) void OnConnectionRequest(ConnectionRequest request); - } - public interface IDeliveryEventListener - { /// /// On reliable message delivered /// /// /// - void OnMessageDelivered(NetPeer peer, object userData); - } + void OnMessageDelivered(NetPeer peer, object userData) { } - public interface INtpEventListener - { /// /// Ntp response /// /// - void OnNtpResponse(NtpPacket packet); + void OnNtpResponse(NtpPacket packet) { } + + /// + /// Called when peer address changed (when AllowPeerAddressChange is enabled) + /// + /// Peer that changed address (with new address) + /// previous IP + void OnPeerAddressChanged(NetPeer peer, IPEndPoint previousAddress) { } } - public interface IPeerAddressChangedListener + /// + /// Interface for implementing own ILiteNetEventListener. This is a bit faster than use EventBasedListener + /// + public interface ILiteNetEventListener { + /// + /// New remote peer connected to host, or client connected to remote host + /// + /// Connected peer object + void OnPeerConnected(LiteNetPeer peer); + + /// + /// Peer disconnected + /// + /// disconnected peer + /// additional info about reason, errorCode or data received with disconnect message + void OnPeerDisconnected(LiteNetPeer peer, DisconnectInfo disconnectInfo); + + /// + /// Network error (on send or receive) + /// + /// From endPoint (can be null) + /// Socket error + void OnNetworkError(IPEndPoint endPoint, SocketError socketError) { } + + /// + /// Received some data + /// + /// From peer + /// DataReader containing all received data + /// Type of received packet + void OnNetworkReceive(LiteNetPeer peer, NetPacketReader reader, DeliveryMethod deliveryMethod); + + /// + /// Received unconnected message + /// + /// From address (IP and Port) + /// Message data + /// Message type (simple, discovery request or response) + void OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType) { } + + /// + /// Latency information updated + /// + /// Peer with updated latency + /// latency value in milliseconds + void OnNetworkLatencyUpdate(LiteNetPeer peer, int latency) { } + + /// + /// On peer connection requested + /// + /// Request information (EndPoint, internal id, additional data) + void OnConnectionRequest(LiteConnectionRequest request); + /// /// Called when peer address changed (when AllowPeerAddressChange is enabled) /// /// Peer that changed address (with new address) /// previous IP - void OnPeerAddressChanged(NetPeer peer, IPEndPoint previousAddress); + void OnPeerAddressChanged(LiteNetPeer peer, IPEndPoint previousAddress) { } + + /// + /// On reliable message delivered + /// + /// + /// + void OnMessageDelivered(LiteNetPeer peer, object userData) { } } - public class EventBasedNetListener : INetEventListener, IDeliveryEventListener, INtpEventListener, IPeerAddressChangedListener + /// + /// Simple event based listener for simple setups and benchmarks + /// + public class EventBasedNetListener : INetEventListener { + /// + /// Delegate for the event that occurs when a new peer has successfully connected. + /// + /// The connected peer. public delegate void OnPeerConnected(NetPeer peer); + /// + /// Delegate for the event that occurs when a peer disconnects or the connection is lost. + /// + /// The disconnected peer. + /// Information regarding the reason and data associated with the disconnection. public delegate void OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo); + /// + /// Delegate for the event that occurs when a network error is detected in the underlying socket. + /// + /// The endpoint associated with the error. + /// The specific socket error code. public delegate void OnNetworkError(IPEndPoint endPoint, SocketError socketError); + /// + /// Delegate for the event that occurs when data is received from a connected peer. + /// + /// The peer that sent the data. + /// The reader containing the received payload. + /// The channel on which the data was received. + /// The delivery method used for this packet. public delegate void OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channel, DeliveryMethod deliveryMethod); + /// + /// Delegate for the event that occurs when a message is received from an unconnected endpoint. + /// + /// The endpoint that sent the message. + /// The reader containing the received payload. + /// The type of unconnected message (e.g., Discovery or UnconnectedData). public delegate void OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType); + /// + /// Delegate for the event that occurs when the round-trip time (RTT) to a peer is updated. + /// + /// The peer whose latency was updated. + /// The new latency value in milliseconds. public delegate void OnNetworkLatencyUpdate(NetPeer peer, int latency); + /// + /// Delegate for the event that occurs when a new connection request is received. + /// + /// The connection request object used to accept or reject the connection. public delegate void OnConnectionRequest(ConnectionRequest request); + /// + /// Delegate for the event that occurs when a reliable packet is successfully delivered or acknowledged. + /// + /// The peer that received the packet. + /// The custom user data that was attached to the sent packet. public delegate void OnDeliveryEvent(NetPeer peer, object userData); + /// + /// Delegate for the event that occurs when an NTP response is received from a time server. + /// + /// The NTP packet containing time information. public delegate void OnNtpResponseEvent(NtpPacket packet); + /// + /// Delegate for the event that occurs when a peer's remote address changes (roaming). + /// + /// The peer whose address changed. + /// The previous IP endpoint of the peer. public delegate void OnPeerAddressChangedEvent(NetPeer peer, IPEndPoint previousAddress); + /// + /// Occurs when a new peer has successfully connected. + /// public event OnPeerConnected PeerConnectedEvent; + /// + /// Occurs when a peer disconnects or the connection is lost. + /// public event OnPeerDisconnected PeerDisconnectedEvent; + /// + /// Occurs when a network error is detected in the underlying socket. + /// public event OnNetworkError NetworkErrorEvent; + /// + /// Occurs when data is received from a connected peer. + /// public event OnNetworkReceive NetworkReceiveEvent; + /// + /// Occurs when a message is received from an unconnected endpoint. + /// public event OnNetworkReceiveUnconnected NetworkReceiveUnconnectedEvent; + /// + /// Occurs when the round-trip time (RTT) to a peer is updated. + /// public event OnNetworkLatencyUpdate NetworkLatencyUpdateEvent; + /// + /// Occurs when a new connection request is received. + /// public event OnConnectionRequest ConnectionRequestEvent; + /// + /// Occurs when a reliable packet is successfully delivered or acknowledged. + /// public event OnDeliveryEvent DeliveryEvent; + /// + /// Occurs when an NTP response is received. + /// public event OnNtpResponseEvent NtpResponseEvent; + /// + /// Occurs when a peer's remote address changes. + /// + public event OnPeerAddressChangedEvent PeerAddressChangedEvent; + + /// Clears all subscribers from . + public void ClearPeerConnectedEvent() => PeerConnectedEvent = null; + /// Clears all subscribers from . + public void ClearPeerDisconnectedEvent() => PeerDisconnectedEvent = null; + /// Clears all subscribers from . + public void ClearNetworkErrorEvent() => NetworkErrorEvent = null; + /// Clears all subscribers from . + public void ClearNetworkReceiveEvent() => NetworkReceiveEvent = null; + /// Clears all subscribers from . + public void ClearNetworkReceiveUnconnectedEvent() => NetworkReceiveUnconnectedEvent = null; + /// Clears all subscribers from . + public void ClearNetworkLatencyUpdateEvent() => NetworkLatencyUpdateEvent = null; + /// Clears all subscribers from . + public void ClearConnectionRequestEvent() => ConnectionRequestEvent = null; + /// Clears all subscribers from . + public void ClearDeliveryEvent() => DeliveryEvent = null; + /// Clears all subscribers from . + public void ClearNtpResponseEvent() => NtpResponseEvent = null; + /// Clears all subscribers from . + public void ClearPeerAddressChangedEvent() => PeerAddressChangedEvent = null; + + void INetEventListener.OnPeerConnected(NetPeer peer) => + PeerConnectedEvent?.Invoke(peer); + + void INetEventListener.OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo) => + PeerDisconnectedEvent?.Invoke(peer, disconnectInfo); + + void INetEventListener.OnNetworkError(IPEndPoint endPoint, SocketError socketErrorCode) => + NetworkErrorEvent?.Invoke(endPoint, socketErrorCode); + + void INetEventListener.OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channelNumber, DeliveryMethod deliveryMethod) => + NetworkReceiveEvent?.Invoke(peer, reader, channelNumber, deliveryMethod); + + void INetEventListener.OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType) => + NetworkReceiveUnconnectedEvent?.Invoke(remoteEndPoint, reader, messageType); + + void INetEventListener.OnNetworkLatencyUpdate(NetPeer peer, int latency) => + NetworkLatencyUpdateEvent?.Invoke(peer, latency); + + void INetEventListener.OnConnectionRequest(ConnectionRequest request) => + ConnectionRequestEvent?.Invoke(request); + + void INetEventListener.OnMessageDelivered(NetPeer peer, object userData) => + DeliveryEvent?.Invoke(peer, userData); + + void INetEventListener.OnNtpResponse(NtpPacket packet) => + NtpResponseEvent?.Invoke(packet); + + void INetEventListener.OnPeerAddressChanged(NetPeer peer, IPEndPoint previousAddress) => + PeerAddressChangedEvent?.Invoke(peer, previousAddress); + } + + /// + /// Simple event based listener for simple setups and benchmarks + /// + public class EventBasedLiteNetListener : ILiteNetEventListener + { + /// + /// Delegate for the event that occurs when a new peer has successfully connected. + /// + /// The connected peer. + public delegate void OnPeerConnected(LiteNetPeer peer); + /// + /// Delegate for the event that occurs when a peer disconnects or the connection is lost. + /// + /// The disconnected peer. + /// Information regarding the reason and data associated with the disconnection. + public delegate void OnPeerDisconnected(LiteNetPeer peer, DisconnectInfo disconnectInfo); + /// + /// Delegate for the event that occurs when a network error is detected in the underlying socket. + /// + /// The endpoint associated with the error. + /// The specific socket error code. + public delegate void OnNetworkError(IPEndPoint endPoint, SocketError socketError); + /// + /// Delegate for the event that occurs when data is received from a connected peer. + /// + /// The peer that sent the data. + /// The reader containing the received payload. + /// The delivery method used for this packet. + public delegate void OnNetworkReceive(LiteNetPeer peer, NetPacketReader reader, DeliveryMethod deliveryMethod); + /// + /// Delegate for the event that occurs when a message is received from an unconnected endpoint. + /// + /// The endpoint that sent the message. + /// The reader containing the received payload. + /// The type of unconnected message (e.g., Discovery or UnconnectedData). + public delegate void OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType); + /// + /// Delegate for the event that occurs when the round-trip time (RTT) to a peer is updated. + /// + /// The peer whose latency was updated. + /// The new latency value in milliseconds. + public delegate void OnNetworkLatencyUpdate(LiteNetPeer peer, int latency); + /// + /// Delegate for the event that occurs when a new connection request is received. + /// + /// The connection request object used to accept or reject the connection. + public delegate void OnConnectionRequest(LiteConnectionRequest request); + /// + /// Delegate for the event that occurs when a reliable packet is successfully delivered or acknowledged. + /// + /// The peer that received the packet. + /// The custom user data that was attached to the sent packet. + public delegate void OnDeliveryEvent(LiteNetPeer peer, object userData); + /// + /// Delegate for the event that occurs when a peer's remote address changes (roaming). + /// + /// The peer whose address changed. + /// The previous IP endpoint of the peer. + public delegate void OnPeerAddressChangedEvent(LiteNetPeer peer, IPEndPoint previousAddress); + + /// + /// Occurs when a new peer has successfully connected. + /// + public event OnPeerConnected PeerConnectedEvent; + /// + /// Occurs when a peer disconnects or the connection is lost. + /// + public event OnPeerDisconnected PeerDisconnectedEvent; + /// + /// Occurs when a network error is detected in the underlying socket. + /// + public event OnNetworkError NetworkErrorEvent; + /// + /// Occurs when data is received from a connected peer. + /// + public event OnNetworkReceive NetworkReceiveEvent; + /// + /// Occurs when a message is received from an unconnected endpoint. + /// + public event OnNetworkReceiveUnconnected NetworkReceiveUnconnectedEvent; + /// + /// Occurs when the round-trip time (RTT) to a peer is updated. + /// + public event OnNetworkLatencyUpdate NetworkLatencyUpdateEvent; + /// + /// Occurs when a new connection request is received. + /// + public event OnConnectionRequest ConnectionRequestEvent; + /// + /// Occurs when a reliable packet is successfully delivered or acknowledged. + /// + public event OnDeliveryEvent DeliveryEvent; + /// + /// Occurs when a peer's remote address changes. + /// public event OnPeerAddressChangedEvent PeerAddressChangedEvent; - public void ClearPeerConnectedEvent() - { - PeerConnectedEvent = null; - } - - public void ClearPeerDisconnectedEvent() - { - PeerDisconnectedEvent = null; - } - - public void ClearNetworkErrorEvent() - { - NetworkErrorEvent = null; - } - - public void ClearNetworkReceiveEvent() - { - NetworkReceiveEvent = null; - } - - public void ClearNetworkReceiveUnconnectedEvent() - { - NetworkReceiveUnconnectedEvent = null; - } - - public void ClearNetworkLatencyUpdateEvent() - { - NetworkLatencyUpdateEvent = null; - } - - public void ClearConnectionRequestEvent() - { - ConnectionRequestEvent = null; - } - - public void ClearDeliveryEvent() - { - DeliveryEvent = null; - } - - public void ClearNtpResponseEvent() - { - NtpResponseEvent = null; - } - - public void ClearPeerAddressChangedEvent() - { - PeerAddressChangedEvent = null; - } - - void INetEventListener.OnPeerConnected(NetPeer peer) - { - if (PeerConnectedEvent != null) - PeerConnectedEvent(peer); - } - - void INetEventListener.OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo) - { - if (PeerDisconnectedEvent != null) - PeerDisconnectedEvent(peer, disconnectInfo); - } - - void INetEventListener.OnNetworkError(IPEndPoint endPoint, SocketError socketErrorCode) - { - if (NetworkErrorEvent != null) - NetworkErrorEvent(endPoint, socketErrorCode); - } - - void INetEventListener.OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channelNumber, DeliveryMethod deliveryMethod) - { - if (NetworkReceiveEvent != null) - NetworkReceiveEvent(peer, reader, channelNumber, deliveryMethod); - } - - void INetEventListener.OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType) - { - if (NetworkReceiveUnconnectedEvent != null) - NetworkReceiveUnconnectedEvent(remoteEndPoint, reader, messageType); - } - - void INetEventListener.OnNetworkLatencyUpdate(NetPeer peer, int latency) - { - if (NetworkLatencyUpdateEvent != null) - NetworkLatencyUpdateEvent(peer, latency); - } - - void INetEventListener.OnConnectionRequest(ConnectionRequest request) - { - if (ConnectionRequestEvent != null) - ConnectionRequestEvent(request); - } - - void IDeliveryEventListener.OnMessageDelivered(NetPeer peer, object userData) - { - if (DeliveryEvent != null) - DeliveryEvent(peer, userData); - } - - void INtpEventListener.OnNtpResponse(NtpPacket packet) - { - if (NtpResponseEvent != null) - NtpResponseEvent(packet); - } - - void IPeerAddressChangedListener.OnPeerAddressChanged(NetPeer peer, IPEndPoint previousAddress) - { - if (PeerAddressChangedEvent != null) - PeerAddressChangedEvent(peer, previousAddress); - } + /// Clears all subscribers from . + public void ClearPeerConnectedEvent() => PeerConnectedEvent = null; + /// Clears all subscribers from . + public void ClearPeerDisconnectedEvent() => PeerDisconnectedEvent = null; + /// Clears all subscribers from . + public void ClearNetworkErrorEvent() => NetworkErrorEvent = null; + /// Clears all subscribers from . + public void ClearNetworkReceiveEvent() => NetworkReceiveEvent = null; + /// Clears all subscribers from . + public void ClearNetworkReceiveUnconnectedEvent() => NetworkReceiveUnconnectedEvent = null; + /// Clears all subscribers from . + public void ClearNetworkLatencyUpdateEvent() => NetworkLatencyUpdateEvent = null; + /// Clears all subscribers from . + public void ClearConnectionRequestEvent() => ConnectionRequestEvent = null; + /// Clears all subscribers from . + public void ClearDeliveryEvent() => DeliveryEvent = null; + /// Clears all subscribers from . + public void ClearPeerAddressChangedEvent() => PeerAddressChangedEvent = null; + + void ILiteNetEventListener.OnPeerConnected(LiteNetPeer peer) => + PeerConnectedEvent?.Invoke(peer); + + void ILiteNetEventListener.OnPeerDisconnected(LiteNetPeer peer, DisconnectInfo disconnectInfo) => + PeerDisconnectedEvent?.Invoke(peer, disconnectInfo); + + void ILiteNetEventListener.OnNetworkError(IPEndPoint endPoint, SocketError socketErrorCode) => + NetworkErrorEvent?.Invoke(endPoint, socketErrorCode); + + void ILiteNetEventListener.OnNetworkReceive(LiteNetPeer peer, NetPacketReader reader, DeliveryMethod deliveryMethod) => + NetworkReceiveEvent?.Invoke(peer, reader, deliveryMethod); + + void ILiteNetEventListener.OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType) => + NetworkReceiveUnconnectedEvent?.Invoke(remoteEndPoint, reader, messageType); + + void ILiteNetEventListener.OnNetworkLatencyUpdate(LiteNetPeer peer, int latency) => + NetworkLatencyUpdateEvent?.Invoke(peer, latency); + + void ILiteNetEventListener.OnConnectionRequest(LiteConnectionRequest request) => + ConnectionRequestEvent?.Invoke(request); + + void ILiteNetEventListener.OnMessageDelivered(LiteNetPeer peer, object userData) => + DeliveryEvent?.Invoke(peer, userData); + + void ILiteNetEventListener.OnPeerAddressChanged(LiteNetPeer peer, IPEndPoint previousAddress) => + PeerAddressChangedEvent?.Invoke(peer, previousAddress); } } diff --git a/LiteNetLib/InternalPackets.cs b/LiteNetLib/InternalPackets.cs index 2eb09fe5..6cd9a8db 100644 --- a/LiteNetLib/InternalPackets.cs +++ b/LiteNetLib/InternalPackets.cs @@ -1,10 +1,10 @@ -using System; +using System; using System.Net; using LiteNetLib.Utils; namespace LiteNetLib { - internal sealed class NetConnectRequestPacket + public sealed class NetConnectRequestPacket { public const int HeaderSize = 18; public readonly long ConnectionTime; @@ -22,12 +22,10 @@ private NetConnectRequestPacket(long connectionTime, byte connectionNumber, int PeerId = localId; } - public static int GetProtocolId(NetPacket packet) - { - return BitConverter.ToInt32(packet.RawData, 1); - } + internal static int GetProtocolId(NetPacket packet) => + BitConverter.ToInt32(packet.RawData, 1); - public static NetConnectRequestPacket FromData(NetPacket packet) + internal static NetConnectRequestPacket FromData(NetPacket packet) { if (packet.ConnectionNumber >= NetConstants.MaxConnectionNumber) return null; @@ -53,7 +51,7 @@ public static NetConnectRequestPacket FromData(NetPacket packet) return new NetConnectRequestPacket(connectionTime, packet.ConnectionNumber, peerId, addressBytes, reader); } - public static NetPacket Make(NetDataWriter connectData, SocketAddress addressBytes, long connectTime, int localId) + internal static NetPacket Make(ReadOnlySpan connectData, SocketAddress addressBytes, long connectTime, int localId) { //Make initial packet var packet = new NetPacket(PacketProperty.ConnectRequest, connectData.Length+addressBytes.Size); @@ -62,10 +60,10 @@ public static NetPacket Make(NetDataWriter connectData, SocketAddress addressByt FastBitConverter.GetBytes(packet.RawData, 1, NetConstants.ProtocolId); FastBitConverter.GetBytes(packet.RawData, 5, connectTime); FastBitConverter.GetBytes(packet.RawData, 13, localId); - packet.RawData[HeaderSize-1] = (byte)addressBytes.Size; + packet.RawData[HeaderSize - 1] = (byte)addressBytes.Size; for (int i = 0; i < addressBytes.Size; i++) packet.RawData[HeaderSize + i] = addressBytes[i]; - Buffer.BlockCopy(connectData.Data, 0, packet.RawData, HeaderSize + addressBytes.Size, connectData.Length); + connectData.CopyTo(packet.RawData.AsSpan(HeaderSize + addressBytes.Size)); return packet; } } @@ -119,8 +117,8 @@ public static NetPacket Make(long connectTime, byte connectNum, int localPeerId) FastBitConverter.GetBytes(packet.RawData, 11, localPeerId); return packet; } - - public static NetPacket MakeNetworkChanged(NetPeer peer) + + public static NetPacket MakeNetworkChanged(LiteNetPeer peer) { var packet = new NetPacket(PacketProperty.PeerNotFound, Size-1); FastBitConverter.GetBytes(packet.RawData, 1, peer.ConnectTime); @@ -130,4 +128,4 @@ public static NetPacket MakeNetworkChanged(NetPeer peer) return packet; } } -} \ No newline at end of file +} diff --git a/LiteNetLib/Layers/Crc32cLayer.cs b/LiteNetLib/Layers/Crc32cLayer.cs index 3ee97d6c..dc07fe19 100644 --- a/LiteNetLib/Layers/Crc32cLayer.cs +++ b/LiteNetLib/Layers/Crc32cLayer.cs @@ -11,7 +11,7 @@ public Crc32cLayer() : base(CRC32C.ChecksumSize) } - public override void ProcessInboundPacket(ref IPEndPoint endPoint, ref byte[] data, ref int offset, ref int length) + public override void ProcessInboundPacket(ref IPEndPoint endPoint, ref byte[] data, ref int length) { if (length < NetConstants.HeaderSize + CRC32C.ChecksumSize) { @@ -22,7 +22,7 @@ public override void ProcessInboundPacket(ref IPEndPoint endPoint, ref byte[] da } int checksumPoint = length - CRC32C.ChecksumSize; - if (CRC32C.Compute(data, offset, checksumPoint) != BitConverter.ToUInt32(data, checksumPoint)) + if (CRC32C.Compute(data, 0, checksumPoint) != BitConverter.ToUInt32(data, checksumPoint)) { NetDebug.Write("[NM] DataReceived checksum: bad!"); //Set length to 0 to have netManager drop the packet. diff --git a/LiteNetLib/Layers/PacketLayerBase.cs b/LiteNetLib/Layers/PacketLayerBase.cs index b3d9b3a7..f2e4c881 100644 --- a/LiteNetLib/Layers/PacketLayerBase.cs +++ b/LiteNetLib/Layers/PacketLayerBase.cs @@ -11,7 +11,7 @@ protected PacketLayerBase(int extraPacketSizeForLayer) ExtraPacketSizeForLayer = extraPacketSizeForLayer; } - public abstract void ProcessInboundPacket(ref IPEndPoint endPoint, ref byte[] data, ref int offset, ref int length); + public abstract void ProcessInboundPacket(ref IPEndPoint endPoint, ref byte[] data, ref int length); public abstract void ProcessOutBoundPacket(ref IPEndPoint endPoint, ref byte[] data, ref int offset, ref int length); } } diff --git a/LiteNetLib/Layers/XorEncryptLayer.cs b/LiteNetLib/Layers/XorEncryptLayer.cs index 9b671969..a3a130bf 100644 --- a/LiteNetLib/Layers/XorEncryptLayer.cs +++ b/LiteNetLib/Layers/XorEncryptLayer.cs @@ -35,14 +35,13 @@ public void SetKey(byte[] key) Buffer.BlockCopy(key, 0, _byteKey, 0, key.Length); } - public override void ProcessInboundPacket(ref IPEndPoint endPoint, ref byte[] data, ref int offset, ref int length) + public override void ProcessInboundPacket(ref IPEndPoint endPoint, ref byte[] data, ref int length) { if (_byteKey == null) return; - var cur = offset; - for (var i = 0; i < length; i++, cur++) + for (int i = 0; i < length; i++) { - data[cur] = (byte)(data[cur] ^ _byteKey[i % _byteKey.Length]); + data[i] = (byte)(data[i] ^ _byteKey[i % _byteKey.Length]); } } @@ -50,8 +49,8 @@ public override void ProcessOutBoundPacket(ref IPEndPoint endPoint, ref byte[] d { if (_byteKey == null) return; - var cur = offset; - for (var i = 0; i < length; i++, cur++) + int cur = offset; + for (int i = 0; i < length; i++, cur++) { data[cur] = (byte)(data[cur] ^ _byteKey[i % _byteKey.Length]); } diff --git a/LiteNetLib/LiteNetLib.csproj b/LiteNetLib/LiteNetLib.csproj index 03f0b170..1b1249b1 100644 --- a/LiteNetLib/LiteNetLib.csproj +++ b/LiteNetLib/LiteNetLib.csproj @@ -3,15 +3,18 @@ LiteNetLib LiteNetLib - net6.0;net5.0;netcoreapp3.1;netstandard2.0;netstandard2.1 - net471;net6.0;net5.0;netstandard2.0;netstandard2.1;netcoreapp3.1 + net8.0;netstandard2.1 true Library - 7.3 + 8 true 1701;1702;1705;1591 - 1.0.0-rc.3 + 2.1.4 + Lite reliable UDP library for Mono and .NET + true + true + 2.1.4 @@ -22,31 +25,27 @@ TRACE + + $(DefineConstants);SIMULATE_NETWORK + + true $(DefineConstants);LITENETLIB_UNSAFE udp reliable-udp network - https://github.com/RevenantX/LiteNetLib/commits/master + https://github.com/RevenantX/LiteNetLib/releases/tag/2.1.4 git https://github.com/RevenantX/LiteNetLib https://github.com/RevenantX/LiteNetLib MIT True - 1.0.0-rc.2 Ruslan Pyrch - Copyright 2022 Ruslan Pyrch + Copyright 2026 Ruslan Pyrch Lite reliable UDP library for .NET, Mono, and .NET Core LNL.png README.md - - - - - - - True diff --git a/LiteNetLib/LiteNetManager.HashSet.cs b/LiteNetLib/LiteNetManager.HashSet.cs new file mode 100644 index 00000000..11fdd114 --- /dev/null +++ b/LiteNetLib/LiteNetManager.HashSet.cs @@ -0,0 +1,323 @@ +using System; +using System.Net; +using System.Threading; + +namespace LiteNetLib +{ + //minimal hashset class from dotnet with some optimizations + public partial class LiteNetManager + { + private const int MaxPrimeArrayLength = 0x7FFFFFC3; + private const int HashPrime = 101; + private const int Lower31BitMask = 0x7FFFFFFF; + private static readonly int[] Primes = + { + 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919, + 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591, + 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437, + 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263, + 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369 + }; + + private static int HashSetGetPrime(int min) + { + foreach (int prime in Primes) + { + if (prime >= min) + return prime; + } + + // Outside of our predefined table. Compute the hard way. + for (int i = (min | 1); i < int.MaxValue; i += 2) + { + if (IsPrime(i) && ((i - 1) % HashPrime != 0)) + return i; + } + return min; + + bool IsPrime(int candidate) + { + if ((candidate & 1) != 0) + { + int limit = (int)Math.Sqrt(candidate); + for (int divisor = 3; divisor <= limit; divisor += 2) + { + if (candidate % divisor == 0) + return false; + } + return true; + } + return candidate == 2; + } + } + + private struct Slot + { + internal int HashCode; + internal int Next; + internal LiteNetPeer Value; + } + + private int[] _buckets; + private Slot[] _slots; + private int _count; + private int _lastIndex; + private int _freeList = -1; + private LiteNetPeer[] _peersArray = new LiteNetPeer[32]; + + protected readonly ReaderWriterLockSlim _peersLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); + protected volatile LiteNetPeer _headPeer; + + private void ClearPeerSet() + { + _peersLock.EnterWriteLock(); + _headPeer = null; + if (_lastIndex > 0) + { + Array.Clear(_slots, 0, _lastIndex); + Array.Clear(_buckets, 0, _buckets.Length); + _lastIndex = 0; + _count = 0; + _freeList = -1; + } + _peersArray = new LiteNetPeer[32]; + _peersLock.ExitWriteLock(); + } + + protected bool ContainsPeer(LiteNetPeer item) + { + if (item == null) + { + NetDebug.WriteError($"Contains peer null: {item}"); + return false; + } + if (_buckets != null) + { + int hashCode = item.GetHashCode() & Lower31BitMask; + for (int i = _buckets[hashCode % _buckets.Length] - 1; i >= 0; i = _slots[i].Next) + { + if (_slots[i].HashCode == hashCode && _slots[i].Value.Equals(item)) + return true; + } + } + return false; + } + + /// + /// Gets peer by peer id + /// + /// id of peer + /// Peer if peer with id exist, otherwise null + public LiteNetPeer GetPeerById(int id) + { + return id >= 0 && id < _peersArray.Length ? _peersArray[id] : null; + } + + /// + /// Gets peer by peer id + /// + /// id of peer + /// resulting peer + /// if peer with id exist; otherwise + public bool TryGetPeerById(int id, out LiteNetPeer peer) + { + peer = GetPeerById(id); + return peer != null; + } + + private void AddPeer(LiteNetPeer peer) + { + if (peer == null) + { + NetDebug.WriteError($"Add peer null: {peer}"); + return; + } + _peersLock.EnterWriteLock(); + if (_headPeer != null) + { + peer.NextPeer = _headPeer; + _headPeer.PrevPeer = peer; + } + _headPeer = peer; + AddPeerToSet(peer); + if (peer.Id >= _peersArray.Length) + { + int newSize = _peersArray.Length * 2; + while (peer.Id >= newSize) + newSize *= 2; + Array.Resize(ref _peersArray, newSize); + } + _peersArray[peer.Id] = peer; + _peersLock.ExitWriteLock(); + } + + private void RemovePeer(LiteNetPeer peer, bool enableWriteLock) + { + if(enableWriteLock) + _peersLock.EnterWriteLock(); + if (!RemovePeerFromSet(peer)) + { + if(enableWriteLock) + _peersLock.ExitWriteLock(); + return; + } + if (peer == _headPeer) + _headPeer = peer.NextPeer; + + if (peer.PrevPeer != null) + peer.PrevPeer.NextPeer = peer.NextPeer; + if (peer.NextPeer != null) + peer.NextPeer.PrevPeer = peer.PrevPeer; + peer.PrevPeer = null; + + _peersArray[peer.Id] = null; + _peerIds.Enqueue(peer.Id); + + if(enableWriteLock) + _peersLock.ExitWriteLock(); + } + + protected bool RemovePeerFromSet(LiteNetPeer peer) + { + if (_buckets == null || peer == null) + return false; + int hashCode = peer.GetHashCode() & Lower31BitMask; + int bucket = hashCode % _buckets.Length; + int last = -1; + for (int i = _buckets[bucket] - 1; i >= 0; last = i, i = _slots[i].Next) + { + if (_slots[i].HashCode == hashCode && _slots[i].Value.Equals(peer)) + { + if (last < 0) + _buckets[bucket] = _slots[i].Next + 1; + else + _slots[last].Next = _slots[i].Next; + _slots[i].HashCode = -1; + _slots[i].Value = null; + _slots[i].Next = _freeList; + + _count--; + if (_count == 0) + { + _lastIndex = 0; + _freeList = -1; + } + else + { + _freeList = i; + } + return true; + } + } + return false; + } + + private bool TryGetPeer(IPEndPoint endPoint, out LiteNetPeer actualValue) + { + if (_buckets != null) + { +#if NET8_0_OR_GREATER + //can be NetPeer or IPEndPoint + int hashCode = (UseNativeSockets ? endPoint.GetHashCode() : endPoint.Serialize().GetHashCode()) & Lower31BitMask; +#else + int hashCode = endPoint.GetHashCode() & Lower31BitMask; +#endif + _peersLock.EnterReadLock(); + for (int i = _buckets[hashCode % _buckets.Length] - 1; i >= 0; i = _slots[i].Next) + { + if (_slots[i].HashCode == hashCode && _slots[i].Value.Equals(endPoint)) + { + actualValue = _slots[i].Value; + _peersLock.ExitReadLock(); + return true; + } + } + _peersLock.ExitReadLock(); + } + actualValue = null; + return false; + } + + //only used for NET8 + private bool TryGetPeer(SocketAddress saddr, out LiteNetPeer actualValue) + { + if (_buckets != null) + { + int hashCode = saddr.GetHashCode() & Lower31BitMask; + _peersLock.EnterReadLock(); + for (int i = _buckets[hashCode % _buckets.Length] - 1; i >= 0; i = _slots[i].Next) + { + if (_slots[i].HashCode == hashCode && _slots[i].Value.Serialize().Equals(saddr)) + { + actualValue = _slots[i].Value; + _peersLock.ExitReadLock(); + return true; + } + } + _peersLock.ExitReadLock(); + } + actualValue = null; + return false; + } + + protected bool AddPeerToSet(LiteNetPeer value) + { + if (_buckets == null) + { + int size = HashSetGetPrime(0); + _buckets = new int[size]; + _slots = new Slot[size]; + } + + int hashCode = value.GetHashCode() & Lower31BitMask; + int bucket = hashCode % _buckets.Length; + for (int i = _buckets[hashCode % _buckets.Length] - 1; i >= 0; i = _slots[i].Next) + { + if (_slots[i].HashCode == hashCode && _slots[i].Value.Equals(value)) + return false; + } + + int index; + if (_freeList >= 0) + { + index = _freeList; + _freeList = _slots[index].Next; + } + else + { + if (_lastIndex == _slots.Length) + { + //increase capacity + int newSize = 2 * _count; + newSize = (uint)newSize > MaxPrimeArrayLength && MaxPrimeArrayLength > _count + ? MaxPrimeArrayLength + : HashSetGetPrime(newSize); + + // Able to increase capacity; copy elements to larger array and rehash + Slot[] newSlots = new Slot[newSize]; + Array.Copy(_slots, 0, newSlots, 0, _lastIndex); + _buckets = new int[newSize]; + for (int i = 0; i < _lastIndex; i++) + { + int b = newSlots[i].HashCode % newSize; + newSlots[i].Next = _buckets[b] - 1; + _buckets[b] = i + 1; + } + _slots = newSlots; + // this will change during resize + bucket = hashCode % _buckets.Length; + } + index = _lastIndex; + _lastIndex++; + } + _slots[index].HashCode = hashCode; + _slots[index].Value = value; + _slots[index].Next = _buckets[bucket] - 1; + _buckets[bucket] = index + 1; + _count++; + + return true; + } + } + +} diff --git a/LiteNetLib/NetManager.PacketPool.cs b/LiteNetLib/LiteNetManager.PacketPool.cs similarity index 94% rename from LiteNetLib/NetManager.PacketPool.cs rename to LiteNetLib/LiteNetManager.PacketPool.cs index b2ae5e56..fd8520f6 100644 --- a/LiteNetLib/NetManager.PacketPool.cs +++ b/LiteNetLib/LiteNetManager.PacketPool.cs @@ -1,9 +1,8 @@ using System; -using System.Threading; namespace LiteNetLib { - public partial class NetManager + public partial class LiteNetManager { private NetPacket _poolHead; private int _poolCount; @@ -42,6 +41,9 @@ private NetPacket PoolGetWithProperty(PacketProperty property) internal NetPacket PoolGetPacket(int size) { + if (size > NetConstants.MaxPacketSize) + return new NetPacket(size); + NetPacket packet; lock (_poolLock) { diff --git a/LiteNetLib/NetManager.Socket.cs b/LiteNetLib/LiteNetManager.Socket.cs similarity index 57% rename from LiteNetLib/NetManager.Socket.cs rename to LiteNetLib/LiteNetManager.Socket.cs index 706e8f81..97d6870b 100644 --- a/LiteNetLib/NetManager.Socket.cs +++ b/LiteNetLib/LiteNetManager.Socket.cs @@ -1,8 +1,7 @@ -#if UNITY_IOS && !UNITY_EDITOR -using UnityEngine; +#if UNITY_2018_3_OR_NEWER +#define UNITY_SOCKET_FIX #endif using System.Runtime.InteropServices; - using System; using System.Collections.Generic; using System.Net; @@ -12,72 +11,34 @@ namespace LiteNetLib { -#if UNITY_IOS && !UNITY_EDITOR - public class UnitySocketFix : MonoBehaviour - { - internal IPAddress BindAddrIPv4; - internal IPAddress BindAddrIPv6; - internal int Port; - internal bool Paused; - internal NetManager Socket; - internal bool ManualMode; - - private void Update() - { - if (Socket == null) - Destroy(gameObject); - } - - private void OnApplicationPause(bool pause) - { - if (Socket == null) - return; - if (pause) - { - Paused = true; - Socket.CloseSocket(true); - } - else if (Paused) - { - if (!Socket.Start(BindAddrIPv4, BindAddrIPv6, Port, ManualMode)) - { - NetDebug.WriteError("[S] Cannot restore connection \"{0}\",\"{1}\" port {2}", BindAddrIPv4, BindAddrIPv6, Port); - Socket.CloseSocket(false); - } - } - } - } -#endif - - public partial class NetManager + public partial class LiteNetManager { - private const int ReceivePollingTime = 500000; //0.5 second - - private Socket _udpSocketv4; + protected Socket _udpSocketv4; private Socket _udpSocketv6; - private Thread _threadv4; - private Thread _threadv6; + private Thread _receiveThread; private IPEndPoint _bufferEndPointv4; private IPEndPoint _bufferEndPointv6; - -#if !LITENETLIB_UNSAFE - [ThreadStatic] private static byte[] _sendToBuffer; +#if UNITY_SOCKET_FIX + private PausedSocketFix _pausedSocketFix; + private bool _useSocketFix; #endif - [ThreadStatic] private static byte[] _endPointBuffer; - private readonly Dictionary _nativeAddrMap = new Dictionary(); +#if NET8_0_OR_GREATER + private readonly SocketAddress _sockAddrCacheV4 = new SocketAddress(AddressFamily.InterNetwork); + private readonly SocketAddress _sockAddrCacheV6 = new SocketAddress(AddressFamily.InterNetworkV6); +#endif private const int SioUdpConnreset = -1744830452; //SIO_UDP_CONNRESET = IOC_IN | IOC_VENDOR | 12 private static readonly IPAddress MulticastAddressV6 = IPAddress.Parse("ff02::1"); public static readonly bool IPv6Support; -#if UNITY_IOS && !UNITY_EDITOR - private UnitySocketFix _unitySocketFix; -#endif + + // special case in iOS (and possibly android that should be resolved in unity) + internal bool NotConnected; /// - /// Maximum packets count that will be processed in Manual PollEvents + /// Poll timeout in microseconds. Increasing can slightly increase performance in cost of slow NetManager.Stop(Socket.Close) /// - public int MaxPacketsReceivePerUpdate = 0; + public int ReceivePollingTime = 50000; //0.05 second public short Ttl { @@ -97,7 +58,7 @@ internal set } } - static NetManager() + static LiteNetManager() { #if DISABLE_IPV6 IPv6Support = false; @@ -109,40 +70,13 @@ static NetManager() #endif } - private bool IsActive() - { -#if UNITY_IOS && !UNITY_EDITOR - var unitySocketFix = _unitySocketFix; //save for multithread - if (unitySocketFix != null && unitySocketFix.Paused) - return false; -#endif - return IsRunning; - } - - private void RegisterEndPoint(IPEndPoint ep) - { - if (UseNativeSockets && ep is NativeEndPoint nep) - { - _nativeAddrMap.Add(new NativeAddr(nep.NativeAddress, nep.NativeAddress.Length), nep); - } - } - - private void UnregisterEndPoint(IPEndPoint ep) - { - if (UseNativeSockets && ep is NativeEndPoint nep) - { - var nativeAddr = new NativeAddr(nep.NativeAddress, nep.NativeAddress.Length); - _nativeAddrMap.Remove(nativeAddr); - } - } - private bool ProcessError(SocketException ex) { switch (ex.SocketErrorCode) { -#if UNITY_IOS && !UNITY_EDITOR case SocketError.NotConnected: -#endif + NotConnected = true; + return true; case SocketError.Interrupted: case SocketError.NotSocket: case SocketError.OperationAborted: @@ -151,6 +85,7 @@ private bool ProcessError(SocketException ex) case SocketError.MessageSize: case SocketError.TimedOut: case SocketError.NetworkReset: + case SocketError.WouldBlock: //NetDebug.Write($"[R]Ignored error: {(int)ex.SocketErrorCode} - {ex}"); break; default: @@ -166,16 +101,12 @@ private void ManualReceive(Socket socket, EndPoint bufferEndPoint) //Reading data try { - int packetsReceived = 0; - while (socket.Available > 0) + int count = MaxPacketPerManualReceive; + while (socket.Available > 0 && (count > 0 || MaxPacketPerManualReceive == 0)) { - var packet = PoolGetPacket(NetConstants.MaxPacketSize); - packet.Size = socket.ReceiveFrom(packet.RawData, 0, NetConstants.MaxPacketSize, SocketFlags.None, - ref bufferEndPoint); - //NetDebug.Write(NetLogLevel.Trace, $"[R]Received data from {bufferEndPoint}, result: {packet.Size}"); - OnMessageReceived(packet, (IPEndPoint) bufferEndPoint); - packetsReceived++; - if (packetsReceived == MaxPacketsReceivePerUpdate) + if(ReceiveFrom(socket, ref bufferEndPoint) > 0) + count--; + else break; } } @@ -190,66 +121,185 @@ private void ManualReceive(Socket socket, EndPoint bufferEndPoint) catch (Exception e) { //protects socket receive thread - NetDebug.WriteError("[NM] SocketReceiveThread error: " + e ); + NetDebug.WriteError("[NM] SocketReceiveThread error: " + e); } } - private void NativeReceiveLogic(object state) + private void NativeReceiveLogic() { - Socket socket = (Socket)state; - IntPtr socketHandle = socket.Handle; - byte[] addrBuffer = new byte[socket.AddressFamily == AddressFamily.InterNetwork - ? NativeSocket.IPv4AddrSize - : NativeSocket.IPv6AddrSize]; + IntPtr socketHandle4 = _udpSocketv4.Handle; + IntPtr socketHandle6 = _udpSocketv6?.Handle ?? IntPtr.Zero; + byte[] addrBuffer4 = new byte[NativeSocket.IPv4AddrSize]; + byte[] addrBuffer6 = new byte[NativeSocket.IPv6AddrSize]; + var tempEndPoint = new IPEndPoint(IPAddress.Any, 0); + var selectReadList = new List(2); + var socketv4 = _udpSocketv4; + var socketV6 = _udpSocketv6; + var packet = PoolGetPacket(NetConstants.MaxPacketSize); + + while (_isRunning) + { + try + { + if (socketV6 == null) + { + if (NativeReceiveFrom(socketHandle4, addrBuffer4) == false) + return; + continue; + } + bool messageReceived = false; + if (socketv4.Available != 0 || selectReadList.Contains(socketv4)) + { + if (NativeReceiveFrom(socketHandle4, addrBuffer4) == false) + return; + messageReceived = true; + } + if (socketV6.Available != 0 || selectReadList.Contains(socketV6)) + { + if (NativeReceiveFrom(socketHandle6, addrBuffer6) == false) + return; + messageReceived = true; + } + + selectReadList.Clear(); + + if (messageReceived) + continue; - int addrSize = addrBuffer.Length; - NetPacket packet = PoolGetPacket(NetConstants.MaxPacketSize); + selectReadList.Add(socketv4); + selectReadList.Add(socketV6); - while (IsActive()) + Socket.Select(selectReadList, null, null, ReceivePollingTime); + } + catch (SocketException ex) + { + if (ProcessError(ex)) + return; + } + catch (ObjectDisposedException) + { + //socket closed + return; + } + catch (ThreadAbortException) + { + //thread closed + return; + } + catch (Exception e) + { + //protects socket receive thread + NetDebug.WriteError("[NM] SocketReceiveThread error: " + e); + } + } + + bool NativeReceiveFrom(IntPtr s, byte[] address) { - //Reading data - packet.Size = NativeSocket.RecvFrom(socketHandle, packet.RawData, NetConstants.MaxPacketSize, addrBuffer, ref addrSize); + int addrSize = address.Length; + packet.Size = NativeSocket.RecvFrom(s, packet.RawData, NetConstants.MaxPacketSize, address, ref addrSize); if (packet.Size == 0) - return; + return true; //socket closed or empty packet + if (packet.Size == -1) { - SocketError errorCode = NativeSocket.GetSocketError(); - if (errorCode == SocketError.WouldBlock || errorCode == SocketError.TimedOut) //Linux timeout EAGAIN - continue; - if (ProcessError(new SocketException((int)errorCode))) - return; - continue; + //Linux timeout EAGAIN + return ProcessError(new SocketException((int)NativeSocket.GetSocketError())) == false; } - NativeAddr nativeAddr = new NativeAddr(addrBuffer, addrSize); - if (!_nativeAddrMap.TryGetValue(nativeAddr, out var endPoint)) - endPoint = new NativeEndPoint(addrBuffer); - - //All ok! //NetDebug.WriteForce($"[R]Received data from {endPoint}, result: {packet.Size}"); - OnMessageReceived(packet, endPoint); + //refresh temp Addr/Port + short family = (short)((address[1] << 8) | address[0]); + tempEndPoint.Port = (ushort)((address[2] << 8) | address[3]); + if ((NativeSocket.UnixMode && family == NativeSocket.AF_INET6) || (!NativeSocket.UnixMode && (AddressFamily)family == AddressFamily.InterNetworkV6)) + { + uint scope = unchecked((uint)( + (address[27] << 24) + + (address[26] << 16) + + (address[25] << 8) + + (address[24]))); + tempEndPoint.Address = new IPAddress(new ReadOnlySpan(address, 8, 16), scope); + } + else //IPv4 + { + long ipv4Addr = unchecked((uint)((address[4] & 0x000000FF) | + (address[5] << 8 & 0x0000FF00) | + (address[6] << 16 & 0x00FF0000) | + (address[7] << 24))); + tempEndPoint.Address = new IPAddress(ipv4Addr); + } + + if (TryGetPeer(tempEndPoint, out var peer)) + { + //use cached native ep + OnMessageReceived(packet, peer); + } + else + { + OnMessageReceived(packet, tempEndPoint); + tempEndPoint = new IPEndPoint(IPAddress.Any, 0); + } packet = PoolGetPacket(NetConstants.MaxPacketSize); + return true; } } - private void ReceiveLogic(object state) + private int ReceiveFrom(Socket s, ref EndPoint bufferEndPoint) { - Socket socket = (Socket)state; - EndPoint bufferEndPoint = new IPEndPoint(socket.AddressFamily == AddressFamily.InterNetwork ? IPAddress.Any : IPAddress.IPv6Any, 0); + var packet = PoolGetPacket(NetConstants.MaxPacketSize); +#if NET8_0_OR_GREATER + var sockAddr = s.AddressFamily == AddressFamily.InterNetwork ? _sockAddrCacheV4 : _sockAddrCacheV6; + packet.Size = s.ReceiveFrom(new Span(packet.RawData, 0, NetConstants.MaxPacketSize), SocketFlags.None, sockAddr); + OnMessageReceived(packet, TryGetPeer(sockAddr, out var peer) ? peer : (IPEndPoint)bufferEndPoint.Create(sockAddr)); +#else + packet.Size = s.ReceiveFrom(packet.RawData, 0, NetConstants.MaxPacketSize, SocketFlags.None, ref bufferEndPoint); + OnMessageReceived(packet, (IPEndPoint)bufferEndPoint); +#endif + return packet.Size; + } - while (IsActive()) + private void ReceiveLogic() + { + EndPoint bufferEndPoint4 = new IPEndPoint(IPAddress.Any, 0); + EndPoint bufferEndPoint6 = new IPEndPoint(IPAddress.IPv6Any, 0); + var selectReadList = new List(2); + var socketv4 = _udpSocketv4; + var socketV6 = _udpSocketv6; + + while (_isRunning) { //Reading data try { - if (socket.Available == 0 && !socket.Poll(ReceivePollingTime, SelectMode.SelectRead)) - continue; - NetPacket packet = PoolGetPacket(NetConstants.MaxPacketSize); - packet.Size = socket.ReceiveFrom(packet.RawData, 0, NetConstants.MaxPacketSize, SocketFlags.None, - ref bufferEndPoint); + if (socketV6 == null) + { + if (socketv4.Available == 0 && !socketv4.Poll(ReceivePollingTime, SelectMode.SelectRead)) + continue; + ReceiveFrom(socketv4, ref bufferEndPoint4); + } + else + { + bool messageReceived = false; + if (socketv4.Available != 0 || selectReadList.Contains(socketv4)) + { + ReceiveFrom(socketv4, ref bufferEndPoint4); + messageReceived = true; + } + if (socketV6.Available != 0 || selectReadList.Contains(socketV6)) + { + ReceiveFrom(socketV6, ref bufferEndPoint6); + messageReceived = true; + } + selectReadList.Clear(); + + if (messageReceived) + continue; + + selectReadList.Add(socketv4); + selectReadList.Add(socketV6); + Socket.Select(selectReadList, null, null, ReceivePollingTime); + } //NetDebug.Write(NetLogLevel.Trace, $"[R]Received data from {bufferEndPoint}, result: {packet.Size}"); - OnMessageReceived(packet, (IPEndPoint)bufferEndPoint); } catch (SocketException ex) { @@ -269,106 +319,78 @@ private void ReceiveLogic(object state) catch (Exception e) { //protects socket receive thread - NetDebug.WriteError("[NM] SocketReceiveThread error: " + e ); + NetDebug.WriteError("[NM] SocketReceiveThread error: " + e); } } } - /// + /// /// Start logic thread and listening on selected port /// /// bind to specific ipv4 address /// bind to specific ipv6 address /// port to listen - /// mode of library + /// + /// When , disables internal background threads.
+ /// You must manually call and .

+ /// Can be used when e.g. 10,000 instances are running on the same machine, reducing the amount of threads used. + /// public bool Start(IPAddress addressIPv4, IPAddress addressIPv6, int port, bool manualMode) { - if (IsRunning && !IsActive()) + if (IsRunning && NotConnected == false) return false; + + NotConnected = false; _manualMode = manualMode; UseNativeSockets = UseNativeSockets && NativeSocket.IsSupported; - - //osx doesn't support dual mode - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && IPv6Mode == IPv6Mode.DualMode) - IPv6Mode = IPv6Mode.SeparateSocket; - - bool dualMode = IPv6Mode == IPv6Mode.DualMode && IPv6Support; - - _udpSocketv4 = new Socket( - dualMode ? AddressFamily.InterNetworkV6 : AddressFamily.InterNetwork, - SocketType.Dgram, - ProtocolType.Udp); - - if (!BindSocket(_udpSocketv4, new IPEndPoint(dualMode ? addressIPv6 : addressIPv4, port))) + _udpSocketv4 = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + if (!BindSocket(_udpSocketv4, new IPEndPoint(addressIPv4, port))) return false; - LocalPort = ((IPEndPoint) _udpSocketv4.LocalEndPoint).Port; + LocalPort = ((IPEndPoint)_udpSocketv4.LocalEndPoint).Port; -#if UNITY_IOS && !UNITY_EDITOR - if (_unitySocketFix == null) - { - var unityFixObj = new GameObject("LiteNetLib_UnitySocketFix"); - GameObject.DontDestroyOnLoad(unityFixObj); - _unitySocketFix = unityFixObj.AddComponent(); - _unitySocketFix.Socket = this; - _unitySocketFix.BindAddrIPv4 = addressIPv4; - _unitySocketFix.BindAddrIPv6 = addressIPv6; - _unitySocketFix.Port = LocalPort; - _unitySocketFix.ManualMode = _manualMode; - } - else - { - _unitySocketFix.Paused = false; - } +#if UNITY_SOCKET_FIX + if (_useSocketFix && _pausedSocketFix == null) + _pausedSocketFix = new PausedSocketFix(this, addressIPv4, addressIPv6, port, manualMode); #endif - if (dualMode) - _udpSocketv6 = _udpSocketv4; - - IsRunning = true; - if (!_manualMode) - { - ParameterizedThreadStart ts = ReceiveLogic; - if (UseNativeSockets) - ts = NativeReceiveLogic; - - _threadv4 = new Thread(ts) - { - Name = $"SocketThreadv4({LocalPort})", - IsBackground = true - }; - _threadv4.Start(_udpSocketv4); - _logicThread = new Thread(UpdateLogic) { Name = "LogicThread", IsBackground = true }; - _logicThread.Start(); - } - else + _isRunning = true; + if (_manualMode) { _bufferEndPointv4 = new IPEndPoint(IPAddress.Any, 0); } //Check IPv6 support - if (IPv6Support && IPv6Mode == IPv6Mode.SeparateSocket) + if (IPv6Support && IPv6Enabled) { _udpSocketv6 = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp); //Use one port for two sockets if (BindSocket(_udpSocketv6, new IPEndPoint(addressIPv6, LocalPort))) { if (_manualMode) - { _bufferEndPointv6 = new IPEndPoint(IPAddress.IPv6Any, 0); - } - else - { - ParameterizedThreadStart ts = ReceiveLogic; - if (UseNativeSockets) - ts = NativeReceiveLogic; - _threadv6 = new Thread(ts) - { - Name = $"SocketThreadv6({LocalPort})", - IsBackground = true - }; - _threadv6.Start(_udpSocketv6); - } + } + else + { + _udpSocketv6 = null; + } + } + + if (!manualMode) + { + ThreadStart ts = ReceiveLogic; + if (UseNativeSockets) + ts = NativeReceiveLogic; + _receiveThread = new Thread(ts) + { + Name = $"ReceiveThread({LocalPort})", + IsBackground = true + }; + _receiveThread.Start(); + if (_logicThread == null) + { + _logicThread = new Thread(UpdateLogic) { Name = "LogicThread", IsBackground = true }; + _logicThread.Start(); } } @@ -382,12 +404,13 @@ private bool BindSocket(Socket socket, IPEndPoint ep) socket.SendTimeout = 500; socket.ReceiveBufferSize = NetConstants.SocketBufferSize; socket.SendBufferSize = NetConstants.SocketBufferSize; + socket.Blocking = true; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { try { - socket.IOControl(SioUdpConnreset, new byte[] {0}, null); + socket.IOControl(SioUdpConnreset, new byte[] { 0 }, null); } catch { @@ -399,12 +422,13 @@ private bool BindSocket(Socket socket, IPEndPoint ep) { socket.ExclusiveAddressUse = !ReuseAddress; socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, ReuseAddress); + socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontRoute, DontRoute); } catch { //Unity with IL2CPP throws an exception here, it doesn't matter in most cases so just ignore it } - if (ep.AddressFamily == AddressFamily.InterNetwork || IPv6Mode == IPv6Mode.DualMode) + if (ep.AddressFamily == AddressFamily.InterNetwork) { Ttl = NetConstants.SocketTTL; @@ -414,15 +438,7 @@ private bool BindSocket(Socket socket, IPEndPoint ep) NetDebug.WriteError($"[B]Broadcast error: {e.SocketErrorCode}"); } - if (IPv6Mode == IPv6Mode.DualMode) - { - try { socket.DualMode = true; } - catch(Exception e) - { - NetDebug.WriteError($"[B]Bind exception (dualmode setting): {e}"); - } - } - else if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { try { socket.DontFragment = true; } catch (SocketException e) @@ -442,7 +458,7 @@ private bool BindSocket(Socket socket, IPEndPoint ep) { try { -#if !UNITY_2018_3_OR_NEWER +#if !UNITY_SOCKET_FIX socket.SetSocketOption( SocketOptionLevel.IPv6, SocketOptionName.AddMembership, @@ -461,7 +477,7 @@ private bool BindSocket(Socket socket, IPEndPoint ep) { //IPv6 bind fix case SocketError.AddressAlreadyInUse: - if (socket.AddressFamily == AddressFamily.InterNetworkV6 && IPv6Mode != IPv6Mode.DualMode) + if (socket.AddressFamily == AddressFamily.InterNetworkV6) { try { @@ -495,14 +511,12 @@ internal int SendRawAndRecycle(NetPacket packet, IPEndPoint remoteEndPoint) return result; } - internal int SendRaw(NetPacket packet, IPEndPoint remoteEndPoint) - { - return SendRaw(packet.RawData, 0, packet.Size, remoteEndPoint); - } + internal int SendRaw(NetPacket packet, IPEndPoint remoteEndPoint) => + SendRaw(packet.RawData, 0, packet.Size, remoteEndPoint); internal int SendRaw(byte[] message, int start, int length, IPEndPoint remoteEndPoint) { - if (!IsRunning) + if (!_isRunning) return 0; NetPacket expandedPacket = null; @@ -515,6 +529,34 @@ internal int SendRaw(byte[] message, int start, int length, IPEndPoint remoteEnd message = expandedPacket.RawData; } +#if DEBUG || SIMULATE_NETWORK + if (HandleSimulateOutboundPacketLoss()) + { + if (expandedPacket != null) + PoolRecycle(expandedPacket); + return 0; // Simulate successful send to avoid triggering error handling + } + + if (HandleSimulateOutboundLatency(message, start, length, remoteEndPoint)) + { + if (expandedPacket != null) + PoolRecycle(expandedPacket); + return length; // Simulate successful send + } +#endif + + int result = SendRawCore(message, start, length, remoteEndPoint); + if (expandedPacket != null) + PoolRecycle(expandedPacket); + return result; + } + + // Core socket sending logic without simulation - used by both SendRaw and delayed packet processing + internal int SendRawCore(byte[] message, int start, int length, IPEndPoint remoteEndPoint) + { + if (!_isRunning) + return 0; + var socket = _udpSocketv4; if (remoteEndPoint.AddressFamily == AddressFamily.InterNetworkV6 && IPv6Support) { @@ -526,75 +568,23 @@ internal int SendRaw(byte[] message, int start, int length, IPEndPoint remoteEnd int result; try { - if (UseNativeSockets) + if (UseNativeSockets && remoteEndPoint is LiteNetPeer peer) { - byte[] socketAddress; - - if (remoteEndPoint is NativeEndPoint nep) - { - socketAddress = nep.NativeAddress; - } - else //Convert endpoint to raw - { - if (_endPointBuffer == null) - _endPointBuffer = new byte[NativeSocket.IPv6AddrSize]; - socketAddress = _endPointBuffer; - - bool ipv4 = remoteEndPoint.AddressFamily == AddressFamily.InterNetwork; - short addressFamily = NativeSocket.GetNativeAddressFamily(remoteEndPoint); - - socketAddress[0] = (byte) (addressFamily); - socketAddress[1] = (byte) (addressFamily >> 8); - socketAddress[2] = (byte) (remoteEndPoint.Port >> 8); - socketAddress[3] = (byte) (remoteEndPoint.Port); - - if (ipv4) - { -#pragma warning disable 618 - long addr = remoteEndPoint.Address.Address; -#pragma warning restore 618 - socketAddress[4] = (byte) (addr); - socketAddress[5] = (byte) (addr >> 8); - socketAddress[6] = (byte) (addr >> 16); - socketAddress[7] = (byte) (addr >> 24); - } - else - { -#if NETCOREAPP || NETSTANDARD2_1 || NETSTANDARD2_1_OR_GREATER - remoteEndPoint.Address.TryWriteBytes(new Span(socketAddress, 8, 16), out _); -#else - byte[] addrBytes = remoteEndPoint.Address.GetAddressBytes(); - Buffer.BlockCopy(addrBytes, 0, socketAddress, 8, 16); -#endif - } - } - -#if LITENETLIB_UNSAFE unsafe { fixed (byte* dataWithOffset = &message[start]) - { - result = - NativeSocket.SendTo(socket.Handle, dataWithOffset, length, socketAddress, socketAddress.Length); - } + result = NativeSocket.SendTo(socket.Handle, dataWithOffset, length, peer.NativeAddress, peer.NativeAddress.Length); } -#else - if (start > 0) - { - if (_sendToBuffer == null) - _sendToBuffer = new byte[NetConstants.MaxPacketSize]; - Buffer.BlockCopy(message, start, _sendToBuffer, 0, length); - message = _sendToBuffer; - } - - result = NativeSocket.SendTo(socket.Handle, message, length, socketAddress, socketAddress.Length); -#endif if (result == -1) throw NativeSocket.GetSocketException(); } else { +#if NET8_0_OR_GREATER + result = socket.SendTo(new ReadOnlySpan(message, start, length), SocketFlags.None, remoteEndPoint.Serialize()); +#else result = socket.SendTo(message, start, length, SocketFlags.None, remoteEndPoint); +#endif } //NetDebug.WriteForce("[S]Send packet to {0}, result: {1}", remoteEndPoint, result); } @@ -606,15 +596,15 @@ internal int SendRaw(byte[] message, int start, int length, IPEndPoint remoteEnd case SocketError.Interrupted: return 0; case SocketError.MessageSize: - NetDebug.Write(NetLogLevel.Trace, "[SRD] 10040, datalen: {0}", length); + NetDebug.Write(NetLogLevel.Trace, $"[SRD] 10040, datalen: {length}"); return 0; case SocketError.HostUnreachable: case SocketError.NetworkUnreachable: - if (DisconnectOnUnreachable && TryGetPeer(remoteEndPoint, out var fromPeer)) + if (DisconnectOnUnreachable && remoteEndPoint is LiteNetPeer peer) { DisconnectPeerForce( - fromPeer, + peer, ex.SocketErrorCode == SocketError.HostUnreachable ? DisconnectReason.HostUnreachable : DisconnectReason.NetworkUnreachable, @@ -625,6 +615,10 @@ internal int SendRaw(byte[] message, int start, int length, IPEndPoint remoteEnd CreateEvent(NetEvent.EType.Error, remoteEndPoint: remoteEndPoint, errorCode: ex.SocketErrorCode); return -1; + case SocketError.Shutdown: + CreateEvent(NetEvent.EType.Error, remoteEndPoint: remoteEndPoint, errorCode: ex.SocketErrorCode); + return -1; + default: NetDebug.WriteError($"[S] {ex}"); return -1; @@ -635,13 +629,6 @@ internal int SendRaw(byte[] message, int start, int length, IPEndPoint remoteEnd NetDebug.WriteError($"[S] {ex}"); return 0; } - finally - { - if (expandedPacket != null) - { - PoolRecycle(expandedPacket); - } - } if (result <= 0) return 0; @@ -655,19 +642,15 @@ internal int SendRaw(byte[] message, int start, int length, IPEndPoint remoteEnd return result; } - public bool SendBroadcast(NetDataWriter writer, int port) - { - return SendBroadcast(writer.Data, 0, writer.Length, port); - } + public bool SendBroadcast(NetDataWriter writer, int port) => + SendBroadcast(writer.Data, 0, writer.Length, port); - public bool SendBroadcast(byte[] data, int port) - { - return SendBroadcast(data, 0, data.Length, port); - } + public bool SendBroadcast(byte[] data, int port) => + SendBroadcast(data, 0, data.Length, port); public bool SendBroadcast(byte[] data, int start, int length, int port) { - if (!IsActive()) + if (!IsRunning) return false; NetPacket packet; @@ -708,6 +691,13 @@ public bool SendBroadcast(byte[] data, int start, int length, int port) new IPEndPoint(MulticastAddressV6, port)) > 0; } } + catch (SocketException ex) + { + if (ex.SocketErrorCode == SocketError.HostUnreachable) + return broadcastSuccess; + NetDebug.WriteError($"[S][MCAST] {ex}"); + return broadcastSuccess; + } catch (Exception ex) { NetDebug.WriteError($"[S][MCAST] {ex}"); @@ -721,31 +711,16 @@ public bool SendBroadcast(byte[] data, int start, int length, int port) return broadcastSuccess || multicastSuccess; } - internal void CloseSocket(bool suspend) + private void CloseSocket() { - if (!suspend) - { - IsRunning = false; -#if UNITY_IOS && !UNITY_EDITOR - _unitySocketFix.Socket = null; - _unitySocketFix = null; -#endif - } - //cleanup dual mode - if (_udpSocketv4 == _udpSocketv6) - _udpSocketv6 = null; - + _isRunning = false; + if (_receiveThread != null && _receiveThread != Thread.CurrentThread) + _receiveThread.Join(); + _receiveThread = null; _udpSocketv4?.Close(); _udpSocketv6?.Close(); _udpSocketv4 = null; _udpSocketv6 = null; - - if (_threadv4 != null && _threadv4 != Thread.CurrentThread) - _threadv4.Join(); - if (_threadv6 != null && _threadv6 != Thread.CurrentThread) - _threadv6.Join(); - _threadv4 = null; - _threadv6 = null; } } } diff --git a/LiteNetLib/LiteNetManager.cs b/LiteNetLib/LiteNetManager.cs new file mode 100644 index 00000000..3d173ce7 --- /dev/null +++ b/LiteNetLib/LiteNetManager.cs @@ -0,0 +1,1672 @@ +#if UNITY_2018_3_OR_NEWER +#define UNITY_SOCKET_FIX +#endif +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using LiteNetLib.Layers; +using LiteNetLib.Utils; + +namespace LiteNetLib +{ + /// + /// Main class for all network operations. Can be used as client and/or server. + /// + public partial class LiteNetManager : IEnumerable + { + public struct NetPeerEnumerator : IEnumerator where T : LiteNetPeer + { + private readonly T _initialPeer; + private T _p; + + public NetPeerEnumerator(T p) + { + _initialPeer = p; + _p = null; + } + + public void Dispose() { } + + public bool MoveNext() + { + _p = _p == null ? _initialPeer : (T)_p.NextPeer; + return _p != null; + } + + public void Reset() => + throw new NotSupportedException(); + + public T Current => _p; + object IEnumerator.Current => _p; + } + +#if DEBUG || SIMULATE_NETWORK + private struct IncomingData + { + public NetPacket Data; + public IPEndPoint EndPoint; + public DateTime TimeWhenGet; + } + private readonly List _pingSimulationList = new List(); + + private struct OutboundDelayedPacket + { + public byte[] Data; + public int Start; + public int Length; + public IPEndPoint EndPoint; + public DateTime TimeWhenSend; + } + private readonly List _outboundSimulationList = new List(); +#endif + + private readonly Random _randomGenerator = new Random(); + private const int MinLatencyThreshold = 5; + + private Thread _logicThread; + private bool _manualMode; + private readonly AutoResetEvent _updateTriggerEvent = new AutoResetEvent(true); + + private NetEvent _pendingEventHead; + private NetEvent _pendingEventTail; + + private NetEvent _netEventPoolHead; + private readonly ILiteNetEventListener _netEventListener; + + private readonly Dictionary _requestsDict = new Dictionary(); + + private long _connectedPeersCount; + private readonly PacketLayerBase _extraPacketLayer; + private int _lastPeerId; + private ConcurrentQueue _peerIds = new ConcurrentQueue(); + + private readonly object _eventLock = new object(); + private volatile bool _isRunning; + + /// + /// Used with and to tag packets that + /// need to be dropped. Only relevant when DEBUG is defined. + /// + private bool _dropPacket; + + //config section + /// + /// Enable messages receiving without connection. (with SendUnconnectedMessage method) + /// + public bool UnconnectedMessagesEnabled = false; + + /// + /// Enable nat punch messages + /// + public bool NatPunchEnabled = false; + + /// + /// Library logic update and send period in milliseconds + /// Lowest values in Windows doesn't change much because of Thread.Sleep precision + /// To more frequent sends (or sends tied to your game logic) use + /// + public int UpdateTime = 15; + + /// + /// Interval for latency detection and checking connection (in milliseconds) + /// + public int PingInterval = 1000; + + /// + /// If NetManager doesn't receive any packet from remote peer during this time (in milliseconds) then connection will be closed + /// (including library internal keepalive packets) + /// + public int DisconnectTimeout = 5000; + + /// + /// Simulate packet loss by dropping random amount of packets. (Works only in DEBUG builds or when SIMULATE_NETWORK is defined) + /// + public bool SimulatePacketLoss = false; + + /// + /// Simulate latency by holding packets for random time. (Works only in DEBUG builds or when SIMULATE_NETWORK is defined) + /// + public bool SimulateLatency = false; + + /// + /// Chance of packet loss when simulation enabled. value in percents (1 - 100). + /// + public int SimulationPacketLossChance = 10; + + /// + /// Minimum simulated round-trip latency (in milliseconds). Actual latency applied per direction is half of this value. + /// + public int SimulationMinLatency = 30; + + /// + /// Maximum simulated round-trip latency (in milliseconds). Actual latency applied per direction is half of this value. + /// + public int SimulationMaxLatency = 100; + + /// + /// Events automatically will be called without PollEvents method from another thread + /// + public bool UnsyncedEvents = false; + + /// + /// If true - receive event will be called from "receive" thread immediately otherwise on PollEvents call + /// + public bool UnsyncedReceiveEvent = false; + + /// + /// If true - delivery event will be called from "receive" thread immediately otherwise on PollEvents call + /// + public bool UnsyncedDeliveryEvent = false; + + /// + /// Allows receive broadcast packets + /// + public bool BroadcastReceiveEnabled = false; + + /// + /// Delay between initial connection attempts (in milliseconds) + /// + public int ReconnectDelay = 500; + + /// + /// Maximum connection attempts before client stops and call disconnect event. + /// + public int MaxConnectAttempts = 10; + + /// + /// Enables socket option "ReuseAddress" for specific purposes + /// + public bool ReuseAddress = false; + + /// + /// UDP Only Socket Option + /// Normally IP sockets send packets of data through routers and gateways until they reach the final destination. + /// If the DontRoute flag is set to True, then data will be delivered on the local subnet only. + /// + public bool DontRoute = false; + + /// + /// Statistics of all connections + /// + public readonly NetStatistics Statistics = new NetStatistics(); + + /// + /// Toggles the collection of network statistics for the instance and all known peers + /// + public bool EnableStatistics = false; + + /// + /// Max fragmented packets size for reliable channels - that equals to data of size fragments count * (MTU-reliable header size) + /// + public ushort MaxFragmentsCount = ushort.MaxValue; + + /// + /// NatPunchModule for NAT hole punching operations + /// + public NatPunchModule NatPunchModule => _natPunchModule.Value; + + private readonly Lazy _natPunchModule; + + /// + /// Returns true if socket listening and update thread is running + /// + public bool IsRunning => _isRunning; + + /// + /// Local EndPoint (host and port) + /// + public int LocalPort { get; private set; } + + /// + /// Automatically recycle NetPacketReader after OnReceive event + /// + public bool AutoRecycle; + + /// + /// IPv6 support + /// + public bool IPv6Enabled = true; + + /// + /// Override MTU for all new peers registered in this NetManager, will ignores MTU Discovery! + /// + public int MtuOverride = 0; + + /// + /// Automatically discovery mtu starting from. Use at own risk because some routers can break MTU detection + /// and connection in result + /// + public bool MtuDiscovery = false; + + /// + /// First peer. Useful for Client mode + /// + public LiteNetPeer FirstPeer => _headPeer; + + /// + /// Experimental feature mostly for servers. Only for Windows/Linux + /// use direct socket calls for send/receive to drastically increase speed and reduce GC pressure + /// + public bool UseNativeSockets = false; + + /// + /// Disconnect peers if HostUnreachable or NetworkUnreachable spawned (old behaviour 0.9.x was true) + /// + public bool DisconnectOnUnreachable = false; + + /// + /// Allows peer change it's ip (lte to wifi, wifi to lte, etc). Use only on server + /// + public bool AllowPeerAddressChange = false; + + /// + /// Returns connected peers count + /// + public int ConnectedPeersCount => (int)Interlocked.Read(ref _connectedPeersCount); + + /// + /// Maximum packets that can be received per one ManualReceive (PollEvents when started using StartInManualMode) + /// 0 - infinite - but this can cause some big delays there is too many incoming packets + /// + public int MaxPacketPerManualReceive = 256; + + /// + /// Gets the additional size in bytes required by the active . + /// + /// + /// This value is used by to calculate the available MTU for user data.
+ /// If a packet layer is active (e.g., for encryption or CRC), this returns the overhead added to every packet. + /// Returns 0 if no packet layer is assigned. + ///
+ public int ExtraPacketSizeForLayer => _extraPacketLayer?.ExtraPacketSizeForLayer ?? 0; + + /// + /// NetManager constructor + /// + /// Network events listener (also can implement IDeliveryEventListener) + /// Extra processing of packages, like CRC checksum or encryption. All connected NetManagers must have same layer. +#if UNITY_SOCKET_FIX + public LiteNetManager(ILiteNetEventListener listener, PacketLayerBase extraPacketLayer = null, bool useSocketFix = true) + { + _useSocketFix = useSocketFix; +#else + public LiteNetManager(ILiteNetEventListener listener, PacketLayerBase extraPacketLayer = null) + { +#endif + _netEventListener = listener; + _natPunchModule = new Lazy(() => new NatPunchModule(this)); + _extraPacketLayer = extraPacketLayer; + } + + internal void ConnectionLatencyUpdated(LiteNetPeer fromPeer, int latency) => + CreateEvent(NetEvent.EType.ConnectionLatencyUpdated, fromPeer, latency: latency); + + internal void MessageDelivered(LiteNetPeer fromPeer, object userData) => + CreateEvent(NetEvent.EType.MessageDelivered, fromPeer, userData: userData); + + internal void DisconnectPeerForce(LiteNetPeer peer, + DisconnectReason reason, + SocketError socketErrorCode, + NetPacket eventData) => + DisconnectPeer(peer, reason, socketErrorCode, true, ReadOnlySpan.Empty, eventData); + + /// + /// Disconnects a peer and handles internal state cleanup. + /// + /// The peer to disconnect. + /// The reason for disconnection provided to the event listener. + /// The error code from the underlying socket, if any. + /// + /// If , immediately sets state to without sending a notification.
+ /// If , sends unreliable disconnect packets until and sets state to . + /// Peer will linger until to ignore late-arriving packets from the old session. + /// + /// Optional custom data to include in the disconnect packet. + /// Internal packet data associated with the disconnect event. + private void DisconnectPeer( + LiteNetPeer peer, + DisconnectReason reason, + SocketError socketErrorCode, + bool force, + ReadOnlySpan data, + NetPacket eventData) + { + var shutdownResult = peer.Shutdown(data, force); + if (shutdownResult == ShutdownResult.None) + return; + if (shutdownResult == ShutdownResult.WasConnected) + Interlocked.Decrement(ref _connectedPeersCount); + CreateEvent( + NetEvent.EType.Disconnect, + peer, + errorCode: socketErrorCode, + disconnectReason: reason, + readerSource: eventData); + } + + private void CreateEvent( + NetEvent.EType type, + LiteNetPeer peer = null, + IPEndPoint remoteEndPoint = null, + SocketError errorCode = 0, + int latency = 0, + DisconnectReason disconnectReason = DisconnectReason.ConnectionFailed, + LiteConnectionRequest connectionRequest = null, + DeliveryMethod deliveryMethod = DeliveryMethod.Unreliable, + byte channelNumber = 0, + NetPacket readerSource = null, + object userData = null) + { + NetEvent evt; + bool unsyncEvent = UnsyncedEvents; + + if (type == NetEvent.EType.Connect) + Interlocked.Increment(ref _connectedPeersCount); + else if (type == NetEvent.EType.MessageDelivered) + unsyncEvent = UnsyncedDeliveryEvent; + + lock (_eventLock) + { + evt = _netEventPoolHead; + if (evt == null) + evt = new NetEvent(this); + else + _netEventPoolHead = evt.Next; + } + + evt.Next = null; + evt.Type = type; + evt.DataReader.SetSource(readerSource, readerSource?.HeaderSize ?? 0); + evt.Peer = peer; + evt.RemoteEndPoint = remoteEndPoint; + evt.Latency = latency; + evt.ErrorCode = errorCode; + evt.DisconnectReason = disconnectReason; + evt.ConnectionRequest = connectionRequest; + evt.DeliveryMethod = deliveryMethod; + evt.ChannelNumber = channelNumber; + evt.UserData = userData; + + if (unsyncEvent || _manualMode) + { + ProcessEvent(evt); + } + else + { + lock (_eventLock) + { + if (_pendingEventTail == null) + _pendingEventHead = evt; + else + _pendingEventTail.Next = evt; + _pendingEventTail = evt; + } + } + } + + protected virtual void ProcessEvent(NetEvent evt) + { + NetDebug.Write("[NM] Processing event: " + evt.Type); + bool emptyData = evt.DataReader.IsNull; + switch (evt.Type) + { + case NetEvent.EType.Connect: + _netEventListener.OnPeerConnected(evt.Peer); + break; + case NetEvent.EType.Disconnect: + var info = new DisconnectInfo + { + Reason = evt.DisconnectReason, + AdditionalData = evt.DataReader, + SocketErrorCode = evt.ErrorCode + }; + _netEventListener.OnPeerDisconnected(evt.Peer, info); + break; + case NetEvent.EType.Receive: + _netEventListener.OnNetworkReceive(evt.Peer, evt.DataReader, evt.DeliveryMethod); + break; + case NetEvent.EType.ReceiveUnconnected: + _netEventListener.OnNetworkReceiveUnconnected(evt.RemoteEndPoint, evt.DataReader, UnconnectedMessageType.BasicMessage); + break; + case NetEvent.EType.Broadcast: + _netEventListener.OnNetworkReceiveUnconnected(evt.RemoteEndPoint, evt.DataReader, UnconnectedMessageType.Broadcast); + break; + case NetEvent.EType.Error: + _netEventListener.OnNetworkError(evt.RemoteEndPoint, evt.ErrorCode); + break; + case NetEvent.EType.ConnectionLatencyUpdated: + _netEventListener.OnNetworkLatencyUpdate(evt.Peer, evt.Latency); + break; + case NetEvent.EType.ConnectionRequest: + _netEventListener.OnConnectionRequest(evt.ConnectionRequest); + break; + case NetEvent.EType.MessageDelivered: + _netEventListener.OnMessageDelivered(evt.Peer, evt.UserData); + break; + case NetEvent.EType.PeerAddressChanged: + _peersLock.EnterUpgradeableReadLock(); + IPEndPoint previousAddress = null; + if (ContainsPeer(evt.Peer)) + { + _peersLock.EnterWriteLock(); + RemovePeerFromSet(evt.Peer); + previousAddress = new IPEndPoint(evt.Peer.Address, evt.Peer.Port); + evt.Peer.FinishEndPointChange(evt.RemoteEndPoint); + AddPeerToSet(evt.Peer); + _peersLock.ExitWriteLock(); + } + _peersLock.ExitUpgradeableReadLock(); + if (previousAddress != null) + _netEventListener.OnPeerAddressChanged(evt.Peer, previousAddress); + break; + } + //Recycle if not message + if (emptyData) + RecycleEvent(evt); + else if (AutoRecycle) + evt.DataReader.RecycleInternal(); + } + + internal void RecycleEvent(NetEvent evt) + { + evt.Peer = null; + evt.ErrorCode = 0; + evt.RemoteEndPoint = null; + evt.ConnectionRequest = null; + lock (_eventLock) + { + evt.Next = _netEventPoolHead; + _netEventPoolHead = evt; + } + } + + protected virtual void ProcessNtpRequests(float elapsedMilliseconds) + { + //not used in lite version + } + + //Update function + private void UpdateLogic() + { + var peersToRemove = new List(); + var stopwatch = new Stopwatch(); + stopwatch.Start(); + + while (_isRunning) + { + try + { + ProcessDelayedPackets(); + float elapsed = (float)(stopwatch.ElapsedTicks / (double)Stopwatch.Frequency * 1000.0); + elapsed = elapsed <= 0.0f ? 0.001f : elapsed; + stopwatch.Restart(); + + for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) + { + if (netPeer.ConnectionState == ConnectionState.Disconnected && + netPeer.TimeSinceLastPacket > DisconnectTimeout) + { + peersToRemove.Add(netPeer); + } + else + { + netPeer.Update(elapsed); + } + } + + if (peersToRemove.Count > 0) + { + _peersLock.EnterWriteLock(); + for (int i = 0; i < peersToRemove.Count; i++) + RemovePeer(peersToRemove[i], false); + _peersLock.ExitWriteLock(); + peersToRemove.Clear(); + } + + ProcessNtpRequests(elapsed); + + int sleepTime = UpdateTime - (int)stopwatch.ElapsedMilliseconds; + if (sleepTime > 0) + _updateTriggerEvent.WaitOne(sleepTime); + } + catch (ThreadAbortException) + { + return; + } + catch (Exception e) + { + NetDebug.WriteError("[NM] LogicThread error: " + e); + } + } + stopwatch.Stop(); + } + + [Conditional("DEBUG"), Conditional("SIMULATE_NETWORK")] + private void ProcessDelayedPackets() + { + if (!SimulateLatency) + return; + +#if DEBUG || SIMULATE_NETWORK + var time = DateTime.UtcNow; + lock (_pingSimulationList) + { + for (int i = 0; i < _pingSimulationList.Count; i++) + { + var incomingData = _pingSimulationList[i]; + if (incomingData.TimeWhenGet <= time) + { + HandleMessageReceived(incomingData.Data, incomingData.EndPoint); + _pingSimulationList.RemoveAt(i); + i--; + } + } + } + + lock (_outboundSimulationList) + { + for (int i = 0; i < _outboundSimulationList.Count; i++) + { + var outboundData = _outboundSimulationList[i]; + if (outboundData.TimeWhenSend <= time) + { + // Send the delayed packet directly to socket layer bypassing simulation + SendRawCore(outboundData.Data, outboundData.Start, outboundData.Length, outboundData.EndPoint); + _outboundSimulationList.RemoveAt(i); + i--; + } + } + } +#endif + } + + + /// + /// Updates internal peer states, handles timeouts, processes NTP requests and sends buffered packets. + /// + /// Time passed since the last update frame. + /// + /// Must be called continuously from the main loop if was set to . + /// + public void ManualUpdate(float elapsedMilliseconds) + { + if (!_manualMode) + return; + + for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) + { + if (netPeer.ConnectionState == ConnectionState.Disconnected && netPeer.TimeSinceLastPacket > DisconnectTimeout) + { + RemovePeer(netPeer, false); + } + else + { + netPeer.Update(elapsedMilliseconds); + } + } + ProcessNtpRequests(elapsedMilliseconds); + } + + //connect to + protected virtual LiteNetPeer CreateOutgoingPeer(IPEndPoint remoteEndPoint, int id, byte connectNum, ReadOnlySpan connectData) => + new LiteNetPeer(this, remoteEndPoint, id, connectNum, connectData); + + //accept + protected virtual LiteNetPeer CreateIncomingPeer(LiteConnectionRequest request, int id) => + new LiteNetPeer(this, request, id); + + //reject + protected virtual LiteNetPeer CreateRejectPeer(IPEndPoint remoteEndPoint, int id) => + new LiteNetPeer(this, remoteEndPoint, id); + + protected virtual LiteConnectionRequest CreateConnectionRequest(IPEndPoint remoteEndPoint, NetConnectRequestPacket requestPacket) => + new LiteConnectionRequest(remoteEndPoint, requestPacket, this); + + internal LiteNetPeer OnConnectionSolved(LiteConnectionRequest request, ReadOnlySpan rejectData) + { + LiteNetPeer netPeer = null; + + if (request.Result == ConnectionRequestResult.RejectForce) + { + NetDebug.Write(NetLogLevel.Trace, "[NM] Peer connect reject force."); + if (!rejectData.IsEmpty) + { + var shutdownPacket = PoolGetWithProperty(PacketProperty.Disconnect, rejectData.Length); + shutdownPacket.ConnectionNumber = request.InternalPacket.ConnectionNumber; + FastBitConverter.GetBytes(shutdownPacket.RawData, 1, request.InternalPacket.ConnectionTime); + if (shutdownPacket.Size >= NetConstants.PossibleMtu[0]) + NetDebug.WriteError("[Peer] Disconnect additional data size more than MTU!"); + else + rejectData.CopyTo(shutdownPacket.RawData.AsSpan(9)); + SendRawAndRecycle(shutdownPacket, request.RemoteEndPoint); + } + lock (_requestsDict) + _requestsDict.Remove(request.RemoteEndPoint); + } + else lock (_requestsDict) + { + if (TryGetPeer(request.RemoteEndPoint, out netPeer)) + { + //already have peer + } + else if (request.Result == ConnectionRequestResult.Reject) + { + netPeer = CreateRejectPeer(request.RemoteEndPoint, GetNextPeerId()); + netPeer.Reject(request.InternalPacket, rejectData); + AddPeer(netPeer); + NetDebug.Write(NetLogLevel.Trace, "[NM] Peer connect reject."); + } + else //Accept + { + netPeer = CreateIncomingPeer(request, GetNextPeerId()); + AddPeer(netPeer); + CreateEvent(NetEvent.EType.Connect, netPeer); + NetDebug.Write(NetLogLevel.Trace, $"[NM] Received peer connection Id: {netPeer.ConnectTime}, EP: {netPeer}"); + } + _requestsDict.Remove(request.RemoteEndPoint); + } + + return netPeer; + } + + private int GetNextPeerId() => + _peerIds.TryDequeue(out int id) ? id : _lastPeerId++; + + private void ProcessConnectRequest( + IPEndPoint remoteEndPoint, + LiteNetPeer netPeer, + NetConnectRequestPacket connRequest) + { + //if we have peer + if (netPeer != null) + { + var processResult = netPeer.ProcessConnectRequest(connRequest); + NetDebug.Write($"ConnectRequest LastId: {netPeer.ConnectTime}, NewId: {connRequest.ConnectionTime}, EP: {remoteEndPoint}, Result: {processResult}"); + + switch (processResult) + { + case ConnectRequestResult.Reconnection: + DisconnectPeerForce(netPeer, DisconnectReason.Reconnect, 0, null); + RemovePeer(netPeer, true); + //go to new connection + break; + case ConnectRequestResult.NewConnection: + RemovePeer(netPeer, true); + //go to new connection + break; + case ConnectRequestResult.P2PLose: + DisconnectPeerForce(netPeer, DisconnectReason.PeerToPeerConnection, 0, null); + RemovePeer(netPeer, true); + //go to new connection + break; + default: + //no operations needed + return; + } + //ConnectRequestResult.NewConnection + //Set next connection number + if (processResult != ConnectRequestResult.P2PLose) + connRequest.ConnectionNumber = (byte)((netPeer.ConnectionNum + 1) % NetConstants.MaxConnectionNumber); + //To reconnect peer + } + else + { + NetDebug.Write($"ConnectRequest Id: {connRequest.ConnectionTime}, EP: {remoteEndPoint}"); + } + + LiteConnectionRequest req; + lock (_requestsDict) + { + if (_requestsDict.TryGetValue(remoteEndPoint, out req)) + { + req.UpdateRequest(connRequest); + return; + } + req = CreateConnectionRequest(remoteEndPoint, connRequest); + _requestsDict.Add(remoteEndPoint, req); + } + NetDebug.Write($"[NM] Creating request event: {connRequest.ConnectionTime}"); + CreateEvent(NetEvent.EType.ConnectionRequest, connectionRequest: req); + } + + private void OnMessageReceived(NetPacket packet, IPEndPoint remoteEndPoint) + { + if (packet.Size == 0) + { + PoolRecycle(packet); + return; + } + + _dropPacket = false; + HandleSimulateLatency(packet, remoteEndPoint); + HandleSimulatePacketLoss(); + if (_dropPacket) + { + return; + } + + // ProcessEvents + HandleMessageReceived(packet, remoteEndPoint); + } + + [Conditional("DEBUG"), Conditional("SIMULATE_NETWORK")] + private void HandleSimulateLatency(NetPacket packet, IPEndPoint remoteEndPoint) + { + if (!SimulateLatency) + { + return; + } +#if DEBUG || SIMULATE_NETWORK + int roundTripLatency = _randomGenerator.Next(SimulationMinLatency, SimulationMaxLatency); + int inboundLatency = roundTripLatency / 2; + if (inboundLatency > MinLatencyThreshold) + { + lock (_pingSimulationList) + { + _pingSimulationList.Add(new IncomingData + { + Data = packet, + EndPoint = remoteEndPoint, + TimeWhenGet = DateTime.UtcNow.AddMilliseconds(inboundLatency) + }); + } + // hold packet + _dropPacket = true; + } +#endif + } + + [Conditional("DEBUG"), Conditional("SIMULATE_NETWORK")] + private void HandleSimulatePacketLoss() + { + if (SimulatePacketLoss && _randomGenerator.NextDouble() * 100 < SimulationPacketLossChance) + { + _dropPacket = true; + } + } + +#if DEBUG || SIMULATE_NETWORK + private bool HandleSimulateOutboundLatency(byte[] data, int start, int length, IPEndPoint remoteEndPoint) + { + if (!SimulateLatency) + { + return false; + } + + int roundTripLatency = _randomGenerator.Next(SimulationMinLatency, SimulationMaxLatency); + int outboundLatency = roundTripLatency / 2; + if (outboundLatency > MinLatencyThreshold) + { + // Create a copy of the data to avoid issues with recycled packets + byte[] dataCopy = new byte[length]; + Array.Copy(data, start, dataCopy, 0, length); + + lock (_outboundSimulationList) + { + _outboundSimulationList.Add(new OutboundDelayedPacket + { + Data = dataCopy, + Start = 0, + Length = length, + EndPoint = remoteEndPoint, + TimeWhenSend = DateTime.UtcNow.AddMilliseconds(outboundLatency) + }); + } + + return true; + } + return false; + } +#endif + +#if DEBUG || SIMULATE_NETWORK + private bool HandleSimulateOutboundPacketLoss() => //Should Drop? + SimulatePacketLoss && _randomGenerator.NextDouble() * 100 < SimulationPacketLossChance; +#endif + + internal virtual bool CustomMessageHandle(NetPacket packet, IPEndPoint remoteEndPoint) => + false; + + private void HandleMessageReceived(NetPacket packet, IPEndPoint remoteEndPoint) + { + var originalPacketSize = packet.Size; + if (EnableStatistics) + { + Statistics.IncrementPacketsReceived(); + Statistics.AddBytesReceived(originalPacketSize); + } + + if (CustomMessageHandle(packet, remoteEndPoint)) + return; + + if (_extraPacketLayer != null) + { + _extraPacketLayer.ProcessInboundPacket(ref remoteEndPoint, ref packet.RawData, ref packet.Size); + if (packet.Size == 0) + return; + } + + if (!packet.Verify()) + { + NetDebug.WriteError("[NM] DataReceived: bad!"); + PoolRecycle(packet); + return; + } + + switch (packet.Property) + { + //special case connect request + case PacketProperty.ConnectRequest: + if (NetConnectRequestPacket.GetProtocolId(packet) != NetConstants.ProtocolId) + { + SendRawAndRecycle(PoolGetWithProperty(PacketProperty.InvalidProtocol), remoteEndPoint); + return; + } + break; + //unconnected messages + case PacketProperty.Broadcast: + if (!BroadcastReceiveEnabled) + return; + CreateEvent(NetEvent.EType.Broadcast, remoteEndPoint: remoteEndPoint, readerSource: packet); + return; + case PacketProperty.UnconnectedMessage: + if (!UnconnectedMessagesEnabled) + return; + CreateEvent(NetEvent.EType.ReceiveUnconnected, remoteEndPoint: remoteEndPoint, readerSource: packet); + return; + case PacketProperty.NatMessage: + if (NatPunchEnabled) + NatPunchModule.ProcessMessage(remoteEndPoint, packet); + return; + } + + //Check normal packets + bool peerFound = remoteEndPoint is LiteNetPeer netPeer || TryGetPeer(remoteEndPoint, out netPeer); + + if (peerFound && EnableStatistics) + { + netPeer.Statistics.IncrementPacketsReceived(); + netPeer.Statistics.AddBytesReceived(originalPacketSize); + } + + switch (packet.Property) + { + case PacketProperty.ConnectRequest: + var connRequest = NetConnectRequestPacket.FromData(packet); + if (connRequest != null) + ProcessConnectRequest(remoteEndPoint, netPeer, connRequest); + break; + case PacketProperty.PeerNotFound: + if (peerFound) //local + { + if (netPeer.ConnectionState != ConnectionState.Connected) + return; + if (packet.Size == 1) + { + //first reply + //send NetworkChanged packet + netPeer.ResetMtu(); + SendRaw(NetConnectAcceptPacket.MakeNetworkChanged(netPeer), remoteEndPoint); + NetDebug.Write($"PeerNotFound sending connection info: {remoteEndPoint}"); + } + else if (packet.Size == 2 && packet.RawData[1] == 1) + { + //second reply + DisconnectPeerForce(netPeer, DisconnectReason.PeerNotFound, 0, null); + } + } + else if (packet.Size > 1) //remote + { + //check if this is old peer + bool isOldPeer = false; + + if (AllowPeerAddressChange) + { + NetDebug.Write($"[NM] Looks like address change: {packet.Size}"); + var remoteData = NetConnectAcceptPacket.FromData(packet); + if (remoteData != null && + remoteData.PeerNetworkChanged && + remoteData.PeerId < _peersArray.Length) + { + _peersLock.EnterUpgradeableReadLock(); + var peer = _peersArray[remoteData.PeerId]; + _peersLock.ExitUpgradeableReadLock(); + if (peer != null && + peer.ConnectTime == remoteData.ConnectionTime && + peer.ConnectionNum == remoteData.ConnectionNumber) + { + if (peer.ConnectionState == ConnectionState.Connected) + { + peer.InitiateEndPointChange(); + CreateEvent(NetEvent.EType.PeerAddressChanged, peer, remoteEndPoint); + NetDebug.Write("[NM] PeerNotFound change address of remote peer"); + } + isOldPeer = true; + } + } + } + + PoolRecycle(packet); + + //else peer really not found + if (!isOldPeer) + { + var secondResponse = PoolGetWithProperty(PacketProperty.PeerNotFound, 1); + secondResponse.RawData[1] = 1; + SendRawAndRecycle(secondResponse, remoteEndPoint); + } + } + break; + case PacketProperty.InvalidProtocol: + if (peerFound && netPeer.ConnectionState == ConnectionState.Outgoing) + DisconnectPeerForce(netPeer, DisconnectReason.InvalidProtocol, 0, null); + break; + case PacketProperty.Disconnect: + if (peerFound) + { + var disconnectResult = netPeer.ProcessDisconnect(packet); + if (disconnectResult == DisconnectResult.None) + { + PoolRecycle(packet); + return; + } + DisconnectPeerForce( + netPeer, + disconnectResult == DisconnectResult.Disconnect + ? DisconnectReason.RemoteConnectionClose + : DisconnectReason.ConnectionRejected, + 0, packet); + } + else + { + PoolRecycle(packet); + } + //Send shutdown + SendRawAndRecycle(PoolGetWithProperty(PacketProperty.ShutdownOk), remoteEndPoint); + break; + case PacketProperty.ConnectAccept: + if (!peerFound) + return; + var connAccept = NetConnectAcceptPacket.FromData(packet); + if (connAccept != null && netPeer.ProcessConnectAccept(connAccept)) + CreateEvent(NetEvent.EType.Connect, netPeer); + break; + default: + if (peerFound) + netPeer.ProcessPacket(packet); + else + SendRawAndRecycle(PoolGetWithProperty(PacketProperty.PeerNotFound), remoteEndPoint); + break; + } + } + + internal void CreateReceiveEvent(NetPacket packet, DeliveryMethod method, byte channelNumber, int headerSize, LiteNetPeer fromPeer) + { + NetEvent evt; + + if (UnsyncedEvents || UnsyncedReceiveEvent || _manualMode) + { + lock (_eventLock) + { + evt = _netEventPoolHead; + if (evt == null) + evt = new NetEvent(this); + else + _netEventPoolHead = evt.Next; + } + evt.Next = null; + evt.Type = NetEvent.EType.Receive; + evt.DataReader.SetSource(packet, headerSize); + evt.Peer = fromPeer; + evt.DeliveryMethod = method; + evt.ChannelNumber = channelNumber; + ProcessEvent(evt); + } + else + { + lock (_eventLock) + { + evt = _netEventPoolHead; + if (evt == null) + evt = new NetEvent(this); + else + _netEventPoolHead = evt.Next; + + evt.Next = null; + evt.Type = NetEvent.EType.Receive; + evt.DataReader.SetSource(packet, headerSize); + evt.Peer = fromPeer; + evt.DeliveryMethod = method; + evt.ChannelNumber = channelNumber; + + if (_pendingEventTail == null) + _pendingEventHead = evt; + else + _pendingEventTail.Next = evt; + _pendingEventTail = evt; + } + } + } + + /// + /// Send data to all connected peers (channel - 0) + /// + /// DataWriter with data + /// Send options (reliable, unreliable, etc.) + public void SendToAll(NetDataWriter writer, DeliveryMethod options) => + SendToAll(writer.Data, 0, writer.Length, options); + + /// + /// Send data to all connected peers (channel - 0) + /// + /// Data + /// Send options (reliable, unreliable, etc.) + public void SendToAll(byte[] data, DeliveryMethod options) => + SendToAll(data, 0, data.Length, options); + + /// + /// Send data to all connected peers (channel - 0) + /// + /// Data + /// Start of data + /// Length of data + /// Send options (reliable, unreliable, etc.) + public void SendToAll(byte[] data, int start, int length, DeliveryMethod options) + { + try + { + _peersLock.EnterReadLock(); + for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) + netPeer.Send(data, start, length, options); + } + finally + { + _peersLock.ExitReadLock(); + } + } + + /// + /// Send data to all connected peers (channel - 0) + /// + /// DataWriter with data + /// Send options (reliable, unreliable, etc.) + /// Excluded peer + public void SendToAll(NetDataWriter writer, DeliveryMethod options, LiteNetPeer excludePeer) => + SendToAll(writer.Data, 0, writer.Length, options, excludePeer); + + /// + /// Send data to all connected peers (channel - 0) + /// + /// Data + /// Send options (reliable, unreliable, etc.) + /// Excluded peer + public void SendToAll(byte[] data, DeliveryMethod options, LiteNetPeer excludePeer) => + SendToAll(data, 0, data.Length, options, excludePeer); + + /// + /// Send data to all connected peers + /// + /// Data + /// Start of data + /// Length of data + /// Send options (reliable, unreliable, etc.) + /// Excluded peer + public void SendToAll(byte[] data, int start, int length, DeliveryMethod options, LiteNetPeer excludePeer) + { + try + { + _peersLock.EnterReadLock(); + for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) + { + if (netPeer != excludePeer) + netPeer.Send(data, start, length, options); + } + } + finally + { + _peersLock.ExitReadLock(); + } + } + + /// + /// Send data to all connected peers (channel - 0) + /// + /// Data + /// Send options (reliable, unreliable, etc.) + public void SendToAll(ReadOnlySpan data, DeliveryMethod options) => + SendToAll(data, options, null); + + /// + /// Send data to all connected peers (channel - 0) + /// + /// Data + /// Send options (reliable, unreliable, etc.) + /// Excluded peer + public void SendToAll(ReadOnlySpan data, DeliveryMethod options, LiteNetPeer excludePeer) + { + try + { + _peersLock.EnterReadLock(); + for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) + { + if (netPeer != excludePeer) + netPeer.Send(data, options); + } + } + finally + { + _peersLock.ExitReadLock(); + } + } + + /// + /// Send message without connection + /// + /// Raw data + /// Packet destination + /// Operation result + public bool SendUnconnectedMessage(ReadOnlySpan message, IPEndPoint remoteEndPoint) + { + int headerSize = NetPacket.GetHeaderSize(PacketProperty.UnconnectedMessage); + var packet = PoolGetPacket(message.Length + headerSize); + packet.Property = PacketProperty.UnconnectedMessage; + message.CopyTo(new Span(packet.RawData, headerSize, message.Length)); + return SendRawAndRecycle(packet, remoteEndPoint) > 0; + } + + /// + /// Start logic thread and listening on available port + /// + public bool Start() => + Start(0); + + /// + /// Start logic thread and listening on selected port + /// + /// bind to specific ipv4 address + /// bind to specific ipv6 address + /// port to listen + public bool Start(IPAddress addressIPv4, IPAddress addressIPv6, int port) => + Start(addressIPv4, addressIPv6, port, false); + + /// + /// Start logic thread and listening on selected port + /// + /// bind to specific ipv4 address + /// bind to specific ipv6 address + /// port to listen + public bool Start(string addressIPv4, string addressIPv6, int port) + { + IPAddress ipv4 = NetUtils.ResolveAddress(addressIPv4); + IPAddress ipv6 = NetUtils.ResolveAddress(addressIPv6); + return Start(ipv4, ipv6, port); + } + + /// + /// Start logic thread and listening on selected port + /// + /// port to listen + public bool Start(int port) => + Start(IPAddress.Any, IPAddress.IPv6Any, port); + + /// + /// Start in manual mode and listening on selected port + /// In this mode you should use ManualReceive (without PollEvents) for receive packets + /// and ManualUpdate(...) for update and send packets + /// This mode useful mostly for single-threaded servers + /// + /// bind to specific ipv4 address + /// bind to specific ipv6 address + /// port to listen + public bool StartInManualMode(IPAddress addressIPv4, IPAddress addressIPv6, int port) => + Start(addressIPv4, addressIPv6, port, true); + + /// + /// Start in manual mode and listening on selected port + /// In this mode you should use ManualReceive (without PollEvents) for receive packets + /// and ManualUpdate(...) for update and send packets + /// This mode useful mostly for single-threaded servers + /// + /// bind to specific ipv4 address + /// bind to specific ipv6 address + /// port to listen + public bool StartInManualMode(string addressIPv4, string addressIPv6, int port) + { + IPAddress ipv4 = NetUtils.ResolveAddress(addressIPv4); + IPAddress ipv6 = NetUtils.ResolveAddress(addressIPv6); + return StartInManualMode(ipv4, ipv6, port); + } + + /// + /// Start in manual mode and listening on selected port + /// In this mode you should use ManualReceive (without PollEvents) for receive packets + /// and ManualUpdate(...) for update and send packets + /// This mode useful mostly for single-threaded servers + /// + /// port to listen + public bool StartInManualMode(int port) => + StartInManualMode(IPAddress.Any, IPAddress.IPv6Any, port); + + /// + /// Send message without connection + /// + /// Raw data + /// Packet destination + /// Operation result + public bool SendUnconnectedMessage(byte[] message, IPEndPoint remoteEndPoint) => + SendUnconnectedMessage(message, 0, message.Length, remoteEndPoint); + + /// + /// Send message without connection. WARNING This method allocates a new IPEndPoint object and + /// synchronously makes a DNS request. If you're calling this method every frame it will be + /// much faster to just cache the IPEndPoint. + /// + /// Data serializer + /// Packet destination IP or hostname + /// Packet destination port + /// Operation result + public bool SendUnconnectedMessage(NetDataWriter writer, string address, int port) => + SendUnconnectedMessage(writer.Data, 0, writer.Length, NetUtils.MakeEndPoint(address, port)); + + /// + /// Send message without connection + /// + /// Data serializer + /// Packet destination + /// Operation result + public bool SendUnconnectedMessage(NetDataWriter writer, IPEndPoint remoteEndPoint) => + SendUnconnectedMessage(writer.Data, 0, writer.Length, remoteEndPoint); + + /// + /// Send message without connection + /// + /// Raw data + /// data start + /// data length + /// Packet destination + /// Operation result + public bool SendUnconnectedMessage(byte[] message, int start, int length, IPEndPoint remoteEndPoint) + { + //No need for CRC here, SendRaw does that + NetPacket packet = PoolGetWithData(PacketProperty.UnconnectedMessage, message, start, length); + return SendRawAndRecycle(packet, remoteEndPoint) > 0; + } + + /// + /// Triggers update and send logic immediately (works asynchronously) + /// + public void TriggerUpdate() => + _updateTriggerEvent.Set(); + + /// + /// Synchronizes arrived events from the background thread to your main-thread/ + /// + /// + /// Must be called continuously from the main loop if was set to to receive data from the UDP sockets. + /// + public void PollEvents() + { + if (_manualMode) + { + if (_udpSocketv4 != null) + ManualReceive(_udpSocketv4, _bufferEndPointv4); + if (_udpSocketv6 != null && _udpSocketv6 != _udpSocketv4) + ManualReceive(_udpSocketv6, _bufferEndPointv6); + ProcessDelayedPackets(); + return; + } + if (UnsyncedEvents) + return; + NetEvent pendingEvent; + lock (_eventLock) + { + pendingEvent = _pendingEventHead; + _pendingEventHead = null; + _pendingEventTail = null; + } + + while (pendingEvent != null) + { + var next = pendingEvent.Next; + ProcessEvent(pendingEvent); + pendingEvent = next; + } + } + + /// + /// Connect to remote host + /// + /// Server IP or hostname + /// Server Port + /// Connection key + /// New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting + /// Manager is not running. Call + public LiteNetPeer Connect(string address, int port, string key) => + Connect(address, port, NetDataWriter.FromString(key)); + + /// + /// Connect to remote host + /// + /// Server IP or hostname + /// Server Port + /// Additional data for remote peer + /// New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting + /// Manager is not running. Call + public LiteNetPeer Connect(string address, int port, NetDataWriter connectionData) + { + IPEndPoint ep; + try + { + ep = NetUtils.MakeEndPoint(address, port); + } + catch + { + CreateEvent(NetEvent.EType.Disconnect, disconnectReason: DisconnectReason.UnknownHost); + return null; + } + return Connect(ep, connectionData); + } + + /// + /// Connect to remote host + /// + /// Server end point (ip and port) + /// Connection key + /// New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting + /// Manager is not running. Call + public LiteNetPeer Connect(IPEndPoint target, string key) => + Connect(target, NetDataWriter.FromString(key)); + + /// + /// Connect to remote host + /// + /// Server end point (ip and port) + /// Additional data for remote peer + /// New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting + /// Manager is not running. Call + public LiteNetPeer Connect(IPEndPoint target, NetDataWriter connectionData) + { + if (!_isRunning) + throw new InvalidOperationException("Client is not running"); + + lock (_requestsDict) + { + if (_requestsDict.ContainsKey(target)) + return null; + + byte connectionNumber = 0; + if (TryGetPeer(target, out var peer)) + { + switch (peer.ConnectionState) + { + //just return already connected peer + case ConnectionState.Connected: + case ConnectionState.Outgoing: + return peer; + } + //else reconnect + connectionNumber = (byte)((peer.ConnectionNum + 1) % NetConstants.MaxConnectionNumber); + RemovePeer(peer, true); + } + + //Create reliable connection + //And send connection request + peer = CreateOutgoingPeer(target, GetNextPeerId(), connectionNumber, connectionData.AsReadOnlySpan()); + AddPeer(peer); + return peer; + } + } + + /// + /// Connect to remote host + /// + /// Server end point (ip and port) + /// Additional data for remote peer + /// New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting + /// Manager is not running. Call + public LiteNetPeer Connect(IPEndPoint target, ReadOnlySpan connectionData) + { + if (!_isRunning) + throw new InvalidOperationException("Client is not running"); + + lock (_requestsDict) + { + if (_requestsDict.ContainsKey(target)) + return null; + + byte connectionNumber = 0; + if (TryGetPeer(target, out var peer)) + { + switch (peer.ConnectionState) + { + //just return already connected peer + case ConnectionState.Connected: + case ConnectionState.Outgoing: + return peer; + } + //else reconnect + connectionNumber = (byte)((peer.ConnectionNum + 1) % NetConstants.MaxConnectionNumber); + RemovePeer(peer, true); + } + + //Create reliable connection + //And send connection request + peer = CreateOutgoingPeer(target, GetNextPeerId(), connectionNumber, connectionData); + AddPeer(peer); + return peer; + } + } + + /// + /// Force closes connection and stop all threads. + /// + public void Stop() => + Stop(true); + + /// + /// Force closes connection and stop all threads. + /// + /// Send disconnect messages + public void Stop(bool sendDisconnectMessages) + { + if (!_isRunning) + return; + + NetDebug.Write("[NM] Stop"); + + //Little hack so shutdown message will not be queued by Outbound latency simulation + //In real world packet will be delayed by routers not by application(that closes) logic + var simLat = SimulateLatency; + SimulateLatency = false; + + //Send last disconnect + for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) + netPeer.Shutdown(ReadOnlySpan.Empty, !sendDisconnectMessages); + + //Stop + CloseSocket(); + + //Return setting + SimulateLatency = simLat; + +#if UNITY_SOCKET_FIX + if (_useSocketFix) + { + _pausedSocketFix.Deinitialize(); + _pausedSocketFix = null; + } +#endif + + _updateTriggerEvent.Set(); + if (!_manualMode) + { + _logicThread.Join(); + _logicThread = null; + } + + //clear peers + ClearPeerSet(); + _peerIds = new ConcurrentQueue(); + _lastPeerId = 0; + +#if DEBUG || SIMULATE_NETWORK + lock (_pingSimulationList) + _pingSimulationList.Clear(); + lock (_outboundSimulationList) + _outboundSimulationList.Clear(); +#endif + + _connectedPeersCount = 0; + _pendingEventHead = null; + _pendingEventTail = null; + } + + /// + /// Return peers count with connection state + /// + /// peer connection state (you can use as bit flags) + /// peers count + public int GetPeersCount(ConnectionState peerState) + { + int count = 0; + _peersLock.EnterReadLock(); + for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) + { + if ((netPeer.ConnectionState & peerState) != 0) + count++; + } + _peersLock.ExitReadLock(); + return count; + } + + /// + /// Get copy of peers (without allocations) + /// + /// List that will contain result + /// State of peers + public void GetPeers(List peers, ConnectionState peerState) + { + peers.Clear(); + _peersLock.EnterReadLock(); + for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) + { + if ((netPeer.ConnectionState & peerState) != 0) + peers.Add(netPeer); + } + _peersLock.ExitReadLock(); + } + + /// + /// Get copy of connected peers (without allocations) + /// + /// List that will contain result + public void GetConnectedPeers(List peers) => + GetPeers(peers, ConnectionState.Connected); + + /// + /// Disconnect all peers without any additional data + /// + public void DisconnectAll() => + DisconnectAll(ReadOnlySpan.Empty); + + /// + /// Disconnect all peers with shutdown message + /// + /// Data to send (must be less or equal MTU) + /// Data start + /// Data count + public void DisconnectAll(byte[] data, int start, int count) => + DisconnectAll(new ReadOnlySpan(data, start, count)); + + /// + /// Disconnect all peers with shutdown message + /// + /// Data to send (must be less or equal MTU) + public void DisconnectAll(ReadOnlySpan data) + { + //Send disconnect packets + _peersLock.EnterReadLock(); + for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) + { + DisconnectPeer( + netPeer, + DisconnectReason.DisconnectPeerCalled, + 0, + false, + data, + null); + } + _peersLock.ExitReadLock(); + } + + /// + /// Immediately disconnect peer from server without additional data + /// + /// peer to disconnect + public void DisconnectPeerForce(LiteNetPeer peer) => + DisconnectPeerForce(peer, DisconnectReason.DisconnectPeerCalled, 0, null); + + /// + /// Disconnect peer from server + /// + /// peer to disconnect + public void DisconnectPeer(LiteNetPeer peer) => + DisconnectPeer(peer, ReadOnlySpan.Empty); + + /// + /// Disconnect peer from server and send additional data (Size must be less or equal MTU - 8) + /// + /// peer to disconnect + /// additional data + public void DisconnectPeer(LiteNetPeer peer, byte[] data) => + DisconnectPeer(peer, new ReadOnlySpan(data)); + + /// + /// Disconnect peer from server and send additional data (Size must be less or equal MTU - 8) + /// + /// peer to disconnect + /// additional data + public void DisconnectPeer(LiteNetPeer peer, NetDataWriter writer) => + DisconnectPeer(peer, writer.AsReadOnlySpan()); + + /// + /// Disconnect peer from server and send additional data (Size must be less or equal MTU - 8) + /// + /// peer to disconnect + /// additional data + /// data start + /// data length + public void DisconnectPeer(LiteNetPeer peer, byte[] data, int start, int count) => + DisconnectPeer(peer, new ReadOnlySpan(data, start, count)); + + /// + /// Disconnect peer from server and send additional data (Size must be less or equal MTU - 8) + /// + /// peer to disconnect + /// additional data + public void DisconnectPeer(LiteNetPeer peer, ReadOnlySpan data) + { + DisconnectPeer( + peer, + DisconnectReason.DisconnectPeerCalled, + 0, + false, + data, + null); + } + + public NetPeerEnumerator GetEnumerator() => + new NetPeerEnumerator(_headPeer); + + IEnumerator IEnumerable.GetEnumerator() => + new NetPeerEnumerator(_headPeer); + + IEnumerator IEnumerable.GetEnumerator() => + new NetPeerEnumerator(_headPeer); + } +} diff --git a/LiteNetLib/LiteNetPeer.cs b/LiteNetLib/LiteNetPeer.cs new file mode 100644 index 00000000..f36796c3 --- /dev/null +++ b/LiteNetLib/LiteNetPeer.cs @@ -0,0 +1,1357 @@ +#if DEBUG +#define STATS_ENABLED +#endif +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net; +using System.Runtime.CompilerServices; +using System.Threading; +using LiteNetLib.Utils; + +namespace LiteNetLib +{ + /// + /// Peer connection state + /// + [Flags] + public enum ConnectionState : byte + { + Outgoing = 1 << 1, + Connected = 1 << 2, + ShutdownRequested = 1 << 3, + Disconnected = 1 << 4, + EndPointChange = 1 << 5, + Any = Outgoing | Connected | ShutdownRequested | EndPointChange + } + + internal enum ConnectRequestResult + { + None, + P2PLose, //when peer connecting + Reconnection, //when peer was connected + NewConnection //when peer was disconnected + } + + internal enum DisconnectResult + { + None, + Reject, + Disconnect + } + + internal enum ShutdownResult + { + None, + Success, + WasConnected + } + + /// + /// Network peer. Main purpose is sending messages to specific peer. + /// + public class LiteNetPeer : IPEndPoint + { + //Ping and RTT + private int _rtt; + private int _avgRtt; + private int _rttCount; + private float _resendDelay = 27.0f; + private float _pingSendTimer; + private float _rttResetTimer; + private readonly Stopwatch _pingTimer = new Stopwatch(); + private float _timeSinceLastPacket; + private long _remoteDelta; + + //Common + private readonly object _shutdownLock = new object(); + + internal volatile LiteNetPeer NextPeer; + internal LiteNetPeer PrevPeer; + + internal byte ConnectionNum + { + get => _connectNum; + private set + { + _connectNum = value; + _mergeData.ConnectionNumber = value; + _pingPacket.ConnectionNumber = value; + _pongPacket.ConnectionNumber = value; + } + } + + //Channels + private NetPacket[] _unreliableSecondQueue; + private NetPacket[] _unreliableChannel; + private int _unreliablePendingCount; + private readonly object _unreliableChannelLock = new object(); + + //MTU + private int _mtu; + private int _mtuIdx; + private bool _finishMtu; + private float _mtuCheckTimer; + private int _mtuCheckAttempts; + private const int MtuCheckDelay = 1000; + private const int MaxMtuCheckAttempts = 4; + private readonly object _mtuMutex = new object(); + + //Fragment + private sealed class IncomingFragments + { + public readonly NetPacket[] Fragments; + public readonly ushort TotalFragments; + public readonly byte ChannelId; + + public int ReceivedCount; + public int TotalSize; + + public IncomingFragments(ushort totalFragments, byte channelId) + { + TotalFragments = totalFragments; + ChannelId = channelId; + Fragments = new NetPacket[totalFragments]; + } + + public void RecycleAll(LiteNetManager netManager) + { + for (int i = 0; i < Fragments.Length; i++) + { + NetPacket packet = Fragments[i]; + if (packet != null) + { + netManager.PoolRecycle(packet); + Fragments[i] = null; + } + } + } + } + private int _fragmentId; + private readonly Dictionary _holdedFragments; + private readonly Dictionary _deliveredFragments; + + //Merging + private readonly NetPacket _mergeData; + private int _mergePos; + private int _mergeCount; + + //Connection + private int _connectAttempts; + private float _connectTimer; + private long _connectTime; + private byte _connectNum; + private ConnectionState _connectionState; + private NetPacket _shutdownPacket; + private const int ShutdownDelay = 300; + private float _shutdownTimer; + private readonly NetPacket _pingPacket; + private readonly NetPacket _pongPacket; + private readonly NetPacket _connectRequestPacket; + private readonly NetPacket _connectAcceptPacket; + + /// + /// Peer parent NetManager + /// + public readonly LiteNetManager NetManager; + + /// + /// Current connection state + /// + public ConnectionState ConnectionState => _connectionState; + + /// + /// Connection time for internal purposes + /// + internal long ConnectTime => _connectTime; + + /// + /// Peer id can be used as key in your dictionary of peers + /// + public readonly int Id; + + /// + /// Id assigned from server + /// + public int RemoteId { get; private set; } + + /// + /// Current one-way ping (RTT/2) in milliseconds + /// + public int Ping => _avgRtt/2; + + /// + /// Round trip time in milliseconds + /// + public int RoundTripTime => _avgRtt; + + /// + /// Current MTU - Maximum Transfer Unit ( maximum udp packet size without fragmentation ) + /// + public int Mtu => _mtu; + + /// + /// Delta with remote time in ticks (not accurate) + /// positive - remote time > our time + /// + public long RemoteTimeDelta => _remoteDelta; + + /// + /// Remote UTC time (not accurate) + /// + public DateTime RemoteUtcTime => new DateTime(DateTime.UtcNow.Ticks + _remoteDelta); + + /// + /// Time since last packet received (including internal library packets) in milliseconds + /// + public float TimeSinceLastPacket => _timeSinceLastPacket; + + /// + /// Fixed part of the resend delay + /// + public float ResendFixedDelay = 25.0f; + + /// + /// Multiplication factor of Rtt in the resend delay calculation + /// + public float ResendRttMultiplier = 2.1f; + + internal float ResendDelay => _resendDelay; + + /// + /// Application defined object containing data about the connection + /// + public object Tag; + + /// + /// Statistics of peer connection + /// + public readonly NetStatistics Statistics; + + private SocketAddress _cachedSocketAddr; + private int _cachedHashCode; + private ReliableChannel _reliableChannel; + private ReliableChannel _reliableUnorderedChannel; + private SequencedChannel _sequencedChannel; + + internal byte[] NativeAddress; + + protected virtual int ChannelsCount => 1; + + /// + /// IPEndPoint serialize + /// + /// SocketAddress + public override SocketAddress Serialize() => + _cachedSocketAddr; + + public override int GetHashCode() => + //uses SocketAddress hash in NET8 and IPEndPoint hash for NativeSockets and previous NET versions + _cachedHashCode; + + //incoming connection constructor + internal LiteNetPeer(LiteNetManager netManager, IPEndPoint remoteEndPoint, int id) : base(remoteEndPoint.Address, remoteEndPoint.Port) + { + Id = id; + Statistics = new NetStatistics(); + NetManager = netManager; + + _cachedSocketAddr = base.Serialize(); + if (NetManager.UseNativeSockets) + { + NativeAddress = new byte[_cachedSocketAddr.Size]; + for (int i = 0; i < _cachedSocketAddr.Size; i++) + NativeAddress[i] = _cachedSocketAddr[i]; + } +#if NET8_0_OR_GREATER + _cachedHashCode = NetManager.UseNativeSockets ? base.GetHashCode() : _cachedSocketAddr.GetHashCode(); +#else + _cachedHashCode = base.GetHashCode(); +#endif + + ResetMtu(); + + _connectionState = ConnectionState.Connected; + _mergeData = new NetPacket(PacketProperty.Merged, NetConstants.MaxPacketSize); + _pongPacket = new NetPacket(PacketProperty.Pong, 0); + _pingPacket = new NetPacket(PacketProperty.Ping, 0) {Sequence = 1}; + + _unreliableSecondQueue = new NetPacket[8]; + _unreliableChannel = new NetPacket[8]; + _holdedFragments = new Dictionary(); + _deliveredFragments = new Dictionary(); + } + + internal void InitiateEndPointChange() + { + ResetMtu(); + _connectionState = ConnectionState.EndPointChange; + } + + internal void FinishEndPointChange(IPEndPoint newEndPoint) + { + if (_connectionState != ConnectionState.EndPointChange) + return; + _connectionState = ConnectionState.Connected; + + Address = newEndPoint.Address; + Port = newEndPoint.Port; + + _cachedSocketAddr = base.Serialize(); + if (NetManager.UseNativeSockets) + { + NativeAddress = new byte[_cachedSocketAddr.Size]; + for (int i = 0; i < _cachedSocketAddr.Size; i++) + NativeAddress[i] = _cachedSocketAddr[i]; + } +#if NET8_0_OR_GREATER + _cachedHashCode = NetManager.UseNativeSockets ? base.GetHashCode() : _cachedSocketAddr.GetHashCode(); +#else + _cachedHashCode = base.GetHashCode(); +#endif + } + + internal void ResetMtu() + { + //finish if discovery disabled + _finishMtu = !NetManager.MtuDiscovery; + if (NetManager.MtuOverride > 0) + OverrideMtu(NetManager.MtuOverride); + else + SetMtu(0); + } + + private void SetMtu(int mtuIdx) + { + _mtuIdx = mtuIdx; + _mtu = NetConstants.PossibleMtu[mtuIdx] - NetManager.ExtraPacketSizeForLayer; + } + + private void OverrideMtu(int mtuValue) + { + _mtu = mtuValue; + _finishMtu = true; + } + + /// + /// Returns packets count in queue for reliable channel 0 + /// + /// type of channel ReliableOrdered or ReliableUnordered + /// packets count in channel queue + public int GetPacketsCountInReliableQueue(bool ordered) => + (ordered ? _reliableChannel : _reliableUnorderedChannel)?.PacketsInQueue ?? 0; + + /// + /// Create temporary packet (maximum size MTU - headerSize) to send later without additional copies + /// + /// Delivery method (reliable, unreliable, etc.) + /// PooledPacket that you can use to write data starting from UserDataOffset + public PooledPacket CreatePacketFromPool(DeliveryMethod deliveryMethod) + { + //multithreaded variable + int mtu = _mtu; + var packet = NetManager.PoolGetPacket(mtu); + if (deliveryMethod == DeliveryMethod.Unreliable) + { + packet.Property = PacketProperty.Unreliable; + return new PooledPacket(packet, mtu, 0); + } + else + { + packet.Property = PacketProperty.Channeled; + return new PooledPacket(packet, mtu, (byte)deliveryMethod); + } + } + + /// + /// Sends pooled packet without data copy + /// + /// packet to send + /// size of user data you want to send + public void SendPooledPacket(PooledPacket packet, int userDataSize) + { + if (_connectionState != ConnectionState.Connected) + return; + packet._packet.Size = packet.UserDataOffset + userDataSize; + if (packet._packet.Property == PacketProperty.Channeled) + { + CreateChannel(packet._channelNumber).AddToQueue(packet._packet); + } + else + { + lock (_unreliableChannelLock) + { + if (_unreliablePendingCount == _unreliableChannel.Length) + Array.Resize(ref _unreliableChannel, _unreliablePendingCount*2); + _unreliableChannel[_unreliablePendingCount++] = packet._packet; + } + } + } + + internal virtual BaseChannel CreateChannel(byte channelNumber) + { + switch ((DeliveryMethod)channelNumber) + { + case DeliveryMethod.ReliableOrdered: + case DeliveryMethod.ReliableSequenced: + return _reliableChannel ??= new ReliableChannel(this, true, (int)DeliveryMethod.ReliableOrdered); + + case DeliveryMethod.ReliableUnordered: + return _reliableUnorderedChannel ??= new ReliableChannel(this, false, channelNumber); + + case DeliveryMethod.Sequenced: + return _sequencedChannel ??= new SequencedChannel(this, true, channelNumber); + + default: + throw new Exception("Invalid channel type"); + } + } + + //"Connect to" constructor + internal LiteNetPeer(LiteNetManager netManager, IPEndPoint remoteEndPoint, int id, byte connectNum, ReadOnlySpan connectData) + : this(netManager, remoteEndPoint, id) + { + _connectTime = DateTime.UtcNow.Ticks; + _connectionState = ConnectionState.Outgoing; + ConnectionNum = connectNum; + + //Make initial packet + _connectRequestPacket = NetConnectRequestPacket.Make(connectData, remoteEndPoint.Serialize(), _connectTime, id); + _connectRequestPacket.ConnectionNumber = connectNum; + + //Send request + NetManager.SendRaw(_connectRequestPacket, this); + + NetDebug.Write(NetLogLevel.Trace, $"[CC] ConnectId: {_connectTime}, ConnectNum: {connectNum}"); + } + + //"Accept" incoming constructor + internal LiteNetPeer(LiteNetManager netManager, LiteConnectionRequest request, int id) + : this(netManager, request.RemoteEndPoint, id) + { + _connectTime = request.InternalPacket.ConnectionTime; + ConnectionNum = request.InternalPacket.ConnectionNumber; + RemoteId = request.InternalPacket.PeerId; + + //Make initial packet + _connectAcceptPacket = NetConnectAcceptPacket.Make(_connectTime, ConnectionNum, id); + + //Make Connected + _connectionState = ConnectionState.Connected; + + //Send + NetManager.SendRaw(_connectAcceptPacket, this); + + NetDebug.Write(NetLogLevel.Trace, $"[CC] ConnectId: {_connectTime}"); + } + + //Reject + internal void Reject(NetConnectRequestPacket requestData, ReadOnlySpan data) + { + _connectTime = requestData.ConnectionTime; + _connectNum = requestData.ConnectionNumber; + Shutdown(data, false); + } + + internal bool ProcessConnectAccept(NetConnectAcceptPacket packet) + { + if (_connectionState != ConnectionState.Outgoing) + return false; + + //check connection id + if (packet.ConnectionTime != _connectTime) + { + NetDebug.Write(NetLogLevel.Trace, $"[NC] Invalid connectId: {packet.ConnectionTime} != our({_connectTime})"); + return false; + } + //check connect num + ConnectionNum = packet.ConnectionNumber; + RemoteId = packet.PeerId; + + NetDebug.Write(NetLogLevel.Trace, "[NC] Received connection accept"); + Interlocked.Exchange(ref _timeSinceLastPacket, 0); + _connectionState = ConnectionState.Connected; + return true; + } + + /// + /// Gets maximum size of packet that will be not fragmented. + /// + /// Type of packet that you want send + /// size in bytes + public int GetMaxSinglePacketSize(DeliveryMethod options) => + _mtu - NetPacket.GetHeaderSize(options == DeliveryMethod.Unreliable ? PacketProperty.Unreliable : PacketProperty.Channeled); + + /// + /// Send data to peer with delivery event called + /// + /// Data + /// Delivery method (reliable, unreliable, etc.) + /// User data that will be received in DeliveryEvent + /// + /// If you trying to send unreliable packet type + /// + public void SendWithDeliveryEvent(byte[] data, DeliveryMethod deliveryMethod, object userData) + { + if (deliveryMethod != DeliveryMethod.ReliableOrdered && deliveryMethod != DeliveryMethod.ReliableUnordered) + throw new ArgumentException("Delivery event will work only for ReliableOrdered/Unordered packets"); + SendInternal(new ReadOnlySpan(data, 0, data.Length), 0, deliveryMethod, userData); + } + + /// + /// Send data to peer with delivery event called + /// + /// Data + /// Start of data + /// Length of data + /// Delivery method (reliable, unreliable, etc.) + /// User data that will be received in DeliveryEvent + /// + /// If you trying to send unreliable packet type + /// + public void SendWithDeliveryEvent(byte[] data, int start, int length, DeliveryMethod deliveryMethod, object userData) + { + if (deliveryMethod != DeliveryMethod.ReliableOrdered && deliveryMethod != DeliveryMethod.ReliableUnordered) + throw new ArgumentException("Delivery event will work only for ReliableOrdered/Unordered packets"); + SendInternal(new ReadOnlySpan(data, start, length), 0, deliveryMethod, userData); + } + + /// + /// Send data to peer with delivery event called + /// + /// Data + /// Delivery method (reliable, unreliable, etc.) + /// User data that will be received in DeliveryEvent + /// + /// If you trying to send unreliable packet type + /// + public void SendWithDeliveryEvent(NetDataWriter dataWriter, DeliveryMethod deliveryMethod, object userData) + { + if (deliveryMethod != DeliveryMethod.ReliableOrdered && deliveryMethod != DeliveryMethod.ReliableUnordered) + throw new ArgumentException("Delivery event will work only for ReliableOrdered/Unordered packets"); + SendInternal(dataWriter.AsReadOnlySpan(), 0, deliveryMethod, userData); + } + + /// + /// Send data to peer (channel - 0) + /// + /// Data + /// Send options (reliable, unreliable, etc.) + /// + /// If size exceeds maximum limit: + /// MTU - headerSize bytes for Unreliable + /// Fragment count exceeded ushort.MaxValue + /// + public void Send(byte[] data, DeliveryMethod deliveryMethod) => + SendInternal(new ReadOnlySpan(data, 0, data.Length), 0, deliveryMethod, null); + + /// + /// Send data to peer (channel - 0) + /// + /// DataWriter with data + /// Send options (reliable, unreliable, etc.) + /// + /// If size exceeds maximum limit: + /// MTU - headerSize bytes for Unreliable + /// Fragment count exceeded ushort.MaxValue + /// + public void Send(NetDataWriter dataWriter, DeliveryMethod deliveryMethod) => + SendInternal(dataWriter.AsReadOnlySpan(), 0, deliveryMethod, null); + + /// + /// Send data to peer (channel - 0) + /// + /// Data + /// Start of data + /// Length of data + /// Send options (reliable, unreliable, etc.) + /// + /// If size exceeds maximum limit: + /// MTU - headerSize bytes for Unreliable + /// Fragment count exceeded ushort.MaxValue + /// + public void Send(byte[] data, int start, int length, DeliveryMethod options) => + SendInternal(new ReadOnlySpan(data, start, length), 0, options, null); + + /// + /// Send data to peer with delivery event called + /// + /// Data + /// Delivery method (reliable, unreliable, etc.) + /// User data that will be received in DeliveryEvent + /// + /// If you trying to send unreliable packet type + /// + public void SendWithDeliveryEvent(ReadOnlySpan data, DeliveryMethod deliveryMethod, object userData) + { + if (deliveryMethod != DeliveryMethod.ReliableOrdered && deliveryMethod != DeliveryMethod.ReliableUnordered) + throw new ArgumentException("Delivery event will work only for ReliableOrdered/Unordered packets"); + SendInternal(data, 0, deliveryMethod, userData); + } + + /// + /// Send data to peer (channel - 0) + /// + /// Data + /// Send options (reliable, unreliable, etc.) + /// + /// If size exceeds maximum limit: + /// MTU - headerSize bytes for Unreliable + /// Fragment count exceeded ushort.MaxValue + /// + public void Send(ReadOnlySpan data, DeliveryMethod deliveryMethod) => + SendInternal(data, 0, deliveryMethod, null); + + protected void SendInternal( + ReadOnlySpan data, + byte channelNumber, + DeliveryMethod deliveryMethod, + object userData) + { + if (_connectionState != ConnectionState.Connected || channelNumber >= ChannelsCount) + return; + + //Select channel + PacketProperty property; + BaseChannel channel = null; + + if (deliveryMethod == DeliveryMethod.Unreliable) + { + property = PacketProperty.Unreliable; + } + else + { + property = PacketProperty.Channeled; + channel = CreateChannel((byte)(channelNumber * NetConstants.ChannelTypeCount + (byte)deliveryMethod)); + } + + //Prepare + NetDebug.Write("[RS]Packet: " + property); + + //Check fragmentation + int headerSize = NetPacket.GetHeaderSize(property); + //Save mtu for multithread + int mtu = _mtu; + int length = data.Length; + if (length + headerSize > mtu) + { + //if cannot be fragmented + if (deliveryMethod != DeliveryMethod.ReliableOrdered && deliveryMethod != DeliveryMethod.ReliableUnordered) + throw new TooBigPacketException("Unreliable or ReliableSequenced packet size exceeded maximum of " + (mtu - headerSize) + " bytes, Check allowed size by GetMaxSinglePacketSize()"); + + int packetFullSize = mtu - headerSize; + int packetDataSize = packetFullSize - NetConstants.FragmentHeaderSize; + int totalPackets = length / packetDataSize + (length % packetDataSize == 0 ? 0 : 1); + + if (totalPackets > NetManager.MaxFragmentsCount) + throw new TooBigPacketException("Data was split in " + totalPackets + " fragments, which exceeds " + NetManager.MaxFragmentsCount); + + ushort currentFragmentId = (ushort)Interlocked.Increment(ref _fragmentId); + + for (ushort partIdx = 0; partIdx < totalPackets; partIdx++) + { + int sendLength = length > packetDataSize ? packetDataSize : length; + + NetPacket p = NetManager.PoolGetPacket(headerSize + sendLength + NetConstants.FragmentHeaderSize); + p.Property = property; + p.UserData = userData; + p.FragmentId = currentFragmentId; + p.FragmentPart = partIdx; + p.FragmentsTotal = (ushort)totalPackets; + p.MarkFragmented(); + + data.Slice(partIdx * packetDataSize, sendLength).CopyTo(new Span(p.RawData, NetConstants.FragmentedHeaderTotalSize, sendLength)); + channel.AddToQueue(p); + + length -= sendLength; + } + return; + } + + //Else just send + NetPacket packet = NetManager.PoolGetPacket(headerSize + length); + packet.Property = property; + data.CopyTo(new Span(packet.RawData, headerSize, length)); + packet.UserData = userData; + + if (channel == null) //unreliable + { + lock (_unreliableChannelLock) + { + if (_unreliablePendingCount == _unreliableChannel.Length) + Array.Resize(ref _unreliableChannel, _unreliablePendingCount*2); + _unreliableChannel[_unreliablePendingCount++] = packet; + } + } + else + { + channel.AddToQueue(packet); + } + } + + public void Disconnect(byte[] data) => + NetManager.DisconnectPeer(this, data); + + public void Disconnect(NetDataWriter writer) => + NetManager.DisconnectPeer(this, writer); + + public void Disconnect(byte[] data, int start, int count) => + NetManager.DisconnectPeer(this, data, start, count); + + public void Disconnect(ReadOnlySpan data) => + NetManager.DisconnectPeer(this, data); + + public void Disconnect() => + NetManager.DisconnectPeer(this); + + internal DisconnectResult ProcessDisconnect(NetPacket packet) + { + if ((_connectionState == ConnectionState.Connected || _connectionState == ConnectionState.Outgoing) && + packet.Size >= 9 && + BitConverter.ToInt64(packet.RawData, 1) == _connectTime && + packet.ConnectionNumber == _connectNum) + { + return _connectionState == ConnectionState.Connected + ? DisconnectResult.Disconnect + : DisconnectResult.Reject; + } + return DisconnectResult.None; + } + + internal virtual void AddToReliableChannelSendQueue(BaseChannel channel) + { + + } + + /// + /// Internally handles the shutdown process for this peer. + /// + /// Optional data to include in the unreliable disconnect packet. + /// + /// If , immediately sets state to without sending a notification.
+ /// If , sends unreliable disconnect packets until a timeout occurs and sets state to + /// Queued reliable packets are bypassed and dropped immediately. + /// + /// A indicating the state change transition. + internal ShutdownResult Shutdown(ReadOnlySpan data, bool force) + { + lock (_shutdownLock) + { + //trying to shutdown already disconnected + if (_connectionState == ConnectionState.Disconnected || + _connectionState == ConnectionState.ShutdownRequested) + { + return ShutdownResult.None; + } + + var result = _connectionState == ConnectionState.Connected + ? ShutdownResult.WasConnected + : ShutdownResult.Success; + + //don't send anything + if (force) + { + _connectionState = ConnectionState.Disconnected; + return result; + } + + //reset time for reconnect protection + Interlocked.Exchange(ref _timeSinceLastPacket, 0); + + //send shutdown packet + _shutdownPacket = new NetPacket(PacketProperty.Disconnect, data.Length) {ConnectionNumber = _connectNum}; + FastBitConverter.GetBytes(_shutdownPacket.RawData, 1, _connectTime); + if (_shutdownPacket.Size >= _mtu) + { + //Drop additional data + NetDebug.WriteError("[Peer] Disconnect additional data size more than MTU - 8!"); + } + else if (!data.IsEmpty) + { + data.CopyTo(_shutdownPacket.RawData.AsSpan(9)); + } + _connectionState = ConnectionState.ShutdownRequested; + NetDebug.Write("[Peer] Send disconnect"); + NetManager.SendRaw(_shutdownPacket, this); + return result; + } + } + + private void UpdateRoundTripTime(int roundTripTime) + { + _rtt += roundTripTime; + _rttCount++; + _avgRtt = _rtt/_rttCount; + _resendDelay = ResendFixedDelay + _avgRtt * ResendRttMultiplier; + } + + internal void AddReliablePacket(DeliveryMethod method, NetPacket p) + { + if (p.IsFragmented) + { + if (p.FragmentsTotal == 0 || p.FragmentsTotal > NetManager.MaxFragmentsCount) + { + NetManager.PoolRecycle(p); + NetDebug.WriteError($"Invalid FragmentsTotal: {p.FragmentsTotal}"); + return; + } + + if (p.FragmentPart >= p.FragmentsTotal) + { + NetManager.PoolRecycle(p); + NetDebug.WriteError($"FragmentPart {p.FragmentPart} >= FragmentsTotal {p.FragmentsTotal}"); + return; + } + + NetDebug.Write($"Fragment. Id: {p.FragmentId}, Part: {p.FragmentPart}, Total: {p.FragmentsTotal}"); + + //Get needed array from dictionary + ushort packetFragId = p.FragmentId; + byte packetChannelId = p.ChannelId; + if (!_holdedFragments.TryGetValue(packetFragId, out var incomingFragments)) + { + //Holded fragments limit reached + if (_holdedFragments.Count >= NetConstants.MaxFragmentsInWindow * ChannelsCount * NetConstants.FragmentedChannelsCount) + { + NetManager.PoolRecycle(p); + //NetDebug.WriteError($"Holded fragments limit reached ({_holdedFragments.Count}/{(NetConstants.DefaultWindowSize / 2) * ChannelsCount * NetConstants.FragmentedChannelsCount}). Dropping fragment id: {packetFragId}"); + return; + } + + incomingFragments = new IncomingFragments(p.FragmentsTotal, p.ChannelId); + _holdedFragments.Add(packetFragId, incomingFragments); + } + else if (p.FragmentsTotal != incomingFragments.TotalFragments || p.ChannelId != incomingFragments.ChannelId) + { + NetManager.PoolRecycle(p); + NetDebug.WriteError("Fragment metadata mismatch"); + return; + } + + //Cache + var fragments = incomingFragments.Fragments; + + //Error check + if (fragments[p.FragmentPart] != null) + { + NetManager.PoolRecycle(p); + NetDebug.WriteError("Invalid fragment packet"); + return; + } + + //Fill array + fragments[p.FragmentPart] = p; + + //Increase received fragments count + incomingFragments.ReceivedCount++; + + //Increase total size + incomingFragments.TotalSize += p.Size - NetConstants.FragmentedHeaderTotalSize; + + //Check for finish + if (incomingFragments.ReceivedCount != incomingFragments.TotalFragments) + return; + + //Just simple packet + NetPacket resultingPacket = NetManager.PoolGetPacket(incomingFragments.TotalSize); + + void AbortReassembly(string error) + { + _holdedFragments.Remove(packetFragId); + incomingFragments.RecycleAll(NetManager); + NetManager.PoolRecycle(resultingPacket); + NetDebug.WriteError(error); + } + + int pos = 0; + for (ushort i = 0; i < incomingFragments.TotalFragments; i++) + { + NetPacket fragment = fragments[i]; + if (fragment == null) + { + AbortReassembly($"Fragment {i} missing during reassembly"); + return; + } + + if (fragment.Size > fragment.RawData.Length) + { + AbortReassembly($"Fragment error size: {fragment.Size} > fragment.RawData.Length: {fragment.RawData.Length}"); + return; + } + + int writtenSize = fragment.Size - NetConstants.FragmentedHeaderTotalSize; + if (pos + writtenSize > resultingPacket.RawData.Length) + { + AbortReassembly($"Fragment error pos: {pos + writtenSize} >= resultPacketSize: {resultingPacket.RawData.Length}"); + return; + } + + //Create resulting big packet + Buffer.BlockCopy( + fragment.RawData, + NetConstants.FragmentedHeaderTotalSize, + resultingPacket.RawData, + pos, + writtenSize); + + pos += writtenSize; + + //Free memory + NetManager.PoolRecycle(fragment); + fragments[i] = null; + } + + //Clear memory + _holdedFragments.Remove(packetFragId); + + //Send to process + NetManager.CreateReceiveEvent(resultingPacket, method, (byte)(packetChannelId / NetConstants.ChannelTypeCount), 0, this); + } + else //Just simple packet + { + NetManager.CreateReceiveEvent(p, method, (byte)(p.ChannelId / NetConstants.ChannelTypeCount), NetConstants.ChanneledHeaderSize, this); + } + } + + private void ProcessMtuPacket(NetPacket packet) + { + //header + int + if (packet.Size < NetConstants.PossibleMtu[0]) + return; + + //first stage check (mtu check and mtu ok) + int receivedMtu = BitConverter.ToInt32(packet.RawData, 1); + int endMtuCheck = BitConverter.ToInt32(packet.RawData, packet.Size - 4); + if (receivedMtu != packet.Size || receivedMtu != endMtuCheck || receivedMtu > NetConstants.MaxPacketSize) + { + NetDebug.WriteError($"[MTU] Broken packet. RMTU {receivedMtu}, EMTU {endMtuCheck}, PSIZE {packet.Size}"); + return; + } + + if (packet.Property == PacketProperty.MtuCheck) + { + _mtuCheckAttempts = 0; + NetDebug.Write("[MTU] check. send back: " + receivedMtu); + packet.Property = PacketProperty.MtuOk; + NetManager.SendRawAndRecycle(packet, this); + } + else if(receivedMtu > _mtu && !_finishMtu) //MtuOk + { + //invalid packet + if (receivedMtu != NetConstants.PossibleMtu[_mtuIdx + 1] - NetManager.ExtraPacketSizeForLayer) + return; + + lock (_mtuMutex) + { + SetMtu(_mtuIdx+1); + } + //if maxed - finish. + if (_mtuIdx == NetConstants.PossibleMtu.Length - 1) + _finishMtu = true; + NetManager.PoolRecycle(packet); + NetDebug.Write("[MTU] ok. Increase to: " + _mtu); + } + } + + private void UpdateMtuLogic(float deltaTime) + { + if (_finishMtu) + return; + + _mtuCheckTimer += deltaTime; + if (_mtuCheckTimer < MtuCheckDelay) + return; + + _mtuCheckTimer = 0; + _mtuCheckAttempts++; + if (_mtuCheckAttempts >= MaxMtuCheckAttempts) + { + _finishMtu = true; + return; + } + + lock (_mtuMutex) + { + if (_mtuIdx >= NetConstants.PossibleMtu.Length - 1) + return; + + //Send increased packet + int newMtu = NetConstants.PossibleMtu[_mtuIdx + 1] - NetManager.ExtraPacketSizeForLayer; + var p = NetManager.PoolGetPacket(newMtu); + p.Property = PacketProperty.MtuCheck; + FastBitConverter.GetBytes(p.RawData, 1, newMtu); //place into start + FastBitConverter.GetBytes(p.RawData, p.Size - 4, newMtu);//and end of packet + + //Must check result for MTU fix + if (NetManager.SendRawAndRecycle(p, this) <= 0) + _finishMtu = true; + } + } + + /// + /// Evaluates incoming connection requests against the current peer state to supply Reconnect Protection. + /// + /// The incoming connection request packet. + /// A directing how the manager should handle the request. + /// + /// If the state is , the peer lingers to ignore older connection requests + /// (where the packet timestamp is smaller than internal ), ensuring older connections are ignored. + /// + internal ConnectRequestResult ProcessConnectRequest(NetConnectRequestPacket connRequest) + { + //current or new request + switch (_connectionState) + { + //P2P case + case ConnectionState.Outgoing: + //fast check + if (connRequest.ConnectionTime < _connectTime) + { + return ConnectRequestResult.P2PLose; + } + //slow rare case check + if (connRequest.ConnectionTime == _connectTime) + { + var localBytes = connRequest.TargetAddress; + for (int i = _cachedSocketAddr.Size-1; i >= 0; i--) + { + byte rb = _cachedSocketAddr[i]; + if (rb == localBytes[i]) + continue; + if (rb < localBytes[i]) + return ConnectRequestResult.P2PLose; + } + } + break; + + case ConnectionState.Connected: + //Old connect request + if (connRequest.ConnectionTime == _connectTime) + { + //just reply accept + NetManager.SendRaw(_connectAcceptPacket, this); + } + //New connect request + else if (connRequest.ConnectionTime > _connectTime) + { + return ConnectRequestResult.Reconnection; + } + break; + + case ConnectionState.Disconnected: + case ConnectionState.ShutdownRequested: + if (connRequest.ConnectionTime >= _connectTime) + return ConnectRequestResult.NewConnection; + break; + } + return ConnectRequestResult.None; + } + + internal virtual void ProcessChanneled(NetPacket packet) + { + if (packet.ChannelId >= NetConstants.ChannelTypeCount || + packet.ChannelId == (int)DeliveryMethod.ReliableSequenced) + { + NetManager.PoolRecycle(packet); + return; + } + + if (!CreateChannel(packet.ChannelId).ProcessPacket(packet)) + NetManager.PoolRecycle(packet); + } + + //Process incoming packet + internal void ProcessPacket(NetPacket packet) + { + //not initialized + if (_connectionState == ConnectionState.Outgoing || _connectionState == ConnectionState.Disconnected) + { + NetManager.PoolRecycle(packet); + return; + } + if (packet.Property == PacketProperty.ShutdownOk) + { + if (_connectionState == ConnectionState.ShutdownRequested) + _connectionState = ConnectionState.Disconnected; + NetManager.PoolRecycle(packet); + return; + } + if (packet.ConnectionNumber != _connectNum) + { + NetDebug.Write(NetLogLevel.Trace, "[RR]Old packet"); + NetManager.PoolRecycle(packet); + return; + } + Interlocked.Exchange(ref _timeSinceLastPacket, 0); + + NetDebug.Write($"[RR]PacketProperty: {packet.Property}"); + switch (packet.Property) + { + case PacketProperty.Merged: + int pos = NetConstants.HeaderSize; + while (pos < packet.Size) + { + ushort size = BitConverter.ToUInt16(packet.RawData, pos); + if (size == 0) + break; + + pos += 2; + if (packet.Size - pos < size) + break; + + NetPacket mergedPacket = NetManager.PoolGetPacket(size); + Buffer.BlockCopy(packet.RawData, pos, mergedPacket.RawData, 0, size); + mergedPacket.Size = size; + + if (!mergedPacket.Verify()) + break; + + pos += size; + ProcessPacket(mergedPacket); + } + NetManager.PoolRecycle(packet); + break; + //If we get ping, send pong + case PacketProperty.Ping: + if (NetUtils.RelativeSequenceNumber(packet.Sequence, _pongPacket.Sequence) > 0) + { + NetDebug.Write("[PP]Ping receive, send pong"); + FastBitConverter.GetBytes(_pongPacket.RawData, 3, DateTime.UtcNow.Ticks); + _pongPacket.Sequence = packet.Sequence; + NetManager.SendRaw(_pongPacket, this); + } + NetManager.PoolRecycle(packet); + break; + + //If we get pong, calculate ping time and rtt + case PacketProperty.Pong: + if (packet.Sequence == _pingPacket.Sequence) + { + _pingTimer.Stop(); + int elapsedMs = (int)_pingTimer.ElapsedMilliseconds; + _remoteDelta = BitConverter.ToInt64(packet.RawData, 3) + (elapsedMs * TimeSpan.TicksPerMillisecond ) / 2 - DateTime.UtcNow.Ticks; + UpdateRoundTripTime(elapsedMs); + NetManager.ConnectionLatencyUpdated(this, elapsedMs / 2); + NetDebug.Write($"[PP]Ping: {packet.Sequence} - {elapsedMs} - {_remoteDelta}"); + } + NetManager.PoolRecycle(packet); + break; + + case PacketProperty.Ack: + case PacketProperty.Channeled: + case PacketProperty.ReliableMerged: + ProcessChanneled(packet); + break; + + //Simple packet without acks + case PacketProperty.Unreliable: + NetManager.CreateReceiveEvent(packet, DeliveryMethod.Unreliable, 0, NetConstants.HeaderSize, this); + return; + + case PacketProperty.MtuCheck: + case PacketProperty.MtuOk: + ProcessMtuPacket(packet); + break; + + default: + NetDebug.WriteError("Error! Unexpected packet type: " + packet.Property); + break; + } + } + + private void SendMerged() + { + if (_mergeCount == 0) + return; + int bytesSent; + if (_mergeCount > 1) + { + NetDebug.Write("[P]Send merged: " + _mergePos + ", count: " + _mergeCount); + bytesSent = NetManager.SendRaw(_mergeData.RawData, 0, NetConstants.HeaderSize + _mergePos, this); + } + else + { + //Send without length information and merging + bytesSent = NetManager.SendRaw(_mergeData.RawData, NetConstants.HeaderSize + 2, _mergePos - 2, this); + } + + if (NetManager.EnableStatistics) + { + Statistics.IncrementPacketsSent(); + Statistics.AddBytesSent(bytesSent); + } + + _mergePos = 0; + _mergeCount = 0; + } + + internal void SendUserData(NetPacket packet) + { + packet.ConnectionNumber = _connectNum; + int mergedPacketSize = NetConstants.HeaderSize + packet.Size + 2; + const int sizeTreshold = 20; + if (mergedPacketSize + sizeTreshold >= _mtu) + { + NetDebug.Write(NetLogLevel.Trace, "[P]SendingPacket: " + packet.Property); + int bytesSent = NetManager.SendRaw(packet, this); + + if (NetManager.EnableStatistics) + { + Statistics.IncrementPacketsSent(); + Statistics.AddBytesSent(bytesSent); + } + + return; + } + if (_mergePos + mergedPacketSize > _mtu) + SendMerged(); + + FastBitConverter.GetBytes(_mergeData.RawData, _mergePos + NetConstants.HeaderSize, (ushort)packet.Size); + Buffer.BlockCopy(packet.RawData, 0, _mergeData.RawData, _mergePos + NetConstants.HeaderSize + 2, packet.Size); + _mergePos += packet.Size + 2; + _mergeCount++; + //DebugWriteForce("Merged: " + _mergePos + "/" + (_mtu - 2) + ", count: " + _mergeCount); + } + + protected virtual void UpdateChannels() + { + _reliableChannel?.SendNextPackets(); + _reliableUnorderedChannel?.SendNextPackets(); + _sequencedChannel?.SendNextPackets(); + } + + internal void Update(float deltaTime) + { + Interlocked.Exchange(ref _timeSinceLastPacket, _timeSinceLastPacket + deltaTime); + switch (_connectionState) + { + case ConnectionState.Connected: + if (_timeSinceLastPacket > NetManager.DisconnectTimeout) + { + NetDebug.Write($"[UPDATE] Disconnect by timeout: {_timeSinceLastPacket} > {NetManager.DisconnectTimeout}"); + NetManager.DisconnectPeerForce(this, DisconnectReason.Timeout, 0, null); + return; + } + break; + + case ConnectionState.ShutdownRequested: + if (_timeSinceLastPacket > NetManager.DisconnectTimeout) + { + _connectionState = ConnectionState.Disconnected; + } + else + { + _shutdownTimer += deltaTime; + if (_shutdownTimer >= ShutdownDelay) + { + _shutdownTimer = 0; + NetManager.SendRaw(_shutdownPacket, this); + } + } + return; + + case ConnectionState.Outgoing: + _connectTimer += deltaTime; + if (_connectTimer > NetManager.ReconnectDelay) + { + _connectTimer = 0; + _connectAttempts++; + if (_connectAttempts > NetManager.MaxConnectAttempts) + { + NetManager.DisconnectPeerForce(this, DisconnectReason.ConnectionFailed, 0, null); + return; + } + + //else send connect again + NetManager.SendRaw(_connectRequestPacket, this); + } + return; + + case ConnectionState.Disconnected: + return; + } + + //Send ping + _pingSendTimer += deltaTime; + if (_pingSendTimer >= NetManager.PingInterval) + { + NetDebug.Write("[PP] Send ping..."); + //reset timer + _pingSendTimer = 0; + //send ping + _pingPacket.Sequence++; + //ping timeout + if (_pingTimer.IsRunning) + UpdateRoundTripTime((int)_pingTimer.ElapsedMilliseconds); + _pingTimer.Restart(); + NetManager.SendRaw(_pingPacket, this); + } + + //RTT - round trip time + _rttResetTimer += deltaTime; + if (_rttResetTimer >= NetManager.PingInterval * 3) + { + _rttResetTimer = 0; + _rtt = _avgRtt; + _rttCount = 1; + } + + UpdateMtuLogic(deltaTime); + + UpdateChannels(); + + if (_unreliablePendingCount > 0) + { + int unreliableCount; + lock (_unreliableChannelLock) + { + (_unreliableChannel, _unreliableSecondQueue) = (_unreliableSecondQueue, _unreliableChannel); + unreliableCount = _unreliablePendingCount; + _unreliablePendingCount = 0; + } + for (int i = 0; i < unreliableCount; i++) + { + var packet = _unreliableSecondQueue[i]; + SendUserData(packet); + NetManager.PoolRecycle(packet); + } + } + + SendMerged(); + } + + //For reliable channel + internal void RecycleAndDeliver(NetPacket packet) + { + if (packet.UserData is MergedPacketUserData mergedUserData) + { + for (int i = 0; i < mergedUserData.Items.Length; i++) + NetManager.MessageDelivered(this, mergedUserData.Items[i]); + packet.UserData = null; + } + else if (packet.UserData != null) + { + if (packet.IsFragmented) + { + _deliveredFragments.TryGetValue(packet.FragmentId, out ushort fragCount); + fragCount++; + if (fragCount == packet.FragmentsTotal) + { + NetManager.MessageDelivered(this, packet.UserData); + _deliveredFragments.Remove(packet.FragmentId); + } + else + { + _deliveredFragments[packet.FragmentId] = fragCount; + } + } + else + { + NetManager.MessageDelivered(this, packet.UserData); + } + packet.UserData = null; + } + NetManager.PoolRecycle(packet); + } + } +} diff --git a/LiteNetLib/NatPunchModule.cs b/LiteNetLib/NatPunchModule.cs index bab2fca0..3259384b 100644 --- a/LiteNetLib/NatPunchModule.cs +++ b/LiteNetLib/NatPunchModule.cs @@ -1,27 +1,71 @@ using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; using System.Net; +using System.Net.Sockets; using LiteNetLib.Utils; namespace LiteNetLib { + /// + /// Specifies the type of network address discovered during NAT punchthrough. + /// public enum NatAddressType { + /// + /// Address within the local area network (LAN). + /// Internal, + /// + /// Publicly accessible address on the wide area network (WAN). + /// External } + /// + /// Interface for handling events related to NAT punchthrough and introduction. + /// public interface INatPunchListener { + /// + /// Called when a NAT introduction request is received from the mediator server. + /// + /// The local endpoint of the client requesting connection. + /// The remote endpoint of the client requesting connection. + /// Custom data token associated with the request. void OnNatIntroductionRequest(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, string token); + + /// + /// Called when NAT punchthrough is successful and a direct connection can be established. + /// + /// The resolved endpoint of the remote peer. + /// The type of address (Internal or External) that succeeded. + /// Custom data token associated with the request. void OnNatIntroductionSuccess(IPEndPoint targetEndPoint, NatAddressType type, string token); } + /// + /// An implementation of that maps callbacks to events. + /// public class EventBasedNatPunchListener : INatPunchListener { + /// + /// Delegate for NAT introduction request events. + /// public delegate void OnNatIntroductionRequest(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, string token); + + /// + /// Delegate for NAT introduction success events. + /// public delegate void OnNatIntroductionSuccess(IPEndPoint targetEndPoint, NatAddressType type, string token); + /// + /// Event triggered when a NAT introduction request is received. + /// public event OnNatIntroductionRequest NatIntroductionRequest; + + /// + /// Event triggered when NAT punchthrough is successfully completed. + /// public event OnNatIntroductionSuccess NatIntroductionSuccess; void INatPunchListener.OnNatIntroductionRequest(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, string token) @@ -58,30 +102,33 @@ struct SuccessEventData class NatIntroduceRequestPacket { - public IPEndPoint Internal { get; set; } - public string Token { get; set; } + public IPEndPoint Internal { [Preserve] get; [Preserve] set; } + public string Token { [Preserve] get; [Preserve] set; } } class NatIntroduceResponsePacket { - public IPEndPoint Internal { get; set; } - public IPEndPoint External { get; set; } - public string Token { get; set; } + public IPEndPoint Internal { [Preserve] get; [Preserve] set; } + public IPEndPoint External { [Preserve] get; [Preserve] set; } + public string Token { [Preserve] get; [Preserve] set; } } class NatPunchPacket { - public string Token { get; set; } - public bool IsExternal { get; set; } + public string Token { [Preserve] get; [Preserve] set; } + public bool IsExternal { [Preserve] get; [Preserve] set; } } - private readonly NetManager _socket; + private readonly LiteNetManager _socket; private readonly ConcurrentQueue _requestEvents = new ConcurrentQueue(); private readonly ConcurrentQueue _successEvents = new ConcurrentQueue(); private readonly NetDataReader _cacheReader = new NetDataReader(); private readonly NetDataWriter _cacheWriter = new NetDataWriter(); private readonly NetPacketProcessor _netPacketProcessor = new NetPacketProcessor(MaxTokenLength); private INatPunchListener _natPunchListener; + /// + /// Maximum allowed length for the NAT introduction token string. + /// public const int MaxTokenLength = 256; /// @@ -89,7 +136,7 @@ class NatPunchPacket /// public bool UnsyncedEvents = false; - internal NatPunchModule(NetManager socket) + internal NatPunchModule(LiteNetManager socket) { _socket = socket; _netPacketProcessor.SubscribeReusable(OnNatIntroductionResponse); @@ -106,12 +153,20 @@ internal void ProcessMessage(IPEndPoint senderEndPoint, NetPacket packet) } } + /// + /// Initializes the NAT punch module with a listener to handle punchthrough events. + /// + /// The listener implementation that will receive NAT events. public void Init(INatPunchListener listener) { _natPunchListener = listener; } - private void Send(T packet, IPEndPoint target) where T : class, new() + private void Send< +#if NET5_0_OR_GREATER + [DynamicallyAccessedMembers(Trimming.SerializerMemberTypes)] +#endif + T>(T packet, IPEndPoint target) where T : class, new() { _cacheWriter.Reset(); _cacheWriter.Put((byte)PacketProperty.NatMessage); @@ -119,6 +174,17 @@ public void Init(INatPunchListener listener) _socket.SendRaw(_cacheWriter.Data, 0, _cacheWriter.Length, target); } + /// + /// Sends NAT introduction packets to both the host and the client to facilitate punchthrough. + /// + /// + /// This is typically called by a mediator (e.g. a master server). + /// + /// Internal (LAN) endpoint of the host. + /// External (WAN) endpoint of the host. + /// Internal (LAN) endpoint of the client. + /// External (WAN) endpoint of the client. + /// Custom token or data to include in the introduction. public void NatIntroduce( IPEndPoint hostInternal, IPEndPoint hostExternal, @@ -142,6 +208,12 @@ public void NatIntroduce( Send(req, hostExternal); } + /// + /// Triggers queued NAT punchthrough events (Success or Request) on the provided . + /// + /// + /// This should be called from the main thread if is . + /// public void PollEvents() { if (UnsyncedEvents) @@ -164,16 +236,27 @@ public void PollEvents() } } + /// + /// Sends a request to the Master Server to introduce this peer to another peer. + /// + /// The hostname or IP of the Master Server. + /// The port of the Master Server. + /// Custom token to identify the connection or room. public void SendNatIntroduceRequest(string host, int port, string additionalInfo) { SendNatIntroduceRequest(NetUtils.MakeEndPoint(host, port), additionalInfo); } + /// + /// Sends a request to the Master Server to introduce this peer to another peer. + /// + /// The endpoint of the Master Server. + /// Custom token to identify the connection or room. public void SendNatIntroduceRequest(IPEndPoint masterServerEndPoint, string additionalInfo) { //prepare outgoing data string networkIp = NetUtils.GetLocalIp(LocalAddrType.IPv4); - if (string.IsNullOrEmpty(networkIp)) + if (string.IsNullOrEmpty(networkIp) || masterServerEndPoint.AddressFamily == AddressFamily.InterNetworkV6) { networkIp = NetUtils.GetLocalIp(LocalAddrType.IPv6); } @@ -216,7 +299,7 @@ private void OnNatIntroductionResponse(NatIntroduceResponsePacket req) // send internal punch var punchPacket = new NatPunchPacket {Token = req.Token}; Send(punchPacket, req.Internal); - NetDebug.Write(NetLogLevel.Trace, "[NAT] internal punch sent to " + req.Internal); + NetDebug.Write(NetLogLevel.Trace, $"[NAT] internal punch sent to {req.Internal}"); // hack for some routers _socket.Ttl = 2; @@ -226,15 +309,14 @@ private void OnNatIntroductionResponse(NatIntroduceResponsePacket req) _socket.Ttl = NetConstants.SocketTTL; punchPacket.IsExternal = true; Send(punchPacket, req.External); - NetDebug.Write(NetLogLevel.Trace, "[NAT] external punch sent to " + req.External); + NetDebug.Write(NetLogLevel.Trace, $"[NAT] external punch sent to {req.External}"); } //We got punch and can connect private void OnNatPunch(NatPunchPacket req, IPEndPoint senderEndPoint) { //Read info - NetDebug.Write(NetLogLevel.Trace, "[NAT] punch received from {0} - additional info: {1}", - senderEndPoint, req.Token); + NetDebug.Write(NetLogLevel.Trace, $"[NAT] punch received from {senderEndPoint} - additional info: {req.Token}"); //Release punch success to client; enabling him to Connect() to Sender if token is ok if(UnsyncedEvents) diff --git a/LiteNetLib/NativeSocket.cs b/LiteNetLib/NativeSocket.cs index fc846c8b..84ecedc4 100644 --- a/LiteNetLib/NativeSocket.cs +++ b/LiteNetLib/NativeSocket.cs @@ -7,111 +7,9 @@ namespace LiteNetLib { - internal readonly struct NativeAddr : IEquatable - { - //common parts - private readonly long _part1; //family, port, etc - private readonly long _part2; - //ipv6 parts - private readonly long _part3; - private readonly int _part4; - - private readonly int _hash; - - public NativeAddr(byte[] address, int len) - { - _part1 = BitConverter.ToInt64(address, 0); - _part2 = BitConverter.ToInt64(address, 8); - if (len > 16) - { - _part3 = BitConverter.ToInt64(address, 16); - _part4 = BitConverter.ToInt32(address, 24); - } - else - { - _part3 = 0; - _part4 = 0; - } - _hash = (int)(_part1 >> 32) ^ (int)_part1 ^ - (int)(_part2 >> 32) ^ (int)_part2 ^ - (int)(_part3 >> 32) ^ (int)_part3 ^ - _part4; - } - - public override int GetHashCode() - { - return _hash; - } - - public bool Equals(NativeAddr other) - { - return _part1 == other._part1 && - _part2 == other._part2 && - _part3 == other._part3 && - _part4 == other._part4; - } - - public override bool Equals(object obj) - { - return obj is NativeAddr other && Equals(other); - } - - public static bool operator ==(NativeAddr left, NativeAddr right) - { - return left.Equals(right); - } - - public static bool operator !=(NativeAddr left, NativeAddr right) - { - return !left.Equals(right); - } - } - - internal class NativeEndPoint : IPEndPoint - { - public readonly byte[] NativeAddress; - - public NativeEndPoint(byte[] address) : base(IPAddress.Any, 0) - { - NativeAddress = new byte[address.Length]; - Buffer.BlockCopy(address, 0, NativeAddress, 0, address.Length); - - short family = (short)((address[1] << 8) | address[0]); - Port =(ushort)((address[2] << 8) | address[3]); - - if ((NativeSocket.UnixMode && family == NativeSocket.AF_INET6) || (!NativeSocket.UnixMode && (AddressFamily)family == AddressFamily.InterNetworkV6)) - { - uint scope = unchecked((uint)( - (address[27] << 24) + - (address[26] << 16) + - (address[25] << 8) + - (address[24]))); -#if NETCOREAPP || NETSTANDARD2_1 || NETSTANDARD2_1_OR_GREATER - Address = new IPAddress(new ReadOnlySpan(address, 8, 16), scope); -#else - byte[] addrBuffer = new byte[16]; - Buffer.BlockCopy(address, 8, addrBuffer, 0, 16); - Address = new IPAddress(addrBuffer, scope); -#endif - } - else //IPv4 - { - long ipv4Addr = unchecked((uint)((address[4] & 0x000000FF) | - (address[5] << 8 & 0x0000FF00) | - (address[6] << 16 & 0x00FF0000) | - (address[7] << 24))); - Address = new IPAddress(ipv4Addr); - } - } - } - internal static class NativeSocket { - static -#if LITENETLIB_UNSAFE - unsafe -#endif - class WinSock + static unsafe class WinSock { private const string LibName = "ws2_32.dll"; @@ -127,22 +25,14 @@ public static extern int recvfrom( [DllImport(LibName, SetLastError = true)] internal static extern int sendto( IntPtr socketHandle, -#if LITENETLIB_UNSAFE byte* pinnedBuffer, -#else - [In] byte[] pinnedBuffer, -#endif [In] int len, [In] SocketFlags socketFlags, [In] byte[] socketAddress, [In] int socketAddressSize); } - static -#if LITENETLIB_UNSAFE - unsafe -#endif - class UnixSock + static unsafe class UnixSock { private const string LibName = "libc"; @@ -158,23 +48,37 @@ public static extern int recvfrom( [DllImport(LibName, SetLastError = true)] internal static extern int sendto( IntPtr socketHandle, -#if LITENETLIB_UNSAFE byte* pinnedBuffer, -#else - [In] byte[] pinnedBuffer, -#endif [In] int len, [In] SocketFlags socketFlags, [In] byte[] socketAddress, [In] int socketAddressSize); } + /// + /// Indicates whether the native socket optimizations are supported on the current platform. + /// public static readonly bool IsSupported = false; + /// + /// Indicates whether the current environment requires Unix-style native socket calls. + /// public static readonly bool UnixMode = false; + /// + /// The size of the native sockaddr_in structure for IPv4. + /// public const int IPv4AddrSize = 16; + /// + /// The size of the native sockaddr_in6 structure for IPv6. + /// public const int IPv6AddrSize = 28; + /// + /// Native Address Family constant for IPv4 (AF_INET). + /// public const int AF_INET = 2; + /// + /// Native Address Family constant for IPv6 (AF_INET6). + /// public const int AF_INET6 = 10; private static readonly Dictionary NativeErrorToSocketError = new Dictionary @@ -236,40 +140,50 @@ static NativeSocket() } } + /// + /// Receives a datagram from the specified socket handle using native OS calls. + /// + /// The OS handle for the socket. + /// A pinned byte array to receive the data. + /// The number of s to receive. + /// A pinned byte array to store the source address (sockaddr). + /// The size of the structure. + /// The number of s received, or a negative value on error. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int RecvFrom( IntPtr socketHandle, byte[] pinnedBuffer, int len, byte[] socketAddress, - ref int socketAddressSize) - { - return UnixMode + ref int socketAddressSize) => + UnixMode ? UnixSock.recvfrom(socketHandle, pinnedBuffer, len, 0, socketAddress, ref socketAddressSize) : WinSock.recvfrom(socketHandle, pinnedBuffer, len, 0, socketAddress, ref socketAddressSize); - } + /// + /// Sends a datagram to the specified socket handle using native OS calls. + /// + /// The OS handle for the socket. + /// A pointer to the pinned memory containing data to send. + /// The number of s to send. + /// A pinned byte array containing the destination address (sockaddr). + /// The size of the structure. + /// The number of s sent, or a negative value on error. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public -#if LITENETLIB_UNSAFE - unsafe -#endif - static int SendTo( + public static unsafe int SendTo( IntPtr socketHandle, -#if LITENETLIB_UNSAFE byte* pinnedBuffer, -#else - byte[] pinnedBuffer, -#endif int len, byte[] socketAddress, - int socketAddressSize) - { - return UnixMode + int socketAddressSize) => + UnixMode ? UnixSock.sendto(socketHandle, pinnedBuffer, len, 0, socketAddress, socketAddressSize) : WinSock.sendto(socketHandle, pinnedBuffer, len, 0, socketAddress, socketAddressSize); - } + /// + /// Retrieves the last OS-specific socket error and translates it to . + /// + /// The translated . public static SocketError GetSocketError() { int error = Marshal.GetLastWin32Error(); @@ -280,6 +194,10 @@ public static SocketError GetSocketError() return (SocketError)error; } + /// + /// Retrieves the last OS-specific socket error and encapsulates it in a . + /// + /// A representing the last native error. public static SocketException GetSocketException() { int error = Marshal.GetLastWin32Error(); @@ -290,12 +208,15 @@ public static SocketException GetSocketException() return new SocketException(error); } + /// + /// Converts the of an endpoint to the corresponding native constant. + /// + /// The endpoint to evaluate. + /// The native address family identifier. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static short GetNativeAddressFamily(IPEndPoint remoteEndPoint) - { - return UnixMode + public static short GetNativeAddressFamily(IPEndPoint remoteEndPoint) => + UnixMode ? (short)(remoteEndPoint.AddressFamily == AddressFamily.InterNetwork ? AF_INET : AF_INET6) : (short)remoteEndPoint.AddressFamily; - } } } diff --git a/LiteNetLib/NetConstants.cs b/LiteNetLib/NetConstants.cs index ca7dfbcd..947cc865 100644 --- a/LiteNetLib/NetConstants.cs +++ b/LiteNetLib/NetConstants.cs @@ -37,26 +37,58 @@ public enum DeliveryMethod : byte ///
public static class NetConstants { - //can be tuned + /// + /// Default window size for reliable channels (number of packets). + /// public const int DefaultWindowSize = 64; - public const int SocketBufferSize = 1024 * 1024; //1mb + /// + /// Size of the underlying UDP socket receive and send buffers in bytes.
+ /// Default is 1MB. + ///
+ public const int SocketBufferSize = 1024 * 1024; + /// + /// Time To Live (TTL) for the UDP packets. + /// public const int SocketTTL = 255; + /// + /// Size of the base packet header (PacketProperty) in s. + /// public const int HeaderSize = 1; + /// + /// Size of the header for sequenced or reliable messages in s.
+ /// Includes , Sequence, and ChannelId. + ///
public const int ChanneledHeaderSize = 4; + /// + /// Additional header size required for fragmented packets in s.
+ /// Includes FragmentId, FragmentPart, and FragmentsTotal. + ///
public const int FragmentHeaderSize = 6; + /// + /// Total header size for a fragmented channeled packet in s.
+ /// Combines and . + ///
public const int FragmentedHeaderTotalSize = ChanneledHeaderSize + FragmentHeaderSize; + /// + /// Maximum possible sequence number before wrapping back to zero. + /// public const ushort MaxSequence = 32768; + /// + /// Half of the , used for sequence comparison and wrap-around logic. + /// public const ushort HalfMaxSequence = MaxSequence / 2; //protocol internal const int ProtocolId = 13; internal const int MaxUdpHeaderSize = 68; internal const int ChannelTypeCount = 4; + internal const int FragmentedChannelsCount = 2; + internal const int MaxFragmentsInWindow = DefaultWindowSize / 2; internal static readonly int[] PossibleMtu = { - 576 - MaxUdpHeaderSize, //minimal (RFC 1191) + //576 - MaxUdpHeaderSize minimal (RFC 1191) 1024, //most games standard 1232 - MaxUdpHeaderSize, 1460 - MaxUdpHeaderSize, //google cloud @@ -65,11 +97,28 @@ public static class NetConstants 1500 - MaxUdpHeaderSize //Ethernet II (RFC 1191) }; - //Max possible single packet size + /// + /// The starting Maximum Transmission Unit (MTU) used for new connections before path MTU discovery. + /// + public static readonly int InitialMtu = PossibleMtu[0]; + /// + /// Maximum possible packet size allowed by the library based on the largest supported MTU. + /// public static readonly int MaxPacketSize = PossibleMtu[PossibleMtu.Length - 1]; + /// + /// Maximum payload size for a single unreliable packet in s.
+ /// Calculated as - . + ///
public static readonly int MaxUnreliableDataSize = MaxPacketSize - HeaderSize; - //peer specific + /// + /// Maximum possible value for . + /// + /// + /// This value is used to distinguish between different connection instances from the same .
+ /// It allows the receiver to identify and discard packets belonging to previous connection attempts that may arrive + /// late due to network jitter, even if they originate from the same address and port.
+ ///
public const byte MaxConnectionNumber = 4; } } diff --git a/LiteNetLib/NetDebug.cs b/LiteNetLib/NetDebug.cs index a326c0d0..44cb6f3e 100644 --- a/LiteNetLib/NetDebug.cs +++ b/LiteNetLib/NetDebug.cs @@ -61,32 +61,32 @@ private static void WriteLogic(NetLogLevel logLevel, string str, params object[] } [Conditional("DEBUG_MESSAGES")] - internal static void Write(string str, params object[] args) + internal static void Write(string str) { - WriteLogic(NetLogLevel.Trace, str, args); + WriteLogic(NetLogLevel.Trace, str); } [Conditional("DEBUG_MESSAGES")] - internal static void Write(NetLogLevel level, string str, params object[] args) + internal static void Write(NetLogLevel level, string str) { - WriteLogic(level, str, args); + WriteLogic(level, str); } [Conditional("DEBUG_MESSAGES"), Conditional("DEBUG")] - internal static void WriteForce(string str, params object[] args) + internal static void WriteForce(string str) { - WriteLogic(NetLogLevel.Trace, str, args); + WriteLogic(NetLogLevel.Trace, str); } [Conditional("DEBUG_MESSAGES"), Conditional("DEBUG")] - internal static void WriteForce(NetLogLevel level, string str, params object[] args) + internal static void WriteForce(NetLogLevel level, string str) { - WriteLogic(level, str, args); + WriteLogic(level, str); } - internal static void WriteError(string str, params object[] args) + internal static void WriteError(string str) { - WriteLogic(NetLogLevel.Error, str, args); + WriteLogic(NetLogLevel.Error, str); } } } diff --git a/LiteNetLib/NetEvent.cs b/LiteNetLib/NetEvent.cs new file mode 100644 index 00000000..7b701e69 --- /dev/null +++ b/LiteNetLib/NetEvent.cs @@ -0,0 +1,107 @@ +using System.Net; +using System.Net.Sockets; + +namespace LiteNetLib +{ + /// + /// Internally used event type + /// + public sealed class NetEvent + { + /// + /// Reference to the next event in the pool or event queue. + /// + public NetEvent Next; + + /// + /// Specifies the category of the network event. + /// + public enum EType + { + /// New peer connected. + Connect, + /// Peer disconnected. + Disconnect, + /// Data received from a connected peer. + Receive, + /// Unconnected message received. + ReceiveUnconnected, + /// Socket or internal protocol error occurred. + Error, + /// Round-trip time (RTT) for a peer has been updated. + ConnectionLatencyUpdated, + /// Broadcast message received. + Broadcast, + /// Incoming connection request from a new peer. + ConnectionRequest, + /// Reliable message was successfully delivered to the remote peer. + MessageDelivered, + /// The IP address or port of an existing peer has changed (e.g., roaming). + PeerAddressChanged + } + + /// + /// The type of network event that occurred. + /// + public EType Type; + + /// + /// The peer associated with this event. for unconnected events. + /// + public LiteNetPeer Peer; + + /// + /// The remote endpoint (IP and Port) from which the event originated. + /// + public IPEndPoint RemoteEndPoint; + + /// + /// Optional user data associated with a connection request or disconnect. + /// + public object UserData; + + /// + /// The updated latency value in milliseconds. Only valid when is . + /// + public int Latency; + + /// + /// The specific socket error. Only valid when is . + /// + public SocketError ErrorCode; + + /// + /// The reason for a peer's disconnection. Only valid when is . + /// + public DisconnectReason DisconnectReason; + + /// + /// Information about an incoming connection. Only valid when is . + /// + public LiteConnectionRequest ConnectionRequest; + + /// + /// The delivery method used for the received packet. Only valid when is . + /// + public DeliveryMethod DeliveryMethod; + + /// + /// The channel on which the packet was received. + /// + public byte ChannelNumber; + + /// + /// A reader for accessing the payload of received data, broadcast, or unconnected messages. + /// + public readonly NetPacketReader DataReader; + + /// + /// Initializes a new instance of the class. + /// + /// The that owns the packet pool and buffers for this event. + public NetEvent(LiteNetManager manager) + { + DataReader = new NetPacketReader(manager, this); + } + } +} diff --git a/LiteNetLib/NetManager.cs b/LiteNetLib/NetManager.cs index 1b1cca67..fc02a659 100644 --- a/LiteNetLib/NetManager.cs +++ b/LiteNetLib/NetManager.cs @@ -1,338 +1,20 @@ using System; -using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Diagnostics; using System.Net; -using System.Net.Sockets; -using System.Threading; using LiteNetLib.Layers; using LiteNetLib.Utils; namespace LiteNetLib { - public enum IPv6Mode - { - Disabled, - SeparateSocket, - DualMode - } - - public sealed class NetPacketReader : NetDataReader - { - private NetPacket _packet; - private readonly NetManager _manager; - private readonly NetEvent _evt; - - internal NetPacketReader(NetManager manager, NetEvent evt) - { - _manager = manager; - _evt = evt; - } - - internal void SetSource(NetPacket packet, int headerSize) - { - if (packet == null) - return; - _packet = packet; - SetSource(packet.RawData, headerSize, packet.Size); - } - - internal void RecycleInternal() - { - Clear(); - if (_packet != null) - _manager.PoolRecycle(_packet); - _packet = null; - _manager.RecycleEvent(_evt); - } - - public void Recycle() - { - if (_manager.AutoRecycle) - return; - RecycleInternal(); - } - } - - internal sealed class NetEvent - { - public NetEvent Next; - - public enum EType - { - Connect, - Disconnect, - Receive, - ReceiveUnconnected, - Error, - ConnectionLatencyUpdated, - Broadcast, - ConnectionRequest, - MessageDelivered, - PeerAddressChanged - } - public EType Type; - - public NetPeer Peer; - public IPEndPoint RemoteEndPoint; - public object UserData; - public int Latency; - public SocketError ErrorCode; - public DisconnectReason DisconnectReason; - public ConnectionRequest ConnectionRequest; - public DeliveryMethod DeliveryMethod; - public byte ChannelNumber; - public readonly NetPacketReader DataReader; - - public NetEvent(NetManager manager) - { - DataReader = new NetPacketReader(manager, this); - } - } - /// - /// Main class for all network operations. Can be used as client and/or server. + /// More feature rich network manager with adjustable channels count /// - public partial class NetManager : IEnumerable + public class NetManager : LiteNetManager, IEnumerable { - private class IPEndPointComparer : IEqualityComparer - { - public bool Equals(IPEndPoint x, IPEndPoint y) - { - return x.Address.Equals(y.Address) && x.Port == y.Port; - } - - public int GetHashCode(IPEndPoint obj) - { - return obj.GetHashCode(); - } - } - - public struct NetPeerEnumerator : IEnumerator - { - private readonly NetPeer _initialPeer; - private NetPeer _p; - - public NetPeerEnumerator(NetPeer p) - { - _initialPeer = p; - _p = null; - } - - public void Dispose() - { - - } - - public bool MoveNext() - { - _p = _p == null ? _initialPeer : _p.NextPeer; - return _p != null; - } - - public void Reset() - { - throw new NotSupportedException(); - } - - public NetPeer Current => _p; - object IEnumerator.Current => _p; - } - -#if DEBUG - private struct IncomingData - { - public NetPacket Data; - public IPEndPoint EndPoint; - public DateTime TimeWhenGet; - } - private readonly List _pingSimulationList = new List(); - private readonly Random _randomGenerator = new Random(); - private const int MinLatencyThreshold = 5; -#endif - - private Thread _logicThread; - private bool _manualMode; - private readonly AutoResetEvent _updateTriggerEvent = new AutoResetEvent(true); - - private Queue _netEventsProduceQueue = new Queue(); - private Queue _netEventsConsumeQueue = new Queue(); - - private NetEvent _netEventPoolHead; private readonly INetEventListener _netEventListener; - private readonly IDeliveryEventListener _deliveryEventListener; - private readonly INtpEventListener _ntpEventListener; - private readonly IPeerAddressChangedListener _peerAddressChangedListener; - - private readonly Dictionary _peersDict = new Dictionary(new IPEndPointComparer()); - private readonly Dictionary _requestsDict = new Dictionary(new IPEndPointComparer()); - private readonly Dictionary _ntpRequests = new Dictionary(new IPEndPointComparer()); - private readonly ReaderWriterLockSlim _peersLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); - private volatile NetPeer _headPeer; - private int _connectedPeersCount; - private readonly List _connectedPeerListCache = new List(); - private NetPeer[] _peersArray = new NetPeer[32]; - private readonly PacketLayerBase _extraPacketLayer; - private int _lastPeerId; - private ConcurrentQueue _peerIds = new ConcurrentQueue(); private byte _channelsCount = 1; - private readonly object _eventLock = new object(); - - //config section - /// - /// Enable messages receiving without connection. (with SendUnconnectedMessage method) - /// - public bool UnconnectedMessagesEnabled = false; - - /// - /// Enable nat punch messages - /// - public bool NatPunchEnabled = false; - - /// - /// Library logic update and send period in milliseconds - /// Lowest values in Windows doesn't change much because of Thread.Sleep precision - /// To more frequent sends (or sends tied to your game logic) use - /// - public int UpdateTime = 15; - - /// - /// Interval for latency detection and checking connection (in milliseconds) - /// - public int PingInterval = 1000; - - /// - /// If NetManager doesn't receive any packet from remote peer during this time (in milliseconds) then connection will be closed - /// (including library internal keepalive packets) - /// - public int DisconnectTimeout = 5000; - - /// - /// Simulate packet loss by dropping random amount of packets. (Works only in DEBUG mode) - /// - public bool SimulatePacketLoss = false; - - /// - /// Simulate latency by holding packets for random time. (Works only in DEBUG mode) - /// - public bool SimulateLatency = false; - - /// - /// Chance of packet loss when simulation enabled. value in percents (1 - 100). - /// - public int SimulationPacketLossChance = 10; - - /// - /// Minimum simulated latency (in milliseconds) - /// - public int SimulationMinLatency = 30; - - /// - /// Maximum simulated latency (in milliseconds) - /// - public int SimulationMaxLatency = 100; - - /// - /// Events automatically will be called without PollEvents method from another thread - /// - public bool UnsyncedEvents = false; - - /// - /// If true - receive event will be called from "receive" thread immediately otherwise on PollEvents call - /// - public bool UnsyncedReceiveEvent = false; - - /// - /// If true - delivery event will be called from "receive" thread immediately otherwise on PollEvents call - /// - public bool UnsyncedDeliveryEvent = false; - - /// - /// Allows receive broadcast packets - /// - public bool BroadcastReceiveEnabled = false; - - /// - /// Delay between initial connection attempts (in milliseconds) - /// - public int ReconnectDelay = 500; - - /// - /// Maximum connection attempts before client stops and call disconnect event. - /// - public int MaxConnectAttempts = 10; - - /// - /// Enables socket option "ReuseAddress" for specific purposes - /// - public bool ReuseAddress = false; - - /// - /// Statistics of all connections - /// - public readonly NetStatistics Statistics = new NetStatistics(); - - /// - /// Toggles the collection of network statistics for the instance and all known peers - /// - public bool EnableStatistics = false; - - /// - /// NatPunchModule for NAT hole punching operations - /// - public readonly NatPunchModule NatPunchModule; - - /// - /// Returns true if socket listening and update thread is running - /// - public bool IsRunning { get; private set; } - - /// - /// Local EndPoint (host and port) - /// - public int LocalPort { get; private set; } - - /// - /// Automatically recycle NetPacketReader after OnReceive event - /// - public bool AutoRecycle; - - /// - /// IPv6 support - /// - public IPv6Mode IPv6Mode = IPv6Mode.SeparateSocket; - - /// - /// Override MTU for all new peers registered in this NetManager, will ignores MTU Discovery! - /// - public int MtuOverride = 0; - - /// - /// Sets initial MTU to lowest possible value according to RFC1191 (576 bytes) - /// - public bool UseSafeMtu = false; - - /// - /// First peer. Useful for Client mode - /// - public NetPeer FirstPeer => _headPeer; - - /// - /// Experimental feature mostly for servers. Only for Windows/Linux - /// use direct socket calls for send/receive to drastically increase speed and reduce GC pressure - /// - public bool UseNativeSockets = false; - - /// - /// Disconnect peers if HostUnreachable or NetworkUnreachable spawned (old behaviour 0.9.x was true) - /// - public bool DisconnectOnUnreachable = false; - - /// - /// Allows peer change it's ip (lte to wifi, wifi to lte, etc). Use only on server - /// - public bool AllowPeerAddressChange = false; + private readonly ConcurrentDictionary _ntpRequests = new ConcurrentDictionary(); /// /// QoS channel count per message type (value must be between 1 and 64 channels) @@ -349,225 +31,124 @@ public byte ChannelsCount } /// - /// Returns connected peers list (with internal cached list) + /// First peer. Useful for Client mode /// - public List ConnectedPeerList - { - get - { - GetPeersNonAlloc(_connectedPeerListCache, ConnectionState.Connected); - return _connectedPeerListCache; - } - } + public new NetPeer FirstPeer => (NetPeer)_headPeer; /// - /// Gets peer by peer id + /// Get copy of peers (without allocations) /// - /// id of peer - /// Peer if peer with id exist, otherwise null - public NetPeer GetPeerById(int id) + /// List that will contain result + /// State of peers + public void GetPeers(List peers, ConnectionState peerState) { - if (id >= 0 && id < _peersArray.Length) + peers.Clear(); + _peersLock.EnterReadLock(); + for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) { - return _peersArray[id]; + if ((netPeer.ConnectionState & peerState) != 0) + peers.Add((NetPeer)netPeer); } - - return null; + _peersLock.ExitReadLock(); } /// - /// Gets peer by peer id + /// Get copy of connected peers (without allocations) /// - /// id of peer - /// resulting peer - /// True if peer with id exist, otherwise false - public bool TryGetPeerById(int id, out NetPeer peer) - { - peer = GetPeerById(id); + /// List that will contain result + public void GetConnectedPeers(List peers) => + GetPeers(peers, ConnectionState.Connected); - return peer != null; - } + public NetManager(INetEventListener listener, PacketLayerBase extraPacketLayer = null) : base(null, extraPacketLayer) => + _netEventListener = listener; /// - /// Returns connected peers count + /// Create the requests for NTP server /// - public int ConnectedPeersCount => Interlocked.CompareExchange(ref _connectedPeersCount,0,0); - - public int ExtraPacketSizeForLayer => _extraPacketLayer?.ExtraPacketSizeForLayer ?? 0; - - private bool TryGetPeer(IPEndPoint endPoint, out NetPeer peer) - { - _peersLock.EnterReadLock(); - bool result = _peersDict.TryGetValue(endPoint, out peer); - _peersLock.ExitReadLock(); - return result; - } - - private void AddPeer(NetPeer peer) - { - _peersLock.EnterWriteLock(); - if (_headPeer != null) - { - peer.NextPeer = _headPeer; - _headPeer.PrevPeer = peer; - } - _headPeer = peer; - _peersDict.Add(peer.EndPoint, peer); - if (peer.Id >= _peersArray.Length) - { - int newSize = _peersArray.Length * 2; - while (peer.Id >= newSize) - newSize *= 2; - Array.Resize(ref _peersArray, newSize); - } - _peersArray[peer.Id] = peer; - RegisterEndPoint(peer.EndPoint); - _peersLock.ExitWriteLock(); - } - - private void RemovePeer(NetPeer peer) - { - _peersLock.EnterWriteLock(); - RemovePeerInternal(peer); - _peersLock.ExitWriteLock(); - } + /// NTP Server address. + public void CreateNtpRequest(IPEndPoint endPoint) => + _ntpRequests.TryAdd(endPoint, new NtpRequest(endPoint)); - private void RemovePeerInternal(NetPeer peer) + /// + /// Create the requests for NTP server + /// + /// NTP Server address. + /// port + public void CreateNtpRequest(string ntpServerAddress, int port) { - if (!_peersDict.Remove(peer.EndPoint)) - return; - if (peer == _headPeer) - _headPeer = peer.NextPeer; - - if (peer.PrevPeer != null) - peer.PrevPeer.NextPeer = peer.NextPeer; - if (peer.NextPeer != null) - peer.NextPeer.PrevPeer = peer.PrevPeer; - peer.PrevPeer = null; - - _peersArray[peer.Id] = null; - _peerIds.Enqueue(peer.Id); - UnregisterEndPoint(peer.EndPoint); + var endPoint = NetUtils.MakeEndPoint(ntpServerAddress, port); + _ntpRequests.TryAdd(endPoint, new NtpRequest(endPoint)); } /// - /// NetManager constructor + /// Create the requests for NTP server (default port) /// - /// Network events listener (also can implement IDeliveryEventListener) - /// Extra processing of packages, like CRC checksum or encryption. All connected NetManagers must have same layer. - public NetManager(INetEventListener listener, PacketLayerBase extraPacketLayer = null) + /// NTP Server address. + public void CreateNtpRequest(string ntpServerAddress) { - _netEventListener = listener; - _deliveryEventListener = listener as IDeliveryEventListener; - _ntpEventListener = listener as INtpEventListener; - _peerAddressChangedListener = listener as IPeerAddressChangedListener; - NatPunchModule = new NatPunchModule(this); - _extraPacketLayer = extraPacketLayer; + var endPoint = NetUtils.MakeEndPoint(ntpServerAddress, NtpRequest.DefaultPort); + _ntpRequests.TryAdd(endPoint, new NtpRequest(endPoint)); } - internal void ConnectionLatencyUpdated(NetPeer fromPeer, int latency) + internal override bool CustomMessageHandle(NetPacket packet, IPEndPoint remoteEndPoint) { - CreateEvent(NetEvent.EType.ConnectionLatencyUpdated, fromPeer, latency: latency); - } + if (_ntpRequests.Count > 0 && _ntpRequests.TryGetValue(remoteEndPoint, out _)) + { + if (packet.Size < 48) + { + NetDebug.Write(NetLogLevel.Trace, $"NTP response too short: {packet.Size}"); + return true; + } - internal void MessageDelivered(NetPeer fromPeer, object userData) - { - if(_deliveryEventListener != null) - CreateEvent(NetEvent.EType.MessageDelivered, fromPeer, userData: userData); - } + byte[] copiedData = new byte[packet.Size]; + Buffer.BlockCopy(packet.RawData, 0, copiedData, 0, packet.Size); + NtpPacket ntpPacket = NtpPacket.FromServerResponse(copiedData, DateTime.UtcNow); + try + { + ntpPacket.ValidateReply(); + } + catch (InvalidOperationException ex) + { + NetDebug.Write(NetLogLevel.Trace, $"NTP response error: {ex.Message}"); + ntpPacket = null; + } - internal void DisconnectPeerForce(NetPeer peer, - DisconnectReason reason, - SocketError socketErrorCode, - NetPacket eventData) - { - DisconnectPeer(peer, reason, socketErrorCode, true, null, 0, 0, eventData); - } + if (ntpPacket != null) + { + _ntpRequests.TryRemove(remoteEndPoint, out _); + _netEventListener.OnNtpResponse(ntpPacket); + } + return true; + } - private void DisconnectPeer( - NetPeer peer, - DisconnectReason reason, - SocketError socketErrorCode, - bool force, - byte[] data, - int start, - int count, - NetPacket eventData) - { - var shutdownResult = peer.Shutdown(data, start, count, force); - if (shutdownResult == ShutdownResult.None) - return; - if(shutdownResult == ShutdownResult.WasConnected) - Interlocked.Decrement(ref _connectedPeersCount); - CreateEvent( - NetEvent.EType.Disconnect, - peer, - errorCode: socketErrorCode, - disconnectReason: reason, - readerSource: eventData); + return false; } - private void CreateEvent( - NetEvent.EType type, - NetPeer peer = null, - IPEndPoint remoteEndPoint = null, - SocketError errorCode = 0, - int latency = 0, - DisconnectReason disconnectReason = DisconnectReason.ConnectionFailed, - ConnectionRequest connectionRequest = null, - DeliveryMethod deliveryMethod = DeliveryMethod.Unreliable, - byte channelNumber = 0, - NetPacket readerSource = null, - object userData = null) - { - NetEvent evt; - bool unsyncEvent = UnsyncedEvents; - - if (type == NetEvent.EType.Connect) - Interlocked.Increment(ref _connectedPeersCount); - else if (type == NetEvent.EType.MessageDelivered) - unsyncEvent = UnsyncedDeliveryEvent; + //connect to + protected override LiteNetPeer CreateOutgoingPeer(IPEndPoint remoteEndPoint, int id, byte connectNum, ReadOnlySpan connectData) => + new NetPeer(this, remoteEndPoint, id, connectNum, connectData); - lock(_eventLock) - { - evt = _netEventPoolHead; - if (evt == null) - evt = new NetEvent(this); - else - _netEventPoolHead = evt.Next; - } + //accept + protected override LiteNetPeer CreateIncomingPeer(LiteConnectionRequest request, int id) => + new NetPeer(this, request, id); - evt.Type = type; - evt.DataReader.SetSource(readerSource, readerSource?.GetHeaderSize() ?? 0); - evt.Peer = peer; - evt.RemoteEndPoint = remoteEndPoint; - evt.Latency = latency; - evt.ErrorCode = errorCode; - evt.DisconnectReason = disconnectReason; - evt.ConnectionRequest = connectionRequest; - evt.DeliveryMethod = deliveryMethod; - evt.ChannelNumber = channelNumber; - evt.UserData = userData; + //reject + protected override LiteNetPeer CreateRejectPeer(IPEndPoint remoteEndPoint, int id) => + new NetPeer(this, remoteEndPoint, id); - if (unsyncEvent || _manualMode) - { - ProcessEvent(evt); - } - else - { - lock(_netEventsProduceQueue) - _netEventsProduceQueue.Enqueue(evt); - } - } + //connection request when you use Accept for getting NetPeer instead of LiteNetPeer + protected override LiteConnectionRequest CreateConnectionRequest(IPEndPoint remoteEndPoint, NetConnectRequestPacket requestPacket) => + new ConnectionRequest(remoteEndPoint, requestPacket, this); - private void ProcessEvent(NetEvent evt) + protected override void ProcessEvent(NetEvent evt) { NetDebug.Write("[NM] Processing event: " + evt.Type); bool emptyData = evt.DataReader.IsNull; + var netPeer = evt.Peer as NetPeer; switch (evt.Type) { case NetEvent.EType.Connect: - _netEventListener.OnPeerConnected(evt.Peer); + _netEventListener.OnPeerConnected(netPeer); break; case NetEvent.EType.Disconnect: var info = new DisconnectInfo @@ -576,10 +157,10 @@ private void ProcessEvent(NetEvent evt) AdditionalData = evt.DataReader, SocketErrorCode = evt.ErrorCode }; - _netEventListener.OnPeerDisconnected(evt.Peer, info); + _netEventListener.OnPeerDisconnected(netPeer, info); break; case NetEvent.EType.Receive: - _netEventListener.OnNetworkReceive(evt.Peer, evt.DataReader, evt.ChannelNumber, evt.DeliveryMethod); + _netEventListener.OnNetworkReceive(netPeer, evt.DataReader, evt.ChannelNumber, evt.DeliveryMethod); break; case NetEvent.EType.ReceiveUnconnected: _netEventListener.OnNetworkReceiveUnconnected(evt.RemoteEndPoint, evt.DataReader, UnconnectedMessageType.BasicMessage); @@ -591,29 +172,29 @@ private void ProcessEvent(NetEvent evt) _netEventListener.OnNetworkError(evt.RemoteEndPoint, evt.ErrorCode); break; case NetEvent.EType.ConnectionLatencyUpdated: - _netEventListener.OnNetworkLatencyUpdate(evt.Peer, evt.Latency); + _netEventListener.OnNetworkLatencyUpdate(netPeer, evt.Latency); break; case NetEvent.EType.ConnectionRequest: - _netEventListener.OnConnectionRequest(evt.ConnectionRequest); + _netEventListener.OnConnectionRequest((ConnectionRequest)evt.ConnectionRequest); break; case NetEvent.EType.MessageDelivered: - _deliveryEventListener.OnMessageDelivered(evt.Peer, evt.UserData); + _netEventListener.OnMessageDelivered(netPeer, evt.UserData); break; case NetEvent.EType.PeerAddressChanged: _peersLock.EnterUpgradeableReadLock(); IPEndPoint previousAddress = null; - if (_peersDict.ContainsKey(evt.Peer.EndPoint)) + if (ContainsPeer(evt.Peer)) { _peersLock.EnterWriteLock(); - _peersDict.Remove(evt.Peer.EndPoint); - previousAddress = evt.Peer.EndPoint; + RemovePeerFromSet(evt.Peer); + previousAddress = new IPEndPoint(evt.Peer.Address, evt.Peer.Port); evt.Peer.FinishEndPointChange(evt.RemoteEndPoint); - _peersDict.Add(evt.Peer.EndPoint, evt.Peer); + AddPeerToSet(evt.Peer); _peersLock.ExitWriteLock(); } _peersLock.ExitUpgradeableReadLock(); - if(previousAddress != null) - _peerAddressChangedListener.OnPeerAddressChanged(evt.Peer, previousAddress); + if (previousAddress != null) + _netEventListener.OnPeerAddressChanged(netPeer, previousAddress); break; } //Recycle if not message @@ -623,561 +204,47 @@ private void ProcessEvent(NetEvent evt) evt.DataReader.RecycleInternal(); } - internal void RecycleEvent(NetEvent evt) - { - evt.Peer = null; - evt.ErrorCode = 0; - evt.RemoteEndPoint = null; - evt.ConnectionRequest = null; - lock(_eventLock) - { - evt.Next = _netEventPoolHead; - _netEventPoolHead = evt; - } - } - - //Update function - private void UpdateLogic() + protected override void ProcessNtpRequests(float elapsedMilliseconds) { - var peersToRemove = new List(); - var stopwatch = new Stopwatch(); - stopwatch.Start(); + if (_ntpRequests.IsEmpty) + return; - while (IsRunning) + List requestsToRemove = null; + foreach (var ntpRequest in _ntpRequests) { - try - { - ProcessDelayedPackets(); - int elapsed = (int)stopwatch.ElapsedMilliseconds; - elapsed = elapsed <= 0 ? 1 : elapsed; - stopwatch.Restart(); - - for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) - { - if (netPeer.ConnectionState == ConnectionState.Disconnected && - netPeer.TimeSinceLastPacket > DisconnectTimeout) - { - peersToRemove.Add(netPeer); - } - else - { - netPeer.Update(elapsed); - } - } - - if (peersToRemove.Count > 0) - { - _peersLock.EnterWriteLock(); - for (int i = 0; i < peersToRemove.Count; i++) - RemovePeerInternal(peersToRemove[i]); - _peersLock.ExitWriteLock(); - peersToRemove.Clear(); - } - - ProcessNtpRequests(elapsed); - - int sleepTime = UpdateTime - (int)stopwatch.ElapsedMilliseconds; - if (sleepTime > 0) - _updateTriggerEvent.WaitOne(sleepTime); - } - catch (ThreadAbortException) - { - return; - } - catch (Exception e) + ntpRequest.Value.Send(_udpSocketv4, elapsedMilliseconds); + if (ntpRequest.Value.NeedToKill) { - NetDebug.WriteError("[NM] LogicThread error: " + e); - } - } - stopwatch.Stop(); - } - - [Conditional("DEBUG")] - private void ProcessDelayedPackets() - { -#if DEBUG - if (!SimulateLatency) - return; - - var time = DateTime.UtcNow; - lock (_pingSimulationList) - { - for (int i = 0; i < _pingSimulationList.Count; i++) - { - var incomingData = _pingSimulationList[i]; - if (incomingData.TimeWhenGet <= time) - { - DebugMessageReceived(incomingData.Data, incomingData.EndPoint); - _pingSimulationList.RemoveAt(i); - i--; - } - } - } -#endif - } - - private void ProcessNtpRequests(int elapsedMilliseconds) - { - List requestsToRemove = null; - foreach (var ntpRequest in _ntpRequests) - { - ntpRequest.Value.Send(_udpSocketv4, elapsedMilliseconds); - if(ntpRequest.Value.NeedToKill) - { - if (requestsToRemove == null) - requestsToRemove = new List(); - requestsToRemove.Add(ntpRequest.Key); + if (requestsToRemove == null) + requestsToRemove = new List(); + requestsToRemove.Add(ntpRequest.Key); } } if (requestsToRemove != null) { foreach (var ipEndPoint in requestsToRemove) - { - _ntpRequests.Remove(ipEndPoint); - } - } - } - - /// - /// Update and send logic. Use this only when NetManager started in manual mode - /// - /// elapsed milliseconds since last update call - public void ManualUpdate(int elapsedMilliseconds) - { - if (!_manualMode) - return; - - for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) - { - if (netPeer.ConnectionState == ConnectionState.Disconnected && netPeer.TimeSinceLastPacket > DisconnectTimeout) - { - RemovePeerInternal(netPeer); - } - else - { - netPeer.Update(elapsedMilliseconds); - } - } - ProcessNtpRequests(elapsedMilliseconds); - } - - internal NetPeer OnConnectionSolved(ConnectionRequest request, byte[] rejectData, int start, int length) - { - NetPeer netPeer = null; - - if (request.Result == ConnectionRequestResult.RejectForce) - { - NetDebug.Write(NetLogLevel.Trace, "[NM] Peer connect reject force."); - if (rejectData != null && length > 0) - { - var shutdownPacket = PoolGetWithProperty(PacketProperty.Disconnect, length); - shutdownPacket.ConnectionNumber = request.InternalPacket.ConnectionNumber; - FastBitConverter.GetBytes(shutdownPacket.RawData, 1, request.InternalPacket.ConnectionTime); - if (shutdownPacket.Size >= NetConstants.PossibleMtu[0]) - NetDebug.WriteError("[Peer] Disconnect additional data size more than MTU!"); - else - Buffer.BlockCopy(rejectData, start, shutdownPacket.RawData, 9, length); - SendRawAndRecycle(shutdownPacket, request.RemoteEndPoint); - } - } - else - { - _peersLock.EnterUpgradeableReadLock(); - if (_peersDict.TryGetValue(request.RemoteEndPoint, out netPeer)) - { - //already have peer - _peersLock.ExitUpgradeableReadLock(); - } - else if (request.Result == ConnectionRequestResult.Reject) - { - netPeer = new NetPeer(this, request.RemoteEndPoint, GetNextPeerId()); - netPeer.Reject(request.InternalPacket, rejectData, start, length); - AddPeer(netPeer); - _peersLock.ExitUpgradeableReadLock(); - NetDebug.Write(NetLogLevel.Trace, "[NM] Peer connect reject."); - } - else //Accept - { - netPeer = new NetPeer(this, request, GetNextPeerId()); - AddPeer(netPeer); - _peersLock.ExitUpgradeableReadLock(); - CreateEvent(NetEvent.EType.Connect, netPeer); - NetDebug.Write(NetLogLevel.Trace, "[NM] Received peer connection Id: {0}, EP: {1}", - netPeer.ConnectTime, netPeer.EndPoint); - } - } - - lock(_requestsDict) - _requestsDict.Remove(request.RemoteEndPoint); - - return netPeer; - } - - private int GetNextPeerId() - { - return _peerIds.TryDequeue(out int id) ? id : _lastPeerId++; - } - - private void ProcessConnectRequest( - IPEndPoint remoteEndPoint, - NetPeer netPeer, - NetConnectRequestPacket connRequest) - { - //if we have peer - if (netPeer != null) - { - var processResult = netPeer.ProcessConnectRequest(connRequest); - NetDebug.Write("ConnectRequest LastId: {0}, NewId: {1}, EP: {2}, Result: {3}", - netPeer.ConnectTime, - connRequest.ConnectionTime, - remoteEndPoint, - processResult); - - switch (processResult) - { - case ConnectRequestResult.Reconnection: - DisconnectPeerForce(netPeer, DisconnectReason.Reconnect, 0, null); - RemovePeer(netPeer); - //go to new connection - break; - case ConnectRequestResult.NewConnection: - RemovePeer(netPeer); - //go to new connection - break; - case ConnectRequestResult.P2PLose: - DisconnectPeerForce(netPeer, DisconnectReason.PeerToPeerConnection, 0, null); - RemovePeer(netPeer); - //go to new connection - break; - default: - //no operations needed - return; - } - //ConnectRequestResult.NewConnection - //Set next connection number - if(processResult != ConnectRequestResult.P2PLose) - connRequest.ConnectionNumber = (byte)((netPeer.ConnectionNum + 1) % NetConstants.MaxConnectionNumber); - //To reconnect peer - } - else - { - NetDebug.Write("ConnectRequest Id: {0}, EP: {1}", connRequest.ConnectionTime, remoteEndPoint); - } - - ConnectionRequest req; - lock (_requestsDict) - { - if (_requestsDict.TryGetValue(remoteEndPoint, out req)) - { - req.UpdateRequest(connRequest); - return; - } - req = new ConnectionRequest(remoteEndPoint, connRequest, this); - _requestsDict.Add(remoteEndPoint, req); - } - NetDebug.Write("[NM] Creating request event: " + connRequest.ConnectionTime); - CreateEvent(NetEvent.EType.ConnectionRequest, connectionRequest: req); - } - - private void OnMessageReceived(NetPacket packet, IPEndPoint remoteEndPoint) - { -#if DEBUG - if (SimulatePacketLoss && _randomGenerator.NextDouble() * 100 < SimulationPacketLossChance) - { - //drop packet - return; - } - if (SimulateLatency) - { - int latency = _randomGenerator.Next(SimulationMinLatency, SimulationMaxLatency); - if (latency > MinLatencyThreshold) - { - lock (_pingSimulationList) - { - _pingSimulationList.Add(new IncomingData - { - Data = packet, - EndPoint = remoteEndPoint, - TimeWhenGet = DateTime.UtcNow.AddMilliseconds(latency) - }); - } - //hold packet - return; - } - } - - //ProcessEvents - DebugMessageReceived(packet, remoteEndPoint); - } - - private void DebugMessageReceived(NetPacket packet, IPEndPoint remoteEndPoint) - { -#endif - var originalPacketSize = packet.Size; - if (EnableStatistics) - { - Statistics.IncrementPacketsReceived(); - Statistics.AddBytesReceived(originalPacketSize); - } - - if (_ntpRequests.Count > 0) - { - if (_ntpRequests.TryGetValue(remoteEndPoint, out var request)) - { - if (packet.Size < 48) - { - NetDebug.Write(NetLogLevel.Trace, "NTP response too short: {}", packet.Size); - return; - } - - byte[] copiedData = new byte[packet.Size]; - Buffer.BlockCopy(packet.RawData, 0, copiedData, 0, packet.Size); - NtpPacket ntpPacket = NtpPacket.FromServerResponse(copiedData, DateTime.UtcNow); - try - { - ntpPacket.ValidateReply(); - } - catch (InvalidOperationException ex) - { - NetDebug.Write(NetLogLevel.Trace, "NTP response error: {}", ex.Message); - ntpPacket = null; - } - - if (ntpPacket != null) - { - _ntpRequests.Remove(remoteEndPoint); - _ntpEventListener?.OnNtpResponse(ntpPacket); - } - return; - } - } - - if (_extraPacketLayer != null) - { - int start = 0; - _extraPacketLayer.ProcessInboundPacket(ref remoteEndPoint, ref packet.RawData, ref start, ref packet.Size); - if (packet.Size == 0) - return; - } - - if (!packet.Verify()) - { - NetDebug.WriteError("[NM] DataReceived: bad!"); - PoolRecycle(packet); - return; - } - - switch (packet.Property) - { - //special case connect request - case PacketProperty.ConnectRequest: - if (NetConnectRequestPacket.GetProtocolId(packet) != NetConstants.ProtocolId) - { - SendRawAndRecycle(PoolGetWithProperty(PacketProperty.InvalidProtocol), remoteEndPoint); - return; - } - break; - //unconnected messages - case PacketProperty.Broadcast: - if (!BroadcastReceiveEnabled) - return; - CreateEvent(NetEvent.EType.Broadcast, remoteEndPoint: remoteEndPoint, readerSource: packet); - return; - case PacketProperty.UnconnectedMessage: - if (!UnconnectedMessagesEnabled) - return; - CreateEvent(NetEvent.EType.ReceiveUnconnected, remoteEndPoint: remoteEndPoint, readerSource: packet); - return; - case PacketProperty.NatMessage: - if (NatPunchEnabled) - NatPunchModule.ProcessMessage(remoteEndPoint, packet); - return; - } - - //Check normal packets - _peersLock.EnterReadLock(); - bool peerFound = _peersDict.TryGetValue(remoteEndPoint, out var netPeer); - _peersLock.ExitReadLock(); - - if (peerFound && EnableStatistics) - { - netPeer.Statistics.IncrementPacketsReceived(); - netPeer.Statistics.AddBytesReceived(originalPacketSize); - } - - switch (packet.Property) - { - case PacketProperty.ConnectRequest: - var connRequest = NetConnectRequestPacket.FromData(packet); - if (connRequest != null) - ProcessConnectRequest(remoteEndPoint, netPeer, connRequest); - break; - case PacketProperty.PeerNotFound: - if (peerFound) //local - { - if (netPeer.ConnectionState != ConnectionState.Connected) - return; - if (packet.Size == 1) - { - //first reply - //send NetworkChanged packet - netPeer.ResetMtu(); - SendRaw(NetConnectAcceptPacket.MakeNetworkChanged(netPeer), remoteEndPoint); - NetDebug.Write($"PeerNotFound sending connection info: {remoteEndPoint}"); - } - else if (packet.Size == 2 && packet.RawData[1] == 1) - { - //second reply - DisconnectPeerForce(netPeer, DisconnectReason.PeerNotFound, 0, null); - } - } - else if (packet.Size > 1) //remote - { - //check if this is old peer - bool isOldPeer = false; - - if (AllowPeerAddressChange) - { - NetDebug.Write($"[NM] Looks like address change: {packet.Size}"); - var remoteData = NetConnectAcceptPacket.FromData(packet); - if (remoteData != null && - remoteData.PeerNetworkChanged && - remoteData.PeerId < _peersArray.Length) - { - _peersLock.EnterUpgradeableReadLock(); - var peer = _peersArray[remoteData.PeerId]; - if (peer != null && - peer.ConnectTime == remoteData.ConnectionTime && - peer.ConnectionNum == remoteData.ConnectionNumber) - { - if (peer.ConnectionState == ConnectionState.Connected) - { - peer.InitiateEndPointChange(); - if (_peerAddressChangedListener != null) - { - CreateEvent(NetEvent.EType.PeerAddressChanged, peer, remoteEndPoint); - } - NetDebug.Write("[NM] PeerNotFound change address of remote peer"); - } - isOldPeer = true; - } - _peersLock.ExitUpgradeableReadLock(); - } - } - - PoolRecycle(packet); - - //else peer really not found - if (!isOldPeer) - { - var secondResponse = PoolGetWithProperty(PacketProperty.PeerNotFound, 1); - secondResponse.RawData[1] = 1; - SendRawAndRecycle(secondResponse, remoteEndPoint); - } - } - break; - case PacketProperty.InvalidProtocol: - if (peerFound && netPeer.ConnectionState == ConnectionState.Outgoing) - DisconnectPeerForce(netPeer, DisconnectReason.InvalidProtocol, 0, null); - break; - case PacketProperty.Disconnect: - if (peerFound) - { - var disconnectResult = netPeer.ProcessDisconnect(packet); - if (disconnectResult == DisconnectResult.None) - { - PoolRecycle(packet); - return; - } - DisconnectPeerForce( - netPeer, - disconnectResult == DisconnectResult.Disconnect - ? DisconnectReason.RemoteConnectionClose - : DisconnectReason.ConnectionRejected, - 0, packet); - } - else - { - PoolRecycle(packet); - } - //Send shutdown - SendRawAndRecycle(PoolGetWithProperty(PacketProperty.ShutdownOk), remoteEndPoint); - break; - case PacketProperty.ConnectAccept: - if (!peerFound) - return; - var connAccept = NetConnectAcceptPacket.FromData(packet); - if (connAccept != null && netPeer.ProcessConnectAccept(connAccept)) - CreateEvent(NetEvent.EType.Connect, netPeer); - break; - default: - if(peerFound) - netPeer.ProcessPacket(packet); - else - SendRawAndRecycle(PoolGetWithProperty(PacketProperty.PeerNotFound), remoteEndPoint); - break; - } - } - - internal void CreateReceiveEvent(NetPacket packet, DeliveryMethod method, byte channelNumber, int headerSize, NetPeer fromPeer) - { - NetEvent evt; - lock (_eventLock) - { - evt = _netEventPoolHead; - if (evt == null) - evt = new NetEvent(this); - else - _netEventPoolHead = evt.Next; - } - evt.Type = NetEvent.EType.Receive; - evt.DataReader.SetSource(packet, headerSize); - evt.Peer = fromPeer; - evt.DeliveryMethod = method; - evt.ChannelNumber = channelNumber; - if (UnsyncedEvents || UnsyncedReceiveEvent || _manualMode) - { - ProcessEvent(evt); - } - else - { - lock(_netEventsProduceQueue) - _netEventsProduceQueue.Enqueue(evt); + _ntpRequests.TryRemove(ipEndPoint, out _); } } /// - /// Send data to all connected peers (channel - 0) + /// Send data to all connected peers /// /// DataWriter with data + /// Number of channel (from 0 to channelsCount - 1) /// Send options (reliable, unreliable, etc.) - public void SendToAll(NetDataWriter writer, DeliveryMethod options) - { - SendToAll(writer.Data, 0, writer.Length, options); - } - - /// - /// Send data to all connected peers (channel - 0) - /// - /// Data - /// Send options (reliable, unreliable, etc.) - public void SendToAll(byte[] data, DeliveryMethod options) - { - SendToAll(data, 0, data.Length, options); - } + public void SendToAll(NetDataWriter writer, byte channelNumber, DeliveryMethod options) => + SendToAll(writer.Data, 0, writer.Length, channelNumber, options); /// - /// Send data to all connected peers (channel - 0) + /// Send data to all connected peers /// /// Data - /// Start of data - /// Length of data + /// Number of channel (from 0 to channelsCount - 1) /// Send options (reliable, unreliable, etc.) - public void SendToAll(byte[] data, int start, int length, DeliveryMethod options) - { - SendToAll(data, start, length, 0, options); - } + public void SendToAll(byte[] data, byte channelNumber, DeliveryMethod options) => + SendToAll(data, 0, data.Length, channelNumber, options); /// /// Send data to all connected peers @@ -1185,10 +252,9 @@ public void SendToAll(byte[] data, int start, int length, DeliveryMethod options /// DataWriter with data /// Number of channel (from 0 to channelsCount - 1) /// Send options (reliable, unreliable, etc.) - public void SendToAll(NetDataWriter writer, byte channelNumber, DeliveryMethod options) - { - SendToAll(writer.Data, 0, writer.Length, channelNumber, options); - } + /// Excluded peer + public void SendToAll(NetDataWriter writer, byte channelNumber, DeliveryMethod options, LiteNetPeer excludePeer) => + SendToAll(writer.Data, 0, writer.Length, channelNumber, options, excludePeer); /// /// Send data to all connected peers @@ -1196,10 +262,9 @@ public void SendToAll(NetDataWriter writer, byte channelNumber, DeliveryMethod o /// Data /// Number of channel (from 0 to channelsCount - 1) /// Send options (reliable, unreliable, etc.) - public void SendToAll(byte[] data, byte channelNumber, DeliveryMethod options) - { - SendToAll(data, 0, data.Length, channelNumber, options); - } + /// Excluded peer + public void SendToAll(byte[] data, byte channelNumber, DeliveryMethod options, LiteNetPeer excludePeer) => + SendToAll(data, 0, data.Length, channelNumber, options, excludePeer); /// /// Send data to all connected peers @@ -1209,13 +274,17 @@ public void SendToAll(byte[] data, byte channelNumber, DeliveryMethod options) /// Length of data /// Number of channel (from 0 to channelsCount - 1) /// Send options (reliable, unreliable, etc.) - public void SendToAll(byte[] data, int start, int length, byte channelNumber, DeliveryMethod options) + /// Excluded peer + public void SendToAll(byte[] data, int start, int length, byte channelNumber, DeliveryMethod options, LiteNetPeer excludePeer) { try { _peersLock.EnterReadLock(); for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) - netPeer.Send(data, start, length, channelNumber, options); + { + if (netPeer != excludePeer) + ((NetPeer)netPeer).Send(data, start, length, channelNumber, options); + } } finally { @@ -1224,50 +293,25 @@ public void SendToAll(byte[] data, int start, int length, byte channelNumber, De } /// - /// Send data to all connected peers (channel - 0) - /// - /// DataWriter with data - /// Send options (reliable, unreliable, etc.) - /// Excluded peer - public void SendToAll(NetDataWriter writer, DeliveryMethod options, NetPeer excludePeer) - { - SendToAll(writer.Data, 0, writer.Length, 0, options, excludePeer); - } - - /// - /// Send data to all connected peers (channel - 0) - /// - /// Data - /// Send options (reliable, unreliable, etc.) - /// Excluded peer - public void SendToAll(byte[] data, DeliveryMethod options, NetPeer excludePeer) - { - SendToAll(data, 0, data.Length, 0, options, excludePeer); - } - - /// - /// Send data to all connected peers (channel - 0) + /// Send data to all connected peers /// /// Data /// Start of data /// Length of data - /// Send options (reliable, unreliable, etc.) - /// Excluded peer - public void SendToAll(byte[] data, int start, int length, DeliveryMethod options, NetPeer excludePeer) - { - SendToAll(data, start, length, 0, options, excludePeer); - } - - /// - /// Send data to all connected peers - /// - /// DataWriter with data /// Number of channel (from 0 to channelsCount - 1) /// Send options (reliable, unreliable, etc.) - /// Excluded peer - public void SendToAll(NetDataWriter writer, byte channelNumber, DeliveryMethod options, NetPeer excludePeer) + public void SendToAll(byte[] data, int start, int length, byte channelNumber, DeliveryMethod options) { - SendToAll(writer.Data, 0, writer.Length, channelNumber, options, excludePeer); + try + { + _peersLock.EnterReadLock(); + for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) + ((NetPeer)netPeer).Send(data, start, length, channelNumber, options); + } + finally + { + _peersLock.ExitReadLock(); + } } /// @@ -1276,23 +320,17 @@ public void SendToAll(NetDataWriter writer, byte channelNumber, DeliveryMethod o /// Data /// Number of channel (from 0 to channelsCount - 1) /// Send options (reliable, unreliable, etc.) - /// Excluded peer - public void SendToAll(byte[] data, byte channelNumber, DeliveryMethod options, NetPeer excludePeer) - { - SendToAll(data, 0, data.Length, channelNumber, options, excludePeer); - } - + public void SendToAll(ReadOnlySpan data, byte channelNumber, DeliveryMethod options) => + SendToAll(data, channelNumber, options, null); /// /// Send data to all connected peers /// /// Data - /// Start of data - /// Length of data /// Number of channel (from 0 to channelsCount - 1) /// Send options (reliable, unreliable, etc.) /// Excluded peer - public void SendToAll(byte[] data, int start, int length, byte channelNumber, DeliveryMethod options, NetPeer excludePeer) + public void SendToAll(ReadOnlySpan data, byte channelNumber, DeliveryMethod options, LiteNetPeer excludePeer) { try { @@ -1300,7 +338,7 @@ public void SendToAll(byte[] data, int start, int length, byte channelNumber, De for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) { if (netPeer != excludePeer) - netPeer.Send(data, start, length, channelNumber, options); + ((NetPeer)netPeer).Send(data, channelNumber, options); } } finally @@ -1309,176 +347,6 @@ public void SendToAll(byte[] data, int start, int length, byte channelNumber, De } } - /// - /// Start logic thread and listening on available port - /// - public bool Start() - { - return Start(0); - } - - /// - /// Start logic thread and listening on selected port - /// - /// bind to specific ipv4 address - /// bind to specific ipv6 address - /// port to listen - public bool Start(IPAddress addressIPv4, IPAddress addressIPv6, int port) - { - return Start(addressIPv4, addressIPv6, port, false); - } - - /// - /// Start logic thread and listening on selected port - /// - /// bind to specific ipv4 address - /// bind to specific ipv6 address - /// port to listen - public bool Start(string addressIPv4, string addressIPv6, int port) - { - IPAddress ipv4 = NetUtils.ResolveAddress(addressIPv4); - IPAddress ipv6 = NetUtils.ResolveAddress(addressIPv6); - return Start(ipv4, ipv6, port); - } - - /// - /// Start logic thread and listening on selected port - /// - /// port to listen - public bool Start(int port) - { - return Start(IPAddress.Any, IPAddress.IPv6Any, port); - } - - /// - /// Start in manual mode and listening on selected port - /// In this mode you should use ManualReceive (without PollEvents) for receive packets - /// and ManualUpdate(...) for update and send packets - /// This mode useful mostly for single-threaded servers - /// - /// bind to specific ipv4 address - /// bind to specific ipv6 address - /// port to listen - public bool StartInManualMode(IPAddress addressIPv4, IPAddress addressIPv6, int port) - { - return Start(addressIPv4, addressIPv6, port, true); - } - - /// - /// Start in manual mode and listening on selected port - /// In this mode you should use ManualReceive (without PollEvents) for receive packets - /// and ManualUpdate(...) for update and send packets - /// This mode useful mostly for single-threaded servers - /// - /// bind to specific ipv4 address - /// bind to specific ipv6 address - /// port to listen - public bool StartInManualMode(string addressIPv4, string addressIPv6, int port) - { - IPAddress ipv4 = NetUtils.ResolveAddress(addressIPv4); - IPAddress ipv6 = NetUtils.ResolveAddress(addressIPv6); - return StartInManualMode(ipv4, ipv6, port); - } - - /// - /// Start in manual mode and listening on selected port - /// In this mode you should use ManualReceive (without PollEvents) for receive packets - /// and ManualUpdate(...) for update and send packets - /// This mode useful mostly for single-threaded servers - /// - /// port to listen - public bool StartInManualMode(int port) - { - return StartInManualMode(IPAddress.Any, IPAddress.IPv6Any, port); - } - - /// - /// Send message without connection - /// - /// Raw data - /// Packet destination - /// Operation result - public bool SendUnconnectedMessage(byte[] message, IPEndPoint remoteEndPoint) - { - return SendUnconnectedMessage(message, 0, message.Length, remoteEndPoint); - } - - /// - /// Send message without connection. WARNING This method allocates a new IPEndPoint object and - /// synchronously makes a DNS request. If you're calling this method every frame it will be - /// much faster to just cache the IPEndPoint. - /// - /// Data serializer - /// Packet destination IP or hostname - /// Packet destination port - /// Operation result - public bool SendUnconnectedMessage(NetDataWriter writer, string address, int port) - { - IPEndPoint remoteEndPoint = NetUtils.MakeEndPoint(address, port); - - return SendUnconnectedMessage(writer.Data, 0, writer.Length, remoteEndPoint); - } - - /// - /// Send message without connection - /// - /// Data serializer - /// Packet destination - /// Operation result - public bool SendUnconnectedMessage(NetDataWriter writer, IPEndPoint remoteEndPoint) - { - return SendUnconnectedMessage(writer.Data, 0, writer.Length, remoteEndPoint); - } - - /// - /// Send message without connection - /// - /// Raw data - /// data start - /// data length - /// Packet destination - /// Operation result - public bool SendUnconnectedMessage(byte[] message, int start, int length, IPEndPoint remoteEndPoint) - { - //No need for CRC here, SendRaw does that - NetPacket packet = PoolGetWithData(PacketProperty.UnconnectedMessage, message, start, length); - return SendRawAndRecycle(packet, remoteEndPoint) > 0; - } - - /// - /// Triggers update and send logic immediately (works asynchronously) - /// - public void TriggerUpdate() - { - _updateTriggerEvent.Set(); - } - - /// - /// Receive all pending events. Call this in game update code - /// In Manual mode it will call also socket Receive (which can be slow) - /// - public void PollEvents() - { - if (_manualMode) - { - if (_udpSocketv4 != null) - ManualReceive(_udpSocketv4, _bufferEndPointv4); - if (_udpSocketv6 != null && _udpSocketv6 != _udpSocketv4) - ManualReceive(_udpSocketv6, _bufferEndPointv6); - ProcessDelayedPackets(); - return; - } - if (UnsyncedEvents) - return; - lock (_netEventsProduceQueue) - { - (_netEventsConsumeQueue, _netEventsProduceQueue) = (_netEventsProduceQueue, _netEventsConsumeQueue); - } - - while(_netEventsConsumeQueue.Count > 0) - ProcessEvent(_netEventsConsumeQueue.Dequeue()); - } - /// /// Connect to remote host /// @@ -1486,11 +354,9 @@ public void PollEvents() /// Server Port /// Connection key /// New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting - /// Manager is not running. Call - public NetPeer Connect(string address, int port, string key) - { - return Connect(address, port, NetDataWriter.FromString(key)); - } + /// Manager is not running. Call + public new NetPeer Connect(string address, int port, string key) => + Connect(address, port, NetDataWriter.FromString(key)); /// /// Connect to remote host @@ -1499,21 +365,9 @@ public NetPeer Connect(string address, int port, string key) /// Server Port /// Additional data for remote peer /// New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting - /// Manager is not running. Call - public NetPeer Connect(string address, int port, NetDataWriter connectionData) - { - IPEndPoint ep; - try - { - ep = NetUtils.MakeEndPoint(address, port); - } - catch - { - CreateEvent(NetEvent.EType.Disconnect, disconnectReason: DisconnectReason.UnknownHost); - return null; - } - return Connect(ep, connectionData); - } + /// Manager is not running. Call + public new NetPeer Connect(string address, int port, NetDataWriter connectionData) => + (NetPeer)base.Connect(address, port, connectionData); /// /// Connect to remote host @@ -1521,11 +375,9 @@ public NetPeer Connect(string address, int port, NetDataWriter connectionData) /// Server end point (ip and port) /// Connection key /// New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting - /// Manager is not running. Call - public NetPeer Connect(IPEndPoint target, string key) - { - return Connect(target, NetDataWriter.FromString(key)); - } + /// Manager is not running. Call + public new NetPeer Connect(IPEndPoint target, string key) => + (NetPeer)base.Connect(target, key); /// /// Connect to remote host @@ -1533,261 +385,24 @@ public NetPeer Connect(IPEndPoint target, string key) /// Server end point (ip and port) /// Additional data for remote peer /// New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting - /// Manager is not running. Call - public NetPeer Connect(IPEndPoint target, NetDataWriter connectionData) - { - if (!IsRunning) - throw new InvalidOperationException("Client is not running"); - - lock(_requestsDict) - { - if (_requestsDict.ContainsKey(target)) - return null; - } - - byte connectionNumber = 0; - _peersLock.EnterUpgradeableReadLock(); - if (_peersDict.TryGetValue(target, out var peer)) - { - switch (peer.ConnectionState) - { - //just return already connected peer - case ConnectionState.Connected: - case ConnectionState.Outgoing: - _peersLock.ExitUpgradeableReadLock(); - return peer; - } - //else reconnect - connectionNumber = (byte)((peer.ConnectionNum + 1) % NetConstants.MaxConnectionNumber); - RemovePeer(peer); - } - - //Create reliable connection - //And send connection request - peer = new NetPeer(this, target, GetNextPeerId(), connectionNumber, connectionData); - AddPeer(peer); - _peersLock.ExitUpgradeableReadLock(); - - return peer; - } - - /// - /// Force closes connection and stop all threads. - /// - public void Stop() - { - Stop(true); - } - - /// - /// Force closes connection and stop all threads. - /// - /// Send disconnect messages - public void Stop(bool sendDisconnectMessages) - { - if (!IsRunning) - return; - NetDebug.Write("[NM] Stop"); - - //Send last disconnect - for(var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) - netPeer.Shutdown(null, 0, 0, !sendDisconnectMessages); - - //Stop - CloseSocket(false); - _updateTriggerEvent.Set(); - if (!_manualMode) - { - _logicThread.Join(); - _logicThread = null; - } - - //clear peers - _peersLock.EnterWriteLock(); - _headPeer = null; - _peersDict.Clear(); - _peersArray = new NetPeer[32]; - _peersLock.ExitWriteLock(); - _peerIds = new ConcurrentQueue(); - _lastPeerId = 0; -#if DEBUG - lock (_pingSimulationList) - _pingSimulationList.Clear(); -#endif - _connectedPeersCount = 0; - _netEventsProduceQueue.Clear(); - _netEventsConsumeQueue.Clear(); - } - - /// - /// Return peers count with connection state - /// - /// peer connection state (you can use as bit flags) - /// peers count - public int GetPeersCount(ConnectionState peerState) - { - int count = 0; - _peersLock.EnterReadLock(); - for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) - { - if ((netPeer.ConnectionState & peerState) != 0) - count++; - } - _peersLock.ExitReadLock(); - return count; - } - - /// - /// Get copy of peers (without allocations) - /// - /// List that will contain result - /// State of peers - public void GetPeersNonAlloc(List peers, ConnectionState peerState) - { - peers.Clear(); - _peersLock.EnterReadLock(); - for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) - { - if ((netPeer.ConnectionState & peerState) != 0) - peers.Add(netPeer); - } - _peersLock.ExitReadLock(); - } - - /// - /// Disconnect all peers without any additional data - /// - public void DisconnectAll() - { - DisconnectAll(null, 0, 0); - } - - /// - /// Disconnect all peers with shutdown message - /// - /// Data to send (must be less or equal MTU) - /// Data start - /// Data count - public void DisconnectAll(byte[] data, int start, int count) - { - //Send disconnect packets - _peersLock.EnterReadLock(); - for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) - { - DisconnectPeer( - netPeer, - DisconnectReason.DisconnectPeerCalled, - 0, - false, - data, - start, - count, - null); - } - _peersLock.ExitReadLock(); - } - - /// - /// Immediately disconnect peer from server without additional data - /// - /// peer to disconnect - public void DisconnectPeerForce(NetPeer peer) - { - DisconnectPeerForce(peer, DisconnectReason.DisconnectPeerCalled, 0, null); - } - - /// - /// Disconnect peer from server - /// - /// peer to disconnect - public void DisconnectPeer(NetPeer peer) - { - DisconnectPeer(peer, null, 0, 0); - } - - /// - /// Disconnect peer from server and send additional data (Size must be less or equal MTU - 8) - /// - /// peer to disconnect - /// additional data - public void DisconnectPeer(NetPeer peer, byte[] data) - { - DisconnectPeer(peer, data, 0, data.Length); - } - - /// - /// Disconnect peer from server and send additional data (Size must be less or equal MTU - 8) - /// - /// peer to disconnect - /// additional data - public void DisconnectPeer(NetPeer peer, NetDataWriter writer) - { - DisconnectPeer(peer, writer.Data, 0, writer.Length); - } - - /// - /// Disconnect peer from server and send additional data (Size must be less or equal MTU - 8) - /// - /// peer to disconnect - /// additional data - /// data start - /// data length - public void DisconnectPeer(NetPeer peer, byte[] data, int start, int count) - { - DisconnectPeer( - peer, - DisconnectReason.DisconnectPeerCalled, - 0, - false, - data, - start, - count, - null); - } - - /// - /// Create the requests for NTP server - /// - /// NTP Server address. - public void CreateNtpRequest(IPEndPoint endPoint) - { - _ntpRequests.Add(endPoint, new NtpRequest(endPoint)); - } - - /// - /// Create the requests for NTP server - /// - /// NTP Server address. - /// port - public void CreateNtpRequest(string ntpServerAddress, int port) - { - IPEndPoint endPoint = NetUtils.MakeEndPoint(ntpServerAddress, port); - _ntpRequests.Add(endPoint, new NtpRequest(endPoint)); - } + /// Manager is not running. Call + public new NetPeer Connect(IPEndPoint target, NetDataWriter connectionData) => + (NetPeer)base.Connect(target, connectionData); /// - /// Create the requests for NTP server (default port) + /// Connect to remote host /// - /// NTP Server address. - public void CreateNtpRequest(string ntpServerAddress) - { - IPEndPoint endPoint = NetUtils.MakeEndPoint(ntpServerAddress, NtpRequest.DefaultPort); - _ntpRequests.Add(endPoint, new NtpRequest(endPoint)); - } - - public NetPeerEnumerator GetEnumerator() - { - return new NetPeerEnumerator(_headPeer); - } + /// Server end point (ip and port) + /// Additional data for remote peer + /// New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting + /// Manager is not running. Call + public new NetPeer Connect(IPEndPoint target, ReadOnlySpan connectionData) => + (NetPeer)base.Connect(target, connectionData); - IEnumerator IEnumerable.GetEnumerator() - { - return new NetPeerEnumerator(_headPeer); - } + public new NetPeerEnumerator GetEnumerator() => + new NetPeerEnumerator((NetPeer)_headPeer); - IEnumerator IEnumerable.GetEnumerator() - { - return new NetPeerEnumerator(_headPeer); - } + IEnumerator IEnumerable.GetEnumerator() => + new NetPeerEnumerator((NetPeer)_headPeer); } } diff --git a/LiteNetLib/NetPacket.cs b/LiteNetLib/NetPacket.cs index 7e29fe96..d0ecdd6a 100644 --- a/LiteNetLib/NetPacket.cs +++ b/LiteNetLib/NetPacket.cs @@ -1,5 +1,4 @@ using System; -using System.Net; using LiteNetLib.Utils; namespace LiteNetLib @@ -8,6 +7,7 @@ internal enum PacketProperty : byte { Unreliable, Channeled, + ReliableMerged, Ack, Ping, Pong, @@ -23,12 +23,13 @@ internal enum PacketProperty : byte PeerNotFound, InvalidProtocol, NatMessage, - Empty + Empty, + Total } internal sealed class NetPacket { - private static readonly int PropertiesCount = Enum.GetValues(typeof(PacketProperty)).Length; + private static readonly int PropertiesCount = (int)PacketProperty.Total; private static readonly int[] HeaderSizes; static NetPacket() @@ -40,6 +41,7 @@ static NetPacket() { case PacketProperty.Channeled: case PacketProperty.Ack: + case PacketProperty.ReliableMerged: HeaderSizes[i] = NetConstants.ChanneledHeaderSize; break; case PacketProperty.Ping: @@ -64,90 +66,148 @@ static NetPacket() } } - //Header + /// + /// Gets or sets the packet property (type). + /// Stored in the first 5 bits of the first byte (0x1F mask). + /// public PacketProperty Property { get => (PacketProperty)(RawData[0] & 0x1F); set => RawData[0] = (byte)((RawData[0] & 0xE0) | (byte)value); } + /// + /// Gets or sets the connection number used to distinguish between multiple connection instances from the same endpoint. + /// Stored in bits 6 and 7 of the first byte (0x60 mask). + /// + /// + /// Used to discard packets from previous connections made in the same frame/time. + /// public byte ConnectionNumber { get => (byte)((RawData[0] & 0x60) >> 5); - set => RawData[0] = (byte) ((RawData[0] & 0x9F) | (value << 5)); + set => RawData[0] = (byte)((RawData[0] & 0x9F) | (value << 5)); } + /// + /// Gets or sets the sequence number of the packet. + /// Located at offset 1 in . + /// public ushort Sequence { get => BitConverter.ToUInt16(RawData, 1); set => FastBitConverter.GetBytes(RawData, 1, value); } + /// + /// Returns if the fragmentation bit (the highest bit of the first byte) is set. + /// public bool IsFragmented => (RawData[0] & 0x80) != 0; - public void MarkFragmented() - { - RawData[0] |= 0x80; //set first bit - } + /// + /// Sets the fragmentation bit (0x80) in the packet header. + /// + public void MarkFragmented() => RawData[0] |= 0x80; + /// + /// Gets or sets the channel identifier. + /// Located at offset 3 in . + /// public byte ChannelId { get => RawData[3]; set => RawData[3] = value; } + /// + /// Gets or sets the unique identifier for a fragmented message. + /// Located at offset 4 in . + /// public ushort FragmentId { get => BitConverter.ToUInt16(RawData, 4); set => FastBitConverter.GetBytes(RawData, 4, value); } + /// + /// Gets or sets the index of the current fragment part. + /// Located at offset 6 in . + /// public ushort FragmentPart { get => BitConverter.ToUInt16(RawData, 6); set => FastBitConverter.GetBytes(RawData, 6, value); } + /// + /// Gets or sets the total number of fragments in the message. + /// Located at offset 8 in . + /// public ushort FragmentsTotal { get => BitConverter.ToUInt16(RawData, 8); set => FastBitConverter.GetBytes(RawData, 8, value); } - //Data + /// + /// The raw array containing the packet header and payload. + /// public byte[] RawData; + + /// + /// The actual size of the data in , including headers. + /// public int Size; - //Delivery + /// + /// Custom user data associated with this packet. Used for delivery notifications. + /// public object UserData; - //Pool node + /// + /// Reference to the next packet in the NetPacketPool. + /// public NetPacket Next; + /// + /// Initializes a new instance of the class with a specific buffer size. + /// + /// Total size of the packet including headers. public NetPacket(int size) { RawData = new byte[size]; Size = size; } - public NetPacket(PacketProperty property, int size) + /// + /// Initializes a new instance of the class, calculating the required size based on the property. + /// + /// The type of packet to create. + /// Size of the user data payload. + public NetPacket(PacketProperty property, int payloadSize) { - size += GetHeaderSize(property); - RawData = new byte[size]; + int totalSize = payloadSize + GetHeaderSize(property); + RawData = new byte[totalSize]; Property = property; - Size = size; - } - - public static int GetHeaderSize(PacketProperty property) - { - return HeaderSizes[(int)property]; - } - - public int GetHeaderSize() - { - return HeaderSizes[RawData[0] & 0x1F]; + Size = totalSize; } + /// + /// Gets the fixed header size for a specific . + /// + /// The packet type. + /// Header size in bytes. + public static int GetHeaderSize(PacketProperty property) => HeaderSizes[(int)property]; + + /// + /// Gets the header size of the current packet based on its property bits. + /// + public int HeaderSize => HeaderSizes[RawData[0] & 0x1F]; + + /// + /// Performs a basic check on the packet header and size. + /// + /// if the packet property is valid and the size is sufficient for the headers. public bool Verify() { byte property = (byte)(RawData[0] & 0x1F); diff --git a/LiteNetLib/NetPacketReader.cs b/LiteNetLib/NetPacketReader.cs new file mode 100644 index 00000000..2098dde2 --- /dev/null +++ b/LiteNetLib/NetPacketReader.cs @@ -0,0 +1,44 @@ +using LiteNetLib.Utils; + +namespace LiteNetLib +{ + public sealed class NetPacketReader : NetDataReader + { + private NetPacket _packet; + private readonly LiteNetManager _manager; + private readonly NetEvent _evt; + + internal NetPacketReader(LiteNetManager manager, NetEvent evt) + { + _manager = manager; + _evt = evt; + } + + internal void SetSource(NetPacket packet, int headerSize) + { + if (packet == null) + return; + _packet = packet; + _data = packet.RawData; + _position = headerSize; + _offset = headerSize; + _dataSize = packet.Size; + } + + internal void RecycleInternal() + { + Clear(); + if (_packet != null) + _manager.PoolRecycle(_packet); + _packet = null; + _manager.RecycleEvent(_evt); + } + + public void Recycle() + { + if (_manager.AutoRecycle) + return; + RecycleInternal(); + } + } +} diff --git a/LiteNetLib/NetPeer.cs b/LiteNetLib/NetPeer.cs index 922c720e..4b320a6d 100644 --- a/LiteNetLib/NetPeer.cs +++ b/LiteNetLib/NetPeer.cs @@ -1,10 +1,5 @@ -#if DEBUG -#define STATS_ENABLED -#endif -using System; +using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; using System.Net; using System.Threading; using LiteNetLib.Utils; @@ -12,414 +7,43 @@ namespace LiteNetLib { /// - /// Peer connection state + /// Improved LiteNetPeer with full multi-channel support /// - [Flags] - public enum ConnectionState : byte + public class NetPeer : LiteNetPeer { - Outgoing = 1 << 1, - Connected = 1 << 2, - ShutdownRequested = 1 << 3, - Disconnected = 1 << 4, - EndPointChange = 1 << 5, - Any = Outgoing | Connected | ShutdownRequested | EndPointChange - } - - internal enum ConnectRequestResult - { - None, - P2PLose, //when peer connecting - Reconnection, //when peer was connected - NewConnection //when peer was disconnected - } - - internal enum DisconnectResult - { - None, - Reject, - Disconnect - } - - internal enum ShutdownResult - { - None, - Success, - WasConnected - } - - /// - /// Network peer. Main purpose is sending messages to specific peer. - /// - public class NetPeer - { - //Ping and RTT - private int _rtt; - private int _avgRtt; - private int _rttCount; - private double _resendDelay = 27.0; - private int _pingSendTimer; - private int _rttResetTimer; - private readonly Stopwatch _pingTimer = new Stopwatch(); - private int _timeSinceLastPacket; - private long _remoteDelta; - - //Common - private readonly object _shutdownLock = new object(); - - internal volatile NetPeer NextPeer; - internal NetPeer PrevPeer; - - internal byte ConnectionNum - { - get => _connectNum; - private set - { - _connectNum = value; - _mergeData.ConnectionNumber = value; - _pingPacket.ConnectionNumber = value; - _pongPacket.ConnectionNumber = value; - } - } - - //Channels - private readonly Queue _unreliableChannel; - private readonly ConcurrentQueue _channelSendQueue; + private readonly ConcurrentQueue _channelSendQueue = new ConcurrentQueue(); private readonly BaseChannel[] _channels; - //MTU - private int _mtu; - private int _mtuIdx; - private bool _finishMtu; - private int _mtuCheckTimer; - private int _mtuCheckAttempts; - private const int MtuCheckDelay = 1000; - private const int MaxMtuCheckAttempts = 4; - private readonly object _mtuMutex = new object(); - - //Fragment - private class IncomingFragments - { - public NetPacket[] Fragments; - public int ReceivedCount; - public int TotalSize; - public byte ChannelId; - } - private int _fragmentId; - private readonly Dictionary _holdedFragments; - private readonly Dictionary _deliveredFragments; - - //Merging - private readonly NetPacket _mergeData; - private int _mergePos; - private int _mergeCount; - - //Connection - private IPEndPoint _remoteEndPoint; - private int _connectAttempts; - private int _connectTimer; - private long _connectTime; - private byte _connectNum; - private ConnectionState _connectionState; - private NetPacket _shutdownPacket; - private const int ShutdownDelay = 300; - private int _shutdownTimer; - private readonly NetPacket _pingPacket; - private readonly NetPacket _pongPacket; - private readonly NetPacket _connectRequestPacket; - private readonly NetPacket _connectAcceptPacket; - - /// - /// Peer ip address and port - /// - public IPEndPoint EndPoint => _remoteEndPoint; - - /// - /// Peer parent NetManager - /// - public readonly NetManager NetManager; - - /// - /// Current connection state - /// - public ConnectionState ConnectionState => _connectionState; - - /// - /// Connection time for internal purposes - /// - internal long ConnectTime => _connectTime; - - /// - /// Peer id can be used as key in your dictionary of peers - /// - public readonly int Id; - - /// - /// Id assigned from server - /// - public int RemoteId { get; private set; } - - /// - /// Current one-way ping (RTT/2) in milliseconds - /// - public int Ping => _avgRtt/2; - - /// - /// Round trip time in milliseconds - /// - public int RoundTripTime => _avgRtt; - - /// - /// Current MTU - Maximum Transfer Unit ( maximum udp packet size without fragmentation ) - /// - public int Mtu => _mtu; - - /// - /// Delta with remote time in ticks (not accurate) - /// positive - remote time > our time - /// - public long RemoteTimeDelta => _remoteDelta; - - /// - /// Remote UTC time (not accurate) - /// - public DateTime RemoteUtcTime => new DateTime(DateTime.UtcNow.Ticks + _remoteDelta); - - /// - /// Time since last packet received (including internal library packets) - /// - public int TimeSinceLastPacket => _timeSinceLastPacket; - - internal double ResendDelay => _resendDelay; + protected override int ChannelsCount => ((NetManager)NetManager).ChannelsCount; - /// - /// Application defined object containing data about the connection - /// - public object Tag; - - /// - /// Statistics of peer connection - /// - public readonly NetStatistics Statistics; - - //incoming connection constructor - internal NetPeer(NetManager netManager, IPEndPoint remoteEndPoint, int id) - { - Id = id; - Statistics = new NetStatistics(); - NetManager = netManager; - ResetMtu(); - _remoteEndPoint = remoteEndPoint; - _connectionState = ConnectionState.Connected; - _mergeData = new NetPacket(PacketProperty.Merged, NetConstants.MaxPacketSize); - _pongPacket = new NetPacket(PacketProperty.Pong, 0); - _pingPacket = new NetPacket(PacketProperty.Ping, 0) {Sequence = 1}; - - _unreliableChannel = new Queue(); - _holdedFragments = new Dictionary(); - _deliveredFragments = new Dictionary(); - - _channels = new BaseChannel[netManager.ChannelsCount * NetConstants.ChannelTypeCount]; - _channelSendQueue = new ConcurrentQueue(); - } - - internal void InitiateEndPointChange() - { - ResetMtu(); - _connectionState = ConnectionState.EndPointChange; - } - - internal void FinishEndPointChange(IPEndPoint newEndPoint) - { - if (_connectionState != ConnectionState.EndPointChange) - return; - _connectionState = ConnectionState.Connected; - _remoteEndPoint = newEndPoint; - } - - internal void ResetMtu() + internal NetPeer(NetManager netManager, IPEndPoint remoteEndPoint, int id) : base(netManager, remoteEndPoint, id) { - _finishMtu = false; - if (NetManager.MtuOverride > 0) - OverrideMtu(NetManager.MtuOverride); - else if (NetManager.UseSafeMtu) - SetMtu(0); - else - SetMtu(1); - } - private void SetMtu(int mtuIdx) - { - _mtuIdx = mtuIdx; - _mtu = NetConstants.PossibleMtu[mtuIdx] - NetManager.ExtraPacketSizeForLayer; } - private void OverrideMtu(int mtuValue) + internal NetPeer(NetManager netManager, IPEndPoint remoteEndPoint, int id, byte connectNum, ReadOnlySpan connectData) : base(netManager, remoteEndPoint, id, connectNum, connectData) { - _mtu = mtuValue; - _finishMtu = true; + _channels = new BaseChannel[netManager.ChannelsCount * NetConstants.ChannelTypeCount]; } - /// - /// Returns packets count in queue for reliable channel - /// - /// number of channel 0-63 - /// type of channel ReliableOrdered or ReliableUnordered - /// packets count in channel queue - public int GetPacketsCountInReliableQueue(byte channelNumber, bool ordered) + internal NetPeer(NetManager netManager, LiteConnectionRequest request, int id) : base(netManager, request, id) { - int idx = channelNumber * NetConstants.ChannelTypeCount + - (byte) (ordered ? DeliveryMethod.ReliableOrdered : DeliveryMethod.ReliableUnordered); - var channel = _channels[idx]; - return channel != null ? ((ReliableChannel)channel).PacketsInQueue : 0; + _channels = new BaseChannel[netManager.ChannelsCount * NetConstants.ChannelTypeCount]; } /// - /// Create temporary packet (maximum size MTU - headerSize) to send later without additional copies + /// Send data to peer /// - /// Delivery method (reliable, unreliable, etc.) + /// DataWriter with data /// Number of channel (from 0 to channelsCount - 1) - /// PooledPacket that you can use to write data starting from UserDataOffset - public PooledPacket CreatePacketFromPool(DeliveryMethod deliveryMethod, byte channelNumber) - { - //multithreaded variable - int mtu = _mtu; - var packet = NetManager.PoolGetPacket(mtu); - if (deliveryMethod == DeliveryMethod.Unreliable) - { - packet.Property = PacketProperty.Unreliable; - return new PooledPacket(packet, mtu, 0); - } - else - { - packet.Property = PacketProperty.Channeled; - return new PooledPacket(packet, mtu, (byte)(channelNumber * NetConstants.ChannelTypeCount + (byte)deliveryMethod)); - } - } - - /// - /// Sends pooled packet without data copy - /// - /// packet to send - /// size of user data you want to send - public void SendPooledPacket(PooledPacket packet, int userDataSize) - { - if (_connectionState != ConnectionState.Connected) - return; - packet._packet.Size = packet.UserDataOffset + userDataSize; - if (packet._packet.Property == PacketProperty.Channeled) - { - CreateChannel(packet._channelNumber).AddToQueue(packet._packet); - } - else - { - lock(_unreliableChannel) - _unreliableChannel.Enqueue(packet._packet); - } - } - - private BaseChannel CreateChannel(byte idx) - { - BaseChannel newChannel = _channels[idx]; - if (newChannel != null) - return newChannel; - switch ((DeliveryMethod)(idx % NetConstants.ChannelTypeCount)) - { - case DeliveryMethod.ReliableUnordered: - newChannel = new ReliableChannel(this, false, idx); - break; - case DeliveryMethod.Sequenced: - newChannel = new SequencedChannel(this, false, idx); - break; - case DeliveryMethod.ReliableOrdered: - newChannel = new ReliableChannel(this, true, idx); - break; - case DeliveryMethod.ReliableSequenced: - newChannel = new SequencedChannel(this, true, idx); - break; - } - BaseChannel prevChannel = Interlocked.CompareExchange(ref _channels[idx], newChannel, null); - if (prevChannel != null) - return prevChannel; - - return newChannel; - } - - //"Connect to" constructor - internal NetPeer(NetManager netManager, IPEndPoint remoteEndPoint, int id, byte connectNum, NetDataWriter connectData) - : this(netManager, remoteEndPoint, id) - { - _connectTime = DateTime.UtcNow.Ticks; - _connectionState = ConnectionState.Outgoing; - ConnectionNum = connectNum; - - //Make initial packet - _connectRequestPacket = NetConnectRequestPacket.Make(connectData, remoteEndPoint.Serialize(), _connectTime, id); - _connectRequestPacket.ConnectionNumber = connectNum; - - //Send request - NetManager.SendRaw(_connectRequestPacket, _remoteEndPoint); - - NetDebug.Write(NetLogLevel.Trace, $"[CC] ConnectId: {_connectTime}, ConnectNum: {connectNum}"); - } - - //"Accept" incoming constructor - internal NetPeer(NetManager netManager, ConnectionRequest request, int id) - : this(netManager, request.RemoteEndPoint, id) - { - _connectTime = request.InternalPacket.ConnectionTime; - ConnectionNum = request.InternalPacket.ConnectionNumber; - RemoteId = request.InternalPacket.PeerId; - - //Make initial packet - _connectAcceptPacket = NetConnectAcceptPacket.Make(_connectTime, ConnectionNum, id); - - //Make Connected - _connectionState = ConnectionState.Connected; - - //Send - NetManager.SendRaw(_connectAcceptPacket, _remoteEndPoint); - - NetDebug.Write(NetLogLevel.Trace, $"[CC] ConnectId: {_connectTime}"); - } - - //Reject - internal void Reject(NetConnectRequestPacket requestData, byte[] data, int start, int length) - { - _connectTime = requestData.ConnectionTime; - _connectNum = requestData.ConnectionNumber; - Shutdown(data, start, length, false); - } - - internal bool ProcessConnectAccept(NetConnectAcceptPacket packet) - { - if (_connectionState != ConnectionState.Outgoing) - return false; - - //check connection id - if (packet.ConnectionTime != _connectTime) - { - NetDebug.Write(NetLogLevel.Trace, $"[NC] Invalid connectId: {packet.ConnectionTime} != our({_connectTime})"); - return false; - } - //check connect num - ConnectionNum = packet.ConnectionNumber; - RemoteId = packet.PeerId; - - NetDebug.Write(NetLogLevel.Trace, "[NC] Received connection accept"); - Interlocked.Exchange(ref _timeSinceLastPacket, 0); - _connectionState = ConnectionState.Connected; - return true; - } - - /// - /// Gets maximum size of packet that will be not fragmented. - /// - /// Type of packet that you want send - /// size in bytes - public int GetMaxSinglePacketSize(DeliveryMethod options) - { - return _mtu - NetPacket.GetHeaderSize(options == DeliveryMethod.Unreliable ? PacketProperty.Unreliable : PacketProperty.Channeled); - } + /// Send options (reliable, unreliable, etc.) + /// + /// If size exceeds maximum limit: + /// MTU - headerSize bytes for Unreliable + /// Fragment count exceeded ushort.MaxValue + /// + public void Send(NetDataWriter dataWriter, byte channelNumber, DeliveryMethod deliveryMethod) => + SendInternal(dataWriter.AsReadOnlySpan(), channelNumber, deliveryMethod, null); /// /// Send data to peer with delivery event called @@ -435,7 +59,7 @@ public void SendWithDeliveryEvent(byte[] data, byte channelNumber, DeliveryMetho { if (deliveryMethod != DeliveryMethod.ReliableOrdered && deliveryMethod != DeliveryMethod.ReliableUnordered) throw new ArgumentException("Delivery event will work only for ReliableOrdered/Unordered packets"); - SendInternal(data, 0, data.Length, channelNumber, deliveryMethod, userData); + SendInternal(new ReadOnlySpan(data, 0, data.Length), channelNumber, deliveryMethod, userData); } /// @@ -454,7 +78,7 @@ public void SendWithDeliveryEvent(byte[] data, int start, int length, byte chann { if (deliveryMethod != DeliveryMethod.ReliableOrdered && deliveryMethod != DeliveryMethod.ReliableUnordered) throw new ArgumentException("Delivery event will work only for ReliableOrdered/Unordered packets"); - SendInternal(data, start, length, channelNumber, deliveryMethod, userData); + SendInternal(new ReadOnlySpan(data, start, length), channelNumber, deliveryMethod, userData); } /// @@ -471,54 +95,47 @@ public void SendWithDeliveryEvent(NetDataWriter dataWriter, byte channelNumber, { if (deliveryMethod != DeliveryMethod.ReliableOrdered && deliveryMethod != DeliveryMethod.ReliableUnordered) throw new ArgumentException("Delivery event will work only for ReliableOrdered/Unordered packets"); - SendInternal(dataWriter.Data, 0, dataWriter.Length, channelNumber, deliveryMethod, userData); + SendInternal(dataWriter.AsReadOnlySpan(), channelNumber, deliveryMethod, userData); } /// - /// Send data to peer (channel - 0) + /// Send data to peer with delivery event called /// /// Data - /// Send options (reliable, unreliable, etc.) - /// - /// If size exceeds maximum limit: - /// MTU - headerSize bytes for Unreliable - /// Fragment count exceeded ushort.MaxValue - /// - public void Send(byte[] data, DeliveryMethod deliveryMethod) - { - SendInternal(data, 0, data.Length, 0, deliveryMethod, null); - } - - /// - /// Send data to peer (channel - 0) - /// - /// DataWriter with data - /// Send options (reliable, unreliable, etc.) - /// - /// If size exceeds maximum limit: - /// MTU - headerSize bytes for Unreliable - /// Fragment count exceeded ushort.MaxValue + /// Number of channel (from 0 to channelsCount - 1) + /// Delivery method (reliable, unreliable, etc.) + /// User data that will be received in DeliveryEvent + /// + /// If you trying to send unreliable packet type /// - public void Send(NetDataWriter dataWriter, DeliveryMethod deliveryMethod) + public void SendWithDeliveryEvent(ReadOnlySpan data, byte channelNumber, DeliveryMethod deliveryMethod, object userData) { - SendInternal(dataWriter.Data, 0, dataWriter.Length, 0, deliveryMethod, null); + if (deliveryMethod != DeliveryMethod.ReliableOrdered && deliveryMethod != DeliveryMethod.ReliableUnordered) + throw new ArgumentException("Delivery event will work only for ReliableOrdered/Unordered packets"); + SendInternal(data, channelNumber, deliveryMethod, userData); } /// - /// Send data to peer (channel - 0) + /// Create temporary packet (maximum size MTU - headerSize) to send later without additional copies /// - /// Data - /// Start of data - /// Length of data - /// Send options (reliable, unreliable, etc.) - /// - /// If size exceeds maximum limit: - /// MTU - headerSize bytes for Unreliable - /// Fragment count exceeded ushort.MaxValue - /// - public void Send(byte[] data, int start, int length, DeliveryMethod options) + /// Delivery method (reliable, unreliable, etc.) + /// Number of channel (from 0 to channelsCount - 1) + /// PooledPacket that you can use to write data starting from UserDataOffset + public PooledPacket CreatePacketFromPool(DeliveryMethod deliveryMethod, byte channelNumber) { - SendInternal(data, start, length, 0, options, null); + //multithreaded variable + int mtu = Mtu; + var packet = NetManager.PoolGetPacket(mtu); + if (deliveryMethod == DeliveryMethod.Unreliable) + { + packet.Property = PacketProperty.Unreliable; + return new PooledPacket(packet, mtu, 0); + } + else + { + packet.Property = PacketProperty.Channeled; + return new PooledPacket(packet, mtu, (byte)(channelNumber * NetConstants.ChannelTypeCount + (byte)deliveryMethod)); + } } /// @@ -532,15 +149,13 @@ public void Send(byte[] data, int start, int length, DeliveryMethod options) /// MTU - headerSize bytes for Unreliable /// Fragment count exceeded ushort.MaxValue /// - public void Send(byte[] data, byte channelNumber, DeliveryMethod deliveryMethod) - { - SendInternal(data, 0, data.Length, channelNumber, deliveryMethod, null); - } + public void Send(byte[] data, byte channelNumber, DeliveryMethod deliveryMethod) => + SendInternal(new ReadOnlySpan(data, 0, data.Length), channelNumber, deliveryMethod, null); /// /// Send data to peer /// - /// DataWriter with data + /// Data /// Number of channel (from 0 to channelsCount - 1) /// Send options (reliable, unreliable, etc.) /// @@ -548,10 +163,8 @@ public void Send(byte[] data, byte channelNumber, DeliveryMethod deliveryMethod) /// MTU - headerSize bytes for Unreliable /// Fragment count exceeded ushort.MaxValue /// - public void Send(NetDataWriter dataWriter, byte channelNumber, DeliveryMethod deliveryMethod) - { - SendInternal(dataWriter.Data, 0, dataWriter.Length, channelNumber, deliveryMethod, null); - } + public void Send(ReadOnlySpan data, byte channelNumber, DeliveryMethod deliveryMethod) => + SendInternal(data, channelNumber, deliveryMethod, null); /// /// Send data to peer @@ -566,840 +179,91 @@ public void Send(NetDataWriter dataWriter, byte channelNumber, DeliveryMethod de /// MTU - headerSize bytes for Unreliable /// Fragment count exceeded ushort.MaxValue /// - public void Send(byte[] data, int start, int length, byte channelNumber, DeliveryMethod deliveryMethod) - { - SendInternal(data, start, length, channelNumber, deliveryMethod, null); - } + public void Send(byte[] data, int start, int length, byte channelNumber, DeliveryMethod deliveryMethod) => + SendInternal(new ReadOnlySpan(data, start, length), channelNumber, deliveryMethod, null); - private void SendInternal( - byte[] data, - int start, - int length, - byte channelNumber, - DeliveryMethod deliveryMethod, - object userData) + /// + /// Returns packets count in queue for reliable channel + /// + /// number of channel 0-63 + /// type of channel ReliableOrdered or ReliableUnordered + /// packets count in channel queue + public int GetPacketsCountInReliableQueue(byte channelNumber, bool ordered) { - if (_connectionState != ConnectionState.Connected || channelNumber >= _channels.Length) - return; + int idx = channelNumber * NetConstants.ChannelTypeCount + + (byte) (ordered ? DeliveryMethod.ReliableOrdered : DeliveryMethod.ReliableUnordered); + return _channels != null + ? ((ReliableChannel)_channels[idx])?.PacketsInQueue ?? 0 + : 0; + } - //Select channel - PacketProperty property; - BaseChannel channel = null; - - if (deliveryMethod == DeliveryMethod.Unreliable) - { - property = PacketProperty.Unreliable; - } - else - { - property = PacketProperty.Channeled; - channel = CreateChannel((byte)(channelNumber * NetConstants.ChannelTypeCount + (byte)deliveryMethod)); - } - - //Prepare - NetDebug.Write("[RS]Packet: " + property); - - //Check fragmentation - int headerSize = NetPacket.GetHeaderSize(property); - //Save mtu for multithread - int mtu = _mtu; - if (length + headerSize > mtu) - { - //if cannot be fragmented - if (deliveryMethod != DeliveryMethod.ReliableOrdered && deliveryMethod != DeliveryMethod.ReliableUnordered) - throw new TooBigPacketException("Unreliable or ReliableSequenced packet size exceeded maximum of " + (mtu - headerSize) + " bytes, Check allowed size by GetMaxSinglePacketSize()"); - - int packetFullSize = mtu - headerSize; - int packetDataSize = packetFullSize - NetConstants.FragmentHeaderSize; - int totalPackets = length / packetDataSize + (length % packetDataSize == 0 ? 0 : 1); - - NetDebug.Write("FragmentSend:\n" + - " MTU: {0}\n" + - " headerSize: {1}\n" + - " packetFullSize: {2}\n" + - " packetDataSize: {3}\n" + - " totalPackets: {4}", - mtu, headerSize, packetFullSize, packetDataSize, totalPackets); - - if (totalPackets > ushort.MaxValue) - throw new TooBigPacketException("Data was split in " + totalPackets + " fragments, which exceeds " + ushort.MaxValue); - - ushort currentFragmentId = (ushort)Interlocked.Increment(ref _fragmentId); - - for(ushort partIdx = 0; partIdx < totalPackets; partIdx++) - { - int sendLength = length > packetDataSize ? packetDataSize : length; - - NetPacket p = NetManager.PoolGetPacket(headerSize + sendLength + NetConstants.FragmentHeaderSize); - p.Property = property; - p.UserData = userData; - p.FragmentId = currentFragmentId; - p.FragmentPart = partIdx; - p.FragmentsTotal = (ushort)totalPackets; - p.MarkFragmented(); - - Buffer.BlockCopy(data, start + partIdx * packetDataSize, p.RawData, NetConstants.FragmentedHeaderTotalSize, sendLength); - channel.AddToQueue(p); - - length -= sendLength; - } - return; - } - - //Else just send - NetPacket packet = NetManager.PoolGetPacket(headerSize + length); - packet.Property = property; - Buffer.BlockCopy(data, start, packet.RawData, headerSize, length); - packet.UserData = userData; - - if (channel == null) //unreliable - { - lock(_unreliableChannel) - _unreliableChannel.Enqueue(packet); - } - else - { - channel.AddToQueue(packet); - } - } - -#if LITENETLIB_SPANS || NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1 || NETCOREAPP3_1 || NET5_0 || NETSTANDARD2_1 /// - /// Send data to peer with delivery event called + /// Returns packets count in queue for reliable channel 0 /// - /// Data - /// Number of channel (from 0 to channelsCount - 1) - /// Delivery method (reliable, unreliable, etc.) - /// User data that will be received in DeliveryEvent - /// - /// If you trying to send unreliable packet type - /// - public void SendWithDeliveryEvent(ReadOnlySpan data, byte channelNumber, DeliveryMethod deliveryMethod, object userData) - { - if (deliveryMethod != DeliveryMethod.ReliableOrdered && deliveryMethod != DeliveryMethod.ReliableUnordered) - throw new ArgumentException("Delivery event will work only for ReliableOrdered/Unordered packets"); - SendInternal(data, channelNumber, deliveryMethod, userData); - } - - /// - /// Send data to peer (channel - 0) - /// - /// Data - /// Send options (reliable, unreliable, etc.) - /// - /// If size exceeds maximum limit: - /// MTU - headerSize bytes for Unreliable - /// Fragment count exceeded ushort.MaxValue - /// - public void Send(ReadOnlySpan data, DeliveryMethod deliveryMethod) - { - SendInternal(data, 0, deliveryMethod, null); - } - - /// - /// Send data to peer - /// - /// Data - /// Number of channel (from 0 to channelsCount - 1) - /// Send options (reliable, unreliable, etc.) - /// - /// If size exceeds maximum limit: - /// MTU - headerSize bytes for Unreliable - /// Fragment count exceeded ushort.MaxValue - /// - public void Send(ReadOnlySpan data, byte channelNumber, DeliveryMethod deliveryMethod) - { - SendInternal(data, channelNumber, deliveryMethod, null); - } + /// type of channel ReliableOrdered or ReliableUnordered + /// packets count in channel queue + public new int GetPacketsCountInReliableQueue(bool ordered) => + GetPacketsCountInReliableQueue(0, ordered); - private void SendInternal( - ReadOnlySpan data, - byte channelNumber, - DeliveryMethod deliveryMethod, - object userData) + protected override void UpdateChannels() { - if (_connectionState != ConnectionState.Connected || channelNumber >= _channels.Length) - return; - - //Select channel - PacketProperty property; - BaseChannel channel = null; - - if (deliveryMethod == DeliveryMethod.Unreliable) - { - property = PacketProperty.Unreliable; - } - else - { - property = PacketProperty.Channeled; - channel = CreateChannel((byte)(channelNumber * NetConstants.ChannelTypeCount + (byte)deliveryMethod)); - } - - //Prepare - NetDebug.Write("[RS]Packet: " + property); - - //Check fragmentation - int headerSize = NetPacket.GetHeaderSize(property); - //Save mtu for multithread - int mtu = _mtu; - int length = data.Length; - if (length + headerSize > mtu) - { - //if cannot be fragmented - if (deliveryMethod != DeliveryMethod.ReliableOrdered && deliveryMethod != DeliveryMethod.ReliableUnordered) - throw new TooBigPacketException("Unreliable or ReliableSequenced packet size exceeded maximum of " + (mtu - headerSize) + " bytes, Check allowed size by GetMaxSinglePacketSize()"); - - int packetFullSize = mtu - headerSize; - int packetDataSize = packetFullSize - NetConstants.FragmentHeaderSize; - int totalPackets = length / packetDataSize + (length % packetDataSize == 0 ? 0 : 1); - - if (totalPackets > ushort.MaxValue) - throw new TooBigPacketException("Data was split in " + totalPackets + " fragments, which exceeds " + ushort.MaxValue); - - ushort currentFragmentId = (ushort)Interlocked.Increment(ref _fragmentId); - - for (ushort partIdx = 0; partIdx < totalPackets; partIdx++) - { - int sendLength = length > packetDataSize ? packetDataSize : length; - - NetPacket p = NetManager.PoolGetPacket(headerSize + sendLength + NetConstants.FragmentHeaderSize); - p.Property = property; - p.UserData = userData; - p.FragmentId = currentFragmentId; - p.FragmentPart = partIdx; - p.FragmentsTotal = (ushort)totalPackets; - p.MarkFragmented(); - - data.Slice(partIdx * packetDataSize, sendLength).CopyTo(new Span(p.RawData, NetConstants.FragmentedHeaderTotalSize, sendLength)); - channel.AddToQueue(p); - - length -= sendLength; - } + //Pending send + if (_channelSendQueue.IsEmpty) return; - } - - //Else just send - NetPacket packet = NetManager.PoolGetPacket(headerSize + length); - packet.Property = property; - data.CopyTo(new Span(packet.RawData, headerSize, length)); - packet.UserData = userData; - - if (channel == null) //unreliable - { - lock(_unreliableChannel) - _unreliableChannel.Enqueue(packet); - } - else - { - channel.AddToQueue(packet); - } - } -#endif - - public void Disconnect(byte[] data) - { - NetManager.DisconnectPeer(this, data); - } - - public void Disconnect(NetDataWriter writer) - { - NetManager.DisconnectPeer(this, writer); - } - - public void Disconnect(byte[] data, int start, int count) - { - NetManager.DisconnectPeer(this, data, start, count); - } - - public void Disconnect() - { - NetManager.DisconnectPeer(this); - } - - internal DisconnectResult ProcessDisconnect(NetPacket packet) - { - if ((_connectionState == ConnectionState.Connected || _connectionState == ConnectionState.Outgoing) && - packet.Size >= 9 && - BitConverter.ToInt64(packet.RawData, 1) == _connectTime && - packet.ConnectionNumber == _connectNum) - { - return _connectionState == ConnectionState.Connected - ? DisconnectResult.Disconnect - : DisconnectResult.Reject; - } - return DisconnectResult.None; - } - - internal void AddToReliableChannelSendQueue(BaseChannel channel) - { - _channelSendQueue.Enqueue(channel); - } - - internal ShutdownResult Shutdown(byte[] data, int start, int length, bool force) - { - lock (_shutdownLock) - { - //trying to shutdown already disconnected - if (_connectionState == ConnectionState.Disconnected || - _connectionState == ConnectionState.ShutdownRequested) - { - return ShutdownResult.None; - } - - var result = _connectionState == ConnectionState.Connected - ? ShutdownResult.WasConnected - : ShutdownResult.Success; - - //don't send anything - if (force) - { - _connectionState = ConnectionState.Disconnected; - return result; - } - - //reset time for reconnect protection - Interlocked.Exchange(ref _timeSinceLastPacket, 0); - //send shutdown packet - _shutdownPacket = new NetPacket(PacketProperty.Disconnect, length) {ConnectionNumber = _connectNum}; - FastBitConverter.GetBytes(_shutdownPacket.RawData, 1, _connectTime); - if (_shutdownPacket.Size >= _mtu) - { - //Drop additional data - NetDebug.WriteError("[Peer] Disconnect additional data size more than MTU - 8!"); - } - else if (data != null && length > 0) - { - Buffer.BlockCopy(data, start, _shutdownPacket.RawData, 9, length); - } - _connectionState = ConnectionState.ShutdownRequested; - NetDebug.Write("[Peer] Send disconnect"); - NetManager.SendRaw(_shutdownPacket, _remoteEndPoint); - return result; - } - } - - private void UpdateRoundTripTime(int roundTripTime) - { - _rtt += roundTripTime; - _rttCount++; - _avgRtt = _rtt/_rttCount; - _resendDelay = 25.0 + _avgRtt * 2.1; // 25 ms + double rtt - } - - internal void AddReliablePacket(DeliveryMethod method, NetPacket p) - { - if (p.IsFragmented) + int count = _channelSendQueue.Count; + while (count-- > 0) { - NetDebug.Write("Fragment. Id: {0}, Part: {1}, Total: {2}", p.FragmentId, p.FragmentPart, p.FragmentsTotal); - //Get needed array from dictionary - ushort packetFragId = p.FragmentId; - byte packetChannelId = p.ChannelId; - if (!_holdedFragments.TryGetValue(packetFragId, out var incomingFragments)) - { - incomingFragments = new IncomingFragments - { - Fragments = new NetPacket[p.FragmentsTotal], - ChannelId = p.ChannelId - }; - _holdedFragments.Add(packetFragId, incomingFragments); - } - - //Cache - var fragments = incomingFragments.Fragments; - - //Error check - if (p.FragmentPart >= fragments.Length || - fragments[p.FragmentPart] != null || - p.ChannelId != incomingFragments.ChannelId) - { - NetManager.PoolRecycle(p); - NetDebug.WriteError("Invalid fragment packet"); - return; - } - //Fill array - fragments[p.FragmentPart] = p; - - //Increase received fragments count - incomingFragments.ReceivedCount++; - - //Increase total size - incomingFragments.TotalSize += p.Size - NetConstants.FragmentedHeaderTotalSize; - - //Check for finish - if (incomingFragments.ReceivedCount != fragments.Length) - return; - - //just simple packet - NetPacket resultingPacket = NetManager.PoolGetPacket(incomingFragments.TotalSize); - - int pos = 0; - for (int i = 0; i < incomingFragments.ReceivedCount; i++) + if (!_channelSendQueue.TryDequeue(out var channel)) + break; + if (channel.SendAndCheckQueue()) { - var fragment = fragments[i]; - int writtenSize = fragment.Size - NetConstants.FragmentedHeaderTotalSize; - - if (pos+writtenSize > resultingPacket.RawData.Length) - { - _holdedFragments.Remove(packetFragId); - NetDebug.WriteError("Fragment error pos: {0} >= resultPacketSize: {1} , totalSize: {2}", - pos + writtenSize, - resultingPacket.RawData.Length, - incomingFragments.TotalSize); - return; - } - if (fragment.Size > fragment.RawData.Length) - { - _holdedFragments.Remove(packetFragId); - NetDebug.WriteError("Fragment error size: {0} > fragment.RawData.Length: {1}", fragment.Size, fragment.RawData.Length); - return; - } - - //Create resulting big packet - Buffer.BlockCopy( - fragment.RawData, - NetConstants.FragmentedHeaderTotalSize, - resultingPacket.RawData, - pos, - writtenSize); - pos += writtenSize; - - //Free memory - NetManager.PoolRecycle(fragment); - fragments[i] = null; + // still has something to send, re-add it to the send queue + _channelSendQueue.Enqueue(channel); } - - //Clear memory - _holdedFragments.Remove(packetFragId); - - //Send to process - NetManager.CreateReceiveEvent(resultingPacket, method, (byte)(packetChannelId / NetConstants.ChannelTypeCount), 0, this); - } - else //Just simple packet - { - NetManager.CreateReceiveEvent(p, method, (byte)(p.ChannelId / NetConstants.ChannelTypeCount), NetConstants.ChanneledHeaderSize, this); } } - private void ProcessMtuPacket(NetPacket packet) + internal override void ProcessChanneled(NetPacket packet) { - //header + int - if (packet.Size < NetConstants.PossibleMtu[0]) - return; - - //first stage check (mtu check and mtu ok) - int receivedMtu = BitConverter.ToInt32(packet.RawData, 1); - int endMtuCheck = BitConverter.ToInt32(packet.RawData, packet.Size - 4); - if (receivedMtu != packet.Size || receivedMtu != endMtuCheck || receivedMtu > NetConstants.MaxPacketSize) + if (packet.ChannelId >= _channels.Length) { - NetDebug.WriteError("[MTU] Broken packet. RMTU {0}, EMTU {1}, PSIZE {2}", receivedMtu, endMtuCheck, packet.Size); - return; - } - - if (packet.Property == PacketProperty.MtuCheck) - { - _mtuCheckAttempts = 0; - NetDebug.Write("[MTU] check. send back: " + receivedMtu); - packet.Property = PacketProperty.MtuOk; - NetManager.SendRawAndRecycle(packet, _remoteEndPoint); - } - else if(receivedMtu > _mtu && !_finishMtu) //MtuOk - { - //invalid packet - if (receivedMtu != NetConstants.PossibleMtu[_mtuIdx + 1] - NetManager.ExtraPacketSizeForLayer) - return; - - lock (_mtuMutex) - { - SetMtu(_mtuIdx+1); - } - //if maxed - finish. - if (_mtuIdx == NetConstants.PossibleMtu.Length - 1) - _finishMtu = true; NetManager.PoolRecycle(packet); - NetDebug.Write("[MTU] ok. Increase to: " + _mtu); - } - } - - private void UpdateMtuLogic(int deltaTime) - { - if (_finishMtu) return; - - _mtuCheckTimer += deltaTime; - if (_mtuCheckTimer < MtuCheckDelay) - return; - - _mtuCheckTimer = 0; - _mtuCheckAttempts++; - if (_mtuCheckAttempts >= MaxMtuCheckAttempts) - { - _finishMtu = true; - return; - } - - lock (_mtuMutex) - { - if (_mtuIdx >= NetConstants.PossibleMtu.Length - 1) - return; - - //Send increased packet - int newMtu = NetConstants.PossibleMtu[_mtuIdx + 1] - NetManager.ExtraPacketSizeForLayer; - var p = NetManager.PoolGetPacket(newMtu); - p.Property = PacketProperty.MtuCheck; - FastBitConverter.GetBytes(p.RawData, 1, newMtu); //place into start - FastBitConverter.GetBytes(p.RawData, p.Size - 4, newMtu);//and end of packet - - //Must check result for MTU fix - if (NetManager.SendRawAndRecycle(p, _remoteEndPoint) <= 0) - _finishMtu = true; } + var channel = _channels[packet.ChannelId] ?? (packet.Property == PacketProperty.Ack ? null : CreateChannel(packet.ChannelId)); + if (channel != null && !channel.ProcessPacket(packet)) + NetManager.PoolRecycle(packet); } - internal ConnectRequestResult ProcessConnectRequest(NetConnectRequestPacket connRequest) - { - //current or new request - switch (_connectionState) - { - //P2P case - case ConnectionState.Outgoing: - //fast check - if (connRequest.ConnectionTime < _connectTime) - { - return ConnectRequestResult.P2PLose; - } - //slow rare case check - if (connRequest.ConnectionTime == _connectTime) - { - var remoteBytes = _remoteEndPoint.Serialize(); - var localBytes = connRequest.TargetAddress; - for (int i = remoteBytes.Size-1; i >= 0; i--) - { - byte rb = remoteBytes[i]; - if (rb == localBytes[i]) - continue; - if (rb < localBytes[i]) - return ConnectRequestResult.P2PLose; - } - } - break; - - case ConnectionState.Connected: - //Old connect request - if (connRequest.ConnectionTime == _connectTime) - { - //just reply accept - NetManager.SendRaw(_connectAcceptPacket, _remoteEndPoint); - } - //New connect request - else if (connRequest.ConnectionTime > _connectTime) - { - return ConnectRequestResult.Reconnection; - } - break; - - case ConnectionState.Disconnected: - case ConnectionState.ShutdownRequested: - if (connRequest.ConnectionTime >= _connectTime) - return ConnectRequestResult.NewConnection; - break; - } - return ConnectRequestResult.None; - } + internal override void AddToReliableChannelSendQueue(BaseChannel channel) => + _channelSendQueue.Enqueue(channel); - //Process incoming packet - internal void ProcessPacket(NetPacket packet) + internal override BaseChannel CreateChannel(byte idx) { - //not initialized - if (_connectionState == ConnectionState.Outgoing || _connectionState == ConnectionState.Disconnected) - { - NetManager.PoolRecycle(packet); - return; - } - if (packet.Property == PacketProperty.ShutdownOk) - { - if (_connectionState == ConnectionState.ShutdownRequested) - _connectionState = ConnectionState.Disconnected; - NetManager.PoolRecycle(packet); - return; - } - if (packet.ConnectionNumber != _connectNum) - { - NetDebug.Write(NetLogLevel.Trace, "[RR]Old packet"); - NetManager.PoolRecycle(packet); - return; - } - Interlocked.Exchange(ref _timeSinceLastPacket, 0); - - NetDebug.Write("[RR]PacketProperty: {0}", packet.Property); - switch (packet.Property) + var newChannel = _channels[idx]; + if (newChannel != null) + return newChannel; + switch ((DeliveryMethod)(idx % NetConstants.ChannelTypeCount)) { - case PacketProperty.Merged: - int pos = NetConstants.HeaderSize; - while (pos < packet.Size) - { - ushort size = BitConverter.ToUInt16(packet.RawData, pos); - pos += 2; - if (packet.RawData.Length - pos < size) - break; - - NetPacket mergedPacket = NetManager.PoolGetPacket(size); - Buffer.BlockCopy(packet.RawData, pos, mergedPacket.RawData, 0, size); - mergedPacket.Size = size; - - if (!mergedPacket.Verify()) - break; - - pos += size; - ProcessPacket(mergedPacket); - } - NetManager.PoolRecycle(packet); - break; - //If we get ping, send pong - case PacketProperty.Ping: - if (NetUtils.RelativeSequenceNumber(packet.Sequence, _pongPacket.Sequence) > 0) - { - NetDebug.Write("[PP]Ping receive, send pong"); - FastBitConverter.GetBytes(_pongPacket.RawData, 3, DateTime.UtcNow.Ticks); - _pongPacket.Sequence = packet.Sequence; - NetManager.SendRaw(_pongPacket, _remoteEndPoint); - } - NetManager.PoolRecycle(packet); - break; - - //If we get pong, calculate ping time and rtt - case PacketProperty.Pong: - if (packet.Sequence == _pingPacket.Sequence) - { - _pingTimer.Stop(); - int elapsedMs = (int)_pingTimer.ElapsedMilliseconds; - _remoteDelta = BitConverter.ToInt64(packet.RawData, 3) + (elapsedMs * TimeSpan.TicksPerMillisecond ) / 2 - DateTime.UtcNow.Ticks; - UpdateRoundTripTime(elapsedMs); - NetManager.ConnectionLatencyUpdated(this, elapsedMs / 2); - NetDebug.Write("[PP]Ping: {0} - {1} - {2}", packet.Sequence, elapsedMs, _remoteDelta); - } - NetManager.PoolRecycle(packet); - break; - - case PacketProperty.Ack: - case PacketProperty.Channeled: - if (packet.ChannelId > _channels.Length) - { - NetManager.PoolRecycle(packet); - break; - } - var channel = _channels[packet.ChannelId] ?? (packet.Property == PacketProperty.Ack ? null : CreateChannel(packet.ChannelId)); - if (channel != null) - { - if (!channel.ProcessPacket(packet)) - NetManager.PoolRecycle(packet); - } - break; - - //Simple packet without acks - case PacketProperty.Unreliable: - NetManager.CreateReceiveEvent(packet, DeliveryMethod.Unreliable, 0, NetConstants.HeaderSize, this); - return; - - case PacketProperty.MtuCheck: - case PacketProperty.MtuOk: - ProcessMtuPacket(packet); + case DeliveryMethod.ReliableUnordered: + newChannel = new ReliableChannel(this, false, idx); break; - - default: - NetDebug.WriteError("Error! Unexpected packet type: " + packet.Property); + case DeliveryMethod.Sequenced: + newChannel = new SequencedChannel(this, false, idx); break; - } - } - - private void SendMerged() - { - if (_mergeCount == 0) - return; - int bytesSent; - if (_mergeCount > 1) - { - NetDebug.Write("[P]Send merged: " + _mergePos + ", count: " + _mergeCount); - bytesSent = NetManager.SendRaw(_mergeData.RawData, 0, NetConstants.HeaderSize + _mergePos, _remoteEndPoint); - } - else - { - //Send without length information and merging - bytesSent = NetManager.SendRaw(_mergeData.RawData, NetConstants.HeaderSize + 2, _mergePos - 2, _remoteEndPoint); - } - - if (NetManager.EnableStatistics) - { - Statistics.IncrementPacketsSent(); - Statistics.AddBytesSent(bytesSent); - } - - _mergePos = 0; - _mergeCount = 0; - } - - internal void SendUserData(NetPacket packet) - { - packet.ConnectionNumber = _connectNum; - int mergedPacketSize = NetConstants.HeaderSize + packet.Size + 2; - const int sizeTreshold = 20; - if (mergedPacketSize + sizeTreshold >= _mtu) - { - NetDebug.Write(NetLogLevel.Trace, "[P]SendingPacket: " + packet.Property); - int bytesSent = NetManager.SendRaw(packet, _remoteEndPoint); - - if (NetManager.EnableStatistics) - { - Statistics.IncrementPacketsSent(); - Statistics.AddBytesSent(bytesSent); - } - - return; - } - if (_mergePos + mergedPacketSize > _mtu) - SendMerged(); - - FastBitConverter.GetBytes(_mergeData.RawData, _mergePos + NetConstants.HeaderSize, (ushort)packet.Size); - Buffer.BlockCopy(packet.RawData, 0, _mergeData.RawData, _mergePos + NetConstants.HeaderSize + 2, packet.Size); - _mergePos += packet.Size + 2; - _mergeCount++; - //DebugWriteForce("Merged: " + _mergePos + "/" + (_mtu - 2) + ", count: " + _mergeCount); - } - - internal void Update(int deltaTime) - { - Interlocked.Add(ref _timeSinceLastPacket, deltaTime); - switch (_connectionState) - { - case ConnectionState.Connected: - if (_timeSinceLastPacket > NetManager.DisconnectTimeout) - { - NetDebug.Write( - "[UPDATE] Disconnect by timeout: {0} > {1}", - _timeSinceLastPacket, - NetManager.DisconnectTimeout); - NetManager.DisconnectPeerForce(this, DisconnectReason.Timeout, 0, null); - return; - } + case DeliveryMethod.ReliableOrdered: + newChannel = new ReliableChannel(this, true, idx); break; - - case ConnectionState.ShutdownRequested: - if (_timeSinceLastPacket > NetManager.DisconnectTimeout) - { - _connectionState = ConnectionState.Disconnected; - } - else - { - _shutdownTimer += deltaTime; - if (_shutdownTimer >= ShutdownDelay) - { - _shutdownTimer = 0; - NetManager.SendRaw(_shutdownPacket, _remoteEndPoint); - } - } - return; - - case ConnectionState.Outgoing: - _connectTimer += deltaTime; - if (_connectTimer > NetManager.ReconnectDelay) - { - _connectTimer = 0; - _connectAttempts++; - if (_connectAttempts > NetManager.MaxConnectAttempts) - { - NetManager.DisconnectPeerForce(this, DisconnectReason.ConnectionFailed, 0, null); - return; - } - - //else send connect again - NetManager.SendRaw(_connectRequestPacket, _remoteEndPoint); - } - return; - - case ConnectionState.Disconnected: - return; - } - - //Send ping - _pingSendTimer += deltaTime; - if (_pingSendTimer >= NetManager.PingInterval) - { - NetDebug.Write("[PP] Send ping..."); - //reset timer - _pingSendTimer = 0; - //send ping - _pingPacket.Sequence++; - //ping timeout - if (_pingTimer.IsRunning) - UpdateRoundTripTime((int)_pingTimer.ElapsedMilliseconds); - _pingTimer.Restart(); - NetManager.SendRaw(_pingPacket, _remoteEndPoint); - } - - //RTT - round trip time - _rttResetTimer += deltaTime; - if (_rttResetTimer >= NetManager.PingInterval * 3) - { - _rttResetTimer = 0; - _rtt = _avgRtt; - _rttCount = 1; - } - - UpdateMtuLogic(deltaTime); - - //Pending send - int count = _channelSendQueue.Count; - while (count-- > 0) - { - if (!_channelSendQueue.TryDequeue(out var channel)) + case DeliveryMethod.ReliableSequenced: + newChannel = new SequencedChannel(this, true, idx); break; - if (channel.SendAndCheckQueue()) - { - // still has something to send, re-add it to the send queue - _channelSendQueue.Enqueue(channel); - } - } - - lock (_unreliableChannel) - { - int unreliableCount = _unreliableChannel.Count; - for (int i = 0; i < unreliableCount; i++) - { - var packet = _unreliableChannel.Dequeue(); - SendUserData(packet); - NetManager.PoolRecycle(packet); - } } + var prevChannel = Interlocked.CompareExchange(ref _channels[idx], newChannel, null); + if (prevChannel != null) + return prevChannel; - SendMerged(); - } - - //For reliable channel - internal void RecycleAndDeliver(NetPacket packet) - { - if (packet.UserData != null) - { - if (packet.IsFragmented) - { - _deliveredFragments.TryGetValue(packet.FragmentId, out ushort fragCount); - fragCount++; - if (fragCount == packet.FragmentsTotal) - { - NetManager.MessageDelivered(this, packet.UserData); - _deliveredFragments.Remove(packet.FragmentId); - } - else - { - _deliveredFragments[packet.FragmentId] = fragCount; - } - } - else - { - NetManager.MessageDelivered(this, packet.UserData); - } - packet.UserData = null; - } - NetManager.PoolRecycle(packet); + return newChannel; } } } diff --git a/LiteNetLib/NetStatistics.cs b/LiteNetLib/NetStatistics.cs index 032e2754..e2d0304c 100644 --- a/LiteNetLib/NetStatistics.cs +++ b/LiteNetLib/NetStatistics.cs @@ -2,6 +2,9 @@ namespace LiteNetLib { + /// + /// Thread-safe counter for network statistics including sent/received packets, bytes, and packet loss. + /// public sealed class NetStatistics { private long _packetsSent; @@ -10,12 +13,35 @@ public sealed class NetStatistics private long _bytesReceived; private long _packetLoss; + /// + /// Total number of packets sent. + /// public long PacketsSent => Interlocked.Read(ref _packetsSent); + + /// + /// Total number of packets received. + /// public long PacketsReceived => Interlocked.Read(ref _packetsReceived); + + /// + /// Total number of bytes sent. + /// public long BytesSent => Interlocked.Read(ref _bytesSent); + + /// + /// Total number of bytes received. + /// public long BytesReceived => Interlocked.Read(ref _bytesReceived); + + /// + /// Total number of packets lost during transmission. + /// public long PacketLoss => Interlocked.Read(ref _packetLoss); + /// + /// Percentage of sent packets that were lost. + /// Calculated as (PacketLoss * 100) / PacketsSent. + /// public long PacketLossPercent { get @@ -26,6 +52,9 @@ public long PacketLossPercent } } + /// + /// Resets all statistical counters to zero. + /// public void Reset() { Interlocked.Exchange(ref _packetsSent, 0); @@ -35,36 +64,49 @@ public void Reset() Interlocked.Exchange(ref _packetLoss, 0); } - public void IncrementPacketsSent() - { + /// + /// Increments the count of sent packets by one. + /// + public void IncrementPacketsSent() => Interlocked.Increment(ref _packetsSent); - } - public void IncrementPacketsReceived() - { + /// + /// Increments the count of received packets by one. + /// + public void IncrementPacketsReceived() => Interlocked.Increment(ref _packetsReceived); - } - public void AddBytesSent(long bytesSent) - { + /// + /// Adds a specific amount to the total bytes sent. + /// + /// Number of bytes to add. + public void AddBytesSent(long bytesSent) => Interlocked.Add(ref _bytesSent, bytesSent); - } - public void AddBytesReceived(long bytesReceived) - { + /// + /// Adds a specific amount to the total bytes received. + /// + /// Number of bytes to add. + public void AddBytesReceived(long bytesReceived) => Interlocked.Add(ref _bytesReceived, bytesReceived); - } - public void IncrementPacketLoss() - { + /// + /// Increments the count of lost packets by one. + /// + public void IncrementPacketLoss() => Interlocked.Increment(ref _packetLoss); - } - public void AddPacketLoss(long packetLoss) - { + /// + /// Adds a specific amount to the total packet loss count. + /// + /// Number of lost packets to add. + public void AddPacketLoss(long packetLoss) => Interlocked.Add(ref _packetLoss, packetLoss); - } + /// + /// Returns a string representation of the current network statistics. + /// + /// A formatted string containing bytes received/sent, packets received/sent, and loss information. public override string ToString() { return diff --git a/LiteNetLib/NetUtils.cs b/LiteNetLib/NetUtils.cs index d162b2da..593c2f2a 100644 --- a/LiteNetLib/NetUtils.cs +++ b/LiteNetLib/NetUtils.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Net; using System.Net.Sockets; using System.Net.NetworkInformation; @@ -22,11 +23,27 @@ public enum LocalAddrType /// public static class NetUtils { - public static IPEndPoint MakeEndPoint(string hostStr, int port) - { - return new IPEndPoint(ResolveAddress(hostStr), port); - } + private static readonly NetworkSorter NetworkSorter = new NetworkSorter(); + /// + /// Creates an from a host string and a port. + /// + /// The host name or IP address string to resolve. + /// The port number for the endpoint. + /// A new instance. + public static IPEndPoint MakeEndPoint(string hostStr, int port) => + new IPEndPoint(ResolveAddress(hostStr), port); + + /// + /// Resolves a host string into an . + /// + /// + /// This method handles "localhost" specifically, attempts to parse the string as a direct IP, + /// and falls back to DNS resolution. It prioritizes IPv6 if is enabled. + /// + /// The host name or IP address string (e.g., "127.0.0.1", "localhost", or "google.com"). + /// The resolved . + /// Thrown when the address cannot be resolved or is invalid. public static IPAddress ResolveAddress(string hostStr) { if(hostStr == "localhost") @@ -34,7 +51,7 @@ public static IPAddress ResolveAddress(string hostStr) if (!IPAddress.TryParse(hostStr, out var ipAddress)) { - if (NetManager.IPv6Support) + if (LiteNetManager.IPv6Support) ipAddress = ResolveAddress(hostStr, AddressFamily.InterNetworkV6); if (ipAddress == null) ipAddress = ResolveAddress(hostStr, AddressFamily.InterNetwork); @@ -45,6 +62,12 @@ public static IPAddress ResolveAddress(string hostStr) return ipAddress; } + /// + /// Resolves a host string using DNS for a specific . + /// + /// The host name to resolve via DNS. + /// The preferred address family (e.g., or ). + /// The first matching the family, or if no match is found. public static IPAddress ResolveAddress(string hostStr, AddressFamily addressFamily) { IPAddress[] addresses = Dns.GetHostEntry(hostStr).AddressList; @@ -81,7 +104,12 @@ public static void GetLocalIpList(IList targetList, LocalAddrType addrTy bool ipv6 = (addrType & LocalAddrType.IPv6) == LocalAddrType.IPv6; try { - foreach (NetworkInterface ni in NetworkInterface.GetAllNetworkInterfaces()) + // Sort networks interfaces so it prefer Wifi over Cellular networks + // Most cellulars networks seems to be incompatible with NAT Punch + var networks = NetworkInterface.GetAllNetworkInterfaces(); + Array.Sort(networks, NetworkSorter); + + foreach (NetworkInterface ni in networks) { //Skip loopback and disabled network interfaces if (ni.NetworkInterfaceType == NetworkInterfaceType.Loopback || @@ -119,7 +147,7 @@ public static void GetLocalIpList(IList targetList, LocalAddrType addrTy { //ignored } - + if (targetList.Count == 0) { if(ipv4) @@ -150,7 +178,7 @@ public static string GetLocalIp(LocalAddrType addrType) // =========================================== internal static void PrintInterfaceInfos() { - NetDebug.WriteForce(NetLogLevel.Info, "IPv6Support: {0}", NetManager.IPv6Support); + NetDebug.WriteForce(NetLogLevel.Info, $"IPv6Support: { LiteNetManager.IPv6Support}"); try { foreach (NetworkInterface ni in NetworkInterface.GetAllNetworkInterfaces()) @@ -162,25 +190,19 @@ internal static void PrintInterfaceInfos() { NetDebug.WriteForce( NetLogLevel.Info, - "Interface: {0}, Type: {1}, Ip: {2}, OpStatus: {3}", - ni.Name, - ni.NetworkInterfaceType.ToString(), - ip.Address.ToString(), - ni.OperationalStatus.ToString()); + $"Interface: {ni.Name}, Type: {ni.NetworkInterfaceType}, Ip: {ip.Address}, OpStatus: {ni.OperationalStatus}"); } } } } catch (Exception e) { - NetDebug.WriteForce(NetLogLevel.Info, "Error while getting interface infos: {0}", e.ToString()); + NetDebug.WriteForce(NetLogLevel.Info, $"Error while getting interface infos: {e}"); } } - internal static int RelativeSequenceNumber(int number, int expected) - { - return (number - expected + NetConstants.MaxSequence + NetConstants.HalfMaxSequence) % NetConstants.MaxSequence - NetConstants.HalfMaxSequence; - } + internal static int RelativeSequenceNumber(int number, int expected) => + (number - expected + NetConstants.MaxSequence + NetConstants.HalfMaxSequence) % NetConstants.MaxSequence - NetConstants.HalfMaxSequence; internal static T[] AllocatePinnedUninitializedArray(int count) where T : unmanaged { @@ -191,4 +213,44 @@ internal static T[] AllocatePinnedUninitializedArray(int count) where T : unm #endif } } + + // Pick the most obvious choice for the local IP + // Ethernet > Wifi > Others > Cellular + internal class NetworkSorter : IComparer + { + [SuppressMessage("ReSharper", "PossibleNullReferenceException")] + public int Compare(NetworkInterface a, NetworkInterface b) + { + var isCellularA = a.NetworkInterfaceType == NetworkInterfaceType.Wman || + a.NetworkInterfaceType == NetworkInterfaceType.Wwanpp || + a.NetworkInterfaceType == NetworkInterfaceType.Wwanpp2; + + var isCellularB = b.NetworkInterfaceType == NetworkInterfaceType.Wman || + b.NetworkInterfaceType == NetworkInterfaceType.Wwanpp || + b.NetworkInterfaceType == NetworkInterfaceType.Wwanpp2; + + var isWifiA = a.NetworkInterfaceType == NetworkInterfaceType.Wireless80211; + var isWifiB = b.NetworkInterfaceType == NetworkInterfaceType.Wireless80211; + + var isEthernetA = a.NetworkInterfaceType == NetworkInterfaceType.Ethernet || + a.NetworkInterfaceType == NetworkInterfaceType.Ethernet3Megabit || + a.NetworkInterfaceType == NetworkInterfaceType.GigabitEthernet || + a.NetworkInterfaceType == NetworkInterfaceType.FastEthernetFx || + a.NetworkInterfaceType == NetworkInterfaceType.FastEthernetT; + + var isEthernetB = b.NetworkInterfaceType == NetworkInterfaceType.Ethernet || + b.NetworkInterfaceType == NetworkInterfaceType.Ethernet3Megabit || + b.NetworkInterfaceType == NetworkInterfaceType.GigabitEthernet || + b.NetworkInterfaceType == NetworkInterfaceType.FastEthernetFx || + b.NetworkInterfaceType == NetworkInterfaceType.FastEthernetT; + + var isOtherA = !isCellularA && !isWifiA && !isEthernetA; + var isOtherB = !isCellularB && !isWifiB && !isEthernetB; + + var priorityA = isEthernetA ? 3 : isWifiA ? 2 : isOtherA ? 1 : 0; + var priorityB = isEthernetB ? 3 : isWifiB ? 2 : isOtherB ? 1 : 0; + + return priorityA > priorityB ? -1 : priorityA < priorityB ? 1 : 0; + } + } } diff --git a/LiteNetLib/PausedSocketFix.cs b/LiteNetLib/PausedSocketFix.cs new file mode 100644 index 00000000..a8947348 --- /dev/null +++ b/LiteNetLib/PausedSocketFix.cs @@ -0,0 +1,67 @@ +#if UNITY_2018_3_OR_NEWER +using System.Net; +using UnityEngine; + +namespace LiteNetLib +{ + public class PausedSocketFix + { + private readonly LiteNetManager _netManager; + private readonly IPAddress _ipv4; + private readonly IPAddress _ipv6; + private readonly int _port; + private readonly bool _manualMode; + private bool _initialized; + + public PausedSocketFix(LiteNetManager netManager, IPAddress ipv4, IPAddress ipv6, int port, bool manualMode) + { + _netManager = netManager; + _ipv4 = ipv4; + _ipv6 = ipv6; + _port = port; + _manualMode = manualMode; + Application.focusChanged += Application_focusChanged; + Application.quitting += Deinitialize; + _initialized = true; + } + + public void Deinitialize() + { + if (_initialized) + { + Application.focusChanged -= Application_focusChanged; + Application.quitting -= Deinitialize; + } + + if (_netManager.IsRunning) + { + _netManager.Stop(); + } + _initialized = false; + } + + private void Application_focusChanged(bool focused) + { + //If coming back into focus see if a reconnect is needed. + if (focused) + { + //try reconnect + if (!_initialized) + return; + //Was intentionally disconnected at some point. + if (!_netManager.IsRunning) + return; + //Socket is in working state. + if (_netManager.NotConnected == false) + return; + + //Socket isn't running but should be. Try to start again. + if (!_netManager.Start(_ipv4, _ipv6, _port, _manualMode)) + { + NetDebug.WriteError($"[S] Cannot restore connection. Ipv4 {_ipv4}, Ipv6 {_ipv6}, Port {_port}, ManualMode {_manualMode}"); + } + } + } + } +} +#endif diff --git a/LiteNetLib/PooledPacket.cs b/LiteNetLib/PooledPacket.cs index 26ef7bd8..927b0335 100644 --- a/LiteNetLib/PooledPacket.cs +++ b/LiteNetLib/PooledPacket.cs @@ -23,7 +23,7 @@ public readonly ref struct PooledPacket internal PooledPacket(NetPacket packet, int maxDataSize, byte channelNumber) { _packet = packet; - UserDataOffset = _packet.GetHeaderSize(); + UserDataOffset = _packet.HeaderSize; _packet.Size = UserDataOffset; MaxUserDataSize = maxDataSize - UserDataOffset; _channelNumber = channelNumber; diff --git a/LiteNetLib/ReliableChannel.cs b/LiteNetLib/ReliableChannel.cs index f825e37d..8aa49851 100644 --- a/LiteNetLib/ReliableChannel.cs +++ b/LiteNetLib/ReliableChannel.cs @@ -1,19 +1,33 @@ using System; +using System.Collections.Generic; +using LiteNetLib.Utils; namespace LiteNetLib { + internal sealed class MergedPacketUserData + { + public readonly object[] Items; + + public MergedPacketUserData(object[] items) + { + Items = items; + } + } + internal sealed class ReliableChannel : BaseChannel { + [ThreadStatic] + private static List _mergedPacketUserDataList; + private const int MergeHeaderSize = 2; + private const int MergeSizeThreshold = 20; + private struct PendingPacket { private NetPacket _packet; private long _timeStamp; private bool _isSent; - public override string ToString() - { - return _packet == null ? "Empty" : _packet.Sequence.ToString(); - } + public override string ToString() => _packet == null ? "Empty" : _packet.Sequence.ToString(); public void Init(NetPacket packet) { @@ -22,7 +36,7 @@ public void Init(NetPacket packet) } //Returns true if there is a pending packet inside - public bool TrySend(long currentTime, NetPeer peer) + public bool TrySend(long currentTime, LiteNetPeer peer) { if (_packet == null) return false; @@ -33,7 +47,7 @@ public bool TrySend(long currentTime, NetPeer peer) double packetHoldTime = currentTime - _timeStamp; if (packetHoldTime < resendDelay) return true; - NetDebug.Write("[RC]Resend: {0} > {1}", (int)packetHoldTime, resendDelay); + NetDebug.Write($"[RC]Resend: {packetHoldTime} > {resendDelay}"); } _timeStamp = currentTime; _isSent = true; @@ -41,7 +55,9 @@ public bool TrySend(long currentTime, NetPeer peer) return true; } - public bool Clear(NetPeer peer) + public bool IsEmpty => _packet == null; + + public bool Clear(LiteNetPeer peer) { if (_packet != null) { @@ -71,7 +87,7 @@ public bool Clear(NetPeer peer) private const int BitsInByte = 8; private readonly byte _id; - public ReliableChannel(NetPeer peer, bool ordered, byte id) : base(peer) + public ReliableChannel(LiteNetPeer peer, bool ordered, byte id) : base(peer) { _id = id; _windowSize = NetConstants.DefaultWindowSize; @@ -98,6 +114,98 @@ public ReliableChannel(NetPeer peer, bool ordered, byte id) : base(peer) _outgoingAcks = new NetPacket(PacketProperty.Ack, (_windowSize - 1) / BitsInByte + 2) {ChannelId = id}; } + private NetPacket GetNextOutgoingPacket() + { + int maxPayloadSize = Peer.Mtu - NetConstants.ChanneledHeaderSize; + NetPacket mergedPacket = null; + int mergePos = 0; + + List userDataList = null; + + while (OutgoingQueue.Count > 0) + { + var packet = OutgoingQueue.Peek(); + if (packet.IsFragmented) + break; + + int payloadSize = packet.Size - NetConstants.ChanneledHeaderSize; + int newSize = mergePos + MergeHeaderSize + payloadSize; + if (newSize + MergeSizeThreshold > maxPayloadSize && mergePos > 0) + break; + if (newSize > maxPayloadSize) + break; + + if (mergedPacket == null) + { + mergedPacket = Peer.NetManager.PoolGetPacket(Peer.Mtu); + mergedPacket.Property = PacketProperty.ReliableMerged; + userDataList = _mergedPacketUserDataList; + if (userDataList == null) + { + userDataList = new List(); + _mergedPacketUserDataList = userDataList; + } + else + { + userDataList.Clear(); + } + } + + FastBitConverter.GetBytes(mergedPacket.RawData, NetConstants.ChanneledHeaderSize + mergePos, (ushort)payloadSize); + Buffer.BlockCopy(packet.RawData, NetConstants.ChanneledHeaderSize, mergedPacket.RawData, NetConstants.ChanneledHeaderSize + mergePos + MergeHeaderSize, payloadSize); + mergePos += payloadSize + MergeHeaderSize; + + if (packet.UserData != null) + { + userDataList.Add(packet.UserData); + packet.UserData = null; + } + + Peer.NetManager.PoolRecycle(OutgoingQueue.Dequeue()); + } + + if (mergedPacket == null) + return OutgoingQueue.Dequeue(); + + mergedPacket.Size = NetConstants.ChanneledHeaderSize + mergePos; + if (userDataList.Count > 0) + mergedPacket.UserData = new MergedPacketUserData(userDataList.ToArray()); + + return mergedPacket; + } + + private void ProcessIncomingPacket(NetPacket packet) + { + if (packet.Property == PacketProperty.ReliableMerged) + { + //ProcessMerged + int pos = NetConstants.ChanneledHeaderSize; + while (pos + MergeHeaderSize <= packet.Size) + { + ushort size = BitConverter.ToUInt16(packet.RawData, pos); + pos += MergeHeaderSize; + if (size == 0 || pos + size > packet.Size) + { + NetDebug.Write("[RR]Merged packet corrupted"); + break; + } + + NetPacket mergedPacket = Peer.NetManager.PoolGetPacket(NetConstants.ChanneledHeaderSize + size); + mergedPacket.Property = PacketProperty.Channeled; + mergedPacket.ChannelId = packet.ChannelId; + Buffer.BlockCopy(packet.RawData, pos, mergedPacket.RawData, NetConstants.ChanneledHeaderSize, size); + pos += size; + + Peer.AddReliablePacket(_deliveryMethod, mergedPacket); + } + Peer.NetManager.PoolRecycle(packet); + } + else + { + Peer.AddReliablePacket(_deliveryMethod, packet); + } + } + //ProcessAck in packet private void ProcessAck(NetPacket packet) { @@ -132,7 +240,7 @@ private void ProcessAck(NetPacket packet) int rel = NetUtils.RelativeSequenceNumber(pendingSeq, ackWindowStart); if (rel >= _windowSize) { - NetDebug.Write("[PA]REL: " + rel); + //NetDebug.Write($"[PA]REL: {rel}"); break; } @@ -141,14 +249,14 @@ private void ProcessAck(NetPacket packet) int currentBit = pendingIdx % BitsInByte; if ((acksData[currentByte] & (1 << currentBit)) == 0) { - if (Peer.NetManager.EnableStatistics) + if (Peer.NetManager.EnableStatistics && !_pendingPackets[pendingIdx].IsEmpty) { Peer.Statistics.IncrementPacketLoss(); Peer.NetManager.Statistics.IncrementPacketLoss(); } //Skip false ack - NetDebug.Write("[PA]False ack: {0}", pendingSeq); + //NetDebug.Write($"[PA]False ack: {pendingSeq}"); continue; } @@ -160,12 +268,12 @@ private void ProcessAck(NetPacket packet) //clear packet if (_pendingPackets[pendingIdx].Clear(Peer)) - NetDebug.Write("[PA]Removing reliableInOrder ack: {0} - true", pendingSeq); + NetDebug.Write($"[PA]Removing reliableInOrder ack: {pendingSeq} - true"); } } } - protected override bool SendNextPackets() + public override bool SendNextPackets() { if (_mustSendAcks) { @@ -181,19 +289,20 @@ protected override bool SendNextPackets() lock (_pendingPackets) { //get packets from queue - while (!OutgoingQueue.IsEmpty) + lock (OutgoingQueue) { - int relate = NetUtils.RelativeSequenceNumber(_localSeqence, _localWindowStart); - if (relate >= _windowSize) - break; - - if (!OutgoingQueue.TryDequeue(out var netPacket)) - break; - - netPacket.Sequence = (ushort) _localSeqence; - netPacket.ChannelId = _id; - _pendingPackets[_localSeqence % _windowSize].Init(netPacket); - _localSeqence = (_localSeqence + 1) % NetConstants.MaxSequence; + while (OutgoingQueue.Count > 0) + { + int relate = NetUtils.RelativeSequenceNumber(_localSeqence, _localWindowStart); + if (relate >= _windowSize) + break; + + var netPacket = GetNextOutgoingPacket(); + netPacket.Sequence = (ushort) _localSeqence; + netPacket.ChannelId = _id; + _pendingPackets[_localSeqence % _windowSize].Init(netPacket); + _localSeqence = (_localSeqence + 1) % NetConstants.MaxSequence; + } } //send @@ -205,7 +314,7 @@ protected override bool SendNextPackets() } } - return hasPendingPackets || _mustSendAcks || !OutgoingQueue.IsEmpty; + return hasPendingPackets || _mustSendAcks || OutgoingQueue.Count > 0; } //Process incoming packet @@ -294,7 +403,7 @@ public override bool ProcessPacket(NetPacket packet) if (seq == _remoteSequence) { NetDebug.Write("[RR]ReliableInOrder packet succes"); - Peer.AddReliablePacket(_deliveryMethod, packet); + ProcessIncomingPacket(packet); _remoteSequence = (_remoteSequence + 1) % NetConstants.MaxSequence; if (_ordered) @@ -304,7 +413,7 @@ public override bool ProcessPacket(NetPacket packet) { //process holden packet _receivedPackets[_remoteSequence % _windowSize] = null; - Peer.AddReliablePacket(_deliveryMethod, p); + ProcessIncomingPacket(p); _remoteSequence = (_remoteSequence + 1) % NetConstants.MaxSequence; } } @@ -328,7 +437,7 @@ public override bool ProcessPacket(NetPacket packet) else { _earlyReceived[ackIdx] = true; - Peer.AddReliablePacket(_deliveryMethod, packet); + ProcessIncomingPacket(packet); } return true; } diff --git a/LiteNetLib/SequencedChannel.cs b/LiteNetLib/SequencedChannel.cs index a3763ee0..4bd1d778 100644 --- a/LiteNetLib/SequencedChannel.cs +++ b/LiteNetLib/SequencedChannel.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace LiteNetLib { @@ -13,7 +13,7 @@ internal sealed class SequencedChannel : BaseChannel private readonly byte _id; private long _lastPacketSendTime; - public SequencedChannel(NetPeer peer, bool reliable, byte id) : base(peer) + public SequencedChannel(LiteNetPeer peer, bool reliable, byte id) : base(peer) { _id = id; _reliable = reliable; @@ -21,7 +21,7 @@ public SequencedChannel(NetPeer peer, bool reliable, byte id) : base(peer) _ackPacket = new NetPacket(PacketProperty.Ack, 0) {ChannelId = id}; } - protected override bool SendNextPackets() + public override bool SendNextPackets() { if (_reliable && OutgoingQueue.Count == 0) { @@ -39,21 +39,25 @@ protected override bool SendNextPackets() } else { - while (OutgoingQueue.TryDequeue(out var packet)) + lock (OutgoingQueue) { - _localSequence = (_localSequence + 1) % NetConstants.MaxSequence; - packet.Sequence = (ushort)_localSequence; - packet.ChannelId = _id; - Peer.SendUserData(packet); - - if (_reliable && OutgoingQueue.Count == 0) - { - _lastPacketSendTime = DateTime.UtcNow.Ticks; - _lastPacket = packet; - } - else + while (OutgoingQueue.Count > 0) { - Peer.NetManager.PoolRecycle(packet); + NetPacket packet = OutgoingQueue.Dequeue(); + _localSequence = (_localSequence + 1) % NetConstants.MaxSequence; + packet.Sequence = (ushort)_localSequence; + packet.ChannelId = _id; + Peer.SendUserData(packet); + + if (_reliable && OutgoingQueue.Count == 0) + { + _lastPacketSendTime = DateTime.UtcNow.Ticks; + _lastPacket = packet; + } + else + { + Peer.NetManager.PoolRecycle(packet); + } } } } diff --git a/LiteNetLib/Trimming.cs b/LiteNetLib/Trimming.cs new file mode 100644 index 00000000..6c575b65 --- /dev/null +++ b/LiteNetLib/Trimming.cs @@ -0,0 +1,12 @@ +#if NET5_0_OR_GREATER +using System.Diagnostics.CodeAnalysis; +using static System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes; + +namespace LiteNetLib +{ + internal static class Trimming + { + internal const DynamicallyAccessedMemberTypes SerializerMemberTypes = PublicProperties | NonPublicProperties; + } +} +#endif diff --git a/LiteNetLib/Utils/FastBitConverter.cs b/LiteNetLib/Utils/FastBitConverter.cs index 3ecd10c7..202cf134 100644 --- a/LiteNetLib/Utils/FastBitConverter.cs +++ b/LiteNetLib/Utils/FastBitConverter.cs @@ -1,25 +1,35 @@ using System; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; +#if UNITY_ANDROID +using Unity.Collections.LowLevel.Unsafe; +#endif namespace LiteNetLib.Utils { public static class FastBitConverter { -#if (LITENETLIB_UNSAFE || LITENETLIB_UNSAFELIB || NETCOREAPP3_1 || NET5_0 || NETCOREAPP3_0_OR_GREATER) && !BIGENDIAN -#if LITENETLIB_UNSAFE + /// + /// Converts a value of type into a byte array starting at the specified index. + /// + /// The type of the value to convert. Must be an unmanaged/blittable type. + /// The destination byte array. + /// The zero-based index in at which to begin writing. + /// The value to be converted and written. + /// + /// Thrown when the array is too small to contain the value at the given . + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void GetBytes(byte[] bytes, int startIndex, T value) where T : unmanaged { int size = sizeof(T); if (bytes.Length < startIndex + size) ThrowIndexOutOfRangeException(); -#if LITENETLIB_UNSAFELIB || NETCOREAPP3_1 || NET5_0 || NETCOREAPP3_0_OR_GREATER - Unsafe.As(ref bytes[startIndex]) = value; +#if NET8_0_OR_GREATER + Unsafe.WriteUnaligned(ref bytes[startIndex], value); #else fixed (byte* ptr = &bytes[startIndex]) { -#if UNITY_ANDROID + #if UNITY_ANDROID // On some android systems, assigning *(T*)ptr throws a NRE if // the ptr isn't aligned (i.e. if Position is 1,2,3,5, etc.). // Here we have to use memcpy. @@ -31,145 +41,13 @@ public static unsafe void GetBytes(byte[] bytes, int startIndex, T value) whe // value types, but all blittable types are anyway. T* valueBuffer = stackalloc T[1] { value }; UnsafeUtility.MemCpy(ptr, valueBuffer, size); -#else + #else *(T*)ptr = value; -#endif + #endif } #endif } -#else - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void GetBytes(byte[] bytes, int startIndex, T value) where T : unmanaged - { - if (bytes.Length < startIndex + Unsafe.SizeOf()) - ThrowIndexOutOfRangeException(); - Unsafe.As(ref bytes[startIndex]) = value; - } -#endif private static void ThrowIndexOutOfRangeException() => throw new IndexOutOfRangeException(); -#else - [StructLayout(LayoutKind.Explicit)] - private struct ConverterHelperDouble - { - [FieldOffset(0)] - public ulong Along; - - [FieldOffset(0)] - public double Adouble; - } - - [StructLayout(LayoutKind.Explicit)] - private struct ConverterHelperFloat - { - [FieldOffset(0)] - public int Aint; - - [FieldOffset(0)] - public float Afloat; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WriteLittleEndian(byte[] buffer, int offset, ulong data) - { -#if BIGENDIAN - buffer[offset + 7] = (byte)(data); - buffer[offset + 6] = (byte)(data >> 8); - buffer[offset + 5] = (byte)(data >> 16); - buffer[offset + 4] = (byte)(data >> 24); - buffer[offset + 3] = (byte)(data >> 32); - buffer[offset + 2] = (byte)(data >> 40); - buffer[offset + 1] = (byte)(data >> 48); - buffer[offset ] = (byte)(data >> 56); -#else - buffer[offset] = (byte)(data); - buffer[offset + 1] = (byte)(data >> 8); - buffer[offset + 2] = (byte)(data >> 16); - buffer[offset + 3] = (byte)(data >> 24); - buffer[offset + 4] = (byte)(data >> 32); - buffer[offset + 5] = (byte)(data >> 40); - buffer[offset + 6] = (byte)(data >> 48); - buffer[offset + 7] = (byte)(data >> 56); -#endif - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WriteLittleEndian(byte[] buffer, int offset, int data) - { -#if BIGENDIAN - buffer[offset + 3] = (byte)(data); - buffer[offset + 2] = (byte)(data >> 8); - buffer[offset + 1] = (byte)(data >> 16); - buffer[offset ] = (byte)(data >> 24); -#else - buffer[offset] = (byte)(data); - buffer[offset + 1] = (byte)(data >> 8); - buffer[offset + 2] = (byte)(data >> 16); - buffer[offset + 3] = (byte)(data >> 24); -#endif - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteLittleEndian(byte[] buffer, int offset, short data) - { -#if BIGENDIAN - buffer[offset + 1] = (byte)(data); - buffer[offset ] = (byte)(data >> 8); -#else - buffer[offset] = (byte)(data); - buffer[offset + 1] = (byte)(data >> 8); -#endif - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void GetBytes(byte[] bytes, int startIndex, double value) - { - ConverterHelperDouble ch = new ConverterHelperDouble { Adouble = value }; - WriteLittleEndian(bytes, startIndex, ch.Along); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void GetBytes(byte[] bytes, int startIndex, float value) - { - ConverterHelperFloat ch = new ConverterHelperFloat { Afloat = value }; - WriteLittleEndian(bytes, startIndex, ch.Aint); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void GetBytes(byte[] bytes, int startIndex, short value) - { - WriteLittleEndian(bytes, startIndex, value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void GetBytes(byte[] bytes, int startIndex, ushort value) - { - WriteLittleEndian(bytes, startIndex, (short)value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void GetBytes(byte[] bytes, int startIndex, int value) - { - WriteLittleEndian(bytes, startIndex, value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void GetBytes(byte[] bytes, int startIndex, uint value) - { - WriteLittleEndian(bytes, startIndex, (int)value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void GetBytes(byte[] bytes, int startIndex, long value) - { - WriteLittleEndian(bytes, startIndex, (ulong)value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void GetBytes(byte[] bytes, int startIndex, ulong value) - { - WriteLittleEndian(bytes, startIndex, value); - } -#endif } } diff --git a/LiteNetLib/Utils/INetSerializable.cs b/LiteNetLib/Utils/INetSerializable.cs index 92f14bee..423e0468 100644 --- a/LiteNetLib/Utils/INetSerializable.cs +++ b/LiteNetLib/Utils/INetSerializable.cs @@ -1,8 +1,23 @@ namespace LiteNetLib.Utils { + /// + /// Interface for implementing custom data serialization for network transmission. + /// + /// + /// This is the most efficient way to send complex objects as it avoids reflection. + /// public interface INetSerializable { + /// + /// Writes the object data into the provided . + /// + /// The writer to pack data into. void Serialize(NetDataWriter writer); + + /// + /// Reads the object data from the provided . + /// + /// The reader to extract data from. void Deserialize(NetDataReader reader); } } diff --git a/LiteNetLib/Utils/NetDataReader.cs b/LiteNetLib/Utils/NetDataReader.cs index 70222653..2e230009 100644 --- a/LiteNetLib/Utils/NetDataReader.cs +++ b/LiteNetLib/Utils/NetDataReader.cs @@ -1,6 +1,8 @@ using System; using System.Net; -using System.Text; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace LiteNetLib.Utils { @@ -9,32 +11,125 @@ public class NetDataReader protected byte[] _data; protected int _position; protected int _dataSize; - private int _offset; + protected int _offset; - public byte[] RawData => _data; - public int RawDataSize => _dataSize; - public int UserDataOffset => _offset; - public int UserDataSize => _dataSize - _offset; - public bool IsNull => _data == null; - public int Position => _position; - public bool EndOfData => _position == _dataSize; - public int AvailableBytes => _dataSize - _position; + private const int IPv4Size = 4; + private const int IPv6Size = 16; + private const int GuidSize = 16; - // Cache encoding instead of creating it with BinaryWriter each time - // 1000 readers before: 1MB GC, 30ms - // 1000 readers after: .8MB GC, 18ms - private static readonly UTF8Encoding _uTF8Encoding = new UTF8Encoding(false, true); + /// + /// Gets the internal array containing the raw network data. + /// + public byte[] RawData + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _data; + } + + /// + /// Gets the total size of the buffer. + /// + public int RawDataSize + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _dataSize; + } - public void SkipBytes(int count) + /// + /// Gets the starting offset of the user payload within the . + /// + public int UserDataOffset { - _position += count; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _offset; + } + + /// + /// Gets the size of the user payload excluding the initial . + /// + public int UserDataSize + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _dataSize - _offset; + } + + /// + /// Gets a value indicating whether the internal data buffer is . + /// + public bool IsNull + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _data == null; + } + + /// + /// Gets the current read position within the buffer. + /// + public int Position + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _position; + } + + /// + /// Gets a value indicating whether the has reached the end of the data. + /// + public bool EndOfData + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _position == _dataSize; + } + + /// + /// Gets the number of s remaining to be read. + /// + public int AvailableBytes + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _dataSize - _position; + } + + /// + /// Verifies that the buffer has at least s available to read. + /// + /// The number of s required. + /// Thrown if exceeds or is negative. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void EnsureAvailable(int count) + { + int available = _dataSize - _position; + if (count < 0 || available < 0 || count > available) + ThrowNotEnoughData(count); } - public void SetPosition(int position) + /// + /// Throws an indicating that there is not enough data to read. + /// + /// The number of bytes that were attempted to be read. + /// Always thrown. + [MethodImpl(MethodImplOptions.NoInlining)] + private void ThrowNotEnoughData(int count) { - _position = position; + throw new InvalidOperationException( + $"Not enough data to read {count} byte(s). Position={_position}, DataSize={_dataSize}"); } + /// + /// Advances the by the specified . + /// + /// The number of s to skip. + public void SkipBytes(int count) => _position += count; + + /// + /// Sets the current read to a specific index. + /// + /// The index to move the to. + public void SetPosition(int position) => _position = position; + + /// + /// Reinitializes the reader using data from a . + /// + /// The source . public void SetSource(NetDataWriter dataWriter) { _data = dataWriter.Data; @@ -43,6 +138,10 @@ public void SetSource(NetDataWriter dataWriter) _dataSize = dataWriter.Length; } + /// + /// Reinitializes the reader using a array. + /// + /// The source array. public void SetSource(byte[] source) { _data = source; @@ -51,6 +150,12 @@ public void SetSource(byte[] source) _dataSize = source.Length; } + /// + /// Reinitializes the reader using a segment of a array. + /// + /// The source array. + /// The starting index for reading. + /// The total number of s available to read from the . public void SetSource(byte[] source, int offset, int maxSize) { _data = source; @@ -59,620 +164,864 @@ public void SetSource(byte[] source, int offset, int maxSize) _dataSize = maxSize; } - public NetDataReader() - { - - } + public NetDataReader() { } - public NetDataReader(NetDataWriter writer) - { - SetSource(writer); - } + public NetDataReader(NetDataWriter writer) => SetSource(writer); - public NetDataReader(byte[] source) - { - SetSource(source); - } + public NetDataReader(byte[] source) => SetSource(source); - public NetDataReader(byte[] source, int offset, int maxSize) - { - SetSource(source, offset, maxSize); - } + public NetDataReader(byte[] source, int offset, int maxSize) => SetSource(source, offset, maxSize); #region GetMethods - public IPEndPoint GetNetEndPoint() - { - string host = GetString(1000); - int port = GetInt(); - return NetUtils.MakeEndPoint(host, port); - } - public byte GetByte() + /// + /// Deserializes a that implements . + /// + /// A type implementing . + /// The deserialized output. + public void Get(out T result) where T : struct, INetSerializable { - byte res = _data[_position]; - _position += 1; - return res; + result = default; + result.Deserialize(this); } - public sbyte GetSByte() + /// + /// Deserializes a that implements using a provided constructor. + /// + /// A type implementing . + /// The deserialized instance output. + /// A factory used to instantiate the . + public void Get(out T result, Func constructor) where T : class, INetSerializable { - var b = (sbyte)_data[_position]; - _position++; - return b; + result = constructor(); + result.Deserialize(this); } - public bool[] GetBoolArray() + /// + /// Deserializes an and assigns it to the parameter. + /// + /// The deserialized output. + public void Get(out IPEndPoint result) => result = GetIPEndPoint(); + + /// + /// Reads an from the current . + /// + /// The deserialized . + /// + /// Reads a to determine the (0 for IPv4, 1 for IPv6), + /// followed by the address bytes and a 2-byte port. + /// + public IPEndPoint GetIPEndPoint() { - ushort size = BitConverter.ToUInt16(_data, _position); - _position += 2; - var arr = new bool[size]; - Buffer.BlockCopy(_data, _position, arr, 0, size); + bool isIPv4 = GetByte() == 0; + + int size = isIPv4 ? IPv4Size : IPv6Size; + EnsureAvailable(size); + + IPAddress address = new IPAddress(new ReadOnlySpan(_data, _position, size)); _position += size; - return arr; + return new IPEndPoint(address, GetUShort()); } - public ushort[] GetUShortArray() - { - ushort size = BitConverter.ToUInt16(_data, _position); - _position += 2; - var arr = new ushort[size]; - Buffer.BlockCopy(_data, _position, arr, 0, size * 2); - _position += size * 2; - return arr; - } + /// Reads a and assigns it to . + public void Get(out byte result) => result = GetByte(); - public short[] GetShortArray() - { - ushort size = BitConverter.ToUInt16(_data, _position); - _position += 2; - var arr = new short[size]; - Buffer.BlockCopy(_data, _position, arr, 0, size * 2); - _position += size * 2; - return arr; - } + /// Reads an and assigns it to . + public void Get(out sbyte result) => result = GetSByte(); - public long[] GetLongArray() - { - ushort size = BitConverter.ToUInt16(_data, _position); - _position += 2; - var arr = new long[size]; - Buffer.BlockCopy(_data, _position, arr, 0, size * 8); - _position += size * 8; - return arr; - } + /// Reads a and assigns it to . + public void Get(out bool result) => result = GetBool(); - public ulong[] GetULongArray() - { - ushort size = BitConverter.ToUInt16(_data, _position); - _position += 2; - var arr = new ulong[size]; - Buffer.BlockCopy(_data, _position, arr, 0, size * 8); - _position += size * 8; - return arr; - } + /// Reads a and assigns it to . + public void Get(out char result) => result = GetChar(); - public int[] GetIntArray() - { - ushort size = BitConverter.ToUInt16(_data, _position); - _position += 2; - var arr = new int[size]; - Buffer.BlockCopy(_data, _position, arr, 0, size * 4); - _position += size * 4; - return arr; - } + /// Reads a and assigns it to . + public void Get(out ushort result) => result = GetUShort(); - public uint[] GetUIntArray() - { - ushort size = BitConverter.ToUInt16(_data, _position); - _position += 2; - var arr = new uint[size]; - Buffer.BlockCopy(_data, _position, arr, 0, size * 4); - _position += size * 4; - return arr; - } + /// Reads a and assigns it to . + public void Get(out short result) => result = GetShort(); - public float[] GetFloatArray() - { - ushort size = BitConverter.ToUInt16(_data, _position); - _position += 2; - var arr = new float[size]; - Buffer.BlockCopy(_data, _position, arr, 0, size * 4); - _position += size * 4; - return arr; - } + /// Reads a and assigns it to . + public void Get(out ulong result) => result = GetULong(); - public double[] GetDoubleArray() - { - ushort size = BitConverter.ToUInt16(_data, _position); - _position += 2; - var arr = new double[size]; - Buffer.BlockCopy(_data, _position, arr, 0, size * 8); - _position += size * 8; - return arr; - } + /// Reads a and assigns it to . + public void Get(out long result) => result = GetLong(); - public string[] GetStringArray() - { - ushort arraySize = GetUShort(); - var arr = new string[arraySize]; - for (int i = 0; i < arraySize; i++) - { - arr[i] = GetString(); - } - return arr; - } + /// Reads a and assigns it to . + public void Get(out uint result) => result = GetUInt(); - public string[] GetStringArray(int maxStringLength) - { - ushort arraySize = GetUShort(); - var arr = new string[arraySize]; - for (int i = 0; i < arraySize; i++) - { - arr[i] = GetString(maxStringLength); - } - return arr; - } + /// Reads an and assigns it to . + public void Get(out int result) => result = GetInt(); - public bool GetBool() - { - bool res = _data[_position] > 0; - _position += 1; - return res; - } + /// Reads a and assigns it to . + public void Get(out double result) => result = GetDouble(); - public char GetChar() - { - return (char)GetUShort(); - } + /// Reads a and assigns it to . + public void Get(out float result) => result = GetFloat(); - public ushort GetUShort() - { - ushort result = BitConverter.ToUInt16(_data, _position); - _position += 2; - return result; - } + /// Reads a and assigns it to . + public void Get(out string result) => result = GetString(); + + /// Reads a with a length limit and assigns it to . + public void Get(out string result, int maxLength) => result = GetString(maxLength); + + /// Reads a and assigns it to . + public void Get(out Guid result) => result = GetGuid(); - public short GetShort() + /// + /// Reads an array of unmanaged values prefixed by a length. + /// + /// An unmanaged type. + /// A new array of type . + public unsafe T[] GetUnmanagedArray() where T : unmanaged { - short result = BitConverter.ToInt16(_data, _position); - _position += 2; + ushort length = GetUShort(); + + int byteLength = checked(length * sizeof(T)); + EnsureAvailable(byteLength); + + ReadOnlySpan slice = _data.AsSpan(_position, byteLength); + T[] result = MemoryMarshal.Cast(slice) + .ToArray(); + _position += byteLength; return result; } - public long GetLong() + /// + /// Reads an array of values by performing a direct memory copy. + /// + /// The element type. + /// The size of a single element in s. + /// A new array of type . + public T[] GetArray(ushort size) { - long result = BitConverter.ToInt64(_data, _position); - _position += 8; + ushort length = GetUShort(); + + int byteLength = checked(length * size); + EnsureAvailable(byteLength); + + T[] result = new T[length]; + if (byteLength > 0) + Buffer.BlockCopy(_data, _position, result, 0, byteLength); + + _position += byteLength; return result; } - public ulong GetULong() + /// + /// Reads an array of objects implementing . + /// + /// A type with a parameterless constructor implementing . + /// A new array of type . + public T[] GetArray() where T : INetSerializable, new() { - ulong result = BitConverter.ToUInt64(_data, _position); - _position += 8; + ushort length = GetUShort(); + T[] result = new T[length]; + for (int i = 0; i < length; i++) + { + var item = new T(); + item.Deserialize(this); + result[i] = item; + } return result; } - public int GetInt() - { - int result = BitConverter.ToInt32(_data, _position); - _position += 4; + /// + /// Reads an array of objects implementing using a specific constructor. + /// + /// A type implementing . + /// The factory used to create instances. + /// A new array of type . + public T[] GetArray(Func constructor) where T : class, INetSerializable + { + ushort length = GetUShort(); + T[] result = new T[length]; + for (int i = 0; i < length; i++) + Get(out result[i], constructor); return result; } - public uint GetUInt() + /// Reads an array of values. + public bool[] GetBoolArray() => GetUnmanagedArray(); + + /// Reads an array of values. + public ushort[] GetUShortArray() => GetUnmanagedArray(); + + /// Reads an array of values. + public short[] GetShortArray() => GetUnmanagedArray(); + + /// Reads an array of values. + public int[] GetIntArray() => GetUnmanagedArray(); + + /// Reads an array of values. + public uint[] GetUIntArray() => GetUnmanagedArray(); + + /// Reads an array of values. + public float[] GetFloatArray() => GetUnmanagedArray(); + + /// Reads an array of values. + public double[] GetDoubleArray() => GetUnmanagedArray(); + + /// Reads an array of values. + public long[] GetLongArray() => GetUnmanagedArray(); + + /// Reads an array of values. + public ulong[] GetULongArray() => GetUnmanagedArray(); + + /// + /// Reads an array of values. + /// + /// A new array. + /// + /// Reads a 2-byte length header followed by each element. + /// + public string[] GetStringArray() { - uint result = BitConverter.ToUInt32(_data, _position); - _position += 4; + ushort length = GetUShort(); + EnsureAvailable(checked(length * sizeof(ushort))); // 2 bytes (ushort) for string length + + string[] result = new string[length]; + for (int i = 0; i < length; i++) + { + result[i] = GetString(); + } + return result; } - public float GetFloat() + /// + /// Reads an array of values with a maximum character limit per element. + /// + /// The maximum number of characters allowed per . + /// A new array. + /// + /// Strings exceeding are returned as . + /// + public string[] GetStringArray(int maxStringLength) { - float result = BitConverter.ToSingle(_data, _position); - _position += 4; + ushort length = GetUShort(); + EnsureAvailable(checked(length * sizeof(ushort))); // 2 bytes (ushort) for string length + + string[] result = new string[length]; + for (int i = 0; i < length; i++) + { + result[i] = GetString(maxStringLength); + } + return result; } - public double GetDouble() + /// Reads the next from the buffer. + public byte GetByte() { - double result = BitConverter.ToDouble(_data, _position); - _position += 8; - return result; + EnsureAvailable(1); + return _data[_position++]; } + /// Reads the next from the buffer. + public sbyte GetSByte() => (sbyte)GetByte(); + + /// Reads a value from the current position. + /// if the byte is 1; otherwise, . + public bool GetBool() => GetByte() == 1; + + /// Reads a value as a 2-byte . + public char GetChar() => GetUnmanaged(); + + /// Reads a value using unmanaged memory access. + public ushort GetUShort() => GetUnmanaged(); + + /// Reads a value using unmanaged memory access. + public short GetShort() => GetUnmanaged(); + + /// Reads a value using unmanaged memory access. + public long GetLong() => GetUnmanaged(); + + /// Reads a value using unmanaged memory access. + public ulong GetULong() => GetUnmanaged(); + + /// Reads an value using unmanaged memory access. + public int GetInt() => GetUnmanaged(); + + /// Reads a value using unmanaged memory access. + public uint GetUInt() => GetUnmanaged(); + + /// Reads a value using unmanaged memory access. + public float GetFloat() => GetUnmanaged(); + + /// Reads a value using unmanaged memory access. + public double GetDouble() => GetUnmanaged(); + /// - /// Note that "maxLength" only limits the number of characters in a string, not its size in bytes. + /// Reads a with a maximum character limit. /// - /// "string.Empty" if value > "maxLength" + /// The maximum allowed character count. + /// The deserialized , or if the character count exceeds . + /// + /// Note that limits the number of characters, not the total size in s. + /// public string GetString(int maxLength) { ushort size = GetUShort(); if (size == 0) - { - return null; - } + return string.Empty; int actualSize = size - 1; - if (actualSize >= NetDataWriter.StringBufferMaxLength) - { - return null; - } + EnsureAvailable(actualSize); - ArraySegment data = GetBytesSegment(actualSize); + string result = maxLength > 0 && + NetDataWriter.uTF8Encoding.GetCharCount(_data, _position, actualSize) > maxLength + ? string.Empty + : NetDataWriter.uTF8Encoding.GetString(_data, _position, actualSize); - return (maxLength > 0 && _uTF8Encoding.GetCharCount(data.Array, data.Offset, data.Count) > maxLength) ? - string.Empty : - _uTF8Encoding.GetString(data.Array, data.Offset, data.Count); + _position += actualSize; + return result; } + /// + /// Reads a from the current position. + /// + /// The deserialized . public string GetString() { ushort size = GetUShort(); if (size == 0) - { - return null; - } + return string.Empty; int actualSize = size - 1; - if (actualSize >= NetDataWriter.StringBufferMaxLength) - { - return null; - } + EnsureAvailable(actualSize); + + string result = NetDataWriter.uTF8Encoding.GetString(_data, _position, actualSize); + _position += actualSize; + return result; + } + + /// + /// Reads a prefixed with a 4-byte length header. + /// + /// The deserialized . + public string GetLargeString() + { + int size = GetInt(); + if (size <= 0) + return string.Empty; - ArraySegment data = GetBytesSegment(actualSize); + EnsureAvailable(size); - return _uTF8Encoding.GetString(data.Array, data.Offset, data.Count); + string result = NetDataWriter.uTF8Encoding.GetString(_data, _position, size); + _position += size; + return result; } + /// + /// Reads a 16-byte . + /// + /// The deserialized . + public Guid GetGuid() + { + EnsureAvailable(GuidSize); + var result = new Guid(_data.AsSpan(_position, GuidSize)); + _position += GuidSize; + return result; + } + + /// + /// Gets an of s from the current position. + /// + /// The number of s to include in the segment. + /// An wrapping the internal buffer. public ArraySegment GetBytesSegment(int count) { + EnsureAvailable(count); ArraySegment segment = new ArraySegment(_data, _position, count); _position += count; return segment; } + /// + /// Gets an containing all remaining s. + /// + /// An from the current position to the end of the data. public ArraySegment GetRemainingBytesSegment() { ArraySegment segment = new ArraySegment(_data, _position, AvailableBytes); - _position = _data.Length; + _position = _dataSize; return segment; } - public T Get() where T : INetSerializable, new() + /// + /// Returns a of s containing all remaining data. + /// + /// A from the current to the end of the buffer. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan GetRemainingBytesSpan() + { + var result = new ReadOnlySpan(_data, _position, AvailableBytes); + _position = _dataSize; + return result; + } + + /// + /// Returns a of s containing all remaining data. + /// + /// A from the current to the end of the buffer. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlyMemory GetRemainingBytesMemory() { - var obj = new T(); - obj.Deserialize(this); - return obj; + var result = new ReadOnlyMemory(_data, _position, AvailableBytes); + _position = _dataSize; + return result; } + /// + /// Reads all remaining s and returns them as a new array. + /// + /// A new array containing the remaining data. + /// + /// This method performs a heap allocation and advances the to the end of the data. + /// public byte[] GetRemainingBytes() { - byte[] outgoingData = new byte[AvailableBytes]; - Buffer.BlockCopy(_data, _position, outgoingData, 0, AvailableBytes); - _position = _data.Length; - return outgoingData; + int size = _dataSize - _position; + if (size == 0) + return Array.Empty(); + + byte[] result = new byte[size]; + Buffer.BlockCopy(_data, _position, result, 0, size); + _position = _dataSize; + return result; + } + + /// + /// Deserializes a that implements . + /// + /// A type implementing . + /// The deserialized . + public T Get() where T : struct, INetSerializable + { + Get(out T result); + return result; + } + + /// + /// Deserializes a that implements using a provided constructor. + /// + /// A type implementing . + /// The factory used to instantiate the . + /// A new instance of . + public T Get(Func constructor) where T : class, INetSerializable + { + Get(out T result, constructor); + return result; } + /// + /// Copies a specified number of s into a destination array at a specific offset. + /// + /// The array to copy data into. + /// The starting index in the array. + /// The number of s to read. public void GetBytes(byte[] destination, int start, int count) { + EnsureAvailable(count); Buffer.BlockCopy(_data, _position, destination, start, count); _position += count; } + /// + /// Copies a specified number of s into a destination array starting at index 0. + /// + /// The array to copy data into. + /// The number of s to read. public void GetBytes(byte[] destination, int count) { + EnsureAvailable(count); Buffer.BlockCopy(_data, _position, destination, 0, count); _position += count; } - public sbyte[] GetSBytesWithLength() - { - int length = GetInt(); - sbyte[] outgoingData = new sbyte[length]; - Buffer.BlockCopy(_data, _position, outgoingData, 0, length); - _position += length; - return outgoingData; + /// + /// Reads an array prefixed with its length. + /// + /// A new array. + public sbyte[] GetSBytesWithLength() => GetUnmanagedArray(); + + /// + /// Reads a array prefixed with its length. + /// + /// A new array. + public byte[] GetBytesWithLength() => GetUnmanagedArray(); + + /// + /// Reads a value of type from the internal byte buffer at the current position, + /// advancing the position by the size of . + /// + /// An unmanaged value type to read from the buffer. + /// The value of type read from the buffer. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe T GetUnmanaged() where T : unmanaged + { + var size = sizeof(T); + EnsureAvailable(size); + +#if NET8_0_OR_GREATER + var value = Unsafe.ReadUnaligned(ref _data[_position]); +#else + T value; + fixed (byte* ptr = &_data[_position]) + { + value = *(T*)ptr; + } +#endif + + _position += size; + return value; } - public byte[] GetBytesWithLength() - { - int length = GetInt(); - byte[] outgoingData = new byte[length]; - Buffer.BlockCopy(_data, _position, outgoingData, 0, length); - _position += length; - return outgoingData; + /// + /// Reads a nullable value of type from the internal byte buffer at the current position, + /// first reading a indicating whether the value is present, + /// and then reading the value itself if it exists.
+ /// Advances the position by 1 byte for the presence flag plus the size of if the value is present. + ///
+ /// An unmanaged value type to read from the buffer. + /// + /// The nullable value of type read from the buffer. + /// Returns if the presence flag indicates no value. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T? GetNullableUnmanaged() where T : unmanaged + { + var hasValue = GetBool(); + if (!hasValue) + { + return null; + } + + return GetUnmanaged(); } + + /// + /// Reads an enum value of type from the internal data buffer at the current position.
+ /// Advances the position by the size of . + ///
+ /// An unmanaged enum type to read. + /// The enum value read from the buffer. + public unsafe T GetEnum() where T : unmanaged, Enum => GetUnmanaged(); + #endregion #region PeekMethods - public byte PeekByte() - { - return _data[_position]; - } + /// Reads the at the current position without advancing the . + public byte PeekByte() => _data[_position]; - public sbyte PeekSByte() - { - return (sbyte)_data[_position]; - } + /// Reads the at the current position without advancing the . + public sbyte PeekSByte() => (sbyte)PeekByte(); - public bool PeekBool() - { - return _data[_position] > 0; - } + /// Reads the at the current position without advancing the . + public bool PeekBool() => PeekByte() == 1; - public char PeekChar() - { - return (char)PeekUShort(); - } + /// Reads the at the current position without advancing the . + public char PeekChar() => PeekUnmanaged(); - public ushort PeekUShort() - { - return BitConverter.ToUInt16(_data, _position); - } + /// Reads the at the current position without advancing the . + public ushort PeekUShort() => PeekUnmanaged(); - public short PeekShort() - { - return BitConverter.ToInt16(_data, _position); - } + /// Reads the at the current position without advancing the . + public short PeekShort() => PeekUnmanaged(); - public long PeekLong() - { - return BitConverter.ToInt64(_data, _position); - } + /// Reads the at the current position without advancing the . + public long PeekLong() => PeekUnmanaged(); - public ulong PeekULong() - { - return BitConverter.ToUInt64(_data, _position); - } + /// Reads the at the current position without advancing the . + public ulong PeekULong() => PeekUnmanaged(); - public int PeekInt() - { - return BitConverter.ToInt32(_data, _position); - } + /// Reads the at the current position without advancing the . + public int PeekInt() => PeekUnmanaged(); - public uint PeekUInt() - { - return BitConverter.ToUInt32(_data, _position); - } + /// Reads the at the current position without advancing the . + public uint PeekUInt() => PeekUnmanaged(); - public float PeekFloat() - { - return BitConverter.ToSingle(_data, _position); - } + /// Reads the at the current position without advancing the . + public float PeekFloat() => PeekUnmanaged(); - public double PeekDouble() - { - return BitConverter.ToDouble(_data, _position); - } + /// Reads the at the current position without advancing the . + public double PeekDouble() => PeekUnmanaged(); + /// + /// Reads a with a character limit without advancing the . + /// + /// Maximum allowed character count. + /// Strings exceeding are returned as . public string PeekString(int maxLength) { ushort size = PeekUShort(); if (size == 0) - { - return null; - } + return string.Empty; int actualSize = size - 1; - if (actualSize >= NetDataWriter.StringBufferMaxLength) - { - return null; - } - - return (maxLength > 0 && _uTF8Encoding.GetCharCount(_data, _position + 2, actualSize) > maxLength) ? - string.Empty : - _uTF8Encoding.GetString(_data, _position + 2, actualSize); + return (maxLength > 0 && NetDataWriter.uTF8Encoding.GetCharCount(_data, _position + sizeof(ushort), actualSize) > maxLength) + ? string.Empty + : NetDataWriter.uTF8Encoding.GetString(_data, _position + sizeof(ushort), actualSize); } + /// + /// Reads a without advancing the . + /// public string PeekString() { ushort size = PeekUShort(); if (size == 0) - { - return null; - } + return string.Empty; int actualSize = size - 1; - if (actualSize >= NetDataWriter.StringBufferMaxLength) - { - return null; - } - - return _uTF8Encoding.GetString(_data, _position + 2, actualSize); + return NetDataWriter.uTF8Encoding.GetString(_data, _position + sizeof(ushort), actualSize); } - #endregion - #region TryGetMethods - public bool TryGetByte(out byte result) - { - if (AvailableBytes >= 1) + /// + /// Reads an unmanaged value of type at the current position without advancing the . + /// + /// An unmanaged value type. + /// The value read from the buffer. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe T PeekUnmanaged() where T : unmanaged + { +#if NET8_0_OR_GREATER + return Unsafe.ReadUnaligned(ref _data[_position]); +#else + T value; + fixed (byte* ptr = &_data[_position]) { - result = GetByte(); - return true; + value = *(T*)ptr; } - result = 0; - return false; + return value; +#endif } - public bool TryGetSByte(out sbyte result) + /// + /// Returns a of s containing all remaining data without advancing the reader . + /// + /// A from the current to the end of the buffer. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan PeekRemainingBytesSpan() { - if (AvailableBytes >= 1) - { - result = GetSByte(); - return true; - } - result = 0; - return false; + return new ReadOnlySpan(_data, _position, AvailableBytes); } - public bool TryGetBool(out bool result) + /// + /// Returns a of s containing all remaining data without advancing the reader . + /// + /// A from the current to the end of the buffer. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlyMemory PeekRemainingBytesMemory() { - if (AvailableBytes >= 1) - { - result = GetBool(); - return true; - } - result = false; - return false; + return new ReadOnlyMemory(_data, _position, AvailableBytes); } - public bool TryGetChar(out char result) + /// + /// Reads all remaining s and returns them as a new array without advancing the reader . + /// + /// A new array containing the remaining data. + /// + /// This method performs a heap allocation by copying the data into a new array. + /// + public byte[] PeekRemainingBytes() { - if (!TryGetUShort(out ushort uShortValue)) - { - result = '\0'; - return false; - } - result = (char)uShortValue; - return true; - } + int size = _dataSize - _position; + if (size == 0) + return Array.Empty(); - public bool TryGetShort(out short result) - { - if (AvailableBytes >= 2) - { - result = GetShort(); - return true; - } - result = 0; - return false; + byte[] result = new byte[size]; + Buffer.BlockCopy(_data, _position, result, 0, size); + return result; } - public bool TryGetUShort(out ushort result) - { - if (AvailableBytes >= 2) - { - result = GetUShort(); - return true; - } - result = 0; - return false; - } + #endregion - public bool TryGetInt(out int result) - { - if (AvailableBytes >= 4) - { - result = GetInt(); - return true; - } - result = 0; - return false; - } + #region TryGetMethods - public bool TryGetUInt(out uint result) + /// Attempts to read a without throwing an exception. + /// The deserialized , or 0 if failed. + /// if enough data was available; otherwise, . + public bool TryGetByte(out byte result) => TryGetUnmanaged(out result); + + /// Attempts to read an without throwing an exception. + /// The deserialized , or 0 if failed. + /// if enough data was available; otherwise, . + public bool TryGetSByte(out sbyte result) => TryGetUnmanaged(out result); + + /// Attempts to read a without throwing an exception. + /// The deserialized , or if failed. + /// if enough data was available; otherwise, . + public bool TryGetBool(out bool result) => TryGetUnmanaged(out result); + + /// Attempts to read a without throwing an exception. + /// The deserialized , or '\0' if failed. + /// if enough data was available; otherwise, . + public bool TryGetChar(out char result) => TryGetUnmanaged(out result); + + /// Attempts to read a without throwing an exception. + /// The deserialized , or 0 if failed. + /// if enough data was available; otherwise, . + public bool TryGetShort(out short result) => TryGetUnmanaged(out result); + + /// Attempts to read a without throwing an exception. + /// The deserialized , or 0 if failed. + /// if enough data was available; otherwise, . + public bool TryGetUShort(out ushort result) => TryGetUnmanaged(out result); + + /// Attempts to read an without throwing an exception. + /// The deserialized , or 0 if failed. + /// if enough data was available; otherwise, . + public bool TryGetInt(out int result) => TryGetUnmanaged(out result); + + /// Attempts to read a without throwing an exception. + /// The deserialized , or 0 if failed. + /// if enough data was available; otherwise, . + public bool TryGetUInt(out uint result) => TryGetUnmanaged(out result); + + /// Attempts to read a without throwing an exception. + /// The deserialized , or 0 if failed. + /// if enough data was available; otherwise, . + public bool TryGetLong(out long result) => TryGetUnmanaged(out result); + + /// Attempts to read a without throwing an exception. + /// The deserialized , or 0 if failed. + /// if enough data was available; otherwise, . + public bool TryGetULong(out ulong result) => TryGetUnmanaged(out result); + + /// Attempts to read a without throwing an exception. + /// The deserialized , or 0 if failed. + /// if enough data was available; otherwise, . + public bool TryGetFloat(out float result) => TryGetUnmanaged(out result); + + /// Attempts to read a without throwing an exception. + /// The deserialized , or 0 if failed. + /// if enough data was available; otherwise, . + public bool TryGetDouble(out double result) => TryGetUnmanaged(out result); + + /// Attempts to read a without throwing an exception. + /// The deserialized , or if failed. + /// if enough data was available; otherwise, . + public bool TryGetString(out string result) { - if (AvailableBytes >= 4) + if (AvailableBytes < sizeof(ushort)) { - result = GetUInt(); - return true; + result = null; + return false; } - result = 0; - return false; - } - public bool TryGetLong(out long result) - { - if (AvailableBytes >= 8) - { - result = GetLong(); - return true; - } - result = 0; - return false; - } + ushort size = PeekUShort(); + int actualSize = size == 0 ? 0 : size - 1; - public bool TryGetULong(out ulong result) - { - if (AvailableBytes >= 8) + if (AvailableBytes < sizeof(ushort) + actualSize) { - result = GetULong(); - return true; + result = null; + return false; } - result = 0; - return false; + + result = GetString(); + return true; } - public bool TryGetFloat(out float result) + /// Attempts to read a array without throwing an exception. + /// The deserialized array, or if failed. + /// if enough data was available; otherwise, . + public bool TryGetStringArray(out string[] result) { - if (AvailableBytes >= 4) + if (AvailableBytes < sizeof(ushort)) { - result = GetFloat(); - return true; + result = null; + return false; } - result = 0; - return false; - } - public bool TryGetDouble(out double result) - { - if (AvailableBytes >= 8) + int startPosition = _position; + + ushort length = GetUShort(); + if (AvailableBytes < checked(length * sizeof(ushort))) // 2 bytes (ushort) for string length { - result = GetDouble(); - return true; + _position = startPosition; // Roll back to the original position + result = null; + return false; } - result = 0; - return false; - } - public bool TryGetString(out string result) - { - if (AvailableBytes >= 2) + string[] values = new string[length]; + for (int i = 0; i < length; i++) { - ushort strSize = PeekUShort(); - if (AvailableBytes >= strSize + 1) + if (!TryGetString(out values[i])) { - result = GetString(); - return true; + _position = startPosition; // Roll back to the original position + result = null; + return false; } } - result = null; - return false; + + result = values; + return true; } - public bool TryGetStringArray(out string[] result) + /// Attempts to read a array with a length header without throwing an exception. + /// The deserialized array, or if failed. + /// if enough data was available; otherwise, . + public bool TryGetBytesWithLength(out byte[] result) { - ushort strArrayLength; - if (!TryGetUShort(out strArrayLength)) + if (AvailableBytes < sizeof(ushort)) { result = null; return false; } - result = new string[strArrayLength]; - for (int i = 0; i < strArrayLength; i++) + ushort length = PeekUShort(); + if (AvailableBytes < sizeof(ushort) + length) { - if (!TryGetString(out result[i])) - { - result = null; - return false; - } + result = null; + return false; } + result = GetBytesWithLength(); return true; } - public bool TryGetBytesWithLength(out byte[] result) - { - if (AvailableBytes >= 4) + /// + /// Attempts to read a value of type from the internal byte buffer at the current position, + /// advancing the position by the size of if successful. + /// + /// An unmanaged value type to read from the buffer. + /// When this method returns, contains the value read from the buffer, or the default value if the read failed. + /// if enough data was available to read the value; otherwise, . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe bool TryGetUnmanaged(out T result) where T : unmanaged + { + int size = sizeof(T); + if (AvailableBytes < size) { - var length = PeekInt(); - if (length >= 0 && AvailableBytes >= length + 4) - { - result = GetBytesWithLength(); - return true; - } + result = default; + return false; + } + +#if NET8_0_OR_GREATER + result = Unsafe.ReadUnaligned(ref _data[_position]); +#else + fixed (byte* ptr = &_data[_position]) + { + result = *(T*)ptr; } - result = null; - return false; +#endif + + _position += size; + return true; } + #endregion + /// Clears the reader state and releases the reference to the internal buffer. public void Clear() { _position = 0; + _offset = 0; _dataSize = 0; _data = null; } diff --git a/LiteNetLib/Utils/NetDataWriter.cs b/LiteNetLib/Utils/NetDataWriter.cs index 1c9d724c..24f09b8d 100644 --- a/LiteNetLib/Utils/NetDataWriter.cs +++ b/LiteNetLib/Utils/NetDataWriter.cs @@ -1,6 +1,8 @@ -using System; +using System; using System.Net; +using System.Net.Sockets; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Text; namespace LiteNetLib.Utils @@ -12,25 +14,59 @@ public class NetDataWriter private const int InitialSize = 64; private readonly bool _autoResize; - public int Capacity => _data.Length; - public byte[] Data => _data; - public int Length => _position; + private const int IPv4Size = 4; + private const int IPv6Size = 16; + private const int GuidSize = 16; - // Cache encoding instead of creating it with BinaryWriter each time - // 1000 readers before: 1MB GC, 30ms - // 1000 readers after: .8MB GC, 18ms - private static readonly UTF8Encoding _uTF8Encoding = new UTF8Encoding(false, true); - public const int StringBufferMaxLength = 1024 * 32; // <- short.MaxValue + 1 - private readonly byte[] _stringBuffer = new byte[StringBufferMaxLength]; + /// + /// Gets the total capacity of the internal buffer. + /// + /// The length of the underlying array. + public int Capacity + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _data.Length; + } - public NetDataWriter() : this(true, InitialSize) + /// + /// Gets the underlying array used by this writer. + /// + /// The internal array. + /// + /// Accessing this directly allows for external manipulation but bypasses bounds checking. + /// + public byte[] Data { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _data; } - public NetDataWriter(bool autoResize) : this(autoResize, InitialSize) + /// + /// Gets the current number of s written to the buffer. + /// + /// The current . + public int Length { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _position; } + /// + /// Returns a representing the currently used portion of the internal buffer. + /// + /// A from index 0 to . + /// + /// Provides a high-performance, zero-allocation view of the data. + /// The span becomes invalid if the internal buffer is resized or if changes. + /// + public ReadOnlySpan AsReadOnlySpan() => new ReadOnlySpan(_data, 0, _position); + + internal static readonly UTF8Encoding uTF8Encoding = new UTF8Encoding(false, true); + + public NetDataWriter() : this(true, InitialSize) { } + + public NetDataWriter(bool autoResize) : this(autoResize, InitialSize) { } + public NetDataWriter(bool autoResize, int initialSize) { _data = new byte[initialSize]; @@ -61,11 +97,26 @@ public static NetDataWriter FromBytes(byte[] bytes, bool copy) /// Length of array public static NetDataWriter FromBytes(byte[] bytes, int offset, int length) { - var netDataWriter = new NetDataWriter(true, bytes.Length); + var netDataWriter = new NetDataWriter(true, length); netDataWriter.Put(bytes, offset, length); return netDataWriter; } + /// + /// Creates NetDataWriter from the given . + /// + public static NetDataWriter FromBytes(Span bytes) + { + var netDataWriter = new NetDataWriter(true, bytes.Length); + netDataWriter.Put(bytes); + return netDataWriter; + } + + /// + /// Creates a new and serializes a into it. + /// + /// The to serialize. + /// A new instance containing the serialized . public static NetDataWriter FromString(string value) { var netDataWriter = new NetDataWriter(); @@ -73,6 +124,14 @@ public static NetDataWriter FromString(string value) return netDataWriter; } + /// + /// Ensures the internal buffer is at least . + /// + /// The required minimum size of the buffer. + /// + /// If an allocation is necessary, the buffer grows to either + /// or doubles its current size, whichever is larger. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ResizeIfNeed(int newSize) { @@ -82,6 +141,14 @@ public void ResizeIfNeed(int newSize) } } + /// + /// Ensures the internal buffer can accommodate more s. + /// + /// The number of additional s to fit. + /// + /// This checks against the current . If the capacity is insufficient, + /// the buffer grows to either the required size or doubles its current size. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void EnsureFit(int additionalSize) { @@ -91,17 +158,32 @@ public void EnsureFit(int additionalSize) } } + + /// + /// Resets the to 0 and ensures the internal buffer has at least the specified . + /// + /// The minimum capacity required for the internal buffer. + /// + /// If the current buffer is smaller than , will allocate a larger array. + /// public void Reset(int size) { ResizeIfNeed(size); _position = 0; } - public void Reset() - { - _position = 0; - } + /// + /// Resets the to 0, effectively clearing the buffer for reuse. + /// + public void Reset() => _position = 0; + /// + /// Creates a array containing the current data from the internal buffer. + /// + /// A new array of length . + /// + /// This method performs a heap allocation and copies the data using . + /// public byte[] CopyData() { byte[] resultData = new byte[_position]; @@ -121,151 +203,191 @@ public int SetPosition(int position) return prevPosition; } - public void Put(float value) - { - if (_autoResize) - ResizeIfNeed(_position + 4); - FastBitConverter.GetBytes(_data, _position, value); - _position += 4; - } + /// + /// Serializes a value. + /// + /// The value to write. + public void Put(float value) => PutUnmanaged(value); - public void Put(double value) - { - if (_autoResize) - ResizeIfNeed(_position + 8); - FastBitConverter.GetBytes(_data, _position, value); - _position += 8; - } + /// + /// Serializes a value. + /// + /// The value to write. + public void Put(double value) => PutUnmanaged(value); - public void Put(long value) - { - if (_autoResize) - ResizeIfNeed(_position + 8); - FastBitConverter.GetBytes(_data, _position, value); - _position += 8; - } + /// + /// Serializes a value. + /// + /// The value to write. + public void Put(long value) => PutUnmanaged(value); - public void Put(ulong value) - { - if (_autoResize) - ResizeIfNeed(_position + 8); - FastBitConverter.GetBytes(_data, _position, value); - _position += 8; - } + /// + /// Serializes a value. + /// + /// The value to write. + public void Put(ulong value) => PutUnmanaged(value); - public void Put(int value) - { - if (_autoResize) - ResizeIfNeed(_position + 4); - FastBitConverter.GetBytes(_data, _position, value); - _position += 4; - } + /// + /// Serializes an value. + /// + /// The value to write. + public void Put(int value) => PutUnmanaged(value); - public void Put(uint value) - { - if (_autoResize) - ResizeIfNeed(_position + 4); - FastBitConverter.GetBytes(_data, _position, value); - _position += 4; - } + /// + /// Serializes a value. + /// + /// The value to write. + public void Put(uint value) => PutUnmanaged(value); - public void Put(char value) - { - Put((ushort)value); - } + /// + /// Serializes a value as a . + /// + /// The value to write. + public void Put(char value) => PutUnmanaged(value); - public void Put(ushort value) - { - if (_autoResize) - ResizeIfNeed(_position + 2); - FastBitConverter.GetBytes(_data, _position, value); - _position += 2; - } + /// + /// Serializes a value. + /// + /// The value to write. + public void Put(ushort value) => PutUnmanaged(value); - public void Put(short value) - { - if (_autoResize) - ResizeIfNeed(_position + 2); - FastBitConverter.GetBytes(_data, _position, value); - _position += 2; - } + /// + /// Serializes a value. + /// + /// The value to write. + public void Put(short value) => PutUnmanaged(value); - public void Put(sbyte value) - { - if (_autoResize) - ResizeIfNeed(_position + 1); - _data[_position] = (byte)value; - _position++; - } + /// + /// Serializes an value. + /// + /// The value to write. + public void Put(sbyte value) => PutUnmanaged(value); + + /// + /// Serializes a value. + /// + /// The value to write. + public void Put(byte value) => PutUnmanaged(value); - public void Put(byte value) + /// + /// Serializes a value. + /// + /// The value to write. + public void Put(Guid value) { if (_autoResize) - ResizeIfNeed(_position + 1); - _data[_position] = value; - _position++; + ResizeIfNeed(_position + GuidSize); + value.TryWriteBytes(_data.AsSpan(_position)); + _position += GuidSize; } + /// + /// Serializes a segment of a array. + /// + /// The source array. + /// The starting index in the source array. + /// The number of s to write. public void Put(byte[] data, int offset, int length) { - if (_autoResize) - ResizeIfNeed(_position + length); - Buffer.BlockCopy(data, offset, _data, _position, length); - _position += length; + Put(data.AsSpan(offset, length)); } + /// + /// Serializes an entire array. + /// + /// The source array. public void Put(byte[] data) + { + Put(data.AsSpan()); + } + + /// + /// Serializes a of s to the internal buffer. + /// + /// The span of data to write. + public void Put(ReadOnlySpan data) { if (_autoResize) ResizeIfNeed(_position + data.Length); - Buffer.BlockCopy(data, 0, _data, _position, data.Length); + data.CopyTo(_data.AsSpan(_position)); _position += data.Length; } - public void PutSBytesWithLength(sbyte[] data, int offset, int length) + /// + /// Serializes a segment of an array prefixed with its length. + /// + /// The source array. + /// The starting index in the source array. + /// The number of elements to write. + public void PutSBytesWithLength(sbyte[] data, int offset, ushort length) { if (_autoResize) - ResizeIfNeed(_position + length + 4); + ResizeIfNeed(_position + 2 + length); + FastBitConverter.GetBytes(_data, _position, length); - Buffer.BlockCopy(data, offset, _data, _position + 4, length); - _position += length + 4; - } + _position += 2; - public void PutSBytesWithLength(sbyte[] data) - { - if (_autoResize) - ResizeIfNeed(_position + data.Length + 4); - FastBitConverter.GetBytes(_data, _position, data.Length); - Buffer.BlockCopy(data, 0, _data, _position + 4, data.Length); - _position += data.Length + 4; + if (length > 0) + { + ReadOnlySpan source = data.AsSpan(offset, length); + ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); + + sourceBytes.CopyTo(_data.AsSpan(_position)); + _position += length; + } } - public void PutBytesWithLength(byte[] data, int offset, int length) + /// + /// Serializes an array prefixed with its length. + /// + /// The source array. + public void PutSBytesWithLength(sbyte[] data) => PutArray(data, 1); + + /// + /// Serializes a segment of a array prefixed with its length. + /// + /// The source array. + /// The starting index in the source array. + /// The number of s to write. + public void PutBytesWithLength(byte[] data, int offset, ushort length) { if (_autoResize) - ResizeIfNeed(_position + length + 4); + ResizeIfNeed(_position + 2 + length); + FastBitConverter.GetBytes(_data, _position, length); - Buffer.BlockCopy(data, offset, _data, _position + 4, length); - _position += length + 4; - } + _position += 2; - public void PutBytesWithLength(byte[] data) - { - if (_autoResize) - ResizeIfNeed(_position + data.Length + 4); - FastBitConverter.GetBytes(_data, _position, data.Length); - Buffer.BlockCopy(data, 0, _data, _position + 4, data.Length); - _position += data.Length + 4; + if (length > 0) + { + data.AsSpan(offset, length).CopyTo(_data.AsSpan(_position)); + _position += length; + } } - public void Put(bool value) - { - Put((byte)(value ? 1 : 0)); - } + /// + /// Serializes a array prefixed with its length. + /// + /// The source array. + public void PutBytesWithLength(byte[] data) => PutArray(data, 1); - private void PutArray(Array arr, int sz) - { - ushort length = arr == null ? (ushort) 0 : (ushort)arr.Length; + /// + /// Serializes a value as a single . + /// + /// The value to write. + public void Put(bool value) => Put((byte)(value ? 1 : 0)); + + /// + /// Serializes an prefixed with a 2-byte length. + /// + /// The source array to serialize. + /// The size of a single element in s. + /// + /// If the array is , a length of 0 is written.
+ /// The total payload size is calculated as length * sz. + ///
+ public void PutArray(Array arr, int sz) + { + ushort length = arr == null ? (ushort)0 : (ushort)arr.Length; sz *= length; if (_autoResize) ResizeIfNeed(_position + sz + 2); @@ -275,51 +397,80 @@ private void PutArray(Array arr, int sz) _position += sz + 2; } - public void PutArray(float[] value) + /// + /// Serializes an array of unmanaged values. + /// + /// The unmanaged type of the array elements. + /// The array to serialize. + public void PutUnmanagedArray(T[] arr) where T : unmanaged { - PutArray(value, 4); + PutSpan(arr.AsSpan()); } - public void PutArray(double[] value) + /// + /// Serializes a of unmanaged values to the internal buffer. + /// + /// The unmanaged type of the span elements. + /// The span of data to write. + /// + /// Writes a 2-byte length header followed by the raw binary data.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void PutSpan(Span span) where T : unmanaged { - PutArray(value, 8); - } + var length = (ushort)span.Length; + var byteLength = length * sizeof(T); - public void PutArray(long[] value) - { - PutArray(value, 8); - } + if (_autoResize) + ResizeIfNeed(_position + byteLength + 2); - public void PutArray(ulong[] value) - { - PutArray(value, 8); - } + FastBitConverter.GetBytes(_data, _position, length); + _position += 2; - public void PutArray(int[] value) - { - PutArray(value, 4); + if (length > 0) + { + var sourceBytes = MemoryMarshal.AsBytes(span); + sourceBytes.CopyTo(_data.AsSpan(_position)); + _position += byteLength; + } } - public void PutArray(uint[] value) - { - PutArray(value, 4); - } + /// + /// Serializes an array of unmanaged values to the internal buffer. + /// + public void PutArray(float[] value) => PutUnmanagedArray(value); - public void PutArray(ushort[] value) - { - PutArray(value, 2); - } + /// + public void PutArray(double[] value) => PutUnmanagedArray(value); - public void PutArray(short[] value) - { - PutArray(value, 2); - } + /// + public void PutArray(long[] value) => PutUnmanagedArray(value); - public void PutArray(bool[] value) - { - PutArray(value, 1); - } + /// + public void PutArray(ulong[] value) => PutUnmanagedArray(value); + + /// + public void PutArray(int[] value) => PutUnmanagedArray(value); + + /// + public void PutArray(uint[] value) => PutUnmanagedArray(value); + /// + public void PutArray(ushort[] value) => PutUnmanagedArray(value); + + /// + public void PutArray(short[] value) => PutUnmanagedArray(value); + + /// + public void PutArray(bool[] value) => PutUnmanagedArray(value); + + /// + /// Serializes an array of values. + /// + /// The array of elements to write. + /// + /// Writes a 2-byte length header followed by each element. + /// public void PutArray(string[] value) { ushort strArrayLength = value == null ? (ushort)0 : (ushort)value.Length; @@ -328,6 +479,11 @@ public void PutArray(string[] value) Put(value[i]); } + /// + /// Serializes an array of values with a maximum length constraint per element. + /// + /// The array of elements to write. + /// The maximum allowed length for each individual . public void PutArray(string[] value, int strMaxLength) { ushort strArrayLength = value == null ? (ushort)0 : (ushort)value.Length; @@ -336,44 +492,190 @@ public void PutArray(string[] value, int strMaxLength) Put(value[i], strMaxLength); } - public void Put(IPEndPoint endPoint) + /// + /// Serializes an array of objects implementing . + /// + /// A type that implements and has a parameterless constructor. + /// The array of objects to serialize. + public void PutArray(T[] value) where T : INetSerializable, new() { - Put(endPoint.Address.ToString()); - Put(endPoint.Port); + ushort strArrayLength = (ushort)(value?.Length ?? 0); + Put(strArrayLength); + for (int i = 0; i < strArrayLength; i++) + value[i].Serialize(this); } - public void Put(string value) + /// + /// Serializes an . + /// + /// The network endpoint to write. + /// Thrown when the is not or . + /// + /// Writes a (0 for IPv4, 1 for IPv6), followed by the address bytes and a 2-byte port. + /// + public void Put(IPEndPoint endPoint) { - Put(value, 0); + int addressSize; + byte familyFlag; + + if (endPoint.AddressFamily == AddressFamily.InterNetwork) + { + addressSize = IPv4Size; + familyFlag = 0; + } + else if (endPoint.AddressFamily == AddressFamily.InterNetworkV6) + { + addressSize = IPv6Size; + familyFlag = 1; + } + else + { + throw new ArgumentException($"Unsupported address family: {endPoint.AddressFamily}"); + } + + if (_autoResize) + ResizeIfNeed(_position + 1 + addressSize + 2); + + _data[_position++] = familyFlag; + + Span destination = _data.AsSpan(_position, addressSize); + if (!endPoint.Address.TryWriteBytes(destination, out int written)) + throw new ArgumentException("Failed to write IP bytes."); + + _position += written; + + Put((ushort)endPoint.Port); } /// - /// Note that "maxLength" only limits the number of characters in a string, not its size in bytes. + /// Serializes a using a 4-byte length header. /// - public void Put(string value, int maxLength) - { - if (value == null) + /// The to write. + /// + /// Recommended for strings that may exceed the 65535 byte limit of standard length headers.
+ /// Uses . + ///
+ public void PutLargeString(string value) + { + if (string.IsNullOrEmpty(value)) { - Put((ushort)0); + Put(0); return; } - int length = maxLength > 0 && value.Length > maxLength ? maxLength : value.Length; - int size = _uTF8Encoding.GetBytes(value, 0, length, _stringBuffer, 0); + int size = uTF8Encoding.GetByteCount(value); + Put(size); + + if (_autoResize) + ResizeIfNeed(_position + size); + + uTF8Encoding.GetBytes(value, 0, value.Length, _data, _position); + _position += size; + } - if (size >= StringBufferMaxLength) + /// + /// Serializes a string using a 2-byte length header. + /// + /// The string to write to the buffer. + /// + /// The maximum number of characters to write. If the string is longer, it will be truncated.
+ /// A value of 0 indicates no limit. + /// + /// + /// Note that limits the number of characters, not the total size in s.
+ /// Uses . + ///
+ public void Put(string value, int maxLength = 0) + { + if (string.IsNullOrEmpty(value)) { Put((ushort)0); return; } + ReadOnlySpan source = value.AsSpan(); + if (maxLength > 0 && source.Length > maxLength) + { + source = source.Slice(0, maxLength); + } + + int maxSize = uTF8Encoding.GetMaxByteCount(source.Length); + if (_autoResize) + ResizeIfNeed(_position + maxSize + sizeof(ushort)); + int size = uTF8Encoding.GetBytes(source, _data.AsSpan(_position + sizeof(ushort))); + if (size == 0) + { + Put((ushort)0); + return; + } Put(checked((ushort)(size + 1))); - Put(_stringBuffer, 0, size); + _position += size; + } + + /// + /// Writes a value of type into the internal byte buffer at the current position, + /// advancing the position by the size of . + /// + /// An unmanaged value type to write into the buffer. + /// The value to write into the buffer. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void PutUnmanaged(T value) where T : unmanaged + { + int size = sizeof(T); + if (_autoResize) + ResizeIfNeed(_position + size); + FastBitConverter.GetBytes(_data, _position, value); + _position += size; + } + + /// + /// Writes a nullable value of type into the internal byte buffer at the current position, + /// first writing a indicating whether the value is present, + /// and then writing the value itself if it exists.
Advances the position by 1 byte for the presence flag plus + /// the size of if the value is present. + ///
+ /// An unmanaged value type to write into the buffer. + /// The nullable value to write into the buffer. If , only a flag is written. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PutNullableUnmanaged(T? value) where T : unmanaged + { + bool hasValue = value.HasValue; + Put(hasValue); + if (!hasValue) + { + return; + } + + PutUnmanaged(value.Value); } - public void Put(T obj) where T : INetSerializable + /// + /// Writes an enum value of type to the internal data buffer at the current position.
+ /// Automatically resizes the buffer if is enabled. + /// Advances the position by the size of . + ///
+ /// An unmanaged enum type to write. + /// The enum value to write. + public unsafe void PutEnum(T value) where T : unmanaged, Enum { - obj.Serialize(this); + var size = sizeof(T); + if (_autoResize) + { + ResizeIfNeed(_position + size); + } + + FastBitConverter.GetBytes(_data, _position, value); + _position += size; } + + /// + /// Serializes an object implementing . + /// + /// A type that implements the interface. + /// The object instance to serialize. + /// + /// This method calls the method on the provided . + /// + public void Put(T obj) where T : INetSerializable => obj.Serialize(this); } } diff --git a/LiteNetLib/Utils/NetPacketProcessor.cs b/LiteNetLib/Utils/NetPacketProcessor.cs index 007d5207..e3d8d981 100644 --- a/LiteNetLib/Utils/NetPacketProcessor.cs +++ b/LiteNetLib/Utils/NetPacketProcessor.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; namespace LiteNetLib.Utils { @@ -26,7 +27,6 @@ static HashCache() protected delegate void SubscribeDelegate(NetDataReader reader, object userData); private readonly NetSerializer _netSerializer; private readonly Dictionary _callbacks = new Dictionary(); - private readonly NetDataWriter _netDataWriter = new NetDataWriter(); public NetPacketProcessor() { @@ -118,35 +118,11 @@ public void ReadPacket(NetDataReader reader) ReadPacket(reader, null); } - public void Send(NetPeer peer, T packet, DeliveryMethod options) where T : class, new() - { - _netDataWriter.Reset(); - Write(_netDataWriter, packet); - peer.Send(_netDataWriter, options); - } - - public void SendNetSerializable(NetPeer peer, ref T packet, DeliveryMethod options) where T : INetSerializable - { - _netDataWriter.Reset(); - WriteNetSerializable(_netDataWriter, ref packet); - peer.Send(_netDataWriter, options); - } - - public void Send(NetManager manager, T packet, DeliveryMethod options) where T : class, new() - { - _netDataWriter.Reset(); - Write(_netDataWriter, packet); - manager.SendToAll(_netDataWriter, options); - } - - public void SendNetSerializable(NetManager manager, ref T packet, DeliveryMethod options) where T : INetSerializable - { - _netDataWriter.Reset(); - WriteNetSerializable(_netDataWriter, ref packet); - manager.SendToAll(_netDataWriter, options); - } - - public void Write(NetDataWriter writer, T packet) where T : class, new() + public void Write< +#if NET5_0_OR_GREATER + [DynamicallyAccessedMembers(Trimming.SerializerMemberTypes)] +#endif + T>(NetDataWriter writer, T packet) where T : class, new() { WriteHash(writer); _netSerializer.Serialize(writer, packet); @@ -175,7 +151,11 @@ public void ReadPacket(NetDataReader reader, object userData) /// event that will be called when packet deserialized with ReadPacket method /// Method that constructs packet instead of slow Activator.CreateInstance /// 's fields are not supported, or it has no fields - public void Subscribe(Action onReceive, Func packetConstructor) where T : class, new() + public void Subscribe< +#if NET5_0_OR_GREATER + [DynamicallyAccessedMembers(Trimming.SerializerMemberTypes)] +#endif + T>(Action onReceive, Func packetConstructor) where T : class, new() { _netSerializer.Register(); _callbacks[GetHash()] = (reader, userData) => @@ -192,7 +172,11 @@ public void ReadPacket(NetDataReader reader, object userData) /// event that will be called when packet deserialized with ReadPacket method /// Method that constructs packet instead of slow Activator.CreateInstance /// 's fields are not supported, or it has no fields - public void Subscribe(Action onReceive, Func packetConstructor) where T : class, new() + public void Subscribe< +#if NET5_0_OR_GREATER + [DynamicallyAccessedMembers(Trimming.SerializerMemberTypes)] +#endif + T, TUserData>(Action onReceive, Func packetConstructor) where T : class, new() { _netSerializer.Register(); _callbacks[GetHash()] = (reader, userData) => @@ -209,7 +193,11 @@ public void ReadPacket(NetDataReader reader, object userData) /// /// event that will be called when packet deserialized with ReadPacket method /// 's fields are not supported, or it has no fields - public void SubscribeReusable(Action onReceive) where T : class, new() + public void SubscribeReusable< +#if NET5_0_OR_GREATER + [DynamicallyAccessedMembers(Trimming.SerializerMemberTypes)] +#endif + T>(Action onReceive) where T : class, new() { _netSerializer.Register(); var reference = new T(); @@ -226,7 +214,11 @@ public void ReadPacket(NetDataReader reader, object userData) /// /// event that will be called when packet deserialized with ReadPacket method /// 's fields are not supported, or it has no fields - public void SubscribeReusable(Action onReceive) where T : class, new() + public void SubscribeReusable< +#if NET5_0_OR_GREATER + [DynamicallyAccessedMembers(Trimming.SerializerMemberTypes)] +#endif + T, TUserData>(Action onReceive) where T : class, new() { _netSerializer.Register(); var reference = new T(); @@ -237,6 +229,13 @@ public void ReadPacket(NetDataReader reader, object userData) }; } + /// + /// Registers a callback for a packet type that implements , using a custom constructor and supporting user data. + /// + /// The type of the packet. Must implement . + /// The type of the user data (typically ). + /// The delegate to be executed when the packet is received. + /// A function that returns a new instance of . public void SubscribeNetSerializable( Action onReceive, Func packetConstructor) where T : INetSerializable @@ -249,6 +248,12 @@ public void SubscribeNetSerializable( }; } + /// + /// Registers a callback for a packet type that implements , using a custom constructor. + /// + /// The type of the packet. Must implement . + /// The delegate to be executed when the packet is received. + /// A function that returns a new instance of . public void SubscribeNetSerializable( Action onReceive, Func packetConstructor) where T : INetSerializable @@ -261,6 +266,15 @@ public void SubscribeNetSerializable( }; } + /// + /// Registers a callback for a packet type that implements and has a parameterless constructor, supporting user data. + /// + /// + /// To reduce allocations, this method uses a single internal reference to for deserialization. + /// + /// The type of the packet. Must implement and have a () constraint. + /// The type of the user data (typically ). + /// The delegate to be executed when the packet is received. public void SubscribeNetSerializable( Action onReceive) where T : INetSerializable, new() { @@ -272,6 +286,14 @@ public void SubscribeNetSerializable( }; } + /// + /// Registers a callback for a packet type that implements and has a parameterless constructor. + /// + /// + /// To reduce allocations, this method uses a single internal reference to for deserialization. + /// + /// The type of the packet. Must implement and have a () constraint. + /// The delegate to be executed when the packet is received. public void SubscribeNetSerializable( Action onReceive) where T : INetSerializable, new() { diff --git a/LiteNetLib/Utils/NetSerializer.cs b/LiteNetLib/Utils/NetSerializer.cs index 63f6cd67..fae854a7 100644 --- a/LiteNetLib/Utils/NetSerializer.cs +++ b/LiteNetLib/Utils/NetSerializer.cs @@ -1,6 +1,7 @@ using System; using System.Reflection; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Net; using System.Runtime.Serialization; @@ -419,7 +420,13 @@ private class CharSerializer : FastCallSpecificAuto private class IPEndPointSerializer : FastCallSpecificAuto { protected override void ElementWrite(NetDataWriter w, ref IPEndPoint prop) { w.Put(prop); } - protected override void ElementRead(NetDataReader r, out IPEndPoint prop) { prop = r.GetNetEndPoint(); } + protected override void ElementRead(NetDataReader r, out IPEndPoint prop) { prop = r.GetIPEndPoint(); } + } + + private class GuidSerializer : FastCallSpecificAuto + { + protected override void ElementWrite(NetDataWriter w, ref Guid guid) { w.Put(guid); } + protected override void ElementRead(NetDataReader r, out Guid guid) { guid = r.GetGuid(); } } private class StringSerializer : FastCallSpecific @@ -568,13 +575,16 @@ public NetSerializer(int maxStringLength) _maxStringLength = maxStringLength; } - private ClassInfo RegisterInternal() + private ClassInfo RegisterInternal< +#if NET5_0_OR_GREATER + [DynamicallyAccessedMembers(Trimming.SerializerMemberTypes)] +#endif + T>() { if (ClassInfo.Instance != null) return ClassInfo.Instance; - Type t = typeof(T); - var props = t.GetProperties( + var props = typeof(T).GetProperties( BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty | @@ -641,6 +651,8 @@ private ClassInfo RegisterInternal() serialzer = new CharSerializer(); else if (elementType == typeof(IPEndPoint)) serialzer = new IPEndPointSerializer(); + else if (elementType == typeof(Guid)) + serialzer = new GuidSerializer(); else { _registeredTypes.TryGetValue(elementType, out var customType); @@ -663,7 +675,11 @@ private ClassInfo RegisterInternal() } /// 's fields are not supported, or it has no fields - public void Register() + public void Register< +#if NET5_0_OR_GREATER + [DynamicallyAccessedMembers(Trimming.SerializerMemberTypes)] +#endif + T>() { RegisterInternal(); } @@ -674,7 +690,11 @@ public void Register() /// NetDataReader with packet /// Returns packet if packet in reader is matched type /// 's fields are not supported, or it has no fields - public T Deserialize(NetDataReader reader) where T : class, new() + public T Deserialize< +#if NET5_0_OR_GREATER + [DynamicallyAccessedMembers(Trimming.SerializerMemberTypes)] +#endif + T>(NetDataReader reader) where T : class, new() { var info = RegisterInternal(); var result = new T(); @@ -696,7 +716,11 @@ public void Register() /// Deserialization target /// Returns true if packet in reader is matched type /// 's fields are not supported, or it has no fields - public bool Deserialize(NetDataReader reader, T target) where T : class, new() + public bool Deserialize< +#if NET5_0_OR_GREATER + [DynamicallyAccessedMembers(Trimming.SerializerMemberTypes)] +#endif + T>(NetDataReader reader, T target) where T : class, new() { var info = RegisterInternal(); try @@ -716,7 +740,11 @@ public void Register() /// Serialization target NetDataWriter /// Object to serialize /// 's fields are not supported, or it has no fields - public void Serialize(NetDataWriter writer, T obj) where T : class, new() + public void Serialize< +#if NET5_0_OR_GREATER + [DynamicallyAccessedMembers(Trimming.SerializerMemberTypes)] +#endif + T>(NetDataWriter writer, T obj) where T : class, new() { RegisterInternal().Write(obj, writer); } @@ -726,7 +754,11 @@ public void Register() /// /// Object to serialize /// byte array with serialized data - public byte[] Serialize(T obj) where T : class, new() + public byte[] Serialize< +#if NET5_0_OR_GREATER + [DynamicallyAccessedMembers(Trimming.SerializerMemberTypes)] +#endif + T>(T obj) where T : class, new() { if (_writer == null) _writer = new NetDataWriter(); diff --git a/LiteNetLib/Utils/NtpRequest.cs b/LiteNetLib/Utils/NtpRequest.cs index bd7f74fe..dc0c2aed 100644 --- a/LiteNetLib/Utils/NtpRequest.cs +++ b/LiteNetLib/Utils/NtpRequest.cs @@ -3,23 +3,47 @@ namespace LiteNetLib.Utils { + /// + /// Represents an active NTP (Network Time Protocol) query to a remote time server. + /// Handles retransmission and lifetime management of the request. + /// internal sealed class NtpRequest { private const int ResendTimer = 1000; private const int KillTimer = 10000; + /// + /// Standard UDP port used by NTP servers. + /// public const int DefaultPort = 123; private readonly IPEndPoint _ntpEndPoint; - private int _resendTime = ResendTimer; - private int _killTime = 0; + private float _resendTime = ResendTimer; + private float _killTime = 0; + /// + /// Initializes a new instance of the class for a specific server. + /// + /// The IP endpoint of the NTP server. public NtpRequest(IPEndPoint endPoint) { _ntpEndPoint = endPoint; } + /// + /// Gets a value indicating whether the request has exceeded its maximum lifetime and should be removed. + /// public bool NeedToKill => _killTime >= KillTimer; - public bool Send(Socket socket, int time) + /// + /// Attempts to send an NTP query packet to the remote endpoint. + /// + /// + /// The packet is only sent if the internal retransmission timer has elapsed. + /// Updates internal timers for both retransmission and total lifetime. + /// + /// The underlying socket used to send the datagram. + /// The amount of time elapsed since the last update/call, in seconds. + /// if the packet was successfully transmitted; otherwise, . + public bool Send(Socket socket, float time) { _resendTime += time; _killTime += time; diff --git a/LiteNetLib/Utils/Preserve.cs b/LiteNetLib/Utils/Preserve.cs new file mode 100644 index 00000000..057798b9 --- /dev/null +++ b/LiteNetLib/Utils/Preserve.cs @@ -0,0 +1,12 @@ +using System; + +namespace LiteNetLib.Utils +{ + /// + /// PreserveAttribute prevents byte code stripping from removing a class, method, field, or property. + /// + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, Inherited = false)] + public class PreserveAttribute : Attribute + { + } +} diff --git a/LiteNetLib/package.json b/LiteNetLib/package.json new file mode 100644 index 00000000..e5011459 --- /dev/null +++ b/LiteNetLib/package.json @@ -0,0 +1,11 @@ +{ + "name": "com.revenantx.litenetlib", + "version": "1.0.1-1", + "displayName": "LiteNetLib", + "description": "Lite reliable UDP library for .NET Standard 2.0 (Mono, .NET Core, .NET Framework)", + "unity": "2018.3", + "author": { + "name": "RevenantX", + "url": "https://github.com/RevenantX" + } +} \ No newline at end of file diff --git a/LiteNetLibSampleUnity/Assets/GameClient.cs b/LiteNetLibSampleUnity/Assets/GameClient.cs index 18fff780..f6a120e9 100644 --- a/LiteNetLibSampleUnity/Assets/GameClient.cs +++ b/LiteNetLibSampleUnity/Assets/GameClient.cs @@ -14,7 +14,7 @@ public class GameClient : MonoBehaviour, INetEventListener private float _oldBallPosX; private float _lerpTime; - void Start() + private void Start() { _netClient = new NetManager(this); _netClient.UnconnectedMessagesEnabled = true; @@ -22,7 +22,7 @@ void Start() _netClient.Start(); } - void Update() + private void Update() { _netClient.PollEvents(); @@ -43,23 +43,23 @@ void Update() } } - void OnDestroy() + private void OnDestroy() { if (_netClient != null) _netClient.Stop(); } - public void OnPeerConnected(NetPeer peer) + void INetEventListener.OnPeerConnected(NetPeer peer) { - Debug.Log("[CLIENT] We connected to " + peer.EndPoint); + Debug.Log("[CLIENT] We connected to " + peer); } - public void OnNetworkError(IPEndPoint endPoint, SocketError socketErrorCode) + void INetEventListener.OnNetworkError(IPEndPoint endPoint, SocketError socketErrorCode) { Debug.Log("[CLIENT] We received error " + socketErrorCode); } - public void OnNetworkReceive(NetPeer peer, NetPacketReader reader, DeliveryMethod deliveryMethod) + void INetEventListener.OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channelNumber, DeliveryMethod deliveryMethod) { _newBallPosX = reader.GetFloat(); @@ -73,7 +73,7 @@ public void OnNetworkReceive(NetPeer peer, NetPacketReader reader, DeliveryMetho _lerpTime = 0f; } - public void OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType) + void INetEventListener.OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType) { if (messageType == UnconnectedMessageType.BasicMessage && _netClient.ConnectedPeersCount == 0 && reader.GetInt() == 1) { @@ -82,17 +82,17 @@ public void OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketRead } } - public void OnNetworkLatencyUpdate(NetPeer peer, int latency) + void INetEventListener.OnNetworkLatencyUpdate(NetPeer peer, int latency) { } - public void OnConnectionRequest(ConnectionRequest request) + void INetEventListener.OnConnectionRequest(ConnectionRequest request) { } - public void OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo) + void INetEventListener.OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo) { Debug.Log("[CLIENT] We disconnected because " + disconnectInfo.Reason); } diff --git a/LiteNetLibSampleUnity/Assets/GameServer.cs b/LiteNetLibSampleUnity/Assets/GameServer.cs index 3e06ee57..0f5641ee 100644 --- a/LiteNetLibSampleUnity/Assets/GameServer.cs +++ b/LiteNetLibSampleUnity/Assets/GameServer.cs @@ -13,7 +13,7 @@ public class GameServer : MonoBehaviour, INetEventListener, INetLogger [SerializeField] private GameObject _serverBall; - void Start() + private void Start() { NetDebug.Logger = this; _dataWriter = new NetDataWriter(); @@ -23,12 +23,12 @@ void Start() _netServer.UpdateTime = 15; } - void Update() + private void Update() { _netServer.PollEvents(); } - void FixedUpdate() + private void FixedUpdate() { if (_ourPeer != null) { @@ -39,25 +39,25 @@ void FixedUpdate() } } - void OnDestroy() + private void OnDestroy() { NetDebug.Logger = null; if (_netServer != null) _netServer.Stop(); } - public void OnPeerConnected(NetPeer peer) + void INetEventListener.OnPeerConnected(NetPeer peer) { - Debug.Log("[SERVER] We have new peer " + peer.EndPoint); + Debug.Log("[SERVER] We have new peer " + peer); _ourPeer = peer; } - public void OnNetworkError(IPEndPoint endPoint, SocketError socketErrorCode) + void INetEventListener.OnNetworkError(IPEndPoint endPoint, SocketError socketErrorCode) { Debug.Log("[SERVER] error " + socketErrorCode); } - public void OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, + void INetEventListener.OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType) { if (messageType == UnconnectedMessageType.Broadcast) @@ -69,27 +69,27 @@ public void OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketRead } } - public void OnNetworkLatencyUpdate(NetPeer peer, int latency) + void INetEventListener.OnNetworkLatencyUpdate(NetPeer peer, int latency) { } - public void OnConnectionRequest(ConnectionRequest request) + void INetEventListener.OnConnectionRequest(ConnectionRequest request) { request.AcceptIfKey("sample_app"); } - public void OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo) + void INetEventListener.OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo) { - Debug.Log("[SERVER] peer disconnected " + peer.EndPoint + ", info: " + disconnectInfo.Reason); + Debug.Log("[SERVER] peer disconnected " + peer + ", info: " + disconnectInfo.Reason); if (peer == _ourPeer) _ourPeer = null; } - public void OnNetworkReceive(NetPeer peer, NetPacketReader reader, DeliveryMethod deliveryMethod) + void INetEventListener.OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channelNumber, DeliveryMethod deliveryMethod) { } - public void WriteNet(NetLogLevel level, string str, params object[] args) + void INetLogger.WriteNet(NetLogLevel level, string str, params object[] args) { Debug.LogFormat(str, args); } diff --git a/LiteNetLibSampleUnity/Assets/LiteNetLib.dll b/LiteNetLibSampleUnity/Assets/LiteNetLib.dll deleted file mode 100644 index 7bc19077..00000000 Binary files a/LiteNetLibSampleUnity/Assets/LiteNetLib.dll and /dev/null differ diff --git a/LiteNetLibSampleUnity/Assets/LiteNetLib.dll.meta b/LiteNetLibSampleUnity/Assets/LiteNetLib.dll.meta deleted file mode 100644 index 8da128bb..00000000 --- a/LiteNetLibSampleUnity/Assets/LiteNetLib.dll.meta +++ /dev/null @@ -1,32 +0,0 @@ -fileFormatVersion: 2 -guid: c132af3a96c6a4049b6b3b3b044dacb0 -PluginImporter: - externalObjects: {} - serializedVersion: 2 - iconMap: {} - executionOrder: {} - defineConstraints: [] - isPreloaded: 0 - isOverridable: 0 - isExplicitlyReferenced: 0 - platformData: - - first: - Any: - second: - enabled: 1 - settings: {} - - first: - Editor: Editor - second: - enabled: 0 - settings: - DefaultValueInitialized: true - - first: - Windows Store Apps: WindowsStoreApps - second: - enabled: 0 - settings: - CPU: AnyCPU - userData: - assetBundleName: - assetBundleVariant: diff --git a/LiteNetLibSampleUnity/Assets/LiteNetLib.xml b/LiteNetLibSampleUnity/Assets/LiteNetLib.xml deleted file mode 100644 index b67031b3..00000000 --- a/LiteNetLibSampleUnity/Assets/LiteNetLib.xml +++ /dev/null @@ -1,1486 +0,0 @@ - - - - LiteNetLib - - - - - Accept connection and get new NetPeer as result - - Connected NetPeer - - - - Type of message that you receive in OnNetworkReceiveUnconnected event - - - - - Disconnect reason that you receive in OnPeerDisconnected event - - - - - Additional information about disconnection - - - - - Additional info why peer disconnected - - - - - Error code (if reason is SocketSendError or SocketReceiveError) - - - - - Additional data that can be accessed (only if reason is RemoteConnectionClose) - - - - - New remote peer connected to host, or client connected to remote host - - Connected peer object - - - - Peer disconnected - - disconnected peer - additional info about reason, errorCode or data received with disconnect message - - - - Network error (on send or receive) - - From endPoint (can be null) - Socket error - - - - Received some data - - From peer - DataReader containing all received data - Number of channel at which packet arrived - Type of received packet - - - - Received unconnected message - - From address (IP and Port) - Message data - Message type (simple, discovery request or response) - - - - Latency information updated - - Peer with updated latency - latency value in milliseconds - - - - On peer connection requested - - Request information (EndPoint, internal id, additional data) - - - - On reliable message delivered - - - - - - - Ntp response - - - - - - Called when peer address changed (when AllowPeerAddressChange is enabled) - - Peer that changed address (with new address) - previous IP - - - - Module for UDP NAT Hole punching operations. Can be accessed from NetManager - - - - - Events automatically will be called without PollEvents method from another thread - - - - - Sending method type - - - - - Unreliable. Packets can be dropped, can be duplicated, can arrive without order. - - - - - Reliable. Packets won't be dropped, won't be duplicated, can arrive without order. - - - - - Unreliable. Packets can be dropped, won't be duplicated, will arrive in order. - - - - - Reliable and ordered. Packets won't be dropped, won't be duplicated, will arrive in order. - - - - - Reliable only last packet. Packets can be dropped (except the last one), won't be duplicated, will arrive in order. - Cannot be fragmented - - - - - Network constants. Can be tuned from sources for your purposes. - - - - - Interface to implement for your own logger - - - - - Static class for defining your own LiteNetLib logger instead of Console.WriteLine - or Debug.Log if compiled with UNITY flag - - - - - Main class for all network operations. Can be used as client and/or server. - - - - - Enable messages receiving without connection. (with SendUnconnectedMessage method) - - - - - Enable nat punch messages - - - - - Library logic update and send period in milliseconds - Lowest values in Windows doesn't change much because of Thread.Sleep precision - To more frequent sends (or sends tied to your game logic) use - - - - - Interval for latency detection and checking connection (in milliseconds) - - - - - If NetManager doesn't receive any packet from remote peer during this time (in milliseconds) then connection will be closed - (including library internal keepalive packets) - - - - - Simulate packet loss by dropping random amount of packets. (Works only in DEBUG mode) - - - - - Simulate latency by holding packets for random time. (Works only in DEBUG mode) - - - - - Chance of packet loss when simulation enabled. value in percents (1 - 100). - - - - - Minimum simulated latency (in milliseconds) - - - - - Maximum simulated latency (in milliseconds) - - - - - Events automatically will be called without PollEvents method from another thread - - - - - If true - receive event will be called from "receive" thread immediately otherwise on PollEvents call - - - - - If true - delivery event will be called from "receive" thread immediately otherwise on PollEvents call - - - - - Allows receive broadcast packets - - - - - Delay between initial connection attempts (in milliseconds) - - - - - Maximum connection attempts before client stops and call disconnect event. - - - - - Enables socket option "ReuseAddress" for specific purposes - - - - - Statistics of all connections - - - - - Toggles the collection of network statistics for the instance and all known peers - - - - - NatPunchModule for NAT hole punching operations - - - - - Returns true if socket listening and update thread is running - - - - - Local EndPoint (host and port) - - - - - Automatically recycle NetPacketReader after OnReceive event - - - - - IPv6 support - - - - - Override MTU for all new peers registered in this NetManager, will ignores MTU Discovery! - - - - - Sets initial MTU to lowest possible value according to RFC1191 (576 bytes) - - - - - First peer. Useful for Client mode - - - - - Experimental feature mostly for servers. Only for Windows/Linux - use direct socket calls for send/receive to drastically increase speed and reduce GC pressure - - - - - Disconnect peers if HostUnreachable or NetworkUnreachable spawned (old behaviour 0.9.x was true) - - - - - Allows peer change it's ip (lte to wifi, wifi to lte, etc). Use only on server - - - - - QoS channel count per message type (value must be between 1 and 64 channels) - - - - - Returns connected peers list (with internal cached list) - - - - - Gets peer by peer id - - id of peer - Peer if peer with id exist, otherwise null - - - - Gets peer by peer id - - id of peer - resulting peer - True if peer with id exist, otherwise false - - - - Returns connected peers count - - - - - NetManager constructor - - Network events listener (also can implement IDeliveryEventListener) - Extra processing of packages, like CRC checksum or encryption. All connected NetManagers must have same layer. - - - - Update and send logic. Use this only when NetManager started in manual mode - - elapsed milliseconds since last update call - - - - Send data to all connected peers (channel - 0) - - DataWriter with data - Send options (reliable, unreliable, etc.) - - - - Send data to all connected peers (channel - 0) - - Data - Send options (reliable, unreliable, etc.) - - - - Send data to all connected peers (channel - 0) - - Data - Start of data - Length of data - Send options (reliable, unreliable, etc.) - - - - Send data to all connected peers - - DataWriter with data - Number of channel (from 0 to channelsCount - 1) - Send options (reliable, unreliable, etc.) - - - - Send data to all connected peers - - Data - Number of channel (from 0 to channelsCount - 1) - Send options (reliable, unreliable, etc.) - - - - Send data to all connected peers - - Data - Start of data - Length of data - Number of channel (from 0 to channelsCount - 1) - Send options (reliable, unreliable, etc.) - - - - Send data to all connected peers (channel - 0) - - DataWriter with data - Send options (reliable, unreliable, etc.) - Excluded peer - - - - Send data to all connected peers (channel - 0) - - Data - Send options (reliable, unreliable, etc.) - Excluded peer - - - - Send data to all connected peers (channel - 0) - - Data - Start of data - Length of data - Send options (reliable, unreliable, etc.) - Excluded peer - - - - Send data to all connected peers - - DataWriter with data - Number of channel (from 0 to channelsCount - 1) - Send options (reliable, unreliable, etc.) - Excluded peer - - - - Send data to all connected peers - - Data - Number of channel (from 0 to channelsCount - 1) - Send options (reliable, unreliable, etc.) - Excluded peer - - - - Send data to all connected peers - - Data - Start of data - Length of data - Number of channel (from 0 to channelsCount - 1) - Send options (reliable, unreliable, etc.) - Excluded peer - - - - Start logic thread and listening on available port - - - - - Start logic thread and listening on selected port - - bind to specific ipv4 address - bind to specific ipv6 address - port to listen - - - - Start logic thread and listening on selected port - - bind to specific ipv4 address - bind to specific ipv6 address - port to listen - - - - Start logic thread and listening on selected port - - port to listen - - - - Start in manual mode and listening on selected port - In this mode you should use ManualReceive (without PollEvents) for receive packets - and ManualUpdate(...) for update and send packets - This mode useful mostly for single-threaded servers - - bind to specific ipv4 address - bind to specific ipv6 address - port to listen - - - - Start in manual mode and listening on selected port - In this mode you should use ManualReceive (without PollEvents) for receive packets - and ManualUpdate(...) for update and send packets - This mode useful mostly for single-threaded servers - - bind to specific ipv4 address - bind to specific ipv6 address - port to listen - - - - Start in manual mode and listening on selected port - In this mode you should use ManualReceive (without PollEvents) for receive packets - and ManualUpdate(...) for update and send packets - This mode useful mostly for single-threaded servers - - port to listen - - - - Send message without connection - - Raw data - Packet destination - Operation result - - - - Send message without connection. WARNING This method allocates a new IPEndPoint object and - synchronously makes a DNS request. If you're calling this method every frame it will be - much faster to just cache the IPEndPoint. - - Data serializer - Packet destination IP or hostname - Packet destination port - Operation result - - - - Send message without connection - - Data serializer - Packet destination - Operation result - - - - Send message without connection - - Raw data - data start - data length - Packet destination - Operation result - - - - Triggers update and send logic immediately (works asynchronously) - - - - - Receive all pending events. Call this in game update code - In Manual mode it will call also socket Receive (which can be slow) - - - - - Connect to remote host - - Server IP or hostname - Server Port - Connection key - New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting - Manager is not running. Call - - - - Connect to remote host - - Server IP or hostname - Server Port - Additional data for remote peer - New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting - Manager is not running. Call - - - - Connect to remote host - - Server end point (ip and port) - Connection key - New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting - Manager is not running. Call - - - - Connect to remote host - - Server end point (ip and port) - Additional data for remote peer - New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting - Manager is not running. Call - - - - Force closes connection and stop all threads. - - - - - Force closes connection and stop all threads. - - Send disconnect messages - - - - Return peers count with connection state - - peer connection state (you can use as bit flags) - peers count - - - - Get copy of peers (without allocations) - - List that will contain result - State of peers - - - - Disconnect all peers without any additional data - - - - - Disconnect all peers with shutdown message - - Data to send (must be less or equal MTU) - Data start - Data count - - - - Immediately disconnect peer from server without additional data - - peer to disconnect - - - - Disconnect peer from server - - peer to disconnect - - - - Disconnect peer from server and send additional data (Size must be less or equal MTU - 8) - - peer to disconnect - additional data - - - - Disconnect peer from server and send additional data (Size must be less or equal MTU - 8) - - peer to disconnect - additional data - - - - Disconnect peer from server and send additional data (Size must be less or equal MTU - 8) - - peer to disconnect - additional data - data start - data length - - - - Create the requests for NTP server - - NTP Server address. - - - - Create the requests for NTP server - - NTP Server address. - port - - - - Create the requests for NTP server (default port) - - NTP Server address. - - - - Maximum packet pool size (increase if you have tons of packets sending) - - - - - Maximum packets count that will be processed in Manual PollEvents - - - - - Start logic thread and listening on selected port - - bind to specific ipv4 address - bind to specific ipv6 address - port to listen - mode of library - - - - Peer connection state - - - - - Network peer. Main purpose is sending messages to specific peer. - - - - - Peer ip address and port - - - - - Peer parent NetManager - - - - - Current connection state - - - - - Connection time for internal purposes - - - - - Peer id can be used as key in your dictionary of peers - - - - - Id assigned from server - - - - - Current one-way ping (RTT/2) in milliseconds - - - - - Round trip time in milliseconds - - - - - Current MTU - Maximum Transfer Unit ( maximum udp packet size without fragmentation ) - - - - - Delta with remote time in ticks (not accurate) - positive - remote time > our time - - - - - Remote UTC time (not accurate) - - - - - Time since last packet received (including internal library packets) - - - - - Application defined object containing data about the connection - - - - - Statistics of peer connection - - - - - Returns packets count in queue for reliable channel - - number of channel 0-63 - type of channel ReliableOrdered or ReliableUnordered - packets count in channel queue - - - - Create temporary packet (maximum size MTU - headerSize) to send later without additional copies - - Delivery method (reliable, unreliable, etc.) - Number of channel (from 0 to channelsCount - 1) - PooledPacket that you can use to write data starting from UserDataOffset - - - - Sends pooled packet without data copy - - packet to send - size of user data you want to send - - - - Gets maximum size of packet that will be not fragmented. - - Type of packet that you want send - size in bytes - - - - Send data to peer with delivery event called - - Data - Number of channel (from 0 to channelsCount - 1) - Delivery method (reliable, unreliable, etc.) - User data that will be received in DeliveryEvent - - If you trying to send unreliable packet type - - - - - Send data to peer with delivery event called - - Data - Start of data - Length of data - Number of channel (from 0 to channelsCount - 1) - Delivery method (reliable, unreliable, etc.) - User data that will be received in DeliveryEvent - - If you trying to send unreliable packet type - - - - - Send data to peer with delivery event called - - Data - Number of channel (from 0 to channelsCount - 1) - Delivery method (reliable, unreliable, etc.) - User data that will be received in DeliveryEvent - - If you trying to send unreliable packet type - - - - - Send data to peer (channel - 0) - - Data - Send options (reliable, unreliable, etc.) - - If size exceeds maximum limit: - MTU - headerSize bytes for Unreliable - Fragment count exceeded ushort.MaxValue - - - - - Send data to peer (channel - 0) - - DataWriter with data - Send options (reliable, unreliable, etc.) - - If size exceeds maximum limit: - MTU - headerSize bytes for Unreliable - Fragment count exceeded ushort.MaxValue - - - - - Send data to peer (channel - 0) - - Data - Start of data - Length of data - Send options (reliable, unreliable, etc.) - - If size exceeds maximum limit: - MTU - headerSize bytes for Unreliable - Fragment count exceeded ushort.MaxValue - - - - - Send data to peer - - Data - Number of channel (from 0 to channelsCount - 1) - Send options (reliable, unreliable, etc.) - - If size exceeds maximum limit: - MTU - headerSize bytes for Unreliable - Fragment count exceeded ushort.MaxValue - - - - - Send data to peer - - DataWriter with data - Number of channel (from 0 to channelsCount - 1) - Send options (reliable, unreliable, etc.) - - If size exceeds maximum limit: - MTU - headerSize bytes for Unreliable - Fragment count exceeded ushort.MaxValue - - - - - Send data to peer - - Data - Start of data - Length of data - Number of channel (from 0 to channelsCount - 1) - Delivery method (reliable, unreliable, etc.) - - If size exceeds maximum limit: - MTU - headerSize bytes for Unreliable - Fragment count exceeded ushort.MaxValue - - - - - Address type that you want to receive from NetUtils.GetLocalIp method - - - - - Some specific network utilities - - - - - Get all local ip addresses - - type of address (IPv4, IPv6 or both) - List with all local ip addresses - - - - Get all local ip addresses (non alloc version) - - result list - type of address (IPv4, IPv6 or both) - - - - Get first detected local ip address - - type of address (IPv4, IPv6 or both) - IP address if available. Else - string.Empty - - - - Maximum data size that you can put into such packet - - - - - Offset for user data when writing to Data array - - - - - Raw packet data. Do not modify header! Use UserDataOffset as start point for your data - - - - - Compute CRC32C for data - - input data - offset - length - CRC32C checksum - - - - Note that "maxLength" only limits the number of characters in a string, not its size in bytes. - - "string.Empty" if value > "maxLength" - - - - Creates NetDataWriter from existing ByteArray - - Source byte array - Copy array to new location or use existing - - - - Creates NetDataWriter from existing ByteArray (always copied data) - - Source byte array - Offset of array - Length of array - - - - Sets position of NetDataWriter to rewrite previous values - - new byte position - previous position of data writer - - - - Note that "maxLength" only limits the number of characters in a string, not its size in bytes. - - - - - Register nested property type - - INetSerializable structure - - - - Register nested property type - - - - - - - Register nested property type - - INetSerializable class - - - - Reads all available data from NetDataReader and calls OnReceive delegates - - NetDataReader with packets data - - - - Reads all available data from NetDataReader and calls OnReceive delegates - - NetDataReader with packets data - Argument that passed to OnReceivedEvent - Malformed packet - - - - Reads one packet from NetDataReader and calls OnReceive delegate - - NetDataReader with packet - Malformed packet - - - - Reads one packet from NetDataReader and calls OnReceive delegate - - NetDataReader with packet - Argument that passed to OnReceivedEvent - Malformed packet - - - - Register and subscribe to packet receive event - - event that will be called when packet deserialized with ReadPacket method - Method that constructs packet instead of slow Activator.CreateInstance - 's fields are not supported, or it has no fields - - - - Register and subscribe to packet receive event (with userData) - - event that will be called when packet deserialized with ReadPacket method - Method that constructs packet instead of slow Activator.CreateInstance - 's fields are not supported, or it has no fields - - - - Register and subscribe to packet receive event - This method will overwrite last received packet class on receive (less garbage) - - event that will be called when packet deserialized with ReadPacket method - 's fields are not supported, or it has no fields - - - - Register and subscribe to packet receive event - This method will overwrite last received packet class on receive (less garbage) - - event that will be called when packet deserialized with ReadPacket method - 's fields are not supported, or it has no fields - - - - Remove any subscriptions by type - - Packet type - true if remove is success - - - - Register custom property type - - INetSerializable structure - - - - Register custom property type - - INetSerializable class - - - - Register custom property type - - Any packet - custom type writer - custom type reader - - - 's fields are not supported, or it has no fields - - - - Reads packet with known type - - NetDataReader with packet - Returns packet if packet in reader is matched type - 's fields are not supported, or it has no fields - - - - Reads packet with known type (non alloc variant) - - NetDataReader with packet - Deserialization target - Returns true if packet in reader is matched type - 's fields are not supported, or it has no fields - - - - Serialize object to NetDataWriter (fast) - - Serialization target NetDataWriter - Object to serialize - 's fields are not supported, or it has no fields - - - - Serialize object to byte array - - Object to serialize - byte array with serialized data - - - - Represents RFC4330 SNTP packet used for communication to and from a network time server. - - - - Most applications should just use the property. - - - The same data structure represents both request and reply packets. - Request and reply differ in which properties are set and to what values. - - - The only real property is . - All other properties read from and write to the underlying byte array - with the exception of , - which is not part of the packet on network and it is instead set locally after receiving the packet. - - - Copied from GuerrillaNtp project - with permission from Robert Vazan (@robertvazan) under MIT license, see https://github.com/RevenantX/LiteNetLib/pull/236 - - - - - - Gets RFC4330-encoded SNTP packet. - - - Byte array containing RFC4330-encoded SNTP packet. It is at least 48 bytes long. - - - This is the only real property. All other properties except - read from or write to this byte array. - - - - - Gets the leap second indicator. - - - Leap second warning, if any. Special value - indicates unsynchronized server clock. - Default is . - - - Only servers fill in this property. Clients can consult this property for possible leap second warning. - - - - - Gets or sets protocol version number. - - - SNTP protocol version. Default is 4, which is the latest version at the time of this writing. - - - In request packets, clients should leave this property at default value 4. - Servers usually reply with the same protocol version. - - - - - Gets or sets SNTP packet mode, i.e. whether this is client or server packet. - - - SNTP packet mode. Default is in newly created packets. - Server reply should have this property set to . - - - - - Gets server's distance from the reference clock. - - - - Distance from the reference clock. This property is set only in server reply packets. - Servers connected directly to reference clock hardware set this property to 1. - Statum number is incremented by 1 on every hop down the NTP server hierarchy. - - - Special value 0 indicates that this packet is a Kiss-o'-Death message - with kiss code stored in . - - - - - - Gets server's preferred polling interval. - - - Polling interval in log2 seconds, e.g. 4 stands for 16s and 17 means 131,072s. - - - - - Gets the precision of server clock. - - - Clock precision in log2 seconds, e.g. -20 for microsecond precision. - - - - - Gets the total round-trip delay from the server to the reference clock. - - - Round-trip delay to the reference clock. Normally a positive value smaller than one second. - - - - - Gets the estimated error in time reported by the server. - - - Estimated error in time reported by the server. Normally a positive value smaller than one second. - - - - - Gets the ID of the time source used by the server or Kiss-o'-Death code sent by the server. - - - - ID of server's time source or Kiss-o'-Death code. - Purpose of this property depends on value of property. - - - Stratum 1 servers write here one of several special values that describe the kind of hardware clock they use. - - - Stratum 2 and lower servers set this property to IPv4 address of their upstream server. - If upstream server has IPv6 address, the address is hashed, because it doesn't fit in this property. - - - When server sets to special value 0, - this property contains so called kiss code that instructs the client to stop querying the server. - - - - - - Gets or sets the time when the server clock was last set or corrected. - - - Time when the server clock was last set or corrected or null when not specified. - - - This Property is usually set only by servers. It usually lags server's current time by several minutes, - so don't use this property for time synchronization. - - - - - Gets or sets the time when the client sent its request. - - - This property is null in request packets. - In reply packets, it is the time when the client sent its request. - Servers copy this value from - that they find in received request packet. - - - - - - - Gets or sets the time when the request was received by the server. - - - This property is null in request packets. - In reply packets, it is the time when the server received client request. - - - - - - - Gets or sets the time when the packet was sent. - - - Time when the packet was sent. It should never be null. - Default value is . - - - This property must be set by both clients and servers. - - - - - - - Gets or sets the time of reception of response SNTP packet on the client. - - - Time of reception of response SNTP packet on the client. It is null in request packets. - - - This property is not part of the protocol and has to be set when reply packet is received. - - - - - - - Gets the round-trip time to the server. - - - Time the request spent traveling to the server plus the time the reply spent traveling back. - This is calculated from timestamps in the packet as (t1 - t0) + (t3 - t2) - where t0 is , - t1 is , - t2 is , - and t3 is . - This property throws an exception in request packets. - - - - - Gets the offset that should be added to local time to synchronize it with server time. - - - Time difference between server and client. It should be added to local time to get server time. - It is calculated from timestamps in the packet as 0.5 * ((t1 - t0) - (t3 - t2)) - where t0 is , - t1 is , - t2 is , - and t3 is . - This property throws an exception in request packets. - - - - - Initializes default request packet. - - - Properties and - are set appropriately for request packet. Property - is set to . - - - - - Initializes packet from received data. - - - - - Initializes packet from data received from a server. - - Data received from the server. - Utc time of reception of response SNTP packet on the client. - - - - - Represents leap second warning from the server that instructs the client to add or remove leap second. - - - - - - No leap second warning. No action required. - - - - - Warns the client that the last minute of the current day has 61 seconds. - - - - - Warns the client that the last minute of the current day has 59 seconds. - - - - - Special value indicating that the server clock is unsynchronized and the returned time is unreliable. - - - - - Describes SNTP packet mode, i.e. client or server. - - - - - - Identifies client-to-server SNTP packet. - - - - - Identifies server-to-client SNTP packet. - - - - diff --git a/LiteNetLibSampleUnity/Assets/LiteNetLib.pdb.meta b/LiteNetLibSampleUnity/Assets/Resources.meta similarity index 67% rename from LiteNetLibSampleUnity/Assets/LiteNetLib.pdb.meta rename to LiteNetLibSampleUnity/Assets/Resources.meta index c632ac23..0ec2b74a 100644 --- a/LiteNetLibSampleUnity/Assets/LiteNetLib.pdb.meta +++ b/LiteNetLibSampleUnity/Assets/Resources.meta @@ -1,5 +1,6 @@ fileFormatVersion: 2 -guid: 8d4c429e3f74b074299de2753ca8de73 +guid: 0680635b8862cad41957568e3143c8c7 +folderAsset: yes DefaultImporter: externalObjects: {} userData: diff --git a/LiteNetLibSampleUnity/Assets/Resources/BillingMode.json b/LiteNetLibSampleUnity/Assets/Resources/BillingMode.json new file mode 100644 index 00000000..6f4bfb71 --- /dev/null +++ b/LiteNetLibSampleUnity/Assets/Resources/BillingMode.json @@ -0,0 +1 @@ +{"androidStore":"GooglePlay"} \ No newline at end of file diff --git a/LiteNetLibSampleUnity/Assets/LiteNetLib.xml.meta b/LiteNetLibSampleUnity/Assets/Resources/BillingMode.json.meta similarity index 75% rename from LiteNetLibSampleUnity/Assets/LiteNetLib.xml.meta rename to LiteNetLibSampleUnity/Assets/Resources/BillingMode.json.meta index 30ffaeba..19a785dd 100644 --- a/LiteNetLibSampleUnity/Assets/LiteNetLib.xml.meta +++ b/LiteNetLibSampleUnity/Assets/Resources/BillingMode.json.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 46009b99ce8ce4b4b9de461f4fc5cec2 +guid: 935f8151a9a85ee40bfcd6cbe0333137 TextScriptImporter: externalObjects: {} userData: diff --git a/LiteNetLibSampleUnity/Packages/manifest.json b/LiteNetLibSampleUnity/Packages/manifest.json new file mode 100644 index 00000000..af623106 --- /dev/null +++ b/LiteNetLibSampleUnity/Packages/manifest.json @@ -0,0 +1,54 @@ +{ + "dependencies": { + "com.revenantx.litenetlib": "1.3.0", + "com.unity.2d.sprite": "1.0.0", + "com.unity.collab-proxy": "2.5.2", + "com.unity.ide.rider": "3.0.34", + "com.unity.ide.visualstudio": "2.0.22", + "com.unity.ide.vscode": "1.2.5", + "com.unity.textmeshpro": "3.0.9", + "com.unity.timeline": "1.7.6", + "com.unity.ugui": "1.0.0", + "com.unity.xr.legacyinputhelpers": "2.1.11", + "com.unity.modules.ai": "1.0.0", + "com.unity.modules.androidjni": "1.0.0", + "com.unity.modules.animation": "1.0.0", + "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.cloth": "1.0.0", + "com.unity.modules.director": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.particlesystem": "1.0.0", + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.physics2d": "1.0.0", + "com.unity.modules.screencapture": "1.0.0", + "com.unity.modules.terrain": "1.0.0", + "com.unity.modules.terrainphysics": "1.0.0", + "com.unity.modules.tilemap": "1.0.0", + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.uielements": "1.0.0", + "com.unity.modules.umbra": "1.0.0", + "com.unity.modules.unityanalytics": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.unitywebrequestassetbundle": "1.0.0", + "com.unity.modules.unitywebrequestaudio": "1.0.0", + "com.unity.modules.unitywebrequesttexture": "1.0.0", + "com.unity.modules.unitywebrequestwww": "1.0.0", + "com.unity.modules.vehicles": "1.0.0", + "com.unity.modules.video": "1.0.0", + "com.unity.modules.vr": "1.0.0", + "com.unity.modules.wind": "1.0.0", + "com.unity.modules.xr": "1.0.0" + }, + "scopedRegistries": [ + { + "name": "package.openupm.com", + "url": "https://package.openupm.com", + "scopes": [ + "com.revenantx.litenetlib" + ] + } + ] +} diff --git a/LiteNetLibSampleUnity/Packages/packages-lock.json b/LiteNetLibSampleUnity/Packages/packages-lock.json new file mode 100644 index 00000000..f5692892 --- /dev/null +++ b/LiteNetLibSampleUnity/Packages/packages-lock.json @@ -0,0 +1,350 @@ +{ + "dependencies": { + "com.revenantx.litenetlib": { + "version": "1.3.0", + "depth": 0, + "source": "registry", + "dependencies": {}, + "url": "https://package.openupm.com" + }, + "com.unity.2d.sprite": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.collab-proxy": { + "version": "2.5.2", + "depth": 0, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.ext.nunit": { + "version": "1.0.6", + "depth": 1, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.ide.rider": { + "version": "3.0.34", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.ext.nunit": "1.0.6" + }, + "url": "https://packages.unity.com" + }, + "com.unity.ide.visualstudio": { + "version": "2.0.22", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.test-framework": "1.1.9" + }, + "url": "https://packages.unity.com" + }, + "com.unity.ide.vscode": { + "version": "1.2.5", + "depth": 0, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.test-framework": { + "version": "1.1.33", + "depth": 1, + "source": "registry", + "dependencies": { + "com.unity.ext.nunit": "1.0.6", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.textmeshpro": { + "version": "3.0.9", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.ugui": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.timeline": { + "version": "1.7.6", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.director": "1.0.0", + "com.unity.modules.animation": "1.0.0", + "com.unity.modules.particlesystem": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.ugui": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.imgui": "1.0.0" + } + }, + "com.unity.xr.legacyinputhelpers": { + "version": "2.1.11", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.modules.vr": "1.0.0", + "com.unity.modules.xr": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.modules.ai": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.androidjni": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.animation": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.assetbundle": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.audio": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.cloth": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0" + } + }, + "com.unity.modules.director": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.animation": "1.0.0" + } + }, + "com.unity.modules.imageconversion": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.imgui": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.jsonserialize": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.particlesystem": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.physics": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.physics2d": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.screencapture": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.imageconversion": "1.0.0" + } + }, + "com.unity.modules.subsystems": { + "version": "1.0.0", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.modules.jsonserialize": "1.0.0" + } + }, + "com.unity.modules.terrain": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.terrainphysics": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.terrain": "1.0.0" + } + }, + "com.unity.modules.tilemap": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics2d": "1.0.0" + } + }, + "com.unity.modules.ui": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.uielements": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + } + }, + "com.unity.modules.umbra": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.unityanalytics": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + } + }, + "com.unity.modules.unitywebrequest": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.unitywebrequestassetbundle": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0" + } + }, + "com.unity.modules.unitywebrequestaudio": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.audio": "1.0.0" + } + }, + "com.unity.modules.unitywebrequesttexture": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0" + } + }, + "com.unity.modules.unitywebrequestwww": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.unitywebrequestassetbundle": "1.0.0", + "com.unity.modules.unitywebrequestaudio": "1.0.0", + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0" + } + }, + "com.unity.modules.vehicles": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0" + } + }, + "com.unity.modules.video": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0" + } + }, + "com.unity.modules.vr": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.xr": "1.0.0" + } + }, + "com.unity.modules.wind": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.xr": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.subsystems": "1.0.0" + } + } + } +} diff --git a/LiteNetLibSampleUnity/ProjectSettings/MemorySettings.asset b/LiteNetLibSampleUnity/ProjectSettings/MemorySettings.asset new file mode 100644 index 00000000..5b5facec --- /dev/null +++ b/LiteNetLibSampleUnity/ProjectSettings/MemorySettings.asset @@ -0,0 +1,35 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!387306366 &1 +MemorySettings: + m_ObjectHideFlags: 0 + m_EditorMemorySettings: + m_MainAllocatorBlockSize: -1 + m_ThreadAllocatorBlockSize: -1 + m_MainGfxBlockSize: -1 + m_ThreadGfxBlockSize: -1 + m_CacheBlockSize: -1 + m_TypetreeBlockSize: -1 + m_ProfilerBlockSize: -1 + m_ProfilerEditorBlockSize: -1 + m_BucketAllocatorGranularity: -1 + m_BucketAllocatorBucketsCount: -1 + m_BucketAllocatorBlockSize: -1 + m_BucketAllocatorBlockCount: -1 + m_ProfilerBucketAllocatorGranularity: -1 + m_ProfilerBucketAllocatorBucketsCount: -1 + m_ProfilerBucketAllocatorBlockSize: -1 + m_ProfilerBucketAllocatorBlockCount: -1 + m_TempAllocatorSizeMain: -1 + m_JobTempAllocatorBlockSize: -1 + m_BackgroundJobTempAllocatorBlockSize: -1 + m_JobTempAllocatorReducedBlockSize: -1 + m_TempAllocatorSizeGIBakingWorker: -1 + m_TempAllocatorSizeNavMeshWorker: -1 + m_TempAllocatorSizeAudioWorker: -1 + m_TempAllocatorSizeCloudWorker: -1 + m_TempAllocatorSizeGfx: -1 + m_TempAllocatorSizeJobWorker: -1 + m_TempAllocatorSizeBackgroundWorker: -1 + m_TempAllocatorSizePreloadManager: -1 + m_PlatformMemorySettings: {} diff --git a/LiteNetLibSampleUnity/ProjectSettings/PackageManagerSettings.asset b/LiteNetLibSampleUnity/ProjectSettings/PackageManagerSettings.asset new file mode 100644 index 00000000..d5a8583c --- /dev/null +++ b/LiteNetLibSampleUnity/ProjectSettings/PackageManagerSettings.asset @@ -0,0 +1,44 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &1 +MonoBehaviour: + m_ObjectHideFlags: 53 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 13964, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_EnablePreReleasePackages: 0 + m_AdvancedSettingsExpanded: 1 + m_ScopedRegistriesSettingsExpanded: 1 + m_SeeAllPackageVersions: 0 + m_DismissPreviewPackagesInUse: 0 + oneTimeWarningShown: 0 + m_Registries: + - m_Id: main + m_Name: + m_Url: https://packages.unity.com + m_Scopes: [] + m_IsDefault: 1 + m_Capabilities: 7 + m_ConfigSource: 0 + - m_Id: scoped:project:package.openupm.com + m_Name: package.openupm.com + m_Url: https://package.openupm.com + m_Scopes: + - com.revenantx.litenetlib + m_IsDefault: 0 + m_Capabilities: 0 + m_ConfigSource: 4 + m_UserSelectedRegistryName: package.openupm.com + m_UserAddingNewScopedRegistry: 0 + m_RegistryInfoDraft: + m_Modified: 0 + m_ErrorMessage: + m_UserModificationsInstanceId: -834 + m_OriginalInstanceId: -836 + m_LoadAssets: 0 diff --git a/LiteNetLibSampleUnity/ProjectSettings/PresetManager.asset b/LiteNetLibSampleUnity/ProjectSettings/PresetManager.asset index 3f07844c..67a94dae 100644 Binary files a/LiteNetLibSampleUnity/ProjectSettings/PresetManager.asset and b/LiteNetLibSampleUnity/ProjectSettings/PresetManager.asset differ diff --git a/LiteNetLibSampleUnity/ProjectSettings/ProjectSettings.asset b/LiteNetLibSampleUnity/ProjectSettings/ProjectSettings.asset index 85038959..ed32fde8 100644 --- a/LiteNetLibSampleUnity/ProjectSettings/ProjectSettings.asset +++ b/LiteNetLibSampleUnity/ProjectSettings/ProjectSettings.asset @@ -3,9 +3,11 @@ --- !u!129 &1 PlayerSettings: m_ObjectHideFlags: 0 - serializedVersion: 11 + serializedVersion: 26 productGUID: cf592f4b245d2b540a06dd6461f51365 AndroidProfiler: 0 + AndroidFilterTouchesWhenObscured: 0 + AndroidEnableSustainedPerformanceMode: 0 defaultScreenOrientation: 4 targetDevice: 2 useOnDemandResources: 0 @@ -14,7 +16,7 @@ PlayerSettings: productName: LiteNetLibSampleUnity defaultCursor: {fileID: 0} cursorHotspot: {x: 0, y: 0} - m_SplashScreenBackgroundColor: {r: 0.13333334, g: 0.17254902, b: 0.21176471, a: 1} + m_SplashScreenBackgroundColor: {r: 0.12156863, g: 0.12156863, b: 0.1254902, a: 1} m_ShowUnitySplashScreen: 1 m_ShowUnitySplashLogo: 1 m_SplashScreenOverlayOpacity: 1 @@ -38,8 +40,6 @@ PlayerSettings: width: 1 height: 1 m_SplashScreenLogos: [] - m_SplashScreenBackgroundLandscape: {fileID: 0} - m_SplashScreenBackgroundPortrait: {fileID: 0} m_VirtualRealitySplashScreen: {fileID: 0} m_HolographicTrackingLossScreen: {fileID: 0} defaultScreenWidth: 1024 @@ -48,37 +48,55 @@ PlayerSettings: defaultScreenHeightWeb: 600 m_StereoRenderingPath: 0 m_ActiveColorSpace: 0 + unsupportedMSAAFallback: 0 + m_SpriteBatchVertexThreshold: 300 m_MTRendering: 1 - m_MobileMTRendering: 0 + mipStripping: 0 + numberOfMipsStripped: 0 + numberOfMipsStrippedPerMipmapLimitGroup: {} m_StackTraceTypes: 010000000100000001000000010000000100000001000000 iosShowActivityIndicatorOnLoading: -1 androidShowActivityIndicatorOnLoading: -1 - tizenShowActivityIndicatorOnLoading: -1 - iosAppInBackgroundBehavior: 0 - displayResolutionDialog: 1 - iosAllowHTTPDownload: 1 + iosUseCustomAppBackgroundBehavior: 0 allowedAutorotateToPortrait: 1 allowedAutorotateToPortraitUpsideDown: 1 allowedAutorotateToLandscapeRight: 1 allowedAutorotateToLandscapeLeft: 1 useOSAutorotation: 1 use32BitDisplayBuffer: 1 + preserveFramebufferAlpha: 0 disableDepthAndStencilBuffers: 0 - defaultIsFullScreen: 1 + androidStartInFullscreen: 1 + androidRenderOutsideSafeArea: 1 + androidUseSwappy: 0 + androidBlitType: 0 + androidResizableWindow: 0 + androidDefaultWindowWidth: 1920 + androidDefaultWindowHeight: 1080 + androidMinimumWindowWidth: 400 + androidMinimumWindowHeight: 300 + androidFullscreenMode: 1 + androidAutoRotationBehavior: 1 defaultIsNativeResolution: 1 + macRetinaSupport: 1 runInBackground: 0 captureSingleScreen: 0 muteOtherAudioSources: 0 Prepare IOS For Recording: 0 + Force IOS Speakers When Recording: 0 + audioSpatialExperience: 0 + deferSystemGesturesMode: 0 + hideHomeButton: 0 submitAnalytics: 1 usePlayerLog: 1 + dedicatedServerOptimizations: 0 bakeCollisionMeshes: 0 forceSingleInstance: 0 + useFlipModelSwapchain: 1 resizableWindow: 0 useMacAppStoreValidation: 0 macAppStoreCategory: public.app-category.games gpuSkinning: 0 - graphicsJobs: 0 xboxPIXTextureCapture: 0 xboxEnableAvatar: 0 xboxEnableKinect: 0 @@ -86,66 +104,77 @@ PlayerSettings: xboxEnableFitness: 0 visibleInBackground: 0 allowFullscreenSwitch: 1 - graphicsJobMode: 0 - macFullscreenMode: 2 - d3d9FullscreenMode: 1 - d3d11FullscreenMode: 1 + fullscreenMode: 1 xboxSpeechDB: 0 xboxEnableHeadOrientation: 0 xboxEnableGuest: 0 xboxEnablePIXSampling: 0 - n3dsDisableStereoscopicView: 0 - n3dsEnableSharedListOpt: 1 - n3dsEnableVSync: 0 - ignoreAlphaClear: 0 + metalFramebufferOnly: 0 xboxOneResolution: 0 + xboxOneSResolution: 0 + xboxOneXResolution: 3 xboxOneMonoLoggingLevel: 0 xboxOneLoggingLevel: 1 - videoMemoryForVertexBuffers: 0 - psp2PowerMode: 0 - psp2AcquireBGM: 1 - wiiUTVResolution: 0 - wiiUGamePadMSAA: 1 - wiiUSupportsNunchuk: 0 - wiiUSupportsClassicController: 0 - wiiUSupportsBalanceBoard: 0 - wiiUSupportsMotionPlus: 0 - wiiUSupportsProController: 0 - wiiUAllowScreenCapture: 1 - wiiUControllerCount: 0 - m_SupportedAspectRatios: - 4:3: 1 - 5:4: 1 - 16:10: 1 - 16:9: 1 - Others: 1 + xboxOneDisableEsram: 0 + xboxOneEnableTypeOptimization: 0 + xboxOnePresentImmediateThreshold: 0 + switchQueueCommandMemory: 1048576 + switchQueueControlMemory: 16384 + switchQueueComputeMemory: 262144 + switchNVNShaderPoolsGranularity: 33554432 + switchNVNDefaultPoolsGranularity: 16777216 + switchNVNOtherPoolsGranularity: 16777216 + switchGpuScratchPoolGranularity: 2097152 + switchAllowGpuScratchShrinking: 0 + switchNVNMaxPublicTextureIDCount: 0 + switchNVNMaxPublicSamplerIDCount: 0 + switchNVNGraphicsFirmwareMemory: 32 + switchMaxWorkerMultiple: 8 + stadiaPresentMode: 0 + stadiaTargetFramerate: 0 + vulkanNumSwapchainBuffers: 3 + vulkanEnableSetSRGBWrite: 0 + vulkanEnablePreTransform: 0 + vulkanEnableLateAcquireNextImage: 0 + vulkanEnableCommandBufferRecycling: 1 + loadStoreDebugModeEnabled: 0 + visionOSBundleVersion: 1.0 + tvOSBundleVersion: 1.0 bundleVersion: 1.0 preloadedAssets: [] metroInputSource: 0 + wsaTransparentSwapchain: 0 m_HolographicPauseOnTrackingLoss: 1 xboxOneDisableKinectGpuReservation: 0 xboxOneEnable7thCore: 0 vrSettings: - cardboard: - depthFormat: 0 - enableTransitionView: 0 - daydream: - depthFormat: 0 - useSustainedPerformanceMode: 0 - hololens: - depthFormat: 1 - protectGraphicsMemory: 0 + enable360StereoCapture: 0 + isWsaHolographicRemotingEnabled: 0 + enableFrameTimingStats: 0 + enableOpenGLProfilerGPURecorders: 1 + allowHDRDisplaySupport: 0 useHDRDisplay: 0 + hdrBitDepth: 0 + m_ColorGamuts: 00000000 + targetPixelDensity: 30 + resolutionScalingMode: 0 + resetResolutionOnWindowResize: 0 + androidSupportedAspectRatio: 1 + androidMaxAspectRatio: 2.1 applicationIdentifier: Android: com.RevenantX.LiteNetLibTest Standalone: unity.RevenantX.LiteNetLibSampleUnity Tizen: com.RevenantX.LiteNetLibTest - iOS: com.RevenantX.LiteNetLibTest + iPhone: com.RevenantX.LiteNetLibTest tvOS: com.RevenantX.LiteNetLibTest buildNumber: - iOS: 0 + Standalone: 0 + VisionOS: 0 + iPhone: 0 + tvOS: 0 + overrideDefaultApplicationIdentifier: 1 AndroidBundleVersionCode: 1 - AndroidMinSdkVersion: 16 + AndroidMinSdkVersion: 22 AndroidTargetSdkVersion: 0 AndroidPreferredInstallLocation: 1 aotOptions: @@ -158,35 +187,31 @@ PlayerSettings: APKExpansionFiles: 0 keepLoadedShadersAlive: 0 StripUnusedMeshComponents: 0 - VertexChannelCompressionMask: - serializedVersion: 2 - m_Bits: 238 + strictShaderVariantMatching: 0 + VertexChannelCompressionMask: 214 iPhoneSdkVersion: 988 - iOSTargetOSVersionString: 6.0 + iOSTargetOSVersionString: 12.0 tvOSSdkVersion: 0 tvOSRequireExtendedGameController: 0 - tvOSTargetOSVersionString: 9.0 + tvOSTargetOSVersionString: 12.0 + VisionOSSdkVersion: 0 + VisionOSTargetOSVersionString: 1.0 uIPrerenderedIcon: 0 uIRequiresPersistentWiFi: 0 uIRequiresFullScreen: 1 uIStatusBarHidden: 1 uIExitOnSuspend: 0 uIStatusBarStyle: 0 - iPhoneSplashScreen: {fileID: 0} - iPhoneHighResSplashScreen: {fileID: 0} - iPhoneTallHighResSplashScreen: {fileID: 0} - iPhone47inSplashScreen: {fileID: 0} - iPhone55inPortraitSplashScreen: {fileID: 0} - iPhone55inLandscapeSplashScreen: {fileID: 0} - iPadPortraitSplashScreen: {fileID: 0} - iPadHighResPortraitSplashScreen: {fileID: 0} - iPadLandscapeSplashScreen: {fileID: 0} - iPadHighResLandscapeSplashScreen: {fileID: 0} appleTVSplashScreen: {fileID: 0} + appleTVSplashScreen2x: {fileID: 0} tvOSSmallIconLayers: [] + tvOSSmallIconLayers2x: [] tvOSLargeIconLayers: [] + tvOSLargeIconLayers2x: [] tvOSTopShelfImageLayers: [] + tvOSTopShelfImageLayers2x: [] tvOSTopShelfImageWideLayers: [] + tvOSTopShelfImageWideLayers2x: [] iOSLaunchScreenType: 0 iOSLaunchScreenPortrait: {fileID: 0} iOSLaunchScreenLandscape: {fileID: 0} @@ -204,31 +229,65 @@ PlayerSettings: iOSLaunchScreeniPadFillPct: 100 iOSLaunchScreeniPadSize: 100 iOSLaunchScreeniPadCustomXibPath: + iOSLaunchScreenCustomStoryboardPath: + iOSLaunchScreeniPadCustomStoryboardPath: iOSDeviceRequirements: [] iOSURLSchemes: [] + macOSURLSchemes: [] iOSBackgroundModes: 0 iOSMetalForceHardShadows: 0 metalEditorSupport: 1 metalAPIValidation: 1 + metalCompileShaderBinary: 0 iOSRenderExtraFrameOnPause: 1 + iosCopyPluginsCodeInsteadOfSymlink: 0 appleDeveloperTeamID: iOSManualSigningProvisioningProfileID: tvOSManualSigningProvisioningProfileID: + VisionOSManualSigningProvisioningProfileID: + iOSManualSigningProvisioningProfileType: 0 + tvOSManualSigningProvisioningProfileType: 0 + VisionOSManualSigningProvisioningProfileType: 0 appleEnableAutomaticSigning: 0 - AndroidTargetDevice: 0 + iOSRequireARKit: 0 + iOSAutomaticallyDetectAndAddCapabilities: 1 + appleEnableProMotion: 0 + shaderPrecisionModel: 0 + clonedFromGUID: 00000000000000000000000000000000 + templatePackageId: + templateDefaultScene: + useCustomMainManifest: 0 + useCustomLauncherManifest: 0 + useCustomMainGradleTemplate: 0 + useCustomLauncherGradleManifest: 0 + useCustomBaseGradleTemplate: 0 + useCustomGradlePropertiesTemplate: 0 + useCustomGradleSettingsTemplate: 0 + useCustomProguardFile: 0 + AndroidTargetArchitectures: 1 + AndroidTargetDevices: 0 AndroidSplashScreenScale: 0 androidSplashScreen: {fileID: 0} - AndroidKeystoreName: + AndroidKeystoreName: '{inproject}: ' AndroidKeyaliasName: + AndroidEnableArmv9SecurityFeatures: 0 + AndroidBuildApkPerCpuArchitecture: 0 AndroidTVCompatibility: 1 AndroidIsGame: 1 + AndroidEnableTango: 0 androidEnableBanner: 1 + androidUseLowAccuracyLocation: 0 + androidUseCustomKeystore: 0 m_AndroidBanners: - width: 320 height: 180 banner: {fileID: 0} androidGamepadSupportLevel: 0 - resolutionDialogBanner: {fileID: 0} + chromeosInputEmulation: 1 + AndroidMinifyRelease: 0 + AndroidMinifyDebug: 0 + AndroidValidateAppBundleSize: 1 + AndroidAppBundleSizeToValidate: 150 m_BuildTargetIcons: - m_BuildTarget: m_Icons: @@ -236,14 +295,256 @@ PlayerSettings: m_Icon: {fileID: 0} m_Width: 128 m_Height: 128 + m_Kind: 0 + m_BuildTargetPlatformIcons: + - m_BuildTarget: Android + m_Icons: + - m_Textures: [] + m_Width: 432 + m_Height: 432 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 324 + m_Height: 324 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 216 + m_Height: 216 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 162 + m_Height: 162 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 108 + m_Height: 108 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 81 + m_Height: 81 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 192 + m_Height: 192 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 144 + m_Height: 144 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 96 + m_Height: 96 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 72 + m_Height: 72 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 48 + m_Height: 48 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 36 + m_Height: 36 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 192 + m_Height: 192 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 144 + m_Height: 144 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 96 + m_Height: 96 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 72 + m_Height: 72 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 48 + m_Height: 48 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 36 + m_Height: 36 + m_Kind: 0 + m_SubKind: + - m_BuildTarget: iPhone + m_Icons: + - m_Textures: [] + m_Width: 180 + m_Height: 180 + m_Kind: 0 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 120 + m_Height: 120 + m_Kind: 0 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 167 + m_Height: 167 + m_Kind: 0 + m_SubKind: iPad + - m_Textures: [] + m_Width: 152 + m_Height: 152 + m_Kind: 0 + m_SubKind: iPad + - m_Textures: [] + m_Width: 76 + m_Height: 76 + m_Kind: 0 + m_SubKind: iPad + - m_Textures: [] + m_Width: 120 + m_Height: 120 + m_Kind: 3 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 80 + m_Height: 80 + m_Kind: 3 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 80 + m_Height: 80 + m_Kind: 3 + m_SubKind: iPad + - m_Textures: [] + m_Width: 40 + m_Height: 40 + m_Kind: 3 + m_SubKind: iPad + - m_Textures: [] + m_Width: 87 + m_Height: 87 + m_Kind: 1 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 58 + m_Height: 58 + m_Kind: 1 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 29 + m_Height: 29 + m_Kind: 1 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 58 + m_Height: 58 + m_Kind: 1 + m_SubKind: iPad + - m_Textures: [] + m_Width: 29 + m_Height: 29 + m_Kind: 1 + m_SubKind: iPad + - m_Textures: [] + m_Width: 60 + m_Height: 60 + m_Kind: 2 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 40 + m_Height: 40 + m_Kind: 2 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 40 + m_Height: 40 + m_Kind: 2 + m_SubKind: iPad + - m_Textures: [] + m_Width: 20 + m_Height: 20 + m_Kind: 2 + m_SubKind: iPad + - m_Textures: [] + m_Width: 1024 + m_Height: 1024 + m_Kind: 4 + m_SubKind: App Store m_BuildTargetBatching: [] - m_BuildTargetGraphicsAPIs: [] + m_BuildTargetShaderSettings: [] + m_BuildTargetGraphicsJobs: + - m_BuildTarget: WindowsStandaloneSupport + m_GraphicsJobs: 0 + - m_BuildTarget: MacStandaloneSupport + m_GraphicsJobs: 0 + - m_BuildTarget: LinuxStandaloneSupport + m_GraphicsJobs: 0 + - m_BuildTarget: AndroidPlayer + m_GraphicsJobs: 0 + - m_BuildTarget: iOSSupport + m_GraphicsJobs: 0 + - m_BuildTarget: PS4Player + m_GraphicsJobs: 0 + - m_BuildTarget: PS5Player + m_GraphicsJobs: 0 + - m_BuildTarget: XboxOnePlayer + m_GraphicsJobs: 0 + - m_BuildTarget: GameCoreXboxOneSupport + m_GraphicsJobs: 0 + - m_BuildTarget: GameCoreScarlettSupport + m_GraphicsJobs: 0 + - m_BuildTarget: Switch + m_GraphicsJobs: 0 + - m_BuildTarget: WebGLSupport + m_GraphicsJobs: 0 + - m_BuildTarget: MetroSupport + m_GraphicsJobs: 0 + - m_BuildTarget: AppleTVSupport + m_GraphicsJobs: 0 + - m_BuildTarget: VisionOSPlayer + m_GraphicsJobs: 0 + - m_BuildTarget: BJMSupport + m_GraphicsJobs: 0 + - m_BuildTarget: CloudRendering + m_GraphicsJobs: 0 + - m_BuildTarget: EmbeddedLinux + m_GraphicsJobs: 0 + - m_BuildTarget: QNX + m_GraphicsJobs: 0 + m_BuildTargetGraphicsJobMode: + - m_BuildTarget: PS4Player + m_GraphicsJobMode: 0 + - m_BuildTarget: XboxOnePlayer + m_GraphicsJobMode: 0 + m_BuildTargetGraphicsAPIs: + - m_BuildTarget: iOSSupport + m_APIs: 10000000 + m_Automatic: 1 + - m_BuildTarget: AndroidPlayer + m_APIs: 0b00000008000000 + m_Automatic: 0 m_BuildTargetVRSettings: - m_BuildTarget: Android m_Enabled: 0 m_Devices: - Oculus - - m_BuildTarget: Metro + - m_BuildTarget: Windows Store Apps m_Enabled: 0 m_Devices: [] - m_BuildTarget: N3DS @@ -287,35 +588,50 @@ PlayerSettings: - m_BuildTarget: XboxOne m_Enabled: 0 m_Devices: [] - - m_BuildTarget: iOS + - m_BuildTarget: iPhone m_Enabled: 0 m_Devices: [] - m_BuildTarget: tvOS m_Enabled: 0 m_Devices: [] + m_DefaultShaderChunkSizeInMB: 16 + m_DefaultShaderChunkCount: 0 openGLRequireES31: 0 openGLRequireES31AEP: 0 - webPlayerTemplate: APPLICATION:Default + openGLRequireES32: 0 m_TemplateCustomTags: {} - wiiUTitleID: 0005000011000000 - wiiUGroupID: 00010000 - wiiUCommonSaveSize: 4096 - wiiUAccountSaveSize: 2048 - wiiUOlvAccessKey: 0 - wiiUTinCode: 0 - wiiUJoinGameId: 0 - wiiUJoinGameModeMask: 0000000000000000 - wiiUCommonBossSize: 0 - wiiUAccountBossSize: 0 - wiiUAddOnUniqueIDs: [] - wiiUMainThreadStackSize: 3072 - wiiULoaderThreadStackSize: 1024 - wiiUSystemHeapSize: 128 - wiiUTVStartupScreen: {fileID: 0} - wiiUGamePadStartupScreen: {fileID: 0} - wiiUDrcBufferDisabled: 0 - wiiUProfilerLibPath: + mobileMTRendering: + VisionOS: 1 + iPhone: 1 + tvOS: 1 + m_BuildTargetGroupLightmapEncodingQuality: + - m_BuildTarget: Standalone + m_EncodingQuality: 1 + - m_BuildTarget: XboxOne + m_EncodingQuality: 1 + - m_BuildTarget: PS4 + m_EncodingQuality: 1 + - m_BuildTarget: GameCoreScarlett + m_EncodingQuality: 1 + - m_BuildTarget: GameCoreXboxOne + m_EncodingQuality: 1 + m_BuildTargetGroupHDRCubemapEncodingQuality: + - m_BuildTarget: Standalone + m_EncodingQuality: 2 + - m_BuildTarget: XboxOne + m_EncodingQuality: 2 + - m_BuildTarget: PS4 + m_EncodingQuality: 2 + - m_BuildTarget: GameCoreScarlett + m_EncodingQuality: 2 + - m_BuildTarget: GameCoreXboxOne + m_EncodingQuality: 2 + m_BuildTargetGroupLightmapSettings: [] + m_BuildTargetGroupLoadStoreDebugModeSettings: [] + m_BuildTargetNormalMapEncoding: [] + m_BuildTargetDefaultTextureCompressionFormat: [] playModeTestRunnerEnabled: 0 + runPlayModeTestAsEditModeTest: 0 actionOnDotNetUnhandledException: 1 enableInternalProfiler: 0 logObjCUncaughtExceptions: 1 @@ -323,13 +639,20 @@ PlayerSettings: cameraUsageDescription: locationUsageDescription: microphoneUsageDescription: + bluetoothUsageDescription: + macOSTargetOSVersion: 10.13.0 + switchNMETAOverride: switchNetLibKey: switchSocketMemoryPoolSize: 6144 switchSocketAllocatorPoolSize: 128 switchSocketConcurrencyLimit: 14 + switchScreenResolutionBehavior: 2 switchUseCPUProfiler: 0 + switchEnableFileSystemTrace: 0 + switchLTOSetting: 0 switchApplicationID: 0x0005000C10000001 switchNSODependencies: + switchCompilerFlags: switchTitleNames_0: switchTitleNames_1: switchTitleNames_2: @@ -345,6 +668,7 @@ PlayerSettings: switchTitleNames_12: switchTitleNames_13: switchTitleNames_14: + switchTitleNames_15: switchPublisherNames_0: switchPublisherNames_1: switchPublisherNames_2: @@ -360,6 +684,7 @@ PlayerSettings: switchPublisherNames_12: switchPublisherNames_13: switchPublisherNames_14: + switchPublisherNames_15: switchIcons_0: {fileID: 0} switchIcons_1: {fileID: 0} switchIcons_2: {fileID: 0} @@ -375,6 +700,7 @@ PlayerSettings: switchIcons_12: {fileID: 0} switchIcons_13: {fileID: 0} switchIcons_14: {fileID: 0} + switchIcons_15: {fileID: 0} switchSmallIcons_0: {fileID: 0} switchSmallIcons_1: {fileID: 0} switchSmallIcons_2: {fileID: 0} @@ -390,6 +716,7 @@ PlayerSettings: switchSmallIcons_12: {fileID: 0} switchSmallIcons_13: {fileID: 0} switchSmallIcons_14: {fileID: 0} + switchSmallIcons_15: {fileID: 0} switchManualHTML: switchAccessibleURLs: switchLegalInformation: @@ -399,13 +726,12 @@ PlayerSettings: switchReleaseVersion: 0 switchDisplayVersion: 1.0.0 switchStartupUserAccount: 0 - switchTouchScreenUsage: 0 switchSupportedLanguagesMask: 0 switchLogoType: 0 switchApplicationErrorCodeCategory: switchUserAccountSaveDataSize: 0 switchUserAccountSaveDataJournalSize: 0 - switchAttribute: 0 + switchApplicationAttribute: 0 switchCardSpecSize: 4 switchCardSpecClock: 25 switchRatingsMask: 0 @@ -421,6 +747,7 @@ PlayerSettings: switchRatingsInt_9: 0 switchRatingsInt_10: 0 switchRatingsInt_11: 0 + switchRatingsInt_12: 0 switchLocalCommunicationIds_0: 0x0005000C10000001 switchLocalCommunicationIds_1: switchLocalCommunicationIds_2: @@ -431,7 +758,32 @@ PlayerSettings: switchLocalCommunicationIds_7: switchParentalControl: 0 switchAllowsScreenshot: 1 + switchAllowsVideoCapturing: 1 + switchAllowsRuntimeAddOnContentInstall: 0 switchDataLossConfirmation: 0 + switchUserAccountLockEnabled: 0 + switchSystemResourceMemory: 16777216 + switchSupportedNpadStyles: 22 + switchNativeFsCacheSize: 32 + switchIsHoldTypeHorizontal: 1 + switchSupportedNpadCount: 8 + switchEnableTouchScreen: 1 + switchSocketConfigEnabled: 0 + switchTcpInitialSendBufferSize: 32 + switchTcpInitialReceiveBufferSize: 64 + switchTcpAutoSendBufferSizeMax: 256 + switchTcpAutoReceiveBufferSizeMax: 256 + switchUdpSendBufferSize: 9 + switchUdpReceiveBufferSize: 42 + switchSocketBufferEfficiency: 4 + switchSocketInitializeEnabled: 1 + switchNetworkInterfaceManagerInitializeEnabled: 1 + switchUseNewStyleFilepaths: 1 + switchUseLegacyFmodPriorities: 0 + switchUseMicroSleepForYield: 1 + switchEnableRamDiskSupport: 0 + switchMicroSleepForYieldTime: 25 + switchRamDiskSpaceSize: 12 ps4NPAgeRating: 12 ps4NPTitleSecret: ps4NPTrophyPackPath: @@ -450,12 +802,15 @@ PlayerSettings: ps4PronunciationSIGPath: ps4BackgroundImagePath: ps4StartupImagePath: + ps4StartupImagesFolder: + ps4IconImagesFolder: ps4SaveDataImagePath: ps4SdkOverride: ps4BGMPath: ps4ShareFilePath: ps4ShareOverlayImagePath: ps4PrivacyGuardImagePath: + ps4ExtraSceSysFile: ps4NPtitleDatPath: ps4RemotePlayKeyAssignment: -1 ps4RemotePlayKeyMappingDir: @@ -468,17 +823,20 @@ PlayerSettings: ps4DownloadDataSize: 0 ps4GarlicHeapSize: 2048 ps4ProGarlicHeapSize: 2560 + playerPrefsMaxSize: 32768 ps4Passcode: frAQBc8Wsa1xVPfvJcrgRYwTiizs2trQ - ps4UseDebugIl2cppLibs: 0 ps4pnSessions: 1 ps4pnPresence: 1 ps4pnFriends: 1 ps4pnGameCustomData: 1 playerPrefsSupport: 0 + enableApplicationExit: 0 + resetTempFolder: 1 restrictedAudioUsageRights: 0 ps4UseResolutionFallback: 0 ps4ReprojectionSupport: 0 ps4UseAudio3dBackend: 0 + ps4UseLowGarlicFragmentationMode: 1 ps4SocialScreenEnabled: 0 ps4ScriptOptimizationLevel: 3 ps4Audio3dVirtualSpeakerCount: 14 @@ -495,63 +853,21 @@ PlayerSettings: ps4disableAutoHideSplash: 0 ps4videoRecordingFeaturesUsed: 0 ps4contentSearchFeaturesUsed: 0 + ps4CompatibilityPS5: 0 + ps4AllowPS5Detection: 0 + ps4GPU800MHz: 1 ps4attribEyeToEyeDistanceSettingVR: 0 ps4IncludedModules: [] + ps4attribVROutputEnabled: 0 monoEnv: - psp2Splashimage: {fileID: 0} - psp2NPTrophyPackPath: - psp2NPSupportGBMorGJP: 0 - psp2NPAgeRating: 12 - psp2NPTitleDatPath: - psp2NPCommsID: - psp2NPCommunicationsID: - psp2NPCommsPassphrase: - psp2NPCommsSig: - psp2ParamSfxPath: - psp2ManualPath: - psp2LiveAreaGatePath: - psp2LiveAreaBackroundPath: - psp2LiveAreaPath: - psp2LiveAreaTrialPath: - psp2PatchChangeInfoPath: - psp2PatchOriginalPackage: - psp2PackagePassword: F69AzBlax3CF3EDNhm3soLBPh71Yexui - psp2KeystoneFile: - psp2MemoryExpansionMode: 0 - psp2DRMType: 0 - psp2StorageType: 0 - psp2MediaCapacity: 0 - psp2DLCConfigPath: - psp2ThumbnailPath: - psp2BackgroundPath: - psp2SoundPath: - psp2TrophyCommId: - psp2TrophyPackagePath: - psp2PackagedResourcesPath: - psp2SaveDataQuota: 10240 - psp2ParentalLevel: 1 - psp2ShortTitle: Not Set - psp2ContentID: IV0000-ABCD12345_00-0123456789ABCDEF - psp2Category: 0 - psp2MasterVersion: 01.00 - psp2AppVersion: 01.00 - psp2TVBootMode: 0 - psp2EnterButtonAssignment: 2 - psp2TVDisableEmu: 0 - psp2AllowTwitterDialog: 1 - psp2Upgradable: 0 - psp2HealthWarning: 0 - psp2UseLibLocation: 0 - psp2InfoBarOnStartup: 0 - psp2InfoBarColor: 0 - psp2UseDebugIl2cppLibs: 0 - psmSplashimage: {fileID: 0} splashScreenBackgroundSourceLandscape: {fileID: 0} splashScreenBackgroundSourcePortrait: {fileID: 0} + blurSplashScreenBackground: 1 spritePackerPolicy: webGLMemorySize: 256 webGLExceptionSupport: 1 webGLNameFilesAsHashes: 0 + webGLShowDiagnostics: 0 webGLDataCaching: 0 webGLDebugSymbols: 0 webGLEmscriptenArgs: @@ -559,19 +875,53 @@ PlayerSettings: webGLTemplate: APPLICATION:Default webGLAnalyzeBuildSize: 0 webGLUseEmbeddedResources: 0 - webGLUseWasm: 0 webGLCompressionFormat: 1 + webGLWasmArithmeticExceptions: 0 + webGLLinkerTarget: 1 + webGLThreadsSupport: 0 + webGLDecompressionFallback: 0 + webGLInitialMemorySize: 32 + webGLMaximumMemorySize: 2048 + webGLMemoryGrowthMode: 2 + webGLMemoryLinearGrowthStep: 16 + webGLMemoryGeometricGrowthStep: 0.2 + webGLMemoryGeometricGrowthCap: 96 + webGLPowerPreference: 2 scriptingDefineSymbols: - 7: UNITY + Android: UNITY + additionalCompilerArguments: {} platformArchitecture: {} scriptingBackend: Android: 0 - Metro: 2 Standalone: 0 WebGL: 1 WebPlayer: 0 + Windows Store Apps: 2 + il2cppCompilerConfiguration: {} + il2cppCodeGeneration: {} + managedStrippingLevel: + EmbeddedLinux: 1 + GameCoreScarlett: 1 + GameCoreXboxOne: 1 + Nintendo Switch: 1 + PS4: 1 + PS5: 1 + QNX: 1 + Stadia: 1 + VisionOS: 1 + WebGL: 1 + Windows Store Apps: 1 + XboxOne: 1 + iPhone: 1 + tvOS: 1 incrementalIl2cppBuild: {} + suppressCommonWarnings: 1 + allowUnsafeCode: 0 + useDeterministicCompilation: 1 additionalIl2CppArgs: + scriptingRuntimeVersion: 1 + gcIncremental: 1 + gcWBarrierValidation: 0 apiCompatibilityLevelPerPlatform: {} m_RenderingPath: 1 m_MobileRenderingPath: 1 @@ -585,47 +935,25 @@ PlayerSettings: metroApplicationDescription: LiteNetLibSampleUnity wsaImages: {} metroTileShortName: - metroCommandLineArgsFile: metroTileShowName: 0 metroMediumTileShowName: 0 metroLargeTileShowName: 0 metroWideTileShowName: 0 + metroSupportStreamingInstall: 0 + metroLastRequiredScene: 0 metroDefaultTileSize: 1 metroTileForegroundText: 1 metroTileBackgroundColor: {r: 0.13333334, g: 0.17254902, b: 0.21568628, a: 0} metroSplashScreenBackgroundColor: {r: 0.13333334, g: 0.17254902, b: 0.21568628, a: 1} metroSplashScreenUseBackgroundColor: 1 + syncCapabilities: 0 platformCapabilities: {} + metroTargetDeviceFamilies: {} metroFTAName: metroFTAFileTypes: [] metroProtocolName: - metroCompilationOverrides: 1 - tizenProductDescription: - tizenProductURL: - tizenSigningProfileName: - tizenGPSPermissions: 0 - tizenMicrophonePermissions: 0 - tizenDeploymentTarget: - tizenDeploymentTargetType: -1 - tizenMinOSVersion: 1 - n3dsUseExtSaveData: 0 - n3dsCompressStaticMem: 1 - n3dsExtSaveDataNumber: 0x12345 - n3dsStackSize: 131072 - n3dsTargetPlatform: 2 - n3dsRegion: 7 - n3dsMediaSize: 0 - n3dsLogoStyle: 3 - n3dsTitle: GameName - n3dsProductCode: - n3dsApplicationId: 0xFF3FF - stvDeviceAddress: - stvProductDescription: - stvProductAuthor: - stvProductAuthorEmail: - stvProductLink: - stvProductCategory: 0 + vcxProjDefaultLanguage: XboxOneProductId: XboxOneUpdateKey: XboxOneSandboxId: @@ -635,6 +963,7 @@ PlayerSettings: XboxOneGameOsOverridePath: XboxOnePackagingOverridePath: XboxOneAppManifestOverridePath: + XboxOneVersion: 1.0.0.0 XboxOnePackageEncryption: 0 XboxOnePackageUpdateGranularity: 2 XboxOneDescription: @@ -643,16 +972,16 @@ PlayerSettings: XboxOneCapability: [] XboxOneGameRating: {} XboxOneIsContentPackage: 0 + XboxOneEnhancedXboxCompatibilityMode: 0 XboxOneEnableGPUVariability: 0 XboxOneSockets: {} XboxOneSplashScreen: {fileID: 0} XboxOneAllowedProductIds: [] XboxOnePersistentLocalStorageSize: 0 - xboxOneScriptCompiler: 0 - vrEditorSettings: - daydream: - daydreamIconForeground: {fileID: 0} - daydreamIconBackground: {fileID: 0} + XboxOneXTitleMemory: 8 + XboxOneOverrideIdentityName: + XboxOneOverrideIdentityPublisher: + vrEditorSettings: {} cloudServicesEnabled: Analytics: 0 Build: 0 @@ -663,10 +992,33 @@ PlayerSettings: Purchasing: 0 UNet: 0 Unity_Ads: 0 - facebookSdkVersion: 7.9.1 - apiCompatibilityLevel: 2 + luminIcon: + m_Name: + m_ModelFolderPath: + m_PortalFolderPath: + luminCert: + m_CertPath: + m_SignPackage: 1 + luminIsChannelApp: 0 + luminVersion: + m_VersionCode: 1 + m_VersionName: + hmiPlayerDataPath: + hmiForceSRGBBlit: 1 + embeddedLinuxEnableGamepadInput: 1 + hmiLogStartupTiming: 0 + hmiCpuConfiguration: + apiCompatibilityLevel: 6 + activeInputHandler: 0 + windowsGamepadBackendHint: 0 cloudProjectId: + framebufferDepthMemorylessMode: 0 + qualitySettingsNames: [] projectName: organizationId: cloudEnabled: 0 - enableNewInputSystem: 0 + legacyClampBlendShapeWeights: 1 + hmiLoadingImage: {fileID: 0} + platformRequiresReadableAssets: 0 + virtualTexturingSupportEnabled: 0 + insecureHttpOption: 0 diff --git a/LiteNetLibSampleUnity/ProjectSettings/ProjectVersion.txt b/LiteNetLibSampleUnity/ProjectSettings/ProjectVersion.txt index d848e35e..8fb87e60 100644 --- a/LiteNetLibSampleUnity/ProjectSettings/ProjectVersion.txt +++ b/LiteNetLibSampleUnity/ProjectSettings/ProjectVersion.txt @@ -1 +1,2 @@ -m_EditorVersion: 2018.3.8f1 +m_EditorVersion: 2022.3.50f1 +m_EditorVersionWithRevision: 2022.3.50f1 (c3db7f8bf9b1) diff --git a/LiteNetLibSampleUnity/ProjectSettings/VFXManager.asset b/LiteNetLibSampleUnity/ProjectSettings/VFXManager.asset index 85b0821b..852348bc 100644 Binary files a/LiteNetLibSampleUnity/ProjectSettings/VFXManager.asset and b/LiteNetLibSampleUnity/ProjectSettings/VFXManager.asset differ diff --git a/LiteNetLibSampleUnity/ProjectSettings/VersionControlSettings.asset b/LiteNetLibSampleUnity/ProjectSettings/VersionControlSettings.asset new file mode 100644 index 00000000..dca28814 --- /dev/null +++ b/LiteNetLibSampleUnity/ProjectSettings/VersionControlSettings.asset @@ -0,0 +1,8 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!890905787 &1 +VersionControlSettings: + m_ObjectHideFlags: 0 + m_Mode: Visible Meta Files + m_CollabEditorSettings: + inProgressEnabled: 1 diff --git a/LiteNetLibSampleUnity/UserSettings/EditorUserSettings.asset b/LiteNetLibSampleUnity/UserSettings/EditorUserSettings.asset new file mode 100644 index 00000000..18e0c11a --- /dev/null +++ b/LiteNetLibSampleUnity/UserSettings/EditorUserSettings.asset @@ -0,0 +1,28 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!162 &1 +EditorUserSettings: + m_ObjectHideFlags: 0 + serializedVersion: 4 + m_ConfigSettings: + RecentlyUsedSceneGuid-0: + value: 0700075501055a5f5e5b5f2742275e4413164e792e2e25362f281b60bab5353b + flags: 0 + vcSharedLogLevel: + value: 0d5e400f0650 + flags: 0 + m_VCAutomaticAdd: 1 + m_VCDebugCom: 0 + m_VCDebugCmd: 0 + m_VCDebugOut: 0 + m_SemanticMergeMode: 2 + m_DesiredImportWorkerCount: 4 + m_StandbyImportWorkerCount: 2 + m_IdleImportWorkerShutdownDelay: 60000 + m_VCShowFailedCheckout: 1 + m_VCOverwriteFailedCheckoutAssets: 1 + m_VCProjectOverlayIcons: 1 + m_VCHierarchyOverlayIcons: 1 + m_VCOtherOverlayIcons: 1 + m_VCAllowAsyncUpdate: 1 + m_ArtifactGarbageCollection: 1 diff --git a/LiteNetLibSampleUnity/UserSettings/Layouts/CurrentMaximizeLayout.dwlt b/LiteNetLibSampleUnity/UserSettings/Layouts/CurrentMaximizeLayout.dwlt new file mode 100644 index 00000000..956ebeb9 --- /dev/null +++ b/LiteNetLibSampleUnity/UserSettings/Layouts/CurrentMaximizeLayout.dwlt @@ -0,0 +1,1680 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &1 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12010, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_Children: + - {fileID: 3} + - {fileID: 8} + - {fileID: 13} + m_Position: + serializedVersion: 2 + x: 0 + y: 30 + width: 2560 + height: 1299 + m_MinSize: {x: 300, y: 100} + m_MaxSize: {x: 24288, y: 16192} + vertical: 0 + controlID: 372 +--- !u!114 &2 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12015, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_MinSize: {x: 200, y: 200} + m_MaxSize: {x: 4000, y: 4000} + m_TitleContent: + m_Text: Game + m_Image: {fileID: -6423792434712278376, guid: 0000000000000000d000000000000000, + type: 0} + m_Tooltip: + m_Pos: + serializedVersion: 2 + x: 0 + y: 19 + width: 1492 + height: 1278 + m_SerializedDataModeController: + m_DataMode: 0 + m_PreferredDataMode: 0 + m_SupportedDataModes: + isAutomatic: 1 + m_ViewDataDictionary: {fileID: 0} + m_OverlayCanvas: + m_LastAppliedPresetName: Default + m_SaveData: [] + m_OverlaysVisible: 1 + m_SerializedViewNames: [] + m_SerializedViewValues: [] + m_PlayModeViewName: GameView + m_ShowGizmos: 0 + m_TargetDisplay: 0 + m_ClearColor: {r: 0, g: 0, b: 0, a: 0} + m_TargetSize: {x: 1492, y: 839} + m_TextureFilterMode: 0 + m_TextureHideFlags: 61 + m_RenderIMGUI: 1 + m_EnterPlayModeBehavior: 1 + m_UseMipMap: 0 + m_VSyncEnabled: 1 + m_Gizmos: 0 + m_Stats: 0 + m_SelectedSizes: 0100000000000000000000000e000000000000000000000000000000000000000000000000000000 + m_ZoomArea: + m_HRangeLocked: 0 + m_VRangeLocked: 0 + hZoomLockedByDefault: 0 + vZoomLockedByDefault: 0 + m_HBaseRangeMin: -746 + m_HBaseRangeMax: 746 + m_VBaseRangeMin: -419.5 + m_VBaseRangeMax: 419.5 + m_HAllowExceedBaseRangeMin: 1 + m_HAllowExceedBaseRangeMax: 1 + m_VAllowExceedBaseRangeMin: 1 + m_VAllowExceedBaseRangeMax: 1 + m_ScaleWithWindow: 0 + m_HSlider: 0 + m_VSlider: 0 + m_IgnoreScrollWheelUntilClicked: 0 + m_EnableMouseInput: 0 + m_EnableSliderZoomHorizontal: 0 + m_EnableSliderZoomVertical: 0 + m_UniformScale: 1 + m_UpDirection: 1 + m_DrawArea: + serializedVersion: 2 + x: 0 + y: 21 + width: 1492 + height: 1257 + m_Scale: {x: 1, y: 1} + m_Translation: {x: 746, y: 628.5} + m_MarginLeft: 0 + m_MarginRight: 0 + m_MarginTop: 0 + m_MarginBottom: 0 + m_LastShownAreaInsideMargins: + serializedVersion: 2 + x: -746 + y: -628.5 + width: 1492 + height: 1257 + m_MinimalGUI: 1 + m_defaultScale: 1 + m_LastWindowPixelSize: {x: 1492, y: 1278} + m_ClearInEditMode: 1 + m_NoCameraWarning: 1 + m_LowResolutionForAspectRatios: 01000001000000000000 + m_XRRenderMode: 0 + m_RenderTexture: {fileID: 0} +--- !u!114 &3 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 12010, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_Children: + - {fileID: 4} + - {fileID: 6} + m_Position: + serializedVersion: 2 + x: 0 + y: 0 + width: 1493 + height: 1299 + m_MinSize: {x: 100, y: 100} + m_MaxSize: {x: 8096, y: 16192} + vertical: 1 + controlID: 496 +--- !u!114 &4 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0} + m_Name: GameView + m_EditorClassIdentifier: + m_Children: [] + m_Position: + serializedVersion: 2 + x: 0 + y: 0 + width: 1493 + height: 866 + m_MinSize: {x: 201, y: 221} + m_MaxSize: {x: 4001, y: 4021} + m_ActualView: {fileID: 2} + m_Panes: + - {fileID: 5} + - {fileID: 2} + m_Selected: 1 + m_LastSelected: 0 +--- !u!114 &5 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12013, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_MinSize: {x: 200, y: 200} + m_MaxSize: {x: 4000, y: 4000} + m_TitleContent: + m_Text: Scene + m_Image: {fileID: 2593428753322112591, guid: 0000000000000000d000000000000000, + type: 0} + m_Tooltip: + m_Pos: + serializedVersion: 2 + x: 0 + y: 73 + width: 1492 + height: 845 + m_SerializedDataModeController: + m_DataMode: 0 + m_PreferredDataMode: 0 + m_SupportedDataModes: + isAutomatic: 1 + m_ViewDataDictionary: {fileID: 0} + m_OverlayCanvas: + m_LastAppliedPresetName: Default + m_SaveData: + - dockPosition: 0 + containerId: overlay-toolbar__top + floating: 0 + collapsed: 0 + displayed: 1 + snapOffset: {x: -166, y: -26} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 3 + id: Tool Settings + index: 0 + layout: 1 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 0 + containerId: overlay-toolbar__top + floating: 0 + collapsed: 0 + displayed: 1 + snapOffset: {x: -141, y: 149} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 1 + id: unity-grid-and-snap-toolbar + index: 1 + layout: 1 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-toolbar__top + floating: 0 + collapsed: 0 + displayed: 1 + snapOffset: {x: 0, y: 25} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: unity-scene-view-toolbar + index: 0 + layout: 1 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-toolbar__top + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 1 + id: unity-search-toolbar + index: 1 + layout: 1 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 0 + containerId: overlay-container--left + floating: 0 + collapsed: 0 + displayed: 1 + snapOffset: {x: 0, y: 25} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: unity-transform-toolbar + index: 0 + layout: 2 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 0 + containerId: overlay-container--left + floating: 0 + collapsed: 0 + displayed: 1 + snapOffset: {x: 0, y: 197} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: unity-component-tools + index: 1 + layout: 2 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 0 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 1 + snapOffset: {x: 67.5, y: -123} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 2 + id: Orientation + index: 0 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Light Settings + index: 2 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Camera + index: 1 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Cloth Constraints + index: 3 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Cloth Collisions + index: 4 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Navmesh Display + index: 4 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Agent Display + index: 5 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Obstacle Display + index: 6 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Occlusion Culling + index: 5 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Physics Debugger + index: 6 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Scene Visibility + index: 7 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 25} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Particles + index: 8 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Tilemap + index: 11 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Tilemap Palette Helper + index: 12 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 1 + snapOffset: {x: -216, y: -156} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 3 + id: AINavigationOverlay + index: 9 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 48, y: 48} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: APV Overlay + index: 8 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 48, y: 48} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/TrailRenderer + index: 10 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 1 + snapOffset: {x: 48, y: 10} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: UnityEditor.SceneViewCameraOverlay + index: 10 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 48, y: 48} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Open Tile Palette + index: 0 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 48, y: 48} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Tilemap Focus + index: 1 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + m_OverlaysVisible: 1 + m_WindowGUID: c9ee0d44a80756c48ba1fd3e952ef1b5 + m_Gizmos: 1 + m_OverrideSceneCullingMask: 6917529027641081856 + m_SceneIsLit: 1 + m_SceneLighting: 1 + m_2DMode: 0 + m_isRotationLocked: 0 + m_PlayAudio: 0 + m_AudioPlay: 0 + m_Position: + m_Target: {x: 0, y: 0, z: 0} + speed: 2 + m_Value: {x: 0, y: 0, z: 0} + m_RenderMode: 0 + m_CameraMode: + drawMode: 0 + name: Shaded + section: Shading Mode + m_ValidateTrueMetals: 0 + m_DoValidateTrueMetals: 0 + m_SceneViewState: + m_AlwaysRefresh: 0 + showFog: 1 + showSkybox: 1 + showFlares: 1 + showImageEffects: 1 + showParticleSystems: 1 + showVisualEffectGraphs: 1 + m_FxEnabled: 1 + m_Grid: + xGrid: + m_Fade: + m_Target: 0 + speed: 2 + m_Value: 0 + m_Color: {r: 0.5, g: 0.5, b: 0.5, a: 0.4} + m_Pivot: {x: 0, y: 0, z: 0} + m_Size: {x: 0, y: 0} + yGrid: + m_Fade: + m_Target: 1 + speed: 2 + m_Value: 1 + m_Color: {r: 0.5, g: 0.5, b: 0.5, a: 0.4} + m_Pivot: {x: 0, y: 0, z: 0} + m_Size: {x: 1, y: 1} + zGrid: + m_Fade: + m_Target: 0 + speed: 2 + m_Value: 0 + m_Color: {r: 0.5, g: 0.5, b: 0.5, a: 0.4} + m_Pivot: {x: 0, y: 0, z: 0} + m_Size: {x: 1, y: 1} + m_ShowGrid: 1 + m_GridAxis: 1 + m_gridOpacity: 0.5 + m_Rotation: + m_Target: {x: -0.08717229, y: 0.89959055, z: -0.21045254, w: -0.3726226} + speed: 2 + m_Value: {x: -0.08717229, y: 0.89959055, z: -0.21045254, w: -0.3726226} + m_Size: + m_Target: 10 + speed: 2 + m_Value: 10 + m_Ortho: + m_Target: 0 + speed: 2 + m_Value: 0 + m_CameraSettings: + m_Speed: 0.8856 + m_SpeedNormalized: 0.44 + m_SpeedMin: 0.01 + m_SpeedMax: 2 + m_EasingEnabled: 1 + m_EasingDuration: 0.4 + m_AccelerationEnabled: 1 + m_FieldOfViewHorizontalOrVertical: 60 + m_NearClip: 0.03 + m_FarClip: 10000 + m_DynamicClip: 1 + m_OcclusionCulling: 0 + m_LastSceneViewRotation: {x: -0.08717229, y: 0.89959055, z: -0.21045254, w: -0.3726226} + m_LastSceneViewOrtho: 0 + m_ReplacementShader: {fileID: 0} + m_ReplacementString: + m_SceneVisActive: 1 + m_LastLockedObject: {fileID: 0} + m_ViewIsLockedToObject: 0 +--- !u!114 &6 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0} + m_Name: ProfilerWindow + m_EditorClassIdentifier: + m_Children: [] + m_Position: + serializedVersion: 2 + x: 0 + y: 866 + width: 1493 + height: 433 + m_MinSize: {x: 901, y: 237} + m_MaxSize: {x: 4001, y: 4021} + m_ActualView: {fileID: 7} + m_Panes: + - {fileID: 7} + m_Selected: 0 + m_LastSelected: 0 +--- !u!114 &7 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 12070, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_MinSize: {x: 900, y: 216} + m_MaxSize: {x: 4000, y: 4000} + m_TitleContent: + m_Text: Profiler + m_Image: {fileID: -1089619856830078684, guid: 0000000000000000d000000000000000, + type: 0} + m_Tooltip: + m_Pos: + serializedVersion: 2 + x: 0 + y: 939 + width: 1492 + height: 412 + m_SerializedDataModeController: + m_DataMode: 0 + m_PreferredDataMode: 0 + m_SupportedDataModes: + isAutomatic: 1 + m_ViewDataDictionary: {fileID: 0} + m_OverlayCanvas: + m_LastAppliedPresetName: Default + m_SaveData: [] + m_OverlaysVisible: 1 + m_Recording: 1 + m_ActiveNativePlatformSupportModuleName: + m_AllModules: + - rid: 7267259948770525184 + - rid: 7267259948770525185 + - rid: 7267259948770525186 + - rid: 7267259948770525187 + - rid: 7267259948770525188 + - rid: 7267259948770525189 + - rid: 7267259948770525190 + - rid: 7267259948770525191 + - rid: 7267259948770525192 + - rid: 7267259948770525193 + - rid: 7267259948770525194 + - rid: 7267259948770525195 + - rid: 7267259948770525196 + - rid: 7267259948770525197 + m_CallstackRecordMode: 1 + m_ClearOnPlay: 0 + references: + version: 2 + RefIds: + - rid: 7267259948770525184 + type: {class: CPUProfilerModule, ns: UnityEditorInternal.Profiling, asm: UnityEditor.CoreModule} + data: + m_Identifier: UnityEditorInternal.Profiling.CPUProfilerModule, UnityEditor.CoreModule, + Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + m_PaneScroll: {x: 0, y: 0} + m_ViewType: 0 + updateViewLive: 0 + m_CurrentFrameIndex: -1 + m_HierarchyOverruledThreadFromSelection: 0 + m_ProfilerViewFilteringOptions: 1 + m_FrameDataHierarchyView: + m_Serialized: 1 + m_TreeViewState: + scrollPos: {x: 0, y: 0} + m_SelectedIDs: + m_LastClickedID: 0 + m_ExpandedIDs: + m_RenameOverlay: + m_UserAcceptedRename: 0 + m_Name: + m_OriginalName: + m_EditFieldRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 0 + height: 0 + m_UserData: 0 + m_IsWaitingForDelay: 0 + m_IsRenaming: 0 + m_OriginalEventType: 11 + m_IsRenamingFilename: 0 + m_ClientGUIView: {fileID: 0} + m_SearchString: + m_MultiColumnHeaderState: + m_Columns: + - width: 200 + sortedAscending: 1 + headerContent: + m_Text: Overview + m_Image: {fileID: 0} + m_Tooltip: + contextMenuText: + headerTextAlignment: 0 + sortingArrowAlignment: 2 + minWidth: 200 + maxWidth: 1000000 + autoResize: 1 + allowToggleVisibility: 0 + canSort: 1 + userData: 0 + - width: 80 + sortedAscending: 0 + headerContent: + m_Text: Total + m_Image: {fileID: 0} + m_Tooltip: + contextMenuText: + headerTextAlignment: 0 + sortingArrowAlignment: 2 + minWidth: 50 + maxWidth: 1000000 + autoResize: 0 + allowToggleVisibility: 1 + canSort: 1 + userData: 0 + - width: 80 + sortedAscending: 0 + headerContent: + m_Text: Self + m_Image: {fileID: 0} + m_Tooltip: + contextMenuText: + headerTextAlignment: 0 + sortingArrowAlignment: 2 + minWidth: 50 + maxWidth: 1000000 + autoResize: 0 + allowToggleVisibility: 1 + canSort: 1 + userData: 0 + - width: 80 + sortedAscending: 0 + headerContent: + m_Text: Calls + m_Image: {fileID: 0} + m_Tooltip: + contextMenuText: + headerTextAlignment: 0 + sortingArrowAlignment: 2 + minWidth: 50 + maxWidth: 1000000 + autoResize: 0 + allowToggleVisibility: 1 + canSort: 1 + userData: 0 + - width: 80 + sortedAscending: 0 + headerContent: + m_Text: GC Alloc + m_Image: {fileID: 0} + m_Tooltip: + contextMenuText: + headerTextAlignment: 0 + sortingArrowAlignment: 2 + minWidth: 50 + maxWidth: 1000000 + autoResize: 0 + allowToggleVisibility: 1 + canSort: 1 + userData: 0 + - width: 80 + sortedAscending: 0 + headerContent: + m_Text: Time ms + m_Image: {fileID: 0} + m_Tooltip: + contextMenuText: + headerTextAlignment: 0 + sortingArrowAlignment: 2 + minWidth: 50 + maxWidth: 1000000 + autoResize: 0 + allowToggleVisibility: 1 + canSort: 1 + userData: 0 + - width: 80 + sortedAscending: 0 + headerContent: + m_Text: Self ms + m_Image: {fileID: 0} + m_Tooltip: + contextMenuText: + headerTextAlignment: 0 + sortingArrowAlignment: 2 + minWidth: 50 + maxWidth: 1000000 + autoResize: 0 + allowToggleVisibility: 1 + canSort: 1 + userData: 0 + - width: 25 + sortedAscending: 0 + headerContent: + m_Text: + m_Image: {fileID: -5161429177145976760, guid: 0000000000000000d000000000000000, + type: 0} + m_Tooltip: Warnings + contextMenuText: + headerTextAlignment: 0 + sortingArrowAlignment: 2 + minWidth: 25 + maxWidth: 25 + autoResize: 0 + allowToggleVisibility: 1 + canSort: 1 + userData: 0 + m_VisibleColumns: 0000000001000000020000000300000004000000050000000600000007000000 + m_SortedColumns: 05000000 + m_ThreadIndexInThreadNames: 0 + m_DetailedViewType: 0 + m_DetailedViewSpliterState: + ID: 0 + splitterInitialOffset: 0 + currentActiveSplitter: -1 + realSizes: + - 0 + - 0 + relativeSizes: + - 0.7 + - 0.3 + minSizes: + - 450 + - 50 + maxSizes: + - 0 + - 0 + lastTotalSize: 0 + splitSize: 6 + xOffset: 0 + m_Version: 1 + oldRealSizes: + oldMinSizes: + oldMaxSizes: + oldSplitSize: 0 + m_DetailedObjectsView: + m_SelectedID: -1 + m_TreeViewState: + scrollPos: {x: 0, y: 0} + m_SelectedIDs: + m_LastClickedID: 0 + m_ExpandedIDs: + m_RenameOverlay: + m_UserAcceptedRename: 0 + m_Name: + m_OriginalName: + m_EditFieldRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 0 + height: 0 + m_UserData: 0 + m_IsWaitingForDelay: 0 + m_IsRenaming: 0 + m_OriginalEventType: 11 + m_IsRenamingFilename: 0 + m_ClientGUIView: {fileID: 0} + m_SearchString: + m_MultiColumnHeaderState: + m_Columns: [] + m_VisibleColumns: + m_SortedColumns: + m_VertSplit: + ID: 0 + splitterInitialOffset: 0 + currentActiveSplitter: 0 + realSizes: [] + relativeSizes: [] + minSizes: [] + maxSizes: [] + lastTotalSize: 0 + splitSize: 0 + xOffset: 0 + m_Version: 1 + oldRealSizes: + oldMinSizes: + oldMaxSizes: + oldSplitSize: 0 + m_DetailedCallsView: + m_SelectedID: -1 + m_VertSplit: + ID: 0 + splitterInitialOffset: 0 + currentActiveSplitter: 0 + realSizes: [] + relativeSizes: [] + minSizes: [] + maxSizes: [] + lastTotalSize: 0 + splitSize: 0 + xOffset: 0 + m_Version: 1 + oldRealSizes: + oldMinSizes: + oldMaxSizes: + oldSplitSize: 0 + m_CalleesTreeView: + m_ViewState: + scrollPos: {x: 0, y: 0} + m_SelectedIDs: + m_LastClickedID: 0 + m_ExpandedIDs: + m_RenameOverlay: + m_UserAcceptedRename: 0 + m_Name: + m_OriginalName: + m_EditFieldRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 0 + height: 0 + m_UserData: 0 + m_IsWaitingForDelay: 0 + m_IsRenaming: 0 + m_OriginalEventType: 11 + m_IsRenamingFilename: 0 + m_ClientGUIView: {fileID: 0} + m_SearchString: + m_ViewHeaderState: + m_Columns: [] + m_VisibleColumns: + m_SortedColumns: + m_CallersTreeView: + m_ViewState: + scrollPos: {x: 0, y: 0} + m_SelectedIDs: + m_LastClickedID: 0 + m_ExpandedIDs: + m_RenameOverlay: + m_UserAcceptedRename: 0 + m_Name: + m_OriginalName: + m_EditFieldRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 0 + height: 0 + m_UserData: 0 + m_IsWaitingForDelay: 0 + m_IsRenaming: 0 + m_OriginalEventType: 11 + m_IsRenamingFilename: 0 + m_ClientGUIView: {fileID: 0} + m_SearchString: + m_ViewHeaderState: + m_Columns: [] + m_VisibleColumns: + m_SortedColumns: + m_FullThreadName: Main Thread + m_ThreadName: Main Thread + k__BackingField: 0 + k__BackingField: -1 + m_GroupName: + - rid: 7267259948770525185 + type: {class: GPUProfilerModule, ns: UnityEditorInternal.Profiling, asm: UnityEditor.CoreModule} + data: + m_Identifier: UnityEditorInternal.Profiling.GPUProfilerModule, UnityEditor.CoreModule, + Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + m_PaneScroll: {x: 0, y: 0} + m_ViewType: 0 + updateViewLive: 0 + m_CurrentFrameIndex: -1 + m_HierarchyOverruledThreadFromSelection: 0 + m_ProfilerViewFilteringOptions: 1 + m_FrameDataHierarchyView: + m_Serialized: 0 + m_TreeViewState: + scrollPos: {x: 0, y: 0} + m_SelectedIDs: + m_LastClickedID: 0 + m_ExpandedIDs: + m_RenameOverlay: + m_UserAcceptedRename: 0 + m_Name: + m_OriginalName: + m_EditFieldRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 0 + height: 0 + m_UserData: 0 + m_IsWaitingForDelay: 0 + m_IsRenaming: 0 + m_OriginalEventType: 11 + m_IsRenamingFilename: 0 + m_ClientGUIView: {fileID: 0} + m_SearchString: + m_MultiColumnHeaderState: + m_Columns: [] + m_VisibleColumns: + m_SortedColumns: + m_ThreadIndexInThreadNames: 0 + m_DetailedViewType: 0 + m_DetailedViewSpliterState: + ID: 0 + splitterInitialOffset: 0 + currentActiveSplitter: 0 + realSizes: [] + relativeSizes: [] + minSizes: [] + maxSizes: [] + lastTotalSize: 0 + splitSize: 0 + xOffset: 0 + m_Version: 1 + oldRealSizes: + oldMinSizes: + oldMaxSizes: + oldSplitSize: 0 + m_DetailedObjectsView: + m_SelectedID: 0 + m_TreeViewState: + scrollPos: {x: 0, y: 0} + m_SelectedIDs: + m_LastClickedID: 0 + m_ExpandedIDs: + m_RenameOverlay: + m_UserAcceptedRename: 0 + m_Name: + m_OriginalName: + m_EditFieldRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 0 + height: 0 + m_UserData: 0 + m_IsWaitingForDelay: 0 + m_IsRenaming: 0 + m_OriginalEventType: 11 + m_IsRenamingFilename: 0 + m_ClientGUIView: {fileID: 0} + m_SearchString: + m_MultiColumnHeaderState: + m_Columns: [] + m_VisibleColumns: + m_SortedColumns: + m_VertSplit: + ID: 0 + splitterInitialOffset: 0 + currentActiveSplitter: 0 + realSizes: [] + relativeSizes: [] + minSizes: [] + maxSizes: [] + lastTotalSize: 0 + splitSize: 0 + xOffset: 0 + m_Version: 1 + oldRealSizes: + oldMinSizes: + oldMaxSizes: + oldSplitSize: 0 + m_DetailedCallsView: + m_SelectedID: 0 + m_VertSplit: + ID: 0 + splitterInitialOffset: 0 + currentActiveSplitter: 0 + realSizes: [] + relativeSizes: [] + minSizes: [] + maxSizes: [] + lastTotalSize: 0 + splitSize: 0 + xOffset: 0 + m_Version: 1 + oldRealSizes: + oldMinSizes: + oldMaxSizes: + oldSplitSize: 0 + m_CalleesTreeView: + m_ViewState: + scrollPos: {x: 0, y: 0} + m_SelectedIDs: + m_LastClickedID: 0 + m_ExpandedIDs: + m_RenameOverlay: + m_UserAcceptedRename: 0 + m_Name: + m_OriginalName: + m_EditFieldRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 0 + height: 0 + m_UserData: 0 + m_IsWaitingForDelay: 0 + m_IsRenaming: 0 + m_OriginalEventType: 11 + m_IsRenamingFilename: 0 + m_ClientGUIView: {fileID: 0} + m_SearchString: + m_ViewHeaderState: + m_Columns: [] + m_VisibleColumns: + m_SortedColumns: + m_CallersTreeView: + m_ViewState: + scrollPos: {x: 0, y: 0} + m_SelectedIDs: + m_LastClickedID: 0 + m_ExpandedIDs: + m_RenameOverlay: + m_UserAcceptedRename: 0 + m_Name: + m_OriginalName: + m_EditFieldRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 0 + height: 0 + m_UserData: 0 + m_IsWaitingForDelay: 0 + m_IsRenaming: 0 + m_OriginalEventType: 11 + m_IsRenamingFilename: 0 + m_ClientGUIView: {fileID: 0} + m_SearchString: + m_ViewHeaderState: + m_Columns: [] + m_VisibleColumns: + m_SortedColumns: + m_FullThreadName: Main Thread + m_ThreadName: Main Thread + k__BackingField: 0 + k__BackingField: -1 + m_GroupName: + - rid: 7267259948770525186 + type: {class: RenderingProfilerModule, ns: UnityEditorInternal.Profiling, asm: UnityEditor.CoreModule} + data: + m_Identifier: UnityEditorInternal.Profiling.RenderingProfilerModule, UnityEditor.CoreModule, + Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + m_PaneScroll: {x: 0, y: 0} + - rid: 7267259948770525187 + type: {class: MemoryProfilerModule, ns: UnityEditorInternal.Profiling, asm: UnityEditor.CoreModule} + data: + m_Identifier: UnityEditorInternal.Profiling.MemoryProfilerModule, UnityEditor.CoreModule, + Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + m_PaneScroll: {x: 0, y: 0} + m_ViewSplit: + ID: 0 + splitterInitialOffset: 0 + currentActiveSplitter: -1 + realSizes: + - 0 + - 0 + relativeSizes: + - 0.7 + - 0.3 + minSizes: + - 450 + - 50 + maxSizes: + - 0 + - 0 + lastTotalSize: 0 + splitSize: 6 + xOffset: 0 + m_Version: 1 + oldRealSizes: + oldMinSizes: + oldMaxSizes: + oldSplitSize: 0 + - rid: 7267259948770525188 + type: {class: AudioProfilerModule, ns: UnityEditorInternal.Profiling, asm: UnityEditor.CoreModule} + data: + m_Identifier: UnityEditorInternal.Profiling.AudioProfilerModule, UnityEditor.CoreModule, + Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + m_PaneScroll: {x: 0, y: 0} + m_ShowInactiveDSPChains: 0 + m_HighlightAudibleDSPChains: 1 + m_DSPGraphZoomFactor: 1 + m_DSPGraphHorizontalLayout: 0 + - rid: 7267259948770525189 + type: {class: VideoProfilerModule, ns: UnityEditorInternal.Profiling, asm: UnityEditor.CoreModule} + data: + m_Identifier: UnityEditorInternal.Profiling.VideoProfilerModule, UnityEditor.CoreModule, + Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + m_PaneScroll: {x: 0, y: 0} + - rid: 7267259948770525190 + type: {class: PhysicsProfilerModule, ns: UnityEditorInternal.Profiling, asm: UnityEditor.CoreModule} + data: + m_Identifier: UnityEditorInternal.Profiling.PhysicsProfilerModule, UnityEditor.CoreModule, + Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + m_PaneScroll: {x: 0, y: 0} + - rid: 7267259948770525191 + type: {class: Physics2DProfilerModule, ns: UnityEditorInternal.Profiling, asm: UnityEditor.CoreModule} + data: + m_Identifier: UnityEditorInternal.Profiling.Physics2DProfilerModule, UnityEditor.CoreModule, + Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + m_PaneScroll: {x: 0, y: 0} + - rid: 7267259948770525192 + type: {class: UIProfilerModule, ns: UnityEditorInternal.Profiling, asm: UnityEditor.CoreModule} + data: + m_Identifier: UnityEditorInternal.Profiling.UIProfilerModule, UnityEditor.CoreModule, + Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + m_PaneScroll: {x: 0, y: 0} + - rid: 7267259948770525193 + type: {class: UIDetailsProfilerModule, ns: UnityEditorInternal.Profiling, asm: UnityEditor.CoreModule} + data: + m_Identifier: UnityEditorInternal.Profiling.UIDetailsProfilerModule, UnityEditor.CoreModule, + Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + m_PaneScroll: {x: 0, y: 0} + - rid: 7267259948770525194 + type: {class: GlobalIlluminationProfilerModule, ns: UnityEditorInternal.Profiling, + asm: UnityEditor.CoreModule} + data: + m_Identifier: UnityEditorInternal.Profiling.GlobalIlluminationProfilerModule, + UnityEditor.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + m_PaneScroll: {x: 0, y: 0} + - rid: 7267259948770525195 + type: {class: VirtualTexturingProfilerModule, ns: UnityEditorInternal.Profiling, + asm: UnityEditor.CoreModule} + data: + m_Identifier: UnityEditorInternal.Profiling.VirtualTexturingProfilerModule, + UnityEditor.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + m_PaneScroll: {x: 0, y: 0} + m_VTProfilerView: + rid: 7267259948770525198 + - rid: 7267259948770525196 + type: {class: FileIOProfilerModule, ns: UnityEditorInternal.Profiling, asm: UnityEditor.CoreModule} + data: + m_Identifier: UnityEditorInternal.Profiling.FileIOProfilerModule, UnityEditor.CoreModule, + Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + m_PaneScroll: {x: 0, y: 0} + - rid: 7267259948770525197 + type: {class: AssetLoadingProfilerModule, ns: UnityEditorInternal.Profiling, + asm: UnityEditor.CoreModule} + data: + m_Identifier: UnityEditorInternal.Profiling.AssetLoadingProfilerModule, UnityEditor.CoreModule, + Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + m_PaneScroll: {x: 0, y: 0} + - rid: 7267259948770525198 + type: {class: VirtualTexturingProfilerView, ns: UnityEditor, asm: UnityEditor.CoreModule} + data: + m_SortAscending: 0 + m_SortedColumn: -1 +--- !u!114 &8 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12010, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_Children: + - {fileID: 9} + - {fileID: 11} + m_Position: + serializedVersion: 2 + x: 1493 + y: 0 + width: 376 + height: 1299 + m_MinSize: {x: 100, y: 100} + m_MaxSize: {x: 8096, y: 16192} + vertical: 1 + controlID: 338 +--- !u!114 &9 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0} + m_Name: SceneHierarchyWindow + m_EditorClassIdentifier: + m_Children: [] + m_Position: + serializedVersion: 2 + x: 0 + y: 0 + width: 376 + height: 624 + m_MinSize: {x: 202, y: 221} + m_MaxSize: {x: 4002, y: 4021} + m_ActualView: {fileID: 10} + m_Panes: + - {fileID: 10} + m_Selected: 0 + m_LastSelected: 0 +--- !u!114 &10 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12061, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_MinSize: {x: 200, y: 200} + m_MaxSize: {x: 4000, y: 4000} + m_TitleContent: + m_Text: Hierarchy + m_Image: {fileID: 7966133145522015247, guid: 0000000000000000d000000000000000, + type: 0} + m_Tooltip: + m_Pos: + serializedVersion: 2 + x: 1493 + y: 73 + width: 374 + height: 603 + m_SerializedDataModeController: + m_DataMode: 0 + m_PreferredDataMode: 0 + m_SupportedDataModes: + isAutomatic: 1 + m_ViewDataDictionary: {fileID: 0} + m_OverlayCanvas: + m_LastAppliedPresetName: Default + m_SaveData: [] + m_OverlaysVisible: 1 + m_SceneHierarchy: + m_TreeViewState: + scrollPos: {x: 0, y: 0} + m_SelectedIDs: + m_LastClickedID: 0 + m_ExpandedIDs: 1638ffff1a3cffff + m_RenameOverlay: + m_UserAcceptedRename: 0 + m_Name: + m_OriginalName: + m_EditFieldRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 0 + height: 0 + m_UserData: 0 + m_IsWaitingForDelay: 0 + m_IsRenaming: 0 + m_OriginalEventType: 11 + m_IsRenamingFilename: 0 + m_ClientGUIView: {fileID: 9} + m_SearchString: + m_ExpandedScenes: [] + m_CurrenRootInstanceID: 0 + m_LockTracker: + m_IsLocked: 0 + m_CurrentSortingName: TransformSorting + m_WindowGUID: 5744dfe58afd7b349b2bf692722dc4ff +--- !u!114 &11 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0} + m_Name: ProjectBrowser + m_EditorClassIdentifier: + m_Children: [] + m_Position: + serializedVersion: 2 + x: 0 + y: 624 + width: 376 + height: 675 + m_MinSize: {x: 232, y: 271} + m_MaxSize: {x: 10002, y: 10021} + m_ActualView: {fileID: 12} + m_Panes: + - {fileID: 12} + m_Selected: 0 + m_LastSelected: 0 +--- !u!114 &12 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12014, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_MinSize: {x: 230, y: 250} + m_MaxSize: {x: 10000, y: 10000} + m_TitleContent: + m_Text: Project + m_Image: {fileID: -5467254957812901981, guid: 0000000000000000d000000000000000, + type: 0} + m_Tooltip: + m_Pos: + serializedVersion: 2 + x: 1493 + y: 697 + width: 374 + height: 654 + m_SerializedDataModeController: + m_DataMode: 0 + m_PreferredDataMode: 0 + m_SupportedDataModes: + isAutomatic: 1 + m_ViewDataDictionary: {fileID: 0} + m_OverlayCanvas: + m_LastAppliedPresetName: Default + m_SaveData: [] + m_OverlaysVisible: 1 + m_SearchFilter: + m_NameFilter: + m_ClassNames: [] + m_AssetLabels: [] + m_AssetBundleNames: [] + m_ReferencingInstanceIDs: + m_SceneHandles: + m_ShowAllHits: 0 + m_SkipHidden: 0 + m_SearchArea: 1 + m_Folders: + - Assets + m_Globs: [] + m_OriginalText: + m_ImportLogFlags: 0 + m_FilterByTypeIntersection: 0 + m_ViewMode: 0 + m_StartGridSize: 16 + m_LastFolders: [] + m_LastFoldersGridSize: -1 + m_LastProjectPath: E:\Projects\LiteNetLib\LiteNetLibSampleUnity + m_LockTracker: + m_IsLocked: 0 + m_FolderTreeState: + scrollPos: {x: 0, y: 0} + m_SelectedIDs: 0e800000 + m_LastClickedID: 32782 + m_ExpandedIDs: ffffffffd0400000 + m_RenameOverlay: + m_UserAcceptedRename: 0 + m_Name: + m_OriginalName: + m_EditFieldRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 0 + height: 0 + m_UserData: 0 + m_IsWaitingForDelay: 0 + m_IsRenaming: 0 + m_OriginalEventType: 11 + m_IsRenamingFilename: 1 + m_ClientGUIView: {fileID: 0} + m_SearchString: + m_CreateAssetUtility: + m_EndAction: {fileID: 0} + m_InstanceID: 0 + m_Path: + m_Icon: {fileID: 0} + m_ResourceFile: + m_AssetTreeState: + scrollPos: {x: 0, y: 0} + m_SelectedIDs: 887a0000 + m_LastClickedID: 31368 + m_ExpandedIDs: ffffffffd0400000 + m_RenameOverlay: + m_UserAcceptedRename: 0 + m_Name: + m_OriginalName: + m_EditFieldRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 0 + height: 0 + m_UserData: 0 + m_IsWaitingForDelay: 0 + m_IsRenaming: 0 + m_OriginalEventType: 11 + m_IsRenamingFilename: 1 + m_ClientGUIView: {fileID: 11} + m_SearchString: + m_CreateAssetUtility: + m_EndAction: {fileID: 0} + m_InstanceID: 0 + m_Path: + m_Icon: {fileID: 0} + m_ResourceFile: + m_ListAreaState: + m_SelectedInstanceIDs: 887a0000 + m_LastClickedInstanceID: 31368 + m_HadKeyboardFocusLastEvent: 0 + m_ExpandedInstanceIDs: c6230000d0640000967900008e6d00007674000022740000a66f000000000000687100009c230000f4b70000288000007a5f00008e7d0000ecbc0000346d0000f09c0000c297000026850000f06c0000de7d0000 + m_RenameOverlay: + m_UserAcceptedRename: 0 + m_Name: + m_OriginalName: + m_EditFieldRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 0 + height: 0 + m_UserData: 0 + m_IsWaitingForDelay: 0 + m_IsRenaming: 0 + m_OriginalEventType: 11 + m_IsRenamingFilename: 1 + m_ClientGUIView: {fileID: 11} + m_CreateAssetUtility: + m_EndAction: {fileID: 0} + m_InstanceID: 0 + m_Path: + m_Icon: {fileID: 0} + m_ResourceFile: + m_NewAssetIndexInList: -1 + m_ScrollPosition: {x: 0, y: 0} + m_GridSize: 16 + m_SkipHiddenPackages: 0 + m_DirectoriesAreaWidth: 178 +--- !u!114 &13 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0} + m_Name: InspectorWindow + m_EditorClassIdentifier: + m_Children: [] + m_Position: + serializedVersion: 2 + x: 1869 + y: 0 + width: 691 + height: 1299 + m_MinSize: {x: 275, y: 50} + m_MaxSize: {x: 4000, y: 4000} + m_ActualView: {fileID: 14} + m_Panes: + - {fileID: 14} + m_Selected: 0 + m_LastSelected: 0 +--- !u!114 &14 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12019, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_MinSize: {x: 275, y: 50} + m_MaxSize: {x: 4000, y: 4000} + m_TitleContent: + m_Text: Inspector + m_Image: {fileID: -2667387946076563598, guid: 0000000000000000d000000000000000, + type: 0} + m_Tooltip: + m_Pos: + serializedVersion: 2 + x: 1869 + y: 73 + width: 690 + height: 1278 + m_SerializedDataModeController: + m_DataMode: 0 + m_PreferredDataMode: 0 + m_SupportedDataModes: + isAutomatic: 1 + m_ViewDataDictionary: {fileID: 0} + m_OverlayCanvas: + m_LastAppliedPresetName: Default + m_SaveData: [] + m_OverlaysVisible: 1 + m_ObjectsLockedBeforeSerialization: [] + m_InstanceIDsLockedBeforeSerialization: + m_PreviewResizer: + m_CachedPref: 526 + m_ControlHash: -371814159 + m_PrefName: Preview_InspectorPreview + m_LastInspectedObjectInstanceID: 31368 + m_LastVerticalScrollValue: 0 + m_GlobalObjectId: + m_InspectorMode: 0 + m_LockTracker: + m_IsLocked: 0 + m_PreviewWindow: {fileID: 0} diff --git a/LiteNetLibSampleUnity/UserSettings/Layouts/default-2022.dwlt b/LiteNetLibSampleUnity/UserSettings/Layouts/default-2022.dwlt new file mode 100644 index 00000000..bea9ed68 --- /dev/null +++ b/LiteNetLibSampleUnity/UserSettings/Layouts/default-2022.dwlt @@ -0,0 +1,2095 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &1 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12004, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_PixelRect: + serializedVersion: 2 + x: 0 + y: 43 + width: 2560 + height: 1349 + m_ShowMode: 4 + m_Title: Profiler + m_RootView: {fileID: 2} + m_MinSize: {x: 875, y: 421} + m_MaxSize: {x: 10000, y: 10000} + m_Maximized: 1 +--- !u!114 &2 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12008, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_Children: + - {fileID: 3} + - {fileID: 5} + - {fileID: 4} + m_Position: + serializedVersion: 2 + x: 0 + y: 0 + width: 2560 + height: 1349 + m_MinSize: {x: 875, y: 300} + m_MaxSize: {x: 10000, y: 10000} + m_UseTopView: 1 + m_TopViewHeight: 30 + m_UseBottomView: 1 + m_BottomViewHeight: 20 +--- !u!114 &3 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12011, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_Children: [] + m_Position: + serializedVersion: 2 + x: 0 + y: 0 + width: 2560 + height: 30 + m_MinSize: {x: 0, y: 0} + m_MaxSize: {x: 0, y: 0} + m_LastLoadedLayoutName: +--- !u!114 &4 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12042, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_Children: [] + m_Position: + serializedVersion: 2 + x: 0 + y: 1329 + width: 2560 + height: 20 + m_MinSize: {x: 0, y: 0} + m_MaxSize: {x: 0, y: 0} +--- !u!114 &5 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12010, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_Children: + - {fileID: 6} + - {fileID: 9} + - {fileID: 12} + m_Position: + serializedVersion: 2 + x: 0 + y: 30 + width: 2560 + height: 1299 + m_MinSize: {x: 300, y: 100} + m_MaxSize: {x: 24288, y: 16192} + vertical: 0 + controlID: 684 +--- !u!114 &6 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 12010, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_Children: + - {fileID: 7} + - {fileID: 8} + m_Position: + serializedVersion: 2 + x: 0 + y: 0 + width: 1493 + height: 1299 + m_MinSize: {x: 100, y: 100} + m_MaxSize: {x: 8096, y: 16192} + vertical: 1 + controlID: 690 +--- !u!114 &7 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0} + m_Name: GameView + m_EditorClassIdentifier: + m_Children: [] + m_Position: + serializedVersion: 2 + x: 0 + y: 0 + width: 1493 + height: 733 + m_MinSize: {x: 201, y: 221} + m_MaxSize: {x: 4001, y: 4021} + m_ActualView: {fileID: 13} + m_Panes: + - {fileID: 14} + - {fileID: 13} + m_Selected: 1 + m_LastSelected: 0 +--- !u!114 &8 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0} + m_Name: ProfilerWindow + m_EditorClassIdentifier: + m_Children: [] + m_Position: + serializedVersion: 2 + x: 0 + y: 733 + width: 1493 + height: 566 + m_MinSize: {x: 901, y: 237} + m_MaxSize: {x: 4001, y: 4021} + m_ActualView: {fileID: 15} + m_Panes: + - {fileID: 15} + m_Selected: 0 + m_LastSelected: 0 +--- !u!114 &9 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12010, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_Children: + - {fileID: 10} + - {fileID: 11} + m_Position: + serializedVersion: 2 + x: 1493 + y: 0 + width: 376 + height: 1299 + m_MinSize: {x: 100, y: 100} + m_MaxSize: {x: 8096, y: 16192} + vertical: 1 + controlID: 635 +--- !u!114 &10 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0} + m_Name: SceneHierarchyWindow + m_EditorClassIdentifier: + m_Children: [] + m_Position: + serializedVersion: 2 + x: 0 + y: 0 + width: 376 + height: 624 + m_MinSize: {x: 202, y: 221} + m_MaxSize: {x: 4002, y: 4021} + m_ActualView: {fileID: 16} + m_Panes: + - {fileID: 16} + m_Selected: 0 + m_LastSelected: 0 +--- !u!114 &11 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0} + m_Name: ProjectBrowser + m_EditorClassIdentifier: + m_Children: [] + m_Position: + serializedVersion: 2 + x: 0 + y: 624 + width: 376 + height: 675 + m_MinSize: {x: 232, y: 271} + m_MaxSize: {x: 10002, y: 10021} + m_ActualView: {fileID: 17} + m_Panes: + - {fileID: 17} + m_Selected: 0 + m_LastSelected: 0 +--- !u!114 &12 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0} + m_Name: InspectorWindow + m_EditorClassIdentifier: + m_Children: [] + m_Position: + serializedVersion: 2 + x: 1869 + y: 0 + width: 691 + height: 1299 + m_MinSize: {x: 276, y: 71} + m_MaxSize: {x: 4001, y: 4021} + m_ActualView: {fileID: 18} + m_Panes: + - {fileID: 18} + m_Selected: 0 + m_LastSelected: 0 +--- !u!114 &13 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12015, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_MinSize: {x: 200, y: 200} + m_MaxSize: {x: 4000, y: 4000} + m_TitleContent: + m_Text: Game + m_Image: {fileID: -6423792434712278376, guid: 0000000000000000d000000000000000, + type: 0} + m_Tooltip: + m_Pos: + serializedVersion: 2 + x: 0 + y: 73 + width: 1492 + height: 712 + m_SerializedDataModeController: + m_DataMode: 0 + m_PreferredDataMode: 0 + m_SupportedDataModes: + isAutomatic: 1 + m_ViewDataDictionary: {fileID: 0} + m_OverlayCanvas: + m_LastAppliedPresetName: Default + m_SaveData: [] + m_OverlaysVisible: 1 + m_SerializedViewNames: [] + m_SerializedViewValues: [] + m_PlayModeViewName: GameView + m_ShowGizmos: 0 + m_TargetDisplay: 0 + m_ClearColor: {r: 0, g: 0, b: 0, a: 0} + m_TargetSize: {x: 1228, y: 691} + m_TextureFilterMode: 0 + m_TextureHideFlags: 61 + m_RenderIMGUI: 1 + m_EnterPlayModeBehavior: 0 + m_UseMipMap: 0 + m_VSyncEnabled: 1 + m_Gizmos: 0 + m_Stats: 0 + m_SelectedSizes: 0100000000000000000000000e000000000000000000000000000000000000000000000000000000 + m_ZoomArea: + m_HRangeLocked: 0 + m_VRangeLocked: 0 + hZoomLockedByDefault: 0 + vZoomLockedByDefault: 0 + m_HBaseRangeMin: -614 + m_HBaseRangeMax: 614 + m_VBaseRangeMin: -345.5 + m_VBaseRangeMax: 345.5 + m_HAllowExceedBaseRangeMin: 1 + m_HAllowExceedBaseRangeMax: 1 + m_VAllowExceedBaseRangeMin: 1 + m_VAllowExceedBaseRangeMax: 1 + m_ScaleWithWindow: 0 + m_HSlider: 0 + m_VSlider: 0 + m_IgnoreScrollWheelUntilClicked: 0 + m_EnableMouseInput: 1 + m_EnableSliderZoomHorizontal: 0 + m_EnableSliderZoomVertical: 0 + m_UniformScale: 1 + m_UpDirection: 1 + m_DrawArea: + serializedVersion: 2 + x: 0 + y: 21 + width: 1492 + height: 691 + m_Scale: {x: 1, y: 1} + m_Translation: {x: 746, y: 345.5} + m_MarginLeft: 0 + m_MarginRight: 0 + m_MarginTop: 0 + m_MarginBottom: 0 + m_LastShownAreaInsideMargins: + serializedVersion: 2 + x: -746 + y: -345.5 + width: 1492 + height: 691 + m_MinimalGUI: 1 + m_defaultScale: 1 + m_LastWindowPixelSize: {x: 1492, y: 712} + m_ClearInEditMode: 1 + m_NoCameraWarning: 1 + m_LowResolutionForAspectRatios: 01000001000000000000 + m_XRRenderMode: 0 + m_RenderTexture: {fileID: 0} +--- !u!114 &14 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12013, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_MinSize: {x: 200, y: 200} + m_MaxSize: {x: 4000, y: 4000} + m_TitleContent: + m_Text: Scene + m_Image: {fileID: 2593428753322112591, guid: 0000000000000000d000000000000000, + type: 0} + m_Tooltip: + m_Pos: + serializedVersion: 2 + x: 0 + y: 73 + width: 1492 + height: 845 + m_SerializedDataModeController: + m_DataMode: 0 + m_PreferredDataMode: 0 + m_SupportedDataModes: + isAutomatic: 1 + m_ViewDataDictionary: {fileID: 0} + m_OverlayCanvas: + m_LastAppliedPresetName: Default + m_SaveData: + - dockPosition: 0 + containerId: overlay-toolbar__top + floating: 0 + collapsed: 0 + displayed: 1 + snapOffset: {x: -166, y: -26} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 3 + id: Tool Settings + index: 0 + layout: 1 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 0 + containerId: overlay-toolbar__top + floating: 0 + collapsed: 0 + displayed: 1 + snapOffset: {x: -141, y: 149} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 1 + id: unity-grid-and-snap-toolbar + index: 1 + layout: 1 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-toolbar__top + floating: 0 + collapsed: 0 + displayed: 1 + snapOffset: {x: 0, y: 25} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: unity-scene-view-toolbar + index: 0 + layout: 1 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-toolbar__top + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 1 + id: unity-search-toolbar + index: 1 + layout: 1 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 0 + containerId: overlay-container--left + floating: 0 + collapsed: 0 + displayed: 1 + snapOffset: {x: 0, y: 25} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: unity-transform-toolbar + index: 0 + layout: 2 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 0 + containerId: overlay-container--left + floating: 0 + collapsed: 0 + displayed: 1 + snapOffset: {x: 0, y: 197} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: unity-component-tools + index: 1 + layout: 2 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 0 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 1 + snapOffset: {x: 67.5, y: -123} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 2 + id: Orientation + index: 0 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Light Settings + index: 2 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Camera + index: 1 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Cloth Constraints + index: 3 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Cloth Collisions + index: 4 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Navmesh Display + index: 4 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Agent Display + index: 5 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Obstacle Display + index: 6 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Occlusion Culling + index: 5 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Physics Debugger + index: 6 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Scene Visibility + index: 7 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 25} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Particles + index: 8 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Tilemap + index: 11 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Tilemap Palette Helper + index: 12 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 1 + snapOffset: {x: -216, y: -156} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 3 + id: AINavigationOverlay + index: 9 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 48, y: 48} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: APV Overlay + index: 8 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 48, y: 48} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/TrailRenderer + index: 10 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 1 + snapOffset: {x: 48, y: 10} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: UnityEditor.SceneViewCameraOverlay + index: 10 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 48, y: 48} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Open Tile Palette + index: 0 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 48, y: 48} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Tilemap Focus + index: 1 + layout: 4 + size: {x: 0, y: 0} + sizeOverriden: 0 + m_OverlaysVisible: 1 + m_WindowGUID: c9ee0d44a80756c48ba1fd3e952ef1b5 + m_Gizmos: 1 + m_OverrideSceneCullingMask: 6917529027641081856 + m_SceneIsLit: 1 + m_SceneLighting: 1 + m_2DMode: 0 + m_isRotationLocked: 0 + m_PlayAudio: 0 + m_AudioPlay: 0 + m_Position: + m_Target: {x: 0, y: 0, z: 0} + speed: 2 + m_Value: {x: 0, y: 0, z: 0} + m_RenderMode: 0 + m_CameraMode: + drawMode: 0 + name: Shaded + section: Shading Mode + m_ValidateTrueMetals: 0 + m_DoValidateTrueMetals: 0 + m_SceneViewState: + m_AlwaysRefresh: 0 + showFog: 1 + showSkybox: 1 + showFlares: 1 + showImageEffects: 1 + showParticleSystems: 1 + showVisualEffectGraphs: 1 + m_FxEnabled: 1 + m_Grid: + xGrid: + m_Fade: + m_Target: 0 + speed: 2 + m_Value: 0 + m_Color: {r: 0.5, g: 0.5, b: 0.5, a: 0.4} + m_Pivot: {x: 0, y: 0, z: 0} + m_Size: {x: 0, y: 0} + yGrid: + m_Fade: + m_Target: 1 + speed: 2 + m_Value: 1 + m_Color: {r: 0.5, g: 0.5, b: 0.5, a: 0.4} + m_Pivot: {x: 0, y: 0, z: 0} + m_Size: {x: 1, y: 1} + zGrid: + m_Fade: + m_Target: 0 + speed: 2 + m_Value: 0 + m_Color: {r: 0.5, g: 0.5, b: 0.5, a: 0.4} + m_Pivot: {x: 0, y: 0, z: 0} + m_Size: {x: 1, y: 1} + m_ShowGrid: 1 + m_GridAxis: 1 + m_gridOpacity: 0.5 + m_Rotation: + m_Target: {x: -0.08717229, y: 0.89959055, z: -0.21045254, w: -0.3726226} + speed: 2 + m_Value: {x: -0.08717229, y: 0.89959055, z: -0.21045254, w: -0.3726226} + m_Size: + m_Target: 10 + speed: 2 + m_Value: 10 + m_Ortho: + m_Target: 0 + speed: 2 + m_Value: 0 + m_CameraSettings: + m_Speed: 0.8856 + m_SpeedNormalized: 0.44 + m_SpeedMin: 0.01 + m_SpeedMax: 2 + m_EasingEnabled: 1 + m_EasingDuration: 0.4 + m_AccelerationEnabled: 1 + m_FieldOfViewHorizontalOrVertical: 60 + m_NearClip: 0.03 + m_FarClip: 10000 + m_DynamicClip: 1 + m_OcclusionCulling: 0 + m_LastSceneViewRotation: {x: -0.08717229, y: 0.89959055, z: -0.21045254, w: -0.3726226} + m_LastSceneViewOrtho: 0 + m_ReplacementShader: {fileID: 0} + m_ReplacementString: + m_SceneVisActive: 1 + m_LastLockedObject: {fileID: 0} + m_ViewIsLockedToObject: 0 +--- !u!114 &15 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 12070, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_MinSize: {x: 900, y: 216} + m_MaxSize: {x: 4000, y: 4000} + m_TitleContent: + m_Text: Profiler + m_Image: {fileID: -1089619856830078684, guid: 0000000000000000d000000000000000, + type: 0} + m_Tooltip: + m_Pos: + serializedVersion: 2 + x: 0 + y: 806 + width: 1492 + height: 545 + m_SerializedDataModeController: + m_DataMode: 0 + m_PreferredDataMode: 0 + m_SupportedDataModes: + isAutomatic: 1 + m_ViewDataDictionary: {fileID: 0} + m_OverlayCanvas: + m_LastAppliedPresetName: Default + m_SaveData: [] + m_OverlaysVisible: 1 + m_Recording: 1 + m_ActiveNativePlatformSupportModuleName: + m_AllModules: + - rid: 7267259948770525184 + - rid: 7267259948770525185 + - rid: 7267259948770525186 + - rid: 7267259948770525187 + - rid: 7267259948770525188 + - rid: 7267259948770525189 + - rid: 7267259948770525190 + - rid: 7267259948770525191 + - rid: 7267259948770525192 + - rid: 7267259948770525193 + - rid: 7267259948770525194 + - rid: 7267259948770525195 + - rid: 7267259948770525196 + - rid: 7267259948770525197 + m_CallstackRecordMode: 1 + m_ClearOnPlay: 0 + references: + version: 2 + RefIds: + - rid: 7267259948770525184 + type: {class: CPUProfilerModule, ns: UnityEditorInternal.Profiling, asm: UnityEditor.CoreModule} + data: + m_Identifier: UnityEditorInternal.Profiling.CPUProfilerModule, UnityEditor.CoreModule, + Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + m_PaneScroll: {x: 0, y: 0} + m_ViewType: 0 + updateViewLive: 0 + m_CurrentFrameIndex: 1103 + m_HierarchyOverruledThreadFromSelection: 0 + m_ProfilerViewFilteringOptions: 1 + m_FrameDataHierarchyView: + m_Serialized: 1 + m_TreeViewState: + scrollPos: {x: 0, y: 0} + m_SelectedIDs: 8b000000 + m_LastClickedID: 139 + m_ExpandedIDs: 040000003a0000008b000000 + m_RenameOverlay: + m_UserAcceptedRename: 0 + m_Name: + m_OriginalName: + m_EditFieldRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 0 + height: 0 + m_UserData: 0 + m_IsWaitingForDelay: 0 + m_IsRenaming: 0 + m_OriginalEventType: 11 + m_IsRenamingFilename: 0 + m_ClientGUIView: {fileID: 0} + m_SearchString: + m_MultiColumnHeaderState: + m_Columns: + - width: 200 + sortedAscending: 1 + headerContent: + m_Text: Overview + m_Image: {fileID: 0} + m_Tooltip: + contextMenuText: + headerTextAlignment: 0 + sortingArrowAlignment: 2 + minWidth: 200 + maxWidth: 1000000 + autoResize: 1 + allowToggleVisibility: 0 + canSort: 1 + userData: 0 + - width: 80 + sortedAscending: 0 + headerContent: + m_Text: Total + m_Image: {fileID: 0} + m_Tooltip: + contextMenuText: + headerTextAlignment: 0 + sortingArrowAlignment: 2 + minWidth: 50 + maxWidth: 1000000 + autoResize: 0 + allowToggleVisibility: 1 + canSort: 1 + userData: 0 + - width: 80 + sortedAscending: 0 + headerContent: + m_Text: Self + m_Image: {fileID: 0} + m_Tooltip: + contextMenuText: + headerTextAlignment: 0 + sortingArrowAlignment: 2 + minWidth: 50 + maxWidth: 1000000 + autoResize: 0 + allowToggleVisibility: 1 + canSort: 1 + userData: 0 + - width: 80 + sortedAscending: 0 + headerContent: + m_Text: Calls + m_Image: {fileID: 0} + m_Tooltip: + contextMenuText: + headerTextAlignment: 0 + sortingArrowAlignment: 2 + minWidth: 50 + maxWidth: 1000000 + autoResize: 0 + allowToggleVisibility: 1 + canSort: 1 + userData: 0 + - width: 80 + sortedAscending: 0 + headerContent: + m_Text: GC Alloc + m_Image: {fileID: 0} + m_Tooltip: + contextMenuText: + headerTextAlignment: 0 + sortingArrowAlignment: 2 + minWidth: 50 + maxWidth: 1000000 + autoResize: 0 + allowToggleVisibility: 1 + canSort: 1 + userData: 0 + - width: 80 + sortedAscending: 0 + headerContent: + m_Text: Time ms + m_Image: {fileID: 0} + m_Tooltip: + contextMenuText: + headerTextAlignment: 0 + sortingArrowAlignment: 2 + minWidth: 50 + maxWidth: 1000000 + autoResize: 0 + allowToggleVisibility: 1 + canSort: 1 + userData: 0 + - width: 80 + sortedAscending: 0 + headerContent: + m_Text: Self ms + m_Image: {fileID: 0} + m_Tooltip: + contextMenuText: + headerTextAlignment: 0 + sortingArrowAlignment: 2 + minWidth: 50 + maxWidth: 1000000 + autoResize: 0 + allowToggleVisibility: 1 + canSort: 1 + userData: 0 + - width: 25 + sortedAscending: 0 + headerContent: + m_Text: + m_Image: {fileID: -5161429177145976760, guid: 0000000000000000d000000000000000, + type: 0} + m_Tooltip: Warnings + contextMenuText: + headerTextAlignment: 0 + sortingArrowAlignment: 2 + minWidth: 25 + maxWidth: 25 + autoResize: 0 + allowToggleVisibility: 1 + canSort: 1 + userData: 0 + m_VisibleColumns: 0000000001000000020000000300000004000000050000000600000007000000 + m_SortedColumns: 05000000 + m_ThreadIndexInThreadNames: 0 + m_DetailedViewType: 0 + m_DetailedViewSpliterState: + ID: 0 + splitterInitialOffset: 0 + currentActiveSplitter: -1 + realSizes: + - 0 + - 0 + relativeSizes: + - 0.7 + - 0.3 + minSizes: + - 450 + - 50 + maxSizes: + - 0 + - 0 + lastTotalSize: 0 + splitSize: 6 + xOffset: 0 + m_Version: 1 + oldRealSizes: + oldMinSizes: + oldMaxSizes: + oldSplitSize: 0 + m_DetailedObjectsView: + m_SelectedID: -1 + m_TreeViewState: + scrollPos: {x: 0, y: 0} + m_SelectedIDs: + m_LastClickedID: 0 + m_ExpandedIDs: + m_RenameOverlay: + m_UserAcceptedRename: 0 + m_Name: + m_OriginalName: + m_EditFieldRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 0 + height: 0 + m_UserData: 0 + m_IsWaitingForDelay: 0 + m_IsRenaming: 0 + m_OriginalEventType: 11 + m_IsRenamingFilename: 0 + m_ClientGUIView: {fileID: 0} + m_SearchString: + m_MultiColumnHeaderState: + m_Columns: [] + m_VisibleColumns: + m_SortedColumns: + m_VertSplit: + ID: 0 + splitterInitialOffset: 0 + currentActiveSplitter: 0 + realSizes: [] + relativeSizes: [] + minSizes: [] + maxSizes: [] + lastTotalSize: 0 + splitSize: 0 + xOffset: 0 + m_Version: 1 + oldRealSizes: + oldMinSizes: + oldMaxSizes: + oldSplitSize: 0 + m_DetailedCallsView: + m_SelectedID: -1 + m_VertSplit: + ID: 0 + splitterInitialOffset: 0 + currentActiveSplitter: 0 + realSizes: [] + relativeSizes: [] + minSizes: [] + maxSizes: [] + lastTotalSize: 0 + splitSize: 0 + xOffset: 0 + m_Version: 1 + oldRealSizes: + oldMinSizes: + oldMaxSizes: + oldSplitSize: 0 + m_CalleesTreeView: + m_ViewState: + scrollPos: {x: 0, y: 0} + m_SelectedIDs: + m_LastClickedID: 0 + m_ExpandedIDs: + m_RenameOverlay: + m_UserAcceptedRename: 0 + m_Name: + m_OriginalName: + m_EditFieldRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 0 + height: 0 + m_UserData: 0 + m_IsWaitingForDelay: 0 + m_IsRenaming: 0 + m_OriginalEventType: 11 + m_IsRenamingFilename: 0 + m_ClientGUIView: {fileID: 0} + m_SearchString: + m_ViewHeaderState: + m_Columns: + - width: 150 + sortedAscending: 1 + headerContent: + m_Text: Called From + m_Image: {fileID: 0} + m_Tooltip: 'Parents the selected function is called from + + + (Press + ''F'' for frame selection)' + contextMenuText: + headerTextAlignment: 0 + sortingArrowAlignment: 2 + minWidth: 150 + maxWidth: 1000000 + autoResize: 1 + allowToggleVisibility: 0 + canSort: 1 + userData: 0 + - width: 60 + sortedAscending: 0 + headerContent: + m_Text: Calls + m_Image: {fileID: 0} + m_Tooltip: Total number of calls in a selected frame + contextMenuText: + headerTextAlignment: 0 + sortingArrowAlignment: 2 + minWidth: 60 + maxWidth: 1000000 + autoResize: 0 + allowToggleVisibility: 1 + canSort: 1 + userData: 0 + - width: 60 + sortedAscending: 0 + headerContent: + m_Text: GC Alloc + m_Image: {fileID: 0} + m_Tooltip: + contextMenuText: + headerTextAlignment: 0 + sortingArrowAlignment: 2 + minWidth: 60 + maxWidth: 1000000 + autoResize: 0 + allowToggleVisibility: 1 + canSort: 1 + userData: 0 + - width: 60 + sortedAscending: 0 + headerContent: + m_Text: Time ms + m_Image: {fileID: 0} + m_Tooltip: Total time the selected function spends within a parent + contextMenuText: + headerTextAlignment: 0 + sortingArrowAlignment: 2 + minWidth: 60 + maxWidth: 1000000 + autoResize: 0 + allowToggleVisibility: 1 + canSort: 1 + userData: 0 + - width: 60 + sortedAscending: 0 + headerContent: + m_Text: Time % + m_Image: {fileID: 0} + m_Tooltip: Shows how often the selected function was called from + the parent call + contextMenuText: + headerTextAlignment: 0 + sortingArrowAlignment: 2 + minWidth: 60 + maxWidth: 1000000 + autoResize: 0 + allowToggleVisibility: 1 + canSort: 1 + userData: 0 + m_VisibleColumns: 0000000001000000020000000300000004000000 + m_SortedColumns: 03000000 + m_CallersTreeView: + m_ViewState: + scrollPos: {x: 0, y: 0} + m_SelectedIDs: + m_LastClickedID: 0 + m_ExpandedIDs: + m_RenameOverlay: + m_UserAcceptedRename: 0 + m_Name: + m_OriginalName: + m_EditFieldRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 0 + height: 0 + m_UserData: 0 + m_IsWaitingForDelay: 0 + m_IsRenaming: 0 + m_OriginalEventType: 11 + m_IsRenamingFilename: 0 + m_ClientGUIView: {fileID: 0} + m_SearchString: + m_ViewHeaderState: + m_Columns: + - width: 150 + sortedAscending: 1 + headerContent: + m_Text: Called From + m_Image: {fileID: 0} + m_Tooltip: 'Parents the selected function is called from + + + (Press + ''F'' for frame selection)' + contextMenuText: + headerTextAlignment: 0 + sortingArrowAlignment: 2 + minWidth: 150 + maxWidth: 1000000 + autoResize: 1 + allowToggleVisibility: 0 + canSort: 1 + userData: 0 + - width: 60 + sortedAscending: 0 + headerContent: + m_Text: Calls + m_Image: {fileID: 0} + m_Tooltip: Total number of calls in a selected frame + contextMenuText: + headerTextAlignment: 0 + sortingArrowAlignment: 2 + minWidth: 60 + maxWidth: 1000000 + autoResize: 0 + allowToggleVisibility: 1 + canSort: 1 + userData: 0 + - width: 60 + sortedAscending: 0 + headerContent: + m_Text: GC Alloc + m_Image: {fileID: 0} + m_Tooltip: + contextMenuText: + headerTextAlignment: 0 + sortingArrowAlignment: 2 + minWidth: 60 + maxWidth: 1000000 + autoResize: 0 + allowToggleVisibility: 1 + canSort: 1 + userData: 0 + - width: 60 + sortedAscending: 0 + headerContent: + m_Text: Time ms + m_Image: {fileID: 0} + m_Tooltip: Total time the selected function spends within a parent + contextMenuText: + headerTextAlignment: 0 + sortingArrowAlignment: 2 + minWidth: 60 + maxWidth: 1000000 + autoResize: 0 + allowToggleVisibility: 1 + canSort: 1 + userData: 0 + - width: 60 + sortedAscending: 0 + headerContent: + m_Text: Time % + m_Image: {fileID: 0} + m_Tooltip: Shows how often the selected function was called from + the parent call + contextMenuText: + headerTextAlignment: 0 + sortingArrowAlignment: 2 + minWidth: 60 + maxWidth: 1000000 + autoResize: 0 + allowToggleVisibility: 1 + canSort: 1 + userData: 0 + m_VisibleColumns: 0000000001000000020000000300000004000000 + m_SortedColumns: 03000000 + m_FullThreadName: Main Thread + m_ThreadName: Main Thread + k__BackingField: 1059876 + k__BackingField: 0 + m_GroupName: + - rid: 7267259948770525185 + type: {class: GPUProfilerModule, ns: UnityEditorInternal.Profiling, asm: UnityEditor.CoreModule} + data: + m_Identifier: UnityEditorInternal.Profiling.GPUProfilerModule, UnityEditor.CoreModule, + Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + m_PaneScroll: {x: 0, y: 0} + m_ViewType: 0 + updateViewLive: 0 + m_CurrentFrameIndex: -1 + m_HierarchyOverruledThreadFromSelection: 0 + m_ProfilerViewFilteringOptions: 1 + m_FrameDataHierarchyView: + m_Serialized: 0 + m_TreeViewState: + scrollPos: {x: 0, y: 0} + m_SelectedIDs: + m_LastClickedID: 0 + m_ExpandedIDs: + m_RenameOverlay: + m_UserAcceptedRename: 0 + m_Name: + m_OriginalName: + m_EditFieldRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 0 + height: 0 + m_UserData: 0 + m_IsWaitingForDelay: 0 + m_IsRenaming: 0 + m_OriginalEventType: 11 + m_IsRenamingFilename: 0 + m_ClientGUIView: {fileID: 0} + m_SearchString: + m_MultiColumnHeaderState: + m_Columns: [] + m_VisibleColumns: + m_SortedColumns: + m_ThreadIndexInThreadNames: 0 + m_DetailedViewType: 0 + m_DetailedViewSpliterState: + ID: 0 + splitterInitialOffset: 0 + currentActiveSplitter: 0 + realSizes: [] + relativeSizes: [] + minSizes: [] + maxSizes: [] + lastTotalSize: 0 + splitSize: 0 + xOffset: 0 + m_Version: 1 + oldRealSizes: + oldMinSizes: + oldMaxSizes: + oldSplitSize: 0 + m_DetailedObjectsView: + m_SelectedID: 0 + m_TreeViewState: + scrollPos: {x: 0, y: 0} + m_SelectedIDs: + m_LastClickedID: 0 + m_ExpandedIDs: + m_RenameOverlay: + m_UserAcceptedRename: 0 + m_Name: + m_OriginalName: + m_EditFieldRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 0 + height: 0 + m_UserData: 0 + m_IsWaitingForDelay: 0 + m_IsRenaming: 0 + m_OriginalEventType: 11 + m_IsRenamingFilename: 0 + m_ClientGUIView: {fileID: 0} + m_SearchString: + m_MultiColumnHeaderState: + m_Columns: [] + m_VisibleColumns: + m_SortedColumns: + m_VertSplit: + ID: 0 + splitterInitialOffset: 0 + currentActiveSplitter: 0 + realSizes: [] + relativeSizes: [] + minSizes: [] + maxSizes: [] + lastTotalSize: 0 + splitSize: 0 + xOffset: 0 + m_Version: 1 + oldRealSizes: + oldMinSizes: + oldMaxSizes: + oldSplitSize: 0 + m_DetailedCallsView: + m_SelectedID: -1 + m_VertSplit: + ID: 0 + splitterInitialOffset: 0 + currentActiveSplitter: 0 + realSizes: [] + relativeSizes: [] + minSizes: [] + maxSizes: [] + lastTotalSize: 0 + splitSize: 0 + xOffset: 0 + m_Version: 1 + oldRealSizes: + oldMinSizes: + oldMaxSizes: + oldSplitSize: 0 + m_CalleesTreeView: + m_ViewState: + scrollPos: {x: 0, y: 0} + m_SelectedIDs: + m_LastClickedID: 0 + m_ExpandedIDs: + m_RenameOverlay: + m_UserAcceptedRename: 0 + m_Name: + m_OriginalName: + m_EditFieldRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 0 + height: 0 + m_UserData: 0 + m_IsWaitingForDelay: 0 + m_IsRenaming: 0 + m_OriginalEventType: 11 + m_IsRenamingFilename: 0 + m_ClientGUIView: {fileID: 0} + m_SearchString: + m_ViewHeaderState: + m_Columns: + - width: 150 + sortedAscending: 1 + headerContent: + m_Text: Called From + m_Image: {fileID: 0} + m_Tooltip: 'Parents the selected function is called from + + + (Press + ''F'' for frame selection)' + contextMenuText: + headerTextAlignment: 0 + sortingArrowAlignment: 2 + minWidth: 150 + maxWidth: 1000000 + autoResize: 1 + allowToggleVisibility: 0 + canSort: 1 + userData: 0 + - width: 60 + sortedAscending: 0 + headerContent: + m_Text: Calls + m_Image: {fileID: 0} + m_Tooltip: Total number of calls in a selected frame + contextMenuText: + headerTextAlignment: 0 + sortingArrowAlignment: 2 + minWidth: 60 + maxWidth: 1000000 + autoResize: 0 + allowToggleVisibility: 1 + canSort: 1 + userData: 0 + - width: 60 + sortedAscending: 0 + headerContent: + m_Text: GC Alloc + m_Image: {fileID: 0} + m_Tooltip: + contextMenuText: + headerTextAlignment: 0 + sortingArrowAlignment: 2 + minWidth: 60 + maxWidth: 1000000 + autoResize: 0 + allowToggleVisibility: 1 + canSort: 1 + userData: 0 + - width: 60 + sortedAscending: 0 + headerContent: + m_Text: Time ms + m_Image: {fileID: 0} + m_Tooltip: Total time the selected function spends within a parent + contextMenuText: + headerTextAlignment: 0 + sortingArrowAlignment: 2 + minWidth: 60 + maxWidth: 1000000 + autoResize: 0 + allowToggleVisibility: 1 + canSort: 1 + userData: 0 + - width: 60 + sortedAscending: 0 + headerContent: + m_Text: Time % + m_Image: {fileID: 0} + m_Tooltip: Shows how often the selected function was called from + the parent call + contextMenuText: + headerTextAlignment: 0 + sortingArrowAlignment: 2 + minWidth: 60 + maxWidth: 1000000 + autoResize: 0 + allowToggleVisibility: 1 + canSort: 1 + userData: 0 + m_VisibleColumns: 0000000001000000020000000300000004000000 + m_SortedColumns: 03000000 + m_CallersTreeView: + m_ViewState: + scrollPos: {x: 0, y: 0} + m_SelectedIDs: + m_LastClickedID: 0 + m_ExpandedIDs: + m_RenameOverlay: + m_UserAcceptedRename: 0 + m_Name: + m_OriginalName: + m_EditFieldRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 0 + height: 0 + m_UserData: 0 + m_IsWaitingForDelay: 0 + m_IsRenaming: 0 + m_OriginalEventType: 11 + m_IsRenamingFilename: 0 + m_ClientGUIView: {fileID: 0} + m_SearchString: + m_ViewHeaderState: + m_Columns: + - width: 150 + sortedAscending: 1 + headerContent: + m_Text: Called From + m_Image: {fileID: 0} + m_Tooltip: 'Parents the selected function is called from + + + (Press + ''F'' for frame selection)' + contextMenuText: + headerTextAlignment: 0 + sortingArrowAlignment: 2 + minWidth: 150 + maxWidth: 1000000 + autoResize: 1 + allowToggleVisibility: 0 + canSort: 1 + userData: 0 + - width: 60 + sortedAscending: 0 + headerContent: + m_Text: Calls + m_Image: {fileID: 0} + m_Tooltip: Total number of calls in a selected frame + contextMenuText: + headerTextAlignment: 0 + sortingArrowAlignment: 2 + minWidth: 60 + maxWidth: 1000000 + autoResize: 0 + allowToggleVisibility: 1 + canSort: 1 + userData: 0 + - width: 60 + sortedAscending: 0 + headerContent: + m_Text: GC Alloc + m_Image: {fileID: 0} + m_Tooltip: + contextMenuText: + headerTextAlignment: 0 + sortingArrowAlignment: 2 + minWidth: 60 + maxWidth: 1000000 + autoResize: 0 + allowToggleVisibility: 1 + canSort: 1 + userData: 0 + - width: 60 + sortedAscending: 0 + headerContent: + m_Text: Time ms + m_Image: {fileID: 0} + m_Tooltip: Total time the selected function spends within a parent + contextMenuText: + headerTextAlignment: 0 + sortingArrowAlignment: 2 + minWidth: 60 + maxWidth: 1000000 + autoResize: 0 + allowToggleVisibility: 1 + canSort: 1 + userData: 0 + - width: 60 + sortedAscending: 0 + headerContent: + m_Text: Time % + m_Image: {fileID: 0} + m_Tooltip: Shows how often the selected function was called from + the parent call + contextMenuText: + headerTextAlignment: 0 + sortingArrowAlignment: 2 + minWidth: 60 + maxWidth: 1000000 + autoResize: 0 + allowToggleVisibility: 1 + canSort: 1 + userData: 0 + m_VisibleColumns: 0000000001000000020000000300000004000000 + m_SortedColumns: 03000000 + m_FullThreadName: Main Thread + m_ThreadName: Main Thread + k__BackingField: 0 + k__BackingField: -1 + m_GroupName: + - rid: 7267259948770525186 + type: {class: RenderingProfilerModule, ns: UnityEditorInternal.Profiling, asm: UnityEditor.CoreModule} + data: + m_Identifier: UnityEditorInternal.Profiling.RenderingProfilerModule, UnityEditor.CoreModule, + Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + m_PaneScroll: {x: 0, y: 0} + - rid: 7267259948770525187 + type: {class: MemoryProfilerModule, ns: UnityEditorInternal.Profiling, asm: UnityEditor.CoreModule} + data: + m_Identifier: UnityEditorInternal.Profiling.MemoryProfilerModule, UnityEditor.CoreModule, + Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + m_PaneScroll: {x: 0, y: 0} + m_ViewSplit: + ID: 0 + splitterInitialOffset: 0 + currentActiveSplitter: -1 + realSizes: + - 0 + - 0 + relativeSizes: + - 0.7 + - 0.3 + minSizes: + - 450 + - 50 + maxSizes: + - 0 + - 0 + lastTotalSize: 0 + splitSize: 6 + xOffset: 0 + m_Version: 1 + oldRealSizes: + oldMinSizes: + oldMaxSizes: + oldSplitSize: 0 + - rid: 7267259948770525188 + type: {class: AudioProfilerModule, ns: UnityEditorInternal.Profiling, asm: UnityEditor.CoreModule} + data: + m_Identifier: UnityEditorInternal.Profiling.AudioProfilerModule, UnityEditor.CoreModule, + Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + m_PaneScroll: {x: 0, y: 0} + m_ShowInactiveDSPChains: 0 + m_HighlightAudibleDSPChains: 1 + m_DSPGraphZoomFactor: 1 + m_DSPGraphHorizontalLayout: 0 + - rid: 7267259948770525189 + type: {class: VideoProfilerModule, ns: UnityEditorInternal.Profiling, asm: UnityEditor.CoreModule} + data: + m_Identifier: UnityEditorInternal.Profiling.VideoProfilerModule, UnityEditor.CoreModule, + Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + m_PaneScroll: {x: 0, y: 0} + - rid: 7267259948770525190 + type: {class: PhysicsProfilerModule, ns: UnityEditorInternal.Profiling, asm: UnityEditor.CoreModule} + data: + m_Identifier: UnityEditorInternal.Profiling.PhysicsProfilerModule, UnityEditor.CoreModule, + Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + m_PaneScroll: {x: 0, y: 0} + - rid: 7267259948770525191 + type: {class: Physics2DProfilerModule, ns: UnityEditorInternal.Profiling, asm: UnityEditor.CoreModule} + data: + m_Identifier: UnityEditorInternal.Profiling.Physics2DProfilerModule, UnityEditor.CoreModule, + Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + m_PaneScroll: {x: 0, y: 0} + - rid: 7267259948770525192 + type: {class: UIProfilerModule, ns: UnityEditorInternal.Profiling, asm: UnityEditor.CoreModule} + data: + m_Identifier: UnityEditorInternal.Profiling.UIProfilerModule, UnityEditor.CoreModule, + Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + m_PaneScroll: {x: 0, y: 0} + - rid: 7267259948770525193 + type: {class: UIDetailsProfilerModule, ns: UnityEditorInternal.Profiling, asm: UnityEditor.CoreModule} + data: + m_Identifier: UnityEditorInternal.Profiling.UIDetailsProfilerModule, UnityEditor.CoreModule, + Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + m_PaneScroll: {x: 0, y: 0} + - rid: 7267259948770525194 + type: {class: GlobalIlluminationProfilerModule, ns: UnityEditorInternal.Profiling, + asm: UnityEditor.CoreModule} + data: + m_Identifier: UnityEditorInternal.Profiling.GlobalIlluminationProfilerModule, + UnityEditor.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + m_PaneScroll: {x: 0, y: 0} + - rid: 7267259948770525195 + type: {class: VirtualTexturingProfilerModule, ns: UnityEditorInternal.Profiling, + asm: UnityEditor.CoreModule} + data: + m_Identifier: UnityEditorInternal.Profiling.VirtualTexturingProfilerModule, + UnityEditor.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + m_PaneScroll: {x: 0, y: 0} + m_VTProfilerView: + rid: 7267259948770525198 + - rid: 7267259948770525196 + type: {class: FileIOProfilerModule, ns: UnityEditorInternal.Profiling, asm: UnityEditor.CoreModule} + data: + m_Identifier: UnityEditorInternal.Profiling.FileIOProfilerModule, UnityEditor.CoreModule, + Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + m_PaneScroll: {x: 0, y: 0} + - rid: 7267259948770525197 + type: {class: AssetLoadingProfilerModule, ns: UnityEditorInternal.Profiling, + asm: UnityEditor.CoreModule} + data: + m_Identifier: UnityEditorInternal.Profiling.AssetLoadingProfilerModule, UnityEditor.CoreModule, + Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + m_PaneScroll: {x: 0, y: 0} + - rid: 7267259948770525198 + type: {class: VirtualTexturingProfilerView, ns: UnityEditor, asm: UnityEditor.CoreModule} + data: + m_SortAscending: 0 + m_SortedColumn: -1 +--- !u!114 &16 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12061, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_MinSize: {x: 200, y: 200} + m_MaxSize: {x: 4000, y: 4000} + m_TitleContent: + m_Text: Hierarchy + m_Image: {fileID: 7966133145522015247, guid: 0000000000000000d000000000000000, + type: 0} + m_Tooltip: + m_Pos: + serializedVersion: 2 + x: 1493 + y: 73 + width: 374 + height: 603 + m_SerializedDataModeController: + m_DataMode: 0 + m_PreferredDataMode: 0 + m_SupportedDataModes: + isAutomatic: 1 + m_ViewDataDictionary: {fileID: 0} + m_OverlayCanvas: + m_LastAppliedPresetName: Default + m_SaveData: [] + m_OverlaysVisible: 1 + m_SceneHierarchy: + m_TreeViewState: + scrollPos: {x: 0, y: 0} + m_SelectedIDs: 887a0000 + m_LastClickedID: 0 + m_ExpandedIDs: 1638ffff1a3cffff + m_RenameOverlay: + m_UserAcceptedRename: 0 + m_Name: + m_OriginalName: + m_EditFieldRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 0 + height: 0 + m_UserData: 0 + m_IsWaitingForDelay: 0 + m_IsRenaming: 0 + m_OriginalEventType: 11 + m_IsRenamingFilename: 0 + m_ClientGUIView: {fileID: 10} + m_SearchString: + m_ExpandedScenes: [] + m_CurrenRootInstanceID: 0 + m_LockTracker: + m_IsLocked: 0 + m_CurrentSortingName: TransformSorting + m_WindowGUID: 5744dfe58afd7b349b2bf692722dc4ff +--- !u!114 &17 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12014, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_MinSize: {x: 230, y: 250} + m_MaxSize: {x: 10000, y: 10000} + m_TitleContent: + m_Text: Project + m_Image: {fileID: -5467254957812901981, guid: 0000000000000000d000000000000000, + type: 0} + m_Tooltip: + m_Pos: + serializedVersion: 2 + x: 1493 + y: 697 + width: 374 + height: 654 + m_SerializedDataModeController: + m_DataMode: 0 + m_PreferredDataMode: 0 + m_SupportedDataModes: + isAutomatic: 1 + m_ViewDataDictionary: {fileID: 0} + m_OverlayCanvas: + m_LastAppliedPresetName: Default + m_SaveData: [] + m_OverlaysVisible: 1 + m_SearchFilter: + m_NameFilter: + m_ClassNames: [] + m_AssetLabels: [] + m_AssetBundleNames: [] + m_ReferencingInstanceIDs: + m_SceneHandles: + m_ShowAllHits: 0 + m_SkipHidden: 0 + m_SearchArea: 1 + m_Folders: + - Assets + m_Globs: [] + m_OriginalText: + m_ImportLogFlags: 0 + m_FilterByTypeIntersection: 0 + m_ViewMode: 0 + m_StartGridSize: 16 + m_LastFolders: [] + m_LastFoldersGridSize: -1 + m_LastProjectPath: E:\Projects\LiteNetLib\LiteNetLibSampleUnity + m_LockTracker: + m_IsLocked: 0 + m_FolderTreeState: + scrollPos: {x: 0, y: 0} + m_SelectedIDs: 0e800000 + m_LastClickedID: 32782 + m_ExpandedIDs: ffffffffd0400000 + m_RenameOverlay: + m_UserAcceptedRename: 0 + m_Name: + m_OriginalName: + m_EditFieldRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 0 + height: 0 + m_UserData: 0 + m_IsWaitingForDelay: 0 + m_IsRenaming: 0 + m_OriginalEventType: 11 + m_IsRenamingFilename: 1 + m_ClientGUIView: {fileID: 0} + m_SearchString: + m_CreateAssetUtility: + m_EndAction: {fileID: 0} + m_InstanceID: 0 + m_Path: + m_Icon: {fileID: 0} + m_ResourceFile: + m_AssetTreeState: + scrollPos: {x: 0, y: 0} + m_SelectedIDs: + m_LastClickedID: 0 + m_ExpandedIDs: ffffffffd0400000 + m_RenameOverlay: + m_UserAcceptedRename: 0 + m_Name: + m_OriginalName: + m_EditFieldRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 0 + height: 0 + m_UserData: 0 + m_IsWaitingForDelay: 0 + m_IsRenaming: 0 + m_OriginalEventType: 11 + m_IsRenamingFilename: 1 + m_ClientGUIView: {fileID: 11} + m_SearchString: + m_CreateAssetUtility: + m_EndAction: {fileID: 0} + m_InstanceID: 0 + m_Path: + m_Icon: {fileID: 0} + m_ResourceFile: + m_ListAreaState: + m_SelectedInstanceIDs: + m_LastClickedInstanceID: 0 + m_HadKeyboardFocusLastEvent: 0 + m_ExpandedInstanceIDs: c6230000d0640000967900008e6d00007674000022740000a66f000000000000687100009c230000f4b70000288000007a5f00008e7d0000ecbc0000346d0000f09c0000c297000026850000f06c0000de7d0000 + m_RenameOverlay: + m_UserAcceptedRename: 0 + m_Name: + m_OriginalName: + m_EditFieldRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 0 + height: 0 + m_UserData: 0 + m_IsWaitingForDelay: 0 + m_IsRenaming: 0 + m_OriginalEventType: 11 + m_IsRenamingFilename: 1 + m_ClientGUIView: {fileID: 11} + m_CreateAssetUtility: + m_EndAction: {fileID: 0} + m_InstanceID: 0 + m_Path: + m_Icon: {fileID: 0} + m_ResourceFile: + m_NewAssetIndexInList: -1 + m_ScrollPosition: {x: 0, y: 0} + m_GridSize: 16 + m_SkipHiddenPackages: 0 + m_DirectoriesAreaWidth: 178 +--- !u!114 &18 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12019, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_MinSize: {x: 275, y: 50} + m_MaxSize: {x: 4000, y: 4000} + m_TitleContent: + m_Text: Inspector + m_Image: {fileID: -2667387946076563598, guid: 0000000000000000d000000000000000, + type: 0} + m_Tooltip: + m_Pos: + serializedVersion: 2 + x: 1869 + y: 73 + width: 690 + height: 1278 + m_SerializedDataModeController: + m_DataMode: 0 + m_PreferredDataMode: 0 + m_SupportedDataModes: + isAutomatic: 1 + m_ViewDataDictionary: {fileID: 0} + m_OverlayCanvas: + m_LastAppliedPresetName: Default + m_SaveData: [] + m_OverlaysVisible: 1 + m_ObjectsLockedBeforeSerialization: [] + m_InstanceIDsLockedBeforeSerialization: + m_PreviewResizer: + m_CachedPref: 526 + m_ControlHash: -371814159 + m_PrefName: Preview_InspectorPreview + m_LastInspectedObjectInstanceID: -1 + m_LastVerticalScrollValue: 0 + m_GlobalObjectId: + m_InspectorMode: 0 + m_LockTracker: + m_IsLocked: 0 + m_PreviewWindow: {fileID: 0} diff --git a/LiteNetLibSampleUnity/UserSettings/Search.settings b/LiteNetLibSampleUnity/UserSettings/Search.settings new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/LiteNetLibSampleUnity/UserSettings/Search.settings @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/README.md b/README.md index ff37c032..85caa020 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,16 @@ -# LiteNetLib 1.0 indev +# LiteNetLib 2 -Lite reliable UDP library for .NET Standard 2.0 (Mono, .NET Core, .NET Framework) +Lite reliable UDP library for .NET Standard 2.1 (Mono, .NET) [![Made in Ukraine](https://img.shields.io/badge/made_in-ukraine-ffd700.svg?labelColor=0057b7)](https://stand-with-ukraine.pp.ua) -[!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/revx) +**HighLevel API Part**: [LiteEntitySystem](https://github.com/RevenantX/LiteEntitySystem) + +**Matrix chat**: [Matrix](https://matrix.to/#/#litenetlib:matrix.org) **Discord chat**: [![Discord](https://img.shields.io/discord/501682175930925058.svg)](https://discord.gg/FATFPdy) -[OLD BRANCH (and examples) for 0.9.x](https://github.com/RevenantX/LiteNetLib/tree/0.9) +[OLD BRANCH (and examples) for 1.x](https://github.com/RevenantX/LiteNetLib/tree/1.x) [Little Game Example on Unity](https://github.com/RevenantX/NetGameExample) @@ -35,7 +37,7 @@ Lite reliable UDP library for .NET Standard 2.0 (Mono, .NET Core, .NET Framework * Different send mechanics * Reliable with order * Reliable without order - * Reliable sequenced (realiable only last packet) + * Reliable sequenced (reliable only last packet) * Ordered but unreliable with duplication prevention * Simple UDP packets without order and reliability * Fast packet serializer [(Usage manual)](https://revenantx.github.io/LiteNetLib/articles/netserializerusage.html) @@ -46,30 +48,40 @@ Lite reliable UDP library for .NET Standard 2.0 (Mono, .NET Core, .NET Framework * UDP NAT hole punching * NTP time requests * Packet loss and latency simulation -* IPv6 support (dual mode) -* Connection statisitcs +* IPv6 support (using separate socket for performance) +* Connection statistics * Multicasting (for discovering hosts in local network) * Unity support +* Support for .NET8 optimized socket calls (much less gc) * Supported platforms: * Windows/Mac/Linux (.NET Framework, Mono, .NET Core, .NET Standard) * Lumin OS (Magic Leap) - * Monogame + * MonoGame * Godot - * Unity 2018.3 (Desktop platforms, Android, iOS, Switch) + * Unity 2021.2 (Desktop platforms, Android, iOS, Switch) + +## Support developer +* [!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/revx) + +* USDT TRC20: `TE5eBgq8SyEeZFKtCgZG9GwL34sANmbc67` + +* USDT BEP20/ERC20: `0x4c0D6DC76c6A6B354f5ec6c9e51893fFC6510d1E` + +* Bitcoin: `bc1q269ecs8r5vnrum5qr5j98sdglhnxlulv0f6egd` ## Unity notes!!! -* Minimal supported Unity is 2018.3. For older Unity versions use [0.9.x library](https://github.com/RevenantX/LiteNetLib/tree/0.9) versions -* Always use library sources instead of precompiled DLL files ( because there are platform specific #ifdefs and workarounds for unity bugs ) +* Minimal supported Unity is 2021.2. For older Unity versions use [1.x library](https://github.com/RevenantX/LiteNetLib/tree/1.x) versions +* Always use library sources or [OpenUPM package](https://openupm.com/packages/com.revenantx.litenetlib/) instead of precompiled DLL files ( because there are platform specific #ifdefs and workarounds for unity bugs ) ## Usage samples ### Client ```csharp -EventBasedNetListener listener = new EventBasedNetListener(); -NetManager client = new NetManager(listener); +var listener = new EventBasedNetListener(); +var client = new NetManager(listener); client.Start(); -client.Connect("localhost" /* host ip or name */, 9050 /* port */, "SomeConnectionKey" /* text key or NetDataWriter */); -listener.NetworkReceiveEvent += (fromPeer, dataReader, deliveryMethod) => +client.Connect("localhost" /* host IP or name */, 9050 /* port */, "SomeConnectionKey" /* text key or NetDataWriter */); +listener.NetworkReceiveEvent += (fromPeer, dataReader, channel, deliveryMethod) => { Console.WriteLine("We got: {0}", dataReader.GetString(100 /* max length of string */)); dataReader.Recycle(); @@ -85,8 +97,8 @@ client.Stop(); ``` ### Server ```csharp -EventBasedNetListener listener = new EventBasedNetListener(); -NetManager server = new NetManager(listener); +var listener = new EventBasedNetListener(); +var server = new NetManager(listener); server.Start(9050 /* port */); listener.ConnectionRequestEvent += request => @@ -99,10 +111,10 @@ listener.ConnectionRequestEvent += request => listener.PeerConnectedEvent += peer => { - Console.WriteLine("We got connection: {0}", peer.EndPoint); // Show peer ip - NetDataWriter writer = new NetDataWriter(); // Create writer class - writer.Put("Hello client!"); // Put some string - peer.Send(writer, DeliveryMethod.ReliableOrdered); // Send with reliability + Console.WriteLine("We got connection: {0}", peer); // Show peer IP + var writer = new NetDataWriter(); // Create writer class + writer.Put("Hello client!"); // Put some string + peer.Send(writer, DeliveryMethod.ReliableOrdered); // Send with reliability }; while (!Console.KeyAvailable) @@ -112,3 +124,7 @@ while (!Console.KeyAvailable) } server.Stop(); ``` + + + + diff --git a/appveyor.yml b/appveyor.yml index 19b1de84..d7204858 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -12,9 +12,8 @@ assembly_info: assembly_file_version: '{version}' build: project: LiteNetLib.sln -test: - assemblies: - - '**\*.Tests.dll' +test_script: + - dotnet test -m:1 "LiteNetLib.Tests\LiteNetLib.Tests.csproj" --configuration Release --no-build artifacts: - path: LiteNetLib/bin/Release/net471 name: LiteNetLib-$(appveyor_build_version) diff --git a/docfx_project/articles/netserializerusage.md b/docfx_project/articles/netserializerusage.md index 20ea8b9b..e18a079b 100644 --- a/docfx_project/articles/netserializerusage.md +++ b/docfx_project/articles/netserializerusage.md @@ -1,13 +1,8 @@ # NetPacketProcessor Fast specialized for network purposes serializer.
-It supports **classes** with **public properties with "get" and "set"** methods or **classes/structs with implemented `INetSerializable`**.
-Serializer adds some overhead in size: 64 bit hash of class name and namespace (8 bytes). All other class fields will be as is in resulting packet. -## Supported property types -```csharp -byte sbyte short ushort int uint long ulong float double bool string char IPEndPoint -``` -Arrays of all this types and custom types also supported
-Enums also supported but work a bit slower than other types +It supports **classes** with **public properties with "get" and "set"** methods or **classes/structs which implements `INetSerializable`**.
+Serializer adds some overhead to packet size: 64 bit hash of class name and namespace (8 bytes). All other class fields will be as is in resulting packet. + ## Serialization speed comparsion Serialization 100000 times of simple structure from [example](https://github.com/RevenantX/LiteNetLib/blob/master/LibSample/SerializerBenchmark.cs) (`NET 4.5`): Serializer|Time|Size @@ -16,29 +11,28 @@ BinaryFormatter|3334 ms|1096 bytes NetSerializer (first run)|45 ms|204 bytes NetSerializer (second run)|37 ms|204 bytes Raw|24 ms|204 bytes -## Packet Example + +## Supported property types ```csharp -class SamplePacket -{ - public string SomeString { get; set; } - public float SomeFloat { get; set; } - public int[] SomeIntArray { get; set; } -} +byte sbyte short ushort int uint long ulong float double bool string char IPEndPoint ``` -## Custom field -NetPacketProcessor doesn't support nested structs or classes.
-But you can register custom type processor.
-That usefull for game engine types such as Vector3 and Quaternion (in Unity3d). +Arrays of all these types (and custom types) are also supported.
+Enums are supported but work a bit slower than other types. + +## Custom types +NetPacketProcessor doesn't support nested structs or classes, but you can register your own custom type processors.
+That useful for game engine types such as Vector3 and Quaternion (in Unity3d). + ```csharp -//Your packet that will be sent over network +// Your packet that will be sent over network class SamplePacket { + // Both property and array are supported public MyType SomeMyType { get; set; } - //Arrays of custom types supported too public MyType[] SomeMyTypes { get; set; } } -//Some custom type (variant 1) +// Some custom type variant 1: Basic struct struct MyType { public int Value1; @@ -60,12 +54,38 @@ struct MyType } ... netPacketProcessor = new NetPacketProcessor(); -netPacketProcessor.RegisterNestedType( MyType.Serialize, MyType.Deserialize ); +netPacketProcessor.RegisterNestedType( MyType.Serialize, MyType.Deserialize ); // Supply Serialization methods +``` + +You can also implement INetSerializable interface: +```csharp +// Some custom type variant 2: INetSerializable struct +struct MyType : INetSerializable +{ + public int Value1; + public string Value2; + + public void Serialize(NetDataWriter writer) + { + writer.Put(Value1); + writer.Put(Value2); + } + + public void Deserialize(NetDataReader reader) + { + Value1 = reader.GetInt(); + Value2 = reader.GetString(); + } +} +... +netPacketProcessor = new NetPacketProcessor(); +netPacketProcessor.RegisterNestedType(); // Serialization handled automatically thanks to INetSerializable ``` -Another variant you can implement INetSerializable interface: + +If you want to use a class instead of a struct you must implement the INetSerializable interface and provide a constructor: ```csharp -//Some custom type (variant 2) -struct SomeMyType : INetSerializable +// Some custom type variant 3: Class, must implement INetSerializable +class MyType : INetSerializable { public int Value1; public string Value2; @@ -84,58 +104,73 @@ struct SomeMyType : INetSerializable } ... netPacketProcessor = new NetPacketProcessor(); -netPacketProcessor.RegisterNestedType(); +netPacketProcessor.RegisterNestedType(() => { return new SomeMyType(); }); // Must provide constructor ``` -Or if you want use struct instead of class (and implement INetSerializable interface) -you must provide constructor: + +## Usage example +For full example look at source [SerializerBenchmark](https://github.com/RevenantX/LiteNetLib/blob/master/LibSample/SerializerBenchmark.cs) + +### Packet ```csharp -netPacketProcessor.RegisterNestedType(() => { return new SomeMyType(); }); +class SamplePacket +{ + // All of these will be automatically serialized and deserialized + public string SomeString { get; set; } + public float SomeFloat { get; set; } + public int[] SomeIntArray { get; set; } +} ``` -## Usage example (for full example look at source [SerializerBenchmark](https://github.com/RevenantX/LiteNetLib/blob/master/LibSample/SerializerBenchmark.cs)) + +### Sending / recieving ```csharp -//First side +// Client class SomeClientListener : INetEventListener { private readonly NetPacketProcessor _netPacketProcessor = new NetPacketProcessor(); ... public void OnPeerConnected(NetPeer peer) { - SamplePacket sp = new SamplePacket + // After connection is established you will have the server as a NetPeer + SamplePacket packet = new SamplePacket { SomeFloat = 3.42f, SomeIntArray = new[] {6, 5, 4}, SomeString = "Test String", } - peer.Send(_netPacketProcessor.Write(sp), DeliveryMethod.ReliableOrdered); - //or you can use _netPacketProcessor.Send(peer, sp, DeliveryMethod.ReliableOrdered); + // Serialize the packet with NetSerializer and send it to the peer (server) + peer.Send(_netPacketProcessor.Write(packet), DeliveryMethod.ReliableOrdered); + //You can also use _netPacketProcessor.Send(peer, packet, DeliveryMethod.ReliableOrdered); } } -//Other side +// Server class SomeServerListener : INetEventListener { private readonly NetPacketProcessor _netPacketProcessor = new NetPacketProcessor(); public SomeServerListener() { - //Subscribe to packet receiving + // Subscribe to recieving packets. _netPacketProcessor.SubscribeReusable(OnSamplePacketReceived); } + // Handler for SamplePacket, registered in constructor above private void OnSamplePacketReceived(SamplePacket samplePacket, NetPeer peer) { Console.WriteLine("[Server] ReceivedPacket:\n" + samplePacket.SomeString); } + // INetEventListener function. public void OnNetworkReceive(NetPeer peer, NetPacketReader reader, DeliveryMethod deliveryMethod) { Console.WriteLine("[Server] received data. Processing..."); + // Deserializes packet and calls the handler registered in constructor _netPacketProcessor.ReadAllPackets(reader, peer); } } ``` ### Mini FAQ - -Q: `NetPacketProcessor` throws "`Undefined packet in NetDataReader`" but all packets registered
-A: check that registered packet classes in same namespace (and better use shared code for packets) \ No newline at end of file +Q: `NetPacketProcessor` throws "`Undefined packet in NetDataReader`" but all packets are registered.
+A: This can happen when packet definitions resides in different namespaces. Check that registered packet classes/structs are in the same namespace on both ends. +To avoid this error altogether, use shared code/-assembly for packets. diff --git a/docfx_project/docfx.json b/docfx_project/docfx.json index 65314cb6..1f4d7ffd 100644 --- a/docfx_project/docfx.json +++ b/docfx_project/docfx.json @@ -12,7 +12,7 @@ "disableDefaultFilter": false, "filter": "filterConfig.yml", "properties": { - "TargetFramework": "net35" + "TargetFramework": "netstandard2.1" } } ], diff --git a/docfx_project/index.md b/docfx_project/index.md index e56e5f61..110827a2 100644 --- a/docfx_project/index.md +++ b/docfx_project/index.md @@ -113,10 +113,10 @@ server.Stop(); * (including library internal keepalive packets) * default value: **5000 msec**. * **SimulatePacketLoss** - * simulate packet loss by dropping random amout of packets. (Works only in DEBUG mode) + * simulate packet loss by dropping random amout of packets. (Works only in DEBUG builds or when SIMULATE_NETWORK is defined) * default value: **false** * **SimulateLatency** - * simulate latency by holding packets for random time. (Works only in DEBUG mode) + * simulate latency by holding packets for random time. (Works only in DEBUG builds or when SIMULATE_NETWORK is defined) * default value: **false** * **SimulationPacketLossChance** * chance of packet loss when simulation enabled. value in percents. diff --git a/docs/api/LiteNetLib.ConnectionRequest.html b/docs/api/LiteNetLib.ConnectionRequest.html index 84481e72..1c60018b 100644 --- a/docs/api/LiteNetLib.ConnectionRequest.html +++ b/docs/api/LiteNetLib.ConnectionRequest.html @@ -1,23 +1,21 @@ - + - + - - Class ConnectionRequest - - - - + + Class ConnectionRequest + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- - + + diff --git a/docs/api/LiteNetLib.ConnectionState.html b/docs/api/LiteNetLib.ConnectionState.html index b83e1c6b..c6497d19 100644 --- a/docs/api/LiteNetLib.ConnectionState.html +++ b/docs/api/LiteNetLib.ConnectionState.html @@ -1,23 +1,21 @@ - + - + - - Enum ConnectionState - - - - + + Enum ConnectionState + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- - + + diff --git a/docs/api/LiteNetLib.DeliveryMethod.html b/docs/api/LiteNetLib.DeliveryMethod.html index 7b0e130d..1d830771 100644 --- a/docs/api/LiteNetLib.DeliveryMethod.html +++ b/docs/api/LiteNetLib.DeliveryMethod.html @@ -1,23 +1,21 @@ - + - + - - Enum DeliveryMethod - - - - + + Enum DeliveryMethod + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- - + + diff --git a/docs/api/LiteNetLib.DisconnectInfo.html b/docs/api/LiteNetLib.DisconnectInfo.html index 09598be6..74879117 100644 --- a/docs/api/LiteNetLib.DisconnectInfo.html +++ b/docs/api/LiteNetLib.DisconnectInfo.html @@ -1,23 +1,21 @@ - + - + - - Struct DisconnectInfo - - - - + + Struct DisconnectInfo + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- - + + diff --git a/docs/api/LiteNetLib.DisconnectReason.html b/docs/api/LiteNetLib.DisconnectReason.html index a9a09512..0a39bf21 100644 --- a/docs/api/LiteNetLib.DisconnectReason.html +++ b/docs/api/LiteNetLib.DisconnectReason.html @@ -1,23 +1,21 @@ - + - + - - Enum DisconnectReason - - - - + + Enum DisconnectReason + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- - + + diff --git a/docs/api/LiteNetLib.EventBasedLiteNetListener.html b/docs/api/LiteNetLib.EventBasedLiteNetListener.html new file mode 100644 index 00000000..9dcfbc72 --- /dev/null +++ b/docs/api/LiteNetLib.EventBasedLiteNetListener.html @@ -0,0 +1,461 @@ + + + + + + + + Class EventBasedLiteNetListener + + + + + + + + + + + + + + + +
+
+ + + + +
+ + + +
+ + + + + + diff --git a/docs/api/LiteNetLib.EventBasedNatPunchListener.html b/docs/api/LiteNetLib.EventBasedNatPunchListener.html index 65f1b003..64a64655 100644 --- a/docs/api/LiteNetLib.EventBasedNatPunchListener.html +++ b/docs/api/LiteNetLib.EventBasedNatPunchListener.html @@ -1,23 +1,21 @@ - + - + - - Class EventBasedNatPunchListener - - - - + + Class EventBasedNatPunchListener + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- - + + diff --git a/docs/api/LiteNetLib.ILiteNetEventListener.html b/docs/api/LiteNetLib.ILiteNetEventListener.html new file mode 100644 index 00000000..09fbcb96 --- /dev/null +++ b/docs/api/LiteNetLib.ILiteNetEventListener.html @@ -0,0 +1,435 @@ + + + + + + + + Interface ILiteNetEventListener + + + + + + + + + + + + + + + +
+
+ + + + +
+ + + +
+ + + + + + diff --git a/docs/api/LiteNetLib.INatPunchListener.html b/docs/api/LiteNetLib.INatPunchListener.html index 5816779a..579fcb36 100644 --- a/docs/api/LiteNetLib.INatPunchListener.html +++ b/docs/api/LiteNetLib.INatPunchListener.html @@ -1,23 +1,21 @@ - + - + - - Interface INatPunchListener - - - - + + Interface INatPunchListener + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- - + + diff --git a/docs/api/LiteNetLib.INetEventListener.html b/docs/api/LiteNetLib.INetEventListener.html index 905c0c07..69aea427 100644 --- a/docs/api/LiteNetLib.INetEventListener.html +++ b/docs/api/LiteNetLib.INetEventListener.html @@ -1,23 +1,21 @@ - + - + - - Interface INetEventListener - - - - + + Interface INetEventListener + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- - + + diff --git a/docs/api/LiteNetLib.INetLogger.html b/docs/api/LiteNetLib.INetLogger.html index 3f29f1d8..51136839 100644 --- a/docs/api/LiteNetLib.INetLogger.html +++ b/docs/api/LiteNetLib.INetLogger.html @@ -1,23 +1,21 @@ - + - + - - Interface INetLogger - - - - + + Interface INetLogger + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- - + + diff --git a/docs/api/LiteNetLib.INtpEventListener.html b/docs/api/LiteNetLib.INtpEventListener.html index d996905d..8fcaeeec 100644 --- a/docs/api/LiteNetLib.INtpEventListener.html +++ b/docs/api/LiteNetLib.INtpEventListener.html @@ -1,23 +1,21 @@ - + - + - - Interface INtpEventListener - - - - + + Interface INtpEventListener + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- - + + diff --git a/docs/api/LiteNetLib.IPeerAddressChangedListener.html b/docs/api/LiteNetLib.IPeerAddressChangedListener.html new file mode 100644 index 00000000..9deead5b --- /dev/null +++ b/docs/api/LiteNetLib.IPeerAddressChangedListener.html @@ -0,0 +1,156 @@ + + + + + + + + Interface IPeerAddressChangedListener + + + + + + + + + + + + + + + +
+
+ + + + +
+ + + +
+ + + + + + diff --git a/docs/api/LiteNetLib.IPv6Mode.html b/docs/api/LiteNetLib.IPv6Mode.html index ba96db57..20d8ea81 100644 --- a/docs/api/LiteNetLib.IPv6Mode.html +++ b/docs/api/LiteNetLib.IPv6Mode.html @@ -1,23 +1,21 @@ - + - + - - Enum IPv6Mode - - - - + + Enum IPv6Mode + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- - + + diff --git a/docs/api/LiteNetLib.InvalidPacketException.html b/docs/api/LiteNetLib.InvalidPacketException.html index 9d348638..5a7d107c 100644 --- a/docs/api/LiteNetLib.InvalidPacketException.html +++ b/docs/api/LiteNetLib.InvalidPacketException.html @@ -1,23 +1,21 @@ - + - + - - Class InvalidPacketException - - - - + + Class InvalidPacketException + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- - + + diff --git a/docs/api/LiteNetLib.Layers.Crc32cLayer.html b/docs/api/LiteNetLib.Layers.Crc32cLayer.html index 64851c0d..11ffd257 100644 --- a/docs/api/LiteNetLib.Layers.Crc32cLayer.html +++ b/docs/api/LiteNetLib.Layers.Crc32cLayer.html @@ -1,23 +1,21 @@ - + - + - - Class Crc32cLayer - - - - + + Class Crc32cLayer + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- +
- - + + diff --git a/docs/api/LiteNetLib.Layers.PacketLayerBase.html b/docs/api/LiteNetLib.Layers.PacketLayerBase.html index 6f6b6928..1a8c519a 100644 --- a/docs/api/LiteNetLib.Layers.PacketLayerBase.html +++ b/docs/api/LiteNetLib.Layers.PacketLayerBase.html @@ -1,23 +1,21 @@ - + - + - - Class PacketLayerBase - - - - + + Class PacketLayerBase + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- - + + diff --git a/docs/api/LiteNetLib.Layers.XorEncryptLayer.html b/docs/api/LiteNetLib.Layers.XorEncryptLayer.html index 89614073..6ef948af 100644 --- a/docs/api/LiteNetLib.Layers.XorEncryptLayer.html +++ b/docs/api/LiteNetLib.Layers.XorEncryptLayer.html @@ -1,23 +1,21 @@ - + - + - - Class XorEncryptLayer - - - - + + Class XorEncryptLayer + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- +
- - + + diff --git a/docs/api/LiteNetLib.Layers.html b/docs/api/LiteNetLib.Layers.html index 0f37b55b..573749d7 100644 --- a/docs/api/LiteNetLib.Layers.html +++ b/docs/api/LiteNetLib.Layers.html @@ -1,23 +1,21 @@ - + - + - - Namespace LiteNetLib.Layers - - - - + + Namespace LiteNetLib.Layers + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- - + + diff --git a/docs/api/LiteNetLib.LiteConnectionRequest.html b/docs/api/LiteNetLib.LiteConnectionRequest.html new file mode 100644 index 00000000..68fb5ad5 --- /dev/null +++ b/docs/api/LiteNetLib.LiteConnectionRequest.html @@ -0,0 +1,527 @@ + + + + + + + + Class LiteConnectionRequest + + + + + + + + + + + + + + + +
+
+ + + + +
+ + + +
+ + + + + + diff --git a/docs/api/LiteNetLib.LiteNetManager.NetPeerEnumerator-1.html b/docs/api/LiteNetLib.LiteNetManager.NetPeerEnumerator-1.html new file mode 100644 index 00000000..f927307d --- /dev/null +++ b/docs/api/LiteNetLib.LiteNetManager.NetPeerEnumerator-1.html @@ -0,0 +1,291 @@ + + + + + + + + Struct LiteNetManager.NetPeerEnumerator<T> + + + + + + + + + + + + + + + +
+
+ + + + +
+ + + +
+ + + + + + diff --git a/docs/api/LiteNetLib.LiteNetManager.NetPeerEnumerator.html b/docs/api/LiteNetLib.LiteNetManager.NetPeerEnumerator.html new file mode 100644 index 00000000..2f0abbe7 --- /dev/null +++ b/docs/api/LiteNetLib.LiteNetManager.NetPeerEnumerator.html @@ -0,0 +1,276 @@ + + + + + + + + Struct LiteNetManager.NetPeerEnumerator + + + + + + + + + + + + + + + +
+
+ + + + +
+ + + +
+ + + + + + diff --git a/docs/api/LiteNetLib.LiteNetManager.html b/docs/api/LiteNetLib.LiteNetManager.html new file mode 100644 index 00000000..96542635 --- /dev/null +++ b/docs/api/LiteNetLib.LiteNetManager.html @@ -0,0 +1,3715 @@ + + + + + + + + Class LiteNetManager + + + + + + + + + + + + + + + +
+
+ + + + +
+ + + +
+ + + + + + diff --git a/docs/api/LiteNetLib.LiteNetPeer.html b/docs/api/LiteNetLib.LiteNetPeer.html new file mode 100644 index 00000000..d541b653 --- /dev/null +++ b/docs/api/LiteNetLib.LiteNetPeer.html @@ -0,0 +1,1390 @@ + + + + + + + + Class LiteNetPeer + + + + + + + + + + + + + + + +
+
+ + + + +
+ + + +
+ + + + + + diff --git a/docs/api/LiteNetLib.LocalAddrType.html b/docs/api/LiteNetLib.LocalAddrType.html index 7ced653c..e6088603 100644 --- a/docs/api/LiteNetLib.LocalAddrType.html +++ b/docs/api/LiteNetLib.LocalAddrType.html @@ -1,23 +1,21 @@ - + - + - - Enum LocalAddrType - - - - + + Enum LocalAddrType + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- - + + diff --git a/docs/api/LiteNetLib.NatAddressType.html b/docs/api/LiteNetLib.NatAddressType.html index 5a6ed174..feae78d5 100644 --- a/docs/api/LiteNetLib.NatAddressType.html +++ b/docs/api/LiteNetLib.NatAddressType.html @@ -1,23 +1,21 @@ - + - + - - Enum NatAddressType - - - - + + Enum NatAddressType + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- - + + diff --git a/docs/api/LiteNetLib.NatPunchModule.html b/docs/api/LiteNetLib.NatPunchModule.html index 45202fcb..a789e407 100644 --- a/docs/api/LiteNetLib.NatPunchModule.html +++ b/docs/api/LiteNetLib.NatPunchModule.html @@ -1,23 +1,21 @@ - + - + - - Class NatPunchModule - - - - + + Class NatPunchModule + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- - + + diff --git a/docs/api/LiteNetLib.NetConnectRequestPacket.html b/docs/api/LiteNetLib.NetConnectRequestPacket.html new file mode 100644 index 00000000..c39a3bde --- /dev/null +++ b/docs/api/LiteNetLib.NetConnectRequestPacket.html @@ -0,0 +1,270 @@ + + + + + + + + Class NetConnectRequestPacket + + + + + + + + + + + + + + + +
+
+ + + + +
+ + + +
+ + + + + + diff --git a/docs/api/LiteNetLib.NetConstants.html b/docs/api/LiteNetLib.NetConstants.html index c38ecf09..e89a2a94 100644 --- a/docs/api/LiteNetLib.NetConstants.html +++ b/docs/api/LiteNetLib.NetConstants.html @@ -1,23 +1,21 @@ - + - + - - Class NetConstants - - - - + + Class NetConstants + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- - + + diff --git a/docs/api/LiteNetLib.NetDebug.html b/docs/api/LiteNetLib.NetDebug.html index 560c41eb..f6d47a40 100644 --- a/docs/api/LiteNetLib.NetDebug.html +++ b/docs/api/LiteNetLib.NetDebug.html @@ -1,23 +1,21 @@ - + - + - - Class NetDebug - - - - + + Class NetDebug + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- - + + diff --git a/docs/api/LiteNetLib.NetEvent.EType.html b/docs/api/LiteNetLib.NetEvent.EType.html new file mode 100644 index 00000000..40a3bc78 --- /dev/null +++ b/docs/api/LiteNetLib.NetEvent.EType.html @@ -0,0 +1,182 @@ + + + + + + + + Enum NetEvent.EType + + + + + + + + + + + + + + + +
+
+ + + + +
+ + + +
+ + + + + + diff --git a/docs/api/LiteNetLib.NetEvent.html b/docs/api/LiteNetLib.NetEvent.html new file mode 100644 index 00000000..2235de1f --- /dev/null +++ b/docs/api/LiteNetLib.NetEvent.html @@ -0,0 +1,458 @@ + + + + + + + + Class NetEvent + + + + + + + + + + + + + + + +
+
+ + + + +
+ + + +
+ + + + + + diff --git a/docs/api/LiteNetLib.NetLogLevel.html b/docs/api/LiteNetLib.NetLogLevel.html index 383ba184..b8decbe7 100644 --- a/docs/api/LiteNetLib.NetLogLevel.html +++ b/docs/api/LiteNetLib.NetLogLevel.html @@ -1,23 +1,21 @@ - + - + - - Enum NetLogLevel - - - - + + Enum NetLogLevel + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- - + + diff --git a/docs/api/LiteNetLib.NetManager.NetPeerEnumerator.html b/docs/api/LiteNetLib.NetManager.NetPeerEnumerator.html index 1ec7731d..3050dfb0 100644 --- a/docs/api/LiteNetLib.NetManager.NetPeerEnumerator.html +++ b/docs/api/LiteNetLib.NetManager.NetPeerEnumerator.html @@ -1,23 +1,21 @@ - + - + - - Struct NetManager.NetPeerEnumerator - - - - + + Struct NetManager.NetPeerEnumerator + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- - + + diff --git a/docs/api/LiteNetLib.NetManager.html b/docs/api/LiteNetLib.NetManager.html index 2ee25ace..4e240dbe 100644 --- a/docs/api/LiteNetLib.NetManager.html +++ b/docs/api/LiteNetLib.NetManager.html @@ -1,23 +1,21 @@ - + - + - - Class NetManager - - - - + + Class NetManager + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- - + + diff --git a/docs/api/LiteNetLib.NetPacketReader.html b/docs/api/LiteNetLib.NetPacketReader.html index 79fb35a5..df51c9cb 100644 --- a/docs/api/LiteNetLib.NetPacketReader.html +++ b/docs/api/LiteNetLib.NetPacketReader.html @@ -1,23 +1,21 @@ - + - + - - Class NetPacketReader - - - - + + Class NetPacketReader + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- - + + diff --git a/docs/api/LiteNetLib.NetPeer.html b/docs/api/LiteNetLib.NetPeer.html index de7d836b..c8234ae8 100644 --- a/docs/api/LiteNetLib.NetPeer.html +++ b/docs/api/LiteNetLib.NetPeer.html @@ -1,23 +1,21 @@ - + - + - - Class NetPeer - - - - + + Class NetPeer + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- - + + diff --git a/docs/api/LiteNetLib.NetStatistics.html b/docs/api/LiteNetLib.NetStatistics.html index 8cf229a2..19c6e66b 100644 --- a/docs/api/LiteNetLib.NetStatistics.html +++ b/docs/api/LiteNetLib.NetStatistics.html @@ -1,23 +1,21 @@ - + - + - - Class NetStatistics - - - - + + Class NetStatistics + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- - + + diff --git a/docs/api/LiteNetLib.NetUtils.html b/docs/api/LiteNetLib.NetUtils.html index dd868845..18dba861 100644 --- a/docs/api/LiteNetLib.NetUtils.html +++ b/docs/api/LiteNetLib.NetUtils.html @@ -1,23 +1,21 @@ - + - + - - Class NetUtils - - - - + + Class NetUtils + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- - + + diff --git a/docs/api/LiteNetLib.PooledPacket.html b/docs/api/LiteNetLib.PooledPacket.html new file mode 100644 index 00000000..21bc7f4b --- /dev/null +++ b/docs/api/LiteNetLib.PooledPacket.html @@ -0,0 +1,199 @@ + + + + + + + + Struct PooledPacket + + + + + + + + + + + + + + + +
+
+ + + + +
+ + + +
+ + + + + + diff --git a/docs/api/LiteNetLib.TooBigPacketException.html b/docs/api/LiteNetLib.TooBigPacketException.html index 354a6bfe..78a1a988 100644 --- a/docs/api/LiteNetLib.TooBigPacketException.html +++ b/docs/api/LiteNetLib.TooBigPacketException.html @@ -1,23 +1,21 @@ - + - + - - Class TooBigPacketException - - - - + + Class TooBigPacketException + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- - + + diff --git a/docs/api/LiteNetLib.UnconnectedMessageType.html b/docs/api/LiteNetLib.UnconnectedMessageType.html index c4befc01..75c7be41 100644 --- a/docs/api/LiteNetLib.UnconnectedMessageType.html +++ b/docs/api/LiteNetLib.UnconnectedMessageType.html @@ -1,23 +1,21 @@ - + - + - - Enum UnconnectedMessageType - - - - + + Enum UnconnectedMessageType + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- - + + diff --git a/docs/api/LiteNetLib.Utils.CRC32C.html b/docs/api/LiteNetLib.Utils.CRC32C.html index 678ec40c..918c04d1 100644 --- a/docs/api/LiteNetLib.Utils.CRC32C.html +++ b/docs/api/LiteNetLib.Utils.CRC32C.html @@ -1,23 +1,21 @@ - + - + - - Class CRC32C - - - - + + Class CRC32C + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- - + + diff --git a/docs/api/LiteNetLib.Utils.FastBitConverter.html b/docs/api/LiteNetLib.Utils.FastBitConverter.html index 05a0ef1f..ba3f4c92 100644 --- a/docs/api/LiteNetLib.Utils.FastBitConverter.html +++ b/docs/api/LiteNetLib.Utils.FastBitConverter.html @@ -1,23 +1,21 @@ - + - + - - Class FastBitConverter - - - - + + Class FastBitConverter + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- - + + diff --git a/docs/api/LiteNetLib.Utils.INetSerializable.html b/docs/api/LiteNetLib.Utils.INetSerializable.html index 4a7ab59e..f536537a 100644 --- a/docs/api/LiteNetLib.Utils.INetSerializable.html +++ b/docs/api/LiteNetLib.Utils.INetSerializable.html @@ -1,23 +1,21 @@ - + - + - - Interface INetSerializable - - - - + + Interface INetSerializable + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- - + + diff --git a/docs/api/LiteNetLib.Utils.InvalidTypeException.html b/docs/api/LiteNetLib.Utils.InvalidTypeException.html index 3acea234..b5c7a616 100644 --- a/docs/api/LiteNetLib.Utils.InvalidTypeException.html +++ b/docs/api/LiteNetLib.Utils.InvalidTypeException.html @@ -1,23 +1,21 @@ - + - + - - Class InvalidTypeException - - - - + + Class InvalidTypeException + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- - + + diff --git a/docs/api/LiteNetLib.Utils.NetDataReader.html b/docs/api/LiteNetLib.Utils.NetDataReader.html index 5c232c92..4b210e38 100644 --- a/docs/api/LiteNetLib.Utils.NetDataReader.html +++ b/docs/api/LiteNetLib.Utils.NetDataReader.html @@ -1,23 +1,21 @@ - + - + - - Class NetDataReader - - - - + + Class NetDataReader + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- - + + diff --git a/docs/api/LiteNetLib.Utils.NetDataWriter.html b/docs/api/LiteNetLib.Utils.NetDataWriter.html index 069ff244..39a22e04 100644 --- a/docs/api/LiteNetLib.Utils.NetDataWriter.html +++ b/docs/api/LiteNetLib.Utils.NetDataWriter.html @@ -1,23 +1,21 @@ - + - + - - Class NetDataWriter - - - - + + Class NetDataWriter + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- - + + diff --git a/docs/api/LiteNetLib.Utils.NetPacketProcessor.html b/docs/api/LiteNetLib.Utils.NetPacketProcessor.html index 44f2a29f..b80de24b 100644 --- a/docs/api/LiteNetLib.Utils.NetPacketProcessor.html +++ b/docs/api/LiteNetLib.Utils.NetPacketProcessor.html @@ -1,23 +1,21 @@ - + - + - - Class NetPacketProcessor - - - - + + Class NetPacketProcessor + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- - + + diff --git a/docs/api/LiteNetLib.Utils.NetSerializer.html b/docs/api/LiteNetLib.Utils.NetSerializer.html index f9ad25f3..3cc35124 100644 --- a/docs/api/LiteNetLib.Utils.NetSerializer.html +++ b/docs/api/LiteNetLib.Utils.NetSerializer.html @@ -1,23 +1,21 @@ - + - + - - Class NetSerializer - - - - + + Class NetSerializer + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- - + + diff --git a/docs/api/LiteNetLib.Utils.NtpLeapIndicator.html b/docs/api/LiteNetLib.Utils.NtpLeapIndicator.html index d7e798de..6ab9dbab 100644 --- a/docs/api/LiteNetLib.Utils.NtpLeapIndicator.html +++ b/docs/api/LiteNetLib.Utils.NtpLeapIndicator.html @@ -1,23 +1,21 @@ - + - + - - Enum NtpLeapIndicator - - - - + + Enum NtpLeapIndicator + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- - + + diff --git a/docs/api/LiteNetLib.Utils.NtpMode.html b/docs/api/LiteNetLib.Utils.NtpMode.html index d69f08d0..55f84b00 100644 --- a/docs/api/LiteNetLib.Utils.NtpMode.html +++ b/docs/api/LiteNetLib.Utils.NtpMode.html @@ -1,23 +1,21 @@ - + - + - - Enum NtpMode - - - - + + Enum NtpMode + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +
- - + + diff --git a/docs/api/LiteNetLib.Utils.NtpPacket.html b/docs/api/LiteNetLib.Utils.NtpPacket.html index c4708117..d63393a4 100644 --- a/docs/api/LiteNetLib.Utils.NtpPacket.html +++ b/docs/api/LiteNetLib.Utils.NtpPacket.html @@ -1,23 +1,21 @@ - + - + - - Class NtpPacket - - - - + + Class NtpPacket + + - - - - - - + + + + + + + @@ -25,7 +23,7 @@
- + - +